Repository: krahets/hello-algo Branch: main Commit: edd13d4c861b Files: 7125 Total size: 15.6 MB Directory structure: gitextract_4c8t4fps/ ├── .gitattributes ├── .github/ │ ├── pull_request_template.md │ └── workflows/ │ ├── c.yml │ ├── cpp.yml │ ├── dart.yml │ ├── dotnet.yml │ ├── go.yml │ ├── java.yml │ ├── javascript.yml │ ├── kotlin.yml │ ├── python.yml │ ├── ruby.yml │ ├── rust.yml │ ├── swift.yml │ └── typescript.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── codes/ │ ├── Dockerfile │ ├── c/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array.c │ │ │ ├── linked_list.c │ │ │ └── my_list.c │ │ ├── chapter_backtracking/ │ │ │ ├── CMakeLists.txt │ │ │ ├── n_queens.c │ │ │ ├── permutations_i.c │ │ │ ├── permutations_ii.c │ │ │ ├── preorder_traversal_i_compact.c │ │ │ ├── preorder_traversal_ii_compact.c │ │ │ ├── preorder_traversal_iii_compact.c │ │ │ ├── preorder_traversal_iii_template.c │ │ │ ├── subset_sum_i.c │ │ │ ├── subset_sum_i_naive.c │ │ │ └── subset_sum_ii.c │ │ ├── chapter_computational_complexity/ │ │ │ ├── CMakeLists.txt │ │ │ ├── iteration.c │ │ │ ├── recursion.c │ │ │ ├── space_complexity.c │ │ │ ├── time_complexity.c │ │ │ └── worst_best_time_complexity.c │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── CMakeLists.txt │ │ │ ├── binary_search_recur.c │ │ │ ├── build_tree.c │ │ │ └── hanota.c │ │ ├── chapter_dynamic_programming/ │ │ │ ├── CMakeLists.txt │ │ │ ├── climbing_stairs_backtrack.c │ │ │ ├── climbing_stairs_constraint_dp.c │ │ │ ├── climbing_stairs_dfs.c │ │ │ ├── climbing_stairs_dfs_mem.c │ │ │ ├── climbing_stairs_dp.c │ │ │ ├── coin_change.c │ │ │ ├── coin_change_ii.c │ │ │ ├── edit_distance.c │ │ │ ├── knapsack.c │ │ │ ├── min_cost_climbing_stairs_dp.c │ │ │ ├── min_path_sum.c │ │ │ └── unbounded_knapsack.c │ │ ├── chapter_graph/ │ │ │ ├── CMakeLists.txt │ │ │ ├── graph_adjacency_list.c │ │ │ ├── graph_adjacency_list_test.c │ │ │ ├── graph_adjacency_matrix.c │ │ │ ├── graph_bfs.c │ │ │ └── graph_dfs.c │ │ ├── chapter_greedy/ │ │ │ ├── CMakeLists.txt │ │ │ ├── coin_change_greedy.c │ │ │ ├── fractional_knapsack.c │ │ │ ├── max_capacity.c │ │ │ └── max_product_cutting.c │ │ ├── chapter_hashing/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array_hash_map.c │ │ │ ├── hash_map_chaining.c │ │ │ ├── hash_map_open_addressing.c │ │ │ └── simple_hash.c │ │ ├── chapter_heap/ │ │ │ ├── CMakeLists.txt │ │ │ ├── my_heap.c │ │ │ ├── my_heap_test.c │ │ │ └── top_k.c │ │ ├── chapter_searching/ │ │ │ ├── CMakeLists.txt │ │ │ ├── binary_search.c │ │ │ ├── binary_search_edge.c │ │ │ ├── binary_search_insertion.c │ │ │ └── two_sum.c │ │ ├── chapter_sorting/ │ │ │ ├── CMakeLists.txt │ │ │ ├── bubble_sort.c │ │ │ ├── bucket_sort.c │ │ │ ├── counting_sort.c │ │ │ ├── heap_sort.c │ │ │ ├── insertion_sort.c │ │ │ ├── merge_sort.c │ │ │ ├── quick_sort.c │ │ │ ├── radix_sort.c │ │ │ └── selection_sort.c │ │ ├── chapter_stack_and_queue/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array_deque.c │ │ │ ├── array_queue.c │ │ │ ├── array_stack.c │ │ │ ├── linkedlist_deque.c │ │ │ ├── linkedlist_queue.c │ │ │ └── linkedlist_stack.c │ │ ├── chapter_tree/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array_binary_tree.c │ │ │ ├── avl_tree.c │ │ │ ├── binary_search_tree.c │ │ │ ├── binary_tree.c │ │ │ ├── binary_tree_bfs.c │ │ │ └── binary_tree_dfs.c │ │ └── utils/ │ │ ├── CMakeLists.txt │ │ ├── common.h │ │ ├── common_test.c │ │ ├── list_node.h │ │ ├── print_util.h │ │ ├── tree_node.h │ │ ├── uthash.h │ │ ├── vector.h │ │ └── vertex.h │ ├── cpp/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array.cpp │ │ │ ├── linked_list.cpp │ │ │ ├── list.cpp │ │ │ └── my_list.cpp │ │ ├── chapter_backtracking/ │ │ │ ├── CMakeLists.txt │ │ │ ├── n_queens.cpp │ │ │ ├── permutations_i.cpp │ │ │ ├── permutations_ii.cpp │ │ │ ├── preorder_traversal_i_compact.cpp │ │ │ ├── preorder_traversal_ii_compact.cpp │ │ │ ├── preorder_traversal_iii_compact.cpp │ │ │ ├── preorder_traversal_iii_template.cpp │ │ │ ├── subset_sum_i.cpp │ │ │ ├── subset_sum_i_naive.cpp │ │ │ └── subset_sum_ii.cpp │ │ ├── chapter_computational_complexity/ │ │ │ ├── CMakeLists.txt │ │ │ ├── iteration.cpp │ │ │ ├── recursion.cpp │ │ │ ├── space_complexity.cpp │ │ │ ├── time_complexity.cpp │ │ │ └── worst_best_time_complexity.cpp │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── CMakeLists.txt │ │ │ ├── binary_search_recur.cpp │ │ │ ├── build_tree.cpp │ │ │ └── hanota.cpp │ │ ├── chapter_dynamic_programming/ │ │ │ ├── CMakeLists.txt │ │ │ ├── climbing_stairs_backtrack.cpp │ │ │ ├── climbing_stairs_constraint_dp.cpp │ │ │ ├── climbing_stairs_dfs.cpp │ │ │ ├── climbing_stairs_dfs_mem.cpp │ │ │ ├── climbing_stairs_dp.cpp │ │ │ ├── coin_change.cpp │ │ │ ├── coin_change_ii.cpp │ │ │ ├── edit_distance.cpp │ │ │ ├── knapsack.cpp │ │ │ ├── min_cost_climbing_stairs_dp.cpp │ │ │ ├── min_path_sum.cpp │ │ │ └── unbounded_knapsack.cpp │ │ ├── chapter_graph/ │ │ │ ├── CMakeLists.txt │ │ │ ├── graph_adjacency_list.cpp │ │ │ ├── graph_adjacency_list_test.cpp │ │ │ ├── graph_adjacency_matrix.cpp │ │ │ ├── graph_bfs.cpp │ │ │ └── graph_dfs.cpp │ │ ├── chapter_greedy/ │ │ │ ├── CMakeLists.txt │ │ │ ├── coin_change_greedy.cpp │ │ │ ├── fractional_knapsack.cpp │ │ │ ├── max_capacity.cpp │ │ │ └── max_product_cutting.cpp │ │ ├── chapter_hashing/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array_hash_map.cpp │ │ │ ├── array_hash_map_test.cpp │ │ │ ├── built_in_hash.cpp │ │ │ ├── hash_map.cpp │ │ │ ├── hash_map_chaining.cpp │ │ │ ├── hash_map_open_addressing.cpp │ │ │ └── simple_hash.cpp │ │ ├── chapter_heap/ │ │ │ ├── CMakeLists.txt │ │ │ ├── heap.cpp │ │ │ ├── my_heap.cpp │ │ │ └── top_k.cpp │ │ ├── chapter_searching/ │ │ │ ├── CMakeLists.txt │ │ │ ├── binary_search.cpp │ │ │ ├── binary_search_edge.cpp │ │ │ ├── binary_search_insertion.cpp │ │ │ ├── hashing_search.cpp │ │ │ ├── linear_search.cpp │ │ │ └── two_sum.cpp │ │ ├── chapter_sorting/ │ │ │ ├── CMakeLists.txt │ │ │ ├── bubble_sort.cpp │ │ │ ├── bucket_sort.cpp │ │ │ ├── counting_sort.cpp │ │ │ ├── heap_sort.cpp │ │ │ ├── insertion_sort.cpp │ │ │ ├── merge_sort.cpp │ │ │ ├── quick_sort.cpp │ │ │ ├── radix_sort.cpp │ │ │ └── selection_sort.cpp │ │ ├── chapter_stack_and_queue/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array_deque.cpp │ │ │ ├── array_queue.cpp │ │ │ ├── array_stack.cpp │ │ │ ├── deque.cpp │ │ │ ├── linkedlist_deque.cpp │ │ │ ├── linkedlist_queue.cpp │ │ │ ├── linkedlist_stack.cpp │ │ │ ├── queue.cpp │ │ │ └── stack.cpp │ │ ├── chapter_tree/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array_binary_tree.cpp │ │ │ ├── avl_tree.cpp │ │ │ ├── binary_search_tree.cpp │ │ │ ├── binary_tree.cpp │ │ │ ├── binary_tree_bfs.cpp │ │ │ └── binary_tree_dfs.cpp │ │ └── utils/ │ │ ├── CMakeLists.txt │ │ ├── common.hpp │ │ ├── list_node.hpp │ │ ├── print_utils.hpp │ │ ├── tree_node.hpp │ │ └── vertex.hpp │ ├── csharp/ │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── GlobalUsing.cs │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.cs │ │ │ ├── linked_list.cs │ │ │ ├── list.cs │ │ │ └── my_list.cs │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.cs │ │ │ ├── permutations_i.cs │ │ │ ├── permutations_ii.cs │ │ │ ├── preorder_traversal_i_compact.cs │ │ │ ├── preorder_traversal_ii_compact.cs │ │ │ ├── preorder_traversal_iii_compact.cs │ │ │ ├── preorder_traversal_iii_template.cs │ │ │ ├── subset_sum_i.cs │ │ │ ├── subset_sum_i_naive.cs │ │ │ └── subset_sum_ii.cs │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.cs │ │ │ ├── recursion.cs │ │ │ ├── space_complexity.cs │ │ │ ├── time_complexity.cs │ │ │ └── worst_best_time_complexity.cs │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.cs │ │ │ ├── build_tree.cs │ │ │ └── hanota.cs │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.cs │ │ │ ├── climbing_stairs_constraint_dp.cs │ │ │ ├── climbing_stairs_dfs.cs │ │ │ ├── climbing_stairs_dfs_mem.cs │ │ │ ├── climbing_stairs_dp.cs │ │ │ ├── coin_change.cs │ │ │ ├── coin_change_ii.cs │ │ │ ├── edit_distance.cs │ │ │ ├── knapsack.cs │ │ │ ├── min_cost_climbing_stairs_dp.cs │ │ │ ├── min_path_sum.cs │ │ │ └── unbounded_knapsack.cs │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.cs │ │ │ ├── graph_adjacency_matrix.cs │ │ │ ├── graph_bfs.cs │ │ │ └── graph_dfs.cs │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.cs │ │ │ ├── fractional_knapsack.cs │ │ │ ├── max_capacity.cs │ │ │ └── max_product_cutting.cs │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.cs │ │ │ ├── built_in_hash.cs │ │ │ ├── hash_map.cs │ │ │ ├── hash_map_chaining.cs │ │ │ ├── hash_map_open_addressing.cs │ │ │ └── simple_hash.cs │ │ ├── chapter_heap/ │ │ │ ├── heap.cs │ │ │ ├── my_heap.cs │ │ │ └── top_k.cs │ │ ├── chapter_searching/ │ │ │ ├── binary_search.cs │ │ │ ├── binary_search_edge.cs │ │ │ ├── binary_search_insertion.cs │ │ │ ├── hashing_search.cs │ │ │ ├── linear_search.cs │ │ │ └── two_sum.cs │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.cs │ │ │ ├── bucket_sort.cs │ │ │ ├── counting_sort.cs │ │ │ ├── heap_sort.cs │ │ │ ├── insertion_sort.cs │ │ │ ├── merge_sort.cs │ │ │ ├── quick_sort.cs │ │ │ ├── radix_sort.cs │ │ │ └── selection_sort.cs │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.cs │ │ │ ├── array_queue.cs │ │ │ ├── array_stack.cs │ │ │ ├── deque.cs │ │ │ ├── linkedlist_deque.cs │ │ │ ├── linkedlist_queue.cs │ │ │ ├── linkedlist_stack.cs │ │ │ ├── queue.cs │ │ │ └── stack.cs │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.cs │ │ │ ├── avl_tree.cs │ │ │ ├── binary_search_tree.cs │ │ │ ├── binary_tree.cs │ │ │ ├── binary_tree_bfs.cs │ │ │ └── binary_tree_dfs.cs │ │ ├── csharp.sln │ │ ├── hello-algo.csproj │ │ └── utils/ │ │ ├── ListNode.cs │ │ ├── PrintUtil.cs │ │ ├── TreeNode.cs │ │ └── Vertex.cs │ ├── dart/ │ │ ├── build.dart │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.dart │ │ │ ├── linked_list.dart │ │ │ ├── list.dart │ │ │ └── my_list.dart │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.dart │ │ │ ├── permutations_i.dart │ │ │ ├── permutations_ii.dart │ │ │ ├── preorder_traversal_i_compact.dart │ │ │ ├── preorder_traversal_ii_compact.dart │ │ │ ├── preorder_traversal_iii_compact.dart │ │ │ ├── preorder_traversal_iii_template.dart │ │ │ ├── subset_sum_i.dart │ │ │ ├── subset_sum_i_naive.dart │ │ │ └── subset_sum_ii.dart │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.dart │ │ │ ├── recursion.dart │ │ │ ├── space_complexity.dart │ │ │ ├── time_complexity.dart │ │ │ └── worst_best_time_complexity.dart │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.dart │ │ │ ├── build_tree.dart │ │ │ └── hanota.dart │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.dart │ │ │ ├── climbing_stairs_constraint_dp.dart │ │ │ ├── climbing_stairs_dfs.dart │ │ │ ├── climbing_stairs_dfs_mem.dart │ │ │ ├── climbing_stairs_dp.dart │ │ │ ├── coin_change.dart │ │ │ ├── coin_change_ii.dart │ │ │ ├── edit_distance.dart │ │ │ ├── knapsack.dart │ │ │ ├── min_cost_climbing_stairs_dp.dart │ │ │ ├── min_path_sum.dart │ │ │ └── unbounded_knapsack.dart │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.dart │ │ │ ├── graph_adjacency_matrix.dart │ │ │ ├── graph_bfs.dart │ │ │ └── graph_dfs.dart │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.dart │ │ │ ├── fractional_knapsack.dart │ │ │ ├── max_capacity.dart │ │ │ └── max_product_cutting.dart │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.dart │ │ │ ├── built_in_hash.dart │ │ │ ├── hash_map.dart │ │ │ ├── hash_map_chaining.dart │ │ │ ├── hash_map_open_addressing.dart │ │ │ └── simple_hash.dart │ │ ├── chapter_heap/ │ │ │ ├── my_heap.dart │ │ │ └── top_k.dart │ │ ├── chapter_searching/ │ │ │ ├── binary_search.dart │ │ │ ├── binary_search_edge.dart │ │ │ ├── binary_search_insertion.dart │ │ │ ├── hashing_search.dart │ │ │ ├── linear_search.dart │ │ │ └── two_sum.dart │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.dart │ │ │ ├── bucket_sort.dart │ │ │ ├── counting_sort.dart │ │ │ ├── heap_sort.dart │ │ │ ├── insertion_sort.dart │ │ │ ├── merge_sort.dart │ │ │ ├── quick_sort.dart │ │ │ ├── radix_sort.dart │ │ │ └── selection_sort.dart │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.dart │ │ │ ├── array_queue.dart │ │ │ ├── array_stack.dart │ │ │ ├── deque.dart │ │ │ ├── linkedlist_deque.dart │ │ │ ├── linkedlist_queue.dart │ │ │ ├── linkedlist_stack.dart │ │ │ ├── queue.dart │ │ │ └── stack.dart │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.dart │ │ │ ├── avl_tree.dart │ │ │ ├── binary_search_tree.dart │ │ │ ├── binary_tree.dart │ │ │ ├── binary_tree_bfs.dart │ │ │ └── binary_tree_dfs.dart │ │ └── utils/ │ │ ├── list_node.dart │ │ ├── print_util.dart │ │ ├── tree_node.dart │ │ └── vertex.dart │ ├── docker-compose.yml │ ├── go/ │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.go │ │ │ ├── array_test.go │ │ │ ├── linked_list.go │ │ │ ├── linked_list_test.go │ │ │ ├── list_test.go │ │ │ ├── my_list.go │ │ │ └── my_list_test.go │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.go │ │ │ ├── n_queens_test.go │ │ │ ├── permutation_test.go │ │ │ ├── permutations_i.go │ │ │ ├── permutations_ii.go │ │ │ ├── preorder_traversal_i_compact.go │ │ │ ├── preorder_traversal_ii_compact.go │ │ │ ├── preorder_traversal_iii_compact.go │ │ │ ├── preorder_traversal_iii_template.go │ │ │ ├── preorder_traversal_test.go │ │ │ ├── subset_sum_i.go │ │ │ ├── subset_sum_i_naive.go │ │ │ ├── subset_sum_ii.go │ │ │ └── subset_sum_test.go │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.go │ │ │ ├── iteration_test.go │ │ │ ├── recursion.go │ │ │ ├── recursion_test.go │ │ │ ├── space_complexity.go │ │ │ ├── space_complexity_test.go │ │ │ ├── time_complexity.go │ │ │ ├── time_complexity_test.go │ │ │ ├── worst_best_time_complexity.go │ │ │ └── worst_best_time_complexity_test.go │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.go │ │ │ ├── binary_search_recur_test.go │ │ │ ├── build_tree.go │ │ │ ├── build_tree_test.go │ │ │ ├── hanota.go │ │ │ └── hanota_test.go │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.go │ │ │ ├── climbing_stairs_constraint_dp.go │ │ │ ├── climbing_stairs_dfs.go │ │ │ ├── climbing_stairs_dfs_mem.go │ │ │ ├── climbing_stairs_dp.go │ │ │ ├── climbing_stairs_test.go │ │ │ ├── coin_change.go │ │ │ ├── coin_change_ii.go │ │ │ ├── coin_change_test.go │ │ │ ├── edit_distance.go │ │ │ ├── edit_distance_test.go │ │ │ ├── knapsack.go │ │ │ ├── knapsack_test.go │ │ │ ├── min_cost_climbing_stairs_dp.go │ │ │ ├── min_path_sum.go │ │ │ ├── min_path_sum_test.go │ │ │ └── unbounded_knapsack.go │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.go │ │ │ ├── graph_adjacency_list_test.go │ │ │ ├── graph_adjacency_matrix.go │ │ │ ├── graph_adjacency_matrix_test.go │ │ │ ├── graph_bfs.go │ │ │ ├── graph_bfs_test.go │ │ │ ├── graph_dfs.go │ │ │ └── graph_dfs_test.go │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.go │ │ │ ├── coin_change_greedy_test.go │ │ │ ├── fractional_knapsack.go │ │ │ ├── fractional_knapsack_test.go │ │ │ ├── max_capacity.go │ │ │ ├── max_capacity_test.go │ │ │ ├── max_product_cutting.go │ │ │ └── max_product_cutting_test.go │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.go │ │ │ ├── array_hash_map_test.go │ │ │ ├── hash_collision_test.go │ │ │ ├── hash_map_chaining.go │ │ │ ├── hash_map_open_addressing.go │ │ │ ├── hash_map_test.go │ │ │ └── simple_hash.go │ │ ├── chapter_heap/ │ │ │ ├── heap.go │ │ │ ├── heap_test.go │ │ │ ├── my_heap.go │ │ │ └── top_k.go │ │ ├── chapter_searching/ │ │ │ ├── binary_search.go │ │ │ ├── binary_search_edge.go │ │ │ ├── binary_search_insertion.go │ │ │ ├── binary_search_test.go │ │ │ ├── hashing_search.go │ │ │ ├── hashing_search_test.go │ │ │ ├── linear_search.go │ │ │ ├── linear_search_test.go │ │ │ ├── two_sum.go │ │ │ └── two_sum_test.go │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.go │ │ │ ├── bubble_sort_test.go │ │ │ ├── bucket_sort.go │ │ │ ├── bucket_sort_test.go │ │ │ ├── counting_sort.go │ │ │ ├── counting_sort_test.go │ │ │ ├── heap_sort.go │ │ │ ├── heap_sort_test.go │ │ │ ├── insertion_sort.go │ │ │ ├── insertion_sort_test.go │ │ │ ├── merge_sort.go │ │ │ ├── merge_sort_test.go │ │ │ ├── quick_sort.go │ │ │ ├── quick_sort_test.go │ │ │ ├── radix_sort.go │ │ │ ├── radix_sort_test.go │ │ │ ├── selection_sort.go │ │ │ └── selection_sort_test.go │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.go │ │ │ ├── array_queue.go │ │ │ ├── array_stack.go │ │ │ ├── deque_test.go │ │ │ ├── linkedlist_deque.go │ │ │ ├── linkedlist_queue.go │ │ │ ├── linkedlist_stack.go │ │ │ ├── queue_test.go │ │ │ └── stack_test.go │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.go │ │ │ ├── array_binary_tree_test.go │ │ │ ├── avl_tree.go │ │ │ ├── avl_tree_test.go │ │ │ ├── binary_search_tree.go │ │ │ ├── binary_search_tree_test.go │ │ │ ├── binary_tree_bfs.go │ │ │ ├── binary_tree_bfs_test.go │ │ │ ├── binary_tree_dfs.go │ │ │ ├── binary_tree_dfs_test.go │ │ │ └── binary_tree_test.go │ │ ├── go.mod │ │ └── pkg/ │ │ ├── list_node.go │ │ ├── list_node_test.go │ │ ├── print_utils.go │ │ ├── tree_node.go │ │ ├── tree_node_test.go │ │ └── vertex.go │ ├── java/ │ │ ├── .gitignore │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.java │ │ │ ├── linked_list.java │ │ │ ├── list.java │ │ │ └── my_list.java │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.java │ │ │ ├── permutations_i.java │ │ │ ├── permutations_ii.java │ │ │ ├── preorder_traversal_i_compact.java │ │ │ ├── preorder_traversal_ii_compact.java │ │ │ ├── preorder_traversal_iii_compact.java │ │ │ ├── preorder_traversal_iii_template.java │ │ │ ├── subset_sum_i.java │ │ │ ├── subset_sum_i_naive.java │ │ │ └── subset_sum_ii.java │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.java │ │ │ ├── recursion.java │ │ │ ├── space_complexity.java │ │ │ ├── time_complexity.java │ │ │ └── worst_best_time_complexity.java │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.java │ │ │ ├── build_tree.java │ │ │ └── hanota.java │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.java │ │ │ ├── climbing_stairs_constraint_dp.java │ │ │ ├── climbing_stairs_dfs.java │ │ │ ├── climbing_stairs_dfs_mem.java │ │ │ ├── climbing_stairs_dp.java │ │ │ ├── coin_change.java │ │ │ ├── coin_change_ii.java │ │ │ ├── edit_distance.java │ │ │ ├── knapsack.java │ │ │ ├── min_cost_climbing_stairs_dp.java │ │ │ ├── min_path_sum.java │ │ │ └── unbounded_knapsack.java │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.java │ │ │ ├── graph_adjacency_matrix.java │ │ │ ├── graph_bfs.java │ │ │ └── graph_dfs.java │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.java │ │ │ ├── fractional_knapsack.java │ │ │ ├── max_capacity.java │ │ │ └── max_product_cutting.java │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.java │ │ │ ├── built_in_hash.java │ │ │ ├── hash_map.java │ │ │ ├── hash_map_chaining.java │ │ │ ├── hash_map_open_addressing.java │ │ │ └── simple_hash.java │ │ ├── chapter_heap/ │ │ │ ├── heap.java │ │ │ ├── my_heap.java │ │ │ └── top_k.java │ │ ├── chapter_searching/ │ │ │ ├── binary_search.java │ │ │ ├── binary_search_edge.java │ │ │ ├── binary_search_insertion.java │ │ │ ├── hashing_search.java │ │ │ ├── linear_search.java │ │ │ └── two_sum.java │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.java │ │ │ ├── bucket_sort.java │ │ │ ├── counting_sort.java │ │ │ ├── heap_sort.java │ │ │ ├── insertion_sort.java │ │ │ ├── merge_sort.java │ │ │ ├── quick_sort.java │ │ │ ├── radix_sort.java │ │ │ └── selection_sort.java │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.java │ │ │ ├── array_queue.java │ │ │ ├── array_stack.java │ │ │ ├── deque.java │ │ │ ├── linkedlist_deque.java │ │ │ ├── linkedlist_queue.java │ │ │ ├── linkedlist_stack.java │ │ │ ├── queue.java │ │ │ └── stack.java │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.java │ │ │ ├── avl_tree.java │ │ │ ├── binary_search_tree.java │ │ │ ├── binary_tree.java │ │ │ ├── binary_tree_bfs.java │ │ │ └── binary_tree_dfs.java │ │ └── utils/ │ │ ├── ListNode.java │ │ ├── PrintUtil.java │ │ ├── TreeNode.java │ │ └── Vertex.java │ ├── javascript/ │ │ ├── .prettierrc │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.js │ │ │ ├── linked_list.js │ │ │ ├── list.js │ │ │ └── my_list.js │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.js │ │ │ ├── permutations_i.js │ │ │ ├── permutations_ii.js │ │ │ ├── preorder_traversal_i_compact.js │ │ │ ├── preorder_traversal_ii_compact.js │ │ │ ├── preorder_traversal_iii_compact.js │ │ │ ├── preorder_traversal_iii_template.js │ │ │ ├── subset_sum_i.js │ │ │ ├── subset_sum_i_naive.js │ │ │ └── subset_sum_ii.js │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.js │ │ │ ├── recursion.js │ │ │ ├── space_complexity.js │ │ │ ├── time_complexity.js │ │ │ └── worst_best_time_complexity.js │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.js │ │ │ ├── build_tree.js │ │ │ └── hanota.js │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.js │ │ │ ├── climbing_stairs_constraint_dp.js │ │ │ ├── climbing_stairs_dfs.js │ │ │ ├── climbing_stairs_dfs_mem.js │ │ │ ├── climbing_stairs_dp.js │ │ │ ├── coin_change.js │ │ │ ├── coin_change_ii.js │ │ │ ├── edit_distance.js │ │ │ ├── knapsack.js │ │ │ ├── min_cost_climbing_stairs_dp.js │ │ │ ├── min_path_sum.js │ │ │ └── unbounded_knapsack.js │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.js │ │ │ ├── graph_adjacency_matrix.js │ │ │ ├── graph_bfs.js │ │ │ └── graph_dfs.js │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.js │ │ │ ├── fractional_knapsack.js │ │ │ ├── max_capacity.js │ │ │ └── max_product_cutting.js │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.js │ │ │ ├── hash_map.js │ │ │ ├── hash_map_chaining.js │ │ │ ├── hash_map_open_addressing.js │ │ │ └── simple_hash.js │ │ ├── chapter_heap/ │ │ │ ├── my_heap.js │ │ │ └── top_k.js │ │ ├── chapter_searching/ │ │ │ ├── binary_search.js │ │ │ ├── binary_search_edge.js │ │ │ ├── binary_search_insertion.js │ │ │ ├── hashing_search.js │ │ │ ├── linear_search.js │ │ │ └── two_sum.js │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.js │ │ │ ├── bucket_sort.js │ │ │ ├── counting_sort.js │ │ │ ├── heap_sort.js │ │ │ ├── insertion_sort.js │ │ │ ├── merge_sort.js │ │ │ ├── quick_sort.js │ │ │ ├── radix_sort.js │ │ │ └── selection_sort.js │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.js │ │ │ ├── array_queue.js │ │ │ ├── array_stack.js │ │ │ ├── deque.js │ │ │ ├── linkedlist_deque.js │ │ │ ├── linkedlist_queue.js │ │ │ ├── linkedlist_stack.js │ │ │ ├── queue.js │ │ │ └── stack.js │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.js │ │ │ ├── avl_tree.js │ │ │ ├── binary_search_tree.js │ │ │ ├── binary_tree.js │ │ │ ├── binary_tree_bfs.js │ │ │ └── binary_tree_dfs.js │ │ ├── modules/ │ │ │ ├── ListNode.js │ │ │ ├── PrintUtil.js │ │ │ ├── TreeNode.js │ │ │ └── Vertex.js │ │ └── test_all.js │ ├── kotlin/ │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.kt │ │ │ ├── linked_list.kt │ │ │ ├── list.kt │ │ │ └── my_list.kt │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.kt │ │ │ ├── permutations_i.kt │ │ │ ├── permutations_ii.kt │ │ │ ├── preorder_traversal_i_compact.kt │ │ │ ├── preorder_traversal_ii_compact.kt │ │ │ ├── preorder_traversal_iii_compact.kt │ │ │ ├── preorder_traversal_iii_template.kt │ │ │ ├── subset_sum_i.kt │ │ │ ├── subset_sum_i_naive.kt │ │ │ └── subset_sum_ii.kt │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.kt │ │ │ ├── recursion.kt │ │ │ ├── space_complexity.kt │ │ │ ├── time_complexity.kt │ │ │ └── worst_best_time_complexity.kt │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.kt │ │ │ ├── build_tree.kt │ │ │ └── hanota.kt │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.kt │ │ │ ├── climbing_stairs_constraint_dp.kt │ │ │ ├── climbing_stairs_dfs.kt │ │ │ ├── climbing_stairs_dfs_mem.kt │ │ │ ├── climbing_stairs_dp.kt │ │ │ ├── coin_change.kt │ │ │ ├── coin_change_ii.kt │ │ │ ├── edit_distance.kt │ │ │ ├── knapsack.kt │ │ │ ├── min_cost_climbing_stairs_dp.kt │ │ │ ├── min_path_sum.kt │ │ │ └── unbounded_knapsack.kt │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.kt │ │ │ ├── graph_adjacency_matrix.kt │ │ │ ├── graph_bfs.kt │ │ │ └── graph_dfs.kt │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.kt │ │ │ ├── fractional_knapsack.kt │ │ │ ├── max_capacity.kt │ │ │ └── max_product_cutting.kt │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.kt │ │ │ ├── built_in_hash.kt │ │ │ ├── hash_map.kt │ │ │ ├── hash_map_chaining.kt │ │ │ ├── hash_map_open_addressing.kt │ │ │ └── simple_hash.kt │ │ ├── chapter_heap/ │ │ │ ├── heap.kt │ │ │ ├── my_heap.kt │ │ │ └── top_k.kt │ │ ├── chapter_searching/ │ │ │ ├── binary_search.kt │ │ │ ├── binary_search_edge.kt │ │ │ ├── binary_search_insertion.kt │ │ │ ├── hashing_search.kt │ │ │ ├── linear_search.kt │ │ │ └── two_sum.kt │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.kt │ │ │ ├── bucket_sort.kt │ │ │ ├── counting_sort.kt │ │ │ ├── heap_sort.kt │ │ │ ├── insertion_sort.kt │ │ │ ├── merge_sort.kt │ │ │ ├── quick_sort.kt │ │ │ ├── radix_sort.kt │ │ │ └── selection_sort.kt │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.kt │ │ │ ├── array_queue.kt │ │ │ ├── array_stack.kt │ │ │ ├── deque.kt │ │ │ ├── linkedlist_deque.kt │ │ │ ├── linkedlist_queue.kt │ │ │ ├── linkedlist_stack.kt │ │ │ ├── queue.kt │ │ │ └── stack.kt │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.kt │ │ │ ├── avl_tree.kt │ │ │ ├── binary_search_tree.kt │ │ │ ├── binary_tree.kt │ │ │ ├── binary_tree_bfs.kt │ │ │ └── binary_tree_dfs.kt │ │ └── utils/ │ │ ├── ListNode.kt │ │ ├── PrintUtil.kt │ │ ├── TreeNode.kt │ │ └── Vertex.kt │ ├── python/ │ │ ├── .gitignore │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.py │ │ │ ├── linked_list.py │ │ │ ├── list.py │ │ │ └── my_list.py │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.py │ │ │ ├── permutations_i.py │ │ │ ├── permutations_ii.py │ │ │ ├── preorder_traversal_i_compact.py │ │ │ ├── preorder_traversal_ii_compact.py │ │ │ ├── preorder_traversal_iii_compact.py │ │ │ ├── preorder_traversal_iii_template.py │ │ │ ├── subset_sum_i.py │ │ │ ├── subset_sum_i_naive.py │ │ │ └── subset_sum_ii.py │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.py │ │ │ ├── recursion.py │ │ │ ├── space_complexity.py │ │ │ ├── time_complexity.py │ │ │ └── worst_best_time_complexity.py │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.py │ │ │ ├── build_tree.py │ │ │ └── hanota.py │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.py │ │ │ ├── climbing_stairs_constraint_dp.py │ │ │ ├── climbing_stairs_dfs.py │ │ │ ├── climbing_stairs_dfs_mem.py │ │ │ ├── climbing_stairs_dp.py │ │ │ ├── coin_change.py │ │ │ ├── coin_change_ii.py │ │ │ ├── edit_distance.py │ │ │ ├── knapsack.py │ │ │ ├── min_cost_climbing_stairs_dp.py │ │ │ ├── min_path_sum.py │ │ │ └── unbounded_knapsack.py │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.py │ │ │ ├── graph_adjacency_matrix.py │ │ │ ├── graph_bfs.py │ │ │ └── graph_dfs.py │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.py │ │ │ ├── fractional_knapsack.py │ │ │ ├── max_capacity.py │ │ │ └── max_product_cutting.py │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.py │ │ │ ├── built_in_hash.py │ │ │ ├── hash_map.py │ │ │ ├── hash_map_chaining.py │ │ │ ├── hash_map_open_addressing.py │ │ │ └── simple_hash.py │ │ ├── chapter_heap/ │ │ │ ├── heap.py │ │ │ ├── my_heap.py │ │ │ └── top_k.py │ │ ├── chapter_searching/ │ │ │ ├── binary_search.py │ │ │ ├── binary_search_edge.py │ │ │ ├── binary_search_insertion.py │ │ │ ├── hashing_search.py │ │ │ ├── linear_search.py │ │ │ └── two_sum.py │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.py │ │ │ ├── bucket_sort.py │ │ │ ├── counting_sort.py │ │ │ ├── heap_sort.py │ │ │ ├── insertion_sort.py │ │ │ ├── merge_sort.py │ │ │ ├── quick_sort.py │ │ │ ├── radix_sort.py │ │ │ └── selection_sort.py │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.py │ │ │ ├── array_queue.py │ │ │ ├── array_stack.py │ │ │ ├── deque.py │ │ │ ├── linkedlist_deque.py │ │ │ ├── linkedlist_queue.py │ │ │ ├── linkedlist_stack.py │ │ │ ├── queue.py │ │ │ └── stack.py │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.py │ │ │ ├── avl_tree.py │ │ │ ├── binary_search_tree.py │ │ │ ├── binary_tree.py │ │ │ ├── binary_tree_bfs.py │ │ │ └── binary_tree_dfs.py │ │ ├── modules/ │ │ │ ├── __init__.py │ │ │ ├── list_node.py │ │ │ ├── print_util.py │ │ │ ├── tree_node.py │ │ │ └── vertex.py │ │ └── test_all.py │ ├── pythontutor/ │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.md │ │ │ ├── linked_list.md │ │ │ └── my_list.md │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.md │ │ │ ├── permutations_i.md │ │ │ ├── permutations_ii.md │ │ │ ├── preorder_traversal_i_compact.md │ │ │ ├── preorder_traversal_ii_compact.md │ │ │ ├── preorder_traversal_iii_compact.md │ │ │ ├── preorder_traversal_iii_template.md │ │ │ ├── subset_sum_i.md │ │ │ ├── subset_sum_i_naive.md │ │ │ └── subset_sum_ii.md │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.md │ │ │ ├── recursion.md │ │ │ ├── space_complexity.md │ │ │ ├── time_complexity.md │ │ │ └── worst_best_time_complexity.md │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.md │ │ │ ├── build_tree.md │ │ │ └── hanota.md │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.md │ │ │ ├── climbing_stairs_constraint_dp.md │ │ │ ├── climbing_stairs_dfs.md │ │ │ ├── climbing_stairs_dfs_mem.md │ │ │ ├── climbing_stairs_dp.md │ │ │ ├── coin_change.md │ │ │ ├── coin_change_ii.md │ │ │ ├── edit_distance.md │ │ │ ├── knapsack.md │ │ │ ├── min_cost_climbing_stairs_dp.md │ │ │ ├── min_path_sum.md │ │ │ └── unbounded_knapsack.md │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.md │ │ │ ├── graph_adjacency_matrix.md │ │ │ ├── graph_bfs.md │ │ │ └── graph_dfs.md │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.md │ │ │ ├── fractional_knapsack.md │ │ │ ├── max_capacity.md │ │ │ └── max_product_cutting.md │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.md │ │ │ ├── hash_map_chaining.md │ │ │ └── simple_hash.md │ │ ├── chapter_heap/ │ │ │ ├── my_heap.md │ │ │ └── top_k.md │ │ ├── chapter_searching/ │ │ │ ├── binary_search.md │ │ │ ├── binary_search_edge.md │ │ │ ├── binary_search_insertion.md │ │ │ └── two_sum.md │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.md │ │ │ ├── bucket_sort.md │ │ │ ├── counting_sort.md │ │ │ ├── heap_sort.md │ │ │ ├── insertion_sort.md │ │ │ ├── merge_sort.md │ │ │ ├── quick_sort.md │ │ │ ├── radix_sort.md │ │ │ └── selection_sort.md │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_queue.md │ │ │ ├── array_stack.md │ │ │ ├── linkedlist_queue.md │ │ │ └── linkedlist_stack.md │ │ └── chapter_tree/ │ │ ├── array_binary_tree.md │ │ ├── binary_search_tree.md │ │ ├── binary_tree_bfs.md │ │ └── binary_tree_dfs.md │ ├── ruby/ │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.rb │ │ │ ├── linked_list.rb │ │ │ ├── list.rb │ │ │ └── my_list.rb │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.rb │ │ │ ├── permutations_i.rb │ │ │ ├── permutations_ii.rb │ │ │ ├── preorder_traversal_i_compact.rb │ │ │ ├── preorder_traversal_ii_compact.rb │ │ │ ├── preorder_traversal_iii_compact.rb │ │ │ ├── preorder_traversal_iii_template.rb │ │ │ ├── subset_sum_i.rb │ │ │ ├── subset_sum_i_naive.rb │ │ │ └── subset_sum_ii.rb │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.rb │ │ │ ├── recursion.rb │ │ │ ├── space_complexity.rb │ │ │ ├── time_complexity.rb │ │ │ └── worst_best_time_complexity.rb │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.rb │ │ │ ├── build_tree.rb │ │ │ └── hanota.rb │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.rb │ │ │ ├── climbing_stairs_constraint_dp.rb │ │ │ ├── climbing_stairs_dfs.rb │ │ │ ├── climbing_stairs_dfs_mem.rb │ │ │ ├── climbing_stairs_dp.rb │ │ │ ├── coin_change.rb │ │ │ ├── coin_change_ii.rb │ │ │ ├── edit_distance.rb │ │ │ ├── knapsack.rb │ │ │ ├── min_cost_climbing_stairs_dp.rb │ │ │ ├── min_path_sum.rb │ │ │ └── unbounded_knapsack.rb │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.rb │ │ │ ├── graph_adjacency_matrix.rb │ │ │ ├── graph_bfs.rb │ │ │ └── graph_dfs.rb │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.rb │ │ │ ├── fractional_knapsack.rb │ │ │ ├── max_capacity.rb │ │ │ └── max_product_cutting.rb │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.rb │ │ │ ├── built_in_hash.rb │ │ │ ├── hash_map.rb │ │ │ ├── hash_map_chaining.rb │ │ │ ├── hash_map_open_addressing.rb │ │ │ └── simple_hash.rb │ │ ├── chapter_heap/ │ │ │ ├── my_heap.rb │ │ │ └── top_k.rb │ │ ├── chapter_searching/ │ │ │ ├── binary_search.rb │ │ │ ├── binary_search_edge.rb │ │ │ ├── binary_search_insertion.rb │ │ │ ├── hashing_search.rb │ │ │ ├── linear_search.rb │ │ │ └── two_sum.rb │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.rb │ │ │ ├── bucket_sort.rb │ │ │ ├── counting_sort.rb │ │ │ ├── heap_sort.rb │ │ │ ├── insertion_sort.rb │ │ │ ├── merge_sort.rb │ │ │ ├── quick_sort.rb │ │ │ ├── radix_sort.rb │ │ │ └── selection_sort.rb │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.rb │ │ │ ├── array_queue.rb │ │ │ ├── array_stack.rb │ │ │ ├── deque.rb │ │ │ ├── linkedlist_deque.rb │ │ │ ├── linkedlist_queue.rb │ │ │ ├── linkedlist_stack.rb │ │ │ ├── queue.rb │ │ │ └── stack.rb │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.rb │ │ │ ├── avl_tree.rb │ │ │ ├── binary_search_tree.rb │ │ │ ├── binary_tree.rb │ │ │ ├── binary_tree_bfs.rb │ │ │ └── binary_tree_dfs.rb │ │ ├── test_all.rb │ │ └── utils/ │ │ ├── list_node.rb │ │ ├── print_util.rb │ │ ├── tree_node.rb │ │ └── vertex.rb │ ├── rust/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.rs │ │ │ ├── linked_list.rs │ │ │ ├── list.rs │ │ │ └── my_list.rs │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.rs │ │ │ ├── permutations_i.rs │ │ │ ├── permutations_ii.rs │ │ │ ├── preorder_traversal_i_compact.rs │ │ │ ├── preorder_traversal_ii_compact.rs │ │ │ ├── preorder_traversal_iii_compact.rs │ │ │ ├── preorder_traversal_iii_template.rs │ │ │ ├── subset_sum_i.rs │ │ │ ├── subset_sum_i_naive.rs │ │ │ └── subset_sum_ii.rs │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.rs │ │ │ ├── recursion.rs │ │ │ ├── space_complexity.rs │ │ │ ├── time_complexity.rs │ │ │ └── worst_best_time_complexity.rs │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.rs │ │ │ ├── build_tree.rs │ │ │ └── hanota.rs │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.rs │ │ │ ├── climbing_stairs_constraint_dp.rs │ │ │ ├── climbing_stairs_dfs.rs │ │ │ ├── climbing_stairs_dfs_mem.rs │ │ │ ├── climbing_stairs_dp.rs │ │ │ ├── coin_change.rs │ │ │ ├── coin_change_ii.rs │ │ │ ├── edit_distance.rs │ │ │ ├── knapsack.rs │ │ │ ├── min_cost_climbing_stairs_dp.rs │ │ │ ├── min_path_sum.rs │ │ │ └── unbounded_knapsack.rs │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.rs │ │ │ ├── graph_adjacency_matrix.rs │ │ │ ├── graph_bfs.rs │ │ │ └── graph_dfs.rs │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.rs │ │ │ ├── fractional_knapsack.rs │ │ │ ├── max_capacity.rs │ │ │ └── max_product_cutting.rs │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.rs │ │ │ ├── build_in_hash.rs │ │ │ ├── hash_map.rs │ │ │ ├── hash_map_chaining.rs │ │ │ ├── hash_map_open_addressing.rs │ │ │ └── simple_hash.rs │ │ ├── chapter_heap/ │ │ │ ├── heap.rs │ │ │ ├── my_heap.rs │ │ │ └── top_k.rs │ │ ├── chapter_searching/ │ │ │ ├── binary_search.rs │ │ │ ├── binary_search_edge.rs │ │ │ ├── binary_search_insertion.rs │ │ │ ├── hashing_search.rs │ │ │ ├── linear_search.rs │ │ │ └── two_sum.rs │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.rs │ │ │ ├── bucket_sort.rs │ │ │ ├── counting_sort.rs │ │ │ ├── heap_sort.rs │ │ │ ├── insertion_sort.rs │ │ │ ├── merge_sort.rs │ │ │ ├── quick_sort.rs │ │ │ ├── radix_sort.rs │ │ │ └── selection_sort.rs │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.rs │ │ │ ├── array_queue.rs │ │ │ ├── array_stack.rs │ │ │ ├── deque.rs │ │ │ ├── linkedlist_deque.rs │ │ │ ├── linkedlist_queue.rs │ │ │ ├── linkedlist_stack.rs │ │ │ ├── queue.rs │ │ │ └── stack.rs │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.rs │ │ │ ├── avl_tree.rs │ │ │ ├── binary_search_tree.rs │ │ │ ├── binary_tree.rs │ │ │ ├── binary_tree_bfs.rs │ │ │ └── binary_tree_dfs.rs │ │ └── src/ │ │ ├── include/ │ │ │ ├── list_node.rs │ │ │ ├── mod.rs │ │ │ ├── print_util.rs │ │ │ ├── tree_node.rs │ │ │ └── vertex.rs │ │ └── lib.rs │ ├── swift/ │ │ ├── .gitignore │ │ ├── Package.resolved │ │ ├── Package.swift │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.swift │ │ │ ├── linked_list.swift │ │ │ ├── list.swift │ │ │ └── my_list.swift │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.swift │ │ │ ├── permutations_i.swift │ │ │ ├── permutations_ii.swift │ │ │ ├── preorder_traversal_i_compact.swift │ │ │ ├── preorder_traversal_ii_compact.swift │ │ │ ├── preorder_traversal_iii_compact.swift │ │ │ ├── preorder_traversal_iii_template.swift │ │ │ ├── subset_sum_i.swift │ │ │ ├── subset_sum_i_naive.swift │ │ │ └── subset_sum_ii.swift │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.swift │ │ │ ├── recursion.swift │ │ │ ├── space_complexity.swift │ │ │ ├── time_complexity.swift │ │ │ └── worst_best_time_complexity.swift │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.swift │ │ │ ├── build_tree.swift │ │ │ └── hanota.swift │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.swift │ │ │ ├── climbing_stairs_constraint_dp.swift │ │ │ ├── climbing_stairs_dfs.swift │ │ │ ├── climbing_stairs_dfs_mem.swift │ │ │ ├── climbing_stairs_dp.swift │ │ │ ├── coin_change.swift │ │ │ ├── coin_change_ii.swift │ │ │ ├── edit_distance.swift │ │ │ ├── knapsack.swift │ │ │ ├── min_cost_climbing_stairs_dp.swift │ │ │ ├── min_path_sum.swift │ │ │ └── unbounded_knapsack.swift │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.swift │ │ │ ├── graph_adjacency_matrix.swift │ │ │ ├── graph_bfs.swift │ │ │ └── graph_dfs.swift │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.swift │ │ │ ├── fractional_knapsack.swift │ │ │ ├── max_capacity.swift │ │ │ └── max_product_cutting.swift │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.swift │ │ │ ├── built_in_hash.swift │ │ │ ├── hash_map.swift │ │ │ ├── hash_map_chaining.swift │ │ │ ├── hash_map_open_addressing.swift │ │ │ └── simple_hash.swift │ │ ├── chapter_heap/ │ │ │ ├── heap.swift │ │ │ ├── my_heap.swift │ │ │ └── top_k.swift │ │ ├── chapter_searching/ │ │ │ ├── binary_search.swift │ │ │ ├── binary_search_edge.swift │ │ │ ├── binary_search_insertion.swift │ │ │ ├── hashing_search.swift │ │ │ ├── linear_search.swift │ │ │ └── two_sum.swift │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.swift │ │ │ ├── bucket_sort.swift │ │ │ ├── counting_sort.swift │ │ │ ├── heap_sort.swift │ │ │ ├── insertion_sort.swift │ │ │ ├── merge_sort.swift │ │ │ ├── quick_sort.swift │ │ │ ├── radix_sort.swift │ │ │ └── selection_sort.swift │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.swift │ │ │ ├── array_queue.swift │ │ │ ├── array_stack.swift │ │ │ ├── deque.swift │ │ │ ├── linkedlist_deque.swift │ │ │ ├── linkedlist_queue.swift │ │ │ ├── linkedlist_stack.swift │ │ │ ├── queue.swift │ │ │ └── stack.swift │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.swift │ │ │ ├── avl_tree.swift │ │ │ ├── binary_search_tree.swift │ │ │ ├── binary_tree.swift │ │ │ ├── binary_tree_bfs.swift │ │ │ └── binary_tree_dfs.swift │ │ └── utils/ │ │ ├── ListNode.swift │ │ ├── Pair.swift │ │ ├── PrintUtil.swift │ │ ├── TreeNode.swift │ │ └── Vertex.swift │ ├── typescript/ │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.ts │ │ │ ├── linked_list.ts │ │ │ ├── list.ts │ │ │ └── my_list.ts │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.ts │ │ │ ├── permutations_i.ts │ │ │ ├── permutations_ii.ts │ │ │ ├── preorder_traversal_i_compact.ts │ │ │ ├── preorder_traversal_ii_compact.ts │ │ │ ├── preorder_traversal_iii_compact.ts │ │ │ ├── preorder_traversal_iii_template.ts │ │ │ ├── subset_sum_i.ts │ │ │ ├── subset_sum_i_naive.ts │ │ │ └── subset_sum_ii.ts │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.ts │ │ │ ├── recursion.ts │ │ │ ├── space_complexity.ts │ │ │ ├── time_complexity.ts │ │ │ └── worst_best_time_complexity.ts │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.ts │ │ │ ├── build_tree.ts │ │ │ └── hanota.ts │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.ts │ │ │ ├── climbing_stairs_constraint_dp.ts │ │ │ ├── climbing_stairs_dfs.ts │ │ │ ├── climbing_stairs_dfs_mem.ts │ │ │ ├── climbing_stairs_dp.ts │ │ │ ├── coin_change.ts │ │ │ ├── coin_change_ii.ts │ │ │ ├── edit_distance.ts │ │ │ ├── knapsack.ts │ │ │ ├── min_cost_climbing_stairs_dp.ts │ │ │ ├── min_path_sum.ts │ │ │ └── unbounded_knapsack.ts │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.ts │ │ │ ├── graph_adjacency_matrix.ts │ │ │ ├── graph_bfs.ts │ │ │ └── graph_dfs.ts │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.ts │ │ │ ├── fractional_knapsack.ts │ │ │ ├── max_capacity.ts │ │ │ └── max_product_cutting.ts │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.ts │ │ │ ├── hash_map.ts │ │ │ ├── hash_map_chaining.ts │ │ │ ├── hash_map_open_addressing.ts │ │ │ └── simple_hash.ts │ │ ├── chapter_heap/ │ │ │ ├── my_heap.ts │ │ │ └── top_k.ts │ │ ├── chapter_searching/ │ │ │ ├── binary_search.ts │ │ │ ├── binary_search_edge.ts │ │ │ ├── binary_search_insertion.ts │ │ │ ├── hashing_search.ts │ │ │ ├── linear_search.ts │ │ │ └── two_sum.ts │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.ts │ │ │ ├── bucket_sort.ts │ │ │ ├── counting_sort.ts │ │ │ ├── heap_sort.ts │ │ │ ├── insertion_sort.ts │ │ │ ├── merge_sort.ts │ │ │ ├── quick_sort.ts │ │ │ ├── radix_sort.ts │ │ │ └── selection_sort.ts │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.ts │ │ │ ├── array_queue.ts │ │ │ ├── array_stack.ts │ │ │ ├── deque.ts │ │ │ ├── linkedlist_deque.ts │ │ │ ├── linkedlist_queue.ts │ │ │ ├── linkedlist_stack.ts │ │ │ ├── queue.ts │ │ │ └── stack.ts │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.ts │ │ │ ├── avl_tree.ts │ │ │ ├── binary_search_tree.ts │ │ │ ├── binary_tree.ts │ │ │ ├── binary_tree_bfs.ts │ │ │ └── binary_tree_dfs.ts │ │ ├── modules/ │ │ │ ├── ListNode.ts │ │ │ ├── PrintUtil.ts │ │ │ ├── TreeNode.ts │ │ │ └── Vertex.ts │ │ ├── package.json │ │ └── tsconfig.json │ └── zig/ │ ├── .gitignore │ ├── build.zig │ ├── chapter_array_and_linkedlist/ │ │ ├── array.zig │ │ ├── linked_list.zig │ │ ├── list.zig │ │ └── my_list.zig │ ├── chapter_computational_complexity/ │ │ ├── iteration.zig │ │ ├── recursion.zig │ │ ├── space_complexity.zig │ │ ├── time_complexity.zig │ │ └── worst_best_time_complexity.zig │ ├── chapter_dynamic_programming/ │ │ ├── climbing_stairs_backtrack.zig │ │ ├── climbing_stairs_constraint_dp.zig │ │ ├── climbing_stairs_dfs.zig │ │ ├── climbing_stairs_dfs_mem.zig │ │ ├── climbing_stairs_dp.zig │ │ ├── coin_change.zig │ │ ├── coin_change_ii.zig │ │ ├── edit_distance.zig │ │ ├── knapsack.zig │ │ ├── min_cost_climbing_stairs_dp.zig │ │ ├── min_path_sum.zig │ │ └── unbounded_knapsack.zig │ ├── chapter_hashing/ │ │ ├── array_hash_map.zig │ │ └── hash_map.zig │ ├── chapter_heap/ │ │ ├── heap.zig │ │ └── my_heap.zig │ ├── chapter_searching/ │ │ ├── binary_search.zig │ │ ├── hashing_search.zig │ │ ├── linear_search.zig │ │ └── two_sum.zig │ ├── chapter_sorting/ │ │ ├── bubble_sort.zig │ │ ├── insertion_sort.zig │ │ ├── merge_sort.zig │ │ ├── quick_sort.zig │ │ └── radix_sort.zig │ ├── chapter_stack_and_queue/ │ │ ├── array_queue.zig │ │ ├── array_stack.zig │ │ ├── deque.zig │ │ ├── linkedlist_deque.zig │ │ ├── linkedlist_queue.zig │ │ ├── linkedlist_stack.zig │ │ ├── queue.zig │ │ └── stack.zig │ ├── chapter_tree/ │ │ ├── avl_tree.zig │ │ ├── binary_search_tree.zig │ │ ├── binary_tree.zig │ │ ├── binary_tree_bfs.zig │ │ └── binary_tree_dfs.zig │ ├── include/ │ │ ├── PrintUtil.zig │ │ └── include.zig │ ├── main.zig │ └── utils/ │ ├── ListNode.zig │ ├── TreeNode.zig │ ├── format.zig │ └── utils.zig ├── docker-compose.yml ├── docs/ │ ├── chapter_appendix/ │ │ ├── contribution.md │ │ ├── index.md │ │ ├── installation.md │ │ └── terminology.md │ ├── chapter_array_and_linkedlist/ │ │ ├── array.md │ │ ├── index.md │ │ ├── linked_list.md │ │ ├── list.md │ │ ├── ram_and_cache.md │ │ └── summary.md │ ├── chapter_backtracking/ │ │ ├── backtracking_algorithm.md │ │ ├── index.md │ │ ├── n_queens_problem.md │ │ ├── permutations_problem.md │ │ ├── subset_sum_problem.md │ │ └── summary.md │ ├── chapter_computational_complexity/ │ │ ├── index.md │ │ ├── iteration_and_recursion.md │ │ ├── performance_evaluation.md │ │ ├── space_complexity.md │ │ ├── summary.md │ │ └── time_complexity.md │ ├── chapter_data_structure/ │ │ ├── basic_data_types.md │ │ ├── character_encoding.md │ │ ├── classification_of_data_structure.md │ │ ├── index.md │ │ ├── number_encoding.md │ │ └── summary.md │ ├── chapter_divide_and_conquer/ │ │ ├── binary_search_recur.md │ │ ├── build_binary_tree_problem.md │ │ ├── divide_and_conquer.md │ │ ├── hanota_problem.md │ │ ├── index.md │ │ └── summary.md │ ├── chapter_dynamic_programming/ │ │ ├── dp_problem_features.md │ │ ├── dp_solution_pipeline.md │ │ ├── edit_distance_problem.md │ │ ├── index.md │ │ ├── intro_to_dynamic_programming.md │ │ ├── knapsack_problem.md │ │ ├── summary.md │ │ └── unbounded_knapsack_problem.md │ ├── chapter_graph/ │ │ ├── graph.md │ │ ├── graph_operations.md │ │ ├── graph_traversal.md │ │ ├── index.md │ │ └── summary.md │ ├── chapter_greedy/ │ │ ├── fractional_knapsack_problem.md │ │ ├── greedy_algorithm.md │ │ ├── index.md │ │ ├── max_capacity_problem.md │ │ ├── max_product_cutting_problem.md │ │ └── summary.md │ ├── chapter_hashing/ │ │ ├── hash_algorithm.md │ │ ├── hash_collision.md │ │ ├── hash_map.md │ │ ├── index.md │ │ └── summary.md │ ├── chapter_heap/ │ │ ├── build_heap.md │ │ ├── heap.md │ │ ├── index.md │ │ ├── summary.md │ │ └── top_k.md │ ├── chapter_hello_algo/ │ │ └── index.md │ ├── chapter_introduction/ │ │ ├── algorithms_are_everywhere.md │ │ ├── index.md │ │ ├── summary.md │ │ └── what_is_dsa.md │ ├── chapter_paperbook/ │ │ └── index.md │ ├── chapter_preface/ │ │ ├── about_the_book.md │ │ ├── index.md │ │ ├── suggestions.md │ │ └── summary.md │ ├── chapter_reference/ │ │ └── index.md │ ├── chapter_searching/ │ │ ├── binary_search.md │ │ ├── binary_search_edge.md │ │ ├── binary_search_insertion.md │ │ ├── index.md │ │ ├── replace_linear_by_hashing.md │ │ ├── searching_algorithm_revisited.md │ │ └── summary.md │ ├── chapter_sorting/ │ │ ├── bubble_sort.md │ │ ├── bucket_sort.md │ │ ├── counting_sort.md │ │ ├── heap_sort.md │ │ ├── index.md │ │ ├── insertion_sort.md │ │ ├── merge_sort.md │ │ ├── quick_sort.md │ │ ├── radix_sort.md │ │ ├── selection_sort.md │ │ ├── sorting_algorithm.md │ │ └── summary.md │ ├── chapter_stack_and_queue/ │ │ ├── deque.md │ │ ├── index.md │ │ ├── queue.md │ │ ├── stack.md │ │ └── summary.md │ ├── chapter_tree/ │ │ ├── array_representation_of_tree.md │ │ ├── avl_tree.md │ │ ├── binary_search_tree.md │ │ ├── binary_tree.md │ │ ├── binary_tree_traversal.md │ │ ├── index.md │ │ └── summary.md │ ├── index.html │ └── index.md ├── en/ │ ├── CONTRIBUTING.md │ ├── README.md │ ├── codes/ │ │ ├── c/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array.c │ │ │ │ ├── linked_list.c │ │ │ │ └── my_list.c │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── n_queens.c │ │ │ │ ├── permutations_i.c │ │ │ │ ├── permutations_ii.c │ │ │ │ ├── preorder_traversal_i_compact.c │ │ │ │ ├── preorder_traversal_ii_compact.c │ │ │ │ ├── preorder_traversal_iii_compact.c │ │ │ │ ├── preorder_traversal_iii_template.c │ │ │ │ ├── subset_sum_i.c │ │ │ │ ├── subset_sum_i_naive.c │ │ │ │ └── subset_sum_ii.c │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── iteration.c │ │ │ │ ├── recursion.c │ │ │ │ ├── space_complexity.c │ │ │ │ ├── time_complexity.c │ │ │ │ └── worst_best_time_complexity.c │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── binary_search_recur.c │ │ │ │ ├── build_tree.c │ │ │ │ └── hanota.c │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── climbing_stairs_backtrack.c │ │ │ │ ├── climbing_stairs_constraint_dp.c │ │ │ │ ├── climbing_stairs_dfs.c │ │ │ │ ├── climbing_stairs_dfs_mem.c │ │ │ │ ├── climbing_stairs_dp.c │ │ │ │ ├── coin_change.c │ │ │ │ ├── coin_change_ii.c │ │ │ │ ├── edit_distance.c │ │ │ │ ├── knapsack.c │ │ │ │ ├── min_cost_climbing_stairs_dp.c │ │ │ │ ├── min_path_sum.c │ │ │ │ └── unbounded_knapsack.c │ │ │ ├── chapter_graph/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── graph_adjacency_list.c │ │ │ │ ├── graph_adjacency_list_test.c │ │ │ │ ├── graph_adjacency_matrix.c │ │ │ │ ├── graph_bfs.c │ │ │ │ └── graph_dfs.c │ │ │ ├── chapter_greedy/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── coin_change_greedy.c │ │ │ │ ├── fractional_knapsack.c │ │ │ │ ├── max_capacity.c │ │ │ │ └── max_product_cutting.c │ │ │ ├── chapter_hashing/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_hash_map.c │ │ │ │ ├── hash_map_chaining.c │ │ │ │ ├── hash_map_open_addressing.c │ │ │ │ └── simple_hash.c │ │ │ ├── chapter_heap/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── my_heap.c │ │ │ │ ├── my_heap_test.c │ │ │ │ └── top_k.c │ │ │ ├── chapter_searching/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── binary_search.c │ │ │ │ ├── binary_search_edge.c │ │ │ │ ├── binary_search_insertion.c │ │ │ │ └── two_sum.c │ │ │ ├── chapter_sorting/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── bubble_sort.c │ │ │ │ ├── bucket_sort.c │ │ │ │ ├── counting_sort.c │ │ │ │ ├── heap_sort.c │ │ │ │ ├── insertion_sort.c │ │ │ │ ├── merge_sort.c │ │ │ │ ├── quick_sort.c │ │ │ │ ├── radix_sort.c │ │ │ │ └── selection_sort.c │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_deque.c │ │ │ │ ├── array_queue.c │ │ │ │ ├── array_stack.c │ │ │ │ ├── linkedlist_deque.c │ │ │ │ ├── linkedlist_queue.c │ │ │ │ └── linkedlist_stack.c │ │ │ ├── chapter_tree/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_binary_tree.c │ │ │ │ ├── avl_tree.c │ │ │ │ ├── binary_search_tree.c │ │ │ │ ├── binary_tree.c │ │ │ │ ├── binary_tree_bfs.c │ │ │ │ └── binary_tree_dfs.c │ │ │ └── utils/ │ │ │ ├── CMakeLists.txt │ │ │ ├── common.h │ │ │ ├── common_test.c │ │ │ ├── list_node.h │ │ │ ├── print_util.h │ │ │ ├── tree_node.h │ │ │ ├── uthash.h │ │ │ ├── vector.h │ │ │ └── vertex.h │ │ ├── cpp/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array.cpp │ │ │ │ ├── linked_list.cpp │ │ │ │ ├── list.cpp │ │ │ │ └── my_list.cpp │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── n_queens.cpp │ │ │ │ ├── permutations_i.cpp │ │ │ │ ├── permutations_ii.cpp │ │ │ │ ├── preorder_traversal_i_compact.cpp │ │ │ │ ├── preorder_traversal_ii_compact.cpp │ │ │ │ ├── preorder_traversal_iii_compact.cpp │ │ │ │ ├── preorder_traversal_iii_template.cpp │ │ │ │ ├── subset_sum_i.cpp │ │ │ │ ├── subset_sum_i_naive.cpp │ │ │ │ └── subset_sum_ii.cpp │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── iteration.cpp │ │ │ │ ├── recursion.cpp │ │ │ │ ├── space_complexity.cpp │ │ │ │ ├── time_complexity.cpp │ │ │ │ └── worst_best_time_complexity.cpp │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── binary_search_recur.cpp │ │ │ │ ├── build_tree.cpp │ │ │ │ └── hanota.cpp │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── climbing_stairs_backtrack.cpp │ │ │ │ ├── climbing_stairs_constraint_dp.cpp │ │ │ │ ├── climbing_stairs_dfs.cpp │ │ │ │ ├── climbing_stairs_dfs_mem.cpp │ │ │ │ ├── climbing_stairs_dp.cpp │ │ │ │ ├── coin_change.cpp │ │ │ │ ├── coin_change_ii.cpp │ │ │ │ ├── edit_distance.cpp │ │ │ │ ├── knapsack.cpp │ │ │ │ ├── min_cost_climbing_stairs_dp.cpp │ │ │ │ ├── min_path_sum.cpp │ │ │ │ └── unbounded_knapsack.cpp │ │ │ ├── chapter_graph/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── graph_adjacency_list.cpp │ │ │ │ ├── graph_adjacency_list_test.cpp │ │ │ │ ├── graph_adjacency_matrix.cpp │ │ │ │ ├── graph_bfs.cpp │ │ │ │ └── graph_dfs.cpp │ │ │ ├── chapter_greedy/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── coin_change_greedy.cpp │ │ │ │ ├── fractional_knapsack.cpp │ │ │ │ ├── max_capacity.cpp │ │ │ │ └── max_product_cutting.cpp │ │ │ ├── chapter_hashing/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_hash_map.cpp │ │ │ │ ├── array_hash_map_test.cpp │ │ │ │ ├── built_in_hash.cpp │ │ │ │ ├── hash_map.cpp │ │ │ │ ├── hash_map_chaining.cpp │ │ │ │ ├── hash_map_open_addressing.cpp │ │ │ │ └── simple_hash.cpp │ │ │ ├── chapter_heap/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── heap.cpp │ │ │ │ ├── my_heap.cpp │ │ │ │ └── top_k.cpp │ │ │ ├── chapter_searching/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── binary_search.cpp │ │ │ │ ├── binary_search_edge.cpp │ │ │ │ ├── binary_search_insertion.cpp │ │ │ │ ├── hashing_search.cpp │ │ │ │ ├── linear_search.cpp │ │ │ │ └── two_sum.cpp │ │ │ ├── chapter_sorting/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── bubble_sort.cpp │ │ │ │ ├── bucket_sort.cpp │ │ │ │ ├── counting_sort.cpp │ │ │ │ ├── heap_sort.cpp │ │ │ │ ├── insertion_sort.cpp │ │ │ │ ├── merge_sort.cpp │ │ │ │ ├── quick_sort.cpp │ │ │ │ ├── radix_sort.cpp │ │ │ │ └── selection_sort.cpp │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_deque.cpp │ │ │ │ ├── array_queue.cpp │ │ │ │ ├── array_stack.cpp │ │ │ │ ├── deque.cpp │ │ │ │ ├── linkedlist_deque.cpp │ │ │ │ ├── linkedlist_queue.cpp │ │ │ │ ├── linkedlist_stack.cpp │ │ │ │ ├── queue.cpp │ │ │ │ └── stack.cpp │ │ │ ├── chapter_tree/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_binary_tree.cpp │ │ │ │ ├── avl_tree.cpp │ │ │ │ ├── binary_search_tree.cpp │ │ │ │ ├── binary_tree.cpp │ │ │ │ ├── binary_tree_bfs.cpp │ │ │ │ └── binary_tree_dfs.cpp │ │ │ └── utils/ │ │ │ ├── CMakeLists.txt │ │ │ ├── common.hpp │ │ │ ├── list_node.hpp │ │ │ ├── print_utils.hpp │ │ │ ├── tree_node.hpp │ │ │ └── vertex.hpp │ │ ├── csharp/ │ │ │ ├── .editorconfig │ │ │ ├── .gitignore │ │ │ ├── GlobalUsing.cs │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.cs │ │ │ │ ├── linked_list.cs │ │ │ │ ├── list.cs │ │ │ │ └── my_list.cs │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.cs │ │ │ │ ├── permutations_i.cs │ │ │ │ ├── permutations_ii.cs │ │ │ │ ├── preorder_traversal_i_compact.cs │ │ │ │ ├── preorder_traversal_ii_compact.cs │ │ │ │ ├── preorder_traversal_iii_compact.cs │ │ │ │ ├── preorder_traversal_iii_template.cs │ │ │ │ ├── subset_sum_i.cs │ │ │ │ ├── subset_sum_i_naive.cs │ │ │ │ └── subset_sum_ii.cs │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.cs │ │ │ │ ├── recursion.cs │ │ │ │ ├── space_complexity.cs │ │ │ │ ├── time_complexity.cs │ │ │ │ └── worst_best_time_complexity.cs │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.cs │ │ │ │ ├── build_tree.cs │ │ │ │ └── hanota.cs │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.cs │ │ │ │ ├── climbing_stairs_constraint_dp.cs │ │ │ │ ├── climbing_stairs_dfs.cs │ │ │ │ ├── climbing_stairs_dfs_mem.cs │ │ │ │ ├── climbing_stairs_dp.cs │ │ │ │ ├── coin_change.cs │ │ │ │ ├── coin_change_ii.cs │ │ │ │ ├── edit_distance.cs │ │ │ │ ├── knapsack.cs │ │ │ │ ├── min_cost_climbing_stairs_dp.cs │ │ │ │ ├── min_path_sum.cs │ │ │ │ └── unbounded_knapsack.cs │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.cs │ │ │ │ ├── graph_adjacency_matrix.cs │ │ │ │ ├── graph_bfs.cs │ │ │ │ └── graph_dfs.cs │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.cs │ │ │ │ ├── fractional_knapsack.cs │ │ │ │ ├── max_capacity.cs │ │ │ │ └── max_product_cutting.cs │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.cs │ │ │ │ ├── built_in_hash.cs │ │ │ │ ├── hash_map.cs │ │ │ │ ├── hash_map_chaining.cs │ │ │ │ ├── hash_map_open_addressing.cs │ │ │ │ └── simple_hash.cs │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.cs │ │ │ │ ├── my_heap.cs │ │ │ │ └── top_k.cs │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.cs │ │ │ │ ├── binary_search_edge.cs │ │ │ │ ├── binary_search_insertion.cs │ │ │ │ ├── hashing_search.cs │ │ │ │ ├── linear_search.cs │ │ │ │ └── two_sum.cs │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.cs │ │ │ │ ├── bucket_sort.cs │ │ │ │ ├── counting_sort.cs │ │ │ │ ├── heap_sort.cs │ │ │ │ ├── insertion_sort.cs │ │ │ │ ├── merge_sort.cs │ │ │ │ ├── quick_sort.cs │ │ │ │ ├── radix_sort.cs │ │ │ │ └── selection_sort.cs │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.cs │ │ │ │ ├── array_queue.cs │ │ │ │ ├── array_stack.cs │ │ │ │ ├── deque.cs │ │ │ │ ├── linkedlist_deque.cs │ │ │ │ ├── linkedlist_queue.cs │ │ │ │ ├── linkedlist_stack.cs │ │ │ │ ├── queue.cs │ │ │ │ └── stack.cs │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.cs │ │ │ │ ├── avl_tree.cs │ │ │ │ ├── binary_search_tree.cs │ │ │ │ ├── binary_tree.cs │ │ │ │ ├── binary_tree_bfs.cs │ │ │ │ └── binary_tree_dfs.cs │ │ │ ├── csharp.sln │ │ │ ├── hello-algo.csproj │ │ │ └── utils/ │ │ │ ├── ListNode.cs │ │ │ ├── PrintUtil.cs │ │ │ ├── TreeNode.cs │ │ │ └── Vertex.cs │ │ ├── dart/ │ │ │ ├── build.dart │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.dart │ │ │ │ ├── linked_list.dart │ │ │ │ ├── list.dart │ │ │ │ └── my_list.dart │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.dart │ │ │ │ ├── permutations_i.dart │ │ │ │ ├── permutations_ii.dart │ │ │ │ ├── preorder_traversal_i_compact.dart │ │ │ │ ├── preorder_traversal_ii_compact.dart │ │ │ │ ├── preorder_traversal_iii_compact.dart │ │ │ │ ├── preorder_traversal_iii_template.dart │ │ │ │ ├── subset_sum_i.dart │ │ │ │ ├── subset_sum_i_naive.dart │ │ │ │ └── subset_sum_ii.dart │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.dart │ │ │ │ ├── recursion.dart │ │ │ │ ├── space_complexity.dart │ │ │ │ ├── time_complexity.dart │ │ │ │ └── worst_best_time_complexity.dart │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.dart │ │ │ │ ├── build_tree.dart │ │ │ │ └── hanota.dart │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.dart │ │ │ │ ├── climbing_stairs_constraint_dp.dart │ │ │ │ ├── climbing_stairs_dfs.dart │ │ │ │ ├── climbing_stairs_dfs_mem.dart │ │ │ │ ├── climbing_stairs_dp.dart │ │ │ │ ├── coin_change.dart │ │ │ │ ├── coin_change_ii.dart │ │ │ │ ├── edit_distance.dart │ │ │ │ ├── knapsack.dart │ │ │ │ ├── min_cost_climbing_stairs_dp.dart │ │ │ │ ├── min_path_sum.dart │ │ │ │ └── unbounded_knapsack.dart │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.dart │ │ │ │ ├── graph_adjacency_matrix.dart │ │ │ │ ├── graph_bfs.dart │ │ │ │ └── graph_dfs.dart │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.dart │ │ │ │ ├── fractional_knapsack.dart │ │ │ │ ├── max_capacity.dart │ │ │ │ └── max_product_cutting.dart │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.dart │ │ │ │ ├── built_in_hash.dart │ │ │ │ ├── hash_map.dart │ │ │ │ ├── hash_map_chaining.dart │ │ │ │ ├── hash_map_open_addressing.dart │ │ │ │ └── simple_hash.dart │ │ │ ├── chapter_heap/ │ │ │ │ ├── my_heap.dart │ │ │ │ └── top_k.dart │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.dart │ │ │ │ ├── binary_search_edge.dart │ │ │ │ ├── binary_search_insertion.dart │ │ │ │ ├── hashing_search.dart │ │ │ │ ├── linear_search.dart │ │ │ │ └── two_sum.dart │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.dart │ │ │ │ ├── bucket_sort.dart │ │ │ │ ├── counting_sort.dart │ │ │ │ ├── heap_sort.dart │ │ │ │ ├── insertion_sort.dart │ │ │ │ ├── merge_sort.dart │ │ │ │ ├── quick_sort.dart │ │ │ │ ├── radix_sort.dart │ │ │ │ └── selection_sort.dart │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.dart │ │ │ │ ├── array_queue.dart │ │ │ │ ├── array_stack.dart │ │ │ │ ├── deque.dart │ │ │ │ ├── linkedlist_deque.dart │ │ │ │ ├── linkedlist_queue.dart │ │ │ │ ├── linkedlist_stack.dart │ │ │ │ ├── queue.dart │ │ │ │ └── stack.dart │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.dart │ │ │ │ ├── avl_tree.dart │ │ │ │ ├── binary_search_tree.dart │ │ │ │ ├── binary_tree.dart │ │ │ │ ├── binary_tree_bfs.dart │ │ │ │ └── binary_tree_dfs.dart │ │ │ └── utils/ │ │ │ ├── list_node.dart │ │ │ ├── print_util.dart │ │ │ ├── tree_node.dart │ │ │ └── vertex.dart │ │ ├── go/ │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.go │ │ │ │ ├── array_test.go │ │ │ │ ├── linked_list.go │ │ │ │ ├── linked_list_test.go │ │ │ │ ├── list_test.go │ │ │ │ ├── my_list.go │ │ │ │ └── my_list_test.go │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.go │ │ │ │ ├── n_queens_test.go │ │ │ │ ├── permutation_test.go │ │ │ │ ├── permutations_i.go │ │ │ │ ├── permutations_ii.go │ │ │ │ ├── preorder_traversal_i_compact.go │ │ │ │ ├── preorder_traversal_ii_compact.go │ │ │ │ ├── preorder_traversal_iii_compact.go │ │ │ │ ├── preorder_traversal_iii_template.go │ │ │ │ ├── preorder_traversal_test.go │ │ │ │ ├── subset_sum_i.go │ │ │ │ ├── subset_sum_i_naive.go │ │ │ │ ├── subset_sum_ii.go │ │ │ │ └── subset_sum_test.go │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.go │ │ │ │ ├── iteration_test.go │ │ │ │ ├── recursion.go │ │ │ │ ├── recursion_test.go │ │ │ │ ├── space_complexity.go │ │ │ │ ├── space_complexity_test.go │ │ │ │ ├── time_complexity.go │ │ │ │ ├── time_complexity_test.go │ │ │ │ ├── worst_best_time_complexity.go │ │ │ │ └── worst_best_time_complexity_test.go │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.go │ │ │ │ ├── binary_search_recur_test.go │ │ │ │ ├── build_tree.go │ │ │ │ ├── build_tree_test.go │ │ │ │ ├── hanota.go │ │ │ │ └── hanota_test.go │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.go │ │ │ │ ├── climbing_stairs_constraint_dp.go │ │ │ │ ├── climbing_stairs_dfs.go │ │ │ │ ├── climbing_stairs_dfs_mem.go │ │ │ │ ├── climbing_stairs_dp.go │ │ │ │ ├── climbing_stairs_test.go │ │ │ │ ├── coin_change.go │ │ │ │ ├── coin_change_ii.go │ │ │ │ ├── coin_change_test.go │ │ │ │ ├── edit_distance.go │ │ │ │ ├── edit_distance_test.go │ │ │ │ ├── knapsack.go │ │ │ │ ├── knapsack_test.go │ │ │ │ ├── min_cost_climbing_stairs_dp.go │ │ │ │ ├── min_path_sum.go │ │ │ │ ├── min_path_sum_test.go │ │ │ │ └── unbounded_knapsack.go │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.go │ │ │ │ ├── graph_adjacency_list_test.go │ │ │ │ ├── graph_adjacency_matrix.go │ │ │ │ ├── graph_adjacency_matrix_test.go │ │ │ │ ├── graph_bfs.go │ │ │ │ ├── graph_bfs_test.go │ │ │ │ ├── graph_dfs.go │ │ │ │ └── graph_dfs_test.go │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.go │ │ │ │ ├── coin_change_greedy_test.go │ │ │ │ ├── fractional_knapsack.go │ │ │ │ ├── fractional_knapsack_test.go │ │ │ │ ├── max_capacity.go │ │ │ │ ├── max_capacity_test.go │ │ │ │ ├── max_product_cutting.go │ │ │ │ └── max_product_cutting_test.go │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.go │ │ │ │ ├── array_hash_map_test.go │ │ │ │ ├── hash_collision_test.go │ │ │ │ ├── hash_map_chaining.go │ │ │ │ ├── hash_map_open_addressing.go │ │ │ │ ├── hash_map_test.go │ │ │ │ └── simple_hash.go │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.go │ │ │ │ ├── heap_test.go │ │ │ │ ├── my_heap.go │ │ │ │ └── top_k.go │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.go │ │ │ │ ├── binary_search_edge.go │ │ │ │ ├── binary_search_insertion.go │ │ │ │ ├── binary_search_test.go │ │ │ │ ├── hashing_search.go │ │ │ │ ├── hashing_search_test.go │ │ │ │ ├── linear_search.go │ │ │ │ ├── linear_search_test.go │ │ │ │ ├── two_sum.go │ │ │ │ └── two_sum_test.go │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.go │ │ │ │ ├── bubble_sort_test.go │ │ │ │ ├── bucket_sort.go │ │ │ │ ├── bucket_sort_test.go │ │ │ │ ├── counting_sort.go │ │ │ │ ├── counting_sort_test.go │ │ │ │ ├── heap_sort.go │ │ │ │ ├── heap_sort_test.go │ │ │ │ ├── insertion_sort.go │ │ │ │ ├── insertion_sort_test.go │ │ │ │ ├── merge_sort.go │ │ │ │ ├── merge_sort_test.go │ │ │ │ ├── quick_sort.go │ │ │ │ ├── quick_sort_test.go │ │ │ │ ├── radix_sort.go │ │ │ │ ├── radix_sort_test.go │ │ │ │ ├── selection_sort.go │ │ │ │ └── selection_sort_test.go │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.go │ │ │ │ ├── array_queue.go │ │ │ │ ├── array_stack.go │ │ │ │ ├── deque_test.go │ │ │ │ ├── linkedlist_deque.go │ │ │ │ ├── linkedlist_queue.go │ │ │ │ ├── linkedlist_stack.go │ │ │ │ ├── queue_test.go │ │ │ │ └── stack_test.go │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.go │ │ │ │ ├── array_binary_tree_test.go │ │ │ │ ├── avl_tree.go │ │ │ │ ├── avl_tree_test.go │ │ │ │ ├── binary_search_tree.go │ │ │ │ ├── binary_search_tree_test.go │ │ │ │ ├── binary_tree_bfs.go │ │ │ │ ├── binary_tree_bfs_test.go │ │ │ │ ├── binary_tree_dfs.go │ │ │ │ ├── binary_tree_dfs_test.go │ │ │ │ └── binary_tree_test.go │ │ │ ├── go.mod │ │ │ └── pkg/ │ │ │ ├── list_node.go │ │ │ ├── list_node_test.go │ │ │ ├── print_utils.go │ │ │ ├── tree_node.go │ │ │ ├── tree_node_test.go │ │ │ └── vertex.go │ │ ├── java/ │ │ │ ├── .gitignore │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.java │ │ │ │ ├── linked_list.java │ │ │ │ ├── list.java │ │ │ │ └── my_list.java │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.java │ │ │ │ ├── permutations_i.java │ │ │ │ ├── permutations_ii.java │ │ │ │ ├── preorder_traversal_i_compact.java │ │ │ │ ├── preorder_traversal_ii_compact.java │ │ │ │ ├── preorder_traversal_iii_compact.java │ │ │ │ ├── preorder_traversal_iii_template.java │ │ │ │ ├── subset_sum_i.java │ │ │ │ ├── subset_sum_i_naive.java │ │ │ │ └── subset_sum_ii.java │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.java │ │ │ │ ├── recursion.java │ │ │ │ ├── space_complexity.java │ │ │ │ ├── time_complexity.java │ │ │ │ └── worst_best_time_complexity.java │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.java │ │ │ │ ├── build_tree.java │ │ │ │ └── hanota.java │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.java │ │ │ │ ├── climbing_stairs_constraint_dp.java │ │ │ │ ├── climbing_stairs_dfs.java │ │ │ │ ├── climbing_stairs_dfs_mem.java │ │ │ │ ├── climbing_stairs_dp.java │ │ │ │ ├── coin_change.java │ │ │ │ ├── coin_change_ii.java │ │ │ │ ├── edit_distance.java │ │ │ │ ├── knapsack.java │ │ │ │ ├── min_cost_climbing_stairs_dp.java │ │ │ │ ├── min_path_sum.java │ │ │ │ └── unbounded_knapsack.java │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.java │ │ │ │ ├── graph_adjacency_matrix.java │ │ │ │ ├── graph_bfs.java │ │ │ │ └── graph_dfs.java │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.java │ │ │ │ ├── fractional_knapsack.java │ │ │ │ ├── max_capacity.java │ │ │ │ └── max_product_cutting.java │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.java │ │ │ │ ├── built_in_hash.java │ │ │ │ ├── hash_map.java │ │ │ │ ├── hash_map_chaining.java │ │ │ │ ├── hash_map_open_addressing.java │ │ │ │ └── simple_hash.java │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.java │ │ │ │ ├── my_heap.java │ │ │ │ └── top_k.java │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.java │ │ │ │ ├── binary_search_edge.java │ │ │ │ ├── binary_search_insertion.java │ │ │ │ ├── hashing_search.java │ │ │ │ ├── linear_search.java │ │ │ │ └── two_sum.java │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.java │ │ │ │ ├── bucket_sort.java │ │ │ │ ├── counting_sort.java │ │ │ │ ├── heap_sort.java │ │ │ │ ├── insertion_sort.java │ │ │ │ ├── merge_sort.java │ │ │ │ ├── quick_sort.java │ │ │ │ ├── radix_sort.java │ │ │ │ └── selection_sort.java │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.java │ │ │ │ ├── array_queue.java │ │ │ │ ├── array_stack.java │ │ │ │ ├── deque.java │ │ │ │ ├── linkedlist_deque.java │ │ │ │ ├── linkedlist_queue.java │ │ │ │ ├── linkedlist_stack.java │ │ │ │ ├── queue.java │ │ │ │ └── stack.java │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.java │ │ │ │ ├── avl_tree.java │ │ │ │ ├── binary_search_tree.java │ │ │ │ ├── binary_tree.java │ │ │ │ ├── binary_tree_bfs.java │ │ │ │ └── binary_tree_dfs.java │ │ │ └── utils/ │ │ │ ├── ListNode.java │ │ │ ├── PrintUtil.java │ │ │ ├── TreeNode.java │ │ │ └── Vertex.java │ │ ├── javascript/ │ │ │ ├── .prettierrc │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.js │ │ │ │ ├── linked_list.js │ │ │ │ ├── list.js │ │ │ │ └── my_list.js │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.js │ │ │ │ ├── permutations_i.js │ │ │ │ ├── permutations_ii.js │ │ │ │ ├── preorder_traversal_i_compact.js │ │ │ │ ├── preorder_traversal_ii_compact.js │ │ │ │ ├── preorder_traversal_iii_compact.js │ │ │ │ ├── preorder_traversal_iii_template.js │ │ │ │ ├── subset_sum_i.js │ │ │ │ ├── subset_sum_i_naive.js │ │ │ │ └── subset_sum_ii.js │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.js │ │ │ │ ├── recursion.js │ │ │ │ ├── space_complexity.js │ │ │ │ ├── time_complexity.js │ │ │ │ └── worst_best_time_complexity.js │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.js │ │ │ │ ├── build_tree.js │ │ │ │ └── hanota.js │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.js │ │ │ │ ├── climbing_stairs_constraint_dp.js │ │ │ │ ├── climbing_stairs_dfs.js │ │ │ │ ├── climbing_stairs_dfs_mem.js │ │ │ │ ├── climbing_stairs_dp.js │ │ │ │ ├── coin_change.js │ │ │ │ ├── coin_change_ii.js │ │ │ │ ├── edit_distance.js │ │ │ │ ├── knapsack.js │ │ │ │ ├── min_cost_climbing_stairs_dp.js │ │ │ │ ├── min_path_sum.js │ │ │ │ └── unbounded_knapsack.js │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.js │ │ │ │ ├── graph_adjacency_matrix.js │ │ │ │ ├── graph_bfs.js │ │ │ │ └── graph_dfs.js │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.js │ │ │ │ ├── fractional_knapsack.js │ │ │ │ ├── max_capacity.js │ │ │ │ └── max_product_cutting.js │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.js │ │ │ │ ├── hash_map.js │ │ │ │ ├── hash_map_chaining.js │ │ │ │ ├── hash_map_open_addressing.js │ │ │ │ └── simple_hash.js │ │ │ ├── chapter_heap/ │ │ │ │ ├── my_heap.js │ │ │ │ └── top_k.js │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.js │ │ │ │ ├── binary_search_edge.js │ │ │ │ ├── binary_search_insertion.js │ │ │ │ ├── hashing_search.js │ │ │ │ ├── linear_search.js │ │ │ │ └── two_sum.js │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.js │ │ │ │ ├── bucket_sort.js │ │ │ │ ├── counting_sort.js │ │ │ │ ├── heap_sort.js │ │ │ │ ├── insertion_sort.js │ │ │ │ ├── merge_sort.js │ │ │ │ ├── quick_sort.js │ │ │ │ ├── radix_sort.js │ │ │ │ └── selection_sort.js │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.js │ │ │ │ ├── array_queue.js │ │ │ │ ├── array_stack.js │ │ │ │ ├── deque.js │ │ │ │ ├── linkedlist_deque.js │ │ │ │ ├── linkedlist_queue.js │ │ │ │ ├── linkedlist_stack.js │ │ │ │ ├── queue.js │ │ │ │ └── stack.js │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.js │ │ │ │ ├── avl_tree.js │ │ │ │ ├── binary_search_tree.js │ │ │ │ ├── binary_tree.js │ │ │ │ ├── binary_tree_bfs.js │ │ │ │ └── binary_tree_dfs.js │ │ │ ├── modules/ │ │ │ │ ├── ListNode.js │ │ │ │ ├── PrintUtil.js │ │ │ │ ├── TreeNode.js │ │ │ │ └── Vertex.js │ │ │ └── test_all.js │ │ ├── kotlin/ │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.kt │ │ │ │ ├── linked_list.kt │ │ │ │ ├── list.kt │ │ │ │ └── my_list.kt │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.kt │ │ │ │ ├── permutations_i.kt │ │ │ │ ├── permutations_ii.kt │ │ │ │ ├── preorder_traversal_i_compact.kt │ │ │ │ ├── preorder_traversal_ii_compact.kt │ │ │ │ ├── preorder_traversal_iii_compact.kt │ │ │ │ ├── preorder_traversal_iii_template.kt │ │ │ │ ├── subset_sum_i.kt │ │ │ │ ├── subset_sum_i_naive.kt │ │ │ │ └── subset_sum_ii.kt │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.kt │ │ │ │ ├── recursion.kt │ │ │ │ ├── space_complexity.kt │ │ │ │ ├── time_complexity.kt │ │ │ │ └── worst_best_time_complexity.kt │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.kt │ │ │ │ ├── build_tree.kt │ │ │ │ └── hanota.kt │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.kt │ │ │ │ ├── climbing_stairs_constraint_dp.kt │ │ │ │ ├── climbing_stairs_dfs.kt │ │ │ │ ├── climbing_stairs_dfs_mem.kt │ │ │ │ ├── climbing_stairs_dp.kt │ │ │ │ ├── coin_change.kt │ │ │ │ ├── coin_change_ii.kt │ │ │ │ ├── edit_distance.kt │ │ │ │ ├── knapsack.kt │ │ │ │ ├── min_cost_climbing_stairs_dp.kt │ │ │ │ ├── min_path_sum.kt │ │ │ │ └── unbounded_knapsack.kt │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.kt │ │ │ │ ├── graph_adjacency_matrix.kt │ │ │ │ ├── graph_bfs.kt │ │ │ │ └── graph_dfs.kt │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.kt │ │ │ │ ├── fractional_knapsack.kt │ │ │ │ ├── max_capacity.kt │ │ │ │ └── max_product_cutting.kt │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.kt │ │ │ │ ├── built_in_hash.kt │ │ │ │ ├── hash_map.kt │ │ │ │ ├── hash_map_chaining.kt │ │ │ │ ├── hash_map_open_addressing.kt │ │ │ │ └── simple_hash.kt │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.kt │ │ │ │ ├── my_heap.kt │ │ │ │ └── top_k.kt │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.kt │ │ │ │ ├── binary_search_edge.kt │ │ │ │ ├── binary_search_insertion.kt │ │ │ │ ├── hashing_search.kt │ │ │ │ ├── linear_search.kt │ │ │ │ └── two_sum.kt │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.kt │ │ │ │ ├── bucket_sort.kt │ │ │ │ ├── counting_sort.kt │ │ │ │ ├── heap_sort.kt │ │ │ │ ├── insertion_sort.kt │ │ │ │ ├── merge_sort.kt │ │ │ │ ├── quick_sort.kt │ │ │ │ ├── radix_sort.kt │ │ │ │ └── selection_sort.kt │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.kt │ │ │ │ ├── array_queue.kt │ │ │ │ ├── array_stack.kt │ │ │ │ ├── deque.kt │ │ │ │ ├── linkedlist_deque.kt │ │ │ │ ├── linkedlist_queue.kt │ │ │ │ ├── linkedlist_stack.kt │ │ │ │ ├── queue.kt │ │ │ │ └── stack.kt │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.kt │ │ │ │ ├── avl_tree.kt │ │ │ │ ├── binary_search_tree.kt │ │ │ │ ├── binary_tree.kt │ │ │ │ ├── binary_tree_bfs.kt │ │ │ │ └── binary_tree_dfs.kt │ │ │ └── utils/ │ │ │ ├── ListNode.kt │ │ │ ├── PrintUtil.kt │ │ │ ├── TreeNode.kt │ │ │ └── Vertex.kt │ │ ├── python/ │ │ │ ├── .gitignore │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.py │ │ │ │ ├── linked_list.py │ │ │ │ ├── list.py │ │ │ │ └── my_list.py │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.py │ │ │ │ ├── permutations_i.py │ │ │ │ ├── permutations_ii.py │ │ │ │ ├── preorder_traversal_i_compact.py │ │ │ │ ├── preorder_traversal_ii_compact.py │ │ │ │ ├── preorder_traversal_iii_compact.py │ │ │ │ ├── preorder_traversal_iii_template.py │ │ │ │ ├── subset_sum_i.py │ │ │ │ ├── subset_sum_i_naive.py │ │ │ │ └── subset_sum_ii.py │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.py │ │ │ │ ├── recursion.py │ │ │ │ ├── space_complexity.py │ │ │ │ ├── time_complexity.py │ │ │ │ └── worst_best_time_complexity.py │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.py │ │ │ │ ├── build_tree.py │ │ │ │ └── hanota.py │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.py │ │ │ │ ├── climbing_stairs_constraint_dp.py │ │ │ │ ├── climbing_stairs_dfs.py │ │ │ │ ├── climbing_stairs_dfs_mem.py │ │ │ │ ├── climbing_stairs_dp.py │ │ │ │ ├── coin_change.py │ │ │ │ ├── coin_change_ii.py │ │ │ │ ├── edit_distance.py │ │ │ │ ├── knapsack.py │ │ │ │ ├── min_cost_climbing_stairs_dp.py │ │ │ │ ├── min_path_sum.py │ │ │ │ └── unbounded_knapsack.py │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.py │ │ │ │ ├── graph_adjacency_matrix.py │ │ │ │ ├── graph_bfs.py │ │ │ │ └── graph_dfs.py │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.py │ │ │ │ ├── fractional_knapsack.py │ │ │ │ ├── max_capacity.py │ │ │ │ └── max_product_cutting.py │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.py │ │ │ │ ├── built_in_hash.py │ │ │ │ ├── hash_map.py │ │ │ │ ├── hash_map_chaining.py │ │ │ │ ├── hash_map_open_addressing.py │ │ │ │ └── simple_hash.py │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.py │ │ │ │ ├── my_heap.py │ │ │ │ └── top_k.py │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.py │ │ │ │ ├── binary_search_edge.py │ │ │ │ ├── binary_search_insertion.py │ │ │ │ ├── hashing_search.py │ │ │ │ ├── linear_search.py │ │ │ │ └── two_sum.py │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.py │ │ │ │ ├── bucket_sort.py │ │ │ │ ├── counting_sort.py │ │ │ │ ├── heap_sort.py │ │ │ │ ├── insertion_sort.py │ │ │ │ ├── merge_sort.py │ │ │ │ ├── quick_sort.py │ │ │ │ ├── radix_sort.py │ │ │ │ └── selection_sort.py │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.py │ │ │ │ ├── array_queue.py │ │ │ │ ├── array_stack.py │ │ │ │ ├── deque.py │ │ │ │ ├── linkedlist_deque.py │ │ │ │ ├── linkedlist_queue.py │ │ │ │ ├── linkedlist_stack.py │ │ │ │ ├── queue.py │ │ │ │ └── stack.py │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.py │ │ │ │ ├── avl_tree.py │ │ │ │ ├── binary_search_tree.py │ │ │ │ ├── binary_tree.py │ │ │ │ ├── binary_tree_bfs.py │ │ │ │ └── binary_tree_dfs.py │ │ │ ├── modules/ │ │ │ │ ├── __init__.py │ │ │ │ ├── list_node.py │ │ │ │ ├── print_util.py │ │ │ │ ├── tree_node.py │ │ │ │ └── vertex.py │ │ │ └── test_all.py │ │ ├── ruby/ │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.rb │ │ │ │ ├── linked_list.rb │ │ │ │ ├── list.rb │ │ │ │ └── my_list.rb │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.rb │ │ │ │ ├── permutations_i.rb │ │ │ │ ├── permutations_ii.rb │ │ │ │ ├── preorder_traversal_i_compact.rb │ │ │ │ ├── preorder_traversal_ii_compact.rb │ │ │ │ ├── preorder_traversal_iii_compact.rb │ │ │ │ ├── preorder_traversal_iii_template.rb │ │ │ │ ├── subset_sum_i.rb │ │ │ │ ├── subset_sum_i_naive.rb │ │ │ │ └── subset_sum_ii.rb │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.rb │ │ │ │ ├── recursion.rb │ │ │ │ ├── space_complexity.rb │ │ │ │ ├── time_complexity.rb │ │ │ │ └── worst_best_time_complexity.rb │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.rb │ │ │ │ ├── build_tree.rb │ │ │ │ └── hanota.rb │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.rb │ │ │ │ ├── climbing_stairs_constraint_dp.rb │ │ │ │ ├── climbing_stairs_dfs.rb │ │ │ │ ├── climbing_stairs_dfs_mem.rb │ │ │ │ ├── climbing_stairs_dp.rb │ │ │ │ ├── coin_change.rb │ │ │ │ ├── coin_change_ii.rb │ │ │ │ ├── edit_distance.rb │ │ │ │ ├── knapsack.rb │ │ │ │ ├── min_cost_climbing_stairs_dp.rb │ │ │ │ ├── min_path_sum.rb │ │ │ │ └── unbounded_knapsack.rb │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.rb │ │ │ │ ├── graph_adjacency_matrix.rb │ │ │ │ ├── graph_bfs.rb │ │ │ │ └── graph_dfs.rb │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.rb │ │ │ │ ├── fractional_knapsack.rb │ │ │ │ ├── max_capacity.rb │ │ │ │ └── max_product_cutting.rb │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.rb │ │ │ │ ├── built_in_hash.rb │ │ │ │ ├── hash_map.rb │ │ │ │ ├── hash_map_chaining.rb │ │ │ │ ├── hash_map_open_addressing.rb │ │ │ │ └── simple_hash.rb │ │ │ ├── chapter_heap/ │ │ │ │ ├── my_heap.rb │ │ │ │ └── top_k.rb │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.rb │ │ │ │ ├── binary_search_edge.rb │ │ │ │ ├── binary_search_insertion.rb │ │ │ │ ├── hashing_search.rb │ │ │ │ ├── linear_search.rb │ │ │ │ └── two_sum.rb │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.rb │ │ │ │ ├── bucket_sort.rb │ │ │ │ ├── counting_sort.rb │ │ │ │ ├── heap_sort.rb │ │ │ │ ├── insertion_sort.rb │ │ │ │ ├── merge_sort.rb │ │ │ │ ├── quick_sort.rb │ │ │ │ ├── radix_sort.rb │ │ │ │ └── selection_sort.rb │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.rb │ │ │ │ ├── array_queue.rb │ │ │ │ ├── array_stack.rb │ │ │ │ ├── deque.rb │ │ │ │ ├── linkedlist_deque.rb │ │ │ │ ├── linkedlist_queue.rb │ │ │ │ ├── linkedlist_stack.rb │ │ │ │ ├── queue.rb │ │ │ │ └── stack.rb │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.rb │ │ │ │ ├── avl_tree.rb │ │ │ │ ├── binary_search_tree.rb │ │ │ │ ├── binary_tree.rb │ │ │ │ ├── binary_tree_bfs.rb │ │ │ │ └── binary_tree_dfs.rb │ │ │ ├── test_all.rb │ │ │ └── utils/ │ │ │ ├── list_node.rb │ │ │ ├── print_util.rb │ │ │ ├── tree_node.rb │ │ │ └── vertex.rb │ │ ├── rust/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.rs │ │ │ │ ├── linked_list.rs │ │ │ │ ├── list.rs │ │ │ │ └── my_list.rs │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.rs │ │ │ │ ├── permutations_i.rs │ │ │ │ ├── permutations_ii.rs │ │ │ │ ├── preorder_traversal_i_compact.rs │ │ │ │ ├── preorder_traversal_ii_compact.rs │ │ │ │ ├── preorder_traversal_iii_compact.rs │ │ │ │ ├── preorder_traversal_iii_template.rs │ │ │ │ ├── subset_sum_i.rs │ │ │ │ ├── subset_sum_i_naive.rs │ │ │ │ └── subset_sum_ii.rs │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.rs │ │ │ │ ├── recursion.rs │ │ │ │ ├── space_complexity.rs │ │ │ │ ├── time_complexity.rs │ │ │ │ └── worst_best_time_complexity.rs │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.rs │ │ │ │ ├── build_tree.rs │ │ │ │ └── hanota.rs │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.rs │ │ │ │ ├── climbing_stairs_constraint_dp.rs │ │ │ │ ├── climbing_stairs_dfs.rs │ │ │ │ ├── climbing_stairs_dfs_mem.rs │ │ │ │ ├── climbing_stairs_dp.rs │ │ │ │ ├── coin_change.rs │ │ │ │ ├── coin_change_ii.rs │ │ │ │ ├── edit_distance.rs │ │ │ │ ├── knapsack.rs │ │ │ │ ├── min_cost_climbing_stairs_dp.rs │ │ │ │ ├── min_path_sum.rs │ │ │ │ └── unbounded_knapsack.rs │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.rs │ │ │ │ ├── graph_adjacency_matrix.rs │ │ │ │ ├── graph_bfs.rs │ │ │ │ └── graph_dfs.rs │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.rs │ │ │ │ ├── fractional_knapsack.rs │ │ │ │ ├── max_capacity.rs │ │ │ │ └── max_product_cutting.rs │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.rs │ │ │ │ ├── build_in_hash.rs │ │ │ │ ├── hash_map.rs │ │ │ │ ├── hash_map_chaining.rs │ │ │ │ ├── hash_map_open_addressing.rs │ │ │ │ └── simple_hash.rs │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.rs │ │ │ │ ├── my_heap.rs │ │ │ │ └── top_k.rs │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.rs │ │ │ │ ├── binary_search_edge.rs │ │ │ │ ├── binary_search_insertion.rs │ │ │ │ ├── hashing_search.rs │ │ │ │ ├── linear_search.rs │ │ │ │ └── two_sum.rs │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.rs │ │ │ │ ├── bucket_sort.rs │ │ │ │ ├── counting_sort.rs │ │ │ │ ├── heap_sort.rs │ │ │ │ ├── insertion_sort.rs │ │ │ │ ├── merge_sort.rs │ │ │ │ ├── quick_sort.rs │ │ │ │ ├── radix_sort.rs │ │ │ │ └── selection_sort.rs │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.rs │ │ │ │ ├── array_queue.rs │ │ │ │ ├── array_stack.rs │ │ │ │ ├── deque.rs │ │ │ │ ├── linkedlist_deque.rs │ │ │ │ ├── linkedlist_queue.rs │ │ │ │ ├── linkedlist_stack.rs │ │ │ │ ├── queue.rs │ │ │ │ └── stack.rs │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.rs │ │ │ │ ├── avl_tree.rs │ │ │ │ ├── binary_search_tree.rs │ │ │ │ ├── binary_tree.rs │ │ │ │ ├── binary_tree_bfs.rs │ │ │ │ └── binary_tree_dfs.rs │ │ │ └── src/ │ │ │ ├── include/ │ │ │ │ ├── list_node.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── print_util.rs │ │ │ │ ├── tree_node.rs │ │ │ │ └── vertex.rs │ │ │ └── lib.rs │ │ ├── swift/ │ │ │ ├── .gitignore │ │ │ ├── Package.resolved │ │ │ ├── Package.swift │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.swift │ │ │ │ ├── linked_list.swift │ │ │ │ ├── list.swift │ │ │ │ └── my_list.swift │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.swift │ │ │ │ ├── permutations_i.swift │ │ │ │ ├── permutations_ii.swift │ │ │ │ ├── preorder_traversal_i_compact.swift │ │ │ │ ├── preorder_traversal_ii_compact.swift │ │ │ │ ├── preorder_traversal_iii_compact.swift │ │ │ │ ├── preorder_traversal_iii_template.swift │ │ │ │ ├── subset_sum_i.swift │ │ │ │ ├── subset_sum_i_naive.swift │ │ │ │ └── subset_sum_ii.swift │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.swift │ │ │ │ ├── recursion.swift │ │ │ │ ├── space_complexity.swift │ │ │ │ ├── time_complexity.swift │ │ │ │ └── worst_best_time_complexity.swift │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.swift │ │ │ │ ├── build_tree.swift │ │ │ │ └── hanota.swift │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.swift │ │ │ │ ├── climbing_stairs_constraint_dp.swift │ │ │ │ ├── climbing_stairs_dfs.swift │ │ │ │ ├── climbing_stairs_dfs_mem.swift │ │ │ │ ├── climbing_stairs_dp.swift │ │ │ │ ├── coin_change.swift │ │ │ │ ├── coin_change_ii.swift │ │ │ │ ├── edit_distance.swift │ │ │ │ ├── knapsack.swift │ │ │ │ ├── min_cost_climbing_stairs_dp.swift │ │ │ │ ├── min_path_sum.swift │ │ │ │ └── unbounded_knapsack.swift │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.swift │ │ │ │ ├── graph_adjacency_list_target.swift │ │ │ │ ├── graph_adjacency_matrix.swift │ │ │ │ ├── graph_bfs.swift │ │ │ │ └── graph_dfs.swift │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.swift │ │ │ │ ├── fractional_knapsack.swift │ │ │ │ ├── max_capacity.swift │ │ │ │ └── max_product_cutting.swift │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.swift │ │ │ │ ├── built_in_hash.swift │ │ │ │ ├── hash_map.swift │ │ │ │ ├── hash_map_chaining.swift │ │ │ │ ├── hash_map_open_addressing.swift │ │ │ │ └── simple_hash.swift │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.swift │ │ │ │ ├── my_heap.swift │ │ │ │ └── top_k.swift │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.swift │ │ │ │ ├── binary_search_edge.swift │ │ │ │ ├── binary_search_insertion.swift │ │ │ │ ├── binary_search_insertion_target.swift │ │ │ │ ├── hashing_search.swift │ │ │ │ ├── linear_search.swift │ │ │ │ └── two_sum.swift │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.swift │ │ │ │ ├── bucket_sort.swift │ │ │ │ ├── counting_sort.swift │ │ │ │ ├── heap_sort.swift │ │ │ │ ├── insertion_sort.swift │ │ │ │ ├── merge_sort.swift │ │ │ │ ├── quick_sort.swift │ │ │ │ ├── radix_sort.swift │ │ │ │ └── selection_sort.swift │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.swift │ │ │ │ ├── array_queue.swift │ │ │ │ ├── array_stack.swift │ │ │ │ ├── deque.swift │ │ │ │ ├── linkedlist_deque.swift │ │ │ │ ├── linkedlist_queue.swift │ │ │ │ ├── linkedlist_stack.swift │ │ │ │ ├── queue.swift │ │ │ │ └── stack.swift │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.swift │ │ │ │ ├── avl_tree.swift │ │ │ │ ├── binary_search_tree.swift │ │ │ │ ├── binary_tree.swift │ │ │ │ ├── binary_tree_bfs.swift │ │ │ │ └── binary_tree_dfs.swift │ │ │ └── utils/ │ │ │ ├── ListNode.swift │ │ │ ├── Pair.swift │ │ │ ├── PrintUtil.swift │ │ │ ├── TreeNode.swift │ │ │ └── Vertex.swift │ │ └── typescript/ │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.ts │ │ │ ├── linked_list.ts │ │ │ ├── list.ts │ │ │ └── my_list.ts │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.ts │ │ │ ├── permutations_i.ts │ │ │ ├── permutations_ii.ts │ │ │ ├── preorder_traversal_i_compact.ts │ │ │ ├── preorder_traversal_ii_compact.ts │ │ │ ├── preorder_traversal_iii_compact.ts │ │ │ ├── preorder_traversal_iii_template.ts │ │ │ ├── subset_sum_i.ts │ │ │ ├── subset_sum_i_naive.ts │ │ │ └── subset_sum_ii.ts │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.ts │ │ │ ├── recursion.ts │ │ │ ├── space_complexity.ts │ │ │ ├── time_complexity.ts │ │ │ └── worst_best_time_complexity.ts │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.ts │ │ │ ├── build_tree.ts │ │ │ └── hanota.ts │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.ts │ │ │ ├── climbing_stairs_constraint_dp.ts │ │ │ ├── climbing_stairs_dfs.ts │ │ │ ├── climbing_stairs_dfs_mem.ts │ │ │ ├── climbing_stairs_dp.ts │ │ │ ├── coin_change.ts │ │ │ ├── coin_change_ii.ts │ │ │ ├── edit_distance.ts │ │ │ ├── knapsack.ts │ │ │ ├── min_cost_climbing_stairs_dp.ts │ │ │ ├── min_path_sum.ts │ │ │ └── unbounded_knapsack.ts │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.ts │ │ │ ├── graph_adjacency_matrix.ts │ │ │ ├── graph_bfs.ts │ │ │ └── graph_dfs.ts │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.ts │ │ │ ├── fractional_knapsack.ts │ │ │ ├── max_capacity.ts │ │ │ └── max_product_cutting.ts │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.ts │ │ │ ├── hash_map.ts │ │ │ ├── hash_map_chaining.ts │ │ │ ├── hash_map_open_addressing.ts │ │ │ └── simple_hash.ts │ │ ├── chapter_heap/ │ │ │ ├── my_heap.ts │ │ │ └── top_k.ts │ │ ├── chapter_searching/ │ │ │ ├── binary_search.ts │ │ │ ├── binary_search_edge.ts │ │ │ ├── binary_search_insertion.ts │ │ │ ├── hashing_search.ts │ │ │ ├── linear_search.ts │ │ │ └── two_sum.ts │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.ts │ │ │ ├── bucket_sort.ts │ │ │ ├── counting_sort.ts │ │ │ ├── heap_sort.ts │ │ │ ├── insertion_sort.ts │ │ │ ├── merge_sort.ts │ │ │ ├── quick_sort.ts │ │ │ ├── radix_sort.ts │ │ │ └── selection_sort.ts │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.ts │ │ │ ├── array_queue.ts │ │ │ ├── array_stack.ts │ │ │ ├── deque.ts │ │ │ ├── linkedlist_deque.ts │ │ │ ├── linkedlist_queue.ts │ │ │ ├── linkedlist_stack.ts │ │ │ ├── queue.ts │ │ │ └── stack.ts │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.ts │ │ │ ├── avl_tree.ts │ │ │ ├── binary_search_tree.ts │ │ │ ├── binary_tree.ts │ │ │ ├── binary_tree_bfs.ts │ │ │ └── binary_tree_dfs.ts │ │ ├── modules/ │ │ │ ├── ListNode.ts │ │ │ ├── PrintUtil.ts │ │ │ ├── TreeNode.ts │ │ │ └── Vertex.ts │ │ ├── package.json │ │ └── tsconfig.json │ ├── docs/ │ │ ├── chapter_appendix/ │ │ │ ├── contribution.md │ │ │ ├── index.md │ │ │ ├── installation.md │ │ │ └── terminology.md │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.md │ │ │ ├── index.md │ │ │ ├── linked_list.md │ │ │ ├── list.md │ │ │ ├── ram_and_cache.md │ │ │ └── summary.md │ │ ├── chapter_backtracking/ │ │ │ ├── backtracking_algorithm.md │ │ │ ├── index.md │ │ │ ├── n_queens_problem.md │ │ │ ├── permutations_problem.md │ │ │ ├── subset_sum_problem.md │ │ │ └── summary.md │ │ ├── chapter_computational_complexity/ │ │ │ ├── index.md │ │ │ ├── iteration_and_recursion.md │ │ │ ├── performance_evaluation.md │ │ │ ├── space_complexity.md │ │ │ ├── summary.md │ │ │ └── time_complexity.md │ │ ├── chapter_data_structure/ │ │ │ ├── basic_data_types.md │ │ │ ├── character_encoding.md │ │ │ ├── classification_of_data_structure.md │ │ │ ├── index.md │ │ │ ├── number_encoding.md │ │ │ └── summary.md │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.md │ │ │ ├── build_binary_tree_problem.md │ │ │ ├── divide_and_conquer.md │ │ │ ├── hanota_problem.md │ │ │ ├── index.md │ │ │ └── summary.md │ │ ├── chapter_dynamic_programming/ │ │ │ ├── dp_problem_features.md │ │ │ ├── dp_solution_pipeline.md │ │ │ ├── edit_distance_problem.md │ │ │ ├── index.md │ │ │ ├── intro_to_dynamic_programming.md │ │ │ ├── knapsack_problem.md │ │ │ ├── summary.md │ │ │ └── unbounded_knapsack_problem.md │ │ ├── chapter_graph/ │ │ │ ├── graph.md │ │ │ ├── graph_operations.md │ │ │ ├── graph_traversal.md │ │ │ ├── index.md │ │ │ └── summary.md │ │ ├── chapter_greedy/ │ │ │ ├── fractional_knapsack_problem.md │ │ │ ├── greedy_algorithm.md │ │ │ ├── index.md │ │ │ ├── max_capacity_problem.md │ │ │ ├── max_product_cutting_problem.md │ │ │ └── summary.md │ │ ├── chapter_hashing/ │ │ │ ├── hash_algorithm.md │ │ │ ├── hash_collision.md │ │ │ ├── hash_map.md │ │ │ ├── index.md │ │ │ └── summary.md │ │ ├── chapter_heap/ │ │ │ ├── build_heap.md │ │ │ ├── heap.md │ │ │ ├── index.md │ │ │ ├── summary.md │ │ │ └── top_k.md │ │ ├── chapter_hello_algo/ │ │ │ └── index.md │ │ ├── chapter_introduction/ │ │ │ ├── algorithms_are_everywhere.md │ │ │ ├── index.md │ │ │ ├── summary.md │ │ │ └── what_is_dsa.md │ │ ├── chapter_preface/ │ │ │ ├── about_the_book.md │ │ │ ├── index.md │ │ │ ├── suggestions.md │ │ │ └── summary.md │ │ ├── chapter_reference/ │ │ │ └── index.md │ │ ├── chapter_searching/ │ │ │ ├── binary_search.md │ │ │ ├── binary_search_edge.md │ │ │ ├── binary_search_insertion.md │ │ │ ├── index.md │ │ │ ├── replace_linear_by_hashing.md │ │ │ ├── searching_algorithm_revisited.md │ │ │ └── summary.md │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.md │ │ │ ├── bucket_sort.md │ │ │ ├── counting_sort.md │ │ │ ├── heap_sort.md │ │ │ ├── index.md │ │ │ ├── insertion_sort.md │ │ │ ├── merge_sort.md │ │ │ ├── quick_sort.md │ │ │ ├── radix_sort.md │ │ │ ├── selection_sort.md │ │ │ ├── sorting_algorithm.md │ │ │ └── summary.md │ │ ├── chapter_stack_and_queue/ │ │ │ ├── deque.md │ │ │ ├── index.md │ │ │ ├── queue.md │ │ │ ├── stack.md │ │ │ └── summary.md │ │ ├── chapter_tree/ │ │ │ ├── array_representation_of_tree.md │ │ │ ├── avl_tree.md │ │ │ ├── binary_search_tree.md │ │ │ ├── binary_tree.md │ │ │ ├── binary_tree_traversal.md │ │ │ ├── index.md │ │ │ └── summary.md │ │ ├── index.html │ │ └── index.md │ └── mkdocs.yml ├── giscus.json ├── ja/ │ ├── README.md │ ├── codes/ │ │ ├── c/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array.c │ │ │ │ ├── linked_list.c │ │ │ │ └── my_list.c │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── n_queens.c │ │ │ │ ├── permutations_i.c │ │ │ │ ├── permutations_ii.c │ │ │ │ ├── preorder_traversal_i_compact.c │ │ │ │ ├── preorder_traversal_ii_compact.c │ │ │ │ ├── preorder_traversal_iii_compact.c │ │ │ │ ├── preorder_traversal_iii_template.c │ │ │ │ ├── subset_sum_i.c │ │ │ │ ├── subset_sum_i_naive.c │ │ │ │ └── subset_sum_ii.c │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── iteration.c │ │ │ │ ├── recursion.c │ │ │ │ ├── space_complexity.c │ │ │ │ ├── time_complexity.c │ │ │ │ └── worst_best_time_complexity.c │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── binary_search_recur.c │ │ │ │ ├── build_tree.c │ │ │ │ └── hanota.c │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── climbing_stairs_backtrack.c │ │ │ │ ├── climbing_stairs_constraint_dp.c │ │ │ │ ├── climbing_stairs_dfs.c │ │ │ │ ├── climbing_stairs_dfs_mem.c │ │ │ │ ├── climbing_stairs_dp.c │ │ │ │ ├── coin_change.c │ │ │ │ ├── coin_change_ii.c │ │ │ │ ├── edit_distance.c │ │ │ │ ├── knapsack.c │ │ │ │ ├── min_cost_climbing_stairs_dp.c │ │ │ │ ├── min_path_sum.c │ │ │ │ └── unbounded_knapsack.c │ │ │ ├── chapter_graph/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── graph_adjacency_list.c │ │ │ │ ├── graph_adjacency_list_test.c │ │ │ │ ├── graph_adjacency_matrix.c │ │ │ │ ├── graph_bfs.c │ │ │ │ └── graph_dfs.c │ │ │ ├── chapter_greedy/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── coin_change_greedy.c │ │ │ │ ├── fractional_knapsack.c │ │ │ │ ├── max_capacity.c │ │ │ │ └── max_product_cutting.c │ │ │ ├── chapter_hashing/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_hash_map.c │ │ │ │ ├── hash_map_chaining.c │ │ │ │ ├── hash_map_open_addressing.c │ │ │ │ └── simple_hash.c │ │ │ ├── chapter_heap/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── my_heap.c │ │ │ │ ├── my_heap_test.c │ │ │ │ └── top_k.c │ │ │ ├── chapter_searching/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── binary_search.c │ │ │ │ ├── binary_search_edge.c │ │ │ │ ├── binary_search_insertion.c │ │ │ │ └── two_sum.c │ │ │ ├── chapter_sorting/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── bubble_sort.c │ │ │ │ ├── bucket_sort.c │ │ │ │ ├── counting_sort.c │ │ │ │ ├── heap_sort.c │ │ │ │ ├── insertion_sort.c │ │ │ │ ├── merge_sort.c │ │ │ │ ├── quick_sort.c │ │ │ │ ├── radix_sort.c │ │ │ │ └── selection_sort.c │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_deque.c │ │ │ │ ├── array_queue.c │ │ │ │ ├── array_stack.c │ │ │ │ ├── linkedlist_deque.c │ │ │ │ ├── linkedlist_queue.c │ │ │ │ └── linkedlist_stack.c │ │ │ ├── chapter_tree/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_binary_tree.c │ │ │ │ ├── avl_tree.c │ │ │ │ ├── binary_search_tree.c │ │ │ │ ├── binary_tree.c │ │ │ │ ├── binary_tree_bfs.c │ │ │ │ └── binary_tree_dfs.c │ │ │ └── utils/ │ │ │ ├── CMakeLists.txt │ │ │ ├── common.h │ │ │ ├── common_test.c │ │ │ ├── list_node.h │ │ │ ├── print_util.h │ │ │ ├── tree_node.h │ │ │ ├── uthash.h │ │ │ ├── vector.h │ │ │ └── vertex.h │ │ ├── cpp/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array.cpp │ │ │ │ ├── linked_list.cpp │ │ │ │ ├── list.cpp │ │ │ │ └── my_list.cpp │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── n_queens.cpp │ │ │ │ ├── permutations_i.cpp │ │ │ │ ├── permutations_ii.cpp │ │ │ │ ├── preorder_traversal_i_compact.cpp │ │ │ │ ├── preorder_traversal_ii_compact.cpp │ │ │ │ ├── preorder_traversal_iii_compact.cpp │ │ │ │ ├── preorder_traversal_iii_template.cpp │ │ │ │ ├── subset_sum_i.cpp │ │ │ │ ├── subset_sum_i_naive.cpp │ │ │ │ └── subset_sum_ii.cpp │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── iteration.cpp │ │ │ │ ├── recursion.cpp │ │ │ │ ├── space_complexity.cpp │ │ │ │ ├── time_complexity.cpp │ │ │ │ └── worst_best_time_complexity.cpp │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── binary_search_recur.cpp │ │ │ │ ├── build_tree.cpp │ │ │ │ └── hanota.cpp │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── climbing_stairs_backtrack.cpp │ │ │ │ ├── climbing_stairs_constraint_dp.cpp │ │ │ │ ├── climbing_stairs_dfs.cpp │ │ │ │ ├── climbing_stairs_dfs_mem.cpp │ │ │ │ ├── climbing_stairs_dp.cpp │ │ │ │ ├── coin_change.cpp │ │ │ │ ├── coin_change_ii.cpp │ │ │ │ ├── edit_distance.cpp │ │ │ │ ├── knapsack.cpp │ │ │ │ ├── min_cost_climbing_stairs_dp.cpp │ │ │ │ ├── min_path_sum.cpp │ │ │ │ └── unbounded_knapsack.cpp │ │ │ ├── chapter_graph/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── graph_adjacency_list.cpp │ │ │ │ ├── graph_adjacency_list_test.cpp │ │ │ │ ├── graph_adjacency_matrix.cpp │ │ │ │ ├── graph_bfs.cpp │ │ │ │ └── graph_dfs.cpp │ │ │ ├── chapter_greedy/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── coin_change_greedy.cpp │ │ │ │ ├── fractional_knapsack.cpp │ │ │ │ ├── max_capacity.cpp │ │ │ │ └── max_product_cutting.cpp │ │ │ ├── chapter_hashing/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_hash_map.cpp │ │ │ │ ├── array_hash_map_test.cpp │ │ │ │ ├── built_in_hash.cpp │ │ │ │ ├── hash_map.cpp │ │ │ │ ├── hash_map_chaining.cpp │ │ │ │ ├── hash_map_open_addressing.cpp │ │ │ │ └── simple_hash.cpp │ │ │ ├── chapter_heap/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── heap.cpp │ │ │ │ ├── my_heap.cpp │ │ │ │ └── top_k.cpp │ │ │ ├── chapter_searching/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── binary_search.cpp │ │ │ │ ├── binary_search_edge.cpp │ │ │ │ ├── binary_search_insertion.cpp │ │ │ │ ├── hashing_search.cpp │ │ │ │ ├── linear_search.cpp │ │ │ │ └── two_sum.cpp │ │ │ ├── chapter_sorting/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── bubble_sort.cpp │ │ │ │ ├── bucket_sort.cpp │ │ │ │ ├── counting_sort.cpp │ │ │ │ ├── heap_sort.cpp │ │ │ │ ├── insertion_sort.cpp │ │ │ │ ├── merge_sort.cpp │ │ │ │ ├── quick_sort.cpp │ │ │ │ ├── radix_sort.cpp │ │ │ │ └── selection_sort.cpp │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_deque.cpp │ │ │ │ ├── array_queue.cpp │ │ │ │ ├── array_stack.cpp │ │ │ │ ├── deque.cpp │ │ │ │ ├── linkedlist_deque.cpp │ │ │ │ ├── linkedlist_queue.cpp │ │ │ │ ├── linkedlist_stack.cpp │ │ │ │ ├── queue.cpp │ │ │ │ └── stack.cpp │ │ │ ├── chapter_tree/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_binary_tree.cpp │ │ │ │ ├── avl_tree.cpp │ │ │ │ ├── binary_search_tree.cpp │ │ │ │ ├── binary_tree.cpp │ │ │ │ ├── binary_tree_bfs.cpp │ │ │ │ └── binary_tree_dfs.cpp │ │ │ └── utils/ │ │ │ ├── CMakeLists.txt │ │ │ ├── common.hpp │ │ │ ├── list_node.hpp │ │ │ ├── print_utils.hpp │ │ │ ├── tree_node.hpp │ │ │ └── vertex.hpp │ │ ├── csharp/ │ │ │ ├── .editorconfig │ │ │ ├── .gitignore │ │ │ ├── GlobalUsing.cs │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.cs │ │ │ │ ├── linked_list.cs │ │ │ │ ├── list.cs │ │ │ │ └── my_list.cs │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.cs │ │ │ │ ├── permutations_i.cs │ │ │ │ ├── permutations_ii.cs │ │ │ │ ├── preorder_traversal_i_compact.cs │ │ │ │ ├── preorder_traversal_ii_compact.cs │ │ │ │ ├── preorder_traversal_iii_compact.cs │ │ │ │ ├── preorder_traversal_iii_template.cs │ │ │ │ ├── subset_sum_i.cs │ │ │ │ ├── subset_sum_i_naive.cs │ │ │ │ └── subset_sum_ii.cs │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.cs │ │ │ │ ├── recursion.cs │ │ │ │ ├── space_complexity.cs │ │ │ │ ├── time_complexity.cs │ │ │ │ └── worst_best_time_complexity.cs │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.cs │ │ │ │ ├── build_tree.cs │ │ │ │ └── hanota.cs │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.cs │ │ │ │ ├── climbing_stairs_constraint_dp.cs │ │ │ │ ├── climbing_stairs_dfs.cs │ │ │ │ ├── climbing_stairs_dfs_mem.cs │ │ │ │ ├── climbing_stairs_dp.cs │ │ │ │ ├── coin_change.cs │ │ │ │ ├── coin_change_ii.cs │ │ │ │ ├── edit_distance.cs │ │ │ │ ├── knapsack.cs │ │ │ │ ├── min_cost_climbing_stairs_dp.cs │ │ │ │ ├── min_path_sum.cs │ │ │ │ └── unbounded_knapsack.cs │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.cs │ │ │ │ ├── graph_adjacency_matrix.cs │ │ │ │ ├── graph_bfs.cs │ │ │ │ └── graph_dfs.cs │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.cs │ │ │ │ ├── fractional_knapsack.cs │ │ │ │ ├── max_capacity.cs │ │ │ │ └── max_product_cutting.cs │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.cs │ │ │ │ ├── built_in_hash.cs │ │ │ │ ├── hash_map.cs │ │ │ │ ├── hash_map_chaining.cs │ │ │ │ ├── hash_map_open_addressing.cs │ │ │ │ └── simple_hash.cs │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.cs │ │ │ │ ├── my_heap.cs │ │ │ │ └── top_k.cs │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.cs │ │ │ │ ├── binary_search_edge.cs │ │ │ │ ├── binary_search_insertion.cs │ │ │ │ ├── hashing_search.cs │ │ │ │ ├── linear_search.cs │ │ │ │ └── two_sum.cs │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.cs │ │ │ │ ├── bucket_sort.cs │ │ │ │ ├── counting_sort.cs │ │ │ │ ├── heap_sort.cs │ │ │ │ ├── insertion_sort.cs │ │ │ │ ├── merge_sort.cs │ │ │ │ ├── quick_sort.cs │ │ │ │ ├── radix_sort.cs │ │ │ │ └── selection_sort.cs │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.cs │ │ │ │ ├── array_queue.cs │ │ │ │ ├── array_stack.cs │ │ │ │ ├── deque.cs │ │ │ │ ├── linkedlist_deque.cs │ │ │ │ ├── linkedlist_queue.cs │ │ │ │ ├── linkedlist_stack.cs │ │ │ │ ├── queue.cs │ │ │ │ └── stack.cs │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.cs │ │ │ │ ├── avl_tree.cs │ │ │ │ ├── binary_search_tree.cs │ │ │ │ ├── binary_tree.cs │ │ │ │ ├── binary_tree_bfs.cs │ │ │ │ └── binary_tree_dfs.cs │ │ │ ├── csharp.sln │ │ │ ├── hello-algo.csproj │ │ │ └── utils/ │ │ │ ├── ListNode.cs │ │ │ ├── PrintUtil.cs │ │ │ ├── TreeNode.cs │ │ │ └── Vertex.cs │ │ ├── dart/ │ │ │ ├── build.dart │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.dart │ │ │ │ ├── linked_list.dart │ │ │ │ ├── list.dart │ │ │ │ └── my_list.dart │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.dart │ │ │ │ ├── permutations_i.dart │ │ │ │ ├── permutations_ii.dart │ │ │ │ ├── preorder_traversal_i_compact.dart │ │ │ │ ├── preorder_traversal_ii_compact.dart │ │ │ │ ├── preorder_traversal_iii_compact.dart │ │ │ │ ├── preorder_traversal_iii_template.dart │ │ │ │ ├── subset_sum_i.dart │ │ │ │ ├── subset_sum_i_naive.dart │ │ │ │ └── subset_sum_ii.dart │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.dart │ │ │ │ ├── recursion.dart │ │ │ │ ├── space_complexity.dart │ │ │ │ ├── time_complexity.dart │ │ │ │ └── worst_best_time_complexity.dart │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.dart │ │ │ │ ├── build_tree.dart │ │ │ │ └── hanota.dart │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.dart │ │ │ │ ├── climbing_stairs_constraint_dp.dart │ │ │ │ ├── climbing_stairs_dfs.dart │ │ │ │ ├── climbing_stairs_dfs_mem.dart │ │ │ │ ├── climbing_stairs_dp.dart │ │ │ │ ├── coin_change.dart │ │ │ │ ├── coin_change_ii.dart │ │ │ │ ├── edit_distance.dart │ │ │ │ ├── knapsack.dart │ │ │ │ ├── min_cost_climbing_stairs_dp.dart │ │ │ │ ├── min_path_sum.dart │ │ │ │ └── unbounded_knapsack.dart │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.dart │ │ │ │ ├── graph_adjacency_matrix.dart │ │ │ │ ├── graph_bfs.dart │ │ │ │ └── graph_dfs.dart │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.dart │ │ │ │ ├── fractional_knapsack.dart │ │ │ │ ├── max_capacity.dart │ │ │ │ └── max_product_cutting.dart │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.dart │ │ │ │ ├── built_in_hash.dart │ │ │ │ ├── hash_map.dart │ │ │ │ ├── hash_map_chaining.dart │ │ │ │ ├── hash_map_open_addressing.dart │ │ │ │ └── simple_hash.dart │ │ │ ├── chapter_heap/ │ │ │ │ ├── my_heap.dart │ │ │ │ └── top_k.dart │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.dart │ │ │ │ ├── binary_search_edge.dart │ │ │ │ ├── binary_search_insertion.dart │ │ │ │ ├── hashing_search.dart │ │ │ │ ├── linear_search.dart │ │ │ │ └── two_sum.dart │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.dart │ │ │ │ ├── bucket_sort.dart │ │ │ │ ├── counting_sort.dart │ │ │ │ ├── heap_sort.dart │ │ │ │ ├── insertion_sort.dart │ │ │ │ ├── merge_sort.dart │ │ │ │ ├── quick_sort.dart │ │ │ │ ├── radix_sort.dart │ │ │ │ └── selection_sort.dart │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.dart │ │ │ │ ├── array_queue.dart │ │ │ │ ├── array_stack.dart │ │ │ │ ├── deque.dart │ │ │ │ ├── linkedlist_deque.dart │ │ │ │ ├── linkedlist_queue.dart │ │ │ │ ├── linkedlist_stack.dart │ │ │ │ ├── queue.dart │ │ │ │ └── stack.dart │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.dart │ │ │ │ ├── avl_tree.dart │ │ │ │ ├── binary_search_tree.dart │ │ │ │ ├── binary_tree.dart │ │ │ │ ├── binary_tree_bfs.dart │ │ │ │ └── binary_tree_dfs.dart │ │ │ └── utils/ │ │ │ ├── list_node.dart │ │ │ ├── print_util.dart │ │ │ ├── tree_node.dart │ │ │ └── vertex.dart │ │ ├── go/ │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.go │ │ │ │ ├── array_test.go │ │ │ │ ├── linked_list.go │ │ │ │ ├── linked_list_test.go │ │ │ │ ├── list_test.go │ │ │ │ ├── my_list.go │ │ │ │ └── my_list_test.go │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.go │ │ │ │ ├── n_queens_test.go │ │ │ │ ├── permutation_test.go │ │ │ │ ├── permutations_i.go │ │ │ │ ├── permutations_ii.go │ │ │ │ ├── preorder_traversal_i_compact.go │ │ │ │ ├── preorder_traversal_ii_compact.go │ │ │ │ ├── preorder_traversal_iii_compact.go │ │ │ │ ├── preorder_traversal_iii_template.go │ │ │ │ ├── preorder_traversal_test.go │ │ │ │ ├── subset_sum_i.go │ │ │ │ ├── subset_sum_i_naive.go │ │ │ │ ├── subset_sum_ii.go │ │ │ │ └── subset_sum_test.go │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.go │ │ │ │ ├── iteration_test.go │ │ │ │ ├── recursion.go │ │ │ │ ├── recursion_test.go │ │ │ │ ├── space_complexity.go │ │ │ │ ├── space_complexity_test.go │ │ │ │ ├── time_complexity.go │ │ │ │ ├── time_complexity_test.go │ │ │ │ ├── worst_best_time_complexity.go │ │ │ │ └── worst_best_time_complexity_test.go │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.go │ │ │ │ ├── binary_search_recur_test.go │ │ │ │ ├── build_tree.go │ │ │ │ ├── build_tree_test.go │ │ │ │ ├── hanota.go │ │ │ │ └── hanota_test.go │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.go │ │ │ │ ├── climbing_stairs_constraint_dp.go │ │ │ │ ├── climbing_stairs_dfs.go │ │ │ │ ├── climbing_stairs_dfs_mem.go │ │ │ │ ├── climbing_stairs_dp.go │ │ │ │ ├── climbing_stairs_test.go │ │ │ │ ├── coin_change.go │ │ │ │ ├── coin_change_ii.go │ │ │ │ ├── coin_change_test.go │ │ │ │ ├── edit_distance.go │ │ │ │ ├── edit_distance_test.go │ │ │ │ ├── knapsack.go │ │ │ │ ├── knapsack_test.go │ │ │ │ ├── min_cost_climbing_stairs_dp.go │ │ │ │ ├── min_path_sum.go │ │ │ │ ├── min_path_sum_test.go │ │ │ │ └── unbounded_knapsack.go │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.go │ │ │ │ ├── graph_adjacency_list_test.go │ │ │ │ ├── graph_adjacency_matrix.go │ │ │ │ ├── graph_adjacency_matrix_test.go │ │ │ │ ├── graph_bfs.go │ │ │ │ ├── graph_bfs_test.go │ │ │ │ ├── graph_dfs.go │ │ │ │ └── graph_dfs_test.go │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.go │ │ │ │ ├── coin_change_greedy_test.go │ │ │ │ ├── fractional_knapsack.go │ │ │ │ ├── fractional_knapsack_test.go │ │ │ │ ├── max_capacity.go │ │ │ │ ├── max_capacity_test.go │ │ │ │ ├── max_product_cutting.go │ │ │ │ └── max_product_cutting_test.go │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.go │ │ │ │ ├── array_hash_map_test.go │ │ │ │ ├── hash_collision_test.go │ │ │ │ ├── hash_map_chaining.go │ │ │ │ ├── hash_map_open_addressing.go │ │ │ │ ├── hash_map_test.go │ │ │ │ └── simple_hash.go │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.go │ │ │ │ ├── heap_test.go │ │ │ │ ├── my_heap.go │ │ │ │ └── top_k.go │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.go │ │ │ │ ├── binary_search_edge.go │ │ │ │ ├── binary_search_insertion.go │ │ │ │ ├── binary_search_test.go │ │ │ │ ├── hashing_search.go │ │ │ │ ├── hashing_search_test.go │ │ │ │ ├── linear_search.go │ │ │ │ ├── linear_search_test.go │ │ │ │ ├── two_sum.go │ │ │ │ └── two_sum_test.go │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.go │ │ │ │ ├── bubble_sort_test.go │ │ │ │ ├── bucket_sort.go │ │ │ │ ├── bucket_sort_test.go │ │ │ │ ├── counting_sort.go │ │ │ │ ├── counting_sort_test.go │ │ │ │ ├── heap_sort.go │ │ │ │ ├── heap_sort_test.go │ │ │ │ ├── insertion_sort.go │ │ │ │ ├── insertion_sort_test.go │ │ │ │ ├── merge_sort.go │ │ │ │ ├── merge_sort_test.go │ │ │ │ ├── quick_sort.go │ │ │ │ ├── quick_sort_test.go │ │ │ │ ├── radix_sort.go │ │ │ │ ├── radix_sort_test.go │ │ │ │ ├── selection_sort.go │ │ │ │ └── selection_sort_test.go │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.go │ │ │ │ ├── array_queue.go │ │ │ │ ├── array_stack.go │ │ │ │ ├── deque_test.go │ │ │ │ ├── linkedlist_deque.go │ │ │ │ ├── linkedlist_queue.go │ │ │ │ ├── linkedlist_stack.go │ │ │ │ ├── queue_test.go │ │ │ │ └── stack_test.go │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.go │ │ │ │ ├── array_binary_tree_test.go │ │ │ │ ├── avl_tree.go │ │ │ │ ├── avl_tree_test.go │ │ │ │ ├── binary_search_tree.go │ │ │ │ ├── binary_search_tree_test.go │ │ │ │ ├── binary_tree_bfs.go │ │ │ │ ├── binary_tree_bfs_test.go │ │ │ │ ├── binary_tree_dfs.go │ │ │ │ ├── binary_tree_dfs_test.go │ │ │ │ └── binary_tree_test.go │ │ │ ├── go.mod │ │ │ └── pkg/ │ │ │ ├── list_node.go │ │ │ ├── list_node_test.go │ │ │ ├── print_utils.go │ │ │ ├── tree_node.go │ │ │ ├── tree_node_test.go │ │ │ └── vertex.go │ │ ├── java/ │ │ │ ├── .gitignore │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.java │ │ │ │ ├── linked_list.java │ │ │ │ ├── list.java │ │ │ │ └── my_list.java │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.java │ │ │ │ ├── permutations_i.java │ │ │ │ ├── permutations_ii.java │ │ │ │ ├── preorder_traversal_i_compact.java │ │ │ │ ├── preorder_traversal_ii_compact.java │ │ │ │ ├── preorder_traversal_iii_compact.java │ │ │ │ ├── preorder_traversal_iii_template.java │ │ │ │ ├── subset_sum_i.java │ │ │ │ ├── subset_sum_i_naive.java │ │ │ │ └── subset_sum_ii.java │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.java │ │ │ │ ├── recursion.java │ │ │ │ ├── space_complexity.java │ │ │ │ ├── time_complexity.java │ │ │ │ └── worst_best_time_complexity.java │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.java │ │ │ │ ├── build_tree.java │ │ │ │ └── hanota.java │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.java │ │ │ │ ├── climbing_stairs_constraint_dp.java │ │ │ │ ├── climbing_stairs_dfs.java │ │ │ │ ├── climbing_stairs_dfs_mem.java │ │ │ │ ├── climbing_stairs_dp.java │ │ │ │ ├── coin_change.java │ │ │ │ ├── coin_change_ii.java │ │ │ │ ├── edit_distance.java │ │ │ │ ├── knapsack.java │ │ │ │ ├── min_cost_climbing_stairs_dp.java │ │ │ │ ├── min_path_sum.java │ │ │ │ └── unbounded_knapsack.java │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.java │ │ │ │ ├── graph_adjacency_matrix.java │ │ │ │ ├── graph_bfs.java │ │ │ │ └── graph_dfs.java │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.java │ │ │ │ ├── fractional_knapsack.java │ │ │ │ ├── max_capacity.java │ │ │ │ └── max_product_cutting.java │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.java │ │ │ │ ├── built_in_hash.java │ │ │ │ ├── hash_map.java │ │ │ │ ├── hash_map_chaining.java │ │ │ │ ├── hash_map_open_addressing.java │ │ │ │ └── simple_hash.java │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.java │ │ │ │ ├── my_heap.java │ │ │ │ └── top_k.java │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.java │ │ │ │ ├── binary_search_edge.java │ │ │ │ ├── binary_search_insertion.java │ │ │ │ ├── hashing_search.java │ │ │ │ ├── linear_search.java │ │ │ │ └── two_sum.java │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.java │ │ │ │ ├── bucket_sort.java │ │ │ │ ├── counting_sort.java │ │ │ │ ├── heap_sort.java │ │ │ │ ├── insertion_sort.java │ │ │ │ ├── merge_sort.java │ │ │ │ ├── quick_sort.java │ │ │ │ ├── radix_sort.java │ │ │ │ └── selection_sort.java │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.java │ │ │ │ ├── array_queue.java │ │ │ │ ├── array_stack.java │ │ │ │ ├── deque.java │ │ │ │ ├── linkedlist_deque.java │ │ │ │ ├── linkedlist_queue.java │ │ │ │ ├── linkedlist_stack.java │ │ │ │ ├── queue.java │ │ │ │ └── stack.java │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.java │ │ │ │ ├── avl_tree.java │ │ │ │ ├── binary_search_tree.java │ │ │ │ ├── binary_tree.java │ │ │ │ ├── binary_tree_bfs.java │ │ │ │ └── binary_tree_dfs.java │ │ │ └── utils/ │ │ │ ├── ListNode.java │ │ │ ├── PrintUtil.java │ │ │ ├── TreeNode.java │ │ │ └── Vertex.java │ │ ├── javascript/ │ │ │ ├── .prettierrc │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.js │ │ │ │ ├── linked_list.js │ │ │ │ ├── list.js │ │ │ │ └── my_list.js │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.js │ │ │ │ ├── permutations_i.js │ │ │ │ ├── permutations_ii.js │ │ │ │ ├── preorder_traversal_i_compact.js │ │ │ │ ├── preorder_traversal_ii_compact.js │ │ │ │ ├── preorder_traversal_iii_compact.js │ │ │ │ ├── preorder_traversal_iii_template.js │ │ │ │ ├── subset_sum_i.js │ │ │ │ ├── subset_sum_i_naive.js │ │ │ │ └── subset_sum_ii.js │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.js │ │ │ │ ├── recursion.js │ │ │ │ ├── space_complexity.js │ │ │ │ ├── time_complexity.js │ │ │ │ └── worst_best_time_complexity.js │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.js │ │ │ │ ├── build_tree.js │ │ │ │ └── hanota.js │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.js │ │ │ │ ├── climbing_stairs_constraint_dp.js │ │ │ │ ├── climbing_stairs_dfs.js │ │ │ │ ├── climbing_stairs_dfs_mem.js │ │ │ │ ├── climbing_stairs_dp.js │ │ │ │ ├── coin_change.js │ │ │ │ ├── coin_change_ii.js │ │ │ │ ├── edit_distance.js │ │ │ │ ├── knapsack.js │ │ │ │ ├── min_cost_climbing_stairs_dp.js │ │ │ │ ├── min_path_sum.js │ │ │ │ └── unbounded_knapsack.js │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.js │ │ │ │ ├── graph_adjacency_matrix.js │ │ │ │ ├── graph_bfs.js │ │ │ │ └── graph_dfs.js │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.js │ │ │ │ ├── fractional_knapsack.js │ │ │ │ ├── max_capacity.js │ │ │ │ └── max_product_cutting.js │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.js │ │ │ │ ├── hash_map.js │ │ │ │ ├── hash_map_chaining.js │ │ │ │ ├── hash_map_open_addressing.js │ │ │ │ └── simple_hash.js │ │ │ ├── chapter_heap/ │ │ │ │ ├── my_heap.js │ │ │ │ └── top_k.js │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.js │ │ │ │ ├── binary_search_edge.js │ │ │ │ ├── binary_search_insertion.js │ │ │ │ ├── hashing_search.js │ │ │ │ ├── linear_search.js │ │ │ │ └── two_sum.js │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.js │ │ │ │ ├── bucket_sort.js │ │ │ │ ├── counting_sort.js │ │ │ │ ├── heap_sort.js │ │ │ │ ├── insertion_sort.js │ │ │ │ ├── merge_sort.js │ │ │ │ ├── quick_sort.js │ │ │ │ ├── radix_sort.js │ │ │ │ └── selection_sort.js │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.js │ │ │ │ ├── array_queue.js │ │ │ │ ├── array_stack.js │ │ │ │ ├── deque.js │ │ │ │ ├── linkedlist_deque.js │ │ │ │ ├── linkedlist_queue.js │ │ │ │ ├── linkedlist_stack.js │ │ │ │ ├── queue.js │ │ │ │ └── stack.js │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.js │ │ │ │ ├── avl_tree.js │ │ │ │ ├── binary_search_tree.js │ │ │ │ ├── binary_tree.js │ │ │ │ ├── binary_tree_bfs.js │ │ │ │ └── binary_tree_dfs.js │ │ │ ├── modules/ │ │ │ │ ├── ListNode.js │ │ │ │ ├── PrintUtil.js │ │ │ │ ├── TreeNode.js │ │ │ │ └── Vertex.js │ │ │ └── test_all.js │ │ ├── kotlin/ │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.kt │ │ │ │ ├── linked_list.kt │ │ │ │ ├── list.kt │ │ │ │ └── my_list.kt │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.kt │ │ │ │ ├── permutations_i.kt │ │ │ │ ├── permutations_ii.kt │ │ │ │ ├── preorder_traversal_i_compact.kt │ │ │ │ ├── preorder_traversal_ii_compact.kt │ │ │ │ ├── preorder_traversal_iii_compact.kt │ │ │ │ ├── preorder_traversal_iii_template.kt │ │ │ │ ├── subset_sum_i.kt │ │ │ │ ├── subset_sum_i_naive.kt │ │ │ │ └── subset_sum_ii.kt │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.kt │ │ │ │ ├── recursion.kt │ │ │ │ ├── space_complexity.kt │ │ │ │ ├── time_complexity.kt │ │ │ │ └── worst_best_time_complexity.kt │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.kt │ │ │ │ ├── build_tree.kt │ │ │ │ └── hanota.kt │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.kt │ │ │ │ ├── climbing_stairs_constraint_dp.kt │ │ │ │ ├── climbing_stairs_dfs.kt │ │ │ │ ├── climbing_stairs_dfs_mem.kt │ │ │ │ ├── climbing_stairs_dp.kt │ │ │ │ ├── coin_change.kt │ │ │ │ ├── coin_change_ii.kt │ │ │ │ ├── edit_distance.kt │ │ │ │ ├── knapsack.kt │ │ │ │ ├── min_cost_climbing_stairs_dp.kt │ │ │ │ ├── min_path_sum.kt │ │ │ │ └── unbounded_knapsack.kt │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.kt │ │ │ │ ├── graph_adjacency_matrix.kt │ │ │ │ ├── graph_bfs.kt │ │ │ │ └── graph_dfs.kt │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.kt │ │ │ │ ├── fractional_knapsack.kt │ │ │ │ ├── max_capacity.kt │ │ │ │ └── max_product_cutting.kt │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.kt │ │ │ │ ├── built_in_hash.kt │ │ │ │ ├── hash_map.kt │ │ │ │ ├── hash_map_chaining.kt │ │ │ │ ├── hash_map_open_addressing.kt │ │ │ │ └── simple_hash.kt │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.kt │ │ │ │ ├── my_heap.kt │ │ │ │ └── top_k.kt │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.kt │ │ │ │ ├── binary_search_edge.kt │ │ │ │ ├── binary_search_insertion.kt │ │ │ │ ├── hashing_search.kt │ │ │ │ ├── linear_search.kt │ │ │ │ └── two_sum.kt │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.kt │ │ │ │ ├── bucket_sort.kt │ │ │ │ ├── counting_sort.kt │ │ │ │ ├── heap_sort.kt │ │ │ │ ├── insertion_sort.kt │ │ │ │ ├── merge_sort.kt │ │ │ │ ├── quick_sort.kt │ │ │ │ ├── radix_sort.kt │ │ │ │ └── selection_sort.kt │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.kt │ │ │ │ ├── array_queue.kt │ │ │ │ ├── array_stack.kt │ │ │ │ ├── deque.kt │ │ │ │ ├── linkedlist_deque.kt │ │ │ │ ├── linkedlist_queue.kt │ │ │ │ ├── linkedlist_stack.kt │ │ │ │ ├── queue.kt │ │ │ │ └── stack.kt │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.kt │ │ │ │ ├── avl_tree.kt │ │ │ │ ├── binary_search_tree.kt │ │ │ │ ├── binary_tree.kt │ │ │ │ ├── binary_tree_bfs.kt │ │ │ │ └── binary_tree_dfs.kt │ │ │ └── utils/ │ │ │ ├── ListNode.kt │ │ │ ├── PrintUtil.kt │ │ │ ├── TreeNode.kt │ │ │ └── Vertex.kt │ │ ├── python/ │ │ │ ├── .gitignore │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.py │ │ │ │ ├── linked_list.py │ │ │ │ ├── list.py │ │ │ │ └── my_list.py │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.py │ │ │ │ ├── permutations_i.py │ │ │ │ ├── permutations_ii.py │ │ │ │ ├── preorder_traversal_i_compact.py │ │ │ │ ├── preorder_traversal_ii_compact.py │ │ │ │ ├── preorder_traversal_iii_compact.py │ │ │ │ ├── preorder_traversal_iii_template.py │ │ │ │ ├── subset_sum_i.py │ │ │ │ ├── subset_sum_i_naive.py │ │ │ │ └── subset_sum_ii.py │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.py │ │ │ │ ├── recursion.py │ │ │ │ ├── space_complexity.py │ │ │ │ ├── time_complexity.py │ │ │ │ └── worst_best_time_complexity.py │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.py │ │ │ │ ├── build_tree.py │ │ │ │ └── hanota.py │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.py │ │ │ │ ├── climbing_stairs_constraint_dp.py │ │ │ │ ├── climbing_stairs_dfs.py │ │ │ │ ├── climbing_stairs_dfs_mem.py │ │ │ │ ├── climbing_stairs_dp.py │ │ │ │ ├── coin_change.py │ │ │ │ ├── coin_change_ii.py │ │ │ │ ├── edit_distance.py │ │ │ │ ├── knapsack.py │ │ │ │ ├── min_cost_climbing_stairs_dp.py │ │ │ │ ├── min_path_sum.py │ │ │ │ └── unbounded_knapsack.py │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.py │ │ │ │ ├── graph_adjacency_matrix.py │ │ │ │ ├── graph_bfs.py │ │ │ │ └── graph_dfs.py │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.py │ │ │ │ ├── fractional_knapsack.py │ │ │ │ ├── max_capacity.py │ │ │ │ └── max_product_cutting.py │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.py │ │ │ │ ├── built_in_hash.py │ │ │ │ ├── hash_map.py │ │ │ │ ├── hash_map_chaining.py │ │ │ │ ├── hash_map_open_addressing.py │ │ │ │ └── simple_hash.py │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.py │ │ │ │ ├── my_heap.py │ │ │ │ └── top_k.py │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.py │ │ │ │ ├── binary_search_edge.py │ │ │ │ ├── binary_search_insertion.py │ │ │ │ ├── hashing_search.py │ │ │ │ ├── linear_search.py │ │ │ │ └── two_sum.py │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.py │ │ │ │ ├── bucket_sort.py │ │ │ │ ├── counting_sort.py │ │ │ │ ├── heap_sort.py │ │ │ │ ├── insertion_sort.py │ │ │ │ ├── merge_sort.py │ │ │ │ ├── quick_sort.py │ │ │ │ ├── radix_sort.py │ │ │ │ └── selection_sort.py │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.py │ │ │ │ ├── array_queue.py │ │ │ │ ├── array_stack.py │ │ │ │ ├── deque.py │ │ │ │ ├── linkedlist_deque.py │ │ │ │ ├── linkedlist_queue.py │ │ │ │ ├── linkedlist_stack.py │ │ │ │ ├── queue.py │ │ │ │ └── stack.py │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.py │ │ │ │ ├── avl_tree.py │ │ │ │ ├── binary_search_tree.py │ │ │ │ ├── binary_tree.py │ │ │ │ ├── binary_tree_bfs.py │ │ │ │ └── binary_tree_dfs.py │ │ │ ├── modules/ │ │ │ │ ├── __init__.py │ │ │ │ ├── list_node.py │ │ │ │ ├── print_util.py │ │ │ │ ├── tree_node.py │ │ │ │ └── vertex.py │ │ │ └── test_all.py │ │ ├── pythontutor/ │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.md │ │ │ │ ├── linked_list.md │ │ │ │ └── my_list.md │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.md │ │ │ │ ├── permutations_i.md │ │ │ │ ├── permutations_ii.md │ │ │ │ ├── preorder_traversal_i_compact.md │ │ │ │ ├── preorder_traversal_ii_compact.md │ │ │ │ ├── preorder_traversal_iii_compact.md │ │ │ │ ├── preorder_traversal_iii_template.md │ │ │ │ ├── subset_sum_i.md │ │ │ │ ├── subset_sum_i_naive.md │ │ │ │ └── subset_sum_ii.md │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.md │ │ │ │ ├── recursion.md │ │ │ │ ├── space_complexity.md │ │ │ │ ├── time_complexity.md │ │ │ │ └── worst_best_time_complexity.md │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.md │ │ │ │ ├── build_tree.md │ │ │ │ └── hanota.md │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.md │ │ │ │ ├── climbing_stairs_constraint_dp.md │ │ │ │ ├── climbing_stairs_dfs.md │ │ │ │ ├── climbing_stairs_dfs_mem.md │ │ │ │ ├── climbing_stairs_dp.md │ │ │ │ ├── coin_change.md │ │ │ │ ├── coin_change_ii.md │ │ │ │ ├── edit_distance.md │ │ │ │ ├── knapsack.md │ │ │ │ ├── min_cost_climbing_stairs_dp.md │ │ │ │ ├── min_path_sum.md │ │ │ │ └── unbounded_knapsack.md │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.md │ │ │ │ ├── graph_adjacency_matrix.md │ │ │ │ ├── graph_bfs.md │ │ │ │ └── graph_dfs.md │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.md │ │ │ │ ├── fractional_knapsack.md │ │ │ │ ├── max_capacity.md │ │ │ │ └── max_product_cutting.md │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.md │ │ │ │ ├── hash_map_chaining.md │ │ │ │ └── simple_hash.md │ │ │ ├── chapter_heap/ │ │ │ │ ├── my_heap.md │ │ │ │ └── top_k.md │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.md │ │ │ │ ├── binary_search_edge.md │ │ │ │ ├── binary_search_insertion.md │ │ │ │ └── two_sum.md │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.md │ │ │ │ ├── bucket_sort.md │ │ │ │ ├── counting_sort.md │ │ │ │ ├── heap_sort.md │ │ │ │ ├── insertion_sort.md │ │ │ │ ├── merge_sort.md │ │ │ │ ├── quick_sort.md │ │ │ │ ├── radix_sort.md │ │ │ │ └── selection_sort.md │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_queue.md │ │ │ │ ├── array_stack.md │ │ │ │ ├── linkedlist_queue.md │ │ │ │ └── linkedlist_stack.md │ │ │ └── chapter_tree/ │ │ │ ├── array_binary_tree.md │ │ │ ├── binary_search_tree.md │ │ │ ├── binary_tree_bfs.md │ │ │ └── binary_tree_dfs.md │ │ ├── ruby/ │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.rb │ │ │ │ ├── linked_list.rb │ │ │ │ ├── list.rb │ │ │ │ └── my_list.rb │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.rb │ │ │ │ ├── permutations_i.rb │ │ │ │ ├── permutations_ii.rb │ │ │ │ ├── preorder_traversal_i_compact.rb │ │ │ │ ├── preorder_traversal_ii_compact.rb │ │ │ │ ├── preorder_traversal_iii_compact.rb │ │ │ │ ├── preorder_traversal_iii_template.rb │ │ │ │ ├── subset_sum_i.rb │ │ │ │ ├── subset_sum_i_naive.rb │ │ │ │ └── subset_sum_ii.rb │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.rb │ │ │ │ ├── recursion.rb │ │ │ │ ├── space_complexity.rb │ │ │ │ ├── time_complexity.rb │ │ │ │ └── worst_best_time_complexity.rb │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.rb │ │ │ │ ├── build_tree.rb │ │ │ │ └── hanota.rb │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.rb │ │ │ │ ├── climbing_stairs_constraint_dp.rb │ │ │ │ ├── climbing_stairs_dfs.rb │ │ │ │ ├── climbing_stairs_dfs_mem.rb │ │ │ │ ├── climbing_stairs_dp.rb │ │ │ │ ├── coin_change.rb │ │ │ │ ├── coin_change_ii.rb │ │ │ │ ├── edit_distance.rb │ │ │ │ ├── knapsack.rb │ │ │ │ ├── min_cost_climbing_stairs_dp.rb │ │ │ │ ├── min_path_sum.rb │ │ │ │ └── unbounded_knapsack.rb │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.rb │ │ │ │ ├── graph_adjacency_matrix.rb │ │ │ │ ├── graph_bfs.rb │ │ │ │ └── graph_dfs.rb │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.rb │ │ │ │ ├── fractional_knapsack.rb │ │ │ │ ├── max_capacity.rb │ │ │ │ └── max_product_cutting.rb │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.rb │ │ │ │ ├── built_in_hash.rb │ │ │ │ ├── hash_map.rb │ │ │ │ ├── hash_map_chaining.rb │ │ │ │ ├── hash_map_open_addressing.rb │ │ │ │ └── simple_hash.rb │ │ │ ├── chapter_heap/ │ │ │ │ ├── my_heap.rb │ │ │ │ └── top_k.rb │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.rb │ │ │ │ ├── binary_search_edge.rb │ │ │ │ ├── binary_search_insertion.rb │ │ │ │ ├── hashing_search.rb │ │ │ │ ├── linear_search.rb │ │ │ │ └── two_sum.rb │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.rb │ │ │ │ ├── bucket_sort.rb │ │ │ │ ├── counting_sort.rb │ │ │ │ ├── heap_sort.rb │ │ │ │ ├── insertion_sort.rb │ │ │ │ ├── merge_sort.rb │ │ │ │ ├── quick_sort.rb │ │ │ │ ├── radix_sort.rb │ │ │ │ └── selection_sort.rb │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.rb │ │ │ │ ├── array_queue.rb │ │ │ │ ├── array_stack.rb │ │ │ │ ├── deque.rb │ │ │ │ ├── linkedlist_deque.rb │ │ │ │ ├── linkedlist_queue.rb │ │ │ │ ├── linkedlist_stack.rb │ │ │ │ ├── queue.rb │ │ │ │ └── stack.rb │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.rb │ │ │ │ ├── avl_tree.rb │ │ │ │ ├── binary_search_tree.rb │ │ │ │ ├── binary_tree.rb │ │ │ │ ├── binary_tree_bfs.rb │ │ │ │ └── binary_tree_dfs.rb │ │ │ ├── test_all.rb │ │ │ └── utils/ │ │ │ ├── list_node.rb │ │ │ ├── print_util.rb │ │ │ ├── tree_node.rb │ │ │ └── vertex.rb │ │ ├── rust/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.rs │ │ │ │ ├── linked_list.rs │ │ │ │ ├── list.rs │ │ │ │ └── my_list.rs │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.rs │ │ │ │ ├── permutations_i.rs │ │ │ │ ├── permutations_ii.rs │ │ │ │ ├── preorder_traversal_i_compact.rs │ │ │ │ ├── preorder_traversal_ii_compact.rs │ │ │ │ ├── preorder_traversal_iii_compact.rs │ │ │ │ ├── preorder_traversal_iii_template.rs │ │ │ │ ├── subset_sum_i.rs │ │ │ │ ├── subset_sum_i_naive.rs │ │ │ │ └── subset_sum_ii.rs │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.rs │ │ │ │ ├── recursion.rs │ │ │ │ ├── space_complexity.rs │ │ │ │ ├── time_complexity.rs │ │ │ │ └── worst_best_time_complexity.rs │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.rs │ │ │ │ ├── build_tree.rs │ │ │ │ └── hanota.rs │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.rs │ │ │ │ ├── climbing_stairs_constraint_dp.rs │ │ │ │ ├── climbing_stairs_dfs.rs │ │ │ │ ├── climbing_stairs_dfs_mem.rs │ │ │ │ ├── climbing_stairs_dp.rs │ │ │ │ ├── coin_change.rs │ │ │ │ ├── coin_change_ii.rs │ │ │ │ ├── edit_distance.rs │ │ │ │ ├── knapsack.rs │ │ │ │ ├── min_cost_climbing_stairs_dp.rs │ │ │ │ ├── min_path_sum.rs │ │ │ │ └── unbounded_knapsack.rs │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.rs │ │ │ │ ├── graph_adjacency_matrix.rs │ │ │ │ ├── graph_bfs.rs │ │ │ │ └── graph_dfs.rs │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.rs │ │ │ │ ├── fractional_knapsack.rs │ │ │ │ ├── max_capacity.rs │ │ │ │ └── max_product_cutting.rs │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.rs │ │ │ │ ├── build_in_hash.rs │ │ │ │ ├── hash_map.rs │ │ │ │ ├── hash_map_chaining.rs │ │ │ │ ├── hash_map_open_addressing.rs │ │ │ │ └── simple_hash.rs │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.rs │ │ │ │ ├── my_heap.rs │ │ │ │ └── top_k.rs │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.rs │ │ │ │ ├── binary_search_edge.rs │ │ │ │ ├── binary_search_insertion.rs │ │ │ │ ├── hashing_search.rs │ │ │ │ ├── linear_search.rs │ │ │ │ └── two_sum.rs │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.rs │ │ │ │ ├── bucket_sort.rs │ │ │ │ ├── counting_sort.rs │ │ │ │ ├── heap_sort.rs │ │ │ │ ├── insertion_sort.rs │ │ │ │ ├── merge_sort.rs │ │ │ │ ├── quick_sort.rs │ │ │ │ ├── radix_sort.rs │ │ │ │ └── selection_sort.rs │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.rs │ │ │ │ ├── array_queue.rs │ │ │ │ ├── array_stack.rs │ │ │ │ ├── deque.rs │ │ │ │ ├── linkedlist_deque.rs │ │ │ │ ├── linkedlist_queue.rs │ │ │ │ ├── linkedlist_stack.rs │ │ │ │ ├── queue.rs │ │ │ │ └── stack.rs │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.rs │ │ │ │ ├── avl_tree.rs │ │ │ │ ├── binary_search_tree.rs │ │ │ │ ├── binary_tree.rs │ │ │ │ ├── binary_tree_bfs.rs │ │ │ │ └── binary_tree_dfs.rs │ │ │ └── src/ │ │ │ ├── include/ │ │ │ │ ├── list_node.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── print_util.rs │ │ │ │ ├── tree_node.rs │ │ │ │ └── vertex.rs │ │ │ └── lib.rs │ │ ├── swift/ │ │ │ ├── .gitignore │ │ │ ├── Package.resolved │ │ │ ├── Package.swift │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.swift │ │ │ │ ├── linked_list.swift │ │ │ │ ├── list.swift │ │ │ │ └── my_list.swift │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.swift │ │ │ │ ├── permutations_i.swift │ │ │ │ ├── permutations_ii.swift │ │ │ │ ├── preorder_traversal_i_compact.swift │ │ │ │ ├── preorder_traversal_ii_compact.swift │ │ │ │ ├── preorder_traversal_iii_compact.swift │ │ │ │ ├── preorder_traversal_iii_template.swift │ │ │ │ ├── subset_sum_i.swift │ │ │ │ ├── subset_sum_i_naive.swift │ │ │ │ └── subset_sum_ii.swift │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.swift │ │ │ │ ├── recursion.swift │ │ │ │ ├── space_complexity.swift │ │ │ │ ├── time_complexity.swift │ │ │ │ └── worst_best_time_complexity.swift │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.swift │ │ │ │ ├── build_tree.swift │ │ │ │ └── hanota.swift │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.swift │ │ │ │ ├── climbing_stairs_constraint_dp.swift │ │ │ │ ├── climbing_stairs_dfs.swift │ │ │ │ ├── climbing_stairs_dfs_mem.swift │ │ │ │ ├── climbing_stairs_dp.swift │ │ │ │ ├── coin_change.swift │ │ │ │ ├── coin_change_ii.swift │ │ │ │ ├── edit_distance.swift │ │ │ │ ├── knapsack.swift │ │ │ │ ├── min_cost_climbing_stairs_dp.swift │ │ │ │ ├── min_path_sum.swift │ │ │ │ └── unbounded_knapsack.swift │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.swift │ │ │ │ ├── graph_adjacency_list_target.swift │ │ │ │ ├── graph_adjacency_matrix.swift │ │ │ │ ├── graph_bfs.swift │ │ │ │ └── graph_dfs.swift │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.swift │ │ │ │ ├── fractional_knapsack.swift │ │ │ │ ├── max_capacity.swift │ │ │ │ └── max_product_cutting.swift │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.swift │ │ │ │ ├── built_in_hash.swift │ │ │ │ ├── hash_map.swift │ │ │ │ ├── hash_map_chaining.swift │ │ │ │ ├── hash_map_open_addressing.swift │ │ │ │ └── simple_hash.swift │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.swift │ │ │ │ ├── my_heap.swift │ │ │ │ └── top_k.swift │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.swift │ │ │ │ ├── binary_search_edge.swift │ │ │ │ ├── binary_search_insertion.swift │ │ │ │ ├── binary_search_insertion_target.swift │ │ │ │ ├── hashing_search.swift │ │ │ │ ├── linear_search.swift │ │ │ │ └── two_sum.swift │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.swift │ │ │ │ ├── bucket_sort.swift │ │ │ │ ├── counting_sort.swift │ │ │ │ ├── heap_sort.swift │ │ │ │ ├── insertion_sort.swift │ │ │ │ ├── merge_sort.swift │ │ │ │ ├── quick_sort.swift │ │ │ │ ├── radix_sort.swift │ │ │ │ └── selection_sort.swift │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.swift │ │ │ │ ├── array_queue.swift │ │ │ │ ├── array_stack.swift │ │ │ │ ├── deque.swift │ │ │ │ ├── linkedlist_deque.swift │ │ │ │ ├── linkedlist_queue.swift │ │ │ │ ├── linkedlist_stack.swift │ │ │ │ ├── queue.swift │ │ │ │ └── stack.swift │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.swift │ │ │ │ ├── avl_tree.swift │ │ │ │ ├── binary_search_tree.swift │ │ │ │ ├── binary_tree.swift │ │ │ │ ├── binary_tree_bfs.swift │ │ │ │ └── binary_tree_dfs.swift │ │ │ └── utils/ │ │ │ ├── ListNode.swift │ │ │ ├── Pair.swift │ │ │ ├── PrintUtil.swift │ │ │ ├── TreeNode.swift │ │ │ └── Vertex.swift │ │ ├── typescript/ │ │ │ ├── .gitignore │ │ │ ├── .prettierrc │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.ts │ │ │ │ ├── linked_list.ts │ │ │ │ ├── list.ts │ │ │ │ └── my_list.ts │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.ts │ │ │ │ ├── permutations_i.ts │ │ │ │ ├── permutations_ii.ts │ │ │ │ ├── preorder_traversal_i_compact.ts │ │ │ │ ├── preorder_traversal_ii_compact.ts │ │ │ │ ├── preorder_traversal_iii_compact.ts │ │ │ │ ├── preorder_traversal_iii_template.ts │ │ │ │ ├── subset_sum_i.ts │ │ │ │ ├── subset_sum_i_naive.ts │ │ │ │ └── subset_sum_ii.ts │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.ts │ │ │ │ ├── recursion.ts │ │ │ │ ├── space_complexity.ts │ │ │ │ ├── time_complexity.ts │ │ │ │ └── worst_best_time_complexity.ts │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.ts │ │ │ │ ├── build_tree.ts │ │ │ │ └── hanota.ts │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.ts │ │ │ │ ├── climbing_stairs_constraint_dp.ts │ │ │ │ ├── climbing_stairs_dfs.ts │ │ │ │ ├── climbing_stairs_dfs_mem.ts │ │ │ │ ├── climbing_stairs_dp.ts │ │ │ │ ├── coin_change.ts │ │ │ │ ├── coin_change_ii.ts │ │ │ │ ├── edit_distance.ts │ │ │ │ ├── knapsack.ts │ │ │ │ ├── min_cost_climbing_stairs_dp.ts │ │ │ │ ├── min_path_sum.ts │ │ │ │ └── unbounded_knapsack.ts │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.ts │ │ │ │ ├── graph_adjacency_matrix.ts │ │ │ │ ├── graph_bfs.ts │ │ │ │ └── graph_dfs.ts │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.ts │ │ │ │ ├── fractional_knapsack.ts │ │ │ │ ├── max_capacity.ts │ │ │ │ └── max_product_cutting.ts │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.ts │ │ │ │ ├── hash_map.ts │ │ │ │ ├── hash_map_chaining.ts │ │ │ │ ├── hash_map_open_addressing.ts │ │ │ │ └── simple_hash.ts │ │ │ ├── chapter_heap/ │ │ │ │ ├── my_heap.ts │ │ │ │ └── top_k.ts │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.ts │ │ │ │ ├── binary_search_edge.ts │ │ │ │ ├── binary_search_insertion.ts │ │ │ │ ├── hashing_search.ts │ │ │ │ ├── linear_search.ts │ │ │ │ └── two_sum.ts │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.ts │ │ │ │ ├── bucket_sort.ts │ │ │ │ ├── counting_sort.ts │ │ │ │ ├── heap_sort.ts │ │ │ │ ├── insertion_sort.ts │ │ │ │ ├── merge_sort.ts │ │ │ │ ├── quick_sort.ts │ │ │ │ ├── radix_sort.ts │ │ │ │ └── selection_sort.ts │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.ts │ │ │ │ ├── array_queue.ts │ │ │ │ ├── array_stack.ts │ │ │ │ ├── deque.ts │ │ │ │ ├── linkedlist_deque.ts │ │ │ │ ├── linkedlist_queue.ts │ │ │ │ ├── linkedlist_stack.ts │ │ │ │ ├── queue.ts │ │ │ │ └── stack.ts │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.ts │ │ │ │ ├── avl_tree.ts │ │ │ │ ├── binary_search_tree.ts │ │ │ │ ├── binary_tree.ts │ │ │ │ ├── binary_tree_bfs.ts │ │ │ │ └── binary_tree_dfs.ts │ │ │ ├── modules/ │ │ │ │ ├── ListNode.ts │ │ │ │ ├── PrintUtil.ts │ │ │ │ ├── TreeNode.ts │ │ │ │ └── Vertex.ts │ │ │ ├── package.json │ │ │ └── tsconfig.json │ │ └── zig/ │ │ ├── .gitignore │ │ ├── build.zig │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.zig │ │ │ ├── linked_list.zig │ │ │ ├── list.zig │ │ │ └── my_list.zig │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.zig │ │ │ ├── recursion.zig │ │ │ ├── space_complexity.zig │ │ │ ├── time_complexity.zig │ │ │ └── worst_best_time_complexity.zig │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.zig │ │ │ ├── climbing_stairs_constraint_dp.zig │ │ │ ├── climbing_stairs_dfs.zig │ │ │ ├── climbing_stairs_dfs_mem.zig │ │ │ ├── climbing_stairs_dp.zig │ │ │ ├── coin_change.zig │ │ │ ├── coin_change_ii.zig │ │ │ ├── edit_distance.zig │ │ │ ├── knapsack.zig │ │ │ ├── min_cost_climbing_stairs_dp.zig │ │ │ ├── min_path_sum.zig │ │ │ └── unbounded_knapsack.zig │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.zig │ │ │ └── hash_map.zig │ │ ├── chapter_heap/ │ │ │ ├── heap.zig │ │ │ └── my_heap.zig │ │ ├── chapter_searching/ │ │ │ ├── binary_search.zig │ │ │ ├── hashing_search.zig │ │ │ ├── linear_search.zig │ │ │ └── two_sum.zig │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.zig │ │ │ ├── insertion_sort.zig │ │ │ ├── merge_sort.zig │ │ │ ├── quick_sort.zig │ │ │ └── radix_sort.zig │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_queue.zig │ │ │ ├── array_stack.zig │ │ │ ├── deque.zig │ │ │ ├── linkedlist_deque.zig │ │ │ ├── linkedlist_queue.zig │ │ │ ├── linkedlist_stack.zig │ │ │ ├── queue.zig │ │ │ └── stack.zig │ │ ├── chapter_tree/ │ │ │ ├── avl_tree.zig │ │ │ ├── binary_search_tree.zig │ │ │ ├── binary_tree.zig │ │ │ ├── binary_tree_bfs.zig │ │ │ └── binary_tree_dfs.zig │ │ ├── include/ │ │ │ ├── PrintUtil.zig │ │ │ └── include.zig │ │ ├── main.zig │ │ └── utils/ │ │ ├── ListNode.zig │ │ ├── TreeNode.zig │ │ ├── format.zig │ │ └── utils.zig │ ├── docs/ │ │ ├── chapter_appendix/ │ │ │ ├── contribution.md │ │ │ ├── index.md │ │ │ ├── installation.md │ │ │ └── terminology.md │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.md │ │ │ ├── index.md │ │ │ ├── linked_list.md │ │ │ ├── list.md │ │ │ ├── ram_and_cache.md │ │ │ └── summary.md │ │ ├── chapter_backtracking/ │ │ │ ├── backtracking_algorithm.md │ │ │ ├── index.md │ │ │ ├── n_queens_problem.md │ │ │ ├── permutations_problem.md │ │ │ ├── subset_sum_problem.md │ │ │ └── summary.md │ │ ├── chapter_computational_complexity/ │ │ │ ├── index.md │ │ │ ├── iteration_and_recursion.md │ │ │ ├── performance_evaluation.md │ │ │ ├── space_complexity.md │ │ │ ├── summary.md │ │ │ └── time_complexity.md │ │ ├── chapter_data_structure/ │ │ │ ├── basic_data_types.md │ │ │ ├── character_encoding.md │ │ │ ├── classification_of_data_structure.md │ │ │ ├── index.md │ │ │ ├── number_encoding.md │ │ │ └── summary.md │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.md │ │ │ ├── build_binary_tree_problem.md │ │ │ ├── divide_and_conquer.md │ │ │ ├── hanota_problem.md │ │ │ ├── index.md │ │ │ └── summary.md │ │ ├── chapter_dynamic_programming/ │ │ │ ├── dp_problem_features.md │ │ │ ├── dp_solution_pipeline.md │ │ │ ├── edit_distance_problem.md │ │ │ ├── index.md │ │ │ ├── intro_to_dynamic_programming.md │ │ │ ├── knapsack_problem.md │ │ │ ├── summary.md │ │ │ └── unbounded_knapsack_problem.md │ │ ├── chapter_graph/ │ │ │ ├── graph.md │ │ │ ├── graph_operations.md │ │ │ ├── graph_traversal.md │ │ │ ├── index.md │ │ │ └── summary.md │ │ ├── chapter_greedy/ │ │ │ ├── fractional_knapsack_problem.md │ │ │ ├── greedy_algorithm.md │ │ │ ├── index.md │ │ │ ├── max_capacity_problem.md │ │ │ ├── max_product_cutting_problem.md │ │ │ └── summary.md │ │ ├── chapter_hashing/ │ │ │ ├── hash_algorithm.md │ │ │ ├── hash_collision.md │ │ │ ├── hash_map.md │ │ │ ├── index.md │ │ │ └── summary.md │ │ ├── chapter_heap/ │ │ │ ├── build_heap.md │ │ │ ├── heap.md │ │ │ ├── index.md │ │ │ ├── summary.md │ │ │ └── top_k.md │ │ ├── chapter_hello_algo/ │ │ │ └── index.md │ │ ├── chapter_introduction/ │ │ │ ├── algorithms_are_everywhere.md │ │ │ ├── index.md │ │ │ ├── summary.md │ │ │ └── what_is_dsa.md │ │ ├── chapter_paperbook/ │ │ │ └── index.md │ │ ├── chapter_preface/ │ │ │ ├── about_the_book.md │ │ │ ├── index.md │ │ │ ├── suggestions.md │ │ │ └── summary.md │ │ ├── chapter_reference/ │ │ │ └── index.md │ │ ├── chapter_searching/ │ │ │ ├── binary_search.md │ │ │ ├── binary_search_edge.md │ │ │ ├── binary_search_insertion.md │ │ │ ├── index.md │ │ │ ├── replace_linear_by_hashing.md │ │ │ ├── searching_algorithm_revisited.md │ │ │ └── summary.md │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.md │ │ │ ├── bucket_sort.md │ │ │ ├── counting_sort.md │ │ │ ├── heap_sort.md │ │ │ ├── index.md │ │ │ ├── insertion_sort.md │ │ │ ├── merge_sort.md │ │ │ ├── quick_sort.md │ │ │ ├── radix_sort.md │ │ │ ├── selection_sort.md │ │ │ ├── sorting_algorithm.md │ │ │ └── summary.md │ │ ├── chapter_stack_and_queue/ │ │ │ ├── deque.md │ │ │ ├── index.md │ │ │ ├── queue.md │ │ │ ├── stack.md │ │ │ └── summary.md │ │ ├── chapter_tree/ │ │ │ ├── array_representation_of_tree.md │ │ │ ├── avl_tree.md │ │ │ ├── binary_search_tree.md │ │ │ ├── binary_tree.md │ │ │ ├── binary_tree_traversal.md │ │ │ ├── index.md │ │ │ └── summary.md │ │ ├── index.html │ │ └── index.md │ └── mkdocs.yml ├── mkdocs.yml ├── overrides/ │ ├── javascripts/ │ │ ├── katex.js │ │ ├── mathjax.js │ │ └── starfield.js │ ├── main.html │ ├── partials/ │ │ ├── LICENSE │ │ ├── actions.html │ │ ├── comments.html │ │ └── content.html │ ├── stylesheets/ │ │ ├── extra.css │ │ ├── giscus-dark.css │ │ └── giscus-light.css │ └── zensical/ │ ├── javascripts/ │ │ └── animation_player.js │ ├── stylesheets/ │ │ ├── animation_player.css │ │ └── extra.css │ └── zensical.toml ├── ru/ │ ├── README.md │ ├── codes/ │ │ ├── Dockerfile │ │ ├── c/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array.c │ │ │ │ ├── linked_list.c │ │ │ │ └── my_list.c │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── n_queens.c │ │ │ │ ├── permutations_i.c │ │ │ │ ├── permutations_ii.c │ │ │ │ ├── preorder_traversal_i_compact.c │ │ │ │ ├── preorder_traversal_ii_compact.c │ │ │ │ ├── preorder_traversal_iii_compact.c │ │ │ │ ├── preorder_traversal_iii_template.c │ │ │ │ ├── subset_sum_i.c │ │ │ │ ├── subset_sum_i_naive.c │ │ │ │ └── subset_sum_ii.c │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── iteration.c │ │ │ │ ├── recursion.c │ │ │ │ ├── space_complexity.c │ │ │ │ ├── time_complexity.c │ │ │ │ └── worst_best_time_complexity.c │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── binary_search_recur.c │ │ │ │ ├── build_tree.c │ │ │ │ └── hanota.c │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── climbing_stairs_backtrack.c │ │ │ │ ├── climbing_stairs_constraint_dp.c │ │ │ │ ├── climbing_stairs_dfs.c │ │ │ │ ├── climbing_stairs_dfs_mem.c │ │ │ │ ├── climbing_stairs_dp.c │ │ │ │ ├── coin_change.c │ │ │ │ ├── coin_change_ii.c │ │ │ │ ├── edit_distance.c │ │ │ │ ├── knapsack.c │ │ │ │ ├── min_cost_climbing_stairs_dp.c │ │ │ │ ├── min_path_sum.c │ │ │ │ └── unbounded_knapsack.c │ │ │ ├── chapter_graph/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── graph_adjacency_list.c │ │ │ │ ├── graph_adjacency_list_test.c │ │ │ │ ├── graph_adjacency_matrix.c │ │ │ │ ├── graph_bfs.c │ │ │ │ └── graph_dfs.c │ │ │ ├── chapter_greedy/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── coin_change_greedy.c │ │ │ │ ├── fractional_knapsack.c │ │ │ │ ├── max_capacity.c │ │ │ │ └── max_product_cutting.c │ │ │ ├── chapter_hashing/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_hash_map.c │ │ │ │ ├── hash_map_chaining.c │ │ │ │ ├── hash_map_open_addressing.c │ │ │ │ └── simple_hash.c │ │ │ ├── chapter_heap/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── my_heap.c │ │ │ │ ├── my_heap_test.c │ │ │ │ └── top_k.c │ │ │ ├── chapter_searching/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── binary_search.c │ │ │ │ ├── binary_search_edge.c │ │ │ │ ├── binary_search_insertion.c │ │ │ │ └── two_sum.c │ │ │ ├── chapter_sorting/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── bubble_sort.c │ │ │ │ ├── bucket_sort.c │ │ │ │ ├── counting_sort.c │ │ │ │ ├── heap_sort.c │ │ │ │ ├── insertion_sort.c │ │ │ │ ├── merge_sort.c │ │ │ │ ├── quick_sort.c │ │ │ │ ├── radix_sort.c │ │ │ │ └── selection_sort.c │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_deque.c │ │ │ │ ├── array_queue.c │ │ │ │ ├── array_stack.c │ │ │ │ ├── linkedlist_deque.c │ │ │ │ ├── linkedlist_queue.c │ │ │ │ └── linkedlist_stack.c │ │ │ ├── chapter_tree/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_binary_tree.c │ │ │ │ ├── avl_tree.c │ │ │ │ ├── binary_search_tree.c │ │ │ │ ├── binary_tree.c │ │ │ │ ├── binary_tree_bfs.c │ │ │ │ └── binary_tree_dfs.c │ │ │ └── utils/ │ │ │ ├── CMakeLists.txt │ │ │ ├── common.h │ │ │ ├── common_test.c │ │ │ ├── list_node.h │ │ │ ├── print_util.h │ │ │ ├── tree_node.h │ │ │ ├── uthash.h │ │ │ ├── vector.h │ │ │ └── vertex.h │ │ ├── cpp/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array.cpp │ │ │ │ ├── linked_list.cpp │ │ │ │ ├── list.cpp │ │ │ │ └── my_list.cpp │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── n_queens.cpp │ │ │ │ ├── permutations_i.cpp │ │ │ │ ├── permutations_ii.cpp │ │ │ │ ├── preorder_traversal_i_compact.cpp │ │ │ │ ├── preorder_traversal_ii_compact.cpp │ │ │ │ ├── preorder_traversal_iii_compact.cpp │ │ │ │ ├── preorder_traversal_iii_template.cpp │ │ │ │ ├── subset_sum_i.cpp │ │ │ │ ├── subset_sum_i_naive.cpp │ │ │ │ └── subset_sum_ii.cpp │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── iteration.cpp │ │ │ │ ├── recursion.cpp │ │ │ │ ├── space_complexity.cpp │ │ │ │ ├── time_complexity.cpp │ │ │ │ └── worst_best_time_complexity.cpp │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── binary_search_recur.cpp │ │ │ │ ├── build_tree.cpp │ │ │ │ └── hanota.cpp │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── climbing_stairs_backtrack.cpp │ │ │ │ ├── climbing_stairs_constraint_dp.cpp │ │ │ │ ├── climbing_stairs_dfs.cpp │ │ │ │ ├── climbing_stairs_dfs_mem.cpp │ │ │ │ ├── climbing_stairs_dp.cpp │ │ │ │ ├── coin_change.cpp │ │ │ │ ├── coin_change_ii.cpp │ │ │ │ ├── edit_distance.cpp │ │ │ │ ├── knapsack.cpp │ │ │ │ ├── min_cost_climbing_stairs_dp.cpp │ │ │ │ ├── min_path_sum.cpp │ │ │ │ └── unbounded_knapsack.cpp │ │ │ ├── chapter_graph/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── graph_adjacency_list.cpp │ │ │ │ ├── graph_adjacency_list_test.cpp │ │ │ │ ├── graph_adjacency_matrix.cpp │ │ │ │ ├── graph_bfs.cpp │ │ │ │ └── graph_dfs.cpp │ │ │ ├── chapter_greedy/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── coin_change_greedy.cpp │ │ │ │ ├── fractional_knapsack.cpp │ │ │ │ ├── max_capacity.cpp │ │ │ │ └── max_product_cutting.cpp │ │ │ ├── chapter_hashing/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_hash_map.cpp │ │ │ │ ├── array_hash_map_test.cpp │ │ │ │ ├── built_in_hash.cpp │ │ │ │ ├── hash_map.cpp │ │ │ │ ├── hash_map_chaining.cpp │ │ │ │ ├── hash_map_open_addressing.cpp │ │ │ │ └── simple_hash.cpp │ │ │ ├── chapter_heap/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── heap.cpp │ │ │ │ ├── my_heap.cpp │ │ │ │ └── top_k.cpp │ │ │ ├── chapter_searching/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── binary_search.cpp │ │ │ │ ├── binary_search_edge.cpp │ │ │ │ ├── binary_search_insertion.cpp │ │ │ │ ├── hashing_search.cpp │ │ │ │ ├── linear_search.cpp │ │ │ │ └── two_sum.cpp │ │ │ ├── chapter_sorting/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── bubble_sort.cpp │ │ │ │ ├── bucket_sort.cpp │ │ │ │ ├── counting_sort.cpp │ │ │ │ ├── heap_sort.cpp │ │ │ │ ├── insertion_sort.cpp │ │ │ │ ├── merge_sort.cpp │ │ │ │ ├── quick_sort.cpp │ │ │ │ ├── radix_sort.cpp │ │ │ │ └── selection_sort.cpp │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_deque.cpp │ │ │ │ ├── array_queue.cpp │ │ │ │ ├── array_stack.cpp │ │ │ │ ├── deque.cpp │ │ │ │ ├── linkedlist_deque.cpp │ │ │ │ ├── linkedlist_queue.cpp │ │ │ │ ├── linkedlist_stack.cpp │ │ │ │ ├── queue.cpp │ │ │ │ └── stack.cpp │ │ │ ├── chapter_tree/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── array_binary_tree.cpp │ │ │ │ ├── avl_tree.cpp │ │ │ │ ├── binary_search_tree.cpp │ │ │ │ ├── binary_tree.cpp │ │ │ │ ├── binary_tree_bfs.cpp │ │ │ │ └── binary_tree_dfs.cpp │ │ │ └── utils/ │ │ │ ├── CMakeLists.txt │ │ │ ├── common.hpp │ │ │ ├── list_node.hpp │ │ │ ├── print_utils.hpp │ │ │ ├── tree_node.hpp │ │ │ └── vertex.hpp │ │ ├── csharp/ │ │ │ ├── .editorconfig │ │ │ ├── .gitignore │ │ │ ├── GlobalUsing.cs │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.cs │ │ │ │ ├── linked_list.cs │ │ │ │ ├── list.cs │ │ │ │ └── my_list.cs │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.cs │ │ │ │ ├── permutations_i.cs │ │ │ │ ├── permutations_ii.cs │ │ │ │ ├── preorder_traversal_i_compact.cs │ │ │ │ ├── preorder_traversal_ii_compact.cs │ │ │ │ ├── preorder_traversal_iii_compact.cs │ │ │ │ ├── preorder_traversal_iii_template.cs │ │ │ │ ├── subset_sum_i.cs │ │ │ │ ├── subset_sum_i_naive.cs │ │ │ │ └── subset_sum_ii.cs │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.cs │ │ │ │ ├── recursion.cs │ │ │ │ ├── space_complexity.cs │ │ │ │ ├── time_complexity.cs │ │ │ │ └── worst_best_time_complexity.cs │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.cs │ │ │ │ ├── build_tree.cs │ │ │ │ └── hanota.cs │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.cs │ │ │ │ ├── climbing_stairs_constraint_dp.cs │ │ │ │ ├── climbing_stairs_dfs.cs │ │ │ │ ├── climbing_stairs_dfs_mem.cs │ │ │ │ ├── climbing_stairs_dp.cs │ │ │ │ ├── coin_change.cs │ │ │ │ ├── coin_change_ii.cs │ │ │ │ ├── edit_distance.cs │ │ │ │ ├── knapsack.cs │ │ │ │ ├── min_cost_climbing_stairs_dp.cs │ │ │ │ ├── min_path_sum.cs │ │ │ │ └── unbounded_knapsack.cs │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.cs │ │ │ │ ├── graph_adjacency_matrix.cs │ │ │ │ ├── graph_bfs.cs │ │ │ │ └── graph_dfs.cs │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.cs │ │ │ │ ├── fractional_knapsack.cs │ │ │ │ ├── max_capacity.cs │ │ │ │ └── max_product_cutting.cs │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.cs │ │ │ │ ├── built_in_hash.cs │ │ │ │ ├── hash_map.cs │ │ │ │ ├── hash_map_chaining.cs │ │ │ │ ├── hash_map_open_addressing.cs │ │ │ │ └── simple_hash.cs │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.cs │ │ │ │ ├── my_heap.cs │ │ │ │ └── top_k.cs │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.cs │ │ │ │ ├── binary_search_edge.cs │ │ │ │ ├── binary_search_insertion.cs │ │ │ │ ├── hashing_search.cs │ │ │ │ ├── linear_search.cs │ │ │ │ └── two_sum.cs │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.cs │ │ │ │ ├── bucket_sort.cs │ │ │ │ ├── counting_sort.cs │ │ │ │ ├── heap_sort.cs │ │ │ │ ├── insertion_sort.cs │ │ │ │ ├── merge_sort.cs │ │ │ │ ├── quick_sort.cs │ │ │ │ ├── radix_sort.cs │ │ │ │ └── selection_sort.cs │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.cs │ │ │ │ ├── array_queue.cs │ │ │ │ ├── array_stack.cs │ │ │ │ ├── deque.cs │ │ │ │ ├── linkedlist_deque.cs │ │ │ │ ├── linkedlist_queue.cs │ │ │ │ ├── linkedlist_stack.cs │ │ │ │ ├── queue.cs │ │ │ │ └── stack.cs │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.cs │ │ │ │ ├── avl_tree.cs │ │ │ │ ├── binary_search_tree.cs │ │ │ │ ├── binary_tree.cs │ │ │ │ ├── binary_tree_bfs.cs │ │ │ │ └── binary_tree_dfs.cs │ │ │ ├── csharp.sln │ │ │ ├── hello-algo.csproj │ │ │ └── utils/ │ │ │ ├── ListNode.cs │ │ │ ├── PrintUtil.cs │ │ │ ├── TreeNode.cs │ │ │ └── Vertex.cs │ │ ├── dart/ │ │ │ ├── build.dart │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.dart │ │ │ │ ├── linked_list.dart │ │ │ │ ├── list.dart │ │ │ │ └── my_list.dart │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.dart │ │ │ │ ├── permutations_i.dart │ │ │ │ ├── permutations_ii.dart │ │ │ │ ├── preorder_traversal_i_compact.dart │ │ │ │ ├── preorder_traversal_ii_compact.dart │ │ │ │ ├── preorder_traversal_iii_compact.dart │ │ │ │ ├── preorder_traversal_iii_template.dart │ │ │ │ ├── subset_sum_i.dart │ │ │ │ ├── subset_sum_i_naive.dart │ │ │ │ └── subset_sum_ii.dart │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.dart │ │ │ │ ├── recursion.dart │ │ │ │ ├── space_complexity.dart │ │ │ │ ├── time_complexity.dart │ │ │ │ └── worst_best_time_complexity.dart │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.dart │ │ │ │ ├── build_tree.dart │ │ │ │ └── hanota.dart │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.dart │ │ │ │ ├── climbing_stairs_constraint_dp.dart │ │ │ │ ├── climbing_stairs_dfs.dart │ │ │ │ ├── climbing_stairs_dfs_mem.dart │ │ │ │ ├── climbing_stairs_dp.dart │ │ │ │ ├── coin_change.dart │ │ │ │ ├── coin_change_ii.dart │ │ │ │ ├── edit_distance.dart │ │ │ │ ├── knapsack.dart │ │ │ │ ├── min_cost_climbing_stairs_dp.dart │ │ │ │ ├── min_path_sum.dart │ │ │ │ └── unbounded_knapsack.dart │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.dart │ │ │ │ ├── graph_adjacency_matrix.dart │ │ │ │ ├── graph_bfs.dart │ │ │ │ └── graph_dfs.dart │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.dart │ │ │ │ ├── fractional_knapsack.dart │ │ │ │ ├── max_capacity.dart │ │ │ │ └── max_product_cutting.dart │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.dart │ │ │ │ ├── built_in_hash.dart │ │ │ │ ├── hash_map.dart │ │ │ │ ├── hash_map_chaining.dart │ │ │ │ ├── hash_map_open_addressing.dart │ │ │ │ └── simple_hash.dart │ │ │ ├── chapter_heap/ │ │ │ │ ├── my_heap.dart │ │ │ │ └── top_k.dart │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.dart │ │ │ │ ├── binary_search_edge.dart │ │ │ │ ├── binary_search_insertion.dart │ │ │ │ ├── hashing_search.dart │ │ │ │ ├── linear_search.dart │ │ │ │ └── two_sum.dart │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.dart │ │ │ │ ├── bucket_sort.dart │ │ │ │ ├── counting_sort.dart │ │ │ │ ├── heap_sort.dart │ │ │ │ ├── insertion_sort.dart │ │ │ │ ├── merge_sort.dart │ │ │ │ ├── quick_sort.dart │ │ │ │ ├── radix_sort.dart │ │ │ │ └── selection_sort.dart │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.dart │ │ │ │ ├── array_queue.dart │ │ │ │ ├── array_stack.dart │ │ │ │ ├── deque.dart │ │ │ │ ├── linkedlist_deque.dart │ │ │ │ ├── linkedlist_queue.dart │ │ │ │ ├── linkedlist_stack.dart │ │ │ │ ├── queue.dart │ │ │ │ └── stack.dart │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.dart │ │ │ │ ├── avl_tree.dart │ │ │ │ ├── binary_search_tree.dart │ │ │ │ ├── binary_tree.dart │ │ │ │ ├── binary_tree_bfs.dart │ │ │ │ └── binary_tree_dfs.dart │ │ │ └── utils/ │ │ │ ├── list_node.dart │ │ │ ├── print_util.dart │ │ │ ├── tree_node.dart │ │ │ └── vertex.dart │ │ ├── docker-compose.yml │ │ ├── go/ │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.go │ │ │ │ ├── array_test.go │ │ │ │ ├── linked_list.go │ │ │ │ ├── linked_list_test.go │ │ │ │ ├── list_test.go │ │ │ │ ├── my_list.go │ │ │ │ └── my_list_test.go │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.go │ │ │ │ ├── n_queens_test.go │ │ │ │ ├── permutation_test.go │ │ │ │ ├── permutations_i.go │ │ │ │ ├── permutations_ii.go │ │ │ │ ├── preorder_traversal_i_compact.go │ │ │ │ ├── preorder_traversal_ii_compact.go │ │ │ │ ├── preorder_traversal_iii_compact.go │ │ │ │ ├── preorder_traversal_iii_template.go │ │ │ │ ├── preorder_traversal_test.go │ │ │ │ ├── subset_sum_i.go │ │ │ │ ├── subset_sum_i_naive.go │ │ │ │ ├── subset_sum_ii.go │ │ │ │ └── subset_sum_test.go │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.go │ │ │ │ ├── iteration_test.go │ │ │ │ ├── recursion.go │ │ │ │ ├── recursion_test.go │ │ │ │ ├── space_complexity.go │ │ │ │ ├── space_complexity_test.go │ │ │ │ ├── time_complexity.go │ │ │ │ ├── time_complexity_test.go │ │ │ │ ├── worst_best_time_complexity.go │ │ │ │ └── worst_best_time_complexity_test.go │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.go │ │ │ │ ├── binary_search_recur_test.go │ │ │ │ ├── build_tree.go │ │ │ │ ├── build_tree_test.go │ │ │ │ ├── hanota.go │ │ │ │ └── hanota_test.go │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.go │ │ │ │ ├── climbing_stairs_constraint_dp.go │ │ │ │ ├── climbing_stairs_dfs.go │ │ │ │ ├── climbing_stairs_dfs_mem.go │ │ │ │ ├── climbing_stairs_dp.go │ │ │ │ ├── climbing_stairs_test.go │ │ │ │ ├── coin_change.go │ │ │ │ ├── coin_change_ii.go │ │ │ │ ├── coin_change_test.go │ │ │ │ ├── edit_distance.go │ │ │ │ ├── edit_distance_test.go │ │ │ │ ├── knapsack.go │ │ │ │ ├── knapsack_test.go │ │ │ │ ├── min_cost_climbing_stairs_dp.go │ │ │ │ ├── min_path_sum.go │ │ │ │ ├── min_path_sum_test.go │ │ │ │ └── unbounded_knapsack.go │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.go │ │ │ │ ├── graph_adjacency_list_test.go │ │ │ │ ├── graph_adjacency_matrix.go │ │ │ │ ├── graph_adjacency_matrix_test.go │ │ │ │ ├── graph_bfs.go │ │ │ │ ├── graph_bfs_test.go │ │ │ │ ├── graph_dfs.go │ │ │ │ └── graph_dfs_test.go │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.go │ │ │ │ ├── coin_change_greedy_test.go │ │ │ │ ├── fractional_knapsack.go │ │ │ │ ├── fractional_knapsack_test.go │ │ │ │ ├── max_capacity.go │ │ │ │ ├── max_capacity_test.go │ │ │ │ ├── max_product_cutting.go │ │ │ │ └── max_product_cutting_test.go │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.go │ │ │ │ ├── array_hash_map_test.go │ │ │ │ ├── hash_collision_test.go │ │ │ │ ├── hash_map_chaining.go │ │ │ │ ├── hash_map_open_addressing.go │ │ │ │ ├── hash_map_test.go │ │ │ │ └── simple_hash.go │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.go │ │ │ │ ├── heap_test.go │ │ │ │ ├── my_heap.go │ │ │ │ └── top_k.go │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.go │ │ │ │ ├── binary_search_edge.go │ │ │ │ ├── binary_search_insertion.go │ │ │ │ ├── binary_search_test.go │ │ │ │ ├── hashing_search.go │ │ │ │ ├── hashing_search_test.go │ │ │ │ ├── linear_search.go │ │ │ │ ├── linear_search_test.go │ │ │ │ ├── two_sum.go │ │ │ │ └── two_sum_test.go │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.go │ │ │ │ ├── bubble_sort_test.go │ │ │ │ ├── bucket_sort.go │ │ │ │ ├── bucket_sort_test.go │ │ │ │ ├── counting_sort.go │ │ │ │ ├── counting_sort_test.go │ │ │ │ ├── heap_sort.go │ │ │ │ ├── heap_sort_test.go │ │ │ │ ├── insertion_sort.go │ │ │ │ ├── insertion_sort_test.go │ │ │ │ ├── merge_sort.go │ │ │ │ ├── merge_sort_test.go │ │ │ │ ├── quick_sort.go │ │ │ │ ├── quick_sort_test.go │ │ │ │ ├── radix_sort.go │ │ │ │ ├── radix_sort_test.go │ │ │ │ ├── selection_sort.go │ │ │ │ └── selection_sort_test.go │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.go │ │ │ │ ├── array_queue.go │ │ │ │ ├── array_stack.go │ │ │ │ ├── deque_test.go │ │ │ │ ├── linkedlist_deque.go │ │ │ │ ├── linkedlist_queue.go │ │ │ │ ├── linkedlist_stack.go │ │ │ │ ├── queue_test.go │ │ │ │ └── stack_test.go │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.go │ │ │ │ ├── array_binary_tree_test.go │ │ │ │ ├── avl_tree.go │ │ │ │ ├── avl_tree_test.go │ │ │ │ ├── binary_search_tree.go │ │ │ │ ├── binary_search_tree_test.go │ │ │ │ ├── binary_tree_bfs.go │ │ │ │ ├── binary_tree_bfs_test.go │ │ │ │ ├── binary_tree_dfs.go │ │ │ │ ├── binary_tree_dfs_test.go │ │ │ │ └── binary_tree_test.go │ │ │ ├── go.mod │ │ │ └── pkg/ │ │ │ ├── list_node.go │ │ │ ├── list_node_test.go │ │ │ ├── print_utils.go │ │ │ ├── tree_node.go │ │ │ ├── tree_node_test.go │ │ │ └── vertex.go │ │ ├── java/ │ │ │ ├── .gitignore │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.java │ │ │ │ ├── linked_list.java │ │ │ │ ├── list.java │ │ │ │ └── my_list.java │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.java │ │ │ │ ├── permutations_i.java │ │ │ │ ├── permutations_ii.java │ │ │ │ ├── preorder_traversal_i_compact.java │ │ │ │ ├── preorder_traversal_ii_compact.java │ │ │ │ ├── preorder_traversal_iii_compact.java │ │ │ │ ├── preorder_traversal_iii_template.java │ │ │ │ ├── subset_sum_i.java │ │ │ │ ├── subset_sum_i_naive.java │ │ │ │ └── subset_sum_ii.java │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.java │ │ │ │ ├── recursion.java │ │ │ │ ├── space_complexity.java │ │ │ │ ├── time_complexity.java │ │ │ │ └── worst_best_time_complexity.java │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.java │ │ │ │ ├── build_tree.java │ │ │ │ └── hanota.java │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.java │ │ │ │ ├── climbing_stairs_constraint_dp.java │ │ │ │ ├── climbing_stairs_dfs.java │ │ │ │ ├── climbing_stairs_dfs_mem.java │ │ │ │ ├── climbing_stairs_dp.java │ │ │ │ ├── coin_change.java │ │ │ │ ├── coin_change_ii.java │ │ │ │ ├── edit_distance.java │ │ │ │ ├── knapsack.java │ │ │ │ ├── min_cost_climbing_stairs_dp.java │ │ │ │ ├── min_path_sum.java │ │ │ │ └── unbounded_knapsack.java │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.java │ │ │ │ ├── graph_adjacency_matrix.java │ │ │ │ ├── graph_bfs.java │ │ │ │ └── graph_dfs.java │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.java │ │ │ │ ├── fractional_knapsack.java │ │ │ │ ├── max_capacity.java │ │ │ │ └── max_product_cutting.java │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.java │ │ │ │ ├── built_in_hash.java │ │ │ │ ├── hash_map.java │ │ │ │ ├── hash_map_chaining.java │ │ │ │ ├── hash_map_open_addressing.java │ │ │ │ └── simple_hash.java │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.java │ │ │ │ ├── my_heap.java │ │ │ │ └── top_k.java │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.java │ │ │ │ ├── binary_search_edge.java │ │ │ │ ├── binary_search_insertion.java │ │ │ │ ├── hashing_search.java │ │ │ │ ├── linear_search.java │ │ │ │ └── two_sum.java │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.java │ │ │ │ ├── bucket_sort.java │ │ │ │ ├── counting_sort.java │ │ │ │ ├── heap_sort.java │ │ │ │ ├── insertion_sort.java │ │ │ │ ├── merge_sort.java │ │ │ │ ├── quick_sort.java │ │ │ │ ├── radix_sort.java │ │ │ │ └── selection_sort.java │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.java │ │ │ │ ├── array_queue.java │ │ │ │ ├── array_stack.java │ │ │ │ ├── deque.java │ │ │ │ ├── linkedlist_deque.java │ │ │ │ ├── linkedlist_queue.java │ │ │ │ ├── linkedlist_stack.java │ │ │ │ ├── queue.java │ │ │ │ └── stack.java │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.java │ │ │ │ ├── avl_tree.java │ │ │ │ ├── binary_search_tree.java │ │ │ │ ├── binary_tree.java │ │ │ │ ├── binary_tree_bfs.java │ │ │ │ └── binary_tree_dfs.java │ │ │ └── utils/ │ │ │ ├── ListNode.java │ │ │ ├── PrintUtil.java │ │ │ ├── TreeNode.java │ │ │ └── Vertex.java │ │ ├── javascript/ │ │ │ ├── .prettierrc │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.js │ │ │ │ ├── linked_list.js │ │ │ │ ├── list.js │ │ │ │ └── my_list.js │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.js │ │ │ │ ├── permutations_i.js │ │ │ │ ├── permutations_ii.js │ │ │ │ ├── preorder_traversal_i_compact.js │ │ │ │ ├── preorder_traversal_ii_compact.js │ │ │ │ ├── preorder_traversal_iii_compact.js │ │ │ │ ├── preorder_traversal_iii_template.js │ │ │ │ ├── subset_sum_i.js │ │ │ │ ├── subset_sum_i_naive.js │ │ │ │ └── subset_sum_ii.js │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.js │ │ │ │ ├── recursion.js │ │ │ │ ├── space_complexity.js │ │ │ │ ├── time_complexity.js │ │ │ │ └── worst_best_time_complexity.js │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.js │ │ │ │ ├── build_tree.js │ │ │ │ └── hanota.js │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.js │ │ │ │ ├── climbing_stairs_constraint_dp.js │ │ │ │ ├── climbing_stairs_dfs.js │ │ │ │ ├── climbing_stairs_dfs_mem.js │ │ │ │ ├── climbing_stairs_dp.js │ │ │ │ ├── coin_change.js │ │ │ │ ├── coin_change_ii.js │ │ │ │ ├── edit_distance.js │ │ │ │ ├── knapsack.js │ │ │ │ ├── min_cost_climbing_stairs_dp.js │ │ │ │ ├── min_path_sum.js │ │ │ │ └── unbounded_knapsack.js │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.js │ │ │ │ ├── graph_adjacency_matrix.js │ │ │ │ ├── graph_bfs.js │ │ │ │ └── graph_dfs.js │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.js │ │ │ │ ├── fractional_knapsack.js │ │ │ │ ├── max_capacity.js │ │ │ │ └── max_product_cutting.js │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.js │ │ │ │ ├── hash_map.js │ │ │ │ ├── hash_map_chaining.js │ │ │ │ ├── hash_map_open_addressing.js │ │ │ │ └── simple_hash.js │ │ │ ├── chapter_heap/ │ │ │ │ ├── my_heap.js │ │ │ │ └── top_k.js │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.js │ │ │ │ ├── binary_search_edge.js │ │ │ │ ├── binary_search_insertion.js │ │ │ │ ├── hashing_search.js │ │ │ │ ├── linear_search.js │ │ │ │ └── two_sum.js │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.js │ │ │ │ ├── bucket_sort.js │ │ │ │ ├── counting_sort.js │ │ │ │ ├── heap_sort.js │ │ │ │ ├── insertion_sort.js │ │ │ │ ├── merge_sort.js │ │ │ │ ├── quick_sort.js │ │ │ │ ├── radix_sort.js │ │ │ │ └── selection_sort.js │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.js │ │ │ │ ├── array_queue.js │ │ │ │ ├── array_stack.js │ │ │ │ ├── deque.js │ │ │ │ ├── linkedlist_deque.js │ │ │ │ ├── linkedlist_queue.js │ │ │ │ ├── linkedlist_stack.js │ │ │ │ ├── queue.js │ │ │ │ └── stack.js │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.js │ │ │ │ ├── avl_tree.js │ │ │ │ ├── binary_search_tree.js │ │ │ │ ├── binary_tree.js │ │ │ │ ├── binary_tree_bfs.js │ │ │ │ └── binary_tree_dfs.js │ │ │ ├── modules/ │ │ │ │ ├── ListNode.js │ │ │ │ ├── PrintUtil.js │ │ │ │ ├── TreeNode.js │ │ │ │ └── Vertex.js │ │ │ └── test_all.js │ │ ├── kotlin/ │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.kt │ │ │ │ ├── linked_list.kt │ │ │ │ ├── list.kt │ │ │ │ └── my_list.kt │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.kt │ │ │ │ ├── permutations_i.kt │ │ │ │ ├── permutations_ii.kt │ │ │ │ ├── preorder_traversal_i_compact.kt │ │ │ │ ├── preorder_traversal_ii_compact.kt │ │ │ │ ├── preorder_traversal_iii_compact.kt │ │ │ │ ├── preorder_traversal_iii_template.kt │ │ │ │ ├── subset_sum_i.kt │ │ │ │ ├── subset_sum_i_naive.kt │ │ │ │ └── subset_sum_ii.kt │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.kt │ │ │ │ ├── recursion.kt │ │ │ │ ├── space_complexity.kt │ │ │ │ ├── time_complexity.kt │ │ │ │ └── worst_best_time_complexity.kt │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.kt │ │ │ │ ├── build_tree.kt │ │ │ │ └── hanota.kt │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.kt │ │ │ │ ├── climbing_stairs_constraint_dp.kt │ │ │ │ ├── climbing_stairs_dfs.kt │ │ │ │ ├── climbing_stairs_dfs_mem.kt │ │ │ │ ├── climbing_stairs_dp.kt │ │ │ │ ├── coin_change.kt │ │ │ │ ├── coin_change_ii.kt │ │ │ │ ├── edit_distance.kt │ │ │ │ ├── knapsack.kt │ │ │ │ ├── min_cost_climbing_stairs_dp.kt │ │ │ │ ├── min_path_sum.kt │ │ │ │ └── unbounded_knapsack.kt │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.kt │ │ │ │ ├── graph_adjacency_matrix.kt │ │ │ │ ├── graph_bfs.kt │ │ │ │ └── graph_dfs.kt │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.kt │ │ │ │ ├── fractional_knapsack.kt │ │ │ │ ├── max_capacity.kt │ │ │ │ └── max_product_cutting.kt │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.kt │ │ │ │ ├── built_in_hash.kt │ │ │ │ ├── hash_map.kt │ │ │ │ ├── hash_map_chaining.kt │ │ │ │ ├── hash_map_open_addressing.kt │ │ │ │ └── simple_hash.kt │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.kt │ │ │ │ ├── my_heap.kt │ │ │ │ └── top_k.kt │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.kt │ │ │ │ ├── binary_search_edge.kt │ │ │ │ ├── binary_search_insertion.kt │ │ │ │ ├── hashing_search.kt │ │ │ │ ├── linear_search.kt │ │ │ │ └── two_sum.kt │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.kt │ │ │ │ ├── bucket_sort.kt │ │ │ │ ├── counting_sort.kt │ │ │ │ ├── heap_sort.kt │ │ │ │ ├── insertion_sort.kt │ │ │ │ ├── merge_sort.kt │ │ │ │ ├── quick_sort.kt │ │ │ │ ├── radix_sort.kt │ │ │ │ └── selection_sort.kt │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.kt │ │ │ │ ├── array_queue.kt │ │ │ │ ├── array_stack.kt │ │ │ │ ├── deque.kt │ │ │ │ ├── linkedlist_deque.kt │ │ │ │ ├── linkedlist_queue.kt │ │ │ │ ├── linkedlist_stack.kt │ │ │ │ ├── queue.kt │ │ │ │ └── stack.kt │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.kt │ │ │ │ ├── avl_tree.kt │ │ │ │ ├── binary_search_tree.kt │ │ │ │ ├── binary_tree.kt │ │ │ │ ├── binary_tree_bfs.kt │ │ │ │ └── binary_tree_dfs.kt │ │ │ └── utils/ │ │ │ ├── ListNode.kt │ │ │ ├── PrintUtil.kt │ │ │ ├── TreeNode.kt │ │ │ └── Vertex.kt │ │ ├── python/ │ │ │ ├── .gitignore │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.py │ │ │ │ ├── linked_list.py │ │ │ │ ├── list.py │ │ │ │ └── my_list.py │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.py │ │ │ │ ├── permutations_i.py │ │ │ │ ├── permutations_ii.py │ │ │ │ ├── preorder_traversal_i_compact.py │ │ │ │ ├── preorder_traversal_ii_compact.py │ │ │ │ ├── preorder_traversal_iii_compact.py │ │ │ │ ├── preorder_traversal_iii_template.py │ │ │ │ ├── subset_sum_i.py │ │ │ │ ├── subset_sum_i_naive.py │ │ │ │ └── subset_sum_ii.py │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.py │ │ │ │ ├── recursion.py │ │ │ │ ├── space_complexity.py │ │ │ │ ├── time_complexity.py │ │ │ │ └── worst_best_time_complexity.py │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.py │ │ │ │ ├── build_tree.py │ │ │ │ └── hanota.py │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.py │ │ │ │ ├── climbing_stairs_constraint_dp.py │ │ │ │ ├── climbing_stairs_dfs.py │ │ │ │ ├── climbing_stairs_dfs_mem.py │ │ │ │ ├── climbing_stairs_dp.py │ │ │ │ ├── coin_change.py │ │ │ │ ├── coin_change_ii.py │ │ │ │ ├── edit_distance.py │ │ │ │ ├── knapsack.py │ │ │ │ ├── min_cost_climbing_stairs_dp.py │ │ │ │ ├── min_path_sum.py │ │ │ │ └── unbounded_knapsack.py │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.py │ │ │ │ ├── graph_adjacency_matrix.py │ │ │ │ ├── graph_bfs.py │ │ │ │ └── graph_dfs.py │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.py │ │ │ │ ├── fractional_knapsack.py │ │ │ │ ├── max_capacity.py │ │ │ │ └── max_product_cutting.py │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.py │ │ │ │ ├── built_in_hash.py │ │ │ │ ├── hash_map.py │ │ │ │ ├── hash_map_chaining.py │ │ │ │ ├── hash_map_open_addressing.py │ │ │ │ └── simple_hash.py │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.py │ │ │ │ ├── my_heap.py │ │ │ │ └── top_k.py │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.py │ │ │ │ ├── binary_search_edge.py │ │ │ │ ├── binary_search_insertion.py │ │ │ │ ├── hashing_search.py │ │ │ │ ├── linear_search.py │ │ │ │ └── two_sum.py │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.py │ │ │ │ ├── bucket_sort.py │ │ │ │ ├── counting_sort.py │ │ │ │ ├── heap_sort.py │ │ │ │ ├── insertion_sort.py │ │ │ │ ├── merge_sort.py │ │ │ │ ├── quick_sort.py │ │ │ │ ├── radix_sort.py │ │ │ │ └── selection_sort.py │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.py │ │ │ │ ├── array_queue.py │ │ │ │ ├── array_stack.py │ │ │ │ ├── deque.py │ │ │ │ ├── linkedlist_deque.py │ │ │ │ ├── linkedlist_queue.py │ │ │ │ ├── linkedlist_stack.py │ │ │ │ ├── queue.py │ │ │ │ └── stack.py │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.py │ │ │ │ ├── avl_tree.py │ │ │ │ ├── binary_search_tree.py │ │ │ │ ├── binary_tree.py │ │ │ │ ├── binary_tree_bfs.py │ │ │ │ └── binary_tree_dfs.py │ │ │ ├── modules/ │ │ │ │ ├── __init__.py │ │ │ │ ├── list_node.py │ │ │ │ ├── print_util.py │ │ │ │ ├── tree_node.py │ │ │ │ └── vertex.py │ │ │ └── test_all.py │ │ ├── pythontutor/ │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.md │ │ │ │ ├── linked_list.md │ │ │ │ └── my_list.md │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.md │ │ │ │ ├── permutations_i.md │ │ │ │ ├── permutations_ii.md │ │ │ │ ├── preorder_traversal_i_compact.md │ │ │ │ ├── preorder_traversal_ii_compact.md │ │ │ │ ├── preorder_traversal_iii_compact.md │ │ │ │ ├── preorder_traversal_iii_template.md │ │ │ │ ├── subset_sum_i.md │ │ │ │ ├── subset_sum_i_naive.md │ │ │ │ └── subset_sum_ii.md │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.md │ │ │ │ ├── recursion.md │ │ │ │ ├── space_complexity.md │ │ │ │ ├── time_complexity.md │ │ │ │ └── worst_best_time_complexity.md │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.md │ │ │ │ ├── build_tree.md │ │ │ │ └── hanota.md │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.md │ │ │ │ ├── climbing_stairs_constraint_dp.md │ │ │ │ ├── climbing_stairs_dfs.md │ │ │ │ ├── climbing_stairs_dfs_mem.md │ │ │ │ ├── climbing_stairs_dp.md │ │ │ │ ├── coin_change.md │ │ │ │ ├── coin_change_ii.md │ │ │ │ ├── edit_distance.md │ │ │ │ ├── knapsack.md │ │ │ │ ├── min_cost_climbing_stairs_dp.md │ │ │ │ ├── min_path_sum.md │ │ │ │ └── unbounded_knapsack.md │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.md │ │ │ │ ├── graph_adjacency_matrix.md │ │ │ │ ├── graph_bfs.md │ │ │ │ └── graph_dfs.md │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.md │ │ │ │ ├── fractional_knapsack.md │ │ │ │ ├── max_capacity.md │ │ │ │ └── max_product_cutting.md │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.md │ │ │ │ ├── hash_map_chaining.md │ │ │ │ └── simple_hash.md │ │ │ ├── chapter_heap/ │ │ │ │ ├── my_heap.md │ │ │ │ └── top_k.md │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.md │ │ │ │ ├── binary_search_edge.md │ │ │ │ ├── binary_search_insertion.md │ │ │ │ └── two_sum.md │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.md │ │ │ │ ├── bucket_sort.md │ │ │ │ ├── counting_sort.md │ │ │ │ ├── heap_sort.md │ │ │ │ ├── insertion_sort.md │ │ │ │ ├── merge_sort.md │ │ │ │ ├── quick_sort.md │ │ │ │ ├── radix_sort.md │ │ │ │ └── selection_sort.md │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_queue.md │ │ │ │ ├── array_stack.md │ │ │ │ ├── linkedlist_queue.md │ │ │ │ └── linkedlist_stack.md │ │ │ └── chapter_tree/ │ │ │ ├── array_binary_tree.md │ │ │ ├── binary_search_tree.md │ │ │ ├── binary_tree_bfs.md │ │ │ └── binary_tree_dfs.md │ │ ├── ruby/ │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.rb │ │ │ │ ├── linked_list.rb │ │ │ │ ├── list.rb │ │ │ │ └── my_list.rb │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.rb │ │ │ │ ├── permutations_i.rb │ │ │ │ ├── permutations_ii.rb │ │ │ │ ├── preorder_traversal_i_compact.rb │ │ │ │ ├── preorder_traversal_ii_compact.rb │ │ │ │ ├── preorder_traversal_iii_compact.rb │ │ │ │ ├── preorder_traversal_iii_template.rb │ │ │ │ ├── subset_sum_i.rb │ │ │ │ ├── subset_sum_i_naive.rb │ │ │ │ └── subset_sum_ii.rb │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.rb │ │ │ │ ├── recursion.rb │ │ │ │ ├── space_complexity.rb │ │ │ │ ├── time_complexity.rb │ │ │ │ └── worst_best_time_complexity.rb │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.rb │ │ │ │ ├── build_tree.rb │ │ │ │ └── hanota.rb │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.rb │ │ │ │ ├── climbing_stairs_constraint_dp.rb │ │ │ │ ├── climbing_stairs_dfs.rb │ │ │ │ ├── climbing_stairs_dfs_mem.rb │ │ │ │ ├── climbing_stairs_dp.rb │ │ │ │ ├── coin_change.rb │ │ │ │ ├── coin_change_ii.rb │ │ │ │ ├── edit_distance.rb │ │ │ │ ├── knapsack.rb │ │ │ │ ├── min_cost_climbing_stairs_dp.rb │ │ │ │ ├── min_path_sum.rb │ │ │ │ └── unbounded_knapsack.rb │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.rb │ │ │ │ ├── graph_adjacency_matrix.rb │ │ │ │ ├── graph_bfs.rb │ │ │ │ └── graph_dfs.rb │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.rb │ │ │ │ ├── fractional_knapsack.rb │ │ │ │ ├── max_capacity.rb │ │ │ │ └── max_product_cutting.rb │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.rb │ │ │ │ ├── built_in_hash.rb │ │ │ │ ├── hash_map.rb │ │ │ │ ├── hash_map_chaining.rb │ │ │ │ ├── hash_map_open_addressing.rb │ │ │ │ └── simple_hash.rb │ │ │ ├── chapter_heap/ │ │ │ │ ├── my_heap.rb │ │ │ │ └── top_k.rb │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.rb │ │ │ │ ├── binary_search_edge.rb │ │ │ │ ├── binary_search_insertion.rb │ │ │ │ ├── hashing_search.rb │ │ │ │ ├── linear_search.rb │ │ │ │ └── two_sum.rb │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.rb │ │ │ │ ├── bucket_sort.rb │ │ │ │ ├── counting_sort.rb │ │ │ │ ├── heap_sort.rb │ │ │ │ ├── insertion_sort.rb │ │ │ │ ├── merge_sort.rb │ │ │ │ ├── quick_sort.rb │ │ │ │ ├── radix_sort.rb │ │ │ │ └── selection_sort.rb │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.rb │ │ │ │ ├── array_queue.rb │ │ │ │ ├── array_stack.rb │ │ │ │ ├── deque.rb │ │ │ │ ├── linkedlist_deque.rb │ │ │ │ ├── linkedlist_queue.rb │ │ │ │ ├── linkedlist_stack.rb │ │ │ │ ├── queue.rb │ │ │ │ └── stack.rb │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.rb │ │ │ │ ├── avl_tree.rb │ │ │ │ ├── binary_search_tree.rb │ │ │ │ ├── binary_tree.rb │ │ │ │ ├── binary_tree_bfs.rb │ │ │ │ └── binary_tree_dfs.rb │ │ │ ├── test_all.rb │ │ │ └── utils/ │ │ │ ├── list_node.rb │ │ │ ├── print_util.rb │ │ │ ├── tree_node.rb │ │ │ └── vertex.rb │ │ ├── rust/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.rs │ │ │ │ ├── linked_list.rs │ │ │ │ ├── list.rs │ │ │ │ └── my_list.rs │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.rs │ │ │ │ ├── permutations_i.rs │ │ │ │ ├── permutations_ii.rs │ │ │ │ ├── preorder_traversal_i_compact.rs │ │ │ │ ├── preorder_traversal_ii_compact.rs │ │ │ │ ├── preorder_traversal_iii_compact.rs │ │ │ │ ├── preorder_traversal_iii_template.rs │ │ │ │ ├── subset_sum_i.rs │ │ │ │ ├── subset_sum_i_naive.rs │ │ │ │ └── subset_sum_ii.rs │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.rs │ │ │ │ ├── recursion.rs │ │ │ │ ├── space_complexity.rs │ │ │ │ ├── time_complexity.rs │ │ │ │ └── worst_best_time_complexity.rs │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.rs │ │ │ │ ├── build_tree.rs │ │ │ │ └── hanota.rs │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.rs │ │ │ │ ├── climbing_stairs_constraint_dp.rs │ │ │ │ ├── climbing_stairs_dfs.rs │ │ │ │ ├── climbing_stairs_dfs_mem.rs │ │ │ │ ├── climbing_stairs_dp.rs │ │ │ │ ├── coin_change.rs │ │ │ │ ├── coin_change_ii.rs │ │ │ │ ├── edit_distance.rs │ │ │ │ ├── knapsack.rs │ │ │ │ ├── min_cost_climbing_stairs_dp.rs │ │ │ │ ├── min_path_sum.rs │ │ │ │ └── unbounded_knapsack.rs │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.rs │ │ │ │ ├── graph_adjacency_matrix.rs │ │ │ │ ├── graph_bfs.rs │ │ │ │ └── graph_dfs.rs │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.rs │ │ │ │ ├── fractional_knapsack.rs │ │ │ │ ├── max_capacity.rs │ │ │ │ └── max_product_cutting.rs │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.rs │ │ │ │ ├── build_in_hash.rs │ │ │ │ ├── hash_map.rs │ │ │ │ ├── hash_map_chaining.rs │ │ │ │ ├── hash_map_open_addressing.rs │ │ │ │ └── simple_hash.rs │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.rs │ │ │ │ ├── my_heap.rs │ │ │ │ └── top_k.rs │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.rs │ │ │ │ ├── binary_search_edge.rs │ │ │ │ ├── binary_search_insertion.rs │ │ │ │ ├── hashing_search.rs │ │ │ │ ├── linear_search.rs │ │ │ │ └── two_sum.rs │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.rs │ │ │ │ ├── bucket_sort.rs │ │ │ │ ├── counting_sort.rs │ │ │ │ ├── heap_sort.rs │ │ │ │ ├── insertion_sort.rs │ │ │ │ ├── merge_sort.rs │ │ │ │ ├── quick_sort.rs │ │ │ │ ├── radix_sort.rs │ │ │ │ └── selection_sort.rs │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.rs │ │ │ │ ├── array_queue.rs │ │ │ │ ├── array_stack.rs │ │ │ │ ├── deque.rs │ │ │ │ ├── linkedlist_deque.rs │ │ │ │ ├── linkedlist_queue.rs │ │ │ │ ├── linkedlist_stack.rs │ │ │ │ ├── queue.rs │ │ │ │ └── stack.rs │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.rs │ │ │ │ ├── avl_tree.rs │ │ │ │ ├── binary_search_tree.rs │ │ │ │ ├── binary_tree.rs │ │ │ │ ├── binary_tree_bfs.rs │ │ │ │ └── binary_tree_dfs.rs │ │ │ └── src/ │ │ │ ├── include/ │ │ │ │ ├── list_node.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── print_util.rs │ │ │ │ ├── tree_node.rs │ │ │ │ └── vertex.rs │ │ │ └── lib.rs │ │ ├── swift/ │ │ │ ├── .gitignore │ │ │ ├── Package.resolved │ │ │ ├── Package.swift │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.swift │ │ │ │ ├── linked_list.swift │ │ │ │ ├── list.swift │ │ │ │ └── my_list.swift │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.swift │ │ │ │ ├── permutations_i.swift │ │ │ │ ├── permutations_ii.swift │ │ │ │ ├── preorder_traversal_i_compact.swift │ │ │ │ ├── preorder_traversal_ii_compact.swift │ │ │ │ ├── preorder_traversal_iii_compact.swift │ │ │ │ ├── preorder_traversal_iii_template.swift │ │ │ │ ├── subset_sum_i.swift │ │ │ │ ├── subset_sum_i_naive.swift │ │ │ │ └── subset_sum_ii.swift │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.swift │ │ │ │ ├── recursion.swift │ │ │ │ ├── space_complexity.swift │ │ │ │ ├── time_complexity.swift │ │ │ │ └── worst_best_time_complexity.swift │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.swift │ │ │ │ ├── build_tree.swift │ │ │ │ └── hanota.swift │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.swift │ │ │ │ ├── climbing_stairs_constraint_dp.swift │ │ │ │ ├── climbing_stairs_dfs.swift │ │ │ │ ├── climbing_stairs_dfs_mem.swift │ │ │ │ ├── climbing_stairs_dp.swift │ │ │ │ ├── coin_change.swift │ │ │ │ ├── coin_change_ii.swift │ │ │ │ ├── edit_distance.swift │ │ │ │ ├── knapsack.swift │ │ │ │ ├── min_cost_climbing_stairs_dp.swift │ │ │ │ ├── min_path_sum.swift │ │ │ │ └── unbounded_knapsack.swift │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.swift │ │ │ │ ├── graph_adjacency_list_target.swift │ │ │ │ ├── graph_adjacency_matrix.swift │ │ │ │ ├── graph_bfs.swift │ │ │ │ └── graph_dfs.swift │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.swift │ │ │ │ ├── fractional_knapsack.swift │ │ │ │ ├── max_capacity.swift │ │ │ │ └── max_product_cutting.swift │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.swift │ │ │ │ ├── built_in_hash.swift │ │ │ │ ├── hash_map.swift │ │ │ │ ├── hash_map_chaining.swift │ │ │ │ ├── hash_map_open_addressing.swift │ │ │ │ └── simple_hash.swift │ │ │ ├── chapter_heap/ │ │ │ │ ├── heap.swift │ │ │ │ ├── my_heap.swift │ │ │ │ └── top_k.swift │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.swift │ │ │ │ ├── binary_search_edge.swift │ │ │ │ ├── binary_search_insertion.swift │ │ │ │ ├── binary_search_insertion_target.swift │ │ │ │ ├── hashing_search.swift │ │ │ │ ├── linear_search.swift │ │ │ │ └── two_sum.swift │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.swift │ │ │ │ ├── bucket_sort.swift │ │ │ │ ├── counting_sort.swift │ │ │ │ ├── heap_sort.swift │ │ │ │ ├── insertion_sort.swift │ │ │ │ ├── merge_sort.swift │ │ │ │ ├── quick_sort.swift │ │ │ │ ├── radix_sort.swift │ │ │ │ └── selection_sort.swift │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.swift │ │ │ │ ├── array_queue.swift │ │ │ │ ├── array_stack.swift │ │ │ │ ├── deque.swift │ │ │ │ ├── linkedlist_deque.swift │ │ │ │ ├── linkedlist_queue.swift │ │ │ │ ├── linkedlist_stack.swift │ │ │ │ ├── queue.swift │ │ │ │ └── stack.swift │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.swift │ │ │ │ ├── avl_tree.swift │ │ │ │ ├── binary_search_tree.swift │ │ │ │ ├── binary_tree.swift │ │ │ │ ├── binary_tree_bfs.swift │ │ │ │ └── binary_tree_dfs.swift │ │ │ └── utils/ │ │ │ ├── ListNode.swift │ │ │ ├── Pair.swift │ │ │ ├── PrintUtil.swift │ │ │ ├── TreeNode.swift │ │ │ └── Vertex.swift │ │ ├── typescript/ │ │ │ ├── .gitignore │ │ │ ├── .prettierrc │ │ │ ├── chapter_array_and_linkedlist/ │ │ │ │ ├── array.ts │ │ │ │ ├── linked_list.ts │ │ │ │ ├── list.ts │ │ │ │ └── my_list.ts │ │ │ ├── chapter_backtracking/ │ │ │ │ ├── n_queens.ts │ │ │ │ ├── permutations_i.ts │ │ │ │ ├── permutations_ii.ts │ │ │ │ ├── preorder_traversal_i_compact.ts │ │ │ │ ├── preorder_traversal_ii_compact.ts │ │ │ │ ├── preorder_traversal_iii_compact.ts │ │ │ │ ├── preorder_traversal_iii_template.ts │ │ │ │ ├── subset_sum_i.ts │ │ │ │ ├── subset_sum_i_naive.ts │ │ │ │ └── subset_sum_ii.ts │ │ │ ├── chapter_computational_complexity/ │ │ │ │ ├── iteration.ts │ │ │ │ ├── recursion.ts │ │ │ │ ├── space_complexity.ts │ │ │ │ ├── time_complexity.ts │ │ │ │ └── worst_best_time_complexity.ts │ │ │ ├── chapter_divide_and_conquer/ │ │ │ │ ├── binary_search_recur.ts │ │ │ │ ├── build_tree.ts │ │ │ │ └── hanota.ts │ │ │ ├── chapter_dynamic_programming/ │ │ │ │ ├── climbing_stairs_backtrack.ts │ │ │ │ ├── climbing_stairs_constraint_dp.ts │ │ │ │ ├── climbing_stairs_dfs.ts │ │ │ │ ├── climbing_stairs_dfs_mem.ts │ │ │ │ ├── climbing_stairs_dp.ts │ │ │ │ ├── coin_change.ts │ │ │ │ ├── coin_change_ii.ts │ │ │ │ ├── edit_distance.ts │ │ │ │ ├── knapsack.ts │ │ │ │ ├── min_cost_climbing_stairs_dp.ts │ │ │ │ ├── min_path_sum.ts │ │ │ │ └── unbounded_knapsack.ts │ │ │ ├── chapter_graph/ │ │ │ │ ├── graph_adjacency_list.ts │ │ │ │ ├── graph_adjacency_matrix.ts │ │ │ │ ├── graph_bfs.ts │ │ │ │ └── graph_dfs.ts │ │ │ ├── chapter_greedy/ │ │ │ │ ├── coin_change_greedy.ts │ │ │ │ ├── fractional_knapsack.ts │ │ │ │ ├── max_capacity.ts │ │ │ │ └── max_product_cutting.ts │ │ │ ├── chapter_hashing/ │ │ │ │ ├── array_hash_map.ts │ │ │ │ ├── hash_map.ts │ │ │ │ ├── hash_map_chaining.ts │ │ │ │ ├── hash_map_open_addressing.ts │ │ │ │ └── simple_hash.ts │ │ │ ├── chapter_heap/ │ │ │ │ ├── my_heap.ts │ │ │ │ └── top_k.ts │ │ │ ├── chapter_searching/ │ │ │ │ ├── binary_search.ts │ │ │ │ ├── binary_search_edge.ts │ │ │ │ ├── binary_search_insertion.ts │ │ │ │ ├── hashing_search.ts │ │ │ │ ├── linear_search.ts │ │ │ │ └── two_sum.ts │ │ │ ├── chapter_sorting/ │ │ │ │ ├── bubble_sort.ts │ │ │ │ ├── bucket_sort.ts │ │ │ │ ├── counting_sort.ts │ │ │ │ ├── heap_sort.ts │ │ │ │ ├── insertion_sort.ts │ │ │ │ ├── merge_sort.ts │ │ │ │ ├── quick_sort.ts │ │ │ │ ├── radix_sort.ts │ │ │ │ └── selection_sort.ts │ │ │ ├── chapter_stack_and_queue/ │ │ │ │ ├── array_deque.ts │ │ │ │ ├── array_queue.ts │ │ │ │ ├── array_stack.ts │ │ │ │ ├── deque.ts │ │ │ │ ├── linkedlist_deque.ts │ │ │ │ ├── linkedlist_queue.ts │ │ │ │ ├── linkedlist_stack.ts │ │ │ │ ├── queue.ts │ │ │ │ └── stack.ts │ │ │ ├── chapter_tree/ │ │ │ │ ├── array_binary_tree.ts │ │ │ │ ├── avl_tree.ts │ │ │ │ ├── binary_search_tree.ts │ │ │ │ ├── binary_tree.ts │ │ │ │ ├── binary_tree_bfs.ts │ │ │ │ └── binary_tree_dfs.ts │ │ │ ├── modules/ │ │ │ │ ├── ListNode.ts │ │ │ │ ├── PrintUtil.ts │ │ │ │ ├── TreeNode.ts │ │ │ │ └── Vertex.ts │ │ │ ├── package.json │ │ │ └── tsconfig.json │ │ └── zig/ │ │ ├── .gitignore │ │ ├── build.zig │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.zig │ │ │ ├── linked_list.zig │ │ │ ├── list.zig │ │ │ └── my_list.zig │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.zig │ │ │ ├── recursion.zig │ │ │ ├── space_complexity.zig │ │ │ ├── time_complexity.zig │ │ │ └── worst_best_time_complexity.zig │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.zig │ │ │ ├── climbing_stairs_constraint_dp.zig │ │ │ ├── climbing_stairs_dfs.zig │ │ │ ├── climbing_stairs_dfs_mem.zig │ │ │ ├── climbing_stairs_dp.zig │ │ │ ├── coin_change.zig │ │ │ ├── coin_change_ii.zig │ │ │ ├── edit_distance.zig │ │ │ ├── knapsack.zig │ │ │ ├── min_cost_climbing_stairs_dp.zig │ │ │ ├── min_path_sum.zig │ │ │ └── unbounded_knapsack.zig │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.zig │ │ │ └── hash_map.zig │ │ ├── chapter_heap/ │ │ │ ├── heap.zig │ │ │ └── my_heap.zig │ │ ├── chapter_searching/ │ │ │ ├── binary_search.zig │ │ │ ├── hashing_search.zig │ │ │ ├── linear_search.zig │ │ │ └── two_sum.zig │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.zig │ │ │ ├── insertion_sort.zig │ │ │ ├── merge_sort.zig │ │ │ ├── quick_sort.zig │ │ │ └── radix_sort.zig │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_queue.zig │ │ │ ├── array_stack.zig │ │ │ ├── deque.zig │ │ │ ├── linkedlist_deque.zig │ │ │ ├── linkedlist_queue.zig │ │ │ ├── linkedlist_stack.zig │ │ │ ├── queue.zig │ │ │ └── stack.zig │ │ ├── chapter_tree/ │ │ │ ├── avl_tree.zig │ │ │ ├── binary_search_tree.zig │ │ │ ├── binary_tree.zig │ │ │ ├── binary_tree_bfs.zig │ │ │ └── binary_tree_dfs.zig │ │ ├── include/ │ │ │ ├── PrintUtil.zig │ │ │ └── include.zig │ │ ├── main.zig │ │ └── utils/ │ │ ├── ListNode.zig │ │ ├── TreeNode.zig │ │ ├── format.zig │ │ └── utils.zig │ ├── docs/ │ │ ├── chapter_appendix/ │ │ │ ├── contribution.md │ │ │ ├── index.md │ │ │ ├── installation.md │ │ │ └── terminology.md │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.md │ │ │ ├── index.md │ │ │ ├── linked_list.md │ │ │ ├── list.md │ │ │ ├── ram_and_cache.md │ │ │ └── summary.md │ │ ├── chapter_backtracking/ │ │ │ ├── backtracking_algorithm.md │ │ │ ├── index.md │ │ │ ├── n_queens_problem.md │ │ │ ├── permutations_problem.md │ │ │ ├── subset_sum_problem.md │ │ │ └── summary.md │ │ ├── chapter_computational_complexity/ │ │ │ ├── index.md │ │ │ ├── iteration_and_recursion.md │ │ │ ├── performance_evaluation.md │ │ │ ├── space_complexity.md │ │ │ ├── summary.md │ │ │ └── time_complexity.md │ │ ├── chapter_data_structure/ │ │ │ ├── basic_data_types.md │ │ │ ├── character_encoding.md │ │ │ ├── classification_of_data_structure.md │ │ │ ├── index.md │ │ │ ├── number_encoding.md │ │ │ └── summary.md │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.md │ │ │ ├── build_binary_tree_problem.md │ │ │ ├── divide_and_conquer.md │ │ │ ├── hanota_problem.md │ │ │ ├── index.md │ │ │ └── summary.md │ │ ├── chapter_dynamic_programming/ │ │ │ ├── dp_problem_features.md │ │ │ ├── dp_solution_pipeline.md │ │ │ ├── edit_distance_problem.md │ │ │ ├── index.md │ │ │ ├── intro_to_dynamic_programming.md │ │ │ ├── knapsack_problem.md │ │ │ ├── summary.md │ │ │ └── unbounded_knapsack_problem.md │ │ ├── chapter_graph/ │ │ │ ├── graph.md │ │ │ ├── graph_operations.md │ │ │ ├── graph_traversal.md │ │ │ ├── index.md │ │ │ └── summary.md │ │ ├── chapter_greedy/ │ │ │ ├── fractional_knapsack_problem.md │ │ │ ├── greedy_algorithm.md │ │ │ ├── index.md │ │ │ ├── max_capacity_problem.md │ │ │ ├── max_product_cutting_problem.md │ │ │ └── summary.md │ │ ├── chapter_hashing/ │ │ │ ├── hash_algorithm.md │ │ │ ├── hash_collision.md │ │ │ ├── hash_map.md │ │ │ ├── index.md │ │ │ └── summary.md │ │ ├── chapter_heap/ │ │ │ ├── build_heap.md │ │ │ ├── heap.md │ │ │ ├── index.md │ │ │ ├── summary.md │ │ │ └── top_k.md │ │ ├── chapter_hello_algo/ │ │ │ └── index.md │ │ ├── chapter_introduction/ │ │ │ ├── algorithms_are_everywhere.md │ │ │ ├── index.md │ │ │ ├── summary.md │ │ │ └── what_is_dsa.md │ │ ├── chapter_preface/ │ │ │ ├── about_the_book.md │ │ │ ├── index.md │ │ │ ├── suggestions.md │ │ │ └── summary.md │ │ ├── chapter_reference/ │ │ │ └── index.md │ │ ├── chapter_searching/ │ │ │ ├── binary_search.md │ │ │ ├── binary_search_edge.md │ │ │ ├── binary_search_insertion.md │ │ │ ├── index.md │ │ │ ├── replace_linear_by_hashing.md │ │ │ ├── searching_algorithm_revisited.md │ │ │ └── summary.md │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.md │ │ │ ├── bucket_sort.md │ │ │ ├── counting_sort.md │ │ │ ├── heap_sort.md │ │ │ ├── index.md │ │ │ ├── insertion_sort.md │ │ │ ├── merge_sort.md │ │ │ ├── quick_sort.md │ │ │ ├── radix_sort.md │ │ │ ├── selection_sort.md │ │ │ ├── sorting_algorithm.md │ │ │ └── summary.md │ │ ├── chapter_stack_and_queue/ │ │ │ ├── deque.md │ │ │ ├── index.md │ │ │ ├── queue.md │ │ │ ├── stack.md │ │ │ └── summary.md │ │ ├── chapter_tree/ │ │ │ ├── array_representation_of_tree.md │ │ │ ├── avl_tree.md │ │ │ ├── binary_search_tree.md │ │ │ ├── binary_tree.md │ │ │ ├── binary_tree_traversal.md │ │ │ ├── index.md │ │ │ └── summary.md │ │ ├── index.html │ │ └── index.md │ └── mkdocs.yml └── zh-hant/ ├── README.md ├── codes/ │ ├── Dockerfile │ ├── c/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array.c │ │ │ ├── linked_list.c │ │ │ └── my_list.c │ │ ├── chapter_backtracking/ │ │ │ ├── CMakeLists.txt │ │ │ ├── n_queens.c │ │ │ ├── permutations_i.c │ │ │ ├── permutations_ii.c │ │ │ ├── preorder_traversal_i_compact.c │ │ │ ├── preorder_traversal_ii_compact.c │ │ │ ├── preorder_traversal_iii_compact.c │ │ │ ├── preorder_traversal_iii_template.c │ │ │ ├── subset_sum_i.c │ │ │ ├── subset_sum_i_naive.c │ │ │ └── subset_sum_ii.c │ │ ├── chapter_computational_complexity/ │ │ │ ├── CMakeLists.txt │ │ │ ├── iteration.c │ │ │ ├── recursion.c │ │ │ ├── space_complexity.c │ │ │ ├── time_complexity.c │ │ │ └── worst_best_time_complexity.c │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── CMakeLists.txt │ │ │ ├── binary_search_recur.c │ │ │ ├── build_tree.c │ │ │ └── hanota.c │ │ ├── chapter_dynamic_programming/ │ │ │ ├── CMakeLists.txt │ │ │ ├── climbing_stairs_backtrack.c │ │ │ ├── climbing_stairs_constraint_dp.c │ │ │ ├── climbing_stairs_dfs.c │ │ │ ├── climbing_stairs_dfs_mem.c │ │ │ ├── climbing_stairs_dp.c │ │ │ ├── coin_change.c │ │ │ ├── coin_change_ii.c │ │ │ ├── edit_distance.c │ │ │ ├── knapsack.c │ │ │ ├── min_cost_climbing_stairs_dp.c │ │ │ ├── min_path_sum.c │ │ │ └── unbounded_knapsack.c │ │ ├── chapter_graph/ │ │ │ ├── CMakeLists.txt │ │ │ ├── graph_adjacency_list.c │ │ │ ├── graph_adjacency_list_test.c │ │ │ ├── graph_adjacency_matrix.c │ │ │ ├── graph_bfs.c │ │ │ └── graph_dfs.c │ │ ├── chapter_greedy/ │ │ │ ├── CMakeLists.txt │ │ │ ├── coin_change_greedy.c │ │ │ ├── fractional_knapsack.c │ │ │ ├── max_capacity.c │ │ │ └── max_product_cutting.c │ │ ├── chapter_hashing/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array_hash_map.c │ │ │ ├── hash_map_chaining.c │ │ │ ├── hash_map_open_addressing.c │ │ │ └── simple_hash.c │ │ ├── chapter_heap/ │ │ │ ├── CMakeLists.txt │ │ │ ├── my_heap.c │ │ │ ├── my_heap_test.c │ │ │ └── top_k.c │ │ ├── chapter_searching/ │ │ │ ├── CMakeLists.txt │ │ │ ├── binary_search.c │ │ │ ├── binary_search_edge.c │ │ │ ├── binary_search_insertion.c │ │ │ └── two_sum.c │ │ ├── chapter_sorting/ │ │ │ ├── CMakeLists.txt │ │ │ ├── bubble_sort.c │ │ │ ├── bucket_sort.c │ │ │ ├── counting_sort.c │ │ │ ├── heap_sort.c │ │ │ ├── insertion_sort.c │ │ │ ├── merge_sort.c │ │ │ ├── quick_sort.c │ │ │ ├── radix_sort.c │ │ │ └── selection_sort.c │ │ ├── chapter_stack_and_queue/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array_deque.c │ │ │ ├── array_queue.c │ │ │ ├── array_stack.c │ │ │ ├── linkedlist_deque.c │ │ │ ├── linkedlist_queue.c │ │ │ └── linkedlist_stack.c │ │ ├── chapter_tree/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array_binary_tree.c │ │ │ ├── avl_tree.c │ │ │ ├── binary_search_tree.c │ │ │ ├── binary_tree.c │ │ │ ├── binary_tree_bfs.c │ │ │ └── binary_tree_dfs.c │ │ └── utils/ │ │ ├── CMakeLists.txt │ │ ├── common.h │ │ ├── common_test.c │ │ ├── list_node.h │ │ ├── print_util.h │ │ ├── tree_node.h │ │ ├── uthash.h │ │ ├── vector.h │ │ └── vertex.h │ ├── cpp/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array.cpp │ │ │ ├── linked_list.cpp │ │ │ ├── list.cpp │ │ │ └── my_list.cpp │ │ ├── chapter_backtracking/ │ │ │ ├── CMakeLists.txt │ │ │ ├── n_queens.cpp │ │ │ ├── permutations_i.cpp │ │ │ ├── permutations_ii.cpp │ │ │ ├── preorder_traversal_i_compact.cpp │ │ │ ├── preorder_traversal_ii_compact.cpp │ │ │ ├── preorder_traversal_iii_compact.cpp │ │ │ ├── preorder_traversal_iii_template.cpp │ │ │ ├── subset_sum_i.cpp │ │ │ ├── subset_sum_i_naive.cpp │ │ │ └── subset_sum_ii.cpp │ │ ├── chapter_computational_complexity/ │ │ │ ├── CMakeLists.txt │ │ │ ├── iteration.cpp │ │ │ ├── recursion.cpp │ │ │ ├── space_complexity.cpp │ │ │ ├── time_complexity.cpp │ │ │ └── worst_best_time_complexity.cpp │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── CMakeLists.txt │ │ │ ├── binary_search_recur.cpp │ │ │ ├── build_tree.cpp │ │ │ └── hanota.cpp │ │ ├── chapter_dynamic_programming/ │ │ │ ├── CMakeLists.txt │ │ │ ├── climbing_stairs_backtrack.cpp │ │ │ ├── climbing_stairs_constraint_dp.cpp │ │ │ ├── climbing_stairs_dfs.cpp │ │ │ ├── climbing_stairs_dfs_mem.cpp │ │ │ ├── climbing_stairs_dp.cpp │ │ │ ├── coin_change.cpp │ │ │ ├── coin_change_ii.cpp │ │ │ ├── edit_distance.cpp │ │ │ ├── knapsack.cpp │ │ │ ├── min_cost_climbing_stairs_dp.cpp │ │ │ ├── min_path_sum.cpp │ │ │ └── unbounded_knapsack.cpp │ │ ├── chapter_graph/ │ │ │ ├── CMakeLists.txt │ │ │ ├── graph_adjacency_list.cpp │ │ │ ├── graph_adjacency_list_test.cpp │ │ │ ├── graph_adjacency_matrix.cpp │ │ │ ├── graph_bfs.cpp │ │ │ └── graph_dfs.cpp │ │ ├── chapter_greedy/ │ │ │ ├── CMakeLists.txt │ │ │ ├── coin_change_greedy.cpp │ │ │ ├── fractional_knapsack.cpp │ │ │ ├── max_capacity.cpp │ │ │ └── max_product_cutting.cpp │ │ ├── chapter_hashing/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array_hash_map.cpp │ │ │ ├── array_hash_map_test.cpp │ │ │ ├── built_in_hash.cpp │ │ │ ├── hash_map.cpp │ │ │ ├── hash_map_chaining.cpp │ │ │ ├── hash_map_open_addressing.cpp │ │ │ └── simple_hash.cpp │ │ ├── chapter_heap/ │ │ │ ├── CMakeLists.txt │ │ │ ├── heap.cpp │ │ │ ├── my_heap.cpp │ │ │ └── top_k.cpp │ │ ├── chapter_searching/ │ │ │ ├── CMakeLists.txt │ │ │ ├── binary_search.cpp │ │ │ ├── binary_search_edge.cpp │ │ │ ├── binary_search_insertion.cpp │ │ │ ├── hashing_search.cpp │ │ │ ├── linear_search.cpp │ │ │ └── two_sum.cpp │ │ ├── chapter_sorting/ │ │ │ ├── CMakeLists.txt │ │ │ ├── bubble_sort.cpp │ │ │ ├── bucket_sort.cpp │ │ │ ├── counting_sort.cpp │ │ │ ├── heap_sort.cpp │ │ │ ├── insertion_sort.cpp │ │ │ ├── merge_sort.cpp │ │ │ ├── quick_sort.cpp │ │ │ ├── radix_sort.cpp │ │ │ └── selection_sort.cpp │ │ ├── chapter_stack_and_queue/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array_deque.cpp │ │ │ ├── array_queue.cpp │ │ │ ├── array_stack.cpp │ │ │ ├── deque.cpp │ │ │ ├── linkedlist_deque.cpp │ │ │ ├── linkedlist_queue.cpp │ │ │ ├── linkedlist_stack.cpp │ │ │ ├── queue.cpp │ │ │ └── stack.cpp │ │ ├── chapter_tree/ │ │ │ ├── CMakeLists.txt │ │ │ ├── array_binary_tree.cpp │ │ │ ├── avl_tree.cpp │ │ │ ├── binary_search_tree.cpp │ │ │ ├── binary_tree.cpp │ │ │ ├── binary_tree_bfs.cpp │ │ │ └── binary_tree_dfs.cpp │ │ └── utils/ │ │ ├── CMakeLists.txt │ │ ├── common.hpp │ │ ├── list_node.hpp │ │ ├── print_utils.hpp │ │ ├── tree_node.hpp │ │ └── vertex.hpp │ ├── csharp/ │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── GlobalUsing.cs │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.cs │ │ │ ├── linked_list.cs │ │ │ ├── list.cs │ │ │ └── my_list.cs │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.cs │ │ │ ├── permutations_i.cs │ │ │ ├── permutations_ii.cs │ │ │ ├── preorder_traversal_i_compact.cs │ │ │ ├── preorder_traversal_ii_compact.cs │ │ │ ├── preorder_traversal_iii_compact.cs │ │ │ ├── preorder_traversal_iii_template.cs │ │ │ ├── subset_sum_i.cs │ │ │ ├── subset_sum_i_naive.cs │ │ │ └── subset_sum_ii.cs │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.cs │ │ │ ├── recursion.cs │ │ │ ├── space_complexity.cs │ │ │ ├── time_complexity.cs │ │ │ └── worst_best_time_complexity.cs │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.cs │ │ │ ├── build_tree.cs │ │ │ └── hanota.cs │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.cs │ │ │ ├── climbing_stairs_constraint_dp.cs │ │ │ ├── climbing_stairs_dfs.cs │ │ │ ├── climbing_stairs_dfs_mem.cs │ │ │ ├── climbing_stairs_dp.cs │ │ │ ├── coin_change.cs │ │ │ ├── coin_change_ii.cs │ │ │ ├── edit_distance.cs │ │ │ ├── knapsack.cs │ │ │ ├── min_cost_climbing_stairs_dp.cs │ │ │ ├── min_path_sum.cs │ │ │ └── unbounded_knapsack.cs │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.cs │ │ │ ├── graph_adjacency_matrix.cs │ │ │ ├── graph_bfs.cs │ │ │ └── graph_dfs.cs │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.cs │ │ │ ├── fractional_knapsack.cs │ │ │ ├── max_capacity.cs │ │ │ └── max_product_cutting.cs │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.cs │ │ │ ├── built_in_hash.cs │ │ │ ├── hash_map.cs │ │ │ ├── hash_map_chaining.cs │ │ │ ├── hash_map_open_addressing.cs │ │ │ └── simple_hash.cs │ │ ├── chapter_heap/ │ │ │ ├── heap.cs │ │ │ ├── my_heap.cs │ │ │ └── top_k.cs │ │ ├── chapter_searching/ │ │ │ ├── binary_search.cs │ │ │ ├── binary_search_edge.cs │ │ │ ├── binary_search_insertion.cs │ │ │ ├── hashing_search.cs │ │ │ ├── linear_search.cs │ │ │ └── two_sum.cs │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.cs │ │ │ ├── bucket_sort.cs │ │ │ ├── counting_sort.cs │ │ │ ├── heap_sort.cs │ │ │ ├── insertion_sort.cs │ │ │ ├── merge_sort.cs │ │ │ ├── quick_sort.cs │ │ │ ├── radix_sort.cs │ │ │ └── selection_sort.cs │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.cs │ │ │ ├── array_queue.cs │ │ │ ├── array_stack.cs │ │ │ ├── deque.cs │ │ │ ├── linkedlist_deque.cs │ │ │ ├── linkedlist_queue.cs │ │ │ ├── linkedlist_stack.cs │ │ │ ├── queue.cs │ │ │ └── stack.cs │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.cs │ │ │ ├── avl_tree.cs │ │ │ ├── binary_search_tree.cs │ │ │ ├── binary_tree.cs │ │ │ ├── binary_tree_bfs.cs │ │ │ └── binary_tree_dfs.cs │ │ ├── csharp.sln │ │ ├── hello-algo.csproj │ │ └── utils/ │ │ ├── ListNode.cs │ │ ├── PrintUtil.cs │ │ ├── TreeNode.cs │ │ └── Vertex.cs │ ├── dart/ │ │ ├── build.dart │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.dart │ │ │ ├── linked_list.dart │ │ │ ├── list.dart │ │ │ └── my_list.dart │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.dart │ │ │ ├── permutations_i.dart │ │ │ ├── permutations_ii.dart │ │ │ ├── preorder_traversal_i_compact.dart │ │ │ ├── preorder_traversal_ii_compact.dart │ │ │ ├── preorder_traversal_iii_compact.dart │ │ │ ├── preorder_traversal_iii_template.dart │ │ │ ├── subset_sum_i.dart │ │ │ ├── subset_sum_i_naive.dart │ │ │ └── subset_sum_ii.dart │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.dart │ │ │ ├── recursion.dart │ │ │ ├── space_complexity.dart │ │ │ ├── time_complexity.dart │ │ │ └── worst_best_time_complexity.dart │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.dart │ │ │ ├── build_tree.dart │ │ │ └── hanota.dart │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.dart │ │ │ ├── climbing_stairs_constraint_dp.dart │ │ │ ├── climbing_stairs_dfs.dart │ │ │ ├── climbing_stairs_dfs_mem.dart │ │ │ ├── climbing_stairs_dp.dart │ │ │ ├── coin_change.dart │ │ │ ├── coin_change_ii.dart │ │ │ ├── edit_distance.dart │ │ │ ├── knapsack.dart │ │ │ ├── min_cost_climbing_stairs_dp.dart │ │ │ ├── min_path_sum.dart │ │ │ └── unbounded_knapsack.dart │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.dart │ │ │ ├── graph_adjacency_matrix.dart │ │ │ ├── graph_bfs.dart │ │ │ └── graph_dfs.dart │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.dart │ │ │ ├── fractional_knapsack.dart │ │ │ ├── max_capacity.dart │ │ │ └── max_product_cutting.dart │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.dart │ │ │ ├── built_in_hash.dart │ │ │ ├── hash_map.dart │ │ │ ├── hash_map_chaining.dart │ │ │ ├── hash_map_open_addressing.dart │ │ │ └── simple_hash.dart │ │ ├── chapter_heap/ │ │ │ ├── my_heap.dart │ │ │ └── top_k.dart │ │ ├── chapter_searching/ │ │ │ ├── binary_search.dart │ │ │ ├── binary_search_edge.dart │ │ │ ├── binary_search_insertion.dart │ │ │ ├── hashing_search.dart │ │ │ ├── linear_search.dart │ │ │ └── two_sum.dart │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.dart │ │ │ ├── bucket_sort.dart │ │ │ ├── counting_sort.dart │ │ │ ├── heap_sort.dart │ │ │ ├── insertion_sort.dart │ │ │ ├── merge_sort.dart │ │ │ ├── quick_sort.dart │ │ │ ├── radix_sort.dart │ │ │ └── selection_sort.dart │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.dart │ │ │ ├── array_queue.dart │ │ │ ├── array_stack.dart │ │ │ ├── deque.dart │ │ │ ├── linkedlist_deque.dart │ │ │ ├── linkedlist_queue.dart │ │ │ ├── linkedlist_stack.dart │ │ │ ├── queue.dart │ │ │ └── stack.dart │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.dart │ │ │ ├── avl_tree.dart │ │ │ ├── binary_search_tree.dart │ │ │ ├── binary_tree.dart │ │ │ ├── binary_tree_bfs.dart │ │ │ └── binary_tree_dfs.dart │ │ └── utils/ │ │ ├── list_node.dart │ │ ├── print_util.dart │ │ ├── tree_node.dart │ │ └── vertex.dart │ ├── docker-compose.yml │ ├── go/ │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.go │ │ │ ├── array_test.go │ │ │ ├── linked_list.go │ │ │ ├── linked_list_test.go │ │ │ ├── list_test.go │ │ │ ├── my_list.go │ │ │ └── my_list_test.go │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.go │ │ │ ├── n_queens_test.go │ │ │ ├── permutation_test.go │ │ │ ├── permutations_i.go │ │ │ ├── permutations_ii.go │ │ │ ├── preorder_traversal_i_compact.go │ │ │ ├── preorder_traversal_ii_compact.go │ │ │ ├── preorder_traversal_iii_compact.go │ │ │ ├── preorder_traversal_iii_template.go │ │ │ ├── preorder_traversal_test.go │ │ │ ├── subset_sum_i.go │ │ │ ├── subset_sum_i_naive.go │ │ │ ├── subset_sum_ii.go │ │ │ └── subset_sum_test.go │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.go │ │ │ ├── iteration_test.go │ │ │ ├── recursion.go │ │ │ ├── recursion_test.go │ │ │ ├── space_complexity.go │ │ │ ├── space_complexity_test.go │ │ │ ├── time_complexity.go │ │ │ ├── time_complexity_test.go │ │ │ ├── worst_best_time_complexity.go │ │ │ └── worst_best_time_complexity_test.go │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.go │ │ │ ├── binary_search_recur_test.go │ │ │ ├── build_tree.go │ │ │ ├── build_tree_test.go │ │ │ ├── hanota.go │ │ │ └── hanota_test.go │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.go │ │ │ ├── climbing_stairs_constraint_dp.go │ │ │ ├── climbing_stairs_dfs.go │ │ │ ├── climbing_stairs_dfs_mem.go │ │ │ ├── climbing_stairs_dp.go │ │ │ ├── climbing_stairs_test.go │ │ │ ├── coin_change.go │ │ │ ├── coin_change_ii.go │ │ │ ├── coin_change_test.go │ │ │ ├── edit_distance.go │ │ │ ├── edit_distance_test.go │ │ │ ├── knapsack.go │ │ │ ├── knapsack_test.go │ │ │ ├── min_cost_climbing_stairs_dp.go │ │ │ ├── min_path_sum.go │ │ │ ├── min_path_sum_test.go │ │ │ └── unbounded_knapsack.go │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.go │ │ │ ├── graph_adjacency_list_test.go │ │ │ ├── graph_adjacency_matrix.go │ │ │ ├── graph_adjacency_matrix_test.go │ │ │ ├── graph_bfs.go │ │ │ ├── graph_bfs_test.go │ │ │ ├── graph_dfs.go │ │ │ └── graph_dfs_test.go │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.go │ │ │ ├── coin_change_greedy_test.go │ │ │ ├── fractional_knapsack.go │ │ │ ├── fractional_knapsack_test.go │ │ │ ├── max_capacity.go │ │ │ ├── max_capacity_test.go │ │ │ ├── max_product_cutting.go │ │ │ └── max_product_cutting_test.go │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.go │ │ │ ├── array_hash_map_test.go │ │ │ ├── hash_collision_test.go │ │ │ ├── hash_map_chaining.go │ │ │ ├── hash_map_open_addressing.go │ │ │ ├── hash_map_test.go │ │ │ └── simple_hash.go │ │ ├── chapter_heap/ │ │ │ ├── heap.go │ │ │ ├── heap_test.go │ │ │ ├── my_heap.go │ │ │ └── top_k.go │ │ ├── chapter_searching/ │ │ │ ├── binary_search.go │ │ │ ├── binary_search_edge.go │ │ │ ├── binary_search_insertion.go │ │ │ ├── binary_search_test.go │ │ │ ├── hashing_search.go │ │ │ ├── hashing_search_test.go │ │ │ ├── linear_search.go │ │ │ ├── linear_search_test.go │ │ │ ├── two_sum.go │ │ │ └── two_sum_test.go │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.go │ │ │ ├── bubble_sort_test.go │ │ │ ├── bucket_sort.go │ │ │ ├── bucket_sort_test.go │ │ │ ├── counting_sort.go │ │ │ ├── counting_sort_test.go │ │ │ ├── heap_sort.go │ │ │ ├── heap_sort_test.go │ │ │ ├── insertion_sort.go │ │ │ ├── insertion_sort_test.go │ │ │ ├── merge_sort.go │ │ │ ├── merge_sort_test.go │ │ │ ├── quick_sort.go │ │ │ ├── quick_sort_test.go │ │ │ ├── radix_sort.go │ │ │ ├── radix_sort_test.go │ │ │ ├── selection_sort.go │ │ │ └── selection_sort_test.go │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.go │ │ │ ├── array_queue.go │ │ │ ├── array_stack.go │ │ │ ├── deque_test.go │ │ │ ├── linkedlist_deque.go │ │ │ ├── linkedlist_queue.go │ │ │ ├── linkedlist_stack.go │ │ │ ├── queue_test.go │ │ │ └── stack_test.go │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.go │ │ │ ├── array_binary_tree_test.go │ │ │ ├── avl_tree.go │ │ │ ├── avl_tree_test.go │ │ │ ├── binary_search_tree.go │ │ │ ├── binary_search_tree_test.go │ │ │ ├── binary_tree_bfs.go │ │ │ ├── binary_tree_bfs_test.go │ │ │ ├── binary_tree_dfs.go │ │ │ ├── binary_tree_dfs_test.go │ │ │ └── binary_tree_test.go │ │ ├── go.mod │ │ └── pkg/ │ │ ├── list_node.go │ │ ├── list_node_test.go │ │ ├── print_utils.go │ │ ├── tree_node.go │ │ ├── tree_node_test.go │ │ └── vertex.go │ ├── java/ │ │ ├── .gitignore │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.java │ │ │ ├── linked_list.java │ │ │ ├── list.java │ │ │ └── my_list.java │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.java │ │ │ ├── permutations_i.java │ │ │ ├── permutations_ii.java │ │ │ ├── preorder_traversal_i_compact.java │ │ │ ├── preorder_traversal_ii_compact.java │ │ │ ├── preorder_traversal_iii_compact.java │ │ │ ├── preorder_traversal_iii_template.java │ │ │ ├── subset_sum_i.java │ │ │ ├── subset_sum_i_naive.java │ │ │ └── subset_sum_ii.java │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.java │ │ │ ├── recursion.java │ │ │ ├── space_complexity.java │ │ │ ├── time_complexity.java │ │ │ └── worst_best_time_complexity.java │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.java │ │ │ ├── build_tree.java │ │ │ └── hanota.java │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.java │ │ │ ├── climbing_stairs_constraint_dp.java │ │ │ ├── climbing_stairs_dfs.java │ │ │ ├── climbing_stairs_dfs_mem.java │ │ │ ├── climbing_stairs_dp.java │ │ │ ├── coin_change.java │ │ │ ├── coin_change_ii.java │ │ │ ├── edit_distance.java │ │ │ ├── knapsack.java │ │ │ ├── min_cost_climbing_stairs_dp.java │ │ │ ├── min_path_sum.java │ │ │ └── unbounded_knapsack.java │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.java │ │ │ ├── graph_adjacency_matrix.java │ │ │ ├── graph_bfs.java │ │ │ └── graph_dfs.java │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.java │ │ │ ├── fractional_knapsack.java │ │ │ ├── max_capacity.java │ │ │ └── max_product_cutting.java │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.java │ │ │ ├── built_in_hash.java │ │ │ ├── hash_map.java │ │ │ ├── hash_map_chaining.java │ │ │ ├── hash_map_open_addressing.java │ │ │ └── simple_hash.java │ │ ├── chapter_heap/ │ │ │ ├── heap.java │ │ │ ├── my_heap.java │ │ │ └── top_k.java │ │ ├── chapter_searching/ │ │ │ ├── binary_search.java │ │ │ ├── binary_search_edge.java │ │ │ ├── binary_search_insertion.java │ │ │ ├── hashing_search.java │ │ │ ├── linear_search.java │ │ │ └── two_sum.java │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.java │ │ │ ├── bucket_sort.java │ │ │ ├── counting_sort.java │ │ │ ├── heap_sort.java │ │ │ ├── insertion_sort.java │ │ │ ├── merge_sort.java │ │ │ ├── quick_sort.java │ │ │ ├── radix_sort.java │ │ │ └── selection_sort.java │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.java │ │ │ ├── array_queue.java │ │ │ ├── array_stack.java │ │ │ ├── deque.java │ │ │ ├── linkedlist_deque.java │ │ │ ├── linkedlist_queue.java │ │ │ ├── linkedlist_stack.java │ │ │ ├── queue.java │ │ │ └── stack.java │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.java │ │ │ ├── avl_tree.java │ │ │ ├── binary_search_tree.java │ │ │ ├── binary_tree.java │ │ │ ├── binary_tree_bfs.java │ │ │ └── binary_tree_dfs.java │ │ └── utils/ │ │ ├── ListNode.java │ │ ├── PrintUtil.java │ │ ├── TreeNode.java │ │ └── Vertex.java │ ├── javascript/ │ │ ├── .prettierrc │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.js │ │ │ ├── linked_list.js │ │ │ ├── list.js │ │ │ └── my_list.js │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.js │ │ │ ├── permutations_i.js │ │ │ ├── permutations_ii.js │ │ │ ├── preorder_traversal_i_compact.js │ │ │ ├── preorder_traversal_ii_compact.js │ │ │ ├── preorder_traversal_iii_compact.js │ │ │ ├── preorder_traversal_iii_template.js │ │ │ ├── subset_sum_i.js │ │ │ ├── subset_sum_i_naive.js │ │ │ └── subset_sum_ii.js │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.js │ │ │ ├── recursion.js │ │ │ ├── space_complexity.js │ │ │ ├── time_complexity.js │ │ │ └── worst_best_time_complexity.js │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.js │ │ │ ├── build_tree.js │ │ │ └── hanota.js │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.js │ │ │ ├── climbing_stairs_constraint_dp.js │ │ │ ├── climbing_stairs_dfs.js │ │ │ ├── climbing_stairs_dfs_mem.js │ │ │ ├── climbing_stairs_dp.js │ │ │ ├── coin_change.js │ │ │ ├── coin_change_ii.js │ │ │ ├── edit_distance.js │ │ │ ├── knapsack.js │ │ │ ├── min_cost_climbing_stairs_dp.js │ │ │ ├── min_path_sum.js │ │ │ └── unbounded_knapsack.js │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.js │ │ │ ├── graph_adjacency_matrix.js │ │ │ ├── graph_bfs.js │ │ │ └── graph_dfs.js │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.js │ │ │ ├── fractional_knapsack.js │ │ │ ├── max_capacity.js │ │ │ └── max_product_cutting.js │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.js │ │ │ ├── hash_map.js │ │ │ ├── hash_map_chaining.js │ │ │ ├── hash_map_open_addressing.js │ │ │ └── simple_hash.js │ │ ├── chapter_heap/ │ │ │ ├── my_heap.js │ │ │ └── top_k.js │ │ ├── chapter_searching/ │ │ │ ├── binary_search.js │ │ │ ├── binary_search_edge.js │ │ │ ├── binary_search_insertion.js │ │ │ ├── hashing_search.js │ │ │ ├── linear_search.js │ │ │ └── two_sum.js │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.js │ │ │ ├── bucket_sort.js │ │ │ ├── counting_sort.js │ │ │ ├── heap_sort.js │ │ │ ├── insertion_sort.js │ │ │ ├── merge_sort.js │ │ │ ├── quick_sort.js │ │ │ ├── radix_sort.js │ │ │ └── selection_sort.js │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.js │ │ │ ├── array_queue.js │ │ │ ├── array_stack.js │ │ │ ├── deque.js │ │ │ ├── linkedlist_deque.js │ │ │ ├── linkedlist_queue.js │ │ │ ├── linkedlist_stack.js │ │ │ ├── queue.js │ │ │ └── stack.js │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.js │ │ │ ├── avl_tree.js │ │ │ ├── binary_search_tree.js │ │ │ ├── binary_tree.js │ │ │ ├── binary_tree_bfs.js │ │ │ └── binary_tree_dfs.js │ │ ├── modules/ │ │ │ ├── ListNode.js │ │ │ ├── PrintUtil.js │ │ │ ├── TreeNode.js │ │ │ └── Vertex.js │ │ └── test_all.js │ ├── kotlin/ │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.kt │ │ │ ├── linked_list.kt │ │ │ ├── list.kt │ │ │ └── my_list.kt │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.kt │ │ │ ├── permutations_i.kt │ │ │ ├── permutations_ii.kt │ │ │ ├── preorder_traversal_i_compact.kt │ │ │ ├── preorder_traversal_ii_compact.kt │ │ │ ├── preorder_traversal_iii_compact.kt │ │ │ ├── preorder_traversal_iii_template.kt │ │ │ ├── subset_sum_i.kt │ │ │ ├── subset_sum_i_naive.kt │ │ │ └── subset_sum_ii.kt │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.kt │ │ │ ├── recursion.kt │ │ │ ├── space_complexity.kt │ │ │ ├── time_complexity.kt │ │ │ └── worst_best_time_complexity.kt │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.kt │ │ │ ├── build_tree.kt │ │ │ └── hanota.kt │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.kt │ │ │ ├── climbing_stairs_constraint_dp.kt │ │ │ ├── climbing_stairs_dfs.kt │ │ │ ├── climbing_stairs_dfs_mem.kt │ │ │ ├── climbing_stairs_dp.kt │ │ │ ├── coin_change.kt │ │ │ ├── coin_change_ii.kt │ │ │ ├── edit_distance.kt │ │ │ ├── knapsack.kt │ │ │ ├── min_cost_climbing_stairs_dp.kt │ │ │ ├── min_path_sum.kt │ │ │ └── unbounded_knapsack.kt │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.kt │ │ │ ├── graph_adjacency_matrix.kt │ │ │ ├── graph_bfs.kt │ │ │ └── graph_dfs.kt │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.kt │ │ │ ├── fractional_knapsack.kt │ │ │ ├── max_capacity.kt │ │ │ └── max_product_cutting.kt │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.kt │ │ │ ├── built_in_hash.kt │ │ │ ├── hash_map.kt │ │ │ ├── hash_map_chaining.kt │ │ │ ├── hash_map_open_addressing.kt │ │ │ └── simple_hash.kt │ │ ├── chapter_heap/ │ │ │ ├── heap.kt │ │ │ ├── my_heap.kt │ │ │ └── top_k.kt │ │ ├── chapter_searching/ │ │ │ ├── binary_search.kt │ │ │ ├── binary_search_edge.kt │ │ │ ├── binary_search_insertion.kt │ │ │ ├── hashing_search.kt │ │ │ ├── linear_search.kt │ │ │ └── two_sum.kt │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.kt │ │ │ ├── bucket_sort.kt │ │ │ ├── counting_sort.kt │ │ │ ├── heap_sort.kt │ │ │ ├── insertion_sort.kt │ │ │ ├── merge_sort.kt │ │ │ ├── quick_sort.kt │ │ │ ├── radix_sort.kt │ │ │ └── selection_sort.kt │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.kt │ │ │ ├── array_queue.kt │ │ │ ├── array_stack.kt │ │ │ ├── deque.kt │ │ │ ├── linkedlist_deque.kt │ │ │ ├── linkedlist_queue.kt │ │ │ ├── linkedlist_stack.kt │ │ │ ├── queue.kt │ │ │ └── stack.kt │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.kt │ │ │ ├── avl_tree.kt │ │ │ ├── binary_search_tree.kt │ │ │ ├── binary_tree.kt │ │ │ ├── binary_tree_bfs.kt │ │ │ └── binary_tree_dfs.kt │ │ └── utils/ │ │ ├── ListNode.kt │ │ ├── PrintUtil.kt │ │ ├── TreeNode.kt │ │ └── Vertex.kt │ ├── python/ │ │ ├── .gitignore │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.py │ │ │ ├── linked_list.py │ │ │ ├── list.py │ │ │ └── my_list.py │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.py │ │ │ ├── permutations_i.py │ │ │ ├── permutations_ii.py │ │ │ ├── preorder_traversal_i_compact.py │ │ │ ├── preorder_traversal_ii_compact.py │ │ │ ├── preorder_traversal_iii_compact.py │ │ │ ├── preorder_traversal_iii_template.py │ │ │ ├── subset_sum_i.py │ │ │ ├── subset_sum_i_naive.py │ │ │ └── subset_sum_ii.py │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.py │ │ │ ├── recursion.py │ │ │ ├── space_complexity.py │ │ │ ├── time_complexity.py │ │ │ └── worst_best_time_complexity.py │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.py │ │ │ ├── build_tree.py │ │ │ └── hanota.py │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.py │ │ │ ├── climbing_stairs_constraint_dp.py │ │ │ ├── climbing_stairs_dfs.py │ │ │ ├── climbing_stairs_dfs_mem.py │ │ │ ├── climbing_stairs_dp.py │ │ │ ├── coin_change.py │ │ │ ├── coin_change_ii.py │ │ │ ├── edit_distance.py │ │ │ ├── knapsack.py │ │ │ ├── min_cost_climbing_stairs_dp.py │ │ │ ├── min_path_sum.py │ │ │ └── unbounded_knapsack.py │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.py │ │ │ ├── graph_adjacency_matrix.py │ │ │ ├── graph_bfs.py │ │ │ └── graph_dfs.py │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.py │ │ │ ├── fractional_knapsack.py │ │ │ ├── max_capacity.py │ │ │ └── max_product_cutting.py │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.py │ │ │ ├── built_in_hash.py │ │ │ ├── hash_map.py │ │ │ ├── hash_map_chaining.py │ │ │ ├── hash_map_open_addressing.py │ │ │ └── simple_hash.py │ │ ├── chapter_heap/ │ │ │ ├── heap.py │ │ │ ├── my_heap.py │ │ │ └── top_k.py │ │ ├── chapter_searching/ │ │ │ ├── binary_search.py │ │ │ ├── binary_search_edge.py │ │ │ ├── binary_search_insertion.py │ │ │ ├── hashing_search.py │ │ │ ├── linear_search.py │ │ │ └── two_sum.py │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.py │ │ │ ├── bucket_sort.py │ │ │ ├── counting_sort.py │ │ │ ├── heap_sort.py │ │ │ ├── insertion_sort.py │ │ │ ├── merge_sort.py │ │ │ ├── quick_sort.py │ │ │ ├── radix_sort.py │ │ │ └── selection_sort.py │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.py │ │ │ ├── array_queue.py │ │ │ ├── array_stack.py │ │ │ ├── deque.py │ │ │ ├── linkedlist_deque.py │ │ │ ├── linkedlist_queue.py │ │ │ ├── linkedlist_stack.py │ │ │ ├── queue.py │ │ │ └── stack.py │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.py │ │ │ ├── avl_tree.py │ │ │ ├── binary_search_tree.py │ │ │ ├── binary_tree.py │ │ │ ├── binary_tree_bfs.py │ │ │ └── binary_tree_dfs.py │ │ ├── modules/ │ │ │ ├── __init__.py │ │ │ ├── list_node.py │ │ │ ├── print_util.py │ │ │ ├── tree_node.py │ │ │ └── vertex.py │ │ └── test_all.py │ ├── pythontutor/ │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.md │ │ │ ├── linked_list.md │ │ │ └── my_list.md │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.md │ │ │ ├── permutations_i.md │ │ │ ├── permutations_ii.md │ │ │ ├── preorder_traversal_i_compact.md │ │ │ ├── preorder_traversal_ii_compact.md │ │ │ ├── preorder_traversal_iii_compact.md │ │ │ ├── preorder_traversal_iii_template.md │ │ │ ├── subset_sum_i.md │ │ │ ├── subset_sum_i_naive.md │ │ │ └── subset_sum_ii.md │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.md │ │ │ ├── recursion.md │ │ │ ├── space_complexity.md │ │ │ ├── time_complexity.md │ │ │ └── worst_best_time_complexity.md │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.md │ │ │ ├── build_tree.md │ │ │ └── hanota.md │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.md │ │ │ ├── climbing_stairs_constraint_dp.md │ │ │ ├── climbing_stairs_dfs.md │ │ │ ├── climbing_stairs_dfs_mem.md │ │ │ ├── climbing_stairs_dp.md │ │ │ ├── coin_change.md │ │ │ ├── coin_change_ii.md │ │ │ ├── edit_distance.md │ │ │ ├── knapsack.md │ │ │ ├── min_cost_climbing_stairs_dp.md │ │ │ ├── min_path_sum.md │ │ │ └── unbounded_knapsack.md │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.md │ │ │ ├── graph_adjacency_matrix.md │ │ │ ├── graph_bfs.md │ │ │ └── graph_dfs.md │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.md │ │ │ ├── fractional_knapsack.md │ │ │ ├── max_capacity.md │ │ │ └── max_product_cutting.md │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.md │ │ │ ├── hash_map_chaining.md │ │ │ └── simple_hash.md │ │ ├── chapter_heap/ │ │ │ ├── my_heap.md │ │ │ └── top_k.md │ │ ├── chapter_searching/ │ │ │ ├── binary_search.md │ │ │ ├── binary_search_edge.md │ │ │ ├── binary_search_insertion.md │ │ │ └── two_sum.md │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.md │ │ │ ├── bucket_sort.md │ │ │ ├── counting_sort.md │ │ │ ├── heap_sort.md │ │ │ ├── insertion_sort.md │ │ │ ├── merge_sort.md │ │ │ ├── quick_sort.md │ │ │ ├── radix_sort.md │ │ │ └── selection_sort.md │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_queue.md │ │ │ ├── array_stack.md │ │ │ ├── linkedlist_queue.md │ │ │ └── linkedlist_stack.md │ │ └── chapter_tree/ │ │ ├── array_binary_tree.md │ │ ├── binary_search_tree.md │ │ ├── binary_tree_bfs.md │ │ └── binary_tree_dfs.md │ ├── ruby/ │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.rb │ │ │ ├── linked_list.rb │ │ │ ├── list.rb │ │ │ └── my_list.rb │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.rb │ │ │ ├── permutations_i.rb │ │ │ ├── permutations_ii.rb │ │ │ ├── preorder_traversal_i_compact.rb │ │ │ ├── preorder_traversal_ii_compact.rb │ │ │ ├── preorder_traversal_iii_compact.rb │ │ │ ├── preorder_traversal_iii_template.rb │ │ │ ├── subset_sum_i.rb │ │ │ ├── subset_sum_i_naive.rb │ │ │ └── subset_sum_ii.rb │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.rb │ │ │ ├── recursion.rb │ │ │ ├── space_complexity.rb │ │ │ ├── time_complexity.rb │ │ │ └── worst_best_time_complexity.rb │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.rb │ │ │ ├── build_tree.rb │ │ │ └── hanota.rb │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.rb │ │ │ ├── climbing_stairs_constraint_dp.rb │ │ │ ├── climbing_stairs_dfs.rb │ │ │ ├── climbing_stairs_dfs_mem.rb │ │ │ ├── climbing_stairs_dp.rb │ │ │ ├── coin_change.rb │ │ │ ├── coin_change_ii.rb │ │ │ ├── edit_distance.rb │ │ │ ├── knapsack.rb │ │ │ ├── min_cost_climbing_stairs_dp.rb │ │ │ ├── min_path_sum.rb │ │ │ └── unbounded_knapsack.rb │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.rb │ │ │ ├── graph_adjacency_matrix.rb │ │ │ ├── graph_bfs.rb │ │ │ └── graph_dfs.rb │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.rb │ │ │ ├── fractional_knapsack.rb │ │ │ ├── max_capacity.rb │ │ │ └── max_product_cutting.rb │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.rb │ │ │ ├── built_in_hash.rb │ │ │ ├── hash_map.rb │ │ │ ├── hash_map_chaining.rb │ │ │ ├── hash_map_open_addressing.rb │ │ │ └── simple_hash.rb │ │ ├── chapter_heap/ │ │ │ ├── my_heap.rb │ │ │ └── top_k.rb │ │ ├── chapter_searching/ │ │ │ ├── binary_search.rb │ │ │ ├── binary_search_edge.rb │ │ │ ├── binary_search_insertion.rb │ │ │ ├── hashing_search.rb │ │ │ ├── linear_search.rb │ │ │ └── two_sum.rb │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.rb │ │ │ ├── bucket_sort.rb │ │ │ ├── counting_sort.rb │ │ │ ├── heap_sort.rb │ │ │ ├── insertion_sort.rb │ │ │ ├── merge_sort.rb │ │ │ ├── quick_sort.rb │ │ │ ├── radix_sort.rb │ │ │ └── selection_sort.rb │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.rb │ │ │ ├── array_queue.rb │ │ │ ├── array_stack.rb │ │ │ ├── deque.rb │ │ │ ├── linkedlist_deque.rb │ │ │ ├── linkedlist_queue.rb │ │ │ ├── linkedlist_stack.rb │ │ │ ├── queue.rb │ │ │ └── stack.rb │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.rb │ │ │ ├── avl_tree.rb │ │ │ ├── binary_search_tree.rb │ │ │ ├── binary_tree.rb │ │ │ ├── binary_tree_bfs.rb │ │ │ └── binary_tree_dfs.rb │ │ ├── test_all.rb │ │ └── utils/ │ │ ├── list_node.rb │ │ ├── print_util.rb │ │ ├── tree_node.rb │ │ └── vertex.rb │ ├── rust/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.rs │ │ │ ├── linked_list.rs │ │ │ ├── list.rs │ │ │ └── my_list.rs │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.rs │ │ │ ├── permutations_i.rs │ │ │ ├── permutations_ii.rs │ │ │ ├── preorder_traversal_i_compact.rs │ │ │ ├── preorder_traversal_ii_compact.rs │ │ │ ├── preorder_traversal_iii_compact.rs │ │ │ ├── preorder_traversal_iii_template.rs │ │ │ ├── subset_sum_i.rs │ │ │ ├── subset_sum_i_naive.rs │ │ │ └── subset_sum_ii.rs │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.rs │ │ │ ├── recursion.rs │ │ │ ├── space_complexity.rs │ │ │ ├── time_complexity.rs │ │ │ └── worst_best_time_complexity.rs │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.rs │ │ │ ├── build_tree.rs │ │ │ └── hanota.rs │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.rs │ │ │ ├── climbing_stairs_constraint_dp.rs │ │ │ ├── climbing_stairs_dfs.rs │ │ │ ├── climbing_stairs_dfs_mem.rs │ │ │ ├── climbing_stairs_dp.rs │ │ │ ├── coin_change.rs │ │ │ ├── coin_change_ii.rs │ │ │ ├── edit_distance.rs │ │ │ ├── knapsack.rs │ │ │ ├── min_cost_climbing_stairs_dp.rs │ │ │ ├── min_path_sum.rs │ │ │ └── unbounded_knapsack.rs │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.rs │ │ │ ├── graph_adjacency_matrix.rs │ │ │ ├── graph_bfs.rs │ │ │ └── graph_dfs.rs │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.rs │ │ │ ├── fractional_knapsack.rs │ │ │ ├── max_capacity.rs │ │ │ └── max_product_cutting.rs │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.rs │ │ │ ├── build_in_hash.rs │ │ │ ├── hash_map.rs │ │ │ ├── hash_map_chaining.rs │ │ │ ├── hash_map_open_addressing.rs │ │ │ └── simple_hash.rs │ │ ├── chapter_heap/ │ │ │ ├── heap.rs │ │ │ ├── my_heap.rs │ │ │ └── top_k.rs │ │ ├── chapter_searching/ │ │ │ ├── binary_search.rs │ │ │ ├── binary_search_edge.rs │ │ │ ├── binary_search_insertion.rs │ │ │ ├── hashing_search.rs │ │ │ ├── linear_search.rs │ │ │ └── two_sum.rs │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.rs │ │ │ ├── bucket_sort.rs │ │ │ ├── counting_sort.rs │ │ │ ├── heap_sort.rs │ │ │ ├── insertion_sort.rs │ │ │ ├── merge_sort.rs │ │ │ ├── quick_sort.rs │ │ │ ├── radix_sort.rs │ │ │ └── selection_sort.rs │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.rs │ │ │ ├── array_queue.rs │ │ │ ├── array_stack.rs │ │ │ ├── deque.rs │ │ │ ├── linkedlist_deque.rs │ │ │ ├── linkedlist_queue.rs │ │ │ ├── linkedlist_stack.rs │ │ │ ├── queue.rs │ │ │ └── stack.rs │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.rs │ │ │ ├── avl_tree.rs │ │ │ ├── binary_search_tree.rs │ │ │ ├── binary_tree.rs │ │ │ ├── binary_tree_bfs.rs │ │ │ └── binary_tree_dfs.rs │ │ ├── include/ │ │ │ ├── include.rs │ │ │ ├── list_node.rs │ │ │ ├── print_util.rs │ │ │ ├── tree_node.rs │ │ │ └── vertex.rs │ │ └── src/ │ │ ├── include/ │ │ │ ├── list_node.rs │ │ │ ├── mod.rs │ │ │ ├── print_util.rs │ │ │ ├── tree_node.rs │ │ │ └── vertex.rs │ │ └── lib.rs │ ├── swift/ │ │ ├── .gitignore │ │ ├── Package.resolved │ │ ├── Package.swift │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.swift │ │ │ ├── linked_list.swift │ │ │ ├── list.swift │ │ │ └── my_list.swift │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.swift │ │ │ ├── permutations_i.swift │ │ │ ├── permutations_ii.swift │ │ │ ├── preorder_traversal_i_compact.swift │ │ │ ├── preorder_traversal_ii_compact.swift │ │ │ ├── preorder_traversal_iii_compact.swift │ │ │ ├── preorder_traversal_iii_template.swift │ │ │ ├── subset_sum_i.swift │ │ │ ├── subset_sum_i_naive.swift │ │ │ └── subset_sum_ii.swift │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.swift │ │ │ ├── recursion.swift │ │ │ ├── space_complexity.swift │ │ │ ├── time_complexity.swift │ │ │ └── worst_best_time_complexity.swift │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.swift │ │ │ ├── build_tree.swift │ │ │ └── hanota.swift │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.swift │ │ │ ├── climbing_stairs_constraint_dp.swift │ │ │ ├── climbing_stairs_dfs.swift │ │ │ ├── climbing_stairs_dfs_mem.swift │ │ │ ├── climbing_stairs_dp.swift │ │ │ ├── coin_change.swift │ │ │ ├── coin_change_ii.swift │ │ │ ├── edit_distance.swift │ │ │ ├── knapsack.swift │ │ │ ├── min_cost_climbing_stairs_dp.swift │ │ │ ├── min_path_sum.swift │ │ │ └── unbounded_knapsack.swift │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.swift │ │ │ ├── graph_adjacency_list_target.swift │ │ │ ├── graph_adjacency_matrix.swift │ │ │ ├── graph_bfs.swift │ │ │ └── graph_dfs.swift │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.swift │ │ │ ├── fractional_knapsack.swift │ │ │ ├── max_capacity.swift │ │ │ └── max_product_cutting.swift │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.swift │ │ │ ├── built_in_hash.swift │ │ │ ├── hash_map.swift │ │ │ ├── hash_map_chaining.swift │ │ │ ├── hash_map_open_addressing.swift │ │ │ └── simple_hash.swift │ │ ├── chapter_heap/ │ │ │ ├── heap.swift │ │ │ ├── my_heap.swift │ │ │ └── top_k.swift │ │ ├── chapter_searching/ │ │ │ ├── binary_search.swift │ │ │ ├── binary_search_edge.swift │ │ │ ├── binary_search_insertion.swift │ │ │ ├── binary_search_insertion_target.swift │ │ │ ├── hashing_search.swift │ │ │ ├── linear_search.swift │ │ │ └── two_sum.swift │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.swift │ │ │ ├── bucket_sort.swift │ │ │ ├── counting_sort.swift │ │ │ ├── heap_sort.swift │ │ │ ├── insertion_sort.swift │ │ │ ├── merge_sort.swift │ │ │ ├── quick_sort.swift │ │ │ ├── radix_sort.swift │ │ │ └── selection_sort.swift │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.swift │ │ │ ├── array_queue.swift │ │ │ ├── array_stack.swift │ │ │ ├── deque.swift │ │ │ ├── linkedlist_deque.swift │ │ │ ├── linkedlist_queue.swift │ │ │ ├── linkedlist_stack.swift │ │ │ ├── queue.swift │ │ │ └── stack.swift │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.swift │ │ │ ├── avl_tree.swift │ │ │ ├── binary_search_tree.swift │ │ │ ├── binary_tree.swift │ │ │ ├── binary_tree_bfs.swift │ │ │ └── binary_tree_dfs.swift │ │ └── utils/ │ │ ├── ListNode.swift │ │ ├── Pair.swift │ │ ├── PrintUtil.swift │ │ ├── TreeNode.swift │ │ └── Vertex.swift │ ├── typescript/ │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── chapter_array_and_linkedlist/ │ │ │ ├── array.ts │ │ │ ├── linked_list.ts │ │ │ ├── list.ts │ │ │ └── my_list.ts │ │ ├── chapter_backtracking/ │ │ │ ├── n_queens.ts │ │ │ ├── permutations_i.ts │ │ │ ├── permutations_ii.ts │ │ │ ├── preorder_traversal_i_compact.ts │ │ │ ├── preorder_traversal_ii_compact.ts │ │ │ ├── preorder_traversal_iii_compact.ts │ │ │ ├── preorder_traversal_iii_template.ts │ │ │ ├── subset_sum_i.ts │ │ │ ├── subset_sum_i_naive.ts │ │ │ └── subset_sum_ii.ts │ │ ├── chapter_computational_complexity/ │ │ │ ├── iteration.ts │ │ │ ├── recursion.ts │ │ │ ├── space_complexity.ts │ │ │ ├── time_complexity.ts │ │ │ └── worst_best_time_complexity.ts │ │ ├── chapter_divide_and_conquer/ │ │ │ ├── binary_search_recur.ts │ │ │ ├── build_tree.ts │ │ │ └── hanota.ts │ │ ├── chapter_dynamic_programming/ │ │ │ ├── climbing_stairs_backtrack.ts │ │ │ ├── climbing_stairs_constraint_dp.ts │ │ │ ├── climbing_stairs_dfs.ts │ │ │ ├── climbing_stairs_dfs_mem.ts │ │ │ ├── climbing_stairs_dp.ts │ │ │ ├── coin_change.ts │ │ │ ├── coin_change_ii.ts │ │ │ ├── edit_distance.ts │ │ │ ├── knapsack.ts │ │ │ ├── min_cost_climbing_stairs_dp.ts │ │ │ ├── min_path_sum.ts │ │ │ └── unbounded_knapsack.ts │ │ ├── chapter_graph/ │ │ │ ├── graph_adjacency_list.ts │ │ │ ├── graph_adjacency_matrix.ts │ │ │ ├── graph_bfs.ts │ │ │ └── graph_dfs.ts │ │ ├── chapter_greedy/ │ │ │ ├── coin_change_greedy.ts │ │ │ ├── fractional_knapsack.ts │ │ │ ├── max_capacity.ts │ │ │ └── max_product_cutting.ts │ │ ├── chapter_hashing/ │ │ │ ├── array_hash_map.ts │ │ │ ├── hash_map.ts │ │ │ ├── hash_map_chaining.ts │ │ │ ├── hash_map_open_addressing.ts │ │ │ └── simple_hash.ts │ │ ├── chapter_heap/ │ │ │ ├── my_heap.ts │ │ │ └── top_k.ts │ │ ├── chapter_searching/ │ │ │ ├── binary_search.ts │ │ │ ├── binary_search_edge.ts │ │ │ ├── binary_search_insertion.ts │ │ │ ├── hashing_search.ts │ │ │ ├── linear_search.ts │ │ │ └── two_sum.ts │ │ ├── chapter_sorting/ │ │ │ ├── bubble_sort.ts │ │ │ ├── bucket_sort.ts │ │ │ ├── counting_sort.ts │ │ │ ├── heap_sort.ts │ │ │ ├── insertion_sort.ts │ │ │ ├── merge_sort.ts │ │ │ ├── quick_sort.ts │ │ │ ├── radix_sort.ts │ │ │ └── selection_sort.ts │ │ ├── chapter_stack_and_queue/ │ │ │ ├── array_deque.ts │ │ │ ├── array_queue.ts │ │ │ ├── array_stack.ts │ │ │ ├── deque.ts │ │ │ ├── linkedlist_deque.ts │ │ │ ├── linkedlist_queue.ts │ │ │ ├── linkedlist_stack.ts │ │ │ ├── queue.ts │ │ │ └── stack.ts │ │ ├── chapter_tree/ │ │ │ ├── array_binary_tree.ts │ │ │ ├── avl_tree.ts │ │ │ ├── binary_search_tree.ts │ │ │ ├── binary_tree.ts │ │ │ ├── binary_tree_bfs.ts │ │ │ └── binary_tree_dfs.ts │ │ ├── modules/ │ │ │ ├── ListNode.ts │ │ │ ├── PrintUtil.ts │ │ │ ├── TreeNode.ts │ │ │ └── Vertex.ts │ │ └── tsconfig.json │ └── zig/ │ ├── .gitignore │ ├── build.zig │ ├── chapter_array_and_linkedlist/ │ │ ├── array.zig │ │ ├── linked_list.zig │ │ ├── list.zig │ │ └── my_list.zig │ ├── chapter_computational_complexity/ │ │ ├── iteration.zig │ │ ├── recursion.zig │ │ ├── space_complexity.zig │ │ ├── time_complexity.zig │ │ └── worst_best_time_complexity.zig │ ├── chapter_dynamic_programming/ │ │ ├── climbing_stairs_backtrack.zig │ │ ├── climbing_stairs_constraint_dp.zig │ │ ├── climbing_stairs_dfs.zig │ │ ├── climbing_stairs_dfs_mem.zig │ │ ├── climbing_stairs_dp.zig │ │ ├── coin_change.zig │ │ ├── coin_change_ii.zig │ │ ├── edit_distance.zig │ │ ├── knapsack.zig │ │ ├── min_cost_climbing_stairs_dp.zig │ │ ├── min_path_sum.zig │ │ └── unbounded_knapsack.zig │ ├── chapter_hashing/ │ │ ├── array_hash_map.zig │ │ └── hash_map.zig │ ├── chapter_heap/ │ │ ├── heap.zig │ │ └── my_heap.zig │ ├── chapter_searching/ │ │ ├── binary_search.zig │ │ ├── hashing_search.zig │ │ ├── linear_search.zig │ │ └── two_sum.zig │ ├── chapter_sorting/ │ │ ├── bubble_sort.zig │ │ ├── insertion_sort.zig │ │ ├── merge_sort.zig │ │ ├── quick_sort.zig │ │ └── radix_sort.zig │ ├── chapter_stack_and_queue/ │ │ ├── array_queue.zig │ │ ├── array_stack.zig │ │ ├── deque.zig │ │ ├── linkedlist_deque.zig │ │ ├── linkedlist_queue.zig │ │ ├── linkedlist_stack.zig │ │ ├── queue.zig │ │ └── stack.zig │ ├── chapter_tree/ │ │ ├── avl_tree.zig │ │ ├── binary_search_tree.zig │ │ ├── binary_tree.zig │ │ ├── binary_tree_bfs.zig │ │ └── binary_tree_dfs.zig │ ├── include/ │ │ ├── ListNode.zig │ │ ├── PrintUtil.zig │ │ ├── TreeNode.zig │ │ └── include.zig │ ├── main.zig │ └── utils/ │ ├── ListNode.zig │ ├── TreeNode.zig │ ├── format.zig │ └── utils.zig ├── docs/ │ ├── chapter_appendix/ │ │ ├── contribution.md │ │ ├── index.md │ │ ├── installation.md │ │ └── terminology.md │ ├── chapter_array_and_linkedlist/ │ │ ├── array.md │ │ ├── index.md │ │ ├── linked_list.md │ │ ├── list.md │ │ ├── ram_and_cache.md │ │ └── summary.md │ ├── chapter_backtracking/ │ │ ├── backtracking_algorithm.md │ │ ├── index.md │ │ ├── n_queens_problem.md │ │ ├── permutations_problem.md │ │ ├── subset_sum_problem.md │ │ └── summary.md │ ├── chapter_computational_complexity/ │ │ ├── index.md │ │ ├── iteration_and_recursion.md │ │ ├── performance_evaluation.md │ │ ├── space_complexity.md │ │ ├── summary.md │ │ └── time_complexity.md │ ├── chapter_data_structure/ │ │ ├── basic_data_types.md │ │ ├── character_encoding.md │ │ ├── classification_of_data_structure.md │ │ ├── index.md │ │ ├── number_encoding.md │ │ └── summary.md │ ├── chapter_divide_and_conquer/ │ │ ├── binary_search_recur.md │ │ ├── build_binary_tree_problem.md │ │ ├── divide_and_conquer.md │ │ ├── hanota_problem.md │ │ ├── index.md │ │ └── summary.md │ ├── chapter_dynamic_programming/ │ │ ├── dp_problem_features.md │ │ ├── dp_solution_pipeline.md │ │ ├── edit_distance_problem.md │ │ ├── index.md │ │ ├── intro_to_dynamic_programming.md │ │ ├── knapsack_problem.md │ │ ├── summary.md │ │ └── unbounded_knapsack_problem.md │ ├── chapter_graph/ │ │ ├── graph.md │ │ ├── graph_operations.md │ │ ├── graph_traversal.md │ │ ├── index.md │ │ └── summary.md │ ├── chapter_greedy/ │ │ ├── fractional_knapsack_problem.md │ │ ├── greedy_algorithm.md │ │ ├── index.md │ │ ├── max_capacity_problem.md │ │ ├── max_product_cutting_problem.md │ │ └── summary.md │ ├── chapter_hashing/ │ │ ├── hash_algorithm.md │ │ ├── hash_collision.md │ │ ├── hash_map.md │ │ ├── index.md │ │ └── summary.md │ ├── chapter_heap/ │ │ ├── build_heap.md │ │ ├── heap.md │ │ ├── index.md │ │ ├── summary.md │ │ └── top_k.md │ ├── chapter_hello_algo/ │ │ └── index.md │ ├── chapter_introduction/ │ │ ├── algorithms_are_everywhere.md │ │ ├── index.md │ │ ├── summary.md │ │ └── what_is_dsa.md │ ├── chapter_preface/ │ │ ├── about_the_book.md │ │ ├── index.md │ │ ├── suggestions.md │ │ └── summary.md │ ├── chapter_reference/ │ │ └── index.md │ ├── chapter_searching/ │ │ ├── binary_search.md │ │ ├── binary_search_edge.md │ │ ├── binary_search_insertion.md │ │ ├── index.md │ │ ├── replace_linear_by_hashing.md │ │ ├── searching_algorithm_revisited.md │ │ └── summary.md │ ├── chapter_sorting/ │ │ ├── bubble_sort.md │ │ ├── bucket_sort.md │ │ ├── counting_sort.md │ │ ├── heap_sort.md │ │ ├── index.md │ │ ├── insertion_sort.md │ │ ├── merge_sort.md │ │ ├── quick_sort.md │ │ ├── radix_sort.md │ │ ├── selection_sort.md │ │ ├── sorting_algorithm.md │ │ └── summary.md │ ├── chapter_stack_and_queue/ │ │ ├── deque.md │ │ ├── index.md │ │ ├── queue.md │ │ ├── stack.md │ │ └── summary.md │ ├── chapter_tree/ │ │ ├── array_representation_of_tree.md │ │ ├── avl_tree.md │ │ ├── binary_search_tree.md │ │ ├── binary_tree.md │ │ ├── binary_tree_traversal.md │ │ ├── index.md │ │ └── summary.md │ ├── index.html │ └── index.md └── mkdocs.yml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.py linguist-language=Python *.cpp linguist-language=C++ *.hpp linguist-language=C++ *.java linguist-language=Java *.go linguist-language=Go *.swift linguist-language=Swift *.js linguist-language=JavaScript *.c linguist-language=Other *.h linguist-language=Other *.cs linguist-language=Other *.zig linguist-language=Other *.rs linguist-language=Other *.ts linguist-language=Other *.dart linguist-language=Other *.kt linguist-language=Other *.html linguist-detectable=false *.css linguist-detectable=false ================================================ FILE: .github/pull_request_template.md ================================================ If this pull request (PR) pertains to **Chinese-to-English translation**, please confirm that you have read the contribution guidelines and complete the checklist below: - [ ] This PR represents the translation of a single, complete document, or contains only bug fixes. - [ ] The translation accurately conveys the original meaning and intent of the Chinese version. If deviations exist, I have provided explanatory comments to clarify the reasons. If this pull request (PR) is associated with **coding or code transpilation**, please attach the relevant console outputs to the PR and complete the following checklist: - [ ] I have thoroughly reviewed the code, focusing on its formatting, comments, indentation, and file headers. - [ ] I have confirmed that the code execution outputs are consistent with those produced by the reference code (Python or Java). - [ ] The code is designed to be compatible on standard operating systems, including Windows, macOS, and Ubuntu. ================================================ FILE: .github/workflows/c.yml ================================================ # This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml name: C on: push: branches: ["main"] paths: - "codes/c/**/*.c" - "codes/c/**/*.h" - "en/codes/c/**/*.c" - "en/codes/c/**/*.h" pull_request: branches: ["main"] paths: - "codes/c/**/*.c" - "codes/c/**/*.h" - "en/codes/c/**/*.c" - "en/codes/c/**/*.h" workflow_dispatch: jobs: build: runs-on: ${{ matrix.os }} strategy: # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. fail-fast: true # Set up a matrix to run the following 3 configurations: # 1. # 2. # 3. # # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. matrix: os: [ubuntu-latest, windows-latest] build_type: [Release] c_compiler: [gcc, clang, cl] code-dir: ["codes/c", "en/codes/c"] include: - os: windows-latest c_compiler: cl cpp_compiler: cl - os: ubuntu-latest c_compiler: gcc cpp_compiler: g++ - os: ubuntu-latest c_compiler: clang cpp_compiler: clang++ exclude: - os: windows-latest c_compiler: gcc - os: windows-latest c_compiler: clang - os: ubuntu-latest c_compiler: cl steps: - uses: actions/checkout@v4 - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: > cmake -B ${{ github.workspace }}/${{ matrix.code-dir }}/build -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -S ${{ github.workspace }}/${{ matrix.code-dir }} - name: Build # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). run: cmake --build ${{ github.workspace }}/${{ matrix.code-dir }}/build --config ${{ matrix.build_type }} - name: Test working-directory: ${{ github.workspace }}/${{ matrix.code-dir }}/build # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest --build-config ${{ matrix.build_type }} ================================================ FILE: .github/workflows/cpp.yml ================================================ # This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml name: C++ on: push: branches: ["main"] paths: - "codes/cpp/**/*.cpp" - "codes/cpp/**/*.hpp" - "en/codes/cpp/**/*.cpp" - "en/codes/cpp/**/*.hpp" pull_request: branches: ["main"] paths: - "codes/cpp/**/*.cpp" - "codes/cpp/**/*.hpp" - "en/codes/cpp/**/*.cpp" - "en/codes/cpp/**/*.hpp" workflow_dispatch: jobs: build: runs-on: ${{ matrix.os }} strategy: # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. fail-fast: true # Set up a matrix to run the following 3 configurations: # 1. # 2. # 3. # # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. matrix: os: [ubuntu-latest, windows-latest] build_type: [Release] c_compiler: [gcc, clang, cl] code-dir: ["codes/cpp", "en/codes/cpp"] include: - os: windows-latest c_compiler: cl cpp_compiler: cl - os: ubuntu-latest c_compiler: gcc cpp_compiler: g++ - os: ubuntu-latest c_compiler: clang cpp_compiler: clang++ exclude: - os: windows-latest c_compiler: gcc - os: windows-latest c_compiler: clang - os: ubuntu-latest c_compiler: cl steps: - uses: actions/checkout@v4 - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: > cmake -B ${{ github.workspace }}/${{ matrix.code-dir }}/build -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -S ${{ github.workspace }}/${{ matrix.code-dir }} - name: Build # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). run: cmake --build ${{ github.workspace }}/${{ matrix.code-dir }}/build --config ${{ matrix.build_type }} - name: Test working-directory: ${{ github.workspace }}/${{ matrix.code-dir }}/build # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest --build-config ${{ matrix.build_type }} ================================================ FILE: .github/workflows/dart.yml ================================================ # This workflow will install Dart SDK, run format, analyze and build with Dart name: Dart on: push: branches: ["main"] paths: - "codes/dart/**/*.dart" - "en/codes/dart/**/*.dart" pull_request: branches: ["main"] paths: - "codes/dart/**/*.dart" - "en/codes/dart/**/*.dart" workflow_dispatch: permissions: contents: read jobs: build: name: Dart ${{ matrix.dart-sdk }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] dart-sdk: [stable] code-dir: ["codes/dart", "en/codes/dart"] steps: - uses: actions/checkout@v4 - name: Set up Dart ${{ matrix.dart-sdk }} uses: dart-lang/setup-dart@v1 with: sdk: ${{ matrix.dart-sdk}} - name: Run format run: dart format ${{ matrix.code-dir }} - name: Run analyze run: dart analyze ${{ matrix.code-dir }} - name: Run build run: dart ${{ matrix.code-dir }}/build.dart ================================================ FILE: .github/workflows/dotnet.yml ================================================ # This workflow will build a .NET project # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net name: .NET on: push: branches: ["main"] paths: - "codes/csharp/**/*.cs" - "en/codes/csharp/**/*.cs" pull_request: branches: ["main"] paths: - "codes/csharp/**/*.cs" - "en/codes/csharp/**/*.cs" workflow_dispatch: jobs: build: name: .NET ${{ matrix.dotnet-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} defaults: run: working-directory: ${{ matrix.code-dir }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] dotnet-version: ["8.0.x"] code-dir: ["codes/csharp", "en/codes/csharp"] steps: - uses: actions/checkout@v4 - name: Setup .NET ${{ matrix.dotnet-version }} uses: actions/setup-dotnet@v3 with: dotnet-version: ${{ matrix.dotnet-version }} - name: Restore dependencies run: dotnet restore hello-algo.csproj - name: Build run: dotnet build --no-restore hello-algo.csproj - name: Test with dotnet run: dotnet test hello-algo.csproj ================================================ FILE: .github/workflows/go.yml ================================================ name: Go on: push: branches: ["main"] paths: - "codes/go/**/*.go" - "en/codes/go/**/*.go" pull_request: branches: ["main"] paths: - "codes/go/**/*.go" - "en/codes/go/**/*.go" workflow_dispatch: jobs: build: name: Go ${{ matrix.go-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} defaults: run: working-directory: ${{ matrix.code-dir }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] go-version: ["1.19.x"] code-dir: ["codes/go", "en/codes/go"] steps: - uses: actions/checkout@v4 - name: Setup Go ${{ matrix.go-version }} uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} - name: Check out code into the Go module directory run: go get -v -t -d ./... - name: Build run: go build -v ./... - name: Test with Go run: go test -v ./... ================================================ FILE: .github/workflows/java.yml ================================================ # # This workflow will install OpenJDK and build the Java project # For more information see: https://github.com/actions/setup-java name: Java on: push: branches: ["main"] paths: - "codes/java/**/*.java" - "en/codes/java/**/*.java" pull_request: branches: ["main"] paths: - "codes/java/**/*.java" - "en/codes/java/**/*.java" workflow_dispatch: jobs: build: runs-on: ubuntu-20.04 strategy: matrix: java: ["11", "17"] code-dir: ["codes/java", "en/codes/java"] name: Java ${{ matrix.Java }} sample steps: - uses: actions/checkout@v4 - name: Setup java uses: actions/setup-java@v3 with: distribution: "temurin" java-version: ${{ matrix.java }} - run: javac -d ${{ matrix.code-dir }}/build ${{ matrix.code-dir }}/**/*.java ================================================ FILE: .github/workflows/javascript.yml ================================================ name: JavaScript on: push: branches: ["main"] paths: - "codes/javascript/**/*.js" - "en/codes/javascript/**/*.js" pull_request: branches: ["main"] paths: - "codes/javascript/**/*.js" - "en/codes/javascript/**/*.js" workflow_dispatch: jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] code-dir: ["codes/javascript", "en/codes/javascript"] steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v6 with: node-version: 24.x - uses: denoland/setup-deno@v2 with: deno-version: v2.x - name: Run JavaScript Code run: deno run -A ${{ matrix.code-dir }}/test_all.js ================================================ FILE: .github/workflows/kotlin.yml ================================================ name: Kotlin on: push: branches: ["main"] paths: - "codes/kotlin/**/*.kt" - "en/codes/kotlin/**/*.kt" pull_request: branches: ["main"] paths: - "codes/kotlin/**/*.kt" - "en/codes/kotlin/**/*.kt" workflow_dispatch: jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest] code-dir: ["codes/kotlin", "en/codes/kotlin"] name: Kotlin on ${{ matrix.os }} steps: - uses: actions/checkout@v4.1.2 - name: Build JAR run: kotlinc ${{ matrix.code-dir }}/**/*.kt -include-runtime -d ${{ matrix.code-dir }}/build/test.jar ================================================ FILE: .github/workflows/python.yml ================================================ # This workflow will install Python dependencies, run tests and lint with Python # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python name: Python on: push: branches: ["main"] paths: - "codes/python/**/*.py" - "en/codes/python/**/*.py" pull_request: branches: ["main"] paths: - "codes/python/**/*.py" - "en/codes/python/**/*.py" workflow_dispatch: permissions: contents: read jobs: build: name: Python ${{ matrix.python-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.10"] code-dir: ["codes/python", "en/codes/python"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install black - name: Lint with black run: | black ${{ matrix.code-dir }} - name: Test python code run: | cd ${{ matrix.code-dir }} && python test_all.py ================================================ FILE: .github/workflows/ruby.yml ================================================ # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by separate terms of service, privacy policy, and support documentation. # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake。 # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby name: Ruby on: push: branches: ["main"] paths: - "codes/ruby/**/*.rb" - "en/codes/ruby/**/*.rb" pull_request: branches: ["main"] paths: - "codes/ruby/**/*.rb" - "en/codes/ruby/**/*.rb" workflow_dispatch: permissions: contents: read jobs: test: name: Ruby ${{ matrix.ruby-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] ruby-version: ["3.3"] code-dir: ["codes/ruby", "en/codes/ruby"] steps: - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} - name: Run tests run: ruby ${{ matrix.code-dir }}/test_all.rb ================================================ FILE: .github/workflows/rust.yml ================================================ name: Rust on: push: branches: ["main"] paths: - "codes/rust/**/*.rs" - "codes/rust/Cargo.toml" - "en/codes/rust/**/*.rs" - "en/codes/rust/Cargo.toml" pull_request: branches: ["main"] paths: - "codes/rust/**/*.rs" - "codes/rust/Cargo.toml" - "en/codes/rust/**/*.rs" - "en/codes/rust/Cargo.toml" jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] code-dir: ["codes/rust", "en/codes/rust"] steps: - uses: brndnmtthws/rust-action-rustup@v1 with: toolchain: nightly - uses: actions/checkout@v4 - name: Build run: cargo build --manifest-path=${{ matrix.code-dir }}/Cargo.toml && cargo build --manifest-path=${{ matrix.code-dir }}/Cargo.toml --release ================================================ FILE: .github/workflows/swift.yml ================================================ # This workflow will build a Swift project # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift name: Swift on: push: branches: ["main"] paths: - "codes/swift/**/*.swift" - "en/codes/swift/**/*.swift" pull_request: branches: ["main"] paths: - "codes/swift/**/*.swift" - "en/codes/swift/**/*.swift" workflow_dispatch: jobs: build: name: Swift on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: ["ubuntu-22.04", "macos-14"] code-dir: ["codes/swift", "en/codes/swift"] steps: - uses: actions/checkout@v4 - name: Build run: swift build --package-path ${{ matrix.code-dir }} ================================================ FILE: .github/workflows/typescript.yml ================================================ name: TypeScript on: push: branches: ["main"] paths: - "codes/typescript/**/*.ts" - "en/codes/typescript/**/*.ts" pull_request: branches: ["main"] paths: - "codes/typescript/**/*.ts" - "en/codes/typescript/**/*.ts" workflow_dispatch: jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] code-dir: ["codes/typescript", "en/codes/typescript"] steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v6 with: node-version: 24.x - name: Install dependencies run: cd ${{ matrix.code-dir }} && npm install - name: Check TypeScript code run: cd ${{ matrix.code-dir }} && npm run check ================================================ FILE: .gitignore ================================================ .DS_Store .vscode/ /node_modules # build /build /site /utils ================================================ FILE: Dockerfile ================================================ FROM python:3.10.0-alpine # Official PyPI is preferred when reachable. ENV PIP_INDEX_URL=https://pypi.org/simple # Use the mirror when official PyPI is unreachable. # ENV PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple RUN pip install --upgrade pip RUN pip install mkdocs-material==9.5.5 mkdocs-glightbox WORKDIR /hello-algo COPY overrides ./build/overrides COPY docs ./build/docs COPY mkdocs.yml mkdocs.yml RUN mkdocs build -f mkdocs.yml COPY zh-hant/docs ./build/zh-hant/docs COPY zh-hant/mkdocs.yml ./zh-hant/mkdocs.yml RUN mkdocs build -f ./zh-hant/mkdocs.yml COPY en/docs ./build/en/docs COPY en/mkdocs.yml ./en/mkdocs.yml RUN mkdocs build -f ./en/mkdocs.yml COPY ja/docs ./build/ja/docs COPY ja/mkdocs.yml ./ja/mkdocs.yml RUN mkdocs build -f ./ja/mkdocs.yml COPY ru/docs ./build/ru/docs COPY ru/mkdocs.yml ./ru/mkdocs.yml RUN mkdocs build -f ./ru/mkdocs.yml WORKDIR /hello-algo/site EXPOSE 8000 CMD ["python", "-m", "http.server", "8000"] ================================================ FILE: LICENSE ================================================ Attribution-NonCommercial-ShareAlike 4.0 International ======================================================================= Creative Commons Corporation ("Creative Commons") is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an "as-is" basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. Using Creative Commons Public Licenses Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC- licensed material, or material used under an exception or limitation to copyright. More considerations for licensors: wiki.creativecommons.org/Considerations_for_licensors Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor's permission is not necessary for any reason--for example, because of any applicable exception or limitation to copyright--then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public: wiki.creativecommons.org/Considerations_for_licensees ======================================================================= Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 -- Definitions. a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. c. BY-NC-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License. d. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. e. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. f. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. g. License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution, NonCommercial, and ShareAlike. h. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. i. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. j. Licensor means the individual(s) or entity(ies) granting rights under this Public License. k. NonCommercial means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. l. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. m. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. n. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 -- Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: a. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and b. produce, reproduce, and Share Adapted Material for NonCommercial purposes only. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section 6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a) (4) never produces Adapted Material. 5. Downstream recipients. a. Offer from the Licensor -- Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. b. Additional offer from the Licensor -- Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter's License You apply. c. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. Section 3 -- License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Material (including in modified form), You must: a. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; b. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and c. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. b. ShareAlike. In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. 1. The Adapter's License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-NC-SA Compatible License. 2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. 3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. Section 4 -- Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. Section 5 -- Disclaimer of Warranties and Limitation of Liability. a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. Section 6 -- Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. Section 7 -- Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. Section 8 -- Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. ======================================================================= Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” The text of the Creative Commons public licenses is dedicated to the public domain under the CC0 Public Domain Dedication. Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark "Creative Commons" or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org. ================================================ FILE: README.md ================================================

hello-algo-typing-svg
动画图解、一键运行的数据结构与算法教程

简体中文 | 繁體中文English日本語Русский

## 关于本书 本项目旨在打造一本开源免费、新手友好的数据结构与算法入门教程。 - 全书采用动画图解,内容清晰易懂、学习曲线平滑,引导初学者探索数据结构与算法的知识地图。 - 源代码可一键运行,帮助读者在练习中提升编程技能,了解算法工作原理和数据结构底层实现。 - 提倡读者互助学习,欢迎大家在评论区提出问题与分享见解,在交流讨论中共同进步。 若本书对您有所帮助,请在页面右上角点个 Star :star: 支持一下,谢谢! ## 推荐语 > “一本通俗易懂的数据结构与算法入门书,引导读者手脑并用地学习,强烈推荐算法初学者阅读。” > > **—— 邓俊辉,清华大学计算机系教授** > “如果我当年学数据结构与算法的时候有《Hello 算法》,学起来应该会简单 10 倍!” > > **—— 李沐,亚马逊资深首席科学家** ## 鸣谢

Warp-Github-LG-02

[Warp is built for coding with multiple AI agents.](https://go.warp.dev/hello-algo) 强烈推荐 Warp 终端,高颜值 + 好用的 AI,体验非常棒! ## 贡献 本开源书仍在持续更新之中,欢迎您参与本项目,一同为读者提供更优质的学习内容。 - [内容修正](https://www.hello-algo.com/chapter_appendix/contribution/):请您协助修正或在评论区指出语法错误、内容缺失、文字歧义、无效链接或代码 bug 等问题。 - [代码转译](https://github.com/krahets/hello-algo/issues/15):期待您贡献各种语言代码,已支持 Python、Java、C++、Go、JavaScript 等 12 门编程语言。 - [中译英](https://github.com/krahets/hello-algo/issues/914):诚邀您加入我们的翻译小组,成员主要来自计算机相关专业、英语专业和英文母语者。 欢迎您提出宝贵意见和建议,如有任何问题请提交 Issues 或微信联系 `krahets-jyd` 。 感谢本开源书的每一位撰稿人,是他们的无私奉献让这本书变得更好,他们是:

## License The texts, code, images, photos, and videos in this repository are licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). ================================================ FILE: codes/Dockerfile ================================================ FROM ubuntu:latest # Use Ubuntu image from Aliyun RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \ sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \ sed -i 's/ports.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list RUN apt-get update && apt-get install -y wget # Install languages environment ARG LANGS RUN for LANG in $LANGS; do \ case $LANG in \ python) \ apt-get install -y python3.10 && \ update-alternatives --install /usr/bin/python python /usr/bin/python3.10 1 ;; \ cpp) \ apt-get install -y g++ gdb ;; \ java) \ apt-get install -y openjdk-17-jdk ;; \ csharp) \ wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \ dpkg -i packages-microsoft-prod.deb && \ apt-get update && \ apt-get install -y dotnet-sdk-8.0 ;; \ # More languages... *) \ echo "Warning: No installation workflow for $LANG" ;; \ esac \ done WORKDIR /codes COPY ./ ./ CMD ["/bin/bash"] ================================================ FILE: codes/c/.gitignore ================================================ # Ignore all * # Unignore all with extensions !*.* # Unignore all dirs !*/ *.dSYM/ build/ ================================================ FILE: codes/c/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(hello_algo C) set(CMAKE_C_STANDARD 11) include_directories(./include) add_subdirectory(chapter_computational_complexity) add_subdirectory(chapter_array_and_linkedlist) add_subdirectory(chapter_stack_and_queue) add_subdirectory(chapter_hashing) add_subdirectory(chapter_tree) add_subdirectory(chapter_heap) add_subdirectory(chapter_graph) add_subdirectory(chapter_searching) add_subdirectory(chapter_sorting) add_subdirectory(chapter_divide_and_conquer) add_subdirectory(chapter_backtracking) add_subdirectory(chapter_dynamic_programming) add_subdirectory(chapter_greedy) ================================================ FILE: codes/c/chapter_array_and_linkedlist/CMakeLists.txt ================================================ add_executable(array array.c) add_executable(linked_list linked_list.c) add_executable(my_list my_list.c) ================================================ FILE: codes/c/chapter_array_and_linkedlist/array.c ================================================ /** * File: array.c * Created Time: 2022-12-20 * Author: MolDuM (moldum@163.com) */ #include "../utils/common.h" /* 随机访问元素 */ int randomAccess(int *nums, int size) { // 在区间 [0, size) 中随机抽取一个数字 int randomIndex = rand() % size; // 获取并返回随机元素 int randomNum = nums[randomIndex]; return randomNum; } /* 扩展数组长度 */ int *extend(int *nums, int size, int enlarge) { // 初始化一个扩展长度后的数组 int *res = (int *)malloc(sizeof(int) * (size + enlarge)); // 将原数组中的所有元素复制到新数组 for (int i = 0; i < size; i++) { res[i] = nums[i]; } // 初始化扩展后的空间 for (int i = size; i < size + enlarge; i++) { res[i] = 0; } // 返回扩展后的新数组 return res; } /* 在数组的索引 index 处插入元素 num */ void insert(int *nums, int size, int num, int index) { // 把索引 index 以及之后的所有元素向后移动一位 for (int i = size - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 将 num 赋给 index 处的元素 nums[index] = num; } /* 删除索引 index 处的元素 */ // 注意:stdio.h 占用了 remove 关键词 void removeItem(int *nums, int size, int index) { // 把索引 index 之后的所有元素向前移动一位 for (int i = index; i < size - 1; i++) { nums[i] = nums[i + 1]; } } /* 遍历数组 */ void traverse(int *nums, int size) { int count = 0; // 通过索引遍历数组 for (int i = 0; i < size; i++) { count += nums[i]; } } /* 在数组中查找指定元素 */ int find(int *nums, int size, int target) { for (int i = 0; i < size; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ int main() { /* 初始化数组 */ int size = 5; int arr[5]; printf("数组 arr = "); printArray(arr, size); int nums[] = {1, 3, 2, 5, 4}; printf("数组 nums = "); printArray(nums, size); /* 随机访问 */ int randomNum = randomAccess(nums, size); printf("在 nums 中获取随机元素 %d", randomNum); /* 长度扩展 */ int enlarge = 3; int *res = extend(nums, size, enlarge); size += enlarge; printf("将数组长度扩展至 8 ,得到 nums = "); printArray(res, size); /* 插入元素 */ insert(res, size, 6, 3); printf("在索引 3 处插入数字 6 ,得到 nums = "); printArray(res, size); /* 删除元素 */ removeItem(res, size, 2); printf("删除索引 2 处的元素,得到 nums = "); printArray(res, size); /* 遍历数组 */ traverse(res, size); /* 查找元素 */ int index = find(res, size, 3); printf("在 res 中查找元素 3 ,得到索引 = %d\n", index); /* 释放内存 */ free(res); return 0; } ================================================ FILE: codes/c/chapter_array_and_linkedlist/linked_list.c ================================================ /** * File: linked_list.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* 在链表的节点 n0 之后插入节点 P */ void insert(ListNode *n0, ListNode *P) { ListNode *n1 = n0->next; P->next = n1; n0->next = P; } /* 删除链表的节点 n0 之后的首个节点 */ // 注意:stdio.h 占用了 remove 关键词 void removeItem(ListNode *n0) { if (!n0->next) return; // n0 -> P -> n1 ListNode *P = n0->next; ListNode *n1 = P->next; n0->next = n1; // 释放内存 free(P); } /* 访问链表中索引为 index 的节点 */ ListNode *access(ListNode *head, int index) { for (int i = 0; i < index; i++) { if (head == NULL) return NULL; head = head->next; } return head; } /* 在链表中查找值为 target 的首个节点 */ int find(ListNode *head, int target) { int index = 0; while (head) { if (head->val == target) return index; head = head->next; index++; } return -1; } /* Driver Code */ int main() { /* 初始化链表 */ // 初始化各个节点 ListNode *n0 = newListNode(1); ListNode *n1 = newListNode(3); ListNode *n2 = newListNode(2); ListNode *n3 = newListNode(5); ListNode *n4 = newListNode(4); // 构建节点之间的引用 n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; printf("初始化的链表为\r\n"); printLinkedList(n0); /* 插入节点 */ insert(n0, newListNode(0)); printf("插入节点后的链表为\r\n"); printLinkedList(n0); /* 删除节点 */ removeItem(n0); printf("删除节点后的链表为\r\n"); printLinkedList(n0); /* 访问节点 */ ListNode *node = access(n0, 3); printf("链表中索引 3 处的节点的值 = %d\r\n", node->val); /* 查找节点 */ int index = find(n0, 2); printf("链表中值为 2 的节点的索引 = %d\r\n", index); // 释放内存 freeMemoryLinkedList(n0); return 0; } ================================================ FILE: codes/c/chapter_array_and_linkedlist/my_list.c ================================================ /** * File: my_list.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* 列表类 */ typedef struct { int *arr; // 数组(存储列表元素) int capacity; // 列表容量 int size; // 列表大小 int extendRatio; // 列表每次扩容的倍数 } MyList; void extendCapacity(MyList *nums); /* 构造函数 */ MyList *newMyList() { MyList *nums = malloc(sizeof(MyList)); nums->capacity = 10; nums->arr = malloc(sizeof(int) * nums->capacity); nums->size = 0; nums->extendRatio = 2; return nums; } /* 析构函数 */ void delMyList(MyList *nums) { free(nums->arr); free(nums); } /* 获取列表长度 */ int size(MyList *nums) { return nums->size; } /* 获取列表容量 */ int capacity(MyList *nums) { return nums->capacity; } /* 访问元素 */ int get(MyList *nums, int index) { assert(index >= 0 && index < nums->size); return nums->arr[index]; } /* 更新元素 */ void set(MyList *nums, int index, int num) { assert(index >= 0 && index < nums->size); nums->arr[index] = num; } /* 在尾部添加元素 */ void add(MyList *nums, int num) { if (size(nums) == capacity(nums)) { extendCapacity(nums); // 扩容 } nums->arr[size(nums)] = num; nums->size++; } /* 在中间插入元素 */ void insert(MyList *nums, int index, int num) { assert(index >= 0 && index < size(nums)); // 元素数量超出容量时,触发扩容机制 if (size(nums) == capacity(nums)) { extendCapacity(nums); // 扩容 } for (int i = size(nums); i > index; --i) { nums->arr[i] = nums->arr[i - 1]; } nums->arr[index] = num; nums->size++; } /* 删除元素 */ // 注意:stdio.h 占用了 remove 关键词 int removeItem(MyList *nums, int index) { assert(index >= 0 && index < size(nums)); int num = nums->arr[index]; for (int i = index; i < size(nums) - 1; i++) { nums->arr[i] = nums->arr[i + 1]; } nums->size--; return num; } /* 列表扩容 */ void extendCapacity(MyList *nums) { // 先分配空间 int newCapacity = capacity(nums) * nums->extendRatio; int *extend = (int *)malloc(sizeof(int) * newCapacity); int *temp = nums->arr; // 拷贝旧数据到新数据 for (int i = 0; i < size(nums); i++) extend[i] = nums->arr[i]; // 释放旧数据 free(temp); // 更新新数据 nums->arr = extend; nums->capacity = newCapacity; } /* 将列表转换为 Array 用于打印 */ int *toArray(MyList *nums) { return nums->arr; } /* Driver Code */ int main() { /* 初始化列表 */ MyList *nums = newMyList(); /* 在尾部添加元素 */ add(nums, 1); add(nums, 3); add(nums, 2); add(nums, 5); add(nums, 4); printf("列表 nums = "); printArray(toArray(nums), size(nums)); printf("容量 = %d ,长度 = %d\n", capacity(nums), size(nums)); /* 在中间插入元素 */ insert(nums, 3, 6); printf("在索引 3 处插入数字 6 ,得到 nums = "); printArray(toArray(nums), size(nums)); /* 删除元素 */ removeItem(nums, 3); printf("删除索引 3 处的元素,得到 nums = "); printArray(toArray(nums), size(nums)); /* 访问元素 */ int num = get(nums, 1); printf("访问索引 1 处的元素,得到 num = %d\n", num); /* 更新元素 */ set(nums, 1, 0); printf("将索引 1 处的元素更新为 0 ,得到 nums = "); printArray(toArray(nums), size(nums)); /* 测试扩容机制 */ for (int i = 0; i < 10; i++) { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 add(nums, i); } printf("扩容后的列表 nums = "); printArray(toArray(nums), size(nums)); printf("容量 = %d ,长度 = %d\n", capacity(nums), size(nums)); /* 释放分配内存 */ delMyList(nums); return 0; } ================================================ FILE: codes/c/chapter_backtracking/CMakeLists.txt ================================================ add_executable(permutations_i permutations_i.c) add_executable(permutations_ii permutations_ii.c) add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.c) add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.c) add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.c) add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.c) add_executable(subset_sum_i_naive subset_sum_i_naive.c) add_executable(subset_sum_i subset_sum_i.c) add_executable(subset_sum_ii subset_sum_ii.c) add_executable(n_queens n_queens.c) ================================================ FILE: codes/c/chapter_backtracking/n_queens.c ================================================ /** * File : n_queens.c * Created Time: 2023-09-25 * Author : lucas (superrat6@gmail.com) */ #include "../utils/common.h" #define MAX_SIZE 100 /* 回溯算法:n 皇后 */ void backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE], bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) { // 当放置完所有行时,记录解 if (row == n) { res[*resSize] = (char **)malloc(sizeof(char *) * n); for (int i = 0; i < n; ++i) { res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1)); strcpy(res[*resSize][i], state[i]); } (*resSize)++; return; } // 遍历所有列 for (int col = 0; col < n; col++) { // 计算该格子对应的主对角线和次对角线 int diag1 = row - col + n - 1; int diag2 = row + col; // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 尝试:将皇后放置在该格子 state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // 放置下一行 backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2); // 回退:将该格子恢复为空位 state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* 求解 n 皇后 */ char ***nQueens(int n, int *returnSize) { char state[MAX_SIZE][MAX_SIZE]; // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { state[i][j] = '#'; } state[i][n] = '\0'; } bool cols[MAX_SIZE] = {false}; // 记录列是否有皇后 bool diags1[2 * MAX_SIZE - 1] = {false}; // 记录主对角线上是否有皇后 bool diags2[2 * MAX_SIZE - 1] = {false}; // 记录次对角线上是否有皇后 char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE); *returnSize = 0; backtrack(0, n, state, res, returnSize, cols, diags1, diags2); return res; } /* Driver Code */ int main() { int n = 4; int returnSize; char ***res = nQueens(n, &returnSize); printf("输入棋盘长宽为%d\n", n); printf("皇后放置方案共有 %d 种\n", returnSize); for (int i = 0; i < returnSize; ++i) { for (int j = 0; j < n; ++j) { printf("["); for (int k = 0; res[i][j][k] != '\0'; ++k) { printf("%c", res[i][j][k]); if (res[i][j][k + 1] != '\0') { printf(", "); } } printf("]\n"); } printf("---------------------\n"); } // 释放内存 for (int i = 0; i < returnSize; ++i) { for (int j = 0; j < n; ++j) { free(res[i][j]); } free(res[i]); } free(res); return 0; } ================================================ FILE: codes/c/chapter_backtracking/permutations_i.c ================================================ /** * File: permutations_i.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com), krahets (krahets@163.com) */ #include "../utils/common.h" // 假设最多有 1000 个排列 #define MAX_SIZE 1000 /* 回溯算法:全排列 I */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // 当状态长度等于元素数量时,记录解 if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // 遍历所有选择 for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // 剪枝:不允许重复选择元素 if (!selected[i]) { // 尝试:做出选择,更新状态 selected[i] = true; state[stateSize] = choice; // 进行下一轮选择 backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // 回退:撤销选择,恢复到之前的状态 selected[i] = false; } } } /* 全排列 I */ int **permutationsI(int *nums, int numsSize, int *returnSize) { int *state = (int *)malloc(numsSize * sizeof(int)); bool *selected = (bool *)malloc(numsSize * sizeof(bool)); for (int i = 0; i < numsSize; i++) { selected[i] = false; } int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); *returnSize = 0; backtrack(state, 0, nums, numsSize, selected, res, returnSize); free(state); free(selected); return res; } /* Driver Code */ int main() { int nums[] = {1, 2, 3}; int numsSize = sizeof(nums) / sizeof(nums[0]); int returnSize; int **res = permutationsI(nums, numsSize, &returnSize); printf("输入数组 nums = "); printArray(nums, numsSize); printf("\n所有排列 res = \n"); for (int i = 0; i < returnSize; i++) { printArray(res[i], numsSize); } // 释放内存 for (int i = 0; i < returnSize; i++) { free(res[i]); } free(res); return 0; } ================================================ FILE: codes/c/chapter_backtracking/permutations_ii.c ================================================ /** * File: permutations_ii.c * Created Time: 2023-10-17 * Author: krahets (krahets@163.com) */ #include "../utils/common.h" // 假设最多有 1000 个排列,元素最大为 1000 #define MAX_SIZE 1000 /* 回溯算法:全排列 II */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // 当状态长度等于元素数量时,记录解 if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // 遍历所有选择 bool duplicated[MAX_SIZE] = {false}; for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 if (!selected[i] && !duplicated[choice]) { // 尝试:做出选择,更新状态 duplicated[choice] = true; // 记录选择过的元素值 selected[i] = true; state[stateSize] = choice; // 进行下一轮选择 backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // 回退:撤销选择,恢复到之前的状态 selected[i] = false; } } } /* 全排列 II */ int **permutationsII(int *nums, int numsSize, int *returnSize) { int *state = (int *)malloc(numsSize * sizeof(int)); bool *selected = (bool *)malloc(numsSize * sizeof(bool)); for (int i = 0; i < numsSize; i++) { selected[i] = false; } int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); *returnSize = 0; backtrack(state, 0, nums, numsSize, selected, res, returnSize); free(state); free(selected); return res; } /* Driver Code */ int main() { int nums[] = {1, 1, 2}; int numsSize = sizeof(nums) / sizeof(nums[0]); int returnSize; int **res = permutationsII(nums, numsSize, &returnSize); printf("输入数组 nums = "); printArray(nums, numsSize); printf("\n所有排列 res = \n"); for (int i = 0; i < returnSize; i++) { printArray(res[i], numsSize); } // 释放内存 for (int i = 0; i < returnSize; i++) { free(res[i]); } free(res); return 0; } ================================================ FILE: codes/c/chapter_backtracking/preorder_traversal_i_compact.c ================================================ /** * File: preorder_traversal_i_compact.c * Created Time: 2023-05-10 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // 假设结果长度不超过 100 #define MAX_SIZE 100 TreeNode *res[MAX_SIZE]; int resSize = 0; /* 前序遍历:例题一 */ void preOrder(TreeNode *root) { if (root == NULL) { return; } if (root->val == 7) { // 记录解 res[resSize++] = root; } preOrder(root->left); preOrder(root->right); } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\n初始化二叉树\n"); printTree(root); // 前序遍历 preOrder(root); printf("\n输出所有值为 7 的节点\n"); int *vals = malloc(resSize * sizeof(int)); for (int i = 0; i < resSize; i++) { vals[i] = res[i]->val; } printArray(vals, resSize); // 释放内存 freeMemoryTree(root); free(vals); return 0; } ================================================ FILE: codes/c/chapter_backtracking/preorder_traversal_ii_compact.c ================================================ /** * File: preorder_traversal_ii_compact.c * Created Time: 2023-05-28 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // 假设路径和结果长度不超过 100 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* 前序遍历:例题二 */ void preOrder(TreeNode *root) { if (root == NULL) { return; } // 尝试 path[pathSize++] = root; if (root->val == 7) { // 记录解 for (int i = 0; i < pathSize; ++i) { res[resSize][i] = path[i]; } resSize++; } preOrder(root->left); preOrder(root->right); // 回退 pathSize--; } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\n初始化二叉树\n"); printTree(root); // 前序遍历 preOrder(root); printf("\n输出所有根节点到节点 7 的路径\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // 释放内存 freeMemoryTree(root); return 0; } ================================================ FILE: codes/c/chapter_backtracking/preorder_traversal_iii_compact.c ================================================ /** * File: preorder_traversal_iii_compact.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // 假设路径和结果长度不超过 100 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* 前序遍历:例题三 */ void preOrder(TreeNode *root) { // 剪枝 if (root == NULL || root->val == 3) { return; } // 尝试 path[pathSize++] = root; if (root->val == 7) { // 记录解 for (int i = 0; i < pathSize; i++) { res[resSize][i] = path[i]; } resSize++; } preOrder(root->left); preOrder(root->right); // 回退 pathSize--; } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\n初始化二叉树\n"); printTree(root); // 前序遍历 preOrder(root); printf("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // 释放内存 freeMemoryTree(root); return 0; } ================================================ FILE: codes/c/chapter_backtracking/preorder_traversal_iii_template.c ================================================ /** * File: preorder_traversal_iii_template.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // 假设路径和结果长度不超过 100 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* 判断当前状态是否为解 */ bool isSolution(void) { return pathSize > 0 && path[pathSize - 1]->val == 7; } /* 记录解 */ void recordSolution(void) { for (int i = 0; i < pathSize; i++) { res[resSize][i] = path[i]; } resSize++; } /* 判断在当前状态下,该选择是否合法 */ bool isValid(TreeNode *choice) { return choice != NULL && choice->val != 3; } /* 更新状态 */ void makeChoice(TreeNode *choice) { path[pathSize++] = choice; } /* 恢复状态 */ void undoChoice(void) { pathSize--; } /* 回溯算法:例题三 */ void backtrack(TreeNode *choices[2]) { // 检查是否为解 if (isSolution()) { // 记录解 recordSolution(); } // 遍历所有选择 for (int i = 0; i < 2; i++) { TreeNode *choice = choices[i]; // 剪枝:检查选择是否合法 if (isValid(choice)) { // 尝试:做出选择,更新状态 makeChoice(choice); // 进行下一轮选择 TreeNode *nextChoices[2] = {choice->left, choice->right}; backtrack(nextChoices); // 回退:撤销选择,恢复到之前的状态 undoChoice(); } } } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\n初始化二叉树\n"); printTree(root); // 回溯算法 TreeNode *choices[2] = {root, NULL}; backtrack(choices); printf("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // 释放内存 freeMemoryTree(root); return 0; } ================================================ FILE: codes/c/chapter_backtracking/subset_sum_i.c ================================================ /** * File: subset_sum_i.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // 状态(子集) int state[MAX_SIZE]; int stateSize = 0; // 结果列表(子集列表) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* 回溯算法:子集和 I */ void backtrack(int target, int *choices, int choicesSize, int start) { // 子集和等于 target 时,记录解 if (target == 0) { for (int i = 0; i < stateSize; ++i) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 for (int i = start; i < choicesSize; i++) { // 剪枝一:若子集和超过 target ,则直接结束循环 // 这是因为数组已排序,后边元素更大,子集和一定超过 target if (target - choices[i] < 0) { break; } // 尝试:做出选择,更新 target, start state[stateSize] = choices[i]; stateSize++; // 进行下一轮选择 backtrack(target - choices[i], choices, choicesSize, i); // 回退:撤销选择,恢复到之前的状态 stateSize--; } } /* 比较函数 */ int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); } /* 求解子集和 I */ void subsetSumI(int *nums, int numsSize, int target) { qsort(nums, numsSize, sizeof(int), cmp); // 对 nums 进行排序 int start = 0; // 遍历起始点 backtrack(target, nums, numsSize, start); } /* Driver Code */ int main() { int nums[] = {3, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumI(nums, numsSize, target); printf("输入数组 nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("所有和等于 %d 的子集 res = \n", target); for (int i = 0; i < resSize; ++i) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: codes/c/chapter_backtracking/subset_sum_i_naive.c ================================================ /** * File: subset_sum_i_naive.c * Created Time: 2023-07-28 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // 状态(子集) int state[MAX_SIZE]; int stateSize = 0; // 结果列表(子集列表) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* 回溯算法:子集和 I */ void backtrack(int target, int total, int *choices, int choicesSize) { // 子集和等于 target 时,记录解 if (total == target) { for (int i = 0; i < stateSize; i++) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // 遍历所有选择 for (int i = 0; i < choicesSize; i++) { // 剪枝:若子集和超过 target ,则跳过该选择 if (total + choices[i] > target) { continue; } // 尝试:做出选择,更新元素和 total state[stateSize++] = choices[i]; // 进行下一轮选择 backtrack(target, total + choices[i], choices, choicesSize); // 回退:撤销选择,恢复到之前的状态 stateSize--; } } /* 求解子集和 I(包含重复子集) */ void subsetSumINaive(int *nums, int numsSize, int target) { resSize = 0; // 初始化解的数量为0 backtrack(target, 0, nums, numsSize); } /* Driver Code */ int main() { int nums[] = {3, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumINaive(nums, numsSize, target); printf("输入数组 nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("所有和等于 %d 的子集 res = \n", target); for (int i = 0; i < resSize; i++) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: codes/c/chapter_backtracking/subset_sum_ii.c ================================================ /** * File: subset_sum_ii.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // 状态(子集) int state[MAX_SIZE]; int stateSize = 0; // 结果列表(子集列表) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* 回溯算法:子集和 II */ void backtrack(int target, int *choices, int choicesSize, int start) { // 子集和等于 target 时,记录解 if (target == 0) { for (int i = 0; i < stateSize; i++) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 // 剪枝三:从 start 开始遍历,避免重复选择同一元素 for (int i = start; i < choicesSize; i++) { // 剪枝一:若子集和超过 target ,则直接跳过 if (target - choices[i] < 0) { continue; } // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 if (i > start && choices[i] == choices[i - 1]) { continue; } // 尝试:做出选择,更新 target, start state[stateSize] = choices[i]; stateSize++; // 进行下一轮选择 backtrack(target - choices[i], choices, choicesSize, i + 1); // 回退:撤销选择,恢复到之前的状态 stateSize--; } } /* 比较函数 */ int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); } /* 求解子集和 II */ void subsetSumII(int *nums, int numsSize, int target) { // 对 nums 进行排序 qsort(nums, numsSize, sizeof(int), cmp); // 开始回溯 backtrack(target, nums, numsSize, 0); } /* Driver Code */ int main() { int nums[] = {4, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumII(nums, numsSize, target); printf("输入数组 nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("所有和等于 %d 的子集 res = \n", target); for (int i = 0; i < resSize; ++i) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: codes/c/chapter_computational_complexity/CMakeLists.txt ================================================ add_executable(iteration iteration.c) add_executable(recursion recursion.c) add_executable(time_complexity time_complexity.c) add_executable(worst_best_time_complexity worst_best_time_complexity.c) add_executable(space_complexity space_complexity.c) ================================================ FILE: codes/c/chapter_computational_complexity/iteration.c ================================================ /** * File: iteration.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com), MwumLi (mwumli@hotmail.com) */ #include "../utils/common.h" /* for 循环 */ int forLoop(int n) { int res = 0; // 循环求和 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } /* while 循环 */ int whileLoop(int n) { int res = 0; int i = 1; // 初始化条件变量 // 循环求和 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // 更新条件变量 } return res; } /* while 循环(两次更新) */ int whileLoopII(int n) { int res = 0; int i = 1; // 初始化条件变量 // 循环求和 1, 4, 10, ... while (i <= n) { res += i; // 更新条件变量 i++; i *= 2; } return res; } /* 双层 for 循环 */ char *nestedForLoop(int n) { // n * n 为对应点数量,"(i, j), " 对应字符串长最大为 6+10*2,加上最后一个空字符 \0 的额外空间 int size = n * n * 26 + 1; char *res = malloc(size * sizeof(char)); // 循环 i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // 循环 j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { char tmp[26]; snprintf(tmp, sizeof(tmp), "(%d, %d), ", i, j); strncat(res, tmp, size - strlen(res) - 1); } } return res; } /* Driver Code */ int main() { int n = 5; int res; res = forLoop(n); printf("\nfor 循环的求和结果 res = %d\n", res); res = whileLoop(n); printf("\nwhile 循环的求和结果 res = %d\n", res); res = whileLoopII(n); printf("\nwhile 循环(两次更新)求和结果 res = %d\n", res); char *resStr = nestedForLoop(n); printf("\n双层 for 循环的遍历结果 %s\r\n", resStr); free(resStr); return 0; } ================================================ FILE: codes/c/chapter_computational_complexity/recursion.c ================================================ /** * File: recursion.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 递归 */ int recur(int n) { // 终止条件 if (n == 1) return 1; // 递:递归调用 int res = recur(n - 1); // 归:返回结果 return n + res; } /* 使用迭代模拟递归 */ int forLoopRecur(int n) { int stack[1000]; // 借助一个大数组来模拟栈 int top = -1; // 栈顶索引 int res = 0; // 递:递归调用 for (int i = n; i > 0; i--) { // 通过“入栈操作”模拟“递” stack[1 + top++] = i; } // 归:返回结果 while (top >= 0) { // 通过“出栈操作”模拟“归” res += stack[top--]; } // res = 1+2+3+...+n return res; } /* 尾递归 */ int tailRecur(int n, int res) { // 终止条件 if (n == 0) return res; // 尾递归调用 return tailRecur(n - 1, res + n); } /* 斐波那契数列:递归 */ int fib(int n) { // 终止条件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // 递归调用 f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // 返回结果 f(n) return res; } /* Driver Code */ int main() { int n = 5; int res; res = recur(n); printf("\n递归函数的求和结果 res = %d\n", res); res = forLoopRecur(n); printf("\n使用迭代模拟递归求和结果 res = %d\n", res); res = tailRecur(n, 0); printf("\n尾递归函数的求和结果 res = %d\n", res); res = fib(n); printf("\n斐波那契数列的第 %d 项为 %d\n", n, res); return 0; } ================================================ FILE: codes/c/chapter_computational_complexity/space_complexity.c ================================================ /** * File: space_complexity.c * Created Time: 2023-04-15 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 函数 */ int func() { // 执行某些操作 return 0; } /* 常数阶 */ void constant(int n) { // 常量、变量、对象占用 O(1) 空间 const int a = 0; int b = 0; int nums[1000]; ListNode *node = newListNode(0); free(node); // 循环中的变量占用 O(1) 空间 for (int i = 0; i < n; i++) { int c = 0; } // 循环中的函数占用 O(1) 空间 for (int i = 0; i < n; i++) { func(); } } /* 哈希表 */ typedef struct { int key; int val; UT_hash_handle hh; // 基于 uthash.h 实现 } HashTable; /* 线性阶 */ void linear(int n) { // 长度为 n 的数组占用 O(n) 空间 int *nums = malloc(sizeof(int) * n); free(nums); // 长度为 n 的列表占用 O(n) 空间 ListNode **nodes = malloc(sizeof(ListNode *) * n); for (int i = 0; i < n; i++) { nodes[i] = newListNode(i); } // 内存释放 for (int i = 0; i < n; i++) { free(nodes[i]); } free(nodes); // 长度为 n 的哈希表占用 O(n) 空间 HashTable *h = NULL; for (int i = 0; i < n; i++) { HashTable *tmp = malloc(sizeof(HashTable)); tmp->key = i; tmp->val = i; HASH_ADD_INT(h, key, tmp); } // 内存释放 HashTable *curr, *tmp; HASH_ITER(hh, h, curr, tmp) { HASH_DEL(h, curr); free(curr); } } /* 线性阶(递归实现) */ void linearRecur(int n) { printf("递归 n = %d\r\n", n); if (n == 1) return; linearRecur(n - 1); } /* 平方阶 */ void quadratic(int n) { // 二维列表占用 O(n^2) 空间 int **numMatrix = malloc(sizeof(int *) * n); for (int i = 0; i < n; i++) { int *tmp = malloc(sizeof(int) * n); for (int j = 0; j < n; j++) { tmp[j] = 0; } numMatrix[i] = tmp; } // 内存释放 for (int i = 0; i < n; i++) { free(numMatrix[i]); } free(numMatrix); } /* 平方阶(递归实现) */ int quadraticRecur(int n) { if (n <= 0) return 0; int *nums = malloc(sizeof(int) * n); printf("递归 n = %d 中的 nums 长度 = %d\r\n", n, n); int res = quadraticRecur(n - 1); free(nums); return res; } /* 指数阶(建立满二叉树) */ TreeNode *buildTree(int n) { if (n == 0) return NULL; TreeNode *root = newTreeNode(0); root->left = buildTree(n - 1); root->right = buildTree(n - 1); return root; } /* Driver Code */ int main() { int n = 5; // 常数阶 constant(n); // 线性阶 linear(n); linearRecur(n); // 平方阶 quadratic(n); quadraticRecur(n); // 指数阶 TreeNode *root = buildTree(n); printTree(root); // 释放内存 freeMemoryTree(root); return 0; } ================================================ FILE: codes/c/chapter_computational_complexity/time_complexity.c ================================================ /** * File: time_complexity.c * Created Time: 2023-01-03 * Author: codingonion (coderonion@gmail.com) */ #include "../utils/common.h" /* 常数阶 */ int constant(int n) { int count = 0; int size = 100000; int i = 0; for (int i = 0; i < size; i++) { count++; } return count; } /* 线性阶 */ int linear(int n) { int count = 0; for (int i = 0; i < n; i++) { count++; } return count; } /* 线性阶(遍历数组) */ int arrayTraversal(int *nums, int n) { int count = 0; // 循环次数与数组长度成正比 for (int i = 0; i < n; i++) { count++; } return count; } /* 平方阶 */ int quadratic(int n) { int count = 0; // 循环次数与数据大小 n 成平方关系 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* 平方阶(冒泡排序) */ int bubbleSort(int *nums, int n) { int count = 0; // 计数器 // 外循环:未排序区间为 [0, i] for (int i = n - 1; i > 0; i--) { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 元素交换包含 3 个单元操作 } } } return count; } /* 指数阶(循环实现) */ int exponential(int n) { int count = 0; int bas = 1; // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for (int i = 0; i < n; i++) { for (int j = 0; j < bas; j++) { count++; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指数阶(递归实现) */ int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 对数阶(循环实现) */ int logarithmic(int n) { int count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* 对数阶(递归实现) */ int logRecur(int n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* 线性对数阶 */ int linearLogRecur(int n) { if (n <= 1) return 1; int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (int i = 0; i < n; i++) { count++; } return count; } /* 阶乘阶(递归实现) */ int factorialRecur(int n) { if (n == 0) return 1; int count = 0; for (int i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ int main(int argc, char *argv[]) { // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 int n = 8; printf("输入数据大小 n = %d\n", n); int count = constant(n); printf("常数阶的操作数量 = %d\n", count); count = linear(n); printf("线性阶的操作数量 = %d\n", count); // 分配堆区内存(创建一维可变长数组:数组中元素数量为 n ,元素类型为 int ) int *nums = (int *)malloc(n * sizeof(int)); count = arrayTraversal(nums, n); printf("线性阶(遍历数组)的操作数量 = %d\n", count); count = quadratic(n); printf("平方阶的操作数量 = %d\n", count); for (int i = 0; i < n; i++) { nums[i] = n - i; // [n,n-1,...,2,1] } count = bubbleSort(nums, n); printf("平方阶(冒泡排序)的操作数量 = %d\n", count); count = exponential(n); printf("指数阶(循环实现)的操作数量 = %d\n", count); count = expRecur(n); printf("指数阶(递归实现)的操作数量 = %d\n", count); count = logarithmic(n); printf("对数阶(循环实现)的操作数量 = %d\n", count); count = logRecur(n); printf("对数阶(递归实现)的操作数量 = %d\n", count); count = linearLogRecur(n); printf("线性对数阶(递归实现)的操作数量 = %d\n", count); count = factorialRecur(n); printf("阶乘阶(递归实现)的操作数量 = %d\n", count); // 释放堆区内存 if (nums != NULL) { free(nums); nums = NULL; } getchar(); return 0; } ================================================ FILE: codes/c/chapter_computational_complexity/worst_best_time_complexity.c ================================================ /** * File: worst_best_time_complexity.c * Created Time: 2023-01-03 * Author: codingonion (coderonion@gmail.com) */ #include "../utils/common.h" /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ int *randomNumbers(int n) { // 分配堆区内存(创建一维可变长数组:数组中元素数量为 n ,元素类型为 int ) int *nums = (int *)malloc(n * sizeof(int)); // 生成数组 nums = { 1, 2, 3, ..., n } for (int i = 0; i < n; i++) { nums[i] = i + 1; } // 随机打乱数组元素 for (int i = n - 1; i > 0; i--) { int j = rand() % (i + 1); int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } return nums; } /* 查找数组 nums 中数字 1 所在索引 */ int findOne(int *nums, int n) { for (int i = 0; i < n; i++) { // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if (nums[i] == 1) return i; } return -1; } /* Driver Code */ int main(int argc, char *argv[]) { // 初始化随机数种子 srand((unsigned int)time(NULL)); for (int i = 0; i < 10; i++) { int n = 100; int *nums = randomNumbers(n); int index = findOne(nums, n); printf("\n数组 [ 1, 2, ..., n ] 被打乱后 = "); printArray(nums, n); printf("数字 1 的索引为 %d\n", index); // 释放堆区内存 if (nums != NULL) { free(nums); nums = NULL; } } return 0; } ================================================ FILE: codes/c/chapter_divide_and_conquer/CMakeLists.txt ================================================ add_executable(binary_search_recur binary_search_recur.c) add_executable(build_tree build_tree.c) add_executable(hanota hanota.c) ================================================ FILE: codes/c/chapter_divide_and_conquer/binary_search_recur.c ================================================ /** * File: binary_search_recur.c * Created Time: 2023-10-01 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 二分查找:问题 f(i, j) */ int dfs(int nums[], int target, int i, int j) { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1; } // 计算中点索引 m int m = (i + j) / 2; if (nums[m] < target) { // 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ int binarySearch(int nums[], int target, int numsSize) { int n = numsSize; // 求解问题 f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ int main() { int target = 6; int nums[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; int numsSize = sizeof(nums) / sizeof(nums[0]); // 二分查找(双闭区间) int index = binarySearch(nums, target, numsSize); printf("目标元素 6 的索引 = %d\n", index); return 0; } ================================================ FILE: codes/c/chapter_divide_and_conquer/build_tree.c ================================================ /** * File : build_tree.c * Created Time: 2023-10-16 * Author : lucas (superrat6@gmail.com) */ #include "../utils/common.h" // 假设所有元素都小于 1000 #define MAX_SIZE 1000 /* 构建二叉树:分治 */ TreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) { // 子树区间为空时终止 if (r - l < 0) return NULL; // 初始化根节点 TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); root->val = preorder[i]; root->left = NULL; root->right = NULL; // 查询 m ,从而划分左右子树 int m = inorderMap[preorder[i]]; // 子问题:构建左子树 root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size); // 子问题:构建右子树 root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size); // 返回根节点 return root; } /* 构建二叉树 */ TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) { // 初始化哈希表,存储 inorder 元素到索引的映射 int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE); for (int i = 0; i < inorderSize; i++) { inorderMap[inorder[i]] = i; } TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize); free(inorderMap); return root; } /* Driver Code */ int main() { int preorder[] = {3, 9, 2, 1, 7}; int inorder[] = {9, 3, 1, 2, 7}; int preorderSize = sizeof(preorder) / sizeof(preorder[0]); int inorderSize = sizeof(inorder) / sizeof(inorder[0]); printf("前序遍历 = "); printArray(preorder, preorderSize); printf("中序遍历 = "); printArray(inorder, inorderSize); TreeNode *root = buildTree(preorder, preorderSize, inorder, inorderSize); printf("构建的二叉树为:\n"); printTree(root); freeMemoryTree(root); return 0; } ================================================ FILE: codes/c/chapter_divide_and_conquer/hanota.c ================================================ /** * File: hanota.c * Created Time: 2023-10-01 * Author: Zuoxun (845242523@qq.com), lucas(superrat6@gmail.com) */ #include "../utils/common.h" // 假设最多有 1000 个排列 #define MAX_SIZE 1000 /* 移动一个圆盘 */ void move(int *src, int *srcSize, int *tar, int *tarSize) { // 从 src 顶部拿出一个圆盘 int pan = src[*srcSize - 1]; src[*srcSize - 1] = 0; (*srcSize)--; // 将圆盘放入 tar 顶部 tar[*tarSize] = pan; (*tarSize)++; } /* 求解汉诺塔问题 f(i) */ void dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) { // 若 src 只剩下一个圆盘,则直接将其移到 tar if (i == 1) { move(src, srcSize, tar, tarSize); return; } // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize); // 子问题 f(1) :将 src 剩余一个圆盘移到 tar move(src, srcSize, tar, tarSize); // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize); } /* 求解汉诺塔问题 */ void solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) { // 将 A 顶部 n 个圆盘借助 B 移到 C dfs(*ASize, A, ASize, B, BSize, C, CSize); } /* Driver Code */ int main() { // 列表尾部是柱子顶部 int a[] = {5, 4, 3, 2, 1}; int b[MAX_SIZE] = {0}; int c[MAX_SIZE] = {0}; int ASize = sizeof(a) / sizeof(a[0]); int BSize = 0; int CSize = 0; printf("\n初始状态下:"); printf("\nA = "); printArray(a, ASize); printf("B = "); printArray(b, BSize); printf("C = "); printArray(c, CSize); solveHanota(a, &ASize, b, &BSize, c, &CSize); printf("\n圆盘移动完成后:"); printf("A = "); printArray(a, ASize); printf("B = "); printArray(b, BSize); printf("C = "); printArray(c, CSize); return 0; } ================================================ FILE: codes/c/chapter_dynamic_programming/CMakeLists.txt ================================================ add_executable(climbing_stairs_constraint_dp climbing_stairs_constraint_dp.c) add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.c) add_executable(min_path_sum min_path_sum.c) add_executable(knapsack knapsack.c) add_executable(unbounded_knapsack unbounded_knapsack.c) add_executable(coin_change coin_change.c) add_executable(coin_change_ii coin_change_ii.c) add_executable(edit_distance edit_distance.c) ================================================ FILE: codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c ================================================ /** * File: climbing_stairs_backtrack.c * Created Time: 2023-09-22 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* 回溯 */ void backtrack(int *choices, int state, int n, int *res, int len) { // 当爬到第 n 阶时,方案数量加 1 if (state == n) res[0]++; // 遍历所有选择 for (int i = 0; i < len; i++) { int choice = choices[i]; // 剪枝:不允许越过第 n 阶 if (state + choice > n) continue; // 尝试:做出选择,更新状态 backtrack(choices, state + choice, n, res, len); // 回退 } } /* 爬楼梯:回溯 */ int climbingStairsBacktrack(int n) { int choices[2] = {1, 2}; // 可选择向上爬 1 阶或 2 阶 int state = 0; // 从第 0 阶开始爬 int *res = (int *)malloc(sizeof(int)); *res = 0; // 使用 res[0] 记录方案数量 int len = sizeof(choices) / sizeof(int); backtrack(choices, state, n, res, len); int result = *res; free(res); return result; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsBacktrack(n); printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res); return 0; } ================================================ FILE: codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c ================================================ /** * File: climbing_stairs_constraint_dp.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 带约束爬楼梯:动态规划 */ int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // 初始化 dp 表,用于存储子问题的解 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(3, sizeof(int)); } // 初始状态:预设最小子问题的解 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 状态转移:从较小子问题逐步求解较大子问题 for (int i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } int res = dp[n][1] + dp[n][2]; // 释放内存 for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsConstraintDP(n); printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res); return 0; } ================================================ FILE: codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c ================================================ /** * File: climbing_stairs_dfs.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* 搜索 */ int dfs(int i) { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1) + dfs(i - 2); return count; } /* 爬楼梯:搜索 */ int climbingStairsDFS(int n) { return dfs(n); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFS(n); printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res); return 0; } ================================================ FILE: codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c ================================================ /** * File: climbing_stairs_dfs_mem.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* 记忆化搜索 */ int dfs(int i, int *mem) { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i; // 若存在记录 dp[i] ,则直接返回之 if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1, mem) + dfs(i - 2, mem); // 记录 dp[i] mem[i] = count; return count; } /* 爬楼梯:记忆化搜索 */ int climbingStairsDFSMem(int n) { // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 int *mem = (int *)malloc((n + 1) * sizeof(int)); for (int i = 0; i <= n; i++) { mem[i] = -1; } int result = dfs(n, mem); free(mem); return result; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFSMem(n); printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res); return 0; } ================================================ FILE: codes/c/chapter_dynamic_programming/climbing_stairs_dp.c ================================================ /** * File: climbing_stairs_dp.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* 爬楼梯:动态规划 */ int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; // 初始化 dp 表,用于存储子问题的解 int *dp = (int *)malloc((n + 1) * sizeof(int)); // 初始状态:预设最小子问题的解 dp[1] = 1; dp[2] = 2; // 状态转移:从较小子问题逐步求解较大子问题 for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } int result = dp[n]; free(dp); return result; } /* 爬楼梯:空间优化后的动态规划 */ int climbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDP(n); printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res); res = climbingStairsDPComp(n); printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res); return 0; } ================================================ FILE: codes/c/chapter_dynamic_programming/coin_change.c ================================================ /** * File: coin_change.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 求最小值 */ int myMin(int a, int b) { return a < b ? a : b; } /* 零钱兑换:动态规划 */ int coinChangeDP(int coins[], int amt, int coinsSize) { int n = coinsSize; int MAX = amt + 1; // 初始化 dp 表 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(amt + 1, sizeof(int)); } // 状态转移:首行首列 for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状态转移:其余行和列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } int res = dp[n][amt] != MAX ? dp[n][amt] : -1; // 释放内存 for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* 零钱兑换:空间优化后的动态规划 */ int coinChangeDPComp(int coins[], int amt, int coinsSize) { int n = coinsSize; int MAX = amt + 1; // 初始化 dp 表 int *dp = malloc((amt + 1) * sizeof(int)); for (int j = 1; j <= amt; j++) { dp[j] = MAX; } dp[0] = 0; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1); } } } int res = dp[amt] != MAX ? dp[amt] : -1; // 释放内存 free(dp); return res; } /* Driver code */ int main() { int coins[] = {1, 2, 5}; int coinsSize = sizeof(coins) / sizeof(coins[0]); int amt = 4; // 动态规划 int res = coinChangeDP(coins, amt, coinsSize); printf("凑到目标金额所需的最少硬币数量为 %d\n", res); // 空间优化后的动态规划 res = coinChangeDPComp(coins, amt, coinsSize); printf("凑到目标金额所需的最少硬币数量为 %d\n", res); return 0; } ================================================ FILE: codes/c/chapter_dynamic_programming/coin_change_ii.c ================================================ /** * File: coin_change_ii.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 零钱兑换 II:动态规划 */ int coinChangeIIDP(int coins[], int amt, int coinsSize) { int n = coinsSize; // 初始化 dp 表 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(amt + 1, sizeof(int)); } // 初始化首列 for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } int res = dp[n][amt]; // 释放内存 for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* 零钱兑换 II:空间优化后的动态规划 */ int coinChangeIIDPComp(int coins[], int amt, int coinsSize) { int n = coinsSize; // 初始化 dp 表 int *dp = calloc(amt + 1, sizeof(int)); dp[0] = 1; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } int res = dp[amt]; // 释放内存 free(dp); return res; } /* Driver code */ int main() { int coins[] = {1, 2, 5}; int coinsSize = sizeof(coins) / sizeof(coins[0]); int amt = 5; // 动态规划 int res = coinChangeIIDP(coins, amt, coinsSize); printf("凑出目标金额的硬币组合数量为 %d\n", res); // 空间优化后的动态规划 res = coinChangeIIDPComp(coins, amt, coinsSize); printf("凑出目标金额的硬币组合数量为 %d\n", res); return 0; } ================================================ FILE: codes/c/chapter_dynamic_programming/edit_distance.c ================================================ /** * File: edit_distance.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 求最小值 */ int myMin(int a, int b) { return a < b ? a : b; } /* 编辑距离:暴力搜索 */ int editDistanceDFS(char *s, char *t, int i, int j) { // 若 s 和 t 都为空,则返回 0 if (i == 0 && j == 0) return 0; // 若 s 为空,则返回 t 长度 if (i == 0) return j; // 若 t 为空,则返回 s 长度 if (j == 0) return i; // 若两字符相等,则直接跳过此两字符 if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 int insert = editDistanceDFS(s, t, i, j - 1); int del = editDistanceDFS(s, t, i - 1, j); int replace = editDistanceDFS(s, t, i - 1, j - 1); // 返回最少编辑步数 return myMin(myMin(insert, del), replace) + 1; } /* 编辑距离:记忆化搜索 */ int editDistanceDFSMem(char *s, char *t, int memCols, int **mem, int i, int j) { // 若 s 和 t 都为空,则返回 0 if (i == 0 && j == 0) return 0; // 若 s 为空,则返回 t 长度 if (i == 0) return j; // 若 t 为空,则返回 s 长度 if (j == 0) return i; // 若已有记录,则直接返回之 if (mem[i][j] != -1) return mem[i][j]; // 若两字符相等,则直接跳过此两字符 if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 int insert = editDistanceDFSMem(s, t, memCols, mem, i, j - 1); int del = editDistanceDFSMem(s, t, memCols, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); // 记录并返回最少编辑步数 mem[i][j] = myMin(myMin(insert, del), replace) + 1; return mem[i][j]; } /* 编辑距离:动态规划 */ int editDistanceDP(char *s, char *t, int n, int m) { int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(m + 1, sizeof(int)); } // 状态转移:首行首列 for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // 状态转移:其余行和列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // 若两字符相等,则直接跳过此两字符 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } int res = dp[n][m]; // 释放内存 for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* 编辑距离:空间优化后的动态规划 */ int editDistanceDPComp(char *s, char *t, int n, int m) { int *dp = calloc(m + 1, sizeof(int)); // 状态转移:首行 for (int j = 1; j <= m; j++) { dp[j] = j; } // 状态转移:其余行 for (int i = 1; i <= n; i++) { // 状态转移:首列 int leftup = dp[0]; // 暂存 dp[i-1, j-1] dp[0] = i; // 状态转移:其余列 for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 若两字符相等,则直接跳过此两字符 dp[j] = leftup; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 更新为下一轮的 dp[i-1, j-1] } } int res = dp[m]; // 释放内存 free(dp); return res; } /* Driver Code */ int main() { char *s = "bag"; char *t = "pack"; int n = strlen(s), m = strlen(t); // 暴力搜索 int res = editDistanceDFS(s, t, n, m); printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res); // 记忆化搜索 int **mem = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { mem[i] = malloc((m + 1) * sizeof(int)); memset(mem[i], -1, (m + 1) * sizeof(int)); } res = editDistanceDFSMem(s, t, m + 1, mem, n, m); printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res); // 释放内存 for (int i = 0; i <= n; i++) { free(mem[i]); } free(mem); // 动态规划 res = editDistanceDP(s, t, n, m); printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res); // 空间优化后的动态规划 res = editDistanceDPComp(s, t, n, m); printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res); return 0; } ================================================ FILE: codes/c/chapter_dynamic_programming/knapsack.c ================================================ /** * File: knapsack.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 求最大值 */ int myMax(int a, int b) { return a > b ? a : b; } /* 0-1 背包:暴力搜索 */ int knapsackDFS(int wgt[], int val[], int i, int c) { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i == 0 || c == 0) { return 0; } // 若超过背包容量,则只能选择不放入背包 if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // 返回两种方案中价值更大的那一个 return myMax(no, yes); } /* 0-1 背包:记忆化搜索 */ int knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i == 0 || c == 0) { return 0; } // 若已有记录,则直接返回 if (mem[i][c] != -1) { return mem[i][c]; } // 若超过背包容量,则只能选择不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 记录并返回两种方案中价值更大的那一个 mem[i][c] = myMax(no, yes); return mem[i][c]; } /* 0-1 背包:动态规划 */ int knapsackDP(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // 初始化 dp 表 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(cap + 1, sizeof(int)); } // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[n][cap]; // 释放内存 for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* 0-1 背包:空间优化后的动态规划 */ int knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // 初始化 dp 表 int *dp = calloc(cap + 1, sizeof(int)); // 状态转移 for (int i = 1; i <= n; i++) { // 倒序遍历 for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // 不选和选物品 i 这两种方案的较大值 dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[cap]; // 释放内存 free(dp); return res; } /* Driver Code */ int main() { int wgt[] = {10, 20, 30, 40, 50}; int val[] = {50, 120, 150, 210, 240}; int cap = 50; int n = sizeof(wgt) / sizeof(wgt[0]); int wgtSize = n; // 暴力搜索 int res = knapsackDFS(wgt, val, n, cap); printf("不超过背包容量的最大物品价值为 %d\n", res); // 记忆化搜索 int **mem = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { mem[i] = malloc((cap + 1) * sizeof(int)); memset(mem[i], -1, (cap + 1) * sizeof(int)); } res = knapsackDFSMem(wgt, val, cap + 1, mem, n, cap); printf("不超过背包容量的最大物品价值为 %d\n", res); // 释放内存 for (int i = 0; i <= n; i++) { free(mem[i]); } free(mem); // 动态规划 res = knapsackDP(wgt, val, cap, wgtSize); printf("不超过背包容量的最大物品价值为 %d\n", res); // 空间优化后的动态规划 res = knapsackDPComp(wgt, val, cap, wgtSize); printf("不超过背包容量的最大物品价值为 %d\n", res); return 0; } ================================================ FILE: codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c ================================================ /** * File: min_cost_climbing_stairs_dp.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 求最小值 */ int myMin(int a, int b) { return a < b ? a : b; } /* 爬楼梯最小代价:动态规划 */ int minCostClimbingStairsDP(int cost[], int costSize) { int n = costSize - 1; if (n == 1 || n == 2) return cost[n]; // 初始化 dp 表,用于存储子问题的解 int *dp = calloc(n + 1, sizeof(int)); // 初始状态:预设最小子问题的解 dp[1] = cost[1]; dp[2] = cost[2]; // 状态转移:从较小子问题逐步求解较大子问题 for (int i = 3; i <= n; i++) { dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i]; } int res = dp[n]; // 释放内存 free(dp); return res; } /* 爬楼梯最小代价:空间优化后的动态规划 */ int minCostClimbingStairsDPComp(int cost[], int costSize) { int n = costSize - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = myMin(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ int main() { int cost[] = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; int costSize = sizeof(cost) / sizeof(cost[0]); printf("输入楼梯的代价列表为:"); printArray(cost, costSize); int res = minCostClimbingStairsDP(cost, costSize); printf("爬完楼梯的最低代价为 %d\n", res); res = minCostClimbingStairsDPComp(cost, costSize); printf("爬完楼梯的最低代价为 %d\n", res); return 0; } ================================================ FILE: codes/c/chapter_dynamic_programming/min_path_sum.c ================================================ /** * File: min_path_sum.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" // 假设矩阵最大行列数为 100 #define MAX_SIZE 100 /* 求最小值 */ int myMin(int a, int b) { return a < b ? a : b; } /* 最小路径和:暴力搜索 */ int minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) { // 若为左上角单元格,则终止搜索 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 || j < 0) { return INT_MAX; } // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); // 返回从左上角到 (i, j) 的最小路径代价 return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; } /* 最小路径和:记忆化搜索 */ int minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) { // 若为左上角单元格,则终止搜索 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 || j < 0) { return INT_MAX; } // 若已有记录,则直接返回 if (mem[i][j] != -1) { return mem[i][j]; } // 左边和上边单元格的最小路径代价 int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // 记录并返回左上角到 (i, j) 的最小路径代价 mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; return mem[i][j]; } /* 最小路径和:动态规划 */ int minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { // 初始化 dp 表 int **dp = malloc(n * sizeof(int *)); for (int i = 0; i < n; i++) { dp[i] = calloc(m, sizeof(int)); } dp[0][0] = grid[0][0]; // 状态转移:首行 for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 状态转移:首列 for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 状态转移:其余行和列 for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } int res = dp[n - 1][m - 1]; // 释放内存 for (int i = 0; i < n; i++) { free(dp[i]); } return res; } /* 最小路径和:空间优化后的动态规划 */ int minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { // 初始化 dp 表 int *dp = calloc(m, sizeof(int)); // 状态转移:首行 dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 状态转移:其余行 for (int i = 1; i < n; i++) { // 状态转移:首列 dp[0] = dp[0] + grid[i][0]; // 状态转移:其余列 for (int j = 1; j < m; j++) { dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j]; } } int res = dp[m - 1]; // 释放内存 free(dp); return res; } /* Driver Code */ int main() { int grid[MAX_SIZE][MAX_SIZE] = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; int n = 4, m = 4; // 矩阵容量为 MAX_SIZE * MAX_SIZE ,有效行列数为 n * m // 暴力搜索 int res = minPathSumDFS(grid, n - 1, m - 1); printf("从左上角到右下角的最小路径和为 %d\n", res); // 记忆化搜索 int mem[MAX_SIZE][MAX_SIZE]; memset(mem, -1, sizeof(mem)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); printf("从左上角到右下角的最小路径和为 %d\n", res); // 动态规划 res = minPathSumDP(grid, n, m); printf("从左上角到右下角的最小路径和为 %d\n", res); // 空间优化后的动态规划 res = minPathSumDPComp(grid, n, m); printf("从左上角到右下角的最小路径和为 %d\n", res); return 0; } ================================================ FILE: codes/c/chapter_dynamic_programming/unbounded_knapsack.c ================================================ /** * File: unbounded_knapsack.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 求最大值 */ int myMax(int a, int b) { return a > b ? a : b; } /* 完全背包:动态规划 */ int unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // 初始化 dp 表 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(cap + 1, sizeof(int)); } // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[n][cap]; // 释放内存 for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* 完全背包:空间优化后的动态规划 */ int unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // 初始化 dp 表 int *dp = calloc(cap + 1, sizeof(int)); // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[cap]; // 释放内存 free(dp); return res; } /* Driver code */ int main() { int wgt[] = {1, 2, 3}; int val[] = {5, 11, 15}; int wgtSize = sizeof(wgt) / sizeof(wgt[0]); int cap = 4; // 动态规划 int res = unboundedKnapsackDP(wgt, val, cap, wgtSize); printf("不超过背包容量的最大物品价值为 %d\n", res); // 空间优化后的动态规划 res = unboundedKnapsackDPComp(wgt, val, cap, wgtSize); printf("不超过背包容量的最大物品价值为 %d\n", res); return 0; } ================================================ FILE: codes/c/chapter_graph/CMakeLists.txt ================================================ add_executable(graph_adjacency_matrix graph_adjacency_matrix.c) add_executable(graph_adjacency_list_test graph_adjacency_list_test.c) add_executable(graph_bfs graph_bfs.c) add_executable(graph_dfs graph_dfs.c) ================================================ FILE: codes/c/chapter_graph/graph_adjacency_list.c ================================================ /** * File: graph_adjacency_list.c * Created Time: 2023-07-07 * Author: NI-SW (947743645@qq.com) */ #include "../utils/common.h" // 假设节点最大数量为 100 #define MAX_SIZE 100 /* 节点结构体 */ typedef struct AdjListNode { Vertex *vertex; // 顶点 struct AdjListNode *next; // 后继节点 } AdjListNode; /* 基于邻接表实现的无向图类 */ typedef struct { AdjListNode *heads[MAX_SIZE]; // 节点数组 int size; // 节点数量 } GraphAdjList; /* 构造函数 */ GraphAdjList *newGraphAdjList() { GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList)); if (!graph) { return NULL; } graph->size = 0; for (int i = 0; i < MAX_SIZE; i++) { graph->heads[i] = NULL; } return graph; } /* 析构函数 */ void delGraphAdjList(GraphAdjList *graph) { for (int i = 0; i < graph->size; i++) { AdjListNode *cur = graph->heads[i]; while (cur != NULL) { AdjListNode *next = cur->next; if (cur != graph->heads[i]) { free(cur); } cur = next; } free(graph->heads[i]->vertex); free(graph->heads[i]); } free(graph); } /* 查找顶点对应的节点 */ AdjListNode *findNode(GraphAdjList *graph, Vertex *vet) { for (int i = 0; i < graph->size; i++) { if (graph->heads[i]->vertex == vet) { return graph->heads[i]; } } return NULL; } /* 添加边辅助函数 */ void addEdgeHelper(AdjListNode *head, Vertex *vet) { AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode)); node->vertex = vet; // 头插法 node->next = head->next; head->next = node; } /* 添加边 */ void addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { AdjListNode *head1 = findNode(graph, vet1); AdjListNode *head2 = findNode(graph, vet2); assert(head1 != NULL && head2 != NULL && head1 != head2); // 添加边 vet1 - vet2 addEdgeHelper(head1, vet2); addEdgeHelper(head2, vet1); } /* 删除边辅助函数 */ void removeEdgeHelper(AdjListNode *head, Vertex *vet) { AdjListNode *pre = head; AdjListNode *cur = head->next; // 在链表中搜索 vet 对应节点 while (cur != NULL && cur->vertex != vet) { pre = cur; cur = cur->next; } if (cur == NULL) return; // 将 vet 对应节点从链表中删除 pre->next = cur->next; // 释放内存 free(cur); } /* 删除边 */ void removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { AdjListNode *head1 = findNode(graph, vet1); AdjListNode *head2 = findNode(graph, vet2); assert(head1 != NULL && head2 != NULL); // 删除边 vet1 - vet2 removeEdgeHelper(head1, head2->vertex); removeEdgeHelper(head2, head1->vertex); } /* 添加顶点 */ void addVertex(GraphAdjList *graph, Vertex *vet) { assert(graph != NULL && graph->size < MAX_SIZE); AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode)); head->vertex = vet; head->next = NULL; // 在邻接表中添加一个新链表 graph->heads[graph->size++] = head; } /* 删除顶点 */ void removeVertex(GraphAdjList *graph, Vertex *vet) { AdjListNode *node = findNode(graph, vet); assert(node != NULL); // 在邻接表中删除顶点 vet 对应的链表 AdjListNode *cur = node, *pre = NULL; while (cur) { pre = cur; cur = cur->next; free(pre); } // 遍历其他顶点的链表,删除所有包含 vet 的边 for (int i = 0; i < graph->size; i++) { cur = graph->heads[i]; pre = NULL; while (cur) { pre = cur; cur = cur->next; if (cur && cur->vertex == vet) { pre->next = cur->next; free(cur); break; } } } // 将该顶点之后的顶点向前移动,以填补空缺 int i; for (i = 0; i < graph->size; i++) { if (graph->heads[i] == node) break; } for (int j = i; j < graph->size - 1; j++) { graph->heads[j] = graph->heads[j + 1]; } graph->size--; free(vet); } /* 打印邻接表 */ void printGraph(const GraphAdjList *graph) { printf("邻接表 =\n"); for (int i = 0; i < graph->size; ++i) { AdjListNode *node = graph->heads[i]; printf("%d: [", node->vertex->val); node = node->next; while (node) { printf("%d, ", node->vertex->val); node = node->next; } printf("]\n"); } } ================================================ FILE: codes/c/chapter_graph/graph_adjacency_list_test.c ================================================ /** * File: graph_adjacency_list_test.c * Created Time: 2023-07-11 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" /* Driver Code */ int main() { int vals[] = {1, 3, 2, 5, 4}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // 添加所有顶点和边 for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\n初始化后,图为\n"); printGraph(graph); /* 添加边 */ // 顶点 1, 2 即 v[0], v[2] addEdge(graph, v[0], v[2]); printf("\n添加边 1-2 后,图为\n"); printGraph(graph); /* 删除边 */ // 顶点 1, 3 即 v[0], v[1] removeEdge(graph, v[0], v[1]); printf("\n删除边 1-3 后,图为\n"); printGraph(graph); /* 添加顶点 */ Vertex *v5 = newVertex(6); addVertex(graph, v5); printf("\n添加顶点 6 后,图为\n"); printGraph(graph); /* 删除顶点 */ // 顶点 3 即 v[1] removeVertex(graph, v[1]); printf("\n删除顶点 3 后,图为:\n"); printGraph(graph); // 释放内存 delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: codes/c/chapter_graph/graph_adjacency_matrix.c ================================================ /** * File: graph_adjacency_matrix.c * Created Time: 2023-07-06 * Author: NI-SW (947743645@qq.com) */ #include "../utils/common.h" // 假设顶点数量最大为 100 #define MAX_SIZE 100 /* 基于邻接矩阵实现的无向图结构体 */ typedef struct { int vertices[MAX_SIZE]; int adjMat[MAX_SIZE][MAX_SIZE]; int size; } GraphAdjMat; /* 构造函数 */ GraphAdjMat *newGraphAdjMat() { GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat)); graph->size = 0; for (int i = 0; i < MAX_SIZE; i++) { for (int j = 0; j < MAX_SIZE; j++) { graph->adjMat[i][j] = 0; } } return graph; } /* 析构函数 */ void delGraphAdjMat(GraphAdjMat *graph) { free(graph); } /* 添加顶点 */ void addVertex(GraphAdjMat *graph, int val) { if (graph->size == MAX_SIZE) { fprintf(stderr, "图的顶点数量已达最大值\n"); return; } // 添加第 n 个顶点,并将第 n 行和列置零 int n = graph->size; graph->vertices[n] = val; for (int i = 0; i <= n; i++) { graph->adjMat[n][i] = graph->adjMat[i][n] = 0; } graph->size++; } /* 删除顶点 */ void removeVertex(GraphAdjMat *graph, int index) { if (index < 0 || index >= graph->size) { fprintf(stderr, "顶点索引越界\n"); return; } // 在顶点列表中移除索引 index 的顶点 for (int i = index; i < graph->size - 1; i++) { graph->vertices[i] = graph->vertices[i + 1]; } // 在邻接矩阵中删除索引 index 的行 for (int i = index; i < graph->size - 1; i++) { for (int j = 0; j < graph->size; j++) { graph->adjMat[i][j] = graph->adjMat[i + 1][j]; } } // 在邻接矩阵中删除索引 index 的列 for (int i = 0; i < graph->size; i++) { for (int j = index; j < graph->size - 1; j++) { graph->adjMat[i][j] = graph->adjMat[i][j + 1]; } } graph->size--; } /* 添加边 */ // 参数 i, j 对应 vertices 元素索引 void addEdge(GraphAdjMat *graph, int i, int j) { if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { fprintf(stderr, "边索引越界或相等\n"); return; } graph->adjMat[i][j] = 1; graph->adjMat[j][i] = 1; } /* 删除边 */ // 参数 i, j 对应 vertices 元素索引 void removeEdge(GraphAdjMat *graph, int i, int j) { if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { fprintf(stderr, "边索引越界或相等\n"); return; } graph->adjMat[i][j] = 0; graph->adjMat[j][i] = 0; } /* 打印邻接矩阵 */ void printGraphAdjMat(GraphAdjMat *graph) { printf("顶点列表 = "); printArray(graph->vertices, graph->size); printf("邻接矩阵 =\n"); for (int i = 0; i < graph->size; i++) { printArray(graph->adjMat[i], graph->size); } } /* Driver Code */ int main() { // 初始化无向图 GraphAdjMat *graph = newGraphAdjMat(); int vertices[] = {1, 3, 2, 5, 4}; for (int i = 0; i < 5; i++) { addVertex(graph, vertices[i]); } int edges[][2] = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; for (int i = 0; i < 6; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\n初始化后,图为\n"); printGraphAdjMat(graph); /* 添加边 */ // 顶点 1, 2 的索引分别为 0, 2 addEdge(graph, 0, 2); printf("\n添加边 1-2 后,图为\n"); printGraphAdjMat(graph); /* 删除边 */ // 顶点 1, 3 的索引分别为 0, 1 removeEdge(graph, 0, 1); printf("\n删除边 1-3 后,图为\n"); printGraphAdjMat(graph); /* 添加顶点 */ addVertex(graph, 6); printf("\n添加顶点 6 后,图为\n"); printGraphAdjMat(graph); /* 删除顶点 */ // 顶点 3 的索引为 1 removeVertex(graph, 1); printf("\n删除顶点 3 后,图为\n"); printGraphAdjMat(graph); // 释放内存 delGraphAdjMat(graph); return 0; } ================================================ FILE: codes/c/chapter_graph/graph_bfs.c ================================================ /** * File: graph_bfs.c * Created Time: 2023-07-11 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" // 假设节点最大数量为 100 #define MAX_SIZE 100 /* 节点队列结构体 */ typedef struct { Vertex *vertices[MAX_SIZE]; int front, rear, size; } Queue; /* 构造函数 */ Queue *newQueue() { Queue *q = (Queue *)malloc(sizeof(Queue)); q->front = q->rear = q->size = 0; return q; } /* 判断队列是否为空 */ int isEmpty(Queue *q) { return q->size == 0; } /* 入队操作 */ void enqueue(Queue *q, Vertex *vet) { q->vertices[q->rear] = vet; q->rear = (q->rear + 1) % MAX_SIZE; q->size++; } /* 出队操作 */ Vertex *dequeue(Queue *q) { Vertex *vet = q->vertices[q->front]; q->front = (q->front + 1) % MAX_SIZE; q->size--; return vet; } /* 检查顶点是否已被访问 */ int isVisited(Vertex **visited, int size, Vertex *vet) { // 遍历查找节点,使用 O(n) 时间 for (int i = 0; i < size; i++) { if (visited[i] == vet) return 1; } return 0; } /* 广度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 void graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) { // 队列用于实现 BFS Queue *queue = newQueue(); enqueue(queue, startVet); visited[(*visitedSize)++] = startVet; // 以顶点 vet 为起点,循环直至访问完所有顶点 while (!isEmpty(queue)) { Vertex *vet = dequeue(queue); // 队首顶点出队 res[(*resSize)++] = vet; // 记录访问顶点 // 遍历该顶点的所有邻接顶点 AdjListNode *node = findNode(graph, vet); while (node != NULL) { // 跳过已被访问的顶点 if (!isVisited(visited, *visitedSize, node->vertex)) { enqueue(queue, node->vertex); // 只入队未访问的顶点 visited[(*visitedSize)++] = node->vertex; // 标记该顶点已被访问 } node = node->next; } } // 释放内存 free(queue); } /* Driver Code */ int main() { // 初始化无向图 int vals[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[1], v[4]}, {v[2], v[5]}, {v[3], v[4]}, {v[3], v[6]}, {v[4], v[5]}, {v[4], v[7]}, {v[5], v[8]}, {v[6], v[7]}, {v[7], v[8]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // 添加所有顶点和边 for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\n初始化后,图为\n"); printGraph(graph); // 广度优先遍历 // 顶点遍历序列 Vertex *res[MAX_SIZE]; int resSize = 0; // 用于记录已被访问过的顶点 Vertex *visited[MAX_SIZE]; int visitedSize = 0; graphBFS(graph, v[0], res, &resSize, visited, &visitedSize); printf("\n广度优先遍历(BFS)顶点序列为\n"); printArray(vetsToVals(res, resSize), resSize); // 释放内存 delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: codes/c/chapter_graph/graph_dfs.c ================================================ /** * File: graph_dfs.c * Created Time: 2023-07-13 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" // 假设节点最大数量为 100 #define MAX_SIZE 100 /* 检查顶点是否已被访问 */ int isVisited(Vertex **res, int size, Vertex *vet) { // 遍历查找节点,使用 O(n) 时间 for (int i = 0; i < size; i++) { if (res[i] == vet) { return 1; } } return 0; } /* 深度优先遍历辅助函数 */ void dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) { // 记录访问顶点 res[(*resSize)++] = vet; // 遍历该顶点的所有邻接顶点 AdjListNode *node = findNode(graph, vet); while (node != NULL) { // 跳过已被访问的顶点 if (!isVisited(res, *resSize, node->vertex)) { // 递归访问邻接顶点 dfs(graph, res, resSize, node->vertex); } node = node->next; } } /* 深度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 void graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) { dfs(graph, res, resSize, startVet); } /* Driver Code */ int main() { // 初始化无向图 int vals[] = {0, 1, 2, 3, 4, 5, 6}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // 添加所有顶点和边 for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\n初始化后,图为\n"); printGraph(graph); // 深度优先遍历 Vertex *res[MAX_SIZE]; int resSize = 0; graphDFS(graph, v[0], res, &resSize); printf("\n深度优先遍历(DFS)顶点序列为\n"); printArray(vetsToVals(res, resSize), resSize); // 释放内存 delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: codes/c/chapter_greedy/CMakeLists.txt ================================================ add_executable(coin_change_greedy coin_change_greedy.c) add_executable(fractional_knapsack fractional_knapsack.c) add_executable(max_capacity max_capacity.c) add_executable(max_product_cutting max_product_cutting.c) if (NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC") target_link_libraries(max_product_cutting m) endif() ================================================ FILE: codes/c/chapter_greedy/coin_change_greedy.c ================================================ /** * File: coin_change_greedy.c * Created Time: 2023-09-07 * Author: lwbaptx (lwbaptx@gmail.com) */ #include "../utils/common.h" /* 零钱兑换:贪心 */ int coinChangeGreedy(int *coins, int size, int amt) { // 假设 coins 列表有序 int i = size - 1; int count = 0; // 循环进行贪心选择,直到无剩余金额 while (amt > 0) { // 找到小于且最接近剩余金额的硬币 while (i > 0 && coins[i] > amt) { i--; } // 选择 coins[i] amt -= coins[i]; count++; } // 若未找到可行方案,则返回 -1 return amt == 0 ? count : -1; } /* Driver Code */ int main() { // 贪心:能够保证找到全局最优解 int coins1[6] = {1, 5, 10, 20, 50, 100}; int amt = 186; int res = coinChangeGreedy(coins1, 6, amt); printf("\ncoins = "); printArray(coins1, 6); printf("amt = %d\n", amt); printf("凑到 %d 所需的最少硬币数量为 %d\n", amt, res); // 贪心:无法保证找到全局最优解 int coins2[3] = {1, 20, 50}; amt = 60; res = coinChangeGreedy(coins2, 3, amt); printf("\ncoins = "); printArray(coins2, 3); printf("amt = %d\n", amt); printf("凑到 %d 所需的最少硬币数量为 %d\n", amt, res); printf("实际上需要的最少数量为 3 ,即 20 + 20 + 20\n"); // 贪心:无法保证找到全局最优解 int coins3[3] = {1, 49, 50}; amt = 98; res = coinChangeGreedy(coins3, 3, amt); printf("\ncoins = "); printArray(coins3, 3); printf("amt = %d\n", amt); printf("凑到 %d 所需的最少硬币数量为 %d\n", amt, res); printf("实际上需要的最少数量为 2 ,即 49 + 49\n"); return 0; } ================================================ FILE: codes/c/chapter_greedy/fractional_knapsack.c ================================================ /** * File: fractional_knapsack.c * Created Time: 2023-09-14 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* 物品 */ typedef struct { int w; // 物品重量 int v; // 物品价值 } Item; /* 按照价值密度排序 */ int sortByValueDensity(const void *a, const void *b) { Item *t1 = (Item *)a; Item *t2 = (Item *)b; return (float)(t1->v) / t1->w < (float)(t2->v) / t2->w; } /* 分数背包:贪心 */ float fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) { // 创建物品列表,包含两个属性:重量、价值 Item *items = malloc(sizeof(Item) * itemCount); for (int i = 0; i < itemCount; i++) { items[i] = (Item){.w = wgt[i], .v = val[i]}; } // 按照单位价值 item.v / item.w 从高到低进行排序 qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity); // 循环贪心选择 float res = 0.0; for (int i = 0; i < itemCount; i++) { if (items[i].w <= cap) { // 若剩余容量充足,则将当前物品整个装进背包 res += items[i].v; cap -= items[i].w; } else { // 若剩余容量不足,则将当前物品的一部分装进背包 res += (float)cap / items[i].w * items[i].v; cap = 0; break; } } free(items); return res; } /* Driver Code */ int main(void) { int wgt[] = {10, 20, 30, 40, 50}; int val[] = {50, 120, 150, 210, 240}; int capacity = 50; // 贪心算法 float res = fractionalKnapsack(wgt, val, sizeof(wgt) / sizeof(int), capacity); printf("不超过背包容量的最大物品价值为 %0.2f\n", res); return 0; } ================================================ FILE: codes/c/chapter_greedy/max_capacity.c ================================================ /** * File: max_capacity.c * Created Time: 2023-09-15 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* 求最小值 */ int myMin(int a, int b) { return a < b ? a : b; } /* 求最大值 */ int myMax(int a, int b) { return a > b ? a : b; } /* 最大容量:贪心 */ int maxCapacity(int ht[], int htLength) { // 初始化 i, j,使其分列数组两端 int i = 0; int j = htLength - 1; // 初始最大容量为 0 int res = 0; // 循环贪心选择,直至两板相遇 while (i < j) { // 更新最大容量 int capacity = myMin(ht[i], ht[j]) * (j - i); res = myMax(res, capacity); // 向内移动短板 if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ int main(void) { int ht[] = {3, 8, 5, 2, 7, 7, 3, 4}; // 贪心算法 int res = maxCapacity(ht, sizeof(ht) / sizeof(int)); printf("最大容量为 %d\n", res); return 0; } ================================================ FILE: codes/c/chapter_greedy/max_product_cutting.c ================================================ /** * File: max_product_cutting.c * Created Time: 2023-09-15 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* 最大切分乘积:贪心 */ int maxProductCutting(int n) { // 当 n <= 3 时,必须切分出一个 1 if (n <= 3) { return 1 * (n - 1); } // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 int a = n / 3; int b = n % 3; if (b == 1) { // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 return pow(3, a - 1) * 2 * 2; } if (b == 2) { // 当余数为 2 时,不做处理 return pow(3, a) * 2; } // 当余数为 0 时,不做处理 return pow(3, a); } /* Driver Code */ int main(void) { int n = 58; // 贪心算法 int res = maxProductCutting(n); printf("最大切分乘积为 %d\n", res); return 0; } ================================================ FILE: codes/c/chapter_hashing/CMakeLists.txt ================================================ add_executable(array_hash_map array_hash_map.c) add_executable(hash_map_chaining hash_map_chaining.c) add_executable(hash_map_open_addressing hash_map_open_addressing.c) add_executable(simple_hash simple_hash.c) ================================================ FILE: codes/c/chapter_hashing/array_hash_map.c ================================================ /** * File: array_hash_map.c * Created Time: 2023-03-18 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* 哈希表默认大小 */ #define MAX_SIZE 100 /* 键值对 int->string */ typedef struct { int key; char *val; } Pair; /* 键值对的集合 */ typedef struct { void *set; int len; } MapSet; /* 基于数组实现的哈希表 */ typedef struct { Pair *buckets[MAX_SIZE]; } ArrayHashMap; /* 构造函数 */ ArrayHashMap *newArrayHashMap() { ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap)); for (int i=0; i < MAX_SIZE; i++) { hmap->buckets[i] = NULL; } return hmap; } /* 析构函数 */ void delArrayHashMap(ArrayHashMap *hmap) { for (int i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { free(hmap->buckets[i]->val); free(hmap->buckets[i]); } } free(hmap); } /* 哈希函数 */ int hashFunc(int key) { int index = key % MAX_SIZE; return index; } /* 查询操作 */ const char *get(const ArrayHashMap *hmap, const int key) { int index = hashFunc(key); const Pair *Pair = hmap->buckets[index]; if (Pair == NULL) return NULL; return Pair->val; } /* 添加操作 */ void put(ArrayHashMap *hmap, const int key, const char *val) { Pair *Pair = malloc(sizeof(Pair)); Pair->key = key; Pair->val = malloc(strlen(val) + 1); strcpy(Pair->val, val); int index = hashFunc(key); hmap->buckets[index] = Pair; } /* 删除操作 */ void removeItem(ArrayHashMap *hmap, const int key) { int index = hashFunc(key); free(hmap->buckets[index]->val); free(hmap->buckets[index]); hmap->buckets[index] = NULL; } /* 获取所有键值对 */ void pairSet(ArrayHashMap *hmap, MapSet *set) { Pair *entries; int i = 0, index = 0; int total = 0; /* 统计有效键值对数量 */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } entries = malloc(sizeof(Pair) * total); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { entries[index].key = hmap->buckets[i]->key; entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1); strcpy(entries[index].val, hmap->buckets[i]->val); index++; } } set->set = entries; set->len = total; } /* 获取所有键 */ void keySet(ArrayHashMap *hmap, MapSet *set) { int *keys; int i = 0, index = 0; int total = 0; /* 统计有效键值对数量 */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } keys = malloc(total * sizeof(int)); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { keys[index] = hmap->buckets[i]->key; index++; } } set->set = keys; set->len = total; } /* 获取所有值 */ void valueSet(ArrayHashMap *hmap, MapSet *set) { char **vals; int i = 0, index = 0; int total = 0; /* 统计有效键值对数量 */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } vals = malloc(total * sizeof(char *)); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { vals[index] = hmap->buckets[i]->val; index++; } } set->set = vals; set->len = total; } /* 打印哈希表 */ void print(ArrayHashMap *hmap) { int i; MapSet set; pairSet(hmap, &set); Pair *entries = (Pair *)set.set; for (i = 0; i < set.len; i++) { printf("%d -> %s\n", entries[i].key, entries[i].val); } free(set.set); } /* Driver Code */ int main() { /* 初始化哈希表 */ ArrayHashMap *hmap = newArrayHashMap(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) put(hmap, 12836, "小哈"); put(hmap, 15937, "小啰"); put(hmap, 16750, "小算"); put(hmap, 13276, "小法"); put(hmap, 10583, "小鸭"); printf("\n添加完成后,哈希表为\nKey -> Value\n"); print(hmap); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value const char *name = get(hmap, 15937); printf("\n输入学号 15937 ,查询到姓名 %s\n", name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) removeItem(hmap, 10583); printf("\n删除 10583 后,哈希表为\nKey -> Value\n"); print(hmap); /* 遍历哈希表 */ int i; printf("\n遍历键值对 Key->Value\n"); print(hmap); MapSet set; keySet(hmap, &set); int *keys = (int *)set.set; printf("\n单独遍历键 Key\n"); for (i = 0; i < set.len; i++) { printf("%d\n", keys[i]); } free(set.set); valueSet(hmap, &set); char **vals = (char **)set.set; printf("\n单独遍历键 Value\n"); for (i = 0; i < set.len; i++) { printf("%s\n", vals[i]); } free(set.set); delArrayHashMap(hmap); return 0; } ================================================ FILE: codes/c/chapter_hashing/hash_map_chaining.c ================================================ /** * File: hash_map_chaining.c * Created Time: 2023-10-13 * Author: SenMing (1206575349@qq.com), krahets (krahets@163.com) */ #include #include #include // 假设 val 最大长度为 100 #define MAX_SIZE 100 /* 键值对 */ typedef struct { int key; char val[MAX_SIZE]; } Pair; /* 链表节点 */ typedef struct Node { Pair *pair; struct Node *next; } Node; /* 链式地址哈希表 */ typedef struct { int size; // 键值对数量 int capacity; // 哈希表容量 double loadThres; // 触发扩容的负载因子阈值 int extendRatio; // 扩容倍数 Node **buckets; // 桶数组 } HashMapChaining; /* 构造函数 */ HashMapChaining *newHashMapChaining() { HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining)); hashMap->size = 0; hashMap->capacity = 4; hashMap->loadThres = 2.0 / 3.0; hashMap->extendRatio = 2; hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); for (int i = 0; i < hashMap->capacity; i++) { hashMap->buckets[i] = NULL; } return hashMap; } /* 析构函数 */ void delHashMapChaining(HashMapChaining *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Node *cur = hashMap->buckets[i]; while (cur) { Node *tmp = cur; cur = cur->next; free(tmp->pair); free(tmp); } } free(hashMap->buckets); free(hashMap); } /* 哈希函数 */ int hashFunc(HashMapChaining *hashMap, int key) { return key % hashMap->capacity; } /* 负载因子 */ double loadFactor(HashMapChaining *hashMap) { return (double)hashMap->size / (double)hashMap->capacity; } /* 查询操作 */ char *get(HashMapChaining *hashMap, int key) { int index = hashFunc(hashMap, key); // 遍历桶,若找到 key ,则返回对应 val Node *cur = hashMap->buckets[index]; while (cur) { if (cur->pair->key == key) { return cur->pair->val; } cur = cur->next; } return ""; // 若未找到 key ,则返回空字符串 } /* 添加操作 */ void put(HashMapChaining *hashMap, int key, const char *val); /* 扩容哈希表 */ void extend(HashMapChaining *hashMap) { // 暂存原哈希表 int oldCapacity = hashMap->capacity; Node **oldBuckets = hashMap->buckets; // 初始化扩容后的新哈希表 hashMap->capacity *= hashMap->extendRatio; hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); for (int i = 0; i < hashMap->capacity; i++) { hashMap->buckets[i] = NULL; } hashMap->size = 0; // 将键值对从原哈希表搬运至新哈希表 for (int i = 0; i < oldCapacity; i++) { Node *cur = oldBuckets[i]; while (cur) { put(hashMap, cur->pair->key, cur->pair->val); Node *temp = cur; cur = cur->next; // 释放内存 free(temp->pair); free(temp); } } free(oldBuckets); } /* 添加操作 */ void put(HashMapChaining *hashMap, int key, const char *val) { // 当负载因子超过阈值时,执行扩容 if (loadFactor(hashMap) > hashMap->loadThres) { extend(hashMap); } int index = hashFunc(hashMap, key); // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 Node *cur = hashMap->buckets[index]; while (cur) { if (cur->pair->key == key) { strcpy(cur->pair->val, val); // 若遇到指定 key ,则更新对应 val 并返回 return; } cur = cur->next; } // 若无该 key ,则将键值对添加至链表头部 Pair *newPair = (Pair *)malloc(sizeof(Pair)); newPair->key = key; strcpy(newPair->val, val); Node *newNode = (Node *)malloc(sizeof(Node)); newNode->pair = newPair; newNode->next = hashMap->buckets[index]; hashMap->buckets[index] = newNode; hashMap->size++; } /* 删除操作 */ void removeItem(HashMapChaining *hashMap, int key) { int index = hashFunc(hashMap, key); Node *cur = hashMap->buckets[index]; Node *pre = NULL; while (cur) { if (cur->pair->key == key) { // 从中删除键值对 if (pre) { pre->next = cur->next; } else { hashMap->buckets[index] = cur->next; } // 释放内存 free(cur->pair); free(cur); hashMap->size--; return; } pre = cur; cur = cur->next; } } /* 打印哈希表 */ void print(HashMapChaining *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Node *cur = hashMap->buckets[i]; printf("["); while (cur) { printf("%d -> %s, ", cur->pair->key, cur->pair->val); cur = cur->next; } printf("]\n"); } } /* Driver Code */ int main() { /* 初始化哈希表 */ HashMapChaining *hashMap = newHashMapChaining(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) put(hashMap, 12836, "小哈"); put(hashMap, 15937, "小啰"); put(hashMap, 16750, "小算"); put(hashMap, 13276, "小法"); put(hashMap, 10583, "小鸭"); printf("\n添加完成后,哈希表为\nKey -> Value\n"); print(hashMap); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value char *name = get(hashMap, 13276); printf("\n输入学号 13276 ,查询到姓名 %s\n", name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) removeItem(hashMap, 12836); printf("\n删除学号 12836 后,哈希表为\nKey -> Value\n"); print(hashMap); /* 释放哈希表空间 */ delHashMapChaining(hashMap); return 0; } ================================================ FILE: codes/c/chapter_hashing/hash_map_open_addressing.c ================================================ /** * File: hash_map_open_addressing.c * Created Time: 2023-10-6 * Author: lclc6 (w1929522410@163.com) */ #include "../utils/common.h" /* 开放寻址哈希表 */ typedef struct { int key; char *val; } Pair; /* 开放寻址哈希表 */ typedef struct { int size; // 键值对数量 int capacity; // 哈希表容量 double loadThres; // 触发扩容的负载因子阈值 int extendRatio; // 扩容倍数 Pair **buckets; // 桶数组 Pair *TOMBSTONE; // 删除标记 } HashMapOpenAddressing; // 函数声明 void extend(HashMapOpenAddressing *hashMap); /* 构造函数 */ HashMapOpenAddressing *newHashMapOpenAddressing() { HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing)); hashMap->size = 0; hashMap->capacity = 4; hashMap->loadThres = 2.0 / 3.0; hashMap->extendRatio = 2; hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair)); hashMap->TOMBSTONE->key = -1; hashMap->TOMBSTONE->val = "-1"; return hashMap; } /* 析构函数 */ void delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Pair *pair = hashMap->buckets[i]; if (pair != NULL && pair != hashMap->TOMBSTONE) { free(pair->val); free(pair); } } free(hashMap->buckets); free(hashMap->TOMBSTONE); free(hashMap); } /* 哈希函数 */ int hashFunc(HashMapOpenAddressing *hashMap, int key) { return key % hashMap->capacity; } /* 负载因子 */ double loadFactor(HashMapOpenAddressing *hashMap) { return (double)hashMap->size / (double)hashMap->capacity; } /* 搜索 key 对应的桶索引 */ int findBucket(HashMapOpenAddressing *hashMap, int key) { int index = hashFunc(hashMap, key); int firstTombstone = -1; // 线性探测,当遇到空桶时跳出 while (hashMap->buckets[index] != NULL) { // 若遇到 key ,返回对应的桶索引 if (hashMap->buckets[index]->key == key) { // 若之前遇到了删除标记,则将键值对移动至该索引处 if (firstTombstone != -1) { hashMap->buckets[firstTombstone] = hashMap->buckets[index]; hashMap->buckets[index] = hashMap->TOMBSTONE; return firstTombstone; // 返回移动后的桶索引 } return index; // 返回桶索引 } // 记录遇到的首个删除标记 if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) { firstTombstone = index; } // 计算桶索引,越过尾部则返回头部 index = (index + 1) % hashMap->capacity; } // 若 key 不存在,则返回添加点的索引 return firstTombstone == -1 ? index : firstTombstone; } /* 查询操作 */ char *get(HashMapOpenAddressing *hashMap, int key) { // 搜索 key 对应的桶索引 int index = findBucket(hashMap, key); // 若找到键值对,则返回对应 val if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { return hashMap->buckets[index]->val; } // 若键值对不存在,则返回空字符串 return ""; } /* 添加操作 */ void put(HashMapOpenAddressing *hashMap, int key, char *val) { // 当负载因子超过阈值时,执行扩容 if (loadFactor(hashMap) > hashMap->loadThres) { extend(hashMap); } // 搜索 key 对应的桶索引 int index = findBucket(hashMap, key); // 若找到键值对,则覆盖 val 并返回 if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { free(hashMap->buckets[index]->val); hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1)); strcpy(hashMap->buckets[index]->val, val); hashMap->buckets[index]->val[strlen(val)] = '\0'; return; } // 若键值对不存在,则添加该键值对 Pair *pair = (Pair *)malloc(sizeof(Pair)); pair->key = key; pair->val = (char *)malloc(sizeof(strlen(val) + 1)); strcpy(pair->val, val); pair->val[strlen(val)] = '\0'; hashMap->buckets[index] = pair; hashMap->size++; } /* 删除操作 */ void removeItem(HashMapOpenAddressing *hashMap, int key) { // 搜索 key 对应的桶索引 int index = findBucket(hashMap, key); // 若找到键值对,则用删除标记覆盖它 if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { Pair *pair = hashMap->buckets[index]; free(pair->val); free(pair); hashMap->buckets[index] = hashMap->TOMBSTONE; hashMap->size--; } } /* 扩容哈希表 */ void extend(HashMapOpenAddressing *hashMap) { // 暂存原哈希表 Pair **bucketsTmp = hashMap->buckets; int oldCapacity = hashMap->capacity; // 初始化扩容后的新哈希表 hashMap->capacity *= hashMap->extendRatio; hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); hashMap->size = 0; // 将键值对从原哈希表搬运至新哈希表 for (int i = 0; i < oldCapacity; i++) { Pair *pair = bucketsTmp[i]; if (pair != NULL && pair != hashMap->TOMBSTONE) { put(hashMap, pair->key, pair->val); free(pair->val); free(pair); } } free(bucketsTmp); } /* 打印哈希表 */ void print(HashMapOpenAddressing *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Pair *pair = hashMap->buckets[i]; if (pair == NULL) { printf("NULL\n"); } else if (pair == hashMap->TOMBSTONE) { printf("TOMBSTONE\n"); } else { printf("%d -> %s\n", pair->key, pair->val); } } } /* Driver Code */ int main() { // 初始化哈希表 HashMapOpenAddressing *hashmap = newHashMapOpenAddressing(); // 添加操作 // 在哈希表中添加键值对 (key, val) put(hashmap, 12836, "小哈"); put(hashmap, 15937, "小啰"); put(hashmap, 16750, "小算"); put(hashmap, 13276, "小法"); put(hashmap, 10583, "小鸭"); printf("\n添加完成后,哈希表为\nKey -> Value\n"); print(hashmap); // 查询操作 // 向哈希表中输入键 key ,得到值 val char *name = get(hashmap, 13276); printf("\n输入学号 13276 ,查询到姓名 %s\n", name); // 删除操作 // 在哈希表中删除键值对 (key, val) removeItem(hashmap, 16750); printf("\n删除 16750 后,哈希表为\nKey -> Value\n"); print(hashmap); // 销毁哈希表 delHashMapOpenAddressing(hashmap); return 0; } ================================================ FILE: codes/c/chapter_hashing/simple_hash.c ================================================ /** * File: simple_hash.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 加法哈希 */ int addHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = (hash + (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* 乘法哈希 */ int mulHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = (31 * hash + (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* 异或哈希 */ int xorHash(char *key) { int hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash ^= (unsigned char)key[i]; } return hash & MODULUS; } /* 旋转哈希 */ int rotHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* Driver Code */ int main() { char *key = "Hello 算法"; int hash = addHash(key); printf("加法哈希值为 %d\n", hash); hash = mulHash(key); printf("乘法哈希值为 %d\n", hash); hash = xorHash(key); printf("异或哈希值为 %d\n", hash); hash = rotHash(key); printf("旋转哈希值为 %d\n", hash); return 0; } ================================================ FILE: codes/c/chapter_heap/CMakeLists.txt ================================================ add_executable(my_heap_test my_heap_test.c) add_executable(top_k top_k.c) ================================================ FILE: codes/c/chapter_heap/my_heap.c ================================================ /** * File: my_heap.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 5000 /* 大顶堆 */ typedef struct { // size 代表的是实际元素的个数 int size; // 使用预先分配内存的数组,避免扩容 int data[MAX_SIZE]; } MaxHeap; // 函数声明 void siftDown(MaxHeap *maxHeap, int i); void siftUp(MaxHeap *maxHeap, int i); int parent(MaxHeap *maxHeap, int i); /* 构造函数,根据切片建堆 */ MaxHeap *newMaxHeap(int nums[], int size) { // 所有元素入堆 MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap)); maxHeap->size = size; memcpy(maxHeap->data, nums, size * sizeof(int)); for (int i = parent(maxHeap, size - 1); i >= 0; i--) { // 堆化除叶节点以外的其他所有节点 siftDown(maxHeap, i); } return maxHeap; } /* 析构函数 */ void delMaxHeap(MaxHeap *maxHeap) { // 释放内存 free(maxHeap); } /* 获取左子节点的索引 */ int left(MaxHeap *maxHeap, int i) { return 2 * i + 1; } /* 获取右子节点的索引 */ int right(MaxHeap *maxHeap, int i) { return 2 * i + 2; } /* 获取父节点的索引 */ int parent(MaxHeap *maxHeap, int i) { return (i - 1) / 2; // 向下取整 } /* 交换元素 */ void swap(MaxHeap *maxHeap, int i, int j) { int temp = maxHeap->data[i]; maxHeap->data[i] = maxHeap->data[j]; maxHeap->data[j] = temp; } /* 获取堆大小 */ int size(MaxHeap *maxHeap) { return maxHeap->size; } /* 判断堆是否为空 */ int isEmpty(MaxHeap *maxHeap) { return maxHeap->size == 0; } /* 访问堆顶元素 */ int peek(MaxHeap *maxHeap) { return maxHeap->data[0]; } /* 元素入堆 */ void push(MaxHeap *maxHeap, int val) { // 默认情况下,不应该添加这么多节点 if (maxHeap->size == MAX_SIZE) { printf("heap is full!"); return; } // 添加节点 maxHeap->data[maxHeap->size] = val; maxHeap->size++; // 从底至顶堆化 siftUp(maxHeap, maxHeap->size - 1); } /* 元素出堆 */ int pop(MaxHeap *maxHeap) { // 判空处理 if (isEmpty(maxHeap)) { printf("heap is empty!"); return INT_MAX; } // 交换根节点与最右叶节点(交换首元素与尾元素) swap(maxHeap, 0, size(maxHeap) - 1); // 删除节点 int val = maxHeap->data[maxHeap->size - 1]; maxHeap->size--; // 从顶至底堆化 siftDown(maxHeap, 0); // 返回堆顶元素 return val; } /* 从节点 i 开始,从顶至底堆化 */ void siftDown(MaxHeap *maxHeap, int i) { while (true) { // 判断节点 i, l, r 中值最大的节点,记为 max int l = left(maxHeap, i); int r = right(maxHeap, i); int max = i; if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) { max = l; } if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) { max = r; } // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if (max == i) { break; } // 交换两节点 swap(maxHeap, i, max); // 循环向下堆化 i = max; } } /* 从节点 i 开始,从底至顶堆化 */ void siftUp(MaxHeap *maxHeap, int i) { while (true) { // 获取节点 i 的父节点 int p = parent(maxHeap, i); // 当“越过根节点”或“节点无须修复”时,结束堆化 if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) { break; } // 交换两节点 swap(maxHeap, i, p); // 循环向上堆化 i = p; } } ================================================ FILE: codes/c/chapter_heap/my_heap_test.c ================================================ /** * File: my_heap_test.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "my_heap.c" /* Driver Code */ int main() { /* 初始化堆 */ // 初始化大顶堆 int nums[] = {9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; MaxHeap *maxHeap = newMaxHeap(nums, sizeof(nums) / sizeof(int)); printf("输入数组并建堆后\n"); printHeap(maxHeap->data, maxHeap->size); /* 获取堆顶元素 */ printf("\n堆顶元素为 %d\n", peek(maxHeap)); /* 元素入堆 */ push(maxHeap, 7); printf("\n元素 7 入堆后\n"); printHeap(maxHeap->data, maxHeap->size); /* 堆顶元素出堆 */ int top = pop(maxHeap); printf("\n堆顶元素 %d 出堆后\n", top); printHeap(maxHeap->data, maxHeap->size); /* 获取堆大小 */ printf("\n堆元素数量为 %d\n", size(maxHeap)); /* 判断堆是否为空 */ printf("\n堆是否为空 %d\n", isEmpty(maxHeap)); // 释放内存 delMaxHeap(maxHeap); return 0; } ================================================ FILE: codes/c/chapter_heap/top_k.c ================================================ /** * File: top_k.c * Created Time: 2023-10-26 * Author: krahets (krahets163.com) */ #include "my_heap.c" /* 元素入堆 */ void pushMinHeap(MaxHeap *maxHeap, int val) { // 元素取反 push(maxHeap, -val); } /* 元素出堆 */ int popMinHeap(MaxHeap *maxHeap) { // 元素取反 return -pop(maxHeap); } /* 访问堆顶元素 */ int peekMinHeap(MaxHeap *maxHeap) { // 元素取反 return -peek(maxHeap); } /* 取出堆中元素 */ int *getMinHeap(MaxHeap *maxHeap) { // 将堆中所有元素取反并存入 res 数组 int *res = (int *)malloc(maxHeap->size * sizeof(int)); for (int i = 0; i < maxHeap->size; i++) { res[i] = -maxHeap->data[i]; } return res; } // 基于堆查找数组中最大的 k 个元素的函数 int *topKHeap(int *nums, int sizeNums, int k) { // 初始化小顶堆 // 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆 int *empty = (int *)malloc(0); MaxHeap *maxHeap = newMaxHeap(empty, 0); // 将数组的前 k 个元素入堆 for (int i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // 从第 k+1 个元素开始,保持堆的长度为 k for (int i = k; i < sizeNums; i++) { // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } int *res = getMinHeap(maxHeap); // 释放内存 delMaxHeap(maxHeap); return res; } /* Driver Code */ int main() { int nums[] = {1, 7, 6, 3, 2}; int k = 3; int sizeNums = sizeof(nums) / sizeof(nums[0]); int *res = topKHeap(nums, sizeNums, k); printf("最大的 %d 个元素为: ", k); printArray(res, k); free(res); return 0; } ================================================ FILE: codes/c/chapter_searching/CMakeLists.txt ================================================ add_executable(binary_search binary_search.c) add_executable(two_sum two_sum.c) add_executable(binary_search_edge binary_search_edge.c) add_executable(binary_search_insertion binary_search_insertion.c) ================================================ FILE: codes/c/chapter_searching/binary_search.c ================================================ /** * File: binary_search.c * Created Time: 2023-03-18 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* 二分查找(双闭区间) */ int binarySearch(int *nums, int len, int target) { // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 int i = 0, j = len - 1; // 循环,当搜索区间为空时跳出(当 i > j 时为空) while (i <= j) { int m = i + (j - i) / 2; // 计算中点索引 m if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 i = m + 1; else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 j = m - 1; else // 找到目标元素,返回其索引 return m; } // 未找到目标元素,返回 -1 return -1; } /* 二分查找(左闭右开区间) */ int binarySearchLCRO(int *nums, int len, int target) { // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 int i = 0, j = len; // 循环,当搜索区间为空时跳出(当 i = j 时为空) while (i < j) { int m = i + (j - i) / 2; // 计算中点索引 m if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j) 中 i = m + 1; else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中 j = m; else // 找到目标元素,返回其索引 return m; } // 未找到目标元素,返回 -1 return -1; } /* Driver Code */ int main() { int target = 6; int nums[10] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; /* 二分查找(双闭区间) */ int index = binarySearch(nums, 10, target); printf("目标元素 6 的索引 = %d\n", index); /* 二分查找(左闭右开区间) */ index = binarySearchLCRO(nums, 10, target); printf("目标元素 6 的索引 = %d\n", index); return 0; } ================================================ FILE: codes/c/chapter_searching/binary_search_edge.c ================================================ /** * File: binary_search_edge.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 二分查找插入点(存在重复元素) */ int binarySearchInsertion(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // 初始化双闭区间 [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // 计算中点索引 m if (nums[m] < target) { i = m + 1; // target 在区间 [m+1, j] 中 } else { j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 } } // 返回插入点 i return i; } /* 二分查找最左一个 target */ int binarySearchLeftEdge(int *nums, int numSize, int target) { // 等价于查找 target 的插入点 int i = binarySearchInsertion(nums, numSize, target); // 未找到 target ,返回 -1 if (i == numSize || nums[i] != target) { return -1; } // 找到 target ,返回索引 i return i; } /* 二分查找最右一个 target */ int binarySearchRightEdge(int *nums, int numSize, int target) { // 转化为查找最左一个 target + 1 int i = binarySearchInsertion(nums, numSize, target + 1); // j 指向最右一个 target ,i 指向首个大于 target 的元素 int j = i - 1; // 未找到 target ,返回 -1 if (j == -1 || nums[j] != target) { return -1; } // 找到 target ,返回索引 j return j; } /* Driver Code */ int main() { // 包含重复元素的数组 int nums[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; printf("\n数组 nums = "); printArray(nums, sizeof(nums) / sizeof(nums[0])); // 二分查找左边界和右边界 int targets[] = {6, 7}; for (int i = 0; i < sizeof(targets) / sizeof(targets[0]); i++) { int index = binarySearchLeftEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); printf("最左一个元素 %d 的索引为 %d\n", targets[i], index); index = binarySearchRightEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); printf("最右一个元素 %d 的索引为 %d\n", targets[i], index); } return 0; } ================================================ FILE: codes/c/chapter_searching/binary_search_insertion.c ================================================ /** * File: binary_search_insertion.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 二分查找插入点(无重复元素) */ int binarySearchInsertionSimple(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // 初始化双闭区间 [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // 计算中点索引 m if (nums[m] < target) { i = m + 1; // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在区间 [i, m-1] 中 } else { return m; // 找到 target ,返回插入点 m } } // 未找到 target ,返回插入点 i return i; } /* 二分查找插入点(存在重复元素) */ int binarySearchInsertion(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // 初始化双闭区间 [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // 计算中点索引 m if (nums[m] < target) { i = m + 1; // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在区间 [i, m-1] 中 } else { j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 } } // 返回插入点 i return i; } /* Driver Code */ int main() { // 无重复元素的数组 int nums1[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; printf("\n数组 nums = "); printArray(nums1, sizeof(nums1) / sizeof(nums1[0])); // 二分查找插入点 int targets1[] = {6, 9}; for (int i = 0; i < sizeof(targets1) / sizeof(targets1[0]); i++) { int index = binarySearchInsertionSimple(nums1, sizeof(nums1) / sizeof(nums1[0]), targets1[i]); printf("元素 %d 的插入点的索引为 %d\n", targets1[i], index); } // 包含重复元素的数组 int nums2[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; printf("\n数组 nums = "); printArray(nums2, sizeof(nums2) / sizeof(nums2[0])); // 二分查找插入点 int targets2[] = {2, 6, 20}; for (int i = 0; i < sizeof(targets2) / sizeof(int); i++) { int index = binarySearchInsertion(nums2, sizeof(nums2) / sizeof(nums2[0]), targets2[i]); printf("元素 %d 的插入点的索引为 %d\n", targets2[i], index); } return 0; } ================================================ FILE: codes/c/chapter_searching/two_sum.c ================================================ /** * File: two_sum.c * Created Time: 2023-01-19 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* 方法一:暴力枚举 */ int *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) { for (int i = 0; i < numsSize; ++i) { for (int j = i + 1; j < numsSize; ++j) { if (nums[i] + nums[j] == target) { int *res = malloc(sizeof(int) * 2); res[0] = i, res[1] = j; *returnSize = 2; return res; } } } *returnSize = 0; return NULL; } /* 哈希表 */ typedef struct { int key; int val; UT_hash_handle hh; // 基于 uthash.h 实现 } HashTable; /* 哈希表查询 */ HashTable *find(HashTable *h, int key) { HashTable *tmp; HASH_FIND_INT(h, &key, tmp); return tmp; } /* 哈希表元素插入 */ void insert(HashTable **h, int key, int val) { HashTable *t = find(*h, key); if (t == NULL) { HashTable *tmp = malloc(sizeof(HashTable)); tmp->key = key, tmp->val = val; HASH_ADD_INT(*h, key, tmp); } else { t->val = val; } } /* 方法二:辅助哈希表 */ int *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) { HashTable *hashtable = NULL; for (int i = 0; i < numsSize; i++) { HashTable *t = find(hashtable, target - nums[i]); if (t != NULL) { int *res = malloc(sizeof(int) * 2); res[0] = t->val, res[1] = i; *returnSize = 2; return res; } insert(&hashtable, nums[i], i); } *returnSize = 0; return NULL; } /* Driver Code */ int main() { // ======= Test Case ======= int nums[] = {2, 7, 11, 15}; int target = 13; // ====== Driver Code ====== int returnSize; int *res = twoSumBruteForce(nums, sizeof(nums) / sizeof(int), target, &returnSize); // 方法一 printf("方法一 res = "); printArray(res, returnSize); // 方法二 res = twoSumHashTable(nums, sizeof(nums) / sizeof(int), target, &returnSize); printf("方法二 res = "); printArray(res, returnSize); return 0; } ================================================ FILE: codes/c/chapter_sorting/CMakeLists.txt ================================================ add_executable(bubble_sort bubble_sort.c) add_executable(insertion_sort insertion_sort.c) add_executable(quick_sort quick_sort.c) add_executable(counting_sort counting_sort.c) add_executable(radix_sort radix_sort.c) add_executable(merge_sort merge_sort.c) add_executable(heap_sort heap_sort.c) add_executable(bucket_sort bucket_sort.c) add_executable(selection_sort selection_sort.c) ================================================ FILE: codes/c/chapter_sorting/bubble_sort.c ================================================ /** * File: bubble_sort.c * Created Time: 2022-12-26 * Author: Listening (https://github.com/L-Super) */ #include "../utils/common.h" /* 冒泡排序 */ void bubbleSort(int nums[], int size) { // 外循环:未排序区间为 [0, i] for (int i = size - 1; i > 0; i--) { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; } } } } /* 冒泡排序(标志优化)*/ void bubbleSortWithFlag(int nums[], int size) { // 外循环:未排序区间为 [0, i] for (int i = size - 1; i > 0; i--) { bool flag = false; // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; flag = true; } } if (!flag) break; } } /* Driver Code */ int main() { int nums[6] = {4, 1, 3, 1, 5, 2}; printf("冒泡排序后: "); bubbleSort(nums, 6); for (int i = 0; i < 6; i++) { printf("%d ", nums[i]); } int nums1[6] = {4, 1, 3, 1, 5, 2}; printf("\n优化版冒泡排序后: "); bubbleSortWithFlag(nums1, 6); for (int i = 0; i < 6; i++) { printf("%d ", nums1[i]); } printf("\n"); return 0; } ================================================ FILE: codes/c/chapter_sorting/bucket_sort.c ================================================ /** * File: bucket_sort.c * Created Time: 2023-05-30 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define SIZE 10 /* 用于 qsort 的比较函数 */ int compare(const void *a, const void *b) { float fa = *(const float *)a; float fb = *(const float *)b; return (fa > fb) - (fa < fb); } /* 桶排序 */ void bucketSort(float nums[], int n) { int k = n / 2; // 初始化 k = n/2 个桶 int *sizes = malloc(k * sizeof(int)); // 记录每个桶的大小 float **buckets = malloc(k * sizeof(float *)); // 动态数组的数组(桶) // 为每个桶预分配足够的空间 for (int i = 0; i < k; ++i) { buckets[i] = (float *)malloc(n * sizeof(float)); sizes[i] = 0; } // 1. 将数组元素分配到各个桶中 for (int i = 0; i < n; ++i) { int idx = (int)(nums[i] * k); buckets[idx][sizes[idx]++] = nums[i]; } // 2. 对各个桶执行排序 for (int i = 0; i < k; ++i) { qsort(buckets[i], sizes[i], sizeof(float), compare); } // 3. 合并排序后的桶 int idx = 0; for (int i = 0; i < k; ++i) { for (int j = 0; j < sizes[i]; ++j) { nums[idx++] = buckets[i][j]; } // 释放内存 free(buckets[i]); } } /* Driver Code */ int main() { // 设输入数据为浮点数,范围为 [0, 1) float nums[SIZE] = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; bucketSort(nums, SIZE); printf("桶排序完成后 nums = "); printArrayFloat(nums, SIZE); return 0; } ================================================ FILE: codes/c/chapter_sorting/counting_sort.c ================================================ /** * File: counting_sort.c * Created Time: 2023-03-20 * Author: Reanon (793584285@qq.com), Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* 计数排序 */ // 简单实现,无法用于排序对象 void countingSortNaive(int nums[], int size) { // 1. 统计数组最大元素 m int m = 0; for (int i = 0; i < size; i++) { if (nums[i] > m) { m = nums[i]; } } // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 int *counter = calloc(m + 1, sizeof(int)); for (int i = 0; i < size; i++) { counter[nums[i]]++; } // 3. 遍历 counter ,将各元素填入原数组 nums int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } // 4. 释放内存 free(counter); } /* 计数排序 */ // 完整实现,可排序对象,并且是稳定排序 void countingSort(int nums[], int size) { // 1. 统计数组最大元素 m int m = 0; for (int i = 0; i < size; i++) { if (nums[i] > m) { m = nums[i]; } } // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 int *counter = calloc(m, sizeof(int)); for (int i = 0; i < size; i++) { counter[nums[i]]++; } // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. 倒序遍历 nums ,将各元素填入结果数组 res // 初始化数组 res 用于记录结果 int *res = malloc(sizeof(int) * size); for (int i = size - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // 将 num 放置到对应索引处 counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 } // 使用结果数组 res 覆盖原数组 nums memcpy(nums, res, size * sizeof(int)); // 5. 释放内存 free(res); free(counter); } /* Driver Code */ int main() { int nums[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; int size = sizeof(nums) / sizeof(int); countingSortNaive(nums, size); printf("计数排序(无法排序对象)完成后 nums = "); printArray(nums, size); int nums1[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; int size1 = sizeof(nums1) / sizeof(int); countingSort(nums1, size1); printf("计数排序完成后 nums1 = "); printArray(nums1, size1); return 0; } ================================================ FILE: codes/c/chapter_sorting/heap_sort.c ================================================ /** * File: heap_sort.c * Created Time: 2023-05-30 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ void siftDown(int nums[], int n, int i) { while (1) { // 判断节点 i, l, r 中值最大的节点,记为 ma int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; if (l < n && nums[l] > nums[ma]) ma = l; if (r < n && nums[r] > nums[ma]) ma = r; // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if (ma == i) { break; } // 交换两节点 int temp = nums[i]; nums[i] = nums[ma]; nums[ma] = temp; // 循环向下堆化 i = ma; } } /* 堆排序 */ void heapSort(int nums[], int n) { // 建堆操作:堆化除叶节点以外的其他所有节点 for (int i = n / 2 - 1; i >= 0; --i) { siftDown(nums, n, i); } // 从堆中提取最大元素,循环 n-1 轮 for (int i = n - 1; i > 0; --i) { // 交换根节点与最右叶节点(交换首元素与尾元素) int tmp = nums[0]; nums[0] = nums[i]; nums[i] = tmp; // 以根节点为起点,从顶至底进行堆化 siftDown(nums, i, 0); } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; int n = sizeof(nums) / sizeof(nums[0]); heapSort(nums, n); printf("堆排序完成后 nums = "); printArray(nums, n); return 0; } ================================================ FILE: codes/c/chapter_sorting/insertion_sort.c ================================================ /** * File: insertion_sort.c * Created Time: 2022-12-29 * Author: Listening (https://github.com/L-Super) */ #include "../utils/common.h" /* 插入排序 */ void insertionSort(int nums[], int size) { // 外循环:已排序区间为 [0, i-1] for (int i = 1; i < size; i++) { int base = nums[i], j = i - 1; // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while (j >= 0 && nums[j] > base) { // 将 nums[j] 向右移动一位 nums[j + 1] = nums[j]; j--; } // 将 base 赋值到正确位置 nums[j + 1] = base; } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; insertionSort(nums, 6); printf("插入排序完成后 nums = "); for (int i = 0; i < 6; i++) { printf("%d ", nums[i]); } printf("\n"); return 0; } ================================================ FILE: codes/c/chapter_sorting/merge_sort.c ================================================ /** * File: merge_sort.c * Created Time: 2022-03-21 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* 合并左子数组和右子数组 */ void merge(int *nums, int left, int mid, int right) { // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] // 创建一个临时数组 tmp ,用于存放合并后的结果 int tmpSize = right - left + 1; int *tmp = (int *)malloc(tmpSize * sizeof(int)); // 初始化左子数组和右子数组的起始索引 int i = left, j = mid + 1, k = 0; // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // 将左子数组和右子数组的剩余元素复制到临时数组中 while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 for (k = 0; k < tmpSize; ++k) { nums[left + k] = tmp[k]; } // 释放内存 free(tmp); } /* 归并排序 */ void mergeSort(int *nums, int left, int right) { // 终止条件 if (left >= right) return; // 当子数组长度为 1 时终止递归 // 划分阶段 int mid = left + (right - left) / 2; // 计算中点 mergeSort(nums, left, mid); // 递归左子数组 mergeSort(nums, mid + 1, right); // 递归右子数组 // 合并阶段 merge(nums, left, mid, right); } /* Driver Code */ int main() { /* 归并排序 */ int nums[] = {7, 3, 2, 6, 0, 1, 5, 4}; int size = sizeof(nums) / sizeof(int); mergeSort(nums, 0, size - 1); printf("归并排序完成后 nums = "); printArray(nums, size); return 0; } ================================================ FILE: codes/c/chapter_sorting/quick_sort.c ================================================ /** * File: quick_sort.c * Created Time: 2023-01-18 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* 元素交换 */ void swap(int nums[], int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵划分 */ int partition(int nums[], int left, int right) { // 以 nums[left] 为基准数 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // 从右向左找首个小于基准数的元素 } while (i < j && nums[i] <= nums[left]) { i++; // 从左向右找首个大于基准数的元素 } // 交换这两个元素 swap(nums, i, j); } // 将基准数交换至两子数组的分界线 swap(nums, i, left); // 返回基准数的索引 return i; } /* 快速排序 */ void quickSort(int nums[], int left, int right) { // 子数组长度为 1 时终止递归 if (left >= right) { return; } // 哨兵划分 int pivot = partition(nums, left, right); // 递归左子数组、右子数组 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } // 以下为中位数优化的快速排序 /* 选取三个候选元素的中位数 */ 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; } /* 哨兵划分(三数取中值) */ int partitionMedian(int 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; // 返回基准数的索引 } /* 快速排序(三数取中值) */ void quickSortMedian(int nums[], int left, int right) { // 子数组长度为 1 时终止递归 if (left >= right) return; // 哨兵划分 int pivot = partitionMedian(nums, left, right); // 递归左子数组、右子数组 quickSortMedian(nums, left, pivot - 1); quickSortMedian(nums, pivot + 1, right); } // 以下为递归深度优化的快速排序 /* 快速排序(递归深度优化) */ void quickSortTailCall(int nums[], int left, int right) { // 子数组长度为 1 时终止 while (left < right) { // 哨兵划分操作 int pivot = partition(nums, left, right); // 对两个子数组中较短的那个执行快速排序 if (pivot - left < right - pivot) { // 递归排序左子数组 quickSortTailCall(nums, left, pivot - 1); // 剩余未排序区间为 [pivot + 1, right] left = pivot + 1; } else { // 递归排序右子数组 quickSortTailCall(nums, pivot + 1, right); // 剩余未排序区间为 [left, pivot - 1] right = pivot - 1; } } } /* Driver Code */ int main() { /* 快速排序 */ int nums[] = {2, 4, 1, 0, 3, 5}; int size = sizeof(nums) / sizeof(int); quickSort(nums, 0, size - 1); printf("快速排序完成后 nums = "); printArray(nums, size); /* 快速排序(中位基准数优化) */ int nums1[] = {2, 4, 1, 0, 3, 5}; quickSortMedian(nums1, 0, size - 1); printf("快速排序(中位基准数优化)完成后 nums = "); printArray(nums1, size); /* 快速排序(递归深度优化) */ int nums2[] = {2, 4, 1, 0, 3, 5}; quickSortTailCall(nums2, 0, size - 1); printf("快速排序(递归深度优化)完成后 nums = "); printArray(nums1, size); return 0; } ================================================ FILE: codes/c/chapter_sorting/radix_sort.c ================================================ /** * File: radix_sort.c * Created Time: 2023-01-18 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ int digit(int num, int exp) { // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 return (num / exp) % 10; } /* 计数排序(根据 nums 第 k 位排序) */ void countingSortDigit(int nums[], int size, int exp) { // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 int *counter = (int *)malloc((sizeof(int) * 10)); memset(counter, 0, sizeof(int) * 10); // 初始化为 0 以支持后续内存释放 // 统计 0~9 各数字的出现次数 for (int i = 0; i < size; i++) { // 获取 nums[i] 第 k 位,记为 d int d = digit(nums[i], exp); // 统计数字 d 的出现次数 counter[d]++; } // 求前缀和,将“出现个数”转换为“数组索引” for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 倒序遍历,根据桶内统计结果,将各元素填入 res int *res = (int *)malloc(sizeof(int) * size); for (int i = size - 1; i >= 0; i--) { int d = digit(nums[i], exp); int j = counter[d] - 1; // 获取 d 在数组中的索引 j res[j] = nums[i]; // 将当前元素填入索引 j counter[d]--; // 将 d 的数量减 1 } // 使用结果覆盖原数组 nums for (int i = 0; i < size; i++) { nums[i] = res[i]; } // 释放内存 free(res); free(counter); } /* 基数排序 */ void radixSort(int nums[], int size) { // 获取数组的最大元素,用于判断最大位数 int max = INT32_MIN; for (int i = 0; i < size; i++) { if (nums[i] > max) { max = nums[i]; } } // 按照从低位到高位的顺序遍历 for (int exp = 1; max >= exp; exp *= 10) // 对数组元素的第 k 位执行计数排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums, size, exp); } /* Driver Code */ int main() { // 基数排序 int nums[] = {10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996}; int size = sizeof(nums) / sizeof(int); radixSort(nums, size); printf("基数排序完成后 nums = "); printArray(nums, size); } ================================================ FILE: codes/c/chapter_sorting/selection_sort.c ================================================ /** * File: selection_sort.c * Created Time: 2023-05-31 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 选择排序 */ void selectionSort(int nums[], int n) { // 外循环:未排序区间为 [i, n-1] for (int i = 0; i < n - 1; i++) { // 内循环:找到未排序区间内的最小元素 int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // 记录最小元素的索引 } // 将该最小元素与未排序区间的首个元素交换 int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; int n = sizeof(nums) / sizeof(nums[0]); selectionSort(nums, n); printf("选择排序完成后 nums = "); printArray(nums, n); return 0; } ================================================ FILE: codes/c/chapter_stack_and_queue/CMakeLists.txt ================================================ add_executable(array_stack array_stack.c) add_executable(linkedlist_stack linkedlist_stack.c) add_executable(array_queue array_queue.c) add_executable(linkedlist_queue linkedlist_queue.c) add_executable(array_deque array_deque.c) add_executable(linkedlist_deque linkedlist_deque.c) ================================================ FILE: codes/c/chapter_stack_and_queue/array_deque.c ================================================ /** * File: array_deque.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 基于环形数组实现的双向队列 */ typedef struct { int *nums; // 用于存储队列元素的数组 int front; // 队首指针,指向队首元素 int queSize; // 尾指针,指向队尾 + 1 int queCapacity; // 队列容量 } ArrayDeque; /* 构造函数 */ ArrayDeque *newArrayDeque(int capacity) { ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque)); // 初始化数组 deque->queCapacity = capacity; deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity); deque->front = deque->queSize = 0; return deque; } /* 析构函数 */ void delArrayDeque(ArrayDeque *deque) { free(deque->nums); free(deque); } /* 获取双向队列的容量 */ int capacity(ArrayDeque *deque) { return deque->queCapacity; } /* 获取双向队列的长度 */ int size(ArrayDeque *deque) { return deque->queSize; } /* 判断双向队列是否为空 */ bool empty(ArrayDeque *deque) { return deque->queSize == 0; } /* 计算环形数组索引 */ int dequeIndex(ArrayDeque *deque, int i) { // 通过取余操作实现数组首尾相连 // 当 i 越过数组尾部时,回到头部 // 当 i 越过数组头部后,回到尾部 return ((i + capacity(deque)) % capacity(deque)); } /* 队首入队 */ void pushFirst(ArrayDeque *deque, int num) { if (deque->queSize == capacity(deque)) { printf("双向队列已满\r\n"); return; } // 队首指针向左移动一位 // 通过取余操作实现 front 越过数组头部回到尾部 deque->front = dequeIndex(deque, deque->front - 1); // 将 num 添加到队首 deque->nums[deque->front] = num; deque->queSize++; } /* 队尾入队 */ void pushLast(ArrayDeque *deque, int num) { if (deque->queSize == capacity(deque)) { printf("双向队列已满\r\n"); return; } // 计算队尾指针,指向队尾索引 + 1 int rear = dequeIndex(deque, deque->front + deque->queSize); // 将 num 添加至队尾 deque->nums[rear] = num; deque->queSize++; } /* 访问队首元素 */ int peekFirst(ArrayDeque *deque) { // 访问异常:双向队列为空 assert(empty(deque) == 0); return deque->nums[deque->front]; } /* 访问队尾元素 */ int peekLast(ArrayDeque *deque) { // 访问异常:双向队列为空 assert(empty(deque) == 0); int last = dequeIndex(deque, deque->front + deque->queSize - 1); return deque->nums[last]; } /* 队首出队 */ int popFirst(ArrayDeque *deque) { int num = peekFirst(deque); // 队首指针向后移动一位 deque->front = dequeIndex(deque, deque->front + 1); deque->queSize--; return num; } /* 队尾出队 */ int popLast(ArrayDeque *deque) { int num = peekLast(deque); deque->queSize--; return num; } /* 返回数组用于打印 */ int *toArray(ArrayDeque *deque, int *queSize) { *queSize = deque->queSize; int *res = (int *)calloc(deque->queSize, sizeof(int)); int j = deque->front; for (int i = 0; i < deque->queSize; i++) { res[i] = deque->nums[j % deque->queCapacity]; j++; } return res; } /* Driver Code */ int main() { /* 初始化队列 */ int capacity = 10; int queSize; ArrayDeque *deque = newArrayDeque(capacity); pushLast(deque, 3); pushLast(deque, 2); pushLast(deque, 5); printf("双向队列 deque = "); printArray(toArray(deque, &queSize), queSize); /* 访问元素 */ int peekFirstNum = peekFirst(deque); printf("队首元素 peekFirst = %d\r\n", peekFirstNum); int peekLastNum = peekLast(deque); printf("队尾元素 peekLast = %d\r\n", peekLastNum); /* 元素入队 */ pushLast(deque, 4); printf("元素 4 队尾入队后 deque = "); printArray(toArray(deque, &queSize), queSize); pushFirst(deque, 1); printf("元素 1 队首入队后 deque = "); printArray(toArray(deque, &queSize), queSize); /* 元素出队 */ int popLastNum = popLast(deque); printf("队尾出队元素 = %d ,队尾出队后 deque= ", popLastNum); printArray(toArray(deque, &queSize), queSize); int popFirstNum = popFirst(deque); printf("队首出队元素 = %d ,队首出队后 deque= ", popFirstNum); printArray(toArray(deque, &queSize), queSize); /* 获取队列的长度 */ int dequeSize = size(deque); printf("双向队列长度 size = %d\r\n", dequeSize); /* 判断队列是否为空 */ bool isEmpty = empty(deque); printf("队列是否为空 = %s\r\n", isEmpty ? "true" : "false"); // 释放内存 delArrayDeque(deque); return 0; } ================================================ FILE: codes/c/chapter_stack_and_queue/array_queue.c ================================================ /** * File: array_queue.c * Created Time: 2023-01-28 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* 基于环形数组实现的队列 */ typedef struct { int *nums; // 用于存储队列元素的数组 int front; // 队首指针,指向队首元素 int queSize; // 当前队列的元素数量 int queCapacity; // 队列容量 } ArrayQueue; /* 构造函数 */ ArrayQueue *newArrayQueue(int capacity) { ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue)); // 初始化数组 queue->queCapacity = capacity; queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity); queue->front = queue->queSize = 0; return queue; } /* 析构函数 */ void delArrayQueue(ArrayQueue *queue) { free(queue->nums); free(queue); } /* 获取队列的容量 */ int capacity(ArrayQueue *queue) { return queue->queCapacity; } /* 获取队列的长度 */ int size(ArrayQueue *queue) { return queue->queSize; } /* 判断队列是否为空 */ bool empty(ArrayQueue *queue) { return queue->queSize == 0; } /* 访问队首元素 */ int peek(ArrayQueue *queue) { assert(size(queue) != 0); return queue->nums[queue->front]; } /* 入队 */ void push(ArrayQueue *queue, int num) { if (size(queue) == capacity(queue)) { printf("队列已满\r\n"); return; } // 计算队尾指针,指向队尾索引 + 1 // 通过取余操作实现 rear 越过数组尾部后回到头部 int rear = (queue->front + queue->queSize) % queue->queCapacity; // 将 num 添加至队尾 queue->nums[rear] = num; queue->queSize++; } /* 出队 */ int pop(ArrayQueue *queue) { int num = peek(queue); // 队首指针向后移动一位,若越过尾部,则返回到数组头部 queue->front = (queue->front + 1) % queue->queCapacity; queue->queSize--; return num; } /* 返回数组用于打印 */ int *toArray(ArrayQueue *queue, int *queSize) { *queSize = queue->queSize; int *res = (int *)calloc(queue->queSize, sizeof(int)); int j = queue->front; for (int i = 0; i < queue->queSize; i++) { res[i] = queue->nums[j % queue->queCapacity]; j++; } return res; } /* Driver Code */ int main() { /* 初始化队列 */ int capacity = 10; int queSize; ArrayQueue *queue = newArrayQueue(capacity); /* 元素入队 */ push(queue, 1); push(queue, 3); push(queue, 2); push(queue, 5); push(queue, 4); printf("队列 queue = "); printArray(toArray(queue, &queSize), queSize); /* 访问队首元素 */ int peekNum = peek(queue); printf("队首元素 peek = %d\r\n", peekNum); /* 元素出队 */ peekNum = pop(queue); printf("出队元素 pop = %d ,出队后 queue = ", peekNum); printArray(toArray(queue, &queSize), queSize); /* 获取队列的长度 */ int queueSize = size(queue); printf("队列长度 size = %d\r\n", queueSize); /* 判断队列是否为空 */ bool isEmpty = empty(queue); printf("队列是否为空 = %s\r\n", isEmpty ? "true" : "false"); /* 测试环形数组 */ for (int i = 0; i < 10; i++) { push(queue, i); pop(queue); printf("第 %d 轮入队 + 出队后 queue = ", i); printArray(toArray(queue, &queSize), queSize); } // 释放内存 delArrayQueue(queue); return 0; } ================================================ FILE: codes/c/chapter_stack_and_queue/array_stack.c ================================================ /** * File: array_stack.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 5000 /* 基于数组实现的栈 */ typedef struct { int *data; int size; } ArrayStack; /* 构造函数 */ ArrayStack *newArrayStack() { ArrayStack *stack = malloc(sizeof(ArrayStack)); // 初始化一个大容量,避免扩容 stack->data = malloc(sizeof(int) * MAX_SIZE); stack->size = 0; return stack; } /* 析构函数 */ void delArrayStack(ArrayStack *stack) { free(stack->data); free(stack); } /* 获取栈的长度 */ int size(ArrayStack *stack) { return stack->size; } /* 判断栈是否为空 */ bool isEmpty(ArrayStack *stack) { return stack->size == 0; } /* 入栈 */ void push(ArrayStack *stack, int num) { if (stack->size == MAX_SIZE) { printf("栈已满\n"); return; } stack->data[stack->size] = num; stack->size++; } /* 访问栈顶元素 */ int peek(ArrayStack *stack) { if (stack->size == 0) { printf("栈为空\n"); return INT_MAX; } return stack->data[stack->size - 1]; } /* 出栈 */ int pop(ArrayStack *stack) { int val = peek(stack); stack->size--; return val; } /* Driver Code */ int main() { /* 初始化栈 */ ArrayStack *stack = newArrayStack(); /* 元素入栈 */ push(stack, 1); push(stack, 3); push(stack, 2); push(stack, 5); push(stack, 4); printf("栈 stack = "); printArray(stack->data, stack->size); /* 访问栈顶元素 */ int val = peek(stack); printf("栈顶元素 top = %d\n", val); /* 元素出栈 */ val = pop(stack); printf("出栈元素 pop = %d ,出栈后 stack = ", val); printArray(stack->data, stack->size); /* 获取栈的长度 */ int size = stack->size; printf("栈的长度 size = %d\n", size); /* 判断是否为空 */ bool empty = isEmpty(stack); printf("栈是否为空 = %s\n", empty ? "true" : "false"); // 释放内存 delArrayStack(stack); return 0; } ================================================ FILE: codes/c/chapter_stack_and_queue/linkedlist_deque.c ================================================ /** * File: linkedlist_deque.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 双向链表节点 */ typedef struct DoublyListNode { int val; // 节点值 struct DoublyListNode *next; // 后继节点 struct DoublyListNode *prev; // 前驱节点 } DoublyListNode; /* 构造函数 */ DoublyListNode *newDoublyListNode(int num) { DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode)); new->val = num; new->next = NULL; new->prev = NULL; return new; } /* 析构函数 */ void delDoublyListNode(DoublyListNode *node) { free(node); } /* 基于双向链表实现的双向队列 */ typedef struct { DoublyListNode *front, *rear; // 头节点 front ,尾节点 rear int queSize; // 双向队列的长度 } LinkedListDeque; /* 构造函数 */ LinkedListDeque *newLinkedListDeque() { LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque)); deque->front = NULL; deque->rear = NULL; deque->queSize = 0; return deque; } /* 析构函数 */ void delLinkedListdeque(LinkedListDeque *deque) { // 释放所有节点 for (int i = 0; i < deque->queSize && deque->front != NULL; i++) { DoublyListNode *tmp = deque->front; deque->front = deque->front->next; free(tmp); } // 释放 deque 结构体 free(deque); } /* 获取队列的长度 */ int size(LinkedListDeque *deque) { return deque->queSize; } /* 判断队列是否为空 */ bool empty(LinkedListDeque *deque) { return (size(deque) == 0); } /* 入队 */ void push(LinkedListDeque *deque, int num, bool isFront) { DoublyListNode *node = newDoublyListNode(num); // 若链表为空,则令 front 和 rear 都指向node if (empty(deque)) { deque->front = deque->rear = node; } // 队首入队操作 else if (isFront) { // 将 node 添加至链表头部 deque->front->prev = node; node->next = deque->front; deque->front = node; // 更新头节点 } // 队尾入队操作 else { // 将 node 添加至链表尾部 deque->rear->next = node; node->prev = deque->rear; deque->rear = node; } deque->queSize++; // 更新队列长度 } /* 队首入队 */ void pushFirst(LinkedListDeque *deque, int num) { push(deque, num, true); } /* 队尾入队 */ void pushLast(LinkedListDeque *deque, int num) { push(deque, num, false); } /* 访问队首元素 */ int peekFirst(LinkedListDeque *deque) { assert(size(deque) && deque->front); return deque->front->val; } /* 访问队尾元素 */ int peekLast(LinkedListDeque *deque) { assert(size(deque) && deque->rear); return deque->rear->val; } /* 出队 */ int pop(LinkedListDeque *deque, bool isFront) { if (empty(deque)) return -1; int val; // 队首出队操作 if (isFront) { val = peekFirst(deque); // 暂存头节点值 DoublyListNode *fNext = deque->front->next; if (fNext) { fNext->prev = NULL; deque->front->next = NULL; } delDoublyListNode(deque->front); deque->front = fNext; // 更新头节点 } // 队尾出队操作 else { val = peekLast(deque); // 暂存尾节点值 DoublyListNode *rPrev = deque->rear->prev; if (rPrev) { rPrev->next = NULL; deque->rear->prev = NULL; } delDoublyListNode(deque->rear); deque->rear = rPrev; // 更新尾节点 } deque->queSize--; // 更新队列长度 return val; } /* 队首出队 */ int popFirst(LinkedListDeque *deque) { return pop(deque, true); } /* 队尾出队 */ int popLast(LinkedListDeque *deque) { return pop(deque, false); } /* 打印队列 */ void printLinkedListDeque(LinkedListDeque *deque) { int *arr = malloc(sizeof(int) * deque->queSize); // 拷贝链表中的数据到数组 int i; DoublyListNode *node; for (i = 0, node = deque->front; i < deque->queSize; i++) { arr[i] = node->val; node = node->next; } printArray(arr, deque->queSize); free(arr); } /* Driver Code */ int main() { /* 初始化双向队列 */ LinkedListDeque *deque = newLinkedListDeque(); pushLast(deque, 3); pushLast(deque, 2); pushLast(deque, 5); printf("双向队列 deque = "); printLinkedListDeque(deque); /* 访问元素 */ int peekFirstNum = peekFirst(deque); printf("队首元素 peekFirst = %d\r\n", peekFirstNum); int peekLastNum = peekLast(deque); printf("队首元素 peekLast = %d\r\n", peekLastNum); /* 元素入队 */ pushLast(deque, 4); printf("元素 4 队尾入队后 deque ="); printLinkedListDeque(deque); pushFirst(deque, 1); printf("元素 1 队首入队后 deque ="); printLinkedListDeque(deque); /* 元素出队 */ int popLastNum = popLast(deque); printf("队尾出队元素 popLast = %d ,队尾出队后 deque = ", popLastNum); printLinkedListDeque(deque); int popFirstNum = popFirst(deque); printf("队首出队元素 popFirst = %d ,队首出队后 deque = ", popFirstNum); printLinkedListDeque(deque); /* 获取队列的长度 */ int dequeSize = size(deque); printf("双向队列长度 size = %d\r\n", dequeSize); /* 判断队列是否为空 */ bool isEmpty = empty(deque); printf("双向队列是否为空 = %s\r\n", isEmpty ? "true" : "false"); // 释放内存 delLinkedListdeque(deque); return 0; } ================================================ FILE: codes/c/chapter_stack_and_queue/linkedlist_queue.c ================================================ /** * File: linkedlist_queue.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 基于链表实现的队列 */ typedef struct { ListNode *front, *rear; int queSize; } LinkedListQueue; /* 构造函数 */ LinkedListQueue *newLinkedListQueue() { LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue)); queue->front = NULL; queue->rear = NULL; queue->queSize = 0; return queue; } /* 析构函数 */ void delLinkedListQueue(LinkedListQueue *queue) { // 释放所有节点 while (queue->front != NULL) { ListNode *tmp = queue->front; queue->front = queue->front->next; free(tmp); } // 释放 queue 结构体 free(queue); } /* 获取队列的长度 */ int size(LinkedListQueue *queue) { return queue->queSize; } /* 判断队列是否为空 */ bool empty(LinkedListQueue *queue) { return (size(queue) == 0); } /* 入队 */ void push(LinkedListQueue *queue, int num) { // 尾节点处添加 node ListNode *node = newListNode(num); // 如果队列为空,则令头、尾节点都指向该节点 if (queue->front == NULL) { queue->front = node; queue->rear = node; } // 如果队列不为空,则将该节点添加到尾节点后 else { queue->rear->next = node; queue->rear = node; } queue->queSize++; } /* 访问队首元素 */ int peek(LinkedListQueue *queue) { assert(size(queue) && queue->front); return queue->front->val; } /* 出队 */ int pop(LinkedListQueue *queue) { int num = peek(queue); ListNode *tmp = queue->front; queue->front = queue->front->next; free(tmp); queue->queSize--; return num; } /* 打印队列 */ void printLinkedListQueue(LinkedListQueue *queue) { int *arr = malloc(sizeof(int) * queue->queSize); // 拷贝链表中的数据到数组 int i; ListNode *node; for (i = 0, node = queue->front; i < queue->queSize; i++) { arr[i] = node->val; node = node->next; } printArray(arr, queue->queSize); free(arr); } /* Driver Code */ int main() { /* 初始化队列 */ LinkedListQueue *queue = newLinkedListQueue(); /* 元素入队 */ push(queue, 1); push(queue, 3); push(queue, 2); push(queue, 5); push(queue, 4); printf("队列 queue = "); printLinkedListQueue(queue); /* 访问队首元素 */ int peekNum = peek(queue); printf("队首元素 peek = %d\r\n", peekNum); /* 元素出队 */ peekNum = pop(queue); printf("出队元素 pop = %d ,出队后 queue = ", peekNum); printLinkedListQueue(queue); /* 获取队列的长度 */ int queueSize = size(queue); printf("队列长度 size = %d\r\n", queueSize); /* 判断队列是否为空 */ bool isEmpty = empty(queue); printf("队列是否为空 = %s\r\n", isEmpty ? "true" : "false"); // 释放内存 delLinkedListQueue(queue); return 0; } ================================================ FILE: codes/c/chapter_stack_and_queue/linkedlist_stack.c ================================================ /** * File: linkedlist_stack.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* 基于链表实现的栈 */ typedef struct { ListNode *top; // 将头节点作为栈顶 int size; // 栈的长度 } LinkedListStack; /* 构造函数 */ LinkedListStack *newLinkedListStack() { LinkedListStack *s = malloc(sizeof(LinkedListStack)); s->top = NULL; s->size = 0; return s; } /* 析构函数 */ void delLinkedListStack(LinkedListStack *s) { while (s->top) { ListNode *n = s->top->next; free(s->top); s->top = n; } free(s); } /* 获取栈的长度 */ int size(LinkedListStack *s) { return s->size; } /* 判断栈是否为空 */ bool isEmpty(LinkedListStack *s) { return size(s) == 0; } /* 入栈 */ void push(LinkedListStack *s, int num) { ListNode *node = (ListNode *)malloc(sizeof(ListNode)); node->next = s->top; // 更新新加节点指针域 node->val = num; // 更新新加节点数据域 s->top = node; // 更新栈顶 s->size++; // 更新栈大小 } /* 访问栈顶元素 */ int peek(LinkedListStack *s) { if (s->size == 0) { printf("栈为空\n"); return INT_MAX; } return s->top->val; } /* 出栈 */ int pop(LinkedListStack *s) { int val = peek(s); ListNode *tmp = s->top; s->top = s->top->next; // 释放内存 free(tmp); s->size--; return val; } /* Driver Code */ int main() { /* 初始化栈 */ LinkedListStack *stack = newLinkedListStack(); /* 元素入栈 */ push(stack, 1); push(stack, 3); push(stack, 2); push(stack, 5); push(stack, 4); printf("栈 stack = "); printLinkedList(stack->top); /* 访问栈顶元素 */ int val = peek(stack); printf("栈顶元素 top = %d\r\n", val); /* 元素出栈 */ val = pop(stack); printf("出栈元素 pop = %d, 出栈后 stack = ", val); printLinkedList(stack->top); /* 获取栈的长度 */ printf("栈的长度 size = %d\n", size(stack)); /* 判断是否为空 */ bool empty = isEmpty(stack); printf("栈是否为空 = %s\n", empty ? "true" : "false"); // 释放内存 delLinkedListStack(stack); return 0; } ================================================ FILE: codes/c/chapter_tree/CMakeLists.txt ================================================ add_executable(avl_tree avl_tree.c) add_executable(binary_tree binary_tree.c) add_executable(binary_tree_bfs binary_tree_bfs.c) add_executable(binary_tree_dfs binary_tree_dfs.c) add_executable(binary_search_tree binary_search_tree.c) add_executable(array_binary_tree array_binary_tree.c) ================================================ FILE: codes/c/chapter_tree/array_binary_tree.c ================================================ /** * File: array_binary_tree.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 数组表示下的二叉树结构体 */ typedef struct { int *tree; int size; } ArrayBinaryTree; /* 构造函数 */ ArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) { ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree)); abt->tree = malloc(sizeof(int) * arrSize); memcpy(abt->tree, arr, sizeof(int) * arrSize); abt->size = arrSize; return abt; } /* 析构函数 */ void delArrayBinaryTree(ArrayBinaryTree *abt) { free(abt->tree); free(abt); } /* 列表容量 */ int size(ArrayBinaryTree *abt) { return abt->size; } /* 获取索引为 i 节点的值 */ int val(ArrayBinaryTree *abt, int i) { // 若索引越界,则返回 INT_MAX ,代表空位 if (i < 0 || i >= size(abt)) return INT_MAX; return abt->tree[i]; } /* 获取索引为 i 节点的左子节点的索引 */ int left(int i) { return 2 * i + 1; } /* 获取索引为 i 节点的右子节点的索引 */ int right(int i) { return 2 * i + 2; } /* 获取索引为 i 节点的父节点的索引 */ int parent(int i) { return (i - 1) / 2; } /* 层序遍历 */ int *levelOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; // 直接遍历数组 for (int i = 0; i < size(abt); i++) { if (val(abt, i) != INT_MAX) res[index++] = val(abt, i); } *returnSize = index; return res; } /* 深度优先遍历 */ void dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) { // 若为空位,则返回 if (val(abt, i) == INT_MAX) return; // 前序遍历 if (strcmp(order, "pre") == 0) res[(*index)++] = val(abt, i); dfs(abt, left(i), order, res, index); // 中序遍历 if (strcmp(order, "in") == 0) res[(*index)++] = val(abt, i); dfs(abt, right(i), order, res, index); // 后序遍历 if (strcmp(order, "post") == 0) res[(*index)++] = val(abt, i); } /* 前序遍历 */ int *preOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "pre", res, &index); *returnSize = index; return res; } /* 中序遍历 */ int *inOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "in", res, &index); *returnSize = index; return res; } /* 后序遍历 */ int *postOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "post", res, &index); *returnSize = index; return res; } /* Driver Code */ int main() { // 初始化二叉树 // 使用 INT_MAX 代表空位 NULL int arr[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; int arrSize = sizeof(arr) / sizeof(arr[0]); TreeNode *root = arrayToTree(arr, arrSize); printf("\n初始化二叉树\n"); printf("二叉树的数组表示:\n"); printArray(arr, arrSize); printf("二叉树的链表表示:\n"); printTree(root); ArrayBinaryTree *abt = newArrayBinaryTree(arr, arrSize); // 访问节点 int i = 1; int l = left(i), r = right(i), p = parent(i); printf("\n当前节点的索引为 %d,值为 %d\n", i, val(abt, i)); printf("其左子节点的索引为 %d,值为 %d\n", l, l < arrSize ? val(abt, l) : INT_MAX); printf("其右子节点的索引为 %d,值为 %d\n", r, r < arrSize ? val(abt, r) : INT_MAX); printf("其父节点的索引为 %d,值为 %d\n", p, p < arrSize ? val(abt, p) : INT_MAX); // 遍历树 int returnSize; int *res; res = levelOrder(abt, &returnSize); printf("\n层序遍历为: "); printArray(res, returnSize); free(res); res = preOrder(abt, &returnSize); printf("前序遍历为: "); printArray(res, returnSize); free(res); res = inOrder(abt, &returnSize); printf("中序遍历为: "); printArray(res, returnSize); free(res); res = postOrder(abt, &returnSize); printf("后序遍历为: "); printArray(res, returnSize); free(res); // 释放内存 delArrayBinaryTree(abt); return 0; } ================================================ FILE: codes/c/chapter_tree/avl_tree.c ================================================ /** * File: avl_tree.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* AVL 树结构体 */ typedef struct { TreeNode *root; } AVLTree; /* 构造函数 */ AVLTree *newAVLTree() { AVLTree *tree = (AVLTree *)malloc(sizeof(AVLTree)); tree->root = NULL; return tree; } /* 析构函数 */ void delAVLTree(AVLTree *tree) { freeMemoryTree(tree->root); free(tree); } /* 获取节点高度 */ int height(TreeNode *node) { // 空节点高度为 -1 ,叶节点高度为 0 if (node != NULL) { return node->height; } return -1; } /* 更新节点高度 */ void updateHeight(TreeNode *node) { int lh = height(node->left); int rh = height(node->right); // 节点高度等于最高子树高度 + 1 if (lh > rh) { node->height = lh + 1; } else { node->height = rh + 1; } } /* 获取平衡因子 */ int balanceFactor(TreeNode *node) { // 空节点平衡因子为 0 if (node == NULL) { return 0; } // 节点平衡因子 = 左子树高度 - 右子树高度 return height(node->left) - height(node->right); } /* 右旋操作 */ TreeNode *rightRotate(TreeNode *node) { TreeNode *child, *grandChild; child = node->left; grandChild = child->right; // 以 child 为原点,将 node 向右旋转 child->right = node; node->left = grandChild; // 更新节点高度 updateHeight(node); updateHeight(child); // 返回旋转后子树的根节点 return child; } /* 左旋操作 */ TreeNode *leftRotate(TreeNode *node) { TreeNode *child, *grandChild; child = node->right; grandChild = child->left; // 以 child 为原点,将 node 向左旋转 child->left = node; node->right = grandChild; // 更新节点高度 updateHeight(node); updateHeight(child); // 返回旋转后子树的根节点 return child; } /* 执行旋转操作,使该子树重新恢复平衡 */ TreeNode *rotate(TreeNode *node) { // 获取节点 node 的平衡因子 int bf = balanceFactor(node); // 左偏树 if (bf > 1) { if (balanceFactor(node->left) >= 0) { // 右旋 return rightRotate(node); } else { // 先左旋后右旋 node->left = leftRotate(node->left); return rightRotate(node); } } // 右偏树 if (bf < -1) { if (balanceFactor(node->right) <= 0) { // 左旋 return leftRotate(node); } else { // 先右旋后左旋 node->right = rightRotate(node->right); return leftRotate(node); } } // 平衡树,无须旋转,直接返回 return node; } /* 递归插入节点(辅助函数) */ TreeNode *insertHelper(TreeNode *node, int val) { if (node == NULL) { return newTreeNode(val); } /* 1. 查找插入位置并插入节点 */ if (val < node->val) { node->left = insertHelper(node->left, val); } else if (val > node->val) { node->right = insertHelper(node->right, val); } else { // 重复节点不插入,直接返回 return node; } // 更新节点高度 updateHeight(node); /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = rotate(node); // 返回子树的根节点 return node; } /* 插入节点 */ void insert(AVLTree *tree, int val) { tree->root = insertHelper(tree->root, val); } /* 递归删除节点(辅助函数) */ TreeNode *removeHelper(TreeNode *node, int val) { TreeNode *child, *grandChild; if (node == NULL) { return NULL; } /* 1. 查找节点并删除 */ if (val < node->val) { node->left = removeHelper(node->left, val); } else if (val > node->val) { node->right = removeHelper(node->right, val); } else { if (node->left == NULL || node->right == NULL) { child = node->left; if (node->right != NULL) { child = node->right; } // 子节点数量 = 0 ,直接删除 node 并返回 if (child == NULL) { return NULL; } else { // 子节点数量 = 1 ,直接删除 node node = child; } } else { // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 TreeNode *temp = node->right; while (temp->left != NULL) { temp = temp->left; } int tempVal = temp->val; node->right = removeHelper(node->right, temp->val); node->val = tempVal; } } // 更新节点高度 updateHeight(node); /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = rotate(node); // 返回子树的根节点 return node; } /* 删除节点 */ // 由于引入了 stdio.h ,此处无法使用 remove 关键词 void removeItem(AVLTree *tree, int val) { TreeNode *root = removeHelper(tree->root, val); } /* 查找节点 */ TreeNode *search(AVLTree *tree, int val) { TreeNode *cur = tree->root; // 循环查找,越过叶节点后跳出 while (cur != NULL) { if (cur->val < val) { // 目标节点在 cur 的右子树中 cur = cur->right; } else if (cur->val > val) { // 目标节点在 cur 的左子树中 cur = cur->left; } else { // 找到目标节点,跳出循环 break; } } // 找到目标节点,跳出循环 return cur; } void testInsert(AVLTree *tree, int val) { insert(tree, val); printf("\n插入节点 %d 后,AVL 树为 \n", val); printTree(tree->root); } void testRemove(AVLTree *tree, int val) { removeItem(tree, val); printf("\n删除节点 %d 后,AVL 树为 \n", val); printTree(tree->root); } /* Driver Code */ int main() { /* 初始化空 AVL 树 */ AVLTree *tree = (AVLTree *)newAVLTree(); /* 插入节点 */ // 请关注插入节点后,AVL 树是如何保持平衡的 testInsert(tree, 1); testInsert(tree, 2); testInsert(tree, 3); testInsert(tree, 4); testInsert(tree, 5); testInsert(tree, 8); testInsert(tree, 7); testInsert(tree, 9); testInsert(tree, 10); testInsert(tree, 6); /* 插入重复节点 */ testInsert(tree, 7); /* 删除节点 */ // 请关注删除节点后,AVL 树是如何保持平衡的 testRemove(tree, 8); // 删除度为 0 的节点 testRemove(tree, 5); // 删除度为 1 的节点 testRemove(tree, 4); // 删除度为 2 的节点 /* 查询节点 */ TreeNode *node = search(tree, 7); printf("\n查找到的节点对象节点值 = %d \n", node->val); // 释放内存 delAVLTree(tree); return 0; } ================================================ FILE: codes/c/chapter_tree/binary_search_tree.c ================================================ /** * File: binary_search_tree.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* 二叉搜索树结构体 */ typedef struct { TreeNode *root; } BinarySearchTree; /* 构造函数 */ BinarySearchTree *newBinarySearchTree() { // 初始化空树 BinarySearchTree *bst = (BinarySearchTree *)malloc(sizeof(BinarySearchTree)); bst->root = NULL; return bst; } /* 析构函数 */ void delBinarySearchTree(BinarySearchTree *bst) { freeMemoryTree(bst->root); free(bst); } /* 获取二叉树根节点 */ TreeNode *getRoot(BinarySearchTree *bst) { return bst->root; } /* 查找节点 */ TreeNode *search(BinarySearchTree *bst, int num) { TreeNode *cur = bst->root; // 循环查找,越过叶节点后跳出 while (cur != NULL) { if (cur->val < num) { // 目标节点在 cur 的右子树中 cur = cur->right; } else if (cur->val > num) { // 目标节点在 cur 的左子树中 cur = cur->left; } else { // 找到目标节点,跳出循环 break; } } // 返回目标节点 return cur; } /* 插入节点 */ void insert(BinarySearchTree *bst, int num) { // 若树为空,则初始化根节点 if (bst->root == NULL) { bst->root = newTreeNode(num); return; } TreeNode *cur = bst->root, *pre = NULL; // 循环查找,越过叶节点后跳出 while (cur != NULL) { // 找到重复节点,直接返回 if (cur->val == num) { return; } pre = cur; if (cur->val < num) { // 插入位置在 cur 的右子树中 cur = cur->right; } else { // 插入位置在 cur 的左子树中 cur = cur->left; } } // 插入节点 TreeNode *node = newTreeNode(num); if (pre->val < num) { pre->right = node; } else { pre->left = node; } } /* 删除节点 */ // 由于引入了 stdio.h ,此处无法使用 remove 关键词 void removeItem(BinarySearchTree *bst, int num) { // 若树为空,直接提前返回 if (bst->root == NULL) return; TreeNode *cur = bst->root, *pre = NULL; // 循环查找,越过叶节点后跳出 while (cur != NULL) { // 找到待删除节点,跳出循环 if (cur->val == num) break; pre = cur; if (cur->val < num) { // 待删除节点在 root 的右子树中 cur = cur->right; } else { // 待删除节点在 root 的左子树中 cur = cur->left; } } // 若无待删除节点,则直接返回 if (cur == NULL) return; // 判断待删除节点是否存在子节点 if (cur->left == NULL || cur->right == NULL) { /* 子节点数量 = 0 or 1 */ // 当子节点数量 = 0 / 1 时, child = nullptr / 该子节点 TreeNode *child = cur->left != NULL ? cur->left : cur->right; // 删除节点 cur if (pre->left == cur) { pre->left = child; } else { pre->right = child; } // 释放内存 free(cur); } else { /* 子节点数量 = 2 */ // 获取中序遍历中 cur 的下一个节点 TreeNode *tmp = cur->right; while (tmp->left != NULL) { tmp = tmp->left; } int tmpVal = tmp->val; // 递归删除节点 tmp removeItem(bst, tmp->val); // 用 tmp 覆盖 cur cur->val = tmpVal; } } /* Driver Code */ int main() { /* 初始化二叉搜索树 */ int nums[] = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; BinarySearchTree *bst = newBinarySearchTree(); for (int i = 0; i < sizeof(nums) / sizeof(int); i++) { insert(bst, nums[i]); } printf("初始化的二叉树为\n"); printTree(getRoot(bst)); /* 查找节点 */ TreeNode *node = search(bst, 7); printf("查找到的节点对象的节点值 = %d\n", node->val); /* 插入节点 */ insert(bst, 16); printf("插入节点 16 后,二叉树为\n"); printTree(getRoot(bst)); /* 删除节点 */ removeItem(bst, 1); printf("删除节点 1 后,二叉树为\n"); printTree(getRoot(bst)); removeItem(bst, 2); printf("删除节点 2 后,二叉树为\n"); printTree(getRoot(bst)); removeItem(bst, 4); printf("删除节点 4 后,二叉树为\n"); printTree(getRoot(bst)); // 释放内存 delBinarySearchTree(bst); return 0; } ================================================ FILE: codes/c/chapter_tree/binary_tree.c ================================================ /** * File: binary_tree.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Driver Code */ int main() { /* 初始化二叉树 */ // 初始化节点 TreeNode *n1 = newTreeNode(1); TreeNode *n2 = newTreeNode(2); TreeNode *n3 = newTreeNode(3); TreeNode *n4 = newTreeNode(4); TreeNode *n5 = newTreeNode(5); // 构建节点之间的引用(指针) n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; printf("初始化二叉树\n"); printTree(n1); /* 插入与删除节点 */ TreeNode *P = newTreeNode(0); // 在 n1 -> n2 中间插入节点 P n1->left = P; P->left = n2; printf("插入节点 P 后\n"); printTree(n1); // 删除节点 P n1->left = n2; // 释放内存 free(P); printf("删除节点 P 后\n"); printTree(n1); freeMemoryTree(n1); return 0; } ================================================ FILE: codes/c/chapter_tree/binary_tree_bfs.c ================================================ /** * File: binary_tree_bfs.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 100 /* 层序遍历 */ int *levelOrder(TreeNode *root, int *size) { /* 辅助队列 */ int front, rear; int index, *arr; TreeNode *node; TreeNode **queue; /* 辅助队列 */ queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE); // 队列指针 front = 0, rear = 0; // 加入根节点 queue[rear++] = root; // 初始化一个列表,用于保存遍历序列 /* 辅助数组 */ arr = (int *)malloc(sizeof(int) * MAX_SIZE); // 数组指针 index = 0; while (front < rear) { // 队列出队 node = queue[front++]; // 保存节点值 arr[index++] = node->val; if (node->left != NULL) { // 左子节点入队 queue[rear++] = node->left; } if (node->right != NULL) { // 右子节点入队 queue[rear++] = node->right; } } // 更新数组长度的值 *size = index; arr = realloc(arr, sizeof(int) * (*size)); // 释放辅助数组空间 free(queue); return arr; } /* Driver Code */ int main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 int nums[] = {1, 2, 3, 4, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); printf("初始化二叉树\n"); printTree(root); /* 层序遍历 */ // 需要传入数组的长度 int *arr = levelOrder(root, &size); printf("层序遍历的节点打印序列 = "); printArray(arr, size); // 释放内存 freeMemoryTree(root); free(arr); return 0; } ================================================ FILE: codes/c/chapter_tree/binary_tree_dfs.c ================================================ /** * File: binary_tree_dfs.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 100 // 辅助数组,用于存储遍历序列 int arr[MAX_SIZE]; /* 前序遍历 */ void preOrder(TreeNode *root, int *size) { if (root == NULL) return; // 访问优先级:根节点 -> 左子树 -> 右子树 arr[(*size)++] = root->val; preOrder(root->left, size); preOrder(root->right, size); } /* 中序遍历 */ void inOrder(TreeNode *root, int *size) { if (root == NULL) return; // 访问优先级:左子树 -> 根节点 -> 右子树 inOrder(root->left, size); arr[(*size)++] = root->val; inOrder(root->right, size); } /* 后序遍历 */ void postOrder(TreeNode *root, int *size) { if (root == NULL) return; // 访问优先级:左子树 -> 右子树 -> 根节点 postOrder(root->left, size); postOrder(root->right, size); arr[(*size)++] = root->val; } /* Driver Code */ int main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 int nums[] = {1, 2, 3, 4, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); printf("初始化二叉树\n"); printTree(root); /* 前序遍历 */ // 初始化辅助数组 size = 0; preOrder(root, &size); printf("前序遍历的节点打印序列 = "); printArray(arr, size); /* 中序遍历 */ size = 0; inOrder(root, &size); printf("中序遍历的节点打印序列 = "); printArray(arr, size); /* 后序遍历 */ size = 0; postOrder(root, &size); printf("后序遍历的节点打印序列 = "); printArray(arr, size); freeMemoryTree(root); return 0; } ================================================ FILE: codes/c/utils/CMakeLists.txt ================================================ add_executable(utils common_test.c common.h print_util.h list_node.h tree_node.h uthash.h) ================================================ FILE: codes/c/utils/common.h ================================================ /** * File: common.h * Created Time: 2022-12-20 * Author: MolDuM (moldum@163.com)、Reanon (793584285@qq.com) */ #ifndef COMMON_H #define COMMON_H #include #include #include #include #include #include #include #include "list_node.h" #include "print_util.h" #include "tree_node.h" #include "vertex.h" // hash table lib #include "uthash.h" #include "vector.h" #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif #endif // COMMON_H ================================================ FILE: codes/c/utils/common_test.c ================================================ /** * File: include_test.c * Created Time: 2023-01-10 * Author: Reanon (793584285@qq.com) */ #include "common.h" void testListNode() { int nums[] = {2, 3, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); ListNode *head = arrToLinkedList(nums, size); printLinkedList(head); } void testTreeNode() { int nums[] = {1, 2, 3, INT_MAX, 5, 6, INT_MAX}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); // print tree printTree(root); // tree to arr int *arr = treeToArray(root, &size); printArray(arr, size); } int main(int argc, char *argv[]) { printf("==testListNode==\n"); testListNode(); printf("==testTreeNode==\n"); testTreeNode(); return 0; } ================================================ FILE: codes/c/utils/list_node.h ================================================ /** * File: list_node.h * Created Time: 2023-01-09 * Author: Reanon (793584285@qq.com) */ #ifndef LIST_NODE_H #define LIST_NODE_H #ifdef __cplusplus extern "C" { #endif /* 链表节点结构体 */ typedef struct ListNode { int val; // 节点值 struct ListNode *next; // 指向下一节点的引用 } ListNode; /* 构造函数,初始化一个新节点 */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *)malloc(sizeof(ListNode)); node->val = val; node->next = NULL; return node; } /* 将数组反序列化为链表 */ ListNode *arrToLinkedList(const int *arr, size_t size) { if (size <= 0) { return NULL; } ListNode *dummy = newListNode(0); ListNode *node = dummy; for (int i = 0; i < size; i++) { node->next = newListNode(arr[i]); node = node->next; } return dummy->next; } /* 释放分配给链表的内存空间 */ void freeMemoryLinkedList(ListNode *cur) { // 释放内存 ListNode *pre; while (cur != NULL) { pre = cur; cur = cur->next; free(pre); } } #ifdef __cplusplus } #endif #endif // LIST_NODE_H ================================================ FILE: codes/c/utils/print_util.h ================================================ /** * File: print_util.h * Created Time: 2022-12-21 * Author: MolDum (moldum@163.com), Reanon (793584285@qq.com) */ #ifndef PRINT_UTIL_H #define PRINT_UTIL_H #include #include #include #include "list_node.h" #include "tree_node.h" #ifdef __cplusplus extern "C" { #endif /* 打印数组 */ void printArray(int arr[], int size) { if (arr == NULL || size == 0) { printf("[]"); return; } printf("["); for (int i = 0; i < size - 1; i++) { printf("%d, ", arr[i]); } printf("%d]\n", arr[size - 1]); } /* 打印数组 */ void printArrayFloat(float arr[], int size) { if (arr == NULL || size == 0) { printf("[]"); return; } printf("["); for (int i = 0; i < size - 1; i++) { printf("%.2f, ", arr[i]); } printf("%.2f]\n", arr[size - 1]); } /* 打印链表 */ void printLinkedList(ListNode *node) { if (node == NULL) { return; } while (node->next != NULL) { printf("%d -> ", node->val); node = node->next; } printf("%d\n", node->val); } typedef struct Trunk { struct Trunk *prev; char *str; } Trunk; Trunk *newTrunk(Trunk *prev, char *str) { Trunk *trunk = (Trunk *)malloc(sizeof(Trunk)); trunk->prev = prev; trunk->str = (char *)malloc(sizeof(char) * 10); strcpy(trunk->str, str); return trunk; } void showTrunks(Trunk *trunk) { if (trunk == NULL) { return; } showTrunks(trunk->prev); printf("%s", trunk->str); } /** * 打印二叉树 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTreeHelper(TreeNode *node, Trunk *prev, bool isRight) { if (node == NULL) { return; } char *prev_str = " "; Trunk *trunk = newTrunk(prev, prev_str); printTreeHelper(node->right, trunk, true); if (prev == NULL) { trunk->str = "———"; } else if (isRight) { trunk->str = "/———"; prev_str = " |"; } else { trunk->str = "\\———"; prev->str = prev_str; } showTrunks(trunk); printf("%d\n", node->val); if (prev != NULL) { prev->str = prev_str; } trunk->str = " |"; printTreeHelper(node->left, trunk, false); } /* 打印二叉树 */ void printTree(TreeNode *root) { printTreeHelper(root, NULL, false); } /* 打印堆 */ void printHeap(int arr[], int size) { TreeNode *root; printf("堆的数组表示:"); printArray(arr, size); printf("堆的树状表示:\n"); root = arrayToTree(arr, size); printTree(root); } #ifdef __cplusplus } #endif #endif // PRINT_UTIL_H ================================================ FILE: codes/c/utils/tree_node.h ================================================ /** * File: tree_node.h * Created Time: 2023-01-09 * Author: Reanon (793584285@qq.com) */ #ifndef TREE_NODE_H #define TREE_NODE_H #ifdef __cplusplus extern "C" { #endif #include #define MAX_NODE_SIZE 5000 /* 二叉树节点结构体 */ typedef struct TreeNode { int val; // 节点值 int height; // 节点高度 struct TreeNode *left; // 左子节点指针 struct TreeNode *right; // 右子节点指针 } TreeNode; /* 构造函数 */ TreeNode *newTreeNode(int val) { TreeNode *node; node = (TreeNode *)malloc(sizeof(TreeNode)); node->val = val; node->height = 0; node->left = NULL; node->right = NULL; return node; } // 序列化编码规则请参考: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二叉树的数组表示: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // 二叉树的链表表示: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* 将列表反序列化为二叉树:递归 */ TreeNode *arrayToTreeDFS(int *arr, int size, int i) { if (i < 0 || i >= size || arr[i] == INT_MAX) { return NULL; } TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); root->val = arr[i]; root->left = arrayToTreeDFS(arr, size, 2 * i + 1); root->right = arrayToTreeDFS(arr, size, 2 * i + 2); return root; } /* 将列表反序列化为二叉树 */ TreeNode *arrayToTree(int *arr, int size) { return arrayToTreeDFS(arr, size, 0); } /* 将二叉树序列化为列表:递归 */ void treeToArrayDFS(TreeNode *root, int i, int *res, int *size) { if (root == NULL) { return; } while (i >= *size) { res = realloc(res, (*size + 1) * sizeof(int)); res[*size] = INT_MAX; (*size)++; } res[i] = root->val; treeToArrayDFS(root->left, 2 * i + 1, res, size); treeToArrayDFS(root->right, 2 * i + 2, res, size); } /* 将二叉树序列化为列表 */ int *treeToArray(TreeNode *root, int *size) { *size = 0; int *res = NULL; treeToArrayDFS(root, 0, res, size); return res; } /* 释放二叉树内存 */ void freeMemoryTree(TreeNode *root) { if (root == NULL) return; freeMemoryTree(root->left); freeMemoryTree(root->right); free(root); } #ifdef __cplusplus } #endif #endif // TREE_NODE_H ================================================ FILE: codes/c/utils/uthash.h ================================================ /* Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTHASH_H #define UTHASH_H #define UTHASH_VERSION 2.3.0 #include /* memcmp, memset, strlen */ #include /* ptrdiff_t */ #include /* exit */ #if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT /* This codepath is provided for backward compatibility, but I plan to remove it. */ #warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" typedef unsigned int uint32_t; typedef unsigned char uint8_t; #elif defined(HASH_NO_STDINT) && HASH_NO_STDINT #else #include /* uint8_t, uint32_t */ #endif /* These macros use decltype or the earlier __typeof GNU extension. As decltype is only available in newer compilers (VS2010 or gcc 4.3+ when compiling c++ source) this code uses whatever method is needed or, for VS2008 where neither is available, uses casting workarounds. */ #if !defined(DECLTYPE) && !defined(NO_DECLTYPE) #if defined(_MSC_VER) /* MS compiler */ #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ #define DECLTYPE(x) (decltype(x)) #else /* VS2008 or older (or VS2010 in C mode) */ #define NO_DECLTYPE #endif #elif defined(__MCST__) /* Elbrus C Compiler */ #define DECLTYPE(x) (__typeof(x)) #elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) #define NO_DECLTYPE #else /* GNU, Sun and other compilers */ #define DECLTYPE(x) (__typeof(x)) #endif #endif #ifdef NO_DECLTYPE #define DECLTYPE(x) #define DECLTYPE_ASSIGN(dst,src) \ do { \ char **_da_dst = (char**)(&(dst)); \ *_da_dst = (char*)(src); \ } while (0) #else #define DECLTYPE_ASSIGN(dst,src) \ do { \ (dst) = DECLTYPE(dst)(src); \ } while (0) #endif #ifndef uthash_malloc #define uthash_malloc(sz) malloc(sz) /* malloc fcn */ #endif #ifndef uthash_free #define uthash_free(ptr,sz) free(ptr) /* free fcn */ #endif #ifndef uthash_bzero #define uthash_bzero(a,n) memset(a,'\0',n) #endif #ifndef uthash_strlen #define uthash_strlen(s) strlen(s) #endif #ifndef HASH_FUNCTION #define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) #endif #ifndef HASH_KEYCMP #define HASH_KEYCMP(a,b,n) memcmp(a,b,n) #endif #ifndef uthash_noexpand_fyi #define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ #endif #ifndef uthash_expand_fyi #define uthash_expand_fyi(tbl) /* can be defined to log expands */ #endif #ifndef HASH_NONFATAL_OOM #define HASH_NONFATAL_OOM 0 #endif #if HASH_NONFATAL_OOM /* malloc failures can be recovered from */ #ifndef uthash_nonfatal_oom #define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) #define IF_HASH_NONFATAL_OOM(x) x #else /* malloc failures result in lost memory, hash tables are unusable */ #ifndef uthash_fatal #define uthash_fatal(msg) exit(-1) /* fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") #define IF_HASH_NONFATAL_OOM(x) #endif /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ #define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ /* calculate the element whose hash handle address is hhp */ #define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) /* calculate the hash handle from element address elp */ #define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) #define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ do { \ struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ unsigned _hd_bkt; \ HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ (head)->hh.tbl->buckets[_hd_bkt].count++; \ _hd_hh_item->hh_next = NULL; \ _hd_hh_item->hh_prev = NULL; \ } while (0) #define HASH_VALUE(keyptr,keylen,hashv) \ do { \ HASH_FUNCTION(keyptr, keylen, hashv); \ } while (0) #define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_bkt; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ } \ } \ } while (0) #define HASH_FIND(hh,head,keyptr,keylen,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_hashv; \ HASH_VALUE(keyptr, keylen, _hf_hashv); \ HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ } \ } while (0) #ifdef HASH_BLOOM #define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) #define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) #define HASH_BLOOM_MAKE(tbl,oomed) \ do { \ (tbl)->bloom_nbits = HASH_BLOOM; \ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ if (!(tbl)->bloom_bv) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ } \ } while (0) #define HASH_BLOOM_FREE(tbl) \ do { \ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ } while (0) #define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) #define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) #define HASH_BLOOM_ADD(tbl,hashv) \ HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #define HASH_BLOOM_TEST(tbl,hashv) \ HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #else #define HASH_BLOOM_MAKE(tbl,oomed) #define HASH_BLOOM_FREE(tbl) #define HASH_BLOOM_ADD(tbl,hashv) #define HASH_BLOOM_TEST(tbl,hashv) (1) #define HASH_BLOOM_BYTELEN 0U #endif #define HASH_MAKE_TABLE(hh,head,oomed) \ do { \ (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ if (!(head)->hh.tbl) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ (head)->hh.tbl->tail = &((head)->hh); \ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ (head)->hh.tbl->signature = HASH_SIGNATURE; \ if (!(head)->hh.tbl->buckets) { \ HASH_RECORD_OOM(oomed); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } else { \ uthash_bzero((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ uthash_free((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } \ ) \ } \ } \ } while (0) #define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ } while (0) #define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ } while (0) #define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ } while (0) #define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ } while (0) #define HASH_APPEND_LIST(hh, head, add) \ do { \ (add)->hh.next = NULL; \ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ (head)->hh.tbl->tail->next = (add); \ (head)->hh.tbl->tail = &((add)->hh); \ } while (0) #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ do { \ if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ break; \ } \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #ifdef NO_DECLTYPE #undef HASH_AKBI_INNER_LOOP #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ char *_hs_saved_head = (char*)(head); \ do { \ DECLTYPE_ASSIGN(head, _hs_iter); \ if (cmpfcn(head, add) > 0) { \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ break; \ } \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #endif #if HASH_NONFATAL_OOM #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ if (!(oomed)) { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ if (oomed) { \ HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ HASH_DELETE_HH(hh, head, &(add)->hh); \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } else { \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } \ } else { \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } \ } while (0) #else #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } while (0) #endif #define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (char*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ void *_hs_iter = (head); \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ if (_hs_iter) { \ (add)->hh.next = _hs_iter; \ if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ } else { \ (head) = (add); \ } \ HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ } else { \ HASH_APPEND_LIST(hh, head, add); \ } \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ } while (0) #define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ do { \ unsigned _hs_hashv; \ HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ } while (0) #define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) #define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) #define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (const void*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_APPEND_LIST(hh, head, add); \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ } while (0) #define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ do { \ unsigned _ha_hashv; \ HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ } while (0) #define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) #define HASH_ADD(hh,head,fieldname,keylen_in,add) \ HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) #define HASH_TO_BKT(hashv,num_bkts,bkt) \ do { \ bkt = ((hashv) & ((num_bkts) - 1U)); \ } while (0) /* delete "delptr" from the hash table. * "the usual" patch-up process for the app-order doubly-linked-list. * The use of _hd_hh_del below deserves special explanation. * These used to be expressed using (delptr) but that led to a bug * if someone used the same symbol for the head and deletee, like * HASH_DELETE(hh,users,users); * We want that to work, but by changing the head (users) below * we were forfeiting our ability to further refer to the deletee (users) * in the patch-up process. Solution: use scratch space to * copy the deletee pointer, then the latter references are via that * scratch pointer rather than through the repointed (users) symbol. */ #define HASH_DELETE(hh,head,delptr) \ HASH_DELETE_HH(hh, head, &(delptr)->hh) #define HASH_DELETE_HH(hh,head,delptrhh) \ do { \ const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } else { \ unsigned _hd_bkt; \ if (_hd_hh_del == (head)->hh.tbl->tail) { \ (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ } \ if (_hd_hh_del->prev != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ } else { \ DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ } \ if (_hd_hh_del->next != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ } \ HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ (head)->hh.tbl->num_items--; \ } \ HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ } while (0) /* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ #define HASH_FIND_STR(head,findstr,out) \ do { \ unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ } while (0) #define HASH_ADD_STR(head,strfield,add) \ do { \ unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ } while (0) #define HASH_REPLACE_STR(head,strfield,add,replaced) \ do { \ unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ } while (0) #define HASH_FIND_INT(head,findint,out) \ HASH_FIND(hh,head,findint,sizeof(int),out) #define HASH_ADD_INT(head,intfield,add) \ HASH_ADD(hh,head,intfield,sizeof(int),add) #define HASH_REPLACE_INT(head,intfield,add,replaced) \ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) #define HASH_FIND_PTR(head,findptr,out) \ HASH_FIND(hh,head,findptr,sizeof(void *),out) #define HASH_ADD_PTR(head,ptrfield,add) \ HASH_ADD(hh,head,ptrfield,sizeof(void *),add) #define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) #define HASH_DEL(head,delptr) \ HASH_DELETE(hh,head,delptr) /* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. */ #ifdef HASH_DEBUG #include /* fprintf, stderr */ #define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) #define HASH_FSCK(hh,head,where) \ do { \ struct UT_hash_handle *_thh; \ if (head) { \ unsigned _bkt_i; \ unsigned _count = 0; \ char *_prev; \ for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ unsigned _bkt_count = 0; \ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ _prev = NULL; \ while (_thh) { \ if (_prev != (char*)(_thh->hh_prev)) { \ HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ (where), (void*)_thh->hh_prev, (void*)_prev); \ } \ _bkt_count++; \ _prev = (char*)(_thh); \ _thh = _thh->hh_next; \ } \ _count += _bkt_count; \ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ } \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ _count = 0; \ _prev = NULL; \ _thh = &(head)->hh; \ while (_thh) { \ _count++; \ if (_prev != (char*)_thh->prev) { \ HASH_OOPS("%s: invalid prev %p, actual %p\n", \ (where), (void*)_thh->prev, (void*)_prev); \ } \ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ } \ } while (0) #else #define HASH_FSCK(hh,head,where) #endif /* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to * the descriptor to which this macro is defined for tuning the hash function. * The app can #include to get the prototype for write(2). */ #ifdef HASH_EMIT_KEYS #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ do { \ unsigned _klen = fieldlen; \ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ } while (0) #else #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) #endif /* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ #define HASH_BER(key,keylen,hashv) \ do { \ unsigned _hb_keylen = (unsigned)keylen; \ const unsigned char *_hb_key = (const unsigned char*)(key); \ (hashv) = 0; \ while (_hb_keylen-- != 0U) { \ (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ } \ } while (0) /* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx * (archive link: https://archive.is/Ivcan ) */ #define HASH_SAX(key,keylen,hashv) \ do { \ unsigned _sx_i; \ const unsigned char *_hs_key = (const unsigned char*)(key); \ hashv = 0; \ for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ } \ } while (0) /* FNV-1a variation */ #define HASH_FNV(key,keylen,hashv) \ do { \ unsigned _fn_i; \ const unsigned char *_hf_key = (const unsigned char*)(key); \ (hashv) = 2166136261U; \ for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ hashv = hashv ^ _hf_key[_fn_i]; \ hashv = hashv * 16777619U; \ } \ } while (0) #define HASH_OAT(key,keylen,hashv) \ do { \ unsigned _ho_i; \ const unsigned char *_ho_key=(const unsigned char*)(key); \ hashv = 0; \ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ hashv += _ho_key[_ho_i]; \ hashv += (hashv << 10); \ hashv ^= (hashv >> 6); \ } \ hashv += (hashv << 3); \ hashv ^= (hashv >> 11); \ hashv += (hashv << 15); \ } while (0) #define HASH_JEN_MIX(a,b,c) \ do { \ a -= b; a -= c; a ^= ( c >> 13 ); \ b -= c; b -= a; b ^= ( a << 8 ); \ c -= a; c -= b; c ^= ( b >> 13 ); \ a -= b; a -= c; a ^= ( c >> 12 ); \ b -= c; b -= a; b ^= ( a << 16 ); \ c -= a; c -= b; c ^= ( b >> 5 ); \ a -= b; a -= c; a ^= ( c >> 3 ); \ b -= c; b -= a; b ^= ( a << 10 ); \ c -= a; c -= b; c ^= ( b >> 15 ); \ } while (0) #define HASH_JEN(key,keylen,hashv) \ do { \ unsigned _hj_i,_hj_j,_hj_k; \ unsigned const char *_hj_key=(unsigned const char*)(key); \ hashv = 0xfeedbeefu; \ _hj_i = _hj_j = 0x9e3779b9u; \ _hj_k = (unsigned)(keylen); \ while (_hj_k >= 12U) { \ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + ( (unsigned)_hj_key[2] << 16 ) \ + ( (unsigned)_hj_key[3] << 24 ) ); \ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + ( (unsigned)_hj_key[6] << 16 ) \ + ( (unsigned)_hj_key[7] << 24 ) ); \ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + ( (unsigned)_hj_key[10] << 16 ) \ + ( (unsigned)_hj_key[11] << 24 ) ); \ \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ \ _hj_key += 12; \ _hj_k -= 12U; \ } \ hashv += (unsigned)(keylen); \ switch ( _hj_k ) { \ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ default: ; \ } \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ } while (0) /* The Paul Hsieh hash function */ #undef get16bits #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) #define get16bits(d) (*((const uint16_t *) (d))) #endif #if !defined (get16bits) #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif #define HASH_SFH(key,keylen,hashv) \ do { \ unsigned const char *_sfh_key=(unsigned const char*)(key); \ uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ \ unsigned _sfh_rem = _sfh_len & 3U; \ _sfh_len >>= 2; \ hashv = 0xcafebabeu; \ \ /* Main loop */ \ for (;_sfh_len > 0U; _sfh_len--) { \ hashv += get16bits (_sfh_key); \ _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ hashv = (hashv << 16) ^ _sfh_tmp; \ _sfh_key += 2U*sizeof (uint16_t); \ hashv += hashv >> 11; \ } \ \ /* Handle end cases */ \ switch (_sfh_rem) { \ case 3: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 16; \ hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ hashv += hashv >> 11; \ break; \ case 2: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 11; \ hashv += hashv >> 17; \ break; \ case 1: hashv += *_sfh_key; \ hashv ^= hashv << 10; \ hashv += hashv >> 1; \ break; \ default: ; \ } \ \ /* Force "avalanching" of final 127 bits */ \ hashv ^= hashv << 3; \ hashv += hashv >> 5; \ hashv ^= hashv << 4; \ hashv += hashv >> 17; \ hashv ^= hashv << 25; \ hashv += hashv >> 6; \ } while (0) /* iterate over items in a known bucket to find desired item */ #define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ do { \ if ((head).hh_head != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ } else { \ (out) = NULL; \ } \ while ((out) != NULL) { \ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ break; \ } \ } \ if ((out)->hh.hh_next != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ } else { \ (out) = NULL; \ } \ } \ } while (0) /* add an item to a bucket */ #define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ do { \ UT_hash_bucket *_ha_head = &(head); \ _ha_head->count++; \ (addhh)->hh_next = _ha_head->hh_head; \ (addhh)->hh_prev = NULL; \ if (_ha_head->hh_head != NULL) { \ _ha_head->hh_head->hh_prev = (addhh); \ } \ _ha_head->hh_head = (addhh); \ if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ && !(addhh)->tbl->noexpand) { \ HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ HASH_DEL_IN_BKT(head,addhh); \ } \ ) \ } \ } while (0) /* remove an item from a given bucket */ #define HASH_DEL_IN_BKT(head,delhh) \ do { \ UT_hash_bucket *_hd_head = &(head); \ _hd_head->count--; \ if (_hd_head->hh_head == (delhh)) { \ _hd_head->hh_head = (delhh)->hh_next; \ } \ if ((delhh)->hh_prev) { \ (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ } \ if ((delhh)->hh_next) { \ (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ } \ } while (0) /* Bucket expansion has the effect of doubling the number of buckets * and redistributing the items into the new buckets. Ideally the * items will distribute more or less evenly into the new buckets * (the extent to which this is true is a measure of the quality of * the hash function as it applies to the key domain). * * With the items distributed into more buckets, the chain length * (item count) in each bucket is reduced. Thus by expanding buckets * the hash keeps a bound on the chain length. This bounded chain * length is the essence of how a hash provides constant time lookup. * * The calculation of tbl->ideal_chain_maxlen below deserves some * explanation. First, keep in mind that we're calculating the ideal * maximum chain length based on the *new* (doubled) bucket count. * In fractions this is just n/b (n=number of items,b=new num buckets). * Since the ideal chain length is an integer, we want to calculate * ceil(n/b). We don't depend on floating point arithmetic in this * hash, so to calculate ceil(n/b) with integers we could write * * ceil(n/b) = (n/b) + ((n%b)?1:0) * * and in fact a previous version of this hash did just that. * But now we have improved things a bit by recognizing that b is * always a power of two. We keep its base 2 log handy (call it lb), * so now we can write this with a bit shift and logical AND: * * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) * */ #define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ do { \ unsigned _he_bkt; \ unsigned _he_bkt_i; \ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ if (!_he_new_buckets) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero(_he_new_buckets, \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ (tbl)->ideal_chain_maxlen = \ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ (tbl)->nonideal_items = 0; \ for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ while (_he_thh != NULL) { \ _he_hh_nxt = _he_thh->hh_next; \ HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ _he_newbkt = &(_he_new_buckets[_he_bkt]); \ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ (tbl)->nonideal_items++; \ if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ _he_newbkt->expand_mult++; \ } \ } \ _he_thh->hh_prev = NULL; \ _he_thh->hh_next = _he_newbkt->hh_head; \ if (_he_newbkt->hh_head != NULL) { \ _he_newbkt->hh_head->hh_prev = _he_thh; \ } \ _he_newbkt->hh_head = _he_thh; \ _he_thh = _he_hh_nxt; \ } \ } \ uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ (tbl)->num_buckets *= 2U; \ (tbl)->log2_num_buckets++; \ (tbl)->buckets = _he_new_buckets; \ (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ ((tbl)->ineff_expands+1U) : 0U; \ if ((tbl)->ineff_expands > 1U) { \ (tbl)->noexpand = 1; \ uthash_noexpand_fyi(tbl); \ } \ uthash_expand_fyi(tbl); \ } \ } while (0) /* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ /* Note that HASH_SORT assumes the hash handle name to be hh. * HASH_SRT was added to allow the hash handle name to be passed in. */ #define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) #define HASH_SRT(hh,head,cmpfcn) \ do { \ unsigned _hs_i; \ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ if (head != NULL) { \ _hs_insize = 1; \ _hs_looping = 1; \ _hs_list = &((head)->hh); \ while (_hs_looping != 0U) { \ _hs_p = _hs_list; \ _hs_list = NULL; \ _hs_tail = NULL; \ _hs_nmerges = 0; \ while (_hs_p != NULL) { \ _hs_nmerges++; \ _hs_q = _hs_p; \ _hs_psize = 0; \ for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ _hs_psize++; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ if (_hs_q == NULL) { \ break; \ } \ } \ _hs_qsize = _hs_insize; \ while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ if (_hs_psize == 0U) { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else if ((cmpfcn( \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ )) <= 0) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } \ if ( _hs_tail != NULL ) { \ _hs_tail->next = ((_hs_e != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ } else { \ _hs_list = _hs_e; \ } \ if (_hs_e != NULL) { \ _hs_e->prev = ((_hs_tail != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ } \ _hs_tail = _hs_e; \ } \ _hs_p = _hs_q; \ } \ if (_hs_tail != NULL) { \ _hs_tail->next = NULL; \ } \ if (_hs_nmerges <= 1U) { \ _hs_looping = 0; \ (head)->hh.tbl->tail = _hs_tail; \ DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ } \ _hs_insize *= 2U; \ } \ HASH_FSCK(hh, head, "HASH_SRT"); \ } \ } while (0) /* This function selects items from one hash into another hash. * The end result is that the selected items have dual presence * in both hashes. There is no copy of the items made; rather * they are added into the new hash through a secondary hash * hash handle that must be present in the structure. */ #define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ do { \ unsigned _src_bkt, _dst_bkt; \ void *_last_elt = NULL, *_elt; \ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ if ((src) != NULL) { \ for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ _src_hh != NULL; \ _src_hh = _src_hh->hh_next) { \ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ if (cond(_elt)) { \ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ _dst_hh->key = _src_hh->key; \ _dst_hh->keylen = _src_hh->keylen; \ _dst_hh->hashv = _src_hh->hashv; \ _dst_hh->prev = _last_elt; \ _dst_hh->next = NULL; \ if (_last_elt_hh != NULL) { \ _last_elt_hh->next = _elt; \ } \ if ((dst) == NULL) { \ DECLTYPE_ASSIGN(dst, _elt); \ HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ uthash_nonfatal_oom(_elt); \ (dst) = NULL; \ continue; \ } \ ) \ } else { \ _dst_hh->tbl = (dst)->hh_dst.tbl; \ } \ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ (dst)->hh_dst.tbl->num_items++; \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ _dst_hh->tbl = NULL; \ uthash_nonfatal_oom(_elt); \ continue; \ } \ ) \ HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ _last_elt = _elt; \ _last_elt_hh = _dst_hh; \ } \ } \ } \ } \ HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ } while (0) #define HASH_CLEAR(hh,head) \ do { \ if ((head) != NULL) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } \ } while (0) #define HASH_OVERHEAD(hh,head) \ (((head) != NULL) ? ( \ (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ sizeof(UT_hash_table) + \ (HASH_BLOOM_BYTELEN))) : 0U) #ifdef NO_DECLTYPE #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) #else #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) #endif /* obtain a count of items in the hash */ #define HASH_COUNT(head) HASH_CNT(hh,head) #define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) typedef struct UT_hash_bucket { struct UT_hash_handle *hh_head; unsigned count; /* expand_mult is normally set to 0. In this situation, the max chain length * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If * the bucket's chain exceeds this length, bucket expansion is triggered). * However, setting expand_mult to a non-zero value delays bucket expansion * (that would be triggered by additions to this particular bucket) * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. * (The multiplier is simply expand_mult+1). The whole idea of this * multiplier is to reduce bucket expansions, since they are expensive, in * situations where we know that a particular bucket tends to be overused. * It is better to let its chain length grow to a longer yet-still-bounded * value, than to do an O(n) bucket expansion too often. */ unsigned expand_mult; } UT_hash_bucket; /* random signature used only to find hash tables in external analysis */ #define HASH_SIGNATURE 0xa0111fe1u #define HASH_BLOOM_SIGNATURE 0xb12220f2u typedef struct UT_hash_table { UT_hash_bucket *buckets; unsigned num_buckets, log2_num_buckets; unsigned num_items; struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ /* in an ideal situation (all buckets used equally), no bucket would have * more than ceil(#items/#buckets) items. that's the ideal chain length. */ unsigned ideal_chain_maxlen; /* nonideal_items is the number of items in the hash whose chain position * exceeds the ideal chain maxlen. these items pay the penalty for an uneven * hash distribution; reaching them in a chain traversal takes >ideal steps */ unsigned nonideal_items; /* ineffective expands occur when a bucket doubling was performed, but * afterward, more than half the items in the hash had nonideal chain * positions. If this happens on two consecutive expansions we inhibit any * further expansion, as it's not helping; this happens when the hash * function isn't a good fit for the key domain. When expansion is inhibited * the hash will still work, albeit no longer in constant time. */ unsigned ineff_expands, noexpand; uint32_t signature; /* used only to find hash tables in external analysis */ #ifdef HASH_BLOOM uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ uint8_t *bloom_bv; uint8_t bloom_nbits; #endif } UT_hash_table; typedef struct UT_hash_handle { struct UT_hash_table *tbl; void *prev; /* prev element in app order */ void *next; /* next element in app order */ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ struct UT_hash_handle *hh_next; /* next hh in bucket order */ const void *key; /* ptr to enclosing struct's key */ unsigned keylen; /* enclosing struct's key len */ unsigned hashv; /* result of hash-fcn(key) */ } UT_hash_handle; #endif /* UTHASH_H */ ================================================ FILE: codes/c/utils/vector.h ================================================ /** * File: vector.h * Created Time: 2023-07-13 * Author: Zuoxun (845242523@qq.com)、Gonglja (glj0@outlook.com) */ #ifndef VECTOR_H #define VECTOR_H #ifdef __cplusplus extern "C" { #endif /* 定义向量类型 */ typedef struct vector { int size; // 当前向量的大小 int capacity; // 当前向量的容量 int depth; // 当前向量的深度 void **data; // 指向数据的指针数组 } vector; /* 构造向量 */ vector *newVector() { vector *v = malloc(sizeof(vector)); v->size = 0; v->capacity = 4; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); return v; } /* 构造向量,指定大小、元素默认值 */ vector *_newVector(int size, void *elem, int elemSize) { vector *v = malloc(sizeof(vector)); v->size = size; v->capacity = size; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); for (int i = 0; i < size; i++) { void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[i] = tmp; } return v; } /* 析构向量 */ void delVector(vector *v) { if (v) { if (v->depth == 0) { return; } else if (v->depth == 1) { for (int i = 0; i < v->size; i++) { free(v->data[i]); } free(v); } else { for (int i = 0; i < v->size; i++) { delVector(v->data[i]); } v->depth--; } } } /* 添加元素(拷贝方式)到向量尾部 */ void vectorPushback(vector *v, void *elem, int elemSize) { if (v->size == v->capacity) { v->capacity *= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[v->size++] = tmp; } /* 从向量尾部弹出元素 */ void vectorPopback(vector *v) { if (v->size != 0) { free(v->data[v->size - 1]); v->size--; } } /* 清空向量 */ void vectorClear(vector *v) { delVector(v); v->size = 0; v->capacity = 4; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); } /* 获取向量的大小 */ int vectorSize(vector *v) { return v->size; } /* 获取向量的尾元素 */ void *vectorBack(vector *v) { int n = v->size; return n > 0 ? v->data[n - 1] : NULL; } /* 获取向量的头元素 */ void *vectorFront(vector *v) { return v->size > 0 ? v->data[0] : NULL; } /* 获取向量下标 pos 的元素 */ void *vectorAt(vector *v, int pos) { if (pos < 0 || pos >= v->size) { printf("vectorAt: out of range\n"); return NULL; } return v->data[pos]; } /* 设置向量下标 pos 的元素 */ void vectorSet(vector *v, int pos, void *elem, int elemSize) { if (pos < 0 || pos >= v->size) { printf("vectorSet: out of range\n"); return; } free(v->data[pos]); void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[pos] = tmp; } /* 向量扩容 */ void vectorExpand(vector *v) { v->capacity *= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } /* 向量缩容 */ void vectorShrink(vector *v) { v->capacity /= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } /* 在向量下标 pos 处插入元素 */ void vectorInsert(vector *v, int pos, void *elem, int elemSize) { if (v->size == v->capacity) { vectorExpand(v); } for (int j = v->size; j > pos; j--) { v->data[j] = v->data[j - 1]; } void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[pos] = tmp; v->size++; } /* 删除向量下标 pos 处的元素 */ void vectorErase(vector *v, int pos) { if (v->size != 0) { free(v->data[pos]); for (int j = pos; j < v->size - 1; j++) { v->data[j] = v->data[j + 1]; } v->size--; } } /* 向量交换元素 */ void vectorSwap(vector *v, int i, int j) { void *tmp = v->data[i]; v->data[i] = v->data[j]; v->data[j] = tmp; } /* 向量是否为空 */ bool vectorEmpty(vector *v) { return v->size == 0; } /* 向量是否已满 */ bool vectorFull(vector *v) { return v->size == v->capacity; } /* 向量是否相等 */ bool vectorEqual(vector *v1, vector *v2) { if (v1->size != v2->size) { printf("size not equal\n"); return false; } for (int i = 0; i < v1->size; i++) { void *a = v1->data[i]; void *b = v2->data[i]; if (memcmp(a, b, sizeof(a)) != 0) { printf("data %d not equal\n", i); return false; } } return true; } /* 对向量内部进行排序 */ void vectorSort(vector *v, int (*cmp)(const void *, const void *)) { qsort(v->data, v->size, sizeof(void *), cmp); } /* 打印函数, 需传递一个打印变量的函数进来 */ /* 当前仅支持打印深度为 1 的 vector */ void printVector(vector *v, void (*printFunc)(vector *v, void *p)) { if (v) { if (v->depth == 0) { return; } else if (v->depth == 1) { if(v->size == 0) { printf("\n"); return; } for (int i = 0; i < v->size; i++) { if (i == 0) { printf("["); } else if (i == v->size - 1) { printFunc(v, v->data[i]); printf("]\r\n"); break; } printFunc(v, v->data[i]); printf(","); } } else { for (int i = 0; i < v->size; i++) { printVector(v->data[i], printFunc); } v->depth--; } } } /* 当前仅支持打印深度为 2 的 vector */ void printVectorMatrix(vector *vv, void (*printFunc)(vector *v, void *p)) { printf("[\n"); for (int i = 0; i < vv->size; i++) { vector *v = (vector *)vv->data[i]; printf(" ["); for (int j = 0; j < v->size; j++) { printFunc(v, v->data[j]); if (j != v->size - 1) printf(","); } printf("],"); printf("\n"); } printf("]\n"); } #ifdef __cplusplus } #endif #endif // VECTOR_H ================================================ FILE: codes/c/utils/vertex.h ================================================ /** * File: vertex.h * Created Time: 2023-10-28 * Author: krahets (krahets@163.com) */ #ifndef VERTEX_H #define VERTEX_H #ifdef __cplusplus extern "C" { #endif /* 顶点结构体 */ typedef struct { int val; } Vertex; /* 构造函数,初始化一个新节点 */ Vertex *newVertex(int val) { Vertex *vet; vet = (Vertex *)malloc(sizeof(Vertex)); vet->val = val; return vet; } /* 将值数组转换为顶点数组 */ Vertex **valsToVets(int *vals, int size) { Vertex **vertices = (Vertex **)malloc(size * sizeof(Vertex *)); for (int i = 0; i < size; ++i) { vertices[i] = newVertex(vals[i]); } return vertices; } /* 将顶点数组转换为值数组 */ int *vetsToVals(Vertex **vertices, int size) { int *vals = (int *)malloc(size * sizeof(int)); for (int i = 0; i < size; ++i) { vals[i] = vertices[i]->val; } return vals; } #ifdef __cplusplus } #endif #endif // VERTEX_H ================================================ FILE: codes/cpp/.gitignore ================================================ # Ignore all * # Unignore all with extensions !*.* # Unignore all dirs !*/ *.dSYM/ build/ ================================================ FILE: codes/cpp/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(hello_algo CXX) set(CMAKE_CXX_STANDARD 11) include_directories(./include) add_subdirectory(chapter_computational_complexity) add_subdirectory(chapter_array_and_linkedlist) add_subdirectory(chapter_stack_and_queue) add_subdirectory(chapter_hashing) add_subdirectory(chapter_tree) add_subdirectory(chapter_heap) add_subdirectory(chapter_graph) add_subdirectory(chapter_searching) add_subdirectory(chapter_sorting) add_subdirectory(chapter_divide_and_conquer) add_subdirectory(chapter_backtracking) add_subdirectory(chapter_dynamic_programming) add_subdirectory(chapter_greedy) ================================================ FILE: codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt ================================================ add_executable(array array.cpp) add_executable(linked_list linked_list.cpp) add_executable(list list.cpp) add_executable(my_list my_list.cpp) ================================================ FILE: codes/cpp/chapter_array_and_linkedlist/array.cpp ================================================ /** * 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 以及之后的所有元素向后移动一位 for (int i = size - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 将 num 赋给 index 处的元素 nums[index] = num; } /* 删除索引 index 处的元素 */ void remove(int *nums, int size, int index) { // 把索引 index 之后的所有元素向前移动一位 for (int i = index; i < size - 1; i++) { nums[i] = nums[i + 1]; } } /* 遍历数组 */ void traverse(int *nums, int size) { int count = 0; // 通过索引遍历数组 for (int i = 0; i < size; i++) { count += nums[i]; } } /* 在数组中查找指定元素 */ int find(int *nums, int size, int target) { for (int i = 0; i < size; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ int main() { /* 初始化数组 */ int size = 5; int *arr = new int[size]; cout << "数组 arr = "; printArray(arr, size); int *nums = new int[size]{1, 3, 2, 5, 4}; cout << "数组 nums = "; printArray(nums, size); /* 随机访问 */ int randomNum = randomAccess(nums, size); cout << "在 nums 中获取随机元素 " << randomNum << endl; /* 长度扩展 */ int enlarge = 3; nums = extend(nums, size, enlarge); size += enlarge; cout << "将数组长度扩展至 8 ,得到 nums = "; printArray(nums, size); /* 插入元素 */ insert(nums, size, 6, 3); cout << "在索引 3 处插入数字 6 ,得到 nums = "; printArray(nums, size); /* 删除元素 */ remove(nums, size, 2); cout << "删除索引 2 处的元素,得到 nums = "; printArray(nums, size); /* 遍历数组 */ traverse(nums, size); /* 查找元素 */ int index = find(nums, size, 3); cout << "在 nums 中查找元素 3 ,得到索引 = " << index << endl; // 释放内存 delete[] arr; delete[] nums; return 0; } ================================================ FILE: codes/cpp/chapter_array_and_linkedlist/linked_list.cpp ================================================ /** * 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; } /* Driver Code */ 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 << "初始化的链表为" << endl; printLinkedList(n0); /* 插入节点 */ insert(n0, new ListNode(0)); cout << "插入节点后的链表为" << endl; printLinkedList(n0); /* 删除节点 */ remove(n0); cout << "删除节点后的链表为" << endl; printLinkedList(n0); /* 访问节点 */ ListNode *node = access(n0, 3); cout << "链表中索引 3 处的节点的值 = " << node->val << endl; /* 查找节点 */ int index = find(n0, 2); cout << "链表中值为 2 的节点的索引 = " << index << endl; // 释放内存 freeMemoryLinkedList(n0); return 0; } ================================================ FILE: codes/cpp/chapter_array_and_linkedlist/list.cpp ================================================ /** * File: list.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* 初始化列表 */ vector nums = {1, 3, 2, 5, 4}; cout << "列表 nums = "; printVector(nums); /* 访问元素 */ int num = nums[1]; cout << "访问索引 1 处的元素,得到 num = " << num << endl; /* 更新元素 */ nums[1] = 0; cout << "将索引 1 处的元素更新为 0 ,得到 nums = "; printVector(nums); /* 清空列表 */ nums.clear(); cout << "清空列表后 nums = "; printVector(nums); /* 在尾部添加元素 */ nums.push_back(1); nums.push_back(3); nums.push_back(2); nums.push_back(5); nums.push_back(4); cout << "添加元素后 nums = "; printVector(nums); /* 在中间插入元素 */ nums.insert(nums.begin() + 3, 6); cout << "在索引 3 处插入数字 6 ,得到 nums = "; printVector(nums); /* 删除元素 */ nums.erase(nums.begin() + 3); cout << "删除索引 3 处的元素,得到 nums = "; printVector(nums); /* 通过索引遍历列表 */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums[i]; } /* 直接遍历列表元素 */ count = 0; for (int x : nums) { count += x; } /* 拼接两个列表 */ vector nums1 = {6, 8, 7, 10, 9}; nums.insert(nums.end(), nums1.begin(), nums1.end()); cout << "将列表 nums1 拼接到 nums 之后,得到 nums = "; printVector(nums); /* 排序列表 */ sort(nums.begin(), nums.end()); cout << "排序列表后 nums = "; printVector(nums); return 0; } ================================================ FILE: codes/cpp/chapter_array_and_linkedlist/my_list.cpp ================================================ /** * 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("索引越界"); return arr[index]; } /* 更新元素 */ void set(int index, int num) { if (index < 0 || index >= size()) throw out_of_range("索引越界"); 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("索引越界"); // 元素数量超出容量时,触发扩容机制 if (size() == capacity()) extendCapacity(); // 将索引 index 以及之后的元素都向后移动一位 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("索引越界"); int num = arr[index]; // 将索引 index 之后的元素都向前移动一位 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; } }; /* Driver Code */ int main() { /* 初始化列表 */ MyList *nums = new MyList(); /* 在尾部添加元素 */ nums->add(1); nums->add(3); nums->add(2); nums->add(5); nums->add(4); cout << "列表 nums = "; vector vec = nums->toVector(); printVector(vec); cout << "容量 = " << nums->capacity() << " ,长度 = " << nums->size() << endl; /* 在中间插入元素 */ nums->insert(3, 6); cout << "在索引 3 处插入数字 6 ,得到 nums = "; vec = nums->toVector(); printVector(vec); /* 删除元素 */ nums->remove(3); cout << "删除索引 3 处的元素,得到 nums = "; vec = nums->toVector(); printVector(vec); /* 访问元素 */ int num = nums->get(1); cout << "访问索引 1 处的元素,得到 num = " << num << endl; /* 更新元素 */ nums->set(1, 0); cout << "将索引 1 处的元素更新为 0 ,得到 nums = "; vec = nums->toVector(); printVector(vec); /* 测试扩容机制 */ for (int i = 0; i < 10; i++) { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 nums->add(i); } cout << "扩容后的列表 nums = "; vec = nums->toVector(); printVector(vec); cout << "容量 = " << nums->capacity() << " ,长度 = " << nums->size() << endl; // 释放内存 delete nums; return 0; } ================================================ FILE: codes/cpp/chapter_backtracking/CMakeLists.txt ================================================ add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.cpp) add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.cpp) add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.cpp) add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.cpp) add_executable(permutations_i permutations_i.cpp) add_executable(permutations_ii permutations_ii.cpp) add_executable(n_queens n_queens.cpp) add_executable(subset_sum_i_naive subset_sum_i_naive.cpp) add_executable(subset_sum_i subset_sum_i.cpp) add_executable(subset_sum_ii subset_sum_ii.cpp) ================================================ FILE: codes/cpp/chapter_backtracking/n_queens.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_backtracking/permutations_i.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector nums = {1, 2, 3}; vector> res = permutationsI(nums); cout << "输入数组 nums = "; printVector(nums); cout << "所有排列 res = "; printVectorMatrix(res); return 0; } ================================================ FILE: codes/cpp/chapter_backtracking/permutations_ii.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector nums = {1, 1, 2}; vector> res = permutationsII(nums); cout << "输入数组 nums = "; printVector(nums); cout << "所有排列 res = "; printVectorMatrix(res); return 0; } ================================================ FILE: codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp ================================================ /** * File: preorder_traversal_i_compact.cpp * Created Time: 2023-04-16 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" vector res; /* 前序遍历:例题一 */ void preOrder(TreeNode *root) { if (root == nullptr) { return; } if (root->val == 7) { // 记录解 res.push_back(root); } preOrder(root->left); preOrder(root->right); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n初始化二叉树" << endl; printTree(root); // 前序遍历 preOrder(root); cout << "\n输出所有值为 7 的节点" << endl; vector vals; for (TreeNode *node : res) { vals.push_back(node->val); } printVector(vals); } ================================================ FILE: codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp ================================================ /** * File: preorder_traversal_ii_compact.cpp * Created Time: 2023-04-16 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" vector path; vector> res; /* 前序遍历:例题二 */ 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(); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n初始化二叉树" << endl; printTree(root); // 前序遍历 preOrder(root); cout << "\n输出所有根节点到节点 7 的路径" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp ================================================ /** * File: preorder_traversal_iii_compact.cpp * Created Time: 2023-04-16 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" vector path; vector> res; /* 前序遍历:例题三 */ 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(); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n初始化二叉树" << endl; printTree(root); // 前序遍历 preOrder(root); cout << "\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp ================================================ /** * 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(); } /* 回溯算法:例题三 */ void backtrack(vector &state, vector &choices, vector> &res) { // 检查是否为解 if (isSolution(state)) { // 记录解 recordSolution(state, res); } // 遍历所有选择 for (TreeNode *choice : choices) { // 剪枝:检查选择是否合法 if (isValid(state, choice)) { // 尝试:做出选择,更新状态 makeChoice(state, choice); // 进行下一轮选择 vector nextChoices{choice->left, choice->right}; backtrack(state, nextChoices, res); // 回退:撤销选择,恢复到之前的状态 undoChoice(state, choice); } } } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n初始化二叉树" << endl; printTree(root); // 回溯算法 vector state; vector choices = {root}; vector> res; backtrack(state, choices, res); cout << "\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: codes/cpp/chapter_backtracking/subset_sum_i.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp ================================================ /** * 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 (size_t 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; } /* Driver Code */ 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); return 0; } ================================================ FILE: codes/cpp/chapter_backtracking/subset_sum_ii.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_computational_complexity/CMakeLists.txt ================================================ add_executable(iteration iteration.cpp) add_executable(recursion recursion.cpp) add_executable(space_complexity space_complexity.cpp) add_executable(time_complexity time_complexity.cpp) add_executable(worst_best_time_complexity worst_best_time_complexity.cpp) ================================================ FILE: codes/cpp/chapter_computational_complexity/iteration.cpp ================================================ /** * 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 循环(两次更新) */ int whileLoopII(int n) { int res = 0; int i = 1; // 初始化条件变量 // 循环求和 1, 4, 10, ... while (i <= n) { res += i; // 更新条件变量 i++; i *= 2; } return res; } /* 双层 for 循环 */ string nestedForLoop(int n) { 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(); } /* Driver Code */ 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 循环(两次更新)求和结果 res = " << res << endl; string resStr = nestedForLoop(n); cout << "\n双层 for 循环的遍历结果 " << resStr << endl; return 0; } ================================================ FILE: codes/cpp/chapter_computational_complexity/recursion.cpp ================================================ /** * 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; } /* Driver Code */ 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斐波那契数列的第 " << n << " 项为 " << res << endl; return 0; } ================================================ FILE: codes/cpp/chapter_computational_complexity/space_complexity.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int n = 5; // 常数阶 constant(n); // 线性阶 linear(n); linearRecur(n); // 平方阶 quadratic(n); quadraticRecur(n); // 指数阶 TreeNode *root = buildTree(n); printTree(root); // 释放内存 freeMemoryTree(root); return 0; } ================================================ FILE: codes/cpp/chapter_computational_complexity/time_complexity.cpp ================================================ /** * 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; // 细胞每轮一分为二,形成数列 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; } /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp ================================================ /** * 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; } /* Driver Code */ int main() { for (int i = 0; i < 1000; i++) { int n = 100; vector nums = randomNumbers(n); int index = findOne(nums); cout << "\n数组 [ 1, 2, ..., n ] 被打乱后 = "; printVector(nums); cout << "数字 1 的索引为 " << index << endl; } return 0; } ================================================ FILE: codes/cpp/chapter_divide_and_conquer/CMakeLists.txt ================================================ add_executable(binary_search_recur binary_search_recur.cpp) add_executable(build_tree build_tree.cpp) add_executable(hanota hanota.cpp) ================================================ FILE: codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp ================================================ /** * 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) / 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); } /* Driver Code */ int main() { int target = 6; vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; // 二分查找(双闭区间) int index = binarySearch(nums, target); cout << "目标元素 6 的索引 = " << index << endl; return 0; } ================================================ FILE: codes/cpp/chapter_divide_and_conquer/build_tree.cpp ================================================ /** * 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) { // 初始化哈希表,存储 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; } /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_divide_and_conquer/hanota.cpp ================================================ /** * 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 只剩下一个圆盘,则直接将其移到 tar if (i == 1) { move(src, tar); return; } // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf dfs(i - 1, src, tar, buf); // 子问题 f(1) :将 src 剩余一个圆盘移到 tar move(src, tar); // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar dfs(i - 1, buf, src, tar); } /* 求解汉诺塔问题 */ void solveHanota(vector &A, vector &B, vector &C) { int n = A.size(); // 将 A 顶部 n 个圆盘借助 B 移到 C dfs(n, A, B, C); } /* Driver Code */ int main() { // 列表尾部是柱子顶部 vector A = {5, 4, 3, 2, 1}; vector B = {}; vector C = {}; cout << "初始状态下:\n"; cout << "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; } ================================================ FILE: codes/cpp/chapter_dynamic_programming/CMakeLists.txt ================================================ add_executable(climbing_stairs_backtrack climbing_stairs_backtrack.cpp) add_executable(climbing_stairs_dfs climbing_stairs_dfs.cpp) add_executable(climbing_stairs_dfs_mem climbing_stairs_dfs_mem.cpp) add_executable(climbing_stairs_dp climbing_stairs_dp.cpp) add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.cpp) add_executable(min_path_sum min_path_sum.cpp) add_executable(unbounded_knapsack unbounded_knapsack.cpp) add_executable(coin_change coin_change.cpp) add_executable(coin_change_ii coin_change_ii.cpp) add_executable(edit_distance edit_distance.cpp) ================================================ FILE: codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp ================================================ /** * 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]; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsBacktrack(n); cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; return 0; } ================================================ FILE: codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp ================================================ /** * 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]; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsConstraintDP(n); cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; return 0; } ================================================ FILE: codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp ================================================ /** * 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); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFS(n); cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; return 0; } ================================================ FILE: codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp ================================================ /** * 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); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFSMem(n); cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; return 0; } ================================================ FILE: codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDP(n); cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; res = climbingStairsDPComp(n); cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; return 0; } ================================================ FILE: codes/cpp/chapter_dynamic_programming/coin_change.cpp ================================================ /** * 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; } /* Driver code */ 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; } ================================================ FILE: codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp ================================================ /** * 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 这两种方案之和 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 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver code */ 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; } ================================================ FILE: codes/cpp/chapter_dynamic_programming/edit_distance.cpp ================================================ /** * 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; // 若两字符相等,则直接跳过此两字符 if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 int insert = editDistanceDFS(s, t, i, j - 1); int del = editDistanceDFS(s, t, i - 1, j); int replace = editDistanceDFS(s, t, i - 1, j - 1); // 返回最少编辑步数 return min(min(insert, del), replace) + 1; } /* 编辑距离:记忆化搜索 */ int editDistanceDFSMem(string s, string t, vector> &mem, int i, int j) { // 若 s 和 t 都为空,则返回 0 if (i == 0 && j == 0) return 0; // 若 s 为空,则返回 t 长度 if (i == 0) return j; // 若 t 为空,则返回 s 长度 if (j == 0) return i; // 若已有记录,则直接返回之 if (mem[i][j] != -1) return mem[i][j]; // 若两字符相等,则直接跳过此两字符 if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 int insert = editDistanceDFSMem(s, t, mem, i, j - 1); int del = editDistanceDFSMem(s, t, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 记录并返回最少编辑步数 mem[i][j] = min(min(insert, del), replace) + 1; return mem[i][j]; } /* 编辑距离:动态规划 */ int editDistanceDP(string s, string t) { int n = s.length(), m = t.length(); vector> dp(n + 1, vector(m + 1, 0)); // 状态转移:首行首列 for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // 状态转移:其余行和列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // 若两字符相等,则直接跳过此两字符 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* 编辑距离:空间优化后的动态规划 */ int editDistanceDPComp(string s, string t) { int n = s.length(), m = t.length(); vector dp(m + 1, 0); // 状态转移:首行 for (int j = 1; j <= m; j++) { dp[j] = j; } // 状态转移:其余行 for (int i = 1; i <= n; i++) { // 状态转移:首列 int leftup = dp[0]; // 暂存 dp[i-1, j-1] dp[0] = i; // 状态转移:其余列 for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 若两字符相等,则直接跳过此两字符 dp[j] = leftup; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 更新为下一轮的 dp[i-1, j-1] } } return dp[m]; } /* Driver Code */ int main() { string s = "bag"; string t = "pack"; int n = s.length(), m = t.length(); // 暴力搜索 int res = editDistanceDFS(s, t, n, m); cout << "将 " << s << " 更改为 " << t << " 最少需要编辑 " << res << " 步\n"; // 记忆化搜索 vector> mem(n + 1, vector(m + 1, -1)); res = editDistanceDFSMem(s, t, mem, n, m); cout << "将 " << s << " 更改为 " << t << " 最少需要编辑 " << res << " 步\n"; // 动态规划 res = editDistanceDP(s, t); cout << "将 " << s << " 更改为 " << t << " 最少需要编辑 " << res << " 步\n"; // 空间优化后的动态规划 res = editDistanceDPComp(s, t); cout << "将 " << s << " 更改为 " << t << " 最少需要编辑 " << res << " 步\n"; return 0; } ================================================ FILE: codes/cpp/chapter_dynamic_programming/knapsack.cpp ================================================ #include #include #include using namespace std; /* 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]; // 返回两种方案中价值更大的那一个 return max(no, yes); } /* 0-1 背包:记忆化搜索 */ int knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i == 0 || c == 0) { return 0; } // 若已有记录,则直接返回 if (mem[i][c] != -1) { return mem[i][c]; } // 若超过背包容量,则只能选择不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 int no = knapsackDFSMem(wgt, val, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 记录并返回两种方案中价值更大的那一个 mem[i][c] = max(no, yes); return mem[i][c]; } /* 0-1 背包:动态规划 */ int knapsackDP(vector &wgt, vector &val, int cap) { int n = wgt.size(); // 初始化 dp 表 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]; } /* Driver Code */ int main() { vector wgt = {10, 20, 30, 40, 50}; vector val = {50, 120, 150, 210, 240}; int cap = 50; int n = wgt.size(); // 暴力搜索 int res = knapsackDFS(wgt, val, n, cap); cout << "不超过背包容量的最大物品价值为 " << res << endl; // 记忆化搜索 vector> mem(n + 1, vector(cap + 1, -1)); res = knapsackDFSMem(wgt, val, mem, n, cap); cout << "不超过背包容量的最大物品价值为 " << res << endl; // 动态规划 res = knapsackDP(wgt, val, cap); cout << "不超过背包容量的最大物品价值为 " << res << endl; // 空间优化后的动态规划 res = knapsackDPComp(wgt, val, cap); cout << "不超过背包容量的最大物品价值为 " << res << endl; return 0; } ================================================ FILE: codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; cout << "输入楼梯的代价列表为 "; printVector(cost); int res = minCostClimbingStairsDP(cost); cout << "爬完楼梯的最低代价为 " << res << endl; res = minCostClimbingStairsDPComp(cost); cout << "爬完楼梯的最低代价为 " << res << endl; return 0; } ================================================ FILE: codes/cpp/chapter_dynamic_programming/min_path_sum.cpp ================================================ /** * 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) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; } /* 最小路径和:记忆化搜索 */ int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) { // 若为左上角单元格,则终止搜索 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 || j < 0) { return INT_MAX; } // 若已有记录,则直接返回 if (mem[i][j] != -1) { return mem[i][j]; } // 左边和上边单元格的最小路径代价 int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // 记录并返回左上角到 (i, j) 的最小路径代价 mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; return mem[i][j]; } /* 最小路径和:动态规划 */ int minPathSumDP(vector> &grid) { int n = grid.size(), m = grid[0].size(); // 初始化 dp 表 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 minPathSumDPComp(vector> &grid) { int n = grid.size(), m = grid[0].size(); // 初始化 dp 表 vector dp(m); // 状态转移:首行 dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 状态转移:其余行 for (int i = 1; i < n; i++) { // 状态转移:首列 dp[0] = dp[0] + grid[i][0]; // 状态转移:其余列 for (int j = 1; j < m; j++) { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ int main() { vector> grid = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; int n = grid.size(), m = grid[0].size(); // 暴力搜索 int res = minPathSumDFS(grid, n - 1, m - 1); cout << "从左上角到右下角的最小路径和为 " << res << endl; // 记忆化搜索 vector> mem(n, vector(m, -1)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); cout << "从左上角到右下角的最小路径和为 " << res << endl; // 动态规划 res = minPathSumDP(grid); cout << "从左上角到右下角的最小路径和为 " << res << endl; // 空间优化后的动态规划 res = minPathSumDPComp(grid); cout << "从左上角到右下角的最小路径和为 " << res << endl; return 0; } ================================================ FILE: codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp ================================================ /** * 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]; } /* Driver code */ int main() { vector wgt = {1, 2, 3}; vector val = {5, 11, 15}; int cap = 4; // 动态规划 int res = unboundedKnapsackDP(wgt, val, cap); cout << "不超过背包容量的最大物品价值为 " << res << endl; // 空间优化后的动态规划 res = unboundedKnapsackDPComp(wgt, val, cap); cout << "不超过背包容量的最大物品价值为 " << res << endl; return 0; } ================================================ FILE: codes/cpp/chapter_graph/CMakeLists.txt ================================================ add_executable(graph_bfs graph_bfs.cpp) add_executable(graph_dfs graph_dfs.cpp) # add_executable(graph_adjacency_list graph_adjacency_list.cpp) add_executable(graph_adjacency_list_test graph_adjacency_list_test.cpp) add_executable(graph_adjacency_matrix graph_adjacency_matrix.cpp) ================================================ FILE: codes/cpp/chapter_graph/graph_adjacency_list.cpp ================================================ /** * 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: // 邻接表,key:顶点,value:该顶点的所有邻接顶点 unordered_map> adjList; /* 在 vector 中删除指定节点 */ 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("不存在顶点"); // 添加边 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("不存在顶点"); // 删除边 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("不存在顶点"); // 在邻接表中删除顶点 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 ================================================ FILE: codes/cpp/chapter_graph/graph_adjacency_list_test.cpp ================================================ /** * File: graph_adjacency_list_test.cpp * Created Time: 2023-02-09 * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) */ #include "./graph_adjacency_list.cpp" /* Driver Code */ int main() { /* 初始化无向图 */ vector v = valsToVets(vector{1, 3, 2, 5, 4}); vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; GraphAdjList graph(edges); cout << "\n初始化后,图为" << endl; graph.print(); /* 添加边 */ // 顶点 1, 2 即 v[0], v[2] graph.addEdge(v[0], v[2]); cout << "\n添加边 1-2 后,图为" << endl; graph.print(); /* 删除边 */ // 顶点 1, 3 即 v[0], v[1] graph.removeEdge(v[0], v[1]); cout << "\n删除边 1-3 后,图为" << endl; graph.print(); /* 添加顶点 */ Vertex *v5 = new Vertex(6); graph.addVertex(v5); cout << "\n添加顶点 6 后,图为" << endl; graph.print(); /* 删除顶点 */ // 顶点 3 即 v[1] graph.removeVertex(v[1]); cout << "\n删除顶点 3 后,图为" << endl; graph.print(); // 释放内存 for (Vertex *vet : v) { delete vet; } return 0; } ================================================ FILE: codes/cpp/chapter_graph/graph_adjacency_matrix.cpp ================================================ /** * 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); } // 添加边 // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 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("顶点不存在"); } // 在顶点列表中移除索引 index 的顶点 vertices.erase(vertices.begin() + index); // 在邻接矩阵中删除索引 index 的行 adjMat.erase(adjMat.begin() + index); // 在邻接矩阵中删除索引 index 的列 for (vector &row : adjMat) { row.erase(row.begin() + index); } } /* 添加边 */ // 参数 i, j 对应 vertices 元素索引 void addEdge(int i, int j) { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw out_of_range("顶点不存在"); } // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) adjMat[i][j] = 1; adjMat[j][i] = 1; } /* 删除边 */ // 参数 i, j 对应 vertices 元素索引 void removeEdge(int i, int j) { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw out_of_range("顶点不存在"); } adjMat[i][j] = 0; adjMat[j][i] = 0; } /* 打印邻接矩阵 */ void print() { cout << "顶点列表 = "; printVector(vertices); cout << "邻接矩阵 =" << endl; printVectorMatrix(adjMat); } }; /* Driver Code */ int main() { /* 初始化无向图 */ // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 vector vertices = {1, 3, 2, 5, 4}; vector> edges = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; GraphAdjMat graph(vertices, edges); 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; } ================================================ FILE: codes/cpp/chapter_graph/graph_bfs.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_graph/graph_dfs.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_greedy/CMakeLists.txt ================================================ add_executable(coin_change_greedy coin_change_greedy.cpp) add_executable(fractional_knapsack fractional_knapsack.cpp) add_executable(max_capacity max_capacity.cpp) ================================================ FILE: codes/cpp/chapter_greedy/coin_change_greedy.cpp ================================================ /** * 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) { // 假设 coins 列表有序 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; } /* Driver Code */ int main() { // 贪心:能够保证找到全局最优解 vector coins = {1, 5, 10, 20, 50, 100}; int amt = 186; int res = coinChangeGreedy(coins, amt); cout << "\ncoins = "; printVector(coins); cout << "amt = " << amt << endl; cout << "凑到 " << amt << " 所需的最少硬币数量为 " << res << endl; // 贪心:无法保证找到全局最优解 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; } ================================================ FILE: codes/cpp/chapter_greedy/fractional_knapsack.cpp ================================================ /** * 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) { // 创建物品列表,包含两个属性:重量、价值 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; } /* Driver Code */ int main() { vector wgt = {10, 20, 30, 40, 50}; vector val = {50, 120, 150, 210, 240}; int cap = 50; // 贪心算法 double res = fractionalKnapsack(wgt, val, cap); cout << "不超过背包容量的最大物品价值为 " << res << endl; return 0; } ================================================ FILE: codes/cpp/chapter_greedy/max_capacity.cpp ================================================ /** * 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; // 循环贪心选择,直至两板相遇 while (i < j) { // 更新最大容量 int cap = min(ht[i], ht[j]) * (j - i); res = max(res, cap); // 向内移动短板 if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ int main() { vector ht = {3, 8, 5, 2, 7, 7, 3, 4}; // 贪心算法 int res = maxCapacity(ht); cout << "最大容量为 " << res << endl; return 0; } ================================================ FILE: codes/cpp/chapter_greedy/max_product_cutting.cpp ================================================ /** * 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); } /* Driver Code */ int main() { int n = 58; // 贪心算法 int res = maxProductCutting(n); cout << "最大切分乘积为" << res << endl; return 0; } ================================================ FILE: codes/cpp/chapter_hashing/CMakeLists.txt ================================================ add_executable(hash_map hash_map.cpp) add_executable(array_hash_map_test array_hash_map_test.cpp) add_executable(hash_map_chaining hash_map_chaining.cpp) add_executable(hash_map_open_addressing hash_map_open_addressing.cpp) add_executable(simple_hash simple_hash.cpp) add_executable(built_in_hash built_in_hash.cpp) ================================================ FILE: codes/cpp/chapter_hashing/array_hash_map.cpp ================================================ /** * 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 ================================================ FILE: codes/cpp/chapter_hashing/array_hash_map_test.cpp ================================================ /** * File: array_hash_map_test.cpp * Created Time: 2022-12-14 * Author: msk397 (machangxinq@gmail.com) */ #include "./array_hash_map.cpp" /* Driver Code */ int main() { /* 初始化哈希表 */ ArrayHashMap map = ArrayHashMap(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(12836, "小哈"); map.put(15937, "小啰"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鸭"); cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; map.print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value string name = map.get(15937); cout << "\n输入学号 15937 ,查询到姓名 " << name << endl; /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(10583); cout << "\n删除 10583 后,哈希表为\nKey -> Value" << endl; map.print(); /* 遍历哈希表 */ cout << "\n遍历键值对 Key->Value" << endl; for (auto kv : map.pairSet()) { cout << kv->key << " -> " << kv->val << endl; } cout << "\n单独遍历键 Key" << endl; for (auto key : map.keySet()) { cout << key << endl; } cout << "\n单独遍历值 Value" << endl; for (auto val : map.valueSet()) { cout << val << endl; } return 0; } ================================================ FILE: codes/cpp/chapter_hashing/built_in_hash.cpp ================================================ /** * File: built_in_hash.cpp * Created Time: 2023-06-21 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { int num = 3; size_t hashNum = hash()(num); cout << "整数 " << num << " 的哈希值为 " << hashNum << "\n"; bool bol = true; size_t hashBol = hash()(bol); cout << "布尔量 " << bol << " 的哈希值为 " << hashBol << "\n"; double dec = 3.14159; size_t hashDec = hash()(dec); cout << "小数 " << dec << " 的哈希值为 " << hashDec << "\n"; string str = "Hello 算法"; size_t hashStr = hash()(str); cout << "字符串 " << str << " 的哈希值为 " << hashStr << "\n"; // 在 C++ 中,内置 std:hash() 仅提供基本数据类型的哈希值计算 // 数组、对象的哈希值计算需要自行实现 } ================================================ FILE: codes/cpp/chapter_hashing/hash_map.cpp ================================================ /** * File: hash_map.cpp * Created Time: 2022-12-14 * Author: msk397 (machangxinq@gmail.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* 初始化哈希表 */ unordered_map map; /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map[12836] = "小哈"; map[15937] = "小啰"; map[16750] = "小算"; map[13276] = "小法"; map[10583] = "小鸭"; cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; printHashMap(map); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value string name = map[15937]; cout << "\n输入学号 15937 ,查询到姓名 " << name << endl; /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.erase(10583); cout << "\n删除 10583 后,哈希表为\nKey -> Value" << endl; printHashMap(map); /* 遍历哈希表 */ cout << "\n遍历键值对 Key->Value" << endl; for (auto kv : map) { cout << kv.first << " -> " << kv.second << endl; } cout << "\n使用迭代器遍历 Key->Value" << endl; for (auto iter = map.begin(); iter != map.end(); iter++) { cout << iter->first << "->" << iter->second << endl; } return 0; } ================================================ FILE: codes/cpp/chapter_hashing/hash_map_chaining.cpp ================================================ /** * 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); // 遍历桶,若找到 key ,则返回对应 val for (Pair *pair : buckets[index]) { if (pair->key == key) { return pair->val; } } // 若未找到 key ,则返回空字符串 return ""; } /* 添加操作 */ void put(int key, string val) { // 当负载因子超过阈值时,执行扩容 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 for (Pair *pair : buckets[index]) { if (pair->key == key) { pair->val = val; return; } } // 若无该 key ,则将键值对添加至尾部 buckets[index].push_back(new Pair(key, val)); size++; } /* 删除操作 */ 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"; } } }; /* Driver Code */ int main() { /* 初始化哈希表 */ HashMapChaining map = HashMapChaining(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(12836, "小哈"); map.put(15937, "小啰"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鸭"); cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; map.print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value string name = map.get(13276); cout << "\n输入学号 13276 ,查询到姓名 " << name << endl; /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(12836); cout << "\n删除 12836 后,哈希表为\nKey -> Value" << endl; map.print(); return 0; } ================================================ FILE: codes/cpp/chapter_hashing/hash_map_open_addressing.cpp ================================================ /** * 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; } } } }; /* Driver Code */ int main() { // 初始化哈希表 HashMapOpenAddressing hashmap; // 添加操作 // 在哈希表中添加键值对 (key, val) hashmap.put(12836, "小哈"); hashmap.put(15937, "小啰"); hashmap.put(16750, "小算"); hashmap.put(13276, "小法"); hashmap.put(10583, "小鸭"); cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; hashmap.print(); // 查询操作 // 向哈希表中输入键 key ,得到值 val string name = hashmap.get(13276); cout << "\n输入学号 13276 ,查询到姓名 " << name << endl; // 删除操作 // 在哈希表中删除键值对 (key, val) hashmap.remove(16750); cout << "\n删除 16750 后,哈希表为\nKey -> Value" << endl; hashmap.print(); return 0; } ================================================ FILE: codes/cpp/chapter_hashing/simple_hash.cpp ================================================ /** * 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; } /* 异或哈希 */ 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; } /* Driver Code */ int main() { string key = "Hello 算法"; int hash = addHash(key); cout << "加法哈希值为 " << hash << endl; hash = mulHash(key); cout << "乘法哈希值为 " << hash << endl; hash = xorHash(key); cout << "异或哈希值为 " << hash << endl; hash = rotHash(key); cout << "旋转哈希值为 " << hash << endl; return 0; } ================================================ FILE: codes/cpp/chapter_heap/CMakeLists.txt ================================================ add_executable(heap heap.cpp) add_executable(my_heap my_heap.cpp) add_executable(top_k top_k.cpp) ================================================ FILE: codes/cpp/chapter_heap/heap.cpp ================================================ /** * 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); } /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_heap/my_heap.cpp ================================================ /** * 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; // 交换两节点 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("堆为空"); } // 交换根节点与最右叶节点(交换首元素与尾元素) 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); } }; /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_heap/top_k.cpp ================================================ /** * 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; } // Driver Code 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; } ================================================ FILE: codes/cpp/chapter_searching/CMakeLists.txt ================================================ add_executable(binary_search binary_search.cpp) add_executable(binary_search_insertion binary_search_insertion.cpp) add_executable(binary_search_edge binary_search_edge.cpp) add_executable(two_sum two_sum.cpp) ================================================ FILE: codes/cpp/chapter_searching/binary_search.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int target = 6; vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; /* 二分查找(双闭区间) */ int index = binarySearch(nums, target); cout << "目标元素 6 的索引 = " << index << endl; /* 二分查找(左闭右开区间) */ index = binarySearchLCRO(nums, target); cout << "目标元素 6 的索引 = " << index << endl; return 0; } ================================================ FILE: codes/cpp/chapter_searching/binary_search_edge.cpp ================================================ /** * 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; // target 在区间 [m+1, j] 中 } else { j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 } } // 返回插入点 i return i; } /* 二分查找最左一个 target */ 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; } /* 二分查找最右一个 target */ int binarySearchRightEdge(vector &nums, int target) { // 转化为查找最左一个 target + 1 int i = binarySearchInsertion(nums, target + 1); // j 指向最右一个 target ,i 指向首个大于 target 的元素 int j = i - 1; // 未找到 target ,返回 -1 if (j == -1 || nums[j] != target) { return -1; } // 找到 target ,返回索引 j return j; } /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_searching/binary_search_insertion.cpp ================================================ /** * 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; // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在区间 [i, m-1] 中 } else { return m; // 找到 target ,返回插入点 m } } // 未找到 target ,返回插入点 i return i; } /* 二分查找插入点(存在重复元素) */ int binarySearchInsertion(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; // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在区间 [i, m-1] 中 } else { j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 } } // 返回插入点 i return i; } /* Driver Code */ int main() { // 无重复元素的数组 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; } ================================================ FILE: codes/cpp/chapter_searching/hashing_search.cpp ================================================ /** * 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) { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 if (map.find(target) == map.end()) return -1; return map[target]; } /* 哈希查找(链表) */ ListNode *hashingSearchLinkedList(unordered_map map, int target) { // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 nullptr if (map.find(target) == map.end()) return nullptr; return map[target]; } /* Driver Code */ 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; // key: 元素,value: 索引 } int index = hashingSearchArray(map, target); cout << "目标元素 3 的索引 = " << index << endl; /* 哈希查找(链表) */ ListNode *head = vecToLinkedList(nums); // 初始化哈希表 unordered_map map1; while (head != nullptr) { map1[head->val] = head; // key: 节点值,value: 节点 head = head->next; } ListNode *node = hashingSearchLinkedList(map1, target); cout << "目标节点值 3 的对应节点对象为 " << node << endl; return 0; } ================================================ FILE: codes/cpp/chapter_searching/linear_search.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int target = 3; /* 在数组中执行线性查找 */ vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; int index = linearSearchArray(nums, target); cout << "目标元素 3 的索引 = " << index << endl; /* 在链表中执行线性查找 */ ListNode *head = vecToLinkedList(nums); ListNode *node = linearSearchLinkedList(head, target); cout << "目标节点值 3 的对应节点对象为 " << node << endl; return 0; } ================================================ FILE: codes/cpp/chapter_searching/two_sum.cpp ================================================ /** * 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 {}; } /* Driver Code */ int main() { // ======= Test Case ======= vector nums = {2, 7, 11, 15}; int target = 13; // ====== Driver Code ====== // 方法一 vector res = twoSumBruteForce(nums, target); cout << "方法一 res = "; printVector(res); // 方法二 res = twoSumHashTable(nums, target); cout << "方法二 res = "; printVector(res); return 0; } ================================================ FILE: codes/cpp/chapter_sorting/CMakeLists.txt ================================================ add_executable(selection_sort selection_sort.cpp) add_executable(bubble_sort bubble_sort.cpp) add_executable(insertion_sort insertion_sort.cpp) add_executable(merge_sort merge_sort.cpp) add_executable(quick_sort quick_sort.cpp) add_executable(heap_sort heap_sort.cpp) ================================================ FILE: codes/cpp/chapter_sorting/bubble_sort.cpp ================================================ /** * 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; // 此轮“冒泡”未交换任何元素,直接跳出 } } /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_sorting/bucket_sort.cpp ================================================ /** * 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; // 将 num 添加进桶 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; } } } /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_sorting/counting_sort.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_sorting/heap_sort.cpp ================================================ /** * 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); } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; heapSort(nums); cout << "堆排序完成后 nums = "; printVector(nums); return 0; } ================================================ FILE: codes/cpp/chapter_sorting/insertion_sort.cpp ================================================ /** * 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 赋值到正确位置 } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; insertionSort(nums); cout << "插入排序完成后 nums = "; printVector(nums); return 0; } ================================================ FILE: codes/cpp/chapter_sorting/merge_sort.cpp ================================================ /** * 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); } /* Driver Code */ int main() { /* 归并排序 */ vector nums = {7, 3, 2, 6, 0, 1, 5, 4}; mergeSort(nums, 0, nums.size() - 1); cout << "归并排序完成后 nums = "; printVector(nums); return 0; } ================================================ FILE: codes/cpp/chapter_sorting/quick_sort.cpp ================================================ /** * File: quick_sort.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* 快速排序类 */ class QuickSort { private: /* 哨兵划分 */ 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], nums[j]); // 交换这两个元素 } swap(nums[i], nums[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 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], nums[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], nums[j]); // 交换这两个元素 } swap(nums[i], nums[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 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], nums[j]); // 交换这两个元素 } swap(nums[i], nums[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] } } } }; /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_sorting/radix_sort.cpp ================================================ /** * 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) { // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 return (num / exp) % 10; } /* 计数排序(根据 nums 第 k 位排序) */ void countingSortDigit(vector &nums, int exp) { // 十进制的位范围为 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); } /* Driver Code */ int main() { // 基数排序 vector nums = {10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996}; radixSort(nums); cout << "基数排序完成后 nums = "; printVector(nums); return 0; } ================================================ FILE: codes/cpp/chapter_sorting/selection_sort.cpp ================================================ /** * 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]); } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; selectionSort(nums); cout << "选择排序完成后 nums = "; printVector(nums); return 0; } ================================================ FILE: codes/cpp/chapter_stack_and_queue/CMakeLists.txt ================================================ add_executable(array_deque array_deque.cpp) add_executable(array_queue array_queue.cpp) add_executable(array_stack array_stack.cpp) add_executable(deque deque.cpp) add_executable(linkedlist_deque linkedlist_deque.cpp) add_executable(linkedlist_queue linkedlist_queue.cpp) add_executable(linkedlist_stack linkedlist_stack.cpp) add_executable(queue queue.cpp) add_executable(stack stack.cpp) ================================================ FILE: codes/cpp/chapter_stack_and_queue/array_deque.cpp ================================================ /** * 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 << "双向队列已满" << endl; return; } // 队首指针向左移动一位 // 通过取余操作实现 front 越过数组头部后回到尾部 front = index(front - 1); // 将 num 添加至队首 nums[front] = num; queSize++; } /* 队尾入队 */ void pushLast(int num) { if (queSize == capacity()) { cout << "双向队列已满" << endl; return; } // 计算队尾指针,指向队尾索引 + 1 int rear = index(front + queSize); // 将 num 添加至队尾 nums[rear] = num; queSize++; } /* 队首出队 */ int popFirst() { int num = peekFirst(); // 队首指针向后移动一位 front = index(front + 1); queSize--; return num; } /* 队尾出队 */ int popLast() { int num = peekLast(); queSize--; return num; } /* 访问队首元素 */ int peekFirst() { if (isEmpty()) throw out_of_range("双向队列为空"); return nums[front]; } /* 访问队尾元素 */ int peekLast() { if (isEmpty()) throw out_of_range("双向队列为空"); // 计算尾元素索引 int last = index(front + queSize - 1); return nums[last]; } /* 返回数组用于打印 */ vector toVector() { // 仅转换有效长度范围内的列表元素 vector res(queSize); for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[index(j)]; } return res; } }; /* Driver Code */ int main() { /* 初始化双向队列 */ ArrayDeque *deque = new ArrayDeque(10); deque->pushLast(3); deque->pushLast(2); deque->pushLast(5); cout << "双向队列 deque = "; printVector(deque->toVector()); /* 访问元素 */ int peekFirst = deque->peekFirst(); cout << "队首元素 peekFirst = " << peekFirst << endl; int peekLast = deque->peekLast(); cout << "队尾元素 peekLast = " << peekLast << endl; /* 元素入队 */ deque->pushLast(4); cout << "元素 4 队尾入队后 deque = "; printVector(deque->toVector()); deque->pushFirst(1); cout << "元素 1 队首入队后 deque = "; printVector(deque->toVector()); /* 元素出队 */ int popLast = deque->popLast(); cout << "队尾出队元素 = " << popLast << ",队尾出队后 deque = "; printVector(deque->toVector()); int popFirst = deque->popFirst(); cout << "队首出队元素 = " << popFirst << ",队首出队后 deque = "; printVector(deque->toVector()); /* 获取双向队列的长度 */ int size = deque->size(); cout << "双向队列长度 size = " << size << endl; /* 判断双向队列是否为空 */ bool isEmpty = deque->isEmpty(); cout << "双向队列是否为空 = " << boolalpha << isEmpty << endl; return 0; } ================================================ FILE: codes/cpp/chapter_stack_and_queue/array_queue.cpp ================================================ /** * 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 << "队列已满" << endl; return; } // 计算队尾指针,指向队尾索引 + 1 // 通过取余操作实现 rear 越过数组尾部后回到头部 int rear = (front + queSize) % queCapacity; // 将 num 添加至队尾 nums[rear] = num; queSize++; } /* 出队 */ int pop() { int num = peek(); // 队首指针向后移动一位,若越过尾部,则返回到数组头部 front = (front + 1) % queCapacity; queSize--; return num; } /* 访问队首元素 */ int peek() { if (isEmpty()) throw out_of_range("队列为空"); 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; } }; /* Driver Code */ int main() { /* 初始化队列 */ int capacity = 10; ArrayQueue *queue = new ArrayQueue(capacity); /* 元素入队 */ queue->push(1); queue->push(3); queue->push(2); queue->push(5); queue->push(4); cout << "队列 queue = "; printVector(queue->toVector()); /* 访问队首元素 */ int peek = queue->peek(); cout << "队首元素 peek = " << peek << endl; /* 元素出队 */ peek = queue->pop(); cout << "出队元素 pop = " << peek << ",出队后 queue = "; printVector(queue->toVector()); /* 获取队列的长度 */ int size = queue->size(); cout << "队列长度 size = " << size << endl; /* 判断队列是否为空 */ bool empty = queue->isEmpty(); cout << "队列是否为空 = " << empty << endl; /* 测试环形数组 */ for (int i = 0; i < 10; i++) { queue->push(i); queue->pop(); cout << "第 " << i << " 轮入队 + 出队后 queue = "; printVector(queue->toVector()); } // 释放内存 delete queue; return 0; } ================================================ FILE: codes/cpp/chapter_stack_and_queue/array_stack.cpp ================================================ /** * 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("栈为空"); return stack.back(); } /* 返回 Vector */ vector toVector() { return stack; } }; /* Driver Code */ int main() { /* 初始化栈 */ ArrayStack *stack = new ArrayStack(); /* 元素入栈 */ stack->push(1); stack->push(3); stack->push(2); stack->push(5); stack->push(4); cout << "栈 stack = "; printVector(stack->toVector()); /* 访问栈顶元素 */ int top = stack->top(); cout << "栈顶元素 top = " << top << endl; /* 元素出栈 */ top = stack->pop(); cout << "出栈元素 pop = " << top << ",出栈后 stack = "; printVector(stack->toVector()); /* 获取栈的长度 */ int size = stack->size(); cout << "栈的长度 size = " << size << endl; /* 判断是否为空 */ bool empty = stack->isEmpty(); cout << "栈是否为空 = " << empty << endl; // 释放内存 delete stack; return 0; } ================================================ FILE: codes/cpp/chapter_stack_and_queue/deque.cpp ================================================ /** * File: deque.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* 初始化双向队列 */ deque deque; /* 元素入队 */ deque.push_back(2); deque.push_back(5); deque.push_back(4); deque.push_front(3); deque.push_front(1); cout << "双向队列 deque = "; printDeque(deque); /* 访问元素 */ int front = deque.front(); cout << "队首元素 front = " << front << endl; int back = deque.back(); cout << "队尾元素 back = " << back << endl; /* 元素出队 */ deque.pop_front(); cout << "队首出队元素 popFront = " << front << ",队首出队后 deque = "; printDeque(deque); deque.pop_back(); cout << "队尾出队元素 popLast = " << back << ",队尾出队后 deque = "; printDeque(deque); /* 获取双向队列的长度 */ int size = deque.size(); cout << "双向队列长度 size = " << size << endl; /* 判断双向队列是否为空 */ bool empty = deque.empty(); cout << "双向队列是否为空 = " << empty << endl; return 0; } ================================================ FILE: codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp ================================================ /** * 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) { // 将 node 添加至链表头部 front->prev = node; node->next = front; front = node; // 更新头节点 // 队尾入队操作 } else { // 将 node 添加至链表尾部 rear->next = node; node->prev = rear; rear = node; // 更新尾节点 } queSize++; // 更新队列长度 } /* 队首入队 */ void pushFirst(int num) { push(num, true); } /* 队尾入队 */ void pushLast(int num) { push(num, false); } /* 出队操作 */ int pop(bool isFront) { if (isEmpty()) throw out_of_range("队列为空"); 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("双向队列为空"); return front->val; } /* 访问队尾元素 */ int peekLast() { if (isEmpty()) throw out_of_range("双向队列为空"); 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; } }; /* Driver Code */ int main() { /* 初始化双向队列 */ LinkedListDeque *deque = new LinkedListDeque(); deque->pushLast(3); deque->pushLast(2); deque->pushLast(5); cout << "双向队列 deque = "; printVector(deque->toVector()); /* 访问元素 */ int peekFirst = deque->peekFirst(); cout << "队首元素 peekFirst = " << peekFirst << endl; int peekLast = deque->peekLast(); cout << "队尾元素 peekLast = " << peekLast << endl; /* 元素入队 */ deque->pushLast(4); cout << "元素 4 队尾入队后 deque ="; printVector(deque->toVector()); deque->pushFirst(1); cout << "元素 1 队首入队后 deque = "; printVector(deque->toVector()); /* 元素出队 */ int popLast = deque->popLast(); cout << "队尾出队元素 = " << popLast << ",队尾出队后 deque = "; printVector(deque->toVector()); int popFirst = deque->popFirst(); cout << "队首出队元素 = " << popFirst << ",队首出队后 deque = "; printVector(deque->toVector()); /* 获取双向队列的长度 */ int size = deque->size(); cout << "双向队列长度 size = " << size << endl; /* 判断双向队列是否为空 */ bool isEmpty = deque->isEmpty(); cout << "双向队列是否为空 = " << boolalpha << isEmpty << endl; // 释放内存 delete deque; return 0; } ================================================ FILE: codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp ================================================ /** * 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("队列为空"); 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; } }; /* Driver Code */ int main() { /* 初始化队列 */ LinkedListQueue *queue = new LinkedListQueue(); /* 元素入队 */ queue->push(1); queue->push(3); queue->push(2); queue->push(5); queue->push(4); cout << "队列 queue = "; printVector(queue->toVector()); /* 访问队首元素 */ int peek = queue->peek(); cout << "队首元素 peek = " << peek << endl; /* 元素出队 */ peek = queue->pop(); cout << "出队元素 pop = " << peek << ",出队后 queue = "; printVector(queue->toVector()); /* 获取队列的长度 */ int size = queue->size(); cout << "队列长度 size = " << size << endl; /* 判断队列是否为空 */ bool empty = queue->isEmpty(); cout << "队列是否为空 = " << empty << endl; // 释放内存 delete queue; return 0; } ================================================ FILE: codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp ================================================ /** * 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("栈为空"); return stackTop->val; } /* 将 List 转化为 Array 并返回 */ 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; } }; /* Driver Code */ int main() { /* 初始化栈 */ LinkedListStack *stack = new LinkedListStack(); /* 元素入栈 */ stack->push(1); stack->push(3); stack->push(2); stack->push(5); stack->push(4); cout << "栈 stack = "; printVector(stack->toVector()); /* 访问栈顶元素 */ int top = stack->top(); cout << "栈顶元素 top = " << top << endl; /* 元素出栈 */ top = stack->pop(); cout << "出栈元素 pop = " << top << ",出栈后 stack = "; printVector(stack->toVector()); /* 获取栈的长度 */ int size = stack->size(); cout << "栈的长度 size = " << size << endl; /* 判断是否为空 */ bool empty = stack->isEmpty(); cout << "栈是否为空 = " << empty << endl; // 释放内存 delete stack; return 0; } ================================================ FILE: codes/cpp/chapter_stack_and_queue/queue.cpp ================================================ /** * File: queue.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* 初始化队列 */ queue queue; /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); cout << "队列 queue = "; printQueue(queue); /* 访问队首元素 */ int front = queue.front(); cout << "队首元素 front = " << front << endl; /* 元素出队 */ queue.pop(); cout << "出队元素 front = " << front << ",出队后 queue = "; printQueue(queue); /* 获取队列的长度 */ int size = queue.size(); cout << "队列长度 size = " << size << endl; /* 判断队列是否为空 */ bool empty = queue.empty(); cout << "队列是否为空 = " << empty << endl; return 0; } ================================================ FILE: codes/cpp/chapter_stack_and_queue/stack.cpp ================================================ /** * File: stack.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* 初始化栈 */ stack stack; /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); cout << "栈 stack = "; printStack(stack); /* 访问栈顶元素 */ int top = stack.top(); cout << "栈顶元素 top = " << top << endl; /* 元素出栈 */ stack.pop(); // 无返回值 cout << "出栈元素 pop = " << top << ",出栈后 stack = "; printStack(stack); /* 获取栈的长度 */ int size = stack.size(); cout << "栈的长度 size = " << size << endl; /* 判断是否为空 */ bool empty = stack.empty(); cout << "栈是否为空 = " << empty << endl; return 0; } ================================================ FILE: codes/cpp/chapter_tree/CMakeLists.txt ================================================ add_executable(avl_tree avl_tree.cpp) add_executable(binary_search_tree binary_search_tree.cpp) add_executable(binary_tree binary_tree.cpp) add_executable(binary_tree_bfs binary_tree_bfs.cpp) add_executable(binary_tree_dfs binary_tree_dfs.cpp) add_executable(array_binary_tree array_binary_tree.cpp) ================================================ FILE: codes/cpp/chapter_tree/array_binary_tree.cpp ================================================ /** * 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 ,代表空位 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)); } }; /* Driver Code */ 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 << ",值为 " << (abt.val(l) != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n"; cout << "其右子节点的索引为 " << r << ",值为 " << (abt.val(r) != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n"; cout << "其父节点的索引为 " << p << ",值为 " << (abt.val(p) != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n"; // 遍历树 vector res = abt.levelOrder(); cout << "\n层序遍历为: "; printVector(res); res = abt.preOrder(); cout << "前序遍历为: "; printVector(res); res = abt.inOrder(); cout << "中序遍历为: "; printVector(res); res = abt.postOrder(); cout << "后序遍历为: "; printVector(res); return 0; } ================================================ FILE: codes/cpp/chapter_tree/avl_tree.cpp ================================================ /** * 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 ,直接删除 node 并返回 if (child == nullptr) { delete node; return nullptr; } // 子节点数量 = 1 ,直接删除 node 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); } /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_tree/binary_search_tree.cpp ================================================ /** * 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 or 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); // 用 tmp 覆盖 cur cur->val = tmpVal; } } }; /* Driver Code */ int main() { /* 初始化二叉搜索树 */ BinarySearchTree *bst = new BinarySearchTree(); // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 vector nums = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; for (int num : nums) { bst->insert(num); } cout << endl << "初始化的二叉树为\n" << endl; 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; } ================================================ FILE: codes/cpp/chapter_tree/binary_tree.cpp ================================================ /** * File: binary_tree.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ 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; } ================================================ FILE: codes/cpp/chapter_tree/binary_tree_bfs.cpp ================================================ /** * 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; } /* Driver Code */ int main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "初始化二叉树\n" << endl; printTree(root); /* 层序遍历 */ vector vec = levelOrder(root); cout << endl << "层序遍历的节点打印序列 = "; printVector(vec); return 0; } ================================================ FILE: codes/cpp/chapter_tree/binary_tree_dfs.cpp ================================================ /** * 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); } /* Driver Code */ int main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "初始化二叉树\n" << endl; printTree(root); /* 前序遍历 */ vec.clear(); preOrder(root); cout << endl << "前序遍历的节点打印序列 = "; printVector(vec); /* 中序遍历 */ vec.clear(); inOrder(root); cout << endl << "中序遍历的节点打印序列 = "; printVector(vec); /* 后序遍历 */ vec.clear(); postOrder(root); cout << endl << "后序遍历的节点打印序列 = "; printVector(vec); return 0; } ================================================ FILE: codes/cpp/utils/CMakeLists.txt ================================================ add_executable(utils common.hpp print_utils.hpp list_node.hpp tree_node.hpp vertex.hpp) ================================================ FILE: codes/cpp/utils/common.hpp ================================================ /** * 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; ================================================ FILE: codes/cpp/utils/list_node.hpp ================================================ /** * 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; } } ================================================ FILE: codes/cpp/utils/print_utils.hpp ================================================ /** * 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 /* Find an element in a vector */ template int vecFind(const vector &vec, T ele) { int j = INT_MAX; for (int i = 0; i < vec.size(); i++) { if (vec[i] == ele) { j = i; } } return j; } /* Concatenate a vector with a delim */ 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(); } /* Repeat a string for n times */ 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; } /* Get the Vector String object */ template string getVectorString(vector &list) { return "[" + strJoin(", ", list) + "]"; } /* 打印列表 */ template void printVector(vector list) { cout << getVectorString(list) << '\n'; } /* 打印矩阵 */ template void printVectorMatrix(vector> &matrix) { cout << "[" << '\n'; for (vector &list : matrix) 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; } /** * 打印二叉树 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTree(TreeNode *root, Trunk *prev, bool isRight) { 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) { // Reverse the input stack stack tmp; while (!stk.empty()) { tmp.push(stk.top()); stk.pop(); } // Generate the string to print ostringstream s; bool flag = true; while (!tmp.empty()) { if (flag) { s << tmp.top(); flag = false; } else s << ", " << tmp.top(); tmp.pop(); } cout << "[" + s.str() + "]" << '\n'; } /* 打印队列 */ template void printQueue(queue queue) { // Generate the string to print 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) { // Generate the string to print 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'; } } /* Expose the underlying storage of the priority_queue container */ template S &Container(priority_queue &pq) { struct HackedQueue : private priority_queue { static S &Container(priority_queue &pq) { 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); } ================================================ FILE: codes/cpp/utils/tree_node.hpp ================================================ /** * 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; } ================================================ FILE: codes/cpp/utils/vertex.hpp ================================================ /** * 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; } ================================================ FILE: codes/csharp/.editorconfig ================================================ # CSharp formatting rules [*.cs] csharp_new_line_before_open_brace = none csharp_new_line_before_else = false csharp_new_line_before_catch = false csharp_new_line_before_finally = false csharp_indent_labels = one_less_than_current csharp_using_directive_placement = outside_namespace:silent csharp_prefer_simple_using_statement = true:suggestion csharp_prefer_braces = true:silent csharp_style_namespace_declarations = block_scoped:silent csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent csharp_style_prefer_primary_constructors = true:suggestion csharp_style_expression_bodied_methods = false:silent csharp_style_expression_bodied_constructors = false:silent csharp_style_expression_bodied_operators = false:silent csharp_style_expression_bodied_properties = true:silent csharp_style_expression_bodied_indexers = true:silent csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_lambdas = true:silent # CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language. dotnet_diagnostic.CS8981.severity = silent # IDE1006: Naming Styles dotnet_diagnostic.IDE1006.severity = silent # CA1822: Mark members as static dotnet_diagnostic.CA1822.severity = silent [*.{cs,vb}] #### Naming styles #### # Naming rules dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion dotnet_naming_rule.types_should_be_pascal_case.symbols = types dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case # Symbol specifications dotnet_naming_symbols.interface.applicable_kinds = interface dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.interface.required_modifiers = dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.types.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.non_field_members.required_modifiers = # Naming styles dotnet_naming_style.begins_with_i.required_prefix = I dotnet_naming_style.begins_with_i.required_suffix = dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_style_operator_placement_when_wrapping = beginning_of_line tab_width = 4 indent_size = 4 end_of_line = crlf # IDE0040: Add accessibility modifiers dotnet_diagnostic.IDE0040.severity = silent # IDE0044: Add readonly modifier dotnet_diagnostic.IDE0044.severity = silent ================================================ FILE: codes/csharp/.gitignore ================================================ .idea/ .vs/ obj/ .Debug bin/ ================================================ FILE: codes/csharp/GlobalUsing.cs ================================================ global using NUnit.Framework; global using hello_algo.utils; global using System.Text; ================================================ FILE: codes/csharp/chapter_array_and_linkedlist/array.cs ================================================ // File: array.cs // Created Time: 2022-12-14 // Author: mingXta (1195669834@qq.com) namespace hello_algo.chapter_array_and_linkedlist; public class array { /* 随机访问元素 */ int RandomAccess(int[] nums) { Random random = new(); // 在区间 [0, nums.Length) 中随机抽取一个数字 int randomIndex = random.Next(nums.Length); // 获取并返回随机元素 int randomNum = nums[randomIndex]; return randomNum; } /* 扩展数组长度 */ int[] Extend(int[] nums, int enlarge) { // 初始化一个扩展长度后的数组 int[] res = new int[nums.Length + enlarge]; // 将原数组中的所有元素复制到新数组 for (int i = 0; i < nums.Length; i++) { res[i] = nums[i]; } // 返回扩展后的新数组 return res; } /* 在数组的索引 index 处插入元素 num */ void Insert(int[] nums, int num, int index) { // 把索引 index 以及之后的所有元素向后移动一位 for (int i = nums.Length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 将 num 赋给 index 处的元素 nums[index] = num; } /* 删除索引 index 处的元素 */ void Remove(int[] nums, int index) { // 把索引 index 之后的所有元素向前移动一位 for (int i = index; i < nums.Length - 1; i++) { nums[i] = nums[i + 1]; } } /* 遍历数组 */ void Traverse(int[] nums) { int count = 0; // 通过索引遍历数组 for (int i = 0; i < nums.Length; i++) { count += nums[i]; } // 直接遍历数组元素 foreach (int num in nums) { count += num; } } /* 在数组中查找指定元素 */ int Find(int[] nums, int target) { for (int i = 0; i < nums.Length; i++) { if (nums[i] == target) return i; } return -1; } /* 辅助函数,数组转字符串 */ string ToString(int[] nums) { return string.Join(",", nums); } [Test] public void Test() { // 初始化数组 int[] arr = new int[5]; Console.WriteLine("数组 arr = " + ToString(arr)); int[] nums = [1, 3, 2, 5, 4]; Console.WriteLine("数组 nums = " + ToString(nums)); // 随机访问 int randomNum = RandomAccess(nums); Console.WriteLine("在 nums 中获取随机元素 " + randomNum); // 长度扩展 nums = Extend(nums, 3); Console.WriteLine("将数组长度扩展至 8 ,得到 nums = " + ToString(nums)); // 插入元素 Insert(nums, 6, 3); Console.WriteLine("在索引 3 处插入数字 6 ,得到 nums = " + ToString(nums)); // 删除元素 Remove(nums, 2); Console.WriteLine("删除索引 2 处的元素,得到 nums = " + ToString(nums)); // 遍历数组 Traverse(nums); // 查找元素 int index = Find(nums, 3); Console.WriteLine("在 nums 中查找元素 3 ,得到索引 = " + index); } } ================================================ FILE: codes/csharp/chapter_array_and_linkedlist/linked_list.cs ================================================ // File: linked_list.cs // Created Time: 2022-12-16 // Author: mingXta (1195669834@qq.com) namespace hello_algo.chapter_array_and_linkedlist; public class linked_list { /* 在链表的节点 n0 之后插入节点 P */ void Insert(ListNode n0, ListNode P) { ListNode? n1 = n0.next; P.next = n1; n0.next = P; } /* 删除链表的节点 n0 之后的首个节点 */ void Remove(ListNode n0) { if (n0.next == null) return; // n0 -> P -> n1 ListNode P = n0.next; ListNode? n1 = P.next; n0.next = n1; } /* 访问链表中索引为 index 的节点 */ ListNode? Access(ListNode? head, int index) { for (int i = 0; i < index; i++) { if (head == null) return null; head = head.next; } return head; } /* 在链表中查找值为 target 的首个节点 */ int Find(ListNode? head, int target) { int index = 0; while (head != null) { if (head.val == target) return index; head = head.next; index++; } return -1; } [Test] public void Test() { // 初始化链表 // 初始化各个节点 ListNode n0 = new(1); ListNode n1 = new(3); ListNode n2 = new(2); ListNode n3 = new(5); ListNode n4 = new(4); // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; Console.WriteLine($"初始化的链表为{n0}"); // 插入节点 Insert(n0, new ListNode(0)); Console.WriteLine($"插入节点后的链表为{n0}"); // 删除节点 Remove(n0); Console.WriteLine($"删除节点后的链表为{n0}"); // 访问节点 ListNode? node = Access(n0, 3); Console.WriteLine($"链表中索引 3 处的节点的值 = {node?.val}"); // 查找节点 int index = Find(n0, 2); Console.WriteLine($"链表中值为 2 的节点的索引 = {index}"); } } ================================================ FILE: codes/csharp/chapter_array_and_linkedlist/list.cs ================================================ /** * File: list.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_array_and_linkedlist; public class list { [Test] public void Test() { /* 初始化列表 */ int[] numbers = [1, 3, 2, 5, 4]; List nums = [.. numbers]; Console.WriteLine("列表 nums = " + string.Join(",", nums)); /* 访问元素 */ int num = nums[1]; Console.WriteLine("访问索引 1 处的元素,得到 num = " + num); /* 更新元素 */ nums[1] = 0; Console.WriteLine("将索引 1 处的元素更新为 0 ,得到 nums = " + string.Join(",", nums)); /* 清空列表 */ nums.Clear(); Console.WriteLine("清空列表后 nums = " + string.Join(",", nums)); /* 在尾部添加元素 */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); Console.WriteLine("添加元素后 nums = " + string.Join(",", nums)); /* 在中间插入元素 */ nums.Insert(3, 6); Console.WriteLine("在索引 3 处插入数字 6 ,得到 nums = " + string.Join(",", nums)); /* 删除元素 */ nums.RemoveAt(3); Console.WriteLine("删除索引 3 处的元素,得到 nums = " + string.Join(",", nums)); /* 通过索引遍历列表 */ int count = 0; for (int i = 0; i < nums.Count; i++) { count += nums[i]; } /* 直接遍历列表元素 */ count = 0; foreach (int x in nums) { count += x; } /* 拼接两个列表 */ List nums1 = [6, 8, 7, 10, 9]; nums.AddRange(nums1); Console.WriteLine("将列表 nums1 拼接到 nums 之后,得到 nums = " + string.Join(",", nums)); /* 排序列表 */ nums.Sort(); // 排序后,列表元素从小到大排列 Console.WriteLine("排序列表后 nums = " + string.Join(",", nums)); } } ================================================ FILE: codes/csharp/chapter_array_and_linkedlist/my_list.cs ================================================ /** * File: my_list.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_array_and_linkedlist; /* 列表类 */ class MyList { private int[] arr; // 数组(存储列表元素) private int arrCapacity = 10; // 列表容量 private int arrSize = 0; // 列表长度(当前元素数量) private readonly int extendRatio = 2; // 每次列表扩容的倍数 /* 构造方法 */ public MyList() { arr = new int[arrCapacity]; } /* 获取列表长度(当前元素数量)*/ public int Size() { return arrSize; } /* 获取列表容量 */ public int Capacity() { return arrCapacity; } /* 访问元素 */ public int Get(int index) { // 索引如果越界,则抛出异常,下同 if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("索引越界"); return arr[index]; } /* 更新元素 */ public void Set(int index, int num) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("索引越界"); arr[index] = num; } /* 在尾部添加元素 */ public void Add(int num) { // 元素数量超出容量时,触发扩容机制 if (arrSize == arrCapacity) ExtendCapacity(); arr[arrSize] = num; // 更新元素数量 arrSize++; } /* 在中间插入元素 */ public void Insert(int index, int num) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("索引越界"); // 元素数量超出容量时,触发扩容机制 if (arrSize == arrCapacity) ExtendCapacity(); // 将索引 index 以及之后的元素都向后移动一位 for (int j = arrSize - 1; j >= index; j--) { arr[j + 1] = arr[j]; } arr[index] = num; // 更新元素数量 arrSize++; } /* 删除元素 */ public int Remove(int index) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("索引越界"); int num = arr[index]; // 将将索引 index 之后的元素都向前移动一位 for (int j = index; j < arrSize - 1; j++) { arr[j] = arr[j + 1]; } // 更新元素数量 arrSize--; // 返回被删除的元素 return num; } /* 列表扩容 */ public void ExtendCapacity() { // 新建一个长度为 arrCapacity * extendRatio 的数组,并将原数组复制到新数组 Array.Resize(ref arr, arrCapacity * extendRatio); // 更新列表容量 arrCapacity = arr.Length; } /* 将列表转换为数组 */ public int[] ToArray() { // 仅转换有效长度范围内的列表元素 int[] arr = new int[arrSize]; for (int i = 0; i < arrSize; i++) { arr[i] = Get(i); } return arr; } } public class my_list { [Test] public void Test() { /* 初始化列表 */ MyList nums = new(); /* 在尾部添加元素 */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); Console.WriteLine("列表 nums = " + string.Join(",", nums.ToArray()) + " ,容量 = " + nums.Capacity() + " ,长度 = " + nums.Size()); /* 在中间插入元素 */ nums.Insert(3, 6); Console.WriteLine("在索引 3 处插入数字 6 ,得到 nums = " + string.Join(",", nums.ToArray())); /* 删除元素 */ nums.Remove(3); Console.WriteLine("删除索引 3 处的元素,得到 nums = " + string.Join(",", nums.ToArray())); /* 访问元素 */ int num = nums.Get(1); Console.WriteLine("访问索引 1 处的元素,得到 num = " + num); /* 更新元素 */ nums.Set(1, 0); Console.WriteLine("将索引 1 处的元素更新为 0 ,得到 nums = " + string.Join(",", nums.ToArray())); /* 测试扩容机制 */ for (int i = 0; i < 10; i++) { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 nums.Add(i); } Console.WriteLine("扩容后的列表 nums = " + string.Join(",", nums.ToArray()) + " ,容量 = " + nums.Capacity() + " ,长度 = " + nums.Size()); } } ================================================ FILE: codes/csharp/chapter_backtracking/n_queens.cs ================================================ /** * File: n_queens.cs * Created Time: 2023-05-04 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class n_queens { /* 回溯算法:n 皇后 */ void Backtrack(int row, int n, List> state, List>> res, bool[] cols, bool[] diags1, bool[] diags2) { // 当放置完所有行时,记录解 if (row == n) { List> copyState = []; foreach (List sRow in state) { copyState.Add(new List(sRow)); } res.Add(copyState); return; } // 遍历所有列 for (int col = 0; col < n; col++) { // 计算该格子对应的主对角线和次对角线 int diag1 = row - col + n - 1; int diag2 = row + col; // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 尝试:将皇后放置在该格子 state[row][col] = "Q"; cols[col] = diags1[diag1] = diags2[diag2] = true; // 放置下一行 Backtrack(row + 1, n, state, res, cols, diags1, diags2); // 回退:将该格子恢复为空位 state[row][col] = "#"; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* 求解 n 皇后 */ List>> NQueens(int n) { // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 List> state = []; for (int i = 0; i < n; i++) { List row = []; for (int j = 0; j < n; j++) { row.Add("#"); } state.Add(row); } bool[] cols = new bool[n]; // 记录列是否有皇后 bool[] diags1 = new bool[2 * n - 1]; // 记录主对角线上是否有皇后 bool[] diags2 = new bool[2 * n - 1]; // 记录次对角线上是否有皇后 List>> res = []; Backtrack(0, n, state, res, cols, diags1, diags2); return res; } [Test] public void Test() { int n = 4; List>> res = NQueens(n); Console.WriteLine("输入棋盘长宽为 " + n); Console.WriteLine("皇后放置方案共有 " + res.Count + " 种"); foreach (List> state in res) { Console.WriteLine("--------------------"); foreach (List row in state) { PrintUtil.PrintList(row); } } } } ================================================ FILE: codes/csharp/chapter_backtracking/permutations_i.cs ================================================ /** * File: permutations_i.cs * Created Time: 2023-04-24 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class permutations_i { /* 回溯算法:全排列 I */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // 当状态长度等于元素数量时,记录解 if (state.Count == choices.Length) { res.Add(new List(state)); return; } // 遍历所有选择 for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // 剪枝:不允许重复选择元素 if (!selected[i]) { // 尝试:做出选择,更新状态 selected[i] = true; state.Add(choice); // 进行下一轮选择 Backtrack(state, choices, selected, res); // 回退:撤销选择,恢复到之前的状态 selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* 全排列 I */ List> PermutationsI(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } [Test] public void Test() { int[] nums = [1, 2, 3]; List> res = PermutationsI(nums); Console.WriteLine("输入数组 nums = " + string.Join(", ", nums)); Console.WriteLine("所有排列 res = "); foreach (List permutation in res) { PrintUtil.PrintList(permutation); } } } ================================================ FILE: codes/csharp/chapter_backtracking/permutations_ii.cs ================================================ /** * File: permutations_ii.cs * Created Time: 2023-04-24 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class permutations_ii { /* 回溯算法:全排列 II */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // 当状态长度等于元素数量时,记录解 if (state.Count == choices.Length) { res.Add(new List(state)); return; } // 遍历所有选择 HashSet duplicated = []; for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 if (!selected[i] && !duplicated.Contains(choice)) { // 尝试:做出选择,更新状态 duplicated.Add(choice); // 记录选择过的元素值 selected[i] = true; state.Add(choice); // 进行下一轮选择 Backtrack(state, choices, selected, res); // 回退:撤销选择,恢复到之前的状态 selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* 全排列 II */ List> PermutationsII(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } [Test] public void Test() { int[] nums = [1, 2, 2]; List> res = PermutationsII(nums); Console.WriteLine("输入数组 nums = " + string.Join(", ", nums)); Console.WriteLine("所有排列 res = "); foreach (List permutation in res) { PrintUtil.PrintList(permutation); } } } ================================================ FILE: codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs ================================================ /** * File: preorder_traversal_i_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_i_compact { List res = []; /* 前序遍历:例题一 */ void PreOrder(TreeNode? root) { if (root == null) { return; } if (root.val == 7) { // 记录解 res.Add(root); } PreOrder(root.left); PreOrder(root.right); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\n初始化二叉树"); PrintUtil.PrintTree(root); // 前序遍历 PreOrder(root); Console.WriteLine("\n输出所有值为 7 的节点"); PrintUtil.PrintList(res.Select(p => p.val).ToList()); } } ================================================ FILE: codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs ================================================ /** * File: preorder_traversal_ii_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_ii_compact { List path = []; List> res = []; /* 前序遍历:例题二 */ void PreOrder(TreeNode? root) { if (root == null) { return; } // 尝试 path.Add(root); if (root.val == 7) { // 记录解 res.Add(new List(path)); } PreOrder(root.left); PreOrder(root.right); // 回退 path.RemoveAt(path.Count - 1); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\n初始化二叉树"); PrintUtil.PrintTree(root); // 前序遍历 PreOrder(root); Console.WriteLine("\n输出所有根节点到节点 7 的路径"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs ================================================ /** * File: preorder_traversal_iii_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_iii_compact { List path = []; List> res = []; /* 前序遍历:例题三 */ void PreOrder(TreeNode? root) { // 剪枝 if (root == null || root.val == 3) { return; } // 尝试 path.Add(root); if (root.val == 7) { // 记录解 res.Add(new List(path)); } PreOrder(root.left); PreOrder(root.right); // 回退 path.RemoveAt(path.Count - 1); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\n初始化二叉树"); PrintUtil.PrintTree(root); // 前序遍历 PreOrder(root); Console.WriteLine("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs ================================================ /** * File: preorder_traversal_iii_template.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_iii_template { /* 判断当前状态是否为解 */ bool IsSolution(List state) { return state.Count != 0 && state[^1].val == 7; } /* 记录解 */ void RecordSolution(List state, List> res) { res.Add(new List(state)); } /* 判断在当前状态下,该选择是否合法 */ bool IsValid(List state, TreeNode choice) { return choice != null && choice.val != 3; } /* 更新状态 */ void MakeChoice(List state, TreeNode choice) { state.Add(choice); } /* 恢复状态 */ void UndoChoice(List state, TreeNode choice) { state.RemoveAt(state.Count - 1); } /* 回溯算法:例题三 */ void Backtrack(List state, List choices, List> res) { // 检查是否为解 if (IsSolution(state)) { // 记录解 RecordSolution(state, res); } // 遍历所有选择 foreach (TreeNode choice in choices) { // 剪枝:检查选择是否合法 if (IsValid(state, choice)) { // 尝试:做出选择,更新状态 MakeChoice(state, choice); // 进行下一轮选择 Backtrack(state, [choice.left!, choice.right!], res); // 回退:撤销选择,恢复到之前的状态 UndoChoice(state, choice); } } } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\n初始化二叉树"); PrintUtil.PrintTree(root); // 回溯算法 List> res = []; List choices = [root!]; Backtrack([], choices, res); Console.WriteLine("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: codes/csharp/chapter_backtracking/subset_sum_i.cs ================================================ /** * File: subset_sum_i.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_i { /* 回溯算法:子集和 I */ void Backtrack(List state, int target, int[] choices, int start, List> res) { // 子集和等于 target 时,记录解 if (target == 0) { res.Add(new List(state)); return; } // 遍历所有选择 // 剪枝二:从 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.RemoveAt(state.Count - 1); } } /* 求解子集和 I */ List> SubsetSumI(int[] nums, int target) { List state = []; // 状态(子集) Array.Sort(nums); // 对 nums 进行排序 int start = 0; // 遍历起始点 List> res = []; // 结果列表(子集列表) Backtrack(state, target, nums, start, res); return res; } [Test] public void Test() { int[] nums = [3, 4, 5]; int target = 9; List> res = SubsetSumI(nums, target); Console.WriteLine("输入数组 nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("所有和等于 " + target + " 的子集 res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } } } ================================================ FILE: codes/csharp/chapter_backtracking/subset_sum_i_naive.cs ================================================ /** * File: subset_sum_i_naive.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_i_naive { /* 回溯算法:子集和 I */ void Backtrack(List state, int target, int total, int[] choices, List> res) { // 子集和等于 target 时,记录解 if (total == target) { res.Add(new List(state)); return; } // 遍历所有选择 for (int i = 0; i < choices.Length; i++) { // 剪枝:若子集和超过 target ,则跳过该选择 if (total + choices[i] > target) { continue; } // 尝试:做出选择,更新元素和 total state.Add(choices[i]); // 进行下一轮选择 Backtrack(state, target, total + choices[i], choices, res); // 回退:撤销选择,恢复到之前的状态 state.RemoveAt(state.Count - 1); } } /* 求解子集和 I(包含重复子集) */ List> SubsetSumINaive(int[] nums, int target) { List state = []; // 状态(子集) int total = 0; // 子集和 List> res = []; // 结果列表(子集列表) Backtrack(state, target, total, nums, res); return res; } [Test] public void Test() { int[] nums = [3, 4, 5]; int target = 9; List> res = SubsetSumINaive(nums, target); Console.WriteLine("输入数组 nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("所有和等于 " + target + " 的子集 res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } Console.WriteLine("请注意,该方法输出的结果包含重复集合"); } } ================================================ FILE: codes/csharp/chapter_backtracking/subset_sum_ii.cs ================================================ /** * File: subset_sum_ii.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_ii { /* 回溯算法:子集和 II */ void Backtrack(List state, int target, int[] choices, int start, List> res) { // 子集和等于 target 时,记录解 if (target == 0) { res.Add(new List(state)); return; } // 遍历所有选择 // 剪枝二:从 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.RemoveAt(state.Count - 1); } } /* 求解子集和 II */ List> SubsetSumII(int[] nums, int target) { List state = []; // 状态(子集) Array.Sort(nums); // 对 nums 进行排序 int start = 0; // 遍历起始点 List> res = []; // 结果列表(子集列表) Backtrack(state, target, nums, start, res); return res; } [Test] public void Test() { int[] nums = [4, 4, 5]; int target = 9; List> res = SubsetSumII(nums, target); Console.WriteLine("输入数组 nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("所有和等于 " + target + " 的子集 res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } } } ================================================ FILE: codes/csharp/chapter_computational_complexity/iteration.cs ================================================ /** * File: iteration.cs * Created Time: 2023-08-28 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_computational_complexity; public class iteration { /* for 循环 */ int ForLoop(int n) { int res = 0; // 循环求和 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } /* while 循环 */ int WhileLoop(int n) { int res = 0; int i = 1; // 初始化条件变量 // 循环求和 1, 2, ..., n-1, n while (i <= n) { res += i; i += 1; // 更新条件变量 } return res; } /* while 循环(两次更新) */ int WhileLoopII(int n) { int res = 0; int i = 1; // 初始化条件变量 // 循环求和 1, 4, 10, ... while (i <= n) { res += i; // 更新条件变量 i += 1; i *= 2; } return res; } /* 双层 for 循环 */ string NestedForLoop(int n) { StringBuilder res = new(); // 循环 i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // 循环 j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { res.Append($"({i}, {j}), "); } } return res.ToString(); } /* Driver Code */ [Test] public void Test() { int n = 5; int res; res = ForLoop(n); Console.WriteLine("\nfor 循环的求和结果 res = " + res); res = WhileLoop(n); Console.WriteLine("\nwhile 循环的求和结果 res = " + res); res = WhileLoopII(n); Console.WriteLine("\nwhile 循环(两次更新)求和结果 res = " + res); string resStr = NestedForLoop(n); Console.WriteLine("\n双层 for 循环的遍历结果 " + resStr); } } ================================================ FILE: codes/csharp/chapter_computational_complexity/recursion.cs ================================================ /** * File: recursion.cs * Created Time: 2023-08-28 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_computational_complexity; public class recursion { /* 递归 */ int Recur(int n) { // 终止条件 if (n == 1) return 1; // 递:递归调用 int res = Recur(n - 1); // 归:返回结果 return n + res; } /* 使用迭代模拟递归 */ int ForLoopRecur(int n) { // 使用一个显式的栈来模拟系统调用栈 Stack stack = new(); int res = 0; // 递:递归调用 for (int i = n; i > 0; i--) { // 通过“入栈操作”模拟“递” stack.Push(i); } // 归:返回结果 while (stack.Count > 0) { // 通过“出栈操作”模拟“归” res += stack.Pop(); } // res = 1+2+3+...+n return res; } /* 尾递归 */ int TailRecur(int n, int res) { // 终止条件 if (n == 0) return res; // 尾递归调用 return TailRecur(n - 1, res + n); } /* 斐波那契数列:递归 */ int Fib(int n) { // 终止条件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // 递归调用 f(n) = f(n-1) + f(n-2) int res = Fib(n - 1) + Fib(n - 2); // 返回结果 f(n) return res; } /* Driver Code */ [Test] public void Test() { int n = 5; int res; res = Recur(n); Console.WriteLine("\n递归函数的求和结果 res = " + res); res = ForLoopRecur(n); Console.WriteLine("\n使用迭代模拟递归求和结果 res = " + res); res = TailRecur(n, 0); Console.WriteLine("\n尾递归函数的求和结果 res = " + res); res = Fib(n); Console.WriteLine("\n斐波那契数列的第 " + n + " 项为 " + res); } } ================================================ FILE: codes/csharp/chapter_computational_complexity/space_complexity.cs ================================================ /** * File: space_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class space_complexity { /* 函数 */ int Function() { // 执行某些操作 return 0; } /* 常数阶 */ void Constant(int n) { // 常量、变量、对象占用 O(1) 空间 int a = 0; int b = 0; int[] nums = new int[10000]; ListNode node = new(0); // 循环中的变量占用 O(1) 空间 for (int i = 0; i < n; i++) { int c = 0; } // 循环中的函数占用 O(1) 空间 for (int i = 0; i < n; i++) { Function(); } } /* 线性阶 */ void Linear(int n) { // 长度为 n 的数组占用 O(n) 空间 int[] nums = new int[n]; // 长度为 n 的列表占用 O(n) 空间 List nodes = []; for (int i = 0; i < n; i++) { nodes.Add(new ListNode(i)); } // 长度为 n 的哈希表占用 O(n) 空间 Dictionary map = []; for (int i = 0; i < n; i++) { map.Add(i, i.ToString()); } } /* 线性阶(递归实现) */ void LinearRecur(int n) { Console.WriteLine("递归 n = " + n); if (n == 1) return; LinearRecur(n - 1); } /* 平方阶 */ void Quadratic(int n) { // 矩阵占用 O(n^2) 空间 int[,] numMatrix = new int[n, n]; // 二维列表占用 O(n^2) 空间 List> numList = []; for (int i = 0; i < n; i++) { List tmp = []; for (int j = 0; j < n; j++) { tmp.Add(0); } numList.Add(tmp); } } /* 平方阶(递归实现) */ int QuadraticRecur(int n) { if (n <= 0) return 0; int[] nums = new int[n]; Console.WriteLine("递归 n = " + n + " 中的 nums 长度 = " + nums.Length); return QuadraticRecur(n - 1); } /* 指数阶(建立满二叉树) */ TreeNode? BuildTree(int n) { if (n == 0) return null; TreeNode root = new(0) { left = BuildTree(n - 1), right = BuildTree(n - 1) }; return root; } [Test] public void Test() { int n = 5; // 常数阶 Constant(n); // 线性阶 Linear(n); LinearRecur(n); // 平方阶 Quadratic(n); QuadraticRecur(n); // 指数阶 TreeNode? root = BuildTree(n); PrintUtil.PrintTree(root); } } ================================================ FILE: codes/csharp/chapter_computational_complexity/time_complexity.cs ================================================ /** * File: time_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class time_complexity { void Algorithm(int n) { int a = 1; // +0(技巧 1) a += n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { Console.WriteLine(0); } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { Console.WriteLine(0); } } } // 算法 A 时间复杂度:常数阶 void AlgorithmA(int n) { Console.WriteLine(0); } // 算法 B 时间复杂度:线性阶 void AlgorithmB(int n) { for (int i = 0; i < n; i++) { Console.WriteLine(0); } } // 算法 C 时间复杂度:常数阶 void AlgorithmC(int n) { for (int i = 0; i < 1000000; i++) { Console.WriteLine(0); } } /* 常数阶 */ int Constant(int n) { int count = 0; int size = 100000; for (int i = 0; i < size; i++) count++; return count; } /* 线性阶 */ int Linear(int n) { int count = 0; for (int i = 0; i < n; i++) count++; return count; } /* 线性阶(遍历数组) */ int ArrayTraversal(int[] nums) { int count = 0; // 循环次数与数组长度成正比 foreach (int num in nums) { count++; } return count; } /* 平方阶 */ int Quadratic(int n) { int count = 0; // 循环次数与数据大小 n 成平方关系 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* 平方阶(冒泡排序) */ int BubbleSort(int[] nums) { int count = 0; // 计数器 // 外循环:未排序区间为 [0, i] for (int i = nums.Length - 1; i > 0; i--) { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); count += 3; // 元素交换包含 3 个单元操作 } } } return count; } /* 指数阶(循环实现) */ int Exponential(int n) { int count = 0, bas = 1; // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for (int i = 0; i < n; i++) { for (int j = 0; j < bas; j++) { count++; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指数阶(递归实现) */ int ExpRecur(int n) { if (n == 1) return 1; return ExpRecur(n - 1) + ExpRecur(n - 1) + 1; } /* 对数阶(循环实现) */ int Logarithmic(int n) { int count = 0; while (n > 1) { n /= 2; count++; } return count; } /* 对数阶(递归实现) */ int LogRecur(int n) { if (n <= 1) return 0; return LogRecur(n / 2) + 1; } /* 线性对数阶 */ int LinearLogRecur(int n) { if (n <= 1) return 1; int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2); for (int i = 0; i < n; i++) { count++; } return count; } /* 阶乘阶(递归实现) */ int FactorialRecur(int n) { if (n == 0) return 1; int count = 0; // 从 1 个分裂出 n 个 for (int i = 0; i < n; i++) { count += FactorialRecur(n - 1); } return count; } [Test] public void Test() { // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 int n = 8; Console.WriteLine("输入数据大小 n = " + n); int count = Constant(n); Console.WriteLine("常数阶的操作数量 = " + count); count = Linear(n); Console.WriteLine("线性阶的操作数量 = " + count); count = ArrayTraversal(new int[n]); Console.WriteLine("线性阶(遍历数组)的操作数量 = " + count); count = Quadratic(n); Console.WriteLine("平方阶的操作数量 = " + count); int[] nums = new int[n]; for (int i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = BubbleSort(nums); Console.WriteLine("平方阶(冒泡排序)的操作数量 = " + count); count = Exponential(n); Console.WriteLine("指数阶(循环实现)的操作数量 = " + count); count = ExpRecur(n); Console.WriteLine("指数阶(递归实现)的操作数量 = " + count); count = Logarithmic(n); Console.WriteLine("对数阶(循环实现)的操作数量 = " + count); count = LogRecur(n); Console.WriteLine("对数阶(递归实现)的操作数量 = " + count); count = LinearLogRecur(n); Console.WriteLine("线性对数阶(递归实现)的操作数量 = " + count); count = FactorialRecur(n); Console.WriteLine("阶乘阶(递归实现)的操作数量 = " + count); } } ================================================ FILE: codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs ================================================ /** * File: worst_best_time_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class worst_best_time_complexity { /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ int[] RandomNumbers(int n) { int[] nums = new int[n]; // 生成数组 nums = { 1, 2, 3, ..., n } for (int i = 0; i < n; i++) { nums[i] = i + 1; } // 随机打乱数组元素 for (int i = 0; i < nums.Length; i++) { int index = new Random().Next(i, nums.Length); (nums[i], nums[index]) = (nums[index], nums[i]); } return nums; } /* 查找数组 nums 中数字 1 所在索引 */ int FindOne(int[] nums) { for (int i = 0; i < nums.Length; i++) { // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if (nums[i] == 1) return i; } return -1; } /* Driver Code */ [Test] public void Test() { for (int i = 0; i < 10; i++) { int n = 100; int[] nums = RandomNumbers(n); int index = FindOne(nums); Console.WriteLine("\n数组 [ 1, 2, ..., n ] 被打乱后 = " + string.Join(",", nums)); Console.WriteLine("数字 1 的索引为 " + index); } } } ================================================ FILE: codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs ================================================ /** * File: binary_search_recur.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class binary_search_recur { /* 二分查找:问题 f(i, j) */ int DFS(int[] nums, int target, int i, int j) { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1; } // 计算中点索引 m int m = (i + j) / 2; if (nums[m] < target) { // 递归子问题 f(m+1, j) return DFS(nums, target, m + 1, j); } else if (nums[m] > target) { // 递归子问题 f(i, m-1) return DFS(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ int BinarySearch(int[] nums, int target) { int n = nums.Length; // 求解问题 f(0, n-1) return DFS(nums, target, 0, n - 1); } [Test] public void Test() { int target = 6; int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分查找(双闭区间) int index = BinarySearch(nums, target); Console.WriteLine("目标元素 6 的索引 = " + index); } } ================================================ FILE: codes/csharp/chapter_divide_and_conquer/build_tree.cs ================================================ /** * File: build_tree.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class build_tree { /* 构建二叉树:分治 */ TreeNode? DFS(int[] preorder, Dictionary inorderMap, int i, int l, int r) { // 子树区间为空时终止 if (r - l < 0) return null; // 初始化根节点 TreeNode root = new(preorder[i]); // 查询 m ,从而划分左右子树 int m = inorderMap[preorder[i]]; // 子问题:构建左子树 root.left = DFS(preorder, inorderMap, i + 1, l, m - 1); // 子问题:构建右子树 root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r); // 返回根节点 return root; } /* 构建二叉树 */ TreeNode? BuildTree(int[] preorder, int[] inorder) { // 初始化哈希表,存储 inorder 元素到索引的映射 Dictionary inorderMap = []; for (int i = 0; i < inorder.Length; i++) { inorderMap.TryAdd(inorder[i], i); } TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1); return root; } [Test] public void Test() { int[] preorder = [3, 9, 2, 1, 7]; int[] inorder = [9, 3, 1, 2, 7]; Console.WriteLine("前序遍历 = " + string.Join(", ", preorder)); Console.WriteLine("中序遍历 = " + string.Join(", ", inorder)); TreeNode? root = BuildTree(preorder, inorder); Console.WriteLine("构建的二叉树为:"); PrintUtil.PrintTree(root); } } ================================================ FILE: codes/csharp/chapter_divide_and_conquer/hanota.cs ================================================ /** * File: hanota.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class hanota { /* 移动一个圆盘 */ void Move(List src, List tar) { // 从 src 顶部拿出一个圆盘 int pan = src[^1]; src.RemoveAt(src.Count - 1); // 将圆盘放入 tar 顶部 tar.Add(pan); } /* 求解汉诺塔问题 f(i) */ void DFS(int i, List src, List buf, List tar) { // 若 src 只剩下一个圆盘,则直接将其移到 tar if (i == 1) { Move(src, tar); return; } // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf DFS(i - 1, src, tar, buf); // 子问题 f(1) :将 src 剩余一个圆盘移到 tar Move(src, tar); // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar DFS(i - 1, buf, src, tar); } /* 求解汉诺塔问题 */ void SolveHanota(List A, List B, List C) { int n = A.Count; // 将 A 顶部 n 个圆盘借助 B 移到 C DFS(n, A, B, C); } [Test] public void Test() { // 列表尾部是柱子顶部 List A = [5, 4, 3, 2, 1]; List B = []; List C = []; Console.WriteLine("初始状态下:"); Console.WriteLine("A = " + string.Join(", ", A)); Console.WriteLine("B = " + string.Join(", ", B)); Console.WriteLine("C = " + string.Join(", ", C)); SolveHanota(A, B, C); Console.WriteLine("圆盘移动完成后:"); Console.WriteLine("A = " + string.Join(", ", A)); Console.WriteLine("B = " + string.Join(", ", B)); Console.WriteLine("C = " + string.Join(", ", C)); } } ================================================ FILE: codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs ================================================ /** * File: climbing_stairs_backtrack.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_backtrack { /* 回溯 */ void Backtrack(List choices, int state, int n, List res) { // 当爬到第 n 阶时,方案数量加 1 if (state == n) res[0]++; // 遍历所有选择 foreach (int choice in choices) { // 剪枝:不允许越过第 n 阶 if (state + choice > n) continue; // 尝试:做出选择,更新状态 Backtrack(choices, state + choice, n, res); // 回退 } } /* 爬楼梯:回溯 */ int ClimbingStairsBacktrack(int n) { List choices = [1, 2]; // 可选择向上爬 1 阶或 2 阶 int state = 0; // 从第 0 阶开始爬 List res = [0]; // 使用 res[0] 记录方案数量 Backtrack(choices, state, n, res); return res[0]; } [Test] public void Test() { int n = 9; int res = ClimbingStairsBacktrack(n); Console.WriteLine($"爬 {n} 阶楼梯共有 {res} 种方案"); } } ================================================ FILE: codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs ================================================ /** * File: climbing_stairs_constraint_dp.cs * Created Time: 2023-07-03 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_constraint_dp { /* 带约束爬楼梯:动态规划 */ int ClimbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // 初始化 dp 表,用于存储子问题的解 int[,] dp = new int[n + 1, 3]; // 初始状态:预设最小子问题的解 dp[1, 1] = 1; dp[1, 2] = 0; dp[2, 1] = 0; dp[2, 2] = 1; // 状态转移:从较小子问题逐步求解较大子问题 for (int i = 3; i <= n; i++) { dp[i, 1] = dp[i - 1, 2]; dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2]; } return dp[n, 1] + dp[n, 2]; } [Test] public void Test() { int n = 9; int res = ClimbingStairsConstraintDP(n); Console.WriteLine($"爬 {n} 阶楼梯共有 {res} 种方案"); } } ================================================ FILE: codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs ================================================ /** * File: climbing_stairs_dfs.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dfs { /* 搜索 */ int DFS(int i) { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] int count = DFS(i - 1) + DFS(i - 2); return count; } /* 爬楼梯:搜索 */ int ClimbingStairsDFS(int n) { return DFS(n); } [Test] public void Test() { int n = 9; int res = ClimbingStairsDFS(n); Console.WriteLine($"爬 {n} 阶楼梯共有 {res} 种方案"); } } ================================================ FILE: codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs ================================================ /** * File: climbing_stairs_dfs_mem.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dfs_mem { /* 记忆化搜索 */ int DFS(int i, int[] mem) { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i; // 若存在记录 dp[i] ,则直接返回之 if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = DFS(i - 1, mem) + DFS(i - 2, mem); // 记录 dp[i] mem[i] = count; return count; } /* 爬楼梯:记忆化搜索 */ int ClimbingStairsDFSMem(int n) { // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 int[] mem = new int[n + 1]; Array.Fill(mem, -1); return DFS(n, mem); } [Test] public void Test() { int n = 9; int res = ClimbingStairsDFSMem(n); Console.WriteLine($"爬 {n} 阶楼梯共有 {res} 种方案"); } } ================================================ FILE: codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs ================================================ /** * File: climbing_stairs_dp.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dp { /* 爬楼梯:动态规划 */ int ClimbingStairsDP(int n) { if (n == 1 || n == 2) return n; // 初始化 dp 表,用于存储子问题的解 int[] dp = new int[n + 1]; // 初始状态:预设最小子问题的解 dp[1] = 1; dp[2] = 2; // 状态转移:从较小子问题逐步求解较大子问题 for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* 爬楼梯:空间优化后的动态规划 */ int ClimbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } [Test] public void Test() { int n = 9; int res = ClimbingStairsDP(n); Console.WriteLine($"爬 {n} 阶楼梯共有 {res} 种方案"); res = ClimbingStairsDPComp(n); Console.WriteLine($"爬 {n} 阶楼梯共有 {res} 种方案"); } } ================================================ FILE: codes/csharp/chapter_dynamic_programming/coin_change.cs ================================================ /** * File: coin_change.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class coin_change { /* 零钱兑换:动态规划 */ int CoinChangeDP(int[] coins, int amt) { int n = coins.Length; int MAX = amt + 1; // 初始化 dp 表 int[,] dp = new int[n + 1, amt + 1]; // 状态转移:首行首列 for (int a = 1; a <= amt; a++) { dp[0, a] = MAX; } // 状态转移:其余行和列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i, a] = dp[i - 1, a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1); } } } return dp[n, amt] != MAX ? dp[n, amt] : -1; } /* 零钱兑换:空间优化后的动态规划 */ int CoinChangeDPComp(int[] coins, int amt) { int n = coins.Length; int MAX = amt + 1; // 初始化 dp 表 int[] dp = new int[amt + 1]; Array.Fill(dp, MAX); dp[0] = 0; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } [Test] public void Test() { int[] coins = [1, 2, 5]; int amt = 4; // 动态规划 int res = CoinChangeDP(coins, amt); Console.WriteLine("凑到目标金额所需的最少硬币数量为 " + res); // 空间优化后的动态规划 res = CoinChangeDPComp(coins, amt); Console.WriteLine("凑到目标金额所需的最少硬币数量为 " + res); } } ================================================ FILE: codes/csharp/chapter_dynamic_programming/coin_change_ii.cs ================================================ /** * File: coin_change_ii.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class coin_change_ii { /* 零钱兑换 II:动态规划 */ int CoinChangeIIDP(int[] coins, int amt) { int n = coins.Length; // 初始化 dp 表 int[,] dp = new int[n + 1, amt + 1]; // 初始化首列 for (int i = 0; i <= n; i++) { dp[i, 0] = 1; } // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i, a] = dp[i - 1, a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]]; } } } return dp[n, amt]; } /* 零钱兑换 II:空间优化后的动态规划 */ int CoinChangeIIDPComp(int[] coins, int amt) { int n = coins.Length; // 初始化 dp 表 int[] dp = new int[amt + 1]; dp[0] = 1; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } [Test] public void Test() { int[] coins = [1, 2, 5]; int amt = 5; // 动态规划 int res = CoinChangeIIDP(coins, amt); Console.WriteLine("凑出目标金额的硬币组合数量为 " + res); // 空间优化后的动态规划 res = CoinChangeIIDPComp(coins, amt); Console.WriteLine("凑出目标金额的硬币组合数量为 " + res); } } ================================================ FILE: codes/csharp/chapter_dynamic_programming/edit_distance.cs ================================================ /** * File: edit_distance.cs * Created Time: 2023-07-14 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class edit_distance { /* 编辑距离:暴力搜索 */ int EditDistanceDFS(string s, string t, int i, int j) { // 若 s 和 t 都为空,则返回 0 if (i == 0 && j == 0) return 0; // 若 s 为空,则返回 t 长度 if (i == 0) return j; // 若 t 为空,则返回 s 长度 if (j == 0) return i; // 若两字符相等,则直接跳过此两字符 if (s[i - 1] == t[j - 1]) return EditDistanceDFS(s, t, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 int insert = EditDistanceDFS(s, t, i, j - 1); int delete = EditDistanceDFS(s, t, i - 1, j); int replace = EditDistanceDFS(s, t, i - 1, j - 1); // 返回最少编辑步数 return Math.Min(Math.Min(insert, delete), replace) + 1; } /* 编辑距离:记忆化搜索 */ int EditDistanceDFSMem(string s, string t, int[][] mem, int i, int j) { // 若 s 和 t 都为空,则返回 0 if (i == 0 && j == 0) return 0; // 若 s 为空,则返回 t 长度 if (i == 0) return j; // 若 t 为空,则返回 s 长度 if (j == 0) return i; // 若已有记录,则直接返回之 if (mem[i][j] != -1) return mem[i][j]; // 若两字符相等,则直接跳过此两字符 if (s[i - 1] == t[j - 1]) return EditDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 int insert = EditDistanceDFSMem(s, t, mem, i, j - 1); int delete = EditDistanceDFSMem(s, t, mem, i - 1, j); int replace = EditDistanceDFSMem(s, t, mem, i - 1, j - 1); // 记录并返回最少编辑步数 mem[i][j] = Math.Min(Math.Min(insert, delete), replace) + 1; return mem[i][j]; } /* 编辑距离:动态规划 */ int EditDistanceDP(string s, string t) { int n = s.Length, m = t.Length; int[,] dp = new int[n + 1, m + 1]; // 状态转移:首行首列 for (int i = 1; i <= n; i++) { dp[i, 0] = i; } for (int j = 1; j <= m; j++) { dp[0, j] = j; } // 状态转移:其余行和列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // 若两字符相等,则直接跳过此两字符 dp[i, j] = dp[i - 1, j - 1]; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1; } } } return dp[n, m]; } /* 编辑距离:空间优化后的动态规划 */ int EditDistanceDPComp(string s, string t) { int n = s.Length, m = t.Length; int[] dp = new int[m + 1]; // 状态转移:首行 for (int j = 1; j <= m; j++) { dp[j] = j; } // 状态转移:其余行 for (int i = 1; i <= n; i++) { // 状态转移:首列 int leftup = dp[0]; // 暂存 dp[i-1, j-1] dp[0] = i; // 状态转移:其余列 for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 若两字符相等,则直接跳过此两字符 dp[j] = leftup; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 更新为下一轮的 dp[i-1, j-1] } } return dp[m]; } [Test] public void Test() { string s = "bag"; string t = "pack"; int n = s.Length, m = t.Length; // 暴力搜索 int res = EditDistanceDFS(s, t, n, m); Console.WriteLine("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步"); // 记忆化搜索 int[][] mem = new int[n + 1][]; for (int i = 0; i <= n; i++) { mem[i] = new int[m + 1]; Array.Fill(mem[i], -1); } res = EditDistanceDFSMem(s, t, mem, n, m); Console.WriteLine("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步"); // 动态规划 res = EditDistanceDP(s, t); Console.WriteLine("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步"); // 空间优化后的动态规划 res = EditDistanceDPComp(s, t); Console.WriteLine("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步"); } } ================================================ FILE: codes/csharp/chapter_dynamic_programming/knapsack.cs ================================================ /** * File: knapsack.cs * Created Time: 2023-07-07 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class knapsack { /* 0-1 背包:暴力搜索 */ int KnapsackDFS(int[] weight, int[] val, int i, int c) { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i == 0 || c == 0) { return 0; } // 若超过背包容量,则只能选择不放入背包 if (weight[i - 1] > c) { return KnapsackDFS(weight, val, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 int no = KnapsackDFS(weight, val, i - 1, c); int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1]; // 返回两种方案中价值更大的那一个 return Math.Max(no, yes); } /* 0-1 背包:记忆化搜索 */ int KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i == 0 || c == 0) { return 0; } // 若已有记录,则直接返回 if (mem[i][c] != -1) { return mem[i][c]; } // 若超过背包容量,则只能选择不放入背包 if (weight[i - 1] > c) { return KnapsackDFSMem(weight, val, mem, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 int no = KnapsackDFSMem(weight, val, mem, i - 1, c); int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1]; // 记录并返回两种方案中价值更大的那一个 mem[i][c] = Math.Max(no, yes); return mem[i][c]; } /* 0-1 背包:动态规划 */ int KnapsackDP(int[] weight, int[] val, int cap) { int n = weight.Length; // 初始化 dp 表 int[,] dp = new int[n + 1, cap + 1]; // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (weight[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i, c] = dp[i - 1, c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]); } } } return dp[n, cap]; } /* 0-1 背包:空间优化后的动态规划 */ int KnapsackDPComp(int[] weight, int[] val, int cap) { int n = weight.Length; // 初始化 dp 表 int[] dp = new int[cap + 1]; // 状态转移 for (int i = 1; i <= n; i++) { // 倒序遍历 for (int c = cap; c > 0; c--) { if (weight[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]); } } } return dp[cap]; } [Test] public void Test() { int[] weight = [10, 20, 30, 40, 50]; int[] val = [50, 120, 150, 210, 240]; int cap = 50; int n = weight.Length; // 暴力搜索 int res = KnapsackDFS(weight, val, n, cap); Console.WriteLine("不超过背包容量的最大物品价值为 " + res); // 记忆化搜索 int[][] mem = new int[n + 1][]; for (int i = 0; i <= n; i++) { mem[i] = new int[cap + 1]; Array.Fill(mem[i], -1); } res = KnapsackDFSMem(weight, val, mem, n, cap); Console.WriteLine("不超过背包容量的最大物品价值为 " + res); // 动态规划 res = KnapsackDP(weight, val, cap); Console.WriteLine("不超过背包容量的最大物品价值为 " + res); // 空间优化后的动态规划 res = KnapsackDPComp(weight, val, cap); Console.WriteLine("不超过背包容量的最大物品价值为 " + res); } } ================================================ FILE: codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs ================================================ /** * File: min_cost_climbing_stairs_dp.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class min_cost_climbing_stairs_dp { /* 爬楼梯最小代价:动态规划 */ int MinCostClimbingStairsDP(int[] cost) { int n = cost.Length - 1; if (n == 1 || n == 2) return cost[n]; // 初始化 dp 表,用于存储子问题的解 int[] dp = new int[n + 1]; // 初始状态:预设最小子问题的解 dp[1] = cost[1]; dp[2] = cost[2]; // 状态转移:从较小子问题逐步求解较大子问题 for (int i = 3; i <= n; i++) { dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* 爬楼梯最小代价:空间优化后的动态规划 */ int MinCostClimbingStairsDPComp(int[] cost) { int n = cost.Length - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = Math.Min(a, tmp) + cost[i]; a = tmp; } return b; } [Test] public void Test() { int[] cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; Console.WriteLine("输入楼梯的代价列表为"); PrintUtil.PrintList(cost); int res = MinCostClimbingStairsDP(cost); Console.WriteLine($"爬完楼梯的最低代价为 {res}"); res = MinCostClimbingStairsDPComp(cost); Console.WriteLine($"爬完楼梯的最低代价为 {res}"); } } ================================================ FILE: codes/csharp/chapter_dynamic_programming/min_path_sum.cs ================================================ /** * File: min_path_sum.cs * Created Time: 2023-07-10 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class min_path_sum { /* 最小路径和:暴力搜索 */ int MinPathSumDFS(int[][] grid, int i, int j) { // 若为左上角单元格,则终止搜索 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 || j < 0) { return int.MaxValue; } // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 int up = MinPathSumDFS(grid, i - 1, j); int left = MinPathSumDFS(grid, i, j - 1); // 返回从左上角到 (i, j) 的最小路径代价 return Math.Min(left, up) + grid[i][j]; } /* 最小路径和:记忆化搜索 */ int MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { // 若为左上角单元格,则终止搜索 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 || j < 0) { return int.MaxValue; } // 若已有记录,则直接返回 if (mem[i][j] != -1) { return mem[i][j]; } // 左边和上边单元格的最小路径代价 int up = MinPathSumDFSMem(grid, mem, i - 1, j); int left = MinPathSumDFSMem(grid, mem, i, j - 1); // 记录并返回左上角到 (i, j) 的最小路径代价 mem[i][j] = Math.Min(left, up) + grid[i][j]; return mem[i][j]; } /* 最小路径和:动态规划 */ int MinPathSumDP(int[][] grid) { int n = grid.Length, m = grid[0].Length; // 初始化 dp 表 int[,] dp = new int[n, m]; dp[0, 0] = grid[0][0]; // 状态转移:首行 for (int j = 1; j < m; j++) { dp[0, j] = dp[0, j - 1] + grid[0][j]; } // 状态转移:首列 for (int i = 1; i < n; i++) { dp[i, 0] = dp[i - 1, 0] + grid[i][0]; } // 状态转移:其余行和列 for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j]; } } return dp[n - 1, m - 1]; } /* 最小路径和:空间优化后的动态规划 */ int MinPathSumDPComp(int[][] grid) { int n = grid.Length, m = grid[0].Length; // 初始化 dp 表 int[] dp = new int[m]; dp[0] = grid[0][0]; // 状态转移:首行 for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 状态转移:其余行 for (int i = 1; i < n; i++) { // 状态转移:首列 dp[0] = dp[0] + grid[i][0]; // 状态转移:其余列 for (int j = 1; j < m; j++) { dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } [Test] public void Test() { int[][] grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2] ]; int n = grid.Length, m = grid[0].Length; // 暴力搜索 int res = MinPathSumDFS(grid, n - 1, m - 1); Console.WriteLine("从左上角到右下角的最小路径和为 " + res); // 记忆化搜索 int[][] mem = new int[n][]; for (int i = 0; i < n; i++) { mem[i] = new int[m]; Array.Fill(mem[i], -1); } res = MinPathSumDFSMem(grid, mem, n - 1, m - 1); Console.WriteLine("从左上角到右下角的最小路径和为 " + res); // 动态规划 res = MinPathSumDP(grid); Console.WriteLine("从左上角到右下角的最小路径和为 " + res); // 空间优化后的动态规划 res = MinPathSumDPComp(grid); Console.WriteLine("从左上角到右下角的最小路径和为 " + res); } } ================================================ FILE: codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs ================================================ /** * File: unbounded_knapsack.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class unbounded_knapsack { /* 完全背包:动态规划 */ int UnboundedKnapsackDP(int[] wgt, int[] val, int cap) { int n = wgt.Length; // 初始化 dp 表 int[,] dp = new int[n + 1, cap + 1]; // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i, c] = dp[i - 1, c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]); } } } return dp[n, cap]; } /* 完全背包:空间优化后的动态规划 */ int UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { int n = wgt.Length; // 初始化 dp 表 int[] dp = new int[cap + 1]; // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } [Test] public void Test() { int[] wgt = [1, 2, 3]; int[] val = [5, 11, 15]; int cap = 4; // 动态规划 int res = UnboundedKnapsackDP(wgt, val, cap); Console.WriteLine("不超过背包容量的最大物品价值为 " + res); // 空间优化后的动态规划 res = UnboundedKnapsackDPComp(wgt, val, cap); Console.WriteLine("不超过背包容量的最大物品价值为 " + res); } } ================================================ FILE: codes/csharp/chapter_graph/graph_adjacency_list.cs ================================================ /** * File: graph_adjacency_list.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_graph; /* 基于邻接表实现的无向图类 */ public class GraphAdjList { // 邻接表,key:顶点,value:该顶点的所有邻接顶点 public Dictionary> adjList; /* 构造函数 */ public GraphAdjList(Vertex[][] edges) { adjList = []; // 添加所有顶点和边 foreach (Vertex[] edge in edges) { AddVertex(edge[0]); AddVertex(edge[1]); AddEdge(edge[0], edge[1]); } } /* 获取顶点数量 */ int Size() { return adjList.Count; } /* 添加边 */ public void AddEdge(Vertex vet1, Vertex vet2) { if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) throw new InvalidOperationException(); // 添加边 vet1 - vet2 adjList[vet1].Add(vet2); adjList[vet2].Add(vet1); } /* 删除边 */ public void RemoveEdge(Vertex vet1, Vertex vet2) { if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) throw new InvalidOperationException(); // 删除边 vet1 - vet2 adjList[vet1].Remove(vet2); adjList[vet2].Remove(vet1); } /* 添加顶点 */ public void AddVertex(Vertex vet) { if (adjList.ContainsKey(vet)) return; // 在邻接表中添加一个新链表 adjList.Add(vet, []); } /* 删除顶点 */ public void RemoveVertex(Vertex vet) { if (!adjList.ContainsKey(vet)) throw new InvalidOperationException(); // 在邻接表中删除顶点 vet 对应的链表 adjList.Remove(vet); // 遍历其他顶点的链表,删除所有包含 vet 的边 foreach (List list in adjList.Values) { list.Remove(vet); } } /* 打印邻接表 */ public void Print() { Console.WriteLine("邻接表 ="); foreach (KeyValuePair> pair in adjList) { List tmp = []; foreach (Vertex vertex in pair.Value) tmp.Add(vertex.val); Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); } } } public class graph_adjacency_list { [Test] public void Test() { /* 初始化无向图 */ Vertex[] v = Vertex.ValsToVets([1, 3, 2, 5, 4]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]] ]; GraphAdjList graph = new(edges); Console.WriteLine("\n初始化后,图为"); graph.Print(); /* 添加边 */ // 顶点 1, 2 即 v[0], v[2] graph.AddEdge(v[0], v[2]); Console.WriteLine("\n添加边 1-2 后,图为"); graph.Print(); /* 删除边 */ // 顶点 1, 3 即 v[0], v[1] graph.RemoveEdge(v[0], v[1]); Console.WriteLine("\n删除边 1-3 后,图为"); graph.Print(); /* 添加顶点 */ Vertex v5 = new(6); graph.AddVertex(v5); Console.WriteLine("\n添加顶点 6 后,图为"); graph.Print(); /* 删除顶点 */ // 顶点 3 即 v[1] graph.RemoveVertex(v[1]); Console.WriteLine("\n删除顶点 3 后,图为"); graph.Print(); } } ================================================ FILE: codes/csharp/chapter_graph/graph_adjacency_matrix.cs ================================================ /** * File: graph_adjacency_matrix.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_graph; /* 基于邻接矩阵实现的无向图类 */ class GraphAdjMat { List vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” List> adjMat; // 邻接矩阵,行列索引对应“顶点索引” /* 构造函数 */ public GraphAdjMat(int[] vertices, int[][] edges) { this.vertices = []; this.adjMat = []; // 添加顶点 foreach (int val in vertices) { AddVertex(val); } // 添加边 // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 foreach (int[] e in edges) { AddEdge(e[0], e[1]); } } /* 获取顶点数量 */ int Size() { return vertices.Count; } /* 添加顶点 */ public void AddVertex(int val) { int n = Size(); // 向顶点列表中添加新顶点的值 vertices.Add(val); // 在邻接矩阵中添加一行 List newRow = new(n); for (int j = 0; j < n; j++) { newRow.Add(0); } adjMat.Add(newRow); // 在邻接矩阵中添加一列 foreach (List row in adjMat) { row.Add(0); } } /* 删除顶点 */ public void RemoveVertex(int index) { if (index >= Size()) throw new IndexOutOfRangeException(); // 在顶点列表中移除索引 index 的顶点 vertices.RemoveAt(index); // 在邻接矩阵中删除索引 index 的行 adjMat.RemoveAt(index); // 在邻接矩阵中删除索引 index 的列 foreach (List row in adjMat) { row.RemoveAt(index); } } /* 添加边 */ // 参数 i, j 对应 vertices 元素索引 public void AddEdge(int i, int j) { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) throw new IndexOutOfRangeException(); // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) adjMat[i][j] = 1; adjMat[j][i] = 1; } /* 删除边 */ // 参数 i, j 对应 vertices 元素索引 public void RemoveEdge(int i, int j) { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) throw new IndexOutOfRangeException(); adjMat[i][j] = 0; adjMat[j][i] = 0; } /* 打印邻接矩阵 */ public void Print() { Console.Write("顶点列表 = "); PrintUtil.PrintList(vertices); Console.WriteLine("邻接矩阵 ="); PrintUtil.PrintMatrix(adjMat); } } public class graph_adjacency_matrix { [Test] public void Test() { /* 初始化无向图 */ // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 int[] vertices = [1, 3, 2, 5, 4]; int[][] edges = [ [0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4] ]; GraphAdjMat graph = new(vertices, edges); Console.WriteLine("\n初始化后,图为"); graph.Print(); /* 添加边 */ // 顶点 1, 2 的索引分别为 0, 2 graph.AddEdge(0, 2); Console.WriteLine("\n添加边 1-2 后,图为"); graph.Print(); /* 删除边 */ // 顶点 1, 3 的索引分别为 0, 1 graph.RemoveEdge(0, 1); Console.WriteLine("\n删除边 1-3 后,图为"); graph.Print(); /* 添加顶点 */ graph.AddVertex(6); Console.WriteLine("\n添加顶点 6 后,图为"); graph.Print(); /* 删除顶点 */ // 顶点 3 的索引为 1 graph.RemoveVertex(1); Console.WriteLine("\n删除顶点 3 后,图为"); graph.Print(); } } ================================================ FILE: codes/csharp/chapter_graph/graph_bfs.cs ================================================ /** * File: graph_bfs.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_graph; public class graph_bfs { /* 广度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 List GraphBFS(GraphAdjList graph, Vertex startVet) { // 顶点遍历序列 List res = []; // 哈希集合,用于记录已被访问过的顶点 HashSet visited = [startVet]; // 队列用于实现 BFS Queue que = new(); que.Enqueue(startVet); // 以顶点 vet 为起点,循环直至访问完所有顶点 while (que.Count > 0) { Vertex vet = que.Dequeue(); // 队首顶点出队 res.Add(vet); // 记录访问顶点 foreach (Vertex adjVet in graph.adjList[vet]) { if (visited.Contains(adjVet)) { continue; // 跳过已被访问的顶点 } que.Enqueue(adjVet); // 只入队未访问的顶点 visited.Add(adjVet); // 标记该顶点已被访问 } } // 返回顶点遍历序列 return res; } [Test] public void Test() { /* 初始化无向图 */ Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]] ]; GraphAdjList graph = new(edges); Console.WriteLine("\n初始化后,图为"); graph.Print(); /* 广度优先遍历 */ List res = GraphBFS(graph, v[0]); Console.WriteLine("\n广度优先遍历(BFS)顶点序列为"); Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); } } ================================================ FILE: codes/csharp/chapter_graph/graph_dfs.cs ================================================ /** * File: graph_dfs.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_graph; public class graph_dfs { /* 深度优先遍历辅助函数 */ void DFS(GraphAdjList graph, HashSet visited, List res, Vertex vet) { res.Add(vet); // 记录访问顶点 visited.Add(vet); // 标记该顶点已被访问 // 遍历该顶点的所有邻接顶点 foreach (Vertex adjVet in graph.adjList[vet]) { if (visited.Contains(adjVet)) { continue; // 跳过已被访问的顶点 } // 递归访问邻接顶点 DFS(graph, visited, res, adjVet); } } /* 深度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 List GraphDFS(GraphAdjList graph, Vertex startVet) { // 顶点遍历序列 List res = []; // 哈希集合,用于记录已被访问过的顶点 HashSet visited = []; DFS(graph, visited, res, startVet); return res; } [Test] public void Test() { /* 初始化无向图 */ Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; GraphAdjList graph = new(edges); Console.WriteLine("\n初始化后,图为"); graph.Print(); /* 深度优先遍历 */ List res = GraphDFS(graph, v[0]); Console.WriteLine("\n深度优先遍历(DFS)顶点序列为"); Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); } } ================================================ FILE: codes/csharp/chapter_greedy/coin_change_greedy.cs ================================================ /** * File: coin_change_greedy.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class coin_change_greedy { /* 零钱兑换:贪心 */ int CoinChangeGreedy(int[] coins, int amt) { // 假设 coins 列表有序 int i = coins.Length - 1; int count = 0; // 循环进行贪心选择,直到无剩余金额 while (amt > 0) { // 找到小于且最接近剩余金额的硬币 while (i > 0 && coins[i] > amt) { i--; } // 选择 coins[i] amt -= coins[i]; count++; } // 若未找到可行方案,则返回 -1 return amt == 0 ? count : -1; } [Test] public void Test() { // 贪心:能够保证找到全局最优解 int[] coins = [1, 5, 10, 20, 50, 100]; int amt = 186; int res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("凑到 " + amt + " 所需的最少硬币数量为 " + res); // 贪心:无法保证找到全局最优解 coins = [1, 20, 50]; amt = 60; res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("凑到 " + amt + " 所需的最少硬币数量为 " + res); Console.WriteLine("实际上需要的最少数量为 3 ,即 20 + 20 + 20"); // 贪心:无法保证找到全局最优解 coins = [1, 49, 50]; amt = 98; res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("凑到 " + amt + " 所需的最少硬币数量为 " + res); Console.WriteLine("实际上需要的最少数量为 2 ,即 49 + 49"); } } ================================================ FILE: codes/csharp/chapter_greedy/fractional_knapsack.cs ================================================ /** * File: fractional_knapsack.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; /* 物品 */ class Item(int w, int v) { public int w = w; // 物品重量 public int v = v; // 物品价值 } public class fractional_knapsack { /* 分数背包:贪心 */ double FractionalKnapsack(int[] wgt, int[] val, int cap) { // 创建物品列表,包含两个属性:重量、价值 Item[] items = new Item[wgt.Length]; for (int i = 0; i < wgt.Length; i++) { items[i] = new Item(wgt[i], val[i]); } // 按照单位价值 item.v / item.w 从高到低进行排序 Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w)); // 循环贪心选择 double res = 0; foreach (Item item in items) { if (item.w <= cap) { // 若剩余容量充足,则将当前物品整个装进背包 res += item.v; cap -= item.w; } else { // 若剩余容量不足,则将当前物品的一部分装进背包 res += (double)item.v / item.w * cap; // 已无剩余容量,因此跳出循环 break; } } return res; } [Test] public void Test() { int[] wgt = [10, 20, 30, 40, 50]; int[] val = [50, 120, 150, 210, 240]; int cap = 50; // 贪心算法 double res = FractionalKnapsack(wgt, val, cap); Console.WriteLine("不超过背包容量的最大物品价值为 " + res); } } ================================================ FILE: codes/csharp/chapter_greedy/max_capacity.cs ================================================ /** * File: max_capacity.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class max_capacity { /* 最大容量:贪心 */ int MaxCapacity(int[] ht) { // 初始化 i, j,使其分列数组两端 int i = 0, j = ht.Length - 1; // 初始最大容量为 0 int res = 0; // 循环贪心选择,直至两板相遇 while (i < j) { // 更新最大容量 int cap = Math.Min(ht[i], ht[j]) * (j - i); res = Math.Max(res, cap); // 向内移动短板 if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } [Test] public void Test() { int[] ht = [3, 8, 5, 2, 7, 7, 3, 4]; // 贪心算法 int res = MaxCapacity(ht); Console.WriteLine("最大容量为 " + res); } } ================================================ FILE: codes/csharp/chapter_greedy/max_product_cutting.cs ================================================ /** * File: max_product_cutting.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class max_product_cutting { /* 最大切分乘积:贪心 */ int MaxProductCutting(int n) { // 当 n <= 3 时,必须切分出一个 1 if (n <= 3) { return 1 * (n - 1); } // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 int a = n / 3; int b = n % 3; if (b == 1) { // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 return (int)Math.Pow(3, a - 1) * 2 * 2; } if (b == 2) { // 当余数为 2 时,不做处理 return (int)Math.Pow(3, a) * 2; } // 当余数为 0 时,不做处理 return (int)Math.Pow(3, a); } [Test] public void Test() { int n = 58; // 贪心算法 int res = MaxProductCutting(n); Console.WriteLine("最大切分乘积为" + res); } } ================================================ FILE: codes/csharp/chapter_hashing/array_hash_map.cs ================================================ /** * File: array_hash_map.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_hashing; /* 键值对 int->string */ class Pair(int key, string val) { public int key = key; public string val = val; } /* 基于数组实现的哈希表 */ class ArrayHashMap { List buckets; public ArrayHashMap() { // 初始化数组,包含 100 个桶 buckets = []; for (int i = 0; i < 100; i++) { buckets.Add(null); } } /* 哈希函数 */ int HashFunc(int key) { int index = key % 100; return index; } /* 查询操作 */ public string? Get(int key) { int index = HashFunc(key); Pair? pair = buckets[index]; if (pair == null) return null; return pair.val; } /* 添加操作 */ public void Put(int key, string val) { Pair pair = new(key, val); int index = HashFunc(key); buckets[index] = pair; } /* 删除操作 */ public void Remove(int key) { int index = HashFunc(key); // 置为 null ,代表删除 buckets[index] = null; } /* 获取所有键值对 */ public List PairSet() { List pairSet = []; foreach (Pair? pair in buckets) { if (pair != null) pairSet.Add(pair); } return pairSet; } /* 获取所有键 */ public List KeySet() { List keySet = []; foreach (Pair? pair in buckets) { if (pair != null) keySet.Add(pair.key); } return keySet; } /* 获取所有值 */ public List ValueSet() { List valueSet = []; foreach (Pair? pair in buckets) { if (pair != null) valueSet.Add(pair.val); } return valueSet; } /* 打印哈希表 */ public void Print() { foreach (Pair kv in PairSet()) { Console.WriteLine(kv.key + " -> " + kv.val); } } } public class array_hash_map { [Test] public void Test() { /* 初始化哈希表 */ ArrayHashMap map = new(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.Put(12836, "小哈"); map.Put(15937, "小啰"); map.Put(16750, "小算"); map.Put(13276, "小法"); map.Put(10583, "小鸭"); Console.WriteLine("\n添加完成后,哈希表为\nKey -> Value"); map.Print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value string? name = map.Get(15937); Console.WriteLine("\n输入学号 15937 ,查询到姓名 " + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.Remove(10583); Console.WriteLine("\n删除 10583 后,哈希表为\nKey -> Value"); map.Print(); /* 遍历哈希表 */ Console.WriteLine("\n遍历键值对 Key->Value"); foreach (Pair kv in map.PairSet()) { Console.WriteLine(kv.key + " -> " + kv.val); } Console.WriteLine("\n单独遍历键 Key"); foreach (int key in map.KeySet()) { Console.WriteLine(key); } Console.WriteLine("\n单独遍历值 Value"); foreach (string val in map.ValueSet()) { Console.WriteLine(val); } } } ================================================ FILE: codes/csharp/chapter_hashing/built_in_hash.cs ================================================ /** * File: built_in_hash.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; public class built_in_hash { [Test] public void Test() { int num = 3; int hashNum = num.GetHashCode(); Console.WriteLine("整数 " + num + " 的哈希值为 " + hashNum); bool bol = true; int hashBol = bol.GetHashCode(); Console.WriteLine("布尔量 " + bol + " 的哈希值为 " + hashBol); double dec = 3.14159; int hashDec = dec.GetHashCode(); Console.WriteLine("小数 " + dec + " 的哈希值为 " + hashDec); string str = "Hello 算法"; int hashStr = str.GetHashCode(); Console.WriteLine("字符串 " + str + " 的哈希值为 " + hashStr); object[] arr = [12836, "小哈"]; int hashTup = arr.GetHashCode(); Console.WriteLine("数组 [" + string.Join(", ", arr) + "] 的哈希值为 " + hashTup); ListNode obj = new(0); int hashObj = obj.GetHashCode(); Console.WriteLine("节点对象 " + obj + " 的哈希值为 " + hashObj); } } ================================================ FILE: codes/csharp/chapter_hashing/hash_map.cs ================================================ /** * File: hash_map.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_hashing; public class hash_map { [Test] public void Test() { /* 初始化哈希表 */ Dictionary map = new() { /* 添加操作 */ // 在哈希表中添加键值对 (key, value) { 12836, "小哈" }, { 15937, "小啰" }, { 16750, "小算" }, { 13276, "小法" }, { 10583, "小鸭" } }; Console.WriteLine("\n添加完成后,哈希表为\nKey -> Value"); PrintUtil.PrintHashMap(map); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value string name = map[15937]; Console.WriteLine("\n输入学号 15937 ,查询到姓名 " + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.Remove(10583); Console.WriteLine("\n删除 10583 后,哈希表为\nKey -> Value"); PrintUtil.PrintHashMap(map); /* 遍历哈希表 */ Console.WriteLine("\n遍历键值对 Key->Value"); foreach (var kv in map) { Console.WriteLine(kv.Key + " -> " + kv.Value); } Console.WriteLine("\n单独遍历键 Key"); foreach (int key in map.Keys) { Console.WriteLine(key); } Console.WriteLine("\n单独遍历值 Value"); foreach (string val in map.Values) { Console.WriteLine(val); } } } ================================================ FILE: codes/csharp/chapter_hashing/hash_map_chaining.cs ================================================ /** * File: hash_map_chaining.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; /* 链式地址哈希表 */ class HashMapChaining { int size; // 键值对数量 int capacity; // 哈希表容量 double loadThres; // 触发扩容的负载因子阈值 int extendRatio; // 扩容倍数 List> buckets; // 桶数组 /* 构造方法 */ public HashMapChaining() { size = 0; capacity = 4; loadThres = 2.0 / 3.0; extendRatio = 2; buckets = new List>(capacity); for (int i = 0; i < capacity; i++) { buckets.Add([]); } } /* 哈希函数 */ int HashFunc(int key) { return key % capacity; } /* 负载因子 */ double LoadFactor() { return (double)size / capacity; } /* 查询操作 */ public string? Get(int key) { int index = HashFunc(key); // 遍历桶,若找到 key ,则返回对应 val foreach (Pair pair in buckets[index]) { if (pair.key == key) { return pair.val; } } // 若未找到 key ,则返回 null return null; } /* 添加操作 */ public void Put(int key, string val) { // 当负载因子超过阈值时,执行扩容 if (LoadFactor() > loadThres) { Extend(); } int index = HashFunc(key); // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 foreach (Pair pair in buckets[index]) { if (pair.key == key) { pair.val = val; return; } } // 若无该 key ,则将键值对添加至尾部 buckets[index].Add(new Pair(key, val)); size++; } /* 删除操作 */ public void Remove(int key) { int index = HashFunc(key); // 遍历桶,从中删除键值对 foreach (Pair pair in buckets[index].ToList()) { if (pair.key == key) { buckets[index].Remove(pair); size--; break; } } } /* 扩容哈希表 */ void Extend() { // 暂存原哈希表 List> bucketsTmp = buckets; // 初始化扩容后的新哈希表 capacity *= extendRatio; buckets = new List>(capacity); for (int i = 0; i < capacity; i++) { buckets.Add([]); } size = 0; // 将键值对从原哈希表搬运至新哈希表 foreach (List bucket in bucketsTmp) { foreach (Pair pair in bucket) { Put(pair.key, pair.val); } } } /* 打印哈希表 */ public void Print() { foreach (List bucket in buckets) { List res = []; foreach (Pair pair in bucket) { res.Add(pair.key + " -> " + pair.val); } foreach (string kv in res) { Console.WriteLine(kv); } } } } public class hash_map_chaining { [Test] public void Test() { /* 初始化哈希表 */ HashMapChaining map = new(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.Put(12836, "小哈"); map.Put(15937, "小啰"); map.Put(16750, "小算"); map.Put(13276, "小法"); map.Put(10583, "小鸭"); Console.WriteLine("\n添加完成后,哈希表为\nKey -> Value"); map.Print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value string? name = map.Get(13276); Console.WriteLine("\n输入学号 13276 ,查询到姓名 " + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.Remove(12836); Console.WriteLine("\n删除 12836 后,哈希表为\nKey -> Value"); map.Print(); } } ================================================ FILE: codes/csharp/chapter_hashing/hash_map_open_addressing.cs ================================================ /** * File: hash_map_open_addressing.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; /* 开放寻址哈希表 */ class HashMapOpenAddressing { int size; // 键值对数量 int capacity = 4; // 哈希表容量 double loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值 int extendRatio = 2; // 扩容倍数 Pair[] buckets; // 桶数组 Pair TOMBSTONE = new(-1, "-1"); // 删除标记 /* 构造方法 */ public HashMapOpenAddressing() { size = 0; buckets = new Pair[capacity]; } /* 哈希函数 */ int HashFunc(int key) { return key % capacity; } /* 负载因子 */ double LoadFactor() { return (double)size / capacity; } /* 搜索 key 对应的桶索引 */ int FindBucket(int key) { int index = HashFunc(key); int firstTombstone = -1; // 线性探测,当遇到空桶时跳出 while (buckets[index] != null) { // 若遇到 key ,返回对应的桶索引 if (buckets[index].key == key) { // 若之前遇到了删除标记,则将键值对移动至该索引处 if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index]; buckets[index] = TOMBSTONE; return firstTombstone; // 返回移动后的桶索引 } return index; // 返回桶索引 } // 记录遇到的首个删除标记 if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index; } // 计算桶索引,越过尾部则返回头部 index = (index + 1) % capacity; } // 若 key 不存在,则返回添加点的索引 return firstTombstone == -1 ? index : firstTombstone; } /* 查询操作 */ public string? Get(int key) { // 搜索 key 对应的桶索引 int index = FindBucket(key); // 若找到键值对,则返回对应 val if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index].val; } // 若键值对不存在,则返回 null return null; } /* 添加操作 */ public void Put(int key, string val) { // 当负载因子超过阈值时,执行扩容 if (LoadFactor() > loadThres) { Extend(); } // 搜索 key 对应的桶索引 int index = FindBucket(key); // 若找到键值对,则覆盖 val 并返回 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index].val = val; return; } // 若键值对不存在,则添加该键值对 buckets[index] = new Pair(key, val); size++; } /* 删除操作 */ public void Remove(int key) { // 搜索 key 对应的桶索引 int index = FindBucket(key); // 若找到键值对,则用删除标记覆盖它 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE; size--; } } /* 扩容哈希表 */ void Extend() { // 暂存原哈希表 Pair[] bucketsTmp = buckets; // 初始化扩容后的新哈希表 capacity *= extendRatio; buckets = new Pair[capacity]; size = 0; // 将键值对从原哈希表搬运至新哈希表 foreach (Pair pair in bucketsTmp) { if (pair != null && pair != TOMBSTONE) { Put(pair.key, pair.val); } } } /* 打印哈希表 */ public void Print() { foreach (Pair pair in buckets) { if (pair == null) { Console.WriteLine("null"); } else if (pair == TOMBSTONE) { Console.WriteLine("TOMBSTONE"); } else { Console.WriteLine(pair.key + " -> " + pair.val); } } } } public class hash_map_open_addressing { [Test] public void Test() { /* 初始化哈希表 */ HashMapOpenAddressing map = new(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.Put(12836, "小哈"); map.Put(15937, "小啰"); map.Put(16750, "小算"); map.Put(13276, "小法"); map.Put(10583, "小鸭"); Console.WriteLine("\n添加完成后,哈希表为\nKey -> Value"); map.Print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value string? name = map.Get(13276); Console.WriteLine("\n输入学号 13276 ,查询到姓名 " + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.Remove(16750); Console.WriteLine("\n删除 16750 后,哈希表为\nKey -> Value"); map.Print(); } } ================================================ FILE: codes/csharp/chapter_hashing/simple_hash.cs ================================================ /** * File: simple_hash.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; public class simple_hash { /* 加法哈希 */ int AddHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = (hash + c) % MODULUS; } return (int)hash; } /* 乘法哈希 */ int MulHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = (31 * hash + c) % MODULUS; } return (int)hash; } /* 异或哈希 */ int XorHash(string key) { int hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash ^= c; } return hash & MODULUS; } /* 旋转哈希 */ int RotHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS; } return (int)hash; } [Test] public void Test() { string key = "Hello 算法"; int hash = AddHash(key); Console.WriteLine("加法哈希值为 " + hash); hash = MulHash(key); Console.WriteLine("乘法哈希值为 " + hash); hash = XorHash(key); Console.WriteLine("异或哈希值为 " + hash); hash = RotHash(key); Console.WriteLine("旋转哈希值为 " + hash); } } ================================================ FILE: codes/csharp/chapter_heap/heap.cs ================================================ /** * File: heap.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_heap; public class heap { void TestPush(PriorityQueue heap, int val) { heap.Enqueue(val, val); // 元素入堆 Console.WriteLine($"\n元素 {val} 入堆后\n"); PrintUtil.PrintHeap(heap); } void TestPop(PriorityQueue heap) { int val = heap.Dequeue(); // 堆顶元素出堆 Console.WriteLine($"\n堆顶元素 {val} 出堆后\n"); PrintUtil.PrintHeap(heap); } [Test] public void Test() { /* 初始化堆 */ // 初始化小顶堆 PriorityQueue minHeap = new(); // 初始化大顶堆(使用 lambda 表达式修改 Comparer 即可) PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); Console.WriteLine("以下测试样例为大顶堆"); /* 元素入堆 */ TestPush(maxHeap, 1); TestPush(maxHeap, 3); TestPush(maxHeap, 2); TestPush(maxHeap, 5); TestPush(maxHeap, 4); /* 获取堆顶元素 */ int peek = maxHeap.Peek(); Console.WriteLine($"堆顶元素为 {peek}"); /* 堆顶元素出堆 */ // 出堆元素会形成一个从大到小的序列 TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); /* 获取堆大小 */ int size = maxHeap.Count; Console.WriteLine($"堆元素数量为 {size}"); /* 判断堆是否为空 */ bool isEmpty = maxHeap.Count == 0; Console.WriteLine($"堆是否为空 {isEmpty}"); /* 输入列表并建堆 */ var list = new int[] { 1, 3, 2, 5, 4 }; minHeap = new PriorityQueue(list.Select(x => (x, x))); Console.WriteLine("输入列表并建立小顶堆后"); PrintUtil.PrintHeap(minHeap); } } ================================================ FILE: codes/csharp/chapter_heap/my_heap.cs ================================================ /** * File: my_heap.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_heap; /* 大顶堆 */ class MaxHeap { // 使用列表而非数组,这样无须考虑扩容问题 List maxHeap; /* 构造函数,建立空堆 */ public MaxHeap() { maxHeap = []; } /* 构造函数,根据输入列表建堆 */ public MaxHeap(IEnumerable nums) { // 将列表元素原封不动添加进堆 maxHeap = new List(nums); // 堆化除叶节点以外的其他所有节点 var size = Parent(this.Size() - 1); for (int i = size; i >= 0; i--) { SiftDown(i); } } /* 获取左子节点的索引 */ int Left(int i) { return 2 * i + 1; } /* 获取右子节点的索引 */ int Right(int i) { return 2 * i + 2; } /* 获取父节点的索引 */ int Parent(int i) { return (i - 1) / 2; // 向下整除 } /* 访问堆顶元素 */ public int Peek() { return maxHeap[0]; } /* 元素入堆 */ public void Push(int val) { // 添加节点 maxHeap.Add(val); // 从底至顶堆化 SiftUp(Size() - 1); } /* 获取堆大小 */ public int Size() { return maxHeap.Count; } /* 判断堆是否为空 */ public bool IsEmpty() { return Size() == 0; } /* 从节点 i 开始,从底至顶堆化 */ void SiftUp(int i) { while (true) { // 获取节点 i 的父节点 int p = Parent(i); // 若“越过根节点”或“节点无须修复”,则结束堆化 if (p < 0 || maxHeap[i] <= maxHeap[p]) break; // 交换两节点 Swap(i, p); // 循环向上堆化 i = p; } } /* 元素出堆 */ public int Pop() { // 判空处理 if (IsEmpty()) throw new IndexOutOfRangeException(); // 交换根节点与最右叶节点(交换首元素与尾元素) Swap(0, Size() - 1); // 删除节点 int val = maxHeap.Last(); maxHeap.RemoveAt(Size() - 1); // 从顶至底堆化 SiftDown(0); // 返回堆顶元素 return val; } /* 从节点 i 开始,从顶至底堆化 */ void SiftDown(int i) { while (true) { // 判断节点 i, l, r 中值最大的节点,记为 ma int l = Left(i), r = Right(i), ma = i; if (l < Size() && maxHeap[l] > maxHeap[ma]) ma = l; if (r < Size() && maxHeap[r] > maxHeap[ma]) ma = r; // 若“节点 i 最大”或“越过叶节点”,则结束堆化 if (ma == i) break; // 交换两节点 Swap(i, ma); // 循环向下堆化 i = ma; } } /* 交换元素 */ void Swap(int i, int p) { (maxHeap[i], maxHeap[p]) = (maxHeap[p], maxHeap[i]); } /* 打印堆(二叉树) */ public void Print() { var queue = new Queue(maxHeap); PrintUtil.PrintHeap(queue); } } public class my_heap { [Test] public void Test() { /* 初始化大顶堆 */ MaxHeap maxHeap = new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); Console.WriteLine("\n输入列表并建堆后"); maxHeap.Print(); /* 获取堆顶元素 */ int peek = maxHeap.Peek(); Console.WriteLine($"堆顶元素为 {peek}"); /* 元素入堆 */ int val = 7; maxHeap.Push(val); Console.WriteLine($"元素 {val} 入堆后"); maxHeap.Print(); /* 堆顶元素出堆 */ peek = maxHeap.Pop(); Console.WriteLine($"堆顶元素 {peek} 出堆后"); maxHeap.Print(); /* 获取堆大小 */ int size = maxHeap.Size(); Console.WriteLine($"堆元素数量为 {size}"); /* 判断堆是否为空 */ bool isEmpty = maxHeap.IsEmpty(); Console.WriteLine($"堆是否为空 {isEmpty}"); } } ================================================ FILE: codes/csharp/chapter_heap/top_k.cs ================================================ /** * File: top_k.cs * Created Time: 2023-06-14 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_heap; public class top_k { /* 基于堆查找数组中最大的 k 个元素 */ PriorityQueue TopKHeap(int[] nums, int k) { // 初始化小顶堆 PriorityQueue heap = new(); // 将数组的前 k 个元素入堆 for (int i = 0; i < k; i++) { heap.Enqueue(nums[i], nums[i]); } // 从第 k+1 个元素开始,保持堆的长度为 k for (int i = k; i < nums.Length; i++) { // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 if (nums[i] > heap.Peek()) { heap.Dequeue(); heap.Enqueue(nums[i], nums[i]); } } return heap; } [Test] public void Test() { int[] nums = [1, 7, 6, 3, 2]; int k = 3; PriorityQueue res = TopKHeap(nums, k); Console.WriteLine("最大的 " + k + " 个元素为"); PrintUtil.PrintHeap(res); } } ================================================ FILE: codes/csharp/chapter_searching/binary_search.cs ================================================ /** * File: binary_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class binary_search { /* 二分查找(双闭区间) */ int BinarySearch(int[] nums, int target) { // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 int i = 0, j = nums.Length - 1; // 循环,当搜索区间为空时跳出(当 i > j 时为空) while (i <= j) { int m = i + (j - i) / 2; // 计算中点索引 m if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 i = m + 1; else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 j = m - 1; else // 找到目标元素,返回其索引 return m; } // 未找到目标元素,返回 -1 return -1; } /* 二分查找(左闭右开区间) */ int BinarySearchLCRO(int[] nums, int target) { // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 int i = 0, j = nums.Length; // 循环,当搜索区间为空时跳出(当 i = j 时为空) while (i < j) { int m = i + (j - i) / 2; // 计算中点索引 m if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j) 中 i = m + 1; else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中 j = m; else // 找到目标元素,返回其索引 return m; } // 未找到目标元素,返回 -1 return -1; } [Test] public void Test() { int target = 6; int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分查找(双闭区间) */ int index = BinarySearch(nums, target); Console.WriteLine("目标元素 6 的索引 = " + index); /* 二分查找(左闭右开区间) */ index = BinarySearchLCRO(nums, target); Console.WriteLine("目标元素 6 的索引 = " + index); } } ================================================ FILE: codes/csharp/chapter_searching/binary_search_edge.cs ================================================ /** * File: binary_search_edge.cs * Created Time: 2023-08-06 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_searching; public class binary_search_edge { /* 二分查找最左一个 target */ int BinarySearchLeftEdge(int[] nums, int target) { // 等价于查找 target 的插入点 int i = binary_search_insertion.BinarySearchInsertion(nums, target); // 未找到 target ,返回 -1 if (i == nums.Length || nums[i] != target) { return -1; } // 找到 target ,返回索引 i return i; } /* 二分查找最右一个 target */ int BinarySearchRightEdge(int[] nums, int target) { // 转化为查找最左一个 target + 1 int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1); // j 指向最右一个 target ,i 指向首个大于 target 的元素 int j = i - 1; // 未找到 target ,返回 -1 if (j == -1 || nums[j] != target) { return -1; } // 找到 target ,返回索引 j return j; } [Test] public void Test() { // 包含重复元素的数组 int[] nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; Console.WriteLine("\n数组 nums = " + nums.PrintList()); // 二分查找左边界和右边界 foreach (int target in new int[] { 6, 7 }) { int index = BinarySearchLeftEdge(nums, target); Console.WriteLine("最左一个元素 " + target + " 的索引为 " + index); index = BinarySearchRightEdge(nums, target); Console.WriteLine("最右一个元素 " + target + " 的索引为 " + index); } } } ================================================ FILE: codes/csharp/chapter_searching/binary_search_insertion.cs ================================================ /** * File: binary_search_insertion.cs * Created Time: 2023-08-06 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_searching; public class binary_search_insertion { /* 二分查找插入点(无重复元素) */ public static int BinarySearchInsertionSimple(int[] nums, int target) { int i = 0, j = nums.Length - 1; // 初始化双闭区间 [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // 计算中点索引 m if (nums[m] < target) { i = m + 1; // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在区间 [i, m-1] 中 } else { return m; // 找到 target ,返回插入点 m } } // 未找到 target ,返回插入点 i return i; } /* 二分查找插入点(存在重复元素) */ public static int BinarySearchInsertion(int[] nums, int target) { int i = 0, j = nums.Length - 1; // 初始化双闭区间 [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // 计算中点索引 m if (nums[m] < target) { i = m + 1; // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在区间 [i, m-1] 中 } else { j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 } } // 返回插入点 i return i; } [Test] public void Test() { // 无重复元素的数组 int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; Console.WriteLine("\n数组 nums = " + nums.PrintList()); // 二分查找插入点 foreach (int target in new int[] { 6, 9 }) { int index = BinarySearchInsertionSimple(nums, target); Console.WriteLine("元素 " + target + " 的插入点的索引为 " + index); } // 包含重复元素的数组 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; Console.WriteLine("\n数组 nums = " + nums.PrintList()); // 二分查找插入点 foreach (int target in new int[] { 2, 6, 20 }) { int index = BinarySearchInsertion(nums, target); Console.WriteLine("元素 " + target + " 的插入点的索引为 " + index); } } } ================================================ FILE: codes/csharp/chapter_searching/hashing_search.cs ================================================ /** * File: hashing_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class hashing_search { /* 哈希查找(数组) */ int HashingSearchArray(Dictionary map, int target) { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 return map.GetValueOrDefault(target, -1); } /* 哈希查找(链表) */ ListNode? HashingSearchLinkedList(Dictionary map, int target) { // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 null return map.GetValueOrDefault(target); } [Test] public void Test() { int target = 3; /* 哈希查找(数组) */ int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // 初始化哈希表 Dictionary map = []; for (int i = 0; i < nums.Length; i++) { map[nums[i]] = i; // key: 元素,value: 索引 } int index = HashingSearchArray(map, target); Console.WriteLine("目标元素 3 的索引 = " + index); /* 哈希查找(链表) */ ListNode? head = ListNode.ArrToLinkedList(nums); // 初始化哈希表 Dictionary map1 = []; while (head != null) { map1[head.val] = head; // key: 节点值,value: 节点 head = head.next; } ListNode? node = HashingSearchLinkedList(map1, target); Console.WriteLine("目标节点值 3 的对应节点对象为 " + node); } } ================================================ FILE: codes/csharp/chapter_searching/linear_search.cs ================================================ /** * File: linear_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class linear_search { /* 线性查找(数组) */ int LinearSearchArray(int[] nums, int target) { // 遍历数组 for (int i = 0; i < nums.Length; i++) { // 找到目标元素,返回其索引 if (nums[i] == target) return i; } // 未找到目标元素,返回 -1 return -1; } /* 线性查找(链表) */ ListNode? LinearSearchLinkedList(ListNode? head, int target) { // 遍历链表 while (head != null) { // 找到目标节点,返回之 if (head.val == target) return head; head = head.next; } // 未找到目标节点,返回 null return null; } [Test] public void Test() { int target = 3; /* 在数组中执行线性查找 */ int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; int index = LinearSearchArray(nums, target); Console.WriteLine("目标元素 3 的索引 = " + index); /* 在链表中执行线性查找 */ ListNode? head = ListNode.ArrToLinkedList(nums); ListNode? node = LinearSearchLinkedList(head, target); Console.WriteLine("目标节点值 3 的对应节点对象为 " + node); } } ================================================ FILE: codes/csharp/chapter_searching/two_sum.cs ================================================ /** * File: two_sum.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class two_sum { /* 方法一:暴力枚举 */ 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 [i, j]; } } return []; } /* 方法二:辅助哈希表 */ int[] TwoSumHashTable(int[] nums, int target) { int size = nums.Length; // 辅助哈希表,空间复杂度为 O(n) Dictionary dic = []; // 单层循环,时间复杂度为 O(n) for (int i = 0; i < size; i++) { if (dic.ContainsKey(target - nums[i])) { return [dic[target - nums[i]], i]; } dic.Add(nums[i], i); } return []; } [Test] public void Test() { // ======= Test Case ======= int[] nums = [2, 7, 11, 15]; int target = 13; // ====== Driver Code ====== // 方法一 int[] res = TwoSumBruteForce(nums, target); Console.WriteLine("方法一 res = " + string.Join(",", res)); // 方法二 res = TwoSumHashTable(nums, target); Console.WriteLine("方法二 res = " + string.Join(",", res)); } } ================================================ FILE: codes/csharp/chapter_sorting/bubble_sort.cs ================================================ /** * File: bubble_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class bubble_sort { /* 冒泡排序 */ void BubbleSort(int[] nums) { // 外循环:未排序区间为 [0, i] for (int i = nums.Length - 1; i > 0; i--) { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); } } } } /* 冒泡排序(标志优化)*/ void BubbleSortWithFlag(int[] nums) { // 外循环:未排序区间为 [0, i] for (int i = nums.Length - 1; i > 0; i--) { bool flag = false; // 初始化标志位 // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); flag = true; // 记录交换元素 } } if (!flag) break; // 此轮“冒泡”未交换任何元素,直接跳出 } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; BubbleSort(nums); Console.WriteLine("冒泡排序完成后 nums = " + string.Join(",", nums)); int[] nums1 = [4, 1, 3, 1, 5, 2]; BubbleSortWithFlag(nums1); Console.WriteLine("冒泡排序完成后 nums1 = " + string.Join(",", nums1)); } } ================================================ FILE: codes/csharp/chapter_sorting/bucket_sort.cs ================================================ /** * File: bucket_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class bucket_sort { /* 桶排序 */ void BucketSort(float[] nums) { // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 int k = nums.Length / 2; List> buckets = []; for (int i = 0; i < k; i++) { buckets.Add([]); } // 1. 将数组元素分配到各个桶中 foreach (float num in nums) { // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] int i = (int)(num * k); // 将 num 添加进桶 i buckets[i].Add(num); } // 2. 对各个桶执行排序 foreach (List bucket in buckets) { // 使用内置排序函数,也可以替换成其他排序算法 bucket.Sort(); } // 3. 遍历桶合并结果 int j = 0; foreach (List bucket in buckets) { foreach (float num in bucket) { nums[j++] = num; } } } [Test] public void Test() { // 设输入数据为浮点数,范围为 [0, 1) float[] nums = [0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f]; BucketSort(nums); Console.WriteLine("桶排序完成后 nums = " + string.Join(" ", nums)); } } ================================================ FILE: codes/csharp/chapter_sorting/counting_sort.cs ================================================ /** * File: counting_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class counting_sort { /* 计数排序 */ // 简单实现,无法用于排序对象 void CountingSortNaive(int[] nums) { // 1. 统计数组最大元素 m int m = 0; foreach (int num in nums) { m = Math.Max(m, num); } // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 int[] counter = new int[m + 1]; foreach (int num in nums) { counter[num]++; } // 3. 遍历 counter ,将各元素填入原数组 nums int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* 计数排序 */ // 完整实现,可排序对象,并且是稳定排序 void CountingSort(int[] nums) { // 1. 统计数组最大元素 m int m = 0; foreach (int num in nums) { m = Math.Max(m, num); } // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 int[] counter = new int[m + 1]; foreach (int num in nums) { counter[num]++; } // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. 倒序遍历 nums ,将各元素填入结果数组 res // 初始化数组 res 用于记录结果 int n = nums.Length; int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // 将 num 放置到对应索引处 counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 } // 使用结果数组 res 覆盖原数组 nums for (int i = 0; i < n; i++) { nums[i] = res[i]; } } [Test] public void Test() { int[] nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; CountingSortNaive(nums); Console.WriteLine("计数排序(无法排序对象)完成后 nums = " + string.Join(" ", nums)); int[] nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; CountingSort(nums1); Console.WriteLine("计数排序完成后 nums1 = " + string.Join(" ", nums)); } } ================================================ FILE: codes/csharp/chapter_sorting/heap_sort.cs ================================================ /** * File: heap_sort.cs * Created Time: 2023-06-01 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class heap_sort { /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ void SiftDown(int[] nums, int n, int i) { while (true) { // 判断节点 i, l, r 中值最大的节点,记为 ma int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; if (l < n && nums[l] > nums[ma]) ma = l; if (r < n && nums[r] > nums[ma]) ma = r; // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if (ma == i) break; // 交换两节点 (nums[ma], nums[i]) = (nums[i], nums[ma]); // 循环向下堆化 i = ma; } } /* 堆排序 */ void HeapSort(int[] nums) { // 建堆操作:堆化除叶节点以外的其他所有节点 for (int i = nums.Length / 2 - 1; i >= 0; i--) { SiftDown(nums, nums.Length, i); } // 从堆中提取最大元素,循环 n-1 轮 for (int i = nums.Length - 1; i > 0; i--) { // 交换根节点与最右叶节点(交换首元素与尾元素) (nums[i], nums[0]) = (nums[0], nums[i]); // 以根节点为起点,从顶至底进行堆化 SiftDown(nums, i, 0); } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; HeapSort(nums); Console.WriteLine("堆排序完成后 nums = " + string.Join(" ", nums)); } } ================================================ FILE: codes/csharp/chapter_sorting/insertion_sort.cs ================================================ /** * File: insertion_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class insertion_sort { /* 插入排序 */ void InsertionSort(int[] nums) { // 外循环:已排序区间为 [0, i-1] for (int i = 1; i < nums.Length; i++) { int bas = nums[i], j = i - 1; // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while (j >= 0 && nums[j] > bas) { nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位 j--; } nums[j + 1] = bas; // 将 base 赋值到正确位置 } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; InsertionSort(nums); Console.WriteLine("插入排序完成后 nums = " + string.Join(",", nums)); } } ================================================ FILE: codes/csharp/chapter_sorting/merge_sort.cs ================================================ /** * File: merge_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class merge_sort { /* 合并左子数组和右子数组 */ void Merge(int[] nums, int left, int mid, int right) { // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] // 创建一个临时数组 tmp ,用于存放合并后的结果 int[] tmp = new int[right - left + 1]; // 初始化左子数组和右子数组的起始索引 int i = left, j = mid + 1, k = 0; // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } // 将左子数组和右子数组的剩余元素复制到临时数组中 while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 for (k = 0; k < tmp.Length; ++k) { nums[left + k] = tmp[k]; } } /* 归并排序 */ void MergeSort(int[] nums, int left, int right) { // 终止条件 if (left >= right) return; // 当子数组长度为 1 时终止递归 // 划分阶段 int mid = left + (right - left) / 2; // 计算中点 MergeSort(nums, left, mid); // 递归左子数组 MergeSort(nums, mid + 1, right); // 递归右子数组 // 合并阶段 Merge(nums, left, mid, right); } [Test] public void Test() { /* 归并排序 */ int[] nums = [7, 3, 2, 6, 0, 1, 5, 4]; MergeSort(nums, 0, nums.Length - 1); Console.WriteLine("归并排序完成后 nums = " + string.Join(",", nums)); } } ================================================ FILE: codes/csharp/chapter_sorting/quick_sort.cs ================================================ /** * File: quick_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; class quickSort { /* 元素交换 */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* 哨兵划分 */ static int Partition(int[] nums, int left, int right) { // 以 nums[left] 为基准数 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 从右向左找首个小于基准数的元素 while (i < j && nums[i] <= nums[left]) i++; // 从左向右找首个大于基准数的元素 Swap(nums, i, j); // 交换这两个元素 } Swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } /* 快速排序 */ public static void QuickSort(int[] nums, int left, int right) { // 子数组长度为 1 时终止递归 if (left >= right) return; // 哨兵划分 int pivot = Partition(nums, left, right); // 递归左子数组、右子数组 QuickSort(nums, left, pivot - 1); QuickSort(nums, pivot + 1, right); } } /* 快速排序类(中位基准数优化) */ class QuickSortMedian { /* 元素交换 */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* 选取三个候选元素的中位数 */ 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; } /* 哨兵划分(三数取中值) */ static int Partition(int[] 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(int[] nums, int left, int right) { // 子数组长度为 1 时终止递归 if (left >= right) return; // 哨兵划分 int pivot = Partition(nums, left, right); // 递归左子数组、右子数组 QuickSort(nums, left, pivot - 1); QuickSort(nums, pivot + 1, right); } } /* 快速排序类(递归深度优化) */ class QuickSortTailCall { /* 元素交换 */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* 哨兵划分 */ static int Partition(int[] nums, int left, int right) { // 以 nums[left] 为基准数 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 从右向左找首个小于基准数的元素 while (i < j && nums[i] <= nums[left]) i++; // 从左向右找首个大于基准数的元素 Swap(nums, i, j); // 交换这两个元素 } Swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } /* 快速排序(递归深度优化) */ public static void QuickSort(int[] 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] } } } } public class quick_sort { [Test] public void Test() { /* 快速排序 */ int[] nums = [2, 4, 1, 0, 3, 5]; quickSort.QuickSort(nums, 0, nums.Length - 1); Console.WriteLine("快速排序完成后 nums = " + string.Join(",", nums)); /* 快速排序(中位基准数优化) */ int[] nums1 = [2, 4, 1, 0, 3, 5]; QuickSortMedian.QuickSort(nums1, 0, nums1.Length - 1); Console.WriteLine("快速排序(中位基准数优化)完成后 nums1 = " + string.Join(",", nums1)); /* 快速排序(递归深度优化) */ int[] nums2 = [2, 4, 1, 0, 3, 5]; QuickSortTailCall.QuickSort(nums2, 0, nums2.Length - 1); Console.WriteLine("快速排序(递归深度优化)完成后 nums2 = " + string.Join(",", nums2)); } } ================================================ FILE: codes/csharp/chapter_sorting/radix_sort.cs ================================================ /** * File: radix_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class radix_sort { /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ int Digit(int num, int exp) { // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 return (num / exp) % 10; } /* 计数排序(根据 nums 第 k 位排序) */ void CountingSortDigit(int[] nums, int exp) { // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 int[] counter = new int[10]; int n = nums.Length; // 统计 0~9 各数字的出现次数 for (int i = 0; i < n; i++) { int d = Digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d counter[d]++; // 统计数字 d 的出现次数 } // 求前缀和,将“出现个数”转换为“数组索引” for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 倒序遍历,根据桶内统计结果,将各元素填入 res int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int d = Digit(nums[i], exp); int j = counter[d] - 1; // 获取 d 在数组中的索引 j res[j] = nums[i]; // 将当前元素填入索引 j counter[d]--; // 将 d 的数量减 1 } // 使用结果覆盖原数组 nums for (int i = 0; i < n; i++) { nums[i] = res[i]; } } /* 基数排序 */ void RadixSort(int[] nums) { // 获取数组的最大元素,用于判断最大位数 int m = int.MinValue; foreach (int num in nums) { if (num > m) m = num; } // 按照从低位到高位的顺序遍历 for (int exp = 1; exp <= m; exp *= 10) { // 对数组元素的第 k 位执行计数排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) CountingSortDigit(nums, exp); } } [Test] public void Test() { // 基数排序 int[] nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ]; RadixSort(nums); Console.WriteLine("基数排序完成后 nums = " + string.Join(" ", nums)); } } ================================================ FILE: codes/csharp/chapter_sorting/selection_sort.cs ================================================ /** * File: selection_sort.cs * Created Time: 2023-06-01 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class selection_sort { /* 选择排序 */ void SelectionSort(int[] nums) { int n = nums.Length; // 外循环:未排序区间为 [i, n-1] for (int i = 0; i < n - 1; i++) { // 内循环:找到未排序区间内的最小元素 int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // 记录最小元素的索引 } // 将该最小元素与未排序区间的首个元素交换 (nums[k], nums[i]) = (nums[i], nums[k]); } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; SelectionSort(nums); Console.WriteLine("选择排序完成后 nums = " + string.Join(" ", nums)); } } ================================================ FILE: codes/csharp/chapter_stack_and_queue/array_deque.cs ================================================ /** * File: array_deque.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_stack_and_queue; /* 基于环形数组实现的双向队列 */ public class ArrayDeque { int[] nums; // 用于存储双向队列元素的数组 int front; // 队首指针,指向队首元素 int queSize; // 双向队列长度 /* 构造方法 */ public ArrayDeque(int capacity) { nums = new int[capacity]; front = queSize = 0; } /* 获取双向队列的容量 */ int Capacity() { return nums.Length; } /* 获取双向队列的长度 */ public int Size() { return queSize; } /* 判断双向队列是否为空 */ public bool IsEmpty() { return queSize == 0; } /* 计算环形数组索引 */ int Index(int i) { // 通过取余操作实现数组首尾相连 // 当 i 越过数组尾部后,回到头部 // 当 i 越过数组头部后,回到尾部 return (i + Capacity()) % Capacity(); } /* 队首入队 */ public void PushFirst(int num) { if (queSize == Capacity()) { Console.WriteLine("双向队列已满"); return; } // 队首指针向左移动一位 // 通过取余操作实现 front 越过数组头部后回到尾部 front = Index(front - 1); // 将 num 添加至队首 nums[front] = num; queSize++; } /* 队尾入队 */ public void PushLast(int num) { if (queSize == Capacity()) { Console.WriteLine("双向队列已满"); return; } // 计算队尾指针,指向队尾索引 + 1 int rear = Index(front + queSize); // 将 num 添加至队尾 nums[rear] = num; queSize++; } /* 队首出队 */ public int PopFirst() { int num = PeekFirst(); // 队首指针向后移动一位 front = Index(front + 1); queSize--; return num; } /* 队尾出队 */ public int PopLast() { int num = PeekLast(); queSize--; return num; } /* 访问队首元素 */ public int PeekFirst() { if (IsEmpty()) { throw new InvalidOperationException(); } return nums[front]; } /* 访问队尾元素 */ public int PeekLast() { if (IsEmpty()) { throw new InvalidOperationException(); } // 计算尾元素索引 int last = Index(front + queSize - 1); return nums[last]; } /* 返回数组用于打印 */ public int[] ToArray() { // 仅转换有效长度范围内的列表元素 int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[Index(j)]; } return res; } } public class array_deque { [Test] public void Test() { /* 初始化双向队列 */ ArrayDeque deque = new(10); deque.PushLast(3); deque.PushLast(2); deque.PushLast(5); Console.WriteLine("双向队列 deque = " + string.Join(" ", deque.ToArray())); /* 访问元素 */ int peekFirst = deque.PeekFirst(); Console.WriteLine("队首元素 peekFirst = " + peekFirst); int peekLast = deque.PeekLast(); Console.WriteLine("队尾元素 peekLast = " + peekLast); /* 元素入队 */ deque.PushLast(4); Console.WriteLine("元素 4 队尾入队后 deque = " + string.Join(" ", deque.ToArray())); deque.PushFirst(1); Console.WriteLine("元素 1 队首入队后 deque = " + string.Join(" ", deque.ToArray())); /* 元素出队 */ int popLast = deque.PopLast(); Console.WriteLine("队尾出队元素 = " + popLast + ",队尾出队后 deque = " + string.Join(" ", deque.ToArray())); int popFirst = deque.PopFirst(); Console.WriteLine("队首出队元素 = " + popFirst + ",队首出队后 deque = " + string.Join(" ", deque.ToArray())); /* 获取双向队列的长度 */ int size = deque.Size(); Console.WriteLine("双向队列长度 size = " + size); /* 判断双向队列是否为空 */ bool isEmpty = deque.IsEmpty(); Console.WriteLine("双向队列是否为空 = " + isEmpty); } } ================================================ FILE: codes/csharp/chapter_stack_and_queue/array_queue.cs ================================================ /** * File: array_queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* 基于环形数组实现的队列 */ class ArrayQueue { int[] nums; // 用于存储队列元素的数组 int front; // 队首指针,指向队首元素 int queSize; // 队列长度 public ArrayQueue(int capacity) { nums = new int[capacity]; front = queSize = 0; } /* 获取队列的容量 */ int Capacity() { return nums.Length; } /* 获取队列的长度 */ public int Size() { return queSize; } /* 判断队列是否为空 */ public bool IsEmpty() { return queSize == 0; } /* 入队 */ public void Push(int num) { if (queSize == Capacity()) { Console.WriteLine("队列已满"); return; } // 计算队尾指针,指向队尾索引 + 1 // 通过取余操作实现 rear 越过数组尾部后回到头部 int rear = (front + queSize) % Capacity(); // 将 num 添加至队尾 nums[rear] = num; queSize++; } /* 出队 */ public int Pop() { int num = Peek(); // 队首指针向后移动一位,若越过尾部,则返回到数组头部 front = (front + 1) % Capacity(); queSize--; return num; } /* 访问队首元素 */ public int Peek() { if (IsEmpty()) throw new Exception(); return nums[front]; } /* 返回数组 */ public int[] ToArray() { // 仅转换有效长度范围内的列表元素 int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[j % this.Capacity()]; } return res; } } public class array_queue { [Test] public void Test() { /* 初始化队列 */ int capacity = 10; ArrayQueue queue = new(capacity); /* 元素入队 */ queue.Push(1); queue.Push(3); queue.Push(2); queue.Push(5); queue.Push(4); Console.WriteLine("队列 queue = " + string.Join(",", queue.ToArray())); /* 访问队首元素 */ int peek = queue.Peek(); Console.WriteLine("队首元素 peek = " + peek); /* 元素出队 */ int pop = queue.Pop(); Console.WriteLine("出队元素 pop = " + pop + ",出队后 queue = " + string.Join(",", queue.ToArray())); /* 获取队列的长度 */ int size = queue.Size(); Console.WriteLine("队列长度 size = " + size); /* 判断队列是否为空 */ bool isEmpty = queue.IsEmpty(); Console.WriteLine("队列是否为空 = " + isEmpty); /* 测试环形数组 */ for (int i = 0; i < 10; i++) { queue.Push(i); queue.Pop(); Console.WriteLine("第 " + i + " 轮入队 + 出队后 queue = " + string.Join(",", queue.ToArray())); } } } ================================================ FILE: codes/csharp/chapter_stack_and_queue/array_stack.cs ================================================ /** * File: array_stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* 基于数组实现的栈 */ class ArrayStack { List stack; public ArrayStack() { // 初始化列表(动态数组) stack = []; } /* 获取栈的长度 */ public int Size() { return stack.Count; } /* 判断栈是否为空 */ public bool IsEmpty() { return Size() == 0; } /* 入栈 */ public void Push(int num) { stack.Add(num); } /* 出栈 */ public int Pop() { if (IsEmpty()) throw new Exception(); var val = Peek(); stack.RemoveAt(Size() - 1); return val; } /* 访问栈顶元素 */ public int Peek() { if (IsEmpty()) throw new Exception(); return stack[Size() - 1]; } /* 将 List 转化为 Array 并返回 */ public int[] ToArray() { return [.. stack]; } } public class array_stack { [Test] public void Test() { /* 初始化栈 */ ArrayStack stack = new(); /* 元素入栈 */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); Console.WriteLine("栈 stack = " + string.Join(",", stack.ToArray())); /* 访问栈顶元素 */ int peek = stack.Peek(); Console.WriteLine("栈顶元素 peek = " + peek); /* 元素出栈 */ int pop = stack.Pop(); Console.WriteLine("出栈元素 pop = " + pop + ",出栈后 stack = " + string.Join(",", stack.ToArray())); /* 获取栈的长度 */ int size = stack.Size(); Console.WriteLine("栈的长度 size = " + size); /* 判断是否为空 */ bool isEmpty = stack.IsEmpty(); Console.WriteLine("栈是否为空 = " + isEmpty); } } ================================================ FILE: codes/csharp/chapter_stack_and_queue/deque.cs ================================================ /** * File: deque.cs * Created Time: 2022-12-30 * Author: moonache (microin1301@outlook.com) */ namespace hello_algo.chapter_stack_and_queue; public class deque { [Test] public void Test() { /* 初始化双向队列 */ // 在 C# 中,将链表 LinkedList 看作双向队列来使用 LinkedList deque = new(); /* 元素入队 */ deque.AddLast(2); // 添加至队尾 deque.AddLast(5); deque.AddLast(4); deque.AddFirst(3); // 添加至队首 deque.AddFirst(1); Console.WriteLine("双向队列 deque = " + string.Join(",", deque)); /* 访问元素 */ int? peekFirst = deque.First?.Value; // 队首元素 Console.WriteLine("队首元素 peekFirst = " + peekFirst); int? peekLast = deque.Last?.Value; // 队尾元素 Console.WriteLine("队尾元素 peekLast = " + peekLast); /* 元素出队 */ deque.RemoveFirst(); // 队首元素出队 Console.WriteLine("队首元素出队后 deque = " + string.Join(",", deque)); deque.RemoveLast(); // 队尾元素出队 Console.WriteLine("队尾元素出队后 deque = " + string.Join(",", deque)); /* 获取双向队列的长度 */ int size = deque.Count; Console.WriteLine("双向队列长度 size = " + size); /* 判断双向队列是否为空 */ bool isEmpty = deque.Count == 0; Console.WriteLine("双向队列是否为空 = " + isEmpty); } } ================================================ FILE: codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs ================================================ /** * File: linkedlist_deque.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_stack_and_queue; /* 双向链表节点 */ public class ListNode(int val) { public int val = val; // 节点值 public ListNode? next = null; // 后继节点引用 public ListNode? prev = null; // 前驱节点引用 } /* 基于双向链表实现的双向队列 */ public class LinkedListDeque { ListNode? front, rear; // 头节点 front, 尾节点 rear int queSize = 0; // 双向队列的长度 public LinkedListDeque() { front = null; rear = null; } /* 获取双向队列的长度 */ public int Size() { return queSize; } /* 判断双向队列是否为空 */ public bool IsEmpty() { return Size() == 0; } /* 入队操作 */ void Push(int num, bool isFront) { ListNode node = new(num); // 若链表为空,则令 front 和 rear 都指向 node if (IsEmpty()) { front = node; rear = node; } // 队首入队操作 else if (isFront) { // 将 node 添加至链表头部 front!.prev = node; node.next = front; front = node; // 更新头节点 } // 队尾入队操作 else { // 将 node 添加至链表尾部 rear!.next = node; node.prev = rear; rear = node; // 更新尾节点 } queSize++; // 更新队列长度 } /* 队首入队 */ public void PushFirst(int num) { Push(num, true); } /* 队尾入队 */ public void PushLast(int num) { Push(num, false); } /* 出队操作 */ int? Pop(bool isFront) { if (IsEmpty()) throw new Exception(); int? val; // 队首出队操作 if (isFront) { val = front?.val; // 暂存头节点值 // 删除头节点 ListNode? fNext = front?.next; if (fNext != null) { fNext.prev = null; front!.next = null; } front = fNext; // 更新头节点 } // 队尾出队操作 else { val = rear?.val; // 暂存尾节点值 // 删除尾节点 ListNode? rPrev = rear?.prev; if (rPrev != null) { rPrev.next = null; rear!.prev = null; } rear = rPrev; // 更新尾节点 } queSize--; // 更新队列长度 return val; } /* 队首出队 */ public int? PopFirst() { return Pop(true); } /* 队尾出队 */ public int? PopLast() { return Pop(false); } /* 访问队首元素 */ public int? PeekFirst() { if (IsEmpty()) throw new Exception(); return front?.val; } /* 访问队尾元素 */ public int? PeekLast() { if (IsEmpty()) throw new Exception(); return rear?.val; } /* 返回数组用于打印 */ public int?[] ToArray() { ListNode? node = front; int?[] res = new int?[Size()]; for (int i = 0; i < res.Length; i++) { res[i] = node?.val; node = node?.next; } return res; } } public class linkedlist_deque { [Test] public void Test() { /* 初始化双向队列 */ LinkedListDeque deque = new(); deque.PushLast(3); deque.PushLast(2); deque.PushLast(5); Console.WriteLine("双向队列 deque = " + string.Join(" ", deque.ToArray())); /* 访问元素 */ int? peekFirst = deque.PeekFirst(); Console.WriteLine("队首元素 peekFirst = " + peekFirst); int? peekLast = deque.PeekLast(); Console.WriteLine("队尾元素 peekLast = " + peekLast); /* 元素入队 */ deque.PushLast(4); Console.WriteLine("元素 4 队尾入队后 deque = " + string.Join(" ", deque.ToArray())); deque.PushFirst(1); Console.WriteLine("元素 1 队首入队后 deque = " + string.Join(" ", deque.ToArray())); /* 元素出队 */ int? popLast = deque.PopLast(); Console.WriteLine("队尾出队元素 = " + popLast + ",队尾出队后 deque = " + string.Join(" ", deque.ToArray())); int? popFirst = deque.PopFirst(); Console.WriteLine("队首出队元素 = " + popFirst + ",队首出队后 deque = " + string.Join(" ", deque.ToArray())); /* 获取双向队列的长度 */ int size = deque.Size(); Console.WriteLine("双向队列长度 size = " + size); /* 判断双向队列是否为空 */ bool isEmpty = deque.IsEmpty(); Console.WriteLine("双向队列是否为空 = " + isEmpty); } } ================================================ FILE: codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs ================================================ /** * File: linkedlist_queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* 基于链表实现的队列 */ class LinkedListQueue { ListNode? front, rear; // 头节点 front ,尾节点 rear int queSize = 0; public LinkedListQueue() { front = null; rear = null; } /* 获取队列的长度 */ public int Size() { return queSize; } /* 判断队列是否为空 */ public bool IsEmpty() { return Size() == 0; } /* 入队 */ public void Push(int num) { // 在尾节点后添加 num ListNode node = new(num); // 如果队列为空,则令头、尾节点都指向该节点 if (front == null) { front = node; rear = node; // 如果队列不为空,则将该节点添加到尾节点后 } else if (rear != null) { rear.next = node; rear = node; } queSize++; } /* 出队 */ public int Pop() { int num = Peek(); // 删除头节点 front = front?.next; queSize--; return num; } /* 访问队首元素 */ public int Peek() { if (IsEmpty()) throw new Exception(); return front!.val; } /* 将链表转化为 Array 并返回 */ public int[] ToArray() { if (front == null) return []; ListNode? node = front; int[] res = new int[Size()]; for (int i = 0; i < res.Length; i++) { res[i] = node!.val; node = node.next; } return res; } } public class linkedlist_queue { [Test] public void Test() { /* 初始化队列 */ LinkedListQueue queue = new(); /* 元素入队 */ queue.Push(1); queue.Push(3); queue.Push(2); queue.Push(5); queue.Push(4); Console.WriteLine("队列 queue = " + string.Join(",", queue.ToArray())); /* 访问队首元素 */ int peek = queue.Peek(); Console.WriteLine("队首元素 peek = " + peek); /* 元素出队 */ int pop = queue.Pop(); Console.WriteLine("出队元素 pop = " + pop + ",出队后 queue = " + string.Join(",", queue.ToArray())); /* 获取队列的长度 */ int size = queue.Size(); Console.WriteLine("队列长度 size = " + size); /* 判断队列是否为空 */ bool isEmpty = queue.IsEmpty(); Console.WriteLine("队列是否为空 = " + isEmpty); } } ================================================ FILE: codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs ================================================ /** * File: linkedlist_stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* 基于链表实现的栈 */ class LinkedListStack { ListNode? stackPeek; // 将头节点作为栈顶 int stkSize = 0; // 栈的长度 public LinkedListStack() { stackPeek = null; } /* 获取栈的长度 */ public int Size() { return stkSize; } /* 判断栈是否为空 */ public bool IsEmpty() { return Size() == 0; } /* 入栈 */ public void Push(int num) { ListNode node = new(num) { next = stackPeek }; stackPeek = node; stkSize++; } /* 出栈 */ public int Pop() { int num = Peek(); stackPeek = stackPeek!.next; stkSize--; return num; } /* 访问栈顶元素 */ public int Peek() { if (IsEmpty()) throw new Exception(); return stackPeek!.val; } /* 将 List 转化为 Array 并返回 */ public int[] ToArray() { if (stackPeek == null) return []; ListNode? node = stackPeek; int[] res = new int[Size()]; for (int i = res.Length - 1; i >= 0; i--) { res[i] = node!.val; node = node.next; } return res; } } public class linkedlist_stack { [Test] public void Test() { /* 初始化栈 */ LinkedListStack stack = new(); /* 元素入栈 */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); Console.WriteLine("栈 stack = " + string.Join(",", stack.ToArray())); /* 访问栈顶元素 */ int peek = stack.Peek(); Console.WriteLine("栈顶元素 peek = " + peek); /* 元素出栈 */ int pop = stack.Pop(); Console.WriteLine("出栈元素 pop = " + pop + ",出栈后 stack = " + string.Join(",", stack.ToArray())); /* 获取栈的长度 */ int size = stack.Size(); Console.WriteLine("栈的长度 size = " + size); /* 判断是否为空 */ bool isEmpty = stack.IsEmpty(); Console.WriteLine("栈是否为空 = " + isEmpty); } } ================================================ FILE: codes/csharp/chapter_stack_and_queue/queue.cs ================================================ /** * File: queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; public class queue { [Test] public void Test() { /* 初始化队列 */ Queue queue = new(); /* 元素入队 */ queue.Enqueue(1); queue.Enqueue(3); queue.Enqueue(2); queue.Enqueue(5); queue.Enqueue(4); Console.WriteLine("队列 queue = " + string.Join(",", queue)); /* 访问队首元素 */ int peek = queue.Peek(); Console.WriteLine("队首元素 peek = " + peek); /* 元素出队 */ int pop = queue.Dequeue(); Console.WriteLine("出队元素 pop = " + pop + ",出队后 queue = " + string.Join(",", queue)); /* 获取队列的长度 */ int size = queue.Count; Console.WriteLine("队列长度 size = " + size); /* 判断队列是否为空 */ bool isEmpty = queue.Count == 0; Console.WriteLine("队列是否为空 = " + isEmpty); } } ================================================ FILE: codes/csharp/chapter_stack_and_queue/stack.cs ================================================ /** * File: stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; public class stack { [Test] public void Test() { /* 初始化栈 */ Stack stack = new(); /* 元素入栈 */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); // 请注意,stack.ToArray() 得到的是倒序序列,即索引 0 为栈顶 Console.WriteLine("栈 stack = " + string.Join(",", stack)); /* 访问栈顶元素 */ int peek = stack.Peek(); Console.WriteLine("栈顶元素 peek = " + peek); /* 元素出栈 */ int pop = stack.Pop(); Console.WriteLine("出栈元素 pop = " + pop + ",出栈后 stack = " + string.Join(",", stack)); /* 获取栈的长度 */ int size = stack.Count; Console.WriteLine("栈的长度 size = " + size); /* 判断是否为空 */ bool isEmpty = stack.Count == 0; Console.WriteLine("栈是否为空 = " + isEmpty); } } ================================================ FILE: codes/csharp/chapter_tree/array_binary_tree.cs ================================================ /** * File: array_binary_tree.cs * Created Time: 2023-07-20 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_tree; /* 数组表示下的二叉树类 */ public class ArrayBinaryTree(List arr) { List tree = new(arr); /* 列表容量 */ public int Size() { return tree.Count; } /* 获取索引为 i 节点的值 */ public int? Val(int i) { // 若索引越界,则返回 null ,代表空位 if (i < 0 || i >= Size()) return null; return tree[i]; } /* 获取索引为 i 节点的左子节点的索引 */ public int Left(int i) { return 2 * i + 1; } /* 获取索引为 i 节点的右子节点的索引 */ public int Right(int i) { return 2 * i + 2; } /* 获取索引为 i 节点的父节点的索引 */ public int Parent(int i) { return (i - 1) / 2; } /* 层序遍历 */ public List LevelOrder() { List res = []; // 直接遍历数组 for (int i = 0; i < Size(); i++) { if (Val(i).HasValue) res.Add(Val(i)!.Value); } return res; } /* 深度优先遍历 */ void DFS(int i, string order, List res) { // 若为空位,则返回 if (!Val(i).HasValue) return; // 前序遍历 if (order == "pre") res.Add(Val(i)!.Value); DFS(Left(i), order, res); // 中序遍历 if (order == "in") res.Add(Val(i)!.Value); DFS(Right(i), order, res); // 后序遍历 if (order == "post") res.Add(Val(i)!.Value); } /* 前序遍历 */ public List PreOrder() { List res = []; DFS(0, "pre", res); return res; } /* 中序遍历 */ public List InOrder() { List res = []; DFS(0, "in", res); return res; } /* 后序遍历 */ public List PostOrder() { List res = []; DFS(0, "post", res); return res; } } public class array_binary_tree { [Test] public void Test() { // 初始化二叉树 // 这里借助了一个从数组直接生成二叉树的函数 List arr = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; TreeNode? root = TreeNode.ListToTree(arr); Console.WriteLine("\n初始化二叉树\n"); Console.WriteLine("二叉树的数组表示:"); Console.WriteLine(arr.PrintList()); Console.WriteLine("二叉树的链表表示:"); PrintUtil.PrintTree(root); // 数组表示下的二叉树类 ArrayBinaryTree abt = new(arr); // 访问节点 int i = 1; int l = abt.Left(i); int r = abt.Right(i); int p = abt.Parent(i); Console.WriteLine("\n当前节点的索引为 " + i + " ,值为 " + abt.Val(i)); Console.WriteLine("其左子节点的索引为 " + l + " ,值为 " + (abt.Val(l).HasValue ? abt.Val(l) : "null")); Console.WriteLine("其右子节点的索引为 " + r + " ,值为 " + (abt.Val(r).HasValue ? abt.Val(r) : "null")); Console.WriteLine("其父节点的索引为 " + p + " ,值为 " + (abt.Val(p).HasValue ? abt.Val(p) : "null")); // 遍历树 List res = abt.LevelOrder(); Console.WriteLine("\n层序遍历为:" + res.PrintList()); res = abt.PreOrder(); Console.WriteLine("前序遍历为:" + res.PrintList()); res = abt.InOrder(); Console.WriteLine("中序遍历为:" + res.PrintList()); res = abt.PostOrder(); Console.WriteLine("后序遍历为:" + res.PrintList()); } } ================================================ FILE: codes/csharp/chapter_tree/avl_tree.cs ================================================ /** * File: avl_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; /* AVL 树 */ class AVLTree { public TreeNode? root; // 根节点 /* 获取节点高度 */ int Height(TreeNode? node) { // 空节点高度为 -1 ,叶节点高度为 0 return node == null ? -1 : node.height; } /* 更新节点高度 */ void UpdateHeight(TreeNode node) { // 节点高度等于最高子树高度 + 1 node.height = Math.Max(Height(node.left), Height(node.right)) + 1; } /* 获取平衡因子 */ public int BalanceFactor(TreeNode? node) { // 空节点平衡因子为 0 if (node == null) return 0; // 节点平衡因子 = 左子树高度 - 右子树高度 return Height(node.left) - Height(node.right); } /* 右旋操作 */ TreeNode? RightRotate(TreeNode? node) { TreeNode? child = node?.left; TreeNode? grandChild = child?.right; // 以 child 为原点,将 node 向右旋转 child.right = node; node.left = grandChild; // 更新节点高度 UpdateHeight(node); UpdateHeight(child); // 返回旋转后子树的根节点 return child; } /* 左旋操作 */ TreeNode? LeftRotate(TreeNode? node) { TreeNode? child = node?.right; TreeNode? grandChild = child?.left; // 以 child 为原点,将 node 向左旋转 child.left = node; node.right = grandChild; // 更新节点高度 UpdateHeight(node); UpdateHeight(child); // 返回旋转后子树的根节点 return child; } /* 执行旋转操作,使该子树重新恢复平衡 */ TreeNode? Rotate(TreeNode? node) { // 获取节点 node 的平衡因子 int balanceFactorInt = BalanceFactor(node); // 左偏树 if (balanceFactorInt > 1) { if (BalanceFactor(node?.left) >= 0) { // 右旋 return RightRotate(node); } else { // 先左旋后右旋 node!.left = LeftRotate(node!.left); return RightRotate(node); } } // 右偏树 if (balanceFactorInt < -1) { if (BalanceFactor(node?.right) <= 0) { // 左旋 return LeftRotate(node); } else { // 先右旋后左旋 node!.right = RightRotate(node!.right); return LeftRotate(node); } } // 平衡树,无须旋转,直接返回 return node; } /* 插入节点 */ public void Insert(int val) { root = InsertHelper(root, val); } /* 递归插入节点(辅助方法) */ TreeNode? InsertHelper(TreeNode? node, int val) { if (node == null) return new TreeNode(val); /* 1. 查找插入位置并插入节点 */ if (val < node.val) node.left = InsertHelper(node.left, val); else if (val > node.val) node.right = InsertHelper(node.right, val); else return node; // 重复节点不插入,直接返回 UpdateHeight(node); // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = Rotate(node); // 返回子树的根节点 return node; } /* 删除节点 */ public void Remove(int val) { root = RemoveHelper(root, val); } /* 递归删除节点(辅助方法) */ TreeNode? RemoveHelper(TreeNode? node, int val) { if (node == null) return null; /* 1. 查找节点并删除 */ if (val < node.val) node.left = RemoveHelper(node.left, val); else if (val > node.val) node.right = RemoveHelper(node.right, val); else { if (node.left == null || node.right == null) { TreeNode? child = node.left ?? node.right; // 子节点数量 = 0 ,直接删除 node 并返回 if (child == null) return null; // 子节点数量 = 1 ,直接删除 node else node = child; } else { // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 TreeNode? temp = node.right; while (temp.left != null) { temp = temp.left; } node.right = RemoveHelper(node.right, temp.val!.Value); node.val = temp.val; } } UpdateHeight(node); // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = Rotate(node); // 返回子树的根节点 return node; } /* 查找节点 */ public TreeNode? Search(int val) { TreeNode? cur = root; // 循环查找,越过叶节点后跳出 while (cur != null) { // 目标节点在 cur 的右子树中 if (cur.val < val) cur = cur.right; // 目标节点在 cur 的左子树中 else if (cur.val > val) cur = cur.left; // 找到目标节点,跳出循环 else break; } // 返回目标节点 return cur; } } public class avl_tree { static void TestInsert(AVLTree tree, int val) { tree.Insert(val); Console.WriteLine("\n插入节点 " + val + " 后,AVL 树为"); PrintUtil.PrintTree(tree.root); } static void TestRemove(AVLTree tree, int val) { tree.Remove(val); Console.WriteLine("\n删除节点 " + val + " 后,AVL 树为"); PrintUtil.PrintTree(tree.root); } [Test] public void Test() { /* 初始化空 AVL 树 */ AVLTree avlTree = new(); /* 插入节点 */ // 请关注插入节点后,AVL 树是如何保持平衡的 TestInsert(avlTree, 1); TestInsert(avlTree, 2); TestInsert(avlTree, 3); TestInsert(avlTree, 4); TestInsert(avlTree, 5); TestInsert(avlTree, 8); TestInsert(avlTree, 7); TestInsert(avlTree, 9); TestInsert(avlTree, 10); TestInsert(avlTree, 6); /* 插入重复节点 */ TestInsert(avlTree, 7); /* 删除节点 */ // 请关注删除节点后,AVL 树是如何保持平衡的 TestRemove(avlTree, 8); // 删除度为 0 的节点 TestRemove(avlTree, 5); // 删除度为 1 的节点 TestRemove(avlTree, 4); // 删除度为 2 的节点 /* 查询节点 */ TreeNode? node = avlTree.Search(7); Console.WriteLine("\n查找到的节点对象为 " + node + ",节点值 = " + node?.val); } } ================================================ FILE: codes/csharp/chapter_tree/binary_search_tree.cs ================================================ /** * File: binary_search_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; class BinarySearchTree { TreeNode? root; public BinarySearchTree() { // 初始化空树 root = null; } /* 获取二叉树根节点 */ public TreeNode? GetRoot() { return root; } /* 查找节点 */ public TreeNode? Search(int num) { TreeNode? cur = root; // 循环查找,越过叶节点后跳出 while (cur != null) { // 目标节点在 cur 的右子树中 if (cur.val < num) cur = cur.right; // 目标节点在 cur 的左子树中 else if (cur.val > num) cur = cur.left; // 找到目标节点,跳出循环 else break; } // 返回目标节点 return cur; } /* 插入节点 */ public void Insert(int num) { // 若树为空,则初始化根节点 if (root == null) { root = new TreeNode(num); return; } TreeNode? cur = root, pre = null; // 循环查找,越过叶节点后跳出 while (cur != null) { // 找到重复节点,直接返回 if (cur.val == num) return; pre = cur; // 插入位置在 cur 的右子树中 if (cur.val < num) cur = cur.right; // 插入位置在 cur 的左子树中 else cur = cur.left; } // 插入节点 TreeNode node = new(num); if (pre != null) { if (pre.val < num) pre.right = node; else pre.left = node; } } /* 删除节点 */ public void Remove(int num) { // 若树为空,直接提前返回 if (root == null) return; TreeNode? cur = root, pre = null; // 循环查找,越过叶节点后跳出 while (cur != null) { // 找到待删除节点,跳出循环 if (cur.val == num) break; pre = cur; // 待删除节点在 cur 的右子树中 if (cur.val < num) cur = cur.right; // 待删除节点在 cur 的左子树中 else cur = cur.left; } // 若无待删除节点,则直接返回 if (cur == null) return; // 子节点数量 = 0 or 1 if (cur.left == null || cur.right == null) { // 当子节点数量 = 0 / 1 时, child = null / 该子节点 TreeNode? child = cur.left ?? cur.right; // 删除节点 cur if (cur != root) { if (pre!.left == cur) pre.left = child; else pre.right = child; } else { // 若删除节点为根节点,则重新指定根节点 root = child; } } // 子节点数量 = 2 else { // 获取中序遍历中 cur 的下一个节点 TreeNode? tmp = cur.right; while (tmp.left != null) { tmp = tmp.left; } // 递归删除节点 tmp Remove(tmp.val!.Value); // 用 tmp 覆盖 cur cur.val = tmp.val; } } } public class binary_search_tree { [Test] public void Test() { /* 初始化二叉搜索树 */ BinarySearchTree bst = new(); // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 int[] nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; foreach (int num in nums) { bst.Insert(num); } Console.WriteLine("\n初始化的二叉树为\n"); PrintUtil.PrintTree(bst.GetRoot()); /* 查找节点 */ TreeNode? node = bst.Search(7); Console.WriteLine("\n查找到的节点对象为 " + node + ",节点值 = " + node?.val); /* 插入节点 */ bst.Insert(16); Console.WriteLine("\n插入节点 16 后,二叉树为\n"); PrintUtil.PrintTree(bst.GetRoot()); /* 删除节点 */ bst.Remove(1); Console.WriteLine("\n删除节点 1 后,二叉树为\n"); PrintUtil.PrintTree(bst.GetRoot()); bst.Remove(2); Console.WriteLine("\n删除节点 2 后,二叉树为\n"); PrintUtil.PrintTree(bst.GetRoot()); bst.Remove(4); Console.WriteLine("\n删除节点 4 后,二叉树为\n"); PrintUtil.PrintTree(bst.GetRoot()); } } ================================================ FILE: codes/csharp/chapter_tree/binary_tree.cs ================================================ /** * File: binary_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree { [Test] public void Test() { /* 初始化二叉树 */ // 初始化节点 TreeNode n1 = new(1); TreeNode n2 = new(2); TreeNode n3 = new(3); TreeNode n4 = new(4); TreeNode n5 = new(5); // 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; Console.WriteLine("\n初始化二叉树\n"); PrintUtil.PrintTree(n1); /* 插入与删除节点 */ TreeNode P = new(0); // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; Console.WriteLine("\n插入节点 P 后\n"); PrintUtil.PrintTree(n1); // 删除节点 P n1.left = n2; Console.WriteLine("\n删除节点 P 后\n"); PrintUtil.PrintTree(n1); } } ================================================ FILE: codes/csharp/chapter_tree/binary_tree_bfs.cs ================================================ /** * File: binary_tree_bfs.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree_bfs { /* 层序遍历 */ List LevelOrder(TreeNode root) { // 初始化队列,加入根节点 Queue queue = new(); queue.Enqueue(root); // 初始化一个列表,用于保存遍历序列 List list = []; while (queue.Count != 0) { TreeNode node = queue.Dequeue(); // 队列出队 list.Add(node.val!.Value); // 保存节点值 if (node.left != null) queue.Enqueue(node.left); // 左子节点入队 if (node.right != null) queue.Enqueue(node.right); // 右子节点入队 } return list; } [Test] public void Test() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); Console.WriteLine("\n初始化二叉树\n"); PrintUtil.PrintTree(root); List list = LevelOrder(root!); Console.WriteLine("\n层序遍历的节点打印序列 = " + string.Join(",", list)); } } ================================================ FILE: codes/csharp/chapter_tree/binary_tree_dfs.cs ================================================ /** * File: binary_tree_dfs.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree_dfs { List list = []; /* 前序遍历 */ void PreOrder(TreeNode? root) { if (root == null) return; // 访问优先级:根节点 -> 左子树 -> 右子树 list.Add(root.val!.Value); PreOrder(root.left); PreOrder(root.right); } /* 中序遍历 */ void InOrder(TreeNode? root) { if (root == null) return; // 访问优先级:左子树 -> 根节点 -> 右子树 InOrder(root.left); list.Add(root.val!.Value); InOrder(root.right); } /* 后序遍历 */ void PostOrder(TreeNode? root) { if (root == null) return; // 访问优先级:左子树 -> 右子树 -> 根节点 PostOrder(root.left); PostOrder(root.right); list.Add(root.val!.Value); } [Test] public void Test() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); Console.WriteLine("\n初始化二叉树\n"); PrintUtil.PrintTree(root); list.Clear(); PreOrder(root); Console.WriteLine("\n前序遍历的节点打印序列 = " + string.Join(",", list)); list.Clear(); InOrder(root); Console.WriteLine("\n中序遍历的节点打印序列 = " + string.Join(",", list)); list.Clear(); PostOrder(root); Console.WriteLine("\n后序遍历的节点打印序列 = " + string.Join(",", list)); } } ================================================ FILE: codes/csharp/csharp.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.002.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hello-algo", "hello-algo.csproj", "{48B60439-EFDC-4C8F-AE8D-41979958C8AC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.Build.0 = Debug|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1E773F8A-FF66-4974-820B-FCE9032D19AE} EndGlobalSection EndGlobal ================================================ FILE: codes/csharp/hello-algo.csproj ================================================  Exe net8.0 hello_algo enable enable all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: codes/csharp/utils/ListNode.cs ================================================ // File: ListNode.cs // Created Time: 2022-12-16 // Author: mingXta (1195669834@qq.com) namespace hello_algo.utils; /* 链表节点 */ public class ListNode(int x) { public int val = x; public ListNode? next; /* 将数组反序列化为链表 */ public static ListNode? ArrToLinkedList(int[] arr) { ListNode dum = new(0); ListNode head = dum; foreach (int val in arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } public override string? ToString() { List list = []; var head = this; while (head != null) { list.Add(head.val.ToString()); head = head.next; } return string.Join("->", list); } } ================================================ FILE: codes/csharp/utils/PrintUtil.cs ================================================ /** * File: PrintUtil.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com), krahets (krahets@163.com) */ namespace hello_algo.utils; public class Trunk(Trunk? prev, string str) { public Trunk? prev = prev; public string str = str; }; public static class PrintUtil { /* 打印列表 */ public static void PrintList(IList list) { Console.WriteLine("[" + string.Join(", ", list) + "]"); } public static string PrintList(this IEnumerable list) { return $"[ {string.Join(", ", list.Select(x => x?.ToString() ?? "null"))} ]"; } /* 打印矩阵 (Array) */ public static void PrintMatrix(T[][] matrix) { Console.WriteLine("["); foreach (T[] row in matrix) { Console.WriteLine(" " + string.Join(", ", row) + ","); } Console.WriteLine("]"); } /* 打印矩阵 (List) */ public static void PrintMatrix(List> matrix) { Console.WriteLine("["); foreach (List row in matrix) { Console.WriteLine(" " + string.Join(", ", row) + ","); } Console.WriteLine("]"); } /* 打印链表 */ public static void PrintLinkedList(ListNode? head) { List list = []; while (head != null) { list.Add(head.val.ToString()); head = head.next; } Console.Write(string.Join(" -> ", list)); } /** * 打印二叉树 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ public static void PrintTree(TreeNode? root) { PrintTree(root, null, false); } /* 打印二叉树 */ public static void PrintTree(TreeNode? root, Trunk? prev, bool isRight) { if (root == null) { return; } string prev_str = " "; Trunk trunk = new(prev, prev_str); PrintTree(root.right, trunk, true); if (prev == null) { trunk.str = "———"; } else if (isRight) { trunk.str = "/———"; prev_str = " |"; } else { trunk.str = "\\———"; prev.str = prev_str; } ShowTrunks(trunk); Console.WriteLine(" " + root.val); if (prev != null) { prev.str = prev_str; } trunk.str = " |"; PrintTree(root.left, trunk, false); } public static void ShowTrunks(Trunk? p) { if (p == null) { return; } ShowTrunks(p.prev); Console.Write(p.str); } /* 打印哈希表 */ public static void PrintHashMap(Dictionary map) where K : notnull { foreach (var kv in map.Keys) { Console.WriteLine(kv.ToString() + " -> " + map[kv]?.ToString()); } } /* 打印堆 */ public static void PrintHeap(Queue queue) { Console.Write("堆的数组表示:"); List list = [.. queue]; Console.WriteLine(string.Join(',', list)); Console.WriteLine("堆的树状表示:"); TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); PrintTree(tree); } /* 打印优先队列 */ public static void PrintHeap(PriorityQueue queue) { var newQueue = new PriorityQueue(queue.UnorderedItems, queue.Comparer); Console.Write("堆的数组表示:"); List list = []; while (newQueue.TryDequeue(out int element, out _)) { list.Add(element); } Console.WriteLine("堆的树状表示:"); Console.WriteLine(string.Join(',', list.ToList())); TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); PrintTree(tree); } } ================================================ FILE: codes/csharp/utils/TreeNode.cs ================================================ /** * File: TreeNode.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.utils; /* 二叉树节点类 */ public class TreeNode(int? x) { public int? val = x; // 节点值 public int height; // 节点高度 public TreeNode? left; // 左子节点引用 public TreeNode? right; // 右子节点引用 // 序列化编码规则请参考: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二叉树的数组表示: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // 二叉树的链表表示: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* 将列表反序列化为二叉树:递归 */ static TreeNode? ListToTreeDFS(List arr, int i) { if (i < 0 || i >= arr.Count || !arr[i].HasValue) { return null; } TreeNode root = new(arr[i]) { left = ListToTreeDFS(arr, 2 * i + 1), right = ListToTreeDFS(arr, 2 * i + 2) }; return root; } /* 将列表反序列化为二叉树 */ public static TreeNode? ListToTree(List arr) { return ListToTreeDFS(arr, 0); } /* 将二叉树序列化为列表:递归 */ static void TreeToListDFS(TreeNode? root, int i, List res) { if (root == null) return; while (i >= res.Count) { res.Add(null); } res[i] = root.val; TreeToListDFS(root.left, 2 * i + 1, res); TreeToListDFS(root.right, 2 * i + 2, res); } /* 将二叉树序列化为列表 */ public static List TreeToList(TreeNode root) { List res = []; TreeToListDFS(root, 0, res); return res; } } ================================================ FILE: codes/csharp/utils/Vertex.cs ================================================ /** * File: Vertex.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com), krahets (krahets@163.com) */ namespace hello_algo.utils; /* 顶点类 */ public class Vertex(int val) { public int val = val; /* 输入值列表 vals ,返回顶点列表 vets */ public static Vertex[] ValsToVets(int[] vals) { Vertex[] vets = new Vertex[vals.Length]; for (int i = 0; i < vals.Length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* 输入顶点列表 vets ,返回值列表 vals */ public static List VetsToVals(List vets) { List vals = []; foreach (Vertex vet in vets) { vals.Add(vet.val); } return vals; } } ================================================ FILE: codes/dart/build.dart ================================================ import 'dart:io'; void main() { Directory foldPath = Directory('codes/dart/'); List files = foldPath.listSync(); int totalCount = 0; int errorCount = 0; for (var file in files) { if (file.path.endsWith('build.dart')) continue; if (file is File && file.path.endsWith('.dart')) { totalCount++; try { Process.runSync('dart', [file.path]); } catch (e) { errorCount++; print('Error: $e'); print('File: ${file.path}'); } } else if (file is Directory) { List subFiles = file.listSync(); for (var subFile in subFiles) { if (subFile is File && subFile.path.endsWith('.dart')) { totalCount++; try { Process.runSync('dart', [subFile.path]); } catch (e) { errorCount++; print('Error: $e'); print('File: ${file.path}'); } } } } } print('===== Build Complete ====='); print('Total: $totalCount'); print('Error: $errorCount'); } ================================================ FILE: codes/dart/chapter_array_and_linkedlist/array.dart ================================================ /** * File: array.dart * Created Time: 2023-01-20 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable import 'dart:math'; /* 随机访问元素 */ int randomAccess(List nums) { // 在区间 [0, nums.length) 中随机抽取一个数字 int randomIndex = Random().nextInt(nums.length); // 获取并返回随机元素 int randomNum = nums[randomIndex]; return randomNum; } /* 扩展数组长度 */ List extend(List nums, int enlarge) { // 初始化一个扩展长度后的数组 List res = List.filled(nums.length + enlarge, 0); // 将原数组中的所有元素复制到新数组 for (var i = 0; i < nums.length; i++) { res[i] = nums[i]; } // 返回扩展后的新数组 return res; } /* 在数组的索引 index 处插入元素 _num */ void insert(List nums, int _num, int index) { // 把索引 index 以及之后的所有元素向后移动一位 for (var i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 将 _num 赋给 index 处元素 nums[index] = _num; } /* 删除索引 index 处的元素 */ void remove(List nums, int index) { // 把索引 index 之后的所有元素向前移动一位 for (var i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* 遍历数组元素 */ void traverse(List nums) { int count = 0; // 通过索引遍历数组 for (var i = 0; i < nums.length; i++) { count += nums[i]; } // 直接遍历数组元素 for (int _num in nums) { count += _num; } // 通过 forEach 方法遍历数组 nums.forEach((_num) { count += _num; }); } /* 在数组中查找指定元素 */ int find(List nums, int target) { for (var i = 0; i < nums.length; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ void main() { /* 初始化数组 */ var arr = List.filled(5, 0); print('数组 arr = $arr'); List nums = [1, 3, 2, 5, 4]; print('数组 nums = $nums'); /* 随机访问 */ int randomNum = randomAccess(nums); print('在 nums 中获取随机元素 $randomNum'); /* 长度扩展 */ nums = extend(nums, 3); print('将数组长度扩展至 8 ,得到 nums = $nums'); /* 插入元素 */ insert(nums, 6, 3); print("在索引 3 处插入数字 6 ,得到 nums = $nums"); /* 删除元素 */ remove(nums, 2); print("删除索引 2 处的元素,得到 nums = $nums"); /* 遍历数组 */ traverse(nums); /* 查找元素 */ int index = find(nums, 3); print("在 nums 中查找元素 3 ,得到索引 = $index"); } ================================================ FILE: codes/dart/chapter_array_and_linkedlist/linked_list.dart ================================================ /** * File: linked_list.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import '../utils/list_node.dart'; import '../utils/print_util.dart'; /* 在链表的节点 n0 之后插入节点 P */ void insert(ListNode n0, ListNode P) { ListNode? n1 = n0.next; P.next = n1; n0.next = P; } /* 删除链表的节点 n0 之后的首个节点 */ void remove(ListNode n0) { if (n0.next == null) return; // n0 -> P -> n1 ListNode P = n0.next!; ListNode? n1 = P.next; n0.next = n1; } /* 访问链表中索引为 index 的节点 */ ListNode? access(ListNode? head, int index) { for (var i = 0; i < index; i++) { if (head == null) return null; head = head.next; } return head; } /* 在链表中查找值为 target 的首个节点 */ int find(ListNode? head, int target) { int index = 0; while (head != null) { if (head.val == target) { return index; } head = head.next; index++; } return -1; } /* Driver Code */ void main() { // 初始化链表 // 初始化各个节点 ListNode n0 = ListNode(1); ListNode n1 = ListNode(3); ListNode n2 = ListNode(2); ListNode n3 = ListNode(5); ListNode n4 = ListNode(4); // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; print('初始化的链表为'); printLinkedList(n0); /* 插入节点 */ insert(n0, ListNode(0)); print('插入节点后的链表为'); printLinkedList(n0); /* 删除节点 */ remove(n0); print('删除节点后的链表为'); printLinkedList(n0); /* 访问节点 */ ListNode? node = access(n0, 3); print('链表中索引 3 处的节点的值 = ${node!.val}'); /* 查找节点 */ int index = find(n0, 2); print('链表中值为 2 的节点的索引 = $index'); } ================================================ FILE: codes/dart/chapter_array_and_linkedlist/list.dart ================================================ /** * File: list.dart * Created Time: 2023-01-24 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable /* Driver Code */ void main() { /* 初始化列表 */ List nums = [1, 3, 2, 5, 4]; print('列表 nums = $nums'); /* 访问元素 */ int _num = nums[1]; print('访问索引 1 处的元素,得到 _num = $_num'); /* 更新元素 */ nums[1] = 0; print('将索引 1 处的元素更新为 0 ,得到 nums = $nums'); /* 清空列表 */ nums.clear(); print('清空列表后 nums = $nums'); /* 在尾部添加元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print('添加元素后 nums = $nums'); /* 在中间插入元素 */ nums.insert(3, 6); print('在索引 3 处插入数字 6 ,得到 nums = $nums'); /* 删除元素 */ nums.removeAt(3); print('删除索引 3 处的元素,得到 nums = $nums'); /* 通过索引遍历列表 */ int count = 0; for (var i = 0; i < nums.length; i++) { count += nums[i]; } /* 直接遍历列表元素 */ count = 0; for (var x in nums) { count += x; } /* 拼接两个列表 */ List nums1 = [6, 8, 7, 10, 9]; nums.addAll(nums1); print('将列表 nums1 拼接到 nums 之后,得到 nums = $nums'); /* 排序列表 */ nums.sort(); print('排序列表后 nums = $nums'); } ================================================ FILE: codes/dart/chapter_array_and_linkedlist/my_list.dart ================================================ /** * File: my_list.dart * Created Time: 2023-02-05 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 列表类 */ class MyList { late List _arr; // 数组(存储列表元素) int _capacity = 10; // 列表容量 int _size = 0; // 列表长度(当前元素数量) int _extendRatio = 2; // 每次列表扩容的倍数 /* 构造方法 */ MyList() { _arr = List.filled(_capacity, 0); } /* 获取列表长度(当前元素数量)*/ int size() => _size; /* 获取列表容量 */ int capacity() => _capacity; /* 访问元素 */ int get(int index) { if (index >= _size) throw RangeError('索引越界'); return _arr[index]; } /* 更新元素 */ void set(int index, int _num) { if (index >= _size) throw RangeError('索引越界'); _arr[index] = _num; } /* 在尾部添加元素 */ void add(int _num) { // 元素数量超出容量时,触发扩容机制 if (_size == _capacity) extendCapacity(); _arr[_size] = _num; // 更新元素数量 _size++; } /* 在中间插入元素 */ void insert(int index, int _num) { if (index >= _size) throw RangeError('索引越界'); // 元素数量超出容量时,触发扩容机制 if (_size == _capacity) extendCapacity(); // 将索引 index 以及之后的元素都向后移动一位 for (var j = _size - 1; j >= index; j--) { _arr[j + 1] = _arr[j]; } _arr[index] = _num; // 更新元素数量 _size++; } /* 删除元素 */ int remove(int index) { if (index >= _size) throw RangeError('索引越界'); int _num = _arr[index]; // 将将索引 index 之后的元素都向前移动一位 for (var j = index; j < _size - 1; j++) { _arr[j] = _arr[j + 1]; } // 更新元素数量 _size--; // 返回被删除的元素 return _num; } /* 列表扩容 */ void extendCapacity() { // 新建一个长度为原数组 _extendRatio 倍的新数组 final _newNums = List.filled(_capacity * _extendRatio, 0); // 将原数组复制到新数组 List.copyRange(_newNums, 0, _arr); // 更新 _arr 的引用 _arr = _newNums; // 更新列表容量 _capacity = _arr.length; } /* 将列表转换为数组 */ List toArray() { List arr = []; for (var i = 0; i < _size; i++) { arr.add(get(i)); } return arr; } } /* Driver Code */ void main() { /* 初始化列表 */ MyList nums = MyList(); /* 在尾部添加元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print( '列表 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,长度 = ${nums.size()}'); /* 在中间插入元素 */ nums.insert(3, 6); print('在索引 3 处插入数字 6 ,得到 nums = ${nums.toArray()}'); /* 删除元素 */ nums.remove(3); print('删除索引 3 处的元素,得到 nums = ${nums.toArray()}'); /* 访问元素 */ int _num = nums.get(1); print('访问索引 1 处的元素,得到 _num = $_num'); /* 更新元素 */ nums.set(1, 0); print('将索引 1 处的元素更新为 0 ,得到 nums = ${nums.toArray()}'); /* 测试扩容机制 */ for (var i = 0; i < 10; i++) { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 nums.add(i); } print( '扩容后的列表 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,长度 = ${nums.size()}'); } ================================================ FILE: codes/dart/chapter_backtracking/n_queens.dart ================================================ /** * File: n_queens.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯算法:n 皇后 */ void backtrack( int row, int n, List> state, List>> res, List cols, List diags1, List diags2, ) { // 当放置完所有行时,记录解 if (row == n) { List> copyState = []; for (List sRow in state) { copyState.add(List.from(sRow)); } res.add(copyState); return; } // 遍历所有列 for (int col = 0; col < n; col++) { // 计算该格子对应的主对角线和次对角线 int diag1 = row - col + n - 1; int diag2 = row + col; // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 尝试:将皇后放置在该格子 state[row][col] = "Q"; cols[col] = true; diags1[diag1] = true; diags2[diag2] = true; // 放置下一行 backtrack(row + 1, n, state, res, cols, diags1, diags2); // 回退:将该格子恢复为空位 state[row][col] = "#"; cols[col] = false; diags1[diag1] = false; diags2[diag2] = false; } } } /* 求解 n 皇后 */ List>> nQueens(int n) { // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 List> state = List.generate(n, (index) => List.filled(n, "#")); List cols = List.filled(n, false); // 记录列是否有皇后 List diags1 = List.filled(2 * n - 1, false); // 记录主对角线上是否有皇后 List diags2 = List.filled(2 * n - 1, false); // 记录次对角线上是否有皇后 List>> res = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } /* Driver Code */ void main() { int n = 4; List>> res = nQueens(n); print("输入棋盘长宽为 $n"); print("皇后放置方案共有 ${res.length} 种"); for (List> state in res) { print("--------------------"); for (List row in state) { print(row); } } } ================================================ FILE: codes/dart/chapter_backtracking/permutations_i.dart ================================================ /** * File: permutations_i.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯算法:全排列 I */ void backtrack( List state, List choices, List selected, List> res, ) { // 当状态长度等于元素数量时,记录解 if (state.length == choices.length) { res.add(List.from(state)); return; } // 遍历所有选择 for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // 剪枝:不允许重复选择元素 if (!selected[i]) { // 尝试:做出选择,更新状态 selected[i] = true; state.add(choice); // 进行下一轮选择 backtrack(state, choices, selected, res); // 回退:撤销选择,恢复到之前的状态 selected[i] = false; state.removeLast(); } } } /* 全排列 I */ List> permutationsI(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } /* Driver Code */ void main() { List nums = [1, 2, 3]; List> res = permutationsI(nums); print("输入数组 nums = $nums"); print("所有排列 res = $res"); } ================================================ FILE: codes/dart/chapter_backtracking/permutations_ii.dart ================================================ /** * File: permutations_ii.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯算法:全排列 II */ void backtrack( List state, List choices, List selected, List> res, ) { // 当状态长度等于元素数量时,记录解 if (state.length == choices.length) { res.add(List.from(state)); return; } // 遍历所有选择 Set duplicated = {}; for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 if (!selected[i] && !duplicated.contains(choice)) { // 尝试:做出选择,更新状态 duplicated.add(choice); // 记录选择过的元素值 selected[i] = true; state.add(choice); // 进行下一轮选择 backtrack(state, choices, selected, res); // 回退:撤销选择,恢复到之前的状态 selected[i] = false; state.removeLast(); } } } /* 全排列 II */ List> permutationsII(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } /* Driver Code */ void main() { List nums = [1, 2, 2]; List> res = permutationsII(nums); print("输入数组 nums = $nums"); print("所有排列 res = $res"); } ================================================ FILE: codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart ================================================ /** * File: preorder_traversal_i_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 前序遍历:例题一 */ void preOrder(TreeNode? root, List res) { if (root == null) { return; } if (root.val == 7) { // 记录解 res.add(root); } preOrder(root.left, res); preOrder(root.right, res); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\n初始化二叉树"); printTree(root); // 前序遍历 List res = []; preOrder(root, res); print("\n输出所有值为 7 的节点"); print(List.generate(res.length, (i) => res[i].val)); } ================================================ FILE: codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart ================================================ /** * File: preorder_traversal_ii_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 前序遍历:例题二 */ void preOrder( TreeNode? root, List path, List> res, ) { if (root == null) { return; } // 尝试 path.add(root); if (root.val == 7) { // 记录解 res.add(List.from(path)); } preOrder(root.left, path, res); preOrder(root.right, path, res); // 回退 path.removeLast(); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\n初始化二叉树"); printTree(root); // 前序遍历 List path = []; List> res = []; preOrder(root, path, res); print("\n输出所有根节点到节点 7 的路径"); for (List vals in res) { print(List.generate(vals.length, (i) => vals[i].val)); } } ================================================ FILE: codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart ================================================ /** * File: preorder_traversal_iii_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 前序遍历:例题三 */ void preOrder( TreeNode? root, List path, List> res, ) { if (root == null || root.val == 3) { return; } // 尝试 path.add(root); if (root.val == 7) { // 记录解 res.add(List.from(path)); } preOrder(root.left, path, res); preOrder(root.right, path, res); // 回退 path.removeLast(); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\n初始化二叉树"); printTree(root); // 前序遍历 List path = []; List> res = []; preOrder(root, path, res); print("\n输出所有根节点到节点 7 的路径"); for (List vals in res) { print(List.generate(vals.length, (i) => vals[i].val)); } } ================================================ FILE: codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart ================================================ /** * File: preorder_traversal_iii_template.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 判断当前状态是否为解 */ bool isSolution(List state) { return state.isNotEmpty && state.last.val == 7; } /* 记录解 */ void recordSolution(List state, List> res) { res.add(List.from(state)); } /* 判断在当前状态下,该选择是否合法 */ bool isValid(List state, TreeNode? choice) { return choice != null && choice.val != 3; } /* 更新状态 */ void makeChoice(List state, TreeNode? choice) { state.add(choice!); } /* 恢复状态 */ void undoChoice(List state, TreeNode? choice) { state.removeLast(); } /* 回溯算法:例题三 */ void backtrack( List state, List choices, List> res, ) { // 检查是否为解 if (isSolution(state)) { // 记录解 recordSolution(state, res); } // 遍历所有选择 for (TreeNode? choice in choices) { // 剪枝:检查选择是否合法 if (isValid(state, choice)) { // 尝试:做出选择,更新状态 makeChoice(state, choice); // 进行下一轮选择 backtrack(state, [choice!.left, choice.right], res); // 回退:撤销选择,恢复到之前的状态 undoChoice(state, choice); } } } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\n初始化二叉树"); printTree(root); // 回溯算法 List> res = []; backtrack([], [root!], res); print("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点"); for (List path in res) { print(List.from(path.map((e) => e.val))); } } ================================================ FILE: codes/dart/chapter_backtracking/subset_sum_i.dart ================================================ /** * File: subset_sum_i.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯算法:子集和 I */ void backtrack( List state, int target, List choices, int start, List> res, ) { // 子集和等于 target 时,记录解 if (target == 0) { res.add(List.from(state)); return; } // 遍历所有选择 // 剪枝二:从 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.removeLast(); } } /* 求解子集和 I */ List> subsetSumI(List nums, int target) { List state = []; // 状态(子集) nums.sort(); // 对 nums 进行排序 int start = 0; // 遍历起始点 List> res = []; // 结果列表(子集列表) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ void main() { List nums = [3, 4, 5]; int target = 9; List> res = subsetSumI(nums, target); print("输入数组 nums = $nums, target = $target"); print("所有和等于 $target 的子集 res = $res"); } ================================================ FILE: codes/dart/chapter_backtracking/subset_sum_i_naive.dart ================================================ /** * File: subset_sum_i_naive.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯算法:子集和 I */ void backtrack( List state, int target, int total, List choices, List> res, ) { // 子集和等于 target 时,记录解 if (total == target) { res.add(List.from(state)); return; } // 遍历所有选择 for (int i = 0; i < choices.length; i++) { // 剪枝:若子集和超过 target ,则跳过该选择 if (total + choices[i] > target) { continue; } // 尝试:做出选择,更新元素和 total state.add(choices[i]); // 进行下一轮选择 backtrack(state, target, total + choices[i], choices, res); // 回退:撤销选择,恢复到之前的状态 state.removeLast(); } } /* 求解子集和 I(包含重复子集) */ List> subsetSumINaive(List nums, int target) { List state = []; // 状态(子集) int total = 0; // 元素和 List> res = []; // 结果列表(子集列表) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ void main() { List nums = [3, 4, 5]; int target = 9; List> res = subsetSumINaive(nums, target); print("输入数组 nums = $nums, target = $target"); print("所有和等于 $target 的子集 res = $res"); print("请注意,该方法输出的结果包含重复集合"); } ================================================ FILE: codes/dart/chapter_backtracking/subset_sum_ii.dart ================================================ /** * File: subset_sum_ii.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯算法:子集和 II */ void backtrack( List state, int target, List choices, int start, List> res, ) { // 子集和等于 target 时,记录解 if (target == 0) { res.add(List.from(state)); return; } // 遍历所有选择 // 剪枝二:从 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.removeLast(); } } /* 求解子集和 II */ List> subsetSumII(List nums, int target) { List state = []; // 状态(子集) nums.sort(); // 对 nums 进行排序 int start = 0; // 遍历起始点 List> res = []; // 结果列表(子集列表) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ void main() { List nums = [4, 4, 5]; int target = 9; List> res = subsetSumII(nums, target); print("输入数组 nums = $nums, target = $target"); print("所有和等于 $target 的子集 res = $res"); } ================================================ FILE: codes/dart/chapter_computational_complexity/iteration.dart ================================================ /** * File: iteration.dart * Created Time: 2023-08-27 * Author: liuyuxin (gvenusleo@gmail.com) */ /* for 循环 */ int forLoop(int n) { int res = 0; // 循环求和 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } /* while 循环 */ int whileLoop(int n) { int res = 0; int i = 1; // 初始化条件变量 // 循环求和 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // 更新条件变量 } return res; } /* while 循环(两次更新) */ int whileLoopII(int n) { int res = 0; int i = 1; // 初始化条件变量 // 循环求和 1, 4, 10, ... while (i <= n) { res += i; // 更新条件变量 i++; i *= 2; } return res; } /* 双层 for 循环 */ String nestedForLoop(int n) { String res = ""; // 循环 i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // 循环 j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { res += "($i, $j), "; } } return res; } /* Driver Code */ void main() { int n = 5; int res; res = forLoop(n); print("\nfor 循环的求和结果 res = $res"); res = whileLoop(n); print("\nwhile 循环的求和结果 res = $res"); res = whileLoopII(n); print("\nwhile 循环(两次更新)的求和结果 res = $res"); String resStr = nestedForLoop(n); print("\n双层 for 循环的结果 $resStr"); } ================================================ FILE: codes/dart/chapter_computational_complexity/recursion.dart ================================================ /** * File: recursion.dart * Created Time: 2023-08-27 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 递归 */ int recur(int n) { // 终止条件 if (n == 1) return 1; // 递:递归调用 int res = recur(n - 1); // 归:返回结果 return n + res; } /* 使用迭代模拟递归 */ int forLoopRecur(int n) { // 使用一个显式的栈来模拟系统调用栈 List stack = []; int res = 0; // 递:递归调用 for (int i = n; i > 0; i--) { // 通过“入栈操作”模拟“递” stack.add(i); } // 归:返回结果 while (!stack.isEmpty) { // 通过“出栈操作”模拟“归” res += stack.removeLast(); } // res = 1+2+3+...+n return res; } /* 尾递归 */ int tailRecur(int n, int res) { // 终止条件 if (n == 0) return res; // 尾递归调用 return tailRecur(n - 1, res + n); } /* 斐波那契数列:递归 */ int fib(int n) { // 终止条件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // 递归调用 f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // 返回结果 f(n) return res; } /* Driver Code */ void main() { int n = 5; int res; res = recur(n); print("\n递归函数的求和结果 res = $res"); res = tailRecur(n, 0); print("\n尾递归函数的求和结果 res = $res"); res = forLoopRecur(n); print("\n使用迭代模拟递归求和结果 res = $res"); res = fib(n); print("\n斐波那契数列的第 $n 项为 $res"); } ================================================ FILE: codes/dart/chapter_computational_complexity/space_complexity.dart ================================================ /** * File: space_complexity.dart * Created Time: 2023-2-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable import 'dart:collection'; import '../utils/list_node.dart'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 函数 */ int function() { // 执行某些操作 return 0; } /* 常数阶 */ void constant(int n) { // 常量、变量、对象占用 O(1) 空间 final int a = 0; int b = 0; List nums = List.filled(10000, 0); ListNode node = ListNode(0); // 循环中的变量占用 O(1) 空间 for (var i = 0; i < n; i++) { int c = 0; } // 循环中的函数占用 O(1) 空间 for (var i = 0; i < n; i++) { function(); } } /* 线性阶 */ void linear(int n) { // 长度为 n 的数组占用 O(n) 空间 List nums = List.filled(n, 0); // 长度为 n 的列表占用 O(n) 空间 List nodes = []; for (var i = 0; i < n; i++) { nodes.add(ListNode(i)); } // 长度为 n 的哈希表占用 O(n) 空间 Map map = HashMap(); for (var i = 0; i < n; i++) { map.putIfAbsent(i, () => i.toString()); } } /* 线性阶(递归实现) */ void linearRecur(int n) { print('递归 n = $n'); if (n == 1) return; linearRecur(n - 1); } /* 平方阶 */ void quadratic(int n) { // 矩阵占用 O(n^2) 空间 List> numMatrix = List.generate(n, (_) => List.filled(n, 0)); // 二维列表占用 O(n^2) 空间 List> numList = []; for (var i = 0; i < n; i++) { List tmp = []; for (int j = 0; j < n; j++) { tmp.add(0); } numList.add(tmp); } } /* 平方阶(递归实现) */ int quadraticRecur(int n) { if (n <= 0) return 0; List nums = List.filled(n, 0); print('递归 n = $n 中的 nums 长度 = ${nums.length}'); return quadraticRecur(n - 1); } /* 指数阶(建立满二叉树) */ TreeNode? buildTree(int n) { if (n == 0) return null; TreeNode root = TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ void main() { int n = 5; // 常数阶 constant(n); // 线性阶 linear(n); linearRecur(n); // 平方阶 quadratic(n); quadraticRecur(n); // 指数阶 TreeNode? root = buildTree(n); printTree(root); } ================================================ FILE: codes/dart/chapter_computational_complexity/time_complexity.dart ================================================ /** * File: time_complexity.dart * Created Time: 2023-02-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable /* 常数阶 */ int constant(int n) { int count = 0; int size = 100000; for (var i = 0; i < size; i++) { count++; } return count; } /* 线性阶 */ int linear(int n) { int count = 0; for (var i = 0; i < n; i++) { count++; } return count; } /* 线性阶(遍历数组) */ int arrayTraversal(List nums) { int count = 0; // 循环次数与数组长度成正比 for (var _num in nums) { count++; } return count; } /* 平方阶 */ int quadratic(int n) { int count = 0; // 循环次数与数据大小 n 成平方关系 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* 平方阶(冒泡排序) */ int bubbleSort(List nums) { int count = 0; // 计数器 // 外循环:未排序区间为 [0, i] for (var i = nums.length - 1; i > 0; i--) { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (var j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 元素交换包含 3 个单元操作 } } } return count; } /* 指数阶(循环实现) */ int exponential(int n) { int count = 0, base = 1; // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for (var i = 0; i < n; i++) { for (var j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指数阶(递归实现) */ int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 对数阶(循环实现) */ int logarithmic(int n) { int count = 0; while (n > 1) { n = n ~/ 2; count++; } return count; } /* 对数阶(递归实现) */ int logRecur(int n) { if (n <= 1) return 0; return logRecur(n ~/ 2) + 1; } /* 线性对数阶 */ int linearLogRecur(int n) { if (n <= 1) return 1; int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2); for (var i = 0; i < n; i++) { count++; } return count; } /* 阶乘阶(递归实现) */ int factorialRecur(int n) { if (n == 0) return 1; int count = 0; // 从 1 个分裂出 n 个 for (var i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ void main() { // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 int n = 8; print('输入数据大小 n = $n'); int count = constant(n); print('常数阶的操作数量 = $count'); count = linear(n); print('线性阶的操作数量 = $count'); count = arrayTraversal(List.filled(n, 0)); print('线性阶(遍历数组)的操作数量 = $count'); count = quadratic(n); print('平方阶的操作数量 = $count'); final nums = List.filled(n, 0); for (int i = 0; i < n; i++) { nums[i] = n - i; // [n,n-1,...,2,1] } count = bubbleSort(nums); print('平方阶(冒泡排序)的操作数量 = $count'); count = exponential(n); print('指数阶(循环实现)的操作数量 = $count'); count = expRecur(n); print('指数阶(递归实现)的操作数量 = $count'); count = logarithmic(n); print('对数阶(循环实现)的操作数量 = $count'); count = logRecur(n); print('对数阶(递归实现)的操作数量 = $count'); count = linearLogRecur(n); print('线性对数阶(递归实现)的操作数量 = $count'); count = factorialRecur(n); print('阶乘阶(递归实现)的操作数量 = $count'); } ================================================ FILE: codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart ================================================ /** * File: worst_best_time_complexity.dart * Created Time: 2023-02-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ List randomNumbers(int n) { final nums = List.filled(n, 0); // 生成数组 nums = { 1, 2, 3, ..., n } for (var i = 0; i < n; i++) { nums[i] = i + 1; } // 随机打乱数组元素 nums.shuffle(); return nums; } /* 查找数组 nums 中数字 1 所在索引 */ int findOne(List nums) { for (var i = 0; i < nums.length; i++) { // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if (nums[i] == 1) return i; } return -1; } /* Driver Code */ void main() { for (var i = 0; i < 10; i++) { int n = 100; final nums = randomNumbers(n); int index = findOne(nums); print('\n数组 [ 1, 2, ..., n ] 被打乱后 = $nums'); print('数字 1 的索引为 + $index'); } } ================================================ FILE: codes/dart/chapter_divide_and_conquer/binary_search_recur.dart ================================================ /** * File: binary_search_recur.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 二分查找:问题 f(i, j) */ int dfs(List nums, int target, int i, int j) { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1; } // 计算中点索引 m int m = (i + j) ~/ 2; if (nums[m] < target) { // 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ int binarySearch(List nums, int target) { int n = nums.length; // 求解问题 f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ void main() { int target = 6; List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分查找(双闭区间) int index = binarySearch(nums, target); print("目标元素 6 的索引 = $index"); } ================================================ FILE: codes/dart/chapter_divide_and_conquer/build_tree.dart ================================================ /** * File: build_tree.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 构建二叉树:分治 */ TreeNode? dfs( List preorder, Map inorderMap, int i, int l, int r, ) { // 子树区间为空时终止 if (r - l < 0) { return null; } // 初始化根节点 TreeNode? root = TreeNode(preorder[i]); // 查询 m ,从而划分左右子树 int m = inorderMap[preorder[i]]!; // 子问题:构建左子树 root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // 子问题:构建右子树 root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // 返回根节点 return root; } /* 构建二叉树 */ TreeNode? buildTree(List preorder, List inorder) { // 初始化哈希表,存储 inorder 元素到索引的映射 Map inorderMap = {}; for (int i = 0; i < inorder.length; i++) { inorderMap[inorder[i]] = i; } TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ void main() { List preorder = [3, 9, 2, 1, 7]; List inorder = [9, 3, 1, 2, 7]; print("前序遍历 = $preorder"); print("中序遍历 = $inorder"); TreeNode? root = buildTree(preorder, inorder); print("构建的二叉树为:"); printTree(root!); } ================================================ FILE: codes/dart/chapter_divide_and_conquer/hanota.dart ================================================ /** * File: hanota.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 移动一个圆盘 */ void move(List src, List tar) { // 从 src 顶部拿出一个圆盘 int pan = src.removeLast(); // 将圆盘放入 tar 顶部 tar.add(pan); } /* 求解汉诺塔问题 f(i) */ void dfs(int i, List src, List buf, List tar) { // 若 src 只剩下一个圆盘,则直接将其移到 tar if (i == 1) { move(src, tar); return; } // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf dfs(i - 1, src, tar, buf); // 子问题 f(1) :将 src 剩余一个圆盘移到 tar move(src, tar); // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar dfs(i - 1, buf, src, tar); } /* 求解汉诺塔问题 */ void solveHanota(List A, List B, List C) { int n = A.length; // 将 A 顶部 n 个圆盘借助 B 移到 C dfs(n, A, B, C); } /* Driver Code */ void main() { // 列表尾部是柱子顶部 List A = [5, 4, 3, 2, 1]; List B = []; List C = []; print("初始状态下:"); print("A = $A"); print("B = $B"); print("C = $C"); solveHanota(A, B, C); print("圆盘移动完成后:"); print("A = $A"); print("B = $B"); print("C = $C"); } ================================================ FILE: codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart ================================================ /** * File: climbing_stairs_backtrack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯 */ void backtrack(List choices, int state, int n, List res) { // 当爬到第 n 阶时,方案数量加 1 if (state == n) { res[0]++; } // 遍历所有选择 for (int choice in choices) { // 剪枝:不允许越过第 n 阶 if (state + choice > n) continue; // 尝试:做出选择,更新状态 backtrack(choices, state + choice, n, res); // 回退 } } /* 爬楼梯:回溯 */ int climbingStairsBacktrack(int n) { List choices = [1, 2]; // 可选择向上爬 1 阶或 2 阶 int state = 0; // 从第 0 阶开始爬 List res = []; res.add(0); // 使用 res[0] 记录方案数量 backtrack(choices, state, n, res); return res[0]; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsBacktrack(n); print("爬 $n 阶楼梯共有 $res 种方案"); } ================================================ FILE: codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart ================================================ /** * File: climbing_stairs_constraint_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 带约束爬楼梯:动态规划 */ int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // 初始化 dp 表,用于存储子问题的解 List> dp = List.generate(n + 1, (index) => List.filled(3, 0)); // 初始状态:预设最小子问题的解 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 状态转移:从较小子问题逐步求解较大子问题 for (int i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsConstraintDP(n); print("爬 $n 阶楼梯共有 $res 种方案"); } ================================================ FILE: codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart ================================================ /** * File: climbing_stairs_dfs.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 搜索 */ int dfs(int i) { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1) + dfs(i - 2); return count; } /* 爬楼梯:搜索 */ int climbingStairsDFS(int n) { return dfs(n); } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDFS(n); print("爬 $n 阶楼梯共有 $res 种方案"); } ================================================ FILE: codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart ================================================ /** * File: climbing_stairs_dfs_mem.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 记忆化搜索 */ int dfs(int i, List mem) { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i; // 若存在记录 dp[i] ,则直接返回之 if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1, mem) + dfs(i - 2, mem); // 记录 dp[i] mem[i] = count; return count; } /* 爬楼梯:记忆化搜索 */ int climbingStairsDFSMem(int n) { // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 List mem = List.filled(n + 1, -1); return dfs(n, mem); } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDFSMem(n); print("爬 $n 阶楼梯共有 $res 种方案"); } ================================================ FILE: codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart ================================================ /** * File: climbing_stairs_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 爬楼梯:动态规划 */ int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; // 初始化 dp 表,用于存储子问题的解 List dp = List.filled(n + 1, 0); // 初始状态:预设最小子问题的解 dp[1] = 1; dp[2] = 2; // 状态转移:从较小子问题逐步求解较大子问题 for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* 爬楼梯:空间优化后的动态规划 */ int climbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDP(n); print("爬 $n 阶楼梯共有 $res 种方案"); res = climbingStairsDPComp(n); print("爬 $n 阶楼梯共有 $res 种方案"); } ================================================ FILE: codes/dart/chapter_dynamic_programming/coin_change.dart ================================================ /** * File: coin_change.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 零钱兑换:动态规划 */ int coinChangeDP(List coins, int amt) { int n = coins.length; int MAX = amt + 1; // 初始化 dp 表 List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); // 状态转移:首行首列 for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状态转移:其余行和列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] != MAX ? dp[n][amt] : -1; } /* 零钱兑换:空间优化后的动态规划 */ int coinChangeDPComp(List coins, int amt) { int n = coins.length; int MAX = amt + 1; // 初始化 dp 表 List dp = List.filled(amt + 1, MAX); dp[0] = 0; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } /* Driver Code */ void main() { List coins = [1, 2, 5]; int amt = 4; // 动态规划 int res = coinChangeDP(coins, amt); print("凑到目标金额所需的最少硬币数量为 $res"); // 空间优化后的动态规划 res = coinChangeDPComp(coins, amt); print("凑到目标金额所需的最少硬币数量为 $res"); } ================================================ FILE: codes/dart/chapter_dynamic_programming/coin_change_ii.dart ================================================ /** * File: coin_change_ii.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 零钱兑换 II:动态规划 */ int coinChangeIIDP(List coins, int amt) { int n = coins.length; // 初始化 dp 表 List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); // 初始化首列 for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* 零钱兑换 II:空间优化后的动态规划 */ int coinChangeIIDPComp(List coins, int amt) { int n = coins.length; // 初始化 dp 表 List dp = List.filled(amt + 1, 0); dp[0] = 1; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ void main() { List coins = [1, 2, 5]; int amt = 5; // 动态规划 int res = coinChangeIIDP(coins, amt); print("凑出目标金额的硬币组合数量为 $res"); // 空间优化后的动态规划 res = coinChangeIIDPComp(coins, amt); print("凑出目标金额的硬币组合数量为 $res"); } ================================================ FILE: codes/dart/chapter_dynamic_programming/edit_distance.dart ================================================ /** * File: edit_distance.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 编辑距离:暴力搜索 */ int editDistanceDFS(String s, String t, int i, int j) { // 若 s 和 t 都为空,则返回 0 if (i == 0 && j == 0) return 0; // 若 s 为空,则返回 t 长度 if (i == 0) return j; // 若 t 为空,则返回 s 长度 if (j == 0) return i; // 若两字符相等,则直接跳过此两字符 if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 int insert = editDistanceDFS(s, t, i, j - 1); int delete = editDistanceDFS(s, t, i - 1, j); int replace = editDistanceDFS(s, t, i - 1, j - 1); // 返回最少编辑步数 return min(min(insert, delete), replace) + 1; } /* 编辑距离:记忆化搜索 */ int editDistanceDFSMem(String s, String t, List> mem, int i, int j) { // 若 s 和 t 都为空,则返回 0 if (i == 0 && j == 0) return 0; // 若 s 为空,则返回 t 长度 if (i == 0) return j; // 若 t 为空,则返回 s 长度 if (j == 0) return i; // 若已有记录,则直接返回之 if (mem[i][j] != -1) return mem[i][j]; // 若两字符相等,则直接跳过此两字符 if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 int insert = editDistanceDFSMem(s, t, mem, i, j - 1); int delete = editDistanceDFSMem(s, t, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 记录并返回最少编辑步数 mem[i][j] = min(min(insert, delete), replace) + 1; return mem[i][j]; } /* 编辑距离:动态规划 */ int editDistanceDP(String s, String t) { int n = s.length, m = t.length; List> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0)); // 状态转移:首行首列 for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // 状态转移:其余行和列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // 若两字符相等,则直接跳过此两字符 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* 编辑距离:空间优化后的动态规划 */ int editDistanceDPComp(String s, String t) { int n = s.length, m = t.length; List dp = List.filled(m + 1, 0); // 状态转移:首行 for (int j = 1; j <= m; j++) { dp[j] = j; } // 状态转移:其余行 for (int i = 1; i <= n; i++) { // 状态转移:首列 int leftup = dp[0]; // 暂存 dp[i-1, j-1] dp[0] = i; // 状态转移:其余列 for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 若两字符相等,则直接跳过此两字符 dp[j] = leftup; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 更新为下一轮的 dp[i-1, j-1] } } return dp[m]; } /* Driver Code */ void main() { String s = "bag"; String t = "pack"; int n = s.length, m = t.length; // 暴力搜索 int res = editDistanceDFS(s, t, n, m); print("将 " + s + " 更改为 " + t + " 最少需要编辑 $res 步"); // 记忆化搜索 List> mem = List.generate(n + 1, (_) => List.filled(m + 1, -1)); res = editDistanceDFSMem(s, t, mem, n, m); print("将 " + s + " 更改为 " + t + " 最少需要编辑 $res 步"); // 动态规划 res = editDistanceDP(s, t); print("将 " + s + " 更改为 " + t + " 最少需要编辑 $res 步"); // 空间优化后的动态规划 res = editDistanceDPComp(s, t); print("将 " + s + " 更改为 " + t + " 最少需要编辑 $res 步"); } ================================================ FILE: codes/dart/chapter_dynamic_programming/knapsack.dart ================================================ /** * File: knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 0-1 背包:暴力搜索 */ int knapsackDFS(List wgt, List val, int i, int c) { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i == 0 || c == 0) { return 0; } // 若超过背包容量,则只能选择不放入背包 if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // 返回两种方案中价值更大的那一个 return max(no, yes); } /* 0-1 背包:记忆化搜索 */ int knapsackDFSMem( List wgt, List val, List> mem, int i, int c, ) { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i == 0 || c == 0) { return 0; } // 若已有记录,则直接返回 if (mem[i][c] != -1) { return mem[i][c]; } // 若超过背包容量,则只能选择不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 int no = knapsackDFSMem(wgt, val, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 记录并返回两种方案中价值更大的那一个 mem[i][c] = max(no, yes); return mem[i][c]; } /* 0-1 背包:动态规划 */ int knapsackDP(List wgt, List val, int cap) { int n = wgt.length; // 初始化 dp 表 List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } /* 0-1 背包:空间优化后的动态规划 */ int knapsackDPComp(List wgt, List val, int cap) { int n = wgt.length; // 初始化 dp 表 List dp = List.filled(cap + 1, 0); // 状态转移 for (int i = 1; i <= n; i++) { // 倒序遍历 for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // 不选和选物品 i 这两种方案的较大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ void main() { List wgt = [10, 20, 30, 40, 50]; List val = [50, 120, 150, 210, 240]; int cap = 50; int n = wgt.length; // 暴力搜索 int res = knapsackDFS(wgt, val, n, cap); print("不超过背包容量的最大物品价值为 $res"); // 记忆化搜索 List> mem = List.generate(n + 1, (index) => List.filled(cap + 1, -1)); res = knapsackDFSMem(wgt, val, mem, n, cap); print("不超过背包容量的最大物品价值为 $res"); // 动态规划 res = knapsackDP(wgt, val, cap); print("不超过背包容量的最大物品价值为 $res"); // 空间优化后的动态规划 res = knapsackDPComp(wgt, val, cap); print("不超过背包容量的最大物品价值为 $res"); } ================================================ FILE: codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart ================================================ /** * File: min_cost_climbing_stairs_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 爬楼梯最小代价:动态规划 */ int minCostClimbingStairsDP(List cost) { int n = cost.length - 1; if (n == 1 || n == 2) return cost[n]; // 初始化 dp 表,用于存储子问题的解 List dp = List.filled(n + 1, 0); // 初始状态:预设最小子问题的解 dp[1] = cost[1]; dp[2] = cost[2]; // 状态转移:从较小子问题逐步求解较大子问题 for (int i = 3; i <= n; i++) { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* 爬楼梯最小代价:空间优化后的动态规划 */ int minCostClimbingStairsDPComp(List cost) { int n = cost.length - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ void main() { List cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; print("输入楼梯的代价列表为 $cost"); int res = minCostClimbingStairsDP(cost); print("爬完楼梯的最低代价为 $res"); res = minCostClimbingStairsDPComp(cost); print("爬完楼梯的最低代价为 $res"); } ================================================ FILE: codes/dart/chapter_dynamic_programming/min_path_sum.dart ================================================ /** * File: min_path_sum.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 最小路径和:暴力搜索 */ int minPathSumDFS(List> grid, int i, int j) { // 若为左上角单元格,则终止搜索 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 || j < 0) { // 在 Dart 中,int 类型是固定范围的整数,不存在表示“无穷大”的值 return BigInt.from(2).pow(31).toInt(); } // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); // 返回从左上角到 (i, j) 的最小路径代价 return min(left, up) + grid[i][j]; } /* 最小路径和:记忆化搜索 */ int minPathSumDFSMem(List> grid, List> mem, int i, int j) { // 若为左上角单元格,则终止搜索 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 || j < 0) { // 在 Dart 中,int 类型是固定范围的整数,不存在表示“无穷大”的值 return BigInt.from(2).pow(31).toInt(); } // 若已有记录,则直接返回 if (mem[i][j] != -1) { return mem[i][j]; } // 左边和上边单元格的最小路径代价 int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // 记录并返回左上角到 (i, j) 的最小路径代价 mem[i][j] = min(left, up) + grid[i][j]; return mem[i][j]; } /* 最小路径和:动态规划 */ int minPathSumDP(List> grid) { int n = grid.length, m = grid[0].length; // 初始化 dp 表 List> dp = List.generate(n, (i) => List.filled(m, 0)); dp[0][0] = grid[0][0]; // 状态转移:首行 for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 状态转移:首列 for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 状态转移:其余行和列 for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* 最小路径和:空间优化后的动态规划 */ int minPathSumDPComp(List> grid) { int n = grid.length, m = grid[0].length; // 初始化 dp 表 List dp = List.filled(m, 0); dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 状态转移:其余行 for (int i = 1; i < n; i++) { // 状态转移:首列 dp[0] = dp[0] + grid[i][0]; // 状态转移:其余列 for (int j = 1; j < m; j++) { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ void main() { List> grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; int n = grid.length, m = grid[0].length; // 暴力搜索 int res = minPathSumDFS(grid, n - 1, m - 1); print("从左上角到右下角的最小路径和为 $res"); // 记忆化搜索 List> mem = List.generate(n, (i) => List.filled(m, -1)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); print("从左上角到右下角的最小路径和为 $res"); // 动态规划 res = minPathSumDP(grid); print("从左上角到右下角的最小路径和为 $res"); // 空间优化后的动态规划 res = minPathSumDPComp(grid); print("从左上角到右下角的最小路径和为 $res"); } ================================================ FILE: codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart ================================================ /** * File: unbounded_knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 完全背包:动态规划 */ int unboundedKnapsackDP(List wgt, List val, int cap) { int n = wgt.length; // 初始化 dp 表 List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } /* 完全背包:空间优化后的动态规划 */ int unboundedKnapsackDPComp(List wgt, List val, int cap) { int n = wgt.length; // 初始化 dp 表 List dp = List.filled(cap + 1, 0); // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ void main() { List wgt = [1, 2, 3]; List val = [5, 11, 15]; int cap = 4; // 动态规划 int res = unboundedKnapsackDP(wgt, val, cap); print("不超过背包容量的最大物品价值为 $res"); // 空间优化后的动态规划 int resComp = unboundedKnapsackDPComp(wgt, val, cap); print("不超过背包容量的最大物品价值为 $resComp"); } ================================================ FILE: codes/dart/chapter_graph/graph_adjacency_list.dart ================================================ /** * File: graph_adjacency_list.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/vertex.dart'; /* 基于邻接表实现的无向图类 */ class GraphAdjList { // 邻接表,key:顶点,value:该顶点的所有邻接顶点 Map> adjList = {}; /* 构造方法 */ GraphAdjList(List> edges) { for (List edge in edges) { addVertex(edge[0]); addVertex(edge[1]); addEdge(edge[0], edge[1]); } } /* 获取顶点数量 */ int size() { return adjList.length; } /* 添加边 */ void addEdge(Vertex vet1, Vertex vet2) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) { throw ArgumentError; } // 添加边 vet1 - vet2 adjList[vet1]!.add(vet2); adjList[vet2]!.add(vet1); } /* 删除边 */ void removeEdge(Vertex vet1, Vertex vet2) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) { throw ArgumentError; } // 删除边 vet1 - vet2 adjList[vet1]!.remove(vet2); adjList[vet2]!.remove(vet1); } /* 添加顶点 */ void addVertex(Vertex vet) { if (adjList.containsKey(vet)) return; // 在邻接表中添加一个新链表 adjList[vet] = []; } /* 删除顶点 */ void removeVertex(Vertex vet) { if (!adjList.containsKey(vet)) { throw ArgumentError; } // 在邻接表中删除顶点 vet 对应的链表 adjList.remove(vet); // 遍历其他顶点的链表,删除所有包含 vet 的边 adjList.forEach((key, value) { value.remove(vet); }); } /* 打印邻接表 */ void printAdjList() { print("邻接表 ="); adjList.forEach((key, value) { List tmp = []; for (Vertex vertex in value) { tmp.add(vertex.val); } print("${key.val}: $tmp,"); }); } } /* Driver Code */ void main() { /* 初始化无向图 */ List v = Vertex.valsToVets([1, 3, 2, 5, 4]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ]; GraphAdjList graph = GraphAdjList(edges); print("\n初始化后,图为"); graph.printAdjList(); /* 添加边 */ // 顶点 1, 2 即 v[0], v[2] graph.addEdge(v[0], v[2]); print("\n添加边 1-2 后,图为"); graph.printAdjList(); /* 删除边 */ // 顶点 1, 3 即 v[0], v[1] graph.removeEdge(v[0], v[1]); print("\n删除边 1-3 后,图为"); graph.printAdjList(); /* 添加顶点 */ Vertex v5 = Vertex(6); graph.addVertex(v5); print("\n添加顶点 6 后,图为"); graph.printAdjList(); /* 删除顶点 */ // 顶点 3 即 v[1] graph.removeVertex(v[1]); print("\n删除顶点 3 后,图为"); graph.printAdjList(); } ================================================ FILE: codes/dart/chapter_graph/graph_adjacency_matrix.dart ================================================ /** * File: graph_adjacency_matrix.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* 基于邻接矩阵实现的无向图类 */ class GraphAdjMat { List vertices = []; // 顶点元素,元素代表“顶点值”,索引代表“顶点索引” List> adjMat = []; //邻接矩阵,行列索引对应“顶点索引” /* 构造方法 */ GraphAdjMat(List vertices, List> edges) { this.vertices = []; this.adjMat = []; // 添加顶点 for (int val in vertices) { addVertex(val); } // 添加边 // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 for (List e in edges) { addEdge(e[0], e[1]); } } /* 获取顶点数量 */ int size() { return vertices.length; } /* 添加顶点 */ void addVertex(int val) { int n = size(); // 向顶点列表中添加新顶点的值 vertices.add(val); // 在邻接矩阵中添加一行 List newRow = List.filled(n, 0, growable: true); adjMat.add(newRow); // 在邻接矩阵中添加一列 for (List row in adjMat) { row.add(0); } } /* 删除顶点 */ void removeVertex(int index) { if (index >= size()) { throw IndexError; } // 在顶点列表中移除索引 index 的顶点 vertices.removeAt(index); // 在邻接矩阵中删除索引 index 的行 adjMat.removeAt(index); // 在邻接矩阵中删除索引 index 的列 for (List row in adjMat) { row.removeAt(index); } } /* 添加边 */ // 参数 i, j 对应 vertices 元素索引 void addEdge(int i, int j) { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw IndexError; } // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) adjMat[i][j] = 1; adjMat[j][i] = 1; } /* 删除边 */ // 参数 i, j 对应 vertices 元素索引 void removeEdge(int i, int j) { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw IndexError; } adjMat[i][j] = 0; adjMat[j][i] = 0; } /* 打印邻接矩阵 */ void printAdjMat() { print("顶点列表 = $vertices"); print("邻接矩阵 = "); printMatrix(adjMat); } } /* Driver Code */ void main() { /* 初始化无向图 */ // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 List vertices = [1, 3, 2, 5, 4]; List> edges = [ [0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4], ]; GraphAdjMat graph = GraphAdjMat(vertices, edges); print("\n初始化后,图为"); graph.printAdjMat(); /* 添加边 */ // 顶点 1, 2 的索引分别为 0, 2 graph.addEdge(0, 2); print("\n添加边 1-2 后,图为"); graph.printAdjMat(); /* 删除边 */ // 顶点 1, 3 的索引分别为 0, 1 graph.removeEdge(0, 1); print("\n删除边 1-3 后,图为"); graph.printAdjMat(); /* 添加顶点 */ graph.addVertex(6); print("\n添加顶点 6 后,图为"); graph.printAdjMat(); /* 删除顶点 */ // 顶点 3 的索引为 1 graph.removeVertex(1); print("\n删除顶点 3 后,图为"); graph.printAdjMat(); } ================================================ FILE: codes/dart/chapter_graph/graph_bfs.dart ================================================ /** * File: graph_bfs.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; import '../utils/vertex.dart'; import 'graph_adjacency_list.dart'; /* 广度优先遍历 */ List graphBFS(GraphAdjList graph, Vertex startVet) { // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 // 顶点遍历序列 List res = []; // 哈希集合,用于记录已被访问过的顶点 Set visited = {}; visited.add(startVet); // 队列用于实现 BFS Queue que = Queue(); que.add(startVet); // 以顶点 vet 为起点,循环直至访问完所有顶点 while (que.isNotEmpty) { Vertex vet = que.removeFirst(); // 队首顶点出队 res.add(vet); // 记录访问顶点 // 遍历该顶点的所有邻接顶点 for (Vertex adjVet in graph.adjList[vet]!) { if (visited.contains(adjVet)) { continue; // 跳过已被访问的顶点 } que.add(adjVet); // 只入队未访问的顶点 visited.add(adjVet); // 标记该顶点已被访问 } } // 返回顶点遍历序列 return res; } /* Dirver Code */ void main() { /* 初始化无向图 */ List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; GraphAdjList graph = GraphAdjList(edges); print("\n初始化后,图为"); graph.printAdjList(); /* 广度优先遍历 */ List res = graphBFS(graph, v[0]); print("\n广度优先遍历(BFS)顶点序列为"); print(Vertex.vetsToVals(res)); } ================================================ FILE: codes/dart/chapter_graph/graph_dfs.dart ================================================ /** * File: graph_dfs.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/vertex.dart'; import 'graph_adjacency_list.dart'; /* 深度优先遍历辅助函数 */ void dfs( GraphAdjList graph, Set visited, List res, Vertex vet, ) { res.add(vet); // 记录访问顶点 visited.add(vet); // 标记该顶点已被访问 // 遍历该顶点的所有邻接顶点 for (Vertex adjVet in graph.adjList[vet]!) { if (visited.contains(adjVet)) { continue; // 跳过已被访问的顶点 } // 递归访问邻接顶点 dfs(graph, visited, res, adjVet); } } /* 深度优先遍历 */ List graphDFS(GraphAdjList graph, Vertex startVet) { // 顶点遍历序列 List res = []; // 哈希集合,用于记录已被访问过的顶点 Set visited = {}; dfs(graph, visited, res, startVet); return res; } /* Driver Code */ void main() { /* 初始化无向图 */ List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; GraphAdjList graph = GraphAdjList(edges); print("\n初始化后,图为"); graph.printAdjList(); /* 深度优先遍历 */ List res = graphDFS(graph, v[0]); print("\n深度优先遍历(DFS)顶点序列为"); print(Vertex.vetsToVals(res)); } ================================================ FILE: codes/dart/chapter_greedy/coin_change_greedy.dart ================================================ /** * File: coin_change_greedy.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 零钱兑换:贪心 */ int coinChangeGreedy(List coins, int amt) { // 假设 coins 列表有序 int i = coins.length - 1; int count = 0; // 循环进行贪心选择,直到无剩余金额 while (amt > 0) { // 找到小于且最接近剩余金额的硬币 while (i > 0 && coins[i] > amt) { i--; } // 选择 coins[i] amt -= coins[i]; count++; } // 若未找到可行方案,则返回 -1 return amt == 0 ? count : -1; } /* Driver Code */ void main() { // 贪心:能够保证找到全局最优解 List coins = [1, 5, 10, 20, 50, 100]; int amt = 186; int res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("凑到 $amt 所需的最少硬币数量为 $res"); // 贪心:无法保证找到全局最优解 coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("凑到 $amt 所需的最少硬币数量为 $res"); print("实际上需要的最少数量为 3 ,即 20 + 20 + 20"); // 贪心:无法保证找到全局最优解 coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("凑到 $amt 所需的最少硬币数量为 $res"); print("实际上需要的最少数量为 2 ,即 49 + 49"); } ================================================ FILE: codes/dart/chapter_greedy/fractional_knapsack.dart ================================================ /** * File: fractional_knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 物品 */ class Item { int w; // 物品重量 int v; // 物品价值 Item(this.w, this.v); } /* 分数背包:贪心 */ double fractionalKnapsack(List wgt, List val, int cap) { // 创建物品列表,包含两个属性:重量、价值 List items = List.generate(wgt.length, (i) => Item(wgt[i], val[i])); // 按照单位价值 item.v / item.w 从高到低进行排序 items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w)); // 循环贪心选择 double res = 0; for (Item item in items) { if (item.w <= cap) { // 若剩余容量充足,则将当前物品整个装进背包 res += item.v; cap -= item.w; } else { // 若剩余容量不足,则将当前物品的一部分装进背包 res += item.v / item.w * cap; // 已无剩余容量,因此跳出循环 break; } } return res; } /* Driver Code */ void main() { List wgt = [10, 20, 30, 40, 50]; List val = [50, 120, 150, 210, 240]; int cap = 50; // 贪心算法 double res = fractionalKnapsack(wgt, val, cap); print("不超过背包容量的最大物品价值为 $res"); } ================================================ FILE: codes/dart/chapter_greedy/max_capacity.dart ================================================ /** * File: max_capacity.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 最大容量:贪心 */ int maxCapacity(List ht) { // 初始化 i, j,使其分列数组两端 int i = 0, j = ht.length - 1; // 初始最大容量为 0 int res = 0; // 循环贪心选择,直至两板相遇 while (i < j) { // 更新最大容量 int cap = min(ht[i], ht[j]) * (j - i); res = max(res, cap); // 向内移动短板 if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ void main() { List ht = [3, 8, 5, 2, 7, 7, 3, 4]; // 贪心算法 int res = maxCapacity(ht); print("最大容量为 $res"); } ================================================ FILE: codes/dart/chapter_greedy/max_product_cutting.dart ================================================ /** * File: max_product_cutting.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 最大切分乘积:贪心 */ int maxProductCutting(int n) { // 当 n <= 3 时,必须切分出一个 1 if (n <= 3) { return 1 * (n - 1); } // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 int a = n ~/ 3; int b = n % 3; if (b == 1) { // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 return (pow(3, a - 1) * 2 * 2).toInt(); } if (b == 2) { // 当余数为 2 时,不做处理 return (pow(3, a) * 2).toInt(); } // 当余数为 0 时,不做处理 return pow(3, a).toInt(); } /* Driver Code */ void main() { int n = 58; // 贪心算法 int res = maxProductCutting(n); print("最大切分乘积为 $res"); } ================================================ FILE: codes/dart/chapter_hashing/array_hash_map.dart ================================================ /** * File: array_hash_map.dart * Created Time: 2023-03-29 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 键值对 */ class Pair { int key; String val; Pair(this.key, this.val); } /* 基于数组实现的哈希表 */ class ArrayHashMap { late List _buckets; ArrayHashMap() { // 初始化数组,包含 100 个桶 _buckets = List.filled(100, null); } /* 哈希函数 */ int _hashFunc(int key) { final int index = key % 100; return index; } /* 查询操作 */ String? get(int key) { final int index = _hashFunc(key); final Pair? pair = _buckets[index]; if (pair == null) { return null; } return pair.val; } /* 添加操作 */ void put(int key, String val) { final Pair pair = Pair(key, val); final int index = _hashFunc(key); _buckets[index] = pair; } /* 删除操作 */ void remove(int key) { final int index = _hashFunc(key); _buckets[index] = null; } /* 获取所有键值对 */ List pairSet() { List pairSet = []; for (final Pair? pair in _buckets) { if (pair != null) { pairSet.add(pair); } } return pairSet; } /* 获取所有键 */ List keySet() { List keySet = []; for (final Pair? pair in _buckets) { if (pair != null) { keySet.add(pair.key); } } return keySet; } /* 获取所有值 */ List values() { List valueSet = []; for (final Pair? pair in _buckets) { if (pair != null) { valueSet.add(pair.val); } } return valueSet; } /* 打印哈希表 */ void printHashMap() { for (final Pair kv in pairSet()) { print("${kv.key} -> ${kv.val}"); } } } /* Driver Code */ void main() { /* 初始化哈希表 */ final ArrayHashMap map = ArrayHashMap(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(12836, "小哈"); map.put(15937, "小啰"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鸭"); print("\n添加完成后,哈希表为\nKey -> Value"); map.printHashMap(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value String? name = map.get(15937); print("\n输入学号 15937 ,查询到姓名 $name"); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(10583); print("\n删除 10583 后,哈希表为\nKey -> Value"); map.printHashMap(); /* 遍历哈希表 */ print("\n遍历键值对 Key->Value"); map.pairSet().forEach((kv) => print("${kv.key} -> ${kv.val}")); print("\n单独遍历键 Key"); map.keySet().forEach((key) => print("$key")); print("\n单独遍历值 Value"); map.values().forEach((val) => print("$val")); } ================================================ FILE: codes/dart/chapter_hashing/built_in_hash.dart ================================================ /** * File: built_in_hash.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../chapter_stack_and_queue/linkedlist_deque.dart'; /* Driver Code */ void main() { int _num = 3; int hashNum = _num.hashCode; print("整数 $_num 的哈希值为 $hashNum"); bool bol = true; int hashBol = bol.hashCode; print("布尔值 $bol 的哈希值为 $hashBol"); double dec = 3.14159; int hashDec = dec.hashCode; print("小数 $dec 的哈希值为 $hashDec"); String str = "Hello 算法"; int hashStr = str.hashCode; print("字符串 $str 的哈希值为 $hashStr"); List arr = [12836, "小哈"]; int hashArr = arr.hashCode; print("数组 $arr 的哈希值为 $hashArr"); ListNode obj = new ListNode(0); int hashObj = obj.hashCode; print("节点对象 $obj 的哈希值为 $hashObj"); } ================================================ FILE: codes/dart/chapter_hashing/hash_map.dart ================================================ /** * File: hash_map.dart * Created Time: 2023-03-29 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Driver Code */ void main() { /* 初始化哈希表 */ final Map map = {}; /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map[12836] = "小哈"; map[15937] = "小啰"; map[16750] = "小算"; map[13276] = "小法"; map[10583] = "小鸭"; print("\n添加完成后,哈希表为\nKey -> Value"); map.forEach((key, value) => print("$key -> $value")); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value final String? name = map[15937]; print("\n输入学号 15937 ,查询到姓名 $name"); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(10583); print("\n删除 10583 后,哈希表为\nKey -> Value"); map.forEach((key, value) => print("$key -> $value")); /* 遍历哈希表 */ print("\n遍历键值对 Key->Value"); map.forEach((key, value) => print("$key -> $value")); print("\n单独遍历键 Key"); map.keys.forEach((key) => print(key)); print("\n单独遍历值 Value"); map.forEach((key, value) => print("$value")); map.values.forEach((value) => print(value)); } ================================================ FILE: codes/dart/chapter_hashing/hash_map_chaining.dart ================================================ /** * File: hash_map_chaining.dart * Created Time: 2023-06-24 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'array_hash_map.dart'; /* 链式地址哈希表 */ class HashMapChaining { late int size; // 键值对数量 late int capacity; // 哈希表容量 late double loadThres; // 触发扩容的负载因子阈值 late int extendRatio; // 扩容倍数 late List> buckets; // 桶数组 /* 构造方法 */ HashMapChaining() { size = 0; capacity = 4; loadThres = 2.0 / 3.0; extendRatio = 2; buckets = List.generate(capacity, (_) => []); } /* 哈希函数 */ int hashFunc(int key) { return key % capacity; } /* 负载因子 */ double loadFactor() { return size / capacity; } /* 查询操作 */ String? get(int key) { int index = hashFunc(key); List bucket = buckets[index]; // 遍历桶,若找到 key ,则返回对应 val for (Pair pair in bucket) { if (pair.key == key) { return pair.val; } } // 若未找到 key ,则返回 null return null; } /* 添加操作 */ void put(int key, String val) { // 当负载因子超过阈值时,执行扩容 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); List bucket = buckets[index]; // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 for (Pair pair in bucket) { if (pair.key == key) { pair.val = val; return; } } // 若无该 key ,则将键值对添加至尾部 Pair pair = Pair(key, val); bucket.add(pair); size++; } /* 删除操作 */ void remove(int key) { int index = hashFunc(key); List bucket = buckets[index]; // 遍历桶,从中删除键值对 for (Pair pair in bucket) { if (pair.key == key) { bucket.remove(pair); size--; break; } } } /* 扩容哈希表 */ void extend() { // 暂存原哈希表 List> bucketsTmp = buckets; // 初始化扩容后的新哈希表 capacity *= extendRatio; buckets = List.generate(capacity, (_) => []); size = 0; // 将键值对从原哈希表搬运至新哈希表 for (List bucket in bucketsTmp) { for (Pair pair in bucket) { put(pair.key, pair.val); } } } /* 打印哈希表 */ void printHashMap() { for (List bucket in buckets) { List res = []; for (Pair pair in bucket) { res.add("${pair.key} -> ${pair.val}"); } print(res); } } } /* Driver Code */ void main() { /* 初始化哈希表 */ HashMapChaining map = HashMapChaining(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(12836, "小哈"); map.put(15937, "小啰"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鸭"); print("\n添加完成后,哈希表为\nKey -> Value"); map.printHashMap(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value String? name = map.get(13276); print("\n输入学号 13276 ,查询到姓名 ${name}"); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(12836); print("\n删除 12836 后,哈希表为\nKey -> Value"); map.printHashMap(); } ================================================ FILE: codes/dart/chapter_hashing/hash_map_open_addressing.dart ================================================ /** * File: hash_map_open_addressing.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'array_hash_map.dart'; /* 开放寻址哈希表 */ class HashMapOpenAddressing { late int _size; // 键值对数量 int _capacity = 4; // 哈希表容量 double _loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值 int _extendRatio = 2; // 扩容倍数 late List _buckets; // 桶数组 Pair _TOMBSTONE = Pair(-1, "-1"); // 删除标记 /* 构造方法 */ HashMapOpenAddressing() { _size = 0; _buckets = List.generate(_capacity, (index) => null); } /* 哈希函数 */ int hashFunc(int key) { return key % _capacity; } /* 负载因子 */ double loadFactor() { return _size / _capacity; } /* 搜索 key 对应的桶索引 */ int findBucket(int key) { int index = hashFunc(key); int firstTombstone = -1; // 线性探测,当遇到空桶时跳出 while (_buckets[index] != null) { // 若遇到 key ,返回对应的桶索引 if (_buckets[index]!.key == key) { // 若之前遇到了删除标记,则将键值对移动至该索引处 if (firstTombstone != -1) { _buckets[firstTombstone] = _buckets[index]; _buckets[index] = _TOMBSTONE; return firstTombstone; // 返回移动后的桶索引 } return index; // 返回桶索引 } // 记录遇到的首个删除标记 if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) { firstTombstone = index; } // 计算桶索引,越过尾部则返回头部 index = (index + 1) % _capacity; } // 若 key 不存在,则返回添加点的索引 return firstTombstone == -1 ? index : firstTombstone; } /* 查询操作 */ String? get(int key) { // 搜索 key 对应的桶索引 int index = findBucket(key); // 若找到键值对,则返回对应 val if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { return _buckets[index]!.val; } // 若键值对不存在,则返回 null return null; } /* 添加操作 */ void put(int key, String val) { // 当负载因子超过阈值时,执行扩容 if (loadFactor() > _loadThres) { extend(); } // 搜索 key 对应的桶索引 int index = findBucket(key); // 若找到键值对,则覆盖 val 并返回 if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { _buckets[index]!.val = val; return; } // 若键值对不存在,则添加该键值对 _buckets[index] = new Pair(key, val); _size++; } /* 删除操作 */ void remove(int key) { // 搜索 key 对应的桶索引 int index = findBucket(key); // 若找到键值对,则用删除标记覆盖它 if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { _buckets[index] = _TOMBSTONE; _size--; } } /* 扩容哈希表 */ void extend() { // 暂存原哈希表 List bucketsTmp = _buckets; // 初始化扩容后的新哈希表 _capacity *= _extendRatio; _buckets = List.generate(_capacity, (index) => null); _size = 0; // 将键值对从原哈希表搬运至新哈希表 for (Pair? pair in bucketsTmp) { if (pair != null && pair != _TOMBSTONE) { put(pair.key, pair.val); } } } /* 打印哈希表 */ void printHashMap() { for (Pair? pair in _buckets) { if (pair == null) { print("null"); } else if (pair == _TOMBSTONE) { print("TOMBSTONE"); } else { print("${pair.key} -> ${pair.val}"); } } } } /* Driver Code */ void main() { /* 初始化哈希表 */ HashMapOpenAddressing map = HashMapOpenAddressing(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(12836, "小哈"); map.put(15937, "小啰"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鸭"); print("\n添加完成后,哈希表为\nKey -> Value"); map.printHashMap(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value String? name = map.get(13276); print("\n输入学号 13276 ,查询到姓名 $name"); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(16750); print("\n删除 16750 后,哈希表为\nKey -> Value"); map.printHashMap(); } ================================================ FILE: codes/dart/chapter_hashing/simple_hash.dart ================================================ /** * File: simple_hash.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 加法哈希 */ int addHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = (hash + key.codeUnitAt(i)) % MODULUS; } return hash; } /* 乘法哈希 */ int mulHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = (31 * hash + key.codeUnitAt(i)) % MODULUS; } return hash; } /* 异或哈希 */ int xorHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash ^= key.codeUnitAt(i); } return hash & MODULUS; } /* 旋转哈希 */ int rotHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS; } return hash; } /* Dirver Code */ void main() { String key = "Hello 算法"; int hash = addHash(key); print("加法哈希值为 $hash"); hash = mulHash(key); print("乘法哈希值为 $hash"); hash = xorHash(key); print("异或哈希值为 $hash"); hash = rotHash(key); print("旋转哈希值为 $hash"); } ================================================ FILE: codes/dart/chapter_heap/my_heap.dart ================================================ /** * File: my_heap.dart * Created Time: 2023-04-09 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* 大顶堆 */ class MaxHeap { late List _maxHeap; /* 构造方法,根据输入列表建堆 */ MaxHeap(List nums) { // 将列表元素原封不动添加进堆 _maxHeap = nums; // 堆化除叶节点以外的其他所有节点 for (int i = _parent(size() - 1); i >= 0; i--) { siftDown(i); } } /* 获取左子节点的索引 */ int _left(int i) { return 2 * i + 1; } /* 获取右子节点的索引 */ int _right(int i) { return 2 * i + 2; } /* 获取父节点的索引 */ int _parent(int i) { return (i - 1) ~/ 2; // 向下整除 } /* 交换元素 */ void _swap(int i, int j) { int tmp = _maxHeap[i]; _maxHeap[i] = _maxHeap[j]; _maxHeap[j] = tmp; } /* 获取堆大小 */ int size() { return _maxHeap.length; } /* 判断堆是否为空 */ bool isEmpty() { return size() == 0; } /* 访问堆顶元素 */ int peek() { return _maxHeap[0]; } /* 元素入堆 */ void push(int val) { // 添加节点 _maxHeap.add(val); // 从底至顶堆化 siftUp(size() - 1); } /* 从节点 i 开始,从底至顶堆化 */ void siftUp(int i) { while (true) { // 获取节点 i 的父节点 int p = _parent(i); // 当“越过根节点”或“节点无须修复”时,结束堆化 if (p < 0 || _maxHeap[i] <= _maxHeap[p]) { break; } // 交换两节点 _swap(i, p); // 循环向上堆化 i = p; } } /* 元素出堆 */ int pop() { // 判空处理 if (isEmpty()) throw Exception('堆为空'); // 交换根节点与最右叶节点(交换首元素与尾元素) _swap(0, size() - 1); // 删除节点 int val = _maxHeap.removeLast(); // 从顶至底堆化 siftDown(0); // 返回堆顶元素 return val; } /* 从节点 i 开始,从顶至底堆化 */ void siftDown(int i) { while (true) { // 判断节点 i, l, r 中值最大的节点,记为 ma int l = _left(i); int r = _right(i); int ma = i; if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l; if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r; // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if (ma == i) break; // 交换两节点 _swap(i, ma); // 循环向下堆化 i = ma; } } /* 打印堆(二叉树) */ void print() { printHeap(_maxHeap); } } /* Driver Code */ void main() { /* 初始化大顶堆 */ MaxHeap maxHeap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); print("\n输入列表并建堆后"); maxHeap.print(); /* 获取堆顶元素 */ int peek = maxHeap.peek(); print("\n堆顶元素为 $peek"); /* 元素入堆 */ int val = 7; maxHeap.push(val); print("\n元素 $val 入堆后"); maxHeap.print(); /* 堆顶元素出堆 */ peek = maxHeap.pop(); print("\n堆顶元素 $peek 出堆后"); maxHeap.print(); /* 获取堆大小 */ int size = maxHeap.size(); print("\n堆元素数量为 $size"); /* 判断堆是否为空 */ bool isEmpty = maxHeap.isEmpty(); print("\n堆是否为空 $isEmpty"); } ================================================ FILE: codes/dart/chapter_heap/top_k.dart ================================================ /** * File: top_k.dart * Created Time: 2023-08-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* 基于堆查找数组中最大的 k 个元素 */ MinHeap topKHeap(List nums, int k) { // 初始化小顶堆,将数组的前 k 个元素入堆 MinHeap heap = MinHeap(nums.sublist(0, k)); // 从第 k+1 个元素开始,保持堆的长度为 k for (int i = k; i < nums.length; i++) { // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 if (nums[i] > heap.peek()) { heap.pop(); heap.push(nums[i]); } } return heap; } /* Driver Code */ void main() { List nums = [1, 7, 6, 3, 2]; int k = 3; MinHeap res = topKHeap(nums, k); print("最大的 $k 个元素为"); res.print(); } /* 小顶堆 */ class MinHeap { late List _minHeap; /* 构造方法,根据输入列表建堆 */ MinHeap(List nums) { // 将列表元素原封不动添加进堆 _minHeap = nums; // 堆化除叶节点以外的其他所有节点 for (int i = _parent(size() - 1); i >= 0; i--) { siftDown(i); } } /* 返回堆中的元素 */ List getHeap() { return _minHeap; } /* 获取左子节点的索引 */ int _left(int i) { return 2 * i + 1; } /* 获取右子节点的索引 */ int _right(int i) { return 2 * i + 2; } /* 获取父节点的索引 */ int _parent(int i) { return (i - 1) ~/ 2; // 向下整除 } /* 交换元素 */ void _swap(int i, int j) { int tmp = _minHeap[i]; _minHeap[i] = _minHeap[j]; _minHeap[j] = tmp; } /* 获取堆大小 */ int size() { return _minHeap.length; } /* 判断堆是否为空 */ bool isEmpty() { return size() == 0; } /* 访问堆顶元素 */ int peek() { return _minHeap[0]; } /* 元素入堆 */ void push(int val) { // 添加节点 _minHeap.add(val); // 从底至顶堆化 siftUp(size() - 1); } /* 从节点 i 开始,从底至顶堆化 */ void siftUp(int i) { while (true) { // 获取节点 i 的父节点 int p = _parent(i); // 当“越过根节点”或“节点无须修复”时,结束堆化 if (p < 0 || _minHeap[i] >= _minHeap[p]) { break; } // 交换两节点 _swap(i, p); // 循环向上堆化 i = p; } } /* 元素出堆 */ int pop() { // 判空处理 if (isEmpty()) throw Exception('堆为空'); // 交换根节点与最右叶节点(交换首元素与尾元素) _swap(0, size() - 1); // 删除节点 int val = _minHeap.removeLast(); // 从顶至底堆化 siftDown(0); // 返回堆顶元素 return val; } /* 从节点 i 开始,从顶至底堆化 */ void siftDown(int i) { while (true) { // 判断节点 i, l, r 中值最大的节点,记为 ma int l = _left(i); int r = _right(i); int mi = i; if (l < size() && _minHeap[l] < _minHeap[mi]) mi = l; if (r < size() && _minHeap[r] < _minHeap[mi]) mi = r; // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if (mi == i) break; // 交换两节点 _swap(i, mi); // 循环向下堆化 i = mi; } } /* 打印堆(二叉树) */ void print() { printHeap(_minHeap); } } ================================================ FILE: codes/dart/chapter_searching/binary_search.dart ================================================ /** * File: binary_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 二分查找(双闭区间) */ int binarySearch(List nums, int target) { // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 int i = 0, j = nums.length - 1; // 循环,当搜索区间为空时跳出(当 i > j 时为空) while (i <= j) { int m = i + (j - i) ~/ 2; // 计算中点索引 m if (nums[m] < target) { // 此情况说明 target 在区间 [m+1, j] 中 i = m + 1; } else if (nums[m] > target) { // 此情况说明 target 在区间 [i, m-1] 中 j = m - 1; } else { // 找到目标元素,返回其索引 return m; } } // 未找到目标元素,返回 -1 return -1; } /* 二分查找(左闭右开区间) */ int binarySearchLCRO(List nums, int target) { // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 int i = 0, j = nums.length; // 循环,当搜索区间为空时跳出(当 i = j 时为空) while (i < j) { int m = i + (j - i) ~/ 2; // 计算中点索引 m if (nums[m] < target) { // 此情况说明 target 在区间 [m+1, j) 中 i = m + 1; } else if (nums[m] > target) { // 此情况说明 target 在区间 [i, m) 中 j = m; } else { // 找到目标元素,返回其索引 return m; } } // 未找到目标元素,返回 -1 return -1; } /* Driver Code*/ void main() { int target = 6; final nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分查找 (双闭区间) */ int index = binarySearch(nums, target); print('目标元素 6 的索引 = $index'); /* 二分查找(左闭右开区间) */ index = binarySearchLCRO(nums, target); print('目标元素 6 的索引 = $index'); } ================================================ FILE: codes/dart/chapter_searching/binary_search_edge.dart ================================================ /** * File: binary_search_edge.dart * Created Time: 2023-08-14 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'binary_search_insertion.dart'; /* 二分查找最左一个 target */ int binarySearchLeftEdge(List nums, int target) { // 等价于查找 target 的插入点 int i = binarySearchInsertion(nums, target); // 未找到 target ,返回 -1 if (i == nums.length || nums[i] != target) { return -1; } // 找到 target ,返回索引 i return i; } /* 二分查找最右一个 target */ int binarySearchRightEdge(List nums, int target) { // 转化为查找最左一个 target + 1 int i = binarySearchInsertion(nums, target + 1); // j 指向最右一个 target ,i 指向首个大于 target 的元素 int j = i - 1; // 未找到 target ,返回 -1 if (j == -1 || nums[j] != target) { return -1; } // 找到 target ,返回索引 j return j; } /* Driver Code */ void main() { // 包含重复元素的数组 List nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; print("\n数组 nums = $nums"); // 二分查找左边界和右边界 for (int target in [6, 7]) { int index = binarySearchLeftEdge(nums, target); print("最左一个元素 $target 的索引为 $index"); index = binarySearchRightEdge(nums, target); print("最右一个元素 $target 的索引为 $index"); } } ================================================ FILE: codes/dart/chapter_searching/binary_search_insertion.dart ================================================ /** * File: binary_search_insertion.dart * Created Time: 2023-08-14 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 二分查找插入点(无重复元素) */ int binarySearchInsertionSimple(List nums, int target) { int i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1] while (i <= j) { int m = i + (j - i) ~/ 2; // 计算中点索引 m if (nums[m] < target) { i = m + 1; // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在区间 [i, m-1] 中 } else { return m; // 找到 target ,返回插入点 m } } // 未找到 target ,返回插入点 i return i; } /* 二分查找插入点(存在重复元素) */ int binarySearchInsertion(List nums, int target) { int i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1] while (i <= j) { int m = i + (j - i) ~/ 2; // 计算中点索引 m if (nums[m] < target) { i = m + 1; // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在区间 [i, m-1] 中 } else { j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 } } // 返回插入点 i return i; } /* Driver Code */ void main() { // 无重复元素的数组 List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; print("\n数组 nums = $nums"); // 二分查找插入点 for (int target in [6, 9]) { int index = binarySearchInsertionSimple(nums, target); print("元素 $target 的插入点的索引为 $index"); } // 包含重复元素的数组 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; print("\n数组 nums = $nums"); // 二分查找插入点 for (int target in [2, 6, 20]) { int index = binarySearchInsertion(nums, target); print("元素 $target 的插入点的索引为 $index"); } } ================================================ FILE: codes/dart/chapter_searching/hashing_search.dart ================================================ /** * File: hashing_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:collection'; import '../utils/list_node.dart'; /* 哈希查找(数组) */ int hashingSearchArray(Map map, int target) { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 if (!map.containsKey(target)) { return -1; } return map[target]!; } /* 哈希查找(链表) */ ListNode? hashingSearchLinkedList(Map map, int target) { // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 null if (!map.containsKey(target)) { return null; } return map[target]!; } /* Driver Code */ void main() { int target = 3; /* 哈希查找(数组) */ List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // 初始化哈希表 Map map = HashMap(); for (int i = 0; i < nums.length; i++) { map.putIfAbsent(nums[i], () => i); // key: 元素,value: 索引 } int index = hashingSearchArray(map, target); print('目标元素 3 的索引 = $index'); /* 哈希查找(链表) */ ListNode? head = listToLinkedList(nums); // 初始化哈希表 Map map1 = HashMap(); while (head != null) { map1.putIfAbsent(head.val, () => head!); // key: 节点值,value: 节点 head = head.next; } ListNode? node = hashingSearchLinkedList(map1, target); print('目标节点值 3 的对应节点对象为 $node'); } ================================================ FILE: codes/dart/chapter_searching/linear_search.dart ================================================ /** * File: linear_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import '../utils/list_node.dart'; /* 线性查找(数组) */ int linearSearchArray(List nums, int target) { // 遍历数组 for (int i = 0; i < nums.length; i++) { // 找到目标元素,返回其索引 if (nums[i] == target) { return i; } } // 未找到目标元素,返回 -1 return -1; } /* 线性查找(链表) */ ListNode? linearSearchList(ListNode? head, int target) { // 遍历链表 while (head != null) { // 找到目标节点,返回之 if (head.val == target) return head; head = head.next; } // 未找到目标元素,返回 null return null; } /* Driver Code */ void main() { int target = 3; /* 在数组中执行线性查找 */ List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; int index = linearSearchArray(nums, target); print('目标元素 3 的索引 = $index'); /* 在链表中执行线性查找 */ ListNode? head = listToLinkedList(nums); ListNode? node = linearSearchList(head, target); print('目标节点值 3 的对应节点对象为 $node'); } ================================================ FILE: codes/dart/chapter_searching/two_sum.dart ================================================ /** * File: two_sum.dart * Created Time: 2023-2-11 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:collection'; /* 方法一: 暴力枚举 */ List twoSumBruteForce(List nums, int target) { int size = nums.length; // 两层循环,时间复杂度为 O(n^2) for (var i = 0; i < size - 1; i++) { for (var j = i + 1; j < size; j++) { if (nums[i] + nums[j] == target) return [i, j]; } } return [0]; } /* 方法二: 辅助哈希表 */ List twoSumHashTable(List nums, int target) { int size = nums.length; // 辅助哈希表,空间复杂度为 O(n) Map dic = HashMap(); // 单层循环,时间复杂度为 O(n) for (var i = 0; i < size; i++) { if (dic.containsKey(target - nums[i])) { return [dic[target - nums[i]]!, i]; } dic.putIfAbsent(nums[i], () => i); } return [0]; } /* Driver Code */ void main() { // ======= Test Case ======= List nums = [2, 7, 11, 15]; int target = 13; // ====== Driver Code ====== // 方法一 List res = twoSumBruteForce(nums, target); print('方法一 res = $res'); // 方法二 res = twoSumHashTable(nums, target); print('方法二 res = $res'); } ================================================ FILE: codes/dart/chapter_sorting/bubble_sort.dart ================================================ /** * File: bubble_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* 冒泡排序 */ void bubbleSort(List nums) { // 外循环:未排序区间为 [0, i] for (int i = nums.length - 1; i > 0; i--) { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* 冒泡排序(标志优化)*/ void bubbleSortWithFlag(List nums) { // 外循环:未排序区间为 [0, i] for (int i = nums.length - 1; i > 0; i--) { bool flag = false; // 初始化标志位 // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // 记录交换元素 } } if (!flag) break; // 此轮“冒泡”未交换任何元素,直接跳出 } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); print("冒泡排序完成后 nums = $nums"); List nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); print("冒泡排序完成后 nums1 = $nums1"); } ================================================ FILE: codes/dart/chapter_sorting/bucket_sort.dart ================================================ /** * File: bucket_sort.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 桶排序 */ void bucketSort(List nums) { // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 int k = nums.length ~/ 2; List> buckets = List.generate(k, (index) => []); // 1. 将数组元素分配到各个桶中 for (double _num in nums) { // 输入数据范围为 [0, 1),使用 _num * k 映射到索引范围 [0, k-1] int i = (_num * k).toInt(); // 将 _num 添加进桶 bucket_idx buckets[i].add(_num); } // 2. 对各个桶执行排序 for (List bucket in buckets) { bucket.sort(); } // 3. 遍历桶合并结果 int i = 0; for (List bucket in buckets) { for (double _num in bucket) { nums[i++] = _num; } } } /* Driver Code*/ void main() { // 设输入数据为浮点数,范围为 [0, 1) final nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); print('桶排序完成后 nums = $nums'); } ================================================ FILE: codes/dart/chapter_sorting/counting_sort.dart ================================================ /** * File: counting_sort.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:math'; /* 计数排序 */ // 简单实现,无法用于排序对象 void countingSortNaive(List nums) { // 1. 统计数组最大元素 m int m = 0; for (int _num in nums) { m = max(m, _num); } // 2. 统计各数字的出现次数 // counter[_num] 代表 _num 的出现次数 List counter = List.filled(m + 1, 0); for (int _num in nums) { counter[_num]++; } // 3. 遍历 counter ,将各元素填入原数组 nums int i = 0; for (int _num = 0; _num < m + 1; _num++) { for (int j = 0; j < counter[_num]; j++, i++) { nums[i] = _num; } } } /* 计数排序 */ // 完整实现,可排序对象,并且是稳定排序 void countingSort(List nums) { // 1. 统计数组最大元素 m int m = 0; for (int _num in nums) { m = max(m, _num); } // 2. 统计各数字的出现次数 // counter[_num] 代表 _num 的出现次数 List counter = List.filled(m + 1, 0); for (int _num in nums) { counter[_num]++; } // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” // 即 counter[_num]-1 是 _num 在 res 中最后一次出现的索引 for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. 倒序遍历 nums ,将各元素填入结果数组 res // 初始化数组 res 用于记录结果 int n = nums.length; List res = List.filled(n, 0); for (int i = n - 1; i >= 0; i--) { int _num = nums[i]; res[counter[_num] - 1] = _num; // 将 _num 放置到对应索引处 counter[_num]--; // 令前缀和自减 1 ,得到下次放置 _num 的索引 } // 使用结果数组 res 覆盖原数组 nums nums.setAll(0, res); } /* Driver Code*/ void main() { final nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); print('计数排序(无法排序对象)完成后 nums = $nums'); final nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); print('计数排序完成后 nums1 = $nums1'); } ================================================ FILE: codes/dart/chapter_sorting/heap_sort.dart ================================================ /** * File: heap_sort.dart * Created Time: 2023-06-01 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ void siftDown(List nums, int n, int i) { while (true) { // 判断节点 i, l, r 中值最大的节点,记为 ma int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; if (l < n && nums[l] > nums[ma]) ma = l; if (r < n && nums[r] > nums[ma]) ma = r; // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if (ma == i) break; // 交换两节点 int temp = nums[i]; nums[i] = nums[ma]; nums[ma] = temp; // 循环向下堆化 i = ma; } } /* 堆排序 */ void heapSort(List nums) { // 建堆操作:堆化除叶节点以外的其他所有节点 for (int i = nums.length ~/ 2 - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // 从堆中提取最大元素,循环 n-1 轮 for (int i = nums.length - 1; i > 0; i--) { // 交换根节点与最右叶节点(交换首元素与尾元素) int tmp = nums[0]; nums[0] = nums[i]; nums[i] = tmp; // 以根节点为起点,从顶至底进行堆化 siftDown(nums, i, 0); } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; heapSort(nums); print("堆排序完成后 nums = $nums"); } ================================================ FILE: codes/dart/chapter_sorting/insertion_sort.dart ================================================ /** * File: insertion_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* 插入排序 */ void insertionSort(List nums) { // 外循环:已排序区间为 [0, i-1] for (int i = 1; i < nums.length; i++) { int base = nums[i], j = i - 1; // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位 j--; } nums[j + 1] = base; // 将 base 赋值到正确位置 } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); print("插入排序完成后 nums = $nums"); } ================================================ FILE: codes/dart/chapter_sorting/merge_sort.dart ================================================ /** * File: merge_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* 合并左子数组和右子数组 */ void merge(List nums, int left, int mid, int right) { // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] // 创建一个临时数组 tmp ,用于存放合并后的结果 List tmp = List.filled(right - left + 1, 0); // 初始化左子数组和右子数组的起始索引 int i = left, j = mid + 1, k = 0; // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } // 将左子数组和右子数组的剩余元素复制到临时数组中 while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* 归并排序 */ void mergeSort(List nums, int left, int right) { // 终止条件 if (left >= right) return; // 当子数组长度为 1 时终止递归 // 划分阶段 int mid = left + (right - left) ~/ 2; // 计算中点 mergeSort(nums, left, mid); // 递归左子数组 mergeSort(nums, mid + 1, right); // 递归右子数组 // 合并阶段 merge(nums, left, mid, right); } /* Driver Code */ void main() { /* 归并排序 */ List nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); print("归并排序完成后 nums = $nums"); } ================================================ FILE: codes/dart/chapter_sorting/quick_sort.dart ================================================ /** * File: quick_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* 快速排序类 */ class QuickSort { /* 元素交换 */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵划分 */ static int _partition(List nums, int left, int right) { // 以 nums[left] 为基准数 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 从右向左找首个小于基准数的元素 while (i < j && nums[i] <= nums[left]) i++; // 从左向右找首个大于基准数的元素 _swap(nums, i, j); // 交换这两个元素 } _swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } /* 快速排序 */ static void quickSort(List nums, int left, int right) { // 子数组长度为 1 时终止递归 if (left >= right) return; // 哨兵划分 int pivot = _partition(nums, left, right); // 递归左子数组、右子数组 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } /* 快速排序类(中位基准数优化) */ class QuickSortMedian { /* 元素交换 */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 选取三个候选元素的中位数 */ static int _medianThree(List nums, int left, int mid, int right) { int l = nums[left], m = nums[mid], r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // m 在 l 和 r 之间 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l 在 m 和 r 之间 return right; } /* 哨兵划分(三数取中值) */ static int _partition(List 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; // 返回基准数的索引 } /* 快速排序 */ static void quickSort(List nums, int left, int right) { // 子数组长度为 1 时终止递归 if (left >= right) return; // 哨兵划分 int pivot = _partition(nums, left, right); // 递归左子数组、右子数组 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } /* 快速排序类(递归深度优化) */ class QuickSortTailCall { /* 元素交换 */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵划分 */ static int _partition(List nums, int left, int right) { // 以 nums[left] 为基准数 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 从右向左找首个小于基准数的元素 while (i < j && nums[i] <= nums[left]) i++; // 从左向右找首个大于基准数的元素 _swap(nums, i, j); // 交换这两个元素 } _swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } /* 快速排序(递归深度优化) */ static void quickSort(List 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] } } } } /* Driver Code */ void main() { /* 快速排序 */ List nums = [2, 4, 1, 0, 3, 5]; QuickSort.quickSort(nums, 0, nums.length - 1); print("快速排序完成后 nums = $nums"); /* 快速排序(中位基准数优化) */ List nums1 = [2, 4, 1, 0, 3, 5]; QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); print("快速排序(中位基准数优化)完成后 nums1 = $nums1"); /* 快速排序(递归深度优化) */ List nums2 = [2, 4, 1, 0, 3, 5]; QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); print("快速排序(递归深度优化)完成后 nums2 = $nums2"); } ================================================ FILE: codes/dart/chapter_sorting/radix_sort.dart ================================================ /** * File: radix_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* 获取元素 _num 的第 k 位,其中 exp = 10^(k-1) */ int digit(int _num, int exp) { // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 return (_num ~/ exp) % 10; } /* 计数排序(根据 nums 第 k 位排序) */ void countingSortDigit(List nums, int exp) { // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 List counter = List.filled(10, 0); int n = nums.length; // 统计 0~9 各数字的出现次数 for (int i = 0; i < n; i++) { int d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d counter[d]++; // 统计数字 d 的出现次数 } // 求前缀和,将“出现个数”转换为“数组索引” for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 倒序遍历,根据桶内统计结果,将各元素填入 res List res = List.filled(n, 0); for (int i = n - 1; i >= 0; i--) { int d = digit(nums[i], exp); int j = counter[d] - 1; // 获取 d 在数组中的索引 j res[j] = nums[i]; // 将当前元素填入索引 j counter[d]--; // 将 d 的数量减 1 } // 使用结果覆盖原数组 nums for (int i = 0; i < n; i++) nums[i] = res[i]; } /* 基数排序 */ void radixSort(List nums) { // 获取数组的最大元素,用于判断最大位数 // dart 中 int 的长度是 64 位的 int m = -1 << 63; for (int _num in nums) if (_num > m) m = _num; // 按照从低位到高位的顺序遍历 for (int exp = 1; exp <= m; exp *= 10) // 对数组元素的第 k 位执行计数排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums, exp); } /* Driver Code */ void main() { // 基数排序 List nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ]; radixSort(nums); print("基数排序完成后 nums = $nums"); } ================================================ FILE: codes/dart/chapter_sorting/selection_sort.dart ================================================ /** * File: selection_sort.dart * Created Time: 2023-06-01 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 选择排序 */ void selectionSort(List nums) { int n = nums.length; // 外循环:未排序区间为 [i, n-1] for (int i = 0; i < n - 1; i++) { // 内循环:找到未排序区间内的最小元素 int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // 记录最小元素的索引 } // 将该最小元素与未排序区间的首个元素交换 int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; selectionSort(nums); print("选择排序完成后 nums = $nums"); } ================================================ FILE: codes/dart/chapter_stack_and_queue/array_deque.dart ================================================ /** * File: array_deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 基于环形数组实现的双向队列 */ class ArrayDeque { late List _nums; // 用于存储双向队列元素的数组 late int _front; // 队首指针,指向队首元素 late int _queSize; // 双向队列长度 /* 构造方法 */ ArrayDeque(int capacity) { this._nums = List.filled(capacity, 0); this._front = this._queSize = 0; } /* 获取双向队列的容量 */ int capacity() { return _nums.length; } /* 获取双向队列的长度 */ int size() { return _queSize; } /* 判断双向队列是否为空 */ bool isEmpty() { return _queSize == 0; } /* 计算环形数组索引 */ int index(int i) { // 通过取余操作实现数组首尾相连 // 当 i 越过数组尾部后,回到头部 // 当 i 越过数组头部后,回到尾部 return (i + capacity()) % capacity(); } /* 队首入队 */ void pushFirst(int _num) { if (_queSize == capacity()) { throw Exception("双向队列已满"); } // 队首指针向左移动一位 // 通过取余操作实现 _front 越过数组头部后回到尾部 _front = index(_front - 1); // 将 _num 添加至队首 _nums[_front] = _num; _queSize++; } /* 队尾入队 */ void pushLast(int _num) { if (_queSize == capacity()) { throw Exception("双向队列已满"); } // 计算队尾指针,指向队尾索引 + 1 int rear = index(_front + _queSize); // 将 _num 添加至队尾 _nums[rear] = _num; _queSize++; } /* 队首出队 */ int popFirst() { int _num = peekFirst(); // 队首指针向右移动一位 _front = index(_front + 1); _queSize--; return _num; } /* 队尾出队 */ int popLast() { int _num = peekLast(); _queSize--; return _num; } /* 访问队首元素 */ int peekFirst() { if (isEmpty()) { throw Exception("双向队列为空"); } return _nums[_front]; } /* 访问队尾元素 */ int peekLast() { if (isEmpty()) { throw Exception("双向队列为空"); } // 计算尾元素索引 int last = index(_front + _queSize - 1); return _nums[last]; } /* 返回数组用于打印 */ List toArray() { // 仅转换有效长度范围内的列表元素 List res = List.filled(_queSize, 0); for (int i = 0, j = _front; i < _queSize; i++, j++) { res[i] = _nums[index(j)]; } return res; } } /* Driver Code */ void main() { /* 初始化双向队列 */ final ArrayDeque deque = ArrayDeque(10); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); print("双向队列 deque = ${deque.toArray()}"); /* 访问元素 */ final int peekFirst = deque.peekFirst(); print("队首元素 peekFirst = $peekFirst"); final int peekLast = deque.peekLast(); print("队尾元素 peekLast = $peekLast"); /* 元素入队 */ deque.pushLast(4); print("元素 4 队尾入队后 deque = ${deque.toArray()}"); deque.pushFirst(1); print("元素 1 队首入队后 deque = ${deque.toArray()}"); /* 元素出队 */ final int popLast = deque.popLast(); print("队尾出队元素 = $popLast ,队尾出队后 deque = ${deque.toArray()}"); final int popFirst = deque.popFirst(); print("队首出队元素 = $popFirst ,队首出队后 deque = ${deque.toArray()}"); /* 获取双向队列的长度 */ final int size = deque.size(); print("双向队列长度 size = $size"); /* 判断双向队列是否为空 */ final bool isEmpty = deque.isEmpty(); print("双向队列是否为空 = $isEmpty"); } ================================================ FILE: codes/dart/chapter_stack_and_queue/array_queue.dart ================================================ /** * File: array_queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 基于环形数组实现的队列 */ class ArrayQueue { late List _nums; // 用于储存队列元素的数组 late int _front; // 队首指针,指向队首元素 late int _queSize; // 队列长度 ArrayQueue(int capacity) { _nums = List.filled(capacity, 0); _front = _queSize = 0; } /* 获取队列的容量 */ int capaCity() { return _nums.length; } /* 获取队列的长度 */ int size() { return _queSize; } /* 判断队列是否为空 */ bool isEmpty() { return _queSize == 0; } /* 入队 */ void push(int _num) { if (_queSize == capaCity()) { throw Exception("队列已满"); } // 计算队尾指针,指向队尾索引 + 1 // 通过取余操作实现 rear 越过数组尾部后回到头部 int rear = (_front + _queSize) % capaCity(); // 将 _num 添加至队尾 _nums[rear] = _num; _queSize++; } /* 出队 */ int pop() { int _num = peek(); // 队首指针向后移动一位,若越过尾部,则返回到数组头部 _front = (_front + 1) % capaCity(); _queSize--; return _num; } /* 访问队首元素 */ int peek() { if (isEmpty()) { throw Exception("队列为空"); } return _nums[_front]; } /* 返回 Array */ List toArray() { // 仅转换有效长度范围内的列表元素 final List res = List.filled(_queSize, 0); for (int i = 0, j = _front; i < _queSize; i++, j++) { res[i] = _nums[j % capaCity()]; } return res; } } /* Driver Code */ void main() { /* 初始化队列 */ final int capacity = 10; final ArrayQueue queue = ArrayQueue(capacity); /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print("队列 queue = ${queue.toArray()}"); /* 访问队首元素 */ final int peek = queue.peek(); print("队首元素 peek = $peek"); /* 元素出队 */ final int pop = queue.pop(); print("出队元素 pop = $pop ,出队后 queue = ${queue.toArray()}"); /* 获取队列长度 */ final int size = queue.size(); print("队列长度 size = $size"); /* 判断队列是否为空 */ final bool isEmpty = queue.isEmpty(); print("队列是否为空 = $isEmpty"); /* 测试环形数组 */ for (int i = 0; i < 10; i++) { queue.push(i); queue.pop(); print("第 $i 轮入队 + 出队后 queue = ${queue.toArray()}"); } } ================================================ FILE: codes/dart/chapter_stack_and_queue/array_stack.dart ================================================ /** * File: array_stack.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 基于数组实现的栈 */ class ArrayStack { late List _stack; ArrayStack() { _stack = []; } /* 获取栈的长度 */ int size() { return _stack.length; } /* 判断栈是否为空 */ bool isEmpty() { return _stack.isEmpty; } /* 入栈 */ void push(int _num) { _stack.add(_num); } /* 出栈 */ int pop() { if (isEmpty()) { throw Exception("栈为空"); } return _stack.removeLast(); } /* 访问栈顶元素 */ int peek() { if (isEmpty()) { throw Exception("栈为空"); } return _stack.last; } /* 将栈转化为 Array 并返回 */ List toArray() => _stack; } /* Driver Code */ void main() { /* 初始化栈 */ final ArrayStack stack = ArrayStack(); /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print("栈 stack = ${stack.toArray()}"); /* 访问栈顶元素 */ final int peek = stack.peek(); print("栈顶元素 peek = $peek"); /* 元素出栈 */ final int pop = stack.pop(); print("出栈元素 pop = $pop ,出栈后 stack = ${stack.toArray()}"); /* 获取栈的长度 */ final int size = stack.size(); print("栈的长度 size = $size"); /* 判断是否为空 */ final bool isEmpty = stack.isEmpty(); print("栈是否为空 = $isEmpty"); } ================================================ FILE: codes/dart/chapter_stack_and_queue/deque.dart ================================================ /** * File: deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; void main() { /* 初始化双向队列 */ final Queue deque = Queue(); deque.addFirst(3); deque.addLast(2); deque.addLast(5); print("双向队列 deque = $deque"); /* 访问元素 */ final int peekFirst = deque.first; print("队首元素 peekFirst = $peekFirst"); final int peekLast = deque.last; print("队尾元素 peekLast = $peekLast"); /* 元素入队 */ deque.addLast(4); print("元素 4 队尾入队后 deque = $deque"); deque.addFirst(1); print("元素 1 队首入队后 deque = $deque"); /* 元素出队 */ final int popLast = deque.removeLast(); print("队尾出队元素 = $popLast ,队尾出队后 deque = $deque"); final int popFirst = deque.removeFirst(); print("队首出队元素 = $popFirst ,队首出队后 deque = $deque"); /* 获取双向队列的长度 */ final int size = deque.length; print("双向队列长度 size = $size"); /* 判断双向队列是否为空 */ final bool isEmpty = deque.isEmpty; print("双向队列是否为空 = $isEmpty"); } ================================================ FILE: codes/dart/chapter_stack_and_queue/linkedlist_deque.dart ================================================ /** * File: linkedlist_deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 双向链表节点 */ class ListNode { int val; // 节点值 ListNode? next; // 后继节点引用 ListNode? prev; // 前驱节点引用 ListNode(this.val, {this.next, this.prev}); } /* 基于双向链表实现的双向对列 */ class LinkedListDeque { late ListNode? _front; // 头节点 _front late ListNode? _rear; // 尾节点 _rear int _queSize = 0; // 双向队列的长度 LinkedListDeque() { this._front = null; this._rear = null; } /* 获取双向队列长度 */ int size() { return this._queSize; } /* 判断双向队列是否为空 */ bool isEmpty() { return size() == 0; } /* 入队操作 */ void push(int _num, bool isFront) { final ListNode node = ListNode(_num); if (isEmpty()) { // 若链表为空,则令 _front 和 _rear 都指向 node _front = _rear = node; } else if (isFront) { // 队首入队操作 // 将 node 添加至链表头部 _front!.prev = node; node.next = _front; _front = node; // 更新头节点 } else { // 队尾入队操作 // 将 node 添加至链表尾部 _rear!.next = node; node.prev = _rear; _rear = node; // 更新尾节点 } _queSize++; // 更新队列长度 } /* 队首入队 */ void pushFirst(int _num) { push(_num, true); } /* 队尾入队 */ void pushLast(int _num) { push(_num, false); } /* 出队操作 */ int? pop(bool isFront) { // 若队列为空,直接返回 null if (isEmpty()) { return null; } final int val; if (isFront) { // 队首出队操作 val = _front!.val; // 暂存头节点值 // 删除头节点 ListNode? fNext = _front!.next; if (fNext != null) { fNext.prev = null; _front!.next = null; } _front = fNext; // 更新头节点 } else { // 队尾出队操作 val = _rear!.val; // 暂存尾节点值 // 删除尾节点 ListNode? rPrev = _rear!.prev; if (rPrev != null) { rPrev.next = null; _rear!.prev = null; } _rear = rPrev; // 更新尾节点 } _queSize--; // 更新队列长度 return val; } /* 队首出队 */ int? popFirst() { return pop(true); } /* 队尾出队 */ int? popLast() { return pop(false); } /* 访问队首元素 */ int? peekFirst() { return _front?.val; } /* 访问队尾元素 */ int? peekLast() { return _rear?.val; } /* 返回数组用于打印 */ List toArray() { ListNode? node = _front; final List res = []; for (int i = 0; i < _queSize; i++) { res.add(node!.val); node = node.next; } return res; } } /* Driver Code */ void main() { /* 初始化双向队列 */ final LinkedListDeque deque = LinkedListDeque(); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); print("双向队列 deque = ${deque.toArray()}"); /* 访问元素 */ int? peekFirst = deque.peekFirst(); print("队首元素 peekFirst = $peekFirst"); int? peekLast = deque.peekLast(); print("队尾元素 peekLast = $peekLast"); /* 元素入队 */ deque.pushLast(4); print("元素 4 队尾入队后 deque = ${deque.toArray()}"); deque.pushFirst(1); print("元素 1 队首入队后 deque = ${deque.toArray()}"); /* 元素出队 */ int? popLast = deque.popLast(); print("队尾出队元素 = $popLast ,队尾出队后 deque = ${deque.toArray()}"); int? popFirst = deque.popFirst(); print("队首出队元素 = $popFirst ,队首出队后 deque = ${deque.toArray()}"); /* 获取双向队列的长度 */ int size = deque.size(); print("双向队列长度 size = $size"); /* 判断双向队列是否为空 */ bool isEmpty = deque.isEmpty(); print("双向队列是否为空 = $isEmpty"); } ================================================ FILE: codes/dart/chapter_stack_and_queue/linkedlist_queue.dart ================================================ /** * File: linkedlist_queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/list_node.dart'; /* 基于链表实现的队列 */ class LinkedListQueue { ListNode? _front; // 头节点 _front ListNode? _rear; // 尾节点 _rear int _queSize = 0; // 队列长度 LinkedListQueue() { _front = null; _rear = null; } /* 获取队列的长度 */ int size() { return _queSize; } /* 判断队列是否为空 */ bool isEmpty() { return _queSize == 0; } /* 入队 */ void push(int _num) { // 在尾节点后添加 _num final node = ListNode(_num); // 如果队列为空,则令头、尾节点都指向该节点 if (_front == null) { _front = node; _rear = node; } else { // 如果队列不为空,则将该节点添加到尾节点后 _rear!.next = node; _rear = node; } _queSize++; } /* 出队 */ int pop() { final int _num = peek(); // 删除头节点 _front = _front!.next; _queSize--; return _num; } /* 访问队首元素 */ int peek() { if (_queSize == 0) { throw Exception('队列为空'); } return _front!.val; } /* 将链表转化为 Array 并返回 */ List toArray() { ListNode? node = _front; final List queue = []; while (node != null) { queue.add(node.val); node = node.next; } return queue; } } /* Driver Code */ void main() { /* 初始化队列 */ final queue = LinkedListQueue(); /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print("队列 queue = ${queue.toArray()}"); /* 访问队首元素 */ final int peek = queue.peek(); print("队首元素 peek = $peek"); /* 元素出队 */ final int pop = queue.pop(); print("出队元素 pop = $pop ,出队后 queue = ${queue.toArray()}"); /* 获取队列的长度 */ final int size = queue.size(); print("队列长度 size = $size"); /* 判断队列是否为空 */ final bool isEmpty = queue.isEmpty(); print("队列是否为空 = $isEmpty"); } ================================================ FILE: codes/dart/chapter_stack_and_queue/linkedlist_stack.dart ================================================ /** * File: linkedlist_stack.dart * Created Time: 2023-03-27 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/list_node.dart'; /* 基于链表类实现的栈 */ class LinkedListStack { ListNode? _stackPeek; // 将头节点作为栈顶 int _stkSize = 0; // 栈的长度 LinkedListStack() { _stackPeek = null; } /* 获取栈的长度 */ int size() { return _stkSize; } /* 判断栈是否为空 */ bool isEmpty() { return _stkSize == 0; } /* 入栈 */ void push(int _num) { final ListNode node = ListNode(_num); node.next = _stackPeek; _stackPeek = node; _stkSize++; } /* 出栈 */ int pop() { final int _num = peek(); _stackPeek = _stackPeek!.next; _stkSize--; return _num; } /* 访问栈顶元素 */ int peek() { if (_stackPeek == null) { throw Exception("栈为空"); } return _stackPeek!.val; } /* 将链表转化为 List 并返回 */ List toList() { ListNode? node = _stackPeek; List list = []; while (node != null) { list.add(node.val); node = node.next; } list = list.reversed.toList(); return list; } } /* Driver Code */ void main() { /* 初始化栈 */ final LinkedListStack stack = LinkedListStack(); /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print("栈 stack = ${stack.toList()}"); /* 访问栈顶元素 */ final int peek = stack.peek(); print("栈顶元素 peek = $peek"); /* 元素出栈 */ final int pop = stack.pop(); print("出栈元素 pop = $pop ,出栈后 stack = ${stack.toList()}"); /* 获取栈的长度 */ final int size = stack.size(); print("栈的长度 size = $size"); /* 判断是否为空 */ final bool isEmpty = stack.isEmpty(); print("栈是否为空 = $isEmpty"); } ================================================ FILE: codes/dart/chapter_stack_and_queue/queue.dart ================================================ /** * File: queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; void main() { /* 初始化队列 */ // 在 Dart 中,一般将双向队列类 Queue 看作队列使用 final Queue queue = Queue(); /* 元素入队 */ queue.add(1); queue.add(3); queue.add(2); queue.add(5); queue.add(4); print("队列 queue = $queue"); /* 访问队首元素 */ final int peek = queue.first; print("队首元素 peek = $peek"); /* 元素出队 */ final int pop = queue.removeFirst(); print("出队元素 pop = $pop ,出队后 queue = $queue"); /* 获取队列长度 */ final int size = queue.length; print("队列长度 size = $size"); /* 判断队列是否为空 */ final bool isEmpty = queue.isEmpty; print("队列是否为空 = $isEmpty"); } ================================================ FILE: codes/dart/chapter_stack_and_queue/stack.dart ================================================ /** * File: stack.dart * Created Time: 2023-03-27 * Author: liuyuxin (gvenusleo@gmail.com) */ void main() { /* 初始化栈 */ // Dart 没有内置的栈类,可以把 List 当作栈来使用 final List stack = []; /* 元素入栈 */ stack.add(1); stack.add(3); stack.add(2); stack.add(5); stack.add(4); print("栈 stack = $stack"); /* 访问栈顶元素 */ final int peek = stack.last; print("栈顶元素 peek = $peek"); /* 元素出栈 */ final int pop = stack.removeLast(); print("出栈元素 pop = $pop ,出栈后 stack = $stack"); /* 获取栈的长度 */ final int size = stack.length; print("栈的长度 size = $size"); /* 判断是否为空 */ final bool isEmpty = stack.isEmpty; print("栈是否为空 = $isEmpty"); } ================================================ FILE: codes/dart/chapter_tree/array_binary_tree.dart ================================================ /** * File: array_binary_tree.dart * Created Time: 2023-08-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 数组表示下的二叉树类 */ class ArrayBinaryTree { late List _tree; /* 构造方法 */ ArrayBinaryTree(this._tree); /* 列表容量 */ int size() { return _tree.length; } /* 获取索引为 i 节点的值 */ int? val(int i) { // 若索引越界,则返回 null ,代表空位 if (i < 0 || i >= size()) { return null; } return _tree[i]; } /* 获取索引为 i 节点的左子节点的索引 */ int? left(int i) { return 2 * i + 1; } /* 获取索引为 i 节点的右子节点的索引 */ int? right(int i) { return 2 * i + 2; } /* 获取索引为 i 节点的父节点的索引 */ int? parent(int i) { return (i - 1) ~/ 2; } /* 层序遍历 */ List levelOrder() { List res = []; for (int i = 0; i < size(); i++) { if (val(i) != null) { res.add(val(i)!); } } return res; } /* 深度优先遍历 */ void dfs(int i, String order, List res) { // 若为空位,则返回 if (val(i) == null) { return; } // 前序遍历 if (order == 'pre') { res.add(val(i)); } dfs(left(i)!, order, res); // 中序遍历 if (order == 'in') { res.add(val(i)); } dfs(right(i)!, order, res); // 后序遍历 if (order == 'post') { res.add(val(i)); } } /* 前序遍历 */ List preOrder() { List res = []; dfs(0, 'pre', res); return res; } /* 中序遍历 */ List inOrder() { List res = []; dfs(0, 'in', res); return res; } /* 后序遍历 */ List postOrder() { List res = []; dfs(0, 'post', res); return res; } } /* Driver Code */ void main() { // 初始化二叉树 // 这里借助了一个从数组直接生成二叉树的函数 List arr = [ 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ]; TreeNode? root = listToTree(arr); print("\n初始化二叉树\n"); print("二叉树的数组表示:"); print(arr); print("二叉树的链表表示:"); printTree(root); // 数组表示下的二叉树类 ArrayBinaryTree abt = ArrayBinaryTree(arr); // 访问节点 int i = 1; int? l = abt.left(i); int? r = abt.right(i); int? p = abt.parent(i); print("\n当前节点的索引为 $i ,值为 ${abt.val(i)}"); print("其左子节点的索引为 $l ,值为 ${(l == null ? "null" : abt.val(l))}"); print("其右子节点的索引为 $r ,值为 ${(r == null ? "null" : abt.val(r))}"); print("其父节点的索引为 $p ,值为 ${(p == null ? "null" : abt.val(p))}"); // 遍历树 List res = abt.levelOrder(); print("\n层序遍历为:$res"); res = abt.preOrder(); print("前序遍历为 $res"); res = abt.inOrder(); print("中序遍历为 $res"); res = abt.postOrder(); print("后序遍历为 $res"); } ================================================ FILE: codes/dart/chapter_tree/avl_tree.dart ================================================ /** * File: avl_tree.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; class AVLTree { TreeNode? root; /* 构造方法 */ AVLTree() { root = null; } /* 获取节点高度 */ int height(TreeNode? node) { // 空节点高度为 -1 ,叶节点高度为 0 return node == null ? -1 : node.height; } /* 更新节点高度 */ void updateHeight(TreeNode? node) { // 节点高度等于最高子树高度 + 1 node!.height = max(height(node.left), height(node.right)) + 1; } /* 获取平衡因子 */ int balanceFactor(TreeNode? node) { // 空节点平衡因子为 0 if (node == null) return 0; // 节点平衡因子 = 左子树高度 - 右子树高度 return height(node.left) - height(node.right); } /* 右旋操作 */ TreeNode? rightRotate(TreeNode? node) { TreeNode? child = node!.left; TreeNode? grandChild = child!.right; // 以 child 为原点,将 node 向右旋转 child.right = node; node.left = grandChild; // 更新节点高度 updateHeight(node); updateHeight(child); // 返回旋转后子树的根节点 return child; } /* 左旋操作 */ TreeNode? leftRotate(TreeNode? node) { TreeNode? child = node!.right; TreeNode? grandChild = child!.left; // 以 child 为原点,将 node 向左旋转 child.left = node; node.right = grandChild; // 更新节点高度 updateHeight(node); updateHeight(child); // 返回旋转后子树的根节点 return child; } /* 执行旋转操作,使该子树重新恢复平衡 */ TreeNode? rotate(TreeNode? node) { // 获取节点 node 的平衡因子 int factor = balanceFactor(node); // 左偏树 if (factor > 1) { if (balanceFactor(node!.left) >= 0) { // 右旋 return rightRotate(node); } else { // 先左旋后右旋 node.left = leftRotate(node.left); return rightRotate(node); } } // 右偏树 if (factor < -1) { if (balanceFactor(node!.right) <= 0) { // 左旋 return leftRotate(node); } else { // 先右旋后左旋 node.right = rightRotate(node.right); return leftRotate(node); } } // 平衡树,无须旋转,直接返回 return node; } /* 插入节点 */ void insert(int val) { root = insertHelper(root, val); } /* 递归插入节点(辅助方法) */ TreeNode? insertHelper(TreeNode? node, int val) { if (node == null) return TreeNode(val); /* 1. 查找插入位置并插入节点 */ if (val < node.val) node.left = insertHelper(node.left, val); else if (val > node.val) node.right = insertHelper(node.right, val); else return node; // 重复节点不插入,直接返回 updateHeight(node); // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = rotate(node); // 返回子树的根节点 return node; } /* 删除节点 */ void remove(int val) { root = removeHelper(root, val); } /* 递归删除节点(辅助方法) */ TreeNode? removeHelper(TreeNode? node, int val) { if (node == null) return null; /* 1. 查找节点并删除 */ if (val < node.val) node.left = removeHelper(node.left, val); else if (val > node.val) node.right = removeHelper(node.right, val); else { if (node.left == null || node.right == null) { TreeNode? child = node.left ?? node.right; // 子节点数量 = 0 ,直接删除 node 并返回 if (child == null) return null; // 子节点数量 = 1 ,直接删除 node else node = child; } else { // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 TreeNode? temp = node.right; while (temp!.left != null) { temp = temp.left; } node.right = removeHelper(node.right, temp.val); node.val = temp.val; } } updateHeight(node); // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = rotate(node); // 返回子树的根节点 return node; } /* 查找节点 */ TreeNode? search(int val) { TreeNode? cur = root; // 循环查找,越过叶节点后跳出 while (cur != null) { // 目标节点在 cur 的右子树中 if (val < cur.val) cur = cur.left; // 目标节点在 cur 的左子树中 else if (val > cur.val) cur = cur.right; // 目标节点与当前节点相等 else break; } return cur; } } void testInsert(AVLTree tree, int val) { tree.insert(val); print("\n插入节点 $val 后,AVL 树为"); printTree(tree.root); } void testRemove(AVLTree tree, int val) { tree.remove(val); print("\n删除节点 $val 后,AVL 树为"); printTree(tree.root); } /* Driver Code */ void main() { /* 初始化空 AVL 树 */ AVLTree avlTree = AVLTree(); /* 插入节点 */ // 请关注插入节点后,AVL 树是如何保持平衡的 testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); testInsert(avlTree, 4); testInsert(avlTree, 5); testInsert(avlTree, 8); testInsert(avlTree, 7); testInsert(avlTree, 9); testInsert(avlTree, 10); testInsert(avlTree, 6); /* 插入重复节点 */ testInsert(avlTree, 7); /* 删除节点 */ // 请关注删除节点后,AVL 树是如何保持平衡的 testRemove(avlTree, 8); // 删除度为 0 的节点 testRemove(avlTree, 5); // 删除度为 1 的节点 testRemove(avlTree, 4); // 删除度为 2 的节点 /* 查询节点 */ TreeNode? node = avlTree.search(7); print("\n查找到的节点对象为 $node ,节点值 = ${node!.val}"); } ================================================ FILE: codes/dart/chapter_tree/binary_search_tree.dart ================================================ /** * File: binary_search_tree.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 二叉搜索树 */ class BinarySearchTree { late TreeNode? _root; /* 构造方法 */ BinarySearchTree() { // 初始化空树 _root = null; } /* 获取二叉树的根节点 */ TreeNode? getRoot() { return _root; } /* 查找节点 */ TreeNode? search(int _num) { TreeNode? cur = _root; // 循环查找,越过叶节点后跳出 while (cur != null) { // 目标节点在 cur 的右子树中 if (cur.val < _num) cur = cur.right; // 目标节点在 cur 的左子树中 else if (cur.val > _num) cur = cur.left; // 找到目标节点,跳出循环 else break; } // 返回目标节点 return cur; } /* 插入节点 */ void insert(int _num) { // 若树为空,则初始化根节点 if (_root == null) { _root = TreeNode(_num); return; } TreeNode? cur = _root; TreeNode? pre = null; // 循环查找,越过叶节点后跳出 while (cur != null) { // 找到重复节点,直接返回 if (cur.val == _num) return; pre = cur; // 插入位置在 cur 的右子树中 if (cur.val < _num) cur = cur.right; // 插入位置在 cur 的左子树中 else cur = cur.left; } // 插入节点 TreeNode? node = TreeNode(_num); if (pre!.val < _num) pre.right = node; else pre.left = node; } /* 删除节点 */ void remove(int _num) { // 若树为空,直接提前返回 if (_root == null) return; TreeNode? cur = _root; TreeNode? pre = null; // 循环查找,越过叶节点后跳出 while (cur != null) { // 找到待删除节点,跳出循环 if (cur.val == _num) break; pre = cur; // 待删除节点在 cur 的右子树中 if (cur.val < _num) cur = cur.right; // 待删除节点在 cur 的左子树中 else cur = cur.left; } // 若无待删除节点,直接返回 if (cur == null) return; // 子节点数量 = 0 or 1 if (cur.left == null || cur.right == null) { // 当子节点数量 = 0 / 1 时, child = null / 该子节点 TreeNode? child = cur.left ?? cur.right; // 删除节点 cur if (cur != _root) { if (pre!.left == cur) pre.left = child; else pre.right = child; } else { // 若删除节点为根节点,则重新指定根节点 _root = child; } } else { // 子节点数量 = 2 // 获取中序遍历中 cur 的下一个节点 TreeNode? tmp = cur.right; while (tmp!.left != null) { tmp = tmp.left; } // 递归删除节点 tmp remove(tmp.val); // 用 tmp 覆盖 cur cur.val = tmp.val; } } } /* Driver Code */ void main() { /* 初始化二叉搜索树 */ BinarySearchTree bst = BinarySearchTree(); // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 List nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (int _num in nums) { bst.insert(_num); } print("\n初始化的二叉树为\n"); printTree(bst.getRoot()); /* 查找节点 */ TreeNode? node = bst.search(7); print("\n查找到的节点对象为 $node ,节点值 = ${node?.val}"); /* 插入节点 */ bst.insert(16); print("\n插入节点 16 后,二叉树为\n"); printTree(bst.getRoot()); /* 删除节点 */ bst.remove(1); print("\n删除节点 1 后,二叉树为\n"); printTree(bst.getRoot()); bst.remove(2); print("\n删除节点 2 后,二叉树为\n"); printTree(bst.getRoot()); bst.remove(4); print("\n删除节点 4 后,二叉树为\n"); printTree(bst.getRoot()); } ================================================ FILE: codes/dart/chapter_tree/binary_tree.dart ================================================ /** * File: binary_tree.dart * Created Time: 2023-04-03 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; void main() { /* 初始化二叉树 */ // 舒适化节点 TreeNode n1 = TreeNode(1); TreeNode n2 = TreeNode(2); TreeNode n3 = TreeNode(3); TreeNode n4 = TreeNode(4); TreeNode n5 = TreeNode(5); // 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; print("\n初始化二叉树\n"); printTree(n1); /* 插入与删除节点 */ TreeNode p = TreeNode(0); // 在 n1 -> n2 中间插入节点 p n1.left = p; p.left = n2; print("\n插入节点 P 后\n"); printTree(n1); // 删除节点 P n1.left = n2; print("\n删除节点 P 后\n"); printTree(n1); } ================================================ FILE: codes/dart/chapter_tree/binary_tree_bfs.dart ================================================ /** * File: binary_tree_bfs.dart * Created Time: 2023-04-03 * Author: liuyuxin (gvenusleo@gmai.com) */ import 'dart:collection'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 层序遍历 */ List levelOrder(TreeNode? root) { // 初始化队列,加入根节点 Queue queue = Queue(); queue.add(root); // 初始化一个列表,用于保存遍历序列 List res = []; while (queue.isNotEmpty) { TreeNode? node = queue.removeFirst(); // 队列出队 res.add(node!.val); // 保存节点值 if (node.left != null) queue.add(node.left); // 左子节点入队 if (node.right != null) queue.add(node.right); // 右子节点入队 } return res; } /* Driver Code */ void main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); print("\n初始化二叉树\n"); printTree(root); // 层序遍历 List res = levelOrder(root); print("\n层序遍历的节点打印序列 = $res"); } ================================================ FILE: codes/dart/chapter_tree/binary_tree_dfs.dart ================================================ /** * File: binary_tree_dfs.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; // 初始化列表,用于存储遍历序列 List list = []; /* 前序遍历 */ void preOrder(TreeNode? node) { if (node == null) return; // 访问优先级:根节点 -> 左子树 -> 右子树 list.add(node.val); preOrder(node.left); preOrder(node.right); } /* 中序遍历 */ void inOrder(TreeNode? node) { if (node == null) return; // 访问优先级:左子树 -> 根节点 -> 右子树 inOrder(node.left); list.add(node.val); inOrder(node.right); } /* 后序遍历 */ void postOrder(TreeNode? node) { if (node == null) return; // 访问优先级:左子树 -> 右子树 -> 根节点 postOrder(node.left); postOrder(node.right); list.add(node.val); } /* Driver Code */ void main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); print("\n初始化二叉树\n"); printTree(root); /* 前序遍历 */ list.clear(); preOrder(root); print("\n前序遍历的节点打印序列 = $list"); /* 中序遍历 */ list.clear(); inOrder(root); print("\n中序遍历的节点打印序列 = $list"); /* 后序遍历 */ list.clear(); postOrder(root); print("\n后序遍历的节点打印序列 = $list"); } ================================================ FILE: codes/dart/utils/list_node.dart ================================================ /** * File: list_node.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 链表节点 */ class ListNode { int val; ListNode? next; ListNode(this.val, [this.next]); } /* 将列表反序列化为链表 */ ListNode? listToLinkedList(List list) { ListNode dum = ListNode(0); ListNode? head = dum; for (int val in list) { head?.next = ListNode(val); head = head?.next; } return dum.next; } ================================================ FILE: codes/dart/utils/print_util.dart ================================================ /** * File: print_util.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:io'; import 'list_node.dart'; import 'tree_node.dart'; class Trunk { Trunk? prev; String str; Trunk(this.prev, this.str); } /* 打印矩阵 (Array) */ void printMatrix(List> matrix) { print("["); for (List row in matrix) { print(" $row,"); } print("]"); } /* 打印链表 */ void printLinkedList(ListNode? head) { List list = []; while (head != null) { list.add('${head.val}'); head = head.next; } print(list.join(' -> ')); } /** * 打印二叉树 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTree(TreeNode? root, [Trunk? prev = null, bool isRight = false]) { if (root == null) { return; } String prev_str = ' '; Trunk trunk = Trunk(prev, prev_str); printTree(root.right, trunk, true); if (prev == null) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); print(' ${root.val}'); if (prev != null) { prev.str = prev_str; } trunk.str = ' |'; printTree(root.left, trunk, false); } void showTrunks(Trunk? p) { if (p == null) { return; } showTrunks(p.prev); stdout.write(p.str); } /* 打印堆 */ void printHeap(List heap) { print("堆的数组表示:$heap"); print("堆的树状表示:"); TreeNode? root = listToTree(heap); printTree(root); } ================================================ FILE: codes/dart/utils/tree_node.dart ================================================ /** * File: tree_node.dart * Created Time: 2023-2-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 二叉树节点类 */ class TreeNode { int val; // 节点值 int height; // 节点高度 TreeNode? left; // 左子节点引用 TreeNode? right; // 右子节点引用 /* 构造方法 */ TreeNode(this.val, [this.height = 0, this.left, this.right]); } /* 将列表反序列化为二叉树:递归 */ TreeNode? listToTreeDFS(List arr, int i) { if (i < 0 || i >= arr.length || arr[i] == null) { return null; } TreeNode? root = TreeNode(arr[i]!); root.left = listToTreeDFS(arr, 2 * i + 1); root.right = listToTreeDFS(arr, 2 * i + 2); return root; } /* 将列表反序列化为二叉树 */ TreeNode? listToTree(List arr) { return listToTreeDFS(arr, 0); } /* 将二叉树序列化为列表:递归 */ void treeToListDFS(TreeNode? root, int i, List res) { if (root == null) return; while (i >= res.length) { res.add(null); } res[i] = root.val; treeToListDFS(root.left, 2 * i + 1, res); treeToListDFS(root.right, 2 * i + 2, res); } /* 将二叉树序列化为列表 */ List treeToList(TreeNode? root) { List res = []; treeToListDFS(root, 0, res); return res; } ================================================ FILE: codes/dart/utils/vertex.dart ================================================ /** * File: Vertex.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 顶点类 */ class Vertex { int val; Vertex(this.val); /* 输入值列表 vals ,返回顶点列表 vets */ static List valsToVets(List vals) { List vets = []; for (int i in vals) { vets.add(Vertex(i)); } return vets; } /* 输入顶点列表 vets ,返回值列表 vals */ static List vetsToVals(List vets) { List vals = []; for (Vertex vet in vets) { vals.add(vet.val); } return vals; } } ================================================ FILE: codes/docker-compose.yml ================================================ version: '3.8' services: hello-algo-code: build: context: . args: # 设置需要安装的语言,使用空格隔开 # Set the languages to be installed, separated by spaces LANGS: "python cpp java csharp" image: hello-algo-code container_name: hello-algo-code stdin_open: true tty: true ================================================ FILE: codes/go/chapter_array_and_linkedlist/array.go ================================================ // File: array.go // Created Time: 2022-12-29 // Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( "math/rand" ) /* 随机访问元素 */ func randomAccess(nums []int) (randomNum int) { // 在区间 [0, nums.length) 中随机抽取一个数字 randomIndex := rand.Intn(len(nums)) // 获取并返回随机元素 randomNum = nums[randomIndex] return } /* 扩展数组长度 */ func extend(nums []int, enlarge int) []int { // 初始化一个扩展长度后的数组 res := make([]int, len(nums)+enlarge) // 将原数组中的所有元素复制到新数组 for i, num := range nums { res[i] = num } // 返回扩展后的新数组 return res } /* 在数组的索引 index 处插入元素 num */ func insert(nums []int, num int, index int) { // 把索引 index 以及之后的所有元素向后移动一位 for i := len(nums) - 1; i > index; i-- { nums[i] = nums[i-1] } // 将 num 赋给 index 处的元素 nums[index] = num } /* 删除索引 index 处的元素 */ func remove(nums []int, index int) { // 把索引 index 之后的所有元素向前移动一位 for i := index; i < len(nums)-1; i++ { nums[i] = nums[i+1] } } /* 遍历数组 */ func traverse(nums []int) { count := 0 // 通过索引遍历数组 for i := 0; i < len(nums); i++ { count += nums[i] } count = 0 // 直接遍历数组元素 for _, num := range nums { count += num } // 同时遍历数据索引和元素 for i, num := range nums { count += nums[i] count += num } } /* 在数组中查找指定元素 */ func find(nums []int, target int) (index int) { index = -1 for i := 0; i < len(nums); i++ { if nums[i] == target { index = i break } } return } ================================================ FILE: codes/go/chapter_array_and_linkedlist/array_test.go ================================================ // File: array_test.go // Created Time: 2022-12-29 // Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist /** 我们将 Go 中的 Slice 切片看作 Array 数组。因为这样可以 降低理解成本,利于我们将关注点放在数据结构与算法上。 */ import ( "fmt" "testing" ) /* Driver Code */ func TestArray(t *testing.T) { /* 初始化数组 */ var arr [5]int fmt.Println("数组 arr =", arr) // 在 Go 中,指定长度时([5]int)为数组,不指定长度时([]int)为切片 // 由于 Go 的数组被设计为在编译期确定长度,因此只能使用常量来指定长度 // 为了方便实现扩容 extend() 函数,以下将切片(Slice)看作数组(Array) nums := []int{1, 3, 2, 5, 4} fmt.Println("数组 nums =", nums) /* 随机访问 */ randomNum := randomAccess(nums) fmt.Println("在 nums 中获取随机元素", randomNum) /* 长度扩展 */ nums = extend(nums, 3) fmt.Println("将数组长度扩展至 8 ,得到 nums =", nums) /* 插入元素 */ insert(nums, 6, 3) fmt.Println("在索引 3 处插入数字 6 ,得到 nums =", nums) /* 删除元素 */ remove(nums, 2) fmt.Println("删除索引 2 处的元素,得到 nums =", nums) /* 遍历数组 */ traverse(nums) /* 查找元素 */ index := find(nums, 3) fmt.Println("在 nums 中查找元素 3 ,得到索引 =", index) } ================================================ FILE: codes/go/chapter_array_and_linkedlist/linked_list.go ================================================ // File: linked_list.go // Created Time: 2022-12-29 // Author: cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( . "github.com/krahets/hello-algo/pkg" ) /* 在链表的节点 n0 之后插入节点 P */ func insertNode(n0 *ListNode, P *ListNode) { n1 := n0.Next P.Next = n1 n0.Next = P } /* 删除链表的节点 n0 之后的首个节点 */ func removeItem(n0 *ListNode) { if n0.Next == nil { return } // n0 -> P -> n1 P := n0.Next n1 := P.Next n0.Next = n1 } /* 访问链表中索引为 index 的节点 */ func access(head *ListNode, index int) *ListNode { for i := 0; i < index; i++ { if head == nil { return nil } head = head.Next } return head } /* 在链表中查找值为 target 的首个节点 */ func findNode(head *ListNode, target int) int { index := 0 for head != nil { if head.Val == target { return index } head = head.Next index++ } return -1 } ================================================ FILE: codes/go/chapter_array_and_linkedlist/linked_list_test.go ================================================ // File: linked_list_test.go // Created Time: 2022-12-29 // Author: cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLinkedList(t *testing.T) { /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个节点 n0 := NewListNode(1) n1 := NewListNode(3) n2 := NewListNode(2) n3 := NewListNode(5) n4 := NewListNode(4) // 构建节点之间的引用 n0.Next = n1 n1.Next = n2 n2.Next = n3 n3.Next = n4 fmt.Println("初始化的链表为") PrintLinkedList(n0) /* 插入节点 */ insertNode(n0, NewListNode(0)) fmt.Println("插入节点后的链表为") PrintLinkedList(n0) /* 删除节点 */ removeItem(n0) fmt.Println("删除节点后的链表为") PrintLinkedList(n0) /* 访问节点 */ node := access(n0, 3) fmt.Println("链表中索引 3 处的节点的值 =", node) /* 查找节点 */ index := findNode(n0, 2) fmt.Println("链表中值为 2 的节点的索引 =", index) } ================================================ FILE: codes/go/chapter_array_and_linkedlist/list_test.go ================================================ // File: list_test.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "sort" "testing" ) /* Driver Code */ func TestList(t *testing.T) { /* 初始化列表 */ nums := []int{1, 3, 2, 5, 4} fmt.Println("列表 nums =", nums) /* 访问元素 */ num := nums[1] // 访问索引 1 处的元素 fmt.Println("访问索引 1 处的元素,得到 num =", num) /* 更新元素 */ nums[1] = 0 // 将索引 1 处的元素更新为 0 fmt.Println("将索引 1 处的元素更新为 0 ,得到 nums =", nums) /* 清空列表 */ nums = nil fmt.Println("清空列表后 nums =", nums) /* 在尾部添加元素 */ nums = append(nums, 1) nums = append(nums, 3) nums = append(nums, 2) nums = append(nums, 5) nums = append(nums, 4) fmt.Println("添加元素后 nums =", nums) /* 在中间插入元素 */ nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // 在索引 3 处插入数字 6 fmt.Println("在索引 3 处插入数字 6 ,得到 nums =", nums) /* 删除元素 */ nums = append(nums[:3], nums[4:]...) // 删除索引 3 处的元素 fmt.Println("删除索引 3 处的元素,得到 nums =", nums) /* 通过索引遍历列表 */ count := 0 for i := 0; i < len(nums); i++ { count += nums[i] } /* 直接遍历列表元素 */ count = 0 for _, x := range nums { count += x } /* 拼接两个列表 */ nums1 := []int{6, 8, 7, 10, 9} nums = append(nums, nums1...) // 将列表 nums1 拼接到 nums 之后 fmt.Println("将列表 nums1 拼接到 nums 之后,得到 nums =", nums) /* 排序列表 */ sort.Ints(nums) // 排序后,列表元素从小到大排列 fmt.Println("排序列表后 nums =", nums) } ================================================ FILE: codes/go/chapter_array_and_linkedlist/my_list.go ================================================ // File: my_list.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist /* 列表类 */ type myList struct { arrCapacity int arr []int arrSize int extendRatio int } /* 构造函数 */ func newMyList() *myList { return &myList{ arrCapacity: 10, // 列表容量 arr: make([]int, 10), // 数组(存储列表元素) arrSize: 0, // 列表长度(当前元素数量) extendRatio: 2, // 每次列表扩容的倍数 } } /* 获取列表长度(当前元素数量) */ func (l *myList) size() int { return l.arrSize } /* 获取列表容量 */ func (l *myList) capacity() int { return l.arrCapacity } /* 访问元素 */ func (l *myList) get(index int) int { // 索引如果越界,则抛出异常,下同 if index < 0 || index >= l.arrSize { panic("索引越界") } return l.arr[index] } /* 更新元素 */ func (l *myList) set(num, index int) { if index < 0 || index >= l.arrSize { panic("索引越界") } l.arr[index] = num } /* 在尾部添加元素 */ func (l *myList) add(num int) { // 元素数量超出容量时,触发扩容机制 if l.arrSize == l.arrCapacity { l.extendCapacity() } l.arr[l.arrSize] = num // 更新元素数量 l.arrSize++ } /* 在中间插入元素 */ func (l *myList) insert(num, index int) { if index < 0 || index >= l.arrSize { panic("索引越界") } // 元素数量超出容量时,触发扩容机制 if l.arrSize == l.arrCapacity { l.extendCapacity() } // 将索引 index 以及之后的元素都向后移动一位 for j := l.arrSize - 1; j >= index; j-- { l.arr[j+1] = l.arr[j] } l.arr[index] = num // 更新元素数量 l.arrSize++ } /* 删除元素 */ func (l *myList) remove(index int) int { if index < 0 || index >= l.arrSize { panic("索引越界") } num := l.arr[index] // 将索引 index 之后的元素都向前移动一位 for j := index; j < l.arrSize-1; j++ { l.arr[j] = l.arr[j+1] } // 更新元素数量 l.arrSize-- // 返回被删除的元素 return num } /* 列表扩容 */ func (l *myList) extendCapacity() { // 新建一个长度为原数组 extendRatio 倍的新数组,并将原数组复制到新数组 l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...) // 更新列表容量 l.arrCapacity = len(l.arr) } /* 返回有效长度的列表 */ func (l *myList) toArray() []int { // 仅转换有效长度范围内的列表元素 return l.arr[:l.arrSize] } ================================================ FILE: codes/go/chapter_array_and_linkedlist/my_list_test.go ================================================ // File: my_list_test.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "testing" ) /* Driver Code */ func TestMyList(t *testing.T) { /* 初始化列表 */ nums := newMyList() /* 在尾部添加元素 */ nums.add(1) nums.add(3) nums.add(2) nums.add(5) nums.add(4) fmt.Printf("列表 nums = %v ,容量 = %v ,长度 = %v\n", nums.toArray(), nums.capacity(), nums.size()) /* 在中间插入元素 */ nums.insert(6, 3) fmt.Printf("在索引 3 处插入数字 6 ,得到 nums = %v\n", nums.toArray()) /* 删除元素 */ nums.remove(3) fmt.Printf("删除索引 3 处的元素,得到 nums = %v\n", nums.toArray()) /* 访问元素 */ num := nums.get(1) fmt.Printf("访问索引 1 处的元素,得到 num = %v\n", num) /* 更新元素 */ nums.set(0, 1) fmt.Printf("将索引 1 处的元素更新为 0 ,得到 nums = %v\n", nums.toArray()) /* 测试扩容机制 */ for i := 0; i < 10; i++ { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 nums.add(i) } fmt.Printf("扩容后的列表 nums = %v ,容量 = %v ,长度 = %v\n", nums.toArray(), nums.capacity(), nums.size()) } ================================================ FILE: codes/go/chapter_backtracking/n_queens.go ================================================ // File: n_queens.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* 回溯算法:n 皇后 */ func backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) { // 当放置完所有行时,记录解 if row == n { newState := make([][]string, len(*state)) for i, _ := range newState { newState[i] = make([]string, len((*state)[0])) copy(newState[i], (*state)[i]) } *res = append(*res, newState) return } // 遍历所有列 for col := 0; col < n; col++ { // 计算该格子对应的主对角线和次对角线 diag1 := row - col + n - 1 diag2 := row + col // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] { // 尝试:将皇后放置在该格子 (*state)[row][col] = "Q" (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true // 放置下一行 backtrack(row+1, n, state, res, cols, diags1, diags2) // 回退:将该格子恢复为空位 (*state)[row][col] = "#" (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false } } } /* 求解 n 皇后 */ func nQueens(n int) [][][]string { // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 state := make([][]string, n) for i := 0; i < n; i++ { row := make([]string, n) for i := 0; i < n; i++ { row[i] = "#" } state[i] = row } // 记录列是否有皇后 cols := make([]bool, n) diags1 := make([]bool, 2*n-1) diags2 := make([]bool, 2*n-1) res := make([][][]string, 0) backtrack(0, n, &state, &res, &cols, &diags1, &diags2) return res } ================================================ FILE: codes/go/chapter_backtracking/n_queens_test.go ================================================ // File: n_queens_test.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" ) func TestNQueens(t *testing.T) { n := 4 res := nQueens(n) fmt.Println("输入棋盘长宽为 ", n) fmt.Println("皇后放置方案共有 ", len(res), " 种") for _, state := range res { fmt.Println("--------------------") for _, row := range state { fmt.Println(row) } } } ================================================ FILE: codes/go/chapter_backtracking/permutation_test.go ================================================ // File: permutation_test.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPermutationI(t *testing.T) { /* 全排列 I */ nums := []int{1, 2, 3} fmt.Printf("输入数组 nums = ") PrintSlice(nums) res := permutationsI(nums) fmt.Printf("所有排列 res = ") fmt.Println(res) } func TestPermutationII(t *testing.T) { nums := []int{1, 2, 2} fmt.Printf("输入数组 nums = ") PrintSlice(nums) res := permutationsII(nums) fmt.Printf("所有排列 res = ") fmt.Println(res) } ================================================ FILE: codes/go/chapter_backtracking/permutations_i.go ================================================ // File: permutations_i.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* 回溯算法:全排列 I */ func backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // 当状态长度等于元素数量时,记录解 if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // 遍历所有选择 for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // 剪枝:不允许重复选择元素 if !(*selected)[i] { // 尝试:做出选择,更新状态 (*selected)[i] = true *state = append(*state, choice) // 进行下一轮选择 backtrackI(state, choices, selected, res) // 回退:撤销选择,恢复到之前的状态 (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* 全排列 I */ func permutationsI(nums []int) [][]int { res := make([][]int, 0) state := make([]int, 0) selected := make([]bool, len(nums)) backtrackI(&state, &nums, &selected, &res) return res } ================================================ FILE: codes/go/chapter_backtracking/permutations_ii.go ================================================ // File: permutations_ii.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* 回溯算法:全排列 II */ func backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // 当状态长度等于元素数量时,记录解 if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // 遍历所有选择 duplicated := make(map[int]struct{}, 0) for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 if _, ok := duplicated[choice]; !ok && !(*selected)[i] { // 尝试:做出选择,更新状态 // 记录选择过的元素值 duplicated[choice] = struct{}{} (*selected)[i] = true *state = append(*state, choice) // 进行下一轮选择 backtrackII(state, choices, selected, res) // 回退:撤销选择,恢复到之前的状态 (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* 全排列 II */ func permutationsII(nums []int) [][]int { res := make([][]int, 0) state := make([]int, 0) selected := make([]bool, len(nums)) backtrackII(&state, &nums, &selected, &res) return res } ================================================ FILE: codes/go/chapter_backtracking/preorder_traversal_i_compact.go ================================================ // File: preorder_traversal_i_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* 前序遍历:例题一 */ func preOrderI(root *TreeNode, res *[]*TreeNode) { if root == nil { return } if (root.Val).(int) == 7 { // 记录解 *res = append(*res, root) } preOrderI(root.Left, res) preOrderI(root.Right, res) } ================================================ FILE: codes/go/chapter_backtracking/preorder_traversal_ii_compact.go ================================================ // File: preorder_traversal_ii_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* 前序遍历:例题二 */ func preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { if root == nil { return } // 尝试 *path = append(*path, root) if root.Val.(int) == 7 { // 记录解 *res = append(*res, append([]*TreeNode{}, *path...)) } preOrderII(root.Left, res, path) preOrderII(root.Right, res, path) // 回退 *path = (*path)[:len(*path)-1] } ================================================ FILE: codes/go/chapter_backtracking/preorder_traversal_iii_compact.go ================================================ // File: preorder_traversal_iii_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* 前序遍历:例题三 */ func preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { // 剪枝 if root == nil || root.Val == 3 { return } // 尝试 *path = append(*path, root) if root.Val.(int) == 7 { // 记录解 *res = append(*res, append([]*TreeNode{}, *path...)) } preOrderIII(root.Left, res, path) preOrderIII(root.Right, res, path) // 回退 *path = (*path)[:len(*path)-1] } ================================================ FILE: codes/go/chapter_backtracking/preorder_traversal_iii_template.go ================================================ // File: preorder_traversal_iii_template.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* 判断当前状态是否为解 */ func isSolution(state *[]*TreeNode) bool { return len(*state) != 0 && (*state)[len(*state)-1].Val == 7 } /* 记录解 */ func recordSolution(state *[]*TreeNode, res *[][]*TreeNode) { *res = append(*res, append([]*TreeNode{}, *state...)) } /* 判断在当前状态下,该选择是否合法 */ func isValid(state *[]*TreeNode, choice *TreeNode) bool { return choice != nil && choice.Val != 3 } /* 更新状态 */ func makeChoice(state *[]*TreeNode, choice *TreeNode) { *state = append(*state, choice) } /* 恢复状态 */ func undoChoice(state *[]*TreeNode, choice *TreeNode) { *state = (*state)[:len(*state)-1] } /* 回溯算法:例题三 */ func backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) { // 检查是否为解 if isSolution(state) { // 记录解 recordSolution(state, res) } // 遍历所有选择 for _, choice := range *choices { // 剪枝:检查选择是否合法 if isValid(state, choice) { // 尝试:做出选择,更新状态 makeChoice(state, choice) // 进行下一轮选择 temp := make([]*TreeNode, 0) temp = append(temp, choice.Left, choice.Right) backtrackIII(state, &temp, res) // 回退:撤销选择,恢复到之前的状态 undoChoice(state, choice) } } } ================================================ FILE: codes/go/chapter_backtracking/preorder_traversal_test.go ================================================ // File: preorder_traversal_i_compact_test.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPreorderTraversalICompact(t *testing.T) { /* 初始化二叉树 */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二叉树") PrintTree(root) // 前序遍历 res := make([]*TreeNode, 0) preOrderI(root, &res) fmt.Println("\n输出所有值为 7 的节点") for _, node := range res { fmt.Printf("%v ", node.Val) } fmt.Println() } func TestPreorderTraversalIICompact(t *testing.T) { /* 初始化二叉树 */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二叉树") PrintTree(root) // 前序遍历 path := make([]*TreeNode, 0) res := make([][]*TreeNode, 0) preOrderII(root, &res, &path) fmt.Println("\n输出所有根节点到节点 7 的路径") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } func TestPreorderTraversalIIICompact(t *testing.T) { /* 初始化二叉树 */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二叉树") PrintTree(root) // 前序遍历 path := make([]*TreeNode, 0) res := make([][]*TreeNode, 0) preOrderIII(root, &res, &path) fmt.Println("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } func TestPreorderTraversalIIITemplate(t *testing.T) { /* 初始化二叉树 */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二叉树") PrintTree(root) // 回溯算法 res := make([][]*TreeNode, 0) state := make([]*TreeNode, 0) choices := make([]*TreeNode, 0) choices = append(choices, root) backtrackIII(&state, &choices, &res) fmt.Println("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } ================================================ FILE: codes/go/chapter_backtracking/subset_sum_i.go ================================================ // File: subset_sum_i.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import "sort" /* 回溯算法:子集和 I */ func backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) { // 子集和等于 target 时,记录解 if target == 0 { newState := append([]int{}, *state...) *res = append(*res, newState) return } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 for i := start; i < len(*choices); i++ { // 剪枝一:若子集和超过 target ,则直接结束循环 // 这是因为数组已排序,后边元素更大,子集和一定超过 target if target-(*choices)[i] < 0 { break } // 尝试:做出选择,更新 target, start *state = append(*state, (*choices)[i]) // 进行下一轮选择 backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res) // 回退:撤销选择,恢复到之前的状态 *state = (*state)[:len(*state)-1] } } /* 求解子集和 I */ func subsetSumI(nums []int, target int) [][]int { state := make([]int, 0) // 状态(子集) sort.Ints(nums) // 对 nums 进行排序 start := 0 // 遍历起始点 res := make([][]int, 0) // 结果列表(子集列表) backtrackSubsetSumI(start, target, &state, &nums, &res) return res } ================================================ FILE: codes/go/chapter_backtracking/subset_sum_i_naive.go ================================================ // File: subset_sum_i_naive.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* 回溯算法:子集和 I */ func backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) { // 子集和等于 target 时,记录解 if target == total { newState := append([]int{}, *state...) *res = append(*res, newState) return } // 遍历所有选择 for i := 0; i < len(*choices); i++ { // 剪枝:若子集和超过 target ,则跳过该选择 if total+(*choices)[i] > target { continue } // 尝试:做出选择,更新元素和 total *state = append(*state, (*choices)[i]) // 进行下一轮选择 backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res) // 回退:撤销选择,恢复到之前的状态 *state = (*state)[:len(*state)-1] } } /* 求解子集和 I(包含重复子集) */ func subsetSumINaive(nums []int, target int) [][]int { state := make([]int, 0) // 状态(子集) total := 0 // 子集和 res := make([][]int, 0) // 结果列表(子集列表) backtrackSubsetSumINaive(total, target, &state, &nums, &res) return res } ================================================ FILE: codes/go/chapter_backtracking/subset_sum_ii.go ================================================ // File: subset_sum_ii.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import "sort" /* 回溯算法:子集和 II */ func backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) { // 子集和等于 target 时,记录解 if target == 0 { newState := append([]int{}, *state...) *res = append(*res, newState) return } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 // 剪枝三:从 start 开始遍历,避免重复选择同一元素 for i := start; i < len(*choices); i++ { // 剪枝一:若子集和超过 target ,则直接结束循环 // 这是因为数组已排序,后边元素更大,子集和一定超过 target if target-(*choices)[i] < 0 { break } // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 if i > start && (*choices)[i] == (*choices)[i-1] { continue } // 尝试:做出选择,更新 target, start *state = append(*state, (*choices)[i]) // 进行下一轮选择 backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res) // 回退:撤销选择,恢复到之前的状态 *state = (*state)[:len(*state)-1] } } /* 求解子集和 II */ func subsetSumII(nums []int, target int) [][]int { state := make([]int, 0) // 状态(子集) sort.Ints(nums) // 对 nums 进行排序 start := 0 // 遍历起始点 res := make([][]int, 0) // 结果列表(子集列表) backtrackSubsetSumII(start, target, &state, &nums, &res) return res } ================================================ FILE: codes/go/chapter_backtracking/subset_sum_test.go ================================================ // File: subset_sum_test.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestSubsetSumINaive(t *testing.T) { nums := []int{3, 4, 5} target := 9 res := subsetSumINaive(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", 输入数组 nums = ") PrintSlice(nums) fmt.Println("所有和等于 " + strconv.Itoa(target) + " 的子集 res = ") for i := range res { PrintSlice(res[i]) } fmt.Println("请注意,该方法输出的结果包含重复集合") } func TestSubsetSumI(t *testing.T) { nums := []int{3, 4, 5} target := 9 res := subsetSumI(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", 输入数组 nums = ") PrintSlice(nums) fmt.Println("所有和等于 " + strconv.Itoa(target) + " 的子集 res = ") for i := range res { PrintSlice(res[i]) } } func TestSubsetSumII(t *testing.T) { nums := []int{4, 4, 5} target := 9 res := subsetSumII(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", 输入数组 nums = ") PrintSlice(nums) fmt.Println("所有和等于 " + strconv.Itoa(target) + " 的子集 res = ") for i := range res { PrintSlice(res[i]) } } ================================================ FILE: codes/go/chapter_computational_complexity/iteration.go ================================================ // File: iteration.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import "fmt" /* for 循环 */ func forLoop(n int) int { res := 0 // 循环求和 1, 2, ..., n-1, n for i := 1; i <= n; i++ { res += i } return res } /* while 循环 */ func whileLoop(n int) int { res := 0 // 初始化条件变量 i := 1 // 循环求和 1, 2, ..., n-1, n for i <= n { res += i // 更新条件变量 i++ } return res } /* while 循环(两次更新) */ func whileLoopII(n int) int { res := 0 // 初始化条件变量 i := 1 // 循环求和 1, 4, 10, ... for i <= n { res += i // 更新条件变量 i++ i *= 2 } return res } /* 双层 for 循环 */ func nestedForLoop(n int) string { res := "" // 循环 i = 1, 2, ..., n-1, n for i := 1; i <= n; i++ { for j := 1; j <= n; j++ { // 循环 j = 1, 2, ..., n-1, n res += fmt.Sprintf("(%d, %d), ", i, j) } } return res } ================================================ FILE: codes/go/chapter_computational_complexity/iteration_test.go ================================================ // File: iteration_test.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import ( "fmt" "testing" ) /* Driver Code */ func TestIteration(t *testing.T) { n := 5 res := forLoop(n) fmt.Println("\nfor 循环的求和结果 res = ", res) res = whileLoop(n) fmt.Println("\nwhile 循环的求和结果 res = ", res) res = whileLoopII(n) fmt.Println("\nwhile 循环(两次更新)求和结果 res = ", res) resStr := nestedForLoop(n) fmt.Println("\n双层 for 循环的遍历结果 ", resStr) } ================================================ FILE: codes/go/chapter_computational_complexity/recursion.go ================================================ // File: recursion.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import "container/list" /* 递归 */ func recur(n int) int { // 终止条件 if n == 1 { return 1 } // 递:递归调用 res := recur(n - 1) // 归:返回结果 return n + res } /* 使用迭代模拟递归 */ func forLoopRecur(n int) int { // 使用一个显式的栈来模拟系统调用栈 stack := list.New() res := 0 // 递:递归调用 for i := n; i > 0; i-- { // 通过“入栈操作”模拟“递” stack.PushBack(i) } // 归:返回结果 for stack.Len() != 0 { // 通过“出栈操作”模拟“归” res += stack.Back().Value.(int) stack.Remove(stack.Back()) } // res = 1+2+3+...+n return res } /* 尾递归 */ func tailRecur(n int, res int) int { // 终止条件 if n == 0 { return res } // 尾递归调用 return tailRecur(n-1, res+n) } /* 斐波那契数列:递归 */ func fib(n int) int { // 终止条件 f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1 } // 递归调用 f(n) = f(n-1) + f(n-2) res := fib(n-1) + fib(n-2) // 返回结果 f(n) return res } ================================================ FILE: codes/go/chapter_computational_complexity/recursion_test.go ================================================ // File: recursion_test.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import ( "fmt" "testing" ) /* Driver Code */ func TestRecursion(t *testing.T) { n := 5 res := recur(n) fmt.Println("\n递归函数的求和结果 res = ", res) res = forLoopRecur(n) fmt.Println("\n使用迭代模拟递归求和结果 res = ", res) res = tailRecur(n, 0) fmt.Println("\n尾递归函数的求和结果 res = ", res) res = fib(n) fmt.Println("\n斐波那契数列的第", n, "项为", res) } ================================================ FILE: codes/go/chapter_computational_complexity/space_complexity.go ================================================ // File: space_complexity.go // Created Time: 2022-12-15 // Author: cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "fmt" "strconv" . "github.com/krahets/hello-algo/pkg" ) /* 结构体 */ type node struct { val int next *node } /* 创建 node 结构体 */ func newNode(val int) *node { return &node{val: val} } /* 函数 */ func function() int { // 执行某些操作... return 0 } /* 常数阶 */ func spaceConstant(n int) { // 常量、变量、对象占用 O(1) 空间 const a = 0 b := 0 nums := make([]int, 10000) node := newNode(0) // 循环中的变量占用 O(1) 空间 var c int for i := 0; i < n; i++ { c = 0 } // 循环中的函数占用 O(1) 空间 for i := 0; i < n; i++ { function() } b += 0 c += 0 nums[0] = 0 node.val = 0 } /* 线性阶 */ func spaceLinear(n int) { // 长度为 n 的数组占用 O(n) 空间 _ = make([]int, n) // 长度为 n 的列表占用 O(n) 空间 var nodes []*node for i := 0; i < n; i++ { nodes = append(nodes, newNode(i)) } // 长度为 n 的哈希表占用 O(n) 空间 m := make(map[int]string, n) for i := 0; i < n; i++ { m[i] = strconv.Itoa(i) } } /* 线性阶(递归实现) */ func spaceLinearRecur(n int) { fmt.Println("递归 n =", n) if n == 1 { return } spaceLinearRecur(n - 1) } /* 平方阶 */ func spaceQuadratic(n int) { // 矩阵占用 O(n^2) 空间 numMatrix := make([][]int, n) for i := 0; i < n; i++ { numMatrix[i] = make([]int, n) } } /* 平方阶(递归实现) */ func spaceQuadraticRecur(n int) int { if n <= 0 { return 0 } nums := make([]int, n) fmt.Printf("递归 n = %d 中的 nums 长度 = %d \n", n, len(nums)) return spaceQuadraticRecur(n - 1) } /* 指数阶(建立满二叉树) */ func buildTree(n int) *TreeNode { if n == 0 { return nil } root := NewTreeNode(0) root.Left = buildTree(n - 1) root.Right = buildTree(n - 1) return root } ================================================ FILE: codes/go/chapter_computational_complexity/space_complexity_test.go ================================================ // File: space_complexity_test.go // Created Time: 2022-12-15 // Author: cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "testing" . "github.com/krahets/hello-algo/pkg" ) func TestSpaceComplexity(t *testing.T) { n := 5 // 常数阶 spaceConstant(n) // 线性阶 spaceLinear(n) spaceLinearRecur(n) // 平方阶 spaceQuadratic(n) spaceQuadraticRecur(n) // 指数阶 root := buildTree(n) PrintTree(root) } ================================================ FILE: codes/go/chapter_computational_complexity/time_complexity.go ================================================ // File: time_complexity.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_computational_complexity /* 常数阶 */ func constant(n int) int { count := 0 size := 100000 for i := 0; i < size; i++ { count++ } return count } /* 线性阶 */ func linear(n int) int { count := 0 for i := 0; i < n; i++ { count++ } return count } /* 线性阶(遍历数组) */ func arrayTraversal(nums []int) int { count := 0 // 循环次数与数组长度成正比 for range nums { count++ } return count } /* 平方阶 */ func quadratic(n int) int { count := 0 // 循环次数与数据大小 n 成平方关系 for i := 0; i < n; i++ { for j := 0; j < n; j++ { count++ } } return count } /* 平方阶(冒泡排序) */ func bubbleSort(nums []int) int { count := 0 // 计数器 // 外循环:未排序区间为 [0, i] for i := len(nums) - 1; i > 0; i-- { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // 交换 nums[j] 与 nums[j + 1] tmp := nums[j] nums[j] = nums[j+1] nums[j+1] = tmp count += 3 // 元素交换包含 3 个单元操作 } } } return count } /* 指数阶(循环实现)*/ func exponential(n int) int { count, base := 0, 1 // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for i := 0; i < n; i++ { for j := 0; j < base; j++ { count++ } base *= 2 } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count } /* 指数阶(递归实现)*/ func expRecur(n int) int { if n == 1 { return 1 } return expRecur(n-1) + expRecur(n-1) + 1 } /* 对数阶(循环实现)*/ func logarithmic(n int) int { count := 0 for n > 1 { n = n / 2 count++ } return count } /* 对数阶(递归实现)*/ func logRecur(n int) int { if n <= 1 { return 0 } return logRecur(n/2) + 1 } /* 线性对数阶 */ func linearLogRecur(n int) int { if n <= 1 { return 1 } count := linearLogRecur(n/2) + linearLogRecur(n/2) for i := 0; i < n; i++ { count++ } return count } /* 阶乘阶(递归实现) */ func factorialRecur(n int) int { if n == 0 { return 1 } count := 0 // 从 1 个分裂出 n 个 for i := 0; i < n; i++ { count += factorialRecur(n - 1) } return count } ================================================ FILE: codes/go/chapter_computational_complexity/time_complexity_test.go ================================================ // File: time_complexity_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_computational_complexity import ( "fmt" "testing" ) func TestTimeComplexity(t *testing.T) { n := 8 fmt.Println("输入数据大小 n =", n) count := constant(n) fmt.Println("常数阶的操作数量 =", count) count = linear(n) fmt.Println("线性阶的操作数量 =", count) count = arrayTraversal(make([]int, n)) fmt.Println("线性阶(遍历数组)的操作数量 =", count) count = quadratic(n) fmt.Println("平方阶的操作数量 =", count) nums := make([]int, n) for i := 0; i < n; i++ { nums[i] = n - i } count = bubbleSort(nums) fmt.Println("平方阶(冒泡排序)的操作数量 =", count) count = exponential(n) fmt.Println("指数阶(循环实现)的操作数量 =", count) count = expRecur(n) fmt.Println("指数阶(递归实现)的操作数量 =", count) count = logarithmic(n) fmt.Println("对数阶(循环实现)的操作数量 =", count) count = logRecur(n) fmt.Println("对数阶(递归实现)的操作数量 =", count) count = linearLogRecur(n) fmt.Println("线性对数阶(递归实现)的操作数量 =", count) count = factorialRecur(n) fmt.Println("阶乘阶(递归实现)的操作数量 =", count) } ================================================ FILE: codes/go/chapter_computational_complexity/worst_best_time_complexity.go ================================================ // File: worst_best_time_complexity.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "math/rand" ) /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ func randomNumbers(n int) []int { nums := make([]int, n) // 生成数组 nums = { 1, 2, 3, ..., n } for i := 0; i < n; i++ { nums[i] = i + 1 } // 随机打乱数组元素 rand.Shuffle(len(nums), func(i, j int) { nums[i], nums[j] = nums[j], nums[i] }) return nums } /* 查找数组 nums 中数字 1 所在索引 */ func findOne(nums []int) int { for i := 0; i < len(nums); i++ { // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if nums[i] == 1 { return i } } return -1 } ================================================ FILE: codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go ================================================ // File: worst_best_time_complexity_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "fmt" "testing" ) func TestWorstBestTimeComplexity(t *testing.T) { for i := 0; i < 10; i++ { n := 100 nums := randomNumbers(n) index := findOne(nums) fmt.Println("\n数组 [ 1, 2, ..., n ] 被打乱后 =", nums) fmt.Println("数字 1 的索引为", index) } } ================================================ FILE: codes/go/chapter_divide_and_conquer/binary_search_recur.go ================================================ // File: binary_search_recur.go // Created Time: 2023-07-19 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer /* 二分查找:问题 f(i, j) */ func dfs(nums []int, target, i, j int) int { // 如果区间为空,代表没有目标元素,则返回 -1 if i > j { return -1 } // 计算索引中点 m := i + ((j - i) >> 1) //判断中点与目标元素大小 if nums[m] < target { // 小于则递归右半数组 // 递归子问题 f(m+1, j) return dfs(nums, target, m+1, j) } else if nums[m] > target { // 大于则递归左半数组 // 递归子问题 f(i, m-1) return dfs(nums, target, i, m-1) } else { // 找到目标元素,返回其索引 return m } } /* 二分查找 */ func binarySearch(nums []int, target int) int { n := len(nums) return dfs(nums, target, 0, n-1) } ================================================ FILE: codes/go/chapter_divide_and_conquer/binary_search_recur_test.go ================================================ // File: binary_search_recur_test.go // Created Time: 2023-07-19 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "fmt" "testing" ) func TestBinarySearch(t *testing.T) { nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} target := 6 noTarget := 99 targetIndex := binarySearch(nums, target) fmt.Println("目标元素 6 的索引 = ", targetIndex) noTargetIndex := binarySearch(nums, noTarget) fmt.Println("不存在目标元素的索引 = ", noTargetIndex) } ================================================ FILE: codes/go/chapter_divide_and_conquer/build_tree.go ================================================ // File: build_tree.go // Created Time: 2023-07-20 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import . "github.com/krahets/hello-algo/pkg" /* 构建二叉树:分治 */ func dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode { // 子树区间为空时终止 if r-l < 0 { return nil } // 初始化根节点 root := NewTreeNode(preorder[i]) // 查询 m ,从而划分左右子树 m := inorderMap[preorder[i]] // 子问题:构建左子树 root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1) // 子问题:构建右子树 root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r) // 返回根节点 return root } /* 构建二叉树 */ func buildTree(preorder, inorder []int) *TreeNode { // 初始化哈希表,存储 inorder 元素到索引的映射 inorderMap := make(map[int]int, len(inorder)) for i := 0; i < len(inorder); i++ { inorderMap[inorder[i]] = i } root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1) return root } ================================================ FILE: codes/go/chapter_divide_and_conquer/build_tree_test.go ================================================ // File: build_tree_test.go // Created Time: 2023-07-20 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestBuildTree(t *testing.T) { preorder := []int{3, 9, 2, 1, 7} inorder := []int{9, 3, 1, 2, 7} fmt.Print("前序遍历 = ") PrintSlice(preorder) fmt.Print("中序遍历 = ") PrintSlice(inorder) root := buildTree(preorder, inorder) fmt.Println("构建的二叉树为:") PrintTree(root) } ================================================ FILE: codes/go/chapter_divide_and_conquer/hanota.go ================================================ // File: hanota.go // Created Time: 2023-07-21 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import "container/list" /* 移动一个圆盘 */ func move(src, tar *list.List) { // 从 src 顶部拿出一个圆盘 pan := src.Back() // 将圆盘放入 tar 顶部 tar.PushBack(pan.Value) // 移除 src 顶部圆盘 src.Remove(pan) } /* 求解汉诺塔问题 f(i) */ func dfsHanota(i int, src, buf, tar *list.List) { // 若 src 只剩下一个圆盘,则直接将其移到 tar if i == 1 { move(src, tar) return } // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf dfsHanota(i-1, src, tar, buf) // 子问题 f(1) :将 src 剩余一个圆盘移到 tar move(src, tar) // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar dfsHanota(i-1, buf, src, tar) } /* 求解汉诺塔问题 */ func solveHanota(A, B, C *list.List) { n := A.Len() // 将 A 顶部 n 个圆盘借助 B 移到 C dfsHanota(n, A, B, C) } ================================================ FILE: codes/go/chapter_divide_and_conquer/hanota_test.go ================================================ // File: hanota_test.go // Created Time: 2023-07-21 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHanota(t *testing.T) { // 列表尾部是柱子顶部 A := list.New() for i := 5; i > 0; i-- { A.PushBack(i) } B := list.New() C := list.New() fmt.Println("初始状态下:") fmt.Print("A = ") PrintList(A) fmt.Print("B = ") PrintList(B) fmt.Print("C = ") PrintList(C) solveHanota(A, B, C) fmt.Println("圆盘移动完成后:") fmt.Print("A = ") PrintList(A) fmt.Print("B = ") PrintList(B) fmt.Print("C = ") PrintList(C) } ================================================ FILE: codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go ================================================ // File: climbing_stairs_backtrack.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 回溯 */ func backtrack(choices []int, state, n int, res []int) { // 当爬到第 n 阶时,方案数量加 1 if state == n { res[0] = res[0] + 1 } // 遍历所有选择 for _, choice := range choices { // 剪枝:不允许越过第 n 阶 if state+choice > n { continue } // 尝试:做出选择,更新状态 backtrack(choices, state+choice, n, res) // 回退 } } /* 爬楼梯:回溯 */ func climbingStairsBacktrack(n int) int { // 可选择向上爬 1 阶或 2 阶 choices := []int{1, 2} // 从第 0 阶开始爬 state := 0 res := make([]int, 1) // 使用 res[0] 记录方案数量 res[0] = 0 backtrack(choices, state, n, res) return res[0] } ================================================ FILE: codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go ================================================ // File: climbing_stairs_constraint_dp.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 带约束爬楼梯:动态规划 */ func climbingStairsConstraintDP(n int) int { if n == 1 || n == 2 { return 1 } // 初始化 dp 表,用于存储子问题的解 dp := make([][3]int, n+1) // 初始状态:预设最小子问题的解 dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // 状态转移:从较小子问题逐步求解较大子问题 for i := 3; i <= n; i++ { dp[i][1] = dp[i-1][2] dp[i][2] = dp[i-2][1] + dp[i-2][2] } return dp[n][1] + dp[n][2] } ================================================ FILE: codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go ================================================ // File: climbing_stairs_dfs.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 搜索 */ func dfs(i int) int { // 已知 dp[1] 和 dp[2] ,返回之 if i == 1 || i == 2 { return i } // dp[i] = dp[i-1] + dp[i-2] count := dfs(i-1) + dfs(i-2) return count } /* 爬楼梯:搜索 */ func climbingStairsDFS(n int) int { return dfs(n) } ================================================ FILE: codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go ================================================ // File: climbing_stairs_dfs_mem.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 记忆化搜索 */ func dfsMem(i int, mem []int) int { // 已知 dp[1] 和 dp[2] ,返回之 if i == 1 || i == 2 { return i } // 若存在记录 dp[i] ,则直接返回之 if mem[i] != -1 { return mem[i] } // dp[i] = dp[i-1] + dp[i-2] count := dfsMem(i-1, mem) + dfsMem(i-2, mem) // 记录 dp[i] mem[i] = count return count } /* 爬楼梯:记忆化搜索 */ func climbingStairsDFSMem(n int) int { // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 mem := make([]int, n+1) for i := range mem { mem[i] = -1 } return dfsMem(n, mem) } ================================================ FILE: codes/go/chapter_dynamic_programming/climbing_stairs_dp.go ================================================ // File: climbing_stairs_dp.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 爬楼梯:动态规划 */ func climbingStairsDP(n int) int { if n == 1 || n == 2 { return n } // 初始化 dp 表,用于存储子问题的解 dp := make([]int, n+1) // 初始状态:预设最小子问题的解 dp[1] = 1 dp[2] = 2 // 状态转移:从较小子问题逐步求解较大子问题 for i := 3; i <= n; i++ { dp[i] = dp[i-1] + dp[i-2] } return dp[n] } /* 爬楼梯:空间优化后的动态规划 */ func climbingStairsDPComp(n int) int { if n == 1 || n == 2 { return n } a, b := 1, 2 // 状态转移:从较小子问题逐步求解较大子问题 for i := 3; i <= n; i++ { a, b = b, a+b } return b } ================================================ FILE: codes/go/chapter_dynamic_programming/climbing_stairs_test.go ================================================ // File: climbing_stairs_test.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestClimbingStairsBacktrack(t *testing.T) { n := 9 res := climbingStairsBacktrack(n) fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) } func TestClimbingStairsDFS(t *testing.T) { n := 9 res := climbingStairsDFS(n) fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) } func TestClimbingStairsDFSMem(t *testing.T) { n := 9 res := climbingStairsDFSMem(n) fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) } func TestClimbingStairsDP(t *testing.T) { n := 9 res := climbingStairsDP(n) fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) } func TestClimbingStairsDPComp(t *testing.T) { n := 9 res := climbingStairsDPComp(n) fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) } func TestClimbingStairsConstraintDP(t *testing.T) { n := 9 res := climbingStairsConstraintDP(n) fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) } func TestMinCostClimbingStairsDPComp(t *testing.T) { cost := []int{0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1} fmt.Printf("输入楼梯的代价列表为 %v\n", cost) res := minCostClimbingStairsDP(cost) fmt.Printf("爬完楼梯的最低代价为 %d\n", res) res = minCostClimbingStairsDPComp(cost) fmt.Printf("爬完楼梯的最低代价为 %d\n", res) } ================================================ FILE: codes/go/chapter_dynamic_programming/coin_change.go ================================================ // File: coin_change.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* 零钱兑换:动态规划 */ func coinChangeDP(coins []int, amt int) int { n := len(coins) max := amt + 1 // 初始化 dp 表 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, amt+1) } // 状态转移:首行首列 for a := 1; a <= amt; a++ { dp[0][a] = max } // 状态转移:其余行和列 for i := 1; i <= n; i++ { for a := 1; a <= amt; a++ { if coins[i-1] > a { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i-1][a] } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1))) } } } if dp[n][amt] != max { return dp[n][amt] } return -1 } /* 零钱兑换:动态规划 */ func coinChangeDPComp(coins []int, amt int) int { n := len(coins) max := amt + 1 // 初始化 dp 表 dp := make([]int, amt+1) for i := 1; i <= amt; i++ { dp[i] = max } // 状态转移 for i := 1; i <= n; i++ { // 正序遍历 for a := 1; a <= amt; a++ { if coins[i-1] > a { // 若超过目标金额,则不选硬币 i dp[a] = dp[a] } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1))) } } } if dp[amt] != max { return dp[amt] } return -1 } ================================================ FILE: codes/go/chapter_dynamic_programming/coin_change_ii.go ================================================ // File: coin_change_ii.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 零钱兑换 II:动态规划 */ func coinChangeIIDP(coins []int, amt int) int { n := len(coins) // 初始化 dp 表 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, amt+1) } // 初始化首列 for i := 0; i <= n; i++ { dp[i][0] = 1 } // 状态转移:其余行和列 for i := 1; i <= n; i++ { for a := 1; a <= amt; a++ { if coins[i-1] > a { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i-1][a] } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]] } } } return dp[n][amt] } /* 零钱兑换 II:空间优化后的动态规划 */ func coinChangeIIDPComp(coins []int, amt int) int { n := len(coins) // 初始化 dp 表 dp := make([]int, amt+1) dp[0] = 1 // 状态转移 for i := 1; i <= n; i++ { // 正序遍历 for a := 1; a <= amt; a++ { if coins[i-1] > a { // 若超过目标金额,则不选硬币 i dp[a] = dp[a] } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a-coins[i-1]] } } } return dp[amt] } ================================================ FILE: codes/go/chapter_dynamic_programming/coin_change_test.go ================================================ // File: coin_change_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestCoinChange(t *testing.T) { coins := []int{1, 2, 5} amt := 4 // 动态规划 res := coinChangeDP(coins, amt) fmt.Printf("凑到目标金额所需的最少硬币数量为 %d\n", res) // 空间优化后的动态规划 res = coinChangeDPComp(coins, amt) fmt.Printf("凑到目标金额所需的最少硬币数量为 %d\n", res) } ================================================ FILE: codes/go/chapter_dynamic_programming/edit_distance.go ================================================ // File: edit_distance.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 编辑距离:暴力搜索 */ func editDistanceDFS(s string, t string, i int, j int) int { // 若 s 和 t 都为空,则返回 0 if i == 0 && j == 0 { return 0 } // 若 s 为空,则返回 t 长度 if i == 0 { return j } // 若 t 为空,则返回 s 长度 if j == 0 { return i } // 若两字符相等,则直接跳过此两字符 if s[i-1] == t[j-1] { return editDistanceDFS(s, t, i-1, j-1) } // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 insert := editDistanceDFS(s, t, i, j-1) deleted := editDistanceDFS(s, t, i-1, j) replace := editDistanceDFS(s, t, i-1, j-1) // 返回最少编辑步数 return MinInt(MinInt(insert, deleted), replace) + 1 } /* 编辑距离:记忆化搜索 */ func editDistanceDFSMem(s string, t string, mem [][]int, i int, j int) int { // 若 s 和 t 都为空,则返回 0 if i == 0 && j == 0 { return 0 } // 若 s 为空,则返回 t 长度 if i == 0 { return j } // 若 t 为空,则返回 s 长度 if j == 0 { return i } // 若已有记录,则直接返回之 if mem[i][j] != -1 { return mem[i][j] } // 若两字符相等,则直接跳过此两字符 if s[i-1] == t[j-1] { return editDistanceDFSMem(s, t, mem, i-1, j-1) } // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 insert := editDistanceDFSMem(s, t, mem, i, j-1) deleted := editDistanceDFSMem(s, t, mem, i-1, j) replace := editDistanceDFSMem(s, t, mem, i-1, j-1) // 记录并返回最少编辑步数 mem[i][j] = MinInt(MinInt(insert, deleted), replace) + 1 return mem[i][j] } /* 编辑距离:动态规划 */ func editDistanceDP(s string, t string) int { n := len(s) m := len(t) dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, m+1) } // 状态转移:首行首列 for i := 1; i <= n; i++ { dp[i][0] = i } for j := 1; j <= m; j++ { dp[0][j] = j } // 状态转移:其余行和列 for i := 1; i <= n; i++ { for j := 1; j <= m; j++ { if s[i-1] == t[j-1] { // 若两字符相等,则直接跳过此两字符 dp[i][j] = dp[i-1][j-1] } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1 } } } return dp[n][m] } /* 编辑距离:空间优化后的动态规划 */ func editDistanceDPComp(s string, t string) int { n := len(s) m := len(t) dp := make([]int, m+1) // 状态转移:首行 for j := 1; j <= m; j++ { dp[j] = j } // 状态转移:其余行 for i := 1; i <= n; i++ { // 状态转移:首列 leftUp := dp[0] // 暂存 dp[i-1, j-1] dp[0] = i // 状态转移:其余列 for j := 1; j <= m; j++ { temp := dp[j] if s[i-1] == t[j-1] { // 若两字符相等,则直接跳过此两字符 dp[j] = leftUp } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1 } leftUp = temp // 更新为下一轮的 dp[i-1, j-1] } } return dp[m] } func MinInt(a, b int) int { if a < b { return a } return b } ================================================ FILE: codes/go/chapter_dynamic_programming/edit_distance_test.go ================================================ // File: edit_distance_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestEditDistanceDFS(test *testing.T) { s := "bag" t := "pack" n := len(s) m := len(t) // 暴力搜索 res := editDistanceDFS(s, t, n, m) fmt.Printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res) // 记忆化搜索 mem := make([][]int, n+1) for i := 0; i <= n; i++ { mem[i] = make([]int, m+1) for j := 0; j <= m; j++ { mem[i][j] = -1 } } res = editDistanceDFSMem(s, t, mem, n, m) fmt.Printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res) // 动态规划 res = editDistanceDP(s, t) fmt.Printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res) // 空间优化后的动态规划 res = editDistanceDPComp(s, t) fmt.Printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res) } ================================================ FILE: codes/go/chapter_dynamic_programming/knapsack.go ================================================ // File: knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* 0-1 背包:暴力搜索 */ func knapsackDFS(wgt, val []int, i, c int) int { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if i == 0 || c == 0 { return 0 } // 若超过背包容量,则只能选择不放入背包 if wgt[i-1] > c { return knapsackDFS(wgt, val, i-1, c) } // 计算不放入和放入物品 i 的最大价值 no := knapsackDFS(wgt, val, i-1, c) yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1] // 返回两种方案中价值更大的那一个 return int(math.Max(float64(no), float64(yes))) } /* 0-1 背包:记忆化搜索 */ func knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if i == 0 || c == 0 { return 0 } // 若已有记录,则直接返回 if mem[i][c] != -1 { return mem[i][c] } // 若超过背包容量,则只能选择不放入背包 if wgt[i-1] > c { return knapsackDFSMem(wgt, val, mem, i-1, c) } // 计算不放入和放入物品 i 的最大价值 no := knapsackDFSMem(wgt, val, mem, i-1, c) yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1] // 返回两种方案中价值更大的那一个 mem[i][c] = int(math.Max(float64(no), float64(yes))) return mem[i][c] } /* 0-1 背包:动态规划 */ func knapsackDP(wgt, val []int, cap int) int { n := len(wgt) // 初始化 dp 表 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, cap+1) } // 状态转移 for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i-1][c] } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1]))) } } } return dp[n][cap] } /* 0-1 背包:空间优化后的动态规划 */ func knapsackDPComp(wgt, val []int, cap int) int { n := len(wgt) // 初始化 dp 表 dp := make([]int, cap+1) // 状态转移 for i := 1; i <= n; i++ { // 倒序遍历 for c := cap; c >= 1; c-- { if wgt[i-1] <= c { // 不选和选物品 i 这两种方案的较大值 dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) } } } return dp[cap] } ================================================ FILE: codes/go/chapter_dynamic_programming/knapsack_test.go ================================================ // File: knapsack_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestKnapsack(t *testing.T) { wgt := []int{10, 20, 30, 40, 50} val := []int{50, 120, 150, 210, 240} c := 50 n := len(wgt) // 暴力搜索 res := knapsackDFS(wgt, val, n, c) fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) // 记忆化搜索 mem := make([][]int, n+1) for i := 0; i <= n; i++ { mem[i] = make([]int, c+1) for j := 0; j <= c; j++ { mem[i][j] = -1 } } res = knapsackDFSMem(wgt, val, mem, n, c) fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) // 动态规划 res = knapsackDP(wgt, val, c) fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) // 空间优化后的动态规划 res = knapsackDPComp(wgt, val, c) fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) } func TestUnboundedKnapsack(t *testing.T) { wgt := []int{1, 2, 3} val := []int{5, 11, 15} c := 4 // 动态规划 res := unboundedKnapsackDP(wgt, val, c) fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) // 空间优化后的动态规划 res = unboundedKnapsackDPComp(wgt, val, c) fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) } ================================================ FILE: codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go ================================================ // File: min_cost_climbing_stairs_dp.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 爬楼梯最小代价:动态规划 */ func minCostClimbingStairsDP(cost []int) int { n := len(cost) - 1 if n == 1 || n == 2 { return cost[n] } min := func(a, b int) int { if a < b { return a } return b } // 初始化 dp 表,用于存储子问题的解 dp := make([]int, n+1) // 初始状态:预设最小子问题的解 dp[1] = cost[1] dp[2] = cost[2] // 状态转移:从较小子问题逐步求解较大子问题 for i := 3; i <= n; i++ { dp[i] = min(dp[i-1], dp[i-2]) + cost[i] } return dp[n] } /* 爬楼梯最小代价:空间优化后的动态规划 */ func minCostClimbingStairsDPComp(cost []int) int { n := len(cost) - 1 if n == 1 || n == 2 { return cost[n] } min := func(a, b int) int { if a < b { return a } return b } // 初始状态:预设最小子问题的解 a, b := cost[1], cost[2] // 状态转移:从较小子问题逐步求解较大子问题 for i := 3; i <= n; i++ { tmp := b b = min(a, tmp) + cost[i] a = tmp } return b } ================================================ FILE: codes/go/chapter_dynamic_programming/min_path_sum.go ================================================ // File: min_path_sum.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* 最小路径和:暴力搜索 */ func minPathSumDFS(grid [][]int, i, j int) int { // 若为左上角单元格,则终止搜索 if i == 0 && j == 0 { return grid[0][0] } // 若行列索引越界,则返回 +∞ 代价 if i < 0 || j < 0 { return math.MaxInt } // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 up := minPathSumDFS(grid, i-1, j) left := minPathSumDFS(grid, i, j-1) // 返回从左上角到 (i, j) 的最小路径代价 return int(math.Min(float64(left), float64(up))) + grid[i][j] } /* 最小路径和:记忆化搜索 */ func minPathSumDFSMem(grid, mem [][]int, i, j int) int { // 若为左上角单元格,则终止搜索 if i == 0 && j == 0 { return grid[0][0] } // 若行列索引越界,则返回 +∞ 代价 if i < 0 || j < 0 { return math.MaxInt } // 若已有记录,则直接返回 if mem[i][j] != -1 { return mem[i][j] } // 左边和上边单元格的最小路径代价 up := minPathSumDFSMem(grid, mem, i-1, j) left := minPathSumDFSMem(grid, mem, i, j-1) // 记录并返回左上角到 (i, j) 的最小路径代价 mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j] return mem[i][j] } /* 最小路径和:动态规划 */ func minPathSumDP(grid [][]int) int { n, m := len(grid), len(grid[0]) // 初始化 dp 表 dp := make([][]int, n) for i := 0; i < n; i++ { dp[i] = make([]int, m) } dp[0][0] = grid[0][0] // 状态转移:首行 for j := 1; j < m; j++ { dp[0][j] = dp[0][j-1] + grid[0][j] } // 状态转移:首列 for i := 1; i < n; i++ { dp[i][0] = dp[i-1][0] + grid[i][0] } // 状态转移:其余行和列 for i := 1; i < n; i++ { for j := 1; j < m; j++ { dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j] } } return dp[n-1][m-1] } /* 最小路径和:空间优化后的动态规划 */ func minPathSumDPComp(grid [][]int) int { n, m := len(grid), len(grid[0]) // 初始化 dp 表 dp := make([]int, m) // 状态转移:首行 dp[0] = grid[0][0] for j := 1; j < m; j++ { dp[j] = dp[j-1] + grid[0][j] } // 状态转移:其余行和列 for i := 1; i < n; i++ { // 状态转移:首列 dp[0] = dp[0] + grid[i][0] // 状态转移:其余列 for j := 1; j < m; j++ { dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j] } } return dp[m-1] } ================================================ FILE: codes/go/chapter_dynamic_programming/min_path_sum_test.go ================================================ // File: min_path_sum_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestMinPathSum(t *testing.T) { grid := [][]int{ {1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}, } n, m := len(grid), len(grid[0]) // 暴力搜索 res := minPathSumDFS(grid, n-1, m-1) fmt.Printf("从左上角到右下角的最小路径和为 %d\n", res) // 记忆化搜索 mem := make([][]int, n) for i := 0; i < n; i++ { mem[i] = make([]int, m) for j := 0; j < m; j++ { mem[i][j] = -1 } } res = minPathSumDFSMem(grid, mem, n-1, m-1) fmt.Printf("从左上角到右下角的最小路径和为 %d\n", res) // 动态规划 res = minPathSumDP(grid) fmt.Printf("从左上角到右下角的最小路径和为 %d\n", res) // 空间优化后的动态规划 res = minPathSumDPComp(grid) fmt.Printf("从左上角到右下角的最小路径和为 %d\n", res) } ================================================ FILE: codes/go/chapter_dynamic_programming/unbounded_knapsack.go ================================================ // File: unbounded_knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* 完全背包:动态规划 */ func unboundedKnapsackDP(wgt, val []int, cap int) int { n := len(wgt) // 初始化 dp 表 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, cap+1) } // 状态转移 for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i-1][c] } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1]))) } } } return dp[n][cap] } /* 完全背包:空间优化后的动态规划 */ func unboundedKnapsackDPComp(wgt, val []int, cap int) int { n := len(wgt) // 初始化 dp 表 dp := make([]int, cap+1) // 状态转移 for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // 若超过背包容量,则不选物品 i dp[c] = dp[c] } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) } } } return dp[cap] } ================================================ FILE: codes/go/chapter_graph/graph_adjacency_list.go ================================================ // File: graph_adjacency_list.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "strconv" "strings" . "github.com/krahets/hello-algo/pkg" ) /* 基于邻接表实现的无向图类 */ type graphAdjList struct { // 邻接表,key:顶点,value:该顶点的所有邻接顶点 adjList map[Vertex][]Vertex } /* 构造函数 */ func newGraphAdjList(edges [][]Vertex) *graphAdjList { g := &graphAdjList{ adjList: make(map[Vertex][]Vertex), } // 添加所有顶点和边 for _, edge := range edges { g.addVertex(edge[0]) g.addVertex(edge[1]) g.addEdge(edge[0], edge[1]) } return g } /* 获取顶点数量 */ func (g *graphAdjList) size() int { return len(g.adjList) } /* 添加边 */ func (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) { _, ok1 := g.adjList[vet1] _, ok2 := g.adjList[vet2] if !ok1 || !ok2 || vet1 == vet2 { panic("error") } // 添加边 vet1 - vet2, 添加匿名 struct{}, g.adjList[vet1] = append(g.adjList[vet1], vet2) g.adjList[vet2] = append(g.adjList[vet2], vet1) } /* 删除边 */ func (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) { _, ok1 := g.adjList[vet1] _, ok2 := g.adjList[vet2] if !ok1 || !ok2 || vet1 == vet2 { panic("error") } // 删除边 vet1 - vet2 g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2) g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1) } /* 添加顶点 */ func (g *graphAdjList) addVertex(vet Vertex) { _, ok := g.adjList[vet] if ok { return } // 在邻接表中添加一个新链表 g.adjList[vet] = make([]Vertex, 0) } /* 删除顶点 */ func (g *graphAdjList) removeVertex(vet Vertex) { _, ok := g.adjList[vet] if !ok { panic("error") } // 在邻接表中删除顶点 vet 对应的链表 delete(g.adjList, vet) // 遍历其他顶点的链表,删除所有包含 vet 的边 for v, list := range g.adjList { g.adjList[v] = DeleteSliceElms(list, vet) } } /* 打印邻接表 */ func (g *graphAdjList) print() { var builder strings.Builder fmt.Printf("邻接表 = \n") for k, v := range g.adjList { builder.WriteString("\t\t" + strconv.Itoa(k.Val) + ": ") for _, vet := range v { builder.WriteString(strconv.Itoa(vet.Val) + " ") } fmt.Println(builder.String()) builder.Reset() } } ================================================ FILE: codes/go/chapter_graph/graph_adjacency_list_test.go ================================================ // File: graph_adjacency_list_test.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphAdjList(t *testing.T) { /* 初始化无向图 */ v := ValsToVets([]int{1, 3, 2, 5, 4}) edges := [][]Vertex{{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}} graph := newGraphAdjList(edges) fmt.Println("初始化后,图为:") graph.print() /* 添加边 */ // 顶点 1, 2 即 v[0], v[2] graph.addEdge(v[0], v[2]) fmt.Println("\n添加边 1-2 后,图为") graph.print() /* 删除边 */ // 顶点 1, 3 即 v[0], v[1] graph.removeEdge(v[0], v[1]) fmt.Println("\n删除边 1-3 后,图为") graph.print() /* 添加顶点 */ v5 := NewVertex(6) graph.addVertex(v5) fmt.Println("\n添加顶点 6 后,图为") graph.print() /* 删除顶点 */ // 顶点 3 即 v[1] graph.removeVertex(v[1]) fmt.Println("\n删除顶点 3 后,图为") graph.print() } ================================================ FILE: codes/go/chapter_graph/graph_adjacency_matrix.go ================================================ // File: graph_adjacency_matrix.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import "fmt" /* 基于邻接矩阵实现的无向图类 */ type graphAdjMat struct { // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” vertices []int // 邻接矩阵,行列索引对应“顶点索引” adjMat [][]int } /* 构造函数 */ func newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat { // 添加顶点 n := len(vertices) adjMat := make([][]int, n) for i := range adjMat { adjMat[i] = make([]int, n) } // 初始化图 g := &graphAdjMat{ vertices: vertices, adjMat: adjMat, } // 添加边 // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 for i := range edges { g.addEdge(edges[i][0], edges[i][1]) } return g } /* 获取顶点数量 */ func (g *graphAdjMat) size() int { return len(g.vertices) } /* 添加顶点 */ func (g *graphAdjMat) addVertex(val int) { n := g.size() // 向顶点列表中添加新顶点的值 g.vertices = append(g.vertices, val) // 在邻接矩阵中添加一行 newRow := make([]int, n) g.adjMat = append(g.adjMat, newRow) // 在邻接矩阵中添加一列 for i := range g.adjMat { g.adjMat[i] = append(g.adjMat[i], 0) } } /* 删除顶点 */ func (g *graphAdjMat) removeVertex(index int) { if index >= g.size() { return } // 在顶点列表中移除索引 index 的顶点 g.vertices = append(g.vertices[:index], g.vertices[index+1:]...) // 在邻接矩阵中删除索引 index 的行 g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...) // 在邻接矩阵中删除索引 index 的列 for i := range g.adjMat { g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...) } } /* 添加边 */ // 参数 i, j 对应 vertices 元素索引 func (g *graphAdjMat) addEdge(i, j int) { // 索引越界与相等处理 if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { fmt.Errorf("%s", "Index Out Of Bounds Exception") } // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) g.adjMat[i][j] = 1 g.adjMat[j][i] = 1 } /* 删除边 */ // 参数 i, j 对应 vertices 元素索引 func (g *graphAdjMat) removeEdge(i, j int) { // 索引越界与相等处理 if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { fmt.Errorf("%s", "Index Out Of Bounds Exception") } g.adjMat[i][j] = 0 g.adjMat[j][i] = 0 } /* 打印邻接矩阵 */ func (g *graphAdjMat) print() { fmt.Printf("\t顶点列表 = %v\n", g.vertices) fmt.Printf("\t邻接矩阵 = \n") for i := range g.adjMat { fmt.Printf("\t\t\t%v\n", g.adjMat[i]) } } ================================================ FILE: codes/go/chapter_graph/graph_adjacency_matrix_test.go ================================================ // File: graph_adjacency_matrix_test.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" ) func TestGraphAdjMat(t *testing.T) { /* 初始化无向图 */ // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 vertices := []int{1, 3, 2, 5, 4} edges := [][]int{{0, 1}, {1, 2}, {2, 3}, {0, 3}, {2, 4}, {3, 4}} graph := newGraphAdjMat(vertices, edges) fmt.Println("初始化后,图为:") graph.print() /* 添加边 */ // 顶点 1, 2 的索引分别为 0, 2 graph.addEdge(0, 2) fmt.Println("添加边 1-2 后,图为") graph.print() /* 删除边 */ // 顶点 1, 3 的索引分别为 0, 1 graph.removeEdge(0, 1) fmt.Println("删除边 1-3 后,图为") graph.print() /* 添加顶点 */ graph.addVertex(6) fmt.Println("添加顶点 6 后,图为") graph.print() /* 删除顶点 */ // 顶点 3 的索引为 1 graph.removeVertex(1) fmt.Println("删除顶点 3 后,图为") graph.print() } ================================================ FILE: codes/go/chapter_graph/graph_bfs.go ================================================ // File: graph_bfs.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( . "github.com/krahets/hello-algo/pkg" ) /* 广度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 func graphBFS(g *graphAdjList, startVet Vertex) []Vertex { // 顶点遍历序列 res := make([]Vertex, 0) // 哈希集合,用于记录已被访问过的顶点 visited := make(map[Vertex]struct{}) visited[startVet] = struct{}{} // 队列用于实现 BFS, 使用切片模拟队列 queue := make([]Vertex, 0) queue = append(queue, startVet) // 以顶点 vet 为起点,循环直至访问完所有顶点 for len(queue) > 0 { // 队首顶点出队 vet := queue[0] queue = queue[1:] // 记录访问顶点 res = append(res, vet) // 遍历该顶点的所有邻接顶点 for _, adjVet := range g.adjList[vet] { _, isExist := visited[adjVet] // 只入队未访问的顶点 if !isExist { queue = append(queue, adjVet) visited[adjVet] = struct{}{} } } } // 返回顶点遍历序列 return res } ================================================ FILE: codes/go/chapter_graph/graph_bfs_test.go ================================================ // File: graph_bfs_test.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphBFS(t *testing.T) { /* 初始化无向图 */ vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) edges := [][]Vertex{ {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[1], vets[4]}, {vets[2], vets[5]}, {vets[3], vets[4]}, {vets[3], vets[6]}, {vets[4], vets[5]}, {vets[4], vets[7]}, {vets[5], vets[8]}, {vets[6], vets[7]}, {vets[7], vets[8]}} graph := newGraphAdjList(edges) fmt.Println("初始化后,图为:") graph.print() /* 广度优先遍历 */ res := graphBFS(graph, vets[0]) fmt.Println("广度优先遍历(BFS)顶点序列为:") PrintSlice(VetsToVals(res)) } ================================================ FILE: codes/go/chapter_graph/graph_dfs.go ================================================ // File: graph_dfs.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( . "github.com/krahets/hello-algo/pkg" ) /* 深度优先遍历辅助函数 */ func dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) { // append 操作会返回新的的引用,必须让原引用重新赋值为新slice的引用 *res = append(*res, vet) visited[vet] = struct{}{} // 遍历该顶点的所有邻接顶点 for _, adjVet := range g.adjList[vet] { _, isExist := visited[adjVet] // 递归访问邻接顶点 if !isExist { dfs(g, visited, res, adjVet) } } } /* 深度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 func graphDFS(g *graphAdjList, startVet Vertex) []Vertex { // 顶点遍历序列 res := make([]Vertex, 0) // 哈希集合,用于记录已被访问过的顶点 visited := make(map[Vertex]struct{}) dfs(g, visited, &res, startVet) // 返回顶点遍历序列 return res } ================================================ FILE: codes/go/chapter_graph/graph_dfs_test.go ================================================ // File: graph_dfs_test.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphDFS(t *testing.T) { /* 初始化无向图 */ vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6}) edges := [][]Vertex{ {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[2], vets[5]}, {vets[4], vets[5]}, {vets[5], vets[6]}} graph := newGraphAdjList(edges) fmt.Println("初始化后,图为:") graph.print() /* 深度优先遍历 */ res := graphDFS(graph, vets[0]) fmt.Println("深度优先遍历(DFS)顶点序列为:") PrintSlice(VetsToVals(res)) } ================================================ FILE: codes/go/chapter_greedy/coin_change_greedy.go ================================================ // File: coin_change_greedy.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy /* 零钱兑换:贪心 */ func coinChangeGreedy(coins []int, amt int) int { // 假设 coins 列表有序 i := len(coins) - 1 count := 0 // 循环进行贪心选择,直到无剩余金额 for amt > 0 { // 找到小于且最接近剩余金额的硬币 for i > 0 && coins[i] > amt { i-- } // 选择 coins[i] amt -= coins[i] count++ } // 若未找到可行方案,则返回 -1 if amt != 0 { return -1 } return count } ================================================ FILE: codes/go/chapter_greedy/coin_change_greedy_test.go ================================================ // File: coin_change_greedy_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestCoinChangeGreedy(t *testing.T) { // 贪心:能够保证找到全局最优解 coins := []int{1, 5, 10, 20, 50, 100} amt := 186 res := coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("凑到 %d 所需的最少硬币数量为 %d\n", amt, res) // 贪心:无法保证找到全局最优解 coins = []int{1, 20, 50} amt = 60 res = coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("凑到 %d 所需的最少硬币数量为 %d\n", amt, res) fmt.Println("实际上需要的最少数量为 3 ,即 20 + 20 + 20") // 贪心:无法保证找到全局最优解 coins = []int{1, 49, 50} amt = 98 res = coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("凑到 %d 所需的最少硬币数量为 %d\n", amt, res) fmt.Println("实际上需要的最少数量为 2 ,即 49 + 49") } ================================================ FILE: codes/go/chapter_greedy/fractional_knapsack.go ================================================ // File: fractional_knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "sort" /* 物品 */ type Item struct { w int // 物品重量 v int // 物品价值 } /* 分数背包:贪心 */ func fractionalKnapsack(wgt []int, val []int, cap int) float64 { // 创建物品列表,包含两个属性:重量、价值 items := make([]Item, len(wgt)) for i := 0; i < len(wgt); i++ { items[i] = Item{wgt[i], val[i]} } // 按照单位价值 item.v / item.w 从高到低进行排序 sort.Slice(items, func(i, j int) bool { return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w) }) // 循环贪心选择 res := 0.0 for _, item := range items { if item.w <= cap { // 若剩余容量充足,则将当前物品整个装进背包 res += float64(item.v) cap -= item.w } else { // 若剩余容量不足,则将当前物品的一部分装进背包 res += float64(item.v) / float64(item.w) * float64(cap) // 已无剩余容量,因此跳出循环 break } } return res } ================================================ FILE: codes/go/chapter_greedy/fractional_knapsack_test.go ================================================ // File: fractional_knapsack_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestFractionalKnapsack(t *testing.T) { wgt := []int{10, 20, 30, 40, 50} val := []int{50, 120, 150, 210, 240} capacity := 50 // 贪心算法 res := fractionalKnapsack(wgt, val, capacity) fmt.Println("不超过背包容量的最大物品价值为", res) } ================================================ FILE: codes/go/chapter_greedy/max_capacity.go ================================================ // File: max_capacity.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "math" /* 最大容量:贪心 */ func maxCapacity(ht []int) int { // 初始化 i, j,使其分列数组两端 i, j := 0, len(ht)-1 // 初始最大容量为 0 res := 0 // 循环贪心选择,直至两板相遇 for i < j { // 更新最大容量 capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i) res = int(math.Max(float64(res), float64(capacity))) // 向内移动短板 if ht[i] < ht[j] { i++ } else { j-- } } return res } ================================================ FILE: codes/go/chapter_greedy/max_capacity_test.go ================================================ // File: max_capacity_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestMaxCapacity(t *testing.T) { ht := []int{3, 8, 5, 2, 7, 7, 3, 4} // 贪心算法 res := maxCapacity(ht) fmt.Println("最大容量为", res) } ================================================ FILE: codes/go/chapter_greedy/max_product_cutting.go ================================================ // File: max_product_cutting.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "math" /* 最大切分乘积:贪心 */ func maxProductCutting(n int) int { // 当 n <= 3 时,必须切分出一个 1 if n <= 3 { return 1 * (n - 1) } // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 a := n / 3 b := n % 3 if b == 1 { // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 return int(math.Pow(3, float64(a-1))) * 2 * 2 } if b == 2 { // 当余数为 2 时,不做处理 return int(math.Pow(3, float64(a))) * 2 } // 当余数为 0 时,不做处理 return int(math.Pow(3, float64(a))) } ================================================ FILE: codes/go/chapter_greedy/max_product_cutting_test.go ================================================ // File: max_product_cutting_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestMaxProductCutting(t *testing.T) { n := 58 // 贪心算法 res := maxProductCutting(n) fmt.Println("最大切分乘积为", res) } ================================================ FILE: codes/go/chapter_hashing/array_hash_map.go ================================================ // File: array_hash_map.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import "fmt" /* 键值对 */ type pair struct { key int val string } /* 基于数组实现的哈希表 */ type arrayHashMap struct { buckets []*pair } /* 初始化哈希表 */ func newArrayHashMap() *arrayHashMap { // 初始化数组,包含 100 个桶 buckets := make([]*pair, 100) return &arrayHashMap{buckets: buckets} } /* 哈希函数 */ func (a *arrayHashMap) hashFunc(key int) int { index := key % 100 return index } /* 查询操作 */ func (a *arrayHashMap) get(key int) string { index := a.hashFunc(key) pair := a.buckets[index] if pair == nil { return "Not Found" } return pair.val } /* 添加操作 */ func (a *arrayHashMap) put(key int, val string) { pair := &pair{key: key, val: val} index := a.hashFunc(key) a.buckets[index] = pair } /* 删除操作 */ func (a *arrayHashMap) remove(key int) { index := a.hashFunc(key) // 置为 nil ,代表删除 a.buckets[index] = nil } /* 获取所有键对 */ func (a *arrayHashMap) pairSet() []*pair { var pairs []*pair for _, pair := range a.buckets { if pair != nil { pairs = append(pairs, pair) } } return pairs } /* 获取所有键 */ func (a *arrayHashMap) keySet() []int { var keys []int for _, pair := range a.buckets { if pair != nil { keys = append(keys, pair.key) } } return keys } /* 获取所有值 */ func (a *arrayHashMap) valueSet() []string { var values []string for _, pair := range a.buckets { if pair != nil { values = append(values, pair.val) } } return values } /* 打印哈希表 */ func (a *arrayHashMap) print() { for _, pair := range a.buckets { if pair != nil { fmt.Println(pair.key, "->", pair.val) } } } ================================================ FILE: codes/go/chapter_hashing/array_hash_map_test.go ================================================ // File: array_hash_map_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "testing" ) func TestArrayHashMap(t *testing.T) { /* 初始化哈希表 */ hmap := newArrayHashMap() /* 添加操作 */ // 在哈希表中添加键值对 (key, value) hmap.put(12836, "小哈") hmap.put(15937, "小啰") hmap.put(16750, "小算") hmap.put(13276, "小法") hmap.put(10583, "小鸭") fmt.Println("\n添加完成后,哈希表为\nKey -> Value") hmap.print() /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value name := hmap.get(15937) fmt.Println("\n输入学号 15937 ,查询到姓名 " + name) /* 删除操作 */ // 在哈希表中删除键值对 (key, value) hmap.remove(10583) fmt.Println("\n删除 10583 后,哈希表为\nKey -> Value") hmap.print() /* 遍历哈希表 */ fmt.Println("\n遍历键值对 Key->Value") for _, kv := range hmap.pairSet() { fmt.Println(kv.key, " -> ", kv.val) } fmt.Println("\n单独遍历键 Key") for _, key := range hmap.keySet() { fmt.Println(key) } fmt.Println("\n单独遍历值 Value") for _, val := range hmap.valueSet() { fmt.Println(val) } } ================================================ FILE: codes/go/chapter_hashing/hash_collision_test.go ================================================ // File: hash_collision_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "testing" ) func TestHashMapChaining(t *testing.T) { /* 初始化哈希表 */ hmap := newHashMapChaining() /* 添加操作 */ // 在哈希表中添加键值对 (key, value) hmap.put(12836, "小哈") hmap.put(15937, "小啰") hmap.put(16750, "小算") hmap.put(13276, "小法") hmap.put(10583, "小鸭") fmt.Println("\n添加完成后,哈希表为\nKey -> Value") hmap.print() /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value name := hmap.get(15937) fmt.Println("\n输入学号 15937 ,查询到姓名", name) /* 删除操作 */ // 在哈希表中删除键值对 (key, value) hmap.remove(12836) fmt.Println("\n删除 12836 后,哈希表为\nKey -> Value") hmap.print() } func TestHashMapOpenAddressing(t *testing.T) { /* 初始化哈希表 */ hmap := newHashMapOpenAddressing() /* 添加操作 */ // 在哈希表中添加键值对 (key, value) hmap.put(12836, "小哈") hmap.put(15937, "小啰") hmap.put(16750, "小算") hmap.put(13276, "小法") hmap.put(10583, "小鸭") fmt.Println("\n添加完成后,哈希表为\nKey -> Value") hmap.print() /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value name := hmap.get(13276) fmt.Println("\n输入学号 13276 ,查询到姓名 ", name) /* 删除操作 */ // 在哈希表中删除键值对 (key, value) hmap.remove(16750) fmt.Println("\n删除 16750 后,哈希表为\nKey -> Value") hmap.print() } ================================================ FILE: codes/go/chapter_hashing/hash_map_chaining.go ================================================ // File: hash_map_chaining.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import ( "fmt" "strconv" "strings" ) /* 链式地址哈希表 */ type hashMapChaining struct { size int // 键值对数量 capacity int // 哈希表容量 loadThres float64 // 触发扩容的负载因子阈值 extendRatio int // 扩容倍数 buckets [][]pair // 桶数组 } /* 构造方法 */ func newHashMapChaining() *hashMapChaining { buckets := make([][]pair, 4) for i := 0; i < 4; i++ { buckets[i] = make([]pair, 0) } return &hashMapChaining{ size: 0, capacity: 4, loadThres: 2.0 / 3.0, extendRatio: 2, buckets: buckets, } } /* 哈希函数 */ func (m *hashMapChaining) hashFunc(key int) int { return key % m.capacity } /* 负载因子 */ func (m *hashMapChaining) loadFactor() float64 { return float64(m.size) / float64(m.capacity) } /* 查询操作 */ func (m *hashMapChaining) get(key int) string { idx := m.hashFunc(key) bucket := m.buckets[idx] // 遍历桶,若找到 key ,则返回对应 val for _, p := range bucket { if p.key == key { return p.val } } // 若未找到 key ,则返回空字符串 return "" } /* 添加操作 */ func (m *hashMapChaining) put(key int, val string) { // 当负载因子超过阈值时,执行扩容 if m.loadFactor() > m.loadThres { m.extend() } idx := m.hashFunc(key) // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 for i := range m.buckets[idx] { if m.buckets[idx][i].key == key { m.buckets[idx][i].val = val return } } // 若无该 key ,则将键值对添加至尾部 p := pair{ key: key, val: val, } m.buckets[idx] = append(m.buckets[idx], p) m.size += 1 } /* 删除操作 */ func (m *hashMapChaining) remove(key int) { idx := m.hashFunc(key) // 遍历桶,从中删除键值对 for i, p := range m.buckets[idx] { if p.key == key { // 切片删除 m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...) m.size -= 1 break } } } /* 扩容哈希表 */ func (m *hashMapChaining) extend() { // 暂存原哈希表 tmpBuckets := make([][]pair, len(m.buckets)) for i := 0; i < len(m.buckets); i++ { tmpBuckets[i] = make([]pair, len(m.buckets[i])) copy(tmpBuckets[i], m.buckets[i]) } // 初始化扩容后的新哈希表 m.capacity *= m.extendRatio m.buckets = make([][]pair, m.capacity) for i := 0; i < m.capacity; i++ { m.buckets[i] = make([]pair, 0) } m.size = 0 // 将键值对从原哈希表搬运至新哈希表 for _, bucket := range tmpBuckets { for _, p := range bucket { m.put(p.key, p.val) } } } /* 打印哈希表 */ func (m *hashMapChaining) print() { var builder strings.Builder for _, bucket := range m.buckets { builder.WriteString("[") for _, p := range bucket { builder.WriteString(strconv.Itoa(p.key) + " -> " + p.val + " ") } builder.WriteString("]") fmt.Println(builder.String()) builder.Reset() } } ================================================ FILE: codes/go/chapter_hashing/hash_map_open_addressing.go ================================================ // File: hash_map_open_addressing.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import ( "fmt" ) /* 开放寻址哈希表 */ type hashMapOpenAddressing struct { size int // 键值对数量 capacity int // 哈希表容量 loadThres float64 // 触发扩容的负载因子阈值 extendRatio int // 扩容倍数 buckets []*pair // 桶数组 TOMBSTONE *pair // 删除标记 } /* 构造方法 */ func newHashMapOpenAddressing() *hashMapOpenAddressing { return &hashMapOpenAddressing{ size: 0, capacity: 4, loadThres: 2.0 / 3.0, extendRatio: 2, buckets: make([]*pair, 4), TOMBSTONE: &pair{-1, "-1"}, } } /* 哈希函数 */ func (h *hashMapOpenAddressing) hashFunc(key int) int { return key % h.capacity // 根据键计算哈希值 } /* 负载因子 */ func (h *hashMapOpenAddressing) loadFactor() float64 { return float64(h.size) / float64(h.capacity) // 计算当前负载因子 } /* 搜索 key 对应的桶索引 */ func (h *hashMapOpenAddressing) findBucket(key int) int { index := h.hashFunc(key) // 获取初始索引 firstTombstone := -1 // 记录遇到的第一个TOMBSTONE的位置 for h.buckets[index] != nil { if h.buckets[index].key == key { if firstTombstone != -1 { // 若之前遇到了删除标记,则将键值对移动至该索引处 h.buckets[firstTombstone] = h.buckets[index] h.buckets[index] = h.TOMBSTONE return firstTombstone // 返回移动后的桶索引 } return index // 返回找到的索引 } if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE { firstTombstone = index // 记录遇到的首个删除标记的位置 } index = (index + 1) % h.capacity // 线性探测,越过尾部则返回头部 } // 若 key 不存在,则返回添加点的索引 if firstTombstone != -1 { return firstTombstone } return index } /* 查询操作 */ func (h *hashMapOpenAddressing) get(key int) string { index := h.findBucket(key) // 搜索 key 对应的桶索引 if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { return h.buckets[index].val // 若找到键值对,则返回对应 val } return "" // 若键值对不存在,则返回 "" } /* 添加操作 */ func (h *hashMapOpenAddressing) put(key int, val string) { if h.loadFactor() > h.loadThres { h.extend() // 当负载因子超过阈值时,执行扩容 } index := h.findBucket(key) // 搜索 key 对应的桶索引 if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE { h.buckets[index] = &pair{key, val} // 若键值对不存在,则添加该键值对 h.size++ } else { h.buckets[index].val = val // 若找到键值对,则覆盖 val } } /* 删除操作 */ func (h *hashMapOpenAddressing) remove(key int) { index := h.findBucket(key) // 搜索 key 对应的桶索引 if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { h.buckets[index] = h.TOMBSTONE // 若找到键值对,则用删除标记覆盖它 h.size-- } } /* 扩容哈希表 */ func (h *hashMapOpenAddressing) extend() { oldBuckets := h.buckets // 暂存原哈希表 h.capacity *= h.extendRatio // 更新容量 h.buckets = make([]*pair, h.capacity) // 初始化扩容后的新哈希表 h.size = 0 // 重置大小 // 将键值对从原哈希表搬运至新哈希表 for _, pair := range oldBuckets { if pair != nil && pair != h.TOMBSTONE { h.put(pair.key, pair.val) } } } /* 打印哈希表 */ func (h *hashMapOpenAddressing) print() { for _, pair := range h.buckets { if pair == nil { fmt.Println("nil") } else if pair == h.TOMBSTONE { fmt.Println("TOMBSTONE") } else { fmt.Printf("%d -> %s\n", pair.key, pair.val) } } } ================================================ FILE: codes/go/chapter_hashing/hash_map_test.go ================================================ // File: hash_map_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHashMap(t *testing.T) { /* 初始化哈希表 */ hmap := make(map[int]string) /* 添加操作 */ // 在哈希表中添加键值对 (key, value) hmap[12836] = "小哈" hmap[15937] = "小啰" hmap[16750] = "小算" hmap[13276] = "小法" hmap[10583] = "小鸭" fmt.Println("\n添加完成后,哈希表为\nKey -> Value") PrintMap(hmap) /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value name := hmap[15937] fmt.Println("\n输入学号 15937 ,查询到姓名 ", name) /* 删除操作 */ // 在哈希表中删除键值对 (key, value) delete(hmap, 10583) fmt.Println("\n删除 10583 后,哈希表为\nKey -> Value") PrintMap(hmap) /* 遍历哈希表 */ // 遍历键值对 key->value fmt.Println("\n遍历键值对 Key->Value") for key, value := range hmap { fmt.Println(key, "->", value) } // 单独遍历键 key fmt.Println("\n单独遍历键 Key") for key := range hmap { fmt.Println(key) } // 单独遍历值 value fmt.Println("\n单独遍历值 Value") for _, value := range hmap { fmt.Println(value) } } func TestSimpleHash(t *testing.T) { var hash int key := "Hello 算法" hash = addHash(key) fmt.Println("加法哈希值为 " + strconv.Itoa(hash)) hash = mulHash(key) fmt.Println("乘法哈希值为 " + strconv.Itoa(hash)) hash = xorHash(key) fmt.Println("异或哈希值为 " + strconv.Itoa(hash)) hash = rotHash(key) fmt.Println("旋转哈希值为 " + strconv.Itoa(hash)) } ================================================ FILE: codes/go/chapter_hashing/simple_hash.go ================================================ // File: simple_hash.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import "fmt" /* 加法哈希 */ func addHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = (hash + int64(b)) % modulus } return int(hash) } /* 乘法哈希 */ func mulHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = (31*hash + int64(b)) % modulus } return int(hash) } /* 异或哈希 */ func xorHash(key string) int { hash := 0 modulus := 1000000007 for _, b := range []byte(key) { fmt.Println(int(b)) hash ^= int(b) hash = (31*hash + int(b)) % modulus } return hash & modulus } /* 旋转哈希 */ func rotHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus } return int(hash) } ================================================ FILE: codes/go/chapter_heap/heap.go ================================================ // File: heap.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap // Go 语言中可以通过实现 heap.Interface 来构建整数大顶堆 // 实现 heap.Interface 需要同时实现 sort.Interface type intHeap []any // Push heap.Interface 的函数,实现推入元素到堆 func (h *intHeap) Push(x any) { // Push 和 Pop 使用 pointer receiver 作为参数 // 因为它们不仅会对切片的内容进行调整,还会修改切片的长度。 *h = append(*h, x.(int)) } // Pop heap.Interface 的函数,实现弹出堆顶元素 func (h *intHeap) Pop() any { // 待出堆元素存放在最后 last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Len sort.Interface 的函数 func (h *intHeap) Len() int { return len(*h) } // Less sort.Interface 的函数 func (h *intHeap) Less(i, j int) bool { // 如果实现小顶堆,则需要调整为小于号 return (*h)[i].(int) > (*h)[j].(int) } // Swap sort.Interface 的函数 func (h *intHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Top 获取堆顶元素 func (h *intHeap) Top() any { return (*h)[0] } ================================================ FILE: codes/go/chapter_heap/heap_test.go ================================================ // File: heap_test.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap import ( "container/heap" "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func testPush(h *intHeap, val int) { // 调用 heap.Interface 的函数,来添加元素 heap.Push(h, val) fmt.Printf("\n元素 %d 入堆后 \n", val) PrintHeap(*h) } func testPop(h *intHeap) { // 调用 heap.Interface 的函数,来移除元素 val := heap.Pop(h) fmt.Printf("\n堆顶元素 %d 出堆后 \n", val) PrintHeap(*h) } func TestHeap(t *testing.T) { /* 初始化堆 */ // 初始化大顶堆 maxHeap := &intHeap{} heap.Init(maxHeap) /* 元素入堆 */ testPush(maxHeap, 1) testPush(maxHeap, 3) testPush(maxHeap, 2) testPush(maxHeap, 5) testPush(maxHeap, 4) /* 获取堆顶元素 */ top := maxHeap.Top() fmt.Printf("堆顶元素为 %d\n", top) /* 堆顶元素出堆 */ testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) /* 获取堆大小 */ size := len(*maxHeap) fmt.Printf("堆元素数量为 %d\n", size) /* 判断堆是否为空 */ isEmpty := len(*maxHeap) == 0 fmt.Printf("堆是否为空 %t\n", isEmpty) } func TestMyHeap(t *testing.T) { /* 初始化堆 */ // 初始化大顶堆 maxHeap := newMaxHeap([]any{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}) fmt.Printf("输入数组并建堆后\n") maxHeap.print() /* 获取堆顶元素 */ peek := maxHeap.peek() fmt.Printf("\n堆顶元素为 %d\n", peek) /* 元素入堆 */ val := 7 maxHeap.push(val) fmt.Printf("\n元素 %d 入堆后\n", val) maxHeap.print() /* 堆顶元素出堆 */ peek = maxHeap.pop() fmt.Printf("\n堆顶元素 %d 出堆后\n", peek) maxHeap.print() /* 获取堆大小 */ size := maxHeap.size() fmt.Printf("\n堆元素数量为 %d\n", size) /* 判断堆是否为空 */ isEmpty := maxHeap.isEmpty() fmt.Printf("\n堆是否为空 %t\n", isEmpty) } func TestTopKHeap(t *testing.T) { /* 初始化堆 */ // 初始化大顶堆 nums := []int{1, 7, 6, 3, 2} k := 3 res := topKHeap(nums, k) fmt.Printf("最大的 " + strconv.Itoa(k) + " 个元素为") PrintHeap(*res) } ================================================ FILE: codes/go/chapter_heap/my_heap.go ================================================ // File: my_heap.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap import ( "fmt" . "github.com/krahets/hello-algo/pkg" ) type maxHeap struct { // 使用切片而非数组,这样无须考虑扩容问题 data []any } /* 构造函数,建立空堆 */ func newHeap() *maxHeap { return &maxHeap{ data: make([]any, 0), } } /* 构造函数,根据切片建堆 */ func newMaxHeap(nums []any) *maxHeap { // 将列表元素原封不动添加进堆 h := &maxHeap{data: nums} for i := h.parent(len(h.data) - 1); i >= 0; i-- { // 堆化除叶节点以外的其他所有节点 h.siftDown(i) } return h } /* 获取左子节点的索引 */ func (h *maxHeap) left(i int) int { return 2*i + 1 } /* 获取右子节点的索引 */ func (h *maxHeap) right(i int) int { return 2*i + 2 } /* 获取父节点的索引 */ func (h *maxHeap) parent(i int) int { // 向下整除 return (i - 1) / 2 } /* 交换元素 */ func (h *maxHeap) swap(i, j int) { h.data[i], h.data[j] = h.data[j], h.data[i] } /* 获取堆大小 */ func (h *maxHeap) size() int { return len(h.data) } /* 判断堆是否为空 */ func (h *maxHeap) isEmpty() bool { return len(h.data) == 0 } /* 访问堆顶元素 */ func (h *maxHeap) peek() any { return h.data[0] } /* 元素入堆 */ func (h *maxHeap) push(val any) { // 添加节点 h.data = append(h.data, val) // 从底至顶堆化 h.siftUp(len(h.data) - 1) } /* 从节点 i 开始,从底至顶堆化 */ func (h *maxHeap) siftUp(i int) { for true { // 获取节点 i 的父节点 p := h.parent(i) // 当“越过根节点”或“节点无须修复”时,结束堆化 if p < 0 || h.data[i].(int) <= h.data[p].(int) { break } // 交换两节点 h.swap(i, p) // 循环向上堆化 i = p } } /* 元素出堆 */ func (h *maxHeap) pop() any { // 判空处理 if h.isEmpty() { fmt.Println("error") return nil } // 交换根节点与最右叶节点(交换首元素与尾元素) h.swap(0, h.size()-1) // 删除节点 val := h.data[len(h.data)-1] h.data = h.data[:len(h.data)-1] // 从顶至底堆化 h.siftDown(0) // 返回堆顶元素 return val } /* 从节点 i 开始,从顶至底堆化 */ func (h *maxHeap) siftDown(i int) { for true { // 判断节点 i, l, r 中值最大的节点,记为 max l, r, max := h.left(i), h.right(i), i if l < h.size() && h.data[l].(int) > h.data[max].(int) { max = l } if r < h.size() && h.data[r].(int) > h.data[max].(int) { max = r } // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if max == i { break } // 交换两节点 h.swap(i, max) // 循环向下堆化 i = max } } /* 打印堆(二叉树) */ func (h *maxHeap) print() { PrintHeap(h.data) } ================================================ FILE: codes/go/chapter_heap/top_k.go ================================================ // File: top_k.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_heap import "container/heap" type minHeap []any func (h *minHeap) Len() int { return len(*h) } func (h *minHeap) Less(i, j int) bool { return (*h)[i].(int) < (*h)[j].(int) } func (h *minHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Push heap.Interface 的方法,实现推入元素到堆 func (h *minHeap) Push(x any) { *h = append(*h, x.(int)) } // Pop heap.Interface 的方法,实现弹出堆顶元素 func (h *minHeap) Pop() any { // 待出堆元素存放在最后 last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Top 获取堆顶元素 func (h *minHeap) Top() any { return (*h)[0] } /* 基于堆查找数组中最大的 k 个元素 */ func topKHeap(nums []int, k int) *minHeap { // 初始化小顶堆 h := &minHeap{} heap.Init(h) // 将数组的前 k 个元素入堆 for i := 0; i < k; i++ { heap.Push(h, nums[i]) } // 从第 k+1 个元素开始,保持堆的长度为 k for i := k; i < len(nums); i++ { // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 if nums[i] > h.Top().(int) { heap.Pop(h) heap.Push(h, nums[i]) } } return h } ================================================ FILE: codes/go/chapter_searching/binary_search.go ================================================ // File: binary_search.go // Created Time: 2022-12-05 // Author: Slone123c (274325721@qq.com) package chapter_searching /* 二分查找(双闭区间) */ func binarySearch(nums []int, target int) int { // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 i, j := 0, len(nums)-1 // 循环,当搜索区间为空时跳出(当 i > j 时为空) for i <= j { m := i + (j-i)/2 // 计算中点索引 m if nums[m] < target { // 此情况说明 target 在区间 [m+1, j] 中 i = m + 1 } else if nums[m] > target { // 此情况说明 target 在区间 [i, m-1] 中 j = m - 1 } else { // 找到目标元素,返回其索引 return m } } // 未找到目标元素,返回 -1 return -1 } /* 二分查找(左闭右开区间) */ func binarySearchLCRO(nums []int, target int) int { // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 i, j := 0, len(nums) // 循环,当搜索区间为空时跳出(当 i = j 时为空) for i < j { m := i + (j-i)/2 // 计算中点索引 m if nums[m] < target { // 此情况说明 target 在区间 [m+1, j) 中 i = m + 1 } else if nums[m] > target { // 此情况说明 target 在区间 [i, m) 中 j = m } else { // 找到目标元素,返回其索引 return m } } // 未找到目标元素,返回 -1 return -1 } ================================================ FILE: codes/go/chapter_searching/binary_search_edge.go ================================================ // File: binary_search_edge.go // Created Time: 2023-08-23 // Author: Reanon (793584285@qq.com) package chapter_searching /* 二分查找最左一个 target */ func binarySearchLeftEdge(nums []int, target int) int { // 等价于查找 target 的插入点 i := binarySearchInsertion(nums, target) // 未找到 target ,返回 -1 if i == len(nums) || nums[i] != target { return -1 } // 找到 target ,返回索引 i return i } /* 二分查找最右一个 target */ func binarySearchRightEdge(nums []int, target int) int { // 转化为查找最左一个 target + 1 i := binarySearchInsertion(nums, target+1) // j 指向最右一个 target ,i 指向首个大于 target 的元素 j := i - 1 // 未找到 target ,返回 -1 if j == -1 || nums[j] != target { return -1 } // 找到 target ,返回索引 j return j } ================================================ FILE: codes/go/chapter_searching/binary_search_insertion.go ================================================ // File: binary_search_insertion.go // Created Time: 2023-08-23 // Author: Reanon (793584285@qq.com) package chapter_searching /* 二分查找插入点(无重复元素) */ func binarySearchInsertionSimple(nums []int, target int) int { // 初始化双闭区间 [0, n-1] i, j := 0, len(nums)-1 for i <= j { // 计算中点索引 m m := i + (j-i)/2 if nums[m] < target { // target 在区间 [m+1, j] 中 i = m + 1 } else if nums[m] > target { // target 在区间 [i, m-1] 中 j = m - 1 } else { // 找到 target ,返回插入点 m return m } } // 未找到 target ,返回插入点 i return i } /* 二分查找插入点(存在重复元素) */ func binarySearchInsertion(nums []int, target int) int { // 初始化双闭区间 [0, n-1] i, j := 0, len(nums)-1 for i <= j { // 计算中点索引 m m := i + (j-i)/2 if nums[m] < target { // target 在区间 [m+1, j] 中 i = m + 1 } else if nums[m] > target { // target 在区间 [i, m-1] 中 j = m - 1 } else { // 首个小于 target 的元素在区间 [i, m-1] 中 j = m - 1 } } // 返回插入点 i return i } ================================================ FILE: codes/go/chapter_searching/binary_search_test.go ================================================ // File: binary_search_test.go // Created Time: 2022-12-05 // Author: Slone123c (274325721@qq.com) package chapter_searching import ( "fmt" "testing" ) func TestBinarySearch(t *testing.T) { var ( target = 6 nums = []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} expected = 2 ) // 在数组中执行二分查找 actual := binarySearch(nums, target) fmt.Println("目标元素 6 的索引 =", actual) if actual != expected { t.Errorf("目标元素 6 的索引 = %d, 应该为 %d", actual, expected) } } func TestBinarySearchEdge(t *testing.T) { // 包含重复元素的数组 nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} fmt.Println("\n数组 nums = ", nums) // 二分查找左边界和右边界 for _, target := range []int{6, 7} { index := binarySearchLeftEdge(nums, target) fmt.Println("最左一个元素", target, "的索引为", index) index = binarySearchRightEdge(nums, target) fmt.Println("最右一个元素", target, "的索引为", index) } } func TestBinarySearchInsertion(t *testing.T) { // 无重复元素的数组 nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} fmt.Println("数组 nums =", nums) // 二分查找插入点 for _, target := range []int{6, 9} { index := binarySearchInsertionSimple(nums, target) fmt.Println("元素", target, "的插入点的索引为", index) } // 包含重复元素的数组 nums = []int{1, 3, 6, 6, 6, 6, 6, 10, 12, 15} fmt.Println("\n数组 nums =", nums) // 二分查找插入点 for _, target := range []int{2, 6, 20} { index := binarySearchInsertion(nums, target) fmt.Println("元素", target, "的插入点的索引为", index) } } ================================================ FILE: codes/go/chapter_searching/hashing_search.go ================================================ // File: hashing_search.go // Created Time: 2022-12-12 // Author: Slone123c (274325721@qq.com) package chapter_searching import . "github.com/krahets/hello-algo/pkg" /* 哈希查找(数组) */ func hashingSearchArray(m map[int]int, target int) int { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 if index, ok := m[target]; ok { return index } else { return -1 } } /* 哈希查找(链表) */ func hashingSearchLinkedList(m map[int]*ListNode, target int) *ListNode { // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 nil if node, ok := m[target]; ok { return node } else { return nil } } ================================================ FILE: codes/go/chapter_searching/hashing_search_test.go ================================================ // File: hashing_search_test.go // Created Time: 2022-12-12 // Author: Slone123c (274325721@qq.com) package chapter_searching import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHashingSearch(t *testing.T) { target := 3 /* 哈希查找(数组) */ nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} // 初始化哈希表 m := make(map[int]int) for i := 0; i < len(nums); i++ { m[nums[i]] = i } index := hashingSearchArray(m, target) fmt.Println("目标元素 3 的索引 = ", index) /* 哈希查找(链表) */ head := ArrayToLinkedList(nums) // 初始化哈希表 m1 := make(map[int]*ListNode) for head != nil { m1[head.Val] = head head = head.Next } node := hashingSearchLinkedList(m1, target) fmt.Println("目标节点值 3 的对应节点对象为 ", node) } ================================================ FILE: codes/go/chapter_searching/linear_search.go ================================================ // File: linear_search.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_searching import ( . "github.com/krahets/hello-algo/pkg" ) /* 线性查找(数组) */ func linearSearchArray(nums []int, target int) int { // 遍历数组 for i := 0; i < len(nums); i++ { // 找到目标元素,返回其索引 if nums[i] == target { return i } } // 未找到目标元素,返回 -1 return -1 } /* 线性查找(链表) */ func linearSearchLinkedList(node *ListNode, target int) *ListNode { // 遍历链表 for node != nil { // 找到目标节点,返回之 if node.Val == target { return node } node = node.Next } // 未找到目标元素,返回 nil return nil } ================================================ FILE: codes/go/chapter_searching/linear_search_test.go ================================================ // File: linear_search_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_searching import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLinearSearch(t *testing.T) { target := 3 nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} // 在数组中执行线性查找 index := linearSearchArray(nums, target) fmt.Println("目标元素 3 的索引 =", index) // 在链表中执行线性查找 head := ArrayToLinkedList(nums) node := linearSearchLinkedList(head, target) fmt.Println("目标节点值 3 的对应节点对象为", node) } ================================================ FILE: codes/go/chapter_searching/two_sum.go ================================================ // File: two_sum.go // Created Time: 2022-11-25 // Author: reanon (793584285@qq.com) package chapter_searching /* 方法一:暴力枚举 */ func twoSumBruteForce(nums []int, target int) []int { size := len(nums) // 两层循环,时间复杂度为 O(n^2) for i := 0; i < size-1; i++ { for j := i + 1; j < size; j++ { if nums[i]+nums[j] == target { return []int{i, j} } } } return nil } /* 方法二:辅助哈希表 */ func twoSumHashTable(nums []int, target int) []int { // 辅助哈希表,空间复杂度为 O(n) hashTable := map[int]int{} // 单层循环,时间复杂度为 O(n) for idx, val := range nums { if preIdx, ok := hashTable[target-val]; ok { return []int{preIdx, idx} } hashTable[val] = idx } return nil } ================================================ FILE: codes/go/chapter_searching/two_sum_test.go ================================================ // File: two_sum_test.go // Created Time: 2022-11-25 // Author: reanon (793584285@qq.com) package chapter_searching import ( "fmt" "testing" ) func TestTwoSum(t *testing.T) { // ======= Test Case ======= nums := []int{2, 7, 11, 15} target := 13 // ====== Driver Code ====== // 方法一:暴力解法 res := twoSumBruteForce(nums, target) fmt.Println("方法一 res =", res) // 方法二:哈希表 res = twoSumHashTable(nums, target) fmt.Println("方法二 res =", res) } ================================================ FILE: codes/go/chapter_sorting/bubble_sort.go ================================================ // File: bubble_sort.go // Created Time: 2022-12-06 // Author: Slone123c (274325721@qq.com) package chapter_sorting /* 冒泡排序 */ func bubbleSort(nums []int) { // 外循环:未排序区间为 [0, i] for i := len(nums) - 1; i > 0; i-- { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // 交换 nums[j] 与 nums[j + 1] nums[j], nums[j+1] = nums[j+1], nums[j] } } } } /* 冒泡排序(标志优化)*/ func bubbleSortWithFlag(nums []int) { // 外循环:未排序区间为 [0, i] for i := len(nums) - 1; i > 0; i-- { flag := false // 初始化标志位 // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // 交换 nums[j] 与 nums[j + 1] nums[j], nums[j+1] = nums[j+1], nums[j] flag = true // 记录交换元素 } } if flag == false { // 此轮“冒泡”未交换任何元素,直接跳出 break } } } ================================================ FILE: codes/go/chapter_sorting/bubble_sort_test.go ================================================ // File: bubble_sort_test.go // Created Time: 2022-12-06 // Author: Slone123c (274325721@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestBubbleSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} bubbleSort(nums) fmt.Println("冒泡排序完成后 nums = ", nums) nums1 := []int{4, 1, 3, 1, 5, 2} bubbleSortWithFlag(nums1) fmt.Println("冒泡排序完成后 nums1 = ", nums1) } ================================================ FILE: codes/go/chapter_sorting/bucket_sort.go ================================================ // File: bucket_sort.go // Created Time: 2023-03-27 // Author: Reanon (793584285@qq.com) package chapter_sorting import "sort" /* 桶排序 */ func bucketSort(nums []float64) { // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 k := len(nums) / 2 buckets := make([][]float64, k) for i := 0; i < k; i++ { buckets[i] = make([]float64, 0) } // 1. 将数组元素分配到各个桶中 for _, num := range nums { // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] i := int(num * float64(k)) // 将 num 添加进桶 i buckets[i] = append(buckets[i], num) } // 2. 对各个桶执行排序 for i := 0; i < k; i++ { // 使用内置切片排序函数,也可以替换成其他排序算法 sort.Float64s(buckets[i]) } // 3. 遍历桶合并结果 i := 0 for _, bucket := range buckets { for _, num := range bucket { nums[i] = num i++ } } } ================================================ FILE: codes/go/chapter_sorting/bucket_sort_test.go ================================================ // File: bucket_sort_test.go // Created Time: 2023-03-27 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestBucketSort(t *testing.T) { // 设输入数据为浮点数,范围为 [0, 1) nums := []float64{0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37} bucketSort(nums) fmt.Println("桶排序完成后 nums = ", nums) } ================================================ FILE: codes/go/chapter_sorting/counting_sort.go ================================================ // File: counting_sort.go // Created Time: 2023-03-20 // Author: Reanon (793584285@qq.com) package chapter_sorting type CountingSort struct{} /* 计数排序 */ // 简单实现,无法用于排序对象 func countingSortNaive(nums []int) { // 1. 统计数组最大元素 m m := 0 for _, num := range nums { if num > m { m = num } } // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 counter := make([]int, m+1) for _, num := range nums { counter[num]++ } // 3. 遍历 counter ,将各元素填入原数组 nums for i, num := 0, 0; num < m+1; num++ { for j := 0; j < counter[num]; j++ { nums[i] = num i++ } } } /* 计数排序 */ // 完整实现,可排序对象,并且是稳定排序 func countingSort(nums []int) { // 1. 统计数组最大元素 m m := 0 for _, num := range nums { if num > m { m = num } } // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 counter := make([]int, m+1) for _, num := range nums { counter[num]++ } // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 for i := 0; i < m; i++ { counter[i+1] += counter[i] } // 4. 倒序遍历 nums ,将各元素填入结果数组 res // 初始化数组 res 用于记录结果 n := len(nums) res := make([]int, n) for i := n - 1; i >= 0; i-- { num := nums[i] // 将 num 放置到对应索引处 res[counter[num]-1] = num // 令前缀和自减 1 ,得到下次放置 num 的索引 counter[num]-- } // 使用结果数组 res 覆盖原数组 nums copy(nums, res) } ================================================ FILE: codes/go/chapter_sorting/counting_sort_test.go ================================================ // File: counting_sort_test.go // Created Time: 2023-03-20 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestCountingSort(t *testing.T) { nums := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} countingSortNaive(nums) fmt.Println("计数排序(无法排序对象)完成后 nums = ", nums) nums1 := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} countingSort(nums1) fmt.Println("计数排序完成后 nums1 = ", nums1) } ================================================ FILE: codes/go/chapter_sorting/heap_sort.go ================================================ // File: heap_sort.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ func siftDown(nums *[]int, n, i int) { for true { // 判断节点 i, l, r 中值最大的节点,记为 ma l := 2*i + 1 r := 2*i + 2 ma := i if l < n && (*nums)[l] > (*nums)[ma] { ma = l } if r < n && (*nums)[r] > (*nums)[ma] { ma = r } // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if ma == i { break } // 交换两节点 (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i] // 循环向下堆化 i = ma } } /* 堆排序 */ func heapSort(nums *[]int) { // 建堆操作:堆化除叶节点以外的其他所有节点 for i := len(*nums)/2 - 1; i >= 0; i-- { siftDown(nums, len(*nums), i) } // 从堆中提取最大元素,循环 n-1 轮 for i := len(*nums) - 1; i > 0; i-- { // 交换根节点与最右叶节点(交换首元素与尾元素) (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0] // 以根节点为起点,从顶至底进行堆化 siftDown(nums, i, 0) } } ================================================ FILE: codes/go/chapter_sorting/heap_sort_test.go ================================================ // File: heap_sort_test.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestHeapSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} heapSort(&nums) fmt.Println("堆排序完成后 nums = ", nums) } ================================================ FILE: codes/go/chapter_sorting/insertion_sort.go ================================================ // File: insertion_sort.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting /* 插入排序 */ func insertionSort(nums []int) { // 外循环:已排序区间为 [0, i-1] for i := 1; i < len(nums); i++ { base := nums[i] j := i - 1 // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 for j >= 0 && nums[j] > base { nums[j+1] = nums[j] // 将 nums[j] 向右移动一位 j-- } nums[j+1] = base // 将 base 赋值到正确位置 } } ================================================ FILE: codes/go/chapter_sorting/insertion_sort_test.go ================================================ // File: insertion_sort_test.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) func TestInsertionSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} insertionSort(nums) fmt.Println("插入排序完成后 nums =", nums) } ================================================ FILE: codes/go/chapter_sorting/merge_sort.go ================================================ // File: merge_sort.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting /* 合并左子数组和右子数组 */ func merge(nums []int, left, mid, right int) { // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] // 创建一个临时数组 tmp ,用于存放合并后的结果 tmp := make([]int, right-left+1) // 初始化左子数组和右子数组的起始索引 i, j, k := left, mid+1, 0 // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 for i <= mid && j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i] i++ } else { tmp[k] = nums[j] j++ } k++ } // 将左子数组和右子数组的剩余元素复制到临时数组中 for i <= mid { tmp[k] = nums[i] i++ k++ } for j <= right { tmp[k] = nums[j] j++ k++ } // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 for k := 0; k < len(tmp); k++ { nums[left+k] = tmp[k] } } /* 归并排序 */ func mergeSort(nums []int, left, right int) { // 终止条件 if left >= right { return } // 划分阶段 mid := left + (right - left) / 2 mergeSort(nums, left, mid) mergeSort(nums, mid+1, right) // 合并阶段 merge(nums, left, mid, right) } ================================================ FILE: codes/go/chapter_sorting/merge_sort_test.go ================================================ // File: merge_sort_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) func TestMergeSort(t *testing.T) { nums := []int{7, 3, 2, 6, 0, 1, 5, 4} mergeSort(nums, 0, len(nums)-1) fmt.Println("归并排序完成后 nums = ", nums) } ================================================ FILE: codes/go/chapter_sorting/quick_sort.go ================================================ // File: quick_sort.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting // 快速排序 type quickSort struct{} // 快速排序(中位基准数优化) type quickSortMedian struct{} // 快速排序(递归深度优化) type quickSortTailCall struct{} /* 哨兵划分 */ func (q *quickSort) partition(nums []int, left, right int) int { // 以 nums[left] 为基准数 i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- // 从右向左找首个小于基准数的元素 } for i < j && nums[i] <= nums[left] { i++ // 从左向右找首个大于基准数的元素 } // 元素交换 nums[i], nums[j] = nums[j], nums[i] } // 将基准数交换至两子数组的分界线 nums[i], nums[left] = nums[left], nums[i] return i // 返回基准数的索引 } /* 快速排序 */ func (q *quickSort) quickSort(nums []int, left, right int) { // 子数组长度为 1 时终止递归 if left >= right { return } // 哨兵划分 pivot := q.partition(nums, left, right) // 递归左子数组、右子数组 q.quickSort(nums, left, pivot-1) q.quickSort(nums, pivot+1, right) } /* 选取三个候选元素的中位数 */ func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int { l, m, r := nums[left], nums[mid], nums[right] if (l <= m && m <= r) || (r <= m && m <= l) { return mid // m 在 l 和 r 之间 } if (m <= l && l <= r) || (r <= l && l <= m) { return left // l 在 m 和 r 之间 } return right } /* 哨兵划分(三数取中值)*/ func (q *quickSortMedian) partition(nums []int, left, right int) int { // 以 nums[left] 为基准数 med := q.medianThree(nums, left, (left+right)/2, right) // 将中位数交换至数组最左端 nums[left], nums[med] = nums[med], nums[left] // 以 nums[left] 为基准数 i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- //从右向左找首个小于基准数的元素 } for i < j && nums[i] <= nums[left] { i++ //从左向右找首个大于基准数的元素 } //元素交换 nums[i], nums[j] = nums[j], nums[i] } //将基准数交换至两子数组的分界线 nums[i], nums[left] = nums[left], nums[i] return i //返回基准数的索引 } /* 快速排序 */ func (q *quickSortMedian) quickSort(nums []int, left, right int) { // 子数组长度为 1 时终止递归 if left >= right { return } // 哨兵划分 pivot := q.partition(nums, left, right) // 递归左子数组、右子数组 q.quickSort(nums, left, pivot-1) q.quickSort(nums, pivot+1, right) } /* 哨兵划分 */ func (q *quickSortTailCall) partition(nums []int, left, right int) int { // 以 nums[left] 为基准数 i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- // 从右向左找首个小于基准数的元素 } for i < j && nums[i] <= nums[left] { i++ // 从左向右找首个大于基准数的元素 } // 元素交换 nums[i], nums[j] = nums[j], nums[i] } // 将基准数交换至两子数组的分界线 nums[i], nums[left] = nums[left], nums[i] return i // 返回基准数的索引 } /* 快速排序(递归深度优化)*/ func (q *quickSortTailCall) quickSort(nums []int, left, right int) { // 子数组长度为 1 时终止 for left < right { // 哨兵划分操作 pivot := q.partition(nums, left, right) // 对两个子数组中较短的那个执行快速排序 if pivot-left < right-pivot { q.quickSort(nums, left, pivot-1) // 递归排序左子数组 left = pivot + 1 // 剩余未排序区间为 [pivot + 1, right] } else { q.quickSort(nums, pivot+1, right) // 递归排序右子数组 right = pivot - 1 // 剩余未排序区间为 [left, pivot - 1] } } } ================================================ FILE: codes/go/chapter_sorting/quick_sort_test.go ================================================ // File: quick_sort_test.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) // 快速排序 func TestQuickSort(t *testing.T) { q := quickSort{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("快速排序完成后 nums = ", nums) } // 快速排序(中位基准数优化) func TestQuickSortMedian(t *testing.T) { q := quickSortMedian{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("快速排序(中位基准数优化)完成后 nums = ", nums) } // 快速排序(递归深度优化) func TestQuickSortTailCall(t *testing.T) { q := quickSortTailCall{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("快速排序(递归深度优化)完成后 nums = ", nums) } ================================================ FILE: codes/go/chapter_sorting/radix_sort.go ================================================ // File: radix_sort.go // Created Time: 2023-01-18 // Author: Reanon (793584285@qq.com) package chapter_sorting import "math" /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ func digit(num, exp int) int { // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 return (num / exp) % 10 } /* 计数排序(根据 nums 第 k 位排序) */ func countingSortDigit(nums []int, exp int) { // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 counter := make([]int, 10) n := len(nums) // 统计 0~9 各数字的出现次数 for i := 0; i < n; i++ { d := digit(nums[i], exp) // 获取 nums[i] 第 k 位,记为 d counter[d]++ // 统计数字 d 的出现次数 } // 求前缀和,将“出现个数”转换为“数组索引” for i := 1; i < 10; i++ { counter[i] += counter[i-1] } // 倒序遍历,根据桶内统计结果,将各元素填入 res res := make([]int, n) for i := n - 1; i >= 0; i-- { d := digit(nums[i], exp) j := counter[d] - 1 // 获取 d 在数组中的索引 j res[j] = nums[i] // 将当前元素填入索引 j counter[d]-- // 将 d 的数量减 1 } // 使用结果覆盖原数组 nums for i := 0; i < n; i++ { nums[i] = res[i] } } /* 基数排序 */ func radixSort(nums []int) { // 获取数组的最大元素,用于判断最大位数 max := math.MinInt for _, num := range nums { if num > max { max = num } } // 按照从低位到高位的顺序遍历 for exp := 1; max >= exp; exp *= 10 { // 对数组元素的第 k 位执行计数排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums, exp) } } ================================================ FILE: codes/go/chapter_sorting/radix_sort_test.go ================================================ // File: radix_sort_test.go // Created Time: 2023-01-18 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestRadixSort(t *testing.T) { /* 基数排序 */ nums := []int{10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996} radixSort(nums) fmt.Println("基数排序完成后 nums = ", nums) } ================================================ FILE: codes/go/chapter_sorting/selection_sort.go ================================================ // File: selection_sort.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting /* 选择排序 */ func selectionSort(nums []int) { n := len(nums) // 外循环:未排序区间为 [i, n-1] for i := 0; i < n-1; i++ { // 内循环:找到未排序区间内的最小元素 k := i for j := i + 1; j < n; j++ { if nums[j] < nums[k] { // 记录最小元素的索引 k = j } } // 将该最小元素与未排序区间的首个元素交换 nums[i], nums[k] = nums[k], nums[i] } } ================================================ FILE: codes/go/chapter_sorting/selection_sort_test.go ================================================ // File: selection_sort_test.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestSelectionSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} selectionSort(nums) fmt.Println("选择排序完成后 nums = ", nums) } ================================================ FILE: codes/go/chapter_stack_and_queue/array_deque.go ================================================ // File: array_deque.go // Created Time: 2023-03-13 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import "fmt" /* 基于环形数组实现的双向队列 */ type arrayDeque struct { nums []int // 用于存储双向队列元素的数组 front int // 队首指针,指向队首元素 queSize int // 双向队列长度 queCapacity int // 队列容量(即最大容纳元素数量) } /* 初始化队列 */ func newArrayDeque(queCapacity int) *arrayDeque { return &arrayDeque{ nums: make([]int, queCapacity), queCapacity: queCapacity, front: 0, queSize: 0, } } /* 获取双向队列的长度 */ func (q *arrayDeque) size() int { return q.queSize } /* 判断双向队列是否为空 */ func (q *arrayDeque) isEmpty() bool { return q.queSize == 0 } /* 计算环形数组索引 */ func (q *arrayDeque) index(i int) int { // 通过取余操作实现数组首尾相连 // 当 i 越过数组尾部后,回到头部 // 当 i 越过数组头部后,回到尾部 return (i + q.queCapacity) % q.queCapacity } /* 队首入队 */ func (q *arrayDeque) pushFirst(num int) { if q.queSize == q.queCapacity { fmt.Println("双向队列已满") return } // 队首指针向左移动一位 // 通过取余操作实现 front 越过数组头部后回到尾部 q.front = q.index(q.front - 1) // 将 num 添加至队首 q.nums[q.front] = num q.queSize++ } /* 队尾入队 */ func (q *arrayDeque) pushLast(num int) { if q.queSize == q.queCapacity { fmt.Println("双向队列已满") return } // 计算队尾指针,指向队尾索引 + 1 rear := q.index(q.front + q.queSize) // 将 num 添加至队尾 q.nums[rear] = num q.queSize++ } /* 队首出队 */ func (q *arrayDeque) popFirst() any { num := q.peekFirst() if num == nil { return nil } // 队首指针向后移动一位 q.front = q.index(q.front + 1) q.queSize-- return num } /* 队尾出队 */ func (q *arrayDeque) popLast() any { num := q.peekLast() if num == nil { return nil } q.queSize-- return num } /* 访问队首元素 */ func (q *arrayDeque) peekFirst() any { if q.isEmpty() { return nil } return q.nums[q.front] } /* 访问队尾元素 */ func (q *arrayDeque) peekLast() any { if q.isEmpty() { return nil } // 计算尾元素索引 last := q.index(q.front + q.queSize - 1) return q.nums[last] } /* 获取 Slice 用于打印 */ func (q *arrayDeque) toSlice() []int { // 仅转换有效长度范围内的列表元素 res := make([]int, q.queSize) for i, j := 0, q.front; i < q.queSize; i++ { res[i] = q.nums[q.index(j)] j++ } return res } ================================================ FILE: codes/go/chapter_stack_and_queue/array_queue.go ================================================ // File: array_queue.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue /* 基于环形数组实现的队列 */ type arrayQueue struct { nums []int // 用于存储队列元素的数组 front int // 队首指针,指向队首元素 queSize int // 队列长度 queCapacity int // 队列容量(即最大容纳元素数量) } /* 初始化队列 */ func newArrayQueue(queCapacity int) *arrayQueue { return &arrayQueue{ nums: make([]int, queCapacity), queCapacity: queCapacity, front: 0, queSize: 0, } } /* 获取队列的长度 */ func (q *arrayQueue) size() int { return q.queSize } /* 判断队列是否为空 */ func (q *arrayQueue) isEmpty() bool { return q.queSize == 0 } /* 入队 */ func (q *arrayQueue) push(num int) { // 当 rear == queCapacity 表示队列已满 if q.queSize == q.queCapacity { return } // 计算队尾指针,指向队尾索引 + 1 // 通过取余操作实现 rear 越过数组尾部后回到头部 rear := (q.front + q.queSize) % q.queCapacity // 将 num 添加至队尾 q.nums[rear] = num q.queSize++ } /* 出队 */ func (q *arrayQueue) pop() any { num := q.peek() if num == nil { return nil } // 队首指针向后移动一位,若越过尾部,则返回到数组头部 q.front = (q.front + 1) % q.queCapacity q.queSize-- return num } /* 访问队首元素 */ func (q *arrayQueue) peek() any { if q.isEmpty() { return nil } return q.nums[q.front] } /* 获取 Slice 用于打印 */ func (q *arrayQueue) toSlice() []int { rear := (q.front + q.queSize) if rear >= q.queCapacity { rear %= q.queCapacity return append(q.nums[q.front:], q.nums[:rear]...) } return q.nums[q.front:rear] } ================================================ FILE: codes/go/chapter_stack_and_queue/array_stack.go ================================================ // File: array_stack.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue /* 基于数组实现的栈 */ type arrayStack struct { data []int // 数据 } /* 初始化栈 */ func newArrayStack() *arrayStack { return &arrayStack{ // 设置栈的长度为 0,容量为 16 data: make([]int, 0, 16), } } /* 栈的长度 */ func (s *arrayStack) size() int { return len(s.data) } /* 栈是否为空 */ func (s *arrayStack) isEmpty() bool { return s.size() == 0 } /* 入栈 */ func (s *arrayStack) push(v int) { // 切片会自动扩容 s.data = append(s.data, v) } /* 出栈 */ func (s *arrayStack) pop() any { val := s.peek() s.data = s.data[:len(s.data)-1] return val } /* 获取栈顶元素 */ func (s *arrayStack) peek() any { if s.isEmpty() { return nil } val := s.data[len(s.data)-1] return val } /* 获取 Slice 用于打印 */ func (s *arrayStack) toSlice() []int { return s.data } ================================================ FILE: codes/go/chapter_stack_and_queue/deque_test.go ================================================ // File: deque_test.go // Created Time: 2022-11-29 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestDeque(t *testing.T) { /* 初始化双向队列 */ // 在 Go 中,将 list 作为双向队列使用 deque := list.New() /* 元素入队 */ deque.PushBack(2) deque.PushBack(5) deque.PushBack(4) deque.PushFront(3) deque.PushFront(1) fmt.Print("双向队列 deque = ") PrintList(deque) /* 访问元素 */ front := deque.Front() fmt.Println("队首元素 front =", front.Value) rear := deque.Back() fmt.Println("队尾元素 rear =", rear.Value) /* 元素出队 */ deque.Remove(front) fmt.Print("队首出队元素 front = ", front.Value, ",队首出队后 deque = ") PrintList(deque) deque.Remove(rear) fmt.Print("队尾出队元素 rear = ", rear.Value, ",队尾出队后 deque = ") PrintList(deque) /* 获取双向队列的长度 */ size := deque.Len() fmt.Println("双向队列长度 size =", size) /* 判断双向队列是否为空 */ isEmpty := deque.Len() == 0 fmt.Println("双向队列是否为空 =", isEmpty) } func TestArrayDeque(t *testing.T) { /* 初始化双向队列 */ // 在 Go 中,将 list 作为双向队列使用 deque := newArrayDeque(16) /* 元素入队 */ deque.pushLast(3) deque.pushLast(2) deque.pushLast(5) fmt.Print("双向队列 deque = ") PrintSlice(deque.toSlice()) /* 访问元素 */ peekFirst := deque.peekFirst() fmt.Println("队首元素 peekFirst =", peekFirst) peekLast := deque.peekLast() fmt.Println("队尾元素 peekLast =", peekLast) /* 元素入队 */ deque.pushLast(4) fmt.Print("元素 4 队尾入队后 deque = ") PrintSlice(deque.toSlice()) deque.pushFirst(1) fmt.Print("元素 1 队首入队后 deque = ") PrintSlice(deque.toSlice()) /* 元素出队 */ popFirst := deque.popFirst() fmt.Print("队首出队元素 popFirst = ", popFirst, ",队首出队后 deque = ") PrintSlice(deque.toSlice()) popLast := deque.popLast() fmt.Print("队尾出队元素 popLast = ", popLast, ",队尾出队后 deque = ") PrintSlice(deque.toSlice()) /* 获取双向队列的长度 */ size := deque.size() fmt.Println("双向队列长度 size =", size) /* 判断双向队列是否为空 */ isEmpty := deque.isEmpty() fmt.Println("双向队列是否为空 =", isEmpty) } func TestLinkedListDeque(t *testing.T) { // 初始化队列 deque := newLinkedListDeque() // 元素入队 deque.pushLast(2) deque.pushLast(5) deque.pushLast(4) deque.pushFirst(3) deque.pushFirst(1) fmt.Print("队列 deque = ") PrintList(deque.toList()) // 访问队首元素 front := deque.peekFirst() fmt.Println("队首元素 front =", front) rear := deque.peekLast() fmt.Println("队尾元素 rear =", rear) // 元素出队 popFirst := deque.popFirst() fmt.Print("队首出队元素 popFirst = ", popFirst, ",队首出队后 deque = ") PrintList(deque.toList()) popLast := deque.popLast() fmt.Print("队尾出队元素 popLast = ", popLast, ",队尾出队后 deque = ") PrintList(deque.toList()) // 获取队的长度 size := deque.size() fmt.Println("队的长度 size =", size) // 判断是否为空 isEmpty := deque.isEmpty() fmt.Println("队是否为空 =", isEmpty) } // BenchmarkLinkedListDeque 67.92 ns/op in Mac M1 Pro func BenchmarkLinkedListDeque(b *testing.B) { deque := newLinkedListDeque() // use b.N for looping for i := 0; i < b.N; i++ { deque.pushLast(777) } for i := 0; i < b.N; i++ { deque.popFirst() } } ================================================ FILE: codes/go/chapter_stack_and_queue/linkedlist_deque.go ================================================ // File: linkedlist_deque.go // Created Time: 2022-11-29 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* 基于双向链表实现的双向队列 */ type linkedListDeque struct { // 使用内置包 list data *list.List } /* 初始化双端队列 */ func newLinkedListDeque() *linkedListDeque { return &linkedListDeque{ data: list.New(), } } /* 队首元素入队 */ func (s *linkedListDeque) pushFirst(value any) { s.data.PushFront(value) } /* 队尾元素入队 */ func (s *linkedListDeque) pushLast(value any) { s.data.PushBack(value) } /* 队首元素出队 */ func (s *linkedListDeque) popFirst() any { if s.isEmpty() { return nil } e := s.data.Front() s.data.Remove(e) return e.Value } /* 队尾元素出队 */ func (s *linkedListDeque) popLast() any { if s.isEmpty() { return nil } e := s.data.Back() s.data.Remove(e) return e.Value } /* 访问队首元素 */ func (s *linkedListDeque) peekFirst() any { if s.isEmpty() { return nil } e := s.data.Front() return e.Value } /* 访问队尾元素 */ func (s *linkedListDeque) peekLast() any { if s.isEmpty() { return nil } e := s.data.Back() return e.Value } /* 获取队列的长度 */ func (s *linkedListDeque) size() int { return s.data.Len() } /* 判断队列是否为空 */ func (s *linkedListDeque) isEmpty() bool { return s.data.Len() == 0 } /* 获取 List 用于打印 */ func (s *linkedListDeque) toList() *list.List { return s.data } ================================================ FILE: codes/go/chapter_stack_and_queue/linkedlist_queue.go ================================================ // File: linkedlist_queue.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* 基于链表实现的队列 */ type linkedListQueue struct { // 使用内置包 list 来实现队列 data *list.List } /* 初始化队列 */ func newLinkedListQueue() *linkedListQueue { return &linkedListQueue{ data: list.New(), } } /* 入队 */ func (s *linkedListQueue) push(value any) { s.data.PushBack(value) } /* 出队 */ func (s *linkedListQueue) pop() any { if s.isEmpty() { return nil } e := s.data.Front() s.data.Remove(e) return e.Value } /* 访问队首元素 */ func (s *linkedListQueue) peek() any { if s.isEmpty() { return nil } e := s.data.Front() return e.Value } /* 获取队列的长度 */ func (s *linkedListQueue) size() int { return s.data.Len() } /* 判断队列是否为空 */ func (s *linkedListQueue) isEmpty() bool { return s.data.Len() == 0 } /* 获取 List 用于打印 */ func (s *linkedListQueue) toList() *list.List { return s.data } ================================================ FILE: codes/go/chapter_stack_and_queue/linkedlist_stack.go ================================================ // File: linkedlist_stack.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* 基于链表实现的栈 */ type linkedListStack struct { // 使用内置包 list 来实现栈 data *list.List } /* 初始化栈 */ func newLinkedListStack() *linkedListStack { return &linkedListStack{ data: list.New(), } } /* 入栈 */ func (s *linkedListStack) push(value int) { s.data.PushBack(value) } /* 出栈 */ func (s *linkedListStack) pop() any { if s.isEmpty() { return nil } e := s.data.Back() s.data.Remove(e) return e.Value } /* 访问栈顶元素 */ func (s *linkedListStack) peek() any { if s.isEmpty() { return nil } e := s.data.Back() return e.Value } /* 获取栈的长度 */ func (s *linkedListStack) size() int { return s.data.Len() } /* 判断栈是否为空 */ func (s *linkedListStack) isEmpty() bool { return s.data.Len() == 0 } /* 获取 List 用于打印 */ func (s *linkedListStack) toList() *list.List { return s.data } ================================================ FILE: codes/go/chapter_stack_and_queue/queue_test.go ================================================ // File: queue_test.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestQueue(t *testing.T) { /* 初始化队列 */ // 在 Go 中,将 list 作为队列来使用 queue := list.New() /* 元素入队 */ queue.PushBack(1) queue.PushBack(3) queue.PushBack(2) queue.PushBack(5) queue.PushBack(4) fmt.Print("队列 queue = ") PrintList(queue) /* 访问队首元素 */ peek := queue.Front() fmt.Println("队首元素 peek =", peek.Value) /* 元素出队 */ pop := queue.Front() queue.Remove(pop) fmt.Print("出队元素 pop = ", pop.Value, ",出队后 queue = ") PrintList(queue) /* 获取队列的长度 */ size := queue.Len() fmt.Println("队列长度 size =", size) /* 判断队列是否为空 */ isEmpty := queue.Len() == 0 fmt.Println("队列是否为空 =", isEmpty) } func TestArrayQueue(t *testing.T) { // 初始化队列,使用队列的通用接口 capacity := 10 queue := newArrayQueue(capacity) if queue.pop() != nil { t.Errorf("want:%v,got:%v", nil, queue.pop()) } // 元素入队 queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) fmt.Print("队列 queue = ") PrintSlice(queue.toSlice()) // 访问队首元素 peek := queue.peek() fmt.Println("队首元素 peek =", peek) // 元素出队 pop := queue.pop() fmt.Print("出队元素 pop = ", pop, ", 出队后 queue = ") PrintSlice(queue.toSlice()) // 获取队的长度 size := queue.size() fmt.Println("队的长度 size =", size) // 判断是否为空 isEmpty := queue.isEmpty() fmt.Println("队是否为空 =", isEmpty) /* 测试环形数组 */ for i := 0; i < 10; i++ { queue.push(i) queue.pop() fmt.Print("第", i, "轮入队 + 出队后 queue =") PrintSlice(queue.toSlice()) } } func TestLinkedListQueue(t *testing.T) { // 初始化队 queue := newLinkedListQueue() // 元素入队 queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) fmt.Print("队列 queue = ") PrintList(queue.toList()) // 访问队首元素 peek := queue.peek() fmt.Println("队首元素 peek =", peek) // 元素出队 pop := queue.pop() fmt.Print("出队元素 pop = ", pop, ", 出队后 queue = ") PrintList(queue.toList()) // 获取队的长度 size := queue.size() fmt.Println("队的长度 size =", size) // 判断是否为空 isEmpty := queue.isEmpty() fmt.Println("队是否为空 =", isEmpty) } // BenchmarkArrayQueue 8 ns/op in Mac M1 Pro func BenchmarkArrayQueue(b *testing.B) { capacity := 1000 queue := newArrayQueue(capacity) // use b.N for looping for i := 0; i < b.N; i++ { queue.push(777) } for i := 0; i < b.N; i++ { queue.pop() } } // BenchmarkLinkedQueue 62.66 ns/op in Mac M1 Pro func BenchmarkLinkedQueue(b *testing.B) { queue := newLinkedListQueue() // use b.N for looping for i := 0; i < b.N; i++ { queue.push(777) } for i := 0; i < b.N; i++ { queue.pop() } } ================================================ FILE: codes/go/chapter_stack_and_queue/stack_test.go ================================================ // File: stack_test.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestStack(t *testing.T) { /* 初始化栈 */ // 在 Go 中,推荐将 Slice 当作栈来使用 var stack []int /* 元素入栈 */ stack = append(stack, 1) stack = append(stack, 3) stack = append(stack, 2) stack = append(stack, 5) stack = append(stack, 4) fmt.Print("栈 stack = ") PrintSlice(stack) /* 访问栈顶元素 */ peek := stack[len(stack)-1] fmt.Println("栈顶元素 peek =", peek) /* 元素出栈 */ pop := stack[len(stack)-1] stack = stack[:len(stack)-1] fmt.Print("出栈元素 pop = ", pop, ",出栈后 stack = ") PrintSlice(stack) /* 获取栈的长度 */ size := len(stack) fmt.Println("栈的长度 size =", size) /* 判断是否为空 */ isEmpty := len(stack) == 0 fmt.Println("栈是否为空 =", isEmpty) } func TestArrayStack(t *testing.T) { // 初始化栈, 使用接口承接 stack := newArrayStack() // 元素入栈 stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) fmt.Print("栈 stack = ") PrintSlice(stack.toSlice()) // 访问栈顶元素 peek := stack.peek() fmt.Println("栈顶元素 peek =", peek) // 元素出栈 pop := stack.pop() fmt.Print("出栈元素 pop = ", pop, ", 出栈后 stack = ") PrintSlice(stack.toSlice()) // 获取栈的长度 size := stack.size() fmt.Println("栈的长度 size =", size) // 判断是否为空 isEmpty := stack.isEmpty() fmt.Println("栈是否为空 =", isEmpty) } func TestLinkedListStack(t *testing.T) { // 初始化栈 stack := newLinkedListStack() // 元素入栈 stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) fmt.Print("栈 stack = ") PrintList(stack.toList()) // 访问栈顶元素 peek := stack.peek() fmt.Println("栈顶元素 peek =", peek) // 元素出栈 pop := stack.pop() fmt.Print("出栈元素 pop = ", pop, ", 出栈后 stack = ") PrintList(stack.toList()) // 获取栈的长度 size := stack.size() fmt.Println("栈的长度 size =", size) // 判断是否为空 isEmpty := stack.isEmpty() fmt.Println("栈是否为空 =", isEmpty) } // BenchmarkArrayStack 8 ns/op in Mac M1 Pro func BenchmarkArrayStack(b *testing.B) { stack := newArrayStack() // use b.N for looping for i := 0; i < b.N; i++ { stack.push(777) } for i := 0; i < b.N; i++ { stack.pop() } } // BenchmarkLinkedListStack 65.02 ns/op in Mac M1 Pro func BenchmarkLinkedListStack(b *testing.B) { stack := newLinkedListStack() // use b.N for looping for i := 0; i < b.N; i++ { stack.push(777) } for i := 0; i < b.N; i++ { stack.pop() } } ================================================ FILE: codes/go/chapter_tree/array_binary_tree.go ================================================ // File: array_binary_tree.go // Created Time: 2023-07-24 // Author: Reanon (793584285@qq.com) package chapter_tree /* 数组表示下的二叉树类 */ type arrayBinaryTree struct { tree []any } /* 构造方法 */ func newArrayBinaryTree(arr []any) *arrayBinaryTree { return &arrayBinaryTree{ tree: arr, } } /* 列表容量 */ func (abt *arrayBinaryTree) size() int { return len(abt.tree) } /* 获取索引为 i 节点的值 */ func (abt *arrayBinaryTree) val(i int) any { // 若索引越界,则返回 null ,代表空位 if i < 0 || i >= abt.size() { return nil } return abt.tree[i] } /* 获取索引为 i 节点的左子节点的索引 */ func (abt *arrayBinaryTree) left(i int) int { return 2*i + 1 } /* 获取索引为 i 节点的右子节点的索引 */ func (abt *arrayBinaryTree) right(i int) int { return 2*i + 2 } /* 获取索引为 i 节点的父节点的索引 */ func (abt *arrayBinaryTree) parent(i int) int { return (i - 1) / 2 } /* 层序遍历 */ func (abt *arrayBinaryTree) levelOrder() []any { var res []any // 直接遍历数组 for i := 0; i < abt.size(); i++ { if abt.val(i) != nil { res = append(res, abt.val(i)) } } return res } /* 深度优先遍历 */ func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) { // 若为空位,则返回 if abt.val(i) == nil { return } // 前序遍历 if order == "pre" { *res = append(*res, abt.val(i)) } abt.dfs(abt.left(i), order, res) // 中序遍历 if order == "in" { *res = append(*res, abt.val(i)) } abt.dfs(abt.right(i), order, res) // 后序遍历 if order == "post" { *res = append(*res, abt.val(i)) } } /* 前序遍历 */ func (abt *arrayBinaryTree) preOrder() []any { var res []any abt.dfs(0, "pre", &res) return res } /* 中序遍历 */ func (abt *arrayBinaryTree) inOrder() []any { var res []any abt.dfs(0, "in", &res) return res } /* 后序遍历 */ func (abt *arrayBinaryTree) postOrder() []any { var res []any abt.dfs(0, "post", &res) return res } ================================================ FILE: codes/go/chapter_tree/array_binary_tree_test.go ================================================ // File: array_binary_tree_test.go // Created Time: 2023-07-24 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestArrayBinaryTree(t *testing.T) { // 初始化二叉树 // 这里借助了一个从数组直接生成二叉树的函数 arr := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} root := SliceToTree(arr) fmt.Println("\n初始化二叉树") fmt.Println("二叉树的数组表示:") fmt.Println(arr) fmt.Println("二叉树的链表表示:") PrintTree(root) // 数组表示下的二叉树类 abt := newArrayBinaryTree(arr) // 访问节点 i := 1 l := abt.left(i) r := abt.right(i) p := abt.parent(i) fmt.Println("\n当前节点的索引为", i, ",值为", abt.val(i)) fmt.Println("其左子节点的索引为", l, ",值为", abt.val(l)) fmt.Println("其右子节点的索引为", r, ",值为", abt.val(r)) fmt.Println("其父节点的索引为", p, ",值为", abt.val(p)) // 遍历树 res := abt.levelOrder() fmt.Println("\n层序遍历为:", res) res = abt.preOrder() fmt.Println("前序遍历为:", res) res = abt.inOrder() fmt.Println("中序遍历为:", res) res = abt.postOrder() fmt.Println("后序遍历为:", res) } ================================================ FILE: codes/go/chapter_tree/avl_tree.go ================================================ // File: avl_tree.go // Created Time: 2023-01-08 // Author: Reanon (793584285@qq.com) package chapter_tree import . "github.com/krahets/hello-algo/pkg" /* AVL 树 */ type aVLTree struct { // 根节点 root *TreeNode } func newAVLTree() *aVLTree { return &aVLTree{root: nil} } /* 获取节点高度 */ func (t *aVLTree) height(node *TreeNode) int { // 空节点高度为 -1 ,叶节点高度为 0 if node != nil { return node.Height } return -1 } /* 更新节点高度 */ func (t *aVLTree) updateHeight(node *TreeNode) { lh := t.height(node.Left) rh := t.height(node.Right) // 节点高度等于最高子树高度 + 1 if lh > rh { node.Height = lh + 1 } else { node.Height = rh + 1 } } /* 获取平衡因子 */ func (t *aVLTree) balanceFactor(node *TreeNode) int { // 空节点平衡因子为 0 if node == nil { return 0 } // 节点平衡因子 = 左子树高度 - 右子树高度 return t.height(node.Left) - t.height(node.Right) } /* 右旋操作 */ func (t *aVLTree) rightRotate(node *TreeNode) *TreeNode { child := node.Left grandChild := child.Right // 以 child 为原点,将 node 向右旋转 child.Right = node node.Left = grandChild // 更新节点高度 t.updateHeight(node) t.updateHeight(child) // 返回旋转后子树的根节点 return child } /* 左旋操作 */ func (t *aVLTree) leftRotate(node *TreeNode) *TreeNode { child := node.Right grandChild := child.Left // 以 child 为原点,将 node 向左旋转 child.Left = node node.Right = grandChild // 更新节点高度 t.updateHeight(node) t.updateHeight(child) // 返回旋转后子树的根节点 return child } /* 执行旋转操作,使该子树重新恢复平衡 */ func (t *aVLTree) rotate(node *TreeNode) *TreeNode { // 获取节点 node 的平衡因子 // Go 推荐短变量,这里 bf 指代 t.balanceFactor bf := t.balanceFactor(node) // 左偏树 if bf > 1 { if t.balanceFactor(node.Left) >= 0 { // 右旋 return t.rightRotate(node) } else { // 先左旋后右旋 node.Left = t.leftRotate(node.Left) return t.rightRotate(node) } } // 右偏树 if bf < -1 { if t.balanceFactor(node.Right) <= 0 { // 左旋 return t.leftRotate(node) } else { // 先右旋后左旋 node.Right = t.rightRotate(node.Right) return t.leftRotate(node) } } // 平衡树,无须旋转,直接返回 return node } /* 插入节点 */ func (t *aVLTree) insert(val int) { t.root = t.insertHelper(t.root, val) } /* 递归插入节点(辅助函数) */ func (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode { if node == nil { return NewTreeNode(val) } /* 1. 查找插入位置并插入节点 */ if val < node.Val.(int) { node.Left = t.insertHelper(node.Left, val) } else if val > node.Val.(int) { node.Right = t.insertHelper(node.Right, val) } else { // 重复节点不插入,直接返回 return node } // 更新节点高度 t.updateHeight(node) /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = t.rotate(node) // 返回子树的根节点 return node } /* 删除节点 */ func (t *aVLTree) remove(val int) { t.root = t.removeHelper(t.root, val) } /* 递归删除节点(辅助函数) */ func (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode { if node == nil { return nil } /* 1. 查找节点并删除 */ if val < node.Val.(int) { node.Left = t.removeHelper(node.Left, val) } else if val > node.Val.(int) { node.Right = t.removeHelper(node.Right, val) } else { if node.Left == nil || node.Right == nil { child := node.Left if node.Right != nil { child = node.Right } if child == nil { // 子节点数量 = 0 ,直接删除 node 并返回 return nil } else { // 子节点数量 = 1 ,直接删除 node node = child } } else { // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 temp := node.Right for temp.Left != nil { temp = temp.Left } node.Right = t.removeHelper(node.Right, temp.Val.(int)) node.Val = temp.Val } } // 更新节点高度 t.updateHeight(node) /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = t.rotate(node) // 返回子树的根节点 return node } /* 查找节点 */ func (t *aVLTree) search(val int) *TreeNode { cur := t.root // 循环查找,越过叶节点后跳出 for cur != nil { if cur.Val.(int) < val { // 目标节点在 cur 的右子树中 cur = cur.Right } else if cur.Val.(int) > val { // 目标节点在 cur 的左子树中 cur = cur.Left } else { // 找到目标节点,跳出循环 break } } // 返回目标节点 return cur } ================================================ FILE: codes/go/chapter_tree/avl_tree_test.go ================================================ // File: avl_tree_test.go // Created Time: 2023-01-08 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestAVLTree(t *testing.T) { /* 初始化空 AVL 树 */ tree := newAVLTree() /* 插入节点 */ // 请关注插入节点后,AVL 树是如何保持平衡的 testInsert(tree, 1) testInsert(tree, 2) testInsert(tree, 3) testInsert(tree, 4) testInsert(tree, 5) testInsert(tree, 8) testInsert(tree, 7) testInsert(tree, 9) testInsert(tree, 10) testInsert(tree, 6) /* 插入重复节点 */ testInsert(tree, 7) /* 删除节点 */ // 请关注删除节点后,AVL 树是如何保持平衡的 testRemove(tree, 8) // 删除度为 0 的节点 testRemove(tree, 5) // 删除度为 1 的节点 testRemove(tree, 4) // 删除度为 2 的节点 /* 查询节点 */ node := tree.search(7) fmt.Printf("\n查找到的节点对象为 %#v ,节点值 = %d \n", node, node.Val) } func testInsert(tree *aVLTree, val int) { tree.insert(val) fmt.Printf("\n插入节点 %d 后,AVL 树为 \n", val) PrintTree(tree.root) } func testRemove(tree *aVLTree, val int) { tree.remove(val) fmt.Printf("\n删除节点 %d 后,AVL 树为 \n", val) PrintTree(tree.root) } ================================================ FILE: codes/go/chapter_tree/binary_search_tree.go ================================================ // File: binary_search_tree.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( . "github.com/krahets/hello-algo/pkg" ) type binarySearchTree struct { root *TreeNode } func newBinarySearchTree() *binarySearchTree { bst := &binarySearchTree{} // 初始化空树 bst.root = nil return bst } /* 获取根节点 */ func (bst *binarySearchTree) getRoot() *TreeNode { return bst.root } /* 查找节点 */ func (bst *binarySearchTree) search(num int) *TreeNode { node := bst.root // 循环查找,越过叶节点后跳出 for node != nil { if node.Val.(int) < num { // 目标节点在 cur 的右子树中 node = node.Right } else if node.Val.(int) > num { // 目标节点在 cur 的左子树中 node = node.Left } else { // 找到目标节点,跳出循环 break } } // 返回目标节点 return node } /* 插入节点 */ func (bst *binarySearchTree) insert(num int) { cur := bst.root // 若树为空,则初始化根节点 if cur == nil { bst.root = NewTreeNode(num) return } // 待插入节点之前的节点位置 var pre *TreeNode = nil // 循环查找,越过叶节点后跳出 for cur != nil { if cur.Val == num { return } pre = cur if cur.Val.(int) < num { cur = cur.Right } else { cur = cur.Left } } // 插入节点 node := NewTreeNode(num) if pre.Val.(int) < num { pre.Right = node } else { pre.Left = node } } /* 删除节点 */ func (bst *binarySearchTree) remove(num int) { cur := bst.root // 若树为空,直接提前返回 if cur == nil { return } // 待删除节点之前的节点位置 var pre *TreeNode = nil // 循环查找,越过叶节点后跳出 for cur != nil { if cur.Val == num { break } pre = cur if cur.Val.(int) < num { // 待删除节点在右子树中 cur = cur.Right } else { // 待删除节点在左子树中 cur = cur.Left } } // 若无待删除节点,则直接返回 if cur == nil { return } // 子节点数为 0 或 1 if cur.Left == nil || cur.Right == nil { var child *TreeNode = nil // 取出待删除节点的子节点 if cur.Left != nil { child = cur.Left } else { child = cur.Right } // 删除节点 cur if cur != bst.root { if pre.Left == cur { pre.Left = child } else { pre.Right = child } } else { // 若删除节点为根节点,则重新指定根节点 bst.root = child } // 子节点数为 2 } else { // 获取中序遍历中待删除节点 cur 的下一个节点 tmp := cur.Right for tmp.Left != nil { tmp = tmp.Left } // 递归删除节点 tmp bst.remove(tmp.Val.(int)) // 用 tmp 覆盖 cur cur.Val = tmp.Val } } /* 打印二叉搜索树 */ func (bst *binarySearchTree) print() { PrintTree(bst.root) } ================================================ FILE: codes/go/chapter_tree/binary_search_tree_test.go ================================================ // File: binary_search_tree_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" ) func TestBinarySearchTree(t *testing.T) { bst := newBinarySearchTree() nums := []int{8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15} // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 for _, num := range nums { bst.insert(num) } fmt.Println("\n初始化的二叉树为:") bst.print() // 获取根节点 node := bst.getRoot() fmt.Println("\n二叉树的根节点为:", node.Val) // 查找节点 node = bst.search(7) fmt.Println("查找到的节点对象为", node, ",节点值 =", node.Val) // 插入节点 bst.insert(16) fmt.Println("\n插入节点后 16 的二叉树为:") bst.print() // 删除节点 bst.remove(1) fmt.Println("\n删除节点 1 后的二叉树为:") bst.print() bst.remove(2) fmt.Println("\n删除节点 2 后的二叉树为:") bst.print() bst.remove(4) fmt.Println("\n删除节点 4 后的二叉树为:") bst.print() } ================================================ FILE: codes/go/chapter_tree/binary_tree_bfs.go ================================================ // File: binary_tree_bfs.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "container/list" . "github.com/krahets/hello-algo/pkg" ) /* 层序遍历 */ func levelOrder(root *TreeNode) []any { // 初始化队列,加入根节点 queue := list.New() queue.PushBack(root) // 初始化一个切片,用于保存遍历序列 nums := make([]any, 0) for queue.Len() > 0 { // 队列出队 node := queue.Remove(queue.Front()).(*TreeNode) // 保存节点值 nums = append(nums, node.Val) if node.Left != nil { // 左子节点入队 queue.PushBack(node.Left) } if node.Right != nil { // 右子节点入队 queue.PushBack(node.Right) } } return nums } ================================================ FILE: codes/go/chapter_tree/binary_tree_bfs_test.go ================================================ // File: binary_tree_bfs_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLevelOrder(t *testing.T) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二叉树: ") PrintTree(root) // 层序遍历 nums := levelOrder(root) fmt.Println("\n层序遍历的节点打印序列 =", nums) } ================================================ FILE: codes/go/chapter_tree/binary_tree_dfs.go ================================================ // File: binary_tree_dfs.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( . "github.com/krahets/hello-algo/pkg" ) var nums []any /* 前序遍历 */ func preOrder(node *TreeNode) { if node == nil { return } // 访问优先级:根节点 -> 左子树 -> 右子树 nums = append(nums, node.Val) preOrder(node.Left) preOrder(node.Right) } /* 中序遍历 */ func inOrder(node *TreeNode) { if node == nil { return } // 访问优先级:左子树 -> 根节点 -> 右子树 inOrder(node.Left) nums = append(nums, node.Val) inOrder(node.Right) } /* 后序遍历 */ func postOrder(node *TreeNode) { if node == nil { return } // 访问优先级:左子树 -> 右子树 -> 根节点 postOrder(node.Left) postOrder(node.Right) nums = append(nums, node.Val) } ================================================ FILE: codes/go/chapter_tree/binary_tree_dfs_test.go ================================================ // File: binary_tree_dfs_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPreInPostOrderTraversal(t *testing.T) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二叉树: ") PrintTree(root) // 前序遍历 nums = nil preOrder(root) fmt.Println("\n前序遍历的节点打印序列 =", nums) // 中序遍历 nums = nil inOrder(root) fmt.Println("\n中序遍历的节点打印序列 =", nums) // 后序遍历 nums = nil postOrder(root) fmt.Println("\n后序遍历的节点打印序列 =", nums) } ================================================ FILE: codes/go/chapter_tree/binary_tree_test.go ================================================ // File: binary_tree_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestBinaryTree(t *testing.T) { /* 初始化二叉树 */ // 初始化节点 n1 := NewTreeNode(1) n2 := NewTreeNode(2) n3 := NewTreeNode(3) n4 := NewTreeNode(4) n5 := NewTreeNode(5) // 构建节点之间的引用(指针) n1.Left = n2 n1.Right = n3 n2.Left = n4 n2.Right = n5 fmt.Println("初始化二叉树") PrintTree(n1) /* 插入与删除节点 */ // 插入节点 p := NewTreeNode(0) n1.Left = p p.Left = n2 fmt.Println("插入节点 P 后") PrintTree(n1) // 删除节点 n1.Left = n2 fmt.Println("删除节点 P 后") PrintTree(n1) } ================================================ FILE: codes/go/go.mod ================================================ module github.com/krahets/hello-algo go 1.19 ================================================ FILE: codes/go/pkg/list_node.go ================================================ // File: list_node.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg // ListNode 链表节点 type ListNode struct { Next *ListNode Val int } // NewListNode 链表节点构造函数 func NewListNode(v int) *ListNode { return &ListNode{ Next: nil, Val: v, } } // ArrayToLinkedList 将数组反序列化为链表 func ArrayToLinkedList(arr []int) *ListNode { // dummy header of linked list dummy := NewListNode(0) node := dummy for _, val := range arr { node.Next = NewListNode(val) node = node.Next } return dummy.Next } ================================================ FILE: codes/go/pkg/list_node_test.go ================================================ // File: list_node_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg import ( "testing" ) func TestListNode(t *testing.T) { arr := []int{2, 3, 5, 6, 7} head := ArrayToLinkedList(arr) PrintLinkedList(head) } ================================================ FILE: codes/go/pkg/print_utils.go ================================================ // File: print_utils.go // Created Time: 2022-12-03 // Author: Reanon (793584285@qq.com), krahets (krahets@163.com), msk397 (machangxinq@gmail.com) package pkg import ( "container/list" "fmt" "strconv" "strings" ) // PrintSlice 打印切片 func PrintSlice[T any](nums []T) { fmt.Printf("%v", nums) fmt.Println() } // PrintList 打印列表 func PrintList(list *list.List) { if list.Len() == 0 { fmt.Print("[]\n") return } e := list.Front() // 强转为 string, 会影响效率 fmt.Print("[") for e.Next() != nil { fmt.Print(e.Value, " ") e = e.Next() } fmt.Print(e.Value, "]\n") } // PrintMap 打印哈希表 func PrintMap[K comparable, V any](m map[K]V) { for key, value := range m { fmt.Println(key, "->", value) } } // PrintHeap 打印堆 func PrintHeap(h []any) { fmt.Printf("堆的数组表示:") fmt.Printf("%v", h) fmt.Printf("\n堆的树状表示:\n") root := SliceToTree(h) PrintTree(root) } // PrintLinkedList 打印链表 func PrintLinkedList(node *ListNode) { if node == nil { return } var builder strings.Builder for node.Next != nil { builder.WriteString(strconv.Itoa(node.Val) + " -> ") node = node.Next } builder.WriteString(strconv.Itoa(node.Val)) fmt.Println(builder.String()) } // PrintTree 打印二叉树 func PrintTree(root *TreeNode) { printTreeHelper(root, nil, false) } // printTreeHelper 打印二叉树 // This tree printer is borrowed from TECHIE DELIGHT // https://www.techiedelight.com/c-program-print-binary-tree/ func printTreeHelper(root *TreeNode, prev *trunk, isRight bool) { if root == nil { return } prevStr := " " trunk := newTrunk(prev, prevStr) printTreeHelper(root.Right, trunk, true) if prev == nil { trunk.str = "———" } else if isRight { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev.str = prevStr } showTrunk(trunk) fmt.Println(root.Val) if prev != nil { prev.str = prevStr } trunk.str = " |" printTreeHelper(root.Left, trunk, false) } type trunk struct { prev *trunk str string } func newTrunk(prev *trunk, str string) *trunk { return &trunk{ prev: prev, str: str, } } func showTrunk(t *trunk) { if t == nil { return } showTrunk(t.prev) fmt.Print(t.str) } ================================================ FILE: codes/go/pkg/tree_node.go ================================================ // File: tree_node.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg // TreeNode 二叉树节点 type TreeNode struct { Val any // 节点值 Height int // 节点高度 Left *TreeNode // 左子节点引用 Right *TreeNode // 右子节点引用 } // NewTreeNode 二叉树节点构造函数 func NewTreeNode(v any) *TreeNode { return &TreeNode{ Val: v, Height: 0, Left: nil, Right: nil, } } // 序列化编码规则请参考: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二叉树的数组表示: // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] // 二叉树的链表表示: // // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // // ——— 1 // // \——— 2 // | /——— 9 // \——— 4 // \——— 8 // SliceToTreeDFS 将列表反序列化为二叉树:递归 func SliceToTreeDFS(arr []any, i int) *TreeNode { if i < 0 || i >= len(arr) || arr[i] == nil { return nil } root := NewTreeNode(arr[i]) root.Left = SliceToTreeDFS(arr, 2*i+1) root.Right = SliceToTreeDFS(arr, 2*i+2) return root } // SliceToTree 将切片反序列化为二叉树 func SliceToTree(arr []any) *TreeNode { return SliceToTreeDFS(arr, 0) } // TreeToSliceDFS 将二叉树序列化为切片:递归 func TreeToSliceDFS(root *TreeNode, i int, res *[]any) { if root == nil { return } for i >= len(*res) { *res = append(*res, nil) } (*res)[i] = root.Val TreeToSliceDFS(root.Left, 2*i+1, res) TreeToSliceDFS(root.Right, 2*i+2, res) } // TreeToSlice 将二叉树序列化为切片 func TreeToSlice(root *TreeNode) []any { var res []any TreeToSliceDFS(root, 0, &res) return res } ================================================ FILE: codes/go/pkg/tree_node_test.go ================================================ // File: tree_node_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg import ( "fmt" "testing" ) func TestTreeNode(t *testing.T) { arr := []any{1, 2, 3, nil, 5, 6, nil} node := SliceToTree(arr) // print tree PrintTree(node) // tree to arr fmt.Println(TreeToSlice(node)) } ================================================ FILE: codes/go/pkg/vertex.go ================================================ // File: vertex.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package pkg // Vertex 顶点类 type Vertex struct { Val int } // NewVertex 顶点构造函数 func NewVertex(val int) Vertex { return Vertex{ Val: val, } } // ValsToVets 将值列表反序列化为顶点列表 func ValsToVets(vals []int) []Vertex { vets := make([]Vertex, len(vals)) for i := 0; i < len(vals); i++ { vets[i] = NewVertex(vals[i]) } return vets } // VetsToVals 将顶点列表序列化为值列表 func VetsToVals(vets []Vertex) []int { vals := make([]int, len(vets)) for i := range vets { vals[i] = vets[i].Val } return vals } // DeleteSliceElms 删除切片指定元素 func DeleteSliceElms[T any](a []T, elms ...T) []T { if len(a) == 0 || len(elms) == 0 { return a } // 先将元素转为 set m := make(map[any]struct{}) for _, v := range elms { m[v] = struct{}{} } // 过滤掉指定元素 res := make([]T, 0, len(a)) for _, v := range a { if _, ok := m[v]; !ok { res = append(res, v) } } return res } ================================================ FILE: codes/java/.gitignore ================================================ build ================================================ FILE: codes/java/chapter_array_and_linkedlist/array.java ================================================ /** * 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 以及之后的所有元素向后移动一位 for (int i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 将 num 赋给 index 处的元素 nums[index] = num; } /* 删除索引 index 处的元素 */ static void remove(int[] nums, int index) { // 把索引 index 之后的所有元素向前移动一位 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; } /* Driver Code */ 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); } } ================================================ FILE: codes/java/chapter_array_and_linkedlist/linked_list.java ================================================ /** * 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; } /* Driver Code */ public static void main(String[] args) { /* 初始化链表 */ // 初始化各个节点 ListNode n0 = new ListNode(1); ListNode n1 = new ListNode(3); ListNode n2 = new ListNode(2); ListNode n3 = new ListNode(5); ListNode n4 = new ListNode(4); // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; System.out.println("初始化的链表为"); 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); } } ================================================ FILE: codes/java/chapter_array_and_linkedlist/list.java ================================================ /** * 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) { /* 初始化列表 */ // 注意数组的元素类型是 int[] 的包装类 Integer[] Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; List nums = new ArrayList<>(Arrays.asList(numbers)); System.out.println("列表 nums = " + nums); /* 访问元素 */ int num = nums.get(1); System.out.println("访问索引 1 处的元素,得到 num = " + num); /* 更新元素 */ 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; } /* 拼接两个列表 */ 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); } } ================================================ FILE: codes/java/chapter_array_and_linkedlist/my_list.java ================================================ /** * 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 以及之后的元素都向后移动一位 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 之后的元素都向前移动一位 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 { /* Driver Code */ 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()); } } ================================================ FILE: codes/java/chapter_backtracking/n_queens.java ================================================ /** * 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); } } } } ================================================ FILE: codes/java/chapter_backtracking/permutations_i.java ================================================ /** * 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); } } ================================================ FILE: codes/java/chapter_backtracking/permutations_ii.java ================================================ /** * 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); } } ================================================ FILE: codes/java/chapter_backtracking/preorder_traversal_i_compact.java ================================================ /** * 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; /* 前序遍历:例题一 */ 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); } } ================================================ FILE: codes/java/chapter_backtracking/preorder_traversal_ii_compact.java ================================================ /** * 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; /* 前序遍历:例题二 */ 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); } } } ================================================ FILE: codes/java/chapter_backtracking/preorder_traversal_iii_compact.java ================================================ /** * 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; /* 前序遍历:例题三 */ 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); } } } ================================================ FILE: codes/java/chapter_backtracking/preorder_traversal_iii_template.java ================================================ /** * 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); } /* 回溯算法:例题三 */ 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); } } } ================================================ FILE: codes/java/chapter_backtracking/subset_sum_i.java ================================================ /** * 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); } } ================================================ FILE: codes/java/chapter_backtracking/subset_sum_i_naive.java ================================================ /** * 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("请注意,该方法输出的结果包含重复集合"); } } ================================================ FILE: codes/java/chapter_backtracking/subset_sum_ii.java ================================================ /** * 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); } } ================================================ FILE: codes/java/chapter_computational_complexity/iteration.java ================================================ /** * 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 循环(两次更新) */ static int whileLoopII(int n) { int res = 0; int i = 1; // 初始化条件变量 // 循环求和 1, 4, 10, ... while (i <= n) { res += i; // 更新条件变量 i++; i *= 2; } return res; } /* 双层 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(); } /* Driver Code */ 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 循环(两次更新)求和结果 res = " + res); String resStr = nestedForLoop(n); System.out.println("\n双层 for 循环的遍历结果 " + resStr); } } ================================================ FILE: codes/java/chapter_computational_complexity/recursion.java ================================================ /** * 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; } /* Driver Code */ 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); } } ================================================ FILE: codes/java/chapter_computational_complexity/space_complexity.java ================================================ /** * 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; } /* Driver Code */ public static void main(String[] args) { int n = 5; // 常数阶 constant(n); // 线性阶 linear(n); linearRecur(n); // 平方阶 quadratic(n); quadraticRecur(n); // 指数阶 TreeNode root = buildTree(n); PrintUtil.printTree(root); } } ================================================ FILE: codes/java/chapter_computational_complexity/time_complexity.java ================================================ /** * 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; // 细胞每轮一分为二,形成数列 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; } /* Driver Code */ 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); } } ================================================ FILE: codes/java/chapter_computational_complexity/worst_best_time_complexity.java ================================================ /** * 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; } /* Driver Code */ public static void main(String[] args) { for (int i = 0; i < 10; i++) { int n = 100; int[] nums = randomNumbers(n); int index = findOne(nums); System.out.println("\n数组 [ 1, 2, ..., n ] 被打乱后 = " + Arrays.toString(nums)); System.out.println("数字 1 的索引为 " + index); } } } ================================================ FILE: codes/java/chapter_divide_and_conquer/binary_search_recur.java ================================================ /** * 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) / 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); } } ================================================ FILE: codes/java/chapter_divide_and_conquer/build_tree.java ================================================ /** * 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) { // 初始化哈希表,存储 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); } } ================================================ FILE: codes/java/chapter_divide_and_conquer/hanota.java ================================================ /** * 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 只剩下一个圆盘,则直接将其移到 tar if (i == 1) { move(src, tar); return; } // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf dfs(i - 1, src, tar, buf); // 子问题 f(1) :将 src 剩余一个圆盘移到 tar move(src, tar); // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar dfs(i - 1, buf, src, tar); } /* 求解汉诺塔问题 */ static void solveHanota(List A, List B, List C) { int n = A.size(); // 将 A 顶部 n 个圆盘借助 B 移到 C dfs(n, A, B, C); } public static void main(String[] args) { // 列表尾部是柱子顶部 List A = new ArrayList<>(Arrays.asList(5, 4, 3, 2, 1)); List B = new ArrayList<>(); List C = new ArrayList<>(); System.out.println("初始状态下:"); System.out.println("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); } } ================================================ FILE: codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java ================================================ /** * 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)); } } ================================================ FILE: codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java ================================================ /** * 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)); } } ================================================ FILE: codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java ================================================ /** * 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)); } } ================================================ FILE: codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java ================================================ /** * 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)); } } ================================================ FILE: codes/java/chapter_dynamic_programming/climbing_stairs_dp.java ================================================ /** * 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)); } } ================================================ FILE: codes/java/chapter_dynamic_programming/coin_change.java ================================================ /** * 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); } } ================================================ FILE: codes/java/chapter_dynamic_programming/coin_change_ii.java ================================================ /** * 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 这两种方案之和 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 这两种方案之和 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); } } ================================================ FILE: codes/java/chapter_dynamic_programming/edit_distance.java ================================================ /** * 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; // 若两字符相等,则直接跳过此两字符 if (s.charAt(i - 1) == t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 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]; // 若两字符相等,则直接跳过此两字符 if (s.charAt(i - 1) == t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 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)) { // 若两字符相等,则直接跳过此两字符 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 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)) { // 若两字符相等,则直接跳过此两字符 dp[j] = leftup; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 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 + " 步"); } } ================================================ FILE: codes/java/chapter_dynamic_programming/knapsack.java ================================================ /** * 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]; // 返回两种方案中价值更大的那一个 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]; // 记录并返回两种方案中价值更大的那一个 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); } } ================================================ FILE: codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java ================================================ /** * 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)); } } ================================================ FILE: codes/java/chapter_dynamic_programming/min_path_sum.java ================================================ /** * 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); } } ================================================ FILE: codes/java/chapter_dynamic_programming/unbounded_knapsack.java ================================================ /** * 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); } } ================================================ FILE: codes/java/chapter_graph/graph_adjacency_list.java ================================================ /** * 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 { // 邻接表,key:顶点,value:该顶点的所有邻接顶点 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(); } } ================================================ FILE: codes/java/chapter_graph/graph_adjacency_matrix.java ================================================ /** * 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); } // 添加边 // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 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 对应 vertices 元素索引 public void addEdge(int i, int j) { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw new IndexOutOfBoundsException(); // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) adjMat.get(i).set(j, 1); adjMat.get(j).set(i, 1); } /* 删除边 */ // 参数 i, j 对应 vertices 元素索引 public void removeEdge(int i, int j) { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw new 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) { /* 初始化无向图 */ // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 int[] vertices = { 1, 3, 2, 5, 4 }; int[][] edges = { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; GraphAdjMat graph = new GraphAdjMat(vertices, edges); System.out.println("\n初始化后,图为"); 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(); } } ================================================ FILE: codes/java/chapter_graph/graph_bfs.java ================================================ /** * 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)); } } ================================================ FILE: codes/java/chapter_graph/graph_dfs.java ================================================ /** * 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)); } } ================================================ FILE: codes/java/chapter_greedy/coin_change_greedy.java ================================================ /** * 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) { // 假设 coins 列表有序 int i = coins.length - 1; int count = 0; // 循环进行贪心选择,直到无剩余金额 while (amt > 0) { // 找到小于且最接近剩余金额的硬币 while (i > 0 && coins[i] > amt) { i--; } // 选择 coins[i] amt -= coins[i]; count++; } // 若未找到可行方案,则返回 -1 return amt == 0 ? count : -1; } 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"); } } ================================================ FILE: codes/java/chapter_greedy/fractional_knapsack.java ================================================ /** * 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) { // 创建物品列表,包含两个属性:重量、价值 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); } } ================================================ FILE: codes/java/chapter_greedy/max_capacity.java ================================================ /** * 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; // 循环贪心选择,直至两板相遇 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); } } ================================================ FILE: codes/java/chapter_greedy/max_product_cutting.java ================================================ /** * 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); } } ================================================ FILE: codes/java/chapter_hashing/array_hash_map.java ================================================ /** * 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, "小哈"); map.put(15937, "小啰"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鸭"); System.out.println("\n添加完成后,哈希表为\nKey -> Value"); map.print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value String name = map.get(15937); System.out.println("\n输入学号 15937 ,查询到姓名 " + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(10583); System.out.println("\n删除 10583 后,哈希表为\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); } } } ================================================ FILE: codes/java/chapter_hashing/built_in_hash.java ================================================ /** * 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 算法"; int hashStr = str.hashCode(); System.out.println("字符串 " + str + " 的哈希值为 " + hashStr); Object[] arr = { 12836, "小哈" }; 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); } } ================================================ FILE: codes/java/chapter_hashing/hash_map.java ================================================ /** * 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, "小哈"); map.put(15937, "小啰"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鸭"); System.out.println("\n添加完成后,哈希表为\nKey -> Value"); PrintUtil.printHashMap(map); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value String name = map.get(15937); System.out.println("\n输入学号 15937 ,查询到姓名 " + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(10583); System.out.println("\n删除 10583 后,哈希表为\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); } } } ================================================ FILE: codes/java/chapter_hashing/hash_map_chaining.java ================================================ /** * 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); // 遍历桶,若找到 key ,则返回对应 val for (Pair pair : bucket) { if (pair.key == key) { return pair.val; } } // 若未找到 key ,则返回 null return null; } /* 添加操作 */ void put(int key, String val) { // 当负载因子超过阈值时,执行扩容 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); List bucket = buckets.get(index); // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 for (Pair pair : bucket) { if (pair.key == key) { pair.val = val; return; } } // 若无该 key ,则将键值对添加至尾部 Pair pair = new Pair(key, val); bucket.add(pair); size++; } /* 删除操作 */ 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, "小哈"); map.put(15937, "小啰"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鸭"); System.out.println("\n添加完成后,哈希表为\nKey -> Value"); map.print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value String name = map.get(13276); System.out.println("\n输入学号 13276 ,查询到姓名 " + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(12836); System.out.println("\n删除 12836 后,哈希表为\nKey -> Value"); map.print(); } } ================================================ FILE: codes/java/chapter_hashing/hash_map_open_addressing.java ================================================ /** * 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, "小哈"); hashmap.put(15937, "小啰"); hashmap.put(16750, "小算"); hashmap.put(13276, "小法"); hashmap.put(10583, "小鸭"); System.out.println("\n添加完成后,哈希表为\nKey -> Value"); hashmap.print(); // 查询操作 // 向哈希表中输入键 key ,得到值 val String name = hashmap.get(13276); System.out.println("\n输入学号 13276 ,查询到姓名 " + name); // 删除操作 // 在哈希表中删除键值对 (key, val) hashmap.remove(16750); System.out.println("\n删除 16750 后,哈希表为\nKey -> Value"); hashmap.print(); } } ================================================ FILE: codes/java/chapter_hashing/simple_hash.java ================================================ /** * 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; } /* 异或哈希 */ 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 算法"; int hash = addHash(key); System.out.println("加法哈希值为 " + hash); hash = mulHash(key); System.out.println("乘法哈希值为 " + hash); hash = xorHash(key); System.out.println("异或哈希值为 " + hash); hash = rotHash(key); System.out.println("旋转哈希值为 " + hash); } } ================================================ FILE: codes/java/chapter_heap/heap.java ================================================ /** * 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<>(); // 初始化大顶堆(使用 lambda 表达式修改 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); } } ================================================ FILE: codes/java/chapter_heap/my_heap.java ================================================ /** * 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; // 交换两节点 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; // 交换两节点 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); } } ================================================ FILE: codes/java/chapter_heap/top_k.java ================================================ /** * 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); } } ================================================ FILE: codes/java/chapter_searching/binary_search.java ================================================ /** * 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); } } ================================================ FILE: codes/java/chapter_searching/binary_search_edge.java ================================================ /** * 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); } } } ================================================ FILE: codes/java/chapter_searching/binary_search_insertion.java ================================================ /** * 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); } } } ================================================ FILE: codes/java/chapter_searching/hashing_search.java ================================================ /** * 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) { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 return map.getOrDefault(target, -1); } /* 哈希查找(链表) */ static ListNode hashingSearchLinkedList(Map map, int target) { // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 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); // key: 元素,value: 索引 } 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); // key: 节点值,value: 节点 head = head.next; } ListNode node = hashingSearchLinkedList(map1, target); System.out.println("目标节点值 3 的对应节点对象为 " + node); } } ================================================ FILE: codes/java/chapter_searching/linear_search.java ================================================ /** * 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); } } ================================================ FILE: codes/java/chapter_searching/two_sum.java ================================================ /** * 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) { // ======= Test Case ======= int[] nums = { 2, 7, 11, 15 }; int target = 13; // ====== Driver Code ====== // 方法一 int[] res = twoSumBruteForce(nums, target); System.out.println("方法一 res = " + Arrays.toString(res)); // 方法二 res = twoSumHashTable(nums, target); System.out.println("方法二 res = " + Arrays.toString(res)); } } ================================================ FILE: codes/java/chapter_sorting/bubble_sort.java ================================================ /** * 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)); } } ================================================ FILE: codes/java/chapter_sorting/bucket_sort.java ================================================ /** * 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)); } } ================================================ FILE: codes/java/chapter_sorting/counting_sort.java ================================================ /** * 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 是 num 在 res 中最后一次出现的索引 for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. 倒序遍历 nums ,将各元素填入结果数组 res // 初始化数组 res 用于记录结果 int n = nums.length; int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // 将 num 放置到对应索引处 counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 } // 使用结果数组 res 覆盖原数组 nums for (int i = 0; i < n; i++) { nums[i] = res[i]; } } 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)); } } ================================================ FILE: codes/java/chapter_sorting/heap_sort.java ================================================ /** * 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; // 交换两节点 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)); } } ================================================ FILE: codes/java/chapter_sorting/insertion_sort.java ================================================ /** * 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] 向右移动一位 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)); } } ================================================ FILE: codes/java/chapter_sorting/merge_sort.java ================================================ /** * 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)); } } ================================================ FILE: codes/java/chapter_sorting/quick_sort.java ================================================ /** * 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); // 交换这两个元素 } swap(nums, i, left); // 将基准数交换至两子数组的分界线 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; } /* 选取三个候选元素的中位数 */ 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; } /* 哨兵划分(三数取中值) */ static int partition(int[] 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(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); // 交换这两个元素 } swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } /* 快速排序(递归深度优化) */ public static void quickSort(int[] 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] } } } } 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)); } } ================================================ FILE: codes/java/chapter_sorting/radix_sort.java ================================================ /** * 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) { // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 return (num / exp) % 10; } /* 计数排序(根据 nums 第 k 位排序) */ static void countingSortDigit(int[] nums, int exp) { // 十进制的位范围为 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)); } } ================================================ FILE: codes/java/chapter_sorting/selection_sort.java ================================================ /** * 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)); } } ================================================ FILE: codes/java/chapter_stack_and_queue/array_deque.java ================================================ /** * 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 越过数组头部后回到尾部 front = index(front - 1); // 将 num 添加至队首 nums[front] = num; queSize++; } /* 队尾入队 */ public void pushLast(int num) { if (queSize == capacity()) { System.out.println("双向队列已满"); return; } // 计算队尾指针,指向队尾索引 + 1 int rear = index(front + queSize); // 将 num 添加至队尾 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() { // 仅转换有效长度范围内的列表元素 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) { /* 初始化双向队列 */ ArrayDeque deque = new ArrayDeque(10); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); 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); /* 元素入队 */ deque.pushLast(4); System.out.println("元素 4 队尾入队后 deque = " + Arrays.toString(deque.toArray())); deque.pushFirst(1); System.out.println("元素 1 队首入队后 deque = " + Arrays.toString(deque.toArray())); /* 元素出队 */ int popLast = deque.popLast(); System.out.println("队尾出队元素 = " + popLast + ",队尾出队后 deque = " + Arrays.toString(deque.toArray())); int popFirst = deque.popFirst(); System.out.println("队首出队元素 = " + popFirst + ",队首出队后 deque = " + Arrays.toString(deque.toArray())); /* 获取双向队列的长度 */ int size = deque.size(); System.out.println("双向队列长度 size = " + size); /* 判断双向队列是否为空 */ boolean isEmpty = deque.isEmpty(); System.out.println("双向队列是否为空 = " + isEmpty); } } ================================================ FILE: codes/java/chapter_stack_and_queue/array_queue.java ================================================ /** * 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; } // 计算队尾指针,指向队尾索引 + 1 // 通过取余操作实现 rear 越过数组尾部后回到头部 int rear = (front + queSize) % capacity(); // 将 num 添加至队尾 nums[rear] = num; queSize++; } /* 出队 */ public int pop() { int num = peek(); // 队首指针向后移动一位,若越过尾部,则返回到数组头部 front = (front + 1) % capacity(); queSize--; return num; } /* 访问队首元素 */ public int peek() { if (isEmpty()) throw new IndexOutOfBoundsException(); return nums[front]; } /* 返回数组 */ public int[] toArray() { // 仅转换有效长度范围内的列表元素 int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[j % 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 = " + pop + ",出队后 queue = " + 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); queue.pop(); System.out.println("第 " + i + " 轮入队 + 出队后 queue = " + Arrays.toString(queue.toArray())); } } } ================================================ FILE: codes/java/chapter_stack_and_queue/array_stack.java ================================================ /** * 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 = " + pop + ",出栈后 stack = " + Arrays.toString(stack.toArray())); /* 获取栈的长度 */ int size = stack.size(); System.out.println("栈的长度 size = " + size); /* 判断是否为空 */ boolean isEmpty = stack.isEmpty(); System.out.println("栈是否为空 = " + isEmpty); } } ================================================ FILE: codes/java/chapter_stack_and_queue/deque.java ================================================ /** * 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 = " + deque); int popFirst = deque.pollFirst(); System.out.println("队首出队元素 = " + popFirst + ",队首出队后 deque = " + deque); /* 获取双向队列的长度 */ int size = deque.size(); System.out.println("双向队列长度 size = " + size); /* 判断双向队列是否为空 */ boolean isEmpty = deque.isEmpty(); System.out.println("双向队列是否为空 = " + isEmpty); } } ================================================ FILE: codes/java/chapter_stack_and_queue/linkedlist_deque.java ================================================ /** * 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; // 更新头节点 // 队尾入队操作 } else { // 将 node 添加至链表尾部 rear.next = node; node.prev = rear; rear = node; // 更新尾节点 } queSize++; // 更新队列长度 } /* 队首入队 */ public void pushFirst(int num) { push(num, true); } /* 队尾入队 */ public void pushLast(int num) { push(num, false); } /* 出队操作 */ 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; // 更新头节点 // 队尾出队操作 } else { val = rear.val; // 暂存尾节点值 // 删除尾节点 ListNode rPrev = rear.prev; if (rPrev != null) { rPrev.next = null; rear.prev = null; } rear = rPrev; // 更新尾节点 } queSize--; // 更新队列长度 return val; } /* 队首出队 */ public int popFirst() { return pop(true); } /* 队尾出队 */ public int popLast() { return pop(false); } /* 访问队首元素 */ public int peekFirst() { if (isEmpty()) throw new 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())); /* 访问元素 */ int peekFirst = deque.peekFirst(); System.out.println("队首元素 peekFirst = " + peekFirst); int peekLast = deque.peekLast(); System.out.println("队尾元素 peekLast = " + peekLast); /* 元素入队 */ deque.pushLast(4); System.out.println("元素 4 队尾入队后 deque = " + Arrays.toString(deque.toArray())); deque.pushFirst(1); System.out.println("元素 1 队首入队后 deque = " + Arrays.toString(deque.toArray())); /* 元素出队 */ int popLast = deque.popLast(); System.out.println("队尾出队元素 = " + popLast + ",队尾出队后 deque = " + Arrays.toString(deque.toArray())); int popFirst = deque.popFirst(); System.out.println("队首出队元素 = " + popFirst + ",队首出队后 deque = " + Arrays.toString(deque.toArray())); /* 获取双向队列的长度 */ int size = deque.size(); System.out.println("双向队列长度 size = " + size); /* 判断双向队列是否为空 */ boolean isEmpty = deque.isEmpty(); System.out.println("双向队列是否为空 = " + isEmpty); } } ================================================ FILE: codes/java/chapter_stack_and_queue/linkedlist_queue.java ================================================ /** * 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; } /* 将链表转化为 Array 并返回 */ 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 = " + pop + ",出队后 queue = " + Arrays.toString(queue.toArray())); /* 获取队列的长度 */ int size = queue.size(); System.out.println("队列长度 size = " + size); /* 判断队列是否为空 */ boolean isEmpty = queue.isEmpty(); System.out.println("队列是否为空 = " + isEmpty); } } ================================================ FILE: codes/java/chapter_stack_and_queue/linkedlist_stack.java ================================================ /** * 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 = " + pop + ",出栈后 stack = " + Arrays.toString(stack.toArray())); /* 获取栈的长度 */ int size = stack.size(); System.out.println("栈的长度 size = " + size); /* 判断是否为空 */ boolean isEmpty = stack.isEmpty(); System.out.println("栈是否为空 = " + isEmpty); } } ================================================ FILE: codes/java/chapter_stack_and_queue/queue.java ================================================ /** * 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 = " + pop + ",出队后 queue = " + queue); /* 获取队列的长度 */ int size = queue.size(); System.out.println("队列长度 size = " + size); /* 判断队列是否为空 */ boolean isEmpty = queue.isEmpty(); System.out.println("队列是否为空 = " + isEmpty); } } ================================================ FILE: codes/java/chapter_stack_and_queue/stack.java ================================================ /** * 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 = " + pop + ",出栈后 stack = " + stack); /* 获取栈的长度 */ int size = stack.size(); System.out.println("栈的长度 size = " + size); /* 判断是否为空 */ boolean isEmpty = stack.isEmpty(); System.out.println("栈是否为空 = " + isEmpty); } } ================================================ FILE: codes/java/chapter_tree/array_binary_tree.java ================================================ /** * 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); } } ================================================ FILE: codes/java/chapter_tree/avl_tree.java ================================================ /** * 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 ,直接删除 node 并返回 if (child == null) return null; // 子节点数量 = 1 ,直接删除 node else node = child; } else { // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 TreeNode temp = node.right; while (temp.left != null) { temp = temp.left; } node.right = removeHelper(node.right, temp.val); node.val = temp.val; } } updateHeight(node); // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = rotate(node); // 返回子树的根节点 return node; } /* 查找节点 */ 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); } } ================================================ FILE: codes/java/chapter_tree/binary_search_tree.java ================================================ /** * 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 or 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); // 用 tmp 覆盖 cur 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()); } } ================================================ FILE: codes/java/chapter_tree/binary_tree.java ================================================ /** * 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); // 在 n1 -> n2 中间插入节点 P 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); } } ================================================ FILE: codes/java/chapter_tree/binary_tree_bfs.java ================================================ /** * 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); } } ================================================ FILE: codes/java/chapter_tree/binary_tree_dfs.java ================================================ /** * 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); } } ================================================ FILE: codes/java/utils/ListNode.java ================================================ /** * 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; } } ================================================ FILE: codes/java/utils/PrintUtil.java ================================================ /** * 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 { /* 打印矩阵(Array) */ public static void printMatrix(T[][] matrix) { System.out.println("["); for (T[] row : matrix) { System.out.println(" " + row + ","); } System.out.println("]"); } /* 打印矩阵(List) */ 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); } /** * 打印二叉树 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ public static void printTree(TreeNode root, Trunk prev, boolean isRight) { if (root == null) { return; } 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); } } ================================================ FILE: codes/java/utils/TreeNode.java ================================================ /** * 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; } } ================================================ FILE: codes/java/utils/Vertex.java ================================================ /** * 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; } } ================================================ FILE: codes/javascript/.prettierrc ================================================ { "tabWidth": 4, "useTabs": false, "semi": true, "singleQuote": true } ================================================ FILE: codes/javascript/chapter_array_and_linkedlist/array.js ================================================ /** * File: array.js * Created Time: 2022-11-27 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 随机访问元素 */ function randomAccess(nums) { // 在区间 [0, nums.length) 中随机抽取一个数字 const random_index = Math.floor(Math.random() * nums.length); // 获取并返回随机元素 const random_num = nums[random_index]; return random_num; } /* 扩展数组长度 */ // 请注意,JavaScript 的 Array 是动态数组,可以直接扩展 // 为了方便学习,本函数将 Array 看作长度不可变的数组 function extend(nums, enlarge) { // 初始化一个扩展长度后的数组 const res = new Array(nums.length + enlarge).fill(0); // 将原数组中的所有元素复制到新数组 for (let i = 0; i < nums.length; i++) { res[i] = nums[i]; } // 返回扩展后的新数组 return res; } /* 在数组的索引 index 处插入元素 num */ function insert(nums, num, index) { // 把索引 index 以及之后的所有元素向后移动一位 for (let i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 将 num 赋给 index 处的元素 nums[index] = num; } /* 删除索引 index 处的元素 */ function remove(nums, index) { // 把索引 index 之后的所有元素向前移动一位 for (let i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* 遍历数组 */ function traverse(nums) { let count = 0; // 通过索引遍历数组 for (let i = 0; i < nums.length; i++) { count += nums[i]; } // 直接遍历数组元素 for (const num of nums) { count += num; } } /* 在数组中查找指定元素 */ function find(nums, target) { for (let i = 0; i < nums.length; i++) { if (nums[i] === target) return i; } return -1; } /* Driver Code */ /* 初始化数组 */ const arr = new Array(5).fill(0); console.log('数组 arr =', arr); let nums = [1, 3, 2, 5, 4]; console.log('数组 nums =', nums); /* 随机访问 */ let random_num = randomAccess(nums); console.log('在 nums 中获取随机元素', random_num); /* 长度扩展 */ nums = extend(nums, 3); console.log('将数组长度扩展至 8 ,得到 nums =', nums); /* 插入元素 */ insert(nums, 6, 3); console.log('在索引 3 处插入数字 6 ,得到 nums =', nums); /* 删除元素 */ remove(nums, 2); console.log('删除索引 2 处的元素,得到 nums =', nums); /* 遍历数组 */ traverse(nums); /* 查找元素 */ let index = find(nums, 3); console.log('在 nums 中查找元素 3 ,得到索引 =', index); ================================================ FILE: codes/javascript/chapter_array_and_linkedlist/linked_list.js ================================================ /** * File: linked_list.js * Created Time: 2022-12-12 * Author: IsChristina (christinaxia77@foxmail.com), Justin (xiefahit@gmail.com) */ const { printLinkedList } = require('../modules/PrintUtil'); const { ListNode } = require('../modules/ListNode'); /* 在链表的节点 n0 之后插入节点 P */ function insert(n0, P) { const n1 = n0.next; P.next = n1; n0.next = P; } /* 删除链表的节点 n0 之后的首个节点 */ function remove(n0) { if (!n0.next) return; // n0 -> P -> n1 const P = n0.next; const n1 = P.next; n0.next = n1; } /* 访问链表中索引为 index 的节点 */ function access(head, index) { for (let i = 0; i < index; i++) { if (!head) { return null; } head = head.next; } return head; } /* 在链表中查找值为 target 的首个节点 */ function find(head, target) { let index = 0; while (head !== null) { if (head.val === target) { return index; } head = head.next; index += 1; } return -1; } /* Driver Code */ /* 初始化链表 */ // 初始化各个节点 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; console.log('初始化的链表为'); printLinkedList(n0); /* 插入节点 */ insert(n0, new ListNode(0)); console.log('插入节点后的链表为'); printLinkedList(n0); /* 删除节点 */ remove(n0); console.log('删除节点后的链表为'); printLinkedList(n0); /* 访问节点 */ const node = access(n0, 3); console.log('链表中索引 3 处的节点的值 = ' + node.val); /* 查找节点 */ const index = find(n0, 2); console.log('链表中值为 2 的节点的索引 = ' + index); ================================================ FILE: codes/javascript/chapter_array_and_linkedlist/list.js ================================================ /** * File: list.js * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 初始化列表 */ const nums = [1, 3, 2, 5, 4]; console.log(`列表 nums = ${nums}`); /* 访问元素 */ const num = nums[1]; console.log(`访问索引 1 处的元素,得到 num = ${num}`); /* 更新元素 */ nums[1] = 0; console.log(`将索引 1 处的元素更新为 0 ,得到 nums = ${nums}`); /* 清空列表 */ nums.length = 0; console.log(`清空列表后 nums = ${nums}`); /* 在尾部添加元素 */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); console.log(`添加元素后 nums = ${nums}`); /* 在中间插入元素 */ nums.splice(3, 0, 6); console.log(`在索引 3 处插入数字 6 ,得到 nums = ${nums}`); /* 删除元素 */ nums.splice(3, 1); console.log(`删除索引 3 处的元素,得到 nums = ${nums}`); /* 通过索引遍历列表 */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* 直接遍历列表元素 */ count = 0; for (const x of nums) { count += x; } /* 拼接两个列表 */ const nums1 = [6, 8, 7, 10, 9]; nums.push(...nums1); console.log(`将列表 nums1 拼接到 nums 之后,得到 nums = ${nums}`); /* 排序列表 */ nums.sort((a, b) => a - b); console.log(`排序列表后 nums = ${nums}`); ================================================ FILE: codes/javascript/chapter_array_and_linkedlist/my_list.js ================================================ /** * File: my_list.js * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 列表类 */ class MyList { #arr = new Array(); // 数组(存储列表元素) #capacity = 10; // 列表容量 #size = 0; // 列表长度(当前元素数量) #extendRatio = 2; // 每次列表扩容的倍数 /* 构造方法 */ constructor() { this.#arr = new Array(this.#capacity); } /* 获取列表长度(当前元素数量)*/ size() { return this.#size; } /* 获取列表容量 */ capacity() { return this.#capacity; } /* 访问元素 */ get(index) { // 索引如果越界,则抛出异常,下同 if (index < 0 || index >= this.#size) throw new Error('索引越界'); return this.#arr[index]; } /* 更新元素 */ set(index, num) { if (index < 0 || index >= this.#size) throw new Error('索引越界'); this.#arr[index] = num; } /* 在尾部添加元素 */ add(num) { // 如果长度等于容量,则需要扩容 if (this.#size === this.#capacity) { this.extendCapacity(); } // 将新元素添加到列表尾部 this.#arr[this.#size] = num; this.#size++; } /* 在中间插入元素 */ insert(index, num) { if (index < 0 || index >= this.#size) throw new Error('索引越界'); // 元素数量超出容量时,触发扩容机制 if (this.#size === this.#capacity) { this.extendCapacity(); } // 将索引 index 以及之后的元素都向后移动一位 for (let j = this.#size - 1; j >= index; j--) { this.#arr[j + 1] = this.#arr[j]; } // 更新元素数量 this.#arr[index] = num; this.#size++; } /* 删除元素 */ remove(index) { if (index < 0 || index >= this.#size) throw new Error('索引越界'); let num = this.#arr[index]; // 将索引 index 之后的元素都向前移动一位 for (let j = index; j < this.#size - 1; j++) { this.#arr[j] = this.#arr[j + 1]; } // 更新元素数量 this.#size--; // 返回被删除的元素 return num; } /* 列表扩容 */ extendCapacity() { // 新建一个长度为原数组 extendRatio 倍的新数组,并将原数组复制到新数组 this.#arr = this.#arr.concat( new Array(this.capacity() * (this.#extendRatio - 1)) ); // 更新列表容量 this.#capacity = this.#arr.length; } /* 将列表转换为数组 */ toArray() { let size = this.size(); // 仅转换有效长度范围内的列表元素 const arr = new Array(size); for (let i = 0; i < size; i++) { arr[i] = this.get(i); } return arr; } } /* Driver Code */ /* 初始化列表 */ const nums = new MyList(); /* 在尾部添加元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); console.log( `列表 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,长度 = ${nums.size()}` ); /* 在中间插入元素 */ nums.insert(3, 6); console.log(`在索引 3 处插入数字 6 ,得到 nums = ${nums.toArray()}`); /* 删除元素 */ nums.remove(3); console.log(`删除索引 3 处的元素,得到 nums = ${nums.toArray()}`); /* 访问元素 */ const num = nums.get(1); console.log(`访问索引 1 处的元素,得到 num = ${num}`); /* 更新元素 */ nums.set(1, 0); console.log(`将索引 1 处的元素更新为 0 ,得到 nums = ${nums.toArray()}`); /* 测试扩容机制 */ for (let i = 0; i < 10; i++) { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 nums.add(i); } console.log( `扩容后的列表 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,长度 = ${nums.size()}` ); ================================================ FILE: codes/javascript/chapter_backtracking/n_queens.js ================================================ /** * File: n_queens.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* 回溯算法:n 皇后 */ function backtrack(row, n, state, res, cols, diags1, diags2) { // 当放置完所有行时,记录解 if (row === n) { res.push(state.map((row) => row.slice())); return; } // 遍历所有列 for (let col = 0; col < n; col++) { // 计算该格子对应的主对角线和次对角线 const diag1 = row - col + n - 1; const diag2 = row + col; // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 尝试:将皇后放置在该格子 state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // 放置下一行 backtrack(row + 1, n, state, res, cols, diags1, diags2); // 回退:将该格子恢复为空位 state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* 求解 n 皇后 */ function nQueens(n) { // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 const state = Array.from({ length: n }, () => Array(n).fill('#')); const cols = Array(n).fill(false); // 记录列是否有皇后 const diags1 = Array(2 * n - 1).fill(false); // 记录主对角线上是否有皇后 const diags2 = Array(2 * n - 1).fill(false); // 记录次对角线上是否有皇后 const res = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } // Driver Code const n = 4; const res = nQueens(n); console.log(`输入棋盘长宽为 ${n}`); console.log(`皇后放置方案共有 ${res.length} 种`); res.forEach((state) => { console.log('--------------------'); state.forEach((row) => console.log(row)); }); ================================================ FILE: codes/javascript/chapter_backtracking/permutations_i.js ================================================ /** * File: permutations_i.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* 回溯算法:全排列 I */ function backtrack(state, choices, selected, res) { // 当状态长度等于元素数量时,记录解 if (state.length === choices.length) { res.push([...state]); return; } // 遍历所有选择 choices.forEach((choice, i) => { // 剪枝:不允许重复选择元素 if (!selected[i]) { // 尝试:做出选择,更新状态 selected[i] = true; state.push(choice); // 进行下一轮选择 backtrack(state, choices, selected, res); // 回退:撤销选择,恢复到之前的状态 selected[i] = false; state.pop(); } }); } /* 全排列 I */ function permutationsI(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums = [1, 2, 3]; const res = permutationsI(nums); console.log(`输入数组 nums = ${JSON.stringify(nums)}`); console.log(`所有排列 res = ${JSON.stringify(res)}`); ================================================ FILE: codes/javascript/chapter_backtracking/permutations_ii.js ================================================ /** * File: permutations_ii.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* 回溯算法:全排列 II */ function backtrack(state, choices, selected, res) { // 当状态长度等于元素数量时,记录解 if (state.length === choices.length) { res.push([...state]); return; } // 遍历所有选择 const duplicated = new Set(); choices.forEach((choice, i) => { // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 if (!selected[i] && !duplicated.has(choice)) { // 尝试:做出选择,更新状态 duplicated.add(choice); // 记录选择过的元素值 selected[i] = true; state.push(choice); // 进行下一轮选择 backtrack(state, choices, selected, res); // 回退:撤销选择,恢复到之前的状态 selected[i] = false; state.pop(); } }); } /* 全排列 II */ function permutationsII(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums = [1, 2, 2]; const res = permutationsII(nums); console.log(`输入数组 nums = ${JSON.stringify(nums)}`); console.log(`所有排列 res = ${JSON.stringify(res)}`); ================================================ FILE: codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js ================================================ /** * File: preorder_traversal_i_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 前序遍历:例题一 */ function preOrder(root, res) { if (root === null) { return; } if (root.val === 7) { // 记录解 res.push(root); } preOrder(root.left, res); preOrder(root.right, res); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树'); printTree(root); // 前序遍历 const res = []; preOrder(root, res); console.log('\n输出所有值为 7 的节点'); console.log(res.map((node) => node.val)); ================================================ FILE: codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js ================================================ /** * File: preorder_traversal_ii_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 前序遍历:例题二 */ function preOrder(root, path, res) { if (root === null) { return; } // 尝试 path.push(root); if (root.val === 7) { // 记录解 res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // 回退 path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树'); printTree(root); // 前序遍历 const path = []; const res = []; preOrder(root, path, res); console.log('\n输出所有根节点到节点 7 的路径'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js ================================================ /** * File: preorder_traversal_iii_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 前序遍历:例题三 */ function preOrder(root, path, res) { // 剪枝 if (root === null || root.val === 3) { return; } // 尝试 path.push(root); if (root.val === 7) { // 记录解 res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // 回退 path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树'); printTree(root); // 前序遍历 const path = []; const res = []; preOrder(root, path, res); console.log('\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js ================================================ /** * File: preorder_traversal_iii_template.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 判断当前状态是否为解 */ function isSolution(state) { return state && state[state.length - 1]?.val === 7; } /* 记录解 */ function recordSolution(state, res) { res.push([...state]); } /* 判断在当前状态下,该选择是否合法 */ function isValid(state, choice) { return choice !== null && choice.val !== 3; } /* 更新状态 */ function makeChoice(state, choice) { state.push(choice); } /* 恢复状态 */ function undoChoice(state) { state.pop(); } /* 回溯算法:例题三 */ function backtrack(state, choices, res) { // 检查是否为解 if (isSolution(state)) { // 记录解 recordSolution(state, res); } // 遍历所有选择 for (const choice of choices) { // 剪枝:检查选择是否合法 if (isValid(state, choice)) { // 尝试:做出选择,更新状态 makeChoice(state, choice); // 进行下一轮选择 backtrack(state, [choice.left, choice.right], res); // 回退:撤销选择,恢复到之前的状态 undoChoice(state); } } } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树'); printTree(root); // 回溯算法 const res = []; backtrack([], [root], res); console.log('\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: codes/javascript/chapter_backtracking/subset_sum_i.js ================================================ /** * File: subset_sum_i.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯算法:子集和 I */ function backtrack(state, target, choices, start, res) { // 子集和等于 target 时,记录解 if (target === 0) { res.push([...state]); return; } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 for (let i = start; i < choices.length; i++) { // 剪枝一:若子集和超过 target ,则直接结束循环 // 这是因为数组已排序,后边元素更大,子集和一定超过 target if (target - choices[i] < 0) { break; } // 尝试:做出选择,更新 target, start state.push(choices[i]); // 进行下一轮选择 backtrack(state, target - choices[i], choices, i, res); // 回退:撤销选择,恢复到之前的状态 state.pop(); } } /* 求解子集和 I */ function subsetSumI(nums, target) { const state = []; // 状态(子集) nums.sort((a, b) => a - b); // 对 nums 进行排序 const start = 0; // 遍历起始点 const res = []; // 结果列表(子集列表) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumI(nums, target); console.log(`输入数组 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`所有和等于 ${target} 的子集 res = ${JSON.stringify(res)}`); ================================================ FILE: codes/javascript/chapter_backtracking/subset_sum_i_naive.js ================================================ /** * File: subset_sum_i_naive.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯算法:子集和 I */ function backtrack(state, target, total, choices, res) { // 子集和等于 target 时,记录解 if (total === target) { res.push([...state]); return; } // 遍历所有选择 for (let i = 0; i < choices.length; i++) { // 剪枝:若子集和超过 target ,则跳过该选择 if (total + choices[i] > target) { continue; } // 尝试:做出选择,更新元素和 total state.push(choices[i]); // 进行下一轮选择 backtrack(state, target, total + choices[i], choices, res); // 回退:撤销选择,恢复到之前的状态 state.pop(); } } /* 求解子集和 I(包含重复子集) */ function subsetSumINaive(nums, target) { const state = []; // 状态(子集) const total = 0; // 子集和 const res = []; // 结果列表(子集列表) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumINaive(nums, target); console.log(`输入数组 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`所有和等于 ${target} 的子集 res = ${JSON.stringify(res)}`); console.log('请注意,该方法输出的结果包含重复集合'); ================================================ FILE: codes/javascript/chapter_backtracking/subset_sum_ii.js ================================================ /** * File: subset_sum_ii.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯算法:子集和 II */ function backtrack(state, target, choices, start, res) { // 子集和等于 target 时,记录解 if (target === 0) { res.push([...state]); return; } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 // 剪枝三:从 start 开始遍历,避免重复选择同一元素 for (let 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.push(choices[i]); // 进行下一轮选择 backtrack(state, target - choices[i], choices, i + 1, res); // 回退:撤销选择,恢复到之前的状态 state.pop(); } } /* 求解子集和 II */ function subsetSumII(nums, target) { const state = []; // 状态(子集) nums.sort((a, b) => a - b); // 对 nums 进行排序 const start = 0; // 遍历起始点 const res = []; // 结果列表(子集列表) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [4, 4, 5]; const target = 9; const res = subsetSumII(nums, target); console.log(`输入数组 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`所有和等于 ${target} 的子集 res = ${JSON.stringify(res)}`); ================================================ FILE: codes/javascript/chapter_computational_complexity/iteration.js ================================================ /** * File: iteration.js * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* for 循环 */ function forLoop(n) { let res = 0; // 循环求和 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { res += i; } return res; } /* while 循环 */ function whileLoop(n) { let res = 0; let i = 1; // 初始化条件变量 // 循环求和 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // 更新条件变量 } return res; } /* while 循环(两次更新) */ function whileLoopII(n) { let res = 0; let i = 1; // 初始化条件变量 // 循环求和 1, 4, 10, ... while (i <= n) { res += i; // 更新条件变量 i++; i *= 2; } return res; } /* 双层 for 循环 */ function nestedForLoop(n) { let res = ''; // 循环 i = 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { // 循环 j = 1, 2, ..., n-1, n for (let j = 1; j <= n; j++) { res += `(${i}, ${j}), `; } } return res; } /* Driver Code */ const n = 5; let res; res = forLoop(n); console.log(`for 循环的求和结果 res = ${res}`); res = whileLoop(n); console.log(`while 循环的求和结果 res = ${res}`); res = whileLoopII(n); console.log(`while 循环(两次更新)求和结果 res = ${res}`); const resStr = nestedForLoop(n); console.log(`双层 for 循环的遍历结果 ${resStr}`); ================================================ FILE: codes/javascript/chapter_computational_complexity/recursion.js ================================================ /** * File: recursion.js * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 递归 */ function recur(n) { // 终止条件 if (n === 1) return 1; // 递:递归调用 const res = recur(n - 1); // 归:返回结果 return n + res; } /* 使用迭代模拟递归 */ function forLoopRecur(n) { // 使用一个显式的栈来模拟系统调用栈 const stack = []; let res = 0; // 递:递归调用 for (let i = n; i > 0; i--) { // 通过“入栈操作”模拟“递” stack.push(i); } // 归:返回结果 while (stack.length) { // 通过“出栈操作”模拟“归” res += stack.pop(); } // res = 1+2+3+...+n return res; } /* 尾递归 */ function tailRecur(n, res) { // 终止条件 if (n === 0) return res; // 尾递归调用 return tailRecur(n - 1, res + n); } /* 斐波那契数列:递归 */ function fib(n) { // 终止条件 f(1) = 0, f(2) = 1 if (n === 1 || n === 2) return n - 1; // 递归调用 f(n) = f(n-1) + f(n-2) const res = fib(n - 1) + fib(n - 2); // 返回结果 f(n) return res; } /* Driver Code */ const n = 5; let res; res = recur(n); console.log(`递归函数的求和结果 res = ${res}`); res = forLoopRecur(n); console.log(`使用迭代模拟递归的求和结果 res = ${res}`); res = tailRecur(n, 0); console.log(`尾递归函数的求和结果 res = ${res}`); res = fib(n); console.log(`斐波那契数列的第 ${n} 项为 ${res}`); ================================================ FILE: codes/javascript/chapter_computational_complexity/space_complexity.js ================================================ /** * File: space_complexity.js * Created Time: 2023-02-05 * Author: Justin (xiefahit@gmail.com) */ const { ListNode } = require('../modules/ListNode'); const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 函数 */ function constFunc() { // 执行某些操作 return 0; } /* 常数阶 */ function constant(n) { // 常量、变量、对象占用 O(1) 空间 const a = 0; const b = 0; const nums = new Array(10000); const node = new ListNode(0); // 循环中的变量占用 O(1) 空间 for (let i = 0; i < n; i++) { const c = 0; } // 循环中的函数占用 O(1) 空间 for (let i = 0; i < n; i++) { constFunc(); } } /* 线性阶 */ function linear(n) { // 长度为 n 的数组占用 O(n) 空间 const nums = new Array(n); // 长度为 n 的列表占用 O(n) 空间 const nodes = []; for (let i = 0; i < n; i++) { nodes.push(new ListNode(i)); } // 长度为 n 的哈希表占用 O(n) 空间 const map = new Map(); for (let i = 0; i < n; i++) { map.set(i, i.toString()); } } /* 线性阶(递归实现) */ function linearRecur(n) { console.log(`递归 n = ${n}`); if (n === 1) return; linearRecur(n - 1); } /* 平方阶 */ function quadratic(n) { // 矩阵占用 O(n^2) 空间 const numMatrix = Array(n) .fill(null) .map(() => Array(n).fill(null)); // 二维列表占用 O(n^2) 空间 const numList = []; for (let i = 0; i < n; i++) { const tmp = []; for (let j = 0; j < n; j++) { tmp.push(0); } numList.push(tmp); } } /* 平方阶(递归实现) */ function quadraticRecur(n) { if (n <= 0) return 0; const nums = new Array(n); console.log(`递归 n = ${n} 中的 nums 长度 = ${nums.length}`); return quadraticRecur(n - 1); } /* 指数阶(建立满二叉树) */ function buildTree(n) { if (n === 0) return null; const root = new TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ const n = 5; // 常数阶 constant(n); // 线性阶 linear(n); linearRecur(n); // 平方阶 quadratic(n); quadraticRecur(n); // 指数阶 const root = buildTree(n); printTree(root); ================================================ FILE: codes/javascript/chapter_computational_complexity/time_complexity.js ================================================ /** * File: time_complexity.js * Created Time: 2023-01-02 * Author: RiverTwilight (contact@rene.wang) */ /* 常数阶 */ function constant(n) { let count = 0; const size = 100000; for (let i = 0; i < size; i++) count++; return count; } /* 线性阶 */ function linear(n) { let count = 0; for (let i = 0; i < n; i++) count++; return count; } /* 线性阶(遍历数组) */ function arrayTraversal(nums) { let count = 0; // 循环次数与数组长度成正比 for (let i = 0; i < nums.length; i++) { count++; } return count; } /* 平方阶 */ function quadratic(n) { let count = 0; // 循环次数与数据大小 n 成平方关系 for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { count++; } } return count; } /* 平方阶(冒泡排序) */ function bubbleSort(nums) { let count = 0; // 计数器 // 外循环:未排序区间为 [0, i] for (let i = nums.length - 1; i > 0; i--) { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 元素交换包含 3 个单元操作 } } } return count; } /* 指数阶(循环实现) */ function exponential(n) { let count = 0, base = 1; // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for (let i = 0; i < n; i++) { for (let j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指数阶(递归实现) */ function expRecur(n) { if (n === 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 对数阶(循环实现) */ function logarithmic(n) { let count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* 对数阶(递归实现) */ function logRecur(n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* 线性对数阶 */ function linearLogRecur(n) { if (n <= 1) return 1; let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (let i = 0; i < n; i++) { count++; } return count; } /* 阶乘阶(递归实现) */ function factorialRecur(n) { if (n === 0) return 1; let count = 0; // 从 1 个分裂出 n 个 for (let i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 const n = 8; console.log('输入数据大小 n = ' + n); let count = constant(n); console.log('常数阶的操作数量 = ' + count); count = linear(n); console.log('线性阶的操作数量 = ' + count); count = arrayTraversal(new Array(n)); console.log('线性阶(遍历数组)的操作数量 = ' + count); count = quadratic(n); console.log('平方阶的操作数量 = ' + count); let nums = new Array(n); for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); console.log('平方阶(冒泡排序)的操作数量 = ' + count); count = exponential(n); console.log('指数阶(循环实现)的操作数量 = ' + count); count = expRecur(n); console.log('指数阶(递归实现)的操作数量 = ' + count); count = logarithmic(n); console.log('对数阶(循环实现)的操作数量 = ' + count); count = logRecur(n); console.log('对数阶(递归实现)的操作数量 = ' + count); count = linearLogRecur(n); console.log('线性对数阶(递归实现)的操作数量 = ' + count); count = factorialRecur(n); console.log('阶乘阶(递归实现)的操作数量 = ' + count); ================================================ FILE: codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js ================================================ /** * File: worst_best_time_complexity.js * Created Time: 2023-01-05 * Author: RiverTwilight (contact@rene.wang) */ /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ function randomNumbers(n) { const nums = Array(n); // 生成数组 nums = { 1, 2, 3, ..., n } for (let i = 0; i < n; i++) { nums[i] = i + 1; } // 随机打乱数组元素 for (let i = 0; i < n; i++) { const r = Math.floor(Math.random() * (i + 1)); const temp = nums[i]; nums[i] = nums[r]; nums[r] = temp; } return nums; } /* 查找数组 nums 中数字 1 所在索引 */ function findOne(nums) { for (let i = 0; i < nums.length; i++) { // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if (nums[i] === 1) { return i; } } return -1; } /* Driver Code */ for (let i = 0; i < 10; i++) { const n = 100; const nums = randomNumbers(n); const index = findOne(nums); console.log('\n数组 [ 1, 2, ..., n ] 被打乱后 = [' + nums.join(', ') + ']'); console.log('数字 1 的索引为 ' + index); } ================================================ FILE: codes/javascript/chapter_divide_and_conquer/binary_search_recur.js ================================================ /** * File: binary_search_recur.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 二分查找:问题 f(i, j) */ function dfs(nums, target, i, j) { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1; } // 计算中点索引 m const m = i + ((j - i) >> 1); if (nums[m] < target) { // 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ function binarySearch(nums, target) { const n = nums.length; // 求解问题 f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分查找(双闭区间) const index = binarySearch(nums, target); console.log(`目标元素 6 的索引 = ${index}`); ================================================ FILE: codes/javascript/chapter_divide_and_conquer/build_tree.js ================================================ /** * File: build_tree.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ const { printTree } = require('../modules/PrintUtil'); const { TreeNode } = require('../modules/TreeNode'); /* 构建二叉树:分治 */ function dfs(preorder, inorderMap, i, l, r) { // 子树区间为空时终止 if (r - l < 0) return null; // 初始化根节点 const root = new TreeNode(preorder[i]); // 查询 m ,从而划分左右子树 const m = inorderMap.get(preorder[i]); // 子问题:构建左子树 root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // 子问题:构建右子树 root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // 返回根节点 return root; } /* 构建二叉树 */ function buildTree(preorder, inorder) { // 初始化哈希表,存储 inorder 元素到索引的映射 let inorderMap = new Map(); for (let i = 0; i < inorder.length; i++) { inorderMap.set(inorder[i], i); } const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ const preorder = [3, 9, 2, 1, 7]; const inorder = [9, 3, 1, 2, 7]; console.log('前序遍历 = ' + JSON.stringify(preorder)); console.log('中序遍历 = ' + JSON.stringify(inorder)); const root = buildTree(preorder, inorder); console.log('构建的二叉树为:'); printTree(root); ================================================ FILE: codes/javascript/chapter_divide_and_conquer/hanota.js ================================================ /** * File: hanota.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 移动一个圆盘 */ function move(src, tar) { // 从 src 顶部拿出一个圆盘 const pan = src.pop(); // 将圆盘放入 tar 顶部 tar.push(pan); } /* 求解汉诺塔问题 f(i) */ function dfs(i, src, buf, tar) { // 若 src 只剩下一个圆盘,则直接将其移到 tar if (i === 1) { move(src, tar); return; } // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf dfs(i - 1, src, tar, buf); // 子问题 f(1) :将 src 剩余一个圆盘移到 tar move(src, tar); // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar dfs(i - 1, buf, src, tar); } /* 求解汉诺塔问题 */ function solveHanota(A, B, C) { const n = A.length; // 将 A 顶部 n 个圆盘借助 B 移到 C dfs(n, A, B, C); } /* Driver Code */ // 列表尾部是柱子顶部 const A = [5, 4, 3, 2, 1]; const B = []; const C = []; console.log('初始状态下:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); solveHanota(A, B, C); console.log('圆盘移动完成后:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); ================================================ FILE: codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js ================================================ /** * File: climbing_stairs_backtrack.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯 */ function backtrack(choices, state, n, res) { // 当爬到第 n 阶时,方案数量加 1 if (state === n) res.set(0, res.get(0) + 1); // 遍历所有选择 for (const choice of choices) { // 剪枝:不允许越过第 n 阶 if (state + choice > n) continue; // 尝试:做出选择,更新状态 backtrack(choices, state + choice, n, res); // 回退 } } /* 爬楼梯:回溯 */ function climbingStairsBacktrack(n) { const choices = [1, 2]; // 可选择向上爬 1 阶或 2 阶 const state = 0; // 从第 0 阶开始爬 const res = new Map(); res.set(0, 0); // 使用 res[0] 记录方案数量 backtrack(choices, state, n, res); return res.get(0); } /* Driver Code */ const n = 9; const res = climbingStairsBacktrack(n); console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); ================================================ FILE: codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js ================================================ /** * File: climbing_stairs_constraint_dp.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 带约束爬楼梯:动态规划 */ function climbingStairsConstraintDP(n) { if (n === 1 || n === 2) { return 1; } // 初始化 dp 表,用于存储子问题的解 const dp = Array.from(new Array(n + 1), () => new Array(3)); // 初始状态:预设最小子问题的解 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 状态转移:从较小子问题逐步求解较大子问题 for (let i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ const n = 9; const res = climbingStairsConstraintDP(n); console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); ================================================ FILE: codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js ================================================ /** * File: climbing_stairs_dfs.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 搜索 */ function dfs(i) { // 已知 dp[1] 和 dp[2] ,返回之 if (i === 1 || i === 2) return i; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1) + dfs(i - 2); return count; } /* 爬楼梯:搜索 */ function climbingStairsDFS(n) { return dfs(n); } /* Driver Code */ const n = 9; const res = climbingStairsDFS(n); console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); ================================================ FILE: codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js ================================================ /** * File: climbing_stairs_dfs_mem.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 记忆化搜索 */ function dfs(i, mem) { // 已知 dp[1] 和 dp[2] ,返回之 if (i === 1 || i === 2) return i; // 若存在记录 dp[i] ,则直接返回之 if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1, mem) + dfs(i - 2, mem); // 记录 dp[i] mem[i] = count; return count; } /* 爬楼梯:记忆化搜索 */ function climbingStairsDFSMem(n) { // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 const mem = new Array(n + 1).fill(-1); return dfs(n, mem); } /* Driver Code */ const n = 9; const res = climbingStairsDFSMem(n); console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); ================================================ FILE: codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js ================================================ /** * File: climbing_stairs_dp.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 爬楼梯:动态规划 */ function climbingStairsDP(n) { if (n === 1 || n === 2) return n; // 初始化 dp 表,用于存储子问题的解 const dp = new Array(n + 1).fill(-1); // 初始状态:预设最小子问题的解 dp[1] = 1; dp[2] = 2; // 状态转移:从较小子问题逐步求解较大子问题 for (let i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* 爬楼梯:空间优化后的动态规划 */ function climbingStairsDPComp(n) { if (n === 1 || n === 2) return n; let a = 1, b = 2; for (let i = 3; i <= n; i++) { const tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ const n = 9; let res = climbingStairsDP(n); console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); res = climbingStairsDPComp(n); console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); ================================================ FILE: codes/javascript/chapter_dynamic_programming/coin_change.js ================================================ /** * File: coin_change.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 零钱兑换:动态规划 */ function coinChangeDP(coins, amt) { const n = coins.length; const MAX = amt + 1; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 状态转移:首行首列 for (let a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状态转移:其余行和列 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] !== MAX ? dp[n][amt] : -1; } /* 零钱兑换:空间优化后的动态规划 */ function coinChangeDPComp(coins, amt) { const n = coins.length; const MAX = amt + 1; // 初始化 dp 表 const dp = Array.from({ length: amt + 1 }, () => MAX); dp[0] = 0; // 状态转移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] !== MAX ? dp[amt] : -1; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 4; // 动态规划 let res = coinChangeDP(coins, amt); console.log(`凑到目标金额所需的最少硬币数量为 ${res}`); // 空间优化后的动态规划 res = coinChangeDPComp(coins, amt); console.log(`凑到目标金额所需的最少硬币数量为 ${res}`); ================================================ FILE: codes/javascript/chapter_dynamic_programming/coin_change_ii.js ================================================ /** * File: coin_change_ii.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 零钱兑换 II:动态规划 */ function coinChangeIIDP(coins, amt) { const n = coins.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 初始化首列 for (let i = 0; i <= n; i++) { dp[i][0] = 1; } // 状态转移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* 零钱兑换 II:空间优化后的动态规划 */ function coinChangeIIDPComp(coins, amt) { const n = coins.length; // 初始化 dp 表 const dp = Array.from({ length: amt + 1 }, () => 0); dp[0] = 1; // 状态转移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 5; // 动态规划 let res = coinChangeIIDP(coins, amt); console.log(`凑出目标金额的硬币组合数量为 ${res}`); // 空间优化后的动态规划 res = coinChangeIIDPComp(coins, amt); console.log(`凑出目标金额的硬币组合数量为 ${res}`); ================================================ FILE: codes/javascript/chapter_dynamic_programming/edit_distance.js ================================================ /** * File: edit_distance.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 编辑距离:暴力搜索 */ function editDistanceDFS(s, t, i, j) { // 若 s 和 t 都为空,则返回 0 if (i === 0 && j === 0) return 0; // 若 s 为空,则返回 t 长度 if (i === 0) return j; // 若 t 为空,则返回 s 长度 if (j === 0) return i; // 若两字符相等,则直接跳过此两字符 if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 const insert = editDistanceDFS(s, t, i, j - 1); const del = editDistanceDFS(s, t, i - 1, j); const replace = editDistanceDFS(s, t, i - 1, j - 1); // 返回最少编辑步数 return Math.min(insert, del, replace) + 1; } /* 编辑距离:记忆化搜索 */ function editDistanceDFSMem(s, t, mem, i, j) { // 若 s 和 t 都为空,则返回 0 if (i === 0 && j === 0) return 0; // 若 s 为空,则返回 t 长度 if (i === 0) return j; // 若 t 为空,则返回 s 长度 if (j === 0) return i; // 若已有记录,则直接返回之 if (mem[i][j] !== -1) return mem[i][j]; // 若两字符相等,则直接跳过此两字符 if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 const insert = editDistanceDFSMem(s, t, mem, i, j - 1); const del = editDistanceDFSMem(s, t, mem, i - 1, j); const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 记录并返回最少编辑步数 mem[i][j] = Math.min(insert, del, replace) + 1; return mem[i][j]; } /* 编辑距离:动态规划 */ function editDistanceDP(s, t) { const n = s.length, m = t.length; const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); // 状态转移:首行首列 for (let i = 1; i <= n; i++) { dp[i][0] = i; } for (let j = 1; j <= m; j++) { dp[0][j] = j; } // 状态转移:其余行和列 for (let i = 1; i <= n; i++) { for (let j = 1; j <= m; j++) { if (s.charAt(i - 1) === t.charAt(j - 1)) { // 若两字符相等,则直接跳过此两字符 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* 编辑距离:空间优化后的动态规划 */ function editDistanceDPComp(s, t) { const n = s.length, m = t.length; const dp = new Array(m + 1).fill(0); // 状态转移:首行 for (let j = 1; j <= m; j++) { dp[j] = j; } // 状态转移:其余行 for (let i = 1; i <= n; i++) { // 状态转移:首列 let leftup = dp[0]; // 暂存 dp[i-1, j-1] dp[0] = i; // 状态转移:其余列 for (let j = 1; j <= m; j++) { const temp = dp[j]; if (s.charAt(i - 1) === t.charAt(j - 1)) { // 若两字符相等,则直接跳过此两字符 dp[j] = leftup; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; } leftup = temp; // 更新为下一轮的 dp[i-1, j-1] } } return dp[m]; } const s = 'bag'; const t = 'pack'; const n = s.length, m = t.length; // 暴力搜索 let res = editDistanceDFS(s, t, n, m); console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); // 记忆化搜索 const mem = Array.from(new Array(n + 1), () => new Array(m + 1).fill(-1)); res = editDistanceDFSMem(s, t, mem, n, m); console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); // 动态规划 res = editDistanceDP(s, t); console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); // 空间优化后的动态规划 res = editDistanceDPComp(s, t); console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); ================================================ FILE: codes/javascript/chapter_dynamic_programming/knapsack.js ================================================ /** * File: knapsack.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 0-1 背包:暴力搜索 */ function knapsackDFS(wgt, val, i, c) { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i === 0 || c === 0) { return 0; } // 若超过背包容量,则只能选择不放入背包 if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 const no = knapsackDFS(wgt, val, i - 1, c); const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // 返回两种方案中价值更大的那一个 return Math.max(no, yes); } /* 0-1 背包:记忆化搜索 */ function knapsackDFSMem(wgt, val, mem, i, c) { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i === 0 || c === 0) { return 0; } // 若已有记录,则直接返回 if (mem[i][c] !== -1) { return mem[i][c]; } // 若超过背包容量,则只能选择不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 const no = knapsackDFSMem(wgt, val, mem, i - 1, c); const yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 记录并返回两种方案中价值更大的那一个 mem[i][c] = Math.max(no, yes); return mem[i][c]; } /* 0-1 背包:动态规划 */ function knapsackDP(wgt, val, cap) { const n = wgt.length; // 初始化 dp 表 const dp = Array(n + 1) .fill(0) .map(() => Array(cap + 1).fill(0)); // 状态转移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = Math.max( dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* 0-1 背包:空间优化后的动态规划 */ function knapsackDPComp(wgt, val, cap) { const n = wgt.length; // 初始化 dp 表 const dp = Array(cap + 1).fill(0); // 状态转移 for (let i = 1; i <= n; i++) { // 倒序遍历 for (let c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // 不选和选物品 i 这两种方案的较大值 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // 暴力搜索 let res = knapsackDFS(wgt, val, n, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); // 记忆化搜索 const mem = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => -1) ); res = knapsackDFSMem(wgt, val, mem, n, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); // 动态规划 res = knapsackDP(wgt, val, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); // 空间优化后的动态规划 res = knapsackDPComp(wgt, val, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); ================================================ FILE: codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js ================================================ /** * File: min_cost_climbing_stairs_dp.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 爬楼梯最小代价:动态规划 */ function minCostClimbingStairsDP(cost) { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } // 初始化 dp 表,用于存储子问题的解 const dp = new Array(n + 1); // 初始状态:预设最小子问题的解 dp[1] = cost[1]; dp[2] = cost[2]; // 状态转移:从较小子问题逐步求解较大子问题 for (let i = 3; i <= n; i++) { dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* 爬楼梯最小代价:空间优化后的动态规划 */ function minCostClimbingStairsDPComp(cost) { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } let a = cost[1], b = cost[2]; for (let i = 3; i <= n; i++) { const tmp = b; b = Math.min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; console.log('输入楼梯的代价列表为:', cost); let res = minCostClimbingStairsDP(cost); console.log(`爬完楼梯的最低代价为:${res}`); res = minCostClimbingStairsDPComp(cost); console.log(`爬完楼梯的最低代价为:${res}`); ================================================ FILE: codes/javascript/chapter_dynamic_programming/min_path_sum.js ================================================ /** * File: min_path_sum.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 最小路径和:暴力搜索 */ function minPathSumDFS(grid, i, j) { // 若为左上角单元格,则终止搜索 if (i === 0 && j === 0) { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 || j < 0) { return Infinity; } // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 const up = minPathSumDFS(grid, i - 1, j); const left = minPathSumDFS(grid, i, j - 1); // 返回从左上角到 (i, j) 的最小路径代价 return Math.min(left, up) + grid[i][j]; } /* 最小路径和:记忆化搜索 */ function minPathSumDFSMem(grid, mem, i, j) { // 若为左上角单元格,则终止搜索 if (i === 0 && j === 0) { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 || j < 0) { return Infinity; } // 若已有记录,则直接返回 if (mem[i][j] !== -1) { return mem[i][j]; } // 左边和上边单元格的最小路径代价 const up = minPathSumDFSMem(grid, mem, i - 1, j); const left = minPathSumDFSMem(grid, mem, i, j - 1); // 记录并返回左上角到 (i, j) 的最小路径代价 mem[i][j] = Math.min(left, up) + grid[i][j]; return mem[i][j]; } /* 最小路径和:动态规划 */ function minPathSumDP(grid) { const n = grid.length, m = grid[0].length; // 初始化 dp 表 const dp = Array.from({ length: n }, () => Array.from({ length: m }, () => 0) ); dp[0][0] = grid[0][0]; // 状态转移:首行 for (let j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 状态转移:首列 for (let i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 状态转移:其余行和列 for (let i = 1; i < n; i++) { for (let j = 1; j < m; j++) { dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* 最小路径和:空间优化后的动态规划 */ function minPathSumDPComp(grid) { const n = grid.length, m = grid[0].length; // 初始化 dp 表 const dp = new Array(m); // 状态转移:首行 dp[0] = grid[0][0]; for (let j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 状态转移:其余行 for (let i = 1; i < n; i++) { // 状态转移:首列 dp[0] = dp[0] + grid[i][0]; // 状态转移:其余列 for (let j = 1; j < m; j++) { dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ const grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; const n = grid.length, m = grid[0].length; // 暴力搜索 let res = minPathSumDFS(grid, n - 1, m - 1); console.log(`从左上角到右下角的最小路径和为 ${res}`); // 记忆化搜索 const mem = Array.from({ length: n }, () => Array.from({ length: m }, () => -1) ); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); console.log(`从左上角到右下角的最小路径和为 ${res}`); // 动态规划 res = minPathSumDP(grid); console.log(`从左上角到右下角的最小路径和为 ${res}`); // 空间优化后的动态规划 res = minPathSumDPComp(grid); console.log(`从左上角到右下角的最小路径和为 ${res}`); ================================================ FILE: codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js ================================================ /** * File: unbounded_knapsack.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 完全背包:动态规划 */ function unboundedKnapsackDP(wgt, val, cap) { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // 状态转移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = Math.max( dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* 完全背包:空间优化后的动态规划 */ function unboundedKnapsackDPComp(wgt, val, cap) { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: cap + 1 }, () => 0); // 状态转移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [1, 2, 3]; const val = [5, 11, 15]; const cap = 4; // 动态规划 let res = unboundedKnapsackDP(wgt, val, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); // 空间优化后的动态规划 res = unboundedKnapsackDPComp(wgt, val, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); ================================================ FILE: codes/javascript/chapter_graph/graph_adjacency_list.js ================================================ /** * File: graph_adjacency_list.js * Created Time: 2023-02-09 * Author: Justin (xiefahit@gmail.com) */ const { Vertex } = require('../modules/Vertex'); /* 基于邻接表实现的无向图类 */ class GraphAdjList { // 邻接表,key:顶点,value:该顶点的所有邻接顶点 adjList; /* 构造方法 */ constructor(edges) { this.adjList = new Map(); // 添加所有顶点和边 for (const edge of edges) { this.addVertex(edge[0]); this.addVertex(edge[1]); this.addEdge(edge[0], edge[1]); } } /* 获取顶点数量 */ size() { return this.adjList.size; } /* 添加边 */ addEdge(vet1, vet2) { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 ) { throw new Error('Illegal Argument Exception'); } // 添加边 vet1 - vet2 this.adjList.get(vet1).push(vet2); this.adjList.get(vet2).push(vet1); } /* 删除边 */ removeEdge(vet1, vet2) { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 || this.adjList.get(vet1).indexOf(vet2) === -1 ) { throw new Error('Illegal Argument Exception'); } // 删除边 vet1 - vet2 this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); } /* 添加顶点 */ addVertex(vet) { if (this.adjList.has(vet)) return; // 在邻接表中添加一个新链表 this.adjList.set(vet, []); } /* 删除顶点 */ removeVertex(vet) { if (!this.adjList.has(vet)) { throw new Error('Illegal Argument Exception'); } // 在邻接表中删除顶点 vet 对应的链表 this.adjList.delete(vet); // 遍历其他顶点的链表,删除所有包含 vet 的边 for (const set of this.adjList.values()) { const index = set.indexOf(vet); if (index > -1) { set.splice(index, 1); } } } /* 打印邻接表 */ print() { console.log('邻接表 ='); for (const [key, value] of this.adjList) { const tmp = []; for (const vertex of value) { tmp.push(vertex.val); } console.log(key.val + ': ' + tmp.join()); } } } if (require.main === module) { /* Driver Code */ /* 初始化无向图 */ const v0 = new Vertex(1), v1 = new Vertex(3), v2 = new Vertex(2), v3 = new Vertex(5), v4 = new Vertex(4); const edges = [ [v0, v1], [v1, v2], [v2, v3], [v0, v3], [v2, v4], [v3, v4], ]; const graph = new GraphAdjList(edges); console.log('\n初始化后,图为'); graph.print(); /* 添加边 */ // 顶点 1, 2 即 v0, v2 graph.addEdge(v0, v2); console.log('\n添加边 1-2 后,图为'); graph.print(); /* 删除边 */ // 顶点 1, 3 即 v0, v1 graph.removeEdge(v0, v1); console.log('\n删除边 1-3 后,图为'); graph.print(); /* 添加顶点 */ const v5 = new Vertex(6); graph.addVertex(v5); console.log('\n添加顶点 6 后,图为'); graph.print(); /* 删除顶点 */ // 顶点 3 即 v1 graph.removeVertex(v1); console.log('\n删除顶点 3 后,图为'); graph.print(); } module.exports = { GraphAdjList, }; ================================================ FILE: codes/javascript/chapter_graph/graph_adjacency_matrix.js ================================================ /** * File: graph_adjacency_matrix.js * Created Time: 2023-02-09 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 基于邻接矩阵实现的无向图类 */ class GraphAdjMat { vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” adjMat; // 邻接矩阵,行列索引对应“顶点索引” /* 构造函数 */ constructor(vertices, edges) { this.vertices = []; this.adjMat = []; // 添加顶点 for (const val of vertices) { this.addVertex(val); } // 添加边 // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 for (const e of edges) { this.addEdge(e[0], e[1]); } } /* 获取顶点数量 */ size() { return this.vertices.length; } /* 添加顶点 */ addVertex(val) { const n = this.size(); // 向顶点列表中添加新顶点的值 this.vertices.push(val); // 在邻接矩阵中添加一行 const newRow = []; for (let j = 0; j < n; j++) { newRow.push(0); } this.adjMat.push(newRow); // 在邻接矩阵中添加一列 for (const row of this.adjMat) { row.push(0); } } /* 删除顶点 */ removeVertex(index) { if (index >= this.size()) { throw new RangeError('Index Out Of Bounds Exception'); } // 在顶点列表中移除索引 index 的顶点 this.vertices.splice(index, 1); // 在邻接矩阵中删除索引 index 的行 this.adjMat.splice(index, 1); // 在邻接矩阵中删除索引 index 的列 for (const row of this.adjMat) { row.splice(index, 1); } } /* 添加边 */ // 参数 i, j 对应 vertices 元素索引 addEdge(i, j) { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) === (j, i) this.adjMat[i][j] = 1; this.adjMat[j][i] = 1; } /* 删除边 */ // 参数 i, j 对应 vertices 元素索引 removeEdge(i, j) { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } this.adjMat[i][j] = 0; this.adjMat[j][i] = 0; } /* 打印邻接矩阵 */ print() { console.log('顶点列表 = ', this.vertices); console.log('邻接矩阵 =', this.adjMat); } } /* Driver Code */ /* 初始化无向图 */ // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 const vertices = [1, 3, 2, 5, 4]; const edges = [ [0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4], ]; const graph = new GraphAdjMat(vertices, edges); console.log('\n初始化后,图为'); graph.print(); /* 添加边 */ // 顶点 1, 2 的索引分别为 0, 2 graph.addEdge(0, 2); console.log('\n添加边 1-2 后,图为'); graph.print(); /* 删除边 */ // 顶点 1, 3 的索引分别为 0, 1 graph.removeEdge(0, 1); console.log('\n删除边 1-3 后,图为'); graph.print(); /* 添加顶点 */ graph.addVertex(6); console.log('\n添加顶点 6 后,图为'); graph.print(); /* 删除顶点 */ // 顶点 3 的索引为 1 graph.removeVertex(1); console.log('\n删除顶点 3 后,图为'); graph.print(); ================================================ FILE: codes/javascript/chapter_graph/graph_bfs.js ================================================ /** * File: graph_bfs.js * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { GraphAdjList } = require('./graph_adjacency_list'); const { Vertex } = require('../modules/Vertex'); /* 广度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 function graphBFS(graph, startVet) { // 顶点遍历序列 const res = []; // 哈希集合,用于记录已被访问过的顶点 const visited = new Set(); visited.add(startVet); // 队列用于实现 BFS const que = [startVet]; // 以顶点 vet 为起点,循环直至访问完所有顶点 while (que.length) { const vet = que.shift(); // 队首顶点出队 res.push(vet); // 记录访问顶点 // 遍历该顶点的所有邻接顶点 for (const adjVet of graph.adjList.get(vet) ?? []) { if (visited.has(adjVet)) { continue; // 跳过已被访问的顶点 } que.push(adjVet); // 只入队未访问的顶点 visited.add(adjVet); // 标记该顶点已被访问 } } // 返回顶点遍历序列 return res; } /* Driver Code */ /* 初始化无向图 */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; const graph = new GraphAdjList(edges); console.log('\n初始化后,图为'); graph.print(); /* 广度优先遍历 */ const res = graphBFS(graph, v[0]); console.log('\n广度优先遍历(BFS)顶点序列为'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: codes/javascript/chapter_graph/graph_dfs.js ================================================ /** * File: graph_dfs.js * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { Vertex } = require('../modules/Vertex'); const { GraphAdjList } = require('./graph_adjacency_list'); /* 深度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 function dfs(graph, visited, res, vet) { res.push(vet); // 记录访问顶点 visited.add(vet); // 标记该顶点已被访问 // 遍历该顶点的所有邻接顶点 for (const adjVet of graph.adjList.get(vet)) { if (visited.has(adjVet)) { continue; // 跳过已被访问的顶点 } // 递归访问邻接顶点 dfs(graph, visited, res, adjVet); } } /* 深度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 function graphDFS(graph, startVet) { // 顶点遍历序列 const res = []; // 哈希集合,用于记录已被访问过的顶点 const visited = new Set(); dfs(graph, visited, res, startVet); return res; } /* Driver Code */ /* 初始化无向图 */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; const graph = new GraphAdjList(edges); console.log('\n初始化后,图为'); graph.print(); /* 深度优先遍历 */ const res = graphDFS(graph, v[0]); console.log('\n深度优先遍历(DFS)顶点序列为'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: codes/javascript/chapter_greedy/coin_change_greedy.js ================================================ /** * File: coin_change_greedy.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 零钱兑换:贪心 */ function coinChangeGreedy(coins, amt) { // 假设 coins 数组有序 let i = coins.length - 1; let count = 0; // 循环进行贪心选择,直到无剩余金额 while (amt > 0) { // 找到小于且最接近剩余金额的硬币 while (i > 0 && coins[i] > amt) { i--; } // 选择 coins[i] amt -= coins[i]; count++; } // 若未找到可行方案,则返回 -1 return amt === 0 ? count : -1; } /* Driver Code */ // 贪心:能够保证找到全局最优解 let coins = [1, 5, 10, 20, 50, 100]; let amt = 186; let res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`凑到 ${amt} 所需的最少硬币数量为 ${res}`); // 贪心:无法保证找到全局最优解 coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`凑到 ${amt} 所需的最少硬币数量为 ${res}`); console.log('实际上需要的最少数量为 3 ,即 20 + 20 + 20'); // 贪心:无法保证找到全局最优解 coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`凑到 ${amt} 所需的最少硬币数量为 ${res}`); console.log('实际上需要的最少数量为 2 ,即 49 + 49'); ================================================ FILE: codes/javascript/chapter_greedy/fractional_knapsack.js ================================================ /** * File: fractional_knapsack.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 物品 */ class Item { constructor(w, v) { this.w = w; // 物品重量 this.v = v; // 物品价值 } } /* 分数背包:贪心 */ function fractionalKnapsack(wgt, val, cap) { // 创建物品列表,包含两个属性:重量、价值 const items = wgt.map((w, i) => new Item(w, val[i])); // 按照单位价值 item.v / item.w 从高到低进行排序 items.sort((a, b) => b.v / b.w - a.v / a.w); // 循环贪心选择 let res = 0; for (const item of items) { if (item.w <= cap) { // 若剩余容量充足,则将当前物品整个装进背包 res += item.v; cap -= item.w; } else { // 若剩余容量不足,则将当前物品的一部分装进背包 res += (item.v / item.w) * cap; // 已无剩余容量,因此跳出循环 break; } } return res; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // 贪心算法 const res = fractionalKnapsack(wgt, val, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); ================================================ FILE: codes/javascript/chapter_greedy/max_capacity.js ================================================ /** * File: max_capacity.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 最大容量:贪心 */ function maxCapacity(ht) { // 初始化 i, j,使其分列数组两端 let i = 0, j = ht.length - 1; // 初始最大容量为 0 let res = 0; // 循环贪心选择,直至两板相遇 while (i < j) { // 更新最大容量 const cap = Math.min(ht[i], ht[j]) * (j - i); res = Math.max(res, cap); // 向内移动短板 if (ht[i] < ht[j]) { i += 1; } else { j -= 1; } } return res; } /* Driver Code */ const ht = [3, 8, 5, 2, 7, 7, 3, 4]; // 贪心算法 const res = maxCapacity(ht); console.log(`最大容量为 ${res}`); ================================================ FILE: codes/javascript/chapter_greedy/max_product_cutting.js ================================================ /** * File: max_product_cutting.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 最大切分乘积:贪心 */ function maxProductCutting(n) { // 当 n <= 3 时,必须切分出一个 1 if (n <= 3) { return 1 * (n - 1); } // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 let a = Math.floor(n / 3); let b = n % 3; if (b === 1) { // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 return Math.pow(3, a - 1) * 2 * 2; } if (b === 2) { // 当余数为 2 时,不做处理 return Math.pow(3, a) * 2; } // 当余数为 0 时,不做处理 return Math.pow(3, a); } /* Driver Code */ let n = 58; // 贪心算法 let res = maxProductCutting(n); console.log(`最大切分乘积为 ${res}`); ================================================ FILE: codes/javascript/chapter_hashing/array_hash_map.js ================================================ /** * File: array_hash_map.js * Created Time: 2022-12-26 * Author: Justin (xiefahit@gmail.com) */ /* 键值对 Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* 基于数组实现的哈希表 */ class ArrayHashMap { #buckets; constructor() { // 初始化数组,包含 100 个桶 this.#buckets = new Array(100).fill(null); } /* 哈希函数 */ #hashFunc(key) { return key % 100; } /* 查询操作 */ get(key) { let index = this.#hashFunc(key); let pair = this.#buckets[index]; if (pair === null) return null; return pair.val; } /* 添加操作 */ set(key, val) { let index = this.#hashFunc(key); this.#buckets[index] = new Pair(key, val); } /* 删除操作 */ delete(key) { let index = this.#hashFunc(key); // 置为 null ,代表删除 this.#buckets[index] = null; } /* 获取所有键值对 */ entries() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i]); } } return arr; } /* 获取所有键 */ keys() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i].key); } } return arr; } /* 获取所有值 */ values() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i].val); } } return arr; } /* 打印哈希表 */ print() { let pairSet = this.entries(); for (const pair of pairSet) { console.info(`${pair.key} -> ${pair.val}`); } } } /* Driver Code */ /* 初始化哈希表 */ const map = new ArrayHashMap(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.set(12836, '小哈'); map.set(15937, '小啰'); map.set(16750, '小算'); map.set(13276, '小法'); map.set(10583, '小鸭'); console.info('\n添加完成后,哈希表为\nKey -> Value'); map.print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value let name = map.get(15937); console.info('\n输入学号 15937 ,查询到姓名 ' + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.delete(10583); console.info('\n删除 10583 后,哈希表为\nKey -> Value'); map.print(); /* 遍历哈希表 */ console.info('\n遍历键值对 Key->Value'); for (const pair of map.entries()) { if (!pair) continue; console.info(pair.key + ' -> ' + pair.val); } console.info('\n单独遍历键 Key'); for (const key of map.keys()) { console.info(key); } console.info('\n单独遍历值 Value'); for (const val of map.values()) { console.info(val); } ================================================ FILE: codes/javascript/chapter_hashing/hash_map.js ================================================ /** * File: hash_map.js * Created Time: 2022-12-26 * Author: Justin (xiefahit@gmail.com) */ /* Driver Code */ /* 初始化哈希表 */ const map = new Map(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.set(12836, '小哈'); map.set(15937, '小啰'); map.set(16750, '小算'); map.set(13276, '小法'); map.set(10583, '小鸭'); console.info('\n添加完成后,哈希表为\nKey -> Value'); console.info(map); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value let name = map.get(15937); console.info('\n输入学号 15937 ,查询到姓名 ' + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.delete(10583); console.info('\n删除 10583 后,哈希表为\nKey -> Value'); console.info(map); /* 遍历哈希表 */ console.info('\n遍历键值对 Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\n单独遍历键 Key'); for (const k of map.keys()) { console.info(k); } console.info('\n单独遍历值 Value'); for (const v of map.values()) { console.info(v); } ================================================ FILE: codes/javascript/chapter_hashing/hash_map_chaining.js ================================================ /** * File: hash_map_chaining.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 键值对 Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* 链式地址哈希表 */ class HashMapChaining { #size; // 键值对数量 #capacity; // 哈希表容量 #loadThres; // 触发扩容的负载因子阈值 #extendRatio; // 扩容倍数 #buckets; // 桶数组 /* 构造方法 */ constructor() { this.#size = 0; this.#capacity = 4; this.#loadThres = 2.0 / 3.0; this.#extendRatio = 2; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); } /* 哈希函数 */ #hashFunc(key) { return key % this.#capacity; } /* 负载因子 */ #loadFactor() { return this.#size / this.#capacity; } /* 查询操作 */ get(key) { const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // 遍历桶,若找到 key ,则返回对应 val for (const pair of bucket) { if (pair.key === key) { return pair.val; } } // 若未找到 key ,则返回 null return null; } /* 添加操作 */ put(key, val) { // 当负载因子超过阈值时,执行扩容 if (this.#loadFactor() > this.#loadThres) { this.#extend(); } const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 for (const pair of bucket) { if (pair.key === key) { pair.val = val; return; } } // 若无该 key ,则将键值对添加至尾部 const pair = new Pair(key, val); bucket.push(pair); this.#size++; } /* 删除操作 */ remove(key) { const index = this.#hashFunc(key); let bucket = this.#buckets[index]; // 遍历桶,从中删除键值对 for (let i = 0; i < bucket.length; i++) { if (bucket[i].key === key) { bucket.splice(i, 1); this.#size--; break; } } } /* 扩容哈希表 */ #extend() { // 暂存原哈希表 const bucketsTmp = this.#buckets; // 初始化扩容后的新哈希表 this.#capacity *= this.#extendRatio; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); this.#size = 0; // 将键值对从原哈希表搬运至新哈希表 for (const bucket of bucketsTmp) { for (const pair of bucket) { this.put(pair.key, pair.val); } } } /* 打印哈希表 */ print() { for (const bucket of this.#buckets) { let res = []; for (const pair of bucket) { res.push(pair.key + ' -> ' + pair.val); } console.log(res); } } } /* Driver Code */ /* 初始化哈希表 */ const map = new HashMapChaining(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(12836, '小哈'); map.put(15937, '小啰'); map.put(16750, '小算'); map.put(13276, '小法'); map.put(10583, '小鸭'); console.log('\n添加完成后,哈希表为\nKey -> Value'); map.print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value const name = map.get(13276); console.log('\n输入学号 13276 ,查询到姓名 ' + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(12836); console.log('\n删除 12836 后,哈希表为\nKey -> Value'); map.print(); ================================================ FILE: codes/javascript/chapter_hashing/hash_map_open_addressing.js ================================================ /** * File: hashMapOpenAddressing.js * Created Time: 2023-06-13 * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) */ /* 键值对 Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* 开放寻址哈希表 */ class HashMapOpenAddressing { #size; // 键值对数量 #capacity; // 哈希表容量 #loadThres; // 触发扩容的负载因子阈值 #extendRatio; // 扩容倍数 #buckets; // 桶数组 #TOMBSTONE; // 删除标记 /* 构造方法 */ constructor() { this.#size = 0; // 键值对数量 this.#capacity = 4; // 哈希表容量 this.#loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值 this.#extendRatio = 2; // 扩容倍数 this.#buckets = Array(this.#capacity).fill(null); // 桶数组 this.#TOMBSTONE = new Pair(-1, '-1'); // 删除标记 } /* 哈希函数 */ #hashFunc(key) { return key % this.#capacity; } /* 负载因子 */ #loadFactor() { return this.#size / this.#capacity; } /* 搜索 key 对应的桶索引 */ #findBucket(key) { let index = this.#hashFunc(key); let firstTombstone = -1; // 线性探测,当遇到空桶时跳出 while (this.#buckets[index] !== null) { // 若遇到 key ,返回对应的桶索引 if (this.#buckets[index].key === key) { // 若之前遇到了删除标记,则将键值对移动至该索引处 if (firstTombstone !== -1) { this.#buckets[firstTombstone] = this.#buckets[index]; this.#buckets[index] = this.#TOMBSTONE; return firstTombstone; // 返回移动后的桶索引 } return index; // 返回桶索引 } // 记录遇到的首个删除标记 if ( firstTombstone === -1 && this.#buckets[index] === this.#TOMBSTONE ) { firstTombstone = index; } // 计算桶索引,越过尾部则返回头部 index = (index + 1) % this.#capacity; } // 若 key 不存在,则返回添加点的索引 return firstTombstone === -1 ? index : firstTombstone; } /* 查询操作 */ get(key) { // 搜索 key 对应的桶索引 const index = this.#findBucket(key); // 若找到键值对,则返回对应 val if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { return this.#buckets[index].val; } // 若键值对不存在,则返回 null return null; } /* 添加操作 */ put(key, val) { // 当负载因子超过阈值时,执行扩容 if (this.#loadFactor() > this.#loadThres) { this.#extend(); } // 搜索 key 对应的桶索引 const index = this.#findBucket(key); // 若找到键值对,则覆盖 val 并返回 if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { this.#buckets[index].val = val; return; } // 若键值对不存在,则添加该键值对 this.#buckets[index] = new Pair(key, val); this.#size++; } /* 删除操作 */ remove(key) { // 搜索 key 对应的桶索引 const index = this.#findBucket(key); // 若找到键值对,则用删除标记覆盖它 if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { this.#buckets[index] = this.#TOMBSTONE; this.#size--; } } /* 扩容哈希表 */ #extend() { // 暂存原哈希表 const bucketsTmp = this.#buckets; // 初始化扩容后的新哈希表 this.#capacity *= this.#extendRatio; this.#buckets = Array(this.#capacity).fill(null); this.#size = 0; // 将键值对从原哈希表搬运至新哈希表 for (const pair of bucketsTmp) { if (pair !== null && pair !== this.#TOMBSTONE) { this.put(pair.key, pair.val); } } } /* 打印哈希表 */ print() { for (const pair of this.#buckets) { if (pair === null) { console.log('null'); } else if (pair === this.#TOMBSTONE) { console.log('TOMBSTONE'); } else { console.log(pair.key + ' -> ' + pair.val); } } } } /* Driver Code */ // 初始化哈希表 const hashmap = new HashMapOpenAddressing(); // 添加操作 // 在哈希表中添加键值对 (key, val) hashmap.put(12836, '小哈'); hashmap.put(15937, '小啰'); hashmap.put(16750, '小算'); hashmap.put(13276, '小法'); hashmap.put(10583, '小鸭'); console.log('\n添加完成后,哈希表为\nKey -> Value'); hashmap.print(); // 查询操作 // 向哈希表中输入键 key ,得到值 val const name = hashmap.get(13276); console.log('\n输入学号 13276 ,查询到姓名 ' + name); // 删除操作 // 在哈希表中删除键值对 (key, val) hashmap.remove(16750); console.log('\n删除 16750 后,哈希表为\nKey -> Value'); hashmap.print(); ================================================ FILE: codes/javascript/chapter_hashing/simple_hash.js ================================================ /** * File: simple_hash.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 加法哈希 */ function addHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* 乘法哈希 */ function mulHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (31 * hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* 异或哈希 */ function xorHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash ^= c.charCodeAt(0); } return hash % MODULUS; } /* 旋转哈希 */ function rotHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; } return hash; } /* Driver Code */ const key = 'Hello 算法'; let hash = addHash(key); console.log('加法哈希值为 ' + hash); hash = mulHash(key); console.log('乘法哈希值为 ' + hash); hash = xorHash(key); console.log('异或哈希值为 ' + hash); hash = rotHash(key); console.log('旋转哈希值为 ' + hash); ================================================ FILE: codes/javascript/chapter_heap/my_heap.js ================================================ /** * File: my_heap.js * Created Time: 2023-02-06 * Author: what-is-me (whatisme@outlook.jp) */ const { printHeap } = require('../modules/PrintUtil'); /* 最大堆类 */ class MaxHeap { #maxHeap; /* 构造方法,建立空堆或根据输入列表建堆 */ constructor(nums) { // 将列表元素原封不动添加进堆 this.#maxHeap = nums === undefined ? [] : [...nums]; // 堆化除叶节点以外的其他所有节点 for (let i = this.#parent(this.size() - 1); i >= 0; i--) { this.#siftDown(i); } } /* 获取左子节点的索引 */ #left(i) { return 2 * i + 1; } /* 获取右子节点的索引 */ #right(i) { return 2 * i + 2; } /* 获取父节点的索引 */ #parent(i) { return Math.floor((i - 1) / 2); // 向下整除 } /* 交换元素 */ #swap(i, j) { const tmp = this.#maxHeap[i]; this.#maxHeap[i] = this.#maxHeap[j]; this.#maxHeap[j] = tmp; } /* 获取堆大小 */ size() { return this.#maxHeap.length; } /* 判断堆是否为空 */ isEmpty() { return this.size() === 0; } /* 访问堆顶元素 */ peek() { return this.#maxHeap[0]; } /* 元素入堆 */ push(val) { // 添加节点 this.#maxHeap.push(val); // 从底至顶堆化 this.#siftUp(this.size() - 1); } /* 从节点 i 开始,从底至顶堆化 */ #siftUp(i) { while (true) { // 获取节点 i 的父节点 const p = this.#parent(i); // 当“越过根节点”或“节点无须修复”时,结束堆化 if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break; // 交换两节点 this.#swap(i, p); // 循环向上堆化 i = p; } } /* 元素出堆 */ pop() { // 判空处理 if (this.isEmpty()) throw new Error('堆为空'); // 交换根节点与最右叶节点(交换首元素与尾元素) this.#swap(0, this.size() - 1); // 删除节点 const val = this.#maxHeap.pop(); // 从顶至底堆化 this.#siftDown(0); // 返回堆顶元素 return val; } /* 从节点 i 开始,从顶至底堆化 */ #siftDown(i) { while (true) { // 判断节点 i, l, r 中值最大的节点,记为 ma const l = this.#left(i), r = this.#right(i); let ma = i; if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l; if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r; // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if (ma === i) break; // 交换两节点 this.#swap(i, ma); // 循环向下堆化 i = ma; } } /* 打印堆(二叉树) */ print() { printHeap(this.#maxHeap); } /* 取出堆中元素 */ getMaxHeap() { return this.#maxHeap; } } /* Driver Code */ if (require.main === module) { /* 初始化大顶堆 */ const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); console.log('\n输入列表并建堆后'); maxHeap.print(); /* 获取堆顶元素 */ let peek = maxHeap.peek(); console.log(`\n堆顶元素为 ${peek}`); /* 元素入堆 */ let val = 7; maxHeap.push(val); console.log(`\n元素 ${val} 入堆后`); maxHeap.print(); /* 堆顶元素出堆 */ peek = maxHeap.pop(); console.log(`\n堆顶元素 ${peek} 出堆后`); maxHeap.print(); /* 获取堆大小 */ let size = maxHeap.size(); console.log(`\n堆元素数量为 ${size}`); /* 判断堆是否为空 */ let isEmpty = maxHeap.isEmpty(); console.log(`\n堆是否为空 ${isEmpty}`); } module.exports = { MaxHeap, }; ================================================ FILE: codes/javascript/chapter_heap/top_k.js ================================================ /** * File: top_k.js * Created Time: 2023-08-13 * Author: Justin (xiefahit@gmail.com) */ const { MaxHeap } = require('./my_heap'); /* 元素入堆 */ function pushMinHeap(maxHeap, val) { // 元素取反 maxHeap.push(-val); } /* 元素出堆 */ function popMinHeap(maxHeap) { // 元素取反 return -maxHeap.pop(); } /* 访问堆顶元素 */ function peekMinHeap(maxHeap) { // 元素取反 return -maxHeap.peek(); } /* 取出堆中元素 */ function getMinHeap(maxHeap) { // 元素取反 return maxHeap.getMaxHeap().map((num) => -num); } /* 基于堆查找数组中最大的 k 个元素 */ function topKHeap(nums, k) { // 初始化小顶堆 // 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆 const maxHeap = new MaxHeap([]); // 将数组的前 k 个元素入堆 for (let i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // 从第 k+1 个元素开始,保持堆的长度为 k for (let i = k; i < nums.length; i++) { // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } // 返回堆中元素 return getMinHeap(maxHeap); } /* Driver Code */ const nums = [1, 7, 6, 3, 2]; const k = 3; const res = topKHeap(nums, k); console.log(`最大的 ${k} 个元素为`, res); ================================================ FILE: codes/javascript/chapter_searching/binary_search.js ================================================ /** * File: binary_search.js * Created Time: 2022-12-22 * Author: JoseHung (szhong@link.cuhk.edu.hk) */ /* 二分查找(双闭区间) */ function binarySearch(nums, target) { // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 let i = 0, j = nums.length - 1; // 循环,当搜索区间为空时跳出(当 i > j 时为空) while (i <= j) { // 计算中点索引 m ,使用 parseInt() 向下取整 const m = parseInt(i + (j - i) / 2); if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 i = m + 1; else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 j = m - 1; else return m; // 找到目标元素,返回其索引 } // 未找到目标元素,返回 -1 return -1; } /* 二分查找(左闭右开区间) */ function binarySearchLCRO(nums, target) { // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 let i = 0, j = nums.length; // 循环,当搜索区间为空时跳出(当 i = j 时为空) while (i < j) { // 计算中点索引 m ,使用 parseInt() 向下取整 const m = parseInt(i + (j - i) / 2); if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j) 中 i = m + 1; else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中 j = m; // 找到目标元素,返回其索引 else return m; } // 未找到目标元素,返回 -1 return -1; } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分查找(双闭区间) */ let index = binarySearch(nums, target); console.log('目标元素 6 的索引 = ' + index); /* 二分查找(左闭右开区间) */ index = binarySearchLCRO(nums, target); console.log('目标元素 6 的索引 = ' + index); ================================================ FILE: codes/javascript/chapter_searching/binary_search_edge.js ================================================ /** * File: binary_search_edge.js * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ const { binarySearchInsertion } = require('./binary_search_insertion.js'); /* 二分查找最左一个 target */ function binarySearchLeftEdge(nums, target) { // 等价于查找 target 的插入点 const i = binarySearchInsertion(nums, target); // 未找到 target ,返回 -1 if (i === nums.length || nums[i] !== target) { return -1; } // 找到 target ,返回索引 i return i; } /* 二分查找最右一个 target */ function binarySearchRightEdge(nums, target) { // 转化为查找最左一个 target + 1 const i = binarySearchInsertion(nums, target + 1); // j 指向最右一个 target ,i 指向首个大于 target 的元素 const j = i - 1; // 未找到 target ,返回 -1 if (j === -1 || nums[j] !== target) { return -1; } // 找到 target ,返回索引 j return j; } /* Driver Code */ // 包含重复元素的数组 const nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\n数组 nums = ' + nums); // 二分查找左边界和右边界 for (const target of [6, 7]) { let index = binarySearchLeftEdge(nums, target); console.log('最左一个元素 ' + target + ' 的索引为 ' + index); index = binarySearchRightEdge(nums, target); console.log('最右一个元素 ' + target + ' 的索引为 ' + index); } ================================================ FILE: codes/javascript/chapter_searching/binary_search_insertion.js ================================================ /** * File: binary_search_insertion.js * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 二分查找插入点(无重复元素) */ function binarySearchInsertionSimple(nums, target) { let i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m, 使用 Math.floor() 向下取整 if (nums[m] < target) { i = m + 1; // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在区间 [i, m-1] 中 } else { return m; // 找到 target ,返回插入点 m } } // 未找到 target ,返回插入点 i return i; } /* 二分查找插入点(存在重复元素) */ function binarySearchInsertion(nums, target) { let i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m, 使用 Math.floor() 向下取整 if (nums[m] < target) { i = m + 1; // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在区间 [i, m-1] 中 } else { j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 } } // 返回插入点 i return i; } /* Driver Code */ // 无重复元素的数组 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; console.log('\n数组 nums = ' + nums); // 二分查找插入点 for (const target of [6, 9]) { const index = binarySearchInsertionSimple(nums, target); console.log('元素 ' + target + ' 的插入点的索引为 ' + index); } // 包含重复元素的数组 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\n数组 nums = ' + nums); // 二分查找插入点 for (const target of [2, 6, 20]) { const index = binarySearchInsertion(nums, target); console.log('元素 ' + target + ' 的插入点的索引为 ' + index); } module.exports = { binarySearchInsertion, }; ================================================ FILE: codes/javascript/chapter_searching/hashing_search.js ================================================ /** * File: hashing_search.js * Created Time: 2022-12-29 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { arrToLinkedList } = require('../modules/ListNode'); /* 哈希查找(数组) */ function hashingSearchArray(map, target) { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 return map.has(target) ? map.get(target) : -1; } /* 哈希查找(链表) */ function hashingSearchLinkedList(map, target) { // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 null return map.has(target) ? map.get(target) : null; } /* Driver Code */ const target = 3; /* 哈希查找(数组) */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // 初始化哈希表 const map = new Map(); for (let i = 0; i < nums.length; i++) { map.set(nums[i], i); // key: 元素,value: 索引 } const index = hashingSearchArray(map, target); console.log('目标元素 3 的索引 = ' + index); /* 哈希查找(链表) */ let head = arrToLinkedList(nums); // 初始化哈希表 const map1 = new Map(); while (head != null) { map1.set(head.val, head); // key: 节点值,value: 节点 head = head.next; } const node = hashingSearchLinkedList(map1, target); console.log('目标节点值 3 的对应节点对象为', node); ================================================ FILE: codes/javascript/chapter_searching/linear_search.js ================================================ /** * File: linear_search.js * Created Time: 2022-12-22 * Author: JoseHung (szhong@link.cuhk.edu.hk) */ const { ListNode, arrToLinkedList } = require('../modules/ListNode'); /* 线性查找(数组) */ function linearSearchArray(nums, target) { // 遍历数组 for (let i = 0; i < nums.length; i++) { // 找到目标元素,返回其索引 if (nums[i] === target) { return i; } } // 未找到目标元素,返回 -1 return -1; } /* 线性查找(链表)*/ function linearSearchLinkedList(head, target) { // 遍历链表 while (head) { // 找到目标节点,返回之 if (head.val === target) { return head; } head = head.next; } // 未找到目标节点,返回 null return null; } /* Driver Code */ const target = 3; /* 在数组中执行线性查找 */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; const index = linearSearchArray(nums, target); console.log('目标元素 3 的索引 = ' + index); /* 在链表中执行线性查找 */ const head = arrToLinkedList(nums); const node = linearSearchLinkedList(head, target); console.log('目标节点值 3 的对应节点对象为 ', node); ================================================ FILE: codes/javascript/chapter_searching/two_sum.js ================================================ /** * File: two_sum.js * Created Time: 2022-12-15 * Author: gyt95 (gytkwan@gmail.com) */ /* 方法一:暴力枚举 */ function twoSumBruteForce(nums, target) { const n = nums.length; // 两层循环,时间复杂度为 O(n^2) for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { if (nums[i] + nums[j] === target) { return [i, j]; } } } return []; } /* 方法二:辅助哈希表 */ function twoSumHashTable(nums, target) { // 辅助哈希表,空间复杂度为 O(n) let m = {}; // 单层循环,时间复杂度为 O(n) for (let i = 0; i < nums.length; i++) { if (m[target - nums[i]] !== undefined) { return [m[target - nums[i]], i]; } else { m[nums[i]] = i; } } return []; } /* Driver Code */ // 方法一 const nums = [2, 7, 11, 15], target = 13; let res = twoSumBruteForce(nums, target); console.log('方法一 res = ', res); // 方法二 res = twoSumHashTable(nums, target); console.log('方法二 res = ', res); ================================================ FILE: codes/javascript/chapter_sorting/bubble_sort.js ================================================ /** * File: bubble_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 冒泡排序 */ function bubbleSort(nums) { // 外循环:未排序区间为 [0, i] for (let i = nums.length - 1; i > 0; i--) { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* 冒泡排序(标志优化)*/ function bubbleSortWithFlag(nums) { // 外循环:未排序区间为 [0, i] for (let i = nums.length - 1; i > 0; i--) { let flag = false; // 初始化标志位 // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // 记录交换元素 } } if (!flag) break; // 此轮“冒泡”未交换任何元素,直接跳出 } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); console.log('冒泡排序完成后 nums =', nums); const nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); console.log('冒泡排序完成后 nums =', nums1); ================================================ FILE: codes/javascript/chapter_sorting/bucket_sort.js ================================================ /** * File: bucket_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 桶排序 */ function bucketSort(nums) { // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 const k = nums.length / 2; const buckets = []; for (let i = 0; i < k; i++) { buckets.push([]); } // 1. 将数组元素分配到各个桶中 for (const num of nums) { // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] const i = Math.floor(num * k); // 将 num 添加进桶 i buckets[i].push(num); } // 2. 对各个桶执行排序 for (const bucket of buckets) { // 使用内置排序函数,也可以替换成其他排序算法 bucket.sort((a, b) => a - b); } // 3. 遍历桶合并结果 let i = 0; for (const bucket of buckets) { for (const num of bucket) { nums[i++] = num; } } } /* Driver Code */ const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); console.log('桶排序完成后 nums =', nums); ================================================ FILE: codes/javascript/chapter_sorting/counting_sort.js ================================================ /** * File: counting_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 计数排序 */ // 简单实现,无法用于排序对象 function countingSortNaive(nums) { // 1. 统计数组最大元素 m let m = Math.max(...nums); // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 const counter = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. 遍历 counter ,将各元素填入原数组 nums let i = 0; for (let num = 0; num < m + 1; num++) { for (let j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* 计数排序 */ // 完整实现,可排序对象,并且是稳定排序 function countingSort(nums) { // 1. 统计数组最大元素 m let m = Math.max(...nums); // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 const counter = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 for (let i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. 倒序遍历 nums ,将各元素填入结果数组 res // 初始化数组 res 用于记录结果 const n = nums.length; const res = new Array(n); for (let i = n - 1; i >= 0; i--) { const num = nums[i]; res[counter[num] - 1] = num; // 将 num 放置到对应索引处 counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 } // 使用结果数组 res 覆盖原数组 nums for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Driver Code */ const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); console.log('计数排序(无法排序对象)完成后 nums =', nums); const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); console.log('计数排序完成后 nums1 =', nums1); ================================================ FILE: codes/javascript/chapter_sorting/heap_sort.js ================================================ /** * File: heap_sort.js * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ function siftDown(nums, n, i) { while (true) { // 判断节点 i, l, r 中值最大的节点,记为 ma let l = 2 * i + 1; let r = 2 * i + 2; let ma = i; if (l < n && nums[l] > nums[ma]) { ma = l; } if (r < n && nums[r] > nums[ma]) { ma = r; } // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if (ma === i) { break; } // 交换两节点 [nums[i], nums[ma]] = [nums[ma], nums[i]]; // 循环向下堆化 i = ma; } } /* 堆排序 */ function heapSort(nums) { // 建堆操作:堆化除叶节点以外的其他所有节点 for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // 从堆中提取最大元素,循环 n-1 轮 for (let i = nums.length - 1; i > 0; i--) { // 交换根节点与最右叶节点(交换首元素与尾元素) [nums[0], nums[i]] = [nums[i], nums[0]]; // 以根节点为起点,从顶至底进行堆化 siftDown(nums, i, 0); } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; heapSort(nums); console.log('堆排序完成后 nums =', nums); ================================================ FILE: codes/javascript/chapter_sorting/insertion_sort.js ================================================ /** * File: insertion_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 插入排序 */ function insertionSort(nums) { // 外循环:已排序区间为 [0, i-1] for (let i = 1; i < nums.length; i++) { let base = nums[i], j = i - 1; // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位 j--; } nums[j + 1] = base; // 将 base 赋值到正确位置 } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); console.log('插入排序完成后 nums =', nums); ================================================ FILE: codes/javascript/chapter_sorting/merge_sort.js ================================================ /** * File: merge_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 合并左子数组和右子数组 */ function merge(nums, left, mid, right) { // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] // 创建一个临时数组 tmp ,用于存放合并后的结果 const tmp = new Array(right - left + 1); // 初始化左子数组和右子数组的起始索引 let i = left, j = mid + 1, k = 0; // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // 将左子数组和右子数组的剩余元素复制到临时数组中 while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* 归并排序 */ function mergeSort(nums, left, right) { // 终止条件 if (left >= right) return; // 当子数组长度为 1 时终止递归 // 划分阶段 let mid = Math.floor(left + (right - left) / 2); // 计算中点 mergeSort(nums, left, mid); // 递归左子数组 mergeSort(nums, mid + 1, right); // 递归右子数组 // 合并阶段 merge(nums, left, mid, right); } /* Driver Code */ const nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); console.log('归并排序完成后 nums =', nums); ================================================ FILE: codes/javascript/chapter_sorting/quick_sort.js ================================================ /** * File: quick_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 快速排序类 */ class QuickSort { /* 元素交换 */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵划分 */ partition(nums, left, right) { // 以 nums[left] 为基准数 let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j -= 1; // 从右向左找首个小于基准数的元素 } while (i < j && nums[i] <= nums[left]) { i += 1; // 从左向右找首个大于基准数的元素 } // 元素交换 this.swap(nums, i, j); // 交换这两个元素 } this.swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } /* 快速排序 */ quickSort(nums, left, right) { // 子数组长度为 1 时终止递归 if (left >= right) return; // 哨兵划分 const pivot = this.partition(nums, left, right); // 递归左子数组、右子数组 this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* 快速排序类(中位基准数优化) */ class QuickSortMedian { /* 元素交换 */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 选取三个候选元素的中位数 */ medianThree(nums, left, mid, right) { let l = nums[left], m = nums[mid], r = nums[right]; // m 在 l 和 r 之间 if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // l 在 m 和 r 之间 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; return right; } /* 哨兵划分(三数取中值) */ partition(nums, left, right) { // 选取三个候选元素的中位数 let med = this.medianThree( nums, left, Math.floor((left + right) / 2), right ); // 将中位数交换至数组最左端 this.swap(nums, left, med); // 以 nums[left] 为基准数 let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 从右向左找首个小于基准数的元素 while (i < j && nums[i] <= nums[left]) i++; // 从左向右找首个大于基准数的元素 this.swap(nums, i, j); // 交换这两个元素 } this.swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } /* 快速排序 */ quickSort(nums, left, right) { // 子数组长度为 1 时终止递归 if (left >= right) return; // 哨兵划分 const pivot = this.partition(nums, left, right); // 递归左子数组、右子数组 this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* 快速排序类(递归深度优化) */ class QuickSortTailCall { /* 元素交换 */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵划分 */ partition(nums, left, right) { // 以 nums[left] 为基准数 let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 从右向左找首个小于基准数的元素 while (i < j && nums[i] <= nums[left]) i++; // 从左向右找首个大于基准数的元素 this.swap(nums, i, j); // 交换这两个元素 } this.swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } /* 快速排序(递归深度优化) */ quickSort(nums, left, right) { // 子数组长度为 1 时终止 while (left < right) { // 哨兵划分操作 let pivot = this.partition(nums, left, right); // 对两个子数组中较短的那个执行快速排序 if (pivot - left < right - pivot) { this.quickSort(nums, left, pivot - 1); // 递归排序左子数组 left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] } else { this.quickSort(nums, pivot + 1, right); // 递归排序右子数组 right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] } } } } /* Driver Code */ /* 快速排序 */ const nums = [2, 4, 1, 0, 3, 5]; const quickSort = new QuickSort(); quickSort.quickSort(nums, 0, nums.length - 1); console.log('快速排序完成后 nums =', nums); /* 快速排序(中位基准数优化) */ const nums1 = [2, 4, 1, 0, 3, 5]; const quickSortMedian = new QuickSortMedian(); quickSortMedian.quickSort(nums1, 0, nums1.length - 1); console.log('快速排序(中位基准数优化)完成后 nums =', nums1); /* 快速排序(递归深度优化) */ const nums2 = [2, 4, 1, 0, 3, 5]; const quickSortTailCall = new QuickSortTailCall(); quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); console.log('快速排序(递归深度优化)完成后 nums =', nums2); ================================================ FILE: codes/javascript/chapter_sorting/radix_sort.js ================================================ /** * File: radix_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ function digit(num, exp) { // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 return Math.floor(num / exp) % 10; } /* 计数排序(根据 nums 第 k 位排序) */ function countingSortDigit(nums, exp) { // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 const counter = new Array(10).fill(0); const n = nums.length; // 统计 0~9 各数字的出现次数 for (let i = 0; i < n; i++) { const d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d counter[d]++; // 统计数字 d 的出现次数 } // 求前缀和,将“出现个数”转换为“数组索引” for (let i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 倒序遍历,根据桶内统计结果,将各元素填入 res const res = new Array(n).fill(0); for (let i = n - 1; i >= 0; i--) { const d = digit(nums[i], exp); const j = counter[d] - 1; // 获取 d 在数组中的索引 j res[j] = nums[i]; // 将当前元素填入索引 j counter[d]--; // 将 d 的数量减 1 } // 使用结果覆盖原数组 nums for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* 基数排序 */ function radixSort(nums) { // 获取数组的最大元素,用于判断最大位数 let m = Math.max(... nums); // 按照从低位到高位的顺序遍历 for (let exp = 1; exp <= m; exp *= 10) { // 对数组元素的第 k 位执行计数排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums, exp); } } /* Driver Code */ const nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radixSort(nums); console.log('基数排序完成后 nums =', nums); ================================================ FILE: codes/javascript/chapter_sorting/selection_sort.js ================================================ /** * File: selection_sort.js * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* 选择排序 */ function selectionSort(nums) { let n = nums.length; // 外循环:未排序区间为 [i, n-1] for (let i = 0; i < n - 1; i++) { // 内循环:找到未排序区间内的最小元素 let k = i; for (let j = i + 1; j < n; j++) { if (nums[j] < nums[k]) { k = j; // 记录最小元素的索引 } } // 将该最小元素与未排序区间的首个元素交换 [nums[i], nums[k]] = [nums[k], nums[i]]; } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; selectionSort(nums); console.log('选择排序完成后 nums =', nums); ================================================ FILE: codes/javascript/chapter_stack_and_queue/array_deque.js ================================================ /** * File: array_deque.js * Created Time: 2023-02-28 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 基于环形数组实现的双向队列 */ class ArrayDeque { #nums; // 用于存储双向队列元素的数组 #front; // 队首指针,指向队首元素 #queSize; // 双向队列长度 /* 构造方法 */ constructor(capacity) { this.#nums = new Array(capacity); this.#front = 0; this.#queSize = 0; } /* 获取双向队列的容量 */ capacity() { return this.#nums.length; } /* 获取双向队列的长度 */ size() { return this.#queSize; } /* 判断双向队列是否为空 */ isEmpty() { return this.#queSize === 0; } /* 计算环形数组索引 */ index(i) { // 通过取余操作实现数组首尾相连 // 当 i 越过数组尾部后,回到头部 // 当 i 越过数组头部后,回到尾部 return (i + this.capacity()) % this.capacity(); } /* 队首入队 */ pushFirst(num) { if (this.#queSize === this.capacity()) { console.log('双向队列已满'); return; } // 队首指针向左移动一位 // 通过取余操作实现 front 越过数组头部后回到尾部 this.#front = this.index(this.#front - 1); // 将 num 添加至队首 this.#nums[this.#front] = num; this.#queSize++; } /* 队尾入队 */ pushLast(num) { if (this.#queSize === this.capacity()) { console.log('双向队列已满'); return; } // 计算队尾指针,指向队尾索引 + 1 const rear = this.index(this.#front + this.#queSize); // 将 num 添加至队尾 this.#nums[rear] = num; this.#queSize++; } /* 队首出队 */ popFirst() { const num = this.peekFirst(); // 队首指针向后移动一位 this.#front = this.index(this.#front + 1); this.#queSize--; return num; } /* 队尾出队 */ popLast() { const num = this.peekLast(); this.#queSize--; return num; } /* 访问队首元素 */ peekFirst() { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); return this.#nums[this.#front]; } /* 访问队尾元素 */ peekLast() { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); // 计算尾元素索引 const last = this.index(this.#front + this.#queSize - 1); return this.#nums[last]; } /* 返回数组用于打印 */ toArray() { // 仅转换有效长度范围内的列表元素 const res = []; for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) { res[i] = this.#nums[this.index(j)]; } return res; } } /* Driver Code */ /* 初始化双向队列 */ const capacity = 5; const deque = new ArrayDeque(capacity); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); console.log('双向队列 deque = [' + deque.toArray() + ']'); /* 访问元素 */ const peekFirst = deque.peekFirst(); console.log('队首元素 peekFirst = ' + peekFirst); const peekLast = deque.peekLast(); console.log('队尾元素 peekLast = ' + peekLast); /* 元素入队 */ deque.pushLast(4); console.log('元素 4 队尾入队后 deque = [' + deque.toArray() + ']'); deque.pushFirst(1); console.log('元素 1 队首入队后 deque = [' + deque.toArray() + ']'); /* 元素出队 */ const popLast = deque.popLast(); console.log( '队尾出队元素 = ' + popLast + ',队尾出队后 deque = [' + deque.toArray() + ']' ); const popFirst = deque.popFirst(); console.log( '队首出队元素 = ' + popFirst + ',队首出队后 deque = [' + deque.toArray() + ']' ); /* 获取双向队列的长度 */ const size = deque.size(); console.log('双向队列长度 size = ' + size); /* 判断双向队列是否为空 */ const isEmpty = deque.isEmpty(); console.log('双向队列是否为空 = ' + isEmpty); ================================================ FILE: codes/javascript/chapter_stack_and_queue/array_queue.js ================================================ /** * File: array_queue.js * Created Time: 2022-12-13 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* 基于环形数组实现的队列 */ class ArrayQueue { #nums; // 用于存储队列元素的数组 #front = 0; // 队首指针,指向队首元素 #queSize = 0; // 队列长度 constructor(capacity) { this.#nums = new Array(capacity); } /* 获取队列的容量 */ get capacity() { return this.#nums.length; } /* 获取队列的长度 */ get size() { return this.#queSize; } /* 判断队列是否为空 */ isEmpty() { return this.#queSize === 0; } /* 入队 */ push(num) { if (this.size === this.capacity) { console.log('队列已满'); return; } // 计算队尾指针,指向队尾索引 + 1 // 通过取余操作实现 rear 越过数组尾部后回到头部 const rear = (this.#front + this.size) % this.capacity; // 将 num 添加至队尾 this.#nums[rear] = num; this.#queSize++; } /* 出队 */ pop() { const num = this.peek(); // 队首指针向后移动一位,若越过尾部,则返回到数组头部 this.#front = (this.#front + 1) % this.capacity; this.#queSize--; return num; } /* 访问队首元素 */ peek() { if (this.isEmpty()) throw new Error('队列为空'); return this.#nums[this.#front]; } /* 返回 Array */ toArray() { // 仅转换有效长度范围内的列表元素 const arr = new Array(this.size); for (let i = 0, j = this.#front; i < this.size; i++, j++) { arr[i] = this.#nums[j % this.capacity]; } return arr; } } /* Driver Code */ /* 初始化队列 */ const capacity = 10; const queue = new ArrayQueue(capacity); /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('队列 queue =', queue.toArray()); /* 访问队首元素 */ const peek = queue.peek(); console.log('队首元素 peek = ' + peek); /* 元素出队 */ const pop = queue.pop(); console.log('出队元素 pop = ' + pop + ',出队后 queue =', queue.toArray()); /* 获取队列的长度 */ const size = queue.size; console.log('队列长度 size = ' + size); /* 判断队列是否为空 */ const isEmpty = queue.isEmpty(); console.log('队列是否为空 = ' + isEmpty); /* 测试环形数组 */ for (let i = 0; i < 10; i++) { queue.push(i); queue.pop(); console.log('第 ' + i + ' 轮入队 + 出队后 queue =', queue.toArray()); } ================================================ FILE: codes/javascript/chapter_stack_and_queue/array_stack.js ================================================ /** * File: array_stack.js * Created Time: 2022-12-09 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* 基于数组实现的栈 */ class ArrayStack { #stack; constructor() { this.#stack = []; } /* 获取栈的长度 */ get size() { return this.#stack.length; } /* 判断栈是否为空 */ isEmpty() { return this.#stack.length === 0; } /* 入栈 */ push(num) { this.#stack.push(num); } /* 出栈 */ pop() { if (this.isEmpty()) throw new Error('栈为空'); return this.#stack.pop(); } /* 访问栈顶元素 */ top() { if (this.isEmpty()) throw new Error('栈为空'); return this.#stack[this.#stack.length - 1]; } /* 返回 Array */ toArray() { return this.#stack; } } /* Driver Code */ /* 初始化栈 */ const stack = new ArrayStack(); /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('栈 stack = '); console.log(stack.toArray()); /* 访问栈顶元素 */ const top = stack.top(); console.log('栈顶元素 top = ' + top); /* 元素出栈 */ const pop = stack.pop(); console.log('出栈元素 pop = ' + pop + ',出栈后 stack = '); console.log(stack.toArray()); /* 获取栈的长度 */ const size = stack.size; console.log('栈的长度 size = ' + size); /* 判断是否为空 */ const isEmpty = stack.isEmpty(); console.log('栈是否为空 = ' + isEmpty); ================================================ FILE: codes/javascript/chapter_stack_and_queue/deque.js ================================================ /** * File: deque.js * Created Time: 2023-01-17 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Driver Code */ /* 初始化双向队列 */ // JavaScript 没有内置的双端队列,只能把 Array 当作双端队列来使用 const deque = []; /* 元素入队 */ deque.push(2); deque.push(5); deque.push(4); // 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n) deque.unshift(3); deque.unshift(1); console.log('双向队列 deque = ', deque); /* 访问元素 */ const peekFirst = deque[0]; console.log('队首元素 peekFirst = ' + peekFirst); const peekLast = deque[deque.length - 1]; console.log('队尾元素 peekLast = ' + peekLast); /* 元素出队 */ // 请注意,由于是数组,shift() 方法的时间复杂度为 O(n) const popFront = deque.shift(); console.log( '队首出队元素 popFront = ' + popFront + ',队首出队后 deque = ' + deque ); const popBack = deque.pop(); console.log( '队尾出队元素 popBack = ' + popBack + ',队尾出队后 deque = ' + deque ); /* 获取双向队列的长度 */ const size = deque.length; console.log('双向队列长度 size = ' + size); /* 判断双向队列是否为空 */ const isEmpty = size === 0; console.log('双向队列是否为空 = ' + isEmpty); ================================================ FILE: codes/javascript/chapter_stack_and_queue/linkedlist_deque.js ================================================ /** * File: linkedlist_deque.js * Created Time: 2023-02-04 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 双向链表节点 */ class ListNode { prev; // 前驱节点引用 (指针) next; // 后继节点引用 (指针) val; // 节点值 constructor(val) { this.val = val; this.next = null; this.prev = null; } } /* 基于双向链表实现的双向队列 */ class LinkedListDeque { #front; // 头节点 front #rear; // 尾节点 rear #queSize; // 双向队列的长度 constructor() { this.#front = null; this.#rear = null; this.#queSize = 0; } /* 队尾入队操作 */ pushLast(val) { const node = new ListNode(val); // 若链表为空,则令 front 和 rear 都指向 node if (this.#queSize === 0) { this.#front = node; this.#rear = node; } else { // 将 node 添加至链表尾部 this.#rear.next = node; node.prev = this.#rear; this.#rear = node; // 更新尾节点 } this.#queSize++; } /* 队首入队操作 */ pushFirst(val) { const node = new ListNode(val); // 若链表为空,则令 front 和 rear 都指向 node if (this.#queSize === 0) { this.#front = node; this.#rear = node; } else { // 将 node 添加至链表头部 this.#front.prev = node; node.next = this.#front; this.#front = node; // 更新头节点 } this.#queSize++; } /* 队尾出队操作 */ popLast() { if (this.#queSize === 0) { return null; } const value = this.#rear.val; // 存储尾节点值 // 删除尾节点 let temp = this.#rear.prev; if (temp !== null) { temp.next = null; this.#rear.prev = null; } this.#rear = temp; // 更新尾节点 this.#queSize--; return value; } /* 队首出队操作 */ popFirst() { if (this.#queSize === 0) { return null; } const value = this.#front.val; // 存储尾节点值 // 删除头节点 let temp = this.#front.next; if (temp !== null) { temp.prev = null; this.#front.next = null; } this.#front = temp; // 更新头节点 this.#queSize--; return value; } /* 访问队尾元素 */ peekLast() { return this.#queSize === 0 ? null : this.#rear.val; } /* 访问队首元素 */ peekFirst() { return this.#queSize === 0 ? null : this.#front.val; } /* 获取双向队列的长度 */ size() { return this.#queSize; } /* 判断双向队列是否为空 */ isEmpty() { return this.#queSize === 0; } /* 打印双向队列 */ print() { const arr = []; let temp = this.#front; while (temp !== null) { arr.push(temp.val); temp = temp.next; } console.log('[' + arr.join(', ') + ']'); } } /* Driver Code */ /* 初始化双向队列 */ const linkedListDeque = new LinkedListDeque(); linkedListDeque.pushLast(3); linkedListDeque.pushLast(2); linkedListDeque.pushLast(5); console.log('双向队列 linkedListDeque = '); linkedListDeque.print(); /* 访问元素 */ const peekFirst = linkedListDeque.peekFirst(); console.log('队首元素 peekFirst = ' + peekFirst); const peekLast = linkedListDeque.peekLast(); console.log('队尾元素 peekLast = ' + peekLast); /* 元素入队 */ linkedListDeque.pushLast(4); console.log('元素 4 队尾入队后 linkedListDeque = '); linkedListDeque.print(); linkedListDeque.pushFirst(1); console.log('元素 1 队首入队后 linkedListDeque = '); linkedListDeque.print(); /* 元素出队 */ const popLast = linkedListDeque.popLast(); console.log('队尾出队元素 = ' + popLast + ',队尾出队后 linkedListDeque = '); linkedListDeque.print(); const popFirst = linkedListDeque.popFirst(); console.log('队首出队元素 = ' + popFirst + ',队首出队后 linkedListDeque = '); linkedListDeque.print(); /* 获取双向队列的长度 */ const size = linkedListDeque.size(); console.log('双向队列长度 size = ' + size); /* 判断双向队列是否为空 */ const isEmpty = linkedListDeque.isEmpty(); console.log('双向队列是否为空 = ' + isEmpty); ================================================ FILE: codes/javascript/chapter_stack_and_queue/linkedlist_queue.js ================================================ /** * File: linkedlist_queue.js * Created Time: 2022-12-20 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ const { ListNode } = require('../modules/ListNode'); /* 基于链表实现的队列 */ class LinkedListQueue { #front; // 头节点 #front #rear; // 尾节点 #rear #queSize = 0; constructor() { this.#front = null; this.#rear = null; } /* 获取队列的长度 */ get size() { return this.#queSize; } /* 判断队列是否为空 */ isEmpty() { return this.size === 0; } /* 入队 */ push(num) { // 在尾节点后添加 num const node = new ListNode(num); // 如果队列为空,则令头、尾节点都指向该节点 if (!this.#front) { this.#front = node; this.#rear = node; // 如果队列不为空,则将该节点添加到尾节点后 } else { this.#rear.next = node; this.#rear = node; } this.#queSize++; } /* 出队 */ pop() { const num = this.peek(); // 删除头节点 this.#front = this.#front.next; this.#queSize--; return num; } /* 访问队首元素 */ peek() { if (this.size === 0) throw new Error('队列为空'); return this.#front.val; } /* 将链表转化为 Array 并返回 */ toArray() { let node = this.#front; const res = new Array(this.size); for (let i = 0; i < res.length; i++) { res[i] = node.val; node = node.next; } return res; } } /* Driver Code */ /* 初始化队列 */ const queue = new LinkedListQueue(); /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('队列 queue = ' + queue.toArray()); /* 访问队首元素 */ const peek = queue.peek(); console.log('队首元素 peek = ' + peek); /* 元素出队 */ const pop = queue.pop(); console.log('出队元素 pop = ' + pop + ',出队后 queue = ' + queue.toArray()); /* 获取队列的长度 */ const size = queue.size; console.log('队列长度 size = ' + size); /* 判断队列是否为空 */ const isEmpty = queue.isEmpty(); console.log('队列是否为空 = ' + isEmpty); ================================================ FILE: codes/javascript/chapter_stack_and_queue/linkedlist_stack.js ================================================ /** * File: linkedlist_stack.js * Created Time: 2022-12-22 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ const { ListNode } = require('../modules/ListNode'); /* 基于链表实现的栈 */ class LinkedListStack { #stackPeek; // 将头节点作为栈顶 #stkSize = 0; // 栈的长度 constructor() { this.#stackPeek = null; } /* 获取栈的长度 */ get size() { return this.#stkSize; } /* 判断栈是否为空 */ isEmpty() { return this.size === 0; } /* 入栈 */ push(num) { const node = new ListNode(num); node.next = this.#stackPeek; this.#stackPeek = node; this.#stkSize++; } /* 出栈 */ pop() { const num = this.peek(); this.#stackPeek = this.#stackPeek.next; this.#stkSize--; return num; } /* 访问栈顶元素 */ peek() { if (!this.#stackPeek) throw new Error('栈为空'); return this.#stackPeek.val; } /* 将链表转化为 Array 并返回 */ toArray() { let node = this.#stackPeek; const res = new Array(this.size); for (let i = res.length - 1; i >= 0; i--) { res[i] = node.val; node = node.next; } return res; } } /* Driver Code */ /* 初始化栈 */ const stack = new LinkedListStack(); /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('栈 stack = ' + stack.toArray()); /* 访问栈顶元素 */ const peek = stack.peek(); console.log('栈顶元素 peek = ' + peek); /* 元素出栈 */ const pop = stack.pop(); console.log('出栈元素 pop = ' + pop + ',出栈后 stack = ' + stack.toArray()); /* 获取栈的长度 */ const size = stack.size; console.log('栈的长度 size = ' + size); /* 判断是否为空 */ const isEmpty = stack.isEmpty(); console.log('栈是否为空 = ' + isEmpty); ================================================ FILE: codes/javascript/chapter_stack_and_queue/queue.js ================================================ /** * File: queue.js * Created Time: 2022-12-05 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* 初始化队列 */ // JavaScript 没有内置的队列,可以把 Array 当作队列来使用 const queue = []; /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('队列 queue =', queue); /* 访问队首元素 */ const peek = queue[0]; console.log('队首元素 peek =', peek); /* 元素出队 */ // 底层是数组,因此 shift() 方法的时间复杂度为 O(n) const pop = queue.shift(); console.log('出队元素 pop =', pop, ',出队后 queue = ', queue); /* 获取队列的长度 */ const size = queue.length; console.log('队列长度 size =', size); /* 判断队列是否为空 */ const isEmpty = queue.length === 0; console.log('队列是否为空 = ', isEmpty); ================================================ FILE: codes/javascript/chapter_stack_and_queue/stack.js ================================================ /** * File: stack.js * Created Time: 2022-12-04 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* 初始化栈 */ // JavaScript 没有内置的栈类,可以把 Array 当作栈来使用 const stack = []; /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('栈 stack =', stack); /* 访问栈顶元素 */ const peek = stack[stack.length - 1]; console.log('栈顶元素 peek =', peek); /* 元素出栈 */ const pop = stack.pop(); console.log('出栈元素 pop =', pop); console.log('出栈后 stack =', stack); /* 获取栈的长度 */ const size = stack.length; console.log('栈的长度 size =', size); /* 判断是否为空 */ const isEmpty = stack.length === 0; console.log('栈是否为空 =', isEmpty); ================================================ FILE: codes/javascript/chapter_tree/array_binary_tree.js ================================================ /** * File: array_binary_tree.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 数组表示下的二叉树类 */ class ArrayBinaryTree { #tree; /* 构造方法 */ constructor(arr) { this.#tree = arr; } /* 列表容量 */ size() { return this.#tree.length; } /* 获取索引为 i 节点的值 */ val(i) { // 若索引越界,则返回 null ,代表空位 if (i < 0 || i >= this.size()) return null; return this.#tree[i]; } /* 获取索引为 i 节点的左子节点的索引 */ left(i) { return 2 * i + 1; } /* 获取索引为 i 节点的右子节点的索引 */ right(i) { return 2 * i + 2; } /* 获取索引为 i 节点的父节点的索引 */ parent(i) { return Math.floor((i - 1) / 2); // 向下整除 } /* 层序遍历 */ levelOrder() { let res = []; // 直接遍历数组 for (let i = 0; i < this.size(); i++) { if (this.val(i) !== null) res.push(this.val(i)); } return res; } /* 深度优先遍历 */ #dfs(i, order, res) { // 若为空位,则返回 if (this.val(i) === null) return; // 前序遍历 if (order === 'pre') res.push(this.val(i)); this.#dfs(this.left(i), order, res); // 中序遍历 if (order === 'in') res.push(this.val(i)); this.#dfs(this.right(i), order, res); // 后序遍历 if (order === 'post') res.push(this.val(i)); } /* 前序遍历 */ preOrder() { const res = []; this.#dfs(0, 'pre', res); return res; } /* 中序遍历 */ inOrder() { const res = []; this.#dfs(0, 'in', res); return res; } /* 后序遍历 */ postOrder() { const res = []; this.#dfs(0, 'post', res); return res; } } /* Driver Code */ // 初始化二叉树 // 这里借助了一个从数组直接生成二叉树的函数 const arr = Array.of( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ); const root = arrToTree(arr); console.log('\n初始化二叉树\n'); console.log('二叉树的数组表示:'); console.log(arr); console.log('二叉树的链表表示:'); printTree(root); // 数组表示下的二叉树类 const abt = new ArrayBinaryTree(arr); // 访问节点 const i = 1; const l = abt.left(i); const r = abt.right(i); const p = abt.parent(i); console.log('\n当前节点的索引为 ' + i + ' ,值为 ' + abt.val(i)); console.log( '其左子节点的索引为 ' + l + ' ,值为 ' + (l === null ? 'null' : abt.val(l)) ); console.log( '其右子节点的索引为 ' + r + ' ,值为 ' + (r === null ? 'null' : abt.val(r)) ); console.log( '其父节点的索引为 ' + p + ' ,值为 ' + (p === null ? 'null' : abt.val(p)) ); // 遍历树 let res = abt.levelOrder(); console.log('\n层序遍历为:' + res); res = abt.preOrder(); console.log('前序遍历为:' + res); res = abt.inOrder(); console.log('中序遍历为:' + res); res = abt.postOrder(); console.log('后序遍历为:' + res); ================================================ FILE: codes/javascript/chapter_tree/avl_tree.js ================================================ /** * File: avl_tree.js * Created Time: 2023-02-05 * Author: what-is-me (whatisme@outlook.jp) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* AVL 树*/ class AVLTree { /* 构造方法 */ constructor() { this.root = null; //根节点 } /* 获取节点高度 */ height(node) { // 空节点高度为 -1 ,叶节点高度为 0 return node === null ? -1 : node.height; } /* 更新节点高度 */ #updateHeight(node) { // 节点高度等于最高子树高度 + 1 node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; } /* 获取平衡因子 */ balanceFactor(node) { // 空节点平衡因子为 0 if (node === null) return 0; // 节点平衡因子 = 左子树高度 - 右子树高度 return this.height(node.left) - this.height(node.right); } /* 右旋操作 */ #rightRotate(node) { const child = node.left; const grandChild = child.right; // 以 child 为原点,将 node 向右旋转 child.right = node; node.left = grandChild; // 更新节点高度 this.#updateHeight(node); this.#updateHeight(child); // 返回旋转后子树的根节点 return child; } /* 左旋操作 */ #leftRotate(node) { const child = node.right; const grandChild = child.left; // 以 child 为原点,将 node 向左旋转 child.left = node; node.right = grandChild; // 更新节点高度 this.#updateHeight(node); this.#updateHeight(child); // 返回旋转后子树的根节点 return child; } /* 执行旋转操作,使该子树重新恢复平衡 */ #rotate(node) { // 获取节点 node 的平衡因子 const balanceFactor = this.balanceFactor(node); // 左偏树 if (balanceFactor > 1) { if (this.balanceFactor(node.left) >= 0) { // 右旋 return this.#rightRotate(node); } else { // 先左旋后右旋 node.left = this.#leftRotate(node.left); return this.#rightRotate(node); } } // 右偏树 if (balanceFactor < -1) { if (this.balanceFactor(node.right) <= 0) { // 左旋 return this.#leftRotate(node); } else { // 先右旋后左旋 node.right = this.#rightRotate(node.right); return this.#leftRotate(node); } } // 平衡树,无须旋转,直接返回 return node; } /* 插入节点 */ insert(val) { this.root = this.#insertHelper(this.root, val); } /* 递归插入节点(辅助方法) */ #insertHelper(node, val) { if (node === null) return new TreeNode(val); /* 1. 查找插入位置并插入节点 */ if (val < node.val) node.left = this.#insertHelper(node.left, val); else if (val > node.val) node.right = this.#insertHelper(node.right, val); else return node; // 重复节点不插入,直接返回 this.#updateHeight(node); // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = this.#rotate(node); // 返回子树的根节点 return node; } /* 删除节点 */ remove(val) { this.root = this.#removeHelper(this.root, val); } /* 递归删除节点(辅助方法) */ #removeHelper(node, val) { if (node === null) return null; /* 1. 查找节点并删除 */ if (val < node.val) node.left = this.#removeHelper(node.left, val); else if (val > node.val) node.right = this.#removeHelper(node.right, val); else { if (node.left === null || node.right === null) { const child = node.left !== null ? node.left : node.right; // 子节点数量 = 0 ,直接删除 node 并返回 if (child === null) return null; // 子节点数量 = 1 ,直接删除 node else node = child; } else { // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 let temp = node.right; while (temp.left !== null) { temp = temp.left; } node.right = this.#removeHelper(node.right, temp.val); node.val = temp.val; } } this.#updateHeight(node); // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = this.#rotate(node); // 返回子树的根节点 return node; } /* 查找节点 */ search(val) { let cur = this.root; // 循环查找,越过叶节点后跳出 while (cur !== null) { // 目标节点在 cur 的右子树中 if (cur.val < val) cur = cur.right; // 目标节点在 cur 的左子树中 else if (cur.val > val) cur = cur.left; // 找到目标节点,跳出循环 else break; } // 返回目标节点 return cur; } } function testInsert(tree, val) { tree.insert(val); console.log('\n插入节点 ' + val + ' 后,AVL 树为'); printTree(tree.root); } function testRemove(tree, val) { tree.remove(val); console.log('\n删除节点 ' + val + ' 后,AVL 树为'); printTree(tree.root); } /* Driver Code */ /* 初始化空 AVL 树 */ const avlTree = new AVLTree(); /* 插入节点 */ // 请关注插入节点后,AVL 树是如何保持平衡的 testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); testInsert(avlTree, 4); testInsert(avlTree, 5); testInsert(avlTree, 8); testInsert(avlTree, 7); testInsert(avlTree, 9); testInsert(avlTree, 10); testInsert(avlTree, 6); /* 插入重复节点 */ testInsert(avlTree, 7); /* 删除节点 */ // 请关注删除节点后,AVL 树是如何保持平衡的 testRemove(avlTree, 8); // 删除度为 0 的节点 testRemove(avlTree, 5); // 删除度为 1 的节点 testRemove(avlTree, 4); // 删除度为 2 的节点 /* 查询节点 */ const node = avlTree.search(7); console.log('\n查找到的节点对象为', node, ',节点值 = ' + node.val); ================================================ FILE: codes/javascript/chapter_tree/binary_search_tree.js ================================================ /** * File: binary_search_tree.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 二叉搜索树 */ class BinarySearchTree { /* 构造方法 */ constructor() { // 初始化空树 this.root = null; } /* 获取二叉树根节点 */ getRoot() { return this.root; } /* 查找节点 */ search(num) { let cur = this.root; // 循环查找,越过叶节点后跳出 while (cur !== null) { // 目标节点在 cur 的右子树中 if (cur.val < num) cur = cur.right; // 目标节点在 cur 的左子树中 else if (cur.val > num) cur = cur.left; // 找到目标节点,跳出循环 else break; } // 返回目标节点 return cur; } /* 插入节点 */ insert(num) { // 若树为空,则初始化根节点 if (this.root === null) { this.root = new TreeNode(num); return; } let cur = this.root, pre = null; // 循环查找,越过叶节点后跳出 while (cur !== null) { // 找到重复节点,直接返回 if (cur.val === num) return; pre = cur; // 插入位置在 cur 的右子树中 if (cur.val < num) cur = cur.right; // 插入位置在 cur 的左子树中 else cur = cur.left; } // 插入节点 const node = new TreeNode(num); if (pre.val < num) pre.right = node; else pre.left = node; } /* 删除节点 */ remove(num) { // 若树为空,直接提前返回 if (this.root === null) return; let cur = this.root, pre = null; // 循环查找,越过叶节点后跳出 while (cur !== null) { // 找到待删除节点,跳出循环 if (cur.val === num) break; pre = cur; // 待删除节点在 cur 的右子树中 if (cur.val < num) cur = cur.right; // 待删除节点在 cur 的左子树中 else cur = cur.left; } // 若无待删除节点,则直接返回 if (cur === null) return; // 子节点数量 = 0 or 1 if (cur.left === null || cur.right === null) { // 当子节点数量 = 0 / 1 时, child = null / 该子节点 const child = cur.left !== null ? cur.left : cur.right; // 删除节点 cur if (cur !== this.root) { if (pre.left === cur) pre.left = child; else pre.right = child; } else { // 若删除节点为根节点,则重新指定根节点 this.root = child; } } // 子节点数量 = 2 else { // 获取中序遍历中 cur 的下一个节点 let tmp = cur.right; while (tmp.left !== null) { tmp = tmp.left; } // 递归删除节点 tmp this.remove(tmp.val); // 用 tmp 覆盖 cur cur.val = tmp.val; } } } /* Driver Code */ /* 初始化二叉搜索树 */ const bst = new BinarySearchTree(); // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (const num of nums) { bst.insert(num); } console.log('\n初始化的二叉树为\n'); printTree(bst.getRoot()); /* 查找节点 */ const node = bst.search(7); console.log('\n查找到的节点对象为 ' + node + ',节点值 = ' + node.val); /* 插入节点 */ bst.insert(16); console.log('\n插入节点 16 后,二叉树为\n'); printTree(bst.getRoot()); /* 删除节点 */ bst.remove(1); console.log('\n删除节点 1 后,二叉树为\n'); printTree(bst.getRoot()); bst.remove(2); console.log('\n删除节点 2 后,二叉树为\n'); printTree(bst.getRoot()); bst.remove(4); console.log('\n删除节点 4 后,二叉树为\n'); printTree(bst.getRoot()); ================================================ FILE: codes/javascript/chapter_tree/binary_tree.js ================================================ /** * File: binary_tree.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 初始化二叉树 */ // 初始化节点 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; console.log('\n初始化二叉树\n'); printTree(n1); /* 插入与删除节点 */ const P = new TreeNode(0); // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; console.log('\n插入节点 P 后\n'); printTree(n1); // 删除节点 P n1.left = n2; console.log('\n删除节点 P 后\n'); printTree(n1); ================================================ FILE: codes/javascript/chapter_tree/binary_tree_bfs.js ================================================ /** * File: binary_tree_bfs.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 层序遍历 */ function levelOrder(root) { // 初始化队列,加入根节点 const queue = [root]; // 初始化一个列表,用于保存遍历序列 const list = []; while (queue.length) { let node = queue.shift(); // 队列出队 list.push(node.val); // 保存节点值 if (node.left) queue.push(node.left); // 左子节点入队 if (node.right) queue.push(node.right); // 右子节点入队 } return list; } /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树\n'); printTree(root); /* 层序遍历 */ const list = levelOrder(root); console.log('\n层序遍历的节点打印序列 = ' + list); ================================================ FILE: codes/javascript/chapter_tree/binary_tree_dfs.js ================================================ /** * File: binary_tree_dfs.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); // 初始化列表,用于存储遍历序列 const list = []; /* 前序遍历 */ function preOrder(root) { if (root === null) return; // 访问优先级:根节点 -> 左子树 -> 右子树 list.push(root.val); preOrder(root.left); preOrder(root.right); } /* 中序遍历 */ function inOrder(root) { if (root === null) return; // 访问优先级:左子树 -> 根节点 -> 右子树 inOrder(root.left); list.push(root.val); inOrder(root.right); } /* 后序遍历 */ function postOrder(root) { if (root === null) return; // 访问优先级:左子树 -> 右子树 -> 根节点 postOrder(root.left); postOrder(root.right); list.push(root.val); } /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树\n'); printTree(root); /* 前序遍历 */ list.length = 0; preOrder(root); console.log('\n前序遍历的节点打印序列 = ' + list); /* 中序遍历 */ list.length = 0; inOrder(root); console.log('\n中序遍历的节点打印序列 = ' + list); /* 后序遍历 */ list.length = 0; postOrder(root); console.log('\n后序遍历的节点打印序列 = ' + list); ================================================ FILE: codes/javascript/modules/ListNode.js ================================================ /** * File: ListNode.js * Created Time: 2022-12-12 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 链表节点 */ class ListNode { val; // 节点值 next; // 指向下一节点的引用(指针) constructor(val, next) { this.val = val === undefined ? 0 : val; this.next = next === undefined ? null : next; } } /* 将列表反序列化为链表 */ function arrToLinkedList(arr) { const dum = new ListNode(0); let head = dum; for (const val of arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } module.exports = { ListNode, arrToLinkedList, }; ================================================ FILE: codes/javascript/modules/PrintUtil.js ================================================ /** * File: PrintUtil.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('./TreeNode'); /* 打印链表 */ function printLinkedList(head) { let list = []; while (head !== null) { list.push(head.val.toString()); head = head.next; } console.log(list.join(' -> ')); } function Trunk(prev, str) { this.prev = prev; this.str = str; } /** * 打印二叉树 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ function printTree(root) { printTree(root, null, false); } /* 打印二叉树 */ function printTree(root, prev, isRight) { if (root === null) { return; } let prev_str = ' '; let trunk = new Trunk(prev, prev_str); printTree(root.right, trunk, true); if (!prev) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); console.log(' ' + root.val); if (prev) { prev.str = prev_str; } trunk.str = ' |'; printTree(root.left, trunk, false); } function showTrunks(p) { if (!p) { return; } showTrunks(p.prev); process.stdout.write(p.str); } /* 打印堆 */ function printHeap(arr) { console.log('堆的数组表示:'); console.log(arr); console.log('堆的树状表示:'); printTree(arrToTree(arr)); } module.exports = { printLinkedList, printTree, printHeap, }; ================================================ FILE: codes/javascript/modules/TreeNode.js ================================================ /** * File: TreeNode.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 二叉树节点 */ class TreeNode { val; // 节点值 left; // 左子节点指针 right; // 右子节点指针 height; //节点高度 constructor(val, left, right, height) { this.val = val === undefined ? 0 : val; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; this.height = height === undefined ? 0 : height; } } /* 将数组反序列化为二叉树 */ function arrToTree(arr, i = 0) { if (i < 0 || i >= arr.length || arr[i] === null) { return null; } let root = new TreeNode(arr[i]); root.left = arrToTree(arr, 2 * i + 1); root.right = arrToTree(arr, 2 * i + 2); return root; } module.exports = { TreeNode, arrToTree, }; ================================================ FILE: codes/javascript/modules/Vertex.js ================================================ /** * File: Vertex.js * Created Time: 2023-02-15 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 顶点类 */ class Vertex { val; constructor(val) { this.val = val; } /* 输入值列表 vals ,返回顶点列表 vets */ static valsToVets(vals) { const vets = []; for (let i = 0; i < vals.length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* 输入顶点列表 vets ,返回值列表 vals */ static vetsToVals(vets) { const vals = []; for (const vet of vets) { vals.push(vet.val); } return vals; } } module.exports = { Vertex, }; ================================================ FILE: codes/javascript/test_all.js ================================================ import { bold, brightRed } from 'jsr:@std/fmt/colors'; import { expandGlob } from 'jsr:@std/fs'; import { relative, resolve } from 'jsr:@std/path'; /** * @typedef {import('jsr:@std/fs').WalkEntry} WalkEntry * @type {WalkEntry[]} */ const entries = []; for await (const entry of expandGlob( resolve(import.meta.dirname, './chapter_*/*.js') )) { entries.push(entry); } /** @type {{ status: Promise; stderr: ReadableStream; }[]} */ const processes = []; for (const file of entries) { const execute = new Deno.Command('node', { args: [relative(import.meta.dirname, file.path)], cwd: import.meta.dirname, stdin: 'piped', stdout: 'piped', stderr: 'piped', }); const process = execute.spawn(); processes.push({ status: process.status, stderr: process.stderr }); } const results = await Promise.all( processes.map(async (item) => { const status = await item.status; return { status, stderr: item.stderr }; }) ); /** @type {ReadableStream[]} */ const errors = []; for (const result of results) { if (!result.status.success) { errors.push(result.stderr); } } console.log(`Tested ${entries.length} files`); console.log(`Found exception in ${errors.length} files`); if (errors.length) { console.log(); for (const error of errors) { const reader = error.getReader(); const { value } = await reader.read(); const decoder = new TextDecoder(); console.log(`${bold(brightRed('error'))}: ${decoder.decode(value)}`); } throw new Error('Test failed'); } ================================================ FILE: codes/kotlin/chapter_array_and_linkedlist/array.kt ================================================ /** * File: array.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_array_and_linkedlist import java.util.concurrent.ThreadLocalRandom /* 随机访问元素 */ fun randomAccess(nums: IntArray): Int { // 在区间 [0, nums.size) 中随机抽取一个数字 val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size) // 获取并返回随机元素 val randomNum = nums[randomIndex] return randomNum } /* 扩展数组长度 */ fun extend(nums: IntArray, enlarge: Int): IntArray { // 初始化一个扩展长度后的数组 val res = IntArray(nums.size + enlarge) // 将原数组中的所有元素复制到新数组 for (i in nums.indices) { res[i] = nums[i] } // 返回扩展后的新数组 return res } /* 在数组的索引 index 处插入元素 num */ fun insert(nums: IntArray, num: Int, index: Int) { // 把索引 index 以及之后的所有元素向后移动一位 for (i in nums.size - 1 downTo index + 1) { nums[i] = nums[i - 1] } // 将 num 赋给 index 处的元素 nums[index] = num } /* 删除索引 index 处的元素 */ fun remove(nums: IntArray, index: Int) { // 把索引 index 之后的所有元素向前移动一位 for (i in index.. P -> n1 val p = n0.next val n1 = p?.next n0.next = n1 } /* 访问链表中索引为 index 的节点 */ fun access(head: ListNode?, index: Int): ListNode? { var h = head for (i in 0..= size) throw IndexOutOfBoundsException("索引越界") return arr[index] } /* 更新元素 */ fun set(index: Int, num: Int) { if (index < 0 || index >= size) throw IndexOutOfBoundsException("索引越界") arr[index] = num } /* 在尾部添加元素 */ fun add(num: Int) { // 元素数量超出容量时,触发扩容机制 if (size == capacity()) extendCapacity() arr[size] = num // 更新元素数量 size++ } /* 在中间插入元素 */ fun insert(index: Int, num: Int) { if (index < 0 || index >= size) throw IndexOutOfBoundsException("索引越界") // 元素数量超出容量时,触发扩容机制 if (size == capacity()) extendCapacity() // 将索引 index 以及之后的元素都向后移动一位 for (j in size - 1 downTo index) arr[j + 1] = arr[j] arr[index] = num // 更新元素数量 size++ } /* 删除元素 */ fun remove(index: Int): Int { if (index < 0 || index >= size) throw IndexOutOfBoundsException("索引越界") val num = arr[index] // 将将索引 index 之后的元素都向前移动一位 for (j in index..>, res: MutableList>?>, cols: BooleanArray, diags1: BooleanArray, diags2: BooleanArray ) { // 当放置完所有行时,记录解 if (row == n) { val copyState = mutableListOf>() for (sRow in state) { copyState.add(sRow.toMutableList()) } res.add(copyState) return } // 遍历所有列 for (col in 0..>?> { // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 val state = mutableListOf>() for (i in 0..() for (j in 0..>?>() backtrack(0, n, state, res, cols, diags1, diags2) return res } /* Driver Code */ fun main() { val n = 4 val res = nQueens(n) println("输入棋盘长宽为 $n") println("皇后放置方案共有 ${res.size} 种") for (state in res) { println("--------------------") for (row in state!!) { println(row) } } } ================================================ FILE: codes/kotlin/chapter_backtracking/permutations_i.kt ================================================ /** * File: permutations_i.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.permutations_i /* 回溯算法:全排列 I */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // 当状态长度等于元素数量时,记录解 if (state.size == choices.size) { res.add(state.toMutableList()) return } // 遍历所有选择 for (i in choices.indices) { val choice = choices[i] // 剪枝:不允许重复选择元素 if (!selected[i]) { // 尝试:做出选择,更新状态 selected[i] = true state.add(choice) // 进行下一轮选择 backtrack(state, choices, selected, res) // 回退:撤销选择,恢复到之前的状态 selected[i] = false state.removeAt(state.size - 1) } } } /* 全排列 I */ fun permutationsI(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(1, 2, 3) val res = permutationsI(nums) println("输入数组 nums = ${nums.contentToString()}") println("所有排列 res = $res") } ================================================ FILE: codes/kotlin/chapter_backtracking/permutations_ii.kt ================================================ /** * File: permutations_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.permutations_ii /* 回溯算法:全排列 II */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // 当状态长度等于元素数量时,记录解 if (state.size == choices.size) { res.add(state.toMutableList()) return } // 遍历所有选择 val duplicated = HashSet() for (i in choices.indices) { val choice = choices[i] // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 if (!selected[i] && !duplicated.contains(choice)) { // 尝试:做出选择,更新状态 duplicated.add(choice) // 记录选择过的元素值 selected[i] = true state.add(choice) // 进行下一轮选择 backtrack(state, choices, selected, res) // 回退:撤销选择,恢复到之前的状态 selected[i] = false state.removeAt(state.size - 1) } } } /* 全排列 II */ fun permutationsII(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(1, 2, 2) val res = permutationsII(nums) println("输入数组 nums = ${nums.contentToString()}") println("所有排列 res = $res") } ================================================ FILE: codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt ================================================ /** * File: preorder_traversal_i_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_i_compact import utils.TreeNode import utils.printTree var res: MutableList? = null /* 前序遍历:例题一 */ fun preOrder(root: TreeNode?) { if (root == null) { return } if (root._val == 7) { // 记录解 res!!.add(root) } preOrder(root.left) preOrder(root.right) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\n初始化二叉树") printTree(root) // 前序遍历 res = mutableListOf() preOrder(root) println("\n输出所有值为 7 的节点") val vals = mutableListOf() for (node in res!!) { vals.add(node._val) } println(vals) } ================================================ FILE: codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt ================================================ /** * File: preorder_traversal_ii_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_ii_compact import utils.TreeNode import utils.printTree var path: MutableList? = null var res: MutableList>? = null /* 前序遍历:例题二 */ fun preOrder(root: TreeNode?) { if (root == null) { return } // 尝试 path!!.add(root) if (root._val == 7) { // 记录解 res!!.add(path!!.toMutableList()) } preOrder(root.left) preOrder(root.right) // 回退 path!!.removeAt(path!!.size - 1) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\n初始化二叉树") printTree(root) // 前序遍历 path = mutableListOf() res = mutableListOf() preOrder(root) println("\n输出所有根节点到节点 7 的路径") for (path in res!!) { val _vals = mutableListOf() for (node in path) { _vals.add(node._val) } println(_vals) } } ================================================ FILE: codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt ================================================ /** * File: preorder_traversal_iii_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_iii_compact import utils.TreeNode import utils.printTree var path: MutableList? = null var res: MutableList>? = null /* 前序遍历:例题三 */ fun preOrder(root: TreeNode?) { // 剪枝 if (root == null || root._val == 3) { return } // 尝试 path!!.add(root) if (root._val == 7) { // 记录解 res!!.add(path!!.toMutableList()) } preOrder(root.left) preOrder(root.right) // 回退 path!!.removeAt(path!!.size - 1) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\n初始化二叉树") printTree(root) // 前序遍历 path = mutableListOf() res = mutableListOf() preOrder(root) println("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") for (path in res!!) { val _vals = mutableListOf() for (node in path) { _vals.add(node._val) } println(_vals) } } ================================================ FILE: codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt ================================================ /** * File: preorder_traversal_iii_template.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_iii_template import utils.TreeNode import utils.printTree /* 判断当前状态是否为解 */ fun isSolution(state: MutableList): Boolean { return state.isNotEmpty() && state[state.size - 1]?._val == 7 } /* 记录解 */ fun recordSolution(state: MutableList?, res: MutableList?>) { res.add(state!!.toMutableList()) } /* 判断在当前状态下,该选择是否合法 */ fun isValid(state: MutableList?, choice: TreeNode?): Boolean { return choice != null && choice._val != 3 } /* 更新状态 */ fun makeChoice(state: MutableList, choice: TreeNode?) { state.add(choice) } /* 恢复状态 */ fun undoChoice(state: MutableList, choice: TreeNode?) { state.removeLast() } /* 回溯算法:例题三 */ fun backtrack( state: MutableList, choices: MutableList, res: MutableList?> ) { // 检查是否为解 if (isSolution(state)) { // 记录解 recordSolution(state, res) } // 遍历所有选择 for (choice in choices) { // 剪枝:检查选择是否合法 if (isValid(state, choice)) { // 尝试:做出选择,更新状态 makeChoice(state, choice) // 进行下一轮选择 backtrack(state, mutableListOf(choice!!.left, choice.right), res) // 回退:撤销选择,恢复到之前的状态 undoChoice(state, choice) } } } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\n初始化二叉树") printTree(root) // 回溯算法 val res = mutableListOf?>() backtrack(mutableListOf(), mutableListOf(root), res) println("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点") for (path in res) { val vals = mutableListOf() for (node in path!!) { if (node != null) { vals.add(node._val) } } println(vals) } } ================================================ FILE: codes/kotlin/chapter_backtracking/subset_sum_i.kt ================================================ /** * File: subset_sum_i.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_i /* 回溯算法:子集和 I */ fun backtrack( state: MutableList, target: Int, choices: IntArray, start: Int, res: MutableList?> ) { // 子集和等于 target 时,记录解 if (target == 0) { res.add(state.toMutableList()) return } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 for (i in start..?> { val state = mutableListOf() // 状态(子集) nums.sort() // 对 nums 进行排序 val start = 0 // 遍历起始点 val res = mutableListOf?>() // 结果列表(子集列表) backtrack(state, target, nums, start, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(3, 4, 5) val target = 9 val res = subsetSumI(nums, target) println("输入数组 nums = ${nums.contentToString()}, target = $target") println("所有和等于 $target 的子集 res = $res") } ================================================ FILE: codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt ================================================ /** * File: subset_sum_i_native.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_i_naive /* 回溯算法:子集和 I */ fun backtrack( state: MutableList, target: Int, total: Int, choices: IntArray, res: MutableList?> ) { // 子集和等于 target 时,记录解 if (total == target) { res.add(state.toMutableList()) return } // 遍历所有选择 for (i in choices.indices) { // 剪枝:若子集和超过 target ,则跳过该选择 if (total + choices[i] > target) { continue } // 尝试:做出选择,更新元素和 total state.add(choices[i]) // 进行下一轮选择 backtrack(state, target, total + choices[i], choices, res) // 回退:撤销选择,恢复到之前的状态 state.removeAt(state.size - 1) } } /* 求解子集和 I(包含重复子集) */ fun subsetSumINaive(nums: IntArray, target: Int): MutableList?> { val state = mutableListOf() // 状态(子集) val total = 0 // 子集和 val res = mutableListOf?>() // 结果列表(子集列表) backtrack(state, target, total, nums, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(3, 4, 5) val target = 9 val res = subsetSumINaive(nums, target) println("输入数组 nums = ${nums.contentToString()}, target = $target") println("所有和等于 $target 的子集 res = $res") println("请注意,该方法输出的结果包含重复集合") } ================================================ FILE: codes/kotlin/chapter_backtracking/subset_sum_ii.kt ================================================ /** * File: subset_sum_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_ii /* 回溯算法:子集和 II */ fun backtrack( state: MutableList, target: Int, choices: IntArray, start: Int, res: MutableList?> ) { // 子集和等于 target 时,记录解 if (target == 0) { res.add(state.toMutableList()) return } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 // 剪枝三:从 start 开始遍历,避免重复选择同一元素 for (i in start.. start && choices[i] == choices[i - 1]) { continue } // 尝试:做出选择,更新 target, start state.add(choices[i]) // 进行下一轮选择 backtrack(state, target - choices[i], choices, i + 1, res) // 回退:撤销选择,恢复到之前的状态 state.removeAt(state.size - 1) } } /* 求解子集和 II */ fun subsetSumII(nums: IntArray, target: Int): MutableList?> { val state = mutableListOf() // 状态(子集) nums.sort() // 对 nums 进行排序 val start = 0 // 遍历起始点 val res = mutableListOf?>() // 结果列表(子集列表) backtrack(state, target, nums, start, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(4, 4, 5) val target = 9 val res = subsetSumII(nums, target) println("输入数组 nums = ${nums.contentToString()}, target = $target") println("所有和等于 $target 的子集 res = $res") } ================================================ FILE: codes/kotlin/chapter_computational_complexity/iteration.kt ================================================ /** * File: iteration.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.iteration /* for 循环 */ fun forLoop(n: Int): Int { var res = 0 // 循环求和 1, 2, ..., n-1, n for (i in 1..n) { res += i } return res } /* while 循环 */ fun whileLoop(n: Int): Int { var res = 0 var i = 1 // 初始化条件变量 // 循环求和 1, 2, ..., n-1, n while (i <= n) { res += i i++ // 更新条件变量 } return res } /* while 循环(两次更新) */ fun whileLoopII(n: Int): Int { var res = 0 var i = 1 // 初始化条件变量 // 循环求和 1, 4, 10, ... while (i <= n) { res += i // 更新条件变量 i++ i *= 2 } return res } /* 双层 for 循环 */ fun nestedForLoop(n: Int): String { val res = StringBuilder() // 循环 i = 1, 2, ..., n-1, n for (i in 1..n) { // 循环 j = 1, 2, ..., n-1, n for (j in 1..n) { res.append(" ($i, $j), ") } } return res.toString() } /* Driver Code */ fun main() { val n = 5 var res: Int res = forLoop(n) println("\nfor 循环的求和结果 res = $res") res = whileLoop(n) println("\nwhile 循环的求和结果 res = $res") res = whileLoopII(n) println("\nwhile 循环 (两次更新) 求和结果 res = $res") val resStr = nestedForLoop(n) println("\n双层 for 循环的遍历结果 $resStr") } ================================================ FILE: codes/kotlin/chapter_computational_complexity/recursion.kt ================================================ /** * File: recursion.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.recursion import java.util.* /* 递归 */ fun recur(n: Int): Int { // 终止条件 if (n == 1) return 1 // 递: 递归调用 val res = recur(n - 1) // 归: 返回结果 return n + res } /* 使用迭代模拟递归 */ fun forLoopRecur(n: Int): Int { // 使用一个显式的栈来模拟系统调用栈 val stack = Stack() var res = 0 // 递: 递归调用 for (i in n downTo 0) { // 通过“入栈操作”模拟“递” stack.push(i) } // 归: 返回结果 while (stack.isNotEmpty()) { // 通过“出栈操作”模拟“归” res += stack.pop() } // res = 1+2+3+...+n return res } /* 尾递归 */ tailrec fun tailRecur(n: Int, res: Int): Int { // 添加 tailrec 关键词,以开启尾递归优化 // 终止条件 if (n == 0) return res // 尾递归调用 return tailRecur(n - 1, res + n) } /* 斐波那契数列:递归 */ fun fib(n: Int): Int { // 终止条件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1 // 递归调用 f(n) = f(n-1) + f(n-2) val res = fib(n - 1) + fib(n - 2) // 返回结果 f(n) return res } /* Driver Code */ fun main() { val n = 5 var res: Int res = recur(n) println("\n递归函数的求和结果 res = $res") res = forLoopRecur(n) println("\n使用迭代模拟递归求和结果 res = $res") res = tailRecur(n, 0) println("\n尾递归函数的求和结果 res = $res") res = fib(n) println("\n斐波那契数列的第 $n 项为 $res") } ================================================ FILE: codes/kotlin/chapter_computational_complexity/space_complexity.kt ================================================ /** * File: space_complexity.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.space_complexity import utils.ListNode import utils.TreeNode import utils.printTree /* 函数 */ fun function(): Int { // 执行某些操作 return 0 } /* 常数阶 */ fun constant(n: Int) { // 常量、变量、对象占用 O(1) 空间 val a = 0 var b = 0 val nums = Array(10000) { 0 } val node = ListNode(0) // 循环中的变量占用 O(1) 空间 for (i in 0..() for (i in 0..() for (i in 0..?>(n) // 二维列表占用 O(n^2) 空间 val numList = mutableListOf>() for (i in 0..() for (j in 0.. nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp count += 3 // 元素交换包含 3 个单元操作 } } } return count } /* 指数阶(循环实现) */ fun exponential(n: Int): Int { var count = 0 var base = 1 // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for (i in 0.. 1) { n1 /= 2 count++ } return count } /* 对数阶(递归实现) */ fun logRecur(n: Int): Int { if (n <= 1) return 0 return logRecur(n / 2) + 1 } /* 线性对数阶 */ fun linearLogRecur(n: Int): Int { if (n <= 1) return 1 var count = linearLogRecur(n / 2) + linearLogRecur(n / 2) for (i in 0.. { val nums = IntArray(n) // 生成数组 nums = { 1, 2, 3, ..., n } for (i in 0..(n) for (i in 0..): Int { for (i in nums.indices) { // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if (nums[i] == 1) return i } return -1 } /* Driver Code */ fun main() { for (i in 0..9) { val n = 100 val nums = randomNumbers(n) val index = findOne(nums) println("\n数组 [ 1, 2, ..., n ] 被打乱后 = ${nums.contentToString()}") println("数字 1 的索引为 $index") } } ================================================ FILE: codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt ================================================ /** * File: binary_search_recur.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.binary_search_recur /* 二分查找:问题 f(i, j) */ fun dfs( nums: IntArray, target: Int, i: Int, j: Int ): Int { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1 } // 计算中点索引 m val m = (i + j) / 2 return if (nums[m] < target) { // 递归子问题 f(m+1, j) dfs(nums, target, m + 1, j) } else if (nums[m] > target) { // 递归子问题 f(i, m-1) dfs(nums, target, i, m - 1) } else { // 找到目标元素,返回其索引 m } } /* 二分查找 */ fun binarySearch(nums: IntArray, target: Int): Int { val n = nums.size // 求解问题 f(0, n-1) return dfs(nums, target, 0, n - 1) } /* Driver Code */ fun main() { val target = 6 val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) // 二分查找(双闭区间) val index = binarySearch(nums, target) println("目标元素 6 的索引 = $index") } ================================================ FILE: codes/kotlin/chapter_divide_and_conquer/build_tree.kt ================================================ /** * File: build_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.build_tree import utils.TreeNode import utils.printTree /* 构建二叉树:分治 */ fun dfs( preorder: IntArray, inorderMap: Map, i: Int, l: Int, r: Int ): TreeNode? { // 子树区间为空时终止 if (r - l < 0) return null // 初始化根节点 val root = TreeNode(preorder[i]) // 查询 m ,从而划分左右子树 val m = inorderMap[preorder[i]]!! // 子问题:构建左子树 root.left = dfs(preorder, inorderMap, i + 1, l, m - 1) // 子问题:构建右子树 root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r) // 返回根节点 return root } /* 构建二叉树 */ fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? { // 初始化哈希表,存储 inorder 元素到索引的映射 val inorderMap = HashMap() for (i in inorder.indices) { inorderMap[inorder[i]] = i } val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1) return root } /* Driver Code */ fun main() { val preorder = intArrayOf(3, 9, 2, 1, 7) val inorder = intArrayOf(9, 3, 1, 2, 7) println("前序遍历 = ${preorder.contentToString()}") println("中序遍历 = ${inorder.contentToString()}") val root = buildTree(preorder, inorder) println("构建的二叉树为:") printTree(root) } ================================================ FILE: codes/kotlin/chapter_divide_and_conquer/hanota.kt ================================================ /** * File: hanota.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.hanota /* 移动一个圆盘 */ fun move(src: MutableList, tar: MutableList) { // 从 src 顶部拿出一个圆盘 val pan = src.removeAt(src.size - 1) // 将圆盘放入 tar 顶部 tar.add(pan) } /* 求解汉诺塔问题 f(i) */ fun dfs(i: Int, src: MutableList, buf: MutableList, tar: MutableList) { // 若 src 只剩下一个圆盘,则直接将其移到 tar if (i == 1) { move(src, tar) return } // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf dfs(i - 1, src, tar, buf) // 子问题 f(1) :将 src 剩余一个圆盘移到 tar move(src, tar) // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar dfs(i - 1, buf, src, tar) } /* 求解汉诺塔问题 */ fun solveHanota(A: MutableList, B: MutableList, C: MutableList) { val n = A.size // 将 A 顶部 n 个圆盘借助 B 移到 C dfs(n, A, B, C) } /* Driver Code */ fun main() { // 列表尾部是柱子顶部 val A = mutableListOf(5, 4, 3, 2, 1) val B = mutableListOf() val C = mutableListOf() println("初始状态下:") println("A = $A") println("B = $B") println("C = $C") solveHanota(A, B, C) println("圆盘移动完成后:") println("A = $A") println("B = $B") println("C = $C") } ================================================ FILE: codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt ================================================ /** * File: climbing_stairs_backtrack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 回溯 */ fun backtrack( choices: MutableList, state: Int, n: Int, res: MutableList ) { // 当爬到第 n 阶时,方案数量加 1 if (state == n) res[0] = res[0] + 1 // 遍历所有选择 for (choice in choices) { // 剪枝:不允许越过第 n 阶 if (state + choice > n) continue // 尝试:做出选择,更新状态 backtrack(choices, state + choice, n, res) // 回退 } } /* 爬楼梯:回溯 */ fun climbingStairsBacktrack(n: Int): Int { val choices = mutableListOf(1, 2) // 可选择向上爬 1 阶或 2 阶 val state = 0 // 从第 0 阶开始爬 val res = mutableListOf() res.add(0) // 使用 res[0] 记录方案数量 backtrack(choices, state, n, res) return res[0] } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsBacktrack(n) println("爬 $n 阶楼梯共有 $res 种方案") } ================================================ FILE: codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt ================================================ /** * File: climbing_stairs_constraint_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 带约束爬楼梯:动态规划 */ fun climbingStairsConstraintDP(n: Int): Int { if (n == 1 || n == 2) { return 1 } // 初始化 dp 表,用于存储子问题的解 val dp = Array(n + 1) { IntArray(3) } // 初始状态:预设最小子问题的解 dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // 状态转移:从较小子问题逐步求解较大子问题 for (i in 3..n) { dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] } return dp[n][1] + dp[n][2] } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsConstraintDP(n) println("爬 $n 阶楼梯共有 $res 种方案") } ================================================ FILE: codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt ================================================ /** * File: climbing_stairs_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 搜索 */ fun dfs(i: Int): Int { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i // dp[i] = dp[i-1] + dp[i-2] val count = dfs(i - 1) + dfs(i - 2) return count } /* 爬楼梯:搜索 */ fun climbingStairsDFS(n: Int): Int { return dfs(n) } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsDFS(n) println("爬 $n 阶楼梯共有 $res 种方案") } ================================================ FILE: codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt ================================================ /** * File: climbing_stairs_dfs_mem.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 记忆化搜索 */ fun dfs(i: Int, mem: IntArray): Int { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i // 若存在记录 dp[i] ,则直接返回之 if (mem[i] != -1) return mem[i] // dp[i] = dp[i-1] + dp[i-2] val count = dfs(i - 1, mem) + dfs(i - 2, mem) // 记录 dp[i] mem[i] = count return count } /* 爬楼梯:记忆化搜索 */ fun climbingStairsDFSMem(n: Int): Int { // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 val mem = IntArray(n + 1) mem.fill(-1) return dfs(n, mem) } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsDFSMem(n) println("爬 $n 阶楼梯共有 $res 种方案") } ================================================ FILE: codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt ================================================ /** * File: climbing_stairs_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 爬楼梯:动态规划 */ fun climbingStairsDP(n: Int): Int { if (n == 1 || n == 2) return n // 初始化 dp 表,用于存储子问题的解 val dp = IntArray(n + 1) // 初始状态:预设最小子问题的解 dp[1] = 1 dp[2] = 2 // 状态转移:从较小子问题逐步求解较大子问题 for (i in 3..n) { dp[i] = dp[i - 1] + dp[i - 2] } return dp[n] } /* 爬楼梯:空间优化后的动态规划 */ fun climbingStairsDPComp(n: Int): Int { if (n == 1 || n == 2) return n var a = 1 var b = 2 for (i in 3..n) { val temp = b b += a a = temp } return b } /* Driver Code */ fun main() { val n = 9 var res = climbingStairsDP(n) println("爬 $n 阶楼梯共有 $res 种方案") res = climbingStairsDPComp(n) println("爬 $n 阶楼梯共有 $res 种方案") } ================================================ FILE: codes/kotlin/chapter_dynamic_programming/coin_change.kt ================================================ /** * File: coin_change.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* 零钱兑换:动态规划 */ fun coinChangeDP(coins: IntArray, amt: Int): Int { val n = coins.size val MAX = amt + 1 // 初始化 dp 表 val dp = Array(n + 1) { IntArray(amt + 1) } // 状态转移:首行首列 for (a in 1..amt) { dp[0][a] = MAX } // 状态转移:其余行和列 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a] } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) } } } return if (dp[n][amt] != MAX) dp[n][amt] else -1 } /* 零钱兑换:空间优化后的动态规划 */ fun coinChangeDPComp(coins: IntArray, amt: Int): Int { val n = coins.size val MAX = amt + 1 // 初始化 dp 表 val dp = IntArray(amt + 1) dp.fill(MAX) dp[0] = 0 // 状态转移 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a] } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) } } } return if (dp[amt] != MAX) dp[amt] else -1 } /* Driver Code */ fun main() { val coins = intArrayOf(1, 2, 5) val amt = 4 // 动态规划 var res = coinChangeDP(coins, amt) println("凑到目标金额所需的最少硬币数量为 $res") // 空间优化后的动态规划 res = coinChangeDPComp(coins, amt) println("凑到目标金额所需的最少硬币数量为 $res") } ================================================ FILE: codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt ================================================ /** * File: coin_change_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 零钱兑换 II:动态规划 */ fun coinChangeIIDP(coins: IntArray, amt: Int): Int { val n = coins.size // 初始化 dp 表 val dp = Array(n + 1) { IntArray(amt + 1) } // 初始化首列 for (i in 0..n) { dp[i][0] = 1 } // 状态转移 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a] } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] } } } return dp[n][amt] } /* 零钱兑换 II:空间优化后的动态规划 */ fun coinChangeIIDPComp(coins: IntArray, amt: Int): Int { val n = coins.size // 初始化 dp 表 val dp = IntArray(amt + 1) dp[0] = 1 // 状态转移 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a] } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]] } } } return dp[amt] } /* Driver Code */ fun main() { val coins = intArrayOf(1, 2, 5) val amt = 5 // 动态规划 var res = coinChangeIIDP(coins, amt) println("凑出目标金额的硬币组合数量为 $res") // 空间优化后的动态规划 res = coinChangeIIDPComp(coins, amt) println("凑出目标金额的硬币组合数量为 $res") } ================================================ FILE: codes/kotlin/chapter_dynamic_programming/edit_distance.kt ================================================ /** * File: edit_distance.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* 编辑距离:暴力搜索 */ fun editDistanceDFS( s: String, t: String, i: Int, j: Int ): Int { // 若 s 和 t 都为空,则返回 0 if (i == 0 && j == 0) return 0 // 若 s 为空,则返回 t 长度 if (i == 0) return j // 若 t 为空,则返回 s 长度 if (j == 0) return i // 若两字符相等,则直接跳过此两字符 if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1) // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 val insert = editDistanceDFS(s, t, i, j - 1) val delete = editDistanceDFS(s, t, i - 1, j) val replace = editDistanceDFS(s, t, i - 1, j - 1) // 返回最少编辑步数 return min(min(insert, delete), replace) + 1 } /* 编辑距离:记忆化搜索 */ fun editDistanceDFSMem( s: String, t: String, mem: Array, i: Int, j: Int ): Int { // 若 s 和 t 都为空,则返回 0 if (i == 0 && j == 0) return 0 // 若 s 为空,则返回 t 长度 if (i == 0) return j // 若 t 为空,则返回 s 长度 if (j == 0) return i // 若已有记录,则直接返回之 if (mem[i][j] != -1) return mem[i][j] // 若两字符相等,则直接跳过此两字符 if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1) // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 val insert = editDistanceDFSMem(s, t, mem, i, j - 1) val delete = editDistanceDFSMem(s, t, mem, i - 1, j) val replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1) // 记录并返回最少编辑步数 mem[i][j] = min(min(insert, delete), replace) + 1 return mem[i][j] } /* 编辑距离:动态规划 */ fun editDistanceDP(s: String, t: String): Int { val n = s.length val m = t.length val dp = Array(n + 1) { IntArray(m + 1) } // 状态转移:首行首列 for (i in 1..n) { dp[i][0] = i } for (j in 1..m) { dp[0][j] = j } // 状态转移:其余行和列 for (i in 1..n) { for (j in 1..m) { if (s[i - 1] == t[j - 1]) { // 若两字符相等,则直接跳过此两字符 dp[i][j] = dp[i - 1][j - 1] } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 } } } return dp[n][m] } /* 编辑距离:空间优化后的动态规划 */ fun editDistanceDPComp(s: String, t: String): Int { val n = s.length val m = t.length val dp = IntArray(m + 1) // 状态转移:首行 for (j in 1..m) { dp[j] = j } // 状态转移:其余行 for (i in 1..n) { // 状态转移:首列 var leftup = dp[0] // 暂存 dp[i-1, j-1] dp[0] = i // 状态转移:其余列 for (j in 1..m) { val temp = dp[j] if (s[i - 1] == t[j - 1]) { // 若两字符相等,则直接跳过此两字符 dp[j] = leftup } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 } leftup = temp // 更新为下一轮的 dp[i-1, j-1] } } return dp[m] } /* Driver Code */ fun main() { val s = "bag" val t = "pack" val n = s.length val m = t.length // 暴力搜索 var res = editDistanceDFS(s, t, n, m) println("将 $s 更改为 $t 最少需要编辑 $res 步") // 记忆化搜索 val mem = Array(n + 1) { IntArray(m + 1) } for (row in mem) row.fill(-1) res = editDistanceDFSMem(s, t, mem, n, m) println("将 $s 更改为 $t 最少需要编辑 $res 步") // 动态规划 res = editDistanceDP(s, t) println("将 $s 更改为 $t 最少需要编辑 $res 步") // 空间优化后的动态规划 res = editDistanceDPComp(s, t) println("将 $s 更改为 $t 最少需要编辑 $res 步") } ================================================ FILE: codes/kotlin/chapter_dynamic_programming/knapsack.kt ================================================ /** * File: knapsack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.max /* 0-1 背包:暴力搜索 */ fun knapsackDFS( wgt: IntArray, _val: IntArray, i: Int, c: Int ): Int { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i == 0 || c == 0) { return 0 } // 若超过背包容量,则只能选择不放入背包 if (wgt[i - 1] > c) { return knapsackDFS(wgt, _val, i - 1, c) } // 计算不放入和放入物品 i 的最大价值 val no = knapsackDFS(wgt, _val, i - 1, c) val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1] // 返回两种方案中价值更大的那一个 return max(no, yes) } /* 0-1 背包:记忆化搜索 */ fun knapsackDFSMem( wgt: IntArray, _val: IntArray, mem: Array, i: Int, c: Int ): Int { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i == 0 || c == 0) { return 0 } // 若已有记录,则直接返回 if (mem[i][c] != -1) { return mem[i][c] } // 若超过背包容量,则只能选择不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, _val, mem, i - 1, c) } // 计算不放入和放入物品 i 的最大价值 val no = knapsackDFSMem(wgt, _val, mem, i - 1, c) val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1] // 记录并返回两种方案中价值更大的那一个 mem[i][c] = max(no, yes) return mem[i][c] } /* 0-1 背包:动态规划 */ fun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int { val n = wgt.size // 初始化 dp 表 val dp = Array(n + 1) { IntArray(cap + 1) } // 状态转移 for (i in 1..n) { for (c in 1..cap) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c] } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1]) } } } return dp[n][cap] } /* 0-1 背包:空间优化后的动态规划 */ fun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int { val n = wgt.size // 初始化 dp 表 val dp = IntArray(cap + 1) // 状态转移 for (i in 1..n) { // 倒序遍历 for (c in cap downTo 1) { if (wgt[i - 1] <= c) { // 不选和选物品 i 这两种方案的较大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) } } } return dp[cap] } /* Driver Code */ fun main() { val wgt = intArrayOf(10, 20, 30, 40, 50) val _val = intArrayOf(50, 120, 150, 210, 240) val cap = 50 val n = wgt.size // 暴力搜索 var res = knapsackDFS(wgt, _val, n, cap) println("不超过背包容量的最大物品价值为 $res") // 记忆化搜索 val mem = Array(n + 1) { IntArray(cap + 1) } for (row in mem) { row.fill(-1) } res = knapsackDFSMem(wgt, _val, mem, n, cap) println("不超过背包容量的最大物品价值为 $res") // 动态规划 res = knapsackDP(wgt, _val, cap) println("不超过背包容量的最大物品价值为 $res") // 空间优化后的动态规划 res = knapsackDPComp(wgt, _val, cap) println("不超过背包容量的最大物品价值为 $res") } ================================================ FILE: codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt ================================================ /** * File: min_cost_climbing_stairs_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* 爬楼梯最小代价:动态规划 */ fun minCostClimbingStairsDP(cost: IntArray): Int { val n = cost.size - 1 if (n == 1 || n == 2) return cost[n] // 初始化 dp 表,用于存储子问题的解 val dp = IntArray(n + 1) // 初始状态:预设最小子问题的解 dp[1] = cost[1] dp[2] = cost[2] // 状态转移:从较小子问题逐步求解较大子问题 for (i in 3..n) { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] } return dp[n] } /* 爬楼梯最小代价:空间优化后的动态规划 */ fun minCostClimbingStairsDPComp(cost: IntArray): Int { val n = cost.size - 1 if (n == 1 || n == 2) return cost[n] var a = cost[1] var b = cost[2] for (i in 3..n) { val tmp = b b = min(a, tmp) + cost[i] a = tmp } return b } /* Driver Code */ fun main() { val cost = intArrayOf(0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1) println("输入楼梯的代价列表为 ${cost.contentToString()}") var res = minCostClimbingStairsDP(cost) println("爬完楼梯的最低代价为 $res") res = minCostClimbingStairsDPComp(cost) println("爬完楼梯的最低代价为 $res") } ================================================ FILE: codes/kotlin/chapter_dynamic_programming/min_path_sum.kt ================================================ /** * File: min_path_sum.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* 最小路径和:暴力搜索 */ fun minPathSumDFS(grid: Array, i: Int, j: Int): Int { // 若为左上角单元格,则终止搜索 if (i == 0 && j == 0) { return grid[0][0] } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 || j < 0) { return Int.MAX_VALUE } // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 val up = minPathSumDFS(grid, i - 1, j) val left = minPathSumDFS(grid, i, j - 1) // 返回从左上角到 (i, j) 的最小路径代价 return min(left, up) + grid[i][j] } /* 最小路径和:记忆化搜索 */ fun minPathSumDFSMem( grid: Array, mem: Array, i: Int, j: Int ): Int { // 若为左上角单元格,则终止搜索 if (i == 0 && j == 0) { return grid[0][0] } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 || j < 0) { return Int.MAX_VALUE } // 若已有记录,则直接返回 if (mem[i][j] != -1) { return mem[i][j] } // 左边和上边单元格的最小路径代价 val up = minPathSumDFSMem(grid, mem, i - 1, j) val left = minPathSumDFSMem(grid, mem, i, j - 1) // 记录并返回左上角到 (i, j) 的最小路径代价 mem[i][j] = min(left, up) + grid[i][j] return mem[i][j] } /* 最小路径和:动态规划 */ fun minPathSumDP(grid: Array): Int { val n = grid.size val m = grid[0].size // 初始化 dp 表 val dp = Array(n) { IntArray(m) } dp[0][0] = grid[0][0] // 状态转移:首行 for (j in 1..): Int { val n = grid.size val m = grid[0].size // 初始化 dp 表 val dp = IntArray(m) // 状态转移:首行 dp[0] = grid[0][0] for (j in 1.. c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c] } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1]) } } } return dp[n][cap] } /* 完全背包:空间优化后的动态规划 */ fun unboundedKnapsackDPComp( wgt: IntArray, _val: IntArray, cap: Int ): Int { val n = wgt.size // 初始化 dp 表 val dp = IntArray(cap + 1) // 状态转移 for (i in 1..n) { for (c in 1..cap) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c] } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) } } } return dp[cap] } /* Driver Code */ fun main() { val wgt = intArrayOf(1, 2, 3) val _val = intArrayOf(5, 11, 15) val cap = 4 // 动态规划 var res = unboundedKnapsackDP(wgt, _val, cap) println("不超过背包容量的最大物品价值为 $res") // 空间优化后的动态规划 res = unboundedKnapsackDPComp(wgt, _val, cap) println("不超过背包容量的最大物品价值为 $res") } ================================================ FILE: codes/kotlin/chapter_graph/graph_adjacency_list.kt ================================================ /** * File: graph_adjacency_list.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex /* 基于邻接表实现的无向图类 */ class GraphAdjList(edges: Array>) { // 邻接表,key:顶点,value:该顶点的所有邻接顶点 val adjList = HashMap>() /* 构造方法 */ init { // 添加所有顶点和边 for (edge in edges) { addVertex(edge[0]!!) addVertex(edge[1]!!) addEdge(edge[0]!!, edge[1]!!) } } /* 获取顶点数量 */ fun size(): Int { return adjList.size } /* 添加边 */ fun addEdge(vet1: Vertex, vet2: Vertex) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) throw IllegalArgumentException() // 添加边 vet1 - vet2 adjList[vet1]?.add(vet2) adjList[vet2]?.add(vet1) } /* 删除边 */ fun removeEdge(vet1: Vertex, vet2: Vertex) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) throw IllegalArgumentException() // 删除边 vet1 - vet2 adjList[vet1]?.remove(vet2) adjList[vet2]?.remove(vet1) } /* 添加顶点 */ fun addVertex(vet: Vertex) { if (adjList.containsKey(vet)) return // 在邻接表中添加一个新链表 adjList[vet] = mutableListOf() } /* 删除顶点 */ fun removeVertex(vet: Vertex) { if (!adjList.containsKey(vet)) throw IllegalArgumentException() // 在邻接表中删除顶点 vet 对应的链表 adjList.remove(vet) // 遍历其他顶点的链表,删除所有包含 vet 的边 for (list in adjList.values) { list.remove(vet) } } /* 打印邻接表 */ fun print() { println("邻接表 =") for (pair in adjList.entries) { val tmp = mutableListOf() for (vertex in pair.value) { tmp.add(vertex._val) } println("${pair.key._val}: $tmp,") } } } /* Driver Code */ fun main() { /* 初始化无向图 */ val v = Vertex.valsToVets(intArrayOf(1, 3, 2, 5, 4)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[2], v[3]), arrayOf(v[2], v[4]), arrayOf(v[3], v[4]) ) val graph = GraphAdjList(edges) println("\n初始化后,图为") graph.print() /* 添加边 */ // 顶点 1, 2 即 v[0], v[2] graph.addEdge(v[0]!!, v[2]!!) println("\n添加边 1-2 后,图为") graph.print() /* 删除边 */ // 顶点 1, 3 即 v[0], v[1] graph.removeEdge(v[0]!!, v[1]!!) println("\n删除边 1-3 后,图为") graph.print() /* 添加顶点 */ val v5 = Vertex(6) graph.addVertex(v5) println("\n添加顶点 6 后,图为") graph.print() /* 删除顶点 */ // 顶点 3 即 v[1] graph.removeVertex(v[1]!!) println("\n删除顶点 3 后,图为") graph.print() } ================================================ FILE: codes/kotlin/chapter_graph/graph_adjacency_matrix.kt ================================================ /** * File: graph_adjacency_matrix.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.printMatrix /* 基于邻接矩阵实现的无向图类 */ class GraphAdjMat(vertices: IntArray, edges: Array) { val vertices = mutableListOf() // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” val adjMat = mutableListOf>() // 邻接矩阵,行列索引对应“顶点索引” /* 构造方法 */ init { // 添加顶点 for (vertex in vertices) { addVertex(vertex) } // 添加边 // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 for (edge in edges) { addEdge(edge[0], edge[1]) } } /* 获取顶点数量 */ fun size(): Int { return vertices.size } /* 添加顶点 */ fun addVertex(_val: Int) { val n = size() // 向顶点列表中添加新顶点的值 vertices.add(_val) // 在邻接矩阵中添加一行 val newRow = mutableListOf() for (j in 0..= size()) throw IndexOutOfBoundsException() // 在顶点列表中移除索引 index 的顶点 vertices.removeAt(index) // 在邻接矩阵中删除索引 index 的行 adjMat.removeAt(index) // 在邻接矩阵中删除索引 index 的列 for (row in adjMat) { row.removeAt(index) } } /* 添加边 */ // 参数 i, j 对应 vertices 元素索引 fun addEdge(i: Int, j: Int) { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw IndexOutOfBoundsException() // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) adjMat[i][j] = 1 adjMat[j][i] = 1 } /* 删除边 */ // 参数 i, j 对应 vertices 元素索引 fun removeEdge(i: Int, j: Int) { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw IndexOutOfBoundsException() adjMat[i][j] = 0 adjMat[j][i] = 0 } /* 打印邻接矩阵 */ fun print() { print("顶点列表 = ") println(vertices) println("邻接矩阵 =") printMatrix(adjMat) } } /* Driver Code */ fun main() { /* 初始化无向图 */ // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 val vertices = intArrayOf(1, 3, 2, 5, 4) val edges = arrayOf( intArrayOf(0, 1), intArrayOf(0, 3), intArrayOf(1, 2), intArrayOf(2, 3), intArrayOf(2, 4), intArrayOf(3, 4) ) val graph = GraphAdjMat(vertices, edges) println("\n初始化后,图为") graph.print() /* 添加边 */ // 顶点 1, 2 的索引分别为 0, 2 graph.addEdge(0, 2) println("\n添加边 1-2 后,图为") graph.print() /* 删除边 */ // 顶点 1, 3 的索引分别为 0, 1 graph.removeEdge(0, 1) println("\n删除边 1-3 后,图为") graph.print() /* 添加顶点 */ graph.addVertex(6) println("\n添加顶点 6 后,图为") graph.print() /* 删除顶点 */ // 顶点 3 的索引为 1 graph.removeVertex(1) println("\n删除顶点 3 后,图为") graph.print() } ================================================ FILE: codes/kotlin/chapter_graph/graph_bfs.kt ================================================ /** * File: graph_bfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex import java.util.* /* 广度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList { // 顶点遍历序列 val res = mutableListOf() // 哈希集合,用于记录已被访问过的顶点 val visited = HashSet() visited.add(startVet) // 队列用于实现 BFS val que = LinkedList() que.offer(startVet) // 以顶点 vet 为起点,循环直至访问完所有顶点 while (!que.isEmpty()) { val vet = que.poll() // 队首顶点出队 res.add(vet) // 记录访问顶点 // 遍历该顶点的所有邻接顶点 for (adjVet in graph.adjList[vet]!!) { if (visited.contains(adjVet)) continue // 跳过已被访问的顶点 que.offer(adjVet) // 只入队未访问的顶点 visited.add(adjVet) // 标记该顶点已被访问 } } // 返回顶点遍历序列 return res } /* Driver Code */ fun main() { /* 初始化无向图 */ val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[1], v[4]), arrayOf(v[2], v[5]), arrayOf(v[3], v[4]), arrayOf(v[3], v[6]), arrayOf(v[4], v[5]), arrayOf(v[4], v[7]), arrayOf(v[5], v[8]), arrayOf(v[6], v[7]), arrayOf(v[7], v[8]) ) val graph = GraphAdjList(edges) println("\n初始化后,图为") graph.print() /* 广度优先遍历 */ val res = graphBFS(graph, v[0]!!) println("\n广度优先遍历(BFS)顶点序列为") println(Vertex.vetsToVals(res)) } ================================================ FILE: codes/kotlin/chapter_graph/graph_dfs.kt ================================================ /** * File: graph_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex /* 深度优先遍历辅助函数 */ fun dfs( graph: GraphAdjList, visited: MutableSet, res: MutableList, vet: Vertex? ) { res.add(vet) // 记录访问顶点 visited.add(vet) // 标记该顶点已被访问 // 遍历该顶点的所有邻接顶点 for (adjVet in graph.adjList[vet]!!) { if (visited.contains(adjVet)) continue // 跳过已被访问的顶点 // 递归访问邻接顶点 dfs(graph, visited, res, adjVet) } } /* 深度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 fun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList { // 顶点遍历序列 val res = mutableListOf() // 哈希集合,用于记录已被访问过的顶点 val visited = HashSet() dfs(graph, visited, res, startVet) return res } /* Driver Code */ fun main() { /* 初始化无向图 */ val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[2], v[5]), arrayOf(v[4], v[5]), arrayOf(v[5], v[6]) ) val graph = GraphAdjList(edges) println("\n初始化后,图为") graph.print() /* 深度优先遍历 */ val res = graphDFS(graph, v[0]) println("\n深度优先遍历(DFS)顶点序列为") println(Vertex.vetsToVals(res)) } ================================================ FILE: codes/kotlin/chapter_greedy/coin_change_greedy.kt ================================================ /** * File: coin_change_greedy.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy /* 零钱兑换:贪心 */ fun coinChangeGreedy(coins: IntArray, amt: Int): Int { // 假设 coins 列表有序 var am = amt var i = coins.size - 1 var count = 0 // 循环进行贪心选择,直到无剩余金额 while (am > 0) { // 找到小于且最接近剩余金额的硬币 while (i > 0 && coins[i] > am) { i-- } // 选择 coins[i] am -= coins[i] count++ } // 若未找到可行方案,则返回 -1 return if (am == 0) count else -1 } /* Driver Code */ fun main() { // 贪心:能够保证找到全局最优解 var coins = intArrayOf(1, 5, 10, 20, 50, 100) var amt = 186 var res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("凑到 $amt 所需的最少硬币数量为 $res") // 贪心:无法保证找到全局最优解 coins = intArrayOf(1, 20, 50) amt = 60 res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("凑到 $amt 所需的最少硬币数量为 $res") println("实际上需要的最少数量为 3 ,即 20 + 20 + 20") // 贪心:无法保证找到全局最优解 coins = intArrayOf(1, 49, 50) amt = 98 res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("凑到 $amt 所需的最少硬币数量为 $res") println("实际上需要的最少数量为 2 ,即 49 + 49") } ================================================ FILE: codes/kotlin/chapter_greedy/fractional_knapsack.kt ================================================ /** * File: fractional_knapsack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy /* 物品 */ class Item( val w: Int, // 物品 val v: Int // 物品价值 ) /* 分数背包:贪心 */ fun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double { // 创建物品列表,包含两个属性:重量、价值 var cap = c val items = arrayOfNulls(wgt.size) for (i in wgt.indices) { items[i] = Item(wgt[i], _val[i]) } // 按照单位价值 item.v / item.w 从高到低进行排序 items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) } // 循环贪心选择 var res = 0.0 for (item in items) { if (item!!.w <= cap) { // 若剩余容量充足,则将当前物品整个装进背包 res += item.v cap -= item.w } else { // 若剩余容量不足,则将当前物品的一部分装进背包 res += item.v.toDouble() / item.w * cap // 已无剩余容量,因此跳出循环 break } } return res } /* Driver Code */ fun main() { val wgt = intArrayOf(10, 20, 30, 40, 50) val _val = intArrayOf(50, 120, 150, 210, 240) val cap = 50 // 贪心算法 val res = fractionalKnapsack(wgt, _val, cap) println("不超过背包容量的最大物品价值为 $res") } ================================================ FILE: codes/kotlin/chapter_greedy/max_capacity.kt ================================================ /** * File: max_capacity.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy import kotlin.math.max import kotlin.math.min /* 最大容量:贪心 */ fun maxCapacity(ht: IntArray): Int { // 初始化 i, j,使其分列数组两端 var i = 0 var j = ht.size - 1 // 初始最大容量为 0 var res = 0 // 循环贪心选择,直至两板相遇 while (i < j) { // 更新最大容量 val cap = min(ht[i], ht[j]) * (j - i) res = max(res, cap) // 向内移动短板 if (ht[i] < ht[j]) { i++ } else { j-- } } return res } /* Driver Code */ fun main() { val ht = intArrayOf(3, 8, 5, 2, 7, 7, 3, 4) // 贪心算法 val res = maxCapacity(ht) println("最大容量为 $res") } ================================================ FILE: codes/kotlin/chapter_greedy/max_product_cutting.kt ================================================ /** * File: max_product_cutting.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy import kotlin.math.pow /* 最大切分乘积:贪心 */ fun maxProductCutting(n: Int): Int { // 当 n <= 3 时,必须切分出一个 1 if (n <= 3) { return 1 * (n - 1) } // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 val a = n / 3 val b = n % 3 if (b == 1) { // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 return 3.0.pow((a - 1)).toInt() * 2 * 2 } if (b == 2) { // 当余数为 2 时,不做处理 return 3.0.pow(a).toInt() * 2 * 2 } // 当余数为 0 时,不做处理 return 3.0.pow(a).toInt() } /* Driver Code */ fun main() { val n = 58 // 贪心算法 val res = maxProductCutting(n) println("最大切分乘积为 $res") } ================================================ FILE: codes/kotlin/chapter_hashing/array_hash_map.kt ================================================ /** * File: array_hash_map.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* 键值对 */ class Pair( var key: Int, var _val: String ) /* 基于数组实现的哈希表 */ class ArrayHashMap { // 初始化数组,包含 100 个桶 private val buckets = arrayOfNulls(100) /* 哈希函数 */ fun hashFunc(key: Int): Int { val index = key % 100 return index } /* 查询操作 */ fun get(key: Int): String? { val index = hashFunc(key) val pair = buckets[index] ?: return null return pair._val } /* 添加操作 */ fun put(key: Int, _val: String) { val pair = Pair(key, _val) val index = hashFunc(key) buckets[index] = pair } /* 删除操作 */ fun remove(key: Int) { val index = hashFunc(key) // 置为 null ,代表删除 buckets[index] = null } /* 获取所有键值对 */ fun pairSet(): MutableList { val pairSet = mutableListOf() for (pair in buckets) { if (pair != null) pairSet.add(pair) } return pairSet } /* 获取所有键 */ fun keySet(): MutableList { val keySet = mutableListOf() for (pair in buckets) { if (pair != null) keySet.add(pair.key) } return keySet } /* 获取所有值 */ fun valueSet(): MutableList { val valueSet = mutableListOf() for (pair in buckets) { if (pair != null) valueSet.add(pair._val) } return valueSet } /* 打印哈希表 */ fun print() { for (kv in pairSet()) { val key = kv.key val _val = kv._val println("$key -> $_val") } } } /* Driver Code */ fun main() { /* 初始化哈希表 */ val map = ArrayHashMap() /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(12836, "小哈") map.put(15937, "小啰") map.put(16750, "小算") map.put(13276, "小法") map.put(10583, "小鸭") println("\n添加完成后,哈希表为\nKey -> Value") map.print() /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value val name = map.get(15937) println("\n输入学号 15937 ,查询到姓名 $name") /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(10583) println("\n删除 10583 后,哈希表为\nKey -> Value") map.print() /* 遍历哈希表 */ println("\n遍历键值对 Key -> Value") for (kv in map.pairSet()) { println("${kv.key} -> ${kv._val}") } println("\n单独遍历键 Key") for (key in map.keySet()) { println(key) } println("\n单独遍历值 Value") for (_val in map.valueSet()) { println(_val) } } ================================================ FILE: codes/kotlin/chapter_hashing/built_in_hash.kt ================================================ /** * File: built_in_hash.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing import utils.ListNode /* Driver Code */ fun main() { val num = 3 val hashNum = num.hashCode() println("整数 $num 的哈希值为 $hashNum") val bol = true val hashBol = bol.hashCode() println("布尔量 $bol 的哈希值为 $hashBol") val dec = 3.14159 val hashDec = dec.hashCode() println("小数 $dec 的哈希值为 $hashDec") val str = "Hello 算法" val hashStr = str.hashCode() println("字符串 $str 的哈希值为 $hashStr") val arr = arrayOf(12836, "小哈") val hashTup = arr.contentHashCode() println("数组 ${arr.contentToString()} 的哈希值为 $hashTup") val obj = ListNode(0) val hashObj = obj.hashCode() println("节点对象 $obj 的哈希值为 $hashObj") } ================================================ FILE: codes/kotlin/chapter_hashing/hash_map.kt ================================================ /** * File: hash_map.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing import utils.printHashMap /* Driver Code */ fun main() { /* 初始化哈希表 */ val map = HashMap() /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map[12836] = "小哈" map[15937] = "小啰" map[16750] = "小算" map[13276] = "小法" map[10583] = "小鸭" println("\n添加完成后,哈希表为\nKey -> Value") printHashMap(map) /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value val name = map[15937] println("\n输入学号 15937 ,查询到姓名 $name") /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(10583) println("\n删除 10583 后,哈希表为\nKey -> Value") printHashMap(map) /* 遍历哈希表 */ println("\n遍历键值对 Key->Value") for ((key, value) in map) { println("$key -> $value") } println("\n单独遍历键 Key") for (key in map.keys) { println(key) } println("\n单独遍历值 Value") for (_val in map.values) { println(_val) } } ================================================ FILE: codes/kotlin/chapter_hashing/hash_map_chaining.kt ================================================ /** * File: hash_map_chaining.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* 链式地址哈希表 */ class HashMapChaining { var size: Int // 键值对数量 var capacity: Int // 哈希表容量 val loadThres: Double // 触发扩容的负载因子阈值 val extendRatio: Int // 扩容倍数 var buckets: MutableList> // 桶数组 /* 构造方法 */ init { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = mutableListOf() for (i in 0.. loadThres) { extend() } val index = hashFunc(key) val bucket = buckets[index] // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 for (pair in bucket) { if (pair.key == key) { pair._val = _val return } } // 若无该 key ,则将键值对添加至尾部 val pair = Pair(key, _val) bucket.add(pair) size++ } /* 删除操作 */ fun remove(key: Int) { val index = hashFunc(key) val bucket = buckets[index] // 遍历桶,从中删除键值对 for (pair in bucket) { if (pair.key == key) { bucket.remove(pair) size-- break } } } /* 扩容哈希表 */ fun extend() { // 暂存原哈希表 val bucketsTmp = buckets // 初始化扩容后的新哈希表 capacity *= extendRatio // mutablelist 无固定大小 buckets = mutableListOf() for (i in 0..() for (pair in bucket) { val k = pair.key val v = pair._val res.add("$k -> $v") } println(res) } } } /* Driver Code */ fun main() { /* 初始化哈希表 */ val map = HashMapChaining() /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(12836, "小哈") map.put(15937, "小啰") map.put(16750, "小算") map.put(13276, "小法") map.put(10583, "小鸭") println("\n添加完成后,哈希表为\nKey -> Value") map.print() /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value val name = map.get(13276) println("\n输入学号 13276 ,查询到姓名 $name") /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(12836) println("\n删除 12836 后,哈希表为\nKey -> Value") map.print() } ================================================ FILE: codes/kotlin/chapter_hashing/hash_map_open_addressing.kt ================================================ /** * File: hash_map_open_addressing.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* 开放寻址哈希表 */ class HashMapOpenAddressing { private var size: Int // 键值对数量 private var capacity: Int // 哈希表容量 private val loadThres: Double // 触发扩容的负载因子阈值 private val extendRatio: Int // 扩容倍数 private var buckets: Array // 桶数组 private val TOMBSTONE: Pair // 删除标记 /* 构造方法 */ init { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = arrayOfNulls(capacity) TOMBSTONE = Pair(-1, "-1") } /* 哈希函数 */ fun hashFunc(key: Int): Int { return key % capacity } /* 负载因子 */ fun loadFactor(): Double { return (size / capacity).toDouble() } /* 搜索 key 对应的桶索引 */ fun findBucket(key: Int): Int { var index = hashFunc(key) var firstTombstone = -1 // 线性探测,当遇到空桶时跳出 while (buckets[index] != null) { // 若遇到 key ,返回对应的桶索引 if (buckets[index]?.key == key) { // 若之前遇到了删除标记,则将键值对移动至该索引处 if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index] buckets[index] = TOMBSTONE return firstTombstone // 返回移动后的桶索引 } return index // 返回桶索引 } // 记录遇到的首个删除标记 if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index } // 计算桶索引,越过尾部则返回头部 index = (index + 1) % capacity } // 若 key 不存在,则返回添加点的索引 return if (firstTombstone == -1) index else firstTombstone } /* 查询操作 */ fun get(key: Int): String? { // 搜索 key 对应的桶索引 val index = findBucket(key) // 若找到键值对,则返回对应 val if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index]?._val } // 若键值对不存在,则返回 null return null } /* 添加操作 */ fun put(key: Int, _val: String) { // 当负载因子超过阈值时,执行扩容 if (loadFactor() > loadThres) { extend() } // 搜索 key 对应的桶索引 val index = findBucket(key) // 若找到键值对,则覆盖 val 并返回 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index]!!._val = _val return } // 若键值对不存在,则添加该键值对 buckets[index] = Pair(key, _val) size++ } /* 删除操作 */ fun remove(key: Int) { // 搜索 key 对应的桶索引 val index = findBucket(key) // 若找到键值对,则用删除标记覆盖它 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE size-- } } /* 扩容哈希表 */ fun extend() { // 暂存原哈希表 val bucketsTmp = buckets // 初始化扩容后的新哈希表 capacity *= extendRatio buckets = arrayOfNulls(capacity) size = 0 // 将键值对从原哈希表搬运至新哈希表 for (pair in bucketsTmp) { if (pair != null && pair != TOMBSTONE) { put(pair.key, pair._val) } } } /* 打印哈希表 */ fun print() { for (pair in buckets) { if (pair == null) { println("null") } else if (pair == TOMBSTONE) { println("TOMESTOME") } else { println("${pair.key} -> ${pair._val}") } } } } /* Driver Code */ fun main() { // 初始化哈希表 val hashmap = HashMapOpenAddressing() // 添加操作 // 在哈希表中添加键值对 (key, val) hashmap.put(12836, "小哈") hashmap.put(15937, "小啰") hashmap.put(16750, "小算") hashmap.put(13276, "小法") hashmap.put(10583, "小鸭") println("\n添加完成后,哈希表为\nKey -> Value") hashmap.print() // 查询操作 // 向哈希表中输入键 key ,得到值 val val name = hashmap.get(13276) println("\n输入学号 13276 ,查询到姓名 $name") // 删除操作 // 在哈希表中删除键值对 (key, val) hashmap.remove(16750) println("\n删除 16750 后,哈希表为\nKey -> Value") hashmap.print() } ================================================ FILE: codes/kotlin/chapter_hashing/simple_hash.kt ================================================ /** * File: simple_hash.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* 加法哈希 */ fun addHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = (hash + c.code) % MODULUS } return hash.toInt() } /* 乘法哈希 */ fun mulHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = (31 * hash + c.code) % MODULUS } return hash.toInt() } /* 异或哈希 */ fun xorHash(key: String): Int { var hash = 0 val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = hash xor c.code } return hash and MODULUS } /* 旋转哈希 */ fun rotHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS } return hash.toInt() } /* Driver Code */ fun main() { val key = "Hello 算法" var hash = addHash(key) println("加法哈希值为 $hash") hash = mulHash(key) println("乘法哈希值为 $hash") hash = xorHash(key) println("异或哈希值为 $hash") hash = rotHash(key) println("旋转哈希值为 $hash") } ================================================ FILE: codes/kotlin/chapter_heap/heap.kt ================================================ /** * File: heap.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* fun testPush(heap: Queue, _val: Int) { heap.offer(_val) // 元素入堆 print("\n元素 $_val 入堆后\n") printHeap(heap) } fun testPop(heap: Queue) { val _val = heap.poll() // 堆顶元素出堆 print("\n堆顶元素 $_val 出堆后\n") printHeap(heap) } /* Driver Code */ fun main() { /* 初始化堆 */ // 初始化小顶堆 var minHeap = PriorityQueue() // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } println("\n以下测试样例为大顶堆") /* 元素入堆 */ testPush(maxHeap, 1) testPush(maxHeap, 3) testPush(maxHeap, 2) testPush(maxHeap, 5) testPush(maxHeap, 4) /* 获取堆顶元素 */ val peek = maxHeap.peek() print("\n堆顶元素为 $peek\n") /* 堆顶元素出堆 */ testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) /* 获取堆大小 */ val size = maxHeap.size print("\n堆元素数量为 $size\n") /* 判断堆是否为空 */ val isEmpty = maxHeap.isEmpty() print("\n堆是否为空 $isEmpty\n") /* 输入列表并建堆 */ // 时间复杂度为 O(n) ,而非 O(nlogn) minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) println("\n输入列表并建立小顶堆后") printHeap(minHeap) } ================================================ FILE: codes/kotlin/chapter_heap/my_heap.kt ================================================ /** * File: my_heap.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* /* 大顶堆 */ class MaxHeap(nums: MutableList?) { // 使用列表而非数组,这样无须考虑扩容问题 private val maxHeap = mutableListOf() /* 构造方法,根据输入列表建堆 */ init { // 将列表元素原封不动添加进堆 maxHeap.addAll(nums!!) // 堆化除叶节点以外的其他所有节点 for (i in parent(size() - 1) downTo 0) { siftDown(i) } } /* 获取左子节点的索引 */ private fun left(i: Int): Int { return 2 * i + 1 } /* 获取右子节点的索引 */ private fun right(i: Int): Int { return 2 * i + 2 } /* 获取父节点的索引 */ private fun parent(i: Int): Int { return (i - 1) / 2 // 向下整除 } /* 交换元素 */ private fun swap(i: Int, j: Int) { val temp = maxHeap[i] maxHeap[i] = maxHeap[j] maxHeap[j] = temp } /* 获取堆大小 */ fun size(): Int { return maxHeap.size } /* 判断堆是否为空 */ fun isEmpty(): Boolean { /* 判断堆是否为空 */ return size() == 0 } /* 访问堆顶元素 */ fun peek(): Int { return maxHeap[0] } /* 元素入堆 */ fun push(_val: Int) { // 添加节点 maxHeap.add(_val) // 从底至顶堆化 siftUp(size() - 1) } /* 从节点 i 开始,从底至顶堆化 */ private fun siftUp(it: Int) { // Kotlin的函数参数不可变,因此创建临时变量 var i = it while (true) { // 获取节点 i 的父节点 val p = parent(i) // 当“越过根节点”或“节点无须修复”时,结束堆化 if (p < 0 || maxHeap[i] <= maxHeap[p]) break // 交换两节点 swap(i, p) // 循环向上堆化 i = p } } /* 元素出堆 */ fun pop(): Int { // 判空处理 if (isEmpty()) throw IndexOutOfBoundsException() // 交换根节点与最右叶节点(交换首元素与尾元素) swap(0, size() - 1) // 删除节点 val _val = maxHeap.removeAt(size() - 1) // 从顶至底堆化 siftDown(0) // 返回堆顶元素 return _val } /* 从节点 i 开始,从顶至底堆化 */ private fun siftDown(it: Int) { // Kotlin的函数参数不可变,因此创建临时变量 var i = it while (true) { // 判断节点 i, l, r 中值最大的节点,记为 ma val l = left(i) val r = right(i) var ma = i if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if (ma == i) break // 交换两节点 swap(i, ma) // 循环向下堆化 i = ma } } /* 打印堆(二叉树) */ fun print() { val queue = PriorityQueue { a: Int, b: Int -> b - a } queue.addAll(maxHeap) printHeap(queue) } } /* Driver Code */ fun main() { /* 初始化大顶堆 */ val maxHeap = MaxHeap(mutableListOf(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)) println("\n输入列表并建堆后") maxHeap.print() /* 获取堆顶元素 */ var peek = maxHeap.peek() print("\n堆顶元素为 $peek\n") /* 元素入堆 */ val _val = 7 maxHeap.push(_val) print("\n元素 $_val 入堆后\n") maxHeap.print() /* 堆顶元素出堆 */ peek = maxHeap.pop() print("\n堆顶元素 $peek 出堆后\n") maxHeap.print() /* 获取堆大小 */ val size = maxHeap.size() print("\n堆元素数量为 $size\n") /* 判断堆是否为空 */ val isEmpty = maxHeap.isEmpty() print("\n堆是否为空 $isEmpty\n") } ================================================ FILE: codes/kotlin/chapter_heap/top_k.kt ================================================ /** * File: top_k.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* /* 基于堆查找数组中最大的 k 个元素 */ fun topKHeap(nums: IntArray, k: Int): Queue { // 初始化小顶堆 val heap = PriorityQueue() // 将数组的前 k 个元素入堆 for (i in 0.. heap.peek()) { heap.poll() heap.offer(nums[i]) } } return heap } /* Driver Code */ fun main() { val nums = intArrayOf(1, 7, 6, 3, 2) val k = 3 val res = topKHeap(nums, k) println("最大的 $k 个元素为") printHeap(res) } ================================================ FILE: codes/kotlin/chapter_searching/binary_search.kt ================================================ /** * File: binary_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* 二分查找(双闭区间) */ fun binarySearch(nums: IntArray, target: Int): Int { // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 var i = 0 var j = nums.size - 1 // 循环,当搜索区间为空时跳出(当 i > j 时为空) while (i <= j) { val m = i + (j - i) / 2 // 计算中点索引 m if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 i = m + 1 else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 j = m - 1 else // 找到目标元素,返回其索引 return m } // 未找到目标元素,返回 -1 return -1 } /* 二分查找(左闭右开区间) */ fun binarySearchLCRO(nums: IntArray, target: Int): Int { // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 var i = 0 var j = nums.size // 循环,当搜索区间为空时跳出(当 i = j 时为空) while (i < j) { val m = i + (j - i) / 2 // 计算中点索引 m if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j) 中 i = m + 1 else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中 j = m else // 找到目标元素,返回其索引 return m } // 未找到目标元素,返回 -1 return -1 } /* Driver Code */ fun main() { val target = 6 val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) /* 二分查找(双闭区间) */ var index = binarySearch(nums, target) println("目标元素 6 的索引 = $index") /* 二分查找(左闭右开区间) */ index = binarySearchLCRO(nums, target) println("目标元素 6 的索引 = $index") } ================================================ FILE: codes/kotlin/chapter_searching/binary_search_edge.kt ================================================ /** * File: binary_search_edge.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* 二分查找最左一个 target */ fun binarySearchLeftEdge(nums: IntArray, target: Int): Int { // 等价于查找 target 的插入点 val i = binarySearchInsertion(nums, target) // 未找到 target ,返回 -1 if (i == nums.size || nums[i] != target) { return -1 } // 找到 target ,返回索引 i return i } /* 二分查找最右一个 target */ fun binarySearchRightEdge(nums: IntArray, target: Int): Int { // 转化为查找最左一个 target + 1 val i = binarySearchInsertion(nums, target + 1) // j 指向最右一个 target ,i 指向首个大于 target 的元素 val j = i - 1 // 未找到 target ,返回 -1 if (j == -1 || nums[j] != target) { return -1 } // 找到 target ,返回索引 j return j } /* Driver Code */ fun main() { // 包含重复元素的数组 val nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) println("\n数组 nums = ${nums.contentToString()}") // 二分查找左边界和右边界 for (target in intArrayOf(6, 7)) { var index = binarySearchLeftEdge(nums, target) println("最左一个元素 $target 的索引为 $index") index = binarySearchRightEdge(nums, target) println("最右一个元素 $target 的索引为 $index") } } ================================================ FILE: codes/kotlin/chapter_searching/binary_search_insertion.kt ================================================ /** * File: binary_search_insertion.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* 二分查找插入点(无重复元素) */ fun binarySearchInsertionSimple(nums: IntArray, target: Int): Int { var i = 0 var j = nums.size - 1 // 初始化双闭区间 [0, n-1] while (i <= j) { val m = i + (j - i) / 2 // 计算中点索引 m if (nums[m] < target) { i = m + 1 // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1 // target 在区间 [i, m-1] 中 } else { return m // 找到 target ,返回插入点 m } } // 未找到 target ,返回插入点 i return i } /* 二分查找插入点(存在重复元素) */ fun binarySearchInsertion(nums: IntArray, target: Int): Int { var i = 0 var j = nums.size - 1 // 初始化双闭区间 [0, n-1] while (i <= j) { val m = i + (j - i) / 2 // 计算中点索引 m if (nums[m] < target) { i = m + 1 // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1 // target 在区间 [i, m-1] 中 } else { j = m - 1 // 首个小于 target 的元素在区间 [i, m-1] 中 } } // 返回插入点 i return i } /* Driver Code */ fun main() { // 无重复元素的数组 var nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) println("\n数组 nums = ${nums.contentToString()}") // 二分查找插入点 for (target in intArrayOf(6, 9)) { val index = binarySearchInsertionSimple(nums, target) println("元素 $target 的插入点的索引为 $index") } // 包含重复元素的数组 nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) println("\n数组 nums = ${nums.contentToString()}") // 二分查找插入点 for (target in intArrayOf(2, 6, 20)) { val index = binarySearchInsertion(nums, target) println("元素 $target 的插入点的索引为 $index") } } ================================================ FILE: codes/kotlin/chapter_searching/hashing_search.kt ================================================ /** * File: hashing_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching import utils.ListNode /* 哈希查找(数组) */ fun hashingSearchArray(map: Map, target: Int): Int { // 哈希表的 key: 目标元素,_val: 索引 // 若哈希表中无此 key ,返回 -1 return map.getOrDefault(target, -1) } /* 哈希查找(链表) */ fun hashingSearchLinkedList(map: Map, target: Int): ListNode? { // 哈希表的 key: 目标节点值,_val: 节点对象 // 若哈希表中无此 key ,返回 null return map.getOrDefault(target, null) } /* Driver Code */ fun main() { val target = 3 /* 哈希查找(数组) */ val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) // 初始化哈希表 val map = HashMap() for (i in nums.indices) { map[nums[i]] = i // key: 元素,_val: 索引 } val index = hashingSearchArray(map, target) println("目标元素 3 的索引 = $index") /* 哈希查找(链表) */ var head = ListNode.arrToLinkedList(nums) // 初始化哈希表 val map1 = HashMap() while (head != null) { map1[head._val] = head // key: 节点值,_val: 节点 head = head.next } val node = hashingSearchLinkedList(map1, target) println("目标节点值 3 的对应节点对象为 $node") } ================================================ FILE: codes/kotlin/chapter_searching/linear_search.kt ================================================ /** * File: linear_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching import utils.ListNode /* 线性查找(数组) */ fun linearSearchArray(nums: IntArray, target: Int): Int { // 遍历数组 for (i in nums.indices) { // 找到目标元素,返回其索引 if (nums[i] == target) return i } // 未找到目标元素,返回 -1 return -1 } /* 线性查找(链表) */ fun linearSearchLinkedList(h: ListNode?, target: Int): ListNode? { // 遍历链表 var head = h while (head != null) { // 找到目标节点,返回之 if (head._val == target) return head head = head.next } // 未找到目标节点,返回 null return null } /* Driver Code */ fun main() { val target = 3 /* 在数组中执行线性查找 */ val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) val index = linearSearchArray(nums, target) println("目标元素 3 的索引 = $index") /* 在链表中执行线性查找 */ val head = ListNode.arrToLinkedList(nums) val node = linearSearchLinkedList(head, target) println("目标节点值 3 的对应节点对象为 $node") } ================================================ FILE: codes/kotlin/chapter_searching/two_sum.kt ================================================ /** * File: two_sum.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* 方法一:暴力枚举 */ fun twoSumBruteForce(nums: IntArray, target: Int): IntArray { val size = nums.size // 两层循环,时间复杂度为 O(n^2) for (i in 0..() // 单层循环,时间复杂度为 O(n) for (i in 0.. nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp } } } } /* 冒泡排序(标志优化) */ fun bubbleSortWithFlag(nums: IntArray) { // 外循环:未排序区间为 [0, i] for (i in nums.size - 1 downTo 1) { var flag = false // 初始化标志位 // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (j in 0.. nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp flag = true // 记录交换元素 } } if (!flag) break // 此轮“冒泡”未交换任何元素,直接跳出 } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) bubbleSort(nums) println("冒泡排序完成后 nums = ${nums.contentToString()}") val nums1 = intArrayOf(4, 1, 3, 1, 5, 2) bubbleSortWithFlag(nums1) println("冒泡排序完成后 nums1 = ${nums1.contentToString()}") } ================================================ FILE: codes/kotlin/chapter_sorting/bucket_sort.kt ================================================ /** * File: bucket_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 桶排序 */ fun bucketSort(nums: FloatArray) { // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 val k = nums.size / 2 val buckets = mutableListOf>() for (i in 0.. nums[ma]) ma = l if (r < n && nums[r] > nums[ma]) ma = r // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if (ma == i) break // 交换两节点 val temp = nums[i] nums[i] = nums[ma] nums[ma] = temp // 循环向下堆化 i = ma } } /* 堆排序 */ fun heapSort(nums: IntArray) { // 建堆操作:堆化除叶节点以外的其他所有节点 for (i in nums.size / 2 - 1 downTo 0) { siftDown(nums, nums.size, i) } // 从堆中提取最大元素,循环 n-1 轮 for (i in nums.size - 1 downTo 1) { // 交换根节点与最右叶节点(交换首元素与尾元素) val temp = nums[0] nums[0] = nums[i] nums[i] = temp // 以根节点为起点,从顶至底进行堆化 siftDown(nums, i, 0) } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) heapSort(nums) println("堆排序完成后 nums = ${nums.contentToString()}") } ================================================ FILE: codes/kotlin/chapter_sorting/insertion_sort.kt ================================================ /** * File: insertion_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 插入排序 */ fun insertionSort(nums: IntArray) { //外循环: 已排序元素为 1, 2, ..., n for (i in nums.indices) { val base = nums[i] var j = i - 1 // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j] // 将 nums[j] 向右移动一位 j-- } nums[j + 1] = base // 将 base 赋值到正确位置 } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) insertionSort(nums) println("插入排序完成后 nums = ${nums.contentToString()}") } ================================================ FILE: codes/kotlin/chapter_sorting/merge_sort.kt ================================================ /** * File: merge_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 合并左子数组和右子数组 */ fun merge(nums: IntArray, left: Int, mid: Int, right: Int) { // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] // 创建一个临时数组 tmp ,用于存放合并后的结果 val tmp = IntArray(right - left + 1) // 初始化左子数组和右子数组的起始索引 var i = left var j = mid + 1 var k = 0 // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++] else tmp[k++] = nums[j++] } // 将左子数组和右子数组的剩余元素复制到临时数组中 while (i <= mid) { tmp[k++] = nums[i++] } while (j <= right) { tmp[k++] = nums[j++] } // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 for (l in tmp.indices) { nums[left + l] = tmp[l] } } /* 归并排序 */ fun mergeSort(nums: IntArray, left: Int, right: Int) { // 终止条件 if (left >= right) return // 当子数组长度为 1 时终止递归 // 划分阶段 val mid = left + (right - left) / 2 // 计算中点 mergeSort(nums, left, mid) // 递归左子数组 mergeSort(nums, mid + 1, right) // 递归右子数组 // 合并阶段 merge(nums, left, mid, right) } /* Driver Code */ fun main() { /* 归并排序 */ val nums = intArrayOf(7, 3, 2, 6, 0, 1, 5, 4) mergeSort(nums, 0, nums.size - 1) println("归并排序完成后 nums = ${nums.contentToString()}") } ================================================ FILE: codes/kotlin/chapter_sorting/quick_sort.kt ================================================ /** * File: quick_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 元素交换 */ fun swap(nums: IntArray, i: Int, j: Int) { val temp = nums[i] nums[i] = nums[j] nums[j] = temp } /* 哨兵划分 */ fun partition(nums: IntArray, left: Int, right: Int): Int { // 以 nums[left] 为基准数 var i = left var j = right while (i < j) { while (i < j && nums[j] >= nums[left]) j-- // 从右向左找首个小于基准数的元素 while (i < j && nums[i] <= nums[left]) i++ // 从左向右找首个大于基准数的元素 swap(nums, i, j) // 交换这两个元素 } swap(nums, i, left) // 将基准数交换至两子数组的分界线 return i // 返回基准数的索引 } /* 快速排序 */ fun quickSort(nums: IntArray, left: Int, right: Int) { // 子数组长度为 1 时终止递归 if (left >= right) return // 哨兵划分 val pivot = partition(nums, left, right) // 递归左子数组、右子数组 quickSort(nums, left, pivot - 1) quickSort(nums, pivot + 1, right) } /* 选取三个候选元素的中位数 */ fun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int { val l = nums[left] val m = nums[mid] val r = nums[right] if ((m in l..r) || (m in r..l)) return mid // m 在 l 和 r 之间 if ((l in m..r) || (l in r..m)) return left // l 在 m 和 r 之间 return right } /* 哨兵划分(三数取中值) */ fun partitionMedian(nums: IntArray, left: Int, right: Int): Int { // 选取三个候选元素的中位数 val med = medianThree(nums, left, (left + right) / 2, right) // 将中位数交换至数组最左端 swap(nums, left, med) // 以 nums[left] 为基准数 var i = left var j = right while (i < j) { while (i < j && nums[j] >= nums[left]) j-- // 从右向左找首个小于基准数的元素 while (i < j && nums[i] <= nums[left]) i++ // 从左向右找首个大于基准数的元素 swap(nums, i, j) // 交换这两个元素 } swap(nums, i, left) // 将基准数交换至两子数组的分界线 return i // 返回基准数的索引 } /* 快速排序 */ fun quickSortMedian(nums: IntArray, left: Int, right: Int) { // 子数组长度为 1 时终止递归 if (left >= right) return // 哨兵划分 val pivot = partitionMedian(nums, left, right) // 递归左子数组、右子数组 quickSort(nums, left, pivot - 1) quickSort(nums, pivot + 1, right) } /* 快速排序(递归深度优化) */ fun quickSortTailCall(nums: IntArray, left: Int, right: Int) { // 子数组长度为 1 时终止 var l = left var r = right while (l < r) { // 哨兵划分操作 val pivot = partition(nums, l, r) // 对两个子数组中较短的那个执行快速排序 if (pivot - l < r - pivot) { quickSort(nums, l, pivot - 1) // 递归排序左子数组 l = pivot + 1 // 剩余未排序区间为 [pivot + 1, right] } else { quickSort(nums, pivot + 1, r) // 递归排序右子数组 r = pivot - 1 // 剩余未排序区间为 [left, pivot - 1] } } } /* Driver Code */ fun main() { /* 快速排序 */ val nums = intArrayOf(2, 4, 1, 0, 3, 5) quickSort(nums, 0, nums.size - 1) println("快速排序完成后 nums = ${nums.contentToString()}") /* 快速排序(中位基准数优化) */ val nums1 = intArrayOf(2, 4, 1, 0, 3, 5) quickSortMedian(nums1, 0, nums1.size - 1) println("快速排序(中位基准数优化)完成后 nums1 = ${nums1.contentToString()}") /* 快速排序(递归深度优化) */ val nums2 = intArrayOf(2, 4, 1, 0, 3, 5) quickSortTailCall(nums2, 0, nums2.size - 1) println("快速排序(递归深度优化)完成后 nums2 = ${nums2.contentToString()}") } ================================================ FILE: codes/kotlin/chapter_sorting/radix_sort.kt ================================================ /** * File: radix_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ fun digit(num: Int, exp: Int): Int { // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 return (num / exp) % 10 } /* 计数排序(根据 nums 第 k 位排序) */ fun countingSortDigit(nums: IntArray, exp: Int) { // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 val counter = IntArray(10) val n = nums.size // 统计 0~9 各数字的出现次数 for (i in 0.. m) m = num var exp = 1 // 按照从低位到高位的顺序遍历 while (exp <= m) { // 对数组元素的第 k 位执行计数排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums, exp) exp *= 10 } } /* Driver Code */ fun main() { // 基数排序 val nums = intArrayOf( 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ) radixSort(nums) println("基数排序完成后 nums = ${nums.contentToString()}") } ================================================ FILE: codes/kotlin/chapter_sorting/selection_sort.kt ================================================ /** * File: selection_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 选择排序 */ fun selectionSort(nums: IntArray) { val n = nums.size // 外循环:未排序区间为 [i, n-1] for (i in 0..() /* 获取栈的长度 */ fun size(): Int { return stack.size } /* 判断栈是否为空 */ fun isEmpty(): Boolean { return size() == 0 } /* 入栈 */ fun push(num: Int) { stack.add(num) } /* 出栈 */ fun pop(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return stack.removeAt(size() - 1) } /* 访问栈顶元素 */ fun peek(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return stack[size() - 1] } /* 将 List 转化为 Array 并返回 */ fun toArray(): Array { return stack.toTypedArray() } } /* Driver Code */ fun main() { /* 初始化栈 */ val stack = ArrayStack() /* 元素入栈 */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("栈 stack = ${stack.toArray().contentToString()}") /* 访问栈顶元素 */ val peek = stack.peek() println("栈顶元素 peek = $peek") /* 元素出栈 */ val pop = stack.pop() println("出栈元素 pop = $pop,出栈后 stack = ${stack.toArray().contentToString()}") /* 获取栈的长度 */ val size = stack.size() println("栈的长度 size = $size") /* 判断是否为空 */ val isEmpty = stack.isEmpty() println("栈是否为空 = $isEmpty") } ================================================ FILE: codes/kotlin/chapter_stack_and_queue/deque.kt ================================================ /** * File: deque.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* 初始化双向队列 */ val deque = LinkedList() deque.offerLast(3) deque.offerLast(2) deque.offerLast(5) println("双向队列 deque = $deque") /* 访问元素 */ val peekFirst = deque.peekFirst() println("队首元素 peekFirst = $peekFirst") val peekLast = deque.peekLast() println("队尾元素 peekLast = $peekLast") /* 元素入队 */ deque.offerLast(4) println("元素 4 队尾入队后 deque = $deque") deque.offerFirst(1) println("元素 1 队首入队后 deque = $deque") /* 元素出队 */ val popLast = deque.pollLast() println("队尾出队元素 = $popLast,队尾出队后 deque = $deque") val popFirst = deque.pollFirst() println("队首出队元素 = $popFirst,队首出队后 deque = $deque") /* 获取双向队列的长度 */ val size = deque.size println("双向队列长度 size = $size") /* 判断双向队列是否为空 */ val isEmpty = deque.isEmpty() println("双向队列是否为空 = $isEmpty") } ================================================ FILE: codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt ================================================ /** * File: linkedlist_deque.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* 双向链表节点 */ class ListNode(var _val: Int) { // 节点值 var next: ListNode? = null // 后继节点引用 var prev: ListNode? = null // 前驱节点引用 } /* 基于双向链表实现的双向队列 */ class LinkedListDeque { private var front: ListNode? = null // 头节点 front private var rear: ListNode? = null // 尾节点 rear private var queSize: Int = 0 // 双向队列的长度 /* 获取双向队列的长度 */ fun size(): Int { return queSize } /* 判断双向队列是否为空 */ fun isEmpty(): Boolean { return size() == 0 } /* 入队操作 */ fun push(num: Int, isFront: Boolean) { val node = ListNode(num) // 若链表为空,则令 front 和 rear 都指向 node if (isEmpty()) { rear = node front = rear // 队首入队操作 } else if (isFront) { // 将 node 添加至链表头部 front?.prev = node node.next = front front = node // 更新头节点 // 队尾入队操作 } else { // 将 node 添加至链表尾部 rear?.next = node node.prev = rear rear = node // 更新尾节点 } queSize++ // 更新队列长度 } /* 队首入队 */ fun pushFirst(num: Int) { push(num, true) } /* 队尾入队 */ fun pushLast(num: Int) { push(num, false) } /* 出队操作 */ fun pop(isFront: Boolean): Int { if (isEmpty()) throw IndexOutOfBoundsException() val _val: Int // 队首出队操作 if (isFront) { _val = front!!._val // 暂存头节点值 // 删除头节点 val fNext = front!!.next if (fNext != null) { fNext.prev = null front!!.next = null } front = fNext // 更新头节点 // 队尾出队操作 } else { _val = rear!!._val // 暂存尾节点值 // 删除尾节点 val rPrev = rear!!.prev if (rPrev != null) { rPrev.next = null rear!!.prev = null } rear = rPrev // 更新尾节点 } queSize-- // 更新队列长度 return _val } /* 队首出队 */ fun popFirst(): Int { return pop(true) } /* 队尾出队 */ fun popLast(): Int { return pop(false) } /* 访问队首元素 */ fun peekFirst(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return front!!._val } /* 访问队尾元素 */ fun peekLast(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return rear!!._val } /* 返回数组用于打印 */ fun toArray(): IntArray { var node = front val res = IntArray(size()) for (i in res.indices) { res[i] = node!!._val node = node.next } return res } } /* Driver Code */ fun main() { /* 初始化双向队列 */ val deque = LinkedListDeque() deque.pushLast(3) deque.pushLast(2) deque.pushLast(5) println("双向队列 deque = ${deque.toArray().contentToString()}") /* 访问元素 */ val peekFirst = deque.peekFirst() println("队首元素 peekFirst = $peekFirst") val peekLast = deque.peekLast() println("队尾元素 peekLast = $peekLast") /* 元素入队 */ deque.pushLast(4) println("元素 4 队尾入队后 deque = ${deque.toArray().contentToString()}") deque.pushFirst(1) println("元素 1 队首入队后 deque = ${deque.toArray().contentToString()}") /* 元素出队 */ val popLast = deque.popLast() println("队尾出队元素 = ${popLast},队尾出队后 deque = ${deque.toArray().contentToString()}") val popFirst = deque.popFirst() println("队首出队元素 = ${popFirst},队首出队后 deque = ${deque.toArray().contentToString()}") /* 获取双向队列的长度 */ val size = deque.size() println("双向队列长度 size = $size") /* 判断双向队列是否为空 */ val isEmpty = deque.isEmpty() println("双向队列是否为空 = $isEmpty") } ================================================ FILE: codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt ================================================ /** * File: linkedlist_queue.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* 基于链表实现的队列 */ class LinkedListQueue( // 头节点 front ,尾节点 rear private var front: ListNode? = null, private var rear: ListNode? = null, private var queSize: Int = 0 ) { /* 获取队列的长度 */ fun size(): Int { return queSize } /* 判断队列是否为空 */ fun isEmpty(): Boolean { return size() == 0 } /* 入队 */ fun push(num: Int) { // 在尾节点后添加 num val node = ListNode(num) // 如果队列为空,则令头、尾节点都指向该节点 if (front == null) { front = node rear = node // 如果队列不为空,则将该节点添加到尾节点后 } else { rear?.next = node rear = node } queSize++ } /* 出队 */ fun pop(): Int { val num = peek() // 删除头节点 front = front?.next queSize-- return num } /* 访问队首元素 */ fun peek(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return front!!._val } /* 将链表转化为 Array 并返回 */ fun toArray(): IntArray { var node = front val res = IntArray(size()) for (i in res.indices) { res[i] = node!!._val node = node.next } return res } } /* Driver Code */ fun main() { /* 初始化队列 */ val queue = LinkedListQueue() /* 元素入队 */ queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) println("队列 queue = ${queue.toArray().contentToString()}") /* 访问队首元素 */ val peek = queue.peek() println("队首元素 peek = $peek") /* 元素出队 */ val pop = queue.pop() println("出队元素 pop = $pop,出队后 queue = ${queue.toArray().contentToString()}") /* 获取队列的长度 */ val size = queue.size() println("队列长度 size = $size") /* 判断队列是否为空 */ val isEmpty = queue.isEmpty() println("队列是否为空 = $isEmpty") } ================================================ FILE: codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt ================================================ /** * File: linkedlist_stack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* 基于链表实现的栈 */ class LinkedListStack( private var stackPeek: ListNode? = null, // 将头节点作为栈顶 private var stkSize: Int = 0 // 栈的长度 ) { /* 获取栈的长度 */ fun size(): Int { return stkSize } /* 判断栈是否为空 */ fun isEmpty(): Boolean { return size() == 0 } /* 入栈 */ fun push(num: Int) { val node = ListNode(num) node.next = stackPeek stackPeek = node stkSize++ } /* 出栈 */ fun pop(): Int? { val num = peek() stackPeek = stackPeek?.next stkSize-- return num } /* 访问栈顶元素 */ fun peek(): Int? { if (isEmpty()) throw IndexOutOfBoundsException() return stackPeek?._val } /* 将 List 转化为 Array 并返回 */ fun toArray(): IntArray { var node = stackPeek val res = IntArray(size()) for (i in res.size - 1 downTo 0) { res[i] = node?._val!! node = node.next } return res } } /* Driver Code */ fun main() { /* 初始化栈 */ val stack = LinkedListStack() /* 元素入栈 */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("栈 stack = ${stack.toArray().contentToString()}") /* 访问栈顶元素 */ val peek = stack.peek()!! println("栈顶元素 peek = $peek") /* 元素出栈 */ val pop = stack.pop()!! println("出栈元素 pop = $pop,出栈后 stack = ${stack.toArray().contentToString()}") /* 获取栈的长度 */ val size = stack.size() println("栈的长度 size = $size") /* 判断是否为空 */ val isEmpty = stack.isEmpty() println("栈是否为空 = $isEmpty") } ================================================ FILE: codes/kotlin/chapter_stack_and_queue/queue.kt ================================================ /** * File: queue.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* 初始化队列 */ val queue = LinkedList() /* 元素入队 */ queue.offer(1) queue.offer(3) queue.offer(2) queue.offer(5) queue.offer(4) println("队列 queue = $queue") /* 访问队首元素 */ val peek = queue.peek() println("队首元素 peek = $peek") /* 元素出队 */ val pop = queue.poll() println("出队元素 pop = $pop,出队后 queue = $queue") /* 获取队列的长度 */ val size = queue.size println("队列长度 size = $size") /* 判断队列是否为空 */ val isEmpty = queue.isEmpty() println("队列是否为空 = $isEmpty") } ================================================ FILE: codes/kotlin/chapter_stack_and_queue/stack.kt ================================================ /** * File: stack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* 初始化栈 */ val stack = Stack() /* 元素入栈 */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("栈 stack = $stack") /* 访问栈顶元素 */ val peek = stack.peek() println("栈顶元素 peek = $peek") /* 元素出栈 */ val pop = stack.pop() println("出栈元素 pop = $pop,出栈后 stack = $stack") /* 获取栈的长度 */ val size = stack.size println("栈的长度 size = $size") /* 判断是否为空 */ val isEmpty = stack.isEmpty() println("栈是否为空 = $isEmpty") } ================================================ FILE: codes/kotlin/chapter_tree/array_binary_tree.kt ================================================ /** * File: array_binary_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* 数组表示下的二叉树类 */ class ArrayBinaryTree(private val tree: MutableList) { /* 列表容量 */ fun size(): Int { return tree.size } /* 获取索引为 i 节点的值 */ fun _val(i: Int): Int? { // 若索引越界,则返回 null ,代表空位 if (i < 0 || i >= size()) return null return tree[i] } /* 获取索引为 i 节点的左子节点的索引 */ fun left(i: Int): Int { return 2 * i + 1 } /* 获取索引为 i 节点的右子节点的索引 */ fun right(i: Int): Int { return 2 * i + 2 } /* 获取索引为 i 节点的父节点的索引 */ fun parent(i: Int): Int { return (i - 1) / 2 } /* 层序遍历 */ fun levelOrder(): MutableList { val res = mutableListOf() // 直接遍历数组 for (i in 0..) { // 若为空位,则返回 if (_val(i) == null) return // 前序遍历 if ("pre" == order) res.add(_val(i)) dfs(left(i), order, res) // 中序遍历 if ("in" == order) res.add(_val(i)) dfs(right(i), order, res) // 后序遍历 if ("post" == order) res.add(_val(i)) } /* 前序遍历 */ fun preOrder(): MutableList { val res = mutableListOf() dfs(0, "pre", res) return res } /* 中序遍历 */ fun inOrder(): MutableList { val res = mutableListOf() dfs(0, "in", res) return res } /* 后序遍历 */ fun postOrder(): MutableList { val res = mutableListOf() dfs(0, "post", res) return res } } /* Driver Code */ fun main() { // 初始化二叉树 // 这里借助了一个从列表直接生成二叉树的函数 val arr = mutableListOf(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15) val root = TreeNode.listToTree(arr) println("\n初始化二叉树\n") println("二叉树的数组表示:") println(arr) println("二叉树的链表表示:") printTree(root) // 数组表示下的二叉树类 val abt = ArrayBinaryTree(arr) // 访问节点 val i = 1 val l = abt.left(i) val r = abt.right(i) val p = abt.parent(i) println("当前节点的索引为 $i ,值为 ${abt._val(i)}") println("其左子节点的索引为 $l ,值为 ${abt._val(l)}") println("其右子节点的索引为 $r ,值为 ${abt._val(r)}") println("其父节点的索引为 $p ,值为 ${abt._val(p)}") // 遍历树 var res = abt.levelOrder() println("\n层序遍历为:$res") res = abt.preOrder() println("前序遍历为:$res") res = abt.inOrder() println("中序遍历为:$res") res = abt.postOrder() println("后序遍历为:$res") } ================================================ FILE: codes/kotlin/chapter_tree/avl_tree.kt ================================================ /** * File: avl_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree import kotlin.math.max /* AVL 树 */ class AVLTree { var root: TreeNode? = null // 根节点 /* 获取节点高度 */ fun height(node: TreeNode?): Int { // 空节点高度为 -1 ,叶节点高度为 0 return node?.height ?: -1 } /* 更新节点高度 */ private fun updateHeight(node: TreeNode?) { // 节点高度等于最高子树高度 + 1 node?.height = max(height(node?.left), height(node?.right)) + 1 } /* 获取平衡因子 */ fun balanceFactor(node: TreeNode?): Int { // 空节点平衡因子为 0 if (node == null) return 0 // 节点平衡因子 = 左子树高度 - 右子树高度 return height(node.left) - height(node.right) } /* 右旋操作 */ private fun rightRotate(node: TreeNode?): TreeNode { val child = node!!.left val grandChild = child!!.right // 以 child 为原点,将 node 向右旋转 child.right = node node.left = grandChild // 更新节点高度 updateHeight(node) updateHeight(child) // 返回旋转后子树的根节点 return child } /* 左旋操作 */ private fun leftRotate(node: TreeNode?): TreeNode { val child = node!!.right val grandChild = child!!.left // 以 child 为原点,将 node 向左旋转 child.left = node node.right = grandChild // 更新节点高度 updateHeight(node) updateHeight(child) // 返回旋转后子树的根节点 return child } /* 执行旋转操作,使该子树重新恢复平衡 */ private fun rotate(node: TreeNode): TreeNode { // 获取节点 node 的平衡因子 val balanceFactor = balanceFactor(node) // 左偏树 if (balanceFactor > 1) { if (balanceFactor(node.left) >= 0) { // 右旋 return rightRotate(node) } else { // 先左旋后右旋 node.left = leftRotate(node.left) return rightRotate(node) } } // 右偏树 if (balanceFactor < -1) { if (balanceFactor(node.right) <= 0) { // 左旋 return leftRotate(node) } else { // 先右旋后左旋 node.right = rightRotate(node.right) return leftRotate(node) } } // 平衡树,无须旋转,直接返回 return node } /* 插入节点 */ fun insert(_val: Int) { root = insertHelper(root, _val) } /* 递归插入节点(辅助方法) */ private fun insertHelper(n: TreeNode?, _val: Int): TreeNode { if (n == null) return TreeNode(_val) var node = n /* 1. 查找插入位置并插入节点 */ if (_val < node._val) node.left = insertHelper(node.left, _val) else if (_val > node._val) node.right = insertHelper(node.right, _val) else return node // 重复节点不插入,直接返回 updateHeight(node) // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = rotate(node) // 返回子树的根节点 return node } /* 删除节点 */ fun remove(_val: Int) { root = removeHelper(root, _val) } /* 递归删除节点(辅助方法) */ private fun removeHelper(n: TreeNode?, _val: Int): TreeNode? { var node = n ?: return null /* 1. 查找节点并删除 */ if (_val < node._val) node.left = removeHelper(node.left, _val) else if (_val > node._val) node.right = removeHelper(node.right, _val) else { if (node.left == null || node.right == null) { val child = if (node.left != null) node.left else node.right // 子节点数量 = 0 ,直接删除 node 并返回 if (child == null) return null // 子节点数量 = 1 ,直接删除 node else node = child } else { // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 var temp = node.right while (temp!!.left != null) { temp = temp.left } node.right = removeHelper(node.right, temp._val) node._val = temp._val } } updateHeight(node) // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = rotate(node) // 返回子树的根节点 return node } /* 查找节点 */ fun search(_val: Int): TreeNode? { var cur = root // 循环查找,越过叶节点后跳出 while (cur != null) { // 目标节点在 cur 的右子树中 cur = if (cur._val < _val) cur.right!! // 目标节点在 cur 的左子树中 else if (cur._val > _val) cur.left // 找到目标节点,跳出循环 else break } // 返回目标节点 return cur } } fun testInsert(tree: AVLTree, _val: Int) { tree.insert(_val) println("\n插入节点 $_val 后,AVL 树为") printTree(tree.root) } fun testRemove(tree: AVLTree, _val: Int) { tree.remove(_val) println("\n删除节点 $_val 后,AVL 树为") printTree(tree.root) } /* Driver Code */ fun main() { /* 初始化空 AVL 树 */ val avlTree = AVLTree() /* 插入节点 */ // 请关注插入节点后,AVL 树是如何保持平衡的 testInsert(avlTree, 1) testInsert(avlTree, 2) testInsert(avlTree, 3) testInsert(avlTree, 4) testInsert(avlTree, 5) testInsert(avlTree, 8) testInsert(avlTree, 7) testInsert(avlTree, 9) testInsert(avlTree, 10) testInsert(avlTree, 6) /* 插入重复节点 */ testInsert(avlTree, 7) /* 删除节点 */ // 请关注删除节点后,AVL 树是如何保持平衡的 testRemove(avlTree, 8) // 删除度为 0 的节点 testRemove(avlTree, 5) // 删除度为 1 的节点 testRemove(avlTree, 4) // 删除度为 2 的节点 /* 查询节点 */ val node = avlTree.search(7) println("\n 查找到的节点对象为 $node,节点值 = ${node?._val}") } ================================================ FILE: codes/kotlin/chapter_tree/binary_search_tree.kt ================================================ /** * File: binary_search_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* 二叉搜索树 */ class BinarySearchTree { // 初始化空树 private var root: TreeNode? = null /* 获取二叉树根节点 */ fun getRoot(): TreeNode? { return root } /* 查找节点 */ fun search(num: Int): TreeNode? { var cur = root // 循环查找,越过叶节点后跳出 while (cur != null) { // 目标节点在 cur 的右子树中 cur = if (cur._val < num) cur.right // 目标节点在 cur 的左子树中 else if (cur._val > num) cur.left // 找到目标节点,跳出循环 else break } // 返回目标节点 return cur } /* 插入节点 */ fun insert(num: Int) { // 若树为空,则初始化根节点 if (root == null) { root = TreeNode(num) return } var cur = root var pre: TreeNode? = null // 循环查找,越过叶节点后跳出 while (cur != null) { // 找到重复节点,直接返回 if (cur._val == num) return pre = cur // 插入位置在 cur 的右子树中 cur = if (cur._val < num) cur.right // 插入位置在 cur 的左子树中 else cur.left } // 插入节点 val node = TreeNode(num) if (pre?._val!! < num) pre.right = node else pre.left = node } /* 删除节点 */ fun remove(num: Int) { // 若树为空,直接提前返回 if (root == null) return var cur = root var pre: TreeNode? = null // 循环查找,越过叶节点后跳出 while (cur != null) { // 找到待删除节点,跳出循环 if (cur._val == num) break pre = cur // 待删除节点在 cur 的右子树中 cur = if (cur._val < num) cur.right // 待删除节点在 cur 的左子树中 else cur.left } // 若无待删除节点,则直接返回 if (cur == null) return // 子节点数量 = 0 or 1 if (cur.left == null || cur.right == null) { // 当子节点数量 = 0 / 1 时, child = null / 该子节点 val child = if (cur.left != null) cur.left else cur.right // 删除节点 cur if (cur != root) { if (pre!!.left == cur) pre.left = child else pre.right = child } else { // 若删除节点为根节点,则重新指定根节点 root = child } // 子节点数量 = 2 } else { // 获取中序遍历中 cur 的下一个节点 var tmp = cur.right while (tmp!!.left != null) { tmp = tmp.left } // 递归删除节点 tmp remove(tmp._val) // 用 tmp 覆盖 cur cur._val = tmp._val } } } /* Driver Code */ fun main() { /* 初始化二叉搜索树 */ val bst = BinarySearchTree() // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 val nums = intArrayOf(8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15) for (num in nums) { bst.insert(num) } println("\n初始化的二叉树为\n") printTree(bst.getRoot()) /* 查找节点 */ val node = bst.search(7) println("查找到的节点对象为 $node,节点值 = ${node?._val}") /* 插入节点 */ bst.insert(16) println("\n插入节点 16 后,二叉树为\n") printTree(bst.getRoot()) /* 删除节点 */ bst.remove(1) println("\n删除节点 1 后,二叉树为\n") printTree(bst.getRoot()) bst.remove(2) println("\n删除节点 2 后,二叉树为\n") printTree(bst.getRoot()) bst.remove(4) println("\n删除节点 4 后,二叉树为\n") printTree(bst.getRoot()) } ================================================ FILE: codes/kotlin/chapter_tree/binary_tree.kt ================================================ /** * File: binary_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* Driver Code */ fun main() { /* 初始化二叉树 */ // 初始化节点 val n1 = TreeNode(1) val n2 = TreeNode(2) val n3 = TreeNode(3) val n4 = TreeNode(4) val n5 = TreeNode(5) // 构建节点之间的引用(指针) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 println("\n初始化二叉树\n") printTree(n1) /* 插入与删除节点 */ val P = TreeNode(0) // 在 n1 -> n2 中间插入节点 P n1.left = P P.left = n2 println("\n插入节点 P 后\n") printTree(n1) // 删除节点 P n1.left = n2 println("\n删除节点 P 后\n") printTree(n1) } ================================================ FILE: codes/kotlin/chapter_tree/binary_tree_bfs.kt ================================================ /** * File: binary_tree_bfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree import java.util.* /* 层序遍历 */ fun levelOrder(root: TreeNode?): MutableList { // 初始化队列,加入根节点 val queue = LinkedList() queue.add(root) // 初始化一个列表,用于保存遍历序列 val list = mutableListOf() while (queue.isNotEmpty()) { val node = queue.poll() // 队列出队 list.add(node?._val!!) // 保存节点值 if (node.left != null) queue.offer(node.left) // 左子节点入队 if (node.right != null) queue.offer(node.right) // 右子节点入队 } return list } /* Driver Code */ fun main() { /* 初始化二叉树 */ // 这里借助了一个从列表直接生成二叉树的函数 val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) println("\n初始化二叉树\n") printTree(root) /* 层序遍历 */ val list = levelOrder(root) println("\n层序遍历的节点打印序列 = $list") } ================================================ FILE: codes/kotlin/chapter_tree/binary_tree_dfs.kt ================================================ /** * File: binary_tree_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree // 初始化列表,用于存储遍历序列 var list = mutableListOf() /* 前序遍历 */ fun preOrder(root: TreeNode?) { if (root == null) return // 访问优先级:根节点 -> 左子树 -> 右子树 list.add(root._val) preOrder(root.left) preOrder(root.right) } /* 中序遍历 */ fun inOrder(root: TreeNode?) { if (root == null) return // 访问优先级:左子树 -> 根节点 -> 右子树 inOrder(root.left) list.add(root._val) inOrder(root.right) } /* 后序遍历 */ fun postOrder(root: TreeNode?) { if (root == null) return // 访问优先级:左子树 -> 右子树 -> 根节点 postOrder(root.left) postOrder(root.right) list.add(root._val) } /* Driver Code */ fun main() { /* 初始化二叉树 */ // 这里借助了一个从列表直接生成二叉树的函数 val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) println("\n初始化二叉树\n") printTree(root) /* 前序遍历 */ list.clear() preOrder(root) println("\n前序遍历的节点打印序列 = $list") /* 中序遍历 */ list.clear() inOrder(root) println("\n中序遍历的节点打印序列 = $list") /* 后序遍历 */ list.clear() postOrder(root) println("\n后序遍历的节点打印序列 = $list") } ================================================ FILE: codes/kotlin/utils/ListNode.kt ================================================ /** * File: ListNode.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* 链表节点 */ class ListNode(var _val: Int) { var next: ListNode? = null companion object { /* 将列表反序列化为链表 */ fun arrToLinkedList(arr: IntArray): ListNode? { val dum = ListNode(0) var head = dum for (_val in arr) { head.next = ListNode(_val) head = head.next!! } return dum.next } } } ================================================ FILE: codes/kotlin/utils/PrintUtil.kt ================================================ /** * File: PrintUtil.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils import java.util.* class Trunk(var prev: Trunk?, var str: String) /* 打印矩阵(Array) */ fun printMatrix(matrix: Array>) { println("[") for (row in matrix) { println(" $row,") } println("]") } /* 打印矩阵(List) */ fun printMatrix(matrix: MutableList>) { println("[") for (row in matrix) { println(" $row,") } println("]") } /* 打印链表 */ fun printLinkedList(h: ListNode?) { var head = h val list = mutableListOf() while (head != null) { list.add(head._val.toString()) head = head.next } println(list.joinToString(separator = " -> ")) } /* 打印二叉树 */ fun printTree(root: TreeNode?) { printTree(root, null, false) } /** * 打印二叉树 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ fun printTree(root: TreeNode?, prev: Trunk?, isRight: Boolean) { if (root == null) { return } var prevStr = " " val trunk = Trunk(prev, prevStr) printTree(root.right, trunk, true) if (prev == null) { trunk.str = "———" } else if (isRight) { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev.str = prevStr } showTrunks(trunk) println(" ${root._val}") if (prev != null) { prev.str = prevStr } trunk.str = " |" printTree(root.left, trunk, false) } fun showTrunks(p: Trunk?) { if (p == null) { return } showTrunks(p.prev) print(p.str) } /* 打印哈希表 */ fun printHashMap(map: Map) { for ((key, value) in map) { println("${key.toString()} -> $value") } } /* 打印堆 */ fun printHeap(queue: Queue?) { val list = mutableListOf() queue?.let { list.addAll(it) } print("堆的数组表示:") println(list) println("堆的树状表示:") val root = TreeNode.listToTree(list) printTree(root) } ================================================ FILE: codes/kotlin/utils/TreeNode.kt ================================================ /** * File: TreeNode.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* 二叉树节点类 */ /* 构造方法 */ class TreeNode( var _val: Int // 节点值 ) { var height: Int = 0 // 节点高度 var left: TreeNode? = null // 左子节点引用 var right: TreeNode? = null // 右子节点引用 // 序列化编码规则请参考: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二叉树的数组表示: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // 二叉树的链表表示: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* 将列表反序列化为二叉树:递归 */ companion object { private fun listToTreeDFS(arr: MutableList, i: Int): TreeNode? { if (i < 0 || i >= arr.size || arr[i] == null) { return null } val root = TreeNode(arr[i]!!) root.left = listToTreeDFS(arr, 2 * i + 1) root.right = listToTreeDFS(arr, 2 * i + 2) return root } /* 将列表反序列化为二叉树 */ fun listToTree(arr: MutableList): TreeNode? { return listToTreeDFS(arr, 0) } /* 将二叉树序列化为列表:递归 */ private fun treeToListDFS(root: TreeNode?, i: Int, res: MutableList) { if (root == null) return while (i >= res.size) { res.add(null) } res[i] = root._val treeToListDFS(root.left, 2 * i + 1, res) treeToListDFS(root.right, 2 * i + 2, res) } /* 将二叉树序列化为列表 */ fun treeToList(root: TreeNode?): MutableList { val res = mutableListOf() treeToListDFS(root, 0, res) return res } } } ================================================ FILE: codes/kotlin/utils/Vertex.kt ================================================ /** * File: Vertex.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* 顶点类 */ class Vertex(val _val: Int) { companion object { /* 输入值列表 vals ,返回顶点列表 vets */ fun valsToVets(vals: IntArray): Array { val vets = arrayOfNulls(vals.size) for (i in vals.indices) { vets[i] = Vertex(vals[i]) } return vets } /* 输入顶点列表 vets ,返回值列表 vals */ fun vetsToVals(vets: MutableList): MutableList { val vals = mutableListOf() for (vet in vets) { vals.add(vet!!._val) } return vals } } } ================================================ FILE: codes/python/.gitignore ================================================ __pycache__ ================================================ FILE: codes/python/chapter_array_and_linkedlist/array.py ================================================ """ 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 是动态数组,可以直接扩展 # 为了方便学习,本函数将 list 看作长度不可变的数组 def extend(nums: list[int], enlarge: int) -> list[int]: """扩展数组长度""" # 初始化一个扩展长度后的数组 res = [0] * (len(nums) + enlarge) # 将原数组中的所有元素复制到新数组 for i in range(len(nums)): res[i] = nums[i] # 返回扩展后的新数组 return res def insert(nums: list[int], num: int, index: int): """在数组的索引 index 处插入元素 num""" # 把索引 index 以及之后的所有元素向后移动一位 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 之后的所有元素向前移动一位 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) ================================================ FILE: codes/python/chapter_array_and_linkedlist/linked_list.py ================================================ """ 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)) ================================================ FILE: codes/python/chapter_array_and_linkedlist/list.py ================================================ """ 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 # 拼接两个列表 nums1 = [6, 8, 7, 10, 9] nums += nums1 print("\n将列表 nums1 拼接到 nums 之后,得到 nums =", nums) # 排序列表 nums.sort() print("\n排序列表后 nums =", nums) ================================================ FILE: codes/python/chapter_array_and_linkedlist/my_list.py ================================================ """ 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("索引越界") return self._arr[index] def set(self, num: int, index: int): """更新元素""" if index < 0 or index >= self._size: raise IndexError("索引越界") 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("索引越界") # 元素数量超出容量时,触发扩容机制 if self._size == self.capacity(): self.extend_capacity() # 将索引 index 以及之后的元素都向后移动一位 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("索引越界") num = self._arr[index] # 将索引 index 之后的元素都向前移动一位 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()}") ================================================ FILE: codes/python/chapter_backtracking/n_queens.py ================================================ """ 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 """Driver Code""" 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) ================================================ FILE: codes/python/chapter_backtracking/permutations_i.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [1, 2, 3] res = permutations_i(nums) print(f"输入数组 nums = {nums}") print(f"所有排列 res = {res}") ================================================ FILE: codes/python/chapter_backtracking/permutations_ii.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [1, 2, 2] res = permutations_ii(nums) print(f"输入数组 nums = {nums}") print(f"所有排列 res = {res}") ================================================ FILE: codes/python/chapter_backtracking/preorder_traversal_i_compact.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n初始化二叉树") print_tree(root) # 前序遍历 res = list[TreeNode]() pre_order(root) print("\n输出所有值为 7 的节点") print([node.val for node in res]) ================================================ FILE: codes/python/chapter_backtracking/preorder_traversal_ii_compact.py ================================================ """ 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() """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n初始化二叉树") print_tree(root) # 前序遍历 path = list[TreeNode]() res = list[list[TreeNode]]() pre_order(root) print("\n输出所有根节点到节点 7 的路径") for path in res: print([node.val for node in path]) ================================================ FILE: codes/python/chapter_backtracking/preorder_traversal_iii_compact.py ================================================ """ 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() """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n初始化二叉树") print_tree(root) # 前序遍历 path = list[TreeNode]() res = list[list[TreeNode]]() pre_order(root) print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") for path in res: print([node.val for node in path]) ================================================ FILE: codes/python/chapter_backtracking/preorder_traversal_iii_template.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n初始化二叉树") print_tree(root) # 回溯算法 res = [] backtrack(state=[], choices=[root], res=res) print("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点") for path in res: print([node.val for node in path]) ================================================ FILE: codes/python/chapter_backtracking/subset_sum_i.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [3, 4, 5] target = 9 res = subset_sum_i(nums, target) print(f"输入数组 nums = {nums}, target = {target}") print(f"所有和等于 {target} 的子集 res = {res}") ================================================ FILE: codes/python/chapter_backtracking/subset_sum_i_naive.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [3, 4, 5] target = 9 res = subset_sum_i_naive(nums, target) print(f"输入数组 nums = {nums}, target = {target}") print(f"所有和等于 {target} 的子集 res = {res}") print(f"请注意,该方法输出的结果包含重复集合") ================================================ FILE: codes/python/chapter_backtracking/subset_sum_ii.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [4, 4, 5] target = 9 res = subset_sum_ii(nums, target) print(f"输入数组 nums = {nums}, target = {target}") print(f"所有和等于 {target} 的子集 res = {res}") ================================================ FILE: codes/python/chapter_computational_complexity/iteration.py ================================================ """ 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 循环(两次更新)""" 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 循环(两次更新)求和结果 res = {res}") res = nested_for_loop(n) print(f"\n双层 for 循环的遍历结果 {res}") ================================================ FILE: codes/python/chapter_computational_complexity/recursion.py ================================================ """ 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}") ================================================ FILE: codes/python/chapter_computational_complexity/space_complexity.py ================================================ """ 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 长度为 n, n-1, ..., 2, 1 nums = [0] * n 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) ================================================ FILE: codes/python/chapter_computational_complexity/time_complexity.py ================================================ """ 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 # 细胞每轮一分为二,形成数列 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 = linear_log_recur(n // 2) + linear_log_recur(n // 2) # 当前子问题包含 n 个操作 for _ in range(n): count += 1 return count def factorial_recur(n: int) -> int: """阶乘阶(递归实现)""" if n == 0: return 1 count = 0 # 从 1 个分裂出 n 个 for _ in range(n): count += factorial_recur(n - 1) return count """Driver Code""" if __name__ == "__main__": # 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 n = 8 print("输入数据大小 n =", n) count = constant(n) print("常数阶的操作数量 =", count) count = linear(n) print("线性阶的操作数量 =", count) count = array_traversal([0] * n) print("线性阶(遍历数组)的操作数量 =", count) count = quadratic(n) print("平方阶的操作数量 =", count) nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1] count = bubble_sort(nums) print("平方阶(冒泡排序)的操作数量 =", count) count = exponential(n) print("指数阶(循环实现)的操作数量 =", count) count = exp_recur(n) print("指数阶(递归实现)的操作数量 =", count) count = logarithmic(n) print("对数阶(循环实现)的操作数量 =", count) count = log_recur(n) print("对数阶(递归实现)的操作数量 =", count) count = linear_log_recur(n) print("线性对数阶(递归实现)的操作数量 =", count) count = factorial_recur(n) print("阶乘阶(递归实现)的操作数量 =", count) ================================================ FILE: codes/python/chapter_computational_complexity/worst_best_time_complexity.py ================================================ """ 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) ================================================ FILE: codes/python/chapter_divide_and_conquer/binary_search_recur.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # 二分查找(双闭区间) index = binary_search(nums, target) print("目标元素 6 的索引 = ", index) ================================================ FILE: codes/python/chapter_divide_and_conquer/build_tree.py ================================================ """ 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 元素到索引的映射 inorder_map = {val: i for i, val in enumerate(inorder)} root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) return root """Driver Code""" if __name__ == "__main__": preorder = [3, 9, 2, 1, 7] inorder = [9, 3, 1, 2, 7] print(f"前序遍历 = {preorder}") print(f"中序遍历 = {inorder}") root = build_tree(preorder, inorder) print("构建的二叉树为:") print_tree(root) ================================================ FILE: codes/python/chapter_divide_and_conquer/hanota.py ================================================ """ 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 只剩下一个圆盘,则直接将其移到 tar if i == 1: move(src, tar) return # 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf dfs(i - 1, src, tar, buf) # 子问题 f(1) :将 src 剩余一个圆盘移到 tar move(src, tar) # 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar dfs(i - 1, buf, src, tar) def solve_hanota(A: list[int], B: list[int], C: list[int]): """求解汉诺塔问题""" n = len(A) # 将 A 顶部 n 个圆盘借助 B 移到 C dfs(n, A, B, C) """Driver Code""" 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}") ================================================ FILE: codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_backtrack(n) print(f"爬 {n} 阶楼梯共有 {res} 种方案") ================================================ FILE: codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_constraint_dp(n) print(f"爬 {n} 阶楼梯共有 {res} 种方案") ================================================ FILE: codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dfs(n) print(f"爬 {n} 阶楼梯共有 {res} 种方案") ================================================ FILE: codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dfs_mem(n) print(f"爬 {n} 阶楼梯共有 {res} 种方案") ================================================ FILE: codes/python/chapter_dynamic_programming/climbing_stairs_dp.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dp(n) print(f"爬 {n} 阶楼梯共有 {res} 种方案") res = climbing_stairs_dp_comp(n) print(f"爬 {n} 阶楼梯共有 {res} 种方案") ================================================ FILE: codes/python/chapter_dynamic_programming/coin_change.py ================================================ """ 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 """Driver Code""" 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}") ================================================ FILE: codes/python/chapter_dynamic_programming/coin_change_ii.py ================================================ """ 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] """Driver Code""" 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}") ================================================ FILE: codes/python/chapter_dynamic_programming/edit_distance.py ================================================ """ File: edit_distancde.py Created Time: 2023-07-04 Author: krahets (krahets@163.com) """ def edit_distance_dfs(s: str, t: str, i: int, j: int) -> int: """编辑距离:暴力搜索""" # 若 s 和 t 都为空,则返回 0 if i == 0 and j == 0: return 0 # 若 s 为空,则返回 t 长度 if i == 0: return j # 若 t 为空,则返回 s 长度 if j == 0: return i # 若两字符相等,则直接跳过此两字符 if s[i - 1] == t[j - 1]: return edit_distance_dfs(s, t, i - 1, j - 1) # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 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] # 若两字符相等,则直接跳过此两字符 if s[i - 1] == t[j - 1]: return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 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]: # 若两字符相等,则直接跳过此两字符 dp[i][j] = dp[i - 1][j - 1] else: # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 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]: # 若两字符相等,则直接跳过此两字符 dp[j] = leftup else: # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[j] = min(dp[j - 1], dp[j], leftup) + 1 leftup = temp # 更新为下一轮的 dp[i-1, j-1] return dp[m] """Driver Code""" if __name__ == "__main__": s = "bag" t = "pack" n, m = len(s), len(t) # 暴力搜索 res = edit_distance_dfs(s, t, n, m) print(f"将 {s} 更改为 {t} 最少需要编辑 {res} 步") # 记忆化搜索 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} 步") ================================================ FILE: codes/python/chapter_dynamic_programming/knapsack.py ================================================ """ 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] # 返回两种方案中价值更大的那一个 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] # 记录并返回两种方案中价值更大的那一个 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] """Driver Code""" if __name__ == "__main__": wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = len(wgt) # 暴力搜索 res = knapsack_dfs(wgt, val, n, cap) print(f"不超过背包容量的最大物品价值为 {res}") # 记忆化搜索 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}") ================================================ FILE: codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py ================================================ """ 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 """Driver Code""" 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}") ================================================ FILE: codes/python/chapter_dynamic_programming/min_path_sum.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] n, m = len(grid), len(grid[0]) # 暴力搜索 res = min_path_sum_dfs(grid, n - 1, m - 1) print(f"从左上角到右下角的最小路径和为 {res}") # 记忆化搜索 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}") ================================================ FILE: codes/python/chapter_dynamic_programming/unbounded_knapsack.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": wgt = [1, 2, 3] val = [5, 11, 15] cap = 4 # 动态规划 res = unbounded_knapsack_dp(wgt, val, cap) print(f"不超过背包容量的最大物品价值为 {res}") # 空间优化后的动态规划 res = unbounded_knapsack_dp_comp(wgt, val, cap) print(f"不超过背包容量的最大物品价值为 {res}") ================================================ FILE: codes/python/chapter_graph/graph_adjacency_list.py ================================================ """ 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]]): """构造方法""" # 邻接表,key:顶点,value:该顶点的所有邻接顶点 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},") """Driver Code""" 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() ================================================ FILE: codes/python/chapter_graph/graph_adjacency_matrix.py ================================================ """ 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 元素代表顶点索引,即对应 vertices 元素索引 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 对应 vertices 元素索引 # 索引越界与相等处理 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 对应 vertices 元素索引 # 索引越界与相等处理 if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: raise IndexError() self.adj_mat[i][j] = 0 self.adj_mat[j][i] = 0 def print(self): """打印邻接矩阵""" print("顶点列表 =", self.vertices) print("邻接矩阵 =") print_matrix(self.adj_mat) """Driver Code""" if __name__ == "__main__": # 初始化无向图 # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 vertices = [1, 3, 2, 5, 4] edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] graph = GraphAdjMat(vertices, edges) print("\n初始化后,图为") graph.print() # 添加边 # 顶点 1, 2 的索引分别为 0, 2 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() ================================================ FILE: codes/python/chapter_graph/graph_bfs.py ================================================ """ 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 """Driver Code""" 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)) ================================================ FILE: codes/python/chapter_graph/graph_dfs.py ================================================ """ 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 """Driver Code""" 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)) ================================================ FILE: codes/python/chapter_greedy/coin_change_greedy.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # 贪心:能够保证找到全局最优解 coins = [1, 5, 10, 20, 50, 100] amt = 186 res = coin_change_greedy(coins, amt) print(f"\ncoins = {coins}, amt = {amt}") print(f"凑到 {amt} 所需的最少硬币数量为 {res}") # 贪心:无法保证找到全局最优解 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") ================================================ FILE: codes/python/chapter_greedy/fractional_knapsack.py ================================================ """ 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: """分数背包:贪心""" # 创建物品列表,包含两个属性:重量、价值 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 """Driver Code""" if __name__ == "__main__": wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = len(wgt) # 贪心算法 res = fractional_knapsack(wgt, val, cap) print(f"不超过背包容量的最大物品价值为 {res}") ================================================ FILE: codes/python/chapter_greedy/max_capacity.py ================================================ """ 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 # 循环贪心选择,直至两板相遇 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 """Driver Code""" if __name__ == "__main__": ht = [3, 8, 5, 2, 7, 7, 3, 4] # 贪心算法 res = max_capacity(ht) print(f"最大容量为 {res}") ================================================ FILE: codes/python/chapter_greedy/max_product_cutting.py ================================================ """ 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)) """Driver Code""" if __name__ == "__main__": n = 58 # 贪心算法 res = max_product_cutting(n) print(f"最大切分乘积为 {res}") ================================================ FILE: codes/python/chapter_hashing/array_hash_map.py ================================================ """ 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 | None: """查询操作""" 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, "小哈") hmap.put(15937, "小啰") hmap.put(16750, "小算") hmap.put(13276, "小法") hmap.put(10583, "小鸭") print("\n添加完成后,哈希表为\nKey -> Value") hmap.print() # 查询操作 # 向哈希表中输入键 key ,得到值 value name = hmap.get(15937) print("\n输入学号 15937 ,查询到姓名 " + name) # 删除操作 # 在哈希表中删除键值对 (key, value) hmap.remove(10583) print("\n删除 10583 后,哈希表为\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) ================================================ FILE: codes/python/chapter_hashing/built_in_hash.py ================================================ """ 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 算法" hash_str = hash(str) print(f"字符串 {str} 的哈希值为 {hash_str}") tup = (12836, "小哈") hash_tup = hash(tup) print(f"元组 {tup} 的哈希值为 {hash(hash_tup)}") obj = ListNode(0) hash_obj = hash(obj) print(f"节点对象 {obj} 的哈希值为 {hash_obj}") ================================================ FILE: codes/python/chapter_hashing/hash_map.py ================================================ """ 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] = "小哈" hmap[15937] = "小啰" hmap[16750] = "小算" hmap[13276] = "小法" hmap[10583] = "小鸭" print("\n添加完成后,哈希表为\nKey -> Value") print_dict(hmap) # 查询操作 # 向哈希表中输入键 key ,得到值 value name: str = hmap[15937] print("\n输入学号 15937 ,查询到姓名 " + name) # 删除操作 # 在哈希表中删除键值对 (key, value) hmap.pop(10583) print("\n删除 10583 后,哈希表为\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) ================================================ FILE: codes/python/chapter_hashing/hash_map_chaining.py ================================================ """ 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] # 遍历桶,若找到 key ,则返回对应 val for pair in bucket: if pair.key == key: return pair.val # 若未找到 key ,则返回 None return None def put(self, key: int, val: str): """添加操作""" # 当负载因子超过阈值时,执行扩容 if self.load_factor() > self.load_thres: self.extend() index = self.hash_func(key) bucket = self.buckets[index] # 遍历桶,若遇到指定 key ,则更新对应 val 并返回 for pair in bucket: if pair.key == key: pair.val = val return # 若无该 key ,则将键值对添加至尾部 pair = Pair(key, val) bucket.append(pair) self.size += 1 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, "小哈") hashmap.put(15937, "小啰") hashmap.put(16750, "小算") hashmap.put(13276, "小法") hashmap.put(10583, "小鸭") print("\n添加完成后,哈希表为\n[Key1 -> Value1, Key2 -> Value2, ...]") hashmap.print() # 查询操作 # 向哈希表中输入键 key ,得到值 value name = hashmap.get(13276) print("\n输入学号 13276 ,查询到姓名 " + name) # 删除操作 # 在哈希表中删除键值对 (key, value) hashmap.remove(12836) print("\n删除 12836 后,哈希表为\n[Key1 -> Value1, Key2 -> Value2, ...]") hashmap.print() ================================================ FILE: codes/python/chapter_hashing/hash_map_open_addressing.py ================================================ """ 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: # 若遇到 key ,返回对应的桶索引 if self.buckets[index].key == key: # 若之前遇到了删除标记,则将键值对移动至该索引处 if first_tombstone != -1: self.buckets[first_tombstone] = self.buckets[index] self.buckets[index] = self.TOMBSTONE return first_tombstone # 返回移动后的桶索引 return index # 返回桶索引 # 记录遇到的首个删除标记 if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE: first_tombstone = index # 计算桶索引,越过尾部则返回头部 index = (index + 1) % self.capacity # 若 key 不存在,则返回添加点的索引 return index if first_tombstone == -1 else first_tombstone def get(self, key: int) -> str: """查询操作""" # 搜索 key 对应的桶索引 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, "小哈") hashmap.put(15937, "小啰") hashmap.put(16750, "小算") hashmap.put(13276, "小法") hashmap.put(10583, "小鸭") print("\n添加完成后,哈希表为\nKey -> Value") hashmap.print() # 查询操作 # 向哈希表中输入键 key ,得到值 val name = hashmap.get(13276) print("\n输入学号 13276 ,查询到姓名 " + name) # 删除操作 # 在哈希表中删除键值对 (key, val) hashmap.remove(16750) print("\n删除 16750 后,哈希表为\nKey -> Value") hashmap.print() ================================================ FILE: codes/python/chapter_hashing/simple_hash.py ================================================ """ 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: """异或哈希""" 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 算法" hash = add_hash(key) print(f"加法哈希值为 {hash}") hash = mul_hash(key) print(f"乘法哈希值为 {hash}") hash = xor_hash(key) print(f"异或哈希值为 {hash}") hash = rot_hash(key) print(f"旋转哈希值为 {hash}") ================================================ FILE: codes/python/chapter_heap/heap.py ================================================ """ 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]) """Driver Code""" if __name__ == "__main__": # 初始化小顶堆 min_heap, flag = [], 1 # 初始化大顶堆 max_heap, flag = [], -1 print("\n以下测试样例为大顶堆") # Python 的 heapq 模块默认实现小顶堆 # 考虑将“元素取负”后再入堆,这样就可以将大小关系颠倒,从而实现大顶堆 # 在本示例中,flag = 1 时对应小顶堆,flag = -1 时对应大顶堆 # 元素入堆 test_push(max_heap, 1, flag) test_push(max_heap, 3, flag) test_push(max_heap, 2, flag) test_push(max_heap, 5, flag) test_push(max_heap, 4, flag) # 获取堆顶元素 peek: int = flag * max_heap[0] print(f"\n堆顶元素为 {peek}") # 堆顶元素出堆 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) ================================================ FILE: codes/python/chapter_heap/my_heap.py ================================================ """ 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 # 交换两节点 self.swap(i, p) # 循环向上堆化 i = p def pop(self) -> int: """元素出堆""" # 判空处理 if self.is_empty(): raise IndexError("堆为空") # 交换根节点与最右叶节点(交换首元素与尾元素) self.swap(0, self.size() - 1) # 删除节点 val = self.max_heap.pop() # 从顶至底堆化 self.sift_down(0) # 返回堆顶元素 return val def sift_down(self, i: int): """从节点 i 开始,从顶至底堆化""" 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 # 交换两节点 self.swap(i, ma) # 循环向下堆化 i = ma def print(self): """打印堆(二叉树)""" print_heap(self.max_heap) """Driver Code""" if __name__ == "__main__": # 初始化大顶堆 max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) print("\n输入列表并建堆后") 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}") ================================================ FILE: codes/python/chapter_heap/top_k.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [1, 7, 6, 3, 2] k = 3 res = top_k_heap(nums, k) print(f"最大的 {k} 个元素为") print_heap(res) ================================================ FILE: codes/python/chapter_searching/binary_search.py ================================================ """ 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) // 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) // 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 """Driver Code""" if __name__ == "__main__": target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # 二分查找(双闭区间) index = binary_search(nums, target) print("目标元素 6 的索引 = ", index) # 二分查找(左闭右开区间) index = binary_search_lcro(nums, target) print("目标元素 6 的索引 = ", index) ================================================ FILE: codes/python/chapter_searching/binary_search_edge.py ================================================ """ 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: """二分查找最左一个 target""" # 等价于查找 target 的插入点 i = binary_search_insertion(nums, target) # 未找到 target ,返回 -1 if i == len(nums) or nums[i] != target: return -1 # 找到 target ,返回索引 i return i def binary_search_right_edge(nums: list[int], target: int) -> int: """二分查找最右一个 target""" # 转化为查找最左一个 target + 1 i = binary_search_insertion(nums, target + 1) # j 指向最右一个 target ,i 指向首个大于 target 的元素 j = i - 1 # 未找到 target ,返回 -1 if j == -1 or nums[j] != target: return -1 # 找到 target ,返回索引 j return j """Driver Code""" if __name__ == "__main__": # 包含重复元素的数组 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print(f"\n数组 nums = {nums}") # 二分查找左边界和右边界 for target in [6, 7]: index = binary_search_left_edge(nums, target) print(f"最左一个元素 {target} 的索引为 {index}") index = binary_search_right_edge(nums, target) print(f"最右一个元素 {target} 的索引为 {index}") ================================================ FILE: codes/python/chapter_searching/binary_search_insertion.py ================================================ """ 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) // 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 # 找到 target ,返回插入点 m # 未找到 target ,返回插入点 i return i def binary_search_insertion(nums: list[int], target: int) -> int: """二分查找插入点(存在重复元素)""" i, j = 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] while i <= j: m = (i + j) // 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: j = m - 1 # 首个小于 target 的元素在区间 [i, m-1] 中 # 返回插入点 i return i """Driver Code""" if __name__ == "__main__": # 无重复元素的数组 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print(f"\n数组 nums = {nums}") # 二分查找插入点 for target in [6, 9]: index = binary_search_insertion_simple(nums, target) print(f"元素 {target} 的插入点的索引为 {index}") # 包含重复元素的数组 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}") ================================================ FILE: codes/python/chapter_searching/hashing_search.py ================================================ """ 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: """哈希查找(数组)""" # 哈希表的 key: 目标元素,value: 索引 # 若哈希表中无此 key ,返回 -1 return hmap.get(target, -1) def hashing_search_linkedlist( hmap: dict[int, ListNode], target: int ) -> ListNode | None: """哈希查找(链表)""" # 哈希表的 key: 目标元素,value: 节点对象 # 若哈希表中无此 key ,返回 None return hmap.get(target, None) """Driver Code""" 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 # key: 元素,value: 索引 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 # key: 节点值,value: 节点 head = head.next node: ListNode = hashing_search_linkedlist(map1, target) print("目标节点值 3 的对应节点对象为", node) ================================================ FILE: codes/python/chapter_searching/linear_search.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": target = 3 # 在数组中执行线性查找 nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] index: int = linear_search_array(nums, target) print("目标元素 3 的索引 =", index) # 在链表中执行线性查找 head: ListNode = list_to_linked_list(nums) node: ListNode | None = linear_search_linkedlist(head, target) print("目标节点值 3 的对应节点对象为", node) ================================================ FILE: codes/python/chapter_searching/two_sum.py ================================================ """ 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 [] """Driver Code""" if __name__ == "__main__": # ======= Test Case ======= nums = [2, 7, 11, 15] target = 13 # ====== Driver Code ====== # 方法一 res: list[int] = two_sum_brute_force(nums, target) print("方法一 res =", res) # 方法二 res: list[int] = two_sum_hash_table(nums, target) print("方法二 res =", res) ================================================ FILE: codes/python/chapter_sorting/bubble_sort.py ================================================ """ 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 # 此轮“冒泡”未交换任何元素,直接跳出 """Driver Code""" 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) ================================================ FILE: codes/python/chapter_sorting/bucket_sort.py ================================================ """ 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) ================================================ FILE: codes/python/chapter_sorting/counting_sort.py ================================================ """ File: counting_sort.py Created Time: 2023-03-21 Author: krahets (krahets@163.com) """ def counting_sort_naive(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 ,将各元素填入原数组 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 是 num 在 res 中最后一次出现的索引 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] """Driver Code""" 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}") ================================================ FILE: codes/python/chapter_sorting/heap_sort.py ================================================ """ 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 # 交换两节点 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) """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] heap_sort(nums) print("堆排序完成后 nums =", nums) ================================================ FILE: codes/python/chapter_sorting/insertion_sort.py ================================================ """ 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] 向右移动一位 j -= 1 nums[j + 1] = base # 将 base 赋值到正确位置 """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] insertion_sort(nums) print("插入排序完成后 nums =", nums) ================================================ FILE: codes/python/chapter_sorting/merge_sort.py ================================================ """ 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) // 2 # 计算中点 merge_sort(nums, left, mid) # 递归左子数组 merge_sort(nums, mid + 1, right) # 递归右子数组 # 合并阶段 merge(nums, left, mid, right) """Driver Code""" if __name__ == "__main__": nums = [7, 3, 2, 6, 0, 1, 5, 4] merge_sort(nums, 0, len(nums) - 1) print("归并排序完成后 nums =", nums) ================================================ FILE: codes/python/chapter_sorting/quick_sort.py ================================================ """ 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] # 将基准数交换至两子数组的分界线 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: """选取三个候选元素的中位数""" 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] # 将基准数交换至两子数组的分界线 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] # 将基准数交换至两子数组的分界线 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) # 对两个子数组中较短的那个执行快速排序 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] """Driver Code""" 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) ================================================ FILE: codes/python/chapter_sorting/radix_sort.py ================================================ """ 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)""" # 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 return (num // exp) % 10 def counting_sort_digit(nums: list[int], exp: int): """计数排序(根据 nums 第 k 位排序)""" # 十进制的位范围为 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 """Driver Code""" if __name__ == "__main__": # 基数排序 nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ] radix_sort(nums) print("基数排序完成后 nums =", nums) ================================================ FILE: codes/python/chapter_sorting/selection_sort.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] selection_sort(nums) print("选择排序完成后 nums =", nums) ================================================ FILE: codes/python/chapter_stack_and_queue/array_deque.py ================================================ """ 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 # 队首指针向左移动一位 # 通过取余操作实现 front 越过数组头部后回到尾部 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() # 队首指针向后移动一位 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("双向队列为空") return self._nums[self._front] def peek_last(self) -> int: """访问队尾元素""" if self.is_empty(): raise IndexError("双向队列为空") # 计算尾元素索引 last = self.index(self._front + self._size - 1) return self._nums[last] def to_array(self) -> list[int]: """返回数组用于打印""" # 仅转换有效长度范围内的列表元素 res = [] for i in range(self._size): res.append(self._nums[self.index(self._front + i)]) 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) ================================================ FILE: codes/python/chapter_stack_and_queue/array_queue.py ================================================ """ 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("队列已满") # 计算队尾指针,指向队尾索引 + 1 # 通过取余操作实现 rear 越过数组尾部后回到头部 rear: int = (self._front + self._size) % self.capacity() # 将 num 添加至队尾 self._nums[rear] = num self._size += 1 def pop(self) -> int: """出队""" num: int = self.peek() # 队首指针向后移动一位,若越过尾部,则返回到数组头部 self._front = (self._front + 1) % self.capacity() self._size -= 1 return num def peek(self) -> int: """访问队首元素""" if self.is_empty(): raise IndexError("队列为空") return self._nums[self._front] def to_list(self) -> list[int]: """返回列表用于打印""" res = [0] * self.size() j: int = self._front for i in range(self.size()): 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 =", 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()) ================================================ FILE: codes/python/chapter_stack_and_queue/array_stack.py ================================================ """ 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("栈为空") return self._stack.pop() def peek(self) -> int: """访问栈顶元素""" if self.is_empty(): raise IndexError("栈为空") 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 =", stack.to_list()) # 获取栈的长度 size: int = stack.size() print("栈的长度 size =", size) # 判断是否为空 is_empty: bool = stack.is_empty() print("栈是否为空 =", is_empty) ================================================ FILE: codes/python/chapter_stack_and_queue/deque.py ================================================ """ 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("队首出队后 deque =", deq) pop_rear: int = deq.pop() # 队尾元素出队 print("队尾出队元素 pop_rear =", pop_rear) print("队尾出队后 deque =", deq) # 获取双向队列的长度 size: int = len(deq) print("双向队列长度 size =", size) # 判断双向队列是否为空 is_empty: bool = len(deq) == 0 print("双向队列是否为空 =", is_empty) ================================================ FILE: codes/python/chapter_stack_and_queue/linkedlist_deque.py ================================================ """ 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: # 将 node 添加至链表头部 self._front.prev = node node.next = self._front self._front = node # 更新头节点 # 队尾入队操作 else: # 将 node 添加至链表尾部 self._rear.next = node node.prev = self._rear self._rear = node # 更新尾节点 self._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("双向队列为空") # 队首出队操作 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("双向队列为空") return self._front.val def peek_last(self) -> int: """访问队尾元素""" if self.is_empty(): raise IndexError("双向队列为空") 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) ================================================ FILE: codes/python/chapter_stack_and_queue/linkedlist_queue.py ================================================ """ 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("队列为空") 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 =", queue.to_list()) # 获取队列的长度 size: int = queue.size() print("队列长度 size =", size) # 判断队列是否为空 is_empty: bool = queue.is_empty() print("队列是否为空 =", is_empty) ================================================ FILE: codes/python/chapter_stack_and_queue/linkedlist_stack.py ================================================ """ 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("栈为空") 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 =", stack.to_list()) # 获取栈的长度 size: int = stack.size() print("栈的长度 size =", size) # 判断是否为空 is_empty: bool = stack.is_empty() print("栈是否为空 =", is_empty) ================================================ FILE: codes/python/chapter_stack_and_queue/queue.py ================================================ """ 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 =", que) # 获取队列的长度 size: int = len(que) print("队列长度 size =", size) # 判断队列是否为空 is_empty: bool = len(que) == 0 print("队列是否为空 =", is_empty) ================================================ FILE: codes/python/chapter_stack_and_queue/stack.py ================================================ """ File: stack.py Created Time: 2022-11-29 Author: Peng Chen (pengchzn@gmail.com) """ """Driver Code""" if __name__ == "__main__": # 初始化栈 # Python 没有内置的栈类,可以把 list 当作栈来使用 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 =", stack) # 获取栈的长度 size: int = len(stack) print("栈的长度 size =", size) # 判断是否为空 is_empty: bool = len(stack) == 0 print("栈是否为空 =", is_empty) ================================================ FILE: codes/python/chapter_tree/array_binary_tree.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # 初始化二叉树 # 这里借助了一个从数组直接生成二叉树的函数 arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] root = list_to_tree(arr) print("\n初始化二叉树\n") print("二叉树的数组表示:") print(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) ================================================ FILE: codes/python/chapter_tree/avl_tree.py ================================================ """ 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 ,直接删除 node 并返回 if child is None: return None # 子节点数量 = 1 ,直接删除 node 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 """Driver Code""" 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)) ================================================ FILE: codes/python/chapter_tree/binary_search_tree.py ================================================ """ 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 or 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) # 用 tmp 覆盖 cur cur.val = tmp.val """Driver Code""" if __name__ == "__main__": # 初始化二叉搜索树 bst = BinarySearchTree() nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] # 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 for num in nums: bst.insert(num) print("\n初始化的二叉树为\n") print_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()) ================================================ FILE: codes/python/chapter_tree/binary_tree.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # 初始化二叉树 # 初始化节点 n1 = TreeNode(val=1) n2 = TreeNode(val=2) n3 = TreeNode(val=3) n4 = TreeNode(val=4) n5 = TreeNode(val=5) # 构建节点之间的引用(指针) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 print("\n初始化二叉树\n") print_tree(n1) # 插入与删除节点 P = TreeNode(0) # 在 n1 -> n2 中间插入节点 P n1.left = P P.left = n2 print("\n插入节点 P 后\n") print_tree(n1) # 删除节点 n1.left = n2 print("\n删除节点 P 后\n") print_tree(n1) ================================================ FILE: codes/python/chapter_tree/binary_tree_bfs.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # 初始化二叉树 # 这里借助了一个从数组直接生成二叉树的函数 root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\n初始化二叉树\n") print_tree(root) # 层序遍历 res: list[int] = level_order(root) print("\n层序遍历的节点打印序列 = ", res) ================================================ FILE: codes/python/chapter_tree/binary_tree_dfs.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": # 初始化二叉树 # 这里借助了一个从数组直接生成二叉树的函数 root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\n初始化二叉树\n") print_tree(root) # 前序遍历 res = [] pre_order(root) print("\n前序遍历的节点打印序列 = ", res) # 中序遍历 res.clear() in_order(root) print("\n中序遍历的节点打印序列 = ", res) # 后序遍历 res.clear() post_order(root) print("\n后序遍历的节点打印序列 = ", res) ================================================ FILE: codes/python/modules/__init__.py ================================================ # Follow the PEP 585 - Type Hinting Generics In Standard Collections # https://peps.python.org/pep-0585/ from __future__ import annotations # Import common libs here to simplify the code by `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, ) ================================================ FILE: codes/python/modules/list_node.py ================================================ """ 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 ================================================ FILE: codes/python/modules/print_util.py ================================================ """ 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 ): """ 打印二叉树 This tree printer is borrowed from 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) ================================================ FILE: codes/python/modules/tree_node.py ================================================ """ 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 ================================================ FILE: codes/python/modules/vertex.py ================================================ # 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] ================================================ FILE: codes/python/test_all.py ================================================ import os import glob import subprocess env = os.environ.copy() env["PYTHONIOENCODING"] = "utf-8" if __name__ == "__main__": # find source code files src_paths = sorted(glob.glob("chapter_*/*.py")) errors = [] # run python code 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' ) # Wait for the process to complete, and get the output and error messages stdout, stderr = process.communicate() # Check the exit status exit_status = process.returncode if exit_status != 0: errors.append(stderr) print(f"Tested {len(src_paths)} files") print(f"Found exception in {len(errors)} files") if len(errors) > 0: raise RuntimeError("\n\n".join(errors)) ================================================ FILE: codes/pythontutor/chapter_array_and_linkedlist/array.md ================================================ https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_access%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8F%E6%9C%BA%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5B0,%20len%28nums%29-1%5D%20%E4%B8%AD%E9%9A%8F%E6%9C%BA%E6%8A%BD%E5%8F%96%E4%B8%80%E4%B8%AA%E6%95%B0%E5%AD%97%0A%20%20%20%20random_index%20%3D%20random.randint%280,%20len%28nums%29%20-%201%29%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%B9%B6%E8%BF%94%E5%9B%9E%E9%9A%8F%E6%9C%BA%E5%85%83%E7%B4%A0%0A%20%20%20%20random_num%20%3D%20nums%5Brandom_index%5D%0A%20%20%20%20return%20random_num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E9%9A%8F%E6%9C%BA%E8%AE%BF%E9%97%AE%0A%20%20%20%20random_num%3A%20int%20%3D%20random_access%28nums%29%0A%20%20%20%20print%28%22%E5%9C%A8%20nums%20%E4%B8%AD%E8%8E%B7%E5%8F%96%E9%9A%8F%E6%9C%BA%E5%85%83%E7%B4%A0%22,%20random_num%29%0A&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20insert%28nums%3A%20list%5Bint%5D,%20num%3A%20int,%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E6%95%B0%E7%BB%84%E7%9A%84%E7%B4%A2%E5%BC%95%20index%20%E5%A4%84%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%20num%22%22%22%0A%20%20%20%20%23%20%E6%8A%8A%E7%B4%A2%E5%BC%95%20index%20%E4%BB%A5%E5%8F%8A%E4%B9%8B%E5%90%8E%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%90%91%E5%90%8E%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201,%20index,%20-1%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E5%B0%86%20num%20%E8%B5%8B%E7%BB%99%20index%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5Bindex%5D%20%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20insert%28nums,%206,%203%29%0A%20%20%20%20print%28%22%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E6%8F%92%E5%85%A5%E6%95%B0%E5%AD%97%206%20%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20remove%28nums%3A%20list%5Bint%5D,%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E6%8A%8A%E7%B4%A2%E5%BC%95%20index%20%E4%B9%8B%E5%90%8E%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%90%91%E5%89%8D%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20for%20i%20in%20range%28index,%20len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20%2B%201%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20remove%28nums,%202%29%0A%20%20%20%20print%28%22%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%202%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20traverse%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E7%B4%A2%E5%BC%95%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%20%20%20%20%23%20%E5%90%8C%E6%97%B6%E9%81%8D%E5%8E%86%E6%95%B0%E6%8D%AE%E7%B4%A2%E5%BC%95%E5%92%8C%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20i,%20num%20in%20enumerate%28nums%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%0A%20%20%20%20traverse%28nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20find%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E6%8C%87%E5%AE%9A%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20index%3A%20int%20%3D%20find%28nums,%203%29%0A%20%20%20%20print%28%22%E5%9C%A8%20nums%20%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%203%20%EF%BC%8C%E5%BE%97%E5%88%B0%E7%B4%A2%E5%BC%95%20%3D%22,%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=%23%20%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8CPython%20%E7%9A%84%20list%20%E6%98%AF%E5%8A%A8%E6%80%81%E6%95%B0%E7%BB%84%EF%BC%8C%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E6%89%A9%E5%B1%95%0A%23%20%E4%B8%BA%E4%BA%86%E6%96%B9%E4%BE%BF%E5%AD%A6%E4%B9%A0%EF%BC%8C%E6%9C%AC%E5%87%BD%E6%95%B0%E5%B0%86%20list%20%E7%9C%8B%E4%BD%9C%E9%95%BF%E5%BA%A6%E4%B8%8D%E5%8F%AF%E5%8F%98%E7%9A%84%E6%95%B0%E7%BB%84%0Adef%20extend%28nums%3A%20list%5Bint%5D,%20enlarge%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%89%A9%E5%B1%95%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E4%B8%AA%E6%89%A9%E5%B1%95%E9%95%BF%E5%BA%A6%E5%90%8E%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20res%20%3D%20%5B0%5D%20*%20%28len%28nums%29%20%2B%20enlarge%29%0A%20%20%20%20%23%20%E5%B0%86%E5%8E%9F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%88%B0%E6%96%B0%E6%95%B0%E7%BB%84%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%89%A9%E5%B1%95%E5%90%8E%E7%9A%84%E6%96%B0%E6%95%B0%E7%BB%84%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E9%95%BF%E5%BA%A6%E6%89%A9%E5%B1%95%0A%20%20%20%20nums%20%3D%20extend%28nums,%203%29%0A%20%20%20%20print%28%22%E5%B0%86%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E6%89%A9%E5%B1%95%E8%87%B3%208%20%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_array_and_linkedlist/linked_list.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20insert%28n0%3A%20ListNode,%20P%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%93%BE%E8%A1%A8%E7%9A%84%E8%8A%82%E7%82%B9%20n0%20%E4%B9%8B%E5%90%8E%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%20P%22%22%22%0A%20%20%20%20n1%20%3D%20n0.next%0A%20%20%20%20P.next%20%3D%20n1%0A%20%20%20%20n0.next%20%3D%20P%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20p%20%3D%20ListNode%280%29%0A%20%20%20%20insert%28n0,%20p%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20remove%28n0%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E8%8A%82%E7%82%B9%20n0%20%E4%B9%8B%E5%90%8E%E7%9A%84%E9%A6%96%E4%B8%AA%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20if%20not%20n0.next%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20n0%20-%3E%20P%20-%3E%20n1%0A%20%20%20%20P%20%3D%20n0.next%0A%20%20%20%20n1%20%3D%20P.next%0A%20%20%20%20n0.next%20%3D%20n1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20remove%28n0%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20access%28head%3A%20ListNode,%20index%3A%20int%29%20-%3E%20ListNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%B4%A2%E5%BC%95%E4%B8%BA%20index%20%E7%9A%84%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20for%20_%20in%20range%28index%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20head%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20return%20head%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E8%8A%82%E7%82%B9%0A%20%20%20%20node%20%3D%20access%28n0,%203%29%0A%20%20%20%20print%28%22%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E7%9A%84%E8%8A%82%E7%82%B9%E7%9A%84%E5%80%BC%20%3D%20%7B%7D%22.format%28node.val%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20find%28head%3A%20ListNode,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%93%BE%E8%A1%A8%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%80%BC%E4%B8%BA%20target%20%E7%9A%84%E9%A6%96%E4%B8%AA%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20index%20%3D%200%0A%20%20%20%20while%20head%3A%0A%20%20%20%20%20%20%20%20if%20head.val%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20index%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20%20%20%20%20index%20%2B%3D%201%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E6%9F%A5%E6%89%BE%E8%8A%82%E7%82%B9%0A%20%20%20%20index%20%3D%20find%28n0,%202%29%0A%20%20%20%20print%28%22%E9%93%BE%E8%A1%A8%E4%B8%AD%E5%80%BC%E4%B8%BA%202%20%E7%9A%84%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%7B%7D%22.format%28index%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_array_and_linkedlist/my_list.md ================================================ https://pythontutor.com/render.html#code=class%20MyList%3A%0A%20%20%20%20%22%22%22%E5%88%97%E8%A1%A8%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._capacity%3A%20int%20%3D%2010%0A%20%20%20%20%20%20%20%20self._arr%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20*%20self._capacity%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%20%20%20%20%20%20%20%20self._extend_ratio%3A%20int%20%3D%202%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%88%97%E8%A1%A8%E9%95%BF%E5%BA%A6%EF%BC%88%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E6%95%B0%E9%87%8F%EF%BC%89%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%88%97%E8%A1%A8%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._capacity%0A%0A%20%20%20%20def%20get%28self,%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20return%20self._arr%5Bindex%5D%0A%0A%20%20%20%20def%20set%28self,%20num%3A%20int,%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%0A%20%20%20%20def%20add%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.size%28%29%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20self._arr%5Bself._size%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20insert%28self,%20num%3A%20int,%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E7%B4%A2%E5%BC%95%20index%20%E4%BB%A5%E5%8F%8A%E4%B9%8B%E5%90%8E%E7%9A%84%E5%85%83%E7%B4%A0%E9%83%BD%E5%90%91%E5%90%8E%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28self._size%20-%201,%20index%20-%201,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%20%2B%201%5D%20%3D%20self._arr%5Bj%5D%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self,%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20num%20%3D%20self._arr%5Bindex%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%20i%20%E4%B9%8B%E5%90%8E%E7%9A%84%E5%85%83%E7%B4%A0%E9%83%BD%E5%90%91%E5%89%8D%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28index,%20self._size%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%5D%20%3D%20self._arr%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20extend_capacity%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%97%E8%A1%A8%E6%89%A9%E5%AE%B9%22%22%22%0A%20%20%20%20%20%20%20%20self._arr%20%3D%20self._arr%20%2B%20%5B0%5D%20*%20self.capacity%28%29%20*%20%28self._extend_ratio%20-%201%29%0A%20%20%20%20%20%20%20%20self._capacity%20%3D%20len%28self._arr%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20MyList%28%29%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.add%281%29%0A%20%20%20%20nums.add%283%29%0A%20%20%20%20nums.add%282%29%0A%20%20%20%20nums.add%285%29%0A%20%20%20%20nums.add%284%29%0A%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%286,%20index%3D3%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.remove%283%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums.get%281%29%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.set%280,%201%29%0A%0A%20%20%20%20%23%20%E6%B5%8B%E8%AF%95%E6%89%A9%E5%AE%B9%E6%9C%BA%E5%88%B6%0A%20%20%20%20for%20i%20in%20range%2810%29%3A%0A%20%20%20%20%20%20%20%20nums.add%28i%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_backtracking/n_queens.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20row%3A%20int,%0A%20%20%20%20n%3A%20int,%0A%20%20%20%20state%3A%20list%5Blist%5Bstr%5D%5D,%0A%20%20%20%20res%3A%20list%5Blist%5Blist%5Bstr%5D%5D%5D,%0A%20%20%20%20cols%3A%20list%5Bbool%5D,%0A%20%20%20%20diags1%3A%20list%5Bbool%5D,%0A%20%20%20%20diags2%3A%20list%5Bbool%5D,%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9AN%20%E7%9A%87%E5%90%8E%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%E6%94%BE%E7%BD%AE%E5%AE%8C%E6%89%80%E6%9C%89%E8%A1%8C%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20row%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res.append%28%5Blist%28row%29%20for%20row%20in%20state%5D%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E5%88%97%0A%20%20%20%20for%20col%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E8%AF%A5%E6%A0%BC%E5%AD%90%E5%AF%B9%E5%BA%94%E7%9A%84%E4%B8%BB%E5%AF%B9%E8%A7%92%E7%BA%BF%E5%92%8C%E6%AC%A1%E5%AF%B9%E8%A7%92%E7%BA%BF%0A%20%20%20%20%20%20%20%20diag1%20%3D%20row%20-%20col%20%2B%20n%20-%201%0A%20%20%20%20%20%20%20%20diag2%20%3D%20row%20%2B%20col%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%AE%B8%E8%AF%A5%E6%A0%BC%E5%AD%90%E6%89%80%E5%9C%A8%E5%88%97%E3%80%81%E4%B8%BB%E5%AF%B9%E8%A7%92%E7%BA%BF%E3%80%81%E6%AC%A1%E5%AF%B9%E8%A7%92%E7%BA%BF%E4%B8%8A%E5%AD%98%E5%9C%A8%E7%9A%87%E5%90%8E%0A%20%20%20%20%20%20%20%20if%20not%20cols%5Bcol%5D%20and%20not%20diags1%5Bdiag1%5D%20and%20not%20diags2%5Bdiag2%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%B0%86%E7%9A%87%E5%90%8E%E6%94%BE%E7%BD%AE%E5%9C%A8%E8%AF%A5%E6%A0%BC%E5%AD%90%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22Q%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%94%BE%E7%BD%AE%E4%B8%8B%E4%B8%80%E8%A1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28row%20%2B%201,%20n,%20state,%20res,%20cols,%20diags1,%20diags2%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E5%B0%86%E8%AF%A5%E6%A0%BC%E5%AD%90%E6%81%A2%E5%A4%8D%E4%B8%BA%E7%A9%BA%E4%BD%8D%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22%23%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20False%0A%0A%0Adef%20n_queens%28n%3A%20int%29%20-%3E%20list%5Blist%5Blist%5Bstr%5D%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%20N%20%E7%9A%87%E5%90%8E%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20n*n%20%E5%A4%A7%E5%B0%8F%E7%9A%84%E6%A3%8B%E7%9B%98%EF%BC%8C%E5%85%B6%E4%B8%AD%20'Q'%20%E4%BB%A3%E8%A1%A8%E7%9A%87%E5%90%8E%EF%BC%8C'%23'%20%E4%BB%A3%E8%A1%A8%E7%A9%BA%E4%BD%8D%0A%20%20%20%20state%20%3D%20%5B%5B%22%23%22%20for%20_%20in%20range%28n%29%5D%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20cols%20%3D%20%5BFalse%5D%20*%20n%20%20%23%20%E8%AE%B0%E5%BD%95%E5%88%97%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20diags1%20%3D%20%5BFalse%5D%20*%20%282%20*%20n%20-%201%29%20%20%23%20%E8%AE%B0%E5%BD%95%E4%B8%BB%E5%AF%B9%E8%A7%92%E7%BA%BF%E4%B8%8A%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20diags2%20%3D%20%5BFalse%5D%20*%20%282%20*%20n%20-%201%29%20%20%23%20%E8%AE%B0%E5%BD%95%E6%AC%A1%E5%AF%B9%E8%A7%92%E7%BA%BF%E4%B8%8A%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%280,%20n,%20state,%20res,%20cols,%20diags1,%20diags2%29%0A%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20res%20%3D%20n_queens%28n%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%A3%8B%E7%9B%98%E9%95%BF%E5%AE%BD%E4%B8%BA%20%7Bn%7D%22%29%0A%20%20%20%20print%28f%22%E7%9A%87%E5%90%8E%E6%94%BE%E7%BD%AE%E6%96%B9%E6%A1%88%E5%85%B1%E6%9C%89%20%7Blen%28res%29%7D%20%E7%A7%8D%22%29%0A%20%20%20%20for%20state%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%22--------------------%22%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20state%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28row%29&cumulative=false&curInstr=61&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_backtracking/permutations_i.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%20choices%3A%20list%5Bint%5D,%20selected%3A%20list%5Bbool%5D,%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%85%A8%E6%8E%92%E5%88%97%20I%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%E7%8A%B6%E6%80%81%E9%95%BF%E5%BA%A6%E7%AD%89%E4%BA%8E%E5%85%83%E7%B4%A0%E6%95%B0%E9%87%8F%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20for%20i,%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%AE%B8%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state,%20choices,%20selected,%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_i%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E6%8E%92%E5%88%97%20I%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D,%20choices%3Dnums,%20selected%3D%5BFalse%5D%20*%20len%28nums%29,%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%202,%203%5D%0A%0A%20%20%20%20res%20%3D%20permutations_i%28nums%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E6%8E%92%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_backtracking/permutations_ii.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%20choices%3A%20list%5Bint%5D,%20selected%3A%20list%5Bbool%5D,%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%85%A8%E6%8E%92%E5%88%97%20II%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%E7%8A%B6%E6%80%81%E9%95%BF%E5%BA%A6%E7%AD%89%E4%BA%8E%E5%85%83%E7%B4%A0%E6%95%B0%E9%87%8F%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20duplicated%20%3D%20set%5Bint%5D%28%29%0A%20%20%20%20for%20i,%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%AE%B8%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E5%85%83%E7%B4%A0%20%E4%B8%94%20%E4%B8%8D%E5%85%81%E8%AE%B8%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E7%9B%B8%E7%AD%89%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%20and%20choice%20not%20in%20duplicated%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20duplicated.add%28choice%29%20%20%23%20%E8%AE%B0%E5%BD%95%E9%80%89%E6%8B%A9%E8%BF%87%E7%9A%84%E5%85%83%E7%B4%A0%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state,%20choices,%20selected,%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_ii%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E6%8E%92%E5%88%97%20II%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D,%20choices%3Dnums,%20selected%3D%5BFalse%5D%20*%20len%28nums%29,%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%202,%202%5D%0A%0A%20%20%20%20res%20%3D%20permutations_ii%28nums%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E6%8E%92%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%9A%E4%BE%8B%E9%A2%98%E4%B8%80%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28root%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1,%207,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BE%93%E5%87%BA%E6%89%80%E6%9C%89%E5%80%BC%E4%B8%BA%207%20%E7%9A%84%E8%8A%82%E7%82%B9%22%29%0A%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20res%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%9A%E4%BE%8B%E9%A2%98%E4%BA%8C%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1,%207,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BE%93%E5%87%BA%E6%89%80%E6%9C%89%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E8%8A%82%E7%82%B9%207%20%E7%9A%84%E8%B7%AF%E5%BE%84%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%9A%E4%BE%8B%E9%A2%98%E4%B8%89%22%22%22%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%0A%20%20%20%20if%20root%20is%20None%20or%20root.val%20%3D%3D%203%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1,%207,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BE%93%E5%87%BA%E6%89%80%E6%9C%89%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E8%8A%82%E7%82%B9%207%20%E7%9A%84%E8%B7%AF%E5%BE%84%EF%BC%8C%E8%B7%AF%E5%BE%84%E4%B8%AD%E4%B8%8D%E5%8C%85%E5%90%AB%E5%80%BC%E4%B8%BA%203%20%E7%9A%84%E8%8A%82%E7%82%B9%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20is_solution%28state%3A%20list%5BTreeNode%5D%29%20-%3E%20bool%3A%0A%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%BD%93%E5%89%8D%E7%8A%B6%E6%80%81%E6%98%AF%E5%90%A6%E4%B8%BA%E8%A7%A3%22%22%22%0A%20%20%20%20return%20state%20and%20state%5B-1%5D.val%20%3D%3D%207%0A%0Adef%20record_solution%28state%3A%20list%5BTreeNode%5D,%20res%3A%20list%5Blist%5BTreeNode%5D%5D%29%3A%0A%20%20%20%20%22%22%22%E8%AE%B0%E5%BD%95%E8%A7%A3%22%22%22%0A%20%20%20%20res.append%28list%28state%29%29%0A%0Adef%20is_valid%28state%3A%20list%5BTreeNode%5D,%20choice%3A%20TreeNode%29%20-%3E%20bool%3A%0A%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%9C%A8%E5%BD%93%E5%89%8D%E7%8A%B6%E6%80%81%E4%B8%8B%EF%BC%8C%E8%AF%A5%E9%80%89%E6%8B%A9%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95%22%22%22%0A%20%20%20%20return%20choice%20is%20not%20None%20and%20choice.val%20!%3D%203%0A%0Adef%20make_choice%28state%3A%20list%5BTreeNode%5D,%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%22%22%22%0A%20%20%20%20state.append%28choice%29%0A%0Adef%20undo_choice%28state%3A%20list%5BTreeNode%5D,%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E6%81%A2%E5%A4%8D%E7%8A%B6%E6%80%81%22%22%22%0A%20%20%20%20state.pop%28%29%0A%0Adef%20backtrack%28%0A%20%20%20%20state%3A%20list%5BTreeNode%5D,%20choices%3A%20list%5BTreeNode%5D,%20res%3A%20list%5Blist%5BTreeNode%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E4%BE%8B%E9%A2%98%E4%B8%89%22%22%22%0A%20%20%20%20%23%20%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E4%B8%BA%E8%A7%A3%0A%20%20%20%20if%20is_solution%28state%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20%20%20%20%20record_solution%28state,%20res%29%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E6%A3%80%E6%9F%A5%E9%80%89%E6%8B%A9%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95%0A%20%20%20%20%20%20%20%20if%20is_valid%28state,%20choice%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20make_choice%28state,%20choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state,%20%5Bchoice.left,%20choice.right%5D,%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20undo_choice%28state,%20choice%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1,%207,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D,%20choices%3D%5Broot%5D,%20res%3Dres%29%0A%20%20%20%20print%28%22%5Cn%E8%BE%93%E5%87%BA%E6%89%80%E6%9C%89%E8%B7%AF%E5%BE%84%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=138&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_backtracking/subset_sum_i.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%20target%3A%20int,%20choices%3A%20list%5Bint%5D,%20start%3A%20int,%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E4%BA%8E%20target%20%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%BA%8C%EF%BC%9A%E4%BB%8E%20start%20%E5%BC%80%E5%A7%8B%E9%81%8D%E5%8E%86%EF%BC%8C%E9%81%BF%E5%85%8D%E7%94%9F%E6%88%90%E9%87%8D%E5%A4%8D%E5%AD%90%E9%9B%86%0A%20%20%20%20for%20i%20in%20range%28start,%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%80%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E8%BF%87%20target%20%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E7%BB%93%E6%9D%9F%E5%BE%AA%E7%8E%AF%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%99%E6%98%AF%E5%9B%A0%E4%B8%BA%E6%95%B0%E7%BB%84%E5%B7%B2%E6%8E%92%E5%BA%8F%EF%BC%8C%E5%90%8E%E8%BE%B9%E5%85%83%E7%B4%A0%E6%9B%B4%E5%A4%A7%EF%BC%8C%E5%AD%90%E9%9B%86%E5%92%8C%E4%B8%80%E5%AE%9A%E8%B6%85%E8%BF%87%20target%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%20target,%20start%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20backtrack%28state,%20target%20-%20choices%5Bi%5D,%20choices,%20i,%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%80%81%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E5%AF%B9%20nums%20%E8%BF%9B%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20start%20%3D%200%20%20%23%20%E9%81%8D%E5%8E%86%E8%B5%B7%E5%A7%8B%E7%82%B9%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%BB%93%E6%9E%9C%E5%88%97%E8%A1%A8%EF%BC%88%E5%AD%90%E9%9B%86%E5%88%97%E8%A1%A8%EF%BC%89%0A%20%20%20%20backtrack%28state,%20target,%20nums,%20start,%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3,%204,%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i%28nums,%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D,%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E4%BA%8E%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%0A%20%20%20%20target%3A%20int,%0A%20%20%20%20total%3A%20int,%0A%20%20%20%20choices%3A%20list%5Bint%5D,%0A%20%20%20%20res%3A%20list%5Blist%5Bint%5D%5D,%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E4%BA%8E%20target%20%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20total%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20for%20i%20in%20range%28len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E8%BF%87%20target%20%EF%BC%8C%E5%88%99%E8%B7%B3%E8%BF%87%E8%AF%A5%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20if%20total%20%2B%20choices%5Bi%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%E5%92%8C%20total%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20backtrack%28state,%20target,%20total%20%2B%20choices%5Bi%5D,%20choices,%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i_naive%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20I%EF%BC%88%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%AD%90%E9%9B%86%EF%BC%89%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%80%81%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20total%20%3D%200%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%BB%93%E6%9E%9C%E5%88%97%E8%A1%A8%EF%BC%88%E5%AD%90%E9%9B%86%E5%88%97%E8%A1%A8%EF%BC%89%0A%20%20%20%20backtrack%28state,%20target,%20total,%20nums,%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3,%204,%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i_naive%28nums,%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D,%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E4%BA%8E%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%AF%A5%E6%96%B9%E6%B3%95%E8%BE%93%E5%87%BA%E7%9A%84%E7%BB%93%E6%9E%9C%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E9%9B%86%E5%90%88%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_backtracking/subset_sum_ii.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%20target%3A%20int,%20choices%3A%20list%5Bint%5D,%20start%3A%20int,%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20II%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E4%BA%8E%20target%20%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%BA%8C%EF%BC%9A%E4%BB%8E%20start%20%E5%BC%80%E5%A7%8B%E9%81%8D%E5%8E%86%EF%BC%8C%E9%81%BF%E5%85%8D%E7%94%9F%E6%88%90%E9%87%8D%E5%A4%8D%E5%AD%90%E9%9B%86%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%89%EF%BC%9A%E4%BB%8E%20start%20%E5%BC%80%E5%A7%8B%E9%81%8D%E5%8E%86%EF%BC%8C%E9%81%BF%E5%85%8D%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E5%90%8C%E4%B8%80%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20i%20in%20range%28start,%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%80%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E8%BF%87%20target%20%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E7%BB%93%E6%9D%9F%E5%BE%AA%E7%8E%AF%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%99%E6%98%AF%E5%9B%A0%E4%B8%BA%E6%95%B0%E7%BB%84%E5%B7%B2%E6%8E%92%E5%BA%8F%EF%BC%8C%E5%90%8E%E8%BE%B9%E5%85%83%E7%B4%A0%E6%9B%B4%E5%A4%A7%EF%BC%8C%E5%AD%90%E9%9B%86%E5%92%8C%E4%B8%80%E5%AE%9A%E8%B6%85%E8%BF%87%20target%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E5%9B%9B%EF%BC%9A%E5%A6%82%E6%9E%9C%E8%AF%A5%E5%85%83%E7%B4%A0%E4%B8%8E%E5%B7%A6%E8%BE%B9%E5%85%83%E7%B4%A0%E7%9B%B8%E7%AD%89%EF%BC%8C%E8%AF%B4%E6%98%8E%E8%AF%A5%E6%90%9C%E7%B4%A2%E5%88%86%E6%94%AF%E9%87%8D%E5%A4%8D%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%B7%B3%E8%BF%87%0A%20%20%20%20%20%20%20%20if%20i%20%3E%20start%20and%20choices%5Bi%5D%20%3D%3D%20choices%5Bi%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%20target,%20start%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20backtrack%28state,%20target%20-%20choices%5Bi%5D,%20choices,%20i%20%2B%201,%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_ii%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20II%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%80%81%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E5%AF%B9%20nums%20%E8%BF%9B%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20start%20%3D%200%20%20%23%20%E9%81%8D%E5%8E%86%E8%B5%B7%E5%A7%8B%E7%82%B9%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%BB%93%E6%9E%9C%E5%88%97%E8%A1%A8%EF%BC%88%E5%AD%90%E9%9B%86%E5%88%97%E8%A1%A8%EF%BC%89%0A%20%20%20%20backtrack%28state,%20target,%20nums,%20start,%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%204,%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_ii%28nums,%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D,%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E4%BA%8E%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_computational_complexity/iteration.md ================================================ https://pythontutor.com/render.html#code=def%20for_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22for%20%E5%BE%AA%E7%8E%AF%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%B1%82%E5%92%8C%201,%202,%20...,%20n-1,%20n%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnfor%20%E5%BE%AA%E7%8E%AF%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D& https://pythontutor.com/render.html#code=def%20while_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E5%BE%AA%E7%8E%AF%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%B1%82%E5%92%8C%201,%202,%20...,%20n-1,%20n%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E6%9B%B4%E6%96%B0%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E5%BE%AA%E7%8E%AF%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20while_loop_ii%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E5%BE%AA%E7%8E%AF%EF%BC%88%E4%B8%A4%E6%AC%A1%E6%9B%B4%E6%96%B0%EF%BC%89%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%B1%82%E5%92%8C%201,%204,%2010,%20...%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20i%20*%3D%202%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop_ii%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E5%BE%AA%E7%8E%AF%EF%BC%88%E4%B8%A4%E6%AC%A1%E6%9B%B4%E6%96%B0%EF%BC%89%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20nested_for_loop%28n%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%22%22%22%E5%8F%8C%E5%B1%82%20for%20%E5%BE%AA%E7%8E%AF%22%22%22%0A%20%20%20%20res%20%3D%20%22%22%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%20i%20%3D%201,%202,%20...,%20n-1,%20n%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%20j%20%3D%201,%202,%20...,%20n-1,%20n%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20f%22%28%7Bi%7D,%20%7Bj%7D%29,%20%22%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20nested_for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cn%E5%8F%8C%E5%B1%82%20for%20%E5%BE%AA%E7%8E%AF%E7%9A%84%E9%81%8D%E5%8E%86%E7%BB%93%E6%9E%9C%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E9%80%92%EF%BC%9A%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E5%BD%92%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E9%80%92%E5%BD%92%E5%87%BD%E6%95%B0%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20tail_recur%28n,%20res%29%3A%0A%20%20%20%20%22%22%22%E5%B0%BE%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E5%B0%BE%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20return%20tail_recur%28n%20-%201,%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n,%200%29%0A%20%20%20%20print%28f%22%5Cn%E5%B0%BE%E9%80%92%E5%BD%92%E5%87%BD%E6%95%B0%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%20f%281%29%20%3D%200,%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%E7%9A%84%E7%AC%AC%20%7Bn%7D%20%E9%A1%B9%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%8B%9F%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E4%B8%80%E4%B8%AA%E6%98%BE%E5%BC%8F%E7%9A%84%E6%A0%88%E6%9D%A5%E6%A8%A1%E6%8B%9F%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E6%A0%88%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E9%80%92%EF%BC%9A%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20for%20i%20in%20range%28n,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E2%80%9C%E5%85%A5%E6%A0%88%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%8B%9F%E2%80%9C%E9%80%92%E2%80%9D%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E5%BD%92%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E2%80%9C%E5%87%BA%E6%A0%88%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%8B%9F%E2%80%9C%E5%BD%92%E2%80%9D%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%8B%9F%E9%80%92%E5%BD%92%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_computational_complexity/recursion.md ================================================ https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E9%80%92%EF%BC%9A%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E5%BD%92%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E9%80%92%E5%BD%92%E5%87%BD%E6%95%B0%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20tail_recur%28n,%20res%29%3A%0A%20%20%20%20%22%22%22%E5%B0%BE%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E5%B0%BE%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20return%20tail_recur%28n%20-%201,%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n,%200%29%0A%20%20%20%20print%28f%22%5Cn%E5%B0%BE%E9%80%92%E5%BD%92%E5%87%BD%E6%95%B0%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%20f%281%29%20%3D%200,%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%E7%9A%84%E7%AC%AC%20%7Bn%7D%20%E9%A1%B9%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%8B%9F%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E4%B8%80%E4%B8%AA%E6%98%BE%E5%BC%8F%E7%9A%84%E6%A0%88%E6%9D%A5%E6%A8%A1%E6%8B%9F%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E6%A0%88%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E9%80%92%EF%BC%9A%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20for%20i%20in%20range%28n,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E2%80%9C%E5%85%A5%E6%A0%88%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%8B%9F%E2%80%9C%E9%80%92%E2%80%9D%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E5%BD%92%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E2%80%9C%E5%87%BA%E6%A0%88%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%8B%9F%E2%80%9C%E5%BD%92%E2%80%9D%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%8B%9F%E9%80%92%E5%BD%92%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_computational_complexity/space_complexity.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20function%28%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%87%BD%E6%95%B0%22%22%22%0A%20%20%20%20%23%20%E6%89%A7%E8%A1%8C%E6%9F%90%E4%BA%9B%E6%93%8D%E4%BD%9C%0A%20%20%20%20return%200%0A%0Adef%20constant%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B8%B8%E6%95%B0%E9%98%B6%22%22%22%0A%20%20%20%20%23%20%E5%B8%B8%E9%87%8F%E3%80%81%E5%8F%98%E9%87%8F%E3%80%81%E5%AF%B9%E8%B1%A1%E5%8D%A0%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20a%20%3D%200%0A%20%20%20%20nums%20%3D%20%5B0%5D%20*%2010%0A%20%20%20%20node%20%3D%20ListNode%280%29%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E4%B8%AD%E7%9A%84%E5%8F%98%E9%87%8F%E5%8D%A0%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20c%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E4%B8%AD%E7%9A%84%E5%87%BD%E6%95%B0%E5%8D%A0%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20function%28%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E5%B8%B8%E6%95%B0%E9%98%B6%0A%20%20%20%20constant%28n%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E9%98%B6%22%22%22%0A%20%20%20%20%23%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20n%20%E7%9A%84%E5%88%97%E8%A1%A8%E5%8D%A0%E7%94%A8%20O%28n%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20nums%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20%23%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20n%20%E7%9A%84%E5%93%88%E5%B8%8C%E8%A1%A8%E5%8D%A0%E7%94%A8%20O%28n%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20hmap%20%3D%20dict%5Bint,%20str%5D%28%29%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20hmap%5Bi%5D%20%3D%20str%28i%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E7%BA%BF%E6%80%A7%E9%98%B6%0A%20%20%20%20linear%28n%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear_recur%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20print%28%22%E9%80%92%E5%BD%92%20n%20%3D%22,%20n%29%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20linear_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E7%BA%BF%E6%80%A7%E9%98%B6%0A%20%20%20%20linear_recur%28n%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20quadratic%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%98%B6%22%22%22%0A%20%20%20%20%23%20%E4%BA%8C%E7%BB%B4%E5%88%97%E8%A1%A8%E5%8D%A0%E7%94%A8%20O%28n%5E2%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20num_matrix%20%3D%20%5B%5B0%5D%20*%20n%20for%20_%20in%20range%28n%29%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E5%B9%B3%E6%96%B9%E9%98%B6%0A%20%20%20%20quadratic%28n%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20quadratic_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E6%95%B0%E7%BB%84%20nums%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20n,%20n-1,%20...,%202,%201%0A%20%20%20%20nums%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20return%20quadratic_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E5%B9%B3%E6%96%B9%E9%98%B6%0A%20%20%20%20quadratic_recur%28n%29&cumulative=false&curInstr=28&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20build_tree%28n%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E5%BB%BA%E7%AB%8B%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%280%29%0A%20%20%20%20root.left%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20root.right%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20return%20root%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E6%8C%87%E6%95%B0%E9%98%B6%0A%20%20%20%20root%20%3D%20build_tree%28n%29&cumulative=false&curInstr=507&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_computational_complexity/time_complexity.md ================================================ https://pythontutor.com/render.html#code=def%20constant%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B8%B8%E6%95%B0%E9%98%B6%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20size%20%3D%2010%0A%20%20%20%20for%20_%20in%20range%28size%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20constant%28n%29%0A%20%20%20%20print%28%22%E5%B8%B8%E6%95%B0%E9%98%B6%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E9%98%B6%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20linear%28n%29%0A%20%20%20%20print%28%22%E7%BA%BF%E6%80%A7%E9%98%B6%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20array_traversal%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E9%98%B6%EF%BC%88%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%AC%A1%E6%95%B0%E4%B8%8E%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E6%88%90%E6%AD%A3%E6%AF%94%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20array_traversal%28%5B0%5D%20*%20n%29%0A%20%20%20%20print%28%22%E7%BA%BF%E6%80%A7%E9%98%B6%EF%BC%88%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20quadratic%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%98%B6%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%AC%A1%E6%95%B0%E4%B8%8E%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%E6%88%90%E5%B9%B3%E6%96%B9%E5%85%B3%E7%B3%BB%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20quadratic%28n%29%0A%20%20%20%20print%28%22%E5%B9%B3%E6%96%B9%E9%98%B6%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%98%B6%EF%BC%88%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%20%20%23%20%E8%AE%A1%E6%95%B0%E5%99%A8%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i%5D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E8%87%B3%E8%AF%A5%E5%8C%BA%E9%97%B4%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%20nums%5Bj%5D%20%E4%B8%8E%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%20%3D%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%203%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E5%8C%85%E5%90%AB%203%20%E4%B8%AA%E5%8D%95%E5%85%83%E6%93%8D%E4%BD%9C%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%28n,%200,%20-1%29%5D%20%20%23%20%5Bn,%20n-1,%20...,%202,%201%5D%0A%20%20%20%20count%20%3D%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E5%B9%B3%E6%96%B9%E9%98%B6%EF%BC%88%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20exponential%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E5%BE%AA%E7%8E%AF%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20base%20%3D%201%0A%20%20%20%20%23%20%E7%BB%86%E8%83%9E%E6%AF%8F%E8%BD%AE%E4%B8%80%E5%88%86%E4%B8%BA%E4%BA%8C%EF%BC%8C%E5%BD%A2%E6%88%90%E6%95%B0%E5%88%97%201,%202,%204,%208,%20...,%202%5E%28n-1%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28base%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%20%20%20%20base%20*%3D%202%0A%20%20%20%20%23%20count%20%3D%201%20%2B%202%20%2B%204%20%2B%208%20%2B%20..%20%2B%202%5E%28n-1%29%20%3D%202%5En%20-%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20exponential%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E5%BE%AA%E7%8E%AF%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20exp_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20return%20exp_recur%28n%20-%201%29%20%2B%20exp_recur%28n%20-%201%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%207%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20exp_recur%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20logarithmic%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E5%BE%AA%E7%8E%AF%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20while%20n%20%3E%201%3A%0A%20%20%20%20%20%20%20%20n%20%3D%20n%20/%202%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20logarithmic%28n%29%0A%20%20%20%20print%28%22%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E5%BE%AA%E7%8E%AF%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20return%20log_recur%28n%20/%202%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20log_recur%28n%29%0A%20%20%20%20print%28%22%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear_log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E5%AF%B9%E6%95%B0%E9%98%B6%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%20//%202%29%20%2B%20linear_log_recur%28n%20//%202%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%29%0A%20%20%20%20print%28%22%E7%BA%BF%E6%80%A7%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20factorial_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%98%B6%E4%B9%98%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E4%BB%8E%201%20%E4%B8%AA%E5%88%86%E8%A3%82%E5%87%BA%20n%20%E4%B8%AA%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20factorial_recur%28n%20-%201%29%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20factorial_recur%28n%29%0A%20%20%20%20print%28%22%E9%98%B6%E4%B9%98%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md ================================================ https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_numbers%28n%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E7%94%9F%E6%88%90%E4%B8%80%E4%B8%AA%E6%95%B0%E7%BB%84%EF%BC%8C%E5%85%83%E7%B4%A0%E4%B8%BA%3A%201,%202,%20...,%20n%20%EF%BC%8C%E9%A1%BA%E5%BA%8F%E8%A2%AB%E6%89%93%E4%B9%B1%22%22%22%0A%20%20%20%20%23%20%E7%94%9F%E6%88%90%E6%95%B0%E7%BB%84%20nums%20%3D%3A%201,%202,%203,%20...,%20n%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%281,%20n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E9%9A%8F%E6%9C%BA%E6%89%93%E4%B9%B1%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%0A%20%20%20%20random.shuffle%28nums%29%0A%20%20%20%20return%20nums%0A%0Adef%20find_one%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9F%A5%E6%89%BE%E6%95%B0%E7%BB%84%20nums%20%E4%B8%AD%E6%95%B0%E5%AD%97%201%20%E6%89%80%E5%9C%A8%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E5%85%83%E7%B4%A0%201%20%E5%9C%A8%E6%95%B0%E7%BB%84%E5%A4%B4%E9%83%A8%E6%97%B6%EF%BC%8C%E8%BE%BE%E5%88%B0%E6%9C%80%E4%BD%B3%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%20O%281%29%0A%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E5%85%83%E7%B4%A0%201%20%E5%9C%A8%E6%95%B0%E7%BB%84%E5%B0%BE%E9%83%A8%E6%97%B6%EF%BC%8C%E8%BE%BE%E5%88%B0%E6%9C%80%E5%B7%AE%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%20O%28n%29%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2010%0A%20%20%20%20nums%20%3D%20random_numbers%28n%29%0A%20%20%20%20index%20%3D%20find_one%28nums%29%0A%20%20%20%20print%28%22%5Cn%E6%95%B0%E7%BB%84%20%5B%201,%202,%20...,%20n%20%5D%20%E8%A2%AB%E6%89%93%E4%B9%B1%E5%90%8E%20%3D%22,%20nums%29%0A%20%20%20%20print%28%22%E6%95%B0%E5%AD%97%201%20%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%22,%20index%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md ================================================ https://pythontutor.com/render.html#code=def%20dfs%28nums%3A%20list%5Bint%5D,%20target%3A%20int,%20i%3A%20int,%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%9A%E9%97%AE%E9%A2%98%20f%28i,%20j%29%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%8C%BA%E9%97%B4%E4%B8%BA%E7%A9%BA%EF%BC%8C%E4%BB%A3%E8%A1%A8%E6%97%A0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20i%20%3E%20j%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%0A%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%92%E5%BD%92%E5%AD%90%E9%97%AE%E9%A2%98%20f%28m%2B1,%20j%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums,%20target,%20m%20%2B%201,%20j%29%0A%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%92%E5%BD%92%E5%AD%90%E9%97%AE%E9%A2%98%20f%28i,%20m-1%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums,%20target,%20i,%20m%20-%201%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20%20%20%20%20return%20m%0A%0Adef%20binary_search%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E6%B1%82%E8%A7%A3%E9%97%AE%E9%A2%98%20f%280,%20n-1%29%0A%20%20%20%20return%20dfs%28nums,%20target,%200,%20n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%208,%2012,%2015,%2023,%2026,%2031,%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums,%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22,%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_divide_and_conquer/build_tree.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20dfs%28%0A%20%20%20%20preorder%3A%20list%5Bint%5D,%0A%20%20%20%20inorder_map%3A%20dict%5Bint,%20int%5D,%0A%20%20%20%20i%3A%20int,%0A%20%20%20%20l%3A%20int,%0A%20%20%20%20r%3A%20int,%0A%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%9E%84%E5%BB%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E5%88%86%E6%B2%BB%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E6%A0%91%E5%8C%BA%E9%97%B4%E4%B8%BA%E7%A9%BA%E6%97%B6%E7%BB%88%E6%AD%A2%0A%20%20%20%20if%20r%20-%20l%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28preorder%5Bi%5D%29%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%20m%20%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%88%92%E5%88%86%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20m%20%3D%20inorder_map%5Bpreorder%5Bi%5D%5D%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%EF%BC%9A%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20dfs%28preorder,%20inorder_map,%20i%20%2B%201,%20l,%20m%20-%201%29%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%EF%BC%9A%E6%9E%84%E5%BB%BA%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.right%20%3D%20dfs%28preorder,%20inorder_map,%20i%20%2B%201%20%2B%20m%20-%20l,%20m%20%2B%201,%20r%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20return%20root%0A%0A%0Adef%20build_tree%28preorder%3A%20list%5Bint%5D,%20inorder%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%9E%84%E5%BB%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%EF%BC%8C%E5%AD%98%E5%82%A8%20inorder%20%E5%85%83%E7%B4%A0%E5%88%B0%E7%B4%A2%E5%BC%95%E7%9A%84%E6%98%A0%E5%B0%84%0A%20%20%20%20inorder_map%20%3D%20%7Bval%3A%20i%20for%20i,%20val%20in%20enumerate%28inorder%29%7D%0A%20%20%20%20root%20%3D%20dfs%28preorder,%20inorder_map,%200,%200,%20len%28inorder%29%20-%201%29%0A%20%20%20%20return%20root%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20preorder%20%3D%20%5B3,%209,%202,%201,%207%5D%0A%20%20%20%20inorder%20%3D%20%5B9,%203,%201,%202,%207%5D%0A%20%20%20%20print%28f%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%20%3D%20%7Bpreorder%7D%22%29%0A%20%20%20%20print%28f%22%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%20%3D%20%7Binorder%7D%22%29%0A%20%20%20%20root%20%3D%20build_tree%28preorder,%20inorder%29&cumulative=false&curInstr=21&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_divide_and_conquer/hanota.md ================================================ https://pythontutor.com/render.html#code=def%20move%28src%3A%20list%5Bint%5D,%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%B8%AA%E5%9C%86%E7%9B%98%22%22%22%0A%20%20%20%20%23%20%E4%BB%8E%20src%20%E9%A1%B6%E9%83%A8%E6%8B%BF%E5%87%BA%E4%B8%80%E4%B8%AA%E5%9C%86%E7%9B%98%0A%20%20%20%20pan%20%3D%20src.pop%28%29%0A%20%20%20%20%23%20%E5%B0%86%E5%9C%86%E7%9B%98%E6%94%BE%E5%85%A5%20tar%20%E9%A1%B6%E9%83%A8%0A%20%20%20%20tar.append%28pan%29%0A%0A%0Adef%20dfs%28i%3A%20int,%20src%3A%20list%5Bint%5D,%20buf%3A%20list%5Bint%5D,%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E6%B1%89%E8%AF%BA%E5%A1%94%E9%97%AE%E9%A2%98%20f%28i%29%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%20src%20%E5%8F%AA%E5%89%A9%E4%B8%8B%E4%B8%80%E4%B8%AA%E5%9C%86%E7%9B%98%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E5%B0%86%E5%85%B6%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20if%20i%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20move%28src,%20tar%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%20f%28i-1%29%20%EF%BC%9A%E5%B0%86%20src%20%E9%A1%B6%E9%83%A8%20i-1%20%E4%B8%AA%E5%9C%86%E7%9B%98%E5%80%9F%E5%8A%A9%20tar%20%E7%A7%BB%E5%88%B0%20buf%0A%20%20%20%20dfs%28i%20-%201,%20src,%20tar,%20buf%29%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%20f%281%29%20%EF%BC%9A%E5%B0%86%20src%20%E5%89%A9%E4%BD%99%E4%B8%80%E4%B8%AA%E5%9C%86%E7%9B%98%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20move%28src,%20tar%29%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%20f%28i-1%29%20%EF%BC%9A%E5%B0%86%20buf%20%E9%A1%B6%E9%83%A8%20i-1%20%E4%B8%AA%E5%9C%86%E7%9B%98%E5%80%9F%E5%8A%A9%20src%20%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20dfs%28i%20-%201,%20buf,%20src,%20tar%29%0A%0A%0Adef%20solve_hanota%28A%3A%20list%5Bint%5D,%20B%3A%20list%5Bint%5D,%20C%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E6%B1%89%E8%AF%BA%E5%A1%94%E9%97%AE%E9%A2%98%22%22%22%0A%20%20%20%20n%20%3D%20len%28A%29%0A%20%20%20%20%23%20%E5%B0%86%20A%20%E9%A1%B6%E9%83%A8%20n%20%E4%B8%AA%E5%9C%86%E7%9B%98%E5%80%9F%E5%8A%A9%20B%20%E7%A7%BB%E5%88%B0%20C%0A%20%20%20%20dfs%28n,%20A,%20B,%20C%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%97%E8%A1%A8%E5%B0%BE%E9%83%A8%E6%98%AF%E6%9F%B1%E5%AD%90%E9%A1%B6%E9%83%A8%0A%20%20%20%20A%20%3D%20%5B5,%204,%203,%202,%201%5D%0A%20%20%20%20B%20%3D%20%5B%5D%0A%20%20%20%20C%20%3D%20%5B%5D%0A%20%20%20%20print%28%22%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81%E4%B8%8B%EF%BC%9A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29%0A%0A%20%20%20%20solve_hanota%28A,%20B,%20C%29%0A%0A%20%20%20%20print%28%22%E5%9C%86%E7%9B%98%E7%A7%BB%E5%8A%A8%E5%AE%8C%E6%88%90%E5%90%8E%EF%BC%9A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28choices%3A%20list%5Bint%5D,%20state%3A%20int,%20n%3A%20int,%20res%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%E7%88%AC%E5%88%B0%E7%AC%AC%20n%20%E9%98%B6%E6%97%B6%EF%BC%8C%E6%96%B9%E6%A1%88%E6%95%B0%E9%87%8F%E5%8A%A0%201%0A%20%20%20%20if%20state%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%5B0%5D%20%2B%3D%201%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%AE%B8%E8%B6%8A%E8%BF%87%E7%AC%AC%20n%20%E9%98%B6%0A%20%20%20%20%20%20%20%20if%20state%20%2B%20choice%20%3E%20n%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20backtrack%28choices,%20state%20%2B%20choice,%20n,%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%0A%0Adef%20climbing_stairs_backtrack%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E5%9B%9E%E6%BA%AF%22%22%22%0A%20%20%20%20choices%20%3D%20%5B1,%202%5D%20%20%23%20%E5%8F%AF%E9%80%89%E6%8B%A9%E5%90%91%E4%B8%8A%E7%88%AC%201%20%E9%98%B6%E6%88%96%202%20%E9%98%B6%0A%20%20%20%20state%20%3D%200%20%20%23%20%E4%BB%8E%E7%AC%AC%200%20%E9%98%B6%E5%BC%80%E5%A7%8B%E7%88%AC%0A%20%20%20%20res%20%3D%20%5B0%5D%20%20%23%20%E4%BD%BF%E7%94%A8%20res%5B0%5D%20%E8%AE%B0%E5%BD%95%E6%96%B9%E6%A1%88%E6%95%B0%E9%87%8F%0A%20%20%20%20backtrack%28choices,%20state,%20n,%20res%29%0A%20%20%20%20return%20res%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_backtrack%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md ================================================ https://pythontutor.com/render.html#code=def%20climbing_stairs_constraint_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B8%A6%E7%BA%A6%E6%9D%9F%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%203%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81%EF%BC%9A%E9%A2%84%E8%AE%BE%E6%9C%80%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D%5B1%5D,%20dp%5B1%5D%5B2%5D%20%3D%201,%200%0A%20%20%20%20dp%5B2%5D%5B1%5D,%20dp%5B2%5D%5B2%5D%20%3D%200,%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E4%BB%8E%E8%BE%83%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BE%83%E5%A4%A7%E5%AD%90%E9%97%AE%E9%A2%98%0A%20%20%20%20for%20i%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B1%5D%20%3D%20dp%5Bi%20-%201%5D%5B2%5D%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B2%5D%20%3D%20dp%5Bi%20-%202%5D%5B1%5D%20%2B%20dp%5Bi%20-%202%5D%5B2%5D%0A%20%20%20%20return%20dp%5Bn%5D%5B1%5D%20%2B%20dp%5Bn%5D%5B2%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_constraint_dp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md ================================================ https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E5%B7%B2%E7%9F%A5%20dp%5B1%5D%20%E5%92%8C%20dp%5B2%5D%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%29%20%2B%20dfs%28i%20-%202%29%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20return%20dfs%28n%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md ================================================ https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int,%20mem%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E5%B7%B2%E7%9F%A5%20dp%5B1%5D%20%E5%92%8C%20dp%5B2%5D%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20%E8%8B%A5%E5%AD%98%E5%9C%A8%E8%AE%B0%E5%BD%95%20dp%5Bi%5D%20%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20mem%5Bi%5D%20!%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201,%20mem%29%20%2B%20dfs%28i%20-%202,%20mem%29%0A%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%20dp%5Bi%5D%0A%20%20%20%20mem%5Bi%5D%20%3D%20count%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs_mem%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20mem%5Bi%5D%20%E8%AE%B0%E5%BD%95%E7%88%AC%E5%88%B0%E7%AC%AC%20i%20%E9%98%B6%E7%9A%84%E6%96%B9%E6%A1%88%E6%80%BB%E6%95%B0%EF%BC%8C-1%20%E4%BB%A3%E8%A1%A8%E6%97%A0%E8%AE%B0%E5%BD%95%0A%20%20%20%20mem%20%3D%20%5B-1%5D%20*%20%28n%20%2B%201%29%0A%20%20%20%20return%20dfs%28n,%20mem%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs_mem%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md ================================================ https://pythontutor.com/render.html#code=def%20climbing_stairs_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81%EF%BC%9A%E9%A2%84%E8%AE%BE%E6%9C%80%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D,%20dp%5B2%5D%20%3D%201,%202%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E4%BB%8E%E8%BE%83%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BE%83%E5%A4%A7%E5%AD%90%E9%97%AE%E9%A2%98%0A%20%20%20%20for%20i%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20dp%5Bi%20-%201%5D%20%2B%20dp%5Bi%20-%202%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20climbing_stairs_dp_comp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20a,%20b%20%3D%201,%202%0A%20%20%20%20for%20_%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a,%20b%20%3D%20b,%20a%20%2B%20b%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp_comp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_dynamic_programming/coin_change.md ================================================ https://pythontutor.com/render.html#code=def%20coin_change_dp%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%E9%A6%96%E5%88%97%0A%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Ba%5D%20%3D%20MAX%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%A1%AC%E5%B8%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%A1%AC%E5%B8%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%B0%8F%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20min%28dp%5Bi%20-%201%5D%5Ba%5D,%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%20if%20dp%5Bn%5D%5Bamt%5D%20!%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1,%202,%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20coin_change_dp%28coins,%20amt%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%88%B0%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B8%81%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20coin_change_dp_comp%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5BMAX%5D%20*%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%200%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%A1%AC%E5%B8%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%A1%AC%E5%B8%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%B0%8F%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20min%28dp%5Ba%5D,%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bamt%5D%20if%20dp%5Bamt%5D%20!%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1,%202,%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20coin_change_dp_comp%28coins,%20amt%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%88%B0%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B8%81%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md ================================================ https://pythontutor.com/render.html#code=def%20coin_change_ii_dp%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%28n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%A1%AC%E5%B8%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%A1%AC%E5%B8%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E4%B9%8B%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%20%2B%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1,%202,%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20coin_change_ii_dp%28coins,%20amt%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%87%BA%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%E7%9A%84%E7%A1%AC%E5%B8%81%E7%BB%84%E5%90%88%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20coin_change_ii_dp_comp%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%A1%AC%E5%B8%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%A1%AC%E5%B8%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E4%B9%8B%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%20%2B%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1,%202,%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20coin_change_ii_dp_comp%28coins,%20amt%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%87%BA%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%E7%9A%84%E7%A1%AC%E5%B8%81%E7%BB%84%E5%90%88%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_dynamic_programming/edit_distance.md ================================================ https://pythontutor.com/render.html#code=def%20edit_distance_dp%28s%3A%20str,%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28m%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20i%0A%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E4%B8%A4%E5%AD%97%E7%AC%A6%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%B7%B3%E8%BF%87%E6%AD%A4%E4%B8%A4%E5%AD%97%E7%AC%A6%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E3%80%81%E6%9B%BF%E6%8D%A2%E8%BF%99%E4%B8%89%E7%A7%8D%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D,%20dp%5Bi%20-%201%5D%5Bj%5D,%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%29%20%2B%201%0A%20%20%20%20return%20dp%5Bn%5D%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20edit_distance_dp%28s,%20t%29%0A%20%20%20%20print%28f%22%E5%B0%86%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E4%B8%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%BC%96%E8%BE%91%20%7Bres%7D%20%E6%AD%A5%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20edit_distance_dp_comp%28s%3A%20str,%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20%20%20%20%20leftup%20%3D%20dp%5B0%5D%20%20%23%20%E6%9A%82%E5%AD%98%20dp%5Bi-1,%20j-1%5D%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20dp%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E4%B8%A4%E5%AD%97%E7%AC%A6%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%B7%B3%E8%BF%87%E6%AD%A4%E4%B8%A4%E5%AD%97%E7%AC%A6%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20leftup%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E3%80%81%E6%9B%BF%E6%8D%A2%E8%BF%99%E4%B8%89%E7%A7%8D%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D,%20dp%5Bj%5D,%20leftup%29%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20leftup%20%3D%20temp%20%20%23%20%E6%9B%B4%E6%96%B0%E4%B8%BA%E4%B8%8B%E4%B8%80%E8%BD%AE%E7%9A%84%20dp%5Bi-1,%20j-1%5D%0A%20%20%20%20return%20dp%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20edit_distance_dp_comp%28s,%20t%29%0A%20%20%20%20print%28f%22%E5%B0%86%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E4%B8%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%BC%96%E8%BE%91%20%7Bres%7D%20%E6%AD%A5%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_dynamic_programming/knapsack.md ================================================ https://pythontutor.com/render.html#code=def%20knapsack_dfs%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20i%3A%20int,%20c%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E6%9A%B4%E5%8A%9B%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E9%80%89%E5%AE%8C%E6%89%80%E6%9C%89%E7%89%A9%E5%93%81%E6%88%96%E8%83%8C%E5%8C%85%E6%97%A0%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%E4%BB%B7%E5%80%BC%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E5%8F%AA%E8%83%BD%E9%80%89%E6%8B%A9%E4%B8%8D%E6%94%BE%E5%85%A5%E8%83%8C%E5%8C%85%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs%28wgt,%20val,%20i%20-%201,%20c%29%0A%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%8D%E6%94%BE%E5%85%A5%E5%92%8C%E6%94%BE%E5%85%A5%E7%89%A9%E5%93%81%20i%20%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC%0A%20%20%20%20no%20%3D%20knapsack_dfs%28wgt,%20val,%20i%20-%201,%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs%28wgt,%20val,%20i%20-%201,%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E4%B8%AD%E4%BB%B7%E5%80%BC%E6%9B%B4%E5%A4%A7%E7%9A%84%E9%82%A3%E4%B8%80%E4%B8%AA%0A%20%20%20%20return%20max%28no,%20yes%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E6%9A%B4%E5%8A%9B%E6%90%9C%E7%B4%A2%0A%20%20%20%20res%20%3D%20knapsack_dfs%28wgt,%20val,%20n,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20knapsack_dfs_mem%28%0A%20%20%20%20wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20mem%3A%20list%5Blist%5Bint%5D%5D,%20i%3A%20int,%20c%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E9%80%89%E5%AE%8C%E6%89%80%E6%9C%89%E7%89%A9%E5%93%81%E6%88%96%E8%83%8C%E5%8C%85%E6%97%A0%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%E4%BB%B7%E5%80%BC%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E6%9C%89%E8%AE%B0%E5%BD%95%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20if%20mem%5Bi%5D%5Bc%5D%20!%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E5%8F%AA%E8%83%BD%E9%80%89%E6%8B%A9%E4%B8%8D%E6%94%BE%E5%85%A5%E8%83%8C%E5%8C%85%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs_mem%28wgt,%20val,%20mem,%20i%20-%201,%20c%29%0A%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%8D%E6%94%BE%E5%85%A5%E5%92%8C%E6%94%BE%E5%85%A5%E7%89%A9%E5%93%81%20i%20%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC%0A%20%20%20%20no%20%3D%20knapsack_dfs_mem%28wgt,%20val,%20mem,%20i%20-%201,%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs_mem%28wgt,%20val,%20mem,%20i%20-%201,%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E5%B9%B6%E8%BF%94%E5%9B%9E%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E4%B8%AD%E4%BB%B7%E5%80%BC%E6%9B%B4%E5%A4%A7%E7%9A%84%E9%82%A3%E4%B8%80%E4%B8%AA%0A%20%20%20%20mem%5Bi%5D%5Bc%5D%20%3D%20max%28no,%20yes%29%0A%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20*%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20res%20%3D%20knapsack_dfs_mem%28wgt,%20val,%20mem,%20n,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20knapsack_dp%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281,%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%89%A9%E5%93%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D,%20dp%5Bi%20-%201%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20knapsack_dp%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20knapsack_dp_comp%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%80%92%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%28cap,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%89%A9%E5%93%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D,%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20knapsack_dp_comp%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md ================================================ https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%E6%9C%80%E5%B0%8F%E4%BB%A3%E4%BB%B7%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81%EF%BC%9A%E9%A2%84%E8%AE%BE%E6%9C%80%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D,%20dp%5B2%5D%20%3D%20cost%5B1%5D,%20cost%5B2%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E4%BB%8E%E8%BE%83%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BE%83%E5%A4%A7%E5%AD%90%E9%97%AE%E9%A2%98%0A%20%20%20%20for%20i%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20min%28dp%5Bi%20-%201%5D,%20dp%5Bi%20-%202%5D%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0,%201,%2010,%201,%201,%201,%2010,%201,%201,%2010,%201%5D%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%A5%BC%E6%A2%AF%E7%9A%84%E4%BB%A3%E4%BB%B7%E5%88%97%E8%A1%A8%E4%B8%BA%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp%28cost%29%0A%20%20%20%20print%28f%22%E7%88%AC%E5%AE%8C%E6%A5%BC%E6%A2%AF%E7%9A%84%E6%9C%80%E4%BD%8E%E4%BB%A3%E4%BB%B7%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp_comp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%E6%9C%80%E5%B0%8F%E4%BB%A3%E4%BB%B7%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20a,%20b%20%3D%20cost%5B1%5D,%20cost%5B2%5D%0A%20%20%20%20for%20i%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a,%20b%20%3D%20b,%20min%28a,%20b%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0,%201,%2010,%201,%201,%201,%2010,%201,%201,%2010,%201%5D%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%A5%BC%E6%A2%AF%E7%9A%84%E4%BB%A3%E4%BB%B7%E5%88%97%E8%A1%A8%E4%B8%BA%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp_comp%28cost%29%0A%20%20%20%20print%28f%22%E7%88%AC%E5%AE%8C%E6%A5%BC%E6%A2%AF%E7%9A%84%E6%9C%80%E4%BD%8E%E4%BB%A3%E4%BB%B7%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_dynamic_programming/min_path_sum.md ================================================ https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs%28grid%3A%20list%5Blist%5Bint%5D%5D,%20i%3A%20int,%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%EF%BC%9A%E6%9A%B4%E5%8A%9B%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E4%B8%BA%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%8D%95%E5%85%83%E6%A0%BC%EF%BC%8C%E5%88%99%E7%BB%88%E6%AD%A2%E6%90%9C%E7%B4%A2%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%A1%8C%E5%88%97%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20%2B%E2%88%9E%20%E4%BB%A3%E4%BB%B7%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i-1,%20j%29%20%E5%92%8C%20%28i,%20j-1%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%BB%A3%E4%BB%B7%0A%20%20%20%20up%20%3D%20min_path_sum_dfs%28grid,%20i%20-%201,%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs%28grid,%20i,%20j%20-%201%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i,%20j%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%BB%A3%E4%BB%B7%0A%20%20%20%20return%20min%28left,%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1,%203,%201,%205%5D,%20%5B2,%202,%204,%202%5D,%20%5B5,%203,%202,%201%5D,%20%5B4,%203,%205,%202%5D%5D%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E6%9A%B4%E5%8A%9B%E6%90%9C%E7%B4%A2%0A%20%20%20%20res%20%3D%20min_path_sum_dfs%28grid,%20n%20-%201,%20m%20-%201%29%0A%20%20%20%20print%28f%22%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs_mem%28%0A%20%20%20%20grid%3A%20list%5Blist%5Bint%5D%5D,%20mem%3A%20list%5Blist%5Bint%5D%5D,%20i%3A%20int,%20j%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%EF%BC%9A%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E4%B8%BA%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%8D%95%E5%85%83%E6%A0%BC%EF%BC%8C%E5%88%99%E7%BB%88%E6%AD%A2%E6%90%9C%E7%B4%A2%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%A1%8C%E5%88%97%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20%2B%E2%88%9E%20%E4%BB%A3%E4%BB%B7%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E6%9C%89%E8%AE%B0%E5%BD%95%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20if%20mem%5Bi%5D%5Bj%5D%20!%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%20%20%20%20%23%20%E5%B7%A6%E8%BE%B9%E5%92%8C%E4%B8%8A%E8%BE%B9%E5%8D%95%E5%85%83%E6%A0%BC%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%BB%A3%E4%BB%B7%0A%20%20%20%20up%20%3D%20min_path_sum_dfs_mem%28grid,%20mem,%20i%20-%201,%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs_mem%28grid,%20mem,%20i,%20j%20-%201%29%0A%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E5%B9%B6%E8%BF%94%E5%9B%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i,%20j%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%BB%A3%E4%BB%B7%0A%20%20%20%20mem%5Bi%5D%5Bj%5D%20%3D%20min%28left,%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1,%203,%201,%205%5D,%20%5B2,%202,%204,%202%5D,%20%5B5,%203,%202,%201%5D,%20%5B4,%203,%205,%202%5D%5D%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%0A%20%20%20%23%20%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20*%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20res%20%3D%20min_path_sum_dfs_mem%28grid,%20mem,%20n%20-%201,%20m%20-%201%29%0A%20%20%20%20print%28f%22%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20dp%5B0%5D%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281,%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20dp%5B0%5D%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20dp%5Bi%20-%201%5D%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D,%20dp%5Bi%20-%201%5D%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bn%20-%201%5D%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1,%203,%201,%205%5D,%20%5B2,%202,%204,%202%5D,%20%5B5,%203,%202,%201%5D,%20%5B4,%203,%205,%202%5D%5D%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20min_path_sum_dp%28grid%29%0A%20%20%20%20print%28f%22%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp_comp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20m%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20dp%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20for%20j%20in%20range%281,%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20dp%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281,%20n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%3D%20dp%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D,%20dp%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1,%203,%201,%205%5D,%20%5B2,%202,%204,%202%5D,%20%5B5,%203,%202,%201%5D,%20%5B4,%203,%205,%202%5D%5D%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20min_path_sum_dp_comp%28grid%29%0A%20%20%20%20print%28f%22%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md ================================================ https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281,%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%89%A9%E5%93%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D,%20dp%5Bi%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1,%202,%203%5D%0A%20%20%20%20val%20%3D%20%5B5,%2011,%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp_comp%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281,%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%89%A9%E5%93%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D,%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1,%202,%203%5D%0A%20%20%20%20val%20%3D%20%5B5,%2011,%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp_comp%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_graph/graph_adjacency_list.md ================================================ https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A1%B6%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BE%93%E5%85%A5%E5%80%BC%E5%88%97%E8%A1%A8%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%82%BB%E6%8E%A5%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%97%A0%E5%90%91%E5%9B%BE%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex,%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%89%80%E6%9C%89%E9%A1%B6%E7%82%B9%E5%92%8C%E8%BE%B9%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D,%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%A1%B6%E7%82%B9%E6%95%B0%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.adj_list%29%0A%0A%20%20%20%20def%20add_edge%28self,%20vet1%3A%20Vertex,%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%BE%B9%20vet1%20-%20vet2%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20remove_edge%28self,%20vet1%3A%20Vertex,%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%BE%B9%20vet1%20-%20vet2%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.remove%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.remove%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E4%B8%80%E4%B8%AA%E6%96%B0%E9%93%BE%E8%A1%A8%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20remove_vertex%28self,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20not%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%20vet%20%E5%AF%B9%E5%BA%94%E7%9A%84%E9%93%BE%E8%A1%A8%0A%20%20%20%20%20%20%20%20self.adj_list.pop%28vet%29%0A%20%20%20%20%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%85%B6%E4%BB%96%E9%A1%B6%E7%82%B9%E7%9A%84%E9%93%BE%E8%A1%A8%EF%BC%8C%E5%88%A0%E9%99%A4%E6%89%80%E6%9C%89%E5%8C%85%E5%90%AB%20vet%20%E7%9A%84%E8%BE%B9%0A%20%20%20%20%20%20%20%20for%20vertex%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%5Bvertex%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.adj_list%5Bvertex%5D.remove%28vet%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%97%A0%E5%90%91%E5%9B%BE%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B1,%203,%202,%205,%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B1%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B3%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B2%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B2%5D,%20v%5B3%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B2%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%BE%B9%0A%20%20%20%20graph.add_edge%28v%5B0%5D,%20v%5B2%5D%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%BE%B9%0A%20%20%20%20graph.remove_edge%28v%5B0%5D,%20v%5B1%5D%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%0A%20%20%20%20v5%20%3D%20Vertex%286%29%0A%20%20%20%20graph.add_vertex%28v5%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%0A%20%20%20%20graph.remove_vertex%28v%5B1%5D%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_graph/graph_adjacency_matrix.md ================================================ https://pythontutor.com/render.html#code=class%20GraphAdjMat%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%97%A0%E5%90%91%E5%9B%BE%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20vertices%3A%20list%5Bint%5D,%20edges%3A%20list%5Blist%5Bint%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.vertices%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.adj_mat%3A%20list%5Blist%5Bint%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20for%20val%20in%20vertices%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%BE%B9%0A%20%20%20%20%20%20%20%20for%20e%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28e%5B0%5D,%20e%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%A1%B6%E7%82%B9%E6%95%B0%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.vertices%29%0A%0A%20%20%20%20def%20add_vertex%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20n%20%3D%20self.size%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%90%91%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E6%96%B0%E9%A1%B6%E7%82%B9%E7%9A%84%E5%80%BC%0A%20%20%20%20%20%20%20%20self.vertices.append%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E4%B8%AD%E6%B7%BB%E5%8A%A0%E4%B8%80%E8%A1%8C%0A%20%20%20%20%20%20%20%20new_row%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20%20%20%20%20self.adj_mat.append%28new_row%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E4%B8%AD%E6%B7%BB%E5%8A%A0%E4%B8%80%E5%88%97%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.append%280%29%0A%0A%20%20%20%20def%20remove_vertex%28self,%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%E4%B8%AD%E7%A7%BB%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20self.vertices.pop%28index%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E4%B8%AD%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E8%A1%8C%0A%20%20%20%20%20%20%20%20self.adj_mat.pop%28index%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E4%B8%AD%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E5%88%97%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.pop%28index%29%0A%0A%20%20%20%20def%20add_edge%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%E4%B8%8E%E7%9B%B8%E7%AD%89%E5%A4%84%E7%90%86%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20j%20%3E%3D%20self.size%28%29%20or%20i%20%3D%3D%20j%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%201%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%201%0A%0A%20%20%20%20def%20remove_edge%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%E4%B8%8E%E7%9B%B8%E7%AD%89%E5%A4%84%E7%90%86%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20j%20%3E%3D%20self.size%28%29%20or%20i%20%3D%3D%20j%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%200%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%200%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%97%A0%E5%90%91%E5%9B%BE%0A%20%20%20%20vertices%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20edges%20%3D%20%5B%5B0,%201%5D,%20%5B0,%203%5D,%20%5B1,%202%5D,%20%5B2,%203%5D,%20%5B2,%204%5D,%20%5B3,%204%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjMat%28vertices,%20edges%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%BE%B9%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%201,%202%20%E7%9A%84%E7%B4%A2%E5%BC%95%E5%88%86%E5%88%AB%E4%B8%BA%200,%202%0A%20%20%20%20graph.add_edge%280,%202%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%BE%B9%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%201,%203%20%E7%9A%84%E7%B4%A2%E5%BC%95%E5%88%86%E5%88%AB%E4%B8%BA%200,%201%0A%20%20%20%20graph.remove_edge%280,%201%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%0A%20%20%20%20graph.add_vertex%286%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%203%20%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%201%0A%20%20%20%20graph.remove_vertex%281%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_graph/graph_bfs.md ================================================ https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A1%B6%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BE%93%E5%85%A5%E5%80%BC%E5%88%97%E8%A1%A8%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%82%BB%E6%8E%A5%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%97%A0%E5%90%91%E5%9B%BE%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex,%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D,%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self,%20vet1%3A%20Vertex,%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20graph_bfs%28graph%3A%20GraphAdjList,%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E5%93%88%E5%B8%8C%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E8%AE%B0%E5%BD%95%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%E8%BF%87%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20%E9%98%9F%E5%88%97%E7%94%A8%E4%BA%8E%E5%AE%9E%E7%8E%B0%20BFS%0A%20%20%20%20que%20%3D%20deque%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20%E4%BB%A5%E9%A1%B6%E7%82%B9%20vet%20%E4%B8%BA%E8%B5%B7%E7%82%B9%EF%BC%8C%E5%BE%AA%E7%8E%AF%E7%9B%B4%E8%87%B3%E8%AE%BF%E9%97%AE%E5%AE%8C%E6%89%80%E6%9C%89%E9%A1%B6%E7%82%B9%0A%20%20%20%20while%20len%28que%29%20%3E%200%3A%0A%20%20%20%20%20%20%20%20vet%20%3D%20que.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E9%A1%B6%E7%82%B9%E5%87%BA%E9%98%9F%0A%20%20%20%20%20%20%20%20res.append%28vet%29%20%20%23%20%E8%AE%B0%E5%BD%95%E8%AE%BF%E9%97%AE%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E8%AF%A5%E9%A1%B6%E7%82%B9%E7%9A%84%E6%89%80%E6%9C%89%E9%82%BB%E6%8E%A5%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20for%20adj_vet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20adj_vet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%B7%B3%E8%BF%87%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20que.append%28adj_vet%29%20%20%23%20%E5%8F%AA%E5%85%A5%E9%98%9F%E6%9C%AA%E8%AE%BF%E9%97%AE%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20visited.add%28adj_vet%29%20%20%23%20%E6%A0%87%E8%AE%B0%E8%AF%A5%E9%A1%B6%E7%82%B9%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E9%A1%B6%E7%82%B9%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%97%A0%E5%90%91%E5%9B%BE%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0,%201,%202,%203,%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B1%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B3%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B2%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%0A%20%20%20%20%23%20%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20graph_bfs%28graph,%20v%5B0%5D%29&cumulative=false&curInstr=131&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_graph/graph_dfs.md ================================================ https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A1%B6%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BE%93%E5%85%A5%E5%80%BC%E5%88%97%E8%A1%A8%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%82%BB%E6%8E%A5%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%97%A0%E5%90%91%E5%9B%BE%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex,%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D,%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self,%20vet1%3A%20Vertex,%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20dfs%28graph%3A%20GraphAdjList,%20visited%3A%20set%5BVertex%5D,%20res%3A%20list%5BVertex%5D,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%E8%BE%85%E5%8A%A9%E5%87%BD%E6%95%B0%22%22%22%0A%20%20%20%20res.append%28vet%29%20%20%23%20%E8%AE%B0%E5%BD%95%E8%AE%BF%E9%97%AE%E9%A1%B6%E7%82%B9%0A%20%20%20%20visited.add%28vet%29%20%20%23%20%E6%A0%87%E8%AE%B0%E8%AF%A5%E9%A1%B6%E7%82%B9%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E8%AF%A5%E9%A1%B6%E7%82%B9%E7%9A%84%E6%89%80%E6%9C%89%E9%82%BB%E6%8E%A5%E9%A1%B6%E7%82%B9%0A%20%20%20%20for%20adjVet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20if%20adjVet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%B7%B3%E8%BF%87%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20%23%20%E9%80%92%E5%BD%92%E8%AE%BF%E9%97%AE%E9%82%BB%E6%8E%A5%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20dfs%28graph,%20visited,%20res,%20adjVet%29%0A%0A%0Adef%20graph_dfs%28graph%3A%20GraphAdjList,%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E9%82%BB%E6%8E%A5%E8%A1%A8%E6%9D%A5%E8%A1%A8%E7%A4%BA%E5%9B%BE%EF%BC%8C%E4%BB%A5%E4%BE%BF%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E9%A1%B6%E7%82%B9%E7%9A%84%E6%89%80%E6%9C%89%E9%82%BB%E6%8E%A5%E9%A1%B6%E7%82%B9%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E5%93%88%E5%B8%8C%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E8%AE%B0%E5%BD%95%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%E8%BF%87%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%29%0A%20%20%20%20dfs%28graph,%20visited,%20res,%20start_vet%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%97%A0%E5%90%91%E5%9B%BE%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0,%201,%202,%203,%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B1%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B3%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B2%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%0A%20%20%20%20%23%20%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20graph_dfs%28graph,%20v%5B0%5D%29&cumulative=false&curInstr=130&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_greedy/coin_change_greedy.md ================================================ https://pythontutor.com/render.html#code=def%20coin_change_greedy%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%EF%BC%9A%E8%B4%AA%E5%BF%83%22%22%22%0A%20%20%20%20%23%20%E5%81%87%E8%AE%BE%20coins%20%E5%88%97%E8%A1%A8%E6%9C%89%E5%BA%8F%0A%20%20%20%20i%20%3D%20len%28coins%29%20-%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E8%BF%9B%E8%A1%8C%E8%B4%AA%E5%BF%83%E9%80%89%E6%8B%A9%EF%BC%8C%E7%9B%B4%E5%88%B0%E6%97%A0%E5%89%A9%E4%BD%99%E9%87%91%E9%A2%9D%0A%20%20%20%20while%20amt%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E5%B0%8F%E4%BA%8E%E4%B8%94%E6%9C%80%E6%8E%A5%E8%BF%91%E5%89%A9%E4%BD%99%E9%87%91%E9%A2%9D%E7%9A%84%E7%A1%AC%E5%B8%81%0A%20%20%20%20%20%20%20%20while%20i%20%3E%200%20and%20coins%5Bi%5D%20%3E%20amt%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20-%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E9%80%89%E6%8B%A9%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20amt%20-%3D%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%23%20%E8%8B%A5%E6%9C%AA%E6%89%BE%E5%88%B0%E5%8F%AF%E8%A1%8C%E6%96%B9%E6%A1%88%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20return%20count%20if%20amt%20%3D%3D%200%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%EF%BC%9A%E8%83%BD%E5%A4%9F%E4%BF%9D%E8%AF%81%E6%89%BE%E5%88%B0%E5%85%A8%E5%B1%80%E6%9C%80%E4%BC%98%E8%A7%A3%0A%20%20%20%20coins%20%3D%20%5B1,%205,%2010,%2020,%2050,%20100%5D%0A%20%20%20%20amt%20%3D%20186%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins,%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D,%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%88%B0%20%7Bamt%7D%20%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B8%81%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29%0A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%EF%BC%9A%E6%97%A0%E6%B3%95%E4%BF%9D%E8%AF%81%E6%89%BE%E5%88%B0%E5%85%A8%E5%B1%80%E6%9C%80%E4%BC%98%E8%A7%A3%0A%20%20%20%20coins%20%3D%20%5B1,%2020,%2050%5D%0A%20%20%20%20amt%20%3D%2060%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins,%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D,%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%88%B0%20%7Bamt%7D%20%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B8%81%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E5%AE%9E%E9%99%85%E4%B8%8A%E9%9C%80%E8%A6%81%E7%9A%84%E6%9C%80%E5%B0%91%E6%95%B0%E9%87%8F%E4%B8%BA%203%20%EF%BC%8C%E5%8D%B3%2020%20%2B%2020%20%2B%2020%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_greedy/fractional_knapsack.md ================================================ https://pythontutor.com/render.html#code=class%20Item%3A%0A%20%20%20%20%22%22%22%E7%89%A9%E5%93%81%22%22%22%0A%20%20%20%20def%20__init__%28self,%20w%3A%20int,%20v%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.w%20%3D%20w%20%20%23%20%E7%89%A9%E5%93%81%E9%87%8D%E9%87%8F%0A%20%20%20%20%20%20%20%20self.v%20%3D%20v%20%20%23%20%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%0A%0Adef%20fractional_knapsack%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%88%86%E6%95%B0%E8%83%8C%E5%8C%85%EF%BC%9A%E8%B4%AA%E5%BF%83%22%22%22%0A%20%20%20%20%23%20%E5%88%9B%E5%BB%BA%E7%89%A9%E5%93%81%E5%88%97%E8%A1%A8%EF%BC%8C%E5%8C%85%E5%90%AB%E4%B8%A4%E4%B8%AA%E5%B1%9E%E6%80%A7%EF%BC%9A%E9%87%8D%E9%87%8F%E3%80%81%E4%BB%B7%E5%80%BC%0A%20%20%20%20items%20%3D%20%5BItem%28w,%20v%29%20for%20w,%20v%20in%20zip%28wgt,%20val%29%5D%0A%20%20%20%20%23%20%E6%8C%89%E7%85%A7%E5%8D%95%E4%BD%8D%E4%BB%B7%E5%80%BC%20item.v%20/%20item.w%20%E4%BB%8E%E9%AB%98%E5%88%B0%E4%BD%8E%E8%BF%9B%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20items.sort%28key%3Dlambda%20item%3A%20item.v%20/%20item.w,%20reverse%3DTrue%29%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E8%B4%AA%E5%BF%83%E9%80%89%E6%8B%A9%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20for%20item%20in%20items%3A%0A%20%20%20%20%20%20%20%20if%20item.w%20%3C%3D%20cap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%E5%85%85%E8%B6%B3%EF%BC%8C%E5%88%99%E5%B0%86%E5%BD%93%E5%89%8D%E7%89%A9%E5%93%81%E6%95%B4%E4%B8%AA%E8%A3%85%E8%BF%9B%E8%83%8C%E5%8C%85%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20item.v%0A%20%20%20%20%20%20%20%20%20%20%20%20cap%20-%3D%20item.w%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%E4%B8%8D%E8%B6%B3%EF%BC%8C%E5%88%99%E5%B0%86%E5%BD%93%E5%89%8D%E7%89%A9%E5%93%81%E7%9A%84%E4%B8%80%E9%83%A8%E5%88%86%E8%A3%85%E8%BF%9B%E8%83%8C%E5%8C%85%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20%28item.v%20/%20item.w%29%20*%20cap%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B7%B2%E6%97%A0%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%9B%A0%E6%AD%A4%E8%B7%B3%E5%87%BA%E5%BE%AA%E7%8E%AF%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20fractional_knapsack%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_greedy/max_capacity.md ================================================ https://pythontutor.com/render.html#code=def%20max_capacity%28ht%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%EF%BC%9A%E8%B4%AA%E5%BF%83%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20i,%20j%EF%BC%8C%E4%BD%BF%E5%85%B6%E5%88%86%E5%88%97%E6%95%B0%E7%BB%84%E4%B8%A4%E7%AB%AF%0A%20%20%20%20i,%20j%20%3D%200,%20len%28ht%29%20-%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E4%B8%BA%200%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E8%B4%AA%E5%BF%83%E9%80%89%E6%8B%A9%EF%BC%8C%E7%9B%B4%E8%87%B3%E4%B8%A4%E6%9D%BF%E7%9B%B8%E9%81%87%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%0A%20%20%20%20%20%20%20%20cap%20%3D%20min%28ht%5Bi%5D,%20ht%5Bj%5D%29%20*%20%28j%20-%20i%29%0A%20%20%20%20%20%20%20%20res%20%3D%20max%28res,%20cap%29%0A%20%20%20%20%20%20%20%20%23%20%E5%90%91%E5%86%85%E7%A7%BB%E5%8A%A8%E7%9F%AD%E6%9D%BF%0A%20%20%20%20%20%20%20%20if%20ht%5Bi%5D%20%3C%20ht%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20ht%20%3D%20%5B3,%208,%205,%202,%207,%207,%203,%204%5D%0A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20max_capacity%28ht%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_greedy/max_product_cutting.md ================================================ https://pythontutor.com/render.html#code=import%20math%0A%0Adef%20max_product_cutting%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%88%87%E5%88%86%E4%B9%98%E7%A7%AF%EF%BC%9A%E8%B4%AA%E5%BF%83%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%20n%20%3C%3D%203%20%E6%97%B6%EF%BC%8C%E5%BF%85%E9%A1%BB%E5%88%87%E5%88%86%E5%87%BA%E4%B8%80%E4%B8%AA%201%0A%20%20%20%20if%20n%20%3C%3D%203%3A%0A%20%20%20%20%20%20%20%20return%201%20*%20%28n%20-%201%29%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%E5%9C%B0%E5%88%87%E5%88%86%E5%87%BA%203%20%EF%BC%8Ca%20%E4%B8%BA%203%20%E7%9A%84%E4%B8%AA%E6%95%B0%EF%BC%8Cb%20%E4%B8%BA%E4%BD%99%E6%95%B0%0A%20%20%20%20a,%20b%20%3D%20n%20//%203,%20n%20%25%203%0A%20%20%20%20if%20b%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E4%BD%99%E6%95%B0%E4%B8%BA%201%20%E6%97%B6%EF%BC%8C%E5%B0%86%E4%B8%80%E5%AF%B9%201%20*%203%20%E8%BD%AC%E5%8C%96%E4%B8%BA%202%20*%202%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283,%20a%20-%201%29%29%20*%202%20*%202%0A%20%20%20%20if%20b%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E4%BD%99%E6%95%B0%E4%B8%BA%202%20%E6%97%B6%EF%BC%8C%E4%B8%8D%E5%81%9A%E5%A4%84%E7%90%86%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283,%20a%29%29%20*%202%0A%20%20%20%20%23%20%E5%BD%93%E4%BD%99%E6%95%B0%E4%B8%BA%200%20%E6%97%B6%EF%BC%8C%E4%B8%8D%E5%81%9A%E5%A4%84%E7%90%86%0A%20%20%20%20return%20int%28math.pow%283,%20a%29%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2058%0A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20max_product_cutting%28n%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%88%87%E5%88%86%E4%B9%98%E7%A7%AF%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_hashing/array_hash_map.md ================================================ https://pythontutor.com/render.html#code=class%20Pair%3A%0A%20%20%20%20%22%22%22%E9%94%AE%E5%80%BC%E5%AF%B9%22%22%22%0A%20%20%20%20def%20__init__%28self,%20key%3A%20int,%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0A%0Aclass%20ArrayHashMap%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E6%95%B0%E7%BB%84%E5%AE%9E%E7%8E%B0%E7%9A%84%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%EF%BC%8C%E5%8C%85%E5%90%AB%2020%20%E4%B8%AA%E6%A1%B6%0A%20%20%20%20%20%20%20%20self.buckets%3A%20list%5BPair%20%7C%20None%5D%20%3D%20%5BNone%5D%20*%2020%0A%0A%20%20%20%20def%20hash_func%28self,%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%93%88%E5%B8%8C%E5%87%BD%E6%95%B0%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20key%20%25%2020%0A%20%20%20%20%20%20%20%20return%20index%0A%0A%20%20%20%20def%20get%28self,%20key%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20pair%3A%20Pair%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20if%20pair%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20pair.val%0A%0A%20%20%20%20def%20put%28self,%20key%3A%20int,%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key,%20val%29%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20pair%0A%0A%20%20%20%20def%20remove%28self,%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20%23%20%E7%BD%AE%E4%B8%BA%20None%20%EF%BC%8C%E4%BB%A3%E8%A1%A8%E5%88%A0%E9%99%A4%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20None%0A%0A%20%20%20%20def%20entry_set%28self%29%20-%3E%20list%5BPair%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%89%80%E6%9C%89%E9%94%AE%E5%80%BC%E5%AF%B9%22%22%22%0A%20%20%20%20%20%20%20%20result%3A%20list%5BPair%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20key_set%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%89%80%E6%9C%89%E9%94%AE%22%22%22%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.key%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20value_set%28self%29%20-%3E%20list%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%89%80%E6%9C%89%E5%80%BC%22%22%22%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.val%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%89%93%E5%8D%B0%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print%28pair.key,%20%22-%3E%22,%20pair.val%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20ArrayHashMap%28%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20hmap.put%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hmap.put%2815937,%20%22%E5%B0%8F%E5%95%B0%22%29%0A%20%20%20%20hmap.put%2816750,%20%22%E5%B0%8F%E7%AE%97%22%29%0A%20%20%20%20hmap.put%2813276,%20%22%E5%B0%8F%E6%B3%95%22%29%0A%20%20%20%20hmap.put%2810583,%20%22%E5%B0%8F%E9%B8%AD%22%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20name%20%3D%20hmap.get%2815937%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20hmap.remove%2810583%29%0A%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20print%28%22%5Cn%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20Key-%3EValue%22%29%0A%20%20%20%20for%20pair%20in%20hmap.entry_set%28%29%3A%0A%20%20%20%20%20%20%20%20print%28pair.key,%20%22-%3E%22,%20pair.val%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_hashing/hash_map_chaining.md ================================================ https://pythontutor.com/render.html#code=class%20Pair%3A%0A%20%20%20%20%22%22%22%E9%94%AE%E5%80%BC%E5%AF%B9%22%22%22%0A%20%20%20%20def%20__init__%28self,%20key%3A%20int,%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Aclass%20HashMapChaining%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E5%BC%8F%E5%9C%B0%E5%9D%80%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20self.capacity%20%3D%204%0A%20%20%20%20%20%20%20%20self.load_thres%20%3D%202.0%20/%203.0%0A%20%20%20%20%20%20%20%20self.extend_ratio%20%3D%202%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%0A%20%20%20%20def%20hash_func%28self,%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%93%88%E5%B8%8C%E5%87%BD%E6%95%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20key%20%25%20self.capacity%0A%0A%20%20%20%20def%20load_factor%28self%29%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%B4%9F%E8%BD%BD%E5%9B%A0%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%20/%20self.capacity%0A%0A%20%20%20%20def%20get%28self,%20key%3A%20int%29%20-%3E%20str%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20pair.val%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20def%20put%28self,%20key%3A%20int,%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.load_factor%28%29%20%3E%20self.load_thres%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend%28%29%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pair.val%20%3D%20val%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key,%20val%29%0A%20%20%20%20%20%20%20%20bucket.append%28pair%29%0A%20%20%20%20%20%20%20%20self.size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self,%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bucket.remove%28pair%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.size%20-%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20def%20extend%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%89%A9%E5%AE%B9%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20buckets%20%3D%20self.buckets%0A%20%20%20%20%20%20%20%20self.capacity%20*%3D%20self.extend_ratio%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.put%28pair.key,%20pair.val%29%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%89%93%E5%8D%B0%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20res.append%28str%28pair.key%29%20%2B%20%22%20-%3E%20%22%20%2B%20pair.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28res%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hashmap%20%3D%20HashMapChaining%28%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.put%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hashmap.put%2815937,%20%22%E5%B0%8F%E5%95%B0%22%29%0A%20%20%20%20hashmap.put%2816750,%20%22%E5%B0%8F%E7%AE%97%22%29%0A%20%20%20%20hashmap.put%2813276,%20%22%E5%B0%8F%E6%B3%95%22%29%0A%20%20%20%20hashmap.put%2810583,%20%22%E5%B0%8F%E9%B8%AD%22%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20name%20%3D%20hashmap.get%2813276%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.remove%2812836%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_hashing/simple_hash.md ================================================ https://pythontutor.com/render.html#code=def%20add_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%8A%A0%E6%B3%95%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%2B%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20mul_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%B9%98%E6%B3%95%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%2031%20*%20hash%20%2B%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20xor_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%BC%82%E6%88%96%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%5E%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20rot_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%97%8B%E8%BD%AC%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%20%28hash%20%3C%3C%204%29%20%5E%20%28hash%20%3E%3E%2028%29%20%5E%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20key%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%0A%20%20%20%20hash%20%3D%20add_hash%28key%29%0A%20%20%20%20print%28f%22%E5%8A%A0%E6%B3%95%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20mul_hash%28key%29%0A%20%20%20%20print%28f%22%E4%B9%98%E6%B3%95%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20xor_hash%28key%29%0A%20%20%20%20print%28f%22%E5%BC%82%E6%88%96%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20rot_hash%28key%29%0A%20%20%20%20print%28f%22%E6%97%8B%E8%BD%AC%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_heap/my_heap.md ================================================ https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A1%B6%E5%A0%86%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%EF%BC%8C%E6%A0%B9%E6%8D%AE%E8%BE%93%E5%85%A5%E5%88%97%E8%A1%A8%E5%BB%BA%E5%A0%86%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8A%A8%E6%B7%BB%E5%8A%A0%E8%BF%9B%E5%A0%86%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%20%20%20%20%20%20%20%20%23%20%E5%A0%86%E5%8C%96%E9%99%A4%E5%8F%B6%E8%8A%82%E7%82%B9%E4%BB%A5%E5%A4%96%E7%9A%84%E5%85%B6%E4%BB%96%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.parent%28self.size%28%29%20-%201%29,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.sift_down%28i%29%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D,%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D,%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20sift_down%28self,%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%8A%82%E7%82%B9%20i,%20l,%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E8%8A%82%E7%82%B9%EF%BC%8C%E8%AE%B0%E4%B8%BA%20ma%0A%20%20%20%20%20%20%20%20%20%20%20%20l,%20r,%20ma%20%3D%20self.left%28i%29,%20self.right%28i%29,%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%8A%82%E7%82%B9%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l,%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E6%97%A0%E9%A1%BB%E7%BB%A7%E7%BB%AD%E5%A0%86%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i,%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8B%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B1,%202,%203,%204,%205%5D%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A1%B6%E5%A0%86%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8A%A8%E6%B7%BB%E5%8A%A0%E8%BF%9B%E5%A0%86%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.max_heap%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%20%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9,%208,%206,%206,%207,%205,%202,%201,%204,%203,%206,%202%5D%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20max_heap.peek%28%29%0A%20%20%20%20print%28f%22%5Cn%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E4%B8%BA%20%7Bpeek%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A1%B6%E5%A0%86%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8A%A8%E6%B7%BB%E5%8A%A0%E8%BF%9B%E5%A0%86%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D,%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D,%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20self.max_heap.append%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E4%BB%8E%E5%BA%95%E8%87%B3%E9%A1%B6%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20self.sift_up%28self.size%28%29%20-%201%29%0A%0A%20%20%20%20def%20sift_up%28self,%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E5%BA%95%E8%87%B3%E9%A1%B6%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E8%8A%82%E7%82%B9%20i%20%E7%9A%84%E7%88%B6%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20p%20%3D%20self.parent%28i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E2%80%9C%E8%B6%8A%E8%BF%87%E6%A0%B9%E8%8A%82%E7%82%B9%E2%80%9D%E6%88%96%E2%80%9C%E8%8A%82%E7%82%B9%E6%97%A0%E9%A1%BB%E4%BF%AE%E5%A4%8D%E2%80%9D%E6%97%B6%EF%BC%8C%E7%BB%93%E6%9D%9F%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20p%20%3C%200%20or%20self.max_heap%5Bi%5D%20%3C%3D%20self.max_heap%5Bp%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i,%20p%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8A%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20p%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9,%208,%206,%206,%207,%205,%202,%201,%204,%203,%206,%202%5D%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20val%20%3D%207%0A%20%20%20%20max_heap.push%28val%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A1%B6%E5%A0%86%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8A%A8%E6%B7%BB%E5%8A%A0%E8%BF%9B%E5%A0%86%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D,%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D,%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E7%A9%BA%E5%A4%84%E7%90%86%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E5%A0%86%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E6%A0%B9%E8%8A%82%E7%82%B9%E4%B8%8E%E6%9C%80%E5%8F%B3%E5%8F%B6%E8%8A%82%E7%82%B9%EF%BC%88%E4%BA%A4%E6%8D%A2%E9%A6%96%E5%85%83%E7%B4%A0%E4%B8%8E%E5%B0%BE%E5%85%83%E7%B4%A0%EF%BC%89%0A%20%20%20%20%20%20%20%20self.swap%280,%20self.size%28%29%20-%201%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20val%20%3D%20self.max_heap.pop%28%29%0A%20%20%20%20%20%20%20%20%23%20%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20self.sift_down%280%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20return%20val%0A%0A%20%20%20%20def%20sift_down%28self,%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%8A%82%E7%82%B9%20i,%20l,%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E8%8A%82%E7%82%B9%EF%BC%8C%E8%AE%B0%E4%B8%BA%20ma%0A%20%20%20%20%20%20%20%20%20%20%20%20l,%20r,%20ma%20%3D%20self.left%28i%29,%20self.right%28i%29,%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%8A%82%E7%82%B9%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l,%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E6%97%A0%E9%A1%BB%E7%BB%A7%E7%BB%AD%E5%A0%86%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i,%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8B%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9,%208,%207,%206,%207,%206,%202,%201,%204,%203,%206,%202,%205%5D%29%0A%0A%20%20%20%20%23%20%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%0A%20%20%20%20peek%20%3D%20max_heap.pop%28%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_heap/top_k.md ================================================ https://pythontutor.com/render.html#code=import%20heapq%0A%0Adef%20top_k_heap%28nums%3A%20list%5Bint%5D,%20k%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E5%A0%86%E6%9F%A5%E6%89%BE%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%20k%20%E4%B8%AA%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20heap%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E5%B0%86%E6%95%B0%E7%BB%84%E7%9A%84%E5%89%8D%20k%20%E4%B8%AA%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20for%20i%20in%20range%28k%29%3A%0A%20%20%20%20%20%20%20%20heapq.heappush%28heap,%20nums%5Bi%5D%29%0A%20%20%20%20%23%20%E4%BB%8E%E7%AC%AC%20k%2B1%20%E4%B8%AA%E5%85%83%E7%B4%A0%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BF%9D%E6%8C%81%E5%A0%86%E7%9A%84%E9%95%BF%E5%BA%A6%E4%B8%BA%20k%0A%20%20%20%20for%20i%20in%20range%28k,%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E5%A4%A7%E4%BA%8E%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%EF%BC%8C%E5%88%99%E5%B0%86%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E3%80%81%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3E%20heap%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappop%28heap%29%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappush%28heap,%20nums%5Bi%5D%29%0A%20%20%20%20return%20heap%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%207,%206,%203,%202%5D%0A%20%20%20%20k%20%3D%203%0A%0A%20%20%20%20res%20%3D%20top_k_heap%28nums,%20k%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_searching/binary_search.md ================================================ https://pythontutor.com/render.html#code=def%20binary_search%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%20%EF%BC%8C%E5%8D%B3%20i,%20j%20%E5%88%86%E5%88%AB%E6%8C%87%E5%90%91%E6%95%B0%E7%BB%84%E9%A6%96%E5%85%83%E7%B4%A0%E3%80%81%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%EF%BC%8C%E5%BD%93%E6%90%9C%E7%B4%A2%E5%8C%BA%E9%97%B4%E4%B8%BA%E7%A9%BA%E6%97%B6%E8%B7%B3%E5%87%BA%EF%BC%88%E5%BD%93%20i%20%3E%20j%20%E6%97%B6%E4%B8%BA%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%90%86%E8%AE%BA%E4%B8%8A%20Python%20%E7%9A%84%E6%95%B0%E5%AD%97%E5%8F%AF%E4%BB%A5%E6%97%A0%E9%99%90%E5%A4%A7%EF%BC%88%E5%8F%96%E5%86%B3%E4%BA%8E%E5%86%85%E5%AD%98%E5%A4%A7%E5%B0%8F%EF%BC%89%EF%BC%8C%E6%97%A0%E9%A1%BB%E8%80%83%E8%99%91%E5%A4%A7%E6%95%B0%E8%B6%8A%E7%95%8C%E9%97%AE%E9%A2%98%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E6%AD%A4%E6%83%85%E5%86%B5%E8%AF%B4%E6%98%8E%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E6%AD%A4%E6%83%85%E5%86%B5%E8%AF%B4%E6%98%8E%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20return%20-1%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%208,%2012,%2015,%2023,%2026,%2031,%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums,%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22,%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20binary_search_lcro%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%B7%A6%E9%97%AD%E5%8F%B3%E5%BC%80%E5%8C%BA%E9%97%B4%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A6%E9%97%AD%E5%8F%B3%E5%BC%80%E5%8C%BA%E9%97%B4%20%5B0,%20n%29%20%EF%BC%8C%E5%8D%B3%20i,%20j%20%E5%88%86%E5%88%AB%E6%8C%87%E5%90%91%E6%95%B0%E7%BB%84%E9%A6%96%E5%85%83%E7%B4%A0%E3%80%81%E5%B0%BE%E5%85%83%E7%B4%A0%2B1%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%EF%BC%8C%E5%BD%93%E6%90%9C%E7%B4%A2%E5%8C%BA%E9%97%B4%E4%B8%BA%E7%A9%BA%E6%97%B6%E8%B7%B3%E5%87%BA%EF%BC%88%E5%BD%93%20i%20%3D%20j%20%E6%97%B6%E4%B8%BA%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E6%AD%A4%E6%83%85%E5%86%B5%E8%AF%B4%E6%98%8E%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%29%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20%20%23%20%E6%AD%A4%E6%83%85%E5%86%B5%E8%AF%B4%E6%98%8E%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m%29%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20return%20-1%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%208,%2012,%2015,%2023,%2026,%2031,%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%B7%A6%E9%97%AD%E5%8F%B3%E5%BC%80%E5%8C%BA%E9%97%B4%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search_lcro%28nums,%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22,%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_searching/binary_search_edge.md ================================================ https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_left_edge%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%9C%80%E5%B7%A6%E4%B8%80%E4%B8%AA%20target%22%22%22%0A%20%20%20%20%23%20%E7%AD%89%E4%BB%B7%E4%BA%8E%E6%9F%A5%E6%89%BE%20target%20%E7%9A%84%E6%8F%92%E5%85%A5%E7%82%B9%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums,%20target%29%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20i%20%3D%3D%20len%28nums%29%20or%20nums%5Bi%5D%20!%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E7%B4%A2%E5%BC%95%20i%0A%20%20%20%20return%20i%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%206,%206,%206,%206,%2010,%2012,%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%B7%A6%E8%BE%B9%E7%95%8C%E5%92%8C%E5%8F%B3%E8%BE%B9%E7%95%8C%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_left_edge%28nums,%20target%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%B7%A6%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_right_edge%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%9C%80%E5%8F%B3%E4%B8%80%E4%B8%AA%20target%22%22%22%0A%20%20%20%20%23%20%E8%BD%AC%E5%8C%96%E4%B8%BA%E6%9F%A5%E6%89%BE%E6%9C%80%E5%B7%A6%E4%B8%80%E4%B8%AA%20target%20%2B%201%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums,%20target%20%2B%201%29%0A%20%20%20%20%23%20j%20%E6%8C%87%E5%90%91%E6%9C%80%E5%8F%B3%E4%B8%80%E4%B8%AA%20target%20%EF%BC%8Ci%20%E6%8C%87%E5%90%91%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20j%20%3D%3D%20-1%20or%20nums%5Bj%5D%20!%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20return%20j%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%206,%206,%206,%206,%2010,%2012,%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%B7%A6%E8%BE%B9%E7%95%8C%E5%92%8C%E5%8F%B3%E8%BE%B9%E7%95%8C%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_right_edge%28nums,%20target%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%8F%B3%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_searching/binary_search_insertion.md ================================================ https://pythontutor.com/render.html#code=def%20binary_search_insertion_simple%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%EF%BC%88%E6%97%A0%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20m%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%208,%2012,%2015,%2023,%2026,%2031,%2035%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion_simple%28nums,%20target%29%0A%20%20%20%20print%28f%22%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E6%8F%92%E5%85%A5%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%206,%206,%206,%206,%2010,%2012,%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion%28nums,%20target%29%0A%20%20%20%20print%28f%22%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E6%8F%92%E5%85%A5%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_searching/two_sum.md ================================================ https://pythontutor.com/render.html#code=def%20two_sum_brute_force%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%E4%B8%80%EF%BC%9A%E6%9A%B4%E5%8A%9B%E6%9E%9A%E4%B8%BE%22%22%22%0A%20%20%20%20%23%20%E4%B8%A4%E5%B1%82%E5%BE%AA%E7%8E%AF%EF%BC%8C%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E4%B8%BA%20O%28n%5E2%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201,%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%2B%20nums%5Bj%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bi,%20j%5D%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2,%207,%2011,%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_brute_force%28nums,%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20two_sum_hash_table%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%E4%BA%8C%EF%BC%9A%E8%BE%85%E5%8A%A9%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%20%20%20%20%23%20%E8%BE%85%E5%8A%A9%E5%93%88%E5%B8%8C%E8%A1%A8%EF%BC%8C%E7%A9%BA%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E4%B8%BA%20O%28n%29%0A%20%20%20%20dic%20%3D%20%7B%7D%0A%20%20%20%20%23%20%E5%8D%95%E5%B1%82%E5%BE%AA%E7%8E%AF%EF%BC%8C%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E4%B8%BA%20O%28n%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20target%20-%20nums%5Bi%5D%20in%20dic%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bdic%5Btarget%20-%20nums%5Bi%5D%5D,%20i%5D%0A%20%20%20%20%20%20%20%20dic%5Bnums%5Bi%5D%5D%20%3D%20i%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2,%207,%2011,%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_hash_table%28nums,%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_sorting/bubble_sort.md ================================================ https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E8%87%B3%E8%AF%A5%E5%8C%BA%E9%97%B4%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%20nums%5Bj%5D%20%E4%B8%8E%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D,%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D,%20nums%5Bj%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20bubble_sort_with_flag%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A0%87%E5%BF%97%E4%BC%98%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20flag%20%3D%20False%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%87%E5%BF%97%E4%BD%8D%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E8%87%B3%E8%AF%A5%E5%8C%BA%E9%97%B4%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%20nums%5Bj%5D%20%E4%B8%8E%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D,%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D,%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20flag%20%3D%20True%20%20%23%20%E8%AE%B0%E5%BD%95%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20flag%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%20%20%23%20%E6%AD%A4%E8%BD%AE%E2%80%9C%E5%86%92%E6%B3%A1%E2%80%9D%E6%9C%AA%E4%BA%A4%E6%8D%A2%E4%BB%BB%E4%BD%95%E5%85%83%E7%B4%A0%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%B7%B3%E5%87%BA%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20bubble_sort_with_flag%28nums%29%0A%20%20%20%20print%28%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_sorting/bucket_sort.md ================================================ https://pythontutor.com/render.html#code=def%20bucket_sort%28nums%3A%20list%5Bfloat%5D%29%3A%0A%20%20%20%20%22%22%22%E6%A1%B6%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20k%20%3D%20n/2%20%E4%B8%AA%E6%A1%B6%EF%BC%8C%E9%A2%84%E6%9C%9F%E5%90%91%E6%AF%8F%E4%B8%AA%E6%A1%B6%E5%88%86%E9%85%8D%202%20%E4%B8%AA%E5%85%83%E7%B4%A0%0A%20%20%20%20k%20%3D%20len%28nums%29%20//%202%0A%20%20%20%20buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28k%29%5D%0A%20%20%20%20%23%201.%20%E5%B0%86%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E5%88%86%E9%85%8D%E5%88%B0%E5%90%84%E4%B8%AA%E6%A1%B6%E4%B8%AD%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E8%8C%83%E5%9B%B4%E4%B8%BA%20%5B0,%201%29%EF%BC%8C%E4%BD%BF%E7%94%A8%20num%20*%20k%20%E6%98%A0%E5%B0%84%E5%88%B0%E7%B4%A2%E5%BC%95%E8%8C%83%E5%9B%B4%20%5B0,%20k-1%5D%0A%20%20%20%20%20%20%20%20i%20%3D%20int%28num%20*%20k%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%20num%20%E6%B7%BB%E5%8A%A0%E8%BF%9B%E6%A1%B6%20i%0A%20%20%20%20%20%20%20%20buckets%5Bi%5D.append%28num%29%0A%20%20%20%20%23%202.%20%E5%AF%B9%E5%90%84%E4%B8%AA%E6%A1%B6%E6%89%A7%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%86%85%E7%BD%AE%E6%8E%92%E5%BA%8F%E5%87%BD%E6%95%B0%EF%BC%8C%E4%B9%9F%E5%8F%AF%E4%BB%A5%E6%9B%BF%E6%8D%A2%E6%88%90%E5%85%B6%E4%BB%96%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%0A%20%20%20%20%20%20%20%20bucket.sort%28%29%0A%20%20%20%20%23%203.%20%E9%81%8D%E5%8E%86%E6%A1%B6%E5%90%88%E5%B9%B6%E7%BB%93%E6%9E%9C%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20for%20num%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%AE%BE%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E4%B8%BA%E6%B5%AE%E7%82%B9%E6%95%B0%EF%BC%8C%E8%8C%83%E5%9B%B4%E4%B8%BA%20%5B0,%201%29%0A%20%20%20%20nums%20%3D%20%5B0.49,%200.96,%200.82,%200.09,%200.57,%200.43,%200.91,%200.75,%200.15,%200.37%5D%0A%20%20%20%20bucket_sort%28nums%29%0A%20%20%20%20print%28%22%E6%A1%B6%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_sorting/counting_sort.md ================================================ https://pythontutor.com/render.html#code=def%20counting_sort_naive%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%AE%80%E5%8D%95%E5%AE%9E%E7%8E%B0%EF%BC%8C%E6%97%A0%E6%B3%95%E7%94%A8%E4%BA%8E%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%0A%20%20%20%20%23%201.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%200%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20max%28m,%20num%29%0A%20%20%20%20%23%202.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E9%81%8D%E5%8E%86%20counter%20%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20num%20in%20range%28m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28counter%5Bnum%5D%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%200,%201,%202,%200,%204,%200,%202,%202,%204%5D%0A%20%20%20%20counting_sort_naive%28nums%29%0A%20%20%20%20print%28f%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%EF%BC%88%E6%97%A0%E6%B3%95%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%EF%BC%89%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20counting_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%AE%8C%E6%95%B4%E5%AE%9E%E7%8E%B0%EF%BC%8C%E5%8F%AF%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%EF%BC%8C%E5%B9%B6%E4%B8%94%E6%98%AF%E7%A8%B3%E5%AE%9A%E6%8E%92%E5%BA%8F%0A%20%20%20%20%23%201.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%202.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E6%B1%82%20counter%20%E7%9A%84%E5%89%8D%E7%BC%80%E5%92%8C%EF%BC%8C%E5%B0%86%E2%80%9C%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E2%80%9D%E8%BD%AC%E6%8D%A2%E4%B8%BA%E2%80%9C%E5%B0%BE%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20%23%20%E5%8D%B3%20counter%5Bnum%5D-1%20%E6%98%AF%20num%20%E5%9C%A8%20res%20%E4%B8%AD%E6%9C%80%E5%90%8E%E4%B8%80%E6%AC%A1%E5%87%BA%E7%8E%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20for%20i%20in%20range%28m%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%20%2B%201%5D%20%2B%3D%20counter%5Bi%5D%0A%20%20%20%20%23%204.%20%E5%80%92%E5%BA%8F%E9%81%8D%E5%8E%86%20nums%20%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%BB%93%E6%9E%9C%E6%95%B0%E7%BB%84%20res%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%20res%20%E7%94%A8%E4%BA%8E%E8%AE%B0%E5%BD%95%E7%BB%93%E6%9E%9C%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20res%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20res%5Bcounter%5Bnum%5D%20-%201%5D%20%3D%20num%20%20%23%20%E5%B0%86%20num%20%E6%94%BE%E7%BD%AE%E5%88%B0%E5%AF%B9%E5%BA%94%E7%B4%A2%E5%BC%95%E5%A4%84%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20-%3D%201%20%20%23%20%E4%BB%A4%E5%89%8D%E7%BC%80%E5%92%8C%E8%87%AA%E5%87%8F%201%20%EF%BC%8C%E5%BE%97%E5%88%B0%E4%B8%8B%E6%AC%A1%E6%94%BE%E7%BD%AE%20num%20%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%BB%93%E6%9E%9C%E6%95%B0%E7%BB%84%20res%20%E8%A6%86%E7%9B%96%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%200,%201,%202,%200,%204,%200,%202,%202,%204%5D%0A%20%20%20%20counting_sort%28nums%29%0A%20%20%20%20print%28f%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_sorting/heap_sort.md ================================================ https://pythontutor.com/render.html#code=def%20sift_down%28nums%3A%20list%5Bint%5D,%20n%3A%20int,%20i%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E7%9A%84%E9%95%BF%E5%BA%A6%E4%B8%BA%20n%20%EF%BC%8C%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%8A%82%E7%82%B9%20i,%20l,%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E8%8A%82%E7%82%B9%EF%BC%8C%E8%AE%B0%E4%B8%BA%20ma%0A%20%20%20%20%20%20%20%20l%20%3D%202%20*%20i%20%2B%201%0A%20%20%20%20%20%20%20%20r%20%3D%202%20*%20i%20%2B%202%0A%20%20%20%20%20%20%20%20ma%20%3D%20i%0A%20%20%20%20%20%20%20%20if%20l%20%3C%20n%20and%20nums%5Bl%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20if%20r%20%3C%20n%20and%20nums%5Br%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%8A%82%E7%82%B9%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l,%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E6%97%A0%E9%A1%BB%E7%BB%A7%E7%BB%AD%E5%A0%86%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bma%5D%20%3D%20nums%5Bma%5D,%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8B%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0Adef%20heap_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%BB%BA%E5%A0%86%E6%93%8D%E4%BD%9C%EF%BC%9A%E5%A0%86%E5%8C%96%E9%99%A4%E5%8F%B6%E8%8A%82%E7%82%B9%E4%BB%A5%E5%A4%96%E7%9A%84%E5%85%B6%E4%BB%96%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20//%202%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20sift_down%28nums,%20len%28nums%29,%20i%29%0A%20%20%20%20%23%20%E4%BB%8E%E5%A0%86%E4%B8%AD%E6%8F%90%E5%8F%96%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E5%BE%AA%E7%8E%AF%20n-1%20%E8%BD%AE%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E6%A0%B9%E8%8A%82%E7%82%B9%E4%B8%8E%E6%9C%80%E5%8F%B3%E5%8F%B6%E8%8A%82%E7%82%B9%EF%BC%88%E4%BA%A4%E6%8D%A2%E9%A6%96%E5%85%83%E7%B4%A0%E4%B8%8E%E5%B0%BE%E5%85%83%E7%B4%A0%EF%BC%89%0A%20%20%20%20%20%20%20%20nums%5B0%5D,%20nums%5Bi%5D%20%3D%20nums%5Bi%5D,%20nums%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E4%BB%A5%E6%A0%B9%E8%8A%82%E7%82%B9%E4%B8%BA%E8%B5%B7%E7%82%B9%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E8%BF%9B%E8%A1%8C%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20sift_down%28nums,%20i,%200%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20heap_sort%28nums%29%0A%20%20%20%20print%28%22%E5%A0%86%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_sorting/insertion_sort.md ================================================ https://pythontutor.com/render.html#code=def%20insertion_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i-1%5D%0A%20%20%20%20for%20i%20in%20range%281,%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20base%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%20base%20%E6%8F%92%E5%85%A5%E5%88%B0%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i-1%5D%20%E4%B8%AD%E7%9A%84%E6%AD%A3%E7%A1%AE%E4%BD%8D%E7%BD%AE%0A%20%20%20%20%20%20%20%20while%20j%20%3E%3D%200%20and%20nums%5Bj%5D%20%3E%20base%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%5D%20%20%23%20%E5%B0%86%20nums%5Bj%5D%20%E5%90%91%E5%8F%B3%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20base%20%20%23%20%E5%B0%86%20base%20%E8%B5%8B%E5%80%BC%E5%88%B0%E6%AD%A3%E7%A1%AE%E4%BD%8D%E7%BD%AE%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20insertion_sort%28nums%29%0A%20%20%20%20print%28%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_sorting/merge_sort.md ================================================ https://pythontutor.com/render.html#code=def%20merge%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20mid%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%90%88%E5%B9%B6%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bleft,%20mid%5D,%20%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bmid%2B1,%20right%5D%0A%20%20%20%20%23%20%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%20tmp%20%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E6%94%BE%E5%90%88%E5%B9%B6%E5%90%8E%E7%9A%84%E7%BB%93%E6%9E%9C%0A%20%20%20%20tmp%20%3D%20%5B0%5D%20*%20%28right%20-%20left%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E8%B5%B7%E5%A7%8B%E7%B4%A2%E5%BC%95%0A%20%20%20%20i,%20j,%20k%20%3D%20left,%20mid%20%2B%201,%200%0A%20%20%20%20%23%20%E5%BD%93%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E9%83%BD%E8%BF%98%E6%9C%89%E5%85%83%E7%B4%A0%E6%97%B6%EF%BC%8C%E8%BF%9B%E8%A1%8C%E6%AF%94%E8%BE%83%E5%B9%B6%E5%B0%86%E8%BE%83%E5%B0%8F%E7%9A%84%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%88%B0%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%20and%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3C%3D%20nums%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%86%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%89%A9%E4%BD%99%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%88%B0%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20while%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%86%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%20tmp%20%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%9B%9E%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%20%E7%9A%84%E5%AF%B9%E5%BA%94%E5%8C%BA%E9%97%B4%0A%20%20%20%20for%20k%20in%20range%280,%20len%28tmp%29%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bleft%20%2B%20k%5D%20%3D%20tmp%5Bk%5D%0A%0A%0Adef%20merge_sort%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%20%20%23%20%E5%BD%93%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E6%97%B6%E7%BB%88%E6%AD%A2%E9%80%92%E5%BD%92%0A%20%20%20%20%23%20%E5%88%92%E5%88%86%E9%98%B6%E6%AE%B5%0A%20%20%20%20mid%20%3D%20%28left%20%2B%20right%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%0A%20%20%20%20merge_sort%28nums,%20left,%20mid%29%20%20%23%20%E9%80%92%E5%BD%92%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20merge_sort%28nums,%20mid%20%2B%201,%20right%29%20%20%23%20%E9%80%92%E5%BD%92%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20%23%20%E5%90%88%E5%B9%B6%E9%98%B6%E6%AE%B5%0A%20%20%20%20merge%28nums,%20left,%20mid,%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B7,%203,%202,%206,%200,%201,%205,%204%5D%0A%20%20%20%20merge_sort%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_sorting/quick_sort.md ================================================ https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20i,%20j%20%3D%20left,%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E4%BB%8E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E4%BB%8E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bj%5D%20%3D%20nums%5Bj%5D,%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%86%E5%9F%BA%E5%87%86%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E4%B8%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%88%86%E7%95%8C%E7%BA%BF%0A%20%20%20%20nums%5Bi%5D,%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D,%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2,%204,%201,%200,%203,%205%5D%0A%20%20%20%20partition%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20i,%20j%20%3D%20left,%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E4%BB%8E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E4%BB%8E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bj%5D%20%3D%20nums%5Bj%5D,%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%86%E5%9F%BA%E5%87%86%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E4%B8%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%88%86%E7%95%8C%E7%BA%BF%0A%20%20%20%20nums%5Bi%5D,%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D,%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E6%97%B6%E7%BB%88%E6%AD%A2%E9%80%92%E5%BD%92%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%0A%20%20%20%20pivot%20%3D%20partition%28nums,%20left,%20right%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E3%80%81%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20quick_sort%28nums,%20left,%20pivot%20-%201%29%0A%20%20%20%20quick_sort%28nums,%20pivot%20%2B%201,%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%0A%20%20%20%20nums%20%3D%20%5B2,%204,%201,%200,%203,%205%5D%0A%20%20%20%20quick_sort%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20median_three%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20mid%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%80%89%E5%8F%96%E4%B8%89%E4%B8%AA%E5%80%99%E9%80%89%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0%22%22%22%0A%20%20%20%20l,%20m,%20r%20%3D%20nums%5Bleft%5D,%20nums%5Bmid%5D,%20nums%5Bright%5D%0A%20%20%20%20if%20%28l%20%3C%3D%20m%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20m%20%3C%3D%20l%29%3A%0A%20%20%20%20%20%20%20%20return%20mid%20%20%23%20m%20%E5%9C%A8%20l%20%E5%92%8C%20r%20%E4%B9%8B%E9%97%B4%0A%20%20%20%20if%20%28m%20%3C%3D%20l%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20l%20%3C%3D%20m%29%3A%0A%20%20%20%20%20%20%20%20return%20left%20%20%23%20l%20%E5%9C%A8%20m%20%E5%92%8C%20r%20%E4%B9%8B%E9%97%B4%0A%20%20%20%20return%20right%0A%0Adef%20partition%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%EF%BC%88%E4%B8%89%E6%95%B0%E5%8F%96%E4%B8%AD%E5%80%BC%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20med%20%3D%20median_three%28nums,%20left,%20%28left%20%2B%20right%29%20//%202,%20right%29%0A%20%20%20%20%23%20%E5%B0%86%E4%B8%AD%E4%BD%8D%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E6%95%B0%E7%BB%84%E6%9C%80%E5%B7%A6%E7%AB%AF%0A%20%20%20%20nums%5Bleft%5D,%20nums%5Bmed%5D%20%3D%20nums%5Bmed%5D,%20nums%5Bleft%5D%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20i,%20j%20%3D%20left,%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E4%BB%8E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E4%BB%8E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bj%5D%20%3D%20nums%5Bj%5D,%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%86%E5%9F%BA%E5%87%86%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E4%B8%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%88%86%E7%95%8C%E7%BA%BF%0A%20%20%20%20nums%5Bi%5D,%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D,%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%B8%AD%E4%BD%8D%E5%9F%BA%E5%87%86%E6%95%B0%E4%BC%98%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B2,%204,%201,%200,%203,%205%5D%0A%20%20%20%20partition%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%EF%BC%88%E4%B8%AD%E4%BD%8D%E5%9F%BA%E5%87%86%E6%95%B0%E4%BC%98%E5%8C%96%EF%BC%89%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20i,%20j%20%3D%20left,%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E4%BB%8E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E4%BB%8E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bj%5D%20%3D%20nums%5Bj%5D,%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%86%E5%9F%BA%E5%87%86%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E4%B8%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%88%86%E7%95%8C%E7%BA%BF%0A%20%20%20%20nums%5Bi%5D,%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D,%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%80%92%E5%BD%92%E4%BC%98%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E6%97%B6%E7%BB%88%E6%AD%A2%0A%20%20%20%20while%20left%20%3C%20right%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%E6%93%8D%E4%BD%9C%0A%20%20%20%20%20%20%20%20pivot%20%3D%20partition%28nums,%20left,%20right%29%0A%20%20%20%20%20%20%20%20%23%20%E5%AF%B9%E4%B8%A4%E4%B8%AA%E5%AD%90%E6%95%B0%E7%BB%84%E4%B8%AD%E8%BE%83%E7%9F%AD%E7%9A%84%E9%82%A3%E4%B8%AA%E6%89%A7%E8%A1%8C%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%0A%20%20%20%20%20%20%20%20if%20pivot%20-%20left%20%3C%20right%20-%20pivot%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums,%20left,%20pivot%20-%201%29%20%20%23%20%E9%80%92%E5%BD%92%E6%8E%92%E5%BA%8F%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20%20%20%20%20%20%20%20%20left%20%3D%20pivot%20%2B%201%20%20%23%20%E5%89%A9%E4%BD%99%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bpivot%20%2B%201,%20right%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums,%20pivot%20%2B%201,%20right%29%20%20%23%20%E9%80%92%E5%BD%92%E6%8E%92%E5%BA%8F%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20%20%20%20%20%20%20%20%20right%20%3D%20pivot%20-%201%20%20%23%20%E5%89%A9%E4%BD%99%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bleft,%20pivot%20-%201%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%80%92%E5%BD%92%E4%BC%98%E5%8C%96%EF%BC%89%0A%20%20%20%20nums%20%3D%20%5B2,%204,%201,%200,%203,%205%5D%0A%20%20%20%20quick_sort%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%80%92%E5%BD%92%E4%BC%98%E5%8C%96%EF%BC%89%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_sorting/radix_sort.md ================================================ https://pythontutor.com/render.html#code=def%20digit%28num%3A%20int,%20exp%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%85%83%E7%B4%A0%20num%20%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E5%85%B6%E4%B8%AD%20exp%20%3D%2010%5E%28k-1%29%22%22%22%0A%20%20%20%20%23%20%E4%BC%A0%E5%85%A5%20exp%20%E8%80%8C%E9%9D%9E%20k%20%E5%8F%AF%E4%BB%A5%E9%81%BF%E5%85%8D%E5%9C%A8%E6%AD%A4%E9%87%8D%E5%A4%8D%E6%89%A7%E8%A1%8C%E6%98%82%E8%B4%B5%E7%9A%84%E6%AC%A1%E6%96%B9%E8%AE%A1%E7%AE%97%0A%20%20%20%20return%20%28num%20//%20exp%29%20%25%2010%0A%0Adef%20counting_sort_digit%28nums%3A%20list%5Bint%5D,%20exp%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A0%B9%E6%8D%AE%20nums%20%E7%AC%AC%20k%20%E4%BD%8D%E6%8E%92%E5%BA%8F%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%8D%81%E8%BF%9B%E5%88%B6%E7%9A%84%E4%BD%8D%E8%8C%83%E5%9B%B4%E4%B8%BA%200~9%20%EF%BC%8C%E5%9B%A0%E6%AD%A4%E9%9C%80%E8%A6%81%E9%95%BF%E5%BA%A6%E4%B8%BA%2010%20%E7%9A%84%E6%A1%B6%E6%95%B0%E7%BB%84%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%2010%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E7%BB%9F%E8%AE%A1%200~9%20%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D,%20exp%29%20%20%23%20%E8%8E%B7%E5%8F%96%20nums%5Bi%5D%20%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E8%AE%B0%E4%B8%BA%20d%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20%2B%3D%201%20%20%23%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E5%AD%97%20d%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20%E6%B1%82%E5%89%8D%E7%BC%80%E5%92%8C%EF%BC%8C%E5%B0%86%E2%80%9C%E5%87%BA%E7%8E%B0%E4%B8%AA%E6%95%B0%E2%80%9D%E8%BD%AC%E6%8D%A2%E4%B8%BA%E2%80%9C%E6%95%B0%E7%BB%84%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20for%20i%20in%20range%281,%2010%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%5D%20%2B%3D%20counter%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E5%80%92%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%8C%E6%A0%B9%E6%8D%AE%E6%A1%B6%E5%86%85%E7%BB%9F%E8%AE%A1%E7%BB%93%E6%9E%9C%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%20res%0A%20%20%20%20res%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D,%20exp%29%0A%20%20%20%20%20%20%20%20j%20%3D%20counter%5Bd%5D%20-%201%20%20%23%20%E8%8E%B7%E5%8F%96%20d%20%E5%9C%A8%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20res%5Bj%5D%20%3D%20nums%5Bi%5D%20%20%23%20%E5%B0%86%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20-%3D%201%20%20%23%20%E5%B0%86%20d%20%E7%9A%84%E6%95%B0%E9%87%8F%E5%87%8F%201%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%BB%93%E6%9E%9C%E8%A6%86%E7%9B%96%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0Adef%20radix_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%88%A4%E6%96%AD%E6%9C%80%E5%A4%A7%E4%BD%8D%E6%95%B0%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%20%E6%8C%89%E7%85%A7%E4%BB%8E%E4%BD%8E%E4%BD%8D%E5%88%B0%E9%AB%98%E4%BD%8D%E7%9A%84%E9%A1%BA%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20exp%20%3D%201%0A%20%20%20%20while%20exp%20%3C%3D%20m%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%AF%B9%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%E6%89%A7%E8%A1%8C%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%201%20-%3E%20exp%20%3D%201%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%202%20-%3E%20exp%20%3D%2010%0A%20%20%20%20%20%20%20%20%23%20%E5%8D%B3%20exp%20%3D%2010%5E%28k-1%29%0A%20%20%20%20%20%20%20%20counting_sort_digit%28nums,%20exp%29%0A%20%20%20%20%20%20%20%20exp%20*%3D%2010%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%0A%20%20%20%20nums%20%3D%20%5B%0A%20%20%20%20%20%20%20%20105,%0A%20%20%20%20%20%20%20%20356,%0A%20%20%20%20%20%20%20%20428,%0A%20%20%20%20%20%20%20%20348,%0A%20%20%20%20%20%20%20%20818,%0A%20%20%20%20%5D%0A%20%20%20%20radix_sort%28nums%29%0A%20%20%20%20print%28%22%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_sorting/selection_sort.md ================================================ https://pythontutor.com/render.html#code=def%20selection_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bi,%20n-1%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%89%BE%E5%88%B0%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E5%86%85%E7%9A%84%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20k%20%3D%20i%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201,%20n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3C%20nums%5Bk%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20k%20%3D%20j%20%20%23%20%E8%AE%B0%E5%BD%95%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E8%AF%A5%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E4%B8%8E%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E7%9A%84%E9%A6%96%E4%B8%AA%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bk%5D%20%3D%20nums%5Bk%5D,%20nums%5Bi%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20selection_sort%28nums%29%0A%20%20%20%20print%28%22%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_stack_and_queue/array_queue.md ================================================ https://pythontutor.com/render.html#code=class%20ArrayQueue%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E7%8E%AF%E5%BD%A2%E6%95%B0%E7%BB%84%E5%AE%9E%E7%8E%B0%E7%9A%84%E9%98%9F%E5%88%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20size%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._nums%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20*%20size%20%20%23%20%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E9%98%9F%E5%88%97%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20%20%20%20%20self._front%3A%20int%20%3D%200%20%20%23%20%E9%98%9F%E9%A6%96%E6%8C%87%E9%92%88%EF%BC%8C%E6%8C%87%E5%90%91%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%20%20%23%20%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._nums%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E9%98%9F%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._size%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E9%98%9F%E5%88%97%E5%B7%B2%E6%BB%A1%22%29%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E9%98%9F%E5%B0%BE%E6%8C%87%E9%92%88%EF%BC%8C%E6%8C%87%E5%90%91%E9%98%9F%E5%B0%BE%E7%B4%A2%E5%BC%95%20%2B%201%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E5%8F%96%E4%BD%99%E6%93%8D%E4%BD%9C%E5%AE%9E%E7%8E%B0%20rear%20%E8%B6%8A%E8%BF%87%E6%95%B0%E7%BB%84%E5%B0%BE%E9%83%A8%E5%90%8E%E5%9B%9E%E5%88%B0%E5%A4%B4%E9%83%A8%0A%20%20%20%20%20%20%20%20rear%3A%20int%20%3D%20%28self._front%20%2B%20self._size%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%20num%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20%20%20%20%20self._nums%5Brear%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E9%98%9F%22%22%22%0A%20%20%20%20%20%20%20%20num%3A%20int%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20%23%20%E9%98%9F%E9%A6%96%E6%8C%87%E9%92%88%E5%90%91%E5%90%8E%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%EF%BC%8C%E8%8B%A5%E8%B6%8A%E8%BF%87%E5%B0%BE%E9%83%A8%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%E5%88%B0%E6%95%B0%E7%BB%84%E5%A4%B4%E9%83%A8%0A%20%20%20%20%20%20%20%20self._front%20%3D%20%28self._front%20%2B%201%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E9%98%9F%E5%88%97%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._nums%5Bself._front%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BF%94%E5%9B%9E%E5%88%97%E8%A1%A8%E7%94%A8%E4%BA%8E%E6%89%93%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20res%20%3D%20%5B0%5D%20*%20self.size%28%29%0A%20%20%20%20%20%20%20%20j%3A%20int%20%3D%20self._front%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20self._nums%5B%28j%20%25%20self.capacity%28%29%29%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20queue%20%3D%20ArrayQueue%2810%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_stack_and_queue/array_stack.md ================================================ https://pythontutor.com/render.html#code=class%20ArrayStack%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E6%95%B0%E7%BB%84%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%A0%88%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._stack%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._stack%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%20%3D%3D%20%5B%5D%0A%0A%20%20%20%20def%20push%28self,%20item%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E6%A0%88%22%22%22%0A%20%20%20%20%20%20%20%20self._stack.append%28item%29%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E6%A0%88%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E6%A0%88%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack.pop%28%29%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E6%A0%88%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack%5B-1%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BF%94%E5%9B%9E%E5%88%97%E8%A1%A8%E7%94%A8%E4%BA%8E%E6%89%93%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20stack%20%3D%20ArrayStack%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%0Aclass%20LinkedListQueue%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%93%BE%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E9%98%9F%E5%88%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._front%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%A4%B4%E8%8A%82%E7%82%B9%20front%0A%20%20%20%20%20%20%20%20self._rear%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B0%BE%E8%8A%82%E7%82%B9%20rear%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20not%20self._front%0A%0A%20%20%20%20def%20push%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E9%98%9F%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E8%8A%82%E7%82%B9%E5%90%8E%E6%B7%BB%E5%8A%A0%20num%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28num%29%0A%20%20%20%20%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E9%98%9F%E5%88%97%E4%B8%BA%E7%A9%BA%EF%BC%8C%E5%88%99%E4%BB%A4%E5%A4%B4%E3%80%81%E5%B0%BE%E8%8A%82%E7%82%B9%E9%83%BD%E6%8C%87%E5%90%91%E8%AF%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20if%20self._front%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._front%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E9%98%9F%E5%88%97%E4%B8%8D%E4%B8%BA%E7%A9%BA%EF%BC%8C%E5%88%99%E5%B0%86%E8%AF%A5%E8%8A%82%E7%82%B9%E6%B7%BB%E5%8A%A0%E5%88%B0%E5%B0%BE%E8%8A%82%E7%82%B9%E5%90%8E%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear.next%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E9%98%9F%22%22%22%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%A4%B4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20self._front%20%3D%20self._front.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E9%98%9F%E5%88%97%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._front.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BD%AC%E5%8C%96%E4%B8%BA%E5%88%97%E8%A1%A8%E7%94%A8%E4%BA%8E%E6%89%93%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20queue%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20temp%20%3D%20self._front%0A%20%20%20%20%20%20%20%20while%20temp%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28temp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20temp.next%0A%20%20%20%20%20%20%20%20return%20queue%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20queue%20%3D%20LinkedListQueue%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%20queue%20%3D%22,%20queue.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%90%8E%20queue%20%3D%22,%20queue.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%0Aclass%20LinkedListStack%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%93%BE%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%A0%88%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._peek%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20not%20self._peek%0A%0A%20%20%20%20def%20push%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E6%A0%88%22%22%22%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28val%29%0A%20%20%20%20%20%20%20%20node.next%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E6%A0%88%22%22%22%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20self._peek.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E6%A0%88%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._peek.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BD%AC%E5%8C%96%E4%B8%BA%E5%88%97%E8%A1%A8%E7%94%A8%E4%BA%8E%E6%89%93%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20arr%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20node%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20while%20node%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20arr.append%28node.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20node%20%3D%20node.next%0A%20%20%20%20%20%20%20%20arr.reverse%28%29%0A%20%20%20%20%20%20%20%20return%20arr%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20stack%20%3D%20LinkedListStack%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_tree/array_binary_tree.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20ArrayBinaryTree%3A%0A%20%20%20%20%22%22%22%E6%95%B0%E7%BB%84%E8%A1%A8%E7%A4%BA%E4%B8%8B%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20arr%3A%20list%5Bint%20%7C%20None%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._tree%20%3D%20list%28arr%29%0A%0A%20%20%20%20def%20size%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%97%E8%A1%A8%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._tree%29%0A%0A%20%20%20%20def%20val%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%B4%A2%E5%BC%95%E4%B8%BA%20i%20%E8%8A%82%E7%82%B9%E7%9A%84%E5%80%BC%22%22%22%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20self._tree%5Bi%5D%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%0A%0A%20%20%20%20def%20level_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20dfs%28self,%20i%3A%20int,%20order%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22pre%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.left%28i%29,%20order%29%0A%20%20%20%20%20%20%20%20%23%20%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22in%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.right%28i%29,%20order%29%0A%20%20%20%20%20%20%20%20%23%20%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22post%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%0A%20%20%20%20def%20pre_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280,%20order%3D%22pre%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20in_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280,%20order%3D%22in%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20post_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280,%20order%3D%22post%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20arr%20%3D%20%5B1,%202,%203,%204,%20None,%206,%20None%5D%0A%20%20%20%20abt%20%3D%20ArrayBinaryTree%28arr%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E8%8A%82%E7%82%B9%0A%20%20%20%20i%20%3D%201%0A%20%20%20%20l,%20r,%20p%20%3D%20abt.left%28i%29,%20abt.right%28i%29,%20abt.parent%28i%29%0A%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%A0%91%0A%20%20%20%20res%20%3D%20abt.level_order%28%29%0A%20%20%20%20res%20%3D%20abt.pre_order%28%29%0A%20%20%20%20res%20%3D%20abt.in_order%28%29%0A%20%20%20%20res%20%3D%20abt.post_order%28%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_tree/binary_search_tree.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%A9%BA%E6%A0%91%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20search%28self,%20num%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E6%89%BE%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20cur%20%3D%20self._root%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%9F%A5%E6%89%BE%EF%BC%8C%E8%B6%8A%E8%BF%87%E5%8F%B6%E8%8A%82%E7%82%B9%E5%90%8E%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A0%87%E8%8A%82%E7%82%B9%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A0%87%E8%8A%82%E7%82%B9%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20cur.val%20%3E%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E8%8A%82%E7%82%B9%EF%BC%8C%E8%B7%B3%E5%87%BA%E5%BE%AA%E7%8E%AF%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20return%20cur%0A%0A%20%20%20%20def%20insert%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E6%A0%91%E4%B8%BA%E7%A9%BA%EF%BC%8C%E5%88%99%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%9F%A5%E6%89%BE%EF%BC%8C%E8%B6%8A%E8%BF%87%E5%8F%B6%E8%8A%82%E7%82%B9%E5%90%8E%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20cur,%20pre%20%3D%20self._root,%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E9%87%8D%E5%A4%8D%E8%8A%82%E7%82%B9%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4,%202,%206,%201,%203,%205,%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E6%89%BE%E8%8A%82%E7%82%B9%0A%20%20%20%20node%20%3D%20bst.search%287%29%0A%20%20%20%20print%28%22%5Cn%E6%9F%A5%E6%89%BE%E5%88%B0%E7%9A%84%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%E4%B8%BA%3A%20%7B%7D%EF%BC%8C%E8%8A%82%E7%82%B9%E5%80%BC%20%3D%20%7B%7D%22.format%28node,%20node.val%29%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%A9%BA%E6%A0%91%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E6%A0%91%E4%B8%BA%E7%A9%BA%EF%BC%8C%E5%88%99%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%9F%A5%E6%89%BE%EF%BC%8C%E8%B6%8A%E8%BF%87%E5%8F%B6%E8%8A%82%E7%82%B9%E5%90%8E%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20cur,%20pre%20%3D%20self._root,%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E9%87%8D%E5%A4%8D%E8%8A%82%E7%82%B9%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4,%202,%206,%201,%203,%205,%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20bst.insert%2816%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20cur,%20pre%20%3D%20self._root,%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%20%20%20%20def%20remove%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E6%9F%A5%E6%89%BE%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20cur,%20pre%20%3D%20self._root,%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20if%20cur%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E8%8A%82%E7%82%B9%E6%95%B0%E9%87%8F%20%3D%200%20or%201%0A%20%20%20%20%20%20%20%20if%20cur.left%20is%20None%20or%20cur.right%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E5%AD%90%E8%8A%82%E7%82%B9%E6%95%B0%E9%87%8F%20%3D%200%20/%201%20%E6%97%B6%EF%BC%8C%20child%20%3D%20null%20/%20%E8%AF%A5%E5%AD%90%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20child%20%3D%20cur.left%20or%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur%20!%3D%20self._root%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20pre.left%20%3D%3D%20cur%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20child%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E8%8A%82%E7%82%B9%E6%95%B0%E9%87%8F%20%3D%202%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E4%B8%AD%20cur%20%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%3A%20TreeNode%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20tmp.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20tmp.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%80%92%E5%BD%92%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20self.remove%28tmp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%94%A8%20tmp%20%E8%A6%86%E7%9B%96%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20cur.val%20%3D%20tmp.val%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4,%202,%206,%201,%203,%205,%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20bst.remove%281%29%20%23%20%E5%BA%A6%E4%B8%BA%200%0A%20%20%20%20bst.remove%282%29%20%23%20%E5%BA%A6%E4%B8%BA%201%0A%20%20%20%20bst.remove%284%29%20%23%20%E5%BA%A6%E4%B8%BA%202&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_tree/binary_tree_bfs.md ================================================ https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20level_order%28root%3A%20TreeNode%20%7C%20None%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%EF%BC%8C%E5%8A%A0%E5%85%A5%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20queue%3A%20deque%5BTreeNode%5D%20%3D%20deque%28%29%0A%20%20%20%20queue.append%28root%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E4%B8%AA%E5%88%97%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E4%BF%9D%E5%AD%98%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20while%20queue%3A%0A%20%20%20%20%20%20%20%20node%3A%20TreeNode%20%3D%20queue.popleft%28%29%20%20%23%20%E9%98%9F%E5%88%97%E5%87%BA%E9%98%9F%0A%20%20%20%20%20%20%20%20res.append%28node.val%29%20%20%23%20%E4%BF%9D%E5%AD%98%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20if%20node.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.left%29%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%85%A5%E9%98%9F%0A%20%20%20%20%20%20%20%20if%20node.right%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.right%29%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%85%A5%E9%98%9F%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E8%BF%99%E9%87%8C%E5%80%9F%E5%8A%A9%E4%BA%86%E4%B8%80%E4%B8%AA%E4%BB%8E%E6%95%B0%E7%BB%84%E7%9B%B4%E6%8E%A5%E7%94%9F%E6%88%90%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%87%BD%E6%95%B0%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1,%202,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20level_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%E7%9A%84%E8%8A%82%E7%82%B9%E6%89%93%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22,%20res%29&cumulative=false&curInstr=127&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/pythontutor/chapter_tree/binary_tree_dfs.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E4%BC%98%E5%85%88%E7%BA%A7%EF%BC%9A%E6%A0%B9%E8%8A%82%E7%82%B9%20-%3E%20%E5%B7%A6%E5%AD%90%E6%A0%91%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20pre_order%28root%3Droot.left%29%0A%20%20%20%20pre_order%28root%3Droot.right%29%0A%0Adef%20in_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E4%BC%98%E5%85%88%E7%BA%A7%EF%BC%9A%E5%B7%A6%E5%AD%90%E6%A0%91%20-%3E%20%E6%A0%B9%E8%8A%82%E7%82%B9%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20in_order%28root%3Droot.left%29%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20in_order%28root%3Droot.right%29%0A%0Adef%20post_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E4%BC%98%E5%85%88%E7%BA%A7%EF%BC%9A%E5%B7%A6%E5%AD%90%E6%A0%91%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A0%91%20-%3E%20%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20post_order%28root%3Droot.left%29%0A%20%20%20%20post_order%28root%3Droot.right%29%0A%20%20%20%20res.append%28root.val%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E8%BF%99%E9%87%8C%E5%80%9F%E5%8A%A9%E4%BA%86%E4%B8%80%E4%B8%AA%E4%BB%8E%E6%95%B0%E7%BB%84%E7%9B%B4%E6%8E%A5%E7%94%9F%E6%88%90%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%87%BD%E6%95%B0%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1,%202,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20pre_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%E7%9A%84%E8%8A%82%E7%82%B9%E6%89%93%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22,%20res%29%0A%0A%20%20%20%20%23%20%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20in_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E7%9A%84%E8%8A%82%E7%82%B9%E6%89%93%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22,%20res%29%0A%0A%20%20%20%20%23%20%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20post_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E7%9A%84%E8%8A%82%E7%82%B9%E6%89%93%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22,%20res%29&cumulative=false&curInstr=129&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: codes/ruby/chapter_array_and_linkedlist/array.rb ================================================ =begin File: array.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 随机访问元素 ### def random_access(nums) # 在区间 [0, nums.length) 中随机抽取一个数字 random_index = Random.rand(0...nums.length) # 获取并返回随机元素 nums[random_index] end ### 扩展数组长度 ### # 请注意,Ruby 的 Array 是动态数组,可以直接扩展 # 为了方便学习,本函数将 Array 看作长度不可变的数组 def extend(nums, enlarge) # 初始化一个扩展长度后的数组 res = Array.new(nums.length + enlarge, 0) # 将原数组中的所有元素复制到新数组 for i in 0...nums.length res[i] = nums[i] end # 返回扩展后的新数组 res end ### 在数组的索引 index 处插入元素 num ### def insert(nums, num, index) # 把索引 index 以及之后的所有元素向后移动一位 for i in (nums.length - 1).downto(index + 1) nums[i] = nums[i - 1] end # 将 num 赋给 index 处的元素 nums[index] = num end ### 删除索引 index 处的元素 ### def remove(nums, index) # 把索引 index 之后的所有元素向前移动一位 for i in index...(nums.length - 1) nums[i] = nums[i + 1] end end ### 遍历数组 ### def traverse(nums) count = 0 # 通过索引遍历数组 for i in 0...nums.length count += nums[i] end # 直接遍历数组元素 for num in nums count += num end end ### 在数组中查找指定元素 ### def find(nums, target) for i in 0...nums.length return i if nums[i] == target end -1 end ### Driver Code ### if __FILE__ == $0 # 初始化数组 arr = Array.new(5, 0) puts "数组 arr = #{arr}" nums = [1, 3, 2, 5, 4] puts "数组 nums = #{nums}" # 随机访问 random_num = random_access(nums) puts "在 nums 中获取随机元素 #{random_num}" # 长度扩展 nums = extend(nums, 3) puts "将数组长度扩展至 8 ,得到 nums = #{nums}" # 插入元素 insert(nums, 6, 3) puts "在索引 3 处插入数字 6 ,得到 nums = #{nums}" # 删除元素 remove(nums, 2) puts "删除索引 2 处的元素,得到 nums = #{nums}" # 遍历数组 traverse(nums) # 查找元素 index = find(nums, 3) puts "在 nums 中查找元素 3 ,得到索引 = #{index}" end ================================================ FILE: codes/ruby/chapter_array_and_linkedlist/linked_list.rb ================================================ =begin File: linked_list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' require_relative '../utils/print_util' ### 在链表的节点 n0 之后插入节点 _p ### # Ruby 的 `p` 是一个内置函数, `P` 是一个常量,所以可以使用 `_p` 代替 def insert(n0, _p) n1 = n0.next _p.next = n1 n0.next = _p end ### 删除链表的节点 n0 之后的首个节点 ### def remove(n0) return if n0.next.nil? # n0 -> remove_node -> n1 remove_node = n0.next n1 = remove_node.next n0.next = n1 end ### 访问链表中索引为 index 的节点 ### def access(head, index) for i in 0...index return nil if head.nil? head = head.next end head end ### 在链表中查找值为 target 的首个节点 ### def find(head, target) index = 0 while head return index if head.val == target head = head.next index += 1 end -1 end ### Driver Code ### if __FILE__ == $0 # 初始化链表 # 初始化各个节点 n0 = ListNode.new(1) n1 = ListNode.new(3) n2 = ListNode.new(2) n3 = ListNode.new(5) n4 = ListNode.new(4) # 构建节点之间的引用 n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 puts "初始化的链表为" print_linked_list(n0) # 插入节点 insert(n0, ListNode.new(0)) print_linked_list n0 # 删除节点 remove(n0) puts "删除节点后的链表为" print_linked_list(n0) # 访问节点 node = access(n0, 3) puts "链表中索引 3 处的节点的值 = #{node.val}" # 查找节点 index = find(n0, 2) puts "链表中值为 2 的节点的索引 = #{index}" end ================================================ FILE: codes/ruby/chapter_array_and_linkedlist/list.rb ================================================ =begin File: list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # 初始化列表 nums = [1, 3, 2, 5, 4] puts "列表 nums = #{nums}" # 访问元素 num = nums[1] puts "访问索引 1 处的元素,得到 num = #{num}" # 更新元素 nums[1] = 0 puts "将索引 1 处的元素更新为 0 ,得到 nums = #{nums}" # 清空列表 nums.clear puts "清空列表后 nums = #{nums}" # 在尾部添加元素 nums << 1 nums << 3 nums << 2 nums << 5 nums << 4 puts "添加元素后 nums = #{nums}" # 在中间插入元素 nums.insert(3, 6) puts "在索引 3 处插入元素 6 ,得到 nums = #{nums}" # 删除元素 nums.delete_at(3) puts "删除索引 3 处的元素,得到 nums = #{nums}" # 通过索引遍历列表 count = 0 for i in 0...nums.length count += nums[i] end # 直接遍历列表元素 count = 0 nums.each do |x| count += x end # 拼接两个列表 nums1 = [6, 8, 7, 10, 9] nums += nums1 puts "将列表 nums1 拼接到 nums 之后,得到 nums = #{nums}" nums = nums.sort { |a, b| a <=> b } puts "排序列表后 nums = #{nums}" end ================================================ FILE: codes/ruby/chapter_array_and_linkedlist/my_list.rb ================================================ =begin File: my_list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 列表类 ### class MyList attr_reader :size # 获取列表长度(当前元素数量) attr_reader :capacity # 获取列表容量 ### 构造方法 ### def initialize @capacity = 10 @size = 0 @extend_ratio = 2 @arr = Array.new(capacity) end ### 访问元素 ### def get(index) # 索引如果越界,则抛出异常,下同 raise IndexError, "索引越界" if index < 0 || index >= size @arr[index] end ### 访问元素 ### def set(index, num) raise IndexError, "索引越界" if index < 0 || index >= size @arr[index] = num end ### 在尾部添加元素 ### def add(num) # 元素数量超出容量时,触发扩容机制 extend_capacity if size == capacity @arr[size] = num # 更新元素数量 @size += 1 end ### 在中间插入元素 ### def insert(index, num) raise IndexError, "索引越界" if index < 0 || index >= size # 元素数量超出容量时,触发扩容机制 extend_capacity if size == capacity # 将索引 index 以及之后的元素都向后移动一位 for j in (size - 1).downto(index) @arr[j + 1] = @arr[j] end @arr[index] = num # 更新元素数量 @size += 1 end ### 删除元素 ### def remove(index) raise IndexError, "索引越界" if index < 0 || index >= size num = @arr[index] # 将将索引 index 之后的元素都向前移动一位 for j in index...size @arr[j] = @arr[j + 1] end # 更新元素数量 @size -= 1 # 返回被删除的元素 num end ### 列表扩容 ### def extend_capacity # 新建一个长度为原数组 extend_ratio 倍的新数组,并将原数组复制到新数组 arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1)) # 更新列表容量 @capacity = arr.length end ### 将列表转换为数组 ### def to_array sz = size # 仅转换有效长度范围内的列表元素 arr = Array.new(sz) for i in 0...sz arr[i] = get(i) end arr end end ### Driver Code ### if __FILE__ == $0 # 初始化列表 nums = MyList.new # 在尾部添加元素 nums.add(1) nums.add(3) nums.add(2) nums.add(5) nums.add(4) puts "列表 nums = #{nums.to_array} ,容量 = #{nums.capacity} ,长度 = #{nums.size}" # 在中间插入元素 nums.insert(3, 6) puts "在索引 3 处插入数字 6 ,得到 nums = #{nums.to_array}" # 删除元素 nums.remove(3) puts "删除索引 3 的元素,得到 nums = #{nums.to_array}" # 访问元素 num = nums.get(1) puts "访问索引 1 处的元素,得到 num = #{num}" # 更新元素 nums.set(1, 0) puts "将索引 1 处的元素更新为 0 ,得到 nums = #{nums.to_array}" # 测试扩容机制 for i in 0...10 # 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 nums.add(i) end puts "扩容后的列表 nums = #{nums.to_array} ,容量 = #{nums.capacity} ,长度 = #{nums.size}" end ================================================ FILE: codes/ruby/chapter_backtracking/n_queens.rb ================================================ =begin File: n_queens.rb Created Time: 2024-05-21 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯算法:n 皇后 ### def backtrack(row, n, state, res, cols, diags1, diags2) # 当放置完所有行时,记录解 if row == n res << state.map { |row| row.dup } return end # 遍历所有列 for col in 0...n # 计算该格子对应的主对角线和次对角线 diag1 = row - col + n - 1 diag2 = row + col # 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 if !cols[col] && !diags1[diag1] && !diags2[diag2] # 尝试:将皇后放置在该格子 state[row][col] = "Q" cols[col] = diags1[diag1] = diags2[diag2] = true # 放置下一行 backtrack(row + 1, n, state, res, cols, diags1, diags2) # 回退:将该格子恢复为空位 state[row][col] = "#" cols[col] = diags1[diag1] = diags2[diag2] = false end end end ### 求解 n 皇后 ### def n_queens(n) # 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 state = Array.new(n) { Array.new(n, "#") } cols = Array.new(n, false) # 记录列是否有皇后 diags1 = Array.new(2 * n - 1, false) # 记录主对角线上是否有皇后 diags2 = Array.new(2 * n - 1, false) # 记录次对角线上是否有皇后 res = [] backtrack(0, n, state, res, cols, diags1, diags2) res end ### Driver Code ### if __FILE__ == $0 n = 4 res = n_queens(n) puts "输入棋盘长宽为 #{n}" puts "皇后放置方案共有 #{res.length} 种" for state in res puts "--------------------" for row in state p row end end end ================================================ FILE: codes/ruby/chapter_backtracking/permutations_i.rb ================================================ =begin File: permutations_i.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯算法:全排列 I ### def backtrack(state, choices, selected, res) # 当状态长度等于元素数量时,记录解 if state.length == choices.length res << state.dup return end # 遍历所有选择 choices.each_with_index do |choice, i| # 剪枝:不允许重复选择元素 unless selected[i] # 尝试:做出选择,更新状态 selected[i] = true state << choice # 进行下一轮选择 backtrack(state, choices, selected, res) # 回退:撤销选择,恢复到之前的状态 selected[i] = false state.pop end end end ### 全排列 I ### def permutations_i(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ### Driver Code ### if __FILE__ == $0 nums = [1, 2, 3] res = permutations_i(nums) puts "输入数组 nums = #{nums}" puts "所有排列 res = #{res}" end ================================================ FILE: codes/ruby/chapter_backtracking/permutations_ii.rb ================================================ =begin File: permutations_ii.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯算法:全排列 II ### def backtrack(state, choices, selected, res) # 当状态长度等于元素数量时,记录解 if state.length == choices.length res << state.dup return end # 遍历所有选择 duplicated = Set.new choices.each_with_index do |choice, i| # 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 if !selected[i] && !duplicated.include?(choice) # 尝试:做出选择,更新状态 duplicated.add(choice) selected[i] = true state << choice # 进行下一轮选择 backtrack(state, choices, selected, res) # 回退:撤销选择,恢复到之前的状态 selected[i] = false state.pop end end end ### 全排列 II ### def permutations_ii(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ### Driver Code ### if __FILE__ == $0 nums = [1, 2, 2] res = permutations_ii(nums) puts "输入数组 nums = #{nums}" puts "所有排列 res = #{res}" end ================================================ FILE: codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb ================================================ =begin File: preorder_traversal_i_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 前序遍历:例题一 ### def pre_order(root) return unless root # 记录解 $res << root if root.val == 7 pre_order(root.left) pre_order(root.right) end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\n初始化二叉树" print_tree(root) # 前序遍历 $res = [] pre_order(root) puts "\n输出所有值为 7 的节点" p $res.map { |node| node.val } end ================================================ FILE: codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb ================================================ =begin File: preorder_traversal_ii_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 前序遍历:例题二 ### def pre_order(root) return unless root # 尝试 $path << root # 记录解 $res << $path.dup if root.val == 7 pre_order(root.left) pre_order(root.right) # 回退 $path.pop end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\n初始化二叉树" print_tree(root) # 前序遍历 $path, $res = [], [] pre_order(root) puts "\n输出所有根节点到节点 7 的路径" for path in $res p path.map { |node| node.val } end end ================================================ FILE: codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb ================================================ =begin File: preorder_traversal_iii_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 前序遍历:例题三 ### def pre_order(root) # 剪枝 return if !root || root.val == 3 # 尝试 $path.append(root) # 记录解 $res << $path.dup if root.val == 7 pre_order(root.left) pre_order(root.right) # 回退 $path.pop end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\n初始化二叉树" print_tree(root) # 前序遍历 $path, $res = [], [] pre_order(root) puts "\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点" for path in $res p path.map { |node| node.val } end end ================================================ FILE: codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb ================================================ =begin File: preorder_traversal_iii_template.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 判断当前状态是否为解 ### def is_solution?(state) !state.empty? && state.last.val == 7 end ### 记录解 ### def record_solution(state, res) res << state.dup end ### 判断在当前状态下,该选择是否合法 ### def is_valid?(state, choice) choice && choice.val != 3 end ### 更新状态 ### def make_choice(state, choice) state << choice end ### 恢复状态 ### def undo_choice(state, choice) state.pop end ### 回溯算法:例题三 ### def backtrack(state, choices, res) # 检查是否为解 record_solution(state, res) if is_solution?(state) # 遍历所有选择 for choice in choices # 剪枝:检查选择是否合法 if is_valid?(state, choice) # 尝试:做出选择,更新状态 make_choice(state, choice) # 进行下一轮选择 backtrack(state, [choice.left, choice.right], res) # 回退:撤销选择,恢复到之前的状态 undo_choice(state, choice) end end end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\n初始化二叉树" print_tree(root) # 回溯算法 res = [] backtrack([], [root], res) puts "\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点" for path in res p path.map { |node| node.val } end end ================================================ FILE: codes/ruby/chapter_backtracking/subset_sum_i.rb ================================================ =begin File: subset_sum_i.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯算法:子集和 I ### def backtrack(state, target, choices, start, res) # 子集和等于 target 时,记录解 if target.zero? res << state.dup return end # 遍历所有选择 # 剪枝二:从 start 开始遍历,避免生成重复子集 for i in start...choices.length # 剪枝一:若子集和超过 target ,则直接结束循环 # 这是因为数组已排序,后边元素更大,子集和一定超过 target break if target - choices[i] < 0 # 尝试:做出选择,更新 target, start state << choices[i] # 进行下一轮选择 backtrack(state, target - choices[i], choices, i, res) # 回退:撤销选择,恢复到之前的状态 state.pop end end ### 求解子集和 I ### def subset_sum_i(nums, target) state = [] # 状态(子集) nums.sort! # 对 nums 进行排序 start = 0 # 遍历起始点 res = [] # 结果列表(子集列表) backtrack(state, target, nums, start, res) res end ### Driver Code ### if __FILE__ == $0 nums = [3, 4, 5] target = 9 res = subset_sum_i(nums, target) puts "输入数组 = #{nums}, target = #{target}" puts "所有和等于 #{target} 的子集 res = #{res}" end ================================================ FILE: codes/ruby/chapter_backtracking/subset_sum_i_naive.rb ================================================ =begin File: subset_sum_i_naive.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯算法:子集和 I ### def backtrack(state, target, total, choices, res) # 子集和等于 target 时,记录解 if total == target res << state.dup return end # 遍历所有选择 for i in 0...choices.length # 剪枝:若子集和超过 target ,则跳过该选择 next if total + choices[i] > target # 尝试:做出选择,更新元素和 total state << choices[i] # 进行下一轮选择 backtrack(state, target, total + choices[i], choices, res) # 回退:撤销选择,恢复到之前的状态 state.pop end end ### 求解子集和 I(包含重复子集)### def subset_sum_i_naive(nums, target) state = [] # 状态(子集) total = 0 # 子集和 res = [] # 结果列表(子集列表) backtrack(state, target, total, nums, res) res end ### Driver Code ### if __FILE__ == $0 nums = [3, 4, 5] target = 9 res = subset_sum_i_naive(nums, target) puts "输入数组 nums = #{nums}, target = #{target}" puts "所有和等于 #{target} 的子集 res = #{res}" puts "请注意,该方法输出的结果包含重复集合" end ================================================ FILE: codes/ruby/chapter_backtracking/subset_sum_ii.rb ================================================ =begin File: subset_sum_ii.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯算法:子集和 II ### def backtrack(state, target, choices, start, res) # 子集和等于 target 时,记录解 if target.zero? res << state.dup return end # 遍历所有选择 # 剪枝二:从 start 开始遍历,避免生成重复子集 # 剪枝三:从 start 开始遍历,避免重复选择同一元素 for i in start...choices.length # 剪枝一:若子集和超过 target ,则直接结束循环 # 这是因为数组已排序,后边元素更大,子集和一定超过 target break if target - choices[i] < 0 # 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 next if i > start && choices[i] == choices[i - 1] # 尝试:做出选择,更新 target, start state << choices[i] # 进行下一轮选择 backtrack(state, target - choices[i], choices, i + 1, res) # 回退:撤销选择,恢复到之前的状态 state.pop end end ### 求解子集和 II ### def subset_sum_ii(nums, target) state = [] # 状态(子集) nums.sort! # 对 nums 进行排序 start = 0 # 遍历起始点 res = [] # 结果列表(子集列表) backtrack(state, target, nums, start, res) res end ### Driver Code ### if __FILE__ == $0 nums = [4, 4, 5] target = 9 res = subset_sum_ii(nums, target) puts "输入数组 nums = #{nums}, target = #{target}" puts "所有和等于 #{target} 的子集 res = #{res}" end ================================================ FILE: codes/ruby/chapter_computational_complexity/iteration.rb ================================================ =begin File: iteration.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com), Cy (9738314@gmail.com) =end ### for 循环 ### def for_loop(n) res = 0 # 循环求和 1, 2, ..., n-1, n for i in 1..n res += i end res end ### while 循环 ### def while_loop(n) res = 0 i = 1 # 初始化条件变量 # 循环求和 1, 2, ..., n-1, n while i <= n res += i i += 1 # 更新条件变量 end res end ### while 循环(两次更新)### def while_loop_ii(n) res = 0 i = 1 # 初始化条件变量 # 循环求和 1, 4, 10, ... while i <= n res += i # 更新条件变量 i += 1 i *= 2 end res end ### 双层 for 循环 ### def nested_for_loop(n) res = "" # 循环 i = 1, 2, ..., n-1, n for i in 1..n # 循环 j = 1, 2, ..., n-1, n for j in 1..n res += "(#{i}, #{j}), " end end res end ### Driver Code ### if __FILE__ == $0 n = 5 res = for_loop(n) puts "\nfor 循环的求和结果 res = #{res}" res = while_loop(n) puts "\nwhile 循环的求和结果 res = #{res}" res = while_loop_ii(n) puts "\nwhile 循环(两次更新)求和结果 res = #{res}" res = nested_for_loop(n) puts "\n双层 for 循环的遍历结果 #{res}" end ================================================ FILE: codes/ruby/chapter_computational_complexity/recursion.rb ================================================ =begin File: recursion.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 递归 ### def recur(n) # 终止条件 return 1 if n == 1 # 递:递归调用 res = recur(n - 1) # 归:返回结果 n + res end ### 使用迭代模拟递归 ### def for_loop_recur(n) # 使用一个显式的栈来模拟系统调用栈 stack = [] res = 0 # 递:递归调用 for i in n.downto(0) # 通过“入栈操作”模拟“递” stack << i end # 归:返回结果 while !stack.empty? res += stack.pop end # res = 1+2+3+...+n res end ### 尾递归 ### def tail_recur(n, res) # 终止条件 return res if n == 0 # 尾递归调用 tail_recur(n - 1, res + n) end ### 斐波那契数列:递归 ### def fib(n) # 终止条件 f(1) = 0, f(2) = 1 return n - 1 if n == 1 || n == 2 # 递归调用 f(n) = f(n-1) + f(n-2) res = fib(n - 1) + fib(n - 2) # 返回结果 f(n) res end ### Driver Code ### if __FILE__ == $0 n = 5 res = recur(n) puts "\n递归函数的求和结果 res = #{res}" res = for_loop_recur(n) puts "\n使用迭代模拟递归求和结果 res = #{res}" res = tail_recur(n, 0) puts "\n尾递归函数的求和结果 res = #{res}" res = fib(n) puts "\n斐波那契数列的第 #{n} 项为 #{res}" end ================================================ FILE: codes/ruby/chapter_computational_complexity/space_complexity.rb ================================================ =begin File: space_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 函数 ### def function # 执行某些操作 0 end ### 常数阶 ### def constant(n) # 常量、变量、对象占用 O(1) 空间 a = 0 nums = [0] * 10000 node = ListNode.new # 循环中的变量占用 O(1) 空间 (0...n).each { c = 0 } # 循环中的函数占用 O(1) 空间 (0...n).each { function } end ### 线性阶 ### def linear(n) # 长度为 n 的列表占用 O(n) 空间 nums = Array.new(n, 0) # 长度为 n 的哈希表占用 O(n) 空间 hmap = {} for i in 0...n hmap[i] = i.to_s end end ### 线性阶(递归实现)### def linear_recur(n) puts "递归 n = #{n}" return if n == 1 linear_recur(n - 1) end ### 平方阶 ### def quadratic(n) # 二维列表占用 O(n^2) 空间 Array.new(n) { Array.new(n, 0) } end ### 平方阶(递归实现)### def quadratic_recur(n) return 0 unless n > 0 # 数组 nums 长度为 n, n-1, ..., 2, 1 nums = Array.new(n, 0) quadratic_recur(n - 1) end ### 指数阶(建立满二叉树)### def build_tree(n) return if n == 0 TreeNode.new.tap do |root| root.left = build_tree(n - 1) root.right = build_tree(n - 1) end end ### Driver Code ### if __FILE__ == $0 n = 5 # 常数阶 constant(n) # 线性阶 linear(n) linear_recur(n) # 平方阶 quadratic(n) quadratic_recur(n) # 指数阶 root = build_tree(n) print_tree(root) end ================================================ FILE: codes/ruby/chapter_computational_complexity/time_complexity.rb ================================================ =begin File: time_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 常数阶 ### def constant(n) count = 0 size = 100000 (0...size).each { count += 1 } count end ### 线性阶 ### def linear(n) count = 0 (0...n).each { count += 1 } count end ### 线性阶(遍历数组)### def array_traversal(nums) count = 0 # 循环次数与数组长度成正比 for num in nums count += 1 end count end ### 平方阶 ### def quadratic(n) count = 0 # 循环次数与数据大小 n 成平方关系 for i in 0...n for j in 0...n count += 1 end end count end ### 平方阶(冒泡排序)### def bubble_sort(nums) count = 0 # 计数器 # 外循环:未排序区间为 [0, i] for i in (nums.length - 1).downto(0) # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in 0...i if nums[j] > nums[j + 1] # 交换 nums[j] 与 nums[j + 1] tmp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = tmp count += 3 # 元素交换包含 3 个单元操作 end end end count end ### 指数阶(循环实现)### def exponential(n) count, base = 0, 1 # 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) (0...n).each do (0...base).each { count += 1 } base *= 2 end # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 count end ### 指数阶(递归实现)### def exp_recur(n) return 1 if n == 1 exp_recur(n - 1) + exp_recur(n - 1) + 1 end ### 对数阶(循环实现)### def logarithmic(n) count = 0 while n > 1 n /= 2 count += 1 end count end ### 对数阶(递归实现)### def log_recur(n) return 0 unless n > 1 log_recur(n / 2) + 1 end ### 线性对数阶 ### def linear_log_recur(n) return 1 unless n > 1 count = linear_log_recur(n / 2) + linear_log_recur(n / 2) (0...n).each { count += 1 } count end ### 阶乘阶(递归实现)### def factorial_recur(n) return 1 if n == 0 count = 0 # 从 1 个分裂出 n 个 (0...n).each { count += factorial_recur(n - 1) } count end ### Driver Code ### if __FILE__ == $0 # 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 n = 8 puts "输入数据大小 n = #{n}" count = constant(n) puts "常数阶的操作数量 = #{count}" count = linear(n) puts "线性阶的操作数量 = #{count}" count = array_traversal(Array.new(n, 0)) puts "线性阶(遍历数组)的操作数量 = #{count}" count = quadratic(n) puts "平方阶的操作数量 = #{count}" nums = Array.new(n) { |i| n - i } # [n, n-1, ..., 2, 1] count = bubble_sort(nums) puts "平方阶(冒泡排序)的操作数量 = #{count}" count = exponential(n) puts "指数阶(循环实现)的操作数量 = #{count}" count = exp_recur(n) puts "指数阶(递归实现)的操作数量 = #{count}" count = logarithmic(n) puts "对数阶(循环实现)的操作数量 = #{count}" count = log_recur(n) puts "对数阶(递归实现)的操作数量 = #{count}" count = linear_log_recur(n) puts "线性对数阶(递归实现)的操作数量 = #{count}" count = factorial_recur(n) puts "阶乘阶(递归实现)的操作数量 = #{count}" end ================================================ FILE: codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb ================================================ =begin File: worst_best_time_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱 ### def random_numbers(n) # 生成数组 nums =: 1, 2, 3, ..., n nums = Array.new(n) { |i| i + 1 } # 随机打乱数组元素 nums.shuffle! end ### 查找数组 nums 中数字 1 所在索引 ### def find_one(nums) for i in 0...nums.length # 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) # 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) return i if nums[i] == 1 end -1 end ### Driver Code ### if __FILE__ == $0 for i in 0...10 n = 100 nums = random_numbers(n) index = find_one(nums) puts "\n数组 [ 1, 2, ..., n ] 被打乱后 = #{nums}" puts "数字 1 的索引为 #{index}" end end ================================================ FILE: codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb ================================================ =begin File: binary_search_recur.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 二分查找:问题 f(i, j) ### def dfs(nums, target, i, j) # 若区间为空,代表无目标元素,则返回 -1 return -1 if i > j # 计算中点索引 m m = (i + j) / 2 if nums[m] < target # 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j) elsif nums[m] > target # 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1) else # 找到目标元素,返回其索引 return m end end ### 二分查找 ### def binary_search(nums, target) n = nums.length # 求解问题 f(0, n-1) dfs(nums, target, 0, n - 1) end ### Driver Code ### if __FILE__ == $0 target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # 二分查找(双闭区间) index = binary_search(nums, target) puts "目标元素 6 的索引 = #{index}" end ================================================ FILE: codes/ruby/chapter_divide_and_conquer/build_tree.rb ================================================ =begin File: build_tree.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 构建二叉树:分治 ### def dfs(preorder, inorder_map, i, l, r) # 子树区间为空时终止 return if r - l < 0 # 初始化根节点 root = TreeNode.new(preorder[i]) # 查询 m ,从而划分左右子树 m = inorder_map[preorder[i]] # 子问题:构建左子树 root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) # 子问题:构建右子树 root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) # 返回根节点 root end ### 构建二叉树 ### def build_tree(preorder, inorder) # 初始化哈希表,存储 inorder 元素到索引的映射 inorder_map = {} inorder.each_with_index { |val, i| inorder_map[val] = i } dfs(preorder, inorder_map, 0, 0, inorder.length - 1) end ### Driver Code ### if __FILE__ == $0 preorder = [3, 9, 2, 1, 7] inorder = [9, 3, 1, 2, 7] puts "前序遍历 = #{preorder}" puts "中序遍历 = #{inorder}" root = build_tree(preorder, inorder) puts "构建的二叉树为:" print_tree(root) end ================================================ FILE: codes/ruby/chapter_divide_and_conquer/hanota.rb ================================================ =begin File: hanota.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 移动一个圆盘 ### def move(src, tar) # 从 src 顶部拿出一个圆盘 pan = src.pop # 将圆盘放入 tar 顶部 tar << pan end ### 求解汉诺塔问题 f(i) ### def dfs(i, src, buf, tar) # 若 src 只剩下一个圆盘,则直接将其移到 tar if i == 1 move(src, tar) return end # 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf dfs(i - 1, src, tar, buf) # 子问题 f(1) :将 src 剩余一个圆盘移到 tar move(src, tar) # 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar dfs(i - 1, buf, src, tar) end ### 求解汉诺塔问题 ### def solve_hanota(_A, _B, _C) n = _A.length # 将 A 顶部 n 个圆盘借助 B 移到 C dfs(n, _A, _B, _C) end ### Driver Code ### if __FILE__ == $0 # 列表尾部是柱子顶部 A = [5, 4, 3, 2, 1] B = [] C = [] puts "初始状态下:" puts "A = #{A}" puts "B = #{B}" puts "C = #{C}" solve_hanota(A, B, C) puts "圆盘移动完成后:" puts "A = #{A}" puts "B = #{B}" puts "C = #{C}" end ================================================ FILE: codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb ================================================ =begin File: climbing_stairs_backtrack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯 ### def backtrack(choices, state, n, res) # 当爬到第 n 阶时,方案数量加 1 res[0] += 1 if state == n # 遍历所有选择 for choice in choices # 剪枝:不允许越过第 n 阶 next if state + choice > n # 尝试:做出选择,更新状态 backtrack(choices, state + choice, n, res) end # 回退 end ### 爬楼梯:回溯 ### def climbing_stairs_backtrack(n) choices = [1, 2] # 可选择向上爬 1 阶或 2 阶 state = 0 # 从第 0 阶开始爬 res = [0] # 使用 res[0] 记录方案数量 backtrack(choices, state, n, res) res.first end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_backtrack(n) puts "爬 #{n} 阶楼梯共有 #{res} 种方案" end ================================================ FILE: codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb ================================================ =begin File: climbing_stairs_constraint_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 带约束爬楼梯:动态规划 ### def climbing_stairs_constraint_dp(n) return 1 if n == 1 || n == 2 # 初始化 dp 表,用于存储子问题的解 dp = Array.new(n + 1) { Array.new(3, 0) } # 初始状态:预设最小子问题的解 dp[1][1], dp[1][2] = 1, 0 dp[2][1], dp[2][2] = 0, 1 # 状态转移:从较小子问题逐步求解较大子问题 for i in 3...(n + 1) dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] end dp[n][1] + dp[n][2] end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_constraint_dp(n) puts "爬 #{n} 阶楼梯共有 #{res} 种方案" end ================================================ FILE: codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb ================================================ =begin File: climbing_stairs_dfs.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 搜索 ### def dfs(i) # 已知 dp[1] 和 dp[2] ,返回之 return i if i == 1 || i == 2 # dp[i] = dp[i-1] + dp[i-2] dfs(i - 1) + dfs(i - 2) end ### 爬楼梯:搜索 ### def climbing_stairs_dfs(n) dfs(n) end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dfs(n) puts "爬 #{n} 阶楼梯共有 #{res} 种方案" end ================================================ FILE: codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb ================================================ =begin File: climbing_stairs_dfs_mem.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 记忆化搜索 ### def dfs(i, mem) # 已知 dp[1] 和 dp[2] ,返回之 return i if i == 1 || i == 2 # 若存在记录 dp[i] ,则直接返回之 return mem[i] if mem[i] != -1 # dp[i] = dp[i-1] + dp[i-2] count = dfs(i - 1, mem) + dfs(i - 2, mem) # 记录 dp[i] mem[i] = count end ### 爬楼梯:记忆化搜索 ### def climbing_stairs_dfs_mem(n) # mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 mem = Array.new(n + 1, -1) dfs(n, mem) end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dfs_mem(n) puts "爬 #{n} 阶楼梯共有 #{res} 种方案" end ================================================ FILE: codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb ================================================ =begin File: climbing_stairs_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 爬楼梯:动态规划 ### def climbing_stairs_dp(n) return n if n == 1 || n == 2 # 初始化 dp 表,用于存储子问题的解 dp = Array.new(n + 1, 0) # 初始状态:预设最小子问题的解 dp[1], dp[2] = 1, 2 # 状态转移:从较小子问题逐步求解较大子问题 (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] } dp[n] end ### 爬楼梯:空间优化后的动态规划 ### def climbing_stairs_dp_comp(n) return n if n == 1 || n == 2 a, b = 1, 2 (3...(n + 1)).each { a, b = b, a + b } b end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dp(n) puts "爬 #{n} 阶楼梯共有 #{res} 种方案" res = climbing_stairs_dp_comp(n) puts "爬 #{n} 阶楼梯共有 #{res} 种方案" end ================================================ FILE: codes/ruby/chapter_dynamic_programming/coin_change.rb ================================================ =begin File: coin_change.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 零钱兑换:动态规划 ### def coin_change_dp(coins, amt) n = coins.length _MAX = amt + 1 # 初始化 dp 表 dp = Array.new(n + 1) { Array.new(amt + 1, 0) } # 状态转移:首行首列 (1...(amt + 1)).each { |a| dp[0][a] = _MAX } # 状态转移:其余行和列 for i in 1...(n + 1) for a in 1...(amt + 1) if coins[i - 1] > a # 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a] else # 不选和选硬币 i 这两种方案的较小值 dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min end end end dp[n][amt] != _MAX ? dp[n][amt] : -1 end ### 零钱兑换:空间优化后的动态规划 ### def coin_change_dp_comp(coins, amt) n = coins.length _MAX = amt + 1 # 初始化 dp 表 dp = Array.new(amt + 1, _MAX) dp[0] = 0 # 状态转移 for i in 1...(n + 1) # 正序遍历 for a in 1...(amt + 1) if coins[i - 1] > a # 若超过目标金额,则不选硬币 i dp[a] = dp[a] else # 不选和选硬币 i 这两种方案的较小值 dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min end end end dp[amt] != _MAX ? dp[amt] : -1 end ### Driver Code ### if __FILE__ == $0 coins = [1, 2, 5] amt = 4 # 动态规划 res = coin_change_dp(coins, amt) puts "凑到目标金额所需的最少硬币数量为 #{res}" # 空间优化后的动态规划 res = coin_change_dp_comp(coins, amt) puts "凑到目标金额所需的最少硬币数量为 #{res}" end ================================================ FILE: codes/ruby/chapter_dynamic_programming/coin_change_ii.rb ================================================ =begin File: coin_change_ii.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 零钱兑换 II:动态规划 ### def coin_change_ii_dp(coins, amt) n = coins.length # 初始化 dp 表 dp = Array.new(n + 1) { Array.new(amt + 1, 0) } # 初始化首列 (0...(n + 1)).each { |i| dp[i][0] = 1 } # 状态转移 for i in 1...(n + 1) for a in 1...(amt + 1) if coins[i - 1] > a # 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a] else # 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] end end end dp[n][amt] end ### 零钱兑换 II:空间优化后的动态规划 ### def coin_change_ii_dp_comp(coins, amt) n = coins.length # 初始化 dp 表 dp = Array.new(amt + 1, 0) dp[0] = 1 # 状态转移 for i in 1...(n + 1) # 正序遍历 for a in 1...(amt + 1) if coins[i - 1] > a # 若超过目标金额,则不选硬币 i dp[a] = dp[a] else # 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]] end end end dp[amt] end ### Driver Code ### if __FILE__ == $0 coins = [1, 2, 5] amt = 5 # 动态规划 res = coin_change_ii_dp(coins, amt) puts "凑出目标金额的硬币组合数量为 #{res}" # 空间优化后的动态规划 res = coin_change_ii_dp_comp(coins, amt) puts "凑出目标金额的硬币组合数量为 #{res}" end ================================================ FILE: codes/ruby/chapter_dynamic_programming/edit_distance.rb ================================================ =begin File: edit_distance.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 编辑距离:暴力搜索 ### def edit_distance_dfs(s, t, i, j) # 若 s 和 t 都为空,则返回 0 return 0 if i == 0 && j == 0 # 若 s 为空,则返回 t 长度 return j if i == 0 # 若 t 为空,则返回 s 长度 return i if j == 0 # 若两字符相等,则直接跳过此两字符 return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1] # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 insert = edit_distance_dfs(s, t, i, j - 1) delete = edit_distance_dfs(s, t, i - 1, j) replace = edit_distance_dfs(s, t, i - 1, j - 1) # 返回最少编辑步数 [insert, delete, replace].min + 1 end def edit_distance_dfs_mem(s, t, mem, i, j) # 若 s 和 t 都为空,则返回 0 return 0 if i == 0 && j == 0 # 若 s 为空,则返回 t 长度 return j if i == 0 # 若 t 为空,则返回 s 长度 return i if j == 0 # 若已有记录,则直接返回之 return mem[i][j] if mem[i][j] != -1 # 若两字符相等,则直接跳过此两字符 return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1] # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) # 记录并返回最少编辑步数 mem[i][j] = [insert, delete, replace].min + 1 end ### 编辑距离:动态规划 ### def edit_distance_dp(s, t) n, m = s.length, t.length dp = Array.new(n + 1) { Array.new(m + 1, 0) } # 状态转移:首行首列 (1...(n + 1)).each { |i| dp[i][0] = i } (1...(m + 1)).each { |j| dp[0][j] = j } # 状态转移:其余行和列 for i in 1...(n + 1) for j in 1...(m +1) if s[i - 1] == t[j - 1] # 若两字符相等,则直接跳过此两字符 dp[i][j] = dp[i - 1][j - 1] else # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1 end end end dp[n][m] end ### 编辑距离:空间优化后的动态规划 ### def edit_distance_dp_comp(s, t) n, m = s.length, t.length dp = Array.new(m + 1, 0) # 状态转移:首行 (1...(m + 1)).each { |j| dp[j] = j } # 状态转移:其余行 for i in 1...(n + 1) # 状态转移:首列 leftup = dp.first # 暂存 dp[i-1, j-1] dp[0] += 1 # 状态转移:其余列 for j in 1...(m + 1) temp = dp[j] if s[i - 1] == t[j - 1] # 若两字符相等,则直接跳过此两字符 dp[j] = leftup else # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[j] = [dp[j - 1], dp[j], leftup].min + 1 end leftup = temp # 更新为下一轮的 dp[i-1, j-1] end end dp[m] end ### Driver Code ### if __FILE__ == $0 s = 'bag' t = 'pack' n, m = s.length, t.length # 暴力搜索 res = edit_distance_dfs(s, t, n, m) puts "将 #{s} 更改为 #{t} 最少需要编辑 #{res} 步" # 记忆化搜索 mem = Array.new(n + 1) { Array.new(m + 1, -1) } res = edit_distance_dfs_mem(s, t, mem, n, m) puts "将 #{s} 更改为 #{t} 最少需要编辑 #{res} 步" # 动态规划 res = edit_distance_dp(s, t) puts "将 #{s} 更改为 #{t} 最少需要编辑 #{res} 步" # 空间优化后的动态规划 res = edit_distance_dp_comp(s, t) puts "将 #{s} 更改为 #{t} 最少需要编辑 #{res} 步" end ================================================ FILE: codes/ruby/chapter_dynamic_programming/knapsack.rb ================================================ =begin File: knapsack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 0-1 背包:暴力搜索 ### def knapsack_dfs(wgt, val, i, c) # 若已选完所有物品或背包无剩余容量,则返回价值 0 return 0 if i == 0 || c == 0 # 若超过背包容量,则只能选择不放入背包 return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c # 计算不放入和放入物品 i 的最大价值 no = knapsack_dfs(wgt, val, i - 1, c) yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] # 返回两种方案中价值更大的那一个 [no, yes].max end ### 0-1 背包:记忆化搜索 ### def knapsack_dfs_mem(wgt, val, mem, i, c) # 若已选完所有物品或背包无剩余容量,则返回价值 0 return 0 if i == 0 || c == 0 # 若已有记录,则直接返回 return mem[i][c] if mem[i][c] != -1 # 若超过背包容量,则只能选择不放入背包 return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c # 计算不放入和放入物品 i 的最大价值 no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] # 记录并返回两种方案中价值更大的那一个 mem[i][c] = [no, yes].max end ### 0-1 背包:动态规划 ### def knapsack_dp(wgt, val, cap) n = wgt.length # 初始化 dp 表 dp = Array.new(n + 1) { Array.new(cap + 1, 0) } # 状态转移 for i in 1...(n + 1) for c in 1...(cap + 1) if wgt[i - 1] > c # 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c] else # 不选和选物品 i 这两种方案的较大值 dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max end end end dp[n][cap] end ### 0-1 背包:空间优化后的动态规划 ### def knapsack_dp_comp(wgt, val, cap) n = wgt.length # 初始化 dp 表 dp = Array.new(cap + 1, 0) # 状态转移 for i in 1...(n + 1) # 倒序遍历 for c in cap.downto(1) if wgt[i - 1] > c # 若超过背包容量,则不选物品 i dp[c] = dp[c] else # 不选和选物品 i 这两种方案的较大值 dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max end end end dp[cap] end ### Driver Code ### if __FILE__ == $0 wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = wgt.length # 暴力搜索 res = knapsack_dfs(wgt, val, n, cap) puts "不超过背包容量的最大物品价值为 #{res}" # 记忆化搜索 mem = Array.new(n + 1) { Array.new(cap + 1, -1) } res = knapsack_dfs_mem(wgt, val, mem, n, cap) puts "不超过背包容量的最大物品价值为 #{res}" # 动态规划 res = knapsack_dp(wgt, val, cap) puts "不超过背包容量的最大物品价值为 #{res}" # 空间优化后的动态规划 res = knapsack_dp_comp(wgt, val, cap) puts "不超过背包容量的最大物品价值为 #{res}" end ================================================ FILE: codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb ================================================ =begin File: min_cost_climbing_stairs_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 爬楼梯最小代价:动态规划 ### def min_cost_climbing_stairs_dp(cost) n = cost.length - 1 return cost[n] if n == 1 || n == 2 # 初始化 dp 表,用于存储子问题的解 dp = Array.new(n + 1, 0) # 初始状态:预设最小子问题的解 dp[1], dp[2] = cost[1], cost[2] # 状态转移:从较小子问题逐步求解较大子问题 (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] } dp[n] end # 爬楼梯最小代价:空间优化后的动态规划 def min_cost_climbing_stairs_dp_comp(cost) n = cost.length - 1 return cost[n] if n == 1 || n == 2 a, b = cost[1], cost[2] (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] } b end ### Driver Code ### if __FILE__ == $0 cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] puts "输入楼梯的代价列表为 #{cost}" res = min_cost_climbing_stairs_dp(cost) puts "爬完楼梯的最低代价为 #{res}" res = min_cost_climbing_stairs_dp_comp(cost) puts "爬完楼梯的最低代价为 #{res}" end ================================================ FILE: codes/ruby/chapter_dynamic_programming/min_path_sum.rb ================================================ =begin File: min_path_sum.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 最小路径和:暴力搜索 ### def min_path_sum_dfs(grid, i, j) # 若为左上角单元格,则终止搜索 return grid[i][j] if i == 0 && j == 0 # 若行列索引越界,则返回 +∞ 代价 return Float::INFINITY if i < 0 || j < 0 # 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 up = min_path_sum_dfs(grid, i - 1, j) left = min_path_sum_dfs(grid, i, j - 1) # 返回从左上角到 (i, j) 的最小路径代价 [left, up].min + grid[i][j] end ### 最小路径和:记忆化搜索 ### def min_path_sum_dfs_mem(grid, mem, i, j) # 若为左上角单元格,则终止搜索 return grid[0][0] if i == 0 && j == 0 # 若行列索引越界,则返回 +∞ 代价 return Float::INFINITY if i < 0 || j < 0 # 若已有记录,则直接返回 return mem[i][j] if mem[i][j] != -1 # 左边和上边单元格的最小路径代价 up = min_path_sum_dfs_mem(grid, mem, i - 1, j) left = min_path_sum_dfs_mem(grid, mem, i, j - 1) # 记录并返回左上角到 (i, j) 的最小路径代价 mem[i][j] = [left, up].min + grid[i][j] end ### 最小路径和:动态规划 ### def min_path_sum_dp(grid) n, m = grid.length, grid.first.length # 初始化 dp 表 dp = Array.new(n) { Array.new(m, 0) } dp[0][0] = grid[0][0] # 状态转移:首行 (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] } # 状态转移:首列 (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] } # 状态转移:其余行和列 for i in 1...n for j in 1...m dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j] end end dp[n -1][m -1] end ### 最小路径和:空间优化后的动态规划 ### def min_path_sum_dp_comp(grid) n, m = grid.length, grid.first.length # 初始化 dp 表 dp = Array.new(m, 0) # 状态转移:首行 dp[0] = grid[0][0] (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] } # 状态转移:其余行 for i in 1...n # 状态转移:首列 dp[0] = dp[0] + grid[i][0] # 状态转移:其余列 (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] } end dp[m - 1] end ### Driver Code ### if __FILE__ == $0 grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] n, m = grid.length, grid.first.length # 暴力搜索 res = min_path_sum_dfs(grid, n - 1, m - 1) puts "从左上角到右下角的最小路径和为 #{res}" # 记忆化搜索 mem = Array.new(n) { Array.new(m, - 1) } res = min_path_sum_dfs_mem(grid, mem, n - 1, m -1) puts "从左上角到右下角的最小路径和为 #{res}" # 动态规划 res = min_path_sum_dp(grid) puts "从左上角到右下角的最小路径和为 #{res}" # 空间优化后的动态规划 res = min_path_sum_dp_comp(grid) puts "从左上角到右下角的最小路径和为 #{res}" end ================================================ FILE: codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb ================================================ =begin File: unbounded_knapsack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 完全背包:动态规划 ### def unbounded_knapsack_dp(wgt, val, cap) n = wgt.length # 初始化 dp 表 dp = Array.new(n + 1) { Array.new(cap + 1, 0) } # 状态转移 for i in 1...(n + 1) for c in 1...(cap + 1) if wgt[i - 1] > c # 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c] else # 不选和选物品 i 这两种方案的较大值 dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max end end end dp[n][cap] end ### 完全背包:空间优化后的动态规划 ##3 def unbounded_knapsack_dp_comp(wgt, val, cap) n = wgt.length # 初始化 dp 表 dp = Array.new(cap + 1, 0) # 状态转移 for i in 1...(n + 1) # 正序遍历 for c in 1...(cap + 1) if wgt[i -1] > c # 若超过背包容量,则不选物品 i dp[c] = dp[c] else # 不选和选物品 i 这两种方案的较大值 dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max end end end dp[cap] end ### Driver Code ### if __FILE__ == $0 wgt = [1, 2, 3] val = [5, 11, 15] cap = 4 # 动态规划 res = unbounded_knapsack_dp(wgt, val, cap) puts "不超过背包容量的最大物品价值为 #{res}" # 空间优化后的动态规划 res = unbounded_knapsack_dp_comp(wgt, val, cap) puts "不超过背包容量的最大物品价值为 #{res}" end ================================================ FILE: codes/ruby/chapter_graph/graph_adjacency_list.rb ================================================ =begin File: graph_adjacency_list.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/vertex' ### 基于邻接表实现的无向图类 ### class GraphAdjList attr_reader :adj_list ### 构造方法 ### def initialize(edges) # 邻接表,key:顶点,value:该顶点的所有邻接顶点 @adj_list = {} # 添加所有顶点和边 for edge in edges add_vertex(edge[0]) add_vertex(edge[1]) add_edge(edge[0], edge[1]) end end ### 获取顶点数量 ### def size @adj_list.length end ### 添加边 ### def add_edge(vet1, vet2) raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) @adj_list[vet1] << vet2 @adj_list[vet2] << vet1 end ### 删除边 ### def remove_edge(vet1, vet2) raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) # 删除边 vet1 - vet2 @adj_list[vet1].delete(vet2) @adj_list[vet2].delete(vet1) end ### 添加顶点 ### def add_vertex(vet) return if @adj_list.include?(vet) # 在邻接表中添加一个新链表 @adj_list[vet] = [] end ### 删除顶点 ### def remove_vertex(vet) raise ArgumentError unless @adj_list.include?(vet) # 在邻接表中删除顶点 vet 对应的链表 @adj_list.delete(vet) # 遍历其他顶点的链表,删除所有包含 vet 的边 for vertex in @adj_list @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet) end end ### 打印邻接表 ### def __print__ puts '邻接表 =' for vertex in @adj_list tmp = @adj_list[vertex.first].map { |v| v.val } puts "#{vertex.first.val}: #{tmp}," end end end ### Driver Code ### if __FILE__ == $0 # 初始化无向图 v = vals_to_vets([1, 3, 2, 5, 4]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ] graph = GraphAdjList.new(edges) puts "\n初始化后,图为" graph.__print__ # 添加边 # 顶点 1,2 即 v[0],v[2] graph.add_edge(v[0], v[2]) puts "\n添加边 1-2 后,图为" graph.__print__ # 删除边 # 顶点 1,3 即 v[0],v[1] graph.remove_edge(v[0], v[1]) puts "\n删除边 1-3 后,图为" graph.__print__ # 添加顶点 v5 = Vertex.new(6) graph.add_vertex(v5) puts "\n添加顶点 6 后,图为" graph.__print__ # 删除顶点 # 顶点 3 即 v[1] graph.remove_vertex(v[1]) puts "\n删除顶点 3 后,图为" graph.__print__ end ================================================ FILE: codes/ruby/chapter_graph/graph_adjacency_matrix.rb ================================================ =begin File: graph_adjacency_matrix.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/print_util' ### 基于邻接矩阵实现的无向图类 ### class GraphAdjMat def initialize(vertices, edges) ### 构造方法 ### # 顶点列表,元素代表“顶点值”,索引代表“顶点索引” @vertices = [] # 邻接矩阵,行列索引对应“顶点索引” @adj_mat = [] # 添加顶点 vertices.each { |val| add_vertex(val) } # 添加边 # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 edges.each { |e| add_edge(e[0], e[1]) } end ### 获取顶点数量 ### def size @vertices.length end ### 添加顶点 ### def add_vertex(val) n = size # 向顶点列表中添加新顶点的值 @vertices << val # 在邻接矩阵中添加一行 new_row = Array.new(n, 0) @adj_mat << new_row # 在邻接矩阵中添加一列 @adj_mat.each { |row| row << 0 } end ### 删除顶点 ### def remove_vertex(index) raise IndexError if index >= size # 在顶点列表中移除索引 index 的顶点 @vertices.delete_at(index) # 在邻接矩阵中删除索引 index 的行 @adj_mat.delete_at(index) # 在邻接矩阵中删除索引 index 的列 @adj_mat.each { |row| row.delete_at(index) } end ### 添加边 ### def add_edge(i, j) # 参数 i, j 对应 vertices 元素索引 # 索引越界与相等处理 if i < 0 || j < 0 || i >= size || j >= size || i == j raise IndexError end # 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) @adj_mat[i][j] = 1 @adj_mat[j][i] = 1 end ### 删除边 ### def remove_edge(i, j) # 参数 i, j 对应 vertices 元素索引 # 索引越界与相等处理 if i < 0 || j < 0 || i >= size || j >= size || i == j raise IndexError end @adj_mat[i][j] = 0 @adj_mat[j][i] = 0 end ### 打印邻接矩阵 ### def __print__ puts "顶点列表 = #{@vertices}" puts '邻接矩阵 =' print_matrix(@adj_mat) end end ### Driver Code ### if __FILE__ == $0 # 初始化无向图 # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 vertices = [1, 3, 2, 5, 4] edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] graph = GraphAdjMat.new(vertices, edges) puts "\n初始化后,图为" graph.__print__ # 添加边 # 顶点 1, 2 的索引分别为 0, 2 graph.add_edge(0, 2) puts "\n添加边 1-2 后,图为" graph.__print__ # 删除边 # 定点 1, 3 的索引分别为 0, 1 graph.remove_edge(0, 1) puts "\n删除边 1-3 后,图为" graph.__print__ # 添加顶点 graph.add_vertex(6) puts "\n添加顶点 6 后,图为" graph.__print__ # 删除顶点 # 顶点 3 的索引为 1 graph.remove_vertex(1) puts "\n删除顶点 3 后,图为" graph.__print__ end ================================================ FILE: codes/ruby/chapter_graph/graph_bfs.rb ================================================ =begin File: graph_bfs.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require 'set' require_relative './graph_adjacency_list' require_relative '../utils/vertex' ### 广度优先遍历 ### def graph_bfs(graph, start_vet) # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 # 顶点遍历序列 res = [] # 哈希集合,用于记录已被访问过的顶点 visited = Set.new([start_vet]) # 队列用于实现 BFS que = [start_vet] # 以顶点 vet 为起点,循环直至访问完所有顶点 while que.length > 0 vet = que.shift # 队首顶点出队 res << vet # 记录访问顶点 # 遍历该顶点的所有邻接顶点 for adj_vet in graph.adj_list[vet] next if visited.include?(adj_vet) # 跳过已被访问的顶点 que << adj_vet # 只入队未访问的顶点 visited.add(adj_vet) # 标记该顶点已被访问 end end # 返回顶点遍历序列 res end ### Driver Code ### if __FILE__ == $0 # 初始化无向图 v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ] graph = GraphAdjList.new(edges) puts "\n初始化后,图为" graph.__print__ # 广度优先遍历 res = graph_bfs(graph, v.first) puts "\n广度优先便利(BFS)顶点序列为" p vets_to_vals(res) end ================================================ FILE: codes/ruby/chapter_graph/graph_dfs.rb ================================================ =begin File: graph_dfs.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require 'set' require_relative './graph_adjacency_list' require_relative '../utils/vertex' ### 深度优先遍历辅助函数 ### def dfs(graph, visited, res, vet) res << vet # 记录访问顶点 visited.add(vet) # 标记该顶点已被访问 # 遍历该顶点的所有邻接顶点 for adj_vet in graph.adj_list[vet] next if visited.include?(adj_vet) # 跳过已被访问的顶点 # 递归访问邻接顶点 dfs(graph, visited, res, adj_vet) end end ### 深度优先遍历 ### def graph_dfs(graph, start_vet) # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 # 顶点遍历序列 res = [] # 哈希集合,用于记录已被访问过的顶点 visited = Set.new dfs(graph, visited, res, start_vet) res end ### Driver Code ### if __FILE__ == $0 # 初始化无向图 v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ] graph = GraphAdjList.new(edges) puts "\n初始化后,图为" graph.__print__ # 深度优先遍历 res = graph_dfs(graph, v[0]) puts "\n深度优先遍历(DFS)顶点序列为" p vets_to_vals(res) end ================================================ FILE: codes/ruby/chapter_greedy/coin_change_greedy.rb ================================================ =begin File: coin_change_greedy.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 零钱兑换:贪心 ### def coin_change_greedy(coins, amt) # 假设 coins 列表有序 i = coins.length - 1 count = 0 # 循环进行贪心选择,直到无剩余金额 while amt > 0 # 找到小于且最接近剩余金额的硬币 while i > 0 && coins[i] > amt i -= 1 end # 选择 coins[i] amt -= coins[i] count += 1 end # 若未找到可行方案, 则返回 -1 amt == 0 ? count : -1 end ### Driver Code ### if __FILE__ == $0 # 贪心:能够保证找到全局最优解 coins = [1, 5, 10, 20, 50, 100] amt = 186 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "凑到 #{amt} 所需的最少硬币数量为 #{res}" # 贪心:无法保证找到全局最优解 coins = [1, 20, 50] amt = 60 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "凑到 #{amt} 所需的最少硬币数量为 #{res}" puts "实际上需要的最少数量为 3 , 即 20 + 20 + 20" # 贪心:无法保证找到全局最优解 coins = [1, 49, 50] amt = 98 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "凑到 #{amt} 所需的最少硬币数量为 #{res}" puts "实际上需要的最少数量为 2 , 即 49 + 49" end ================================================ FILE: codes/ruby/chapter_greedy/fractional_knapsack.rb ================================================ =begin File: fractional_knapsack.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 物品 ### class Item attr_accessor :w # 物品重量 attr_accessor :v # 物品价值 def initialize(w, v) @w = w @v = v end end ### 分数背包:贪心 ### def fractional_knapsack(wgt, val, cap) # 创建物品列表,包含两个属性:重量,价值 items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) } # 按照单位价值 item.v / item.w 从高到低进行排序 items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) } # 循环贪心选择 res = 0 for item in items if item.w <= cap # 若剩余容量充足,则将当前物品整个装进背包 res += item.v cap -= item.w else # 若剩余容量不足,则将当前物品的一部分装进背包 res += (item.v.to_f / item.w) * cap # 已无剩余容量,因此跳出循环 break end end res end ### Driver Code ### if __FILE__ == $0 wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = wgt.length # 贪心算法 res = fractional_knapsack(wgt, val, cap) puts "不超过背包容量的最大物品价值为 #{res}" end ================================================ FILE: codes/ruby/chapter_greedy/max_capacity.rb ================================================ =begin File: max_capacity.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 最大容量:贪心 ### def max_capacity(ht) # 初始化 i, j,使其分列数组两端 i, j = 0, ht.length - 1 # 初始最大容量为 0 res = 0 # 循环贪心选择,直至两板相遇 while i < j # 更新最大容量 cap = [ht[i], ht[j]].min * (j - i) res = [res, cap].max # 向内移动短板 if ht[i] < ht[j] i += 1 else j -= 1 end end res end ### Driver Code ### if __FILE__ == $0 ht = [3, 8, 5, 2, 7, 7, 3, 4] # 贪心算法 res = max_capacity(ht) puts "最大容量为 #{res}" end ================================================ FILE: codes/ruby/chapter_greedy/max_product_cutting.rb ================================================ =begin File: max_product_cutting.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 最大切分乘积:贪心 ### def max_product_cutting(n) # 当 n <= 3 时,必须切分出一个 1 return 1 * (n - 1) if n <= 3 # 贪心地切分出 3 ,a 为 3 的个数,b 为余数 a, b = n / 3, n % 3 # 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 return (3.pow(a - 1) * 2 * 2).to_i if b == 1 # 当余数为 2 时,不做处理 return (3.pow(a) * 2).to_i if b == 2 # 当余数为 0 时,不做处理 3.pow(a).to_i end ### Driver Code ### if __FILE__ == $0 n = 58 # 贪心算法 res = max_product_cutting(n) puts "最大切分乘积为 #{res}" end ================================================ FILE: codes/ruby/chapter_hashing/array_hash_map.rb ================================================ =begin File: array_hash_map.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 键值对 ### class Pair attr_accessor :key, :val def initialize(key, val) @key = key @val = val end end ### 基于数组实现的哈希表 ### class ArrayHashMap ### 构造方法 ### def initialize # 初始化数组,包含 100 个桶 @buckets = Array.new(100) end ### 哈希函数 ### def hash_func(key) index = key % 100 end ### 查询操作 ### def get(key) index = hash_func(key) pair = @buckets[index] return if pair.nil? pair.val end ### 添加操作 ### def put(key, val) pair = Pair.new(key, val) index = hash_func(key) @buckets[index] = pair end ### 删除操作 ### def remove(key) index = hash_func(key) # 置为 nil ,代表删除 @buckets[index] = nil end ### 获取所有键值对 ### def entry_set result = [] @buckets.each { |pair| result << pair unless pair.nil? } result end ### 获取所有键 ### def key_set result = [] @buckets.each { |pair| result << pair.key unless pair.nil? } result end ### 获取所有值 ### def value_set result = [] @buckets.each { |pair| result << pair.val unless pair.nil? } result end ### 打印哈希表 ### def print @buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? } end end ### Driver Code ### if __FILE__ == $0 # 初始化哈希表 hmap = ArrayHashMap.new # 添加操作 # 在哈希表中添加键值对 (key, value) hmap.put(12836, "小哈") hmap.put(15937, "小啰") hmap.put(16750, "小算") hmap.put(13276, "小法") hmap.put(10583, "小鸭") puts "\n添加完成后,哈希表为\nKey -> Value" hmap.print # 查询操作 # 向哈希表中输入键 key , 得到值 value name = hmap.get(15937) puts "\n输入学号 15937 ,查询到姓名 #{name}" # 删除操作 # 在哈希表中删除值对 (key, value) hmap.remove(10583) puts "\n删除 10583 后,哈希表为\nKey -> Value" hmap.print # 遍历哈希表 puts "\n遍历键值对 Key->Value" for pair in hmap.entry_set puts "#{pair.key} -> #{pair.val}" end puts "\n单独篇遍历键 Key" for key in hmap.key_set puts key end puts "\n单独遍历值 Value" for val in hmap.value_set puts val end end ================================================ FILE: codes/ruby/chapter_hashing/built_in_hash.rb ================================================ =begin File: built_in_hash.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### Driver Code ### if __FILE__ == $0 num = 3 hash_num = num.hash puts "整数 #{num} 的哈希值为 #{hash_num}" bol = true hash_bol = bol.hash puts "布尔量 #{bol} 的哈希值为 #{hash_bol}" dec = 3.14159 hash_dec = dec.hash puts "小数 #{dec} 的哈希值为 #{hash_dec}" str = "Hello 算法" hash_str = str.hash puts "字符串 #{str} 的哈希值为 #{hash_str}" tup = [12836, '小哈'] hash_tup = tup.hash puts "元组 #{tup} 的哈希值为 #{hash_tup}" obj = ListNode.new(0) hash_obj = obj.hash puts "节点对象 #{obj} 的哈希值为 #{hash_obj}" end ================================================ FILE: codes/ruby/chapter_hashing/hash_map.rb ================================================ =begin File: hash_map.rb Created Time: 2024-04-14 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/print_util' ### Driver Code ### if __FILE__ == $0 # 初始化哈希表 hmap = {} # 添加操作 # 在哈希表中添加键值对 (key, value) hmap[12836] = "小哈" hmap[15937] = "小啰" hmap[16750] = "小算" hmap[13276] = "小法" hmap[10583] = "小鸭" puts "\n添加完成后,哈希表为\nKey -> Value" print_hash_map(hmap) # 查询操作 # 向哈希表中输入键 key ,得到值 value name = hmap[15937] puts "\n输入学号 15937 ,查询到姓名 #{name}" # 删除操作 # 在哈希表中删除键值对 (key, value) hmap.delete(10583) puts "\n删除 10583 后,哈希表为\nKey -> Value" print_hash_map(hmap) # 遍历哈希表 puts "\n遍历键值对 Key->Value" hmap.entries.each { |key, value| puts "#{key} -> #{value}" } puts "\n单独遍历键 Key" hmap.keys.each { |key| puts key } puts "\n单独遍历值 Value" hmap.values.each { |val| puts val } end ================================================ FILE: codes/ruby/chapter_hashing/hash_map_chaining.rb ================================================ =begin File: hash_map_chaining.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative './array_hash_map' ### 键式地址哈希表 ### class HashMapChaining ### 构造方法 ### def initialize @size = 0 # 键值对数量 @capacity = 4 # 哈希表容量 @load_thres = 2.0 / 3.0 # 触发扩容的负载因子阈值 @extend_ratio = 2 # 扩容倍数 @buckets = Array.new(@capacity) { [] } # 桶数组 end ### 哈希函数 ### def hash_func(key) key % @capacity end ### 负载因子 ### def load_factor @size / @capacity end ### 查询操作 ### def get(key) index = hash_func(key) bucket = @buckets[index] # 遍历桶,若找到 key ,则返回对应 val for pair in bucket return pair.val if pair.key == key end # 若未找到 key , 则返回 nil nil end ### 添加操作 ### def put(key, val) # 当负载因子超过阈值时,执行扩容 extend if load_factor > @load_thres index = hash_func(key) bucket = @buckets[index] # 遍历桶,若遇到指定 key ,则更新对应 val 并返回 for pair in bucket if pair.key == key pair.val = val return end end # 若无该 key ,则将键值对添加至尾部 pair = Pair.new(key, val) bucket << pair @size += 1 end ### 删除操作 ### def remove(key) index = hash_func(key) bucket = @buckets[index] # 遍历桶,从中删除键值对 for pair in bucket if pair.key == key bucket.delete(pair) @size -= 1 break end end end ### 扩容哈希表 ### def extend # 暫存原哈希表 buckets = @buckets # 初始化扩容后的新哈希表 @capacity *= @extend_ratio @buckets = Array.new(@capacity) { [] } @size = 0 # 将键值对从原哈希表搬运至新哈希表 for bucket in buckets for pair in bucket put(pair.key, pair.val) end end end ### 打印哈希表 ### def print for bucket in @buckets res = [] for pair in bucket res << "#{pair.key} -> #{pair.val}" end pp res end end end ### Driver Code ### if __FILE__ == $0 ### 初始化哈希表 hashmap = HashMapChaining.new # 添加操作 # 在哈希表中添加键值对 (key, value) hashmap.put(12836, "小哈") hashmap.put(15937, "小啰") hashmap.put(16750, "小算") hashmap.put(13276, "小法") hashmap.put(10583, "小鸭") puts "\n添加完成后,哈希表为\n[Key1 -> Value1, Key2 -> Value2, ...]" hashmap.print # 查询操作 # 向哈希表中输入键 key ,得到值 value name = hashmap.get(13276) puts "\n输入学号 13276 ,查询到姓名 #{name}" # 删除操作 # 在哈希表中删除键值对 (key, value) hashmap.remove(12836) puts "\n删除 12836 后,哈希表为\n[Key1 -> Value1, Key2 -> Value2, ...]" hashmap.print end ================================================ FILE: codes/ruby/chapter_hashing/hash_map_open_addressing.rb ================================================ =begin File: hash_map_open_addressing.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative './array_hash_map' ### 开放寻址哈希表 ### class HashMapOpenAddressing TOMBSTONE = Pair.new(-1, '-1') # 删除标记 ### 构造方法 ### def initialize @size = 0 # 键值对数量 @capacity = 4 # 哈希表容量 @load_thres = 2.0 / 3.0 # 触发扩容的负载因子阈值 @extend_ratio = 2 # 扩容倍数 @buckets = Array.new(@capacity) # 桶数组 end ### 哈希函数 ### def hash_func(key) key % @capacity end ### 负载因子 ### def load_factor @size / @capacity end ### 搜索 key 对应的桶索引 ### def find_bucket(key) index = hash_func(key) first_tombstone = -1 # 线性探测,当遇到空桶时跳出 while !@buckets[index].nil? # 若遇到 key ,返回对应的桶索引 if @buckets[index].key == key # 若之前遇到了删除标记,则将键值对移动至该索引处 if first_tombstone != -1 @buckets[first_tombstone] = @buckets[index] @buckets[index] = TOMBSTONE return first_tombstone # 返回移动后的桶索引 end return index # 返回桶索引 end # 记录遇到的首个删除标记 first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE # 计算桶索引,越过尾部则返回头部 index = (index + 1) % @capacity end # 若 key 不存在,则返回添加点的索引 first_tombstone == -1 ? index : first_tombstone end ### 查询操作 ### def get(key) # 搜索 key 对应的桶索引 index = find_bucket(key) # 若找到键值对,则返回对应 val return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index]) # 若键值对不存在,则返回 nil nil end ### 添加操作 ### def put(key, val) # 当负载因子超过阈值时,执行扩容 extend if load_factor > @load_thres # 搜索 key 对应的桶索引 index = find_bucket(key) # 若找到键值对,则覆盖 val 开返回 unless [nil, TOMBSTONE].include?(@buckets[index]) @buckets[index].val = val return end # 若键值对不存在,则添加该键值对 @buckets[index] = Pair.new(key, val) @size += 1 end ### 删除操作 ### def remove(key) # 搜索 key 对应的桶索引 index = find_bucket(key) # 若找到键值对,则用删除标记覆盖它 unless [nil, TOMBSTONE].include?(@buckets[index]) @buckets[index] = TOMBSTONE @size -= 1 end end ### 扩容哈希表 ### def extend # 暂存原哈希表 buckets_tmp = @buckets # 初始化扩容后的新哈希表 @capacity *= @extend_ratio @buckets = Array.new(@capacity) @size = 0 # 将键值对从原哈希表搬运至新哈希表 for pair in buckets_tmp put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair) end end ### 打印哈希表 ### def print for pair in @buckets if pair.nil? puts "Nil" elsif pair == TOMBSTONE puts "TOMBSTONE" else puts "#{pair.key} -> #{pair.val}" end end end end ### Driver Code ### if __FILE__ == $0 # 初始化哈希表 hashmap = HashMapOpenAddressing.new # 添加操作 # 在哈希表中添加键值对 (key, val) hashmap.put(12836, "小哈") hashmap.put(15937, "小啰") hashmap.put(16750, "小算") hashmap.put(13276, "小法") hashmap.put(10583, "小鸭") puts "\n添加完成后,哈希表为\nKey -> Value" hashmap.print # 查询操作 # 向哈希表中输入键 key ,得到值 val name = hashmap.get(13276) puts "\n输入学号 13276 ,查询到姓名 #{name}" # 删除操作 # 在哈希表中删除键值对 (key, val) hashmap.remove(16750) puts "\n删除 16750 后,哈希表为\nKey -> Value" hashmap.print end ================================================ FILE: codes/ruby/chapter_hashing/simple_hash.rb ================================================ =begin File: simple_hash.rb Created Time: 2024-04-14 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 加法哈希 ### def add_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash += c.ord } hash % modulus end ### 乘法哈希 ### def mul_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash = 31 * hash + c.ord } hash % modulus end ### 异或哈希 ### def xor_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash ^= c.ord } hash % modulus end ### 旋转哈希 ### def rot_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord } hash % modulus end ### Driver Code ### if __FILE__ == $0 key = "Hello 算法" hash = add_hash(key) puts "加法哈希值为 #{hash}" hash = mul_hash(key) puts "乘法哈希值为 #{hash}" hash = xor_hash(key) puts "异或哈希值为 #{hash}" hash = rot_hash(key) puts "旋转哈希值为 #{hash}" end ================================================ FILE: codes/ruby/chapter_heap/my_heap.rb ================================================ =begin File: my_heap.rb Created Time: 2024-04-19 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/print_util' ### 大顶堆 ### class MaxHeap attr_reader :max_heap ### 构造方法,根据输入列表建堆 ### def initialize(nums) # 将列表元素原封不动添加进堆 @max_heap = nums # 堆化除叶节点以外的其他所有节点 parent(size - 1).downto(0) do |i| sift_down(i) end end ### 获取左子节点的索引 ### def left(i) 2 * i + 1 end ### 获取右子节点的索引 ### def right(i) 2 * i + 2 end ### 获取父节点的索引 ### def parent(i) (i - 1) / 2 # 向下整除 end ### 交换元素 ### def swap(i, j) @max_heap[i], @max_heap[j] = @max_heap[j], @max_heap[i] end ### 获取堆大小 ### def size @max_heap.length end ### 判断堆是否为空 ### def is_empty? size == 0 end ### 访问堆顶元素 ### def peek @max_heap[0] end ### 元素入堆 ### def push(val) # 添加节点 @max_heap << val # 从底至顶堆化 sift_up(size - 1) end ### 从节点 i 开始,从底至顶堆化 ### def sift_up(i) loop do # 获取节点 i 的父节点 p = parent(i) # 当“越过根节点”或“节点无须修复”时,结束堆化 break if p < 0 || @max_heap[i] <= @max_heap[p] # 交换两节点 swap(i, p) # 循环向上堆化 i = p end end ### 元素出堆 ### def pop # 判空处理 raise IndexError, "堆为空" if is_empty? # 交换根节点与最右叶节点(交换首元素与尾元素) swap(0, size - 1) # 删除节点 val = @max_heap.pop # 从顶至底堆化 sift_down(0) # 返回堆顶元素 val end ### 从节点 i 开始,从顶至底堆化 ### def sift_down(i) loop do # 判断节点 i, l, r 中值最大的节点,记为 ma l, r, ma = left(i), right(i), i ma = l if l < size && @max_heap[l] > @max_heap[ma] ma = r if r < size && @max_heap[r] > @max_heap[ma] # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 break if ma == i # 交换两节点 swap(i, ma) # 循环向下堆化 i = ma end end ### 打印堆(二叉树)### def __print__ print_heap(@max_heap) end end ### Driver Code ### if __FILE__ == $0 # 初始化大顶堆 max_heap = MaxHeap.new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) puts "\n输入列表并建堆后" max_heap.__print__ # 获取堆顶元素 peek = max_heap.peek puts "\n堆顶元素为 #{peek}" # 元素入堆 val = 7 max_heap.push(val) puts "\n元素 #{val} 入堆后" max_heap.__print__ # 堆顶元素出堆 peek = max_heap.pop puts "\n堆顶元素 #{peek} 出堆后" max_heap.__print__ # 获取堆大小 size = max_heap.size puts "\n堆元素数量为 #{size}" # 判断堆是否为空 is_empty = max_heap.is_empty? puts "\n堆是否为空 #{is_empty}" end ================================================ FILE: codes/ruby/chapter_heap/top_k.rb ================================================ =begin File: top_k.rb Created Time: 2024-04-19 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative "./my_heap" ### 元素入堆 ### def push_min_heap(heap, val) # 元素取反 heap.push(-val) end ### 元素出堆 ### def pop_min_heap(heap) # 元素取反 -heap.pop end ### 访问堆顶元素 ### def peek_min_heap(heap) # 元素取反 -heap.peek end ### 取出堆中元素 ### def get_min_heap(heap) # 将堆中所有元素取反 heap.max_heap.map { |x| -x } end ### 基于堆查找数组中最大的 k 个元素 ### def top_k_heap(nums, k) # 初始化小顶堆 # 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆 max_heap = MaxHeap.new([]) # 将数组的前 k 个元素入堆 for i in 0...k push_min_heap(max_heap, nums[i]) end # 从第 k+1 个元素开始,保持堆的长度为 k for i in k...nums.length # 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 if nums[i] > peek_min_heap(max_heap) pop_min_heap(max_heap) push_min_heap(max_heap, nums[i]) end end get_min_heap(max_heap) end ### Driver Code ### if __FILE__ == $0 nums = [1, 7, 6, 3, 2] k = 3 res = top_k_heap(nums, k) puts "最大的 #{k} 个元素为" print_heap(res) end ================================================ FILE: codes/ruby/chapter_searching/binary_search.rb ================================================ =begin File: binary_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### 二分查找(双闭区间) ### def binary_search(nums, target) # 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 i, j = 0, nums.length - 1 # 循环,当搜索区间为空时跳出(当 i > j 时为空) while i <= j # 理论上 Ruby 的数字可以无限大(取决于内存大小),无须考虑大数越界问题 m = (i + j) / 2 # 计算中点索引 m if nums[m] < target i = m + 1 # 此情况说明 target 在区间 [m+1, j] 中 elsif nums[m] > target j = m - 1 # 此情况说明 target 在区间 [i, m-1] 中 else return m # 找到目标元素,返回其索引 end end -1 # 未找到目标元素,返回 -1 end ### 二分查找(左闭右开区间) ### def binary_search_lcro(nums, target) # 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 i, j = 0, nums.length # 循环,当搜索区间为空时跳出(当 i = j 时为空) while i < j # 计算中点索引 m m = (i + j) / 2 if nums[m] < target i = m + 1 # 此情况说明 target 在区间 [m+1, j) 中 elsif nums[m] > target j = m - 1 # 此情况说明 target 在区间 [i, m) 中 else return m # 找到目标元素,返回其索引 end end -1 # 未找到目标元素,返回 -1 end ### Driver Code ### if __FILE__ == $0 target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # 二分查找(双闭区间) index = binary_search(nums, target) puts "目标元素 6 的索引 = #{index}" # 二分查找(左闭右开区间) index = binary_search_lcro(nums, target) puts "目标元素 6 的索引 = #{index}" end ================================================ FILE: codes/ruby/chapter_searching/binary_search_edge.rb ================================================ =begin File: binary_search_edge.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative './binary_search_insertion' ### 二分查找最左一个 target ### def binary_search_left_edge(nums, target) # 等价于查找 target 的插入点 i = binary_search_insertion(nums, target) # 未找到 target ,返回 -1 return -1 if i == nums.length || nums[i] != target i # 找到 target ,返回索引 i end ### 二分查找最右一个 target ### def binary_search_right_edge(nums, target) # 转化为查找最左一个 target + 1 i = binary_search_insertion(nums, target + 1) # j 指向最右一个 target ,i 指向首个大于 target 的元素 j = i - 1 # 未找到 target ,返回 -1 return -1 if j == -1 || nums[j] != target j # 找到 target ,返回索引 j end ### Driver Code ### if __FILE__ == $0 # 包含重复元素的数组 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] puts "\n数组 nums = #{nums}" # 二分查找左边界和右边界 for target in [6, 7] index = binary_search_left_edge(nums, target) puts "最左一个元素 #{target} 的索引为 #{index}" index = binary_search_right_edge(nums, target) puts "最右一个元素 #{target} 的索引为 #{index}" end end ================================================ FILE: codes/ruby/chapter_searching/binary_search_insertion.rb ================================================ =begin File: binary_search_insertion.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### 二分查找插入点(无重复元素) ### def binary_search_insertion_simple(nums, target) # 初始化双闭区间 [0, n-1] i, j = 0, nums.length - 1 while i <= j # 计算中点索引 m m = (i + j) / 2 if nums[m] < target i = m + 1 # target 在区间 [m+1, j] 中 elsif nums[m] > target j = m - 1 # target 在区间 [i, m-1] 中 else return m # 找到 target ,返回插入点 m end end i # 未找到 target ,返回插入点 i end ### 二分查找插入点(存在重复元素) ### def binary_search_insertion(nums, target) # 初始化双闭区间 [0, n-1] i, j = 0, nums.length - 1 while i <= j # 计算中点索引 m m = (i + j) / 2 if nums[m] < target i = m + 1 # target 在区间 [m+1, j] 中 elsif nums[m] > target j = m - 1 # target 在区间 [i, m-1] 中 else j = m - 1 # 首个小于 target 的元素在区间 [i, m-1] 中 end end i # 返回插入点 i end ### Driver Code ### if __FILE__ == $0 # 无重复元素的数组 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] puts "\n数组 nums = #{nums}" # 二分查找插入点 for target in [6, 9] index = binary_search_insertion_simple(nums, target) puts "元素 #{target} 的插入点的索引为 #{index}" end # 包含重复元素的数组 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] puts "\n数组 nums = #{nums}" # 二分查找插入点 for target in [2, 6, 20] index = binary_search_insertion(nums, target) puts "元素 #{target} 的插入点的索引为 #{index}" end end ================================================ FILE: codes/ruby/chapter_searching/hashing_search.rb ================================================ =begin File: hashing_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/list_node' ### 哈希查找(数组) ### def hashing_search_array(hmap, target) # 哈希表的 key: 目标元素,value: 索引 # 若哈希表中无此 key ,返回 -1 hmap[target] || -1 end ### 哈希查找(链表) ### def hashing_search_linkedlist(hmap, target) # 哈希表的 key: 目标元素,value: 节点对象 # 若哈希表中无此 key ,返回 None hmap[target] || nil end ### Driver Code ### if __FILE__ == $0 target = 3 # 哈希查找(数组) nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] # 初始化哈希表 map0 = {} for i in 0...nums.length map0[nums[i]] = i # key: 元素,value: 索引 end index = hashing_search_array(map0, target) puts "目标元素 3 的索引 = #{index}" # 哈希查找(链表) head = arr_to_linked_list(nums) # 初始化哈希表 map1 = {} while head map1[head.val] = head head = head.next end node = hashing_search_linkedlist(map1, target) puts "目标节点值 3 的对应节点对象为 #{node}" end ================================================ FILE: codes/ruby/chapter_searching/linear_search.rb ================================================ =begin File: linear_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/list_node' ### 线性查找(数组) ### def linear_search_array(nums, target) # 遍历数组 for i in 0...nums.length return i if nums[i] == target # 找到目标元素,返回其索引 end -1 # 未找到目标元素,返回 -1 end ### 线性查找(链表) ### def linear_search_linkedlist(head, target) # 遍历链表 while head return head if head.val == target # 找到目标节点,返回之 head = head.next end nil # 未找到目标节点,返回 None end ### Driver Code ### if __FILE__ == $0 target = 3 # 在数组中执行线性查找 nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] index = linear_search_array(nums, target) puts "目标元素 3 的索引 = #{index}" # 在链表中执行线性查找 head = arr_to_linked_list(nums) node = linear_search_linkedlist(head, target) puts "目标节点值 3 的对应节点对象为 #{node}" end ================================================ FILE: codes/ruby/chapter_searching/two_sum.rb ================================================ =begin File: two_sum.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### 方法一:暴力枚举 ### def two_sum_brute_force(nums, target) # 两层循环,时间复杂度为 O(n^2) for i in 0...(nums.length - 1) for j in (i + 1)...nums.length return [i, j] if nums[i] + nums[j] == target end end [] end ### 方法二:辅助哈希表 ### def two_sum_hash_table(nums, target) # 辅助哈希表,空间复杂度为 O(n) dic = {} # 单层循环,时间复杂度为 O(n) for i in 0...nums.length return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i]) dic[nums[i]] = i end [] end ### Driver Code ### if __FILE__ == $0 # ======= Test Case ======= nums = [2, 7, 11, 15] target = 13 # ====== Driver Code ====== # 方法一 res = two_sum_brute_force(nums, target) puts "方法一 res = #{res}" # 方法二 res = two_sum_hash_table(nums, target) puts "方法二 res = #{res}" end ================================================ FILE: codes/ruby/chapter_sorting/bubble_sort.rb ================================================ =begin File: bubble_sort.rb Created Time: 2024-05-02 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 冒泡排序 ### def bubble_sort(nums) n = nums.length # 外循环:未排序区间为 [0, i] for i in (n - 1).downto(1) # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in 0...i if nums[j] > nums[j + 1] # 交换 nums[j] 与 nums[j + 1] nums[j], nums[j + 1] = nums[j + 1], nums[j] end end end end ### 冒泡排序(标志优化)### def bubble_sort_with_flag(nums) n = nums.length # 外循环:未排序区间为 [0, i] for i in (n - 1).downto(1) flag = false # 初始化标志位 # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in 0...i if nums[j] > nums[j + 1] # 交换 nums[j] 与 nums[j + 1] nums[j], nums[j + 1] = nums[j + 1], nums[j] flag = true # 记录交换元素 end end break unless flag # 此轮“冒泡”未交换任何元素,直接跳出 end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] bubble_sort(nums) puts "冒泡排序完成后 nums = #{nums}" nums1 = [4, 1, 3, 1, 5, 2] bubble_sort_with_flag(nums1) puts "冒泡排序完成后 nums = #{nums1}" end ================================================ FILE: codes/ruby/chapter_sorting/bucket_sort.rb ================================================ =begin File: bucket_sort.rb Created Time: 2024-04-17 Author: Martin Xu (martin.xus@gmail.com) =end ### 桶排序 ### def bucket_sort(nums) # 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 k = nums.length / 2 buckets = Array.new(k) { [] } # 1. 将数组元素分配到各个桶中 nums.each do |num| # 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] i = (num * k).to_i # 将 num 添加进桶 i buckets[i] << num end # 2. 对各个桶执行排序 buckets.each do |bucket| # 使用内置排序函数,也可以替换成其他排序算法 bucket.sort! end # 3. 遍历桶合并结果 i = 0 buckets.each do |bucket| bucket.each do |num| nums[i] = num i += 1 end end end ### Driver Code ### if __FILE__ == $0 # 设输入数据为浮点数,范围为 [0, 1) nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] bucket_sort(nums) puts "桶排序完成后 nums = #{nums}" end ================================================ FILE: codes/ruby/chapter_sorting/counting_sort.rb ================================================ =begin File: counting_sort.rb Created Time: 2024-05-02 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 计数排序 ### def counting_sort_naive(nums) # 简单实现,无法用于排序对象 # 1. 统计数组最大元素 m m = 0 nums.each { |num| m = [m, num].max } # 2. 统计各数字的出现次数 # counter[num] 代表 num 的出现次数 counter = Array.new(m + 1, 0) nums.each { |num| counter[num] += 1 } # 3. 遍历 counter ,将各元素填入原数组 nums i = 0 for num in 0...(m + 1) (0...counter[num]).each do nums[i] = num i += 1 end end end ### 计数排序 ### def counting_sort(nums) # 完整实现,可排序对象,并且是稳定排序 # 1. 统计数组最大元素 m m = nums.max # 2. 统计各数字的出现次数 # counter[num] 代表 num 的出现次数 counter = Array.new(m + 1, 0) nums.each { |num| counter[num] += 1 } # 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” # 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 (0...m).each { |i| counter[i + 1] += counter[i] } # 4. 倒序遍历 nums, 将各元素填入结果数组 res # 初始化数组 res 用于记录结果 n = nums.length res = Array.new(n, 0) (n - 1).downto(0).each do |i| num = nums[i] res[counter[num] - 1] = num # 将 num 放置到对应索引处 counter[num] -= 1 # 令前缀和自减 1 ,得到下次放置 num 的索引 end # 使用结果数组 res 覆盖原数组 nums (0...n).each { |i| nums[i] = res[i] } end ### Driver Code ### if __FILE__ == $0 nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort_naive(nums) puts "计数排序(无法排序对象)完成后 nums = #{nums}" nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort(nums1) puts "计数排序完成后 nums1 = #{nums1}" end ================================================ FILE: codes/ruby/chapter_sorting/heap_sort.rb ================================================ =begin File: heap_sort.rb Created Time: 2024-04-10 Author: junminhong (junminhong1110@gmail.com) =end ### 堆的长度为 n ,从节点 i 开始,从顶至底堆化 ### def sift_down(nums, n, i) while true # 判断节点 i, l, r 中值最大的节点,记为 ma l = 2 * i + 1 r = 2 * i + 2 ma = i ma = l if l < n && nums[l] > nums[ma] ma = r if r < n && nums[r] > nums[ma] # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 break if ma == i # 交换两节点 nums[i], nums[ma] = nums[ma], nums[i] # 循环向下堆化 i = ma end end ### 堆排序 ### def heap_sort(nums) # 建堆操作:堆化除叶节点以外的其他所有节点 (nums.length / 2 - 1).downto(0) do |i| sift_down(nums, nums.length, i) end # 从堆中提取最大元素,循环 n-1 轮 (nums.length - 1).downto(1) do |i| # 交换根节点与最右叶节点(交换首元素与尾元素) nums[0], nums[i] = nums[i], nums[0] # 以根节点为起点,从顶至底进行堆化 sift_down(nums, i, 0) end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] heap_sort(nums) puts "堆排序完成后 nums = #{nums.inspect}" end ================================================ FILE: codes/ruby/chapter_sorting/insertion_sort.rb ================================================ =begin File: insertion_sort.rb Created Time: 2024-04-02 Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 插入排序 ### def insertion_sort(nums) n = nums.length # 外循环:已排序区间为 [0, i-1] for i in 1...n base = nums[i] j = i - 1 # 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while j >= 0 && nums[j] > base nums[j + 1] = nums[j] # 将 nums[j] 向右移动一位 j -= 1 end nums[j + 1] = base # 将 base 赋值到正确位置 end end ### Driver Code ### nums = [4, 1, 3, 1, 5, 2] insertion_sort(nums) puts "插入排序完成后 nums = #{nums}" ================================================ FILE: codes/ruby/chapter_sorting/merge_sort.rb ================================================ =begin File: merge_sort.rb Created Time: 2024-04-10 Author: junminhong (junminhong1110@gmail.com) =end ### 合并左子数组和右子数组 ### def merge(nums, left, mid, right) # 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] # 创建一个临时数组 tmp,用于存放合并后的结果 tmp = Array.new(right - left + 1, 0) # 初始化左子数组和右子数组的起始索引 i, j, k = left, mid + 1, 0 # 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 while i <= mid && j <= right if nums[i] <= nums[j] tmp[k] = nums[i] i += 1 else tmp[k] = nums[j] j += 1 end k += 1 end # 将左子数组和右子数组的剩余元素复制到临时数组中 while i <= mid tmp[k] = nums[i] i += 1 k += 1 end while j <= right tmp[k] = nums[j] j += 1 k += 1 end # 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 (0...tmp.length).each do |k| nums[left + k] = tmp[k] end end ### 归并排序 ### def merge_sort(nums, left, right) # 终止条件 # 当子数组长度为 1 时终止递归 return if left >= right # 划分阶段 mid = left + (right - left) / 2 # 计算中点 merge_sort(nums, left, mid) # 递归左子数组 merge_sort(nums, mid + 1, right) # 递归右子数组 # 合并阶段 merge(nums, left, mid, right) end ### Driver Code ### if __FILE__ == $0 nums = [7, 3, 2, 6, 0, 1, 5, 4] merge_sort(nums, 0, nums.length - 1) puts "归并排序完成后 nums = #{nums.inspect}" end ================================================ FILE: codes/ruby/chapter_sorting/quick_sort.rb ================================================ =begin File: quick_sort.rb Created Time: 2024-04-01 Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 快速排序类 ### class QuickSort class << self ### 哨兵划分 ### def partition(nums, left, right) # 以 nums[left] 为基准数 i, j = left, right while i < j while i < j && nums[j] >= nums[left] j -= 1 # 从右向左找首个小于基准数的元素 end while i < j && nums[i] <= nums[left] i += 1 # 从左向右找首个大于基准数的元素 end # 元素交换 nums[i], nums[j] = nums[j], nums[i] end # 将基准数交换至两子数组的分界线 nums[i], nums[left] = nums[left], nums[i] i # 返回基准数的索引 end ### 快速排序类 ### def quick_sort(nums, left, right) # 子数组长度不为 1 时递归 if left < right # 哨兵划分 pivot = partition(nums, left, right) # 递归左子数组、右子数组 quick_sort(nums, left, pivot - 1) quick_sort(nums, pivot + 1, right) end nums end end end ### 快速排序类(中位数优化)### class QuickSortMedian class << self ### 选取三个候选元素的中位数 ### def median_three(nums, left, mid, right) # 选取三个候选元素的中位数 _l, _m, _r = nums[left], nums[mid], nums[right] # m 在 l 和 r 之间 return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l) # l 在 m 和 r 之间 return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m) return right end ### 哨兵划分(三数取中值)### def partition(nums, left, right) ### 以 nums[left] 为基准数 med = median_three(nums, left, (left + right) / 2, right) # 将中位数交换至数组最左断 nums[left], nums[med] = nums[med], nums[left] i, j = left, right while i < j while i < j && nums[j] >= nums[left] j -= 1 # 从右向左找首个小于基准数的元素 end while i < j && nums[i] <= nums[left] i += 1 # 从左向右找首个大于基准数的元素 end # 元素交换 nums[i], nums[j] = nums[j], nums[i] end # 将基准数交换至两子数组的分界线 nums[i], nums[left] = nums[left], nums[i] i # 返回基准数的索引 end ### 快速排序 ### def quick_sort(nums, left, right) # 子数组长度不为 1 时递归 if left < right # 哨兵划分 pivot = partition(nums, left, right) # 递归左子数组、右子数组 quick_sort(nums, left, pivot - 1) quick_sort(nums, pivot + 1, right) end nums end end end ### 快速排序类(递归深度优化)### class QuickSortTailCall class << self ### 哨兵划分 ### def partition(nums, left, right) # 以 nums[left]为基准数 i = left j = right while i < j while i < j && nums[j] >= nums[left] j -= 1 # 从右向左找首个小于基准数的元素 end while i < j && nums[i] <= nums[left] i += 1 # 从左向右找首个大于基准数的元素 end # 元素交换 nums[i], nums[j] = nums[j], nums[i] end # 将基准数交换至两子数组的分界线 nums[i], nums[left] = nums[left], nums[i] i # 返回基准数的索引 end ### 快速排序(递归深度优化)### def quick_sort(nums, left, right) # 子数组长度不为 1 时递归 while left < right # 哨兵划分 pivot = partition(nums, left, right) # 对两个子数组中较短的那个执行快速排序 if pivot - left < right - pivot quick_sort(nums, left, pivot - 1) left = pivot + 1 # 剩余未排序区间为 [pivot + 1, right] else quick_sort(nums, pivot + 1, right) right = pivot - 1 # 剩余未排序区间为 [left, pivot - 1] end end end end end ### Driver Code ### if __FILE__ == $0 # 快速排序 nums = [2, 4, 1, 0, 3, 5] QuickSort.quick_sort(nums, 0, nums.length - 1) puts "快速排序完成后 nums = #{nums}" # 快速排序(中位基准数优化) nums1 = [2, 4, 1, 0, 3, 5] QuickSortMedian.quick_sort(nums1, 0, nums1.length - 1) puts "快速排序(中位基准数优化)完成后 nums1 = #{nums1}" # 快速排序(递归深度优化) nums2 = [2, 4, 1, 0, 3, 5] QuickSortTailCall.quick_sort(nums2, 0, nums2.length - 1) puts "快速排序(递归深度优化)完成后 nums2 = #{nums2}" end ================================================ FILE: codes/ruby/chapter_sorting/radix_sort.rb ================================================ =begin File: radix_sort.rb Created Time: 2024-05-03 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 获取元素 num 的第 k 位,其中 exp = 10^(k-1) ### def digit(num, exp) # 转入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 (num / exp) % 10 end ### 计数排序(根据 nums 第 k 位排序)### def counting_sort_digit(nums, exp) # 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 counter = Array.new(10, 0) n = nums.length # 统计 0~9 各数字的出现次数 for i in 0...n d = digit(nums[i], exp) # 获取 nums[i] 第 k 位,记为 d counter[d] += 1 # 统计数字 d 的出现次数 end # 求前缀和,将“出现个数”转换为“数组索引” (1...10).each { |i| counter[i] += counter[i - 1] } # 倒序遍历,根据桶内统计结果,将各元素填入 res res = Array.new(n, 0) for i in (n - 1).downto(0) d = digit(nums[i], exp) j = counter[d] - 1 # 获取 d 在数组中的索引 j res[j] = nums[i] # 将当前元素填入索引 j counter[d] -= 1 # 将 d 的数量减 1 end # 使用结果覆盖原数组 nums (0...n).each { |i| nums[i] = res[i] } end ### 基数排序 ### def radix_sort(nums) # 获取数组的最大元素,用于判断最大位数 m = nums.max # 按照从低位到高位的顺序遍历 exp = 1 while exp <= m # 对数组元素的第 k 位执行计数排序 # k = 1 -> exp = 1 # k = 2 -> exp = 10 # 即 exp = 10^(k-1) counting_sort_digit(nums, exp) exp *= 10 end end ### Driver Code ### if __FILE__ == $0 # 基数排序 nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ] radix_sort(nums) puts "基数排序完成后 nums = #{nums}" end ================================================ FILE: codes/ruby/chapter_sorting/selection_sort.rb ================================================ =begin File: selection_sort.rb Created Time: 2024-05-03 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 选择排序 ### def selection_sort(nums) n = nums.length # 外循环:未排序区间为 [i, n-1] for i in 0...(n - 1) # 内循环:找到未排序区间内的最小元素 k = i for j in (i + 1)...n if nums[j] < nums[k] k = j # 记录最小元素的索引 end end # 将该最小元素与未排序区间的首个元素交换 nums[i], nums[k] = nums[k], nums[i] end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] selection_sort(nums) puts "选择排序完成后 nums = #{nums}" end ================================================ FILE: codes/ruby/chapter_stack_and_queue/array_deque.rb ================================================ =begin File: array_deque.rb Created Time: 2024-04-05 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 基于环形数组实现的双向队列 ### class ArrayDeque ### 获取双向队列的长度 ### attr_reader :size ### 构造方法 ### def initialize(capacity) @nums = Array.new(capacity, 0) @front = 0 @size = 0 end ### 获取双向队列的容量 ### def capacity @nums.length end ### 判断双向队列是否为空 ### def is_empty? size.zero? end ### 队首入队 ### def push_first(num) if size == capacity puts '双向队列已满' return end # 队首指针向左移动一位 # 通过取余操作实现 front 越过数组头部后回到尾部 @front = index(@front - 1) # 将 num 添加至队首 @nums[@front] = num @size += 1 end ### 队尾入队 ### def push_last(num) if size == capacity puts '双向队列已满' return end # 计算队尾指针,指向队尾索引 + 1 rear = index(@front + size) # 将 num 添加至队尾 @nums[rear] = num @size += 1 end ### 队首出队 ### def pop_first num = peek_first # 队首指针向后移动一位 @front = index(@front + 1) @size -= 1 num end ### 队尾出队 ### def pop_last num = peek_last @size -= 1 num end ### 访问队首元素 ### def peek_first raise IndexError, '双向队列为空' if is_empty? @nums[@front] end ### 访问队尾元素 ### def peek_last raise IndexError, '双向队列为空' if is_empty? # 计算尾元素索引 last = index(@front + size - 1) @nums[last] end ### 返回数组用于打印 ### def to_array # 仅转换有效长度范围内的列表元素 res = [] for i in 0...size res << @nums[index(@front + i)] end res end private ### 计算环形数组索引 ### def index(i) # 通过取余操作实现数组首尾相连 # 当 i 越过数组尾部后,回到头部 # 当 i 越过数组头部后,回到尾部 (i + capacity) % capacity end end ### Driver Code ### if __FILE__ == $0 # 初始化双向队列 deque = ArrayDeque.new(10) deque.push_last(3) deque.push_last(2) deque.push_last(5) puts "双向队列 deque = #{deque.to_array}" # 访问元素 peek_first = deque.peek_first puts "队首元素 peek_first = #{peek_first}" peek_last = deque.peek_last puts "队尾元素 peek_last = #{peek_last}" # 元素入队 deque.push_last(4) puts "元素 4 队尾入队后 deque = #{deque.to_array}" deque.push_first(1) puts "元素 1 队尾入队后 deque = #{deque.to_array}" # 元素出队 pop_last = deque.pop_last puts "队尾出队元素 = #{pop_last},队尾出队后 deque = #{deque.to_array}" pop_first = deque.pop_first puts "队尾出队元素 = #{pop_first},队尾出队后 deque = #{deque.to_array}" # 获取双向队列的长度 size = deque.size puts "双向队列长度 size = #{size}" # 判断双向队列是否为空 is_empty = deque.is_empty? puts "双向队列是否为空 = #{is_empty}" end ================================================ FILE: codes/ruby/chapter_stack_and_queue/array_queue.rb ================================================ =begin File: array_queue.rb Created Time: 2024-04-05 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 基于环形数组实现的队列 ### class ArrayQueue ### 获取队列的长度 ### attr_reader :size ### 构造方法 ### def initialize(size) @nums = Array.new(size, 0) # 用于存储队列元素的数组 @front = 0 # 队首指针,指向队首元素 @size = 0 # 队列长度 end ### 获取队列的容量 ### def capacity @nums.length end ### 判断队列是否为空 ### def is_empty? size.zero? end ### 入队 ### def push(num) raise IndexError, '队列已满' if size == capacity # 计算队尾指针,指向队尾索引 + 1 # 通过取余操作实现 rear 越过数组尾部后回到头部 rear = (@front + size) % capacity # 将 num 添加至队尾 @nums[rear] = num @size += 1 end ### 出队 ### def pop num = peek # 队首指针向后移动一位,若越过尾部,则返回到数组头部 @front = (@front + 1) % capacity @size -= 1 num end ### 访问队首元素 ### def peek raise IndexError, '队列为空' if is_empty? @nums[@front] end ### 返回列表用于打印 ### def to_array res = Array.new(size, 0) j = @front for i in 0...size res[i] = @nums[j % capacity] j += 1 end res end end ### Driver Code ### if __FILE__ == $0 # 初始化队列 queue = ArrayQueue.new(10) # 元素入队 queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "队列 queue = #{queue.to_array}" # 访问队首元素 peek = queue.peek puts "队首元素 peek = #{peek}" # 元素出队 pop = queue.pop puts "出队元素 pop = #{pop}" puts "出队后 queue = #{queue.to_array}" # 获取队列的长度 size = queue.size puts "队列长度 size = #{size}" # 判断队列是否为空 is_empty = queue.is_empty? puts "队列是否为空 = #{is_empty}" # 测试环形数组 for i in 0...10 queue.push(i) queue.pop puts "第 #{i} 轮入队 + 出队后 queue = #{queue.to_array}" end end ================================================ FILE: codes/ruby/chapter_stack_and_queue/array_stack.rb ================================================ =begin File: array_stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 基于数组实现的栈 ### class ArrayStack ### 构造方法 ### def initialize @stack = [] end ### 获取栈的长度 ### def size @stack.length end ### 判断栈是否为空 ### def is_empty? @stack.empty? end ### 入栈 ### def push(item) @stack << item end ### 出栈 ### def pop raise IndexError, '栈为空' if is_empty? @stack.pop end ### 访问栈顶元素 ### def peek raise IndexError, '栈为空' if is_empty? @stack.last end ### 返回列表用于打印 ### def to_array @stack end end ### Driver Code ### if __FILE__ == $0 # 初始化栈 stack = ArrayStack.new # 元素入栈 stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) puts "栈 stack = #{stack.to_array}" # 访问栈顶元素 peek = stack.peek puts "栈顶元素 peek = #{peek}" # 元素出栈 pop = stack.pop puts "出栈元素 pop = #{pop}" puts "出栈后 stack = #{stack.to_array}" # 获取栈的长度 size = stack.size puts "栈的长度 size = #{size}" # 判断是否为空 is_empty = stack.is_empty? puts "栈是否为空 = #{is_empty}" end ================================================ FILE: codes/ruby/chapter_stack_and_queue/deque.rb ================================================ =begin File: deque.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # 初始化双向队列 # Ruby 没有内直的双端队列,只能把 Array 当作双端队列来使用 deque = [] # 元素如队 deque << 2 deque << 5 deque << 4 # 请注意,由于是数组,Array#unshift 方法的时间复杂度为 O(n) deque.unshift(3) deque.unshift(1) puts "双向队列 deque = #{deque}" # 访问元素 peek_first = deque.first puts "队首元素 peek_first = #{peek_first}" peek_last = deque.last puts "队尾元素 peek_last = #{peek_last}" # 元素出队 # 请注意,由于是数组, Array#shift 方法的时间复杂度为 O(n) pop_front = deque.shift puts "队首出队元素 pop_front = #{pop_front},队首出队后 deque = #{deque}" pop_back = deque.pop puts "队尾出队元素 pop_back = #{pop_back}, 队尾出队后 deque = #{deque}" # 获取双向队列的长度 size = deque.length puts "双向队列长度 size = #{size}" # 判断双向队列是否为空 is_empty = size.zero? puts "双向队列是否为空 = #{is_empty}" end ================================================ FILE: codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb ================================================ =begin File: linkedlist_deque.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 双向链表节点 class ListNode attr_accessor :val attr_accessor :next # 后继节点引用 attr_accessor :prev # 前躯节点引用 ### 构造方法 ### def initialize(val) @val = val end end ### 基于双向链表实现的双向队列 ### class LinkedListDeque ### 获取双向队列的长度 ### attr_reader :size ### 构造方法 ### def initialize @front = nil # 头节点 front @rear = nil # 尾节点 rear @size = 0 # 双向队列的长度 end ### 判断双向队列是否为空 ### def is_empty? size.zero? end ### 入队操作 ### def push(num, is_front) node = ListNode.new(num) # 若链表为空, 则令 front 和 rear 都指向 node if is_empty? @front = @rear = node # 队首入队操作 elsif is_front # 将 node 添加至链表头部 @front.prev = node node.next = @front @front = node # 更新头节点 # 队尾入队操作 else # 将 node 添加至链表尾部 @rear.next = node node.prev = @rear @rear = node # 更新尾节点 end @size += 1 # 更新队列长度 end ### 队首入队 ### def push_first(num) push(num, true) end ### 队尾入队 ### def push_last(num) push(num, false) end ### 出队操作 ### def pop(is_front) raise IndexError, '双向队列为空' if is_empty? # 队首出队操作 if is_front val = @front.val # 暂存头节点值 # 删除头节点 fnext = @front.next unless fnext.nil? fnext.prev = nil @front.next = nil end @front = fnext # 更新头节点 # 队尾出队操作 else val = @rear.val # 暂存尾节点值 # 删除尾节点 rprev = @rear.prev unless rprev.nil? rprev.next = nil @rear.prev = nil end @rear = rprev # 更新尾节点 end @size -= 1 # 更新队列长度 val end ### 队首出队 ### def pop_first pop(true) end ### 队首出队 ### def pop_last pop(false) end ### 访问队首元素 ### def peek_first raise IndexError, '双向队列为空' if is_empty? @front.val end ### 访问队尾元素 ### def peek_last raise IndexError, '双向队列为空' if is_empty? @rear.val end ### 返回数组用于打印 ### def to_array node = @front res = Array.new(size, 0) for i in 0...size res[i] = node.val node = node.next end res end end ### Driver Code ### if __FILE__ == $0 # 初始化双向队列 deque = LinkedListDeque.new deque.push_last(3) deque.push_last(2) deque.push_last(5) puts "双向队列 deque = #{deque.to_array}" # 访问元素 peek_first = deque.peek_first puts "队首元素 peek_first = #{peek_first}" peek_last = deque.peek_last puts "队首元素 peek_last = #{peek_last}" # 元素入队 deque.push_last(4) puts "元素 4 队尾入队后 deque = #{deque.to_array}" deque.push_first(1) puts "元素 1 队首入队后 deque = #{deque.to_array}" # 元素出队 pop_last = deque.pop_last puts "队尾出队元素 = #{pop_last}, 队尾出队后 deque = #{deque.to_array}" pop_first = deque.pop_first puts "队首出队元素 = #{pop_first},队首出队后 deque = #{deque.to_array}" # 获取双向队列的长度 size = deque.size puts "双向队列长度 size = #{size}" # 判断双向队列是否为空 is_empty = deque.is_empty? puts "双向队列是否为空 = #{is_empty}" end ================================================ FILE: codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb ================================================ =begin File: linkedlist_queue.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### 基于链表头现的队列 ### class LinkedListQueue ### 获取队列的长度 ### attr_reader :size ### 构造方法 ### def initialize @front = nil # 头节点 front @rear = nil # 尾节点 rear @size = 0 end ### 判断队列是否为空 ### def is_empty? @front.nil? end ### 入队 ### def push(num) # 在尾节点后添加 num node = ListNode.new(num) # 如果队列为空,则令头,尾节点都指向该节点 if @front.nil? @front = node @rear = node # 如果队列不为空,则令该节点添加到尾节点后 else @rear.next = node @rear = node end @size += 1 end ### 出队 ### def pop num = peek # 删除头节点 @front = @front.next @size -= 1 num end ### 访问队首元素 ### def peek raise IndexError, '队列为空' if is_empty? @front.val end ### 将链表为 Array 并返回 ### def to_array queue = [] temp = @front while temp queue << temp.val temp = temp.next end queue end end ### Driver Code ### if __FILE__ == $0 # 初始化队列 queue = LinkedListQueue.new # 元素如队 queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "队列 queue = #{queue.to_array}" # 访问队首元素 peek = queue.peek puts "队首元素 front = #{peek}" # 元素出队 pop_front = queue.pop puts "出队元素 pop = #{pop_front}" puts "出队后 queue = #{queue.to_array}" # 获取队列的长度 size = queue.size puts "队列长度 size = #{size}" # 判断队列是否为空 is_empty = queue.is_empty? puts "队列是否为空 = #{is_empty}" end ================================================ FILE: codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb ================================================ =begin File: linkedlist_stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### 基于链表实现的栈 ### class LinkedListStack attr_reader :size ### 构造方法 ### def initialize @size = 0 end ### 判断栈是否为空 ### def is_empty? @peek.nil? end ### 入栈 ### def push(val) node = ListNode.new(val) node.next = @peek @peek = node @size += 1 end ### 出栈 ### def pop num = peek @peek = @peek.next @size -= 1 num end ### 访问栈顶元素 ### def peek raise IndexError, '栈为空' if is_empty? @peek.val end ### 将链表转化为 Array 并反回 ### def to_array arr = [] node = @peek while node arr << node.val node = node.next end arr.reverse end end ### Driver Code ### if __FILE__ == $0 # 初始化栈 stack = LinkedListStack.new # 元素入栈 stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) puts "栈 stack = #{stack.to_array}" # 访问栈顶元素 peek = stack.peek puts "栈顶元素 peek = #{peek}" # 元素出栈 pop = stack.pop puts "出栈元素 pop = #{pop}" puts "出栈后 stack = #{stack.to_array}" # 获取栈的长度 size = stack.size puts "栈的长度 size = #{size}" # 判断是否为空 is_empty = stack.is_empty? puts "栈是否为空 = #{is_empty}" end ================================================ FILE: codes/ruby/chapter_stack_and_queue/queue.rb ================================================ =begin File: queue.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # 初始化队列 # Ruby 内置的队列(Thread::Queue) 没有 peek 和遍历方法,可以把 Array 当作队列来使用 queue = [] # 元素入队 queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "队列 queue = #{queue}" # 访问队列元素 peek = queue.first puts "队首元素 peek = #{peek}" # 元素出队 # 清注意,由于是数组,Array#shift 方法时间复杂度为 O(n) pop = queue.shift puts "出队元素 pop = #{pop}" puts "出队后 queue = #{queue}" # 获取队列的长度 size = queue.length puts "队列长度 size = #{size}" # 判断队列是否为空 is_empty = queue.empty? puts "队列是否为空 = #{is_empty}" end ================================================ FILE: codes/ruby/chapter_stack_and_queue/stack.rb ================================================ =begin File: stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # 初始化栈 # Ruby 没有内置的栈类,可以把 Array 当作栈来使用 stack = [] # 元素入栈 stack << 1 stack << 3 stack << 2 stack << 5 stack << 4 puts "栈 stack = #{stack}" # 访问栈顶元素 peek = stack.last puts "栈顶元素 peek = #{peek}" # 元素出栈 pop = stack.pop puts "出栈元素 pop = #{pop}" puts "出栈后 stack = #{stack}" # 获取栈的长度 size = stack.length puts "栈的长度 size = #{size}" # 判断是否为空 is_empty = stack.empty? puts "栈是否为空 = #{is_empty}" end ================================================ FILE: codes/ruby/chapter_tree/array_binary_tree.rb ================================================ =begin File: array_binary_tree.rb Created Time: 2024-04-17 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 数组表示下的二叉树类 ### class ArrayBinaryTree ### 构造方法 ### def initialize(arr) @tree = arr.to_a end ### 列表容量 ### def size @tree.length end ### 获取索引为 i 节点的值 ### def val(i) # 若索引越界,则返回 nil ,代表空位 return if i < 0 || i >= size @tree[i] end ### 获取索引为 i 节点的左子节点的索引 ### def left(i) 2 * i + 1 end ### 获取索引为 i 节点的右子节点的索引 ### def right(i) 2 * i + 2 end ### 获取索引为 i 节点的父节点的索引 ### def parent(i) (i - 1) / 2 end ### 层序遍历 ### def level_order @res = [] # 直接遍历数组 for i in 0...size @res << val(i) unless val(i).nil? end @res end ### 深度优先遍历 ### def dfs(i, order) return if val(i).nil? # 前序遍历 @res << val(i) if order == :pre dfs(left(i), order) # 中序遍历 @res << val(i) if order == :in dfs(right(i), order) # 后序遍历 @res << val(i) if order == :post end ### 前序遍历 ### def pre_order @res = [] dfs(0, :pre) @res end ### 中序遍历 ### def in_order @res = [] dfs(0, :in) @res end ### 后序遍历 ### def post_order @res = [] dfs(0, :post) @res end end ### Driver Code ### if __FILE__ == $0 # 初始化二叉树 # 这里借助了一个从数组直接生成二叉树的函数 arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] root = arr_to_tree(arr) puts "\n初始化二叉树\n\n" puts '二叉树的数组表示:' pp arr puts '二叉树的链表表示:' print_tree(root) # 数组表示下的二叉树类 abt = ArrayBinaryTree.new(arr) # 访问节点 i = 1 l, r, _p = abt.left(i), abt.right(i), abt.parent(i) puts "\n当前节点的索引为 #{i} ,值为 #{abt.val(i).inspect}" puts "其左子节点的索引为 #{l} ,值为 #{abt.val(l).inspect}" puts "其右子节点的索引为 #{r} ,值为 #{abt.val(r).inspect}" puts "其父节点的索引为 #{_p} ,值为 #{abt.val(_p).inspect}" # 遍历树 res = abt.level_order puts "\n层序遍历为: #{res}" res = abt.pre_order puts "前序遍历为: #{res}" res = abt.in_order puts "中序遍历为: #{res}" res = abt.post_order puts "后序遍历为: #{res}" end ================================================ FILE: codes/ruby/chapter_tree/avl_tree.rb ================================================ =begin File: avl_tree.rb Created Time: 2024-04-17 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### AVL 树 ### class AVLTree ### 构造方法 ### def initialize @root = nil end ### 获取二叉树根节点 ### def get_root @root end ### 获取节点高度 ### def height(node) # 空节点高度为 -1 ,叶节点高度为 0 return node.height unless node.nil? -1 end ### 更新节点高度 ### def update_height(node) # 节点高度等于最高子树高度 + 1 node.height = [height(node.left), height(node.right)].max + 1 end ### 获取平衡因子 ### def balance_factor(node) # 空节点平衡因子为 0 return 0 if node.nil? # 节点平衡因子 = 左子树高度 - 右子树高度 height(node.left) - height(node.right) end ### 右旋操作 ### def right_rotate(node) child = node.left grand_child = child.right # 以 child 为原点,将 node 向右旋转 child.right = node node.left = grand_child # 更新节点高度 update_height(node) update_height(child) # 返回旋转后子树的根节点 child end ### 左旋操作 ### def left_rotate(node) child = node.right grand_child = child.left # 以 child 为原点,将 node 向左旋转 child.left = node node.right = grand_child # 更新节点高度 update_height(node) update_height(child) # 返回旋转后子树的根节点 child end ### 执行旋转操作,使该子树重新恢复平衡 ### def rotate(node) # 获取节点 node 的平衡因子 balance_factor = balance_factor(node) # 左遍树 if balance_factor > 1 if balance_factor(node.left) >= 0 # 右旋 return right_rotate(node) else # 先左旋后右旋 node.left = left_rotate(node.left) return right_rotate(node) end # 右遍树 elsif balance_factor < -1 if balance_factor(node.right) <= 0 # 左旋 return left_rotate(node) else # 先右旋后左旋 node.right = right_rotate(node.right) return left_rotate(node) end end # 平衡树,无须旋转,直接返回 node end ### 插入节点 ### def insert(val) @root = insert_helper(@root, val) end ### 递归插入节点(辅助方法)### def insert_helper(node, val) return TreeNode.new(val) if node.nil? # 1. 查找插入位置并插入节点 if val < node.val node.left = insert_helper(node.left, val) elsif val > node.val node.right = insert_helper(node.right, val) else # 重复节点不插入,直接返回 return node end # 更新节点高度 update_height(node) # 2. 执行旋转操作,使该子树重新恢复平衡 rotate(node) end ### 删除节点 ### def remove(val) @root = remove_helper(@root, val) end ### 递归删除节点(辅助方法)### def remove_helper(node, val) return if node.nil? # 1. 查找节点并删除 if val < node.val node.left = remove_helper(node.left, val) elsif val > node.val node.right = remove_helper(node.right, val) else if node.left.nil? || node.right.nil? child = node.left || node.right # 子节点数量 = 0 ,直接删除 node 并返回 return if child.nil? # 子节点数量 = 1 ,直接删除 node node = child else # 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 temp = node.right while !temp.left.nil? temp = temp.left end node.right = remove_helper(node.right, temp.val) node.val = temp.val end end # 更新节点高度 update_height(node) # 2. 执行旋转操作,使该子树重新恢复平衡 rotate(node) end ### 查找节点 ### def search(val) cur = @root # 循环查找,越过叶节点后跳出 while !cur.nil? # 目标节点在 cur 的右子树中 if cur.val < val cur = cur.right # 目标节点在 cur 的左子树中 elsif cur.val > val cur = cur.left # 找到目标节点,跳出循环 else break end end # 返回目标节点 cur end end ### Driver Code ### if __FILE__ == $0 def test_insert(tree, val) tree.insert(val) puts "\n插入节点 #{val} 后,AVL 树为" print_tree(tree.get_root) end def test_remove(tree, val) tree.remove(val) puts "\n删除节点 #{val} 后,AVL 树为" print_tree(tree.get_root) end # 初始化空 AVL 树 avl_tree = AVLTree.new # 插入节点 # 请关注插入节点后,AVL 树是如何保持平衡的 for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6] test_insert(avl_tree, val) end # 插入重复节点 test_insert(avl_tree, 7) # 删除节点 # 请关注删除节点后,AVL 树是如何保持平衡的 test_remove(avl_tree, 8) # 删除度为 0 的节点 test_remove(avl_tree, 5) # 删除度为 1 的节点 test_remove(avl_tree, 4) # 删除度为 2 的节点 result_node = avl_tree.search(7) puts "\n查找到的节点对象为 #{result_node},节点值 = #{result_node.val}" end ================================================ FILE: codes/ruby/chapter_tree/binary_search_tree.rb ================================================ =begin File: binary_search_tree.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 二叉搜索树 ### class BinarySearchTree ### 构造方法 ### def initialize # 初始化空树 @root = nil end ### 获取二叉树根节点 ### def get_root @root end ### 查找节点 ### def search(num) cur = @root # 循环查找,越过叶节点后跳出 while !cur.nil? # 目标节点在 cur 的右子树中 if cur.val < num cur = cur.right # 目标节点在 cur 的左子树中 elsif cur.val > num cur = cur.left # 找到目标节点,跳出循环 else break end end cur end ### 插入节点 ### def insert(num) # 若树为空,则初始化根节点 if @root.nil? @root = TreeNode.new(num) return end # 循环查找,越过叶节点后跳出 cur, pre = @root, nil while !cur.nil? # 找到重复节点,直接返回 return if cur.val == num pre = cur # 插入位置在 cur 的右子树中 if cur.val < num cur = cur.right # 插入位置在 cur 的左子树中 else cur = cur.left end end # 插入节点 node = TreeNode.new(num) if pre.val < num pre.right = node else pre.left = node end end ### 删除节点 ### def remove(num) # 若树为空,直接提前返回 return if @root.nil? # 循环查找,越过叶节点后跳出 cur, pre = @root, nil while !cur.nil? # 找到待删除节点,跳出循环 break if cur.val == num pre = cur # 待删除节点在 cur 的右子树中 if cur.val < num cur = cur.right # 待删除节点在 cur 的左子树中 else cur = cur.left end end # 若无待删除节点,则直接返回 return if cur.nil? # 子节点数量 = 0 or 1 if cur.left.nil? || cur.right.nil? # 当子节点数量 = 0 / 1 时, child = null / 该子节点 child = cur.left || cur.right # 删除节点 cur if cur != @root if pre.left == cur pre.left = child else pre.right = child end else # 若删除节点为根节点,则重新指定根节点 @root = child end # 子节点数量 = 2 else # 获取中序遍历中 cur 的下一个节点 tmp = cur.right while !tmp.left.nil? tmp = tmp.left end # 递归删除节点 tmp remove(tmp.val) # 用 tmp 覆盖 cur cur.val = tmp.val end end end ### Driver Code ### if __FILE__ == $0 # 初始化二叉搜索树 bst = BinarySearchTree.new nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] # 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 nums.each { |num| bst.insert(num) } puts "\n初始化的二叉树为\n" print_tree(bst.get_root) # 查找节点 node = bst.search(7) puts "\n查找到的节点对象为: #{node},节点值 = #{node.val}" # 插入节点 bst.insert(16) puts "\n插入节点 16 后,二叉树为\n" print_tree(bst.get_root) # 删除节点 bst.remove(1) puts "\n删除节点 1 后,二叉树为\n" print_tree(bst.get_root) bst.remove(2) puts "\n删除节点 2 后,二叉树为\n" print_tree(bst.get_root) bst.remove(4) puts "\n删除节点 4 后,二叉树为\n" print_tree(bst.get_root) end ================================================ FILE: codes/ruby/chapter_tree/binary_tree.rb ================================================ =begin File: binary_tree.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Driver Code ### if __FILE__ == $0 # 初始化二叉树 # 初始化节点 n1 = TreeNode.new(1) n2 = TreeNode.new(2) n3 = TreeNode.new(3) n4 = TreeNode.new(4) n5 = TreeNode.new(5) # 构建节点之间的引用(指针) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 puts "\n初始化二叉树\n\n" print_tree(n1) # 插入与删除节点 _p = TreeNode.new(0) # 在 n1 -> n2 中间插入节点 _p n1.left = _p _p.left = n2 puts "\n插入节点 _p 后\n\n" print_tree(n1) # 删除节点 n1.left = n2 puts "\n删除节点 _p 后\n\n" print_tree(n1) end ================================================ FILE: codes/ruby/chapter_tree/binary_tree_bfs.rb ================================================ =begin File: binary_tree_bfs.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 层序遍历 ### def level_order(root) # 初始化队列,加入根节点 queue = [root] # 初始化一个列表,用于保存遍历序列 res = [] while !queue.empty? node = queue.shift # 队列出队 res << node.val # 保存节点值 queue << node.left unless node.left.nil? # 左子节点入队 queue << node.right unless node.right.nil? # 右子节点入队 end res end ### Driver Code ### if __FILE__ == $0 # 初始化二叉树 # 这里借助了一个从数组直接生成二叉树的函数 root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) puts "\n初始化二叉树\n\n" print_tree(root) # 层序遍历 res = level_order(root) puts "\n层序遍历的节点打印序列 = #{res}" end ================================================ FILE: codes/ruby/chapter_tree/binary_tree_dfs.rb ================================================ =begin File: binary_tree_dfs.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 前序遍历 ### def pre_order(root) return if root.nil? # 访问优先级:根节点 -> 左子树 -> 右子树 $res << root.val pre_order(root.left) pre_order(root.right) end ### 中序遍历 ### def in_order(root) return if root.nil? # 访问优先级:左子树 -> 根节点 -> 右子树 in_order(root.left) $res << root.val in_order(root.right) end ### 后序遍历 ### def post_order(root) return if root.nil? # 访问优先级:左子树 -> 右子树 -> 根节点 post_order(root.left) post_order(root.right) $res << root.val end ### Driver Code ### if __FILE__ == $0 # 初始化二叉树 # 这里借助了一个从数组直接生成二叉树的函数 root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) puts "\n初始化二叉树\n\n" print_tree(root) # 前序遍历 $res = [] pre_order(root) puts "\n前序遍历的节点打印序列 = #{$res}" # 中序遍历 $res.clear in_order(root) puts "\nn中序遍历的节点打印序列 = #{$res}" # 后序遍历 $res.clear post_order(root) puts "\nn后序遍历的节点打印序列 = #{$res}" end ================================================ FILE: codes/ruby/test_all.rb ================================================ require 'open3' start_time = Time.now ruby_code_dir = File.dirname(__FILE__) files = Dir.glob("#{ruby_code_dir}/chapter_*/*.rb") errors = [] files.each do |file| stdout, stderr, status = Open3.capture3("ruby #{file}") errors << stderr unless status.success? end puts "\x1b[34mTested #{files.count} files\x1b[m" unless errors.empty? puts "\x1b[33mFound exception in #{errors.length} files\x1b[m" raise errors.join("\n\n") else puts "\x1b[32mPASS\x1b[m" end puts "Testing finishes after #{((Time.now - start_time) * 1000).round} ms" ================================================ FILE: codes/ruby/utils/list_node.rb ================================================ =begin File: list_node.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 链表节点类 ### class ListNode attr_accessor :val # 节点值 attr_accessor :next # 指向下一节点的引用 def initialize(val=0, next_node=nil) @val = val @next = next_node end end ### 将列表反序列化为链表 ### def arr_to_linked_list(arr) head = current = ListNode.new(arr[0]) for i in 1...arr.length current.next = ListNode.new(arr[i]) current = current.next end head end ### 将链表序列化为列表 ### def linked_list_to_arr(head) arr = [] while head arr << head.val head = head.next end end ================================================ FILE: codes/ruby/utils/print_util.rb ================================================ =begin File: print_util.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative "./tree_node" ### 打印矩阵 ### def print_matrix(mat) s = [] mat.each { |arr| s << " #{arr.to_s}" } puts "[\n#{s.join(",\n")}\n]" end ### 打印链表 ### def print_linked_list(head) list = [] while head list << head.val head = head.next end puts "#{list.join(" -> ")}" end class Trunk attr_accessor :prev, :str def initialize(prev, str) @prev = prev @str = str end end def show_trunk(p) return if p.nil? show_trunk(p.prev) print p.str end ### 打印二叉树 ### # This tree printer is borrowed from TECHIE DELIGHT # https://www.techiedelight.com/c-program-print-binary-tree/ def print_tree(root, prev=nil, is_right=false) return if root.nil? prev_str = " " trunk = Trunk.new(prev, prev_str) print_tree(root.right, trunk, true) if prev.nil? trunk.str = "———" elsif is_right trunk.str = "/———" prev_str = " |" else trunk.str = "\\———" prev.str = prev_str end show_trunk(trunk) puts " #{root.val}" prev.str = prev_str if prev trunk.str = " |" print_tree(root.left, trunk, false) end ### 打印哈希表 ### def print_hash_map(hmap) hmap.entries.each { |key, value| puts "#{key} -> #{value}" } end ### 打印堆 ### def print_heap(heap) puts "堆的数组表示:#{heap}" puts "堆的树状表示:" root = arr_to_tree(heap) print_tree(root) end ================================================ FILE: codes/ruby/utils/tree_node.rb ================================================ =begin File: tree_node.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 二叉树节点类 ### class TreeNode attr_accessor :val # 节点值 attr_accessor :height # 节点高度 attr_accessor :left # 左子节点引用 attr_accessor :right # 右子节点引用 def initialize(val=0) @val = val @height = 0 end end ### 将列表反序列化为二叉数树:递归 ### def arr_to_tree_dfs(arr, i) # 如果索引超出数组长度,或者对应的元素为 nil ,则返回 nil return if i < 0 || i >= arr.length || arr[i].nil? # 构建当前节点 root = TreeNode.new(arr[i]) # 递归构建左右子树 root.left = arr_to_tree_dfs(arr, 2 * i + 1) root.right = arr_to_tree_dfs(arr, 2 * i + 2) root end ### 将列表反序列化为二叉树 ### def arr_to_tree(arr) arr_to_tree_dfs(arr, 0) end ### 将二叉树序列化为列表:递归 ### def tree_to_arr_dfs(root, i, res) return if root.nil? res += Array.new(i - res.length + 1) if i >= res.length res[i] = root.val tree_to_arr_dfs(root.left, 2 * i + 1, res) tree_to_arr_dfs(root.right, 2 * i + 2, res) end ### 将二叉树序列化为列表 ### def tree_to_arr(root) res = [] tree_to_arr_dfs(root, 0, res) res end ================================================ FILE: codes/ruby/utils/vertex.rb ================================================ =begin File: vertex.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 顶点类 ### class Vertex attr_accessor :val def initialize(val) @val = val end end ### 输入值列表 vals ,返回顶点列表 vets ### def vals_to_vets(vals) Array.new(vals.length) { |i| Vertex.new(vals[i]) } end ### 输入顶点列表 vets, 返回值列表 vals ### def vets_to_vals(vets) Array.new(vets.length) { |i| vets[i].val } end ================================================ FILE: codes/rust/.gitignore ================================================ target/ Cargo.lock ================================================ FILE: codes/rust/Cargo.toml ================================================ [package] name = "hello-algo-rust" version = "0.1.0" edition = "2021" publish = false # Run Command: cargo run --bin time_complexity [[bin]] name = "time_complexity" path = "chapter_computational_complexity/time_complexity.rs" # Run Command: cargo run --bin worst_best_time_complexity [[bin]] name = "worst_best_time_complexity" path = "chapter_computational_complexity/worst_best_time_complexity.rs" # Run Command: cargo run --bin space_complexity [[bin]] name = "space_complexity" path = "chapter_computational_complexity/space_complexity.rs" # Run Command: cargo run --bin iteration [[bin]] name = "iteration" path = "chapter_computational_complexity/iteration.rs" # Run Command: cargo run --bin recursion [[bin]] name = "recursion" path = "chapter_computational_complexity/recursion.rs" # Run Command: cargo run --bin two_sum [[bin]] name = "two_sum" path = "chapter_searching/two_sum.rs" # Run Command: cargo run --bin array [[bin]] name = "array" path = "chapter_array_and_linkedlist/array.rs" # Run Command: cargo run --bin linked_list [[bin]] name = "linked_list" path = "chapter_array_and_linkedlist/linked_list.rs" # Run Command: cargo run --bin list [[bin]] name = "list" path = "chapter_array_and_linkedlist/list.rs" # Run Command: cargo run --bin my_list [[bin]] name = "my_list" path = "chapter_array_and_linkedlist/my_list.rs" # Run Command: cargo run --bin stack [[bin]] name = "stack" path = "chapter_stack_and_queue/stack.rs" # Run Command: cargo run --bin linkedlist_stack [[bin]] name = "linkedlist_stack" path = "chapter_stack_and_queue/linkedlist_stack.rs" # Run Command: cargo run --bin queue [[bin]] name = "queue" path = "chapter_stack_and_queue/queue.rs" # Run Command: cargo run --bin linkedlist_queue [[bin]] name = "linkedlist_queue" path = "chapter_stack_and_queue/linkedlist_queue.rs" # Run Command: cargo run --bin deque [[bin]] name = "deque" path = "chapter_stack_and_queue/deque.rs" # Run Command: cargo run --bin array_deque [[bin]] name = "array_deque" path = "chapter_stack_and_queue/array_deque.rs" # Run Command: cargo run --bin linkedlist_deque [[bin]] name = "linkedlist_deque" path = "chapter_stack_and_queue/linkedlist_deque.rs" # Run Command: cargo run --bin simple_hash [[bin]] name = "simple_hash" path = "chapter_hashing/simple_hash.rs" # Run Command: cargo run --bin hash_map [[bin]] name = "hash_map" path = "chapter_hashing/hash_map.rs" # Run Command: cargo run --bin array_hash_map [[bin]] name = "array_hash_map" path = "chapter_hashing/array_hash_map.rs" # Run Command: cargo run --bin build_in_hash [[bin]] name = "build_in_hash" path = "chapter_hashing/build_in_hash.rs" # Run Command: cargo run --bin hash_map_chaining [[bin]] name = "hash_map_chaining" path = "chapter_hashing/hash_map_chaining.rs" # Run Command: cargo run --bin hash_map_open_addressing [[bin]] name = "hash_map_open_addressing" path = "chapter_hashing/hash_map_open_addressing.rs" # Run Command: cargo run --bin binary_search [[bin]] name = "binary_search" path = "chapter_searching/binary_search.rs" # Run Command: cargo run --bin binary_search_edge [[bin]] name = "binary_search_edge" path = "chapter_searching/binary_search_edge.rs" # Run Command: cargo run --bin binary_search_insertion [[bin]] name = "binary_search_insertion" path = "chapter_searching/binary_search_insertion.rs" # Run Command: cargo run --bin bubble_sort [[bin]] name = "bubble_sort" path = "chapter_sorting/bubble_sort.rs" # Run Command: cargo run --bin insertion_sort [[bin]] name = "insertion_sort" path = "chapter_sorting/insertion_sort.rs" # Run Command: cargo run --bin quick_sort [[bin]] name = "quick_sort" path = "chapter_sorting/quick_sort.rs" # Run Command: cargo run --bin merge_sort [[bin]] name = "merge_sort" path = "chapter_sorting/merge_sort.rs" # Run Command: cargo run --bin selection_sort [[bin]] name = "selection_sort" path = "chapter_sorting/selection_sort.rs" # Run Command: cargo run --bin bucket_sort [[bin]] name = "bucket_sort" path = "chapter_sorting/bucket_sort.rs" # Run Command: cargo run --bin heap_sort [[bin]] name = "heap_sort" path = "chapter_sorting/heap_sort.rs" # Run Command: cargo run --bin counting_sort [[bin]] name = "counting_sort" path = "chapter_sorting/counting_sort.rs" # Run Command: cargo run --bin radix_sort [[bin]] name = "radix_sort" path = "chapter_sorting/radix_sort.rs" # Run Command: cargo run --bin array_stack [[bin]] name = "array_stack" path = "chapter_stack_and_queue/array_stack.rs" # Run Command: cargo run --bin array_queue [[bin]] name = "array_queue" path = "chapter_stack_and_queue/array_queue.rs" # Run Command: cargo run --bin array_binary_tree [[bin]] name = "array_binary_tree" path = "chapter_tree/array_binary_tree.rs" # Run Command: cargo run --bin avl_tree [[bin]] name = "avl_tree" path = "chapter_tree/avl_tree.rs" # Run Command: cargo run --bin binary_search_tree [[bin]] name = "binary_search_tree" path = "chapter_tree/binary_search_tree.rs" # Run Command: cargo run --bin binary_tree_bfs [[bin]] name = "binary_tree_bfs" path = "chapter_tree/binary_tree_bfs.rs" # Run Command: cargo run --bin binary_tree_dfs [[bin]] name = "binary_tree_dfs" path = "chapter_tree/binary_tree_dfs.rs" # Run Command: cargo run --bin binary_tree [[bin]] name = "binary_tree" path = "chapter_tree/binary_tree.rs" # Run Command: cargo run --bin heap [[bin]] name = "heap" path = "chapter_heap/heap.rs" # Run Command: cargo run --bin my_heap [[bin]] name = "my_heap" path = "chapter_heap/my_heap.rs" # Run Command: cargo run --bin top_k [[bin]] name = "top_k" path = "chapter_heap/top_k.rs" # Run Command: cargo run --bin graph_adjacency_list [[bin]] name = "graph_adjacency_list" path = "chapter_graph/graph_adjacency_list.rs" # Run Command: cargo run --bin graph_adjacency_matrix [[bin]] name = "graph_adjacency_matrix" path = "chapter_graph/graph_adjacency_matrix.rs" # Run Command: cargo run --bin graph_bfs [[bin]] name = "graph_bfs" path = "chapter_graph/graph_bfs.rs" # Run Command: cargo run --bin graph_dfs [[bin]] name = "graph_dfs" path = "chapter_graph/graph_dfs.rs" # Run Command: cargo run --bin linear_search [[bin]] name = "linear_search" path = "chapter_searching/linear_search.rs" # Run Command: cargo run --bin hashing_search [[bin]] name = "hashing_search" path = "chapter_searching/hashing_search.rs" # Run Command: cargo run --bin climbing_stairs_dfs [[bin]] name = "climbing_stairs_dfs" path = "chapter_dynamic_programming/climbing_stairs_dfs.rs" # Run Command: cargo run --bin climbing_stairs_dfs_mem [[bin]] name = "climbing_stairs_dfs_mem" path = "chapter_dynamic_programming/climbing_stairs_dfs_mem.rs" # Run Command: cargo run --bin climbing_stairs_dp [[bin]] name = "climbing_stairs_dp" path = "chapter_dynamic_programming/climbing_stairs_dp.rs" # Run Command: cargo run --bin min_cost_climbing_stairs_dp [[bin]] name = "min_cost_climbing_stairs_dp" path = "chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs" # Run Command: cargo run --bin climbing_stairs_constraint_dp [[bin]] name = "climbing_stairs_constraint_dp" path = "chapter_dynamic_programming/climbing_stairs_constraint_dp.rs" # Run Command: cargo run --bin climbing_stairs_backtrack [[bin]] name = "climbing_stairs_backtrack" path = "chapter_dynamic_programming/climbing_stairs_backtrack.rs" # Run Command: cargo run --bin subset_sum_i_naive [[bin]] name = "subset_sum_i_naive" path = "chapter_backtracking/subset_sum_i_naive.rs" # Run Command: cargo run --bin subset_sum_i [[bin]] name = "subset_sum_i" path = "chapter_backtracking/subset_sum_i.rs" # Run Command: cargo run --bin subset_sum_ii [[bin]] name = "subset_sum_ii" path = "chapter_backtracking/subset_sum_ii.rs" # Run Command: cargo run --bin coin_change [[bin]] name = "coin_change" path = "chapter_dynamic_programming/coin_change.rs" # Run Command: cargo run --bin coin_change_ii [[bin]] name = "coin_change_ii" path = "chapter_dynamic_programming/coin_change_ii.rs" # Run Command: cargo run --bin unbounded_knapsack [[bin]] name = "unbounded_knapsack" path = "chapter_dynamic_programming/unbounded_knapsack.rs" # Run Command: cargo run --bin knapsack [[bin]] name = "knapsack" path = "chapter_dynamic_programming/knapsack.rs" # Run Command: cargo run --bin min_path_sum [[bin]] name = "min_path_sum" path = "chapter_dynamic_programming/min_path_sum.rs" # Run Command: cargo run --bin edit_distance [[bin]] name = "edit_distance" path = "chapter_dynamic_programming/edit_distance.rs" # Run Command: cargo run --bin n_queens [[bin]] name = "n_queens" path = "chapter_backtracking/n_queens.rs" # Run Command: cargo run --bin permutations_i [[bin]] name = "permutations_i" path = "chapter_backtracking/permutations_i.rs" # Run Command: cargo run --bin permutations_ii [[bin]] name = "permutations_ii" path = "chapter_backtracking/permutations_ii.rs" # Run Command: cargo run --bin preorder_traversal_i_compact [[bin]] name = "preorder_traversal_i_compact" path = "chapter_backtracking/preorder_traversal_i_compact.rs" # Run Command: cargo run --bin preorder_traversal_ii_compact [[bin]] name = "preorder_traversal_ii_compact" path = "chapter_backtracking/preorder_traversal_ii_compact.rs" # Run Command: cargo run --bin preorder_traversal_iii_compact [[bin]] name = "preorder_traversal_iii_compact" path = "chapter_backtracking/preorder_traversal_iii_compact.rs" # Run Command: cargo run --bin preorder_traversal_iii_template [[bin]] name = "preorder_traversal_iii_template" path = "chapter_backtracking/preorder_traversal_iii_template.rs" # Run Command: cargo run --bin binary_search_recur [[bin]] name = "binary_search_recur" path = "chapter_divide_and_conquer/binary_search_recur.rs" # Run Command: cargo run --bin hanota [[bin]] name = "hanota" path = "chapter_divide_and_conquer/hanota.rs" # Run Command: cargo run --bin build_tree [[bin]] name = "build_tree" path = "chapter_divide_and_conquer/build_tree.rs" # Run Command: cargo run --bin coin_change_greedy [[bin]] name = "coin_change_greedy" path = "chapter_greedy/coin_change_greedy.rs" # Run Command: cargo run --bin fractional_knapsack [[bin]] name = "fractional_knapsack" path = "chapter_greedy/fractional_knapsack.rs" # Run Command: cargo run --bin max_capacity [[bin]] name = "max_capacity" path = "chapter_greedy/max_capacity.rs" # Run Command: cargo run --bin max_product_cutting [[bin]] name = "max_product_cutting" path = "chapter_greedy/max_product_cutting.rs" [dependencies] rand = "0.8.5" ================================================ FILE: codes/rust/chapter_array_and_linkedlist/array.rs ================================================ /* * File: array.rs * Created Time: 2023-01-15 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use rand::Rng; /* 随机访问元素 */ fn random_access(nums: &[i32]) -> i32 { // 在区间 [0, nums.len()) 中随机抽取一个数字 let random_index = rand::thread_rng().gen_range(0..nums.len()); // 获取并返回随机元素 let random_num = nums[random_index]; random_num } /* 扩展数组长度 */ fn extend(nums: &[i32], enlarge: usize) -> Vec { // 初始化一个扩展长度后的数组 let mut res: Vec = vec![0; nums.len() + enlarge]; // 将原数组中的所有元素复制到新 res[0..nums.len()].copy_from_slice(nums); // 返回扩展后的新数组 res } /* 在数组的索引 index 处插入元素 num */ fn insert(nums: &mut [i32], num: i32, index: usize) { // 把索引 index 以及之后的所有元素向后移动一位 for i in (index + 1..nums.len()).rev() { nums[i] = nums[i - 1]; } // 将 num 赋给 index 处的元素 nums[index] = num; } /* 删除索引 index 处的元素 */ fn remove(nums: &mut [i32], index: usize) { // 把索引 index 之后的所有元素向前移动一位 for i in index..nums.len() - 1 { nums[i] = nums[i + 1]; } } /* 遍历数组 */ fn traverse(nums: &[i32]) { let mut _count = 0; // 通过索引遍历数组 for i in 0..nums.len() { _count += nums[i]; } // 直接遍历数组元素 _count = 0; for &num in nums { _count += num; } } /* 在数组中查找指定元素 */ fn find(nums: &[i32], target: i32) -> Option { for i in 0..nums.len() { if nums[i] == target { return Some(i); } } None } /* Driver Code */ fn main() { /* 初始化数组 */ let arr: [i32; 5] = [0; 5]; print!("数组 arr = "); print_util::print_array(&arr); // 在 Rust 中,指定长度时([i32; 5])为数组,不指定长度时(&[i32])为切片 // 由于 Rust 的数组被设计为在编译期确定长度,因此只能使用常量来指定长度 // Vector 是 Rust 一般情况下用作动态数组的类型 // 为了方便实现扩容 extend() 方法,以下将 vector 看作数组(array) let nums: Vec = vec![1, 3, 2, 5, 4]; print!("\n数组 nums = "); print_util::print_array(&nums); // 随机访问 let random_num = random_access(&nums); println!("\n在 nums 中获取随机元素 {}", random_num); // 长度扩展 let mut nums: Vec = extend(&nums, 3); print!("将数组长度扩展至 8 ,得到 nums = "); print_util::print_array(&nums); // 插入元素 insert(&mut nums, 6, 3); print!("\n在索引 3 处插入数字 6 ,得到 nums = "); print_util::print_array(&nums); // 删除元素 remove(&mut nums, 2); print!("\n删除索引 2 处的元素,得到 nums = "); print_util::print_array(&nums); // 遍历数组 traverse(&nums); // 查找元素 let index = find(&nums, 3).unwrap(); println!("\n在 nums 中查找元素 3 ,得到索引 = {}", index); } ================================================ FILE: codes/rust/chapter_array_and_linkedlist/linked_list.rs ================================================ /* * File: linked_list.rs * Created Time: 2023-03-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* 在链表的节点 n0 之后插入节点 P */ #[allow(non_snake_case)] pub fn insert(n0: &Rc>>, P: Rc>>) { let n1 = n0.borrow_mut().next.take(); P.borrow_mut().next = n1; n0.borrow_mut().next = Some(P); } /* 删除链表的节点 n0 之后的首个节点 */ #[allow(non_snake_case)] pub fn remove(n0: &Rc>>) { // n0 -> P -> n1 let P = n0.borrow_mut().next.take(); if let Some(node) = P { let n1 = node.borrow_mut().next.take(); n0.borrow_mut().next = n1; } } /* 访问链表中索引为 index 的节点 */ pub fn access(head: Rc>>, index: i32) -> Option>>> { fn dfs( head: Option<&Rc>>>, index: i32, ) -> Option>>> { if index <= 0 { return head.cloned(); } if let Some(node) = head { dfs(node.borrow().next.as_ref(), index - 1) } else { None } } dfs(Some(head).as_ref(), index) } /* 在链表中查找值为 target 的首个节点 */ pub fn find(head: Rc>>, target: T) -> i32 { fn find(head: Option<&Rc>>>, target: T, idx: i32) -> i32 { if let Some(node) = head { if node.borrow().val == target { return idx; } return find(node.borrow().next.as_ref(), target, idx + 1); } else { -1 } } find(Some(head).as_ref(), target, 0) } /* Driver Code */ fn main() { /* 初始化链表 */ // 初始化各个节点 let n0 = ListNode::new(1); let n1 = ListNode::new(3); let n2 = ListNode::new(2); let n3 = ListNode::new(5); let n4 = ListNode::new(4); // 构建节点之间的引用 n0.borrow_mut().next = Some(n1.clone()); n1.borrow_mut().next = Some(n2.clone()); n2.borrow_mut().next = Some(n3.clone()); n3.borrow_mut().next = Some(n4.clone()); print!("初始化的链表为 "); print_util::print_linked_list(&n0); /* 插入节点 */ insert(&n0, ListNode::new(0)); print!("插入节点后的链表为 "); print_util::print_linked_list(&n0); /* 删除节点 */ remove(&n0); print!("删除节点后的链表为 "); print_util::print_linked_list(&n0); /* 访问节点 */ let node = access(n0.clone(), 3); println!("链表中索引 3 处的节点的值 = {}", node.unwrap().borrow().val); /* 查找节点 */ let index = find(n0.clone(), 2); println!("链表中值为 2 的节点的索引 = {}", index); } ================================================ FILE: codes/rust/chapter_array_and_linkedlist/list.rs ================================================ /* * File: list.rs * Created Time: 2023-01-18 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Driver Code */ fn main() { // 初始化列表 let mut nums: Vec = vec![1, 3, 2, 5, 4]; print!("列表 nums = "); print_util::print_array(&nums); // 访问元素 let num = nums[1]; println!("\n访问索引 1 处的元素,得到 num = {num}"); // 更新元素 nums[1] = 0; print!("将索引 1 处的元素更新为 0 ,得到 nums = "); print_util::print_array(&nums); // 清空列表 nums.clear(); print!("\n清空列表后 nums = "); print_util::print_array(&nums); // 在尾部添加元素 nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); print!("\n添加元素后 nums = "); print_util::print_array(&nums); // 在中间插入元素 nums.insert(3, 6); print!("\n在索引 3 处插入数字 6 ,得到 nums = "); print_util::print_array(&nums); // 删除元素 nums.remove(3); print!("\n删除索引 3 处的元素,得到 nums = "); print_util::print_array(&nums); // 通过索引遍历列表 let mut _count = 0; for i in 0..nums.len() { _count += nums[i]; } // 直接遍历列表元素 _count = 0; for x in &nums { _count += x; } // 拼接两个列表 let mut nums1 = vec![6, 8, 7, 10, 9]; nums.append(&mut nums1); // append(移动) 之后 nums1 为空! // nums.extend(&nums1); // extend(借用) nums1 能继续使用 print!("\n将列表 nums1 拼接到 nums 之后,得到 nums = "); print_util::print_array(&nums); // 排序列表 nums.sort(); print!("\n排序列表后 nums = "); print_util::print_array(&nums); } ================================================ FILE: codes/rust/chapter_array_and_linkedlist/my_list.rs ================================================ /* * File: my_list.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* 列表类 */ #[allow(dead_code)] struct MyList { arr: Vec, // 数组(存储列表元素) capacity: usize, // 列表容量 size: usize, // 列表长度(当前元素数量) extend_ratio: usize, // 每次列表扩容的倍数 } #[allow(unused, unused_comparisons)] impl MyList { /* 构造方法 */ pub fn new(capacity: usize) -> Self { let mut vec = vec![0; capacity]; Self { arr: vec, capacity, size: 0, extend_ratio: 2, } } /* 获取列表长度(当前元素数量)*/ pub fn size(&self) -> usize { return self.size; } /* 获取列表容量 */ pub fn capacity(&self) -> usize { return self.capacity; } /* 访问元素 */ pub fn get(&self, index: usize) -> i32 { // 索引如果越界,则抛出异常,下同 if index >= self.size { panic!("索引越界") }; return self.arr[index]; } /* 更新元素 */ pub fn set(&mut self, index: usize, num: i32) { if index >= self.size { panic!("索引越界") }; self.arr[index] = num; } /* 在尾部添加元素 */ pub fn add(&mut self, num: i32) { // 元素数量超出容量时,触发扩容机制 if self.size == self.capacity() { self.extend_capacity(); } self.arr[self.size] = num; // 更新元素数量 self.size += 1; } /* 在中间插入元素 */ pub fn insert(&mut self, index: usize, num: i32) { if index >= self.size() { panic!("索引越界") }; // 元素数量超出容量时,触发扩容机制 if self.size == self.capacity() { self.extend_capacity(); } // 将索引 index 以及之后的元素都向后移动一位 for j in (index..self.size).rev() { self.arr[j + 1] = self.arr[j]; } self.arr[index] = num; // 更新元素数量 self.size += 1; } /* 删除元素 */ pub fn remove(&mut self, index: usize) -> i32 { if index >= self.size() { panic!("索引越界") }; let num = self.arr[index]; // 将索引 index 之后的元素都向前移动一位 for j in index..self.size - 1 { self.arr[j] = self.arr[j + 1]; } // 更新元素数量 self.size -= 1; // 返回被删除的元素 return num; } /* 列表扩容 */ pub fn extend_capacity(&mut self) { // 新建一个长度为原数组 extend_ratio 倍的新数组,并将原数组复制到新数组 let new_capacity = self.capacity * self.extend_ratio; self.arr.resize(new_capacity, 0); // 更新列表容量 self.capacity = new_capacity; } /* 将列表转换为数组 */ pub fn to_array(&self) -> Vec { // 仅转换有效长度范围内的列表元素 let mut arr = Vec::new(); for i in 0..self.size { arr.push(self.get(i)); } arr } } /* Driver Code */ fn main() { /* 初始化列表 */ let mut nums = MyList::new(10); /* 在尾部添加元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print!("列表 nums = "); print_util::print_array(&nums.to_array()); print!(" ,容量 = {} ,长度 = {}", nums.capacity(), nums.size()); /* 在中间插入元素 */ nums.insert(3, 6); print!("\n在索引 3 处插入数字 6 ,得到 nums = "); print_util::print_array(&nums.to_array()); /* 删除元素 */ nums.remove(3); print!("\n删除索引 3 处的元素,得到 nums = "); print_util::print_array(&nums.to_array()); /* 访问元素 */ let num = nums.get(1); println!("\n访问索引 1 处的元素,得到 num = {num}"); /* 更新元素 */ nums.set(1, 0); print!("将索引 1 处的元素更新为 0 ,得到 nums = "); print_util::print_array(&nums.to_array()); /* 测试扩容机制 */ for i in 0..10 { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 nums.add(i); } print!("\n扩容后的列表 nums = "); print_util::print_array(&nums.to_array()); print!(" ,容量 = {} ,长度 = {}", nums.capacity(), nums.size()); } ================================================ FILE: codes/rust/chapter_backtracking/n_queens.rs ================================================ /* * File: n_queens.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* 回溯算法:n 皇后 */ fn backtrack( row: usize, n: usize, state: &mut Vec>, res: &mut Vec>>, cols: &mut [bool], diags1: &mut [bool], diags2: &mut [bool], ) { // 当放置完所有行时,记录解 if row == n { res.push(state.clone()); return; } // 遍历所有列 for col in 0..n { // 计算该格子对应的主对角线和次对角线 let diag1 = row + n - 1 - col; let diag2 = row + col; // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 if !cols[col] && !diags1[diag1] && !diags2[diag2] { // 尝试:将皇后放置在该格子 state[row][col] = "Q".into(); (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true); // 放置下一行 backtrack(row + 1, n, state, res, cols, diags1, diags2); // 回退:将该格子恢复为空位 state[row][col] = "#".into(); (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false); } } } /* 求解 n 皇后 */ fn n_queens(n: usize) -> Vec>> { // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 let mut state: Vec> = vec![vec!["#".to_string(); n]; n]; let mut cols = vec![false; n]; // 记录列是否有皇后 let mut diags1 = vec![false; 2 * n - 1]; // 记录主对角线上是否有皇后 let mut diags2 = vec![false; 2 * n - 1]; // 记录次对角线上是否有皇后 let mut res: Vec>> = Vec::new(); backtrack( 0, n, &mut state, &mut res, &mut cols, &mut diags1, &mut diags2, ); res } /* Driver Code */ pub fn main() { let n: usize = 4; let res = n_queens(n); println!("输入棋盘长宽为 {n}"); println!("皇后放置方案共有 {} 种", res.len()); for state in res.iter() { println!("--------------------"); for row in state.iter() { println!("{:?}", row); } } } ================================================ FILE: codes/rust/chapter_backtracking/permutations_i.rs ================================================ /* * File: permutations_i.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* 回溯算法:全排列 I */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // 当状态长度等于元素数量时,记录解 if state.len() == choices.len() { res.push(state); return; } // 遍历所有选择 for i in 0..choices.len() { let choice = choices[i]; // 剪枝:不允许重复选择元素 if !selected[i] { // 尝试:做出选择,更新状态 selected[i] = true; state.push(choice); // 进行下一轮选择 backtrack(state.clone(), choices, selected, res); // 回退:撤销选择,恢复到之前的状态 selected[i] = false; state.pop(); } } } /* 全排列 I */ fn permutations_i(nums: &mut [i32]) -> Vec> { let mut res = Vec::new(); // 状态(子集) backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [1, 2, 3]; let res = permutations_i(&mut nums); println!("输入数组 nums = {:?}", &nums); println!("所有排列 res = {:?}", &res); } ================================================ FILE: codes/rust/chapter_backtracking/permutations_ii.rs ================================================ /* * File: permutations_ii.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use std::collections::HashSet; /* 回溯算法:全排列 II */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // 当状态长度等于元素数量时,记录解 if state.len() == choices.len() { res.push(state); return; } // 遍历所有选择 let mut duplicated = HashSet::::new(); for i in 0..choices.len() { let choice = choices[i]; // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 if !selected[i] && !duplicated.contains(&choice) { // 尝试:做出选择,更新状态 duplicated.insert(choice); // 记录选择过的元素值 selected[i] = true; state.push(choice); // 进行下一轮选择 backtrack(state.clone(), choices, selected, res); // 回退:撤销选择,恢复到之前的状态 selected[i] = false; state.pop(); } } } /* 全排列 II */ fn permutations_ii(nums: &mut [i32]) -> Vec> { let mut res = Vec::new(); backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [1, 2, 2]; let res = permutations_ii(&mut nums); println!("输入数组 nums = {:?}", &nums); println!("所有排列 res = {:?}", &res); } ================================================ FILE: codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs ================================================ /* * File: preorder_traversal_i_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* 前序遍历:例题一 */ fn pre_order(res: &mut Vec>>, root: Option<&Rc>>) { if root.is_none() { return; } if let Some(node) = root { if node.borrow().val == 7 { // 记录解 res.push(node.clone()); } pre_order(res, node.borrow().left.as_ref()); pre_order(res, node.borrow().right.as_ref()); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("初始化二叉树"); print_util::print_tree(root.as_ref().unwrap()); // 前序遍历 let mut res = Vec::new(); pre_order(&mut res, root.as_ref()); println!("\n输出所有值为 7 的节点"); let mut vals = Vec::new(); for node in res { vals.push(node.borrow().val) } println!("{:?}", vals); } ================================================ FILE: codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs ================================================ /* * File: preorder_traversal_ii_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* 前序遍历:例题二 */ fn pre_order( res: &mut Vec>>>, path: &mut Vec>>, root: Option<&Rc>>, ) { if root.is_none() { return; } if let Some(node) = root { // 尝试 path.push(node.clone()); if node.borrow().val == 7 { // 记录解 res.push(path.clone()); } pre_order(res, path, node.borrow().left.as_ref()); pre_order(res, path, node.borrow().right.as_ref()); // 回退 path.pop(); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("初始化二叉树"); print_util::print_tree(root.as_ref().unwrap()); // 前序遍历 let mut path = Vec::new(); let mut res = Vec::new(); pre_order(&mut res, &mut path, root.as_ref()); println!("\n输出所有根节点到节点 7 的路径"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs ================================================ /* * File: preorder_traversal_iii_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* 前序遍历:例题三 */ fn pre_order( res: &mut Vec>>>, path: &mut Vec>>, root: Option<&Rc>>, ) { // 剪枝 if root.is_none() || root.as_ref().unwrap().borrow().val == 3 { return; } if let Some(node) = root { // 尝试 path.push(node.clone()); if node.borrow().val == 7 { // 记录解 res.push(path.clone()); } pre_order(res, path, node.borrow().left.as_ref()); pre_order(res, path, node.borrow().right.as_ref()); // 回退 path.pop(); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("初始化二叉树"); print_util::print_tree(root.as_ref().unwrap()); // 前序遍历 let mut path = Vec::new(); let mut res = Vec::new(); pre_order(&mut res, &mut path, root.as_ref()); println!("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs ================================================ /* * File: preorder_traversal_iii_template.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* 判断当前状态是否为解 */ fn is_solution(state: &mut Vec>>) -> bool { return !state.is_empty() && state.last().unwrap().borrow().val == 7; } /* 记录解 */ fn record_solution( state: &mut Vec>>, res: &mut Vec>>>, ) { res.push(state.clone()); } /* 判断在当前状态下,该选择是否合法 */ fn is_valid(_: &mut Vec>>, choice: Option<&Rc>>) -> bool { return choice.is_some() && choice.unwrap().borrow().val != 3; } /* 更新状态 */ fn make_choice(state: &mut Vec>>, choice: Rc>) { state.push(choice); } /* 恢复状态 */ fn undo_choice(state: &mut Vec>>, _: Rc>) { state.pop(); } /* 回溯算法:例题三 */ fn backtrack( state: &mut Vec>>, choices: &Vec>>>, res: &mut Vec>>>, ) { // 检查是否为解 if is_solution(state) { // 记录解 record_solution(state, res); } // 遍历所有选择 for &choice in choices.iter() { // 剪枝:检查选择是否合法 if is_valid(state, choice) { // 尝试:做出选择,更新状态 make_choice(state, choice.unwrap().clone()); // 进行下一轮选择 backtrack( state, &vec![ choice.unwrap().borrow().left.as_ref(), choice.unwrap().borrow().right.as_ref(), ], res, ); // 回退:撤销选择,恢复到之前的状态 undo_choice(state, choice.unwrap().clone()); } } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("初始化二叉树"); print_util::print_tree(root.as_ref().unwrap()); // 回溯算法 let mut res = Vec::new(); backtrack(&mut Vec::new(), &mut vec![root.as_ref()], &mut res); println!("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: codes/rust/chapter_backtracking/subset_sum_i.rs ================================================ /* * File: subset_sum_i.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 回溯算法:子集和 I */ fn backtrack( state: &mut Vec, target: i32, choices: &[i32], start: usize, res: &mut Vec>, ) { // 子集和等于 target 时,记录解 if target == 0 { res.push(state.clone()); return; } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 for i in start..choices.len() { // 剪枝一:若子集和超过 target ,则直接结束循环 // 这是因为数组已排序,后边元素更大,子集和一定超过 target if target - choices[i] < 0 { break; } // 尝试:做出选择,更新 target, start state.push(choices[i]); // 进行下一轮选择 backtrack(state, target - choices[i], choices, i, res); // 回退:撤销选择,恢复到之前的状态 state.pop(); } } /* 求解子集和 I */ fn subset_sum_i(nums: &mut [i32], target: i32) -> Vec> { let mut state = Vec::new(); // 状态(子集) nums.sort(); // 对 nums 进行排序 let start = 0; // 遍历起始点 let mut res = Vec::new(); // 结果列表(子集列表) backtrack(&mut state, target, nums, start, &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [3, 4, 5]; let target = 9; let res = subset_sum_i(&mut nums, target); println!("输入数组 nums = {:?}, target = {}", &nums, target); println!("所有和等于 {} 的子集 res = {:?}", target, &res); } ================================================ FILE: codes/rust/chapter_backtracking/subset_sum_i_naive.rs ================================================ /* * File: subset_sum_i_naive.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 回溯算法:子集和 I */ fn backtrack( state: &mut Vec, target: i32, total: i32, choices: &[i32], res: &mut Vec>, ) { // 子集和等于 target 时,记录解 if total == target { res.push(state.clone()); return; } // 遍历所有选择 for i in 0..choices.len() { // 剪枝:若子集和超过 target ,则跳过该选择 if total + choices[i] > target { continue; } // 尝试:做出选择,更新元素和 total state.push(choices[i]); // 进行下一轮选择 backtrack(state, target, total + choices[i], choices, res); // 回退:撤销选择,恢复到之前的状态 state.pop(); } } /* 求解子集和 I(包含重复子集) */ fn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec> { let mut state = Vec::new(); // 状态(子集) let total = 0; // 子集和 let mut res = Vec::new(); // 结果列表(子集列表) backtrack(&mut state, target, total, nums, &mut res); res } /* Driver Code */ pub fn main() { let nums = [3, 4, 5]; let target = 9; let res = subset_sum_i_naive(&nums, target); println!("输入数组 nums = {:?}, target = {}", &nums, target); println!("所有和等于 {} 的子集 res = {:?}", target, &res); println!("请注意,该方法输出的结果包含重复集合"); } ================================================ FILE: codes/rust/chapter_backtracking/subset_sum_ii.rs ================================================ /* * File: subset_sum_ii.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 回溯算法:子集和 II */ fn backtrack( state: &mut Vec, target: i32, choices: &[i32], start: usize, res: &mut Vec>, ) { // 子集和等于 target 时,记录解 if target == 0 { res.push(state.clone()); return; } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 // 剪枝三:从 start 开始遍历,避免重复选择同一元素 for i in start..choices.len() { // 剪枝一:若子集和超过 target ,则直接结束循环 // 这是因为数组已排序,后边元素更大,子集和一定超过 target if target - choices[i] < 0 { break; } // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 if i > start && choices[i] == choices[i - 1] { continue; } // 尝试:做出选择,更新 target, start state.push(choices[i]); // 进行下一轮选择 backtrack(state, target - choices[i], choices, i + 1, res); // 回退:撤销选择,恢复到之前的状态 state.pop(); } } /* 求解子集和 II */ fn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec> { let mut state = Vec::new(); // 状态(子集) nums.sort(); // 对 nums 进行排序 let start = 0; // 遍历起始点 let mut res = Vec::new(); // 结果列表(子集列表) backtrack(&mut state, target, nums, start, &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [4, 4, 5]; let target = 9; let res = subset_sum_ii(&mut nums, target); println!("输入数组 nums = {:?}, target = {}", &nums, target); println!("所有和等于 {} 的子集 res = {:?}", target, &res); } ================================================ FILE: codes/rust/chapter_computational_complexity/iteration.rs ================================================ /* * File: iteration.rs * Created Time: 2023-09-02 * Author: night-cruise (2586447362@qq.com) */ /* for 循环 */ fn for_loop(n: i32) -> i32 { let mut res = 0; // 循环求和 1, 2, ..., n-1, n for i in 1..=n { res += i; } res } /* while 循环 */ fn while_loop(n: i32) -> i32 { let mut res = 0; let mut i = 1; // 初始化条件变量 // 循环求和 1, 2, ..., n-1, n while i <= n { res += i; i += 1; // 更新条件变量 } res } /* while 循环(两次更新) */ fn while_loop_ii(n: i32) -> i32 { let mut res = 0; let mut i = 1; // 初始化条件变量 // 循环求和 1, 4, 10, ... while i <= n { res += i; // 更新条件变量 i += 1; i *= 2; } res } /* 双层 for 循环 */ fn nested_for_loop(n: i32) -> String { let mut res = vec![]; // 循环 i = 1, 2, ..., n-1, n for i in 1..=n { // 循环 j = 1, 2, ..., n-1, n for j in 1..=n { res.push(format!("({}, {}), ", i, j)); } } res.join("") } /* Driver Code */ fn main() { let n = 5; let mut res; res = for_loop(n); println!("\nfor 循环的求和结果 res = {res}"); res = while_loop(n); println!("\nwhile 循环的求和结果 res = {res}"); res = while_loop_ii(n); println!("\nwhile 循环(两次更新)求和结果 res = {}", res); let res = nested_for_loop(n); println!("\n双层 for 循环的遍历结果 {res}"); } ================================================ FILE: codes/rust/chapter_computational_complexity/recursion.rs ================================================ /* * File: recursion.rs * Created Time: 2023-09-02 * Author: night-cruise (2586447362@qq.com) */ /* 递归 */ fn recur(n: i32) -> i32 { // 终止条件 if n == 1 { return 1; } // 递:递归调用 let res = recur(n - 1); // 归:返回结果 n + res } /* 使用迭代模拟递归 */ fn for_loop_recur(n: i32) -> i32 { // 使用一个显式的栈来模拟系统调用栈 let mut stack = Vec::new(); let mut res = 0; // 递:递归调用 for i in (1..=n).rev() { // 通过“入栈操作”模拟“递” stack.push(i); } // 归:返回结果 while !stack.is_empty() { // 通过“出栈操作”模拟“归” res += stack.pop().unwrap(); } // res = 1+2+3+...+n res } /* 尾递归 */ fn tail_recur(n: i32, res: i32) -> i32 { // 终止条件 if n == 0 { return res; } // 尾递归调用 tail_recur(n - 1, res + n) } /* 斐波那契数列:递归 */ fn fib(n: i32) -> i32 { // 终止条件 f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1; } // 递归调用 f(n) = f(n-1) + f(n-2) let res = fib(n - 1) + fib(n - 2); // 返回结果 res } /* Driver Code */ fn main() { let n = 5; let mut res; res = recur(n); println!("\n递归函数的求和结果 res = {res}"); res = for_loop_recur(n); println!("\n使用迭代模拟递归求和结果 res = {res}"); res = tail_recur(n, 0); println!("\n尾递归函数的求和结果 res = {res}"); res = fib(n); println!("\n斐波那契数列的第 {n} 项为 {res}"); } ================================================ FILE: codes/rust/chapter_computational_complexity/space_complexity.rs ================================================ /* * File: space_complexity.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode, TreeNode}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; /* 函数 */ fn function() -> i32 { // 执行某些操作 return 0; } /* 常数阶 */ #[allow(unused)] fn constant(n: i32) { // 常量、变量、对象占用 O(1) 空间 const A: i32 = 0; let b = 0; let nums = vec![0; 10000]; let node = ListNode::new(0); // 循环中的变量占用 O(1) 空间 for i in 0..n { let c = 0; } // 循环中的函数占用 O(1) 空间 for i in 0..n { function(); } } /* 线性阶 */ #[allow(unused)] fn linear(n: i32) { // 长度为 n 的数组占用 O(n) 空间 let mut nums = vec![0; n as usize]; // 长度为 n 的列表占用 O(n) 空间 let mut nodes = Vec::new(); for i in 0..n { nodes.push(ListNode::new(i)) } // 长度为 n 的哈希表占用 O(n) 空间 let mut map = HashMap::new(); for i in 0..n { map.insert(i, i.to_string()); } } /* 线性阶(递归实现) */ fn linear_recur(n: i32) { println!("递归 n = {}", n); if n == 1 { return; }; linear_recur(n - 1); } /* 平方阶 */ #[allow(unused)] fn quadratic(n: i32) { // 矩阵占用 O(n^2) 空间 let num_matrix = vec![vec![0; n as usize]; n as usize]; // 二维列表占用 O(n^2) 空间 let mut num_list = Vec::new(); for i in 0..n { let mut tmp = Vec::new(); for j in 0..n { tmp.push(0); } num_list.push(tmp); } } /* 平方阶(递归实现) */ fn quadratic_recur(n: i32) -> i32 { if n <= 0 { return 0; }; // 数组 nums 长度为 n, n-1, ..., 2, 1 let nums = vec![0; n as usize]; println!("递归 n = {} 中的 nums 长度 = {}", n, nums.len()); return quadratic_recur(n - 1); } /* 指数阶(建立满二叉树) */ fn build_tree(n: i32) -> Option>> { if n == 0 { return None; }; let root = TreeNode::new(0); root.borrow_mut().left = build_tree(n - 1); root.borrow_mut().right = build_tree(n - 1); return Some(root); } /* Driver Code */ fn main() { let n = 5; // 常数阶 constant(n); // 线性阶 linear(n); linear_recur(n); // 平方阶 quadratic(n); quadratic_recur(n); // 指数阶 let root = build_tree(n); print_util::print_tree(&root.unwrap()); } ================================================ FILE: codes/rust/chapter_computational_complexity/time_complexity.rs ================================================ /* * File: time_complexity.rs * Created Time: 2023-01-10 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ /* 常数阶 */ fn constant(n: i32) -> i32 { _ = n; let mut count = 0; let size = 100_000; for _ in 0..size { count += 1; } count } /* 线性阶 */ fn linear(n: i32) -> i32 { let mut count = 0; for _ in 0..n { count += 1; } count } /* 线性阶(遍历数组) */ fn array_traversal(nums: &[i32]) -> i32 { let mut count = 0; // 循环次数与数组长度成正比 for _ in nums { count += 1; } count } /* 平方阶 */ fn quadratic(n: i32) -> i32 { let mut count = 0; // 循环次数与数据大小 n 成平方关系 for _ in 0..n { for _ in 0..n { count += 1; } } count } /* 平方阶(冒泡排序) */ fn bubble_sort(nums: &mut [i32]) -> i32 { let mut count = 0; // 计数器 // 外循环:未排序区间为 [0, i] for i in (1..nums.len()).rev() { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in 0..i { if nums[j] > nums[j + 1] { // 交换 nums[j] 与 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 元素交换包含 3 个单元操作 } } } count } /* 指数阶(循环实现) */ fn exponential(n: i32) -> i32 { let mut count = 0; let mut base = 1; // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for _ in 0..n { for _ in 0..base { count += 1 } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 count } /* 指数阶(递归实现) */ fn exp_recur(n: i32) -> i32 { if n == 1 { return 1; } exp_recur(n - 1) + exp_recur(n - 1) + 1 } /* 对数阶(循环实现) */ fn logarithmic(mut n: i32) -> i32 { let mut count = 0; while n > 1 { n = n / 2; count += 1; } count } /* 对数阶(递归实现) */ fn log_recur(n: i32) -> i32 { if n <= 1 { return 0; } log_recur(n / 2) + 1 } /* 线性对数阶 */ fn linear_log_recur(n: i32) -> i32 { if n <= 1 { return 1; } let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2); for _ in 0..n { count += 1; } return count; } /* 阶乘阶(递归实现) */ fn factorial_recur(n: i32) -> i32 { if n == 0 { return 1; } let mut count = 0; // 从 1 个分裂出 n 个 for _ in 0..n { count += factorial_recur(n - 1); } count } /* Driver Code */ fn main() { // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 let n: i32 = 8; println!("输入数据大小 n = {}", n); let mut count = constant(n); println!("常数阶的操作数量 = {}", count); count = linear(n); println!("线性阶的操作数量 = {}", count); count = array_traversal(&vec![0; n as usize]); println!("线性阶(遍历数组)的操作数量 = {}", count); count = quadratic(n); println!("平方阶的操作数量 = {}", count); let mut nums = (1..=n).rev().collect::>(); // [n,n-1,...,2,1] count = bubble_sort(&mut nums); println!("平方阶(冒泡排序)的操作数量 = {}", count); count = exponential(n); println!("指数阶(循环实现)的操作数量 = {}", count); count = exp_recur(n); println!("指数阶(递归实现)的操作数量 = {}", count); count = logarithmic(n); println!("对数阶(循环实现)的操作数量 = {}", count); count = log_recur(n); println!("对数阶(递归实现)的操作数量 = {}", count); count = linear_log_recur(n); println!("线性对数阶(递归实现)的操作数量 = {}", count); count = factorial_recur(n); println!("阶乘阶(递归实现)的操作数量 = {}", count); } ================================================ FILE: codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs ================================================ /* * File: worst_best_time_complexity.rs * Created Time: 2023-01-13 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use rand::seq::SliceRandom; use rand::thread_rng; /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ fn random_numbers(n: i32) -> Vec { // 生成数组 nums = { 1, 2, 3, ..., n } let mut nums = (1..=n).collect::>(); // 随机打乱数组元素 nums.shuffle(&mut thread_rng()); nums } /* 查找数组 nums 中数字 1 所在索引 */ fn find_one(nums: &[i32]) -> Option { for i in 0..nums.len() { // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if nums[i] == 1 { return Some(i); } } None } /* Driver Code */ fn main() { for _ in 0..10 { let n = 100; let nums = random_numbers(n); let index = find_one(&nums).unwrap(); print!("\n数组 [ 1, 2, ..., n ] 被打乱后 = "); print_util::print_array(&nums); println!("\n数字 1 的索引为 {}", index); } } ================================================ FILE: codes/rust/chapter_divide_and_conquer/binary_search_recur.rs ================================================ /* * File: binary_search_recur.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* 二分查找:问题 f(i, j) */ fn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 { // 若区间为空,代表无目标元素,则返回 -1 if i > j { return -1; } let m: i32 = i + (j - i) / 2; if nums[m as usize] < target { // 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j); } else if nums[m as usize] > target { // 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ fn binary_search(nums: &[i32], target: i32) -> i32 { let n = nums.len() as i32; // 求解问题 f(0, n-1) dfs(nums, target, 0, n - 1) } /* Driver Code */ pub fn main() { let target = 6; let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分查找(双闭区间) let index = binary_search(&nums, target); println!("目标元素 6 的索引 = {index}"); } ================================================ FILE: codes/rust/chapter_divide_and_conquer/build_tree.rs ================================================ /* * File: build_tree.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, TreeNode}; use std::collections::HashMap; use std::{cell::RefCell, rc::Rc}; /* 构建二叉树:分治 */ fn dfs( preorder: &[i32], inorder_map: &HashMap, i: i32, l: i32, r: i32, ) -> Option>> { // 子树区间为空时终止 if r - l < 0 { return None; } // 初始化根节点 let root = TreeNode::new(preorder[i as usize]); // 查询 m ,从而划分左右子树 let m = inorder_map.get(&preorder[i as usize]).unwrap(); // 子问题:构建左子树 root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1); // 子问题:构建右子树 root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r); // 返回根节点 Some(root) } /* 构建二叉树 */ fn build_tree(preorder: &[i32], inorder: &[i32]) -> Option>> { // 初始化哈希表,存储 inorder 元素到索引的映射 let mut inorder_map: HashMap = HashMap::new(); for i in 0..inorder.len() { inorder_map.insert(inorder[i], i as i32); } let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1); root } /* Driver Code */ fn main() { let preorder = [3, 9, 2, 1, 7]; let inorder = [9, 3, 1, 2, 7]; println!("中序遍历 = {:?}", preorder); println!("前序遍历 = {:?}", inorder); let root = build_tree(&preorder, &inorder); println!("构建的二叉树为:"); print_util::print_tree(root.as_ref().unwrap()); } ================================================ FILE: codes/rust/chapter_divide_and_conquer/hanota.rs ================================================ /* * File: hanota.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ #![allow(non_snake_case)] /* 移动一个圆盘 */ fn move_pan(src: &mut Vec, tar: &mut Vec) { // 从 src 顶部拿出一个圆盘 let pan = src.pop().unwrap(); // 将圆盘放入 tar 顶部 tar.push(pan); } /* 求解汉诺塔问题 f(i) */ fn dfs(i: i32, src: &mut Vec, buf: &mut Vec, tar: &mut Vec) { // 若 src 只剩下一个圆盘,则直接将其移到 tar if i == 1 { move_pan(src, tar); return; } // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf dfs(i - 1, src, tar, buf); // 子问题 f(1) :将 src 剩余一个圆盘移到 tar move_pan(src, tar); // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar dfs(i - 1, buf, src, tar); } /* 求解汉诺塔问题 */ fn solve_hanota(A: &mut Vec, B: &mut Vec, C: &mut Vec) { let n = A.len() as i32; // 将 A 顶部 n 个圆盘借助 B 移到 C dfs(n, A, B, C); } /* Driver Code */ pub fn main() { let mut A = vec![5, 4, 3, 2, 1]; let mut B = Vec::new(); let mut C = Vec::new(); println!("初始状态下:"); println!("A = {:?}", A); println!("B = {:?}", B); println!("C = {:?}", C); solve_hanota(&mut A, &mut B, &mut C); println!("圆盘移动完成后:"); println!("A = {:?}", A); println!("B = {:?}", B); println!("C = {:?}", C); } ================================================ FILE: codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs ================================================ /* * File: climbing_stairs_backtrack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 回溯 */ fn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) { // 当爬到第 n 阶时,方案数量加 1 if state == n { res[0] = res[0] + 1; } // 遍历所有选择 for &choice in choices { // 剪枝:不允许越过第 n 阶 if state + choice > n { continue; } // 尝试:做出选择,更新状态 backtrack(choices, state + choice, n, res); // 回退 } } /* 爬楼梯:回溯 */ fn climbing_stairs_backtrack(n: usize) -> i32 { let choices = vec![1, 2]; // 可选择向上爬 1 阶或 2 阶 let state = 0; // 从第 0 阶开始爬 let mut res = Vec::new(); res.push(0); // 使用 res[0] 记录方案数量 backtrack(&choices, state, n as i32, &mut res); res[0] } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_backtrack(n); println!("爬 {n} 阶楼梯共有 {res} 种方案"); } ================================================ FILE: codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs ================================================ /* * File: climbing_stairs_constraint_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 带约束爬楼梯:动态规划 */ fn climbing_stairs_constraint_dp(n: usize) -> i32 { if n == 1 || n == 2 { return 1; }; // 初始化 dp 表,用于存储子问题的解 let mut dp = vec![vec![-1; 3]; n + 1]; // 初始状态:预设最小子问题的解 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 状态转移:从较小子问题逐步求解较大子问题 for i in 3..=n { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } dp[n][1] + dp[n][2] } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_constraint_dp(n); println!("爬 {n} 阶楼梯共有 {res} 种方案"); } ================================================ FILE: codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs ================================================ /* * File: climbing_stairs_dfs.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 搜索 */ fn dfs(i: usize) -> i32 { // 已知 dp[1] 和 dp[2] ,返回之 if i == 1 || i == 2 { return i as i32; } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i - 1) + dfs(i - 2); count } /* 爬楼梯:搜索 */ fn climbing_stairs_dfs(n: usize) -> i32 { dfs(n) } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dfs(n); println!("爬 {n} 阶楼梯共有 {res} 种方案"); } ================================================ FILE: codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs ================================================ /* * File: climbing_stairs_dfs_mem.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 记忆化搜索 */ fn dfs(i: usize, mem: &mut [i32]) -> i32 { // 已知 dp[1] 和 dp[2] ,返回之 if i == 1 || i == 2 { return i as i32; } // 若存在记录 dp[i] ,则直接返回之 if mem[i] != -1 { return mem[i]; } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i - 1, mem) + dfs(i - 2, mem); // 记录 dp[i] mem[i] = count; count } /* 爬楼梯:记忆化搜索 */ fn climbing_stairs_dfs_mem(n: usize) -> i32 { // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 let mut mem = vec![-1; n + 1]; dfs(n, &mut mem) } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dfs_mem(n); println!("爬 {n} 阶楼梯共有 {res} 种方案"); } ================================================ FILE: codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs ================================================ /* * File: climbing_stairs_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 爬楼梯:动态规划 */ fn climbing_stairs_dp(n: usize) -> i32 { // 已知 dp[1] 和 dp[2] ,返回之 if n == 1 || n == 2 { return n as i32; } // 初始化 dp 表,用于存储子问题的解 let mut dp = vec![-1; n + 1]; // 初始状态:预设最小子问题的解 dp[1] = 1; dp[2] = 2; // 状态转移:从较小子问题逐步求解较大子问题 for i in 3..=n { dp[i] = dp[i - 1] + dp[i - 2]; } dp[n] } /* 爬楼梯:空间优化后的动态规划 */ fn climbing_stairs_dp_comp(n: usize) -> i32 { if n == 1 || n == 2 { return n as i32; } let (mut a, mut b) = (1, 2); for _ in 3..=n { let tmp = b; b = a + b; a = tmp; } b } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dp(n); println!("爬 {n} 阶楼梯共有 {res} 种方案"); let res = climbing_stairs_dp_comp(n); println!("爬 {n} 阶楼梯共有 {res} 种方案"); } ================================================ FILE: codes/rust/chapter_dynamic_programming/coin_change.rs ================================================ /* * File: coin_change.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 零钱兑换:动态规划 */ fn coin_change_dp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); let max = amt + 1; // 初始化 dp 表 let mut dp = vec![vec![0; amt + 1]; n + 1]; // 状态转移:首行首列 for a in 1..=amt { dp[0][a] = max; } // 状态转移:其余行和列 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1); } } } if dp[n][amt] != max { return dp[n][amt] as i32; } else { -1 } } /* 零钱兑换:空间优化后的动态规划 */ fn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); let max = amt + 1; // 初始化 dp 表 let mut dp = vec![0; amt + 1]; dp.fill(max); dp[0] = 0; // 状态转移 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1); } } } if dp[amt] != max { return dp[amt] as i32; } else { -1 } } /* Driver Code */ pub fn main() { let coins = [1, 2, 5]; let amt: usize = 4; // 动态规划 let res = coin_change_dp(&coins, amt); println!("凑到目标金额所需的最少硬币数量为 {res}"); // 空间优化后的动态规划 let res = coin_change_dp_comp(&coins, amt); println!("凑到目标金额所需的最少硬币数量为 {res}"); } ================================================ FILE: codes/rust/chapter_dynamic_programming/coin_change_ii.rs ================================================ /* * File: coin_change_ii.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 零钱兑换 II:动态规划 */ fn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); // 初始化 dp 表 let mut dp = vec![vec![0; amt + 1]; n + 1]; // 初始化首列 for i in 0..=n { dp[i][0] = 1; } // 状态转移 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize]; } } } dp[n][amt] } /* 零钱兑换 II:空间优化后的动态规划 */ fn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); // 初始化 dp 表 let mut dp = vec![0; amt + 1]; dp[0] = 1; // 状态转移 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1] as usize]; } } } dp[amt] } /* Driver Code */ pub fn main() { let coins = [1, 2, 5]; let amt: usize = 5; // 动态规划 let res = coin_change_ii_dp(&coins, amt); println!("凑出目标金额的硬币组合数量为 {res}"); // 空间优化后的动态规划 let res = coin_change_ii_dp_comp(&coins, amt); println!("凑出目标金额的硬币组合数量为 {res}"); } ================================================ FILE: codes/rust/chapter_dynamic_programming/edit_distance.rs ================================================ /* * File: edit_distance.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 编辑距离:暴力搜索 */ fn edit_distance_dfs(s: &str, t: &str, i: usize, j: usize) -> i32 { // 若 s 和 t 都为空,则返回 0 if i == 0 && j == 0 { return 0; } // 若 s 为空,则返回 t 长度 if i == 0 { return j as i32; } // 若 t 为空,则返回 s 长度 if j == 0 { return i as i32; } // 若两字符相等,则直接跳过此两字符 if s.chars().nth(i - 1) == t.chars().nth(j - 1) { return edit_distance_dfs(s, t, i - 1, j - 1); } // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 let insert = edit_distance_dfs(s, t, i, j - 1); let delete = edit_distance_dfs(s, t, i - 1, j); let replace = edit_distance_dfs(s, t, i - 1, j - 1); // 返回最少编辑步数 std::cmp::min(std::cmp::min(insert, delete), replace) + 1 } /* 编辑距离:记忆化搜索 */ fn edit_distance_dfs_mem(s: &str, t: &str, mem: &mut Vec>, i: usize, j: usize) -> i32 { // 若 s 和 t 都为空,则返回 0 if i == 0 && j == 0 { return 0; } // 若 s 为空,则返回 t 长度 if i == 0 { return j as i32; } // 若 t 为空,则返回 s 长度 if j == 0 { return i as i32; } // 若已有记录,则直接返回之 if mem[i][j] != -1 { return mem[i][j]; } // 若两字符相等,则直接跳过此两字符 if s.chars().nth(i - 1) == t.chars().nth(j - 1) { return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); } // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 let insert = edit_distance_dfs_mem(s, t, mem, i, j - 1); let delete = edit_distance_dfs_mem(s, t, mem, i - 1, j); let replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); // 记录并返回最少编辑步数 mem[i][j] = std::cmp::min(std::cmp::min(insert, delete), replace) + 1; mem[i][j] } /* 编辑距离:动态规划 */ fn edit_distance_dp(s: &str, t: &str) -> i32 { let (n, m) = (s.len(), t.len()); let mut dp = vec![vec![0; m + 1]; n + 1]; // 状态转移:首行首列 for i in 1..=n { dp[i][0] = i as i32; } for j in 1..m { dp[0][j] = j as i32; } // 状态转移:其余行和列 for i in 1..=n { for j in 1..=m { if s.chars().nth(i - 1) == t.chars().nth(j - 1) { // 若两字符相等,则直接跳过此两字符 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[i][j] = std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } dp[n][m] } /* 编辑距离:空间优化后的动态规划 */ fn edit_distance_dp_comp(s: &str, t: &str) -> i32 { let (n, m) = (s.len(), t.len()); let mut dp = vec![0; m + 1]; // 状态转移:首行 for j in 1..m { dp[j] = j as i32; } // 状态转移:其余行 for i in 1..=n { // 状态转移:首列 let mut leftup = dp[0]; // 暂存 dp[i-1, j-1] dp[0] = i as i32; // 状态转移:其余列 for j in 1..=m { let temp = dp[j]; if s.chars().nth(i - 1) == t.chars().nth(j - 1) { // 若两字符相等,则直接跳过此两字符 dp[j] = leftup; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 更新为下一轮的 dp[i-1, j-1] } } dp[m] } /* Driver Code */ pub fn main() { let s = "bag"; let t = "pack"; let (n, m) = (s.len(), t.len()); // 暴力搜索 let res = edit_distance_dfs(s, t, n, m); println!("将 {s} 更改为 {t} 最少需要编辑 {res} 步"); // 记忆搜索 let mut mem = vec![vec![0; m + 1]; n + 1]; for row in mem.iter_mut() { row.fill(-1); } let res = edit_distance_dfs_mem(s, t, &mut mem, n, m); println!("将 {s} 更改为 {t} 最少需要编辑 {res} 步"); // 动态规划 let res = edit_distance_dp(s, t); println!("将 {s} 更改为 {t} 最少需要编辑 {res} 步"); // 空间优化后的动态规划 let res = edit_distance_dp_comp(s, t); println!("将 {s} 更改为 {t} 最少需要编辑 {res} 步"); } ================================================ FILE: codes/rust/chapter_dynamic_programming/knapsack.rs ================================================ /* * File: knapsack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 0-1 背包:暴力搜索 */ fn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if i == 0 || c == 0 { return 0; } // 若超过背包容量,则只能选择不放入背包 if wgt[i - 1] > c as i32 { return knapsack_dfs(wgt, val, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 let no = knapsack_dfs(wgt, val, i - 1, c); let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; // 返回两种方案中价值更大的那一个 std::cmp::max(no, yes) } /* 0-1 背包:记忆化搜索 */ fn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec>, i: usize, c: usize) -> i32 { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if i == 0 || c == 0 { return 0; } // 若已有记录,则直接返回 if mem[i][c] != -1 { return mem[i][c]; } // 若超过背包容量,则只能选择不放入背包 if wgt[i - 1] > c as i32 { return knapsack_dfs_mem(wgt, val, mem, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c); let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; // 记录并返回两种方案中价值更大的那一个 mem[i][c] = std::cmp::max(no, yes); mem[i][c] } /* 0-1 背包:动态规划 */ fn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // 初始化 dp 表 let mut dp = vec![vec![0; cap + 1]; n + 1]; // 状态转移 for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = std::cmp::max( dp[i - 1][c], dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1], ); } } } dp[n][cap] } /* 0-1 背包:空间优化后的动态规划 */ fn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // 初始化 dp 表 let mut dp = vec![0; cap + 1]; // 状态转移 for i in 1..=n { // 倒序遍历 for c in (1..=cap).rev() { if wgt[i - 1] <= c as i32 { // 不选和选物品 i 这两种方案的较大值 dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); } } } dp[cap] } /* Driver Code */ pub fn main() { let wgt = [10, 20, 30, 40, 50]; let val = [50, 120, 150, 210, 240]; let cap: usize = 50; let n = wgt.len(); // 暴力搜索 let res = knapsack_dfs(&wgt, &val, n, cap); println!("不超过背包容量的最大物品价值为 {res}"); // 记忆搜索 let mut mem = vec![vec![0; cap + 1]; n + 1]; for row in mem.iter_mut() { row.fill(-1); } let res = knapsack_dfs_mem(&wgt, &val, &mut mem, n, cap); println!("不超过背包容量的最大物品价值为 {res}"); // 动态规划 let res = knapsack_dp(&wgt, &val, cap); println!("不超过背包容量的最大物品价值为 {res}"); // 空间优化后的动态规划 let res = knapsack_dp_comp(&wgt, &val, cap); println!("不超过背包容量的最大物品价值为 {res}"); } ================================================ FILE: codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs ================================================ /* * File: min_cost_climbing_stairs_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use std::cmp; /* 爬楼梯最小代价:动态规划 */ fn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 { let n = cost.len() - 1; if n == 1 || n == 2 { return cost[n]; } // 初始化 dp 表,用于存储子问题的解 let mut dp = vec![-1; n + 1]; // 初始状态:预设最小子问题的解 dp[1] = cost[1]; dp[2] = cost[2]; // 状态转移:从较小子问题逐步求解较大子问题 for i in 3..=n { dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i]; } dp[n] } /* 爬楼梯最小代价:空间优化后的动态规划 */ fn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 { let n = cost.len() - 1; if n == 1 || n == 2 { return cost[n]; }; let (mut a, mut b) = (cost[1], cost[2]); for i in 3..=n { let tmp = b; b = cmp::min(a, tmp) + cost[i]; a = tmp; } b } /* Driver Code */ pub fn main() { let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; println!("输入楼梯的代价列表为 {:?}", &cost); let res = min_cost_climbing_stairs_dp(&cost); println!("爬完楼梯的最低代价为 {res}"); let res = min_cost_climbing_stairs_dp_comp(&cost); println!("爬完楼梯的最低代价为 {res}"); } ================================================ FILE: codes/rust/chapter_dynamic_programming/min_path_sum.rs ================================================ /* * File: min_path_sum.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 最小路径和:暴力搜索 */ fn min_path_sum_dfs(grid: &Vec>, i: i32, j: i32) -> i32 { // 若为左上角单元格,则终止搜索 if i == 0 && j == 0 { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if i < 0 || j < 0 { return i32::MAX; } // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 let up = min_path_sum_dfs(grid, i - 1, j); let left = min_path_sum_dfs(grid, i, j - 1); // 返回从左上角到 (i, j) 的最小路径代价 std::cmp::min(left, up) + grid[i as usize][j as usize] } /* 最小路径和:记忆化搜索 */ fn min_path_sum_dfs_mem(grid: &Vec>, mem: &mut Vec>, i: i32, j: i32) -> i32 { // 若为左上角单元格,则终止搜索 if i == 0 && j == 0 { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if i < 0 || j < 0 { return i32::MAX; } // 若已有记录,则直接返回 if mem[i as usize][j as usize] != -1 { return mem[i as usize][j as usize]; } // 左边和上边单元格的最小路径代价 let up = min_path_sum_dfs_mem(grid, mem, i - 1, j); let left = min_path_sum_dfs_mem(grid, mem, i, j - 1); // 记录并返回左上角到 (i, j) 的最小路径代价 mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize]; mem[i as usize][j as usize] } /* 最小路径和:动态规划 */ fn min_path_sum_dp(grid: &Vec>) -> i32 { let (n, m) = (grid.len(), grid[0].len()); // 初始化 dp 表 let mut dp = vec![vec![0; m]; n]; dp[0][0] = grid[0][0]; // 状态转移:首行 for j in 1..m { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 状态转移:首列 for i in 1..n { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 状态转移:其余行和列 for i in 1..n { for j in 1..m { dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } dp[n - 1][m - 1] } /* 最小路径和:空间优化后的动态规划 */ fn min_path_sum_dp_comp(grid: &Vec>) -> i32 { let (n, m) = (grid.len(), grid[0].len()); // 初始化 dp 表 let mut dp = vec![0; m]; // 状态转移:首行 dp[0] = grid[0][0]; for j in 1..m { dp[j] = dp[j - 1] + grid[0][j]; } // 状态转移:其余行 for i in 1..n { // 状态转移:首列 dp[0] = dp[0] + grid[i][0]; // 状态转移:其余列 for j in 1..m { dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j]; } } dp[m - 1] } /* Driver Code */ pub fn main() { let grid = vec![ vec![1, 3, 1, 5], vec![2, 2, 4, 2], vec![5, 3, 2, 1], vec![4, 3, 5, 2], ]; let (n, m) = (grid.len(), grid[0].len()); // 暴力搜索 let res = min_path_sum_dfs(&grid, n as i32 - 1, m as i32 - 1); println!("从左上角到右下角的最小路径和为 {res}"); // 记忆化搜索 let mut mem = vec![vec![0; m]; n]; for row in mem.iter_mut() { row.fill(-1); } let res = min_path_sum_dfs_mem(&grid, &mut mem, n as i32 - 1, m as i32 - 1); println!("从左上角到右下角的最小路径和为 {res}"); // 动态规划 let res = min_path_sum_dp(&grid); println!("从左上角到右下角的最小路径和为 {res}"); // 空间优化后的动态规划 let res = min_path_sum_dp_comp(&grid); println!("从左上角到右下角的最小路径和为 {res}"); } ================================================ FILE: codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs ================================================ /* * File: unbounded_knapsack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 完全背包:动态规划 */ fn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // 初始化 dp 表 let mut dp = vec![vec![0; cap + 1]; n + 1]; // 状态转移 for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]); } } } return dp[n][cap]; } /* 完全背包:空间优化后的动态规划 */ fn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // 初始化 dp 表 let mut dp = vec![0; cap + 1]; // 状态转移 for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); } } } dp[cap] } /* Driver Code */ pub fn main() { let wgt = [1, 2, 3]; let val = [5, 11, 15]; let cap: usize = 4; // 动态规划 let res = unbounded_knapsack_dp(&wgt, &val, cap); println!("不超过背包容量的最大物品价值为 {res}"); // 空间优化后的动态规划 let res = unbounded_knapsack_dp_comp(&wgt, &val, cap); println!("不超过背包容量的最大物品价值为 {res}"); } ================================================ FILE: codes/rust/chapter_graph/graph_adjacency_list.rs ================================================ /* * File: graph_adjacency_list.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ pub use hello_algo_rust::include::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::HashMap; /* 基于邻接表实现的无向图类型 */ pub struct GraphAdjList { // 邻接表,key:顶点,value:该顶点的所有邻接顶点 pub adj_list: HashMap>, // maybe HashSet for value part is better? } impl GraphAdjList { /* 构造方法 */ pub fn new(edges: Vec<[Vertex; 2]>) -> Self { let mut graph = GraphAdjList { adj_list: HashMap::new(), }; // 添加所有顶点和边 for edge in edges { graph.add_vertex(edge[0]); graph.add_vertex(edge[1]); graph.add_edge(edge[0], edge[1]); } graph } /* 获取顶点数量 */ #[allow(unused)] pub fn size(&self) -> usize { self.adj_list.len() } /* 添加边 */ pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) { if vet1 == vet2 { panic!("value error"); } // 添加边 vet1 - vet2 self.adj_list.entry(vet1).or_default().push(vet2); self.adj_list.entry(vet2).or_default().push(vet1); } /* 删除边 */ #[allow(unused)] pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) { if vet1 == vet2 { panic!("value error"); } // 删除边 vet1 - vet2 self.adj_list .entry(vet1) .and_modify(|v| v.retain(|&e| e != vet2)); self.adj_list .entry(vet2) .and_modify(|v| v.retain(|&e| e != vet1)); } /* 添加顶点 */ pub fn add_vertex(&mut self, vet: Vertex) { if self.adj_list.contains_key(&vet) { return; } // 在邻接表中添加一个新链表 self.adj_list.insert(vet, vec![]); } /* 删除顶点 */ #[allow(unused)] pub fn remove_vertex(&mut self, vet: Vertex) { // 在邻接表中删除顶点 vet 对应的链表 self.adj_list.remove(&vet); // 遍历其他顶点的链表,删除所有包含 vet 的边 for list in self.adj_list.values_mut() { list.retain(|&v| v != vet); } } /* 打印邻接表 */ pub fn print(&self) { println!("邻接表 ="); for (vertex, list) in &self.adj_list { let list = list.iter().map(|vertex| vertex.val).collect::>(); println!("{}: {:?},", vertex.val, list); } } } /* Driver Code */ #[allow(unused)] fn main() { /* 初始化无向图 */ let v = vals_to_vets(vec![1, 3, 2, 5, 4]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ]; let mut graph = GraphAdjList::new(edges); println!("\n初始化后,图为"); graph.print(); /* 添加边 */ // 顶点 1, 2 即 v[0], v[2] graph.add_edge(v[0], v[2]); println!("\n添加边 1-2 后,图为"); graph.print(); /* 删除边 */ // 顶点 1, 3 即 v[0], v[1] graph.remove_edge(v[0], v[1]); println!("\n删除边 1-3 后,图为"); graph.print(); /* 添加顶点 */ let v5 = Vertex { val: 6 }; graph.add_vertex(v5); println!("\n添加顶点 6 后,图为"); graph.print(); /* 删除顶点 */ // 顶点 3 即 v[1] graph.remove_vertex(v[1]); println!("\n删除顶点 3 后,图为"); graph.print(); } ================================================ FILE: codes/rust/chapter_graph/graph_adjacency_matrix.rs ================================================ /* * File: graph_adjacency_matrix.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ /* 基于邻接矩阵实现的无向图类型 */ pub struct GraphAdjMat { // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” pub vertices: Vec, // 邻接矩阵,行列索引对应“顶点索引” pub adj_mat: Vec>, } impl GraphAdjMat { /* 构造方法 */ pub fn new(vertices: Vec, edges: Vec<[usize; 2]>) -> Self { let mut graph = GraphAdjMat { vertices: vec![], adj_mat: vec![], }; // 添加顶点 for val in vertices { graph.add_vertex(val); } // 添加边 // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 for edge in edges { graph.add_edge(edge[0], edge[1]) } graph } /* 获取顶点数量 */ pub fn size(&self) -> usize { self.vertices.len() } /* 添加顶点 */ pub fn add_vertex(&mut self, val: i32) { let n = self.size(); // 向顶点列表中添加新顶点的值 self.vertices.push(val); // 在邻接矩阵中添加一行 self.adj_mat.push(vec![0; n]); // 在邻接矩阵中添加一列 for row in self.adj_mat.iter_mut() { row.push(0); } } /* 删除顶点 */ pub fn remove_vertex(&mut self, index: usize) { if index >= self.size() { panic!("index error") } // 在顶点列表中移除索引 index 的顶点 self.vertices.remove(index); // 在邻接矩阵中删除索引 index 的行 self.adj_mat.remove(index); // 在邻接矩阵中删除索引 index 的列 for row in self.adj_mat.iter_mut() { row.remove(index); } } /* 添加边 */ pub fn add_edge(&mut self, i: usize, j: usize) { // 参数 i, j 对应 vertices 元素索引 // 索引越界与相等处理 if i >= self.size() || j >= self.size() || i == j { panic!("index error") } // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) self.adj_mat[i][j] = 1; self.adj_mat[j][i] = 1; } /* 删除边 */ // 参数 i, j 对应 vertices 元素索引 pub fn remove_edge(&mut self, i: usize, j: usize) { // 参数 i, j 对应 vertices 元素索引 // 索引越界与相等处理 if i >= self.size() || j >= self.size() || i == j { panic!("index error") } self.adj_mat[i][j] = 0; self.adj_mat[j][i] = 0; } /* 打印邻接矩阵 */ pub fn print(&self) { println!("顶点列表 = {:?}", self.vertices); println!("邻接矩阵 ="); println!("["); for row in &self.adj_mat { println!(" {:?},", row); } println!("]") } } /* Driver Code */ fn main() { /* 初始化无向图 */ // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 let vertices = vec![1, 3, 2, 5, 4]; let edges = vec![[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]]; let mut graph = GraphAdjMat::new(vertices, edges); println!("\n初始化后,图为"); graph.print(); /* 添加边 */ // 顶点 1, 2 的索引分别为 0, 2 graph.add_edge(0, 2); println!("\n添加边 1-2 后,图为"); graph.print(); /* 删除边 */ // 顶点 1, 3 的索引分别为 0, 1 graph.remove_edge(0, 1); println!("\n删除边 1-3 后,图为"); graph.print(); /* 添加顶点 */ graph.add_vertex(6); println!("\n添加顶点 6 后,图为"); graph.print(); /* 删除顶点 */ // 顶点 3 的索引为 1 graph.remove_vertex(1); println!("\n删除顶点 3 后,图为"); graph.print(); } ================================================ FILE: codes/rust/chapter_graph/graph_bfs.rs ================================================ /* * File: graph_bfs.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ mod graph_adjacency_list; use graph_adjacency_list::GraphAdjList; use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::{HashSet, VecDeque}; /* 广度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { // 顶点遍历序列 let mut res = vec![]; // 哈希集合,用于记录已被访问过的顶点 let mut visited = HashSet::new(); visited.insert(start_vet); // 队列用于实现 BFS let mut que = VecDeque::new(); que.push_back(start_vet); // 以顶点 vet 为起点,循环直至访问完所有顶点 while let Some(vet) = que.pop_front() { res.push(vet); // 记录访问顶点 // 遍历该顶点的所有邻接顶点 if let Some(adj_vets) = graph.adj_list.get(&vet) { for &adj_vet in adj_vets { if visited.contains(&adj_vet) { continue; // 跳过已被访问的顶点 } que.push_back(adj_vet); // 只入队未访问的顶点 visited.insert(adj_vet); // 标记该顶点已被访问 } } } // 返回顶点遍历序列 res } /* Driver Code */ fn main() { /* 初始化无向图 */ let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; let graph = GraphAdjList::new(edges); println!("\n初始化后,图为"); graph.print(); /* 广度优先遍历 */ let res = graph_bfs(graph, v[0]); println!("\n广度优先遍历(BFS)顶点序列为"); println!("{:?}", vets_to_vals(res)); } ================================================ FILE: codes/rust/chapter_graph/graph_dfs.rs ================================================ /* * File: graph_dfs.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ mod graph_adjacency_list; use graph_adjacency_list::GraphAdjList; use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::HashSet; /* 深度优先遍历辅助函数 */ fn dfs(graph: &GraphAdjList, visited: &mut HashSet, res: &mut Vec, vet: Vertex) { res.push(vet); // 记录访问顶点 visited.insert(vet); // 标记该顶点已被访问 // 遍历该顶点的所有邻接顶点 if let Some(adj_vets) = graph.adj_list.get(&vet) { for &adj_vet in adj_vets { if visited.contains(&adj_vet) { continue; // 跳过已被访问的顶点 } // 递归访问邻接顶点 dfs(graph, visited, res, adj_vet); } } } /* 深度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { // 顶点遍历序列 let mut res = vec![]; // 哈希集合,用于记录已被访问过的顶点 let mut visited = HashSet::new(); dfs(&graph, &mut visited, &mut res, start_vet); res } /* Driver Code */ fn main() { /* 初始化无向图 */ let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; let graph = GraphAdjList::new(edges); println!("\n初始化后,图为"); graph.print(); /* 深度优先遍历 */ let res = graph_dfs(graph, v[0]); println!("\n深度优先遍历(DFS)顶点序列为"); println!("{:?}", vets_to_vals(res)); } ================================================ FILE: codes/rust/chapter_greedy/coin_change_greedy.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* 零钱兑换:贪心 */ fn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 { // 假设 coins 列表有序 let mut i = coins.len() - 1; let mut count = 0; // 循环进行贪心选择,直到无剩余金额 while amt > 0 { // 找到小于且最接近剩余金额的硬币 while i > 0 && coins[i] > amt { i -= 1; } // 选择 coins[i] amt -= coins[i]; count += 1; } // 若未找到可行方案,则返回 -1 if amt == 0 { count } else { -1 } } /* Driver Code */ fn main() { // 贪心:能够保证找到全局最优解 let coins = [1, 5, 10, 20, 50, 100]; let amt = 186; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("凑到 {} 所需的最少硬币数量为 {}", amt, res); // 贪心:无法保证找到全局最优解 let coins = [1, 20, 50]; let amt = 60; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("凑到 {} 所需的最少硬币数量为 {}", amt, res); println!("实际上需要的最少数量为 3 ,即 20 + 20 + 20"); // 贪心:无法保证找到全局最优解 let coins = [1, 49, 50]; let amt = 98; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("凑到 {} 所需的最少硬币数量为 {}", amt, res); println!("实际上需要的最少数量为 2 ,即 49 + 49"); } ================================================ FILE: codes/rust/chapter_greedy/fractional_knapsack.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* 物品 */ struct Item { w: i32, // 物品重量 v: i32, // 物品价值 } impl Item { fn new(w: i32, v: i32) -> Self { Self { w, v } } } /* 分数背包:贪心 */ fn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 { // 创建物品列表,包含两个属性:重量、价值 let mut items = wgt .iter() .zip(val.iter()) .map(|(&w, &v)| Item::new(w, v)) .collect::>(); // 按照单位价值 item.v / item.w 从高到低进行排序 items.sort_by(|a, b| { (b.v as f64 / b.w as f64) .partial_cmp(&(a.v as f64 / a.w as f64)) .unwrap() }); // 循环贪心选择 let mut res = 0.0; for item in &items { if item.w <= cap { // 若剩余容量充足,则将当前物品整个装进背包 res += item.v as f64; cap -= item.w; } else { // 若剩余容量不足,则将当前物品的一部分装进背包 res += item.v as f64 / item.w as f64 * cap as f64; // 已无剩余容量,因此跳出循环 break; } } res } /* Driver Code */ fn main() { let wgt = [10, 20, 30, 40, 50]; let val = [50, 120, 150, 210, 240]; let cap = 50; // 贪心算法 let res = fractional_knapsack(&wgt, &val, cap); println!("不超过背包容量的最大物品价值为 {}", res); } ================================================ FILE: codes/rust/chapter_greedy/max_capacity.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* 最大容量:贪心 */ fn max_capacity(ht: &[i32]) -> i32 { // 初始化 i, j,使其分列数组两端 let mut i = 0; let mut j = ht.len() - 1; // 初始最大容量为 0 let mut res = 0; // 循环贪心选择,直至两板相遇 while i < j { // 更新最大容量 let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32; res = std::cmp::max(res, cap); // 向内移动短板 if ht[i] < ht[j] { i += 1; } else { j -= 1; } } res } /* Driver Code */ fn main() { let ht = [3, 8, 5, 2, 7, 7, 3, 4]; // 贪心算法 let res = max_capacity(&ht); println!("最大容量为 {}", res); } ================================================ FILE: codes/rust/chapter_greedy/max_product_cutting.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* 最大切分乘积:贪心 */ fn max_product_cutting(n: i32) -> i32 { // 当 n <= 3 时,必须切分出一个 1 if n <= 3 { return 1 * (n - 1); } // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 let a = n / 3; let b = n % 3; if b == 1 { // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 3_i32.pow(a as u32 - 1) * 2 * 2 } else if b == 2 { // 当余数为 2 时,不做处理 3_i32.pow(a as u32) * 2 } else { // 当余数为 0 时,不做处理 3_i32.pow(a as u32) } } /* Driver Code */ fn main() { let n = 58; // 贪心算法 let res = max_product_cutting(n); println!("最大切分乘积为 {}", res); } ================================================ FILE: codes/rust/chapter_hashing/array_hash_map.rs ================================================ /** * File: array_hash_map.rs * Created Time: 2023-2-18 * Author: xBLACICEx (xBLACKICEx@outlook.com) */ /* 键值对 */ #[derive(Debug, Clone, PartialEq)] pub struct Pair { pub key: i32, pub val: String, } /* 基于数组实现的哈希表 */ pub struct ArrayHashMap { buckets: Vec>, } impl ArrayHashMap { pub fn new() -> ArrayHashMap { // 初始化数组,包含 100 个桶 Self { buckets: vec![None; 100], } } /* 哈希函数 */ fn hash_func(&self, key: i32) -> usize { key as usize % 100 } /* 查询操作 */ pub fn get(&self, key: i32) -> Option<&String> { let index = self.hash_func(key); self.buckets[index].as_ref().map(|pair| &pair.val) } /* 添加操作 */ pub fn put(&mut self, key: i32, val: &str) { let index = self.hash_func(key); self.buckets[index] = Some(Pair { key, val: val.to_string(), }); } /* 删除操作 */ pub fn remove(&mut self, key: i32) { let index = self.hash_func(key); // 置为 None ,代表删除 self.buckets[index] = None; } /* 获取所有键值对 */ pub fn entry_set(&self) -> Vec<&Pair> { self.buckets .iter() .filter_map(|pair| pair.as_ref()) .collect() } /* 获取所有键 */ pub fn key_set(&self) -> Vec<&i32> { self.buckets .iter() .filter_map(|pair| pair.as_ref().map(|pair| &pair.key)) .collect() } /* 获取所有值 */ pub fn value_set(&self) -> Vec<&String> { self.buckets .iter() .filter_map(|pair| pair.as_ref().map(|pair| &pair.val)) .collect() } /* 打印哈希表 */ pub fn print(&self) { for pair in self.entry_set() { println!("{} -> {}", pair.key, pair.val); } } } fn main() { /* 初始化哈希表 */ let mut map = ArrayHashMap::new(); /*添加操作 */ // 在哈希表中添加键值对(key, value) map.put(12836, "小哈"); map.put(15937, "小啰"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鸭"); println!("\n添加完成后,哈希表为\nKey -> Value"); map.print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value let name = map.get(15937).unwrap(); println!("\n输入学号 15937 ,查询到姓名 {}", name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(10583); println!("\n删除 10583 后,哈希表为\nKey -> Value"); map.print(); /* 遍历哈希表 */ println!("\n遍历键值对 Key->Value"); for pair in map.entry_set() { println!("{} -> {}", pair.key, pair.val); } println!("\n单独遍历键 Key"); for key in map.key_set() { println!("{}", key); } println!("\n单独遍历值 Value"); for val in map.value_set() { println!("{}", val); } } ================================================ FILE: codes/rust/chapter_hashing/build_in_hash.rs ================================================ /* * File: build_in_hash.rs * Created Time: 2023-7-6 * Author: WSL0809 (wslzzy@outlook.com) */ use hello_algo_rust::include::ListNode; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; /* Driver Code */ fn main() { let num = 3; let mut num_hasher = DefaultHasher::new(); num.hash(&mut num_hasher); let hash_num = num_hasher.finish(); println!("整数 {} 的哈希值为 {}", num, hash_num); let bol = true; let mut bol_hasher = DefaultHasher::new(); bol.hash(&mut bol_hasher); let hash_bol = bol_hasher.finish(); println!("布尔量 {} 的哈希值为 {}", bol, hash_bol); let dec: f32 = 3.14159; let mut dec_hasher = DefaultHasher::new(); dec.to_bits().hash(&mut dec_hasher); let hash_dec = dec_hasher.finish(); println!("小数 {} 的哈希值为 {}", dec, hash_dec); let str = "Hello 算法"; let mut str_hasher = DefaultHasher::new(); str.hash(&mut str_hasher); let hash_str = str_hasher.finish(); println!("字符串 {} 的哈希值为 {}", str, hash_str); let arr = (&12836, &"小哈"); let mut tup_hasher = DefaultHasher::new(); arr.hash(&mut tup_hasher); let hash_tup = tup_hasher.finish(); println!("元组 {:?} 的哈希值为 {}", arr, hash_tup); let node = ListNode::new(42); let mut hasher = DefaultHasher::new(); node.borrow().val.hash(&mut hasher); let hash = hasher.finish(); println!("节点对象 {:?} 的哈希值为{}", node, hash); } ================================================ FILE: codes/rust/chapter_hashing/hash_map.rs ================================================ /* * File: hash_map.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::collections::HashMap; /* Driver Code */ pub fn main() { // 初始化哈希表 let mut map = HashMap::new(); // 添加操作 // 在哈希表中添加键值对 (key, value) map.insert(12836, "小哈"); map.insert(15937, "小啰"); map.insert(16750, "小算"); map.insert(13276, "小法"); map.insert(10583, "小鸭"); println!("\n添加完成后,哈希表为\nKey -> Value"); print_util::print_hash_map(&map); // 查询操作 // 向哈希表中输入键 key ,得到值 value let name = map.get(&15937).copied().unwrap(); println!("\n输入学号 15937 ,查询到姓名 {name}"); // 删除操作 // 在哈希表中删除键值对 (key, value) _ = map.remove(&10583); println!("\n删除 10583 后,哈希表为\nKey -> Value"); print_util::print_hash_map(&map); // 遍历哈希表 println!("\n遍历键值对 Key->Value"); print_util::print_hash_map(&map); println!("\n单独遍历键 Key"); for key in map.keys() { println!("{key}"); } println!("\n单独遍历值 value"); for value in map.values() { println!("{value}"); } } ================================================ FILE: codes/rust/chapter_hashing/hash_map_chaining.rs ================================================ /* * File: hash_map_chaining.rs * Created Time: 2023-07-07 * Author: WSL0809 (wslzzy@outlook.com) */ #[derive(Clone)] /* 键值对 */ struct Pair { key: i32, val: String, } /* 链式地址哈希表 */ struct HashMapChaining { size: usize, capacity: usize, load_thres: f32, extend_ratio: usize, buckets: Vec>, } impl HashMapChaining { /* 构造方法 */ fn new() -> Self { Self { size: 0, capacity: 4, load_thres: 2.0 / 3.0, extend_ratio: 2, buckets: vec![vec![]; 4], } } /* 哈希函数 */ fn hash_func(&self, key: i32) -> usize { key as usize % self.capacity } /* 负载因子 */ fn load_factor(&self) -> f32 { self.size as f32 / self.capacity as f32 } /* 删除操作 */ fn remove(&mut self, key: i32) -> Option { let index = self.hash_func(key); // 遍历桶,从中删除键值对 for (i, p) in self.buckets[index].iter_mut().enumerate() { if p.key == key { let pair = self.buckets[index].remove(i); self.size -= 1; return Some(pair.val); } } // 若未找到 key ,则返回 None None } /* 扩容哈希表 */ fn extend(&mut self) { // 暂存原哈希表 let buckets_tmp = std::mem::take(&mut self.buckets); // 初始化扩容后的新哈希表 self.capacity *= self.extend_ratio; self.buckets = vec![Vec::new(); self.capacity as usize]; self.size = 0; // 将键值对从原哈希表搬运至新哈希表 for bucket in buckets_tmp { for pair in bucket { self.put(pair.key, pair.val); } } } /* 打印哈希表 */ fn print(&self) { for bucket in &self.buckets { let mut res = Vec::new(); for pair in bucket { res.push(format!("{} -> {}", pair.key, pair.val)); } println!("{:?}", res); } } /* 添加操作 */ fn put(&mut self, key: i32, val: String) { // 当负载因子超过阈值时,执行扩容 if self.load_factor() > self.load_thres { self.extend(); } let index = self.hash_func(key); // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 for pair in self.buckets[index].iter_mut() { if pair.key == key { pair.val = val; return; } } // 若无该 key ,则将键值对添加至尾部 let pair = Pair { key, val }; self.buckets[index].push(pair); self.size += 1; } /* 查询操作 */ fn get(&self, key: i32) -> Option<&str> { let index = self.hash_func(key); // 遍历桶,若找到 key ,则返回对应 val for pair in self.buckets[index].iter() { if pair.key == key { return Some(&pair.val); } } // 若未找到 key ,则返回 None None } } /* Driver Code */ pub fn main() { /* 初始化哈希表 */ let mut map = HashMapChaining::new(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(12836, "小哈".to_string()); map.put(15937, "小啰".to_string()); map.put(16750, "小算".to_string()); map.put(13276, "小法".to_string()); map.put(10583, "小鸭".to_string()); println!("\n添加完成后,哈希表为\nKey -> Value"); map.print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value println!( "\n输入学号 13276,查询到姓名 {}", match map.get(13276) { Some(value) => value, None => "Not a valid Key", } ); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(12836); println!("\n删除 12836 后,哈希表为\nKey -> Value"); map.print(); } ================================================ FILE: codes/rust/chapter_hashing/hash_map_open_addressing.rs ================================================ /* * File: hash_map_open_addressing.rs * Created Time: 2023-07-16 * Author: WSL0809 (wslzzy@outlook.com), night-cruise (2586447362@qq.com) */ #![allow(non_snake_case)] #![allow(unused)] mod array_hash_map; use array_hash_map::Pair; /* 开放寻址哈希表 */ struct HashMapOpenAddressing { size: usize, // 键值对数量 capacity: usize, // 哈希表容量 load_thres: f64, // 触发扩容的负载因子阈值 extend_ratio: usize, // 扩容倍数 buckets: Vec>, // 桶数组 TOMBSTONE: Option, // 删除标记 } impl HashMapOpenAddressing { /* 构造方法 */ fn new() -> Self { Self { size: 0, capacity: 4, load_thres: 2.0 / 3.0, extend_ratio: 2, buckets: vec![None; 4], TOMBSTONE: Some(Pair { key: -1, val: "-1".to_string(), }), } } /* 哈希函数 */ fn hash_func(&self, key: i32) -> usize { (key % self.capacity as i32) as usize } /* 负载因子 */ fn load_factor(&self) -> f64 { self.size as f64 / self.capacity as f64 } /* 搜索 key 对应的桶索引 */ fn find_bucket(&mut self, key: i32) -> usize { let mut index = self.hash_func(key); let mut first_tombstone = -1; // 线性探测,当遇到空桶时跳出 while self.buckets[index].is_some() { // 若遇到 key,返回对应的桶索引 if self.buckets[index].as_ref().unwrap().key == key { // 若之前遇到了删除标记,则将建值对移动至该索引 if first_tombstone != -1 { self.buckets[first_tombstone as usize] = self.buckets[index].take(); self.buckets[index] = self.TOMBSTONE.clone(); return first_tombstone as usize; // 返回移动后的桶索引 } return index; // 返回桶索引 } // 记录遇到的首个删除标记 if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE { first_tombstone = index as i32; } // 计算桶索引,越过尾部则返回头部 index = (index + 1) % self.capacity; } // 若 key 不存在,则返回添加点的索引 if first_tombstone == -1 { index } else { first_tombstone as usize } } /* 查询操作 */ fn get(&mut self, key: i32) -> Option<&str> { // 搜索 key 对应的桶索引 let index = self.find_bucket(key); // 若找到键值对,则返回对应 val if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { return self.buckets[index].as_ref().map(|pair| &pair.val as &str); } // 若键值对不存在,则返回 null None } /* 添加操作 */ fn put(&mut self, key: i32, val: String) { // 当负载因子超过阈值时,执行扩容 if self.load_factor() > self.load_thres { self.extend(); } // 搜索 key 对应的桶索引 let index = self.find_bucket(key); // 若找到键值对,则覆盖 val 并返回 if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { self.buckets[index].as_mut().unwrap().val = val; return; } // 若键值对不存在,则添加该键值对 self.buckets[index] = Some(Pair { key, val }); self.size += 1; } /* 删除操作 */ fn remove(&mut self, key: i32) { // 搜索 key 对应的桶索引 let index = self.find_bucket(key); // 若找到键值对,则用删除标记覆盖它 if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { self.buckets[index] = self.TOMBSTONE.clone(); self.size -= 1; } } /* 扩容哈希表 */ fn extend(&mut self) { // 暂存原哈希表 let buckets_tmp = self.buckets.clone(); // 初始化扩容后的新哈希表 self.capacity *= self.extend_ratio; self.buckets = vec![None; self.capacity]; self.size = 0; // 将键值对从原哈希表搬运至新哈希表 for pair in buckets_tmp { if pair.is_none() || pair == self.TOMBSTONE { continue; } let pair = pair.unwrap(); self.put(pair.key, pair.val); } } /* 打印哈希表 */ fn print(&self) { for pair in &self.buckets { if pair.is_none() { println!("null"); } else if pair == &self.TOMBSTONE { println!("TOMBSTONE"); } else { let pair = pair.as_ref().unwrap(); println!("{} -> {}", pair.key, pair.val); } } } } /* Driver Code */ fn main() { /* 初始化哈希表 */ let mut hashmap = HashMapOpenAddressing::new(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) hashmap.put(12836, "小哈".to_string()); hashmap.put(15937, "小啰".to_string()); hashmap.put(16750, "小算".to_string()); hashmap.put(13276, "小法".to_string()); hashmap.put(10583, "小鸭".to_string()); println!("\n添加完成后,哈希表为\nKey -> Value"); hashmap.print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 val let name = hashmap.get(13276).unwrap(); println!("\n输入学号 13276 ,查询到姓名 {}", name); /* 删除操作 */ // 在哈希表中删除键值对 (key, val) hashmap.remove(16750); println!("\n删除 16750 后,哈希表为\nKey -> Value"); hashmap.print(); } ================================================ FILE: codes/rust/chapter_hashing/simple_hash.rs ================================================ /* * File: simple_hash.rs * Created Time: 2023-09-07 * Author: night-cruise (2586447362@qq.com) */ /* 加法哈希 */ fn add_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = (hash + c as i64) % MODULUS; } hash as i32 } /* 乘法哈希 */ fn mul_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = (31 * hash + c as i64) % MODULUS; } hash as i32 } /* 异或哈希 */ fn xor_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash ^= c as i64; } (hash & MODULUS) as i32 } /* 旋转哈希 */ fn rot_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS; } hash as i32 } /* Driver Code */ fn main() { let key = "Hello 算法"; let hash = add_hash(key); println!("加法哈希值为 {hash}"); let hash = mul_hash(key); println!("乘法哈希值为 {hash}"); let hash = xor_hash(key); println!("异或哈希值为 {hash}"); let hash = rot_hash(key); println!("旋转哈希值为 {hash}"); } ================================================ FILE: codes/rust/chapter_heap/heap.rs ================================================ /* * File: heap.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::{cmp::Reverse, collections::BinaryHeap}; fn test_push_max(heap: &mut BinaryHeap, val: i32) { heap.push(val); // 元素入堆 println!("\n元素 {} 入堆后", val); print_util::print_heap(heap.iter().map(|&val| val).collect()); } fn test_pop_max(heap: &mut BinaryHeap) { let val = heap.pop().unwrap(); println!("\n堆顶元素 {} 出堆后", val); print_util::print_heap(heap.iter().map(|&val| val).collect()); } /* Driver Code */ fn main() { /* 初始化堆 */ // 初始化小顶堆 #[allow(unused_assignments)] let mut min_heap = BinaryHeap::new(); // Rust 的 BinaryHeap 是大顶堆,小顶堆一般会“套上”Reverse // 初始化大顶堆 let mut max_heap = BinaryHeap::new(); println!("\n以下测试样例为大顶堆"); /* 元素入堆 */ test_push_max(&mut max_heap, 1); test_push_max(&mut max_heap, 3); test_push_max(&mut max_heap, 2); test_push_max(&mut max_heap, 5); test_push_max(&mut max_heap, 4); /* 获取堆顶元素 */ let peek = max_heap.peek().unwrap(); println!("\n堆顶元素为 {}", peek); /* 堆顶元素出堆 */ test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); /* 获取堆大小 */ let size = max_heap.len(); println!("\n堆元素数量为 {}", size); /* 判断堆是否为空 */ let is_empty = max_heap.is_empty(); println!("\n堆是否为空 {}", is_empty); /* 输入列表并建堆 */ // 时间复杂度为 O(n) ,而非 O(nlogn) min_heap = BinaryHeap::from( vec![1, 3, 2, 5, 4] .into_iter() .map(|val| Reverse(val)) .collect::>>(), ); println!("\n输入列表并建立小顶堆后"); print_util::print_heap(min_heap.iter().map(|&val| val.0).collect()); } ================================================ FILE: codes/rust/chapter_heap/my_heap.rs ================================================ /* * File: my_heap.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* 大顶堆 */ struct MaxHeap { // 使用 vector 而非数组,这样无须考虑扩容问题 max_heap: Vec, } impl MaxHeap { /* 构造方法,根据输入列表建堆 */ fn new(nums: Vec) -> Self { // 将列表元素原封不动添加进堆 let mut heap = MaxHeap { max_heap: nums }; // 堆化除叶节点以外的其他所有节点 for i in (0..=Self::parent(heap.size() - 1)).rev() { heap.sift_down(i); } heap } /* 获取左子节点的索引 */ fn left(i: usize) -> usize { 2 * i + 1 } /* 获取右子节点的索引 */ fn right(i: usize) -> usize { 2 * i + 2 } /* 获取父节点的索引 */ fn parent(i: usize) -> usize { (i - 1) / 2 // 向下整除 } /* 交换元素 */ fn swap(&mut self, i: usize, j: usize) { self.max_heap.swap(i, j); } /* 获取堆大小 */ fn size(&self) -> usize { self.max_heap.len() } /* 判断堆是否为空 */ fn is_empty(&self) -> bool { self.max_heap.is_empty() } /* 访问堆顶元素 */ fn peek(&self) -> Option { self.max_heap.first().copied() } /* 元素入堆 */ fn push(&mut self, val: i32) { // 添加节点 self.max_heap.push(val); // 从底至顶堆化 self.sift_up(self.size() - 1); } /* 从节点 i 开始,从底至顶堆化 */ fn sift_up(&mut self, mut i: usize) { loop { // 节点 i 已经是堆顶节点了,结束堆化 if i == 0 { break; } // 获取节点 i 的父节点 let p = Self::parent(i); // 当“节点无须修复”时,结束堆化 if self.max_heap[i] <= self.max_heap[p] { break; } // 交换两节点 self.swap(i, p); // 循环向上堆化 i = p; } } /* 元素出堆 */ fn pop(&mut self) -> i32 { // 判空处理 if self.is_empty() { panic!("index out of bounds"); } // 交换根节点与最右叶节点(交换首元素与尾元素) self.swap(0, self.size() - 1); // 删除节点 let val = self.max_heap.pop().unwrap(); // 从顶至底堆化 self.sift_down(0); // 返回堆顶元素 val } /* 从节点 i 开始,从顶至底堆化 */ fn sift_down(&mut self, mut i: usize) { loop { // 判断节点 i, l, r 中值最大的节点,记为 ma let (l, r, mut ma) = (Self::left(i), Self::right(i), i); if l < self.size() && self.max_heap[l] > self.max_heap[ma] { ma = l; } if r < self.size() && self.max_heap[r] > self.max_heap[ma] { ma = r; } // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if ma == i { break; } // 交换两节点 self.swap(i, ma); // 循环向下堆化 i = ma; } } /* 打印堆(二叉树) */ fn print(&self) { print_util::print_heap(self.max_heap.clone()); } } /* Driver Code */ fn main() { /* 初始化大顶堆 */ let mut max_heap = MaxHeap::new(vec![9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); println!("\n输入列表并建堆后"); max_heap.print(); /* 获取堆顶元素 */ let peek = max_heap.peek(); if let Some(peek) = peek { println!("\n堆顶元素为 {}", peek); } /* 元素入堆 */ let val = 7; max_heap.push(val); println!("\n元素 {} 入堆后", val); max_heap.print(); /* 堆顶元素出堆 */ let peek = max_heap.pop(); println!("\n堆顶元素 {} 出堆后", peek); max_heap.print(); /* 获取堆大小 */ let size = max_heap.size(); println!("\n堆元素数量为 {}", size); /* 判断堆是否为空 */ let is_empty = max_heap.is_empty(); println!("\n堆是否为空 {}", is_empty); } ================================================ FILE: codes/rust/chapter_heap/top_k.rs ================================================ /* * File: top_k.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::cmp::Reverse; use std::collections::BinaryHeap; /* 基于堆查找数组中最大的 k 个元素 */ fn top_k_heap(nums: Vec, k: usize) -> BinaryHeap> { // BinaryHeap 是大顶堆,使用 Reverse 将元素取反,从而实现小顶堆 let mut heap = BinaryHeap::>::new(); // 将数组的前 k 个元素入堆 for &num in nums.iter().take(k) { heap.push(Reverse(num)); } // 从第 k+1 个元素开始,保持堆的长度为 k for &num in nums.iter().skip(k) { // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 if num > heap.peek().unwrap().0 { heap.pop(); heap.push(Reverse(num)); } } heap } /* Driver Code */ fn main() { let nums = vec![1, 7, 6, 3, 2]; let k = 3; let res = top_k_heap(nums, k); println!("最大的 {} 个元素为", k); print_util::print_heap(res.into_iter().map(|item| item.0).collect()); } ================================================ FILE: codes/rust/chapter_searching/binary_search.rs ================================================ /* * File: binary_search.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ /* 二分查找(双闭区间) */ fn binary_search(nums: &[i32], target: i32) -> i32 { // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 let mut i = 0; let mut j = nums.len() as i32 - 1; // 循环,当搜索区间为空时跳出(当 i > j 时为空) while i <= j { let m = i + (j - i) / 2; // 计算中点索引 m if nums[m as usize] < target { // 此情况说明 target 在区间 [m+1, j] 中 i = m + 1; } else if nums[m as usize] > target { // 此情况说明 target 在区间 [i, m-1] 中 j = m - 1; } else { // 找到目标元素,返回其索引 return m; } } // 未找到目标元素,返回 -1 return -1; } /* 二分查找(左闭右开区间) */ fn binary_search_lcro(nums: &[i32], target: i32) -> i32 { // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 let mut i = 0; let mut j = nums.len() as i32; // 循环,当搜索区间为空时跳出(当 i = j 时为空) while i < j { let m = i + (j - i) / 2; // 计算中点索引 m if nums[m as usize] < target { // 此情况说明 target 在区间 [m+1, j) 中 i = m + 1; } else if nums[m as usize] > target { // 此情况说明 target 在区间 [i, m) 中 j = m; } else { // 找到目标元素,返回其索引 return m; } } // 未找到目标元素,返回 -1 return -1; } /* Driver Code */ pub fn main() { let target = 6; let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分查找(双闭区间) let mut index = binary_search(&nums, target); println!("目标元素 6 的索引 = {index}"); // 二分查找(左闭右开区间) index = binary_search_lcro(&nums, target); println!("目标元素 6 的索引 = {index}"); } ================================================ FILE: codes/rust/chapter_searching/binary_search_edge.rs ================================================ /* * File: binary_search_edge.rs * Created Time: 2023-08-30 * Author: night-cruise (2586447362@qq.com) */ mod binary_search_insertion; use binary_search_insertion::binary_search_insertion; /* 二分查找最左一个 target */ fn binary_search_left_edge(nums: &[i32], target: i32) -> i32 { // 等价于查找 target 的插入点 let i = binary_search_insertion(nums, target); // 未找到 target ,返回 -1 if i == nums.len() as i32 || nums[i as usize] != target { return -1; } // 找到 target ,返回索引 i i } /* 二分查找最右一个 target */ fn binary_search_right_edge(nums: &[i32], target: i32) -> i32 { // 转化为查找最左一个 target + 1 let i = binary_search_insertion(nums, target + 1); // j 指向最右一个 target ,i 指向首个大于 target 的元素 let j = i - 1; // 未找到 target ,返回 -1 if j == -1 || nums[j as usize] != target { return -1; } // 找到 target ,返回索引 j j } /* Driver Code */ fn main() { // 包含重复元素的数组 let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; println!("\n数组 nums = {:?}", nums); // 二分查找左边界和右边界 for target in [6, 7] { let index = binary_search_left_edge(&nums, target); println!("最左一个元素 {} 的索引为 {}", target, index); let index = binary_search_right_edge(&nums, target); println!("最右一个元素 {} 的索引为 {}", target, index); } } ================================================ FILE: codes/rust/chapter_searching/binary_search_insertion.rs ================================================ /* * File: binary_search_insertion.rs * Created Time: 2023-08-30 * Author: night-cruise (2586447362@qq.com) */ #![allow(unused)] /* 二分查找插入点(无重复元素) */ fn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 { let (mut i, mut j) = (0, nums.len() as i32 - 1); // 初始化双闭区间 [0, n-1] while i <= j { let m = i + (j - i) / 2; // 计算中点索引 m if nums[m as usize] < target { i = m + 1; // target 在区间 [m+1, j] 中 } else if nums[m as usize] > target { j = m - 1; // target 在区间 [i, m-1] 中 } else { return m; } } // 未找到 target ,返回插入点 i i } /* 二分查找插入点(存在重复元素) */ pub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 { let (mut i, mut j) = (0, nums.len() as i32 - 1); // 初始化双闭区间 [0, n-1] while i <= j { let m = i + (j - i) / 2; // 计算中点索引 m if nums[m as usize] < target { i = m + 1; // target 在区间 [m+1, j] 中 } else if nums[m as usize] > target { j = m - 1; // target 在区间 [i, m-1] 中 } else { j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 } } // 返回插入点 i i } /* Driver Code */ fn main() { // 无重复元素的数组 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; println!("\n数组 nums = {:?}", nums); // 二分查找插入点 for target in [6, 9] { let index = binary_search_insertion_simple(&nums, target); println!("元素 {} 的插入点的索引为 {}", target, index); } // 包含重复元素的数组 let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; println!("\n数组 nums = {:?}", nums); // 二分查找插入点 for target in [2, 6, 20] { let index = binary_search_insertion(&nums, target); println!("元素 {} 的插入点的索引为 {}", target, index); } } ================================================ FILE: codes/rust/chapter_searching/hashing_search.rs ================================================ /* * File: hashing_search.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::ListNode; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; /* 哈希查找(数组) */ fn hashing_search_array<'a>(map: &'a HashMap, target: i32) -> Option<&'a usize> { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 None map.get(&target) } /* 哈希查找(链表) */ fn hashing_search_linked_list( map: &HashMap>>>, target: i32, ) -> Option<&Rc>>> { // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 None map.get(&target) } /* Driver Code */ pub fn main() { let target = 3; /* 哈希查找(数组) */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // 初始化哈希表 let mut map = HashMap::new(); for (i, num) in nums.iter().enumerate() { map.insert(*num, i); // key: 元素,value: 索引 } let index = hashing_search_array(&map, target); println!("目标元素 3 的索引 = {}", index.unwrap()); /* 哈希查找(链表) */ let head = ListNode::arr_to_linked_list(&nums); // 初始化哈希表 // let mut map1 = HashMap::new(); let map1 = ListNode::linked_list_to_hashmap(head); let node = hashing_search_linked_list(&map1, target); println!("目标节点值 3 的对应节点对象为 {:?}", node); } ================================================ FILE: codes/rust/chapter_searching/linear_search.rs ================================================ /* * File: linear_search.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::ListNode; use std::cell::RefCell; use std::rc::Rc; /* 线性查找(数组) */ fn linear_search_array(nums: &[i32], target: i32) -> i32 { // 遍历数组 for (i, num) in nums.iter().enumerate() { // 找到目标元素,返回其索引 if num == &target { return i as i32; } } // 未找到目标元素,返回 -1 return -1; } /* 线性查找(链表) */ fn linear_search_linked_list( head: Rc>>, target: i32, ) -> Option>>> { // 找到目标节点,返回之 if head.borrow().val == target { return Some(head); }; // 找到目标节点,返回之 if let Some(node) = &head.borrow_mut().next { return linear_search_linked_list(node.clone(), target); } // 未找到目标节点,返回 None return None; } /* Driver Code */ pub fn main() { let target = 3; /* 在数组中执行线性查找 */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; let index = linear_search_array(&nums, target); println!("目标元素 3 的索引 = {}", index); /* 在链表中执行线性查找 */ let head = ListNode::arr_to_linked_list(&nums); let node = linear_search_linked_list(head.unwrap(), target); println!("目标节点值 3 的对应节点对象为 {:?}", node); } ================================================ FILE: codes/rust/chapter_searching/two_sum.rs ================================================ /* * File: two_sum.rs * Created Time: 2023-01-14 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::collections::HashMap; /* 方法一:暴力枚举 */ pub fn two_sum_brute_force(nums: &Vec, target: i32) -> Option> { let size = nums.len(); // 两层循环,时间复杂度为 O(n^2) for i in 0..size - 1 { for j in i + 1..size { if nums[i] + nums[j] == target { return Some(vec![i as i32, j as i32]); } } } None } /* 方法二:辅助哈希表 */ pub fn two_sum_hash_table(nums: &Vec, target: i32) -> Option> { // 辅助哈希表,空间复杂度为 O(n) let mut dic = HashMap::new(); // 单层循环,时间复杂度为 O(n) for (i, num) in nums.iter().enumerate() { match dic.get(&(target - num)) { Some(v) => return Some(vec![*v as i32, i as i32]), None => dic.insert(num, i as i32), }; } None } fn main() { // ======= Test Case ======= let nums = vec![2, 7, 11, 15]; let target = 13; // ====== Driver Code ====== // 方法一 let res = two_sum_brute_force(&nums, target).unwrap(); print!("方法一 res = "); print_util::print_array(&res); // 方法二 let res = two_sum_hash_table(&nums, target).unwrap(); print!("\n方法二 res = "); print_util::print_array(&res); } ================================================ FILE: codes/rust/chapter_sorting/bubble_sort.rs ================================================ /* * File: bubble_sort.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* 冒泡排序 */ fn bubble_sort(nums: &mut [i32]) { // 外循环:未排序区间为 [0, i] for i in (1..nums.len()).rev() { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in 0..i { if nums[j] > nums[j + 1] { // 交换 nums[j] 与 nums[j + 1] nums.swap(j, j + 1); } } } } /* 冒泡排序(标志优化) */ fn bubble_sort_with_flag(nums: &mut [i32]) { // 外循环:未排序区间为 [0, i] for i in (1..nums.len()).rev() { let mut flag = false; // 初始化标志位 // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in 0..i { if nums[j] > nums[j + 1] { // 交换 nums[j] 与 nums[j + 1] nums.swap(j, j + 1); flag = true; // 记录交换元素 } } if !flag { break; // 此轮“冒泡”未交换任何元素,直接跳出 }; } } /* Driver Code */ pub fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; bubble_sort(&mut nums); print!("冒泡排序完成后 nums = "); print_util::print_array(&nums); let mut nums1 = [4, 1, 3, 1, 5, 2]; bubble_sort_with_flag(&mut nums1); print!("\n冒泡排序完成后 nums1 = "); print_util::print_array(&nums1); } ================================================ FILE: codes/rust/chapter_sorting/bucket_sort.rs ================================================ /* * File: bucket_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* 桶排序 */ fn bucket_sort(nums: &mut [f64]) { // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 let k = nums.len() / 2; let mut buckets = vec![vec![]; k]; // 1. 将数组元素分配到各个桶中 for &num in nums.iter() { // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] let i = (num * k as f64) as usize; // 将 num 添加进桶 i buckets[i].push(num); } // 2. 对各个桶执行排序 for bucket in &mut buckets { // 使用内置排序函数,也可以替换成其他排序算法 bucket.sort_by(|a, b| a.partial_cmp(b).unwrap()); } // 3. 遍历桶合并结果 let mut i = 0; for bucket in buckets.iter() { for &num in bucket.iter() { nums[i] = num; i += 1; } } } /* Driver Code */ fn main() { // 设输入数据为浮点数,范围为 [0, 1) let mut nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucket_sort(&mut nums); print!("桶排序完成后 nums = "); print_util::print_array(&nums); } ================================================ FILE: codes/rust/chapter_sorting/counting_sort.rs ================================================ /* * File: counting_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* 计数排序 */ // 简单实现,无法用于排序对象 fn counting_sort_naive(nums: &mut [i32]) { // 1. 统计数组最大元素 m let m = *nums.iter().max().unwrap(); // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 let mut counter = vec![0; m as usize + 1]; for &num in nums.iter() { counter[num as usize] += 1; } // 3. 遍历 counter ,将各元素填入原数组 nums let mut i = 0; for num in 0..m + 1 { for _ in 0..counter[num as usize] { nums[i] = num; i += 1; } } } /* 计数排序 */ // 完整实现,可排序对象,并且是稳定排序 fn counting_sort(nums: &mut [i32]) { // 1. 统计数组最大元素 m let m = *nums.iter().max().unwrap() as usize; // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 let mut counter = vec![0; m + 1]; for &num in nums.iter() { counter[num as usize] += 1; } // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 for i in 0..m { counter[i + 1] += counter[i]; } // 4. 倒序遍历 nums ,将各元素填入结果数组 res // 初始化数组 res 用于记录结果 let n = nums.len(); let mut res = vec![0; n]; for i in (0..n).rev() { let num = nums[i]; res[counter[num as usize] - 1] = num; // 将 num 放置到对应索引处 counter[num as usize] -= 1; // 令前缀和自减 1 ,得到下次放置 num 的索引 } // 使用结果数组 res 覆盖原数组 nums nums.copy_from_slice(&res) } /* Driver Code */ fn main() { let mut nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; counting_sort_naive(&mut nums); print!("计数排序(无法排序对象)完成后 nums = "); print_util::print_array(&nums); let mut nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; counting_sort(&mut nums1); print!("\n计数排序完成后 nums1 = "); print_util::print_array(&nums1); } ================================================ FILE: codes/rust/chapter_sorting/heap_sort.rs ================================================ /* * File: heap_sort.rs * Created Time: 2023-07-04 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ fn sift_down(nums: &mut [i32], n: usize, mut i: usize) { loop { // 判断节点 i, l, r 中值最大的节点,记为 ma let l = 2 * i + 1; let r = 2 * i + 2; let mut ma = i; if l < n && nums[l] > nums[ma] { ma = l; } if r < n && nums[r] > nums[ma] { ma = r; } // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if ma == i { break; } // 交换两节点 nums.swap(i, ma); // 循环向下堆化 i = ma; } } /* 堆排序 */ fn heap_sort(nums: &mut [i32]) { // 建堆操作:堆化除叶节点以外的其他所有节点 for i in (0..nums.len() / 2).rev() { sift_down(nums, nums.len(), i); } // 从堆中提取最大元素,循环 n-1 轮 for i in (1..nums.len()).rev() { // 交换根节点与最右叶节点(交换首元素与尾元素) nums.swap(0, i); // 以根节点为起点,从顶至底进行堆化 sift_down(nums, i, 0); } } /* Driver Code */ fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; heap_sort(&mut nums); print!("堆排序完成后 nums = "); print_util::print_array(&nums); } ================================================ FILE: codes/rust/chapter_sorting/insertion_sort.rs ================================================ /* * File: insertion_sort.rs * Created Time: 2023-02-13 * Author: xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; /* 插入排序 */ fn insertion_sort(nums: &mut [i32]) { // 外循环:已排序区间为 [0, i-1] for i in 1..nums.len() { let (base, mut j) = (nums[i], (i - 1) as i32); // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while j >= 0 && nums[j as usize] > base { nums[(j + 1) as usize] = nums[j as usize]; // 将 nums[j] 向右移动一位 j -= 1; } nums[(j + 1) as usize] = base; // 将 base 赋值到正确位置 } } /* Driver Code */ fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; insertion_sort(&mut nums); print!("插入排序完成后 nums = "); print_util::print_array(&nums); } ================================================ FILE: codes/rust/chapter_sorting/merge_sort.rs ================================================ /** * File: merge_sort.rs * Created Time: 2023-02-14 * Author: xBLACKICEx (xBLACKICEx@outlook.com) */ /* 合并左子数组和右子数组 */ fn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) { // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] // 创建一个临时数组 tmp ,用于存放合并后的结果 let tmp_size = right - left + 1; let mut tmp = vec![0; tmp_size]; // 初始化左子数组和右子数组的起始索引 let (mut i, mut j, mut k) = (left, mid + 1, 0); // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 while i <= mid && j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i]; i += 1; } else { tmp[k] = nums[j]; j += 1; } k += 1; } // 将左子数组和右子数组的剩余元素复制到临时数组中 while i <= mid { tmp[k] = nums[i]; k += 1; i += 1; } while j <= right { tmp[k] = nums[j]; k += 1; j += 1; } // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 for k in 0..tmp_size { nums[left + k] = tmp[k]; } } /* 归并排序 */ fn merge_sort(nums: &mut [i32], left: usize, right: usize) { // 终止条件 if left >= right { return; // 当子数组长度为 1 时终止递归 } // 划分阶段 let mid = left + (right - left) / 2; // 计算中点 merge_sort(nums, left, mid); // 递归左子数组 merge_sort(nums, mid + 1, right); // 递归右子数组 // 合并阶段 merge(nums, left, mid, right); } /* Driver Code */ fn main() { /* 归并排序 */ let mut nums = [7, 3, 2, 6, 0, 1, 5, 4]; let right = nums.len() - 1; merge_sort(&mut nums, 0, right); println!("归并排序完成后 nums = {:?}", nums); } ================================================ FILE: codes/rust/chapter_sorting/quick_sort.rs ================================================ /** * File: quick_sort.rs * Created Time: 2023-02-16 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ /* 快速排序 */ struct QuickSort; impl QuickSort { /* 哨兵划分 */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // 以 nums[left] 为基准数 let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // 从右向左找首个小于基准数的元素 } while i < j && nums[i] <= nums[left] { i += 1; // 从左向右找首个大于基准数的元素 } nums.swap(i, j); // 交换这两个元素 } nums.swap(i, left); // 将基准数交换至两子数组的分界线 i // 返回基准数的索引 } /* 快速排序 */ pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { // 子数组长度为 1 时终止递归 if left >= right { return; } // 哨兵划分 let pivot = Self::partition(nums, left as usize, right as usize) as i32; // 递归左子数组、右子数组 Self::quick_sort(left, pivot - 1, nums); Self::quick_sort(pivot + 1, right, nums); } } /* 快速排序(中位基准数优化) */ struct QuickSortMedian; impl QuickSortMedian { /* 选取三个候选元素的中位数 */ fn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize { let (l, m, r) = (nums[left], nums[mid], nums[right]); if (l <= m && m <= r) || (r <= m && m <= l) { return mid; // m 在 l 和 r 之间 } if (m <= l && l <= r) || (r <= l && l <= m) { return left; // l 在 m 和 r 之间 } right } /* 哨兵划分(三数取中值) */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // 选取三个候选元素的中位数 let med = Self::median_three(nums, left, (left + right) / 2, right); // 将中位数交换至数组最左端 nums.swap(left, med); // 以 nums[left] 为基准数 let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // 从右向左找首个小于基准数的元素 } while i < j && nums[i] <= nums[left] { i += 1; // 从左向右找首个大于基准数的元素 } nums.swap(i, j); // 交换这两个元素 } nums.swap(i, left); // 将基准数交换至两子数组的分界线 i // 返回基准数的索引 } /* 快速排序 */ pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { // 子数组长度为 1 时终止递归 if left >= right { return; } // 哨兵划分 let pivot = Self::partition(nums, left as usize, right as usize) as i32; // 递归左子数组、右子数组 Self::quick_sort(left, pivot - 1, nums); Self::quick_sort(pivot + 1, right, nums); } } /* 快速排序(递归深度优化) */ struct QuickSortTailCall; impl QuickSortTailCall { /* 哨兵划分 */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // 以 nums[left] 为基准数 let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // 从右向左找首个小于基准数的元素 } while i < j && nums[i] <= nums[left] { i += 1; // 从左向右找首个大于基准数的元素 } nums.swap(i, j); // 交换这两个元素 } nums.swap(i, left); // 将基准数交换至两子数组的分界线 i // 返回基准数的索引 } /* 快速排序(递归深度优化) */ pub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) { // 子数组长度为 1 时终止 while left < right { // 哨兵划分操作 let pivot = Self::partition(nums, left as usize, right as usize) as i32; // 对两个子数组中较短的那个执行快速排序 if pivot - left < right - pivot { Self::quick_sort(left, pivot - 1, nums); // 递归排序左子数组 left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] } else { Self::quick_sort(pivot + 1, right, nums); // 递归排序右子数组 right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] } } } } /* Driver Code */ fn main() { /* 快速排序 */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSort::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("快速排序完成后 nums = {:?}", nums); /* 快速排序(中位基准数优化) */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSortMedian::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("快速排序(中位基准数优化)完成后 nums = {:?}", nums); /* 快速排序(递归深度优化) */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSortTailCall::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("快速排序(递归深度优化)完成后 nums = {:?}", nums); } ================================================ FILE: codes/rust/chapter_sorting/radix_sort.rs ================================================ /* * File: radix_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ fn digit(num: i32, exp: i32) -> usize { // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 return ((num / exp) % 10) as usize; } /* 计数排序(根据 nums 第 k 位排序) */ fn counting_sort_digit(nums: &mut [i32], exp: i32) { // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 let mut counter = [0; 10]; let n = nums.len(); // 统计 0~9 各数字的出现次数 for i in 0..n { let d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d counter[d] += 1; // 统计数字 d 的出现次数 } // 求前缀和,将“出现个数”转换为“数组索引” for i in 1..10 { counter[i] += counter[i - 1]; } // 倒序遍历,根据桶内统计结果,将各元素填入 res let mut res = vec![0; n]; for i in (0..n).rev() { let d = digit(nums[i], exp); let j = counter[d] - 1; // 获取 d 在数组中的索引 j res[j] = nums[i]; // 将当前元素填入索引 j counter[d] -= 1; // 将 d 的数量减 1 } // 使用结果覆盖原数组 nums nums.copy_from_slice(&res); } /* 基数排序 */ fn radix_sort(nums: &mut [i32]) { // 获取数组的最大元素,用于判断最大位数 let m = *nums.into_iter().max().unwrap(); // 按照从低位到高位的顺序遍历 let mut exp = 1; while exp <= m { counting_sort_digit(nums, exp); exp *= 10; } } /* Driver Code */ fn main() { // 基数排序 let mut nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radix_sort(&mut nums); print!("基数排序完成后 nums = "); print_util::print_array(&nums); } ================================================ FILE: codes/rust/chapter_sorting/selection_sort.rs ================================================ /* * File: selection_sort.rs * Created Time: 2023-05-30 * Author: WSL0809 (wslzzy@outlook.com) */ use hello_algo_rust::include::print_util; /* 选择排序 */ fn selection_sort(nums: &mut [i32]) { if nums.is_empty() { return; } let n = nums.len(); // 外循环:未排序区间为 [i, n-1] for i in 0..n - 1 { // 内循环:找到未排序区间内的最小元素 let mut k = i; for j in i + 1..n { if nums[j] < nums[k] { k = j; // 记录最小元素的索引 } } // 将该最小元素与未排序区间的首个元素交换 nums.swap(i, k); } } /* Driver Code */ pub fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; selection_sort(&mut nums); print!("\n选择排序完成后 nums = "); print_util::print_array(&nums); } ================================================ FILE: codes/rust/chapter_stack_and_queue/array_deque.rs ================================================ /* * File: array_deque.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* 基于环形数组实现的双向队列 */ struct ArrayDeque { nums: Vec, // 用于存储双向队列元素的数组 front: usize, // 队首指针,指向队首元素 que_size: usize, // 双向队列长度 } impl ArrayDeque { /* 构造方法 */ pub fn new(capacity: usize) -> Self { Self { nums: vec![T::default(); capacity], front: 0, que_size: 0, } } /* 获取双向队列的容量 */ pub fn capacity(&self) -> usize { self.nums.len() } /* 获取双向队列的长度 */ pub fn size(&self) -> usize { self.que_size } /* 判断双向队列是否为空 */ pub fn is_empty(&self) -> bool { self.que_size == 0 } /* 计算环形数组索引 */ fn index(&self, i: i32) -> usize { // 通过取余操作实现数组首尾相连 // 当 i 越过数组尾部后,回到头部 // 当 i 越过数组头部后,回到尾部 ((i + self.capacity() as i32) % self.capacity() as i32) as usize } /* 队首入队 */ pub fn push_first(&mut self, num: T) { if self.que_size == self.capacity() { println!("双向队列已满"); return; } // 队首指针向左移动一位 // 通过取余操作实现 front 越过数组头部后回到尾部 self.front = self.index(self.front as i32 - 1); // 将 num 添加至队首 self.nums[self.front] = num; self.que_size += 1; } /* 队尾入队 */ pub fn push_last(&mut self, num: T) { if self.que_size == self.capacity() { println!("双向队列已满"); return; } // 计算队尾指针,指向队尾索引 + 1 let rear = self.index(self.front as i32 + self.que_size as i32); // 将 num 添加至队尾 self.nums[rear] = num; self.que_size += 1; } /* 队首出队 */ fn pop_first(&mut self) -> T { let num = self.peek_first(); // 队首指针向后移动一位 self.front = self.index(self.front as i32 + 1); self.que_size -= 1; num } /* 队尾出队 */ fn pop_last(&mut self) -> T { let num = self.peek_last(); self.que_size -= 1; num } /* 访问队首元素 */ fn peek_first(&self) -> T { if self.is_empty() { panic!("双向队列为空") }; self.nums[self.front] } /* 访问队尾元素 */ fn peek_last(&self) -> T { if self.is_empty() { panic!("双向队列为空") }; // 计算尾元素索引 let last = self.index(self.front as i32 + self.que_size as i32 - 1); self.nums[last] } /* 返回数组用于打印 */ fn to_array(&self) -> Vec { // 仅转换有效长度范围内的列表元素 let mut res = vec![T::default(); self.que_size]; let mut j = self.front; for i in 0..self.que_size { res[i] = self.nums[self.index(j as i32)]; j += 1; } res } } /* Driver Code */ fn main() { /* 初始化双向队列 */ let mut deque = ArrayDeque::new(10); deque.push_last(3); deque.push_last(2); deque.push_last(5); print!("双向队列 deque = "); print_util::print_array(&deque.to_array()); /* 访问元素 */ let peek_first = deque.peek_first(); print!("\n队首元素 peek_first = {}", peek_first); let peek_last = deque.peek_last(); print!("\n队尾元素 peek_last = {}", peek_last); /* 元素入队 */ deque.push_last(4); print!("\n元素 4 队尾入队后 deque = "); print_util::print_array(&deque.to_array()); deque.push_first(1); print!("\n元素 1 队首入队后 deque = "); print_util::print_array(&deque.to_array()); /* 元素出队 */ let pop_last = deque.pop_last(); print!("\n队尾出队元素 = {},队尾出队后 deque = ", pop_last); print_util::print_array(&deque.to_array()); let pop_first = deque.pop_first(); print!("\n队首出队元素 = {},队首出队后 deque = ", pop_first); print_util::print_array(&deque.to_array()); /* 获取双向队列的长度 */ let size = deque.size(); print!("\n双向队列长度 size = {}", size); /* 判断双向队列是否为空 */ let is_empty = deque.is_empty(); print!("\n双向队列是否为空 = {}", is_empty); } ================================================ FILE: codes/rust/chapter_stack_and_queue/array_queue.rs ================================================ /* * File: array_queue.rs * Created Time: 2023-02-06 * Author: WSL0809 (wslzzy@outlook.com) */ /* 基于环形数组实现的队列 */ struct ArrayQueue { nums: Vec, // 用于存储队列元素的数组 front: i32, // 队首指针,指向队首元素 que_size: i32, // 队列长度 que_capacity: i32, // 队列容量 } impl ArrayQueue { /* 构造方法 */ fn new(capacity: i32) -> ArrayQueue { ArrayQueue { nums: vec![T::default(); capacity as usize], front: 0, que_size: 0, que_capacity: capacity, } } /* 获取队列的容量 */ fn capacity(&self) -> i32 { self.que_capacity } /* 获取队列的长度 */ fn size(&self) -> i32 { self.que_size } /* 判断队列是否为空 */ fn is_empty(&self) -> bool { self.que_size == 0 } /* 入队 */ fn push(&mut self, num: T) { if self.que_size == self.capacity() { println!("队列已满"); return; } // 计算队尾指针,指向队尾索引 + 1 // 通过取余操作实现 rear 越过数组尾部后回到头部 let rear = (self.front + self.que_size) % self.que_capacity; // 将 num 添加至队尾 self.nums[rear as usize] = num; self.que_size += 1; } /* 出队 */ fn pop(&mut self) -> T { let num = self.peek(); // 队首指针向后移动一位,若越过尾部,则返回到数组头部 self.front = (self.front + 1) % self.que_capacity; self.que_size -= 1; num } /* 访问队首元素 */ fn peek(&self) -> T { if self.is_empty() { panic!("index out of bounds"); } self.nums[self.front as usize] } /* 返回数组 */ fn to_vector(&self) -> Vec { let cap = self.que_capacity; let mut j = self.front; let mut arr = vec![T::default(); cap as usize]; for i in 0..self.que_size { arr[i as usize] = self.nums[(j % cap) as usize]; j += 1; } arr } } /* Driver Code */ fn main() { /* 初始化队列 */ let capacity = 10; let mut queue = ArrayQueue::new(capacity); /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); println!("队列 queue = {:?}", queue.to_vector()); /* 访问队首元素 */ let peek = queue.peek(); println!("队首元素 peek = {}", peek); /* 元素出队 */ let pop = queue.pop(); println!( "出队元素 pop = {:?},出队后 queue = {:?}", pop, queue.to_vector() ); /* 获取队列的长度 */ let size = queue.size(); println!("队列长度 size = {}", size); /* 判断队列是否为空 */ let is_empty = queue.is_empty(); println!("队列是否为空 = {}", is_empty); /* 测试环形数组 */ for i in 0..10 { queue.push(i); queue.pop(); println!("第 {:?} 轮入队 + 出队后 queue = {:?}", i, queue.to_vector()); } } ================================================ FILE: codes/rust/chapter_stack_and_queue/array_stack.rs ================================================ /* * File: array_stack.rs * Created Time: 2023-02-05 * Author: WSL0809 (wslzzy@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* 基于数组实现的栈 */ struct ArrayStack { stack: Vec, } impl ArrayStack { /* 初始化栈 */ fn new() -> ArrayStack { ArrayStack:: { stack: Vec::::new(), } } /* 获取栈的长度 */ fn size(&self) -> usize { self.stack.len() } /* 判断栈是否为空 */ fn is_empty(&self) -> bool { self.size() == 0 } /* 入栈 */ fn push(&mut self, num: T) { self.stack.push(num); } /* 出栈 */ fn pop(&mut self) -> Option { self.stack.pop() } /* 访问栈顶元素 */ fn peek(&self) -> Option<&T> { if self.is_empty() { panic!("栈为空") }; self.stack.last() } /* 返回 &Vec */ fn to_array(&self) -> &Vec { &self.stack } } /* Driver Code */ fn main() { // 初始化栈 let mut stack = ArrayStack::::new(); // 元素入栈 stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("栈 stack = "); print_util::print_array(stack.to_array()); //访问栈顶元素 let peek = stack.peek().unwrap(); print!("\n栈顶元素 peek = {}", peek); // 元素出栈 let pop = stack.pop().unwrap(); print!("\n出栈元素 pop = {pop},出栈后 stack = "); print_util::print_array(stack.to_array()); // 获取栈的长度 let size = stack.size(); print!("\n栈的长度 size = {size}"); // 判断是否为空 let is_empty = stack.is_empty(); print!("\n栈是否为空 = {is_empty}"); } ================================================ FILE: codes/rust/chapter_stack_and_queue/deque.rs ================================================ /* * File: deque.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; use std::collections::VecDeque; /* Driver Code */ pub fn main() { // 初始化双向队列 let mut deque: VecDeque = VecDeque::new(); deque.push_back(3); deque.push_back(2); deque.push_back(5); print!("双向队列 deque = "); print_util::print_queue(&deque); // 访问元素 let peek_first = deque.front().unwrap(); print!("\n队首元素 peekFirst = {peek_first}"); let peek_last = deque.back().unwrap(); print!("\n队尾元素 peekLast = {peek_last}"); /* 元素入队 */ deque.push_back(4); print!("\n元素 4 队尾入队后 deque = "); print_util::print_queue(&deque); deque.push_front(1); print!("\n元素 1 队首入队后 deque = "); print_util::print_queue(&deque); // 元素出队 let pop_last = deque.pop_back().unwrap(); print!("\n队尾出队元素 = {pop_last},队尾出队后 deque = "); print_util::print_queue(&deque); let pop_first = deque.pop_front().unwrap(); print!("\n队首出队元素 = {pop_first},队首出队后 deque = "); print_util::print_queue(&deque); // 获取双向队列的长度 let size = deque.len(); print!("\n双向队列长度 size = {size}"); // 判断双向队列是否为空 let is_empty = deque.is_empty(); print!("\n双向队列是否为空 = {is_empty}"); } ================================================ FILE: codes/rust/chapter_stack_and_queue/linkedlist_deque.rs ================================================ /* * File: linkedlist_deque.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::cell::RefCell; use std::rc::Rc; /* 双向链表节点 */ pub struct ListNode { pub val: T, // 节点值 pub next: Option>>>, // 后继节点指针 pub prev: Option>>>, // 前驱节点指针 } impl ListNode { pub fn new(val: T) -> Rc>> { Rc::new(RefCell::new(ListNode { val, next: None, prev: None, })) } } /* 基于双向链表实现的双向队列 */ #[allow(dead_code)] pub struct LinkedListDeque { front: Option>>>, // 头节点 front rear: Option>>>, // 尾节点 rear que_size: usize, // 双向队列的长度 } impl LinkedListDeque { pub fn new() -> Self { Self { front: None, rear: None, que_size: 0, } } /* 获取双向队列的长度 */ pub fn size(&self) -> usize { return self.que_size; } /* 判断双向队列是否为空 */ pub fn is_empty(&self) -> bool { return self.que_size == 0; } /* 入队操作 */ fn push(&mut self, num: T, is_front: bool) { let node = ListNode::new(num); // 队首入队操作 if is_front { match self.front.take() { // 若链表为空,则令 front 和 rear 都指向 node None => { self.rear = Some(node.clone()); self.front = Some(node); } // 将 node 添加至链表头部 Some(old_front) => { old_front.borrow_mut().prev = Some(node.clone()); node.borrow_mut().next = Some(old_front); self.front = Some(node); // 更新头节点 } } } // 队尾入队操作 else { match self.rear.take() { // 若链表为空,则令 front 和 rear 都指向 node None => { self.front = Some(node.clone()); self.rear = Some(node); } // 将 node 添加至链表尾部 Some(old_rear) => { old_rear.borrow_mut().next = Some(node.clone()); node.borrow_mut().prev = Some(old_rear); self.rear = Some(node); // 更新尾节点 } } } self.que_size += 1; // 更新队列长度 } /* 队首入队 */ pub fn push_first(&mut self, num: T) { self.push(num, true); } /* 队尾入队 */ pub fn push_last(&mut self, num: T) { self.push(num, false); } /* 出队操作 */ fn pop(&mut self, is_front: bool) -> Option { // 若队列为空,直接返回 None if self.is_empty() { return None; }; // 队首出队操作 if is_front { self.front.take().map(|old_front| { match old_front.borrow_mut().next.take() { Some(new_front) => { new_front.borrow_mut().prev.take(); self.front = Some(new_front); // 更新头节点 } None => { self.rear.take(); } } self.que_size -= 1; // 更新队列长度 old_front.borrow().val }) } // 队尾出队操作 else { self.rear.take().map(|old_rear| { match old_rear.borrow_mut().prev.take() { Some(new_rear) => { new_rear.borrow_mut().next.take(); self.rear = Some(new_rear); // 更新尾节点 } None => { self.front.take(); } } self.que_size -= 1; // 更新队列长度 old_rear.borrow().val }) } } /* 队首出队 */ pub fn pop_first(&mut self) -> Option { return self.pop(true); } /* 队尾出队 */ pub fn pop_last(&mut self) -> Option { return self.pop(false); } /* 访问队首元素 */ pub fn peek_first(&self) -> Option<&Rc>>> { self.front.as_ref() } /* 访问队尾元素 */ pub fn peek_last(&self) -> Option<&Rc>>> { self.rear.as_ref() } /* 返回数组用于打印 */ pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { let mut res: Vec = Vec::new(); fn recur(cur: Option<&Rc>>>, res: &mut Vec) { if let Some(cur) = cur { res.push(cur.borrow().val); recur(cur.borrow().next.as_ref(), res); } } recur(head, &mut res); res } } /* Driver Code */ fn main() { /* 初始化双向队列 */ let mut deque = LinkedListDeque::new(); deque.push_last(3); deque.push_last(2); deque.push_last(5); print!("双向队列 deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); /* 访问元素 */ let peek_first = deque.peek_first().unwrap().borrow().val; print!("\n队首元素 peek_first = {}", peek_first); let peek_last = deque.peek_last().unwrap().borrow().val; print!("\n队尾元素 peek_last = {}", peek_last); /* 元素入队 */ deque.push_last(4); print!("\n元素 4 队尾入队后 deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); deque.push_first(1); print!("\n元素 1 队首入队后 deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); /* 元素出队 */ let pop_last = deque.pop_last().unwrap(); print!("\n队尾出队元素 = {},队尾出队后 deque = ", pop_last); print_util::print_array(&deque.to_array(deque.peek_first())); let pop_first = deque.pop_first().unwrap(); print!("\n队首出队元素 = {},队首出队后 deque = ", pop_first); print_util::print_array(&deque.to_array(deque.peek_first())); /* 获取双向队列的长度 */ let size = deque.size(); print!("\n双向队列长度 size = {}", size); /* 判断双向队列是否为空 */ let is_empty = deque.is_empty(); print!("\n双向队列是否为空 = {}", is_empty); } ================================================ FILE: codes/rust/chapter_stack_and_queue/linkedlist_queue.rs ================================================ /* * File: linkedlist_queue.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* 基于链表实现的队列 */ #[allow(dead_code)] pub struct LinkedListQueue { front: Option>>>, // 头节点 front rear: Option>>>, // 尾节点 rear que_size: usize, // 队列的长度 } impl LinkedListQueue { pub fn new() -> Self { Self { front: None, rear: None, que_size: 0, } } /* 获取队列的长度 */ pub fn size(&self) -> usize { return self.que_size; } /* 判断队列是否为空 */ pub fn is_empty(&self) -> bool { return self.que_size == 0; } /* 入队 */ pub fn push(&mut self, num: T) { // 在尾节点后添加 num let new_rear = ListNode::new(num); match self.rear.take() { // 如果队列不为空,则将该节点添加到尾节点后 Some(old_rear) => { old_rear.borrow_mut().next = Some(new_rear.clone()); self.rear = Some(new_rear); } // 如果队列为空,则令头、尾节点都指向该节点 None => { self.front = Some(new_rear.clone()); self.rear = Some(new_rear); } } self.que_size += 1; } /* 出队 */ pub fn pop(&mut self) -> Option { self.front.take().map(|old_front| { match old_front.borrow_mut().next.take() { Some(new_front) => { self.front = Some(new_front); } None => { self.rear.take(); } } self.que_size -= 1; old_front.borrow().val }) } /* 访问队首元素 */ pub fn peek(&self) -> Option<&Rc>>> { self.front.as_ref() } /* 将链表转化为 Array 并返回 */ pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { let mut res: Vec = Vec::new(); fn recur(cur: Option<&Rc>>>, res: &mut Vec) { if let Some(cur) = cur { res.push(cur.borrow().val); recur(cur.borrow().next.as_ref(), res); } } recur(head, &mut res); res } } /* Driver Code */ fn main() { /* 初始化队列 */ let mut queue = LinkedListQueue::new(); /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print!("队列 queue = "); print_util::print_array(&queue.to_array(queue.peek())); /* 访问队首元素 */ let peek = queue.peek().unwrap().borrow().val; print!("\n队首元素 peek = {}", peek); /* 元素出队 */ let pop = queue.pop().unwrap(); print!("\n出队元素 pop = {},出队后 queue = ", pop); print_util::print_array(&queue.to_array(queue.peek())); /* 获取队列的长度 */ let size = queue.size(); print!("\n队列长度 size = {}", size); /* 判断队列是否为空 */ let is_empty = queue.is_empty(); print!("\n队列是否为空 = {}", is_empty); } ================================================ FILE: codes/rust/chapter_stack_and_queue/linkedlist_stack.rs ================================================ /* * File: linkedlist_stack.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* 基于链表实现的栈 */ #[allow(dead_code)] pub struct LinkedListStack { stack_peek: Option>>>, // 将头节点作为栈顶 stk_size: usize, // 栈的长度 } impl LinkedListStack { pub fn new() -> Self { Self { stack_peek: None, stk_size: 0, } } /* 获取栈的长度 */ pub fn size(&self) -> usize { return self.stk_size; } /* 判断栈是否为空 */ pub fn is_empty(&self) -> bool { return self.size() == 0; } /* 入栈 */ pub fn push(&mut self, num: T) { let node = ListNode::new(num); node.borrow_mut().next = self.stack_peek.take(); self.stack_peek = Some(node); self.stk_size += 1; } /* 出栈 */ pub fn pop(&mut self) -> Option { self.stack_peek.take().map(|old_head| { self.stack_peek = old_head.borrow_mut().next.take(); self.stk_size -= 1; old_head.borrow().val }) } /* 访问栈顶元素 */ pub fn peek(&self) -> Option<&Rc>>> { self.stack_peek.as_ref() } /* 将 List 转化为 Array 并返回 */ pub fn to_array(&self) -> Vec { fn _to_array(head: Option<&Rc>>>) -> Vec { if let Some(node) = head { let mut nums = _to_array(node.borrow().next.as_ref()); nums.push(node.borrow().val); return nums; } return Vec::new(); } _to_array(self.peek()) } } /* Driver Code */ fn main() { /* 初始化栈 */ let mut stack = LinkedListStack::new(); /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("栈 stack = "); print_util::print_array(&stack.to_array()); /* 访问栈顶元素 */ let peek = stack.peek().unwrap().borrow().val; print!("\n栈顶元素 peek = {}", peek); /* 元素出栈 */ let pop = stack.pop().unwrap(); print!("\n出栈元素 pop = {},出栈后 stack = ", pop); print_util::print_array(&stack.to_array()); /* 获取栈的长度 */ let size = stack.size(); print!("\n栈的长度 size = {}", size); /* 判断是否为空 */ let is_empty = stack.is_empty(); print!("\n栈是否为空 = {}", is_empty); } ================================================ FILE: codes/rust/chapter_stack_and_queue/queue.rs ================================================ /* * File: queue.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; use std::collections::VecDeque; /* Driver Code */ pub fn main() { // 初始化队列 let mut queue: VecDeque = VecDeque::new(); // 元素入队 queue.push_back(1); queue.push_back(3); queue.push_back(2); queue.push_back(5); queue.push_back(4); print!("队列 queue = "); print_util::print_queue(&queue); // 访问队首元素 let peek = queue.front().unwrap(); println!("\n队首元素 peek = {peek}"); // 元素出队 let pop = queue.pop_front().unwrap(); print!("出队元素 pop = {pop},出队后 queue = "); print_util::print_queue(&queue); // 获取队列的长度 let size = queue.len(); print!("\n队列长度 size = {size}"); // 判断队列是否为空 let is_empty = queue.is_empty(); print!("\n队列是否为空 = {is_empty}"); } ================================================ FILE: codes/rust/chapter_stack_and_queue/stack.rs ================================================ /* * File: stack.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Driver Code */ pub fn main() { // 初始化栈 // 在 rust 中,推荐将 Vec 当作栈来使用 let mut stack: Vec = Vec::new(); // 元素入栈 stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("栈 stack = "); print_util::print_array(&stack); // 访问栈顶元素 let peek = stack.last().unwrap(); print!("\n栈顶元素 peek = {peek}"); // 元素出栈 let pop = stack.pop().unwrap(); print!("\n出栈元素 pop = {pop},出栈后 stack = "); print_util::print_array(&stack); // 获取栈的长度 let size = stack.len(); print!("\n栈的长度 size = {size}"); // 判断栈是否为空 let is_empty = stack.is_empty(); print!("\n栈是否为空 = {is_empty}"); } ================================================ FILE: codes/rust/chapter_tree/array_binary_tree.rs ================================================ /* * File: array_binary_tree.rs * Created Time: 2023-07-25 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::{print_util, tree_node}; /* 数组表示下的二叉树类 */ struct ArrayBinaryTree { tree: Vec>, } impl ArrayBinaryTree { /* 构造方法 */ fn new(arr: Vec>) -> Self { Self { tree: arr } } /* 列表容量 */ fn size(&self) -> i32 { self.tree.len() as i32 } /* 获取索引为 i 节点的值 */ fn val(&self, i: i32) -> Option { // 若索引越界,则返回 None ,代表空位 if i < 0 || i >= self.size() { None } else { self.tree[i as usize] } } /* 获取索引为 i 节点的左子节点的索引 */ fn left(&self, i: i32) -> i32 { 2 * i + 1 } /* 获取索引为 i 节点的右子节点的索引 */ fn right(&self, i: i32) -> i32 { 2 * i + 2 } /* 获取索引为 i 节点的父节点的索引 */ fn parent(&self, i: i32) -> i32 { (i - 1) / 2 } /* 层序遍历 */ fn level_order(&self) -> Vec { self.tree.iter().filter_map(|&x| x).collect() } /* 深度优先遍历 */ fn dfs(&self, i: i32, order: &'static str, res: &mut Vec) { if self.val(i).is_none() { return; } let val = self.val(i).unwrap(); // 前序遍历 if order == "pre" { res.push(val); } self.dfs(self.left(i), order, res); // 中序遍历 if order == "in" { res.push(val); } self.dfs(self.right(i), order, res); // 后序遍历 if order == "post" { res.push(val); } } /* 前序遍历 */ fn pre_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "pre", &mut res); res } /* 中序遍历 */ fn in_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "in", &mut res); res } /* 后序遍历 */ fn post_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "post", &mut res); res } } /* Driver Code */ fn main() { // 初始化二叉树 // 这里借助了一个从数组直接生成二叉树的函数 let arr = vec![ Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15), ]; let root = tree_node::vec_to_tree(arr.clone()).unwrap(); println!("\n初始化二叉树\n"); println!("二叉树的数组表示:"); println!( "[{}]", arr.iter() .map(|&val| if let Some(val) = val { format!("{val}") } else { "null".to_string() }) .collect::>() .join(", ") ); println!("二叉树的链表表示:"); print_util::print_tree(&root); // 数组表示下的二叉树类 let abt = ArrayBinaryTree::new(arr); // 访问节点 let i = 1; let l = abt.left(i); let r = abt.right(i); let p = abt.parent(i); println!( "\n当前节点的索引为 {} ,值为 {}", i, if let Some(val) = abt.val(i) { format!("{val}") } else { "null".to_string() } ); println!( "其左子节点的索引为 {} ,值为 {}", l, if let Some(val) = abt.val(l) { format!("{val}") } else { "null".to_string() } ); println!( "其右子节点的索引为 {} ,值为 {}", r, if let Some(val) = abt.val(r) { format!("{val}") } else { "null".to_string() } ); println!( "其父节点的索引为 {} ,值为 {}", p, if let Some(val) = abt.val(p) { format!("{val}") } else { "null".to_string() } ); // 遍历树 let mut res = abt.level_order(); println!("\n层序遍历为:{:?}", res); res = abt.pre_order(); println!("前序遍历为:{:?}", res); res = abt.in_order(); println!("中序遍历为:{:?}", res); res = abt.post_order(); println!("后序遍历为:{:?}", res); } ================================================ FILE: codes/rust/chapter_tree/avl_tree.rs ================================================ /* * File: avl_tree.rs * Created Time: 2023-07-14 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::{print_util, TreeNode}; use std::cell::RefCell; use std::cmp::Ordering; use std::rc::Rc; type OptionTreeNodeRc = Option>>; /* AVL 树 */ struct AVLTree { root: OptionTreeNodeRc, // 根节点 } impl AVLTree { /* 构造方法 */ fn new() -> Self { Self { root: None } } /* 获取节点高度 */ fn height(node: OptionTreeNodeRc) -> i32 { // 空节点高度为 -1 ,叶节点高度为 0 match node { Some(node) => node.borrow().height, None => -1, } } /* 更新节点高度 */ fn update_height(node: OptionTreeNodeRc) { if let Some(node) = node { let left = node.borrow().left.clone(); let right = node.borrow().right.clone(); // 节点高度等于最高子树高度 + 1 node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1; } } /* 获取平衡因子 */ fn balance_factor(node: OptionTreeNodeRc) -> i32 { match node { // 空节点平衡因子为 0 None => 0, // 节点平衡因子 = 左子树高度 - 右子树高度 Some(node) => { Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone()) } } } /* 右旋操作 */ fn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { match node { Some(node) => { let child = node.borrow().left.clone().unwrap(); let grand_child = child.borrow().right.clone(); // 以 child 为原点,将 node 向右旋转 child.borrow_mut().right = Some(node.clone()); node.borrow_mut().left = grand_child; // 更新节点高度 Self::update_height(Some(node)); Self::update_height(Some(child.clone())); // 返回旋转后子树的根节点 Some(child) } None => None, } } /* 左旋操作 */ fn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { match node { Some(node) => { let child = node.borrow().right.clone().unwrap(); let grand_child = child.borrow().left.clone(); // 以 child 为原点,将 node 向左旋转 child.borrow_mut().left = Some(node.clone()); node.borrow_mut().right = grand_child; // 更新节点高度 Self::update_height(Some(node)); Self::update_height(Some(child.clone())); // 返回旋转后子树的根节点 Some(child) } None => None, } } /* 执行旋转操作,使该子树重新恢复平衡 */ fn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { // 获取节点 node 的平衡因子 let balance_factor = Self::balance_factor(node.clone()); // 左偏树 if balance_factor > 1 { let node = node.unwrap(); if Self::balance_factor(node.borrow().left.clone()) >= 0 { // 右旋 Self::right_rotate(Some(node)) } else { // 先左旋后右旋 let left = node.borrow().left.clone(); node.borrow_mut().left = Self::left_rotate(left); Self::right_rotate(Some(node)) } } // 右偏树 else if balance_factor < -1 { let node = node.unwrap(); if Self::balance_factor(node.borrow().right.clone()) <= 0 { // 左旋 Self::left_rotate(Some(node)) } else { // 先右旋后左旋 let right = node.borrow().right.clone(); node.borrow_mut().right = Self::right_rotate(right); Self::left_rotate(Some(node)) } } else { // 平衡树,无须旋转,直接返回 node } } /* 插入节点 */ fn insert(&mut self, val: i32) { self.root = Self::insert_helper(self.root.clone(), val); } /* 递归插入节点(辅助方法) */ fn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { match node { Some(mut node) => { /* 1. 查找插入位置并插入节点 */ match { let node_val = node.borrow().val; node_val } .cmp(&val) { Ordering::Greater => { let left = node.borrow().left.clone(); node.borrow_mut().left = Self::insert_helper(left, val); } Ordering::Less => { let right = node.borrow().right.clone(); node.borrow_mut().right = Self::insert_helper(right, val); } Ordering::Equal => { return Some(node); // 重复节点不插入,直接返回 } } Self::update_height(Some(node.clone())); // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = Self::rotate(Some(node)).unwrap(); // 返回子树的根节点 Some(node) } None => Some(TreeNode::new(val)), } } /* 删除节点 */ fn remove(&self, val: i32) { Self::remove_helper(self.root.clone(), val); } /* 递归删除节点(辅助方法) */ fn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { match node { Some(mut node) => { /* 1. 查找节点并删除 */ if val < node.borrow().val { let left = node.borrow().left.clone(); node.borrow_mut().left = Self::remove_helper(left, val); } else if val > node.borrow().val { let right = node.borrow().right.clone(); node.borrow_mut().right = Self::remove_helper(right, val); } else if node.borrow().left.is_none() || node.borrow().right.is_none() { let child = if node.borrow().left.is_some() { node.borrow().left.clone() } else { node.borrow().right.clone() }; match child { // 子节点数量 = 0 ,直接删除 node 并返回 None => { return None; } // 子节点数量 = 1 ,直接删除 node Some(child) => node = child, } } else { // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 let mut temp = node.borrow().right.clone().unwrap(); loop { let temp_left = temp.borrow().left.clone(); if temp_left.is_none() { break; } temp = temp_left.unwrap(); } let right = node.borrow().right.clone(); node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val); node.borrow_mut().val = temp.borrow().val; } Self::update_height(Some(node.clone())); // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = Self::rotate(Some(node)).unwrap(); // 返回子树的根节点 Some(node) } None => None, } } /* 查找节点 */ fn search(&self, val: i32) -> OptionTreeNodeRc { let mut cur = self.root.clone(); // 循环查找,越过叶节点后跳出 while let Some(current) = cur.clone() { match current.borrow().val.cmp(&val) { // 目标节点在 cur 的右子树中 Ordering::Less => { cur = current.borrow().right.clone(); } // 目标节点在 cur 的左子树中 Ordering::Greater => { cur = current.borrow().left.clone(); } // 找到目标节点,跳出循环 Ordering::Equal => { break; } } } // 返回目标节点 cur } } /* Driver Code */ fn main() { fn test_insert(tree: &mut AVLTree, val: i32) { tree.insert(val); println!("\n插入节点 {} 后,AVL 树为", val); print_util::print_tree(&tree.root.clone().unwrap()); } fn test_remove(tree: &mut AVLTree, val: i32) { tree.remove(val); println!("\n删除节点 {} 后,AVL 树为", val); print_util::print_tree(&tree.root.clone().unwrap()); } /* 初始化空 AVL 树 */ let mut avl_tree = AVLTree::new(); /* 插入节点 */ // 请关注插入节点后,AVL 树是如何保持平衡的 test_insert(&mut avl_tree, 1); test_insert(&mut avl_tree, 2); test_insert(&mut avl_tree, 3); test_insert(&mut avl_tree, 4); test_insert(&mut avl_tree, 5); test_insert(&mut avl_tree, 8); test_insert(&mut avl_tree, 7); test_insert(&mut avl_tree, 9); test_insert(&mut avl_tree, 10); test_insert(&mut avl_tree, 6); /* 插入重复节点 */ test_insert(&mut avl_tree, 7); /* 删除节点 */ // 请关注删除节点后,AVL 树是如何保持平衡的 test_remove(&mut avl_tree, 8); // 删除度为 0 的节点 test_remove(&mut avl_tree, 5); // 删除度为 1 的节点 test_remove(&mut avl_tree, 4); // 删除度为 2 的节点 /* 查询节点 */ let node = avl_tree.search(7); if let Some(node) = node { println!( "\n查找到的节点对象为 {:?},节点值 = {}", &*node.borrow(), node.borrow().val ); } } ================================================ FILE: codes/rust/chapter_tree/binary_search_tree.rs ================================================ /* * File: binary_search_tree.rs * Created Time: 2023-04-20 * Author: xBLACKICEx (xBLACKICE@outlook.com)、night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::cell::RefCell; use std::cmp::Ordering; use std::rc::Rc; use hello_algo_rust::include::TreeNode; type OptionTreeNodeRc = Option>>; /* 二叉搜索树 */ pub struct BinarySearchTree { root: OptionTreeNodeRc, } impl BinarySearchTree { /* 构造方法 */ pub fn new() -> Self { // 初始化空树 Self { root: None } } /* 获取二叉树根节点 */ pub fn get_root(&self) -> OptionTreeNodeRc { self.root.clone() } /* 查找节点 */ pub fn search(&self, num: i32) -> OptionTreeNodeRc { let mut cur = self.root.clone(); // 循环查找,越过叶节点后跳出 while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // 目标节点在 cur 的右子树中 Ordering::Greater => cur = node.borrow().right.clone(), // 目标节点在 cur 的左子树中 Ordering::Less => cur = node.borrow().left.clone(), // 找到目标节点,跳出循环 Ordering::Equal => break, } } // 返回目标节点 cur } /* 插入节点 */ pub fn insert(&mut self, num: i32) { // 若树为空,则初始化根节点 if self.root.is_none() { self.root = Some(TreeNode::new(num)); return; } let mut cur = self.root.clone(); let mut pre = None; // 循环查找,越过叶节点后跳出 while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // 找到重复节点,直接返回 Ordering::Equal => return, // 插入位置在 cur 的右子树中 Ordering::Greater => { pre = cur.clone(); cur = node.borrow().right.clone(); } // 插入位置在 cur 的左子树中 Ordering::Less => { pre = cur.clone(); cur = node.borrow().left.clone(); } } } // 插入节点 let pre = pre.unwrap(); let node = Some(TreeNode::new(num)); if num > pre.borrow().val { pre.borrow_mut().right = node; } else { pre.borrow_mut().left = node; } } /* 删除节点 */ pub fn remove(&mut self, num: i32) { // 若树为空,直接提前返回 if self.root.is_none() { return; } let mut cur = self.root.clone(); let mut pre = None; // 循环查找,越过叶节点后跳出 while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // 找到待删除节点,跳出循环 Ordering::Equal => break, // 待删除节点在 cur 的右子树中 Ordering::Greater => { pre = cur.clone(); cur = node.borrow().right.clone(); } // 待删除节点在 cur 的左子树中 Ordering::Less => { pre = cur.clone(); cur = node.borrow().left.clone(); } } } // 若无待删除节点,则直接返回 if cur.is_none() { return; } let cur = cur.unwrap(); let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone()); match (left_child.clone(), right_child.clone()) { // 子节点数量 = 0 or 1 (None, None) | (Some(_), None) | (None, Some(_)) => { // 当子节点数量 = 0 / 1 时, child = nullptr / 该子节点 let child = left_child.or(right_child); let pre = pre.unwrap(); // 删除节点 cur if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) { let left = pre.borrow().left.clone(); if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) { pre.borrow_mut().left = child; } else { pre.borrow_mut().right = child; } } else { // 若删除节点为根节点,则重新指定根节点 self.root = child; } } // 子节点数量 = 2 (Some(_), Some(_)) => { // 获取中序遍历中 cur 的下一个节点 let mut tmp = cur.borrow().right.clone(); while let Some(node) = tmp.clone() { if node.borrow().left.is_some() { tmp = node.borrow().left.clone(); } else { break; } } let tmp_val = tmp.unwrap().borrow().val; // 递归删除节点 tmp self.remove(tmp_val); // 用 tmp 覆盖 cur cur.borrow_mut().val = tmp_val; } } } } /* Driver Code */ fn main() { /* 初始化二叉搜索树 */ let mut bst = BinarySearchTree::new(); // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for &num in &nums { bst.insert(num); } println!("\n初始化的二叉树为\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); /* 查找结点 */ let node = bst.search(7); println!( "\n查找到的节点对象为 {:?},节点值 = {}", node.clone().unwrap(), node.clone().unwrap().borrow().val ); /* 插入节点 */ bst.insert(16); println!("\n插入节点 16 后,二叉树为\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); /* 删除节点 */ bst.remove(1); println!("\n删除节点 1 后,二叉树为\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); bst.remove(2); println!("\n删除节点 2 后,二叉树为\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); bst.remove(4); println!("\n删除节点 4 后,二叉树为\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); } ================================================ FILE: codes/rust/chapter_tree/binary_tree.rs ================================================ /** * File: binary_tree.rs * Created Time: 2023-02-27 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use std::rc::Rc; use hello_algo_rust::include::{print_util, TreeNode}; /* Driver Code */ fn main() { /* 初始化二叉树 */ // 初始化节点 let n1 = TreeNode::new(1); let n2 = TreeNode::new(2); let n3 = TreeNode::new(3); let n4 = TreeNode::new(4); let n5 = TreeNode::new(5); // 构建节点之间的引用(指针) n1.borrow_mut().left = Some(Rc::clone(&n2)); n1.borrow_mut().right = Some(Rc::clone(&n3)); n2.borrow_mut().left = Some(Rc::clone(&n4)); n2.borrow_mut().right = Some(Rc::clone(&n5)); println!("\n初始化二叉树\n"); print_util::print_tree(&n1); // 插入节点与删除节点 let p = TreeNode::new(0); // 在 n1 -> n2 中间插入节点 P p.borrow_mut().left = Some(Rc::clone(&n2)); n1.borrow_mut().left = Some(Rc::clone(&p)); println!("\n插入节点 P 后\n"); print_util::print_tree(&n1); // 删除节点 P drop(p); n1.borrow_mut().left = Some(Rc::clone(&n2)); println!("\n删除节点 P 后\n"); print_util::print_tree(&n1); } ================================================ FILE: codes/rust/chapter_tree/binary_tree_bfs.rs ================================================ /* * File: binary_tree_bfs.rs * Created Time: 2023-04-07 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use hello_algo_rust::op_vec; use std::collections::VecDeque; use std::{cell::RefCell, rc::Rc}; /* 层序遍历 */ fn level_order(root: &Rc>) -> Vec { // 初始化队列,加入根节点 let mut que = VecDeque::new(); que.push_back(root.clone()); // 初始化一个列表,用于保存遍历序列 let mut vec = Vec::new(); while let Some(node) = que.pop_front() { // 队列出队 vec.push(node.borrow().val); // 保存节点值 if let Some(left) = node.borrow().left.as_ref() { que.push_back(left.clone()); // 左子节点入队 } if let Some(right) = node.borrow().right.as_ref() { que.push_back(right.clone()); // 右子节点入队 }; } vec } /* Driver Code */ fn main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]).unwrap(); println!("初始化二叉树\n"); print_util::print_tree(&root); /* 层序遍历 */ let vec = level_order(&root); print!("\n层序遍历的节点打印序列 = {:?}", vec); } ================================================ FILE: codes/rust/chapter_tree/binary_tree_dfs.rs ================================================ /* * File: binary_tree_dfs.rs * Created Time: 2023-04-06 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use hello_algo_rust::op_vec; use std::cell::RefCell; use std::rc::Rc; /* 前序遍历 */ fn pre_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // 访问优先级:根节点 -> 左子树 -> 右子树 let node = node.borrow(); res.push(node.val); dfs(node.left.as_ref(), res); dfs(node.right.as_ref(), res); } } dfs(root, &mut result); result } /* 中序遍历 */ fn in_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // 访问优先级:左子树 -> 根节点 -> 右子树 let node = node.borrow(); dfs(node.left.as_ref(), res); res.push(node.val); dfs(node.right.as_ref(), res); } } dfs(root, &mut result); result } /* 后序遍历 */ fn post_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // 访问优先级:左子树 -> 右子树 -> 根节点 let node = node.borrow(); dfs(node.left.as_ref(), res); dfs(node.right.as_ref(), res); res.push(node.val); } } dfs(root, &mut result); result } /* Driver Code */ fn main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]); println!("初始化二叉树\n"); print_util::print_tree(root.as_ref().unwrap()); /* 前序遍历 */ let vec = pre_order(root.as_ref()); println!("\n前序遍历的节点打印序列 = {:?}", vec); /* 中序遍历 */ let vec = in_order(root.as_ref()); println!("\n中序遍历的节点打印序列 = {:?}", vec); /* 后序遍历 */ let vec = post_order(root.as_ref()); print!("\n后序遍历的节点打印序列 = {:?}", vec); } ================================================ FILE: codes/rust/src/include/list_node.rs ================================================ /* * File: list_node.rs * Created Time: 2023-03-05 * Author: codingonion (coderonion@gmail.com), rongyi (hiarongyi@gmail.com) */ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; #[derive(Debug)] pub struct ListNode { pub val: T, pub next: Option>>>, } impl ListNode { pub fn new(val: T) -> Rc>> { Rc::new(RefCell::new(ListNode { val, next: None })) } /* 将数组反序列化为链表 */ pub fn arr_to_linked_list(array: &[T]) -> Option>>> where T: Copy + Clone, { let mut head = None; // insert in reverse order for item in array.iter().rev() { let node = Rc::new(RefCell::new(ListNode { val: *item, next: head.take(), })); head = Some(node); } head } /* 将链表转化为哈希表 */ pub fn linked_list_to_hashmap( linked_list: Option>>>, ) -> HashMap>>> where T: std::hash::Hash + Eq + Copy + Clone, { let mut hashmap = HashMap::new(); let mut node = linked_list; while let Some(cur) = node { let borrow = cur.borrow(); hashmap.insert(borrow.val.clone(), cur.clone()); node = borrow.next.clone(); } hashmap } } ================================================ FILE: codes/rust/src/include/mod.rs ================================================ /* * File: include.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICE@outlook.com) */ pub mod list_node; pub mod print_util; pub mod tree_node; pub mod vertex; // rexport to include pub use list_node::*; pub use print_util::*; pub use tree_node::*; pub use vertex::*; ================================================ FILE: codes/rust/src/include/print_util.rs ================================================ /* * File: print_util.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use std::cell::{Cell, RefCell}; use std::fmt::Display; use std::collections::{HashMap, VecDeque}; use std::rc::Rc; use super::list_node::ListNode; use super::tree_node::{TreeNode, vec_to_tree}; struct Trunk<'a, 'b> { prev: Option<&'a Trunk<'a, 'b>>, str: Cell<&'b str>, } /* 打印数组 */ pub fn print_array(nums: &[T]) { print!("["); if nums.len() > 0 { for (i, num) in nums.iter().enumerate() { print!("{}{}", num, if i == nums.len() - 1 {"]"} else {", "} ); } } else { print!("]"); } } /* 打印哈希表 */ pub fn print_hash_map(map: &HashMap) { for (key, value) in map { println!("{key} -> {value}"); } } /* 打印队列(双向队列) */ pub fn print_queue(queue: &VecDeque) { print!("["); let iter = queue.iter(); for (i, data) in iter.enumerate() { print!("{}{}", data, if i == queue.len() - 1 {"]"} else {", "} ); } } /* 打印链表 */ pub fn print_linked_list(head: &Rc>>) { print!("{}{}", head.borrow().val, if head.borrow().next.is_none() {"\n"} else {" -> "}); if let Some(node) = &head.borrow().next { return print_linked_list(node); } } /* 打印二叉树 */ pub fn print_tree(root: &Rc>) { _print_tree(Some(root), None, false); } /* 打印二叉树 */ fn _print_tree(root: Option<&Rc>>, prev: Option<&Trunk>, is_right: bool) { if let Some(node) = root { let mut prev_str = " "; let trunk = Trunk { prev, str: Cell::new(prev_str) }; _print_tree(node.borrow().right.as_ref(), Some(&trunk), true); if prev.is_none() { trunk.str.set("———"); } else if is_right { trunk.str.set("/———"); prev_str = " |"; } else { trunk.str.set("\\———"); prev.as_ref().unwrap().str.set(prev_str); } show_trunks(Some(&trunk)); println!(" {}", node.borrow().val); if let Some(prev) = prev { prev.str.set(prev_str); } trunk.str.set(" |"); _print_tree(node.borrow().left.as_ref(), Some(&trunk), false); } } fn show_trunks(trunk: Option<&Trunk>) { if let Some(trunk) = trunk { show_trunks(trunk.prev); print!("{}", trunk.str.get()); } } /* 打印堆 */ pub fn print_heap(heap: Vec) { println!("堆的数组表示:{:?}", heap); println!("堆的树状表示:"); if let Some(root) = vec_to_tree(heap.into_iter().map(|val| Some(val)).collect()) { print_tree(&root); } } ================================================ FILE: codes/rust/src/include/tree_node.rs ================================================ /* * File: tree_node.rs * Created Time: 2023-02-27 * Author: xBLACKICEx (xBLACKICE@outlook.com), night-cruise (2586447362@qq.com) */ use std::cell::RefCell; use std::rc::Rc; /* 二叉树节点类型 */ #[derive(Debug)] pub struct TreeNode { pub val: i32, pub height: i32, pub parent: Option>>, pub left: Option>>, pub right: Option>>, } impl TreeNode { /* 构造方法 */ pub fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, height: 0, parent: None, left: None, right: None, })) } } #[macro_export] macro_rules! op_vec { ( $( $x:expr ),* ) => { vec![ $(Option::from($x)),* ] }; } // 序列化编码规则请参考: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二叉树的数组表示: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // 二叉树的链表表示: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* 将列表反序列化为二叉树:递归 */ fn vec_to_tree_dfs(arr: &[Option], i: usize) -> Option>> { if i >= arr.len() || arr[i].is_none() { return None; } let root = TreeNode::new(arr[i].unwrap()); root.borrow_mut().left = vec_to_tree_dfs(arr, 2 * i + 1); root.borrow_mut().right = vec_to_tree_dfs(arr, 2 * i + 2); Some(root) } /* 将列表反序列化为二叉树 */ pub fn vec_to_tree(arr: Vec>) -> Option>> { vec_to_tree_dfs(&arr, 0) } /* 将二叉树序列化为列表:递归 */ fn tree_to_vec_dfs(root: Option<&Rc>>, i: usize, res: &mut Vec>) { if let Some(root) = root { // i + 1 is the minimum valid size to access index i while res.len() < i + 1 { res.push(None); } res[i] = Some(root.borrow().val); tree_to_vec_dfs(root.borrow().left.as_ref(), 2 * i + 1, res); tree_to_vec_dfs(root.borrow().right.as_ref(), 2 * i + 2, res); } } /* 将二叉树序列化为列表 */ pub fn tree_to_vec(root: Option>>) -> Vec> { let mut res = vec![]; tree_to_vec_dfs(root.as_ref(), 0, &mut res); res } ================================================ FILE: codes/rust/src/include/vertex.rs ================================================ /* * File: vertex.rs * Created Time: 2023-07-13 * Author: night-cruise (2586447362@qq.com) */ /* 顶点类型 */ #[derive(Copy, Clone, Hash, PartialEq, Eq)] pub struct Vertex { pub val: i32, } impl From for Vertex { fn from(value: i32) -> Self { Self { val: value } } } /* 输入值列表 vals ,返回顶点列表 vets */ pub fn vals_to_vets(vals: Vec) -> Vec { vals.into_iter().map(|val| val.into()).collect() } /* 输入顶点列表 vets ,返回值列表 vals */ pub fn vets_to_vals(vets: Vec) -> Vec { vets.into_iter().map(|vet| vet.val).collect() } ================================================ FILE: codes/rust/src/lib.rs ================================================ pub mod include; ================================================ FILE: codes/swift/.gitignore ================================================ # Created by https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager # Edit at https://www.toptal.com/developers/gitignore?templates=objective-c,swift,swiftpackagemanager ### Objective-C ### # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## User settings xcuserdata/ ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint *.xccheckout ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ DerivedData/ *.moved-aside *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 ## Obj-C/Swift specific *.hmap ## App packaging *.ipa *.dSYM.zip *.dSYM # CocoaPods # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build/ # fastlane # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output # Code Injection # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ ### Objective-C Patch ### ### Swift ### # Xcode # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins # Package.resolved # *.xcodeproj # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project # .swiftpm .build/ # CocoaPods # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts # Accio dependency management Dependencies/ .accio/ # fastlane # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control # Code Injection # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode ### SwiftPackageManager ### Packages xcuserdata *.xcodeproj # End of https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager ================================================ FILE: codes/swift/Package.resolved ================================================ { "pins" : [ { "identity" : "swift-collections", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { "branch" : "release/1.1", "revision" : "4a1d92ba85027010d2c528c05576cde9a362254b" } } ], "version" : 2 } ================================================ FILE: codes/swift/Package.swift ================================================ // swift-tools-version: 5.7 import PackageDescription let package = Package( name: "HelloAlgo", products: [ // chapter_computational_complexity .executable(name: "iteration", targets: ["iteration"]), .executable(name: "recursion", targets: ["recursion"]), .executable(name: "time_complexity", targets: ["time_complexity"]), .executable(name: "worst_best_time_complexity", targets: ["worst_best_time_complexity"]), .executable(name: "space_complexity", targets: ["space_complexity"]), // chapter_array_and_linkedlist .executable(name: "array", targets: ["array"]), .executable(name: "linked_list", targets: ["linked_list"]), .executable(name: "list", targets: ["list"]), .executable(name: "my_list", targets: ["my_list"]), // chapter_stack_and_queue .executable(name: "stack", targets: ["stack"]), .executable(name: "linkedlist_stack", targets: ["linkedlist_stack"]), .executable(name: "array_stack", targets: ["array_stack"]), .executable(name: "queue", targets: ["queue"]), .executable(name: "linkedlist_queue", targets: ["linkedlist_queue"]), .executable(name: "array_queue", targets: ["array_queue"]), .executable(name: "deque", targets: ["deque"]), .executable(name: "linkedlist_deque", targets: ["linkedlist_deque"]), .executable(name: "array_deque", targets: ["array_deque"]), // chapter_hashing .executable(name: "hash_map", targets: ["hash_map"]), .executable(name: "array_hash_map", targets: ["array_hash_map"]), .executable(name: "hash_map_chaining", targets: ["hash_map_chaining"]), .executable(name: "hash_map_open_addressing", targets: ["hash_map_open_addressing"]), .executable(name: "simple_hash", targets: ["simple_hash"]), .executable(name: "built_in_hash", targets: ["built_in_hash"]), // chapter_tree .executable(name: "binary_tree", targets: ["binary_tree"]), .executable(name: "binary_tree_bfs", targets: ["binary_tree_bfs"]), .executable(name: "binary_tree_dfs", targets: ["binary_tree_dfs"]), .executable(name: "array_binary_tree", targets: ["array_binary_tree"]), .executable(name: "binary_search_tree", targets: ["binary_search_tree"]), .executable(name: "avl_tree", targets: ["avl_tree"]), // chapter_heap .executable(name: "heap", targets: ["heap"]), .executable(name: "my_heap", targets: ["my_heap"]), .executable(name: "top_k", targets: ["top_k"]), // chapter_graph .executable(name: "graph_adjacency_matrix", targets: ["graph_adjacency_matrix"]), .executable(name: "graph_adjacency_list", targets: ["graph_adjacency_list"]), .executable(name: "graph_bfs", targets: ["graph_bfs"]), .executable(name: "graph_dfs", targets: ["graph_dfs"]), // chapter_searching .executable(name: "binary_search", targets: ["binary_search"]), .executable(name: "binary_search_insertion", targets: ["binary_search_insertion"]), .executable(name: "binary_search_edge", targets: ["binary_search_edge"]), .executable(name: "two_sum", targets: ["two_sum"]), .executable(name: "linear_search", targets: ["linear_search"]), .executable(name: "hashing_search", targets: ["hashing_search"]), // chapter_sorting .executable(name: "selection_sort", targets: ["selection_sort"]), .executable(name: "bubble_sort", targets: ["bubble_sort"]), .executable(name: "insertion_sort", targets: ["insertion_sort"]), .executable(name: "quick_sort", targets: ["quick_sort"]), .executable(name: "merge_sort", targets: ["merge_sort"]), .executable(name: "heap_sort", targets: ["heap_sort"]), .executable(name: "bucket_sort", targets: ["bucket_sort"]), .executable(name: "counting_sort", targets: ["counting_sort"]), .executable(name: "radix_sort", targets: ["radix_sort"]), // chapter_divide_and_conquer .executable(name: "binary_search_recur", targets: ["binary_search_recur"]), .executable(name: "build_tree", targets: ["build_tree"]), .executable(name: "hanota", targets: ["hanota"]), // chapter_backtracking .executable(name: "preorder_traversal_i_compact", targets: ["preorder_traversal_i_compact"]), .executable(name: "preorder_traversal_ii_compact", targets: ["preorder_traversal_ii_compact"]), .executable(name: "preorder_traversal_iii_compact", targets: ["preorder_traversal_iii_compact"]), .executable(name: "preorder_traversal_iii_template", targets: ["preorder_traversal_iii_template"]), .executable(name: "permutations_i", targets: ["permutations_i"]), .executable(name: "permutations_ii", targets: ["permutations_ii"]), .executable(name: "subset_sum_i_naive", targets: ["subset_sum_i_naive"]), .executable(name: "subset_sum_i", targets: ["subset_sum_i"]), .executable(name: "subset_sum_ii", targets: ["subset_sum_ii"]), .executable(name: "n_queens", targets: ["n_queens"]), // chapter_dynamic_programming .executable(name: "climbing_stairs_backtrack", targets: ["climbing_stairs_backtrack"]), .executable(name: "climbing_stairs_dfs", targets: ["climbing_stairs_dfs"]), .executable(name: "climbing_stairs_dfs_mem", targets: ["climbing_stairs_dfs_mem"]), .executable(name: "climbing_stairs_dp", targets: ["climbing_stairs_dp"]), .executable(name: "min_cost_climbing_stairs_dp", targets: ["min_cost_climbing_stairs_dp"]), .executable(name: "climbing_stairs_constraint_dp", targets: ["climbing_stairs_constraint_dp"]), .executable(name: "min_path_sum", targets: ["min_path_sum"]), .executable(name: "knapsack", targets: ["knapsack"]), .executable(name: "unbounded_knapsack", targets: ["unbounded_knapsack"]), .executable(name: "coin_change", targets: ["coin_change"]), .executable(name: "coin_change_ii", targets: ["coin_change_ii"]), .executable(name: "edit_distance", targets: ["edit_distance"]), // chapter_greedy .executable(name: "coin_change_greedy", targets: ["coin_change_greedy"]), .executable(name: "fractional_knapsack", targets: ["fractional_knapsack"]), .executable(name: "max_capacity", targets: ["max_capacity"]), .executable(name: "max_product_cutting", targets: ["max_product_cutting"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-collections", branch: "release/1.1"), ], targets: [ // helper .target(name: "utils", path: "utils"), .target(name: "graph_adjacency_list_target", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list_target.swift"], swiftSettings: [.define("TARGET")]), .target(name: "binary_search_insertion_target", path: "chapter_searching", sources: ["binary_search_insertion_target.swift"], swiftSettings: [.define("TARGET")]), // chapter_computational_complexity .executableTarget(name: "iteration", path: "chapter_computational_complexity", sources: ["iteration.swift"]), .executableTarget(name: "recursion", path: "chapter_computational_complexity", sources: ["recursion.swift"]), .executableTarget(name: "time_complexity", path: "chapter_computational_complexity", sources: ["time_complexity.swift"]), .executableTarget(name: "worst_best_time_complexity", path: "chapter_computational_complexity", sources: ["worst_best_time_complexity.swift"]), .executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]), // chapter_array_and_linkedlist .executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]), .executableTarget(name: "linked_list", dependencies: ["utils"], path: "chapter_array_and_linkedlist", sources: ["linked_list.swift"]), .executableTarget(name: "list", path: "chapter_array_and_linkedlist", sources: ["list.swift"]), .executableTarget(name: "my_list", path: "chapter_array_and_linkedlist", sources: ["my_list.swift"]), // chapter_stack_and_queue .executableTarget(name: "stack", path: "chapter_stack_and_queue", sources: ["stack.swift"]), .executableTarget(name: "linkedlist_stack", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_stack.swift"]), .executableTarget(name: "array_stack", path: "chapter_stack_and_queue", sources: ["array_stack.swift"]), .executableTarget(name: "queue", path: "chapter_stack_and_queue", sources: ["queue.swift"]), .executableTarget(name: "linkedlist_queue", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_queue.swift"]), .executableTarget(name: "array_queue", path: "chapter_stack_and_queue", sources: ["array_queue.swift"]), .executableTarget(name: "deque", path: "chapter_stack_and_queue", sources: ["deque.swift"]), .executableTarget(name: "linkedlist_deque", path: "chapter_stack_and_queue", sources: ["linkedlist_deque.swift"]), .executableTarget(name: "array_deque", path: "chapter_stack_and_queue", sources: ["array_deque.swift"]), // chapter_hashing .executableTarget(name: "hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map.swift"]), .executableTarget(name: "array_hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["array_hash_map.swift"]), .executableTarget(name: "hash_map_chaining", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_chaining.swift"]), .executableTarget(name: "hash_map_open_addressing", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_open_addressing.swift"]), .executableTarget(name: "simple_hash", path: "chapter_hashing", sources: ["simple_hash.swift"]), .executableTarget(name: "built_in_hash", dependencies: ["utils"], path: "chapter_hashing", sources: ["built_in_hash.swift"]), // chapter_tree .executableTarget(name: "binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree.swift"]), .executableTarget(name: "binary_tree_bfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_bfs.swift"]), .executableTarget(name: "binary_tree_dfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_dfs.swift"]), .executableTarget(name: "array_binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["array_binary_tree.swift"]), .executableTarget(name: "binary_search_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_search_tree.swift"]), .executableTarget(name: "avl_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["avl_tree.swift"]), // chapter_heap .executableTarget(name: "heap", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["heap.swift"]), .executableTarget(name: "my_heap", dependencies: ["utils"], path: "chapter_heap", sources: ["my_heap.swift"]), .executableTarget(name: "top_k", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["top_k.swift"]), // chapter_graph .executableTarget(name: "graph_adjacency_matrix", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_matrix.swift"]), .executableTarget(name: "graph_adjacency_list", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list.swift"]), .executableTarget(name: "graph_bfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_bfs.swift"]), .executableTarget(name: "graph_dfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_dfs.swift"]), // chapter_searching .executableTarget(name: "binary_search", path: "chapter_searching", sources: ["binary_search.swift"]), .executableTarget(name: "binary_search_insertion", path: "chapter_searching", sources: ["binary_search_insertion.swift"]), .executableTarget(name: "binary_search_edge", dependencies: ["binary_search_insertion_target"], path: "chapter_searching", sources: ["binary_search_edge.swift"]), .executableTarget(name: "two_sum", path: "chapter_searching", sources: ["two_sum.swift"]), .executableTarget(name: "linear_search", dependencies: ["utils"], path: "chapter_searching", sources: ["linear_search.swift"]), .executableTarget(name: "hashing_search", dependencies: ["utils"], path: "chapter_searching", sources: ["hashing_search.swift"]), // chapter_sorting .executableTarget(name: "selection_sort", path: "chapter_sorting", sources: ["selection_sort.swift"]), .executableTarget(name: "bubble_sort", path: "chapter_sorting", sources: ["bubble_sort.swift"]), .executableTarget(name: "insertion_sort", path: "chapter_sorting", sources: ["insertion_sort.swift"]), .executableTarget(name: "quick_sort", path: "chapter_sorting", sources: ["quick_sort.swift"]), .executableTarget(name: "merge_sort", path: "chapter_sorting", sources: ["merge_sort.swift"]), .executableTarget(name: "heap_sort", path: "chapter_sorting", sources: ["heap_sort.swift"]), .executableTarget(name: "bucket_sort", path: "chapter_sorting", sources: ["bucket_sort.swift"]), .executableTarget(name: "counting_sort", path: "chapter_sorting", sources: ["counting_sort.swift"]), .executableTarget(name: "radix_sort", path: "chapter_sorting", sources: ["radix_sort.swift"]), // chapter_divide_and_conquer .executableTarget(name: "binary_search_recur", path: "chapter_divide_and_conquer", sources: ["binary_search_recur.swift"]), .executableTarget(name: "build_tree", dependencies: ["utils"], path: "chapter_divide_and_conquer", sources: ["build_tree.swift"]), .executableTarget(name: "hanota", path: "chapter_divide_and_conquer", sources: ["hanota.swift"]), // chapter_backtracking .executableTarget(name: "preorder_traversal_i_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_i_compact.swift"]), .executableTarget(name: "preorder_traversal_ii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_ii_compact.swift"]), .executableTarget(name: "preorder_traversal_iii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_compact.swift"]), .executableTarget(name: "preorder_traversal_iii_template", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_template.swift"]), .executableTarget(name: "permutations_i", path: "chapter_backtracking", sources: ["permutations_i.swift"]), .executableTarget(name: "permutations_ii", path: "chapter_backtracking", sources: ["permutations_ii.swift"]), .executableTarget(name: "subset_sum_i_naive", path: "chapter_backtracking", sources: ["subset_sum_i_naive.swift"]), .executableTarget(name: "subset_sum_i", path: "chapter_backtracking", sources: ["subset_sum_i.swift"]), .executableTarget(name: "subset_sum_ii", path: "chapter_backtracking", sources: ["subset_sum_ii.swift"]), .executableTarget(name: "n_queens", path: "chapter_backtracking", sources: ["n_queens.swift"]), // chapter_dynamic_programming .executableTarget(name: "climbing_stairs_backtrack", path: "chapter_dynamic_programming", sources: ["climbing_stairs_backtrack.swift"]), .executableTarget(name: "climbing_stairs_dfs", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs.swift"]), .executableTarget(name: "climbing_stairs_dfs_mem", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs_mem.swift"]), .executableTarget(name: "climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dp.swift"]), .executableTarget(name: "min_cost_climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["min_cost_climbing_stairs_dp.swift"]), .executableTarget(name: "climbing_stairs_constraint_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_constraint_dp.swift"]), .executableTarget(name: "min_path_sum", path: "chapter_dynamic_programming", sources: ["min_path_sum.swift"]), .executableTarget(name: "knapsack", path: "chapter_dynamic_programming", sources: ["knapsack.swift"]), .executableTarget(name: "unbounded_knapsack", path: "chapter_dynamic_programming", sources: ["unbounded_knapsack.swift"]), .executableTarget(name: "coin_change", path: "chapter_dynamic_programming", sources: ["coin_change.swift"]), .executableTarget(name: "coin_change_ii", path: "chapter_dynamic_programming", sources: ["coin_change_ii.swift"]), .executableTarget(name: "edit_distance", path: "chapter_dynamic_programming", sources: ["edit_distance.swift"]), // chapter_greedy .executableTarget(name: "coin_change_greedy", path: "chapter_greedy", sources: ["coin_change_greedy.swift"]), .executableTarget(name: "fractional_knapsack", path: "chapter_greedy", sources: ["fractional_knapsack.swift"]), .executableTarget(name: "max_capacity", path: "chapter_greedy", sources: ["max_capacity.swift"]), .executableTarget(name: "max_product_cutting", path: "chapter_greedy", sources: ["max_product_cutting.swift"]), ] ) ================================================ FILE: codes/swift/chapter_array_and_linkedlist/array.swift ================================================ /** * File: array.swift * Created Time: 2023-01-05 * Author: nuomi1 (nuomi1@qq.com) */ /* 随机访问元素 */ func randomAccess(nums: [Int]) -> Int { // 在区间 [0, nums.count) 中随机抽取一个数字 let randomIndex = nums.indices.randomElement()! // 获取并返回随机元素 let randomNum = nums[randomIndex] return randomNum } /* 扩展数组长度 */ func extend(nums: [Int], enlarge: Int) -> [Int] { // 初始化一个扩展长度后的数组 var res = Array(repeating: 0, count: nums.count + enlarge) // 将原数组中的所有元素复制到新数组 for i in nums.indices { res[i] = nums[i] } // 返回扩展后的新数组 return res } /* 在数组的索引 index 处插入元素 num */ func insert(nums: inout [Int], num: Int, index: Int) { // 把索引 index 以及之后的所有元素向后移动一位 for i in nums.indices.dropFirst(index).reversed() { nums[i] = nums[i - 1] } // 将 num 赋给 index 处的元素 nums[index] = num } /* 删除索引 index 处的元素 */ func remove(nums: inout [Int], index: Int) { // 把索引 index 之后的所有元素向前移动一位 for i in nums.indices.dropFirst(index).dropLast() { nums[i] = nums[i + 1] } } /* 遍历数组 */ func traverse(nums: [Int]) { var count = 0 // 通过索引遍历数组 for i in nums.indices { count += nums[i] } // 直接遍历数组元素 for num in nums { count += num } // 同时遍历数据索引和元素 for (i, num) in nums.enumerated() { count += nums[i] count += num } } /* 在数组中查找指定元素 */ func find(nums: [Int], target: Int) -> Int { for i in nums.indices { if nums[i] == target { return i } } return -1 } @main enum _Array { /* Driver Code */ static func main() { /* 初始化数组 */ let arr = Array(repeating: 0, count: 5) print("数组 arr = \(arr)") var nums = [1, 3, 2, 5, 4] print("数组 nums = \(nums)") /* 随机访问 */ let randomNum = randomAccess(nums: nums) print("在 nums 中获取随机元素 \(randomNum)") /* 长度扩展 */ nums = extend(nums: nums, enlarge: 3) print("将数组长度扩展至 8 ,得到 nums = \(nums)") /* 插入元素 */ insert(nums: &nums, num: 6, index: 3) print("在索引 3 处插入数字 6 ,得到 nums = \(nums)") /* 删除元素 */ remove(nums: &nums, index: 2) print("删除索引 2 处的元素,得到 nums = \(nums)") /* 遍历数组 */ traverse(nums: nums) /* 查找元素 */ let index = find(nums: nums, target: 3) print("在 nums 中查找元素 3 ,得到索引 = \(index)") } } ================================================ FILE: codes/swift/chapter_array_and_linkedlist/linked_list.swift ================================================ /** * File: linked_list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 在链表的节点 n0 之后插入节点 P */ func insert(n0: ListNode, P: ListNode) { let n1 = n0.next P.next = n1 n0.next = P } /* 删除链表的节点 n0 之后的首个节点 */ func remove(n0: ListNode) { if n0.next == nil { return } // n0 -> P -> n1 let P = n0.next let n1 = P?.next n0.next = n1 } /* 访问链表中索引为 index 的节点 */ func access(head: ListNode, index: Int) -> ListNode? { var head: ListNode? = head for _ in 0 ..< index { if head == nil { return nil } head = head?.next } return head } /* 在链表中查找值为 target 的首个节点 */ func find(head: ListNode, target: Int) -> Int { var head: ListNode? = head var index = 0 while head != nil { if head?.val == target { return index } head = head?.next index += 1 } return -1 } @main enum LinkedList { /* Driver Code */ static func main() { /* 初始化链表 */ // 初始化各个节点 let n0 = ListNode(x: 1) let n1 = ListNode(x: 3) let n2 = ListNode(x: 2) let n3 = ListNode(x: 5) let n4 = ListNode(x: 4) // 构建节点之间的引用 n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 print("初始化的链表为") PrintUtil.printLinkedList(head: n0) /* 插入节点 */ insert(n0: n0, P: ListNode(x: 0)) print("插入节点后的链表为") PrintUtil.printLinkedList(head: n0) /* 删除节点 */ remove(n0: n0) print("删除节点后的链表为") PrintUtil.printLinkedList(head: n0) /* 访问节点 */ let node = access(head: n0, index: 3) print("链表中索引 3 处的节点的值 = \(node!.val)") /* 查找节点 */ let index = find(head: n0, target: 2) print("链表中值为 2 的节点的索引 = \(index)") } } ================================================ FILE: codes/swift/chapter_array_and_linkedlist/list.swift ================================================ /** * File: list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ @main enum List { /* Driver Code */ static func main() { /* 初始化列表 */ var nums = [1, 3, 2, 5, 4] print("列表 nums = \(nums)") /* 访问元素 */ let num = nums[1] print("访问索引 1 处的元素,得到 num = \(num)") /* 更新元素 */ nums[1] = 0 print("将索引 1 处的元素更新为 0 ,得到 nums = \(nums)") /* 清空列表 */ nums.removeAll() print("清空列表后 nums = \(nums)") /* 在尾部添加元素 */ nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) print("添加元素后 nums = \(nums)") /* 在中间插入元素 */ nums.insert(6, at: 3) print("在索引 3 处插入数字 6 ,得到 nums = \(nums)") /* 删除元素 */ nums.remove(at: 3) print("删除索引 3 处的元素,得到 nums = \(nums)") /* 通过索引遍历列表 */ var count = 0 for i in nums.indices { count += nums[i] } /* 直接遍历列表元素 */ count = 0 for x in nums { count += x } /* 拼接两个列表 */ let nums1 = [6, 8, 7, 10, 9] nums.append(contentsOf: nums1) print("将列表 nums1 拼接到 nums 之后,得到 nums = \(nums)") /* 排序列表 */ nums.sort() print("排序列表后 nums = \(nums)") } } ================================================ FILE: codes/swift/chapter_array_and_linkedlist/my_list.swift ================================================ /** * File: my_list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ /* 列表类 */ class MyList { private var arr: [Int] // 数组(存储列表元素) private var _capacity: Int // 列表容量 private var _size: Int // 列表长度(当前元素数量) private let extendRatio: Int // 每次列表扩容的倍数 /* 构造方法 */ init() { _capacity = 10 _size = 0 extendRatio = 2 arr = Array(repeating: 0, count: _capacity) } /* 获取列表长度(当前元素数量)*/ func size() -> Int { _size } /* 获取列表容量 */ func capacity() -> Int { _capacity } /* 访问元素 */ func get(index: Int) -> Int { // 索引如果越界则抛出错误,下同 if index < 0 || index >= size() { fatalError("索引越界") } return arr[index] } /* 更新元素 */ func set(index: Int, num: Int) { if index < 0 || index >= size() { fatalError("索引越界") } arr[index] = num } /* 在尾部添加元素 */ func add(num: Int) { // 元素数量超出容量时,触发扩容机制 if size() == capacity() { extendCapacity() } arr[size()] = num // 更新元素数量 _size += 1 } /* 在中间插入元素 */ func insert(index: Int, num: Int) { if index < 0 || index >= size() { fatalError("索引越界") } // 元素数量超出容量时,触发扩容机制 if size() == capacity() { extendCapacity() } // 将索引 index 以及之后的元素都向后移动一位 for j in (index ..< size()).reversed() { arr[j + 1] = arr[j] } arr[index] = num // 更新元素数量 _size += 1 } /* 删除元素 */ @discardableResult func remove(index: Int) -> Int { if index < 0 || index >= size() { fatalError("索引越界") } let num = arr[index] // 将将索引 index 之后的元素都向前移动一位 for j in index ..< (size() - 1) { arr[j] = arr[j + 1] } // 更新元素数量 _size -= 1 // 返回被删除的元素 return num } /* 列表扩容 */ func extendCapacity() { // 新建一个长度为原数组 extendRatio 倍的新数组,并将原数组复制到新数组 arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1)) // 更新列表容量 _capacity = arr.count } /* 将列表转换为数组 */ func toArray() -> [Int] { Array(arr.prefix(size())) } } @main enum _MyList { /* Driver Code */ static func main() { /* 初始化列表 */ let nums = MyList() /* 在尾部添加元素 */ nums.add(num: 1) nums.add(num: 3) nums.add(num: 2) nums.add(num: 5) nums.add(num: 4) print("列表 nums = \(nums.toArray()) ,容量 = \(nums.capacity()) ,长度 = \(nums.size())") /* 在中间插入元素 */ nums.insert(index: 3, num: 6) print("在索引 3 处插入数字 6 ,得到 nums = \(nums.toArray())") /* 删除元素 */ nums.remove(index: 3) print("删除索引 3 处的元素,得到 nums = \(nums.toArray())") /* 访问元素 */ let num = nums.get(index: 1) print("访问索引 1 处的元素,得到 num = \(num)") /* 更新元素 */ nums.set(index: 1, num: 0) print("将索引 1 处的元素更新为 0 ,得到 nums = \(nums.toArray())") /* 测试扩容机制 */ for i in 0 ..< 10 { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 nums.add(num: i) } print("扩容后的列表 nums = \(nums.toArray()) ,容量 = \(nums.capacity()) ,长度 = \(nums.size())") } } ================================================ FILE: codes/swift/chapter_backtracking/n_queens.swift ================================================ /** * File: n_queens.swift * Created Time: 2023-05-14 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯算法:n 皇后 */ func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) { // 当放置完所有行时,记录解 if row == n { res.append(state) return } // 遍历所有列 for col in 0 ..< n { // 计算该格子对应的主对角线和次对角线 let diag1 = row - col + n - 1 let diag2 = row + col // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 if !cols[col] && !diags1[diag1] && !diags2[diag2] { // 尝试:将皇后放置在该格子 state[row][col] = "Q" cols[col] = true diags1[diag1] = true diags2[diag2] = true // 放置下一行 backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) // 回退:将该格子恢复为空位 state[row][col] = "#" cols[col] = false diags1[diag1] = false diags2[diag2] = false } } } /* 求解 n 皇后 */ func nQueens(n: Int) -> [[[String]]] { // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 var state = Array(repeating: Array(repeating: "#", count: n), count: n) var cols = Array(repeating: false, count: n) // 记录列是否有皇后 var diags1 = Array(repeating: false, count: 2 * n - 1) // 记录主对角线上是否有皇后 var diags2 = Array(repeating: false, count: 2 * n - 1) // 记录次对角线上是否有皇后 var res: [[[String]]] = [] backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) return res } @main enum NQueens { /* Driver Code */ static func main() { let n = 4 let res = nQueens(n: n) print("输入棋盘长宽为 \(n)") print("皇后放置方案共有 \(res.count) 种") for state in res { print("--------------------") for row in state { print(row) } } } } ================================================ FILE: codes/swift/chapter_backtracking/permutations_i.swift ================================================ /** * File: permutations_i.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯算法:全排列 I */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // 当状态长度等于元素数量时,记录解 if state.count == choices.count { res.append(state) return } // 遍历所有选择 for (i, choice) in choices.enumerated() { // 剪枝:不允许重复选择元素 if !selected[i] { // 尝试:做出选择,更新状态 selected[i] = true state.append(choice) // 进行下一轮选择 backtrack(state: &state, choices: choices, selected: &selected, res: &res) // 回退:撤销选择,恢复到之前的状态 selected[i] = false state.removeLast() } } } /* 全排列 I */ func permutationsI(nums: [Int]) -> [[Int]] { var state: [Int] = [] var selected = Array(repeating: false, count: nums.count) var res: [[Int]] = [] backtrack(state: &state, choices: nums, selected: &selected, res: &res) return res } @main enum PermutationsI { /* Driver Code */ static func main() { let nums = [1, 2, 3] let res = permutationsI(nums: nums) print("输入数组 nums = \(nums)") print("所有排列 res = \(res)") } } ================================================ FILE: codes/swift/chapter_backtracking/permutations_ii.swift ================================================ /** * File: permutations_ii.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯算法:全排列 II */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // 当状态长度等于元素数量时,记录解 if state.count == choices.count { res.append(state) return } // 遍历所有选择 var duplicated: Set = [] for (i, choice) in choices.enumerated() { // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 if !selected[i], !duplicated.contains(choice) { // 尝试:做出选择,更新状态 duplicated.insert(choice) // 记录选择过的元素值 selected[i] = true state.append(choice) // 进行下一轮选择 backtrack(state: &state, choices: choices, selected: &selected, res: &res) // 回退:撤销选择,恢复到之前的状态 selected[i] = false state.removeLast() } } } /* 全排列 II */ func permutationsII(nums: [Int]) -> [[Int]] { var state: [Int] = [] var selected = Array(repeating: false, count: nums.count) var res: [[Int]] = [] backtrack(state: &state, choices: nums, selected: &selected, res: &res) return res } @main enum PermutationsII { /* Driver Code */ static func main() { let nums = [1, 2, 3] let res = permutationsII(nums: nums) print("输入数组 nums = \(nums)") print("所有排列 res = \(res)") } } ================================================ FILE: codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift ================================================ /** * File: preorder_traversal_i_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var res: [TreeNode] = [] /* 前序遍历:例题一 */ func preOrder(root: TreeNode?) { guard let root = root else { return } if root.val == 7 { // 记录解 res.append(root) } preOrder(root: root.left) preOrder(root: root.right) } @main enum PreorderTraversalICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\n初始化二叉树") PrintUtil.printTree(root: root) // 前序遍历 res = [] preOrder(root: root) print("\n输出所有值为 7 的节点") var vals: [Int] = [] for node in res { vals.append(node.val) } print(vals) } } ================================================ FILE: codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift ================================================ /** * File: preorder_traversal_ii_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var path: [TreeNode] = [] var res: [[TreeNode]] = [] /* 前序遍历:例题二 */ func preOrder(root: TreeNode?) { guard let root = root else { return } // 尝试 path.append(root) if root.val == 7 { // 记录解 res.append(path) } preOrder(root: root.left) preOrder(root: root.right) // 回退 path.removeLast() } @main enum PreorderTraversalIICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\n初始化二叉树") PrintUtil.printTree(root: root) // 前序遍历 path = [] res = [] preOrder(root: root) print("\n输出所有根节点到节点 7 的路径") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift ================================================ /** * File: preorder_traversal_iii_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var path: [TreeNode] = [] var res: [[TreeNode]] = [] /* 前序遍历:例题三 */ func preOrder(root: TreeNode?) { // 剪枝 guard let root = root, root.val != 3 else { return } // 尝试 path.append(root) if root.val == 7 { // 记录解 res.append(path) } preOrder(root: root.left) preOrder(root: root.right) // 回退 path.removeLast() } @main enum PreorderTraversalIIICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\n初始化二叉树") PrintUtil.printTree(root: root) // 前序遍历 path = [] res = [] preOrder(root: root) print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift ================================================ /** * File: preorder_traversal_iii_template.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 判断当前状态是否为解 */ func isSolution(state: [TreeNode]) -> Bool { !state.isEmpty && state.last!.val == 7 } /* 记录解 */ func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) { res.append(state) } /* 判断在当前状态下,该选择是否合法 */ func isValid(state: [TreeNode], choice: TreeNode?) -> Bool { choice != nil && choice!.val != 3 } /* 更新状态 */ func makeChoice(state: inout [TreeNode], choice: TreeNode) { state.append(choice) } /* 恢复状态 */ func undoChoice(state: inout [TreeNode], choice: TreeNode) { state.removeLast() } /* 回溯算法:例题三 */ func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) { // 检查是否为解 if isSolution(state: state) { recordSolution(state: state, res: &res) } // 遍历所有选择 for choice in choices { // 剪枝:检查选择是否合法 if isValid(state: state, choice: choice) { // 尝试:做出选择,更新状态 makeChoice(state: &state, choice: choice) // 进行下一轮选择 backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res) // 回退:撤销选择,恢复到之前的状态 undoChoice(state: &state, choice: choice) } } } @main enum PreorderTraversalIIITemplate { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\n初始化二叉树") PrintUtil.printTree(root: root) // 回溯算法 var state: [TreeNode] = [] var res: [[TreeNode]] = [] backtrack(state: &state, choices: [root].compactMap { $0 }, res: &res) print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: codes/swift/chapter_backtracking/subset_sum_i.swift ================================================ /** * File: subset_sum_i.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯算法:子集和 I */ func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { // 子集和等于 target 时,记录解 if target == 0 { res.append(state) return } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 for i in choices.indices.dropFirst(start) { // 剪枝一:若子集和超过 target ,则直接结束循环 // 这是因为数组已排序,后边元素更大,子集和一定超过 target if target - choices[i] < 0 { break } // 尝试:做出选择,更新 target, start state.append(choices[i]) // 进行下一轮选择 backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res) // 回退:撤销选择,恢复到之前的状态 state.removeLast() } } /* 求解子集和 I */ func subsetSumI(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // 状态(子集) let nums = nums.sorted() // 对 nums 进行排序 let start = 0 // 遍历起始点 var res: [[Int]] = [] // 结果列表(子集列表) backtrack(state: &state, target: target, choices: nums, start: start, res: &res) return res } @main enum SubsetSumI { /* Driver Code */ static func main() { let nums = [3, 4, 5] let target = 9 let res = subsetSumI(nums: nums, target: target) print("输入数组 nums = \(nums), target = \(target)") print("所有和等于 \(target) 的子集 res = \(res)") } } ================================================ FILE: codes/swift/chapter_backtracking/subset_sum_i_naive.swift ================================================ /** * File: subset_sum_i_naive.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯算法:子集和 I */ func backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) { // 子集和等于 target 时,记录解 if total == target { res.append(state) return } // 遍历所有选择 for i in choices.indices { // 剪枝:若子集和超过 target ,则跳过该选择 if total + choices[i] > target { continue } // 尝试:做出选择,更新元素和 total state.append(choices[i]) // 进行下一轮选择 backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res) // 回退:撤销选择,恢复到之前的状态 state.removeLast() } } /* 求解子集和 I(包含重复子集) */ func subsetSumINaive(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // 状态(子集) let total = 0 // 子集和 var res: [[Int]] = [] // 结果列表(子集列表) backtrack(state: &state, target: target, total: total, choices: nums, res: &res) return res } @main enum SubsetSumINaive { /* Driver Code */ static func main() { let nums = [3, 4, 5] let target = 9 let res = subsetSumINaive(nums: nums, target: target) print("输入数组 nums = \(nums), target = \(target)") print("所有和等于 \(target) 的子集 res = \(res)") print("请注意,该方法输出的结果包含重复集合") } } ================================================ FILE: codes/swift/chapter_backtracking/subset_sum_ii.swift ================================================ /** * File: subset_sum_ii.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯算法:子集和 II */ func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { // 子集和等于 target 时,记录解 if target == 0 { res.append(state) return } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 // 剪枝三:从 start 开始遍历,避免重复选择同一元素 for i in choices.indices.dropFirst(start) { // 剪枝一:若子集和超过 target ,则直接结束循环 // 这是因为数组已排序,后边元素更大,子集和一定超过 target if target - choices[i] < 0 { break } // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 if i > start, choices[i] == choices[i - 1] { continue } // 尝试:做出选择,更新 target, start state.append(choices[i]) // 进行下一轮选择 backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res) // 回退:撤销选择,恢复到之前的状态 state.removeLast() } } /* 求解子集和 II */ func subsetSumII(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // 状态(子集) let nums = nums.sorted() // 对 nums 进行排序 let start = 0 // 遍历起始点 var res: [[Int]] = [] // 结果列表(子集列表) backtrack(state: &state, target: target, choices: nums, start: start, res: &res) return res } @main enum SubsetSumII { /* Driver Code */ static func main() { let nums = [4, 4, 5] let target = 9 let res = subsetSumII(nums: nums, target: target) print("输入数组 nums = \(nums), target = \(target)") print("所有和等于 \(target) 的子集 res = \(res)") } } ================================================ FILE: codes/swift/chapter_computational_complexity/iteration.swift ================================================ /** * File: iteration.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* for 循环 */ func forLoop(n: Int) -> Int { var res = 0 // 循环求和 1, 2, ..., n-1, n for i in 1 ... n { res += i } return res } /* while 循环 */ func whileLoop(n: Int) -> Int { var res = 0 var i = 1 // 初始化条件变量 // 循环求和 1, 2, ..., n-1, n while i <= n { res += i i += 1 // 更新条件变量 } return res } /* while 循环(两次更新) */ func whileLoopII(n: Int) -> Int { var res = 0 var i = 1 // 初始化条件变量 // 循环求和 1, 4, 10, ... while i <= n { res += i // 更新条件变量 i += 1 i *= 2 } return res } /* 双层 for 循环 */ func nestedForLoop(n: Int) -> String { var res = "" // 循环 i = 1, 2, ..., n-1, n for i in 1 ... n { // 循环 j = 1, 2, ..., n-1, n for j in 1 ... n { res.append("(\(i), \(j)), ") } } return res } @main enum Iteration { /* Driver Code */ static func main() { let n = 5 var res = 0 res = forLoop(n: n) print("\nfor 循环的求和结果 res = \(res)") res = whileLoop(n: n) print("\nwhile 循环的求和结果 res = \(res)") res = whileLoopII(n: n) print("\nwhile 循环(两次更新)求和结果 res = \(res)") let resStr = nestedForLoop(n: n) print("\n双层 for 循环的遍历结果 \(resStr)") } } ================================================ FILE: codes/swift/chapter_computational_complexity/recursion.swift ================================================ /** * File: recursion.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 递归 */ func recur(n: Int) -> Int { // 终止条件 if n == 1 { return 1 } // 递:递归调用 let res = recur(n: n - 1) // 归:返回结果 return n + res } /* 使用迭代模拟递归 */ func forLoopRecur(n: Int) -> Int { // 使用一个显式的栈来模拟系统调用栈 var stack: [Int] = [] var res = 0 // 递:递归调用 for i in (1 ... n).reversed() { // 通过“入栈操作”模拟“递” stack.append(i) } // 归:返回结果 while !stack.isEmpty { // 通过“出栈操作”模拟“归” res += stack.removeLast() } // res = 1+2+3+...+n return res } /* 尾递归 */ func tailRecur(n: Int, res: Int) -> Int { // 终止条件 if n == 0 { return res } // 尾递归调用 return tailRecur(n: n - 1, res: res + n) } /* 斐波那契数列:递归 */ func fib(n: Int) -> Int { // 终止条件 f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1 } // 递归调用 f(n) = f(n-1) + f(n-2) let res = fib(n: n - 1) + fib(n: n - 2) // 返回结果 f(n) return res } @main enum Recursion { /* Driver Code */ static func main() { let n = 5 var res = 0 res = recursion.recur(n: n) print("\n递归函数的求和结果 res = \(res)") res = recursion.forLoopRecur(n: n) print("\n使用迭代模拟递归求和结果 res = \(res)") res = recursion.tailRecur(n: n, res: 0) print("\n尾递归函数的求和结果 res = \(res)") res = recursion.fib(n: n) print("\n斐波那契数列的第 \(n) 项为 \(res)") } } ================================================ FILE: codes/swift/chapter_computational_complexity/space_complexity.swift ================================================ /** * File: space_complexity.swift * Created Time: 2023-01-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 函数 */ @discardableResult func function() -> Int { // 执行某些操作 return 0 } /* 常数阶 */ func constant(n: Int) { // 常量、变量、对象占用 O(1) 空间 let a = 0 var b = 0 let nums = Array(repeating: 0, count: 10000) let node = ListNode(x: 0) // 循环中的变量占用 O(1) 空间 for _ in 0 ..< n { let c = 0 } // 循环中的函数占用 O(1) 空间 for _ in 0 ..< n { function() } } /* 线性阶 */ func linear(n: Int) { // 长度为 n 的数组占用 O(n) 空间 let nums = Array(repeating: 0, count: n) // 长度为 n 的列表占用 O(n) 空间 let nodes = (0 ..< n).map { ListNode(x: $0) } // 长度为 n 的哈希表占用 O(n) 空间 let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") }) } /* 线性阶(递归实现) */ func linearRecur(n: Int) { print("递归 n = \(n)") if n == 1 { return } linearRecur(n: n - 1) } /* 平方阶 */ func quadratic(n: Int) { // 二维列表占用 O(n^2) 空间 let numList = Array(repeating: Array(repeating: 0, count: n), count: n) } /* 平方阶(递归实现) */ @discardableResult func quadraticRecur(n: Int) -> Int { if n <= 0 { return 0 } // 数组 nums 长度为 n, n-1, ..., 2, 1 let nums = Array(repeating: 0, count: n) print("递归 n = \(n) 中的 nums 长度 = \(nums.count)") return quadraticRecur(n: n - 1) } /* 指数阶(建立满二叉树) */ func buildTree(n: Int) -> TreeNode? { if n == 0 { return nil } let root = TreeNode(x: 0) root.left = buildTree(n: n - 1) root.right = buildTree(n: n - 1) return root } @main enum SpaceComplexity { /* Driver Code */ static func main() { let n = 5 // 常数阶 constant(n: n) // 线性阶 linear(n: n) linearRecur(n: n) // 平方阶 quadratic(n: n) quadraticRecur(n: n) // 指数阶 let root = buildTree(n: n) PrintUtil.printTree(root: root) } } ================================================ FILE: codes/swift/chapter_computational_complexity/time_complexity.swift ================================================ /** * File: time_complexity.swift * Created Time: 2022-12-26 * Author: nuomi1 (nuomi1@qq.com) */ /* 常数阶 */ func constant(n: Int) -> Int { var count = 0 let size = 100_000 for _ in 0 ..< size { count += 1 } return count } /* 线性阶 */ func linear(n: Int) -> Int { var count = 0 for _ in 0 ..< n { count += 1 } return count } /* 线性阶(遍历数组) */ func arrayTraversal(nums: [Int]) -> Int { var count = 0 // 循环次数与数组长度成正比 for _ in nums { count += 1 } return count } /* 平方阶 */ func quadratic(n: Int) -> Int { var count = 0 // 循环次数与数据大小 n 成平方关系 for _ in 0 ..< n { for _ in 0 ..< n { count += 1 } } return count } /* 平方阶(冒泡排序) */ func bubbleSort(nums: inout [Int]) -> Int { var count = 0 // 计数器 // 外循环:未排序区间为 [0, i] for i in nums.indices.dropFirst().reversed() { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in 0 ..< i { if nums[j] > nums[j + 1] { // 交换 nums[j] 与 nums[j + 1] let tmp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = tmp count += 3 // 元素交换包含 3 个单元操作 } } } return count } /* 指数阶(循环实现) */ func exponential(n: Int) -> Int { var count = 0 var base = 1 // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for _ in 0 ..< n { for _ in 0 ..< base { count += 1 } base *= 2 } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count } /* 指数阶(递归实现) */ func expRecur(n: Int) -> Int { if n == 1 { return 1 } return expRecur(n: n - 1) + expRecur(n: n - 1) + 1 } /* 对数阶(循环实现) */ func logarithmic(n: Int) -> Int { var count = 0 var n = n while n > 1 { n = n / 2 count += 1 } return count } /* 对数阶(递归实现) */ func logRecur(n: Int) -> Int { if n <= 1 { return 0 } return logRecur(n: n / 2) + 1 } /* 线性对数阶 */ func linearLogRecur(n: Int) -> Int { if n <= 1 { return 1 } var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2) for _ in stride(from: 0, to: n, by: 1) { count += 1 } return count } /* 阶乘阶(递归实现) */ func factorialRecur(n: Int) -> Int { if n == 0 { return 1 } var count = 0 // 从 1 个分裂出 n 个 for _ in 0 ..< n { count += factorialRecur(n: n - 1) } return count } @main enum TimeComplexity { /* Driver Code */ static func main() { // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 let n = 8 print("输入数据大小 n = \(n)") var count = constant(n: n) print("常数阶的操作数量 = \(count)") count = linear(n: n) print("线性阶的操作数量 = \(count)") count = arrayTraversal(nums: Array(repeating: 0, count: n)) print("线性阶(遍历数组)的操作数量 = \(count)") count = quadratic(n: n) print("平方阶的操作数量 = \(count)") var nums = Array(stride(from: n, to: 0, by: -1)) // [n,n-1,...,2,1] count = bubbleSort(nums: &nums) print("平方阶(冒泡排序)的操作数量 = \(count)") count = exponential(n: n) print("指数阶(循环实现)的操作数量 = \(count)") count = expRecur(n: n) print("指数阶(递归实现)的操作数量 = \(count)") count = logarithmic(n: n) print("对数阶(循环实现)的操作数量 = \(count)") count = logRecur(n: n) print("对数阶(递归实现)的操作数量 = \(count)") count = linearLogRecur(n: n) print("线性对数阶(递归实现)的操作数量 = \(count)") count = factorialRecur(n: n) print("阶乘阶(递归实现)的操作数量 = \(count)") } } ================================================ FILE: codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift ================================================ /** * File: worst_best_time_complexity.swift * Created Time: 2022-12-26 * Author: nuomi1 (nuomi1@qq.com) */ /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ func randomNumbers(n: Int) -> [Int] { // 生成数组 nums = { 1, 2, 3, ..., n } var nums = Array(1 ... n) // 随机打乱数组元素 nums.shuffle() return nums } /* 查找数组 nums 中数字 1 所在索引 */ func findOne(nums: [Int]) -> Int { for i in nums.indices { // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if nums[i] == 1 { return i } } return -1 } @main enum WorstBestTimeComplexity { /* Driver Code */ static func main() { for _ in 0 ..< 10 { let n = 100 let nums = randomNumbers(n: n) let index = findOne(nums: nums) print("数组 [ 1, 2, ..., n ] 被打乱后 = \(nums)") print("数字 1 的索引为 \(index)") } } } ================================================ FILE: codes/swift/chapter_divide_and_conquer/binary_search_recur.swift ================================================ /** * File: binary_search_recur.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 二分查找:问题 f(i, j) */ func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int { // 若区间为空,代表无目标元素,则返回 -1 if i > j { return -1 } // 计算中点索引 m let m = (i + j) / 2 if nums[m] < target { // 递归子问题 f(m+1, j) return dfs(nums: nums, target: target, i: m + 1, j: j) } else if nums[m] > target { // 递归子问题 f(i, m-1) return dfs(nums: nums, target: target, i: i, j: m - 1) } else { // 找到目标元素,返回其索引 return m } } /* 二分查找 */ func binarySearch(nums: [Int], target: Int) -> Int { // 求解问题 f(0, n-1) dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1) } @main enum BinarySearchRecur { /* Driver Code */ static func main() { let target = 6 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] // 二分查找(双闭区间) let index = binarySearch(nums: nums, target: target) print("目标元素 6 的索引 = \(index)") } } ================================================ FILE: codes/swift/chapter_divide_and_conquer/build_tree.swift ================================================ /** * File: build_tree.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 构建二叉树:分治 */ func dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? { // 子树区间为空时终止 if r - l < 0 { return nil } // 初始化根节点 let root = TreeNode(x: preorder[i]) // 查询 m ,从而划分左右子树 let m = inorderMap[preorder[i]]! // 子问题:构建左子树 root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1) // 子问题:构建右子树 root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r) // 返回根节点 return root } /* 构建二叉树 */ func buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? { // 初始化哈希表,存储 inorder 元素到索引的映射 let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset } return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1) } @main enum BuildTree { /* Driver Code */ static func main() { let preorder = [3, 9, 2, 1, 7] let inorder = [9, 3, 1, 2, 7] print("前序遍历 = \(preorder)") print("中序遍历 = \(inorder)") let root = buildTree(preorder: preorder, inorder: inorder) print("构建的二叉树为:") PrintUtil.printTree(root: root) } } ================================================ FILE: codes/swift/chapter_divide_and_conquer/hanota.swift ================================================ /** * File: hanota.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 移动一个圆盘 */ func move(src: inout [Int], tar: inout [Int]) { // 从 src 顶部拿出一个圆盘 let pan = src.popLast()! // 将圆盘放入 tar 顶部 tar.append(pan) } /* 求解汉诺塔问题 f(i) */ func dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) { // 若 src 只剩下一个圆盘,则直接将其移到 tar if i == 1 { move(src: &src, tar: &tar) return } // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf dfs(i: i - 1, src: &src, buf: &tar, tar: &buf) // 子问题 f(1) :将 src 剩余一个圆盘移到 tar move(src: &src, tar: &tar) // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar dfs(i: i - 1, src: &buf, buf: &src, tar: &tar) } /* 求解汉诺塔问题 */ func solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) { let n = A.count // 列表尾部是柱子顶部 // 将 src 顶部 n 个圆盘借助 B 移到 C dfs(i: n, src: &A, buf: &B, tar: &C) } @main enum Hanota { /* Driver Code */ static func main() { // 列表尾部是柱子顶部 var A = [5, 4, 3, 2, 1] var B: [Int] = [] var C: [Int] = [] print("初始状态下:") print("A = \(A)") print("B = \(B)") print("C = \(C)") solveHanota(A: &A, B: &B, C: &C) print("圆盘移动完成后:") print("A = \(A)") print("B = \(B)") print("C = \(C)") } } ================================================ FILE: codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift ================================================ /** * File: climbing_stairs_backtrack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯 */ func backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) { // 当爬到第 n 阶时,方案数量加 1 if state == n { res[0] += 1 } // 遍历所有选择 for choice in choices { // 剪枝:不允许越过第 n 阶 if state + choice > n { continue } // 尝试:做出选择,更新状态 backtrack(choices: choices, state: state + choice, n: n, res: &res) // 回退 } } /* 爬楼梯:回溯 */ func climbingStairsBacktrack(n: Int) -> Int { let choices = [1, 2] // 可选择向上爬 1 阶或 2 阶 let state = 0 // 从第 0 阶开始爬 var res: [Int] = [] res.append(0) // 使用 res[0] 记录方案数量 backtrack(choices: choices, state: state, n: n, res: &res) return res[0] } @main enum ClimbingStairsBacktrack { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsBacktrack(n: n) print("爬 \(n) 阶楼梯共有 \(res) 种方案") } } ================================================ FILE: codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift ================================================ /** * File: climbing_stairs_constraint_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 带约束爬楼梯:动态规划 */ func climbingStairsConstraintDP(n: Int) -> Int { if n == 1 || n == 2 { return 1 } // 初始化 dp 表,用于存储子问题的解 var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1) // 初始状态:预设最小子问题的解 dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // 状态转移:从较小子问题逐步求解较大子问题 for i in 3 ... n { dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] } return dp[n][1] + dp[n][2] } @main enum ClimbingStairsConstraintDP { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsConstraintDP(n: n) print("爬 \(n) 阶楼梯共有 \(res) 种方案") } } ================================================ FILE: codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift ================================================ /** * File: climbing_stairs_dfs.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 搜索 */ func dfs(i: Int) -> Int { // 已知 dp[1] 和 dp[2] ,返回之 if i == 1 || i == 2 { return i } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i: i - 1) + dfs(i: i - 2) return count } /* 爬楼梯:搜索 */ func climbingStairsDFS(n: Int) -> Int { dfs(i: n) } @main enum ClimbingStairsDFS { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsDFS(n: n) print("爬 \(n) 阶楼梯共有 \(res) 种方案") } } ================================================ FILE: codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift ================================================ /** * File: climbing_stairs_dfs_mem.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 记忆化搜索 */ func dfs(i: Int, mem: inout [Int]) -> Int { // 已知 dp[1] 和 dp[2] ,返回之 if i == 1 || i == 2 { return i } // 若存在记录 dp[i] ,则直接返回之 if mem[i] != -1 { return mem[i] } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem) // 记录 dp[i] mem[i] = count return count } /* 爬楼梯:记忆化搜索 */ func climbingStairsDFSMem(n: Int) -> Int { // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 var mem = Array(repeating: -1, count: n + 1) return dfs(i: n, mem: &mem) } @main enum ClimbingStairsDFSMem { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsDFSMem(n: n) print("爬 \(n) 阶楼梯共有 \(res) 种方案") } } ================================================ FILE: codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift ================================================ /** * File: climbing_stairs_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 爬楼梯:动态规划 */ func climbingStairsDP(n: Int) -> Int { if n == 1 || n == 2 { return n } // 初始化 dp 表,用于存储子问题的解 var dp = Array(repeating: 0, count: n + 1) // 初始状态:预设最小子问题的解 dp[1] = 1 dp[2] = 2 // 状态转移:从较小子问题逐步求解较大子问题 for i in 3 ... n { dp[i] = dp[i - 1] + dp[i - 2] } return dp[n] } /* 爬楼梯:空间优化后的动态规划 */ func climbingStairsDPComp(n: Int) -> Int { if n == 1 || n == 2 { return n } var a = 1 var b = 2 for _ in 3 ... n { (a, b) = (b, a + b) } return b } @main enum ClimbingStairsDP { /* Driver Code */ static func main() { let n = 9 var res = climbingStairsDP(n: n) print("爬 \(n) 阶楼梯共有 \(res) 种方案") res = climbingStairsDPComp(n: n) print("爬 \(n) 阶楼梯共有 \(res) 种方案") } } ================================================ FILE: codes/swift/chapter_dynamic_programming/coin_change.swift ================================================ /** * File: coin_change.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 零钱兑换:动态规划 */ func coinChangeDP(coins: [Int], amt: Int) -> Int { let n = coins.count let MAX = amt + 1 // 初始化 dp 表 var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) // 状态转移:首行首列 for a in 1 ... amt { dp[0][a] = MAX } // 状态转移:其余行和列 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a] } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) } } } return dp[n][amt] != MAX ? dp[n][amt] : -1 } /* 零钱兑换:空间优化后的动态规划 */ func coinChangeDPComp(coins: [Int], amt: Int) -> Int { let n = coins.count let MAX = amt + 1 // 初始化 dp 表 var dp = Array(repeating: MAX, count: amt + 1) dp[0] = 0 // 状态转移 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 若超过目标金额,则不选硬币 i dp[a] = dp[a] } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) } } } return dp[amt] != MAX ? dp[amt] : -1 } @main enum CoinChange { /* Driver Code */ static func main() { let coins = [1, 2, 5] let amt = 4 // 动态规划 var res = coinChangeDP(coins: coins, amt: amt) print("凑到目标金额所需的最少硬币数量为 \(res)") // 空间优化后的动态规划 res = coinChangeDPComp(coins: coins, amt: amt) print("凑到目标金额所需的最少硬币数量为 \(res)") } } ================================================ FILE: codes/swift/chapter_dynamic_programming/coin_change_ii.swift ================================================ /** * File: coin_change_ii.swift * Created Time: 2023-07-16 * Author: nuomi1 (nuomi1@qq.com) */ /* 零钱兑换 II:动态规划 */ func coinChangeIIDP(coins: [Int], amt: Int) -> Int { let n = coins.count // 初始化 dp 表 var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) // 初始化首列 for i in 0 ... n { dp[i][0] = 1 } // 状态转移 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a] } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] } } } return dp[n][amt] } /* 零钱兑换 II:空间优化后的动态规划 */ func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { let n = coins.count // 初始化 dp 表 var dp = Array(repeating: 0, count: amt + 1) dp[0] = 1 // 状态转移 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 若超过目标金额,则不选硬币 i dp[a] = dp[a] } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]] } } } return dp[amt] } @main enum CoinChangeII { /* Driver Code */ static func main() { let coins = [1, 2, 5] let amt = 5 // 动态规划 var res = coinChangeIIDP(coins: coins, amt: amt) print("凑出目标金额的硬币组合数量为 \(res)") // 空间优化后的动态规划 res = coinChangeIIDPComp(coins: coins, amt: amt) print("凑出目标金额的硬币组合数量为 \(res)") } } ================================================ FILE: codes/swift/chapter_dynamic_programming/edit_distance.swift ================================================ /** * File: edit_distance.swift * Created Time: 2023-07-16 * Author: nuomi1 (nuomi1@qq.com) */ /* 编辑距离:暴力搜索 */ func editDistanceDFS(s: String, t: String, i: Int, j: Int) -> Int { // 若 s 和 t 都为空,则返回 0 if i == 0, j == 0 { return 0 } // 若 s 为空,则返回 t 长度 if i == 0 { return j } // 若 t 为空,则返回 s 长度 if j == 0 { return i } // 若两字符相等,则直接跳过此两字符 if s.utf8CString[i - 1] == t.utf8CString[j - 1] { return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) } // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) // 返回最少编辑步数 return min(min(insert, delete), replace) + 1 } /* 编辑距离:记忆化搜索 */ func editDistanceDFSMem(s: String, t: String, mem: inout [[Int]], i: Int, j: Int) -> Int { // 若 s 和 t 都为空,则返回 0 if i == 0, j == 0 { return 0 } // 若 s 为空,则返回 t 长度 if i == 0 { return j } // 若 t 为空,则返回 s 长度 if j == 0 { return i } // 若已有记录,则直接返回之 if mem[i][j] != -1 { return mem[i][j] } // 若两字符相等,则直接跳过此两字符 if s.utf8CString[i - 1] == t.utf8CString[j - 1] { return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) } // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) // 记录并返回最少编辑步数 mem[i][j] = min(min(insert, delete), replace) + 1 return mem[i][j] } /* 编辑距离:动态规划 */ func editDistanceDP(s: String, t: String) -> Int { let n = s.utf8CString.count let m = t.utf8CString.count var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1) // 状态转移:首行首列 for i in 1 ... n { dp[i][0] = i } for j in 1 ... m { dp[0][j] = j } // 状态转移:其余行和列 for i in 1 ... n { for j in 1 ... m { if s.utf8CString[i - 1] == t.utf8CString[j - 1] { // 若两字符相等,则直接跳过此两字符 dp[i][j] = dp[i - 1][j - 1] } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 } } } return dp[n][m] } /* 编辑距离:空间优化后的动态规划 */ func editDistanceDPComp(s: String, t: String) -> Int { let n = s.utf8CString.count let m = t.utf8CString.count var dp = Array(repeating: 0, count: m + 1) // 状态转移:首行 for j in 1 ... m { dp[j] = j } // 状态转移:其余行 for i in 1 ... n { // 状态转移:首列 var leftup = dp[0] // 暂存 dp[i-1, j-1] dp[0] = i // 状态转移:其余列 for j in 1 ... m { let temp = dp[j] if s.utf8CString[i - 1] == t.utf8CString[j - 1] { // 若两字符相等,则直接跳过此两字符 dp[j] = leftup } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 } leftup = temp // 更新为下一轮的 dp[i-1, j-1] } } return dp[m] } @main enum EditDistance { /* Driver Code */ static func main() { let s = "bag" let t = "pack" let n = s.utf8CString.count let m = t.utf8CString.count // 暴力搜索 var res = editDistanceDFS(s: s, t: t, i: n, j: m) print("将 \(s) 更改为 \(t) 最少需要编辑 \(res) 步") // 记忆化搜索 var mem = Array(repeating: Array(repeating: -1, count: m + 1), count: n + 1) res = editDistanceDFSMem(s: s, t: t, mem: &mem, i: n, j: m) print("将 \(s) 更改为 \(t) 最少需要编辑 \(res) 步") // 动态规划 res = editDistanceDP(s: s, t: t) print("将 \(s) 更改为 \(t) 最少需要编辑 \(res) 步") // 空间优化后的动态规划 res = editDistanceDPComp(s: s, t: t) print("将 \(s) 更改为 \(t) 最少需要编辑 \(res) 步") } } ================================================ FILE: codes/swift/chapter_dynamic_programming/knapsack.swift ================================================ /** * File: knapsack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 0-1 背包:暴力搜索 */ func knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if i == 0 || c == 0 { return 0 } // 若超过背包容量,则只能选择不放入背包 if wgt[i - 1] > c { return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) } // 计算不放入和放入物品 i 的最大价值 let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] // 返回两种方案中价值更大的那一个 return max(no, yes) } /* 0-1 背包:记忆化搜索 */ func knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if i == 0 || c == 0 { return 0 } // 若已有记录,则直接返回 if mem[i][c] != -1 { return mem[i][c] } // 若超过背包容量,则只能选择不放入背包 if wgt[i - 1] > c { return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) } // 计算不放入和放入物品 i 的最大价值 let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] // 记录并返回两种方案中价值更大的那一个 mem[i][c] = max(no, yes) return mem[i][c] } /* 0-1 背包:动态规划 */ func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // 初始化 dp 表 var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) // 状态转移 for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c] } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) } } } return dp[n][cap] } /* 0-1 背包:空间优化后的动态规划 */ func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // 初始化 dp 表 var dp = Array(repeating: 0, count: cap + 1) // 状态转移 for i in 1 ... n { // 倒序遍历 for c in (1 ... cap).reversed() { if wgt[i - 1] <= c { // 不选和选物品 i 这两种方案的较大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) } } } return dp[cap] } @main enum Knapsack { /* Driver Code */ static func main() { let wgt = [10, 20, 30, 40, 50] let val = [50, 120, 150, 210, 240] let cap = 50 let n = wgt.count // 暴力搜索 var res = knapsackDFS(wgt: wgt, val: val, i: n, c: cap) print("不超过背包容量的最大物品价值为 \(res)") // 记忆化搜索 var mem = Array(repeating: Array(repeating: -1, count: cap + 1), count: n + 1) res = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: n, c: cap) print("不超过背包容量的最大物品价值为 \(res)") // 动态规划 res = knapsackDP(wgt: wgt, val: val, cap: cap) print("不超过背包容量的最大物品价值为 \(res)") // 空间优化后的动态规划 res = knapsackDPComp(wgt: wgt, val: val, cap: cap) print("不超过背包容量的最大物品价值为 \(res)") } } ================================================ FILE: codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift ================================================ /** * File: min_cost_climbing_stairs_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 爬楼梯最小代价:动态规划 */ func minCostClimbingStairsDP(cost: [Int]) -> Int { let n = cost.count - 1 if n == 1 || n == 2 { return cost[n] } // 初始化 dp 表,用于存储子问题的解 var dp = Array(repeating: 0, count: n + 1) // 初始状态:预设最小子问题的解 dp[1] = cost[1] dp[2] = cost[2] // 状态转移:从较小子问题逐步求解较大子问题 for i in 3 ... n { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] } return dp[n] } /* 爬楼梯最小代价:空间优化后的动态规划 */ func minCostClimbingStairsDPComp(cost: [Int]) -> Int { let n = cost.count - 1 if n == 1 || n == 2 { return cost[n] } var (a, b) = (cost[1], cost[2]) for i in 3 ... n { (a, b) = (b, min(a, b) + cost[i]) } return b } @main enum MinCostClimbingStairsDP { /* Driver Code */ static func main() { let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] print("输入楼梯的代价列表为 \(cost)") var res = minCostClimbingStairsDP(cost: cost) print("爬完楼梯的最低代价为 \(res)") res = minCostClimbingStairsDPComp(cost: cost) print("爬完楼梯的最低代价为 \(res)") } } ================================================ FILE: codes/swift/chapter_dynamic_programming/min_path_sum.swift ================================================ /** * File: min_path_sum.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 最小路径和:暴力搜索 */ func minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int { // 若为左上角单元格,则终止搜索 if i == 0, j == 0 { return grid[0][0] } // 若行列索引越界,则返回 +∞ 代价 if i < 0 || j < 0 { return .max } // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 let up = minPathSumDFS(grid: grid, i: i - 1, j: j) let left = minPathSumDFS(grid: grid, i: i, j: j - 1) // 返回从左上角到 (i, j) 的最小路径代价 return min(left, up) + grid[i][j] } /* 最小路径和:记忆化搜索 */ func minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int { // 若为左上角单元格,则终止搜索 if i == 0, j == 0 { return grid[0][0] } // 若行列索引越界,则返回 +∞ 代价 if i < 0 || j < 0 { return .max } // 若已有记录,则直接返回 if mem[i][j] != -1 { return mem[i][j] } // 左边和上边单元格的最小路径代价 let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j) let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1) // 记录并返回左上角到 (i, j) 的最小路径代价 mem[i][j] = min(left, up) + grid[i][j] return mem[i][j] } /* 最小路径和:动态规划 */ func minPathSumDP(grid: [[Int]]) -> Int { let n = grid.count let m = grid[0].count // 初始化 dp 表 var dp = Array(repeating: Array(repeating: 0, count: m), count: n) dp[0][0] = grid[0][0] // 状态转移:首行 for j in 1 ..< m { dp[0][j] = dp[0][j - 1] + grid[0][j] } // 状态转移:首列 for i in 1 ..< n { dp[i][0] = dp[i - 1][0] + grid[i][0] } // 状态转移:其余行和列 for i in 1 ..< n { for j in 1 ..< m { dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] } } return dp[n - 1][m - 1] } /* 最小路径和:空间优化后的动态规划 */ func minPathSumDPComp(grid: [[Int]]) -> Int { let n = grid.count let m = grid[0].count // 初始化 dp 表 var dp = Array(repeating: 0, count: m) // 状态转移:首行 dp[0] = grid[0][0] for j in 1 ..< m { dp[j] = dp[j - 1] + grid[0][j] } // 状态转移:其余行 for i in 1 ..< n { // 状态转移:首列 dp[0] = dp[0] + grid[i][0] // 状态转移:其余列 for j in 1 ..< m { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] } } return dp[m - 1] } @main enum MinPathSum { /* Driver Code */ static func main() { let grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ] let n = grid.count let m = grid[0].count // 暴力搜索 var res = minPathSumDFS(grid: grid, i: n - 1, j: m - 1) print("从左上角到右下角的最小路径和为 \(res)") // 记忆化搜索 var mem = Array(repeating: Array(repeating: -1, count: m), count: n) res = minPathSumDFSMem(grid: grid, mem: &mem, i: n - 1, j: m - 1) print("从左上角到右下角的最小路径和为 \(res)") // 动态规划 res = minPathSumDP(grid: grid) print("从左上角到右下角的最小路径和为 \(res)") // 空间优化后的动态规划 res = minPathSumDPComp(grid: grid) print("从左上角到右下角的最小路径和为 \(res)") } } ================================================ FILE: codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift ================================================ /** * File: unbounded_knapsack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 完全背包:动态规划 */ func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // 初始化 dp 表 var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) // 状态转移 for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c] } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) } } } return dp[n][cap] } /* 完全背包:空间优化后的动态规划 */ func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // 初始化 dp 表 var dp = Array(repeating: 0, count: cap + 1) // 状态转移 for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // 若超过背包容量,则不选物品 i dp[c] = dp[c] } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) } } } return dp[cap] } @main enum UnboundedKnapsack { /* Driver Code */ static func main() { let wgt = [1, 2, 3] let val = [5, 11, 15] let cap = 4 // 动态规划 var res = unboundedKnapsackDP(wgt: wgt, val: val, cap: cap) print("不超过背包容量的最大物品价值为 \(res)") // 空间优化后的动态规划 res = unboundedKnapsackDPComp(wgt: wgt, val: val, cap: cap) print("不超过背包容量的最大物品价值为 \(res)") } } ================================================ FILE: codes/swift/chapter_graph/graph_adjacency_list.swift ================================================ /** * File: graph_adjacency_list.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 基于邻接表实现的无向图类 */ public class GraphAdjList { // 邻接表,key:顶点,value:该顶点的所有邻接顶点 public private(set) var adjList: [Vertex: [Vertex]] /* 构造方法 */ public init(edges: [[Vertex]]) { adjList = [:] // 添加所有顶点和边 for edge in edges { addVertex(vet: edge[0]) addVertex(vet: edge[1]) addEdge(vet1: edge[0], vet2: edge[1]) } } /* 获取顶点数量 */ public func size() -> Int { adjList.count } /* 添加边 */ public func addEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("参数错误") } // 添加边 vet1 - vet2 adjList[vet1]?.append(vet2) adjList[vet2]?.append(vet1) } /* 删除边 */ public func removeEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("参数错误") } // 删除边 vet1 - vet2 adjList[vet1]?.removeAll { $0 == vet2 } adjList[vet2]?.removeAll { $0 == vet1 } } /* 添加顶点 */ public func addVertex(vet: Vertex) { if adjList[vet] != nil { return } // 在邻接表中添加一个新链表 adjList[vet] = [] } /* 删除顶点 */ public func removeVertex(vet: Vertex) { if adjList[vet] == nil { fatalError("参数错误") } // 在邻接表中删除顶点 vet 对应的链表 adjList.removeValue(forKey: vet) // 遍历其他顶点的链表,删除所有包含 vet 的边 for key in adjList.keys { adjList[key]?.removeAll { $0 == vet } } } /* 打印邻接表 */ public func print() { Swift.print("邻接表 =") for (vertex, list) in adjList { let list = list.map { $0.val } Swift.print("\(vertex.val): \(list),") } } } #if !TARGET @main enum GraphAdjacencyList { /* Driver Code */ static func main() { /* 初始化无向图 */ let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] let graph = GraphAdjList(edges: edges) print("\n初始化后,图为") graph.print() /* 添加边 */ // 顶点 1, 2 即 v[0], v[2] graph.addEdge(vet1: v[0], vet2: v[2]) print("\n添加边 1-2 后,图为") graph.print() /* 删除边 */ // 顶点 1, 3 即 v[0], v[1] graph.removeEdge(vet1: v[0], vet2: v[1]) print("\n删除边 1-3 后,图为") graph.print() /* 添加顶点 */ let v5 = Vertex(val: 6) graph.addVertex(vet: v5) print("\n添加顶点 6 后,图为") graph.print() /* 删除顶点 */ // 顶点 3 即 v[1] graph.removeVertex(vet: v[1]) print("\n删除顶点 3 后,图为") graph.print() } } #endif ================================================ FILE: codes/swift/chapter_graph/graph_adjacency_matrix.swift ================================================ /** * File: graph_adjacency_matrix.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 基于邻接矩阵实现的无向图类 */ class GraphAdjMat { private var vertices: [Int] // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” private var adjMat: [[Int]] // 邻接矩阵,行列索引对应“顶点索引” /* 构造方法 */ init(vertices: [Int], edges: [[Int]]) { self.vertices = [] adjMat = [] // 添加顶点 for val in vertices { addVertex(val: val) } // 添加边 // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 for e in edges { addEdge(i: e[0], j: e[1]) } } /* 获取顶点数量 */ func size() -> Int { vertices.count } /* 添加顶点 */ func addVertex(val: Int) { let n = size() // 向顶点列表中添加新顶点的值 vertices.append(val) // 在邻接矩阵中添加一行 let newRow = Array(repeating: 0, count: n) adjMat.append(newRow) // 在邻接矩阵中添加一列 for i in adjMat.indices { adjMat[i].append(0) } } /* 删除顶点 */ func removeVertex(index: Int) { if index >= size() { fatalError("越界") } // 在顶点列表中移除索引 index 的顶点 vertices.remove(at: index) // 在邻接矩阵中删除索引 index 的行 adjMat.remove(at: index) // 在邻接矩阵中删除索引 index 的列 for i in adjMat.indices { adjMat[i].remove(at: index) } } /* 添加边 */ // 参数 i, j 对应 vertices 元素索引 func addEdge(i: Int, j: Int) { // 索引越界与相等处理 if i < 0 || j < 0 || i >= size() || j >= size() || i == j { fatalError("越界") } // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) adjMat[i][j] = 1 adjMat[j][i] = 1 } /* 删除边 */ // 参数 i, j 对应 vertices 元素索引 func removeEdge(i: Int, j: Int) { // 索引越界与相等处理 if i < 0 || j < 0 || i >= size() || j >= size() || i == j { fatalError("越界") } adjMat[i][j] = 0 adjMat[j][i] = 0 } /* 打印邻接矩阵 */ func print() { Swift.print("顶点列表 = ", terminator: "") Swift.print(vertices) Swift.print("邻接矩阵 =") PrintUtil.printMatrix(matrix: adjMat) } } @main enum GraphAdjacencyMatrix { /* Driver Code */ static func main() { /* 初始化无向图 */ // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 let vertices = [1, 3, 2, 5, 4] let edges = [[0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4]] let graph = GraphAdjMat(vertices: vertices, edges: edges) print("\n初始化后,图为") graph.print() /* 添加边 */ // 顶点 1, 2 的索引分别为 0, 2 graph.addEdge(i: 0, j: 2) print("\n添加边 1-2 后,图为") graph.print() /* 删除边 */ // 顶点 1, 3 的索引分别为 0, 1 graph.removeEdge(i: 0, j: 1) print("\n删除边 1-3 后,图为") graph.print() /* 添加顶点 */ graph.addVertex(val: 6) print("\n添加顶点 6 后,图为") graph.print() /* 删除顶点 */ // 顶点 3 的索引为 1 graph.removeVertex(index: 1) print("\n删除顶点 3 后,图为") graph.print() } } ================================================ FILE: codes/swift/chapter_graph/graph_bfs.swift ================================================ /** * File: graph_bfs.swift * Created Time: 2023-02-21 * Author: nuomi1 (nuomi1@qq.com) */ import graph_adjacency_list_target import utils /* 广度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { // 顶点遍历序列 var res: [Vertex] = [] // 哈希集合,用于记录已被访问过的顶点 var visited: Set = [startVet] // 队列用于实现 BFS var que: [Vertex] = [startVet] // 以顶点 vet 为起点,循环直至访问完所有顶点 while !que.isEmpty { let vet = que.removeFirst() // 队首顶点出队 res.append(vet) // 记录访问顶点 // 遍历该顶点的所有邻接顶点 for adjVet in graph.adjList[vet] ?? [] { if visited.contains(adjVet) { continue // 跳过已被访问的顶点 } que.append(adjVet) // 只入队未访问的顶点 visited.insert(adjVet) // 标记该顶点已被访问 } } // 返回顶点遍历序列 return res } @main enum GraphBFS { /* Driver Code */ static func main() { /* 初始化无向图 */ let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) let edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ] let graph = GraphAdjList(edges: edges) print("\n初始化后,图为") graph.print() /* 广度优先遍历 */ let res = graphBFS(graph: graph, startVet: v[0]) print("\n广度优先遍历(BFS)顶点序列为") print(Vertex.vetsToVals(vets: res)) } } ================================================ FILE: codes/swift/chapter_graph/graph_dfs.swift ================================================ /** * File: graph_dfs.swift * Created Time: 2023-02-21 * Author: nuomi1 (nuomi1@qq.com) */ import graph_adjacency_list_target import utils /* 深度优先遍历辅助函数 */ func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { res.append(vet) // 记录访问顶点 visited.insert(vet) // 标记该顶点已被访问 // 遍历该顶点的所有邻接顶点 for adjVet in graph.adjList[vet] ?? [] { if visited.contains(adjVet) { continue // 跳过已被访问的顶点 } // 递归访问邻接顶点 dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) } } /* 深度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { // 顶点遍历序列 var res: [Vertex] = [] // 哈希集合,用于记录已被访问过的顶点 var visited: Set = [] dfs(graph: graph, visited: &visited, res: &res, vet: startVet) return res } @main enum GraphDFS { /* Driver Code */ static func main() { /* 初始化无向图 */ let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6]) let edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ] let graph = GraphAdjList(edges: edges) print("\n初始化后,图为") graph.print() /* 深度优先遍历 */ let res = graphDFS(graph: graph, startVet: v[0]) print("\n深度优先遍历(DFS)顶点序列为") print(Vertex.vetsToVals(vets: res)) } } ================================================ FILE: codes/swift/chapter_greedy/coin_change_greedy.swift ================================================ /** * File: coin_change_greedy.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* 零钱兑换:贪心 */ func coinChangeGreedy(coins: [Int], amt: Int) -> Int { // 假设 coins 列表有序 var i = coins.count - 1 var count = 0 var amt = amt // 循环进行贪心选择,直到无剩余金额 while amt > 0 { // 找到小于且最接近剩余金额的硬币 while i > 0 && coins[i] > amt { i -= 1 } // 选择 coins[i] amt -= coins[i] count += 1 } // 若未找到可行方案,则返回 -1 return amt == 0 ? count : -1 } @main enum CoinChangeGreedy { /* Driver Code */ static func main() { // 贪心:能够保证找到全局最优解 var coins = [1, 5, 10, 20, 50, 100] var amt = 186 var res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("凑到 \(amt) 所需的最少硬币数量为 \(res)") // 贪心:无法保证找到全局最优解 coins = [1, 20, 50] amt = 60 res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("凑到 \(amt) 所需的最少硬币数量为 \(res)") print("实际上需要的最少数量为 3 ,即 20 + 20 + 20") // 贪心:无法保证找到全局最优解 coins = [1, 49, 50] amt = 98 res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("凑到 \(amt) 所需的最少硬币数量为 \(res)") print("实际上需要的最少数量为 2 ,即 49 + 49") } } ================================================ FILE: codes/swift/chapter_greedy/fractional_knapsack.swift ================================================ /** * File: fractional_knapsack.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* 物品 */ class Item { var w: Int // 物品重量 var v: Int // 物品价值 init(w: Int, v: Int) { self.w = w self.v = v } } /* 分数背包:贪心 */ func fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double { // 创建物品列表,包含两个属性:重量、价值 var items = zip(wgt, val).map { Item(w: $0, v: $1) } // 按照单位价值 item.v / item.w 从高到低进行排序 items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) } // 循环贪心选择 var res = 0.0 var cap = cap for item in items { if item.w <= cap { // 若剩余容量充足,则将当前物品整个装进背包 res += Double(item.v) cap -= item.w } else { // 若剩余容量不足,则将当前物品的一部分装进背包 res += Double(item.v) / Double(item.w) * Double(cap) // 已无剩余容量,因此跳出循环 break } } return res } @main enum FractionalKnapsack { /* Driver Code */ static func main() { // 物品重量 let wgt = [10, 20, 30, 40, 50] // 物品价值 let val = [50, 120, 150, 210, 240] // 背包容量 let cap = 50 // 贪心算法 let res = fractionalKnapsack(wgt: wgt, val: val, cap: cap) print("不超过背包容量的最大物品价值为 \(res)") } } ================================================ FILE: codes/swift/chapter_greedy/max_capacity.swift ================================================ /** * File: max_capacity.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* 最大容量:贪心 */ func maxCapacity(ht: [Int]) -> Int { // 初始化 i, j,使其分列数组两端 var i = ht.startIndex, j = ht.endIndex - 1 // 初始最大容量为 0 var res = 0 // 循环贪心选择,直至两板相遇 while i < j { // 更新最大容量 let cap = min(ht[i], ht[j]) * (j - i) res = max(res, cap) // 向内移动短板 if ht[i] < ht[j] { i += 1 } else { j -= 1 } } return res } @main enum MaxCapacity { /* Driver Code */ static func main() { let ht = [3, 8, 5, 2, 7, 7, 3, 4] // 贪心算法 let res = maxCapacity(ht: ht) print("最大容量为 \(res)") } } ================================================ FILE: codes/swift/chapter_greedy/max_product_cutting.swift ================================================ /** * File: max_product_cutting.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ import Foundation func pow(_ x: Int, _ y: Int) -> Int { Int(Double(truncating: pow(Decimal(x), y) as NSDecimalNumber)) } /* 最大切分乘积:贪心 */ func maxProductCutting(n: Int) -> Int { // 当 n <= 3 时,必须切分出一个 1 if n <= 3 { return 1 * (n - 1) } // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 let a = n / 3 let b = n % 3 if b == 1 { // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 return pow(3, a - 1) * 2 * 2 } if b == 2 { // 当余数为 2 时,不做处理 return pow(3, a) * 2 } // 当余数为 0 时,不做处理 return pow(3, a) } @main enum MaxProductCutting { static func main() { let n = 58 // 贪心算法 let res = maxProductCutting(n: n) print("最大切分乘积为 \(res)") } } ================================================ FILE: codes/swift/chapter_hashing/array_hash_map.swift ================================================ /** * File: array_hash_map.swift * Created Time: 2023-01-16 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 基于数组实现的哈希表 */ class ArrayHashMap { private var buckets: [Pair?] init() { // 初始化数组,包含 100 个桶 buckets = Array(repeating: nil, count: 100) } /* 哈希函数 */ private func hashFunc(key: Int) -> Int { let index = key % 100 return index } /* 查询操作 */ func get(key: Int) -> String? { let index = hashFunc(key: key) let pair = buckets[index] return pair?.val } /* 添加操作 */ func put(key: Int, val: String) { let pair = Pair(key: key, val: val) let index = hashFunc(key: key) buckets[index] = pair } /* 删除操作 */ func remove(key: Int) { let index = hashFunc(key: key) // 置为 nil ,代表删除 buckets[index] = nil } /* 获取所有键值对 */ func pairSet() -> [Pair] { buckets.compactMap { $0 } } /* 获取所有键 */ func keySet() -> [Int] { buckets.compactMap { $0?.key } } /* 获取所有值 */ func valueSet() -> [String] { buckets.compactMap { $0?.val } } /* 打印哈希表 */ func print() { for pair in pairSet() { Swift.print("\(pair.key) -> \(pair.val)") } } } @main enum _ArrayHashMap { /* Driver Code */ static func main() { /* 初始化哈希表 */ let map = ArrayHashMap() /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(key: 12836, val: "小哈") map.put(key: 15937, val: "小啰") map.put(key: 16750, val: "小算") map.put(key: 13276, val: "小法") map.put(key: 10583, val: "小鸭") print("\n添加完成后,哈希表为\nKey -> Value") map.print() /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value let name = map.get(key: 15937)! print("\n输入学号 15937 ,查询到姓名 \(name)") /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(key: 10583) print("\n删除 10583 后,哈希表为\nKey -> Value") map.print() /* 遍历哈希表 */ print("\n遍历键值对 Key->Value") for pair in map.pairSet() { print("\(pair.key) -> \(pair.val)") } print("\n单独遍历键 Key") for key in map.keySet() { print(key) } print("\n单独遍历值 Value") for val in map.valueSet() { print(val) } } } ================================================ FILE: codes/swift/chapter_hashing/built_in_hash.swift ================================================ /** * File: built_in_hash.swift * Created Time: 2023-07-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum BuiltInHash { /* Driver Code */ static func main() { let num = 3 let hashNum = num.hashValue print("整数 \(num) 的哈希值为 \(hashNum)") let bol = true let hashBol = bol.hashValue print("布尔量 \(bol) 的哈希值为 \(hashBol)") let dec = 3.14159 let hashDec = dec.hashValue print("小数 \(dec) 的哈希值为 \(hashDec)") let str = "Hello 算法" let hashStr = str.hashValue print("字符串 \(str) 的哈希值为 \(hashStr)") let arr = [AnyHashable(12836), AnyHashable("小哈")] let hashTup = arr.hashValue print("数组 \(arr) 的哈希值为 \(hashTup)") let obj = ListNode(x: 0) let hashObj = obj.hashValue print("节点对象 \(obj) 的哈希值为 \(hashObj)") } } ================================================ FILE: codes/swift/chapter_hashing/hash_map.swift ================================================ /** * File: hash_map.swift * Created Time: 2023-01-16 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum HashMap { /* Driver Code */ static func main() { /* 初始化哈希表 */ var map: [Int: String] = [:] /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map[12836] = "小哈" map[15937] = "小啰" map[16750] = "小算" map[13276] = "小法" map[10583] = "小鸭" print("\n添加完成后,哈希表为\nKey -> Value") PrintUtil.printHashMap(map: map) /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value let name = map[15937]! print("\n输入学号 15937 ,查询到姓名 \(name)") /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.removeValue(forKey: 10583) print("\n删除 10583 后,哈希表为\nKey -> Value") PrintUtil.printHashMap(map: map) /* 遍历哈希表 */ print("\n遍历键值对 Key->Value") for (key, value) in map { print("\(key) -> \(value)") } print("\n单独遍历键 Key") for key in map.keys { print(key) } print("\n单独遍历值 Value") for value in map.values { print(value) } } } ================================================ FILE: codes/swift/chapter_hashing/hash_map_chaining.swift ================================================ /** * File: hash_map_chaining.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 链式地址哈希表 */ class HashMapChaining { var size: Int // 键值对数量 var capacity: Int // 哈希表容量 var loadThres: Double // 触发扩容的负载因子阈值 var extendRatio: Int // 扩容倍数 var buckets: [[Pair]] // 桶数组 /* 构造方法 */ init() { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = Array(repeating: [], count: capacity) } /* 哈希函数 */ func hashFunc(key: Int) -> Int { key % capacity } /* 负载因子 */ func loadFactor() -> Double { Double(size) / Double(capacity) } /* 查询操作 */ func get(key: Int) -> String? { let index = hashFunc(key: key) let bucket = buckets[index] // 遍历桶,若找到 key ,则返回对应 val for pair in bucket { if pair.key == key { return pair.val } } // 若未找到 key ,则返回 nil return nil } /* 添加操作 */ func put(key: Int, val: String) { // 当负载因子超过阈值时,执行扩容 if loadFactor() > loadThres { extend() } let index = hashFunc(key: key) let bucket = buckets[index] // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 for pair in bucket { if pair.key == key { pair.val = val return } } // 若无该 key ,则将键值对添加至尾部 let pair = Pair(key: key, val: val) buckets[index].append(pair) size += 1 } /* 删除操作 */ func remove(key: Int) { let index = hashFunc(key: key) let bucket = buckets[index] // 遍历桶,从中删除键值对 for (pairIndex, pair) in bucket.enumerated() { if pair.key == key { buckets[index].remove(at: pairIndex) size -= 1 break } } } /* 扩容哈希表 */ func extend() { // 暂存原哈希表 let bucketsTmp = buckets // 初始化扩容后的新哈希表 capacity *= extendRatio buckets = Array(repeating: [], count: capacity) size = 0 // 将键值对从原哈希表搬运至新哈希表 for bucket in bucketsTmp { for pair in bucket { put(key: pair.key, val: pair.val) } } } /* 打印哈希表 */ func print() { for bucket in buckets { let res = bucket.map { "\($0.key) -> \($0.val)" } Swift.print(res) } } } @main enum _HashMapChaining { /* Driver Code */ static func main() { /* 初始化哈希表 */ let map = HashMapChaining() /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(key: 12836, val: "小哈") map.put(key: 15937, val: "小啰") map.put(key: 16750, val: "小算") map.put(key: 13276, val: "小法") map.put(key: 10583, val: "小鸭") print("\n添加完成后,哈希表为\nKey -> Value") map.print() /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value let name = map.get(key: 13276) print("\n输入学号 13276 ,查询到姓名 \(name!)") /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(key: 12836) print("\n删除 12836 后,哈希表为\nKey -> Value") map.print() } } ================================================ FILE: codes/swift/chapter_hashing/hash_map_open_addressing.swift ================================================ /** * File: hash_map_open_addressing.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 开放寻址哈希表 */ class HashMapOpenAddressing { var size: Int // 键值对数量 var capacity: Int // 哈希表容量 var loadThres: Double // 触发扩容的负载因子阈值 var extendRatio: Int // 扩容倍数 var buckets: [Pair?] // 桶数组 var TOMBSTONE: Pair // 删除标记 /* 构造方法 */ init() { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = Array(repeating: nil, count: capacity) TOMBSTONE = Pair(key: -1, val: "-1") } /* 哈希函数 */ func hashFunc(key: Int) -> Int { key % capacity } /* 负载因子 */ func loadFactor() -> Double { Double(size) / Double(capacity) } /* 搜索 key 对应的桶索引 */ func findBucket(key: Int) -> Int { var index = hashFunc(key: key) var firstTombstone = -1 // 线性探测,当遇到空桶时跳出 while buckets[index] != nil { // 若遇到 key ,返回对应的桶索引 if buckets[index]!.key == key { // 若之前遇到了删除标记,则将键值对移动至该索引处 if firstTombstone != -1 { buckets[firstTombstone] = buckets[index] buckets[index] = TOMBSTONE return firstTombstone // 返回移动后的桶索引 } return index // 返回桶索引 } // 记录遇到的首个删除标记 if firstTombstone == -1 && buckets[index] == TOMBSTONE { firstTombstone = index } // 计算桶索引,越过尾部则返回头部 index = (index + 1) % capacity } // 若 key 不存在,则返回添加点的索引 return firstTombstone == -1 ? index : firstTombstone } /* 查询操作 */ func get(key: Int) -> String? { // 搜索 key 对应的桶索引 let index = findBucket(key: key) // 若找到键值对,则返回对应 val if buckets[index] != nil, buckets[index] != TOMBSTONE { return buckets[index]!.val } // 若键值对不存在,则返回 null return nil } /* 添加操作 */ func put(key: Int, val: String) { // 当负载因子超过阈值时,执行扩容 if loadFactor() > loadThres { extend() } // 搜索 key 对应的桶索引 let index = findBucket(key: key) // 若找到键值对,则覆盖 val 并返回 if buckets[index] != nil, buckets[index] != TOMBSTONE { buckets[index]!.val = val return } // 若键值对不存在,则添加该键值对 buckets[index] = Pair(key: key, val: val) size += 1 } /* 删除操作 */ func remove(key: Int) { // 搜索 key 对应的桶索引 let index = findBucket(key: key) // 若找到键值对,则用删除标记覆盖它 if buckets[index] != nil, buckets[index] != TOMBSTONE { buckets[index] = TOMBSTONE size -= 1 } } /* 扩容哈希表 */ func extend() { // 暂存原哈希表 let bucketsTmp = buckets // 初始化扩容后的新哈希表 capacity *= extendRatio buckets = Array(repeating: nil, count: capacity) size = 0 // 将键值对从原哈希表搬运至新哈希表 for pair in bucketsTmp { if let pair, pair != TOMBSTONE { put(key: pair.key, val: pair.val) } } } /* 打印哈希表 */ func print() { for pair in buckets { if pair == nil { Swift.print("null") } else if pair == TOMBSTONE { Swift.print("TOMBSTONE") } else { Swift.print("\(pair!.key) -> \(pair!.val)") } } } } @main enum _HashMapOpenAddressing { /* Driver Code */ static func main() { /* 初始化哈希表 */ let map = HashMapOpenAddressing() /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(key: 12836, val: "小哈") map.put(key: 15937, val: "小啰") map.put(key: 16750, val: "小算") map.put(key: 13276, val: "小法") map.put(key: 10583, val: "小鸭") print("\n添加完成后,哈希表为\nKey -> Value") map.print() /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value let name = map.get(key: 13276) print("\n输入学号 13276 ,查询到姓名 \(name!)") /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(key: 16750) print("\n删除 16750 后,哈希表为\nKey -> Value") map.print() } } ================================================ FILE: codes/swift/chapter_hashing/simple_hash.swift ================================================ /** * File: simple_hash.swift * Created Time: 2023-07-01 * Author: nuomi1 (nuomi1@qq.com) */ /* 加法哈希 */ func addHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = (hash + Int(scalar.value)) % MODULUS } } return hash } /* 乘法哈希 */ func mulHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = (31 * hash + Int(scalar.value)) % MODULUS } } return hash } /* 异或哈希 */ func xorHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash ^= Int(scalar.value) } } return hash & MODULUS } /* 旋转哈希 */ func rotHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS } } return hash } @main enum SimpleHash { /* Driver Code */ static func main() { let key = "Hello 算法" var hash = addHash(key: key) print("加法哈希值为 \(hash)") hash = mulHash(key: key) print("乘法哈希值为 \(hash)") hash = xorHash(key: key) print("异或哈希值为 \(hash)") hash = rotHash(key: key) print("旋转哈希值为 \(hash)") } } ================================================ FILE: codes/swift/chapter_heap/heap.swift ================================================ /** * File: heap.swift * Created Time: 2024-03-17 * Author: nuomi1 (nuomi1@qq.com) */ import HeapModule import utils func testPush(heap: inout Heap, val: Int) { heap.insert(val) print("\n元素 \(val) 入堆后\n") PrintUtil.printHeap(queue: heap.unordered) } func testPop(heap: inout Heap) { let val = heap.removeMax() print("\n堆顶元素 \(val) 出堆后\n") PrintUtil.printHeap(queue: heap.unordered) } @main enum _Heap { /* Driver Code */ static func main() { /* 初始化堆 */ // Swift 的 Heap 类型同时支持最大堆和最小堆 var heap = Heap() /* 元素入堆 */ testPush(heap: &heap, val: 1) testPush(heap: &heap, val: 3) testPush(heap: &heap, val: 2) testPush(heap: &heap, val: 5) testPush(heap: &heap, val: 4) /* 获取堆顶元素 */ let peek = heap.max() print("\n堆顶元素为 \(peek!)\n") /* 堆顶元素出堆 */ testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) /* 获取堆大小 */ let size = heap.count print("\n堆元素数量为 \(size)\n") /* 判断堆是否为空 */ let isEmpty = heap.isEmpty print("\n堆是否为空 \(isEmpty)\n") /* 输入列表并建堆 */ // 时间复杂度为 O(n) ,而非 O(nlogn) let heap2 = Heap([1, 3, 2, 5, 4]) print("\n输入列表并建立堆后") PrintUtil.printHeap(queue: heap2.unordered) } } ================================================ FILE: codes/swift/chapter_heap/my_heap.swift ================================================ /** * File: my_heap.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 大顶堆 */ class MaxHeap { private var maxHeap: [Int] /* 构造方法,根据输入列表建堆 */ init(nums: [Int]) { // 将列表元素原封不动添加进堆 maxHeap = nums // 堆化除叶节点以外的其他所有节点 for i in (0 ... parent(i: size() - 1)).reversed() { siftDown(i: i) } } /* 获取左子节点的索引 */ private func left(i: Int) -> Int { 2 * i + 1 } /* 获取右子节点的索引 */ private func right(i: Int) -> Int { 2 * i + 2 } /* 获取父节点的索引 */ private func parent(i: Int) -> Int { (i - 1) / 2 // 向下整除 } /* 交换元素 */ private func swap(i: Int, j: Int) { maxHeap.swapAt(i, j) } /* 获取堆大小 */ func size() -> Int { maxHeap.count } /* 判断堆是否为空 */ func isEmpty() -> Bool { size() == 0 } /* 访问堆顶元素 */ func peek() -> Int { maxHeap[0] } /* 元素入堆 */ func push(val: Int) { // 添加节点 maxHeap.append(val) // 从底至顶堆化 siftUp(i: size() - 1) } /* 从节点 i 开始,从底至顶堆化 */ private func siftUp(i: Int) { var i = i while true { // 获取节点 i 的父节点 let p = parent(i: i) // 当“越过根节点”或“节点无须修复”时,结束堆化 if p < 0 || maxHeap[i] <= maxHeap[p] { break } // 交换两节点 swap(i: i, j: p) // 循环向上堆化 i = p } } /* 元素出堆 */ func pop() -> Int { // 判空处理 if isEmpty() { fatalError("堆为空") } // 交换根节点与最右叶节点(交换首元素与尾元素) swap(i: 0, j: size() - 1) // 删除节点 let val = maxHeap.remove(at: size() - 1) // 从顶至底堆化 siftDown(i: 0) // 返回堆顶元素 return val } /* 从节点 i 开始,从顶至底堆化 */ private func siftDown(i: Int) { var i = i while true { // 判断节点 i, l, r 中值最大的节点,记为 ma let l = left(i: i) let r = right(i: i) var ma = i if l < size(), maxHeap[l] > maxHeap[ma] { ma = l } if r < size(), maxHeap[r] > maxHeap[ma] { ma = r } // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if ma == i { break } // 交换两节点 swap(i: i, j: ma) // 循环向下堆化 i = ma } } /* 打印堆(二叉树) */ func print() { let queue = maxHeap PrintUtil.printHeap(queue: queue) } } @main enum MyHeap { /* Driver Code */ static func main() { /* 初始化大顶堆 */ let maxHeap = MaxHeap(nums: [9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) print("\n输入列表并建堆后") maxHeap.print() /* 获取堆顶元素 */ var peek = maxHeap.peek() print("\n堆顶元素为 \(peek)") /* 元素入堆 */ let val = 7 maxHeap.push(val: val) print("\n元素 \(val) 入堆后") maxHeap.print() /* 堆顶元素出堆 */ peek = maxHeap.pop() print("\n堆顶元素 \(peek) 出堆后") maxHeap.print() /* 获取堆大小 */ let size = maxHeap.size() print("\n堆元素数量为 \(size)") /* 判断堆是否为空 */ let isEmpty = maxHeap.isEmpty() print("\n堆是否为空 \(isEmpty)") } } ================================================ FILE: codes/swift/chapter_heap/top_k.swift ================================================ /** * File: top_k.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ import HeapModule import utils /* 基于堆查找数组中最大的 k 个元素 */ func topKHeap(nums: [Int], k: Int) -> [Int] { // 初始化一个小顶堆,并将前 k 个元素建堆 var heap = Heap(nums.prefix(k)) // 从第 k+1 个元素开始,保持堆的长度为 k for i in nums.indices.dropFirst(k) { // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 if nums[i] > heap.min()! { _ = heap.removeMin() heap.insert(nums[i]) } } return heap.unordered } @main enum TopK { /* Driver Code */ static func main() { let nums = [1, 7, 6, 3, 2] let k = 3 let res = topKHeap(nums: nums, k: k) print("最大的 \(k) 个元素为") PrintUtil.printHeap(queue: res) } } ================================================ FILE: codes/swift/chapter_searching/binary_search.swift ================================================ /** * File: binary_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ /* 二分查找(双闭区间) */ func binarySearch(nums: [Int], target: Int) -> Int { // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 var i = nums.startIndex var j = nums.endIndex - 1 // 循环,当搜索区间为空时跳出(当 i > j 时为空) while i <= j { let m = i + (j - i) / 2 // 计算中点索引 m if nums[m] < target { // 此情况说明 target 在区间 [m+1, j] 中 i = m + 1 } else if nums[m] > target { // 此情况说明 target 在区间 [i, m-1] 中 j = m - 1 } else { // 找到目标元素,返回其索引 return m } } // 未找到目标元素,返回 -1 return -1 } /* 二分查找(左闭右开区间) */ func binarySearchLCRO(nums: [Int], target: Int) -> Int { // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 var i = nums.startIndex var j = nums.endIndex // 循环,当搜索区间为空时跳出(当 i = j 时为空) while i < j { let m = i + (j - i) / 2 // 计算中点索引 m if nums[m] < target { // 此情况说明 target 在区间 [m+1, j) 中 i = m + 1 } else if nums[m] > target { // 此情况说明 target 在区间 [i, m) 中 j = m } else { // 找到目标元素,返回其索引 return m } } // 未找到目标元素,返回 -1 return -1 } @main enum BinarySearch { /* Driver Code */ static func main() { let target = 6 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] /* 二分查找(双闭区间) */ var index = binarySearch(nums: nums, target: target) print("目标元素 6 的索引 = \(index)") /* 二分查找(左闭右开区间) */ index = binarySearchLCRO(nums: nums, target: target) print("目标元素 6 的索引 = \(index)") } } ================================================ FILE: codes/swift/chapter_searching/binary_search_edge.swift ================================================ /** * File: binary_search_edge.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ import binary_search_insertion_target /* 二分查找最左一个 target */ func binarySearchLeftEdge(nums: [Int], target: Int) -> Int { // 等价于查找 target 的插入点 let i = binarySearchInsertion(nums: nums, target: target) // 未找到 target ,返回 -1 if i == nums.endIndex || nums[i] != target { return -1 } // 找到 target ,返回索引 i return i } /* 二分查找最右一个 target */ func binarySearchRightEdge(nums: [Int], target: Int) -> Int { // 转化为查找最左一个 target + 1 let i = binarySearchInsertion(nums: nums, target: target + 1) // j 指向最右一个 target ,i 指向首个大于 target 的元素 let j = i - 1 // 未找到 target ,返回 -1 if j == -1 || nums[j] != target { return -1 } // 找到 target ,返回索引 j return j } @main enum BinarySearchEdge { /* Driver Code */ static func main() { // 包含重复元素的数组 let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\n数组 nums = \(nums)") // 二分查找左边界和右边界 for target in [6, 7] { var index = binarySearchLeftEdge(nums: nums, target: target) print("最左一个元素 \(target) 的索引为 \(index)") index = binarySearchRightEdge(nums: nums, target: target) print("最右一个元素 \(target) 的索引为 \(index)") } } } ================================================ FILE: codes/swift/chapter_searching/binary_search_insertion.swift ================================================ /** * File: binary_search_insertion.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ /* 二分查找插入点(无重复元素) */ func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { // 初始化双闭区间 [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // 计算中点索引 m if nums[m] < target { i = m + 1 // target 在区间 [m+1, j] 中 } else if nums[m] > target { j = m - 1 // target 在区间 [i, m-1] 中 } else { return m // 找到 target ,返回插入点 m } } // 未找到 target ,返回插入点 i return i } /* 二分查找插入点(存在重复元素) */ public func binarySearchInsertion(nums: [Int], target: Int) -> Int { // 初始化双闭区间 [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // 计算中点索引 m if nums[m] < target { i = m + 1 // target 在区间 [m+1, j] 中 } else if nums[m] > target { j = m - 1 // target 在区间 [i, m-1] 中 } else { j = m - 1 // 首个小于 target 的元素在区间 [i, m-1] 中 } } // 返回插入点 i return i } #if !TARGET @main enum BinarySearchInsertion { /* Driver Code */ static func main() { // 无重复元素的数组 var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print("\n数组 nums = \(nums)") // 二分查找插入点 for target in [6, 9] { let index = binarySearchInsertionSimple(nums: nums, target: target) print("元素 \(target) 的插入点的索引为 \(index)") } // 包含重复元素的数组 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\n数组 nums = \(nums)") // 二分查找插入点 for target in [2, 6, 20] { let index = binarySearchInsertion(nums: nums, target: target) print("元素 \(target) 的插入点的索引为 \(index)") } } } #endif ================================================ FILE: codes/swift/chapter_searching/hashing_search.swift ================================================ /** * File: hashing_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 哈希查找(数组) */ func hashingSearchArray(map: [Int: Int], target: Int) -> Int { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 return map[target, default: -1] } /* 哈希查找(链表) */ func hashingSearchLinkedList(map: [Int: ListNode], target: Int) -> ListNode? { // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 null return map[target] } @main enum HashingSearch { /* Driver Code */ static func main() { let target = 3 /* 哈希查找(数组) */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] // 初始化哈希表 var map: [Int: Int] = [:] for i in nums.indices { map[nums[i]] = i // key: 元素,value: 索引 } let index = hashingSearchArray(map: map, target: target) print("目标元素 3 的索引 = \(index)") /* 哈希查找(链表) */ var head = ListNode.arrToLinkedList(arr: nums) // 初始化哈希表 var map1: [Int: ListNode] = [:] while head != nil { map1[head!.val] = head! // key: 节点值,value: 节点 head = head?.next } let node = hashingSearchLinkedList(map: map1, target: target) print("目标节点值 3 的对应节点对象为 \(node!)") } } ================================================ FILE: codes/swift/chapter_searching/linear_search.swift ================================================ /** * File: linear_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 线性查找(数组) */ func linearSearchArray(nums: [Int], target: Int) -> Int { // 遍历数组 for i in nums.indices { // 找到目标元素,返回其索引 if nums[i] == target { return i } } // 未找到目标元素,返回 -1 return -1 } /* 线性查找(链表) */ func linearSearchLinkedList(head: ListNode?, target: Int) -> ListNode? { var head = head // 遍历链表 while head != nil { // 找到目标节点,返回之 if head?.val == target { return head } head = head?.next } // 未找到目标节点,返回 null return nil } @main enum LinearSearch { /* Driver Code */ static func main() { let target = 3 /* 在数组中执行线性查找 */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] let index = linearSearchArray(nums: nums, target: target) print("目标元素 3 的索引 = \(index)") /* 在链表中执行线性查找 */ let head = ListNode.arrToLinkedList(arr: nums) let node = linearSearchLinkedList(head: head, target: target) print("目标节点值 3 的对应节点对象为 \(node!)") } } ================================================ FILE: codes/swift/chapter_searching/two_sum.swift ================================================ /** * File: two_sum.swift * Created Time: 2023-01-03 * Author: nuomi1 (nuomi1@qq.com) */ /* 方法一:暴力枚举 */ func twoSumBruteForce(nums: [Int], target: Int) -> [Int] { // 两层循环,时间复杂度为 O(n^2) for i in nums.indices.dropLast() { for j in nums.indices.dropFirst(i + 1) { if nums[i] + nums[j] == target { return [i, j] } } } return [0] } /* 方法二:辅助哈希表 */ func twoSumHashTable(nums: [Int], target: Int) -> [Int] { // 辅助哈希表,空间复杂度为 O(n) var dic: [Int: Int] = [:] // 单层循环,时间复杂度为 O(n) for i in nums.indices { if let j = dic[target - nums[i]] { return [j, i] } dic[nums[i]] = i } return [0] } @main enum LeetcodeTwoSum { /* Driver Code */ static func main() { // ======= Test Case ======= let nums = [2, 7, 11, 15] let target = 13 // ====== Driver Code ====== // 方法一 var res = twoSumBruteForce(nums: nums, target: target) print("方法一 res = \(res)") // 方法二 res = twoSumHashTable(nums: nums, target: target) print("方法二 res = \(res)") } } ================================================ FILE: codes/swift/chapter_sorting/bubble_sort.swift ================================================ /** * File: bubble_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* 冒泡排序 */ func bubbleSort(nums: inout [Int]) { // 外循环:未排序区间为 [0, i] for i in nums.indices.dropFirst().reversed() { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in 0 ..< i { if nums[j] > nums[j + 1] { // 交换 nums[j] 与 nums[j + 1] nums.swapAt(j, j + 1) } } } } /* 冒泡排序(标志优化)*/ func bubbleSortWithFlag(nums: inout [Int]) { // 外循环:未排序区间为 [0, i] for i in nums.indices.dropFirst().reversed() { var flag = false // 初始化标志位 for j in 0 ..< i { if nums[j] > nums[j + 1] { // 交换 nums[j] 与 nums[j + 1] nums.swapAt(j, j + 1) flag = true // 记录交换元素 } } if !flag { // 此轮“冒泡”未交换任何元素,直接跳出 break } } } @main enum BubbleSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] bubbleSort(nums: &nums) print("冒泡排序完成后 nums = \(nums)") var nums1 = [4, 1, 3, 1, 5, 2] bubbleSortWithFlag(nums: &nums1) print("冒泡排序完成后 nums1 = \(nums1)") } } ================================================ FILE: codes/swift/chapter_sorting/bucket_sort.swift ================================================ /** * File: bucket_sort.swift * Created Time: 2023-03-27 * Author: nuomi1 (nuomi1@qq.com) */ /* 桶排序 */ func bucketSort(nums: inout [Double]) { // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 let k = nums.count / 2 var buckets = (0 ..< k).map { _ in [Double]() } // 1. 将数组元素分配到各个桶中 for num in nums { // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] let i = Int(num * Double(k)) // 将 num 添加进桶 i buckets[i].append(num) } // 2. 对各个桶执行排序 for i in buckets.indices { // 使用内置排序函数,也可以替换成其他排序算法 buckets[i].sort() } // 3. 遍历桶合并结果 var i = nums.startIndex for bucket in buckets { for num in bucket { nums[i] = num i += 1 } } } @main enum BucketSort { /* Driver Code */ static func main() { // 设输入数据为浮点数,范围为 [0, 1) var nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] bucketSort(nums: &nums) print("桶排序完成后 nums = \(nums)") } } ================================================ FILE: codes/swift/chapter_sorting/counting_sort.swift ================================================ /** * File: counting_sort.swift * Created Time: 2023-03-22 * Author: nuomi1 (nuomi1@qq.com) */ /* 计数排序 */ // 简单实现,无法用于排序对象 func countingSortNaive(nums: inout [Int]) { // 1. 统计数组最大元素 m let m = nums.max()! // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 var counter = Array(repeating: 0, count: m + 1) for num in nums { counter[num] += 1 } // 3. 遍历 counter ,将各元素填入原数组 nums var i = 0 for num in 0 ..< m + 1 { for _ in 0 ..< counter[num] { nums[i] = num i += 1 } } } /* 计数排序 */ // 完整实现,可排序对象,并且是稳定排序 func countingSort(nums: inout [Int]) { // 1. 统计数组最大元素 m let m = nums.max()! // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 var counter = Array(repeating: 0, count: m + 1) for num in nums { counter[num] += 1 } // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 for i in 0 ..< m { counter[i + 1] += counter[i] } // 4. 倒序遍历 nums ,将各元素填入结果数组 res // 初始化数组 res 用于记录结果 var res = Array(repeating: 0, count: nums.count) for i in nums.indices.reversed() { let num = nums[i] res[counter[num] - 1] = num // 将 num 放置到对应索引处 counter[num] -= 1 // 令前缀和自减 1 ,得到下次放置 num 的索引 } // 使用结果数组 res 覆盖原数组 nums for i in nums.indices { nums[i] = res[i] } } @main enum CountingSort { /* Driver Code */ static func main() { var nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] countingSortNaive(nums: &nums) print("计数排序(无法排序对象)完成后 nums = \(nums)") var nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] countingSort(nums: &nums1) print("计数排序完成后 nums1 = \(nums1)") } } ================================================ FILE: codes/swift/chapter_sorting/heap_sort.swift ================================================ /** * File: heap_sort.swift * Created Time: 2023-05-28 * Author: nuomi1 (nuomi1@qq.com) */ /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ func siftDown(nums: inout [Int], n: Int, i: Int) { var i = i while true { // 判断节点 i, l, r 中值最大的节点,记为 ma let l = 2 * i + 1 let r = 2 * i + 2 var ma = i if l < n, nums[l] > nums[ma] { ma = l } if r < n, nums[r] > nums[ma] { ma = r } // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if ma == i { break } // 交换两节点 nums.swapAt(i, ma) // 循环向下堆化 i = ma } } /* 堆排序 */ func heapSort(nums: inout [Int]) { // 建堆操作:堆化除叶节点以外的其他所有节点 for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) { siftDown(nums: &nums, n: nums.count, i: i) } // 从堆中提取最大元素,循环 n-1 轮 for i in nums.indices.dropFirst().reversed() { // 交换根节点与最右叶节点(交换首元素与尾元素) nums.swapAt(0, i) // 以根节点为起点,从顶至底进行堆化 siftDown(nums: &nums, n: i, i: 0) } } @main enum HeapSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] heapSort(nums: &nums) print("堆排序完成后 nums = \(nums)") } } ================================================ FILE: codes/swift/chapter_sorting/insertion_sort.swift ================================================ /** * File: insertion_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* 插入排序 */ func insertionSort(nums: inout [Int]) { // 外循环:已排序区间为 [0, i-1] for i in nums.indices.dropFirst() { let base = nums[i] var j = i - 1 // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while j >= 0, nums[j] > base { nums[j + 1] = nums[j] // 将 nums[j] 向右移动一位 j -= 1 } nums[j + 1] = base // 将 base 赋值到正确位置 } } @main enum InsertionSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] insertionSort(nums: &nums) print("插入排序完成后 nums = \(nums)") } } ================================================ FILE: codes/swift/chapter_sorting/merge_sort.swift ================================================ /** * File: merge_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* 合并左子数组和右子数组 */ func merge(nums: inout [Int], left: Int, mid: Int, right: Int) { // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] // 创建一个临时数组 tmp ,用于存放合并后的结果 var tmp = Array(repeating: 0, count: right - left + 1) // 初始化左子数组和右子数组的起始索引 var i = left, j = mid + 1, k = 0 // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 while i <= mid, j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i] i += 1 } else { tmp[k] = nums[j] j += 1 } k += 1 } // 将左子数组和右子数组的剩余元素复制到临时数组中 while i <= mid { tmp[k] = nums[i] i += 1 k += 1 } while j <= right { tmp[k] = nums[j] j += 1 k += 1 } // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 for k in tmp.indices { nums[left + k] = tmp[k] } } /* 归并排序 */ func mergeSort(nums: inout [Int], left: Int, right: Int) { // 终止条件 if left >= right { // 当子数组长度为 1 时终止递归 return } // 划分阶段 let mid = left + (right - left) / 2 // 计算中点 mergeSort(nums: &nums, left: left, right: mid) // 递归左子数组 mergeSort(nums: &nums, left: mid + 1, right: right) // 递归右子数组 // 合并阶段 merge(nums: &nums, left: left, mid: mid, right: right) } @main enum MergeSort { /* Driver Code */ static func main() { /* 归并排序 */ var nums = [7, 3, 2, 6, 0, 1, 5, 4] mergeSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) print("归并排序完成后 nums = \(nums)") } } ================================================ FILE: codes/swift/chapter_sorting/quick_sort.swift ================================================ /** * File: quick_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* 快速排序类 */ /* 哨兵划分 */ func partition(nums: inout [Int], left: Int, right: Int) -> Int { // 以 nums[left] 为基准数 var i = left var j = right while i < j { while i < j, nums[j] >= nums[left] { j -= 1 // 从右向左找首个小于基准数的元素 } while i < j, nums[i] <= nums[left] { i += 1 // 从左向右找首个大于基准数的元素 } nums.swapAt(i, j) // 交换这两个元素 } nums.swapAt(i, left) // 将基准数交换至两子数组的分界线 return i // 返回基准数的索引 } /* 快速排序 */ func quickSort(nums: inout [Int], left: Int, right: Int) { // 子数组长度为 1 时终止递归 if left >= right { return } // 哨兵划分 let pivot = partition(nums: &nums, left: left, right: right) // 递归左子数组、右子数组 quickSort(nums: &nums, left: left, right: pivot - 1) quickSort(nums: &nums, left: pivot + 1, right: right) } /* 快速排序类(中位基准数优化) */ /* 选取三个候选元素的中位数 */ func medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int { let l = nums[left] let m = nums[mid] let r = nums[right] if (l <= m && m <= r) || (r <= m && m <= l) { return mid // m 在 l 和 r 之间 } if (m <= l && l <= r) || (r <= l && l <= m) { return left // l 在 m 和 r 之间 } return right } /* 哨兵划分(三数取中值) */ func partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int { // 选取三个候选元素的中位数 let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right) // 将中位数交换至数组最左端 nums.swapAt(left, med) return partition(nums: &nums, left: left, right: right) } /* 快速排序(中位基准数优化) */ func quickSortMedian(nums: inout [Int], left: Int, right: Int) { // 子数组长度为 1 时终止递归 if left >= right { return } // 哨兵划分 let pivot = partitionMedian(nums: &nums, left: left, right: right) // 递归左子数组、右子数组 quickSortMedian(nums: &nums, left: left, right: pivot - 1) quickSortMedian(nums: &nums, left: pivot + 1, right: right) } /* 快速排序(递归深度优化) */ func quickSortTailCall(nums: inout [Int], left: Int, right: Int) { var left = left var right = right // 子数组长度为 1 时终止 while left < right { // 哨兵划分操作 let pivot = partition(nums: &nums, left: left, right: right) // 对两个子数组中较短的那个执行快速排序 if (pivot - left) < (right - pivot) { quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // 递归排序左子数组 left = pivot + 1 // 剩余未排序区间为 [pivot + 1, right] } else { quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // 递归排序右子数组 right = pivot - 1 // 剩余未排序区间为 [left, pivot - 1] } } } @main enum QuickSort { /* Driver Code */ static func main() { /* 快速排序 */ var nums = [2, 4, 1, 0, 3, 5] quickSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) print("快速排序完成后 nums = \(nums)") /* 快速排序(中位基准数优化) */ var nums1 = [2, 4, 1, 0, 3, 5] quickSortMedian(nums: &nums1, left: nums1.startIndex, right: nums1.endIndex - 1) print("快速排序(中位基准数优化)完成后 nums1 = \(nums1)") /* 快速排序(递归深度优化) */ var nums2 = [2, 4, 1, 0, 3, 5] quickSortTailCall(nums: &nums2, left: nums2.startIndex, right: nums2.endIndex - 1) print("快速排序(递归深度优化)完成后 nums2 = \(nums2)") } } ================================================ FILE: codes/swift/chapter_sorting/radix_sort.swift ================================================ /** * File: radix_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ func digit(num: Int, exp: Int) -> Int { // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 (num / exp) % 10 } /* 计数排序(根据 nums 第 k 位排序) */ func countingSortDigit(nums: inout [Int], exp: Int) { // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 var counter = Array(repeating: 0, count: 10) // 统计 0~9 各数字的出现次数 for i in nums.indices { let d = digit(num: nums[i], exp: exp) // 获取 nums[i] 第 k 位,记为 d counter[d] += 1 // 统计数字 d 的出现次数 } // 求前缀和,将“出现个数”转换为“数组索引” for i in 1 ..< 10 { counter[i] += counter[i - 1] } // 倒序遍历,根据桶内统计结果,将各元素填入 res var res = Array(repeating: 0, count: nums.count) for i in nums.indices.reversed() { let d = digit(num: nums[i], exp: exp) let j = counter[d] - 1 // 获取 d 在数组中的索引 j res[j] = nums[i] // 将当前元素填入索引 j counter[d] -= 1 // 将 d 的数量减 1 } // 使用结果覆盖原数组 nums for i in nums.indices { nums[i] = res[i] } } /* 基数排序 */ func radixSort(nums: inout [Int]) { // 获取数组的最大元素,用于判断最大位数 var m = Int.min for num in nums { if num > m { m = num } } // 按照从低位到高位的顺序遍历 for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) { // 对数组元素的第 k 位执行计数排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums: &nums, exp: exp) } } @main enum RadixSort { /* Driver Code */ static func main() { // 基数排序 var nums = [ 10_546_151, 35_663_510, 42_865_989, 34_862_445, 81_883_077, 88_906_420, 72_429_244, 30_524_779, 82_060_337, 63_832_996, ] radixSort(nums: &nums) print("基数排序完成后 nums = \(nums)") } } ================================================ FILE: codes/swift/chapter_sorting/selection_sort.swift ================================================ /** * File: selection_sort.swift * Created Time: 2023-05-28 * Author: nuomi1 (nuomi1@qq.com) */ /* 选择排序 */ func selectionSort(nums: inout [Int]) { // 外循环:未排序区间为 [i, n-1] for i in nums.indices.dropLast() { // 内循环:找到未排序区间内的最小元素 var k = i for j in nums.indices.dropFirst(i + 1) { if nums[j] < nums[k] { k = j // 记录最小元素的索引 } } // 将该最小元素与未排序区间的首个元素交换 nums.swapAt(i, k) } } @main enum SelectionSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] selectionSort(nums: &nums) print("选择排序完成后 nums = \(nums)") } } ================================================ FILE: codes/swift/chapter_stack_and_queue/array_deque.swift ================================================ /** * File: array_deque.swift * Created Time: 2023-02-22 * Author: nuomi1 (nuomi1@qq.com) */ /* 基于环形数组实现的双向队列 */ class ArrayDeque { private var nums: [Int] // 用于存储双向队列元素的数组 private var front: Int // 队首指针,指向队首元素 private var _size: Int // 双向队列长度 /* 构造方法 */ init(capacity: Int) { nums = Array(repeating: 0, count: capacity) front = 0 _size = 0 } /* 获取双向队列的容量 */ func capacity() -> Int { nums.count } /* 获取双向队列的长度 */ func size() -> Int { _size } /* 判断双向队列是否为空 */ func isEmpty() -> Bool { size() == 0 } /* 计算环形数组索引 */ private func index(i: Int) -> Int { // 通过取余操作实现数组首尾相连 // 当 i 越过数组尾部后,回到头部 // 当 i 越过数组头部后,回到尾部 (i + capacity()) % capacity() } /* 队首入队 */ func pushFirst(num: Int) { if size() == capacity() { print("双向队列已满") return } // 队首指针向左移动一位 // 通过取余操作实现 front 越过数组头部后回到尾部 front = index(i: front - 1) // 将 num 添加至队首 nums[front] = num _size += 1 } /* 队尾入队 */ func pushLast(num: Int) { if size() == capacity() { print("双向队列已满") return } // 计算队尾指针,指向队尾索引 + 1 let rear = index(i: front + size()) // 将 num 添加至队尾 nums[rear] = num _size += 1 } /* 队首出队 */ func popFirst() -> Int { let num = peekFirst() // 队首指针向后移动一位 front = index(i: front + 1) _size -= 1 return num } /* 队尾出队 */ func popLast() -> Int { let num = peekLast() _size -= 1 return num } /* 访问队首元素 */ func peekFirst() -> Int { if isEmpty() { fatalError("双向队列为空") } return nums[front] } /* 访问队尾元素 */ func peekLast() -> Int { if isEmpty() { fatalError("双向队列为空") } // 计算尾元素索引 let last = index(i: front + size() - 1) return nums[last] } /* 返回数组用于打印 */ func toArray() -> [Int] { // 仅转换有效长度范围内的列表元素 (front ..< front + size()).map { nums[index(i: $0)] } } } @main enum _ArrayDeque { /* Driver Code */ static func main() { /* 初始化双向队列 */ let deque = ArrayDeque(capacity: 10) deque.pushLast(num: 3) deque.pushLast(num: 2) deque.pushLast(num: 5) print("双向队列 deque = \(deque.toArray())") /* 访问元素 */ let peekFirst = deque.peekFirst() print("队首元素 peekFirst = \(peekFirst)") let peekLast = deque.peekLast() print("队尾元素 peekLast = \(peekLast)") /* 元素入队 */ deque.pushLast(num: 4) print("元素 4 队尾入队后 deque = \(deque.toArray())") deque.pushFirst(num: 1) print("元素 1 队首入队后 deque = \(deque.toArray())") /* 元素出队 */ let popLast = deque.popLast() print("队尾出队元素 = \(popLast),队尾出队后 deque = \(deque.toArray())") let popFirst = deque.popFirst() print("队首出队元素 = \(popFirst),队首出队后 deque = \(deque.toArray())") /* 获取双向队列的长度 */ let size = deque.size() print("双向队列长度 size = \(size)") /* 判断双向队列是否为空 */ let isEmpty = deque.isEmpty() print("双向队列是否为空 = \(isEmpty)") } } ================================================ FILE: codes/swift/chapter_stack_and_queue/array_queue.swift ================================================ /** * File: array_queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ /* 基于环形数组实现的队列 */ class ArrayQueue { private var nums: [Int] // 用于存储队列元素的数组 private var front: Int // 队首指针,指向队首元素 private var _size: Int // 队列长度 init(capacity: Int) { // 初始化数组 nums = Array(repeating: 0, count: capacity) front = 0 _size = 0 } /* 获取队列的容量 */ func capacity() -> Int { nums.count } /* 获取队列的长度 */ func size() -> Int { _size } /* 判断队列是否为空 */ func isEmpty() -> Bool { size() == 0 } /* 入队 */ func push(num: Int) { if size() == capacity() { print("队列已满") return } // 计算队尾指针,指向队尾索引 + 1 // 通过取余操作实现 rear 越过数组尾部后回到头部 let rear = (front + size()) % capacity() // 将 num 添加至队尾 nums[rear] = num _size += 1 } /* 出队 */ @discardableResult func pop() -> Int { let num = peek() // 队首指针向后移动一位,若越过尾部,则返回到数组头部 front = (front + 1) % capacity() _size -= 1 return num } /* 访问队首元素 */ func peek() -> Int { if isEmpty() { fatalError("队列为空") } return nums[front] } /* 返回数组 */ func toArray() -> [Int] { // 仅转换有效长度范围内的列表元素 (front ..< front + size()).map { nums[$0 % capacity()] } } } @main enum _ArrayQueue { /* Driver Code */ static func main() { /* 初始化队列 */ let capacity = 10 let queue = ArrayQueue(capacity: capacity) /* 元素入队 */ queue.push(num: 1) queue.push(num: 3) queue.push(num: 2) queue.push(num: 5) queue.push(num: 4) print("队列 queue = \(queue.toArray())") /* 访问队首元素 */ let peek = queue.peek() print("队首元素 peek = \(peek)") /* 元素出队 */ let pop = queue.pop() print("出队元素 pop = \(pop),出队后 queue = \(queue.toArray())") /* 获取队列的长度 */ let size = queue.size() print("队列长度 size = \(size)") /* 判断队列是否为空 */ let isEmpty = queue.isEmpty() print("队列是否为空 = \(isEmpty)") /* 测试环形数组 */ for i in 0 ..< 10 { queue.push(num: i) queue.pop() print("第 \(i) 轮入队 + 出队后 queue = \(queue.toArray())") } } } ================================================ FILE: codes/swift/chapter_stack_and_queue/array_stack.swift ================================================ /** * File: array_stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ /* 基于数组实现的栈 */ class ArrayStack { private var stack: [Int] init() { // 初始化列表(动态数组) stack = [] } /* 获取栈的长度 */ func size() -> Int { stack.count } /* 判断栈是否为空 */ func isEmpty() -> Bool { stack.isEmpty } /* 入栈 */ func push(num: Int) { stack.append(num) } /* 出栈 */ @discardableResult func pop() -> Int { if isEmpty() { fatalError("栈为空") } return stack.removeLast() } /* 访问栈顶元素 */ func peek() -> Int { if isEmpty() { fatalError("栈为空") } return stack.last! } /* 将 List 转化为 Array 并返回 */ func toArray() -> [Int] { stack } } @main enum _ArrayStack { /* Driver Code */ static func main() { /* 初始化栈 */ let stack = ArrayStack() /* 元素入栈 */ stack.push(num: 1) stack.push(num: 3) stack.push(num: 2) stack.push(num: 5) stack.push(num: 4) print("栈 stack = \(stack.toArray())") /* 访问栈顶元素 */ let peek = stack.peek() print("栈顶元素 peek = \(peek)") /* 元素出栈 */ let pop = stack.pop() print("出栈元素 pop = \(pop),出栈后 stack = \(stack.toArray())") /* 获取栈的长度 */ let size = stack.size() print("栈的长度 size = \(size)") /* 判断是否为空 */ let isEmpty = stack.isEmpty() print("栈是否为空 = \(isEmpty)") } } ================================================ FILE: codes/swift/chapter_stack_and_queue/deque.swift ================================================ /** * File: deque.swift * Created Time: 2023-01-14 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Deque { /* Driver Code */ static func main() { /* 初始化双向队列 */ // Swift 没有内置的双向队列类,可以把 Array 当作双向队列来使用 var deque: [Int] = [] /* 元素入队 */ deque.append(2) deque.append(5) deque.append(4) deque.insert(3, at: 0) deque.insert(1, at: 0) print("双向队列 deque = \(deque)") /* 访问元素 */ let peekFirst = deque.first! print("队首元素 peekFirst = \(peekFirst)") let peekLast = deque.last! print("队尾元素 peekLast = \(peekLast)") /* 元素出队 */ // 使用 Array 模拟时 popFirst 的复杂度为 O(n) let popFirst = deque.removeFirst() print("队首出队元素 popFirst = \(popFirst),队首出队后 deque = \(deque)") let popLast = deque.removeLast() print("队尾出队元素 popLast = \(popLast),队尾出队后 deque = \(deque)") /* 获取双向队列的长度 */ let size = deque.count print("双向队列长度 size = \(size)") /* 判断双向队列是否为空 */ let isEmpty = deque.isEmpty print("双向队列是否为空 = \(isEmpty)") } } ================================================ FILE: codes/swift/chapter_stack_and_queue/linkedlist_deque.swift ================================================ /** * File: linkedlist_deque.swift * Created Time: 2023-02-22 * Author: nuomi1 (nuomi1@qq.com) */ /* 双向链表节点 */ class ListNode { var val: Int // 节点值 var next: ListNode? // 后继节点引用 weak var prev: ListNode? // 前驱节点引用 init(val: Int) { self.val = val } } /* 基于双向链表实现的双向队列 */ class LinkedListDeque { private var front: ListNode? // 头节点 front private var rear: ListNode? // 尾节点 rear private var _size: Int // 双向队列的长度 init() { _size = 0 } /* 获取双向队列的长度 */ func size() -> Int { _size } /* 判断双向队列是否为空 */ func isEmpty() -> Bool { size() == 0 } /* 入队操作 */ private func push(num: Int, isFront: Bool) { let node = ListNode(val: num) // 若链表为空,则令 front 和 rear 都指向 node if isEmpty() { front = node rear = node } // 队首入队操作 else if isFront { // 将 node 添加至链表头部 front?.prev = node node.next = front front = node // 更新头节点 } // 队尾入队操作 else { // 将 node 添加至链表尾部 rear?.next = node node.prev = rear rear = node // 更新尾节点 } _size += 1 // 更新队列长度 } /* 队首入队 */ func pushFirst(num: Int) { push(num: num, isFront: true) } /* 队尾入队 */ func pushLast(num: Int) { push(num: num, isFront: false) } /* 出队操作 */ private func pop(isFront: Bool) -> Int { if isEmpty() { fatalError("双向队列为空") } let val: Int // 队首出队操作 if isFront { val = front!.val // 暂存头节点值 // 删除头节点 let fNext = front?.next if fNext != nil { fNext?.prev = nil front?.next = nil } front = fNext // 更新头节点 } // 队尾出队操作 else { val = rear!.val // 暂存尾节点值 // 删除尾节点 let rPrev = rear?.prev if rPrev != nil { rPrev?.next = nil rear?.prev = nil } rear = rPrev // 更新尾节点 } _size -= 1 // 更新队列长度 return val } /* 队首出队 */ func popFirst() -> Int { pop(isFront: true) } /* 队尾出队 */ func popLast() -> Int { pop(isFront: false) } /* 访问队首元素 */ func peekFirst() -> Int { if isEmpty() { fatalError("双向队列为空") } return front!.val } /* 访问队尾元素 */ func peekLast() -> Int { if isEmpty() { fatalError("双向队列为空") } return rear!.val } /* 返回数组用于打印 */ func toArray() -> [Int] { var node = front var res = Array(repeating: 0, count: size()) for i in res.indices { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListDeque { /* Driver Code */ static func main() { /* 初始化双向队列 */ let deque = LinkedListDeque() deque.pushLast(num: 3) deque.pushLast(num: 2) deque.pushLast(num: 5) print("双向队列 deque = \(deque.toArray())") /* 访问元素 */ let peekFirst = deque.peekFirst() print("队首元素 peekFirst = \(peekFirst)") let peekLast = deque.peekLast() print("队尾元素 peekLast = \(peekLast)") /* 元素入队 */ deque.pushLast(num: 4) print("元素 4 队尾入队后 deque = \(deque.toArray())") deque.pushFirst(num: 1) print("元素 1 队首入队后 deque = \(deque.toArray())") /* 元素出队 */ let popLast = deque.popLast() print("队尾出队元素 = \(popLast),队尾出队后 deque = \(deque.toArray())") let popFirst = deque.popFirst() print("队首出队元素 = \(popFirst),队首出队后 deque = \(deque.toArray())") /* 获取双向队列的长度 */ let size = deque.size() print("双向队列长度 size = \(size)") /* 判断双向队列是否为空 */ let isEmpty = deque.isEmpty() print("双向队列是否为空 = \(isEmpty)") } } ================================================ FILE: codes/swift/chapter_stack_and_queue/linkedlist_queue.swift ================================================ /** * File: linkedlist_queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 基于链表实现的队列 */ class LinkedListQueue { private var front: ListNode? // 头节点 private var rear: ListNode? // 尾节点 private var _size: Int init() { _size = 0 } /* 获取队列的长度 */ func size() -> Int { _size } /* 判断队列是否为空 */ func isEmpty() -> Bool { size() == 0 } /* 入队 */ func push(num: Int) { // 在尾节点后添加 num let node = ListNode(x: num) // 如果队列为空,则令头、尾节点都指向该节点 if front == nil { front = node rear = node } // 如果队列不为空,则将该节点添加到尾节点后 else { rear?.next = node rear = node } _size += 1 } /* 出队 */ @discardableResult func pop() -> Int { let num = peek() // 删除头节点 front = front?.next _size -= 1 return num } /* 访问队首元素 */ func peek() -> Int { if isEmpty() { fatalError("队列为空") } return front!.val } /* 将链表转化为 Array 并返回 */ func toArray() -> [Int] { var node = front var res = Array(repeating: 0, count: size()) for i in res.indices { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListQueue { /* Driver Code */ static func main() { /* 初始化队列 */ let queue = LinkedListQueue() /* 元素入队 */ queue.push(num: 1) queue.push(num: 3) queue.push(num: 2) queue.push(num: 5) queue.push(num: 4) print("队列 queue = \(queue.toArray())") /* 访问队首元素 */ let peek = queue.peek() print("队首元素 peek = \(peek)") /* 元素出队 */ let pop = queue.pop() print("出队元素 pop = \(pop),出队后 queue = \(queue.toArray())") /* 获取队列的长度 */ let size = queue.size() print("队列长度 size = \(size)") /* 判断队列是否为空 */ let isEmpty = queue.isEmpty() print("队列是否为空 = \(isEmpty)") } } ================================================ FILE: codes/swift/chapter_stack_and_queue/linkedlist_stack.swift ================================================ /** * File: linkedlist_stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 基于链表实现的栈 */ class LinkedListStack { private var _peek: ListNode? // 将头节点作为栈顶 private var _size: Int // 栈的长度 init() { _size = 0 } /* 获取栈的长度 */ func size() -> Int { _size } /* 判断栈是否为空 */ func isEmpty() -> Bool { size() == 0 } /* 入栈 */ func push(num: Int) { let node = ListNode(x: num) node.next = _peek _peek = node _size += 1 } /* 出栈 */ @discardableResult func pop() -> Int { let num = peek() _peek = _peek?.next _size -= 1 return num } /* 访问栈顶元素 */ func peek() -> Int { if isEmpty() { fatalError("栈为空") } return _peek!.val } /* 将 List 转化为 Array 并返回 */ func toArray() -> [Int] { var node = _peek var res = Array(repeating: 0, count: size()) for i in res.indices.reversed() { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListStack { /* Driver Code */ static func main() { /* 初始化栈 */ let stack = LinkedListStack() /* 元素入栈 */ stack.push(num: 1) stack.push(num: 3) stack.push(num: 2) stack.push(num: 5) stack.push(num: 4) print("栈 stack = \(stack.toArray())") /* 访问栈顶元素 */ let peek = stack.peek() print("栈顶元素 peek = \(peek)") /* 元素出栈 */ let pop = stack.pop() print("出栈元素 pop = \(pop),出栈后 stack = \(stack.toArray())") /* 获取栈的长度 */ let size = stack.size() print("栈的长度 size = \(size)") /* 判断是否为空 */ let isEmpty = stack.isEmpty() print("栈是否为空 = \(isEmpty)") } } ================================================ FILE: codes/swift/chapter_stack_and_queue/queue.swift ================================================ /** * File: queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Queue { /* Driver Code */ static func main() { /* 初始化队列 */ // Swift 没有内置的队列类,可以把 Array 当作队列来使用 var queue: [Int] = [] /* 元素入队 */ queue.append(1) queue.append(3) queue.append(2) queue.append(5) queue.append(4) print("队列 queue = \(queue)") /* 访问队首元素 */ let peek = queue.first! print("队首元素 peek = \(peek)") /* 元素出队 */ // 使用 Array 模拟时 pop 的复杂度为 O(n) let pool = queue.removeFirst() print("出队元素 pop = \(pool),出队后 queue = \(queue)") /* 获取队列的长度 */ let size = queue.count print("队列长度 size = \(size)") /* 判断队列是否为空 */ let isEmpty = queue.isEmpty print("队列是否为空 = \(isEmpty)") } } ================================================ FILE: codes/swift/chapter_stack_and_queue/stack.swift ================================================ /** * File: stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Stack { /* Driver Code */ static func main() { /* 初始化栈 */ // Swift 没有内置的栈类,可以把 Array 当作栈来使用 var stack: [Int] = [] /* 元素入栈 */ stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) print("栈 stack = \(stack)") /* 访问栈顶元素 */ let peek = stack.last! print("栈顶元素 peek = \(peek)") /* 元素出栈 */ let pop = stack.removeLast() print("出栈元素 pop = \(pop),出栈后 stack = \(stack)") /* 获取栈的长度 */ let size = stack.count print("栈的长度 size = \(size)") /* 判断是否为空 */ let isEmpty = stack.isEmpty print("栈是否为空 = \(isEmpty)") } } ================================================ FILE: codes/swift/chapter_tree/array_binary_tree.swift ================================================ /** * File: array_binary_tree.swift * Created Time: 2023-07-23 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 数组表示下的二叉树类 */ class ArrayBinaryTree { private var tree: [Int?] /* 构造方法 */ init(arr: [Int?]) { tree = arr } /* 列表容量 */ func size() -> Int { tree.count } /* 获取索引为 i 节点的值 */ func val(i: Int) -> Int? { // 若索引越界,则返回 null ,代表空位 if i < 0 || i >= size() { return nil } return tree[i] } /* 获取索引为 i 节点的左子节点的索引 */ func left(i: Int) -> Int { 2 * i + 1 } /* 获取索引为 i 节点的右子节点的索引 */ func right(i: Int) -> Int { 2 * i + 2 } /* 获取索引为 i 节点的父节点的索引 */ func parent(i: Int) -> Int { (i - 1) / 2 } /* 层序遍历 */ func levelOrder() -> [Int] { var res: [Int] = [] // 直接遍历数组 for i in 0 ..< size() { if let val = val(i: i) { res.append(val) } } return res } /* 深度优先遍历 */ private func dfs(i: Int, order: String, res: inout [Int]) { // 若为空位,则返回 guard let val = val(i: i) else { return } // 前序遍历 if order == "pre" { res.append(val) } dfs(i: left(i: i), order: order, res: &res) // 中序遍历 if order == "in" { res.append(val) } dfs(i: right(i: i), order: order, res: &res) // 后序遍历 if order == "post" { res.append(val) } } /* 前序遍历 */ func preOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "pre", res: &res) return res } /* 中序遍历 */ func inOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "in", res: &res) return res } /* 后序遍历 */ func postOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "post", res: &res) return res } } @main enum _ArrayBinaryTree { /* Driver Code */ static func main() { // 初始化二叉树 // 这里借助了一个从数组直接生成二叉树的函数 let arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] let root = TreeNode.listToTree(arr: arr) print("\n初始化二叉树\n") print("二叉树的数组表示:") print(arr) print("二叉树的链表表示:") PrintUtil.printTree(root: root) // 数组表示下的二叉树类 let abt = ArrayBinaryTree(arr: arr) // 访问节点 let i = 1 let l = abt.left(i: i) let r = abt.right(i: i) let p = abt.parent(i: i) print("\n当前节点的索引为 \(i) ,值为 \(abt.val(i: i) as Any)") print("其左子节点的索引为 \(l) ,值为 \(abt.val(i: l) as Any)") print("其右子节点的索引为 \(r) ,值为 \(abt.val(i: r) as Any)") print("其父节点的索引为 \(p) ,值为 \(abt.val(i: p) as Any)") // 遍历树 var res = abt.levelOrder() print("\n层序遍历为:\(res)") res = abt.preOrder() print("前序遍历为:\(res)") res = abt.inOrder() print("中序遍历为:\(res)") res = abt.postOrder() print("后序遍历为:\(res)") } } ================================================ FILE: codes/swift/chapter_tree/avl_tree.swift ================================================ /** * File: avl_tree.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* AVL 树 */ class AVLTree { fileprivate var root: TreeNode? // 根节点 init() {} /* 获取节点高度 */ func height(node: TreeNode?) -> Int { // 空节点高度为 -1 ,叶节点高度为 0 node?.height ?? -1 } /* 更新节点高度 */ private func updateHeight(node: TreeNode?) { // 节点高度等于最高子树高度 + 1 node?.height = max(height(node: node?.left), height(node: node?.right)) + 1 } /* 获取平衡因子 */ func balanceFactor(node: TreeNode?) -> Int { // 空节点平衡因子为 0 guard let node = node else { return 0 } // 节点平衡因子 = 左子树高度 - 右子树高度 return height(node: node.left) - height(node: node.right) } /* 右旋操作 */ private func rightRotate(node: TreeNode?) -> TreeNode? { let child = node?.left let grandChild = child?.right // 以 child 为原点,将 node 向右旋转 child?.right = node node?.left = grandChild // 更新节点高度 updateHeight(node: node) updateHeight(node: child) // 返回旋转后子树的根节点 return child } /* 左旋操作 */ private func leftRotate(node: TreeNode?) -> TreeNode? { let child = node?.right let grandChild = child?.left // 以 child 为原点,将 node 向左旋转 child?.left = node node?.right = grandChild // 更新节点高度 updateHeight(node: node) updateHeight(node: child) // 返回旋转后子树的根节点 return child } /* 执行旋转操作,使该子树重新恢复平衡 */ private func rotate(node: TreeNode?) -> TreeNode? { // 获取节点 node 的平衡因子 let balanceFactor = balanceFactor(node: node) // 左偏树 if balanceFactor > 1 { if self.balanceFactor(node: node?.left) >= 0 { // 右旋 return rightRotate(node: node) } else { // 先左旋后右旋 node?.left = leftRotate(node: node?.left) return rightRotate(node: node) } } // 右偏树 if balanceFactor < -1 { if self.balanceFactor(node: node?.right) <= 0 { // 左旋 return leftRotate(node: node) } else { // 先右旋后左旋 node?.right = rightRotate(node: node?.right) return leftRotate(node: node) } } // 平衡树,无须旋转,直接返回 return node } /* 插入节点 */ func insert(val: Int) { root = insertHelper(node: root, val: val) } /* 递归插入节点(辅助方法) */ private func insertHelper(node: TreeNode?, val: Int) -> TreeNode? { var node = node if node == nil { return TreeNode(x: val) } /* 1. 查找插入位置并插入节点 */ if val < node!.val { node?.left = insertHelper(node: node?.left, val: val) } else if val > node!.val { node?.right = insertHelper(node: node?.right, val: val) } else { return node // 重复节点不插入,直接返回 } updateHeight(node: node) // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = rotate(node: node) // 返回子树的根节点 return node } /* 删除节点 */ func remove(val: Int) { root = removeHelper(node: root, val: val) } /* 递归删除节点(辅助方法) */ private func removeHelper(node: TreeNode?, val: Int) -> TreeNode? { var node = node if node == nil { return nil } /* 1. 查找节点并删除 */ if val < node!.val { node?.left = removeHelper(node: node?.left, val: val) } else if val > node!.val { node?.right = removeHelper(node: node?.right, val: val) } else { if node?.left == nil || node?.right == nil { let child = node?.left ?? node?.right // 子节点数量 = 0 ,直接删除 node 并返回 if child == nil { return nil } // 子节点数量 = 1 ,直接删除 node else { node = child } } else { // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 var temp = node?.right while temp?.left != nil { temp = temp?.left } node?.right = removeHelper(node: node?.right, val: temp!.val) node?.val = temp!.val } } updateHeight(node: node) // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = rotate(node: node) // 返回子树的根节点 return node } /* 查找节点 */ func search(val: Int) -> TreeNode? { var cur = root while cur != nil { // 目标节点在 cur 的右子树中 if cur!.val < val { cur = cur?.right } // 目标节点在 cur 的左子树中 else if cur!.val > val { cur = cur?.left } // 找到目标节点,跳出循环 else { break } } // 返回目标节点 return cur } } @main enum _AVLTree { static func testInsert(tree: AVLTree, val: Int) { tree.insert(val: val) print("\n插入节点 \(val) 后,AVL 树为") PrintUtil.printTree(root: tree.root) } static func testRemove(tree: AVLTree, val: Int) { tree.remove(val: val) print("\n删除节点 \(val) 后,AVL 树为") PrintUtil.printTree(root: tree.root) } /* Driver Code */ static func main() { /* 初始化空 AVL 树 */ let avlTree = AVLTree() /* 插入节点 */ // 请关注插入节点后,AVL 树是如何保持平衡的 testInsert(tree: avlTree, val: 1) testInsert(tree: avlTree, val: 2) testInsert(tree: avlTree, val: 3) testInsert(tree: avlTree, val: 4) testInsert(tree: avlTree, val: 5) testInsert(tree: avlTree, val: 8) testInsert(tree: avlTree, val: 7) testInsert(tree: avlTree, val: 9) testInsert(tree: avlTree, val: 10) testInsert(tree: avlTree, val: 6) /* 插入重复节点 */ testInsert(tree: avlTree, val: 7) /* 删除节点 */ // 请关注删除节点后,AVL 树是如何保持平衡的 testRemove(tree: avlTree, val: 8) // 删除度为 0 的节点 testRemove(tree: avlTree, val: 5) // 删除度为 1 的节点 testRemove(tree: avlTree, val: 4) // 删除度为 2 的节点 /* 查询节点 */ let node = avlTree.search(val: 7) print("\n查找到的节点对象为 \(node!),节点值 = \(node!.val)") } } ================================================ FILE: codes/swift/chapter_tree/binary_search_tree.swift ================================================ /** * File: binary_search_tree.swift * Created Time: 2023-01-26 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 二叉搜索树 */ class BinarySearchTree { private var root: TreeNode? /* 构造方法 */ init() { // 初始化空树 root = nil } /* 获取二叉树根节点 */ func getRoot() -> TreeNode? { root } /* 查找节点 */ func search(num: Int) -> TreeNode? { var cur = root // 循环查找,越过叶节点后跳出 while cur != nil { // 目标节点在 cur 的右子树中 if cur!.val < num { cur = cur?.right } // 目标节点在 cur 的左子树中 else if cur!.val > num { cur = cur?.left } // 找到目标节点,跳出循环 else { break } } // 返回目标节点 return cur } /* 插入节点 */ func insert(num: Int) { // 若树为空,则初始化根节点 if root == nil { root = TreeNode(x: num) return } var cur = root var pre: TreeNode? // 循环查找,越过叶节点后跳出 while cur != nil { // 找到重复节点,直接返回 if cur!.val == num { return } pre = cur // 插入位置在 cur 的右子树中 if cur!.val < num { cur = cur?.right } // 插入位置在 cur 的左子树中 else { cur = cur?.left } } // 插入节点 let node = TreeNode(x: num) if pre!.val < num { pre?.right = node } else { pre?.left = node } } /* 删除节点 */ func remove(num: Int) { // 若树为空,直接提前返回 if root == nil { return } var cur = root var pre: TreeNode? // 循环查找,越过叶节点后跳出 while cur != nil { // 找到待删除节点,跳出循环 if cur!.val == num { break } pre = cur // 待删除节点在 cur 的右子树中 if cur!.val < num { cur = cur?.right } // 待删除节点在 cur 的左子树中 else { cur = cur?.left } } // 若无待删除节点,则直接返回 if cur == nil { return } // 子节点数量 = 0 or 1 if cur?.left == nil || cur?.right == nil { // 当子节点数量 = 0 / 1 时, child = null / 该子节点 let child = cur?.left ?? cur?.right // 删除节点 cur if cur !== root { if pre?.left === cur { pre?.left = child } else { pre?.right = child } } else { // 若删除节点为根节点,则重新指定根节点 root = child } } // 子节点数量 = 2 else { // 获取中序遍历中 cur 的下一个节点 var tmp = cur?.right while tmp?.left != nil { tmp = tmp?.left } // 递归删除节点 tmp remove(num: tmp!.val) // 用 tmp 覆盖 cur cur?.val = tmp!.val } } } @main enum _BinarySearchTree { /* Driver Code */ static func main() { /* 初始化二叉搜索树 */ let bst = BinarySearchTree() // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] for num in nums { bst.insert(num: num) } print("\n初始化的二叉树为\n") PrintUtil.printTree(root: bst.getRoot()) /* 查找节点 */ let node = bst.search(num: 7) print("\n查找到的节点对象为 \(node!),节点值 = \(node!.val)") /* 插入节点 */ bst.insert(num: 16) print("\n插入节点 16 后,二叉树为\n") PrintUtil.printTree(root: bst.getRoot()) /* 删除节点 */ bst.remove(num: 1) print("\n删除节点 1 后,二叉树为\n") PrintUtil.printTree(root: bst.getRoot()) bst.remove(num: 2) print("\n删除节点 2 后,二叉树为\n") PrintUtil.printTree(root: bst.getRoot()) bst.remove(num: 4) print("\n删除节点 4 后,二叉树为\n") PrintUtil.printTree(root: bst.getRoot()) } } ================================================ FILE: codes/swift/chapter_tree/binary_tree.swift ================================================ /** * File: binary_tree.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum BinaryTree { /* Driver Code */ static func main() { /* 初始化二叉树 */ // 初始化节点 let n1 = TreeNode(x: 1) let n2 = TreeNode(x: 2) let n3 = TreeNode(x: 3) let n4 = TreeNode(x: 4) let n5 = TreeNode(x: 5) // 构建节点之间的引用(指针) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 print("\n初始化二叉树\n") PrintUtil.printTree(root: n1) /* 插入与删除节点 */ let P = TreeNode(x: 0) // 在 n1 -> n2 中间插入节点 P n1.left = P P.left = n2 print("\n插入节点 P 后\n") PrintUtil.printTree(root: n1) // 删除节点 P n1.left = n2 print("\n删除节点 P 后\n") PrintUtil.printTree(root: n1) } } ================================================ FILE: codes/swift/chapter_tree/binary_tree_bfs.swift ================================================ /** * File: binary_tree_bfs.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 层序遍历 */ func levelOrder(root: TreeNode) -> [Int] { // 初始化队列,加入根节点 var queue: [TreeNode] = [root] // 初始化一个列表,用于保存遍历序列 var list: [Int] = [] while !queue.isEmpty { let node = queue.removeFirst() // 队列出队 list.append(node.val) // 保存节点值 if let left = node.left { queue.append(left) // 左子节点入队 } if let right = node.right { queue.append(right) // 右子节点入队 } } return list } @main enum BinaryTreeBFS { /* Driver Code */ static func main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 let node = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! print("\n初始化二叉树\n") PrintUtil.printTree(root: node) /* 层序遍历 */ let list = levelOrder(root: node) print("\n层序遍历的节点打印序列 = \(list)") } } ================================================ FILE: codes/swift/chapter_tree/binary_tree_dfs.swift ================================================ /** * File: binary_tree_dfs.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils // 初始化列表,用于存储遍历序列 var list: [Int] = [] /* 前序遍历 */ func preOrder(root: TreeNode?) { guard let root = root else { return } // 访问优先级:根节点 -> 左子树 -> 右子树 list.append(root.val) preOrder(root: root.left) preOrder(root: root.right) } /* 中序遍历 */ func inOrder(root: TreeNode?) { guard let root = root else { return } // 访问优先级:左子树 -> 根节点 -> 右子树 inOrder(root: root.left) list.append(root.val) inOrder(root: root.right) } /* 后序遍历 */ func postOrder(root: TreeNode?) { guard let root = root else { return } // 访问优先级:左子树 -> 右子树 -> 根节点 postOrder(root: root.left) postOrder(root: root.right) list.append(root.val) } @main enum BinaryTreeDFS { /* Driver Code */ static func main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 let root = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! print("\n初始化二叉树\n") PrintUtil.printTree(root: root) /* 前序遍历 */ list.removeAll() preOrder(root: root) print("\n前序遍历的节点打印序列 = \(list)") /* 中序遍历 */ list.removeAll() inOrder(root: root) print("\n中序遍历的节点打印序列 = \(list)") /* 后序遍历 */ list.removeAll() postOrder(root: root) print("\n后序遍历的节点打印序列 = \(list)") } } ================================================ FILE: codes/swift/utils/ListNode.swift ================================================ /** * File: ListNode.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ public class ListNode: Hashable { public var val: Int // 节点值 public var next: ListNode? // 后继节点引用 public init(x: Int) { val = x } public static func == (lhs: ListNode, rhs: ListNode) -> Bool { lhs.val == rhs.val && lhs.next.map { ObjectIdentifier($0) } == rhs.next.map { ObjectIdentifier($0) } } public func hash(into hasher: inout Hasher) { hasher.combine(val) hasher.combine(next.map { ObjectIdentifier($0) }) } public static func arrToLinkedList(arr: [Int]) -> ListNode? { let dum = ListNode(x: 0) var head: ListNode? = dum for val in arr { head?.next = ListNode(x: val) head = head?.next } return dum.next } } ================================================ FILE: codes/swift/utils/Pair.swift ================================================ /** * File: Pair.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ /* 键值对 */ public class Pair: Equatable { public var key: Int public var val: String public init(key: Int, val: String) { self.key = key self.val = val } public static func == (lhs: Pair, rhs: Pair) -> Bool { lhs.key == rhs.key && lhs.val == rhs.val } } ================================================ FILE: codes/swift/utils/PrintUtil.swift ================================================ /** * File: PrintUtil.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ public enum PrintUtil { private class Trunk { var prev: Trunk? var str: String init(prev: Trunk?, str: String) { self.prev = prev self.str = str } } public static func printLinkedList(head: ListNode) { var head: ListNode? = head var list: [String] = [] while head != nil { list.append("\(head!.val)") head = head?.next } print(list.joined(separator: " -> ")) } public static func printTree(root: TreeNode?) { printTree(root: root, prev: nil, isRight: false) } private static func printTree(root: TreeNode?, prev: Trunk?, isRight: Bool) { if root == nil { return } var prevStr = " " let trunk = Trunk(prev: prev, str: prevStr) printTree(root: root?.right, prev: trunk, isRight: true) if prev == nil { trunk.str = "———" } else if isRight { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev?.str = prevStr } showTrunks(p: trunk) print(" \(root!.val)") if prev != nil { prev?.str = prevStr } trunk.str = " |" printTree(root: root?.left, prev: trunk, isRight: false) } private static func showTrunks(p: Trunk?) { if p == nil { return } showTrunks(p: p?.prev) print(p!.str, terminator: "") } public static func printHashMap(map: [K: V]) { for (key, value) in map { print("\(key) -> \(value)") } } public static func printHeap(queue: [Int]) { print("堆的数组表示:", terminator: "") print(queue) print("堆的树状表示:") let root = TreeNode.listToTree(arr: queue) printTree(root: root) } public static func printMatrix(matrix: [[T]]) { print("[") for row in matrix { print(" \(row),") } print("]") } } ================================================ FILE: codes/swift/utils/TreeNode.swift ================================================ /** * File: TreeNode.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 二叉树节点类 */ public class TreeNode { public var val: Int // 节点值 public var height: Int // 节点高度 public var left: TreeNode? // 左子节点引用 public var right: TreeNode? // 右子节点引用 /* 构造方法 */ public init(x: Int) { val = x height = 0 } // 序列化编码规则请参考: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二叉树的数组表示: // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] // 二叉树的链表表示: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* 将列表反序列化为二叉树:递归 */ private static func listToTreeDFS(arr: [Int?], i: Int) -> TreeNode? { if i < 0 || i >= arr.count || arr[i] == nil { return nil } let root = TreeNode(x: arr[i]!) root.left = listToTreeDFS(arr: arr, i: 2 * i + 1) root.right = listToTreeDFS(arr: arr, i: 2 * i + 2) return root } /* 将列表反序列化为二叉树 */ public static func listToTree(arr: [Int?]) -> TreeNode? { listToTreeDFS(arr: arr, i: 0) } /* 将二叉树序列化为列表:递归 */ private static func treeToListDFS(root: TreeNode?, i: Int, res: inout [Int?]) { if root == nil { return } while i >= res.count { res.append(nil) } res[i] = root?.val treeToListDFS(root: root?.left, i: 2 * i + 1, res: &res) treeToListDFS(root: root?.right, i: 2 * i + 2, res: &res) } /* 将二叉树序列化为列表 */ public static func treeToList(root: TreeNode?) -> [Int?] { var res: [Int?] = [] treeToListDFS(root: root, i: 0, res: &res) return res } } ================================================ FILE: codes/swift/utils/Vertex.swift ================================================ /** * File: Vertex.swift * Created Time: 2023-02-19 * Author: nuomi1 (nuomi1@qq.com) */ /* 顶点类 */ public class Vertex: Hashable { public var val: Int public init(val: Int) { self.val = val } public static func == (lhs: Vertex, rhs: Vertex) -> Bool { lhs.val == rhs.val } public func hash(into hasher: inout Hasher) { hasher.combine(val) } /* 输入值列表 vals ,返回顶点列表 vets */ public static func valsToVets(vals: [Int]) -> [Vertex] { vals.map { Vertex(val: $0) } } /* 输入顶点列表 vets ,返回值列表 vals */ public static func vetsToVals(vets: [Vertex]) -> [Int] { vets.map { $0.val } } } ================================================ FILE: codes/typescript/.gitignore ================================================ node_modules package-lock.json ================================================ FILE: codes/typescript/.prettierrc ================================================ { "tabWidth": 4, "useTabs": false, "semi": true, "singleQuote": true } ================================================ FILE: codes/typescript/chapter_array_and_linkedlist/array.ts ================================================ /** * File: array.ts * Created Time: 2022-12-04 * Author: Justin (xiefahit@gmail.com) */ /* 随机访问元素 */ function randomAccess(nums: number[]): number { // 在区间 [0, nums.length) 中随机抽取一个数字 const random_index = Math.floor(Math.random() * nums.length); // 获取并返回随机元素 const random_num = nums[random_index]; return random_num; } /* 扩展数组长度 */ // 请注意,TypeScript 的 Array 是动态数组,可以直接扩展 // 为了方便学习,本函数将 Array 看作长度不可变的数组 function extend(nums: number[], enlarge: number): number[] { // 初始化一个扩展长度后的数组 const res = new Array(nums.length + enlarge).fill(0); // 将原数组中的所有元素复制到新数组 for (let i = 0; i < nums.length; i++) { res[i] = nums[i]; } // 返回扩展后的新数组 return res; } /* 在数组的索引 index 处插入元素 num */ function insert(nums: number[], num: number, index: number): void { // 把索引 index 以及之后的所有元素向后移动一位 for (let i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 将 num 赋给 index 处的元素 nums[index] = num; } /* 删除索引 index 处的元素 */ function remove(nums: number[], index: number): void { // 把索引 index 之后的所有元素向前移动一位 for (let i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* 遍历数组 */ function traverse(nums: number[]): void { let count = 0; // 通过索引遍历数组 for (let i = 0; i < nums.length; i++) { count += nums[i]; } // 直接遍历数组元素 for (const num of nums) { count += num; } } /* 在数组中查找指定元素 */ function find(nums: number[], target: number): number { for (let i = 0; i < nums.length; i++) { if (nums[i] === target) { return i; } } return -1; } /* Driver Code */ /* 初始化数组 */ const arr: number[] = new Array(5).fill(0); console.log('数组 arr =', arr); let nums: number[] = [1, 3, 2, 5, 4]; console.log('数组 nums =', nums); /* 随机访问 */ let random_num = randomAccess(nums); console.log('在 nums 中获取随机元素', random_num); /* 长度扩展 */ nums = extend(nums, 3); console.log('将数组长度扩展至 8 ,得到 nums =', nums); /* 插入元素 */ insert(nums, 6, 3); console.log('在索引 3 处插入数字 6 ,得到 nums =', nums); /* 删除元素 */ remove(nums, 2); console.log('删除索引 2 处的元素,得到 nums =', nums); /* 遍历数组 */ traverse(nums); /* 查找元素 */ let index = find(nums, 3); console.log('在 nums 中查找元素 3 ,得到索引 =', index); export {}; ================================================ FILE: codes/typescript/chapter_array_and_linkedlist/linked_list.ts ================================================ /** * File: linked_list.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from '../modules/ListNode'; import { printLinkedList } from '../modules/PrintUtil'; /* 在链表的节点 n0 之后插入节点 P */ function insert(n0: ListNode, P: ListNode): void { const n1 = n0.next; P.next = n1; n0.next = P; } /* 删除链表的节点 n0 之后的首个节点 */ function remove(n0: ListNode): void { if (!n0.next) { return; } // n0 -> P -> n1 const P = n0.next; const n1 = P.next; n0.next = n1; } /* 访问链表中索引为 index 的节点 */ function access(head: ListNode | null, index: number): ListNode | null { for (let i = 0; i < index; i++) { if (!head) { return null; } head = head.next; } return head; } /* 在链表中查找值为 target 的首个节点 */ function find(head: ListNode | null, target: number): number { let index = 0; while (head !== null) { if (head.val === target) { return index; } head = head.next; index += 1; } return -1; } /* Driver Code */ /* 初始化链表 */ // 初始化各个节点 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; console.log('初始化的链表为'); printLinkedList(n0); /* 插入节点 */ insert(n0, new ListNode(0)); console.log('插入节点后的链表为'); printLinkedList(n0); /* 删除节点 */ remove(n0); console.log('删除节点后的链表为'); printLinkedList(n0); /* 访问节点 */ const node = access(n0, 3); console.log(`链表中索引 3 处的节点的值 = ${node?.val}`); /* 查找节点 */ const index = find(n0, 2); console.log(`链表中值为 2 的节点的索引 = ${index}`); export {}; ================================================ FILE: codes/typescript/chapter_array_and_linkedlist/list.ts ================================================ /** * File: list.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ /* 初始化列表 */ const nums: number[] = [1, 3, 2, 5, 4]; console.log(`列表 nums = ${nums}`); /* 访问元素 */ const num: number = nums[1]; console.log(`访问索引 1 处的元素,得到 num = ${num}`); /* 更新元素 */ nums[1] = 0; console.log(`将索引 1 处的元素更新为 0 ,得到 nums = ${nums}`); /* 清空列表 */ nums.length = 0; console.log(`清空列表后 nums = ${nums}`); /* 在尾部添加元素 */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); console.log(`添加元素后 nums = ${nums}`); /* 在中间插入元素 */ nums.splice(3, 0, 6); console.log(`在索引 3 处插入数字 6 ,得到 nums = ${nums}`); /* 删除元素 */ nums.splice(3, 1); console.log(`删除索引 3 处的元素,得到 nums = ${nums}`); /* 通过索引遍历列表 */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* 直接遍历列表元素 */ count = 0; for (const x of nums) { count += x; } /* 拼接两个列表 */ const nums1: number[] = [6, 8, 7, 10, 9]; nums.push(...nums1); console.log(`将列表 nums1 拼接到 nums 之后,得到 nums = ${nums}`); /* 排序列表 */ nums.sort((a, b) => a - b); console.log(`排序列表后 nums = ${nums}`); export {}; ================================================ FILE: codes/typescript/chapter_array_and_linkedlist/my_list.ts ================================================ /** * File: my_list.ts * Created Time: 2022-12-11 * Author: Justin (xiefahit@gmail.com) */ /* 列表类 */ class MyList { private arr: Array; // 数组(存储列表元素) private _capacity: number = 10; // 列表容量 private _size: number = 0; // 列表长度(当前元素数量) private extendRatio: number = 2; // 每次列表扩容的倍数 /* 构造方法 */ constructor() { this.arr = new Array(this._capacity); } /* 获取列表长度(当前元素数量)*/ public size(): number { return this._size; } /* 获取列表容量 */ public capacity(): number { return this._capacity; } /* 访问元素 */ public get(index: number): number { // 索引如果越界,则抛出异常,下同 if (index < 0 || index >= this._size) throw new Error('索引越界'); return this.arr[index]; } /* 更新元素 */ public set(index: number, num: number): void { if (index < 0 || index >= this._size) throw new Error('索引越界'); this.arr[index] = num; } /* 在尾部添加元素 */ public add(num: number): void { // 如果长度等于容量,则需要扩容 if (this._size === this._capacity) this.extendCapacity(); // 将新元素添加到列表尾部 this.arr[this._size] = num; this._size++; } /* 在中间插入元素 */ public insert(index: number, num: number): void { if (index < 0 || index >= this._size) throw new Error('索引越界'); // 元素数量超出容量时,触发扩容机制 if (this._size === this._capacity) { this.extendCapacity(); } // 将索引 index 以及之后的元素都向后移动一位 for (let j = this._size - 1; j >= index; j--) { this.arr[j + 1] = this.arr[j]; } // 更新元素数量 this.arr[index] = num; this._size++; } /* 删除元素 */ public remove(index: number): number { if (index < 0 || index >= this._size) throw new Error('索引越界'); let num = this.arr[index]; // 将将索引 index 之后的元素都向前移动一位 for (let j = index; j < this._size - 1; j++) { this.arr[j] = this.arr[j + 1]; } // 更新元素数量 this._size--; // 返回被删除的元素 return num; } /* 列表扩容 */ public extendCapacity(): void { // 新建一个长度为 size 的数组,并将原数组复制到新数组 this.arr = this.arr.concat( new Array(this.capacity() * (this.extendRatio - 1)) ); // 更新列表容量 this._capacity = this.arr.length; } /* 将列表转换为数组 */ public toArray(): number[] { let size = this.size(); // 仅转换有效长度范围内的列表元素 const arr = new Array(size); for (let i = 0; i < size; i++) { arr[i] = this.get(i); } return arr; } } /* Driver Code */ /* 初始化列表 */ const nums = new MyList(); /* 在尾部添加元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); console.log( `列表 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,长度 = ${nums.size()}` ); /* 在中间插入元素 */ nums.insert(3, 6); console.log(`在索引 3 处插入数字 6 ,得到 nums = ${nums.toArray()}`); /* 删除元素 */ nums.remove(3); console.log(`删除索引 3 处的元素,得到 nums = ${nums.toArray()}`); /* 访问元素 */ const num = nums.get(1); console.log(`访问索引 1 处的元素,得到 num = ${num}`); /* 更新元素 */ nums.set(1, 0); console.log(`将索引 1 处的元素更新为 0 ,得到 nums = ${nums.toArray()}`); /* 测试扩容机制 */ for (let i = 0; i < 10; i++) { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 nums.add(i); } console.log( `扩容后的列表 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,长度 = ${nums.size()}` ); export {}; ================================================ FILE: codes/typescript/chapter_backtracking/n_queens.ts ================================================ /** * File: n_queens.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* 回溯算法:n 皇后 */ function backtrack( row: number, n: number, state: string[][], res: string[][][], cols: boolean[], diags1: boolean[], diags2: boolean[] ): void { // 当放置完所有行时,记录解 if (row === n) { res.push(state.map((row) => row.slice())); return; } // 遍历所有列 for (let col = 0; col < n; col++) { // 计算该格子对应的主对角线和次对角线 const diag1 = row - col + n - 1; const diag2 = row + col; // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 尝试:将皇后放置在该格子 state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // 放置下一行 backtrack(row + 1, n, state, res, cols, diags1, diags2); // 回退:将该格子恢复为空位 state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* 求解 n 皇后 */ function nQueens(n: number): string[][][] { // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 const state = Array.from({ length: n }, () => Array(n).fill('#')); const cols = Array(n).fill(false); // 记录列是否有皇后 const diags1 = Array(2 * n - 1).fill(false); // 记录主对角线上是否有皇后 const diags2 = Array(2 * n - 1).fill(false); // 记录次对角线上是否有皇后 const res: string[][][] = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } // Driver Code const n = 4; const res = nQueens(n); console.log(`输入棋盘长宽为 ${n}`); console.log(`皇后放置方案共有 ${res.length} 种`); res.forEach((state) => { console.log('--------------------'); state.forEach((row) => console.log(row)); }); export {}; ================================================ FILE: codes/typescript/chapter_backtracking/permutations_i.ts ================================================ /** * File: permutations_i.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* 回溯算法:全排列 I */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // 当状态长度等于元素数量时,记录解 if (state.length === choices.length) { res.push([...state]); return; } // 遍历所有选择 choices.forEach((choice, i) => { // 剪枝:不允许重复选择元素 if (!selected[i]) { // 尝试:做出选择,更新状态 selected[i] = true; state.push(choice); // 进行下一轮选择 backtrack(state, choices, selected, res); // 回退:撤销选择,恢复到之前的状态 selected[i] = false; state.pop(); } }); } /* 全排列 I */ function permutationsI(nums: number[]): number[][] { const res: number[][] = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums: number[] = [1, 2, 3]; const res: number[][] = permutationsI(nums); console.log(`输入数组 nums = ${JSON.stringify(nums)}`); console.log(`所有排列 res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: codes/typescript/chapter_backtracking/permutations_ii.ts ================================================ /** * File: permutations_ii.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* 回溯算法:全排列 II */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // 当状态长度等于元素数量时,记录解 if (state.length === choices.length) { res.push([...state]); return; } // 遍历所有选择 const duplicated = new Set(); choices.forEach((choice, i) => { // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 if (!selected[i] && !duplicated.has(choice)) { // 尝试:做出选择,更新状态 duplicated.add(choice); // 记录选择过的元素值 selected[i] = true; state.push(choice); // 进行下一轮选择 backtrack(state, choices, selected, res); // 回退:撤销选择,恢复到之前的状态 selected[i] = false; state.pop(); } }); } /* 全排列 II */ function permutationsII(nums: number[]): number[][] { const res: number[][] = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums: number[] = [1, 2, 2]; const res: number[][] = permutationsII(nums); console.log(`输入数组 nums = ${JSON.stringify(nums)}`); console.log(`所有排列 res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts ================================================ /** * File: preorder_traversal_i_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 前序遍历:例题一 */ function preOrder(root: TreeNode | null, res: TreeNode[]): void { if (root === null) { return; } if (root.val === 7) { // 记录解 res.push(root); } preOrder(root.left, res); preOrder(root.right, res); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树'); printTree(root); // 前序遍历 const res: TreeNode[] = []; preOrder(root, res); console.log('\n输出所有值为 7 的节点'); console.log(res.map((node) => node.val)); export {}; ================================================ FILE: codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts ================================================ /** * File: preorder_traversal_ii_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 前序遍历:例题二 */ function preOrder( root: TreeNode | null, path: TreeNode[], res: TreeNode[][] ): void { if (root === null) { return; } // 尝试 path.push(root); if (root.val === 7) { // 记录解 res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // 回退 path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树'); printTree(root); // 前序遍历 const path: TreeNode[] = []; const res: TreeNode[][] = []; preOrder(root, path, res); console.log('\n输出所有根节点到节点 7 的路径'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts ================================================ /** * File: preorder_traversal_iii_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 前序遍历:例题三 */ function preOrder( root: TreeNode | null, path: TreeNode[], res: TreeNode[][] ): void { // 剪枝 if (root === null || root.val === 3) { return; } // 尝试 path.push(root); if (root.val === 7) { // 记录解 res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // 回退 path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树'); printTree(root); // 前序遍历 const path: TreeNode[] = []; const res: TreeNode[][] = []; preOrder(root, path, res); console.log('\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts ================================================ /** * File: preorder_traversal_iii_template.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 判断当前状态是否为解 */ function isSolution(state: TreeNode[]): boolean { return state && state[state.length - 1]?.val === 7; } /* 记录解 */ function recordSolution(state: TreeNode[], res: TreeNode[][]): void { res.push([...state]); } /* 判断在当前状态下,该选择是否合法 */ function isValid(state: TreeNode[], choice: TreeNode): boolean { return choice !== null && choice.val !== 3; } /* 更新状态 */ function makeChoice(state: TreeNode[], choice: TreeNode): void { state.push(choice); } /* 恢复状态 */ function undoChoice(state: TreeNode[]): void { state.pop(); } /* 回溯算法:例题三 */ function backtrack( state: TreeNode[], choices: TreeNode[], res: TreeNode[][] ): void { // 检查是否为解 if (isSolution(state)) { // 记录解 recordSolution(state, res); } // 遍历所有选择 for (const choice of choices) { // 剪枝:检查选择是否合法 if (isValid(state, choice)) { // 尝试:做出选择,更新状态 makeChoice(state, choice); // 进行下一轮选择 backtrack(state, [choice.left, choice.right], res); // 回退:撤销选择,恢复到之前的状态 undoChoice(state); } } } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树'); printTree(root); // 回溯算法 const res: TreeNode[][] = []; backtrack([], [root], res); console.log('\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: codes/typescript/chapter_backtracking/subset_sum_i.ts ================================================ /** * File: subset_sum_i.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯算法:子集和 I */ function backtrack( state: number[], target: number, choices: number[], start: number, res: number[][] ): void { // 子集和等于 target 时,记录解 if (target === 0) { res.push([...state]); return; } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 for (let i = start; i < choices.length; i++) { // 剪枝一:若子集和超过 target ,则直接结束循环 // 这是因为数组已排序,后边元素更大,子集和一定超过 target if (target - choices[i] < 0) { break; } // 尝试:做出选择,更新 target, start state.push(choices[i]); // 进行下一轮选择 backtrack(state, target - choices[i], choices, i, res); // 回退:撤销选择,恢复到之前的状态 state.pop(); } } /* 求解子集和 I */ function subsetSumI(nums: number[], target: number): number[][] { const state = []; // 状态(子集) nums.sort((a, b) => a - b); // 对 nums 进行排序 const start = 0; // 遍历起始点 const res = []; // 结果列表(子集列表) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumI(nums, target); console.log(`输入数组 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`所有和等于 ${target} 的子集 res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: codes/typescript/chapter_backtracking/subset_sum_i_naive.ts ================================================ /** * File: subset_sum_i_naive.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯算法:子集和 I */ function backtrack( state: number[], target: number, total: number, choices: number[], res: number[][] ): void { // 子集和等于 target 时,记录解 if (total === target) { res.push([...state]); return; } // 遍历所有选择 for (let i = 0; i < choices.length; i++) { // 剪枝:若子集和超过 target ,则跳过该选择 if (total + choices[i] > target) { continue; } // 尝试:做出选择,更新元素和 total state.push(choices[i]); // 进行下一轮选择 backtrack(state, target, total + choices[i], choices, res); // 回退:撤销选择,恢复到之前的状态 state.pop(); } } /* 求解子集和 I(包含重复子集) */ function subsetSumINaive(nums: number[], target: number): number[][] { const state = []; // 状态(子集) const total = 0; // 子集和 const res = []; // 结果列表(子集列表) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumINaive(nums, target); console.log(`输入数组 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`所有和等于 ${target} 的子集 res = ${JSON.stringify(res)}`); console.log('请注意,该方法输出的结果包含重复集合'); export {}; ================================================ FILE: codes/typescript/chapter_backtracking/subset_sum_ii.ts ================================================ /** * File: subset_sum_ii.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯算法:子集和 II */ function backtrack( state: number[], target: number, choices: number[], start: number, res: number[][] ): void { // 子集和等于 target 时,记录解 if (target === 0) { res.push([...state]); return; } // 遍历所有选择 // 剪枝二:从 start 开始遍历,避免生成重复子集 // 剪枝三:从 start 开始遍历,避免重复选择同一元素 for (let 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.push(choices[i]); // 进行下一轮选择 backtrack(state, target - choices[i], choices, i + 1, res); // 回退:撤销选择,恢复到之前的状态 state.pop(); } } /* 求解子集和 II */ function subsetSumII(nums: number[], target: number): number[][] { const state = []; // 状态(子集) nums.sort((a, b) => a - b); // 对 nums 进行排序 const start = 0; // 遍历起始点 const res = []; // 结果列表(子集列表) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [4, 4, 5]; const target = 9; const res = subsetSumII(nums, target); console.log(`输入数组 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`所有和等于 ${target} 的子集 res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: codes/typescript/chapter_computational_complexity/iteration.ts ================================================ /** * File: iteration.ts * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* for 循环 */ function forLoop(n: number): number { let res = 0; // 循环求和 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { res += i; } return res; } /* while 循环 */ function whileLoop(n: number): number { let res = 0; let i = 1; // 初始化条件变量 // 循环求和 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // 更新条件变量 } return res; } /* while 循环(两次更新) */ function whileLoopII(n: number): number { let res = 0; let i = 1; // 初始化条件变量 // 循环求和 1, 4, 10, ... while (i <= n) { res += i; // 更新条件变量 i++; i *= 2; } return res; } /* 双层 for 循环 */ function nestedForLoop(n: number): string { let res = ''; // 循环 i = 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { // 循环 j = 1, 2, ..., n-1, n for (let j = 1; j <= n; j++) { res += `(${i}, ${j}), `; } } return res; } /* Driver Code */ const n = 5; let res: number; res = forLoop(n); console.log(`for 循环的求和结果 res = ${res}`); res = whileLoop(n); console.log(`while 循环的求和结果 res = ${res}`); res = whileLoopII(n); console.log(`while 循环(两次更新)求和结果 res = ${res}`); const resStr = nestedForLoop(n); console.log(`双层 for 循环的遍历结果 ${resStr}`); export {}; ================================================ FILE: codes/typescript/chapter_computational_complexity/recursion.ts ================================================ /** * File: recursion.ts * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 递归 */ function recur(n: number): number { // 终止条件 if (n === 1) return 1; // 递:递归调用 const res = recur(n - 1); // 归:返回结果 return n + res; } /* 使用迭代模拟递归 */ function forLoopRecur(n: number): number { // 使用一个显式的栈来模拟系统调用栈 const stack: number[] = []; let res: number = 0; // 递:递归调用 for (let i = n; i > 0; i--) { // 通过“入栈操作”模拟“递” stack.push(i); } // 归:返回结果 while (stack.length) { // 通过“出栈操作”模拟“归” res += stack.pop(); } // res = 1+2+3+...+n return res; } /* 尾递归 */ function tailRecur(n: number, res: number): number { // 终止条件 if (n === 0) return res; // 尾递归调用 return tailRecur(n - 1, res + n); } /* 斐波那契数列:递归 */ function fib(n: number): number { // 终止条件 f(1) = 0, f(2) = 1 if (n === 1 || n === 2) return n - 1; // 递归调用 f(n) = f(n-1) + f(n-2) const res = fib(n - 1) + fib(n - 2); // 返回结果 f(n) return res; } /* Driver Code */ const n = 5; let res: number; res = recur(n); console.log(`递归函数的求和结果 res = ${res}`); res = forLoopRecur(n); console.log(`使用迭代模拟递归的求和结果 res = ${res}`); res = tailRecur(n, 0); console.log(`尾递归函数的求和结果 res = ${res}`); res = fib(n); console.log(`斐波那契数列的第 ${n} 项为 ${res}`); export {}; ================================================ FILE: codes/typescript/chapter_computational_complexity/space_complexity.ts ================================================ /** * File: space_complexity.ts * Created Time: 2023-02-05 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from '../modules/ListNode'; import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 函数 */ function constFunc(): number { // 执行某些操作 return 0; } /* 常数阶 */ function constant(n: number): void { // 常量、变量、对象占用 O(1) 空间 const a = 0; const b = 0; const nums = new Array(10000); const node = new ListNode(0); // 循环中的变量占用 O(1) 空间 for (let i = 0; i < n; i++) { const c = 0; } // 循环中的函数占用 O(1) 空间 for (let i = 0; i < n; i++) { constFunc(); } } /* 线性阶 */ function linear(n: number): void { // 长度为 n 的数组占用 O(n) 空间 const nums = new Array(n); // 长度为 n 的列表占用 O(n) 空间 const nodes: ListNode[] = []; for (let i = 0; i < n; i++) { nodes.push(new ListNode(i)); } // 长度为 n 的哈希表占用 O(n) 空间 const map = new Map(); for (let i = 0; i < n; i++) { map.set(i, i.toString()); } } /* 线性阶(递归实现) */ function linearRecur(n: number): void { console.log(`递归 n = ${n}`); if (n === 1) return; linearRecur(n - 1); } /* 平方阶 */ function quadratic(n: number): void { // 矩阵占用 O(n^2) 空间 const numMatrix = Array(n) .fill(null) .map(() => Array(n).fill(null)); // 二维列表占用 O(n^2) 空间 const numList = []; for (let i = 0; i < n; i++) { const tmp = []; for (let j = 0; j < n; j++) { tmp.push(0); } numList.push(tmp); } } /* 平方阶(递归实现) */ function quadraticRecur(n: number): number { if (n <= 0) return 0; const nums = new Array(n); console.log(`递归 n = ${n} 中的 nums 长度 = ${nums.length}`); return quadraticRecur(n - 1); } /* 指数阶(建立满二叉树) */ function buildTree(n: number): TreeNode | null { if (n === 0) return null; const root = new TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ const n = 5; // 常数阶 constant(n); // 线性阶 linear(n); linearRecur(n); // 平方阶 quadratic(n); quadraticRecur(n); // 指数阶 const root = buildTree(n); printTree(root); ================================================ FILE: codes/typescript/chapter_computational_complexity/time_complexity.ts ================================================ /** * File: time_complexity.ts * Created Time: 2023-01-02 * Author: RiverTwilight (contact@rene.wang) */ /* 常数阶 */ function constant(n: number): number { let count = 0; const size = 100000; for (let i = 0; i < size; i++) count++; return count; } /* 线性阶 */ function linear(n: number): number { let count = 0; for (let i = 0; i < n; i++) count++; return count; } /* 线性阶(遍历数组) */ function arrayTraversal(nums: number[]): number { let count = 0; // 循环次数与数组长度成正比 for (let i = 0; i < nums.length; i++) { count++; } return count; } /* 平方阶 */ function quadratic(n: number): number { let count = 0; // 循环次数与数据大小 n 成平方关系 for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { count++; } } return count; } /* 平方阶(冒泡排序) */ function bubbleSort(nums: number[]): number { let count = 0; // 计数器 // 外循环:未排序区间为 [0, i] for (let i = nums.length - 1; i > 0; i--) { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 元素交换包含 3 个单元操作 } } } return count; } /* 指数阶(循环实现) */ function exponential(n: number): number { let count = 0, base = 1; // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for (let i = 0; i < n; i++) { for (let j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指数阶(递归实现) */ function expRecur(n: number): number { if (n === 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 对数阶(循环实现) */ function logarithmic(n: number): number { let count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* 对数阶(递归实现) */ function logRecur(n: number): number { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* 线性对数阶 */ function linearLogRecur(n: number): number { if (n <= 1) return 1; let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (let i = 0; i < n; i++) { count++; } return count; } /* 阶乘阶(递归实现) */ function factorialRecur(n: number): number { if (n === 0) return 1; let count = 0; // 从 1 个分裂出 n 个 for (let i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 const n = 8; console.log('输入数据大小 n = ' + n); let count = constant(n); console.log('常数阶的操作数量 = ' + count); count = linear(n); console.log('线性阶的操作数量 = ' + count); count = arrayTraversal(new Array(n)); console.log('线性阶(遍历数组)的操作数量 = ' + count); count = quadratic(n); console.log('平方阶的操作数量 = ' + count); var nums = new Array(n); for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); console.log('平方阶(冒泡排序)的操作数量 = ' + count); count = exponential(n); console.log('指数阶(循环实现)的操作数量 = ' + count); count = expRecur(n); console.log('指数阶(递归实现)的操作数量 = ' + count); count = logarithmic(n); console.log('对数阶(循环实现)的操作数量 = ' + count); count = logRecur(n); console.log('对数阶(递归实现)的操作数量 = ' + count); count = linearLogRecur(n); console.log('线性对数阶(递归实现)的操作数量 = ' + count); count = factorialRecur(n); console.log('阶乘阶(递归实现)的操作数量 = ' + count); export {}; ================================================ FILE: codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts ================================================ /** * File: worst_best_time_complexity.ts * Created Time: 2023-01-05 * Author: RiverTwilight (contact@rene.wang) */ /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ function randomNumbers(n: number): number[] { const nums = Array(n); // 生成数组 nums = { 1, 2, 3, ..., n } for (let i = 0; i < n; i++) { nums[i] = i + 1; } // 随机打乱数组元素 for (let i = 0; i < n; i++) { const r = Math.floor(Math.random() * (i + 1)); const temp = nums[i]; nums[i] = nums[r]; nums[r] = temp; } return nums; } /* 查找数组 nums 中数字 1 所在索引 */ function findOne(nums: number[]): number { for (let i = 0; i < nums.length; i++) { // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if (nums[i] === 1) { return i; } } return -1; } /* Driver Code */ for (let i = 0; i < 10; i++) { const n = 100; const nums = randomNumbers(n); const index = findOne(nums); console.log('\n数组 [ 1, 2, ..., n ] 被打乱后 = [' + nums.join(', ') + ']'); console.log('数字 1 的索引为 ' + index); } export {}; ================================================ FILE: codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts ================================================ /** * File: binary_search_recur.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 二分查找:问题 f(i, j) */ function dfs(nums: number[], target: number, i: number, j: number): number { // 若区间为空,代表无目标元素,则返回 -1 if (i > j) { return -1; } // 计算中点索引 m const m = i + ((j - i) >> 1); if (nums[m] < target) { // 递归子问题 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 递归子问题 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目标元素,返回其索引 return m; } } /* 二分查找 */ function binarySearch(nums: number[], target: number): number { const n = nums.length; // 求解问题 f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分查找(双闭区间) const index = binarySearch(nums, target); console.log(`目标元素 6 的索引 = ${index}`); export {}; ================================================ FILE: codes/typescript/chapter_divide_and_conquer/build_tree.ts ================================================ /** * File: build_tree.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ import { printTree } from '../modules/PrintUtil'; import { TreeNode } from '../modules/TreeNode'; /* 构建二叉树:分治 */ function dfs( preorder: number[], inorderMap: Map, i: number, l: number, r: number ): TreeNode | null { // 子树区间为空时终止 if (r - l < 0) return null; // 初始化根节点 const root: TreeNode = new TreeNode(preorder[i]); // 查询 m ,从而划分左右子树 const m = inorderMap.get(preorder[i]); // 子问题:构建左子树 root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // 子问题:构建右子树 root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // 返回根节点 return root; } /* 构建二叉树 */ function buildTree(preorder: number[], inorder: number[]): TreeNode | null { // 初始化哈希表,存储 inorder 元素到索引的映射 let inorderMap = new Map(); for (let i = 0; i < inorder.length; i++) { inorderMap.set(inorder[i], i); } const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ const preorder = [3, 9, 2, 1, 7]; const inorder = [9, 3, 1, 2, 7]; console.log('前序遍历 = ' + JSON.stringify(preorder)); console.log('中序遍历 = ' + JSON.stringify(inorder)); const root = buildTree(preorder, inorder); console.log('构建的二叉树为:'); printTree(root); ================================================ FILE: codes/typescript/chapter_divide_and_conquer/hanota.ts ================================================ /** * File: hanota.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 移动一个圆盘 */ function move(src: number[], tar: number[]): void { // 从 src 顶部拿出一个圆盘 const pan = src.pop(); // 将圆盘放入 tar 顶部 tar.push(pan); } /* 求解汉诺塔问题 f(i) */ function dfs(i: number, src: number[], buf: number[], tar: number[]): void { // 若 src 只剩下一个圆盘,则直接将其移到 tar if (i === 1) { move(src, tar); return; } // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf dfs(i - 1, src, tar, buf); // 子问题 f(1) :将 src 剩余一个圆盘移到 tar move(src, tar); // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar dfs(i - 1, buf, src, tar); } /* 求解汉诺塔问题 */ function solveHanota(A: number[], B: number[], C: number[]): void { const n = A.length; // 将 A 顶部 n 个圆盘借助 B 移到 C dfs(n, A, B, C); } /* Driver Code */ // 列表尾部是柱子顶部 const A = [5, 4, 3, 2, 1]; const B = []; const C = []; console.log('初始状态下:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); solveHanota(A, B, C); console.log('圆盘移动完成后:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); ================================================ FILE: codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts ================================================ /** * File: climbing_stairs_backtrack.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯 */ function backtrack( choices: number[], state: number, n: number, res: Map<0, any> ): void { // 当爬到第 n 阶时,方案数量加 1 if (state === n) res.set(0, res.get(0) + 1); // 遍历所有选择 for (const choice of choices) { // 剪枝:不允许越过第 n 阶 if (state + choice > n) continue; // 尝试:做出选择,更新状态 backtrack(choices, state + choice, n, res); // 回退 } } /* 爬楼梯:回溯 */ function climbingStairsBacktrack(n: number): number { const choices = [1, 2]; // 可选择向上爬 1 阶或 2 阶 const state = 0; // 从第 0 阶开始爬 const res = new Map(); res.set(0, 0); // 使用 res[0] 记录方案数量 backtrack(choices, state, n, res); return res.get(0); } /* Driver Code */ const n = 9; const res = climbingStairsBacktrack(n); console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); export {}; ================================================ FILE: codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts ================================================ /** * File: climbing_stairs_constraint_dp.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 带约束爬楼梯:动态规划 */ function climbingStairsConstraintDP(n: number): number { if (n === 1 || n === 2) { return 1; } // 初始化 dp 表,用于存储子问题的解 const dp = Array.from({ length: n + 1 }, () => new Array(3)); // 初始状态:预设最小子问题的解 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 状态转移:从较小子问题逐步求解较大子问题 for (let i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ const n = 9; const res = climbingStairsConstraintDP(n); console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); export {}; ================================================ FILE: codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts ================================================ /** * File: climbing_stairs_dfs.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 搜索 */ function dfs(i: number): number { // 已知 dp[1] 和 dp[2] ,返回之 if (i === 1 || i === 2) return i; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1) + dfs(i - 2); return count; } /* 爬楼梯:搜索 */ function climbingStairsDFS(n: number): number { return dfs(n); } /* Driver Code */ const n = 9; const res = climbingStairsDFS(n); console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); export {}; ================================================ FILE: codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts ================================================ /** * File: climbing_stairs_dfs_mem.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 记忆化搜索 */ function dfs(i: number, mem: number[]): number { // 已知 dp[1] 和 dp[2] ,返回之 if (i === 1 || i === 2) return i; // 若存在记录 dp[i] ,则直接返回之 if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1, mem) + dfs(i - 2, mem); // 记录 dp[i] mem[i] = count; return count; } /* 爬楼梯:记忆化搜索 */ function climbingStairsDFSMem(n: number): number { // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 const mem = new Array(n + 1).fill(-1); return dfs(n, mem); } /* Driver Code */ const n = 9; const res = climbingStairsDFSMem(n); console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); export {}; ================================================ FILE: codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts ================================================ /** * File: climbing_stairs_dp.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 爬楼梯:动态规划 */ function climbingStairsDP(n: number): number { if (n === 1 || n === 2) return n; // 初始化 dp 表,用于存储子问题的解 const dp = new Array(n + 1).fill(-1); // 初始状态:预设最小子问题的解 dp[1] = 1; dp[2] = 2; // 状态转移:从较小子问题逐步求解较大子问题 for (let i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* 爬楼梯:空间优化后的动态规划 */ function climbingStairsDPComp(n: number): number { if (n === 1 || n === 2) return n; let a = 1, b = 2; for (let i = 3; i <= n; i++) { const tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ const n = 9; let res = climbingStairsDP(n); console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); res = climbingStairsDPComp(n); console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); export {}; ================================================ FILE: codes/typescript/chapter_dynamic_programming/coin_change.ts ================================================ /** * File: coin_change.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 零钱兑换:动态规划 */ function coinChangeDP(coins: Array, amt: number): number { const n = coins.length; const MAX = amt + 1; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 状态转移:首行首列 for (let a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状态转移:其余行和列 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] !== MAX ? dp[n][amt] : -1; } /* 零钱兑换:空间优化后的动态规划 */ function coinChangeDPComp(coins: Array, amt: number): number { const n = coins.length; const MAX = amt + 1; // 初始化 dp 表 const dp = Array.from({ length: amt + 1 }, () => MAX); dp[0] = 0; // 状态转移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] !== MAX ? dp[amt] : -1; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 4; // 动态规划 let res = coinChangeDP(coins, amt); console.log(`凑到目标金额所需的最少硬币数量为 ${res}`); // 空间优化后的动态规划 res = coinChangeDPComp(coins, amt); console.log(`凑到目标金额所需的最少硬币数量为 ${res}`); export {}; ================================================ FILE: codes/typescript/chapter_dynamic_programming/coin_change_ii.ts ================================================ /** * File: coin_change_ii.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 零钱兑换 II:动态规划 */ function coinChangeIIDP(coins: Array, amt: number): number { const n = coins.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 初始化首列 for (let i = 0; i <= n; i++) { dp[i][0] = 1; } // 状态转移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* 零钱兑换 II:空间优化后的动态规划 */ function coinChangeIIDPComp(coins: Array, amt: number): number { const n = coins.length; // 初始化 dp 表 const dp = Array.from({ length: amt + 1 }, () => 0); dp[0] = 1; // 状态转移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 5; // 动态规划 let res = coinChangeIIDP(coins, amt); console.log(`凑出目标金额的硬币组合数量为 ${res}`); // 空间优化后的动态规划 res = coinChangeIIDPComp(coins, amt); console.log(`凑出目标金额的硬币组合数量为 ${res}`); export {}; ================================================ FILE: codes/typescript/chapter_dynamic_programming/edit_distance.ts ================================================ /** * File: edit_distance.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 编辑距离:暴力搜索 */ function editDistanceDFS(s: string, t: string, i: number, j: number): number { // 若 s 和 t 都为空,则返回 0 if (i === 0 && j === 0) return 0; // 若 s 为空,则返回 t 长度 if (i === 0) return j; // 若 t 为空,则返回 s 长度 if (j === 0) return i; // 若两字符相等,则直接跳过此两字符 if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 const insert = editDistanceDFS(s, t, i, j - 1); const del = editDistanceDFS(s, t, i - 1, j); const replace = editDistanceDFS(s, t, i - 1, j - 1); // 返回最少编辑步数 return Math.min(insert, del, replace) + 1; } /* 编辑距离:记忆化搜索 */ function editDistanceDFSMem( s: string, t: string, mem: Array>, i: number, j: number ): number { // 若 s 和 t 都为空,则返回 0 if (i === 0 && j === 0) return 0; // 若 s 为空,则返回 t 长度 if (i === 0) return j; // 若 t 为空,则返回 s 长度 if (j === 0) return i; // 若已有记录,则直接返回之 if (mem[i][j] !== -1) return mem[i][j]; // 若两字符相等,则直接跳过此两字符 if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 const insert = editDistanceDFSMem(s, t, mem, i, j - 1); const del = editDistanceDFSMem(s, t, mem, i - 1, j); const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 记录并返回最少编辑步数 mem[i][j] = Math.min(insert, del, replace) + 1; return mem[i][j]; } /* 编辑距离:动态规划 */ function editDistanceDP(s: string, t: string): number { const n = s.length, m = t.length; const dp = Array.from({ length: n + 1 }, () => Array.from({ length: m + 1 }, () => 0) ); // 状态转移:首行首列 for (let i = 1; i <= n; i++) { dp[i][0] = i; } for (let j = 1; j <= m; j++) { dp[0][j] = j; } // 状态转移:其余行和列 for (let i = 1; i <= n; i++) { for (let j = 1; j <= m; j++) { if (s.charAt(i - 1) === t.charAt(j - 1)) { // 若两字符相等,则直接跳过此两字符 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* 编辑距离:空间优化后的动态规划 */ function editDistanceDPComp(s: string, t: string): number { const n = s.length, m = t.length; const dp = new Array(m + 1).fill(0); // 状态转移:首行 for (let j = 1; j <= m; j++) { dp[j] = j; } // 状态转移:其余行 for (let i = 1; i <= n; i++) { // 状态转移:首列 let leftup = dp[0]; // 暂存 dp[i-1, j-1] dp[0] = i; // 状态转移:其余列 for (let j = 1; j <= m; j++) { const temp = dp[j]; if (s.charAt(i - 1) === t.charAt(j - 1)) { // 若两字符相等,则直接跳过此两字符 dp[j] = leftup; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; } leftup = temp; // 更新为下一轮的 dp[i-1, j-1] } } return dp[m]; } /* Driver Code */ const s = 'bag'; const t = 'pack'; const n = s.length, m = t.length; // 暴力搜索 let res = editDistanceDFS(s, t, n, m); console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); // 记忆化搜索 const mem = Array.from({ length: n + 1 }, () => Array.from({ length: m + 1 }, () => -1) ); res = editDistanceDFSMem(s, t, mem, n, m); console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); // 动态规划 res = editDistanceDP(s, t); console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); // 空间优化后的动态规划 res = editDistanceDPComp(s, t); console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); export {}; ================================================ FILE: codes/typescript/chapter_dynamic_programming/knapsack.ts ================================================ /** * File: knapsack.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 0-1 背包:暴力搜索 */ function knapsackDFS( wgt: Array, val: Array, i: number, c: number ): number { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i === 0 || c === 0) { return 0; } // 若超过背包容量,则只能选择不放入背包 if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 const no = knapsackDFS(wgt, val, i - 1, c); const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // 返回两种方案中价值更大的那一个 return Math.max(no, yes); } /* 0-1 背包:记忆化搜索 */ function knapsackDFSMem( wgt: Array, val: Array, mem: Array>, i: number, c: number ): number { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i === 0 || c === 0) { return 0; } // 若已有记录,则直接返回 if (mem[i][c] !== -1) { return mem[i][c]; } // 若超过背包容量,则只能选择不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 const no = knapsackDFSMem(wgt, val, mem, i - 1, c); const yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 记录并返回两种方案中价值更大的那一个 mem[i][c] = Math.max(no, yes); return mem[i][c]; } /* 0-1 背包:动态规划 */ function knapsackDP( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // 状态转移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = Math.max( dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* 0-1 背包:空间优化后的动态规划 */ function knapsackDPComp( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // 初始化 dp 表 const dp = Array(cap + 1).fill(0); // 状态转移 for (let i = 1; i <= n; i++) { // 倒序遍历 for (let c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // 不选和选物品 i 这两种方案的较大值 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // 暴力搜索 let res = knapsackDFS(wgt, val, n, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); // 记忆化搜索 const mem = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => -1) ); res = knapsackDFSMem(wgt, val, mem, n, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); // 动态规划 res = knapsackDP(wgt, val, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); // 空间优化后的动态规划 res = knapsackDPComp(wgt, val, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); export {}; ================================================ FILE: codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts ================================================ /** * File: min_cost_climbing_stairs_dp.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 爬楼梯最小代价:动态规划 */ function minCostClimbingStairsDP(cost: Array): number { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } // 初始化 dp 表,用于存储子问题的解 const dp = new Array(n + 1); // 初始状态:预设最小子问题的解 dp[1] = cost[1]; dp[2] = cost[2]; // 状态转移:从较小子问题逐步求解较大子问题 for (let i = 3; i <= n; i++) { dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* 爬楼梯最小代价:空间优化后的动态规划 */ function minCostClimbingStairsDPComp(cost: Array): number { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } let a = cost[1], b = cost[2]; for (let i = 3; i <= n; i++) { const tmp = b; b = Math.min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; console.log(`输入楼梯的代价列表为:${cost}`); let res = minCostClimbingStairsDP(cost); console.log(`爬完楼梯的最低代价为:${res}`); res = minCostClimbingStairsDPComp(cost); console.log(`爬完楼梯的最低代价为:${res}`); export {}; ================================================ FILE: codes/typescript/chapter_dynamic_programming/min_path_sum.ts ================================================ /** * File: min_path_sum.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 最小路径和:暴力搜索 */ function minPathSumDFS( grid: Array>, i: number, j: number ): number { // 若为左上角单元格,则终止搜索 if (i === 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 || j < 0) { return Infinity; } // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 const up = minPathSumDFS(grid, i - 1, j); const left = minPathSumDFS(grid, i, j - 1); // 返回从左上角到 (i, j) 的最小路径代价 return Math.min(left, up) + grid[i][j]; } /* 最小路径和:记忆化搜索 */ function minPathSumDFSMem( grid: Array>, mem: Array>, i: number, j: number ): number { // 若为左上角单元格,则终止搜索 if (i === 0 && j === 0) { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 || j < 0) { return Infinity; } // 若已有记录,则直接返回 if (mem[i][j] != -1) { return mem[i][j]; } // 左边和上边单元格的最小路径代价 const up = minPathSumDFSMem(grid, mem, i - 1, j); const left = minPathSumDFSMem(grid, mem, i, j - 1); // 记录并返回左上角到 (i, j) 的最小路径代价 mem[i][j] = Math.min(left, up) + grid[i][j]; return mem[i][j]; } /* 最小路径和:动态规划 */ function minPathSumDP(grid: Array>): number { const n = grid.length, m = grid[0].length; // 初始化 dp 表 const dp = Array.from({ length: n }, () => Array.from({ length: m }, () => 0) ); dp[0][0] = grid[0][0]; // 状态转移:首行 for (let j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 状态转移:首列 for (let i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 状态转移:其余行和列 for (let i = 1; i < n; i++) { for (let j: number = 1; j < m; j++) { dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* 最小路径和:空间优化后的动态规划 */ function minPathSumDPComp(grid: Array>): number { const n = grid.length, m = grid[0].length; // 初始化 dp 表 const dp = new Array(m); // 状态转移:首行 dp[0] = grid[0][0]; for (let j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 状态转移:其余行 for (let i = 1; i < n; i++) { // 状态转移:首列 dp[0] = dp[0] + grid[i][0]; // 状态转移:其余列 for (let j = 1; j < m; j++) { dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ const grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; const n = grid.length, m = grid[0].length; // 暴力搜索 let res = minPathSumDFS(grid, n - 1, m - 1); console.log(`从左上角到右下角的最小路径和为 ${res}`); // 记忆化搜索 const mem = Array.from({ length: n }, () => Array.from({ length: m }, () => -1) ); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); console.log(`从左上角到右下角的最小路径和为 ${res}`); // 动态规划 res = minPathSumDP(grid); console.log(`从左上角到右下角的最小路径和为 ${res}`); // 空间优化后的动态规划 res = minPathSumDPComp(grid); console.log(`从左上角到右下角的最小路径和为 ${res}`); export {}; ================================================ FILE: codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts ================================================ /** * File: unbounded_knapsack.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 完全背包:动态规划 */ function unboundedKnapsackDP( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // 状态转移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = Math.max( dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* 完全背包:空间优化后的动态规划 */ function unboundedKnapsackDPComp( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: cap + 1 }, () => 0); // 状态转移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [1, 2, 3]; const val = [5, 11, 15]; const cap = 4; // 动态规划 let res = unboundedKnapsackDP(wgt, val, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); // 空间优化后的动态规划 res = unboundedKnapsackDPComp(wgt, val, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); export {}; ================================================ FILE: codes/typescript/chapter_graph/graph_adjacency_list.ts ================================================ /** * File: graph_adjacency_list.ts * Created Time: 2023-02-09 * Author: Justin (xiefahit@gmail.com) */ import { Vertex } from '../modules/Vertex'; /* 基于邻接表实现的无向图类 */ class GraphAdjList { // 邻接表,key:顶点,value:该顶点的所有邻接顶点 adjList: Map; /* 构造方法 */ constructor(edges: Vertex[][]) { this.adjList = new Map(); // 添加所有顶点和边 for (const edge of edges) { this.addVertex(edge[0]); this.addVertex(edge[1]); this.addEdge(edge[0], edge[1]); } } /* 获取顶点数量 */ size(): number { return this.adjList.size; } /* 添加边 */ addEdge(vet1: Vertex, vet2: Vertex): void { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 ) { throw new Error('Illegal Argument Exception'); } // 添加边 vet1 - vet2 this.adjList.get(vet1).push(vet2); this.adjList.get(vet2).push(vet1); } /* 删除边 */ removeEdge(vet1: Vertex, vet2: Vertex): void { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 || this.adjList.get(vet1).indexOf(vet2) === -1 ) { throw new Error('Illegal Argument Exception'); } // 删除边 vet1 - vet2 this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); } /* 添加顶点 */ addVertex(vet: Vertex): void { if (this.adjList.has(vet)) return; // 在邻接表中添加一个新链表 this.adjList.set(vet, []); } /* 删除顶点 */ removeVertex(vet: Vertex): void { if (!this.adjList.has(vet)) { throw new Error('Illegal Argument Exception'); } // 在邻接表中删除顶点 vet 对应的链表 this.adjList.delete(vet); // 遍历其他顶点的链表,删除所有包含 vet 的边 for (const set of this.adjList.values()) { const index: number = set.indexOf(vet); if (index > -1) { set.splice(index, 1); } } } /* 打印邻接表 */ print(): void { console.log('邻接表 ='); for (const [key, value] of this.adjList.entries()) { const tmp = []; for (const vertex of value) { tmp.push(vertex.val); } console.log(key.val + ': ' + tmp.join()); } } } /* Driver Code */ if (import.meta.url.endsWith(process.argv[1])) { /* 初始化无向图 */ const v0 = new Vertex(1), v1 = new Vertex(3), v2 = new Vertex(2), v3 = new Vertex(5), v4 = new Vertex(4); const edges = [ [v0, v1], [v1, v2], [v2, v3], [v0, v3], [v2, v4], [v3, v4], ]; const graph = new GraphAdjList(edges); console.log('\n初始化后,图为'); graph.print(); /* 添加边 */ // 顶点 1, 2 即 v0, v2 graph.addEdge(v0, v2); console.log('\n添加边 1-2 后,图为'); graph.print(); /* 删除边 */ // 顶点 1, 3 即 v0, v1 graph.removeEdge(v0, v1); console.log('\n删除边 1-3 后,图为'); graph.print(); /* 添加顶点 */ const v5 = new Vertex(6); graph.addVertex(v5); console.log('\n添加顶点 6 后,图为'); graph.print(); /* 删除顶点 */ // 顶点 3 即 v1 graph.removeVertex(v1); console.log('\n删除顶点 3 后,图为'); graph.print(); } export { GraphAdjList }; ================================================ FILE: codes/typescript/chapter_graph/graph_adjacency_matrix.ts ================================================ /** * File: graph_adjacency_matrix.ts * Created Time: 2023-02-09 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 基于邻接矩阵实现的无向图类 */ class GraphAdjMat { vertices: number[]; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” adjMat: number[][]; // 邻接矩阵,行列索引对应“顶点索引” /* 构造函数 */ constructor(vertices: number[], edges: number[][]) { this.vertices = []; this.adjMat = []; // 添加顶点 for (const val of vertices) { this.addVertex(val); } // 添加边 // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 for (const e of edges) { this.addEdge(e[0], e[1]); } } /* 获取顶点数量 */ size(): number { return this.vertices.length; } /* 添加顶点 */ addVertex(val: number): void { const n: number = this.size(); // 向顶点列表中添加新顶点的值 this.vertices.push(val); // 在邻接矩阵中添加一行 const newRow: number[] = []; for (let j: number = 0; j < n; j++) { newRow.push(0); } this.adjMat.push(newRow); // 在邻接矩阵中添加一列 for (const row of this.adjMat) { row.push(0); } } /* 删除顶点 */ removeVertex(index: number): void { if (index >= this.size()) { throw new RangeError('Index Out Of Bounds Exception'); } // 在顶点列表中移除索引 index 的顶点 this.vertices.splice(index, 1); // 在邻接矩阵中删除索引 index 的行 this.adjMat.splice(index, 1); // 在邻接矩阵中删除索引 index 的列 for (const row of this.adjMat) { row.splice(index, 1); } } /* 添加边 */ // 参数 i, j 对应 vertices 元素索引 addEdge(i: number, j: number): void { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) === (j, i) this.adjMat[i][j] = 1; this.adjMat[j][i] = 1; } /* 删除边 */ // 参数 i, j 对应 vertices 元素索引 removeEdge(i: number, j: number): void { // 索引越界与相等处理 if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } this.adjMat[i][j] = 0; this.adjMat[j][i] = 0; } /* 打印邻接矩阵 */ print(): void { console.log('顶点列表 = ', this.vertices); console.log('邻接矩阵 =', this.adjMat); } } /* Driver Code */ /* 初始化无向图 */ // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 const vertices: number[] = [1, 3, 2, 5, 4]; const edges: number[][] = [ [0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4], ]; const graph: GraphAdjMat = new GraphAdjMat(vertices, edges); console.log('\n初始化后,图为'); graph.print(); /* 添加边 */ // 顶点 1, 2 的索引分别为 0, 2 graph.addEdge(0, 2); console.log('\n添加边 1-2 后,图为'); graph.print(); /* 删除边 */ // 顶点 1, 3 的索引分别为 0, 1 graph.removeEdge(0, 1); console.log('\n删除边 1-3 后,图为'); graph.print(); /* 添加顶点 */ graph.addVertex(6); console.log('\n添加顶点 6 后,图为'); graph.print(); /* 删除顶点 */ // 顶点 3 的索引为 1 graph.removeVertex(1); console.log('\n删除顶点 3 后,图为'); graph.print(); export {}; ================================================ FILE: codes/typescript/chapter_graph/graph_bfs.ts ================================================ /** * File: graph_bfs.ts * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { GraphAdjList } from './graph_adjacency_list'; import { Vertex } from '../modules/Vertex'; /* 广度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { // 顶点遍历序列 const res: Vertex[] = []; // 哈希集合,用于记录已被访问过的顶点 const visited: Set = new Set(); visited.add(startVet); // 队列用于实现 BFS const que = [startVet]; // 以顶点 vet 为起点,循环直至访问完所有顶点 while (que.length) { const vet = que.shift(); // 队首顶点出队 res.push(vet); // 记录访问顶点 // 遍历该顶点的所有邻接顶点 for (const adjVet of graph.adjList.get(vet) ?? []) { if (visited.has(adjVet)) { continue; // 跳过已被访问的顶点 } que.push(adjVet); // 只入队未访问 visited.add(adjVet); // 标记该顶点已被访问 } } // 返回顶点遍历序列 return res; } /* Driver Code */ /* 初始化无向图 */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; const graph = new GraphAdjList(edges); console.log('\n初始化后,图为'); graph.print(); /* 广度优先遍历 */ const res = graphBFS(graph, v[0]); console.log('\n广度优先遍历(BFS)顶点序列为'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: codes/typescript/chapter_graph/graph_dfs.ts ================================================ /** * File: graph_dfs.ts * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { Vertex } from '../modules/Vertex'; import { GraphAdjList } from './graph_adjacency_list'; /* 深度优先遍历辅助函数 */ function dfs( graph: GraphAdjList, visited: Set, res: Vertex[], vet: Vertex ): void { res.push(vet); // 记录访问顶点 visited.add(vet); // 标记该顶点已被访问 // 遍历该顶点的所有邻接顶点 for (const adjVet of graph.adjList.get(vet)) { if (visited.has(adjVet)) { continue; // 跳过已被访问的顶点 } // 递归访问邻接顶点 dfs(graph, visited, res, adjVet); } } /* 深度优先遍历 */ // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { // 顶点遍历序列 const res: Vertex[] = []; // 哈希集合,用于记录已被访问过的顶点 const visited: Set = new Set(); dfs(graph, visited, res, startVet); return res; } /* Driver Code */ /* 初始化无向图 */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; const graph = new GraphAdjList(edges); console.log('\n初始化后,图为'); graph.print(); /* 深度优先遍历 */ const res = graphDFS(graph, v[0]); console.log('\n深度优先遍历(DFS)顶点序列为'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: codes/typescript/chapter_greedy/coin_change_greedy.ts ================================================ /** * File: coin_change_greedy.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 零钱兑换:贪心 */ function coinChangeGreedy(coins: number[], amt: number): number { // 假设 coins 数组有序 let i = coins.length - 1; let count = 0; // 循环进行贪心选择,直到无剩余金额 while (amt > 0) { // 找到小于且最接近剩余金额的硬币 while (i > 0 && coins[i] > amt) { i--; } // 选择 coins[i] amt -= coins[i]; count++; } // 若未找到可行方案,则返回 -1 return amt === 0 ? count : -1; } /* Driver Code */ // 贪心:能够保证找到全局最优解 let coins: number[] = [1, 5, 10, 20, 50, 100]; let amt: number = 186; let res: number = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`凑到 ${amt} 所需的最少硬币数量为 ${res}`); // 贪心:无法保证找到全局最优解 coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`凑到 ${amt} 所需的最少硬币数量为 ${res}`); console.log('实际上需要的最少数量为 3 ,即 20 + 20 + 20'); // 贪心:无法保证找到全局最优解 coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`凑到 ${amt} 所需的最少硬币数量为 ${res}`); console.log('实际上需要的最少数量为 2 ,即 49 + 49'); export {}; ================================================ FILE: codes/typescript/chapter_greedy/fractional_knapsack.ts ================================================ /** * File: fractional_knapsack.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 物品 */ class Item { w: number; // 物品重量 v: number; // 物品价值 constructor(w: number, v: number) { this.w = w; this.v = v; } } /* 分数背包:贪心 */ function fractionalKnapsack(wgt: number[], val: number[], cap: number): number { // 创建物品列表,包含两个属性:重量、价值 const items: Item[] = wgt.map((w, i) => new Item(w, val[i])); // 按照单位价值 item.v / item.w 从高到低进行排序 items.sort((a, b) => b.v / b.w - a.v / a.w); // 循环贪心选择 let res = 0; for (const item of items) { if (item.w <= cap) { // 若剩余容量充足,则将当前物品整个装进背包 res += item.v; cap -= item.w; } else { // 若剩余容量不足,则将当前物品的一部分装进背包 res += (item.v / item.w) * cap; // 已无剩余容量,因此跳出循环 break; } } return res; } /* Driver Code */ const wgt: number[] = [10, 20, 30, 40, 50]; const val: number[] = [50, 120, 150, 210, 240]; const cap: number = 50; // 贪心算法 const res: number = fractionalKnapsack(wgt, val, cap); console.log(`不超过背包容量的最大物品价值为 ${res}`); export {}; ================================================ FILE: codes/typescript/chapter_greedy/max_capacity.ts ================================================ /** * File: max_capacity.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 最大容量:贪心 */ function maxCapacity(ht: number[]): number { // 初始化 i, j,使其分列数组两端 let i = 0, j = ht.length - 1; // 初始最大容量为 0 let res = 0; // 循环贪心选择,直至两板相遇 while (i < j) { // 更新最大容量 const cap: number = Math.min(ht[i], ht[j]) * (j - i); res = Math.max(res, cap); // 向内移动短板 if (ht[i] < ht[j]) { i += 1; } else { j -= 1; } } return res; } /* Driver Code */ const ht: number[] = [3, 8, 5, 2, 7, 7, 3, 4]; // 贪心算法 const res: number = maxCapacity(ht); console.log(`最大容量为 ${res}`); export {}; ================================================ FILE: codes/typescript/chapter_greedy/max_product_cutting.ts ================================================ /** * File: max_product_cutting.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 最大切分乘积:贪心 */ function maxProductCutting(n: number): number { // 当 n <= 3 时,必须切分出一个 1 if (n <= 3) { return 1 * (n - 1); } // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 let a: number = Math.floor(n / 3); let b: number = n % 3; if (b === 1) { // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 return Math.pow(3, a - 1) * 2 * 2; } if (b === 2) { // 当余数为 2 时,不做处理 return Math.pow(3, a) * 2; } // 当余数为 0 时,不做处理 return Math.pow(3, a); } /* Driver Code */ let n: number = 58; // 贪心算法 let res: number = maxProductCutting(n); console.log(`最大切分乘积为 ${res}`); export {}; ================================================ FILE: codes/typescript/chapter_hashing/array_hash_map.ts ================================================ /** * File: array_hash_map.ts * Created Time: 2022-12-20 * Author: Daniel (better.sunjian@gmail.com) */ /* 键值对 Number -> String */ class Pair { public key: number; public val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* 基于数组实现的哈希表 */ class ArrayHashMap { private readonly buckets: (Pair | null)[]; constructor() { // 初始化数组,包含 100 个桶 this.buckets = new Array(100).fill(null); } /* 哈希函数 */ private hashFunc(key: number): number { return key % 100; } /* 查询操作 */ public get(key: number): string | null { let index = this.hashFunc(key); let pair = this.buckets[index]; if (pair === null) return null; return pair.val; } /* 添加操作 */ public set(key: number, val: string) { let index = this.hashFunc(key); this.buckets[index] = new Pair(key, val); } /* 删除操作 */ public delete(key: number) { let index = this.hashFunc(key); // 置为 null ,代表删除 this.buckets[index] = null; } /* 获取所有键值对 */ public entries(): (Pair | null)[] { let arr: (Pair | null)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i]); } } return arr; } /* 获取所有键 */ public keys(): (number | undefined)[] { let arr: (number | undefined)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i].key); } } return arr; } /* 获取所有值 */ public values(): (string | undefined)[] { let arr: (string | undefined)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i].val); } } return arr; } /* 打印哈希表 */ public print() { let pairSet = this.entries(); for (const pair of pairSet) { console.info(`${pair.key} -> ${pair.val}`); } } } /* Driver Code */ /* 初始化哈希表 */ const map = new ArrayHashMap(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.set(12836, '小哈'); map.set(15937, '小啰'); map.set(16750, '小算'); map.set(13276, '小法'); map.set(10583, '小鸭'); console.info('\n添加完成后,哈希表为\nKey -> Value'); map.print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value let name = map.get(15937); console.info('\n输入学号 15937 ,查询到姓名 ' + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.delete(10583); console.info('\n删除 10583 后,哈希表为\nKey -> Value'); map.print(); /* 遍历哈希表 */ console.info('\n遍历键值对 Key->Value'); for (const pair of map.entries()) { if (!pair) continue; console.info(pair.key + ' -> ' + pair.val); } console.info('\n单独遍历键 Key'); for (const key of map.keys()) { console.info(key); } console.info('\n单独遍历值 Value'); for (const val of map.values()) { console.info(val); } export {}; ================================================ FILE: codes/typescript/chapter_hashing/hash_map.ts ================================================ /** * File: hash_map.ts * Created Time: 2022-12-20 * Author: Daniel (better.sunjian@gmail.com) */ /* Driver Code */ /* 初始化哈希表 */ const map = new Map(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.set(12836, '小哈'); map.set(15937, '小啰'); map.set(16750, '小算'); map.set(13276, '小法'); map.set(10583, '小鸭'); console.info('\n添加完成后,哈希表为\nKey -> Value'); console.info(map); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value let name = map.get(15937); console.info('\n输入学号 15937 ,查询到姓名 ' + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.delete(10583); console.info('\n删除 10583 后,哈希表为\nKey -> Value'); console.info(map); /* 遍历哈希表 */ console.info('\n遍历键值对 Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\n单独遍历键 Key'); for (const k of map.keys()) { console.info(k); } console.info('\n单独遍历值 Value'); for (const v of map.values()) { console.info(v); } export {}; ================================================ FILE: codes/typescript/chapter_hashing/hash_map_chaining.ts ================================================ /** * File: hash_map_chaining.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 键值对 Number -> String */ class Pair { key: number; val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* 链式地址哈希表 */ class HashMapChaining { #size: number; // 键值对数量 #capacity: number; // 哈希表容量 #loadThres: number; // 触发扩容的负载因子阈值 #extendRatio: number; // 扩容倍数 #buckets: Pair[][]; // 桶数组 /* 构造方法 */ constructor() { this.#size = 0; this.#capacity = 4; this.#loadThres = 2.0 / 3.0; this.#extendRatio = 2; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); } /* 哈希函数 */ #hashFunc(key: number): number { return key % this.#capacity; } /* 负载因子 */ #loadFactor(): number { return this.#size / this.#capacity; } /* 查询操作 */ get(key: number): string | null { const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // 遍历桶,若找到 key ,则返回对应 val for (const pair of bucket) { if (pair.key === key) { return pair.val; } } // 若未找到 key ,则返回 null return null; } /* 添加操作 */ put(key: number, val: string): void { // 当负载因子超过阈值时,执行扩容 if (this.#loadFactor() > this.#loadThres) { this.#extend(); } const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 for (const pair of bucket) { if (pair.key === key) { pair.val = val; return; } } // 若无该 key ,则将键值对添加至尾部 const pair = new Pair(key, val); bucket.push(pair); this.#size++; } /* 删除操作 */ remove(key: number): void { const index = this.#hashFunc(key); let bucket = this.#buckets[index]; // 遍历桶,从中删除键值对 for (let i = 0; i < bucket.length; i++) { if (bucket[i].key === key) { bucket.splice(i, 1); this.#size--; break; } } } /* 扩容哈希表 */ #extend(): void { // 暂存原哈希表 const bucketsTmp = this.#buckets; // 初始化扩容后的新哈希表 this.#capacity *= this.#extendRatio; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); this.#size = 0; // 将键值对从原哈希表搬运至新哈希表 for (const bucket of bucketsTmp) { for (const pair of bucket) { this.put(pair.key, pair.val); } } } /* 打印哈希表 */ print(): void { for (const bucket of this.#buckets) { let res = []; for (const pair of bucket) { res.push(pair.key + ' -> ' + pair.val); } console.log(res); } } } /* Driver Code */ /* 初始化哈希表 */ const map = new HashMapChaining(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(12836, '小哈'); map.put(15937, '小啰'); map.put(16750, '小算'); map.put(13276, '小法'); map.put(10583, '小鸭'); console.log('\n添加完成后,哈希表为\nKey -> Value'); map.print(); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value const name = map.get(13276); console.log('\n输入学号 13276 ,查询到姓名 ' + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(12836); console.log('\n删除 12836 后,哈希表为\nKey -> Value'); map.print(); export {}; ================================================ FILE: codes/typescript/chapter_hashing/hash_map_open_addressing.ts ================================================ /** * File: hash_map_open_addressing.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) */ /* 键值对 Number -> String */ class Pair { key: number; val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* 开放寻址哈希表 */ class HashMapOpenAddressing { private size: number; // 键值对数量 private capacity: number; // 哈希表容量 private loadThres: number; // 触发扩容的负载因子阈值 private extendRatio: number; // 扩容倍数 private buckets: Array; // 桶数组 private TOMBSTONE: Pair; // 删除标记 /* 构造方法 */ constructor() { this.size = 0; // 键值对数量 this.capacity = 4; // 哈希表容量 this.loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值 this.extendRatio = 2; // 扩容倍数 this.buckets = Array(this.capacity).fill(null); // 桶数组 this.TOMBSTONE = new Pair(-1, '-1'); // 删除标记 } /* 哈希函数 */ private hashFunc(key: number): number { return key % this.capacity; } /* 负载因子 */ private loadFactor(): number { return this.size / this.capacity; } /* 搜索 key 对应的桶索引 */ private findBucket(key: number): number { let index = this.hashFunc(key); let firstTombstone = -1; // 线性探测,当遇到空桶时跳出 while (this.buckets[index] !== null) { // 若遇到 key ,返回对应的桶索引 if (this.buckets[index]!.key === key) { // 若之前遇到了删除标记,则将键值对移动至该索引处 if (firstTombstone !== -1) { this.buckets[firstTombstone] = this.buckets[index]; this.buckets[index] = this.TOMBSTONE; return firstTombstone; // 返回移动后的桶索引 } return index; // 返回桶索引 } // 记录遇到的首个删除标记 if ( firstTombstone === -1 && this.buckets[index] === this.TOMBSTONE ) { firstTombstone = index; } // 计算桶索引,越过尾部则返回头部 index = (index + 1) % this.capacity; } // 若 key 不存在,则返回添加点的索引 return firstTombstone === -1 ? index : firstTombstone; } /* 查询操作 */ get(key: number): string | null { // 搜索 key 对应的桶索引 const index = this.findBucket(key); // 若找到键值对,则返回对应 val if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { return this.buckets[index]!.val; } // 若键值对不存在,则返回 null return null; } /* 添加操作 */ put(key: number, val: string): void { // 当负载因子超过阈值时,执行扩容 if (this.loadFactor() > this.loadThres) { this.extend(); } // 搜索 key 对应的桶索引 const index = this.findBucket(key); // 若找到键值对,则覆盖 val 并返回 if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { this.buckets[index]!.val = val; return; } // 若键值对不存在,则添加该键值对 this.buckets[index] = new Pair(key, val); this.size++; } /* 删除操作 */ remove(key: number): void { // 搜索 key 对应的桶索引 const index = this.findBucket(key); // 若找到键值对,则用删除标记覆盖它 if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { this.buckets[index] = this.TOMBSTONE; this.size--; } } /* 扩容哈希表 */ private extend(): void { // 暂存原哈希表 const bucketsTmp = this.buckets; // 初始化扩容后的新哈希表 this.capacity *= this.extendRatio; this.buckets = Array(this.capacity).fill(null); this.size = 0; // 将键值对从原哈希表搬运至新哈希表 for (const pair of bucketsTmp) { if (pair !== null && pair !== this.TOMBSTONE) { this.put(pair.key, pair.val); } } } /* 打印哈希表 */ print(): void { for (const pair of this.buckets) { if (pair === null) { console.log('null'); } else if (pair === this.TOMBSTONE) { console.log('TOMBSTONE'); } else { console.log(pair.key + ' -> ' + pair.val); } } } } /* Driver Code */ // 初始化哈希表 const hashmap = new HashMapOpenAddressing(); // 添加操作 // 在哈希表中添加键值对 (key, val) hashmap.put(12836, '小哈'); hashmap.put(15937, '小啰'); hashmap.put(16750, '小算'); hashmap.put(13276, '小法'); hashmap.put(10583, '小鸭'); console.log('\n添加完成后,哈希表为\nKey -> Value'); hashmap.print(); // 查询操作 // 向哈希表中输入键 key ,得到值 val const name = hashmap.get(13276); console.log('\n输入学号 13276 ,查询到姓名 ' + name); // 删除操作 // 在哈希表中删除键值对 (key, val) hashmap.remove(16750); console.log('\n删除 16750 后,哈希表为\nKey -> Value'); hashmap.print(); export {}; ================================================ FILE: codes/typescript/chapter_hashing/simple_hash.ts ================================================ /** * File: simple_hash.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 加法哈希 */ function addHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* 乘法哈希 */ function mulHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (31 * hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* 异或哈希 */ function xorHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash ^= c.charCodeAt(0); } return hash % MODULUS; } /* 旋转哈希 */ function rotHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; } return hash; } /* Driver Code */ const key = 'Hello 算法'; let hash = addHash(key); console.log('加法哈希值为 ' + hash); hash = mulHash(key); console.log('乘法哈希值为 ' + hash); hash = xorHash(key); console.log('异或哈希值为 ' + hash); hash = rotHash(key); console.log('旋转哈希值为 ' + hash); ================================================ FILE: codes/typescript/chapter_heap/my_heap.ts ================================================ /** * File: my_heap.ts * Created Time: 2023-02-07 * Author: Justin (xiefahit@gmail.com) */ import { printHeap } from '../modules/PrintUtil'; /* 最大堆类 */ class MaxHeap { private maxHeap: number[]; /* 构造方法,建立空堆或根据输入列表建堆 */ constructor(nums?: number[]) { // 将列表元素原封不动添加进堆 this.maxHeap = nums === undefined ? [] : [...nums]; // 堆化除叶节点以外的其他所有节点 for (let i = this.parent(this.size() - 1); i >= 0; i--) { this.siftDown(i); } } /* 获取左子节点的索引 */ private left(i: number): number { return 2 * i + 1; } /* 获取右子节点的索引 */ private right(i: number): number { return 2 * i + 2; } /* 获取父节点的索引 */ private parent(i: number): number { return Math.floor((i - 1) / 2); // 向下整除 } /* 交换元素 */ private swap(i: number, j: number): void { const tmp = this.maxHeap[i]; this.maxHeap[i] = this.maxHeap[j]; this.maxHeap[j] = tmp; } /* 获取堆大小 */ public size(): number { return this.maxHeap.length; } /* 判断堆是否为空 */ public isEmpty(): boolean { return this.size() === 0; } /* 访问堆顶元素 */ public peek(): number { return this.maxHeap[0]; } /* 元素入堆 */ public push(val: number): void { // 添加节点 this.maxHeap.push(val); // 从底至顶堆化 this.siftUp(this.size() - 1); } /* 从节点 i 开始,从底至顶堆化 */ private siftUp(i: number): void { while (true) { // 获取节点 i 的父节点 const p = this.parent(i); // 当“越过根节点”或“节点无须修复”时,结束堆化 if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break; // 交换两节点 this.swap(i, p); // 循环向上堆化 i = p; } } /* 元素出堆 */ public pop(): number { // 判空处理 if (this.isEmpty()) throw new RangeError('Heap is empty.'); // 交换根节点与最右叶节点(交换首元素与尾元素) this.swap(0, this.size() - 1); // 删除节点 const val = this.maxHeap.pop(); // 从顶至底堆化 this.siftDown(0); // 返回堆顶元素 return val; } /* 从节点 i 开始,从顶至底堆化 */ private siftDown(i: number): void { while (true) { // 判断节点 i, l, r 中值最大的节点,记为 ma const l = this.left(i), r = this.right(i); let ma = i; if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l; if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r; // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if (ma === i) break; // 交换两节点 this.swap(i, ma); // 循环向下堆化 i = ma; } } /* 打印堆(二叉树) */ public print(): void { printHeap(this.maxHeap); } /* 取出堆中元素 */ public getMaxHeap(): number[] { return this.maxHeap; } } /* Driver Code */ if (import.meta.url.endsWith(process.argv[1])) { /* 初始化大顶堆 */ const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); console.log('\n输入列表并建堆后'); maxHeap.print(); /* 获取堆顶元素 */ let peek = maxHeap.peek(); console.log(`\n堆顶元素为 ${peek}`); /* 元素入堆 */ const val = 7; maxHeap.push(val); console.log(`\n元素 ${val} 入堆后`); maxHeap.print(); /* 堆顶元素出堆 */ peek = maxHeap.pop(); console.log(`\n堆顶元素 ${peek} 出堆后`); maxHeap.print(); /* 获取堆大小 */ const size = maxHeap.size(); console.log(`\n堆元素数量为 ${size}`); /* 判断堆是否为空 */ const isEmpty = maxHeap.isEmpty(); console.log(`\n堆是否为空 ${isEmpty}`); } export { MaxHeap }; ================================================ FILE: codes/typescript/chapter_heap/top_k.ts ================================================ /** * File: top_k.ts * Created Time: 2023-08-13 * Author: Justin (xiefahit@gmail.com) */ import { MaxHeap } from './my_heap'; /* 元素入堆 */ function pushMinHeap(maxHeap: MaxHeap, val: number): void { // 元素取反 maxHeap.push(-val); } /* 元素出堆 */ function popMinHeap(maxHeap: MaxHeap): number { // 元素取反 return -maxHeap.pop(); } /* 访问堆顶元素 */ function peekMinHeap(maxHeap: MaxHeap): number { // 元素取反 return -maxHeap.peek(); } /* 取出堆中元素 */ function getMinHeap(maxHeap: MaxHeap): number[] { // 元素取反 return maxHeap.getMaxHeap().map((num: number) => -num); } /* 基于堆查找数组中最大的 k 个元素 */ function topKHeap(nums: number[], k: number): number[] { // 初始化小顶堆 // 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆 const maxHeap = new MaxHeap([]); // 将数组的前 k 个元素入堆 for (let i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // 从第 k+1 个元素开始,保持堆的长度为 k for (let i = k; i < nums.length; i++) { // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } // 返回堆中元素 return getMinHeap(maxHeap); } /* Driver Code */ const nums = [1, 7, 6, 3, 2]; const k = 3; const res = topKHeap(nums, k); console.log(`最大的 ${k} 个元素为`, res); ================================================ FILE: codes/typescript/chapter_searching/binary_search.ts ================================================ /** * File: binary_search.ts * Created Time: 2022-12-27 * Author: Daniel (better.sunjian@gmail.com) */ /* 二分查找(双闭区间) */ function binarySearch(nums: number[], target: number): number { // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 let i = 0, j = nums.length - 1; // 循环,当搜索区间为空时跳出(当 i > j 时为空) while (i <= j) { // 计算中点索引 m const m = Math.floor(i + (j - i) / 2); if (nums[m] < target) { // 此情况说明 target 在区间 [m+1, j] 中 i = m + 1; } else if (nums[m] > target) { // 此情况说明 target 在区间 [i, m-1] 中 j = m - 1; } else { // 找到目标元素,返回其索引 return m; } } return -1; // 未找到目标元素,返回 -1 } /* 二分查找(左闭右开区间) */ function binarySearchLCRO(nums: number[], target: number): number { // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 let i = 0, j = nums.length; // 循环,当搜索区间为空时跳出(当 i = j 时为空) while (i < j) { // 计算中点索引 m const m = Math.floor(i + (j - i) / 2); if (nums[m] < target) { // 此情况说明 target 在区间 [m+1, j) 中 i = m + 1; } else if (nums[m] > target) { // 此情况说明 target 在区间 [i, m) 中 j = m; } else { // 找到目标元素,返回其索引 return m; } } return -1; // 未找到目标元素,返回 -1 } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分查找(双闭区间) */ let index = binarySearch(nums, target); console.info('目标元素 6 的索引 = %d', index); /* 二分查找(左闭右开区间) */ index = binarySearchLCRO(nums, target); console.info('目标元素 6 的索引 = %d', index); export {}; ================================================ FILE: codes/typescript/chapter_searching/binary_search_edge.ts ================================================ /** * File: binary_search_edge.ts * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ import { binarySearchInsertion } from './binary_search_insertion'; /* 二分查找最左一个 target */ function binarySearchLeftEdge(nums: Array, target: number): number { // 等价于查找 target 的插入点 const i = binarySearchInsertion(nums, target); // 未找到 target ,返回 -1 if (i === nums.length || nums[i] !== target) { return -1; } // 找到 target ,返回索引 i return i; } /* 二分查找最右一个 target */ function binarySearchRightEdge(nums: Array, target: number): number { // 转化为查找最左一个 target + 1 const i = binarySearchInsertion(nums, target + 1); // j 指向最右一个 target ,i 指向首个大于 target 的元素 const j = i - 1; // 未找到 target ,返回 -1 if (j === -1 || nums[j] !== target) { return -1; } // 找到 target ,返回索引 j return j; } /* Driver Code */ // 包含重复元素的数组 let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\n数组 nums = ' + nums); // 二分查找左边界和右边界 for (const target of [6, 7]) { let index = binarySearchLeftEdge(nums, target); console.log('最左一个元素 ' + target + ' 的索引为 ' + index); index = binarySearchRightEdge(nums, target); console.log('最右一个元素 ' + target + ' 的索引为 ' + index); } export {}; ================================================ FILE: codes/typescript/chapter_searching/binary_search_insertion.ts ================================================ /** * File: binary_search_insertion.ts * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 二分查找插入点(无重复元素) */ function binarySearchInsertionSimple( nums: Array, target: number ): number { let i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m, 使用 Math.floor() 向下取整 if (nums[m] < target) { i = m + 1; // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在区间 [i, m-1] 中 } else { return m; // 找到 target ,返回插入点 m } } // 未找到 target ,返回插入点 i return i; } /* 二分查找插入点(存在重复元素) */ function binarySearchInsertion(nums: Array, target: number): number { let i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m, 使用 Math.floor() 向下取整 if (nums[m] < target) { i = m + 1; // target 在区间 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在区间 [i, m-1] 中 } else { j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 } } // 返回插入点 i return i; } /* Driver Code */ // 无重复元素的数组 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; console.log('\n数组 nums = ' + nums); // 二分查找插入点 for (const target of [6, 9]) { const index = binarySearchInsertionSimple(nums, target); console.log('元素 ' + target + ' 的插入点的索引为 ' + index); } // 包含重复元素的数组 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\n数组 nums = ' + nums); // 二分查找插入点 for (const target of [2, 6, 20]) { const index = binarySearchInsertion(nums, target); console.log('元素 ' + target + ' 的插入点的索引为 ' + index); } export { binarySearchInsertion }; ================================================ FILE: codes/typescript/chapter_searching/hashing_search.ts ================================================ /** * File: hashing_search.ts * Created Time: 2022-12-29 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { ListNode, arrToLinkedList } from '../modules/ListNode'; /* 哈希查找(数组) */ function hashingSearchArray(map: Map, target: number): number { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 return map.has(target) ? (map.get(target) as number) : -1; } /* 哈希查找(链表) */ function hashingSearchLinkedList( map: Map, target: number ): ListNode | null { // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 null return map.has(target) ? (map.get(target) as ListNode) : null; } /* Driver Code */ const target = 3; /* 哈希查找(数组) */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // 初始化哈希表 const map = new Map(); for (let i = 0; i < nums.length; i++) { map.set(nums[i], i); // key: 元素,value: 索引 } const index = hashingSearchArray(map, target); console.log('目标元素 3 的索引 = ' + index); /* 哈希查找(链表) */ let head = arrToLinkedList(nums); // 初始化哈希表 const map1 = new Map(); while (head != null) { map1.set(head.val, head); // key: 节点值,value: 节点 head = head.next; } const node = hashingSearchLinkedList(map1, target); console.log('目标节点值 3 的对应节点对象为', node); export {}; ================================================ FILE: codes/typescript/chapter_searching/linear_search.ts ================================================ /** * File: linear_search.ts * Created Time: 2023-01-07 * Author: Daniel (better.sunjian@gmail.com) */ import { ListNode, arrToLinkedList } from '../modules/ListNode'; /* 线性查找(数组)*/ function linearSearchArray(nums: number[], target: number): number { // 遍历数组 for (let i = 0; i < nums.length; i++) { // 找到目标元素,返回其索引 if (nums[i] === target) { return i; } } // 未找到目标元素,返回 -1 return -1; } /* 线性查找(链表)*/ function linearSearchLinkedList( head: ListNode | null, target: number ): ListNode | null { // 遍历链表 while (head) { // 找到目标节点,返回之 if (head.val === target) { return head; } head = head.next; } // 未找到目标节点,返回 null return null; } /* Driver Code */ const target = 3; /* 在数组中执行线性查找 */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; const index = linearSearchArray(nums, target); console.log('目标元素 3 的索引 =', index); /* 在链表中执行线性查找 */ const head = arrToLinkedList(nums); const node = linearSearchLinkedList(head, target); console.log('目标节点值 3 的对应节点对象为', node); export {}; ================================================ FILE: codes/typescript/chapter_searching/two_sum.ts ================================================ /** * File: two_sum.ts * Created Time: 2022-12-15 * Author: gyt95 (gytkwan@gmail.com) */ /* 方法一:暴力枚举 */ function twoSumBruteForce(nums: number[], target: number): number[] { const n = nums.length; // 两层循环,时间复杂度为 O(n^2) for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { if (nums[i] + nums[j] === target) { return [i, j]; } } } return []; } /* 方法二:辅助哈希表 */ function twoSumHashTable(nums: number[], target: number): number[] { // 辅助哈希表,空间复杂度为 O(n) let m: Map = new Map(); // 单层循环,时间复杂度为 O(n) for (let i = 0; i < nums.length; i++) { let index = m.get(target - nums[i]); if (index !== undefined) { return [index, i]; } else { m.set(nums[i], i); } } return []; } /* Driver Code */ // 方法一 const nums = [2, 7, 11, 15], target = 13; let res = twoSumBruteForce(nums, target); console.log('方法一 res = ', res); // 方法二 res = twoSumHashTable(nums, target); console.log('方法二 res = ', res); export {}; ================================================ FILE: codes/typescript/chapter_sorting/bubble_sort.ts ================================================ /** * File: bubble_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 冒泡排序 */ function bubbleSort(nums: number[]): void { // 外循环:未排序区间为 [0, i] for (let i = nums.length - 1; i > 0; i--) { // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* 冒泡排序(标志优化)*/ function bubbleSortWithFlag(nums: number[]): void { // 外循环:未排序区间为 [0, i] for (let i = nums.length - 1; i > 0; i--) { let flag = false; // 初始化标志位 // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // 记录交换元素 } } if (!flag) break; // 此轮“冒泡”未交换任何元素,直接跳出 } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); console.log('冒泡排序完成后 nums =', nums); const nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); console.log('冒泡排序完成后 nums =', nums1); export {}; ================================================ FILE: codes/typescript/chapter_sorting/bucket_sort.ts ================================================ /** * File: bucket_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 桶排序 */ function bucketSort(nums: number[]): void { // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 const k = nums.length / 2; const buckets: number[][] = []; for (let i = 0; i < k; i++) { buckets.push([]); } // 1. 将数组元素分配到各个桶中 for (const num of nums) { // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] const i = Math.floor(num * k); // 将 num 添加进桶 i buckets[i].push(num); } // 2. 对各个桶执行排序 for (const bucket of buckets) { // 使用内置排序函数,也可以替换成其他排序算法 bucket.sort((a, b) => a - b); } // 3. 遍历桶合并结果 let i = 0; for (const bucket of buckets) { for (const num of bucket) { nums[i++] = num; } } } /* Driver Code */ const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); console.log('桶排序完成后 nums =', nums); export {}; ================================================ FILE: codes/typescript/chapter_sorting/counting_sort.ts ================================================ /** * File: counting_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 计数排序 */ // 简单实现,无法用于排序对象 function countingSortNaive(nums: number[]): void { // 1. 统计数组最大元素 m let m: number = Math.max(...nums); // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 const counter: number[] = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. 遍历 counter ,将各元素填入原数组 nums let i = 0; for (let num = 0; num < m + 1; num++) { for (let j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* 计数排序 */ // 完整实现,可排序对象,并且是稳定排序 function countingSort(nums: number[]): void { // 1. 统计数组最大元素 m let m: number = Math.max(...nums); // 2. 统计各数字的出现次数 // counter[num] 代表 num 的出现次数 const counter: number[] = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 for (let i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. 倒序遍历 nums ,将各元素填入结果数组 res // 初始化数组 res 用于记录结果 const n = nums.length; const res: number[] = new Array(n); for (let i = n - 1; i >= 0; i--) { const num = nums[i]; res[counter[num] - 1] = num; // 将 num 放置到对应索引处 counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 } // 使用结果数组 res 覆盖原数组 nums for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Driver Code */ const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); console.log('计数排序(无法排序对象)完成后 nums =', nums); const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); console.log('计数排序完成后 nums1 =', nums1); export {}; ================================================ FILE: codes/typescript/chapter_sorting/heap_sort.ts ================================================ /** * File: heap_sort.ts * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ function siftDown(nums: number[], n: number, i: number): void { while (true) { // 判断节点 i, l, r 中值最大的节点,记为 ma let l = 2 * i + 1; let r = 2 * i + 2; let ma = i; if (l < n && nums[l] > nums[ma]) { ma = l; } if (r < n && nums[r] > nums[ma]) { ma = r; } // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if (ma === i) { break; } // 交换两节点 [nums[i], nums[ma]] = [nums[ma], nums[i]]; // 循环向下堆化 i = ma; } } /* 堆排序 */ function heapSort(nums: number[]): void { // 建堆操作:堆化除叶节点以外的其他所有节点 for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // 从堆中提取最大元素,循环 n-1 轮 for (let i = nums.length - 1; i > 0; i--) { // 交换根节点与最右叶节点(交换首元素与尾元素) [nums[0], nums[i]] = [nums[i], nums[0]]; // 以根节点为起点,从顶至底进行堆化 siftDown(nums, i, 0); } } /* Driver Code */ const nums: number[] = [4, 1, 3, 1, 5, 2]; heapSort(nums); console.log('堆排序完成后 nums =', nums); export {}; ================================================ FILE: codes/typescript/chapter_sorting/insertion_sort.ts ================================================ /** * File: insertion_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 插入排序 */ function insertionSort(nums: number[]): void { // 外循环:已排序区间为 [0, i-1] for (let i = 1; i < nums.length; i++) { const base = nums[i]; let j = i - 1; // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位 j--; } nums[j + 1] = base; // 将 base 赋值到正确位置 } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); console.log('插入排序完成后 nums =', nums); export {}; ================================================ FILE: codes/typescript/chapter_sorting/merge_sort.ts ================================================ /** * File: merge_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 合并左子数组和右子数组 */ function merge(nums: number[], left: number, mid: number, right: number): void { // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] // 创建一个临时数组 tmp ,用于存放合并后的结果 const tmp = new Array(right - left + 1); // 初始化左子数组和右子数组的起始索引 let i = left, j = mid + 1, k = 0; // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // 将左子数组和右子数组的剩余元素复制到临时数组中 while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* 归并排序 */ function mergeSort(nums: number[], left: number, right: number): void { // 终止条件 if (left >= right) return; // 当子数组长度为 1 时终止递归 // 划分阶段 let mid = Math.floor(left + (right - left) / 2); // 计算中点 mergeSort(nums, left, mid); // 递归左子数组 mergeSort(nums, mid + 1, right); // 递归右子数组 // 合并阶段 merge(nums, left, mid, right); } /* Driver Code */ const nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); console.log('归并排序完成后 nums =', nums); export {}; ================================================ FILE: codes/typescript/chapter_sorting/quick_sort.ts ================================================ /** * File: quick_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 快速排序类 */ class QuickSort { /* 元素交换 */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵划分 */ partition(nums: number[], left: number, right: number): number { // 以 nums[left] 为基准数 let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j -= 1; // 从右向左找首个小于基准数的元素 } while (i < j && nums[i] <= nums[left]) { i += 1; // 从左向右找首个大于基准数的元素 } // 元素交换 this.swap(nums, i, j); // 交换这两个元素 } this.swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } /* 快速排序 */ quickSort(nums: number[], left: number, right: number): void { // 子数组长度为 1 时终止递归 if (left >= right) { return; } // 哨兵划分 const pivot = this.partition(nums, left, right); // 递归左子数组、右子数组 this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* 快速排序类(中位基准数优化) */ class QuickSortMedian { /* 元素交换 */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 选取三个候选元素的中位数 */ medianThree( nums: number[], left: number, mid: number, right: number ): number { let l = nums[left], m = nums[mid], r = nums[right]; // m 在 l 和 r 之间 if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // l 在 m 和 r 之间 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; return right; } /* 哨兵划分(三数取中值) */ partition(nums: number[], left: number, right: number): number { // 选取三个候选元素的中位数 let med = this.medianThree( nums, left, Math.floor((left + right) / 2), right ); // 将中位数交换至数组最左端 this.swap(nums, left, med); // 以 nums[left] 为基准数 let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // 从右向左找首个小于基准数的元素 } while (i < j && nums[i] <= nums[left]) { i++; // 从左向右找首个大于基准数的元素 } this.swap(nums, i, j); // 交换这两个元素 } this.swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } /* 快速排序 */ quickSort(nums: number[], left: number, right: number): void { // 子数组长度为 1 时终止递归 if (left >= right) { return; } // 哨兵划分 const pivot = this.partition(nums, left, right); // 递归左子数组、右子数组 this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* 快速排序类(递归深度优化) */ class QuickSortTailCall { /* 元素交换 */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵划分 */ partition(nums: number[], left: number, right: number): number { // 以 nums[left] 为基准数 let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // 从右向左找首个小于基准数的元素 } while (i < j && nums[i] <= nums[left]) { i++; // 从左向右找首个大于基准数的元素 } this.swap(nums, i, j); // 交换这两个元素 } this.swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } /* 快速排序(递归深度优化) */ quickSort(nums: number[], left: number, right: number): void { // 子数组长度为 1 时终止 while (left < right) { // 哨兵划分操作 let pivot = this.partition(nums, left, right); // 对两个子数组中较短的那个执行快速排序 if (pivot - left < right - pivot) { this.quickSort(nums, left, pivot - 1); // 递归排序左子数组 left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] } else { this.quickSort(nums, pivot + 1, right); // 递归排序右子数组 right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] } } } } /* Driver Code */ /* 快速排序 */ const nums = [2, 4, 1, 0, 3, 5]; const quickSort = new QuickSort(); quickSort.quickSort(nums, 0, nums.length - 1); console.log('快速排序完成后 nums =', nums); /* 快速排序(中位基准数优化) */ const nums1 = [2, 4, 1, 0, 3, 5]; const quickSortMedian = new QuickSortMedian(); quickSortMedian.quickSort(nums1, 0, nums1.length - 1); console.log('快速排序(中位基准数优化)完成后 nums =', nums1); /* 快速排序(递归深度优化) */ const nums2 = [2, 4, 1, 0, 3, 5]; const quickSortTailCall = new QuickSortTailCall(); quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); console.log('快速排序(递归深度优化)完成后 nums =', nums2); export {}; ================================================ FILE: codes/typescript/chapter_sorting/radix_sort.ts ================================================ /** * File: radix_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ function digit(num: number, exp: number): number { // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 return Math.floor(num / exp) % 10; } /* 计数排序(根据 nums 第 k 位排序) */ function countingSortDigit(nums: number[], exp: number): void { // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 const counter = new Array(10).fill(0); const n = nums.length; // 统计 0~9 各数字的出现次数 for (let i = 0; i < n; i++) { const d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d counter[d]++; // 统计数字 d 的出现次数 } // 求前缀和,将“出现个数”转换为“数组索引” for (let i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 倒序遍历,根据桶内统计结果,将各元素填入 res const res = new Array(n).fill(0); for (let i = n - 1; i >= 0; i--) { const d = digit(nums[i], exp); const j = counter[d] - 1; // 获取 d 在数组中的索引 j res[j] = nums[i]; // 将当前元素填入索引 j counter[d]--; // 将 d 的数量减 1 } // 使用结果覆盖原数组 nums for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* 基数排序 */ function radixSort(nums: number[]): void { // 获取数组的最大元素,用于判断最大位数 let m: number = Math.max(... nums); // 按照从低位到高位的顺序遍历 for (let exp = 1; exp <= m; exp *= 10) { // 对数组元素的第 k 位执行计数排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums, exp); } } /* Driver Code */ const nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radixSort(nums); console.log('基数排序完成后 nums =', nums); export {}; ================================================ FILE: codes/typescript/chapter_sorting/selection_sort.ts ================================================ /** * File: selection_sort.ts * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* 选择排序 */ function selectionSort(nums: number[]): void { let n = nums.length; // 外循环:未排序区间为 [i, n-1] for (let i = 0; i < n - 1; i++) { // 内循环:找到未排序区间内的最小元素 let k = i; for (let j = i + 1; j < n; j++) { if (nums[j] < nums[k]) { k = j; // 记录最小元素的索引 } } // 将该最小元素与未排序区间的首个元素交换 [nums[i], nums[k]] = [nums[k], nums[i]]; } } /* Driver Code */ const nums: number[] = [4, 1, 3, 1, 5, 2]; selectionSort(nums); console.log('选择排序完成后 nums =', nums); export {}; ================================================ FILE: codes/typescript/chapter_stack_and_queue/array_deque.ts ================================================ /** * File: array_deque.ts * Created Time: 2023-02-28 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 基于环形数组实现的双向队列 */ class ArrayDeque { private nums: number[]; // 用于存储双向队列元素的数组 private front: number; // 队首指针,指向队首元素 private queSize: number; // 双向队列长度 /* 构造方法 */ constructor(capacity: number) { this.nums = new Array(capacity); this.front = 0; this.queSize = 0; } /* 获取双向队列的容量 */ capacity(): number { return this.nums.length; } /* 获取双向队列的长度 */ size(): number { return this.queSize; } /* 判断双向队列是否为空 */ isEmpty(): boolean { return this.queSize === 0; } /* 计算环形数组索引 */ index(i: number): number { // 通过取余操作实现数组首尾相连 // 当 i 越过数组尾部后,回到头部 // 当 i 越过数组头部后,回到尾部 return (i + this.capacity()) % this.capacity(); } /* 队首入队 */ pushFirst(num: number): void { if (this.queSize === this.capacity()) { console.log('双向队列已满'); return; } // 队首指针向左移动一位 // 通过取余操作实现 front 越过数组头部后回到尾部 this.front = this.index(this.front - 1); // 将 num 添加至队首 this.nums[this.front] = num; this.queSize++; } /* 队尾入队 */ pushLast(num: number): void { if (this.queSize === this.capacity()) { console.log('双向队列已满'); return; } // 计算队尾指针,指向队尾索引 + 1 const rear: number = this.index(this.front + this.queSize); // 将 num 添加至队尾 this.nums[rear] = num; this.queSize++; } /* 队首出队 */ popFirst(): number { const num: number = this.peekFirst(); // 队首指针向后移动一位 this.front = this.index(this.front + 1); this.queSize--; return num; } /* 队尾出队 */ popLast(): number { const num: number = this.peekLast(); this.queSize--; return num; } /* 访问队首元素 */ peekFirst(): number { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); return this.nums[this.front]; } /* 访问队尾元素 */ peekLast(): number { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); // 计算尾元素索引 const last = this.index(this.front + this.queSize - 1); return this.nums[last]; } /* 返回数组用于打印 */ toArray(): number[] { // 仅转换有效长度范围内的列表元素 const res: number[] = []; for (let i = 0, j = this.front; i < this.queSize; i++, j++) { res[i] = this.nums[this.index(j)]; } return res; } } /* Driver Code */ /* 初始化双向队列 */ const capacity = 5; const deque: ArrayDeque = new ArrayDeque(capacity); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); console.log('双向队列 deque = [' + deque.toArray() + ']'); /* 访问元素 */ const peekFirst = deque.peekFirst(); console.log('队首元素 peekFirst = ' + peekFirst); const peekLast = deque.peekLast(); console.log('队尾元素 peekLast = ' + peekLast); /* 元素入队 */ deque.pushLast(4); console.log('元素 4 队尾入队后 deque = [' + deque.toArray() + ']'); deque.pushFirst(1); console.log('元素 1 队首入队后 deque = [' + deque.toArray() + ']'); /* 元素出队 */ const popLast = deque.popLast(); console.log( '队尾出队元素 = ' + popLast + ',队尾出队后 deque = [' + deque.toArray() + ']' ); const popFirst = deque.popFirst(); console.log( '队首出队元素 = ' + popFirst + ',队首出队后 deque = [' + deque.toArray() + ']' ); /* 获取双向队列的长度 */ const size = deque.size(); console.log('双向队列长度 size = ' + size); /* 判断双向队列是否为空 */ const isEmpty = deque.isEmpty(); console.log('双向队列是否为空 = ' + isEmpty); export {}; ================================================ FILE: codes/typescript/chapter_stack_and_queue/array_queue.ts ================================================ /** * File: array_queue.ts * Created Time: 2022-12-11 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* 基于环形数组实现的队列 */ class ArrayQueue { private nums: number[]; // 用于存储队列元素的数组 private front: number; // 队首指针,指向队首元素 private queSize: number; // 队列长度 constructor(capacity: number) { this.nums = new Array(capacity); this.front = this.queSize = 0; } /* 获取队列的容量 */ get capacity(): number { return this.nums.length; } /* 获取队列的长度 */ get size(): number { return this.queSize; } /* 判断队列是否为空 */ isEmpty(): boolean { return this.queSize === 0; } /* 入队 */ push(num: number): void { if (this.size === this.capacity) { console.log('队列已满'); return; } // 计算队尾指针,指向队尾索引 + 1 // 通过取余操作实现 rear 越过数组尾部后回到头部 const rear = (this.front + this.queSize) % this.capacity; // 将 num 添加至队尾 this.nums[rear] = num; this.queSize++; } /* 出队 */ pop(): number { const num = this.peek(); // 队首指针向后移动一位,若越过尾部,则返回到数组头部 this.front = (this.front + 1) % this.capacity; this.queSize--; return num; } /* 访问队首元素 */ peek(): number { if (this.isEmpty()) throw new Error('队列为空'); return this.nums[this.front]; } /* 返回 Array */ toArray(): number[] { // 仅转换有效长度范围内的列表元素 const arr = new Array(this.size); for (let i = 0, j = this.front; i < this.size; i++, j++) { arr[i] = this.nums[j % this.capacity]; } return arr; } } /* Driver Code */ /* 初始化队列 */ const capacity = 10; const queue = new ArrayQueue(capacity); /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('队列 queue =', queue.toArray()); /* 访问队首元素 */ const peek = queue.peek(); console.log('队首元素 peek = ' + peek); /* 元素出队 */ const pop = queue.pop(); console.log('出队元素 pop = ' + pop + ',出队后 queue =', queue.toArray()); /* 获取队列的长度 */ const size = queue.size; console.log('队列长度 size = ' + size); /* 判断队列是否为空 */ const isEmpty = queue.isEmpty(); console.log('队列是否为空 = ' + isEmpty); /* 测试环形数组 */ for (let i = 0; i < 10; i++) { queue.push(i); queue.pop(); console.log('第 ' + i + ' 轮入队 + 出队后 queue =', queue.toArray()); } export {}; ================================================ FILE: codes/typescript/chapter_stack_and_queue/array_stack.ts ================================================ /** * File: array_stack.ts * Created Time: 2022-12-08 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* 基于数组实现的栈 */ class ArrayStack { private stack: number[]; constructor() { this.stack = []; } /* 获取栈的长度 */ get size(): number { return this.stack.length; } /* 判断栈是否为空 */ isEmpty(): boolean { return this.stack.length === 0; } /* 入栈 */ push(num: number): void { this.stack.push(num); } /* 出栈 */ pop(): number | undefined { if (this.isEmpty()) throw new Error('栈为空'); return this.stack.pop(); } /* 访问栈顶元素 */ top(): number | undefined { if (this.isEmpty()) throw new Error('栈为空'); return this.stack[this.stack.length - 1]; } /* 返回 Array */ toArray() { return this.stack; } } /* Driver Code */ /* 初始化栈 */ const stack = new ArrayStack(); /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('栈 stack = '); console.log(stack.toArray()); /* 访问栈顶元素 */ const top = stack.top(); console.log('栈顶元素 top = ' + top); /* 元素出栈 */ const pop = stack.pop(); console.log('出栈元素 pop = ' + pop + ',出栈后 stack = '); console.log(stack.toArray()); /* 获取栈的长度 */ const size = stack.size; console.log('栈的长度 size = ' + size); /* 判断是否为空 */ const isEmpty = stack.isEmpty(); console.log('栈是否为空 = ' + isEmpty); export {}; ================================================ FILE: codes/typescript/chapter_stack_and_queue/deque.ts ================================================ /** * File: deque.ts * Created Time: 2023-01-17 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Driver Code */ /* 初始化双向队列 */ // TypeScript 没有内置的双端队列,只能把 Array 当作双端队列来使用 const deque: number[] = []; /* 元素入队 */ deque.push(2); deque.push(5); deque.push(4); // 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n) deque.unshift(3); deque.unshift(1); console.log('双向队列 deque = ', deque); /* 访问元素 */ const peekFirst: number = deque[0]; console.log('队首元素 peekFirst = ' + peekFirst); const peekLast: number = deque[deque.length - 1]; console.log('队尾元素 peekLast = ' + peekLast); /* 元素出队 */ // 请注意,由于是数组,shift() 方法的时间复杂度为 O(n) const popFront: number = deque.shift() as number; console.log( '队首出队元素 popFront = ' + popFront + ',队首出队后 deque = ' + deque ); const popBack: number = deque.pop() as number; console.log( '队尾出队元素 popBack = ' + popBack + ',队尾出队后 deque = ' + deque ); /* 获取双向队列的长度 */ const size: number = deque.length; console.log('双向队列长度 size = ' + size); /* 判断双向队列是否为空 */ const isEmpty: boolean = size === 0; console.log('双向队列是否为空 = ' + isEmpty); export {}; ================================================ FILE: codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts ================================================ /** * File: linkedlist_deque.ts * Created Time: 2023-02-04 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 双向链表节点 */ class ListNode { prev: ListNode; // 前驱节点引用 (指针) next: ListNode; // 后继节点引用 (指针) val: number; // 节点值 constructor(val: number) { this.val = val; this.next = null; this.prev = null; } } /* 基于双向链表实现的双向队列 */ class LinkedListDeque { private front: ListNode; // 头节点 front private rear: ListNode; // 尾节点 rear private queSize: number; // 双向队列的长度 constructor() { this.front = null; this.rear = null; this.queSize = 0; } /* 队尾入队操作 */ pushLast(val: number): void { const node: ListNode = new ListNode(val); // 若链表为空,则令 front 和 rear 都指向 node if (this.queSize === 0) { this.front = node; this.rear = node; } else { // 将 node 添加至链表尾部 this.rear.next = node; node.prev = this.rear; this.rear = node; // 更新尾节点 } this.queSize++; } /* 队首入队操作 */ pushFirst(val: number): void { const node: ListNode = new ListNode(val); // 若链表为空,则令 front 和 rear 都指向 node if (this.queSize === 0) { this.front = node; this.rear = node; } else { // 将 node 添加至链表头部 this.front.prev = node; node.next = this.front; this.front = node; // 更新头节点 } this.queSize++; } /* 队尾出队操作 */ popLast(): number { if (this.queSize === 0) { return null; } const value: number = this.rear.val; // 存储尾节点值 // 删除尾节点 let temp: ListNode = this.rear.prev; if (temp !== null) { temp.next = null; this.rear.prev = null; } this.rear = temp; // 更新尾节点 this.queSize--; return value; } /* 队首出队操作 */ popFirst(): number { if (this.queSize === 0) { return null; } const value: number = this.front.val; // 存储尾节点值 // 删除头节点 let temp: ListNode = this.front.next; if (temp !== null) { temp.prev = null; this.front.next = null; } this.front = temp; // 更新头节点 this.queSize--; return value; } /* 访问队尾元素 */ peekLast(): number { return this.queSize === 0 ? null : this.rear.val; } /* 访问队首元素 */ peekFirst(): number { return this.queSize === 0 ? null : this.front.val; } /* 获取双向队列的长度 */ size(): number { return this.queSize; } /* 判断双向队列是否为空 */ isEmpty(): boolean { return this.queSize === 0; } /* 打印双向队列 */ print(): void { const arr: number[] = []; let temp: ListNode = this.front; while (temp !== null) { arr.push(temp.val); temp = temp.next; } console.log('[' + arr.join(', ') + ']'); } } /* Driver Code */ /* 初始化双向队列 */ const linkedListDeque: LinkedListDeque = new LinkedListDeque(); linkedListDeque.pushLast(3); linkedListDeque.pushLast(2); linkedListDeque.pushLast(5); console.log('双向队列 linkedListDeque = '); linkedListDeque.print(); /* 访问元素 */ const peekFirst: number = linkedListDeque.peekFirst(); console.log('队首元素 peekFirst = ' + peekFirst); const peekLast: number = linkedListDeque.peekLast(); console.log('队尾元素 peekLast = ' + peekLast); /* 元素入队 */ linkedListDeque.pushLast(4); console.log('元素 4 队尾入队后 linkedListDeque = '); linkedListDeque.print(); linkedListDeque.pushFirst(1); console.log('元素 1 队首入队后 linkedListDeque = '); linkedListDeque.print(); /* 元素出队 */ const popLast: number = linkedListDeque.popLast(); console.log('队尾出队元素 = ' + popLast + ',队尾出队后 linkedListDeque = '); linkedListDeque.print(); const popFirst: number = linkedListDeque.popFirst(); console.log('队首出队元素 = ' + popFirst + ',队首出队后 linkedListDeque = '); linkedListDeque.print(); /* 获取双向队列的长度 */ const size: number = linkedListDeque.size(); console.log('双向队列长度 size = ' + size); /* 判断双向队列是否为空 */ const isEmpty: boolean = linkedListDeque.isEmpty(); console.log('双向队列是否为空 = ' + isEmpty); ================================================ FILE: codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts ================================================ /** * File: linkedlist_queue.ts * Created Time: 2022-12-19 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ import { ListNode } from '../modules/ListNode'; /* 基于链表实现的队列 */ class LinkedListQueue { private front: ListNode | null; // 头节点 front private rear: ListNode | null; // 尾节点 rear private queSize: number = 0; constructor() { this.front = null; this.rear = null; } /* 获取队列的长度 */ get size(): number { return this.queSize; } /* 判断队列是否为空 */ isEmpty(): boolean { return this.size === 0; } /* 入队 */ push(num: number): void { // 在尾节点后添加 num const node = new ListNode(num); // 如果队列为空,则令头、尾节点都指向该节点 if (!this.front) { this.front = node; this.rear = node; // 如果队列不为空,则将该节点添加到尾节点后 } else { this.rear!.next = node; this.rear = node; } this.queSize++; } /* 出队 */ pop(): number { const num = this.peek(); if (!this.front) throw new Error('队列为空'); // 删除头节点 this.front = this.front.next; this.queSize--; return num; } /* 访问队首元素 */ peek(): number { if (this.size === 0) throw new Error('队列为空'); return this.front!.val; } /* 将链表转化为 Array 并返回 */ toArray(): number[] { let node = this.front; const res = new Array(this.size); for (let i = 0; i < res.length; i++) { res[i] = node!.val; node = node!.next; } return res; } } /* Driver Code */ /* 初始化队列 */ const queue = new LinkedListQueue(); /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('队列 queue = ' + queue.toArray()); /* 访问队首元素 */ const peek = queue.peek(); console.log('队首元素 peek = ' + peek); /* 元素出队 */ const pop = queue.pop(); console.log('出队元素 pop = ' + pop + ',出队后 queue = ' + queue.toArray()); /* 获取队列的长度 */ const size = queue.size; console.log('队列长度 size = ' + size); /* 判断队列是否为空 */ const isEmpty = queue.isEmpty(); console.log('队列是否为空 = ' + isEmpty); export {}; ================================================ FILE: codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts ================================================ /** * File: linkedlist_stack.ts * Created Time: 2022-12-21 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ import { ListNode } from '../modules/ListNode'; /* 基于链表实现的栈 */ class LinkedListStack { private stackPeek: ListNode | null; // 将头节点作为栈顶 private stkSize: number = 0; // 栈的长度 constructor() { this.stackPeek = null; } /* 获取栈的长度 */ get size(): number { return this.stkSize; } /* 判断栈是否为空 */ isEmpty(): boolean { return this.size === 0; } /* 入栈 */ push(num: number): void { const node = new ListNode(num); node.next = this.stackPeek; this.stackPeek = node; this.stkSize++; } /* 出栈 */ pop(): number { const num = this.peek(); if (!this.stackPeek) throw new Error('栈为空'); this.stackPeek = this.stackPeek.next; this.stkSize--; return num; } /* 访问栈顶元素 */ peek(): number { if (!this.stackPeek) throw new Error('栈为空'); return this.stackPeek.val; } /* 将链表转化为 Array 并返回 */ toArray(): number[] { let node = this.stackPeek; const res = new Array(this.size); for (let i = res.length - 1; i >= 0; i--) { res[i] = node!.val; node = node!.next; } return res; } } /* Driver Code */ /* 初始化栈 */ const stack = new LinkedListStack(); /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('栈 stack = ' + stack.toArray()); /* 访问栈顶元素 */ const peek = stack.peek(); console.log('栈顶元素 peek = ' + peek); /* 元素出栈 */ const pop = stack.pop(); console.log('出栈元素 pop = ' + pop + ',出栈后 stack = ' + stack.toArray()); /* 获取栈的长度 */ const size = stack.size; console.log('栈的长度 size = ' + size); /* 判断是否为空 */ const isEmpty = stack.isEmpty(); console.log('栈是否为空 = ' + isEmpty); export {}; ================================================ FILE: codes/typescript/chapter_stack_and_queue/queue.ts ================================================ /** * File: queue.ts * Created Time: 2022-12-05 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* 初始化队列 */ // TypeScript 没有内置的队列,可以把 Array 当作队列来使用 const queue: number[] = []; /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('队列 queue =', queue); /* 访问队首元素 */ const peek = queue[0]; console.log('队首元素 peek =', peek); /* 元素出队 */ // 底层是数组,因此 shift() 方法的时间复杂度为 O(n) const pop = queue.shift(); console.log('出队元素 pop =', pop, ',出队后 queue = ', queue); /* 获取队列的长度 */ const size = queue.length; console.log('队列长度 size =', size); /* 判断队列是否为空 */ const isEmpty = queue.length === 0; console.log('队列是否为空 = ', isEmpty); export {}; ================================================ FILE: codes/typescript/chapter_stack_and_queue/stack.ts ================================================ /** * File: stack.ts * Created Time: 2022-12-04 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* 初始化栈 */ // TypeScript 没有内置的栈类,可以把 Array 当作栈来使用 const stack: number[] = []; /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('栈 stack =', stack); /* 访问栈顶元素 */ const peek = stack[stack.length - 1]; console.log('栈顶元素 peek =', peek); /* 元素出栈 */ const pop = stack.pop(); console.log('出栈元素 pop =', pop); console.log('出栈后 stack =', stack); /* 获取栈的长度 */ const size = stack.length; console.log('栈的长度 size =', size); /* 判断是否为空 */ const isEmpty = stack.length === 0; console.log('栈是否为空 =', isEmpty); export {}; ================================================ FILE: codes/typescript/chapter_tree/array_binary_tree.ts ================================================ /** * File: array_binary_tree.js * Created Time: 2023-08-09 * Author: yuan0221 (yl1452491917@gmail.com) */ import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; type Order = 'pre' | 'in' | 'post'; /* 数组表示下的二叉树类 */ class ArrayBinaryTree { #tree: (number | null)[]; /* 构造方法 */ constructor(arr: (number | null)[]) { this.#tree = arr; } /* 列表容量 */ size(): number { return this.#tree.length; } /* 获取索引为 i 节点的值 */ val(i: number): number | null { // 若索引越界,则返回 null ,代表空位 if (i < 0 || i >= this.size()) return null; return this.#tree[i]; } /* 获取索引为 i 节点的左子节点的索引 */ left(i: number): number { return 2 * i + 1; } /* 获取索引为 i 节点的右子节点的索引 */ right(i: number): number { return 2 * i + 2; } /* 获取索引为 i 节点的父节点的索引 */ parent(i: number): number { return Math.floor((i - 1) / 2); // 向下整除 } /* 层序遍历 */ levelOrder(): number[] { let res = []; // 直接遍历数组 for (let i = 0; i < this.size(); i++) { if (this.val(i) !== null) res.push(this.val(i)); } return res; } /* 深度优先遍历 */ #dfs(i: number, order: Order, res: (number | null)[]): void { // 若为空位,则返回 if (this.val(i) === null) return; // 前序遍历 if (order === 'pre') res.push(this.val(i)); this.#dfs(this.left(i), order, res); // 中序遍历 if (order === 'in') res.push(this.val(i)); this.#dfs(this.right(i), order, res); // 后序遍历 if (order === 'post') res.push(this.val(i)); } /* 前序遍历 */ preOrder(): (number | null)[] { const res = []; this.#dfs(0, 'pre', res); return res; } /* 中序遍历 */ inOrder(): (number | null)[] { const res = []; this.#dfs(0, 'in', res); return res; } /* 后序遍历 */ postOrder(): (number | null)[] { const res = []; this.#dfs(0, 'post', res); return res; } } /* Driver Code */ // 初始化二叉树 // 这里借助了一个从数组直接生成二叉树的函数 const arr = Array.of( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ); const root = arrToTree(arr); console.log('\n初始化二叉树\n'); console.log('二叉树的数组表示:'); console.log(arr); console.log('二叉树的链表表示:'); printTree(root); // 数组表示下的二叉树类 const abt = new ArrayBinaryTree(arr); // 访问节点 const i = 1; const l = abt.left(i); const r = abt.right(i); const p = abt.parent(i); console.log('\n当前节点的索引为 ' + i + ' ,值为 ' + abt.val(i)); console.log( '其左子节点的索引为 ' + l + ' ,值为 ' + (l === null ? 'null' : abt.val(l)) ); console.log( '其右子节点的索引为 ' + r + ' ,值为 ' + (r === null ? 'null' : abt.val(r)) ); console.log( '其父节点的索引为 ' + p + ' ,值为 ' + (p === null ? 'null' : abt.val(p)) ); // 遍历树 let res = abt.levelOrder(); console.log('\n层序遍历为:' + res); res = abt.preOrder(); console.log('前序遍历为:' + res); res = abt.inOrder(); console.log('中序遍历为:' + res); res = abt.postOrder(); console.log('后序遍历为:' + res); export {}; ================================================ FILE: codes/typescript/chapter_tree/avl_tree.ts ================================================ /** * File: avl_tree.ts * Created Time: 2023-02-06 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* AVL 树*/ class AVLTree { root: TreeNode; /* 构造方法 */ constructor() { this.root = null; //根节点 } /* 获取节点高度 */ height(node: TreeNode): number { // 空节点高度为 -1 ,叶节点高度为 0 return node === null ? -1 : node.height; } /* 更新节点高度 */ private updateHeight(node: TreeNode): void { // 节点高度等于最高子树高度 + 1 node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; } /* 获取平衡因子 */ balanceFactor(node: TreeNode): number { // 空节点平衡因子为 0 if (node === null) return 0; // 节点平衡因子 = 左子树高度 - 右子树高度 return this.height(node.left) - this.height(node.right); } /* 右旋操作 */ private rightRotate(node: TreeNode): TreeNode { const child = node.left; const grandChild = child.right; // 以 child 为原点,将 node 向右旋转 child.right = node; node.left = grandChild; // 更新节点高度 this.updateHeight(node); this.updateHeight(child); // 返回旋转后子树的根节点 return child; } /* 左旋操作 */ private leftRotate(node: TreeNode): TreeNode { const child = node.right; const grandChild = child.left; // 以 child 为原点,将 node 向左旋转 child.left = node; node.right = grandChild; // 更新节点高度 this.updateHeight(node); this.updateHeight(child); // 返回旋转后子树的根节点 return child; } /* 执行旋转操作,使该子树重新恢复平衡 */ private rotate(node: TreeNode): TreeNode { // 获取节点 node 的平衡因子 const balanceFactor = this.balanceFactor(node); // 左偏树 if (balanceFactor > 1) { if (this.balanceFactor(node.left) >= 0) { // 右旋 return this.rightRotate(node); } else { // 先左旋后右旋 node.left = this.leftRotate(node.left); return this.rightRotate(node); } } // 右偏树 if (balanceFactor < -1) { if (this.balanceFactor(node.right) <= 0) { // 左旋 return this.leftRotate(node); } else { // 先右旋后左旋 node.right = this.rightRotate(node.right); return this.leftRotate(node); } } // 平衡树,无须旋转,直接返回 return node; } /* 插入节点 */ insert(val: number): void { this.root = this.insertHelper(this.root, val); } /* 递归插入节点(辅助方法) */ private insertHelper(node: TreeNode, val: number): TreeNode { if (node === null) return new TreeNode(val); /* 1. 查找插入位置并插入节点 */ if (val < node.val) { node.left = this.insertHelper(node.left, val); } else if (val > node.val) { node.right = this.insertHelper(node.right, val); } else { return node; // 重复节点不插入,直接返回 } this.updateHeight(node); // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = this.rotate(node); // 返回子树的根节点 return node; } /* 删除节点 */ remove(val: number): void { this.root = this.removeHelper(this.root, val); } /* 递归删除节点(辅助方法) */ private removeHelper(node: TreeNode, val: number): TreeNode { if (node === null) return null; /* 1. 查找节点并删除 */ if (val < node.val) { node.left = this.removeHelper(node.left, val); } else if (val > node.val) { node.right = this.removeHelper(node.right, val); } else { if (node.left === null || node.right === null) { const child = node.left !== null ? node.left : node.right; // 子节点数量 = 0 ,直接删除 node 并返回 if (child === null) { return null; } else { // 子节点数量 = 1 ,直接删除 node node = child; } } else { // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 let temp = node.right; while (temp.left !== null) { temp = temp.left; } node.right = this.removeHelper(node.right, temp.val); node.val = temp.val; } } this.updateHeight(node); // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = this.rotate(node); // 返回子树的根节点 return node; } /* 查找节点 */ search(val: number): TreeNode { let cur = this.root; // 循环查找,越过叶节点后跳出 while (cur !== null) { if (cur.val < val) { // 目标节点在 cur 的右子树中 cur = cur.right; } else if (cur.val > val) { // 目标节点在 cur 的左子树中 cur = cur.left; } else { // 找到目标节点,跳出循环 break; } } // 返回目标节点 return cur; } } function testInsert(tree: AVLTree, val: number): void { tree.insert(val); console.log('\n插入节点 ' + val + ' 后,AVL 树为'); printTree(tree.root); } function testRemove(tree: AVLTree, val: number): void { tree.remove(val); console.log('\n删除节点 ' + val + ' 后,AVL 树为'); printTree(tree.root); } /* Driver Code */ /* 初始化空 AVL 树 */ const avlTree = new AVLTree(); /* 插入节点 */ // 请关注插入节点后,AVL 树是如何保持平衡的 testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); testInsert(avlTree, 4); testInsert(avlTree, 5); testInsert(avlTree, 8); testInsert(avlTree, 7); testInsert(avlTree, 9); testInsert(avlTree, 10); testInsert(avlTree, 6); /* 插入重复节点 */ testInsert(avlTree, 7); /* 删除节点 */ // 请关注删除节点后,AVL 树是如何保持平衡的 testRemove(avlTree, 8); // 删除度为 0 的节点 testRemove(avlTree, 5); // 删除度为 1 的节点 testRemove(avlTree, 4); // 删除度为 2 的节点 /* 查询节点 */ const node = avlTree.search(7); console.log('\n查找到的节点对象为', node, ',节点值 = ' + node.val); export {}; ================================================ FILE: codes/typescript/chapter_tree/binary_search_tree.ts ================================================ /** * File: binary_search_tree.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 二叉搜索树 */ class BinarySearchTree { private root: TreeNode | null; /* 构造方法 */ constructor() { // 初始化空树 this.root = null; } /* 获取二叉树根节点 */ getRoot(): TreeNode | null { return this.root; } /* 查找节点 */ search(num: number): TreeNode | null { let cur = this.root; // 循环查找,越过叶节点后跳出 while (cur !== null) { // 目标节点在 cur 的右子树中 if (cur.val < num) cur = cur.right; // 目标节点在 cur 的左子树中 else if (cur.val > num) cur = cur.left; // 找到目标节点,跳出循环 else break; } // 返回目标节点 return cur; } /* 插入节点 */ insert(num: number): void { // 若树为空,则初始化根节点 if (this.root === null) { this.root = new TreeNode(num); return; } let cur: TreeNode | null = this.root, pre: TreeNode | null = null; // 循环查找,越过叶节点后跳出 while (cur !== null) { // 找到重复节点,直接返回 if (cur.val === num) return; pre = cur; // 插入位置在 cur 的右子树中 if (cur.val < num) cur = cur.right; // 插入位置在 cur 的左子树中 else cur = cur.left; } // 插入节点 const node = new TreeNode(num); if (pre!.val < num) pre!.right = node; else pre!.left = node; } /* 删除节点 */ remove(num: number): void { // 若树为空,直接提前返回 if (this.root === null) return; let cur: TreeNode | null = this.root, pre: TreeNode | null = null; // 循环查找,越过叶节点后跳出 while (cur !== null) { // 找到待删除节点,跳出循环 if (cur.val === num) break; pre = cur; // 待删除节点在 cur 的右子树中 if (cur.val < num) cur = cur.right; // 待删除节点在 cur 的左子树中 else cur = cur.left; } // 若无待删除节点,则直接返回 if (cur === null) return; // 子节点数量 = 0 or 1 if (cur.left === null || cur.right === null) { // 当子节点数量 = 0 / 1 时, child = null / 该子节点 const child: TreeNode | null = cur.left !== null ? cur.left : cur.right; // 删除节点 cur if (cur !== this.root) { if (pre!.left === cur) pre!.left = child; else pre!.right = child; } else { // 若删除节点为根节点,则重新指定根节点 this.root = child; } } // 子节点数量 = 2 else { // 获取中序遍历中 cur 的下一个节点 let tmp: TreeNode | null = cur.right; while (tmp!.left !== null) { tmp = tmp!.left; } // 递归删除节点 tmp this.remove(tmp!.val); // 用 tmp 覆盖 cur cur.val = tmp!.val; } } } /* Driver Code */ /* 初始化二叉搜索树 */ const bst = new BinarySearchTree(); // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (const num of nums) { bst.insert(num); } console.log('\n初始化的二叉树为\n'); printTree(bst.getRoot()); /* 查找节点 */ const node = bst.search(7); console.log( '\n查找到的节点对象为 ' + node + ',节点值 = ' + (node ? node.val : 'null') ); /* 插入节点 */ bst.insert(16); console.log('\n插入节点 16 后,二叉树为\n'); printTree(bst.getRoot()); /* 删除节点 */ bst.remove(1); console.log('\n删除节点 1 后,二叉树为\n'); printTree(bst.getRoot()); bst.remove(2); console.log('\n删除节点 2 后,二叉树为\n'); printTree(bst.getRoot()); bst.remove(4); console.log('\n删除节点 4 后,二叉树为\n'); printTree(bst.getRoot()); export {}; ================================================ FILE: codes/typescript/chapter_tree/binary_tree.ts ================================================ /** * File: binary_tree.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 初始化二叉树 */ // 初始化节点 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; console.log('\n初始化二叉树\n'); printTree(n1); /* 插入与删除节点 */ const P = new TreeNode(0); // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; console.log('\n插入节点 P 后\n'); printTree(n1); // 删除节点 P n1.left = n2; console.log('\n删除节点 P 后\n'); printTree(n1); export {}; ================================================ FILE: codes/typescript/chapter_tree/binary_tree_bfs.ts ================================================ /** * File: binary_tree_bfs.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 层序遍历 */ function levelOrder(root: TreeNode | null): number[] { // 初始化队列,加入根节点 const queue = [root]; // 初始化一个列表,用于保存遍历序列 const list: number[] = []; while (queue.length) { let node = queue.shift() as TreeNode; // 队列出队 list.push(node.val); // 保存节点值 if (node.left) { queue.push(node.left); // 左子节点入队 } if (node.right) { queue.push(node.right); // 右子节点入队 } } return list; } /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树\n'); printTree(root); /* 层序遍历 */ const list = levelOrder(root); console.log('\n层序遍历的节点打印序列 = ' + list); export {}; ================================================ FILE: codes/typescript/chapter_tree/binary_tree_dfs.ts ================================================ /** * File: binary_tree_dfs.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; // 初始化列表,用于存储遍历序列 const list: number[] = []; /* 前序遍历 */ function preOrder(root: TreeNode | null): void { if (root === null) { return; } // 访问优先级:根节点 -> 左子树 -> 右子树 list.push(root.val); preOrder(root.left); preOrder(root.right); } /* 中序遍历 */ function inOrder(root: TreeNode | null): void { if (root === null) { return; } // 访问优先级:左子树 -> 根节点 -> 右子树 inOrder(root.left); list.push(root.val); inOrder(root.right); } /* 后序遍历 */ function postOrder(root: TreeNode | null): void { if (root === null) { return; } // 访问优先级:左子树 -> 右子树 -> 根节点 postOrder(root.left); postOrder(root.right); list.push(root.val); } /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树\n'); printTree(root); /* 前序遍历 */ list.length = 0; preOrder(root); console.log('\n前序遍历的节点打印序列 = ' + list); /* 中序遍历 */ list.length = 0; inOrder(root); console.log('\n中序遍历的节点打印序列 = ' + list); /* 后序遍历 */ list.length = 0; postOrder(root); console.log('\n后序遍历的节点打印序列 = ' + list); export {}; ================================================ FILE: codes/typescript/modules/ListNode.ts ================================================ /** * File: ListNode.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ /* 链表节点 */ class ListNode { val: number; next: ListNode | null; constructor(val?: number, next?: ListNode | null) { this.val = val === undefined ? 0 : val; this.next = next === undefined ? null : next; } } /* 将数组反序列化为链表 */ function arrToLinkedList(arr: number[]): ListNode | null { const dum: ListNode = new ListNode(0); let head = dum; for (const val of arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } export { ListNode, arrToLinkedList }; ================================================ FILE: codes/typescript/modules/PrintUtil.ts ================================================ /** * File: PrintUtil.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from './ListNode'; import { TreeNode, arrToTree } from './TreeNode'; /* 打印链表 */ function printLinkedList(head: ListNode | null): void { const list: string[] = []; while (head !== null) { list.push(head.val.toString()); head = head.next; } console.log(list.join(' -> ')); } class Trunk { prev: Trunk | null; str: string; constructor(prev: Trunk | null, str: string) { this.prev = prev; this.str = str; } } /** * 打印二叉树 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ function printTree(root: TreeNode | null) { printTreeHelper(root, null, false); } /* 打印二叉树 */ function printTreeHelper( root: TreeNode | null, prev: Trunk | null, isRight: boolean ) { if (root === null) { return; } let prev_str = ' '; const trunk = new Trunk(prev, prev_str); printTreeHelper(root.right, trunk, true); if (prev === null) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); console.log(' ' + root.val); if (prev) { prev.str = prev_str; } trunk.str = ' |'; printTreeHelper(root.left, trunk, false); } function showTrunks(p: Trunk | null) { if (p === null) { return; } showTrunks(p.prev); process.stdout.write(p.str); } /* 打印堆 */ function printHeap(arr: number[]): void { console.log('堆的数组表示:'); console.log(arr); console.log('堆的树状表示:'); const root = arrToTree(arr); printTree(root); } export { printLinkedList, printTree, printHeap }; ================================================ FILE: codes/typescript/modules/TreeNode.ts ================================================ /** * File: TreeNode.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ /* 二叉树节点 */ class TreeNode { val: number; // 节点值 height: number; // 节点高度 left: TreeNode | null; // 左子节点指针 right: TreeNode | null; // 右子节点指针 constructor( val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null ) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } /* 将数组反序列化为二叉树 */ function arrToTree(arr: (number | null)[], i: number = 0): TreeNode | null { if (i < 0 || i >= arr.length || arr[i] === null) { return null; } let root = new TreeNode(arr[i]); root.left = arrToTree(arr, 2 * i + 1); root.right = arrToTree(arr, 2 * i + 2); return root; } export { TreeNode, arrToTree }; ================================================ FILE: codes/typescript/modules/Vertex.ts ================================================ /** * File: Vertex.ts * Created Time: 2023-02-15 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 顶点类 */ class Vertex { val: number; constructor(val: number) { this.val = val; } /* 输入值列表 vals ,返回顶点列表 vets */ public static valsToVets(vals: number[]): Vertex[] { const vets: Vertex[] = []; for (let i = 0; i < vals.length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* 输入顶点列表 vets ,返回值列表 vals */ public static vetsToVals(vets: Vertex[]): number[] { const vals: number[] = []; for (const vet of vets) { vals.push(vet.val); } return vals; } } export { Vertex }; ================================================ FILE: codes/typescript/package.json ================================================ { "private": true, "type": "module", "scripts": { "check": "tsc" }, "devDependencies": { "@types/node": "^24.9.2", "typescript": "^5.9.3" } } ================================================ FILE: codes/typescript/tsconfig.json ================================================ { "compilerOptions": { "baseUrl": ".", "module": "esnext", "moduleResolution": "node", "types": ["@types/node"], "noEmit": true, "target": "esnext", }, "include": ["chapter_*/*.ts"], "exclude": ["node_modules"] } ================================================ FILE: codes/zig/.gitignore ================================================ zig-out zig-cache .zig-cache !/.vscode/ ================================================ FILE: codes/zig/build.zig ================================================ // File: build.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) //! Zig Version: 0.14.1 //! Build Command: zig build //! Run Command: zig build run | zig build run_* //! Test Command: zig build test | zig build test -Dtest-filter=* const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const chapters = [_][]const u8{ "chapter_computational_complexity", "chapter_array_and_linkedlist", "chapter_stack_and_queue", "chapter_hashing", "chapter_tree", "chapter_heap", "chapter_searching", "chapter_sorting", "chapter_dynamic_programming", }; const test_step = b.step("test", "Run unit tests"); const test_filters = b.option([]const []const u8, "test-filter", "Skip tests that do not match any filter") orelse &[0][]const u8{}; buildChapterExeModules(b, target, optimize, &chapters, test_step, test_filters); buildMainExeModule(b, target, optimize); } fn buildChapterExeModules( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, chapter_dirs: []const []const u8, test_step: *std.Build.Step, test_filters: []const []const u8, ) void { for (chapter_dirs) |chapter_dir_name| { const chapter_dir_path = std.fs.path.join(b.allocator, &[_][]const u8{chapter_dir_name}) catch continue; var chapter_dir = std.fs.cwd().openDir(chapter_dir_path, .{ .iterate = true }) catch continue; defer chapter_dir.close(); var it = chapter_dir.iterate(); while (it.next() catch continue) |chapter_dir_entry| { if (chapter_dir_entry.kind != .file or !std.mem.endsWith(u8, chapter_dir_entry.name, ".zig")) continue; const exe_mod = buildExeModuleFromChapterDirEntry(b, target, optimize, chapter_dir_name, chapter_dir_entry) catch continue; addTestStepToExeModule(b, test_step, exe_mod, test_filters); } } } fn buildExeModuleFromChapterDirEntry( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, chapter_dir_name: []const u8, chapter_dir_entry: std.fs.Dir.Entry, ) !*std.Build.Module { const zig_file_path = try std.fs.path.join(b.allocator, &[_][]const u8{ chapter_dir_name, chapter_dir_entry.name }); const zig_file_name = chapter_dir_entry.name[0 .. chapter_dir_entry.name.len - 4]; // abstract zig file name from xxx.zig // 这里临时只添加数组和链表章节部分,后续修改完后全部放开 const new_algo_names = [_][]const u8{ "array", "linked_list", "list", "my_list", "iteration", "recursion", "space_complexity", "time_complexity", "worst_best_time_complexity", }; var can_run = false; for (new_algo_names) |name| { if (std.mem.eql(u8, zig_file_name, name)) { can_run = true; } } if (!can_run) { return error.CanNotRunUseOldZigCodes; } // std.debug.print("now run zig file name = {s}\n", .{zig_file_name}); const exe_mod = b.createModule(.{ .root_source_file = b.path(zig_file_path), .target = target, .optimize = optimize, }); const exe = b.addExecutable(.{ .name = zig_file_name, .root_module = exe_mod, }); const utils_mod = createUtilsModule(b, target, optimize); exe_mod.addImport("utils", utils_mod); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const step_name = try std.fmt.allocPrint(b.allocator, "run_{s}", .{zig_file_name}); const step_desc = try std.fmt.allocPrint(b.allocator, "Run {s}/{s}.zig", .{ chapter_dir_name, zig_file_name }); const run_step = b.step(step_name, step_desc); run_step.dependOn(&run_cmd.step); return exe_mod; } fn buildMainExeModule( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, ) void { const exe_mod = b.createModule(.{ .root_source_file = b.path("main.zig"), .target = target, .optimize = optimize, }); const utils_mod = createUtilsModule(b, target, optimize); exe_mod.addImport("utils", utils_mod); const exe = b.addExecutable(.{ .name = "main", .root_module = exe_mod, }); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run all hello algo zig"); run_step.dependOn(&run_cmd.step); } fn createUtilsModule(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Module { const utils_mod = b.createModule(.{ .root_source_file = b.path("utils/utils.zig"), .target = target, .optimize = optimize, }); return utils_mod; } fn addTestStepToExeModule(b: *std.Build, test_step: *std.Build.Step, exe_mod: *std.Build.Module, test_filters: []const []const u8) void { const exe_unit_tests = b.addTest(.{ .root_module = exe_mod, .filters = test_filters, }); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); test_step.dependOn(&run_exe_unit_tests.step); } ================================================ FILE: codes/zig/chapter_array_and_linkedlist/array.zig ================================================ // File: array.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // 随机访问元素 pub fn randomAccess(nums: []const i32) i32 { // 在区间 [0, nums.len) 中随机抽取一个整数 const random_index = std.crypto.random.intRangeLessThan(usize, 0, nums.len); // 获取并返回随机元素 const randomNum = nums[random_index]; return randomNum; } // 扩展数组长度 pub fn extend(allocator: std.mem.Allocator, nums: []const i32, enlarge: usize) ![]i32 { // 初始化一个扩展长度后的数组 const res = try allocator.alloc(i32, nums.len + enlarge); @memset(res, 0); // 将原数组中的所有元素复制到新数组 std.mem.copyForwards(i32, res, nums); // 返回扩展后的新数组 return res; } // 在数组的索引 index 处插入元素 num pub fn insert(nums: []i32, num: i32, index: usize) void { // 把索引 index 以及之后的所有元素向后移动一位 var i = nums.len - 1; while (i > index) : (i -= 1) { nums[i] = nums[i - 1]; } // 将 num 赋给 index 处的元素 nums[index] = num; } // 删除索引 index 处的元素 pub fn remove(nums: []i32, index: usize) void { // 把索引 index 之后的所有元素向前移动一位 var i = index; while (i < nums.len - 1) : (i += 1) { nums[i] = nums[i + 1]; } } // 遍历数组 pub fn traverse(nums: []const i32) void { var count: i32 = 0; // 通过索引遍历数组 var i: usize = 0; while (i < nums.len) : (i += 1) { count += nums[i]; } // 直接遍历数组元素 count = 0; for (nums) |num| { count += num; } // 同时遍历数据索引和元素 for (nums, 0..) |num, index| { count += nums[index]; count += num; } } // 在数组中查找指定元素 pub fn find(nums: []i32, target: i32) i32 { for (nums, 0..) |num, i| { if (num == target) return @intCast(i); } return -1; } // Driver Code pub fn run() !void { // 初始化数组 const arr = [_]i32{0} ** 5; std.debug.print("数组 arr = {}\n", .{utils.fmt.slice(&arr)}); // 数组切片 var array = [_]i32{ 1, 3, 2, 5, 4 }; var known_at_runtime_zero: usize = 0; _ = &known_at_runtime_zero; var nums = array[known_at_runtime_zero..array.len]; // 通过 known_at_runtime_zero 运行时变量将指针变切片 std.debug.print("数组 nums = {}\n", .{utils.fmt.slice(nums)}); // 随机访问 const randomNum = randomAccess(nums); std.debug.print("在 nums 中获取随机元素 {}\n", .{randomNum}); // 初始化内存分配器 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); // 长度扩展 nums = try extend(allocator, nums, 3); std.debug.print("将数组长度扩展至 8 ,得到 nums = {}\n", .{utils.fmt.slice(nums)}); // 插入元素 insert(nums, 6, 3); std.debug.print("在索引 3 处插入数字 6 ,得到 nums = {}\n", .{utils.fmt.slice(nums)}); // 删除元素 remove(nums, 2); std.debug.print("删除索引 2 处的元素,得到 nums = {}\n", .{utils.fmt.slice(nums)}); // 遍历数组 traverse(nums); // 查找元素 const index = find(nums, 3); std.debug.print("在 nums 中查找元素 3 ,得到索引 = {}\n", .{index}); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "array" { try run(); } ================================================ FILE: codes/zig/chapter_array_and_linkedlist/linked_list.zig ================================================ // File: linked_list.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); const ListNode = utils.ListNode; // 在链表的节点 n0 之后插入节点 P pub fn insert(comptime T: type, n0: *ListNode(T), P: *ListNode(T)) void { const n1 = n0.next; P.next = n1; n0.next = P; } // 删除链表的节点 n0 之后的首个节点 pub fn remove(comptime T: type, n0: *ListNode(T)) void { // n0 -> P -> n1 => n0 -> n1 const P = n0.next; const n1 = P.?.next; n0.next = n1; } // 访问链表中索引为 index 的节点 pub fn access(comptime T: type, node: *ListNode(T), index: i32) ?*ListNode(T) { var head: ?*ListNode(T) = node; var i: i32 = 0; while (i < index) : (i += 1) { if (head) |cur| { head = cur.next; } else { return null; } } return head; } // 在链表中查找值为 target 的首个节点 pub fn find(comptime T: type, node: *ListNode(T), target: T) i32 { var head: ?*ListNode(T) = node; var index: i32 = 0; while (head) |cur| { if (cur.val == target) return index; head = cur.next; index += 1; } return -1; } // Driver Code pub fn run() void { // 初始化各个节点 var n0 = ListNode(i32){ .val = 1 }; var n1 = ListNode(i32){ .val = 3 }; var n2 = ListNode(i32){ .val = 2 }; var n3 = ListNode(i32){ .val = 5 }; var n4 = ListNode(i32){ .val = 4 }; // 构建节点之间的引用 n0.next = &n1; n1.next = &n2; n2.next = &n3; n3.next = &n4; std.debug.print( "初始化的链表为 {}\n", .{utils.fmt.linkedList(i32, &n0)}, ); // 插入节点 var tmp = ListNode(i32){ .val = 0 }; insert(i32, &n0, &tmp); std.debug.print( "插入节点后的链表为 {}\n", .{utils.fmt.linkedList(i32, &n0)}, ); // 删除节点 remove(i32, &n0); std.debug.print( "删除节点后的链表为{}\n", .{utils.fmt.linkedList(i32, &n0)}, ); // 访问节点 const node = access(i32, &n0, 3); std.debug.print( "链表中索引 3 处的节点的值 = {}\n", .{node.?.val}, ); // 查找节点 const index = find(i32, &n0, 2); std.debug.print( "链表中值为 2 的节点的索引 = {}\n", .{index}, ); std.debug.print("\n", .{}); } pub fn main() void { run(); } test "linked_list" { run(); } ================================================ FILE: codes/zig/chapter_array_and_linkedlist/list.zig ================================================ // File: list.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // Driver Code pub fn run() !void { // 初始化列表 var nums = std.ArrayList(i32).init(std.heap.page_allocator); defer nums.deinit(); // 延迟释放内存 try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); std.debug.print("列表 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 访问元素 const num = nums.items[1]; std.debug.print("访问索引 1 处的元素,得到 num = {}\n", .{num}); // 更新元素 nums.items[1] = 0; std.debug.print("将索引 1 处的元素更新为 0 ,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 清空列表 nums.clearRetainingCapacity(); std.debug.print("清空列表后 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 在尾部添加元素 try nums.append(1); try nums.append(3); try nums.append(2); try nums.append(5); try nums.append(4); std.debug.print("添加元素后 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 在中间插入元素 try nums.insert(3, 6); std.debug.print("在索引 3 处插入数字 6 ,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 删除元素 _ = nums.orderedRemove(3); std.debug.print("删除索引 3 处的元素,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 通过索引遍历列表 var count: i32 = 0; var i: usize = 0; while (i < nums.items.len) : (i += 1) { count += nums.items[i]; } // 直接遍历列表元素 count = 0; for (nums.items) |x| { count += x; } // 拼接两个列表 var nums1 = std.ArrayList(i32).init(std.heap.page_allocator); defer nums1.deinit(); try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); try nums.insertSlice(nums.items.len, nums1.items); std.debug.print("将列表 nums1 拼接到 nums 之后,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 排序列表 std.mem.sort(i32, nums.items, {}, comptime std.sort.asc(i32)); std.debug.print("排序列表后 nums = {}\n", .{utils.fmt.slice(nums.items)}); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "list" { try run(); } ================================================ FILE: codes/zig/chapter_array_and_linkedlist/my_list.zig ================================================ // File: my_list.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // 列表类 const MyList = struct { const Self = @This(); items: []i32, // 数组(存储列表元素) capacity: usize, // 列表容量 allocator: std.mem.Allocator, // 内存分配器 extend_ratio: usize = 2, // 每次列表扩容的倍数 // 构造函数(分配内存+初始化列表) pub fn init(allocator: std.mem.Allocator) Self { return Self{ .items = &[_]i32{}, .capacity = 0, .allocator = allocator, }; } // 析构函数(释放内存) pub fn deinit(self: Self) void { self.allocator.free(self.allocatedSlice()); } // 在尾部添加元素 pub fn add(self: *Self, item: i32) !void { // 元素数量超出容量时,触发扩容机制 const newlen = self.items.len + 1; try self.ensureTotalCapacity(newlen); // 更新元素 self.items.len += 1; const new_item_ptr = &self.items[self.items.len - 1]; new_item_ptr.* = item; } // 获取列表长度(当前元素数量) pub fn getSize(self: *Self) usize { return self.items.len; } // 获取列表容量 pub fn getCapacity(self: *Self) usize { return self.capacity; } // 访问元素 pub fn get(self: *Self, index: usize) i32 { // 索引如果越界,则抛出异常,下同 if (index < 0 or index >= self.items.len) { @panic("索引越界"); } return self.items[index]; } // 更新元素 pub fn set(self: *Self, index: usize, num: i32) void { // 索引如果越界,则抛出异常,下同 if (index < 0 or index >= self.items.len) { @panic("索引越界"); } self.items[index] = num; } // 在中间插入元素 pub fn insert(self: *Self, index: usize, item: i32) !void { if (index < 0 or index >= self.items.len) { @panic("索引越界"); } // 元素数量超出容量时,触发扩容机制 const newlen = self.items.len + 1; try self.ensureTotalCapacity(newlen); // 将索引 index 以及之后的元素都向后移动一位 self.items.len += 1; var i = self.items.len - 1; while (i >= index) : (i -= 1) { self.items[i] = self.items[i - 1]; } self.items[index] = item; } // 删除元素 pub fn remove(self: *Self, index: usize) i32 { if (index < 0 or index >= self.getSize()) { @panic("索引越界"); } // 将索引 index 之后的元素都向前移动一位 const item = self.items[index]; var i = index; while (i < self.items.len - 1) : (i += 1) { self.items[i] = self.items[i + 1]; } self.items.len -= 1; // 返回被删除的元素 return item; } // 将列表转换为数组 pub fn toArraySlice(self: *Self) ![]i32 { return self.toOwnedSlice(false); } // 返回新的切片并设置是否要重置或清空列表容器 pub fn toOwnedSlice(self: *Self, clear: bool) ![]i32 { const allocator = self.allocator; const old_memory = self.allocatedSlice(); if (allocator.remap(old_memory, self.items.len)) |new_items| { if (clear) { self.* = init(allocator); } return new_items; } const new_memory = try allocator.alloc(i32, self.items.len); @memcpy(new_memory, self.items); if (clear) { self.clearAndFree(); } return new_memory; } // 列表扩容 fn ensureTotalCapacity(self: *Self, new_capacity: usize) !void { if (self.capacity >= new_capacity) return; const capcacity = if (self.capacity == 0) 10 else self.capacity; const better_capacity = capcacity * self.extend_ratio; const old_memory = self.allocatedSlice(); if (self.allocator.remap(old_memory, better_capacity)) |new_memory| { self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } else { const new_memory = try self.allocator.alloc(i32, better_capacity); @memcpy(new_memory[0..self.items.len], self.items); self.allocator.free(old_memory); self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } } fn clearAndFree(self: *Self, allocator: std.mem.Allocator) void { allocator.free(self.allocatedSlice()); self.items.len = 0; self.capacity = 0; } fn allocatedSlice(self: Self) []i32 { return self.items.ptr[0..self.capacity]; } }; // Driver Code pub fn run() !void { var gpa = std.heap.DebugAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // 初始化列表 var nums = MyList.init(allocator); // 延迟释放内存 defer nums.deinit(); // 在尾部添加元素 try nums.add(1); try nums.add(3); try nums.add(2); try nums.add(5); try nums.add(4); std.debug.print("列表 nums = {} ,容量 = {} ,长度 = {}\n", .{ utils.fmt.slice(nums.items), nums.getCapacity(), nums.getSize(), }); // 在中间插入元素 try nums.insert(3, 6); std.debug.print( "在索引 3 处插入数字 6 ,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}, ); // 删除元素 _ = nums.remove(3); std.debug.print( "删除索引 3 处的元素,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}, ); // 访问元素 const num = nums.get(1); std.debug.print("访问索引 1 处的元素,得到 num = {}\n", .{num}); // 更新元素 nums.set(1, 0); std.debug.print( "将索引 1 处的元素更新为 0 ,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}, ); // 测试扩容机制 var i: i32 = 0; while (i < 10) : (i += 1) { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 try nums.add(i); } std.debug.print( "扩容后的列表 nums = {} ,容量 = {} ,长度 = {}\n", .{ utils.fmt.slice(nums.items), nums.getCapacity(), nums.getSize(), }, ); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "my_list" { try run(); } ================================================ FILE: codes/zig/chapter_computational_complexity/iteration.zig ================================================ // File: iteration.zig // Created Time: 2023-09-27 // Author: QiLOL (pikaqqpika@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const Allocator = std.mem.Allocator; // for 循环 fn forLoop(n: usize) i32 { var res: i32 = 0; // 循环求和 1, 2, ..., n-1, n for (1..n + 1) |i| { res += @intCast(i); } return res; } // while 循环 fn whileLoop(n: i32) i32 { var res: i32 = 0; var i: i32 = 1; // 初始化条件变量 // 循环求和 1, 2, ..., n-1, n while (i <= n) : (i += 1) { res += @intCast(i); } return res; } // while 循环(两次更新) fn whileLoopII(n: i32) i32 { var res: i32 = 0; var i: i32 = 1; // 初始化条件变量 // 循环求和 1, 4, 10, ... while (i <= n) : ({ // 更新条件变量 i += 1; i *= 2; }) { res += @intCast(i); } return res; } // 双层 for 循环 fn nestedForLoop(allocator: Allocator, n: usize) ![]const u8 { var res = std.ArrayList(u8).init(allocator); defer res.deinit(); var buffer: [20]u8 = undefined; // 循环 i = 1, 2, ..., n-1, n for (1..n + 1) |i| { // 循环 j = 1, 2, ..., n-1, n for (1..n + 1) |j| { const str = try std.fmt.bufPrint(&buffer, "({d}, {d}), ", .{ i, j }); try res.appendSlice(str); } } return res.toOwnedSlice(); } // Driver Code pub fn run() !void { var gpa = std.heap.DebugAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const n: i32 = 5; var res: i32 = 0; res = forLoop(n); std.debug.print("for 循环的求和结果 res = {}\n", .{res}); res = whileLoop(n); std.debug.print("while 循环的求和结果 res = {}\n", .{res}); res = whileLoopII(n); std.debug.print("while 循环(两次更新)求和结果 res = {}\n", .{res}); const resStr = try nestedForLoop(allocator, n); std.debug.print("双层 for 循环的遍历结果 {s}\n", .{resStr}); allocator.free(resStr); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "interation" { try run(); } ================================================ FILE: codes/zig/chapter_computational_complexity/recursion.zig ================================================ // File: recursion.zig // Created Time: 2023-09-27 // Author: QiLOL (pikaqqpika@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // 递归函数 fn recur(n: i32) i32 { // 终止条件 if (n == 1) { return 1; } // 递:递归调用 const res = recur(n - 1); // 归:返回结果 return n + res; } // 使用迭代模拟递归 fn forLoopRecur(comptime n: i32) i32 { // 使用一个显式的栈来模拟系统调用栈 var stack: [n]i32 = undefined; var res: i32 = 0; // 递:递归调用 var i: usize = n; while (i > 0) { stack[i - 1] = @intCast(i); i -= 1; } // 归:返回结果 var index: usize = n; while (index > 0) { index -= 1; res += stack[index]; } // res = 1+2+3+...+n return res; } // 尾递归函数 fn tailRecur(n: i32, res: i32) i32 { // 终止条件 if (n == 0) { return res; } // 尾递归调用 return tailRecur(n - 1, res + n); } // 斐波那契数列 fn fib(n: i32) i32 { // 终止条件 f(1) = 0, f(2) = 1 if (n == 1 or n == 2) { return n - 1; } // 递归调用 f(n) = f(n-1) + f(n-2) const res: i32 = fib(n - 1) + fib(n - 2); // 返回结果 f(n) return res; } // Driver Code pub fn run() void { const n: i32 = 5; var res: i32 = 0; res = recur(n); std.debug.print("递归函数的求和结果 res = {}\n", .{recur(n)}); res = forLoopRecur(n); std.debug.print("使用迭代模拟递归的求和结果 res = {}\n", .{forLoopRecur(n)}); res = tailRecur(n, 0); std.debug.print("尾递归函数的求和结果 res = {}\n", .{tailRecur(n, 0)}); res = fib(n); std.debug.print("斐波那契数列的第 {} 项为 {}\n", .{ n, fib(n) }); std.debug.print("\n", .{}); } pub fn main() void { run(); } test "recursion" { run(); } ================================================ FILE: codes/zig/chapter_computational_complexity/space_complexity.zig ================================================ // File: space_complexity.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); const ListNode = utils.ListNode; const TreeNode = utils.TreeNode; // 函数 fn function() i32 { // 执行某些操作 return 0; } // 常数阶 fn constant(n: i32) void { // 常量、变量、对象占用 O(1) 空间 const a: i32 = 0; const b: i32 = 0; const nums = [_]i32{0} ** 10000; const node = ListNode(i32){ .val = 0 }; var i: i32 = 0; // 循环中的变量占用 O(1) 空间 while (i < n) : (i += 1) { const c: i32 = 0; _ = c; } // 循环中的函数占用 O(1) 空间 i = 0; while (i < n) : (i += 1) { _ = function(); } _ = a; _ = b; _ = nums; _ = node; } // 线性阶 fn linear(comptime n: i32) !void { // 长度为 n 的数组占用 O(n) 空间 const nums = [_]i32{0} ** n; // 长度为 n 的列表占用 O(n) 空间 var nodes = std.ArrayList(i32).init(std.heap.page_allocator); defer nodes.deinit(); var i: i32 = 0; while (i < n) : (i += 1) { try nodes.append(i); } // 长度为 n 的哈希表占用 O(n) 空间 var map = std.AutoArrayHashMap(i32, []const u8).init(std.heap.page_allocator); defer map.deinit(); var j: i32 = 0; while (j < n) : (j += 1) { const string = try std.fmt.allocPrint(std.heap.page_allocator, "{d}", .{j}); defer std.heap.page_allocator.free(string); try map.put(i, string); } _ = nums; } // 线性阶(递归实现) fn linearRecur(comptime n: i32) void { std.debug.print("递归 n = {}\n", .{n}); if (n == 1) return; linearRecur(n - 1); } // 平方阶 fn quadratic(n: i32) !void { // 二维列表占用 O(n^2) 空间 var nodes = std.ArrayList(std.ArrayList(i32)).init(std.heap.page_allocator); defer nodes.deinit(); var i: i32 = 0; while (i < n) : (i += 1) { var tmp = std.ArrayList(i32).init(std.heap.page_allocator); defer tmp.deinit(); var j: i32 = 0; while (j < n) : (j += 1) { try tmp.append(0); } try nodes.append(tmp); } } // 平方阶(递归实现) fn quadraticRecur(comptime n: i32) i32 { if (n <= 0) return 0; const nums = [_]i32{0} ** n; std.debug.print("递归 n = {} 中的 nums 长度 = {}\n", .{ n, nums.len }); return quadraticRecur(n - 1); } // 指数阶(建立满二叉树) fn buildTree(allocator: std.mem.Allocator, n: i32) !?*TreeNode(i32) { if (n == 0) return null; const root = try allocator.create(TreeNode(i32)); root.init(0); root.left = try buildTree(allocator, n - 1); root.right = try buildTree(allocator, n - 1); return root; } // 释放树的内存 fn freeTree(allocator: std.mem.Allocator, root: ?*const TreeNode(i32)) void { if (root == null) return; freeTree(allocator, root.?.left); freeTree(allocator, root.?.right); allocator.destroy(root.?); } // Driver Code pub fn run() !void { var gpa = std.heap.DebugAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const n: i32 = 5; // 常数阶 constant(n); // 线性阶 try linear(n); linearRecur(n); // 平方阶 try quadratic(n); _ = quadraticRecur(n); // 指数阶 const root = try buildTree(allocator, n); defer freeTree(allocator, root); std.debug.print("{}\n", .{utils.fmt.tree(i32, root)}); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "space_complexity" { try run(); } ================================================ FILE: codes/zig/chapter_computational_complexity/time_complexity.zig ================================================ // File: time_complexity.zig // Created Time: 2022-12-28 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // 常数阶 fn constant(n: i32) i32 { _ = n; var count: i32 = 0; const size: i32 = 100_000; var i: i32 = 0; while (i < size) : (i += 1) { count += 1; } return count; } // 线性阶 fn linear(n: i32) i32 { var count: i32 = 0; var i: i32 = 0; while (i < n) : (i += 1) { count += 1; } return count; } // 线性阶(遍历数组) fn arrayTraversal(nums: []i32) i32 { var count: i32 = 0; // 循环次数与数组长度成正比 for (nums) |_| { count += 1; } return count; } // 平方阶 fn quadratic(n: i32) i32 { var count: i32 = 0; var i: i32 = 0; // 循环次数与数据大小 n 成平方关系 while (i < n) : (i += 1) { var j: i32 = 0; while (j < n) : (j += 1) { count += 1; } } return count; } // 平方阶(冒泡排序) fn bubbleSort(nums: []i32) i32 { var count: i32 = 0; // 计数器 // 外循环:未排序区间为 [0, i] var i: i32 = @as(i32, @intCast(nums.len)) - 1; while (i > 0) : (i -= 1) { var j: usize = 0; // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] const tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 元素交换包含 3 个单元操作 } } } return count; } // 指数阶(循环实现) fn exponential(n: i32) i32 { var count: i32 = 0; var bas: i32 = 1; var i: i32 = 0; // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) while (i < n) : (i += 1) { var j: i32 = 0; while (j < bas) : (j += 1) { count += 1; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } // 指数阶(递归实现) fn expRecur(n: i32) i32 { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } // 对数阶(循环实现) fn logarithmic(n: i32) i32 { var count: i32 = 0; var n_var: i32 = n; while (n_var > 1) : (n_var = @divTrunc(n_var, 2)) { count += 1; } return count; } // 对数阶(递归实现) fn logRecur(n: i32) i32 { if (n <= 1) return 0; return logRecur(@divTrunc(n, 2)) + 1; } // 线性对数阶 fn linearLogRecur(n: i32) i32 { if (n <= 1) return 1; var count: i32 = linearLogRecur(@divTrunc(n, 2)) + linearLogRecur(@divTrunc(n, 2)); var i: i32 = 0; while (i < n) : (i += 1) { count += 1; } return count; } // 阶乘阶(递归实现) fn factorialRecur(n: i32) i32 { if (n == 0) return 1; var count: i32 = 0; var i: i32 = 0; // 从 1 个分裂出 n 个 while (i < n) : (i += 1) { count += factorialRecur(n - 1); } return count; } // Driver Code pub fn run() void { // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 const n: i32 = 8; std.debug.print("输入数据大小 n = {}\n", .{n}); var count = constant(n); std.debug.print("常数阶的操作数量 = {}\n", .{count}); count = linear(n); std.debug.print("线性阶的操作数量 = {}\n", .{count}); var nums = [_]i32{0} ** n; count = arrayTraversal(&nums); std.debug.print("线性阶(遍历数组)的操作数量 = {}\n", .{count}); count = quadratic(n); std.debug.print("平方阶的操作数量 = {}\n", .{count}); for (&nums, 0..) |*num, i| { num.* = n - @as(i32, @intCast(i)); // [n,n-1,...,2,1] } count = bubbleSort(&nums); std.debug.print("平方阶(冒泡排序)的操作数量 = {}\n", .{count}); count = exponential(n); std.debug.print("指数阶(循环实现)的操作数量 = {}\n", .{count}); count = expRecur(n); std.debug.print("指数阶(递归实现)的操作数量 = {}\n", .{count}); count = logarithmic(n); std.debug.print("对数阶(循环实现)的操作数量 = {}\n", .{count}); count = logRecur(n); std.debug.print("对数阶(递归实现)的操作数量 = {}\n", .{count}); count = linearLogRecur(n); std.debug.print("线性对数阶(递归实现)的操作数量 = {}\n", .{count}); count = factorialRecur(n); std.debug.print("阶乘阶(递归实现)的操作数量 = {}\n", .{count}); std.debug.print("\n", .{}); } pub fn main() !void { run(); } test "time_complexity" { run(); } ================================================ FILE: codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig ================================================ // File: worst_best_time_complexity.zig // Created Time: 2022-12-28 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 pub fn randomNumbers(comptime n: usize) [n]i32 { var nums: [n]i32 = undefined; // 生成数组 nums = { 1, 2, 3, ..., n } for (&nums, 0..) |*num, i| { num.* = @as(i32, @intCast(i)) + 1; } // 随机打乱数组元素 const rand = std.crypto.random; rand.shuffle(i32, &nums); return nums; } // 查找数组 nums 中数字 1 所在索引 pub fn findOne(nums: []i32) i32 { for (nums, 0..) |num, i| { // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if (num == 1) return @intCast(i); } return -1; } // Driver Code pub fn run() void { var i: i32 = 0; while (i < 10) : (i += 1) { const n: usize = 100; var nums = randomNumbers(n); const index = findOne(&nums); std.debug.print("数组 [ 1, 2, ..., n ] 被打乱后 = ", .{}); std.debug.print("{}\n", .{utils.fmt.slice(nums)}); std.debug.print("数字 1 的索引为 {}\n", .{index}); } std.debug.print("\n", .{}); } pub fn main() !void { run(); } test "worst_best_time_complexity" { run(); } ================================================ FILE: codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig ================================================ // File: climbing_stairs_backtrack.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 回溯 fn backtrack(choices: []i32, state: i32, n: i32, res: std.ArrayList(i32)) void { // 当爬到第 n 阶时,方案数量加 1 if (state == n) { res.items[0] = res.items[0] + 1; } // 遍历所有选择 for (choices) |choice| { // 剪枝:不允许越过第 n 阶 if (state + choice > n) { continue; } // 尝试:做出选择,更新状态 backtrack(choices, state + choice, n, res); // 回退 } } // 爬楼梯:回溯 fn climbingStairsBacktrack(n: usize) !i32 { var choices = [_]i32{ 1, 2 }; // 可选择向上爬 1 阶或 2 阶 var state: i32 = 0; // 从第 0 阶开始爬 var res = std.ArrayList(i32).init(std.heap.page_allocator); defer res.deinit(); try res.append(0); // 使用 res[0] 记录方案数量 backtrack(&choices, state, @intCast(n), res); return res.items[0]; } // Driver Code pub fn main() !void { var n: usize = 9; var res = try climbingStairsBacktrack(n); std.debug.print("爬 {} 阶楼梯共有 {} 种方案\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig ================================================ // File: climbing_stairs_constraint_dp.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 带约束爬楼梯:动态规划 fn climbingStairsConstraintDP(comptime n: usize) i32 { if (n == 1 or n == 2) { return 1; } // 初始化 dp 表,用于存储子问题的解 var dp = [_][3]i32{ [_]i32{ -1, -1, -1 } } ** (n + 1); // 初始状态:预设最小子问题的解 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 状态转移:从较小子问题逐步求解较大子问题 for (3..n + 1) |i| { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsConstraintDP(n); std.debug.print("爬 {} 阶楼梯共有 {} 种方案\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig ================================================ // File: climbing_stairs_dfs.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 搜索 fn dfs(i: usize) i32 { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 or i == 2) { return @intCast(i); } // dp[i] = dp[i-1] + dp[i-2] var count = dfs(i - 1) + dfs(i - 2); return count; } // 爬楼梯:搜索 fn climbingStairsDFS(comptime n: usize) i32 { return dfs(n); } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsDFS(n); std.debug.print("爬 {} 阶楼梯共有 {} 种方案\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig ================================================ // File: climbing_stairs_dfs_mem.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 记忆化搜索 fn dfs(i: usize, mem: []i32) i32 { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 or i == 2) { return @intCast(i); } // 若存在记录 dp[i] ,则直接返回之 if (mem[i] != -1) { return mem[i]; } // dp[i] = dp[i-1] + dp[i-2] var count = dfs(i - 1, mem) + dfs(i - 2, mem); // 记录 dp[i] mem[i] = count; return count; } // 爬楼梯:记忆化搜索 fn climbingStairsDFSMem(comptime n: usize) i32 { // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 var mem = [_]i32{ -1 } ** (n + 1); return dfs(n, &mem); } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsDFSMem(n); std.debug.print("爬 {} 阶楼梯共有 {} 种方案\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig ================================================ // File: climbing_stairs_dp.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 爬楼梯:动态规划 fn climbingStairsDP(comptime n: usize) i32 { // 已知 dp[1] 和 dp[2] ,返回之 if (n == 1 or n == 2) { return @intCast(n); } // 初始化 dp 表,用于存储子问题的解 var dp = [_]i32{-1} ** (n + 1); // 初始状态:预设最小子问题的解 dp[1] = 1; dp[2] = 2; // 状态转移:从较小子问题逐步求解较大子问题 for (3..n + 1) |i| { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } // 爬楼梯:空间优化后的动态规划 fn climbingStairsDPComp(comptime n: usize) i32 { if (n == 1 or n == 2) { return @intCast(n); } var a: i32 = 1; var b: i32 = 2; for (3..n + 1) |_| { var tmp = b; b = a + b; a = tmp; } return b; } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsDP(n); std.debug.print("爬 {} 阶楼梯共有 {} 种方案\n", .{ n, res }); res = climbingStairsDPComp(n); std.debug.print("爬 {} 阶楼梯共有 {} 种方案\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_dynamic_programming/coin_change.zig ================================================ // File: coin_change.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 零钱兑换:动态规划 fn coinChangeDP(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; comptime var max = amt + 1; // 初始化 dp 表 var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); // 状态转移:首行首列 for (1..amt + 1) |a| { dp[0][a] = max; } // 状态转移:其余行和列 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = @min(dp[i - 1][a], dp[i][a - @as(usize, @intCast(coins[i - 1]))] + 1); } } } if (dp[n][amt] != max) { return @intCast(dp[n][amt]); } else { return -1; } } // 零钱兑换:空间优化后的动态规划 fn coinChangeDPComp(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; comptime var max = amt + 1; // 初始化 dp 表 var dp = [_]i32{0} ** (amt + 1); @memset(&dp, max); dp[0] = 0; // 状态转移 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = @min(dp[a], dp[a - @as(usize, @intCast(coins[i - 1]))] + 1); } } } if (dp[amt] != max) { return @intCast(dp[amt]); } else { return -1; } } // Driver Code pub fn main() !void { comptime var coins = [_]i32{ 1, 2, 5 }; comptime var amt: usize = 4; // 动态规划 var res = coinChangeDP(&coins, amt); std.debug.print("凑到目标金额所需的最少硬币数量为 {}\n", .{res}); // 空间优化后的动态规划 res = coinChangeDPComp(&coins, amt); std.debug.print("凑到目标金额所需的最少硬币数量为 {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_dynamic_programming/coin_change_ii.zig ================================================ // File: coin_change_ii.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 零钱兑换 II:动态规划 fn coinChangeIIDP(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; // 初始化 dp 表 var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); // 初始化首列 for (0..n + 1) |i| { dp[i][0] = 1; } // 状态转移 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = dp[i - 1][a] + dp[i][a - @as(usize, @intCast(coins[i - 1]))]; } } } return dp[n][amt]; } // 零钱兑换 II:空间优化后的动态规划 fn coinChangeIIDPComp(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; // 初始化 dp 表 var dp = [_]i32{0} ** (amt + 1); dp[0] = 1; // 状态转移 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = dp[a] + dp[a - @as(usize, @intCast(coins[i - 1]))]; } } } return dp[amt]; } // Driver Code pub fn main() !void { comptime var coins = [_]i32{ 1, 2, 5 }; comptime var amt: usize = 5; // 动态规划 var res = coinChangeIIDP(&coins, amt); std.debug.print("凑出目标金额的硬币组合数量为 {}\n", .{res}); // 空间优化后的动态规划 res = coinChangeIIDPComp(&coins, amt); std.debug.print("凑出目标金额的硬币组合数量为 {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_dynamic_programming/edit_distance.zig ================================================ // File: edit_distance.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 编辑距离:暴力搜索 fn editDistanceDFS(comptime s: []const u8, comptime t: []const u8, i: usize, j: usize) i32 { // 若 s 和 t 都为空,则返回 0 if (i == 0 and j == 0) { return 0; } // 若 s 为空,则返回 t 长度 if (i == 0) { return @intCast(j); } // 若 t 为空,则返回 s 长度 if (j == 0) { return @intCast(i); } // 若两字符相等,则直接跳过此两字符 if (s[i - 1] == t[j - 1]) { return editDistanceDFS(s, t, i - 1, j - 1); } // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 var insert = editDistanceDFS(s, t, i, j - 1); var delete = editDistanceDFS(s, t, i - 1, j); var replace = editDistanceDFS(s, t, i - 1, j - 1); // 返回最少编辑步数 return @min(@min(insert, delete), replace) + 1; } // 编辑距离:记忆化搜索 fn editDistanceDFSMem(comptime s: []const u8, comptime t: []const u8, mem: anytype, i: usize, j: usize) i32 { // 若 s 和 t 都为空,则返回 0 if (i == 0 and j == 0) { return 0; } // 若 s 为空,则返回 t 长度 if (i == 0) { return @intCast(j); } // 若 t 为空,则返回 s 长度 if (j == 0) { return @intCast(i); } // 若已有记录,则直接返回之 if (mem[i][j] != -1) { return mem[i][j]; } // 若两字符相等,则直接跳过此两字符 if (s[i - 1] == t[j - 1]) { return editDistanceDFSMem(s, t, mem, i - 1, j - 1); } // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 var insert = editDistanceDFSMem(s, t, mem, i, j - 1); var delete = editDistanceDFSMem(s, t, mem, i - 1, j); var replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 记录并返回最少编辑步数 mem[i][j] = @min(@min(insert, delete), replace) + 1; return mem[i][j]; } // 编辑距离:动态规划 fn editDistanceDP(comptime s: []const u8, comptime t: []const u8) i32 { comptime var n = s.len; comptime var m = t.len; var dp = [_][m + 1]i32{[_]i32{0} ** (m + 1)} ** (n + 1); // 状态转移:首行首列 for (1..n + 1) |i| { dp[i][0] = @intCast(i); } for (1..m + 1) |j| { dp[0][j] = @intCast(j); } // 状态转移:其余行和列 for (1..n + 1) |i| { for (1..m + 1) |j| { if (s[i - 1] == t[j - 1]) { // 若两字符相等,则直接跳过此两字符 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[i][j] = @min(@min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } // 编辑距离:空间优化后的动态规划 fn editDistanceDPComp(comptime s: []const u8, comptime t: []const u8) i32 { comptime var n = s.len; comptime var m = t.len; var dp = [_]i32{0} ** (m + 1); // 状态转移:首行 for (1..m + 1) |j| { dp[j] = @intCast(j); } // 状态转移:其余行 for (1..n + 1) |i| { // 状态转移:首列 var leftup = dp[0]; // 暂存 dp[i-1, j-1] dp[0] = @intCast(i); // 状态转移:其余列 for (1..m + 1) |j| { var temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 若两字符相等,则直接跳过此两字符 dp[j] = leftup; } else { // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 dp[j] = @min(@min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 更新为下一轮的 dp[i-1, j-1] } } return dp[m]; } // Driver Code pub fn main() !void { const s = "bag"; const t = "pack"; comptime var n = s.len; comptime var m = t.len; // 暴力搜索 var res = editDistanceDFS(s, t, n, m); std.debug.print("将 {s} 更改为 {s} 最少需要编辑 {} 步\n", .{ s, t, res }); // 记忆搜索 var mem = [_][m + 1]i32{[_]i32{-1} ** (m + 1)} ** (n + 1); res = editDistanceDFSMem(s, t, @constCast(&mem), n, m); std.debug.print("将 {s} 更改为 {s} 最少需要编辑 {} 步\n", .{ s, t, res }); // 动态规划 res = editDistanceDP(s, t); std.debug.print("将 {s} 更改为 {s} 最少需要编辑 {} 步\n", .{ s, t, res }); // 空间优化后的动态规划 res = editDistanceDPComp(s, t); std.debug.print("将 {s} 更改为 {s} 最少需要编辑 {} 步\n", .{ s, t, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_dynamic_programming/knapsack.zig ================================================ // File: knapsack.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 0-1 背包:暴力搜索 fn knapsackDFS(wgt: []i32, val: []i32, i: usize, c: usize) i32 { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i == 0 or c == 0) { return 0; } // 若超过背包容量,则只能选择不放入背包 if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 var no = knapsackDFS(wgt, val, i - 1, c); var yes = knapsackDFS(wgt, val, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; // 返回两种方案中价值更大的那一个 return @max(no, yes); } // 0-1 背包:记忆化搜索 fn knapsackDFSMem(wgt: []i32, val: []i32, mem: anytype, i: usize, c: usize) i32 { // 若已选完所有物品或背包无剩余容量,则返回价值 0 if (i == 0 or c == 0) { return 0; } // 若已有记录,则直接返回 if (mem[i][c] != -1) { return mem[i][c]; } // 若超过背包容量,则只能选择不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 计算不放入和放入物品 i 的最大价值 var no = knapsackDFSMem(wgt, val, mem, i - 1, c); var yes = knapsackDFSMem(wgt, val, mem, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; // 记录并返回两种方案中价值更大的那一个 mem[i][c] = @max(no, yes); return mem[i][c]; } // 0-1 背包:动态规划 fn knapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // 初始化 dp 表 var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); // 状态转移 for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = @max(dp[i - 1][c], dp[i - 1][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[n][cap]; } // 0-1 背包:空间优化后的动态规划 fn knapsackDPComp(wgt: []i32, val: []i32, comptime cap: usize) i32 { var n = wgt.len; // 初始化 dp 表 var dp = [_]i32{0} ** (cap + 1); // 状态转移 for (1..n + 1) |i| { // 倒序遍历 var c = cap; while (c > 0) : (c -= 1) { if (wgt[i - 1] < c) { // 不选和选物品 i 这两种方案的较大值 dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[cap]; } // Driver Code pub fn main() !void { comptime var wgt = [_]i32{ 10, 20, 30, 40, 50 }; comptime var val = [_]i32{ 50, 120, 150, 210, 240 }; comptime var cap = 50; comptime var n = wgt.len; // 暴力搜索 var res = knapsackDFS(&wgt, &val, n, cap); std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res}); // 记忆搜索 var mem = [_][cap + 1]i32{[_]i32{-1} ** (cap + 1)} ** (n + 1); res = knapsackDFSMem(&wgt, &val, @constCast(&mem), n, cap); std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res}); // 动态规划 res = knapsackDP(&wgt, &val, cap); std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res}); // 空间优化后的动态规划 res = knapsackDPComp(&wgt, &val, cap); std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig ================================================ // File: min_cost_climbing_stairs_dp.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 爬楼梯最小代价:动态规划 fn minCostClimbingStairsDP(comptime cost: []i32) i32 { comptime var n = cost.len - 1; if (n == 1 or n == 2) { return cost[n]; } // 初始化 dp 表,用于存储子问题的解 var dp = [_]i32{-1} ** (n + 1); // 初始状态:预设最小子问题的解 dp[1] = cost[1]; dp[2] = cost[2]; // 状态转移:从较小子问题逐步求解较大子问题 for (3..n + 1) |i| { dp[i] = @min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } // 爬楼梯最小代价:空间优化后的动态规划 fn minCostClimbingStairsDPComp(cost: []i32) i32 { var n = cost.len - 1; if (n == 1 or n == 2) { return cost[n]; } var a = cost[1]; var b = cost[2]; // 状态转移:从较小子问题逐步求解较大子问题 for (3..n + 1) |i| { var tmp = b; b = @min(a, tmp) + cost[i]; a = tmp; } return b; } // Driver Code pub fn main() !void { comptime var cost = [_]i32{ 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; std.debug.print("输入楼梯的代价列表为 {any}\n", .{cost}); var res = minCostClimbingStairsDP(&cost); std.debug.print("输入楼梯的代价列表为 {}\n", .{res}); res = minCostClimbingStairsDPComp(&cost); std.debug.print("输入楼梯的代价列表为 {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_dynamic_programming/min_path_sum.zig ================================================ // File: min_path_sum.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 最小路径和:暴力搜索 fn minPathSumDFS(grid: anytype, i: i32, j: i32) i32 { // 若为左上角单元格,则终止搜索 if (i == 0 and j == 0) { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 or j < 0) { return std.math.maxInt(i32); } // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 var up = minPathSumDFS(grid, i - 1, j); var left = minPathSumDFS(grid, i, j - 1); // 返回从左上角到 (i, j) 的最小路径代价 return @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; } // 最小路径和:记忆化搜索 fn minPathSumDFSMem(grid: anytype, mem: anytype, i: i32, j: i32) i32 { // 若为左上角单元格,则终止搜索 if (i == 0 and j == 0) { return grid[0][0]; } // 若行列索引越界,则返回 +∞ 代价 if (i < 0 or j < 0) { return std.math.maxInt(i32); } // 若已有记录,则直接返回 if (mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] != -1) { return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; } // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 var up = minPathSumDFSMem(grid, mem, i - 1, j); var left = minPathSumDFSMem(grid, mem, i, j - 1); // 返回从左上角到 (i, j) 的最小路径代价 // 记录并返回左上角到 (i, j) 的最小路径代价 mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] = @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; } // 最小路径和:动态规划 fn minPathSumDP(comptime grid: anytype) i32 { comptime var n = grid.len; comptime var m = grid[0].len; // 初始化 dp 表 var dp = [_][m]i32{[_]i32{0} ** m} ** n; dp[0][0] = grid[0][0]; // 状态转移:首行 for (1..m) |j| { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 状态转移:首列 for (1..n) |i| { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 状态转移:其余行和列 for (1..n) |i| { for (1..m) |j| { dp[i][j] = @min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } // 最小路径和:空间优化后的动态规划 fn minPathSumDPComp(comptime grid: anytype) i32 { comptime var n = grid.len; comptime var m = grid[0].len; // 初始化 dp 表 var dp = [_]i32{0} ** m; // 状态转移:首行 dp[0] = grid[0][0]; for (1..m) |j| { dp[j] = dp[j - 1] + grid[0][j]; } // 状态转移:其余行 for (1..n) |i| { // 状态转移:首列 dp[0] = dp[0] + grid[i][0]; for (1..m) |j| { dp[j] = @min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } // Driver Code pub fn main() !void { comptime var grid = [_][4]i32{ [_]i32{ 1, 3, 1, 5 }, [_]i32{ 2, 2, 4, 2 }, [_]i32{ 5, 3, 2, 1 }, [_]i32{ 4, 3, 5, 2 }, }; comptime var n = grid.len; comptime var m = grid[0].len; // 暴力搜索 var res = minPathSumDFS(&grid, n - 1, m - 1); std.debug.print("从左上角到右下角的最小路径和为 {}\n", .{res}); // 记忆化搜索 var mem = [_][m]i32{[_]i32{-1} ** m} ** n; res = minPathSumDFSMem(&grid, &mem, n - 1, m - 1); std.debug.print("从左上角到右下角的最小路径和为 {}\n", .{res}); // 动态规划 res = minPathSumDP(&grid); std.debug.print("从左上角到右下角的最小路径和为 {}\n", .{res}); // 空间优化后的动态规划 res = minPathSumDPComp(&grid); std.debug.print("从左上角到右下角的最小路径和为 {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig ================================================ // File: unbounded_knapsack.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 完全背包:动态规划 fn unboundedKnapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // 初始化 dp 表 var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); // 状态转移 for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = @max(dp[i - 1][c], dp[i][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[n][cap]; } // 完全背包:空间优化后的动态规划 fn unboundedKnapsackDPComp(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // 初始化 dp 表 var dp = [_]i32{0} ** (cap + 1); // 状态转移 for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[cap]; } // Driver Code pub fn main() !void { comptime var wgt = [_]i32{ 1, 2, 3 }; comptime var val = [_]i32{ 5, 11, 15 }; comptime var cap = 4; // 动态规划 var res = unboundedKnapsackDP(&wgt, &val, cap); std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res}); // 空间优化后的动态规划 res = unboundedKnapsackDPComp(&wgt, &val, cap); std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_hashing/array_hash_map.zig ================================================ // File: array_hash_map.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 键值对 const Pair = struct { key: usize = undefined, val: []const u8 = undefined, pub fn init(key: usize, val: []const u8) Pair { return Pair { .key = key, .val = val, }; } }; // 基于数组实现的哈希表 pub fn ArrayHashMap(comptime T: type) type { return struct { bucket: ?std.ArrayList(?T) = null, mem_allocator: std.mem.Allocator = undefined, const Self = @This(); // 构造函数 pub fn init(self: *Self, allocator: std.mem.Allocator) !void { self.mem_allocator = allocator; // 初始化一个长度为 100 的桶(数组) self.bucket = std.ArrayList(?T).init(self.mem_allocator); var i: i32 = 0; while (i < 100) : (i += 1) { try self.bucket.?.append(null); } } // 析构函数 pub fn deinit(self: *Self) void { if (self.bucket != null) self.bucket.?.deinit(); } // 哈希函数 fn hashFunc(key: usize) usize { var index = key % 100; return index; } // 查询操作 pub fn get(self: *Self, key: usize) []const u8 { var index = hashFunc(key); var pair = self.bucket.?.items[index]; return pair.?.val; } // 添加操作 pub fn put(self: *Self, key: usize, val: []const u8) !void { var pair = Pair.init(key, val); var index = hashFunc(key); self.bucket.?.items[index] = pair; } // 删除操作 pub fn remove(self: *Self, key: usize) !void { var index = hashFunc(key); // 置为 null ,代表删除 self.bucket.?.items[index] = null; } // 获取所有键值对 pub fn pairSet(self: *Self) !std.ArrayList(T) { var entry_set = std.ArrayList(T).init(self.mem_allocator); for (self.bucket.?.items) |item| { if (item == null) continue; try entry_set.append(item.?); } return entry_set; } // 获取所有键 pub fn keySet(self: *Self) !std.ArrayList(usize) { var key_set = std.ArrayList(usize).init(self.mem_allocator); for (self.bucket.?.items) |item| { if (item == null) continue; try key_set.append(item.?.key); } return key_set; } // 获取所有值 pub fn valueSet(self: *Self) !std.ArrayList([]const u8) { var value_set = std.ArrayList([]const u8).init(self.mem_allocator); for (self.bucket.?.items) |item| { if (item == null) continue; try value_set.append(item.?.val); } return value_set; } // 打印哈希表 pub fn print(self: *Self) !void { var entry_set = try self.pairSet(); defer entry_set.deinit(); for (entry_set.items) |item| { std.debug.print("{} -> {s}\n", .{item.key, item.val}); } } }; } // Driver Code pub fn main() !void { // 初始化哈希表 var map = ArrayHashMap(Pair){}; try map.init(std.heap.page_allocator); defer map.deinit(); // 添加操作 // 在哈希表中添加键值对 (key, value) try map.put(12836, "小哈"); try map.put(15937, "小啰"); try map.put(16750, "小算"); try map.put(13276, "小法"); try map.put(10583, "小鸭"); std.debug.print("\n添加完成后,哈希表为\nKey -> Value\n", .{}); try map.print(); // 查询操作 // 向哈希表中输入键 key ,得到值 value var name = map.get(15937); std.debug.print("\n输入学号 15937 ,查询到姓名 {s}\n", .{name}); // 删除操作 // 在哈希表中删除键值对 (key, value) try map.remove(10583); std.debug.print("\n删除 10583 后,哈希表为\nKey -> Value\n", .{}); try map.print(); // 遍历哈希表 std.debug.print("\n遍历键值对 Key->Value\n", .{}); var entry_set = try map.pairSet(); for (entry_set.items) |kv| { std.debug.print("{} -> {s}\n", .{kv.key, kv.val}); } defer entry_set.deinit(); std.debug.print("\n单独遍历键 Key\n", .{}); var key_set = try map.keySet(); for (key_set.items) |key| { std.debug.print("{}\n", .{key}); } defer key_set.deinit(); std.debug.print("\n单独遍历值 value\n", .{}); var value_set = try map.valueSet(); for (value_set.items) |val| { std.debug.print("{s}\n", .{val}); } defer value_set.deinit(); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_hashing/hash_map.zig ================================================ // File: hash_map.zig // Created Time: 2023-01-13 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // 初始化哈希表 var map = std.AutoHashMap(i32, []const u8).init(std.heap.page_allocator); // 延迟释放内存 defer map.deinit(); // 添加操作 // 在哈希表中添加键值对 (key, value) try map.put(12836, "小哈"); try map.put(15937, "小啰"); try map.put(16750, "小算"); try map.put(13276, "小法"); try map.put(10583, "小鸭"); std.debug.print("\n添加完成后,哈希表为\nKey -> Value\n", .{}); inc.PrintUtil.printHashMap(i32, []const u8, map); // 查询操作 // 向哈希表中输入键 key ,得到值 value var name = map.get(15937).?; std.debug.print("\n输入学号 15937 ,查询到姓名 {s}\n", .{name}); // 删除操作 // 在哈希表中删除键值对 (key, value) _ = map.remove(10583); std.debug.print("\n删除 10583 后,哈希表为\nKey -> Value\n", .{}); inc.PrintUtil.printHashMap(i32, []const u8, map); // 遍历哈希表 std.debug.print("\n遍历键值对 Key->Value\n", .{}); inc.PrintUtil.printHashMap(i32, []const u8, map); std.debug.print("\n单独遍历键 Key\n", .{}); var it = map.iterator(); while (it.next()) |kv| { std.debug.print("{}\n", .{kv.key_ptr.*}); } std.debug.print("\n单独遍历值 value\n", .{}); it = map.iterator(); while (it.next()) |kv| { std.debug.print("{s}\n", .{kv.value_ptr.*}); } _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_heap/heap.zig ================================================ // File: heap.zig // Created Time: 2023-01-14 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); fn lessThan(context: void, a: i32, b: i32) std.math.Order { _ = context; return std.math.order(a, b); } fn greaterThan(context: void, a: i32, b: i32) std.math.Order { return lessThan(context, a, b).invert(); } fn testPush(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype, val: T) !void { try heap.add(val); //元素入堆 std.debug.print("\n元素 {} 入堆后\n", .{val}); try inc.PrintUtil.printHeap(T, mem_allocator, heap); } fn testPop(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype) !void { var val = heap.remove(); //堆顶元素出堆 std.debug.print("\n堆顶元素 {} 出堆后\n", .{val}); try inc.PrintUtil.printHeap(T, mem_allocator, heap); } // Driver Code pub fn main() !void { // 初始化内存分配器 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // 初始化堆 // 初始化小顶堆 const PQlt = std.PriorityQueue(i32, void, lessThan); var min_heap = PQlt.init(std.heap.page_allocator, {}); defer min_heap.deinit(); // 初始化大顶堆 const PQgt = std.PriorityQueue(i32, void, greaterThan); var max_heap = PQgt.init(std.heap.page_allocator, {}); defer max_heap.deinit(); std.debug.print("\n以下测试样例为大顶堆", .{}); // 元素入堆 try testPush(i32, mem_allocator, &max_heap, 1); try testPush(i32, mem_allocator, &max_heap, 3); try testPush(i32, mem_allocator, &max_heap, 2); try testPush(i32, mem_allocator, &max_heap, 5); try testPush(i32, mem_allocator, &max_heap, 4); // 获取堆顶元素 var peek = max_heap.peek().?; std.debug.print("\n堆顶元素为 {}\n", .{peek}); // 堆顶元素出堆 try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); // 获取堆的大小 var size = max_heap.len; std.debug.print("\n堆元素数量为 {}\n", .{size}); // 判断堆是否为空 var is_empty = if (max_heap.len == 0) true else false; std.debug.print("\n堆是否为空 {}\n", .{is_empty}); // 输入列表并建堆 try min_heap.addSlice(&[_]i32{ 1, 3, 2, 5, 4 }); std.debug.print("\n输入列表并建立小顶堆后\n", .{}); try inc.PrintUtil.printHeap(i32, mem_allocator, min_heap); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_heap/my_heap.zig ================================================ // File: my_heap.zig // Created Time: 2023-01-14 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 堆类简易实现 pub fn MaxHeap(comptime T: type) type { return struct { const Self = @This(); max_heap: ?std.ArrayList(T) = null, // 使用列表而非数组,这样无须考虑扩容问题 // 构造方法,根据输入列表建堆 pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []const T) !void { if (self.max_heap != null) return; self.max_heap = std.ArrayList(T).init(allocator); // 将列表元素原封不动添加进堆 try self.max_heap.?.appendSlice(nums); // 堆化除叶节点以外的其他所有节点 var i: usize = parent(self.size() - 1) + 1; while (i > 0) : (i -= 1) { try self.siftDown(i - 1); } } // 析构方法,释放内存 pub fn deinit(self: *Self) void { if (self.max_heap != null) self.max_heap.?.deinit(); } // 获取左子节点的索引 fn left(i: usize) usize { return 2 * i + 1; } // 获取右子节点的索引 fn right(i: usize) usize { return 2 * i + 2; } // 获取父节点的索引 fn parent(i: usize) usize { // return (i - 1) / 2; // 向下整除 return @divFloor(i - 1, 2); } // 交换元素 fn swap(self: *Self, i: usize, j: usize) !void { var tmp = self.max_heap.?.items[i]; try self.max_heap.?.replaceRange(i, 1, &[_]T{self.max_heap.?.items[j]}); try self.max_heap.?.replaceRange(j, 1, &[_]T{tmp}); } // 获取堆大小 pub fn size(self: *Self) usize { return self.max_heap.?.items.len; } // 判断堆是否为空 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // 访问堆顶元素 pub fn peek(self: *Self) T { return self.max_heap.?.items[0]; } // 元素入堆 pub fn push(self: *Self, val: T) !void { // 添加节点 try self.max_heap.?.append(val); // 从底至顶堆化 try self.siftUp(self.size() - 1); } // 从节点 i 开始,从底至顶堆化 fn siftUp(self: *Self, i_: usize) !void { var i = i_; while (true) { // 获取节点 i 的父节点 var p = parent(i); // 当“越过根节点”或“节点无须修复”时,结束堆化 if (p < 0 or self.max_heap.?.items[i] <= self.max_heap.?.items[p]) break; // 交换两节点 try self.swap(i, p); // 循环向上堆化 i = p; } } // 元素出堆 pub fn pop(self: *Self) !T { // 判断处理 if (self.isEmpty()) unreachable; // 交换根节点与最右叶节点(交换首元素与尾元素) try self.swap(0, self.size() - 1); // 删除节点 var val = self.max_heap.?.pop(); // 从顶至底堆化 try self.siftDown(0); // 返回堆顶元素 return val; } // 从节点 i 开始,从顶至底堆化 fn siftDown(self: *Self, i_: usize) !void { var i = i_; while (true) { // 判断节点 i, l, r 中值最大的节点,记为 ma var l = left(i); var r = right(i); var ma = i; if (l < self.size() and self.max_heap.?.items[l] > self.max_heap.?.items[ma]) ma = l; if (r < self.size() and self.max_heap.?.items[r] > self.max_heap.?.items[ma]) ma = r; // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 if (ma == i) break; // 交换两节点 try self.swap(i, ma); // 循环向下堆化 i = ma; } } fn lessThan(context: void, a: T, b: T) std.math.Order { _ = context; return std.math.order(a, b); } fn greaterThan(context: void, a: T, b: T) std.math.Order { return lessThan(context, a, b).invert(); } // 打印堆(二叉树) pub fn print(self: *Self, mem_allocator: std.mem.Allocator) !void { const PQgt = std.PriorityQueue(T, void, greaterThan); var queue = PQgt.init(std.heap.page_allocator, {}); defer queue.deinit(); try queue.addSlice(self.max_heap.?.items); try inc.PrintUtil.printHeap(T, mem_allocator, queue); } }; } // Driver Code pub fn main() !void { // 初始化内存分配器 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // 初始化大顶堆 var max_heap = MaxHeap(i32){}; try max_heap.init(std.heap.page_allocator, &[_]i32{ 9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2 }); defer max_heap.deinit(); std.debug.print("\n输入列表并建堆后\n", .{}); try max_heap.print(mem_allocator); // 获取堆顶元素 var peek = max_heap.peek(); std.debug.print("\n堆顶元素为 {}\n", .{peek}); // 元素入堆 const val = 7; try max_heap.push(val); std.debug.print("\n元素 {} 入堆后\n", .{val}); try max_heap.print(mem_allocator); // 堆顶元素出堆 peek = try max_heap.pop(); std.debug.print("\n堆顶元素 {} 出堆后\n", .{peek}); try max_heap.print(mem_allocator); // 获取堆的大小 var size = max_heap.size(); std.debug.print("\n堆元素数量为 {}", .{size}); // 判断堆是否为空 var is_empty = max_heap.isEmpty(); std.debug.print("\n堆是否为空 {}\n", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_searching/binary_search.zig ================================================ // File: binary_search.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 二分查找(双闭区间) fn binarySearch(comptime T: type, nums: std.ArrayList(T), target: T) T { // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 var i: usize = 0; var j: usize = nums.items.len - 1; // 循环,当搜索区间为空时跳出(当 i > j 时为空) while (i <= j) { var m = i + (j - i) / 2; // 计算中点索引 m if (nums.items[m] < target) { // 此情况说明 target 在区间 [m+1, j] 中 i = m + 1; } else if (nums.items[m] > target) { // 此情况说明 target 在区间 [i, m-1] 中 j = m - 1; } else { // 找到目标元素,返回其索引 return @intCast(m); } } // 未找到目标元素,返回 -1 return -1; } // 二分查找(左闭右开区间) fn binarySearchLCRO(comptime T: type, nums: std.ArrayList(T), target: T) T { // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 var i: usize = 0; var j: usize = nums.items.len; // 循环,当搜索区间为空时跳出(当 i = j 时为空) while (i <= j) { var m = i + (j - i) / 2; // 计算中点索引 m if (nums.items[m] < target) { // 此情况说明 target 在区间 [m+1, j) 中 i = m + 1; } else if (nums.items[m] > target) { // 此情况说明 target 在区间 [i, m) 中 j = m; } else { // 找到目标元素,返回其索引 return @intCast(m); } } // 未找到目标元素,返回 -1 return -1; } // Driver Code pub fn main() !void { var target: i32 = 6; var nums = std.ArrayList(i32).init(std.heap.page_allocator); defer nums.deinit(); try nums.appendSlice(&[_]i32{ 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }); // 二分查找(双闭区间) var index = binarySearch(i32, nums, target); std.debug.print("目标元素 6 的索引 = {}\n", .{index}); // 二分查找(左闭右开区间) index = binarySearchLCRO(i32, nums, target); std.debug.print("目标元素 6 的索引 = {}\n", .{index}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_searching/hashing_search.zig ================================================ // File: hashing_search.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 哈希查找(数组) fn hashingSearchArray(comptime T: type, map: std.AutoHashMap(T, T), target: T) T { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 if (map.getKey(target) == null) return -1; return map.get(target).?; } // 哈希查找(链表) fn hashingSearchLinkedList(comptime T: type, map: std.AutoHashMap(T, *inc.ListNode(T)), target: T) ?*inc.ListNode(T) { // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 null if (map.getKey(target) == null) return null; return map.get(target); } // Driver Code pub fn main() !void { var target: i32 = 3; // 哈希查找(数组) var nums = [_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; // 初始化哈希表 var map = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); defer map.deinit(); for (nums, 0..) |num, i| { try map.put(num, @as(i32, @intCast(i))); // key: 元素,value: 索引 } var index = hashingSearchArray(i32, map, target); std.debug.print("目标元素 3 的索引 = {}\n", .{index}); // 哈希查找(链表) var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var head = try inc.ListUtil.arrToLinkedList(i32, mem_allocator, &nums); // 初始化哈希表 var map1 = std.AutoHashMap(i32, *inc.ListNode(i32)).init(std.heap.page_allocator); defer map1.deinit(); while (head != null) { try map1.put(head.?.val, head.?); head = head.?.next; } var node = hashingSearchLinkedList(i32, map1, target); std.debug.print("目标节点值 3 的对应节点对象为 ", .{}); try inc.PrintUtil.printLinkedList(i32, node); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_searching/linear_search.zig ================================================ // File: linear_search.zig // Created Time: 2023-01-13 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 线性查找(数组) fn linearSearchArray(comptime T: type, nums: std.ArrayList(T), target: T) T { // 遍历数组 for (nums.items, 0..) |num, i| { // 找到目标元素, 返回其索引 if (num == target) { return @intCast(i); } } // 未找到目标元素,返回 -1 return -1; } // 线性查找(链表) pub fn linearSearchLinkedList(comptime T: type, node: ?*inc.ListNode(T), target: T) ?*inc.ListNode(T) { var head = node; // 遍历链表 while (head != null) { // 找到目标节点,返回之 if (head.?.val == target) return head; head = head.?.next; } return null; } // Driver Code pub fn main() !void { var target: i32 = 3; // 在数组中执行线性查找 var nums = std.ArrayList(i32).init(std.heap.page_allocator); defer nums.deinit(); try nums.appendSlice(&[_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }); var index = linearSearchArray(i32, nums, target); std.debug.print("目标元素 3 的索引 = {}\n", .{index}); // 在链表中执行线性查找 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var head = try inc.ListUtil.listToLinkedList(i32, mem_allocator, nums); var node = linearSearchLinkedList(i32, head, target); std.debug.print("目标节点值 3 的对应节点对象为 ", .{}); try inc.PrintUtil.printLinkedList(i32, node); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_searching/two_sum.zig ================================================ // File: two_sum.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 方法一:暴力枚举 pub fn twoSumBruteForce(nums: []i32, target: i32) ?[2]i32 { var size: usize = nums.len; var i: usize = 0; // 两层循环,时间复杂度为 O(n^2) while (i < size - 1) : (i += 1) { var j = i + 1; while (j < size) : (j += 1) { if (nums[i] + nums[j] == target) { return [_]i32{@intCast(i), @intCast(j)}; } } } return null; } // 方法二:辅助哈希表 pub fn twoSumHashTable(nums: []i32, target: i32) !?[2]i32 { var size: usize = nums.len; // 辅助哈希表,空间复杂度为 O(n) var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); defer dic.deinit(); var i: usize = 0; // 单层循环,时间复杂度为 O(n) while (i < size) : (i += 1) { if (dic.contains(target - nums[i])) { return [_]i32{dic.get(target - nums[i]).?, @intCast(i)}; } try dic.put(nums[i], @intCast(i)); } return null; } pub fn main() !void { // ======= Test Case ======= var nums = [_]i32{ 2, 7, 11, 15 }; var target: i32 = 9; // ====== Driver Code ====== // 方法一 var res = twoSumBruteForce(&nums, target).?; std.debug.print("方法一 res = ", .{}); inc.PrintUtil.printArray(i32, &res); // 方法二 res = (try twoSumHashTable(&nums, target)).?; std.debug.print("\n方法二 res = ", .{}); inc.PrintUtil.printArray(i32, &res); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_sorting/bubble_sort.zig ================================================ // File: bubble_sort.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 冒泡排序 fn bubbleSort(nums: []i32) void { // 外循环:未排序区间为 [0, i] var i: usize = nums.len - 1; while (i > 0) : (i -= 1) { var j: usize = 0; // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] var tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } // 冒泡排序(标志优化) fn bubbleSortWithFlag(nums: []i32) void { // 外循环:未排序区间为 [0, i] var i: usize = nums.len - 1; while (i > 0) : (i -= 1) { var flag = false; // 初始化标志位 var j: usize = 0; // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] var tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; } } if (!flag) break; // 此轮“冒泡”未交换任何元素,直接跳出 } } // Driver Code pub fn main() !void { var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; bubbleSort(&nums); std.debug.print("冒泡排序完成后 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); var nums1 = [_]i32{ 4, 1, 3, 1, 5, 2 }; bubbleSortWithFlag(&nums1); std.debug.print("\n冒泡排序完成后 nums1 = ", .{}); inc.PrintUtil.printArray(i32, &nums1); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_sorting/insertion_sort.zig ================================================ // File: insertion_sort.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 插入排序 fn insertionSort(nums: []i32) void { // 外循环:已排序区间为 [0, i-1] var i: usize = 1; while (i < nums.len) : (i += 1) { var base = nums[i]; var j: usize = i; // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while (j >= 1 and nums[j - 1] > base) : (j -= 1) { nums[j] = nums[j - 1]; // 将 nums[j] 向右移动一位 } nums[j] = base; // 将 base 赋值到正确位置 } } // Driver Code pub fn main() !void { var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; insertionSort(&nums); std.debug.print("插入排序完成后 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_sorting/merge_sort.zig ================================================ // File: merge_sort.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 合并左子数组和右子数组 // 左子数组区间 [left, mid] // 右子数组区间 [mid + 1, right] fn merge(nums: []i32, left: usize, mid: usize, right: usize) !void { // 初始化辅助数组 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var tmp = try mem_allocator.alloc(i32, right + 1 - left); std.mem.copy(i32, tmp, nums[left..right+1]); // 左子数组的起始索引和结束索引 var leftStart = left - left; var leftEnd = mid - left; // 右子数组的起始索引和结束索引 var rightStart = mid + 1 - left; var rightEnd = right - left; // i, j 分别指向左子数组、右子数组的首元素 var i = leftStart; var j = rightStart; // 通过覆盖原数组 nums 来合并左子数组和右子数组 var k = left; while (k <= right) : (k += 1) { // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ if (i > leftEnd) { nums[k] = tmp[j]; j += 1; // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ } else if (j > rightEnd or tmp[i] <= tmp[j]) { nums[k] = tmp[i]; i += 1; // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ } else { nums[k] = tmp[j]; j += 1; } } } // 归并排序 fn mergeSort(nums: []i32, left: usize, right: usize) !void { // 终止条件 if (left >= right) return; // 当子数组长度为 1 时终止递归 // 划分阶段 var mid = left + (right - left) / 2; // 计算中点 try mergeSort(nums, left, mid); // 递归左子数组 try mergeSort(nums, mid + 1, right); // 递归右子数组 // 合并阶段 try merge(nums, left, mid, right); } // Driver Code pub fn main() !void { // 归并排序 var nums = [_]i32{ 7, 3, 2, 6, 0, 1, 5, 4 }; try mergeSort(&nums, 0, nums.len - 1); std.debug.print("归并排序完成后 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_sorting/quick_sort.zig ================================================ // File: quick_sort.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 快速排序类 const QuickSort = struct { // 元素交换 pub fn swap(nums: []i32, i: usize, j: usize) void { var tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } // 哨兵划分 pub fn partition(nums: []i32, left: usize, right: usize) usize { // 以 nums[left] 为基准数 var i = left; var j = right; while (i < j) { while (i < j and nums[j] >= nums[left]) j -= 1; // 从右向左找首个小于基准数的元素 while (i < j and nums[i] <= nums[left]) i += 1; // 从左向右找首个大于基准数的元素 swap(nums, i, j); // 交换这两个元素 } swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } // 快速排序 pub fn quickSort(nums: []i32, left: usize, right: usize) void { // 子数组长度为 1 时终止递归 if (left >= right) return; // 哨兵划分 var pivot = partition(nums, left, right); // 递归左子数组、右子数组 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } }; // 快速排序类(中位基准数优化) const QuickSortMedian = struct { // 元素交换 pub fn swap(nums: []i32, i: usize, j: usize) void { var tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } // 选取三个候选元素的中位数 pub fn medianThree(nums: []i32, left: usize, mid: usize, right: usize) usize { var l = nums[left]; var m = nums[mid]; var r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // m 在 l 和 r 之间 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l 在 m 和 r 之间 return right; } // 哨兵划分(三数取中值) pub fn partition(nums: []i32, left: usize, right: usize) usize { // 选取三个候选元素的中位数 var med = medianThree(nums, left, (left + right) / 2, right); // 将中位数交换至数组最左端 swap(nums, left, med); // 以 nums[left] 为基准数 var i = left; var j = right; while (i < j) { while (i < j and nums[j] >= nums[left]) j -= 1; // 从右向左找首个小于基准数的元素 while (i < j and nums[i] <= nums[left]) i += 1; // 从左向右找首个大于基准数的元素 swap(nums, i, j); // 交换这两个元素 } swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } // 快速排序 pub fn quickSort(nums: []i32, left: usize, right: usize) void { // 子数组长度为 1 时终止递归 if (left >= right) return; // 哨兵划分 var pivot = partition(nums, left, right); if (pivot == 0) return; // 递归左子数组、右子数组 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } }; // 快速排序类(递归深度优化) const QuickSortTailCall = struct { // 元素交换 pub fn swap(nums: []i32, i: usize, j: usize) void { var tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } // 哨兵划分 pub fn partition(nums: []i32, left: usize, right: usize) usize { // 以 nums[left] 为基准数 var i = left; var j = right; while (i < j) { while (i < j and nums[j] >= nums[left]) j -= 1; // 从右向左找首个小于基准数的元素 while (i < j and nums[i] <= nums[left]) i += 1; // 从左向右找首个大于基准数的元素 swap(nums, i, j); // 交换这两个元素 } swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } // 快速排序(递归深度优化) pub fn quickSort(nums: []i32, left_: usize, right_: usize) void { var left = left_; var right = right_; // 子数组长度为 1 时终止递归 while (left < right) { // 哨兵划分操作 var pivot = partition(nums, left, right); // 对两个子数组中较短的那个执行快速排序 if (pivot - left < right - pivot) { quickSort(nums, left, pivot - 1); // 递归排序左子数组 left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] } else { quickSort(nums, pivot + 1, right); // 递归排序右子数组 right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] } } } }; // Driver Code pub fn main() !void { // 快速排序 var nums = [_]i32{ 2, 4, 1, 0, 3, 5 }; QuickSort.quickSort(&nums, 0, nums.len - 1); std.debug.print("快速排序完成后 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); // 快速排序(中位基准数优化) var nums1 = [_]i32{ 2, 4, 1, 0, 3, 5 }; QuickSortMedian.quickSort(&nums1, 0, nums1.len - 1); std.debug.print("\n快速排序(中位基准数优化)完成后 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums1); // 快速排序(递归深度优化) var nums2 = [_]i32{ 2, 4, 1, 0, 3, 5 }; QuickSortTailCall.quickSort(&nums2, 0, nums2.len - 1); std.debug.print("\n快速排序(递归深度优化)完成后 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums2); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_sorting/radix_sort.zig ================================================ // File: radix_sort.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 获取元素 num 的第 k 位,其中 exp = 10^(k-1) fn digit(num: i32, exp: i32) i32 { // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 return @mod(@divFloor(num, exp), 10); } // 计数排序(根据 nums 第 k 位排序) fn countingSortDigit(nums: []i32, exp: i32) !void { // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); // defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var counter = try mem_allocator.alloc(usize, 10); @memset(counter, 0); var n = nums.len; // 统计 0~9 各数字的出现次数 for (nums) |num| { var d: u32 = @bitCast(digit(num, exp)); // 获取 nums[i] 第 k 位,记为 d counter[d] += 1; // 统计数字 d 的出现次数 } // 求前缀和,将“出现个数”转换为“数组索引” var i: usize = 1; while (i < 10) : (i += 1) { counter[i] += counter[i - 1]; } // 倒序遍历,根据桶内统计结果,将各元素填入 res var res = try mem_allocator.alloc(i32, n); i = n - 1; while (i >= 0) : (i -= 1) { var d: u32 = @bitCast(digit(nums[i], exp)); var j = counter[d] - 1; // 获取 d 在数组中的索引 j res[j] = nums[i]; // 将当前元素填入索引 j counter[d] -= 1; // 将 d 的数量减 1 if (i == 0) break; } // 使用结果覆盖原数组 nums i = 0; while (i < n) : (i += 1) { nums[i] = res[i]; } } // 基数排序 fn radixSort(nums: []i32) !void { // 获取数组的最大元素,用于判断最大位数 var m: i32 = std.math.minInt(i32); for (nums) |num| { if (num > m) m = num; } // 按照从低位到高位的顺序遍历 var exp: i32 = 1; while (exp <= m) : (exp *= 10) { // 对数组元素的第 k 位执行计数排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) try countingSortDigit(nums, exp); } } // Driver Code pub fn main() !void { // 基数排序 var nums = [_]i32{ 23, 12, 3, 4, 788, 192 }; try radixSort(&nums); std.debug.print("基数排序完成后 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_stack_and_queue/array_queue.zig ================================================ // File: array_queue.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 基于环形数组实现的队列 pub fn ArrayQueue(comptime T: type) type { return struct { const Self = @This(); nums: []T = undefined, // 用于存储队列元素的数组 cap: usize = 0, // 队列容量 front: usize = 0, // 队首指针,指向队首元素 queSize: usize = 0, // 尾指针,指向队尾 + 1 mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // 内存分配器 // 构造函数(分配内存+初始化数组) pub fn init(self: *Self, allocator: std.mem.Allocator, cap: usize) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.cap = cap; self.nums = try self.mem_allocator.alloc(T, self.cap); @memset(self.nums, @as(T, 0)); } // 析构函数(释放内存) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 获取队列的容量 pub fn capacity(self: *Self) usize { return self.cap; } // 获取队列的长度 pub fn size(self: *Self) usize { return self.queSize; } // 判断队列是否为空 pub fn isEmpty(self: *Self) bool { return self.queSize == 0; } // 入队 pub fn push(self: *Self, num: T) !void { if (self.size() == self.capacity()) { std.debug.print("队列已满\n", .{}); return; } // 计算队尾指针,指向队尾索引 + 1 // 通过取余操作实现 rear 越过数组尾部后回到头部 var rear = (self.front + self.queSize) % self.capacity(); // 在尾节点后添加 num self.nums[rear] = num; self.queSize += 1; } // 出队 pub fn pop(self: *Self) T { var num = self.peek(); // 队首指针向后移动一位,若越过尾部,则返回到数组头部 self.front = (self.front + 1) % self.capacity(); self.queSize -= 1; return num; } // 访问队首元素 pub fn peek(self: *Self) T { if (self.isEmpty()) @panic("队列为空"); return self.nums[self.front]; } // 返回数组 pub fn toArray(self: *Self) ![]T { // 仅转换有效长度范围内的列表元素 var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; var j: usize = self.front; while (i < self.size()) : ({ i += 1; j += 1; }) { res[i] = self.nums[j % self.capacity()]; } return res; } }; } // Driver Code pub fn main() !void { // 初始化队列 var capacity: usize = 10; var queue = ArrayQueue(i32){}; try queue.init(std.heap.page_allocator, capacity); defer queue.deinit(); // 元素入队 try queue.push(1); try queue.push(3); try queue.push(2); try queue.push(5); try queue.push(4); std.debug.print("队列 queue = ", .{}); inc.PrintUtil.printArray(i32, try queue.toArray()); // 访问队首元素 var peek = queue.peek(); std.debug.print("\n队首元素 peek = {}", .{peek}); // 元素出队 var pop = queue.pop(); std.debug.print("\n出队元素 pop = {},出队后 queue = ", .{pop}); inc.PrintUtil.printArray(i32, try queue.toArray()); // 获取队列的长度 var size = queue.size(); std.debug.print("\n队列长度 size = {}", .{size}); // 判断队列是否为空 var is_empty = queue.isEmpty(); std.debug.print("\n队列是否为空 = {}", .{is_empty}); // 测试环形数组 var i: i32 = 0; while (i < 10) : (i += 1) { try queue.push(i); _ = queue.pop(); std.debug.print("\n第 {} 轮入队 + 出队后 queue = ", .{i}); inc.PrintUtil.printArray(i32, try queue.toArray()); } _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_stack_and_queue/array_stack.zig ================================================ // File: array_stack.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 基于数组实现的栈 pub fn ArrayStack(comptime T: type) type { return struct { const Self = @This(); stack: ?std.ArrayList(T) = null, // 构造方法(分配内存+初始化栈) pub fn init(self: *Self, allocator: std.mem.Allocator) void { if (self.stack == null) { self.stack = std.ArrayList(T).init(allocator); } } // 析构方法(释放内存) pub fn deinit(self: *Self) void { if (self.stack == null) return; self.stack.?.deinit(); } // 获取栈的长度 pub fn size(self: *Self) usize { return self.stack.?.items.len; } // 判断栈是否为空 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // 访问栈顶元素 pub fn peek(self: *Self) T { if (self.isEmpty()) @panic("栈为空"); return self.stack.?.items[self.size() - 1]; } // 入栈 pub fn push(self: *Self, num: T) !void { try self.stack.?.append(num); } // 出栈 pub fn pop(self: *Self) T { var num = self.stack.?.pop(); return num; } // 返回 ArrayList pub fn toList(self: *Self) std.ArrayList(T) { return self.stack.?; } }; } // Driver Code pub fn main() !void { // 初始化栈 var stack = ArrayStack(i32){}; stack.init(std.heap.page_allocator); // 延迟释放内存 defer stack.deinit(); // 元素入栈 try stack.push(1); try stack.push(3); try stack.push(2); try stack.push(5); try stack.push(4); std.debug.print("栈 stack = ", .{}); inc.PrintUtil.printList(i32, stack.toList()); // 访问栈顶元素 var peek = stack.peek(); std.debug.print("\n栈顶元素 peek = {}", .{peek}); // 元素出栈 var top = stack.pop(); std.debug.print("\n出栈元素 pop = {},出栈后 stack = ", .{top}); inc.PrintUtil.printList(i32, stack.toList()); // 获取栈的长度 var size = stack.size(); std.debug.print("\n栈的长度 size = {}", .{size}); // 判断栈是否为空 var is_empty = stack.isEmpty(); std.debug.print("\n栈是否为空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_stack_and_queue/deque.zig ================================================ // File: deque.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // 初始化双向队列 const L = std.TailQueue(i32); var deque = L{}; // 元素入队 var node1 = L.Node{ .data = 2 }; var node2 = L.Node{ .data = 5 }; var node3 = L.Node{ .data = 4 }; var node4 = L.Node{ .data = 3 }; var node5 = L.Node{ .data = 1 }; deque.append(&node1); // 添加至队尾 deque.append(&node2); deque.append(&node3); deque.prepend(&node4); // 添加至队首 deque.prepend(&node5); std.debug.print("双向队列 deque = ", .{}); inc.PrintUtil.printQueue(i32, deque); // 访问元素 var peek_first = deque.first.?.data; // 队首元素 std.debug.print("\n队首元素 peek_first = {}", .{peek_first}); var peek_last = deque.last.?.data; // 队尾元素 std.debug.print("\n队尾元素 peek_last = {}", .{peek_last}); // 元素出队 var pop_first = deque.popFirst().?.data; // 队首元素出队 std.debug.print("\n队首出队元素 pop_first = {},队首出队后 deque = ", .{pop_first}); inc.PrintUtil.printQueue(i32, deque); var pop_last = deque.pop().?.data; // 队尾元素出队 std.debug.print("\n队尾出队元素 pop_last = {},队尾出队后 deque = ", .{pop_last}); inc.PrintUtil.printQueue(i32, deque); // 获取双向队列的长度 var size = deque.len; std.debug.print("\n双向队列长度 size = {}", .{size}); // 判断双向队列是否为空 var is_empty = if (deque.len == 0) true else false; std.debug.print("\n双向队列是否为空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_stack_and_queue/linkedlist_deque.zig ================================================ // File: linkedlist_deque.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 双向链表节点 pub fn ListNode(comptime T: type) type { return struct { const Self = @This(); val: T = undefined, // 节点值 next: ?*Self = null, // 后继节点指针 prev: ?*Self = null, // 前驱节点指针 // Initialize a list node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.next = null; self.prev = null; } }; } // 基于双向链表实现的双向队列 pub fn LinkedListDeque(comptime T: type) type { return struct { const Self = @This(); front: ?*ListNode(T) = null, // 头节点 front rear: ?*ListNode(T) = null, // 尾节点 rear que_size: usize = 0, // 双向队列的长度 mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // 内存分配器 // 构造函数(分配内存+初始化队列) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.front = null; self.rear = null; self.que_size = 0; } // 析构函数(释放内存) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 获取双向队列的长度 pub fn size(self: *Self) usize { return self.que_size; } // 判断双向队列是否为空 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // 入队操作 pub fn push(self: *Self, num: T, is_front: bool) !void { var node = try self.mem_allocator.create(ListNode(T)); node.init(num); // 若链表为空,则令 front 和 rear 都指向 node if (self.isEmpty()) { self.front = node; self.rear = node; // 队首入队操作 } else if (is_front) { // 将 node 添加至链表头部 self.front.?.prev = node; node.next = self.front; self.front = node; // 更新头节点 // 队尾入队操作 } else { // 将 node 添加至链表尾部 self.rear.?.next = node; node.prev = self.rear; self.rear = node; // 更新尾节点 } self.que_size += 1; // 更新队列长度 } // 队首入队 pub fn pushFirst(self: *Self, num: T) !void { try self.push(num, true); } // 队尾入队 pub fn pushLast(self: *Self, num: T) !void { try self.push(num, false); } // 出队操作 pub fn pop(self: *Self, is_front: bool) T { if (self.isEmpty()) @panic("双向队列为空"); var val: T = undefined; // 队首出队操作 if (is_front) { val = self.front.?.val; // 暂存头节点值 // 删除头节点 var fNext = self.front.?.next; if (fNext != null) { fNext.?.prev = null; self.front.?.next = null; } self.front = fNext; // 更新头节点 // 队尾出队操作 } else { val = self.rear.?.val; // 暂存尾节点值 // 删除尾节点 var rPrev = self.rear.?.prev; if (rPrev != null) { rPrev.?.next = null; self.rear.?.prev = null; } self.rear = rPrev; // 更新尾节点 } self.que_size -= 1; // 更新队列长度 return val; } // 队首出队 pub fn popFirst(self: *Self) T { return self.pop(true); } // 队尾出队 pub fn popLast(self: *Self) T { return self.pop(false); } // 访问队首元素 pub fn peekFirst(self: *Self) T { if (self.isEmpty()) @panic("双向队列为空"); return self.front.?.val; } // 访问队尾元素 pub fn peekLast(self: *Self) T { if (self.isEmpty()) @panic("双向队列为空"); return self.rear.?.val; } // 返回数组用于打印 pub fn toArray(self: *Self) ![]T { var node = self.front; var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; while (i < res.len) : (i += 1) { res[i] = node.?.val; node = node.?.next; } return res; } }; } // Driver Code pub fn main() !void { // 初始化双向队列 var deque = LinkedListDeque(i32){}; try deque.init(std.heap.page_allocator); defer deque.deinit(); try deque.pushLast(3); try deque.pushLast(2); try deque.pushLast(5); std.debug.print("双向队列 deque = ", .{}); inc.PrintUtil.printArray(i32, try deque.toArray()); // 访问元素 var peek_first = deque.peekFirst(); std.debug.print("\n队首元素 peek_first = {}", .{peek_first}); var peek_last = deque.peekLast(); std.debug.print("\n队尾元素 peek_last = {}", .{peek_last}); // 元素入队 try deque.pushLast(4); std.debug.print("\n元素 4 队尾入队后 deque = ", .{}); inc.PrintUtil.printArray(i32, try deque.toArray()); try deque.pushFirst(1); std.debug.print("\n元素 1 队首入队后 deque = ", .{}); inc.PrintUtil.printArray(i32, try deque.toArray()); // 元素出队 var pop_last = deque.popLast(); std.debug.print("\n队尾出队元素 = {},队尾出队后 deque = ", .{pop_last}); inc.PrintUtil.printArray(i32, try deque.toArray()); var pop_first = deque.popFirst(); std.debug.print("\n队首出队元素 = {},队首出队后 deque = ", .{pop_first}); inc.PrintUtil.printArray(i32, try deque.toArray()); // 获取双向队列的长度 var size = deque.size(); std.debug.print("\n双向队列长度 size = {}", .{size}); // 判断双向队列是否为空 var is_empty = deque.isEmpty(); std.debug.print("\n双向队列是否为空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_stack_and_queue/linkedlist_queue.zig ================================================ // File: linkedlist_queue.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 基于链表实现的队列 pub fn LinkedListQueue(comptime T: type) type { return struct { const Self = @This(); front: ?*inc.ListNode(T) = null, // 头节点 front rear: ?*inc.ListNode(T) = null, // 尾节点 rear que_size: usize = 0, // 队列的长度 mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // 内存分配器 // 构造函数(分配内存+初始化队列) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.front = null; self.rear = null; self.que_size = 0; } // 析构函数(释放内存) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 获取队列的长度 pub fn size(self: *Self) usize { return self.que_size; } // 判断队列是否为空 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // 访问队首元素 pub fn peek(self: *Self) T { if (self.size() == 0) @panic("队列为空"); return self.front.?.val; } // 入队 pub fn push(self: *Self, num: T) !void { // 在尾节点后添加 num var node = try self.mem_allocator.create(inc.ListNode(T)); node.init(num); // 如果队列为空,则令头、尾节点都指向该节点 if (self.front == null) { self.front = node; self.rear = node; // 如果队列不为空,则将该节点添加到尾节点后 } else { self.rear.?.next = node; self.rear = node; } self.que_size += 1; } // 出队 pub fn pop(self: *Self) T { var num = self.peek(); // 删除头节点 self.front = self.front.?.next; self.que_size -= 1; return num; } // 将链表转换为数组 pub fn toArray(self: *Self) ![]T { var node = self.front; var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; while (i < res.len) : (i += 1) { res[i] = node.?.val; node = node.?.next; } return res; } }; } // Driver Code pub fn main() !void { // 初始化队列 var queue = LinkedListQueue(i32){}; try queue.init(std.heap.page_allocator); defer queue.deinit(); // 元素入队 try queue.push(1); try queue.push(3); try queue.push(2); try queue.push(5); try queue.push(4); std.debug.print("队列 queue = ", .{}); inc.PrintUtil.printArray(i32, try queue.toArray()); // 访问队首元素 var peek = queue.peek(); std.debug.print("\n队首元素 peek = {}", .{peek}); // 元素出队 var pop = queue.pop(); std.debug.print("\n出队元素 pop = {},出队后 queue = ", .{pop}); inc.PrintUtil.printArray(i32, try queue.toArray()); // 获取队列的长度 var size = queue.size(); std.debug.print("\n队列长度 size = {}", .{size}); // 判断队列是否为空 var is_empty = queue.isEmpty(); std.debug.print("\n队列是否为空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_stack_and_queue/linkedlist_stack.zig ================================================ // File: linkedlist_stack.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 基于链表实现的栈 pub fn LinkedListStack(comptime T: type) type { return struct { const Self = @This(); stack_top: ?*inc.ListNode(T) = null, // 将头节点作为栈顶 stk_size: usize = 0, // 栈的长度 mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // 内存分配器 // 构造函数(分配内存+初始化栈) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.stack_top = null; self.stk_size = 0; } // 析构函数(释放内存) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 获取栈的长度 pub fn size(self: *Self) usize { return self.stk_size; } // 判断栈是否为空 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // 访问栈顶元素 pub fn peek(self: *Self) T { if (self.size() == 0) @panic("栈为空"); return self.stack_top.?.val; } // 入栈 pub fn push(self: *Self, num: T) !void { var node = try self.mem_allocator.create(inc.ListNode(T)); node.init(num); node.next = self.stack_top; self.stack_top = node; self.stk_size += 1; } // 出栈 pub fn pop(self: *Self) T { var num = self.peek(); self.stack_top = self.stack_top.?.next; self.stk_size -= 1; return num; } // 将栈转换为数组 pub fn toArray(self: *Self) ![]T { var node = self.stack_top; var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; while (i < res.len) : (i += 1) { res[res.len - i - 1] = node.?.val; node = node.?.next; } return res; } }; } // Driver Code pub fn main() !void { // 初始化栈 var stack = LinkedListStack(i32){}; try stack.init(std.heap.page_allocator); // 延迟释放内存 defer stack.deinit(); // 元素入栈 try stack.push(1); try stack.push(3); try stack.push(2); try stack.push(5); try stack.push(4); std.debug.print("栈 stack = ", .{}); inc.PrintUtil.printArray(i32, try stack.toArray()); // 访问栈顶元素 var peek = stack.peek(); std.debug.print("\n栈顶元素 top = {}", .{peek}); // 元素出栈 var pop = stack.pop(); std.debug.print("\n出栈元素 pop = {},出栈后 stack = ", .{pop}); inc.PrintUtil.printArray(i32, try stack.toArray()); // 获取栈的长度 var size = stack.size(); std.debug.print("\n栈的长度 size = {}", .{size}); // 判断栈是否为空 var is_empty = stack.isEmpty(); std.debug.print("\n栈是否为空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_stack_and_queue/queue.zig ================================================ // File: queue.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // 初始化队列 const L = std.TailQueue(i32); var queue = L{}; // 元素入队 var node1 = L.Node{ .data = 1 }; var node2 = L.Node{ .data = 3 }; var node3 = L.Node{ .data = 2 }; var node4 = L.Node{ .data = 5 }; var node5 = L.Node{ .data = 4 }; queue.append(&node1); queue.append(&node2); queue.append(&node3); queue.append(&node4); queue.append(&node5); std.debug.print("队列 queue = ", .{}); inc.PrintUtil.printQueue(i32, queue); // 访问队首元素 var peek = queue.first.?.data; std.debug.print("\n队首元素 peek = {}", .{peek}); // 元素出队 var pop = queue.popFirst().?.data; std.debug.print("\n出队元素 pop = {},出队后 queue = ", .{pop}); inc.PrintUtil.printQueue(i32, queue); // 获取队列的长度 var size = queue.len; std.debug.print("\n队列长度 size = {}", .{size}); // 判断队列是否为空 var is_empty = if (queue.len == 0) true else false; std.debug.print("\n队列是否为空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_stack_and_queue/stack.zig ================================================ // File: stack.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // 初始化栈 // 在 zig 中,推荐将 ArrayList 当作栈来使用 var stack = std.ArrayList(i32).init(std.heap.page_allocator); // 延迟释放内存 defer stack.deinit(); // 元素入栈 try stack.append(1); try stack.append(3); try stack.append(2); try stack.append(5); try stack.append(4); std.debug.print("栈 stack = ", .{}); inc.PrintUtil.printList(i32, stack); // 访问栈顶元素 var peek = stack.items[stack.items.len - 1]; std.debug.print("\n栈顶元素 peek = {}", .{peek}); // 元素出栈 var pop = stack.pop(); std.debug.print("\n出栈元素 pop = {},出栈后 stack = ", .{pop}); inc.PrintUtil.printList(i32, stack); // 获取栈的长度 var size = stack.items.len; std.debug.print("\n栈的长度 size = {}", .{size}); // 判断栈是否为空 var is_empty = if (stack.items.len == 0) true else false; std.debug.print("\n栈是否为空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_tree/avl_tree.zig ================================================ // File: avl_tree.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // AVL 树 pub fn AVLTree(comptime T: type) type { return struct { const Self = @This(); root: ?*inc.TreeNode(T) = null, // 根节点 mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // 内存分配器 // 构造方法 pub fn init(self: *Self, allocator: std.mem.Allocator) void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } } // 析构方法 pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 获取节点高度 fn height(self: *Self, node: ?*inc.TreeNode(T)) i32 { _ = self; // 空节点高度为 -1 ,叶节点高度为 0 return if (node == null) -1 else node.?.height; } // 更新节点高度 fn updateHeight(self: *Self, node: ?*inc.TreeNode(T)) void { // 节点高度等于最高子树高度 + 1 node.?.height = @max(self.height(node.?.left), self.height(node.?.right)) + 1; } // 获取平衡因子 fn balanceFactor(self: *Self, node: ?*inc.TreeNode(T)) i32 { // 空节点平衡因子为 0 if (node == null) return 0; // 节点平衡因子 = 左子树高度 - 右子树高度 return self.height(node.?.left) - self.height(node.?.right); } // 右旋操作 fn rightRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { var child = node.?.left; var grandChild = child.?.right; // 以 child 为原点,将 node 向右旋转 child.?.right = node; node.?.left = grandChild; // 更新节点高度 self.updateHeight(node); self.updateHeight(child); // 返回旋转后子树的根节点 return child; } // 左旋操作 fn leftRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { var child = node.?.right; var grandChild = child.?.left; // 以 child 为原点,将 node 向左旋转 child.?.left = node; node.?.right = grandChild; // 更新节点高度 self.updateHeight(node); self.updateHeight(child); // 返回旋转后子树的根节点 return child; } // 执行旋转操作,使该子树重新恢复平衡 fn rotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { // 获取节点 node 的平衡因子 var balance_factor = self.balanceFactor(node); // 左偏树 if (balance_factor > 1) { if (self.balanceFactor(node.?.left) >= 0) { // 右旋 return self.rightRotate(node); } else { // 先左旋后右旋 node.?.left = self.leftRotate(node.?.left); return self.rightRotate(node); } } // 右偏树 if (balance_factor < -1) { if (self.balanceFactor(node.?.right) <= 0) { // 左旋 return self.leftRotate(node); } else { // 先右旋后左旋 node.?.right = self.rightRotate(node.?.right); return self.leftRotate(node); } } // 平衡树,无须旋转,直接返回 return node; } // 插入节点 fn insert(self: *Self, val: T) !void { self.root = (try self.insertHelper(self.root, val)).?; } // 递归插入节点(辅助方法) fn insertHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) !?*inc.TreeNode(T) { var node = node_; if (node == null) { var tmp_node = try self.mem_allocator.create(inc.TreeNode(T)); tmp_node.init(val); return tmp_node; } // 1. 查找插入位置并插入节点 if (val < node.?.val) { node.?.left = try self.insertHelper(node.?.left, val); } else if (val > node.?.val) { node.?.right = try self.insertHelper(node.?.right, val); } else { return node; // 重复节点不插入,直接返回 } self.updateHeight(node); // 更新节点高度 // 2. 执行旋转操作,使该子树重新恢复平衡 node = self.rotate(node); // 返回子树的根节点 return node; } // 删除节点 fn remove(self: *Self, val: T) void { self.root = self.removeHelper(self.root, val).?; } // 递归删除节点(辅助方法) fn removeHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) ?*inc.TreeNode(T) { var node = node_; if (node == null) return null; // 1. 查找节点并删除 if (val < node.?.val) { node.?.left = self.removeHelper(node.?.left, val); } else if (val > node.?.val) { node.?.right = self.removeHelper(node.?.right, val); } else { if (node.?.left == null or node.?.right == null) { var child = if (node.?.left != null) node.?.left else node.?.right; // 子节点数量 = 0 ,直接删除 node 并返回 if (child == null) { return null; // 子节点数量 = 1 ,直接删除 node } else { node = child; } } else { // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 var temp = node.?.right; while (temp.?.left != null) { temp = temp.?.left; } node.?.right = self.removeHelper(node.?.right, temp.?.val); node.?.val = temp.?.val; } } self.updateHeight(node); // 更新节点高度 // 2. 执行旋转操作,使该子树重新恢复平衡 node = self.rotate(node); // 返回子树的根节点 return node; } // 查找节点 fn search(self: *Self, val: T) ?*inc.TreeNode(T) { var cur = self.root; // 循环查找,越过叶节点后跳出 while (cur != null) { // 目标节点在 cur 的右子树中 if (cur.?.val < val) { cur = cur.?.right; // 目标节点在 cur 的左子树中 } else if (cur.?.val > val) { cur = cur.?.left; // 找到目标节点,跳出循环 } else { break; } } // 返回目标节点 return cur; } }; } pub fn testInsert(comptime T: type, tree_: *AVLTree(T), val: T) !void { var tree = tree_; try tree.insert(val); std.debug.print("\n插入节点 {} 后,AVL 树为\n", .{val}); try inc.PrintUtil.printTree(tree.root, null, false); } pub fn testRemove(comptime T: type, tree_: *AVLTree(T), val: T) void { var tree = tree_; tree.remove(val); std.debug.print("\n删除节点 {} 后,AVL 树为\n", .{val}); try inc.PrintUtil.printTree(tree.root, null, false); } // Driver Code pub fn main() !void { // 初始化空 AVL 树 var avl_tree = AVLTree(i32){}; avl_tree.init(std.heap.page_allocator); defer avl_tree.deinit(); // 插入节点 // 请关注插入节点后,AVL 树是如何保持平衡的 try testInsert(i32, &avl_tree, 1); try testInsert(i32, &avl_tree, 2); try testInsert(i32, &avl_tree, 3); try testInsert(i32, &avl_tree, 4); try testInsert(i32, &avl_tree, 5); try testInsert(i32, &avl_tree, 8); try testInsert(i32, &avl_tree, 7); try testInsert(i32, &avl_tree, 9); try testInsert(i32, &avl_tree, 10); try testInsert(i32, &avl_tree, 6); // 插入重复节点 try testInsert(i32, &avl_tree, 7); // 删除节点 // 请关注删除节点后,AVL 树是如何保持平衡的 testRemove(i32, &avl_tree, 8); // 删除度为 0 的节点 testRemove(i32, &avl_tree, 5); // 删除度为 1 的节点 testRemove(i32, &avl_tree, 4); // 删除度为 2 的节点 // 查找节点 var node = avl_tree.search(7).?; std.debug.print("\n查找到的节点对象为 {any},节点值 = {}\n", .{node, node.val}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_tree/binary_search_tree.zig ================================================ // File: binary_search_tree.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 二叉搜索树 pub fn BinarySearchTree(comptime T: type) type { return struct { const Self = @This(); root: ?*inc.TreeNode(T) = null, mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // 内存分配器 // 构造方法 pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []T) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } std.mem.sort(T, nums, {}, comptime std.sort.asc(T)); // 排序数组 self.root = try self.buildTree(nums, 0, nums.len - 1); // 构建二叉搜索树 } // 析构方法 pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 构建二叉搜索树 fn buildTree(self: *Self, nums: []T, i: usize, j: usize) !?*inc.TreeNode(T) { if (i > j) return null; // 将数组中间节点作为根节点 var mid = i + (j - i) / 2; var node = try self.mem_allocator.create(inc.TreeNode(T)); node.init(nums[mid]); // 递归建立左子树和右子树 if (mid >= 1) node.left = try self.buildTree(nums, i, mid - 1); node.right = try self.buildTree(nums, mid + 1, j); return node; } // 获取二叉树根节点 fn getRoot(self: *Self) ?*inc.TreeNode(T) { return self.root; } // 查找节点 fn search(self: *Self, num: T) ?*inc.TreeNode(T) { var cur = self.root; // 循环查找,越过叶节点后跳出 while (cur != null) { // 目标节点在 cur 的右子树中 if (cur.?.val < num) { cur = cur.?.right; // 目标节点在 cur 的左子树中 } else if (cur.?.val > num) { cur = cur.?.left; // 找到目标节点,跳出循环 } else { break; } } // 返回目标节点 return cur; } // 插入节点 fn insert(self: *Self, num: T) !void { // 若树为空,则初始化根节点 if (self.root == null) { self.root = try self.mem_allocator.create(inc.TreeNode(T)); return; } var cur = self.root; var pre: ?*inc.TreeNode(T) = null; // 循环查找,越过叶节点后跳出 while (cur != null) { // 找到重复节点,直接返回 if (cur.?.val == num) return; pre = cur; // 插入位置在 cur 的右子树中 if (cur.?.val < num) { cur = cur.?.right; // 插入位置在 cur 的左子树中 } else { cur = cur.?.left; } } // 插入节点 var node = try self.mem_allocator.create(inc.TreeNode(T)); node.init(num); if (pre.?.val < num) { pre.?.right = node; } else { pre.?.left = node; } } // 删除节点 fn remove(self: *Self, num: T) void { // 若树为空,直接提前返回 if (self.root == null) return; var cur = self.root; var pre: ?*inc.TreeNode(T) = null; // 循环查找,越过叶节点后跳出 while (cur != null) { // 找到待删除节点,跳出循环 if (cur.?.val == num) break; pre = cur; // 待删除节点在 cur 的右子树中 if (cur.?.val < num) { cur = cur.?.right; // 待删除节点在 cur 的左子树中 } else { cur = cur.?.left; } } // 若无待删除节点,则直接返回 if (cur == null) return; // 子节点数量 = 0 or 1 if (cur.?.left == null or cur.?.right == null) { // 当子节点数量 = 0 / 1 时, child = null / 该子节点 var child = if (cur.?.left != null) cur.?.left else cur.?.right; // 删除节点 cur if (pre.?.left == cur) { pre.?.left = child; } else { pre.?.right = child; } // 子节点数量 = 2 } else { // 获取中序遍历中 cur 的下一个节点 var tmp = cur.?.right; while (tmp.?.left != null) { tmp = tmp.?.left; } var tmp_val = tmp.?.val; // 递归删除节点 tmp self.remove(tmp.?.val); // 用 tmp 覆盖 cur cur.?.val = tmp_val; } } }; } // Driver Code pub fn main() !void { // 初始化二叉树 var nums = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; var bst = BinarySearchTree(i32){}; try bst.init(std.heap.page_allocator, &nums); defer bst.deinit(); std.debug.print("初始化的二叉树为\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); // 查找节点 var node = bst.search(7); std.debug.print("\n查找到的节点对象为 {any},节点值 = {}\n", .{node, node.?.val}); // 插入节点 try bst.insert(16); std.debug.print("\n插入节点 16 后,二叉树为\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); // 删除节点 bst.remove(1); std.debug.print("\n删除节点 1 后,二叉树为\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); bst.remove(2); std.debug.print("\n删除节点 2 后,二叉树为\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); bst.remove(4); std.debug.print("\n删除节点 4 后,二叉树为\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_tree/binary_tree.zig ================================================ // File: binary_tree.zig // Created Time: 2023-01-14 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // 初始化二叉树 // 初始化节点 var n1 = inc.TreeNode(i32){ .val = 1 }; var n2 = inc.TreeNode(i32){ .val = 2 }; var n3 = inc.TreeNode(i32){ .val = 3 }; var n4 = inc.TreeNode(i32){ .val = 4 }; var n5 = inc.TreeNode(i32){ .val = 5 }; // 构建节点之间的引用(指针) n1.left = &n2; n1.right = &n3; n2.left = &n4; n2.right = &n5; std.debug.print("初始化二叉树\n", .{}); try inc.PrintUtil.printTree(&n1, null, false); // 插入与删除节点 var p = inc.TreeNode(i32){ .val = 0 }; // 在 n1 -> n2 中间插入节点 P n1.left = &p; p.left = &n2; std.debug.print("插入节点 P 后\n", .{}); try inc.PrintUtil.printTree(&n1, null, false); // 删除节点 n1.left = &n2; std.debug.print("删除节点 P 后\n", .{}); try inc.PrintUtil.printTree(&n1, null, false); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_tree/binary_tree_bfs.zig ================================================ // File: binary_tree_bfs.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 层序遍历 fn levelOrder(comptime T: type, mem_allocator: std.mem.Allocator, root: *inc.TreeNode(T)) !std.ArrayList(T) { // 初始化队列,加入根节点 const L = std.TailQueue(*inc.TreeNode(T)); var queue = L{}; var root_node = try mem_allocator.create(L.Node); root_node.data = root; queue.append(root_node); // 初始化一个列表,用于保存遍历序列 var list = std.ArrayList(T).init(std.heap.page_allocator); while (queue.len > 0) { var queue_node = queue.popFirst().?; // 队列出队 var node = queue_node.data; try list.append(node.val); // 保存节点值 if (node.left != null) { var tmp_node = try mem_allocator.create(L.Node); tmp_node.data = node.left.?; queue.append(tmp_node); // 左子节点入队 } if (node.right != null) { var tmp_node = try mem_allocator.create(L.Node); tmp_node.data = node.right.?; queue.append(tmp_node); // 右子节点入队 } } return list; } // Driver Code pub fn main() !void { // 初始化内存分配器 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // 初始化二叉树 // 这里借助了一个从数组直接生成二叉树的函数 var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); std.debug.print("初始化二叉树\n", .{}); try inc.PrintUtil.printTree(root, null, false); // 层序遍历 var list = try levelOrder(i32, mem_allocator, root.?); defer list.deinit(); std.debug.print("\n层序遍历的节点打印序列 = ", .{}); inc.PrintUtil.printList(i32, list); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/chapter_tree/binary_tree_dfs.zig ================================================ // File: binary_tree_dfs.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); var list = std.ArrayList(i32).init(std.heap.page_allocator); // 前序遍历 fn preOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { if (root == null) return; // 访问优先级:根节点 -> 左子树 -> 右子树 try list.append(root.?.val); try preOrder(T, root.?.left); try preOrder(T, root.?.right); } // 中序遍历 fn inOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { if (root == null) return; // 访问优先级:左子树 -> 根节点 -> 右子树 try inOrder(T, root.?.left); try list.append(root.?.val); try inOrder(T, root.?.right); } // 后序遍历 fn postOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { if (root == null) return; // 访问优先级:左子树 -> 右子树 -> 根节点 try postOrder(T, root.?.left); try postOrder(T, root.?.right); try list.append(root.?.val); } // Driver Code pub fn main() !void { // 初始化内存分配器 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // 初始化二叉树 // 这里借助了一个从数组直接生成二叉树的函数 var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); std.debug.print("初始化二叉树\n", .{}); try inc.PrintUtil.printTree(root, null, false); // 前序遍历 list.clearRetainingCapacity(); try preOrder(i32, root); std.debug.print("\n前序遍历的节点打印序列 = ", .{}); inc.PrintUtil.printList(i32, list); // 中序遍历 list.clearRetainingCapacity(); try inOrder(i32, root); std.debug.print("\n中序遍历的节点打印序列 = ", .{}); inc.PrintUtil.printList(i32, list); // 后序遍历 list.clearRetainingCapacity(); try postOrder(i32, root); std.debug.print("\n后续遍历的节点打印序列 = ", .{}); inc.PrintUtil.printList(i32, list); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: codes/zig/include/PrintUtil.zig ================================================ // File: PrintUtil.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); pub const ListUtil = @import("ListNode.zig"); pub const ListNode = ListUtil.ListNode; pub const TreeUtil = @import("TreeNode.zig"); pub const TreeNode = TreeUtil.TreeNode; // 打印队列 pub fn printQueue(comptime T: type, queue: std.TailQueue(T)) void { var node = queue.first; std.debug.print("[", .{}); var i: i32 = 0; while (node != null) : (i += 1) { var data = node.?.data; std.debug.print("{}{s}", .{ data, if (i == queue.len - 1) "]" else ", " }); node = node.?.next; } } // 打印哈希表 pub fn printHashMap(comptime TKey: type, comptime TValue: type, map: std.AutoHashMap(TKey, TValue)) void { var it = map.iterator(); while (it.next()) |kv| { var key = kv.key_ptr.*; var value = kv.value_ptr.*; std.debug.print("{} -> {s}\n", .{ key, value }); } } // 打印堆 pub fn printHeap(comptime T: type, mem_allocator: std.mem.Allocator, queue: anytype) !void { var arr = queue.items; var len = queue.len; std.debug.print("堆的数组表示:", .{}); printArray(T, arr[0..len]); std.debug.print("\n堆的树状表示:\n", .{}); var root = try TreeUtil.arrToTree(T, mem_allocator, arr[0..len]); try printTree(root, null, false); } ================================================ FILE: codes/zig/include/include.zig ================================================ // File: include.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) pub const PrintUtil = @import("PrintUtil.zig"); pub const TreeUtil = @import("TreeNode.zig"); pub const TreeNode = TreeUtil.TreeNode; ================================================ FILE: codes/zig/main.zig ================================================ const std = @import("std"); const iteration = @import("chapter_computational_complexity/iteration.zig"); const recursion = @import("chapter_computational_complexity/recursion.zig"); const time_complexity = @import("chapter_computational_complexity/time_complexity.zig"); const space_complexity = @import("chapter_computational_complexity/space_complexity.zig"); const worst_best_time_complexity = @import("chapter_computational_complexity/worst_best_time_complexity.zig"); const array = @import("chapter_array_and_linkedlist/array.zig"); const linked_list = @import("chapter_array_and_linkedlist/linked_list.zig"); const list = @import("chapter_array_and_linkedlist/list.zig"); const my_list = @import("chapter_array_and_linkedlist/my_list.zig"); pub fn main() !void { try iteration.run(); recursion.run(); time_complexity.run(); try space_complexity.run(); worst_best_time_complexity.run(); try array.run(); linked_list.run(); try list.run(); try my_list.run(); } ================================================ FILE: codes/zig/utils/ListNode.zig ================================================ // File: ListNode.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // 链表节点 pub fn ListNode(comptime T: type) type { return struct { const Self = @This(); val: T = 0, next: ?*Self = null, // Initialize a list node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.next = null; } }; } // 将列表反序列化为链表 pub fn listToLinkedList(comptime T: type, allocator: std.mem.Allocator, list: std.ArrayList(T)) !?*ListNode(T) { var dum = try allocator.create(ListNode(T)); dum.init(0); var head = dum; for (list.items) |val| { var tmp = try allocator.create(ListNode(T)); tmp.init(val); head.next = tmp; head = head.next.?; } return dum.next; } // 将数组反序列化为链表 pub fn arrToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*ListNode(T) { var dum = try mem_allocator.create(ListNode(T)); dum.init(0); var head = dum; for (arr) |val| { var tmp = try mem_allocator.create(ListNode(T)); tmp.init(val); head.next = tmp; head = head.next.?; } return dum.next; } ================================================ FILE: codes/zig/utils/TreeNode.zig ================================================ // File: TreeNode.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // 二叉树节点 pub fn TreeNode(comptime T: type) type { return struct { const Self = @This(); val: T = undefined, // 节点值 height: i32 = undefined, // 节点高度 left: ?*Self = null, // 左子节点指针 right: ?*Self = null, // 右子节点指针 // Initialize a tree node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.height = 0; self.left = null; self.right = null; } }; } // 将数组反序列化为二叉树 pub fn arrToTree(comptime T: type, allocator: std.mem.Allocator, arr: []T) !?*TreeNode(T) { if (arr.len == 0) return null; var root = try allocator.create(TreeNode(T)); root.init(arr[0]); const L = std.TailQueue(*TreeNode(T)); var que = L{}; var root_node = try allocator.create(L.Node); root_node.data = root; que.append(root_node); var index: usize = 0; while (que.len > 0) { const que_node = que.popFirst().?; var node = que_node.data; index += 1; if (index >= arr.len) break; if (index < arr.len) { var tmp = try allocator.create(TreeNode(T)); tmp.init(arr[index]); node.left = tmp; var tmp_node = try allocator.create(L.Node); tmp_node.data = node.left.?; que.append(tmp_node); } index += 1; if (index >= arr.len) break; if (index < arr.len) { var tmp = try allocator.create(TreeNode(T)); tmp.init(arr[index]); node.right = tmp; var tmp_node = try allocator.create(L.Node); tmp_node.data = node.right.?; que.append(tmp_node); } } return root; } ================================================ FILE: codes/zig/utils/format.zig ================================================ // File: format.zig // Created Time: 2025-07-19 // Author: CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const ListNode = @import("ListNode.zig").ListNode; const TreeNode = @import("TreeNode.zig").TreeNode; pub fn slice(items: anytype) SliceFormatter(@TypeOf(items)) { return .{ .items = items }; } pub fn SliceFormatter(comptime SliceType: type) type { return struct { const Self = @This(); items: SliceType, pub fn format( self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) !void { try writer.writeAll("["); if (self.items.len > 0) { for (self.items, 0..) |item, i| { try std.fmt.format(writer, "{}", .{item}); if (i != self.items.len - 1) { try writer.writeAll(", "); } } } try writer.writeAll("]"); } }; } pub fn linkedList(comptime T: type, head: *const ListNode(T)) LinkedListFormatter(T) { return .{ .head = head }; } pub fn LinkedListFormatter(comptime T: type) type { return struct { const Self = @This(); head: *const ListNode(T), pub fn format( self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) !void { try printLinkedList(self.head, writer); } pub fn printLinkedList(head: *const ListNode(T), writer: anytype) !void { try std.fmt.format(writer, "{}", .{head.val}); if (head.next) |next_node| { try writer.writeAll("->"); try printLinkedList(next_node, writer); } } }; } pub fn tree(comptime T: type, root: ?*const TreeNode(T)) TreeFormatter(T) { return .{ .root = root }; } pub fn TreeFormatter(comptime T: type) type { return struct { const Self = @This(); root: ?*const TreeNode(T), pub fn format( self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) !void { try printTree(self.root, null, false, writer); } // 打印二叉树 fn printTree(root: ?*const TreeNode(T), prev: ?*Trunk, isRight: bool, writer: anytype) !void { if (root == null) { return; } var prev_str = " "; var trunk = Trunk{ .prev = prev, .str = prev_str }; try printTree(root.?.right, &trunk, true, writer); if (prev == null) { trunk.str = "———"; } else if (isRight) { trunk.str = "/———"; prev_str = " |"; } else { trunk.str = "\\———"; prev.?.str = prev_str; } try showTrunks(&trunk, writer); try std.fmt.format(writer, "{d}\n", .{root.?.val}); if (prev) |_| { prev.?.str = prev_str; } trunk.str = " |"; try printTree(root.?.left, &trunk, false, writer); } // 打印二叉树 // This tree printer is borrowed from TECHIE DELIGHT // https://www.techiedelight.com/c-program-print-binary-tree/ const Trunk = struct { prev: ?*Trunk = null, str: []const u8 = undefined, pub fn init(self: *Trunk, prev: ?*Trunk, str: []const u8) void { self.prev = prev; self.str = str; } }; pub fn showTrunks(p: ?*Trunk, writer: anytype) !void { if (p == null) return; try showTrunks(p.?.prev, writer); try std.fmt.format(writer, "{s}", .{p.?.str}); } }; } ================================================ FILE: codes/zig/utils/utils.zig ================================================ // File: format.zig // Created Time: 2025-07-15 // Author: CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); pub const fmt = @import("format.zig"); pub const ListNode = @import("ListNode.zig").ListNode; pub const TreeNode = @import("TreeNode.zig").TreeNode; ================================================ FILE: docker-compose.yml ================================================ version: '3' services: hello-algo: build: . image: hello-algo container_name: hello-algo ports: - "8000:8000" ================================================ FILE: docs/chapter_appendix/contribution.md ================================================ # 一起参与创作 由于笔者能力有限,书中难免存在一些遗漏和错误,请您谅解。如果您发现了笔误、链接失效、内容缺失、文字歧义、解释不清晰或行文结构不合理等问题,请协助我们进行修正,以给读者提供更优质的学习资源。 所有[撰稿人](https://github.com/krahets/hello-algo/graphs/contributors)的 GitHub ID 将在本书仓库、网页版和 PDF 版的主页上进行展示,以感谢他们对开源社区的无私奉献。 !!! success "开源的魅力" 纸质图书的两次印刷的间隔时间往往较久,内容更新非常不方便。 而在本开源书中,内容更迭的时间被缩短至数日甚至几个小时。 ### 内容微调 如下图所示,每个页面的右上角都有“编辑图标”。您可以按照以下步骤修改文本或代码。 1. 点击“编辑图标”,如果遇到“需要 Fork 此仓库”的提示,请同意该操作。 2. 修改 Markdown 源文件内容,检查内容的正确性,并尽量保持排版格式的统一。 3. 在页面底部填写修改说明,然后点击“Propose file change”按钮。页面跳转后,点击“Create pull request”按钮即可发起拉取请求。 ![页面编辑按键](contribution.assets/edit_markdown.png) 图片无法直接修改,需要通过新建 [Issue](https://github.com/krahets/hello-algo/issues) 或评论留言来描述问题,我们会尽快重新绘制并替换图片。 ### 内容创作 如果您有兴趣参与此开源项目,包括将代码翻译成其他编程语言、扩展文章内容等,那么需要实施以下 Pull Request 工作流程。 1. 登录 GitHub ,将本书的[代码仓库](https://github.com/krahets/hello-algo) Fork 到个人账号下。 2. 进入您的 Fork 仓库网页,使用 `git clone` 命令将仓库克隆至本地。 3. 在本地进行内容创作,并进行完整测试,验证代码的正确性。 4. 将本地所做更改 Commit ,然后 Push 至远程仓库。 5. 刷新仓库网页,点击“Create pull request”按钮即可发起拉取请求。 ### Docker 部署 在 `hello-algo` 根目录下,执行以下 Docker 脚本,即可在 `http://localhost:8000` 访问本项目: ```shell docker-compose up -d ``` 使用以下命令即可删除部署: ```shell docker-compose down ``` ================================================ FILE: docs/chapter_appendix/index.md ================================================ # 附录 ![附录](../assets/covers/chapter_appendix.jpg) ================================================ FILE: docs/chapter_appendix/installation.md ================================================ # 编程环境安装 ## 安装 IDE 推荐使用开源、轻量的 VS Code 作为本地集成开发环境(IDE)。访问 [VS Code 官网](https://code.visualstudio.com/),根据操作系统选择相应版本的 VS Code 进行下载和安装。 ![从官网下载 VS Code](installation.assets/vscode_installation.png) VS Code 拥有强大的扩展包生态系统,支持大多数编程语言的运行和调试。以 Python 为例,安装“Python Extension Pack”扩展包之后,即可进行 Python 代码调试。安装步骤如下图所示。 ![安装 VS Code 扩展包](installation.assets/vscode_extension_installation.png) ## 安装语言环境 ### Python 环境 1. 下载并安装 [Miniconda3](https://docs.conda.io/en/latest/miniconda.html) ,需要 Python 3.10 或更新版本。 2. 在 VS Code 的插件市场中搜索 `python` ,安装 Python Extension Pack 。 3. (可选)在命令行输入 `pip install black` ,安装代码格式化工具。 ### C/C++ 环境 1. Windows 系统需要安装 [MinGW](https://sourceforge.net/projects/mingw-w64/files/)([配置教程](https://blog.csdn.net/qq_33698226/article/details/129031241));MacOS 自带 Clang ,无须安装。 2. 在 VS Code 的插件市场中搜索 `c++` ,安装 C/C++ Extension Pack 。 3. (可选)打开 Settings 页面,搜索 `Clang_format_fallback Style` 代码格式化选项,设置为 `{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }` 。 ### Java 环境 1. 下载并安装 [OpenJDK](https://jdk.java.net/18/)(版本需满足 > JDK 9)。 2. 在 VS Code 的插件市场中搜索 `java` ,安装 Extension Pack for Java 。 ### C# 环境 1. 下载并安装 [.Net 8.0](https://dotnet.microsoft.com/en-us/download) 。 2. 在 VS Code 的插件市场中搜索 `C# Dev Kit` ,安装 C# Dev Kit ([配置教程](https://code.visualstudio.com/docs/csharp/get-started))。 3. 也可使用 Visual Studio([安装教程](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022))。 ### Go 环境 1. 下载并安装 [go](https://go.dev/dl/) 。 2. 在 VS Code 的插件市场中搜索 `go` ,安装 Go 。 3. 按快捷键 `Ctrl + Shift + P` 呼出命令栏,输入 go ,选择 `Go: Install/Update Tools` ,全部勾选并安装即可。 ### Swift 环境 1. 下载并安装 [Swift](https://www.swift.org/download/) 。 2. 在 VS Code 的插件市场中搜索 `swift` ,安装 [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang) 。 ### JavaScript 环境 1. 下载并安装 [Node.js](https://nodejs.org/en/) 。 2. (可选)在 VS Code 的插件市场中搜索 `Prettier` ,安装代码格式化工具。 ### TypeScript 环境 1. 同 JavaScript 环境安装步骤。 2. 安装 [TypeScript Execute (tsx)](https://github.com/privatenumber/tsx?tab=readme-ov-file#global-installation) 。 3. 在 VS Code 的插件市场中搜索 `typescript` ,安装 [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) 。 ### Dart 环境 1. 下载并安装 [Dart](https://dart.dev/get-dart) 。 2. 在 VS Code 的插件市场中搜索 `dart` ,安装 [Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code) 。 ### Rust 环境 1. 下载并安装 [Rust](https://www.rust-lang.org/tools/install) 。 2. 在 VS Code 的插件市场中搜索 `rust` ,安装 [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) 。 ================================================ FILE: docs/chapter_appendix/terminology.md ================================================ # 术语表 下表列出了书中出现的重要术语,值得注意以下几点。 - 建议记住名词的英文叫法,以便阅读英文文献。 - 部分名词在简体中文和繁体中文下的叫法不同。

  数据结构与算法的重要名词

| English | 简体中文 | 繁体中文 | | ------------------------------ | -------------- | -------------- | | algorithm | 算法 | 演算法 | | data structure | 数据结构 | 資料結構 | | code | 代码 | 程式碼 | | file | 文件 | 檔案 | | function | 函数 | 函式 | | method | 方法 | 方法 | | variable | 变量 | 變數 | | asymptotic complexity analysis | 渐近复杂度分析 | 漸近複雜度分析 | | time complexity | 时间复杂度 | 時間複雜度 | | space complexity | 空间复杂度 | 空間複雜度 | | loop | 循环 | 迴圈 | | iteration | 迭代 | 迭代 | | recursion | 递归 | 遞迴 | | tail recursion | 尾递归 | 尾遞迴 | | recursion tree | 递归树 | 遞迴樹 | | big-$O$ notation | 大 $O$ 记号 | 大 $O$ 記號 | | asymptotic upper bound | 渐近上界 | 漸近上界 | | sign-magnitude | 原码 | 原碼 | | 1’s complement | 反码 | 一補數 | | 2’s complement | 补码 | 二補數 | | array | 数组 | 陣列 | | index | 索引 | 索引 | | linked list | 链表 | 鏈結串列 | | linked list node, list node | 链表节点 | 鏈結串列節點 | | head node | 头节点 | 頭節點 | | tail node | 尾节点 | 尾節點 | | list | 列表 | 串列 | | dynamic array | 动态数组 | 動態陣列 | | hard disk | 硬盘 | 硬碟 | | random-access memory (RAM) | 内存 | 記憶體 | | cache memory | 缓存 | 快取 | | cache miss | 缓存未命中 | 快取未命中 | | cache hit rate | 缓存命中率 | 快取命中率 | | stack | 栈 | 堆疊 | | top of the stack | 栈顶 | 堆疊頂 | | bottom of the stack | 栈底 | 堆疊底 | | queue | 队列 | 佇列 | | double-ended queue | 双向队列 | 雙向佇列 | | front of the queue | 队首 | 佇列首 | | rear of the queue | 队尾 | 佇列尾 | | hash table | 哈希表 | 雜湊表 | | hash set | 哈希集合 | 雜湊集合 | | bucket | 桶 | 桶 | | hash function | 哈希函数 | 雜湊函式 | | hash collision | 哈希冲突 | 雜湊衝突 | | load factor | 负载因子 | 負載因子 | | separate chaining | 链式地址 | 鏈結位址 | | open addressing | 开放寻址 | 開放定址 | | linear probing | 线性探测 | 線性探查 | | lazy deletion | 懒删除 | 懶刪除 | | binary tree | 二叉树 | 二元樹 | | tree node | 树节点 | 樹節點 | | left-child node | 左子节点 | 左子節點 | | right-child node | 右子节点 | 右子節點 | | parent node | 父节点 | 父節點 | | left subtree | 左子树 | 左子樹 | | right subtree | 右子树 | 右子樹 | | root node | 根节点 | 根節點 | | leaf node | 叶节点 | 葉節點 | | edge | 边 | 邊 | | level | 层 | 層 | | degree | 度 | 度 | | height | 高度 | 高度 | | depth | 深度 | 深度 | | perfect binary tree | 完美二叉树 | 完美二元樹 | | complete binary tree | 完全二叉树 | 完全二元樹 | | full binary tree | 完满二叉树 | 完滿二元樹 | | balanced binary tree | 平衡二叉树 | 平衡二元樹 | | binary search tree | 二叉搜索树 | 二元搜尋樹 | | AVL tree | AVL 树 | AVL 樹 | | red-black tree | 红黑树 | 紅黑樹 | | level-order traversal | 层序遍历 | 層序走訪 | | breadth-first traversal | 广度优先遍历 | 廣度優先走訪 | | depth-first traversal | 深度优先遍历 | 深度優先走訪 | | binary search tree | 二叉搜索树 | 二元搜尋樹 | | balanced binary search tree | 平衡二叉搜索树 | 平衡二元搜尋樹 | | balance factor | 平衡因子 | 平衡因子 | | heap | 堆 | 堆積 | | max heap | 大顶堆 | 大頂堆積 | | min heap | 小顶堆 | 小頂堆積 | | priority queue | 优先队列 | 優先佇列 | | heapify | 堆化 | 堆積化 | | top-$k$ problem | Top-$k$ 问题 | Top-$k$ 問題 | | graph | 图 | 圖 | | vertex | 顶点 | 頂點 | | undirected graph | 无向图 | 無向圖 | | directed graph | 有向图 | 有向圖 | | connected graph | 连通图 | 連通圖 | | disconnected graph | 非连通图 | 非連通圖 | | weighted graph | 有权图 | 有權圖 | | adjacency | 邻接 | 鄰接 | | path | 路径 | 路徑 | | in-degree | 入度 | 入度 | | out-degree | 出度 | 出度 | | adjacency matrix | 邻接矩阵 | 鄰接矩陣 | | adjacency list | 邻接表 | 鄰接表 | | breadth-first search | 广度优先搜索 | 廣度優先搜尋 | | depth-first search | 深度优先搜索 | 深度優先搜尋 | | binary search | 二分查找 | 二分搜尋 | | searching algorithm | 搜索算法 | 搜尋演算法 | | sorting algorithm | 排序算法 | 排序演算法 | | selection sort | 选择排序 | 選擇排序 | | bubble sort | 冒泡排序 | 泡沫排序 | | insertion sort | 插入排序 | 插入排序 | | quick sort | 快速排序 | 快速排序 | | merge sort | 归并排序 | 合併排序 | | heap sort | 堆排序 | 堆積排序 | | bucket sort | 桶排序 | 桶排序 | | counting sort | 计数排序 | 計數排序 | | radix sort | 基数排序 | 基數排序 | | divide and conquer | 分治 | 分治 | | hanota problem | 汉诺塔问题 | 河內塔問題 | | backtracking algorithm | 回溯算法 | 回溯演算法 | | constraint | 约束 | 約束 | | solution | 解 | 解 | | state | 状态 | 狀態 | | pruning | 剪枝 | 剪枝 | | permutations problem | 全排列问题 | 全排列問題 | | subset-sum problem | 子集和问题 | 子集合問題 | | $n$-queens problem | $n$ 皇后问题 | $n$ 皇后問題 | | dynamic programming | 动态规划 | 動態規劃 | | initial state | 初始状态 | 初始狀態 | | state-transition equation | 状态转移方程 | 狀態轉移方程 | | knapsack problem | 背包问题 | 背包問題 | | edit distance problem | 编辑距离问题 | 編輯距離問題 | | greedy algorithm | 贪心算法 | 貪婪演算法 | ================================================ FILE: docs/chapter_array_and_linkedlist/array.md ================================================ # 数组 数组(array)是一种线性数据结构,其将相同类型的元素存储在连续的内存空间中。我们将元素在数组中的位置称为该元素的索引(index)。下图展示了数组的主要概念和存储方式。 ![数组定义与存储方式](array.assets/array_definition.png) ## 数组常用操作 ### 初始化数组 我们可以根据需求选用数组的两种初始化方式:无初始值、给定初始值。在未指定初始值的情况下,大多数编程语言会将数组元素初始化为 $0$ : === "Python" ```python title="array.py" # 初始化数组 arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] nums: list[int] = [1, 3, 2, 5, 4] ``` === "C++" ```cpp title="array.cpp" /* 初始化数组 */ // 存储在栈上 int arr[5]; int nums[5] = { 1, 3, 2, 5, 4 }; // 存储在堆上(需要手动释放空间) int* arr1 = new int[5]; int* nums1 = new int[5] { 1, 3, 2, 5, 4 }; ``` === "Java" ```java title="array.java" /* 初始化数组 */ int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } int[] nums = { 1, 3, 2, 5, 4 }; ``` === "C#" ```csharp title="array.cs" /* 初始化数组 */ int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ] int[] nums = [1, 3, 2, 5, 4]; ``` === "Go" ```go title="array.go" /* 初始化数组 */ var arr [5]int // 在 Go 中,指定长度时([5]int)为数组,不指定长度时([]int)为切片 // 由于 Go 的数组被设计为在编译期确定长度,因此只能使用常量来指定长度 // 为了方便实现扩容 extend() 方法,以下将切片(Slice)看作数组(Array) nums := []int{1, 3, 2, 5, 4} ``` === "Swift" ```swift title="array.swift" /* 初始化数组 */ let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] let nums = [1, 3, 2, 5, 4] ``` === "JS" ```javascript title="array.js" /* 初始化数组 */ var arr = new Array(5).fill(0); var nums = [1, 3, 2, 5, 4]; ``` === "TS" ```typescript title="array.ts" /* 初始化数组 */ let arr: number[] = new Array(5).fill(0); let nums: number[] = [1, 3, 2, 5, 4]; ``` === "Dart" ```dart title="array.dart" /* 初始化数组 */ List arr = List.filled(5, 0); // [0, 0, 0, 0, 0] List nums = [1, 3, 2, 5, 4]; ``` === "Rust" ```rust title="array.rs" /* 初始化数组 */ let arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0] let slice: &[i32] = &[0; 5]; // 在 Rust 中,指定长度时([i32; 5])为数组,不指定长度时(&[i32])为切片 // 由于 Rust 的数组被设计为在编译期确定长度,因此只能使用常量来指定长度 // Vector 是 Rust 一般情况下用作动态数组的类型 // 为了方便实现扩容 extend() 方法,以下将 vector 看作数组(array) let nums: Vec = vec![1, 3, 2, 5, 4]; ``` === "C" ```c title="array.c" /* 初始化数组 */ int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 } int nums[5] = { 1, 3, 2, 5, 4 }; ``` === "Kotlin" ```kotlin title="array.kt" /* 初始化数组 */ var arr = IntArray(5) // { 0, 0, 0, 0, 0 } var nums = intArrayOf(1, 3, 2, 5, 4) ``` === "Ruby" ```ruby title="array.rb" # 初始化数组 arr = Array.new(5, 0) nums = [1, 3, 2, 5, 4] ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0Aarr%20%3D%20%5B0%5D%20*%205%20%20%23%20%5B%200,%200,%200,%200,%200%20%5D%0Anums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 访问元素 数组元素被存储在连续的内存空间中,这意味着计算数组元素的内存地址非常容易。给定数组内存地址(首元素内存地址)和某个元素的索引,我们可以使用下图所示的公式计算得到该元素的内存地址,从而直接访问该元素。 ![数组元素的内存地址计算](array.assets/array_memory_location_calculation.png) 观察上图,我们发现数组首个元素的索引为 $0$ ,这似乎有些反直觉,因为从 $1$ 开始计数会更自然。但从地址计算公式的角度看,**索引本质上是内存地址的偏移量**。首个元素的地址偏移量是 $0$ ,因此它的索引为 $0$ 是合理的。 在数组中访问元素非常高效,我们可以在 $O(1)$ 时间内随机访问数组中的任意一个元素。 ```src [file]{array}-[class]{}-[func]{random_access} ``` ### 插入元素 数组元素在内存中是“紧挨着的”,它们之间没有空间再存放任何数据。如下图所示,如果想在数组中间插入一个元素,则需要将该元素之后的所有元素都向后移动一位,之后再把元素赋值给该索引。 ![数组插入元素示例](array.assets/array_insert_element.png) 值得注意的是,由于数组的长度是固定的,因此插入一个元素必定会导致数组尾部元素“丢失”。我们将这个问题的解决方案留在“列表”章节中讨论。 ```src [file]{array}-[class]{}-[func]{insert} ``` ### 删除元素 同理,如下图所示,若想删除索引 $i$ 处的元素,则需要把索引 $i$ 之后的元素都向前移动一位。 ![数组删除元素示例](array.assets/array_remove_element.png) 请注意,删除元素完成后,原先末尾的元素变得“无意义”了,所以我们无须特意去修改它。 ```src [file]{array}-[class]{}-[func]{remove} ``` 总的来看,数组的插入与删除操作有以下缺点。 - **时间复杂度高**:数组的插入和删除的平均时间复杂度均为 $O(n)$ ,其中 $n$ 为数组长度。 - **丢失元素**:由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会丢失。 - **内存浪费**:我们可以初始化一个比较长的数组,只用前面一部分,这样在插入数据时,丢失的末尾元素都是“无意义”的,但这样做会造成部分内存空间浪费。 ### 遍历数组 在大多数编程语言中,我们既可以通过索引遍历数组,也可以直接遍历获取数组中的每个元素: ```src [file]{array}-[class]{}-[func]{traverse} ``` ### 查找元素 在数组中查找指定元素需要遍历数组,每轮判断元素值是否匹配,若匹配则输出对应索引。 因为数组是线性数据结构,所以上述查找操作被称为“线性查找”。 ```src [file]{array}-[class]{}-[func]{find} ``` ### 扩容数组 在复杂的系统环境中,程序难以保证数组之后的内存空间是可用的,从而无法安全地扩展数组容量。因此在大多数编程语言中,**数组的长度是不可变的**。 如果我们希望扩容数组,则需重新建立一个更大的数组,然后把原数组元素依次复制到新数组。这是一个 $O(n)$ 的操作,在数组很大的情况下非常耗时。代码如下所示: ```src [file]{array}-[class]{}-[func]{extend} ``` ## 数组的优点与局限性 数组存储在连续的内存空间内,且元素类型相同。这种做法包含丰富的先验信息,系统可以利用这些信息来优化数据结构的操作效率。 - **空间效率高**:数组为数据分配了连续的内存块,无须额外的结构开销。 - **支持随机访问**:数组允许在 $O(1)$ 时间内访问任何元素。 - **缓存局部性**:当访问数组元素时,计算机不仅会加载它,还会缓存其周围的其他数据,从而借助高速缓存来提升后续操作的执行速度。 连续空间存储是一把双刃剑,其存在以下局限性。 - **插入与删除效率低**:当数组中元素较多时,插入与删除操作需要移动大量的元素。 - **长度不可变**:数组在初始化后长度就固定了,扩容数组需要将所有数据复制到新数组,开销很大。 - **空间浪费**:如果数组分配的大小超过实际所需,那么多余的空间就被浪费了。 ## 数组典型应用 数组是一种基础且常见的数据结构,既频繁应用在各类算法之中,也可用于实现各种复杂数据结构。 - **随机访问**:如果我们想随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现随机抽样。 - **排序和搜索**:数组是排序和搜索算法最常用的数据结构。快速排序、归并排序、二分查找等都主要在数组上进行。 - **查找表**:当需要快速查找一个元素或其对应关系时,可以使用数组作为查找表。假如我们想实现字符到 ASCII 码的映射,则可以将字符的 ASCII 码值作为索引,对应的元素存放在数组中的对应位置。 - **机器学习**:神经网络中大量使用了向量、矩阵、张量之间的线性代数运算,这些数据都是以数组的形式构建的。数组是神经网络编程中最常使用的数据结构。 - **数据结构实现**:数组可以用于实现栈、队列、哈希表、堆、图等数据结构。例如,图的邻接矩阵表示实际上是一个二维数组。 ================================================ FILE: docs/chapter_array_and_linkedlist/index.md ================================================ # 数组与链表 ![数组与链表](../assets/covers/chapter_array_and_linkedlist.jpg) !!! abstract 数据结构的世界如同一堵厚实的砖墙。 数组的砖块整齐排列,逐个紧贴。链表的砖块分散各处,连接的藤蔓自由地穿梭于砖缝之间。 ================================================ FILE: docs/chapter_array_and_linkedlist/linked_list.md ================================================ # 链表 内存空间是所有程序的公共资源,在一个复杂的系统运行环境下,空闲的内存空间可能散落在内存各处。我们知道,存储数组的内存空间必须是连续的,而当数组非常大时,内存可能无法提供如此大的连续空间。此时链表的灵活性优势就体现出来了。 链表(linked list)是一种线性数据结构,其中的每个元素都是一个节点对象,各个节点通过“引用”相连接。引用记录了下一个节点的内存地址,通过它可以从当前节点访问到下一个节点。 链表的设计使得各个节点可以分散存储在内存各处,它们的内存地址无须连续。 ![链表定义与存储方式](linked_list.assets/linkedlist_definition.png) 观察上图,链表的组成单位是节点(node)对象。每个节点都包含两项数据:节点的“值”和指向下一节点的“引用”。 - 链表的首个节点被称为“头节点”,最后一个节点被称为“尾节点”。 - 尾节点指向的是“空”,它在 Java、C++ 和 Python 中分别被记为 `null`、`nullptr` 和 `None` 。 - 在 C、C++、Go 和 Rust 等支持指针的语言中,上述“引用”应被替换为“指针”。 如以下代码所示,链表节点 `ListNode` 除了包含值,还需额外保存一个引用(指针)。因此在相同数据量下,**链表比数组占用更多的内存空间**。 === "Python" ```python title="" class ListNode: """链表节点类""" def __init__(self, val: int): self.val: int = val # 节点值 self.next: ListNode | None = None # 指向下一节点的引用 ``` === "C++" ```cpp title="" /* 链表节点结构体 */ struct ListNode { int val; // 节点值 ListNode *next; // 指向下一节点的指针 ListNode(int x) : val(x), next(nullptr) {} // 构造函数 }; ``` === "Java" ```java title="" /* 链表节点类 */ class ListNode { int val; // 节点值 ListNode next; // 指向下一节点的引用 ListNode(int x) { val = x; } // 构造函数 } ``` === "C#" ```csharp title="" /* 链表节点类 */ class ListNode(int x) { //构造函数 int val = x; // 节点值 ListNode? next; // 指向下一节点的引用 } ``` === "Go" ```go title="" /* 链表节点结构体 */ type ListNode struct { Val int // 节点值 Next *ListNode // 指向下一节点的指针 } // NewListNode 构造函数,创建一个新的链表 func NewListNode(val int) *ListNode { return &ListNode{ Val: val, Next: nil, } } ``` === "Swift" ```swift title="" /* 链表节点类 */ class ListNode { var val: Int // 节点值 var next: ListNode? // 指向下一节点的引用 init(x: Int) { // 构造函数 val = x } } ``` === "JS" ```javascript title="" /* 链表节点类 */ class ListNode { constructor(val, next) { this.val = (val === undefined ? 0 : val); // 节点值 this.next = (next === undefined ? null : next); // 指向下一节点的引用 } } ``` === "TS" ```typescript title="" /* 链表节点类 */ class ListNode { val: number; next: ListNode | null; constructor(val?: number, next?: ListNode | null) { this.val = val === undefined ? 0 : val; // 节点值 this.next = next === undefined ? null : next; // 指向下一节点的引用 } } ``` === "Dart" ```dart title="" /* 链表节点类 */ class ListNode { int val; // 节点值 ListNode? next; // 指向下一节点的引用 ListNode(this.val, [this.next]); // 构造函数 } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* 链表节点类 */ #[derive(Debug)] struct ListNode { val: i32, // 节点值 next: Option>>, // 指向下一节点的指针 } ``` === "C" ```c title="" /* 链表节点结构体 */ typedef struct ListNode { int val; // 节点值 struct ListNode *next; // 指向下一节点的指针 } ListNode; /* 构造函数 */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *) malloc(sizeof(ListNode)); node->val = val; node->next = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* 链表节点类 */ // 构造方法 class ListNode(x: Int) { val _val: Int = x // 节点值 val next: ListNode? = null // 指向下一个节点的引用 } ``` === "Ruby" ```ruby title="" # 链表节点类 class ListNode attr_accessor :val # 节点值 attr_accessor :next # 指向下一节点的引用 def initialize(val=0, next_node=nil) @val = val @next = next_node end end ``` ## 链表常用操作 ### 初始化链表 建立链表分为两步,第一步是初始化各个节点对象,第二步是构建节点之间的引用关系。初始化完成后,我们就可以从链表的头节点出发,通过引用指向 `next` 依次访问所有节点。 === "Python" ```python title="linked_list.py" # 初始化链表 1 -> 3 -> 2 -> 5 -> 4 # 初始化各个节点 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 ``` === "C++" ```cpp title="linked_list.cpp" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个节点 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; ``` === "Java" ```java title="linked_list.java" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个节点 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; ``` === "C#" ```csharp title="linked_list.cs" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个节点 ListNode n0 = new(1); ListNode n1 = new(3); ListNode n2 = new(2); ListNode n3 = new(5); ListNode n4 = new(4); // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Go" ```go title="linked_list.go" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个节点 n0 := NewListNode(1) n1 := NewListNode(3) n2 := NewListNode(2) n3 := NewListNode(5) n4 := NewListNode(4) // 构建节点之间的引用 n0.Next = n1 n1.Next = n2 n2.Next = n3 n3.Next = n4 ``` === "Swift" ```swift title="linked_list.swift" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个节点 let n0 = ListNode(x: 1) let n1 = ListNode(x: 3) let n2 = ListNode(x: 2) let n3 = ListNode(x: 5) let n4 = ListNode(x: 4) // 构建节点之间的引用 n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 ``` === "JS" ```javascript title="linked_list.js" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个节点 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "TS" ```typescript title="linked_list.ts" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个节点 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Dart" ```dart title="linked_list.dart" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */\ // 初始化各个节点 ListNode n0 = ListNode(1); ListNode n1 = ListNode(3); ListNode n2 = ListNode(2); ListNode n3 = ListNode(5); ListNode n4 = ListNode(4); // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Rust" ```rust title="linked_list.rs" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个节点 let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None })); let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None })); let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None })); let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None })); let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None })); // 构建节点之间的引用 n0.borrow_mut().next = Some(n1.clone()); n1.borrow_mut().next = Some(n2.clone()); n2.borrow_mut().next = Some(n3.clone()); n3.borrow_mut().next = Some(n4.clone()); ``` === "C" ```c title="linked_list.c" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个节点 ListNode* n0 = newListNode(1); ListNode* n1 = newListNode(3); ListNode* n2 = newListNode(2); ListNode* n3 = newListNode(5); ListNode* n4 = newListNode(4); // 构建节点之间的引用 n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; ``` === "Kotlin" ```kotlin title="linked_list.kt" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个节点 val n0 = ListNode(1) val n1 = ListNode(3) val n2 = ListNode(2) val n3 = ListNode(5) val n4 = ListNode(4) // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Ruby" ```ruby title="linked_list.rb" # 初始化链表 1 -> 3 -> 2 -> 5 -> 4 # 初始化各个节点 n0 = ListNode.new(1) n1 = ListNode.new(3) n2 = ListNode.new(2) n3 = ListNode.new(5) n4 = ListNode.new(4) # 构建节点之间的引用 n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%201%20-%3E%203%20-%3E%202%20-%3E%205%20-%3E%204%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false 数组整体是一个变量,比如数组 `nums` 包含元素 `nums[0]` 和 `nums[1]` 等,而链表是由多个独立的节点对象组成的。**我们通常将头节点当作链表的代称**,比如以上代码中的链表可记作链表 `n0` 。 ### 插入节点 在链表中插入节点非常容易。如下图所示,假设我们想在相邻的两个节点 `n0` 和 `n1` 之间插入一个新节点 `P` ,**则只需改变两个节点引用(指针)即可**,时间复杂度为 $O(1)$ 。 相比之下,在数组中插入元素的时间复杂度为 $O(n)$ ,在大数据量下的效率较低。 ![链表插入节点示例](linked_list.assets/linkedlist_insert_node.png) ```src [file]{linked_list}-[class]{}-[func]{insert} ``` ### 删除节点 如下图所示,在链表中删除节点也非常方便,**只需改变一个节点的引用(指针)即可**。 请注意,尽管在删除操作完成后节点 `P` 仍然指向 `n1` ,但实际上遍历此链表已经无法访问到 `P` ,这意味着 `P` 已经不再属于该链表了。 ![链表删除节点](linked_list.assets/linkedlist_remove_node.png) ```src [file]{linked_list}-[class]{}-[func]{remove} ``` ### 访问节点 **在链表中访问节点的效率较低**。如上一节所述,我们可以在 $O(1)$ 时间下访问数组中的任意元素。链表则不然,程序需要从头节点出发,逐个向后遍历,直至找到目标节点。也就是说,访问链表的第 $i$ 个节点需要循环 $i - 1$ 轮,时间复杂度为 $O(n)$ 。 ```src [file]{linked_list}-[class]{}-[func]{access} ``` ### 查找节点 遍历链表,查找其中值为 `target` 的节点,输出该节点在链表中的索引。此过程也属于线性查找。代码如下所示: ```src [file]{linked_list}-[class]{}-[func]{find} ``` ## 数组 vs. 链表 下表总结了数组和链表的各项特点并对比了操作效率。由于它们采用两种相反的存储策略,因此各种性质和操作效率也呈现对立的特点。

  数组与链表的效率对比

| | 数组 | 链表 | | -------- | ------------------------------ | -------------- | | 存储方式 | 连续内存空间 | 分散内存空间 | | 容量扩展 | 长度不可变 | 可灵活扩展 | | 内存效率 | 元素占用内存少、但可能浪费空间 | 元素占用内存多 | | 访问元素 | $O(1)$ | $O(n)$ | | 添加元素 | $O(n)$ | $O(1)$ | | 删除元素 | $O(n)$ | $O(1)$ | ## 常见链表类型 如下图所示,常见的链表类型包括三种。 - **单向链表**:即前面介绍的普通链表。单向链表的节点包含值和指向下一节点的引用两项数据。我们将首个节点称为头节点,将最后一个节点称为尾节点,尾节点指向空 `None` 。 - **环形链表**:如果我们令单向链表的尾节点指向头节点(首尾相接),则得到一个环形链表。在环形链表中,任意节点都可以视作头节点。 - **双向链表**:与单向链表相比,双向链表记录了两个方向的引用。双向链表的节点定义同时包含指向后继节点(下一个节点)和前驱节点(上一个节点)的引用(指针)。相较于单向链表,双向链表更具灵活性,可以朝两个方向遍历链表,但相应地也需要占用更多的内存空间。 === "Python" ```python title="" class ListNode: """双向链表节点类""" def __init__(self, val: int): self.val: int = val # 节点值 self.next: ListNode | None = None # 指向后继节点的引用 self.prev: ListNode | None = None # 指向前驱节点的引用 ``` === "C++" ```cpp title="" /* 双向链表节点结构体 */ struct ListNode { int val; // 节点值 ListNode *next; // 指向后继节点的指针 ListNode *prev; // 指向前驱节点的指针 ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // 构造函数 }; ``` === "Java" ```java title="" /* 双向链表节点类 */ class ListNode { int val; // 节点值 ListNode next; // 指向后继节点的引用 ListNode prev; // 指向前驱节点的引用 ListNode(int x) { val = x; } // 构造函数 } ``` === "C#" ```csharp title="" /* 双向链表节点类 */ class ListNode(int x) { // 构造函数 int val = x; // 节点值 ListNode next; // 指向后继节点的引用 ListNode prev; // 指向前驱节点的引用 } ``` === "Go" ```go title="" /* 双向链表节点结构体 */ type DoublyListNode struct { Val int // 节点值 Next *DoublyListNode // 指向后继节点的指针 Prev *DoublyListNode // 指向前驱节点的指针 } // NewDoublyListNode 初始化 func NewDoublyListNode(val int) *DoublyListNode { return &DoublyListNode{ Val: val, Next: nil, Prev: nil, } } ``` === "Swift" ```swift title="" /* 双向链表节点类 */ class ListNode { var val: Int // 节点值 var next: ListNode? // 指向后继节点的引用 var prev: ListNode? // 指向前驱节点的引用 init(x: Int) { // 构造函数 val = x } } ``` === "JS" ```javascript title="" /* 双向链表节点类 */ class ListNode { constructor(val, next, prev) { this.val = val === undefined ? 0 : val; // 节点值 this.next = next === undefined ? null : next; // 指向后继节点的引用 this.prev = prev === undefined ? null : prev; // 指向前驱节点的引用 } } ``` === "TS" ```typescript title="" /* 双向链表节点类 */ class ListNode { val: number; next: ListNode | null; prev: ListNode | null; constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) { this.val = val === undefined ? 0 : val; // 节点值 this.next = next === undefined ? null : next; // 指向后继节点的引用 this.prev = prev === undefined ? null : prev; // 指向前驱节点的引用 } } ``` === "Dart" ```dart title="" /* 双向链表节点类 */ class ListNode { int val; // 节点值 ListNode? next; // 指向后继节点的引用 ListNode? prev; // 指向前驱节点的引用 ListNode(this.val, [this.next, this.prev]); // 构造函数 } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* 双向链表节点类型 */ #[derive(Debug)] struct ListNode { val: i32, // 节点值 next: Option>>, // 指向后继节点的指针 prev: Option>>, // 指向前驱节点的指针 } /* 构造函数 */ impl ListNode { fn new(val: i32) -> Self { ListNode { val, next: None, prev: None, } } } ``` === "C" ```c title="" /* 双向链表节点结构体 */ typedef struct ListNode { int val; // 节点值 struct ListNode *next; // 指向后继节点的指针 struct ListNode *prev; // 指向前驱节点的指针 } ListNode; /* 构造函数 */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *) malloc(sizeof(ListNode)); node->val = val; node->next = NULL; node->prev = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* 双向链表节点类 */ // 构造方法 class ListNode(x: Int) { val _val: Int = x // 节点值 val next: ListNode? = null // 指向后继节点的引用 val prev: ListNode? = null // 指向前驱节点的引用 } ``` === "Ruby" ```ruby title="" # 双向链表节点类 class ListNode attr_accessor :val # 节点值 attr_accessor :next # 指向后继节点的引用 attr_accessor :prev # 指向前驱节点的引用 def initialize(val=0, next_node=nil, prev_node=nil) @val = val @next = next_node @prev = prev_node end end ``` ![常见链表种类](linked_list.assets/linkedlist_common_types.png) ## 链表典型应用 单向链表通常用于实现栈、队列、哈希表和图等数据结构。 - **栈与队列**:当插入和删除操作都在链表的一端进行时,它表现的特性为先进后出,对应栈;当插入操作在链表的一端进行,删除操作在链表的另一端进行,它表现的特性为先进先出,对应队列。 - **哈希表**:链式地址是解决哈希冲突的主流方案之一,在该方案中,所有冲突的元素都会被放到一个链表中。 - **图**:邻接表是表示图的一种常用方式,其中图的每个顶点都与一个链表相关联,链表中的每个元素都代表与该顶点相连的其他顶点。 双向链表常用于需要快速查找前一个和后一个元素的场景。 - **高级数据结构**:比如在红黑树、B 树中,我们需要访问节点的父节点,这可以通过在节点中保存一个指向父节点的引用来实现,类似于双向链表。 - **浏览器历史**:在网页浏览器中,当用户点击前进或后退按钮时,浏览器需要知道用户访问过的前一个和后一个网页。双向链表的特性使得这种操作变得简单。 - **LRU 算法**:在缓存淘汰(LRU)算法中,我们需要快速找到最近最少使用的数据,以及支持快速添加和删除节点。这时候使用双向链表就非常合适。 环形链表常用于需要周期性操作的场景,比如操作系统的资源调度。 - **时间片轮转调度算法**:在操作系统中,时间片轮转调度算法是一种常见的 CPU 调度算法,它需要对一组进程进行循环。每个进程被赋予一个时间片,当时间片用完时,CPU 将切换到下一个进程。这种循环操作可以通过环形链表来实现。 - **数据缓冲区**:在某些数据缓冲区的实现中,也可能会使用环形链表。比如在音频、视频播放器中,数据流可能会被分成多个缓冲块并放入一个环形链表,以便实现无缝播放。 ================================================ FILE: docs/chapter_array_and_linkedlist/list.md ================================================ # 列表 列表(list)是一个抽象的数据结构概念,它表示元素的有序集合,支持元素访问、修改、添加、删除和遍历等操作,无须使用者考虑容量限制的问题。列表可以基于链表或数组实现。 - 链表天然可以看作一个列表,其支持元素增删查改操作,并且可以灵活动态扩容。 - 数组也支持元素增删查改,但由于其长度不可变,因此只能看作一个具有长度限制的列表。 当使用数组实现列表时,**长度不可变的性质会导致列表的实用性降低**。这是因为我们通常无法事先确定需要存储多少数据,从而难以选择合适的列表长度。若长度过小,则很可能无法满足使用需求;若长度过大,则会造成内存空间浪费。 为解决此问题,我们可以使用动态数组(dynamic array)来实现列表。它继承了数组的各项优点,并且可以在程序运行过程中进行动态扩容。 实际上,**许多编程语言中的标准库提供的列表是基于动态数组实现的**,例如 Python 中的 `list` 、Java 中的 `ArrayList` 、C++ 中的 `vector` 和 C# 中的 `List` 等。在接下来的讨论中,我们将把“列表”和“动态数组”视为等同的概念。 ## 列表常用操作 ### 初始化列表 我们通常使用“无初始值”和“有初始值”这两种初始化方法: === "Python" ```python title="list.py" # 初始化列表 # 无初始值 nums1: list[int] = [] # 有初始值 nums: list[int] = [1, 3, 2, 5, 4] ``` === "C++" ```cpp title="list.cpp" /* 初始化列表 */ // 需注意,C++ 中 vector 即是本文描述的 nums // 无初始值 vector nums1; // 有初始值 vector nums = { 1, 3, 2, 5, 4 }; ``` === "Java" ```java title="list.java" /* 初始化列表 */ // 无初始值 List nums1 = new ArrayList<>(); // 有初始值(注意数组的元素类型需为 int[] 的包装类 Integer[]) Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; List nums = new ArrayList<>(Arrays.asList(numbers)); ``` === "C#" ```csharp title="list.cs" /* 初始化列表 */ // 无初始值 List nums1 = []; // 有初始值 int[] numbers = [1, 3, 2, 5, 4]; List nums = [.. numbers]; ``` === "Go" ```go title="list_test.go" /* 初始化列表 */ // 无初始值 nums1 := []int{} // 有初始值 nums := []int{1, 3, 2, 5, 4} ``` === "Swift" ```swift title="list.swift" /* 初始化列表 */ // 无初始值 let nums1: [Int] = [] // 有初始值 var nums = [1, 3, 2, 5, 4] ``` === "JS" ```javascript title="list.js" /* 初始化列表 */ // 无初始值 const nums1 = []; // 有初始值 const nums = [1, 3, 2, 5, 4]; ``` === "TS" ```typescript title="list.ts" /* 初始化列表 */ // 无初始值 const nums1: number[] = []; // 有初始值 const nums: number[] = [1, 3, 2, 5, 4]; ``` === "Dart" ```dart title="list.dart" /* 初始化列表 */ // 无初始值 List nums1 = []; // 有初始值 List nums = [1, 3, 2, 5, 4]; ``` === "Rust" ```rust title="list.rs" /* 初始化列表 */ // 无初始值 let nums1: Vec = Vec::new(); // 有初始值 let nums: Vec = vec![1, 3, 2, 5, 4]; ``` === "C" ```c title="list.c" // C 未提供内置动态数组 ``` === "Kotlin" ```kotlin title="list.kt" /* 初始化列表 */ // 无初始值 var nums1 = listOf() // 有初始值 var numbers = arrayOf(1, 3, 2, 5, 4) var nums = numbers.toMutableList() ``` === "Ruby" ```ruby title="list.rb" # 初始化列表 # 无初始值 nums1 = [] # 有初始值 nums = [1, 3, 2, 5, 4] ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20%23%20%E6%97%A0%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums1%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 访问元素 列表本质上是数组,因此可以在 $O(1)$ 时间内访问和更新元素,效率很高。 === "Python" ```python title="list.py" # 访问元素 num: int = nums[1] # 访问索引 1 处的元素 # 更新元素 nums[1] = 0 # 将索引 1 处的元素更新为 0 ``` === "C++" ```cpp title="list.cpp" /* 访问元素 */ int num = nums[1]; // 访问索引 1 处的元素 /* 更新元素 */ nums[1] = 0; // 将索引 1 处的元素更新为 0 ``` === "Java" ```java title="list.java" /* 访问元素 */ int num = nums.get(1); // 访问索引 1 处的元素 /* 更新元素 */ nums.set(1, 0); // 将索引 1 处的元素更新为 0 ``` === "C#" ```csharp title="list.cs" /* 访问元素 */ int num = nums[1]; // 访问索引 1 处的元素 /* 更新元素 */ nums[1] = 0; // 将索引 1 处的元素更新为 0 ``` === "Go" ```go title="list_test.go" /* 访问元素 */ num := nums[1] // 访问索引 1 处的元素 /* 更新元素 */ nums[1] = 0 // 将索引 1 处的元素更新为 0 ``` === "Swift" ```swift title="list.swift" /* 访问元素 */ let num = nums[1] // 访问索引 1 处的元素 /* 更新元素 */ nums[1] = 0 // 将索引 1 处的元素更新为 0 ``` === "JS" ```javascript title="list.js" /* 访问元素 */ const num = nums[1]; // 访问索引 1 处的元素 /* 更新元素 */ nums[1] = 0; // 将索引 1 处的元素更新为 0 ``` === "TS" ```typescript title="list.ts" /* 访问元素 */ const num: number = nums[1]; // 访问索引 1 处的元素 /* 更新元素 */ nums[1] = 0; // 将索引 1 处的元素更新为 0 ``` === "Dart" ```dart title="list.dart" /* 访问元素 */ int num = nums[1]; // 访问索引 1 处的元素 /* 更新元素 */ nums[1] = 0; // 将索引 1 处的元素更新为 0 ``` === "Rust" ```rust title="list.rs" /* 访问元素 */ let num: i32 = nums[1]; // 访问索引 1 处的元素 /* 更新元素 */ nums[1] = 0; // 将索引 1 处的元素更新为 0 ``` === "C" ```c title="list.c" // C 未提供内置动态数组 ``` === "Kotlin" ```kotlin title="list.kt" /* 访问元素 */ val num = nums[1] // 访问索引 1 处的元素 /* 更新元素 */ nums[1] = 0 // 将索引 1 处的元素更新为 0 ``` === "Ruby" ```ruby title="list.rb" # 访问元素 num = nums[1] # 访问索引 1 处的元素 # 更新元素 nums[1] = 0 # 将索引 1 处的元素更新为 0 ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums%5B1%5D%20%20%23%20%E8%AE%BF%E9%97%AE%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5B1%5D%20%3D%200%20%20%20%20%23%20%E5%B0%86%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%E6%9B%B4%E6%96%B0%E4%B8%BA%200&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 插入与删除元素 相较于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但插入和删除元素的效率仍与数组相同,时间复杂度为 $O(n)$ 。 === "Python" ```python title="list.py" # 清空列表 nums.clear() # 在尾部添加元素 nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) # 在中间插入元素 nums.insert(3, 6) # 在索引 3 处插入数字 6 # 删除元素 nums.pop(3) # 删除索引 3 处的元素 ``` === "C++" ```cpp title="list.cpp" /* 清空列表 */ nums.clear(); /* 在尾部添加元素 */ nums.push_back(1); nums.push_back(3); nums.push_back(2); nums.push_back(5); nums.push_back(4); /* 在中间插入元素 */ nums.insert(nums.begin() + 3, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ nums.erase(nums.begin() + 3); // 删除索引 3 处的元素 ``` === "Java" ```java title="list.java" /* 清空列表 */ nums.clear(); /* 在尾部添加元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); /* 在中间插入元素 */ nums.add(3, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ nums.remove(3); // 删除索引 3 处的元素 ``` === "C#" ```csharp title="list.cs" /* 清空列表 */ nums.Clear(); /* 在尾部添加元素 */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); /* 在中间插入元素 */ nums.Insert(3, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ nums.RemoveAt(3); // 删除索引 3 处的元素 ``` === "Go" ```go title="list_test.go" /* 清空列表 */ nums = nil /* 在尾部添加元素 */ nums = append(nums, 1) nums = append(nums, 3) nums = append(nums, 2) nums = append(nums, 5) nums = append(nums, 4) /* 在中间插入元素 */ nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // 在索引 3 处插入数字 6 /* 删除元素 */ nums = append(nums[:3], nums[4:]...) // 删除索引 3 处的元素 ``` === "Swift" ```swift title="list.swift" /* 清空列表 */ nums.removeAll() /* 在尾部添加元素 */ nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) /* 在中间插入元素 */ nums.insert(6, at: 3) // 在索引 3 处插入数字 6 /* 删除元素 */ nums.remove(at: 3) // 删除索引 3 处的元素 ``` === "JS" ```javascript title="list.js" /* 清空列表 */ nums.length = 0; /* 在尾部添加元素 */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); /* 在中间插入元素 */ nums.splice(3, 0, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ nums.splice(3, 1); // 删除索引 3 处的元素 ``` === "TS" ```typescript title="list.ts" /* 清空列表 */ nums.length = 0; /* 在尾部添加元素 */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); /* 在中间插入元素 */ nums.splice(3, 0, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ nums.splice(3, 1); // 删除索引 3 处的元素 ``` === "Dart" ```dart title="list.dart" /* 清空列表 */ nums.clear(); /* 在尾部添加元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); /* 在中间插入元素 */ nums.insert(3, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ nums.removeAt(3); // 删除索引 3 处的元素 ``` === "Rust" ```rust title="list.rs" /* 清空列表 */ nums.clear(); /* 在尾部添加元素 */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); /* 在中间插入元素 */ nums.insert(3, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ nums.remove(3); // 删除索引 3 处的元素 ``` === "C" ```c title="list.c" // C 未提供内置动态数组 ``` === "Kotlin" ```kotlin title="list.kt" /* 清空列表 */ nums.clear(); /* 在尾部添加元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); /* 在中间插入元素 */ nums.add(3, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ nums.remove(3); // 删除索引 3 处的元素 ``` === "Ruby" ```ruby title="list.rb" # 清空列表 nums.clear # 在尾部添加元素 nums << 1 nums << 3 nums << 2 nums << 5 nums << 4 # 在中间插入元素 nums.insert(3, 6) # 在索引 3 处插入数字 6 # 删除元素 nums.delete_at(3) # 删除索引 3 处的元素 ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B8%85%E7%A9%BA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.clear%28%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.append%281%29%0A%20%20%20%20nums.append%283%29%0A%20%20%20%20nums.append%282%29%0A%20%20%20%20nums.append%285%29%0A%20%20%20%20nums.append%284%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%283,%206%29%20%20%23%20%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E6%8F%92%E5%85%A5%E6%95%B0%E5%AD%97%206%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.pop%283%29%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 遍历列表 与数组一样,列表可以根据索引遍历,也可以直接遍历各元素。 === "Python" ```python title="list.py" # 通过索引遍历列表 count = 0 for i in range(len(nums)): count += nums[i] # 直接遍历列表元素 for num in nums: count += num ``` === "C++" ```cpp title="list.cpp" /* 通过索引遍历列表 */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums[i]; } /* 直接遍历列表元素 */ count = 0; for (int num : nums) { count += num; } ``` === "Java" ```java title="list.java" /* 通过索引遍历列表 */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums.get(i); } /* 直接遍历列表元素 */ for (int num : nums) { count += num; } ``` === "C#" ```csharp title="list.cs" /* 通过索引遍历列表 */ int count = 0; for (int i = 0; i < nums.Count; i++) { count += nums[i]; } /* 直接遍历列表元素 */ count = 0; foreach (int num in nums) { count += num; } ``` === "Go" ```go title="list_test.go" /* 通过索引遍历列表 */ count := 0 for i := 0; i < len(nums); i++ { count += nums[i] } /* 直接遍历列表元素 */ count = 0 for _, num := range nums { count += num } ``` === "Swift" ```swift title="list.swift" /* 通过索引遍历列表 */ var count = 0 for i in nums.indices { count += nums[i] } /* 直接遍历列表元素 */ count = 0 for num in nums { count += num } ``` === "JS" ```javascript title="list.js" /* 通过索引遍历列表 */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* 直接遍历列表元素 */ count = 0; for (const num of nums) { count += num; } ``` === "TS" ```typescript title="list.ts" /* 通过索引遍历列表 */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* 直接遍历列表元素 */ count = 0; for (const num of nums) { count += num; } ``` === "Dart" ```dart title="list.dart" /* 通过索引遍历列表 */ int count = 0; for (var i = 0; i < nums.length; i++) { count += nums[i]; } /* 直接遍历列表元素 */ count = 0; for (var num in nums) { count += num; } ``` === "Rust" ```rust title="list.rs" // 通过索引遍历列表 let mut _count = 0; for i in 0..nums.len() { _count += nums[i]; } // 直接遍历列表元素 _count = 0; for num in &nums { _count += num; } ``` === "C" ```c title="list.c" // C 未提供内置动态数组 ``` === "Kotlin" ```kotlin title="list.kt" /* 通过索引遍历列表 */ var count = 0 for (i in nums.indices) { count += nums[i] } /* 直接遍历列表元素 */ for (num in nums) { count += num } ``` === "Ruby" ```ruby title="list.rb" # 通过索引遍历列表 count = 0 for i in 0...nums.length count += nums[i] end # 直接遍历列表元素 count = 0 for num in nums count += num end ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E7%B4%A2%E5%BC%95%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 拼接列表 给定一个新列表 `nums1` ,我们可以将其拼接到原列表的尾部。 === "Python" ```python title="list.py" # 拼接两个列表 nums1: list[int] = [6, 8, 7, 10, 9] nums += nums1 # 将列表 nums1 拼接到 nums 之后 ``` === "C++" ```cpp title="list.cpp" /* 拼接两个列表 */ vector nums1 = { 6, 8, 7, 10, 9 }; // 将列表 nums1 拼接到 nums 之后 nums.insert(nums.end(), nums1.begin(), nums1.end()); ``` === "Java" ```java title="list.java" /* 拼接两个列表 */ List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); nums.addAll(nums1); // 将列表 nums1 拼接到 nums 之后 ``` === "C#" ```csharp title="list.cs" /* 拼接两个列表 */ List nums1 = [6, 8, 7, 10, 9]; nums.AddRange(nums1); // 将列表 nums1 拼接到 nums 之后 ``` === "Go" ```go title="list_test.go" /* 拼接两个列表 */ nums1 := []int{6, 8, 7, 10, 9} nums = append(nums, nums1...) // 将列表 nums1 拼接到 nums 之后 ``` === "Swift" ```swift title="list.swift" /* 拼接两个列表 */ let nums1 = [6, 8, 7, 10, 9] nums.append(contentsOf: nums1) // 将列表 nums1 拼接到 nums 之后 ``` === "JS" ```javascript title="list.js" /* 拼接两个列表 */ const nums1 = [6, 8, 7, 10, 9]; nums.push(...nums1); // 将列表 nums1 拼接到 nums 之后 ``` === "TS" ```typescript title="list.ts" /* 拼接两个列表 */ const nums1: number[] = [6, 8, 7, 10, 9]; nums.push(...nums1); // 将列表 nums1 拼接到 nums 之后 ``` === "Dart" ```dart title="list.dart" /* 拼接两个列表 */ List nums1 = [6, 8, 7, 10, 9]; nums.addAll(nums1); // 将列表 nums1 拼接到 nums 之后 ``` === "Rust" ```rust title="list.rs" /* 拼接两个列表 */ let nums1: Vec = vec![6, 8, 7, 10, 9]; nums.extend(nums1); ``` === "C" ```c title="list.c" // C 未提供内置动态数组 ``` === "Kotlin" ```kotlin title="list.kt" /* 拼接两个列表 */ val nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList() nums.addAll(nums1) // 将列表 nums1 拼接到 nums 之后 ``` === "Ruby" ```ruby title="list.rb" # 拼接两个列表 nums1 = [6, 8, 7, 10, 9] nums += nums1 ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8B%BC%E6%8E%A5%E4%B8%A4%E4%B8%AA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums1%20%3D%20%5B6,%208,%207,%2010,%209%5D%0A%20%20%20%20nums%20%2B%3D%20nums1%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%20nums1%20%E6%8B%BC%E6%8E%A5%E5%88%B0%20nums%20%E4%B9%8B%E5%90%8E&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 排序列表 完成列表排序后,我们便可以使用在数组类算法题中经常考查的“二分查找”和“双指针”算法。 === "Python" ```python title="list.py" # 排序列表 nums.sort() # 排序后,列表元素从小到大排列 ``` === "C++" ```cpp title="list.cpp" /* 排序列表 */ sort(nums.begin(), nums.end()); // 排序后,列表元素从小到大排列 ``` === "Java" ```java title="list.java" /* 排序列表 */ Collections.sort(nums); // 排序后,列表元素从小到大排列 ``` === "C#" ```csharp title="list.cs" /* 排序列表 */ nums.Sort(); // 排序后,列表元素从小到大排列 ``` === "Go" ```go title="list_test.go" /* 排序列表 */ sort.Ints(nums) // 排序后,列表元素从小到大排列 ``` === "Swift" ```swift title="list.swift" /* 排序列表 */ nums.sort() // 排序后,列表元素从小到大排列 ``` === "JS" ```javascript title="list.js" /* 排序列表 */ nums.sort((a, b) => a - b); // 排序后,列表元素从小到大排列 ``` === "TS" ```typescript title="list.ts" /* 排序列表 */ nums.sort((a, b) => a - b); // 排序后,列表元素从小到大排列 ``` === "Dart" ```dart title="list.dart" /* 排序列表 */ nums.sort(); // 排序后,列表元素从小到大排列 ``` === "Rust" ```rust title="list.rs" /* 排序列表 */ nums.sort(); // 排序后,列表元素从小到大排列 ``` === "C" ```c title="list.c" // C 未提供内置动态数组 ``` === "Kotlin" ```kotlin title="list.kt" /* 排序列表 */ nums.sort() // 排序后,列表元素从小到大排列 ``` === "Ruby" ```ruby title="list.rb" # 排序列表 nums = nums.sort { |a, b| a <=> b } # 排序后,列表元素从小到大排列 ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8E%92%E5%BA%8F%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E6%8E%92%E5%BA%8F%E5%90%8E%EF%BC%8C%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E4%BB%8E%E5%B0%8F%E5%88%B0%E5%A4%A7%E6%8E%92%E5%88%97&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## 列表实现 许多编程语言内置了列表,例如 Java、C++、Python 等。它们的实现比较复杂,各个参数的设定也非常考究,例如初始容量、扩容倍数等。感兴趣的读者可以查阅源码进行学习。 为了加深对列表工作原理的理解,我们尝试实现一个简易版列表,包括以下三个重点设计。 - **初始容量**:选取一个合理的数组初始容量。在本示例中,我们选择 10 作为初始容量。 - **数量记录**:声明一个变量 `size` ,用于记录列表当前元素数量,并随着元素插入和删除实时更新。根据此变量,我们可以定位列表尾部,以及判断是否需要扩容。 - **扩容机制**:若插入元素时列表容量已满,则需要进行扩容。先根据扩容倍数创建一个更大的数组,再将当前数组的所有元素依次移动至新数组。在本示例中,我们规定每次将数组扩容至之前的 2 倍。 ```src [file]{my_list}-[class]{my_list}-[func]{} ``` ================================================ FILE: docs/chapter_array_and_linkedlist/ram_and_cache.md ================================================ # 内存与缓存 * 在本章的前两节中,我们探讨了数组和链表这两种基础且重要的数据结构,它们分别代表了“连续存储”和“分散存储”两种物理结构。 实际上,**物理结构在很大程度上决定了程序对内存和缓存的使用效率**,进而影响算法程序的整体性能。 ## 计算机存储设备 计算机中包括三种类型的存储设备:硬盘(hard disk)内存(random-access memory, RAM)缓存(cache memory)。下表展示了它们在计算机系统中的不同角色和性能特点。

  计算机的存储设备

| | 硬盘 | 内存 | 缓存 | | -------------- | ---------------------------------------- | -------------------------------------- | ------------------------------------------------- | | 用途 | 长期存储数据,包括操作系统、程序、文件等 | 临时存储当前运行的程序和正在处理的数据 | 存储经常访问的数据和指令,减少 CPU 访问内存的次数 | | 易失性 | 断电后数据不会丢失 | 断电后数据会丢失 | 断电后数据会丢失 | | 容量 | 较大,TB 级别 | 较小,GB 级别 | 非常小,MB 级别 | | 速度 | 较慢,几百到几千 MB/s | 较快,几十 GB/s | 非常快,几十到几百 GB/s | | 价格(人民币) | 较便宜,几毛到几元 / GB | 较贵,几十到几百元 / GB | 非常贵,随 CPU 打包计价 | 我们可以将计算机存储系统想象为下图所示的金字塔结构。越靠近金字塔顶端的存储设备的速度越快、容量越小、成本越高。这种多层级的设计并非偶然,而是计算机科学家和工程师们经过深思熟虑的结果。 - **硬盘难以被内存取代**。首先,内存中的数据在断电后会丢失,因此它不适合长期存储数据;其次,内存的成本是硬盘的几十倍,这使得它难以在消费者市场普及。 - **缓存的大容量和高速度难以兼得**。随着 L1、L2、L3 缓存的容量逐步增大,其物理尺寸会变大,与 CPU 核心之间的物理距离会变远,从而导致数据传输时间增加,元素访问延迟变高。在当前技术下,多层级的缓存结构是容量、速度和成本之间的最佳平衡点。 ![计算机存储系统](ram_and_cache.assets/storage_pyramid.png) !!! tip 计算机的存储层次结构体现了速度、容量和成本三者之间的精妙平衡。实际上,这种权衡普遍存在于所有工业领域,它要求我们在不同的优势和限制之间找到最佳平衡点。 总的来说,**硬盘用于长期存储大量数据,内存用于临时存储程序运行中正在处理的数据,而缓存则用于存储经常访问的数据和指令**,以提高程序运行效率。三者共同协作,确保计算机系统高效运行。 如下图所示,在程序运行时,数据会从硬盘中被读取到内存中,供 CPU 计算使用。缓存可以看作 CPU 的一部分,**它通过智能地从内存加载数据**,给 CPU 提供高速的数据读取,从而显著提升程序的执行效率,减少对较慢的内存的依赖。 ![硬盘、内存和缓存之间的数据流通](ram_and_cache.assets/computer_storage_devices.png) ## 数据结构的内存效率 在内存空间利用方面,数组和链表各自具有优势和局限性。 一方面,**内存是有限的,且同一块内存不能被多个程序共享**,因此我们希望数据结构能够尽可能高效地利用空间。数组的元素紧密排列,不需要额外的空间来存储链表节点间的引用(指针),因此空间效率更高。然而,数组需要一次性分配足够的连续内存空间,这可能导致内存浪费,数组扩容也需要额外的时间和空间成本。相比之下,链表以“节点”为单位进行动态内存分配和回收,提供了更大的灵活性。 另一方面,在程序运行时,**随着反复申请与释放内存,空闲内存的碎片化程度会越来越高**,从而导致内存的利用效率降低。数组由于其连续的存储方式,相对不容易导致内存碎片化。相反,链表的元素是分散存储的,在频繁的插入与删除操作中,更容易导致内存碎片化。 ## 数据结构的缓存效率 缓存虽然在空间容量上远小于内存,但它比内存快得多,在程序执行速度上起着至关重要的作用。由于缓存的容量有限,只能存储一小部分频繁访问的数据,因此当 CPU 尝试访问的数据不在缓存中时,就会发生缓存未命中(cache miss),此时 CPU 不得不从速度较慢的内存中加载所需数据。 显然,**“缓存未命中”越少,CPU 读写数据的效率就越高**,程序性能也就越好。我们将 CPU 从缓存中成功获取数据的比例称为缓存命中率(cache hit rate),这个指标通常用来衡量缓存效率。 为了尽可能达到更高的效率,缓存会采取以下数据加载机制。 - **缓存行**:缓存不是单个字节地存储与加载数据,而是以缓存行为单位。相比于单个字节的传输,缓存行的传输形式更加高效。 - **预取机制**:处理器会尝试预测数据访问模式(例如顺序访问、固定步长跳跃访问等),并根据特定模式将数据加载至缓存之中,从而提升命中率。 - **空间局部性**:如果一个数据被访问,那么它附近的数据可能近期也会被访问。因此,缓存在加载某一数据时,也会加载其附近的数据,以提高命中率。 - **时间局部性**:如果一个数据被访问,那么它在不久的将来很可能再次被访问。缓存利用这一原理,通过保留最近访问过的数据来提高命中率。 实际上,**数组和链表对缓存的利用效率是不同的**,主要体现在以下几个方面。 - **占用空间**:链表元素比数组元素占用空间更多,导致缓存中容纳的有效数据量更少。 - **缓存行**:链表数据分散在内存各处,而缓存是“按行加载”的,因此加载到无效数据的比例更高。 - **预取机制**:数组比链表的数据访问模式更具“可预测性”,即系统更容易猜出即将被加载的数据。 - **空间局部性**:数组被存储在集中的内存空间中,因此被加载数据附近的数据更有可能即将被访问。 总体而言,**数组具有更高的缓存命中率,因此它在操作效率上通常优于链表**。这使得在解决算法问题时,基于数组实现的数据结构往往更受欢迎。 需要注意的是,**高缓存效率并不意味着数组在所有情况下都优于链表**。实际应用中选择哪种数据结构,应根据具体需求来决定。例如,数组和链表都可以实现“栈”数据结构(下一章会详细介绍),但它们适用于不同场景。 - 在做算法题时,我们会倾向于选择基于数组实现的栈,因为它提供了更高的操作效率和随机访问的能力,代价仅是需要预先为数组分配一定的内存空间。 - 如果数据量非常大、动态性很高、栈的预期大小难以估计,那么基于链表实现的栈更加合适。链表能够将大量数据分散存储于内存的不同部分,并且避免了数组扩容产生的额外开销。 ================================================ FILE: docs/chapter_array_and_linkedlist/summary.md ================================================ # 小结 ### 重点回顾 - 数组和链表是两种基本的数据结构,分别代表数据在计算机内存中的两种存储方式:连续空间存储和分散空间存储。两者的特点呈现出互补的特性。 - 数组支持随机访问、占用内存较少;但插入和删除元素效率低,且初始化后长度不可变。 - 链表通过更改引用(指针)实现高效的节点插入与删除,且可以灵活调整长度;但节点访问效率低、占用内存较多。常见的链表类型包括单向链表、环形链表、双向链表。 - 列表是一种支持增删查改的元素有序集合,通常基于动态数组实现。它保留了数组的优势,同时可以灵活调整长度。 - 列表的出现大幅提高了数组的实用性,但可能导致部分内存空间浪费。 - 程序运行时,数据主要存储在内存中。数组可提供更高的内存空间效率,而链表则在内存使用上更加灵活。 - 缓存通过缓存行、预取机制以及空间局部性和时间局部性等数据加载机制,为 CPU 提供快速数据访问,显著提升程序的执行效率。 - 由于数组具有更高的缓存命中率,因此它通常比链表更高效。在选择数据结构时,应根据具体需求和场景做出恰当选择。 ### Q & A **Q**:数组存储在栈上和存储在堆上,对时间效率和空间效率是否有影响? 存储在栈上和堆上的数组都被存储在连续内存空间内,数据操作效率基本一致。然而,栈和堆具有各自的特点,从而导致以下不同点。 1. 分配和释放效率:栈是一块较小的内存,分配由编译器自动完成;而堆内存相对更大,可以在代码中动态分配,更容易碎片化。因此,堆上的分配和释放操作通常比栈上的慢。 2. 大小限制:栈内存相对较小,堆的大小一般受限于可用内存。因此堆更加适合存储大型数组。 3. 灵活性:栈上的数组的大小需要在编译时确定,而堆上的数组的大小可以在运行时动态确定。 **Q**:为什么数组要求相同类型的元素,而在链表中却没有强调相同类型呢? 链表由节点组成,节点之间通过引用(指针)连接,各个节点可以存储不同类型的数据,例如 `int`、`double`、`string`、`object` 等。 相对地,数组元素则必须是相同类型的,这样才能通过计算偏移量来获取对应元素位置。例如,数组同时包含 `int` 和 `long` 两种类型,单个元素分别占用 4 字节和 8 字节 ,此时就不能用以下公式计算偏移量了,因为数组中包含了两种“元素长度”。 ```shell # 元素内存地址 = 数组内存地址(首元素内存地址) + 元素长度 * 元素索引 ``` **Q**:删除节点 `P` 后,是否需要把 `P.next` 设为 `None` 呢? 不修改 `P.next` 也可以。从该链表的角度看,从头节点遍历到尾节点已经不会遇到 `P` 了。这意味着节点 `P` 已经从链表中删除了,此时节点 `P` 指向哪里都不会对该链表产生影响。 从数据结构与算法(做题)的角度看,不断开没有关系,只要保证程序的逻辑是正确的就行。从标准库的角度看,断开更加安全、逻辑更加清晰。如果不断开,假设被删除节点未被正常回收,那么它会影响后继节点的内存回收。 **Q**:在链表中插入和删除操作的时间复杂度是 $O(1)$ 。但是增删之前都需要 $O(n)$ 的时间查找元素,那为什么时间复杂度不是 $O(n)$ 呢? 如果是先查找元素、再删除元素,时间复杂度确实是 $O(n)$ 。然而,链表的 $O(1)$ 增删的优势可以在其他应用上得到体现。例如,双向队列适合使用链表实现,我们维护一个指针变量始终指向头节点、尾节点,每次插入与删除操作都是 $O(1)$ 。 **Q**:图“链表定义与存储方式”中,浅蓝色的存储节点指针是占用一块内存地址吗?还是和节点值各占一半呢? 该示意图只是定性表示,定量表示需要根据具体情况进行分析。 - 不同类型的节点值占用的空间是不同的,比如 `int`、`long`、`double` 和实例对象等。 - 指针变量占用的内存空间大小根据所使用的操作系统及编译环境而定,大多为 8 字节或 4 字节。 **Q**:在列表末尾添加元素是否时时刻刻都为 $O(1)$ ? 如果添加元素时超出列表长度,则需要先扩容列表再添加。系统会申请一块新的内存,并将原列表的所有元素搬运过去,这时候时间复杂度就会是 $O(n)$ 。 **Q**:“列表的出现极大地提高了数组的实用性,但可能导致部分内存空间浪费”,这里的空间浪费是指额外增加的变量如容量、长度、扩容倍数所占的内存吗? 这里的空间浪费主要有两方面含义:一方面,列表都会设定一个初始长度,我们不一定需要用这么多;另一方面,为了防止频繁扩容,扩容一般会乘以一个系数,比如 $\times 1.5$ 。这样一来,也会出现很多空位,我们通常不能完全填满它们。 **Q**:在 Python 中初始化 `n = [1, 2, 3]` 后,这 3 个元素的地址是相连的,但是初始化 `m = [2, 1, 3]` 会发现它们每个元素的 id 并不是连续的,而是分别跟 `n` 中的相同。这些元素的地址不连续,那么 `m` 还是数组吗? 假如把列表元素换成链表节点 `n = [n1, n2, n3, n4, n5]` ,通常情况下这 5 个节点对象也分散存储在内存各处。然而,给定一个列表索引,我们仍然可以在 $O(1)$ 时间内获取节点内存地址,从而访问到对应的节点。这是因为数组中存储的是节点的引用,而非节点本身。 与许多语言不同,Python 中的数字也被包装为对象,列表中存储的不是数字本身,而是对数字的引用。因此,我们会发现两个数组中的相同数字拥有同一个 id ,并且这些数字的内存地址无须连续。 **Q**:C++ STL 里面的 `std::list` 已经实现了双向链表,但好像一些算法书上不怎么直接使用它,是不是因为有什么局限性呢? 一方面,我们往往更青睐使用数组实现算法,而只在必要时才使用链表,主要有两个原因。 - 空间开销:由于每个元素需要两个额外的指针(一个用于前一个元素,一个用于后一个元素),所以 `std::list` 通常比 `std::vector` 更占用空间。 - 缓存不友好:由于数据不是连续存放的,因此 `std::list` 对缓存的利用率较低。一般情况下,`std::vector` 的性能会更好。 另一方面,必要使用链表的情况主要是二叉树和图。栈和队列往往会使用编程语言提供的 `stack` 和 `queue` ,而非链表。 **Q**:操作 `res = [[0]] * n` 生成了一个二维列表,其中每一个 `[0]` 都是独立的吗? 不是独立的。此二维列表中,所有的 `[0]` 实际上是同一个对象的引用。如果我们修改其中一个元素,会发现所有的对应元素都会随之改变。 如果希望二维列表中的每个 `[0]` 都是独立的,可以使用 `res = [[0] for _ in range(n)]` 来实现。这种方式的原理是初始化了 $n$ 个独立的 `[0]` 列表对象。 **Q**:操作 `res = [0] * n` 生成了一个列表,其中每一个整数 0 都是独立的吗? 在该列表中,所有整数 0 都是同一个对象的引用。这是因为 Python 对小整数(通常是 -5 到 256)采用了缓存池机制,以便最大化对象复用,从而提升性能。 虽然它们指向同一个对象,但我们仍然可以独立修改列表中的每个元素,这是因为 Python 的整数是“不可变对象”。当我们修改某个元素时,实际上是切换为另一个对象的引用,而不是改变原有对象本身。 然而,当列表元素是“可变对象”时(例如列表、字典或类实例等),修改某个元素会直接改变该对象本身,所有引用该对象的元素都会产生相同变化。 ================================================ FILE: docs/chapter_backtracking/backtracking_algorithm.md ================================================ # 回溯算法 回溯算法(backtracking algorithm)是一种通过穷举来解决问题的方法,它的核心思想是从一个初始状态出发,暴力搜索所有可能的解决方案,当遇到正确的解则将其记录,直到找到解或者尝试了所有可能的选择都无法找到解为止。 回溯算法通常采用“深度优先搜索”来遍历解空间。在“二叉树”章节中,我们提到前序、中序和后序遍历都属于深度优先搜索。接下来,我们利用前序遍历构造一个回溯问题,逐步了解回溯算法的工作原理。 !!! question "例题一" 给定一棵二叉树,搜索并记录所有值为 $7$ 的节点,请返回节点列表。 对于此题,我们前序遍历这棵树,并判断当前节点的值是否为 $7$ ,若是,则将该节点的值加入结果列表 `res` 之中。相关过程实现如下图和以下代码所示: ```src [file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order} ``` ![在前序遍历中搜索节点](backtracking_algorithm.assets/preorder_find_nodes.png) ## 尝试与回退 **之所以称之为回溯算法,是因为该算法在搜索解空间时会采用“尝试”与“回退”的策略**。当算法在搜索过程中遇到某个状态无法继续前进或无法得到满足条件的解时,它会撤销上一步的选择,退回到之前的状态,并尝试其他可能的选择。 对于例题一,访问每个节点都代表一次“尝试”,而越过叶节点或返回父节点的 `return` 则表示“回退”。 值得说明的是,**回退并不仅仅包括函数返回**。为解释这一点,我们对例题一稍作拓展。 !!! question "例题二" 在二叉树中搜索所有值为 $7$ 的节点,**请返回根节点到这些节点的路径**。 在例题一代码的基础上,我们需要借助一个列表 `path` 记录访问过的节点路径。当访问到值为 $7$ 的节点时,则复制 `path` 并添加进结果列表 `res` 。遍历完成后,`res` 中保存的就是所有的解。代码如下所示: ```src [file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order} ``` 在每次“尝试”中,我们通过将当前节点添加进 `path` 来记录路径;而在“回退”前,我们需要将该节点从 `path` 中弹出,**以恢复本次尝试之前的状态**。 观察下图所示的过程,**我们可以将尝试和回退理解为“前进”与“撤销”**,两个操作互为逆向。 === "<1>" ![尝试与回退](backtracking_algorithm.assets/preorder_find_paths_step1.png) === "<2>" ![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png) === "<3>" ![preorder_find_paths_step3](backtracking_algorithm.assets/preorder_find_paths_step3.png) === "<4>" ![preorder_find_paths_step4](backtracking_algorithm.assets/preorder_find_paths_step4.png) === "<5>" ![preorder_find_paths_step5](backtracking_algorithm.assets/preorder_find_paths_step5.png) === "<6>" ![preorder_find_paths_step6](backtracking_algorithm.assets/preorder_find_paths_step6.png) === "<7>" ![preorder_find_paths_step7](backtracking_algorithm.assets/preorder_find_paths_step7.png) === "<8>" ![preorder_find_paths_step8](backtracking_algorithm.assets/preorder_find_paths_step8.png) === "<9>" ![preorder_find_paths_step9](backtracking_algorithm.assets/preorder_find_paths_step9.png) === "<10>" ![preorder_find_paths_step10](backtracking_algorithm.assets/preorder_find_paths_step10.png) === "<11>" ![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png) ## 剪枝 复杂的回溯问题通常包含一个或多个约束条件,**约束条件通常可用于“剪枝”**。 !!! question "例题三" 在二叉树中搜索所有值为 $7$ 的节点,请返回根节点到这些节点的路径,**并要求路径中不包含值为 $3$ 的节点**。 为了满足以上约束条件,**我们需要添加剪枝操作**:在搜索过程中,若遇到值为 $3$ 的节点,则提前返回,不再继续搜索。代码如下所示: ```src [file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order} ``` “剪枝”是一个非常形象的名词。如下图所示,在搜索过程中,**我们“剪掉”了不满足约束条件的搜索分支**,避免许多无意义的尝试,从而提高了搜索效率。 ![根据约束条件剪枝](backtracking_algorithm.assets/preorder_find_constrained_paths.png) ## 框架代码 接下来,我们尝试将回溯的“尝试、回退、剪枝”的主体框架提炼出来,提升代码的通用性。 在以下框架代码中,`state` 表示问题的当前状态,`choices` 表示当前状态下可以做出的选择: === "Python" ```python title="" def backtrack(state: State, choices: list[choice], res: list[state]): """回溯算法框架""" # 判断是否为解 if is_solution(state): # 记录解 record_solution(state, res) # 不再继续搜索 return # 遍历所有选择 for choice in choices: # 剪枝:判断选择是否合法 if is_valid(state, choice): # 尝试:做出选择,更新状态 make_choice(state, choice) backtrack(state, choices, res) # 回退:撤销选择,恢复到之前的状态 undo_choice(state, choice) ``` === "C++" ```cpp title="" /* 回溯算法框架 */ void backtrack(State *state, vector &choices, vector &res) { // 判断是否为解 if (isSolution(state)) { // 记录解 recordSolution(state, res); // 不再继续搜索 return; } // 遍历所有选择 for (Choice choice : choices) { // 剪枝:判断选择是否合法 if (isValid(state, choice)) { // 尝试:做出选择,更新状态 makeChoice(state, choice); backtrack(state, choices, res); // 回退:撤销选择,恢复到之前的状态 undoChoice(state, choice); } } } ``` === "Java" ```java title="" /* 回溯算法框架 */ void backtrack(State state, List choices, List res) { // 判断是否为解 if (isSolution(state)) { // 记录解 recordSolution(state, res); // 不再继续搜索 return; } // 遍历所有选择 for (Choice choice : choices) { // 剪枝:判断选择是否合法 if (isValid(state, choice)) { // 尝试:做出选择,更新状态 makeChoice(state, choice); backtrack(state, choices, res); // 回退:撤销选择,恢复到之前的状态 undoChoice(state, choice); } } } ``` === "C#" ```csharp title="" /* 回溯算法框架 */ void Backtrack(State state, List choices, List res) { // 判断是否为解 if (IsSolution(state)) { // 记录解 RecordSolution(state, res); // 不再继续搜索 return; } // 遍历所有选择 foreach (Choice choice in choices) { // 剪枝:判断选择是否合法 if (IsValid(state, choice)) { // 尝试:做出选择,更新状态 MakeChoice(state, choice); Backtrack(state, choices, res); // 回退:撤销选择,恢复到之前的状态 UndoChoice(state, choice); } } } ``` === "Go" ```go title="" /* 回溯算法框架 */ func backtrack(state *State, choices []Choice, res *[]State) { // 判断是否为解 if isSolution(state) { // 记录解 recordSolution(state, res) // 不再继续搜索 return } // 遍历所有选择 for _, choice := range choices { // 剪枝:判断选择是否合法 if isValid(state, choice) { // 尝试:做出选择,更新状态 makeChoice(state, choice) backtrack(state, choices, res) // 回退:撤销选择,恢复到之前的状态 undoChoice(state, choice) } } } ``` === "Swift" ```swift title="" /* 回溯算法框架 */ func backtrack(state: inout State, choices: [Choice], res: inout [State]) { // 判断是否为解 if isSolution(state: state) { // 记录解 recordSolution(state: state, res: &res) // 不再继续搜索 return } // 遍历所有选择 for choice in choices { // 剪枝:判断选择是否合法 if isValid(state: state, choice: choice) { // 尝试:做出选择,更新状态 makeChoice(state: &state, choice: choice) backtrack(state: &state, choices: choices, res: &res) // 回退:撤销选择,恢复到之前的状态 undoChoice(state: &state, choice: choice) } } } ``` === "JS" ```javascript title="" /* 回溯算法框架 */ function backtrack(state, choices, res) { // 判断是否为解 if (isSolution(state)) { // 记录解 recordSolution(state, res); // 不再继续搜索 return; } // 遍历所有选择 for (let choice of choices) { // 剪枝:判断选择是否合法 if (isValid(state, choice)) { // 尝试:做出选择,更新状态 makeChoice(state, choice); backtrack(state, choices, res); // 回退:撤销选择,恢复到之前的状态 undoChoice(state, choice); } } } ``` === "TS" ```typescript title="" /* 回溯算法框架 */ function backtrack(state: State, choices: Choice[], res: State[]): void { // 判断是否为解 if (isSolution(state)) { // 记录解 recordSolution(state, res); // 不再继续搜索 return; } // 遍历所有选择 for (let choice of choices) { // 剪枝:判断选择是否合法 if (isValid(state, choice)) { // 尝试:做出选择,更新状态 makeChoice(state, choice); backtrack(state, choices, res); // 回退:撤销选择,恢复到之前的状态 undoChoice(state, choice); } } } ``` === "Dart" ```dart title="" /* 回溯算法框架 */ void backtrack(State state, List, List res) { // 判断是否为解 if (isSolution(state)) { // 记录解 recordSolution(state, res); // 不再继续搜索 return; } // 遍历所有选择 for (Choice choice in choices) { // 剪枝:判断选择是否合法 if (isValid(state, choice)) { // 尝试:做出选择,更新状态 makeChoice(state, choice); backtrack(state, choices, res); // 回退:撤销选择,恢复到之前的状态 undoChoice(state, choice); } } } ``` === "Rust" ```rust title="" /* 回溯算法框架 */ fn backtrack(state: &mut State, choices: &Vec, res: &mut Vec) { // 判断是否为解 if is_solution(state) { // 记录解 record_solution(state, res); // 不再继续搜索 return; } // 遍历所有选择 for choice in choices { // 剪枝:判断选择是否合法 if is_valid(state, choice) { // 尝试:做出选择,更新状态 make_choice(state, choice); backtrack(state, choices, res); // 回退:撤销选择,恢复到之前的状态 undo_choice(state, choice); } } } ``` === "C" ```c title="" /* 回溯算法框架 */ void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) { // 判断是否为解 if (isSolution(state)) { // 记录解 recordSolution(state, res, numRes); // 不再继续搜索 return; } // 遍历所有选择 for (int i = 0; i < numChoices; i++) { // 剪枝:判断选择是否合法 if (isValid(state, &choices[i])) { // 尝试:做出选择,更新状态 makeChoice(state, &choices[i]); backtrack(state, choices, numChoices, res, numRes); // 回退:撤销选择,恢复到之前的状态 undoChoice(state, &choices[i]); } } } ``` === "Kotlin" ```kotlin title="" /* 回溯算法框架 */ fun backtrack(state: State?, choices: List, res: List?) { // 判断是否为解 if (isSolution(state)) { // 记录解 recordSolution(state, res) // 不再继续搜索 return } // 遍历所有选择 for (choice in choices) { // 剪枝:判断选择是否合法 if (isValid(state, choice)) { // 尝试:做出选择,更新状态 makeChoice(state, choice) backtrack(state, choices, res) // 回退:撤销选择,恢复到之前的状态 undoChoice(state, choice) } } } ``` === "Ruby" ```ruby title="" ### 回溯算法框架 ### def backtrack(state, choices, res) # 判断是否为解 if is_solution?(state) # 记录解 record_solution(state, res) return end # 遍历所有选择 for choice in choices # 剪枝:判断选择是否合法 if is_valid?(state, choice) # 尝试:做出选择,更新状态 make_choice(state, choice) backtrack(state, choices, res) # 回退:撤销选择,恢复到之前的状态 undo_choice(state, choice) end end end ``` 接下来,我们基于框架代码来解决例题三。状态 `state` 为节点遍历路径,选择 `choices` 为当前节点的左子节点和右子节点,结果 `res` 是路径列表: ```src [file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack} ``` 根据题意,我们在找到值为 $7$ 的节点后应该继续搜索,**因此需要将记录解之后的 `return` 语句删除**。下图对比了保留或删除 `return` 语句的搜索过程。 ![保留与删除 return 的搜索过程对比](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) 相比基于前序遍历的代码实现,基于回溯算法框架的代码实现虽然显得啰唆,但通用性更好。实际上,**许多回溯问题可以在该框架下解决**。我们只需根据具体问题来定义 `state` 和 `choices` ,并实现框架中的各个方法即可。 ## 常用术语 为了更清晰地分析算法问题,我们总结一下回溯算法中常用术语的含义,并对照例题三给出对应示例,如下表所示。

  常见的回溯算法术语

| 名词 | 定义 | 例题三 | | ---------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- | | 解(solution) | 解是满足问题特定条件的答案,可能有一个或多个 | 根节点到节点 $7$ 的满足约束条件的所有路径 | | 约束条件(constraint) | 约束条件是问题中限制解的可行性的条件,通常用于剪枝 | 路径中不包含节点 $3$ | | 状态(state) | 状态表示问题在某一时刻的情况,包括已经做出的选择 | 当前已访问的节点路径,即 `path` 节点列表 | | 尝试(attempt) | 尝试是根据可用选择来探索解空间的过程,包括做出选择,更新状态,检查是否为解 | 递归访问左(右)子节点,将节点添加进 `path` ,判断节点的值是否为 $7$ | | 回退(backtracking) | 回退指遇到不满足约束条件的状态时,撤销前面做出的选择,回到上一个状态 | 当越过叶节点、结束节点访问、遇到值为 $3$ 的节点时终止搜索,函数返回 | | 剪枝(pruning) | 剪枝是根据问题特性和约束条件避免无意义的搜索路径的方法,可提高搜索效率 | 当遇到值为 $3$ 的节点时,则不再继续搜索 | !!! tip 问题、解、状态等概念是通用的,在分治、回溯、动态规划、贪心等算法中都有涉及。 ## 优点与局限性 回溯算法本质上是一种深度优先搜索算法,它尝试所有可能的解决方案直到找到满足条件的解。这种方法的优点在于能够找到所有可能的解决方案,而且在合理的剪枝操作下,具有很高的效率。 然而,在处理大规模或者复杂问题时,**回溯算法的运行效率可能难以接受**。 - **时间**:回溯算法通常需要遍历状态空间的所有可能,时间复杂度可以达到指数阶或阶乘阶。 - **空间**:在递归调用中需要保存当前的状态(例如路径、用于剪枝的辅助变量等),当深度很大时,空间需求可能会变得很大。 即便如此,**回溯算法仍然是某些搜索问题和约束满足问题的最佳解决方案**。对于这些问题,由于无法预测哪些选择可生成有效的解,因此我们必须对所有可能的选择进行遍历。在这种情况下,**关键是如何优化效率**,常见的效率优化方法有两种。 - **剪枝**:避免搜索那些肯定不会产生解的路径,从而节省时间和空间。 - **启发式搜索**:在搜索过程中引入一些策略或者估计值,从而优先搜索最有可能产生有效解的路径。 ## 回溯典型例题 回溯算法可用于解决许多搜索问题、约束满足问题和组合优化问题。 **搜索问题**:这类问题的目标是找到满足特定条件的解决方案。 - 全排列问题:给定一个集合,求出其所有可能的排列组合。 - 子集和问题:给定一个集合和一个目标和,找到集合中所有和为目标和的子集。 - 汉诺塔问题:给定三根柱子和一系列大小不同的圆盘,要求将所有圆盘从一根柱子移动到另一根柱子,每次只能移动一个圆盘,且不能将大圆盘放在小圆盘上。 **约束满足问题**:这类问题的目标是找到满足所有约束条件的解。 - $n$ 皇后:在 $n \times n$ 的棋盘上放置 $n$ 个皇后,使得它们互不攻击。 - 数独:在 $9 \times 9$ 的网格中填入数字 $1$ ~ $9$ ,使得每行、每列和每个 $3 \times 3$ 子网格中的数字不重复。 - 图着色问题:给定一个无向图,用最少的颜色给图的每个顶点着色,使得相邻顶点颜色不同。 **组合优化问题**:这类问题的目标是在一个组合空间中找到满足某些条件的最优解。 - 0-1 背包问题:给定一组物品和一个背包,每个物品有一定的价值和重量,要求在背包容量限制内,选择物品使得总价值最大。 - 旅行商问题:在一个图中,从一个点出发,访问所有其他点恰好一次后返回起点,求最短路径。 - 最大团问题:给定一个无向图,找到最大的完全子图,即子图中的任意两个顶点之间都有边相连。 请注意,对于许多组合优化问题,回溯不是最优解决方案。 - 0-1 背包问题通常使用动态规划解决,以达到更高的时间效率。 - 旅行商是一个著名的 NP-Hard 问题,常用解法有遗传算法和蚁群算法等。 - 最大团问题是图论中的一个经典问题,可用贪心算法等启发式算法来解决。 ================================================ FILE: docs/chapter_backtracking/index.md ================================================ # 回溯 ![回溯](../assets/covers/chapter_backtracking.jpg) !!! abstract 我们如同迷宫中的探索者,在前进的道路上可能会遇到困难。 回溯的力量让我们能够重新开始,不断尝试,最终找到通往光明的出口。 ================================================ FILE: docs/chapter_backtracking/n_queens_problem.md ================================================ # n 皇后问题 !!! question 根据国际象棋的规则,皇后可以攻击与同处一行、一列或一条斜线上的棋子。给定 $n$ 个皇后和一个 $n \times n$ 大小的棋盘,寻找使得所有皇后之间无法相互攻击的摆放方案。 如下图所示,当 $n = 4$ 时,共可以找到两个解。从回溯算法的角度看,$n \times n$ 大小的棋盘共有 $n^2$ 个格子,给出了所有的选择 `choices` 。在逐个放置皇后的过程中,棋盘状态在不断地变化,每个时刻的棋盘就是状态 `state` 。 ![4 皇后问题的解](n_queens_problem.assets/solution_4_queens.png) 下图展示了本题的三个约束条件:**多个皇后不能在同一行、同一列、同一条对角线上**。值得注意的是,对角线分为主对角线 `\` 和次对角线 `/` 两种。 ![n 皇后问题的约束条件](n_queens_problem.assets/n_queens_constraints.png) ### 逐行放置策略 皇后的数量和棋盘的行数都为 $n$ ,因此我们容易得到一个推论:**棋盘每行都允许且只允许放置一个皇后**。 也就是说,我们可以采取逐行放置策略:从第一行开始,在每行放置一个皇后,直至最后一行结束。 下图所示为 4 皇后问题的逐行放置过程。受画幅限制,下图仅展开了第一行的其中一个搜索分支,并且将不满足列约束和对角线约束的方案都进行了剪枝。 ![逐行放置策略](n_queens_problem.assets/n_queens_placing.png) 从本质上看,**逐行放置策略起到了剪枝的作用**,它避免了同一行出现多个皇后的所有搜索分支。 ### 列与对角线剪枝 为了满足列约束,我们可以利用一个长度为 $n$ 的布尔型数组 `cols` 记录每一列是否有皇后。在每次决定放置前,我们通过 `cols` 将已有皇后的列进行剪枝,并在回溯中动态更新 `cols` 的状态。 !!! tip 请注意,矩阵的起点位于左上角,其中行索引从上到下增加,列索引从左到右增加。 那么,如何处理对角线约束呢?设棋盘中某个格子的行列索引为 $(row, col)$ ,选定矩阵中的某条主对角线,我们发现该对角线上所有格子的行索引减列索引都相等,**即主对角线上所有格子的 $row - col$ 为恒定值**。 也就是说,如果两个格子满足 $row_1 - col_1 = row_2 - col_2$ ,则它们一定处在同一条主对角线上。利用该规律,我们可以借助下图所示的数组 `diags1` 记录每条主对角线上是否有皇后。 同理,**次对角线上的所有格子的 $row + col$ 是恒定值**。我们同样也可以借助数组 `diags2` 来处理次对角线约束。 ![处理列约束和对角线约束](n_queens_problem.assets/n_queens_cols_diagonals.png) ### 代码实现 请注意,$n$ 维方阵中 $row - col$ 的范围是 $[-n + 1, n - 1]$ ,$row + col$ 的范围是 $[0, 2n - 2]$ ,所以主对角线和次对角线的数量都为 $2n - 1$ ,即数组 `diags1` 和 `diags2` 的长度都为 $2n - 1$ 。 ```src [file]{n_queens}-[class]{}-[func]{n_queens} ``` 逐行放置 $n$ 次,考虑列约束,则从第一行到最后一行分别有 $n$、$n-1$、$\dots$、$2$、$1$ 个选择,使用 $O(n!)$ 时间。当记录解时,需要复制矩阵 `state` 并添加进 `res` ,复制操作使用 $O(n^2)$ 时间。因此,**总体时间复杂度为 $O(n! \cdot n^2)$** 。实际上,根据对角线约束的剪枝也能够大幅缩小搜索空间,因而搜索效率往往优于以上时间复杂度。 数组 `state` 使用 $O(n^2)$ 空间,数组 `cols`、`diags1` 和 `diags2` 皆使用 $O(n)$ 空间。最大递归深度为 $n$ ,使用 $O(n)$ 栈帧空间。因此,**空间复杂度为 $O(n^2)$** 。 ================================================ FILE: docs/chapter_backtracking/permutations_problem.md ================================================ # 全排列问题 全排列问题是回溯算法的一个典型应用。它的定义是在给定一个集合(如一个数组或字符串)的情况下,找出其中元素的所有可能的排列。 下表列举了几个示例数据,包括输入数组和对应的所有排列。

  全排列示例

| 输入数组 | 所有排列 | | :---------- | :----------------------------------------------------------------- | | $[1]$ | $[1]$ | | $[1, 2]$ | $[1, 2], [2, 1]$ | | $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ | ## 无相等元素的情况 !!! question 输入一个整数数组,其中不包含重复元素,返回所有可能的排列。 从回溯算法的角度看,**我们可以把生成排列的过程想象成一系列选择的结果**。假设输入数组为 $[1, 2, 3]$ ,如果我们先选择 $1$ ,再选择 $3$ ,最后选择 $2$ ,则获得排列 $[1, 3, 2]$ 。回退表示撤销一个选择,之后继续尝试其他选择。 从回溯代码的角度看,候选集合 `choices` 是输入数组中的所有元素,状态 `state` 是直至目前已被选择的元素。请注意,每个元素只允许被选择一次,**因此 `state` 中的所有元素都应该是唯一的**。 如下图所示,我们可以将搜索过程展开成一棵递归树,树中的每个节点代表当前状态 `state` 。从根节点开始,经过三轮选择后到达叶节点,每个叶节点都对应一个排列。 ![全排列的递归树](permutations_problem.assets/permutations_i.png) ### 重复选择剪枝 为了实现每个元素只被选择一次,我们考虑引入一个布尔型数组 `selected` ,其中 `selected[i]` 表示 `choices[i]` 是否已被选择,并基于它实现以下剪枝操作。 - 在做出选择 `choice[i]` 后,我们就将 `selected[i]` 赋值为 $\text{True}$ ,代表它已被选择。 - 遍历选择列表 `choices` 时,跳过所有已被选择的节点,即剪枝。 如下图所示,假设我们第一轮选择 1 ,第二轮选择 3 ,第三轮选择 2 ,则需要在第二轮剪掉元素 1 的分支,在第三轮剪掉元素 1 和元素 3 的分支。 ![全排列剪枝示例](permutations_problem.assets/permutations_i_pruning.png) 观察上图发现,该剪枝操作将搜索空间大小从 $O(n^n)$ 减小至 $O(n!)$ 。 ### 代码实现 想清楚以上信息之后,我们就可以在框架代码中做“完形填空”了。为了缩短整体代码,我们不单独实现框架代码中的各个函数,而是将它们展开在 `backtrack()` 函数中: ```src [file]{permutations_i}-[class]{}-[func]{permutations_i} ``` ## 考虑相等元素的情况 !!! question 输入一个整数数组,**数组中可能包含重复元素**,返回所有不重复的排列。 假设输入数组为 $[1, 1, 2]$ 。为了方便区分两个重复元素 $1$ ,我们将第二个 $1$ 记为 $\hat{1}$ 。 如下图所示,上述方法生成的排列有一半是重复的。 ![重复排列](permutations_problem.assets/permutations_ii.png) 那么如何去除重复的排列呢?最直接地,考虑借助一个哈希集合,直接对排列结果进行去重。然而这样做不够优雅,**因为生成重复排列的搜索分支没有必要,应当提前识别并剪枝**,这样可以进一步提升算法效率。 ### 相等元素剪枝 观察下图,在第一轮中,选择 $1$ 或选择 $\hat{1}$ 是等价的,在这两个选择之下生成的所有排列都是重复的。因此应该把 $\hat{1}$ 剪枝。 同理,在第一轮选择 $2$ 之后,第二轮选择中的 $1$ 和 $\hat{1}$ 也会产生重复分支,因此也应将第二轮的 $\hat{1}$ 剪枝。 从本质上看,**我们的目标是在某一轮选择中,保证多个相等的元素仅被选择一次**。 ![重复排列剪枝](permutations_problem.assets/permutations_ii_pruning.png) ### 代码实现 在上一题的代码的基础上,我们考虑在每一轮选择中开启一个哈希集合 `duplicated` ,用于记录该轮中已经尝试过的元素,并将重复元素剪枝: ```src [file]{permutations_ii}-[class]{}-[func]{permutations_ii} ``` 假设元素两两之间互不相同,则 $n$ 个元素共有 $n!$ 种排列(阶乘);在记录结果时,需要复制长度为 $n$ 的列表,使用 $O(n)$ 时间。**因此时间复杂度为 $O(n!n)$** 。 最大递归深度为 $n$ ,使用 $O(n)$ 栈帧空间。`selected` 使用 $O(n)$ 空间。同一时刻最多共有 $n$ 个 `duplicated` ,使用 $O(n^2)$ 空间。**因此空间复杂度为 $O(n^2)$** 。 ### 两种剪枝对比 请注意,虽然 `selected` 和 `duplicated` 都用于剪枝,但两者的目标不同。 - **重复选择剪枝**:整个搜索过程中只有一个 `selected` 。它记录的是当前状态中包含哪些元素,其作用是避免某个元素在 `state` 中重复出现。 - **相等元素剪枝**:每轮选择(每个调用的 `backtrack` 函数)都包含一个 `duplicated` 。它记录的是在本轮遍历(`for` 循环)中哪些元素已被选择过,其作用是保证相等元素只被选择一次。 下图展示了两个剪枝条件的生效范围。注意,树中的每个节点代表一个选择,从根节点到叶节点的路径上的各个节点构成一个排列。 ![两种剪枝条件的作用范围](permutations_problem.assets/permutations_ii_pruning_summary.png) ================================================ FILE: docs/chapter_backtracking/subset_sum_problem.md ================================================ # 子集和问题 ## 无重复元素的情况 !!! question 给定一个正整数数组 `nums` 和一个目标正整数 `target` ,请找出所有可能的组合,使得组合中的元素和等于 `target` 。给定数组无重复元素,每个元素可以被选取多次。请以列表形式返回这些组合,列表中不应包含重复组合。 例如,输入集合 $\{3, 4, 5\}$ 和目标整数 $9$ ,解为 $\{3, 3, 3\}, \{4, 5\}$ 。需要注意以下两点。 - 输入集合中的元素可以被无限次重复选取。 - 子集不区分元素顺序,比如 $\{4, 5\}$ 和 $\{5, 4\}$ 是同一个子集。 ### 参考全排列解法 类似于全排列问题,我们可以把子集的生成过程想象成一系列选择的结果,并在选择过程中实时更新“元素和”,当元素和等于 `target` 时,就将子集记录至结果列表。 而与全排列问题不同的是,**本题集合中的元素可以被无限次选取**,因此无须借助 `selected` 布尔列表来记录元素是否已被选择。我们可以对全排列代码进行小幅修改,初步得到解题代码: ```src [file]{subset_sum_i_naive}-[class]{}-[func]{subset_sum_i_naive} ``` 向以上代码输入数组 $[3, 4, 5]$ 和目标元素 $9$ ,输出结果为 $[3, 3, 3], [4, 5], [5, 4]$ 。**虽然成功找出了所有和为 $9$ 的子集,但其中存在重复的子集 $[4, 5]$ 和 $[5, 4]$** 。 这是因为搜索过程是区分选择顺序的,然而子集不区分选择顺序。如下图所示,先选 $4$ 后选 $5$ 与先选 $5$ 后选 $4$ 是不同的分支,但对应同一个子集。 ![子集搜索与越界剪枝](subset_sum_problem.assets/subset_sum_i_naive.png) 为了去除重复子集,**一种直接的思路是对结果列表进行去重**。但这个方法效率很低,有两方面原因。 - 当数组元素较多,尤其是当 `target` 较大时,搜索过程会产生大量的重复子集。 - 比较子集(数组)的异同非常耗时,需要先排序数组,再比较数组中每个元素的异同。 ### 重复子集剪枝 **我们考虑在搜索过程中通过剪枝进行去重**。观察下图,重复子集是在以不同顺序选择数组元素时产生的,例如以下情况。 1. 当第一轮和第二轮分别选择 $3$ 和 $4$ 时,会生成包含这两个元素的所有子集,记为 $[3, 4, \dots]$ 。 2. 之后,当第一轮选择 $4$ 时,**则第二轮应该跳过 $3$** ,因为该选择产生的子集 $[4, 3, \dots]$ 和第 `1.` 步中生成的子集完全重复。 在搜索过程中,每一层的选择都是从左到右被逐个尝试的,因此越靠右的分支被剪掉的越多。 1. 前两轮选择 $3$ 和 $5$ ,生成子集 $[3, 5, \dots]$ 。 2. 前两轮选择 $4$ 和 $5$ ,生成子集 $[4, 5, \dots]$ 。 3. 若第一轮选择 $5$ ,**则第二轮应该跳过 $3$ 和 $4$** ,因为子集 $[5, 3, \dots]$ 和 $[5, 4, \dots]$ 与第 `1.` 步和第 `2.` 步中描述的子集完全重复。 ![不同选择顺序导致的重复子集](subset_sum_problem.assets/subset_sum_i_pruning.png) 总结来看,给定输入数组 $[x_1, x_2, \dots, x_n]$ ,设搜索过程中的选择序列为 $[x_{i_1}, x_{i_2}, \dots, x_{i_m}]$ ,则该选择序列需要满足 $i_1 \leq i_2 \leq \dots \leq i_m$ ,**不满足该条件的选择序列都会造成重复,应当剪枝**。 ### 代码实现 为实现该剪枝,我们初始化变量 `start` ,用于指示遍历起始点。**当做出选择 $x_{i}$ 后,设定下一轮从索引 $i$ 开始遍历**。这样做就可以让选择序列满足 $i_1 \leq i_2 \leq \dots \leq i_m$ ,从而保证子集唯一。 除此之外,我们还对代码进行了以下两项优化。 - 在开启搜索前,先将数组 `nums` 排序。在遍历所有选择时,**当子集和超过 `target` 时直接结束循环**,因为后边的元素更大,其子集和一定超过 `target` 。 - 省去元素和变量 `total` ,**通过在 `target` 上执行减法来统计元素和**,当 `target` 等于 $0$ 时记录解。 ```src [file]{subset_sum_i}-[class]{}-[func]{subset_sum_i} ``` 下图所示为将数组 $[3, 4, 5]$ 和目标元素 $9$ 输入以上代码后的整体回溯过程。 ![子集和 I 回溯过程](subset_sum_problem.assets/subset_sum_i.png) ## 考虑重复元素的情况 !!! question 给定一个正整数数组 `nums` 和一个目标正整数 `target` ,请找出所有可能的组合,使得组合中的元素和等于 `target` 。**给定数组可能包含重复元素,每个元素只可被选择一次**。请以列表形式返回这些组合,列表中不应包含重复组合。 相比于上题,**本题的输入数组可能包含重复元素**,这引入了新的问题。例如,给定数组 $[4, \hat{4}, 5]$ 和目标元素 $9$ ,则现有代码的输出结果为 $[4, 5], [\hat{4}, 5]$ ,出现了重复子集。 **造成这种重复的原因是相等元素在某轮中被多次选择**。在下图中,第一轮共有三个选择,其中两个都为 $4$ ,会产生两个重复的搜索分支,从而输出重复子集;同理,第二轮的两个 $4$ 也会产生重复子集。 ![相等元素导致的重复子集](subset_sum_problem.assets/subset_sum_ii_repeat.png) ### 相等元素剪枝 为解决此问题,**我们需要限制相等元素在每一轮中只能被选择一次**。实现方式比较巧妙:由于数组是已排序的,因此相等元素都是相邻的。这意味着在某轮选择中,若当前元素与其左边元素相等,则说明它已经被选择过,因此直接跳过当前元素。 与此同时,**本题规定每个数组元素只能被选择一次**。幸运的是,我们也可以利用变量 `start` 来满足该约束:当做出选择 $x_{i}$ 后,设定下一轮从索引 $i + 1$ 开始向后遍历。这样既能去除重复子集,也能避免重复选择元素。 ### 代码实现 ```src [file]{subset_sum_ii}-[class]{}-[func]{subset_sum_ii} ``` 下图展示了数组 $[4, 4, 5]$ 和目标元素 $9$ 的回溯过程,共包含四种剪枝操作。请你将图示与代码注释相结合,理解整个搜索过程,以及每种剪枝操作是如何工作的。 ![子集和 II 回溯过程](subset_sum_problem.assets/subset_sum_ii.png) ================================================ FILE: docs/chapter_backtracking/summary.md ================================================ # 小结 ### 重点回顾 - 回溯算法本质是穷举法,通过对解空间进行深度优先遍历来寻找符合条件的解。在搜索过程中,遇到满足条件的解则记录,直至找到所有解或遍历完成后结束。 - 回溯算法的搜索过程包括尝试与回退两个部分。它通过深度优先搜索来尝试各种选择,当遇到不满足约束条件的情况时,则撤销上一步的选择,退回到之前的状态,并继续尝试其他选择。尝试与回退是两个方向相反的操作。 - 回溯问题通常包含多个约束条件,它们可用于实现剪枝操作。剪枝可以提前结束不必要的搜索分支,大幅提升搜索效率。 - 回溯算法主要可用于解决搜索问题和约束满足问题。组合优化问题虽然可以用回溯算法解决,但往往存在效率更高或效果更好的解法。 - 全排列问题旨在搜索给定集合元素的所有可能的排列。我们借助一个数组来记录每个元素是否被选择,剪掉重复选择同一元素的搜索分支,确保每个元素只被选择一次。 - 在全排列问题中,如果集合中存在重复元素,则最终结果会出现重复排列。我们需要约束相等元素在每轮中只能被选择一次,这通常借助一个哈希集合来实现。 - 子集和问题的目标是在给定集合中找到和为目标值的所有子集。集合不区分元素顺序,而搜索过程会输出所有顺序的结果,产生重复子集。我们在回溯前将数据进行排序,并设置一个变量来指示每一轮的遍历起始点,从而将生成重复子集的搜索分支进行剪枝。 - 对于子集和问题,数组中的相等元素会产生重复集合。我们利用数组已排序的前置条件,通过判断相邻元素是否相等实现剪枝,从而确保相等元素在每轮中只能被选中一次。 - $n$ 皇后问题旨在寻找将 $n$ 个皇后放置到 $n \times n$ 尺寸棋盘上的方案,要求所有皇后两两之间无法攻击对方。该问题的约束条件有行约束、列约束、主对角线和次对角线约束。为满足行约束,我们采用按行放置的策略,保证每一行放置一个皇后。 - 列约束和对角线约束的处理方式类似。对于列约束,我们利用一个数组来记录每一列是否有皇后,从而指示选中的格子是否合法。对于对角线约束,我们借助两个数组来分别记录该主、次对角线上是否存在皇后;难点在于找出处在同一主(副)对角线上的格子所满足的行列索引规律。 ### Q & A **Q**:怎么理解回溯和递归的关系? 总的来看,回溯是一种“算法策略”,而递归更像是一个“工具”。 - 回溯算法通常基于递归实现。然而,回溯是递归的应用场景之一,是递归在搜索问题中的应用。 - 递归的结构体现了“子问题分解”的解题范式,常用于解决分治、回溯、动态规划(记忆化递归)等问题。 ================================================ FILE: docs/chapter_computational_complexity/index.md ================================================ # 复杂度分析 ![复杂度分析](../assets/covers/chapter_complexity_analysis.jpg) !!! abstract 复杂度分析犹如浩瀚的算法宇宙中的时空向导。 它带领我们在时间与空间这两个维度上深入探索,寻找更优雅的解决方案。 ================================================ FILE: docs/chapter_computational_complexity/iteration_and_recursion.md ================================================ # 迭代与递归 在算法中,重复执行某个任务是很常见的,它与复杂度分析息息相关。因此,在介绍时间复杂度和空间复杂度之前,我们先来了解如何在程序中实现重复执行任务,即两种基本的程序控制结构:迭代、递归。 ## 迭代 迭代(iteration)是一种重复执行某个任务的控制结构。在迭代中,程序会在满足一定的条件下重复执行某段代码,直到这个条件不再满足。 ### for 循环 `for` 循环是最常见的迭代形式之一,**适合在预先知道迭代次数时使用**。 以下函数基于 `for` 循环实现了求和 $1 + 2 + \dots + n$ ,求和结果使用变量 `res` 记录。需要注意的是,Python 中 `range(a, b)` 对应的区间是“左闭右开”的,对应的遍历范围为 $a, a + 1, \dots, b-1$ : ```src [file]{iteration}-[class]{}-[func]{for_loop} ``` 下图是该求和函数的流程框图。 ![求和函数的流程框图](iteration_and_recursion.assets/iteration.png) 此求和函数的操作数量与输入数据大小 $n$ 成正比,或者说成“线性关系”。实际上,**时间复杂度描述的就是这个“线性关系”**。相关内容将会在下一节中详细介绍。 ### while 循环 与 `for` 循环类似,`while` 循环也是一种实现迭代的方法。在 `while` 循环中,程序每轮都会先检查条件,如果条件为真,则继续执行,否则就结束循环。 下面我们用 `while` 循环来实现求和 $1 + 2 + \dots + n$ : ```src [file]{iteration}-[class]{}-[func]{while_loop} ``` **`while` 循环比 `for` 循环的自由度更高**。在 `while` 循环中,我们可以自由地设计条件变量的初始化和更新步骤。 例如在以下代码中,条件变量 $i$ 每轮进行两次更新,这种情况就不太方便用 `for` 循环实现: ```src [file]{iteration}-[class]{}-[func]{while_loop_ii} ``` 总的来说,**`for` 循环的代码更加紧凑,`while` 循环更加灵活**,两者都可以实现迭代结构。选择使用哪一个应该根据特定问题的需求来决定。 ### 嵌套循环 我们可以在一个循环结构内嵌套另一个循环结构,下面以 `for` 循环为例: ```src [file]{iteration}-[class]{}-[func]{nested_for_loop} ``` 下图是该嵌套循环的流程框图。 ![嵌套循环的流程框图](iteration_and_recursion.assets/nested_iteration.png) 在这种情况下,函数的操作数量与 $n^2$ 成正比,或者说算法运行时间和输入数据大小 $n$ 成“平方关系”。 我们可以继续添加嵌套循环,每一次嵌套都是一次“升维”,将会使时间复杂度提高至“立方关系”“四次方关系”,以此类推。 ## 递归 递归(recursion)是一种算法策略,通过函数调用自身来解决问题。它主要包含两个阶段。 1. **递**:程序不断深入地调用自身,通常传入更小或更简化的参数,直到达到“终止条件”。 2. **归**:触发“终止条件”后,程序从最深层的递归函数开始逐层返回,汇聚每一层的结果。 而从实现的角度看,递归代码主要包含三个要素。 1. **终止条件**:用于决定什么时候由“递”转“归”。 2. **递归调用**:对应“递”,函数调用自身,通常输入更小或更简化的参数。 3. **返回结果**:对应“归”,将当前递归层级的结果返回至上一层。 观察以下代码,我们只需调用函数 `recur(n)` ,就可以完成 $1 + 2 + \dots + n$ 的计算: ```src [file]{recursion}-[class]{}-[func]{recur} ``` 下图展示了该函数的递归过程。 ![求和函数的递归过程](iteration_and_recursion.assets/recursion_sum.png) 虽然从计算角度看,迭代与递归可以得到相同的结果,**但它们代表了两种完全不同的思考和解决问题的范式**。 - **迭代**:“自下而上”地解决问题。从最基础的步骤开始,然后不断重复或累加这些步骤,直到任务完成。 - **递归**:“自上而下”地解决问题。将原问题分解为更小的子问题,这些子问题和原问题具有相同的形式。接下来将子问题继续分解为更小的子问题,直到基本情况时停止(基本情况的解是已知的)。 以上述求和函数为例,设问题 $f(n) = 1 + 2 + \dots + n$ 。 - **迭代**:在循环中模拟求和过程,从 $1$ 遍历到 $n$ ,每轮执行求和操作,即可求得 $f(n)$ 。 - **递归**:将问题分解为子问题 $f(n) = n + f(n-1)$ ,不断(递归地)分解下去,直至基本情况 $f(1) = 1$ 时终止。 ### 调用栈 递归函数每次调用自身时,系统都会为新开启的函数分配内存,以存储局部变量、调用地址和其他信息等。这将导致两方面的结果。 - 函数的上下文数据都存储在称为“栈帧空间”的内存区域中,直至函数返回后才会被释放。因此,**递归通常比迭代更加耗费内存空间**。 - 递归调用函数会产生额外的开销。**因此递归通常比循环的时间效率更低**。 如下图所示,在触发终止条件前,同时存在 $n$ 个未返回的递归函数,**递归深度为 $n$** 。 ![递归调用深度](iteration_and_recursion.assets/recursion_sum_depth.png) 在实际中,编程语言允许的递归深度通常是有限的,过深的递归可能导致栈溢出错误。 ### 尾递归 有趣的是,**如果函数在返回前的最后一步才进行递归调用**,则该函数可以被编译器或解释器优化,使其在空间效率上与迭代相当。这种情况被称为尾递归(tail recursion)。 - **普通递归**:当函数返回到上一层级的函数后,需要继续执行代码,因此系统需要保存上一层调用的上下文。 - **尾递归**:递归调用是函数返回前的最后一个操作,这意味着函数返回到上一层级后,无须继续执行其他操作,因此系统无须保存上一层函数的上下文。 以计算 $1 + 2 + \dots + n$ 为例,我们可以将结果变量 `res` 设为函数参数,从而实现尾递归: ```src [file]{recursion}-[class]{}-[func]{tail_recur} ``` 尾递归的执行过程如下图所示。对比普通递归和尾递归,两者的求和操作的执行点是不同的。 - **普通递归**:求和操作是在“归”的过程中执行的,每层返回后都要再执行一次求和操作。 - **尾递归**:求和操作是在“递”的过程中执行的,“归”的过程只需层层返回。 ![尾递归过程](iteration_and_recursion.assets/tail_recursion_sum.png) !!! tip 请注意,许多编译器或解释器并不支持尾递归优化。例如,Python 默认不支持尾递归优化,因此即使函数是尾递归形式,仍然可能会遇到栈溢出问题。 ### 递归树 当处理与“分治”相关的算法问题时,递归往往比迭代的思路更加直观、代码更加易读。以“斐波那契数列”为例。 !!! question 给定一个斐波那契数列 $0, 1, 1, 2, 3, 5, 8, 13, \dots$ ,求该数列的第 $n$ 个数字。 设斐波那契数列的第 $n$ 个数字为 $f(n)$ ,易得两个结论。 - 数列的前两个数字为 $f(1) = 0$ 和 $f(2) = 1$ 。 - 数列中的每个数字是前两个数字的和,即 $f(n) = f(n - 1) + f(n - 2)$ 。 按照递推关系进行递归调用,将前两个数字作为终止条件,便可写出递归代码。调用 `fib(n)` 即可得到斐波那契数列的第 $n$ 个数字: ```src [file]{recursion}-[class]{}-[func]{fib} ``` 观察以上代码,我们在函数内递归调用了两个函数,**这意味着从一个调用产生了两个调用分支**。如下图所示,这样不断递归调用下去,最终将产生一棵层数为 $n$ 的递归树(recursion tree)。 ![斐波那契数列的递归树](iteration_and_recursion.assets/recursion_tree.png) 从本质上看,递归体现了“将问题分解为更小子问题”的思维范式,这种分治策略至关重要。 - 从算法角度看,搜索、排序、回溯、分治、动态规划等许多重要算法策略直接或间接地应用了这种思维方式。 - 从数据结构角度看,递归天然适合处理链表、树和图的相关问题,因为它们非常适合用分治思想进行分析。 ## 两者对比 总结以上内容,如下表所示,迭代和递归在实现、性能和适用性上有所不同。

  迭代与递归特点对比

| | 迭代 | 递归 | | -------- | -------------------------------------- | ------------------------------------------------------------ | | 实现方式 | 循环结构 | 函数调用自身 | | 时间效率 | 效率通常较高,无函数调用开销 | 每次函数调用都会产生开销 | | 内存使用 | 通常使用固定大小的内存空间 | 累积函数调用可能使用大量的栈帧空间 | | 适用问题 | 适用于简单循环任务,代码直观、可读性好 | 适用于子问题分解,如树、图、分治、回溯等,代码结构简洁、清晰 | !!! tip 如果感觉以下内容理解困难,可以在读完“栈”章节后再来复习。 那么,迭代和递归具有什么内在联系呢?以上述递归函数为例,求和操作在递归的“归”阶段进行。这意味着最初被调用的函数实际上是最后完成其求和操作的,**这种工作机制与栈的“先入后出”原则异曲同工**。 事实上,“调用栈”和“栈帧空间”这类递归术语已经暗示了递归与栈之间的密切关系。 1. **递**:当函数被调用时,系统会在“调用栈”上为该函数分配新的栈帧,用于存储函数的局部变量、参数、返回地址等数据。 2. **归**:当函数完成执行并返回时,对应的栈帧会被从“调用栈”上移除,恢复之前函数的执行环境。 因此,**我们可以使用一个显式的栈来模拟调用栈的行为**,从而将递归转化为迭代形式: ```src [file]{recursion}-[class]{}-[func]{for_loop_recur} ``` 观察以上代码,当递归转化为迭代后,代码变得更加复杂了。尽管迭代和递归在很多情况下可以互相转化,但不一定值得这样做,有以下两点原因。 - 转化后的代码可能更加难以理解,可读性更差。 - 对于某些复杂问题,模拟系统调用栈的行为可能非常困难。 总之,**选择迭代还是递归取决于特定问题的性质**。在编程实践中,权衡两者的优劣并根据情境选择合适的方法至关重要。 ================================================ FILE: docs/chapter_computational_complexity/performance_evaluation.md ================================================ # 算法效率评估 在算法设计中,我们先后追求以下两个层面的目标。 1. **找到问题解法**:算法需要在规定的输入范围内可靠地求得问题的正确解。 2. **寻求最优解法**:同一个问题可能存在多种解法,我们希望找到尽可能高效的算法。 也就是说,在能够解决问题的前提下,算法效率已成为衡量算法优劣的主要评价指标,它包括以下两个维度。 - **时间效率**:算法运行时间的长短。 - **空间效率**:算法占用内存空间的大小。 简而言之,**我们的目标是设计“既快又省”的数据结构与算法**。而有效地评估算法效率至关重要,因为只有这样,我们才能将各种算法进行对比,进而指导算法设计与优化过程。 效率评估方法主要分为两种:实际测试、理论估算。 ## 实际测试 假设我们现在有算法 `A` 和算法 `B` ,它们都能解决同一问题,现在需要对比这两个算法的效率。最直接的方法是找一台计算机,运行这两个算法,并监控记录它们的运行时间和内存占用情况。这种评估方式能够反映真实情况,但也存在较大的局限性。 一方面,**难以排除测试环境的干扰因素**。硬件配置会影响算法的性能表现。比如一个算法的并行度较高,那么它就更适合在多核 CPU 上运行,一个算法的内存操作密集,那么它在高性能内存上的表现就会更好。也就是说,算法在不同的机器上的测试结果可能是不一致的。这意味着我们需要在各种机器上进行测试,统计平均效率,而这是不现实的。 另一方面,**展开完整测试非常耗费资源**。随着输入数据量的变化,算法会表现出不同的效率。例如,在输入数据量较小时,算法 `A` 的运行时间比算法 `B` 短;而在输入数据量较大时,测试结果可能恰恰相反。因此,为了得到有说服力的结论,我们需要测试各种规模的输入数据,而这需要耗费大量的计算资源。 ## 理论估算 由于实际测试具有较大的局限性,我们可以考虑仅通过一些计算来评估算法的效率。这种估算方法被称为渐近复杂度分析(asymptotic complexity analysis),简称复杂度分析。 复杂度分析能够体现算法运行所需的时间和空间资源与输入数据规模之间的关系。**它描述了随着输入数据规模的增加,算法执行所需时间和空间的增长趋势**。这个定义有些拗口,我们可以将其分为三个重点来理解。 - “时间和空间资源”分别对应时间复杂度(time complexity)空间复杂度(space complexity)。 - “随着输入数据规模的增加”意味着复杂度反映了算法运行效率与输入数据规模之间的关系。 - “时间和空间的增长趋势”表示复杂度分析关注的不是运行时间或占用空间的具体值,而是时间或空间增长的“快慢”。 **复杂度分析克服了实际测试方法的弊端**,体现在以下几个方面。 - 它无需实际运行代码,更加绿色节能。 - 它独立于测试环境,分析结果适用于所有运行平台。 - 它可以体现不同数据量下的算法效率,尤其是在大数据量下的算法性能。 !!! tip 如果你仍对复杂度的概念感到困惑,无须担心,我们会在后续章节中详细介绍。 复杂度分析为我们提供了一把评估算法效率的“标尺”,使我们可以衡量执行某个算法所需的时间和空间资源,对比不同算法之间的效率。 复杂度是个数学概念,对于初学者可能比较抽象,学习难度相对较高。从这个角度看,复杂度分析可能不太适合作为最先介绍的内容。然而,当我们讨论某个数据结构或算法的特点时,难以避免要分析其运行速度和空间使用情况。 综上所述,建议你在深入学习数据结构与算法之前,**先对复杂度分析建立初步的了解,以便能够完成简单算法的复杂度分析**。 ================================================ FILE: docs/chapter_computational_complexity/space_complexity.md ================================================ # 空间复杂度 空间复杂度(space complexity)用于衡量算法占用内存空间随着数据量变大时的增长趋势。这个概念与时间复杂度非常类似,只需将“运行时间”替换为“占用内存空间”。 ## 算法相关空间 算法在运行过程中使用的内存空间主要包括以下几种。 - **输入空间**:用于存储算法的输入数据。 - **暂存空间**:用于存储算法在运行过程中的变量、对象、函数上下文等数据。 - **输出空间**:用于存储算法的输出数据。 一般情况下,空间复杂度的统计范围是“暂存空间”加上“输出空间”。 暂存空间可以进一步划分为三个部分。 - **暂存数据**:用于保存算法运行过程中的各种常量、变量、对象等。 - **栈帧空间**:用于保存调用函数的上下文数据。系统在每次调用函数时都会在栈顶部创建一个栈帧,函数返回后,栈帧空间会被释放。 - **指令空间**:用于保存编译后的程序指令,在实际统计中通常忽略不计。 在分析一段程序的空间复杂度时,**我们通常统计暂存数据、栈帧空间和输出数据三部分**,如下图所示。 ![算法使用的相关空间](space_complexity.assets/space_types.png) 相关代码如下: === "Python" ```python title="" class Node: """类""" def __init__(self, x: int): self.val: int = x # 节点值 self.next: Node | None = None # 指向下一节点的引用 def function() -> int: """函数""" # 执行某些操作... return 0 def algorithm(n) -> int: # 输入数据 A = 0 # 暂存数据(常量,一般用大写字母表示) b = 0 # 暂存数据(变量) node = Node(0) # 暂存数据(对象) c = function() # 栈帧空间(调用函数) return A + b + c # 输出数据 ``` === "C++" ```cpp title="" /* 结构体 */ struct Node { int val; Node *next; Node(int x) : val(x), next(nullptr) {} }; /* 函数 */ int func() { // 执行某些操作... return 0; } int algorithm(int n) { // 输入数据 const int a = 0; // 暂存数据(常量) int b = 0; // 暂存数据(变量) Node* node = new Node(0); // 暂存数据(对象) int c = func(); // 栈帧空间(调用函数) return a + b + c; // 输出数据 } ``` === "Java" ```java title="" /* 类 */ class Node { int val; Node next; Node(int x) { val = x; } } /* 函数 */ int function() { // 执行某些操作... return 0; } int algorithm(int n) { // 输入数据 final int a = 0; // 暂存数据(常量) int b = 0; // 暂存数据(变量) Node node = new Node(0); // 暂存数据(对象) int c = function(); // 栈帧空间(调用函数) return a + b + c; // 输出数据 } ``` === "C#" ```csharp title="" /* 类 */ class Node(int x) { int val = x; Node next; } /* 函数 */ int Function() { // 执行某些操作... return 0; } int Algorithm(int n) { // 输入数据 const int a = 0; // 暂存数据(常量) int b = 0; // 暂存数据(变量) Node node = new(0); // 暂存数据(对象) int c = Function(); // 栈帧空间(调用函数) return a + b + c; // 输出数据 } ``` === "Go" ```go title="" /* 结构体 */ type node struct { val int next *node } /* 创建 node 结构体 */ func newNode(val int) *node { return &node{val: val} } /* 函数 */ func function() int { // 执行某些操作... return 0 } func algorithm(n int) int { // 输入数据 const a = 0 // 暂存数据(常量) b := 0 // 暂存数据(变量) newNode(0) // 暂存数据(对象) c := function() // 栈帧空间(调用函数) return a + b + c // 输出数据 } ``` === "Swift" ```swift title="" /* 类 */ class Node { var val: Int var next: Node? init(x: Int) { val = x } } /* 函数 */ func function() -> Int { // 执行某些操作... return 0 } func algorithm(n: Int) -> Int { // 输入数据 let a = 0 // 暂存数据(常量) var b = 0 // 暂存数据(变量) let node = Node(x: 0) // 暂存数据(对象) let c = function() // 栈帧空间(调用函数) return a + b + c // 输出数据 } ``` === "JS" ```javascript title="" /* 类 */ class Node { val; next; constructor(val) { this.val = val === undefined ? 0 : val; // 节点值 this.next = null; // 指向下一节点的引用 } } /* 函数 */ function constFunc() { // 执行某些操作 return 0; } function algorithm(n) { // 输入数据 const a = 0; // 暂存数据(常量) let b = 0; // 暂存数据(变量) const node = new Node(0); // 暂存数据(对象) const c = constFunc(); // 栈帧空间(调用函数) return a + b + c; // 输出数据 } ``` === "TS" ```typescript title="" /* 类 */ class Node { val: number; next: Node | null; constructor(val?: number) { this.val = val === undefined ? 0 : val; // 节点值 this.next = null; // 指向下一节点的引用 } } /* 函数 */ function constFunc(): number { // 执行某些操作 return 0; } function algorithm(n: number): number { // 输入数据 const a = 0; // 暂存数据(常量) let b = 0; // 暂存数据(变量) const node = new Node(0); // 暂存数据(对象) const c = constFunc(); // 栈帧空间(调用函数) return a + b + c; // 输出数据 } ``` === "Dart" ```dart title="" /* 类 */ class Node { int val; Node next; Node(this.val, [this.next]); } /* 函数 */ int function() { // 执行某些操作... return 0; } int algorithm(int n) { // 输入数据 const int a = 0; // 暂存数据(常量) int b = 0; // 暂存数据(变量) Node node = Node(0); // 暂存数据(对象) int c = function(); // 栈帧空间(调用函数) return a + b + c; // 输出数据 } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* 结构体 */ struct Node { val: i32, next: Option>>, } /* 创建 Node 结构体 */ impl Node { fn new(val: i32) -> Self { Self { val: val, next: None } } } /* 函数 */ fn function() -> i32 { // 执行某些操作... return 0; } fn algorithm(n: i32) -> i32 { // 输入数据 const a: i32 = 0; // 暂存数据(常量) let mut b = 0; // 暂存数据(变量) let node = Node::new(0); // 暂存数据(对象) let c = function(); // 栈帧空间(调用函数) return a + b + c; // 输出数据 } ``` === "C" ```c title="" /* 函数 */ int func() { // 执行某些操作... return 0; } int algorithm(int n) { // 输入数据 const int a = 0; // 暂存数据(常量) int b = 0; // 暂存数据(变量) int c = func(); // 栈帧空间(调用函数) return a + b + c; // 输出数据 } ``` === "Kotlin" ```kotlin title="" /* 类 */ class Node(var _val: Int) { var next: Node? = null } /* 函数 */ fun function(): Int { // 执行某些操作... return 0 } fun algorithm(n: Int): Int { // 输入数据 val a = 0 // 暂存数据(常量) var b = 0 // 暂存数据(变量) val node = Node(0) // 暂存数据(对象) val c = function() // 栈帧空间(调用函数) return a + b + c // 输出数据 } ``` === "Ruby" ```ruby title="" ### 类 ### class Node attr_accessor :val # 节点值 attr_accessor :next # 指向下一节点的引用 def initialize(x) @val = x end end ### 函数 ### def function # 执行某些操作... 0 end ### 算法 ### def algorithm(n) # 输入数据 a = 0 # 暂存数据(常量) b = 0 # 暂存数据(变量) node = Node.new(0) # 暂存数据(对象) c = function # 栈帧空间(调用函数) a + b + c # 输出数据 end ``` ## 推算方法 空间复杂度的推算方法与时间复杂度大致相同,只需将统计对象从“操作数量”转为“使用空间大小”。 而与时间复杂度不同的是,**我们通常只关注最差空间复杂度**。这是因为内存空间是一项硬性要求,我们必须确保在所有输入数据下都有足够的内存空间预留。 观察以下代码,最差空间复杂度中的“最差”有两层含义。 1. **以最差输入数据为准**:当 $n < 10$ 时,空间复杂度为 $O(1)$ ;但当 $n > 10$ 时,初始化的数组 `nums` 占用 $O(n)$ 空间,因此最差空间复杂度为 $O(n)$ 。 2. **以算法运行中的峰值内存为准**:例如,程序在执行最后一行之前,占用 $O(1)$ 空间;当初始化数组 `nums` 时,程序占用 $O(n)$ 空间,因此最差空间复杂度为 $O(n)$ 。 === "Python" ```python title="" def algorithm(n: int): a = 0 # O(1) b = [0] * 10000 # O(1) if n > 10: nums = [0] * n # O(n) ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 0; // O(1) vector b(10000); // O(1) if (n > 10) vector nums(n); // O(n) } ``` === "Java" ```java title="" void algorithm(int n) { int a = 0; // O(1) int[] b = new int[10000]; // O(1) if (n > 10) int[] nums = new int[n]; // O(n) } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 0; // O(1) int[] b = new int[10000]; // O(1) if (n > 10) { int[] nums = new int[n]; // O(n) } } ``` === "Go" ```go title="" func algorithm(n int) { a := 0 // O(1) b := make([]int, 10000) // O(1) var nums []int if n > 10 { nums := make([]int, n) // O(n) } fmt.Println(a, b, nums) } ``` === "Swift" ```swift title="" func algorithm(n: Int) { let a = 0 // O(1) let b = Array(repeating: 0, count: 10000) // O(1) if n > 10 { let nums = Array(repeating: 0, count: n) // O(n) } } ``` === "JS" ```javascript title="" function algorithm(n) { const a = 0; // O(1) const b = new Array(10000); // O(1) if (n > 10) { const nums = new Array(n); // O(n) } } ``` === "TS" ```typescript title="" function algorithm(n: number): void { const a = 0; // O(1) const b = new Array(10000); // O(1) if (n > 10) { const nums = new Array(n); // O(n) } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 0; // O(1) List b = List.filled(10000, 0); // O(1) if (n > 10) { List nums = List.filled(n, 0); // O(n) } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let a = 0; // O(1) let b = [0; 10000]; // O(1) if n > 10 { let nums = vec![0; n as usize]; // O(n) } } ``` === "C" ```c title="" void algorithm(int n) { int a = 0; // O(1) int b[10000]; // O(1) if (n > 10) int nums[n] = {0}; // O(n) } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { val a = 0 // O(1) val b = IntArray(10000) // O(1) if (n > 10) { val nums = IntArray(n) // O(n) } } ``` === "Ruby" ```ruby title="" def algorithm(n) a = 0 # O(1) b = Array.new(10000) # O(1) nums = Array.new(n) if n > 10 # O(n) end ``` **在递归函数中,需要注意统计栈帧空间**。观察以下代码: === "Python" ```python title="" def function() -> int: # 执行某些操作 return 0 def loop(n: int): """循环的空间复杂度为 O(1)""" for _ in range(n): function() def recur(n: int): """递归的空间复杂度为 O(n)""" if n == 1: return return recur(n - 1) ``` === "C++" ```cpp title="" int func() { // 执行某些操作 return 0; } /* 循环的空间复杂度为 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } /* 递归的空间复杂度为 O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Java" ```java title="" int function() { // 执行某些操作 return 0; } /* 循环的空间复杂度为 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } /* 递归的空间复杂度为 O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "C#" ```csharp title="" int Function() { // 执行某些操作 return 0; } /* 循环的空间复杂度为 O(1) */ void Loop(int n) { for (int i = 0; i < n; i++) { Function(); } } /* 递归的空间复杂度为 O(n) */ int Recur(int n) { if (n == 1) return 1; return Recur(n - 1); } ``` === "Go" ```go title="" func function() int { // 执行某些操作 return 0 } /* 循环的空间复杂度为 O(1) */ func loop(n int) { for i := 0; i < n; i++ { function() } } /* 递归的空间复杂度为 O(n) */ func recur(n int) { if n == 1 { return } recur(n - 1) } ``` === "Swift" ```swift title="" @discardableResult func function() -> Int { // 执行某些操作 return 0 } /* 循环的空间复杂度为 O(1) */ func loop(n: Int) { for _ in 0 ..< n { function() } } /* 递归的空间复杂度为 O(n) */ func recur(n: Int) { if n == 1 { return } recur(n: n - 1) } ``` === "JS" ```javascript title="" function constFunc() { // 执行某些操作 return 0; } /* 循环的空间复杂度为 O(1) */ function loop(n) { for (let i = 0; i < n; i++) { constFunc(); } } /* 递归的空间复杂度为 O(n) */ function recur(n) { if (n === 1) return; return recur(n - 1); } ``` === "TS" ```typescript title="" function constFunc(): number { // 执行某些操作 return 0; } /* 循环的空间复杂度为 O(1) */ function loop(n: number): void { for (let i = 0; i < n; i++) { constFunc(); } } /* 递归的空间复杂度为 O(n) */ function recur(n: number): void { if (n === 1) return; return recur(n - 1); } ``` === "Dart" ```dart title="" int function() { // 执行某些操作 return 0; } /* 循环的空间复杂度为 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } /* 递归的空间复杂度为 O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Rust" ```rust title="" fn function() -> i32 { // 执行某些操作 return 0; } /* 循环的空间复杂度为 O(1) */ fn loop(n: i32) { for i in 0..n { function(); } } /* 递归的空间复杂度为 O(n) */ fn recur(n: i32) { if n == 1 { return; } recur(n - 1); } ``` === "C" ```c title="" int func() { // 执行某些操作 return 0; } /* 循环的空间复杂度为 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } /* 递归的空间复杂度为 O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Kotlin" ```kotlin title="" fun function(): Int { // 执行某些操作 return 0 } /* 循环的空间复杂度为 O(1) */ fun loop(n: Int) { for (i in 0..函数(function)可以被独立执行,所有参数都以显式传递。方法(method)与一个对象关联,被隐式传递给调用它的对象,能够对类的实例中包含的数据进行操作。 下面以几种常见的编程语言为例来说明。 - C 语言是过程式编程语言,没有面向对象的概念,所以只有函数。但我们可以通过创建结构体(struct)来模拟面向对象编程,与结构体相关联的函数就相当于其他编程语言中的方法。 - Java 和 C# 是面向对象的编程语言,代码块(方法)通常作为某个类的一部分。静态方法的行为类似于函数,因为它被绑定在类上,不能访问特定的实例变量。 - C++ 和 Python 既支持过程式编程(函数),也支持面向对象编程(方法)。 **Q**:图解“常见的空间复杂度类型”反映的是否是占用空间的绝对大小? 不是,该图展示的是空间复杂度,其反映的是增长趋势,而不是占用空间的绝对大小。 假设取 $n = 8$ ,你可能会发现每条曲线的值与函数对应不上。这是因为每条曲线都包含一个常数项,用于将取值范围压缩到一个视觉舒适的范围内。 在实际中,因为我们通常不知道每个方法的“常数项”复杂度是多少,所以一般无法仅凭复杂度来选择 $n = 8$ 之下的最优解法。但对于 $n = 8^5$ 就很好选了,这时增长趋势已经占主导了。 **Q** 是否存在根据实际使用场景,选择牺牲时间(或空间)来设计算法的情况? 在实际应用中,大部分情况会选择牺牲空间换时间。例如数据库索引,我们通常选择建立 B+ 树或哈希索引,占用大量内存空间,以换取 $O(\log n)$ 甚至 $O(1)$ 的高效查询。 在空间资源宝贵的场景,也会选择牺牲时间换空间。例如在嵌入式开发中,设备内存很宝贵,工程师可能会放弃使用哈希表,选择使用数组顺序查找,以节省内存占用,代价是查找变慢。 ================================================ FILE: docs/chapter_computational_complexity/time_complexity.md ================================================ # 时间复杂度 运行时间可以直观且准确地反映算法的效率。如果我们想准确预估一段代码的运行时间,应该如何操作呢? 1. **确定运行平台**,包括硬件配置、编程语言、系统环境等,这些因素都会影响代码的运行效率。 2. **评估各种计算操作所需的运行时间**,例如加法操作 `+` 需要 1 ns ,乘法操作 `*` 需要 10 ns ,打印操作 `print()` 需要 5 ns 等。 3. **统计代码中所有的计算操作**,并将所有操作的执行时间求和,从而得到运行时间。 例如在以下代码中,输入数据大小为 $n$ : === "Python" ```python title="" # 在某运行平台下 def algorithm(n: int): a = 2 # 1 ns a = a + 1 # 1 ns a = a * 2 # 10 ns # 循环 n 次 for _ in range(n): # 1 ns print(0) # 5 ns ``` === "C++" ```cpp title="" // 在某运行平台下 void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 循环 n 次 for (int i = 0; i < n; i++) { // 1 ns cout << 0 << endl; // 5 ns } } ``` === "Java" ```java title="" // 在某运行平台下 void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 循环 n 次 for (int i = 0; i < n; i++) { // 1 ns System.out.println(0); // 5 ns } } ``` === "C#" ```csharp title="" // 在某运行平台下 void Algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 循环 n 次 for (int i = 0; i < n; i++) { // 1 ns Console.WriteLine(0); // 5 ns } } ``` === "Go" ```go title="" // 在某运行平台下 func algorithm(n int) { a := 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns // 循环 n 次 for i := 0; i < n; i++ { // 1 ns fmt.Println(a) // 5 ns } } ``` === "Swift" ```swift title="" // 在某运行平台下 func algorithm(n: Int) { var a = 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns // 循环 n 次 for _ in 0 ..< n { // 1 ns print(0) // 5 ns } } ``` === "JS" ```javascript title="" // 在某运行平台下 function algorithm(n) { var a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 循环 n 次 for(let i = 0; i < n; i++) { // 1 ns console.log(0); // 5 ns } } ``` === "TS" ```typescript title="" // 在某运行平台下 function algorithm(n: number): void { var a: number = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 循环 n 次 for(let i = 0; i < n; i++) { // 1 ns console.log(0); // 5 ns } } ``` === "Dart" ```dart title="" // 在某运行平台下 void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 循环 n 次 for (int i = 0; i < n; i++) { // 1 ns print(0); // 5 ns } } ``` === "Rust" ```rust title="" // 在某运行平台下 fn algorithm(n: i32) { let mut a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 循环 n 次 for _ in 0..n { // 1 ns println!("{}", 0); // 5 ns } } ``` === "C" ```c title="" // 在某运行平台下 void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 循环 n 次 for (int i = 0; i < n; i++) { // 1 ns printf("%d", 0); // 5 ns } } ``` === "Kotlin" ```kotlin title="" // 在某运行平台下 fun algorithm(n: Int) { var a = 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns // 循环 n 次 for (i in 0.. 1$ 时比算法 `A` 更慢,在 $n > 1000000$ 时比算法 `C` 更慢。事实上,只要输入数据大小 $n$ 足够大,复杂度为“常数阶”的算法一定优于“线性阶”的算法,这正是时间增长趋势的含义。 - **时间复杂度的推算方法更简便**。显然,运行平台和计算操作类型都与算法运行时间的增长趋势无关。因此在时间复杂度分析中,我们可以简单地将所有计算操作的执行时间视为相同的“单位时间”,从而将“计算操作运行时间统计”简化为“计算操作数量统计”,这样一来估算难度就大大降低了。 - **时间复杂度也存在一定的局限性**。例如,尽管算法 `A` 和 `C` 的时间复杂度相同,但实际运行时间差别很大。同样,尽管算法 `B` 的时间复杂度比 `C` 高,但在输入数据大小 $n$ 较小时,算法 `B` 明显优于算法 `C` 。对于此类情况,我们时常难以仅凭时间复杂度判断算法效率的高低。当然,尽管存在上述问题,复杂度分析仍然是评判算法效率最有效且常用的方法。 ## 函数渐近上界 给定一个输入大小为 $n$ 的函数: === "Python" ```python title="" def algorithm(n: int): a = 1 # +1 a = a + 1 # +1 a = a * 2 # +1 # 循环 n 次 for i in range(n): # +1 print(0) # +1 ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 循环 n 次 for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) cout << 0 << endl; // +1 } } ``` === "Java" ```java title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 循环 n 次 for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) System.out.println(0); // +1 } } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 循环 n 次 for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) Console.WriteLine(0); // +1 } } ``` === "Go" ```go title="" func algorithm(n int) { a := 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // 循环 n 次 for i := 0; i < n; i++ { // +1 fmt.Println(a) // +1 } } ``` === "Swift" ```swift title="" func algorithm(n: Int) { var a = 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // 循环 n 次 for _ in 0 ..< n { // +1 print(0) // +1 } } ``` === "JS" ```javascript title="" function algorithm(n) { var a = 1; // +1 a += 1; // +1 a *= 2; // +1 // 循环 n 次 for(let i = 0; i < n; i++){ // +1(每轮都执行 i ++) console.log(0); // +1 } } ``` === "TS" ```typescript title="" function algorithm(n: number): void{ var a: number = 1; // +1 a += 1; // +1 a *= 2; // +1 // 循环 n 次 for(let i = 0; i < n; i++){ // +1(每轮都执行 i ++) console.log(0); // +1 } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 循环 n 次 for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) print(0); // +1 } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let mut a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 循环 n 次 for _ in 0..n { // +1(每轮都执行 i ++) println!("{}", 0); // +1 } } ``` === "C" ```c title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 循环 n 次 for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) printf("%d", 0); // +1 } } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { var a = 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // 循环 n 次 for (i in 0..大 $O$ 记号(big-$O$ notation),表示函数 $T(n)$ 的渐近上界(asymptotic upper bound)。 时间复杂度分析本质上是计算“操作数量 $T(n)$”的渐近上界,它具有明确的数学定义。 !!! note "函数渐近上界" 若存在正实数 $c$ 和实数 $n_0$ ,使得对于所有的 $n > n_0$ ,均有 $T(n) \leq c \cdot f(n)$ ,则可认为 $f(n)$ 给出了 $T(n)$ 的一个渐近上界,记为 $T(n) = O(f(n))$ 。 如下图所示,计算渐近上界就是寻找一个函数 $f(n)$ ,使得当 $n$ 趋向于无穷大时,$T(n)$ 和 $f(n)$ 处于相同的增长级别,仅相差一个常数系数 $c$。 ![函数的渐近上界](time_complexity.assets/asymptotic_upper_bound.png) ## 推算方法 渐近上界的数学味儿有点重,如果你感觉没有完全理解,也无须担心。我们可以先掌握推算方法,在不断的实践中,就可以逐渐领悟其数学意义。 根据定义,确定 $f(n)$ 之后,我们便可得到时间复杂度 $O(f(n))$ 。那么如何确定渐近上界 $f(n)$ 呢?总体分为两步:首先统计操作数量,然后判断渐近上界。 ### 第一步:统计操作数量 针对代码,逐行从上到下计算即可。然而,由于上述 $c \cdot f(n)$ 中的常数系数 $c$ 可以取任意大小,**因此操作数量 $T(n)$ 中的各种系数、常数项都可以忽略**。根据此原则,可以总结出以下计数简化技巧。 1. **忽略 $T(n)$ 中的常数**。因为它们都与 $n$ 无关,所以对时间复杂度不产生影响。 2. **省略所有系数**。例如,循环 $2n$ 次、$5n + 1$ 次等,都可以简化记为 $n$ 次,因为 $n$ 前面的系数对时间复杂度没有影响。 3. **循环嵌套时使用乘法**。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用第 `1.` 点和第 `2.` 点的技巧。 给定一个函数,我们可以用上述技巧来统计操作数量: === "Python" ```python title="" def algorithm(n: int): a = 1 # +0(技巧 1) a = a + n # +0(技巧 1) # +n(技巧 2) for i in range(5 * n + 1): print(0) # +n*n(技巧 3) for i in range(2 * n): for j in range(n + 1): print(0) ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { cout << 0 << endl; } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { cout << 0 << endl; } } } ``` === "Java" ```java title="" void algorithm(int n) { int a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { System.out.println(0); } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { System.out.println(0); } } } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { Console.WriteLine(0); } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { Console.WriteLine(0); } } } ``` === "Go" ```go title="" func algorithm(n int) { a := 1 // +0(技巧 1) a = a + n // +0(技巧 1) // +n(技巧 2) for i := 0; i < 5 * n + 1; i++ { fmt.Println(0) } // +n*n(技巧 3) for i := 0; i < 2 * n; i++ { for j := 0; j < n + 1; j++ { fmt.Println(0) } } } ``` === "Swift" ```swift title="" func algorithm(n: Int) { var a = 1 // +0(技巧 1) a = a + n // +0(技巧 1) // +n(技巧 2) for _ in 0 ..< (5 * n + 1) { print(0) } // +n*n(技巧 3) for _ in 0 ..< (2 * n) { for _ in 0 ..< (n + 1) { print(0) } } } ``` === "JS" ```javascript title="" function algorithm(n) { let a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (let i = 0; i < 5 * n + 1; i++) { console.log(0); } // +n*n(技巧 3) for (let i = 0; i < 2 * n; i++) { for (let j = 0; j < n + 1; j++) { console.log(0); } } } ``` === "TS" ```typescript title="" function algorithm(n: number): void { let a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (let i = 0; i < 5 * n + 1; i++) { console.log(0); } // +n*n(技巧 3) for (let i = 0; i < 2 * n; i++) { for (let j = 0; j < n + 1; j++) { console.log(0); } } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { print(0); } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { print(0); } } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let mut a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for i in 0..(5 * n + 1) { println!("{}", 0); } // +n*n(技巧 3) for i in 0..(2 * n) { for j in 0..(n + 1) { println!("{}", 0); } } } ``` === "C" ```c title="" void algorithm(int n) { int a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { printf("%d", 0); } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { printf("%d", 0); } } } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { var a = 1 // +0(技巧 1) a = a + n // +0(技巧 1) // +n(技巧 2) for (i in 0..<5 * n + 1) { println(0) } // +n*n(技巧 3) for (i in 0..<2 * n) { for (j in 0..   不同操作数量对应的时间复杂度

| 操作数量 $T(n)$ | 时间复杂度 $O(f(n))$ | | ---------------------- | -------------------- | | $100000$ | $O(1)$ | | $3n + 2$ | $O(n)$ | | $2n^2 + 3n + 2$ | $O(n^2)$ | | $n^3 + 10000n^2$ | $O(n^3)$ | | $2^n + 10000n^{10000}$ | $O(2^n)$ | ## 常见类型 设输入数据大小为 $n$ ,常见的时间复杂度类型如下图所示(按照从低到高的顺序排列)。 $$ \begin{aligned} O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline \text{常数阶} < \text{对数阶} < \text{线性阶} < \text{线性对数阶} < \text{平方阶} < \text{指数阶} < \text{阶乘阶} \end{aligned} $$ ![常见的时间复杂度类型](time_complexity.assets/time_complexity_common_types.png) ### 常数阶 $O(1)$ 常数阶的操作数量与输入数据大小 $n$ 无关,即不随着 $n$ 的变化而变化。 在以下函数中,尽管操作数量 `size` 可能很大,但由于其与输入数据大小 $n$ 无关,因此时间复杂度仍为 $O(1)$ : ```src [file]{time_complexity}-[class]{}-[func]{constant} ``` ### 线性阶 $O(n)$ 线性阶的操作数量相对于输入数据大小 $n$ 以线性级别增长。线性阶通常出现在单层循环中: ```src [file]{time_complexity}-[class]{}-[func]{linear} ``` 遍历数组和遍历链表等操作的时间复杂度均为 $O(n)$ ,其中 $n$ 为数组或链表的长度: ```src [file]{time_complexity}-[class]{}-[func]{array_traversal} ``` 值得注意的是,**输入数据大小 $n$ 需根据输入数据的类型来具体确定**。比如在第一个示例中,变量 $n$ 为输入数据大小;在第二个示例中,数组长度 $n$ 为数据大小。 ### 平方阶 $O(n^2)$ 平方阶的操作数量相对于输入数据大小 $n$ 以平方级别增长。平方阶通常出现在嵌套循环中,外层循环和内层循环的时间复杂度都为 $O(n)$ ,因此总体的时间复杂度为 $O(n^2)$ : ```src [file]{time_complexity}-[class]{}-[func]{quadratic} ``` 下图对比了常数阶、线性阶和平方阶三种时间复杂度。 ![常数阶、线性阶和平方阶的时间复杂度](time_complexity.assets/time_complexity_constant_linear_quadratic.png) 以冒泡排序为例,外层循环执行 $n - 1$ 次,内层循环执行 $n-1$、$n-2$、$\dots$、$2$、$1$ 次,平均为 $n / 2$ 次,因此时间复杂度为 $O((n - 1) n / 2) = O(n^2)$ : ```src [file]{time_complexity}-[class]{}-[func]{bubble_sort} ``` ### 指数阶 $O(2^n)$ 生物学的“细胞分裂”是指数阶增长的典型例子:初始状态为 $1$ 个细胞,分裂一轮后变为 $2$ 个,分裂两轮后变为 $4$ 个,以此类推,分裂 $n$ 轮后有 $2^n$ 个细胞。 下图和以下代码模拟了细胞分裂的过程,时间复杂度为 $O(2^n)$ 。请注意,输入 $n$ 表示分裂轮数,返回值 `count` 表示总分裂次数。 ```src [file]{time_complexity}-[class]{}-[func]{exponential} ``` ![指数阶的时间复杂度](time_complexity.assets/time_complexity_exponential.png) 在实际算法中,指数阶常出现于递归函数中。例如在以下代码中,其递归地一分为二,经过 $n$ 次分裂后停止: ```src [file]{time_complexity}-[class]{}-[func]{exp_recur} ``` 指数阶增长非常迅速,在穷举法(暴力搜索、回溯等)中比较常见。对于数据规模较大的问题,指数阶是不可接受的,通常需要使用动态规划或贪心算法等来解决。 ### 对数阶 $O(\log n)$ 与指数阶相反,对数阶反映了“每轮缩减到一半”的情况。设输入数据大小为 $n$ ,由于每轮缩减到一半,因此循环次数是 $\log_2 n$ ,即 $2^n$ 的反函数。 下图和以下代码模拟了“每轮缩减到一半”的过程,时间复杂度为 $O(\log_2 n)$ ,简记为 $O(\log n)$ : ```src [file]{time_complexity}-[class]{}-[func]{logarithmic} ``` ![对数阶的时间复杂度](time_complexity.assets/time_complexity_logarithmic.png) 与指数阶类似,对数阶也常出现于递归函数中。以下代码形成了一棵高度为 $\log_2 n$ 的递归树: ```src [file]{time_complexity}-[class]{}-[func]{log_recur} ``` 对数阶常出现于基于分治策略的算法中,体现了“一分为多”和“化繁为简”的算法思想。它增长缓慢,是仅次于常数阶的理想的时间复杂度。 !!! tip "$O(\log n)$ 的底数是多少?" 准确来说,“一分为 $m$”对应的时间复杂度是 $O(\log_m n)$ 。而通过对数换底公式,我们可以得到具有不同底数、相等的时间复杂度: $$ O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n) $$ 也就是说,底数 $m$ 可以在不影响复杂度的前提下转换。因此我们通常会省略底数 $m$ ,将对数阶直接记为 $O(\log n)$ 。 ### 线性对数阶 $O(n \log n)$ 线性对数阶常出现于嵌套循环中,两层循环的时间复杂度分别为 $O(\log n)$ 和 $O(n)$ 。相关代码如下: ```src [file]{time_complexity}-[class]{}-[func]{linear_log_recur} ``` 下图展示了线性对数阶的生成方式。二叉树的每一层的操作总数都为 $n$ ,树共有 $\log_2 n + 1$ 层,因此时间复杂度为 $O(n \log n)$ 。 ![线性对数阶的时间复杂度](time_complexity.assets/time_complexity_logarithmic_linear.png) 主流排序算法的时间复杂度通常为 $O(n \log n)$ ,例如快速排序、归并排序、堆排序等。 ### 阶乘阶 $O(n!)$ 阶乘阶对应数学上的“全排列”问题。给定 $n$ 个互不重复的元素,求其所有可能的排列方案,方案数量为: $$ n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1 $$ 阶乘通常使用递归实现。如下图和以下代码所示,第一层分裂出 $n$ 个,第二层分裂出 $n - 1$ 个,以此类推,直至第 $n$ 层时停止分裂: ```src [file]{time_complexity}-[class]{}-[func]{factorial_recur} ``` ![阶乘阶的时间复杂度](time_complexity.assets/time_complexity_factorial.png) 请注意,因为当 $n \geq 4$ 时恒有 $n! > 2^n$ ,所以阶乘阶比指数阶增长得更快,在 $n$ 较大时也是不可接受的。 ## 最差、最佳、平均时间复杂度 **算法的时间效率往往不是固定的,而是与输入数据的分布有关**。假设输入一个长度为 $n$ 的数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,每个数字只出现一次;但元素顺序是随机打乱的,任务目标是返回元素 $1$ 的索引。我们可以得出以下结论。 - 当 `nums = [?, ?, ..., 1]` ,即当末尾元素是 $1$ 时,需要完整遍历数组,**达到最差时间复杂度 $O(n)$** 。 - 当 `nums = [1, ?, ?, ...]` ,即当首个元素为 $1$ 时,无论数组多长都不需要继续遍历,**达到最佳时间复杂度 $\Omega(1)$** 。 “最差时间复杂度”对应函数渐近上界,使用大 $O$ 记号表示。相应地,“最佳时间复杂度”对应函数渐近下界,用 $\Omega$ 记号表示: ```src [file]{worst_best_time_complexity}-[class]{}-[func]{find_one} ``` 值得说明的是,我们在实际中很少使用最佳时间复杂度,因为通常只有在很小概率下才能达到,可能会带来一定的误导性。**而最差时间复杂度更为实用,因为它给出了一个效率安全值**,让我们可以放心地使用算法。 从上述示例可以看出,最差时间复杂度和最佳时间复杂度只出现于“特殊的数据分布”,这些情况的出现概率可能很小,并不能真实地反映算法运行效率。相比之下,**平均时间复杂度可以体现算法在随机输入数据下的运行效率**,用 $\Theta$ 记号来表示。 对于部分算法,我们可以简单地推算出随机数据分布下的平均情况。比如上述示例,由于输入数组是被打乱的,因此元素 $1$ 出现在任意索引的概率都是相等的,那么算法的平均循环次数就是数组长度的一半 $n / 2$ ,平均时间复杂度为 $\Theta(n / 2) = \Theta(n)$ 。 但对于较为复杂的算法,计算平均时间复杂度往往比较困难,因为很难分析出在数据分布下的整体数学期望。在这种情况下,我们通常使用最差时间复杂度作为算法效率的评判标准。 !!! question "为什么很少看到 $\Theta$ 符号?" 可能由于 $O$ 符号过于朗朗上口,因此我们常常使用它来表示平均时间复杂度。但从严格意义上讲,这种做法并不规范。在本书和其他资料中,若遇到类似“平均时间复杂度 $O(n)$”的表述,请将其直接理解为 $\Theta(n)$ 。 ================================================ FILE: docs/chapter_data_structure/basic_data_types.md ================================================ # 基本数据类型 当谈及计算机中的数据时,我们会想到文本、图片、视频、语音、3D 模型等各种形式。尽管这些数据的组织形式各异,但它们都由各种基本数据类型构成。 **基本数据类型是 CPU 可以直接进行运算的类型**,在算法中直接被使用,主要包括以下几种。 - 整数类型 `byte`、`short`、`int`、`long` 。 - 浮点数类型 `float`、`double` ,用于表示小数。 - 字符类型 `char` ,用于表示各种语言的字母、标点符号甚至表情符号等。 - 布尔类型 `bool` ,用于表示“是”与“否”判断。 **基本数据类型以二进制的形式存储在计算机中**。一个二进制位即为 $1$ 比特。在绝大多数现代操作系统中,$1$ 字节(byte)由 $8$ 比特(bit)组成。 基本数据类型的取值范围取决于其占用的空间大小。下面以 Java 为例。 - 整数类型 `byte` 占用 $1$ 字节 = $8$ 比特 ,可以表示 $2^{8}$ 个数字。 - 整数类型 `int` 占用 $4$ 字节 = $32$ 比特 ,可以表示 $2^{32}$ 个数字。 下表列举了 Java 中各种基本数据类型的占用空间、取值范围和默认值。此表格无须死记硬背,大致理解即可,需要时可以通过查表来回忆。

  基本数据类型的占用空间和取值范围

| 类型 | 符号 | 占用空间 | 最小值 | 最大值 | 默认值 | | ------ | -------- | -------- | ------------------------ | ----------------------- | -------------- | | 整数 | `byte` | 1 字节 | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | $0$ | | | `short` | 2 字节 | $-2^{15}$ | $2^{15} - 1$ | $0$ | | | `int` | 4 字节 | $-2^{31}$ | $2^{31} - 1$ | $0$ | | | `long` | 8 字节 | $-2^{63}$ | $2^{63} - 1$ | $0$ | | 浮点数 | `float` | 4 字节 | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ | | | `double` | 8 字节 | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | $0.0$ | | 字符 | `char` | 2 字节 | $0$ | $2^{16} - 1$ | $0$ | | 布尔 | `bool` | 1 字节 | $\text{false}$ | $\text{true}$ | $\text{false}$ | 请注意,上表针对的是 Java 的基本数据类型的情况。每种编程语言都有各自的数据类型定义,它们的占用空间、取值范围和默认值可能会有所不同。 - 在 Python 中,整数类型 `int` 可以是任意大小,只受限于可用内存;浮点数 `float` 是双精度 64 位;没有 `char` 类型,单个字符实际上是长度为 1 的字符串 `str` 。 - C 和 C++ 未明确规定基本数据类型的大小,而因实现和平台各异。上表遵循 LP64 [数据模型](https://en.cppreference.com/w/cpp/language/types#Properties),其用于包括 Linux 和 macOS 在内的 Unix 64 位操作系统。 - 字符 `char` 的大小在 C 和 C++ 中为 1 字节,在大多数编程语言中取决于特定的字符编码方法,详见“字符编码”章节。 - 即使表示布尔量仅需 1 位($0$ 或 $1$),它在内存中通常也存储为 1 字节。这是因为现代计算机 CPU 通常将 1 字节作为最小寻址内存单元。 那么,基本数据类型与数据结构之间有什么联系呢?我们知道,数据结构是在计算机中组织与存储数据的方式。这句话的主语是“结构”而非“数据”。 如果想表示“一排数字”,我们自然会想到使用数组。这是因为数组的线性结构可以表示数字的相邻关系和顺序关系,但至于存储的内容是整数 `int`、小数 `float` 还是字符 `char` ,则与“数据结构”无关。 换句话说,**基本数据类型提供了数据的“内容类型”,而数据结构提供了数据的“组织方式”**。例如以下代码,我们用相同的数据结构(数组)来存储与表示不同的基本数据类型,包括 `int`、`float`、`char`、`bool` 等。 === "Python" ```python title="" # 使用多种基本数据类型来初始化数组 numbers: list[int] = [0] * 5 decimals: list[float] = [0.0] * 5 # Python 的字符实际上是长度为 1 的字符串 characters: list[str] = ['0'] * 5 bools: list[bool] = [False] * 5 # Python 的列表可以自由存储各种基本数据类型和对象引用 data = [0, 0.0, 'a', False, ListNode(0)] ``` === "C++" ```cpp title="" // 使用多种基本数据类型来初始化数组 int numbers[5]; float decimals[5]; char characters[5]; bool bools[5]; ``` === "Java" ```java title="" // 使用多种基本数据类型来初始化数组 int[] numbers = new int[5]; float[] decimals = new float[5]; char[] characters = new char[5]; boolean[] bools = new boolean[5]; ``` === "C#" ```csharp title="" // 使用多种基本数据类型来初始化数组 int[] numbers = new int[5]; float[] decimals = new float[5]; char[] characters = new char[5]; bool[] bools = new bool[5]; ``` === "Go" ```go title="" // 使用多种基本数据类型来初始化数组 var numbers = [5]int{} var decimals = [5]float64{} var characters = [5]byte{} var bools = [5]bool{} ``` === "Swift" ```swift title="" // 使用多种基本数据类型来初始化数组 let numbers = Array(repeating: 0, count: 5) let decimals = Array(repeating: 0.0, count: 5) let characters: [Character] = Array(repeating: "a", count: 5) let bools = Array(repeating: false, count: 5) ``` === "JS" ```javascript title="" // JavaScript 的数组可以自由存储各种基本数据类型和对象 const array = [0, 0.0, 'a', false]; ``` === "TS" ```typescript title="" // 使用多种基本数据类型来初始化数组 const numbers: number[] = []; const characters: string[] = []; const bools: boolean[] = []; ``` === "Dart" ```dart title="" // 使用多种基本数据类型来初始化数组 List numbers = List.filled(5, 0); List decimals = List.filled(5, 0.0); List characters = List.filled(5, 'a'); List bools = List.filled(5, false); ``` === "Rust" ```rust title="" // 使用多种基本数据类型来初始化数组 let numbers: Vec = vec![0; 5]; let decimals: Vec = vec![0.0; 5]; let characters: Vec = vec!['0'; 5]; let bools: Vec = vec![false; 5]; ``` === "C" ```c title="" // 使用多种基本数据类型来初始化数组 int numbers[10]; float decimals[10]; char characters[10]; bool bools[10]; ``` === "Kotlin" ```kotlin title="" // 使用多种基本数据类型来初始化数组 val numbers = IntArray(5) val decinals = FloatArray(5) val characters = CharArray(5) val bools = BooleanArray(5) ``` === "Ruby" ```ruby title="" # Ruby 的列表可以自由存储各种基本数据类型和对象引用 data = [0, 0.0, 'a', false, ListNode(0)] ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%9D%A5%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20*%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%AD%97%E7%AC%A6%E5%AE%9E%E9%99%85%E4%B8%8A%E6%98%AF%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%0A%20%20%20%20characters%20%3D%20%5B'0'%5D%20*%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%88%97%E8%A1%A8%E5%8F%AF%E4%BB%A5%E8%87%AA%E7%94%B1%E5%AD%98%E5%82%A8%E5%90%84%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%0A%20%20%20%20data%20%3D%20%5B0,%200.0,%20'a',%20False,%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: docs/chapter_data_structure/character_encoding.md ================================================ # 字符编码 * 在计算机中,所有数据都是以二进制数的形式存储的,字符 `char` 也不例外。为了表示字符,我们需要建立一套“字符集”,规定每个字符和二进制数之间的一一对应关系。有了字符集之后,计算机就可以通过查表完成二进制数到字符的转换。 ## ASCII 字符集 ASCII 码是最早出现的字符集,其全称为 American Standard Code for Information Interchange(美国标准信息交换代码)。它使用 7 位二进制数(一个字节的低 7 位)表示一个字符,最多能够表示 128 个不同的字符。如下图所示,ASCII 码包括英文字母的大小写、数字 0 ~ 9、一些标点符号,以及一些控制字符(如换行符和制表符)。 ![ASCII 码](character_encoding.assets/ascii_table.png) 然而,**ASCII 码仅能够表示英文**。随着计算机的全球化,诞生了一种能够表示更多语言的 EASCII 字符集。它在 ASCII 的 7 位基础上扩展到 8 位,能够表示 256 个不同的字符。 在世界范围内,陆续出现了一批适用于不同地区的 EASCII 字符集。这些字符集的前 128 个字符统一为 ASCII 码,后 128 个字符定义不同,以适应不同语言的需求。 ## GBK 字符集 后来人们发现,**EASCII 码仍然无法满足许多语言的字符数量要求**。比如汉字有近十万个,光日常使用的就有几千个。中国国家标准总局于 1980 年发布了 GB2312 字符集,其收录了 6763 个汉字,基本满足了汉字的计算机处理需要。 然而,GB2312 无法处理部分罕见字和繁体字。GBK 字符集是在 GB2312 的基础上扩展得到的,它共收录了 21886 个汉字。在 GBK 的编码方案中,ASCII 字符使用一个字节表示,汉字使用两个字节表示。 ## Unicode 字符集 随着计算机技术的蓬勃发展,字符集与编码标准百花齐放,而这带来了许多问题。一方面,这些字符集一般只定义了特定语言的字符,无法在多语言环境下正常工作。另一方面,同一种语言存在多种字符集标准,如果两台计算机使用的是不同的编码标准,则在信息传递时就会出现乱码。 那个时代的研究人员就在想:**如果推出一个足够完整的字符集,将世界范围内的所有语言和符号都收录其中,不就可以解决跨语言环境和乱码问题了吗**?在这种想法的驱动下,一个大而全的字符集 Unicode 应运而生。 Unicode 的中文名称为“统一码”,理论上能容纳 100 多万个字符。它致力于将全球范围内的字符纳入统一的字符集之中,提供一种通用的字符集来处理和显示各种语言文字,减少因为编码标准不同而产生的乱码问题。 自 1991 年发布以来,Unicode 不断扩充新的语言与字符。截至 2022 年 9 月,Unicode 已经包含 149186 个字符,包括各种语言的字符、符号甚至表情符号等。在庞大的 Unicode 字符集中,常用的字符占用 2 字节,有些生僻的字符占用 3 字节甚至 4 字节。 Unicode 是一种通用字符集,本质上是给每个字符分配一个编号(称为“码点”),**但它并没有规定在计算机中如何存储这些字符码点**。我们不禁会问:当多种长度的 Unicode 码点同时出现在一个文本中时,系统如何解析字符?例如给定一个长度为 2 字节的编码,系统如何确认它是一个 2 字节的字符还是两个 1 字节的字符? 对于以上问题,**一种直接的解决方案是将所有字符存储为等长的编码**。如下图所示,“Hello”中的每个字符占用 1 字节,“算法”中的每个字符占用 2 字节。我们可以通过高位填 0 将“Hello 算法”中的所有字符都编码为 2 字节长度。这样系统就可以每隔 2 字节解析一个字符,恢复这个短语的内容了。 ![Unicode 编码示例](character_encoding.assets/unicode_hello_algo.png) 然而 ASCII 码已经向我们证明,编码英文只需 1 字节。若采用上述方案,英文文本占用空间的大小将会是 ASCII 编码下的两倍,非常浪费内存空间。因此,我们需要一种更加高效的 Unicode 编码方法。 ## UTF-8 编码 目前,UTF-8 已成为国际上使用最广泛的 Unicode 编码方法。**它是一种可变长度的编码**,使用 1 到 4 字节来表示一个字符,根据字符的复杂性而变。ASCII 字符只需 1 字节,拉丁字母和希腊字母需要 2 字节,常用的中文字符需要 3 字节,其他的一些生僻字符需要 4 字节。 UTF-8 的编码规则并不复杂,分为以下两种情况。 - 对于长度为 1 字节的字符,将最高位设置为 $0$ ,其余 7 位设置为 Unicode 码点。值得注意的是,ASCII 字符在 Unicode 字符集中占据了前 128 个码点。也就是说,**UTF-8 编码可以向下兼容 ASCII 码**。这意味着我们可以使用 UTF-8 来解析年代久远的 ASCII 码文本。 - 对于长度为 $n$ 字节的字符(其中 $n > 1$),将首个字节的高 $n$ 位都设置为 $1$ ,第 $n + 1$ 位设置为 $0$ ;从第二个字节开始,将每个字节的高 2 位都设置为 $10$ ;其余所有位用于填充字符的 Unicode 码点。 下图展示了“Hello算法”对应的 UTF-8 编码。观察发现,由于最高 $n$ 位都设置为 $1$ ,因此系统可以通过读取最高位 $1$ 的个数来解析出字符的长度为 $n$ 。 但为什么要将其余所有字节的高 2 位都设置为 $10$ 呢?实际上,这个 $10$ 能够起到校验符的作用。假设系统从一个错误的字节开始解析文本,字节头部的 $10$ 能够帮助系统快速判断出异常。 之所以将 $10$ 当作校验符,是因为在 UTF-8 编码规则下,不可能有字符的最高两位是 $10$ 。这个结论可以用反证法来证明:假设一个字符的最高两位是 $10$ ,说明该字符的长度为 $1$ ,对应 ASCII 码。而 ASCII 码的最高位应该是 $0$ ,与假设矛盾。 ![UTF-8 编码示例](character_encoding.assets/utf-8_hello_algo.png) 除了 UTF-8 之外,常见的编码方式还包括以下两种。 - **UTF-16 编码**:使用 2 或 4 字节来表示一个字符。所有的 ASCII 字符和常用的非英文字符,都用 2 字节表示;少数字符需要用到 4 字节表示。对于 2 字节的字符,UTF-16 编码与 Unicode 码点相等。 - **UTF-32 编码**:每个字符都使用 4 字节。这意味着 UTF-32 比 UTF-8 和 UTF-16 更占用空间,特别是对于 ASCII 字符占比较高的文本。 从存储空间占用的角度看,使用 UTF-8 表示英文字符非常高效,因为它仅需 1 字节;使用 UTF-16 编码某些非英文字符(例如中文)会更加高效,因为它仅需 2 字节,而 UTF-8 可能需要 3 字节。 从兼容性的角度看,UTF-8 的通用性最佳,许多工具和库优先支持 UTF-8 。 ## 编程语言的字符编码 对于以往的大多数编程语言,程序运行中的字符串都采用 UTF-16 或 UTF-32 这类等长编码。在等长编码下,我们可以将字符串看作数组来处理,这种做法具有以下优点。 - **随机访问**:UTF-16 编码的字符串可以很容易地进行随机访问。UTF-8 是一种变长编码,要想找到第 $i$ 个字符,我们需要从字符串的开始处遍历到第 $i$ 个字符,这需要 $O(n)$ 的时间。 - **字符计数**:与随机访问类似,计算 UTF-16 编码的字符串的长度也是 $O(1)$ 的操作。但是,计算 UTF-8 编码的字符串的长度需要遍历整个字符串。 - **字符串操作**:在 UTF-16 编码的字符串上,很多字符串操作(如分割、连接、插入、删除等)更容易进行。在 UTF-8 编码的字符串上,进行这些操作通常需要额外的计算,以确保不会产生无效的 UTF-8 编码。 实际上,编程语言的字符编码方案设计是一个很有趣的话题,涉及许多因素。 - Java 的 `String` 类型使用 UTF-16 编码,每个字符占用 2 字节。这是因为 Java 语言设计之初,人们认为 16 位足以表示所有可能的字符。然而,这是一个不正确的判断。后来 Unicode 规范扩展到了超过 16 位,所以 Java 中的字符现在可能由一对 16 位的值(称为“代理对”)表示。 - JavaScript 和 TypeScript 的字符串使用 UTF-16 编码的原因与 Java 类似。当 1995 年 Netscape 公司首次推出 JavaScript 语言时,Unicode 还处于发展早期,那时候使用 16 位的编码就足以表示所有的 Unicode 字符了。 - C# 使用 UTF-16 编码,主要是因为 .NET 平台是由 Microsoft 设计的,而 Microsoft 的很多技术(包括 Windows 操作系统)都广泛使用 UTF-16 编码。 由于以上编程语言对字符数量的低估,它们不得不采取“代理对”的方式来表示超过 16 位长度的 Unicode 字符。这是一个不得已为之的无奈之举。一方面,包含代理对的字符串中,一个字符可能占用 2 字节或 4 字节,从而丧失了等长编码的优势。另一方面,处理代理对需要额外增加代码,这提高了编程的复杂性和调试难度。 出于以上原因,部分编程语言提出了一些不同的编码方案。 - Python 中的 `str` 使用 Unicode 编码,并采用一种灵活的字符串表示,存储的字符长度取决于字符串中最大的 Unicode 码点。若字符串中全部是 ASCII 字符,则每个字符占用 1 字节;如果有字符超出了 ASCII 范围,但全部在基本多语言平面(BMP)内,则每个字符占用 2 字节;如果有超出 BMP 的字符,则每个字符占用 4 字节。 - Go 语言的 `string` 类型在内部使用 UTF-8 编码。Go 语言还提供了 `rune` 类型,它用于表示单个 Unicode 码点。 - Rust 语言的 `str` 和 `String` 类型在内部使用 UTF-8 编码。Rust 也提供了 `char` 类型,用于表示单个 Unicode 码点。 需要注意的是,以上讨论的都是字符串在编程语言中的存储方式,**这和字符串如何在文件中存储或在网络中传输是不同的问题**。在文件存储或网络传输中,我们通常会将字符串编码为 UTF-8 格式,以达到最优的兼容性和空间效率。 ================================================ FILE: docs/chapter_data_structure/classification_of_data_structure.md ================================================ # 数据结构分类 常见的数据结构包括数组、链表、栈、队列、哈希表、树、堆、图,它们可以从“逻辑结构”和“物理结构”两个维度进行分类。 ## 逻辑结构:线性与非线性 **逻辑结构揭示了数据元素之间的逻辑关系**。在数组和链表中,数据按照一定顺序排列,体现了数据之间的线性关系;而在树中,数据从顶部向下按层次排列,表现出“祖先”与“后代”之间的派生关系;图则由节点和边构成,反映了复杂的网络关系。 如下图所示,逻辑结构可分为“线性”和“非线性”两大类。线性结构比较直观,指数据在逻辑关系上呈线性排列;非线性结构则相反,呈非线性排列。 - **线性数据结构**:数组、链表、栈、队列、哈希表,元素之间是一对一的顺序关系。 - **非线性数据结构**:树、堆、图、哈希表。 非线性数据结构可以进一步划分为树形结构和网状结构。 - **树形结构**:树、堆、哈希表,元素之间是一对多的关系。 - **网状结构**:图,元素之间是多对多的关系。 ![线性数据结构与非线性数据结构](classification_of_data_structure.assets/classification_logic_structure.png) ## 物理结构:连续与分散 **当算法程序运行时,正在处理的数据主要存储在内存中**。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储一定大小的数据。 **系统通过内存地址来访问目标位置的数据**。如下图所示,计算机根据特定规则为表格中的每个单元格分配编号,确保每个内存空间都有唯一的内存地址。有了这些地址,程序便可以访问内存中的数据。 ![内存条、内存空间、内存地址](classification_of_data_structure.assets/computer_memory_location.png) !!! tip 值得说明的是,将内存比作 Excel 表格是一个简化的类比,实际内存的工作机制比较复杂,涉及地址空间、内存管理、缓存机制、虚拟内存和物理内存等概念。 内存是所有程序的共享资源,当某块内存被某个程序占用时,则通常无法被其他程序同时使用了。**因此在数据结构与算法的设计中,内存资源是一个重要的考虑因素**。比如,算法所占用的内存峰值不应超过系统剩余空闲内存;如果缺少连续大块的内存空间,那么所选用的数据结构必须能够存储在分散的内存空间内。 如下图所示,**物理结构反映了数据在计算机内存中的存储方式**,可分为连续空间存储(数组)和分散空间存储(链表)。物理结构从底层决定了数据的访问、更新、增删等操作方法,两种物理结构在时间效率和空间效率方面呈现出互补的特点。 ![连续空间存储与分散空间存储](classification_of_data_structure.assets/classification_phisical_structure.png) 值得说明的是,**所有数据结构都是基于数组、链表或二者的组合实现的**。例如,栈和队列既可以使用数组实现,也可以使用链表实现;而哈希表的实现可能同时包含数组和链表。 - **基于数组可实现**:栈、队列、哈希表、树、堆、图、矩阵、张量(维度 $\geq 3$ 的数组)等。 - **基于链表可实现**:栈、队列、哈希表、树、堆、图等。 链表在初始化后,仍可以在程序运行过程中对其长度进行调整,因此也称“动态数据结构”。数组在初始化后长度不可变,因此也称“静态数据结构”。值得注意的是,数组可通过重新分配内存实现长度变化,从而具备一定的“动态性”。 !!! tip 如果你感觉物理结构理解起来有困难,建议先阅读下一章,然后再回顾本节内容。 ================================================ FILE: docs/chapter_data_structure/index.md ================================================ # 数据结构 ![数据结构](../assets/covers/chapter_data_structure.jpg) !!! abstract 数据结构如同一副稳固而多样的框架。 它为数据的有序组织提供了蓝图,算法得以在此基础上生动起来。 ================================================ FILE: docs/chapter_data_structure/number_encoding.md ================================================ # 数字编码 * !!! tip 在本书中,标题带有 * 符号的是选读章节。如果你时间有限或感到理解困难,可以先跳过,等学完必读章节后再单独攻克。 ## 原码、反码和补码 在上一节的表格中我们发现,所有整数类型能够表示的负数都比正数多一个,例如 `byte` 的取值范围是 $[-128, 127]$ 。这个现象比较反直觉,它的内在原因涉及原码、反码、补码的相关知识。 首先需要指出,**数字是以“补码”的形式存储在计算机中的**。在分析这样做的原因之前,首先给出三者的定义。 - **原码**:我们将数字的二进制表示的最高位视为符号位,其中 $0$ 表示正数,$1$ 表示负数,其余位表示数字的值。 - **反码**:正数的反码与其原码相同,负数的反码是对其原码除符号位外的所有位取反。 - **补码**:正数的补码与其原码相同,负数的补码是在其反码的基础上加 $1$ 。 下图展示了原码、反码和补码之间的转换方法。 ![原码、反码与补码之间的相互转换](number_encoding.assets/1s_2s_complement.png) 原码(sign-magnitude)虽然最直观,但存在一些局限性。一方面,**负数的原码不能直接用于运算**。例如在原码下计算 $1 + (-2)$ ,得到的结果是 $-3$ ,这显然是不对的。 $$ \begin{aligned} & 1 + (-2) \newline & \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline & = 1000 \; 0011 \newline & \rightarrow -3 \end{aligned} $$ 为了解决此问题,计算机引入了反码(1's complement)。如果我们先将原码转换为反码,并在反码下计算 $1 + (-2)$ ,最后将结果从反码转换回原码,则可得到正确结果 $-1$ 。 $$ \begin{aligned} & 1 + (-2) \newline & \rightarrow 0000 \; 0001 \; \text{(原码)} + 1000 \; 0010 \; \text{(原码)} \newline & = 0000 \; 0001 \; \text{(反码)} + 1111 \; 1101 \; \text{(反码)} \newline & = 1111 \; 1110 \; \text{(反码)} \newline & = 1000 \; 0001 \; \text{(原码)} \newline & \rightarrow -1 \end{aligned} $$ 另一方面,**数字零的原码有 $+0$ 和 $-0$ 两种表示方式**。这意味着数字零对应两个不同的二进制编码,这可能会带来歧义。比如在条件判断中,如果没有区分正零和负零,则可能会导致判断结果出错。而如果我们想处理正零和负零歧义,则需要引入额外的判断操作,这可能会降低计算机的运算效率。 $$ \begin{aligned} +0 & \rightarrow 0000 \; 0000 \newline -0 & \rightarrow 1000 \; 0000 \end{aligned} $$ 与原码一样,反码也存在正负零歧义问题,因此计算机进一步引入了补码(2's complement)。我们先来观察一下负零的原码、反码、补码的转换过程: $$ \begin{aligned} -0 \rightarrow \; & 1000 \; 0000 \; \text{(原码)} \newline = \; & 1111 \; 1111 \; \text{(反码)} \newline = 1 \; & 0000 \; 0000 \; \text{(补码)} \newline \end{aligned} $$ 在负零的反码基础上加 $1$ 会产生进位,但 `byte` 类型的长度只有 8 位,因此溢出到第 9 位的 $1$ 会被舍弃。也就是说,**负零的补码为 $0000 \; 0000$ ,与正零的补码相同**。这意味着在补码表示中只存在一个零,正负零歧义从而得到解决。 还剩最后一个疑惑:`byte` 类型的取值范围是 $[-128, 127]$ ,多出来的一个负数 $-128$ 是如何得到的呢?我们注意到,区间 $[-127, +127]$ 内的所有整数都有对应的原码、反码和补码,并且原码和补码之间可以互相转换。 然而,**补码 $1000 \; 0000$ 是一个例外,它并没有对应的原码**。根据转换方法,我们得到该补码的原码为 $0000 \; 0000$ 。这显然是矛盾的,因为该原码表示数字 $0$ ,它的补码应该是自身。计算机规定这个特殊的补码 $1000 \; 0000$ 代表 $-128$ 。实际上,$(-1) + (-127)$ 在补码下的计算结果就是 $-128$ 。 $$ \begin{aligned} & (-127) + (-1) \newline & \rightarrow 1111 \; 1111 \; \text{(原码)} + 1000 \; 0001 \; \text{(原码)} \newline & = 1000 \; 0000 \; \text{(反码)} + 1111 \; 1110 \; \text{(反码)} \newline & = 1000 \; 0001 \; \text{(补码)} + 1111 \; 1111 \; \text{(补码)} \newline & = 1000 \; 0000 \; \text{(补码)} \newline & \rightarrow -128 \end{aligned} $$ 你可能已经发现了,上述所有计算都是加法运算。这暗示着一个重要事实:**计算机内部的硬件电路主要是基于加法运算设计的**。这是因为加法运算相对于其他运算(比如乘法、除法和减法)来说,硬件实现起来更简单,更容易进行并行化处理,运算速度更快。 请注意,这并不意味着计算机只能做加法。**通过将加法与一些基本逻辑运算结合,计算机能够实现各种其他的数学运算**。例如,计算减法 $a - b$ 可以转换为计算加法 $a + (-b)$ ;计算乘法和除法可以转换为计算多次加法或减法。 现在我们可以总结出计算机使用补码的原因:基于补码表示,计算机可以用同样的电路和操作来处理正数和负数的加法,不需要设计特殊的硬件电路来处理减法,并且无须特别处理正负零的歧义问题。这大大简化了硬件设计,提高了运算效率。 补码的设计非常精妙,因篇幅关系我们就先介绍到这里,建议有兴趣的读者进一步深入了解。 ## 浮点数编码 细心的你可能会发现:`int` 和 `float` 长度相同,都是 4 字节 ,但为什么 `float` 的取值范围远大于 `int` ?这非常反直觉,因为按理说 `float` 需要表示小数,取值范围应该变小才对。 实际上,**这是因为浮点数 `float` 采用了不同的表示方式**。记一个 32 比特长度的二进制数为: $$ b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0 $$ 根据 IEEE 754 标准,32-bit 长度的 `float` 由以下三个部分构成。 - 符号位 $\mathrm{S}$ :占 1 位 ,对应 $b_{31}$ 。 - 指数位 $\mathrm{E}$ :占 8 位 ,对应 $b_{30} b_{29} \ldots b_{23}$ 。 - 分数位 $\mathrm{N}$ :占 23 位 ,对应 $b_{22} b_{21} \ldots b_0$ 。 二进制数 `float` 对应值的计算方法为: $$ \text {val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2-127} \times\left(1 . b_{22} b_{21} \ldots b_0\right)_2 $$ 转化到十进制下的计算公式为: $$ \text {val}=(-1)^{\mathrm{S}} \times 2^{\mathrm{E} -127} \times (1 + \mathrm{N}) $$ 其中各项的取值范围为: $$ \begin{aligned} \mathrm{S} \in & \{ 0, 1\}, \quad \mathrm{E} \in \{ 1, 2, \dots, 254 \} \newline (1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} 2^{-i}) \subset [1, 2 - 2^{-23}] \end{aligned} $$ ![IEEE 754 标准下的 float 的计算示例](number_encoding.assets/ieee_754_float.png) 观察上图,给定一个示例数据 $\mathrm{S} = 0$ , $\mathrm{E} = 124$ ,$\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ ,则有: $$ \text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875 $$ 现在我们可以回答最初的问题:**`float` 的表示方式包含指数位,导致其取值范围远大于 `int`** 。根据以上计算,`float` 可表示的最大正数为 $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ ,切换符号位便可得到最小负数。 **尽管浮点数 `float` 扩展了取值范围,但其副作用是牺牲了精度**。整数类型 `int` 将全部 32 比特用于表示数字,数字是均匀分布的;而由于指数位的存在,浮点数 `float` 的数值越大,相邻两个数字之间的差值就会趋向越大。 如下表所示,指数位 $\mathrm{E} = 0$ 和 $\mathrm{E} = 255$ 具有特殊含义,**用于表示零、无穷大、$\mathrm{NaN}$ 等**。

  指数位含义

| 指数位 E | 分数位 $\mathrm{N} = 0$ | 分数位 $\mathrm{N} \ne 0$ | 计算公式 | | ------------------ | ----------------------- | ------------------------- | ---------------------------------------------------------------------- | | $0$ | $\pm 0$ | 次正规数 | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ | | $1, 2, \dots, 254$ | 正规数 | 正规数 | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ | | $255$ | $\pm \infty$ | $\mathrm{NaN}$ | | 值得说明的是,次正规数显著提升了浮点数的精度。最小正正规数为 $2^{-126}$ ,最小正次正规数为 $2^{-126} \times 2^{-23}$ 。 双精度 `double` 也采用类似于 `float` 的表示方法,在此不做赘述。 ================================================ FILE: docs/chapter_data_structure/summary.md ================================================ # 小结 ### 重点回顾 - 数据结构可以从逻辑结构和物理结构两个角度进行分类。逻辑结构描述了数据元素之间的逻辑关系,而物理结构描述了数据在计算机内存中的存储方式。 - 常见的逻辑结构包括线性、树状和网状等。通常我们根据逻辑结构将数据结构分为线性(数组、链表、栈、队列)和非线性(树、图、堆)两种。哈希表的实现可能同时包含线性数据结构和非线性数据结构。 - 当程序运行时,数据被存储在计算机内存中。每个内存空间都拥有对应的内存地址,程序通过这些内存地址访问数据。 - 物理结构主要分为连续空间存储(数组)和分散空间存储(链表)。所有数据结构都是由数组、链表或两者的组合实现的。 - 计算机中的基本数据类型包括整数 `byte`、`short`、`int`、`long` ,浮点数 `float`、`double` ,字符 `char` 和布尔 `bool` 。它们的取值范围取决于占用空间大小和表示方式。 - 原码、反码和补码是在计算机中编码数字的三种方法,它们之间可以相互转换。整数的原码的最高位是符号位,其余位是数字的值。 - 整数在计算机中是以补码的形式存储的。在补码表示下,计算机可以对正数和负数的加法一视同仁,不需要为减法操作单独设计特殊的硬件电路,并且不存在正负零歧义的问题。 - 浮点数的编码由 1 位符号位、8 位指数位和 23 位分数位构成。由于存在指数位,因此浮点数的取值范围远大于整数,代价是牺牲了精度。 - ASCII 码是最早出现的英文字符集,长度为 1 字节,共收录 127 个字符。GBK 字符集是常用的中文字符集,共收录两万多个汉字。Unicode 致力于提供一个完整的字符集标准,收录世界上各种语言的字符,从而解决由于字符编码方法不一致而导致的乱码问题。 - UTF-8 是最受欢迎的 Unicode 编码方法,通用性非常好。它是一种变长的编码方法,具有很好的扩展性,有效提升了存储空间的使用效率。UTF-16 和 UTF-32 是等长的编码方法。在编码中文时,UTF-16 占用的空间比 UTF-8 更小。Java 和 C# 等编程语言默认使用 UTF-16 编码。 ### Q & A **Q**:为什么哈希表同时包含线性数据结构和非线性数据结构? 哈希表底层是数组,而为了解决哈希冲突,我们可能会使用“链式地址”(后续“哈希冲突”章节会讲):数组中每个桶指向一个链表,当链表长度超过一定阈值时,又可能被转化为树(通常为红黑树)。 从存储的角度来看,哈希表的底层是数组,其中每一个桶槽位可能包含一个值,也可能包含一个链表或一棵树。因此,哈希表可能同时包含线性数据结构(数组、链表)和非线性数据结构(树)。 **Q**:`char` 类型的长度是 1 字节吗? `char` 类型的长度由编程语言采用的编码方法决定。例如,Java、JavaScript、TypeScript、C# 都采用 UTF-16 编码(保存 Unicode 码点),因此 `char` 类型的长度为 2 字节。 **Q**:基于数组实现的数据结构也称“静态数据结构” 是否有歧义?栈也可以进行出栈和入栈等操作,这些操作都是“动态”的。 栈确实可以实现动态的数据操作,但数据结构仍然是“静态”(长度不可变)的。尽管基于数组的数据结构可以动态地添加或删除元素,但它们的容量是固定的。如果数据量超出了预分配的大小,就需要创建一个新的更大的数组,并将旧数组的内容复制到新数组中。 **Q**:在构建栈(队列)的时候,未指定它的大小,为什么它们是“静态数据结构”呢? 在高级编程语言中,我们无须人工指定栈(队列)的初始容量,这个工作由类内部自动完成。例如,Java 的 `ArrayList` 的初始容量通常为 10。另外,扩容操作也是自动实现的。详见后续的“列表”章节。 **Q**:原码转补码的方法是“先取反后加 1”,那么补码转原码应该是逆运算“先减 1 后取反”,而补码转原码也一样可以通过“先取反后加 1”得到,这是为什么呢? 这是因为原码和补码的相互转换实际上是计算“补数”的过程。我们先给出补数的定义:假设 $a + b = c$ ,那么我们称 $a$ 是 $b$ 到 $c$ 的补数,反之也称 $b$ 是 $a$ 到 $c$ 的补数。 给定一个 $n = 4$ 位长度的二进制数 $0010$ ,如果将这个数字看作原码(不考虑符号位),那么它的补码需通过“先取反后加 1”得到: $$ 0010 \rightarrow 1101 \rightarrow 1110 $$ 我们会发现,原码和补码的和是 $0010 + 1110 = 10000$ ,也就是说,补码 $1110$ 是原码 $0010$ 到 $10000$ 的“补数”。**这意味着上述“先取反后加 1”实际上是计算到 $10000$ 的补数的过程**。 那么,补码 $1110$ 到 $10000$ 的“补数”是多少呢?我们依然可以用“先取反后加 1”得到它: $$ 1110 \rightarrow 0001 \rightarrow 0010 $$ 换句话说,原码和补码互为对方到 $10000$ 的“补数”,因此“原码转补码”和“补码转原码”可以用相同的操作(先取反后加 1 )实现。 当然,我们也可以用逆运算来求补码 $1110$ 的原码,即“先减 1 后取反”: $$ 1110 \rightarrow 1101 \rightarrow 0010 $$ 总结来看,“先取反后加 1”和“先减 1 后取反”这两种运算都是在计算到 $10000$ 的补数,它们是等价的。 本质上看,“取反”操作实际上是求到 $1111$ 的补数(因为恒有 `原码 + 反码 = 1111`);而在反码基础上再加 1 得到的补码,就是到 $10000$ 的补数。 上述以 $n = 4$ 为例,其可被推广至任意位数的二进制数。 ================================================ FILE: docs/chapter_divide_and_conquer/binary_search_recur.md ================================================ # 分治搜索策略 我们已经学过,搜索算法分为两大类。 - **暴力搜索**:它通过遍历数据结构实现,时间复杂度为 $O(n)$ 。 - **自适应搜索**:它利用特有的数据组织形式或先验信息,时间复杂度可达到 $O(\log n)$ 甚至 $O(1)$ 。 实际上,**时间复杂度为 $O(\log n)$ 的搜索算法通常是基于分治策略实现的**,例如二分查找和树。 - 二分查找的每一步都将问题(在数组中搜索目标元素)分解为一个小问题(在数组的一半中搜索目标元素),这个过程一直持续到数组为空或找到目标元素为止。 - 树是分治思想的代表,在二叉搜索树、AVL 树、堆等数据结构中,各种操作的时间复杂度皆为 $O(\log n)$ 。 二分查找的分治策略如下所示。 - **问题可以分解**:二分查找递归地将原问题(在数组中进行查找)分解为子问题(在数组的一半中进行查找),这是通过比较中间元素和目标元素来实现的。 - **子问题是独立的**:在二分查找中,每轮只处理一个子问题,它不受其他子问题的影响。 - **子问题的解无须合并**:二分查找旨在查找一个特定元素,因此不需要将子问题的解进行合并。当子问题得到解决时,原问题也会同时得到解决。 分治能够提升搜索效率,本质上是因为暴力搜索每轮只能排除一个选项,**而分治搜索每轮可以排除一半选项**。 ### 基于分治实现二分查找 在之前的章节中,二分查找是基于递推(迭代)实现的。现在我们基于分治(递归)来实现它。 !!! question 给定一个长度为 $n$ 的有序数组 `nums` ,其中所有元素都是唯一的,请查找元素 `target` 。 从分治角度,我们将搜索区间 $[i, j]$ 对应的子问题记为 $f(i, j)$ 。 以原问题 $f(0, n-1)$ 为起始点,通过以下步骤进行二分查找。 1. 计算搜索区间 $[i, j]$ 的中点 $m$ ,根据它排除一半搜索区间。 2. 递归求解规模减小一半的子问题,可能为 $f(i, m-1)$ 或 $f(m+1, j)$ 。 3. 循环第 `1.` 步和第 `2.` 步,直至找到 `target` 或区间为空时返回。 下图展示了在数组中二分查找元素 $6$ 的分治过程。 ![二分查找的分治过程](binary_search_recur.assets/binary_search_recur.png) 在实现代码中,我们声明一个递归函数 `dfs()` 来求解问题 $f(i, j)$ : ```src [file]{binary_search_recur}-[class]{}-[func]{binary_search} ``` ================================================ FILE: docs/chapter_divide_and_conquer/build_binary_tree_problem.md ================================================ # 构建二叉树问题 !!! question 给定一棵二叉树的前序遍历 `preorder` 和中序遍历 `inorder` ,请从中构建二叉树,返回二叉树的根节点。假设二叉树中没有值重复的节点(如下图所示)。 ![构建二叉树的示例数据](build_binary_tree_problem.assets/build_tree_example.png) ### 判断是否为分治问题 原问题定义为从 `preorder` 和 `inorder` 构建二叉树,是一个典型的分治问题。 - **问题可以分解**:从分治的角度切入,我们可以将原问题划分为两个子问题:构建左子树、构建右子树,加上一步操作:初始化根节点。而对于每棵子树(子问题),我们仍然可以复用以上划分方法,将其划分为更小的子树(子问题),直至达到最小子问题(空子树)时终止。 - **子问题是独立的**:左子树和右子树是相互独立的,它们之间没有交集。在构建左子树时,我们只需关注中序遍历和前序遍历中与左子树对应的部分。右子树同理。 - **子问题的解可以合并**:一旦得到了左子树和右子树(子问题的解),我们就可以将它们链接到根节点上,得到原问题的解。 ### 如何划分子树 根据以上分析,这道题可以使用分治来求解,**但如何通过前序遍历 `preorder` 和中序遍历 `inorder` 来划分左子树和右子树呢**? 根据定义,`preorder` 和 `inorder` 都可以划分为三个部分。 - 前序遍历:`[ 根节点 | 左子树 | 右子树 ]` ,例如上图的树对应 `[ 3 | 9 | 2 1 7 ]` 。 - 中序遍历:`[ 左子树 | 根节点 | 右子树 ]` ,例如上图的树对应 `[ 9 | 3 | 1 2 7 ]` 。 以上图数据为例,我们可以通过下图所示的步骤得到划分结果。 1. 前序遍历的首元素 3 是根节点的值。 2. 查找根节点 3 在 `inorder` 中的索引,利用该索引可将 `inorder` 划分为 `[ 9 | 3 | 1 2 7 ]` 。 3. 根据 `inorder` 的划分结果,易得左子树和右子树的节点数量分别为 1 和 3 ,从而可将 `preorder` 划分为 `[ 3 | 9 | 2 1 7 ]` 。 ![在前序遍历和中序遍历中划分子树](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png) ### 基于变量描述子树区间 根据以上划分方法,**我们已经得到根节点、左子树、右子树在 `preorder` 和 `inorder` 中的索引区间**。而为了描述这些索引区间,我们需要借助几个指针变量。 - 将当前树的根节点在 `preorder` 中的索引记为 $i$ 。 - 将当前树的根节点在 `inorder` 中的索引记为 $m$ 。 - 将当前树在 `inorder` 中的索引区间记为 $[l, r]$ 。 如下表所示,通过以上变量即可表示根节点在 `preorder` 中的索引,以及子树在 `inorder` 中的索引区间。

  根节点和子树在前序遍历和中序遍历中的索引

| | 根节点在 `preorder` 中的索引 | 子树在 `inorder` 中的索引区间 | | ------ | ---------------------------- | ----------------------------- | | 当前树 | $i$ | $[l, r]$ | | 左子树 | $i + 1$ | $[l, m-1]$ | | 右子树 | $i + 1 + (m - l)$ | $[m+1, r]$ | 请注意,右子树根节点索引中的 $(m-l)$ 的含义是“左子树的节点数量”,建议结合下图理解。 ![根节点和左右子树的索引区间表示](build_binary_tree_problem.assets/build_tree_division_pointers.png) ### 代码实现 为了提升查询 $m$ 的效率,我们借助一个哈希表 `hmap` 来存储数组 `inorder` 中元素到索引的映射: ```src [file]{build_tree}-[class]{}-[func]{build_tree} ``` 下图展示了构建二叉树的递归过程,各个节点是在向下“递”的过程中建立的,而各条边(引用)是在向上“归”的过程中建立的。 === "<1>" ![构建二叉树的递归过程](build_binary_tree_problem.assets/built_tree_step1.png) === "<2>" ![built_tree_step2](build_binary_tree_problem.assets/built_tree_step2.png) === "<3>" ![built_tree_step3](build_binary_tree_problem.assets/built_tree_step3.png) === "<4>" ![built_tree_step4](build_binary_tree_problem.assets/built_tree_step4.png) === "<5>" ![built_tree_step5](build_binary_tree_problem.assets/built_tree_step5.png) === "<6>" ![built_tree_step6](build_binary_tree_problem.assets/built_tree_step6.png) === "<7>" ![built_tree_step7](build_binary_tree_problem.assets/built_tree_step7.png) === "<8>" ![built_tree_step8](build_binary_tree_problem.assets/built_tree_step8.png) === "<9>" ![built_tree_step9](build_binary_tree_problem.assets/built_tree_step9.png) 每个递归函数内的前序遍历 `preorder` 和中序遍历 `inorder` 的划分结果如下图所示。 ![每个递归函数中的划分结果](build_binary_tree_problem.assets/built_tree_overall.png) 设树的节点数量为 $n$ ,初始化每一个节点(执行一个递归函数 `dfs()` )使用 $O(1)$ 时间。**因此总体时间复杂度为 $O(n)$** 。 哈希表存储 `inorder` 元素到索引的映射,空间复杂度为 $O(n)$ 。在最差情况下,即二叉树退化为链表时,递归深度达到 $n$ ,使用 $O(n)$ 的栈帧空间。**因此总体空间复杂度为 $O(n)$** 。 ================================================ FILE: docs/chapter_divide_and_conquer/divide_and_conquer.md ================================================ # 分治算法 分治(divide and conquer),全称分而治之,是一种非常重要且常见的算法策略。分治通常基于递归实现,包括“分”和“治”两个步骤。 1. **分(划分阶段)**:递归地将原问题分解为两个或多个子问题,直至到达最小子问题时终止。 2. **治(合并阶段)**:从已知解的最小子问题开始,从底至顶地将子问题的解进行合并,从而构建出原问题的解。 如下图所示,“归并排序”是分治策略的典型应用之一。 1. **分**:递归地将原数组(原问题)划分为两个子数组(子问题),直到子数组只剩一个元素(最小子问题)。 2. **治**:从底至顶地将有序的子数组(子问题的解)进行合并,从而得到有序的原数组(原问题的解)。 ![归并排序的分治策略](divide_and_conquer.assets/divide_and_conquer_merge_sort.png) ## 如何判断分治问题 一个问题是否适合使用分治解决,通常可以参考以下几个判断依据。 1. **问题可以分解**:原问题可以分解成规模更小、类似的子问题,以及能够以相同方式递归地进行划分。 2. **子问题是独立的**:子问题之间没有重叠,互不依赖,可以独立解决。 3. **子问题的解可以合并**:原问题的解通过合并子问题的解得来。 显然,归并排序满足以上三个判断依据。 1. **问题可以分解**:递归地将数组(原问题)划分为两个子数组(子问题)。 2. **子问题是独立的**:每个子数组都可以独立地进行排序(子问题可以独立进行求解)。 3. **子问题的解可以合并**:两个有序子数组(子问题的解)可以合并为一个有序数组(原问题的解)。 ## 通过分治提升效率 **分治不仅可以有效地解决算法问题,往往还可以提升算法效率**。在排序算法中,快速排序、归并排序、堆排序相较于选择、冒泡、插入排序更快,就是因为它们应用了分治策略。 那么,我们不禁发问:**为什么分治可以提升算法效率,其底层逻辑是什么**?换句话说,将大问题分解为多个子问题、解决子问题、将子问题的解合并为原问题的解,这几步的效率为什么比直接解决原问题的效率更高?这个问题可以从操作数量和并行计算两方面来讨论。 ### 操作数量优化 以“冒泡排序”为例,其处理一个长度为 $n$ 的数组需要 $O(n^2)$ 时间。假设我们按照下图所示的方式,将数组从中点处分为两个子数组,则划分需要 $O(n)$ 时间,排序每个子数组需要 $O((n / 2)^2)$ 时间,合并两个子数组需要 $O(n)$ 时间,总体时间复杂度为: $$ O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n) $$ ![划分数组前后的冒泡排序](divide_and_conquer.assets/divide_and_conquer_bubble_sort.png) 接下来,我们计算以下不等式,其左边和右边分别为划分前和划分后的操作总数: $$ \begin{aligned} n^2 & > \frac{n^2}{2} + 2n \newline n^2 - \frac{n^2}{2} - 2n & > 0 \newline n(n - 4) & > 0 \end{aligned} $$ **这意味着当 $n > 4$ 时,划分后的操作数量更少,排序效率应该更高**。请注意,划分后的时间复杂度仍然是平方阶 $O(n^2)$ ,只是复杂度中的常数项变小了。 进一步想,**如果我们把子数组不断地再从中点处划分为两个子数组**,直至子数组只剩一个元素时停止划分呢?这种思路实际上就是“归并排序”,时间复杂度为 $O(n \log n)$ 。 再思考,**如果我们多设置几个划分点**,将原数组平均划分为 $k$ 个子数组呢?这种情况与“桶排序”非常类似,它非常适合排序海量数据,理论上时间复杂度可以达到 $O(n + k)$ 。 ### 并行计算优化 我们知道,分治生成的子问题是相互独立的,**因此通常可以并行解决**。也就是说,分治不仅可以降低算法的时间复杂度,**还有利于操作系统的并行优化**。 并行优化在多核或多处理器的环境中尤其有效,因为系统可以同时处理多个子问题,更加充分地利用计算资源,从而显著减少总体的运行时间。 比如在下图所示的“桶排序”中,我们将海量的数据平均分配到各个桶中,则可将所有桶的排序任务分散到各个计算单元,完成后再合并结果。 ![桶排序的并行计算](divide_and_conquer.assets/divide_and_conquer_parallel_computing.png) ## 分治常见应用 一方面,分治可以用来解决许多经典算法问题。 - **寻找最近点对**:该算法首先将点集分成两部分,然后分别找出两部分中的最近点对,最后找出跨越两部分的最近点对。 - **大整数乘法**:例如 Karatsuba 算法,它将大整数乘法分解为几个较小的整数的乘法和加法。 - **矩阵乘法**:例如 Strassen 算法,它将大矩阵乘法分解为多个小矩阵的乘法和加法。 - **汉诺塔问题**:汉诺塔问题可以通过递归解决,这是典型的分治策略应用。 - **求解逆序对**:在一个序列中,如果前面的数字大于后面的数字,那么这两个数字构成一个逆序对。求解逆序对问题可以利用分治的思想,借助归并排序进行求解。 另一方面,分治在算法和数据结构的设计中应用得非常广泛。 - **二分查找**:二分查找是将有序数组从中点索引处分为两部分,然后根据目标值与中间元素值比较结果,决定排除哪一半区间,并在剩余区间执行相同的二分操作。 - **归并排序**:本节开头已介绍,不再赘述。 - **快速排序**:快速排序是选取一个基准值,然后把数组分为两个子数组,一个子数组的元素比基准值小,另一子数组的元素比基准值大,再对这两部分进行相同的划分操作,直至子数组只剩下一个元素。 - **桶排序**:桶排序的基本思想是将数据分散到多个桶,然后对每个桶内的元素进行排序,最后将各个桶的元素依次取出,从而得到一个有序数组。 - **树**:例如二叉搜索树、AVL 树、红黑树、B 树、B+ 树等,它们的查找、插入和删除等操作都可以视为分治策略的应用。 - **堆**:堆是一种特殊的完全二叉树,其各种操作,如插入、删除和堆化,实际上都隐含了分治的思想。 - **哈希表**:虽然哈希表并不直接应用分治,但某些哈希冲突解决方案间接应用了分治策略,例如,链式地址中的长链表会被转化为红黑树,以提升查询效率。 可以看出,**分治是一种“润物细无声”的算法思想**,隐含在各种算法与数据结构之中。 ================================================ FILE: docs/chapter_divide_and_conquer/hanota_problem.md ================================================ # 汉诺塔问题 在归并排序和构建二叉树中,我们都是将原问题分解为两个规模为原问题一半的子问题。然而对于汉诺塔问题,我们采用不同的分解策略。 !!! question 给定三根柱子,记为 `A`、`B` 和 `C` 。起始状态下,柱子 `A` 上套着 $n$ 个圆盘,它们从上到下按照从小到大的顺序排列。我们的任务是要把这 $n$ 个圆盘移到柱子 `C` 上,并保持它们的原有顺序不变(如下图所示)。在移动圆盘的过程中,需要遵守以下规则。 1. 圆盘只能从一根柱子顶部拿出,从另一根柱子顶部放入。 2. 每次只能移动一个圆盘。 3. 小圆盘必须时刻位于大圆盘之上。 ![汉诺塔问题示例](hanota_problem.assets/hanota_example.png) **我们将规模为 $i$ 的汉诺塔问题记作 $f(i)$** 。例如 $f(3)$ 代表将 $3$ 个圆盘从 `A` 移动至 `C` 的汉诺塔问题。 ### 考虑基本情况 如下图所示,对于问题 $f(1)$ ,即当只有一个圆盘时,我们将它直接从 `A` 移动至 `C` 即可。 === "<1>" ![规模为 1 的问题的解](hanota_problem.assets/hanota_f1_step1.png) === "<2>" ![hanota_f1_step2](hanota_problem.assets/hanota_f1_step2.png) 如下图所示,对于问题 $f(2)$ ,即当有两个圆盘时,**由于要时刻满足小圆盘在大圆盘之上,因此需要借助 `B` 来完成移动**。 1. 先将上面的小圆盘从 `A` 移至 `B` 。 2. 再将大圆盘从 `A` 移至 `C` 。 3. 最后将小圆盘从 `B` 移至 `C` 。 === "<1>" ![规模为 2 的问题的解](hanota_problem.assets/hanota_f2_step1.png) === "<2>" ![hanota_f2_step2](hanota_problem.assets/hanota_f2_step2.png) === "<3>" ![hanota_f2_step3](hanota_problem.assets/hanota_f2_step3.png) === "<4>" ![hanota_f2_step4](hanota_problem.assets/hanota_f2_step4.png) 解决问题 $f(2)$ 的过程可总结为:**将两个圆盘借助 `B` 从 `A` 移至 `C`** 。其中,`C` 称为目标柱、`B` 称为缓冲柱。 ### 子问题分解 对于问题 $f(3)$ ,即当有三个圆盘时,情况变得稍微复杂了一些。 因为已知 $f(1)$ 和 $f(2)$ 的解,所以我们可从分治角度思考,**将 `A` 顶部的两个圆盘看作一个整体**,执行下图所示的步骤。这样三个圆盘就被顺利地从 `A` 移至 `C` 了。 1. 令 `B` 为目标柱、`C` 为缓冲柱,将两个圆盘从 `A` 移至 `B` 。 2. 将 `A` 中剩余的一个圆盘从 `A` 直接移动至 `C` 。 3. 令 `C` 为目标柱、`A` 为缓冲柱,将两个圆盘从 `B` 移至 `C` 。 === "<1>" ![规模为 3 的问题的解](hanota_problem.assets/hanota_f3_step1.png) === "<2>" ![hanota_f3_step2](hanota_problem.assets/hanota_f3_step2.png) === "<3>" ![hanota_f3_step3](hanota_problem.assets/hanota_f3_step3.png) === "<4>" ![hanota_f3_step4](hanota_problem.assets/hanota_f3_step4.png) 从本质上看,**我们将问题 $f(3)$ 划分为两个子问题 $f(2)$ 和一个子问题 $f(1)$** 。按顺序解决这三个子问题之后,原问题随之得到解决。这说明子问题是独立的,而且解可以合并。 至此,我们可总结出下图所示的解决汉诺塔问题的分治策略:将原问题 $f(n)$ 划分为两个子问题 $f(n-1)$ 和一个子问题 $f(1)$ ,并按照以下顺序解决这三个子问题。 1. 将 $n-1$ 个圆盘借助 `C` 从 `A` 移至 `B` 。 2. 将剩余 $1$ 个圆盘从 `A` 直接移至 `C` 。 3. 将 $n-1$ 个圆盘借助 `A` 从 `B` 移至 `C` 。 对于这两个子问题 $f(n-1)$ ,**可以通过相同的方式进行递归划分**,直至达到最小子问题 $f(1)$ 。而 $f(1)$ 的解是已知的,只需一次移动操作即可。 ![解决汉诺塔问题的分治策略](hanota_problem.assets/hanota_divide_and_conquer.png) ### 代码实现 在代码中,我们声明一个递归函数 `dfs(i, src, buf, tar)` ,它的作用是将柱 `src` 顶部的 $i$ 个圆盘借助缓冲柱 `buf` 移动至目标柱 `tar` : ```src [file]{hanota}-[class]{}-[func]{solve_hanota} ``` 如下图所示,汉诺塔问题形成一棵高度为 $n$ 的递归树,每个节点代表一个子问题,对应一个开启的 `dfs()` 函数,**因此时间复杂度为 $O(2^n)$ ,空间复杂度为 $O(n)$** 。 ![汉诺塔问题的递归树](hanota_problem.assets/hanota_recursive_tree.png) !!! quote 汉诺塔问题源自一个古老的传说。在古印度的一个寺庙里,僧侣们有三根高大的钻石柱子,以及 $64$ 个大小不一的金圆盘。僧侣们不断地移动圆盘,他们相信在最后一个圆盘被正确放置的那一刻,这个世界就会结束。 然而,即使僧侣们每秒钟移动一次,总共需要大约 $2^{64} \approx 1.84×10^{19}$ 秒,合约 $5850$ 亿年,远远超过了现在对宇宙年龄的估计。所以,倘若这个传说是真的,我们应该不需要担心世界末日的到来。 ================================================ FILE: docs/chapter_divide_and_conquer/index.md ================================================ # 分治 ![分治](../assets/covers/chapter_divide_and_conquer.jpg) !!! abstract 难题被逐层拆解,每一次的拆解都使它变得更为简单。 分而治之揭示了一个重要的事实:从简单做起,一切都不再复杂。 ================================================ FILE: docs/chapter_divide_and_conquer/summary.md ================================================ # 小结 ### 重点回顾 - 分治是一种常见的算法设计策略,包括分(划分)和治(合并)两个阶段,通常基于递归实现。 - 判断是否是分治算法问题的依据包括:问题能否分解、子问题是否独立、子问题能否合并。 - 归并排序是分治策略的典型应用,其递归地将数组划分为等长的两个子数组,直到只剩一个元素时开始逐层合并,从而完成排序。 - 引入分治策略往往可以提升算法效率。一方面,分治策略减少了操作数量;另一方面,分治后有利于系统的并行优化。 - 分治既可以解决许多算法问题,也广泛应用于数据结构与算法设计中,处处可见其身影。 - 相较于暴力搜索,自适应搜索效率更高。时间复杂度为 $O(\log n)$ 的搜索算法通常是基于分治策略实现的。 - 二分查找是分治策略的另一个典型应用,它不包含将子问题的解进行合并的步骤。我们可以通过递归分治实现二分查找。 - 在构建二叉树的问题中,构建树(原问题)可以划分为构建左子树和右子树(子问题),这可以通过划分前序遍历和中序遍历的索引区间来实现。 - 在汉诺塔问题中,一个规模为 $n$ 的问题可以划分为两个规模为 $n-1$ 的子问题和一个规模为 $1$ 的子问题。按顺序解决这三个子问题后,原问题随之得到解决。 ================================================ FILE: docs/chapter_dynamic_programming/dp_problem_features.md ================================================ # 动态规划问题特性 在上一节中,我们学习了动态规划是如何通过子问题分解来求解原问题的。实际上,子问题分解是一种通用的算法思路,在分治、动态规划、回溯中的侧重点不同。 - 分治算法递归地将原问题划分为多个相互独立的子问题,直至最小子问题,并在回溯中合并子问题的解,最终得到原问题的解。 - 动态规划也对问题进行递归分解,但与分治算法的主要区别是,动态规划中的子问题是相互依赖的,在分解过程中会出现许多重叠子问题。 - 回溯算法在尝试和回退中穷举所有可能的解,并通过剪枝避免不必要的搜索分支。原问题的解由一系列决策步骤构成,我们可以将每个决策步骤之前的子序列看作一个子问题。 实际上,动态规划常用来求解最优化问题,它们不仅包含重叠子问题,还具有另外两大特性:最优子结构、无后效性。 ## 最优子结构 我们对爬楼梯问题稍作改动,使之更加适合展示最优子结构概念。 !!! question "爬楼梯最小代价" 给定一个楼梯,你每步可以上 $1$ 阶或者 $2$ 阶,每一阶楼梯上都贴有一个非负整数,表示你在该台阶所需要付出的代价。给定一个非负整数数组 $cost$ ,其中 $cost[i]$ 表示在第 $i$ 个台阶需要付出的代价,$cost[0]$ 为地面(起始点)。请计算最少需要付出多少代价才能到达顶部? 如下图所示,若第 $1$、$2$、$3$ 阶的代价分别为 $1$、$10$、$1$ ,则从地面爬到第 $3$ 阶的最小代价为 $2$ 。 ![爬到第 3 阶的最小代价](dp_problem_features.assets/min_cost_cs_example.png) 设 $dp[i]$ 为爬到第 $i$ 阶累计付出的代价,由于第 $i$ 阶只可能从 $i - 1$ 阶或 $i - 2$ 阶走来,因此 $dp[i]$ 只可能等于 $dp[i - 1] + cost[i]$ 或 $dp[i - 2] + cost[i]$ 。为了尽可能减少代价,我们应该选择两者中较小的那一个: $$ dp[i] = \min(dp[i-1], dp[i-2]) + cost[i] $$ 这便可以引出最优子结构的含义:**原问题的最优解是从子问题的最优解构建得来的**。 本题显然具有最优子结构:我们从两个子问题最优解 $dp[i-1]$ 和 $dp[i-2]$ 中挑选出较优的那一个,并用它构建出原问题 $dp[i]$ 的最优解。 那么,上一节的爬楼梯题目有没有最优子结构呢?它的目标是求解方案数量,看似是一个计数问题,但如果换一种问法:“求解最大方案数量”。我们意外地发现,**虽然题目修改前后是等价的,但最优子结构浮现出来了**:第 $n$ 阶最大方案数量等于第 $n-1$ 阶和第 $n-2$ 阶最大方案数量之和。所以说,最优子结构的解释方式比较灵活,在不同问题中会有不同的含义。 根据状态转移方程,以及初始状态 $dp[1] = cost[1]$ 和 $dp[2] = cost[2]$ ,我们就可以得到动态规划代码: ```src [file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp} ``` 下图展示了以上代码的动态规划过程。 ![爬楼梯最小代价的动态规划过程](dp_problem_features.assets/min_cost_cs_dp.png) 本题也可以进行空间优化,将一维压缩至零维,使得空间复杂度从 $O(n)$ 降至 $O(1)$ : ```src [file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp_comp} ``` ## 无后效性 无后效性是动态规划能够有效解决问题的重要特性之一,其定义为:**给定一个确定的状态,它的未来发展只与当前状态有关,而与过去经历的所有状态无关**。 以爬楼梯问题为例,给定状态 $i$ ,它会发展出状态 $i+1$ 和状态 $i+2$ ,分别对应跳 $1$ 步和跳 $2$ 步。在做出这两种选择时,我们无须考虑状态 $i$ 之前的状态,它们对状态 $i$ 的未来没有影响。 然而,如果我们给爬楼梯问题添加一个约束,情况就不一样了。 !!! question "带约束爬楼梯" 给定一个共有 $n$ 阶的楼梯,你每步可以上 $1$ 阶或者 $2$ 阶,**但不能连续两轮跳 $1$ 阶**,请问有多少种方案可以爬到楼顶? 如下图所示,爬上第 $3$ 阶仅剩 $2$ 种可行方案,其中连续三次跳 $1$ 阶的方案不满足约束条件,因此被舍弃。 ![带约束爬到第 3 阶的方案数量](dp_problem_features.assets/climbing_stairs_constraint_example.png) 在该问题中,如果上一轮是跳 $1$ 阶上来的,那么下一轮就必须跳 $2$ 阶。这意味着,**下一步选择不能由当前状态(当前所在楼梯阶数)独立决定,还和前一个状态(上一轮所在楼梯阶数)有关**。 不难发现,此问题已不满足无后效性,状态转移方程 $dp[i] = dp[i-1] + dp[i-2]$ 也失效了,因为 $dp[i-1]$ 代表本轮跳 $1$ 阶,但其中包含了许多“上一轮是跳 $1$ 阶上来的”方案,而为了满足约束,我们就不能将 $dp[i-1]$ 直接计入 $dp[i]$ 中。 为此,我们需要扩展状态定义:**状态 $[i, j]$ 表示处在第 $i$ 阶并且上一轮跳了 $j$ 阶**,其中 $j \in \{1, 2\}$ 。此状态定义有效地区分了上一轮跳了 $1$ 阶还是 $2$ 阶,我们可以据此判断当前状态是从何而来的。 - 当上一轮跳了 $1$ 阶时,上上一轮只能选择跳 $2$ 阶,即 $dp[i, 1]$ 只能从 $dp[i-1, 2]$ 转移过来。 - 当上一轮跳了 $2$ 阶时,上上一轮可选择跳 $1$ 阶或跳 $2$ 阶,即 $dp[i, 2]$ 可以从 $dp[i-2, 1]$ 或 $dp[i-2, 2]$ 转移过来。 如下图所示,在该定义下,$dp[i, j]$ 表示状态 $[i, j]$ 对应的方案数。此时状态转移方程为: $$ \begin{cases} dp[i, 1] = dp[i-1, 2] \\ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] \end{cases} $$ ![考虑约束下的递推关系](dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png) 最终,返回 $dp[n, 1] + dp[n, 2]$ 即可,两者之和代表爬到第 $n$ 阶的方案总数: ```src [file]{climbing_stairs_constraint_dp}-[class]{}-[func]{climbing_stairs_constraint_dp} ``` 在上面的案例中,由于仅需多考虑前面一个状态,因此我们仍然可以通过扩展状态定义,使得问题重新满足无后效性。然而,某些问题具有非常严重的“有后效性”。 !!! question "爬楼梯与障碍生成" 给定一个共有 $n$ 阶的楼梯,你每步可以上 $1$ 阶或者 $2$ 阶。**规定当爬到第 $i$ 阶时,系统自动会在第 $2i$ 阶上放上障碍物,之后所有轮都不允许跳到第 $2i$ 阶上**。例如,前两轮分别跳到了第 $2$、$3$ 阶上,则之后就不能跳到第 $4$、$6$ 阶上。请问有多少种方案可以爬到楼顶? 在这个问题中,下次跳跃依赖过去所有的状态,因为每一次跳跃都会在更高的阶梯上设置障碍,并影响未来的跳跃。对于这类问题,动态规划往往难以解决。 实际上,许多复杂的组合优化问题(例如旅行商问题)不满足无后效性。对于这类问题,我们通常会选择使用其他方法,例如启发式搜索、遗传算法、强化学习等,从而在有限时间内得到可用的局部最优解。 ================================================ FILE: docs/chapter_dynamic_programming/dp_solution_pipeline.md ================================================ # 动态规划解题思路 上两节介绍了动态规划问题的主要特征,接下来我们一起探究两个更加实用的问题。 1. 如何判断一个问题是不是动态规划问题? 2. 求解动态规划问题该从何处入手,完整步骤是什么? ## 问题判断 总的来说,如果一个问题包含重叠子问题、最优子结构,并满足无后效性,那么它通常适合用动态规划求解。然而,我们很难从问题描述中直接提取出这些特性。因此我们通常会放宽条件,**先观察问题是否适合使用回溯(穷举)解决**。 **适合用回溯解决的问题通常满足“决策树模型”**,这种问题可以使用树形结构来描述,其中每一个节点代表一个决策,每一条路径代表一个决策序列。 换句话说,如果问题包含明确的决策概念,并且解是通过一系列决策产生的,那么它就满足决策树模型,通常可以使用回溯来解决。 在此基础上,动态规划问题还有一些判断的“加分项”。 - 问题包含最大(小)或最多(少)等最优化描述。 - 问题的状态能够使用一个列表、多维矩阵或树来表示,并且一个状态与其周围的状态存在递推关系。 相应地,也存在一些“减分项”。 - 问题的目标是找出所有可能的解决方案,而不是找出最优解。 - 问题描述中有明显的排列组合的特征,需要返回具体的多个方案。 如果一个问题满足决策树模型,并具有较为明显的“加分项”,我们就可以假设它是一个动态规划问题,并在求解过程中验证它。 ## 问题求解步骤 动态规划的解题流程会因问题的性质和难度而有所不同,但通常遵循以下步骤:描述决策,定义状态,建立 $dp$ 表,推导状态转移方程,确定边界条件等。 为了更形象地展示解题步骤,我们使用一个经典问题“最小路径和”来举例。 !!! question 给定一个 $n \times m$ 的二维网格 `grid` ,网格中的每个单元格包含一个非负整数,表示该单元格的代价。机器人以左上角单元格为起始点,每次只能向下或者向右移动一步,直至到达右下角单元格。请返回从左上角到右下角的最小路径和。 下图展示了一个例子,给定网格的最小路径和为 $13$ 。 ![最小路径和示例数据](dp_solution_pipeline.assets/min_path_sum_example.png) **第一步:思考每轮的决策,定义状态,从而得到 $dp$ 表** 本题的每一轮的决策就是从当前格子向下或向右走一步。设当前格子的行列索引为 $[i, j]$ ,则向下或向右走一步后,索引变为 $[i+1, j]$ 或 $[i, j+1]$ 。因此,状态应包含行索引和列索引两个变量,记为 $[i, j]$ 。 状态 $[i, j]$ 对应的子问题为:从起始点 $[0, 0]$ 走到 $[i, j]$ 的最小路径和,解记为 $dp[i, j]$ 。 至此,我们就得到了下图所示的二维 $dp$ 矩阵,其尺寸与输入网格 $grid$ 相同。 ![状态定义与 dp 表](dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png) !!! note 动态规划和回溯过程可以描述为一个决策序列,而状态由所有决策变量构成。它应当包含描述解题进度的所有变量,其包含了足够的信息,能够用来推导出下一个状态。 每个状态都对应一个子问题,我们会定义一个 $dp$ 表来存储所有子问题的解,状态的每个独立变量都是 $dp$ 表的一个维度。从本质上看,$dp$ 表是状态和子问题的解之间的映射。 **第二步:找出最优子结构,进而推导出状态转移方程** 对于状态 $[i, j]$ ,它只能从上边格子 $[i-1, j]$ 和左边格子 $[i, j-1]$ 转移而来。因此最优子结构为:到达 $[i, j]$ 的最小路径和由 $[i, j-1]$ 的最小路径和与 $[i-1, j]$ 的最小路径和中较小的那一个决定。 根据以上分析,可推出下图所示的状态转移方程: $$ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j] $$ ![最优子结构与状态转移方程](dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png) !!! note 根据定义好的 $dp$ 表,思考原问题和子问题的关系,找出通过子问题的最优解来构造原问题的最优解的方法,即最优子结构。 一旦我们找到了最优子结构,就可以使用它来构建出状态转移方程。 **第三步:确定边界条件和状态转移顺序** 在本题中,处在首行的状态只能从其左边的状态得来,处在首列的状态只能从其上边的状态得来,因此首行 $i = 0$ 和首列 $j = 0$ 是边界条件。 如下图所示,由于每个格子是由其左方格子和上方格子转移而来,因此我们使用循环来遍历矩阵,外循环遍历各行,内循环遍历各列。 ![边界条件与状态转移顺序](dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png) !!! note 边界条件在动态规划中用于初始化 $dp$ 表,在搜索中用于剪枝。 状态转移顺序的核心是要保证在计算当前问题的解时,所有它依赖的更小子问题的解都已经被正确地计算出来。 根据以上分析,我们已经可以直接写出动态规划代码。然而子问题分解是一种从顶至底的思想,因此按照“暴力搜索 $\rightarrow$ 记忆化搜索 $\rightarrow$ 动态规划”的顺序实现更加符合思维习惯。 ### 方法一:暴力搜索 从状态 $[i, j]$ 开始搜索,不断分解为更小的状态 $[i-1, j]$ 和 $[i, j-1]$ ,递归函数包括以下要素。 - **递归参数**:状态 $[i, j]$ 。 - **返回值**:从 $[0, 0]$ 到 $[i, j]$ 的最小路径和 $dp[i, j]$ 。 - **终止条件**:当 $i = 0$ 且 $j = 0$ 时,返回代价 $grid[0, 0]$ 。 - **剪枝**:当 $i < 0$ 时或 $j < 0$ 时索引越界,此时返回代价 $+\infty$ ,代表不可行。 实现代码如下: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs} ``` 下图给出了以 $dp[2, 1]$ 为根节点的递归树,其中包含一些重叠子问题,其数量会随着网格 `grid` 的尺寸变大而急剧增多。 从本质上看,造成重叠子问题的原因为:**存在多条路径可以从左上角到达某一单元格**。 ![暴力搜索递归树](dp_solution_pipeline.assets/min_path_sum_dfs.png) 每个状态都有向下和向右两种选择,从左上角走到右下角总共需要 $m + n - 2$ 步,所以最差时间复杂度为 $O(2^{m + n})$ ,其中 $n$ 和 $m$ 分别为网格的行数和列数。请注意,这种计算方式未考虑临近网格边界的情况,当到达网格边界时只剩下一种选择,因此实际的路径数量会少一些。 ### 方法二:记忆化搜索 我们引入一个和网格 `grid` 相同尺寸的记忆列表 `mem` ,用于记录各个子问题的解,并将重叠子问题进行剪枝: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs_mem} ``` 如下图所示,在引入记忆化后,所有子问题的解只需计算一次,因此时间复杂度取决于状态总数,即网格尺寸 $O(nm)$ 。 ![记忆化搜索递归树](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png) ### 方法三:动态规划 基于迭代实现动态规划解法,代码如下所示: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp} ``` 下图展示了最小路径和的状态转移过程,其遍历了整个网格,**因此时间复杂度为 $O(nm)$** 。 数组 `dp` 大小为 $n \times m$ ,**因此空间复杂度为 $O(nm)$** 。 === "<1>" ![最小路径和的动态规划过程](dp_solution_pipeline.assets/min_path_sum_dp_step1.png) === "<2>" ![min_path_sum_dp_step2](dp_solution_pipeline.assets/min_path_sum_dp_step2.png) === "<3>" ![min_path_sum_dp_step3](dp_solution_pipeline.assets/min_path_sum_dp_step3.png) === "<4>" ![min_path_sum_dp_step4](dp_solution_pipeline.assets/min_path_sum_dp_step4.png) === "<5>" ![min_path_sum_dp_step5](dp_solution_pipeline.assets/min_path_sum_dp_step5.png) === "<6>" ![min_path_sum_dp_step6](dp_solution_pipeline.assets/min_path_sum_dp_step6.png) === "<7>" ![min_path_sum_dp_step7](dp_solution_pipeline.assets/min_path_sum_dp_step7.png) === "<8>" ![min_path_sum_dp_step8](dp_solution_pipeline.assets/min_path_sum_dp_step8.png) === "<9>" ![min_path_sum_dp_step9](dp_solution_pipeline.assets/min_path_sum_dp_step9.png) === "<10>" ![min_path_sum_dp_step10](dp_solution_pipeline.assets/min_path_sum_dp_step10.png) === "<11>" ![min_path_sum_dp_step11](dp_solution_pipeline.assets/min_path_sum_dp_step11.png) === "<12>" ![min_path_sum_dp_step12](dp_solution_pipeline.assets/min_path_sum_dp_step12.png) ### 空间优化 由于每个格子只与其左边和上边的格子有关,因此我们可以只用一个单行数组来实现 $dp$ 表。 请注意,因为数组 `dp` 只能表示一行的状态,所以我们无法提前初始化首列状态,而是在遍历每行时更新它: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp_comp} ``` ================================================ FILE: docs/chapter_dynamic_programming/edit_distance_problem.md ================================================ # 编辑距离问题 编辑距离,也称 Levenshtein 距离,指两个字符串之间互相转换的最少修改次数,通常用于在信息检索和自然语言处理中度量两个序列的相似度。 !!! question 输入两个字符串 $s$ 和 $t$ ,返回将 $s$ 转换为 $t$ 所需的最少编辑步数。 你可以在一个字符串中进行三种编辑操作:插入一个字符、删除一个字符、将字符替换为任意一个字符。 如下图所示,将 `kitten` 转换为 `sitting` 需要编辑 3 步,包括 2 次替换操作与 1 次添加操作;将 `hello` 转换为 `algo` 需要 3 步,包括 2 次替换操作和 1 次删除操作。 ![编辑距离的示例数据](edit_distance_problem.assets/edit_distance_example.png) **编辑距离问题可以很自然地用决策树模型来解释**。字符串对应树节点,一轮决策(一次编辑操作)对应树的一条边。 如下图所示,在不限制操作的情况下,每个节点都可以派生出许多条边,每条边对应一种操作,这意味着从 `hello` 转换到 `algo` 有许多种可能的路径。 从决策树的角度看,本题的目标是求解节点 `hello` 和节点 `algo` 之间的最短路径。 ![基于决策树模型表示编辑距离问题](edit_distance_problem.assets/edit_distance_decision_tree.png) ### 动态规划思路 **第一步:思考每轮的决策,定义状态,从而得到 $dp$ 表** 每一轮的决策是对字符串 $s$ 进行一次编辑操作。 我们希望在编辑操作的过程中,问题的规模逐渐缩小,这样才能构建子问题。设字符串 $s$ 和 $t$ 的长度分别为 $n$ 和 $m$ ,我们先考虑两字符串尾部的字符 $s[n-1]$ 和 $t[m-1]$ 。 - 若 $s[n-1]$ 和 $t[m-1]$ 相同,我们可以跳过它们,直接考虑 $s[n-2]$ 和 $t[m-2]$ 。 - 若 $s[n-1]$ 和 $t[m-1]$ 不同,我们需要对 $s$ 进行一次编辑(插入、删除、替换),使得两字符串尾部的字符相同,从而可以跳过它们,考虑规模更小的问题。 也就是说,我们在字符串 $s$ 中进行的每一轮决策(编辑操作),都会使得 $s$ 和 $t$ 中剩余的待匹配字符发生变化。因此,状态为当前在 $s$ 和 $t$ 中考虑的第 $i$ 和第 $j$ 个字符,记为 $[i, j]$ 。 状态 $[i, j]$ 对应的子问题:**将 $s$ 的前 $i$ 个字符更改为 $t$ 的前 $j$ 个字符所需的最少编辑步数**。 至此,得到一个尺寸为 $(i+1) \times (j+1)$ 的二维 $dp$ 表。 **第二步:找出最优子结构,进而推导出状态转移方程** 考虑子问题 $dp[i, j]$ ,其对应的两个字符串的尾部字符为 $s[i-1]$ 和 $t[j-1]$ ,可根据不同编辑操作分为下图所示的三种情况。 1. 在 $s[i-1]$ 之后添加 $t[j-1]$ ,则剩余子问题 $dp[i, j-1]$ 。 2. 删除 $s[i-1]$ ,则剩余子问题 $dp[i-1, j]$ 。 3. 将 $s[i-1]$ 替换为 $t[j-1]$ ,则剩余子问题 $dp[i-1, j-1]$ 。 ![编辑距离的状态转移](edit_distance_problem.assets/edit_distance_state_transfer.png) 根据以上分析,可得最优子结构:$dp[i, j]$ 的最少编辑步数等于 $dp[i, j-1]$、$dp[i-1, j]$、$dp[i-1, j-1]$ 三者中的最少编辑步数,再加上本次的编辑步数 $1$ 。对应的状态转移方程为: $$ dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 $$ 请注意,**当 $s[i-1]$ 和 $t[j-1]$ 相同时,无须编辑当前字符**,这种情况下的状态转移方程为: $$ dp[i, j] = dp[i-1, j-1] $$ **第三步:确定边界条件和状态转移顺序** 当两字符串都为空时,编辑步数为 $0$ ,即 $dp[0, 0] = 0$ 。当 $s$ 为空但 $t$ 不为空时,最少编辑步数等于 $t$ 的长度,即首行 $dp[0, j] = j$ 。当 $s$ 不为空但 $t$ 为空时,最少编辑步数等于 $s$ 的长度,即首列 $dp[i, 0] = i$ 。 观察状态转移方程,解 $dp[i, j]$ 依赖左方、上方、左上方的解,因此通过两层循环正序遍历整个 $dp$ 表即可。 ### 代码实现 ```src [file]{edit_distance}-[class]{}-[func]{edit_distance_dp} ``` 如下图所示,编辑距离问题的状态转移过程与背包问题非常类似,都可以看作填写一个二维网格的过程。 === "<1>" ![编辑距离的动态规划过程](edit_distance_problem.assets/edit_distance_dp_step1.png) === "<2>" ![edit_distance_dp_step2](edit_distance_problem.assets/edit_distance_dp_step2.png) === "<3>" ![edit_distance_dp_step3](edit_distance_problem.assets/edit_distance_dp_step3.png) === "<4>" ![edit_distance_dp_step4](edit_distance_problem.assets/edit_distance_dp_step4.png) === "<5>" ![edit_distance_dp_step5](edit_distance_problem.assets/edit_distance_dp_step5.png) === "<6>" ![edit_distance_dp_step6](edit_distance_problem.assets/edit_distance_dp_step6.png) === "<7>" ![edit_distance_dp_step7](edit_distance_problem.assets/edit_distance_dp_step7.png) === "<8>" ![edit_distance_dp_step8](edit_distance_problem.assets/edit_distance_dp_step8.png) === "<9>" ![edit_distance_dp_step9](edit_distance_problem.assets/edit_distance_dp_step9.png) === "<10>" ![edit_distance_dp_step10](edit_distance_problem.assets/edit_distance_dp_step10.png) === "<11>" ![edit_distance_dp_step11](edit_distance_problem.assets/edit_distance_dp_step11.png) === "<12>" ![edit_distance_dp_step12](edit_distance_problem.assets/edit_distance_dp_step12.png) === "<13>" ![edit_distance_dp_step13](edit_distance_problem.assets/edit_distance_dp_step13.png) === "<14>" ![edit_distance_dp_step14](edit_distance_problem.assets/edit_distance_dp_step14.png) === "<15>" ![edit_distance_dp_step15](edit_distance_problem.assets/edit_distance_dp_step15.png) ### 空间优化 由于 $dp[i,j]$ 是由上方 $dp[i-1, j]$、左方 $dp[i, j-1]$、左上方 $dp[i-1, j-1]$ 转移而来的,而正序遍历会丢失左上方 $dp[i-1, j-1]$ ,倒序遍历无法提前构建 $dp[i, j-1]$ ,因此两种遍历顺序都不可取。 为此,我们可以使用一个变量 `leftup` 来暂存左上方的解 $dp[i-1, j-1]$ ,从而只需考虑左方和上方的解。此时的情况与完全背包问题相同,可使用正序遍历。代码如下所示: ```src [file]{edit_distance}-[class]{}-[func]{edit_distance_dp_comp} ``` ================================================ FILE: docs/chapter_dynamic_programming/index.md ================================================ # 动态规划 ![动态规划](../assets/covers/chapter_dynamic_programming.jpg) !!! abstract 小溪汇入河流,江河汇入大海。 动态规划将小问题的解汇集成大问题的答案,一步步引领我们走向解决问题的彼岸。 ================================================ FILE: docs/chapter_dynamic_programming/intro_to_dynamic_programming.md ================================================ # 初探动态规划 动态规划(dynamic programming)是一个重要的算法范式,它将一个问题分解为一系列更小的子问题,并通过存储子问题的解来避免重复计算,从而大幅提升时间效率。 在本节中,我们从一个经典例题入手,先给出它的暴力回溯解法,观察其中包含的重叠子问题,再逐步导出更高效的动态规划解法。 !!! question "爬楼梯" 给定一个共有 $n$ 阶的楼梯,你每步可以上 $1$ 阶或者 $2$ 阶,请问有多少种方案可以爬到楼顶? 如下图所示,对于一个 $3$ 阶楼梯,共有 $3$ 种方案可以爬到楼顶。 ![爬到第 3 阶的方案数量](intro_to_dynamic_programming.assets/climbing_stairs_example.png) 本题的目标是求解方案数量,**我们可以考虑通过回溯来穷举所有可能性**。具体来说,将爬楼梯想象为一个多轮选择的过程:从地面出发,每轮选择上 $1$ 阶或 $2$ 阶,每当到达楼梯顶部时就将方案数量加 $1$ ,当越过楼梯顶部时就将其剪枝。代码如下所示: ```src [file]{climbing_stairs_backtrack}-[class]{}-[func]{climbing_stairs_backtrack} ``` ## 方法一:暴力搜索 回溯算法通常并不显式地对问题进行拆解,而是将求解问题看作一系列决策步骤,通过试探和剪枝,搜索所有可能的解。 我们可以尝试从问题分解的角度分析这道题。设爬到第 $i$ 阶共有 $dp[i]$ 种方案,那么 $dp[i]$ 就是原问题,其子问题包括: $$ dp[i-1], dp[i-2], \dots, dp[2], dp[1] $$ 由于每轮只能上 $1$ 阶或 $2$ 阶,因此当我们站在第 $i$ 阶楼梯上时,上一轮只可能站在第 $i - 1$ 阶或第 $i - 2$ 阶上。换句话说,我们只能从第 $i -1$ 阶或第 $i - 2$ 阶迈向第 $i$ 阶。 由此便可得出一个重要推论:**爬到第 $i - 1$ 阶的方案数加上爬到第 $i - 2$ 阶的方案数就等于爬到第 $i$ 阶的方案数**。公式如下: $$ dp[i] = dp[i-1] + dp[i-2] $$ 这意味着在爬楼梯问题中,各个子问题之间存在递推关系,**原问题的解可以由子问题的解构建得来**。下图展示了该递推关系。 ![方案数量递推关系](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png) 我们可以根据递推公式得到暴力搜索解法。以 $dp[n]$ 为起始点,**递归地将一个较大问题拆解为两个较小问题的和**,直至到达最小子问题 $dp[1]$ 和 $dp[2]$ 时返回。其中,最小子问题的解是已知的,即 $dp[1] = 1$、$dp[2] = 2$ ,表示爬到第 $1$、$2$ 阶分别有 $1$、$2$ 种方案。 观察以下代码,它和标准回溯代码都属于深度优先搜索,但更加简洁: ```src [file]{climbing_stairs_dfs}-[class]{}-[func]{climbing_stairs_dfs} ``` 下图展示了暴力搜索形成的递归树。对于问题 $dp[n]$ ,其递归树的深度为 $n$ ,时间复杂度为 $O(2^n)$ 。指数阶属于爆炸式增长,如果我们输入一个比较大的 $n$ ,则会陷入漫长的等待之中。 ![爬楼梯对应递归树](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png) 观察上图,**指数阶的时间复杂度是“重叠子问题”导致的**。例如 $dp[9]$ 被分解为 $dp[8]$ 和 $dp[7]$ ,$dp[8]$ 被分解为 $dp[7]$ 和 $dp[6]$ ,两者都包含子问题 $dp[7]$ 。 以此类推,子问题中包含更小的重叠子问题,子子孙孙无穷尽也。绝大部分计算资源都浪费在这些重叠的子问题上。 ## 方法二:记忆化搜索 为了提升算法效率,**我们希望所有的重叠子问题都只被计算一次**。为此,我们声明一个数组 `mem` 来记录每个子问题的解,并在搜索过程中将重叠子问题剪枝。 1. 当首次计算 $dp[i]$ 时,我们将其记录至 `mem[i]` ,以便之后使用。 2. 当再次需要计算 $dp[i]$ 时,我们便可直接从 `mem[i]` 中获取结果,从而避免重复计算该子问题。 代码如下所示: ```src [file]{climbing_stairs_dfs_mem}-[class]{}-[func]{climbing_stairs_dfs_mem} ``` 观察下图,**经过记忆化处理后,所有重叠子问题都只需计算一次,时间复杂度优化至 $O(n)$** ,这是一个巨大的飞跃。 ![记忆化搜索对应递归树](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png) ## 方法三:动态规划 **记忆化搜索是一种“从顶至底”的方法**:我们从原问题(根节点)开始,递归地将较大子问题分解为较小子问题,直至解已知的最小子问题(叶节点)。之后,通过回溯逐层收集子问题的解,构建出原问题的解。 与之相反,**动态规划是一种“从底至顶”的方法**:从最小子问题的解开始,迭代地构建更大子问题的解,直至得到原问题的解。 由于动态规划不包含回溯过程,因此只需使用循环迭代实现,无须使用递归。在以下代码中,我们初始化一个数组 `dp` 来存储子问题的解,它起到了与记忆化搜索中数组 `mem` 相同的记录作用: ```src [file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp} ``` 下图模拟了以上代码的执行过程。 ![爬楼梯的动态规划过程](intro_to_dynamic_programming.assets/climbing_stairs_dp.png) 与回溯算法一样,动态规划也使用“状态”概念来表示问题求解的特定阶段,每个状态都对应一个子问题以及相应的局部最优解。例如,爬楼梯问题的状态定义为当前所在楼梯阶数 $i$ 。 根据以上内容,我们可以总结出动态规划的常用术语。 - 将数组 `dp` 称为 dp 表,$dp[i]$ 表示状态 $i$ 对应子问题的解。 - 将最小子问题对应的状态(第 $1$ 阶和第 $2$ 阶楼梯)称为初始状态。 - 将递推公式 $dp[i] = dp[i-1] + dp[i-2]$ 称为状态转移方程。 ## 空间优化 细心的读者可能发现了,**由于 $dp[i]$ 只与 $dp[i-1]$ 和 $dp[i-2]$ 有关,因此我们无须使用一个数组 `dp` 来存储所有子问题的解**,而只需两个变量滚动前进即可。代码如下所示: ```src [file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp_comp} ``` 观察以上代码,由于省去了数组 `dp` 占用的空间,因此空间复杂度从 $O(n)$ 降至 $O(1)$ 。 在动态规划问题中,当前状态往往仅与前面有限个状态有关,这时我们可以只保留必要的状态,通过“降维”来节省内存空间。**这种空间优化技巧被称为“滚动变量”或“滚动数组”**。 ================================================ FILE: docs/chapter_dynamic_programming/knapsack_problem.md ================================================ # 0-1 背包问题 背包问题是一个非常好的动态规划入门题目,是动态规划中最常见的问题形式。其具有很多变种,例如 0-1 背包问题、完全背包问题、多重背包问题等。 在本节中,我们先来求解最常见的 0-1 背包问题。 !!! question 给定 $n$ 个物品,第 $i$ 个物品的重量为 $wgt[i-1]$、价值为 $val[i-1]$ ,和一个容量为 $cap$ 的背包。每个物品只能选择一次,问在限定背包容量下能放入物品的最大价值。 观察下图,由于物品编号 $i$ 从 $1$ 开始计数,数组索引从 $0$ 开始计数,因此物品 $i$ 对应重量 $wgt[i-1]$ 和价值 $val[i-1]$ 。 ![0-1 背包的示例数据](knapsack_problem.assets/knapsack_example.png) 我们可以将 0-1 背包问题看作一个由 $n$ 轮决策组成的过程,对于每个物体都有不放入和放入两种决策,因此该问题满足决策树模型。 该问题的目标是求解“在限定背包容量下能放入物品的最大价值”,因此较大概率是一个动态规划问题。 **第一步:思考每轮的决策,定义状态,从而得到 $dp$ 表** 对于每个物品来说,不放入背包,背包容量不变;放入背包,背包容量减小。由此可得状态定义:当前物品编号 $i$ 和背包容量 $c$ ,记为 $[i, c]$ 。 状态 $[i, c]$ 对应的子问题为:**前 $i$ 个物品在容量为 $c$ 的背包中的最大价值**,记为 $dp[i, c]$ 。 待求解的是 $dp[n, cap]$ ,因此需要一个尺寸为 $(n+1) \times (cap+1)$ 的二维 $dp$ 表。 **第二步:找出最优子结构,进而推导出状态转移方程** 当我们做出物品 $i$ 的决策后,剩余的是前 $i-1$ 个物品决策的子问题,可分为以下两种情况。 - **不放入物品 $i$** :背包容量不变,状态变化为 $[i-1, c]$ 。 - **放入物品 $i$** :背包容量减少 $wgt[i-1]$ ,价值增加 $val[i-1]$ ,状态变化为 $[i-1, c-wgt[i-1]]$ 。 上述分析向我们揭示了本题的最优子结构:**最大价值 $dp[i, c]$ 等于不放入物品 $i$ 和放入物品 $i$ 两种方案中价值更大的那一个**。由此可推导出状态转移方程: $$ dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) $$ 需要注意的是,若当前物品重量 $wgt[i - 1]$ 超出剩余背包容量 $c$ ,则只能选择不放入背包。 **第三步:确定边界条件和状态转移顺序** 当无物品或背包容量为 $0$ 时最大价值为 $0$ ,即首列 $dp[i, 0]$ 和首行 $dp[0, c]$ 都等于 $0$ 。 当前状态 $[i, c]$ 从上方的状态 $[i-1, c]$ 和左上方的状态 $[i-1, c-wgt[i-1]]$ 转移而来,因此通过两层循环正序遍历整个 $dp$ 表即可。 根据以上分析,我们接下来按顺序实现暴力搜索、记忆化搜索、动态规划解法。 ### 方法一:暴力搜索 搜索代码包含以下要素。 - **递归参数**:状态 $[i, c]$ 。 - **返回值**:子问题的解 $dp[i, c]$ 。 - **终止条件**:当物品编号越界 $i = 0$ 或背包剩余容量为 $0$ 时,终止递归并返回价值 $0$ 。 - **剪枝**:若当前物品重量超出背包剩余容量,则只能选择不放入背包。 ```src [file]{knapsack}-[class]{}-[func]{knapsack_dfs} ``` 如下图所示,由于每个物品都会产生不选和选两条搜索分支,因此时间复杂度为 $O(2^n)$ 。 观察递归树,容易发现其中存在重叠子问题,例如 $dp[1, 10]$ 等。而当物品较多、背包容量较大,尤其是相同重量的物品较多时,重叠子问题的数量将会大幅增多。 ![0-1 背包问题的暴力搜索递归树](knapsack_problem.assets/knapsack_dfs.png) ### 方法二:记忆化搜索 为了保证重叠子问题只被计算一次,我们借助记忆列表 `mem` 来记录子问题的解,其中 `mem[i][c]` 对应 $dp[i, c]$ 。 引入记忆化之后,**时间复杂度取决于子问题数量**,也就是 $O(n \times cap)$ 。实现代码如下: ```src [file]{knapsack}-[class]{}-[func]{knapsack_dfs_mem} ``` 下图展示了在记忆化搜索中被剪掉的搜索分支。 ![0-1 背包问题的记忆化搜索递归树](knapsack_problem.assets/knapsack_dfs_mem.png) ### 方法三:动态规划 动态规划实质上就是在状态转移中填充 $dp$ 表的过程,代码如下所示: ```src [file]{knapsack}-[class]{}-[func]{knapsack_dp} ``` 如下图所示,时间复杂度和空间复杂度都由数组 `dp` 大小决定,即 $O(n \times cap)$ 。 === "<1>" ![0-1 背包问题的动态规划过程](knapsack_problem.assets/knapsack_dp_step1.png) === "<2>" ![knapsack_dp_step2](knapsack_problem.assets/knapsack_dp_step2.png) === "<3>" ![knapsack_dp_step3](knapsack_problem.assets/knapsack_dp_step3.png) === "<4>" ![knapsack_dp_step4](knapsack_problem.assets/knapsack_dp_step4.png) === "<5>" ![knapsack_dp_step5](knapsack_problem.assets/knapsack_dp_step5.png) === "<6>" ![knapsack_dp_step6](knapsack_problem.assets/knapsack_dp_step6.png) === "<7>" ![knapsack_dp_step7](knapsack_problem.assets/knapsack_dp_step7.png) === "<8>" ![knapsack_dp_step8](knapsack_problem.assets/knapsack_dp_step8.png) === "<9>" ![knapsack_dp_step9](knapsack_problem.assets/knapsack_dp_step9.png) === "<10>" ![knapsack_dp_step10](knapsack_problem.assets/knapsack_dp_step10.png) === "<11>" ![knapsack_dp_step11](knapsack_problem.assets/knapsack_dp_step11.png) === "<12>" ![knapsack_dp_step12](knapsack_problem.assets/knapsack_dp_step12.png) === "<13>" ![knapsack_dp_step13](knapsack_problem.assets/knapsack_dp_step13.png) === "<14>" ![knapsack_dp_step14](knapsack_problem.assets/knapsack_dp_step14.png) ### 空间优化 由于每个状态都只与其上一行的状态有关,因此我们可以使用两个数组滚动前进,将空间复杂度从 $O(n^2)$ 降至 $O(n)$ 。 进一步思考,我们能否仅用一个数组实现空间优化呢?观察可知,每个状态都是由正上方或左上方的格子转移过来的。假设只有一个数组,当开始遍历第 $i$ 行时,该数组存储的仍然是第 $i-1$ 行的状态。 - 如果采取正序遍历,那么遍历到 $dp[i, j]$ 时,左上方 $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ 值可能已经被覆盖,此时就无法得到正确的状态转移结果。 - 如果采取倒序遍历,则不会发生覆盖问题,状态转移可以正确进行。 下图展示了在单个数组下从第 $i = 1$ 行转换至第 $i = 2$ 行的过程。请思考正序遍历和倒序遍历的区别。 === "<1>" ![0-1 背包的空间优化后的动态规划过程](knapsack_problem.assets/knapsack_dp_comp_step1.png) === "<2>" ![knapsack_dp_comp_step2](knapsack_problem.assets/knapsack_dp_comp_step2.png) === "<3>" ![knapsack_dp_comp_step3](knapsack_problem.assets/knapsack_dp_comp_step3.png) === "<4>" ![knapsack_dp_comp_step4](knapsack_problem.assets/knapsack_dp_comp_step4.png) === "<5>" ![knapsack_dp_comp_step5](knapsack_problem.assets/knapsack_dp_comp_step5.png) === "<6>" ![knapsack_dp_comp_step6](knapsack_problem.assets/knapsack_dp_comp_step6.png) 在代码实现中,我们仅需将数组 `dp` 的第一维 $i$ 直接删除,并且把内循环更改为倒序遍历即可: ```src [file]{knapsack}-[class]{}-[func]{knapsack_dp_comp} ``` ================================================ FILE: docs/chapter_dynamic_programming/summary.md ================================================ # 小结 ### 重点回顾 - 动态规划对问题进行分解,并通过存储子问题的解来规避重复计算,提高计算效率。 - 不考虑时间的前提下,所有动态规划问题都可以用回溯(暴力搜索)进行求解,但递归树中存在大量的重叠子问题,效率极低。通过引入记忆化列表,可以存储所有计算过的子问题的解,从而保证重叠子问题只被计算一次。 - 记忆化搜索是一种从顶至底的递归式解法,而与之对应的动态规划是一种从底至顶的递推式解法,其如同“填写表格”一样。由于当前状态仅依赖某些局部状态,因此我们可以消除 $dp$ 表的一个维度,从而降低空间复杂度。 - 子问题分解是一种通用的算法思路,在分治、动态规划、回溯中具有不同的性质。 - 动态规划问题有三大特性:重叠子问题、最优子结构、无后效性。 - 如果原问题的最优解可以从子问题的最优解构建得来,则它就具有最优子结构。 - 无后效性指对于一个状态,其未来发展只与该状态有关,而与过去经历的所有状态无关。许多组合优化问题不具有无后效性,无法使用动态规划快速求解。 **背包问题** - 背包问题是最典型的动态规划问题之一,具有 0-1 背包、完全背包、多重背包等变种。 - 0-1 背包的状态定义为前 $i$ 个物品在容量为 $c$ 的背包中的最大价值。根据不放入背包和放入背包两种决策,可得到最优子结构,并构建出状态转移方程。在空间优化中,由于每个状态依赖正上方和左上方的状态,因此需要倒序遍历列表,避免左上方状态被覆盖。 - 完全背包问题的每种物品的选取数量无限制,因此选择放入物品的状态转移与 0-1 背包问题不同。由于状态依赖正上方和正左方的状态,因此在空间优化中应当正序遍历。 - 零钱兑换问题是完全背包问题的一个变种。它从求“最大”价值变为求“最小”硬币数量,因此状态转移方程中的 $\max()$ 应改为 $\min()$ 。从追求“不超过”背包容量到追求“恰好”凑出目标金额,因此使用 $amt + 1$ 来表示“无法凑出目标金额”的无效解。 - 零钱兑换问题 II 从求“最少硬币数量”改为求“硬币组合数量”,状态转移方程相应地从 $\min()$ 改为求和运算符。 **编辑距离问题** - 编辑距离(Levenshtein 距离)用于衡量两个字符串之间的相似度,其定义为从一个字符串到另一个字符串的最少编辑步数,编辑操作包括添加、删除、替换。 - 编辑距离问题的状态定义为将 $s$ 的前 $i$ 个字符更改为 $t$ 的前 $j$ 个字符所需的最少编辑步数。当 $s[i] \ne t[j]$ 时,具有三种决策:添加、删除、替换,它们都有相应的剩余子问题。据此便可以找出最优子结构与构建状态转移方程。而当 $s[i] = t[j]$ 时,无须编辑当前字符。 - 在编辑距离中,状态依赖其正上方、正左方、左上方的状态,因此空间优化后正序或倒序遍历都无法正确地进行状态转移。为此,我们利用一个变量暂存左上方状态,从而转化到与完全背包问题等价的情况,可以在空间优化后进行正序遍历。 ================================================ FILE: docs/chapter_dynamic_programming/unbounded_knapsack_problem.md ================================================ # 完全背包问题 在本节中,我们先求解另一个常见的背包问题:完全背包,再了解它的一种特例:零钱兑换。 ## 完全背包问题 !!! question 给定 $n$ 个物品,第 $i$ 个物品的重量为 $wgt[i-1]$、价值为 $val[i-1]$ ,和一个容量为 $cap$ 的背包。**每个物品可以重复选取**,问在限定背包容量下能放入物品的最大价值。示例如下图所示。 ![完全背包问题的示例数据](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png) ### 动态规划思路 完全背包问题和 0-1 背包问题非常相似,**区别仅在于不限制物品的选择次数**。 - 在 0-1 背包问题中,每种物品只有一个,因此将物品 $i$ 放入背包后,只能从前 $i-1$ 个物品中选择。 - 在完全背包问题中,每种物品的数量是无限的,因此将物品 $i$ 放入背包后,**仍可以从前 $i$ 个物品中选择**。 在完全背包问题的规定下,状态 $[i, c]$ 的变化分为两种情况。 - **不放入物品 $i$** :与 0-1 背包问题相同,转移至 $[i-1, c]$ 。 - **放入物品 $i$** :与 0-1 背包问题不同,转移至 $[i, c-wgt[i-1]]$ 。 从而状态转移方程变为: $$ dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) $$ ### 代码实现 对比两道题目的代码,状态转移中有一处从 $i-1$ 变为 $i$ ,其余完全一致: ```src [file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp} ``` ### 空间优化 由于当前状态是从左边和上边的状态转移而来的,**因此空间优化后应该对 $dp$ 表中的每一行进行正序遍历**。 这个遍历顺序与 0-1 背包正好相反。请借助下图来理解两者的区别。 === "<1>" ![完全背包问题在空间优化后的动态规划过程](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png) === "<2>" ![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png) === "<3>" ![unbounded_knapsack_dp_comp_step3](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png) === "<4>" ![unbounded_knapsack_dp_comp_step4](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png) === "<5>" ![unbounded_knapsack_dp_comp_step5](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png) === "<6>" ![unbounded_knapsack_dp_comp_step6](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png) 代码实现比较简单,仅需将数组 `dp` 的第一维删除: ```src [file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp_comp} ``` ## 零钱兑换问题 背包问题是一大类动态规划问题的代表,其拥有很多变种,例如零钱兑换问题。 !!! question 给定 $n$ 种硬币,第 $i$ 种硬币的面值为 $coins[i - 1]$ ,目标金额为 $amt$ ,**每种硬币可以重复选取**,问能够凑出目标金额的最少硬币数量。如果无法凑出目标金额,则返回 $-1$ 。示例如下图所示。 ![零钱兑换问题的示例数据](unbounded_knapsack_problem.assets/coin_change_example.png) ### 动态规划思路 **零钱兑换可以看作完全背包问题的一种特殊情况**,两者具有以下联系与不同点。 - 两道题可以相互转换,“物品”对应“硬币”、“物品重量”对应“硬币面值”、“背包容量”对应“目标金额”。 - 优化目标相反,完全背包问题是要最大化物品价值,零钱兑换问题是要最小化硬币数量。 - 完全背包问题是求“不超过”背包容量下的解,零钱兑换是求“恰好”凑到目标金额的解。 **第一步:思考每轮的决策,定义状态,从而得到 $dp$ 表** 状态 $[i, a]$ 对应的子问题为:**前 $i$ 种硬币能够凑出金额 $a$ 的最少硬币数量**,记为 $dp[i, a]$ 。 二维 $dp$ 表的尺寸为 $(n+1) \times (amt+1)$ 。 **第二步:找出最优子结构,进而推导出状态转移方程** 本题与完全背包问题的状态转移方程存在以下两点差异。 - 本题要求最小值,因此需将运算符 $\max()$ 更改为 $\min()$ 。 - 优化主体是硬币数量而非商品价值,因此在选中硬币时执行 $+1$ 即可。 $$ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) $$ **第三步:确定边界条件和状态转移顺序** 当目标金额为 $0$ 时,凑出它的最少硬币数量为 $0$ ,即首列所有 $dp[i, 0]$ 都等于 $0$ 。 当无硬币时,**无法凑出任意 $> 0$ 的目标金额**,即是无效解。为使状态转移方程中的 $\min()$ 函数能够识别并过滤无效解,我们考虑使用 $+ \infty$ 来表示它们,即令首行所有 $dp[0, a]$ 都等于 $+ \infty$ 。 ### 代码实现 大多数编程语言并未提供 $+ \infty$ 变量,只能使用整型 `int` 的最大值来代替。而这又会导致大数越界:状态转移方程中的 $+ 1$ 操作可能发生溢出。 为此,我们采用数字 $amt + 1$ 来表示无效解,因为凑出 $amt$ 的硬币数量最多为 $amt$ 。最后返回前,判断 $dp[n, amt]$ 是否等于 $amt + 1$ ,若是则返回 $-1$ ,代表无法凑出目标金额。代码如下所示: ```src [file]{coin_change}-[class]{}-[func]{coin_change_dp} ``` 下图展示了零钱兑换的动态规划过程,和完全背包问题非常相似。 === "<1>" ![零钱兑换问题的动态规划过程](unbounded_knapsack_problem.assets/coin_change_dp_step1.png) === "<2>" ![coin_change_dp_step2](unbounded_knapsack_problem.assets/coin_change_dp_step2.png) === "<3>" ![coin_change_dp_step3](unbounded_knapsack_problem.assets/coin_change_dp_step3.png) === "<4>" ![coin_change_dp_step4](unbounded_knapsack_problem.assets/coin_change_dp_step4.png) === "<5>" ![coin_change_dp_step5](unbounded_knapsack_problem.assets/coin_change_dp_step5.png) === "<6>" ![coin_change_dp_step6](unbounded_knapsack_problem.assets/coin_change_dp_step6.png) === "<7>" ![coin_change_dp_step7](unbounded_knapsack_problem.assets/coin_change_dp_step7.png) === "<8>" ![coin_change_dp_step8](unbounded_knapsack_problem.assets/coin_change_dp_step8.png) === "<9>" ![coin_change_dp_step9](unbounded_knapsack_problem.assets/coin_change_dp_step9.png) === "<10>" ![coin_change_dp_step10](unbounded_knapsack_problem.assets/coin_change_dp_step10.png) === "<11>" ![coin_change_dp_step11](unbounded_knapsack_problem.assets/coin_change_dp_step11.png) === "<12>" ![coin_change_dp_step12](unbounded_knapsack_problem.assets/coin_change_dp_step12.png) === "<13>" ![coin_change_dp_step13](unbounded_knapsack_problem.assets/coin_change_dp_step13.png) === "<14>" ![coin_change_dp_step14](unbounded_knapsack_problem.assets/coin_change_dp_step14.png) === "<15>" ![coin_change_dp_step15](unbounded_knapsack_problem.assets/coin_change_dp_step15.png) ### 空间优化 零钱兑换的空间优化的处理方式和完全背包问题一致: ```src [file]{coin_change}-[class]{}-[func]{coin_change_dp_comp} ``` ## 零钱兑换问题 II !!! question 给定 $n$ 种硬币,第 $i$ 种硬币的面值为 $coins[i - 1]$ ,目标金额为 $amt$ ,每种硬币可以重复选取,**问凑出目标金额的硬币组合数量**。示例如下图所示。 ![零钱兑换问题 II 的示例数据](unbounded_knapsack_problem.assets/coin_change_ii_example.png) ### 动态规划思路 相比于上一题,本题目标是求组合数量,因此子问题变为:**前 $i$ 种硬币能够凑出金额 $a$ 的组合数量**。而 $dp$ 表仍然是尺寸为 $(n+1) \times (amt + 1)$ 的二维矩阵。 当前状态的组合数量等于不选当前硬币与选当前硬币这两种决策的组合数量之和。状态转移方程为: $$ dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] $$ 当目标金额为 $0$ 时,无须选择任何硬币即可凑出目标金额,因此应将首列所有 $dp[i, 0]$ 都初始化为 $1$ 。当无硬币时,无法凑出任何 $>0$ 的目标金额,因此首行所有 $dp[0, a]$ 都等于 $0$ 。 ### 代码实现 ```src [file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp} ``` ### 空间优化 空间优化处理方式相同,删除硬币维度即可: ```src [file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp_comp} ``` ================================================ FILE: docs/chapter_graph/graph.md ================================================ # 图 图(graph)是一种非线性数据结构,由顶点(vertex)边(edge)组成。我们可以将图 $G$ 抽象地表示为一组顶点 $V$ 和一组边 $E$ 的集合。以下示例展示了一个包含 5 个顶点和 7 条边的图。 $$ \begin{aligned} V & = \{ 1, 2, 3, 4, 5 \} \newline E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline G & = \{ V, E \} \newline \end{aligned} $$ 如果将顶点看作节点,将边看作连接各个节点的引用(指针),我们就可以将图看作一种从链表拓展而来的数据结构。如下图所示,**相较于线性关系(链表)和分治关系(树),网络关系(图)的自由度更高**,因而更为复杂。 ![链表、树、图之间的关系](graph.assets/linkedlist_tree_graph.png) ## 图的常见类型与术语 根据边是否具有方向,可分为无向图(undirected graph)有向图(directed graph),如下图所示。 - 在无向图中,边表示两顶点之间的“双向”连接关系,例如微信或 QQ 中的“好友关系”。 - 在有向图中,边具有方向性,即 $A \rightarrow B$ 和 $A \leftarrow B$ 两个方向的边是相互独立的,例如微博或抖音上的“关注”与“被关注”关系。 ![有向图与无向图](graph.assets/directed_graph.png) 根据所有顶点是否连通,可分为连通图(connected graph)非连通图(disconnected graph),如下图所示。 - 对于连通图,从某个顶点出发,可以到达其余任意顶点。 - 对于非连通图,从某个顶点出发,至少有一个顶点无法到达。 ![连通图与非连通图](graph.assets/connected_graph.png) 我们还可以为边添加“权重”变量,从而得到如下图所示的有权图(weighted graph)。例如在《王者荣耀》等手游中,系统会根据共同游戏时间来计算玩家之间的“亲密度”,这种亲密度网络就可以用有权图来表示。 ![有权图与无权图](graph.assets/weighted_graph.png) 图数据结构包含以下常用术语。 - 邻接(adjacency):当两顶点之间存在边相连时,称这两顶点“邻接”。在上图中,顶点 1 的邻接顶点为顶点 2、3、5。 - 路径(path):从顶点 A 到顶点 B 经过的边构成的序列被称为从 A 到 B 的“路径”。在上图中,边序列 1-5-2-4 是顶点 1 到顶点 4 的一条路径。 - 度(degree):一个顶点拥有的边数。对于有向图,入度(in-degree)表示有多少条边指向该顶点,出度(out-degree)表示有多少条边从该顶点指出。 ## 图的表示 图的常用表示方式包括“邻接矩阵”和“邻接表”。以下使用无向图进行举例。 ### 邻接矩阵 设图的顶点数量为 $n$ ,邻接矩阵(adjacency matrix)使用一个 $n \times n$ 大小的矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,用 $1$ 或 $0$ 表示两个顶点之间是否存在边。 如下图所示,设邻接矩阵为 $M$、顶点列表为 $V$ ,那么矩阵元素 $M[i, j] = 1$ 表示顶点 $V[i]$ 到顶点 $V[j]$ 之间存在边,反之 $M[i, j] = 0$ 表示两顶点之间无边。 ![图的邻接矩阵表示](graph.assets/adjacency_matrix.png) 邻接矩阵具有以下特性。 - 在简单图中,顶点不能与自身相连,此时邻接矩阵主对角线元素没有意义。 - 对于无向图,两个方向的边等价,此时邻接矩阵关于主对角线对称。 - 将邻接矩阵的元素从 $1$ 和 $0$ 替换为权重,则可表示有权图。 使用邻接矩阵表示图时,我们可以直接访问矩阵元素以获取边,因此增删查改操作的效率很高,时间复杂度均为 $O(1)$ 。然而,矩阵的空间复杂度为 $O(n^2)$ ,内存占用较多。 ### 邻接表 邻接表(adjacency list)使用 $n$ 个链表来表示图,链表节点表示顶点。第 $i$ 个链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点(与该顶点相连的顶点)。下图展示了一个使用邻接表存储的图的示例。 ![图的邻接表表示](graph.assets/adjacency_list.png) 邻接表仅存储实际存在的边,而边的总数通常远小于 $n^2$ ,因此它更加节省空间。然而,在邻接表中需要通过遍历链表来查找边,因此其时间效率不如邻接矩阵。 观察上图,**邻接表结构与哈希表中的“链式地址”非常相似,因此我们也可以采用类似的方法来优化效率**。比如当链表较长时,可以将链表转化为 AVL 树或红黑树,从而将时间效率从 $O(n)$ 优化至 $O(\log n)$ ;还可以把链表转换为哈希表,从而将时间复杂度降至 $O(1)$ 。 ## 图的常见应用 如下表所示,许多现实系统可以用图来建模,相应的问题也可以约化为图计算问题。

  现实生活中常见的图

| | 顶点 | 边 | 图计算问题 | | -------- | ---- | -------------------- | ------------ | | 社交网络 | 用户 | 好友关系 | 潜在好友推荐 | | 地铁线路 | 站点 | 站点间的连通性 | 最短路线推荐 | | 太阳系 | 星体 | 星体间的万有引力作用 | 行星轨道计算 | ================================================ FILE: docs/chapter_graph/graph_operations.md ================================================ # 图的基础操作 图的基础操作可分为对“边”的操作和对“顶点”的操作。在“邻接矩阵”和“邻接表”两种表示方法下,实现方式有所不同。 ## 基于邻接矩阵的实现 给定一个顶点数量为 $n$ 的无向图,则各种操作的实现方式如下图所示。 - **添加或删除边**:直接在邻接矩阵中修改指定的边即可,使用 $O(1)$ 时间。而由于是无向图,因此需要同时更新两个方向的边。 - **添加顶点**:在邻接矩阵的尾部添加一行一列,并全部填 $0$ 即可,使用 $O(n)$ 时间。 - **删除顶点**:在邻接矩阵中删除一行一列。当删除首行首列时达到最差情况,需要将 $(n-1)^2$ 个元素“向左上移动”,从而使用 $O(n^2)$ 时间。 - **初始化**:传入 $n$ 个顶点,初始化长度为 $n$ 的顶点列表 `vertices` ,使用 $O(n)$ 时间;初始化 $n \times n$ 大小的邻接矩阵 `adjMat` ,使用 $O(n^2)$ 时间。 === "<1>" ![邻接矩阵的初始化、增删边、增删顶点](graph_operations.assets/adjacency_matrix_step1_initialization.png) === "<2>" ![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png) === "<3>" ![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png) === "<4>" ![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png) === "<5>" ![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png) 以下是基于邻接矩阵表示图的实现代码: ```src [file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{} ``` ## 基于邻接表的实现 设无向图的顶点总数为 $n$、边总数为 $m$ ,则可根据下图所示的方法实现各种操作。 - **添加边**:在顶点对应链表的末尾添加边即可,使用 $O(1)$ 时间。因为是无向图,所以需要同时添加两个方向的边。 - **删除边**:在顶点对应链表中查找并删除指定边,使用 $O(m)$ 时间。在无向图中,需要同时删除两个方向的边。 - **添加顶点**:在邻接表中添加一个链表,并将新增顶点作为链表头节点,使用 $O(1)$ 时间。 - **删除顶点**:需遍历整个邻接表,删除包含指定顶点的所有边,使用 $O(n + m)$ 时间。 - **初始化**:在邻接表中创建 $n$ 个顶点和 $2m$ 条边,使用 $O(n + m)$ 时间。 === "<1>" ![邻接表的初始化、增删边、增删顶点](graph_operations.assets/adjacency_list_step1_initialization.png) === "<2>" ![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png) === "<3>" ![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png) === "<4>" ![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png) === "<5>" ![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png) 以下是邻接表的代码实现。对比上图,实际代码有以下不同。 - 为了方便添加与删除顶点,以及简化代码,我们使用列表(动态数组)来代替链表。 - 使用哈希表来存储邻接表,`key` 为顶点实例,`value` 为该顶点的邻接顶点列表(链表)。 另外,我们在邻接表中使用 `Vertex` 类来表示顶点,这样做的原因是:如果与邻接矩阵一样,用列表索引来区分不同顶点,那么假设要删除索引为 $i$ 的顶点,则需遍历整个邻接表,将所有大于 $i$ 的索引全部减 $1$ ,效率很低。而如果每个顶点都是唯一的 `Vertex` 实例,删除某一顶点之后就无须改动其他顶点了。 ```src [file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{} ``` ## 效率对比 设图中共有 $n$ 个顶点和 $m$ 条边,下表对比了邻接矩阵和邻接表的时间效率和空间效率。请注意,邻接表(链表)对应本文实现,而邻接表(哈希表)专指将所有链表替换为哈希表后的实现。

  邻接矩阵与邻接表对比

| | 邻接矩阵 | 邻接表(链表) | 邻接表(哈希表) | | ------------ | -------- | -------------- | ---------------- | | 判断是否邻接 | $O(1)$ | $O(n)$ | $O(1)$ | | 添加边 | $O(1)$ | $O(1)$ | $O(1)$ | | 删除边 | $O(1)$ | $O(n)$ | $O(1)$ | | 添加顶点 | $O(n)$ | $O(1)$ | $O(1)$ | | 删除顶点 | $O(n^2)$ | $O(n + m)$ | $O(n)$ | | 内存空间占用 | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | 观察上表,似乎邻接表(哈希表)的时间效率与空间效率最优。但实际上,在邻接矩阵中操作边的效率更高,只需一次数组访问或赋值操作即可。综合来看,邻接矩阵体现了“以空间换时间”的原则,而邻接表体现了“以时间换空间”的原则。 ================================================ FILE: docs/chapter_graph/graph_traversal.md ================================================ # 图的遍历 树代表的是“一对多”的关系,而图则具有更高的自由度,可以表示任意的“多对多”关系。因此,我们可以把树看作图的一种特例。显然,**树的遍历操作也是图的遍历操作的一种特例**。 图和树都需要应用搜索算法来实现遍历操作。图的遍历方式也可分为两种:广度优先遍历深度优先遍历。 ## 广度优先遍历 **广度优先遍历是一种由近及远的遍历方式,从某个节点出发,始终优先访问距离最近的顶点,并一层层向外扩张**。如下图所示,从左上角顶点出发,首先遍历该顶点的所有邻接顶点,然后遍历下一个顶点的所有邻接顶点,以此类推,直至所有顶点访问完毕。 ![图的广度优先遍历](graph_traversal.assets/graph_bfs.png) ### 算法实现 BFS 通常借助队列来实现,代码如下所示。队列具有“先入先出”的性质,这与 BFS 的“由近及远”的思想异曲同工。 1. 将遍历起始顶点 `startVet` 加入队列,并开启循环。 2. 在循环的每轮迭代中,弹出队首顶点并记录访问,然后将该顶点的所有邻接顶点加入到队列尾部。 3. 循环步骤 `2.` ,直到所有顶点被访问完毕后结束。 为了防止重复遍历顶点,我们需要借助一个哈希集合 `visited` 来记录哪些节点已被访问。 !!! tip 哈希集合可以看作一个只存储 `key` 而不存储 `value` 的哈希表,它可以在 $O(1)$ 时间复杂度下进行 `key` 的增删查改操作。根据 `key` 的唯一性,哈希集合通常用于数据去重等场景。 ```src [file]{graph_bfs}-[class]{}-[func]{graph_bfs} ``` 代码相对抽象,建议对照下图来加深理解。 === "<1>" ![图的广度优先遍历步骤](graph_traversal.assets/graph_bfs_step1.png) === "<2>" ![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png) === "<3>" ![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png) === "<4>" ![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png) === "<5>" ![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png) === "<6>" ![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png) === "<7>" ![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png) === "<8>" ![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png) === "<9>" ![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png) === "<10>" ![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png) === "<11>" ![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png) !!! question "广度优先遍历的序列是否唯一?" 不唯一。广度优先遍历只要求按“由近及远”的顺序遍历,**而多个相同距离的顶点的遍历顺序允许被任意打乱**。以上图为例,顶点 $1$、$3$ 的访问顺序可以交换,顶点 $2$、$4$、$6$ 的访问顺序也可以任意交换。 ### 复杂度分析 **时间复杂度**:所有顶点都会入队并出队一次,使用 $O(|V|)$ 时间;在遍历邻接顶点的过程中,由于是无向图,因此所有边都会被访问 $2$ 次,使用 $O(2|E|)$ 时间;总体使用 $O(|V| + |E|)$ 时间。 **空间复杂度**:列表 `res` ,哈希集合 `visited` ,队列 `que` 中的顶点数量最多为 $|V|$ ,使用 $O(|V|)$ 空间。 ## 深度优先遍历 **深度优先遍历是一种优先走到底、无路可走再回头的遍历方式**。如下图所示,从左上角顶点出发,访问当前顶点的某个邻接顶点,直到走到尽头时返回,再继续走到尽头并返回,以此类推,直至所有顶点遍历完成。 ![图的深度优先遍历](graph_traversal.assets/graph_dfs.png) ### 算法实现 这种“走到尽头再返回”的算法范式通常基于递归来实现。与广度优先遍历类似,在深度优先遍历中,我们也需要借助一个哈希集合 `visited` 来记录已被访问的顶点,以避免重复访问顶点。 ```src [file]{graph_dfs}-[class]{}-[func]{graph_dfs} ``` 深度优先遍历的算法流程如下图所示。 - **直虚线代表向下递推**,表示开启了一个新的递归方法来访问新顶点。 - **曲虚线代表向上回溯**,表示此递归方法已经返回,回溯到了开启此方法的位置。 为了加深理解,建议将下图与代码结合起来,在脑中模拟(或者用笔画下来)整个 DFS 过程,包括每个递归方法何时开启、何时返回。 === "<1>" ![图的深度优先遍历步骤](graph_traversal.assets/graph_dfs_step1.png) === "<2>" ![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png) === "<3>" ![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png) === "<4>" ![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png) === "<5>" ![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png) === "<6>" ![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png) === "<7>" ![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png) === "<8>" ![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png) === "<9>" ![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png) === "<10>" ![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png) === "<11>" ![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png) !!! question "深度优先遍历的序列是否唯一?" 与广度优先遍历类似,深度优先遍历序列的顺序也不是唯一的。给定某顶点,先往哪个方向探索都可以,即邻接顶点的顺序可以任意打乱,都是深度优先遍历。 以树的遍历为例,“根 $\rightarrow$ 左 $\rightarrow$ 右”“左 $\rightarrow$ 根 $\rightarrow$ 右”“左 $\rightarrow$ 右 $\rightarrow$ 根”分别对应前序、中序、后序遍历,它们展示了三种遍历优先级,然而这三者都属于深度优先遍历。 ### 复杂度分析 **时间复杂度**:所有顶点都会被访问 $1$ 次,使用 $O(|V|)$ 时间;所有边都会被访问 $2$ 次,使用 $O(2|E|)$ 时间;总体使用 $O(|V| + |E|)$ 时间。 **空间复杂度**:列表 `res` ,哈希集合 `visited` 顶点数量最多为 $|V|$ ,递归深度最大为 $|V|$ ,因此使用 $O(|V|)$ 空间。 ================================================ FILE: docs/chapter_graph/index.md ================================================ # 图 ![图](../assets/covers/chapter_graph.jpg) !!! abstract 在生命旅途中,我们就像是一个个节点,被无数看不见的边相连。 每一次的相识与相离,都在这张巨大的网络图中留下独特的印记。 ================================================ FILE: docs/chapter_graph/summary.md ================================================ # 小结 ### 重点回顾 - 图由顶点和边组成,可以表示为一组顶点和一组边构成的集合。 - 相较于线性关系(链表)和分治关系(树),网络关系(图)具有更高的自由度,因而更为复杂。 - 有向图的边具有方向性,连通图中的任意顶点均可达,有权图的每条边都包含权重变量。 - 邻接矩阵利用矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,用 $1$ 或 $0$ 表示两个顶点之间有边或无边。邻接矩阵在增删查改操作上效率很高,但空间占用较多。 - 邻接表使用多个链表来表示图,第 $i$ 个链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点。邻接表相对于邻接矩阵更加节省空间,但由于需要遍历链表来查找边,因此时间效率较低。 - 当邻接表中的链表过长时,可以将其转换为红黑树或哈希表,从而提升查询效率。 - 从算法思想的角度分析,邻接矩阵体现了“以空间换时间”,邻接表体现了“以时间换空间”。 - 图可用于建模各类现实系统,如社交网络、地铁线路等。 - 树是图的一种特例,树的遍历也是图的遍历的一种特例。 - 图的广度优先遍历是一种由近及远、层层扩张的搜索方式,通常借助队列实现。 - 图的深度优先遍历是一种优先走到底、无路可走时再回溯的搜索方式,常基于递归来实现。 ### Q & A **Q**:路径的定义是顶点序列还是边序列? 维基百科上不同语言版本的定义不一致:英文版是“路径是一个边序列”,而中文版是“路径是一个顶点序列”。以下是英文版原文:In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices. 在本文中,路径被视为一个边序列,而不是一个顶点序列。这是因为两个顶点之间可能存在多条边连接,此时每条边都对应一条路径。 **Q**:非连通图中是否会有无法遍历到的点? 在非连通图中,从某个顶点出发,至少有一个顶点无法到达。遍历非连通图需要设置多个起点,以遍历到图的所有连通分量。 **Q**:在邻接表中,“与该顶点相连的所有顶点”的顶点顺序是否有要求? 可以是任意顺序。但在实际应用中,可能需要按照指定规则来排序,比如按照顶点添加的次序,或者按照顶点值大小的顺序等,这样有助于快速查找“带有某种极值”的顶点。 ================================================ FILE: docs/chapter_greedy/fractional_knapsack_problem.md ================================================ # 分数背包问题 !!! question 给定 $n$ 个物品,第 $i$ 个物品的重量为 $wgt[i-1]$、价值为 $val[i-1]$ ,和一个容量为 $cap$ 的背包。每个物品只能选择一次,**但可以选择物品的一部分,价值根据选择的重量比例计算**,问在限定背包容量下背包中物品的最大价值。示例如下图所示。 ![分数背包问题的示例数据](fractional_knapsack_problem.assets/fractional_knapsack_example.png) 分数背包问题和 0-1 背包问题整体上非常相似,状态包含当前物品 $i$ 和容量 $c$ ,目标是求限定背包容量下的最大价值。 不同点在于,本题允许只选择物品的一部分。如下图所示,**我们可以对物品任意地进行切分,并按照重量比例来计算相应价值**。 1. 对于物品 $i$ ,它在单位重量下的价值为 $val[i-1] / wgt[i-1]$ ,简称单位价值。 2. 假设放入一部分物品 $i$ ,重量为 $w$ ,则背包增加的价值为 $w \times val[i-1] / wgt[i-1]$ 。 ![物品在单位重量下的价值](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png) ### 贪心策略确定 最大化背包内物品总价值,**本质上是最大化单位重量下的物品价值**。由此便可推理出下图所示的贪心策略。 1. 将物品按照单位价值从高到低进行排序。 2. 遍历所有物品,**每轮贪心地选择单位价值最高的物品**。 3. 若剩余背包容量不足,则使用当前物品的一部分填满背包。 ![分数背包问题的贪心策略](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png) ### 代码实现 我们建立了一个物品类 `Item` ,以便将物品按照单位价值进行排序。循环进行贪心选择,当背包已满时跳出并返回解: ```src [file]{fractional_knapsack}-[class]{}-[func]{fractional_knapsack} ``` 内置排序算法的时间复杂度通常为 $O(\log n)$ ,空间复杂度通常为 $O(\log n)$ 或 $O(n)$ ,取决于编程语言的具体实现。 除排序之外,在最差情况下,需要遍历整个物品列表,**因此时间复杂度为 $O(n)$** ,其中 $n$ 为物品数量。 由于初始化了一个 `Item` 对象列表,**因此空间复杂度为 $O(n)$** 。 ### 正确性证明 采用反证法。假设物品 $x$ 是单位价值最高的物品,使用某算法求得最大价值为 `res` ,但该解中不包含物品 $x$ 。 现在从背包中拿出单位重量的任意物品,并替换为单位重量的物品 $x$ 。由于物品 $x$ 的单位价值最高,因此替换后的总价值一定大于 `res` 。**这与 `res` 是最优解矛盾,说明最优解中必须包含物品 $x$** 。 对于该解中的其他物品,我们也可以构建出上述矛盾。总而言之,**单位价值更大的物品总是更优选择**,这说明贪心策略是有效的。 如下图所示,如果将物品重量和物品单位价值分别看作一张二维图表的横轴和纵轴,则分数背包问题可转化为“求在有限横轴区间下围成的最大面积”。这个类比可以帮助我们从几何角度理解贪心策略的有效性。 ![分数背包问题的几何表示](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png) ================================================ FILE: docs/chapter_greedy/greedy_algorithm.md ================================================ # 贪心算法 贪心算法(greedy algorithm)是一种常见的解决优化问题的算法,其基本思想是在问题的每个决策阶段,都选择当前看起来最优的选择,即贪心地做出局部最优的决策,以期获得全局最优解。贪心算法简洁且高效,在许多实际问题中有着广泛的应用。 贪心算法和动态规划都常用于解决优化问题。它们之间存在一些相似之处,比如都依赖最优子结构性质,但工作原理不同。 - 动态规划会根据之前阶段的所有决策来考虑当前决策,并使用过去子问题的解来构建当前子问题的解。 - 贪心算法不会考虑过去的决策,而是一路向前地进行贪心选择,不断缩小问题范围,直至问题被解决。 我们先通过例题“零钱兑换”了解贪心算法的工作原理。这道题已经在“完全背包问题”章节中介绍过,相信你对它并不陌生。 !!! question 给定 $n$ 种硬币,第 $i$ 种硬币的面值为 $coins[i - 1]$ ,目标金额为 $amt$ ,每种硬币可以重复选取,问能够凑出目标金额的最少硬币数量。如果无法凑出目标金额,则返回 $-1$ 。 本题采取的贪心策略如下图所示。给定目标金额,**我们贪心地选择不大于且最接近它的硬币**,不断循环该步骤,直至凑出目标金额为止。 ![零钱兑换的贪心策略](greedy_algorithm.assets/coin_change_greedy_strategy.png) 实现代码如下所示: ```src [file]{coin_change_greedy}-[class]{}-[func]{coin_change_greedy} ``` 你可能会不由地发出感叹:So clean !贪心算法仅用约十行代码就解决了零钱兑换问题。 ## 贪心算法的优点与局限性 **贪心算法不仅操作直接、实现简单,而且通常效率也很高**。在以上代码中,记硬币最小面值为 $\min(coins)$ ,则贪心选择最多循环 $amt / \min(coins)$ 次,时间复杂度为 $O(amt / \min(coins))$ 。这比动态规划解法的时间复杂度 $O(n \times amt)$ 小了一个数量级。 然而,**对于某些硬币面值组合,贪心算法并不能找到最优解**。下图给出了两个示例。 - **正例 $coins = [1, 5, 10, 20, 50, 100]$**:在该硬币组合下,给定任意 $amt$ ,贪心算法都可以找到最优解。 - **反例 $coins = [1, 20, 50]$**:假设 $amt = 60$ ,贪心算法只能找到 $50 + 1 \times 10$ 的兑换组合,共计 $11$ 枚硬币,但动态规划可以找到最优解 $20 + 20 + 20$ ,仅需 $3$ 枚硬币。 - **反例 $coins = [1, 49, 50]$**:假设 $amt = 98$ ,贪心算法只能找到 $50 + 1 \times 48$ 的兑换组合,共计 $49$ 枚硬币,但动态规划可以找到最优解 $49 + 49$ ,仅需 $2$ 枚硬币。 ![贪心算法无法找出最优解的示例](greedy_algorithm.assets/coin_change_greedy_vs_dp.png) 也就是说,对于零钱兑换问题,贪心算法无法保证找到全局最优解,并且有可能找到非常差的解。它更适合用动态规划解决。 一般情况下,贪心算法的适用情况分以下两种。 1. **可以保证找到最优解**:贪心算法在这种情况下往往是最优选择,因为它往往比回溯、动态规划更高效。 2. **可以找到近似最优解**:贪心算法在这种情况下也是可用的。对于很多复杂问题来说,寻找全局最优解非常困难,能以较高效率找到次优解也是非常不错的。 ## 贪心算法特性 那么问题来了,什么样的问题适合用贪心算法求解呢?或者说,贪心算法在什么情况下可以保证找到最优解? 相较于动态规划,贪心算法的使用条件更加苛刻,其主要关注问题的两个性质。 - **贪心选择性质**:只有当局部最优选择始终可以导致全局最优解时,贪心算法才能保证得到最优解。 - **最优子结构**:原问题的最优解包含子问题的最优解。 最优子结构已经在“动态规划”章节中介绍过,这里不再赘述。值得注意的是,一些问题的最优子结构并不明显,但仍然可使用贪心算法解决。 我们主要探究贪心选择性质的判断方法。虽然它的描述看上去比较简单,**但实际上对于许多问题,证明贪心选择性质并非易事**。 例如零钱兑换问题,我们虽然能够容易地举出反例,对贪心选择性质进行证伪,但证实的难度较大。如果问:**满足什么条件的硬币组合可以使用贪心算法求解**?我们往往只能凭借直觉或举例子来给出一个模棱两可的答案,而难以给出严谨的数学证明。 !!! quote 有一篇论文给出了一个 $O(n^3)$ 时间复杂度的算法,用于判断一个硬币组合能否使用贪心算法找出任意金额的最优解。 Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234. ## 贪心算法解题步骤 贪心问题的解决流程大体可分为以下三步。 1. **问题分析**:梳理与理解问题特性,包括状态定义、优化目标和约束条件等。这一步在回溯和动态规划中都有涉及。 2. **确定贪心策略**:确定如何在每一步中做出贪心选择。这个策略能够在每一步减小问题的规模,并最终解决整个问题。 3. **正确性证明**:通常需要证明问题具有贪心选择性质和最优子结构。这个步骤可能需要用到数学证明,例如归纳法或反证法等。 确定贪心策略是求解问题的核心步骤,但实施起来可能并不容易,主要有以下原因。 - **不同问题的贪心策略的差异较大**。对于许多问题来说,贪心策略比较浅显,我们通过一些大概的思考与尝试就能得出。而对于一些复杂问题,贪心策略可能非常隐蔽,这种情况就非常考验个人的解题经验与算法能力了。 - **某些贪心策略具有较强的迷惑性**。当我们满怀信心设计好贪心策略,写出解题代码并提交运行,很可能发现部分测试样例无法通过。这是因为设计的贪心策略只是“部分正确”的,上文介绍的零钱兑换就是一个典型案例。 为了保证正确性,我们应该对贪心策略进行严谨的数学证明,**通常需要用到反证法或数学归纳法**。 然而,正确性证明也很可能不是一件易事。如若没有头绪,我们通常会选择面向测试用例进行代码调试,一步步修改与验证贪心策略。 ## 贪心算法典型例题 贪心算法常常应用在满足贪心选择性质和最优子结构的优化问题中,以下列举了一些典型的贪心算法问题。 - **硬币找零问题**:在某些硬币组合下,贪心算法总是可以得到最优解。 - **区间调度问题**:假设你有一些任务,每个任务在一段时间内进行,你的目标是完成尽可能多的任务。如果每次都选择结束时间最早的任务,那么贪心算法就可以得到最优解。 - **分数背包问题**:给定一组物品和一个载重量,你的目标是选择一组物品,使得总重量不超过载重量,且总价值最大。如果每次都选择性价比最高(价值 / 重量)的物品,那么贪心算法在一些情况下可以得到最优解。 - **股票买卖问题**:给定一组股票的历史价格,你可以进行多次买卖,但如果你已经持有股票,那么在卖出之前不能再买,目标是获取最大利润。 - **霍夫曼编码**:霍夫曼编码是一种用于无损数据压缩的贪心算法。通过构建霍夫曼树,每次选择出现频率最低的两个节点合并,最后得到的霍夫曼树的带权路径长度(编码长度)最小。 - **Dijkstra 算法**:它是一种解决给定源顶点到其余各顶点的最短路径问题的贪心算法。 ================================================ FILE: docs/chapter_greedy/index.md ================================================ # 贪心 ![贪心](../assets/covers/chapter_greedy.jpg) !!! abstract 向日葵朝着太阳转动,时刻追求自身成长的最大可能。 贪心策略在一轮轮的简单选择中,逐步导向最佳答案。 ================================================ FILE: docs/chapter_greedy/max_capacity_problem.md ================================================ # 最大容量问题 !!! question 输入一个数组 $ht$ ,其中的每个元素代表一个垂直隔板的高度。数组中的任意两个隔板,以及它们之间的空间可以组成一个容器。 容器的容量等于高度和宽度的乘积(面积),其中高度由较短的隔板决定,宽度是两个隔板的数组索引之差。 请在数组中选择两个隔板,使得组成的容器的容量最大,返回最大容量。示例如下图所示。 ![最大容量问题的示例数据](max_capacity_problem.assets/max_capacity_example.png) 容器由任意两个隔板围成,**因此本题的状态为两个隔板的索引,记为 $[i, j]$** 。 根据题意,容量等于高度乘以宽度,其中高度由短板决定,宽度是两隔板的数组索引之差。设容量为 $cap[i, j]$ ,则可得计算公式: $$ cap[i, j] = \min(ht[i], ht[j]) \times (j - i) $$ 设数组长度为 $n$ ,两个隔板的组合数量(状态总数)为 $C_n^2 = \frac{n(n - 1)}{2}$ 个。最直接地,**我们可以穷举所有状态**,从而求得最大容量,时间复杂度为 $O(n^2)$ 。 ### 贪心策略确定 这道题还有更高效率的解法。如下图所示,现选取一个状态 $[i, j]$ ,其满足索引 $i < j$ 且高度 $ht[i] < ht[j]$ ,即 $i$ 为短板、$j$ 为长板。 ![初始状态](max_capacity_problem.assets/max_capacity_initial_state.png) 如下图所示,**若此时将长板 $j$ 向短板 $i$ 靠近,则容量一定变小**。 这是因为在移动长板 $j$ 后,宽度 $j-i$ 肯定变小;而高度由短板决定,因此高度只可能不变( $i$ 仍为短板)或变小(移动后的 $j$ 成为短板)。 ![向内移动长板后的状态](max_capacity_problem.assets/max_capacity_moving_long_board.png) 反向思考,**我们只有向内收缩短板 $i$ ,才有可能使容量变大**。因为虽然宽度一定变小,**但高度可能会变大**(移动后的短板 $i$ 可能会变长)。例如在下图中,移动短板后面积变大。 ![向内移动短板后的状态](max_capacity_problem.assets/max_capacity_moving_short_board.png) 由此便可推出本题的贪心策略:初始化两指针,使其分列容器两端,每轮向内收缩短板对应的指针,直至两指针相遇。 下图展示了贪心策略的执行过程。 1. 初始状态下,指针 $i$ 和 $j$ 分列数组两端。 2. 计算当前状态的容量 $cap[i, j]$ ,并更新最大容量。 3. 比较板 $i$ 和板 $j$ 的高度,并将短板向内移动一格。 4. 循环执行第 `2.` 步和第 `3.` 步,直至 $i$ 和 $j$ 相遇时结束。 === "<1>" ![最大容量问题的贪心过程](max_capacity_problem.assets/max_capacity_greedy_step1.png) === "<2>" ![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png) === "<3>" ![max_capacity_greedy_step3](max_capacity_problem.assets/max_capacity_greedy_step3.png) === "<4>" ![max_capacity_greedy_step4](max_capacity_problem.assets/max_capacity_greedy_step4.png) === "<5>" ![max_capacity_greedy_step5](max_capacity_problem.assets/max_capacity_greedy_step5.png) === "<6>" ![max_capacity_greedy_step6](max_capacity_problem.assets/max_capacity_greedy_step6.png) === "<7>" ![max_capacity_greedy_step7](max_capacity_problem.assets/max_capacity_greedy_step7.png) === "<8>" ![max_capacity_greedy_step8](max_capacity_problem.assets/max_capacity_greedy_step8.png) === "<9>" ![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png) ### 代码实现 代码循环最多 $n$ 轮,**因此时间复杂度为 $O(n)$** 。 变量 $i$、$j$、$res$ 使用常数大小的额外空间,**因此空间复杂度为 $O(1)$** 。 ```src [file]{max_capacity}-[class]{}-[func]{max_capacity} ``` ### 正确性证明 之所以贪心比穷举更快,是因为每轮的贪心选择都会“跳过”一些状态。 比如在状态 $cap[i, j]$ 下,$i$ 为短板、$j$ 为长板。若贪心地将短板 $i$ 向内移动一格,会导致下图所示的状态被“跳过”。**这意味着之后无法验证这些状态的容量大小**。 $$ cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1] $$ ![移动短板导致被跳过的状态](max_capacity_problem.assets/max_capacity_skipped_states.png) 观察发现,**这些被跳过的状态实际上就是将长板 $j$ 向内移动的所有状态**。前面我们已经证明内移长板一定会导致容量变小。也就是说,被跳过的状态都不可能是最优解,**跳过它们不会导致错过最优解**。 以上分析说明,移动短板的操作是“安全”的,贪心策略是有效的。 ================================================ FILE: docs/chapter_greedy/max_product_cutting_problem.md ================================================ # 最大切分乘积问题 !!! question 给定一个正整数 $n$ ,将其切分为至少两个正整数的和,求切分后所有整数的乘积最大是多少,如下图所示。 ![最大切分乘积的问题定义](max_product_cutting_problem.assets/max_product_cutting_definition.png) 假设我们将 $n$ 切分为 $m$ 个整数因子,其中第 $i$ 个因子记为 $n_i$ ,即 $$ n = \sum_{i=1}^{m}n_i $$ 本题的目标是求得所有整数因子的最大乘积,即 $$ \max(\prod_{i=1}^{m}n_i) $$ 我们需要思考的是:切分数量 $m$ 应该多大,每个 $n_i$ 应该是多少? ### 贪心策略确定 根据经验,两个整数的乘积往往比它们的加和更大。假设从 $n$ 中分出一个因子 $2$ ,则它们的乘积为 $2(n-2)$ 。我们将该乘积与 $n$ 作比较: $$ \begin{aligned} 2(n-2) & \geq n \newline 2n - n - 4 & \geq 0 \newline n & \geq 4 \end{aligned} $$ 如下图所示,当 $n \geq 4$ 时,切分出一个 $2$ 后乘积会变大,**这说明大于等于 $4$ 的整数都应该被切分**。 **贪心策略一**:如果切分方案中包含 $\geq 4$ 的因子,那么它就应该被继续切分。最终的切分方案只应出现 $1$、$2$、$3$ 这三种因子。 ![切分导致乘积变大](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png) 接下来思考哪个因子是最优的。在 $1$、$2$、$3$ 这三个因子中,显然 $1$ 是最差的,因为 $1 \times (n-1) < n$ 恒成立,即切分出 $1$ 反而会导致乘积减小。 如下图所示,当 $n = 6$ 时,有 $3 \times 3 > 2 \times 2 \times 2$ 。**这意味着切分出 $3$ 比切分出 $2$ 更优**。 **贪心策略二**:在切分方案中,最多只应存在两个 $2$ 。因为三个 $2$ 总是可以替换为两个 $3$ ,从而获得更大的乘积。 ![最优切分因子](max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png) 综上所述,可推理出以下贪心策略。 1. 输入整数 $n$ ,从其不断地切分出因子 $3$ ,直至余数为 $0$、$1$、$2$ 。 2. 当余数为 $0$ 时,代表 $n$ 是 $3$ 的倍数,因此不做任何处理。 3. 当余数为 $2$ 时,不继续划分,保留。 4. 当余数为 $1$ 时,由于 $2 \times 2 > 1 \times 3$ ,因此应将最后一个 $3$ 替换为 $2$ 。 ### 代码实现 如下图所示,我们无须通过循环来切分整数,而可以利用向下整除运算得到 $3$ 的个数 $a$ ,用取模运算得到余数 $b$ ,此时有: $$ n = 3 a + b $$ 请注意,对于 $n \leq 3$ 的边界情况,必须拆分出一个 $1$ ,乘积为 $1 \times (n - 1)$ 。 ```src [file]{max_product_cutting}-[class]{}-[func]{max_product_cutting} ``` ![最大切分乘积的计算方法](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png) **时间复杂度取决于编程语言的幂运算的实现方法**。以 Python 为例,常用的幂计算函数有三种。 - 运算符 `**` 和函数 `pow()` 的时间复杂度均为 $O(\log⁡ a)$ 。 - 函数 `math.pow()` 内部调用 C 语言库的 `pow()` 函数,其执行浮点取幂,时间复杂度为 $O(1)$ 。 变量 $a$ 和 $b$ 使用常数大小的额外空间,**因此空间复杂度为 $O(1)$** 。 ### 正确性证明 使用反证法,只分析 $n \geq 4$ 的情况。 1. **所有因子 $\leq 3$** :假设最优切分方案中存在 $\geq 4$ 的因子 $x$ ,那么一定可以将其继续划分为 $2(x-2)$ ,从而获得更大(或相等)的乘积。这与假设矛盾。 2. **切分方案不包含 $1$** :假设最优切分方案中存在一个因子 $1$ ,那么它一定可以合并入另外一个因子中,以获得更大的乘积。这与假设矛盾。 3. **切分方案最多包含两个 $2$** :假设最优切分方案中包含三个 $2$ ,那么一定可以替换为两个 $3$ ,乘积更大。这与假设矛盾。 ================================================ FILE: docs/chapter_greedy/summary.md ================================================ # 小结 ### 重点回顾 - 贪心算法通常用于解决最优化问题,其原理是在每个决策阶段都做出局部最优的决策,以期获得全局最优解。 - 贪心算法会迭代地做出一个又一个的贪心选择,每轮都将问题转化成一个规模更小的子问题,直到问题被解决。 - 贪心算法不仅实现简单,还具有很高的解题效率。相比于动态规划,贪心算法的时间复杂度通常更低。 - 在零钱兑换问题中,对于某些硬币组合,贪心算法可以保证找到最优解;对于另外一些硬币组合则不然,贪心算法可能找到很差的解。 - 适合用贪心算法求解的问题具有两大性质:贪心选择性质和最优子结构。贪心选择性质代表贪心策略的有效性。 - 对于某些复杂问题,贪心选择性质的证明并不简单。相对来说,证伪更加容易,例如零钱兑换问题。 - 求解贪心问题主要分为三步:问题分析、确定贪心策略、正确性证明。其中,确定贪心策略是核心步骤,正确性证明往往是难点。 - 分数背包问题在 0-1 背包的基础上,允许选择物品的一部分,因此可使用贪心算法求解。贪心策略的正确性可以使用反证法来证明。 - 最大容量问题可使用穷举法求解,时间复杂度为 $O(n^2)$ 。通过设计贪心策略,每轮向内移动短板,可将时间复杂度优化至 $O(n)$ 。 - 在最大切分乘积问题中,我们先后推理出两个贪心策略:$\geq 4$ 的整数都应该继续切分,最优切分因子为 $3$ 。代码中包含幂运算,时间复杂度取决于幂运算实现方法,通常为 $O(1)$ 或 $O(\log n)$ 。 ================================================ FILE: docs/chapter_hashing/hash_algorithm.md ================================================ # 哈希算法 前两节介绍了哈希表的工作原理和哈希冲突的处理方法。然而无论是开放寻址还是链式地址,**它们只能保证哈希表可以在发生冲突时正常工作,而无法减少哈希冲突的发生**。 如果哈希冲突过于频繁,哈希表的性能则会急剧劣化。如下图所示,对于链式地址哈希表,理想情况下键值对均匀分布在各个桶中,达到最佳查询效率;最差情况下所有键值对都存储到同一个桶中,时间复杂度退化至 $O(n)$ 。 ![哈希冲突的最佳情况与最差情况](hash_algorithm.assets/hash_collision_best_worst_condition.png) **键值对的分布情况由哈希函数决定**。回忆哈希函数的计算步骤,先计算哈希值,再对数组长度取模: ```shell index = hash(key) % capacity ``` 观察以上公式,当哈希表容量 `capacity` 固定时,**哈希算法 `hash()` 决定了输出值**,进而决定了键值对在哈希表中的分布情况。 这意味着,为了降低哈希冲突的发生概率,我们应当将注意力集中在哈希算法 `hash()` 的设计上。 ## 哈希算法的目标 为了实现“既快又稳”的哈希表数据结构,哈希算法应具备以下特点。 - **确定性**:对于相同的输入,哈希算法应始终产生相同的输出。这样才能确保哈希表是可靠的。 - **效率高**:计算哈希值的过程应该足够快。计算开销越小,哈希表的实用性越高。 - **均匀分布**:哈希算法应使得键值对均匀分布在哈希表中。分布越均匀,哈希冲突的概率就越低。 实际上,哈希算法除了可以用于实现哈希表,还广泛应用于其他领域中。 - **密码存储**:为了保护用户密码的安全,系统通常不会直接存储用户的明文密码,而是存储密码的哈希值。当用户输入密码时,系统会对输入的密码计算哈希值,然后与存储的哈希值进行比较。如果两者匹配,那么密码就被视为正确。 - **数据完整性检查**:数据发送方可以计算数据的哈希值并将其一同发送;接收方可以重新计算接收到的数据的哈希值,并与接收到的哈希值进行比较。如果两者匹配,那么数据就被视为完整。 对于密码学的相关应用,为了防止从哈希值推导出原始密码等逆向工程,哈希算法需要具备更高等级的安全特性。 - **单向性**:无法通过哈希值反推出关于输入数据的任何信息。 - **抗碰撞性**:应当极难找到两个不同的输入,使得它们的哈希值相同。 - **雪崩效应**:输入的微小变化应当导致输出的显著且不可预测的变化。 请注意,**“均匀分布”与“抗碰撞性”是两个独立的概念**,满足均匀分布不一定满足抗碰撞性。例如,在随机输入 `key` 下,哈希函数 `key % 100` 可以产生均匀分布的输出。然而该哈希算法过于简单,所有后两位相等的 `key` 的输出都相同,因此我们可以很容易地从哈希值反推出可用的 `key` ,从而破解密码。 ## 哈希算法的设计 哈希算法的设计是一个需要考虑许多因素的复杂问题。然而对于某些要求不高的场景,我们也能设计一些简单的哈希算法。 - **加法哈希**:对输入的每个字符的 ASCII 码进行相加,将得到的总和作为哈希值。 - **乘法哈希**:利用乘法的不相关性,每轮乘以一个常数,将各个字符的 ASCII 码累积到哈希值中。 - **异或哈希**:将输入数据的每个元素通过异或操作累积到一个哈希值中。 - **旋转哈希**:将每个字符的 ASCII 码累积到一个哈希值中,每次累积之前都会对哈希值进行旋转操作。 ```src [file]{simple_hash}-[class]{}-[func]{rot_hash} ``` 观察发现,每种哈希算法的最后一步都是对大质数 $1000000007$ 取模,以确保哈希值在合适的范围内。值得思考的是,为什么要强调对质数取模,或者说对合数取模的弊端是什么?这是一个有趣的问题。 先给出结论:**使用大质数作为模数,可以最大化地保证哈希值的均匀分布**。因为质数不与其他数字存在公约数,可以减少因取模操作而产生的周期性模式,从而避免哈希冲突。 举个例子,假设我们选择合数 $9$ 作为模数,它可以被 $3$ 整除,那么所有可以被 $3$ 整除的 `key` 都会被映射到 $0$、$3$、$6$ 这三个哈希值。 $$ \begin{aligned} \text{modulus} & = 9 \newline \text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline \text{hash} & = \{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\dots \} \end{aligned} $$ 如果输入 `key` 恰好满足这种等差数列的数据分布,那么哈希值就会出现聚堆,从而加重哈希冲突。现在,假设将 `modulus` 替换为质数 $13$ ,由于 `key` 和 `modulus` 之间不存在公约数,因此输出的哈希值的均匀性会明显提升。 $$ \begin{aligned} \text{modulus} & = 13 \newline \text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline \text{hash} & = \{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \dots \} \end{aligned} $$ 值得说明的是,如果能够保证 `key` 是随机均匀分布的,那么选择质数或者合数作为模数都可以,它们都能输出均匀分布的哈希值。而当 `key` 的分布存在某种周期性时,对合数取模更容易出现聚集现象。 总而言之,我们通常选取质数作为模数,并且这个质数最好足够大,以尽可能消除周期性模式,提升哈希算法的稳健性。 ## 常见哈希算法 不难发现,以上介绍的简单哈希算法都比较“脆弱”,远远没有达到哈希算法的设计目标。例如,由于加法和异或满足交换律,因此加法哈希和异或哈希无法区分内容相同但顺序不同的字符串,这可能会加剧哈希冲突,并引起一些安全问题。 在实际中,我们通常会用一些标准哈希算法,例如 MD5、SHA-1、SHA-2 和 SHA-3 等。它们可以将任意长度的输入数据映射到恒定长度的哈希值。 近一个世纪以来,哈希算法处在不断升级与优化的过程中。一部分研究人员努力提升哈希算法的性能,另一部分研究人员和黑客则致力于寻找哈希算法的安全性问题。下表展示了在实际应用中常见的哈希算法。 - MD5 和 SHA-1 已多次被成功攻击,因此它们被各类安全应用弃用。 - SHA-2 系列中的 SHA-256 是最安全的哈希算法之一,仍未出现成功的攻击案例,因此常用在各类安全应用与协议中。 - SHA-3 相较 SHA-2 的实现开销更低、计算效率更高,但目前使用覆盖度不如 SHA-2 系列。

  常见的哈希算法

| | MD5 | SHA-1 | SHA-2 | SHA-3 | | -------- | ------------------------------ | ---------------- | ---------------------------- | ------------------- | | 推出时间 | 1992 | 1995 | 2002 | 2008 | | 输出长度 | 128 bit | 160 bit | 256/512 bit | 224/256/384/512 bit | | 哈希冲突 | 较多 | 较多 | 很少 | 很少 | | 安全等级 | 低,已被成功攻击 | 低,已被成功攻击 | 高 | 高 | | 应用 | 已被弃用,仍用于数据完整性检查 | 已被弃用 | 加密货币交易验证、数字签名等 | 可用于替代 SHA-2 | ## 数据结构的哈希值 我们知道,哈希表的 `key` 可以是整数、小数或字符串等数据类型。编程语言通常会为这些数据类型提供内置的哈希算法,用于计算哈希表中的桶索引。以 Python 为例,我们可以调用 `hash()` 函数来计算各种数据类型的哈希值。 - 整数和布尔量的哈希值就是其本身。 - 浮点数和字符串的哈希值计算较为复杂,有兴趣的读者请自行学习。 - 元组的哈希值是对其中每一个元素进行哈希,然后将这些哈希值组合起来,得到单一的哈希值。 - 对象的哈希值基于其内存地址生成。通过重写对象的哈希方法,可实现基于内容生成哈希值。 !!! tip 请注意,不同编程语言的内置哈希值计算函数的定义和方法不同。 === "Python" ```python title="built_in_hash.py" num = 3 hash_num = hash(num) # 整数 3 的哈希值为 3 bol = True hash_bol = hash(bol) # 布尔量 True 的哈希值为 1 dec = 3.14159 hash_dec = hash(dec) # 小数 3.14159 的哈希值为 326484311674566659 str = "Hello 算法" hash_str = hash(str) # 字符串“Hello 算法”的哈希值为 4617003410720528961 tup = (12836, "小哈") hash_tup = hash(tup) # 元组 (12836, '小哈') 的哈希值为 1029005403108185979 obj = ListNode(0) hash_obj = hash(obj) # 节点对象 的哈希值为 274267521 ``` === "C++" ```cpp title="built_in_hash.cpp" int num = 3; size_t hashNum = hash()(num); // 整数 3 的哈希值为 3 bool bol = true; size_t hashBol = hash()(bol); // 布尔量 1 的哈希值为 1 double dec = 3.14159; size_t hashDec = hash()(dec); // 小数 3.14159 的哈希值为 4614256650576692846 string str = "Hello 算法"; size_t hashStr = hash()(str); // 字符串“Hello 算法”的哈希值为 15466937326284535026 // 在 C++ 中,内置 std:hash() 仅提供基本数据类型的哈希值计算 // 数组、对象的哈希值计算需要自行实现 ``` === "Java" ```java title="built_in_hash.java" int num = 3; int hashNum = Integer.hashCode(num); // 整数 3 的哈希值为 3 boolean bol = true; int hashBol = Boolean.hashCode(bol); // 布尔量 true 的哈希值为 1231 double dec = 3.14159; int hashDec = Double.hashCode(dec); // 小数 3.14159 的哈希值为 -1340954729 String str = "Hello 算法"; int hashStr = str.hashCode(); // 字符串“Hello 算法”的哈希值为 -727081396 Object[] arr = { 12836, "小哈" }; int hashTup = Arrays.hashCode(arr); // 数组 [12836, 小哈] 的哈希值为 1151158 ListNode obj = new ListNode(0); int hashObj = obj.hashCode(); // 节点对象 utils.ListNode@7dc5e7b4 的哈希值为 2110121908 ``` === "C#" ```csharp title="built_in_hash.cs" int num = 3; int hashNum = num.GetHashCode(); // 整数 3 的哈希值为 3; bool bol = true; int hashBol = bol.GetHashCode(); // 布尔量 true 的哈希值为 1; double dec = 3.14159; int hashDec = dec.GetHashCode(); // 小数 3.14159 的哈希值为 -1340954729; string str = "Hello 算法"; int hashStr = str.GetHashCode(); // 字符串“Hello 算法”的哈希值为 -586107568; object[] arr = [12836, "小哈"]; int hashTup = arr.GetHashCode(); // 数组 [12836, 小哈] 的哈希值为 42931033; ListNode obj = new(0); int hashObj = obj.GetHashCode(); // 节点对象 0 的哈希值为 39053774; ``` === "Go" ```go title="built_in_hash.go" // Go 未提供内置 hash code 函数 ``` === "Swift" ```swift title="built_in_hash.swift" let num = 3 let hashNum = num.hashValue // 整数 3 的哈希值为 9047044699613009734 let bol = true let hashBol = bol.hashValue // 布尔量 true 的哈希值为 -4431640247352757451 let dec = 3.14159 let hashDec = dec.hashValue // 小数 3.14159 的哈希值为 -2465384235396674631 let str = "Hello 算法" let hashStr = str.hashValue // 字符串“Hello 算法”的哈希值为 -7850626797806988787 let arr = [AnyHashable(12836), AnyHashable("小哈")] let hashTup = arr.hashValue // 数组 [AnyHashable(12836), AnyHashable("小哈")] 的哈希值为 -2308633508154532996 let obj = ListNode(x: 0) let hashObj = obj.hashValue // 节点对象 utils.ListNode 的哈希值为 -2434780518035996159 ``` === "JS" ```javascript title="built_in_hash.js" // JavaScript 未提供内置 hash code 函数 ``` === "TS" ```typescript title="built_in_hash.ts" // TypeScript 未提供内置 hash code 函数 ``` === "Dart" ```dart title="built_in_hash.dart" int num = 3; int hashNum = num.hashCode; // 整数 3 的哈希值为 34803 bool bol = true; int hashBol = bol.hashCode; // 布尔值 true 的哈希值为 1231 double dec = 3.14159; int hashDec = dec.hashCode; // 小数 3.14159 的哈希值为 2570631074981783 String str = "Hello 算法"; int hashStr = str.hashCode; // 字符串“Hello 算法”的哈希值为 468167534 List arr = [12836, "小哈"]; int hashArr = arr.hashCode; // 数组 [12836, 小哈] 的哈希值为 976512528 ListNode obj = new ListNode(0); int hashObj = obj.hashCode; // 节点对象 Instance of 'ListNode' 的哈希值为 1033450432 ``` === "Rust" ```rust title="built_in_hash.rs" use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; let num = 3; let mut num_hasher = DefaultHasher::new(); num.hash(&mut num_hasher); let hash_num = num_hasher.finish(); // 整数 3 的哈希值为 568126464209439262 let bol = true; let mut bol_hasher = DefaultHasher::new(); bol.hash(&mut bol_hasher); let hash_bol = bol_hasher.finish(); // 布尔量 true 的哈希值为 4952851536318644461 let dec: f32 = 3.14159; let mut dec_hasher = DefaultHasher::new(); dec.to_bits().hash(&mut dec_hasher); let hash_dec = dec_hasher.finish(); // 小数 3.14159 的哈希值为 2566941990314602357 let str = "Hello 算法"; let mut str_hasher = DefaultHasher::new(); str.hash(&mut str_hasher); let hash_str = str_hasher.finish(); // 字符串“Hello 算法”的哈希值为 16092673739211250988 let arr = (&12836, &"小哈"); let mut tup_hasher = DefaultHasher::new(); arr.hash(&mut tup_hasher); let hash_tup = tup_hasher.finish(); // 元组 (12836, "小哈") 的哈希值为 1885128010422702749 let node = ListNode::new(42); let mut hasher = DefaultHasher::new(); node.borrow().val.hash(&mut hasher); let hash = hasher.finish(); // 节点对象 RefCell { value: ListNode { val: 42, next: None } } 的哈希值为15387811073369036852 ``` === "C" ```c title="built_in_hash.c" // C 未提供内置 hash code 函数 ``` === "Kotlin" ```kotlin title="built_in_hash.kt" val num = 3 val hashNum = num.hashCode() // 整数 3 的哈希值为 3 val bol = true val hashBol = bol.hashCode() // 布尔量 true 的哈希值为 1231 val dec = 3.14159 val hashDec = dec.hashCode() // 小数 3.14159 的哈希值为 -1340954729 val str = "Hello 算法" val hashStr = str.hashCode() // 字符串“Hello 算法”的哈希值为 -727081396 val arr = arrayOf(12836, "小哈") val hashTup = arr.hashCode() // 数组 [12836, 小哈] 的哈希值为 189568618 val obj = ListNode(0) val hashObj = obj.hashCode() // 节点对象 utils.ListNode@1d81eb93 的哈希值为 495053715 ``` === "Ruby" ```ruby title="built_in_hash.rb" num = 3 hash_num = num.hash # 整数 3 的哈希值为 -4385856518450339636 bol = true hash_bol = bol.hash # 布尔量 true 的哈希值为 -1617938112149317027 dec = 3.14159 hash_dec = dec.hash # 小数 3.14159 的哈希值为 -1479186995943067893 str = "Hello 算法" hash_str = str.hash # 字符串“Hello 算法”的哈希值为 -4075943250025831763 tup = [12836, '小哈'] hash_tup = tup.hash # 元组 (12836, '小哈') 的哈希值为 1999544809202288822 obj = ListNode.new(0) hash_obj = obj.hash # 节点对象 # 的哈希值为 4302940560806366381 ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B0%203%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E5%B0%94%E9%87%8F%20True%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B0%203.14159%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E2%80%9CHello%20%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%BB%84%20%2812836,%20'%E5%B0%8F%E5%93%88'%29%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false 在许多编程语言中,**只有不可变对象才可作为哈希表的 `key`** 。假如我们将列表(动态数组)作为 `key` ,当列表的内容发生变化时,它的哈希值也随之改变,我们就无法在哈希表中查询到原先的 `value` 了。 虽然自定义对象(比如链表节点)的成员变量是可变的,但它是可哈希的。**这是因为对象的哈希值通常是基于内存地址生成的**,即使对象的内容发生了变化,但它的内存地址不变,哈希值仍然是不变的。 细心的你可能发现在不同控制台中运行程序时,输出的哈希值是不同的。**这是因为 Python 解释器在每次启动时,都会为字符串哈希函数加入一个随机的盐(salt)值**。这种做法可以有效防止 HashDoS 攻击,提升哈希算法的安全性。 ================================================ FILE: docs/chapter_hashing/hash_collision.md ================================================ # 哈希冲突 上一节提到,**通常情况下哈希函数的输入空间远大于输出空间**,因此理论上哈希冲突是不可避免的。比如,输入空间为全体整数,输出空间为数组容量大小,则必然有多个整数映射至同一桶索引。 哈希冲突会导致查询结果错误,严重影响哈希表的可用性。为了解决该问题,每当遇到哈希冲突时,我们就进行哈希表扩容,直至冲突消失为止。此方法简单粗暴且有效,但效率太低,因为哈希表扩容需要进行大量的数据搬运与哈希值计算。为了提升效率,我们可以采用以下策略。 1. 改良哈希表数据结构,**使得哈希表可以在出现哈希冲突时正常工作**。 2. 仅在必要时,即当哈希冲突比较严重时,才执行扩容操作。 哈希表的结构改良方法主要包括“链式地址”和“开放寻址”。 ## 链式地址 在原始哈希表中,每个桶仅能存储一个键值对。链式地址(separate chaining)将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。下图展示了一个链式地址哈希表的例子。 ![链式地址哈希表](hash_collision.assets/hash_table_chaining.png) 基于链式地址实现的哈希表的操作方法发生了以下变化。 - **查询元素**:输入 `key` ,经过哈希函数得到桶索引,即可访问链表头节点,然后遍历链表并对比 `key` 以查找目标键值对。 - **添加元素**:首先通过哈希函数访问链表头节点,然后将节点(键值对)添加到链表中。 - **删除元素**:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标节点并将其删除。 链式地址存在以下局限性。 - **占用空间增大**:链表包含节点指针,它相比数组更加耗费内存空间。 - **查询效率降低**:因为需要线性遍历链表来查找对应元素。 以下代码给出了链式地址哈希表的简单实现,需要注意两点。 - 使用列表(动态数组)代替链表,从而简化代码。在这种设定下,哈希表(数组)包含多个桶,每个桶都是一个列表。 - 以下实现包含哈希表扩容方法。当负载因子超过 $\frac{2}{3}$ 时,我们将哈希表扩容至原先的 $2$ 倍。 ```src [file]{hash_map_chaining}-[class]{hash_map_chaining}-[func]{} ``` 值得注意的是,当链表很长时,查询效率 $O(n)$ 很差。**此时可以将链表转换为“AVL 树”或“红黑树”**,从而将查询操作的时间复杂度优化至 $O(\log n)$ 。 ## 开放寻址 开放寻址(open addressing)不引入额外的数据结构,而是通过“多次探测”来处理哈希冲突,探测方式主要包括线性探测、平方探测和多次哈希等。 下面以线性探测为例,介绍开放寻址哈希表的工作机制。 ### 线性探测 线性探测采用固定步长的线性搜索来进行探测,其操作方法与普通哈希表有所不同。 - **插入元素**:通过哈希函数计算桶索引,若发现桶内已有元素,则从冲突位置向后线性遍历(步长通常为 $1$ ),直至找到空桶,将元素插入其中。 - **查找元素**:若发现哈希冲突,则使用相同步长向后进行线性遍历,直到找到对应元素,返回 `value` 即可;如果遇到空桶,说明目标元素不在哈希表中,返回 `None` 。 下图展示了开放寻址(线性探测)哈希表的键值对分布。根据此哈希函数,最后两位相同的 `key` 都会被映射到相同的桶。而通过线性探测,它们被依次存储在该桶以及之下的桶中。 ![开放寻址(线性探测)哈希表的键值对分布](hash_collision.assets/hash_table_linear_probing.png) 然而,**线性探测容易产生“聚集现象”**。具体来说,数组中连续被占用的位置越长,这些连续位置发生哈希冲突的可能性越大,从而进一步促使该位置的聚堆生长,形成恶性循环,最终导致增删查改操作效率劣化。 值得注意的是,**我们不能在开放寻址哈希表中直接删除元素**。这是因为删除元素会在数组内产生一个空桶 `None` ,而当查询元素时,线性探测到该空桶就会返回,因此在该空桶之下的元素都无法再被访问到,程序可能误判这些元素不存在,如下图所示。 ![在开放寻址中删除元素导致的查询问题](hash_collision.assets/hash_table_open_addressing_deletion.png) 为了解决该问题,我们可以采用懒删除(lazy deletion)机制:它不直接从哈希表中移除元素,**而是利用一个常量 `TOMBSTONE` 来标记这个桶**。在该机制下,`None` 和 `TOMBSTONE` 都代表空桶,都可以放置键值对。但不同的是,线性探测到 `TOMBSTONE` 时应该继续遍历,因为其之下可能还存在键值对。 然而,**懒删除可能会加速哈希表的性能退化**。这是因为每次删除操作都会产生一个删除标记,随着 `TOMBSTONE` 的增加,搜索时间也会增加,因为线性探测可能需要跳过多个 `TOMBSTONE` 才能找到目标元素。 为此,考虑在线性探测中记录遇到的首个 `TOMBSTONE` 的索引,并将搜索到的目标元素与该 `TOMBSTONE` 交换位置。这样做的好处是当每次查询或添加元素时,元素会被移动至距离理想位置(探测起始点)更近的桶,从而优化查询效率。 以下代码实现了一个包含懒删除的开放寻址(线性探测)哈希表。为了更加充分地使用哈希表的空间,我们将哈希表看作一个“环形数组”,当越过数组尾部时,回到头部继续遍历。 ```src [file]{hash_map_open_addressing}-[class]{hash_map_open_addressing}-[func]{} ``` ### 平方探测 平方探测与线性探测类似,都是开放寻址的常见策略之一。当发生冲突时,平方探测不是简单地跳过一个固定的步数,而是跳过“探测次数的平方”的步数,即 $1, 4, 9, \dots$ 步。 平方探测主要具有以下优势。 - 平方探测通过跳过探测次数平方的距离,试图缓解线性探测的聚集效应。 - 平方探测会跳过更大的距离来寻找空位置,有助于数据分布得更加均匀。 然而,平方探测并不是完美的。 - 仍然存在聚集现象,即某些位置比其他位置更容易被占用。 - 由于平方的增长,平方探测可能不会探测整个哈希表,这意味着即使哈希表中有空桶,平方探测也可能无法访问到它。 ### 多次哈希 顾名思义,多次哈希方法使用多个哈希函数 $f_1(x)$、$f_2(x)$、$f_3(x)$、$\dots$ 进行探测。 - **插入元素**:若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推,直到找到空位后插入元素。 - **查找元素**:在相同的哈希函数顺序下进行查找,直到找到目标元素时返回;若遇到空位或已尝试所有哈希函数,说明哈希表中不存在该元素,则返回 `None` 。 与线性探测相比,多次哈希方法不易产生聚集,但多个哈希函数会带来额外的计算量。 !!! tip 请注意,开放寻址(线性探测、平方探测和多次哈希)哈希表都存在“不能直接删除元素”的问题。 ## 编程语言的选择 各种编程语言采取了不同的哈希表实现策略,下面举几个例子。 - Python 采用开放寻址。字典 `dict` 使用伪随机数进行探测。 - Java 采用链式地址。自 JDK 1.8 以来,当 `HashMap` 内数组长度达到 64 且链表长度达到 8 时,链表会转换为红黑树以提升查找性能。 - Go 采用链式地址。Go 规定每个桶最多存储 8 个键值对,超出容量则连接一个溢出桶;当溢出桶过多时,会执行一次特殊的等量扩容操作,以确保性能。 ================================================ FILE: docs/chapter_hashing/hash_map.md ================================================ # 哈希表 哈希表(hash table),又称散列表,它通过建立键 `key` 与值 `value` 之间的映射,实现高效的元素查询。具体而言,我们向哈希表中输入一个键 `key` ,则可以在 $O(1)$ 时间内获取对应的值 `value` 。 如下图所示,给定 $n$ 个学生,每个学生都有“姓名”和“学号”两项数据。假如我们希望实现“输入一个学号,返回对应的姓名”的查询功能,则可以采用下图所示的哈希表来实现。 ![哈希表的抽象表示](hash_map.assets/hash_table_lookup.png) 除哈希表外,数组和链表也可以实现查询功能,它们的效率对比如下表所示。 - **添加元素**:仅需将元素添加至数组(链表)的尾部即可,使用 $O(1)$ 时间。 - **查询元素**:由于数组(链表)是乱序的,因此需要遍历其中的所有元素,使用 $O(n)$ 时间。 - **删除元素**:需要先查询到元素,再从数组(链表)中删除,使用 $O(n)$ 时间。

  元素查询效率对比

| | 数组 | 链表 | 哈希表 | | -------- | ------ | ------ | ------ | | 查找元素 | $O(n)$ | $O(n)$ | $O(1)$ | | 添加元素 | $O(1)$ | $O(1)$ | $O(1)$ | | 删除元素 | $O(n)$ | $O(n)$ | $O(1)$ | 观察发现,**在哈希表中进行增删查改的时间复杂度都是 $O(1)$** ,非常高效。 ## 哈希表常用操作 哈希表的常见操作包括:初始化、查询操作、添加键值对和删除键值对等,示例代码如下: === "Python" ```python title="hash_map.py" # 初始化哈希表 hmap: dict = {} # 添加操作 # 在哈希表中添加键值对 (key, value) hmap[12836] = "小哈" hmap[15937] = "小啰" hmap[16750] = "小算" hmap[13276] = "小法" hmap[10583] = "小鸭" # 查询操作 # 向哈希表中输入键 key ,得到值 value name: str = hmap[15937] # 删除操作 # 在哈希表中删除键值对 (key, value) hmap.pop(10583) ``` === "C++" ```cpp title="hash_map.cpp" /* 初始化哈希表 */ unordered_map map; /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map[12836] = "小哈"; map[15937] = "小啰"; map[16750] = "小算"; map[13276] = "小法"; map[10583] = "小鸭"; /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value string name = map[15937]; /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.erase(10583); ``` === "Java" ```java title="hash_map.java" /* 初始化哈希表 */ Map map = new HashMap<>(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.put(12836, "小哈"); map.put(15937, "小啰"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鸭"); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value String name = map.get(15937); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(10583); ``` === "C#" ```csharp title="hash_map.cs" /* 初始化哈希表 */ Dictionary map = new() { /* 添加操作 */ // 在哈希表中添加键值对 (key, value) { 12836, "小哈" }, { 15937, "小啰" }, { 16750, "小算" }, { 13276, "小法" }, { 10583, "小鸭" } }; /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value string name = map[15937]; /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.Remove(10583); ``` === "Go" ```go title="hash_map_test.go" /* 初始化哈希表 */ hmap := make(map[int]string) /* 添加操作 */ // 在哈希表中添加键值对 (key, value) hmap[12836] = "小哈" hmap[15937] = "小啰" hmap[16750] = "小算" hmap[13276] = "小法" hmap[10583] = "小鸭" /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value name := hmap[15937] /* 删除操作 */ // 在哈希表中删除键值对 (key, value) delete(hmap, 10583) ``` === "Swift" ```swift title="hash_map.swift" /* 初始化哈希表 */ var map: [Int: String] = [:] /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map[12836] = "小哈" map[15937] = "小啰" map[16750] = "小算" map[13276] = "小法" map[10583] = "小鸭" /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value let name = map[15937]! /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.removeValue(forKey: 10583) ``` === "JS" ```javascript title="hash_map.js" /* 初始化哈希表 */ const map = new Map(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.set(12836, '小哈'); map.set(15937, '小啰'); map.set(16750, '小算'); map.set(13276, '小法'); map.set(10583, '小鸭'); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value let name = map.get(15937); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.delete(10583); ``` === "TS" ```typescript title="hash_map.ts" /* 初始化哈希表 */ const map = new Map(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.set(12836, '小哈'); map.set(15937, '小啰'); map.set(16750, '小算'); map.set(13276, '小法'); map.set(10583, '小鸭'); console.info('\n添加完成后,哈希表为\nKey -> Value'); console.info(map); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value let name = map.get(15937); console.info('\n输入学号 15937 ,查询到姓名 ' + name); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.delete(10583); console.info('\n删除 10583 后,哈希表为\nKey -> Value'); console.info(map); ``` === "Dart" ```dart title="hash_map.dart" /* 初始化哈希表 */ Map map = {}; /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map[12836] = "小哈"; map[15937] = "小啰"; map[16750] = "小算"; map[13276] = "小法"; map[10583] = "小鸭"; /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value String name = map[15937]; /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(10583); ``` === "Rust" ```rust title="hash_map.rs" use std::collections::HashMap; /* 初始化哈希表 */ let mut map: HashMap = HashMap::new(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.insert(12836, "小哈".to_string()); map.insert(15937, "小啰".to_string()); map.insert(16750, "小算".to_string()); map.insert(13279, "小法".to_string()); map.insert(10583, "小鸭".to_string()); /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value let _name: Option<&String> = map.get(&15937); /* 删除操作 */ // 在哈希表中删除键值对 (key, value) let _removed_value: Option = map.remove(&10583); ``` === "C" ```c title="hash_map.c" // C 未提供内置哈希表 ``` === "Kotlin" ```kotlin title="hash_map.kt" /* 初始化哈希表 */ val map = HashMap() /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map[12836] = "小哈" map[15937] = "小啰" map[16750] = "小算" map[13276] = "小法" map[10583] = "小鸭" /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value val name = map[15937] /* 删除操作 */ // 在哈希表中删除键值对 (key, value) map.remove(10583) ``` === "Ruby" ```ruby title="hash_map.rb" # 初始化哈希表 hmap = {} # 添加操作 # 在哈希表中添加键值对 (key, value) hmap[12836] = "小哈" hmap[15937] = "小啰" hmap[16750] = "小算" hmap[13276] = "小法" hmap[10583] = "小鸭" # 查询操作 # 向哈希表中输入键 key ,得到值 value name = hmap[15937] # 删除操作 # 在哈希表中删除键值对 (key, value) hmap.delete(10583) ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E8%BE%93%E5%85%A5%E9%94%AE%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false 哈希表有三种常用的遍历方式:遍历键值对、遍历键和遍历值。示例代码如下: === "Python" ```python title="hash_map.py" # 遍历哈希表 # 遍历键值对 key->value for key, value in hmap.items(): print(key, "->", value) # 单独遍历键 key for key in hmap.keys(): print(key) # 单独遍历值 value for value in hmap.values(): print(value) ``` === "C++" ```cpp title="hash_map.cpp" /* 遍历哈希表 */ // 遍历键值对 key->value for (auto kv: map) { cout << kv.first << " -> " << kv.second << endl; } // 使用迭代器遍历 key->value for (auto iter = map.begin(); iter != map.end(); iter++) { cout << iter->first << "->" << iter->second << endl; } ``` === "Java" ```java title="hash_map.java" /* 遍历哈希表 */ // 遍历键值对 key->value for (Map.Entry kv: map.entrySet()) { System.out.println(kv.getKey() + " -> " + kv.getValue()); } // 单独遍历键 key for (int key: map.keySet()) { System.out.println(key); } // 单独遍历值 value for (String val: map.values()) { System.out.println(val); } ``` === "C#" ```csharp title="hash_map.cs" /* 遍历哈希表 */ // 遍历键值对 Key->Value foreach (var kv in map) { Console.WriteLine(kv.Key + " -> " + kv.Value); } // 单独遍历键 key foreach (int key in map.Keys) { Console.WriteLine(key); } // 单独遍历值 value foreach (string val in map.Values) { Console.WriteLine(val); } ``` === "Go" ```go title="hash_map_test.go" /* 遍历哈希表 */ // 遍历键值对 key->value for key, value := range hmap { fmt.Println(key, "->", value) } // 单独遍历键 key for key := range hmap { fmt.Println(key) } // 单独遍历值 value for _, value := range hmap { fmt.Println(value) } ``` === "Swift" ```swift title="hash_map.swift" /* 遍历哈希表 */ // 遍历键值对 Key->Value for (key, value) in map { print("\(key) -> \(value)") } // 单独遍历键 Key for key in map.keys { print(key) } // 单独遍历值 Value for value in map.values { print(value) } ``` === "JS" ```javascript title="hash_map.js" /* 遍历哈希表 */ console.info('\n遍历键值对 Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\n单独遍历键 Key'); for (const k of map.keys()) { console.info(k); } console.info('\n单独遍历值 Value'); for (const v of map.values()) { console.info(v); } ``` === "TS" ```typescript title="hash_map.ts" /* 遍历哈希表 */ console.info('\n遍历键值对 Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\n单独遍历键 Key'); for (const k of map.keys()) { console.info(k); } console.info('\n单独遍历值 Value'); for (const v of map.values()) { console.info(v); } ``` === "Dart" ```dart title="hash_map.dart" /* 遍历哈希表 */ // 遍历键值对 Key->Value map.forEach((key, value) { print('$key -> $value'); }); // 单独遍历键 Key map.keys.forEach((key) { print(key); }); // 单独遍历值 Value map.values.forEach((value) { print(value); }); ``` === "Rust" ```rust title="hash_map.rs" /* 遍历哈希表 */ // 遍历键值对 Key->Value for (key, value) in &map { println!("{key} -> {value}"); } // 单独遍历键 Key for key in map.keys() { println!("{key}"); } // 单独遍历值 Value for value in map.values() { println!("{value}"); } ``` === "C" ```c title="hash_map.c" // C 未提供内置哈希表 ``` === "Kotlin" ```kotlin title="hash_map.kt" /* 遍历哈希表 */ // 遍历键值对 key->value for ((key, value) in map) { println("$key -> $value") } // 单独遍历键 key for (key in map.keys) { println(key) } // 单独遍历值 value for (_val in map.values) { println(_val) } ``` === "Ruby" ```ruby title="hash_map.rb" # 遍历哈希表 # 遍历键值对 key->value hmap.entries.each { |key, value| puts "#{key} -> #{value}" } # 单独遍历键 key hmap.keys.each { |key| puts key } # 单独遍历值 value hmap.values.each { |val| puts val } ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20key-%3Evalue%0A%20%20%20%20for%20key,%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key,%20%22-%3E%22,%20value%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E9%94%AE%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E5%80%BC%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## 哈希表简单实现 我们先考虑最简单的情况,**仅用一个数组来实现哈希表**。在哈希表中,我们将数组中的每个空位称为桶(bucket),每个桶可存储一个键值对。因此,查询操作就是找到 `key` 对应的桶,并在桶中获取 `value` 。 那么,如何基于 `key` 定位对应的桶呢?这是通过哈希函数(hash function)实现的。哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中,输入空间是所有 `key` ,输出空间是所有桶(数组索引)。换句话说,输入一个 `key` ,**我们可以通过哈希函数得到该 `key` 对应的键值对在数组中的存储位置**。 输入一个 `key` ,哈希函数的计算过程分为以下两步。 1. 通过某种哈希算法 `hash()` 计算得到哈希值。 2. 将哈希值对桶数量(数组长度)`capacity` 取模,从而获取该 `key` 对应的桶(数组索引)`index` 。 ```shell index = hash(key) % capacity ``` 随后,我们就可以利用 `index` 在哈希表中访问对应的桶,从而获取 `value` 。 设数组长度 `capacity = 100`、哈希算法 `hash(key) = key` ,易得哈希函数为 `key % 100` 。下图以 `key` 学号和 `value` 姓名为例,展示了哈希函数的工作原理。 ![哈希函数工作原理](hash_map.assets/hash_function.png) 以下代码实现了一个简单哈希表。其中,我们将 `key` 和 `value` 封装成一个类 `Pair` ,以表示键值对。 ```src [file]{array_hash_map}-[class]{array_hash_map}-[func]{} ``` ## 哈希冲突与扩容 从本质上看,哈希函数的作用是将所有 `key` 构成的输入空间映射到数组所有索引构成的输出空间,而输入空间往往远大于输出空间。因此,**理论上一定存在“多个输入对应相同输出”的情况**。 对于上述示例中的哈希函数,当输入的 `key` 后两位相同时,哈希函数的输出结果也相同。例如,查询学号为 12836 和 20336 的两个学生时,我们得到: ```shell 12836 % 100 = 36 20336 % 100 = 36 ``` 如下图所示,两个学号指向了同一个姓名,这显然是不对的。我们将这种多个输入对应同一输出的情况称为哈希冲突(hash collision)。 ![哈希冲突示例](hash_map.assets/hash_collision.png) 容易想到,哈希表容量 $n$ 越大,多个 `key` 被分配到同一个桶中的概率就越低,冲突就越少。因此,**我们可以通过扩容哈希表来减少哈希冲突**。 如下图所示,扩容前键值对 `(136, A)` 和 `(236, D)` 发生冲突,扩容后冲突消失。 ![哈希表扩容](hash_map.assets/hash_table_reshash.png) 类似于数组扩容,哈希表扩容需将所有键值对从原哈希表迁移至新哈希表,非常耗时;并且由于哈希表容量 `capacity` 改变,我们需要通过哈希函数来重新计算所有键值对的存储位置,这进一步增加了扩容过程的计算开销。为此,编程语言通常会预留足够大的哈希表容量,防止频繁扩容。 负载因子(load factor)是哈希表的一个重要概念,其定义为哈希表的元素数量除以桶数量,用于衡量哈希冲突的严重程度,**也常作为哈希表扩容的触发条件**。例如在 Java 中,当负载因子超过 $0.75$ 时,系统会将哈希表扩容至原先的 $2$ 倍。 ================================================ FILE: docs/chapter_hashing/index.md ================================================ # 哈希表 ![哈希表](../assets/covers/chapter_hashing.jpg) !!! abstract 在计算机世界中,哈希表如同一位聪慧的图书管理员。 他知道如何计算索书号,从而可以快速找到目标图书。 ================================================ FILE: docs/chapter_hashing/summary.md ================================================ # 小结 ### 重点回顾 - 输入 `key` ,哈希表能够在 $O(1)$ 时间内查询到 `value` ,效率非常高。 - 常见的哈希表操作包括查询、添加键值对、删除键值对和遍历哈希表等。 - 哈希函数将 `key` 映射为数组索引,从而访问对应桶并获取 `value` 。 - 两个不同的 `key` 可能在经过哈希函数后得到相同的数组索引,导致查询结果出错,这种现象被称为哈希冲突。 - 哈希表容量越大,哈希冲突的概率就越低。因此可以通过扩容哈希表来缓解哈希冲突。与数组扩容类似,哈希表扩容操作的开销很大。 - 负载因子定义为哈希表中元素数量除以桶数量,反映了哈希冲突的严重程度,常用作触发哈希表扩容的条件。 - 链式地址通过将单个元素转化为链表,将所有冲突元素存储在同一个链表中。然而,链表过长会降低查询效率,可以通过进一步将链表转换为红黑树来提高效率。 - 开放寻址通过多次探测来处理哈希冲突。线性探测使用固定步长,缺点是不能删除元素,且容易产生聚集。多次哈希使用多个哈希函数进行探测,相较线性探测更不易产生聚集,但多个哈希函数增加了计算量。 - 不同编程语言采取了不同的哈希表实现。例如,Java 的 `HashMap` 使用链式地址,而 Python 的 `Dict` 采用开放寻址。 - 在哈希表中,我们希望哈希算法具有确定性、高效率和均匀分布的特点。在密码学中,哈希算法还应该具备抗碰撞性和雪崩效应。 - 哈希算法通常采用大质数作为模数,以最大化地保证哈希值均匀分布,减少哈希冲突。 - 常见的哈希算法包括 MD5、SHA-1、SHA-2 和 SHA-3 等。MD5 常用于校验文件完整性,SHA-2 常用于安全应用与协议。 - 编程语言通常会为数据类型提供内置哈希算法,用于计算哈希表中的桶索引。通常情况下,只有不可变对象是可哈希的。 ### Q & A **Q**:哈希表的时间复杂度在什么情况下是 $O(n)$ ? 当哈希冲突比较严重时,哈希表的时间复杂度会退化至 $O(n)$ 。当哈希函数设计得比较好、容量设置比较合理、冲突比较平均时,时间复杂度是 $O(1)$ 。我们使用编程语言内置的哈希表时,通常认为时间复杂度是 $O(1)$ 。 **Q**:为什么不使用哈希函数 $f(x) = x$ 呢?这样就不会有冲突了。 在 $f(x) = x$ 哈希函数下,每个元素对应唯一的桶索引,这与数组等价。然而,输入空间通常远大于输出空间(数组长度),因此哈希函数的最后一步往往是对数组长度取模。换句话说,哈希表的目标是将一个较大的状态空间映射到一个较小的空间,并提供 $O(1)$ 的查询效率。 **Q**:哈希表底层实现是数组、链表、二叉树,但为什么效率可以比它们更高呢? 首先,哈希表的时间效率变高,但空间效率变低了。哈希表有相当一部分内存未使用。 其次,只是在特定使用场景下时间效率变高了。如果一个功能能够在相同的时间复杂度下使用数组或链表实现,那么通常比哈希表更快。这是因为哈希函数计算需要开销,时间复杂度的常数项更大。 最后,哈希表的时间复杂度可能发生劣化。例如在链式地址中,我们采取在链表或红黑树中执行查找操作,仍然有退化至 $O(n)$ 时间的风险。 **Q**:多次哈希有不能直接删除元素的缺陷吗?标记为已删除的空间还能再次使用吗? 多次哈希是开放寻址的一种,开放寻址法都有不能直接删除元素的缺陷,需要通过标记删除。标记为已删除的空间可以再次使用。当将新元素插入哈希表,并且通过哈希函数找到标记为已删除的位置时,该位置可以被新元素使用。这样做既能保持哈希表的探测序列不变,又能保证哈希表的空间使用率。 **Q**:为什么在线性探测中,查找元素的时候会出现哈希冲突呢? 查找的时候通过哈希函数找到对应的桶和键值对,发现 `key` 不匹配,这就代表有哈希冲突。因此,线性探测法会根据预先设定的步长依次向下查找,直至找到正确的键值对或无法找到跳出为止。 **Q**:为什么哈希表扩容能够缓解哈希冲突? 哈希函数的最后一步往往是对数组长度 $n$ 取模(取余),让输出值落在数组索引范围内;在扩容后,数组长度 $n$ 发生变化,而 `key` 对应的索引也可能发生变化。原先落在同一个桶的多个 `key` ,在扩容后可能会被分配到多个桶中,从而实现哈希冲突的缓解。 **Q**:如果为了高效的存取,那么直接使用数组不就好了吗? 当数据的 `key` 是连续的小范围整数时,直接用数组即可,简单高效。但当 `key` 是其他类型(例如字符串)时,就需要借助哈希函数将 `key` 映射为数组索引,再通过桶数组存储元素,这样的结构就是哈希表。 ================================================ FILE: docs/chapter_heap/build_heap.md ================================================ # 建堆操作 在某些情况下,我们希望使用一个列表的所有元素来构建一个堆,这个过程被称为“建堆操作”。 ## 借助入堆操作实现 我们首先创建一个空堆,然后遍历列表,依次对每个元素执行“入堆操作”,即先将元素添加至堆的尾部,再对该元素执行“从底至顶”堆化。 每当一个元素入堆,堆的长度就加一。由于节点是从顶到底依次被添加进二叉树的,因此堆是“自上而下”构建的。 设元素数量为 $n$ ,每个元素的入堆操作使用 $O(\log{n})$ 时间,因此该建堆方法的时间复杂度为 $O(n \log n)$ 。 ## 通过遍历堆化实现 实际上,我们可以实现一种更为高效的建堆方法,共分为两步。 1. 将列表所有元素原封不动地添加到堆中,此时堆的性质尚未得到满足。 2. 倒序遍历堆(层序遍历的倒序),依次对每个非叶节点执行“从顶至底堆化”。 **每当堆化一个节点后,以该节点为根节点的子树就形成一个合法的子堆**。而由于是倒序遍历,因此堆是“自下而上”构建的。 之所以选择倒序遍历,是因为这样能够保证当前节点之下的子树已经是合法的子堆,这样堆化当前节点才是有效的。 值得说明的是,**由于叶节点没有子节点,因此它们天然就是合法的子堆,无须堆化**。如以下代码所示,最后一个非叶节点是最后一个节点的父节点,我们从它开始倒序遍历并执行堆化: ```src [file]{my_heap}-[class]{max_heap}-[func]{__init__} ``` ## 复杂度分析 下面,我们来尝试推算第二种建堆方法的时间复杂度。 - 假设完全二叉树的节点数量为 $n$ ,则叶节点数量为 $(n + 1) / 2$ ,其中 $/$ 为向下整除。因此需要堆化的节点数量为 $(n - 1) / 2$ 。 - 在从顶至底堆化的过程中,每个节点最多堆化到叶节点,因此最大迭代次数为二叉树高度 $\log n$ 。 将上述两者相乘,可得到建堆过程的时间复杂度为 $O(n \log n)$ 。**但这个估算结果并不准确,因为我们没有考虑到二叉树底层节点数量远多于顶层节点的性质**。 接下来我们来进行更为准确的计算。为了降低计算难度,假设给定一个节点数量为 $n$ 、高度为 $h$ 的“完美二叉树”,该假设不会影响计算结果的正确性。 ![完美二叉树的各层节点数量](build_heap.assets/heapify_operations_count.png) 如上图所示,节点“从顶至底堆化”的最大迭代次数等于该节点到叶节点的距离,而该距离正是“节点高度”。因此,我们可以对各层的“节点数量 $\times$ 节点高度”求和,**得到所有节点的堆化迭代次数的总和**。 $$ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1 $$ 化简上式需要借助中学的数列知识,先将 $T(h)$ 乘以 $2$ ,得到: $$ \begin{aligned} T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline 2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^{h}\times1 \newline \end{aligned} $$ 使用错位相减法,用下式 $2 T(h)$ 减去上式 $T(h)$ ,可得: $$ 2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h $$ 观察上式,发现 $T(h)$ 是一个等比数列,可直接使用求和公式,得到时间复杂度为: $$ \begin{aligned} T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline & = 2^{h+1} - h - 2 \newline & = O(2^h) \end{aligned} $$ 进一步,高度为 $h$ 的完美二叉树的节点数量为 $n = 2^{h+1} - 1$ ,易得复杂度为 $O(2^h) = O(n)$ 。以上推算表明,**输入列表并建堆的时间复杂度为 $O(n)$ ,非常高效**。 ================================================ FILE: docs/chapter_heap/heap.md ================================================ # 堆 堆(heap)是一种满足特定条件的完全二叉树,主要可分为两种类型,如下图所示。 - 小顶堆(min heap):任意节点的值 $\leq$ 其子节点的值。 - 大顶堆(max heap):任意节点的值 $\geq$ 其子节点的值。 ![小顶堆与大顶堆](heap.assets/min_heap_and_max_heap.png) 堆作为完全二叉树的一个特例,具有以下特性。 - 最底层节点靠左填充,其他层的节点都被填满。 - 我们将二叉树的根节点称为“堆顶”,将底层最靠右的节点称为“堆底”。 - 对于大顶堆(小顶堆),堆顶元素(根节点)的值是最大(最小)的。 ## 堆的常用操作 需要指出的是,许多编程语言提供的是优先队列(priority queue),这是一种抽象的数据结构,定义为具有优先级排序的队列。 实际上,**堆通常用于实现优先队列,大顶堆相当于元素按从大到小的顺序出队的优先队列**。从使用角度来看,我们可以将“优先队列”和“堆”看作等价的数据结构。因此,本书对两者不做特别区分,统一称作“堆”。 堆的常用操作见下表,方法名需要根据编程语言来确定。

  堆的操作效率

| 方法名 | 描述 | 时间复杂度 | | ----------- | ------------------------------------------------ | ----------- | | `push()` | 元素入堆 | $O(\log n)$ | | `pop()` | 堆顶元素出堆 | $O(\log n)$ | | `peek()` | 访问堆顶元素(对于大 / 小顶堆分别为最大 / 小值) | $O(1)$ | | `size()` | 获取堆的元素数量 | $O(1)$ | | `isEmpty()` | 判断堆是否为空 | $O(1)$ | 在实际应用中,我们可以直接使用编程语言提供的堆类(或优先队列类)。 类似于排序算法中的“从小到大排列”和“从大到小排列”,我们可以通过设置一个 `flag` 或修改 `Comparator` 实现“小顶堆”与“大顶堆”之间的转换。代码如下所示: === "Python" ```python title="heap.py" # 初始化小顶堆 min_heap, flag = [], 1 # 初始化大顶堆 max_heap, flag = [], -1 # Python 的 heapq 模块默认实现小顶堆 # 考虑将“元素取负”后再入堆,这样就可以将大小关系颠倒,从而实现大顶堆 # 在本示例中,flag = 1 时对应小顶堆,flag = -1 时对应大顶堆 # 元素入堆 heapq.heappush(max_heap, flag * 1) heapq.heappush(max_heap, flag * 3) heapq.heappush(max_heap, flag * 2) heapq.heappush(max_heap, flag * 5) heapq.heappush(max_heap, flag * 4) # 获取堆顶元素 peek: int = flag * max_heap[0] # 5 # 堆顶元素出堆 # 出堆元素会形成一个从大到小的序列 val = flag * heapq.heappop(max_heap) # 5 val = flag * heapq.heappop(max_heap) # 4 val = flag * heapq.heappop(max_heap) # 3 val = flag * heapq.heappop(max_heap) # 2 val = flag * heapq.heappop(max_heap) # 1 # 获取堆大小 size: int = len(max_heap) # 判断堆是否为空 is_empty: bool = not max_heap # 输入列表并建堆 min_heap: list[int] = [1, 3, 2, 5, 4] heapq.heapify(min_heap) ``` === "C++" ```cpp title="heap.cpp" /* 初始化堆 */ // 初始化小顶堆 priority_queue, greater> minHeap; // 初始化大顶堆 priority_queue, less> maxHeap; /* 元素入堆 */ maxHeap.push(1); maxHeap.push(3); maxHeap.push(2); maxHeap.push(5); maxHeap.push(4); /* 获取堆顶元素 */ int peek = maxHeap.top(); // 5 /* 堆顶元素出堆 */ // 出堆元素会形成一个从大到小的序列 maxHeap.pop(); // 5 maxHeap.pop(); // 4 maxHeap.pop(); // 3 maxHeap.pop(); // 2 maxHeap.pop(); // 1 /* 获取堆大小 */ int size = maxHeap.size(); /* 判断堆是否为空 */ bool isEmpty = maxHeap.empty(); /* 输入列表并建堆 */ vector input{1, 3, 2, 5, 4}; priority_queue, greater> minHeap(input.begin(), input.end()); ``` === "Java" ```java title="heap.java" /* 初始化堆 */ // 初始化小顶堆 Queue minHeap = new PriorityQueue<>(); // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); /* 元素入堆 */ maxHeap.offer(1); maxHeap.offer(3); maxHeap.offer(2); maxHeap.offer(5); maxHeap.offer(4); /* 获取堆顶元素 */ int peek = maxHeap.peek(); // 5 /* 堆顶元素出堆 */ // 出堆元素会形成一个从大到小的序列 peek = maxHeap.poll(); // 5 peek = maxHeap.poll(); // 4 peek = maxHeap.poll(); // 3 peek = maxHeap.poll(); // 2 peek = maxHeap.poll(); // 1 /* 获取堆大小 */ int size = maxHeap.size(); /* 判断堆是否为空 */ boolean isEmpty = maxHeap.isEmpty(); /* 输入列表并建堆 */ minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); ``` === "C#" ```csharp title="heap.cs" /* 初始化堆 */ // 初始化小顶堆 PriorityQueue minHeap = new(); // 初始化大顶堆(使用 lambda 表达式修改 Comparer 即可) PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); /* 元素入堆 */ maxHeap.Enqueue(1, 1); maxHeap.Enqueue(3, 3); maxHeap.Enqueue(2, 2); maxHeap.Enqueue(5, 5); maxHeap.Enqueue(4, 4); /* 获取堆顶元素 */ int peek = maxHeap.Peek();//5 /* 堆顶元素出堆 */ // 出堆元素会形成一个从大到小的序列 peek = maxHeap.Dequeue(); // 5 peek = maxHeap.Dequeue(); // 4 peek = maxHeap.Dequeue(); // 3 peek = maxHeap.Dequeue(); // 2 peek = maxHeap.Dequeue(); // 1 /* 获取堆大小 */ int size = maxHeap.Count; /* 判断堆是否为空 */ bool isEmpty = maxHeap.Count == 0; /* 输入列表并建堆 */ minHeap = new PriorityQueue([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]); ``` === "Go" ```go title="heap.go" // Go 语言中可以通过实现 heap.Interface 来构建整数大顶堆 // 实现 heap.Interface 需要同时实现 sort.Interface type intHeap []any // Push heap.Interface 的方法,实现推入元素到堆 func (h *intHeap) Push(x any) { // Push 和 Pop 使用 pointer receiver 作为参数 // 因为它们不仅会对切片的内容进行调整,还会修改切片的长度。 *h = append(*h, x.(int)) } // Pop heap.Interface 的方法,实现弹出堆顶元素 func (h *intHeap) Pop() any { // 待出堆元素存放在最后 last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Len sort.Interface 的方法 func (h *intHeap) Len() int { return len(*h) } // Less sort.Interface 的方法 func (h *intHeap) Less(i, j int) bool { // 如果实现小顶堆,则需要调整为小于号 return (*h)[i].(int) > (*h)[j].(int) } // Swap sort.Interface 的方法 func (h *intHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Top 获取堆顶元素 func (h *intHeap) Top() any { return (*h)[0] } /* Driver Code */ func TestHeap(t *testing.T) { /* 初始化堆 */ // 初始化大顶堆 maxHeap := &intHeap{} heap.Init(maxHeap) /* 元素入堆 */ // 调用 heap.Interface 的方法,来添加元素 heap.Push(maxHeap, 1) heap.Push(maxHeap, 3) heap.Push(maxHeap, 2) heap.Push(maxHeap, 4) heap.Push(maxHeap, 5) /* 获取堆顶元素 */ top := maxHeap.Top() fmt.Printf("堆顶元素为 %d\n", top) /* 堆顶元素出堆 */ // 调用 heap.Interface 的方法,来移除元素 heap.Pop(maxHeap) // 5 heap.Pop(maxHeap) // 4 heap.Pop(maxHeap) // 3 heap.Pop(maxHeap) // 2 heap.Pop(maxHeap) // 1 /* 获取堆大小 */ size := len(*maxHeap) fmt.Printf("堆元素数量为 %d\n", size) /* 判断堆是否为空 */ isEmpty := len(*maxHeap) == 0 fmt.Printf("堆是否为空 %t\n", isEmpty) } ``` === "Swift" ```swift title="heap.swift" /* 初始化堆 */ // Swift 的 Heap 类型同时支持最大堆和最小堆,且需要引入 swift-collections var heap = Heap() /* 元素入堆 */ heap.insert(1) heap.insert(3) heap.insert(2) heap.insert(5) heap.insert(4) /* 获取堆顶元素 */ var peek = heap.max()! /* 堆顶元素出堆 */ peek = heap.removeMax() // 5 peek = heap.removeMax() // 4 peek = heap.removeMax() // 3 peek = heap.removeMax() // 2 peek = heap.removeMax() // 1 /* 获取堆大小 */ let size = heap.count /* 判断堆是否为空 */ let isEmpty = heap.isEmpty /* 输入列表并建堆 */ let heap2 = Heap([1, 3, 2, 5, 4]) ``` === "JS" ```javascript title="heap.js" // JavaScript 未提供内置 Heap 类 ``` === "TS" ```typescript title="heap.ts" // TypeScript 未提供内置 Heap 类 ``` === "Dart" ```dart title="heap.dart" // Dart 未提供内置 Heap 类 ``` === "Rust" ```rust title="heap.rs" use std::collections::BinaryHeap; use std::cmp::Reverse; /* 初始化堆 */ // 初始化小顶堆 let mut min_heap = BinaryHeap::>::new(); // 初始化大顶堆 let mut max_heap = BinaryHeap::new(); /* 元素入堆 */ max_heap.push(1); max_heap.push(3); max_heap.push(2); max_heap.push(5); max_heap.push(4); /* 获取堆顶元素 */ let peek = max_heap.peek().unwrap(); // 5 /* 堆顶元素出堆 */ // 出堆元素会形成一个从大到小的序列 let peek = max_heap.pop().unwrap(); // 5 let peek = max_heap.pop().unwrap(); // 4 let peek = max_heap.pop().unwrap(); // 3 let peek = max_heap.pop().unwrap(); // 2 let peek = max_heap.pop().unwrap(); // 1 /* 获取堆大小 */ let size = max_heap.len(); /* 判断堆是否为空 */ let is_empty = max_heap.is_empty(); /* 输入列表并建堆 */ let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]); ``` === "C" ```c title="heap.c" // C 未提供内置 Heap 类 ``` === "Kotlin" ```kotlin title="heap.kt" /* 初始化堆 */ // 初始化小顶堆 var minHeap = PriorityQueue() // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } /* 元素入堆 */ maxHeap.offer(1) maxHeap.offer(3) maxHeap.offer(2) maxHeap.offer(5) maxHeap.offer(4) /* 获取堆顶元素 */ var peek = maxHeap.peek() // 5 /* 堆顶元素出堆 */ // 出堆元素会形成一个从大到小的序列 peek = maxHeap.poll() // 5 peek = maxHeap.poll() // 4 peek = maxHeap.poll() // 3 peek = maxHeap.poll() // 2 peek = maxHeap.poll() // 1 /* 获取堆大小 */ val size = maxHeap.size /* 判断堆是否为空 */ val isEmpty = maxHeap.isEmpty() /* 输入列表并建堆 */ minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) ``` === "Ruby" ```ruby title="heap.rb" # Ruby 未提供内置 Heap 类 ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20min_heap,%20flag%20%3D%20%5B%5D,%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20max_heap,%20flag%20%3D%20%5B%5D,%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20Python%20%E7%9A%84%20heapq%20%E6%A8%A1%E5%9D%97%E9%BB%98%E8%AE%A4%E5%AE%9E%E7%8E%B0%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%80%83%E8%99%91%E5%B0%86%E2%80%9C%E5%85%83%E7%B4%A0%E5%8F%96%E8%B4%9F%E2%80%9D%E5%90%8E%E5%86%8D%E5%85%A5%E5%A0%86%EF%BC%8C%E8%BF%99%E6%A0%B7%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%B0%86%E5%A4%A7%E5%B0%8F%E5%85%B3%E7%B3%BB%E9%A2%A0%E5%80%92%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%AE%9E%E7%8E%B0%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E5%9C%A8%E6%9C%AC%E7%A4%BA%E4%BE%8B%E4%B8%AD%EF%BC%8Cflag%20%3D%201%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%B0%8F%E9%A1%B6%E5%A0%86%EF%BC%8Cflag%20%3D%20-1%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%201%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%203%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%202%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%205%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20flag%20*%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%0A%20%20%20%20%23%20%E5%87%BA%E5%A0%86%E5%85%83%E7%B4%A0%E4%BC%9A%E5%BD%A2%E6%88%90%E4%B8%80%E4%B8%AA%E4%BB%8E%E5%A4%A7%E5%88%B0%E5%B0%8F%E7%9A%84%E5%BA%8F%E5%88%97%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E5%88%97%E8%A1%A8%E5%B9%B6%E5%BB%BA%E5%A0%86%0A%20%20%20%20min_heap%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## 堆的实现 下文实现的是大顶堆。若要将其转换为小顶堆,只需将所有大小逻辑判断进行逆转(例如,将 $\geq$ 替换为 $\leq$ )。感兴趣的读者可以自行实现。 ### 堆的存储与表示 “二叉树”章节讲过,完全二叉树非常适合用数组来表示。由于堆正是一种完全二叉树,**因此我们将采用数组来存储堆**。 当使用数组表示二叉树时,元素代表节点值,索引代表节点在二叉树中的位置。**节点指针通过索引映射公式来实现**。 如下图所示,给定索引 $i$ ,其左子节点的索引为 $2i + 1$ ,右子节点的索引为 $2i + 2$ ,父节点的索引为 $(i - 1) / 2$(向下整除)。当索引越界时,表示空节点或节点不存在。 ![堆的表示与存储](heap.assets/representation_of_heap.png) 我们可以将索引映射公式封装成函数,方便后续使用: ```src [file]{my_heap}-[class]{max_heap}-[func]{parent} ``` ### 访问堆顶元素 堆顶元素即为二叉树的根节点,也就是列表的首个元素: ```src [file]{my_heap}-[class]{max_heap}-[func]{peek} ``` ### 元素入堆 给定元素 `val` ,我们首先将其添加到堆底。添加之后,由于 `val` 可能大于堆中其他元素,堆的成立条件可能已被破坏,**因此需要修复从插入节点到根节点的路径上的各个节点**,这个操作被称为堆化(heapify)。 考虑从入堆节点开始,**从底至顶执行堆化**。如下图所示,我们比较插入节点与其父节点的值,如果插入节点更大,则将它们交换。然后继续执行此操作,从底至顶修复堆中的各个节点,直至越过根节点或遇到无须交换的节点时结束。 === "<1>" ![元素入堆步骤](heap.assets/heap_push_step1.png) === "<2>" ![heap_push_step2](heap.assets/heap_push_step2.png) === "<3>" ![heap_push_step3](heap.assets/heap_push_step3.png) === "<4>" ![heap_push_step4](heap.assets/heap_push_step4.png) === "<5>" ![heap_push_step5](heap.assets/heap_push_step5.png) === "<6>" ![heap_push_step6](heap.assets/heap_push_step6.png) === "<7>" ![heap_push_step7](heap.assets/heap_push_step7.png) === "<8>" ![heap_push_step8](heap.assets/heap_push_step8.png) === "<9>" ![heap_push_step9](heap.assets/heap_push_step9.png) 设节点总数为 $n$ ,则树的高度为 $O(\log n)$ 。由此可知,堆化操作的循环轮数最多为 $O(\log n)$ ,**元素入堆操作的时间复杂度为 $O(\log n)$** 。代码如下所示: ```src [file]{my_heap}-[class]{max_heap}-[func]{sift_up} ``` ### 堆顶元素出堆 堆顶元素是二叉树的根节点,即列表首元素。如果我们直接从列表中删除首元素,那么二叉树中所有节点的索引都会发生变化,这将使得后续使用堆化进行修复变得困难。为了尽量减少元素索引的变动,我们采用以下操作步骤。 1. 交换堆顶元素与堆底元素(交换根节点与最右叶节点)。 2. 交换完成后,将堆底从列表中删除(注意,由于已经交换,因此实际上删除的是原来的堆顶元素)。 3. 从根节点开始,**从顶至底执行堆化**。 如下图所示,**“从顶至底堆化”的操作方向与“从底至顶堆化”相反**,我们将根节点的值与其两个子节点的值进行比较,将最大的子节点与根节点交换。然后循环执行此操作,直到越过叶节点或遇到无须交换的节点时结束。 === "<1>" ![堆顶元素出堆步骤](heap.assets/heap_pop_step1.png) === "<2>" ![heap_pop_step2](heap.assets/heap_pop_step2.png) === "<3>" ![heap_pop_step3](heap.assets/heap_pop_step3.png) === "<4>" ![heap_pop_step4](heap.assets/heap_pop_step4.png) === "<5>" ![heap_pop_step5](heap.assets/heap_pop_step5.png) === "<6>" ![heap_pop_step6](heap.assets/heap_pop_step6.png) === "<7>" ![heap_pop_step7](heap.assets/heap_pop_step7.png) === "<8>" ![heap_pop_step8](heap.assets/heap_pop_step8.png) === "<9>" ![heap_pop_step9](heap.assets/heap_pop_step9.png) === "<10>" ![heap_pop_step10](heap.assets/heap_pop_step10.png) 与元素入堆操作相似,堆顶元素出堆操作的时间复杂度也为 $O(\log n)$ 。代码如下所示: ```src [file]{my_heap}-[class]{max_heap}-[func]{sift_down} ``` ## 堆的常见应用 - **优先队列**:堆通常作为实现优先队列的首选数据结构,其入队和出队操作的时间复杂度均为 $O(\log n)$ ,而建堆操作为 $O(n)$ ,这些操作都非常高效。 - **堆排序**:给定一组数据,我们可以用它们建立一个堆,然后不断地执行元素出堆操作,从而得到有序数据。然而,我们通常会使用一种更优雅的方式实现堆排序,详见“堆排序”章节。 - **获取最大的 $k$ 个元素**:这是一个经典的算法问题,同时也是一种典型应用,例如选择热度前 10 的新闻作为微博热搜,选取销量前 10 的商品等。 ================================================ FILE: docs/chapter_heap/index.md ================================================ # 堆 ![堆](../assets/covers/chapter_heap.jpg) !!! abstract 堆就像是山岳峰峦,层叠起伏、形态各异。 座座山峰高低错落,而最高的山峰总是最先映入眼帘。 ================================================ FILE: docs/chapter_heap/summary.md ================================================ # 小结 ### 重点回顾 - 堆是一棵完全二叉树,根据成立条件可分为大顶堆和小顶堆。大(小)顶堆的堆顶元素是最大(小)的。 - 优先队列的定义是具有出队优先级的队列,通常使用堆来实现。 - 堆的常用操作及其对应的时间复杂度包括:元素入堆 $O(\log n)$、堆顶元素出堆 $O(\log n)$ 和访问堆顶元素 $O(1)$ 等。 - 完全二叉树非常适合用数组表示,因此我们通常使用数组来存储堆。 - 堆化操作用于维护堆的性质,在入堆和出堆操作中都会用到。 - 输入 $n$ 个元素并建堆的时间复杂度可以优化至 $O(n)$ ,非常高效。 - Top-k 是一个经典算法问题,可以使用堆数据结构高效解决,时间复杂度为 $O(n \log k)$ 。 ### Q & A **Q**:数据结构的“堆”与内存管理的“堆”是同一个概念吗? 两者不是同一个概念,只是碰巧都叫“堆”。计算机系统内存中的堆是动态内存分配的一部分,程序在运行时可以使用它来存储数据。程序可以请求一定量的堆内存,用于存储如对象和数组等复杂结构。当这些数据不再需要时,程序需要释放这些内存,以防止内存泄漏。相较于栈内存,堆内存的管理和使用需要更谨慎,使用不当可能会导致内存泄漏和野指针等问题。 ================================================ FILE: docs/chapter_heap/top_k.md ================================================ # Top-k 问题 !!! question 给定一个长度为 $n$ 的无序数组 `nums` ,请返回数组中最大的 $k$ 个元素。 对于该问题,我们先介绍两种思路比较直接的解法,再介绍效率更高的堆解法。 ## 方法一:遍历选择 我们可以进行下图所示的 $k$ 轮遍历,分别在每轮中提取第 $1$、$2$、$\dots$、$k$ 大的元素,时间复杂度为 $O(nk)$ 。 此方法只适用于 $k \ll n$ 的情况,因为当 $k$ 与 $n$ 比较接近时,其时间复杂度趋向于 $O(n^2)$ ,非常耗时。 ![遍历寻找最大的 k 个元素](top_k.assets/top_k_traversal.png) !!! tip 当 $k = n$ 时,我们可以得到完整的有序序列,此时等价于“选择排序”算法。 ## 方法二:排序 如下图所示,我们可以先对数组 `nums` 进行排序,再返回最右边的 $k$ 个元素,时间复杂度为 $O(n \log n)$ 。 显然,该方法“超额”完成任务了,因为我们只需找出最大的 $k$ 个元素即可,而不需要排序其他元素。 ![排序寻找最大的 k 个元素](top_k.assets/top_k_sorting.png) ## 方法三:堆 我们可以基于堆更加高效地解决 Top-k 问题,流程如下图所示。 1. 初始化一个小顶堆,其堆顶元素最小。 2. 先将数组的前 $k$ 个元素依次入堆。 3. 从第 $k + 1$ 个元素开始,若当前元素大于堆顶元素,则将堆顶元素出堆,并将当前元素入堆。 4. 遍历完成后,堆中保存的就是最大的 $k$ 个元素。 === "<1>" ![基于堆寻找最大的 k 个元素](top_k.assets/top_k_heap_step1.png) === "<2>" ![top_k_heap_step2](top_k.assets/top_k_heap_step2.png) === "<3>" ![top_k_heap_step3](top_k.assets/top_k_heap_step3.png) === "<4>" ![top_k_heap_step4](top_k.assets/top_k_heap_step4.png) === "<5>" ![top_k_heap_step5](top_k.assets/top_k_heap_step5.png) === "<6>" ![top_k_heap_step6](top_k.assets/top_k_heap_step6.png) === "<7>" ![top_k_heap_step7](top_k.assets/top_k_heap_step7.png) === "<8>" ![top_k_heap_step8](top_k.assets/top_k_heap_step8.png) === "<9>" ![top_k_heap_step9](top_k.assets/top_k_heap_step9.png) 示例代码如下: ```src [file]{top_k}-[class]{}-[func]{top_k_heap} ``` 总共执行了 $n$ 轮入堆和出堆,堆的最大长度为 $k$ ,因此时间复杂度为 $O(n \log k)$ 。该方法的效率很高,当 $k$ 较小时,时间复杂度趋向 $O(n)$ ;当 $k$ 较大时,时间复杂度不会超过 $O(n \log n)$ 。 另外,该方法适用于动态数据流的使用场景。在不断加入数据时,我们可以持续维护堆内的元素,从而实现最大的 $k$ 个元素的动态更新。 ================================================ FILE: docs/chapter_hello_algo/index.md ================================================ --- comments: true icon: material/rocket-launch-outline --- # 序 几年前,我在力扣上分享了“剑指 Offer”系列题解,受到了许多读者的鼓励和支持。在与读者交流期间,我最常被问的一个问题是“如何入门算法”。逐渐地,我对这个问题产生了浓厚的兴趣。 两眼一抹黑地刷题似乎是最受欢迎的方法,简单、直接且有效。然而刷题就如同玩“扫雷”游戏,自学能力强的人能够顺利将地雷逐个排掉,而基础不足的人很可能被炸得满头是包,并在挫折中步步退缩。通读教材也是一种常见做法,但对于面向求职的人来说,毕业论文、投递简历、准备笔试和面试已经消耗了大部分精力,啃厚重的书往往变成了一项艰巨的挑战。 如果你也面临类似的困扰,那么很幸运这本书“找”到了你。本书是我对这个问题给出的答案,即使不是最优解,也至少是一次积极的尝试。本书虽然不足以让你直接拿到 Offer,但会引导你探索数据结构与算法的“知识地图”,带你了解不同“地雷”的形状、大小和分布位置,让你掌握各种“排雷方法”。有了这些本领,相信你可以更加自如地刷题和阅读文献,逐步构建起完整的知识体系。 我深深赞同费曼教授所言:“Knowledge isn't free. You have to pay attention.”从这个意义上看,这本书并非完全“免费”。为了不辜负你为本书所付出的宝贵“注意力”,我会竭尽所能,投入最大的“注意力”来完成本书的创作。 本人自知学疏才浅,书中内容虽然已经过一段时间的打磨,但一定仍有许多错误,恳请各位老师和同学批评指正。 ![Hello 算法](../assets/covers/chapter_hello_algo.jpg){ class="cover-image" }

Hello,算法!

计算机的出现给世界带来了巨大变革,它凭借高速的计算能力和出色的可编程性,成为了执行算法与处理数据的理想媒介。无论是电子游戏的逼真画面、自动驾驶的智能决策,还是 AlphaGo 的精彩棋局、ChatGPT 的自然交互,这些应用都是算法在计算机上的精妙演绎。 事实上,在计算机问世之前,算法和数据结构就已经存在于世界的各个角落。早期的算法相对简单,例如古代的计数方法和工具制作步骤等。随着文明的进步,算法逐渐变得更加精细和复杂。从巧夺天工的匠人技艺、到解放生产力的工业产品、再到宇宙运行的科学规律,几乎每一件平凡或令人惊叹的事物背后,都隐藏着精妙的算法思想。 同样,数据结构无处不在:大到社会网络,小到地铁线路,许多系统都可以建模为“图”;大到一个国家,小到一个家庭,社会的主要组织形式呈现出“树”的特征;冬天的衣服就像“栈”,最先穿上的最后才能脱下;羽毛球筒则如同“队列”,一端放入、另一端取出;字典就像一个“哈希表”,能够快速查找目标词条。 本书旨在通过清晰易懂的动画图解和可运行的代码示例,使读者理解算法和数据结构的核心概念,并能够通过编程来实现它们。在此基础上,本书致力于揭示算法在复杂世界中的生动体现,展现算法之美。希望本书能够帮助到你! ================================================ FILE: docs/chapter_introduction/algorithms_are_everywhere.md ================================================ # 算法无处不在 当我们听到“算法”这个词时,很自然地会想到数学。然而实际上,许多算法并不涉及复杂数学,而是更多地依赖基本逻辑,这些逻辑在我们的日常生活中处处可见。 在正式探讨算法之前,有一个有趣的事实值得分享:**你已经在不知不觉中学会了许多算法,并习惯将它们应用到日常生活中了**。下面我将举几个具体的例子来证实这一点。 **例一:查字典**。在字典里,每个汉字都对应一个拼音,而字典是按照拼音字母顺序排列的。假设我们需要查找一个拼音首字母为 $r$ 的字,通常会按照下图所示的方式实现。 1. 翻开字典约一半的页数,查看该页的首字母是什么,假设首字母为 $m$ 。 2. 由于在拼音字母表中 $r$ 位于 $m$ 之后,所以排除字典前半部分,查找范围缩小到后半部分。 3. 不断重复步骤 `1.` 和步骤 `2.` ,直至找到拼音首字母为 $r$ 的页码为止。 === "<1>" ![查字典步骤](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png) === "<2>" ![binary_search_dictionary_step2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png) === "<3>" ![binary_search_dictionary_step3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png) === "<4>" ![binary_search_dictionary_step4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png) === "<5>" ![binary_search_dictionary_step5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png) 查字典这个小学生必备技能,实际上就是著名的“二分查找”算法。从数据结构的角度,我们可以把字典视为一个已排序的“数组”;从算法的角度,我们可以将上述查字典的一系列操作看作“二分查找”。 **例二:整理扑克**。我们在打牌时,每局都需要整理手中的扑克牌,使其从小到大排列,实现流程如下图所示。 1. 将扑克牌划分为“有序”和“无序”两部分,并假设初始状态下最左 1 张扑克牌已经有序。 2. 在无序部分抽出一张扑克牌,插入至有序部分的正确位置;完成后最左 2 张扑克已经有序。 3. 不断循环步骤 `2.` ,每一轮将一张扑克牌从无序部分插入至有序部分,直至所有扑克牌都有序。 ![扑克排序步骤](algorithms_are_everywhere.assets/playing_cards_sorting.png) 上述整理扑克牌的方法本质上是“插入排序”算法,它在处理小型数据集时非常高效。许多编程语言的排序库函数中都有插入排序的身影。 **例三:货币找零**。假设我们在超市购买了 $69$ 元的商品,给了收银员 $100$ 元,则收银员需要找我们 $31$ 元。他会很自然地完成如下图所示的思考。 1. 可选项是比 $31$ 元面值更小的货币,包括 $1$ 元、$5$ 元、$10$ 元、$20$ 元。 2. 从可选项中拿出最大的 $20$ 元,剩余 $31 - 20 = 11$ 元。 3. 从剩余可选项中拿出最大的 $10$ 元,剩余 $11 - 10 = 1$ 元。 4. 从剩余可选项中拿出最大的 $1$ 元,剩余 $1 - 1 = 0$ 元。 5. 完成找零,方案为 $20 + 10 + 1 = 31$ 元。 ![货币找零过程](algorithms_are_everywhere.assets/greedy_change.png) 在以上步骤中,我们每一步都采取当前看来最好的选择(尽可能用大面额的货币),最终得到了可行的找零方案。从数据结构与算法的角度看,这种方法本质上是“贪心”算法。 小到烹饪一道菜,大到星际航行,几乎所有问题的解决都离不开算法。计算机的出现使得我们能够通过编程将数据结构存储在内存中,同时编写代码调用 CPU 和 GPU 执行算法。这样一来,我们就能把生活中的问题转移到计算机上,以更高效的方式解决各种复杂问题。 !!! tip 如果你对数据结构、算法、数组和二分查找等概念仍感到一知半解,请继续往下阅读,本书将引导你迈入数据结构与算法的知识殿堂。 ================================================ FILE: docs/chapter_introduction/index.md ================================================ # 初识算法 ![初识算法](../assets/covers/chapter_introduction.jpg) !!! abstract 一位少女翩翩起舞,与数据交织在一起,裙摆上飘扬着算法的旋律。 她邀请你共舞,请紧跟她的步伐,踏入充满逻辑与美感的算法世界。 ================================================ FILE: docs/chapter_introduction/summary.md ================================================ # 小结 ### 重点回顾 - 算法在日常生活中无处不在,并不是遥不可及的高深知识。实际上,我们已经在不知不觉中学会了许多算法,用以解决生活中的大小问题。 - 查字典的原理与二分查找算法相一致。二分查找算法体现了分而治之的重要算法思想。 - 整理扑克的过程与插入排序算法非常类似。插入排序算法适合排序小型数据集。 - 货币找零的步骤本质上是贪心算法,每一步都采取当前看来最好的选择。 - 算法是在有限时间内解决特定问题的一组指令或操作步骤,而数据结构是计算机中组织和存储数据的方式。 - 数据结构与算法紧密相连。数据结构是算法的基石,而算法为数据结构注入生命力。 - 我们可以将数据结构与算法类比为拼装积木,积木代表数据,积木的形状和连接方式等代表数据结构,拼装积木的步骤则对应算法。 ### Q & A **Q**:作为一名程序员,我在日常工作中从未用算法解决过问题,常用算法都被编程语言封装好了,直接用就可以了;这是否意味着我们工作中的问题还没有到达需要算法的程度? 如果把具体的工作技能比作是武功的“招式”的话,那么基础科目应该更像是“内功”。 我认为学算法(以及其他基础科目)的意义不是在于在工作中从零实现它,而是基于学到的知识,在解决问题时能够作出专业的反应和判断,从而提升工作的整体质量。举一个简单例子,每种编程语言都内置了排序函数: - 如果我们没有学过数据结构与算法,那么给定任何数据,我们可能都塞给这个排序函数去做了。运行顺畅、性能不错,看上去并没有什么问题。 - 但如果学过算法,我们就会知道内置排序函数的时间复杂度是 $O(n \log n)$ ;而如果给定的数据是固定位数的整数(例如学号),那么我们就可以用效率更高的“基数排序”来做,将时间复杂度降为 $O(nk)$ ,其中 $k$ 为位数。当数据体量很大时,节省出来的运行时间就能创造较大价值(成本降低、体验变好等)。 在工程领域中,大量问题是难以达到最优解的,许多问题只是被“差不多”地解决了。问题的难易程度一方面取决于问题本身的性质,另一方面也取决于观测问题的人的知识储备。人的知识越完备、经验越多,分析问题就会越深入,问题就能被解决得更优雅。 ================================================ FILE: docs/chapter_introduction/what_is_dsa.md ================================================ # 算法是什么 ## 算法定义 算法(algorithm)是在有限时间内解决特定问题的一组指令或操作步骤,它具有以下特性。 - 问题是明确的,包含清晰的输入和输出定义。 - 具有可行性,能够在有限步骤、时间和内存空间下完成。 - 各步骤都有确定的含义,在相同的输入和运行条件下,输出始终相同。 ## 数据结构定义 数据结构(data structure)是组织和存储数据的方式,涵盖数据内容、数据之间关系和数据操作方法,它具有以下设计目标。 - 空间占用尽量少,以节省计算机内存。 - 数据操作尽可能快速,涵盖数据访问、添加、删除、更新等。 - 提供简洁的数据表示和逻辑信息,以便算法高效运行。 **数据结构设计是一个充满权衡的过程**。如果想在某方面取得提升,往往需要在另一方面作出妥协。下面举两个例子。 - 链表相较于数组,在数据添加和删除操作上更加便捷,但牺牲了数据访问速度。 - 图相较于链表,提供了更丰富的逻辑信息,但需要占用更大的内存空间。 ## 数据结构与算法的关系 如下图所示,数据结构与算法高度相关、紧密结合,具体表现在以下三个方面。 - 数据结构是算法的基石。数据结构为算法提供了结构化存储的数据,以及操作数据的方法。 - 算法为数据结构注入生命力。数据结构本身仅存储数据信息,结合算法才能解决特定问题。 - 算法通常可以基于不同的数据结构实现,但执行效率可能相差很大,选择合适的数据结构是关键。 ![数据结构与算法的关系](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png) 数据结构与算法犹如下图所示的拼装积木。一套积木,除了包含许多零件之外,还附有详细的组装说明书。我们按照说明书一步步操作,就能组装出精美的积木模型。 ![拼装积木](what_is_dsa.assets/assembling_blocks.png) 两者的详细对应关系如下表所示。

  将数据结构与算法类比为拼装积木

| 数据结构与算法 | 拼装积木 | | -------------- | ---------------------------------------- | | 输入数据 | 未拼装的积木 | | 数据结构 | 积木组织形式,包括形状、大小、连接方式等 | | 算法 | 把积木拼成目标形态的一系列操作步骤 | | 输出数据 | 积木模型 | 值得说明的是,数据结构与算法是独立于编程语言的。正因如此,本书得以提供基于多种编程语言的实现。 !!! tip "约定俗成的简称" 在实际讨论时,我们通常会将“数据结构与算法”简称为“算法”。比如众所周知的 LeetCode 算法题目,实际上同时考查数据结构和算法两方面的知识。 ================================================ FILE: docs/chapter_paperbook/index.md ================================================ --- comments: true icon: material/book-open-page-variant status: new --- # 纸质书 经过长时间的打磨,《Hello 算法》纸质书终于发布了!此时的心情可以用一句诗来形容:

追风赶月莫停留,平芜尽处是春山。

![](index.assets/paper_book_overview.jpg){ class="animation-figure" } 以下视频展示了纸质书,并且包含我的一些思考: - 学习数据结构与算法的重要性。 - 为什么在纸质书中选择 Python。 - 对知识分享的理解。 > 新人 UP 主,请多多关照、一键三连~谢谢!
附纸质书快照: ![](index.assets/paper_book_chapter_heap.jpg){ class="animation-figure" } ![](index.assets/paper_book_avl_tree.jpg){ class="animation-figure" } ## 优势与不足 总结一下纸质书可能会给大家带来惊喜的地方: - 采用全彩印刷,能够原汁原味地发挥出本书“动画图解”的优势。 - 考究纸张材质,既保证色彩高度还原,也保留纸质书特有的质感。 - 纸质版比网页版的格式更加规范,例如图中的公式使用斜体。 - 在不提升定价的前提下,附赠思维导图折页、书签。 - 纸质书、网页版、PDF 版内容同步,随意切换阅读。 !!! tip 由于纸质书和网页版的同步难度较大,因此可能会有一些细节上的不同,请您见谅! 当然,纸质书也有一些值得大家入手前考虑的地方: - 使用 Python 语言,可能不匹配你的主语言(可以把 Python 看作伪代码,重在理解思路)。 - 全彩印刷虽然大幅提升了图解和代码的阅读体验,但价格会比黑白印刷高一些。 !!! tip “印刷质量”和“价格”就像算法中的“时间效率”和“空间效率”,难以两全。而我认为,“印刷质量”对应的是“时间效率”,更应该被注重。 ## 购买链接 如果你对纸质书感兴趣,可以考虑入手一本。我们为大家争取到了新书 5 折优惠,请见[此链接](https://3.cn/1X-qmTD3)或扫描以下二维码: ![](index.assets/book_jd_link.jpg){ class="animation-figure" } ## 尾记 起初,我低估了纸质书出版的工作量,以为只要维护好了开源项目,纸质版就可以通过某些自动化手段生成出来。实践证明,纸质书的生产流程与开源项目的更新机制存在很大的不同,两者之间的转化需要做许多额外工作。 一本书的初稿与达到出版标准的定稿之间仍有较长距离,需要出版社(策划、编辑、设计、市场等)与作者的通力合作、长期雕琢。在此感谢图灵策划编辑王军花、以及人民邮电出版社和图灵社区每位参与本书出版流程的工作人员! 希望这本书能够帮助到你! ================================================ FILE: docs/chapter_preface/about_the_book.md ================================================ # 关于本书 本项目旨在创建一本开源、免费、对新手友好的数据结构与算法入门教程。 - 全书采用动画图解,内容清晰易懂、学习曲线平滑,引导初学者探索数据结构与算法的知识地图。 - 源代码可一键运行,帮助读者在练习中提升编程技能,了解算法工作原理和数据结构底层实现。 - 提倡读者互助学习,欢迎大家在评论区提出问题与分享见解,在交流讨论中共同进步。 ## 读者对象 若你是算法初学者,从未接触过算法,或者已经有一些刷题经验,对数据结构与算法有模糊的认识,在会与不会之间反复横跳,那么本书正是为你量身定制的! 如果你已经积累一定的刷题量,熟悉大部分题型,那么本书可助你回顾与梳理算法知识体系,仓库源代码可以当作“刷题工具库”或“算法字典”来使用。 若你是算法“大神”,我们期待收到你的宝贵建议,或者[一起参与创作](https://www.hello-algo.com/chapter_appendix/contribution/)。 !!! success "前置条件" 你需要至少具备任一语言的编程基础,能够阅读和编写简单代码。 ## 内容结构 本书的主要内容如下图所示。 - **复杂度分析**:数据结构和算法的评价维度与方法。时间复杂度和空间复杂度的推算方法、常见类型、示例等。 - **数据结构**:基本数据类型和数据结构的分类方法。数组、链表、栈、队列、哈希表、树、堆、图等数据结构的定义、优缺点、常用操作、常见类型、典型应用、实现方法等。 - **算法**:搜索、排序、分治、回溯、动态规划、贪心等算法的定义、优缺点、效率、应用场景、解题步骤和示例问题等。 ![本书主要内容](about_the_book.assets/hello_algo_mindmap.png) ## 致谢 本书在开源社区众多贡献者的共同努力下不断完善。感谢每一位投入时间与精力的撰稿人,他们是(按照 GitHub 自动生成的顺序):krahets、coderonion、Gonglja、nuomi1、Reanon、justin-tse、hpstory、danielsss、curtishd、night-cruise、S-N-O-R-L-A-X、rongyi、msk397、gvenusleo、khoaxuantu、rivertwilight、K3v123、gyt95、zhuoqinyue、yuelinxin、Zuoxun、mingXta、Phoenix0415、FangYuan33、GN-Yu、longsizhuo、IsChristina、xBLACKICEx、guowei-gong、Cathay-Chen、pengchzn、QiLOL、magentaqin、hello-ikun、JoseHung、qualifier1024、thomasq0、sunshinesDL、L-Super、Guanngxu、Transmigration-zhou、WSL0809、Slone123c、lhxsm、yuan0221、what-is-me、Shyam-Chen、theNefelibatas、longranger2、codeberg-user、xiongsp、JeffersonHuang、prinpal、seven1240、Wonderdch、malone6、xiaomiusa87、gaofer、bluebean-cloud、a16su、SamJin98、hongyun-robot、nanlei、XiaChuerwu、yd-j、iron-irax、mgisr、steventimes、junminhong、heshuyue、danny900714、MolDuM、Nigh、Dr-XYZ、XC-Zero、reeswell、PXG-XPG、NI-SW、Horbin-Magician、Enlightenus、YangXuanyi、beatrix-chan、DullSword、xjr7670、jiaxianhua、qq909244296、iStig、boloboloda、hts0000、gledfish、wenjianmin、keshida、kilikilikid、lclc6、lwbaptx、linyejoe2、liuxjerry、llql1211、fbigm、echo1937、szu17dmy、dshlstarr、Yucao-cy、coderlef、czruby、bongbongbakudan、beintentional、ZongYangL、ZhongYuuu、ZhongGuanbin、hezhizhen、linzeyan、ZJKung、luluxia、xb534、ztkuaikuai、yw-1021、ElaBosak233、baagod、zhouLion、yishangzhang、yi427、yanedie、yabo083、weibk、wangwang105、th1nk3r-ing、tao363、4yDX3906、syd168、sslmj2020、smilelsb、siqyka、selear、sdshaoda、Xi-Row、popozhu、nuquist19、noobcodemaker、XiaoK29、chadyi、lyl625760、lucaswangdev、0130w、shanghai-Jerry、EJackYang、Javesun99、eltociear、lipusheng、KNChiu、BlindTerran、ShiMaRing、lovelock、FreddieLi、FloranceYeh、fanchenggang、gltianwen、goerll、nedchu、curly210102、CuB3y0nd、KraHsu、CarrotDLaw、youshaoXG、bubble9um、Asashishi、Asa0oo0o0o、fanenr、eagleanurag、akshiterate、52coder、foursevenlove、KorsChen、GaochaoZhu、hopkings2008、yang-le、realwujing、Evilrabbit520、Umer-Jahangir、Turing-1024-Lee、Suremotoo、paoxiaomooo、Chieko-Seren、Allen-Scai、ymmmas、Risuntsy、Richard-Zhang1019、RafaelCaso、qingpeng9802、primexiao、Urbaner3、zhongfq、nidhoggfgg、MwumLi、CreatorMetaSky、martinx、ZnYang2018、hugtyftg、logan-qiu、psychelzh、Keynman、KeiichiKasai 和 KawaiiAsh。 本书的代码审阅工作由 coderonion、curtishd、Gonglja、gvenusleo、hpstory、justin-tse、khoaxuantu、krahets、night-cruise、nuomi1、Reanon 和 rongyi 完成(按照首字母顺序排列)。感谢他们付出的时间与精力,正是他们确保了各语言代码的规范与统一。 本书的繁体中文版由 Shyam-Chen 和 Dr-XYZ 审阅,英文版由 yuelinxin、K3v123、QiLOL、Phoenix0415、SamJin98、yanedie、RafaelCaso、pengchzn、thomasq0 和 magentaqin 审阅,日文版由 eltociear 审阅。正是因为他们的持续贡献,这本书才能够服务于更广泛的读者群体,感谢他们。 本书的 ePub 电子书生成工具由 zhongfq 开发。感谢他的贡献,为读者提供了更加自由的阅读方式。 在本书的创作过程中,我得到了许多人的帮助。 - 感谢我在公司的导师李汐博士,在一次畅谈中你鼓励我“快行动起来”,坚定了我写这本书的决心; - 感谢我的女朋友泡泡作为本书的首位读者,从算法小白的角度提出许多宝贵建议,使得本书更适合新手阅读; - 感谢腾宝、琦宝、飞宝为本书起了一个富有创意的名字,唤起大家写下第一行代码“Hello World!”的美好回忆; - 感谢校铨在知识产权方面提供的专业帮助,这对本开源书的完善起到了重要作用; - 感谢苏潼为本书设计了精美的封面和 logo ,并在我的强迫症的驱使下多次耐心修改; - 感谢 @squidfunk 提供的排版建议,以及他开发的开源文档主题 [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master) 。 在写作过程中,我阅读了许多关于数据结构与算法的教材和文章。这些作品为本书提供了优秀的范本,确保了本书内容的准确性与品质。在此感谢所有老师和前辈的杰出贡献! 本书倡导手脑并用的学习方式,在这一点上我深受[《动手学深度学习》](https://github.com/d2l-ai/d2l-zh)的启发。在此向各位读者强烈推荐这本优秀的著作。 **衷心感谢我的父母,正是你们一直以来的支持与鼓励,让我有机会做这件富有趣味的事**。 ================================================ FILE: docs/chapter_preface/index.md ================================================ # 前言 ![前言](../assets/covers/chapter_preface.jpg) !!! abstract 算法犹如美妙的交响乐,每一行代码都像韵律般流淌。 愿这本书在你的脑海中轻轻响起,留下独特而深刻的旋律。 ================================================ FILE: docs/chapter_preface/suggestions.md ================================================ # 如何使用本书 !!! tip 为了获得最佳的阅读体验,建议你通读本节内容。 ## 行文风格约定 - 标题后标注 `*` 的是选读章节,内容相对困难。如果你的时间有限,可以先跳过。 - 专业术语会使用黑体(纸质版和 PDF 版)或添加下划线(网页版),例如数组(array)。建议记住它们,以便阅读文献。 - 重点内容和总结性语句会 **加粗**,这类文字值得特别关注。 - 有特指含义的词句会使用“引号”标注,以避免歧义。 - 当涉及编程语言之间不一致的名词时,本书均以 Python 为准,例如使用 `None` 来表示“空”。 - 本书部分放弃了编程语言的注释规范,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。 === "Python" ```python title="" """标题注释,用于标注函数、类、测试样例等""" # 内容注释,用于详解代码 """ 多行 注释 """ ``` === "C++" ```cpp title="" /* 标题注释,用于标注函数、类、测试样例等 */ // 内容注释,用于详解代码 /** * 多行 * 注释 */ ``` === "Java" ```java title="" /* 标题注释,用于标注函数、类、测试样例等 */ // 内容注释,用于详解代码 /** * 多行 * 注释 */ ``` === "C#" ```csharp title="" /* 标题注释,用于标注函数、类、测试样例等 */ // 内容注释,用于详解代码 /** * 多行 * 注释 */ ``` === "Go" ```go title="" /* 标题注释,用于标注函数、类、测试样例等 */ // 内容注释,用于详解代码 /** * 多行 * 注释 */ ``` === "Swift" ```swift title="" /* 标题注释,用于标注函数、类、测试样例等 */ // 内容注释,用于详解代码 /** * 多行 * 注释 */ ``` === "JS" ```javascript title="" /* 标题注释,用于标注函数、类、测试样例等 */ // 内容注释,用于详解代码 /** * 多行 * 注释 */ ``` === "TS" ```typescript title="" /* 标题注释,用于标注函数、类、测试样例等 */ // 内容注释,用于详解代码 /** * 多行 * 注释 */ ``` === "Dart" ```dart title="" /* 标题注释,用于标注函数、类、测试样例等 */ // 内容注释,用于详解代码 /** * 多行 * 注释 */ ``` === "Rust" ```rust title="" /* 标题注释,用于标注函数、类、测试样例等 */ // 内容注释,用于详解代码 // 多行 // 注释 ``` === "C" ```c title="" /* 标题注释,用于标注函数、类、测试样例等 */ // 内容注释,用于详解代码 /** * 多行 * 注释 */ ``` === "Kotlin" ```kotlin title="" /* 标题注释,用于标注函数、类、测试样例等 */ // 内容注释,用于详解代码 /** * 多行 * 注释 */ ``` === "Ruby" ```ruby title="" ### 标题注释,用于标注函数、类、测试样例等 ### # 内容注释,用于详解代码 # 多行 # 注释 ``` ## 在动画图解中高效学习 相较于文字,视频和图片具有更高的信息密度和结构化程度,更易于理解。在本书中,**重点和难点知识将主要通过动画以图解形式展示**,而文字则作为解释与补充。 如果你在阅读本书时,发现某段内容提供了如下图所示的动画图解,**请以图为主、以文字为辅**,综合两者来理解内容。 ![动画图解示例](../index.assets/animation.gif) ## 在代码实践中加深理解 本书的配套代码托管在 [GitHub 仓库](https://github.com/krahets/hello-algo)。如下图所示,**源代码附有测试样例,可一键运行**。 如果时间允许,**建议你参照代码自行敲一遍**。如果学习时间有限,请至少通读并运行所有代码。 与阅读代码相比,编写代码的过程往往能带来更多收获。**动手学,才是真的学**。 ![运行代码示例](../index.assets/running_code.gif) 运行代码的前置工作主要分为三步。 **第一步:安装本地编程环境**。请参照附录所示的[教程](https://www.hello-algo.com/chapter_appendix/installation/)进行安装,如果已安装,则可跳过此步骤。 **第二步:克隆或下载代码仓库**。前往 [GitHub 仓库](https://github.com/krahets/hello-algo)。如果已经安装 [Git](https://git-scm.com/downloads) ,可以通过以下命令克隆本仓库: ```shell git clone https://github.com/krahets/hello-algo.git ``` 当然,你也可以在下图所示的位置,点击“Download ZIP”按钮直接下载代码压缩包,然后在本地解压即可。 ![克隆仓库与下载代码](suggestions.assets/download_code.png) **第三步:运行源代码**。如下图所示,对于顶部标有文件名称的代码块,我们可以在仓库的 `codes` 文件夹内找到对应的源代码文件。源代码文件可一键运行,将帮助你节省不必要的调试时间,让你能够专注于学习内容。 ![代码块与对应的源代码文件](suggestions.assets/code_md_to_repo.png) 除了本地运行代码,**网页版还支持 Python 代码的可视化运行**(基于 [pythontutor](https://pythontutor.com/) 实现)。如下图所示,你可以点击代码块下方的“可视化运行”来展开视图,观察算法代码的执行过程;也可以点击“全屏观看”,以获得更好的阅览体验。 ![Python 代码的可视化运行](suggestions.assets/pythontutor_example.png) ## 在提问讨论中共同成长 在阅读本书时,请不要轻易跳过那些没学明白的知识点。**欢迎在评论区提出你的问题**,我和小伙伴们将竭诚为你解答,一般情况下可在两天内回复。 如下图所示,网页版每个章节的底部都配有评论区。希望你能多关注评论区的内容。一方面,你可以了解大家遇到的问题,从而查漏补缺,激发更深入的思考。另一方面,期待你能慷慨地回答其他小伙伴的问题,分享你的见解,帮助他人进步。 ![评论区示例](../index.assets/comment.gif) ## 算法学习路线 从总体上看,我们可以将学习数据结构与算法的过程划分为三个阶段。 1. **阶段一:算法入门**。我们需要熟悉各种数据结构的特点和用法,学习不同算法的原理、流程、用途和效率等方面的内容。 2. **阶段二:刷算法题**。建议从热门题目开刷,先积累至少 100 道题目,熟悉主流的算法问题。初次刷题时,“知识遗忘”可能是一个挑战,但请放心,这是很正常的。我们可以按照“艾宾浩斯遗忘曲线”来复习题目,通常在进行 3~5 轮的重复后,就能将其牢记在心。推荐的题单和刷题计划请见此 [GitHub 仓库](https://github.com/krahets/LeetCode-Book)。 3. **阶段三:搭建知识体系**。在学习方面,我们可以阅读算法专栏文章、解题框架和算法教材,以不断丰富知识体系。在刷题方面,可以尝试采用进阶刷题策略,如按专题分类、一题多解、一解多题等,相关的刷题心得可以在各个社区找到。 如下图所示,本书内容主要涵盖“阶段一”,旨在帮助你更高效地展开阶段二和阶段三的学习。 ![算法学习路线](suggestions.assets/learning_route.png) ================================================ FILE: docs/chapter_preface/summary.md ================================================ # 小结 ### 重点回顾 - 本书的主要受众是算法初学者。如果你已有一定基础,本书能帮助你系统回顾算法知识,书中源代码也可作为“刷题工具库”使用。 - 书中内容主要包括复杂度分析、数据结构和算法三部分,涵盖了该领域的大部分主题。 - 对于算法新手,在初学阶段阅读一本入门书至关重要,可以少走许多弯路。 - 书中的动画图解通常用于介绍重点和难点知识。阅读本书时,应给予这些内容更多关注。 - 实践乃学习编程之最佳途径。强烈建议运行源代码并亲自敲代码。 - 本书网页版的每个章节都设有评论区,欢迎随时分享你的疑惑与见解。 ================================================ FILE: docs/chapter_reference/index.md ================================================ --- icon: material/bookshelf --- # 参考文献 [1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition). [2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition). [3] Robert Sedgewick, et al. Algorithms (4th Edition). [4] 严蔚敏. 数据结构(C 语言版). [5] 邓俊辉. 数据结构(C++ 语言版,第三版). [6] 马克 艾伦 维斯著,陈越译. 数据结构与算法分析:Java语言描述(第三版). [7] 程杰. 大话数据结构. [8] 王争. 数据结构与算法之美. [9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition). [10] Aston Zhang, et al. Dive into Deep Learning. ================================================ FILE: docs/chapter_searching/binary_search.md ================================================ # 二分查找 二分查找(binary search)是一种基于分治策略的高效搜索算法。它利用数据的有序性,每轮缩小一半搜索范围,直至找到目标元素或搜索区间为空为止。 !!! question 给定一个长度为 $n$ 的数组 `nums` ,元素按从小到大的顺序排列且不重复。请查找并返回元素 `target` 在该数组中的索引。若数组不包含该元素,则返回 $-1$ 。示例如下图所示。 ![二分查找示例数据](binary_search.assets/binary_search_example.png) 如下图所示,我们先初始化指针 $i = 0$ 和 $j = n - 1$ ,分别指向数组首元素和尾元素,代表搜索区间 $[0, n - 1]$ 。请注意,中括号表示闭区间,其包含边界值本身。 接下来,循环执行以下两步。 1. 计算中点索引 $m = \lfloor {(i + j) / 2} \rfloor$ ,其中 $\lfloor \: \rfloor$ 表示向下取整操作。 2. 判断 `nums[m]` 和 `target` 的大小关系,分为以下三种情况。 1. 当 `nums[m] < target` 时,说明 `target` 在区间 $[m + 1, j]$ 中,因此执行 $i = m + 1$ 。 2. 当 `nums[m] > target` 时,说明 `target` 在区间 $[i, m - 1]$ 中,因此执行 $j = m - 1$ 。 3. 当 `nums[m] = target` 时,说明找到 `target` ,因此返回索引 $m$ 。 若数组不包含目标元素,搜索区间最终会缩小为空。此时返回 $-1$ 。 === "<1>" ![二分查找流程](binary_search.assets/binary_search_step1.png) === "<2>" ![binary_search_step2](binary_search.assets/binary_search_step2.png) === "<3>" ![binary_search_step3](binary_search.assets/binary_search_step3.png) === "<4>" ![binary_search_step4](binary_search.assets/binary_search_step4.png) === "<5>" ![binary_search_step5](binary_search.assets/binary_search_step5.png) === "<6>" ![binary_search_step6](binary_search.assets/binary_search_step6.png) === "<7>" ![binary_search_step7](binary_search.assets/binary_search_step7.png) 值得注意的是,由于 $i$ 和 $j$ 都是 `int` 类型,**因此 $i + j$ 可能会超出 `int` 类型的取值范围**。为了避免大数越界,我们通常采用公式 $m = \lfloor {i + (j - i) / 2} \rfloor$ 来计算中点。 代码如下所示: ```src [file]{binary_search}-[class]{}-[func]{binary_search} ``` **时间复杂度为 $O(\log n)$** :在二分循环中,区间每轮缩小一半,因此循环次数为 $\log_2 n$ 。 **空间复杂度为 $O(1)$** :指针 $i$ 和 $j$ 使用常数大小空间。 ## 区间表示方法 除了上述双闭区间外,常见的区间表示还有“左闭右开”区间,定义为 $[0, n)$ ,即左边界包含自身,右边界不包含自身。在该表示下,区间 $[i, j)$ 在 $i = j$ 时为空。 我们可以基于该表示实现具有相同功能的二分查找算法: ```src [file]{binary_search}-[class]{}-[func]{binary_search_lcro} ``` 如下图所示,在两种区间表示下,二分查找算法的初始化、循环条件和缩小区间操作皆有所不同。 由于“双闭区间”表示中的左右边界都被定义为闭区间,因此通过指针 $i$ 和指针 $j$ 缩小区间的操作也是对称的。这样更不容易出错,**因此一般建议采用“双闭区间”的写法**。 ![两种区间定义](binary_search.assets/binary_search_ranges.png) ## 优点与局限性 二分查找在时间和空间方面都有较好的性能。 - 二分查找的时间效率高。在大数据量下,对数阶的时间复杂度具有显著优势。例如,当数据大小 $n = 2^{20}$ 时,线性查找需要 $2^{20} = 1048576$ 轮循环,而二分查找仅需 $\log_2 2^{20} = 20$ 轮循环。 - 二分查找无须额外空间。相较于需要借助额外空间的搜索算法(例如哈希查找),二分查找更加节省空间。 然而,二分查找并非适用于所有情况,主要有以下原因。 - 二分查找仅适用于有序数据。若输入数据无序,为了使用二分查找而专门进行排序,得不偿失。因为排序算法的时间复杂度通常为 $O(n \log n)$ ,比线性查找和二分查找都更高。对于频繁插入元素的场景,为保持数组有序性,需要将元素插入到特定位置,时间复杂度为 $O(n)$ ,也是非常昂贵的。 - 二分查找仅适用于数组。二分查找需要跳跃式(非连续地)访问元素,而在链表中执行跳跃式访问的效率较低,因此不适合应用在链表或基于链表实现的数据结构。 - 小数据量下,线性查找性能更佳。在线性查找中,每轮只需 1 次判断操作;而在二分查找中,需要 1 次加法、1 次除法、1 ~ 3 次判断操作、1 次加法(减法),共 4 ~ 6 个单元操作;因此,当数据量 $n$ 较小时,线性查找反而比二分查找更快。 ================================================ FILE: docs/chapter_searching/binary_search_edge.md ================================================ # 二分查找边界 ## 查找左边界 !!! question 给定一个长度为 $n$ 的有序数组 `nums` ,其中可能包含重复元素。请返回数组中最左一个元素 `target` 的索引。若数组中不包含该元素,则返回 $-1$ 。 回忆二分查找插入点的方法,搜索完成后 $i$ 指向最左一个 `target` ,**因此查找插入点本质上是在查找最左一个 `target` 的索引**。 考虑通过查找插入点的函数实现查找左边界。请注意,数组中可能不包含 `target` ,这种情况可能导致以下两种结果。 - 插入点的索引 $i$ 越界。 - 元素 `nums[i]` 与 `target` 不相等。 当遇到以上两种情况时,直接返回 $-1$ 即可。代码如下所示: ```src [file]{binary_search_edge}-[class]{}-[func]{binary_search_left_edge} ``` ## 查找右边界 那么如何查找最右一个 `target` 呢?最直接的方式是修改代码,替换在 `nums[m] == target` 情况下的指针收缩操作。代码在此省略,有兴趣的读者可以自行实现。 下面我们介绍两种更加取巧的方法。 ### 复用查找左边界 实际上,我们可以利用查找最左元素的函数来查找最右元素,具体方法为:**将查找最右一个 `target` 转化为查找最左一个 `target + 1`**。 如下图所示,查找完成后,指针 $i$ 指向最左一个 `target + 1`(如果存在),而 $j$ 指向最右一个 `target` ,**因此返回 $j$ 即可**。 ![将查找右边界转化为查找左边界](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png) 请注意,返回的插入点是 $i$ ,因此需要将其减 $1$ ,从而获得 $j$ : ```src [file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge} ``` ### 转化为查找元素 我们知道,当数组不包含 `target` 时,最终 $i$ 和 $j$ 会分别指向首个大于、小于 `target` 的元素。 因此,如下图所示,我们可以构造一个数组中不存在的元素,用于查找左右边界。 - 查找最左一个 `target` :可以转化为查找 `target - 0.5` ,并返回指针 $i$ 。 - 查找最右一个 `target` :可以转化为查找 `target + 0.5` ,并返回指针 $j$ 。 ![将查找边界转化为查找元素](binary_search_edge.assets/binary_search_edge_by_element.png) 代码在此省略,以下两点值得注意。 - 给定数组不包含小数,这意味着我们无须关心如何处理相等的情况。 - 因为该方法引入了小数,所以需要将函数中的变量 `target` 改为浮点数类型(Python 无须改动)。 ================================================ FILE: docs/chapter_searching/binary_search_insertion.md ================================================ # 二分查找插入点 二分查找不仅可用于搜索目标元素,还可用于解决许多变种问题,比如搜索目标元素的插入位置。 ## 无重复元素的情况 !!! question 给定一个长度为 $n$ 的有序数组 `nums` 和一个元素 `target` ,数组不存在重复元素。现将 `target` 插入数组 `nums` 中,并保持其有序性。若数组中已存在元素 `target` ,则插入到其左方。请返回插入后 `target` 在数组中的索引。示例如下图所示。 ![二分查找插入点示例数据](binary_search_insertion.assets/binary_search_insertion_example.png) 如果想复用上一节的二分查找代码,则需要回答以下两个问题。 **问题一**:当数组中包含 `target` 时,插入点的索引是否是该元素的索引? 题目要求将 `target` 插入到相等元素的左边,这意味着新插入的 `target` 替换了原来 `target` 的位置。也就是说,**当数组包含 `target` 时,插入点的索引就是该 `target` 的索引**。 **问题二**:当数组中不存在 `target` 时,插入点是哪个元素的索引? 进一步思考二分查找过程:当 `nums[m] < target` 时 $i$ 移动,这意味着指针 $i$ 在向大于等于 `target` 的元素靠近。同理,指针 $j$ 始终在向小于等于 `target` 的元素靠近。 因此二分结束时一定有:$i$ 指向首个大于 `target` 的元素,$j$ 指向首个小于 `target` 的元素。**易得当数组不包含 `target` 时,插入索引为 $i$** 。代码如下所示: ```src [file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple} ``` ## 存在重复元素的情况 !!! question 在上一题的基础上,规定数组可能包含重复元素,其余不变。 假设数组中存在多个 `target` ,则普通二分查找只能返回其中一个 `target` 的索引,**而无法确定该元素的左边和右边还有多少 `target`**。 题目要求将目标元素插入到最左边,**所以我们需要查找数组中最左一个 `target` 的索引**。初步考虑通过下图所示的步骤实现。 1. 执行二分查找,得到任意一个 `target` 的索引,记为 $k$ 。 2. 从索引 $k$ 开始,向左进行线性遍历,当找到最左边的 `target` 时返回。 ![线性查找重复元素的插入点](binary_search_insertion.assets/binary_search_insertion_naive.png) 此方法虽然可用,但其包含线性查找,因此时间复杂度为 $O(n)$ 。当数组中存在很多重复的 `target` 时,该方法效率很低。 现考虑拓展二分查找代码。如下图所示,整体流程保持不变,每轮先计算中点索引 $m$ ,再判断 `target` 和 `nums[m]` 的大小关系,分为以下几种情况。 - 当 `nums[m] < target` 或 `nums[m] > target` 时,说明还没有找到 `target` ,因此采用普通二分查找的缩小区间操作,**从而使指针 $i$ 和 $j$ 向 `target` 靠近**。 - 当 `nums[m] == target` 时,说明小于 `target` 的元素在区间 $[i, m - 1]$ 中,因此采用 $j = m - 1$ 来缩小区间,**从而使指针 $j$ 向小于 `target` 的元素靠近**。 循环完成后,$i$ 指向最左边的 `target` ,$j$ 指向首个小于 `target` 的元素,**因此索引 $i$ 就是插入点**。 === "<1>" ![二分查找重复元素的插入点的步骤](binary_search_insertion.assets/binary_search_insertion_step1.png) === "<2>" ![binary_search_insertion_step2](binary_search_insertion.assets/binary_search_insertion_step2.png) === "<3>" ![binary_search_insertion_step3](binary_search_insertion.assets/binary_search_insertion_step3.png) === "<4>" ![binary_search_insertion_step4](binary_search_insertion.assets/binary_search_insertion_step4.png) === "<5>" ![binary_search_insertion_step5](binary_search_insertion.assets/binary_search_insertion_step5.png) === "<6>" ![binary_search_insertion_step6](binary_search_insertion.assets/binary_search_insertion_step6.png) === "<7>" ![binary_search_insertion_step7](binary_search_insertion.assets/binary_search_insertion_step7.png) === "<8>" ![binary_search_insertion_step8](binary_search_insertion.assets/binary_search_insertion_step8.png) 观察以下代码,判断分支 `nums[m] > target` 和 `nums[m] == target` 的操作相同,因此两者可以合并。 即便如此,我们仍然可以将判断条件保持展开,因为其逻辑更加清晰、可读性更好。 ```src [file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion} ``` !!! tip 本节的代码都是“双闭区间”写法。有兴趣的读者可以自行实现“左闭右开”写法。 总的来看,二分查找无非就是给指针 $i$ 和 $j$ 分别设定搜索目标,目标可能是一个具体的元素(例如 `target` ),也可能是一个元素范围(例如小于 `target` 的元素)。 在不断的循环二分中,指针 $i$ 和 $j$ 都逐渐逼近预先设定的目标。最终,它们或是成功找到答案,或是越过边界后停止。 ================================================ FILE: docs/chapter_searching/index.md ================================================ # 搜索 ![搜索](../assets/covers/chapter_searching.jpg) !!! abstract 搜索是一场未知的冒险,我们或许需要走遍神秘空间的每个角落,又或许可以快速锁定目标。 在这场寻觅之旅中,每一次探索都可能得到一个未曾料想的答案。 ================================================ FILE: docs/chapter_searching/replace_linear_by_hashing.md ================================================ # 哈希优化策略 在算法题中,**我们常通过将线性查找替换为哈希查找来降低算法的时间复杂度**。我们借助一个算法题来加深理解。 !!! question 给定一个整数数组 `nums` 和一个目标元素 `target` ,请在数组中搜索“和”为 `target` 的两个元素,并返回它们的数组索引。返回任意一个解即可。 ## 线性查找:以时间换空间 考虑直接遍历所有可能的组合。如下图所示,我们开启一个两层循环,在每轮中判断两个整数的和是否为 `target` ,若是,则返回它们的索引。 ![线性查找求解两数之和](replace_linear_by_hashing.assets/two_sum_brute_force.png) 代码如下所示: ```src [file]{two_sum}-[class]{}-[func]{two_sum_brute_force} ``` 此方法的时间复杂度为 $O(n^2)$ ,空间复杂度为 $O(1)$ ,在大数据量下非常耗时。 ## 哈希查找:以空间换时间 考虑借助一个哈希表,键值对分别为数组元素和元素索引。循环遍历数组,每轮执行下图所示的步骤。 1. 判断数字 `target - nums[i]` 是否在哈希表中,若是,则直接返回这两个元素的索引。 2. 将键值对 `nums[i]` 和索引 `i` 添加进哈希表。 === "<1>" ![辅助哈希表求解两数之和](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) === "<2>" ![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png) === "<3>" ![two_sum_hashtable_step3](replace_linear_by_hashing.assets/two_sum_hashtable_step3.png) 实现代码如下所示,仅需单层循环即可: ```src [file]{two_sum}-[class]{}-[func]{two_sum_hash_table} ``` 此方法通过哈希查找将时间复杂度从 $O(n^2)$ 降至 $O(n)$ ,大幅提升运行效率。 由于需要维护一个额外的哈希表,因此空间复杂度为 $O(n)$ 。**尽管如此,该方法的整体时空效率更为均衡,因此它是本题的最优解法**。 ================================================ FILE: docs/chapter_searching/searching_algorithm_revisited.md ================================================ # 重识搜索算法 搜索算法(searching algorithm)用于在数据结构(例如数组、链表、树或图)中搜索一个或一组满足特定条件的元素。 搜索算法可根据实现思路分为以下两类。 - **通过遍历数据结构来定位目标元素**,例如数组、链表、树和图的遍历等。 - **利用数据组织结构或数据包含的先验信息,实现高效元素查找**,例如二分查找、哈希查找和二叉搜索树查找等。 不难发现,这些知识点都已在前面的章节中介绍过,因此搜索算法对于我们来说并不陌生。在本节中,我们将从更加系统的视角切入,重新审视搜索算法。 ## 暴力搜索 暴力搜索通过遍历数据结构的每个元素来定位目标元素。 - “线性搜索”适用于数组和链表等线性数据结构。它从数据结构的一端开始,逐个访问元素,直到找到目标元素或到达另一端仍没有找到目标元素为止。 - “广度优先搜索”和“深度优先搜索”是图和树的两种遍历策略。广度优先搜索从初始节点开始逐层搜索,由近及远地访问各个节点。深度优先搜索从初始节点开始,沿着一条路径走到头,再回溯并尝试其他路径,直到遍历完整个数据结构。 暴力搜索的优点是简单且通用性好,**无须对数据做预处理和借助额外的数据结构**。 然而,**此类算法的时间复杂度为 $O(n)$** ,其中 $n$ 为元素数量,因此在数据量较大的情况下性能较差。 ## 自适应搜索 自适应搜索利用数据的特有属性(例如有序性)来优化搜索过程,从而更高效地定位目标元素。 - “二分查找”利用数据的有序性实现高效查找,仅适用于数组。 - “哈希查找”利用哈希表将搜索数据和目标数据建立为键值对映射,从而实现查询操作。 - “树查找”在特定的树结构(例如二叉搜索树)中,基于比较节点值来快速排除节点,从而定位目标元素。 此类算法的优点是效率高,**时间复杂度可达到 $O(\log n)$ 甚至 $O(1)$** 。 然而,**使用这些算法往往需要对数据进行预处理**。例如,二分查找需要预先对数组进行排序,哈希查找和树查找都需要借助额外的数据结构,维护这些数据结构也需要额外的时间和空间开销。 !!! tip 自适应搜索算法常被称为查找算法,**主要用于在特定数据结构中快速检索目标元素**。 ## 搜索方法选取 给定大小为 $n$ 的一组数据,我们可以使用线性搜索、二分查找、树查找、哈希查找等多种方法从中搜索目标元素。各个方法的工作原理如下图所示。 ![多种搜索策略](searching_algorithm_revisited.assets/searching_algorithms.png) 上述几种方法的操作效率与特性如下表所示。

  查找算法效率对比

| | 线性搜索 | 二分查找 | 树查找 | 哈希查找 | | ------------ | -------- | ------------------ | ------------------ | --------------- | | 查找元素 | $O(n)$ | $O(\log n)$ | $O(\log n)$ | $O(1)$ | | 插入元素 | $O(1)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | | 删除元素 | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | | 额外空间 | $O(1)$ | $O(1)$ | $O(n)$ | $O(n)$ | | 数据预处理 | / | 排序 $O(n \log n)$ | 建树 $O(n \log n)$ | 建哈希表 $O(n)$ | | 数据是否有序 | 无序 | 有序 | 有序 | 无序 | 搜索算法的选择还取决规模、搜索性能要求、数据查询与更新频率等。 **线性搜索** - 通用性较好,无须任何数据预处理操作。假如我们仅需查询一次数据,那么其他三种方法的数据预处理的时间比线性搜索的时间还要更长。 - 适用于体量较小的数据,此情况下时间复杂度对效率影响较小。 - 适用于数据更新频率较高的场景,因为该方法不需要对数据进行任何额外维护。 **二分查找** - 适用于大数据量的情况,效率表现稳定,最差时间复杂度为 $O(\log n)$ 。 - 数据量不能过大,因为存储数组需要连续的内存空间。 - 不适用于高频增删数据的场景,因为维护有序数组的开销较大。 **哈希查找** - 适合对查询性能要求很高的场景,平均时间复杂度为 $O(1)$ 。 - 不适合需要有序数据或范围查找的场景,因为哈希表无法维护数据的有序性。 - 对哈希函数和哈希冲突处理策略的依赖性较高,具有较大的性能劣化风险。 - 不适合数据量过大的情况,因为哈希表需要额外空间来最大程度地减少冲突,从而提供良好的查询性能。 **树查找** - 适用于海量数据,因为树节点在内存中是分散存储的。 - 适合需要维护有序数据或范围查找的场景。 - 在持续增删节点的过程中,二叉搜索树可能产生倾斜,时间复杂度劣化至 $O(n)$ 。 - 若使用 AVL 树或红黑树,则各项操作可在 $O(\log n)$ 效率下稳定运行,但维护树平衡的操作会增加额外的开销。 ================================================ FILE: docs/chapter_searching/summary.md ================================================ # 小结 ### 重点回顾 - 二分查找依赖数据的有序性,通过循环逐步缩减一半搜索区间来进行查找。它要求输入数据有序,且仅适用于数组或基于数组实现的数据结构。 - 暴力搜索通过遍历数据结构来定位数据。线性搜索适用于数组和链表,广度优先搜索和深度优先搜索适用于图和树。此类算法通用性好,无须对数据进行预处理,但时间复杂度 $O(n)$ 较高。 - 哈希查找、树查找和二分查找属于高效搜索方法,可在特定数据结构中快速定位目标元素。此类算法效率高,时间复杂度可达 $O(\log n)$ 甚至 $O(1)$ ,但通常需要借助额外数据结构。 - 实际中,我们需要对数据规模、搜索性能要求、数据查询和更新频率等因素进行具体分析,从而选择合适的搜索方法。 - 线性搜索适用于小型或频繁更新的数据;二分查找适用于大型、排序的数据;哈希查找适用于对查询效率要求较高且无须范围查询的数据;树查找适用于需要维护顺序和支持范围查询的大型动态数据。 - 用哈希查找替换线性查找是一种常用的优化运行时间的策略,可将时间复杂度从 $O(n)$ 降至 $O(1)$ 。 ================================================ FILE: docs/chapter_sorting/bubble_sort.md ================================================ # 冒泡排序 冒泡排序(bubble sort)通过连续地比较与交换相邻元素实现排序。这个过程就像气泡从底部升到顶部一样,因此得名冒泡排序。 如下图所示,冒泡过程可以利用元素交换操作来模拟:从数组最左端开始向右遍历,依次比较相邻元素大小,如果“左元素 > 右元素”就交换二者。遍历完成后,最大的元素会被移动到数组的最右端。 === "<1>" ![利用元素交换操作模拟冒泡](bubble_sort.assets/bubble_operation_step1.png) === "<2>" ![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png) === "<3>" ![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png) === "<4>" ![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png) === "<5>" ![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png) === "<6>" ![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png) === "<7>" ![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png) ## 算法流程 设数组的长度为 $n$ ,冒泡排序的步骤如下图所示。 1. 首先,对 $n$ 个元素执行“冒泡”,**将数组的最大元素交换至正确位置**。 2. 接下来,对剩余 $n - 1$ 个元素执行“冒泡”,**将第二大元素交换至正确位置**。 3. 以此类推,经过 $n - 1$ 轮“冒泡”后,**前 $n - 1$ 大的元素都被交换至正确位置**。 4. 仅剩的一个元素必定是最小元素,无须排序,因此数组排序完成。 ![冒泡排序流程](bubble_sort.assets/bubble_sort_overview.png) 示例代码如下: ```src [file]{bubble_sort}-[class]{}-[func]{bubble_sort} ``` ## 效率优化 我们发现,如果某轮“冒泡”中没有执行任何交换操作,说明数组已经完成排序,可直接返回结果。因此,可以增加一个标志位 `flag` 来监测这种情况,一旦出现就立即返回。 经过优化,冒泡排序的最差时间复杂度和平均时间复杂度仍为 $O(n^2)$ ;但当输入数组完全有序时,可达到最佳时间复杂度 $O(n)$ 。 ```src [file]{bubble_sort}-[class]{}-[func]{bubble_sort_with_flag} ``` ## 算法特性 - **时间复杂度为 $O(n^2)$、自适应排序**:各轮“冒泡”遍历的数组长度依次为 $n - 1$、$n - 2$、$\dots$、$2$、$1$ ,总和为 $(n - 1) n / 2$ 。在引入 `flag` 优化后,最佳时间复杂度可达到 $O(n)$ 。 - **空间复杂度为 $O(1)$、原地排序**:指针 $i$ 和 $j$ 使用常数大小的额外空间。 - **稳定排序**:由于在“冒泡”中遇到相等元素不交换。 ================================================ FILE: docs/chapter_sorting/bucket_sort.md ================================================ # 桶排序 前述几种排序算法都属于“基于比较的排序算法”,它们通过比较元素间的大小来实现排序。此类排序算法的时间复杂度无法超越 $O(n \log n)$ 。接下来,我们将探讨几种“非比较排序算法”,它们的时间复杂度可以达到线性阶。 桶排序(bucket sort)是分治策略的一个典型应用。它通过设置一些具有大小顺序的桶,每个桶对应一个数据范围,将数据平均分配到各个桶中;然后,在每个桶内部分别执行排序;最终按照桶的顺序将所有数据合并。 ## 算法流程 考虑一个长度为 $n$ 的数组,其元素是范围 $[0, 1)$ 内的浮点数。桶排序的流程如下图所示。 1. 初始化 $k$ 个桶,将 $n$ 个元素分配到 $k$ 个桶中。 2. 对每个桶分别执行排序(这里采用编程语言的内置排序函数)。 3. 按照桶从小到大的顺序合并结果。 ![桶排序算法流程](bucket_sort.assets/bucket_sort_overview.png) 代码如下所示: ```src [file]{bucket_sort}-[class]{}-[func]{bucket_sort} ``` ## 算法特性 桶排序适用于处理体量很大的数据。例如,输入数据包含 100 万个元素,由于空间限制,系统内存无法一次性加载所有数据。此时,可以将数据分成 1000 个桶,然后分别对每个桶进行排序,最后将结果合并。 - **时间复杂度为 $O(n + k)$** :假设元素在各个桶内平均分布,那么每个桶内的元素数量为 $\frac{n}{k}$ 。假设排序单个桶使用 $O(\frac{n}{k} \log\frac{n}{k})$ 时间,则排序所有桶使用 $O(n \log\frac{n}{k})$ 时间。**当桶数量 $k$ 比较大时,时间复杂度则趋向于 $O(n)$** 。合并结果时需要遍历所有桶和元素,花费 $O(n + k)$ 时间。在最差情况下,所有数据被分配到一个桶中,且排序该桶使用 $O(n^2)$ 时间。 - **空间复杂度为 $O(n + k)$、非原地排序**:需要借助 $k$ 个桶和总共 $n$ 个元素的额外空间。 - 桶排序是否稳定取决于排序桶内元素的算法是否稳定。 ## 如何实现平均分配 桶排序的时间复杂度理论上可以达到 $O(n)$ ,**关键在于将元素均匀分配到各个桶中**,因为实际数据往往不是均匀分布的。例如,我们想要将淘宝上的所有商品按价格范围平均分配到 10 个桶中,但商品价格分布不均,低于 100 元的非常多,高于 1000 元的非常少。若将价格区间平均划分为 10 个,各个桶中的商品数量差距会非常大。 为实现平均分配,我们可以先设定一条大致的分界线,将数据粗略地分到 3 个桶中。**分配完毕后,再将商品较多的桶继续划分为 3 个桶,直至所有桶中的元素数量大致相等**。 如下图所示,这种方法本质上是创建一棵递归树,目标是让叶节点的值尽可能平均。当然,不一定要每轮将数据划分为 3 个桶,具体划分方式可根据数据特点灵活选择。 ![递归划分桶](bucket_sort.assets/scatter_in_buckets_recursively.png) 如果我们提前知道商品价格的概率分布,**则可以根据数据概率分布设置每个桶的价格分界线**。值得注意的是,数据分布并不一定需要特意统计,也可以根据数据特点采用某种概率模型进行近似。 如下图所示,我们假设商品价格服从正态分布,这样就可以合理地设定价格区间,从而将商品平均分配到各个桶中。 ![根据概率分布划分桶](bucket_sort.assets/scatter_in_buckets_distribution.png) ================================================ FILE: docs/chapter_sorting/counting_sort.md ================================================ # 计数排序 计数排序(counting sort)通过统计元素数量来实现排序,通常应用于整数数组。 ## 简单实现 先来看一个简单的例子。给定一个长度为 $n$ 的数组 `nums` ,其中的元素都是“非负整数”,计数排序的整体流程如下图所示。 1. 遍历数组,找出其中的最大数字,记为 $m$ ,然后创建一个长度为 $m + 1$ 的辅助数组 `counter` 。 2. **借助 `counter` 统计 `nums` 中各数字的出现次数**,其中 `counter[num]` 对应数字 `num` 的出现次数。统计方法很简单,只需遍历 `nums`(设当前数字为 `num`),每轮将 `counter[num]` 增加 $1$ 即可。 3. **由于 `counter` 的各个索引天然有序,因此相当于所有数字已经排序好了**。接下来,我们遍历 `counter` ,根据各数字出现次数从小到大的顺序填入 `nums` 即可。 ![计数排序流程](counting_sort.assets/counting_sort_overview.png) 代码如下所示: ```src [file]{counting_sort}-[class]{}-[func]{counting_sort_naive} ``` !!! note "计数排序与桶排序的联系" 从桶排序的角度看,我们可以将计数排序中的计数数组 `counter` 的每个索引视为一个桶,将统计数量的过程看作将各个元素分配到对应的桶中。本质上,计数排序是桶排序在整型数据下的一个特例。 ## 完整实现 细心的读者可能发现了,**如果输入数据是对象,上述步骤 `3.` 就失效了**。假设输入数据是商品对象,我们想按照商品价格(类的成员变量)对商品进行排序,而上述算法只能给出价格的排序结果。 那么如何才能得到原数据的排序结果呢?我们首先计算 `counter` 的“前缀和”。顾名思义,索引 `i` 处的前缀和 `prefix[i]` 等于数组前 `i` 个元素之和: $$ \text{prefix}[i] = \sum_{j=0}^i \text{counter[j]} $$ **前缀和具有明确的意义,`prefix[num] - 1` 代表元素 `num` 在结果数组 `res` 中最后一次出现的索引**。这个信息非常关键,因为它告诉我们各个元素应该出现在结果数组的哪个位置。接下来,我们倒序遍历原数组 `nums` 的每个元素 `num` ,在每轮迭代中执行以下两步。 1. 将 `num` 填入数组 `res` 的索引 `prefix[num] - 1` 处。 2. 令前缀和 `prefix[num]` 减小 $1$ ,从而得到下次放置 `num` 的索引。 遍历完成后,数组 `res` 中就是排序好的结果,最后使用 `res` 覆盖原数组 `nums` 即可。下图展示了完整的计数排序流程。 === "<1>" ![计数排序步骤](counting_sort.assets/counting_sort_step1.png) === "<2>" ![counting_sort_step2](counting_sort.assets/counting_sort_step2.png) === "<3>" ![counting_sort_step3](counting_sort.assets/counting_sort_step3.png) === "<4>" ![counting_sort_step4](counting_sort.assets/counting_sort_step4.png) === "<5>" ![counting_sort_step5](counting_sort.assets/counting_sort_step5.png) === "<6>" ![counting_sort_step6](counting_sort.assets/counting_sort_step6.png) === "<7>" ![counting_sort_step7](counting_sort.assets/counting_sort_step7.png) === "<8>" ![counting_sort_step8](counting_sort.assets/counting_sort_step8.png) 计数排序的实现代码如下所示: ```src [file]{counting_sort}-[class]{}-[func]{counting_sort} ``` ## 算法特性 - **时间复杂度为 $O(n + m)$、非自适应排序** :涉及遍历 `nums` 和遍历 `counter` ,都使用线性时间。一般情况下 $n \gg m$ ,时间复杂度趋于 $O(n)$ 。 - **空间复杂度为 $O(n + m)$、非原地排序**:借助了长度分别为 $n$ 和 $m$ 的数组 `res` 和 `counter` 。 - **稳定排序**:由于向 `res` 中填充元素的顺序是“从右向左”的,因此倒序遍历 `nums` 可以避免改变相等元素之间的相对位置,从而实现稳定排序。实际上,正序遍历 `nums` 也可以得到正确的排序结果,但结果是非稳定的。 ## 局限性 看到这里,你也许会觉得计数排序非常巧妙,仅通过统计数量就可以实现高效的排序。然而,使用计数排序的前置条件相对较为严格。 **计数排序只适用于非负整数**。若想将其用于其他类型的数据,需要确保这些数据可以转换为非负整数,并且在转换过程中不能改变各个元素之间的相对大小关系。例如,对于包含负数的整数数组,可以先给所有数字加上一个常数,将全部数字转化为正数,排序完成后再转换回去。 **计数排序适用于数据量大但数据范围较小的情况**。比如,在上述示例中 $m$ 不能太大,否则会占用过多空间。而当 $n \ll m$ 时,计数排序使用 $O(m)$ 时间,可能比 $O(n \log n)$ 的排序算法还要慢。 ================================================ FILE: docs/chapter_sorting/heap_sort.md ================================================ # 堆排序 !!! tip 阅读本节前,请确保已学完“堆”章节。 堆排序(heap sort)是一种基于堆数据结构实现的高效排序算法。我们可以利用已经学过的“建堆操作”和“元素出堆操作”实现堆排序。 1. 输入数组并建立小顶堆,此时最小元素位于堆顶。 2. 不断执行出堆操作,依次记录出堆元素,即可得到从小到大排序的序列。 以上方法虽然可行,但需要借助一个额外数组来保存弹出的元素,比较浪费空间。在实际中,我们通常使用一种更加优雅的实现方式。 ## 算法流程 设数组的长度为 $n$ ,堆排序的流程如下图所示。 1. 输入数组并建立大顶堆。完成后,最大元素位于堆顶。 2. 将堆顶元素(第一个元素)与堆底元素(最后一个元素)交换。完成交换后,堆的长度减 $1$ ,已排序元素数量加 $1$ 。 3. 从堆顶元素开始,从顶到底执行堆化操作(sift down)。完成堆化后,堆的性质得到修复。 4. 循环执行第 `2.` 步和第 `3.` 步。循环 $n - 1$ 轮后,即可完成数组排序。 !!! tip 实际上,元素出堆操作中也包含第 `2.` 步和第 `3.` 步,只是多了一个弹出元素的步骤。 === "<1>" ![堆排序步骤](heap_sort.assets/heap_sort_step1.png) === "<2>" ![heap_sort_step2](heap_sort.assets/heap_sort_step2.png) === "<3>" ![heap_sort_step3](heap_sort.assets/heap_sort_step3.png) === "<4>" ![heap_sort_step4](heap_sort.assets/heap_sort_step4.png) === "<5>" ![heap_sort_step5](heap_sort.assets/heap_sort_step5.png) === "<6>" ![heap_sort_step6](heap_sort.assets/heap_sort_step6.png) === "<7>" ![heap_sort_step7](heap_sort.assets/heap_sort_step7.png) === "<8>" ![heap_sort_step8](heap_sort.assets/heap_sort_step8.png) === "<9>" ![heap_sort_step9](heap_sort.assets/heap_sort_step9.png) === "<10>" ![heap_sort_step10](heap_sort.assets/heap_sort_step10.png) === "<11>" ![heap_sort_step11](heap_sort.assets/heap_sort_step11.png) === "<12>" ![heap_sort_step12](heap_sort.assets/heap_sort_step12.png) 在代码实现中,我们使用了与“堆”章节相同的从顶至底堆化 `sift_down()` 函数。值得注意的是,由于堆的长度会随着提取最大元素而减小,因此我们需要给 `sift_down()` 函数添加一个长度参数 $n$ ,用于指定堆的当前有效长度。代码如下所示: ```src [file]{heap_sort}-[class]{}-[func]{heap_sort} ``` ## 算法特性 - **时间复杂度为 $O(n \log n)$、非自适应排序**:建堆操作使用 $O(n)$ 时间。从堆中提取最大元素的时间复杂度为 $O(\log n)$ ,共循环 $n - 1$ 轮。 - **空间复杂度为 $O(1)$、原地排序**:几个指针变量使用 $O(1)$ 空间。元素交换和堆化操作都是在原数组上进行的。 - **非稳定排序**:在交换堆顶元素和堆底元素时,相等元素的相对位置可能发生变化。 ================================================ FILE: docs/chapter_sorting/index.md ================================================ # 排序 ![排序](../assets/covers/chapter_sorting.jpg) !!! abstract 排序犹如一把将混乱变为秩序的魔法钥匙,使我们能以更高效的方式理解与处理数据。 无论是简单的升序,还是复杂的分类排列,排序都向我们展示了数据的和谐美感。 ================================================ FILE: docs/chapter_sorting/insertion_sort.md ================================================ # 插入排序 插入排序(insertion sort)是一种简单的排序算法,它的工作原理与手动整理一副牌的过程非常相似。 具体来说,我们在未排序区间选择一个基准元素,将该元素与其左侧已排序区间的元素逐一比较大小,并将该元素插入到正确的位置。 下图展示了数组插入元素的操作流程。设基准元素为 `base` ,我们需要将从目标索引到 `base` 之间的所有元素向右移动一位,然后将 `base` 赋值给目标索引。 ![单次插入操作](insertion_sort.assets/insertion_operation.png) ## 算法流程 插入排序的整体流程如下图所示。 1. 初始状态下,数组的第 1 个元素已完成排序。 2. 选取数组的第 2 个元素作为 `base` ,将其插入到正确位置后,**数组的前 2 个元素已排序**。 3. 选取第 3 个元素作为 `base` ,将其插入到正确位置后,**数组的前 3 个元素已排序**。 4. 以此类推,在最后一轮中,选取最后一个元素作为 `base` ,将其插入到正确位置后,**所有元素均已排序**。 ![插入排序流程](insertion_sort.assets/insertion_sort_overview.png) 示例代码如下: ```src [file]{insertion_sort}-[class]{}-[func]{insertion_sort} ``` ## 算法特性 - **时间复杂度为 $O(n^2)$、自适应排序**:在最差情况下,每次插入操作分别需要循环 $n - 1$、$n-2$、$\dots$、$2$、$1$ 次,求和得到 $(n - 1) n / 2$ ,因此时间复杂度为 $O(n^2)$ 。在遇到有序数据时,插入操作会提前终止。当输入数组完全有序时,插入排序达到最佳时间复杂度 $O(n)$ 。 - **空间复杂度为 $O(1)$、原地排序**:指针 $i$ 和 $j$ 使用常数大小的额外空间。 - **稳定排序**:在插入操作过程中,我们会将元素插入到相等元素的右侧,不会改变它们的顺序。 ## 插入排序的优势 插入排序的时间复杂度为 $O(n^2)$ ,而我们即将学习的快速排序的时间复杂度为 $O(n \log n)$ 。尽管插入排序的时间复杂度更高,**但在数据量较小的情况下,插入排序通常更快**。 这个结论与线性查找和二分查找的适用情况的结论类似。快速排序这类 $O(n \log n)$ 的算法属于基于分治策略的排序算法,往往包含更多单元计算操作。而在数据量较小时,$n^2$ 和 $n \log n$ 的数值比较接近,复杂度不占主导地位,每轮中的单元操作数量起到决定性作用。 实际上,许多编程语言(例如 Java)的内置排序函数采用了插入排序,大致思路为:对于长数组,采用基于分治策略的排序算法,例如快速排序;对于短数组,直接使用插入排序。 虽然冒泡排序、选择排序和插入排序的时间复杂度都为 $O(n^2)$ ,但在实际情况中,**插入排序的使用频率显著高于冒泡排序和选择排序**,主要有以下原因。 - 冒泡排序基于元素交换实现,需要借助一个临时变量,共涉及 3 个单元操作;插入排序基于元素赋值实现,仅需 1 个单元操作。因此,**冒泡排序的计算开销通常比插入排序更高**。 - 选择排序在任何情况下的时间复杂度都为 $O(n^2)$ 。**如果给定一组部分有序的数据,插入排序通常比选择排序效率更高**。 - 选择排序不稳定,无法应用于多级排序。 ================================================ FILE: docs/chapter_sorting/merge_sort.md ================================================ # 归并排序 归并排序(merge sort)是一种基于分治策略的排序算法,包含下图所示的“划分”和“合并”阶段。 1. **划分阶段**:通过递归不断地将数组从中点处分开,将长数组的排序问题转换为短数组的排序问题。 2. **合并阶段**:当子数组长度为 1 时终止划分,开始合并,持续地将左右两个较短的有序数组合并为一个较长的有序数组,直至结束。 ![归并排序的划分与合并阶段](merge_sort.assets/merge_sort_overview.png) ## 算法流程 如下图所示,“划分阶段”从顶至底递归地将数组从中点切分为两个子数组。 1. 计算数组中点 `mid` ,递归划分左子数组(区间 `[left, mid]` )和右子数组(区间 `[mid + 1, right]` )。 2. 递归执行步骤 `1.` ,直至子数组区间长度为 1 时终止。 “合并阶段”从底至顶地将左子数组和右子数组合并为一个有序数组。需要注意的是,从长度为 1 的子数组开始合并,合并阶段中的每个子数组都是有序的。 === "<1>" ![归并排序步骤](merge_sort.assets/merge_sort_step1.png) === "<2>" ![merge_sort_step2](merge_sort.assets/merge_sort_step2.png) === "<3>" ![merge_sort_step3](merge_sort.assets/merge_sort_step3.png) === "<4>" ![merge_sort_step4](merge_sort.assets/merge_sort_step4.png) === "<5>" ![merge_sort_step5](merge_sort.assets/merge_sort_step5.png) === "<6>" ![merge_sort_step6](merge_sort.assets/merge_sort_step6.png) === "<7>" ![merge_sort_step7](merge_sort.assets/merge_sort_step7.png) === "<8>" ![merge_sort_step8](merge_sort.assets/merge_sort_step8.png) === "<9>" ![merge_sort_step9](merge_sort.assets/merge_sort_step9.png) === "<10>" ![merge_sort_step10](merge_sort.assets/merge_sort_step10.png) 观察发现,归并排序与二叉树后序遍历的递归顺序是一致的。 - **后序遍历**:先递归左子树,再递归右子树,最后处理根节点。 - **归并排序**:先递归左子数组,再递归右子数组,最后处理合并。 归并排序的实现如以下代码所示。请注意,`nums` 的待合并区间为 `[left, right]` ,而 `tmp` 的对应区间为 `[0, right - left]` 。 ```src [file]{merge_sort}-[class]{}-[func]{merge_sort} ``` ## 算法特性 - **时间复杂度为 $O(n \log n)$、非自适应排序**:划分产生高度为 $\log n$ 的递归树,每层合并的总操作数量为 $n$ ,因此总体时间复杂度为 $O(n \log n)$ 。 - **空间复杂度为 $O(n)$、非原地排序**:递归深度为 $\log n$ ,使用 $O(\log n)$ 大小的栈帧空间。合并操作需要借助辅助数组实现,使用 $O(n)$ 大小的额外空间。 - **稳定排序**:在合并过程中,相等元素的次序保持不变。 ## 链表排序 对于链表,归并排序相较于其他排序算法具有显著优势,**可以将链表排序任务的空间复杂度优化至 $O(1)$** 。 - **划分阶段**:可以使用“迭代”替代“递归”来实现链表划分工作,从而省去递归使用的栈帧空间。 - **合并阶段**:在链表中,节点增删操作仅需改变引用(指针)即可实现,因此合并阶段(将两个短有序链表合并为一个长有序链表)无须创建额外链表。 具体实现细节比较复杂,有兴趣的读者可以查阅相关资料进行学习。 ================================================ FILE: docs/chapter_sorting/quick_sort.md ================================================ # 快速排序 快速排序(quick sort)是一种基于分治策略的排序算法,运行高效,应用广泛。 快速排序的核心操作是“哨兵划分”,其目标是:选择数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧。具体来说,哨兵划分的流程如下图所示。 1. 选取数组最左端元素作为基准数,初始化两个指针 `i` 和 `j` 分别指向数组的两端。 2. 设置一个循环,在每轮中使用 `i`(`j`)分别寻找第一个比基准数大(小)的元素,然后交换这两个元素。 3. 循环执行步骤 `2.` ,直到 `i` 和 `j` 相遇时停止,最后将基准数交换至两个子数组的分界线。 === "<1>" ![哨兵划分步骤](quick_sort.assets/pivot_division_step1.png) === "<2>" ![pivot_division_step2](quick_sort.assets/pivot_division_step2.png) === "<3>" ![pivot_division_step3](quick_sort.assets/pivot_division_step3.png) === "<4>" ![pivot_division_step4](quick_sort.assets/pivot_division_step4.png) === "<5>" ![pivot_division_step5](quick_sort.assets/pivot_division_step5.png) === "<6>" ![pivot_division_step6](quick_sort.assets/pivot_division_step6.png) === "<7>" ![pivot_division_step7](quick_sort.assets/pivot_division_step7.png) === "<8>" ![pivot_division_step8](quick_sort.assets/pivot_division_step8.png) === "<9>" ![pivot_division_step9](quick_sort.assets/pivot_division_step9.png) 哨兵划分完成后,原数组被划分成三部分:左子数组、基准数、右子数组,且满足“左子数组任意元素 $\leq$ 基准数 $\leq$ 右子数组任意元素”。因此,我们接下来只需对这两个子数组进行排序。 !!! note "快速排序的分治策略" 哨兵划分的实质是将一个较长数组的排序问题简化为两个较短数组的排序问题。 ```src [file]{quick_sort}-[class]{quick_sort}-[func]{partition} ``` ## 算法流程 快速排序的整体流程如下图所示。 1. 首先,对原数组执行一次“哨兵划分”,得到未排序的左子数组和右子数组。 2. 然后,对左子数组和右子数组分别递归执行“哨兵划分”。 3. 持续递归,直至子数组长度为 1 时终止,从而完成整个数组的排序。 ![快速排序流程](quick_sort.assets/quick_sort_overview.png) ```src [file]{quick_sort}-[class]{quick_sort}-[func]{quick_sort} ``` ## 算法特性 - **时间复杂度为 $O(n \log n)$、非自适应排序**:在平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。在最差情况下,每轮哨兵划分操作都将长度为 $n$ 的数组划分为长度为 $0$ 和 $n - 1$ 的两个子数组,此时递归层数达到 $n$ ,每层中的循环数为 $n$ ,总体使用 $O(n^2)$ 时间。 - **空间复杂度为 $O(n)$、原地排序**:在输入数组完全倒序的情况下,达到最差递归深度 $n$ ,使用 $O(n)$ 栈帧空间。排序操作是在原数组上进行的,未借助额外数组。 - **非稳定排序**:在哨兵划分的最后一步,基准数可能会被交换至相等元素的右侧。 ## 快速排序为什么快 从名称上就能看出,快速排序在效率方面应该具有一定的优势。尽管快速排序的平均时间复杂度与“归并排序”和“堆排序”相同,但通常快速排序的效率更高,主要有以下原因。 - **出现最差情况的概率很低**:虽然快速排序的最差时间复杂度为 $O(n^2)$ ,没有归并排序稳定,但在绝大多数情况下,快速排序能在 $O(n \log n)$ 的时间复杂度下运行。 - **缓存使用效率高**:在执行哨兵划分操作时,系统可将整个子数组加载到缓存,因此访问元素的效率较高。而像“堆排序”这类算法需要跳跃式访问元素,从而缺乏这一特性。 - **复杂度的常数系数小**:在上述三种算法中,快速排序的比较、赋值、交换等操作的总数量最少。这与“插入排序”比“冒泡排序”更快的原因类似。 ## 基准数优化 **快速排序在某些输入下的时间效率可能降低**。举一个极端例子,假设输入数组是完全倒序的,由于我们选择最左端元素作为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,导致左子数组长度为 $n - 1$、右子数组长度为 $0$ 。如此递归下去,每轮哨兵划分后都有一个子数组的长度为 $0$ ,分治策略失效,快速排序退化为“冒泡排序”的近似形式。 为了尽量避免这种情况发生,**我们可以优化哨兵划分中的基准数的选取策略**。例如,我们可以随机选取一个元素作为基准数。然而,如果运气不佳,每次都选到不理想的基准数,效率仍然不尽如人意。 需要注意的是,编程语言通常生成的是“伪随机数”。如果我们针对伪随机数序列构建一个特定的测试样例,那么快速排序的效率仍然可能劣化。 为了进一步改进,我们可以在数组中选取三个候选元素(通常为数组的首、尾、中点元素),**并将这三个候选元素的中位数作为基准数**。这样一来,基准数“既不太小也不太大”的概率将大幅提升。当然,我们还可以选取更多候选元素,以进一步提高算法的稳健性。采用这种方法后,时间复杂度劣化至 $O(n^2)$ 的概率大大降低。 示例代码如下: ```src [file]{quick_sort}-[class]{quick_sort_median}-[func]{partition} ``` ## 递归深度优化 **在某些输入下,快速排序可能占用空间较多**。以完全有序的输入数组为例,设递归中的子数组长度为 $m$ ,每轮哨兵划分操作都将产生长度为 $0$ 的左子数组和长度为 $m - 1$ 的右子数组,这意味着每一层递归调用减少的问题规模非常小(只减少一个元素),递归树的高度会达到 $n - 1$ ,此时需要占用 $O(n)$ 大小的栈帧空间。 为了防止栈帧空间的累积,我们可以在每轮哨兵排序完成后,比较两个子数组的长度,**仅对较短的子数组进行递归**。由于较短子数组的长度不会超过 $n / 2$ ,因此这种方法能确保递归深度不超过 $\log n$ ,从而将最差空间复杂度优化至 $O(\log n)$ 。代码如下所示: ```src [file]{quick_sort}-[class]{quick_sort_tail_call}-[func]{quick_sort} ``` ================================================ FILE: docs/chapter_sorting/radix_sort.md ================================================ # 基数排序 上一节介绍了计数排序,它适用于数据量 $n$ 较大但数据范围 $m$ 较小的情况。假设我们需要对 $n = 10^6$ 个学号进行排序,而学号是一个 $8$ 位数字,这意味着数据范围 $m = 10^8$ 非常大,使用计数排序需要分配大量内存空间,而基数排序可以避免这种情况。 基数排序(radix sort)的核心思想与计数排序一致,也通过统计个数来实现排序。在此基础上,基数排序利用数字各位之间的递进关系,依次对每一位进行排序,从而得到最终的排序结果。 ## 算法流程 以学号数据为例,假设数字的最低位是第 $1$ 位,最高位是第 $8$ 位,基数排序的流程如下图所示。 1. 初始化位数 $k = 1$ 。 2. 对学号的第 $k$ 位执行“计数排序”。完成后,数据会根据第 $k$ 位从小到大排序。 3. 将 $k$ 增加 $1$ ,然后返回步骤 `2.` 继续迭代,直到所有位都排序完成后结束。 ![基数排序算法流程](radix_sort.assets/radix_sort_overview.png) 下面剖析代码实现。对于一个 $d$ 进制的数字 $x$ ,要获取其第 $k$ 位 $x_k$ ,可以使用以下计算公式: $$ x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d $$ 其中 $\lfloor a \rfloor$ 表示对浮点数 $a$ 向下取整,而 $\bmod \: d$ 表示对 $d$ 取模(取余)。对于学号数据,$d = 10$ 且 $k \in [1, 8]$ 。 此外,我们需要小幅改动计数排序代码,使之可以根据数字的第 $k$ 位进行排序: ```src [file]{radix_sort}-[class]{}-[func]{radix_sort} ``` !!! question "为什么从最低位开始排序?" 在连续的排序轮次中,后一轮排序会覆盖前一轮排序的结果。举例来说,如果第一轮排序结果 $a < b$ ,而第二轮排序结果 $a > b$ ,那么第二轮的结果将取代第一轮的结果。由于数字的高位优先级高于低位,因此应该先排序低位再排序高位。 ## 算法特性 相较于计数排序,基数排序适用于数值范围较大的情况,**但前提是数据必须可以表示为固定位数的格式,且位数不能过大**。例如,浮点数不适合使用基数排序,因为其位数 $k$ 过大,可能导致时间复杂度 $O(nk) \gg O(n^2)$ 。 - **时间复杂度为 $O(nk)$、非自适应排序**:设数据量为 $n$、数据为 $d$ 进制、最大位数为 $k$ ,则对某一位执行计数排序使用 $O(n + d)$ 时间,排序所有 $k$ 位使用 $O((n + d)k)$ 时间。通常情况下,$d$ 和 $k$ 都相对较小,时间复杂度趋向 $O(n)$ 。 - **空间复杂度为 $O(n + d)$、非原地排序**:与计数排序相同,基数排序需要借助长度为 $n$ 和 $d$ 的数组 `res` 和 `counter` 。 - **稳定排序**:当计数排序稳定时,基数排序也稳定;当计数排序不稳定时,基数排序无法保证得到正确的排序结果。 ================================================ FILE: docs/chapter_sorting/selection_sort.md ================================================ # 选择排序 选择排序(selection sort)的工作原理非常简单:开启一个循环,每轮从未排序区间选择最小的元素,将其放到已排序区间的末尾。 设数组的长度为 $n$ ,选择排序的算法流程如下图所示。 1. 初始状态下,所有元素未排序,即未排序(索引)区间为 $[0, n-1]$ 。 2. 选取区间 $[0, n-1]$ 中的最小元素,将其与索引 $0$ 处的元素交换。完成后,数组前 1 个元素已排序。 3. 选取区间 $[1, n-1]$ 中的最小元素,将其与索引 $1$ 处的元素交换。完成后,数组前 2 个元素已排序。 4. 以此类推。经过 $n - 1$ 轮选择与交换后,数组前 $n - 1$ 个元素已排序。 5. 仅剩的一个元素必定是最大元素,无须排序,因此数组排序完成。 === "<1>" ![选择排序步骤](selection_sort.assets/selection_sort_step1.png) === "<2>" ![selection_sort_step2](selection_sort.assets/selection_sort_step2.png) === "<3>" ![selection_sort_step3](selection_sort.assets/selection_sort_step3.png) === "<4>" ![selection_sort_step4](selection_sort.assets/selection_sort_step4.png) === "<5>" ![selection_sort_step5](selection_sort.assets/selection_sort_step5.png) === "<6>" ![selection_sort_step6](selection_sort.assets/selection_sort_step6.png) === "<7>" ![selection_sort_step7](selection_sort.assets/selection_sort_step7.png) === "<8>" ![selection_sort_step8](selection_sort.assets/selection_sort_step8.png) === "<9>" ![selection_sort_step9](selection_sort.assets/selection_sort_step9.png) === "<10>" ![selection_sort_step10](selection_sort.assets/selection_sort_step10.png) === "<11>" ![selection_sort_step11](selection_sort.assets/selection_sort_step11.png) 在代码中,我们用 $k$ 来记录未排序区间内的最小元素: ```src [file]{selection_sort}-[class]{}-[func]{selection_sort} ``` ## 算法特性 - **时间复杂度为 $O(n^2)$、非自适应排序**:外循环共 $n - 1$ 轮,第一轮的未排序区间长度为 $n$ ,最后一轮的未排序区间长度为 $2$ ,即各轮外循环分别包含 $n$、$n - 1$、$\dots$、$3$、$2$ 轮内循环,求和为 $\frac{(n - 1)(n + 2)}{2}$ 。 - **空间复杂度为 $O(1)$、原地排序**:指针 $i$ 和 $j$ 使用常数大小的额外空间。 - **非稳定排序**:如下图所示,元素 `nums[i]` 有可能被交换至与其相等的元素的右边,导致两者的相对顺序发生改变。 ![选择排序非稳定示例](selection_sort.assets/selection_sort_instability.png) ================================================ FILE: docs/chapter_sorting/sorting_algorithm.md ================================================ # 排序算法 排序算法(sorting algorithm)用于对一组数据按照特定顺序进行排列。排序算法有着广泛的应用,因为有序数据通常能够被更高效地查找、分析和处理。 如下图所示,排序算法中的数据类型可以是整数、浮点数、字符或字符串等。排序的判断规则可根据需求设定,如数字大小、字符 ASCII 码顺序或自定义规则。 ![数据类型和判断规则示例](sorting_algorithm.assets/sorting_examples.png) ## 评价维度 **运行效率**:我们期望排序算法的时间复杂度尽量低,且总体操作数量较少(时间复杂度中的常数项变小)。对于大数据量的情况,运行效率显得尤为重要。 **就地性**:顾名思义,原地排序通过在原数组上直接操作实现排序,无须借助额外的辅助数组,从而节省内存。通常情况下,原地排序的数据搬运操作较少,运行速度也更快。 **稳定性**:稳定排序在完成排序后,相等元素在数组中的相对顺序不发生改变。 稳定排序是多级排序场景的必要条件。假设我们有一个存储学生信息的表格,第 1 列和第 2 列分别是姓名和年龄。在这种情况下,非稳定排序可能导致输入数据的有序性丧失: ```shell # 输入数据是按照姓名排序好的 # (name, age) ('A', 19) ('B', 18) ('C', 21) ('D', 19) ('E', 23) # 假设使用非稳定排序算法按年龄排序列表, # 结果中 ('D', 19) 和 ('A', 19) 的相对位置改变, # 输入数据按姓名排序的性质丢失 ('B', 18) ('D', 19) ('A', 19) ('C', 21) ('E', 23) ``` **自适应性**:自适应排序能够利用输入数据已有的顺序信息来减少计算量,达到更优的时间效率。自适应排序算法的最佳时间复杂度通常优于平均时间复杂度。 **是否基于比较**:基于比较的排序依赖比较运算符($<$、$=$、$>$)来判断元素的相对顺序,从而排序整个数组,理论最优时间复杂度为 $O(n \log n)$ 。而非比较排序不使用比较运算符,时间复杂度可达 $O(n)$ ,但其通用性相对较差。 ## 理想排序算法 **运行快、原地、稳定、自适应、通用性好**。显然,迄今为止尚未发现兼具以上所有特性的排序算法。因此,在选择排序算法时,需要根据具体的数据特点和问题需求来决定。 接下来,我们将共同学习各种排序算法,并基于上述评价维度对各个排序算法的优缺点进行分析。 ================================================ FILE: docs/chapter_sorting/summary.md ================================================ # 小结 ### 重点回顾 - 冒泡排序通过交换相邻元素来实现排序。通过添加一个标志位来实现提前返回,我们可以将冒泡排序的最佳时间复杂度优化到 $O(n)$ 。 - 插入排序每轮将未排序区间内的元素插入到已排序区间的正确位置,从而完成排序。虽然插入排序的时间复杂度为 $O(n^2)$ ,但由于单元操作相对较少,因此在小数据量的排序任务中非常受欢迎。 - 快速排序基于哨兵划分操作实现排序。在哨兵划分中,有可能每次都选取到最差的基准数,导致时间复杂度劣化至 $O(n^2)$ 。引入中位数基准数或随机基准数可以降低这种劣化的概率。通过优先递归较短子区间,可有效减小递归深度,将空间复杂度优化到 $O(\log n)$ 。 - 归并排序包括划分和合并两个阶段,典型地体现了分治策略。在归并排序中,排序数组需要创建辅助数组,空间复杂度为 $O(n)$ ;然而排序链表的空间复杂度可以优化至 $O(1)$ 。 - 桶排序包含三个步骤:数据分桶、桶内排序和合并结果。它同样体现了分治策略,适用于数据体量很大的情况。桶排序的关键在于对数据进行平均分配。 - 计数排序是桶排序的一个特例,它通过统计数据出现的次数来实现排序。计数排序适用于数据量大但数据范围有限的情况,并且要求数据能够转换为正整数。 - 基数排序通过逐位排序来实现数据排序,要求数据能够表示为固定位数的数字。 - 总的来说,我们希望找到一种排序算法,具有高效率、稳定、原地以及自适应性等优点。然而,正如其他数据结构和算法一样,没有一种排序算法能够同时满足所有这些条件。在实际应用中,我们需要根据数据的特性来选择合适的排序算法。 - 下图对比了主流排序算法的效率、稳定性、就地性和自适应性等。 ![排序算法对比](summary.assets/sorting_algorithms_comparison.png) ### Q & A **Q**:排序算法稳定性在什么情况下是必需的? 在现实中,我们有可能基于对象的某个属性进行排序。例如,学生有姓名和身高两个属性,我们希望实现一个多级排序:先按照姓名进行排序,得到 `(A, 180) (B, 185) (C, 170) (D, 170)` ;再对身高进行排序。由于排序算法不稳定,因此可能得到 `(D, 170) (C, 170) (A, 180) (B, 185)` 。 可以发现,学生 D 和 C 的位置发生了交换,姓名的有序性被破坏了,而这是我们不希望看到的。 **Q**:哨兵划分中“从右往左查找”与“从左往右查找”的顺序可以交换吗? 不行,当我们以最左端元素为基准数时,必须先“从右往左查找”再“从左往右查找”。这个结论有些反直觉,我们来剖析一下原因。 哨兵划分 `partition()` 的最后一步是交换 `nums[left]` 和 `nums[i]` 。完成交换后,基准数左边的元素都 `<=` 基准数,**这就要求最后一步交换前 `nums[left] >= nums[i]` 必须成立**。假设我们先“从左往右查找”,那么如果找不到比基准数更大的元素,**则会在 `i == j` 时跳出循环,此时可能 `nums[j] == nums[i] > nums[left]`**。也就是说,此时最后一步交换操作会把一个比基准数更大的元素交换至数组最左端,导致哨兵划分失败。 举个例子,给定数组 `[0, 0, 0, 0, 1]` ,如果先“从左向右查找”,哨兵划分后数组为 `[1, 0, 0, 0, 0]` ,这个结果是不正确的。 再深入思考一下,如果我们选择 `nums[right]` 为基准数,那么正好反过来,必须先“从左往右查找”。 **Q**:关于快速排序的递归深度优化,为什么选短的数组能保证递归深度不超过 $\log n$ ? 递归深度就是当前未返回的递归方法的数量。每轮哨兵划分我们将原数组划分为两个子数组。在递归深度优化后,向下递归的子数组长度最大为原数组长度的一半。假设最差情况,一直为一半长度,那么最终的递归深度就是 $\log n$ 。 回顾原始的快速排序,我们有可能会连续地递归长度较大的数组,最差情况下为 $n$、$n - 1$、$\dots$、$2$、$1$ ,递归深度为 $n$ 。递归深度优化可以避免这种情况出现。 **Q**:当数组中所有元素都相等时,快速排序的时间复杂度是 $O(n^2)$ 吗?该如何处理这种退化情况? 是的。对于这种情况,可以考虑通过哨兵划分将数组划分为三个部分:小于、等于、大于基准数。仅向下递归小于和大于的两部分。在该方法下,输入元素全部相等的数组,仅一轮哨兵划分即可完成排序。 **Q**:桶排序的最差时间复杂度为什么是 $O(n^2)$ ? 最差情况下,所有元素被分至同一个桶中。如果我们采用一个 $O(n^2)$ 算法来排序这些元素,则时间复杂度为 $O(n^2)$ 。 ================================================ FILE: docs/chapter_stack_and_queue/deque.md ================================================ # 双向队列 在队列中,我们仅能删除头部元素或在尾部添加元素。如下图所示,双向队列(double-ended queue)提供了更高的灵活性,允许在头部和尾部执行元素的添加或删除操作。 ![双向队列的操作](deque.assets/deque_operations.png) ## 双向队列常用操作 双向队列的常用操作如下表所示,具体的方法名称需要根据所使用的编程语言来确定。

  双向队列操作效率

| 方法名 | 描述 | 时间复杂度 | | -------------- | ---------------- | ---------- | | `push_first()` | 将元素添加至队首 | $O(1)$ | | `push_last()` | 将元素添加至队尾 | $O(1)$ | | `pop_first()` | 删除队首元素 | $O(1)$ | | `pop_last()` | 删除队尾元素 | $O(1)$ | | `peek_first()` | 访问队首元素 | $O(1)$ | | `peek_last()` | 访问队尾元素 | $O(1)$ | 同样地,我们可以直接使用编程语言中已实现的双向队列类: === "Python" ```python title="deque.py" from collections import deque # 初始化双向队列 deq: deque[int] = deque() # 元素入队 deq.append(2) # 添加至队尾 deq.append(5) deq.append(4) deq.appendleft(3) # 添加至队首 deq.appendleft(1) # 访问元素 front: int = deq[0] # 队首元素 rear: int = deq[-1] # 队尾元素 # 元素出队 pop_front: int = deq.popleft() # 队首元素出队 pop_rear: int = deq.pop() # 队尾元素出队 # 获取双向队列的长度 size: int = len(deq) # 判断双向队列是否为空 is_empty: bool = len(deq) == 0 ``` === "C++" ```cpp title="deque.cpp" /* 初始化双向队列 */ deque deque; /* 元素入队 */ deque.push_back(2); // 添加至队尾 deque.push_back(5); deque.push_back(4); deque.push_front(3); // 添加至队首 deque.push_front(1); /* 访问元素 */ int front = deque.front(); // 队首元素 int back = deque.back(); // 队尾元素 /* 元素出队 */ deque.pop_front(); // 队首元素出队 deque.pop_back(); // 队尾元素出队 /* 获取双向队列的长度 */ int size = deque.size(); /* 判断双向队列是否为空 */ bool empty = deque.empty(); ``` === "Java" ```java title="deque.java" /* 初始化双向队列 */ Deque deque = new LinkedList<>(); /* 元素入队 */ deque.offerLast(2); // 添加至队尾 deque.offerLast(5); deque.offerLast(4); deque.offerFirst(3); // 添加至队首 deque.offerFirst(1); /* 访问元素 */ int peekFirst = deque.peekFirst(); // 队首元素 int peekLast = deque.peekLast(); // 队尾元素 /* 元素出队 */ int popFirst = deque.pollFirst(); // 队首元素出队 int popLast = deque.pollLast(); // 队尾元素出队 /* 获取双向队列的长度 */ int size = deque.size(); /* 判断双向队列是否为空 */ boolean isEmpty = deque.isEmpty(); ``` === "C#" ```csharp title="deque.cs" /* 初始化双向队列 */ // 在 C# 中,将链表 LinkedList 看作双向队列来使用 LinkedList deque = new(); /* 元素入队 */ deque.AddLast(2); // 添加至队尾 deque.AddLast(5); deque.AddLast(4); deque.AddFirst(3); // 添加至队首 deque.AddFirst(1); /* 访问元素 */ int peekFirst = deque.First.Value; // 队首元素 int peekLast = deque.Last.Value; // 队尾元素 /* 元素出队 */ deque.RemoveFirst(); // 队首元素出队 deque.RemoveLast(); // 队尾元素出队 /* 获取双向队列的长度 */ int size = deque.Count; /* 判断双向队列是否为空 */ bool isEmpty = deque.Count == 0; ``` === "Go" ```go title="deque_test.go" /* 初始化双向队列 */ // 在 Go 中,将 list 作为双向队列使用 deque := list.New() /* 元素入队 */ deque.PushBack(2) // 添加至队尾 deque.PushBack(5) deque.PushBack(4) deque.PushFront(3) // 添加至队首 deque.PushFront(1) /* 访问元素 */ front := deque.Front() // 队首元素 rear := deque.Back() // 队尾元素 /* 元素出队 */ deque.Remove(front) // 队首元素出队 deque.Remove(rear) // 队尾元素出队 /* 获取双向队列的长度 */ size := deque.Len() /* 判断双向队列是否为空 */ isEmpty := deque.Len() == 0 ``` === "Swift" ```swift title="deque.swift" /* 初始化双向队列 */ // Swift 没有内置的双向队列类,可以把 Array 当作双向队列来使用 var deque: [Int] = [] /* 元素入队 */ deque.append(2) // 添加至队尾 deque.append(5) deque.append(4) deque.insert(3, at: 0) // 添加至队首 deque.insert(1, at: 0) /* 访问元素 */ let peekFirst = deque.first! // 队首元素 let peekLast = deque.last! // 队尾元素 /* 元素出队 */ // 使用 Array 模拟时 popFirst 的复杂度为 O(n) let popFirst = deque.removeFirst() // 队首元素出队 let popLast = deque.removeLast() // 队尾元素出队 /* 获取双向队列的长度 */ let size = deque.count /* 判断双向队列是否为空 */ let isEmpty = deque.isEmpty ``` === "JS" ```javascript title="deque.js" /* 初始化双向队列 */ // JavaScript 没有内置的双端队列,只能把 Array 当作双端队列来使用 const deque = []; /* 元素入队 */ deque.push(2); deque.push(5); deque.push(4); // 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n) deque.unshift(3); deque.unshift(1); /* 访问元素 */ const peekFirst = deque[0]; const peekLast = deque[deque.length - 1]; /* 元素出队 */ // 请注意,由于是数组,shift() 方法的时间复杂度为 O(n) const popFront = deque.shift(); const popBack = deque.pop(); /* 获取双向队列的长度 */ const size = deque.length; /* 判断双向队列是否为空 */ const isEmpty = size === 0; ``` === "TS" ```typescript title="deque.ts" /* 初始化双向队列 */ // TypeScript 没有内置的双端队列,只能把 Array 当作双端队列来使用 const deque: number[] = []; /* 元素入队 */ deque.push(2); deque.push(5); deque.push(4); // 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n) deque.unshift(3); deque.unshift(1); /* 访问元素 */ const peekFirst: number = deque[0]; const peekLast: number = deque[deque.length - 1]; /* 元素出队 */ // 请注意,由于是数组,shift() 方法的时间复杂度为 O(n) const popFront: number = deque.shift() as number; const popBack: number = deque.pop() as number; /* 获取双向队列的长度 */ const size: number = deque.length; /* 判断双向队列是否为空 */ const isEmpty: boolean = size === 0; ``` === "Dart" ```dart title="deque.dart" /* 初始化双向队列 */ // 在 Dart 中,Queue 被定义为双向队列 Queue deque = Queue(); /* 元素入队 */ deque.addLast(2); // 添加至队尾 deque.addLast(5); deque.addLast(4); deque.addFirst(3); // 添加至队首 deque.addFirst(1); /* 访问元素 */ int peekFirst = deque.first; // 队首元素 int peekLast = deque.last; // 队尾元素 /* 元素出队 */ int popFirst = deque.removeFirst(); // 队首元素出队 int popLast = deque.removeLast(); // 队尾元素出队 /* 获取双向队列的长度 */ int size = deque.length; /* 判断双向队列是否为空 */ bool isEmpty = deque.isEmpty; ``` === "Rust" ```rust title="deque.rs" /* 初始化双向队列 */ let mut deque: VecDeque = VecDeque::new(); /* 元素入队 */ deque.push_back(2); // 添加至队尾 deque.push_back(5); deque.push_back(4); deque.push_front(3); // 添加至队首 deque.push_front(1); /* 访问元素 */ if let Some(front) = deque.front() { // 队首元素 } if let Some(rear) = deque.back() { // 队尾元素 } /* 元素出队 */ if let Some(pop_front) = deque.pop_front() { // 队首元素出队 } if let Some(pop_rear) = deque.pop_back() { // 队尾元素出队 } /* 获取双向队列的长度 */ let size = deque.len(); /* 判断双向队列是否为空 */ let is_empty = deque.is_empty(); ``` === "C" ```c title="deque.c" // C 未提供内置双向队列 ``` === "Kotlin" ```kotlin title="deque.kt" /* 初始化双向队列 */ val deque = LinkedList() /* 元素入队 */ deque.offerLast(2) // 添加至队尾 deque.offerLast(5) deque.offerLast(4) deque.offerFirst(3) // 添加至队首 deque.offerFirst(1) /* 访问元素 */ val peekFirst = deque.peekFirst() // 队首元素 val peekLast = deque.peekLast() // 队尾元素 /* 元素出队 */ val popFirst = deque.pollFirst() // 队首元素出队 val popLast = deque.pollLast() // 队尾元素出队 /* 获取双向队列的长度 */ val size = deque.size /* 判断双向队列是否为空 */ val isEmpty = deque.isEmpty() ``` === "Ruby" ```ruby title="deque.rb" # 初始化双向队列 # Ruby 没有内直的双端队列,只能把 Array 当作双端队列来使用 deque = [] # 元素如队 deque << 2 deque << 5 deque << 4 # 请注意,由于是数组,Array#unshift 方法的时间复杂度为 O(n) deque.unshift(3) deque.unshift(1) # 访问元素 peek_first = deque.first peek_last = deque.last # 元素出队 # 请注意,由于是数组, Array#shift 方法的时间复杂度为 O(n) pop_front = deque.shift pop_back = deque.pop # 获取双向队列的长度 size = deque.length # 判断双向队列是否为空 is_empty = size.zero? ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22,%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22,%20pop_rear%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## 双向队列实现 * 双向队列的实现与队列类似,可以选择链表或数组作为底层数据结构。 ### 基于双向链表的实现 回顾上一节内容,我们使用普通单向链表来实现队列,因为它可以方便地删除头节点(对应出队操作)和在尾节点后添加新节点(对应入队操作)。 对于双向队列而言,头部和尾部都可以执行入队和出队操作。换句话说,双向队列需要实现另一个对称方向的操作。为此,我们采用“双向链表”作为双向队列的底层数据结构。 如下图所示,我们将双向链表的头节点和尾节点视为双向队列的队首和队尾,同时实现在两端添加和删除节点的功能。 === "<1>" ![基于链表实现双向队列的入队出队操作](deque.assets/linkedlist_deque_step1.png) === "<2>" ![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png) === "<3>" ![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png) === "<4>" ![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png) === "<5>" ![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png) 实现代码如下所示: ```src [file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{} ``` ### 基于数组的实现 如下图所示,与基于数组实现队列类似,我们也可以使用环形数组来实现双向队列。 === "<1>" ![基于数组实现双向队列的入队出队操作](deque.assets/array_deque_step1.png) === "<2>" ![array_deque_push_last](deque.assets/array_deque_step2_push_last.png) === "<3>" ![array_deque_push_first](deque.assets/array_deque_step3_push_first.png) === "<4>" ![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png) === "<5>" ![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png) 在队列的实现基础上,仅需增加“队首入队”和“队尾出队”的方法: ```src [file]{array_deque}-[class]{array_deque}-[func]{} ``` ## 双向队列应用 双向队列兼具栈与队列的逻辑,**因此它可以实现这两者的所有应用场景,同时提供更高的自由度**。 我们知道,软件的“撤销”功能通常使用栈来实现:系统将每次更改操作 `push` 到栈中,然后通过 `pop` 实现撤销。然而,考虑到系统资源的限制,软件通常会限制撤销的步数(例如仅允许保存 $50$ 步)。当栈的长度超过 $50$ 时,软件需要在栈底(队首)执行删除操作。**但栈无法实现该功能,此时就需要使用双向队列来替代栈**。请注意,“撤销”的核心逻辑仍然遵循栈的先入后出原则,只是双向队列能够更加灵活地实现一些额外逻辑。 ================================================ FILE: docs/chapter_stack_and_queue/index.md ================================================ # 栈与队列 ![栈与队列](../assets/covers/chapter_stack_and_queue.jpg) !!! abstract 栈如同叠猫猫,而队列就像猫猫排队。 两者分别代表先入后出和先入先出的逻辑关系。 ================================================ FILE: docs/chapter_stack_and_queue/queue.md ================================================ # 队列 队列(queue)是一种遵循先入先出规则的线性数据结构。顾名思义,队列模拟了排队现象,即新来的人不断加入队列尾部,而位于队列头部的人逐个离开。 如下图所示,我们将队列头部称为“队首”,尾部称为“队尾”,将把元素加入队尾的操作称为“入队”,删除队首元素的操作称为“出队”。 ![队列的先入先出规则](queue.assets/queue_operations.png) ## 队列常用操作 队列的常见操作如下表所示。需要注意的是,不同编程语言的方法名称可能会有所不同。我们在此采用与栈相同的方法命名。

  队列操作效率

| 方法名 | 描述 | 时间复杂度 | | -------- | ---------------------------- | ---------- | | `push()` | 元素入队,即将元素添加至队尾 | $O(1)$ | | `pop()` | 队首元素出队 | $O(1)$ | | `peek()` | 访问队首元素 | $O(1)$ | 我们可以直接使用编程语言中现成的队列类: === "Python" ```python title="queue.py" from collections import deque # 初始化队列 # 在 Python 中,我们一般将双向队列类 deque 当作队列使用 # 虽然 queue.Queue() 是纯正的队列类,但不太好用,因此不推荐 que: deque[int] = deque() # 元素入队 que.append(1) que.append(3) que.append(2) que.append(5) que.append(4) # 访问队首元素 front: int = que[0] # 元素出队 pop: int = que.popleft() # 获取队列的长度 size: int = len(que) # 判断队列是否为空 is_empty: bool = len(que) == 0 ``` === "C++" ```cpp title="queue.cpp" /* 初始化队列 */ queue queue; /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); /* 访问队首元素 */ int front = queue.front(); /* 元素出队 */ queue.pop(); /* 获取队列的长度 */ int size = queue.size(); /* 判断队列是否为空 */ bool empty = queue.empty(); ``` === "Java" ```java title="queue.java" /* 初始化队列 */ Queue queue = new LinkedList<>(); /* 元素入队 */ queue.offer(1); queue.offer(3); queue.offer(2); queue.offer(5); queue.offer(4); /* 访问队首元素 */ int peek = queue.peek(); /* 元素出队 */ int pop = queue.poll(); /* 获取队列的长度 */ int size = queue.size(); /* 判断队列是否为空 */ boolean isEmpty = queue.isEmpty(); ``` === "C#" ```csharp title="queue.cs" /* 初始化队列 */ Queue queue = new(); /* 元素入队 */ queue.Enqueue(1); queue.Enqueue(3); queue.Enqueue(2); queue.Enqueue(5); queue.Enqueue(4); /* 访问队首元素 */ int peek = queue.Peek(); /* 元素出队 */ int pop = queue.Dequeue(); /* 获取队列的长度 */ int size = queue.Count; /* 判断队列是否为空 */ bool isEmpty = queue.Count == 0; ``` === "Go" ```go title="queue_test.go" /* 初始化队列 */ // 在 Go 中,将 list 作为队列来使用 queue := list.New() /* 元素入队 */ queue.PushBack(1) queue.PushBack(3) queue.PushBack(2) queue.PushBack(5) queue.PushBack(4) /* 访问队首元素 */ peek := queue.Front() /* 元素出队 */ pop := queue.Front() queue.Remove(pop) /* 获取队列的长度 */ size := queue.Len() /* 判断队列是否为空 */ isEmpty := queue.Len() == 0 ``` === "Swift" ```swift title="queue.swift" /* 初始化队列 */ // Swift 没有内置的队列类,可以把 Array 当作队列来使用 var queue: [Int] = [] /* 元素入队 */ queue.append(1) queue.append(3) queue.append(2) queue.append(5) queue.append(4) /* 访问队首元素 */ let peek = queue.first! /* 元素出队 */ // 由于是数组,因此 removeFirst 的复杂度为 O(n) let pool = queue.removeFirst() /* 获取队列的长度 */ let size = queue.count /* 判断队列是否为空 */ let isEmpty = queue.isEmpty ``` === "JS" ```javascript title="queue.js" /* 初始化队列 */ // JavaScript 没有内置的队列,可以把 Array 当作队列来使用 const queue = []; /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); /* 访问队首元素 */ const peek = queue[0]; /* 元素出队 */ // 底层是数组,因此 shift() 方法的时间复杂度为 O(n) const pop = queue.shift(); /* 获取队列的长度 */ const size = queue.length; /* 判断队列是否为空 */ const empty = queue.length === 0; ``` === "TS" ```typescript title="queue.ts" /* 初始化队列 */ // TypeScript 没有内置的队列,可以把 Array 当作队列来使用 const queue: number[] = []; /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); /* 访问队首元素 */ const peek = queue[0]; /* 元素出队 */ // 底层是数组,因此 shift() 方法的时间复杂度为 O(n) const pop = queue.shift(); /* 获取队列的长度 */ const size = queue.length; /* 判断队列是否为空 */ const empty = queue.length === 0; ``` === "Dart" ```dart title="queue.dart" /* 初始化队列 */ // 在 Dart 中,队列类 Qeque 是双向队列,也可作为队列使用 Queue queue = Queue(); /* 元素入队 */ queue.add(1); queue.add(3); queue.add(2); queue.add(5); queue.add(4); /* 访问队首元素 */ int peek = queue.first; /* 元素出队 */ int pop = queue.removeFirst(); /* 获取队列的长度 */ int size = queue.length; /* 判断队列是否为空 */ bool isEmpty = queue.isEmpty; ``` === "Rust" ```rust title="queue.rs" /* 初始化双向队列 */ // 在 Rust 中使用双向队列作为普通队列来使用 let mut deque: VecDeque = VecDeque::new(); /* 元素入队 */ deque.push_back(1); deque.push_back(3); deque.push_back(2); deque.push_back(5); deque.push_back(4); /* 访问队首元素 */ if let Some(front) = deque.front() { } /* 元素出队 */ if let Some(pop) = deque.pop_front() { } /* 获取队列的长度 */ let size = deque.len(); /* 判断队列是否为空 */ let is_empty = deque.is_empty(); ``` === "C" ```c title="queue.c" // C 未提供内置队列 ``` === "Kotlin" ```kotlin title="queue.kt" /* 初始化队列 */ val queue = LinkedList() /* 元素入队 */ queue.offer(1) queue.offer(3) queue.offer(2) queue.offer(5) queue.offer(4) /* 访问队首元素 */ val peek = queue.peek() /* 元素出队 */ val pop = queue.poll() /* 获取队列的长度 */ val size = queue.size /* 判断队列是否为空 */ val isEmpty = queue.isEmpty() ``` === "Ruby" ```ruby title="queue.rb" # 初始化队列 # Ruby 内置的队列(Thread::Queue) 没有 peek 和遍历方法,可以把 Array 当作队列来使用 queue = [] # 元素入队 queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) # 访问队列元素 peek = queue.first # 元素出队 # 清注意,由于是数组,Array#shift 方法时间复杂度为 O(n) pop = queue.shift # 获取队列的长度 size = queue.length # 判断队列是否为空 is_empty = queue.empty? ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20%23%20%E5%9C%A8%20Python%20%E4%B8%AD%EF%BC%8C%E6%88%91%E4%BB%AC%E4%B8%80%E8%88%AC%E5%B0%86%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%B1%BB%20deque%20%E7%9C%8B%E4%BD%9C%E9%98%9F%E5%88%97%E4%BD%BF%E7%94%A8%0A%20%20%20%20%23%20%E8%99%BD%E7%84%B6%20queue.Queue%28%29%20%E6%98%AF%E7%BA%AF%E6%AD%A3%E7%9A%84%E9%98%9F%E5%88%97%E7%B1%BB%EF%BC%8C%E4%BD%86%E4%B8%8D%E5%A4%AA%E5%A5%BD%E7%94%A8%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%90%8E%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## 队列实现 为了实现队列,我们需要一种数据结构,可以在一端添加元素,并在另一端删除元素,链表和数组都符合要求。 ### 基于链表的实现 如下图所示,我们可以将链表的“头节点”和“尾节点”分别视为“队首”和“队尾”,规定队尾仅可添加节点,队首仅可删除节点。 === "<1>" ![基于链表实现队列的入队出队操作](queue.assets/linkedlist_queue_step1.png) === "<2>" ![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png) === "<3>" ![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png) 以下是用链表实现队列的代码: ```src [file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{} ``` ### 基于数组的实现 在数组中删除首元素的时间复杂度为 $O(n)$ ,这会导致出队操作效率较低。然而,我们可以采用以下巧妙方法来避免这个问题。 我们可以使用一个变量 `front` 指向队首元素的索引,并维护一个变量 `size` 用于记录队列长度。定义 `rear = front + size` ,这个公式计算出的 `rear` 指向队尾元素之后的下一个位置。 基于此设计,**数组中包含元素的有效区间为 `[front, rear - 1]`**,各种操作的实现方法如下图所示。 - 入队操作:将输入元素赋值给 `rear` 索引处,并将 `size` 增加 1 。 - 出队操作:只需将 `front` 增加 1 ,并将 `size` 减少 1 。 可以看到,入队和出队操作都只需进行一次操作,时间复杂度均为 $O(1)$ 。 === "<1>" ![基于数组实现队列的入队出队操作](queue.assets/array_queue_step1.png) === "<2>" ![array_queue_push](queue.assets/array_queue_step2_push.png) === "<3>" ![array_queue_pop](queue.assets/array_queue_step3_pop.png) 你可能会发现一个问题:在不断进行入队和出队的过程中,`front` 和 `rear` 都在向右移动,**当它们到达数组尾部时就无法继续移动了**。为了解决此问题,我们可以将数组视为首尾相接的“环形数组”。 对于环形数组,我们需要让 `front` 或 `rear` 在越过数组尾部时,直接回到数组头部继续遍历。这种周期性规律可以通过“取余操作”来实现,代码如下所示: ```src [file]{array_queue}-[class]{array_queue}-[func]{} ``` 以上实现的队列仍然具有局限性:其长度不可变。然而,这个问题不难解决,我们可以将数组替换为动态数组,从而引入扩容机制。有兴趣的读者可以尝试自行实现。 两种实现的对比结论与栈一致,在此不再赘述。 ## 队列典型应用 - **淘宝订单**。购物者下单后,订单将加入队列中,系统随后会根据顺序处理队列中的订单。在双十一期间,短时间内会产生海量订单,高并发成为工程师们需要重点攻克的问题。 - **各类待办事项**。任何需要实现“先来后到”功能的场景,例如打印机的任务队列、餐厅的出餐队列等,队列在这些场景中可以有效地维护处理顺序。 ================================================ FILE: docs/chapter_stack_and_queue/stack.md ================================================ # 栈 栈(stack)是一种遵循先入后出逻辑的线性数据结构。 我们可以将栈类比为桌面上的一摞盘子,规定每次只能移动一个盘子,那么想取出底部的盘子,则需要先将上面的盘子依次移走。我们将盘子替换为各种类型的元素(如整数、字符、对象等),就得到了栈这种数据结构。 如下图所示,我们把堆叠元素的顶部称为“栈顶”,底部称为“栈底”。将把元素添加到栈顶的操作叫作“入栈”,删除栈顶元素的操作叫作“出栈”。 ![栈的先入后出规则](stack.assets/stack_operations.png) ## 栈的常用操作 栈的常用操作如下表所示,具体的方法名需要根据所使用的编程语言来确定。在此,我们以常见的 `push()`、`pop()`、`peek()` 命名为例。

  栈的操作效率

| 方法 | 描述 | 时间复杂度 | | -------- | ---------------------- | ---------- | | `push()` | 元素入栈(添加至栈顶) | $O(1)$ | | `pop()` | 栈顶元素出栈 | $O(1)$ | | `peek()` | 访问栈顶元素 | $O(1)$ | 通常情况下,我们可以直接使用编程语言内置的栈类。然而,某些语言可能没有专门提供栈类,这时我们可以将该语言的“数组”或“链表”当作栈来使用,并在程序逻辑上忽略与栈无关的操作。 === "Python" ```python title="stack.py" # 初始化栈 # Python 没有内置的栈类,可以把 list 当作栈来使用 stack: list[int] = [] # 元素入栈 stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) # 访问栈顶元素 peek: int = stack[-1] # 元素出栈 pop: int = stack.pop() # 获取栈的长度 size: int = len(stack) # 判断是否为空 is_empty: bool = len(stack) == 0 ``` === "C++" ```cpp title="stack.cpp" /* 初始化栈 */ stack stack; /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* 访问栈顶元素 */ int top = stack.top(); /* 元素出栈 */ stack.pop(); // 无返回值 /* 获取栈的长度 */ int size = stack.size(); /* 判断是否为空 */ bool empty = stack.empty(); ``` === "Java" ```java title="stack.java" /* 初始化栈 */ Stack stack = new Stack<>(); /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* 访问栈顶元素 */ int peek = stack.peek(); /* 元素出栈 */ int pop = stack.pop(); /* 获取栈的长度 */ int size = stack.size(); /* 判断是否为空 */ boolean isEmpty = stack.isEmpty(); ``` === "C#" ```csharp title="stack.cs" /* 初始化栈 */ Stack stack = new(); /* 元素入栈 */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); /* 访问栈顶元素 */ int peek = stack.Peek(); /* 元素出栈 */ int pop = stack.Pop(); /* 获取栈的长度 */ int size = stack.Count; /* 判断是否为空 */ bool isEmpty = stack.Count == 0; ``` === "Go" ```go title="stack_test.go" /* 初始化栈 */ // 在 Go 中,推荐将 Slice 当作栈来使用 var stack []int /* 元素入栈 */ stack = append(stack, 1) stack = append(stack, 3) stack = append(stack, 2) stack = append(stack, 5) stack = append(stack, 4) /* 访问栈顶元素 */ peek := stack[len(stack)-1] /* 元素出栈 */ pop := stack[len(stack)-1] stack = stack[:len(stack)-1] /* 获取栈的长度 */ size := len(stack) /* 判断是否为空 */ isEmpty := len(stack) == 0 ``` === "Swift" ```swift title="stack.swift" /* 初始化栈 */ // Swift 没有内置的栈类,可以把 Array 当作栈来使用 var stack: [Int] = [] /* 元素入栈 */ stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) /* 访问栈顶元素 */ let peek = stack.last! /* 元素出栈 */ let pop = stack.removeLast() /* 获取栈的长度 */ let size = stack.count /* 判断是否为空 */ let isEmpty = stack.isEmpty ``` === "JS" ```javascript title="stack.js" /* 初始化栈 */ // JavaScript 没有内置的栈类,可以把 Array 当作栈来使用 const stack = []; /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* 访问栈顶元素 */ const peek = stack[stack.length-1]; /* 元素出栈 */ const pop = stack.pop(); /* 获取栈的长度 */ const size = stack.length; /* 判断是否为空 */ const is_empty = stack.length === 0; ``` === "TS" ```typescript title="stack.ts" /* 初始化栈 */ // TypeScript 没有内置的栈类,可以把 Array 当作栈来使用 const stack: number[] = []; /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* 访问栈顶元素 */ const peek = stack[stack.length - 1]; /* 元素出栈 */ const pop = stack.pop(); /* 获取栈的长度 */ const size = stack.length; /* 判断是否为空 */ const is_empty = stack.length === 0; ``` === "Dart" ```dart title="stack.dart" /* 初始化栈 */ // Dart 没有内置的栈类,可以把 List 当作栈来使用 List stack = []; /* 元素入栈 */ stack.add(1); stack.add(3); stack.add(2); stack.add(5); stack.add(4); /* 访问栈顶元素 */ int peek = stack.last; /* 元素出栈 */ int pop = stack.removeLast(); /* 获取栈的长度 */ int size = stack.length; /* 判断是否为空 */ bool isEmpty = stack.isEmpty; ``` === "Rust" ```rust title="stack.rs" /* 初始化栈 */ // 把 Vec 当作栈来使用 let mut stack: Vec = Vec::new(); /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* 访问栈顶元素 */ let top = stack.last().unwrap(); /* 元素出栈 */ let pop = stack.pop().unwrap(); /* 获取栈的长度 */ let size = stack.len(); /* 判断是否为空 */ let is_empty = stack.is_empty(); ``` === "C" ```c title="stack.c" // C 未提供内置栈 ``` === "Kotlin" ```kotlin title="stack.kt" /* 初始化栈 */ val stack = Stack() /* 元素入栈 */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) /* 访问栈顶元素 */ val peek = stack.peek() /* 元素出栈 */ val pop = stack.pop() /* 获取栈的长度 */ val size = stack.size /* 判断是否为空 */ val isEmpty = stack.isEmpty() ``` === "Ruby" ```ruby title="stack.rb" # 初始化栈 # Ruby 没有内置的栈类,可以把 Array 当作栈来使用 stack = [] # 元素入栈 stack << 1 stack << 3 stack << 2 stack << 5 stack << 4 # 访问栈顶元素 peek = stack.last # 元素出栈 pop = stack.pop # 获取栈的长度 size = stack.length # 判断是否为空 is_empty = stack.empty? ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20%23%20Python%20%E6%B2%A1%E6%9C%89%E5%86%85%E7%BD%AE%E7%9A%84%E6%A0%88%E7%B1%BB%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E5%BD%93%E4%BD%9C%E6%A0%88%E6%9D%A5%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## 栈的实现 为了深入了解栈的运行机制,我们来尝试自己实现一个栈类。 栈遵循先入后出的原则,因此我们只能在栈顶添加或删除元素。然而,数组和链表都可以在任意位置添加和删除元素,**因此栈可以视为一种受限制的数组或链表**。换句话说,我们可以“屏蔽”数组或链表的部分无关操作,使其对外表现的逻辑符合栈的特性。 ### 基于链表的实现 使用链表实现栈时,我们可以将链表的头节点视为栈顶,尾节点视为栈底。 如下图所示,对于入栈操作,我们只需将元素插入链表头部,这种节点插入方法被称为“头插法”。而对于出栈操作,只需将头节点从链表中删除即可。 === "<1>" ![基于链表实现栈的入栈出栈操作](stack.assets/linkedlist_stack_step1.png) === "<2>" ![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png) === "<3>" ![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png) 以下是基于链表实现栈的示例代码: ```src [file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{} ``` ### 基于数组的实现 使用数组实现栈时,我们可以将数组的尾部作为栈顶。如下图所示,入栈与出栈操作分别对应在数组尾部添加元素与删除元素,时间复杂度都为 $O(1)$ 。 === "<1>" ![基于数组实现栈的入栈出栈操作](stack.assets/array_stack_step1.png) === "<2>" ![array_stack_push](stack.assets/array_stack_step2_push.png) === "<3>" ![array_stack_pop](stack.assets/array_stack_step3_pop.png) 由于入栈的元素可能会源源不断地增加,因此我们可以使用动态数组,这样就无须自行处理数组扩容问题。以下为示例代码: ```src [file]{array_stack}-[class]{array_stack}-[func]{} ``` ## 两种实现对比 **支持操作** 两种实现都支持栈定义中的各项操作。数组实现额外支持随机访问,但这已超出了栈的定义范畴,因此一般不会用到。 **时间效率** 在基于数组的实现中,入栈和出栈操作都在预先分配好的连续内存中进行,具有很好的缓存本地性,因此效率较高。然而,如果入栈时超出数组容量,会触发扩容机制,导致该次入栈操作的时间复杂度变为 $O(n)$ 。 在基于链表的实现中,链表的扩容非常灵活,不存在上述数组扩容时效率降低的问题。但是,入栈操作需要初始化节点对象并修改指针,因此效率相对较低。不过,如果入栈元素本身就是节点对象,那么可以省去初始化步骤,从而提高效率。 综上所述,当入栈与出栈操作的元素是基本数据类型时,例如 `int` 或 `double` ,我们可以得出以下结论。 - 基于数组实现的栈在触发扩容时效率会降低,但由于扩容是低频操作,因此平均效率更高。 - 基于链表实现的栈可以提供更加稳定的效率表现。 **空间效率** 在初始化列表时,系统会为列表分配“初始容量”,该容量可能超出实际需求;并且,扩容机制通常是按照特定倍率(例如 2 倍)进行扩容的,扩容后的容量也可能超出实际需求。因此,**基于数组实现的栈可能造成一定的空间浪费**。 然而,由于链表节点需要额外存储指针,**因此链表节点占用的空间相对较大**。 综上,我们不能简单地确定哪种实现更加节省内存,需要针对具体情况进行分析。 ## 栈的典型应用 - **浏览器中的后退与前进、软件中的撤销与反撤销**。每当我们打开新的网页,浏览器就会对上一个网页执行入栈,这样我们就可以通过后退操作回到上一个网页。后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么需要两个栈来配合实现。 - **程序内存管理**。每次调用函数时,系统都会在栈顶添加一个栈帧,用于记录函数的上下文信息。在递归函数中,向下递推阶段会不断执行入栈操作,而向上回溯阶段则会不断执行出栈操作。 ================================================ FILE: docs/chapter_stack_and_queue/summary.md ================================================ # 小结 ### 重点回顾 - 栈是一种遵循先入后出原则的数据结构,可通过数组或链表来实现。 - 在时间效率方面,栈的数组实现具有较高的平均效率,但在扩容过程中,单次入栈操作的时间复杂度会劣化至 $O(n)$ 。相比之下,栈的链表实现具有更为稳定的效率表现。 - 在空间效率方面,栈的数组实现可能导致一定程度的空间浪费。但需要注意的是,链表节点所占用的内存空间比数组元素更大。 - 队列是一种遵循先入先出原则的数据结构,同样可以通过数组或链表来实现。在时间效率和空间效率的对比上,队列的结论与前述栈的结论相似。 - 双向队列是一种具有更高自由度的队列,它允许在两端进行元素的添加和删除操作。 ### Q & A **Q**:浏览器的前进后退是否是双向链表实现? 浏览器的前进后退功能本质上是“栈”的体现。当用户访问一个新页面时,该页面会被添加到栈顶;当用户点击后退按钮时,该页面会从栈顶弹出。使用双向队列可以方便地实现一些额外操作,这个在“双向队列”章节有提到。 **Q**:在出栈后,是否需要释放出栈节点的内存? 如果后续仍需要使用弹出节点,则不需要释放内存。若之后不需要用到,`Java` 和 `Python` 等语言拥有自动垃圾回收机制,因此不需要手动释放内存;在 `C` 和 `C++` 中需要手动释放内存。 **Q**:双向队列像是两个栈拼接在了一起,它的用途是什么? 双向队列就像是栈和队列的组合或两个栈拼在了一起。它表现的是栈 + 队列的逻辑,因此可以实现栈与队列的所有应用,并且更加灵活。 **Q**:撤销(undo)和反撤销(redo)具体是如何实现的? 使用两个栈,栈 `A` 用于撤销,栈 `B` 用于反撤销。 1. 每当用户执行一个操作,将这个操作压入栈 `A` ,并清空栈 `B` 。 2. 当用户执行“撤销”时,从栈 `A` 中弹出最近的操作,并将其压入栈 `B` 。 3. 当用户执行“反撤销”时,从栈 `B` 中弹出最近的操作,并将其压入栈 `A` 。 ================================================ FILE: docs/chapter_tree/array_representation_of_tree.md ================================================ # 二叉树数组表示 在链表表示下,二叉树的存储单元为节点 `TreeNode` ,节点之间通过指针相连接。上一节介绍了链表表示下的二叉树的各项基本操作。 那么,我们能否用数组来表示二叉树呢?答案是肯定的。 ## 表示完美二叉树 先分析一个简单案例。给定一棵完美二叉树,我们将所有节点按照层序遍历的顺序存储在一个数组中,则每个节点都对应唯一的数组索引。 根据层序遍历的特性,我们可以推导出父节点索引与子节点索引之间的“映射公式”:**若某节点的索引为 $i$ ,则该节点的左子节点索引为 $2i + 1$ ,右子节点索引为 $2i + 2$** 。下图展示了各个节点索引之间的映射关系。 ![完美二叉树的数组表示](array_representation_of_tree.assets/array_representation_binary_tree.png) **映射公式的角色相当于链表中的节点引用(指针)**。给定数组中的任意一个节点,我们都可以通过映射公式来访问它的左(右)子节点。 ## 表示任意二叉树 完美二叉树是一个特例,在二叉树的中间层通常存在许多 `None` 。由于层序遍历序列并不包含这些 `None` ,因此我们无法仅凭该序列来推测 `None` 的数量和分布位置。**这意味着存在多种二叉树结构都符合该层序遍历序列**。 如下图所示,给定一棵非完美二叉树,上述数组表示方法已经失效。 ![层序遍历序列对应多种二叉树可能性](array_representation_of_tree.assets/array_representation_without_empty.png) 为了解决此问题,**我们可以考虑在层序遍历序列中显式地写出所有 `None`** 。如下图所示,这样处理后,层序遍历序列就可以唯一表示二叉树了。示例代码如下: === "Python" ```python title="" # 二叉树的数组表示 # 使用 None 来表示空位 tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] ``` === "C++" ```cpp title="" /* 二叉树的数组表示 */ // 使用 int 最大值 INT_MAX 标记空位 vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` === "Java" ```java title="" /* 二叉树的数组表示 */ // 使用 int 的包装类 Integer ,就可以使用 null 来标记空位 Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; ``` === "C#" ```csharp title="" /* 二叉树的数组表示 */ // 使用 int? 可空类型 ,就可以使用 null 来标记空位 int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Go" ```go title="" /* 二叉树的数组表示 */ // 使用 any 类型的切片, 就可以使用 nil 来标记空位 tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} ``` === "Swift" ```swift title="" /* 二叉树的数组表示 */ // 使用 Int? 可空类型 ,就可以使用 nil 来标记空位 let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` === "JS" ```javascript title="" /* 二叉树的数组表示 */ // 使用 null 来表示空位 let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "TS" ```typescript title="" /* 二叉树的数组表示 */ // 使用 null 来表示空位 let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Dart" ```dart title="" /* 二叉树的数组表示 */ // 使用 int? 可空类型 ,就可以使用 null 来标记空位 List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Rust" ```rust title="" /* 二叉树的数组表示 */ // 使用 None 来标记空位 let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)]; ``` === "C" ```c title="" /* 二叉树的数组表示 */ // 使用 int 最大值标记空位,因此要求节点值不能为 INT_MAX int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` === "Kotlin" ```kotlin title="" /* 二叉树的数组表示 */ // 使用 null 来表示空位 val tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) ``` === "Ruby" ```ruby title="" ### 二叉树的数组表示 ### # 使用 nil 来表示空位 tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` ![任意类型二叉树的数组表示](array_representation_of_tree.assets/array_representation_with_empty.png) 值得说明的是,**完全二叉树非常适合使用数组来表示**。回顾完全二叉树的定义,`None` 只出现在最底层且靠右的位置,**因此所有 `None` 一定出现在层序遍历序列的末尾**。 这意味着使用数组表示完全二叉树时,可以省略存储所有 `None` ,非常方便。下图给出了一个例子。 ![完全二叉树的数组表示](array_representation_of_tree.assets/array_representation_complete_binary_tree.png) 以下代码实现了一棵基于数组表示的二叉树,包括以下几种操作。 - 给定某节点,获取它的值、左(右)子节点、父节点。 - 获取前序遍历、中序遍历、后序遍历、层序遍历序列。 ```src [file]{array_binary_tree}-[class]{array_binary_tree}-[func]{} ``` ## 优点与局限性 二叉树的数组表示主要有以下优点。 - 数组存储在连续的内存空间中,对缓存友好,访问与遍历速度较快。 - 不需要存储指针,比较节省空间。 - 允许随机访问节点。 然而,数组表示也存在一些局限性。 - 数组存储需要连续内存空间,因此不适合存储数据量过大的树。 - 增删节点需要通过数组插入与删除操作实现,效率较低。 - 当二叉树中存在大量 `None` 时,数组中包含的节点数据比重较低,空间利用率较低。 ================================================ FILE: docs/chapter_tree/avl_tree.md ================================================ # AVL 树 * 在“二叉搜索树”章节中我们提到,在多次插入和删除操作后,二叉搜索树可能退化为链表。在这种情况下,所有操作的时间复杂度将从 $O(\log n)$ 劣化为 $O(n)$ 。 如下图所示,经过两次删除节点操作,这棵二叉搜索树便会退化为链表。 ![AVL 树在删除节点后发生退化](avl_tree.assets/avltree_degradation_from_removing_node.png) 再例如,在下图所示的完美二叉树中插入两个节点后,树将严重向左倾斜,查找操作的时间复杂度也随之劣化。 ![AVL 树在插入节点后发生退化](avl_tree.assets/avltree_degradation_from_inserting_node.png) 1962 年 G. M. Adelson-Velsky 和 E. M. Landis 在论文“An algorithm for the organization of information”中提出了 AVL 树。论文中详细描述了一系列操作,确保在持续添加和删除节点后,AVL 树不会退化,从而使得各种操作的时间复杂度保持在 $O(\log n)$ 级别。换句话说,在需要频繁进行增删查改操作的场景中,AVL 树能始终保持高效的数据操作性能,具有很好的应用价值。 ## AVL 树常见术语 AVL 树既是二叉搜索树,也是平衡二叉树,同时满足这两类二叉树的所有性质,因此是一种平衡二叉搜索树(balanced binary search tree)。 ### 节点高度 由于 AVL 树的相关操作需要获取节点高度,因此我们需要为节点类添加 `height` 变量: === "Python" ```python title="" class TreeNode: """AVL 树节点类""" def __init__(self, val: int): self.val: int = val # 节点值 self.height: int = 0 # 节点高度 self.left: TreeNode | None = None # 左子节点引用 self.right: TreeNode | None = None # 右子节点引用 ``` === "C++" ```cpp title="" /* AVL 树节点类 */ struct TreeNode { int val{}; // 节点值 int height = 0; // 节点高度 TreeNode *left{}; // 左子节点 TreeNode *right{}; // 右子节点 TreeNode() = default; explicit TreeNode(int x) : val(x){} }; ``` === "Java" ```java title="" /* AVL 树节点类 */ class TreeNode { public int val; // 节点值 public int height; // 节点高度 public TreeNode left; // 左子节点 public TreeNode right; // 右子节点 public TreeNode(int x) { val = x; } } ``` === "C#" ```csharp title="" /* AVL 树节点类 */ class TreeNode(int? x) { public int? val = x; // 节点值 public int height; // 节点高度 public TreeNode? left; // 左子节点引用 public TreeNode? right; // 右子节点引用 } ``` === "Go" ```go title="" /* AVL 树节点结构体 */ type TreeNode struct { Val int // 节点值 Height int // 节点高度 Left *TreeNode // 左子节点引用 Right *TreeNode // 右子节点引用 } ``` === "Swift" ```swift title="" /* AVL 树节点类 */ class TreeNode { var val: Int // 节点值 var height: Int // 节点高度 var left: TreeNode? // 左子节点 var right: TreeNode? // 右子节点 init(x: Int) { val = x height = 0 } } ``` === "JS" ```javascript title="" /* AVL 树节点类 */ class TreeNode { val; // 节点值 height; //节点高度 left; // 左子节点指针 right; // 右子节点指针 constructor(val, left, right, height) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } ``` === "TS" ```typescript title="" /* AVL 树节点类 */ class TreeNode { val: number; // 节点值 height: number; // 节点高度 left: TreeNode | null; // 左子节点指针 right: TreeNode | null; // 右子节点指针 constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } ``` === "Dart" ```dart title="" /* AVL 树节点类 */ class TreeNode { int val; // 节点值 int height; // 节点高度 TreeNode? left; // 左子节点 TreeNode? right; // 右子节点 TreeNode(this.val, [this.height = 0, this.left, this.right]); } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* AVL 树节点结构体 */ struct TreeNode { val: i32, // 节点值 height: i32, // 节点高度 left: Option>>, // 左子节点 right: Option>>, // 右子节点 } impl TreeNode { /* 构造方法 */ fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, height: 0, left: None, right: None })) } } ``` === "C" ```c title="" /* AVL 树节点结构体 */ typedef struct TreeNode { int val; int height; struct TreeNode *left; struct TreeNode *right; } TreeNode; /* 构造函数 */ TreeNode *newTreeNode(int val) { TreeNode *node; node = (TreeNode *)malloc(sizeof(TreeNode)); node->val = val; node->height = 0; node->left = NULL; node->right = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* AVL 树节点类 */ class TreeNode(val _val: Int) { // 节点值 val height: Int = 0 // 节点高度 val left: TreeNode? = null // 左子节点 val right: TreeNode? = null // 右子节点 } ``` === "Ruby" ```ruby title="" ### AVL 树节点类 ### class TreeNode attr_accessor :val # 节点值 attr_accessor :height # 节点高度 attr_accessor :left # 左子节点引用 attr_accessor :right # 右子节点引用 def initialize(val) @val = val @height = 0 end end ``` “节点高度”是指从该节点到它的最远叶节点的距离,即所经过的“边”的数量。需要特别注意的是,叶节点的高度为 $0$ ,而空节点的高度为 $-1$ 。我们将创建两个工具函数,分别用于获取和更新节点的高度: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{update_height} ``` ### 节点平衡因子 节点的平衡因子(balance factor)定义为节点左子树的高度减去右子树的高度,同时规定空节点的平衡因子为 $0$ 。我们同样将获取节点平衡因子的功能封装成函数,方便后续使用: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{balance_factor} ``` !!! tip 设平衡因子为 $f$ ,则一棵 AVL 树的任意节点的平衡因子皆满足 $-1 \le f \le 1$ 。 ## AVL 树旋转 AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中序遍历序列的前提下,使失衡节点重新恢复平衡。换句话说,**旋转操作既能保持“二叉搜索树”的性质,也能使树重新变为“平衡二叉树”**。 我们将平衡因子绝对值 $> 1$ 的节点称为“失衡节点”。根据节点失衡情况的不同,旋转操作分为四种:右旋、左旋、先右旋后左旋、先左旋后右旋。下面详细介绍这些旋转操作。 ### 右旋 如下图所示,节点下方为平衡因子。从底至顶看,二叉树中首个失衡节点是“节点 3”。我们关注以该失衡节点为根节点的子树,将该节点记为 `node` ,其左子节点记为 `child` ,执行“右旋”操作。完成右旋后,子树恢复平衡,并且仍然保持二叉搜索树的性质。 === "<1>" ![右旋操作步骤](avl_tree.assets/avltree_right_rotate_step1.png) === "<2>" ![avltree_right_rotate_step2](avl_tree.assets/avltree_right_rotate_step2.png) === "<3>" ![avltree_right_rotate_step3](avl_tree.assets/avltree_right_rotate_step3.png) === "<4>" ![avltree_right_rotate_step4](avl_tree.assets/avltree_right_rotate_step4.png) 如下图所示,当节点 `child` 有右子节点(记为 `grand_child` )时,需要在右旋中添加一步:将 `grand_child` 作为 `node` 的左子节点。 ![有 grand_child 的右旋操作](avl_tree.assets/avltree_right_rotate_with_grandchild.png) “向右旋转”是一种形象化的说法,实际上需要通过修改节点指针来实现,代码如下所示: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{right_rotate} ``` ### 左旋 相应地,如果考虑上述失衡二叉树的“镜像”,则需要执行下图所示的“左旋”操作。 ![左旋操作](avl_tree.assets/avltree_left_rotate.png) 同理,如下图所示,当节点 `child` 有左子节点(记为 `grand_child` )时,需要在左旋中添加一步:将 `grand_child` 作为 `node` 的右子节点。 ![有 grand_child 的左旋操作](avl_tree.assets/avltree_left_rotate_with_grandchild.png) 可以观察到,**右旋和左旋操作在逻辑上是镜像对称的,它们分别解决的两种失衡情况也是对称的**。基于对称性,我们只需将右旋的实现代码中的所有的 `left` 替换为 `right` ,将所有的 `right` 替换为 `left` ,即可得到左旋的实现代码: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{left_rotate} ``` ### 先左旋后右旋 对于下图中的失衡节点 3 ,仅使用左旋或右旋都无法使子树恢复平衡。此时需要先对 `child` 执行“左旋”,再对 `node` 执行“右旋”。 ![先左旋后右旋](avl_tree.assets/avltree_left_right_rotate.png) ### 先右旋后左旋 如下图所示,对于上述失衡二叉树的镜像情况,需要先对 `child` 执行“右旋”,再对 `node` 执行“左旋”。 ![先右旋后左旋](avl_tree.assets/avltree_right_left_rotate.png) ### 旋转的选择 下图展示的四种失衡情况与上述案例逐个对应,分别需要采用右旋、先左旋后右旋、先右旋后左旋、左旋的操作。 ![AVL 树的四种旋转情况](avl_tree.assets/avltree_rotation_cases.png) 如下表所示,我们通过判断失衡节点的平衡因子以及较高一侧子节点的平衡因子的正负号,来确定失衡节点属于上图中的哪种情况。

  四种旋转情况的选择条件

| 失衡节点的平衡因子 | 子节点的平衡因子 | 应采用的旋转方法 | | ------------------ | ---------------- | ---------------- | | $> 1$ (左偏树) | $\geq 0$ | 右旋 | | $> 1$ (左偏树) | $<0$ | 先左旋后右旋 | | $< -1$ (右偏树) | $\leq 0$ | 左旋 | | $< -1$ (右偏树) | $>0$ | 先右旋后左旋 | 为了便于使用,我们将旋转操作封装成一个函数。**有了这个函数,我们就能对各种失衡情况进行旋转,使失衡节点重新恢复平衡**。代码如下所示: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{rotate} ``` ## AVL 树常用操作 ### 插入节点 AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区别在于,在 AVL 树中插入节点后,从该节点到根节点的路径上可能会出现一系列失衡节点。因此,**我们需要从这个节点开始,自底向上执行旋转操作,使所有失衡节点恢复平衡**。代码如下所示: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{insert_helper} ``` ### 删除节点 类似地,在二叉搜索树的删除节点方法的基础上,需要从底至顶执行旋转操作,使所有失衡节点恢复平衡。代码如下所示: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{remove_helper} ``` ### 查找节点 AVL 树的节点查找操作与二叉搜索树一致,在此不再赘述。 ## AVL 树典型应用 - 组织和存储大型数据,适用于高频查找、低频增删的场景。 - 用于构建数据库中的索引系统。 - 红黑树也是一种常见的平衡二叉搜索树。相较于 AVL 树,红黑树的平衡条件更宽松,插入与删除节点所需的旋转操作更少,节点增删操作的平均效率更高。 ================================================ FILE: docs/chapter_tree/binary_search_tree.md ================================================ # 二叉搜索树 如下图所示,二叉搜索树(binary search tree)满足以下条件。 1. 对于根节点,左子树中所有节点的值 $<$ 根节点的值 $<$ 右子树中所有节点的值。 2. 任意节点的左、右子树也是二叉搜索树,即同样满足条件 `1.` 。 ![二叉搜索树](binary_search_tree.assets/binary_search_tree.png) ## 二叉搜索树的操作 我们将二叉搜索树封装为一个类 `BinarySearchTree` ,并声明一个成员变量 `root` ,指向树的根节点。 ### 查找节点 给定目标节点值 `num` ,可以根据二叉搜索树的性质来查找。如下图所示,我们声明一个节点 `cur` ,从二叉树的根节点 `root` 出发,循环比较节点值 `cur.val` 和 `num` 之间的大小关系。 - 若 `cur.val < num` ,说明目标节点在 `cur` 的右子树中,因此执行 `cur = cur.right` 。 - 若 `cur.val > num` ,说明目标节点在 `cur` 的左子树中,因此执行 `cur = cur.left` 。 - 若 `cur.val = num` ,说明找到目标节点,跳出循环并返回该节点。 === "<1>" ![二叉搜索树查找节点示例](binary_search_tree.assets/bst_search_step1.png) === "<2>" ![bst_search_step2](binary_search_tree.assets/bst_search_step2.png) === "<3>" ![bst_search_step3](binary_search_tree.assets/bst_search_step3.png) === "<4>" ![bst_search_step4](binary_search_tree.assets/bst_search_step4.png) 二叉搜索树的查找操作与二分查找算法的工作原理一致,都是每轮排除一半情况。循环次数最多为二叉树的高度,当二叉树平衡时,使用 $O(\log n)$ 时间。示例代码如下: ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{search} ``` ### 插入节点 给定一个待插入元素 `num` ,为了保持二叉搜索树“左子树 < 根节点 < 右子树”的性质,插入操作流程如下图所示。 1. **查找插入位置**:与查找操作相似,从根节点出发,根据当前节点值和 `num` 的大小关系循环向下搜索,直到越过叶节点(遍历至 `None` )时跳出循环。 2. **在该位置插入节点**:初始化节点 `num` ,将该节点置于 `None` 的位置。 ![在二叉搜索树中插入节点](binary_search_tree.assets/bst_insert.png) 在代码实现中,需要注意以下两点。 - 二叉搜索树不允许存在重复节点,否则将违反其定义。因此,若待插入节点在树中已存在,则不执行插入,直接返回。 - 为了实现插入节点,我们需要借助节点 `pre` 保存上一轮循环的节点。这样在遍历至 `None` 时,我们可以获取到其父节点,从而完成节点插入操作。 ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{insert} ``` 与查找节点相同,插入节点使用 $O(\log n)$ 时间。 ### 删除节点 先在二叉树中查找到目标节点,再将其删除。与插入节点类似,我们需要保证在删除操作完成后,二叉搜索树的“左子树 < 根节点 < 右子树”的性质仍然满足。因此,我们根据目标节点的子节点数量,分 0、1 和 2 三种情况,执行对应的删除节点操作。 如下图所示,当待删除节点的度为 $0$ 时,表示该节点是叶节点,可以直接删除。 ![在二叉搜索树中删除节点(度为 0 )](binary_search_tree.assets/bst_remove_case1.png) 如下图所示,当待删除节点的度为 $1$ 时,将待删除节点替换为其子节点即可。 ![在二叉搜索树中删除节点(度为 1 )](binary_search_tree.assets/bst_remove_case2.png) 当待删除节点的度为 $2$ 时,我们无法直接删除它,而需要使用一个节点替换该节点。由于要保持二叉搜索树“左子树 $<$ 根节点 $<$ 右子树”的性质,**因此这个节点可以是右子树的最小节点或左子树的最大节点**。 假设我们选择右子树的最小节点(中序遍历的下一个节点),则删除操作流程如下图所示。 1. 找到待删除节点在“中序遍历序列”中的下一个节点,记为 `tmp` 。 2. 用 `tmp` 的值覆盖待删除节点的值,并在树中递归删除节点 `tmp` 。 === "<1>" ![在二叉搜索树中删除节点(度为 2 )](binary_search_tree.assets/bst_remove_case3_step1.png) === "<2>" ![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png) === "<3>" ![bst_remove_case3_step3](binary_search_tree.assets/bst_remove_case3_step3.png) === "<4>" ![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png) 删除节点操作同样使用 $O(\log n)$ 时间,其中查找待删除节点需要 $O(\log n)$ 时间,获取中序遍历后继节点需要 $O(\log n)$ 时间。示例代码如下: ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{remove} ``` ### 中序遍历有序 如下图所示,二叉树的中序遍历遵循“左 $\rightarrow$ 根 $\rightarrow$ 右”的遍历顺序,而二叉搜索树满足“左子节点 $<$ 根节点 $<$ 右子节点”的大小关系。 这意味着在二叉搜索树中进行中序遍历时,总是会优先遍历下一个最小节点,从而得出一个重要性质:**二叉搜索树的中序遍历序列是升序的**。 利用中序遍历升序的性质,我们在二叉搜索树中获取有序数据仅需 $O(n)$ 时间,无须进行额外的排序操作,非常高效。 ![二叉搜索树的中序遍历序列](binary_search_tree.assets/bst_inorder_traversal.png) ## 二叉搜索树的效率 给定一组数据,我们考虑使用数组或二叉搜索树存储。观察下表,二叉搜索树的各项操作的时间复杂度都是对数阶,具有稳定且高效的性能。只有在高频添加、低频查找删除数据的场景下,数组比二叉搜索树的效率更高。

  数组与搜索树的效率对比

| | 无序数组 | 二叉搜索树 | | -------- | -------- | ----------- | | 查找元素 | $O(n)$ | $O(\log n)$ | | 插入元素 | $O(1)$ | $O(\log n)$ | | 删除元素 | $O(n)$ | $O(\log n)$ | 在理想情况下,二叉搜索树是“平衡”的,这样就可以在 $\log n$ 轮循环内查找任意节点。 然而,如果我们在二叉搜索树中不断地插入和删除节点,可能导致二叉树退化为下图所示的链表,这时各种操作的时间复杂度也会退化为 $O(n)$ 。 ![二叉搜索树退化](binary_search_tree.assets/bst_degradation.png) ## 二叉搜索树常见应用 - 用作系统中的多级索引,实现高效的查找、插入、删除操作。 - 作为某些搜索算法的底层数据结构。 - 用于存储数据流,以保持其有序状态。 ================================================ FILE: docs/chapter_tree/binary_tree.md ================================================ # 二叉树 二叉树(binary tree)是一种非线性数据结构,代表“祖先”与“后代”之间的派生关系,体现了“一分为二”的分治逻辑。与链表类似,二叉树的基本单元是节点,每个节点包含值、左子节点引用和右子节点引用。 === "Python" ```python title="" class TreeNode: """二叉树节点类""" def __init__(self, val: int): self.val: int = val # 节点值 self.left: TreeNode | None = None # 左子节点引用 self.right: TreeNode | None = None # 右子节点引用 ``` === "C++" ```cpp title="" /* 二叉树节点结构体 */ struct TreeNode { int val; // 节点值 TreeNode *left; // 左子节点指针 TreeNode *right; // 右子节点指针 TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; ``` === "Java" ```java title="" /* 二叉树节点类 */ class TreeNode { int val; // 节点值 TreeNode left; // 左子节点引用 TreeNode right; // 右子节点引用 TreeNode(int x) { val = x; } } ``` === "C#" ```csharp title="" /* 二叉树节点类 */ class TreeNode(int? x) { public int? val = x; // 节点值 public TreeNode? left; // 左子节点引用 public TreeNode? right; // 右子节点引用 } ``` === "Go" ```go title="" /* 二叉树节点结构体 */ type TreeNode struct { Val int Left *TreeNode Right *TreeNode } /* 构造方法 */ func NewTreeNode(v int) *TreeNode { return &TreeNode{ Left: nil, // 左子节点指针 Right: nil, // 右子节点指针 Val: v, // 节点值 } } ``` === "Swift" ```swift title="" /* 二叉树节点类 */ class TreeNode { var val: Int // 节点值 var left: TreeNode? // 左子节点引用 var right: TreeNode? // 右子节点引用 init(x: Int) { val = x } } ``` === "JS" ```javascript title="" /* 二叉树节点类 */ class TreeNode { val; // 节点值 left; // 左子节点指针 right; // 右子节点指针 constructor(val, left, right) { this.val = val === undefined ? 0 : val; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } ``` === "TS" ```typescript title="" /* 二叉树节点类 */ class TreeNode { val: number; left: TreeNode | null; right: TreeNode | null; constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { this.val = val === undefined ? 0 : val; // 节点值 this.left = left === undefined ? null : left; // 左子节点引用 this.right = right === undefined ? null : right; // 右子节点引用 } } ``` === "Dart" ```dart title="" /* 二叉树节点类 */ class TreeNode { int val; // 节点值 TreeNode? left; // 左子节点引用 TreeNode? right; // 右子节点引用 TreeNode(this.val, [this.left, this.right]); } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* 二叉树节点结构体 */ struct TreeNode { val: i32, // 节点值 left: Option>>, // 左子节点引用 right: Option>>, // 右子节点引用 } impl TreeNode { /* 构造方法 */ fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, left: None, right: None })) } } ``` === "C" ```c title="" /* 二叉树节点结构体 */ typedef struct TreeNode { int val; // 节点值 int height; // 节点高度 struct TreeNode *left; // 左子节点指针 struct TreeNode *right; // 右子节点指针 } TreeNode; /* 构造函数 */ TreeNode *newTreeNode(int val) { TreeNode *node; node = (TreeNode *)malloc(sizeof(TreeNode)); node->val = val; node->height = 0; node->left = NULL; node->right = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* 二叉树节点类 */ class TreeNode(val _val: Int) { // 节点值 val left: TreeNode? = null // 左子节点引用 val right: TreeNode? = null // 右子节点引用 } ``` === "Ruby" ```ruby title="" ### 二叉树节点类 ### class TreeNode attr_accessor :val # 节点值 attr_accessor :left # 左子节点引用 attr_accessor :right # 右子节点引用 def initialize(val) @val = val end end ``` 每个节点都有两个引用(指针),分别指向左子节点(left-child node)右子节点(right-child node),该节点被称为这两个子节点的父节点(parent node)。当给定一个二叉树的节点时,我们将该节点的左子节点及其以下节点形成的树称为该节点的左子树(left subtree),同理可得右子树(right subtree)。 **在二叉树中,除叶节点外,其他所有节点都包含子节点和非空子树**。如下图所示,如果将“节点 2”视为父节点,则其左子节点和右子节点分别是“节点 4”和“节点 5”,左子树是“节点 4 及其以下节点形成的树”,右子树是“节点 5 及其以下节点形成的树”。 ![父节点、子节点、子树](binary_tree.assets/binary_tree_definition.png) ## 二叉树常见术语 二叉树的常用术语如下图所示。 - 根节点(root node):位于二叉树顶层的节点,没有父节点。 - 叶节点(leaf node):没有子节点的节点,其两个指针均指向 `None` 。 - 边(edge):连接两个节点的线段,即节点引用(指针)。 - 节点所在的层(level):从顶至底递增,根节点所在层为 1 。 - 节点的度(degree):节点的子节点的数量。在二叉树中,度的取值范围是 0、1、2 。 - 二叉树的高度(height):从根节点到最远叶节点所经过的边的数量。 - 节点的深度(depth):从根节点到该节点所经过的边的数量。 - 节点的高度(height):从距离该节点最远的叶节点到该节点所经过的边的数量。 ![二叉树的常用术语](binary_tree.assets/binary_tree_terminology.png) !!! tip 请注意,我们通常将“高度”和“深度”定义为“经过的边的数量”,但有些题目或教材可能会将其定义为“经过的节点的数量”。在这种情况下,高度和深度都需要加 1 。 ## 二叉树基本操作 ### 初始化二叉树 与链表类似,首先初始化节点,然后构建引用(指针)。 === "Python" ```python title="binary_tree.py" # 初始化二叉树 # 初始化节点 n1 = TreeNode(val=1) n2 = TreeNode(val=2) n3 = TreeNode(val=3) n4 = TreeNode(val=4) n5 = TreeNode(val=5) # 构建节点之间的引用(指针) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` === "C++" ```cpp title="binary_tree.cpp" /* 初始化二叉树 */ // 初始化节点 TreeNode* n1 = new TreeNode(1); TreeNode* n2 = new TreeNode(2); TreeNode* n3 = new TreeNode(3); TreeNode* n4 = new TreeNode(4); TreeNode* n5 = new TreeNode(5); // 构建节点之间的引用(指针) n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; ``` === "Java" ```java title="binary_tree.java" // 初始化节点 TreeNode n1 = new TreeNode(1); TreeNode n2 = new TreeNode(2); TreeNode n3 = new TreeNode(3); TreeNode n4 = new TreeNode(4); TreeNode n5 = new TreeNode(5); // 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "C#" ```csharp title="binary_tree.cs" /* 初始化二叉树 */ // 初始化节点 TreeNode n1 = new(1); TreeNode n2 = new(2); TreeNode n3 = new(3); TreeNode n4 = new(4); TreeNode n5 = new(5); // 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "Go" ```go title="binary_tree.go" /* 初始化二叉树 */ // 初始化节点 n1 := NewTreeNode(1) n2 := NewTreeNode(2) n3 := NewTreeNode(3) n4 := NewTreeNode(4) n5 := NewTreeNode(5) // 构建节点之间的引用(指针) n1.Left = n2 n1.Right = n3 n2.Left = n4 n2.Right = n5 ``` === "Swift" ```swift title="binary_tree.swift" // 初始化节点 let n1 = TreeNode(x: 1) let n2 = TreeNode(x: 2) let n3 = TreeNode(x: 3) let n4 = TreeNode(x: 4) let n5 = TreeNode(x: 5) // 构建节点之间的引用(指针) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` === "JS" ```javascript title="binary_tree.js" /* 初始化二叉树 */ // 初始化节点 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "TS" ```typescript title="binary_tree.ts" /* 初始化二叉树 */ // 初始化节点 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "Dart" ```dart title="binary_tree.dart" /* 初始化二叉树 */ // 初始化节点 TreeNode n1 = new TreeNode(1); TreeNode n2 = new TreeNode(2); TreeNode n3 = new TreeNode(3); TreeNode n4 = new TreeNode(4); TreeNode n5 = new TreeNode(5); // 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "Rust" ```rust title="binary_tree.rs" // 初始化节点 let n1 = TreeNode::new(1); let n2 = TreeNode::new(2); let n3 = TreeNode::new(3); let n4 = TreeNode::new(4); let n5 = TreeNode::new(5); // 构建节点之间的引用(指针) n1.borrow_mut().left = Some(n2.clone()); n1.borrow_mut().right = Some(n3); n2.borrow_mut().left = Some(n4); n2.borrow_mut().right = Some(n5); ``` === "C" ```c title="binary_tree.c" /* 初始化二叉树 */ // 初始化节点 TreeNode *n1 = newTreeNode(1); TreeNode *n2 = newTreeNode(2); TreeNode *n3 = newTreeNode(3); TreeNode *n4 = newTreeNode(4); TreeNode *n5 = newTreeNode(5); // 构建节点之间的引用(指针) n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; ``` === "Kotlin" ```kotlin title="binary_tree.kt" // 初始化节点 val n1 = TreeNode(1) val n2 = TreeNode(2) val n3 = TreeNode(3) val n4 = TreeNode(4) val n5 = TreeNode(5) // 构建节点之间的引用(指针) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` === "Ruby" ```ruby title="binary_tree.rb" # 初始化二叉树 # 初始化节点 n1 = TreeNode.new(1) n2 = TreeNode.new(2) n3 = TreeNode.new(3) n4 = TreeNode.new(4) n5 = TreeNode.new(5) # 构建节点之间的引用(指针) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 插入与删除节点 与链表类似,在二叉树中插入与删除节点可以通过修改指针来实现。下图给出了一个示例。 ![在二叉树中插入与删除节点](binary_tree.assets/binary_tree_add_remove.png) === "Python" ```python title="binary_tree.py" # 插入与删除节点 p = TreeNode(0) # 在 n1 -> n2 中间插入节点 P n1.left = p p.left = n2 # 删除节点 P n1.left = n2 ``` === "C++" ```cpp title="binary_tree.cpp" /* 插入与删除节点 */ TreeNode* P = new TreeNode(0); // 在 n1 -> n2 中间插入节点 P n1->left = P; P->left = n2; // 删除节点 P n1->left = n2; // 释放内存 delete P; ``` === "Java" ```java title="binary_tree.java" TreeNode P = new TreeNode(0); // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; // 删除节点 P n1.left = n2; ``` === "C#" ```csharp title="binary_tree.cs" /* 插入与删除节点 */ TreeNode P = new(0); // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; // 删除节点 P n1.left = n2; ``` === "Go" ```go title="binary_tree.go" /* 插入与删除节点 */ // 在 n1 -> n2 中间插入节点 P p := NewTreeNode(0) n1.Left = p p.Left = n2 // 删除节点 P n1.Left = n2 ``` === "Swift" ```swift title="binary_tree.swift" let P = TreeNode(x: 0) // 在 n1 -> n2 中间插入节点 P n1.left = P P.left = n2 // 删除节点 P n1.left = n2 ``` === "JS" ```javascript title="binary_tree.js" /* 插入与删除节点 */ let P = new TreeNode(0); // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; // 删除节点 P n1.left = n2; ``` === "TS" ```typescript title="binary_tree.ts" /* 插入与删除节点 */ const P = new TreeNode(0); // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; // 删除节点 P n1.left = n2; ``` === "Dart" ```dart title="binary_tree.dart" /* 插入与删除节点 */ TreeNode P = new TreeNode(0); // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; // 删除节点 P n1.left = n2; ``` === "Rust" ```rust title="binary_tree.rs" let p = TreeNode::new(0); // 在 n1 -> n2 中间插入节点 P n1.borrow_mut().left = Some(p.clone()); p.borrow_mut().left = Some(n2.clone()); // 删除节点 p n1.borrow_mut().left = Some(n2); ``` === "C" ```c title="binary_tree.c" /* 插入与删除节点 */ TreeNode *P = newTreeNode(0); // 在 n1 -> n2 中间插入节点 P n1->left = P; P->left = n2; // 删除节点 P n1->left = n2; // 释放内存 free(P); ``` === "Kotlin" ```kotlin title="binary_tree.kt" val P = TreeNode(0) // 在 n1 -> n2 中间插入节点 P n1.left = P P.left = n2 // 删除节点 P n1.left = n2 ``` === "Ruby" ```ruby title="binary_tree.rb" # 插入与删除节点 _p = TreeNode.new(0) # 在 n1 -> n2 中间插入节点 _p n1.left = _p _p.left = n2 # 删除节点 n1.left = n2 ``` ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%B8%8E%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%E5%9C%A8%20n1%20-%3E%20n2%20%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20n2&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false !!! tip 需要注意的是,插入节点可能会改变二叉树的原有逻辑结构,而删除节点通常意味着删除该节点及其所有子树。因此,在二叉树中,插入与删除通常是由一套操作配合完成的,以实现有实际意义的操作。 ## 常见二叉树类型 ### 完美二叉树 如下图所示,完美二叉树(perfect binary tree)所有层的节点都被完全填满。在完美二叉树中,叶节点的度为 $0$ ,其余所有节点的度都为 $2$ ;若树的高度为 $h$ ,则节点总数为 $2^{h+1} - 1$ ,呈现标准的指数级关系,反映了自然界中常见的细胞分裂现象。 !!! tip 请注意,在中文社区中,完美二叉树常被称为满二叉树。 ![完美二叉树](binary_tree.assets/perfect_binary_tree.png) ### 完全二叉树 如下图所示,完全二叉树(complete binary tree)仅允许最底层的节点不完全填满,且最底层的节点必须从左至右依次连续填充。请注意,完美二叉树也是一棵完全二叉树。 ![完全二叉树](binary_tree.assets/complete_binary_tree.png) ### 完满二叉树 如下图所示,完满二叉树(full binary tree)除了叶节点之外,其余所有节点都有两个子节点。 ![完满二叉树](binary_tree.assets/full_binary_tree.png) ### 平衡二叉树 如下图所示,平衡二叉树(balanced binary tree)中任意节点的左子树和右子树的高度之差的绝对值不超过 1 。 ![平衡二叉树](binary_tree.assets/balanced_binary_tree.png) ## 二叉树的退化 下图展示了二叉树的理想结构与退化结构。当二叉树的每层节点都被填满时,达到“完美二叉树”;而当所有节点都偏向一侧时,二叉树退化为“链表”。 - 完美二叉树是理想情况,可以充分发挥二叉树“分治”的优势。 - 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 $O(n)$ 。 ![二叉树的最佳结构与最差结构](binary_tree.assets/binary_tree_best_worst_cases.png) 如下表所示,在最佳结构和最差结构下,二叉树的叶节点数量、节点总数、高度等达到极大值或极小值。

  二叉树的最佳结构与最差结构

| | 完美二叉树 | 链表 | | --------------------------- | ------------------ | ------- | | 第 $i$ 层的节点数量 | $2^{i-1}$ | $1$ | | 高度为 $h$ 的树的叶节点数量 | $2^h$ | $1$ | | 高度为 $h$ 的树的节点总数 | $2^{h+1} - 1$ | $h + 1$ | | 节点总数为 $n$ 的树的高度 | $\log_2 (n+1) - 1$ | $n - 1$ | ================================================ FILE: docs/chapter_tree/binary_tree_traversal.md ================================================ # 二叉树遍历 从物理结构的角度来看,树是一种基于链表的数据结构,因此其遍历方式是通过指针逐个访问节点。然而,树是一种非线性数据结构,这使得遍历树比遍历链表更加复杂,需要借助搜索算法来实现。 二叉树常见的遍历方式包括层序遍历、前序遍历、中序遍历和后序遍历等。 ## 层序遍历 如下图所示,层序遍历(level-order traversal)从顶部到底部逐层遍历二叉树,并在每一层按照从左到右的顺序访问节点。 层序遍历本质上属于广度优先遍历(breadth-first traversal),也称广度优先搜索(breadth-first search, BFS),它体现了一种“一圈一圈向外扩展”的逐层遍历方式。 ![二叉树的层序遍历](binary_tree_traversal.assets/binary_tree_bfs.png) ### 代码实现 广度优先遍历通常借助“队列”来实现。队列遵循“先进先出”的规则,而广度优先遍历则遵循“逐层推进”的规则,两者背后的思想是一致的。实现代码如下: ```src [file]{binary_tree_bfs}-[class]{}-[func]{level_order} ``` ### 复杂度分析 - **时间复杂度为 $O(n)$** :所有节点被访问一次,使用 $O(n)$ 时间,其中 $n$ 为节点数量。 - **空间复杂度为 $O(n)$** :在最差情况下,即满二叉树时,遍历到最底层之前,队列中最多同时存在 $(n + 1) / 2$ 个节点,占用 $O(n)$ 空间。 ## 前序、中序、后序遍历 相应地,前序、中序和后序遍历都属于深度优先遍历(depth-first traversal),也称深度优先搜索(depth-first search, DFS),它体现了一种“先走到尽头,再回溯继续”的遍历方式。 下图展示了对二叉树进行深度优先遍历的工作原理。**深度优先遍历就像是绕着整棵二叉树的外围“走”一圈**,在每个节点都会遇到三个位置,分别对应前序遍历、中序遍历和后序遍历。 ![二叉搜索树的前序、中序、后序遍历](binary_tree_traversal.assets/binary_tree_dfs.png) ### 代码实现 深度优先搜索通常基于递归实现: ```src [file]{binary_tree_dfs}-[class]{}-[func]{post_order} ``` !!! tip 深度优先搜索也可以基于迭代实现,有兴趣的读者可以自行研究。 下图展示了前序遍历二叉树的递归过程,其可分为“递”和“归”两个逆向的部分。 1. “递”表示开启新方法,程序在此过程中访问下一个节点。 2. “归”表示函数返回,代表当前节点已经访问完毕。 === "<1>" ![前序遍历的递归过程](binary_tree_traversal.assets/preorder_step1.png) === "<2>" ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png) === "<3>" ![preorder_step3](binary_tree_traversal.assets/preorder_step3.png) === "<4>" ![preorder_step4](binary_tree_traversal.assets/preorder_step4.png) === "<5>" ![preorder_step5](binary_tree_traversal.assets/preorder_step5.png) === "<6>" ![preorder_step6](binary_tree_traversal.assets/preorder_step6.png) === "<7>" ![preorder_step7](binary_tree_traversal.assets/preorder_step7.png) === "<8>" ![preorder_step8](binary_tree_traversal.assets/preorder_step8.png) === "<9>" ![preorder_step9](binary_tree_traversal.assets/preorder_step9.png) === "<10>" ![preorder_step10](binary_tree_traversal.assets/preorder_step10.png) === "<11>" ![preorder_step11](binary_tree_traversal.assets/preorder_step11.png) ### 复杂度分析 - **时间复杂度为 $O(n)$** :所有节点被访问一次,使用 $O(n)$ 时间。 - **空间复杂度为 $O(n)$** :在最差情况下,即树退化为链表时,递归深度达到 $n$ ,系统占用 $O(n)$ 栈帧空间。 ================================================ FILE: docs/chapter_tree/index.md ================================================ # 树 ![树](../assets/covers/chapter_tree.jpg) !!! abstract 参天大树充满生命力,根深叶茂,分枝扶疏。 它为我们展现了数据分治的生动形态。 ================================================ FILE: docs/chapter_tree/summary.md ================================================ # 小结 ### 重点回顾 - 二叉树是一种非线性数据结构,体现“一分为二”的分治逻辑。每个二叉树节点包含一个值以及两个指针,分别指向其左子节点和右子节点。 - 对于二叉树中的某个节点,其左(右)子节点及其以下形成的树被称为该节点的左(右)子树。 - 二叉树的相关术语包括根节点、叶节点、层、度、边、高度和深度等。 - 二叉树的初始化、节点插入和节点删除操作与链表操作方法类似。 - 常见的二叉树类型有完美二叉树、完全二叉树、完满二叉树和平衡二叉树。完美二叉树是最理想的状态,而链表是退化后的最差状态。 - 二叉树可以用数组表示,方法是将节点值和空位按层序遍历顺序排列,并根据父节点与子节点之间的索引映射关系来实现指针。 - 二叉树的层序遍历是一种广度优先搜索方法,它体现了“一圈一圈向外扩展”的逐层遍历方式,通常通过队列来实现。 - 前序、中序、后序遍历皆属于深度优先搜索,它们体现了“先走到尽头,再回溯继续”的遍历方式,通常使用递归来实现。 - 二叉搜索树是一种高效的元素查找数据结构,其查找、插入和删除操作的时间复杂度均为 $O(\log n)$ 。当二叉搜索树退化为链表时,各项时间复杂度会劣化至 $O(n)$ 。 - AVL 树,也称平衡二叉搜索树,它通过旋转操作确保在不断插入和删除节点后树仍然保持平衡。 - AVL 树的旋转操作包括右旋、左旋、先右旋再左旋、先左旋再右旋。在插入或删除节点后,AVL 树会从底向顶执行旋转操作,使树重新恢复平衡。 ### Q & A **Q**:对于只有一个节点的二叉树,树的高度和根节点的深度都是 $0$ 吗? 是的,因为高度和深度通常定义为“经过的边的数量”。 **Q**:二叉树中的插入与删除一般由一套操作配合完成,这里的“一套操作”指什么呢?可以理解为资源的子节点的资源释放吗? 拿二叉搜索树来举例,删除节点操作要分三种情况处理,其中每种情况都需要进行多个步骤的节点操作。 **Q**:为什么 DFS 遍历二叉树有前、中、后三种顺序,分别有什么用呢? 与顺序和逆序遍历数组类似,前序、中序、后序遍历是三种二叉树遍历方法,我们可以使用它们得到一个特定顺序的遍历结果。例如在二叉搜索树中,由于节点大小满足 `左子节点值 < 根节点值 < 右子节点值` ,因此我们只要按照“左 $\rightarrow$ 根 $\rightarrow$ 右”的优先级遍历树,就可以获得有序的节点序列。 **Q**:右旋操作是处理失衡节点 `node`、`child`、`grand_child` 之间的关系,那 `node` 的父节点和 `node` 原来的连接不需要维护吗?右旋操作后岂不是断掉了? 我们需要从递归的视角来看这个问题。右旋操作 `right_rotate(root)` 传入的是子树的根节点,最终 `return child` 返回旋转之后的子树的根节点。子树的根节点和其父节点的连接是在该函数返回后完成的,不属于右旋操作的维护范围。 **Q**:在 C++ 中,函数被划分到 `private` 和 `public` 中,这方面有什么考量吗?为什么要将 `height()` 函数和 `updateHeight()` 函数分别放在 `public` 和 `private` 中呢? 主要看方法的使用范围,如果方法只在类内部使用,那么就设计为 `private` 。例如,用户单独调用 `updateHeight()` 是没有意义的,它只是插入、删除操作中的一步。而 `height()` 是访问节点高度,类似于 `vector.size()` ,因此设置成 `public` 以便使用。 **Q**:如何从一组输入数据构建一棵二叉搜索树?根节点的选择是不是很重要? 是的,构建树的方法已在二叉搜索树代码中的 `build_tree()` 方法中给出。至于根节点的选择,我们通常会将输入数据排序,然后将中点元素作为根节点,再递归地构建左右子树。这样做可以最大程度保证树的平衡性。 **Q**:在 Java 中,字符串对比是否一定要用 `equals()` 方法? 在 Java 中,对于基本数据类型,`==` 用于对比两个变量的值是否相等。对于引用类型,两种符号的工作原理是不同的。 - `==` :用来比较两个变量是否指向同一个对象,即它们在内存中的位置是否相同。 - `equals()`:用来对比两个对象的值是否相等。 因此,如果要对比值,我们应该使用 `equals()` 。然而,通过 `String a = "hi"; String b = "hi";` 初始化的字符串都存储在字符串常量池中,它们指向同一个对象,因此也可以用 `a == b` 来比较两个字符串的内容。 **Q**:广度优先遍历到最底层之前,队列中的节点数量是 $2^h$ 吗? 是的,例如高度 $h = 2$ 的满二叉树,其节点总数 $n = 7$ ,则底层节点数量 $4 = 2^h = (n + 1) / 2$ 。 ================================================ FILE: docs/index.html ================================================

动画图解、一键运行的数据结构与算法教程

开始阅读 GitHub

500 幅动画图解、14 种编程语言代码、3000 条社区问答,助你快速入门数据结构与算法

推荐语

“一本通俗易懂的数据结构与算法入门书,引导读者手脑并用地学习,强烈推荐算法初学者阅读。”

—— 邓俊辉,清华大学计算机系教授

“如果我当年学数据结构与算法的时候有《Hello 算法》,学起来应该会简单 10 倍!”

—— 李沐,亚马逊资深首席科学家

动画图解

内容清晰易懂,学习曲线平滑

"A picture is worth a thousand words."
“一图胜千言”

一键运行

十余种编程语言,代码可视化运行

"Talk is cheap. Show me the code."
“少吹牛,看代码”

互助学习

欢迎讨论与提问,读者间携手共进

"Learning by teaching."
“教学相长”

贡献者

本书在开源社区 200 多位贡献者的共同努力下不断完善,感谢他们付出的时间与精力!

Contributors
================================================ FILE: docs/index.md ================================================ # Hello 算法 动画图解、一键运行的数据结构与算法教程。 [开始阅读](chapter_hello_algo/) ================================================ FILE: en/CONTRIBUTING.md ================================================ # Contributing guidelines for Chinese-to-English We are working on translating "Hello Algo" from Chinese to English with the following approach: 1. **AI translation**: Carry out an initial pass of translations using large language models. 2. **Human optimization**: Manually refine the machine-generated outputs to ensure authenticity and accuracy. 3. **Pull request review**: The optimized translation will be double checked by the reviewers through GitHub pull request workflow. 4. Repeat steps `2.` and `3.` for further improvements. translation_pipeline ## Join us We're seeking contributors who meet the following criteria. - **Technical background**: Strong foundation in computer science, particularly in data structures and algorithms. - **Language skills**: Native proficiency in Chinese with professional-level English, or native English. - **Available time**: Dedicated to contributing to open-source projects with a willingness to engage in long-term translation efforts. That is, our contributors are computer scientists, engineers, and students from different linguistic backgrounds, and their objectives have different focal points: - **Native Chinese with professional working English**: Ensuring translation accuracy and consistency between CN and EN versions. - **Native English**: Enhance the authenticity and fluency of the English content to flow naturally and to be engaging. > [!note] > If you are interested in joining us, don't hesitate to contact me via krahetx@gmail.com or WeChat `krahets-jyd`. > > We use this [Notion page](https://hello-algo.notion.site/chinese-to-english) to track progress and assign tasks. Please visit it for more details. ## Translation process > [!important] > Before diving in, ensure you're comfortable with the GitHub pull request workflow and have read the "Translation standards" and "Pseudo-code for translation" below. 1. **Task assignment**: Self-assign a task in the Notion workspace. 2. **Translation**: Optimize the translation on your local PC, referring to the “Translation Pseudo-Code” section below for more details. 3. **Peer review**: Carefully review your changes before submitting a Pull Request (PR). The PR will be merged into the main branch after approval from two reviewers. ## Translation standards > [!tip] > **The "Accuracy" and "Authenticity" are primarily handled by native Chinese speakers and native English speakers, respectively.** > > In some instances, "Accuracy (consistency)" and "Authenticity" represent a trade-off, where optimizing one aspect could significantly affect the other. In such cases, please leave a comment in the pull request for discussion. **Accuracy**: - Maintain consistency in terminology across translations by referring to the [Terminology](https://www.hello-algo.com/chapter_appendix/terminology/) section. - Prioritize technical accuracy and maintain the tone and style of the Chinese version. - Always take into account the content and context of the Chinese version to ensure modifications are accurate and comprehensive. **Authenticity**: - Translations should flow naturally and fluently, adhering to English expression conventions. - Always consider the context of the content to harmonize the article. - Be aware of cultural differences between Chinese and English. For instance, Chinese "pinyin" does not exist in English. - If the optimized sentence could alter the original meaning, please add a comment for discussion. **Formatting**: - Figures and tables will be automatically numbered during deployment, so DO NOT manually number them. - Each PR should cover at least one complete document to ensure manageable review sizes, except for bug fixes. **Review**: - During the review, prioritize evaluating the changes, consulting the surrounding context as needed. - Learning from each other's perspectives can lead to better translations and more cohesive results. ## Translation pseudo-code The following pseudo-code models the steps in a typical translation process. ```python def optimize_translation(markdown_texts, lang_skill): """Optimize the translation""" for sentence in markdown_texts: """Accuracy is handled primarily by native Chinese speakers""" if lang_skill is "Native Chinese + Professional working English": if is_accurate_Chinese_to_English(sentence): continue # Optimize the accuracy result = refine_accuracy(sentence) """ Authenticity is handled primarily by native English speakers and secondarily by native Chinese speakers """ if is_authentic_English(sentence): continue # Optimize the authenticity result = refine_authenticity(sentence) # Add comments in the PR if it may break consistency if break_consistency(result): add_comment(description) pull_request = submit_pull_request(markdown_texts) # The PR will be merged after approved by >= 2 reviewers while count_approvals(pull_request) < 2: continue merge(pull_request) ``` The following pseudo-code is for the reviewers: ```python def review_pull_requests(pull_request, lang_skill): """Review the PR""" # Loop through all the changes in the PR while is_anything_left_to_review(pull_request): change = get_next_change(pull_request) """Accuracy is handled primarily by native Chinese speakers""" if lang_skill is "Native Chinese + Professional working English": # Check the accuracy(consistency) between CN and EN versions if is_accurate_Chinese_to_English(change): continue # Optimize the accuracy(consistency) result = refine_accuracy(change) # Add comments in the PR add_comment(result) """ Authenticity is handled primarily by native English speakers and secondarily by native Chinese speakers """ if is_authentic_English(change): continue # Optimize the authenticity if it is not authentic English result = refine_authenticity(change) # Add comments in the PR add_comment(result) approve(pull_request) ``` ================================================ FILE: en/README.md ================================================

hello-algo-typing-svg
Data structures and algorithms crash course with animated illustrations and off-the-shelf code

简体中文繁體中文 | English | 日本語Русский

## The book This open-source project aims to create a free and beginner-friendly crash course for data structures and algorithms. - Animated illustrations, easy-to-understand content, and a smooth learning curve help beginners explore the "knowledge map" of data structures and algorithms. - Run code with just one click, helping readers improve their programming skills and understand the working principle of algorithms and the underlying implementation of data structures. - Promoting learning by teaching, feel free to ask questions and share insights. Let's grow together through discussion. If you find this book helpful, please give it a Star :star: to support us, thank you! ## Endorsements > "An easy-to-understand book on data structures and algorithms, which guides readers to learn by minds-on and hands-on. Strongly recommended for algorithm beginners!" > > **—— Junhui Deng, Professor, Department of computer science and technology, Tsinghua University** > "If I had 'Hello Algo' when I was learning data structures and algorithms, it would have been 10 times easier!" > > **—— Mu Li, Senior Principal Scientist, Amazon** ## Special thanks

Warp-Github-LG-02

[Warp is built for coding with multiple AI agents.](https://go.warp.dev/hello-algo) ## Contributing > [!Important] > > Welcome to contribute to Chinese-to-English translation! For more information please see [CONTRIBUTING.md](CONTRIBUTING.md). This open-source book is continuously being updated, and we welcome your participation in this project to provide better learning content for readers. - [Content Correction](https://www.hello-algo.com/en/chapter_appendix/contribution/): Please help us correct or point out mistakes in the comments section such as grammatical errors, missing content, ambiguities, invalid links, or code bugs. - [Code Transpilation](https://github.com/krahets/hello-algo/issues/15): We look forward to your contributions in various programming languages. We currently support 12 languages including Python, Java, C++, Go, and JavaScript. We welcome your valuable suggestions and feedback. If you have any questions, please submit Issues or reach out via WeChat: `krahets-jyd`. We would like to dedicate our thanks to all the contributors of this book. It is their selfless dedication that has made this book better. They are:

## License The texts, code, images, photos, and videos in this repository are licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). ================================================ FILE: en/codes/c/.gitignore ================================================ # Ignore all * # Unignore all with extensions !*.* # Unignore all dirs !*/ *.dSYM/ build/ ================================================ FILE: en/codes/c/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(hello_algo C) set(CMAKE_C_STANDARD 11) include_directories(./include) add_subdirectory(chapter_computational_complexity) add_subdirectory(chapter_array_and_linkedlist) add_subdirectory(chapter_stack_and_queue) add_subdirectory(chapter_hashing) add_subdirectory(chapter_tree) add_subdirectory(chapter_heap) add_subdirectory(chapter_graph) add_subdirectory(chapter_searching) add_subdirectory(chapter_sorting) add_subdirectory(chapter_divide_and_conquer) add_subdirectory(chapter_backtracking) add_subdirectory(chapter_dynamic_programming) add_subdirectory(chapter_greedy) ================================================ FILE: en/codes/c/chapter_array_and_linkedlist/CMakeLists.txt ================================================ add_executable(array array.c) add_executable(linked_list linked_list.c) add_executable(my_list my_list.c) ================================================ FILE: en/codes/c/chapter_array_and_linkedlist/array.c ================================================ /** * File: array.c * Created Time: 2022-12-20 * Author: MolDuM (moldum@163.com) */ #include "../utils/common.h" /* Random access to element */ int randomAccess(int *nums, int size) { // Randomly select a number from interval [0, size) int randomIndex = rand() % size; // Retrieve and return the random element int randomNum = nums[randomIndex]; return randomNum; } /* Extend array length */ int *extend(int *nums, int size, int enlarge) { // Initialize an array with extended length int *res = (int *)malloc(sizeof(int) * (size + enlarge)); // Copy all elements from the original array to the new array for (int i = 0; i < size; i++) { res[i] = nums[i]; } // Initialize expanded space for (int i = size; i < size + enlarge; i++) { res[i] = 0; } // Return the extended new array return res; } /* Insert element num at index index in the array */ void insert(int *nums, int size, int num, int index) { // Move all elements at and after index index backward by one position for (int i = size - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Assign num to the element at index index nums[index] = num; } /* Remove the element at index index */ // Note: stdio.h occupies the remove keyword void removeItem(int *nums, int size, int index) { // Move all elements after index index forward by one position for (int i = index; i < size - 1; i++) { nums[i] = nums[i + 1]; } } /* Traverse array */ void traverse(int *nums, int size) { int count = 0; // Traverse array by index for (int i = 0; i < size; i++) { count += nums[i]; } } /* Find the specified element in the array */ int find(int *nums, int size, int target) { for (int i = 0; i < size; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ int main() { /* Initialize array */ int size = 5; int arr[5]; printf("Array arr = "); printArray(arr, size); int nums[] = {1, 3, 2, 5, 4}; printf("Array nums = "); printArray(nums, size); /* Insert element */ int randomNum = randomAccess(nums, size); printf("Get random element %d from nums", randomNum); /* Traverse array */ int enlarge = 3; int *res = extend(nums, size, enlarge); size += enlarge; printf("Extend array length to 8, resulting in nums = "); printArray(res, size); /* Insert element */ insert(res, size, 6, 3); printf("Insert number 6 at index 3, resulting in nums = "); printArray(res, size); /* Remove element */ removeItem(res, size, 2); printf("Remove element at index 2, resulting in nums = "); printArray(res, size); /* Traverse array */ traverse(res, size); /* Find element */ int index = find(res, size, 3); printf("Find element 3 in res, index = %d\n", index); /* Free memory */ free(res); return 0; } ================================================ FILE: en/codes/c/chapter_array_and_linkedlist/linked_list.c ================================================ /** * File: linked_list.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* Insert node P after node n0 in the linked list */ void insert(ListNode *n0, ListNode *P) { ListNode *n1 = n0->next; P->next = n1; n0->next = P; } /* Remove the first node after node n0 in the linked list */ // Note: stdio.h occupies the remove keyword void removeItem(ListNode *n0) { if (!n0->next) return; // n0 -> P -> n1 ListNode *P = n0->next; ListNode *n1 = P->next; n0->next = n1; // Free memory free(P); } /* Access the node at index index in the linked list */ ListNode *access(ListNode *head, int index) { for (int i = 0; i < index; i++) { if (head == NULL) return NULL; head = head->next; } return head; } /* Find the first node with value target in the linked list */ int find(ListNode *head, int target) { int index = 0; while (head) { if (head->val == target) return index; head = head->next; index++; } return -1; } /* Driver Code */ int main() { /* Initialize linked list */ // Initialize each node ListNode *n0 = newListNode(1); ListNode *n1 = newListNode(3); ListNode *n2 = newListNode(2); ListNode *n3 = newListNode(5); ListNode *n4 = newListNode(4); // Build references between nodes n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; printf("Initialized linked list is\r\n"); printLinkedList(n0); /* Insert node */ insert(n0, newListNode(0)); printf("Linked list after node insertion is\r\n"); printLinkedList(n0); /* Remove node */ removeItem(n0); printf("Linked list after node deletion is\r\n"); printLinkedList(n0); /* Access node */ ListNode *node = access(n0, 3); printf("Value of node at index 3 in linked list = %d\r\n", node->val); /* Search node */ int index = find(n0, 2); printf("Index of node with value 2 in linked list = %d\r\n", index); // Free memory freeMemoryLinkedList(n0); return 0; } ================================================ FILE: en/codes/c/chapter_array_and_linkedlist/my_list.c ================================================ /** * File: my_list.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* List class */ typedef struct { int *arr; // Array (stores list elements) int capacity; // List capacity int size; // List size int extendRatio; // List expansion multiplier } MyList; void extendCapacity(MyList *nums); /* Constructor */ MyList *newMyList() { MyList *nums = malloc(sizeof(MyList)); nums->capacity = 10; nums->arr = malloc(sizeof(int) * nums->capacity); nums->size = 0; nums->extendRatio = 2; return nums; } /* Destructor */ void delMyList(MyList *nums) { free(nums->arr); free(nums); } /* Get list length */ int size(MyList *nums) { return nums->size; } /* Get list capacity */ int capacity(MyList *nums) { return nums->capacity; } /* Update element */ int get(MyList *nums, int index) { assert(index >= 0 && index < nums->size); return nums->arr[index]; } /* Add elements at the end */ void set(MyList *nums, int index, int num) { assert(index >= 0 && index < nums->size); nums->arr[index] = num; } /* Direct traversal of list elements */ void add(MyList *nums, int num) { if (size(nums) == capacity(nums)) { extendCapacity(nums); // Expand capacity } nums->arr[size(nums)] = num; nums->size++; } /* Sort list */ void insert(MyList *nums, int index, int num) { assert(index >= 0 && index < size(nums)); // When the number of elements exceeds capacity, trigger the extension mechanism if (size(nums) == capacity(nums)) { extendCapacity(nums); // Expand capacity } for (int i = size(nums); i > index; --i) { nums->arr[i] = nums->arr[i - 1]; } nums->arr[index] = num; nums->size++; } /* Remove element */ // Note: stdio.h occupies the remove keyword int removeItem(MyList *nums, int index) { assert(index >= 0 && index < size(nums)); int num = nums->arr[index]; for (int i = index; i < size(nums) - 1; i++) { nums->arr[i] = nums->arr[i + 1]; } nums->size--; return num; } /* Driver Code */ void extendCapacity(MyList *nums) { // Allocate space first int newCapacity = capacity(nums) * nums->extendRatio; int *extend = (int *)malloc(sizeof(int) * newCapacity); int *temp = nums->arr; // Copy old data to new data for (int i = 0; i < size(nums); i++) extend[i] = nums->arr[i]; // Free old data free(temp); // Update new data nums->arr = extend; nums->capacity = newCapacity; } /* Convert list to Array for printing */ int *toArray(MyList *nums) { return nums->arr; } /* Driver Code */ int main() { /* Initialize list */ MyList *nums = newMyList(); /* Direct traversal of list elements */ add(nums, 1); add(nums, 3); add(nums, 2); add(nums, 5); add(nums, 4); printf("List nums = "); printArray(toArray(nums), size(nums)); printf("Capacity = %d, Length = %d\n", capacity(nums), size(nums)); /* Sort list */ insert(nums, 3, 6); printf("Insert number 6 at index 3, resulting in nums = "); printArray(toArray(nums), size(nums)); /* Remove element */ removeItem(nums, 3); printf("Remove element at index 3, resulting in nums = "); printArray(toArray(nums), size(nums)); /* Update element */ int num = get(nums, 1); printf("Access element at index 1, get num = %d\n", num); /* Add elements at the end */ set(nums, 1, 0); printf("Update element at index 1 to 0, resulting in nums = "); printArray(toArray(nums), size(nums)); /* Test capacity expansion mechanism */ for (int i = 0; i < 10; i++) { // At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism add(nums, i); } printf("List nums after expansion = "); printArray(toArray(nums), size(nums)); printf("Capacity = %d, Length = %d\n", capacity(nums), size(nums)); /* Free allocated memory */ delMyList(nums); return 0; } ================================================ FILE: en/codes/c/chapter_backtracking/CMakeLists.txt ================================================ add_executable(permutations_i permutations_i.c) add_executable(permutations_ii permutations_ii.c) add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.c) add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.c) add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.c) add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.c) add_executable(subset_sum_i_naive subset_sum_i_naive.c) add_executable(subset_sum_i subset_sum_i.c) add_executable(subset_sum_ii subset_sum_ii.c) add_executable(n_queens n_queens.c) ================================================ FILE: en/codes/c/chapter_backtracking/n_queens.c ================================================ /** * File : n_queens.c * Created Time: 2023-09-25 * Author : lucas (superrat6@gmail.com) */ #include "../utils/common.h" #define MAX_SIZE 100 /* Backtracking algorithm: N queens */ void backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE], bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) { // When all rows are placed, record the solution if (row == n) { res[*resSize] = (char **)malloc(sizeof(char *) * n); for (int i = 0; i < n; ++i) { res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1)); strcpy(res[*resSize][i], state[i]); } (*resSize)++; return; } // Traverse all columns for (int col = 0; col < n; col++) { // Calculate the main diagonal and anti-diagonal corresponding to this cell int diag1 = row - col + n - 1; int diag2 = row + col; // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Attempt: place the queen in this cell state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // Place the next row backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2); // Backtrack: restore this cell to an empty cell state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Solve N queens */ char ***nQueens(int n, int *returnSize) { char state[MAX_SIZE][MAX_SIZE]; // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { state[i][j] = '#'; } state[i][n] = '\0'; } bool cols[MAX_SIZE] = {false}; // Record whether there is a queen in the column bool diags1[2 * MAX_SIZE - 1] = {false}; // Record whether there is a queen on the main diagonal bool diags2[2 * MAX_SIZE - 1] = {false}; // Record whether there is a queen on the anti-diagonal char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE); *returnSize = 0; backtrack(0, n, state, res, returnSize, cols, diags1, diags2); return res; } /* Driver Code */ int main() { int n = 4; int returnSize; char ***res = nQueens(n, &returnSize); printf("Input board size is %d\n", n); printf("Total queen placement solutions: %d\n", returnSize); for (int i = 0; i < returnSize; ++i) { for (int j = 0; j < n; ++j) { printf("["); for (int k = 0; res[i][j][k] != '\0'; ++k) { printf("%c", res[i][j][k]); if (res[i][j][k + 1] != '\0') { printf(", "); } } printf("]\n"); } printf("---------------------\n"); } // Free memory for (int i = 0; i < returnSize; ++i) { for (int j = 0; j < n; ++j) { free(res[i][j]); } free(res[i]); } free(res); return 0; } ================================================ FILE: en/codes/c/chapter_backtracking/permutations_i.c ================================================ /** * File: permutations_i.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com), krahets (krahets@163.com) */ #include "../utils/common.h" // Assume at most 1000 permutations #define MAX_SIZE 1000 /* Backtracking algorithm: Permutations I */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // When the state length equals the number of elements, record the solution if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // Traverse all choices for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state[stateSize] = choice; // Proceed to the next round of selection backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // Backtrack: undo choice, restore to previous state selected[i] = false; } } } /* Permutations I */ int **permutationsI(int *nums, int numsSize, int *returnSize) { int *state = (int *)malloc(numsSize * sizeof(int)); bool *selected = (bool *)malloc(numsSize * sizeof(bool)); for (int i = 0; i < numsSize; i++) { selected[i] = false; } int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); *returnSize = 0; backtrack(state, 0, nums, numsSize, selected, res, returnSize); free(state); free(selected); return res; } /* Driver Code */ int main() { int nums[] = {1, 2, 3}; int numsSize = sizeof(nums) / sizeof(nums[0]); int returnSize; int **res = permutationsI(nums, numsSize, &returnSize); printf("Input array nums = "); printArray(nums, numsSize); printf("\nAll permutations res = \n"); for (int i = 0; i < returnSize; i++) { printArray(res[i], numsSize); } // Free memory for (int i = 0; i < returnSize; i++) { free(res[i]); } free(res); return 0; } ================================================ FILE: en/codes/c/chapter_backtracking/permutations_ii.c ================================================ /** * File: permutations_ii.c * Created Time: 2023-10-17 * Author: krahets (krahets@163.com) */ #include "../utils/common.h" // Assume at most 1000 permutations, max element value 1000 #define MAX_SIZE 1000 /* Backtracking algorithm: Permutations II */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // When the state length equals the number of elements, record the solution if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // Traverse all choices bool duplicated[MAX_SIZE] = {false}; for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated[choice]) { // Attempt: make choice, update state duplicated[choice] = true; // Record the selected element value selected[i] = true; state[stateSize] = choice; // Proceed to the next round of selection backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // Backtrack: undo choice, restore to previous state selected[i] = false; } } } /* Permutations II */ int **permutationsII(int *nums, int numsSize, int *returnSize) { int *state = (int *)malloc(numsSize * sizeof(int)); bool *selected = (bool *)malloc(numsSize * sizeof(bool)); for (int i = 0; i < numsSize; i++) { selected[i] = false; } int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); *returnSize = 0; backtrack(state, 0, nums, numsSize, selected, res, returnSize); free(state); free(selected); return res; } /* Driver Code */ int main() { int nums[] = {1, 1, 2}; int numsSize = sizeof(nums) / sizeof(nums[0]); int returnSize; int **res = permutationsII(nums, numsSize, &returnSize); printf("Input array nums = "); printArray(nums, numsSize); printf("\nAll permutations res = \n"); for (int i = 0; i < returnSize; i++) { printArray(res[i], numsSize); } // Free memory for (int i = 0; i < returnSize; i++) { free(res[i]); } free(res); return 0; } ================================================ FILE: en/codes/c/chapter_backtracking/preorder_traversal_i_compact.c ================================================ /** * File: preorder_traversal_i_compact.c * Created Time: 2023-05-10 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // Assume result length not exceeding 100 #define MAX_SIZE 100 TreeNode *res[MAX_SIZE]; int resSize = 0; /* Preorder traversal: Example 1 */ void preOrder(TreeNode *root) { if (root == NULL) { return; } if (root->val == 7) { // Record solution res[resSize++] = root; } preOrder(root->left); preOrder(root->right); } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\nInitialize binary tree\n"); printTree(root); // Preorder traversal preOrder(root); printf("\nOutput all nodes with value 7\n"); int *vals = malloc(resSize * sizeof(int)); for (int i = 0; i < resSize; i++) { vals[i] = res[i]->val; } printArray(vals, resSize); // Free memory freeMemoryTree(root); free(vals); return 0; } ================================================ FILE: en/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c ================================================ /** * File: preorder_traversal_ii_compact.c * Created Time: 2023-05-28 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // Assume path and result length not exceeding 100 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* Preorder traversal: Example 2 */ void preOrder(TreeNode *root) { if (root == NULL) { return; } // Attempt path[pathSize++] = root; if (root->val == 7) { // Record solution for (int i = 0; i < pathSize; ++i) { res[resSize][i] = path[i]; } resSize++; } preOrder(root->left); preOrder(root->right); // Backtrack pathSize--; } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\nInitialize binary tree\n"); printTree(root); // Preorder traversal preOrder(root); printf("\nOutput all paths from root to node 7\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // Free memory freeMemoryTree(root); return 0; } ================================================ FILE: en/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c ================================================ /** * File: preorder_traversal_iii_compact.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // Assume path and result length not exceeding 100 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* Preorder traversal: Example 3 */ void preOrder(TreeNode *root) { // Pruning if (root == NULL || root->val == 3) { return; } // Attempt path[pathSize++] = root; if (root->val == 7) { // Record solution for (int i = 0; i < pathSize; i++) { res[resSize][i] = path[i]; } resSize++; } preOrder(root->left); preOrder(root->right); // Backtrack pathSize--; } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\nInitialize binary tree\n"); printTree(root); // Preorder traversal preOrder(root); printf("\nOutput all paths from root to node 7, excluding nodes with value 3\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // Free memory freeMemoryTree(root); return 0; } ================================================ FILE: en/codes/c/chapter_backtracking/preorder_traversal_iii_template.c ================================================ /** * File: preorder_traversal_iii_template.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // Assume path and result length not exceeding 100 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* Check if the current state is a solution */ bool isSolution(void) { return pathSize > 0 && path[pathSize - 1]->val == 7; } /* Record solution */ void recordSolution(void) { for (int i = 0; i < pathSize; i++) { res[resSize][i] = path[i]; } resSize++; } /* Check if the choice is valid under the current state */ bool isValid(TreeNode *choice) { return choice != NULL && choice->val != 3; } /* Update state */ void makeChoice(TreeNode *choice) { path[pathSize++] = choice; } /* Restore state */ void undoChoice(void) { pathSize--; } /* Backtracking algorithm: Example 3 */ void backtrack(TreeNode *choices[2]) { // Check if it is a solution if (isSolution()) { // Record solution recordSolution(); } // Traverse all choices for (int i = 0; i < 2; i++) { TreeNode *choice = choices[i]; // Pruning: check if the choice is valid if (isValid(choice)) { // Attempt: make choice, update state makeChoice(choice); // Proceed to the next round of selection TreeNode *nextChoices[2] = {choice->left, choice->right}; backtrack(nextChoices); // Backtrack: undo choice, restore to previous state undoChoice(); } } } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\nInitialize binary tree\n"); printTree(root); // Backtracking algorithm TreeNode *choices[2] = {root, NULL}; backtrack(choices); printf("\nOutput all paths from root to node 7, excluding nodes with value 3\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // Free memory freeMemoryTree(root); return 0; } ================================================ FILE: en/codes/c/chapter_backtracking/subset_sum_i.c ================================================ /** * File: subset_sum_i.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // State (subset) int state[MAX_SIZE]; int stateSize = 0; // Result list (subset list) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* Backtracking algorithm: Subset sum I */ void backtrack(int target, int *choices, int choicesSize, int start) { // When the subset sum equals target, record the solution if (target == 0) { for (int i = 0; i < stateSize; ++i) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets for (int i = start; i < choicesSize; i++) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if (target - choices[i] < 0) { break; } // Attempt: make choice, update target, start state[stateSize] = choices[i]; stateSize++; // Proceed to the next round of selection backtrack(target - choices[i], choices, choicesSize, i); // Backtrack: undo choice, restore to previous state stateSize--; } } /* Comparison function */ int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); } /* Solve subset sum I */ void subsetSumI(int *nums, int numsSize, int target) { qsort(nums, numsSize, sizeof(int), cmp); // Sort nums int start = 0; // Start point for traversal backtrack(target, nums, numsSize, start); } /* Driver Code */ int main() { int nums[] = {3, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumI(nums, numsSize, target); printf("Input array nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("All subsets with sum equal to %d res = \n", target); for (int i = 0; i < resSize; ++i) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: en/codes/c/chapter_backtracking/subset_sum_i_naive.c ================================================ /** * File: subset_sum_i_naive.c * Created Time: 2023-07-28 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // State (subset) int state[MAX_SIZE]; int stateSize = 0; // Result list (subset list) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* Backtracking algorithm: Subset sum I */ void backtrack(int target, int total, int *choices, int choicesSize) { // When the subset sum equals target, record the solution if (total == target) { for (int i = 0; i < stateSize; i++) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // Traverse all choices for (int i = 0; i < choicesSize; i++) { // Pruning: if the subset sum exceeds target, skip this choice if (total + choices[i] > target) { continue; } // Attempt: make choice, update element sum total state[stateSize++] = choices[i]; // Proceed to the next round of selection backtrack(target, total + choices[i], choices, choicesSize); // Backtrack: undo choice, restore to previous state stateSize--; } } /* Solve subset sum I (including duplicate subsets) */ void subsetSumINaive(int *nums, int numsSize, int target) { resSize = 0; // Initialize solution count to 0 backtrack(target, 0, nums, numsSize); } /* Driver Code */ int main() { int nums[] = {3, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumINaive(nums, numsSize, target); printf("Input array nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("All subsets with sum equal to %d res = \n", target); for (int i = 0; i < resSize; i++) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: en/codes/c/chapter_backtracking/subset_sum_ii.c ================================================ /** * File: subset_sum_ii.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // State (subset) int state[MAX_SIZE]; int stateSize = 0; // Result list (subset list) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* Backtracking algorithm: Subset sum II */ void backtrack(int target, int *choices, int choicesSize, int start) { // When the subset sum equals target, record the solution if (target == 0) { for (int i = 0; i < stateSize; i++) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets // Pruning 3: start traversing from start to avoid repeatedly selecting the same element for (int i = start; i < choicesSize; i++) { // Pruning 1: Skip if subset sum exceeds target if (target - choices[i] < 0) { continue; } // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly if (i > start && choices[i] == choices[i - 1]) { continue; } // Attempt: make choice, update target, start state[stateSize] = choices[i]; stateSize++; // Proceed to the next round of selection backtrack(target - choices[i], choices, choicesSize, i + 1); // Backtrack: undo choice, restore to previous state stateSize--; } } /* Comparison function */ int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); } /* Solve subset sum II */ void subsetSumII(int *nums, int numsSize, int target) { // Sort nums qsort(nums, numsSize, sizeof(int), cmp); // Start backtracking backtrack(target, nums, numsSize, 0); } /* Driver Code */ int main() { int nums[] = {4, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumII(nums, numsSize, target); printf("Input array nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("All subsets with sum equal to %d res = \n", target); for (int i = 0; i < resSize; ++i) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: en/codes/c/chapter_computational_complexity/CMakeLists.txt ================================================ add_executable(iteration iteration.c) add_executable(recursion recursion.c) add_executable(time_complexity time_complexity.c) add_executable(worst_best_time_complexity worst_best_time_complexity.c) add_executable(space_complexity space_complexity.c) ================================================ FILE: en/codes/c/chapter_computational_complexity/iteration.c ================================================ /** * File: iteration.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com), MwumLi (mwumli@hotmail.com) */ #include "../utils/common.h" /* for loop */ int forLoop(int n) { int res = 0; // Sum 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } /* while loop */ int whileLoop(int n) { int res = 0; int i = 1; // Initialize condition variable // Sum 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Update condition variable } return res; } /* while loop (two updates) */ int whileLoopII(int n) { int res = 0; int i = 1; // Initialize condition variable // Sum 1, 4, 10, ... while (i <= n) { res += i; // Update condition variable i++; i *= 2; } return res; } /* Nested for loop */ char *nestedForLoop(int n) { // n * n is the number of points, "(i, j), " string max length is 6+10*2, plus extra space for null character \0 int size = n * n * 26 + 1; char *res = malloc(size * sizeof(char)); // Loop i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // Loop j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { char tmp[26]; snprintf(tmp, sizeof(tmp), "(%d, %d), ", i, j); strncat(res, tmp, size - strlen(res) - 1); } } return res; } /* Driver Code */ int main() { int n = 5; int res; res = forLoop(n); printf("\nFor loop sum result res = %d\n", res); res = whileLoop(n); printf("\nWhile loop sum result res = %d\n", res); res = whileLoopII(n); printf("\nWhile loop (two updates) sum result res = %d\n", res); char *resStr = nestedForLoop(n); printf("\nNested for loop traversal result %s\r\n", resStr); free(resStr); return 0; } ================================================ FILE: en/codes/c/chapter_computational_complexity/recursion.c ================================================ /** * File: recursion.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Recursion */ int recur(int n) { // Termination condition if (n == 1) return 1; // Recurse: recursive call int res = recur(n - 1); // Return: return result return n + res; } /* Simulate recursion using iteration */ int forLoopRecur(int n) { int stack[1000]; // Use a large array to simulate stack int top = -1; // Stack top index int res = 0; // Recurse: recursive call for (int i = n; i > 0; i--) { // Simulate "recurse" with "push" stack[1 + top++] = i; } // Return: return result while (top >= 0) { // Simulate "return" with "pop" res += stack[top--]; } // res = 1+2+3+...+n return res; } /* Tail recursion */ int tailRecur(int n, int res) { // Termination condition if (n == 0) return res; // Tail recursive call return tailRecur(n - 1, res + n); } /* Fibonacci sequence: recursion */ int fib(int n) { // Termination condition f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // Recursive call f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // Return result f(n) return res; } /* Driver Code */ int main() { int n = 5; int res; res = recur(n); printf("\nRecursion sum result res = %d\n", res); res = forLoopRecur(n); printf("\nUsing iteration to simulate recursion sum result res = %d\n", res); res = tailRecur(n, 0); printf("\nTail recursion sum result res = %d\n", res); res = fib(n); printf("\nThe %dth Fibonacci number is %d\n", n, res); return 0; } ================================================ FILE: en/codes/c/chapter_computational_complexity/space_complexity.c ================================================ /** * File: space_complexity.c * Created Time: 2023-04-15 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Function */ int func() { // Perform some operations return 0; } /* Constant order */ void constant(int n) { // Constants, variables, objects occupy O(1) space const int a = 0; int b = 0; int nums[1000]; ListNode *node = newListNode(0); free(node); // Variables in the loop occupy O(1) space for (int i = 0; i < n; i++) { int c = 0; } // Functions in the loop occupy O(1) space for (int i = 0; i < n; i++) { func(); } } /* Hash table */ typedef struct { int key; int val; UT_hash_handle hh; // Implemented using uthash.h } HashTable; /* Linear order */ void linear(int n) { // Array of length n uses O(n) space int *nums = malloc(sizeof(int) * n); free(nums); // A list of length n occupies O(n) space ListNode **nodes = malloc(sizeof(ListNode *) * n); for (int i = 0; i < n; i++) { nodes[i] = newListNode(i); } // Memory release for (int i = 0; i < n; i++) { free(nodes[i]); } free(nodes); // A hash table of length n occupies O(n) space HashTable *h = NULL; for (int i = 0; i < n; i++) { HashTable *tmp = malloc(sizeof(HashTable)); tmp->key = i; tmp->val = i; HASH_ADD_INT(h, key, tmp); } // Memory release HashTable *curr, *tmp; HASH_ITER(hh, h, curr, tmp) { HASH_DEL(h, curr); free(curr); } } /* Linear order (recursive implementation) */ void linearRecur(int n) { printf("Recursion n = %d\r\n", n); if (n == 1) return; linearRecur(n - 1); } /* Exponential order */ void quadratic(int n) { // 2D list uses O(n^2) space int **numMatrix = malloc(sizeof(int *) * n); for (int i = 0; i < n; i++) { int *tmp = malloc(sizeof(int) * n); for (int j = 0; j < n; j++) { tmp[j] = 0; } numMatrix[i] = tmp; } // Memory release for (int i = 0; i < n; i++) { free(numMatrix[i]); } free(numMatrix); } /* Quadratic order (recursive implementation) */ int quadraticRecur(int n) { if (n <= 0) return 0; int *nums = malloc(sizeof(int) * n); printf("In recursion n = %d, nums length = %d\r\n", n, n); int res = quadraticRecur(n - 1); free(nums); return res; } /* Driver Code */ TreeNode *buildTree(int n) { if (n == 0) return NULL; TreeNode *root = newTreeNode(0); root->left = buildTree(n - 1); root->right = buildTree(n - 1); return root; } /* Driver Code */ int main() { int n = 5; // Constant order constant(n); // Linear order linear(n); linearRecur(n); // Exponential order quadratic(n); quadraticRecur(n); // Exponential order TreeNode *root = buildTree(n); printTree(root); // Free memory freeMemoryTree(root); return 0; } ================================================ FILE: en/codes/c/chapter_computational_complexity/time_complexity.c ================================================ /** * File: time_complexity.c * Created Time: 2023-01-03 * Author: codingonion (coderonion@gmail.com) */ #include "../utils/common.h" /* Constant order */ int constant(int n) { int count = 0; int size = 100000; int i = 0; for (int i = 0; i < size; i++) { count++; } return count; } /* Linear order */ int linear(int n) { int count = 0; for (int i = 0; i < n; i++) { count++; } return count; } /* Linear order (traversing array) */ int arrayTraversal(int *nums, int n) { int count = 0; // Number of iterations is proportional to the array length for (int i = 0; i < n; i++) { count++; } return count; } /* Exponential order */ int quadratic(int n) { int count = 0; // Number of iterations is quadratically related to the data size n for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* Quadratic order (bubble sort) */ int bubbleSort(int *nums, int n) { int count = 0; // Counter // Outer loop: unsorted range is [0, i] for (int i = n - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // Element swap includes 3 unit operations } } } return count; } /* Exponential order (loop implementation) */ int exponential(int n) { int count = 0; int bas = 1; // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1) for (int i = 0; i < n; i++) { for (int j = 0; j < bas; j++) { count++; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* Exponential order (recursive implementation) */ int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* Logarithmic order (loop implementation) */ int logarithmic(int n) { int count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* Logarithmic order (recursive implementation) */ int logRecur(int n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* Linearithmic order */ 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; } /* Factorial order (recursive implementation) */ int factorialRecur(int n) { if (n == 0) return 1; int count = 0; for (int i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ int main(int argc, char *argv[]) { // You can modify n to run and observe the trend of the number of operations for various complexities int n = 8; printf("Input data size n = %d\n", n); int count = constant(n); printf("Constant-time operations count = %d\n", count); count = linear(n); printf("Linear-time operations count = %d\n", count); // Allocate heap memory (create 1D variable-length array: n elements of type int) int *nums = (int *)malloc(n * sizeof(int)); count = arrayTraversal(nums, n); printf("Linear-time (array traversal) operations count = %d\n", count); count = quadratic(n); printf("Quadratic-time operations count = %d\n", count); for (int i = 0; i < n; i++) { nums[i] = n - i; // [n,n-1,...,2,1] } count = bubbleSort(nums, n); printf("Quadratic-time (bubble sort) operations count = %d\n", count); count = exponential(n); printf("Exponential-time (iterative) operations count = %d\n", count); count = expRecur(n); printf("Exponential-time (recursive) operations count = %d\n", count); count = logarithmic(n); printf("Logarithmic-time (iterative) operations count = %d\n", count); count = logRecur(n); printf("Logarithmic-time (recursive) operations count = %d\n", count); count = linearLogRecur(n); printf("Linearithmic-time (recursive) operations count = %d\n", count); count = factorialRecur(n); printf("Factorial-time (recursive) operations count = %d\n", count); // Free heap memory if (nums != NULL) { free(nums); nums = NULL; } getchar(); return 0; } ================================================ FILE: en/codes/c/chapter_computational_complexity/worst_best_time_complexity.c ================================================ /** * File: worst_best_time_complexity.c * Created Time: 2023-01-03 * Author: codingonion (coderonion@gmail.com) */ #include "../utils/common.h" /* Generate an array with elements { 1, 2, ..., n }, order shuffled */ int *randomNumbers(int n) { // Allocate heap memory (create 1D variable-length array: n elements of type int) int *nums = (int *)malloc(n * sizeof(int)); // Generate array nums = { 1, 2, 3, ..., n } for (int i = 0; i < n; i++) { nums[i] = i + 1; } // Randomly shuffle array elements for (int i = n - 1; i > 0; i--) { int j = rand() % (i + 1); int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } return nums; } /* Find the index of number 1 in array nums */ int findOne(int *nums, int n) { for (int i = 0; i < n; i++) { // When element 1 is at the head of the array, best time complexity O(1) is achieved // When element 1 is at the tail of the array, worst time complexity O(n) is achieved if (nums[i] == 1) return i; } return -1; } /* Driver Code */ int main(int argc, char *argv[]) { // Initialize random seed srand((unsigned int)time(NULL)); for (int i = 0; i < 10; i++) { int n = 100; int *nums = randomNumbers(n); int index = findOne(nums, n); printf("\nArray [ 1, 2, ..., n ] after shuffling = "); printArray(nums, n); printf("Index of number 1 is %d\n", index); // Free heap memory if (nums != NULL) { free(nums); nums = NULL; } } return 0; } ================================================ FILE: en/codes/c/chapter_divide_and_conquer/CMakeLists.txt ================================================ add_executable(binary_search_recur binary_search_recur.c) add_executable(build_tree build_tree.c) add_executable(hanota hanota.c) ================================================ FILE: en/codes/c/chapter_divide_and_conquer/binary_search_recur.c ================================================ /** * File: binary_search_recur.c * Created Time: 2023-10-01 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Binary search: problem f(i, j) */ int dfs(int nums[], int target, int i, int j) { // If the interval is empty, it means there is no target element, return -1 if (i > j) { return -1; } // Calculate the midpoint index m int m = (i + j) / 2; if (nums[m] < target) { // Recursion subproblem f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // Recursion subproblem f(i, m-1) return dfs(nums, target, i, m - 1); } else { // Found the target element, return its index return m; } } /* Binary search */ int binarySearch(int nums[], int target, int numsSize) { int n = numsSize; // Solve the problem f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ int main() { int target = 6; int nums[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; int numsSize = sizeof(nums) / sizeof(nums[0]); // Binary search (closed interval on both sides) int index = binarySearch(nums, target, numsSize); printf("Index of target element 6 = %d\n", index); return 0; } ================================================ FILE: en/codes/c/chapter_divide_and_conquer/build_tree.c ================================================ /** * File : build_tree.c * Created Time: 2023-10-16 * Author : lucas (superrat6@gmail.com) */ #include "../utils/common.h" // Assume all elements less than 1000 #define MAX_SIZE 1000 /* Build binary tree: divide and conquer */ TreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) { // Terminate when the subtree interval is empty if (r - l < 0) return NULL; // Initialize the root node TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); root->val = preorder[i]; root->left = NULL; root->right = NULL; // Query m to divide the left and right subtrees int m = inorderMap[preorder[i]]; // Subproblem: build the left subtree root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size); // Subproblem: build the right subtree root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size); // Return the root node return root; } /* Build binary tree */ TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) { // Initialize hash map, storing the mapping from inorder elements to indices int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE); for (int i = 0; i < inorderSize; i++) { inorderMap[inorder[i]] = i; } TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize); free(inorderMap); return root; } /* Driver Code */ int main() { int preorder[] = {3, 9, 2, 1, 7}; int inorder[] = {9, 3, 1, 2, 7}; int preorderSize = sizeof(preorder) / sizeof(preorder[0]); int inorderSize = sizeof(inorder) / sizeof(inorder[0]); printf("Preorder traversal = "); printArray(preorder, preorderSize); printf("Inorder traversal = "); printArray(inorder, inorderSize); TreeNode *root = buildTree(preorder, preorderSize, inorder, inorderSize); printf("The constructed binary tree is:\n"); printTree(root); freeMemoryTree(root); return 0; } ================================================ FILE: en/codes/c/chapter_divide_and_conquer/hanota.c ================================================ /** * File: hanota.c * Created Time: 2023-10-01 * Author: Zuoxun (845242523@qq.com), lucas(superrat6@gmail.com) */ #include "../utils/common.h" // Assume at most 1000 permutations #define MAX_SIZE 1000 /* Move a disk */ void move(int *src, int *srcSize, int *tar, int *tarSize) { // Take out a disk from the top of src int pan = src[*srcSize - 1]; src[*srcSize - 1] = 0; (*srcSize)--; // Place the disk on top of tar tar[*tarSize] = pan; (*tarSize)++; } /* Solve the Tower of Hanoi problem f(i) */ void dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) { // If there is only one disk left in src, move it directly to tar if (i == 1) { move(src, srcSize, tar, tarSize); return; } // Subproblem f(i-1): move the top i-1 disks from src to buf using tar dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize); // Subproblem f(1): move the remaining disk from src to tar move(src, srcSize, tar, tarSize); // Subproblem f(i-1): move the top i-1 disks from buf to tar using src dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize); } /* Solve the Tower of Hanoi problem */ void solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) { // Move the top n disks from A to C using B dfs(*ASize, A, ASize, B, BSize, C, CSize); } /* Driver Code */ int main() { // The tail of the list is the top of the rod int a[] = {5, 4, 3, 2, 1}; int b[MAX_SIZE] = {0}; int c[MAX_SIZE] = {0}; int ASize = sizeof(a) / sizeof(a[0]); int BSize = 0; int CSize = 0; printf("\nInitial state:"); printf("\nA = "); printArray(a, ASize); printf("B = "); printArray(b, BSize); printf("C = "); printArray(c, CSize); solveHanota(a, &ASize, b, &BSize, c, &CSize); printf("\nAfter disk movement:"); printf("A = "); printArray(a, ASize); printf("B = "); printArray(b, BSize); printf("C = "); printArray(c, CSize); return 0; } ================================================ FILE: en/codes/c/chapter_dynamic_programming/CMakeLists.txt ================================================ add_executable(climbing_stairs_constraint_dp climbing_stairs_constraint_dp.c) add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.c) add_executable(min_path_sum min_path_sum.c) add_executable(knapsack knapsack.c) add_executable(unbounded_knapsack unbounded_knapsack.c) add_executable(coin_change coin_change.c) add_executable(coin_change_ii coin_change_ii.c) add_executable(edit_distance edit_distance.c) ================================================ FILE: en/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c ================================================ /** * File: climbing_stairs_backtrack.c * Created Time: 2023-09-22 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* Backtracking */ void backtrack(int *choices, int state, int n, int *res, int len) { // When climbing to the n-th stair, add 1 to the solution count if (state == n) res[0]++; // Traverse all choices for (int i = 0; i < len; i++) { int choice = choices[i]; // Pruning: not allowed to go beyond the n-th stair if (state + choice > n) continue; // Attempt: make choice, update state backtrack(choices, state + choice, n, res, len); // Backtrack } } /* Climbing stairs: Backtracking */ int climbingStairsBacktrack(int n) { int choices[2] = {1, 2}; // Can choose to climb up 1 or 2 stairs int state = 0; // Start climbing from the 0-th stair int *res = (int *)malloc(sizeof(int)); *res = 0; // Use res[0] to record the solution count int len = sizeof(choices) / sizeof(int); backtrack(choices, state, n, res, len); int result = *res; free(res); return result; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsBacktrack(n); printf("Climbing %d stairs has %d solutions\n", n, res); return 0; } ================================================ FILE: en/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c ================================================ /** * File: climbing_stairs_constraint_dp.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Climbing stairs with constraint: Dynamic programming */ int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // Initialize dp table, used to store solutions to subproblems int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(3, sizeof(int)); } // Initial state: preset the solution to the smallest subproblem dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } int res = dp[n][1] + dp[n][2]; // Free memory for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsConstraintDP(n); printf("Climbing %d stairs has %d solutions\n", n, res); return 0; } ================================================ FILE: en/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c ================================================ /** * File: climbing_stairs_dfs.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* Search */ int dfs(int i) { // Known dp[1] and dp[2], return them 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; } /* Climbing stairs: Search */ int climbingStairsDFS(int n) { return dfs(n); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFS(n); printf("Climbing %d stairs has %d solutions\n", n, res); return 0; } ================================================ FILE: en/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c ================================================ /** * File: climbing_stairs_dfs_mem.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* Memoization search */ int dfs(int i, int *mem) { // Known dp[1] and dp[2], return them if (i == 1 || i == 2) return i; // If record dp[i] exists, return it directly 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); // Record dp[i] mem[i] = count; return count; } /* Climbing stairs: Memoization search */ int climbingStairsDFSMem(int n) { // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record int *mem = (int *)malloc((n + 1) * sizeof(int)); for (int i = 0; i <= n; i++) { mem[i] = -1; } int result = dfs(n, mem); free(mem); return result; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFSMem(n); printf("Climbing %d stairs has %d solutions\n", n, res); return 0; } ================================================ FILE: en/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c ================================================ /** * File: climbing_stairs_dp.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* Climbing stairs: Dynamic programming */ int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; // Initialize dp table, used to store solutions to subproblems int *dp = (int *)malloc((n + 1) * sizeof(int)); // Initial state: preset the solution to the smallest subproblem dp[1] = 1; dp[2] = 2; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } int result = dp[n]; free(dp); return result; } /* Climbing stairs: Space-optimized dynamic programming */ int climbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDP(n); printf("Climbing %d stairs has %d solutions\n", n, res); res = climbingStairsDPComp(n); printf("Climbing %d stairs has %d solutions\n", n, res); return 0; } ================================================ FILE: en/codes/c/chapter_dynamic_programming/coin_change.c ================================================ /** * File: coin_change.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Find minimum value */ int myMin(int a, int b) { return a < b ? a : b; } /* Coin change: Dynamic programming */ int coinChangeDP(int coins[], int amt, int coinsSize) { int n = coinsSize; int MAX = amt + 1; // Initialize dp table int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(amt + 1, sizeof(int)); } // State transition: first row and first column for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // State transition: rest of the rows and columns for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // The smaller value between not selecting and selecting coin i dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } int res = dp[n][amt] != MAX ? dp[n][amt] : -1; // Free memory for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* Coin change: Space-optimized dynamic programming */ int coinChangeDPComp(int coins[], int amt, int coinsSize) { int n = coinsSize; int MAX = amt + 1; // Initialize dp table int *dp = malloc((amt + 1) * sizeof(int)); for (int j = 1; j <= amt; j++) { dp[j] = MAX; } dp[0] = 0; // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // The smaller value between not selecting and selecting coin i dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1); } } } int res = dp[amt] != MAX ? dp[amt] : -1; // Free memory free(dp); return res; } /* Driver code */ int main() { int coins[] = {1, 2, 5}; int coinsSize = sizeof(coins) / sizeof(coins[0]); int amt = 4; // Dynamic programming int res = coinChangeDP(coins, amt, coinsSize); printf("Minimum number of coins needed to make target amount is %d\n", res); // Space-optimized dynamic programming res = coinChangeDPComp(coins, amt, coinsSize); printf("Minimum number of coins needed to make target amount is %d\n", res); return 0; } ================================================ FILE: en/codes/c/chapter_dynamic_programming/coin_change_ii.c ================================================ /** * File: coin_change_ii.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Coin change II: Dynamic programming */ int coinChangeIIDP(int coins[], int amt, int coinsSize) { int n = coinsSize; // Initialize dp table int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(amt + 1, sizeof(int)); } // Initialize first column for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // Sum of the two options: not selecting and selecting coin i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } int res = dp[n][amt]; // Free memory for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* Coin change II: Space-optimized dynamic programming */ int coinChangeIIDPComp(int coins[], int amt, int coinsSize) { int n = coinsSize; // Initialize dp table int *dp = calloc(amt + 1, sizeof(int)); dp[0] = 1; // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // Sum of the two options: not selecting and selecting coin i dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } int res = dp[amt]; // Free memory free(dp); return res; } /* Driver code */ int main() { int coins[] = {1, 2, 5}; int coinsSize = sizeof(coins) / sizeof(coins[0]); int amt = 5; // Dynamic programming int res = coinChangeIIDP(coins, amt, coinsSize); printf("Number of coin combinations to make target amount is %d\n", res); // Space-optimized dynamic programming res = coinChangeIIDPComp(coins, amt, coinsSize); printf("Number of coin combinations to make target amount is %d\n", res); return 0; } ================================================ FILE: en/codes/c/chapter_dynamic_programming/edit_distance.c ================================================ /** * File: edit_distance.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Find minimum value */ int myMin(int a, int b) { return a < b ? a : b; } /* Edit distance: Brute-force search */ int editDistanceDFS(char *s, char *t, int i, int j) { // If both s and t are empty, return 0 if (i == 0 && j == 0) return 0; // If s is empty, return length of t if (i == 0) return j; // If t is empty, return length of s if (j == 0) return i; // If two characters are equal, skip both characters if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 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 minimum edit steps return myMin(myMin(insert, del), replace) + 1; } /* Edit distance: Memoization search */ int editDistanceDFSMem(char *s, char *t, int memCols, int **mem, int i, int j) { // If both s and t are empty, return 0 if (i == 0 && j == 0) return 0; // If s is empty, return length of t if (i == 0) return j; // If t is empty, return length of s if (j == 0) return i; // If there's a record, return it directly if (mem[i][j] != -1) return mem[i][j]; // If two characters are equal, skip both characters if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 int insert = editDistanceDFSMem(s, t, memCols, mem, i, j - 1); int del = editDistanceDFSMem(s, t, memCols, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); // Record and return minimum edit steps mem[i][j] = myMin(myMin(insert, del), replace) + 1; return mem[i][j]; } /* Edit distance: Dynamic programming */ int editDistanceDP(char *s, char *t, int n, int m) { int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(m + 1, sizeof(int)); } // State transition: first row and first column for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // State transition: rest of the rows and columns for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // If two characters are equal, skip both characters dp[i][j] = dp[i - 1][j - 1]; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } int res = dp[n][m]; // Free memory for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* Edit distance: Space-optimized dynamic programming */ int editDistanceDPComp(char *s, char *t, int n, int m) { int *dp = calloc(m + 1, sizeof(int)); // State transition: first row for (int j = 1; j <= m; j++) { dp[j] = j; } // State transition: rest of the rows for (int i = 1; i <= n; i++) { // State transition: first column int leftup = dp[0]; // Temporarily store dp[i-1, j-1] dp[0] = i; // State transition: rest of the columns for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // If two characters are equal, skip both characters dp[j] = leftup; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // Update for next round's dp[i-1, j-1] } } int res = dp[m]; // Free memory free(dp); return res; } /* Driver Code */ int main() { char *s = "bag"; char *t = "pack"; int n = strlen(s), m = strlen(t); // Brute-force search int res = editDistanceDFS(s, t, n, m); printf("Changing %s to %s requires a minimum of %d edits\n", s, t, res); // Memoization search int **mem = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { mem[i] = malloc((m + 1) * sizeof(int)); memset(mem[i], -1, (m + 1) * sizeof(int)); } res = editDistanceDFSMem(s, t, m + 1, mem, n, m); printf("Changing %s to %s requires a minimum of %d edits\n", s, t, res); // Free memory for (int i = 0; i <= n; i++) { free(mem[i]); } free(mem); // Dynamic programming res = editDistanceDP(s, t, n, m); printf("Changing %s to %s requires a minimum of %d edits\n", s, t, res); // Space-optimized dynamic programming res = editDistanceDPComp(s, t, n, m); printf("Changing %s to %s requires a minimum of %d edits\n", s, t, res); return 0; } ================================================ FILE: en/codes/c/chapter_dynamic_programming/knapsack.c ================================================ /** * File: knapsack.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Find maximum value */ int myMax(int a, int b) { return a > b ? a : b; } /* 0-1 knapsack: Brute-force search */ int knapsackDFS(int wgt[], int val[], int i, int c) { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i == 0 || c == 0) { return 0; } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // Calculate the maximum value of not putting in and putting in item i int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // Return the larger value of the two options return myMax(no, yes); } /* 0-1 knapsack: Memoization search */ int knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i == 0 || c == 0) { return 0; } // If there's a record, return it directly if (mem[i][c] != -1) { return mem[i][c]; } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); } // Calculate the maximum value of not putting in and putting in item i int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // Record and return the larger value of the two options mem[i][c] = myMax(no, yes); return mem[i][c]; } /* 0-1 knapsack: Dynamic programming */ int knapsackDP(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // Initialize dp table int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(cap + 1, sizeof(int)); } // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item i dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[n][cap]; // Free memory for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* 0-1 knapsack: Space-optimized dynamic programming */ int knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // Initialize dp table int *dp = calloc(cap + 1, sizeof(int)); // State transition for (int i = 1; i <= n; i++) { // Traverse in reverse order for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // The larger value between not selecting and selecting item i dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[cap]; // Free memory free(dp); return res; } /* Driver Code */ int main() { int wgt[] = {10, 20, 30, 40, 50}; int val[] = {50, 120, 150, 210, 240}; int cap = 50; int n = sizeof(wgt) / sizeof(wgt[0]); int wgtSize = n; // Brute-force search int res = knapsackDFS(wgt, val, n, cap); printf("Maximum item value not exceeding knapsack capacity is %d\n", res); // Memoization search int **mem = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { mem[i] = malloc((cap + 1) * sizeof(int)); memset(mem[i], -1, (cap + 1) * sizeof(int)); } res = knapsackDFSMem(wgt, val, cap + 1, mem, n, cap); printf("Maximum item value not exceeding knapsack capacity is %d\n", res); // Free memory for (int i = 0; i <= n; i++) { free(mem[i]); } free(mem); // Dynamic programming res = knapsackDP(wgt, val, cap, wgtSize); printf("Maximum item value not exceeding knapsack capacity is %d\n", res); // Space-optimized dynamic programming res = knapsackDPComp(wgt, val, cap, wgtSize); printf("Maximum item value not exceeding knapsack capacity is %d\n", res); return 0; } ================================================ FILE: en/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c ================================================ /** * File: min_cost_climbing_stairs_dp.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Find minimum value */ int myMin(int a, int b) { return a < b ? a : b; } /* Minimum cost climbing stairs: Dynamic programming */ int minCostClimbingStairsDP(int cost[], int costSize) { int n = costSize - 1; if (n == 1 || n == 2) return cost[n]; // Initialize dp table, used to store solutions to subproblems int *dp = calloc(n + 1, sizeof(int)); // Initial state: preset the solution to the smallest subproblem dp[1] = cost[1]; dp[2] = cost[2]; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i]; } int res = dp[n]; // Free memory free(dp); return res; } /* Minimum cost climbing stairs: Space-optimized dynamic programming */ int minCostClimbingStairsDPComp(int cost[], int costSize) { int n = costSize - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = myMin(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ int main() { int cost[] = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; int costSize = sizeof(cost) / sizeof(cost[0]); printf("Input stair cost list is:"); printArray(cost, costSize); int res = minCostClimbingStairsDP(cost, costSize); printf("Minimum cost to climb stairs is %d\n", res); res = minCostClimbingStairsDPComp(cost, costSize); printf("Minimum cost to climb stairs is %d\n", res); return 0; } ================================================ FILE: en/codes/c/chapter_dynamic_programming/min_path_sum.c ================================================ /** * File: min_path_sum.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" // Assume max matrix rows and columns is 100 #define MAX_SIZE 100 /* Find minimum value */ int myMin(int a, int b) { return a < b ? a : b; } /* Minimum path sum: Brute-force search */ int minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) { // If it's the top-left cell, terminate the search if (i == 0 && j == 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return INT_MAX; } // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1) int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); // Return the minimum path cost from top-left to (i, j) return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; } /* Minimum path sum: Memoization search */ int minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) { // If it's the top-left cell, terminate the search if (i == 0 && j == 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return INT_MAX; } // If there's a record, return it directly if (mem[i][j] != -1) { return mem[i][j]; } // Minimum path cost for left and upper cells int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // Record and return the minimum path cost from top-left to (i, j) mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; return mem[i][j]; } /* Minimum path sum: Dynamic programming */ int minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { // Initialize dp table int **dp = malloc(n * sizeof(int *)); for (int i = 0; i < n; i++) { dp[i] = calloc(m, sizeof(int)); } dp[0][0] = grid[0][0]; // State transition: first row for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // State transition: first column for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // State transition: rest of the rows and columns for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } int res = dp[n - 1][m - 1]; // Free memory for (int i = 0; i < n; i++) { free(dp[i]); } return res; } /* Minimum path sum: Space-optimized dynamic programming */ int minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { // Initialize dp table int *dp = calloc(m, sizeof(int)); // State transition: first row dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // State transition: rest of the rows for (int i = 1; i < n; i++) { // State transition: first column dp[0] = dp[0] + grid[i][0]; // State transition: rest of the columns for (int j = 1; j < m; j++) { dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j]; } } int res = dp[m - 1]; // Free memory free(dp); return res; } /* Driver Code */ int main() { int grid[MAX_SIZE][MAX_SIZE] = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; int n = 4, m = 4; // Matrix capacity is MAX_SIZE * MAX_SIZE, valid rows and columns are n * m // Brute-force search int res = minPathSumDFS(grid, n - 1, m - 1); printf("Minimum path sum from top-left to bottom-right is %d\n", res); // Memoization search int mem[MAX_SIZE][MAX_SIZE]; memset(mem, -1, sizeof(mem)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); printf("Minimum path sum from top-left to bottom-right is %d\n", res); // Dynamic programming res = minPathSumDP(grid, n, m); printf("Minimum path sum from top-left to bottom-right is %d\n", res); // Space-optimized dynamic programming res = minPathSumDPComp(grid, n, m); printf("Minimum path sum from top-left to bottom-right is %d\n", res); return 0; } ================================================ FILE: en/codes/c/chapter_dynamic_programming/unbounded_knapsack.c ================================================ /** * File: unbounded_knapsack.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Find maximum value */ int myMax(int a, int b) { return a > b ? a : b; } /* Unbounded knapsack: Dynamic programming */ int unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // Initialize dp table int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(cap + 1, sizeof(int)); } // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item i dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[n][cap]; // Free memory for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* Unbounded knapsack: Space-optimized dynamic programming */ int unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // Initialize dp table int *dp = calloc(cap + 1, sizeof(int)); // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[c] = dp[c]; } else { // The larger value between not selecting and selecting item i dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[cap]; // Free memory free(dp); return res; } /* Driver code */ int main() { int wgt[] = {1, 2, 3}; int val[] = {5, 11, 15}; int wgtSize = sizeof(wgt) / sizeof(wgt[0]); int cap = 4; // Dynamic programming int res = unboundedKnapsackDP(wgt, val, cap, wgtSize); printf("Maximum item value not exceeding knapsack capacity is %d\n", res); // Space-optimized dynamic programming res = unboundedKnapsackDPComp(wgt, val, cap, wgtSize); printf("Maximum item value not exceeding knapsack capacity is %d\n", res); return 0; } ================================================ FILE: en/codes/c/chapter_graph/CMakeLists.txt ================================================ add_executable(graph_adjacency_matrix graph_adjacency_matrix.c) add_executable(graph_adjacency_list_test graph_adjacency_list_test.c) add_executable(graph_bfs graph_bfs.c) add_executable(graph_dfs graph_dfs.c) ================================================ FILE: en/codes/c/chapter_graph/graph_adjacency_list.c ================================================ /** * File: graph_adjacency_list.c * Created Time: 2023-07-07 * Author: NI-SW (947743645@qq.com) */ #include "../utils/common.h" // Assume max node count is 100 #define MAX_SIZE 100 /* Node structure */ typedef struct AdjListNode { Vertex *vertex; // Vertex struct AdjListNode *next; // Successor node } AdjListNode; /* Undirected graph class based on adjacency list */ typedef struct { AdjListNode *heads[MAX_SIZE]; // Node array int size; // Node count } GraphAdjList; /* Constructor */ GraphAdjList *newGraphAdjList() { GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList)); if (!graph) { return NULL; } graph->size = 0; for (int i = 0; i < MAX_SIZE; i++) { graph->heads[i] = NULL; } return graph; } /* Destructor */ void delGraphAdjList(GraphAdjList *graph) { for (int i = 0; i < graph->size; i++) { AdjListNode *cur = graph->heads[i]; while (cur != NULL) { AdjListNode *next = cur->next; if (cur != graph->heads[i]) { free(cur); } cur = next; } free(graph->heads[i]->vertex); free(graph->heads[i]); } free(graph); } /* Find node corresponding to vertex */ AdjListNode *findNode(GraphAdjList *graph, Vertex *vet) { for (int i = 0; i < graph->size; i++) { if (graph->heads[i]->vertex == vet) { return graph->heads[i]; } } return NULL; } /* Add edge helper function */ void addEdgeHelper(AdjListNode *head, Vertex *vet) { AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode)); node->vertex = vet; // Head insertion node->next = head->next; head->next = node; } /* Add edge */ void addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { AdjListNode *head1 = findNode(graph, vet1); AdjListNode *head2 = findNode(graph, vet2); assert(head1 != NULL && head2 != NULL && head1 != head2); // Add edge vet1 - vet2 addEdgeHelper(head1, vet2); addEdgeHelper(head2, vet1); } /* Remove edge helper function */ void removeEdgeHelper(AdjListNode *head, Vertex *vet) { AdjListNode *pre = head; AdjListNode *cur = head->next; // Search for node corresponding to vet in list while (cur != NULL && cur->vertex != vet) { pre = cur; cur = cur->next; } if (cur == NULL) return; // Remove node corresponding to vet from list pre->next = cur->next; // Free memory free(cur); } /* Remove edge */ void removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { AdjListNode *head1 = findNode(graph, vet1); AdjListNode *head2 = findNode(graph, vet2); assert(head1 != NULL && head2 != NULL); // Remove edge vet1 - vet2 removeEdgeHelper(head1, head2->vertex); removeEdgeHelper(head2, head1->vertex); } /* Add vertex */ void addVertex(GraphAdjList *graph, Vertex *vet) { assert(graph != NULL && graph->size < MAX_SIZE); AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode)); head->vertex = vet; head->next = NULL; // Add a new linked list in the adjacency list graph->heads[graph->size++] = head; } /* Remove vertex */ void removeVertex(GraphAdjList *graph, Vertex *vet) { AdjListNode *node = findNode(graph, vet); assert(node != NULL); // Remove the linked list corresponding to vertex vet in the adjacency list AdjListNode *cur = node, *pre = NULL; while (cur) { pre = cur; cur = cur->next; free(pre); } // Traverse the linked lists of other vertices and remove all edges containing vet for (int i = 0; i < graph->size; i++) { cur = graph->heads[i]; pre = NULL; while (cur) { pre = cur; cur = cur->next; if (cur && cur->vertex == vet) { pre->next = cur->next; free(cur); break; } } } // Move vertices after this vertex forward to fill gap int i; for (i = 0; i < graph->size; i++) { if (graph->heads[i] == node) break; } for (int j = i; j < graph->size - 1; j++) { graph->heads[j] = graph->heads[j + 1]; } graph->size--; free(vet); } /* Print adjacency list */ void printGraph(const GraphAdjList *graph) { printf("Adjacency list =\n"); for (int i = 0; i < graph->size; ++i) { AdjListNode *node = graph->heads[i]; printf("%d: [", node->vertex->val); node = node->next; while (node) { printf("%d, ", node->vertex->val); node = node->next; } printf("]\n"); } } ================================================ FILE: en/codes/c/chapter_graph/graph_adjacency_list_test.c ================================================ /** * File: graph_adjacency_list_test.c * Created Time: 2023-07-11 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" /* Driver Code */ int main() { int vals[] = {1, 3, 2, 5, 4}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // Add all vertices and edges for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\nAfter initialization, graph is\n"); printGraph(graph); /* Add edge */ // Vertices 1, 3 are v[0], v[1] addEdge(graph, v[0], v[2]); printf("\nAfter adding edge 1-2, graph is\n"); printGraph(graph); /* Remove edge */ // Vertex 3 is v[1] removeEdge(graph, v[0], v[1]); printf("\nAfter deleting edge 1-3, graph is\n"); printGraph(graph); /* Add vertex */ Vertex *v5 = newVertex(6); addVertex(graph, v5); printf("\nAfter adding vertex 6, graph is\n"); printGraph(graph); /* Remove vertex */ // Vertex 3 is v[1] removeVertex(graph, v[1]); printf("\nAfter deleting vertex 3, graph is:\n"); printGraph(graph); // Free memory delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: en/codes/c/chapter_graph/graph_adjacency_matrix.c ================================================ /** * File: graph_adjacency_matrix.c * Created Time: 2023-07-06 * Author: NI-SW (947743645@qq.com) */ #include "../utils/common.h" // Assume max vertex count is 100 #define MAX_SIZE 100 /* Undirected graph structure based on adjacency matrix */ typedef struct { int vertices[MAX_SIZE]; int adjMat[MAX_SIZE][MAX_SIZE]; int size; } GraphAdjMat; /* Constructor */ GraphAdjMat *newGraphAdjMat() { GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat)); graph->size = 0; for (int i = 0; i < MAX_SIZE; i++) { for (int j = 0; j < MAX_SIZE; j++) { graph->adjMat[i][j] = 0; } } return graph; } /* Destructor */ void delGraphAdjMat(GraphAdjMat *graph) { free(graph); } /* Add vertex */ void addVertex(GraphAdjMat *graph, int val) { if (graph->size == MAX_SIZE) { fprintf(stderr, "Graph vertex count has reached maximum\n"); return; } // Add nth vertex and zero nth row and column int n = graph->size; graph->vertices[n] = val; for (int i = 0; i <= n; i++) { graph->adjMat[n][i] = graph->adjMat[i][n] = 0; } graph->size++; } /* Remove vertex */ void removeVertex(GraphAdjMat *graph, int index) { if (index < 0 || index >= graph->size) { fprintf(stderr, "Vertex index out of bounds\n"); return; } // Remove the vertex at index from the vertex list for (int i = index; i < graph->size - 1; i++) { graph->vertices[i] = graph->vertices[i + 1]; } // Remove the row at index from the adjacency matrix for (int i = index; i < graph->size - 1; i++) { for (int j = 0; j < graph->size; j++) { graph->adjMat[i][j] = graph->adjMat[i + 1][j]; } } // Remove the column at index from the adjacency matrix for (int i = 0; i < graph->size; i++) { for (int j = index; j < graph->size - 1; j++) { graph->adjMat[i][j] = graph->adjMat[i][j + 1]; } } graph->size--; } /* Add edge */ // Parameters i, j correspond to the vertices element indices void addEdge(GraphAdjMat *graph, int i, int j) { if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { fprintf(stderr, "Edge index out of bounds or equal\n"); return; } graph->adjMat[i][j] = 1; graph->adjMat[j][i] = 1; } /* Remove edge */ // Parameters i, j correspond to the vertices element indices void removeEdge(GraphAdjMat *graph, int i, int j) { if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { fprintf(stderr, "Edge index out of bounds or equal\n"); return; } graph->adjMat[i][j] = 0; graph->adjMat[j][i] = 0; } /* Print adjacency matrix */ void printGraphAdjMat(GraphAdjMat *graph) { printf("Vertex list = "); printArray(graph->vertices, graph->size); printf("Adjacency matrix =\n"); for (int i = 0; i < graph->size; i++) { printArray(graph->adjMat[i], graph->size); } } /* Driver Code */ int main() { // Add edge GraphAdjMat *graph = newGraphAdjMat(); int vertices[] = {1, 3, 2, 5, 4}; for (int i = 0; i < 5; i++) { addVertex(graph, vertices[i]); } int edges[][2] = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; for (int i = 0; i < 6; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\nAfter initialization, graph is\n"); printGraphAdjMat(graph); /* Add edge */ // Add vertex addEdge(graph, 0, 2); printf("\nAfter adding edge 1-2, graph is\n"); printGraphAdjMat(graph); /* Remove edge */ // Vertices 1, 3 have indices 0, 1 respectively removeEdge(graph, 0, 1); printf("\nAfter deleting edge 1-3, graph is\n"); printGraphAdjMat(graph); /* Add vertex */ addVertex(graph, 6); printf("\nAfter adding vertex 6, graph is\n"); printGraphAdjMat(graph); /* Remove vertex */ // Vertex 3 has index 1 removeVertex(graph, 1); printf("\nAfter deleting vertex 3, graph is\n"); printGraphAdjMat(graph); // Free memory delGraphAdjMat(graph); return 0; } ================================================ FILE: en/codes/c/chapter_graph/graph_bfs.c ================================================ /** * File: graph_bfs.c * Created Time: 2023-07-11 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" // Assume max node count is 100 #define MAX_SIZE 100 /* Node queue structure */ typedef struct { Vertex *vertices[MAX_SIZE]; int front, rear, size; } Queue; /* Constructor */ Queue *newQueue() { Queue *q = (Queue *)malloc(sizeof(Queue)); q->front = q->rear = q->size = 0; return q; } /* Check if the queue is empty */ int isEmpty(Queue *q) { return q->size == 0; } /* Enqueue operation */ void enqueue(Queue *q, Vertex *vet) { q->vertices[q->rear] = vet; q->rear = (q->rear + 1) % MAX_SIZE; q->size++; } /* Dequeue operation */ Vertex *dequeue(Queue *q) { Vertex *vet = q->vertices[q->front]; q->front = (q->front + 1) % MAX_SIZE; q->size--; return vet; } /* Check if vertex has been visited */ int isVisited(Vertex **visited, int size, Vertex *vet) { // Traverse to find node using O(n) time for (int i = 0; i < size; i++) { if (visited[i] == vet) return 1; } return 0; } /* Breadth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex void graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) { // Queue used to implement BFS Queue *queue = newQueue(); enqueue(queue, startVet); visited[(*visitedSize)++] = startVet; // Starting from vertex vet, loop until all vertices are visited while (!isEmpty(queue)) { Vertex *vet = dequeue(queue); // Dequeue the front vertex res[(*resSize)++] = vet; // Record visited vertex // Traverse all adjacent vertices of this vertex AdjListNode *node = findNode(graph, vet); while (node != NULL) { // Skip vertices that have been visited if (!isVisited(visited, *visitedSize, node->vertex)) { enqueue(queue, node->vertex); // Only enqueue unvisited vertices visited[(*visitedSize)++] = node->vertex; // Mark this vertex as visited } node = node->next; } } // Free memory free(queue); } /* Driver Code */ int main() { // Add edge int vals[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[1], v[4]}, {v[2], v[5]}, {v[3], v[4]}, {v[3], v[6]}, {v[4], v[5]}, {v[4], v[7]}, {v[5], v[8]}, {v[6], v[7]}, {v[7], v[8]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // Add all vertices and edges for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\nAfter initialization, graph is\n"); printGraph(graph); // Breadth-first traversal // Vertex traversal sequence Vertex *res[MAX_SIZE]; int resSize = 0; // Used to record visited vertices Vertex *visited[MAX_SIZE]; int visitedSize = 0; graphBFS(graph, v[0], res, &resSize, visited, &visitedSize); printf("\nBreadth-first traversal (BFS) vertex sequence is\n"); printArray(vetsToVals(res, resSize), resSize); // Free memory delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: en/codes/c/chapter_graph/graph_dfs.c ================================================ /** * File: graph_dfs.c * Created Time: 2023-07-13 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" // Assume max node count is 100 #define MAX_SIZE 100 /* Check if vertex has been visited */ int isVisited(Vertex **res, int size, Vertex *vet) { // Traverse to find node using O(n) time for (int i = 0; i < size; i++) { if (res[i] == vet) { return 1; } } return 0; } /* Depth-first traversal helper function */ void dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) { // Record visited vertex res[(*resSize)++] = vet; // Traverse all adjacent vertices of this vertex AdjListNode *node = findNode(graph, vet); while (node != NULL) { // Skip vertices that have been visited if (!isVisited(res, *resSize, node->vertex)) { // Recursively visit adjacent vertices dfs(graph, res, resSize, node->vertex); } node = node->next; } } /* Depth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex void graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) { dfs(graph, res, resSize, startVet); } /* Driver Code */ int main() { // Add edge int vals[] = {0, 1, 2, 3, 4, 5, 6}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // Add all vertices and edges for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\nAfter initialization, graph is\n"); printGraph(graph); // Depth-first traversal Vertex *res[MAX_SIZE]; int resSize = 0; graphDFS(graph, v[0], res, &resSize); printf("\nDepth-first traversal (DFS) vertex sequence is\n"); printArray(vetsToVals(res, resSize), resSize); // Free memory delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: en/codes/c/chapter_greedy/CMakeLists.txt ================================================ add_executable(coin_change_greedy coin_change_greedy.c) add_executable(fractional_knapsack fractional_knapsack.c) add_executable(max_capacity max_capacity.c) add_executable(max_product_cutting max_product_cutting.c) if (NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC") target_link_libraries(max_product_cutting m) endif() ================================================ FILE: en/codes/c/chapter_greedy/coin_change_greedy.c ================================================ /** * File: coin_change_greedy.c * Created Time: 2023-09-07 * Author: lwbaptx (lwbaptx@gmail.com) */ #include "../utils/common.h" /* Coin change: Greedy algorithm */ int coinChangeGreedy(int *coins, int size, int amt) { // Assume coins list is sorted int i = size - 1; int count = 0; // Loop to make greedy choices until no remaining amount while (amt > 0) { // Find the coin that is less than and closest to the remaining amount while (i > 0 && coins[i] > amt) { i--; } // Choose coins[i] amt -= coins[i]; count++; } // If no feasible solution is found, return -1 return amt == 0 ? count : -1; } /* Driver Code */ int main() { // Greedy algorithm: Can guarantee finding the global optimal solution int coins1[6] = {1, 5, 10, 20, 50, 100}; int amt = 186; int res = coinChangeGreedy(coins1, 6, amt); printf("\ncoins = "); printArray(coins1, 6); printf("amt = %d\n", amt); printf("Minimum number of coins needed to make %d is %d\n", amt, res); // Greedy algorithm: Cannot guarantee finding the global optimal solution int coins2[3] = {1, 20, 50}; amt = 60; res = coinChangeGreedy(coins2, 3, amt); printf("\ncoins = "); printArray(coins2, 3); printf("amt = %d\n", amt); printf("Minimum number of coins needed to make %d is %d\n", amt, res); printf("Actually minimum needed is 3, i.e., 20 + 20 + 20\n"); // Greedy algorithm: Cannot guarantee finding the global optimal solution int coins3[3] = {1, 49, 50}; amt = 98; res = coinChangeGreedy(coins3, 3, amt); printf("\ncoins = "); printArray(coins3, 3); printf("amt = %d\n", amt); printf("Minimum number of coins needed to make %d is %d\n", amt, res); printf("Actually minimum needed is 2, i.e., 49 + 49\n"); return 0; } ================================================ FILE: en/codes/c/chapter_greedy/fractional_knapsack.c ================================================ /** * File: fractional_knapsack.c * Created Time: 2023-09-14 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* Item */ typedef struct { int w; // Item weight int v; // Item value } Item; /* Sort by value density */ int sortByValueDensity(const void *a, const void *b) { Item *t1 = (Item *)a; Item *t2 = (Item *)b; return (float)(t1->v) / t1->w < (float)(t2->v) / t2->w; } /* Fractional knapsack: Greedy algorithm */ float fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) { // Create item list with two attributes: weight, value Item *items = malloc(sizeof(Item) * itemCount); for (int i = 0; i < itemCount; i++) { items[i] = (Item){.w = wgt[i], .v = val[i]}; } // Sort by unit value item.v / item.w from high to low qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity); // Loop for greedy selection float res = 0.0; for (int i = 0; i < itemCount; i++) { if (items[i].w <= cap) { // If remaining capacity is sufficient, put the entire current item into the knapsack res += items[i].v; cap -= items[i].w; } else { // If remaining capacity is insufficient, put part of the current item into the knapsack res += (float)cap / items[i].w * items[i].v; cap = 0; break; } } free(items); return res; } /* Driver Code */ int main(void) { int wgt[] = {10, 20, 30, 40, 50}; int val[] = {50, 120, 150, 210, 240}; int capacity = 50; // Greedy algorithm float res = fractionalKnapsack(wgt, val, sizeof(wgt) / sizeof(int), capacity); printf("Maximum item value not exceeding knapsack capacity is %0.2f\n", res); return 0; } ================================================ FILE: en/codes/c/chapter_greedy/max_capacity.c ================================================ /** * File: max_capacity.c * Created Time: 2023-09-15 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* Find minimum value */ int myMin(int a, int b) { return a < b ? a : b; } /* Find maximum value */ int myMax(int a, int b) { return a > b ? a : b; } /* Max capacity: Greedy algorithm */ int maxCapacity(int ht[], int htLength) { // Initialize i, j to be at both ends of the array int i = 0; int j = htLength - 1; // Initial max capacity is 0 int res = 0; // Loop for greedy selection until the two boards meet while (i < j) { // Update max capacity int capacity = myMin(ht[i], ht[j]) * (j - i); res = myMax(res, capacity); // Move the shorter board inward if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ int main(void) { int ht[] = {3, 8, 5, 2, 7, 7, 3, 4}; // Greedy algorithm int res = maxCapacity(ht, sizeof(ht) / sizeof(int)); printf("Maximum capacity is %d\n", res); return 0; } ================================================ FILE: en/codes/c/chapter_greedy/max_product_cutting.c ================================================ /** * File: max_product_cutting.c * Created Time: 2023-09-15 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* Max product cutting: Greedy algorithm */ int maxProductCutting(int n) { // When n <= 3, must cut out a 1 if (n <= 3) { return 1 * (n - 1); } // Greedily cut out 3, a is the number of 3s, b is the remainder int a = n / 3; int b = n % 3; if (b == 1) { // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2 return pow(3, a - 1) * 2 * 2; } if (b == 2) { // When the remainder is 2, do nothing return pow(3, a) * 2; } // When the remainder is 0, do nothing return pow(3, a); } /* Driver Code */ int main(void) { int n = 58; // Greedy algorithm int res = maxProductCutting(n); printf("Maximum cutting product is %d\n", res); return 0; } ================================================ FILE: en/codes/c/chapter_hashing/CMakeLists.txt ================================================ add_executable(array_hash_map array_hash_map.c) add_executable(hash_map_chaining hash_map_chaining.c) add_executable(hash_map_open_addressing hash_map_open_addressing.c) add_executable(simple_hash simple_hash.c) ================================================ FILE: en/codes/c/chapter_hashing/array_hash_map.c ================================================ /** * File: array_hash_map.c * Created Time: 2023-03-18 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* Default hash table size */ #define MAX_SIZE 100 /* Key-value pair int->string */ typedef struct { int key; char *val; } Pair; /* Collection of key-value pairs */ typedef struct { void *set; int len; } MapSet; /* Hash table based on array implementation */ typedef struct { Pair *buckets[MAX_SIZE]; } ArrayHashMap; /* Constructor */ ArrayHashMap *newArrayHashMap() { ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap)); for (int i=0; i < MAX_SIZE; i++) { hmap->buckets[i] = NULL; } return hmap; } /* Destructor */ void delArrayHashMap(ArrayHashMap *hmap) { for (int i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { free(hmap->buckets[i]->val); free(hmap->buckets[i]); } } free(hmap); } /* Hash function */ int hashFunc(int key) { int index = key % MAX_SIZE; return index; } /* Query operation */ const char *get(const ArrayHashMap *hmap, const int key) { int index = hashFunc(key); const Pair *Pair = hmap->buckets[index]; if (Pair == NULL) return NULL; return Pair->val; } /* Add operation */ void put(ArrayHashMap *hmap, const int key, const char *val) { Pair *Pair = malloc(sizeof(Pair)); Pair->key = key; Pair->val = malloc(strlen(val) + 1); strcpy(Pair->val, val); int index = hashFunc(key); hmap->buckets[index] = Pair; } /* Remove operation */ void removeItem(ArrayHashMap *hmap, const int key) { int index = hashFunc(key); free(hmap->buckets[index]->val); free(hmap->buckets[index]); hmap->buckets[index] = NULL; } /* Get all key-value pairs */ void pairSet(ArrayHashMap *hmap, MapSet *set) { Pair *entries; int i = 0, index = 0; int total = 0; /* Count valid key-value pairs */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } entries = malloc(sizeof(Pair) * total); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { entries[index].key = hmap->buckets[i]->key; entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1); strcpy(entries[index].val, hmap->buckets[i]->val); index++; } } set->set = entries; set->len = total; } /* Get all keys */ void keySet(ArrayHashMap *hmap, MapSet *set) { int *keys; int i = 0, index = 0; int total = 0; /* Count valid key-value pairs */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } keys = malloc(total * sizeof(int)); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { keys[index] = hmap->buckets[i]->key; index++; } } set->set = keys; set->len = total; } /* Get all values */ void valueSet(ArrayHashMap *hmap, MapSet *set) { char **vals; int i = 0, index = 0; int total = 0; /* Count valid key-value pairs */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } vals = malloc(total * sizeof(char *)); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { vals[index] = hmap->buckets[i]->val; index++; } } set->set = vals; set->len = total; } /* Print hash table */ void print(ArrayHashMap *hmap) { int i; MapSet set; pairSet(hmap, &set); Pair *entries = (Pair *)set.set; for (i = 0; i < set.len; i++) { printf("%d -> %s\n", entries[i].key, entries[i].val); } free(set.set); } /* Driver Code */ int main() { /* Initialize hash table */ ArrayHashMap *hmap = newArrayHashMap(); /* Add operation */ // Add key-value pair (key, value) to the hash table put(hmap, 12836, "Xiao Ha"); put(hmap, 15937, "Xiao Luo"); put(hmap, 16750, "Xiao Suan"); put(hmap, 13276, "Xiao Fa"); put(hmap, 10583, "Xiao Ya"); printf("\nAfter addition, hash table is\nKey -> Value\n"); print(hmap); /* Query operation */ // Input key into hash table to get value const char *name = get(hmap, 15937); printf("\nInput student ID 15937, found name %s\n", name); /* Remove operation */ // Remove key-value pair (key, value) from hash table removeItem(hmap, 10583); printf("\nAfter deleting 10583, hash table is\nKey -> Value\n"); print(hmap); /* Traverse hash table */ int i; printf("\nTraverse key-value pairs Key->Value\n"); print(hmap); MapSet set; keySet(hmap, &set); int *keys = (int *)set.set; printf("\nTraverse keys only Key\n"); for (i = 0; i < set.len; i++) { printf("%d\n", keys[i]); } free(set.set); valueSet(hmap, &set); char **vals = (char **)set.set; printf("\nTraverse values only Value\n"); for (i = 0; i < set.len; i++) { printf("%s\n", vals[i]); } free(set.set); delArrayHashMap(hmap); return 0; } ================================================ FILE: en/codes/c/chapter_hashing/hash_map_chaining.c ================================================ /** * File: hash_map_chaining.c * Created Time: 2023-10-13 * Author: SenMing (1206575349@qq.com), krahets (krahets@163.com) */ #include #include #include // Assume max val length is 100 #define MAX_SIZE 100 /* Key-value pair */ typedef struct { int key; char val[MAX_SIZE]; } Pair; /* Linked list node */ typedef struct Node { Pair *pair; struct Node *next; } Node; /* Hash table with separate chaining */ typedef struct { int size; // Number of key-value pairs int capacity; // Hash table capacity double loadThres; // Load factor threshold for triggering expansion int extendRatio; // Expansion multiplier Node **buckets; // Bucket array } HashMapChaining; /* Constructor */ HashMapChaining *newHashMapChaining() { HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining)); hashMap->size = 0; hashMap->capacity = 4; hashMap->loadThres = 2.0 / 3.0; hashMap->extendRatio = 2; hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); for (int i = 0; i < hashMap->capacity; i++) { hashMap->buckets[i] = NULL; } return hashMap; } /* Destructor */ void delHashMapChaining(HashMapChaining *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Node *cur = hashMap->buckets[i]; while (cur) { Node *tmp = cur; cur = cur->next; free(tmp->pair); free(tmp); } } free(hashMap->buckets); free(hashMap); } /* Hash function */ int hashFunc(HashMapChaining *hashMap, int key) { return key % hashMap->capacity; } /* Load factor */ double loadFactor(HashMapChaining *hashMap) { return (double)hashMap->size / (double)hashMap->capacity; } /* Query operation */ char *get(HashMapChaining *hashMap, int key) { int index = hashFunc(hashMap, key); // Traverse bucket, if key is found, return corresponding val Node *cur = hashMap->buckets[index]; while (cur) { if (cur->pair->key == key) { return cur->pair->val; } cur = cur->next; } return ""; // Return empty string if key not found } /* Add operation */ void put(HashMapChaining *hashMap, int key, const char *val); /* Expand hash table */ void extend(HashMapChaining *hashMap) { // Temporarily store the original hash table int oldCapacity = hashMap->capacity; Node **oldBuckets = hashMap->buckets; // Initialize expanded new hash table hashMap->capacity *= hashMap->extendRatio; hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); for (int i = 0; i < hashMap->capacity; i++) { hashMap->buckets[i] = NULL; } hashMap->size = 0; // Move key-value pairs from original hash table to new hash table for (int i = 0; i < oldCapacity; i++) { Node *cur = oldBuckets[i]; while (cur) { put(hashMap, cur->pair->key, cur->pair->val); Node *temp = cur; cur = cur->next; // Free memory free(temp->pair); free(temp); } } free(oldBuckets); } /* Add operation */ void put(HashMapChaining *hashMap, int key, const char *val) { // When load factor exceeds threshold, perform expansion if (loadFactor(hashMap) > hashMap->loadThres) { extend(hashMap); } int index = hashFunc(hashMap, key); // Traverse bucket, if specified key is encountered, update corresponding val and return Node *cur = hashMap->buckets[index]; while (cur) { if (cur->pair->key == key) { strcpy(cur->pair->val, val); // If specified key is found, update corresponding val and return return; } cur = cur->next; } // If key not found, add key-value pair to list head Pair *newPair = (Pair *)malloc(sizeof(Pair)); newPair->key = key; strcpy(newPair->val, val); Node *newNode = (Node *)malloc(sizeof(Node)); newNode->pair = newPair; newNode->next = hashMap->buckets[index]; hashMap->buckets[index] = newNode; hashMap->size++; } /* Remove operation */ void removeItem(HashMapChaining *hashMap, int key) { int index = hashFunc(hashMap, key); Node *cur = hashMap->buckets[index]; Node *pre = NULL; while (cur) { if (cur->pair->key == key) { // Remove key-value pair from it if (pre) { pre->next = cur->next; } else { hashMap->buckets[index] = cur->next; } // Free memory free(cur->pair); free(cur); hashMap->size--; return; } pre = cur; cur = cur->next; } } /* Print hash table */ void print(HashMapChaining *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Node *cur = hashMap->buckets[i]; printf("["); while (cur) { printf("%d -> %s, ", cur->pair->key, cur->pair->val); cur = cur->next; } printf("]\n"); } } /* Driver Code */ int main() { /* Initialize hash table */ HashMapChaining *hashMap = newHashMapChaining(); /* Add operation */ // Add key-value pair (key, value) to the hash table put(hashMap, 12836, "Xiao Ha"); put(hashMap, 15937, "Xiao Luo"); put(hashMap, 16750, "Xiao Suan"); put(hashMap, 13276, "Xiao Fa"); put(hashMap, 10583, "Xiao Ya"); printf("\nAfter addition, hash table is\nKey -> Value\n"); print(hashMap); /* Query operation */ // Input key into hash table to get value char *name = get(hashMap, 13276); printf("\nInput student ID 13276, found name %s\n", name); /* Remove operation */ // Remove key-value pair (key, value) from hash table removeItem(hashMap, 12836); printf("\nAfter deleting student ID 12836, hash table is\nKey -> Value\n"); print(hashMap); /* Free hash table space */ delHashMapChaining(hashMap); return 0; } ================================================ FILE: en/codes/c/chapter_hashing/hash_map_open_addressing.c ================================================ /** * File: hash_map_open_addressing.c * Created Time: 2023-10-6 * Author: lclc6 (w1929522410@163.com) */ #include "../utils/common.h" /* Hash table with open addressing */ typedef struct { int key; char *val; } Pair; /* Hash table with open addressing */ typedef struct { int size; // Number of key-value pairs int capacity; // Hash table capacity double loadThres; // Load factor threshold for triggering expansion int extendRatio; // Expansion multiplier Pair **buckets; // Bucket array Pair *TOMBSTONE; // Removal marker } HashMapOpenAddressing; // Function declaration void extend(HashMapOpenAddressing *hashMap); /* Constructor */ HashMapOpenAddressing *newHashMapOpenAddressing() { HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing)); hashMap->size = 0; hashMap->capacity = 4; hashMap->loadThres = 2.0 / 3.0; hashMap->extendRatio = 2; hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair)); hashMap->TOMBSTONE->key = -1; hashMap->TOMBSTONE->val = "-1"; return hashMap; } /* Destructor */ void delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Pair *pair = hashMap->buckets[i]; if (pair != NULL && pair != hashMap->TOMBSTONE) { free(pair->val); free(pair); } } free(hashMap->buckets); free(hashMap->TOMBSTONE); free(hashMap); } /* Hash function */ int hashFunc(HashMapOpenAddressing *hashMap, int key) { return key % hashMap->capacity; } /* Load factor */ double loadFactor(HashMapOpenAddressing *hashMap) { return (double)hashMap->size / (double)hashMap->capacity; } /* Search for bucket index corresponding to key */ int findBucket(HashMapOpenAddressing *hashMap, int key) { int index = hashFunc(hashMap, key); int firstTombstone = -1; // Linear probing, break when encountering an empty bucket while (hashMap->buckets[index] != NULL) { // If key is encountered, return the corresponding bucket index if (hashMap->buckets[index]->key == key) { // If a removal marker was encountered before, move the key-value pair to that index if (firstTombstone != -1) { hashMap->buckets[firstTombstone] = hashMap->buckets[index]; hashMap->buckets[index] = hashMap->TOMBSTONE; return firstTombstone; // Return the moved bucket index } return index; // Return bucket index } // Record the first removal marker encountered if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) { firstTombstone = index; } // Calculate bucket index, wrap around to the head if past the tail index = (index + 1) % hashMap->capacity; } // If key does not exist, return the index for insertion return firstTombstone == -1 ? index : firstTombstone; } /* Query operation */ char *get(HashMapOpenAddressing *hashMap, int key) { // Search for bucket index corresponding to key int index = findBucket(hashMap, key); // If key-value pair is found, return corresponding val if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { return hashMap->buckets[index]->val; } // Return empty string if key-value pair does not exist return ""; } /* Add operation */ void put(HashMapOpenAddressing *hashMap, int key, char *val) { // When load factor exceeds threshold, perform expansion if (loadFactor(hashMap) > hashMap->loadThres) { extend(hashMap); } // Search for bucket index corresponding to key int index = findBucket(hashMap, key); // If key-value pair is found, overwrite val and return if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { free(hashMap->buckets[index]->val); hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1)); strcpy(hashMap->buckets[index]->val, val); hashMap->buckets[index]->val[strlen(val)] = '\0'; return; } // If key-value pair does not exist, add the key-value pair Pair *pair = (Pair *)malloc(sizeof(Pair)); pair->key = key; pair->val = (char *)malloc(sizeof(strlen(val) + 1)); strcpy(pair->val, val); pair->val[strlen(val)] = '\0'; hashMap->buckets[index] = pair; hashMap->size++; } /* Remove operation */ void removeItem(HashMapOpenAddressing *hashMap, int key) { // Search for bucket index corresponding to key int index = findBucket(hashMap, key); // If key-value pair is found, overwrite it with removal marker if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { Pair *pair = hashMap->buckets[index]; free(pair->val); free(pair); hashMap->buckets[index] = hashMap->TOMBSTONE; hashMap->size--; } } /* Expand hash table */ void extend(HashMapOpenAddressing *hashMap) { // Temporarily store the original hash table Pair **bucketsTmp = hashMap->buckets; int oldCapacity = hashMap->capacity; // Initialize expanded new hash table hashMap->capacity *= hashMap->extendRatio; hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); hashMap->size = 0; // Move key-value pairs from original hash table to new hash table for (int i = 0; i < oldCapacity; i++) { Pair *pair = bucketsTmp[i]; if (pair != NULL && pair != hashMap->TOMBSTONE) { put(hashMap, pair->key, pair->val); free(pair->val); free(pair); } } free(bucketsTmp); } /* Print hash table */ void print(HashMapOpenAddressing *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Pair *pair = hashMap->buckets[i]; if (pair == NULL) { printf("NULL\n"); } else if (pair == hashMap->TOMBSTONE) { printf("TOMBSTONE\n"); } else { printf("%d -> %s\n", pair->key, pair->val); } } } /* Driver Code */ int main() { // Initialize hash table HashMapOpenAddressing *hashmap = newHashMapOpenAddressing(); // Add operation // Add key-value pair (key, val) to the hash table put(hashmap, 12836, "Xiao Ha"); put(hashmap, 15937, "Xiao Luo"); put(hashmap, 16750, "Xiao Suan"); put(hashmap, 13276, "Xiao Fa"); put(hashmap, 10583, "Xiao Ya"); printf("\nAfter addition, hash table is\nKey -> Value\n"); print(hashmap); // Query operation // Input key into hash table to get value val char *name = get(hashmap, 13276); printf("\nInput student ID 13276, found name %s\n", name); // Remove operation // Remove key-value pair (key, val) from hash table removeItem(hashmap, 16750); printf("\nAfter deleting 16750, hash table is\nKey -> Value\n"); print(hashmap); // Destroy hash table delHashMapOpenAddressing(hashmap); return 0; } ================================================ FILE: en/codes/c/chapter_hashing/simple_hash.c ================================================ /** * File: simple_hash.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Additive hash */ int addHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = (hash + (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* Multiplicative hash */ int mulHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = (31 * hash + (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* XOR hash */ int xorHash(char *key) { int hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash ^= (unsigned char)key[i]; } return hash & MODULUS; } /* Rotational hash */ int rotHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* Driver Code */ int main() { char *key = "Hello Algo"; int hash = addHash(key); printf("Additive hash value is %d\n", hash); hash = mulHash(key); printf("Multiplicative hash value is %d\n", hash); hash = xorHash(key); printf("XOR hash value is %d\n", hash); hash = rotHash(key); printf("Rotational hash value is %d\n", hash); return 0; } ================================================ FILE: en/codes/c/chapter_heap/CMakeLists.txt ================================================ add_executable(my_heap_test my_heap_test.c) add_executable(top_k top_k.c) ================================================ FILE: en/codes/c/chapter_heap/my_heap.c ================================================ /** * File: my_heap.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 5000 /* Max heap */ typedef struct { // size represents the actual number of elements int size; // Use pre-allocated array to avoid expansion int data[MAX_SIZE]; } MaxHeap; // Function declaration void siftDown(MaxHeap *maxHeap, int i); void siftUp(MaxHeap *maxHeap, int i); int parent(MaxHeap *maxHeap, int i); /* Constructor, build heap from slice */ MaxHeap *newMaxHeap(int nums[], int size) { // Push all elements to heap MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap)); maxHeap->size = size; memcpy(maxHeap->data, nums, size * sizeof(int)); for (int i = parent(maxHeap, size - 1); i >= 0; i--) { // Heapify all nodes except leaf nodes siftDown(maxHeap, i); } return maxHeap; } /* Destructor */ void delMaxHeap(MaxHeap *maxHeap) { // Free memory free(maxHeap); } /* Get index of left child node */ int left(MaxHeap *maxHeap, int i) { return 2 * i + 1; } /* Get index of right child node */ int right(MaxHeap *maxHeap, int i) { return 2 * i + 2; } /* Get index of parent node */ int parent(MaxHeap *maxHeap, int i) { return (i - 1) / 2; // Round down } /* Swap elements */ void swap(MaxHeap *maxHeap, int i, int j) { int temp = maxHeap->data[i]; maxHeap->data[i] = maxHeap->data[j]; maxHeap->data[j] = temp; } /* Get heap size */ int size(MaxHeap *maxHeap) { return maxHeap->size; } /* Check if heap is empty */ int isEmpty(MaxHeap *maxHeap) { return maxHeap->size == 0; } /* Access top element */ int peek(MaxHeap *maxHeap) { return maxHeap->data[0]; } /* Element enters heap */ void push(MaxHeap *maxHeap, int val) { // By default, should not add this many nodes if (maxHeap->size == MAX_SIZE) { printf("heap is full!"); return; } // Add node maxHeap->data[maxHeap->size] = val; maxHeap->size++; // Heapify from bottom to top siftUp(maxHeap, maxHeap->size - 1); } /* Element exits heap */ int pop(MaxHeap *maxHeap) { // Handle empty case if (isEmpty(maxHeap)) { printf("heap is empty!"); return INT_MAX; } // Delete node swap(maxHeap, 0, size(maxHeap) - 1); // Remove node int val = maxHeap->data[maxHeap->size - 1]; maxHeap->size--; // Return top element siftDown(maxHeap, 0); // Return heap top element return val; } /* Starting from node i, heapify from top to bottom */ void siftDown(MaxHeap *maxHeap, int i) { while (true) { // Find node with maximum value among nodes i, l, r, denoted as max int l = left(maxHeap, i); int r = right(maxHeap, i); int max = i; if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) { max = l; } if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) { max = r; } // Swap two nodes if (max == i) { break; } // Swap two nodes swap(maxHeap, i, max); // Loop downwards heapification i = max; } } /* Starting from node i, heapify from bottom to top */ void siftUp(MaxHeap *maxHeap, int i) { while (true) { // Get parent node of node i int p = parent(maxHeap, i); // When "crossing root node" or "node needs no repair", end heapify if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) { break; } // Swap two nodes swap(maxHeap, i, p); // Loop upward heapify i = p; } } ================================================ FILE: en/codes/c/chapter_heap/my_heap_test.c ================================================ /** * File: my_heap_test.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "my_heap.c" /* Driver Code */ int main() { /* Initialize heap */ // Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap int nums[] = {9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; MaxHeap *maxHeap = newMaxHeap(nums, sizeof(nums) / sizeof(int)); printf("After input array and building heap\n"); printHeap(maxHeap->data, maxHeap->size); /* Check if heap is empty */ printf("\nHeap top element is %d\n", peek(maxHeap)); /* Element enters heap */ push(maxHeap, 7); printf("\nAfter element 7 pushes to heap\n"); printHeap(maxHeap->data, maxHeap->size); /* Time complexity is O(n), not O(nlogn) */ int top = pop(maxHeap); printf("\nAfter heap top element %d exits heap\n", top); printHeap(maxHeap->data, maxHeap->size); /* Get heap size */ printf("\nHeap element count is %d\n", size(maxHeap)); /* Check if heap is empty */ printf("\nIs heap empty %d\n", isEmpty(maxHeap)); // Free memory delMaxHeap(maxHeap); return 0; } ================================================ FILE: en/codes/c/chapter_heap/top_k.c ================================================ /** * File: top_k.c * Created Time: 2023-10-26 * Author: krahets (krahets163.com) */ #include "my_heap.c" /* Element enters heap */ void pushMinHeap(MaxHeap *maxHeap, int val) { // Negate element push(maxHeap, -val); } /* Element exits heap */ int popMinHeap(MaxHeap *maxHeap) { // Negate element return -pop(maxHeap); } /* Access top element */ int peekMinHeap(MaxHeap *maxHeap) { // Negate element return -peek(maxHeap); } /* Extract elements from heap */ int *getMinHeap(MaxHeap *maxHeap) { // Negate all heap elements and store in res array int *res = (int *)malloc(maxHeap->size * sizeof(int)); for (int i = 0; i < maxHeap->size; i++) { res[i] = -maxHeap->data[i]; } return res; } // Function to find k largest elements in array using heap int *topKHeap(int *nums, int sizeNums, int k) { // Python's heapq module implements min heap by default // Note: We negate all heap elements to simulate min heap using max heap int *empty = (int *)malloc(0); MaxHeap *maxHeap = newMaxHeap(empty, 0); // Enter the first k elements of array into heap for (int i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // Starting from the (k+1)th element, maintain heap length as k for (int i = k; i < sizeNums; i++) { // If current element is greater than top element, top element exits heap, current element enters heap if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } int *res = getMinHeap(maxHeap); // Free memory delMaxHeap(maxHeap); return res; } /* Driver Code */ int main() { int nums[] = {1, 7, 6, 3, 2}; int k = 3; int sizeNums = sizeof(nums) / sizeof(nums[0]); int *res = topKHeap(nums, sizeNums, k); printf("The largest %d elements are: ", k); printArray(res, k); free(res); return 0; } ================================================ FILE: en/codes/c/chapter_searching/CMakeLists.txt ================================================ add_executable(binary_search binary_search.c) add_executable(two_sum two_sum.c) add_executable(binary_search_edge binary_search_edge.c) add_executable(binary_search_insertion binary_search_insertion.c) ================================================ FILE: en/codes/c/chapter_searching/binary_search.c ================================================ /** * File: binary_search.c * Created Time: 2023-03-18 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* Binary search (closed interval on both sides) */ int binarySearch(int *nums, int len, int target) { // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array int i = 0, j = len - 1; // Loop, exit when the search interval is empty (empty when i > j) while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) // This means target is in the interval [m+1, j] i = m + 1; else if (nums[m] > target) // This means target is in the interval [i, m-1] j = m - 1; else // Found the target element, return its index return m; } // Target element not found, return -1 return -1; } /* Binary search (left-closed right-open interval) */ int binarySearchLCRO(int *nums, int len, int target) { // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1 int i = 0, j = len; // Loop, exit when the search interval is empty (empty when i = j) while (i < j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) // This means target is in the interval [m+1, j) i = m + 1; else if (nums[m] > target) // This means target is in the interval [i, m) j = m; else // Found the target element, return its index return m; } // Target element not found, return -1 return -1; } /* Driver Code */ int main() { int target = 6; int nums[10] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; /* Binary search (closed interval on both sides) */ int index = binarySearch(nums, 10, target); printf("Index of target element 6 = %d\n", index); /* Binary search (left-closed right-open interval) */ index = binarySearchLCRO(nums, 10, target); printf("Index of target element 6 = %d\n", index); return 0; } ================================================ FILE: en/codes/c/chapter_searching/binary_search_edge.c ================================================ /** * File: binary_search_edge.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Binary search for insertion point (with duplicate elements) */ int binarySearchInsertion(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // Initialize closed interval [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else { j = m - 1; // The first element less than target is in the interval [i, m-1] } } // Return insertion point i return i; } /* Binary search for the leftmost target */ int binarySearchLeftEdge(int *nums, int numSize, int target) { // Equivalent to finding the insertion point of target int i = binarySearchInsertion(nums, numSize, target); // Target not found, return -1 if (i == numSize || nums[i] != target) { return -1; } // Found target, return index i return i; } /* Binary search for the rightmost target */ int binarySearchRightEdge(int *nums, int numSize, int target) { // Convert to finding the leftmost target + 1 int i = binarySearchInsertion(nums, numSize, target + 1); // j points to the rightmost target, i points to the first element greater than target int j = i - 1; // Target not found, return -1 if (j == -1 || nums[j] != target) { return -1; } // Found target, return index j return j; } /* Driver Code */ int main() { // Array with duplicate elements int nums[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; printf("\nArray nums = "); printArray(nums, sizeof(nums) / sizeof(nums[0])); // Binary search left and right boundaries int targets[] = {6, 7}; for (int i = 0; i < sizeof(targets) / sizeof(targets[0]); i++) { int index = binarySearchLeftEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); printf("Leftmost element %d index is %d\n", targets[i], index); index = binarySearchRightEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); printf("Rightmost element %d index is %d\n", targets[i], index); } return 0; } ================================================ FILE: en/codes/c/chapter_searching/binary_search_insertion.c ================================================ /** * File: binary_search_insertion.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Binary search for insertion point (no duplicate elements) */ int binarySearchInsertionSimple(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // Initialize closed interval [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { return m; // Found target, return insertion point m } } // Target not found, return insertion point i return i; } /* Binary search for insertion point (with duplicate elements) */ int binarySearchInsertion(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // Initialize closed interval [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { j = m - 1; // The first element less than target is in the interval [i, m-1] } } // Return insertion point i return i; } /* Driver Code */ int main() { // Array without duplicate elements int nums1[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; printf("\nArray nums = "); printArray(nums1, sizeof(nums1) / sizeof(nums1[0])); // Binary search for insertion point int targets1[] = {6, 9}; for (int i = 0; i < sizeof(targets1) / sizeof(targets1[0]); i++) { int index = binarySearchInsertionSimple(nums1, sizeof(nums1) / sizeof(nums1[0]), targets1[i]); printf("Insertion point index for element %d is %d\n", targets1[i], index); } // Array with duplicate elements int nums2[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; printf("\nArray nums = "); printArray(nums2, sizeof(nums2) / sizeof(nums2[0])); // Binary search for insertion point int targets2[] = {2, 6, 20}; for (int i = 0; i < sizeof(targets2) / sizeof(int); i++) { int index = binarySearchInsertion(nums2, sizeof(nums2) / sizeof(nums2[0]), targets2[i]); printf("Insertion point index for element %d is %d\n", targets2[i], index); } return 0; } ================================================ FILE: en/codes/c/chapter_searching/two_sum.c ================================================ /** * File: two_sum.c * Created Time: 2023-01-19 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Method 1: Brute force enumeration */ int *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) { for (int i = 0; i < numsSize; ++i) { for (int j = i + 1; j < numsSize; ++j) { if (nums[i] + nums[j] == target) { int *res = malloc(sizeof(int) * 2); res[0] = i, res[1] = j; *returnSize = 2; return res; } } } *returnSize = 0; return NULL; } /* Hash table */ typedef struct { int key; int val; UT_hash_handle hh; // Implemented using uthash.h } HashTable; /* Hash table lookup */ HashTable *find(HashTable *h, int key) { HashTable *tmp; HASH_FIND_INT(h, &key, tmp); return tmp; } /* Hash table element insertion */ void insert(HashTable **h, int key, int val) { HashTable *t = find(*h, key); if (t == NULL) { HashTable *tmp = malloc(sizeof(HashTable)); tmp->key = key, tmp->val = val; HASH_ADD_INT(*h, key, tmp); } else { t->val = val; } } /* Method 2: Auxiliary hash table */ int *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) { HashTable *hashtable = NULL; for (int i = 0; i < numsSize; i++) { HashTable *t = find(hashtable, target - nums[i]); if (t != NULL) { int *res = malloc(sizeof(int) * 2); res[0] = t->val, res[1] = i; *returnSize = 2; return res; } insert(&hashtable, nums[i], i); } *returnSize = 0; return NULL; } /* Driver Code */ int main() { // ======= Test Case ======= int nums[] = {2, 7, 11, 15}; int target = 13; // ====== Driver Code ====== int returnSize; int *res = twoSumBruteForce(nums, sizeof(nums) / sizeof(int), target, &returnSize); // Method 1 printf("Method 1 res = "); printArray(res, returnSize); // Method 2 res = twoSumHashTable(nums, sizeof(nums) / sizeof(int), target, &returnSize); printf("Method 2 res = "); printArray(res, returnSize); return 0; } ================================================ FILE: en/codes/c/chapter_sorting/CMakeLists.txt ================================================ add_executable(bubble_sort bubble_sort.c) add_executable(insertion_sort insertion_sort.c) add_executable(quick_sort quick_sort.c) add_executable(counting_sort counting_sort.c) add_executable(radix_sort radix_sort.c) add_executable(merge_sort merge_sort.c) add_executable(heap_sort heap_sort.c) add_executable(bucket_sort bucket_sort.c) add_executable(selection_sort selection_sort.c) ================================================ FILE: en/codes/c/chapter_sorting/bubble_sort.c ================================================ /** * File: bubble_sort.c * Created Time: 2022-12-26 * Author: Listening (https://github.com/L-Super) */ #include "../utils/common.h" /* Bubble sort */ void bubbleSort(int nums[], int size) { // Outer loop: unsorted range is [0, i] for (int i = size - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; } } } } /* Bubble sort (flag optimization) */ void bubbleSortWithFlag(int nums[], int size) { // Outer loop: unsorted range is [0, i] for (int i = size - 1; i > 0; i--) { bool flag = false; // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; flag = true; } } if (!flag) break; } } /* Driver Code */ int main() { int nums[6] = {4, 1, 3, 1, 5, 2}; printf("After bubble sort: "); bubbleSort(nums, 6); for (int i = 0; i < 6; i++) { printf("%d ", nums[i]); } int nums1[6] = {4, 1, 3, 1, 5, 2}; printf("\nAfter optimized bubble sort: "); bubbleSortWithFlag(nums1, 6); for (int i = 0; i < 6; i++) { printf("%d ", nums1[i]); } printf("\n"); return 0; } ================================================ FILE: en/codes/c/chapter_sorting/bucket_sort.c ================================================ /** * File: bucket_sort.c * Created Time: 2023-05-30 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define SIZE 10 /* Comparison function for qsort */ int compare(const void *a, const void *b) { float fa = *(const float *)a; float fb = *(const float *)b; return (fa > fb) - (fa < fb); } /* Bucket sort */ void bucketSort(float nums[], int n) { int k = n / 2; // Initialize k = n/2 buckets int *sizes = malloc(k * sizeof(int)); // Record each bucket's size float **buckets = malloc(k * sizeof(float *)); // Array of dynamic arrays (buckets) // Pre-allocate sufficient space for each bucket for (int i = 0; i < k; ++i) { buckets[i] = (float *)malloc(n * sizeof(float)); sizes[i] = 0; } // 1. Distribute array elements into various buckets for (int i = 0; i < n; ++i) { int idx = (int)(nums[i] * k); buckets[idx][sizes[idx]++] = nums[i]; } // 2. Sort each bucket for (int i = 0; i < k; ++i) { qsort(buckets[i], sizes[i], sizeof(float), compare); } // 3. Merge sorted buckets int idx = 0; for (int i = 0; i < k; ++i) { for (int j = 0; j < sizes[i]; ++j) { nums[idx++] = buckets[i][j]; } // Free memory free(buckets[i]); } } /* Driver Code */ int main() { // Assume input data is floating point, interval [0, 1) float nums[SIZE] = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; bucketSort(nums, SIZE); printf("After bucket sort completes, nums = "); printArrayFloat(nums, SIZE); return 0; } ================================================ FILE: en/codes/c/chapter_sorting/counting_sort.c ================================================ /** * File: counting_sort.c * Created Time: 2023-03-20 * Author: Reanon (793584285@qq.com), Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* Counting sort */ // Simple implementation, cannot be used for sorting objects void countingSortNaive(int nums[], int size) { // 1. Count the maximum element m in the array int m = 0; for (int i = 0; i < size; i++) { if (nums[i] > m) { m = nums[i]; } } // 2. Count the occurrence of each number // counter[num] represents the occurrence of num int *counter = calloc(m + 1, sizeof(int)); for (int i = 0; i < size; i++) { counter[nums[i]]++; } // 3. Traverse counter, filling each element back into the original array nums int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } // 4. Free memory free(counter); } /* Counting sort */ // Complete implementation, can sort objects and is a stable sort void countingSort(int nums[], int size) { // 1. Count the maximum element m in the array int m = 0; for (int i = 0; i < size; i++) { if (nums[i] > m) { m = nums[i]; } } // 2. Count the occurrence of each number // counter[num] represents the occurrence of num int *counter = calloc(m, sizeof(int)); for (int i = 0; i < size; i++) { counter[nums[i]]++; } // 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" // counter[num]-1 is the last index where num appears in res for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. Traverse nums in reverse order, placing each element into the result array res // Initialize the array res to record results int *res = malloc(sizeof(int) * size); for (int i = size - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // Place num at the corresponding index counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num } // Use result array res to overwrite the original array nums memcpy(nums, res, size * sizeof(int)); // 5. Free memory free(res); free(counter); } /* Driver Code */ int main() { int nums[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; int size = sizeof(nums) / sizeof(int); countingSortNaive(nums, size); printf("After counting sort (cannot sort objects) completes, nums = "); printArray(nums, size); int nums1[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; int size1 = sizeof(nums1) / sizeof(int); countingSort(nums1, size1); printf("After counting sort completes, nums1 = "); printArray(nums1, size1); return 0; } ================================================ FILE: en/codes/c/chapter_sorting/heap_sort.c ================================================ /** * File: heap_sort.c * Created Time: 2023-05-30 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Heap length is n, start heapifying node i, from top to bottom */ void siftDown(int nums[], int n, int i) { while (1) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break 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; // Swap two nodes if (ma == i) { break; } // Swap two nodes int temp = nums[i]; nums[i] = nums[ma]; nums[ma] = temp; // Loop downwards heapification i = ma; } } /* Heap sort */ void heapSort(int nums[], int n) { // Build heap operation: heapify all nodes except leaves for (int i = n / 2 - 1; i >= 0; --i) { siftDown(nums, n, i); } // Extract the largest element from the heap and repeat for n-1 rounds for (int i = n - 1; i > 0; --i) { // Delete node int tmp = nums[0]; nums[0] = nums[i]; nums[i] = tmp; // Start heapifying the root node, from top to bottom siftDown(nums, i, 0); } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; int n = sizeof(nums) / sizeof(nums[0]); heapSort(nums, n); printf("After heap sort completes, nums = "); printArray(nums, n); return 0; } ================================================ FILE: en/codes/c/chapter_sorting/insertion_sort.c ================================================ /** * File: insertion_sort.c * Created Time: 2022-12-29 * Author: Listening (https://github.com/L-Super) */ #include "../utils/common.h" /* Insertion sort */ void insertionSort(int nums[], int size) { // Outer loop: sorted interval is [0, i-1] for (int i = 1; i < size; i++) { int base = nums[i], j = i - 1; // Inner loop: insert base into the correct position within the sorted interval [0, i-1] while (j >= 0 && nums[j] > base) { // Move nums[j] to the right by one position nums[j + 1] = nums[j]; j--; } // Assign base to the correct position nums[j + 1] = base; } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; insertionSort(nums, 6); printf("After insertion sort completes, nums = "); for (int i = 0; i < 6; i++) { printf("%d ", nums[i]); } printf("\n"); return 0; } ================================================ FILE: en/codes/c/chapter_sorting/merge_sort.c ================================================ /** * File: merge_sort.c * Created Time: 2022-03-21 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* Merge left subarray and right subarray */ void merge(int *nums, int left, int mid, int right) { // Left subarray interval is [left, mid], right subarray interval is [mid+1, right] // Create a temporary array tmp to store the merged results int tmpSize = right - left + 1; int *tmp = (int *)malloc(tmpSize * sizeof(int)); // Initialize the start indices of the left and right subarrays int i = left, j = mid + 1, k = 0; // While both subarrays still have elements, compare and copy the smaller element into the temporary array while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // Copy the remaining elements of the left and right subarrays into the temporary array while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval for (k = 0; k < tmpSize; ++k) { nums[left + k] = tmp[k]; } // Free memory free(tmp); } /* Merge sort */ void mergeSort(int *nums, int left, int right) { // Termination condition if (left >= right) return; // Terminate recursion when subarray length is 1 // Divide and conquer stage int mid = left + (right - left) / 2; // Calculate midpoint mergeSort(nums, left, mid); // Recursively process the left subarray mergeSort(nums, mid + 1, right); // Recursively process the right subarray // Merge stage merge(nums, left, mid, right); } /* Driver Code */ int main() { /* Merge sort */ int nums[] = {7, 3, 2, 6, 0, 1, 5, 4}; int size = sizeof(nums) / sizeof(int); mergeSort(nums, 0, size - 1); printf("After merge sort completes, nums = "); printArray(nums, size); return 0; } ================================================ FILE: en/codes/c/chapter_sorting/quick_sort.c ================================================ /** * File: quick_sort.c * Created Time: 2023-01-18 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Swap elements */ void swap(int nums[], int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Sentinel partition */ int partition(int nums[], int left, int right) { // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // Search from right to left for the first element smaller than the pivot } while (i < j && nums[i] <= nums[left]) { i++; // Search from left to right for the first element greater than the pivot } // Swap these two elements swap(nums, i, j); } // Swap the pivot to the boundary between the two subarrays swap(nums, i, left); // Return the index of the pivot return i; } /* Quick sort */ void quickSort(int nums[], int left, int right) { // Terminate recursion when subarray length is 1 if (left >= right) { return; } // Sentinel partition int pivot = partition(nums, left, right); // Recursively process the left subarray and right subarray quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } // Quick sort with median-of-three optimization below /* Select the median of three candidate elements */ 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 is between l and r if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l is between m and r return right; } /* Sentinel partition (median of three) */ int partitionMedian(int nums[], int left, int right) { // Select the median of three candidate elements int med = medianThree(nums, left, (left + right) / 2, right); // Swap the median to the array's leftmost position swap(nums, left, med); // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot swap(nums, i, j); // Swap these two elements } swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort (median-of-three) */ void quickSortMedian(int nums[], int left, int right) { // Terminate recursion when subarray length is 1 if (left >= right) return; // Sentinel partition int pivot = partitionMedian(nums, left, right); // Recursively process the left subarray and right subarray quickSortMedian(nums, left, pivot - 1); quickSortMedian(nums, pivot + 1, right); } // Quick sort with recursion depth optimization below /* Quick sort (recursion depth optimization) */ void quickSortTailCall(int nums[], int left, int right) { // Terminate when subarray length is 1 while (left < right) { // Sentinel partition operation int pivot = partition(nums, left, right); // Perform quick sort on the shorter of the two subarrays if (pivot - left < right - pivot) { // Recursively sort the left subarray quickSortTailCall(nums, left, pivot - 1); // Remaining unsorted interval is [pivot + 1, right] left = pivot + 1; } else { // Recursively sort the right subarray quickSortTailCall(nums, pivot + 1, right); // Remaining unsorted interval is [left, pivot - 1] right = pivot - 1; } } } /* Driver Code */ int main() { /* Quick sort */ int nums[] = {2, 4, 1, 0, 3, 5}; int size = sizeof(nums) / sizeof(int); quickSort(nums, 0, size - 1); printf("After quick sort completes, nums = "); printArray(nums, size); /* Quick sort (recursion depth optimization) */ int nums1[] = {2, 4, 1, 0, 3, 5}; quickSortMedian(nums1, 0, size - 1); printf("After quick sort (median pivot optimization), nums = "); printArray(nums1, size); /* Quick sort (recursion depth optimization) */ int nums2[] = {2, 4, 1, 0, 3, 5}; quickSortTailCall(nums2, 0, size - 1); printf("After quick sort (recursion depth optimization), nums = "); printArray(nums1, size); return 0; } ================================================ FILE: en/codes/c/chapter_sorting/radix_sort.c ================================================ /** * File: radix_sort.c * Created Time: 2023-01-18 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Get the k-th digit of element num, where exp = 10^(k-1) */ int digit(int num, int exp) { // Passing exp instead of k can avoid repeated expensive exponentiation here return (num / exp) % 10; } /* Counting sort (based on nums k-th digit) */ void countingSortDigit(int nums[], int size, int exp) { // Decimal digit range is 0~9, therefore need a bucket array of length 10 int *counter = (int *)malloc((sizeof(int) * 10)); memset(counter, 0, sizeof(int) * 10); // Initialize to 0 to support subsequent memory release // Count the occurrence of digits 0~9 for (int i = 0; i < size; i++) { // Get the k-th digit of nums[i], noted as d int d = digit(nums[i], exp); // Count the occurrence of digit d counter[d]++; } // Calculate prefix sum, converting "occurrence count" into "array index" for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // Traverse in reverse, based on bucket statistics, place each element into res int *res = (int *)malloc(sizeof(int) * size); for (int i = size - 1; i >= 0; i--) { int d = digit(nums[i], exp); int j = counter[d] - 1; // Get the index j for d in the array res[j] = nums[i]; // Place the current element at index j counter[d]--; // Decrease the count of d by 1 } // Use result to overwrite the original array nums for (int i = 0; i < size; i++) { nums[i] = res[i]; } // Free memory free(res); free(counter); } /* Radix sort */ void radixSort(int nums[], int size) { // Get the maximum element of the array, used to determine the maximum number of digits int max = INT32_MIN; for (int i = 0; i < size; i++) { if (nums[i] > max) { max = nums[i]; } } // Traverse from the lowest to the highest digit for (int exp = 1; max >= exp; exp *= 10) // Perform counting sort on the k-th digit of array elements // k = 1 -> exp = 1 // k = 2 -> exp = 10 // i.e., exp = 10^(k-1) countingSortDigit(nums, size, exp); } /* Driver Code */ int main() { // Radix sort int nums[] = {10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996}; int size = sizeof(nums) / sizeof(int); radixSort(nums, size); printf("After radix sort completes, nums = "); printArray(nums, size); } ================================================ FILE: en/codes/c/chapter_sorting/selection_sort.c ================================================ /** * File: selection_sort.c * Created Time: 2023-05-31 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Selection sort */ void selectionSort(int nums[], int n) { // Outer loop: unsorted interval is [i, n-1] for (int i = 0; i < n - 1; i++) { // Inner loop: find the smallest element within the unsorted interval int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // Record the index of the smallest element } // Swap the smallest element with the first element of the unsorted interval int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; int n = sizeof(nums) / sizeof(nums[0]); selectionSort(nums, n); printf("After selection sort completes, nums = "); printArray(nums, n); return 0; } ================================================ FILE: en/codes/c/chapter_stack_and_queue/CMakeLists.txt ================================================ add_executable(array_stack array_stack.c) add_executable(linkedlist_stack linkedlist_stack.c) add_executable(array_queue array_queue.c) add_executable(linkedlist_queue linkedlist_queue.c) add_executable(array_deque array_deque.c) add_executable(linkedlist_deque linkedlist_deque.c) ================================================ FILE: en/codes/c/chapter_stack_and_queue/array_deque.c ================================================ /** * File: array_deque.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Double-ended queue based on circular array implementation */ typedef struct { int *nums; // Array for storing queue elements int front; // Front pointer, points to the front of the queue element int queSize; // Rear pointer, points to rear + 1 int queCapacity; // Queue capacity } ArrayDeque; /* Constructor */ ArrayDeque *newArrayDeque(int capacity) { ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque)); // Initialize array deque->queCapacity = capacity; deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity); deque->front = deque->queSize = 0; return deque; } /* Destructor */ void delArrayDeque(ArrayDeque *deque) { free(deque->nums); free(deque); } /* Get the capacity of the double-ended queue */ int capacity(ArrayDeque *deque) { return deque->queCapacity; } /* Get the length of the double-ended queue */ int size(ArrayDeque *deque) { return deque->queSize; } /* Check if the double-ended queue is empty */ bool empty(ArrayDeque *deque) { return deque->queSize == 0; } /* Calculate circular array index */ int dequeIndex(ArrayDeque *deque, int i) { // Use modulo operation to wrap the array head and tail together // When i exceeds array end, wrap to head // When i passes the head of the array, return to the tail return ((i + capacity(deque)) % capacity(deque)); } /* Front of the queue enqueue */ void pushFirst(ArrayDeque *deque, int num) { if (deque->queSize == capacity(deque)) { printf("Deque is full\r\n"); return; } // Use modulo operation to wrap front around to the tail after passing the head of the array // Use modulo to wrap front from array head to rear deque->front = dequeIndex(deque, deque->front - 1); // Add num to queue front deque->nums[deque->front] = num; deque->queSize++; } /* Rear of the queue enqueue */ void pushLast(ArrayDeque *deque, int num) { if (deque->queSize == capacity(deque)) { printf("Deque is full\r\n"); return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array int rear = dequeIndex(deque, deque->front + deque->queSize); // Front pointer moves one position backward deque->nums[rear] = num; deque->queSize++; } /* Return list for printing */ int peekFirst(ArrayDeque *deque) { // Access error: Deque is empty assert(empty(deque) == 0); return deque->nums[deque->front]; } /* Driver Code */ int peekLast(ArrayDeque *deque) { // Access error: Deque is empty assert(empty(deque) == 0); int last = dequeIndex(deque, deque->front + deque->queSize - 1); return deque->nums[last]; } /* Rear of the queue dequeue */ int popFirst(ArrayDeque *deque) { int num = peekFirst(deque); // Move front pointer backward by one position deque->front = dequeIndex(deque, deque->front + 1); deque->queSize--; return num; } /* Access rear of the queue element */ int popLast(ArrayDeque *deque) { int num = peekLast(deque); deque->queSize--; return num; } /* Return array for printing */ int *toArray(ArrayDeque *deque, int *queSize) { *queSize = deque->queSize; int *res = (int *)calloc(deque->queSize, sizeof(int)); int j = deque->front; for (int i = 0; i < deque->queSize; i++) { res[i] = deque->nums[j % deque->queCapacity]; j++; } return res; } /* Driver Code */ int main() { /* Access front of the queue element */ int capacity = 10; int queSize; ArrayDeque *deque = newArrayDeque(capacity); pushLast(deque, 3); pushLast(deque, 2); pushLast(deque, 5); printf("Double-ended queue deque = "); printArray(toArray(deque, &queSize), queSize); /* Update element */ int peekFirstNum = peekFirst(deque); printf("Front element peekFirst = %d\r\n", peekFirstNum); int peekLastNum = peekLast(deque); printf("Rear element peekLast = %d\r\n", peekLastNum); /* Elements enqueue */ pushLast(deque, 4); printf("After element 4 enqueues at rear, deque = "); printArray(toArray(deque, &queSize), queSize); pushFirst(deque, 1); printf("After element 1 enqueues at front, deque = "); printArray(toArray(deque, &queSize), queSize); /* Element dequeue */ int popLastNum = popLast(deque); printf("Dequeue from rear = %d, deque after rear dequeue = ", popLastNum); printArray(toArray(deque, &queSize), queSize); int popFirstNum = popFirst(deque); printf("Dequeue from front = %d, deque after front dequeue = ", popFirstNum); printArray(toArray(deque, &queSize), queSize); /* Get the length of the queue */ int dequeSize = size(deque); printf("Deque size = %d\r\n", dequeSize); /* Check if the queue is empty */ bool isEmpty = empty(deque); printf("Is queue empty = %s\r\n", isEmpty ? "true" : "false"); // Free memory delArrayDeque(deque); return 0; } ================================================ FILE: en/codes/c/chapter_stack_and_queue/array_queue.c ================================================ /** * File: array_queue.c * Created Time: 2023-01-28 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* Queue based on circular array implementation */ typedef struct { int *nums; // Array for storing queue elements int front; // Front pointer, points to the front of the queue element int queSize; // Current number of elements in the queue int queCapacity; // Queue capacity } ArrayQueue; /* Constructor */ ArrayQueue *newArrayQueue(int capacity) { ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue)); // Initialize array queue->queCapacity = capacity; queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity); queue->front = queue->queSize = 0; return queue; } /* Destructor */ void delArrayQueue(ArrayQueue *queue) { free(queue->nums); free(queue); } /* Get the capacity of the queue */ int capacity(ArrayQueue *queue) { return queue->queCapacity; } /* Get the length of the queue */ int size(ArrayQueue *queue) { return queue->queSize; } /* Check if the queue is empty */ bool empty(ArrayQueue *queue) { return queue->queSize == 0; } /* Return list for printing */ int peek(ArrayQueue *queue) { assert(size(queue) != 0); return queue->nums[queue->front]; } /* Enqueue */ void push(ArrayQueue *queue, int num) { if (size(queue) == capacity(queue)) { printf("Queue is full\r\n"); return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array // Add num to the rear of the queue int rear = (queue->front + queue->queSize) % queue->queCapacity; // Front pointer moves one position backward queue->nums[rear] = num; queue->queSize++; } /* Dequeue */ int pop(ArrayQueue *queue) { int num = peek(queue); // Move front pointer backward by one position, if it passes the tail, return to array head queue->front = (queue->front + 1) % queue->queCapacity; queue->queSize--; return num; } /* Return array for printing */ int *toArray(ArrayQueue *queue, int *queSize) { *queSize = queue->queSize; int *res = (int *)calloc(queue->queSize, sizeof(int)); int j = queue->front; for (int i = 0; i < queue->queSize; i++) { res[i] = queue->nums[j % queue->queCapacity]; j++; } return res; } /* Driver Code */ int main() { /* Access front of the queue element */ int capacity = 10; int queSize; ArrayQueue *queue = newArrayQueue(capacity); /* Elements enqueue */ push(queue, 1); push(queue, 3); push(queue, 2); push(queue, 5); push(queue, 4); printf("Queue queue = "); printArray(toArray(queue, &queSize), queSize); /* Return list for printing */ int peekNum = peek(queue); printf("Front element peek = %d\r\n", peekNum); /* Element dequeue */ peekNum = pop(queue); printf("Dequeue element pop = %d, queue after dequeue = ", peekNum); printArray(toArray(queue, &queSize), queSize); /* Get the length of the queue */ int queueSize = size(queue); printf("Queue size = %d\r\n", queueSize); /* Check if the queue is empty */ bool isEmpty = empty(queue); printf("Is queue empty = %s\r\n", isEmpty ? "true" : "false"); /* Test circular array */ for (int i = 0; i < 10; i++) { push(queue, i); pop(queue); printf("After round %d enqueue + dequeue, queue = ", i); printArray(toArray(queue, &queSize), queSize); } // Free memory delArrayQueue(queue); return 0; } ================================================ FILE: en/codes/c/chapter_stack_and_queue/array_stack.c ================================================ /** * File: array_stack.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 5000 /* Stack based on array implementation */ typedef struct { int *data; int size; } ArrayStack; /* Constructor */ ArrayStack *newArrayStack() { ArrayStack *stack = malloc(sizeof(ArrayStack)); // Initialize with large capacity to avoid expansion stack->data = malloc(sizeof(int) * MAX_SIZE); stack->size = 0; return stack; } /* Destructor */ void delArrayStack(ArrayStack *stack) { free(stack->data); free(stack); } /* Get the length of the stack */ int size(ArrayStack *stack) { return stack->size; } /* Check if the stack is empty */ bool isEmpty(ArrayStack *stack) { return stack->size == 0; } /* Push */ void push(ArrayStack *stack, int num) { if (stack->size == MAX_SIZE) { printf("Stack is full\n"); return; } stack->data[stack->size] = num; stack->size++; } /* Return list for printing */ int peek(ArrayStack *stack) { if (stack->size == 0) { printf("Stack is empty\n"); return INT_MAX; } return stack->data[stack->size - 1]; } /* Pop */ int pop(ArrayStack *stack) { int val = peek(stack); stack->size--; return val; } /* Driver Code */ int main() { /* Access top of the stack element */ ArrayStack *stack = newArrayStack(); /* Elements push onto stack */ push(stack, 1); push(stack, 3); push(stack, 2); push(stack, 5); push(stack, 4); printf("Stack stack = "); printArray(stack->data, stack->size); /* Return list for printing */ int val = peek(stack); printf("Top element top = %d\n", val); /* Element pop from stack */ val = pop(stack); printf("Pop element pop = %d, stack after pop = ", val); printArray(stack->data, stack->size); /* Get the length of the stack */ int size = stack->size; printf("Stack size = %d\n", size); /* Check if empty */ bool empty = isEmpty(stack); printf("Is stack empty = %s\n", empty ? "true" : "false"); // Free memory delArrayStack(stack); return 0; } ================================================ FILE: en/codes/c/chapter_stack_and_queue/linkedlist_deque.c ================================================ /** * File: linkedlist_deque.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Doubly linked list node */ typedef struct DoublyListNode { int val; // Node value struct DoublyListNode *next; // Successor node struct DoublyListNode *prev; // Predecessor node } DoublyListNode; /* Constructor */ DoublyListNode *newDoublyListNode(int num) { DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode)); new->val = num; new->next = NULL; new->prev = NULL; return new; } /* Destructor */ void delDoublyListNode(DoublyListNode *node) { free(node); } /* Double-ended queue based on doubly linked list implementation */ typedef struct { DoublyListNode *front, *rear; // Head node front, tail node rear int queSize; // Length of the double-ended queue } LinkedListDeque; /* Constructor */ LinkedListDeque *newLinkedListDeque() { LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque)); deque->front = NULL; deque->rear = NULL; deque->queSize = 0; return deque; } /* Destructor */ void delLinkedListdeque(LinkedListDeque *deque) { // Free all nodes for (int i = 0; i < deque->queSize && deque->front != NULL; i++) { DoublyListNode *tmp = deque->front; deque->front = deque->front->next; free(tmp); } // Free deque structure free(deque); } /* Get the length of the queue */ int size(LinkedListDeque *deque) { return deque->queSize; } /* Check if the queue is empty */ bool empty(LinkedListDeque *deque) { return (size(deque) == 0); } /* Enqueue */ void push(LinkedListDeque *deque, int num, bool isFront) { DoublyListNode *node = newDoublyListNode(num); // If list is empty, set both front and rear to node if (empty(deque)) { deque->front = deque->rear = node; } // Front of the queue enqueue operation else if (isFront) { // Add node to the head of the linked list deque->front->prev = node; node->next = deque->front; deque->front = node; // Update head node } // Rear of the queue enqueue operation else { // Add node to the tail of the linked list deque->rear->next = node; node->prev = deque->rear; deque->rear = node; } deque->queSize++; // Update queue length } /* Front of the queue enqueue */ void pushFirst(LinkedListDeque *deque, int num) { push(deque, num, true); } /* Rear of the queue enqueue */ void pushLast(LinkedListDeque *deque, int num) { push(deque, num, false); } /* Return list for printing */ int peekFirst(LinkedListDeque *deque) { assert(size(deque) && deque->front); return deque->front->val; } /* Driver Code */ int peekLast(LinkedListDeque *deque) { assert(size(deque) && deque->rear); return deque->rear->val; } /* Dequeue */ int pop(LinkedListDeque *deque, bool isFront) { if (empty(deque)) return -1; int val; // Temporarily store head node value if (isFront) { val = peekFirst(deque); // Delete head node DoublyListNode *fNext = deque->front->next; if (fNext) { fNext->prev = NULL; deque->front->next = NULL; } delDoublyListNode(deque->front); deque->front = fNext; // Update head node } // Temporarily store tail node value else { val = peekLast(deque); // Delete tail node DoublyListNode *rPrev = deque->rear->prev; if (rPrev) { rPrev->next = NULL; deque->rear->prev = NULL; } delDoublyListNode(deque->rear); deque->rear = rPrev; // Update tail node } deque->queSize--; // Update queue length return val; } /* Rear of the queue dequeue */ int popFirst(LinkedListDeque *deque) { return pop(deque, true); } /* Access rear of the queue element */ int popLast(LinkedListDeque *deque) { return pop(deque, false); } /* Print queue */ void printLinkedListDeque(LinkedListDeque *deque) { int *arr = malloc(sizeof(int) * deque->queSize); // Copy data from list to array int i; DoublyListNode *node; for (i = 0, node = deque->front; i < deque->queSize; i++) { arr[i] = node->val; node = node->next; } printArray(arr, deque->queSize); free(arr); } /* Driver Code */ int main() { /* Get the length of the double-ended queue */ LinkedListDeque *deque = newLinkedListDeque(); pushLast(deque, 3); pushLast(deque, 2); pushLast(deque, 5); printf("Double-ended queue deque = "); printLinkedListDeque(deque); /* Update element */ int peekFirstNum = peekFirst(deque); printf("Front element peekFirst = %d\r\n", peekFirstNum); int peekLastNum = peekLast(deque); printf("Front element peekLast = %d\r\n", peekLastNum); /* Elements enqueue */ pushLast(deque, 4); printf("After element 4 enqueues at back, deque ="); printLinkedListDeque(deque); pushFirst(deque, 1); printf("After element 1 enqueues at front, deque ="); printLinkedListDeque(deque); /* Element dequeue */ int popLastNum = popLast(deque); printf("Dequeue from rear popLast = %d, deque after rear dequeue = ", popLastNum); printLinkedListDeque(deque); int popFirstNum = popFirst(deque); printf("Dequeue from front popFirst = %d, deque after front dequeue = ", popFirstNum); printLinkedListDeque(deque); /* Get the length of the queue */ int dequeSize = size(deque); printf("Deque size = %d\r\n", dequeSize); /* Check if the queue is empty */ bool isEmpty = empty(deque); printf("Is deque empty = %s\r\n", isEmpty ? "true" : "false"); // Free memory delLinkedListdeque(deque); return 0; } ================================================ FILE: en/codes/c/chapter_stack_and_queue/linkedlist_queue.c ================================================ /** * File: linkedlist_queue.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Queue based on linked list implementation */ typedef struct { ListNode *front, *rear; int queSize; } LinkedListQueue; /* Constructor */ LinkedListQueue *newLinkedListQueue() { LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue)); queue->front = NULL; queue->rear = NULL; queue->queSize = 0; return queue; } /* Destructor */ void delLinkedListQueue(LinkedListQueue *queue) { // Free all nodes while (queue->front != NULL) { ListNode *tmp = queue->front; queue->front = queue->front->next; free(tmp); } // Free queue structure free(queue); } /* Get the length of the queue */ int size(LinkedListQueue *queue) { return queue->queSize; } /* Check if the queue is empty */ bool empty(LinkedListQueue *queue) { return (size(queue) == 0); } /* Enqueue */ void push(LinkedListQueue *queue, int num) { // Add node at tail ListNode *node = newListNode(num); // If the queue is empty, make both front and rear point to the node if (queue->front == NULL) { queue->front = node; queue->rear = node; } // If the queue is not empty, add the node after the tail node else { queue->rear->next = node; queue->rear = node; } queue->queSize++; } /* Return list for printing */ int peek(LinkedListQueue *queue) { assert(size(queue) && queue->front); return queue->front->val; } /* Dequeue */ int pop(LinkedListQueue *queue) { int num = peek(queue); ListNode *tmp = queue->front; queue->front = queue->front->next; free(tmp); queue->queSize--; return num; } /* Print queue */ void printLinkedListQueue(LinkedListQueue *queue) { int *arr = malloc(sizeof(int) * queue->queSize); // Copy data from list to array int i; ListNode *node; for (i = 0, node = queue->front; i < queue->queSize; i++) { arr[i] = node->val; node = node->next; } printArray(arr, queue->queSize); free(arr); } /* Driver Code */ int main() { /* Access front of the queue element */ LinkedListQueue *queue = newLinkedListQueue(); /* Elements enqueue */ push(queue, 1); push(queue, 3); push(queue, 2); push(queue, 5); push(queue, 4); printf("Queue queue = "); printLinkedListQueue(queue); /* Return list for printing */ int peekNum = peek(queue); printf("Front element peek = %d\r\n", peekNum); /* Element dequeue */ peekNum = pop(queue); printf("Dequeue element pop = %d, queue after dequeue = ", peekNum); printLinkedListQueue(queue); /* Get the length of the queue */ int queueSize = size(queue); printf("Queue size = %d\r\n", queueSize); /* Check if the queue is empty */ bool isEmpty = empty(queue); printf("Is queue empty = %s\r\n", isEmpty ? "true" : "false"); // Free memory delLinkedListQueue(queue); return 0; } ================================================ FILE: en/codes/c/chapter_stack_and_queue/linkedlist_stack.c ================================================ /** * File: linkedlist_stack.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* Stack based on linked list implementation */ typedef struct { ListNode *top; // Use head node as stack top int size; // Stack length } LinkedListStack; /* Constructor */ LinkedListStack *newLinkedListStack() { LinkedListStack *s = malloc(sizeof(LinkedListStack)); s->top = NULL; s->size = 0; return s; } /* Destructor */ void delLinkedListStack(LinkedListStack *s) { while (s->top) { ListNode *n = s->top->next; free(s->top); s->top = n; } free(s); } /* Get the length of the stack */ int size(LinkedListStack *s) { return s->size; } /* Check if the stack is empty */ bool isEmpty(LinkedListStack *s) { return size(s) == 0; } /* Push */ void push(LinkedListStack *s, int num) { ListNode *node = (ListNode *)malloc(sizeof(ListNode)); node->next = s->top; // Update new node's pointer field node->val = num; // Update new node's data field s->top = node; // Update stack top s->size++; // Update stack size } /* Return list for printing */ int peek(LinkedListStack *s) { if (s->size == 0) { printf("Stack is empty\n"); return INT_MAX; } return s->top->val; } /* Pop */ int pop(LinkedListStack *s) { int val = peek(s); ListNode *tmp = s->top; s->top = s->top->next; // Free memory free(tmp); s->size--; return val; } /* Driver Code */ int main() { /* Access top of the stack element */ LinkedListStack *stack = newLinkedListStack(); /* Elements push onto stack */ push(stack, 1); push(stack, 3); push(stack, 2); push(stack, 5); push(stack, 4); printf("Stack stack = "); printLinkedList(stack->top); /* Return list for printing */ int val = peek(stack); printf("Top element top = %d\r\n", val); /* Element pop from stack */ val = pop(stack); printf("Pop element pop = %d, stack after pop = ", val); printLinkedList(stack->top); /* Get the length of the stack */ printf("Stack size = %d\n", size(stack)); /* Check if empty */ bool empty = isEmpty(stack); printf("Is stack empty = %s\n", empty ? "true" : "false"); // Free memory delLinkedListStack(stack); return 0; } ================================================ FILE: en/codes/c/chapter_tree/CMakeLists.txt ================================================ add_executable(avl_tree avl_tree.c) add_executable(binary_tree binary_tree.c) add_executable(binary_tree_bfs binary_tree_bfs.c) add_executable(binary_tree_dfs binary_tree_dfs.c) add_executable(binary_search_tree binary_search_tree.c) add_executable(array_binary_tree array_binary_tree.c) ================================================ FILE: en/codes/c/chapter_tree/array_binary_tree.c ================================================ /** * File: array_binary_tree.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Binary tree structure in array representation */ typedef struct { int *tree; int size; } ArrayBinaryTree; /* Constructor */ ArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) { ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree)); abt->tree = malloc(sizeof(int) * arrSize); memcpy(abt->tree, arr, sizeof(int) * arrSize); abt->size = arrSize; return abt; } /* Destructor */ void delArrayBinaryTree(ArrayBinaryTree *abt) { free(abt->tree); free(abt); } /* List capacity */ int size(ArrayBinaryTree *abt) { return abt->size; } /* Get value of node at index i */ int val(ArrayBinaryTree *abt, int i) { // Return INT_MAX if index out of bounds, representing empty position if (i < 0 || i >= size(abt)) return INT_MAX; return abt->tree[i]; } /* Get index of left child node of node at index i */ int left(int i) { return 2 * i + 1; } /* Get index of right child node of node at index i */ int right(int i) { return 2 * i + 2; } /* Get index of parent node of node at index i */ int parent(int i) { return (i - 1) / 2; } /* Level-order traversal */ int *levelOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; // Traverse array directly for (int i = 0; i < size(abt); i++) { if (val(abt, i) != INT_MAX) res[index++] = val(abt, i); } *returnSize = index; return res; } /* Depth-first traversal */ void dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) { // If empty position, return if (val(abt, i) == INT_MAX) return; // Preorder traversal if (strcmp(order, "pre") == 0) res[(*index)++] = val(abt, i); dfs(abt, left(i), order, res, index); // Inorder traversal if (strcmp(order, "in") == 0) res[(*index)++] = val(abt, i); dfs(abt, right(i), order, res, index); // Postorder traversal if (strcmp(order, "post") == 0) res[(*index)++] = val(abt, i); } /* Preorder traversal */ int *preOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "pre", res, &index); *returnSize = index; return res; } /* Inorder traversal */ int *inOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "in", res, &index); *returnSize = index; return res; } /* Postorder traversal */ int *postOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "post", res, &index); *returnSize = index; return res; } /* Driver Code */ int main() { // Initialize binary tree // Use INT_MAX to represent NULL int arr[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; int arrSize = sizeof(arr) / sizeof(arr[0]); TreeNode *root = arrayToTree(arr, arrSize); printf("\nInitialize binary tree\n"); printf("Array representation of binary tree:\n"); printArray(arr, arrSize); printf("Linked list representation of binary tree:\n"); printTree(root); ArrayBinaryTree *abt = newArrayBinaryTree(arr, arrSize); // Access node int i = 1; int l = left(i), r = right(i), p = parent(i); printf("\nCurrent node index is %d, value is %d\n", i, val(abt, i)); printf("Its left child index is %d, value is %d\n", l, l < arrSize ? val(abt, l) : INT_MAX); printf("Its right child index is %d, value is %d\n", r, r < arrSize ? val(abt, r) : INT_MAX); printf("Its parent node index is %d, value is %d\n", p, p < arrSize ? val(abt, p) : INT_MAX); // Traverse tree int returnSize; int *res; res = levelOrder(abt, &returnSize); printf("\nLevel-order traversal: "); printArray(res, returnSize); free(res); res = preOrder(abt, &returnSize); printf("Pre-order traversal: "); printArray(res, returnSize); free(res); res = inOrder(abt, &returnSize); printf("In-order traversal: "); printArray(res, returnSize); free(res); res = postOrder(abt, &returnSize); printf("Post-order traversal: "); printArray(res, returnSize); free(res); // Free memory delArrayBinaryTree(abt); return 0; } ================================================ FILE: en/codes/c/chapter_tree/avl_tree.c ================================================ /** * File: avl_tree.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* AVL tree structure */ typedef struct { TreeNode *root; } AVLTree; /* Constructor */ AVLTree *newAVLTree() { AVLTree *tree = (AVLTree *)malloc(sizeof(AVLTree)); tree->root = NULL; return tree; } /* Destructor */ void delAVLTree(AVLTree *tree) { freeMemoryTree(tree->root); free(tree); } /* Get node height */ int height(TreeNode *node) { // Empty node height is -1, leaf node height is 0 if (node != NULL) { return node->height; } return -1; } /* Update node height */ void updateHeight(TreeNode *node) { int lh = height(node->left); int rh = height(node->right); // Node height equals the height of the tallest subtree + 1 if (lh > rh) { node->height = lh + 1; } else { node->height = rh + 1; } } /* Get balance factor */ int balanceFactor(TreeNode *node) { // Empty node balance factor is 0 if (node == NULL) { return 0; } // Node balance factor = left subtree height - right subtree height return height(node->left) - height(node->right); } /* Right rotation operation */ TreeNode *rightRotate(TreeNode *node) { TreeNode *child, *grandChild; child = node->left; grandChild = child->right; // Using child as pivot, rotate node to the right child->right = node; node->left = grandChild; // Update node height updateHeight(node); updateHeight(child); // Return root node of subtree after rotation return child; } /* Left rotation operation */ TreeNode *leftRotate(TreeNode *node) { TreeNode *child, *grandChild; child = node->right; grandChild = child->left; // Using child as pivot, rotate node to the left child->left = node; node->right = grandChild; // Update node height updateHeight(node); updateHeight(child); // Return root node of subtree after rotation return child; } /* Perform rotation operation to restore balance to this subtree */ TreeNode *rotate(TreeNode *node) { // Get balance factor of node int bf = balanceFactor(node); // Left-leaning tree if (bf > 1) { if (balanceFactor(node->left) >= 0) { // Right rotation return rightRotate(node); } else { // First left rotation then right rotation node->left = leftRotate(node->left); return rightRotate(node); } } // Right-leaning tree if (bf < -1) { if (balanceFactor(node->right) <= 0) { // Left rotation return leftRotate(node); } else { // First right rotation then left rotation node->right = rightRotate(node->right); return leftRotate(node); } } // Balanced tree, no rotation needed, return directly return node; } /* Recursively insert node (helper function) */ TreeNode *insertHelper(TreeNode *node, int val) { if (node == NULL) { return newTreeNode(val); } /* 1. Find insertion position and insert node */ if (val < node->val) { node->left = insertHelper(node->left, val); } else if (val > node->val) { node->right = insertHelper(node->right, val); } else { // Duplicate node not inserted, return directly return node; } // Update node height updateHeight(node); /* 2. Perform rotation operation to restore balance to this subtree */ node = rotate(node); // Return root node of subtree return node; } /* Insert node */ void insert(AVLTree *tree, int val) { tree->root = insertHelper(tree->root, val); } /* Recursively remove node (helper function) */ TreeNode *removeHelper(TreeNode *node, int val) { TreeNode *child, *grandChild; if (node == NULL) { return NULL; } /* 1. Find node and delete */ if (val < node->val) { node->left = removeHelper(node->left, val); } else if (val > node->val) { node->right = removeHelper(node->right, val); } else { if (node->left == NULL || node->right == NULL) { child = node->left; if (node->right != NULL) { child = node->right; } // Number of child nodes = 0, delete node directly and return if (child == NULL) { return NULL; } else { // Number of child nodes = 1, delete node directly node = child; } } else { // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it TreeNode *temp = node->right; while (temp->left != NULL) { temp = temp->left; } int tempVal = temp->val; node->right = removeHelper(node->right, temp->val); node->val = tempVal; } } // Update node height updateHeight(node); /* 2. Perform rotation operation to restore balance to this subtree */ node = rotate(node); // Return root node of subtree return node; } /* Remove node */ // Cannot use remove keyword here due to stdio.h inclusion void removeItem(AVLTree *tree, int val) { TreeNode *root = removeHelper(tree->root, val); } /* Search node */ TreeNode *search(AVLTree *tree, int val) { TreeNode *cur = tree->root; // Loop search, exit after passing leaf node while (cur != NULL) { if (cur->val < val) { // Target node is in cur's right subtree cur = cur->right; } else if (cur->val > val) { // Target node is in cur's left subtree cur = cur->left; } else { // Found target node, exit loop break; } } // Found target node, exit loop return cur; } void testInsert(AVLTree *tree, int val) { insert(tree, val); printf("\nAfter inserting node %d, AVL tree is \n", val); printTree(tree->root); } void testRemove(AVLTree *tree, int val) { removeItem(tree, val); printf("\nAfter removing node %d, AVL tree is \n", val); printTree(tree->root); } /* Driver Code */ int main() { /* Please pay attention to how the AVL tree maintains balance after inserting nodes */ AVLTree *tree = (AVLTree *)newAVLTree(); /* Insert node */ // Delete nodes testInsert(tree, 1); testInsert(tree, 2); testInsert(tree, 3); testInsert(tree, 4); testInsert(tree, 5); testInsert(tree, 8); testInsert(tree, 7); testInsert(tree, 9); testInsert(tree, 10); testInsert(tree, 6); /* Please pay attention to how the AVL tree maintains balance after deleting nodes */ testInsert(tree, 7); /* Remove node */ // Delete node with degree 1 testRemove(tree, 8); // Delete node with degree 2 testRemove(tree, 5); // Remove node with degree 1 testRemove(tree, 4); // Remove node with degree 2 /* Search node */ TreeNode *node = search(tree, 7); printf("\nFound node object value = %d \n", node->val); // Free memory delAVLTree(tree); return 0; } ================================================ FILE: en/codes/c/chapter_tree/binary_search_tree.c ================================================ /** * File: binary_search_tree.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Binary search tree structure */ typedef struct { TreeNode *root; } BinarySearchTree; /* Constructor */ BinarySearchTree *newBinarySearchTree() { // Initialize empty tree BinarySearchTree *bst = (BinarySearchTree *)malloc(sizeof(BinarySearchTree)); bst->root = NULL; return bst; } /* Destructor */ void delBinarySearchTree(BinarySearchTree *bst) { freeMemoryTree(bst->root); free(bst); } /* Get binary tree root node */ TreeNode *getRoot(BinarySearchTree *bst) { return bst->root; } /* Search node */ TreeNode *search(BinarySearchTree *bst, int num) { TreeNode *cur = bst->root; // Loop search, exit after passing leaf node while (cur != NULL) { if (cur->val < num) { // Target node is in cur's right subtree cur = cur->right; } else if (cur->val > num) { // Target node is in cur's left subtree cur = cur->left; } else { // Found target node, exit loop break; } } // Return target node return cur; } /* Insert node */ void insert(BinarySearchTree *bst, int num) { // If tree is empty, initialize root node if (bst->root == NULL) { bst->root = newTreeNode(num); return; } TreeNode *cur = bst->root, *pre = NULL; // Loop search, exit after passing leaf node while (cur != NULL) { // Found duplicate node, return directly if (cur->val == num) { return; } pre = cur; if (cur->val < num) { // Insertion position is in cur's right subtree cur = cur->right; } else { // Insertion position is in cur's left subtree cur = cur->left; } } // Insert node TreeNode *node = newTreeNode(num); if (pre->val < num) { pre->right = node; } else { pre->left = node; } } /* Remove node */ // Cannot use remove keyword here due to stdio.h inclusion void removeItem(BinarySearchTree *bst, int num) { // If tree is empty, return directly if (bst->root == NULL) return; TreeNode *cur = bst->root, *pre = NULL; // Loop search, exit after passing leaf node while (cur != NULL) { // Found node to delete, exit loop if (cur->val == num) break; pre = cur; if (cur->val < num) { // Node to delete is in right subtree of root cur = cur->right; } else { // Node to delete is in left subtree of root cur = cur->left; } } // If no node to delete, return directly if (cur == NULL) return; // Check if node to delete has children if (cur->left == NULL || cur->right == NULL) { /* Number of child nodes = 0 or 1 */ // When number of child nodes = 0 / 1, child = nullptr / that child node TreeNode *child = cur->left != NULL ? cur->left : cur->right; // Delete node cur if (pre->left == cur) { pre->left = child; } else { pre->right = child; } // Free memory free(cur); } else { /* Number of child nodes = 2 */ // Get next node of cur in inorder traversal TreeNode *tmp = cur->right; while (tmp->left != NULL) { tmp = tmp->left; } int tmpVal = tmp->val; // Recursively delete node tmp removeItem(bst, tmp->val); // Replace cur with tmp cur->val = tmpVal; } } /* Driver Code */ int main() { /* Initialize binary search tree */ int nums[] = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; BinarySearchTree *bst = newBinarySearchTree(); for (int i = 0; i < sizeof(nums) / sizeof(int); i++) { insert(bst, nums[i]); } printf("Initialized binary tree is\n"); printTree(getRoot(bst)); /* Search node */ TreeNode *node = search(bst, 7); printf("Found node object value = %d\n", node->val); /* Insert node */ insert(bst, 16); printf("After inserting node 16, binary tree is\n"); printTree(getRoot(bst)); /* Remove node */ removeItem(bst, 1); printf("After removing node 1, binary tree is\n"); printTree(getRoot(bst)); removeItem(bst, 2); printf("After removing node 2, binary tree is\n"); printTree(getRoot(bst)); removeItem(bst, 4); printf("After removing node 4, binary tree is\n"); printTree(getRoot(bst)); // Free memory delBinarySearchTree(bst); return 0; } ================================================ FILE: en/codes/c/chapter_tree/binary_tree.c ================================================ /** * File: binary_tree.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Driver Code */ int main() { /* Initialize binary tree */ // Initialize nodes TreeNode *n1 = newTreeNode(1); TreeNode *n2 = newTreeNode(2); TreeNode *n3 = newTreeNode(3); TreeNode *n4 = newTreeNode(4); TreeNode *n5 = newTreeNode(5); // Build references (pointers) between nodes n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; printf("Initialize binary tree\n"); printTree(n1); /* Insert node P between n1 -> n2 */ TreeNode *P = newTreeNode(0); // Delete node n1->left = P; P->left = n2; printf("After inserting node P\n"); printTree(n1); // Remove node P n1->left = n2; // Free memory free(P); printf("After removing node P\n"); printTree(n1); freeMemoryTree(n1); return 0; } ================================================ FILE: en/codes/c/chapter_tree/binary_tree_bfs.c ================================================ /** * File: binary_tree_bfs.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 100 /* Level-order traversal */ int *levelOrder(TreeNode *root, int *size) { /* Auxiliary queue */ int front, rear; int index, *arr; TreeNode *node; TreeNode **queue; /* Auxiliary queue */ queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE); // Queue pointer front = 0, rear = 0; // Add root node queue[rear++] = root; // Initialize a list to save the traversal sequence /* Auxiliary array */ arr = (int *)malloc(sizeof(int) * MAX_SIZE); // Array pointer index = 0; while (front < rear) { // Dequeue node = queue[front++]; // Save node value arr[index++] = node->val; if (node->left != NULL) { // Left child node enqueue queue[rear++] = node->left; } if (node->right != NULL) { // Right child node enqueue queue[rear++] = node->right; } } // Update array length value *size = index; arr = realloc(arr, sizeof(int) * (*size)); // Free auxiliary array space free(queue); return arr; } /* Driver Code */ int main() { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array int nums[] = {1, 2, 3, 4, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); printf("Initialize binary tree\n"); printTree(root); /* Level-order traversal */ // Need to pass array length int *arr = levelOrder(root, &size); printf("Level-order traversal node print sequence = "); printArray(arr, size); // Free memory freeMemoryTree(root); free(arr); return 0; } ================================================ FILE: en/codes/c/chapter_tree/binary_tree_dfs.c ================================================ /** * File: binary_tree_dfs.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 100 // Auxiliary array for storing traversal sequence int arr[MAX_SIZE]; /* Preorder traversal */ void preOrder(TreeNode *root, int *size) { if (root == NULL) return; // Visit priority: root node -> left subtree -> right subtree arr[(*size)++] = root->val; preOrder(root->left, size); preOrder(root->right, size); } /* Inorder traversal */ void inOrder(TreeNode *root, int *size) { if (root == NULL) return; // Visit priority: left subtree -> root node -> right subtree inOrder(root->left, size); arr[(*size)++] = root->val; inOrder(root->right, size); } /* Postorder traversal */ void postOrder(TreeNode *root, int *size) { if (root == NULL) return; // Visit priority: left subtree -> right subtree -> root node postOrder(root->left, size); postOrder(root->right, size); arr[(*size)++] = root->val; } /* Driver Code */ int main() { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array int nums[] = {1, 2, 3, 4, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); printf("Initialize binary tree\n"); printTree(root); /* Preorder traversal */ // Initialize auxiliary array size = 0; preOrder(root, &size); printf("Pre-order traversal node print sequence = "); printArray(arr, size); /* Inorder traversal */ size = 0; inOrder(root, &size); printf("In-order traversal node print sequence = "); printArray(arr, size); /* Postorder traversal */ size = 0; postOrder(root, &size); printf("Post-order traversal node print sequence = "); printArray(arr, size); freeMemoryTree(root); return 0; } ================================================ FILE: en/codes/c/utils/CMakeLists.txt ================================================ add_executable(utils common_test.c common.h print_util.h list_node.h tree_node.h uthash.h) ================================================ FILE: en/codes/c/utils/common.h ================================================ /** * File: common.h * Created Time: 2022-12-20 * Author: MolDuM (moldum@163.com)、Reanon (793584285@qq.com) */ #ifndef COMMON_H #define COMMON_H #include #include #include #include #include #include #include #include "list_node.h" #include "print_util.h" #include "tree_node.h" #include "vertex.h" // hash table lib #include "uthash.h" #include "vector.h" #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif #endif // COMMON_H ================================================ FILE: en/codes/c/utils/common_test.c ================================================ /** * File: include_test.c * Created Time: 2023-01-10 * Author: Reanon (793584285@qq.com) */ #include "common.h" void testListNode() { int nums[] = {2, 3, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); ListNode *head = arrToLinkedList(nums, size); printLinkedList(head); } void testTreeNode() { int nums[] = {1, 2, 3, INT_MAX, 5, 6, INT_MAX}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); // print tree printTree(root); // tree to arr int *arr = treeToArray(root, &size); printArray(arr, size); } int main(int argc, char *argv[]) { printf("==testListNode==\n"); testListNode(); printf("==testTreeNode==\n"); testTreeNode(); return 0; } ================================================ FILE: en/codes/c/utils/list_node.h ================================================ /** * File: list_node.h * Created Time: 2023-01-09 * Author: Reanon (793584285@qq.com) */ #ifndef LIST_NODE_H #define LIST_NODE_H #ifdef __cplusplus extern "C" { #endif /* Linked list node structure */ typedef struct ListNode { int val; // Node value struct ListNode *next; // Reference to next node } ListNode; /* Constructor, initialize a new node */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *)malloc(sizeof(ListNode)); node->val = val; node->next = NULL; return node; } /* Deserialize array to linked list */ ListNode *arrToLinkedList(const int *arr, size_t size) { if (size <= 0) { return NULL; } ListNode *dummy = newListNode(0); ListNode *node = dummy; for (int i = 0; i < size; i++) { node->next = newListNode(arr[i]); node = node->next; } return dummy->next; } /* Free memory allocated to linked list */ void freeMemoryLinkedList(ListNode *cur) { // Free memory ListNode *pre; while (cur != NULL) { pre = cur; cur = cur->next; free(pre); } } #ifdef __cplusplus } #endif #endif // LIST_NODE_H ================================================ FILE: en/codes/c/utils/print_util.h ================================================ /** * File: print_util.h * Created Time: 2022-12-21 * Author: MolDum (moldum@163.com), Reanon (793584285@qq.com) */ #ifndef PRINT_UTIL_H #define PRINT_UTIL_H #include #include #include #include "list_node.h" #include "tree_node.h" #ifdef __cplusplus extern "C" { #endif /* Print array */ void printArray(int arr[], int size) { if (arr == NULL || size == 0) { printf("[]"); return; } printf("["); for (int i = 0; i < size - 1; i++) { printf("%d, ", arr[i]); } printf("%d]\n", arr[size - 1]); } /* Print array */ void printArrayFloat(float arr[], int size) { if (arr == NULL || size == 0) { printf("[]"); return; } printf("["); for (int i = 0; i < size - 1; i++) { printf("%.2f, ", arr[i]); } printf("%.2f]\n", arr[size - 1]); } /* Print linked list */ void printLinkedList(ListNode *node) { if (node == NULL) { return; } while (node->next != NULL) { printf("%d -> ", node->val); node = node->next; } printf("%d\n", node->val); } typedef struct Trunk { struct Trunk *prev; char *str; } Trunk; Trunk *newTrunk(Trunk *prev, char *str) { Trunk *trunk = (Trunk *)malloc(sizeof(Trunk)); trunk->prev = prev; trunk->str = (char *)malloc(sizeof(char) * 10); strcpy(trunk->str, str); return trunk; } void showTrunks(Trunk *trunk) { if (trunk == NULL) { return; } showTrunks(trunk->prev); printf("%s", trunk->str); } /** * Print binary tree * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTreeHelper(TreeNode *node, Trunk *prev, bool isRight) { if (node == NULL) { return; } char *prev_str = " "; Trunk *trunk = newTrunk(prev, prev_str); printTreeHelper(node->right, trunk, true); if (prev == NULL) { trunk->str = "———"; } else if (isRight) { trunk->str = "/———"; prev_str = " |"; } else { trunk->str = "\\———"; prev->str = prev_str; } showTrunks(trunk); printf("%d\n", node->val); if (prev != NULL) { prev->str = prev_str; } trunk->str = " |"; printTreeHelper(node->left, trunk, false); } /* Print binary tree */ void printTree(TreeNode *root) { printTreeHelper(root, NULL, false); } /* Print heap */ void printHeap(int arr[], int size) { TreeNode *root; printf("Heap array representation:"); printArray(arr, size); printf("Heap tree representation:\n"); root = arrayToTree(arr, size); printTree(root); } #ifdef __cplusplus } #endif #endif // PRINT_UTIL_H ================================================ FILE: en/codes/c/utils/tree_node.h ================================================ /** * File: tree_node.h * Created Time: 2023-01-09 * Author: Reanon (793584285@qq.com) */ #ifndef TREE_NODE_H #define TREE_NODE_H #ifdef __cplusplus extern "C" { #endif #include #define MAX_NODE_SIZE 5000 /* Binary tree node structure */ typedef struct TreeNode { int val; // Node value int height; // Node height struct TreeNode *left; // Left child pointer struct TreeNode *right; // Right child pointer } TreeNode; /* Constructor */ TreeNode *newTreeNode(int val) { TreeNode *node; node = (TreeNode *)malloc(sizeof(TreeNode)); node->val = val; node->height = 0; node->left = NULL; node->right = NULL; return node; } // For the serialization encoding rules, please refer to: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Array representation of binary tree: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // Linked list representation of binary tree: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* Deserialize a list into a binary tree: recursion */ TreeNode *arrayToTreeDFS(int *arr, int size, int i) { if (i < 0 || i >= size || arr[i] == INT_MAX) { return NULL; } TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); root->val = arr[i]; root->left = arrayToTreeDFS(arr, size, 2 * i + 1); root->right = arrayToTreeDFS(arr, size, 2 * i + 2); return root; } /* Deserialize a list into a binary tree */ TreeNode *arrayToTree(int *arr, int size) { return arrayToTreeDFS(arr, size, 0); } /* Serialize a binary tree into a list: recursion */ void treeToArrayDFS(TreeNode *root, int i, int *res, int *size) { if (root == NULL) { return; } while (i >= *size) { res = realloc(res, (*size + 1) * sizeof(int)); res[*size] = INT_MAX; (*size)++; } res[i] = root->val; treeToArrayDFS(root->left, 2 * i + 1, res, size); treeToArrayDFS(root->right, 2 * i + 2, res, size); } /* Serialize a binary tree into a list */ int *treeToArray(TreeNode *root, int *size) { *size = 0; int *res = NULL; treeToArrayDFS(root, 0, res, size); return res; } /* Free binary tree memory */ void freeMemoryTree(TreeNode *root) { if (root == NULL) return; freeMemoryTree(root->left); freeMemoryTree(root->right); free(root); } #ifdef __cplusplus } #endif #endif // TREE_NODE_H ================================================ FILE: en/codes/c/utils/uthash.h ================================================ /* Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTHASH_H #define UTHASH_H #define UTHASH_VERSION 2.3.0 #include /* memcmp, memset, strlen */ #include /* ptrdiff_t */ #include /* exit */ #if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT /* This codepath is provided for backward compatibility, but I plan to remove it. */ #warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" typedef unsigned int uint32_t; typedef unsigned char uint8_t; #elif defined(HASH_NO_STDINT) && HASH_NO_STDINT #else #include /* uint8_t, uint32_t */ #endif /* These macros use decltype or the earlier __typeof GNU extension. As decltype is only available in newer compilers (VS2010 or gcc 4.3+ when compiling c++ source) this code uses whatever method is needed or, for VS2008 where neither is available, uses casting workarounds. */ #if !defined(DECLTYPE) && !defined(NO_DECLTYPE) #if defined(_MSC_VER) /* MS compiler */ #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ #define DECLTYPE(x) (decltype(x)) #else /* VS2008 or older (or VS2010 in C mode) */ #define NO_DECLTYPE #endif #elif defined(__MCST__) /* Elbrus C Compiler */ #define DECLTYPE(x) (__typeof(x)) #elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) #define NO_DECLTYPE #else /* GNU, Sun and other compilers */ #define DECLTYPE(x) (__typeof(x)) #endif #endif #ifdef NO_DECLTYPE #define DECLTYPE(x) #define DECLTYPE_ASSIGN(dst,src) \ do { \ char **_da_dst = (char**)(&(dst)); \ *_da_dst = (char*)(src); \ } while (0) #else #define DECLTYPE_ASSIGN(dst,src) \ do { \ (dst) = DECLTYPE(dst)(src); \ } while (0) #endif #ifndef uthash_malloc #define uthash_malloc(sz) malloc(sz) /* malloc fcn */ #endif #ifndef uthash_free #define uthash_free(ptr,sz) free(ptr) /* free fcn */ #endif #ifndef uthash_bzero #define uthash_bzero(a,n) memset(a,'\0',n) #endif #ifndef uthash_strlen #define uthash_strlen(s) strlen(s) #endif #ifndef HASH_FUNCTION #define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) #endif #ifndef HASH_KEYCMP #define HASH_KEYCMP(a,b,n) memcmp(a,b,n) #endif #ifndef uthash_noexpand_fyi #define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ #endif #ifndef uthash_expand_fyi #define uthash_expand_fyi(tbl) /* can be defined to log expands */ #endif #ifndef HASH_NONFATAL_OOM #define HASH_NONFATAL_OOM 0 #endif #if HASH_NONFATAL_OOM /* malloc failures can be recovered from */ #ifndef uthash_nonfatal_oom #define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) #define IF_HASH_NONFATAL_OOM(x) x #else /* malloc failures result in lost memory, hash tables are unusable */ #ifndef uthash_fatal #define uthash_fatal(msg) exit(-1) /* fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") #define IF_HASH_NONFATAL_OOM(x) #endif /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ #define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ /* calculate the element whose hash handle address is hhp */ #define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) /* calculate the hash handle from element address elp */ #define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) #define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ do { \ struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ unsigned _hd_bkt; \ HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ (head)->hh.tbl->buckets[_hd_bkt].count++; \ _hd_hh_item->hh_next = NULL; \ _hd_hh_item->hh_prev = NULL; \ } while (0) #define HASH_VALUE(keyptr,keylen,hashv) \ do { \ HASH_FUNCTION(keyptr, keylen, hashv); \ } while (0) #define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_bkt; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ } \ } \ } while (0) #define HASH_FIND(hh,head,keyptr,keylen,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_hashv; \ HASH_VALUE(keyptr, keylen, _hf_hashv); \ HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ } \ } while (0) #ifdef HASH_BLOOM #define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) #define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) #define HASH_BLOOM_MAKE(tbl,oomed) \ do { \ (tbl)->bloom_nbits = HASH_BLOOM; \ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ if (!(tbl)->bloom_bv) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ } \ } while (0) #define HASH_BLOOM_FREE(tbl) \ do { \ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ } while (0) #define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) #define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) #define HASH_BLOOM_ADD(tbl,hashv) \ HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #define HASH_BLOOM_TEST(tbl,hashv) \ HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #else #define HASH_BLOOM_MAKE(tbl,oomed) #define HASH_BLOOM_FREE(tbl) #define HASH_BLOOM_ADD(tbl,hashv) #define HASH_BLOOM_TEST(tbl,hashv) (1) #define HASH_BLOOM_BYTELEN 0U #endif #define HASH_MAKE_TABLE(hh,head,oomed) \ do { \ (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ if (!(head)->hh.tbl) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ (head)->hh.tbl->tail = &((head)->hh); \ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ (head)->hh.tbl->signature = HASH_SIGNATURE; \ if (!(head)->hh.tbl->buckets) { \ HASH_RECORD_OOM(oomed); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } else { \ uthash_bzero((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ uthash_free((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } \ ) \ } \ } \ } while (0) #define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ } while (0) #define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ } while (0) #define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ } while (0) #define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ } while (0) #define HASH_APPEND_LIST(hh, head, add) \ do { \ (add)->hh.next = NULL; \ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ (head)->hh.tbl->tail->next = (add); \ (head)->hh.tbl->tail = &((add)->hh); \ } while (0) #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ do { \ if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ break; \ } \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #ifdef NO_DECLTYPE #undef HASH_AKBI_INNER_LOOP #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ char *_hs_saved_head = (char*)(head); \ do { \ DECLTYPE_ASSIGN(head, _hs_iter); \ if (cmpfcn(head, add) > 0) { \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ break; \ } \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #endif #if HASH_NONFATAL_OOM #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ if (!(oomed)) { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ if (oomed) { \ HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ HASH_DELETE_HH(hh, head, &(add)->hh); \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } else { \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } \ } else { \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } \ } while (0) #else #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } while (0) #endif #define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (char*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ void *_hs_iter = (head); \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ if (_hs_iter) { \ (add)->hh.next = _hs_iter; \ if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ } else { \ (head) = (add); \ } \ HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ } else { \ HASH_APPEND_LIST(hh, head, add); \ } \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ } while (0) #define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ do { \ unsigned _hs_hashv; \ HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ } while (0) #define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) #define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) #define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (const void*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_APPEND_LIST(hh, head, add); \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ } while (0) #define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ do { \ unsigned _ha_hashv; \ HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ } while (0) #define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) #define HASH_ADD(hh,head,fieldname,keylen_in,add) \ HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) #define HASH_TO_BKT(hashv,num_bkts,bkt) \ do { \ bkt = ((hashv) & ((num_bkts) - 1U)); \ } while (0) /* delete "delptr" from the hash table. * "the usual" patch-up process for the app-order doubly-linked-list. * The use of _hd_hh_del below deserves special explanation. * These used to be expressed using (delptr) but that led to a bug * if someone used the same symbol for the head and deletee, like * HASH_DELETE(hh,users,users); * We want that to work, but by changing the head (users) below * we were forfeiting our ability to further refer to the deletee (users) * in the patch-up process. Solution: use scratch space to * copy the deletee pointer, then the latter references are via that * scratch pointer rather than through the repointed (users) symbol. */ #define HASH_DELETE(hh,head,delptr) \ HASH_DELETE_HH(hh, head, &(delptr)->hh) #define HASH_DELETE_HH(hh,head,delptrhh) \ do { \ const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } else { \ unsigned _hd_bkt; \ if (_hd_hh_del == (head)->hh.tbl->tail) { \ (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ } \ if (_hd_hh_del->prev != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ } else { \ DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ } \ if (_hd_hh_del->next != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ } \ HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ (head)->hh.tbl->num_items--; \ } \ HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ } while (0) /* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ #define HASH_FIND_STR(head,findstr,out) \ do { \ unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ } while (0) #define HASH_ADD_STR(head,strfield,add) \ do { \ unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ } while (0) #define HASH_REPLACE_STR(head,strfield,add,replaced) \ do { \ unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ } while (0) #define HASH_FIND_INT(head,findint,out) \ HASH_FIND(hh,head,findint,sizeof(int),out) #define HASH_ADD_INT(head,intfield,add) \ HASH_ADD(hh,head,intfield,sizeof(int),add) #define HASH_REPLACE_INT(head,intfield,add,replaced) \ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) #define HASH_FIND_PTR(head,findptr,out) \ HASH_FIND(hh,head,findptr,sizeof(void *),out) #define HASH_ADD_PTR(head,ptrfield,add) \ HASH_ADD(hh,head,ptrfield,sizeof(void *),add) #define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) #define HASH_DEL(head,delptr) \ HASH_DELETE(hh,head,delptr) /* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. */ #ifdef HASH_DEBUG #include /* fprintf, stderr */ #define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) #define HASH_FSCK(hh,head,where) \ do { \ struct UT_hash_handle *_thh; \ if (head) { \ unsigned _bkt_i; \ unsigned _count = 0; \ char *_prev; \ for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ unsigned _bkt_count = 0; \ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ _prev = NULL; \ while (_thh) { \ if (_prev != (char*)(_thh->hh_prev)) { \ HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ (where), (void*)_thh->hh_prev, (void*)_prev); \ } \ _bkt_count++; \ _prev = (char*)(_thh); \ _thh = _thh->hh_next; \ } \ _count += _bkt_count; \ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ } \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ _count = 0; \ _prev = NULL; \ _thh = &(head)->hh; \ while (_thh) { \ _count++; \ if (_prev != (char*)_thh->prev) { \ HASH_OOPS("%s: invalid prev %p, actual %p\n", \ (where), (void*)_thh->prev, (void*)_prev); \ } \ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ } \ } while (0) #else #define HASH_FSCK(hh,head,where) #endif /* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to * the descriptor to which this macro is defined for tuning the hash function. * The app can #include to get the prototype for write(2). */ #ifdef HASH_EMIT_KEYS #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ do { \ unsigned _klen = fieldlen; \ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ } while (0) #else #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) #endif /* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ #define HASH_BER(key,keylen,hashv) \ do { \ unsigned _hb_keylen = (unsigned)keylen; \ const unsigned char *_hb_key = (const unsigned char*)(key); \ (hashv) = 0; \ while (_hb_keylen-- != 0U) { \ (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ } \ } while (0) /* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx * (archive link: https://archive.is/Ivcan ) */ #define HASH_SAX(key,keylen,hashv) \ do { \ unsigned _sx_i; \ const unsigned char *_hs_key = (const unsigned char*)(key); \ hashv = 0; \ for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ } \ } while (0) /* FNV-1a variation */ #define HASH_FNV(key,keylen,hashv) \ do { \ unsigned _fn_i; \ const unsigned char *_hf_key = (const unsigned char*)(key); \ (hashv) = 2166136261U; \ for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ hashv = hashv ^ _hf_key[_fn_i]; \ hashv = hashv * 16777619U; \ } \ } while (0) #define HASH_OAT(key,keylen,hashv) \ do { \ unsigned _ho_i; \ const unsigned char *_ho_key=(const unsigned char*)(key); \ hashv = 0; \ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ hashv += _ho_key[_ho_i]; \ hashv += (hashv << 10); \ hashv ^= (hashv >> 6); \ } \ hashv += (hashv << 3); \ hashv ^= (hashv >> 11); \ hashv += (hashv << 15); \ } while (0) #define HASH_JEN_MIX(a,b,c) \ do { \ a -= b; a -= c; a ^= ( c >> 13 ); \ b -= c; b -= a; b ^= ( a << 8 ); \ c -= a; c -= b; c ^= ( b >> 13 ); \ a -= b; a -= c; a ^= ( c >> 12 ); \ b -= c; b -= a; b ^= ( a << 16 ); \ c -= a; c -= b; c ^= ( b >> 5 ); \ a -= b; a -= c; a ^= ( c >> 3 ); \ b -= c; b -= a; b ^= ( a << 10 ); \ c -= a; c -= b; c ^= ( b >> 15 ); \ } while (0) #define HASH_JEN(key,keylen,hashv) \ do { \ unsigned _hj_i,_hj_j,_hj_k; \ unsigned const char *_hj_key=(unsigned const char*)(key); \ hashv = 0xfeedbeefu; \ _hj_i = _hj_j = 0x9e3779b9u; \ _hj_k = (unsigned)(keylen); \ while (_hj_k >= 12U) { \ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + ( (unsigned)_hj_key[2] << 16 ) \ + ( (unsigned)_hj_key[3] << 24 ) ); \ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + ( (unsigned)_hj_key[6] << 16 ) \ + ( (unsigned)_hj_key[7] << 24 ) ); \ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + ( (unsigned)_hj_key[10] << 16 ) \ + ( (unsigned)_hj_key[11] << 24 ) ); \ \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ \ _hj_key += 12; \ _hj_k -= 12U; \ } \ hashv += (unsigned)(keylen); \ switch ( _hj_k ) { \ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ default: ; \ } \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ } while (0) /* The Paul Hsieh hash function */ #undef get16bits #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) #define get16bits(d) (*((const uint16_t *) (d))) #endif #if !defined (get16bits) #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif #define HASH_SFH(key,keylen,hashv) \ do { \ unsigned const char *_sfh_key=(unsigned const char*)(key); \ uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ \ unsigned _sfh_rem = _sfh_len & 3U; \ _sfh_len >>= 2; \ hashv = 0xcafebabeu; \ \ /* Main loop */ \ for (;_sfh_len > 0U; _sfh_len--) { \ hashv += get16bits (_sfh_key); \ _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ hashv = (hashv << 16) ^ _sfh_tmp; \ _sfh_key += 2U*sizeof (uint16_t); \ hashv += hashv >> 11; \ } \ \ /* Handle end cases */ \ switch (_sfh_rem) { \ case 3: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 16; \ hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ hashv += hashv >> 11; \ break; \ case 2: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 11; \ hashv += hashv >> 17; \ break; \ case 1: hashv += *_sfh_key; \ hashv ^= hashv << 10; \ hashv += hashv >> 1; \ break; \ default: ; \ } \ \ /* Force "avalanching" of final 127 bits */ \ hashv ^= hashv << 3; \ hashv += hashv >> 5; \ hashv ^= hashv << 4; \ hashv += hashv >> 17; \ hashv ^= hashv << 25; \ hashv += hashv >> 6; \ } while (0) /* iterate over items in a known bucket to find desired item */ #define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ do { \ if ((head).hh_head != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ } else { \ (out) = NULL; \ } \ while ((out) != NULL) { \ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ break; \ } \ } \ if ((out)->hh.hh_next != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ } else { \ (out) = NULL; \ } \ } \ } while (0) /* add an item to a bucket */ #define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ do { \ UT_hash_bucket *_ha_head = &(head); \ _ha_head->count++; \ (addhh)->hh_next = _ha_head->hh_head; \ (addhh)->hh_prev = NULL; \ if (_ha_head->hh_head != NULL) { \ _ha_head->hh_head->hh_prev = (addhh); \ } \ _ha_head->hh_head = (addhh); \ if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ && !(addhh)->tbl->noexpand) { \ HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ HASH_DEL_IN_BKT(head,addhh); \ } \ ) \ } \ } while (0) /* remove an item from a given bucket */ #define HASH_DEL_IN_BKT(head,delhh) \ do { \ UT_hash_bucket *_hd_head = &(head); \ _hd_head->count--; \ if (_hd_head->hh_head == (delhh)) { \ _hd_head->hh_head = (delhh)->hh_next; \ } \ if ((delhh)->hh_prev) { \ (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ } \ if ((delhh)->hh_next) { \ (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ } \ } while (0) /* Bucket expansion has the effect of doubling the number of buckets * and redistributing the items into the new buckets. Ideally the * items will distribute more or less evenly into the new buckets * (the extent to which this is true is a measure of the quality of * the hash function as it applies to the key domain). * * With the items distributed into more buckets, the chain length * (item count) in each bucket is reduced. Thus by expanding buckets * the hash keeps a bound on the chain length. This bounded chain * length is the essence of how a hash provides constant time lookup. * * The calculation of tbl->ideal_chain_maxlen below deserves some * explanation. First, keep in mind that we're calculating the ideal * maximum chain length based on the *new* (doubled) bucket count. * In fractions this is just n/b (n=number of items,b=new num buckets). * Since the ideal chain length is an integer, we want to calculate * ceil(n/b). We don't depend on floating point arithmetic in this * hash, so to calculate ceil(n/b) with integers we could write * * ceil(n/b) = (n/b) + ((n%b)?1:0) * * and in fact a previous version of this hash did just that. * But now we have improved things a bit by recognizing that b is * always a power of two. We keep its base 2 log handy (call it lb), * so now we can write this with a bit shift and logical AND: * * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) * */ #define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ do { \ unsigned _he_bkt; \ unsigned _he_bkt_i; \ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ if (!_he_new_buckets) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero(_he_new_buckets, \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ (tbl)->ideal_chain_maxlen = \ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ (tbl)->nonideal_items = 0; \ for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ while (_he_thh != NULL) { \ _he_hh_nxt = _he_thh->hh_next; \ HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ _he_newbkt = &(_he_new_buckets[_he_bkt]); \ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ (tbl)->nonideal_items++; \ if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ _he_newbkt->expand_mult++; \ } \ } \ _he_thh->hh_prev = NULL; \ _he_thh->hh_next = _he_newbkt->hh_head; \ if (_he_newbkt->hh_head != NULL) { \ _he_newbkt->hh_head->hh_prev = _he_thh; \ } \ _he_newbkt->hh_head = _he_thh; \ _he_thh = _he_hh_nxt; \ } \ } \ uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ (tbl)->num_buckets *= 2U; \ (tbl)->log2_num_buckets++; \ (tbl)->buckets = _he_new_buckets; \ (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ ((tbl)->ineff_expands+1U) : 0U; \ if ((tbl)->ineff_expands > 1U) { \ (tbl)->noexpand = 1; \ uthash_noexpand_fyi(tbl); \ } \ uthash_expand_fyi(tbl); \ } \ } while (0) /* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ /* Note that HASH_SORT assumes the hash handle name to be hh. * HASH_SRT was added to allow the hash handle name to be passed in. */ #define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) #define HASH_SRT(hh,head,cmpfcn) \ do { \ unsigned _hs_i; \ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ if (head != NULL) { \ _hs_insize = 1; \ _hs_looping = 1; \ _hs_list = &((head)->hh); \ while (_hs_looping != 0U) { \ _hs_p = _hs_list; \ _hs_list = NULL; \ _hs_tail = NULL; \ _hs_nmerges = 0; \ while (_hs_p != NULL) { \ _hs_nmerges++; \ _hs_q = _hs_p; \ _hs_psize = 0; \ for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ _hs_psize++; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ if (_hs_q == NULL) { \ break; \ } \ } \ _hs_qsize = _hs_insize; \ while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ if (_hs_psize == 0U) { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else if ((cmpfcn( \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ )) <= 0) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } \ if ( _hs_tail != NULL ) { \ _hs_tail->next = ((_hs_e != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ } else { \ _hs_list = _hs_e; \ } \ if (_hs_e != NULL) { \ _hs_e->prev = ((_hs_tail != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ } \ _hs_tail = _hs_e; \ } \ _hs_p = _hs_q; \ } \ if (_hs_tail != NULL) { \ _hs_tail->next = NULL; \ } \ if (_hs_nmerges <= 1U) { \ _hs_looping = 0; \ (head)->hh.tbl->tail = _hs_tail; \ DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ } \ _hs_insize *= 2U; \ } \ HASH_FSCK(hh, head, "HASH_SRT"); \ } \ } while (0) /* This function selects items from one hash into another hash. * The end result is that the selected items have dual presence * in both hashes. There is no copy of the items made; rather * they are added into the new hash through a secondary hash * hash handle that must be present in the structure. */ #define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ do { \ unsigned _src_bkt, _dst_bkt; \ void *_last_elt = NULL, *_elt; \ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ if ((src) != NULL) { \ for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ _src_hh != NULL; \ _src_hh = _src_hh->hh_next) { \ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ if (cond(_elt)) { \ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ _dst_hh->key = _src_hh->key; \ _dst_hh->keylen = _src_hh->keylen; \ _dst_hh->hashv = _src_hh->hashv; \ _dst_hh->prev = _last_elt; \ _dst_hh->next = NULL; \ if (_last_elt_hh != NULL) { \ _last_elt_hh->next = _elt; \ } \ if ((dst) == NULL) { \ DECLTYPE_ASSIGN(dst, _elt); \ HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ uthash_nonfatal_oom(_elt); \ (dst) = NULL; \ continue; \ } \ ) \ } else { \ _dst_hh->tbl = (dst)->hh_dst.tbl; \ } \ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ (dst)->hh_dst.tbl->num_items++; \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ _dst_hh->tbl = NULL; \ uthash_nonfatal_oom(_elt); \ continue; \ } \ ) \ HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ _last_elt = _elt; \ _last_elt_hh = _dst_hh; \ } \ } \ } \ } \ HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ } while (0) #define HASH_CLEAR(hh,head) \ do { \ if ((head) != NULL) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } \ } while (0) #define HASH_OVERHEAD(hh,head) \ (((head) != NULL) ? ( \ (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ sizeof(UT_hash_table) + \ (HASH_BLOOM_BYTELEN))) : 0U) #ifdef NO_DECLTYPE #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) #else #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) #endif /* obtain a count of items in the hash */ #define HASH_COUNT(head) HASH_CNT(hh,head) #define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) typedef struct UT_hash_bucket { struct UT_hash_handle *hh_head; unsigned count; /* expand_mult is normally set to 0. In this situation, the max chain length * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If * the bucket's chain exceeds this length, bucket expansion is triggered). * However, setting expand_mult to a non-zero value delays bucket expansion * (that would be triggered by additions to this particular bucket) * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. * (The multiplier is simply expand_mult+1). The whole idea of this * multiplier is to reduce bucket expansions, since they are expensive, in * situations where we know that a particular bucket tends to be overused. * It is better to let its chain length grow to a longer yet-still-bounded * value, than to do an O(n) bucket expansion too often. */ unsigned expand_mult; } UT_hash_bucket; /* random signature used only to find hash tables in external analysis */ #define HASH_SIGNATURE 0xa0111fe1u #define HASH_BLOOM_SIGNATURE 0xb12220f2u typedef struct UT_hash_table { UT_hash_bucket *buckets; unsigned num_buckets, log2_num_buckets; unsigned num_items; struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ /* in an ideal situation (all buckets used equally), no bucket would have * more than ceil(#items/#buckets) items. that's the ideal chain length. */ unsigned ideal_chain_maxlen; /* nonideal_items is the number of items in the hash whose chain position * exceeds the ideal chain maxlen. these items pay the penalty for an uneven * hash distribution; reaching them in a chain traversal takes >ideal steps */ unsigned nonideal_items; /* ineffective expands occur when a bucket doubling was performed, but * afterward, more than half the items in the hash had nonideal chain * positions. If this happens on two consecutive expansions we inhibit any * further expansion, as it's not helping; this happens when the hash * function isn't a good fit for the key domain. When expansion is inhibited * the hash will still work, albeit no longer in constant time. */ unsigned ineff_expands, noexpand; uint32_t signature; /* used only to find hash tables in external analysis */ #ifdef HASH_BLOOM uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ uint8_t *bloom_bv; uint8_t bloom_nbits; #endif } UT_hash_table; typedef struct UT_hash_handle { struct UT_hash_table *tbl; void *prev; /* prev element in app order */ void *next; /* next element in app order */ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ struct UT_hash_handle *hh_next; /* next hh in bucket order */ const void *key; /* ptr to enclosing struct's key */ unsigned keylen; /* enclosing struct's key len */ unsigned hashv; /* result of hash-fcn(key) */ } UT_hash_handle; #endif /* UTHASH_H */ ================================================ FILE: en/codes/c/utils/vector.h ================================================ /** * File: vector.h * Created Time: 2023-07-13 * Author: Zuoxun (845242523@qq.com)、Gonglja (glj0@outlook.com) */ #ifndef VECTOR_H #define VECTOR_H #ifdef __cplusplus extern "C" { #endif /* Define vector type */ typedef struct vector { int size; // Current vector size int capacity; // Current vector capacity int depth; // Current vector depth void **data; // Pointer array to data } vector; /* Construct vector */ vector *newVector() { vector *v = malloc(sizeof(vector)); v->size = 0; v->capacity = 4; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); return v; } /* Construct vector with specified size and default value */ vector *_newVector(int size, void *elem, int elemSize) { vector *v = malloc(sizeof(vector)); v->size = size; v->capacity = size; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); for (int i = 0; i < size; i++) { void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[i] = tmp; } return v; } /* Destruct vector */ void delVector(vector *v) { if (v) { if (v->depth == 0) { return; } else if (v->depth == 1) { for (int i = 0; i < v->size; i++) { free(v->data[i]); } free(v); } else { for (int i = 0; i < v->size; i++) { delVector(v->data[i]); } v->depth--; } } } /* Add element (by copy) to vector tail */ void vectorPushback(vector *v, void *elem, int elemSize) { if (v->size == v->capacity) { v->capacity *= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[v->size++] = tmp; } /* Pop element from vector tail */ void vectorPopback(vector *v) { if (v->size != 0) { free(v->data[v->size - 1]); v->size--; } } /* Clear vector */ void vectorClear(vector *v) { delVector(v); v->size = 0; v->capacity = 4; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); } /* Get vector size */ int vectorSize(vector *v) { return v->size; } /* Get vector tail element */ void *vectorBack(vector *v) { int n = v->size; return n > 0 ? v->data[n - 1] : NULL; } /* Get vector head element */ void *vectorFront(vector *v) { return v->size > 0 ? v->data[0] : NULL; } /* Get vector element at index pos */ void *vectorAt(vector *v, int pos) { if (pos < 0 || pos >= v->size) { printf("vectorAt: out of range\n"); return NULL; } return v->data[pos]; } /* Set vector element at index pos */ void vectorSet(vector *v, int pos, void *elem, int elemSize) { if (pos < 0 || pos >= v->size) { printf("vectorSet: out of range\n"); return; } free(v->data[pos]); void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[pos] = tmp; } /* Expand vector capacity */ void vectorExpand(vector *v) { v->capacity *= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } /* Shrink vector capacity */ void vectorShrink(vector *v) { v->capacity /= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } /* Insert element at vector index pos */ void vectorInsert(vector *v, int pos, void *elem, int elemSize) { if (v->size == v->capacity) { vectorExpand(v); } for (int j = v->size; j > pos; j--) { v->data[j] = v->data[j - 1]; } void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[pos] = tmp; v->size++; } /* Delete element at vector index pos */ void vectorErase(vector *v, int pos) { if (v->size != 0) { free(v->data[pos]); for (int j = pos; j < v->size - 1; j++) { v->data[j] = v->data[j + 1]; } v->size--; } } /* Swap vector elements */ void vectorSwap(vector *v, int i, int j) { void *tmp = v->data[i]; v->data[i] = v->data[j]; v->data[j] = tmp; } /* Is vector empty */ bool vectorEmpty(vector *v) { return v->size == 0; } /* Is vector full */ bool vectorFull(vector *v) { return v->size == v->capacity; } /* Are vectors equal */ bool vectorEqual(vector *v1, vector *v2) { if (v1->size != v2->size) { printf("size not equal\n"); return false; } for (int i = 0; i < v1->size; i++) { void *a = v1->data[i]; void *b = v2->data[i]; if (memcmp(a, b, sizeof(a)) != 0) { printf("data %d not equal\n", i); return false; } } return true; } /* Sort vector internally */ void vectorSort(vector *v, int (*cmp)(const void *, const void *)) { qsort(v->data, v->size, sizeof(void *), cmp); } /* Print function, need to pass a function to print variables */ /* Currently only supports printing vector with depth 1 */ void printVector(vector *v, void (*printFunc)(vector *v, void *p)) { if (v) { if (v->depth == 0) { return; } else if (v->depth == 1) { if(v->size == 0) { printf("\n"); return; } for (int i = 0; i < v->size; i++) { if (i == 0) { printf("["); } else if (i == v->size - 1) { printFunc(v, v->data[i]); printf("]\r\n"); break; } printFunc(v, v->data[i]); printf(","); } } else { for (int i = 0; i < v->size; i++) { printVector(v->data[i], printFunc); } v->depth--; } } } /* Currently only supports printing vector with depth 2 */ void printVectorMatrix(vector *vv, void (*printFunc)(vector *v, void *p)) { printf("[\n"); for (int i = 0; i < vv->size; i++) { vector *v = (vector *)vv->data[i]; printf(" ["); for (int j = 0; j < v->size; j++) { printFunc(v, v->data[j]); if (j != v->size - 1) printf(","); } printf("],"); printf("\n"); } printf("]\n"); } #ifdef __cplusplus } #endif #endif // VECTOR_H ================================================ FILE: en/codes/c/utils/vertex.h ================================================ /** * File: vertex.h * Created Time: 2023-10-28 * Author: krahets (krahets@163.com) */ #ifndef VERTEX_H #define VERTEX_H #ifdef __cplusplus extern "C" { #endif /* Vertex structure */ typedef struct { int val; } Vertex; /* Constructor, initialize a new node */ Vertex *newVertex(int val) { Vertex *vet; vet = (Vertex *)malloc(sizeof(Vertex)); vet->val = val; return vet; } /* Convert value array to vertex array */ Vertex **valsToVets(int *vals, int size) { Vertex **vertices = (Vertex **)malloc(size * sizeof(Vertex *)); for (int i = 0; i < size; ++i) { vertices[i] = newVertex(vals[i]); } return vertices; } /* Convert vertex array to value array */ int *vetsToVals(Vertex **vertices, int size) { int *vals = (int *)malloc(size * sizeof(int)); for (int i = 0; i < size; ++i) { vals[i] = vertices[i]->val; } return vals; } #ifdef __cplusplus } #endif #endif // VERTEX_H ================================================ FILE: en/codes/cpp/.gitignore ================================================ # Ignore all * # Unignore all with extensions !*.* # Unignore all dirs !*/ *.dSYM/ build/ ================================================ FILE: en/codes/cpp/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(hello_algo CXX) set(CMAKE_CXX_STANDARD 11) include_directories(./include) add_subdirectory(chapter_computational_complexity) add_subdirectory(chapter_array_and_linkedlist) add_subdirectory(chapter_stack_and_queue) add_subdirectory(chapter_hashing) add_subdirectory(chapter_tree) add_subdirectory(chapter_heap) add_subdirectory(chapter_graph) add_subdirectory(chapter_searching) add_subdirectory(chapter_sorting) add_subdirectory(chapter_divide_and_conquer) add_subdirectory(chapter_backtracking) add_subdirectory(chapter_dynamic_programming) add_subdirectory(chapter_greedy) ================================================ FILE: en/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt ================================================ add_executable(array array.cpp) add_executable(linked_list linked_list.cpp) add_executable(list list.cpp) add_executable(my_list my_list.cpp) ================================================ FILE: en/codes/cpp/chapter_array_and_linkedlist/array.cpp ================================================ /** * File: array.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Random access to element */ int randomAccess(int *nums, int size) { // Randomly select a number from interval [0, size) int randomIndex = rand() % size; // Retrieve and return the random element int randomNum = nums[randomIndex]; return randomNum; } /* Extend array length */ int *extend(int *nums, int size, int enlarge) { // Initialize an array with extended length int *res = new int[size + enlarge]; // Copy all elements from the original array to the new array for (int i = 0; i < size; i++) { res[i] = nums[i]; } // Free memory delete[] nums; // Return the extended new array return res; } /* Insert element num at index index in the array */ void insert(int *nums, int size, int num, int index) { // Move all elements at and after index index backward by one position for (int i = size - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Assign num to the element at index index nums[index] = num; } /* Remove the element at index index */ void remove(int *nums, int size, int index) { // Move all elements after index index forward by one position for (int i = index; i < size - 1; i++) { nums[i] = nums[i + 1]; } } /* Traverse array */ void traverse(int *nums, int size) { int count = 0; // Traverse array by index for (int i = 0; i < size; i++) { count += nums[i]; } } /* Find the specified element in the array */ int find(int *nums, int size, int target) { for (int i = 0; i < size; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ int main() { /* Initialize array */ 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); /* Insert element */ int randomNum = randomAccess(nums, size); cout << "Get random element in nums " << randomNum << endl; /* Traverse array */ int enlarge = 3; nums = extend(nums, size, enlarge); size += enlarge; cout << "Extend array length to 8, resulting in nums = "; printArray(nums, size); /* Insert element */ insert(nums, size, 6, 3); cout << "Insert number 6 at index 3, resulting in nums = "; printArray(nums, size); /* Remove element */ remove(nums, size, 2); cout << "Remove element at index 2, resulting in nums = "; printArray(nums, size); /* Traverse array */ traverse(nums, size); /* Find element */ int index = find(nums, size, 3); cout << "Find element 3 in nums, get index = " << index << endl; // Free memory delete[] arr; delete[] nums; return 0; } ================================================ FILE: en/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp ================================================ /** * File: linked_list.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Insert node P after node n0 in the linked list */ void insert(ListNode *n0, ListNode *P) { ListNode *n1 = n0->next; P->next = n1; n0->next = P; } /* Remove the first node after node n0 in the linked list */ void remove(ListNode *n0) { if (n0->next == nullptr) return; // n0 -> P -> n1 ListNode *P = n0->next; ListNode *n1 = P->next; n0->next = n1; // Free memory delete P; } /* Access the node at index index in the linked list */ ListNode *access(ListNode *head, int index) { for (int i = 0; i < index; i++) { if (head == nullptr) return nullptr; head = head->next; } return head; } /* Find the first node with value target in the linked list */ int find(ListNode *head, int target) { int index = 0; while (head != nullptr) { if (head->val == target) return index; head = head->next; index++; } return -1; } /* Driver Code */ int main() { /* Initialize linked list */ // Initialize each node 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); // Build references between nodes n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; cout << "Initialized linked list is" << endl; printLinkedList(n0); /* Insert node */ insert(n0, new ListNode(0)); cout << "Linked list after inserting node is" << endl; printLinkedList(n0); /* Remove node */ remove(n0); cout << "Linked list after removing node is" << endl; printLinkedList(n0); /* Access node */ ListNode *node = access(n0, 3); cout << "Value of node at index 3 in linked list = " << node->val << endl; /* Search node */ int index = find(n0, 2); cout << "Index of node with value 2 in linked list = " << index << endl; // Free memory freeMemoryLinkedList(n0); return 0; } ================================================ FILE: en/codes/cpp/chapter_array_and_linkedlist/list.cpp ================================================ /** * File: list.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* Initialize list */ vector nums = {1, 3, 2, 5, 4}; cout << "List nums = "; printVector(nums); /* Update element */ int num = nums[1]; cout << "Access element at index 1, get num = " << num << endl; /* Add elements at the end */ nums[1] = 0; cout << "Update element at index 1 to 0, resulting in nums = "; printVector(nums); /* Remove element */ nums.clear(); cout << "After clearing list, nums = "; printVector(nums); /* Direct traversal of list elements */ 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); /* Sort list */ nums.insert(nums.begin() + 3, 6); cout << "Insert number 6 at index 3, resulting in nums = "; printVector(nums); /* Remove element */ nums.erase(nums.begin() + 3); cout << "Remove element at index 3, resulting in nums = "; printVector(nums); /* Traverse list by index */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums[i]; } /* Directly traverse list elements */ count = 0; for (int x : nums) { count += x; } /* Concatenate two lists */ 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 list */ sort(nums.begin(), nums.end()); cout << "After sorting list, nums = "; printVector(nums); return 0; } ================================================ FILE: en/codes/cpp/chapter_array_and_linkedlist/my_list.cpp ================================================ /** * File: my_list.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* List class */ class MyList { private: int *arr; // Array (stores list elements) int arrCapacity = 10; // List capacity int arrSize = 0; // List length (current number of elements) int extendRatio = 2; // Multiple by which the list capacity is extended each time public: /* Constructor */ MyList() { arr = new int[arrCapacity]; } /* Destructor */ ~MyList() { delete[] arr; } /* Get list length (current number of elements)*/ int size() { return arrSize; } /* Get list capacity */ int capacity() { return arrCapacity; } /* Update element */ int get(int index) { // If the index is out of bounds, throw an exception, as below if (index < 0 || index >= size()) throw out_of_range("Index out of bounds"); return arr[index]; } /* Add elements at the end */ void set(int index, int num) { if (index < 0 || index >= size()) throw out_of_range("Index out of bounds"); arr[index] = num; } /* Direct traversal of list elements */ void add(int num) { // When the number of elements exceeds capacity, trigger the extension mechanism if (size() == capacity()) extendCapacity(); arr[size()] = num; // Update the number of elements arrSize++; } /* Sort list */ void insert(int index, int num) { if (index < 0 || index >= size()) throw out_of_range("Index out of bounds"); // When the number of elements exceeds capacity, trigger the extension mechanism if (size() == capacity()) extendCapacity(); // Move all elements after index index forward by one position for (int j = size() - 1; j >= index; j--) { arr[j + 1] = arr[j]; } arr[index] = num; // Update the number of elements arrSize++; } /* Remove element */ int remove(int index) { if (index < 0 || index >= size()) throw out_of_range("Index out of bounds"); int num = arr[index]; // Create a new array with length _extend_ratio times the original array, and copy the original array to the new array for (int j = index; j < size() - 1; j++) { arr[j] = arr[j + 1]; } // Update the number of elements arrSize--; // Return the removed element return num; } /* Driver Code */ void extendCapacity() { // Create a new array with length extendRatio times the original array int newCapacity = capacity() * extendRatio; int *tmp = arr; arr = new int[newCapacity]; // Copy all elements from the original array to the new array for (int i = 0; i < size(); i++) { arr[i] = tmp[i]; } // Free memory delete[] tmp; arrCapacity = newCapacity; } /* Convert list to Vector for printing */ vector toVector() { // Elements enqueue vector vec(size()); for (int i = 0; i < size(); i++) { vec[i] = arr[i]; } return vec; } }; /* Driver Code */ int main() { /* Initialize list */ MyList *nums = new MyList(); /* Direct traversal of list elements */ 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; /* Sort list */ nums->insert(3, 6); cout << "Insert number 6 at index 3, resulting in nums = "; vec = nums->toVector(); printVector(vec); /* Remove element */ nums->remove(3); cout << "Remove element at index 3, resulting in nums = "; vec = nums->toVector(); printVector(vec); /* Update element */ int num = nums->get(1); cout << "Access element at index 1, get num = " << num << endl; /* Add elements at the end */ nums->set(1, 0); cout << "Update element at index 1 to 0, resulting in nums = "; vec = nums->toVector(); printVector(vec); /* Test capacity expansion mechanism */ for (int i = 0; i < 10; i++) { // At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism nums->add(i); } cout << "List nums after expansion = "; vec = nums->toVector(); printVector(vec); cout << "Capacity = " << nums->capacity() << ", length = " << nums->size() << endl; // Free memory delete nums; return 0; } ================================================ FILE: en/codes/cpp/chapter_backtracking/CMakeLists.txt ================================================ add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.cpp) add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.cpp) add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.cpp) add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.cpp) add_executable(permutations_i permutations_i.cpp) add_executable(permutations_ii permutations_ii.cpp) add_executable(n_queens n_queens.cpp) add_executable(subset_sum_i_naive subset_sum_i_naive.cpp) add_executable(subset_sum_i subset_sum_i.cpp) add_executable(subset_sum_ii subset_sum_ii.cpp) ================================================ FILE: en/codes/cpp/chapter_backtracking/n_queens.cpp ================================================ /** * File: n_queens.cpp * Created Time: 2023-05-04 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Backtracking algorithm: N queens */ void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols, vector &diags1, vector &diags2) { // When all rows are placed, record the solution if (row == n) { res.push_back(state); return; } // Traverse all columns for (int col = 0; col < n; col++) { // Calculate the main diagonal and anti-diagonal corresponding to this cell int diag1 = row - col + n - 1; int diag2 = row + col; // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Attempt: place the queen in this cell state[row][col] = "Q"; cols[col] = diags1[diag1] = diags2[diag2] = true; // Place the next row backtrack(row + 1, n, state, res, cols, diags1, diags2); // Backtrack: restore this cell to an empty cell state[row][col] = "#"; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Solve N queens */ vector>> nQueens(int n) { // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell vector> state(n, vector(n, "#")); vector cols(n, false); // Record whether there is a queen in the column vector diags1(2 * n - 1, false); // Record whether there is a queen on the main diagonal vector diags2(2 * n - 1, false); // Record whether there is a queen on the anti-diagonal vector>> res; backtrack(0, n, state, res, cols, diags1, diags2); return res; } /* Driver Code */ int main() { int n = 4; vector>> res = nQueens(n); cout << "Input board size is " << n << endl; cout << "Total queen placement solutions: " << res.size() << endl; for (const vector> &state : res) { cout << "--------------------" << endl; for (const vector &row : state) { printVector(row); } } return 0; } ================================================ FILE: en/codes/cpp/chapter_backtracking/permutations_i.cpp ================================================ /** * File: permutations_i.cpp * Created Time: 2023-04-24 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Backtracking algorithm: Permutations I */ void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { // When the state length equals the number of elements, record the solution if (state.size() == choices.size()) { res.push_back(state); return; } // Traverse all choices for (int i = 0; i < choices.size(); i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state.push_back(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop_back(); } } } /* Permutations I */ vector> permutationsI(vector nums) { vector state; vector selected(nums.size(), false); vector> res; backtrack(state, nums, selected, res); return res; } /* Driver Code */ int main() { vector nums = {1, 2, 3}; vector> res = permutationsI(nums); cout << "Input array nums = "; printVector(nums); cout << "All permutations res = "; printVectorMatrix(res); return 0; } ================================================ FILE: en/codes/cpp/chapter_backtracking/permutations_ii.cpp ================================================ /** * File: permutations_ii.cpp * Created Time: 2023-04-24 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Backtracking algorithm: Permutations II */ void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { // When the state length equals the number of elements, record the solution if (state.size() == choices.size()) { res.push_back(state); return; } // Traverse all choices unordered_set duplicated; for (int i = 0; i < choices.size(); i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && duplicated.find(choice) == duplicated.end()) { // Attempt: make choice, update state duplicated.emplace(choice); // Record the selected element value selected[i] = true; state.push_back(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop_back(); } } } /* Permutations II */ vector> permutationsII(vector nums) { vector state; vector selected(nums.size(), false); vector> res; backtrack(state, nums, selected, res); return res; } /* Driver Code */ int main() { vector nums = {1, 1, 2}; vector> res = permutationsII(nums); cout << "Input array nums = "; printVector(nums); cout << "All permutations res = "; printVectorMatrix(res); return 0; } ================================================ FILE: en/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp ================================================ /** * File: preorder_traversal_i_compact.cpp * Created Time: 2023-04-16 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" vector res; /* Preorder traversal: Example 1 */ void preOrder(TreeNode *root) { if (root == nullptr) { return; } if (root->val == 7) { // Record solution res.push_back(root); } preOrder(root->left); preOrder(root->right); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\nInitialize binary tree" << endl; printTree(root); // Preorder traversal preOrder(root); cout << "\nOutput all nodes with value 7" << endl; vector vals; for (TreeNode *node : res) { vals.push_back(node->val); } printVector(vals); } ================================================ FILE: en/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp ================================================ /** * File: preorder_traversal_ii_compact.cpp * Created Time: 2023-04-16 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" vector path; vector> res; /* Preorder traversal: Example 2 */ void preOrder(TreeNode *root) { if (root == nullptr) { return; } // Attempt path.push_back(root); if (root->val == 7) { // Record solution res.push_back(path); } preOrder(root->left); preOrder(root->right); // Backtrack path.pop_back(); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\nInitialize binary tree" << endl; printTree(root); // Preorder traversal preOrder(root); cout << "\nOutput all paths from root node to node 7" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: en/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp ================================================ /** * File: preorder_traversal_iii_compact.cpp * Created Time: 2023-04-16 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" vector path; vector> res; /* Preorder traversal: Example 3 */ void preOrder(TreeNode *root) { // Pruning if (root == nullptr || root->val == 3) { return; } // Attempt path.push_back(root); if (root->val == 7) { // Record solution res.push_back(path); } preOrder(root->left); preOrder(root->right); // Backtrack path.pop_back(); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\nInitialize binary tree" << endl; printTree(root); // Preorder traversal preOrder(root); cout << "\nOutput all paths from root node to node 7, requiring paths do not include nodes with value 3" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: en/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp ================================================ /** * File: preorder_traversal_iii_template.cpp * Created Time: 2023-04-16 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Check if the current state is a solution */ bool isSolution(vector &state) { return !state.empty() && state.back()->val == 7; } /* Record solution */ void recordSolution(vector &state, vector> &res) { res.push_back(state); } /* Check if the choice is valid under the current state */ bool isValid(vector &state, TreeNode *choice) { return choice != nullptr && choice->val != 3; } /* Update state */ void makeChoice(vector &state, TreeNode *choice) { state.push_back(choice); } /* Restore state */ void undoChoice(vector &state, TreeNode *choice) { state.pop_back(); } /* Backtracking algorithm: Example 3 */ void backtrack(vector &state, vector &choices, vector> &res) { // Check if it is a solution if (isSolution(state)) { // Record solution recordSolution(state, res); } // Traverse all choices for (TreeNode *choice : choices) { // Pruning: check if the choice is valid if (isValid(state, choice)) { // Attempt: make choice, update state makeChoice(state, choice); // Proceed to the next round of selection vector nextChoices{choice->left, choice->right}; backtrack(state, nextChoices, res); // Backtrack: undo choice, restore to previous state undoChoice(state, choice); } } } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\nInitialize binary tree" << endl; printTree(root); // Backtracking algorithm vector state; vector choices = {root}; vector> res; backtrack(state, choices, res); cout << "\nOutput all paths from root node to node 7, requiring paths do not include nodes with value 3" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: en/codes/cpp/chapter_backtracking/subset_sum_i.cpp ================================================ /** * File: subset_sum_i.cpp * Created Time: 2023-06-21 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Backtracking algorithm: Subset sum I */ void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { // When the subset sum equals target, record the solution if (target == 0) { res.push_back(state); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets for (int i = start; i < choices.size(); i++) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if (target - choices[i] < 0) { break; } // Attempt: make choice, update target, start state.push_back(choices[i]); // Proceed to the next round of selection backtrack(state, target - choices[i], choices, i, res); // Backtrack: undo choice, restore to previous state state.pop_back(); } } /* Solve subset sum I */ vector> subsetSumI(vector &nums, int target) { vector state; // State (subset) sort(nums.begin(), nums.end()); // Sort nums int start = 0; // Start point for traversal vector> res; // Result list (subset list) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ int main() { vector nums = {3, 4, 5}; int target = 9; vector> res = subsetSumI(nums, target); cout << "Input array nums = "; printVector(nums); cout << "target = " << target << endl; cout << "All subsets with sum equal to " << target << " are res = " << endl; printVectorMatrix(res); return 0; } ================================================ FILE: en/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp ================================================ /** * File: subset_sum_i_naive.cpp * Created Time: 2023-06-21 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Backtracking algorithm: Subset sum I */ void backtrack(vector &state, int target, int total, vector &choices, vector> &res) { // When the subset sum equals target, record the solution if (total == target) { res.push_back(state); return; } // Traverse all choices for (size_t i = 0; i < choices.size(); i++) { // Pruning: if the subset sum exceeds target, skip this choice if (total + choices[i] > target) { continue; } // Attempt: make choice, update element sum total state.push_back(choices[i]); // Proceed to the next round of selection backtrack(state, target, total + choices[i], choices, res); // Backtrack: undo choice, restore to previous state state.pop_back(); } } /* Solve subset sum I (including duplicate subsets) */ vector> subsetSumINaive(vector &nums, int target) { vector state; // State (subset) int total = 0; // Subset sum vector> res; // Result list (subset list) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ int main() { vector nums = {3, 4, 5}; int target = 9; vector> res = subsetSumINaive(nums, target); cout << "Input array nums = "; printVector(nums); cout << "target = " << target << endl; cout << "All subsets with sum equal to " << target << " are res = " << endl; printVectorMatrix(res); return 0; } ================================================ FILE: en/codes/cpp/chapter_backtracking/subset_sum_ii.cpp ================================================ /** * File: subset_sum_ii.cpp * Created Time: 2023-06-21 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Backtracking algorithm: Subset sum II */ void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { // When the subset sum equals target, record the solution if (target == 0) { res.push_back(state); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets // Pruning 3: start traversing from start to avoid repeatedly selecting the same element for (int i = start; i < choices.size(); i++) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if (target - choices[i] < 0) { break; } // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly if (i > start && choices[i] == choices[i - 1]) { continue; } // Attempt: make choice, update target, start state.push_back(choices[i]); // Proceed to the next round of selection backtrack(state, target - choices[i], choices, i + 1, res); // Backtrack: undo choice, restore to previous state state.pop_back(); } } /* Solve subset sum II */ vector> subsetSumII(vector &nums, int target) { vector state; // State (subset) sort(nums.begin(), nums.end()); // Sort nums int start = 0; // Start point for traversal vector> res; // Result list (subset list) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ int main() { vector nums = {4, 4, 5}; int target = 9; vector> res = subsetSumII(nums, target); cout << "Input array nums = "; printVector(nums); cout << "target = " << target << endl; cout << "All subsets with sum equal to " << target << " are res = " << endl; printVectorMatrix(res); return 0; } ================================================ FILE: en/codes/cpp/chapter_computational_complexity/CMakeLists.txt ================================================ add_executable(iteration iteration.cpp) add_executable(recursion recursion.cpp) add_executable(space_complexity space_complexity.cpp) add_executable(time_complexity time_complexity.cpp) add_executable(worst_best_time_complexity worst_best_time_complexity.cpp) ================================================ FILE: en/codes/cpp/chapter_computational_complexity/iteration.cpp ================================================ /** * File: iteration.cpp * Created Time: 2023-08-24 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* for loop */ int forLoop(int n) { int res = 0; // Sum 1, 2, ..., n-1, n for (int i = 1; i <= n; ++i) { res += i; } return res; } /* while loop */ int whileLoop(int n) { int res = 0; int i = 1; // Initialize condition variable // Sum 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Update condition variable } return res; } /* while loop (two updates) */ int whileLoopII(int n) { int res = 0; int i = 1; // Initialize condition variable // Sum 1, 4, 10, ... while (i <= n) { res += i; // Update condition variable i++; i *= 2; } return res; } /* Nested for loop */ string nestedForLoop(int n) { ostringstream res; // Loop i = 1, 2, ..., n-1, n for (int i = 1; i <= n; ++i) { // Loop j = 1, 2, ..., n-1, n for (int j = 1; j <= n; ++j) { res << "(" << i << ", " << j << "), "; } } return res.str(); } /* Driver Code */ int main() { int n = 5; int res; res = forLoop(n); cout << "\nfor loop sum result res = " << res << endl; res = whileLoop(n); cout << "\nwhile loop sum result res = " << res << endl; res = whileLoopII(n); cout << "\nwhile loop (two updates) sum result res = " << res << endl; string resStr = nestedForLoop(n); cout << "\nDouble for loop traversal result " << resStr << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_computational_complexity/recursion.cpp ================================================ /** * File: recursion.cpp * Created Time: 2023-08-24 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Recursion */ int recur(int n) { // Termination condition if (n == 1) return 1; // Recurse: recursive call int res = recur(n - 1); // Return: return result return n + res; } /* Simulate recursion using iteration */ int forLoopRecur(int n) { // Use an explicit stack to simulate the system call stack stack stack; int res = 0; // Recurse: recursive call for (int i = n; i > 0; i--) { // Simulate "recurse" with "push" stack.push(i); } // Return: return result while (!stack.empty()) { // Simulate "return" with "pop" res += stack.top(); stack.pop(); } // res = 1+2+3+...+n return res; } /* Tail recursion */ int tailRecur(int n, int res) { // Termination condition if (n == 0) return res; // Tail recursive call return tailRecur(n - 1, res + n); } /* Fibonacci sequence: recursion */ int fib(int n) { // Termination condition f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // Recursive call f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // Return result f(n) return res; } /* Driver Code */ int main() { int n = 5; int res; res = recur(n); cout << "\nRecursive function sum result res = " << res << endl; res = forLoopRecur(n); cout << "\nUsing iteration to simulate recursive sum result res = " << res << endl; res = tailRecur(n, 0); cout << "\nTail recursive function sum result res = " << res << endl; res = fib(n); cout << "\nThe " << n << "th term of the Fibonacci sequence is " << res << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_computational_complexity/space_complexity.cpp ================================================ /** * File: space_complexity.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Function */ int func() { // Perform some operations return 0; } /* Constant order */ void constant(int n) { // Constants, variables, objects occupy O(1) space const int a = 0; int b = 0; vector nums(10000); ListNode node(0); // Variables in the loop occupy O(1) space for (int i = 0; i < n; i++) { int c = 0; } // Functions in the loop occupy O(1) space for (int i = 0; i < n; i++) { func(); } } /* Linear order */ void linear(int n) { // Array of length n uses O(n) space vector nums(n); // A list of length n occupies O(n) space vector nodes; for (int i = 0; i < n; i++) { nodes.push_back(ListNode(i)); } // A hash table of length n occupies O(n) space unordered_map map; for (int i = 0; i < n; i++) { map[i] = to_string(i); } } /* Linear order (recursive implementation) */ void linearRecur(int n) { cout << "Recursion n = " << n << endl; if (n == 1) return; linearRecur(n - 1); } /* Exponential order */ void quadratic(int n) { // 2D list uses O(n^2) space 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); } } /* Quadratic order (recursive implementation) */ int quadraticRecur(int n) { if (n <= 0) return 0; vector nums(n); cout << "In recursion n = " << n << ", nums length = " << nums.size() << endl; return quadraticRecur(n - 1); } /* Driver Code */ 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; } /* Driver Code */ int main() { int n = 5; // Constant order constant(n); // Linear order linear(n); linearRecur(n); // Exponential order quadratic(n); quadraticRecur(n); // Exponential order TreeNode *root = buildTree(n); printTree(root); // Free memory freeMemoryTree(root); return 0; } ================================================ FILE: en/codes/cpp/chapter_computational_complexity/time_complexity.cpp ================================================ /** * File: time_complexity.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Constant order */ int constant(int n) { int count = 0; int size = 100000; for (int i = 0; i < size; i++) count++; return count; } /* Linear order */ int linear(int n) { int count = 0; for (int i = 0; i < n; i++) count++; return count; } /* Linear order (traversing array) */ int arrayTraversal(vector &nums) { int count = 0; // Number of iterations is proportional to the array length for (int num : nums) { count++; } return count; } /* Exponential order */ int quadratic(int n) { int count = 0; // Number of iterations is quadratically related to the data size n for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* Quadratic order (bubble sort) */ int bubbleSort(vector &nums) { int count = 0; // Counter // Outer loop: unsorted range is [0, i] for (int i = nums.size() - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // Element swap includes 3 unit operations } } } return count; } /* Exponential order (loop implementation) */ int exponential(int n) { int count = 0, base = 1; // Cells divide into two every round, forming sequence 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; } /* Exponential order (recursive implementation) */ int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* Logarithmic order (loop implementation) */ int logarithmic(int n) { int count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* Logarithmic order (recursive implementation) */ int logRecur(int n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* Linearithmic order */ 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; } /* Factorial order (recursive implementation) */ int factorialRecur(int n) { if (n == 0) return 1; int count = 0; // Split from 1 into n for (int i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ int main() { // You can modify n to run and observe the trend of the number of operations for various complexities int n = 8; cout << "Input data size n = " << n << endl; int count = constant(n); cout << "Constant order operation count = " << count << endl; count = linear(n); cout << "Linear order operation count = " << count << endl; vector arr(n); count = arrayTraversal(arr); cout << "Linear order (array traversal) operation count = " << count << endl; count = quadratic(n); cout << "Quadratic order operation count = " << 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 << "Quadratic order (bubble sort) operation count = " << count << endl; count = exponential(n); cout << "Exponential order (loop implementation) operation count = " << count << endl; count = expRecur(n); cout << "Exponential order (recursive implementation) operation count = " << count << endl; count = logarithmic(n); cout << "Logarithmic order (loop implementation) operation count = " << count << endl; count = logRecur(n); cout << "Logarithmic order (recursive implementation) operation count = " << count << endl; count = linearLogRecur(n); cout << "Linearithmic order (recursive implementation) operation count = " << count << endl; count = factorialRecur(n); cout << "Factorial order (recursive implementation) operation count = " << count << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp ================================================ /** * File: worst_best_time_complexity.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Generate an array with elements { 1, 2, ..., n }, order shuffled */ vector randomNumbers(int n) { vector nums(n); // Generate array nums = { 1, 2, 3, ..., n } for (int i = 0; i < n; i++) { nums[i] = i + 1; } // Use system time to generate random seed unsigned seed = chrono::system_clock::now().time_since_epoch().count(); // Randomly shuffle array elements shuffle(nums.begin(), nums.end(), default_random_engine(seed)); return nums; } /* Find the index of number 1 in array nums */ int findOne(vector &nums) { for (int i = 0; i < nums.size(); i++) { // When element 1 is at the head of the array, best time complexity O(1) is achieved // When element 1 is at the tail of the array, worst time complexity O(n) is achieved if (nums[i] == 1) return i; } return -1; } /* Driver Code */ int main() { for (int i = 0; i < 1000; i++) { int n = 100; vector nums = randomNumbers(n); int index = findOne(nums); cout << "\nArray [ 1, 2, ..., n ] after shuffling = "; printVector(nums); cout << "Index of number 1 is " << index << endl; } return 0; } ================================================ FILE: en/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt ================================================ add_executable(binary_search_recur binary_search_recur.cpp) add_executable(build_tree build_tree.cpp) add_executable(hanota hanota.cpp) ================================================ FILE: en/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp ================================================ /** * File: binary_search_recur.cpp * Created Time: 2023-07-17 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Binary search: problem f(i, j) */ int dfs(vector &nums, int target, int i, int j) { // If the interval is empty, it means there is no target element, return -1 if (i > j) { return -1; } // Calculate the midpoint index m int m = (i + j) / 2; if (nums[m] < target) { // Recursion subproblem f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // Recursion subproblem f(i, m-1) return dfs(nums, target, i, m - 1); } else { // Found the target element, return its index return m; } } /* Binary search */ int binarySearch(vector &nums, int target) { int n = nums.size(); // Solve the problem f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ int main() { int target = 6; vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; // Binary search (closed interval on both sides) int index = binarySearch(nums, target); cout << "Index of target element 6 = " << index << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_divide_and_conquer/build_tree.cpp ================================================ /** * File: build_tree.cpp * Created Time: 2023-07-17 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Build binary tree: divide and conquer */ TreeNode *dfs(vector &preorder, unordered_map &inorderMap, int i, int l, int r) { // Terminate when the subtree interval is empty if (r - l < 0) return NULL; // Initialize the root node TreeNode *root = new TreeNode(preorder[i]); // Query m to divide the left and right subtrees int m = inorderMap[preorder[i]]; // Subproblem: build the left subtree root->left = dfs(preorder, inorderMap, i + 1, l, m - 1); // Subproblem: build the right subtree root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // Return the root node return root; } /* Build binary tree */ TreeNode *buildTree(vector &preorder, vector &inorder) { // Initialize hash map, storing the mapping from inorder elements to indices 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; } /* Driver Code */ int main() { vector preorder = {3, 9, 2, 1, 7}; vector inorder = {9, 3, 1, 2, 7}; cout << "Preorder traversal = "; printVector(preorder); cout << "Inorder traversal = "; printVector(inorder); TreeNode *root = buildTree(preorder, inorder); cout << "The constructed binary tree is:\n"; printTree(root); return 0; } ================================================ FILE: en/codes/cpp/chapter_divide_and_conquer/hanota.cpp ================================================ /** * File: hanota.cpp * Created Time: 2023-07-17 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Move a disk */ void move(vector &src, vector &tar) { // Take out a disk from the top of src int pan = src.back(); src.pop_back(); // Place the disk on top of tar tar.push_back(pan); } /* Solve the Tower of Hanoi problem f(i) */ void dfs(int i, vector &src, vector &buf, vector &tar) { // If there is only one disk left in src, move it directly to tar if (i == 1) { move(src, tar); return; } // Subproblem f(i-1): move the top i-1 disks from src to buf using tar dfs(i - 1, src, tar, buf); // Subproblem f(1): move the remaining disk from src to tar move(src, tar); // Subproblem f(i-1): move the top i-1 disks from buf to tar using src dfs(i - 1, buf, src, tar); } /* Solve the Tower of Hanoi problem */ void solveHanota(vector &A, vector &B, vector &C) { int n = A.size(); // Move the top n disks from A to C using B dfs(n, A, B, C); } /* Driver Code */ int main() { // The tail of the list is the top of the rod vector A = {5, 4, 3, 2, 1}; vector B = {}; vector C = {}; cout << "Initial state:\n"; cout << "A ="; printVector(A); cout << "B ="; printVector(B); cout << "C ="; printVector(C); solveHanota(A, B, C); cout << "After disk movement:\n"; cout << "A ="; printVector(A); cout << "B ="; printVector(B); cout << "C ="; printVector(C); return 0; } ================================================ FILE: en/codes/cpp/chapter_dynamic_programming/CMakeLists.txt ================================================ add_executable(climbing_stairs_backtrack climbing_stairs_backtrack.cpp) add_executable(climbing_stairs_dfs climbing_stairs_dfs.cpp) add_executable(climbing_stairs_dfs_mem climbing_stairs_dfs_mem.cpp) add_executable(climbing_stairs_dp climbing_stairs_dp.cpp) add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.cpp) add_executable(min_path_sum min_path_sum.cpp) add_executable(unbounded_knapsack unbounded_knapsack.cpp) add_executable(coin_change coin_change.cpp) add_executable(coin_change_ii coin_change_ii.cpp) add_executable(edit_distance edit_distance.cpp) ================================================ FILE: en/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp ================================================ /** * File: climbing_stairs_backtrack.cpp * Created Time: 2023-06-30 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Backtracking */ void backtrack(vector &choices, int state, int n, vector &res) { // When climbing to the n-th stair, add 1 to the solution count if (state == n) res[0]++; // Traverse all choices for (auto &choice : choices) { // Pruning: not allowed to go beyond the n-th stair if (state + choice > n) continue; // Attempt: make choice, update state backtrack(choices, state + choice, n, res); // Backtrack } } /* Climbing stairs: Backtracking */ int climbingStairsBacktrack(int n) { vector choices = {1, 2}; // Can choose to climb up 1 or 2 stairs int state = 0; // Start climbing from the 0-th stair vector res = {0}; // Use res[0] to record the solution count backtrack(choices, state, n, res); return res[0]; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsBacktrack(n); cout << "Climbing " << n << " stairs has " << res << " solutions" << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp ================================================ /** * File: climbing_stairs_constraint_dp.cpp * Created Time: 2023-07-01 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Climbing stairs with constraint: Dynamic programming */ int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // Initialize dp table, used to store solutions to subproblems vector> dp(n + 1, vector(3, 0)); // Initial state: preset the solution to the smallest subproblem dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsConstraintDP(n); cout << "Climbing " << n << " stairs has " << res << " solutions" << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp ================================================ /** * File: climbing_stairs_dfs.cpp * Created Time: 2023-06-30 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Search */ int dfs(int i) { // Known dp[1] and dp[2], return them 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; } /* Climbing stairs: Search */ int climbingStairsDFS(int n) { return dfs(n); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFS(n); cout << "Climbing " << n << " stairs has " << res << " solutions" << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp ================================================ /** * File: climbing_stairs_dfs_mem.cpp * Created Time: 2023-06-30 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Memoization search */ int dfs(int i, vector &mem) { // Known dp[1] and dp[2], return them if (i == 1 || i == 2) return i; // If record dp[i] exists, return it directly 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); // Record dp[i] mem[i] = count; return count; } /* Climbing stairs: Memoization search */ int climbingStairsDFSMem(int n) { // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record vector mem(n + 1, -1); return dfs(n, mem); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFSMem(n); cout << "Climbing " << n << " stairs has " << res << " solutions" << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp ================================================ /** * File: climbing_stairs_dp.cpp * Created Time: 2023-06-30 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Climbing stairs: Dynamic programming */ int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; // Initialize dp table, used to store solutions to subproblems vector dp(n + 1); // Initial state: preset the solution to the smallest subproblem dp[1] = 1; dp[2] = 2; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* Climbing stairs: Space-optimized dynamic programming */ int climbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDP(n); cout << "Climbing " << n << " stairs has " << res << " solutions" << endl; res = climbingStairsDPComp(n); cout << "Climbing " << n << " stairs has " << res << " solutions" << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_dynamic_programming/coin_change.cpp ================================================ /** * File: coin_change.cpp * Created Time: 2023-07-11 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Coin change: Dynamic programming */ int coinChangeDP(vector &coins, int amt) { int n = coins.size(); int MAX = amt + 1; // Initialize dp table vector> dp(n + 1, vector(amt + 1, 0)); // State transition: first row and first column for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // State transition: rest of the rows and columns for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // The smaller value between not selecting and selecting coin 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; } /* Coin change: Space-optimized dynamic programming */ int coinChangeDPComp(vector &coins, int amt) { int n = coins.size(); int MAX = amt + 1; // Initialize dp table vector dp(amt + 1, MAX); dp[0] = 0; // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // The smaller value between not selecting and selecting coin i dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } /* Driver code */ int main() { vector coins = {1, 2, 5}; int amt = 4; // Dynamic programming int res = coinChangeDP(coins, amt); cout << "Minimum number of coins needed to make target amount is " << res << endl; // Space-optimized dynamic programming res = coinChangeDPComp(coins, amt); cout << "Minimum number of coins needed to make target amount is " << res << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp ================================================ /** * File: coin_change_ii.cpp * Created Time: 2023-07-11 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Coin change II: Dynamic programming */ int coinChangeIIDP(vector &coins, int amt) { int n = coins.size(); // Initialize dp table vector> dp(n + 1, vector(amt + 1, 0)); // Initialize first column for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // Sum of the two options: not selecting and selecting coin i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* Coin change II: Space-optimized dynamic programming */ int coinChangeIIDPComp(vector &coins, int amt) { int n = coins.size(); // Initialize dp table vector dp(amt + 1, 0); dp[0] = 1; // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // Sum of the two options: not selecting and selecting coin i dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver code */ int main() { vector coins = {1, 2, 5}; int amt = 5; // Dynamic programming int res = coinChangeIIDP(coins, amt); cout << "Number of coin combinations to make target amount is " << res << endl; // Space-optimized dynamic programming res = coinChangeIIDPComp(coins, amt); cout << "Number of coin combinations to make target amount is " << res << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_dynamic_programming/edit_distance.cpp ================================================ /** * File: edit_distance.cpp * Created Time: 2023-07-13 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Edit distance: Brute-force search */ int editDistanceDFS(string s, string t, int i, int j) { // If both s and t are empty, return 0 if (i == 0 && j == 0) return 0; // If s is empty, return length of t if (i == 0) return j; // If t is empty, return length of s if (j == 0) return i; // If two characters are equal, skip both characters if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 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 minimum edit steps return min(min(insert, del), replace) + 1; } /* Edit distance: Memoization search */ int editDistanceDFSMem(string s, string t, vector> &mem, int i, int j) { // If both s and t are empty, return 0 if (i == 0 && j == 0) return 0; // If s is empty, return length of t if (i == 0) return j; // If t is empty, return length of s if (j == 0) return i; // If there's a record, return it directly if (mem[i][j] != -1) return mem[i][j]; // If two characters are equal, skip both characters if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 int insert = editDistanceDFSMem(s, t, mem, i, j - 1); int del = editDistanceDFSMem(s, t, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Record and return minimum edit steps mem[i][j] = min(min(insert, del), replace) + 1; return mem[i][j]; } /* Edit distance: Dynamic programming */ int editDistanceDP(string s, string t) { int n = s.length(), m = t.length(); vector> dp(n + 1, vector(m + 1, 0)); // State transition: first row and first column for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // State transition: rest of the rows and columns for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // If two characters are equal, skip both characters dp[i][j] = dp[i - 1][j - 1]; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* Edit distance: Space-optimized dynamic programming */ int editDistanceDPComp(string s, string t) { int n = s.length(), m = t.length(); vector dp(m + 1, 0); // State transition: first row for (int j = 1; j <= m; j++) { dp[j] = j; } // State transition: rest of the rows for (int i = 1; i <= n; i++) { // State transition: first column int leftup = dp[0]; // Temporarily store dp[i-1, j-1] dp[0] = i; // State transition: rest of the columns for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // If two characters are equal, skip both characters dp[j] = leftup; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // Update for next round's dp[i-1, j-1] } } return dp[m]; } /* Driver Code */ int main() { string s = "bag"; string t = "pack"; int n = s.length(), m = t.length(); // Brute-force search int res = editDistanceDFS(s, t, n, m); cout << "Changing " << s << " to " << t << " requires a minimum of " << res << " edits"; // Memoization search vector> mem(n + 1, vector(m + 1, -1)); res = editDistanceDFSMem(s, t, mem, n, m); cout << "Changing " << s << " to " << t << " requires a minimum of " << res << " edits"; // Dynamic programming res = editDistanceDP(s, t); cout << "Changing " << s << " to " << t << " requires a minimum of " << res << " edits"; // Space-optimized dynamic programming res = editDistanceDPComp(s, t); cout << "Changing " << s << " to " << t << " requires a minimum of " << res << " edits"; return 0; } ================================================ FILE: en/codes/cpp/chapter_dynamic_programming/knapsack.cpp ================================================ #include #include #include using namespace std; /* 0-1 knapsack: Brute-force search */ int knapsackDFS(vector &wgt, vector &val, int i, int c) { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i == 0 || c == 0) { return 0; } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // Calculate the maximum value of not putting in and putting in item i int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // Return the larger value of the two options return max(no, yes); } /* 0-1 knapsack: Memoization search */ int knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i == 0 || c == 0) { return 0; } // If there's a record, return it directly if (mem[i][c] != -1) { return mem[i][c]; } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // Calculate the maximum value of not putting in and putting in item 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]; // Record and return the larger value of the two options mem[i][c] = max(no, yes); return mem[i][c]; } /* 0-1 knapsack: Dynamic programming */ int knapsackDP(vector &wgt, vector &val, int cap) { int n = wgt.size(); // Initialize dp table vector> dp(n + 1, vector(cap + 1, 0)); // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item 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 knapsack: Space-optimized dynamic programming */ int knapsackDPComp(vector &wgt, vector &val, int cap) { int n = wgt.size(); // Initialize dp table vector dp(cap + 1, 0); // State transition for (int i = 1; i <= n; i++) { // Traverse in reverse order for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // The larger value between not selecting and selecting item i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ int main() { vector wgt = {10, 20, 30, 40, 50}; vector val = {50, 120, 150, 210, 240}; int cap = 50; int n = wgt.size(); // Brute-force search int res = knapsackDFS(wgt, val, n, cap); cout << "Maximum item value not exceeding knapsack capacity is " << res << endl; // Memoization search vector> mem(n + 1, vector(cap + 1, -1)); res = knapsackDFSMem(wgt, val, mem, n, cap); cout << "Maximum item value not exceeding knapsack capacity is " << res << endl; // Dynamic programming res = knapsackDP(wgt, val, cap); cout << "Maximum item value not exceeding knapsack capacity is " << res << endl; // Space-optimized dynamic programming res = knapsackDPComp(wgt, val, cap); cout << "Maximum item value not exceeding knapsack capacity is " << res << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp ================================================ /** * File: min_cost_climbing_stairs_dp.cpp * Created Time: 2023-06-30 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Minimum cost climbing stairs: Dynamic programming */ int minCostClimbingStairsDP(vector &cost) { int n = cost.size() - 1; if (n == 1 || n == 2) return cost[n]; // Initialize dp table, used to store solutions to subproblems vector dp(n + 1); // Initial state: preset the solution to the smallest subproblem dp[1] = cost[1]; dp[2] = cost[2]; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* Minimum cost climbing stairs: Space-optimized dynamic programming */ 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; } /* Driver Code */ int main() { vector cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; cout << "Input stair cost list is "; printVector(cost); int res = minCostClimbingStairsDP(cost); cout << "Minimum cost to climb stairs is " << res << endl; res = minCostClimbingStairsDPComp(cost); cout << "Minimum cost to climb stairs is " << res << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp ================================================ /** * File: min_path_sum.cpp * Created Time: 2023-07-10 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Minimum path sum: Brute-force search */ int minPathSumDFS(vector> &grid, int i, int j) { // If it's the top-left cell, terminate the search if (i == 0 && j == 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return INT_MAX; } // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1) int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); // Return the minimum path cost from top-left to (i, j) return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; } /* Minimum path sum: Memoization search */ int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) { // If it's the top-left cell, terminate the search if (i == 0 && j == 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return INT_MAX; } // If there's a record, return it directly if (mem[i][j] != -1) { return mem[i][j]; } // Minimum path cost for left and upper cells int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // Record and return the minimum path cost from top-left to (i, j) mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; return mem[i][j]; } /* Minimum path sum: Dynamic programming */ int minPathSumDP(vector> &grid) { int n = grid.size(), m = grid[0].size(); // Initialize dp table vector> dp(n, vector(m)); dp[0][0] = grid[0][0]; // State transition: first row for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // State transition: first column for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // State transition: rest of the rows and columns 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]; } /* Minimum path sum: Space-optimized dynamic programming */ int minPathSumDPComp(vector> &grid) { int n = grid.size(), m = grid[0].size(); // Initialize dp table vector dp(m); // State transition: first row dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // State transition: rest of the rows for (int i = 1; i < n; i++) { // State transition: first column dp[0] = dp[0] + grid[i][0]; // State transition: rest of the columns for (int j = 1; j < m; j++) { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ int main() { vector> grid = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; int n = grid.size(), m = grid[0].size(); // Brute-force search int res = minPathSumDFS(grid, n - 1, m - 1); cout << "Minimum path sum from top-left to bottom-right is " << res << endl; // Memoization search vector> mem(n, vector(m, -1)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); cout << "Minimum path sum from top-left to bottom-right is " << res << endl; // Dynamic programming res = minPathSumDP(grid); cout << "Minimum path sum from top-left to bottom-right is " << res << endl; // Space-optimized dynamic programming res = minPathSumDPComp(grid); cout << "Minimum path sum from top-left to bottom-right is " << res << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp ================================================ /** * File: unbounded_knapsack.cpp * Created Time: 2023-07-11 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Unbounded knapsack: Dynamic programming */ int unboundedKnapsackDP(vector &wgt, vector &val, int cap) { int n = wgt.size(); // Initialize dp table vector> dp(n + 1, vector(cap + 1, 0)); // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item i dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } /* Unbounded knapsack: Space-optimized dynamic programming */ int unboundedKnapsackDPComp(vector &wgt, vector &val, int cap) { int n = wgt.size(); // Initialize dp table vector dp(cap + 1, 0); // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[c] = dp[c]; } else { // The larger value between not selecting and selecting item i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver code */ int main() { vector wgt = {1, 2, 3}; vector val = {5, 11, 15}; int cap = 4; // Dynamic programming int res = unboundedKnapsackDP(wgt, val, cap); cout << "Maximum item value not exceeding knapsack capacity is " << res << endl; // Space-optimized dynamic programming res = unboundedKnapsackDPComp(wgt, val, cap); cout << "Maximum item value not exceeding knapsack capacity is " << res << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_graph/CMakeLists.txt ================================================ add_executable(graph_bfs graph_bfs.cpp) add_executable(graph_dfs graph_dfs.cpp) # add_executable(graph_adjacency_list graph_adjacency_list.cpp) add_executable(graph_adjacency_list_test graph_adjacency_list_test.cpp) add_executable(graph_adjacency_matrix graph_adjacency_matrix.cpp) ================================================ FILE: en/codes/cpp/chapter_graph/graph_adjacency_list.cpp ================================================ /** * 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" /* Undirected graph class based on adjacency list */ class GraphAdjList { public: // Adjacency list, key: vertex, value: all adjacent vertices of that vertex unordered_map> adjList; /* Remove specified node from vector */ void remove(vector &vec, Vertex *vet) { for (int i = 0; i < vec.size(); i++) { if (vec[i] == vet) { vec.erase(vec.begin() + i); break; } } } /* Constructor */ GraphAdjList(const vector> &edges) { // Add all vertices and edges for (const vector &edge : edges) { addVertex(edge[0]); addVertex(edge[1]); addEdge(edge[0], edge[1]); } } /* Get the number of vertices */ int size() { return adjList.size(); } /* Add edge */ void addEdge(Vertex *vet1, Vertex *vet2) { if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) throw invalid_argument("Vertex does not exist"); // Add edge vet1 - vet2 adjList[vet1].push_back(vet2); adjList[vet2].push_back(vet1); } /* Remove edge */ void removeEdge(Vertex *vet1, Vertex *vet2) { if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) throw invalid_argument("Vertex does not exist"); // Remove edge vet1 - vet2 remove(adjList[vet1], vet2); remove(adjList[vet2], vet1); } /* Add vertex */ void addVertex(Vertex *vet) { if (adjList.count(vet)) return; // Add a new linked list in the adjacency list adjList[vet] = vector(); } /* Remove vertex */ void removeVertex(Vertex *vet) { if (!adjList.count(vet)) throw invalid_argument("Vertex does not exist"); // Remove the linked list corresponding to vertex vet in the adjacency list adjList.erase(vet); // Traverse the linked lists of other vertices and remove all edges containing vet for (auto &adj : adjList) { remove(adj.second, vet); } } /* Print adjacency list */ void print() { cout << "Adjacency list =" << endl; for (auto &adj : adjList) { const auto &key = adj.first; const auto &vec = adj.second; cout << key->val << ": "; printVector(vetsToVals(vec)); } } }; // See graph_adjacency_list_test.cpp for test cases ================================================ FILE: en/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp ================================================ /** * File: graph_adjacency_list_test.cpp * Created Time: 2023-02-09 * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) */ #include "./graph_adjacency_list.cpp" /* Driver Code */ int main() { /* Add edge */ vector v = valsToVets(vector{1, 3, 2, 5, 4}); vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; GraphAdjList graph(edges); cout << "\nAfter initialization, graph is" << endl; graph.print(); /* Add edge */ // Vertices 1, 3 are v[0], v[1] graph.addEdge(v[0], v[2]); cout << "\nAfter adding edge 1-2, graph is" << endl; graph.print(); /* Remove edge */ // Vertex 3 is v[1] graph.removeEdge(v[0], v[1]); cout << "\nAfter removing edge 1-3, graph is" << endl; graph.print(); /* Add vertex */ Vertex *v5 = new Vertex(6); graph.addVertex(v5); cout << "\nAfter adding vertex 6, graph is" << endl; graph.print(); /* Remove vertex */ // Vertex 3 is v[1] graph.removeVertex(v[1]); cout << "\nAfter removing vertex 3, graph is" << endl; graph.print(); // Free memory for (Vertex *vet : v) { delete vet; } return 0; } ================================================ FILE: en/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp ================================================ /** * File: graph_adjacency_matrix.cpp * Created Time: 2023-02-09 * Author: what-is-me (whatisme@outlook.jp) */ #include "../utils/common.hpp" /* Undirected graph class based on adjacency matrix */ class GraphAdjMat { vector vertices; // Vertex list, where the element represents the "vertex value" and the index represents the "vertex index" vector> adjMat; // Adjacency matrix, where the row and column indices correspond to the "vertex index" public: /* Constructor */ GraphAdjMat(const vector &vertices, const vector> &edges) { // Add vertex for (int val : vertices) { addVertex(val); } // Add edge // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices for (const vector &edge : edges) { addEdge(edge[0], edge[1]); } } /* Get the number of vertices */ int size() const { return vertices.size(); } /* Add vertex */ void addVertex(int val) { int n = size(); // Add the value of the new vertex to the vertex list vertices.push_back(val); // Add a row to the adjacency matrix adjMat.emplace_back(vector(n, 0)); // Add a column to the adjacency matrix for (vector &row : adjMat) { row.push_back(0); } } /* Remove vertex */ void removeVertex(int index) { if (index >= size()) { throw out_of_range("Vertex does not exist"); } // Remove the vertex at index from the vertex list vertices.erase(vertices.begin() + index); // Remove the row at index from the adjacency matrix adjMat.erase(adjMat.begin() + index); // Remove the column at index from the adjacency matrix for (vector &row : adjMat) { row.erase(row.begin() + index); } } /* Add edge */ // Parameters i, j correspond to the vertices element indices void addEdge(int i, int j) { // Handle index out of bounds and equality if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw out_of_range("Vertex does not exist"); } // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i) adjMat[i][j] = 1; adjMat[j][i] = 1; } /* Remove edge */ // Parameters i, j correspond to the vertices element indices void removeEdge(int i, int j) { // Handle index out of bounds and equality 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; } /* Print adjacency matrix */ void print() { cout << "Vertex list = "; printVector(vertices); cout << "Adjacency matrix =" << endl; printVectorMatrix(adjMat); } }; /* Driver Code */ int main() { /* Add edge */ // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices 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 << "\nAfter initialization, graph is" << endl; graph.print(); /* Add edge */ // Add vertex graph.addEdge(0, 2); cout << "\nAfter adding edge 1-2, graph is" << endl; graph.print(); /* Remove edge */ // Vertices 1, 3 have indices 0, 1 respectively graph.removeEdge(0, 1); cout << "\nAfter removing edge 1-3, graph is" << endl; graph.print(); /* Add vertex */ graph.addVertex(6); cout << "\nAfter adding vertex 6, graph is" << endl; graph.print(); /* Remove vertex */ // Vertex 3 has index 1 graph.removeVertex(1); cout << "\nAfter removing vertex 3, graph is" << endl; graph.print(); return 0; } ================================================ FILE: en/codes/cpp/chapter_graph/graph_bfs.cpp ================================================ /** * File: graph_bfs.cpp * Created Time: 2023-03-02 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" #include "./graph_adjacency_list.cpp" /* Breadth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex vector graphBFS(GraphAdjList &graph, Vertex *startVet) { // Vertex traversal sequence vector res; // Hash set for recording vertices that have been visited unordered_set visited = {startVet}; // Queue used to implement BFS queue que; que.push(startVet); // Starting from vertex vet, loop until all vertices are visited while (!que.empty()) { Vertex *vet = que.front(); que.pop(); // Dequeue the front vertex res.push_back(vet); // Record visited vertex // Traverse all adjacent vertices of this vertex for (auto adjVet : graph.adjList[vet]) { if (visited.count(adjVet)) continue; // Skip vertices that have been visited que.push(adjVet); // Only enqueue unvisited vertices visited.emplace(adjVet); // Mark this vertex as visited } } // Return vertex traversal sequence return res; } /* Driver Code */ int main() { /* Add edge */ 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 << "\nAfter initialization, graph is\\n"; graph.print(); /* Breadth-first traversal */ vector res = graphBFS(graph, v[0]); cout << "\nBreadth-first traversal (BFS) vertex sequence is" << endl; printVector(vetsToVals(res)); // Free memory for (Vertex *vet : v) { delete vet; } return 0; } ================================================ FILE: en/codes/cpp/chapter_graph/graph_dfs.cpp ================================================ /** * File: graph_dfs.cpp * Created Time: 2023-03-02 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" #include "./graph_adjacency_list.cpp" /* Depth-first traversal helper function */ void dfs(GraphAdjList &graph, unordered_set &visited, vector &res, Vertex *vet) { res.push_back(vet); // Record visited vertex visited.emplace(vet); // Mark this vertex as visited // Traverse all adjacent vertices of this vertex for (Vertex *adjVet : graph.adjList[vet]) { if (visited.count(adjVet)) continue; // Skip vertices that have been visited // Recursively visit adjacent vertices dfs(graph, visited, res, adjVet); } } /* Depth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex vector graphDFS(GraphAdjList &graph, Vertex *startVet) { // Vertex traversal sequence vector res; // Hash set for recording vertices that have been visited unordered_set visited; dfs(graph, visited, res, startVet); return res; } /* Driver Code */ int main() { /* Add edge */ 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 << "\nAfter initialization, graph is" << endl; graph.print(); /* Depth-first traversal */ vector res = graphDFS(graph, v[0]); cout << "\nDepth-first traversal (DFS) vertex sequence is" << endl; printVector(vetsToVals(res)); // Free memory for (Vertex *vet : v) { delete vet; } return 0; } ================================================ FILE: en/codes/cpp/chapter_greedy/CMakeLists.txt ================================================ add_executable(coin_change_greedy coin_change_greedy.cpp) add_executable(fractional_knapsack fractional_knapsack.cpp) add_executable(max_capacity max_capacity.cpp) ================================================ FILE: en/codes/cpp/chapter_greedy/coin_change_greedy.cpp ================================================ /** * File: coin_change_greedy.cpp * Created Time: 2023-07-20 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Coin change: Greedy algorithm */ int coinChangeGreedy(vector &coins, int amt) { // Assume coins list is sorted int i = coins.size() - 1; int count = 0; // Loop to make greedy choices until no remaining amount while (amt > 0) { // Find the coin that is less than and closest to the remaining amount while (i > 0 && coins[i] > amt) { i--; } // Choose coins[i] amt -= coins[i]; count++; } // If no feasible solution is found, return -1 return amt == 0 ? count : -1; } /* Driver Code */ int main() { // Greedy algorithm: Can guarantee finding the global optimal solution 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 << "Minimum number of coins needed to make " << amt << " is " << res << endl; // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = {1, 20, 50}; amt = 60; res = coinChangeGreedy(coins, amt); cout << "\ncoins = "; printVector(coins); cout << "amt = " << amt << endl; cout << "Minimum number of coins needed to make " << amt << " is " << res << endl; cout << "Actually the minimum number needed is 3, i.e., 20 + 20 + 20" << endl; // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = {1, 49, 50}; amt = 98; res = coinChangeGreedy(coins, amt); cout << "\ncoins = "; printVector(coins); cout << "amt = " << amt << endl; cout << "Minimum number of coins needed to make " << amt << " is " << res << endl; cout << "Actually the minimum number needed is 2, i.e., 49 + 49" << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_greedy/fractional_knapsack.cpp ================================================ /** * File: fractional_knapsack.cpp * Created Time: 2023-07-20 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Item */ class Item { public: int w; // Item weight int v; // Item value Item(int w, int v) : w(w), v(v) { } }; /* Fractional knapsack: Greedy algorithm */ double fractionalKnapsack(vector &wgt, vector &val, int cap) { // Create item list with two attributes: weight, value vector items; for (int i = 0; i < wgt.size(); i++) { items.push_back(Item(wgt[i], val[i])); } // Sort by unit value item.v / item.w from high to low sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; }); // Loop for greedy selection double res = 0; for (auto &item : items) { if (item.w <= cap) { // If remaining capacity is sufficient, put the entire current item into the knapsack res += item.v; cap -= item.w; } else { // If remaining capacity is insufficient, put part of the current item into the knapsack res += (double)item.v / item.w * cap; // No remaining capacity, so break out of the loop break; } } return res; } /* Driver Code */ int main() { vector wgt = {10, 20, 30, 40, 50}; vector val = {50, 120, 150, 210, 240}; int cap = 50; // Greedy algorithm double res = fractionalKnapsack(wgt, val, cap); cout << "Maximum item value not exceeding knapsack capacity is " << res << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_greedy/max_capacity.cpp ================================================ /** * File: max_capacity.cpp * Created Time: 2023-07-21 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Max capacity: Greedy algorithm */ int maxCapacity(vector &ht) { // Initialize i, j to be at both ends of the array int i = 0, j = ht.size() - 1; // Initial max capacity is 0 int res = 0; // Loop for greedy selection until the two boards meet while (i < j) { // Update max capacity int cap = min(ht[i], ht[j]) * (j - i); res = max(res, cap); // Move the shorter board inward if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ int main() { vector ht = {3, 8, 5, 2, 7, 7, 3, 4}; // Greedy algorithm int res = maxCapacity(ht); cout << "Maximum capacity is " << res << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_greedy/max_product_cutting.cpp ================================================ /** * File: max_product_cutting.cpp * Created Time: 2023-07-21 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Max product cutting: Greedy algorithm */ int maxProductCutting(int n) { // When n <= 3, must cut out a 1 if (n <= 3) { return 1 * (n - 1); } // Greedily cut out 3, a is the number of 3s, b is the remainder int a = n / 3; int b = n % 3; if (b == 1) { // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2 return (int)pow(3, a - 1) * 2 * 2; } if (b == 2) { // When the remainder is 2, do nothing return (int)pow(3, a) * 2; } // When the remainder is 0, do nothing return (int)pow(3, a); } /* Driver Code */ int main() { int n = 58; // Greedy algorithm int res = maxProductCutting(n); cout << "Maximum cutting product is" << res << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_hashing/CMakeLists.txt ================================================ add_executable(hash_map hash_map.cpp) add_executable(array_hash_map_test array_hash_map_test.cpp) add_executable(hash_map_chaining hash_map_chaining.cpp) add_executable(hash_map_open_addressing hash_map_open_addressing.cpp) add_executable(simple_hash simple_hash.cpp) add_executable(built_in_hash built_in_hash.cpp) ================================================ FILE: en/codes/cpp/chapter_hashing/array_hash_map.cpp ================================================ /** * File: array_hash_map.cpp * Created Time: 2022-12-14 * Author: msk397 (machangxinq@gmail.com) */ #include "../utils/common.hpp" /* Key-value pair */ struct Pair { public: int key; string val; Pair(int key, string val) { this->key = key; this->val = val; } }; /* Hash table based on array implementation */ class ArrayHashMap { private: vector buckets; public: ArrayHashMap() { // Initialize array with 100 buckets buckets = vector(100); } ~ArrayHashMap() { // Free memory for (const auto &bucket : buckets) { delete bucket; } buckets.clear(); } /* Hash function */ int hashFunc(int key) { int index = key % 100; return index; } /* Query operation */ string get(int key) { int index = hashFunc(key); Pair *pair = buckets[index]; if (pair == nullptr) return ""; return pair->val; } /* Add operation */ void put(int key, string val) { Pair *pair = new Pair(key, val); int index = hashFunc(key); buckets[index] = pair; } /* Remove operation */ void remove(int key) { int index = hashFunc(key); // Free memory and set to nullptr delete buckets[index]; buckets[index] = nullptr; } /* Get all key-value pairs */ vector pairSet() { vector pairSet; for (Pair *pair : buckets) { if (pair != nullptr) { pairSet.push_back(pair); } } return pairSet; } /* Get all keys */ vector keySet() { vector keySet; for (Pair *pair : buckets) { if (pair != nullptr) { keySet.push_back(pair->key); } } return keySet; } /* Get all values */ vector valueSet() { vector valueSet; for (Pair *pair : buckets) { if (pair != nullptr) { valueSet.push_back(pair->val); } } return valueSet; } /* Print hash table */ void print() { for (Pair *kv : pairSet()) { cout << kv->key << " -> " << kv->val << endl; } } }; // See array_hash_map_test.cpp for test cases ================================================ FILE: en/codes/cpp/chapter_hashing/array_hash_map_test.cpp ================================================ /** * File: array_hash_map_test.cpp * Created Time: 2022-12-14 * Author: msk397 (machangxinq@gmail.com) */ #include "./array_hash_map.cpp" /* Driver Code */ int main() { /* Initialize hash table */ ArrayHashMap map = ArrayHashMap(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(12836, "Xiao Ha"); map.put(15937, "Xiao Luo"); map.put(16750, "Xiao Suan"); map.put(13276, "Xiao Fa"); map.put(10583, "Xiao Ya"); cout << "\nAfter adding is complete, hash table is\nKey -> Value" << endl; map.print(); /* Query operation */ // Input key into hash table to get value string name = map.get(15937); cout << "\nInput student ID 15937, query name " << name << endl; /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(10583); cout << "\nAfter removing 10583, hash table is\nKey -> Value" << endl; map.print(); /* Traverse hash table */ cout << "\nTraverse key-value pairs Key->Value" << endl; for (auto kv : map.pairSet()) { cout << kv->key << " -> " << kv->val << endl; } cout << "\nTraverse keys only Key" << endl; for (auto key : map.keySet()) { cout << key << endl; } cout << "\nTraverse values only Value" << endl; for (auto val : map.valueSet()) { cout << val << endl; } return 0; } ================================================ FILE: en/codes/cpp/chapter_hashing/built_in_hash.cpp ================================================ /** * File: built_in_hash.cpp * Created Time: 2023-06-21 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { int num = 3; size_t hashNum = hash()(num); cout << "Hash value of integer " << num << " is " << hashNum << "\n"; bool bol = true; size_t hashBol = hash()(bol); cout << "Hash value of boolean " << bol << " is " << hashBol << "\n"; double dec = 3.14159; size_t hashDec = hash()(dec); cout << "Hash value of decimal " << dec << " is " << hashDec << "\n"; string str = "Hello Algo"; size_t hashStr = hash()(str); cout << "Hash value of string " << str << " is " << hashStr << "\n"; // In C++, built-in std::hash() only provides hash calculation for basic data types // Hash calculation for arrays and objects needs to be implemented manually } ================================================ FILE: en/codes/cpp/chapter_hashing/hash_map.cpp ================================================ /** * File: hash_map.cpp * Created Time: 2022-12-14 * Author: msk397 (machangxinq@gmail.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* Initialize hash table */ unordered_map map; /* Add operation */ // Add key-value pair (key, value) to the hash table map[12836] = "Xiao Ha"; map[15937] = "Xiao Luo"; map[16750] = "Xiao Suan"; map[13276] = "Xiao Fa"; map[10583] = "Xiao Ya"; cout << "\nAfter adding is complete, hash table is\nKey -> Value" << endl; printHashMap(map); /* Query operation */ // Input key into hash table to get value string name = map[15937]; cout << "\nInput student ID 15937, query name " << name << endl; /* Remove operation */ // Remove key-value pair (key, value) from hash table map.erase(10583); cout << "\nAfter removing 10583, hash table is\nKey -> Value" << endl; printHashMap(map); /* Traverse hash table */ cout << "\nTraverse key-value pairs Key->Value" << endl; for (auto kv : map) { cout << kv.first << " -> " << kv.second << endl; } cout << "\nTraverse Key->Value using iterator" << endl; for (auto iter = map.begin(); iter != map.end(); iter++) { cout << iter->first << "->" << iter->second << endl; } return 0; } ================================================ FILE: en/codes/cpp/chapter_hashing/hash_map_chaining.cpp ================================================ /** * File: hash_map_chaining.cpp * Created Time: 2023-06-13 * Author: krahets (krahets@163.com) */ #include "./array_hash_map.cpp" /* Hash table with separate chaining */ class HashMapChaining { private: int size; // Number of key-value pairs int capacity; // Hash table capacity double loadThres; // Load factor threshold for triggering expansion int extendRatio; // Expansion multiplier vector> buckets; // Bucket array public: /* Constructor */ HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) { buckets.resize(capacity); } /* Destructor */ ~HashMapChaining() { for (auto &bucket : buckets) { for (Pair *pair : bucket) { // Free memory delete pair; } } } /* Hash function */ int hashFunc(int key) { return key % capacity; } /* Load factor */ double loadFactor() { return (double)size / (double)capacity; } /* Query operation */ string get(int key) { int index = hashFunc(key); // Traverse bucket, if key is found, return corresponding val for (Pair *pair : buckets[index]) { if (pair->key == key) { return pair->val; } } // Return empty string if key not found return ""; } /* Add operation */ void put(int key, string val) { // When load factor exceeds threshold, perform expansion if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); // Traverse bucket, if specified key is encountered, update corresponding val and return for (Pair *pair : buckets[index]) { if (pair->key == key) { pair->val = val; return; } } // If key does not exist, append key-value pair to the end buckets[index].push_back(new Pair(key, val)); size++; } /* Remove operation */ void remove(int key) { int index = hashFunc(key); auto &bucket = buckets[index]; // Traverse bucket and remove key-value pair from it for (int i = 0; i < bucket.size(); i++) { if (bucket[i]->key == key) { Pair *tmp = bucket[i]; bucket.erase(bucket.begin() + i); // Remove key-value pair from it delete tmp; // Free memory size--; return; } } } /* Expand hash table */ void extend() { // Temporarily store the original hash table vector> bucketsTmp = buckets; // Initialize expanded new hash table capacity *= extendRatio; buckets.clear(); buckets.resize(capacity); size = 0; // Move key-value pairs from original hash table to new hash table for (auto &bucket : bucketsTmp) { for (Pair *pair : bucket) { put(pair->key, pair->val); // Free memory delete pair; } } } /* Print hash table */ void print() { for (auto &bucket : buckets) { cout << "["; for (Pair *pair : bucket) { cout << pair->key << " -> " << pair->val << ", "; } cout << "]\n"; } } }; /* Driver Code */ int main() { /* Initialize hash table */ HashMapChaining map = HashMapChaining(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(12836, "Xiao Ha"); map.put(15937, "Xiao Luo"); map.put(16750, "Xiao Suan"); map.put(13276, "Xiao Fa"); map.put(10583, "Xiao Ya"); cout << "\nAfter adding is complete, hash table is\nKey -> Value" << endl; map.print(); /* Query operation */ // Input key into hash table to get value string name = map.get(13276); cout << "\nInput student ID 13276, query name " << name << endl; /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(12836); cout << "\nAfter removing 12836, hash table is\nKey -> Value" << endl; map.print(); return 0; } ================================================ FILE: en/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp ================================================ /** * File: hash_map_open_addressing.cpp * Created Time: 2023-06-13 * Author: krahets (krahets@163.com) */ #include "./array_hash_map.cpp" /* Hash table with open addressing */ class HashMapOpenAddressing { private: int size; // Number of key-value pairs int capacity = 4; // Hash table capacity const double loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion const int extendRatio = 2; // Expansion multiplier vector buckets; // Bucket array Pair *TOMBSTONE = new Pair(-1, "-1"); // Removal marker public: /* Constructor */ HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) { } /* Destructor */ ~HashMapOpenAddressing() { for (Pair *pair : buckets) { if (pair != nullptr && pair != TOMBSTONE) { delete pair; } } delete TOMBSTONE; } /* Hash function */ int hashFunc(int key) { return key % capacity; } /* Load factor */ double loadFactor() { return (double)size / capacity; } /* Search for bucket index corresponding to key */ int findBucket(int key) { int index = hashFunc(key); int firstTombstone = -1; // Linear probing, break when encountering an empty bucket while (buckets[index] != nullptr) { // If key is encountered, return the corresponding bucket index if (buckets[index]->key == key) { // If a removal marker was encountered before, move the key-value pair to that index if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index]; buckets[index] = TOMBSTONE; return firstTombstone; // Return the moved bucket index } return index; // Return bucket index } // Record the first removal marker encountered if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index; } // Calculate bucket index, wrap around to the head if past the tail index = (index + 1) % capacity; } // If key does not exist, return the index for insertion return firstTombstone == -1 ? index : firstTombstone; } /* Query operation */ string get(int key) { // Search for bucket index corresponding to key int index = findBucket(key); // If key-value pair is found, return corresponding val if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { return buckets[index]->val; } // Return empty string if key-value pair does not exist return ""; } /* Add operation */ void put(int key, string val) { // When load factor exceeds threshold, perform expansion if (loadFactor() > loadThres) { extend(); } // Search for bucket index corresponding to key int index = findBucket(key); // If key-value pair is found, overwrite val and return if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { buckets[index]->val = val; return; } // If key-value pair does not exist, add the key-value pair buckets[index] = new Pair(key, val); size++; } /* Remove operation */ void remove(int key) { // Search for bucket index corresponding to key int index = findBucket(key); // If key-value pair is found, overwrite it with removal marker if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { delete buckets[index]; buckets[index] = TOMBSTONE; size--; } } /* Expand hash table */ void extend() { // Temporarily store the original hash table vector bucketsTmp = buckets; // Initialize expanded new hash table capacity *= extendRatio; buckets = vector(capacity, nullptr); size = 0; // Move key-value pairs from original hash table to new hash table for (Pair *pair : bucketsTmp) { if (pair != nullptr && pair != TOMBSTONE) { put(pair->key, pair->val); delete pair; } } } /* Print hash table */ 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; } } } }; /* Driver Code */ int main() { // Initialize hash table HashMapOpenAddressing hashmap; // Add operation // Add key-value pair (key, val) to the hash table hashmap.put(12836, "Xiao Ha"); hashmap.put(15937, "Xiao Luo"); hashmap.put(16750, "Xiao Suan"); hashmap.put(13276, "Xiao Fa"); hashmap.put(10583, "Xiao Ya"); cout << "\nAfter adding is complete, hash table is\nKey -> Value" << endl; hashmap.print(); // Query operation // Input key into hash table to get value val string name = hashmap.get(13276); cout << "\nInput student ID 13276, query name " << name << endl; // Remove operation // Remove key-value pair (key, val) from hash table hashmap.remove(16750); cout << "\nAfter removing 16750, hash table is\nKey -> Value" << endl; hashmap.print(); return 0; } ================================================ FILE: en/codes/cpp/chapter_hashing/simple_hash.cpp ================================================ /** * File: simple_hash.cpp * Created Time: 2023-06-21 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Additive hash */ 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; } /* Multiplicative 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 hash */ int xorHash(string key) { int hash = 0; const int MODULUS = 1000000007; for (unsigned char c : key) { hash ^= (int)c; } return hash & MODULUS; } /* Rotational hash */ 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; } /* Driver Code */ int main() { string key = "Hello Algo"; 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; } ================================================ FILE: en/codes/cpp/chapter_heap/CMakeLists.txt ================================================ add_executable(heap heap.cpp) add_executable(my_heap my_heap.cpp) add_executable(top_k top_k.cpp) ================================================ FILE: en/codes/cpp/chapter_heap/heap.cpp ================================================ /** * 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); // Element enters heap cout << "\nAfter element " << val << " pushes to heap" << endl; printHeap(heap); } void testPop(priority_queue &heap) { int val = heap.top(); heap.pop(); cout << "\nAfter heap top element " << val << " pops from heap" << endl; printHeap(heap); } /* Driver Code */ int main() { /* Initialize heap */ // Python's heapq module implements min heap by default // priority_queue, greater> minHeap; // Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap priority_queue, less> maxHeap; cout << "\nThe following test cases are for max heap" << endl; /* Element enters heap */ testPush(maxHeap, 1); testPush(maxHeap, 3); testPush(maxHeap, 2); testPush(maxHeap, 5); testPush(maxHeap, 4); /* Check if heap is empty */ int peek = maxHeap.top(); cout << "\nHeap top element is " << peek << endl; /* Time complexity is O(n), not O(nlogn) */ testPop(maxHeap); testPop(maxHeap); testPop(maxHeap); testPop(maxHeap); testPop(maxHeap); /* Get heap size */ int size = maxHeap.size(); cout << "\nHeap size is " << size << endl; /* Check if heap is empty */ bool isEmpty = maxHeap.empty(); cout << "\nIs heap empty " << isEmpty << endl; /* Input list and build heap */ // Time complexity is O(n), not O(nlogn) vector input{1, 3, 2, 5, 4}; priority_queue, greater> minHeap(input.begin(), input.end()); cout << "After input list and building min heap" << endl; printHeap(minHeap); return 0; } ================================================ FILE: en/codes/cpp/chapter_heap/my_heap.cpp ================================================ /** * File: my_heap.cpp * Created Time: 2023-02-04 * Author: LoneRanger (836253168@qq.com), what-is-me (whatisme@outlook.jp) */ #include "../utils/common.hpp" /* Max heap */ class MaxHeap { private: // Use dynamic array to avoid expansion issues vector maxHeap; /* Get index of left child node */ int left(int i) { return 2 * i + 1; } /* Get index of right child node */ int right(int i) { return 2 * i + 2; } /* Get index of parent node */ int parent(int i) { return (i - 1) / 2; // Floor division } /* Starting from node i, heapify from bottom to top */ void siftUp(int i) { while (true) { // Get parent node of node i int p = parent(i); // When "crossing root node" or "node needs no repair", end heapify if (p < 0 || maxHeap[i] <= maxHeap[p]) break; // Swap two nodes swap(maxHeap[i], maxHeap[p]); // Loop upward heapify i = p; } } /* Starting from node i, heapify from top to bottom */ void siftDown(int i) { while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break 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; // Swap two nodes if (ma == i) break; swap(maxHeap[i], maxHeap[ma]); // Loop downwards heapification i = ma; } } public: /* Constructor, build heap based on input list */ MaxHeap(vector nums) { // Add list elements to heap as is maxHeap = nums; // Heapify all nodes except leaf nodes for (int i = parent(size() - 1); i >= 0; i--) { siftDown(i); } } /* Get heap size */ int size() { return maxHeap.size(); } /* Check if heap is empty */ bool isEmpty() { return size() == 0; } /* Access top element */ int peek() { return maxHeap[0]; } /* Element enters heap */ void push(int val) { // Add node maxHeap.push_back(val); // Heapify from bottom to top siftUp(size() - 1); } /* Element exits heap */ void pop() { // Handle empty case if (isEmpty()) { throw out_of_range("Heap is empty"); } // Delete node swap(maxHeap[0], maxHeap[size() - 1]); // Remove node maxHeap.pop_back(); // Return top element siftDown(0); } /* Driver Code*/ void print() { cout << "Heap array representation:"; printVector(maxHeap); cout << "Heap tree representation:" << endl; TreeNode *root = vectorToTree(maxHeap); printTree(root); freeMemoryTree(root); } }; /* Driver Code */ int main() { /* Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap */ vector vec{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; MaxHeap maxHeap(vec); cout << "\nAfter inputting list and building heap" << endl; maxHeap.print(); /* Check if heap is empty */ int peek = maxHeap.peek(); cout << "\nHeap top element is " << peek << endl; /* Element enters heap */ int val = 7; maxHeap.push(val); cout << "\nAfter element " << val << " pushes to heap" << endl; maxHeap.print(); /* Time complexity is O(n), not O(nlogn) */ peek = maxHeap.peek(); maxHeap.pop(); cout << "\nAfter heap top element " << peek << " pops from heap" << endl; maxHeap.print(); /* Get heap size */ int size = maxHeap.size(); cout << "\nHeap size is " << size << endl; /* Check if heap is empty */ bool isEmpty = maxHeap.isEmpty(); cout << "\nIs heap empty " << isEmpty << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_heap/top_k.cpp ================================================ /** * File: top_k.cpp * Created Time: 2023-06-12 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Find the largest k elements in array based on heap */ priority_queue, greater> topKHeap(vector &nums, int k) { // Python's heapq module implements min heap by default priority_queue, greater> heap; // Enter the first k elements of array into heap for (int i = 0; i < k; i++) { heap.push(nums[i]); } // Starting from the (k+1)th element, maintain heap length as k for (int i = k; i < nums.size(); i++) { // If current element is greater than top element, top element exits heap, current element enters heap if (nums[i] > heap.top()) { heap.pop(); heap.push(nums[i]); } } return heap; } // Driver Code int main() { vector nums = {1, 7, 6, 3, 2}; int k = 3; priority_queue, greater> res = topKHeap(nums, k); cout << "The largest " << k << " elements are: "; printHeap(res); return 0; } ================================================ FILE: en/codes/cpp/chapter_searching/CMakeLists.txt ================================================ add_executable(binary_search binary_search.cpp) add_executable(binary_search_insertion binary_search_insertion.cpp) add_executable(binary_search_edge binary_search_edge.cpp) add_executable(two_sum two_sum.cpp) ================================================ FILE: en/codes/cpp/chapter_searching/binary_search.cpp ================================================ /** * File: binary_search.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Binary search (closed interval on both sides) */ int binarySearch(vector &nums, int target) { // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array int i = 0, j = nums.size() - 1; // Loop, exit when the search interval is empty (empty when i > j) while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) // This means target is in the interval [m+1, j] i = m + 1; else if (nums[m] > target) // This means target is in the interval [i, m-1] j = m - 1; else // Found the target element, return its index return m; } // Target element not found, return -1 return -1; } /* Binary search (left-closed right-open interval) */ int binarySearchLCRO(vector &nums, int target) { // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1 int i = 0, j = nums.size(); // Loop, exit when the search interval is empty (empty when i = j) while (i < j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) // This means target is in the interval [m+1, j) i = m + 1; else if (nums[m] > target) // This means target is in the interval [i, m) j = m; else // Found the target element, return its index return m; } // Target element not found, return -1 return -1; } /* Driver Code */ int main() { int target = 6; vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; /* Binary search (closed interval on both sides) */ int index = binarySearch(nums, target); cout << "Index of target element 6 = " << index << endl; /* Binary search (left-closed right-open interval) */ index = binarySearchLCRO(nums, target); cout << "Index of target element 6 = " << index << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_searching/binary_search_edge.cpp ================================================ /** * File: binary_search_edge.cpp * Created Time: 2023-08-04 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Binary search for insertion point (with duplicate elements) */ int binarySearchInsertion(const vector &nums, int target) { int i = 0, j = nums.size() - 1; // Initialize closed interval [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else { j = m - 1; // The first element less than target is in the interval [i, m-1] } } // Return insertion point i return i; } /* Binary search for the leftmost target */ int binarySearchLeftEdge(vector &nums, int target) { // Equivalent to finding the insertion point of target int i = binarySearchInsertion(nums, target); // Target not found, return -1 if (i == nums.size() || nums[i] != target) { return -1; } // Found target, return index i return i; } /* Binary search for the rightmost target */ int binarySearchRightEdge(vector &nums, int target) { // Convert to finding the leftmost target + 1 int i = binarySearchInsertion(nums, target + 1); // j points to the rightmost target, i points to the first element greater than target int j = i - 1; // Target not found, return -1 if (j == -1 || nums[j] != target) { return -1; } // Found target, return index j return j; } /* Driver Code */ int main() { // Array with duplicate elements vector nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; cout << "\nArray nums = "; printVector(nums); // Binary search left and right boundaries for (int target : {6, 7}) { int index = binarySearchLeftEdge(nums, target); cout << "Index of leftmost element " << target << " is " << index << endl; index = binarySearchRightEdge(nums, target); cout << "Index of rightmost element " << target << " is " << index << endl; } return 0; } ================================================ FILE: en/codes/cpp/chapter_searching/binary_search_insertion.cpp ================================================ /** * File: binary_search_insertion.cpp * Created Time: 2023-08-04 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Binary search for insertion point (no duplicate elements) */ int binarySearchInsertionSimple(vector &nums, int target) { int i = 0, j = nums.size() - 1; // Initialize closed interval [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { return m; // Found target, return insertion point m } } // Target not found, return insertion point i return i; } /* Binary search for insertion point (with duplicate elements) */ int binarySearchInsertion(vector &nums, int target) { int i = 0, j = nums.size() - 1; // Initialize closed interval [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { j = m - 1; // The first element less than target is in the interval [i, m-1] } } // Return insertion point i return i; } /* Driver Code */ int main() { // Array without duplicate elements vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; cout << "\nArray nums = "; printVector(nums); // Binary search for insertion point for (int target : {6, 9}) { int index = binarySearchInsertionSimple(nums, target); cout << "Insertion point index for element " << target << " is " << index << endl; } // Array with duplicate elements nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; cout << "\nArray nums = "; printVector(nums); // Binary search for insertion point for (int target : {2, 6, 20}) { int index = binarySearchInsertion(nums, target); cout << "Insertion point index for element " << target << " is " << index << endl; } return 0; } ================================================ FILE: en/codes/cpp/chapter_searching/hashing_search.cpp ================================================ /** * File: hashing_search.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Hash search (array) */ int hashingSearchArray(unordered_map map, int target) { // Hash table's key: target element, value: index // If this key does not exist in the hash table, return -1 if (map.find(target) == map.end()) return -1; return map[target]; } /* Hash search (linked list) */ ListNode *hashingSearchLinkedList(unordered_map map, int target) { // Hash table key: target node value, value: node object // Return nullptr if key does not exist in hash table if (map.find(target) == map.end()) return nullptr; return map[target]; } /* Driver Code */ int main() { int target = 3; /* Hash search (array) */ vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; // Initialize hash table unordered_map map; for (int i = 0; i < nums.size(); i++) { map[nums[i]] = i; // key: element, value: index } int index = hashingSearchArray(map, target); cout << "Index of target element 3 = " << index << endl; /* Hash search (linked list) */ ListNode *head = vecToLinkedList(nums); // Initialize hash table unordered_map map1; while (head != nullptr) { map1[head->val] = head; // key: node value, value: node head = head->next; } ListNode *node = hashingSearchLinkedList(map1, target); cout << "Node object corresponding to target node value 3 is " << node << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_searching/linear_search.cpp ================================================ /** * File: linear_search.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Linear search (array) */ int linearSearchArray(vector &nums, int target) { // Traverse array for (int i = 0; i < nums.size(); i++) { // Found the target element, return its index if (nums[i] == target) return i; } // Target element not found, return -1 return -1; } /* Linear search (linked list) */ ListNode *linearSearchLinkedList(ListNode *head, int target) { // Traverse the linked list while (head != nullptr) { // Found the target node, return it if (head->val == target) return head; head = head->next; } // Target node not found, return nullptr return nullptr; } /* Driver Code */ int main() { int target = 3; /* Perform linear search in array */ vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; int index = linearSearchArray(nums, target); cout << "Index of target element 3 = " << index << endl; /* Perform linear search in linked list */ ListNode *head = vecToLinkedList(nums); ListNode *node = linearSearchLinkedList(head, target); cout << "Node object corresponding to target node value 3 is " << node << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_searching/two_sum.cpp ================================================ /** * File: two_sum.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Method 1: Brute force enumeration */ vector twoSumBruteForce(vector &nums, int target) { int size = nums.size(); // Two nested loops, time complexity is 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 {}; } /* Method 2: Auxiliary hash table */ vector twoSumHashTable(vector &nums, int target) { int size = nums.size(); // Auxiliary hash table, space complexity is O(n) unordered_map dic; // Single loop, time complexity is 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 {}; } /* Driver Code */ int main() { // ======= Test Case ======= vector nums = {2, 7, 11, 15}; int target = 13; // ====== Driver Code ====== // Method 1 vector res = twoSumBruteForce(nums, target); cout << "Method 1 res = "; printVector(res); // Method 2 res = twoSumHashTable(nums, target); cout << "Method 2 res = "; printVector(res); return 0; } ================================================ FILE: en/codes/cpp/chapter_sorting/CMakeLists.txt ================================================ add_executable(selection_sort selection_sort.cpp) add_executable(bubble_sort bubble_sort.cpp) add_executable(insertion_sort insertion_sort.cpp) add_executable(merge_sort merge_sort.cpp) add_executable(quick_sort quick_sort.cpp) add_executable(heap_sort heap_sort.cpp) ================================================ FILE: en/codes/cpp/chapter_sorting/bubble_sort.cpp ================================================ /** * File: bubble_sort.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Bubble sort */ void bubbleSort(vector &nums) { // Outer loop: unsorted range is [0, i] for (int i = nums.size() - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] // Using std::swap() function here swap(nums[j], nums[j + 1]); } } } } /* Bubble sort (flag optimization)*/ void bubbleSortWithFlag(vector &nums) { // Outer loop: unsorted range is [0, i] for (int i = nums.size() - 1; i > 0; i--) { bool flag = false; // Initialize flag // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] // Using std::swap() function here swap(nums[j], nums[j + 1]); flag = true; // Record element swap } } if (!flag) break; // No elements were swapped in this round of "bubbling", exit directly } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; bubbleSort(nums); cout << "After bubble sort completes, nums = "; printVector(nums); vector nums1 = {4, 1, 3, 1, 5, 2}; bubbleSortWithFlag(nums1); cout << "After bubble sort completes, nums1 = "; printVector(nums1); return 0; } ================================================ FILE: en/codes/cpp/chapter_sorting/bucket_sort.cpp ================================================ /** * File: bucket_sort.cpp * Created Time: 2023-03-30 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Bucket sort */ void bucketSort(vector &nums) { // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket int k = nums.size() / 2; vector> buckets(k); // 1. Distribute array elements into various buckets for (float num : nums) { // Input data range is [0, 1), use num * k to map to index range [0, k-1] int i = num * k; // Add num to bucket bucket_idx buckets[i].push_back(num); } // 2. Sort each bucket for (vector &bucket : buckets) { // Use built-in sorting function, can also replace with other sorting algorithms sort(bucket.begin(), bucket.end()); } // 3. Traverse buckets to merge results int i = 0; for (vector &bucket : buckets) { for (float num : bucket) { nums[i++] = num; } } } /* Driver Code */ int main() { // Assume input data is floating point, interval [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 << "After bucket sort completes, nums = "; printVector(nums); return 0; } ================================================ FILE: en/codes/cpp/chapter_sorting/counting_sort.cpp ================================================ /** * File: counting_sort.cpp * Created Time: 2023-03-17 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Counting sort */ // Simple implementation, cannot be used for sorting objects void countingSortNaive(vector &nums) { // 1. Count the maximum element m in the array int m = 0; for (int num : nums) { m = max(m, num); } // 2. Count the occurrence of each number // counter[num] represents the occurrence of num vector counter(m + 1, 0); for (int num : nums) { counter[num]++; } // 3. Traverse counter, filling each element back into the original array nums int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* Counting sort */ // Complete implementation, can sort objects and is a stable sort void countingSort(vector &nums) { // 1. Count the maximum element m in the array int m = 0; for (int num : nums) { m = max(m, num); } // 2. Count the occurrence of each number // counter[num] represents the occurrence of num vector counter(m + 1, 0); for (int num : nums) { counter[num]++; } // 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" // counter[num]-1 is the last index where num appears in res for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. Traverse nums in reverse order, placing each element into the result array res // Initialize the array res to record results int n = nums.size(); vector res(n); for (int i = n - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // Place num at the corresponding index counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num } // Use result array res to overwrite the original array nums nums = res; } /* Driver Code */ int main() { vector nums = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; countingSortNaive(nums); cout << "After counting sort (cannot sort objects) completes, nums = "; printVector(nums); vector nums1 = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; countingSort(nums1); cout << "After counting sort completes, nums1 = "; printVector(nums1); return 0; } ================================================ FILE: en/codes/cpp/chapter_sorting/heap_sort.cpp ================================================ /** * File: heap_sort.cpp * Created Time: 2023-05-26 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Heap length is n, start heapifying node i, from top to bottom */ void siftDown(vector &nums, int n, int i) { while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break 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; // Swap two nodes if (ma == i) { break; } // Swap two nodes swap(nums[i], nums[ma]); // Loop downwards heapification i = ma; } } /* Heap sort */ void heapSort(vector &nums) { // Build heap operation: heapify all nodes except leaves for (int i = nums.size() / 2 - 1; i >= 0; --i) { siftDown(nums, nums.size(), i); } // Extract the largest element from the heap and repeat for n-1 rounds for (int i = nums.size() - 1; i > 0; --i) { // Delete node swap(nums[0], nums[i]); // Start heapifying the root node, from top to bottom siftDown(nums, i, 0); } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; heapSort(nums); cout << "After heap sort completes, nums = "; printVector(nums); return 0; } ================================================ FILE: en/codes/cpp/chapter_sorting/insertion_sort.cpp ================================================ /** * File: insertion_sort.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Insertion sort */ void insertionSort(vector &nums) { // Outer loop: sorted interval is [0, i-1] for (int i = 1; i < nums.size(); i++) { int base = nums[i], j = i - 1; // Inner loop: insert base into the correct position within the sorted interval [0, i-1] while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // Move nums[j] to the right by one position j--; } nums[j + 1] = base; // Assign base to the correct position } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; insertionSort(nums); cout << "After insertion sort completes, nums = "; printVector(nums); return 0; } ================================================ FILE: en/codes/cpp/chapter_sorting/merge_sort.cpp ================================================ /** * File: merge_sort.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Merge left subarray and right subarray */ void merge(vector &nums, int left, int mid, int right) { // Left subarray interval is [left, mid], right subarray interval is [mid+1, right] // Create a temporary array tmp to store the merged results vector tmp(right - left + 1); // Initialize the start indices of the left and right subarrays int i = left, j = mid + 1, k = 0; // While both subarrays still have elements, compare and copy the smaller element into the temporary array while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } // Copy the remaining elements of the left and right subarrays into the temporary array while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval for (k = 0; k < tmp.size(); k++) { nums[left + k] = tmp[k]; } } /* Merge sort */ void mergeSort(vector &nums, int left, int right) { // Termination condition if (left >= right) return; // Terminate recursion when subarray length is 1 // Divide and conquer stage int mid = left + (right - left) / 2; // Calculate midpoint mergeSort(nums, left, mid); // Recursively process the left subarray mergeSort(nums, mid + 1, right); // Recursively process the right subarray // Merge stage merge(nums, left, mid, right); } /* Driver Code */ int main() { /* Merge sort */ vector nums = {7, 3, 2, 6, 0, 1, 5, 4}; mergeSort(nums, 0, nums.size() - 1); cout << "After merge sort completes, nums = "; printVector(nums); return 0; } ================================================ FILE: en/codes/cpp/chapter_sorting/quick_sort.cpp ================================================ /** * File: quick_sort.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Quick sort class */ class QuickSort { private: /* Sentinel partition */ static int partition(vector &nums, int left, int right) { // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot swap(nums[i], nums[j]); // Swap these two elements } swap(nums[i], nums[left]); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } public: /* Quick sort */ static void quickSort(vector &nums, int left, int right) { // Terminate recursion when subarray length is 1 if (left >= right) return; // Sentinel partition int pivot = partition(nums, left, right); // Recursively process the left subarray and right subarray quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } }; /* Quick sort class (median pivot optimization) */ class QuickSortMedian { private: /* Select the median of three candidate elements */ 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 is between l and r if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l is between m and r return right; } /* Sentinel partition (median of three) */ static int partition(vector &nums, int left, int right) { // Select the median of three candidate elements int med = medianThree(nums, left, (left + right) / 2, right); // Swap the median to the array's leftmost position swap(nums[left], nums[med]); // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot swap(nums[i], nums[j]); // Swap these two elements } swap(nums[i], nums[left]); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } public: /* Quick sort */ static void quickSort(vector &nums, int left, int right) { // Terminate recursion when subarray length is 1 if (left >= right) return; // Sentinel partition int pivot = partition(nums, left, right); // Recursively process the left subarray and right subarray quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } }; /* Quick sort class (recursion depth optimization) */ class QuickSortTailCall { private: /* Sentinel partition */ static int partition(vector &nums, int left, int right) { // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot swap(nums[i], nums[j]); // Swap these two elements } swap(nums[i], nums[left]); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } public: /* Quick sort (recursion depth optimization) */ static void quickSort(vector &nums, int left, int right) { // Terminate when subarray length is 1 while (left < right) { // Sentinel partition operation int pivot = partition(nums, left, right); // Perform quick sort on the shorter of the two subarrays if (pivot - left < right - pivot) { quickSort(nums, left, pivot - 1); // Recursively sort the left subarray left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right] } else { quickSort(nums, pivot + 1, right); // Recursively sort the right subarray right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1] } } } }; /* Driver Code */ int main() { /* Quick sort */ vector nums{2, 4, 1, 0, 3, 5}; QuickSort::quickSort(nums, 0, nums.size() - 1); cout << "After quick sort completes, nums = "; printVector(nums); /* Quick sort (recursion depth optimization) */ vector nums1 = {2, 4, 1, 0, 3, 5}; QuickSortMedian::quickSort(nums1, 0, nums1.size() - 1); cout << "After quick sort (median pivot optimization), nums = "; printVector(nums1); /* Quick sort (recursion depth optimization) */ vector nums2 = {2, 4, 1, 0, 3, 5}; QuickSortTailCall::quickSort(nums2, 0, nums2.size() - 1); cout << "After quick sort (recursion depth optimization), nums = "; printVector(nums2); return 0; } ================================================ FILE: en/codes/cpp/chapter_sorting/radix_sort.cpp ================================================ /** * File: radix_sort.cpp * Created Time: 2023-03-26 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Get the k-th digit of element num, where exp = 10^(k-1) */ int digit(int num, int exp) { // Passing exp instead of k can avoid repeated expensive exponentiation here return (num / exp) % 10; } /* Counting sort (based on nums k-th digit) */ void countingSortDigit(vector &nums, int exp) { // Decimal digit range is 0~9, therefore need a bucket array of length 10 vector counter(10, 0); int n = nums.size(); // Count the occurrence of digits 0~9 for (int i = 0; i < n; i++) { int d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d counter[d]++; // Count the occurrence of digit d } // Calculate prefix sum, converting "occurrence count" into "array index" for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // Traverse in reverse, based on bucket statistics, place each element into res vector res(n, 0); for (int i = n - 1; i >= 0; i--) { int d = digit(nums[i], exp); int j = counter[d] - 1; // Get the index j for d in the array res[j] = nums[i]; // Place the current element at index j counter[d]--; // Decrease the count of d by 1 } // Use result to overwrite the original array nums for (int i = 0; i < n; i++) nums[i] = res[i]; } /* Radix sort */ void radixSort(vector &nums) { // Get the maximum element of the array, used to determine the maximum number of digits int m = *max_element(nums.begin(), nums.end()); // Traverse from the lowest to the highest digit for (int exp = 1; exp <= m; exp *= 10) // Perform counting sort on the k-th digit of array elements // k = 1 -> exp = 1 // k = 2 -> exp = 10 // i.e., exp = 10^(k-1) countingSortDigit(nums, exp); } /* Driver Code */ int main() { // Radix sort vector nums = {10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996}; radixSort(nums); cout << "After radix sort completes, nums = "; printVector(nums); return 0; } ================================================ FILE: en/codes/cpp/chapter_sorting/selection_sort.cpp ================================================ /** * File: selection_sort.cpp * Created Time: 2023-05-23 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Selection sort */ void selectionSort(vector &nums) { int n = nums.size(); // Outer loop: unsorted interval is [i, n-1] for (int i = 0; i < n - 1; i++) { // Inner loop: find the smallest element within the unsorted interval int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // Record the index of the smallest element } // Swap the smallest element with the first element of the unsorted interval swap(nums[i], nums[k]); } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; selectionSort(nums); cout << "After selection sort completes, nums = "; printVector(nums); return 0; } ================================================ FILE: en/codes/cpp/chapter_stack_and_queue/CMakeLists.txt ================================================ add_executable(array_deque array_deque.cpp) add_executable(array_queue array_queue.cpp) add_executable(array_stack array_stack.cpp) add_executable(deque deque.cpp) add_executable(linkedlist_deque linkedlist_deque.cpp) add_executable(linkedlist_queue linkedlist_queue.cpp) add_executable(linkedlist_stack linkedlist_stack.cpp) add_executable(queue queue.cpp) add_executable(stack stack.cpp) ================================================ FILE: en/codes/cpp/chapter_stack_and_queue/array_deque.cpp ================================================ /** * File: array_deque.cpp * Created Time: 2023-03-02 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Double-ended queue based on circular array implementation */ class ArrayDeque { private: vector nums; // Array for storing double-ended queue elements int front; // Front pointer, points to the front of the queue element int queSize; // Double-ended queue length public: /* Constructor */ ArrayDeque(int capacity) { nums.resize(capacity); front = queSize = 0; } /* Get the capacity of the double-ended queue */ int capacity() { return nums.size(); } /* Get the length of the double-ended queue */ int size() { return queSize; } /* Check if the double-ended queue is empty */ bool isEmpty() { return queSize == 0; } /* Calculate circular array index */ int index(int i) { // Use modulo operation to wrap the array head and tail together // When i passes the tail of the array, return to the head // When i passes the head of the array, return to the tail return (i + capacity()) % capacity(); } /* Front of the queue enqueue */ void pushFirst(int num) { if (queSize == capacity()) { cout << "Double-ended queue is full" << endl; return; } // Use modulo operation to wrap front around to the tail after passing the head of the array // Add num to the front of the queue front = index(front - 1); // Add num to front of queue nums[front] = num; queSize++; } /* Rear of the queue enqueue */ void pushLast(int num) { if (queSize == capacity()) { cout << "Double-ended queue is full" << endl; return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array int rear = index(front + queSize); // Front pointer moves one position backward nums[rear] = num; queSize++; } /* Rear of the queue dequeue */ int popFirst() { int num = peekFirst(); // Move front pointer backward by one position front = index(front + 1); queSize--; return num; } /* Access rear of the queue element */ int popLast() { int num = peekLast(); queSize--; return num; } /* Return list for printing */ int peekFirst() { if (isEmpty()) throw out_of_range("Deque is empty"); return nums[front]; } /* Driver Code */ int peekLast() { if (isEmpty()) throw out_of_range("Deque is empty"); // Initialize double-ended queue int last = index(front + queSize - 1); return nums[last]; } /* Return array for printing */ vector toVector() { // Elements enqueue vector res(queSize); for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[index(j)]; } return res; } }; /* Driver Code */ int main() { /* Get the length of the double-ended queue */ ArrayDeque *deque = new ArrayDeque(10); deque->pushLast(3); deque->pushLast(2); deque->pushLast(5); cout << "Double-ended queue deque = "; printVector(deque->toVector()); /* Update element */ int peekFirst = deque->peekFirst(); cout << "Front element peekFirst = " << peekFirst << endl; int peekLast = deque->peekLast(); cout << "Rear element peekLast = " << peekLast << endl; /* Elements enqueue */ deque->pushLast(4); cout << "After element 4 enqueues at rear, deque = "; printVector(deque->toVector()); deque->pushFirst(1); cout << "After element 1 enqueues at front, deque = "; printVector(deque->toVector()); /* Element dequeue */ int popLast = deque->popLast(); cout << "Rear dequeue element = " << popLast << ", after rear dequeue, deque = "; printVector(deque->toVector()); int popFirst = deque->popFirst(); cout << "Front dequeue element = " << popFirst << ", after front dequeue, deque = "; printVector(deque->toVector()); /* Get the length of the double-ended queue */ int size = deque->size(); cout << "Double-ended queue length size = " << size << endl; /* Check if the double-ended queue is empty */ bool isEmpty = deque->isEmpty(); cout << "Double-ended queue is empty = " << boolalpha << isEmpty << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_stack_and_queue/array_queue.cpp ================================================ /** * File: array_queue.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Queue based on circular array implementation */ class ArrayQueue { private: int *nums; // Array for storing queue elements int front; // Front pointer, points to the front of the queue element int queSize; // Queue length int queCapacity; // Queue capacity public: ArrayQueue(int capacity) { // Initialize array nums = new int[capacity]; queCapacity = capacity; front = queSize = 0; } ~ArrayQueue() { delete[] nums; } /* Get the capacity of the queue */ int capacity() { return queCapacity; } /* Get the length of the queue */ int size() { return queSize; } /* Check if the queue is empty */ bool isEmpty() { return size() == 0; } /* Enqueue */ void push(int num) { if (queSize == queCapacity) { cout << "Queue is full" << endl; return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array // Add num to the rear of the queue int rear = (front + queSize) % queCapacity; // Front pointer moves one position backward nums[rear] = num; queSize++; } /* Dequeue */ int pop() { int num = peek(); // Move front pointer backward by one position, if it passes the tail, return to array head front = (front + 1) % queCapacity; queSize--; return num; } /* Return list for printing */ int peek() { if (isEmpty()) throw out_of_range("Queue is empty"); return nums[front]; } /* Convert array to Vector and return */ vector toVector() { // Elements enqueue vector arr(queSize); for (int i = 0, j = front; i < queSize; i++, j++) { arr[i] = nums[j % queCapacity]; } return arr; } }; /* Driver Code */ int main() { /* Access front of the queue element */ int capacity = 10; ArrayQueue *queue = new ArrayQueue(capacity); /* Elements enqueue */ queue->push(1); queue->push(3); queue->push(2); queue->push(5); queue->push(4); cout << "Queue queue = "; printVector(queue->toVector()); /* Return list for printing */ int peek = queue->peek(); cout << "Front element peek = " << peek << endl; /* Element dequeue */ peek = queue->pop(); cout << "Dequeue element pop = " << peek << ", after dequeue, queue = "; printVector(queue->toVector()); /* Get the length of the queue */ int size = queue->size(); cout << "Queue length size = " << size << endl; /* Check if the queue is empty */ bool empty = queue->isEmpty(); cout << "Queue is empty = " << empty << endl; /* Test circular array */ for (int i = 0; i < 10; i++) { queue->push(i); queue->pop(); cout << "After round " << i << " enqueue + dequeue, queue = "; printVector(queue->toVector()); } // Free memory delete queue; return 0; } ================================================ FILE: en/codes/cpp/chapter_stack_and_queue/array_stack.cpp ================================================ /** * File: array_stack.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) */ #include "../utils/common.hpp" /* Stack based on array implementation */ class ArrayStack { private: vector stack; public: /* Get the length of the stack */ int size() { return stack.size(); } /* Check if the stack is empty */ bool isEmpty() { return stack.size() == 0; } /* Push */ void push(int num) { stack.push_back(num); } /* Pop */ int pop() { int num = top(); stack.pop_back(); return num; } /* Return list for printing */ int top() { if (isEmpty()) throw out_of_range("Stack is empty"); return stack.back(); } /* Return Vector */ vector toVector() { return stack; } }; /* Driver Code */ int main() { /* Access top of the stack element */ ArrayStack *stack = new ArrayStack(); /* Elements push onto stack */ stack->push(1); stack->push(3); stack->push(2); stack->push(5); stack->push(4); cout << "Stack stack = "; printVector(stack->toVector()); /* Return list for printing */ int top = stack->top(); cout << "Stack top element top = " << top << endl; /* Element pop from stack */ top = stack->pop(); cout << "Pop element pop = " << top << ", after pop, stack = "; printVector(stack->toVector()); /* Get the length of the stack */ int size = stack->size(); cout << "Stack length size = " << size << endl; /* Check if empty */ bool empty = stack->isEmpty(); cout << "Stack is empty = " << empty << endl; // Free memory delete stack; return 0; } ================================================ FILE: en/codes/cpp/chapter_stack_and_queue/deque.cpp ================================================ /** * File: deque.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* Get the length of the double-ended queue */ deque deque; /* Elements enqueue */ 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); /* Update element */ int front = deque.front(); cout << "Front element front = " << front << endl; int back = deque.back(); cout << "Back element back = " << back << endl; /* Element dequeue */ deque.pop_front(); cout << "Front dequeue element popFront = " << front << ", after front dequeue, deque = "; printDeque(deque); deque.pop_back(); cout << "Rear dequeue element popLast = " << back << ", after rear dequeue, deque = "; printDeque(deque); /* Get the length of the double-ended queue */ int size = deque.size(); cout << "Double-ended queue length size = " << size << endl; /* Check if the double-ended queue is empty */ bool empty = deque.empty(); cout << "Double-ended queue is empty = " << empty << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp ================================================ /** * File: linkedlist_deque.cpp * Created Time: 2023-03-02 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Doubly linked list node */ struct DoublyListNode { int val; // Node value DoublyListNode *next; // Successor node pointer DoublyListNode *prev; // Predecessor node pointer DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) { } }; /* Double-ended queue based on doubly linked list implementation */ class LinkedListDeque { private: DoublyListNode *front, *rear; // Head node front, tail node rear int queSize = 0; // Length of the double-ended queue public: /* Constructor */ LinkedListDeque() : front(nullptr), rear(nullptr) { } /* Destructor */ ~LinkedListDeque() { // Traverse linked list to delete nodes and free memory DoublyListNode *pre, *cur = front; while (cur != nullptr) { pre = cur; cur = cur->next; delete pre; } } /* Get the length of the double-ended queue */ int size() { return queSize; } /* Check if the double-ended queue is empty */ bool isEmpty() { return size() == 0; } /* Enqueue operation */ void push(int num, bool isFront) { DoublyListNode *node = new DoublyListNode(num); // If the linked list is empty, make both front and rear point to node if (isEmpty()) front = rear = node; // Front of the queue enqueue operation else if (isFront) { // Add node to the head of the linked list front->prev = node; node->next = front; front = node; // Update head node // Rear of the queue enqueue operation } else { // Add node to the tail of the linked list rear->next = node; node->prev = rear; rear = node; // Update tail node } queSize++; // Update queue length } /* Front of the queue enqueue */ void pushFirst(int num) { push(num, true); } /* Rear of the queue enqueue */ void pushLast(int num) { push(num, false); } /* Dequeue operation */ int pop(bool isFront) { if (isEmpty()) throw out_of_range("Queue is empty"); int val; // Temporarily store head node value if (isFront) { val = front->val; // Delete head node // Delete head node DoublyListNode *fNext = front->next; if (fNext != nullptr) { fNext->prev = nullptr; front->next = nullptr; } delete front; front = fNext; // Update head node // Temporarily store tail node value } else { val = rear->val; // Delete tail node // Update tail node DoublyListNode *rPrev = rear->prev; if (rPrev != nullptr) { rPrev->next = nullptr; rear->prev = nullptr; } delete rear; rear = rPrev; // Update tail node } queSize--; // Update queue length return val; } /* Rear of the queue dequeue */ int popFirst() { return pop(true); } /* Access rear of the queue element */ int popLast() { return pop(false); } /* Return list for printing */ int peekFirst() { if (isEmpty()) throw out_of_range("Deque is empty"); return front->val; } /* Driver Code */ int peekLast() { if (isEmpty()) throw out_of_range("Deque is empty"); return rear->val; } /* Return array for printing */ 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; } }; /* Driver Code */ int main() { /* Get the length of the double-ended queue */ LinkedListDeque *deque = new LinkedListDeque(); deque->pushLast(3); deque->pushLast(2); deque->pushLast(5); cout << "Double-ended queue deque = "; printVector(deque->toVector()); /* Update element */ int peekFirst = deque->peekFirst(); cout << "Front element peekFirst = " << peekFirst << endl; int peekLast = deque->peekLast(); cout << "Rear element peekLast = " << peekLast << endl; /* Elements enqueue */ deque->pushLast(4); cout << "After element 4 enqueues at back, deque ="; printVector(deque->toVector()); deque->pushFirst(1); cout << "After element 1 enqueues at front, deque = "; printVector(deque->toVector()); /* Element dequeue */ int popLast = deque->popLast(); cout << "Rear dequeue element = " << popLast << ", after rear dequeue, deque = "; printVector(deque->toVector()); int popFirst = deque->popFirst(); cout << "Front dequeue element = " << popFirst << ", after front dequeue, deque = "; printVector(deque->toVector()); /* Get the length of the double-ended queue */ int size = deque->size(); cout << "Double-ended queue length size = " << size << endl; /* Check if the double-ended queue is empty */ bool isEmpty = deque->isEmpty(); cout << "Double-ended queue is empty = " << boolalpha << isEmpty << endl; // Free memory delete deque; return 0; } ================================================ FILE: en/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp ================================================ /** * File: linkedlist_queue.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Queue based on linked list implementation */ class LinkedListQueue { private: ListNode *front, *rear; // Head node front, tail node rear int queSize; public: LinkedListQueue() { front = nullptr; rear = nullptr; queSize = 0; } ~LinkedListQueue() { // Traverse linked list to delete nodes and free memory freeMemoryLinkedList(front); } /* Get the length of the queue */ int size() { return queSize; } /* Check if the queue is empty */ bool isEmpty() { return queSize == 0; } /* Enqueue */ void push(int num) { // Add num after the tail node ListNode *node = new ListNode(num); // If the queue is empty, make both front and rear point to the node if (front == nullptr) { front = node; rear = node; } // If the queue is not empty, add the node after the tail node else { rear->next = node; rear = node; } queSize++; } /* Dequeue */ int pop() { int num = peek(); // Delete head node ListNode *tmp = front; front = front->next; // Free memory delete tmp; queSize--; return num; } /* Return list for printing */ int peek() { if (size() == 0) throw out_of_range("Queue is empty"); return front->val; } /* Convert linked list to Vector and return */ 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; } }; /* Driver Code */ int main() { /* Access front of the queue element */ LinkedListQueue *queue = new LinkedListQueue(); /* Elements enqueue */ queue->push(1); queue->push(3); queue->push(2); queue->push(5); queue->push(4); cout << "Queue queue = "; printVector(queue->toVector()); /* Return list for printing */ int peek = queue->peek(); cout << "Front element peek = " << peek << endl; /* Element dequeue */ peek = queue->pop(); cout << "Dequeue element pop = " << peek << ", after dequeue, queue = "; printVector(queue->toVector()); /* Get the length of the queue */ int size = queue->size(); cout << "Queue length size = " << size << endl; /* Check if the queue is empty */ bool empty = queue->isEmpty(); cout << "Queue is empty = " << empty << endl; // Free memory delete queue; return 0; } ================================================ FILE: en/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp ================================================ /** * File: linkedlist_stack.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) */ #include "../utils/common.hpp" /* Stack based on linked list implementation */ class LinkedListStack { private: ListNode *stackTop; // Use head node as stack top int stkSize; // Stack length public: LinkedListStack() { stackTop = nullptr; stkSize = 0; } ~LinkedListStack() { // Traverse linked list to delete nodes and free memory freeMemoryLinkedList(stackTop); } /* Get the length of the stack */ int size() { return stkSize; } /* Check if the stack is empty */ bool isEmpty() { return size() == 0; } /* Push */ void push(int num) { ListNode *node = new ListNode(num); node->next = stackTop; stackTop = node; stkSize++; } /* Pop */ int pop() { int num = top(); ListNode *tmp = stackTop; stackTop = stackTop->next; // Free memory delete tmp; stkSize--; return num; } /* Return list for printing */ int top() { if (isEmpty()) throw out_of_range("Stack is empty"); return stackTop->val; } /* Convert List to Array and return */ 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; } }; /* Driver Code */ int main() { /* Access top of the stack element */ LinkedListStack *stack = new LinkedListStack(); /* Elements push onto stack */ stack->push(1); stack->push(3); stack->push(2); stack->push(5); stack->push(4); cout << "Stack stack = "; printVector(stack->toVector()); /* Return list for printing */ int top = stack->top(); cout << "Stack top element top = " << top << endl; /* Element pop from stack */ top = stack->pop(); cout << "Pop element pop = " << top << ", after pop, stack = "; printVector(stack->toVector()); /* Get the length of the stack */ int size = stack->size(); cout << "Stack length size = " << size << endl; /* Check if empty */ bool empty = stack->isEmpty(); cout << "Stack is empty = " << empty << endl; // Free memory delete stack; return 0; } ================================================ FILE: en/codes/cpp/chapter_stack_and_queue/queue.cpp ================================================ /** * File: queue.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* Access front of the queue element */ queue queue; /* Elements enqueue */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); cout << "Queue queue = "; printQueue(queue); /* Return list for printing */ int front = queue.front(); cout << "Front element front = " << front << endl; /* Element dequeue */ queue.pop(); cout << "Dequeue element front = " << front << ", after dequeue, queue = "; printQueue(queue); /* Get the length of the queue */ int size = queue.size(); cout << "Queue length size = " << size << endl; /* Check if the queue is empty */ bool empty = queue.empty(); cout << "Queue is empty = " << empty << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_stack_and_queue/stack.cpp ================================================ /** * File: stack.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* Access top of the stack element */ stack stack; /* Elements push onto stack */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); cout << "Stack stack = "; printStack(stack); /* Return list for printing */ int top = stack.top(); cout << "Stack top element top = " << top << endl; /* Element pop from stack */ stack.pop(); // No return value cout << "Pop element pop = " << top << ", after pop, stack = "; printStack(stack); /* Get the length of the stack */ int size = stack.size(); cout << "Stack length size = " << size << endl; /* Check if empty */ bool empty = stack.empty(); cout << "Stack is empty = " << empty << endl; return 0; } ================================================ FILE: en/codes/cpp/chapter_tree/CMakeLists.txt ================================================ add_executable(avl_tree avl_tree.cpp) add_executable(binary_search_tree binary_search_tree.cpp) add_executable(binary_tree binary_tree.cpp) add_executable(binary_tree_bfs binary_tree_bfs.cpp) add_executable(binary_tree_dfs binary_tree_dfs.cpp) add_executable(array_binary_tree array_binary_tree.cpp) ================================================ FILE: en/codes/cpp/chapter_tree/array_binary_tree.cpp ================================================ /** * File: array_binary_tree.cpp * Created Time: 2023-07-19 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Binary tree class represented by array */ class ArrayBinaryTree { public: /* Constructor */ ArrayBinaryTree(vector arr) { tree = arr; } /* List capacity */ int size() { return tree.size(); } /* Get value of node at index i */ int val(int i) { // Return INT_MAX if index out of bounds, representing empty position if (i < 0 || i >= size()) return INT_MAX; return tree[i]; } /* Get index of left child node of node at index i */ int left(int i) { return 2 * i + 1; } /* Get index of right child node of node at index i */ int right(int i) { return 2 * i + 2; } /* Get index of parent node of node at index i */ int parent(int i) { return (i - 1) / 2; } /* Level-order traversal */ vector levelOrder() { vector res; // Traverse array directly for (int i = 0; i < size(); i++) { if (val(i) != INT_MAX) res.push_back(val(i)); } return res; } /* Preorder traversal */ vector preOrder() { vector res; dfs(0, "pre", res); return res; } /* Inorder traversal */ vector inOrder() { vector res; dfs(0, "in", res); return res; } /* Postorder traversal */ vector postOrder() { vector res; dfs(0, "post", res); return res; } private: vector tree; /* Depth-first traversal */ void dfs(int i, string order, vector &res) { // If empty position, return if (val(i) == INT_MAX) return; // Preorder traversal if (order == "pre") res.push_back(val(i)); dfs(left(i), order, res); // Inorder traversal if (order == "in") res.push_back(val(i)); dfs(right(i), order, res); // Postorder traversal if (order == "post") res.push_back(val(i)); } }; /* Driver Code */ int main() { // Initialize binary tree // Use INT_MAX to represent empty position 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 << "\nInitialize binary tree\n"; cout << "Array representation of binary tree:\n"; printVector(arr); cout << "Linked list representation of binary tree:\n"; printTree(root); // Binary tree class represented by array ArrayBinaryTree abt(arr); // Access node int i = 1; int l = abt.left(i), r = abt.right(i), p = abt.parent(i); cout << "\nCurrent node index is " << i << ", value is " << abt.val(i) << "\n"; cout << "Its left child node index is " << l << ", value is " << (abt.val(l) != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n"; cout << "Its right child node index is " << r << ", value is " << (abt.val(r) != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n"; cout << "Its parent node index is " << p << ", value is " << (abt.val(p) != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n"; // Traverse tree vector res = abt.levelOrder(); cout << "\nLevel-order traversal: "; printVector(res); res = abt.preOrder(); cout << "Pre-order traversal: "; printVector(res); res = abt.inOrder(); cout << "In-order traversal: "; printVector(res); res = abt.postOrder(); cout << "Post-order traversal: "; printVector(res); return 0; } ================================================ FILE: en/codes/cpp/chapter_tree/avl_tree.cpp ================================================ /** * File: avl_tree.cpp * Created Time: 2023-02-03 * Author: what-is-me (whatisme@outlook.jp) */ #include "../utils/common.hpp" /* AVL tree */ class AVLTree { private: /* Update node height */ void updateHeight(TreeNode *node) { // Node height equals the height of the tallest subtree + 1 node->height = max(height(node->left), height(node->right)) + 1; } /* Right rotation operation */ TreeNode *rightRotate(TreeNode *node) { TreeNode *child = node->left; TreeNode *grandChild = child->right; // Using child as pivot, rotate node to the right child->right = node; node->left = grandChild; // Update node height updateHeight(node); updateHeight(child); // Return root node of subtree after rotation return child; } /* Left rotation operation */ TreeNode *leftRotate(TreeNode *node) { TreeNode *child = node->right; TreeNode *grandChild = child->left; // Using child as pivot, rotate node to the left child->left = node; node->right = grandChild; // Update node height updateHeight(node); updateHeight(child); // Return root node of subtree after rotation return child; } /* Perform rotation operation to restore balance to this subtree */ TreeNode *rotate(TreeNode *node) { // Get balance factor of node int _balanceFactor = balanceFactor(node); // Left-leaning tree if (_balanceFactor > 1) { if (balanceFactor(node->left) >= 0) { // Right rotation return rightRotate(node); } else { // First left rotation then right rotation node->left = leftRotate(node->left); return rightRotate(node); } } // Right-leaning tree if (_balanceFactor < -1) { if (balanceFactor(node->right) <= 0) { // Left rotation return leftRotate(node); } else { // First right rotation then left rotation node->right = rightRotate(node->right); return leftRotate(node); } } // Balanced tree, no rotation needed, return directly return node; } /* Recursively insert node (helper method) */ TreeNode *insertHelper(TreeNode *node, int val) { if (node == nullptr) return new TreeNode(val); /* 1. Find insertion position and insert node */ if (val < node->val) node->left = insertHelper(node->left, val); else if (val > node->val) node->right = insertHelper(node->right, val); else return node; // Duplicate node not inserted, return directly updateHeight(node); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = rotate(node); // Return root node of subtree return node; } /* Recursively delete node (helper method) */ TreeNode *removeHelper(TreeNode *node, int val) { if (node == nullptr) return nullptr; /* 1. Find node and delete */ 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; // Number of child nodes = 0, delete node directly and return if (child == nullptr) { delete node; return nullptr; } // Number of child nodes = 1, delete node directly else { delete node; node = child; } } else { // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it 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); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = rotate(node); // Return root node of subtree return node; } public: TreeNode *root; // Root node /* Get node height */ int height(TreeNode *node) { // Empty node height is -1, leaf node height is 0 return node == nullptr ? -1 : node->height; } /* Get balance factor */ int balanceFactor(TreeNode *node) { // Empty node balance factor is 0 if (node == nullptr) return 0; // Node balance factor = left subtree height - right subtree height return height(node->left) - height(node->right); } /* Insert node */ void insert(int val) { root = insertHelper(root, val); } /* Remove node */ void remove(int val) { root = removeHelper(root, val); } /* Search node */ TreeNode *search(int val) { TreeNode *cur = root; // Loop search, exit after passing leaf node while (cur != nullptr) { // Target node is in cur's right subtree if (cur->val < val) cur = cur->right; // Target node is in cur's left subtree else if (cur->val > val) cur = cur->left; // Found target node, exit loop else break; } // Return target node return cur; } /*Constructor*/ AVLTree() : root(nullptr) { } /*Destructor*/ ~AVLTree() { freeMemoryTree(root); } }; void testInsert(AVLTree &tree, int val) { tree.insert(val); cout << "\nAfter inserting node " << val << ", AVL tree is" << endl; printTree(tree.root); } void testRemove(AVLTree &tree, int val) { tree.remove(val); cout << "\nAfter removing node " << val << ", AVL tree is" << endl; printTree(tree.root); } /* Driver Code */ int main() { /* Please pay attention to how the AVL tree maintains balance after inserting nodes */ AVLTree avlTree; /* Insert node */ // Delete nodes 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); /* Please pay attention to how the AVL tree maintains balance after deleting nodes */ testInsert(avlTree, 7); /* Remove node */ // Delete node with degree 1 testRemove(avlTree, 8); // Delete node with degree 2 testRemove(avlTree, 5); // Remove node with degree 1 testRemove(avlTree, 4); // Remove node with degree 2 /* Search node */ TreeNode *node = avlTree.search(7); cout << "\nFound node object is " << node << ", node value = " << node->val << endl; } ================================================ FILE: en/codes/cpp/chapter_tree/binary_search_tree.cpp ================================================ /** * File: binary_search_tree.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Binary search tree */ class BinarySearchTree { private: TreeNode *root; public: /* Constructor */ BinarySearchTree() { // Initialize empty tree root = nullptr; } /* Destructor */ ~BinarySearchTree() { freeMemoryTree(root); } /* Get binary tree root node */ TreeNode *getRoot() { return root; } /* Search node */ TreeNode *search(int num) { TreeNode *cur = root; // Loop search, exit after passing leaf node while (cur != nullptr) { // Target node is in cur's right subtree if (cur->val < num) cur = cur->right; // Target node is in cur's left subtree else if (cur->val > num) cur = cur->left; // Found target node, exit loop else break; } // Return target node return cur; } /* Insert node */ void insert(int num) { // If tree is empty, initialize root node if (root == nullptr) { root = new TreeNode(num); return; } TreeNode *cur = root, *pre = nullptr; // Loop search, exit after passing leaf node while (cur != nullptr) { // Found duplicate node, return directly if (cur->val == num) return; pre = cur; // Insertion position is in cur's right subtree if (cur->val < num) cur = cur->right; // Insertion position is in cur's left subtree else cur = cur->left; } // Insert node TreeNode *node = new TreeNode(num); if (pre->val < num) pre->right = node; else pre->left = node; } /* Remove node */ void remove(int num) { // If tree is empty, return directly if (root == nullptr) return; TreeNode *cur = root, *pre = nullptr; // Loop search, exit after passing leaf node while (cur != nullptr) { // Found node to delete, exit loop if (cur->val == num) break; pre = cur; // Node to delete is in cur's right subtree if (cur->val < num) cur = cur->right; // Node to delete is in cur's left subtree else cur = cur->left; } // If no node to delete, return directly if (cur == nullptr) return; // Number of child nodes = 0 or 1 if (cur->left == nullptr || cur->right == nullptr) { // When number of child nodes = 0 / 1, child = nullptr / that child node TreeNode *child = cur->left != nullptr ? cur->left : cur->right; // Delete node cur if (cur != root) { if (pre->left == cur) pre->left = child; else pre->right = child; } else { // If deleted node is root node, reassign root node root = child; } // Free memory delete cur; } // Number of child nodes = 2 else { // Get next node of cur in inorder traversal TreeNode *tmp = cur->right; while (tmp->left != nullptr) { tmp = tmp->left; } int tmpVal = tmp->val; // Recursively delete node tmp remove(tmp->val); // Replace cur with tmp cur->val = tmpVal; } } }; /* Driver Code */ int main() { /* Initialize binary search tree */ BinarySearchTree *bst = new BinarySearchTree(); // Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree 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 << "Initialized binary tree is\n" << endl; printTree(bst->getRoot()); /* Search node */ TreeNode *node = bst->search(7); cout << endl << "Found node object is " << node << ", node value = " << node->val << endl; /* Insert node */ bst->insert(16); cout << endl << "After inserting node 16, binary tree is\n" << endl; printTree(bst->getRoot()); /* Remove node */ bst->remove(1); cout << endl << "After removing node 1, binary tree is\n" << endl; printTree(bst->getRoot()); bst->remove(2); cout << endl << "After removing node 2, binary tree is\n" << endl; printTree(bst->getRoot()); bst->remove(4); cout << endl << "After removing node 4, binary tree is\n" << endl; printTree(bst->getRoot()); // Free memory delete bst; return 0; } ================================================ FILE: en/codes/cpp/chapter_tree/binary_tree.cpp ================================================ /** * File: binary_tree.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* Initialize binary tree */ // Initialize nodes 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); // Build references (pointers) between nodes n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; cout << endl << "Initialize binary tree\n" << endl; printTree(n1); /* Insert node P between n1 -> n2 */ TreeNode *P = new TreeNode(0); // Delete node n1->left = P; P->left = n2; cout << endl << "After inserting node P\n" << endl; printTree(n1); // Remove node P n1->left = n2; delete P; // Free memory cout << endl << "After removing node P\n" << endl; printTree(n1); // Free memory freeMemoryTree(n1); return 0; } ================================================ FILE: en/codes/cpp/chapter_tree/binary_tree_bfs.cpp ================================================ /** * File: binary_tree_bfs.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Level-order traversal */ vector levelOrder(TreeNode *root) { // Initialize queue, add root node queue queue; queue.push(root); // Initialize a list to save the traversal sequence vector vec; while (!queue.empty()) { TreeNode *node = queue.front(); queue.pop(); // Dequeue vec.push_back(node->val); // Save node value if (node->left != nullptr) queue.push(node->left); // Left child node enqueue if (node->right != nullptr) queue.push(node->right); // Right child node enqueue } return vec; } /* Driver Code */ int main() { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "Initialize binary tree\n" << endl; printTree(root); /* Level-order traversal */ vector vec = levelOrder(root); cout << endl << "Level-order traversal node print sequence = "; printVector(vec); return 0; } ================================================ FILE: en/codes/cpp/chapter_tree/binary_tree_dfs.cpp ================================================ /** * File: binary_tree_dfs.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" // Initialize list for storing traversal sequence vector vec; /* Preorder traversal */ void preOrder(TreeNode *root) { if (root == nullptr) return; // Visit priority: root node -> left subtree -> right subtree vec.push_back(root->val); preOrder(root->left); preOrder(root->right); } /* Inorder traversal */ void inOrder(TreeNode *root) { if (root == nullptr) return; // Visit priority: left subtree -> root node -> right subtree inOrder(root->left); vec.push_back(root->val); inOrder(root->right); } /* Postorder traversal */ void postOrder(TreeNode *root) { if (root == nullptr) return; // Visit priority: left subtree -> right subtree -> root node postOrder(root->left); postOrder(root->right); vec.push_back(root->val); } /* Driver Code */ int main() { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "Initialize binary tree\n" << endl; printTree(root); /* Preorder traversal */ vec.clear(); preOrder(root); cout << endl << "Pre-order traversal node print sequence = "; printVector(vec); /* Inorder traversal */ vec.clear(); inOrder(root); cout << endl << "In-order traversal node print sequence = "; printVector(vec); /* Postorder traversal */ vec.clear(); postOrder(root); cout << endl << "Post-order traversal node print sequence = "; printVector(vec); return 0; } ================================================ FILE: en/codes/cpp/utils/CMakeLists.txt ================================================ add_executable(utils common.hpp print_utils.hpp list_node.hpp tree_node.hpp vertex.hpp) ================================================ FILE: en/codes/cpp/utils/common.hpp ================================================ /** * 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; ================================================ FILE: en/codes/cpp/utils/list_node.hpp ================================================ /** * File: list_node.hpp * Created Time: 2021-12-19 * Author: krahets (krahets@163.com) */ #pragma once #include #include using namespace std; /* Linked list node */ struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(nullptr) { } }; /* Deserialize a list into a linked list */ 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; } /* Free memory allocated to linked list */ void freeMemoryLinkedList(ListNode *cur) { // Free memory ListNode *pre; while (cur != nullptr) { pre = cur; cur = cur->next; delete pre; } } ================================================ FILE: en/codes/cpp/utils/print_utils.hpp ================================================ /** * 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 /* Find an element in a vector */ template int vecFind(const vector &vec, T ele) { int j = INT_MAX; for (int i = 0; i < vec.size(); i++) { if (vec[i] == ele) { j = i; } } return j; } /* Concatenate a vector with a delim */ 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(); } /* Repeat a string for n times */ string strRepeat(string str, int n) { ostringstream os; for (int i = 0; i < n; i++) os << str; return os.str(); } /* Print array */ 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; } /* Get the Vector String object */ template string getVectorString(vector &list) { return "[" + strJoin(", ", list) + "]"; } /* Print list */ template void printVector(vector list) { cout << getVectorString(list) << '\n'; } /* Print matrix */ template void printVectorMatrix(vector> &matrix) { cout << "[" << '\n'; for (vector &list : matrix) cout << " " + getVectorString(list) + "," << '\n'; cout << "]" << '\n'; } /* Print linked list */ 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; } /** * 打印二叉树 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTree(TreeNode *root, Trunk *prev, bool isRight) { 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); } /* Print binary tree */ void printTree(TreeNode *root) { printTree(root, nullptr, false); } /* Print stack */ template void printStack(stack stk) { // Reverse the input stack stack tmp; while (!stk.empty()) { tmp.push(stk.top()); stk.pop(); } // Generate the string to print ostringstream s; bool flag = true; while (!tmp.empty()) { if (flag) { s << tmp.top(); flag = false; } else s << ", " << tmp.top(); tmp.pop(); } cout << "[" + s.str() + "]" << '\n'; } /* Print queue */ template void printQueue(queue queue) { // Generate the string to print 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'; } /* Print deque */ template void printDeque(deque deque) { // Generate the string to print 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'; } /* Print hash table */ // Define template parameters TKey and TValue to specify key-value pair types template void printHashMap(unordered_map map) { for (auto kv : map) { cout << kv.first << " -> " << kv.second << '\n'; } } /* Expose the underlying storage of the priority_queue container */ template S &Container(priority_queue &pq) { struct HackedQueue : private priority_queue { static S &Container(priority_queue &pq) { return pq.*&HackedQueue::c; } }; return HackedQueue::Container(pq); } /* Print heap (priority queue) */ template void printHeap(priority_queue &heap) { vector vec = Container(heap); cout << "Heap array representation:"; printVector(vec); cout << "Heap tree representation:" << endl; TreeNode *root = vectorToTree(vec); printTree(root); freeMemoryTree(root); } ================================================ FILE: en/codes/cpp/utils/tree_node.hpp ================================================ /** * File: tree_node.hpp * Created Time: 2021-12-19 * Author: krahets (krahets@163.com) */ #pragma once #include #include using namespace std; /* Binary tree node structure */ 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) { } }; // For the serialization encoding rules, please refer to: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Array representation of binary tree: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // Linked list representation of binary tree: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* Deserialize a list into a binary tree: recursion */ 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; } /* Deserialize a list into a binary tree */ TreeNode *vectorToTree(vector arr) { return vectorToTreeDFS(arr, 0); } /* Serialize a binary tree into a list: recursion */ 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); } /* Serialize a binary tree into a list */ vector treeToVecor(TreeNode *root) { vector res; treeToVecorDFS(root, 0, res); return res; } /* Free binary tree memory */ void freeMemoryTree(TreeNode *root) { if (root == nullptr) return; freeMemoryTree(root->left); freeMemoryTree(root->right); delete root; } ================================================ FILE: en/codes/cpp/utils/vertex.hpp ================================================ /** * File: vertex.hpp * Created Time: 2023-03-02 * Author: krahets (krahets@163.com) */ #pragma once #include using namespace std; /* Vertex class */ struct Vertex { int val; Vertex(int x) : val(x) { } }; /* Input value list vals, return vertex list vets */ vector valsToVets(vector vals) { vector vets; for (int val : vals) { vets.push_back(new Vertex(val)); } return vets; } /* Input vertex list vets, return value list vals */ vector vetsToVals(vector vets) { vector vals; for (Vertex *vet : vets) { vals.push_back(vet->val); } return vals; } ================================================ FILE: en/codes/csharp/.editorconfig ================================================ # CSharp formatting rules [*.cs] csharp_new_line_before_open_brace = none csharp_new_line_before_else = false csharp_new_line_before_catch = false csharp_new_line_before_finally = false csharp_indent_labels = one_less_than_current csharp_using_directive_placement = outside_namespace:silent csharp_prefer_simple_using_statement = true:suggestion csharp_prefer_braces = true:silent csharp_style_namespace_declarations = block_scoped:silent csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent csharp_style_prefer_primary_constructors = true:suggestion csharp_style_expression_bodied_methods = false:silent csharp_style_expression_bodied_constructors = false:silent csharp_style_expression_bodied_operators = false:silent csharp_style_expression_bodied_properties = true:silent csharp_style_expression_bodied_indexers = true:silent csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_lambdas = true:silent # CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language. dotnet_diagnostic.CS8981.severity = silent # IDE1006: Naming Styles dotnet_diagnostic.IDE1006.severity = silent # CA1822: Mark members as static dotnet_diagnostic.CA1822.severity = silent [*.{cs,vb}] #### Naming styles #### # Naming rules dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion dotnet_naming_rule.types_should_be_pascal_case.symbols = types dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case # Symbol specifications dotnet_naming_symbols.interface.applicable_kinds = interface dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.interface.required_modifiers = dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.types.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.non_field_members.required_modifiers = # Naming styles dotnet_naming_style.begins_with_i.required_prefix = I dotnet_naming_style.begins_with_i.required_suffix = dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_style_operator_placement_when_wrapping = beginning_of_line tab_width = 4 indent_size = 4 end_of_line = crlf # IDE0040: Add accessibility modifiers dotnet_diagnostic.IDE0040.severity = silent # IDE0044: Add readonly modifier dotnet_diagnostic.IDE0044.severity = silent ================================================ FILE: en/codes/csharp/.gitignore ================================================ .idea/ .vs/ obj/ .Debug bin/ ================================================ FILE: en/codes/csharp/GlobalUsing.cs ================================================ global using NUnit.Framework; global using hello_algo.utils; global using System.Text; ================================================ FILE: en/codes/csharp/chapter_array_and_linkedlist/array.cs ================================================ // File: array.cs // Created Time: 2022-12-14 // Author: mingXta (1195669834@qq.com) namespace hello_algo.chapter_array_and_linkedlist; public class array { /* Random access to element */ int RandomAccess(int[] nums) { Random random = new(); // Randomly select a number in interval [0, nums.Length) int randomIndex = random.Next(nums.Length); // Retrieve and return the random element int randomNum = nums[randomIndex]; return randomNum; } /* Extend array length */ int[] Extend(int[] nums, int enlarge) { // Initialize an array with extended length int[] res = new int[nums.Length + enlarge]; // Copy all elements from the original array to the new array for (int i = 0; i < nums.Length; i++) { res[i] = nums[i]; } // Return the extended new array return res; } /* Insert element num at index index in the array */ void Insert(int[] nums, int num, int index) { // Move all elements at and after index index backward by one position for (int i = nums.Length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Assign num to the element at index index nums[index] = num; } /* Remove the element at index index */ void Remove(int[] nums, int index) { // Move all elements after index index forward by one position for (int i = index; i < nums.Length - 1; i++) { nums[i] = nums[i + 1]; } } /* Traverse array */ void Traverse(int[] nums) { int count = 0; // Traverse array by index for (int i = 0; i < nums.Length; i++) { count += nums[i]; } // Direct traversal of array elements foreach (int num in nums) { count += num; } } /* Find the specified element in the array */ int Find(int[] nums, int target) { for (int i = 0; i < nums.Length; i++) { if (nums[i] == target) return i; } return -1; } /* Helper function, convert array to string */ string ToString(int[] nums) { return string.Join(",", nums); } [Test] public void Test() { // Initialize array int[] arr = new int[5]; Console.WriteLine("Array arr = " + ToString(arr)); int[] nums = [1, 3, 2, 5, 4]; Console.WriteLine("Array nums = " + ToString(nums)); // Insert element int randomNum = RandomAccess(nums); Console.WriteLine("Get random element in nums " + randomNum); // Traverse array nums = Extend(nums, 3); Console.WriteLine("Extend array length to 8, resulting in nums = " + ToString(nums)); // Insert element Insert(nums, 6, 3); Console.WriteLine("Insert number 6 at index 3, resulting in nums = " + ToString(nums)); // Remove element Remove(nums, 2); Console.WriteLine("Remove element at index 2, resulting in nums = " + ToString(nums)); // Traverse array Traverse(nums); // Find element int index = Find(nums, 3); Console.WriteLine("Find element 3 in nums, get index = " + index); } } ================================================ FILE: en/codes/csharp/chapter_array_and_linkedlist/linked_list.cs ================================================ // File: linked_list.cs // Created Time: 2022-12-16 // Author: mingXta (1195669834@qq.com) namespace hello_algo.chapter_array_and_linkedlist; public class linked_list { /* Insert node P after node n0 in the linked list */ void Insert(ListNode n0, ListNode P) { ListNode? n1 = n0.next; P.next = n1; n0.next = P; } /* Remove the first node after node n0 in the linked list */ void Remove(ListNode n0) { if (n0.next == null) return; // n0 -> P -> n1 ListNode P = n0.next; ListNode? n1 = P.next; n0.next = n1; } /* Access the node at index index in the linked list */ ListNode? Access(ListNode? head, int index) { for (int i = 0; i < index; i++) { if (head == null) return null; head = head.next; } return head; } /* Find the first node with value target in the linked list */ int Find(ListNode? head, int target) { int index = 0; while (head != null) { if (head.val == target) return index; head = head.next; index++; } return -1; } [Test] public void Test() { // Initialize linked list // Initialize each node ListNode n0 = new(1); ListNode n1 = new(3); ListNode n2 = new(2); ListNode n3 = new(5); ListNode n4 = new(4); // Build references between nodes n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; Console.WriteLine($"Initialized linked list is{n0}"); // Insert node Insert(n0, new ListNode(0)); Console.WriteLine($"Linked list after node insertion is{n0}"); // Remove node Remove(n0); Console.WriteLine($"Linked list after node deletion is{n0}"); // Access node ListNode? node = Access(n0, 3); Console.WriteLine($"Value of node at index 3 in linked list = {node?.val}"); // Search node int index = Find(n0, 2); Console.WriteLine($"Index of node with value 2 in linked list = {index}"); } } ================================================ FILE: en/codes/csharp/chapter_array_and_linkedlist/list.cs ================================================ /** * File: list.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_array_and_linkedlist; public class list { [Test] public void Test() { /* Initialize list */ int[] numbers = [1, 3, 2, 5, 4]; List nums = [.. numbers]; Console.WriteLine("List nums = " + string.Join(",", nums)); /* Update element */ int num = nums[1]; Console.WriteLine("Access element at index 1, get num = " + num); /* Add elements at the end */ nums[1] = 0; Console.WriteLine("Update element at index 1 to 0, resulting in nums = " + string.Join(",", nums)); /* Remove element */ nums.Clear(); Console.WriteLine("After clearing list, nums = " + string.Join(",", nums)); /* Direct traversal of list elements */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); Console.WriteLine("After adding elements, nums = " + string.Join(",", nums)); /* Sort list */ nums.Insert(3, 6); Console.WriteLine("Insert number 6 at index 3, resulting in nums = " + string.Join(",", nums)); /* Remove element */ nums.RemoveAt(3); Console.WriteLine("Remove element at index 3, resulting in nums = " + string.Join(",", nums)); /* Traverse list by index */ int count = 0; for (int i = 0; i < nums.Count; i++) { count += nums[i]; } /* Directly traverse list elements */ count = 0; foreach (int x in nums) { count += x; } /* Concatenate two lists */ List nums1 = [6, 8, 7, 10, 9]; nums.AddRange(nums1); Console.WriteLine("Concatenate list nums1 to nums, resulting in nums = " + string.Join(",", nums)); /* Sort list */ nums.Sort(); // After sorting, list elements are arranged from smallest to largest Console.WriteLine("After sorting list, nums = " + string.Join(",", nums)); } } ================================================ FILE: en/codes/csharp/chapter_array_and_linkedlist/my_list.cs ================================================ /** * File: my_list.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_array_and_linkedlist; /* List class */ class MyList { private int[] arr; // Array (stores list elements) private int arrCapacity = 10; // List capacity private int arrSize = 0; // List length (current number of elements) private readonly int extendRatio = 2; // Multiple by which the list capacity is extended each time /* Constructor */ public MyList() { arr = new int[arrCapacity]; } /* Get list length (current number of elements) */ public int Size() { return arrSize; } /* Get list capacity */ public int Capacity() { return arrCapacity; } /* Update element */ public int Get(int index) { // If the index is out of bounds, throw an exception, as below if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("Index out of bounds"); return arr[index]; } /* Add elements at the end */ public void Set(int index, int num) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("Index out of bounds"); arr[index] = num; } /* Direct traversal of list elements */ public void Add(int num) { // When the number of elements exceeds capacity, trigger the extension mechanism if (arrSize == arrCapacity) ExtendCapacity(); arr[arrSize] = num; // Update the number of elements arrSize++; } /* Sort list */ public void Insert(int index, int num) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("Index out of bounds"); // When the number of elements exceeds capacity, trigger the extension mechanism if (arrSize == arrCapacity) ExtendCapacity(); // Move all elements after index index forward by one position for (int j = arrSize - 1; j >= index; j--) { arr[j + 1] = arr[j]; } arr[index] = num; // Update the number of elements arrSize++; } /* Remove element */ public int Remove(int index) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("Index out of bounds"); int num = arr[index]; // Move all elements after index forward by one position for (int j = index; j < arrSize - 1; j++) { arr[j] = arr[j + 1]; } // Update the number of elements arrSize--; // Return the removed element return num; } /* Driver Code */ public void ExtendCapacity() { // Create new array of length arrCapacity * extendRatio and copy original array to new array Array.Resize(ref arr, arrCapacity * extendRatio); // Add elements at the end arrCapacity = arr.Length; } /* Convert list to array */ public int[] ToArray() { // Elements enqueue int[] arr = new int[arrSize]; for (int i = 0; i < arrSize; i++) { arr[i] = Get(i); } return arr; } } public class my_list { [Test] public void Test() { /* Initialize list */ MyList nums = new(); /* Direct traversal of list elements */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); Console.WriteLine("List nums = " + string.Join(",", nums.ToArray()) + ", capacity = " + nums.Capacity() + ", length = " + nums.Size()); /* Sort list */ nums.Insert(3, 6); Console.WriteLine("Insert number 6 at index 3, resulting in nums = " + string.Join(",", nums.ToArray())); /* Remove element */ nums.Remove(3); Console.WriteLine("Remove element at index 3, resulting in nums = " + string.Join(",", nums.ToArray())); /* Update element */ int num = nums.Get(1); Console.WriteLine("Access element at index 1, get num = " + num); /* Add elements at the end */ nums.Set(1, 0); Console.WriteLine("Update element at index 1 to 0, resulting in nums = " + string.Join(",", nums.ToArray())); /* Test capacity expansion mechanism */ for (int i = 0; i < 10; i++) { // At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism nums.Add(i); } Console.WriteLine("List nums after expansion = " + string.Join(",", nums.ToArray()) + ", capacity = " + nums.Capacity() + ", length = " + nums.Size()); } } ================================================ FILE: en/codes/csharp/chapter_backtracking/n_queens.cs ================================================ /** * File: n_queens.cs * Created Time: 2023-05-04 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class n_queens { /* Backtracking algorithm: N queens */ void Backtrack(int row, int n, List> state, List>> res, bool[] cols, bool[] diags1, bool[] diags2) { // When all rows are placed, record the solution if (row == n) { List> copyState = []; foreach (List sRow in state) { copyState.Add(new List(sRow)); } res.Add(copyState); return; } // Traverse all columns for (int col = 0; col < n; col++) { // Calculate the main diagonal and anti-diagonal corresponding to this cell int diag1 = row - col + n - 1; int diag2 = row + col; // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Attempt: place the queen in this cell state[row][col] = "Q"; cols[col] = diags1[diag1] = diags2[diag2] = true; // Place the next row Backtrack(row + 1, n, state, res, cols, diags1, diags2); // Backtrack: restore this cell to an empty cell state[row][col] = "#"; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Solve N queens */ List>> NQueens(int n) { // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell List> state = []; for (int i = 0; i < n; i++) { List row = []; for (int j = 0; j < n; j++) { row.Add("#"); } state.Add(row); } bool[] cols = new bool[n]; // Record whether there is a queen in the column bool[] diags1 = new bool[2 * n - 1]; // Record whether there is a queen on the main diagonal bool[] diags2 = new bool[2 * n - 1]; // Record whether there is a queen on the anti-diagonal List>> res = []; Backtrack(0, n, state, res, cols, diags1, diags2); return res; } [Test] public void Test() { int n = 4; List>> res = NQueens(n); Console.WriteLine("Input board size is " + n); Console.WriteLine("Total queen placement solutions: " + res.Count + " solutions"); foreach (List> state in res) { Console.WriteLine("--------------------"); foreach (List row in state) { PrintUtil.PrintList(row); } } } } ================================================ FILE: en/codes/csharp/chapter_backtracking/permutations_i.cs ================================================ /** * File: permutations_i.cs * Created Time: 2023-04-24 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class permutations_i { /* Backtracking algorithm: Permutations I */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // When the state length equals the number of elements, record the solution if (state.Count == choices.Length) { res.Add(new List(state)); return; } // Traverse all choices for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state.Add(choice); // Proceed to the next round of selection Backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* Permutations I */ List> PermutationsI(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } [Test] public void Test() { int[] nums = [1, 2, 3]; List> res = PermutationsI(nums); Console.WriteLine("Input array nums = " + string.Join(", ", nums)); Console.WriteLine("All permutations res = "); foreach (List permutation in res) { PrintUtil.PrintList(permutation); } } } ================================================ FILE: en/codes/csharp/chapter_backtracking/permutations_ii.cs ================================================ /** * File: permutations_ii.cs * Created Time: 2023-04-24 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class permutations_ii { /* Backtracking algorithm: Permutations II */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // When the state length equals the number of elements, record the solution if (state.Count == choices.Length) { res.Add(new List(state)); return; } // Traverse all choices HashSet duplicated = []; for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated.Contains(choice)) { // Attempt: make choice, update state duplicated.Add(choice); // Record the selected element value selected[i] = true; state.Add(choice); // Proceed to the next round of selection Backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* Permutations II */ List> PermutationsII(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } [Test] public void Test() { int[] nums = [1, 2, 2]; List> res = PermutationsII(nums); Console.WriteLine("Input array nums = " + string.Join(", ", nums)); Console.WriteLine("All permutations res = "); foreach (List permutation in res) { PrintUtil.PrintList(permutation); } } } ================================================ FILE: en/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs ================================================ /** * File: preorder_traversal_i_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_i_compact { List res = []; /* Preorder traversal: Example 1 */ void PreOrder(TreeNode? root) { if (root == null) { return; } if (root.val == 7) { // Record solution res.Add(root); } PreOrder(root.left); PreOrder(root.right); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\nInitialize binary tree"); PrintUtil.PrintTree(root); // Preorder traversal PreOrder(root); Console.WriteLine("\nOutput all nodes with value 7"); PrintUtil.PrintList(res.Select(p => p.val).ToList()); } } ================================================ FILE: en/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs ================================================ /** * File: preorder_traversal_ii_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_ii_compact { List path = []; List> res = []; /* Preorder traversal: Example 2 */ void PreOrder(TreeNode? root) { if (root == null) { return; } // Attempt path.Add(root); if (root.val == 7) { // Record solution res.Add(new List(path)); } PreOrder(root.left); PreOrder(root.right); // Backtrack path.RemoveAt(path.Count - 1); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\nInitialize binary tree"); PrintUtil.PrintTree(root); // Preorder traversal PreOrder(root); Console.WriteLine("\nOutput all paths from root node to node 7"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: en/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs ================================================ /** * File: preorder_traversal_iii_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_iii_compact { List path = []; List> res = []; /* Preorder traversal: Example 3 */ void PreOrder(TreeNode? root) { // Pruning if (root == null || root.val == 3) { return; } // Attempt path.Add(root); if (root.val == 7) { // Record solution res.Add(new List(path)); } PreOrder(root.left); PreOrder(root.right); // Backtrack path.RemoveAt(path.Count - 1); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\nInitialize binary tree"); PrintUtil.PrintTree(root); // Preorder traversal PreOrder(root); Console.WriteLine("\nOutput all paths from root node to node 7, paths do not include nodes with value 3"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: en/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs ================================================ /** * File: preorder_traversal_iii_template.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_iii_template { /* Check if the current state is a solution */ bool IsSolution(List state) { return state.Count != 0 && state[^1].val == 7; } /* Record solution */ void RecordSolution(List state, List> res) { res.Add(new List(state)); } /* Check if the choice is valid under the current state */ bool IsValid(List state, TreeNode choice) { return choice != null && choice.val != 3; } /* Update state */ void MakeChoice(List state, TreeNode choice) { state.Add(choice); } /* Restore state */ void UndoChoice(List state, TreeNode choice) { state.RemoveAt(state.Count - 1); } /* Backtracking algorithm: Example 3 */ void Backtrack(List state, List choices, List> res) { // Check if it is a solution if (IsSolution(state)) { // Record solution RecordSolution(state, res); } // Traverse all choices foreach (TreeNode choice in choices) { // Pruning: check if the choice is valid if (IsValid(state, choice)) { // Attempt: make choice, update state MakeChoice(state, choice); // Proceed to the next round of selection Backtrack(state, [choice.left!, choice.right!], res); // Backtrack: undo choice, restore to previous state UndoChoice(state, choice); } } } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\nInitialize binary tree"); PrintUtil.PrintTree(root); // Backtracking algorithm List> res = []; List choices = [root!]; Backtrack([], choices, res); Console.WriteLine("\nOutput all paths from root node to node 7, requiring paths do not include nodes with value 3"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: en/codes/csharp/chapter_backtracking/subset_sum_i.cs ================================================ /** * File: subset_sum_i.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_i { /* Backtracking algorithm: Subset sum I */ void Backtrack(List state, int target, int[] choices, int start, List> res) { // When the subset sum equals target, record the solution if (target == 0) { res.Add(new List(state)); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets for (int i = start; i < choices.Length; i++) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if (target - choices[i] < 0) { break; } // Attempt: make choice, update target, start state.Add(choices[i]); // Proceed to the next round of selection Backtrack(state, target - choices[i], choices, i, res); // Backtrack: undo choice, restore to previous state state.RemoveAt(state.Count - 1); } } /* Solve subset sum I */ List> SubsetSumI(int[] nums, int target) { List state = []; // State (subset) Array.Sort(nums); // Sort nums int start = 0; // Start point for traversal List> res = []; // Result list (subset list) Backtrack(state, target, nums, start, res); return res; } [Test] public void Test() { int[] nums = [3, 4, 5]; int target = 9; List> res = SubsetSumI(nums, target); Console.WriteLine("Input array nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("All subsets with sum equal to " + target + " are res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } } } ================================================ FILE: en/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs ================================================ /** * File: subset_sum_i_naive.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_i_naive { /* Backtracking algorithm: Subset sum I */ void Backtrack(List state, int target, int total, int[] choices, List> res) { // When the subset sum equals target, record the solution if (total == target) { res.Add(new List(state)); return; } // Traverse all choices for (int i = 0; i < choices.Length; i++) { // Pruning: if the subset sum exceeds target, skip this choice if (total + choices[i] > target) { continue; } // Attempt: make choice, update element sum total state.Add(choices[i]); // Proceed to the next round of selection Backtrack(state, target, total + choices[i], choices, res); // Backtrack: undo choice, restore to previous state state.RemoveAt(state.Count - 1); } } /* Solve subset sum I (including duplicate subsets) */ List> SubsetSumINaive(int[] nums, int target) { List state = []; // State (subset) int total = 0; // Subset sum List> res = []; // Result list (subset list) Backtrack(state, target, total, nums, res); return res; } [Test] public void Test() { int[] nums = [3, 4, 5]; int target = 9; List> res = SubsetSumINaive(nums, target); Console.WriteLine("Input array nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("All subsets with sum equal to " + target + " are res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } Console.WriteLine("Please note that this method outputs results containing duplicate sets"); } } ================================================ FILE: en/codes/csharp/chapter_backtracking/subset_sum_ii.cs ================================================ /** * File: subset_sum_ii.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_ii { /* Backtracking algorithm: Subset sum II */ void Backtrack(List state, int target, int[] choices, int start, List> res) { // When the subset sum equals target, record the solution if (target == 0) { res.Add(new List(state)); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets // Pruning 3: start traversing from start to avoid repeatedly selecting the same element for (int i = start; i < choices.Length; i++) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if (target - choices[i] < 0) { break; } // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly if (i > start && choices[i] == choices[i - 1]) { continue; } // Attempt: make choice, update target, start state.Add(choices[i]); // Proceed to the next round of selection Backtrack(state, target - choices[i], choices, i + 1, res); // Backtrack: undo choice, restore to previous state state.RemoveAt(state.Count - 1); } } /* Solve subset sum II */ List> SubsetSumII(int[] nums, int target) { List state = []; // State (subset) Array.Sort(nums); // Sort nums int start = 0; // Start point for traversal List> res = []; // Result list (subset list) Backtrack(state, target, nums, start, res); return res; } [Test] public void Test() { int[] nums = [4, 4, 5]; int target = 9; List> res = SubsetSumII(nums, target); Console.WriteLine("Input array nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("All subsets with sum equal to " + target + " are res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } } } ================================================ FILE: en/codes/csharp/chapter_computational_complexity/iteration.cs ================================================ /** * File: iteration.cs * Created Time: 2023-08-28 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_computational_complexity; public class iteration { /* for loop */ int ForLoop(int n) { int res = 0; // Sum 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } /* while loop */ int WhileLoop(int n) { int res = 0; int i = 1; // Initialize condition variable // Sum 1, 2, ..., n-1, n while (i <= n) { res += i; i += 1; // Update condition variable } return res; } /* while loop (two updates) */ int WhileLoopII(int n) { int res = 0; int i = 1; // Initialize condition variable // Sum 1, 4, 10, ... while (i <= n) { res += i; // Update condition variable i += 1; i *= 2; } return res; } /* Nested for loop */ string NestedForLoop(int n) { StringBuilder res = new(); // Loop i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // Loop j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { res.Append($"({i}, {j}), "); } } return res.ToString(); } /* Driver Code */ [Test] public void Test() { int n = 5; int res; res = ForLoop(n); Console.WriteLine("\nfor loop sum result res = " + res); res = WhileLoop(n); Console.WriteLine("\nwhile loop sum result res = " + res); res = WhileLoopII(n); Console.WriteLine("\nwhile loop (two updates) sum result res = " + res); string resStr = NestedForLoop(n); Console.WriteLine("\nDouble for loop traversal result " + resStr); } } ================================================ FILE: en/codes/csharp/chapter_computational_complexity/recursion.cs ================================================ /** * File: recursion.cs * Created Time: 2023-08-28 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_computational_complexity; public class recursion { /* Recursion */ int Recur(int n) { // Termination condition if (n == 1) return 1; // Recurse: recursive call int res = Recur(n - 1); // Return: return result return n + res; } /* Simulate recursion using iteration */ int ForLoopRecur(int n) { // Use an explicit stack to simulate the system call stack Stack stack = new(); int res = 0; // Recurse: recursive call for (int i = n; i > 0; i--) { // Simulate "recurse" with "push" stack.Push(i); } // Return: return result while (stack.Count > 0) { // Simulate "return" with "pop" res += stack.Pop(); } // res = 1+2+3+...+n return res; } /* Tail recursion */ int TailRecur(int n, int res) { // Termination condition if (n == 0) return res; // Tail recursive call return TailRecur(n - 1, res + n); } /* Fibonacci sequence: recursion */ int Fib(int n) { // Termination condition f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // Recursive call f(n) = f(n-1) + f(n-2) int res = Fib(n - 1) + Fib(n - 2); // Return result f(n) return res; } /* Driver Code */ [Test] public void Test() { int n = 5; int res; res = Recur(n); Console.WriteLine("\nRecursive function sum result res = " + res); res = ForLoopRecur(n); Console.WriteLine("\nUsing iteration to simulate recursive sum result res = " + res); res = TailRecur(n, 0); Console.WriteLine("\nTail recursive function sum result res = " + res); res = Fib(n); Console.WriteLine("\nThe " + n + "th term of the Fibonacci sequence is " + res); } } ================================================ FILE: en/codes/csharp/chapter_computational_complexity/space_complexity.cs ================================================ /** * File: space_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class space_complexity { /* Function */ int Function() { // Perform some operations return 0; } /* Constant order */ void Constant(int n) { // Constants, variables, objects occupy O(1) space int a = 0; int b = 0; int[] nums = new int[10000]; ListNode node = new(0); // Variables in the loop occupy O(1) space for (int i = 0; i < n; i++) { int c = 0; } // Functions in the loop occupy O(1) space for (int i = 0; i < n; i++) { Function(); } } /* Linear order */ void Linear(int n) { // Array of length n uses O(n) space int[] nums = new int[n]; // A list of length n occupies O(n) space List nodes = []; for (int i = 0; i < n; i++) { nodes.Add(new ListNode(i)); } // A hash table of length n occupies O(n) space Dictionary map = []; for (int i = 0; i < n; i++) { map.Add(i, i.ToString()); } } /* Linear order (recursive implementation) */ void LinearRecur(int n) { Console.WriteLine("Recursion n = " + n); if (n == 1) return; LinearRecur(n - 1); } /* Exponential order */ void Quadratic(int n) { // Matrix uses O(n^2) space int[,] numMatrix = new int[n, n]; // 2D list uses O(n^2) space List> numList = []; for (int i = 0; i < n; i++) { List tmp = []; for (int j = 0; j < n; j++) { tmp.Add(0); } numList.Add(tmp); } } /* Quadratic order (recursive implementation) */ int QuadraticRecur(int n) { if (n <= 0) return 0; int[] nums = new int[n]; Console.WriteLine("Recursion n = " + n + ", nums length = " + nums.Length); return QuadraticRecur(n - 1); } /* Driver Code */ TreeNode? BuildTree(int n) { if (n == 0) return null; TreeNode root = new(0) { left = BuildTree(n - 1), right = BuildTree(n - 1) }; return root; } [Test] public void Test() { int n = 5; // Constant order Constant(n); // Linear order Linear(n); LinearRecur(n); // Exponential order Quadratic(n); QuadraticRecur(n); // Exponential order TreeNode? root = BuildTree(n); PrintUtil.PrintTree(root); } } ================================================ FILE: en/codes/csharp/chapter_computational_complexity/time_complexity.cs ================================================ /** * File: time_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class time_complexity { void Algorithm(int n) { int a = 1; // +0 (technique 1) a += n; // +0 (technique 1) // +n (technique 2) for (int i = 0; i < 5 * n + 1; i++) { Console.WriteLine(0); } // +n*n (technique 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { Console.WriteLine(0); } } } // Algorithm A time complexity: constant void AlgorithmA(int n) { Console.WriteLine(0); } // Algorithm B time complexity: linear void AlgorithmB(int n) { for (int i = 0; i < n; i++) { Console.WriteLine(0); } } // Algorithm C time complexity: constant void AlgorithmC(int n) { for (int i = 0; i < 1000000; i++) { Console.WriteLine(0); } } /* Constant order */ int Constant(int n) { int count = 0; int size = 100000; for (int i = 0; i < size; i++) count++; return count; } /* Linear order */ int Linear(int n) { int count = 0; for (int i = 0; i < n; i++) count++; return count; } /* Linear order (traversing array) */ int ArrayTraversal(int[] nums) { int count = 0; // Number of iterations is proportional to the array length foreach (int num in nums) { count++; } return count; } /* Exponential order */ int Quadratic(int n) { int count = 0; // Number of iterations is quadratically related to the data size n for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* Quadratic order (bubble sort) */ int BubbleSort(int[] nums) { int count = 0; // Counter // Outer loop: unsorted range is [0, i] for (int i = nums.Length - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); count += 3; // Element swap includes 3 unit operations } } } return count; } /* Exponential order (loop implementation) */ int Exponential(int n) { int count = 0, bas = 1; // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1) for (int i = 0; i < n; i++) { for (int j = 0; j < bas; j++) { count++; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* Exponential order (recursive implementation) */ int ExpRecur(int n) { if (n == 1) return 1; return ExpRecur(n - 1) + ExpRecur(n - 1) + 1; } /* Logarithmic order (loop implementation) */ int Logarithmic(int n) { int count = 0; while (n > 1) { n /= 2; count++; } return count; } /* Logarithmic order (recursive implementation) */ int LogRecur(int n) { if (n <= 1) return 0; return LogRecur(n / 2) + 1; } /* Linearithmic order */ 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; } /* Factorial order (recursive implementation) */ int FactorialRecur(int n) { if (n == 0) return 1; int count = 0; // Split from 1 into n for (int i = 0; i < n; i++) { count += FactorialRecur(n - 1); } return count; } [Test] public void Test() { // You can modify n to run and observe the trend of the number of operations for various complexities int n = 8; Console.WriteLine("Input data size n = " + n); int count = Constant(n); Console.WriteLine("Constant order operation count = " + count); count = Linear(n); Console.WriteLine("Linear order operation count = " + count); count = ArrayTraversal(new int[n]); Console.WriteLine("Linear order (array traversal) operation count = " + count); count = Quadratic(n); Console.WriteLine("Quadratic order operation count = " + count); int[] nums = new int[n]; for (int i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = BubbleSort(nums); Console.WriteLine("Quadratic order (bubble sort) operation count = " + count); count = Exponential(n); Console.WriteLine("Exponential order (loop implementation) operation count = " + count); count = ExpRecur(n); Console.WriteLine("Exponential order (recursive implementation) operation count = " + count); count = Logarithmic(n); Console.WriteLine("Logarithmic order (loop implementation) operation count = " + count); count = LogRecur(n); Console.WriteLine("Logarithmic order (recursive implementation) operation count = " + count); count = LinearLogRecur(n); Console.WriteLine("Linearithmic order (recursive implementation) operation count = " + count); count = FactorialRecur(n); Console.WriteLine("Factorial order (recursive implementation) operation count = " + count); } } ================================================ FILE: en/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs ================================================ /** * File: worst_best_time_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class worst_best_time_complexity { /* Generate an array with elements { 1, 2, ..., n }, order shuffled */ int[] RandomNumbers(int n) { int[] nums = new int[n]; // Generate array nums = { 1, 2, 3, ..., n } for (int i = 0; i < n; i++) { nums[i] = i + 1; } // Randomly shuffle array elements for (int i = 0; i < nums.Length; i++) { int index = new Random().Next(i, nums.Length); (nums[i], nums[index]) = (nums[index], nums[i]); } return nums; } /* Find the index of number 1 in array nums */ int FindOne(int[] nums) { for (int i = 0; i < nums.Length; i++) { // When element 1 is at the head of the array, best time complexity O(1) is achieved // When element 1 is at the tail of the array, worst time complexity O(n) is achieved if (nums[i] == 1) return i; } return -1; } /* Driver Code */ [Test] public void Test() { for (int i = 0; i < 10; i++) { int n = 100; int[] nums = RandomNumbers(n); int index = FindOne(nums); Console.WriteLine("\nArray [ 1, 2, ..., n ] after shuffling = " + string.Join(",", nums)); Console.WriteLine("Index of number 1 is " + index); } } } ================================================ FILE: en/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs ================================================ /** * File: binary_search_recur.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class binary_search_recur { /* Binary search: problem f(i, j) */ int DFS(int[] nums, int target, int i, int j) { // If the interval is empty, it means there is no target element, return -1 if (i > j) { return -1; } // Calculate the midpoint index m int m = (i + j) / 2; if (nums[m] < target) { // Recursion subproblem f(m+1, j) return DFS(nums, target, m + 1, j); } else if (nums[m] > target) { // Recursion subproblem f(i, m-1) return DFS(nums, target, i, m - 1); } else { // Found the target element, return its index return m; } } /* Binary search */ int BinarySearch(int[] nums, int target) { int n = nums.Length; // Solve the problem f(0, n-1) return DFS(nums, target, 0, n - 1); } [Test] public void Test() { int target = 6; int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // Binary search (closed interval on both sides) int index = BinarySearch(nums, target); Console.WriteLine("Index of target element 6 = " + index); } } ================================================ FILE: en/codes/csharp/chapter_divide_and_conquer/build_tree.cs ================================================ /** * File: build_tree.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class build_tree { /* Build binary tree: divide and conquer */ TreeNode? DFS(int[] preorder, Dictionary inorderMap, int i, int l, int r) { // Terminate when the subtree interval is empty if (r - l < 0) return null; // Initialize the root node TreeNode root = new(preorder[i]); // Query m to divide the left and right subtrees int m = inorderMap[preorder[i]]; // Subproblem: build the left subtree root.left = DFS(preorder, inorderMap, i + 1, l, m - 1); // Subproblem: build the right subtree root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r); // Return the root node return root; } /* Build binary tree */ TreeNode? BuildTree(int[] preorder, int[] inorder) { // Initialize hash map, storing the mapping from inorder elements to indices Dictionary inorderMap = []; for (int i = 0; i < inorder.Length; i++) { inorderMap.TryAdd(inorder[i], i); } TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1); return root; } [Test] public void Test() { int[] preorder = [3, 9, 2, 1, 7]; int[] inorder = [9, 3, 1, 2, 7]; Console.WriteLine("Preorder traversal = " + string.Join(", ", preorder)); Console.WriteLine("Inorder traversal = " + string.Join(", ", inorder)); TreeNode? root = BuildTree(preorder, inorder); Console.WriteLine("The constructed binary tree is:"); PrintUtil.PrintTree(root); } } ================================================ FILE: en/codes/csharp/chapter_divide_and_conquer/hanota.cs ================================================ /** * File: hanota.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class hanota { /* Move a disk */ void Move(List src, List tar) { // Take out a disk from the top of src int pan = src[^1]; src.RemoveAt(src.Count - 1); // Place the disk on top of tar tar.Add(pan); } /* Solve the Tower of Hanoi problem f(i) */ void DFS(int i, List src, List buf, List tar) { // If there is only one disk left in src, move it directly to tar if (i == 1) { Move(src, tar); return; } // Subproblem f(i-1): move the top i-1 disks from src to buf using tar DFS(i - 1, src, tar, buf); // Subproblem f(1): move the remaining disk from src to tar Move(src, tar); // Subproblem f(i-1): move the top i-1 disks from buf to tar using src DFS(i - 1, buf, src, tar); } /* Solve the Tower of Hanoi problem */ void SolveHanota(List A, List B, List C) { int n = A.Count; // Move the top n disks from A to C using B DFS(n, A, B, C); } [Test] public void Test() { // The tail of the list is the top of the rod List A = [5, 4, 3, 2, 1]; List B = []; List C = []; Console.WriteLine("In initial state:"); Console.WriteLine("A = " + string.Join(", ", A)); Console.WriteLine("B = " + string.Join(", ", B)); Console.WriteLine("C = " + string.Join(", ", C)); SolveHanota(A, B, C); Console.WriteLine("After disk movement is complete:"); Console.WriteLine("A = " + string.Join(", ", A)); Console.WriteLine("B = " + string.Join(", ", B)); Console.WriteLine("C = " + string.Join(", ", C)); } } ================================================ FILE: en/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs ================================================ /** * File: climbing_stairs_backtrack.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_backtrack { /* Backtracking */ void Backtrack(List choices, int state, int n, List res) { // When climbing to the n-th stair, add 1 to the solution count if (state == n) res[0]++; // Traverse all choices foreach (int choice in choices) { // Pruning: not allowed to go beyond the n-th stair if (state + choice > n) continue; // Attempt: make choice, update state Backtrack(choices, state + choice, n, res); // Backtrack } } /* Climbing stairs: Backtracking */ int ClimbingStairsBacktrack(int n) { List choices = [1, 2]; // Can choose to climb up 1 or 2 stairs int state = 0; // Start climbing from the 0-th stair List res = [0]; // Use res[0] to record the solution count Backtrack(choices, state, n, res); return res[0]; } [Test] public void Test() { int n = 9; int res = ClimbingStairsBacktrack(n); Console.WriteLine($"Climbing {n} stairs has {res} solutions"); } } ================================================ FILE: en/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs ================================================ /** * File: climbing_stairs_constraint_dp.cs * Created Time: 2023-07-03 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_constraint_dp { /* Climbing stairs with constraint: Dynamic programming */ int ClimbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // Initialize dp table, used to store solutions to subproblems int[,] dp = new int[n + 1, 3]; // Initial state: preset the solution to the smallest subproblem dp[1, 1] = 1; dp[1, 2] = 0; dp[2, 1] = 0; dp[2, 2] = 1; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i, 1] = dp[i - 1, 2]; dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2]; } return dp[n, 1] + dp[n, 2]; } [Test] public void Test() { int n = 9; int res = ClimbingStairsConstraintDP(n); Console.WriteLine($"Climbing {n} stairs has {res} solutions"); } } ================================================ FILE: en/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs ================================================ /** * File: climbing_stairs_dfs.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dfs { /* Search */ int DFS(int i) { // Known dp[1] and dp[2], return them 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; } /* Climbing stairs: Search */ int ClimbingStairsDFS(int n) { return DFS(n); } [Test] public void Test() { int n = 9; int res = ClimbingStairsDFS(n); Console.WriteLine($"Climbing {n} stairs has {res} solutions"); } } ================================================ FILE: en/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs ================================================ /** * File: climbing_stairs_dfs_mem.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dfs_mem { /* Memoization search */ int DFS(int i, int[] mem) { // Known dp[1] and dp[2], return them if (i == 1 || i == 2) return i; // If record dp[i] exists, return it directly 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); // Record dp[i] mem[i] = count; return count; } /* Climbing stairs: Memoization search */ int ClimbingStairsDFSMem(int n) { // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record int[] mem = new int[n + 1]; Array.Fill(mem, -1); return DFS(n, mem); } [Test] public void Test() { int n = 9; int res = ClimbingStairsDFSMem(n); Console.WriteLine($"Climbing {n} stairs has {res} solutions"); } } ================================================ FILE: en/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs ================================================ /** * File: climbing_stairs_dp.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dp { /* Climbing stairs: Dynamic programming */ int ClimbingStairsDP(int n) { if (n == 1 || n == 2) return n; // Initialize dp table, used to store solutions to subproblems int[] dp = new int[n + 1]; // Initial state: preset the solution to the smallest subproblem dp[1] = 1; dp[2] = 2; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* Climbing stairs: Space-optimized dynamic programming */ int ClimbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } [Test] public void Test() { int n = 9; int res = ClimbingStairsDP(n); Console.WriteLine($"Climbing {n} stairs has {res} solutions"); res = ClimbingStairsDPComp(n); Console.WriteLine($"Climbing {n} stairs has {res} solutions"); } } ================================================ FILE: en/codes/csharp/chapter_dynamic_programming/coin_change.cs ================================================ /** * File: coin_change.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class coin_change { /* Coin change: Dynamic programming */ int CoinChangeDP(int[] coins, int amt) { int n = coins.Length; int MAX = amt + 1; // Initialize dp table int[,] dp = new int[n + 1, amt + 1]; // State transition: first row and first column for (int a = 1; a <= amt; a++) { dp[0, a] = MAX; } // State transition: rest of the rows and columns for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i, a] = dp[i - 1, a]; } else { // The smaller value between not selecting and selecting coin 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; } /* Coin change: Space-optimized dynamic programming */ int CoinChangeDPComp(int[] coins, int amt) { int n = coins.Length; int MAX = amt + 1; // Initialize dp table int[] dp = new int[amt + 1]; Array.Fill(dp, MAX); dp[0] = 0; // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // The smaller value between not selecting and selecting coin i dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } [Test] public void Test() { int[] coins = [1, 2, 5]; int amt = 4; // Dynamic programming int res = CoinChangeDP(coins, amt); Console.WriteLine("Minimum number of coins needed to make target amount is " + res); // Space-optimized dynamic programming res = CoinChangeDPComp(coins, amt); Console.WriteLine("Minimum number of coins needed to make target amount is " + res); } } ================================================ FILE: en/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs ================================================ /** * File: coin_change_ii.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class coin_change_ii { /* Coin change II: Dynamic programming */ int CoinChangeIIDP(int[] coins, int amt) { int n = coins.Length; // Initialize dp table int[,] dp = new int[n + 1, amt + 1]; // Initialize first column for (int i = 0; i <= n; i++) { dp[i, 0] = 1; } // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i, a] = dp[i - 1, a]; } else { // Sum of the two options: not selecting and selecting coin i dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]]; } } } return dp[n, amt]; } /* Coin change II: Space-optimized dynamic programming */ int CoinChangeIIDPComp(int[] coins, int amt) { int n = coins.Length; // Initialize dp table int[] dp = new int[amt + 1]; dp[0] = 1; // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // Sum of the two options: not selecting and selecting coin i dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } [Test] public void Test() { int[] coins = [1, 2, 5]; int amt = 5; // Dynamic programming int res = CoinChangeIIDP(coins, amt); Console.WriteLine("Number of coin combinations to make target amount is " + res); // Space-optimized dynamic programming res = CoinChangeIIDPComp(coins, amt); Console.WriteLine("Number of coin combinations to make target amount is " + res); } } ================================================ FILE: en/codes/csharp/chapter_dynamic_programming/edit_distance.cs ================================================ /** * File: edit_distance.cs * Created Time: 2023-07-14 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class edit_distance { /* Edit distance: Brute-force search */ int EditDistanceDFS(string s, string t, int i, int j) { // If both s and t are empty, return 0 if (i == 0 && j == 0) return 0; // If s is empty, return length of t if (i == 0) return j; // If t is empty, return length of s if (j == 0) return i; // If two characters are equal, skip both characters if (s[i - 1] == t[j - 1]) return EditDistanceDFS(s, t, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 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 minimum edit steps return Math.Min(Math.Min(insert, delete), replace) + 1; } /* Edit distance: Memoization search */ int EditDistanceDFSMem(string s, string t, int[][] mem, int i, int j) { // If both s and t are empty, return 0 if (i == 0 && j == 0) return 0; // If s is empty, return length of t if (i == 0) return j; // If t is empty, return length of s if (j == 0) return i; // If there's a record, return it directly if (mem[i][j] != -1) return mem[i][j]; // If two characters are equal, skip both characters if (s[i - 1] == t[j - 1]) return EditDistanceDFSMem(s, t, mem, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 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); // Record and return minimum edit steps mem[i][j] = Math.Min(Math.Min(insert, delete), replace) + 1; return mem[i][j]; } /* Edit distance: Dynamic programming */ int EditDistanceDP(string s, string t) { int n = s.Length, m = t.Length; int[,] dp = new int[n + 1, m + 1]; // State transition: first row and first column for (int i = 1; i <= n; i++) { dp[i, 0] = i; } for (int j = 1; j <= m; j++) { dp[0, j] = j; } // State transition: rest of the rows and columns for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // If two characters are equal, skip both characters dp[i, j] = dp[i - 1, j - 1]; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 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]; } /* Edit distance: Space-optimized dynamic programming */ int EditDistanceDPComp(string s, string t) { int n = s.Length, m = t.Length; int[] dp = new int[m + 1]; // State transition: first row for (int j = 1; j <= m; j++) { dp[j] = j; } // State transition: rest of the rows for (int i = 1; i <= n; i++) { // State transition: first column int leftup = dp[0]; // Temporarily store dp[i-1, j-1] dp[0] = i; // State transition: rest of the columns for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // If two characters are equal, skip both characters dp[j] = leftup; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // Update for next round's dp[i-1, j-1] } } return dp[m]; } [Test] public void Test() { string s = "bag"; string t = "pack"; int n = s.Length, m = t.Length; // Brute-force search int res = EditDistanceDFS(s, t, n, m); Console.WriteLine("Change " + s + " to " + t + " requires a minimum of " + res + " edits"); // Memoization search int[][] mem = new int[n + 1][]; for (int i = 0; i <= n; i++) { mem[i] = new int[m + 1]; Array.Fill(mem[i], -1); } res = EditDistanceDFSMem(s, t, mem, n, m); Console.WriteLine("Change " + s + " to " + t + " requires a minimum of " + res + " edits"); // Dynamic programming res = EditDistanceDP(s, t); Console.WriteLine("Change " + s + " to " + t + " requires a minimum of " + res + " edits"); // Space-optimized dynamic programming res = EditDistanceDPComp(s, t); Console.WriteLine("Change " + s + " to " + t + " requires a minimum of " + res + " edits"); } } ================================================ FILE: en/codes/csharp/chapter_dynamic_programming/knapsack.cs ================================================ /** * File: knapsack.cs * Created Time: 2023-07-07 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class knapsack { /* 0-1 knapsack: Brute-force search */ int KnapsackDFS(int[] weight, int[] val, int i, int c) { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i == 0 || c == 0) { return 0; } // If exceeds knapsack capacity, can only choose not to put it in if (weight[i - 1] > c) { return KnapsackDFS(weight, val, i - 1, c); } // Calculate the maximum value of not putting in and putting in item i int no = KnapsackDFS(weight, val, i - 1, c); int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1]; // Return the larger value of the two options return Math.Max(no, yes); } /* 0-1 knapsack: Memoization search */ int KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i == 0 || c == 0) { return 0; } // If there's a record, return it directly if (mem[i][c] != -1) { return mem[i][c]; } // If exceeds knapsack capacity, can only choose not to put it in if (weight[i - 1] > c) { return KnapsackDFSMem(weight, val, mem, i - 1, c); } // Calculate the maximum value of not putting in and putting in item i int no = KnapsackDFSMem(weight, val, mem, i - 1, c); int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1]; // Record and return the larger value of the two options mem[i][c] = Math.Max(no, yes); return mem[i][c]; } /* 0-1 knapsack: Dynamic programming */ int KnapsackDP(int[] weight, int[] val, int cap) { int n = weight.Length; // Initialize dp table int[,] dp = new int[n + 1, cap + 1]; // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (weight[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i, c] = dp[i - 1, c]; } else { // The larger value between not selecting and selecting item i dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]); } } } return dp[n, cap]; } /* 0-1 knapsack: Space-optimized dynamic programming */ int KnapsackDPComp(int[] weight, int[] val, int cap) { int n = weight.Length; // Initialize dp table int[] dp = new int[cap + 1]; // State transition for (int i = 1; i <= n; i++) { // Traverse in reverse order for (int c = cap; c > 0; c--) { if (weight[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[c] = dp[c]; } else { // The larger value between not selecting and selecting item i dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]); } } } return dp[cap]; } [Test] public void Test() { int[] weight = [10, 20, 30, 40, 50]; int[] val = [50, 120, 150, 210, 240]; int cap = 50; int n = weight.Length; // Brute-force search int res = KnapsackDFS(weight, val, n, cap); Console.WriteLine("Maximum item value not exceeding knapsack capacity is " + res); // Memoization search int[][] mem = new int[n + 1][]; for (int i = 0; i <= n; i++) { mem[i] = new int[cap + 1]; Array.Fill(mem[i], -1); } res = KnapsackDFSMem(weight, val, mem, n, cap); Console.WriteLine("Maximum item value not exceeding knapsack capacity is " + res); // Dynamic programming res = KnapsackDP(weight, val, cap); Console.WriteLine("Maximum item value not exceeding knapsack capacity is " + res); // Space-optimized dynamic programming res = KnapsackDPComp(weight, val, cap); Console.WriteLine("Maximum item value not exceeding knapsack capacity is " + res); } } ================================================ FILE: en/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs ================================================ /** * File: min_cost_climbing_stairs_dp.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class min_cost_climbing_stairs_dp { /* Minimum cost climbing stairs: Dynamic programming */ int MinCostClimbingStairsDP(int[] cost) { int n = cost.Length - 1; if (n == 1 || n == 2) return cost[n]; // Initialize dp table, used to store solutions to subproblems int[] dp = new int[n + 1]; // Initial state: preset the solution to the smallest subproblem dp[1] = cost[1]; dp[2] = cost[2]; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* Minimum cost climbing stairs: Space-optimized dynamic programming */ int MinCostClimbingStairsDPComp(int[] cost) { int n = cost.Length - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = Math.Min(a, tmp) + cost[i]; a = tmp; } return b; } [Test] public void Test() { int[] cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; Console.WriteLine("Input stair cost list is"); PrintUtil.PrintList(cost); int res = MinCostClimbingStairsDP(cost); Console.WriteLine($"Minimum cost to climb stairs is {res}"); res = MinCostClimbingStairsDPComp(cost); Console.WriteLine($"Minimum cost to climb stairs is {res}"); } } ================================================ FILE: en/codes/csharp/chapter_dynamic_programming/min_path_sum.cs ================================================ /** * File: min_path_sum.cs * Created Time: 2023-07-10 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class min_path_sum { /* Minimum path sum: Brute-force search */ int MinPathSumDFS(int[][] grid, int i, int j) { // If it's the top-left cell, terminate the search if (i == 0 && j == 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return int.MaxValue; } // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1) int up = MinPathSumDFS(grid, i - 1, j); int left = MinPathSumDFS(grid, i, j - 1); // Return the minimum path cost from top-left to (i, j) return Math.Min(left, up) + grid[i][j]; } /* Minimum path sum: Memoization search */ int MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { // If it's the top-left cell, terminate the search if (i == 0 && j == 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return int.MaxValue; } // If there's a record, return it directly if (mem[i][j] != -1) { return mem[i][j]; } // Minimum path cost for left and upper cells int up = MinPathSumDFSMem(grid, mem, i - 1, j); int left = MinPathSumDFSMem(grid, mem, i, j - 1); // Record and return the minimum path cost from top-left to (i, j) mem[i][j] = Math.Min(left, up) + grid[i][j]; return mem[i][j]; } /* Minimum path sum: Dynamic programming */ int MinPathSumDP(int[][] grid) { int n = grid.Length, m = grid[0].Length; // Initialize dp table int[,] dp = new int[n, m]; dp[0, 0] = grid[0][0]; // State transition: first row for (int j = 1; j < m; j++) { dp[0, j] = dp[0, j - 1] + grid[0][j]; } // State transition: first column for (int i = 1; i < n; i++) { dp[i, 0] = dp[i - 1, 0] + grid[i][0]; } // State transition: rest of the rows and columns 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]; } /* Minimum path sum: Space-optimized dynamic programming */ int MinPathSumDPComp(int[][] grid) { int n = grid.Length, m = grid[0].Length; // Initialize dp table int[] dp = new int[m]; dp[0] = grid[0][0]; // State transition: first row for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // State transition: rest of the rows for (int i = 1; i < n; i++) { // State transition: first column dp[0] = dp[0] + grid[i][0]; // State transition: rest of the columns for (int j = 1; j < m; j++) { dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } [Test] public void Test() { int[][] grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2] ]; int n = grid.Length, m = grid[0].Length; // Brute-force search int res = MinPathSumDFS(grid, n - 1, m - 1); Console.WriteLine("Minimum path sum from top-left to bottom-right is " + res); // Memoization search int[][] mem = new int[n][]; for (int i = 0; i < n; i++) { mem[i] = new int[m]; Array.Fill(mem[i], -1); } res = MinPathSumDFSMem(grid, mem, n - 1, m - 1); Console.WriteLine("Minimum path sum from top-left to bottom-right is " + res); // Dynamic programming res = MinPathSumDP(grid); Console.WriteLine("Minimum path sum from top-left to bottom-right is " + res); // Space-optimized dynamic programming res = MinPathSumDPComp(grid); Console.WriteLine("Minimum path sum from top-left to bottom-right is " + res); } } ================================================ FILE: en/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs ================================================ /** * File: unbounded_knapsack.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class unbounded_knapsack { /* Unbounded knapsack: Dynamic programming */ int UnboundedKnapsackDP(int[] wgt, int[] val, int cap) { int n = wgt.Length; // Initialize dp table int[,] dp = new int[n + 1, cap + 1]; // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i, c] = dp[i - 1, c]; } else { // The larger value between not selecting and selecting item i dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]); } } } return dp[n, cap]; } /* Unbounded knapsack: Space-optimized dynamic programming */ int UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { int n = wgt.Length; // Initialize dp table int[] dp = new int[cap + 1]; // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[c] = dp[c]; } else { // The larger value between not selecting and selecting item i dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } [Test] public void Test() { int[] wgt = [1, 2, 3]; int[] val = [5, 11, 15]; int cap = 4; // Dynamic programming int res = UnboundedKnapsackDP(wgt, val, cap); Console.WriteLine("Maximum item value not exceeding knapsack capacity is " + res); // Space-optimized dynamic programming res = UnboundedKnapsackDPComp(wgt, val, cap); Console.WriteLine("Maximum item value not exceeding knapsack capacity is " + res); } } ================================================ FILE: en/codes/csharp/chapter_graph/graph_adjacency_list.cs ================================================ /** * File: graph_adjacency_list.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_graph; /* Undirected graph class based on adjacency list */ public class GraphAdjList { // Adjacency list, key: vertex, value: all adjacent vertices of that vertex public Dictionary> adjList; /* Constructor */ public GraphAdjList(Vertex[][] edges) { adjList = []; // Add all vertices and edges foreach (Vertex[] edge in edges) { AddVertex(edge[0]); AddVertex(edge[1]); AddEdge(edge[0], edge[1]); } } /* Get the number of vertices */ int Size() { return adjList.Count; } /* Add edge */ public void AddEdge(Vertex vet1, Vertex vet2) { if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) throw new InvalidOperationException(); // Add edge vet1 - vet2 adjList[vet1].Add(vet2); adjList[vet2].Add(vet1); } /* Remove edge */ public void RemoveEdge(Vertex vet1, Vertex vet2) { if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) throw new InvalidOperationException(); // Remove edge vet1 - vet2 adjList[vet1].Remove(vet2); adjList[vet2].Remove(vet1); } /* Add vertex */ public void AddVertex(Vertex vet) { if (adjList.ContainsKey(vet)) return; // Add a new linked list in the adjacency list adjList.Add(vet, []); } /* Remove vertex */ public void RemoveVertex(Vertex vet) { if (!adjList.ContainsKey(vet)) throw new InvalidOperationException(); // Remove the linked list corresponding to vertex vet in the adjacency list adjList.Remove(vet); // Traverse the linked lists of other vertices and remove all edges containing vet foreach (List list in adjList.Values) { list.Remove(vet); } } /* Print adjacency list */ public void Print() { Console.WriteLine("Adjacency list ="); foreach (KeyValuePair> pair in adjList) { List tmp = []; foreach (Vertex vertex in pair.Value) tmp.Add(vertex.val); Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); } } } public class graph_adjacency_list { [Test] public void Test() { /* Add edge */ Vertex[] v = Vertex.ValsToVets([1, 3, 2, 5, 4]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]] ]; GraphAdjList graph = new(edges); Console.WriteLine("\nAfter initialization, graph is"); graph.Print(); /* Add edge */ // Vertices 1, 3 are v[0], v[1] graph.AddEdge(v[0], v[2]); Console.WriteLine("\nAfter adding edge 1-2, graph is"); graph.Print(); /* Remove edge */ // Vertex 3 is v[1] graph.RemoveEdge(v[0], v[1]); Console.WriteLine("\nAfter removing edge 1-3, graph is"); graph.Print(); /* Add vertex */ Vertex v5 = new(6); graph.AddVertex(v5); Console.WriteLine("\nAfter adding vertex 6, graph is"); graph.Print(); /* Remove vertex */ // Vertex 3 is v[1] graph.RemoveVertex(v[1]); Console.WriteLine("\nAfter removing vertex 3, graph is"); graph.Print(); } } ================================================ FILE: en/codes/csharp/chapter_graph/graph_adjacency_matrix.cs ================================================ /** * File: graph_adjacency_matrix.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_graph; /* Undirected graph class based on adjacency matrix */ class GraphAdjMat { List vertices; // Vertex list, where the element represents the "vertex value" and the index represents the "vertex index" List> adjMat; // Adjacency matrix, where the row and column indices correspond to the "vertex index" /* Constructor */ public GraphAdjMat(int[] vertices, int[][] edges) { this.vertices = []; this.adjMat = []; // Add vertex foreach (int val in vertices) { AddVertex(val); } // Add edge // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices foreach (int[] e in edges) { AddEdge(e[0], e[1]); } } /* Get the number of vertices */ int Size() { return vertices.Count; } /* Add vertex */ public void AddVertex(int val) { int n = Size(); // Add the value of the new vertex to the vertex list vertices.Add(val); // Add a row to the adjacency matrix List newRow = new(n); for (int j = 0; j < n; j++) { newRow.Add(0); } adjMat.Add(newRow); // Add a column to the adjacency matrix foreach (List row in adjMat) { row.Add(0); } } /* Remove vertex */ public void RemoveVertex(int index) { if (index >= Size()) throw new IndexOutOfRangeException(); // Remove the vertex at index from the vertex list vertices.RemoveAt(index); // Remove the row at index from the adjacency matrix adjMat.RemoveAt(index); // Remove the column at index from the adjacency matrix foreach (List row in adjMat) { row.RemoveAt(index); } } /* Add edge */ // Parameters i, j correspond to the vertices element indices public void AddEdge(int i, int j) { // Handle index out of bounds and equality if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) throw new IndexOutOfRangeException(); // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i) adjMat[i][j] = 1; adjMat[j][i] = 1; } /* Remove edge */ // Parameters i, j correspond to the vertices element indices public void RemoveEdge(int i, int j) { // Handle index out of bounds and equality if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) throw new IndexOutOfRangeException(); adjMat[i][j] = 0; adjMat[j][i] = 0; } /* Print adjacency matrix */ public void Print() { Console.Write("Vertex list = "); PrintUtil.PrintList(vertices); Console.WriteLine("Adjacency matrix ="); PrintUtil.PrintMatrix(adjMat); } } public class graph_adjacency_matrix { [Test] public void Test() { /* Add edge */ // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices int[] vertices = [1, 3, 2, 5, 4]; int[][] edges = [ [0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4] ]; GraphAdjMat graph = new(vertices, edges); Console.WriteLine("\nAfter initialization, graph is"); graph.Print(); /* Add edge */ // Add vertex graph.AddEdge(0, 2); Console.WriteLine("\nAfter adding edge 1-2, graph is"); graph.Print(); /* Remove edge */ // Vertices 1, 3 have indices 0, 1 respectively graph.RemoveEdge(0, 1); Console.WriteLine("\nAfter removing edge 1-3, graph is"); graph.Print(); /* Add vertex */ graph.AddVertex(6); Console.WriteLine("\nAfter adding vertex 6, graph is"); graph.Print(); /* Remove vertex */ // Vertex 3 has index 1 graph.RemoveVertex(1); Console.WriteLine("\nAfter removing vertex 3, graph is"); graph.Print(); } } ================================================ FILE: en/codes/csharp/chapter_graph/graph_bfs.cs ================================================ /** * File: graph_bfs.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_graph; public class graph_bfs { /* Breadth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex List GraphBFS(GraphAdjList graph, Vertex startVet) { // Vertex traversal sequence List res = []; // Hash set for recording vertices that have been visited HashSet visited = [startVet]; // Queue used to implement BFS Queue que = new(); que.Enqueue(startVet); // Starting from vertex vet, loop until all vertices are visited while (que.Count > 0) { Vertex vet = que.Dequeue(); // Dequeue the front vertex res.Add(vet); // Record visited vertex foreach (Vertex adjVet in graph.adjList[vet]) { if (visited.Contains(adjVet)) { continue; // Skip vertices that have been visited } que.Enqueue(adjVet); // Only enqueue unvisited vertices visited.Add(adjVet); // Mark this vertex as visited } } // Return vertex traversal sequence return res; } [Test] public void Test() { /* Add edge */ Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]] ]; GraphAdjList graph = new(edges); Console.WriteLine("\nAfter initialization, graph is"); graph.Print(); /* Breadth-first traversal */ List res = GraphBFS(graph, v[0]); Console.WriteLine("\nBreadth-first traversal (BFS) vertex sequence is"); Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); } } ================================================ FILE: en/codes/csharp/chapter_graph/graph_dfs.cs ================================================ /** * File: graph_dfs.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_graph; public class graph_dfs { /* Depth-first traversal helper function */ void DFS(GraphAdjList graph, HashSet visited, List res, Vertex vet) { res.Add(vet); // Record visited vertex visited.Add(vet); // Mark this vertex as visited // Traverse all adjacent vertices of this vertex foreach (Vertex adjVet in graph.adjList[vet]) { if (visited.Contains(adjVet)) { continue; // Skip vertices that have been visited } // Recursively visit adjacent vertices DFS(graph, visited, res, adjVet); } } /* Depth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex List GraphDFS(GraphAdjList graph, Vertex startVet) { // Vertex traversal sequence List res = []; // Hash set for recording vertices that have been visited HashSet visited = []; DFS(graph, visited, res, startVet); return res; } [Test] public void Test() { /* Add edge */ Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; GraphAdjList graph = new(edges); Console.WriteLine("\nAfter initialization, graph is"); graph.Print(); /* Depth-first traversal */ List res = GraphDFS(graph, v[0]); Console.WriteLine("\nDepth-first traversal (DFS) vertex sequence is"); Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); } } ================================================ FILE: en/codes/csharp/chapter_greedy/coin_change_greedy.cs ================================================ /** * File: coin_change_greedy.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class coin_change_greedy { /* Coin change: Greedy algorithm */ int CoinChangeGreedy(int[] coins, int amt) { // Assume coins list is sorted int i = coins.Length - 1; int count = 0; // Loop to make greedy choices until no remaining amount while (amt > 0) { // Find the coin that is less than and closest to the remaining amount while (i > 0 && coins[i] > amt) { i--; } // Choose coins[i] amt -= coins[i]; count++; } // If no feasible solution is found, return -1 return amt == 0 ? count : -1; } [Test] public void Test() { // Greedy algorithm: Can guarantee finding the global optimal solution int[] coins = [1, 5, 10, 20, 50, 100]; int amt = 186; int res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("To make " + amt + ", minimum number of coins needed is " + res); // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 20, 50]; amt = 60; res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("To make " + amt + ", minimum number of coins needed is " + res); Console.WriteLine("Actually the minimum number needed is 3, i.e., 20 + 20 + 20"); // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 49, 50]; amt = 98; res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("To make " + amt + ", minimum number of coins needed is " + res); Console.WriteLine("Actually the minimum number needed is 2, i.e., 49 + 49"); } } ================================================ FILE: en/codes/csharp/chapter_greedy/fractional_knapsack.cs ================================================ /** * File: fractional_knapsack.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; /* Item */ class Item(int w, int v) { public int w = w; // Item weight public int v = v; // Item value } public class fractional_knapsack { /* Fractional knapsack: Greedy algorithm */ double FractionalKnapsack(int[] wgt, int[] val, int cap) { // Create item list with two attributes: weight, value Item[] items = new Item[wgt.Length]; for (int i = 0; i < wgt.Length; i++) { items[i] = new Item(wgt[i], val[i]); } // Sort by unit value item.v / item.w from high to low Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w)); // Loop for greedy selection double res = 0; foreach (Item item in items) { if (item.w <= cap) { // If remaining capacity is sufficient, put the entire current item into the knapsack res += item.v; cap -= item.w; } else { // If remaining capacity is insufficient, put part of the current item into the knapsack res += (double)item.v / item.w * cap; // No remaining capacity, so break out of the loop break; } } return res; } [Test] public void Test() { int[] wgt = [10, 20, 30, 40, 50]; int[] val = [50, 120, 150, 210, 240]; int cap = 50; // Greedy algorithm double res = FractionalKnapsack(wgt, val, cap); Console.WriteLine("Maximum item value not exceeding knapsack capacity is " + res); } } ================================================ FILE: en/codes/csharp/chapter_greedy/max_capacity.cs ================================================ /** * File: max_capacity.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class max_capacity { /* Max capacity: Greedy algorithm */ int MaxCapacity(int[] ht) { // Initialize i, j to be at both ends of the array int i = 0, j = ht.Length - 1; // Initial max capacity is 0 int res = 0; // Loop for greedy selection until the two boards meet while (i < j) { // Update max capacity int cap = Math.Min(ht[i], ht[j]) * (j - i); res = Math.Max(res, cap); // Move the shorter board inward if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } [Test] public void Test() { int[] ht = [3, 8, 5, 2, 7, 7, 3, 4]; // Greedy algorithm int res = MaxCapacity(ht); Console.WriteLine("Maximum capacity is " + res); } } ================================================ FILE: en/codes/csharp/chapter_greedy/max_product_cutting.cs ================================================ /** * File: max_product_cutting.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class max_product_cutting { /* Max product cutting: Greedy algorithm */ int MaxProductCutting(int n) { // When n <= 3, must cut out a 1 if (n <= 3) { return 1 * (n - 1); } // Greedily cut out 3, a is the number of 3s, b is the remainder int a = n / 3; int b = n % 3; if (b == 1) { // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2 return (int)Math.Pow(3, a - 1) * 2 * 2; } if (b == 2) { // When the remainder is 2, do nothing return (int)Math.Pow(3, a) * 2; } // When the remainder is 0, do nothing return (int)Math.Pow(3, a); } [Test] public void Test() { int n = 58; // Greedy algorithm int res = MaxProductCutting(n); Console.WriteLine("Maximum cutting product is" + res); } } ================================================ FILE: en/codes/csharp/chapter_hashing/array_hash_map.cs ================================================ /** * File: array_hash_map.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_hashing; /* Key-value pair int->string */ class Pair(int key, string val) { public int key = key; public string val = val; } /* Hash table based on array implementation */ class ArrayHashMap { List buckets; public ArrayHashMap() { // Initialize array with 100 buckets buckets = []; for (int i = 0; i < 100; i++) { buckets.Add(null); } } /* Hash function */ int HashFunc(int key) { int index = key % 100; return index; } /* Query operation */ public string? Get(int key) { int index = HashFunc(key); Pair? pair = buckets[index]; if (pair == null) return null; return pair.val; } /* Add operation */ public void Put(int key, string val) { Pair pair = new(key, val); int index = HashFunc(key); buckets[index] = pair; } /* Remove operation */ public void Remove(int key) { int index = HashFunc(key); // Set to null to represent deletion buckets[index] = null; } /* Get all key-value pairs */ public List PairSet() { List pairSet = []; foreach (Pair? pair in buckets) { if (pair != null) pairSet.Add(pair); } return pairSet; } /* Get all keys */ public List KeySet() { List keySet = []; foreach (Pair? pair in buckets) { if (pair != null) keySet.Add(pair.key); } return keySet; } /* Get all values */ public List ValueSet() { List valueSet = []; foreach (Pair? pair in buckets) { if (pair != null) valueSet.Add(pair.val); } return valueSet; } /* Print hash table */ public void Print() { foreach (Pair kv in PairSet()) { Console.WriteLine(kv.key + " -> " + kv.val); } } } public class array_hash_map { [Test] public void Test() { /* Initialize hash table */ ArrayHashMap map = new(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.Put(12836, "Xiao Ha"); map.Put(15937, "Xiao Luo"); map.Put(16750, "Xiao Suan"); map.Put(13276, "Xiao Fa"); map.Put(10583, "Xiao Ya"); Console.WriteLine("\nAfter adding is complete, hash table is\nKey -> Value"); map.Print(); /* Query operation */ // Input key into hash table to get value string? name = map.Get(15937); Console.WriteLine("\nInput student ID 15937, query name " + name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.Remove(10583); Console.WriteLine("\nAfter removing 10583, hash table is\nKey -> Value"); map.Print(); /* Traverse hash table */ Console.WriteLine("\nTraverse key-value pairs Key->Value"); foreach (Pair kv in map.PairSet()) { Console.WriteLine(kv.key + " -> " + kv.val); } Console.WriteLine("\nTraverse keys only Key"); foreach (int key in map.KeySet()) { Console.WriteLine(key); } Console.WriteLine("\nTraverse values only Value"); foreach (string val in map.ValueSet()) { Console.WriteLine(val); } } } ================================================ FILE: en/codes/csharp/chapter_hashing/built_in_hash.cs ================================================ /** * File: built_in_hash.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; public class built_in_hash { [Test] public void Test() { int num = 3; int hashNum = num.GetHashCode(); Console.WriteLine("Integer " + num + " has hash value " + hashNum); bool bol = true; int hashBol = bol.GetHashCode(); Console.WriteLine("Boolean " + bol + " has hash value " + hashBol); double dec = 3.14159; int hashDec = dec.GetHashCode(); Console.WriteLine("Decimal " + dec + " has hash value " + hashDec); string str = "Hello Algo"; int hashStr = str.GetHashCode(); Console.WriteLine("String " + str + " has hash value " + hashStr); object[] arr = [12836, "Xiao Ha"]; int hashTup = arr.GetHashCode(); Console.WriteLine("Array [" + string.Join(", ", arr) + "] hash value is " + hashTup); ListNode obj = new(0); int hashObj = obj.GetHashCode(); Console.WriteLine("Node object " + obj + " has hash value " + hashObj); } } ================================================ FILE: en/codes/csharp/chapter_hashing/hash_map.cs ================================================ /** * File: hash_map.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_hashing; public class hash_map { [Test] public void Test() { /* Initialize hash table */ Dictionary map = new() { /* Add operation */ // Add key-value pair (key, value) to the hash table { 12836, "Xiao Ha" }, { 15937, "Xiao Luo" }, { 16750, "Xiao Suan" }, { 13276, "Xiao Fa" }, { 10583, "Xiao Ya" } }; Console.WriteLine("\nAfter adding is complete, hash table is\nKey -> Value"); PrintUtil.PrintHashMap(map); /* Query operation */ // Input key into hash table to get value string name = map[15937]; Console.WriteLine("\nInput student ID 15937, query name " + name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.Remove(10583); Console.WriteLine("\nAfter removing 10583, hash table is\nKey -> Value"); PrintUtil.PrintHashMap(map); /* Traverse hash table */ Console.WriteLine("\nTraverse key-value pairs Key->Value"); foreach (var kv in map) { Console.WriteLine(kv.Key + " -> " + kv.Value); } Console.WriteLine("\nTraverse keys only Key"); foreach (int key in map.Keys) { Console.WriteLine(key); } Console.WriteLine("\nTraverse values only Value"); foreach (string val in map.Values) { Console.WriteLine(val); } } } ================================================ FILE: en/codes/csharp/chapter_hashing/hash_map_chaining.cs ================================================ /** * File: hash_map_chaining.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; /* Hash table with separate chaining */ class HashMapChaining { int size; // Number of key-value pairs int capacity; // Hash table capacity double loadThres; // Load factor threshold for triggering expansion int extendRatio; // Expansion multiplier List> buckets; // Bucket array /* Constructor */ public HashMapChaining() { size = 0; capacity = 4; loadThres = 2.0 / 3.0; extendRatio = 2; buckets = new List>(capacity); for (int i = 0; i < capacity; i++) { buckets.Add([]); } } /* Hash function */ int HashFunc(int key) { return key % capacity; } /* Load factor */ double LoadFactor() { return (double)size / capacity; } /* Query operation */ public string? Get(int key) { int index = HashFunc(key); // Traverse bucket, if key is found, return corresponding val foreach (Pair pair in buckets[index]) { if (pair.key == key) { return pair.val; } } // If key is not found, return null return null; } /* Add operation */ public void Put(int key, string val) { // When load factor exceeds threshold, perform expansion if (LoadFactor() > loadThres) { Extend(); } int index = HashFunc(key); // Traverse bucket, if specified key is encountered, update corresponding val and return foreach (Pair pair in buckets[index]) { if (pair.key == key) { pair.val = val; return; } } // If key does not exist, append key-value pair to the end buckets[index].Add(new Pair(key, val)); size++; } /* Remove operation */ public void Remove(int key) { int index = HashFunc(key); // Traverse bucket and remove key-value pair from it foreach (Pair pair in buckets[index].ToList()) { if (pair.key == key) { buckets[index].Remove(pair); size--; break; } } } /* Expand hash table */ void Extend() { // Temporarily store the original hash table List> bucketsTmp = buckets; // Initialize expanded new hash table capacity *= extendRatio; buckets = new List>(capacity); for (int i = 0; i < capacity; i++) { buckets.Add([]); } size = 0; // Move key-value pairs from original hash table to new hash table foreach (List bucket in bucketsTmp) { foreach (Pair pair in bucket) { Put(pair.key, pair.val); } } } /* Print hash table */ public void Print() { foreach (List bucket in buckets) { List res = []; foreach (Pair pair in bucket) { res.Add(pair.key + " -> " + pair.val); } foreach (string kv in res) { Console.WriteLine(kv); } } } } public class hash_map_chaining { [Test] public void Test() { /* Initialize hash table */ HashMapChaining map = new(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.Put(12836, "Xiao Ha"); map.Put(15937, "Xiao Luo"); map.Put(16750, "Xiao Suan"); map.Put(13276, "Xiao Fa"); map.Put(10583, "Xiao Ya"); Console.WriteLine("\nAfter adding is complete, hash table is\nKey -> Value"); map.Print(); /* Query operation */ // Input key into hash table to get value string? name = map.Get(13276); Console.WriteLine("\nInput student ID 13276, query name " + name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.Remove(12836); Console.WriteLine("\nAfter removing 12836, hash table is\nKey -> Value"); map.Print(); } } ================================================ FILE: en/codes/csharp/chapter_hashing/hash_map_open_addressing.cs ================================================ /** * File: hash_map_open_addressing.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; /* Hash table with open addressing */ class HashMapOpenAddressing { int size; // Number of key-value pairs int capacity = 4; // Hash table capacity double loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion int extendRatio = 2; // Expansion multiplier Pair[] buckets; // Bucket array Pair TOMBSTONE = new(-1, "-1"); // Removal marker /* Constructor */ public HashMapOpenAddressing() { size = 0; buckets = new Pair[capacity]; } /* Hash function */ int HashFunc(int key) { return key % capacity; } /* Load factor */ double LoadFactor() { return (double)size / capacity; } /* Search for bucket index corresponding to key */ int FindBucket(int key) { int index = HashFunc(key); int firstTombstone = -1; // Linear probing, break when encountering an empty bucket while (buckets[index] != null) { // If key is encountered, return the corresponding bucket index if (buckets[index].key == key) { // If a removal marker was encountered before, move the key-value pair to that index if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index]; buckets[index] = TOMBSTONE; return firstTombstone; // Return the moved bucket index } return index; // Return bucket index } // Record the first removal marker encountered if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index; } // Calculate bucket index, wrap around to the head if past the tail index = (index + 1) % capacity; } // If key does not exist, return the index for insertion return firstTombstone == -1 ? index : firstTombstone; } /* Query operation */ public string? Get(int key) { // Search for bucket index corresponding to key int index = FindBucket(key); // If key-value pair is found, return corresponding val if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index].val; } // If key-value pair does not exist, return null return null; } /* Add operation */ public void Put(int key, string val) { // When load factor exceeds threshold, perform expansion if (LoadFactor() > loadThres) { Extend(); } // Search for bucket index corresponding to key int index = FindBucket(key); // If key-value pair is found, overwrite val and return if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index].val = val; return; } // If key-value pair does not exist, add the key-value pair buckets[index] = new Pair(key, val); size++; } /* Remove operation */ public void Remove(int key) { // Search for bucket index corresponding to key int index = FindBucket(key); // If key-value pair is found, overwrite it with removal marker if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE; size--; } } /* Expand hash table */ void Extend() { // Temporarily store the original hash table Pair[] bucketsTmp = buckets; // Initialize expanded new hash table capacity *= extendRatio; buckets = new Pair[capacity]; size = 0; // Move key-value pairs from original hash table to new hash table foreach (Pair pair in bucketsTmp) { if (pair != null && pair != TOMBSTONE) { Put(pair.key, pair.val); } } } /* Print hash table */ public void Print() { foreach (Pair pair in buckets) { if (pair == null) { Console.WriteLine("null"); } else if (pair == TOMBSTONE) { Console.WriteLine("TOMBSTONE"); } else { Console.WriteLine(pair.key + " -> " + pair.val); } } } } public class hash_map_open_addressing { [Test] public void Test() { /* Initialize hash table */ HashMapOpenAddressing map = new(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.Put(12836, "Xiao Ha"); map.Put(15937, "Xiao Luo"); map.Put(16750, "Xiao Suan"); map.Put(13276, "Xiao Fa"); map.Put(10583, "Xiao Ya"); Console.WriteLine("\nAfter adding is complete, hash table is\nKey -> Value"); map.Print(); /* Query operation */ // Input key into hash table to get value string? name = map.Get(13276); Console.WriteLine("\nInput student ID 13276, query name " + name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.Remove(16750); Console.WriteLine("\nAfter removing 16750, hash table is\nKey -> Value"); map.Print(); } } ================================================ FILE: en/codes/csharp/chapter_hashing/simple_hash.cs ================================================ /** * File: simple_hash.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; public class simple_hash { /* Additive hash */ int AddHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = (hash + c) % MODULUS; } return (int)hash; } /* Multiplicative hash */ int MulHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = (31 * hash + c) % MODULUS; } return (int)hash; } /* XOR hash */ int XorHash(string key) { int hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash ^= c; } return hash & MODULUS; } /* Rotational hash */ int RotHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS; } return (int)hash; } [Test] public void Test() { string key = "Hello Algo"; int hash = AddHash(key); Console.WriteLine("Additive hash value is " + hash); hash = MulHash(key); Console.WriteLine("Multiplicative hash value is " + hash); hash = XorHash(key); Console.WriteLine("XOR hash value is " + hash); hash = RotHash(key); Console.WriteLine("Rotational hash value is " + hash); } } ================================================ FILE: en/codes/csharp/chapter_heap/heap.cs ================================================ /** * File: heap.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_heap; public class heap { void TestPush(PriorityQueue heap, int val) { heap.Enqueue(val, val); // Element enters heap Console.WriteLine($"\nAfter element {val} pushes to heap\n"); PrintUtil.PrintHeap(heap); } void TestPop(PriorityQueue heap) { int val = heap.Dequeue(); // Time complexity is O(n), not O(nlogn) Console.WriteLine($"\nAfter heap top element {val} pops from heap\n"); PrintUtil.PrintHeap(heap); } [Test] public void Test() { /* Initialize heap */ // Python's heapq module implements min heap by default PriorityQueue minHeap = new(); // Initialize max heap (modify Comparer using lambda expression) PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); Console.WriteLine("Following test cases are for max heap"); /* Element enters heap */ TestPush(maxHeap, 1); TestPush(maxHeap, 3); TestPush(maxHeap, 2); TestPush(maxHeap, 5); TestPush(maxHeap, 4); /* Check if heap is empty */ int peek = maxHeap.Peek(); Console.WriteLine($"Heap top element is {peek}"); /* Time complexity is O(n), not O(nlogn) */ // Dequeued elements form a descending sequence TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); /* Get heap size */ int size = maxHeap.Count; Console.WriteLine($"Heap size is {size}"); /* Check if heap is empty */ bool isEmpty = maxHeap.Count == 0; Console.WriteLine($"Is heap empty {isEmpty}"); /* Input list and build heap */ var list = new int[] { 1, 3, 2, 5, 4 }; minHeap = new PriorityQueue(list.Select(x => (x, x))); Console.WriteLine("After input list and building min heap"); PrintUtil.PrintHeap(minHeap); } } ================================================ FILE: en/codes/csharp/chapter_heap/my_heap.cs ================================================ /** * File: my_heap.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_heap; /* Max heap */ class MaxHeap { // Use list instead of array, no need to consider capacity expansion List maxHeap; /* Constructor, build empty heap */ public MaxHeap() { maxHeap = []; } /* Constructor, build heap from input list */ public MaxHeap(IEnumerable nums) { // Add list elements to heap as is maxHeap = new List(nums); // Heapify all nodes except leaf nodes var size = Parent(this.Size() - 1); for (int i = size; i >= 0; i--) { SiftDown(i); } } /* Get index of left child node */ int Left(int i) { return 2 * i + 1; } /* Get index of right child node */ int Right(int i) { return 2 * i + 2; } /* Get index of parent node */ int Parent(int i) { return (i - 1) / 2; // Floor division } /* Access top element */ public int Peek() { return maxHeap[0]; } /* Element enters heap */ public void Push(int val) { // Add node maxHeap.Add(val); // Heapify from bottom to top SiftUp(Size() - 1); } /* Get heap size */ public int Size() { return maxHeap.Count; } /* Check if heap is empty */ public bool IsEmpty() { return Size() == 0; } /* Starting from node i, heapify from bottom to top */ void SiftUp(int i) { while (true) { // Get parent node of node i int p = Parent(i); // If 'past root node' or 'node needs no repair', end heapify if (p < 0 || maxHeap[i] <= maxHeap[p]) break; // Swap two nodes Swap(i, p); // Loop upward heapify i = p; } } /* Element exits heap */ public int Pop() { // Handle empty case if (IsEmpty()) throw new IndexOutOfRangeException(); // Delete node Swap(0, Size() - 1); // Remove node int val = maxHeap.Last(); maxHeap.RemoveAt(Size() - 1); // Return top element SiftDown(0); // Return heap top element return val; } /* Starting from node i, heapify from top to bottom */ void SiftDown(int i) { while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break 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; // If 'node i is largest' or 'past leaf node', end heapify if (ma == i) break; // Swap two nodes Swap(i, ma); // Loop downwards heapification i = ma; } } /* Swap elements */ void Swap(int i, int p) { (maxHeap[i], maxHeap[p]) = (maxHeap[p], maxHeap[i]); } /* Driver Code */ public void Print() { var queue = new Queue(maxHeap); PrintUtil.PrintHeap(queue); } } public class my_heap { [Test] public void Test() { /* Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap */ MaxHeap maxHeap = new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); Console.WriteLine("\nAfter inputting list and building heap"); maxHeap.Print(); /* Check if heap is empty */ int peek = maxHeap.Peek(); Console.WriteLine($"Heap top element is {peek}"); /* Element enters heap */ int val = 7; maxHeap.Push(val); Console.WriteLine($"After element {val} pushes to heap"); maxHeap.Print(); /* Time complexity is O(n), not O(nlogn) */ peek = maxHeap.Pop(); Console.WriteLine($"After heap top element {peek} pops from heap"); maxHeap.Print(); /* Get heap size */ int size = maxHeap.Size(); Console.WriteLine($"Heap size is {size}"); /* Check if heap is empty */ bool isEmpty = maxHeap.IsEmpty(); Console.WriteLine($"Is heap empty {isEmpty}"); } } ================================================ FILE: en/codes/csharp/chapter_heap/top_k.cs ================================================ /** * File: top_k.cs * Created Time: 2023-06-14 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_heap; public class top_k { /* Find the largest k elements in array based on heap */ PriorityQueue TopKHeap(int[] nums, int k) { // Python's heapq module implements min heap by default PriorityQueue heap = new(); // Enter the first k elements of array into heap for (int i = 0; i < k; i++) { heap.Enqueue(nums[i], nums[i]); } // Starting from the (k+1)th element, maintain heap length as k for (int i = k; i < nums.Length; i++) { // If current element is greater than top element, top element exits heap, current element enters heap if (nums[i] > heap.Peek()) { heap.Dequeue(); heap.Enqueue(nums[i], nums[i]); } } return heap; } [Test] public void Test() { int[] nums = [1, 7, 6, 3, 2]; int k = 3; PriorityQueue res = TopKHeap(nums, k); Console.WriteLine("The largest " + k + " elements are"); PrintUtil.PrintHeap(res); } } ================================================ FILE: en/codes/csharp/chapter_searching/binary_search.cs ================================================ /** * File: binary_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class binary_search { /* Binary search (closed interval on both sides) */ int BinarySearch(int[] nums, int target) { // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array int i = 0, j = nums.Length - 1; // Loop, exit when the search interval is empty (empty when i > j) while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) // This means target is in the interval [m+1, j] i = m + 1; else if (nums[m] > target) // This means target is in the interval [i, m-1] j = m - 1; else // Found the target element, return its index return m; } // Target element not found, return -1 return -1; } /* Binary search (left-closed right-open interval) */ int BinarySearchLCRO(int[] nums, int target) { // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1 int i = 0, j = nums.Length; // Loop, exit when the search interval is empty (empty when i = j) while (i < j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) // This means target is in the interval [m+1, j) i = m + 1; else if (nums[m] > target) // This means target is in the interval [i, m) j = m; else // Found the target element, return its index return m; } // Target element not found, return -1 return -1; } [Test] public void Test() { int target = 6; int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* Binary search (closed interval on both sides) */ int index = BinarySearch(nums, target); Console.WriteLine("Index of target element 6 = " + index); /* Binary search (left-closed right-open interval) */ index = BinarySearchLCRO(nums, target); Console.WriteLine("Index of target element 6 = " + index); } } ================================================ FILE: en/codes/csharp/chapter_searching/binary_search_edge.cs ================================================ /** * File: binary_search_edge.cs * Created Time: 2023-08-06 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_searching; public class binary_search_edge { /* Binary search for the leftmost target */ int BinarySearchLeftEdge(int[] nums, int target) { // Equivalent to finding the insertion point of target int i = binary_search_insertion.BinarySearchInsertion(nums, target); // Target not found, return -1 if (i == nums.Length || nums[i] != target) { return -1; } // Found target, return index i return i; } /* Binary search for the rightmost target */ int BinarySearchRightEdge(int[] nums, int target) { // Convert to finding the leftmost target + 1 int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1); // j points to the rightmost target, i points to the first element greater than target int j = i - 1; // Target not found, return -1 if (j == -1 || nums[j] != target) { return -1; } // Found target, return index j return j; } [Test] public void Test() { // Array with duplicate elements int[] nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; Console.WriteLine("\nArray nums = " + nums.PrintList()); // Binary search left and right boundaries foreach (int target in new int[] { 6, 7 }) { int index = BinarySearchLeftEdge(nums, target); Console.WriteLine("Leftmost element " + target + " has index " + index); index = BinarySearchRightEdge(nums, target); Console.WriteLine("Rightmost element " + target + " has index " + index); } } } ================================================ FILE: en/codes/csharp/chapter_searching/binary_search_insertion.cs ================================================ /** * File: binary_search_insertion.cs * Created Time: 2023-08-06 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_searching; public class binary_search_insertion { /* Binary search for insertion point (no duplicate elements) */ public static int BinarySearchInsertionSimple(int[] nums, int target) { int i = 0, j = nums.Length - 1; // Initialize closed interval [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { return m; // Found target, return insertion point m } } // Target not found, return insertion point i return i; } /* Binary search for insertion point (with duplicate elements) */ public static int BinarySearchInsertion(int[] nums, int target) { int i = 0, j = nums.Length - 1; // Initialize closed interval [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { j = m - 1; // The first element less than target is in the interval [i, m-1] } } // Return insertion point i return i; } [Test] public void Test() { // Array without duplicate elements int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; Console.WriteLine("\nArray nums = " + nums.PrintList()); // Binary search for insertion point foreach (int target in new int[] { 6, 9 }) { int index = BinarySearchInsertionSimple(nums, target); Console.WriteLine("Element " + target + "'s insertion point index is " + index); } // Array with duplicate elements nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; Console.WriteLine("\nArray nums = " + nums.PrintList()); // Binary search for insertion point foreach (int target in new int[] { 2, 6, 20 }) { int index = BinarySearchInsertion(nums, target); Console.WriteLine("Element " + target + "'s insertion point index is " + index); } } } ================================================ FILE: en/codes/csharp/chapter_searching/hashing_search.cs ================================================ /** * File: hashing_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class hashing_search { /* Hash search (array) */ int HashingSearchArray(Dictionary map, int target) { // Hash table's key: target element, value: index // If this key does not exist in the hash table, return -1 return map.GetValueOrDefault(target, -1); } /* Hash search (linked list) */ ListNode? HashingSearchLinkedList(Dictionary map, int target) { // Hash table key: target node value, value: node object // If key is not in hash table, return null return map.GetValueOrDefault(target); } [Test] public void Test() { int target = 3; /* Hash search (array) */ int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // Initialize hash table Dictionary map = []; for (int i = 0; i < nums.Length; i++) { map[nums[i]] = i; // key: element, value: index } int index = HashingSearchArray(map, target); Console.WriteLine("Index of target element 3 = " + index); /* Hash search (linked list) */ ListNode? head = ListNode.ArrToLinkedList(nums); // Initialize hash table Dictionary map1 = []; while (head != null) { map1[head.val] = head; // key: node value, value: node head = head.next; } ListNode? node = HashingSearchLinkedList(map1, target); Console.WriteLine("Node object corresponding to target node value 3 is " + node); } } ================================================ FILE: en/codes/csharp/chapter_searching/linear_search.cs ================================================ /** * File: linear_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class linear_search { /* Linear search (array) */ int LinearSearchArray(int[] nums, int target) { // Traverse array for (int i = 0; i < nums.Length; i++) { // Found the target element, return its index if (nums[i] == target) return i; } // Target element not found, return -1 return -1; } /* Linear search (linked list) */ ListNode? LinearSearchLinkedList(ListNode? head, int target) { // Traverse the linked list while (head != null) { // Found the target node, return it if (head.val == target) return head; head = head.next; } // Target node not found, return null return null; } [Test] public void Test() { int target = 3; /* Perform linear search in array */ int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; int index = LinearSearchArray(nums, target); Console.WriteLine("Index of target element 3 = " + index); /* Perform linear search in linked list */ ListNode? head = ListNode.ArrToLinkedList(nums); ListNode? node = LinearSearchLinkedList(head, target); Console.WriteLine("Node object corresponding to target node value 3 is " + node); } } ================================================ FILE: en/codes/csharp/chapter_searching/two_sum.cs ================================================ /** * File: two_sum.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class two_sum { /* Method 1: Brute force enumeration */ int[] TwoSumBruteForce(int[] nums, int target) { int size = nums.Length; // Two nested loops, time complexity is 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 []; } /* Method 2: Auxiliary hash table */ int[] TwoSumHashTable(int[] nums, int target) { int size = nums.Length; // Auxiliary hash table, space complexity is O(n) Dictionary dic = []; // Single loop, time complexity is O(n) for (int i = 0; i < size; i++) { if (dic.ContainsKey(target - nums[i])) { return [dic[target - nums[i]], i]; } dic.Add(nums[i], i); } return []; } [Test] public void Test() { // ======= Test Case ======= int[] nums = [2, 7, 11, 15]; int target = 13; // ====== Driver Code ====== // Method 1 int[] res = TwoSumBruteForce(nums, target); Console.WriteLine("Method 1 res = " + string.Join(",", res)); // Method 2 res = TwoSumHashTable(nums, target); Console.WriteLine("Method 2 res = " + string.Join(",", res)); } } ================================================ FILE: en/codes/csharp/chapter_sorting/bubble_sort.cs ================================================ /** * File: bubble_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class bubble_sort { /* Bubble sort */ void BubbleSort(int[] nums) { // Outer loop: unsorted range is [0, i] for (int i = nums.Length - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); } } } } /* Bubble sort (flag optimization) */ void BubbleSortWithFlag(int[] nums) { // Outer loop: unsorted range is [0, i] for (int i = nums.Length - 1; i > 0; i--) { bool flag = false; // Initialize flag // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); flag = true; // Record element swap } } if (!flag) break; // No elements were swapped in this round of "bubbling", exit directly } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; BubbleSort(nums); Console.WriteLine("After bubble sort, nums = " + string.Join(",", nums)); int[] nums1 = [4, 1, 3, 1, 5, 2]; BubbleSortWithFlag(nums1); Console.WriteLine("After bubble sort completes, nums1 = " + string.Join(",", nums1)); } } ================================================ FILE: en/codes/csharp/chapter_sorting/bucket_sort.cs ================================================ /** * File: bucket_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class bucket_sort { /* Bucket sort */ void BucketSort(float[] nums) { // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket int k = nums.Length / 2; List> buckets = []; for (int i = 0; i < k; i++) { buckets.Add([]); } // 1. Distribute array elements into various buckets foreach (float num in nums) { // Input data range is [0, 1), use num * k to map to index range [0, k-1] int i = (int)(num * k); // Add num to bucket i buckets[i].Add(num); } // 2. Sort each bucket foreach (List bucket in buckets) { // Use built-in sorting function, can also replace with other sorting algorithms bucket.Sort(); } // 3. Traverse buckets to merge results int j = 0; foreach (List bucket in buckets) { foreach (float num in bucket) { nums[j++] = num; } } } [Test] public void Test() { // Assume input data is floating point, interval [0, 1) float[] nums = [0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f]; BucketSort(nums); Console.WriteLine("After bucket sort completes, nums = " + string.Join(" ", nums)); } } ================================================ FILE: en/codes/csharp/chapter_sorting/counting_sort.cs ================================================ /** * File: counting_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class counting_sort { /* Counting sort */ // Simple implementation, cannot be used for sorting objects void CountingSortNaive(int[] nums) { // 1. Count the maximum element m in the array int m = 0; foreach (int num in nums) { m = Math.Max(m, num); } // 2. Count the occurrence of each number // counter[num] represents the occurrence of num int[] counter = new int[m + 1]; foreach (int num in nums) { counter[num]++; } // 3. Traverse counter, filling each element back into the original array nums int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* Counting sort */ // Complete implementation, can sort objects and is a stable sort void CountingSort(int[] nums) { // 1. Count the maximum element m in the array int m = 0; foreach (int num in nums) { m = Math.Max(m, num); } // 2. Count the occurrence of each number // counter[num] represents the occurrence of num int[] counter = new int[m + 1]; foreach (int num in nums) { counter[num]++; } // 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" // counter[num]-1 is the last index where num appears in res for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. Traverse nums in reverse order, placing each element into the result array res // Initialize the array res to record results 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; // Place num at the corresponding index counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num } // Use result array res to overwrite the original array nums for (int i = 0; i < n; i++) { nums[i] = res[i]; } } [Test] public void Test() { int[] nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; CountingSortNaive(nums); Console.WriteLine("After counting sort (cannot sort objects) completes, nums = " + string.Join(" ", nums)); int[] nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; CountingSort(nums1); Console.WriteLine("After counting sort completes, nums1 = " + string.Join(" ", nums)); } } ================================================ FILE: en/codes/csharp/chapter_sorting/heap_sort.cs ================================================ /** * File: heap_sort.cs * Created Time: 2023-06-01 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class heap_sort { /* Heap length is n, start heapifying node i, from top to bottom */ void SiftDown(int[] nums, int n, int i) { while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break 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; // Swap two nodes if (ma == i) break; // Swap two nodes (nums[ma], nums[i]) = (nums[i], nums[ma]); // Loop downwards heapification i = ma; } } /* Heap sort */ void HeapSort(int[] nums) { // Build heap operation: heapify all nodes except leaves for (int i = nums.Length / 2 - 1; i >= 0; i--) { SiftDown(nums, nums.Length, i); } // Extract the largest element from the heap and repeat for n-1 rounds for (int i = nums.Length - 1; i > 0; i--) { // Delete node (nums[i], nums[0]) = (nums[0], nums[i]); // Start heapifying the root node, from top to bottom SiftDown(nums, i, 0); } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; HeapSort(nums); Console.WriteLine("After heap sort completes, nums = " + string.Join(" ", nums)); } } ================================================ FILE: en/codes/csharp/chapter_sorting/insertion_sort.cs ================================================ /** * File: insertion_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class insertion_sort { /* Insertion sort */ void InsertionSort(int[] nums) { // Outer loop: sorted interval is [0, i-1] for (int i = 1; i < nums.Length; i++) { int bas = nums[i], j = i - 1; // Inner loop: insert base into the correct position within the sorted interval [0, i-1] while (j >= 0 && nums[j] > bas) { nums[j + 1] = nums[j]; // Move nums[j] to the right by one position j--; } nums[j + 1] = bas; // Assign base to the correct position } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; InsertionSort(nums); Console.WriteLine("After insertion sort completes, nums = " + string.Join(",", nums)); } } ================================================ FILE: en/codes/csharp/chapter_sorting/merge_sort.cs ================================================ /** * File: merge_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class merge_sort { /* Merge left subarray and right subarray */ void Merge(int[] nums, int left, int mid, int right) { // Left subarray interval is [left, mid], right subarray interval is [mid+1, right] // Create a temporary array tmp to store the merged results int[] tmp = new int[right - left + 1]; // Initialize the start indices of the left and right subarrays int i = left, j = mid + 1, k = 0; // While both subarrays still have elements, compare and copy the smaller element into the temporary array while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } // Copy the remaining elements of the left and right subarrays into the temporary array while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval for (k = 0; k < tmp.Length; ++k) { nums[left + k] = tmp[k]; } } /* Merge sort */ void MergeSort(int[] nums, int left, int right) { // Termination condition if (left >= right) return; // Terminate recursion when subarray length is 1 // Divide and conquer stage int mid = left + (right - left) / 2; // Calculate midpoint MergeSort(nums, left, mid); // Recursively process the left subarray MergeSort(nums, mid + 1, right); // Recursively process the right subarray // Merge stage Merge(nums, left, mid, right); } [Test] public void Test() { /* Merge sort */ int[] nums = [7, 3, 2, 6, 0, 1, 5, 4]; MergeSort(nums, 0, nums.Length - 1); Console.WriteLine("After merge sort completes, nums = " + string.Join(",", nums)); } } ================================================ FILE: en/codes/csharp/chapter_sorting/quick_sort.cs ================================================ /** * File: quick_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; class quickSort { /* Swap elements */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* Sentinel partition */ static int Partition(int[] nums, int left, int right) { // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot Swap(nums, i, j); // Swap these two elements } Swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort */ public static void QuickSort(int[] nums, int left, int right) { // Terminate recursion when subarray length is 1 if (left >= right) return; // Sentinel partition int pivot = Partition(nums, left, right); // Recursively process the left subarray and right subarray QuickSort(nums, left, pivot - 1); QuickSort(nums, pivot + 1, right); } } /* Quick sort class (median pivot optimization) */ class QuickSortMedian { /* Swap elements */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* Select the median of three candidate elements */ 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 is between l and r if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l is between m and r return right; } /* Sentinel partition (median of three) */ static int Partition(int[] nums, int left, int right) { // Select the median of three candidate elements int med = MedianThree(nums, left, (left + right) / 2, right); // Swap the median to the array's leftmost position Swap(nums, left, med); // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot Swap(nums, i, j); // Swap these two elements } Swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort */ public static void QuickSort(int[] nums, int left, int right) { // Terminate recursion when subarray length is 1 if (left >= right) return; // Sentinel partition int pivot = Partition(nums, left, right); // Recursively process the left subarray and right subarray QuickSort(nums, left, pivot - 1); QuickSort(nums, pivot + 1, right); } } /* Quick sort class (recursion depth optimization) */ class QuickSortTailCall { /* Swap elements */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* Sentinel partition */ static int Partition(int[] nums, int left, int right) { // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot Swap(nums, i, j); // Swap these two elements } Swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort (recursion depth optimization) */ public static void QuickSort(int[] nums, int left, int right) { // Terminate when subarray length is 1 while (left < right) { // Sentinel partition operation int pivot = Partition(nums, left, right); // Perform quick sort on the shorter of the two subarrays if (pivot - left < right - pivot) { QuickSort(nums, left, pivot - 1); // Recursively sort the left subarray left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right] } else { QuickSort(nums, pivot + 1, right); // Recursively sort the right subarray right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1] } } } } public class quick_sort { [Test] public void Test() { /* Quick sort */ int[] nums = [2, 4, 1, 0, 3, 5]; quickSort.QuickSort(nums, 0, nums.Length - 1); Console.WriteLine("After quick sort completes, nums = " + string.Join(",", nums)); /* Quick sort (recursion depth optimization) */ int[] nums1 = [2, 4, 1, 0, 3, 5]; QuickSortMedian.QuickSort(nums1, 0, nums1.Length - 1); Console.WriteLine("After quick sort (median pivot optimization) completes, nums1 = " + string.Join(",", nums1)); /* Quick sort (recursion depth optimization) */ int[] nums2 = [2, 4, 1, 0, 3, 5]; QuickSortTailCall.QuickSort(nums2, 0, nums2.Length - 1); Console.WriteLine("After quick sort (recursion depth optimization) completes, nums2 = " + string.Join(",", nums2)); } } ================================================ FILE: en/codes/csharp/chapter_sorting/radix_sort.cs ================================================ /** * File: radix_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class radix_sort { /* Get the k-th digit of element num, where exp = 10^(k-1) */ int Digit(int num, int exp) { // Passing exp instead of k can avoid repeated expensive exponentiation here return (num / exp) % 10; } /* Counting sort (based on nums k-th digit) */ void CountingSortDigit(int[] nums, int exp) { // Decimal digit range is 0~9, therefore need a bucket array of length 10 int[] counter = new int[10]; int n = nums.Length; // Count the occurrence of digits 0~9 for (int i = 0; i < n; i++) { int d = Digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d counter[d]++; // Count the occurrence of digit d } // Calculate prefix sum, converting "occurrence count" into "array index" for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // Traverse in reverse, based on bucket statistics, place each element into 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; // Get the index j for d in the array res[j] = nums[i]; // Place the current element at index j counter[d]--; // Decrease the count of d by 1 } // Use result to overwrite the original array nums for (int i = 0; i < n; i++) { nums[i] = res[i]; } } /* Radix sort */ void RadixSort(int[] nums) { // Get the maximum element of the array, used to determine the maximum number of digits int m = int.MinValue; foreach (int num in nums) { if (num > m) m = num; } // Traverse from the lowest to the highest digit for (int exp = 1; exp <= m; exp *= 10) { // Perform counting sort on the k-th digit of array elements // k = 1 -> exp = 1 // k = 2 -> exp = 10 // i.e., exp = 10^(k-1) CountingSortDigit(nums, exp); } } [Test] public void Test() { // Radix sort int[] nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ]; RadixSort(nums); Console.WriteLine("After radix sort completes, nums = " + string.Join(" ", nums)); } } ================================================ FILE: en/codes/csharp/chapter_sorting/selection_sort.cs ================================================ /** * File: selection_sort.cs * Created Time: 2023-06-01 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class selection_sort { /* Selection sort */ void SelectionSort(int[] nums) { int n = nums.Length; // Outer loop: unsorted interval is [i, n-1] for (int i = 0; i < n - 1; i++) { // Inner loop: find the smallest element within the unsorted interval int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // Record the index of the smallest element } // Swap the smallest element with the first element of the unsorted interval (nums[k], nums[i]) = (nums[i], nums[k]); } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; SelectionSort(nums); Console.WriteLine("After selection sort completes, nums = " + string.Join(" ", nums)); } } ================================================ FILE: en/codes/csharp/chapter_stack_and_queue/array_deque.cs ================================================ /** * File: array_deque.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_stack_and_queue; /* Double-ended queue based on circular array implementation */ public class ArrayDeque { int[] nums; // Array for storing double-ended queue elements int front; // Front pointer, points to the front of the queue element int queSize; // Double-ended queue length /* Constructor */ public ArrayDeque(int capacity) { nums = new int[capacity]; front = queSize = 0; } /* Get the capacity of the double-ended queue */ int Capacity() { return nums.Length; } /* Get the length of the double-ended queue */ public int Size() { return queSize; } /* Check if the double-ended queue is empty */ public bool IsEmpty() { return queSize == 0; } /* Calculate circular array index */ int Index(int i) { // Use modulo operation to wrap the array head and tail together // When i passes the tail of the array, return to the head // When i passes the head of the array, return to the tail return (i + Capacity()) % Capacity(); } /* Front of the queue enqueue */ public void PushFirst(int num) { if (queSize == Capacity()) { Console.WriteLine("Double-ended queue is full"); return; } // Use modulo operation to wrap front around to the tail after passing the head of the array // Add num to the front of the queue front = Index(front - 1); // Add num to front of queue nums[front] = num; queSize++; } /* Rear of the queue enqueue */ public void PushLast(int num) { if (queSize == Capacity()) { Console.WriteLine("Double-ended queue is full"); return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array int rear = Index(front + queSize); // Front pointer moves one position backward nums[rear] = num; queSize++; } /* Rear of the queue dequeue */ public int PopFirst() { int num = PeekFirst(); // Move front pointer backward by one position front = Index(front + 1); queSize--; return num; } /* Access rear of the queue element */ public int PopLast() { int num = PeekLast(); queSize--; return num; } /* Return list for printing */ public int PeekFirst() { if (IsEmpty()) { throw new InvalidOperationException(); } return nums[front]; } /* Driver Code */ public int PeekLast() { if (IsEmpty()) { throw new InvalidOperationException(); } // Initialize double-ended queue int last = Index(front + queSize - 1); return nums[last]; } /* Return array for printing */ public int[] ToArray() { // Elements enqueue int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[Index(j)]; } return res; } } public class array_deque { [Test] public void Test() { /* Get the length of the double-ended queue */ ArrayDeque deque = new(10); deque.PushLast(3); deque.PushLast(2); deque.PushLast(5); Console.WriteLine("Double-ended queue deque = " + string.Join(" ", deque.ToArray())); /* Update element */ int peekFirst = deque.PeekFirst(); Console.WriteLine("Front element peekFirst = " + peekFirst); int peekLast = deque.PeekLast(); Console.WriteLine("Rear element peekLast = " + peekLast); /* Elements enqueue */ deque.PushLast(4); Console.WriteLine("After element 4 enqueues at rear, deque = " + string.Join(" ", deque.ToArray())); deque.PushFirst(1); Console.WriteLine("After element 1 enqueues at front, deque = " + string.Join(" ", deque.ToArray())); /* Element dequeue */ int popLast = deque.PopLast(); Console.WriteLine("Rear dequeue element = " + popLast + ", after rear dequeue, deque = " + string.Join(" ", deque.ToArray())); int popFirst = deque.PopFirst(); Console.WriteLine("Front dequeue element = " + popFirst + ", after front dequeue, deque = " + string.Join(" ", deque.ToArray())); /* Get the length of the double-ended queue */ int size = deque.Size(); Console.WriteLine("Double-ended queue length size = " + size); /* Check if the double-ended queue is empty */ bool isEmpty = deque.IsEmpty(); Console.WriteLine("Double-ended queue is empty = " + isEmpty); } } ================================================ FILE: en/codes/csharp/chapter_stack_and_queue/array_queue.cs ================================================ /** * File: array_queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* Queue based on circular array implementation */ class ArrayQueue { int[] nums; // Array for storing queue elements int front; // Front pointer, points to the front of the queue element int queSize; // Queue length public ArrayQueue(int capacity) { nums = new int[capacity]; front = queSize = 0; } /* Get the capacity of the queue */ int Capacity() { return nums.Length; } /* Get the length of the queue */ public int Size() { return queSize; } /* Check if the queue is empty */ public bool IsEmpty() { return queSize == 0; } /* Enqueue */ public void Push(int num) { if (queSize == Capacity()) { Console.WriteLine("Queue is full"); return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array // Add num to the rear of the queue int rear = (front + queSize) % Capacity(); // Front pointer moves one position backward nums[rear] = num; queSize++; } /* Dequeue */ public int Pop() { int num = Peek(); // Move front pointer backward by one position, if it passes the tail, return to array head front = (front + 1) % Capacity(); queSize--; return num; } /* Return list for printing */ public int Peek() { if (IsEmpty()) throw new Exception(); return nums[front]; } /* Return array */ public int[] ToArray() { // Elements enqueue int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[j % this.Capacity()]; } return res; } } public class array_queue { [Test] public void Test() { /* Access front of the queue element */ int capacity = 10; ArrayQueue queue = new(capacity); /* Elements enqueue */ queue.Push(1); queue.Push(3); queue.Push(2); queue.Push(5); queue.Push(4); Console.WriteLine("Queue queue = " + string.Join(",", queue.ToArray())); /* Return list for printing */ int peek = queue.Peek(); Console.WriteLine("Front element peek = " + peek); /* Element dequeue */ int pop = queue.Pop(); Console.WriteLine("Dequeue element pop = " + pop + ", after dequeue, queue = " + string.Join(",", queue.ToArray())); /* Get the length of the queue */ int size = queue.Size(); Console.WriteLine("Queue length size = " + size); /* Check if the queue is empty */ bool isEmpty = queue.IsEmpty(); Console.WriteLine("Queue is empty = " + isEmpty); /* Test circular array */ for (int i = 0; i < 10; i++) { queue.Push(i); queue.Pop(); Console.WriteLine("Round " + i + " enqueue + dequeue, queue = " + string.Join(",", queue.ToArray())); } } } ================================================ FILE: en/codes/csharp/chapter_stack_and_queue/array_stack.cs ================================================ /** * File: array_stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* Stack based on array implementation */ class ArrayStack { List stack; public ArrayStack() { // Initialize list (dynamic array) stack = []; } /* Get the length of the stack */ public int Size() { return stack.Count; } /* Check if the stack is empty */ public bool IsEmpty() { return Size() == 0; } /* Push */ public void Push(int num) { stack.Add(num); } /* Pop */ public int Pop() { if (IsEmpty()) throw new Exception(); var val = Peek(); stack.RemoveAt(Size() - 1); return val; } /* Return list for printing */ public int Peek() { if (IsEmpty()) throw new Exception(); return stack[Size() - 1]; } /* Convert List to Array and return */ public int[] ToArray() { return [.. stack]; } } public class array_stack { [Test] public void Test() { /* Access top of the stack element */ ArrayStack stack = new(); /* Elements push onto stack */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); Console.WriteLine("Stack stack = " + string.Join(",", stack.ToArray())); /* Return list for printing */ int peek = stack.Peek(); Console.WriteLine("Stack top element peek = " + peek); /* Element pop from stack */ int pop = stack.Pop(); Console.WriteLine("Pop element pop = " + pop + ", after pop, stack = " + string.Join(",", stack.ToArray())); /* Get the length of the stack */ int size = stack.Size(); Console.WriteLine("Stack length size = " + size); /* Check if empty */ bool isEmpty = stack.IsEmpty(); Console.WriteLine("Stack is empty = " + isEmpty); } } ================================================ FILE: en/codes/csharp/chapter_stack_and_queue/deque.cs ================================================ /** * File: deque.cs * Created Time: 2022-12-30 * Author: moonache (microin1301@outlook.com) */ namespace hello_algo.chapter_stack_and_queue; public class deque { [Test] public void Test() { /* Get the length of the double-ended queue */ // In C#, use LinkedList as deque LinkedList deque = new(); /* Elements enqueue */ deque.AddLast(2); // Add to the rear of the queue deque.AddLast(5); deque.AddLast(4); deque.AddFirst(3); // Add to the front of the queue deque.AddFirst(1); Console.WriteLine("Double-ended queue deque = " + string.Join(",", deque)); /* Update element */ int? peekFirst = deque.First?.Value; // Rear of the queue element Console.WriteLine("Front element peekFirst = " + peekFirst); int? peekLast = deque.Last?.Value; // Front of the queue element dequeues Console.WriteLine("Rear element peekLast = " + peekLast); /* Element dequeue */ deque.RemoveFirst(); // Check if the double-ended queue is empty Console.WriteLine("After front element dequeues, deque = " + string.Join(",", deque)); deque.RemoveLast(); // Rear element dequeue Console.WriteLine("After rear element dequeues, deque = " + string.Join(",", deque)); /* Get the length of the double-ended queue */ int size = deque.Count; Console.WriteLine("Double-ended queue length size = " + size); /* Check if the double-ended queue is empty */ bool isEmpty = deque.Count == 0; Console.WriteLine("Double-ended queue is empty = " + isEmpty); } } ================================================ FILE: en/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs ================================================ /** * File: linkedlist_deque.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_stack_and_queue; /* Doubly linked list node */ public class ListNode(int val) { public int val = val; // Node value public ListNode? next = null; // Successor node reference public ListNode? prev = null; // Predecessor node reference } /* Double-ended queue based on doubly linked list implementation */ public class LinkedListDeque { ListNode? front, rear; // Head node front, tail node rear int queSize = 0; // Length of the double-ended queue public LinkedListDeque() { front = null; rear = null; } /* Get the length of the double-ended queue */ public int Size() { return queSize; } /* Check if the double-ended queue is empty */ public bool IsEmpty() { return Size() == 0; } /* Enqueue operation */ void Push(int num, bool isFront) { ListNode node = new(num); // If the linked list is empty, make both front and rear point to node if (IsEmpty()) { front = node; rear = node; } // Front of the queue enqueue operation else if (isFront) { // Add node to the head of the linked list front!.prev = node; node.next = front; front = node; // Update head node } // Rear of the queue enqueue operation else { // Add node to the tail of the linked list rear!.next = node; node.prev = rear; rear = node; // Update tail node } queSize++; // Update queue length } /* Front of the queue enqueue */ public void PushFirst(int num) { Push(num, true); } /* Rear of the queue enqueue */ public void PushLast(int num) { Push(num, false); } /* Dequeue operation */ int? Pop(bool isFront) { if (IsEmpty()) throw new Exception(); int? val; // Temporarily store head node value if (isFront) { val = front?.val; // Delete head node // Delete head node ListNode? fNext = front?.next; if (fNext != null) { fNext.prev = null; front!.next = null; } front = fNext; // Update head node } // Temporarily store tail node value else { val = rear?.val; // Delete tail node // Update tail node ListNode? rPrev = rear?.prev; if (rPrev != null) { rPrev.next = null; rear!.prev = null; } rear = rPrev; // Update tail node } queSize--; // Update queue length return val; } /* Rear of the queue dequeue */ public int? PopFirst() { return Pop(true); } /* Access rear of the queue element */ public int? PopLast() { return Pop(false); } /* Return list for printing */ public int? PeekFirst() { if (IsEmpty()) throw new Exception(); return front?.val; } /* Driver Code */ public int? PeekLast() { if (IsEmpty()) throw new Exception(); return rear?.val; } /* Return array for printing */ public int?[] ToArray() { ListNode? node = front; int?[] res = new int?[Size()]; for (int i = 0; i < res.Length; i++) { res[i] = node?.val; node = node?.next; } return res; } } public class linkedlist_deque { [Test] public void Test() { /* Get the length of the double-ended queue */ LinkedListDeque deque = new(); deque.PushLast(3); deque.PushLast(2); deque.PushLast(5); Console.WriteLine("Double-ended queue deque = " + string.Join(" ", deque.ToArray())); /* Update element */ int? peekFirst = deque.PeekFirst(); Console.WriteLine("Front element peekFirst = " + peekFirst); int? peekLast = deque.PeekLast(); Console.WriteLine("Rear element peekLast = " + peekLast); /* Elements enqueue */ deque.PushLast(4); Console.WriteLine("After element 4 enqueues at rear, deque = " + string.Join(" ", deque.ToArray())); deque.PushFirst(1); Console.WriteLine("After element 1 enqueues at front, deque = " + string.Join(" ", deque.ToArray())); /* Element dequeue */ int? popLast = deque.PopLast(); Console.WriteLine("Rear dequeue element = " + popLast + ", after rear dequeue, deque = " + string.Join(" ", deque.ToArray())); int? popFirst = deque.PopFirst(); Console.WriteLine("Front dequeue element = " + popFirst + ", after front dequeue, deque = " + string.Join(" ", deque.ToArray())); /* Get the length of the double-ended queue */ int size = deque.Size(); Console.WriteLine("Double-ended queue length size = " + size); /* Check if the double-ended queue is empty */ bool isEmpty = deque.IsEmpty(); Console.WriteLine("Double-ended queue is empty = " + isEmpty); } } ================================================ FILE: en/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs ================================================ /** * File: linkedlist_queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* Queue based on linked list implementation */ class LinkedListQueue { ListNode? front, rear; // Head node front, tail node rear int queSize = 0; public LinkedListQueue() { front = null; rear = null; } /* Get the length of the queue */ public int Size() { return queSize; } /* Check if the queue is empty */ public bool IsEmpty() { return Size() == 0; } /* Enqueue */ public void Push(int num) { // Add num after the tail node ListNode node = new(num); // If the queue is empty, make both front and rear point to the node if (front == null) { front = node; rear = node; // If the queue is not empty, add the node after the tail node } else if (rear != null) { rear.next = node; rear = node; } queSize++; } /* Dequeue */ public int Pop() { int num = Peek(); // Delete head node front = front?.next; queSize--; return num; } /* Return list for printing */ public int Peek() { if (IsEmpty()) throw new Exception(); return front!.val; } /* Convert linked list to Array and return */ public int[] ToArray() { if (front == null) return []; ListNode? node = front; int[] res = new int[Size()]; for (int i = 0; i < res.Length; i++) { res[i] = node!.val; node = node.next; } return res; } } public class linkedlist_queue { [Test] public void Test() { /* Access front of the queue element */ LinkedListQueue queue = new(); /* Elements enqueue */ queue.Push(1); queue.Push(3); queue.Push(2); queue.Push(5); queue.Push(4); Console.WriteLine("Queue queue = " + string.Join(",", queue.ToArray())); /* Return list for printing */ int peek = queue.Peek(); Console.WriteLine("Front element peek = " + peek); /* Element dequeue */ int pop = queue.Pop(); Console.WriteLine("Dequeue element pop = " + pop + ", after dequeue, queue = " + string.Join(",", queue.ToArray())); /* Get the length of the queue */ int size = queue.Size(); Console.WriteLine("Queue length size = " + size); /* Check if the queue is empty */ bool isEmpty = queue.IsEmpty(); Console.WriteLine("Queue is empty = " + isEmpty); } } ================================================ FILE: en/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs ================================================ /** * File: linkedlist_stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* Stack based on linked list implementation */ class LinkedListStack { ListNode? stackPeek; // Use head node as stack top int stkSize = 0; // Stack length public LinkedListStack() { stackPeek = null; } /* Get the length of the stack */ public int Size() { return stkSize; } /* Check if the stack is empty */ public bool IsEmpty() { return Size() == 0; } /* Push */ public void Push(int num) { ListNode node = new(num) { next = stackPeek }; stackPeek = node; stkSize++; } /* Pop */ public int Pop() { int num = Peek(); stackPeek = stackPeek!.next; stkSize--; return num; } /* Return list for printing */ public int Peek() { if (IsEmpty()) throw new Exception(); return stackPeek!.val; } /* Convert List to Array and return */ public int[] ToArray() { if (stackPeek == null) return []; ListNode? node = stackPeek; int[] res = new int[Size()]; for (int i = res.Length - 1; i >= 0; i--) { res[i] = node!.val; node = node.next; } return res; } } public class linkedlist_stack { [Test] public void Test() { /* Access top of the stack element */ LinkedListStack stack = new(); /* Elements push onto stack */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); Console.WriteLine("Stack stack = " + string.Join(",", stack.ToArray())); /* Return list for printing */ int peek = stack.Peek(); Console.WriteLine("Stack top element peek = " + peek); /* Element pop from stack */ int pop = stack.Pop(); Console.WriteLine("Pop element pop = " + pop + ", after pop, stack = " + string.Join(",", stack.ToArray())); /* Get the length of the stack */ int size = stack.Size(); Console.WriteLine("Stack length size = " + size); /* Check if empty */ bool isEmpty = stack.IsEmpty(); Console.WriteLine("Stack is empty = " + isEmpty); } } ================================================ FILE: en/codes/csharp/chapter_stack_and_queue/queue.cs ================================================ /** * File: queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; public class queue { [Test] public void Test() { /* Access front of the queue element */ Queue queue = new(); /* Elements enqueue */ queue.Enqueue(1); queue.Enqueue(3); queue.Enqueue(2); queue.Enqueue(5); queue.Enqueue(4); Console.WriteLine("Queue queue = " + string.Join(",", queue)); /* Return list for printing */ int peek = queue.Peek(); Console.WriteLine("Front element peek = " + peek); /* Element dequeue */ int pop = queue.Dequeue(); Console.WriteLine("Dequeue element pop = " + pop + ", after dequeue, queue = " + string.Join(",", queue)); /* Get the length of the queue */ int size = queue.Count; Console.WriteLine("Queue length size = " + size); /* Check if the queue is empty */ bool isEmpty = queue.Count == 0; Console.WriteLine("Queue is empty = " + isEmpty); } } ================================================ FILE: en/codes/csharp/chapter_stack_and_queue/stack.cs ================================================ /** * File: stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; public class stack { [Test] public void Test() { /* Access top of the stack element */ Stack stack = new(); /* Elements push onto stack */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); // Note: stack.ToArray() returns reversed sequence, index 0 is stack top Console.WriteLine("Stack stack = " + string.Join(",", stack)); /* Return list for printing */ int peek = stack.Peek(); Console.WriteLine("Stack top element peek = " + peek); /* Element pop from stack */ int pop = stack.Pop(); Console.WriteLine("Pop element pop = " + pop + ", after pop, stack = " + string.Join(",", stack)); /* Get the length of the stack */ int size = stack.Count; Console.WriteLine("Stack length size = " + size); /* Check if empty */ bool isEmpty = stack.Count == 0; Console.WriteLine("Stack is empty = " + isEmpty); } } ================================================ FILE: en/codes/csharp/chapter_tree/array_binary_tree.cs ================================================ /** * File: array_binary_tree.cs * Created Time: 2023-07-20 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_tree; /* Binary tree class represented by array */ public class ArrayBinaryTree(List arr) { List tree = new(arr); /* List capacity */ public int Size() { return tree.Count; } /* Get value of node at index i */ public int? Val(int i) { // If index out of bounds, return null to represent empty position if (i < 0 || i >= Size()) return null; return tree[i]; } /* Get index of left child node of node at index i */ public int Left(int i) { return 2 * i + 1; } /* Get index of right child node of node at index i */ public int Right(int i) { return 2 * i + 2; } /* Get index of parent node of node at index i */ public int Parent(int i) { return (i - 1) / 2; } /* Level-order traversal */ public List LevelOrder() { List res = []; // Traverse array directly for (int i = 0; i < Size(); i++) { if (Val(i).HasValue) res.Add(Val(i)!.Value); } return res; } /* Depth-first traversal */ void DFS(int i, string order, List res) { // If empty position, return if (!Val(i).HasValue) return; // Preorder traversal if (order == "pre") res.Add(Val(i)!.Value); DFS(Left(i), order, res); // Inorder traversal if (order == "in") res.Add(Val(i)!.Value); DFS(Right(i), order, res); // Postorder traversal if (order == "post") res.Add(Val(i)!.Value); } /* Preorder traversal */ public List PreOrder() { List res = []; DFS(0, "pre", res); return res; } /* Inorder traversal */ public List InOrder() { List res = []; DFS(0, "in", res); return res; } /* Postorder traversal */ public List PostOrder() { List res = []; DFS(0, "post", res); return res; } } public class array_binary_tree { [Test] public void Test() { // Initialize binary tree // Here we use a function to generate a binary tree directly from an array List arr = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; TreeNode? root = TreeNode.ListToTree(arr); Console.WriteLine("\nInitialize binary tree\n"); Console.WriteLine("Array representation of binary tree:"); Console.WriteLine(arr.PrintList()); Console.WriteLine("Linked list representation of binary tree:"); PrintUtil.PrintTree(root); // Binary tree class represented by array ArrayBinaryTree abt = new(arr); // Access node int i = 1; int l = abt.Left(i); int r = abt.Right(i); int p = abt.Parent(i); Console.WriteLine("\nCurrent node index is " + i + ", value is " + abt.Val(i)); Console.WriteLine("Its left child node index is " + l + ", value is " + (abt.Val(l).HasValue ? abt.Val(l) : "null")); Console.WriteLine("Its right child node index is " + r + ", value is " + (abt.Val(r).HasValue ? abt.Val(r) : "null")); Console.WriteLine("Its parent node index is " + p + ", value is " + (abt.Val(p).HasValue ? abt.Val(p) : "null")); // Traverse tree List res = abt.LevelOrder(); Console.WriteLine("\nLevel-order traversal is:" + res.PrintList()); res = abt.PreOrder(); Console.WriteLine("Preorder traversal is:" + res.PrintList()); res = abt.InOrder(); Console.WriteLine("Inorder traversal is:" + res.PrintList()); res = abt.PostOrder(); Console.WriteLine("Postorder traversal is:" + res.PrintList()); } } ================================================ FILE: en/codes/csharp/chapter_tree/avl_tree.cs ================================================ /** * File: avl_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; /* AVL tree */ class AVLTree { public TreeNode? root; // Root node /* Get node height */ int Height(TreeNode? node) { // Empty node height is -1, leaf node height is 0 return node == null ? -1 : node.height; } /* Update node height */ void UpdateHeight(TreeNode node) { // Node height equals the height of the tallest subtree + 1 node.height = Math.Max(Height(node.left), Height(node.right)) + 1; } /* Get balance factor */ public int BalanceFactor(TreeNode? node) { // Empty node balance factor is 0 if (node == null) return 0; // Node balance factor = left subtree height - right subtree height return Height(node.left) - Height(node.right); } /* Right rotation operation */ TreeNode? RightRotate(TreeNode? node) { TreeNode? child = node?.left; TreeNode? grandChild = child?.right; // Using child as pivot, rotate node to the right child.right = node; node.left = grandChild; // Update node height UpdateHeight(node); UpdateHeight(child); // Return root node of subtree after rotation return child; } /* Left rotation operation */ TreeNode? LeftRotate(TreeNode? node) { TreeNode? child = node?.right; TreeNode? grandChild = child?.left; // Using child as pivot, rotate node to the left child.left = node; node.right = grandChild; // Update node height UpdateHeight(node); UpdateHeight(child); // Return root node of subtree after rotation return child; } /* Perform rotation operation to restore balance to this subtree */ TreeNode? Rotate(TreeNode? node) { // Get balance factor of node int balanceFactorInt = BalanceFactor(node); // Left-leaning tree if (balanceFactorInt > 1) { if (BalanceFactor(node?.left) >= 0) { // Right rotation return RightRotate(node); } else { // First left rotation then right rotation node!.left = LeftRotate(node!.left); return RightRotate(node); } } // Right-leaning tree if (balanceFactorInt < -1) { if (BalanceFactor(node?.right) <= 0) { // Left rotation return LeftRotate(node); } else { // First right rotation then left rotation node!.right = RightRotate(node!.right); return LeftRotate(node); } } // Balanced tree, no rotation needed, return directly return node; } /* Insert node */ public void Insert(int val) { root = InsertHelper(root, val); } /* Recursively insert node (helper method) */ TreeNode? InsertHelper(TreeNode? node, int val) { if (node == null) return new TreeNode(val); /* 1. Find insertion position and insert node */ if (val < node.val) node.left = InsertHelper(node.left, val); else if (val > node.val) node.right = InsertHelper(node.right, val); else return node; // Duplicate node not inserted, return directly UpdateHeight(node); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = Rotate(node); // Return root node of subtree return node; } /* Remove node */ public void Remove(int val) { root = RemoveHelper(root, val); } /* Recursively delete node (helper method) */ TreeNode? RemoveHelper(TreeNode? node, int val) { if (node == null) return null; /* 1. Find node and delete */ if (val < node.val) node.left = RemoveHelper(node.left, val); else if (val > node.val) node.right = RemoveHelper(node.right, val); else { if (node.left == null || node.right == null) { TreeNode? child = node.left ?? node.right; // Number of child nodes = 0, delete node directly and return if (child == null) return null; // Number of child nodes = 1, delete node directly else node = child; } else { // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it TreeNode? temp = node.right; while (temp.left != null) { temp = temp.left; } node.right = RemoveHelper(node.right, temp.val!.Value); node.val = temp.val; } } UpdateHeight(node); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = Rotate(node); // Return root node of subtree return node; } /* Search node */ public TreeNode? Search(int val) { TreeNode? cur = root; // Loop search, exit after passing leaf node while (cur != null) { // Target node is in cur's right subtree if (cur.val < val) cur = cur.right; // Target node is in cur's left subtree else if (cur.val > val) cur = cur.left; // Found target node, exit loop else break; } // Return target node return cur; } } public class avl_tree { static void TestInsert(AVLTree tree, int val) { tree.Insert(val); Console.WriteLine("\nInsert node " + val + ", AVL tree is"); PrintUtil.PrintTree(tree.root); } static void TestRemove(AVLTree tree, int val) { tree.Remove(val); Console.WriteLine("\nRemove node " + val + ", AVL tree is"); PrintUtil.PrintTree(tree.root); } [Test] public void Test() { /* Please pay attention to how the AVL tree maintains balance after inserting nodes */ AVLTree avlTree = new(); /* Insert node */ // Delete nodes 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); /* Please pay attention to how the AVL tree maintains balance after deleting nodes */ TestInsert(avlTree, 7); /* Remove node */ // Delete node with degree 1 TestRemove(avlTree, 8); // Delete node with degree 2 TestRemove(avlTree, 5); // Remove node with degree 1 TestRemove(avlTree, 4); // Remove node with degree 2 /* Search node */ TreeNode? node = avlTree.Search(7); Console.WriteLine("\nFound node object is " + node + ", node value = " + node?.val); } } ================================================ FILE: en/codes/csharp/chapter_tree/binary_search_tree.cs ================================================ /** * File: binary_search_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; class BinarySearchTree { TreeNode? root; public BinarySearchTree() { // Initialize empty tree root = null; } /* Get binary tree root node */ public TreeNode? GetRoot() { return root; } /* Search node */ public TreeNode? Search(int num) { TreeNode? cur = root; // Loop search, exit after passing leaf node while (cur != null) { // Target node is in cur's right subtree if (cur.val < num) cur = cur.right; // Target node is in cur's left subtree else if (cur.val > num) cur = cur.left; // Found target node, exit loop else break; } // Return target node return cur; } /* Insert node */ public void Insert(int num) { // If tree is empty, initialize root node if (root == null) { root = new TreeNode(num); return; } TreeNode? cur = root, pre = null; // Loop search, exit after passing leaf node while (cur != null) { // Found duplicate node, return directly if (cur.val == num) return; pre = cur; // Insertion position is in cur's right subtree if (cur.val < num) cur = cur.right; // Insertion position is in cur's left subtree else cur = cur.left; } // Insert node TreeNode node = new(num); if (pre != null) { if (pre.val < num) pre.right = node; else pre.left = node; } } /* Remove node */ public void Remove(int num) { // If tree is empty, return directly if (root == null) return; TreeNode? cur = root, pre = null; // Loop search, exit after passing leaf node while (cur != null) { // Found node to delete, exit loop if (cur.val == num) break; pre = cur; // Node to delete is in cur's right subtree if (cur.val < num) cur = cur.right; // Node to delete is in cur's left subtree else cur = cur.left; } // If no node to delete, return directly if (cur == null) return; // Number of child nodes = 0 or 1 if (cur.left == null || cur.right == null) { // When number of child nodes = 0 / 1, child = null / that child node TreeNode? child = cur.left ?? cur.right; // Delete node cur if (cur != root) { if (pre!.left == cur) pre.left = child; else pre.right = child; } else { // If deleted node is root node, reassign root node root = child; } } // Number of child nodes = 2 else { // Get next node of cur in inorder traversal TreeNode? tmp = cur.right; while (tmp.left != null) { tmp = tmp.left; } // Recursively delete node tmp Remove(tmp.val!.Value); // Replace cur with tmp cur.val = tmp.val; } } } public class binary_search_tree { [Test] public void Test() { /* Initialize binary search tree */ BinarySearchTree bst = new(); // Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree int[] nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; foreach (int num in nums) { bst.Insert(num); } Console.WriteLine("\nInitialized binary tree is\n"); PrintUtil.PrintTree(bst.GetRoot()); /* Search node */ TreeNode? node = bst.Search(7); Console.WriteLine("\nFound node object is " + node + ", node value = " + node?.val); /* Insert node */ bst.Insert(16); Console.WriteLine("\nAfter inserting node 16, binary tree is\n"); PrintUtil.PrintTree(bst.GetRoot()); /* Remove node */ bst.Remove(1); Console.WriteLine("\nAfter removing node 1, binary tree is\n"); PrintUtil.PrintTree(bst.GetRoot()); bst.Remove(2); Console.WriteLine("\nAfter removing node 2, binary tree is\n"); PrintUtil.PrintTree(bst.GetRoot()); bst.Remove(4); Console.WriteLine("\nAfter removing node 4, binary tree is\n"); PrintUtil.PrintTree(bst.GetRoot()); } } ================================================ FILE: en/codes/csharp/chapter_tree/binary_tree.cs ================================================ /** * File: binary_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree { [Test] public void Test() { /* Initialize binary tree */ // Initialize nodes TreeNode n1 = new(1); TreeNode n2 = new(2); TreeNode n3 = new(3); TreeNode n4 = new(4); TreeNode n5 = new(5); // Build references (pointers) between nodes n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; Console.WriteLine("\nInitialize binary tree\n"); PrintUtil.PrintTree(n1); /* Insert node P between n1 -> n2 */ TreeNode P = new(0); // Delete node n1.left = P; P.left = n2; Console.WriteLine("\nAfter inserting node P\n"); PrintUtil.PrintTree(n1); // Remove node P n1.left = n2; Console.WriteLine("\nAfter removing node P\n"); PrintUtil.PrintTree(n1); } } ================================================ FILE: en/codes/csharp/chapter_tree/binary_tree_bfs.cs ================================================ /** * File: binary_tree_bfs.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree_bfs { /* Level-order traversal */ List LevelOrder(TreeNode root) { // Initialize queue, add root node Queue queue = new(); queue.Enqueue(root); // Initialize a list to save the traversal sequence List list = []; while (queue.Count != 0) { TreeNode node = queue.Dequeue(); // Dequeue list.Add(node.val!.Value); // Save node value if (node.left != null) queue.Enqueue(node.left); // Left child node enqueue if (node.right != null) queue.Enqueue(node.right); // Right child node enqueue } return list; } [Test] public void Test() { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); Console.WriteLine("\nInitialize binary tree\n"); PrintUtil.PrintTree(root); List list = LevelOrder(root!); Console.WriteLine("\nLevel-order traversal node print sequence = " + string.Join(",", list)); } } ================================================ FILE: en/codes/csharp/chapter_tree/binary_tree_dfs.cs ================================================ /** * File: binary_tree_dfs.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree_dfs { List list = []; /* Preorder traversal */ void PreOrder(TreeNode? root) { if (root == null) return; // Visit priority: root node -> left subtree -> right subtree list.Add(root.val!.Value); PreOrder(root.left); PreOrder(root.right); } /* Inorder traversal */ void InOrder(TreeNode? root) { if (root == null) return; // Visit priority: left subtree -> root node -> right subtree InOrder(root.left); list.Add(root.val!.Value); InOrder(root.right); } /* Postorder traversal */ void PostOrder(TreeNode? root) { if (root == null) return; // Visit priority: left subtree -> right subtree -> root node PostOrder(root.left); PostOrder(root.right); list.Add(root.val!.Value); } [Test] public void Test() { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); Console.WriteLine("\nInitialize binary tree\n"); PrintUtil.PrintTree(root); list.Clear(); PreOrder(root); Console.WriteLine("\nPreorder traversal node print sequence = " + string.Join(",", list)); list.Clear(); InOrder(root); Console.WriteLine("\nInorder traversal node print sequence = " + string.Join(",", list)); list.Clear(); PostOrder(root); Console.WriteLine("\nPostorder traversal node print sequence = " + string.Join(",", list)); } } ================================================ FILE: en/codes/csharp/csharp.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.002.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hello-algo", "hello-algo.csproj", "{48B60439-EFDC-4C8F-AE8D-41979958C8AC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.Build.0 = Debug|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1E773F8A-FF66-4974-820B-FCE9032D19AE} EndGlobalSection EndGlobal ================================================ FILE: en/codes/csharp/hello-algo.csproj ================================================  Exe net8.0 hello_algo enable enable all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: en/codes/csharp/utils/ListNode.cs ================================================ // File: ListNode.cs // Created Time: 2022-12-16 // Author: mingXta (1195669834@qq.com) namespace hello_algo.utils; /* Linked list node */ public class ListNode(int x) { public int val = x; public ListNode? next; /* Deserialize array to linked list */ public static ListNode? ArrToLinkedList(int[] arr) { ListNode dum = new(0); ListNode head = dum; foreach (int val in arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } public override string? ToString() { List list = []; var head = this; while (head != null) { list.Add(head.val.ToString()); head = head.next; } return string.Join("->", list); } } ================================================ FILE: en/codes/csharp/utils/PrintUtil.cs ================================================ /** * File: PrintUtil.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com), krahets (krahets@163.com) */ namespace hello_algo.utils; public class Trunk(Trunk? prev, string str) { public Trunk? prev = prev; public string str = str; }; public static class PrintUtil { /* Print list */ public static void PrintList(IList list) { Console.WriteLine("[" + string.Join(", ", list) + "]"); } public static string PrintList(this IEnumerable list) { return $"[ {string.Join(", ", list.Select(x => x?.ToString() ?? "null"))} ]"; } /* Print matrix (Array) */ public static void PrintMatrix(T[][] matrix) { Console.WriteLine("["); foreach (T[] row in matrix) { Console.WriteLine(" " + string.Join(", ", row) + ","); } Console.WriteLine("]"); } /* Print matrix (List) */ public static void PrintMatrix(List> matrix) { Console.WriteLine("["); foreach (List row in matrix) { Console.WriteLine(" " + string.Join(", ", row) + ","); } Console.WriteLine("]"); } /* Print linked list */ public static void PrintLinkedList(ListNode? head) { List list = []; while (head != null) { list.Add(head.val.ToString()); head = head.next; } Console.Write(string.Join(" -> ", list)); } /** * Print binary tree * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ public static void PrintTree(TreeNode? root) { PrintTree(root, null, false); } /* Print binary tree */ public static void PrintTree(TreeNode? root, Trunk? prev, bool isRight) { if (root == null) { return; } string prev_str = " "; Trunk trunk = new(prev, prev_str); PrintTree(root.right, trunk, true); if (prev == null) { trunk.str = "———"; } else if (isRight) { trunk.str = "/———"; prev_str = " |"; } else { trunk.str = "\\———"; prev.str = prev_str; } ShowTrunks(trunk); Console.WriteLine(" " + root.val); if (prev != null) { prev.str = prev_str; } trunk.str = " |"; PrintTree(root.left, trunk, false); } public static void ShowTrunks(Trunk? p) { if (p == null) { return; } ShowTrunks(p.prev); Console.Write(p.str); } /* Print hash table */ public static void PrintHashMap(Dictionary map) where K : notnull { foreach (var kv in map.Keys) { Console.WriteLine(kv.ToString() + " -> " + map[kv]?.ToString()); } } /* Print heap */ public static void PrintHeap(Queue queue) { Console.Write("Heap array representation:"); List list = [.. queue]; Console.WriteLine(string.Join(',', list)); Console.WriteLine("Heap tree representation:"); TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); PrintTree(tree); } /* Print priority queue */ public static void PrintHeap(PriorityQueue queue) { var newQueue = new PriorityQueue(queue.UnorderedItems, queue.Comparer); Console.Write("Heap array representation:"); List list = []; while (newQueue.TryDequeue(out int element, out _)) { list.Add(element); } Console.WriteLine("Heap tree representation:"); Console.WriteLine(string.Join(',', list.ToList())); TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); PrintTree(tree); } } ================================================ FILE: en/codes/csharp/utils/TreeNode.cs ================================================ /** * File: TreeNode.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.utils; /* Binary tree node class */ public class TreeNode(int? x) { public int? val = x; // Node value public int height; // Node height public TreeNode? left; // Reference to left child node public TreeNode? right; // Reference to right child node // For the serialization encoding rules, please refer to: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Array representation of binary tree: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // Linked list representation of binary tree: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* Deserialize a list into a binary tree: recursion */ static TreeNode? ListToTreeDFS(List arr, int i) { if (i < 0 || i >= arr.Count || !arr[i].HasValue) { return null; } TreeNode root = new(arr[i]) { left = ListToTreeDFS(arr, 2 * i + 1), right = ListToTreeDFS(arr, 2 * i + 2) }; return root; } /* Deserialize a list into a binary tree */ public static TreeNode? ListToTree(List arr) { return ListToTreeDFS(arr, 0); } /* Serialize a binary tree into a list: recursion */ static void TreeToListDFS(TreeNode? root, int i, List res) { if (root == null) return; while (i >= res.Count) { res.Add(null); } res[i] = root.val; TreeToListDFS(root.left, 2 * i + 1, res); TreeToListDFS(root.right, 2 * i + 2, res); } /* Serialize a binary tree into a list */ public static List TreeToList(TreeNode root) { List res = []; TreeToListDFS(root, 0, res); return res; } } ================================================ FILE: en/codes/csharp/utils/Vertex.cs ================================================ /** * File: Vertex.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com), krahets (krahets@163.com) */ namespace hello_algo.utils; /* Vertex class */ public class Vertex(int val) { public int val = val; /* Input value list vals, return vertex list 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; } /* Input vertex list vets, return value list vals */ public static List VetsToVals(List vets) { List vals = []; foreach (Vertex vet in vets) { vals.Add(vet.val); } return vals; } } ================================================ FILE: en/codes/dart/build.dart ================================================ import 'dart:io'; void main() { Directory foldPath = Directory('codes/dart/'); List files = foldPath.listSync(); int totalCount = 0; int errorCount = 0; for (var file in files) { if (file.path.endsWith('build.dart')) continue; if (file is File && file.path.endsWith('.dart')) { totalCount++; try { Process.runSync('dart', [file.path]); } catch (e) { errorCount++; print('Error: $e'); print('File: ${file.path}'); } } else if (file is Directory) { List subFiles = file.listSync(); for (var subFile in subFiles) { if (subFile is File && subFile.path.endsWith('.dart')) { totalCount++; try { Process.runSync('dart', [subFile.path]); } catch (e) { errorCount++; print('Error: $e'); print('File: ${file.path}'); } } } } } print('===== Build Complete ====='); print('Total: $totalCount'); print('Error: $errorCount'); } ================================================ FILE: en/codes/dart/chapter_array_and_linkedlist/array.dart ================================================ /** * File: array.dart * Created Time: 2023-01-20 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable import 'dart:math'; /* Random access to element */ int randomAccess(List nums) { // Randomly select a number in the interval [0, nums.length) int randomIndex = Random().nextInt(nums.length); // Retrieve and return the random element int randomNum = nums[randomIndex]; return randomNum; } /* Extend array length */ List extend(List nums, int enlarge) { // Initialize an array with extended length List res = List.filled(nums.length + enlarge, 0); // Copy all elements from the original array to the new array for (var i = 0; i < nums.length; i++) { res[i] = nums[i]; } // Return the extended new array return res; } /* Insert element _num at array index index */ void insert(List nums, int _num, int index) { // Move all elements at and after index index backward by one position for (var i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Assign _num to element at index nums[index] = _num; } /* Remove the element at index index */ void remove(List nums, int index) { // Move all elements after index index forward by one position for (var i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* Traverse array elements */ void traverse(List nums) { int count = 0; // Traverse array by index for (var i = 0; i < nums.length; i++) { count += nums[i]; } // Direct traversal of array elements for (int _num in nums) { count += _num; } // Traverse array using forEach method nums.forEach((_num) { count += _num; }); } /* Find the specified element in the array */ int find(List nums, int target) { for (var i = 0; i < nums.length; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ void main() { /* Initialize array */ var arr = List.filled(5, 0); print('Array arr = $arr'); List nums = [1, 3, 2, 5, 4]; print('Array nums = $nums'); /* Insert element */ int randomNum = randomAccess(nums); print('Get random element $randomNum from nums'); /* Traverse array */ nums = extend(nums, 3); print('Extend array length to 8, get nums = $nums'); /* Insert element */ insert(nums, 6, 3); print("Insert number 6 at index 3, get nums = $nums"); /* Remove element */ remove(nums, 2); print("Delete element at index 2, get nums = $nums"); /* Traverse array */ traverse(nums); /* Find element */ int index = find(nums, 3); print("Find element 3 in nums, index = $index"); } ================================================ FILE: en/codes/dart/chapter_array_and_linkedlist/linked_list.dart ================================================ /** * File: linked_list.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import '../utils/list_node.dart'; import '../utils/print_util.dart'; /* Insert node P after node n0 in the linked list */ void insert(ListNode n0, ListNode P) { ListNode? n1 = n0.next; P.next = n1; n0.next = P; } /* Remove the first node after node n0 in the linked list */ void remove(ListNode n0) { if (n0.next == null) return; // n0 -> P -> n1 ListNode P = n0.next!; ListNode? n1 = P.next; n0.next = n1; } /* Access the node at index index in the linked list */ ListNode? access(ListNode? head, int index) { for (var i = 0; i < index; i++) { if (head == null) return null; head = head.next; } return head; } /* Find the first node with value target in the linked list */ int find(ListNode? head, int target) { int index = 0; while (head != null) { if (head.val == target) { return index; } head = head.next; index++; } return -1; } /* Driver Code */ void main() { // Initialize linked list // Initialize each node ListNode n0 = ListNode(1); ListNode n1 = ListNode(3); ListNode n2 = ListNode(2); ListNode n3 = ListNode(5); ListNode n4 = ListNode(4); // Build references between nodes n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; print('Initialized linked list is'); printLinkedList(n0); /* Insert node */ insert(n0, ListNode(0)); print('Linked list after inserting node is'); printLinkedList(n0); /* Remove node */ remove(n0); print('Linked list after removing node is'); printLinkedList(n0); /* Access node */ ListNode? node = access(n0, 3); print('Value of node at index 3 in linked list = ${node!.val}'); /* Search node */ int index = find(n0, 2); print('Index of node with value 2 in linked list = $index'); } ================================================ FILE: en/codes/dart/chapter_array_and_linkedlist/list.dart ================================================ /** * File: list.dart * Created Time: 2023-01-24 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable /* Driver Code */ void main() { /* Initialize list */ List nums = [1, 3, 2, 5, 4]; print('List nums = $nums'); /* Update element */ int _num = nums[1]; print('Access element at index 1, get _num = $_num'); /* Add elements at the end */ nums[1] = 0; print('Update element at index 1 to 0, get nums = $nums'); /* Remove element */ nums.clear(); print('After clearing list, nums = $nums'); /* Direct traversal of list elements */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print('After adding elements, nums = $nums'); /* Sort list */ nums.insert(3, 6); print('Insert number 6 at index 3, get nums = $nums'); /* Remove element */ nums.removeAt(3); print('Delete element at index 3, get nums = $nums'); /* Traverse list by index */ int count = 0; for (var i = 0; i < nums.length; i++) { count += nums[i]; } /* Directly traverse list elements */ count = 0; for (var x in nums) { count += x; } /* Concatenate two lists */ List nums1 = [6, 8, 7, 10, 9]; nums.addAll(nums1); print('After concatenating list nums1 to nums, get nums = $nums'); /* Sort list */ nums.sort(); print('After sorting list, nums = $nums'); } ================================================ FILE: en/codes/dart/chapter_array_and_linkedlist/my_list.dart ================================================ /** * File: my_list.dart * Created Time: 2023-02-05 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* List class */ class MyList { late List _arr; // Array (stores list elements) int _capacity = 10; // List capacity int _size = 0; // List length (current number of elements) int _extendRatio = 2; // Multiple by which the list capacity is extended each time /* Constructor */ MyList() { _arr = List.filled(_capacity, 0); } /* Get list length (current number of elements) */ int size() => _size; /* Get list capacity */ int capacity() => _capacity; /* Update element */ int get(int index) { if (index >= _size) throw RangeError('Index out of bounds'); return _arr[index]; } /* Add elements at the end */ void set(int index, int _num) { if (index >= _size) throw RangeError('Index out of bounds'); _arr[index] = _num; } /* Direct traversal of list elements */ void add(int _num) { // When the number of elements exceeds capacity, trigger the extension mechanism if (_size == _capacity) extendCapacity(); _arr[_size] = _num; // Update the number of elements _size++; } /* Sort list */ void insert(int index, int _num) { if (index >= _size) throw RangeError('Index out of bounds'); // When the number of elements exceeds capacity, trigger the extension mechanism if (_size == _capacity) extendCapacity(); // Move all elements after index index forward by one position for (var j = _size - 1; j >= index; j--) { _arr[j + 1] = _arr[j]; } _arr[index] = _num; // Update the number of elements _size++; } /* Remove element */ int remove(int index) { if (index >= _size) throw RangeError('Index out of bounds'); int _num = _arr[index]; // Move all elements after index forward by one position for (var j = index; j < _size - 1; j++) { _arr[j] = _arr[j + 1]; } // Update the number of elements _size--; // Return the removed element return _num; } /* Driver Code */ void extendCapacity() { // Create new array with length _extendRatio times original array final _newNums = List.filled(_capacity * _extendRatio, 0); // Copy original array to new array List.copyRange(_newNums, 0, _arr); // Update _arr reference _arr = _newNums; // Add elements at the end _capacity = _arr.length; } /* Convert list to array */ List toArray() { List arr = []; for (var i = 0; i < _size; i++) { arr.add(get(i)); } return arr; } } /* Driver Code */ void main() { /* Initialize list */ MyList nums = MyList(); /* Direct traversal of list elements */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print( 'List nums = ${nums.toArray()}, capacity = ${nums.capacity()}, length = ${nums.size()}'); /* Sort list */ nums.insert(3, 6); print('Insert number 6 at index 3, get nums = ${nums.toArray()}'); /* Remove element */ nums.remove(3); print('Delete element at index 3, get nums = ${nums.toArray()}'); /* Update element */ int _num = nums.get(1); print('Access element at index 1, get _num = $_num'); /* Add elements at the end */ nums.set(1, 0); print('Update element at index 1 to 0, get nums = ${nums.toArray()}'); /* Test capacity expansion mechanism */ for (var i = 0; i < 10; i++) { // At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism nums.add(i); } print( 'After expansion, list nums = ${nums.toArray()}, capacity = ${nums.capacity()}, length = ${nums.size()}'); } ================================================ FILE: en/codes/dart/chapter_backtracking/n_queens.dart ================================================ /** * File: n_queens.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Backtracking algorithm: N queens */ void backtrack( int row, int n, List> state, List>> res, List cols, List diags1, List diags2, ) { // When all rows are placed, record the solution if (row == n) { List> copyState = []; for (List sRow in state) { copyState.add(List.from(sRow)); } res.add(copyState); return; } // Traverse all columns for (int col = 0; col < n; col++) { // Calculate the main diagonal and anti-diagonal corresponding to this cell int diag1 = row - col + n - 1; int diag2 = row + col; // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Attempt: place the queen in this cell state[row][col] = "Q"; cols[col] = true; diags1[diag1] = true; diags2[diag2] = true; // Place the next row backtrack(row + 1, n, state, res, cols, diags1, diags2); // Backtrack: restore this cell to an empty cell state[row][col] = "#"; cols[col] = false; diags1[diag1] = false; diags2[diag2] = false; } } } /* Solve N queens */ List>> nQueens(int n) { // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell List> state = List.generate(n, (index) => List.filled(n, "#")); List cols = List.filled(n, false); // Record whether there is a queen in the column List diags1 = List.filled(2 * n - 1, false); // Record whether there is a queen on the main diagonal List diags2 = List.filled(2 * n - 1, false); // Record whether there is a queen on the anti-diagonal List>> res = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } /* Driver Code */ void main() { int n = 4; List>> res = nQueens(n); print("Input board size is $n"); print("Total queen placement solutions: ${res.length}"); for (List> state in res) { print("--------------------"); for (List row in state) { print(row); } } } ================================================ FILE: en/codes/dart/chapter_backtracking/permutations_i.dart ================================================ /** * File: permutations_i.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Backtracking algorithm: Permutations I */ void backtrack( List state, List choices, List selected, List> res, ) { // When the state length equals the number of elements, record the solution if (state.length == choices.length) { res.add(List.from(state)); return; } // Traverse all choices for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state.add(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.removeLast(); } } } /* Permutations I */ List> permutationsI(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } /* Driver Code */ void main() { List nums = [1, 2, 3]; List> res = permutationsI(nums); print("Input array nums = $nums"); print("All permutations res = $res"); } ================================================ FILE: en/codes/dart/chapter_backtracking/permutations_ii.dart ================================================ /** * File: permutations_ii.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Backtracking algorithm: Permutations II */ void backtrack( List state, List choices, List selected, List> res, ) { // When the state length equals the number of elements, record the solution if (state.length == choices.length) { res.add(List.from(state)); return; } // Traverse all choices Set duplicated = {}; for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated.contains(choice)) { // Attempt: make choice, update state duplicated.add(choice); // Record the selected element value selected[i] = true; state.add(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.removeLast(); } } } /* Permutations II */ List> permutationsII(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } /* Driver Code */ void main() { List nums = [1, 2, 2]; List> res = permutationsII(nums); print("Input array nums = $nums"); print("All permutations res = $res"); } ================================================ FILE: en/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart ================================================ /** * File: preorder_traversal_i_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Preorder traversal: Example 1 */ void preOrder(TreeNode? root, List res) { if (root == null) { return; } if (root.val == 7) { // Record solution res.add(root); } preOrder(root.left, res); preOrder(root.right, res); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\nInitialize binary tree"); printTree(root); // Preorder traversal List res = []; preOrder(root, res); print("\nOutput all nodes with value 7"); print(List.generate(res.length, (i) => res[i].val)); } ================================================ FILE: en/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart ================================================ /** * File: preorder_traversal_ii_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Preorder traversal: Example 2 */ void preOrder( TreeNode? root, List path, List> res, ) { if (root == null) { return; } // Attempt path.add(root); if (root.val == 7) { // Record solution res.add(List.from(path)); } preOrder(root.left, path, res); preOrder(root.right, path, res); // Backtrack path.removeLast(); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\nInitialize binary tree"); printTree(root); // Preorder traversal List path = []; List> res = []; preOrder(root, path, res); print("\nOutput all paths from root node to node 7"); for (List vals in res) { print(List.generate(vals.length, (i) => vals[i].val)); } } ================================================ FILE: en/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart ================================================ /** * File: preorder_traversal_iii_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Preorder traversal: Example 3 */ void preOrder( TreeNode? root, List path, List> res, ) { if (root == null || root.val == 3) { return; } // Attempt path.add(root); if (root.val == 7) { // Record solution res.add(List.from(path)); } preOrder(root.left, path, res); preOrder(root.right, path, res); // Backtrack path.removeLast(); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\nInitialize binary tree"); printTree(root); // Preorder traversal List path = []; List> res = []; preOrder(root, path, res); print("\nOutput all paths from root node to node 7"); for (List vals in res) { print(List.generate(vals.length, (i) => vals[i].val)); } } ================================================ FILE: en/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart ================================================ /** * File: preorder_traversal_iii_template.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Check if the current state is a solution */ bool isSolution(List state) { return state.isNotEmpty && state.last.val == 7; } /* Record solution */ void recordSolution(List state, List> res) { res.add(List.from(state)); } /* Check if the choice is valid under the current state */ bool isValid(List state, TreeNode? choice) { return choice != null && choice.val != 3; } /* Update state */ void makeChoice(List state, TreeNode? choice) { state.add(choice!); } /* Restore state */ void undoChoice(List state, TreeNode? choice) { state.removeLast(); } /* Backtracking algorithm: Example 3 */ void backtrack( List state, List choices, List> res, ) { // Check if it is a solution if (isSolution(state)) { // Record solution recordSolution(state, res); } // Traverse all choices for (TreeNode? choice in choices) { // Pruning: check if the choice is valid if (isValid(state, choice)) { // Attempt: make choice, update state makeChoice(state, choice); // Proceed to the next round of selection backtrack(state, [choice!.left, choice.right], res); // Backtrack: undo choice, restore to previous state undoChoice(state, choice); } } } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\nInitialize binary tree"); printTree(root); // Backtracking algorithm List> res = []; backtrack([], [root!], res); print("\nOutput all paths from root node to node 7, requiring paths do not include nodes with value 3"); for (List path in res) { print(List.from(path.map((e) => e.val))); } } ================================================ FILE: en/codes/dart/chapter_backtracking/subset_sum_i.dart ================================================ /** * File: subset_sum_i.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Backtracking algorithm: Subset sum I */ void backtrack( List state, int target, List choices, int start, List> res, ) { // When the subset sum equals target, record the solution if (target == 0) { res.add(List.from(state)); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets for (int i = start; i < choices.length; i++) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if (target - choices[i] < 0) { break; } // Attempt: make choice, update target, start state.add(choices[i]); // Proceed to the next round of selection backtrack(state, target - choices[i], choices, i, res); // Backtrack: undo choice, restore to previous state state.removeLast(); } } /* Solve subset sum I */ List> subsetSumI(List nums, int target) { List state = []; // State (subset) nums.sort(); // Sort nums int start = 0; // Start point for traversal List> res = []; // Result list (subset list) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ void main() { List nums = [3, 4, 5]; int target = 9; List> res = subsetSumI(nums, target); print("Input array nums = $nums, target = $target"); print("All subsets with sum equal to $target res = $res"); } ================================================ FILE: en/codes/dart/chapter_backtracking/subset_sum_i_naive.dart ================================================ /** * File: subset_sum_i_naive.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Backtracking algorithm: Subset sum I */ void backtrack( List state, int target, int total, List choices, List> res, ) { // When the subset sum equals target, record the solution if (total == target) { res.add(List.from(state)); return; } // Traverse all choices for (int i = 0; i < choices.length; i++) { // Pruning: if the subset sum exceeds target, skip this choice if (total + choices[i] > target) { continue; } // Attempt: make choice, update element sum total state.add(choices[i]); // Proceed to the next round of selection backtrack(state, target, total + choices[i], choices, res); // Backtrack: undo choice, restore to previous state state.removeLast(); } } /* Solve subset sum I (including duplicate subsets) */ List> subsetSumINaive(List nums, int target) { List state = []; // State (subset) int total = 0; // Sum of elements List> res = []; // Result list (subset list) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ void main() { List nums = [3, 4, 5]; int target = 9; List> res = subsetSumINaive(nums, target); print("Input array nums = $nums, target = $target"); print("All subsets with sum equal to $target res = $res"); print("Please note that this method outputs results containing duplicate sets"); } ================================================ FILE: en/codes/dart/chapter_backtracking/subset_sum_ii.dart ================================================ /** * File: subset_sum_ii.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Backtracking algorithm: Subset sum II */ void backtrack( List state, int target, List choices, int start, List> res, ) { // When the subset sum equals target, record the solution if (target == 0) { res.add(List.from(state)); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets // Pruning 3: start traversing from start to avoid repeatedly selecting the same element for (int i = start; i < choices.length; i++) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if (target - choices[i] < 0) { break; } // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly if (i > start && choices[i] == choices[i - 1]) { continue; } // Attempt: make choice, update target, start state.add(choices[i]); // Proceed to the next round of selection backtrack(state, target - choices[i], choices, i + 1, res); // Backtrack: undo choice, restore to previous state state.removeLast(); } } /* Solve subset sum II */ List> subsetSumII(List nums, int target) { List state = []; // State (subset) nums.sort(); // Sort nums int start = 0; // Start point for traversal List> res = []; // Result list (subset list) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ void main() { List nums = [4, 4, 5]; int target = 9; List> res = subsetSumII(nums, target); print("Input array nums = $nums, target = $target"); print("All subsets with sum equal to $target res = $res"); } ================================================ FILE: en/codes/dart/chapter_computational_complexity/iteration.dart ================================================ /** * File: iteration.dart * Created Time: 2023-08-27 * Author: liuyuxin (gvenusleo@gmail.com) */ /* for loop */ int forLoop(int n) { int res = 0; // Sum 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } /* while loop */ int whileLoop(int n) { int res = 0; int i = 1; // Initialize condition variable // Sum 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Update condition variable } return res; } /* while loop (two updates) */ int whileLoopII(int n) { int res = 0; int i = 1; // Initialize condition variable // Sum 1, 4, 10, ... while (i <= n) { res += i; // Update condition variable i++; i *= 2; } return res; } /* Nested for loop */ String nestedForLoop(int n) { String res = ""; // Loop i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // Loop j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { res += "($i, $j), "; } } return res; } /* Driver Code */ void main() { int n = 5; int res; res = forLoop(n); print("\nFor loop sum result res = $res"); res = whileLoop(n); print("\nWhile loop sum result res = $res"); res = whileLoopII(n); print("\nWhile loop (two updates) sum result res = $res"); String resStr = nestedForLoop(n); print("\nNested for loop result $resStr"); } ================================================ FILE: en/codes/dart/chapter_computational_complexity/recursion.dart ================================================ /** * File: recursion.dart * Created Time: 2023-08-27 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Recursion */ int recur(int n) { // Termination condition if (n == 1) return 1; // Recurse: recursive call int res = recur(n - 1); // Return: return result return n + res; } /* Simulate recursion using iteration */ int forLoopRecur(int n) { // Use an explicit stack to simulate the system call stack List stack = []; int res = 0; // Recurse: recursive call for (int i = n; i > 0; i--) { // Simulate "recurse" with "push" stack.add(i); } // Return: return result while (!stack.isEmpty) { // Simulate "return" with "pop" res += stack.removeLast(); } // res = 1+2+3+...+n return res; } /* Tail recursion */ int tailRecur(int n, int res) { // Termination condition if (n == 0) return res; // Tail recursive call return tailRecur(n - 1, res + n); } /* Fibonacci sequence: recursion */ int fib(int n) { // Termination condition f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // Recursive call f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // Return result f(n) return res; } /* Driver Code */ void main() { int n = 5; int res; res = recur(n); print("\nRecursion sum result res = $res"); res = tailRecur(n, 0); print("\nTail recursion sum result res = $res"); res = forLoopRecur(n); print("\nUsing iteration to simulate recursion sum result res = $res"); res = fib(n); print("\nThe ${n}th Fibonacci number is $res"); } ================================================ FILE: en/codes/dart/chapter_computational_complexity/space_complexity.dart ================================================ /** * File: space_complexity.dart * Created Time: 2023-2-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable import 'dart:collection'; import '../utils/list_node.dart'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Function */ int function() { // Perform some operations return 0; } /* Constant order */ void constant(int n) { // Constants, variables, objects occupy O(1) space final int a = 0; int b = 0; List nums = List.filled(10000, 0); ListNode node = ListNode(0); // Variables in the loop occupy O(1) space for (var i = 0; i < n; i++) { int c = 0; } // Functions in the loop occupy O(1) space for (var i = 0; i < n; i++) { function(); } } /* Linear order */ void linear(int n) { // Array of length n uses O(n) space List nums = List.filled(n, 0); // A list of length n occupies O(n) space List nodes = []; for (var i = 0; i < n; i++) { nodes.add(ListNode(i)); } // A hash table of length n occupies O(n) space Map map = HashMap(); for (var i = 0; i < n; i++) { map.putIfAbsent(i, () => i.toString()); } } /* Linear order (recursive implementation) */ void linearRecur(int n) { print('Recursion n = $n'); if (n == 1) return; linearRecur(n - 1); } /* Exponential order */ void quadratic(int n) { // Matrix uses O(n^2) space List> numMatrix = List.generate(n, (_) => List.filled(n, 0)); // 2D list uses O(n^2) space List> numList = []; for (var i = 0; i < n; i++) { List tmp = []; for (int j = 0; j < n; j++) { tmp.add(0); } numList.add(tmp); } } /* Quadratic order (recursive implementation) */ int quadraticRecur(int n) { if (n <= 0) return 0; List nums = List.filled(n, 0); print('In recursion n = $n, nums length = ${nums.length}'); return quadraticRecur(n - 1); } /* Driver Code */ TreeNode? buildTree(int n) { if (n == 0) return null; TreeNode root = TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ void main() { int n = 5; // Constant order constant(n); // Linear order linear(n); linearRecur(n); // Exponential order quadratic(n); quadraticRecur(n); // Exponential order TreeNode? root = buildTree(n); printTree(root); } ================================================ FILE: en/codes/dart/chapter_computational_complexity/time_complexity.dart ================================================ /** * File: time_complexity.dart * Created Time: 2023-02-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable /* Constant order */ int constant(int n) { int count = 0; int size = 100000; for (var i = 0; i < size; i++) { count++; } return count; } /* Linear order */ int linear(int n) { int count = 0; for (var i = 0; i < n; i++) { count++; } return count; } /* Linear order (traversing array) */ int arrayTraversal(List nums) { int count = 0; // Number of iterations is proportional to the array length for (var _num in nums) { count++; } return count; } /* Exponential order */ int quadratic(int n) { int count = 0; // Number of iterations is quadratically related to the data size n for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* Quadratic order (bubble sort) */ int bubbleSort(List nums) { int count = 0; // Counter // Outer loop: unsorted range is [0, i] for (var i = nums.length - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (var j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // Element swap includes 3 unit operations } } } return count; } /* Exponential order (loop implementation) */ int exponential(int n) { int count = 0, base = 1; // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1) for (var i = 0; i < n; i++) { for (var j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* Exponential order (recursive implementation) */ int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* Logarithmic order (loop implementation) */ int logarithmic(int n) { int count = 0; while (n > 1) { n = n ~/ 2; count++; } return count; } /* Logarithmic order (recursive implementation) */ int logRecur(int n) { if (n <= 1) return 0; return logRecur(n ~/ 2) + 1; } /* Linearithmic order */ int linearLogRecur(int n) { if (n <= 1) return 1; int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2); for (var i = 0; i < n; i++) { count++; } return count; } /* Factorial order (recursive implementation) */ int factorialRecur(int n) { if (n == 0) return 1; int count = 0; // Split from 1 into n for (var i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ void main() { // You can modify n to run and observe the trend of the number of operations for various complexities int n = 8; print('Input data size n = $n'); int count = constant(n); print('Constant-time operations count = $count'); count = linear(n); print('Linear-time operations count = $count'); count = arrayTraversal(List.filled(n, 0)); print('Linear-time (array traversal) operations count = $count'); count = quadratic(n); print('Quadratic-time operations count = $count'); final nums = List.filled(n, 0); for (int i = 0; i < n; i++) { nums[i] = n - i; // [n,n-1,...,2,1] } count = bubbleSort(nums); print('Quadratic-time (bubble sort) operations count = $count'); count = exponential(n); print('Exponential-time (iterative) operations count = $count'); count = expRecur(n); print('Exponential-time (recursive) operations count = $count'); count = logarithmic(n); print('Logarithmic-time (iterative) operations count = $count'); count = logRecur(n); print('Logarithmic-time (recursive) operations count = $count'); count = linearLogRecur(n); print('Linearithmic-time (recursive) operations count = $count'); count = factorialRecur(n); print('Factorial-time (recursive) operations count = $count'); } ================================================ FILE: en/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart ================================================ /** * File: worst_best_time_complexity.dart * Created Time: 2023-02-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* Generate an array with elements { 1, 2, ..., n }, order shuffled */ List randomNumbers(int n) { final nums = List.filled(n, 0); // Generate array nums = { 1, 2, 3, ..., n } for (var i = 0; i < n; i++) { nums[i] = i + 1; } // Randomly shuffle array elements nums.shuffle(); return nums; } /* Find the index of number 1 in array nums */ int findOne(List nums) { for (var i = 0; i < nums.length; i++) { // When element 1 is at the head of the array, best time complexity O(1) is achieved // When element 1 is at the tail of the array, worst time complexity O(n) is achieved if (nums[i] == 1) return i; } return -1; } /* Driver Code */ void main() { for (var i = 0; i < 10; i++) { int n = 100; final nums = randomNumbers(n); int index = findOne(nums); print('\nArray [ 1, 2, ..., n ] after shuffling = $nums'); print('Index of number 1 is + $index'); } } ================================================ FILE: en/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart ================================================ /** * File: binary_search_recur.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Binary search: problem f(i, j) */ int dfs(List nums, int target, int i, int j) { // If the interval is empty, it means there is no target element, return -1 if (i > j) { return -1; } // Calculate the midpoint index m int m = (i + j) ~/ 2; if (nums[m] < target) { // Recursion subproblem f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // Recursion subproblem f(i, m-1) return dfs(nums, target, i, m - 1); } else { // Found the target element, return its index return m; } } /* Binary search */ int binarySearch(List nums, int target) { int n = nums.length; // Solve the problem f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ void main() { int target = 6; List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // Binary search (closed interval on both sides) int index = binarySearch(nums, target); print("Index of target element 6 = $index"); } ================================================ FILE: en/codes/dart/chapter_divide_and_conquer/build_tree.dart ================================================ /** * File: build_tree.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Build binary tree: divide and conquer */ TreeNode? dfs( List preorder, Map inorderMap, int i, int l, int r, ) { // Terminate when the subtree interval is empty if (r - l < 0) { return null; } // Initialize the root node TreeNode? root = TreeNode(preorder[i]); // Query m to divide the left and right subtrees int m = inorderMap[preorder[i]]!; // Subproblem: build the left subtree root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // Subproblem: build the right subtree root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // Return the root node return root; } /* Build binary tree */ TreeNode? buildTree(List preorder, List inorder) { // Initialize hash map, storing the mapping from inorder elements to indices Map inorderMap = {}; for (int i = 0; i < inorder.length; i++) { inorderMap[inorder[i]] = i; } TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ void main() { List preorder = [3, 9, 2, 1, 7]; List inorder = [9, 3, 1, 2, 7]; print("Pre-order traversal = $preorder"); print("In-order traversal = $inorder"); TreeNode? root = buildTree(preorder, inorder); print("The constructed binary tree is:"); printTree(root!); } ================================================ FILE: en/codes/dart/chapter_divide_and_conquer/hanota.dart ================================================ /** * File: hanota.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Move a disk */ void move(List src, List tar) { // Take out a disk from the top of src int pan = src.removeLast(); // Place the disk on top of tar tar.add(pan); } /* Solve the Tower of Hanoi problem f(i) */ void dfs(int i, List src, List buf, List tar) { // If there is only one disk left in src, move it directly to tar if (i == 1) { move(src, tar); return; } // Subproblem f(i-1): move the top i-1 disks from src to buf using tar dfs(i - 1, src, tar, buf); // Subproblem f(1): move the remaining disk from src to tar move(src, tar); // Subproblem f(i-1): move the top i-1 disks from buf to tar using src dfs(i - 1, buf, src, tar); } /* Solve the Tower of Hanoi problem */ void solveHanota(List A, List B, List C) { int n = A.length; // Move the top n disks from A to C using B dfs(n, A, B, C); } /* Driver Code */ void main() { // The tail of the list is the top of the rod List A = [5, 4, 3, 2, 1]; List B = []; List C = []; print("In initial state:"); print("A = $A"); print("B = $B"); print("C = $C"); solveHanota(A, B, C); print("After disk movement is complete:"); print("A = $A"); print("B = $B"); print("C = $C"); } ================================================ FILE: en/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart ================================================ /** * File: climbing_stairs_backtrack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Backtracking */ void backtrack(List choices, int state, int n, List res) { // When climbing to the n-th stair, add 1 to the solution count if (state == n) { res[0]++; } // Traverse all choices for (int choice in choices) { // Pruning: not allowed to go beyond the n-th stair if (state + choice > n) continue; // Attempt: make choice, update state backtrack(choices, state + choice, n, res); // Backtrack } } /* Climbing stairs: Backtracking */ int climbingStairsBacktrack(int n) { List choices = [1, 2]; // Can choose to climb up 1 or 2 stairs int state = 0; // Start climbing from the 0-th stair List res = []; res.add(0); // Use res[0] to record the solution count backtrack(choices, state, n, res); return res[0]; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsBacktrack(n); print("Climbing $n stairs has $res solutions"); } ================================================ FILE: en/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart ================================================ /** * File: climbing_stairs_constraint_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Climbing stairs with constraint: Dynamic programming */ int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // Initialize dp table, used to store solutions to subproblems List> dp = List.generate(n + 1, (index) => List.filled(3, 0)); // Initial state: preset the solution to the smallest subproblem dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsConstraintDP(n); print("Climbing $n stairs has $res solutions"); } ================================================ FILE: en/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart ================================================ /** * File: climbing_stairs_dfs.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Search */ int dfs(int i) { // Known dp[1] and dp[2], return them 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; } /* Climbing stairs: Search */ int climbingStairsDFS(int n) { return dfs(n); } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDFS(n); print("Climbing $n stairs has $res solutions"); } ================================================ FILE: en/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart ================================================ /** * File: climbing_stairs_dfs_mem.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Memoization search */ int dfs(int i, List mem) { // Known dp[1] and dp[2], return them if (i == 1 || i == 2) return i; // If record dp[i] exists, return it directly 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); // Record dp[i] mem[i] = count; return count; } /* Climbing stairs: Memoization search */ int climbingStairsDFSMem(int n) { // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record List mem = List.filled(n + 1, -1); return dfs(n, mem); } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDFSMem(n); print("Climbing $n stairs has $res solutions"); } ================================================ FILE: en/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart ================================================ /** * File: climbing_stairs_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Climbing stairs: Dynamic programming */ int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; // Initialize dp table, used to store solutions to subproblems List dp = List.filled(n + 1, 0); // Initial state: preset the solution to the smallest subproblem dp[1] = 1; dp[2] = 2; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* Climbing stairs: Space-optimized dynamic programming */ int climbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDP(n); print("Climbing $n stairs has $res solutions"); res = climbingStairsDPComp(n); print("Climbing $n stairs has $res solutions"); } ================================================ FILE: en/codes/dart/chapter_dynamic_programming/coin_change.dart ================================================ /** * File: coin_change.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Coin change: Dynamic programming */ int coinChangeDP(List coins, int amt) { int n = coins.length; int MAX = amt + 1; // Initialize dp table List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); // State transition: first row and first column for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // State transition: rest of the rows and columns for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // The smaller value between not selecting and selecting coin 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; } /* Coin change: Space-optimized dynamic programming */ int coinChangeDPComp(List coins, int amt) { int n = coins.length; int MAX = amt + 1; // Initialize dp table List dp = List.filled(amt + 1, MAX); dp[0] = 0; // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // The smaller value between not selecting and selecting coin i dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } /* Driver Code */ void main() { List coins = [1, 2, 5]; int amt = 4; // Dynamic programming int res = coinChangeDP(coins, amt); print("Minimum coins needed to make target amount is $res"); // Space-optimized dynamic programming res = coinChangeDPComp(coins, amt); print("Minimum coins needed to make target amount is $res"); } ================================================ FILE: en/codes/dart/chapter_dynamic_programming/coin_change_ii.dart ================================================ /** * File: coin_change_ii.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Coin change II: Dynamic programming */ int coinChangeIIDP(List coins, int amt) { int n = coins.length; // Initialize dp table List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); // Initialize first column for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // Sum of the two options: not selecting and selecting coin i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* Coin change II: Space-optimized dynamic programming */ int coinChangeIIDPComp(List coins, int amt) { int n = coins.length; // Initialize dp table List dp = List.filled(amt + 1, 0); dp[0] = 1; // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // Sum of the two options: not selecting and selecting coin i dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ void main() { List coins = [1, 2, 5]; int amt = 5; // Dynamic programming int res = coinChangeIIDP(coins, amt); print("Number of coin combinations to make target amount is $res"); // Space-optimized dynamic programming res = coinChangeIIDPComp(coins, amt); print("Number of coin combinations to make target amount is $res"); } ================================================ FILE: en/codes/dart/chapter_dynamic_programming/edit_distance.dart ================================================ /** * File: edit_distance.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Edit distance: Brute-force search */ int editDistanceDFS(String s, String t, int i, int j) { // If both s and t are empty, return 0 if (i == 0 && j == 0) return 0; // If s is empty, return length of t if (i == 0) return j; // If t is empty, return length of s if (j == 0) return i; // If two characters are equal, skip both characters if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 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 minimum edit steps return min(min(insert, delete), replace) + 1; } /* Edit distance: Memoization search */ int editDistanceDFSMem(String s, String t, List> mem, int i, int j) { // If both s and t are empty, return 0 if (i == 0 && j == 0) return 0; // If s is empty, return length of t if (i == 0) return j; // If t is empty, return length of s if (j == 0) return i; // If there's a record, return it directly if (mem[i][j] != -1) return mem[i][j]; // If two characters are equal, skip both characters if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 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); // Record and return minimum edit steps mem[i][j] = min(min(insert, delete), replace) + 1; return mem[i][j]; } /* Edit distance: Dynamic programming */ int editDistanceDP(String s, String t) { int n = s.length, m = t.length; List> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0)); // State transition: first row and first column for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // State transition: rest of the rows and columns for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // If two characters are equal, skip both characters dp[i][j] = dp[i - 1][j - 1]; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* Edit distance: Space-optimized dynamic programming */ int editDistanceDPComp(String s, String t) { int n = s.length, m = t.length; List dp = List.filled(m + 1, 0); // State transition: first row for (int j = 1; j <= m; j++) { dp[j] = j; } // State transition: rest of the rows for (int i = 1; i <= n; i++) { // State transition: first column int leftup = dp[0]; // Temporarily store dp[i-1, j-1] dp[0] = i; // State transition: rest of the columns for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // If two characters are equal, skip both characters dp[j] = leftup; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // Update for next round's dp[i-1, j-1] } } return dp[m]; } /* Driver Code */ void main() { String s = "bag"; String t = "pack"; int n = s.length, m = t.length; // Brute-force search int res = editDistanceDFS(s, t, n, m); print("Changing " + s + " to " + t + " requires minimum $res edits"); // Memoization search List> mem = List.generate(n + 1, (_) => List.filled(m + 1, -1)); res = editDistanceDFSMem(s, t, mem, n, m); print("Changing " + s + " to " + t + " requires minimum $res edits"); // Dynamic programming res = editDistanceDP(s, t); print("Changing " + s + " to " + t + " requires minimum $res edits"); // Space-optimized dynamic programming res = editDistanceDPComp(s, t); print("Changing " + s + " to " + t + " requires minimum $res edits"); } ================================================ FILE: en/codes/dart/chapter_dynamic_programming/knapsack.dart ================================================ /** * File: knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 0-1 knapsack: Brute-force search */ int knapsackDFS(List wgt, List val, int i, int c) { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i == 0 || c == 0) { return 0; } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // Calculate the maximum value of not putting in and putting in item i int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // Return the larger value of the two options return max(no, yes); } /* 0-1 knapsack: Memoization search */ int knapsackDFSMem( List wgt, List val, List> mem, int i, int c, ) { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i == 0 || c == 0) { return 0; } // If there's a record, return it directly if (mem[i][c] != -1) { return mem[i][c]; } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // Calculate the maximum value of not putting in and putting in item 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]; // Record and return the larger value of the two options mem[i][c] = max(no, yes); return mem[i][c]; } /* 0-1 knapsack: Dynamic programming */ int knapsackDP(List wgt, List val, int cap) { int n = wgt.length; // Initialize dp table List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item 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 knapsack: Space-optimized dynamic programming */ int knapsackDPComp(List wgt, List val, int cap) { int n = wgt.length; // Initialize dp table List dp = List.filled(cap + 1, 0); // State transition for (int i = 1; i <= n; i++) { // Traverse in reverse order for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // The larger value between not selecting and selecting item i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ void main() { List wgt = [10, 20, 30, 40, 50]; List val = [50, 120, 150, 210, 240]; int cap = 50; int n = wgt.length; // Brute-force search int res = knapsackDFS(wgt, val, n, cap); print("Maximum item value not exceeding knapsack capacity is $res"); // Memoization search List> mem = List.generate(n + 1, (index) => List.filled(cap + 1, -1)); res = knapsackDFSMem(wgt, val, mem, n, cap); print("Maximum item value not exceeding knapsack capacity is $res"); // Dynamic programming res = knapsackDP(wgt, val, cap); print("Maximum item value not exceeding knapsack capacity is $res"); // Space-optimized dynamic programming res = knapsackDPComp(wgt, val, cap); print("Maximum item value not exceeding knapsack capacity is $res"); } ================================================ FILE: en/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart ================================================ /** * File: min_cost_climbing_stairs_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Minimum cost climbing stairs: Dynamic programming */ int minCostClimbingStairsDP(List cost) { int n = cost.length - 1; if (n == 1 || n == 2) return cost[n]; // Initialize dp table, used to store solutions to subproblems List dp = List.filled(n + 1, 0); // Initial state: preset the solution to the smallest subproblem dp[1] = cost[1]; dp[2] = cost[2]; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* Minimum cost climbing stairs: Space-optimized dynamic programming */ int minCostClimbingStairsDPComp(List cost) { int n = cost.length - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ void main() { List cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; print("Input stair cost list is $cost"); int res = minCostClimbingStairsDP(cost); print("Minimum cost to climb stairs is $res"); res = minCostClimbingStairsDPComp(cost); print("Minimum cost to climb stairs is $res"); } ================================================ FILE: en/codes/dart/chapter_dynamic_programming/min_path_sum.dart ================================================ /** * File: min_path_sum.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Minimum path sum: Brute-force search */ int minPathSumDFS(List> grid, int i, int j) { // If it's the top-left cell, terminate the search if (i == 0 && j == 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { // In Dart, int type is fixed-range integer, no value representing "infinity" return BigInt.from(2).pow(31).toInt(); } // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1) int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); // Return the minimum path cost from top-left to (i, j) return min(left, up) + grid[i][j]; } /* Minimum path sum: Memoization search */ int minPathSumDFSMem(List> grid, List> mem, int i, int j) { // If it's the top-left cell, terminate the search if (i == 0 && j == 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { // In Dart, int type is fixed-range integer, no value representing "infinity" return BigInt.from(2).pow(31).toInt(); } // If there's a record, return it directly if (mem[i][j] != -1) { return mem[i][j]; } // Minimum path cost for left and upper cells int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // Record and return the minimum path cost from top-left to (i, j) mem[i][j] = min(left, up) + grid[i][j]; return mem[i][j]; } /* Minimum path sum: Dynamic programming */ int minPathSumDP(List> grid) { int n = grid.length, m = grid[0].length; // Initialize dp table List> dp = List.generate(n, (i) => List.filled(m, 0)); dp[0][0] = grid[0][0]; // State transition: first row for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // State transition: first column for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // State transition: rest of the rows and columns 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]; } /* Minimum path sum: Space-optimized dynamic programming */ int minPathSumDPComp(List> grid) { int n = grid.length, m = grid[0].length; // Initialize dp table List dp = List.filled(m, 0); dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // State transition: rest of the rows for (int i = 1; i < n; i++) { // State transition: first column dp[0] = dp[0] + grid[i][0]; // State transition: rest of the columns for (int j = 1; j < m; j++) { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ void main() { List> grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; int n = grid.length, m = grid[0].length; // Brute-force search int res = minPathSumDFS(grid, n - 1, m - 1); print("Minimum path sum from top-left to bottom-right is $res"); // Memoization search List> mem = List.generate(n, (i) => List.filled(m, -1)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); print("Minimum path sum from top-left to bottom-right is $res"); // Dynamic programming res = minPathSumDP(grid); print("Minimum path sum from top-left to bottom-right is $res"); // Space-optimized dynamic programming res = minPathSumDPComp(grid); print("Minimum path sum from top-left to bottom-right is $res"); } ================================================ FILE: en/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart ================================================ /** * File: unbounded_knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Unbounded knapsack: Dynamic programming */ int unboundedKnapsackDP(List wgt, List val, int cap) { int n = wgt.length; // Initialize dp table List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item i dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } /* Unbounded knapsack: Space-optimized dynamic programming */ int unboundedKnapsackDPComp(List wgt, List val, int cap) { int n = wgt.length; // Initialize dp table List dp = List.filled(cap + 1, 0); // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[c] = dp[c]; } else { // The larger value between not selecting and selecting item i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ void main() { List wgt = [1, 2, 3]; List val = [5, 11, 15]; int cap = 4; // Dynamic programming int res = unboundedKnapsackDP(wgt, val, cap); print("Maximum item value not exceeding knapsack capacity is $res"); // Space-optimized dynamic programming int resComp = unboundedKnapsackDPComp(wgt, val, cap); print("Maximum item value not exceeding knapsack capacity is $resComp"); } ================================================ FILE: en/codes/dart/chapter_graph/graph_adjacency_list.dart ================================================ /** * File: graph_adjacency_list.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/vertex.dart'; /* Undirected graph class based on adjacency list */ class GraphAdjList { // Adjacency list, key: vertex, value: all adjacent vertices of that vertex Map> adjList = {}; /* Constructor */ GraphAdjList(List> edges) { for (List edge in edges) { addVertex(edge[0]); addVertex(edge[1]); addEdge(edge[0], edge[1]); } } /* Get the number of vertices */ int size() { return adjList.length; } /* Add edge */ void addEdge(Vertex vet1, Vertex vet2) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) { throw ArgumentError; } // Add edge vet1 - vet2 adjList[vet1]!.add(vet2); adjList[vet2]!.add(vet1); } /* Remove edge */ void removeEdge(Vertex vet1, Vertex vet2) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) { throw ArgumentError; } // Remove edge vet1 - vet2 adjList[vet1]!.remove(vet2); adjList[vet2]!.remove(vet1); } /* Add vertex */ void addVertex(Vertex vet) { if (adjList.containsKey(vet)) return; // Add a new linked list in the adjacency list adjList[vet] = []; } /* Remove vertex */ void removeVertex(Vertex vet) { if (!adjList.containsKey(vet)) { throw ArgumentError; } // Remove the linked list corresponding to vertex vet in the adjacency list adjList.remove(vet); // Traverse the linked lists of other vertices and remove all edges containing vet adjList.forEach((key, value) { value.remove(vet); }); } /* Print adjacency list */ void printAdjList() { print("Adjacency list ="); adjList.forEach((key, value) { List tmp = []; for (Vertex vertex in value) { tmp.add(vertex.val); } print("${key.val}: $tmp,"); }); } } /* Driver Code */ void main() { /* Add edge */ List v = Vertex.valsToVets([1, 3, 2, 5, 4]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ]; GraphAdjList graph = GraphAdjList(edges); print("\nAfter initialization, graph is"); graph.printAdjList(); /* Add edge */ // Vertices 1, 3 are v[0], v[1] graph.addEdge(v[0], v[2]); print("\nAfter adding edge 1-2, graph is"); graph.printAdjList(); /* Remove edge */ // Vertex 3 is v[1] graph.removeEdge(v[0], v[1]); print("\nAfter removing edge 1-3, graph is"); graph.printAdjList(); /* Add vertex */ Vertex v5 = Vertex(6); graph.addVertex(v5); print("\nAfter adding vertex 6, graph is"); graph.printAdjList(); /* Remove vertex */ // Vertex 3 is v[1] graph.removeVertex(v[1]); print("\nAfter removing vertex 3, graph is"); graph.printAdjList(); } ================================================ FILE: en/codes/dart/chapter_graph/graph_adjacency_matrix.dart ================================================ /** * File: graph_adjacency_matrix.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* Undirected graph class based on adjacency matrix */ class GraphAdjMat { List vertices = []; // Vertex elements, elements represent "vertex values", indices represent "vertex indices" List> adjMat = []; // Adjacency matrix, where the row and column indices correspond to the "vertex index" /* Constructor */ GraphAdjMat(List vertices, List> edges) { this.vertices = []; this.adjMat = []; // Add vertex for (int val in vertices) { addVertex(val); } // Add edge // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices for (List e in edges) { addEdge(e[0], e[1]); } } /* Get the number of vertices */ int size() { return vertices.length; } /* Add vertex */ void addVertex(int val) { int n = size(); // Add the value of the new vertex to the vertex list vertices.add(val); // Add a row to the adjacency matrix List newRow = List.filled(n, 0, growable: true); adjMat.add(newRow); // Add a column to the adjacency matrix for (List row in adjMat) { row.add(0); } } /* Remove vertex */ void removeVertex(int index) { if (index >= size()) { throw IndexError; } // Remove the vertex at index from the vertex list vertices.removeAt(index); // Remove the row at index from the adjacency matrix adjMat.removeAt(index); // Remove the column at index from the adjacency matrix for (List row in adjMat) { row.removeAt(index); } } /* Add edge */ // Parameters i, j correspond to the vertices element indices void addEdge(int i, int j) { // Handle index out of bounds and equality if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw IndexError; } // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i) adjMat[i][j] = 1; adjMat[j][i] = 1; } /* Remove edge */ // Parameters i, j correspond to the vertices element indices void removeEdge(int i, int j) { // Handle index out of bounds and equality if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw IndexError; } adjMat[i][j] = 0; adjMat[j][i] = 0; } /* Print adjacency matrix */ void printAdjMat() { print("Vertex list = $vertices"); print("Adjacency matrix = "); printMatrix(adjMat); } } /* Driver Code */ void main() { /* Add edge */ // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices List vertices = [1, 3, 2, 5, 4]; List> edges = [ [0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4], ]; GraphAdjMat graph = GraphAdjMat(vertices, edges); print("\nAfter initialization, graph is"); graph.printAdjMat(); /* Add edge */ // Add vertex graph.addEdge(0, 2); print("\nAfter adding edge 1-2, graph is"); graph.printAdjMat(); /* Remove edge */ // Vertices 1, 3 have indices 0, 1 respectively graph.removeEdge(0, 1); print("\nAfter removing edge 1-3, graph is"); graph.printAdjMat(); /* Add vertex */ graph.addVertex(6); print("\nAfter adding vertex 6, graph is"); graph.printAdjMat(); /* Remove vertex */ // Vertex 3 has index 1 graph.removeVertex(1); print("\nAfter removing vertex 3, graph is"); graph.printAdjMat(); } ================================================ FILE: en/codes/dart/chapter_graph/graph_bfs.dart ================================================ /** * File: graph_bfs.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; import '../utils/vertex.dart'; import 'graph_adjacency_list.dart'; /* Breadth-first traversal */ List graphBFS(GraphAdjList graph, Vertex startVet) { // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex // Vertex traversal sequence List res = []; // Hash set for recording vertices that have been visited Set visited = {}; visited.add(startVet); // Queue used to implement BFS Queue que = Queue(); que.add(startVet); // Starting from vertex vet, loop until all vertices are visited while (que.isNotEmpty) { Vertex vet = que.removeFirst(); // Dequeue the front vertex res.add(vet); // Record visited vertex // Traverse all adjacent vertices of this vertex for (Vertex adjVet in graph.adjList[vet]!) { if (visited.contains(adjVet)) { continue; // Skip vertices that have been visited } que.add(adjVet); // Only enqueue unvisited vertices visited.add(adjVet); // Mark this vertex as visited } } // Return vertex traversal sequence return res; } /* Dirver Code */ void main() { /* Add edge */ List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; GraphAdjList graph = GraphAdjList(edges); print("\nAfter initialization, graph is"); graph.printAdjList(); /* Breadth-first traversal */ List res = graphBFS(graph, v[0]); print("\nBreadth-first traversal (BFS) vertex sequence is"); print(Vertex.vetsToVals(res)); } ================================================ FILE: en/codes/dart/chapter_graph/graph_dfs.dart ================================================ /** * File: graph_dfs.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/vertex.dart'; import 'graph_adjacency_list.dart'; /* Depth-first traversal helper function */ void dfs( GraphAdjList graph, Set visited, List res, Vertex vet, ) { res.add(vet); // Record visited vertex visited.add(vet); // Mark this vertex as visited // Traverse all adjacent vertices of this vertex for (Vertex adjVet in graph.adjList[vet]!) { if (visited.contains(adjVet)) { continue; // Skip vertices that have been visited } // Recursively visit adjacent vertices dfs(graph, visited, res, adjVet); } } /* Depth-first traversal */ List graphDFS(GraphAdjList graph, Vertex startVet) { // Vertex traversal sequence List res = []; // Hash set for recording vertices that have been visited Set visited = {}; dfs(graph, visited, res, startVet); return res; } /* Driver Code */ void main() { /* Add edge */ List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; GraphAdjList graph = GraphAdjList(edges); print("\nAfter initialization, graph is"); graph.printAdjList(); /* Depth-first traversal */ List res = graphDFS(graph, v[0]); print("\nDepth-first traversal (DFS) vertex sequence is"); print(Vertex.vetsToVals(res)); } ================================================ FILE: en/codes/dart/chapter_greedy/coin_change_greedy.dart ================================================ /** * File: coin_change_greedy.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Coin change: Greedy algorithm */ int coinChangeGreedy(List coins, int amt) { // Assume coins list is sorted int i = coins.length - 1; int count = 0; // Loop to make greedy choices until no remaining amount while (amt > 0) { // Find the coin that is less than and closest to the remaining amount while (i > 0 && coins[i] > amt) { i--; } // Choose coins[i] amt -= coins[i]; count++; } // If no feasible solution is found, return -1 return amt == 0 ? count : -1; } /* Driver Code */ void main() { // Greedy algorithm: Can guarantee finding the global optimal solution List coins = [1, 5, 10, 20, 50, 100]; int amt = 186; int res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("Minimum coins needed to make $amt is $res"); // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("Minimum coins needed to make $amt is $res"); print("Actually the minimum number needed is 3, i.e., 20 + 20 + 20"); // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("Minimum coins needed to make $amt is $res"); print("Actually the minimum number needed is 2, i.e., 49 + 49"); } ================================================ FILE: en/codes/dart/chapter_greedy/fractional_knapsack.dart ================================================ /** * File: fractional_knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Item */ class Item { int w; // Item weight int v; // Item value Item(this.w, this.v); } /* Fractional knapsack: Greedy algorithm */ double fractionalKnapsack(List wgt, List val, int cap) { // Create item list with two attributes: weight, value List items = List.generate(wgt.length, (i) => Item(wgt[i], val[i])); // Sort by unit value item.v / item.w from high to low items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w)); // Loop for greedy selection double res = 0; for (Item item in items) { if (item.w <= cap) { // If remaining capacity is sufficient, put the entire current item into the knapsack res += item.v; cap -= item.w; } else { // If remaining capacity is insufficient, put part of the current item into the knapsack res += item.v / item.w * cap; // No remaining capacity, so break out of the loop break; } } return res; } /* Driver Code */ void main() { List wgt = [10, 20, 30, 40, 50]; List val = [50, 120, 150, 210, 240]; int cap = 50; // Greedy algorithm double res = fractionalKnapsack(wgt, val, cap); print("Maximum item value not exceeding knapsack capacity is $res"); } ================================================ FILE: en/codes/dart/chapter_greedy/max_capacity.dart ================================================ /** * File: max_capacity.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Max capacity: Greedy algorithm */ int maxCapacity(List ht) { // Initialize i, j to be at both ends of the array int i = 0, j = ht.length - 1; // Initial max capacity is 0 int res = 0; // Loop for greedy selection until the two boards meet while (i < j) { // Update max capacity int cap = min(ht[i], ht[j]) * (j - i); res = max(res, cap); // Move the shorter board inward if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ void main() { List ht = [3, 8, 5, 2, 7, 7, 3, 4]; // Greedy algorithm int res = maxCapacity(ht); print("Maximum capacity is $res"); } ================================================ FILE: en/codes/dart/chapter_greedy/max_product_cutting.dart ================================================ /** * File: max_product_cutting.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Max product cutting: Greedy algorithm */ int maxProductCutting(int n) { // When n <= 3, must cut out a 1 if (n <= 3) { return 1 * (n - 1); } // Greedily cut out 3, a is the number of 3s, b is the remainder int a = n ~/ 3; int b = n % 3; if (b == 1) { // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2 return (pow(3, a - 1) * 2 * 2).toInt(); } if (b == 2) { // When the remainder is 2, do nothing return (pow(3, a) * 2).toInt(); } // When the remainder is 0, do nothing return pow(3, a).toInt(); } /* Driver Code */ void main() { int n = 58; // Greedy algorithm int res = maxProductCutting(n); print("Maximum cutting product is $res"); } ================================================ FILE: en/codes/dart/chapter_hashing/array_hash_map.dart ================================================ /** * File: array_hash_map.dart * Created Time: 2023-03-29 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Key-value pair */ class Pair { int key; String val; Pair(this.key, this.val); } /* Hash table based on array implementation */ class ArrayHashMap { late List _buckets; ArrayHashMap() { // Initialize array with 100 buckets _buckets = List.filled(100, null); } /* Hash function */ int _hashFunc(int key) { final int index = key % 100; return index; } /* Query operation */ String? get(int key) { final int index = _hashFunc(key); final Pair? pair = _buckets[index]; if (pair == null) { return null; } return pair.val; } /* Add operation */ void put(int key, String val) { final Pair pair = Pair(key, val); final int index = _hashFunc(key); _buckets[index] = pair; } /* Remove operation */ void remove(int key) { final int index = _hashFunc(key); _buckets[index] = null; } /* Get all key-value pairs */ List pairSet() { List pairSet = []; for (final Pair? pair in _buckets) { if (pair != null) { pairSet.add(pair); } } return pairSet; } /* Get all keys */ List keySet() { List keySet = []; for (final Pair? pair in _buckets) { if (pair != null) { keySet.add(pair.key); } } return keySet; } /* Get all values */ List values() { List valueSet = []; for (final Pair? pair in _buckets) { if (pair != null) { valueSet.add(pair.val); } } return valueSet; } /* Print hash table */ void printHashMap() { for (final Pair kv in pairSet()) { print("${kv.key} -> ${kv.val}"); } } } /* Driver Code */ void main() { /* Initialize hash table */ final ArrayHashMap map = ArrayHashMap(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(12836, "Xiao Ha"); map.put(15937, "Xiao Luo"); map.put(16750, "Xiao Suan"); map.put(13276, "Xiao Fa"); map.put(10583, "Xiao Ya"); print("\nAfter adding is complete, hash table is\nKey -> Value"); map.printHashMap(); /* Query operation */ // Input key into hash table to get value String? name = map.get(15937); print("\nInput student ID 15937, found name $name"); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(10583); print("\nAfter removing 10583, hash table is\nKey -> Value"); map.printHashMap(); /* Traverse hash table */ print("\nTraverse key-value pairs Key->Value"); map.pairSet().forEach((kv) => print("${kv.key} -> ${kv.val}")); print("\nTraverse keys only Key"); map.keySet().forEach((key) => print("$key")); print("\nTraverse values only Value"); map.values().forEach((val) => print("$val")); } ================================================ FILE: en/codes/dart/chapter_hashing/built_in_hash.dart ================================================ /** * File: built_in_hash.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../chapter_stack_and_queue/linkedlist_deque.dart'; /* Driver Code */ void main() { int _num = 3; int hashNum = _num.hashCode; print("Hash value of integer $_num is $hashNum"); bool bol = true; int hashBol = bol.hashCode; print("Hash value of boolean $bol is $hashBol"); double dec = 3.14159; int hashDec = dec.hashCode; print("Hash value of decimal $dec is $hashDec"); String str = "Hello Algo"; int hashStr = str.hashCode; print("Hash value of string $str is $hashStr"); List arr = [12836, "Xiao Ha"]; int hashArr = arr.hashCode; print("Hash value of array $arr is $hashArr"); ListNode obj = new ListNode(0); int hashObj = obj.hashCode; print("Hash value of node object $obj is $hashObj"); } ================================================ FILE: en/codes/dart/chapter_hashing/hash_map.dart ================================================ /** * File: hash_map.dart * Created Time: 2023-03-29 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Driver Code */ void main() { /* Initialize hash table */ final Map map = {}; /* Add operation */ // Add key-value pair (key, value) to the hash table map[12836] = "Xiao Ha"; map[15937] = "Xiao Luo"; map[16750] = "Xiao Suan"; map[13276] = "Xiao Fa"; map[10583] = "Xiao Ya"; print("\nAfter adding is complete, hash table is\nKey -> Value"); map.forEach((key, value) => print("$key -> $value")); /* Query operation */ // Input key into hash table to get value final String? name = map[15937]; print("\nInput student ID 15937, found name $name"); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(10583); print("\nAfter removing 10583, hash table is\nKey -> Value"); map.forEach((key, value) => print("$key -> $value")); /* Traverse hash table */ print("\nTraverse key-value pairs Key->Value"); map.forEach((key, value) => print("$key -> $value")); print("\nTraverse keys only Key"); map.keys.forEach((key) => print(key)); print("\nTraverse values only Value"); map.forEach((key, value) => print("$value")); map.values.forEach((value) => print(value)); } ================================================ FILE: en/codes/dart/chapter_hashing/hash_map_chaining.dart ================================================ /** * File: hash_map_chaining.dart * Created Time: 2023-06-24 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'array_hash_map.dart'; /* Hash table with separate chaining */ class HashMapChaining { late int size; // Number of key-value pairs late int capacity; // Hash table capacity late double loadThres; // Load factor threshold for triggering expansion late int extendRatio; // Expansion multiplier late List> buckets; // Bucket array /* Constructor */ HashMapChaining() { size = 0; capacity = 4; loadThres = 2.0 / 3.0; extendRatio = 2; buckets = List.generate(capacity, (_) => []); } /* Hash function */ int hashFunc(int key) { return key % capacity; } /* Load factor */ double loadFactor() { return size / capacity; } /* Query operation */ String? get(int key) { int index = hashFunc(key); List bucket = buckets[index]; // Traverse bucket, if key is found, return corresponding val for (Pair pair in bucket) { if (pair.key == key) { return pair.val; } } // If key is not found, return null return null; } /* Add operation */ void put(int key, String val) { // When load factor exceeds threshold, perform expansion if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); List bucket = buckets[index]; // Traverse bucket, if specified key is encountered, update corresponding val and return for (Pair pair in bucket) { if (pair.key == key) { pair.val = val; return; } } // If key does not exist, append key-value pair to the end Pair pair = Pair(key, val); bucket.add(pair); size++; } /* Remove operation */ void remove(int key) { int index = hashFunc(key); List bucket = buckets[index]; // Traverse bucket and remove key-value pair from it for (Pair pair in bucket) { if (pair.key == key) { bucket.remove(pair); size--; break; } } } /* Expand hash table */ void extend() { // Temporarily store the original hash table List> bucketsTmp = buckets; // Initialize expanded new hash table capacity *= extendRatio; buckets = List.generate(capacity, (_) => []); size = 0; // Move key-value pairs from original hash table to new hash table for (List bucket in bucketsTmp) { for (Pair pair in bucket) { put(pair.key, pair.val); } } } /* Print hash table */ void printHashMap() { for (List bucket in buckets) { List res = []; for (Pair pair in bucket) { res.add("${pair.key} -> ${pair.val}"); } print(res); } } } /* Driver Code */ void main() { /* Initialize hash table */ HashMapChaining map = HashMapChaining(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(12836, "Xiao Ha"); map.put(15937, "Xiao Luo"); map.put(16750, "Xiao Suan"); map.put(13276, "Xiao Fa"); map.put(10583, "Xiao Ya"); print("\nAfter adding is complete, hash table is\nKey -> Value"); map.printHashMap(); /* Query operation */ // Input key into hash table to get value String? name = map.get(13276); print("\nInput student ID 13276, found name ${name}"); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(12836); print("\nAfter removing 12836, hash table is\nKey -> Value"); map.printHashMap(); } ================================================ FILE: en/codes/dart/chapter_hashing/hash_map_open_addressing.dart ================================================ /** * File: hash_map_open_addressing.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'array_hash_map.dart'; /* Hash table with open addressing */ class HashMapOpenAddressing { late int _size; // Number of key-value pairs int _capacity = 4; // Hash table capacity double _loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion int _extendRatio = 2; // Expansion multiplier late List _buckets; // Bucket array Pair _TOMBSTONE = Pair(-1, "-1"); // Removal marker /* Constructor */ HashMapOpenAddressing() { _size = 0; _buckets = List.generate(_capacity, (index) => null); } /* Hash function */ int hashFunc(int key) { return key % _capacity; } /* Load factor */ double loadFactor() { return _size / _capacity; } /* Search for bucket index corresponding to key */ int findBucket(int key) { int index = hashFunc(key); int firstTombstone = -1; // Linear probing, break when encountering an empty bucket while (_buckets[index] != null) { // If key is encountered, return the corresponding bucket index if (_buckets[index]!.key == key) { // If a removal marker was encountered before, move the key-value pair to that index if (firstTombstone != -1) { _buckets[firstTombstone] = _buckets[index]; _buckets[index] = _TOMBSTONE; return firstTombstone; // Return the moved bucket index } return index; // Return bucket index } // Record the first removal marker encountered if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) { firstTombstone = index; } // Calculate bucket index, wrap around to the head if past the tail index = (index + 1) % _capacity; } // If key does not exist, return the index for insertion return firstTombstone == -1 ? index : firstTombstone; } /* Query operation */ String? get(int key) { // Search for bucket index corresponding to key int index = findBucket(key); // If key-value pair is found, return corresponding val if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { return _buckets[index]!.val; } // If key-value pair does not exist, return null return null; } /* Add operation */ void put(int key, String val) { // When load factor exceeds threshold, perform expansion if (loadFactor() > _loadThres) { extend(); } // Search for bucket index corresponding to key int index = findBucket(key); // If key-value pair is found, overwrite val and return if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { _buckets[index]!.val = val; return; } // If key-value pair does not exist, add the key-value pair _buckets[index] = new Pair(key, val); _size++; } /* Remove operation */ void remove(int key) { // Search for bucket index corresponding to key int index = findBucket(key); // If key-value pair is found, overwrite it with removal marker if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { _buckets[index] = _TOMBSTONE; _size--; } } /* Expand hash table */ void extend() { // Temporarily store the original hash table List bucketsTmp = _buckets; // Initialize expanded new hash table _capacity *= _extendRatio; _buckets = List.generate(_capacity, (index) => null); _size = 0; // Move key-value pairs from original hash table to new hash table for (Pair? pair in bucketsTmp) { if (pair != null && pair != _TOMBSTONE) { put(pair.key, pair.val); } } } /* Print hash table */ void printHashMap() { for (Pair? pair in _buckets) { if (pair == null) { print("null"); } else if (pair == _TOMBSTONE) { print("TOMBSTONE"); } else { print("${pair.key} -> ${pair.val}"); } } } } /* Driver Code */ void main() { /* Initialize hash table */ HashMapOpenAddressing map = HashMapOpenAddressing(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(12836, "Xiao Ha"); map.put(15937, "Xiao Luo"); map.put(16750, "Xiao Suan"); map.put(13276, "Xiao Fa"); map.put(10583, "Xiao Ya"); print("\nAfter adding is complete, hash table is\nKey -> Value"); map.printHashMap(); /* Query operation */ // Input key into hash table to get value String? name = map.get(13276); print("\nInput student ID 13276, found name $name"); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(16750); print("\nAfter removing 16750, hash table is\nKey -> Value"); map.printHashMap(); } ================================================ FILE: en/codes/dart/chapter_hashing/simple_hash.dart ================================================ /** * File: simple_hash.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Additive hash */ int addHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = (hash + key.codeUnitAt(i)) % MODULUS; } return hash; } /* Multiplicative hash */ int mulHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = (31 * hash + key.codeUnitAt(i)) % MODULUS; } return hash; } /* XOR hash */ int xorHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash ^= key.codeUnitAt(i); } return hash & MODULUS; } /* Rotational hash */ int rotHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS; } return hash; } /* Dirver Code */ void main() { String key = "Hello Algo"; int hash = addHash(key); print("Additive hash value is $hash"); hash = mulHash(key); print("Multiplicative hash value is $hash"); hash = xorHash(key); print("XOR hash value is $hash"); hash = rotHash(key); print("Rotational hash value is $hash"); } ================================================ FILE: en/codes/dart/chapter_heap/my_heap.dart ================================================ /** * File: my_heap.dart * Created Time: 2023-04-09 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* Max heap */ class MaxHeap { late List _maxHeap; /* Constructor, build heap based on input list */ MaxHeap(List nums) { // Add list elements to heap as is _maxHeap = nums; // Heapify all nodes except leaf nodes for (int i = _parent(size() - 1); i >= 0; i--) { siftDown(i); } } /* Get index of left child node */ int _left(int i) { return 2 * i + 1; } /* Get index of right child node */ int _right(int i) { return 2 * i + 2; } /* Get index of parent node */ int _parent(int i) { return (i - 1) ~/ 2; // Floor division } /* Swap elements */ void _swap(int i, int j) { int tmp = _maxHeap[i]; _maxHeap[i] = _maxHeap[j]; _maxHeap[j] = tmp; } /* Get heap size */ int size() { return _maxHeap.length; } /* Check if heap is empty */ bool isEmpty() { return size() == 0; } /* Access top element */ int peek() { return _maxHeap[0]; } /* Element enters heap */ void push(int val) { // Add node _maxHeap.add(val); // Heapify from bottom to top siftUp(size() - 1); } /* Starting from node i, heapify from bottom to top */ void siftUp(int i) { while (true) { // Get parent node of node i int p = _parent(i); // When "crossing root node" or "node needs no repair", end heapify if (p < 0 || _maxHeap[i] <= _maxHeap[p]) { break; } // Swap two nodes _swap(i, p); // Loop upward heapify i = p; } } /* Element exits heap */ int pop() { // Handle empty case if (isEmpty()) throw Exception('Heap is empty'); // Delete node _swap(0, size() - 1); // Remove node int val = _maxHeap.removeLast(); // Return top element siftDown(0); // Return heap top element return val; } /* Starting from node i, heapify from top to bottom */ void siftDown(int i) { while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break int l = _left(i); int r = _right(i); int ma = i; if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l; if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r; // Swap two nodes if (ma == i) break; // Swap two nodes _swap(i, ma); // Loop downwards heapification i = ma; } } /* Driver Code */ void print() { printHeap(_maxHeap); } } /* Driver Code */ void main() { /* Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap */ MaxHeap maxHeap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); print("\nAfter inputting list and building heap"); maxHeap.print(); /* Check if heap is empty */ int peek = maxHeap.peek(); print("\nHeap top element is $peek"); /* Element enters heap */ int val = 7; maxHeap.push(val); print("\nAfter element $val pushes to heap"); maxHeap.print(); /* Time complexity is O(n), not O(nlogn) */ peek = maxHeap.pop(); print("\nAfter heap top element $peek pops from heap"); maxHeap.print(); /* Get heap size */ int size = maxHeap.size(); print("\nHeap size is $size"); /* Check if heap is empty */ bool isEmpty = maxHeap.isEmpty(); print("\nIs heap empty $isEmpty"); } ================================================ FILE: en/codes/dart/chapter_heap/top_k.dart ================================================ /** * File: top_k.dart * Created Time: 2023-08-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* Find the largest k elements in array based on heap */ MinHeap topKHeap(List nums, int k) { // Initialize min heap, push first k elements of array to heap MinHeap heap = MinHeap(nums.sublist(0, k)); // Starting from the (k+1)th element, maintain heap length as k for (int i = k; i < nums.length; i++) { // If current element is greater than top element, top element exits heap, current element enters heap if (nums[i] > heap.peek()) { heap.pop(); heap.push(nums[i]); } } return heap; } /* Driver Code */ void main() { List nums = [1, 7, 6, 3, 2]; int k = 3; MinHeap res = topKHeap(nums, k); print("The largest $k elements are"); res.print(); } /* Min heap */ class MinHeap { late List _minHeap; /* Constructor, build heap based on input list */ MinHeap(List nums) { // Add list elements to heap as is _minHeap = nums; // Heapify all nodes except leaf nodes for (int i = _parent(size() - 1); i >= 0; i--) { siftDown(i); } } /* Return elements in heap */ List getHeap() { return _minHeap; } /* Get index of left child node */ int _left(int i) { return 2 * i + 1; } /* Get index of right child node */ int _right(int i) { return 2 * i + 2; } /* Get index of parent node */ int _parent(int i) { return (i - 1) ~/ 2; // Floor division } /* Swap elements */ void _swap(int i, int j) { int tmp = _minHeap[i]; _minHeap[i] = _minHeap[j]; _minHeap[j] = tmp; } /* Get heap size */ int size() { return _minHeap.length; } /* Check if heap is empty */ bool isEmpty() { return size() == 0; } /* Access top element */ int peek() { return _minHeap[0]; } /* Element enters heap */ void push(int val) { // Add node _minHeap.add(val); // Heapify from bottom to top siftUp(size() - 1); } /* Starting from node i, heapify from bottom to top */ void siftUp(int i) { while (true) { // Get parent node of node i int p = _parent(i); // When "crossing root node" or "node needs no repair", end heapify if (p < 0 || _minHeap[i] >= _minHeap[p]) { break; } // Swap two nodes _swap(i, p); // Loop upward heapify i = p; } } /* Element exits heap */ int pop() { // Handle empty case if (isEmpty()) throw Exception('Heap is empty'); // Delete node _swap(0, size() - 1); // Remove node int val = _minHeap.removeLast(); // Return top element siftDown(0); // Return heap top element return val; } /* Starting from node i, heapify from top to bottom */ void siftDown(int i) { while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break int l = _left(i); int r = _right(i); int mi = i; if (l < size() && _minHeap[l] < _minHeap[mi]) mi = l; if (r < size() && _minHeap[r] < _minHeap[mi]) mi = r; // Swap two nodes if (mi == i) break; // Swap two nodes _swap(i, mi); // Loop downwards heapification i = mi; } } /* Driver Code */ void print() { printHeap(_minHeap); } } ================================================ FILE: en/codes/dart/chapter_searching/binary_search.dart ================================================ /** * File: binary_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* Binary search (closed interval on both sides) */ int binarySearch(List nums, int target) { // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array int i = 0, j = nums.length - 1; // Loop, exit when the search interval is empty (empty when i > j) while (i <= j) { int m = i + (j - i) ~/ 2; // Calculate the midpoint index m if (nums[m] < target) { // This means target is in the interval [m+1, j] i = m + 1; } else if (nums[m] > target) { // This means target is in the interval [i, m-1] j = m - 1; } else { // Found the target element, return its index return m; } } // Target element not found, return -1 return -1; } /* Binary search (left-closed right-open interval) */ int binarySearchLCRO(List nums, int target) { // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1 int i = 0, j = nums.length; // Loop, exit when the search interval is empty (empty when i = j) while (i < j) { int m = i + (j - i) ~/ 2; // Calculate the midpoint index m if (nums[m] < target) { // This means target is in the interval [m+1, j) i = m + 1; } else if (nums[m] > target) { // This means target is in the interval [i, m) j = m; } else { // Found the target element, return its index return m; } } // Target element not found, return -1 return -1; } /* Driver Code*/ void main() { int target = 6; final nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* Binary search (closed interval) */ int index = binarySearch(nums, target); print('Index of target element 6 = $index'); /* Binary search (left-closed right-open interval) */ index = binarySearchLCRO(nums, target); print('Index of target element 6 = $index'); } ================================================ FILE: en/codes/dart/chapter_searching/binary_search_edge.dart ================================================ /** * File: binary_search_edge.dart * Created Time: 2023-08-14 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'binary_search_insertion.dart'; /* Binary search for the leftmost target */ int binarySearchLeftEdge(List nums, int target) { // Equivalent to finding the insertion point of target int i = binarySearchInsertion(nums, target); // Target not found, return -1 if (i == nums.length || nums[i] != target) { return -1; } // Found target, return index i return i; } /* Binary search for the rightmost target */ int binarySearchRightEdge(List nums, int target) { // Convert to finding the leftmost target + 1 int i = binarySearchInsertion(nums, target + 1); // j points to the rightmost target, i points to the first element greater than target int j = i - 1; // Target not found, return -1 if (j == -1 || nums[j] != target) { return -1; } // Found target, return index j return j; } /* Driver Code */ void main() { // Array with duplicate elements List nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; print("\nArray nums = $nums"); // Binary search left and right boundaries for (int target in [6, 7]) { int index = binarySearchLeftEdge(nums, target); print("Leftmost element $target index is $index"); index = binarySearchRightEdge(nums, target); print("Rightmost element $target index is $index"); } } ================================================ FILE: en/codes/dart/chapter_searching/binary_search_insertion.dart ================================================ /** * File: binary_search_insertion.dart * Created Time: 2023-08-14 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Binary search for insertion point (no duplicate elements) */ int binarySearchInsertionSimple(List nums, int target) { int i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1] while (i <= j) { int m = i + (j - i) ~/ 2; // Calculate the midpoint index m if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { return m; // Found target, return insertion point m } } // Target not found, return insertion point i return i; } /* Binary search for insertion point (with duplicate elements) */ int binarySearchInsertion(List nums, int target) { int i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1] while (i <= j) { int m = i + (j - i) ~/ 2; // Calculate the midpoint index m if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { j = m - 1; // The first element less than target is in the interval [i, m-1] } } // Return insertion point i return i; } /* Driver Code */ void main() { // Array without duplicate elements List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; print("\nArray nums = $nums"); // Binary search for insertion point for (int target in [6, 9]) { int index = binarySearchInsertionSimple(nums, target); print("Insertion point index for element $target is $index"); } // Array with duplicate elements nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; print("\nArray nums = $nums"); // Binary search for insertion point for (int target in [2, 6, 20]) { int index = binarySearchInsertion(nums, target); print("Insertion point index for element $target is $index"); } } ================================================ FILE: en/codes/dart/chapter_searching/hashing_search.dart ================================================ /** * File: hashing_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:collection'; import '../utils/list_node.dart'; /* Hash search (array) */ int hashingSearchArray(Map map, int target) { // Hash table's key: target element, value: index // If this key does not exist in the hash table, return -1 if (!map.containsKey(target)) { return -1; } return map[target]!; } /* Hash search (linked list) */ ListNode? hashingSearchLinkedList(Map map, int target) { // Hash table key: target node value, value: node object // If key is not in hash table, return null if (!map.containsKey(target)) { return null; } return map[target]!; } /* Driver Code */ void main() { int target = 3; /* Hash search (array) */ List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // Initialize hash table Map map = HashMap(); for (int i = 0; i < nums.length; i++) { map.putIfAbsent(nums[i], () => i); // key: element, value: index } int index = hashingSearchArray(map, target); print('Index of target element 3 = $index'); /* Hash search (linked list) */ ListNode? head = listToLinkedList(nums); // Initialize hash table Map map1 = HashMap(); while (head != null) { map1.putIfAbsent(head.val, () => head!); // key: node value, value: node head = head.next; } ListNode? node = hashingSearchLinkedList(map1, target); print('Node object corresponding to target node value 3 is $node'); } ================================================ FILE: en/codes/dart/chapter_searching/linear_search.dart ================================================ /** * File: linear_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import '../utils/list_node.dart'; /* Linear search (array) */ int linearSearchArray(List nums, int target) { // Traverse array for (int i = 0; i < nums.length; i++) { // Found the target element, return its index if (nums[i] == target) { return i; } } // Target element not found, return -1 return -1; } /* Linear search (linked list) */ ListNode? linearSearchList(ListNode? head, int target) { // Traverse the linked list while (head != null) { // Found the target node, return it if (head.val == target) return head; head = head.next; } // Target element not found, return null return null; } /* Driver Code */ void main() { int target = 3; /* Perform linear search in array */ List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; int index = linearSearchArray(nums, target); print('Index of target element 3 = $index'); /* Perform linear search in linked list */ ListNode? head = listToLinkedList(nums); ListNode? node = linearSearchList(head, target); print('Node object corresponding to target node value 3 is $node'); } ================================================ FILE: en/codes/dart/chapter_searching/two_sum.dart ================================================ /** * File: two_sum.dart * Created Time: 2023-2-11 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:collection'; /* Method 1: Brute force enumeration */ List twoSumBruteForce(List nums, int target) { int size = nums.length; // Two nested loops, time complexity is O(n^2) for (var i = 0; i < size - 1; i++) { for (var j = i + 1; j < size; j++) { if (nums[i] + nums[j] == target) return [i, j]; } } return [0]; } /* Method 2: Auxiliary hash table */ List twoSumHashTable(List nums, int target) { int size = nums.length; // Auxiliary hash table, space complexity is O(n) Map dic = HashMap(); // Single loop, time complexity is O(n) for (var i = 0; i < size; i++) { if (dic.containsKey(target - nums[i])) { return [dic[target - nums[i]]!, i]; } dic.putIfAbsent(nums[i], () => i); } return [0]; } /* Driver Code */ void main() { // ======= Test Case ======= List nums = [2, 7, 11, 15]; int target = 13; // ====== Driver Code ====== // Method 1 List res = twoSumBruteForce(nums, target); print('Method 1 res = $res'); // Method 2 res = twoSumHashTable(nums, target); print('Method 2 res = $res'); } ================================================ FILE: en/codes/dart/chapter_sorting/bubble_sort.dart ================================================ /** * File: bubble_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* Bubble sort */ void bubbleSort(List nums) { // Outer loop: unsorted range is [0, i] for (int i = nums.length - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* Bubble sort (flag optimization) */ void bubbleSortWithFlag(List nums) { // Outer loop: unsorted range is [0, i] for (int i = nums.length - 1; i > 0; i--) { bool flag = false; // Initialize flag // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // Record element swap } } if (!flag) break; // No elements were swapped in this round of "bubbling", exit directly } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); print("After bubble sort, nums = $nums"); List nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); print("After bubble sort, nums1 = $nums1"); } ================================================ FILE: en/codes/dart/chapter_sorting/bucket_sort.dart ================================================ /** * File: bucket_sort.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* Bucket sort */ void bucketSort(List nums) { // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket int k = nums.length ~/ 2; List> buckets = List.generate(k, (index) => []); // 1. Distribute array elements into various buckets for (double _num in nums) { // Input data range is [0, 1), use _num * k to map to index range [0, k-1] int i = (_num * k).toInt(); // Add _num to bucket bucket_idx buckets[i].add(_num); } // 2. Sort each bucket for (List bucket in buckets) { bucket.sort(); } // 3. Traverse buckets to merge results int i = 0; for (List bucket in buckets) { for (double _num in bucket) { nums[i++] = _num; } } } /* Driver Code*/ void main() { // Assume input data is floating point, interval [0, 1) final nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); print('After bucket sort, nums = $nums'); } ================================================ FILE: en/codes/dart/chapter_sorting/counting_sort.dart ================================================ /** * File: counting_sort.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:math'; /* Counting sort */ // Simple implementation, cannot be used for sorting objects void countingSortNaive(List nums) { // 1. Count the maximum element m in the array int m = 0; for (int _num in nums) { m = max(m, _num); } // 2. Count the occurrence of each number // counter[_num] represents occurrence count of _num List counter = List.filled(m + 1, 0); for (int _num in nums) { counter[_num]++; } // 3. Traverse counter, filling each element back into the original array nums int i = 0; for (int _num = 0; _num < m + 1; _num++) { for (int j = 0; j < counter[_num]; j++, i++) { nums[i] = _num; } } } /* Counting sort */ // Complete implementation, can sort objects and is a stable sort void countingSort(List nums) { // 1. Count the maximum element m in the array int m = 0; for (int _num in nums) { m = max(m, _num); } // 2. Count the occurrence of each number // counter[_num] represents occurrence count of _num List counter = List.filled(m + 1, 0); for (int _num in nums) { counter[_num]++; } // 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" // That is, counter[_num]-1 is the last occurrence index of _num in res for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. Traverse nums in reverse order, placing each element into the result array res // Initialize the array res to record results int n = nums.length; List res = List.filled(n, 0); for (int i = n - 1; i >= 0; i--) { int _num = nums[i]; res[counter[_num] - 1] = _num; // Place _num at corresponding index counter[_num]--; // Decrement prefix sum by 1 to get next placement index for _num } // Use result array res to overwrite the original array nums nums.setAll(0, res); } /* Driver Code*/ void main() { final nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); print('After counting sort (cannot sort objects), nums = $nums'); final nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); print('After counting sort, nums1 = $nums1'); } ================================================ FILE: en/codes/dart/chapter_sorting/heap_sort.dart ================================================ /** * File: heap_sort.dart * Created Time: 2023-06-01 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Heap length is n, start heapifying node i, from top to bottom */ void siftDown(List nums, int n, int i) { while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break 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; // Swap two nodes if (ma == i) break; // Swap two nodes int temp = nums[i]; nums[i] = nums[ma]; nums[ma] = temp; // Loop downwards heapification i = ma; } } /* Heap sort */ void heapSort(List nums) { // Build heap operation: heapify all nodes except leaves for (int i = nums.length ~/ 2 - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // Extract the largest element from the heap and repeat for n-1 rounds for (int i = nums.length - 1; i > 0; i--) { // Delete node int tmp = nums[0]; nums[0] = nums[i]; nums[i] = tmp; // Start heapifying the root node, from top to bottom siftDown(nums, i, 0); } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; heapSort(nums); print("After heap sort, nums = $nums"); } ================================================ FILE: en/codes/dart/chapter_sorting/insertion_sort.dart ================================================ /** * File: insertion_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* Insertion sort */ void insertionSort(List nums) { // Outer loop: sorted interval is [0, i-1] for (int i = 1; i < nums.length; i++) { int base = nums[i], j = i - 1; // Inner loop: insert base into the correct position within the sorted interval [0, i-1] while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // Move nums[j] to the right by one position j--; } nums[j + 1] = base; // Assign base to the correct position } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); print("After insertion sort, nums = $nums"); } ================================================ FILE: en/codes/dart/chapter_sorting/merge_sort.dart ================================================ /** * File: merge_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* Merge left subarray and right subarray */ void merge(List nums, int left, int mid, int right) { // Left subarray interval is [left, mid], right subarray interval is [mid+1, right] // Create a temporary array tmp to store the merged results List tmp = List.filled(right - left + 1, 0); // Initialize the start indices of the left and right subarrays int i = left, j = mid + 1, k = 0; // While both subarrays still have elements, compare and copy the smaller element into the temporary array while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } // Copy the remaining elements of the left and right subarrays into the temporary array while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* Merge sort */ void mergeSort(List nums, int left, int right) { // Termination condition if (left >= right) return; // Terminate recursion when subarray length is 1 // Divide and conquer stage int mid = left + (right - left) ~/ 2; // Calculate midpoint mergeSort(nums, left, mid); // Recursively process the left subarray mergeSort(nums, mid + 1, right); // Recursively process the right subarray // Merge stage merge(nums, left, mid, right); } /* Driver Code */ void main() { /* Merge sort */ List nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); print("After merge sort, nums = $nums"); } ================================================ FILE: en/codes/dart/chapter_sorting/quick_sort.dart ================================================ /** * File: quick_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* Quick sort class */ class QuickSort { /* Swap elements */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Sentinel partition */ static int _partition(List nums, int left, int right) { // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot _swap(nums, i, j); // Swap these two elements } _swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort */ static void quickSort(List nums, int left, int right) { // Terminate recursion when subarray length is 1 if (left >= right) return; // Sentinel partition int pivot = _partition(nums, left, right); // Recursively process the left subarray and right subarray quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } /* Quick sort class (median pivot optimization) */ class QuickSortMedian { /* Swap elements */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Select the median of three candidate elements */ static int _medianThree(List nums, int left, int mid, int right) { int l = nums[left], m = nums[mid], r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // m is between l and r if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l is between m and r return right; } /* Sentinel partition (median of three) */ static int _partition(List nums, int left, int right) { // Select the median of three candidate elements int med = _medianThree(nums, left, (left + right) ~/ 2, right); // Swap the median to the array's leftmost position _swap(nums, left, med); // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot _swap(nums, i, j); // Swap these two elements } _swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort */ static void quickSort(List nums, int left, int right) { // Terminate recursion when subarray length is 1 if (left >= right) return; // Sentinel partition int pivot = _partition(nums, left, right); // Recursively process the left subarray and right subarray quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } /* Quick sort class (recursion depth optimization) */ class QuickSortTailCall { /* Swap elements */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Sentinel partition */ static int _partition(List nums, int left, int right) { // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot _swap(nums, i, j); // Swap these two elements } _swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort (recursion depth optimization) */ static void quickSort(List nums, int left, int right) { // Terminate when subarray length is 1 while (left < right) { // Sentinel partition operation int pivot = _partition(nums, left, right); // Perform quick sort on the shorter of the two subarrays if (pivot - left < right - pivot) { quickSort(nums, left, pivot - 1); // Recursively sort the left subarray left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right] } else { quickSort(nums, pivot + 1, right); // Recursively sort the right subarray right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1] } } } } /* Driver Code */ void main() { /* Quick sort */ List nums = [2, 4, 1, 0, 3, 5]; QuickSort.quickSort(nums, 0, nums.length - 1); print("After quick sort, nums = $nums"); /* Quick sort (recursion depth optimization) */ List nums1 = [2, 4, 1, 0, 3, 5]; QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); print("After quick sort (median pivot optimization), nums1 = $nums1"); /* Quick sort (recursion depth optimization) */ List nums2 = [2, 4, 1, 0, 3, 5]; QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); print("After quick sort (recursion depth optimization), nums2 = $nums2"); } ================================================ FILE: en/codes/dart/chapter_sorting/radix_sort.dart ================================================ /** * File: radix_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* Get k-th digit of element _num, where exp = 10^(k-1) */ int digit(int _num, int exp) { // Passing exp instead of k can avoid repeated expensive exponentiation here return (_num ~/ exp) % 10; } /* Counting sort (based on nums k-th digit) */ void countingSortDigit(List nums, int exp) { // Decimal digit range is 0~9, therefore need a bucket array of length 10 List counter = List.filled(10, 0); int n = nums.length; // Count the occurrence of digits 0~9 for (int i = 0; i < n; i++) { int d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d counter[d]++; // Count the occurrence of digit d } // Calculate prefix sum, converting "occurrence count" into "array index" for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // Traverse in reverse, based on bucket statistics, place each element into res List res = List.filled(n, 0); for (int i = n - 1; i >= 0; i--) { int d = digit(nums[i], exp); int j = counter[d] - 1; // Get the index j for d in the array res[j] = nums[i]; // Place the current element at index j counter[d]--; // Decrease the count of d by 1 } // Use result to overwrite the original array nums for (int i = 0; i < n; i++) nums[i] = res[i]; } /* Radix sort */ void radixSort(List nums) { // Get the maximum element of the array, used to determine the maximum number of digits // In Dart, int length is 64 bits int m = -1 << 63; for (int _num in nums) if (_num > m) m = _num; // Traverse from the lowest to the highest digit for (int exp = 1; exp <= m; exp *= 10) // Perform counting sort on the k-th digit of array elements // k = 1 -> exp = 1 // k = 2 -> exp = 10 // i.e., exp = 10^(k-1) countingSortDigit(nums, exp); } /* Driver Code */ void main() { // Radix sort List nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ]; radixSort(nums); print("After radix sort, nums = $nums"); } ================================================ FILE: en/codes/dart/chapter_sorting/selection_sort.dart ================================================ /** * File: selection_sort.dart * Created Time: 2023-06-01 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Selection sort */ void selectionSort(List nums) { int n = nums.length; // Outer loop: unsorted interval is [i, n-1] for (int i = 0; i < n - 1; i++) { // Inner loop: find the smallest element within the unsorted interval int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // Record the index of the smallest element } // Swap the smallest element with the first element of the unsorted interval int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; selectionSort(nums); print("After selection sort, nums = $nums"); } ================================================ FILE: en/codes/dart/chapter_stack_and_queue/array_deque.dart ================================================ /** * File: array_deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Double-ended queue based on circular array implementation */ class ArrayDeque { late List _nums; // Array for storing double-ended queue elements late int _front; // Front pointer, points to the front of the queue element late int _queSize; // Double-ended queue length /* Constructor */ ArrayDeque(int capacity) { this._nums = List.filled(capacity, 0); this._front = this._queSize = 0; } /* Get the capacity of the double-ended queue */ int capacity() { return _nums.length; } /* Get the length of the double-ended queue */ int size() { return _queSize; } /* Check if the double-ended queue is empty */ bool isEmpty() { return _queSize == 0; } /* Calculate circular array index */ int index(int i) { // Use modulo operation to wrap the array head and tail together // When i passes the tail of the array, return to the head // When i passes the head of the array, return to the tail return (i + capacity()) % capacity(); } /* Front of the queue enqueue */ void pushFirst(int _num) { if (_queSize == capacity()) { throw Exception("Double-ended queue is full"); } // Use modulo operation to wrap front around to the tail after passing the head of the array // Use modulo operation to wrap _front from array head back to tail _front = index(_front - 1); // Add _num to queue front _nums[_front] = _num; _queSize++; } /* Rear of the queue enqueue */ void pushLast(int _num) { if (_queSize == capacity()) { throw Exception("Double-ended queue is full"); } // Use modulo operation to wrap rear around to the head after passing the tail of the array int rear = index(_front + _queSize); // Add _num to queue rear _nums[rear] = _num; _queSize++; } /* Rear of the queue dequeue */ int popFirst() { int _num = peekFirst(); // Move front pointer right by one _front = index(_front + 1); _queSize--; return _num; } /* Access rear of the queue element */ int popLast() { int _num = peekLast(); _queSize--; return _num; } /* Return list for printing */ int peekFirst() { if (isEmpty()) { throw Exception("Deque is empty"); } return _nums[_front]; } /* Driver Code */ int peekLast() { if (isEmpty()) { throw Exception("Deque is empty"); } // Initialize double-ended queue int last = index(_front + _queSize - 1); return _nums[last]; } /* Return array for printing */ List toArray() { // Elements enqueue List res = List.filled(_queSize, 0); for (int i = 0, j = _front; i < _queSize; i++, j++) { res[i] = _nums[index(j)]; } return res; } } /* Driver Code */ void main() { /* Get the length of the double-ended queue */ final ArrayDeque deque = ArrayDeque(10); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); print("Deque deque = ${deque.toArray()}"); /* Update element */ final int peekFirst = deque.peekFirst(); print("Front element peekFirst = $peekFirst"); final int peekLast = deque.peekLast(); print("Rear element peekLast = $peekLast"); /* Elements enqueue */ deque.pushLast(4); print("After element 4 enqueues at rear, deque = ${deque.toArray()}"); deque.pushFirst(1); print("After element 1 enqueues at front, deque = ${deque.toArray()}"); /* Element dequeue */ final int popLast = deque.popLast(); print("Dequeue rear element = $popLast, after rear dequeue deque = ${deque.toArray()}"); final int popFirst = deque.popFirst(); print("Dequeue front element = $popFirst, after front dequeue deque = ${deque.toArray()}"); /* Get the length of the double-ended queue */ final int size = deque.size(); print("Deque length size = $size"); /* Check if the double-ended queue is empty */ final bool isEmpty = deque.isEmpty(); print("Is deque empty = $isEmpty"); } ================================================ FILE: en/codes/dart/chapter_stack_and_queue/array_queue.dart ================================================ /** * File: array_queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Queue based on circular array implementation */ class ArrayQueue { late List _nums; // Array for storing queue elements late int _front; // Front pointer, points to the front of the queue element late int _queSize; // Queue length ArrayQueue(int capacity) { _nums = List.filled(capacity, 0); _front = _queSize = 0; } /* Get the capacity of the queue */ int capaCity() { return _nums.length; } /* Get the length of the queue */ int size() { return _queSize; } /* Check if the queue is empty */ bool isEmpty() { return _queSize == 0; } /* Enqueue */ void push(int _num) { if (_queSize == capaCity()) { throw Exception("Queue is full"); } // Use modulo operation to wrap rear around to the head after passing the tail of the array // Add num to the rear of the queue int rear = (_front + _queSize) % capaCity(); // Add _num to queue rear _nums[rear] = _num; _queSize++; } /* Dequeue */ int pop() { int _num = peek(); // Move front pointer backward by one position, if it passes the tail, return to array head _front = (_front + 1) % capaCity(); _queSize--; return _num; } /* Return list for printing */ int peek() { if (isEmpty()) { throw Exception("Queue is empty"); } return _nums[_front]; } /* Return Array */ List toArray() { // Elements enqueue final List res = List.filled(_queSize, 0); for (int i = 0, j = _front; i < _queSize; i++, j++) { res[i] = _nums[j % capaCity()]; } return res; } } /* Driver Code */ void main() { /* Access front of the queue element */ final int capacity = 10; final ArrayQueue queue = ArrayQueue(capacity); /* Elements enqueue */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print("Queue queue = ${queue.toArray()}"); /* Return list for printing */ final int peek = queue.peek(); print("Front element peek = $peek"); /* Element dequeue */ final int pop = queue.pop(); print("Dequeue element pop = $pop, after dequeue queue = ${queue.toArray()}"); /* Get queue length */ final int size = queue.size(); print("Queue length size = $size"); /* Check if the queue is empty */ final bool isEmpty = queue.isEmpty(); print("Is queue empty = $isEmpty"); /* Test circular array */ for (int i = 0; i < 10; i++) { queue.push(i); queue.pop(); print("After round $i enqueue + dequeue, queue = ${queue.toArray()}"); } } ================================================ FILE: en/codes/dart/chapter_stack_and_queue/array_stack.dart ================================================ /** * File: array_stack.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Stack based on array implementation */ class ArrayStack { late List _stack; ArrayStack() { _stack = []; } /* Get the length of the stack */ int size() { return _stack.length; } /* Check if the stack is empty */ bool isEmpty() { return _stack.isEmpty; } /* Push */ void push(int _num) { _stack.add(_num); } /* Pop */ int pop() { if (isEmpty()) { throw Exception("Stack is empty"); } return _stack.removeLast(); } /* Return list for printing */ int peek() { if (isEmpty()) { throw Exception("Stack is empty"); } return _stack.last; } /* Convert stack to Array and return */ List toArray() => _stack; } /* Driver Code */ void main() { /* Access top of the stack element */ final ArrayStack stack = ArrayStack(); /* Elements push onto stack */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print("Stack stack = ${stack.toArray()}"); /* Return list for printing */ final int peek = stack.peek(); print("Top element peek = $peek"); /* Element pop from stack */ final int pop = stack.pop(); print("Pop element pop = $pop, after pop stack = ${stack.toArray()}"); /* Get the length of the stack */ final int size = stack.size(); print("Stack length size = $size"); /* Check if empty */ final bool isEmpty = stack.isEmpty(); print("Is stack empty = $isEmpty"); } ================================================ FILE: en/codes/dart/chapter_stack_and_queue/deque.dart ================================================ /** * File: deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; void main() { /* Get the length of the double-ended queue */ final Queue deque = Queue(); deque.addFirst(3); deque.addLast(2); deque.addLast(5); print("Deque deque = $deque"); /* Update element */ final int peekFirst = deque.first; print("Front element peekFirst = $peekFirst"); final int peekLast = deque.last; print("Rear element peekLast = $peekLast"); /* Elements enqueue */ deque.addLast(4); print("After element 4 enqueues at rear, deque = $deque"); deque.addFirst(1); print("After element 1 enqueues at front, deque = $deque"); /* Element dequeue */ final int popLast = deque.removeLast(); print("Dequeue rear element = $popLast, after rear dequeue deque = $deque"); final int popFirst = deque.removeFirst(); print("Dequeue front element = $popFirst, after front dequeue deque = $deque"); /* Get the length of the double-ended queue */ final int size = deque.length; print("Deque length size = $size"); /* Check if the double-ended queue is empty */ final bool isEmpty = deque.isEmpty; print("Is deque empty = $isEmpty"); } ================================================ FILE: en/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart ================================================ /** * File: linkedlist_deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Doubly linked list node */ class ListNode { int val; // Node value ListNode? next; // Successor node reference ListNode? prev; // Predecessor node reference ListNode(this.val, {this.next, this.prev}); } /* Deque implemented based on doubly linked list */ class LinkedListDeque { late ListNode? _front; // Head node _front late ListNode? _rear; // Tail node _rear int _queSize = 0; // Length of the double-ended queue LinkedListDeque() { this._front = null; this._rear = null; } /* Get deque length */ int size() { return this._queSize; } /* Check if the double-ended queue is empty */ bool isEmpty() { return size() == 0; } /* Enqueue operation */ void push(int _num, bool isFront) { final ListNode node = ListNode(_num); if (isEmpty()) { // If list is empty, let both _front and _rear point to node _front = _rear = node; } else if (isFront) { // Front of the queue enqueue operation // Add node to the head of the linked list _front!.prev = node; node.next = _front; _front = node; // Update head node } else { // Rear of the queue enqueue operation // Add node to the tail of the linked list _rear!.next = node; node.prev = _rear; _rear = node; // Update tail node } _queSize++; // Update queue length } /* Front of the queue enqueue */ void pushFirst(int _num) { push(_num, true); } /* Rear of the queue enqueue */ void pushLast(int _num) { push(_num, false); } /* Dequeue operation */ int? pop(bool isFront) { // If queue is empty, return null directly if (isEmpty()) { return null; } final int val; if (isFront) { // Temporarily store head node value val = _front!.val; // Delete head node // Delete head node ListNode? fNext = _front!.next; if (fNext != null) { fNext.prev = null; _front!.next = null; } _front = fNext; // Update head node } else { // Temporarily store tail node value val = _rear!.val; // Delete tail node // Update tail node ListNode? rPrev = _rear!.prev; if (rPrev != null) { rPrev.next = null; _rear!.prev = null; } _rear = rPrev; // Update tail node } _queSize--; // Update queue length return val; } /* Rear of the queue dequeue */ int? popFirst() { return pop(true); } /* Access rear of the queue element */ int? popLast() { return pop(false); } /* Return list for printing */ int? peekFirst() { return _front?.val; } /* Driver Code */ int? peekLast() { return _rear?.val; } /* Return array for printing */ List toArray() { ListNode? node = _front; final List res = []; for (int i = 0; i < _queSize; i++) { res.add(node!.val); node = node.next; } return res; } } /* Driver Code */ void main() { /* Get the length of the double-ended queue */ final LinkedListDeque deque = LinkedListDeque(); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); print("Deque deque = ${deque.toArray()}"); /* Update element */ int? peekFirst = deque.peekFirst(); print("Front element peekFirst = $peekFirst"); int? peekLast = deque.peekLast(); print("Rear element peekLast = $peekLast"); /* Elements enqueue */ deque.pushLast(4); print("After element 4 enqueues at rear, deque = ${deque.toArray()}"); deque.pushFirst(1); print("After element 1 enqueues at front, deque = ${deque.toArray()}"); /* Element dequeue */ int? popLast = deque.popLast(); print("Dequeue rear element = $popLast, after rear dequeue deque = ${deque.toArray()}"); int? popFirst = deque.popFirst(); print("Dequeue front element = $popFirst, after front dequeue deque = ${deque.toArray()}"); /* Get the length of the double-ended queue */ int size = deque.size(); print("Deque length size = $size"); /* Check if the double-ended queue is empty */ bool isEmpty = deque.isEmpty(); print("Is deque empty = $isEmpty"); } ================================================ FILE: en/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart ================================================ /** * File: linkedlist_queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/list_node.dart'; /* Queue based on linked list implementation */ class LinkedListQueue { ListNode? _front; // Head node _front ListNode? _rear; // Tail node _rear int _queSize = 0; // Queue length LinkedListQueue() { _front = null; _rear = null; } /* Get the length of the queue */ int size() { return _queSize; } /* Check if the queue is empty */ bool isEmpty() { return _queSize == 0; } /* Enqueue */ void push(int _num) { // Add _num after tail node final node = ListNode(_num); // If the queue is empty, make both front and rear point to the node if (_front == null) { _front = node; _rear = node; } else { // If the queue is not empty, add the node after the tail node _rear!.next = node; _rear = node; } _queSize++; } /* Dequeue */ int pop() { final int _num = peek(); // Delete head node _front = _front!.next; _queSize--; return _num; } /* Return list for printing */ int peek() { if (_queSize == 0) { throw Exception('Queue is empty'); } return _front!.val; } /* Convert linked list to Array and return */ List toArray() { ListNode? node = _front; final List queue = []; while (node != null) { queue.add(node.val); node = node.next; } return queue; } } /* Driver Code */ void main() { /* Access front of the queue element */ final queue = LinkedListQueue(); /* Elements enqueue */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print("Queue queue = ${queue.toArray()}"); /* Return list for printing */ final int peek = queue.peek(); print("Front element peek = $peek"); /* Element dequeue */ final int pop = queue.pop(); print("Dequeue element pop = $pop, after dequeue queue = ${queue.toArray()}"); /* Get the length of the queue */ final int size = queue.size(); print("Queue length size = $size"); /* Check if the queue is empty */ final bool isEmpty = queue.isEmpty(); print("Is queue empty = $isEmpty"); } ================================================ FILE: en/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart ================================================ /** * File: linkedlist_stack.dart * Created Time: 2023-03-27 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/list_node.dart'; /* Stack implemented based on linked list class */ class LinkedListStack { ListNode? _stackPeek; // Use head node as stack top int _stkSize = 0; // Stack length LinkedListStack() { _stackPeek = null; } /* Get the length of the stack */ int size() { return _stkSize; } /* Check if the stack is empty */ bool isEmpty() { return _stkSize == 0; } /* Push */ void push(int _num) { final ListNode node = ListNode(_num); node.next = _stackPeek; _stackPeek = node; _stkSize++; } /* Pop */ int pop() { final int _num = peek(); _stackPeek = _stackPeek!.next; _stkSize--; return _num; } /* Return list for printing */ int peek() { if (_stackPeek == null) { throw Exception("Stack is empty"); } return _stackPeek!.val; } /* Convert linked list to List and return */ List toList() { ListNode? node = _stackPeek; List list = []; while (node != null) { list.add(node.val); node = node.next; } list = list.reversed.toList(); return list; } } /* Driver Code */ void main() { /* Access top of the stack element */ final LinkedListStack stack = LinkedListStack(); /* Elements push onto stack */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print("Stack stack = ${stack.toList()}"); /* Return list for printing */ final int peek = stack.peek(); print("Top element peek = $peek"); /* Element pop from stack */ final int pop = stack.pop(); print("Pop element pop = $pop, after pop stack = ${stack.toList()}"); /* Get the length of the stack */ final int size = stack.size(); print("Stack length size = $size"); /* Check if empty */ final bool isEmpty = stack.isEmpty(); print("Is stack empty = $isEmpty"); } ================================================ FILE: en/codes/dart/chapter_stack_and_queue/queue.dart ================================================ /** * File: queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; void main() { /* Access front of the queue element */ // In Dart, generally use Queue class as queue final Queue queue = Queue(); /* Elements enqueue */ queue.add(1); queue.add(3); queue.add(2); queue.add(5); queue.add(4); print("Queue queue = $queue"); /* Return list for printing */ final int peek = queue.first; print("Front element peek = $peek"); /* Element dequeue */ final int pop = queue.removeFirst(); print("Dequeue element pop = $pop, after dequeue queue = $queue"); /* Get queue length */ final int size = queue.length; print("Queue length size = $size"); /* Check if the queue is empty */ final bool isEmpty = queue.isEmpty; print("Is queue empty = $isEmpty"); } ================================================ FILE: en/codes/dart/chapter_stack_and_queue/stack.dart ================================================ /** * File: stack.dart * Created Time: 2023-03-27 * Author: liuyuxin (gvenusleo@gmail.com) */ void main() { /* Access top of the stack element */ // Dart has no built-in stack class, can use List as stack final List stack = []; /* Elements push onto stack */ stack.add(1); stack.add(3); stack.add(2); stack.add(5); stack.add(4); print("Stack stack = $stack"); /* Return list for printing */ final int peek = stack.last; print("Top element peek = $peek"); /* Element pop from stack */ final int pop = stack.removeLast(); print("Pop element pop = $pop, after pop stack = $stack"); /* Get the length of the stack */ final int size = stack.length; print("Stack length size = $size"); /* Check if empty */ final bool isEmpty = stack.isEmpty; print("Is stack empty = $isEmpty"); } ================================================ FILE: en/codes/dart/chapter_tree/array_binary_tree.dart ================================================ /** * File: array_binary_tree.dart * Created Time: 2023-08-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Binary tree class represented by array */ class ArrayBinaryTree { late List _tree; /* Constructor */ ArrayBinaryTree(this._tree); /* List capacity */ int size() { return _tree.length; } /* Get value of node at index i */ int? val(int i) { // If index out of bounds, return null to represent empty position if (i < 0 || i >= size()) { return null; } return _tree[i]; } /* Get index of left child node of node at index i */ int? left(int i) { return 2 * i + 1; } /* Get index of right child node of node at index i */ int? right(int i) { return 2 * i + 2; } /* Get index of parent node of node at index i */ int? parent(int i) { return (i - 1) ~/ 2; } /* Level-order traversal */ List levelOrder() { List res = []; for (int i = 0; i < size(); i++) { if (val(i) != null) { res.add(val(i)!); } } return res; } /* Depth-first traversal */ void dfs(int i, String order, List res) { // If empty position, return if (val(i) == null) { return; } // Preorder traversal if (order == 'pre') { res.add(val(i)); } dfs(left(i)!, order, res); // Inorder traversal if (order == 'in') { res.add(val(i)); } dfs(right(i)!, order, res); // Postorder traversal if (order == 'post') { res.add(val(i)); } } /* Preorder traversal */ List preOrder() { List res = []; dfs(0, 'pre', res); return res; } /* Inorder traversal */ List inOrder() { List res = []; dfs(0, 'in', res); return res; } /* Postorder traversal */ List postOrder() { List res = []; dfs(0, 'post', res); return res; } } /* Driver Code */ void main() { // Initialize binary tree // Here we use a function to generate a binary tree directly from an array List arr = [ 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ]; TreeNode? root = listToTree(arr); print("\nInitialize binary tree\n"); print("Array representation of binary tree:"); print(arr); print("Linked list representation of binary tree:"); printTree(root); // Binary tree class represented by array ArrayBinaryTree abt = ArrayBinaryTree(arr); // Access node int i = 1; int? l = abt.left(i); int? r = abt.right(i); int? p = abt.parent(i); print("\nCurrent node index is $i, value is ${abt.val(i)}"); print("Its left child index is $l, value is ${(l == null ? "null" : abt.val(l))}"); print("Its right child index is $r, value is ${(r == null ? "null" : abt.val(r))}"); print("Its parent node index is $p, value is ${(p == null ? "null" : abt.val(p))}"); // Traverse tree List res = abt.levelOrder(); print("\nLevel-order traversal is: $res"); res = abt.preOrder(); print("Pre-order traversal is $res"); res = abt.inOrder(); print("In-order traversal is $res"); res = abt.postOrder(); print("Post-order traversal is $res"); } ================================================ FILE: en/codes/dart/chapter_tree/avl_tree.dart ================================================ /** * File: avl_tree.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; class AVLTree { TreeNode? root; /* Constructor */ AVLTree() { root = null; } /* Get node height */ int height(TreeNode? node) { // Empty node height is -1, leaf node height is 0 return node == null ? -1 : node.height; } /* Update node height */ void updateHeight(TreeNode? node) { // Node height equals the height of the tallest subtree + 1 node!.height = max(height(node.left), height(node.right)) + 1; } /* Get balance factor */ int balanceFactor(TreeNode? node) { // Empty node balance factor is 0 if (node == null) return 0; // Node balance factor = left subtree height - right subtree height return height(node.left) - height(node.right); } /* Right rotation operation */ TreeNode? rightRotate(TreeNode? node) { TreeNode? child = node!.left; TreeNode? grandChild = child!.right; // Using child as pivot, rotate node to the right child.right = node; node.left = grandChild; // Update node height updateHeight(node); updateHeight(child); // Return root node of subtree after rotation return child; } /* Left rotation operation */ TreeNode? leftRotate(TreeNode? node) { TreeNode? child = node!.right; TreeNode? grandChild = child!.left; // Using child as pivot, rotate node to the left child.left = node; node.right = grandChild; // Update node height updateHeight(node); updateHeight(child); // Return root node of subtree after rotation return child; } /* Perform rotation operation to restore balance to this subtree */ TreeNode? rotate(TreeNode? node) { // Get balance factor of node int factor = balanceFactor(node); // Left-leaning tree if (factor > 1) { if (balanceFactor(node!.left) >= 0) { // Right rotation return rightRotate(node); } else { // First left rotation then right rotation node.left = leftRotate(node.left); return rightRotate(node); } } // Right-leaning tree if (factor < -1) { if (balanceFactor(node!.right) <= 0) { // Left rotation return leftRotate(node); } else { // First right rotation then left rotation node.right = rightRotate(node.right); return leftRotate(node); } } // Balanced tree, no rotation needed, return directly return node; } /* Insert node */ void insert(int val) { root = insertHelper(root, val); } /* Recursively insert node (helper method) */ TreeNode? insertHelper(TreeNode? node, int val) { if (node == null) return TreeNode(val); /* 1. Find insertion position and insert node */ if (val < node.val) node.left = insertHelper(node.left, val); else if (val > node.val) node.right = insertHelper(node.right, val); else return node; // Duplicate node not inserted, return directly updateHeight(node); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = rotate(node); // Return root node of subtree return node; } /* Remove node */ void remove(int val) { root = removeHelper(root, val); } /* Recursively delete node (helper method) */ TreeNode? removeHelper(TreeNode? node, int val) { if (node == null) return null; /* 1. Find node and delete */ if (val < node.val) node.left = removeHelper(node.left, val); else if (val > node.val) node.right = removeHelper(node.right, val); else { if (node.left == null || node.right == null) { TreeNode? child = node.left ?? node.right; // Number of child nodes = 0, delete node directly and return if (child == null) return null; // Number of child nodes = 1, delete node directly else node = child; } else { // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it TreeNode? temp = node.right; while (temp!.left != null) { temp = temp.left; } node.right = removeHelper(node.right, temp.val); node.val = temp.val; } } updateHeight(node); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = rotate(node); // Return root node of subtree return node; } /* Search node */ TreeNode? search(int val) { TreeNode? cur = root; // Loop search, exit after passing leaf node while (cur != null) { // Target node is in cur's right subtree if (val < cur.val) cur = cur.left; // Target node is in cur's left subtree else if (val > cur.val) cur = cur.right; // Target node equals current node else break; } return cur; } } void testInsert(AVLTree tree, int val) { tree.insert(val); print("\nAfter inserting node $val, AVL tree is"); printTree(tree.root); } void testRemove(AVLTree tree, int val) { tree.remove(val); print("\nAfter deleting node $val, AVL tree is"); printTree(tree.root); } /* Driver Code */ void main() { /* Please pay attention to how the AVL tree maintains balance after inserting nodes */ AVLTree avlTree = AVLTree(); /* Insert node */ // Delete nodes 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); /* Please pay attention to how the AVL tree maintains balance after deleting nodes */ testInsert(avlTree, 7); /* Remove node */ // Delete node with degree 1 testRemove(avlTree, 8); // Delete node with degree 2 testRemove(avlTree, 5); // Remove node with degree 1 testRemove(avlTree, 4); // Remove node with degree 2 /* Search node */ TreeNode? node = avlTree.search(7); print("\nFound node object is $node, node value = ${node!.val}"); } ================================================ FILE: en/codes/dart/chapter_tree/binary_search_tree.dart ================================================ /** * File: binary_search_tree.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Binary search tree */ class BinarySearchTree { late TreeNode? _root; /* Constructor */ BinarySearchTree() { // Initialize empty tree _root = null; } /* Get root node of binary tree */ TreeNode? getRoot() { return _root; } /* Search node */ TreeNode? search(int _num) { TreeNode? cur = _root; // Loop search, exit after passing leaf node while (cur != null) { // Target node is in cur's right subtree if (cur.val < _num) cur = cur.right; // Target node is in cur's left subtree else if (cur.val > _num) cur = cur.left; // Found target node, exit loop else break; } // Return target node return cur; } /* Insert node */ void insert(int _num) { // If tree is empty, initialize root node if (_root == null) { _root = TreeNode(_num); return; } TreeNode? cur = _root; TreeNode? pre = null; // Loop search, exit after passing leaf node while (cur != null) { // Found duplicate node, return directly if (cur.val == _num) return; pre = cur; // Insertion position is in cur's right subtree if (cur.val < _num) cur = cur.right; // Insertion position is in cur's left subtree else cur = cur.left; } // Insert node TreeNode? node = TreeNode(_num); if (pre!.val < _num) pre.right = node; else pre.left = node; } /* Remove node */ void remove(int _num) { // If tree is empty, return directly if (_root == null) return; TreeNode? cur = _root; TreeNode? pre = null; // Loop search, exit after passing leaf node while (cur != null) { // Found node to delete, exit loop if (cur.val == _num) break; pre = cur; // Node to delete is in cur's right subtree if (cur.val < _num) cur = cur.right; // Node to delete is in cur's left subtree else cur = cur.left; } // If no node to delete, return directly if (cur == null) return; // Number of child nodes = 0 or 1 if (cur.left == null || cur.right == null) { // When number of child nodes = 0 / 1, child = null / that child node TreeNode? child = cur.left ?? cur.right; // Delete node cur if (cur != _root) { if (pre!.left == cur) pre.left = child; else pre.right = child; } else { // If deleted node is root node, reassign root node _root = child; } } else { // Number of child nodes = 2 // Get next node of cur in inorder traversal TreeNode? tmp = cur.right; while (tmp!.left != null) { tmp = tmp.left; } // Recursively delete node tmp remove(tmp.val); // Replace cur with tmp cur.val = tmp.val; } } } /* Driver Code */ void main() { /* Initialize binary search tree */ BinarySearchTree bst = BinarySearchTree(); // Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree List nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (int _num in nums) { bst.insert(_num); } print("\nInitialized binary tree is\n"); printTree(bst.getRoot()); /* Search node */ TreeNode? node = bst.search(7); print("\nFound node object is $node, node value = ${node?.val}"); /* Insert node */ bst.insert(16); print("\nAfter inserting node 16, binary tree is\n"); printTree(bst.getRoot()); /* Remove node */ bst.remove(1); print("\nAfter removing node 1, binary tree is\n"); printTree(bst.getRoot()); bst.remove(2); print("\nAfter removing node 2, binary tree is\n"); printTree(bst.getRoot()); bst.remove(4); print("\nAfter removing node 4, binary tree is\n"); printTree(bst.getRoot()); } ================================================ FILE: en/codes/dart/chapter_tree/binary_tree.dart ================================================ /** * File: binary_tree.dart * Created Time: 2023-04-03 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; void main() { /* Initialize binary tree */ // Initialize node TreeNode n1 = TreeNode(1); TreeNode n2 = TreeNode(2); TreeNode n3 = TreeNode(3); TreeNode n4 = TreeNode(4); TreeNode n5 = TreeNode(5); // Build references (pointers) between nodes n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; print("\nInitialize binary tree\n"); printTree(n1); /* Insert node P between n1 -> n2 */ TreeNode p = TreeNode(0); // Insert node p between n1 -> n2 n1.left = p; p.left = n2; print("\nAfter inserting node P\n"); printTree(n1); // Remove node P n1.left = n2; print("\nAfter removing node P\n"); printTree(n1); } ================================================ FILE: en/codes/dart/chapter_tree/binary_tree_bfs.dart ================================================ /** * File: binary_tree_bfs.dart * Created Time: 2023-04-03 * Author: liuyuxin (gvenusleo@gmai.com) */ import 'dart:collection'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Level-order traversal */ List levelOrder(TreeNode? root) { // Initialize queue, add root node Queue queue = Queue(); queue.add(root); // Initialize a list to save the traversal sequence List res = []; while (queue.isNotEmpty) { TreeNode? node = queue.removeFirst(); // Dequeue res.add(node!.val); // Save node value if (node.left != null) queue.add(node.left); // Left child node enqueue if (node.right != null) queue.add(node.right); // Right child node enqueue } return res; } /* Driver Code */ void main() { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); print("\nInitialize binary tree\n"); printTree(root); // Level-order traversal List res = levelOrder(root); print("\nLevel-order traversal node print sequence = $res"); } ================================================ FILE: en/codes/dart/chapter_tree/binary_tree_dfs.dart ================================================ /** * File: binary_tree_dfs.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; // Initialize list for storing traversal sequence List list = []; /* Preorder traversal */ void preOrder(TreeNode? node) { if (node == null) return; // Visit priority: root node -> left subtree -> right subtree list.add(node.val); preOrder(node.left); preOrder(node.right); } /* Inorder traversal */ void inOrder(TreeNode? node) { if (node == null) return; // Visit priority: left subtree -> root node -> right subtree inOrder(node.left); list.add(node.val); inOrder(node.right); } /* Postorder traversal */ void postOrder(TreeNode? node) { if (node == null) return; // Visit priority: left subtree -> right subtree -> root node postOrder(node.left); postOrder(node.right); list.add(node.val); } /* Driver Code */ void main() { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); print("\nInitialize binary tree\n"); printTree(root); /* Preorder traversal */ list.clear(); preOrder(root); print("\nPre-order traversal node print sequence = $list"); /* Inorder traversal */ list.clear(); inOrder(root); print("\nIn-order traversal node print sequence = $list"); /* Postorder traversal */ list.clear(); postOrder(root); print("\nPost-order traversal node print sequence = $list"); } ================================================ FILE: en/codes/dart/utils/list_node.dart ================================================ /** * File: list_node.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* Linked list node */ class ListNode { int val; ListNode? next; ListNode(this.val, [this.next]); } /* Deserialize a list into a linked list */ ListNode? listToLinkedList(List list) { ListNode dum = ListNode(0); ListNode? head = dum; for (int val in list) { head?.next = ListNode(val); head = head?.next; } return dum.next; } ================================================ FILE: en/codes/dart/utils/print_util.dart ================================================ /** * File: print_util.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:io'; import 'list_node.dart'; import 'tree_node.dart'; class Trunk { Trunk? prev; String str; Trunk(this.prev, this.str); } /* Print matrix (Array) */ void printMatrix(List> matrix) { print("["); for (List row in matrix) { print(" $row,"); } print("]"); } /* Print linked list */ void printLinkedList(ListNode? head) { List list = []; while (head != null) { list.add('${head.val}'); head = head.next; } print(list.join(' -> ')); } /** * Print binary tree * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTree(TreeNode? root, [Trunk? prev = null, bool isRight = false]) { if (root == null) { return; } String prev_str = ' '; Trunk trunk = Trunk(prev, prev_str); printTree(root.right, trunk, true); if (prev == null) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); print(' ${root.val}'); if (prev != null) { prev.str = prev_str; } trunk.str = ' |'; printTree(root.left, trunk, false); } void showTrunks(Trunk? p) { if (p == null) { return; } showTrunks(p.prev); stdout.write(p.str); } /* Print heap */ void printHeap(List heap) { print("Array representation of heap: $heap"); print("Heap tree representation:"); TreeNode? root = listToTree(heap); printTree(root); } ================================================ FILE: en/codes/dart/utils/tree_node.dart ================================================ /** * File: tree_node.dart * Created Time: 2023-2-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* Binary tree node class */ class TreeNode { int val; // Node value int height; // Node height TreeNode? left; // Reference to left child node TreeNode? right; // Reference to right child node /* Constructor */ TreeNode(this.val, [this.height = 0, this.left, this.right]); } /* Deserialize a list into a binary tree: recursion */ TreeNode? listToTreeDFS(List arr, int i) { if (i < 0 || i >= arr.length || arr[i] == null) { return null; } TreeNode? root = TreeNode(arr[i]!); root.left = listToTreeDFS(arr, 2 * i + 1); root.right = listToTreeDFS(arr, 2 * i + 2); return root; } /* Deserialize a list into a binary tree */ TreeNode? listToTree(List arr) { return listToTreeDFS(arr, 0); } /* Serialize a binary tree into a list: recursion */ void treeToListDFS(TreeNode? root, int i, List res) { if (root == null) return; while (i >= res.length) { res.add(null); } res[i] = root.val; treeToListDFS(root.left, 2 * i + 1, res); treeToListDFS(root.right, 2 * i + 2, res); } /* Serialize a binary tree into a list */ List treeToList(TreeNode? root) { List res = []; treeToListDFS(root, 0, res); return res; } ================================================ FILE: en/codes/dart/utils/vertex.dart ================================================ /** * File: Vertex.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Vertex class */ class Vertex { int val; Vertex(this.val); /* Input value list vals, return vertex list vets */ static List valsToVets(List vals) { List vets = []; for (int i in vals) { vets.add(Vertex(i)); } return vets; } /* Input vertex list vets, return value list vals */ static List vetsToVals(List vets) { List vals = []; for (Vertex vet in vets) { vals.add(vet.val); } return vals; } } ================================================ FILE: en/codes/go/chapter_array_and_linkedlist/array.go ================================================ // File: array.go // Created Time: 2022-12-29 // Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( "math/rand" ) /* Random access to element */ func randomAccess(nums []int) (randomNum int) { // Randomly select a number in the interval [0, nums.length) randomIndex := rand.Intn(len(nums)) // Retrieve and return the random element randomNum = nums[randomIndex] return } /* Extend array length */ func extend(nums []int, enlarge int) []int { // Initialize an array with extended length res := make([]int, len(nums)+enlarge) // Copy all elements from the original array to the new array for i, num := range nums { res[i] = num } // Return the extended new array return res } /* Insert element num at index index in the array */ func insert(nums []int, num int, index int) { // Move all elements at and after index index backward by one position for i := len(nums) - 1; i > index; i-- { nums[i] = nums[i-1] } // Assign num to the element at index index nums[index] = num } /* Remove the element at index index */ func remove(nums []int, index int) { // Move all elements after index index forward by one position for i := index; i < len(nums)-1; i++ { nums[i] = nums[i+1] } } /* Traverse array */ func traverse(nums []int) { count := 0 // Traverse array by index for i := 0; i < len(nums); i++ { count += nums[i] } count = 0 // Direct traversal of array elements for _, num := range nums { count += num } // Traverse simultaneously data index and elements for i, num := range nums { count += nums[i] count += num } } /* Find the specified element in the array */ func find(nums []int, target int) (index int) { index = -1 for i := 0; i < len(nums); i++ { if nums[i] == target { index = i break } } return } ================================================ FILE: en/codes/go/chapter_array_and_linkedlist/array_test.go ================================================ // File: array_test.go // Created Time: 2022-12-29 // Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist /** We treat Go Slice as Array here. This reduces the learning cost and allows us to focus on data structures and algorithms. */ import ( "fmt" "testing" ) /* Driver Code */ func TestArray(t *testing.T) { /* Initialize array */ var arr [5]int fmt.Println("Array arr =", arr) // In Go, specifying length ([5]int) creates an array, not specifying length ([]int) creates a slice // Since Go arrays are designed to have their length determined at compile time, only constants can be used to specify the length // For convenience in implementing the extend() function, slices are treated as arrays below nums := []int{1, 3, 2, 5, 4} fmt.Println("Array nums =", nums) /* Insert element */ randomNum := randomAccess(nums) fmt.Println("Get random element in nums", randomNum) /* Traverse array */ nums = extend(nums, 3) fmt.Println("Extend array length to 8, get nums =", nums) /* Insert element */ insert(nums, 6, 3) fmt.Println("Insert number 6 at index 3, get nums =", nums) /* Remove element */ remove(nums, 2) fmt.Println("Remove element at index 2, get nums =", nums) /* Traverse array */ traverse(nums) /* Find element */ index := find(nums, 3) fmt.Println("Find element 3 in nums, get index =", index) } ================================================ FILE: en/codes/go/chapter_array_and_linkedlist/linked_list.go ================================================ // File: linked_list.go // Created Time: 2022-12-29 // Author: cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( . "github.com/krahets/hello-algo/pkg" ) /* Insert node P after node n0 in the linked list */ func insertNode(n0 *ListNode, P *ListNode) { n1 := n0.Next P.Next = n1 n0.Next = P } /* Remove the first node after node n0 in the linked list */ func removeItem(n0 *ListNode) { if n0.Next == nil { return } // n0 -> P -> n1 P := n0.Next n1 := P.Next n0.Next = n1 } /* Access the node at index index in the linked list */ func access(head *ListNode, index int) *ListNode { for i := 0; i < index; i++ { if head == nil { return nil } head = head.Next } return head } /* Find the first node with value target in the linked list */ func findNode(head *ListNode, target int) int { index := 0 for head != nil { if head.Val == target { return index } head = head.Next index++ } return -1 } ================================================ FILE: en/codes/go/chapter_array_and_linkedlist/linked_list_test.go ================================================ // File: linked_list_test.go // Created Time: 2022-12-29 // Author: cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLinkedList(t *testing.T) { /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */ // Initialize each node n0 := NewListNode(1) n1 := NewListNode(3) n2 := NewListNode(2) n3 := NewListNode(5) n4 := NewListNode(4) // Build references between nodes n0.Next = n1 n1.Next = n2 n2.Next = n3 n3.Next = n4 fmt.Println("Initialized linked list is") PrintLinkedList(n0) /* Insert node */ insertNode(n0, NewListNode(0)) fmt.Println("Linked list after inserting node is") PrintLinkedList(n0) /* Remove node */ removeItem(n0) fmt.Println("Linked list after removing node is") PrintLinkedList(n0) /* Access node */ node := access(n0, 3) fmt.Println("Value of node at index 3 in linked list =", node) /* Search node */ index := findNode(n0, 2) fmt.Println("Index of node with value 2 in linked list =", index) } ================================================ FILE: en/codes/go/chapter_array_and_linkedlist/list_test.go ================================================ // File: list_test.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "sort" "testing" ) /* Driver Code */ func TestList(t *testing.T) { /* Initialize list */ nums := []int{1, 3, 2, 5, 4} fmt.Println("List nums =", nums) /* Update element */ num := nums[1] // Access element at index 1 fmt.Println("Access element at index 1, get num =", num) /* Add elements at the end */ nums[1] = 0 // Update element at index 1 to 0 fmt.Println("Update element at index 1 to 0, get nums =", nums) /* Remove element */ nums = nil fmt.Println("After clearing list, nums =", nums) /* Direct traversal of list elements */ nums = append(nums, 1) nums = append(nums, 3) nums = append(nums, 2) nums = append(nums, 5) nums = append(nums, 4) fmt.Println("After adding elements, nums =", nums) /* Sort list */ nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // Insert number 6 at index 3 fmt.Println("Insert number 6 at index 3, get nums =", nums) /* Remove element */ nums = append(nums[:3], nums[4:]...) // Remove element at index 3 fmt.Println("Remove element at index 3, get nums =", nums) /* Traverse list by index */ count := 0 for i := 0; i < len(nums); i++ { count += nums[i] } /* Directly traverse list elements */ count = 0 for _, x := range nums { count += x } /* Concatenate two lists */ nums1 := []int{6, 8, 7, 10, 9} nums = append(nums, nums1...) // Concatenate list nums1 to nums fmt.Println("Concatenate list nums1 to nums, get nums =", nums) /* Sort list */ sort.Ints(nums) // After sorting, list elements are arranged from smallest to largest fmt.Println("After sorting list, nums =", nums) } ================================================ FILE: en/codes/go/chapter_array_and_linkedlist/my_list.go ================================================ // File: my_list.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist /* List class */ type myList struct { arrCapacity int arr []int arrSize int extendRatio int } /* Constructor */ func newMyList() *myList { return &myList{ arrCapacity: 10, // List capacity arr: make([]int, 10), // Array (stores list elements) arrSize: 0, // List length (current number of elements) extendRatio: 2, // Multiple by which the list capacity is extended each time } } /* Get list length (current number of elements) */ func (l *myList) size() int { return l.arrSize } /* Get list capacity */ func (l *myList) capacity() int { return l.arrCapacity } /* Update element */ func (l *myList) get(index int) int { // If the index is out of bounds, throw an exception, as below if index < 0 || index >= l.arrSize { panic("Index out of bounds") } return l.arr[index] } /* Add elements at the end */ func (l *myList) set(num, index int) { if index < 0 || index >= l.arrSize { panic("Index out of bounds") } l.arr[index] = num } /* Direct traversal of list elements */ func (l *myList) add(num int) { // When the number of elements exceeds capacity, trigger the extension mechanism if l.arrSize == l.arrCapacity { l.extendCapacity() } l.arr[l.arrSize] = num // Update the number of elements l.arrSize++ } /* Sort list */ func (l *myList) insert(num, index int) { if index < 0 || index >= l.arrSize { panic("Index out of bounds") } // When the number of elements exceeds capacity, trigger the extension mechanism if l.arrSize == l.arrCapacity { l.extendCapacity() } // Move all elements after index index forward by one position for j := l.arrSize - 1; j >= index; j-- { l.arr[j+1] = l.arr[j] } l.arr[index] = num // Update the number of elements l.arrSize++ } /* Remove element */ func (l *myList) remove(index int) int { if index < 0 || index >= l.arrSize { panic("Index out of bounds") } num := l.arr[index] // Create a new array with length _extend_ratio times the original array, and copy the original array to the new array for j := index; j < l.arrSize-1; j++ { l.arr[j] = l.arr[j+1] } // Update the number of elements l.arrSize-- // Return the removed element return num } /* Driver Code */ func (l *myList) extendCapacity() { // Create a new array with length extendRatio times the original array and copy the original array to the new array l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...) // Add elements at the end l.arrCapacity = len(l.arr) } /* Return list with valid length */ func (l *myList) toArray() []int { // Elements enqueue return l.arr[:l.arrSize] } ================================================ FILE: en/codes/go/chapter_array_and_linkedlist/my_list_test.go ================================================ // File: my_list_test.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "testing" ) /* Driver Code */ func TestMyList(t *testing.T) { /* Initialize list */ nums := newMyList() /* Direct traversal of list elements */ nums.add(1) nums.add(3) nums.add(2) nums.add(5) nums.add(4) fmt.Printf("List nums = %v, capacity = %v, length = %v\n", nums.toArray(), nums.capacity(), nums.size()) /* Sort list */ nums.insert(6, 3) fmt.Printf("Insert number 6 at index 3, get nums = %v\n", nums.toArray()) /* Remove element */ nums.remove(3) fmt.Printf("Remove element at index 3, get nums = %v\n", nums.toArray()) /* Update element */ num := nums.get(1) fmt.Printf("Access element at index 1, get num = %v\n", num) /* Add elements at the end */ nums.set(0, 1) fmt.Printf("Update element at index 1 to 0, get nums = %v\n", nums.toArray()) /* Test capacity expansion mechanism */ for i := 0; i < 10; i++ { // At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism nums.add(i) } fmt.Printf("After expansion, list nums = %v, capacity = %v, length = %v\n", nums.toArray(), nums.capacity(), nums.size()) } ================================================ FILE: en/codes/go/chapter_backtracking/n_queens.go ================================================ // File: n_queens.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* Backtracking algorithm: N queens */ func backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) { // When all rows are placed, record the solution if row == n { newState := make([][]string, len(*state)) for i, _ := range newState { newState[i] = make([]string, len((*state)[0])) copy(newState[i], (*state)[i]) } *res = append(*res, newState) return } // Traverse all columns for col := 0; col < n; col++ { // Calculate the main diagonal and anti-diagonal corresponding to this cell diag1 := row - col + n - 1 diag2 := row + col // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] { // Attempt: place the queen in this cell (*state)[row][col] = "Q" (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true // Place the next row backtrack(row+1, n, state, res, cols, diags1, diags2) // Backtrack: restore this cell to an empty cell (*state)[row][col] = "#" (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false } } } /* Solve N queens */ func nQueens(n int) [][][]string { // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell state := make([][]string, n) for i := 0; i < n; i++ { row := make([]string, n) for i := 0; i < n; i++ { row[i] = "#" } state[i] = row } // Record whether there is a queen in the column cols := make([]bool, n) diags1 := make([]bool, 2*n-1) diags2 := make([]bool, 2*n-1) res := make([][][]string, 0) backtrack(0, n, &state, &res, &cols, &diags1, &diags2) return res } ================================================ FILE: en/codes/go/chapter_backtracking/n_queens_test.go ================================================ // File: n_queens_test.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" ) func TestNQueens(t *testing.T) { n := 4 res := nQueens(n) fmt.Println("Input board size is ", n) fmt.Println("Total queen placement solutions: ", len(res), " solutions") for _, state := range res { fmt.Println("--------------------") for _, row := range state { fmt.Println(row) } } } ================================================ FILE: en/codes/go/chapter_backtracking/permutation_test.go ================================================ // File: permutation_test.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPermutationI(t *testing.T) { /* Permutations I */ nums := []int{1, 2, 3} fmt.Printf("Input array nums = ") PrintSlice(nums) res := permutationsI(nums) fmt.Printf("All permutations res = ") fmt.Println(res) } func TestPermutationII(t *testing.T) { nums := []int{1, 2, 2} fmt.Printf("Input array nums = ") PrintSlice(nums) res := permutationsII(nums) fmt.Printf("All permutations res = ") fmt.Println(res) } ================================================ FILE: en/codes/go/chapter_backtracking/permutations_i.go ================================================ // File: permutations_i.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* Backtracking algorithm: Permutations I */ func backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // When the state length equals the number of elements, record the solution if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // Traverse all choices for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // Pruning: do not allow repeated selection of elements if !(*selected)[i] { // Attempt: make choice, update state (*selected)[i] = true *state = append(*state, choice) // Proceed to the next round of selection backtrackI(state, choices, selected, res) // Backtrack: undo choice, restore to previous state (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* Permutations I */ func permutationsI(nums []int) [][]int { res := make([][]int, 0) state := make([]int, 0) selected := make([]bool, len(nums)) backtrackI(&state, &nums, &selected, &res) return res } ================================================ FILE: en/codes/go/chapter_backtracking/permutations_ii.go ================================================ // File: permutations_ii.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* Backtracking algorithm: Permutations II */ func backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // When the state length equals the number of elements, record the solution if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // Traverse all choices duplicated := make(map[int]struct{}, 0) for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if _, ok := duplicated[choice]; !ok && !(*selected)[i] { // Attempt: make choice, update state // Record the selected element value duplicated[choice] = struct{}{} (*selected)[i] = true *state = append(*state, choice) // Proceed to the next round of selection backtrackII(state, choices, selected, res) // Backtrack: undo choice, restore to previous state (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* Permutations II */ func permutationsII(nums []int) [][]int { res := make([][]int, 0) state := make([]int, 0) selected := make([]bool, len(nums)) backtrackII(&state, &nums, &selected, &res) return res } ================================================ FILE: en/codes/go/chapter_backtracking/preorder_traversal_i_compact.go ================================================ // File: preorder_traversal_i_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* Preorder traversal: Example 1 */ func preOrderI(root *TreeNode, res *[]*TreeNode) { if root == nil { return } if (root.Val).(int) == 7 { // Record solution *res = append(*res, root) } preOrderI(root.Left, res) preOrderI(root.Right, res) } ================================================ FILE: en/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go ================================================ // File: preorder_traversal_ii_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* Preorder traversal: Example 2 */ func preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { if root == nil { return } // Attempt *path = append(*path, root) if root.Val.(int) == 7 { // Record solution *res = append(*res, append([]*TreeNode{}, *path...)) } preOrderII(root.Left, res, path) preOrderII(root.Right, res, path) // Backtrack *path = (*path)[:len(*path)-1] } ================================================ FILE: en/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go ================================================ // File: preorder_traversal_iii_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* Preorder traversal: Example 3 */ func preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { // Pruning if root == nil || root.Val == 3 { return } // Attempt *path = append(*path, root) if root.Val.(int) == 7 { // Record solution *res = append(*res, append([]*TreeNode{}, *path...)) } preOrderIII(root.Left, res, path) preOrderIII(root.Right, res, path) // Backtrack *path = (*path)[:len(*path)-1] } ================================================ FILE: en/codes/go/chapter_backtracking/preorder_traversal_iii_template.go ================================================ // File: preorder_traversal_iii_template.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* Check if the current state is a solution */ func isSolution(state *[]*TreeNode) bool { return len(*state) != 0 && (*state)[len(*state)-1].Val == 7 } /* Record solution */ func recordSolution(state *[]*TreeNode, res *[][]*TreeNode) { *res = append(*res, append([]*TreeNode{}, *state...)) } /* Check if the choice is valid under the current state */ func isValid(state *[]*TreeNode, choice *TreeNode) bool { return choice != nil && choice.Val != 3 } /* Update state */ func makeChoice(state *[]*TreeNode, choice *TreeNode) { *state = append(*state, choice) } /* Restore state */ func undoChoice(state *[]*TreeNode, choice *TreeNode) { *state = (*state)[:len(*state)-1] } /* Backtracking algorithm: Example 3 */ func backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) { // Check if it is a solution if isSolution(state) { // Record solution recordSolution(state, res) } // Traverse all choices for _, choice := range *choices { // Pruning: check if the choice is valid if isValid(state, choice) { // Attempt: make choice, update state makeChoice(state, choice) // Proceed to the next round of selection temp := make([]*TreeNode, 0) temp = append(temp, choice.Left, choice.Right) backtrackIII(state, &temp, res) // Backtrack: undo choice, restore to previous state undoChoice(state, choice) } } } ================================================ FILE: en/codes/go/chapter_backtracking/preorder_traversal_test.go ================================================ // File: preorder_traversal_i_compact_test.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPreorderTraversalICompact(t *testing.T) { /* Initialize binary tree */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\nInitialize binary tree") PrintTree(root) // Preorder traversal res := make([]*TreeNode, 0) preOrderI(root, &res) fmt.Println("\nOutput all nodes with value 7") for _, node := range res { fmt.Printf("%v ", node.Val) } fmt.Println() } func TestPreorderTraversalIICompact(t *testing.T) { /* Initialize binary tree */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\nInitialize binary tree") PrintTree(root) // Preorder traversal path := make([]*TreeNode, 0) res := make([][]*TreeNode, 0) preOrderII(root, &res, &path) fmt.Println("\nOutput all paths from root node to node 7") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } func TestPreorderTraversalIIICompact(t *testing.T) { /* Initialize binary tree */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\nInitialize binary tree") PrintTree(root) // Preorder traversal path := make([]*TreeNode, 0) res := make([][]*TreeNode, 0) preOrderIII(root, &res, &path) fmt.Println("\nOutput all paths from root node to node 7, paths do not include nodes with value 3") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } func TestPreorderTraversalIIITemplate(t *testing.T) { /* Initialize binary tree */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\nInitialize binary tree") PrintTree(root) // Backtracking algorithm res := make([][]*TreeNode, 0) state := make([]*TreeNode, 0) choices := make([]*TreeNode, 0) choices = append(choices, root) backtrackIII(&state, &choices, &res) fmt.Println("\nOutput all paths from root node to node 7, paths do not include nodes with value 3") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } ================================================ FILE: en/codes/go/chapter_backtracking/subset_sum_i.go ================================================ // File: subset_sum_i.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import "sort" /* Backtracking algorithm: Subset sum I */ func backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) { // When the subset sum equals target, record the solution if target == 0 { newState := append([]int{}, *state...) *res = append(*res, newState) return } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets for i := start; i < len(*choices); i++ { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if target-(*choices)[i] < 0 { break } // Attempt: make choice, update target, start *state = append(*state, (*choices)[i]) // Proceed to the next round of selection backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res) // Backtrack: undo choice, restore to previous state *state = (*state)[:len(*state)-1] } } /* Solve subset sum I */ func subsetSumI(nums []int, target int) [][]int { state := make([]int, 0) // State (subset) sort.Ints(nums) // Sort nums start := 0 // Start point for traversal res := make([][]int, 0) // Result list (subset list) backtrackSubsetSumI(start, target, &state, &nums, &res) return res } ================================================ FILE: en/codes/go/chapter_backtracking/subset_sum_i_naive.go ================================================ // File: subset_sum_i_naive.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* Backtracking algorithm: Subset sum I */ func backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) { // When the subset sum equals target, record the solution if target == total { newState := append([]int{}, *state...) *res = append(*res, newState) return } // Traverse all choices for i := 0; i < len(*choices); i++ { // Pruning: if the subset sum exceeds target, skip this choice if total+(*choices)[i] > target { continue } // Attempt: make choice, update element sum total *state = append(*state, (*choices)[i]) // Proceed to the next round of selection backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res) // Backtrack: undo choice, restore to previous state *state = (*state)[:len(*state)-1] } } /* Solve subset sum I (including duplicate subsets) */ func subsetSumINaive(nums []int, target int) [][]int { state := make([]int, 0) // State (subset) total := 0 // Subset sum res := make([][]int, 0) // Result list (subset list) backtrackSubsetSumINaive(total, target, &state, &nums, &res) return res } ================================================ FILE: en/codes/go/chapter_backtracking/subset_sum_ii.go ================================================ // File: subset_sum_ii.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import "sort" /* Backtracking algorithm: Subset sum II */ func backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) { // When the subset sum equals target, record the solution if target == 0 { newState := append([]int{}, *state...) *res = append(*res, newState) return } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets // Pruning 3: start traversing from start to avoid repeatedly selecting the same element for i := start; i < len(*choices); i++ { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if target-(*choices)[i] < 0 { break } // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly if i > start && (*choices)[i] == (*choices)[i-1] { continue } // Attempt: make choice, update target, start *state = append(*state, (*choices)[i]) // Proceed to the next round of selection backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res) // Backtrack: undo choice, restore to previous state *state = (*state)[:len(*state)-1] } } /* Solve subset sum II */ func subsetSumII(nums []int, target int) [][]int { state := make([]int, 0) // State (subset) sort.Ints(nums) // Sort nums start := 0 // Start point for traversal res := make([][]int, 0) // Result list (subset list) backtrackSubsetSumII(start, target, &state, &nums, &res) return res } ================================================ FILE: en/codes/go/chapter_backtracking/subset_sum_test.go ================================================ // File: subset_sum_test.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestSubsetSumINaive(t *testing.T) { nums := []int{3, 4, 5} target := 9 res := subsetSumINaive(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", input array nums = ") PrintSlice(nums) fmt.Println("All subsets with sum equal to " + strconv.Itoa(target) + " are res = ") for i := range res { PrintSlice(res[i]) } fmt.Println("Please note that this method outputs results containing duplicate sets") } func TestSubsetSumI(t *testing.T) { nums := []int{3, 4, 5} target := 9 res := subsetSumI(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", input array nums = ") PrintSlice(nums) fmt.Println("All subsets with sum equal to " + strconv.Itoa(target) + " are res = ") for i := range res { PrintSlice(res[i]) } } func TestSubsetSumII(t *testing.T) { nums := []int{4, 4, 5} target := 9 res := subsetSumII(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", input array nums = ") PrintSlice(nums) fmt.Println("All subsets with sum equal to " + strconv.Itoa(target) + " are res = ") for i := range res { PrintSlice(res[i]) } } ================================================ FILE: en/codes/go/chapter_computational_complexity/iteration.go ================================================ // File: iteration.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import "fmt" /* for loop */ func forLoop(n int) int { res := 0 // Sum 1, 2, ..., n-1, n for i := 1; i <= n; i++ { res += i } return res } /* while loop */ func whileLoop(n int) int { res := 0 // Initialize condition variable i := 1 // Sum 1, 2, ..., n-1, n for i <= n { res += i // Update condition variable i++ } return res } /* while loop (two updates) */ func whileLoopII(n int) int { res := 0 // Initialize condition variable i := 1 // Sum 1, 4, 10, ... for i <= n { res += i // Update condition variable i++ i *= 2 } return res } /* Nested for loop */ func nestedForLoop(n int) string { res := "" // Loop i = 1, 2, ..., n-1, n for i := 1; i <= n; i++ { for j := 1; j <= n; j++ { // Loop j = 1, 2, ..., n-1, n res += fmt.Sprintf("(%d, %d), ", i, j) } } return res } ================================================ FILE: en/codes/go/chapter_computational_complexity/iteration_test.go ================================================ // File: iteration_test.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import ( "fmt" "testing" ) /* Driver Code */ func TestIteration(t *testing.T) { n := 5 res := forLoop(n) fmt.Println("\nfor loop sum result res = ", res) res = whileLoop(n) fmt.Println("\nwhile loop sum result res = ", res) res = whileLoopII(n) fmt.Println("\nwhile loop (two updates) sum result res = ", res) resStr := nestedForLoop(n) fmt.Println("\nDouble for loop traversal result ", resStr) } ================================================ FILE: en/codes/go/chapter_computational_complexity/recursion.go ================================================ // File: recursion.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import "container/list" /* Recursion */ func recur(n int) int { // Termination condition if n == 1 { return 1 } // Recurse: recursive call res := recur(n - 1) // Return: return result return n + res } /* Simulate recursion using iteration */ func forLoopRecur(n int) int { // Use an explicit stack to simulate the system call stack stack := list.New() res := 0 // Recurse: recursive call for i := n; i > 0; i-- { // Simulate "recurse" with "push" stack.PushBack(i) } // Return: return result for stack.Len() != 0 { // Simulate "return" with "pop" res += stack.Back().Value.(int) stack.Remove(stack.Back()) } // res = 1+2+3+...+n return res } /* Tail recursion */ func tailRecur(n int, res int) int { // Termination condition if n == 0 { return res } // Tail recursive call return tailRecur(n-1, res+n) } /* Fibonacci sequence: recursion */ func fib(n int) int { // Termination condition f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1 } // Recursive call f(n) = f(n-1) + f(n-2) res := fib(n-1) + fib(n-2) // Return result f(n) return res } ================================================ FILE: en/codes/go/chapter_computational_complexity/recursion_test.go ================================================ // File: recursion_test.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import ( "fmt" "testing" ) /* Driver Code */ func TestRecursion(t *testing.T) { n := 5 res := recur(n) fmt.Println("\nRecursive function sum result res = ", res) res = forLoopRecur(n) fmt.Println("\nUsing iteration to simulate recursive sum result res = ", res) res = tailRecur(n, 0) fmt.Println("\nTail recursive function sum result res = ", res) res = fib(n) fmt.Println("\nThe ", n, "th term of Fibonacci sequence is", res) } ================================================ FILE: en/codes/go/chapter_computational_complexity/space_complexity.go ================================================ // File: space_complexity.go // Created Time: 2022-12-15 // Author: cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "fmt" "strconv" . "github.com/krahets/hello-algo/pkg" ) /* Struct */ type node struct { val int next *node } /* Create node struct */ func newNode(val int) *node { return &node{val: val} } /* Function */ func function() int { // Perform some operations... return 0 } /* Constant order */ func spaceConstant(n int) { // Constants, variables, objects occupy O(1) space const a = 0 b := 0 nums := make([]int, 10000) node := newNode(0) // Variables in the loop occupy O(1) space var c int for i := 0; i < n; i++ { c = 0 } // Functions in the loop occupy O(1) space for i := 0; i < n; i++ { function() } b += 0 c += 0 nums[0] = 0 node.val = 0 } /* Linear order */ func spaceLinear(n int) { // Array of length n uses O(n) space _ = make([]int, n) // A list of length n occupies O(n) space var nodes []*node for i := 0; i < n; i++ { nodes = append(nodes, newNode(i)) } // A hash table of length n occupies O(n) space m := make(map[int]string, n) for i := 0; i < n; i++ { m[i] = strconv.Itoa(i) } } /* Linear order (recursive implementation) */ func spaceLinearRecur(n int) { fmt.Println("Recursion n =", n) if n == 1 { return } spaceLinearRecur(n - 1) } /* Exponential order */ func spaceQuadratic(n int) { // Matrix uses O(n^2) space numMatrix := make([][]int, n) for i := 0; i < n; i++ { numMatrix[i] = make([]int, n) } } /* Quadratic order (recursive implementation) */ func spaceQuadraticRecur(n int) int { if n <= 0 { return 0 } nums := make([]int, n) fmt.Printf("In recursion n = %d, nums length = %d \n", n, len(nums)) return spaceQuadraticRecur(n - 1) } /* Driver Code */ func buildTree(n int) *TreeNode { if n == 0 { return nil } root := NewTreeNode(0) root.Left = buildTree(n - 1) root.Right = buildTree(n - 1) return root } ================================================ FILE: en/codes/go/chapter_computational_complexity/space_complexity_test.go ================================================ // File: space_complexity_test.go // Created Time: 2022-12-15 // Author: cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "testing" . "github.com/krahets/hello-algo/pkg" ) func TestSpaceComplexity(t *testing.T) { n := 5 // Constant order spaceConstant(n) // Linear order spaceLinear(n) spaceLinearRecur(n) // Exponential order spaceQuadratic(n) spaceQuadraticRecur(n) // Exponential order root := buildTree(n) PrintTree(root) } ================================================ FILE: en/codes/go/chapter_computational_complexity/time_complexity.go ================================================ // File: time_complexity.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_computational_complexity /* Constant order */ func constant(n int) int { count := 0 size := 100000 for i := 0; i < size; i++ { count++ } return count } /* Linear order */ func linear(n int) int { count := 0 for i := 0; i < n; i++ { count++ } return count } /* Linear order (traversing array) */ func arrayTraversal(nums []int) int { count := 0 // Number of iterations is proportional to the array length for range nums { count++ } return count } /* Exponential order */ func quadratic(n int) int { count := 0 // Number of iterations is quadratically related to the data size n for i := 0; i < n; i++ { for j := 0; j < n; j++ { count++ } } return count } /* Quadratic order (bubble sort) */ func bubbleSort(nums []int) int { count := 0 // Counter // Outer loop: unsorted range is [0, i] for i := len(nums) - 1; i > 0; i-- { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // Swap nums[j] and nums[j + 1] tmp := nums[j] nums[j] = nums[j+1] nums[j+1] = tmp count += 3 // Element swap includes 3 unit operations } } } return count } /* Exponential order (loop implementation) */ func exponential(n int) int { count, base := 0, 1 // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1) for i := 0; i < n; i++ { for j := 0; j < base; j++ { count++ } base *= 2 } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count } /* Exponential order (recursive implementation) */ func expRecur(n int) int { if n == 1 { return 1 } return expRecur(n-1) + expRecur(n-1) + 1 } /* Logarithmic order (loop implementation) */ func logarithmic(n int) int { count := 0 for n > 1 { n = n / 2 count++ } return count } /* Logarithmic order (recursive implementation) */ func logRecur(n int) int { if n <= 1 { return 0 } return logRecur(n/2) + 1 } /* Linearithmic order */ func linearLogRecur(n int) int { if n <= 1 { return 1 } count := linearLogRecur(n/2) + linearLogRecur(n/2) for i := 0; i < n; i++ { count++ } return count } /* Factorial order (recursive implementation) */ func factorialRecur(n int) int { if n == 0 { return 1 } count := 0 // Split from 1 into n for i := 0; i < n; i++ { count += factorialRecur(n - 1) } return count } ================================================ FILE: en/codes/go/chapter_computational_complexity/time_complexity_test.go ================================================ // File: time_complexity_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_computational_complexity import ( "fmt" "testing" ) func TestTimeComplexity(t *testing.T) { n := 8 fmt.Println("Input data size n =", n) count := constant(n) fmt.Println("Number of constant-order operations =", count) count = linear(n) fmt.Println("Number of linear-order operations =", count) count = arrayTraversal(make([]int, n)) fmt.Println("Number of linear-order (array traversal) operations =", count) count = quadratic(n) fmt.Println("Number of quadratic-order operations =", count) nums := make([]int, n) for i := 0; i < n; i++ { nums[i] = n - i } count = bubbleSort(nums) fmt.Println("Number of quadratic-order (bubble sort) operations =", count) count = exponential(n) fmt.Println("Number of exponential-order (loop implementation) operations =", count) count = expRecur(n) fmt.Println("Number of exponential-order (recursive implementation) operations =", count) count = logarithmic(n) fmt.Println("Number of logarithmic-order (loop implementation) operations =", count) count = logRecur(n) fmt.Println("Number of logarithmic-order (recursive implementation) operations =", count) count = linearLogRecur(n) fmt.Println("Number of linearithmic-order (recursive implementation) operations =", count) count = factorialRecur(n) fmt.Println("Number of factorial-order (recursive implementation) operations =", count) } ================================================ FILE: en/codes/go/chapter_computational_complexity/worst_best_time_complexity.go ================================================ // File: worst_best_time_complexity.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "math/rand" ) /* Generate an array with elements { 1, 2, ..., n }, order shuffled */ func randomNumbers(n int) []int { nums := make([]int, n) // Generate array nums = { 1, 2, 3, ..., n } for i := 0; i < n; i++ { nums[i] = i + 1 } // Randomly shuffle array elements rand.Shuffle(len(nums), func(i, j int) { nums[i], nums[j] = nums[j], nums[i] }) return nums } /* Find the index of number 1 in array nums */ func findOne(nums []int) int { for i := 0; i < len(nums); i++ { // When element 1 is at the head of the array, best time complexity O(1) is achieved // When element 1 is at the tail of the array, worst time complexity O(n) is achieved if nums[i] == 1 { return i } } return -1 } ================================================ FILE: en/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go ================================================ // File: worst_best_time_complexity_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "fmt" "testing" ) func TestWorstBestTimeComplexity(t *testing.T) { for i := 0; i < 10; i++ { n := 100 nums := randomNumbers(n) index := findOne(nums) fmt.Println("\nAfter shuffling array [ 1, 2, ..., n ] =", nums) fmt.Println("Index of number 1 is", index) } } ================================================ FILE: en/codes/go/chapter_divide_and_conquer/binary_search_recur.go ================================================ // File: binary_search_recur.go // Created Time: 2023-07-19 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer /* Binary search: problem f(i, j) */ func dfs(nums []int, target, i, j int) int { // If interval is empty, indicating no target element, return -1 if i > j { return -1 } // Calculate midpoint index m := i + ((j - i) >> 1) // Compare midpoint with target element if nums[m] < target { // If smaller, recurse on right half of array // Recursion subproblem f(m+1, j) return dfs(nums, target, m+1, j) } else if nums[m] > target { // If larger, recurse on left half of array // Recursion subproblem f(i, m-1) return dfs(nums, target, i, m-1) } else { // Found the target element, return its index return m } } /* Binary search */ func binarySearch(nums []int, target int) int { n := len(nums) return dfs(nums, target, 0, n-1) } ================================================ FILE: en/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go ================================================ // File: binary_search_recur_test.go // Created Time: 2023-07-19 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "fmt" "testing" ) func TestBinarySearch(t *testing.T) { nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} target := 6 noTarget := 99 targetIndex := binarySearch(nums, target) fmt.Println("Index of target element 6 = ", targetIndex) noTargetIndex := binarySearch(nums, noTarget) fmt.Println("Index of non-existent target element = ", noTargetIndex) } ================================================ FILE: en/codes/go/chapter_divide_and_conquer/build_tree.go ================================================ // File: build_tree.go // Created Time: 2023-07-20 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import . "github.com/krahets/hello-algo/pkg" /* Build binary tree: divide and conquer */ func dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode { // Terminate when the subtree interval is empty if r-l < 0 { return nil } // Initialize the root node root := NewTreeNode(preorder[i]) // Query m to divide the left and right subtrees m := inorderMap[preorder[i]] // Subproblem: build the left subtree root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1) // Subproblem: build the right subtree root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r) // Return the root node return root } /* Build binary tree */ func buildTree(preorder, inorder []int) *TreeNode { // Initialize hash map, storing the mapping from inorder elements to indices inorderMap := make(map[int]int, len(inorder)) for i := 0; i < len(inorder); i++ { inorderMap[inorder[i]] = i } root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1) return root } ================================================ FILE: en/codes/go/chapter_divide_and_conquer/build_tree_test.go ================================================ // File: build_tree_test.go // Created Time: 2023-07-20 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestBuildTree(t *testing.T) { preorder := []int{3, 9, 2, 1, 7} inorder := []int{9, 3, 1, 2, 7} fmt.Print("Preorder traversal = ") PrintSlice(preorder) fmt.Print("Inorder traversal = ") PrintSlice(inorder) root := buildTree(preorder, inorder) fmt.Println("The constructed binary tree is:") PrintTree(root) } ================================================ FILE: en/codes/go/chapter_divide_and_conquer/hanota.go ================================================ // File: hanota.go // Created Time: 2023-07-21 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import "container/list" /* Move a disk */ func move(src, tar *list.List) { // Take out a disk from the top of src pan := src.Back() // Place the disk on top of tar tar.PushBack(pan.Value) // Remove top disk from src src.Remove(pan) } /* Solve the Tower of Hanoi problem f(i) */ func dfsHanota(i int, src, buf, tar *list.List) { // If there is only one disk left in src, move it directly to tar if i == 1 { move(src, tar) return } // Subproblem f(i-1): move the top i-1 disks from src to buf using tar dfsHanota(i-1, src, tar, buf) // Subproblem f(1): move the remaining disk from src to tar move(src, tar) // Subproblem f(i-1): move the top i-1 disks from buf to tar using src dfsHanota(i-1, buf, src, tar) } /* Solve the Tower of Hanoi problem */ func solveHanota(A, B, C *list.List) { n := A.Len() // Move the top n disks from A to C using B dfsHanota(n, A, B, C) } ================================================ FILE: en/codes/go/chapter_divide_and_conquer/hanota_test.go ================================================ // File: hanota_test.go // Created Time: 2023-07-21 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHanota(t *testing.T) { // The tail of the list is the top of the rod A := list.New() for i := 5; i > 0; i-- { A.PushBack(i) } B := list.New() C := list.New() fmt.Println("In initial state:") fmt.Print("A = ") PrintList(A) fmt.Print("B = ") PrintList(B) fmt.Print("C = ") PrintList(C) solveHanota(A, B, C) fmt.Println("After disk movement is complete:") fmt.Print("A = ") PrintList(A) fmt.Print("B = ") PrintList(B) fmt.Print("C = ") PrintList(C) } ================================================ FILE: en/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go ================================================ // File: climbing_stairs_backtrack.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Backtracking */ func backtrack(choices []int, state, n int, res []int) { // When climbing to the n-th stair, add 1 to the solution count if state == n { res[0] = res[0] + 1 } // Traverse all choices for _, choice := range choices { // Pruning: not allowed to go beyond the n-th stair if state+choice > n { continue } // Attempt: make choice, update state backtrack(choices, state+choice, n, res) // Backtrack } } /* Climbing stairs: Backtracking */ func climbingStairsBacktrack(n int) int { // Can choose to climb up 1 or 2 stairs choices := []int{1, 2} // Start climbing from the 0-th stair state := 0 res := make([]int, 1) // Use res[0] to record the solution count res[0] = 0 backtrack(choices, state, n, res) return res[0] } ================================================ FILE: en/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go ================================================ // File: climbing_stairs_constraint_dp.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Climbing stairs with constraint: Dynamic programming */ func climbingStairsConstraintDP(n int) int { if n == 1 || n == 2 { return 1 } // Initialize dp table, used to store solutions to subproblems dp := make([][3]int, n+1) // Initial state: preset the solution to the smallest subproblem dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // State transition: gradually solve larger subproblems from smaller ones for i := 3; i <= n; i++ { dp[i][1] = dp[i-1][2] dp[i][2] = dp[i-2][1] + dp[i-2][2] } return dp[n][1] + dp[n][2] } ================================================ FILE: en/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go ================================================ // File: climbing_stairs_dfs.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Search */ func dfs(i int) int { // Known dp[1] and dp[2], return them if i == 1 || i == 2 { return i } // dp[i] = dp[i-1] + dp[i-2] count := dfs(i-1) + dfs(i-2) return count } /* Climbing stairs: Search */ func climbingStairsDFS(n int) int { return dfs(n) } ================================================ FILE: en/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go ================================================ // File: climbing_stairs_dfs_mem.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Memoization search */ func dfsMem(i int, mem []int) int { // Known dp[1] and dp[2], return them if i == 1 || i == 2 { return i } // If record dp[i] exists, return it directly if mem[i] != -1 { return mem[i] } // dp[i] = dp[i-1] + dp[i-2] count := dfsMem(i-1, mem) + dfsMem(i-2, mem) // Record dp[i] mem[i] = count return count } /* Climbing stairs: Memoization search */ func climbingStairsDFSMem(n int) int { // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record mem := make([]int, n+1) for i := range mem { mem[i] = -1 } return dfsMem(n, mem) } ================================================ FILE: en/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go ================================================ // File: climbing_stairs_dp.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Climbing stairs: Dynamic programming */ func climbingStairsDP(n int) int { if n == 1 || n == 2 { return n } // Initialize dp table, used to store solutions to subproblems dp := make([]int, n+1) // Initial state: preset the solution to the smallest subproblem dp[1] = 1 dp[2] = 2 // State transition: gradually solve larger subproblems from smaller ones for i := 3; i <= n; i++ { dp[i] = dp[i-1] + dp[i-2] } return dp[n] } /* Climbing stairs: Space-optimized dynamic programming */ func climbingStairsDPComp(n int) int { if n == 1 || n == 2 { return n } a, b := 1, 2 // State transition: gradually solve larger subproblems from smaller ones for i := 3; i <= n; i++ { a, b = b, a+b } return b } ================================================ FILE: en/codes/go/chapter_dynamic_programming/climbing_stairs_test.go ================================================ // File: climbing_stairs_test.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestClimbingStairsBacktrack(t *testing.T) { n := 9 res := climbingStairsBacktrack(n) fmt.Printf("Climbing %d stairs has %d solutions\n", n, res) } func TestClimbingStairsDFS(t *testing.T) { n := 9 res := climbingStairsDFS(n) fmt.Printf("Climbing %d stairs has %d solutions\n", n, res) } func TestClimbingStairsDFSMem(t *testing.T) { n := 9 res := climbingStairsDFSMem(n) fmt.Printf("Climbing %d stairs has %d solutions\n", n, res) } func TestClimbingStairsDP(t *testing.T) { n := 9 res := climbingStairsDP(n) fmt.Printf("Climbing %d stairs has %d solutions\n", n, res) } func TestClimbingStairsDPComp(t *testing.T) { n := 9 res := climbingStairsDPComp(n) fmt.Printf("Climbing %d stairs has %d solutions\n", n, res) } func TestClimbingStairsConstraintDP(t *testing.T) { n := 9 res := climbingStairsConstraintDP(n) fmt.Printf("Climbing %d stairs has %d solutions\n", n, res) } func TestMinCostClimbingStairsDPComp(t *testing.T) { cost := []int{0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1} fmt.Printf("Input stair cost list is %v\n", cost) res := minCostClimbingStairsDP(cost) fmt.Printf("Minimum cost to climb stairs is %d\n", res) res = minCostClimbingStairsDPComp(cost) fmt.Printf("Minimum cost to climb stairs is %d\n", res) } ================================================ FILE: en/codes/go/chapter_dynamic_programming/coin_change.go ================================================ // File: coin_change.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* Coin change: Dynamic programming */ func coinChangeDP(coins []int, amt int) int { n := len(coins) max := amt + 1 // Initialize dp table dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, amt+1) } // State transition: first row and first column for a := 1; a <= amt; a++ { dp[0][a] = max } // State transition: rest of the rows and columns for i := 1; i <= n; i++ { for a := 1; a <= amt; a++ { if coins[i-1] > a { // If exceeds target amount, don't select coin i dp[i][a] = dp[i-1][a] } else { // The smaller value between not selecting and selecting coin i dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1))) } } } if dp[n][amt] != max { return dp[n][amt] } return -1 } /* Coin change: Dynamic programming */ func coinChangeDPComp(coins []int, amt int) int { n := len(coins) max := amt + 1 // Initialize dp table dp := make([]int, amt+1) for i := 1; i <= amt; i++ { dp[i] = max } // State transition for i := 1; i <= n; i++ { // Traverse in forward order for a := 1; a <= amt; a++ { if coins[i-1] > a { // If exceeds target amount, don't select coin i dp[a] = dp[a] } else { // The smaller value between not selecting and selecting coin i dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1))) } } } if dp[amt] != max { return dp[amt] } return -1 } ================================================ FILE: en/codes/go/chapter_dynamic_programming/coin_change_ii.go ================================================ // File: coin_change_ii.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Coin change II: Dynamic programming */ func coinChangeIIDP(coins []int, amt int) int { n := len(coins) // Initialize dp table dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, amt+1) } // Initialize first column for i := 0; i <= n; i++ { dp[i][0] = 1 } // State transition: rest of the rows and columns for i := 1; i <= n; i++ { for a := 1; a <= amt; a++ { if coins[i-1] > a { // If exceeds target amount, don't select coin i dp[i][a] = dp[i-1][a] } else { // Sum of the two options: not selecting and selecting coin i dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]] } } } return dp[n][amt] } /* Coin change II: Space-optimized dynamic programming */ func coinChangeIIDPComp(coins []int, amt int) int { n := len(coins) // Initialize dp table dp := make([]int, amt+1) dp[0] = 1 // State transition for i := 1; i <= n; i++ { // Traverse in forward order for a := 1; a <= amt; a++ { if coins[i-1] > a { // If exceeds target amount, don't select coin i dp[a] = dp[a] } else { // Sum of the two options: not selecting and selecting coin i dp[a] = dp[a] + dp[a-coins[i-1]] } } } return dp[amt] } ================================================ FILE: en/codes/go/chapter_dynamic_programming/coin_change_test.go ================================================ // File: coin_change_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestCoinChange(t *testing.T) { coins := []int{1, 2, 5} amt := 4 // Dynamic programming res := coinChangeDP(coins, amt) fmt.Printf("Minimum number of coins needed to make target amount is %d\n", res) // Space-optimized dynamic programming res = coinChangeDPComp(coins, amt) fmt.Printf("Minimum number of coins needed to make target amount is %d\n", res) } ================================================ FILE: en/codes/go/chapter_dynamic_programming/edit_distance.go ================================================ // File: edit_distance.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Edit distance: Brute-force search */ func editDistanceDFS(s string, t string, i int, j int) int { // If both s and t are empty, return 0 if i == 0 && j == 0 { return 0 } // If s is empty, return length of t if i == 0 { return j } // If t is empty, return length of s if j == 0 { return i } // If two characters are equal, skip both characters if s[i-1] == t[j-1] { return editDistanceDFS(s, t, i-1, j-1) } // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 insert := editDistanceDFS(s, t, i, j-1) deleted := editDistanceDFS(s, t, i-1, j) replace := editDistanceDFS(s, t, i-1, j-1) // Return minimum edit steps return MinInt(MinInt(insert, deleted), replace) + 1 } /* Edit distance: Memoization search */ func editDistanceDFSMem(s string, t string, mem [][]int, i int, j int) int { // If both s and t are empty, return 0 if i == 0 && j == 0 { return 0 } // If s is empty, return length of t if i == 0 { return j } // If t is empty, return length of s if j == 0 { return i } // If there's a record, return it directly if mem[i][j] != -1 { return mem[i][j] } // If two characters are equal, skip both characters if s[i-1] == t[j-1] { return editDistanceDFSMem(s, t, mem, i-1, j-1) } // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 insert := editDistanceDFSMem(s, t, mem, i, j-1) deleted := editDistanceDFSMem(s, t, mem, i-1, j) replace := editDistanceDFSMem(s, t, mem, i-1, j-1) // Record and return minimum edit steps mem[i][j] = MinInt(MinInt(insert, deleted), replace) + 1 return mem[i][j] } /* Edit distance: Dynamic programming */ func editDistanceDP(s string, t string) int { n := len(s) m := len(t) dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, m+1) } // State transition: first row and first column for i := 1; i <= n; i++ { dp[i][0] = i } for j := 1; j <= m; j++ { dp[0][j] = j } // State transition: rest of the rows and columns for i := 1; i <= n; i++ { for j := 1; j <= m; j++ { if s[i-1] == t[j-1] { // If two characters are equal, skip both characters dp[i][j] = dp[i-1][j-1] } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1 } } } return dp[n][m] } /* Edit distance: Space-optimized dynamic programming */ func editDistanceDPComp(s string, t string) int { n := len(s) m := len(t) dp := make([]int, m+1) // State transition: first row for j := 1; j <= m; j++ { dp[j] = j } // State transition: rest of the rows for i := 1; i <= n; i++ { // State transition: first column leftUp := dp[0] // Temporarily store dp[i-1, j-1] dp[0] = i // State transition: rest of the columns for j := 1; j <= m; j++ { temp := dp[j] if s[i-1] == t[j-1] { // If two characters are equal, skip both characters dp[j] = leftUp } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1 } leftUp = temp // Update for next round's dp[i-1, j-1] } } return dp[m] } func MinInt(a, b int) int { if a < b { return a } return b } ================================================ FILE: en/codes/go/chapter_dynamic_programming/edit_distance_test.go ================================================ // File: edit_distance_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestEditDistanceDFS(test *testing.T) { s := "bag" t := "pack" n := len(s) m := len(t) // Brute-force search res := editDistanceDFS(s, t, n, m) fmt.Printf("Changing %s to %s requires a minimum of %d edits\n", s, t, res) // Memoization search mem := make([][]int, n+1) for i := 0; i <= n; i++ { mem[i] = make([]int, m+1) for j := 0; j <= m; j++ { mem[i][j] = -1 } } res = editDistanceDFSMem(s, t, mem, n, m) fmt.Printf("Changing %s to %s requires a minimum of %d edits\n", s, t, res) // Dynamic programming res = editDistanceDP(s, t) fmt.Printf("Changing %s to %s requires a minimum of %d edits\n", s, t, res) // Space-optimized dynamic programming res = editDistanceDPComp(s, t) fmt.Printf("Changing %s to %s requires a minimum of %d edits\n", s, t, res) } ================================================ FILE: en/codes/go/chapter_dynamic_programming/knapsack.go ================================================ // File: knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* 0-1 knapsack: Brute-force search */ func knapsackDFS(wgt, val []int, i, c int) int { // If all items have been selected or knapsack has no remaining capacity, return value 0 if i == 0 || c == 0 { return 0 } // If exceeds knapsack capacity, can only choose not to put it in if wgt[i-1] > c { return knapsackDFS(wgt, val, i-1, c) } // Calculate the maximum value of not putting in and putting in item i no := knapsackDFS(wgt, val, i-1, c) yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1] // Return the larger value of the two options return int(math.Max(float64(no), float64(yes))) } /* 0-1 knapsack: Memoization search */ func knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int { // If all items have been selected or knapsack has no remaining capacity, return value 0 if i == 0 || c == 0 { return 0 } // If there's a record, return it directly if mem[i][c] != -1 { return mem[i][c] } // If exceeds knapsack capacity, can only choose not to put it in if wgt[i-1] > c { return knapsackDFSMem(wgt, val, mem, i-1, c) } // Calculate the maximum value of not putting in and putting in item i no := knapsackDFSMem(wgt, val, mem, i-1, c) yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1] // Return the larger value of the two options mem[i][c] = int(math.Max(float64(no), float64(yes))) return mem[i][c] } /* 0-1 knapsack: Dynamic programming */ func knapsackDP(wgt, val []int, cap int) int { n := len(wgt) // Initialize dp table dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, cap+1) } // State transition for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i-1][c] } else { // The larger value between not selecting and selecting item i dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1]))) } } } return dp[n][cap] } /* 0-1 knapsack: Space-optimized dynamic programming */ func knapsackDPComp(wgt, val []int, cap int) int { n := len(wgt) // Initialize dp table dp := make([]int, cap+1) // State transition for i := 1; i <= n; i++ { // Traverse in reverse order for c := cap; c >= 1; c-- { if wgt[i-1] <= c { // The larger value between not selecting and selecting item i dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) } } } return dp[cap] } ================================================ FILE: en/codes/go/chapter_dynamic_programming/knapsack_test.go ================================================ // File: knapsack_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestKnapsack(t *testing.T) { wgt := []int{10, 20, 30, 40, 50} val := []int{50, 120, 150, 210, 240} c := 50 n := len(wgt) // Brute-force search res := knapsackDFS(wgt, val, n, c) fmt.Printf("Maximum item value not exceeding knapsack capacity is %d\n", res) // Memoization search mem := make([][]int, n+1) for i := 0; i <= n; i++ { mem[i] = make([]int, c+1) for j := 0; j <= c; j++ { mem[i][j] = -1 } } res = knapsackDFSMem(wgt, val, mem, n, c) fmt.Printf("Maximum item value not exceeding knapsack capacity is %d\n", res) // Dynamic programming res = knapsackDP(wgt, val, c) fmt.Printf("Maximum item value not exceeding knapsack capacity is %d\n", res) // Space-optimized dynamic programming res = knapsackDPComp(wgt, val, c) fmt.Printf("Maximum item value not exceeding knapsack capacity is %d\n", res) } func TestUnboundedKnapsack(t *testing.T) { wgt := []int{1, 2, 3} val := []int{5, 11, 15} c := 4 // Dynamic programming res := unboundedKnapsackDP(wgt, val, c) fmt.Printf("Maximum item value not exceeding knapsack capacity is %d\n", res) // Space-optimized dynamic programming res = unboundedKnapsackDPComp(wgt, val, c) fmt.Printf("Maximum item value not exceeding knapsack capacity is %d\n", res) } ================================================ FILE: en/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go ================================================ // File: min_cost_climbing_stairs_dp.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Minimum cost climbing stairs: Dynamic programming */ func minCostClimbingStairsDP(cost []int) int { n := len(cost) - 1 if n == 1 || n == 2 { return cost[n] } min := func(a, b int) int { if a < b { return a } return b } // Initialize dp table, used to store solutions to subproblems dp := make([]int, n+1) // Initial state: preset the solution to the smallest subproblem dp[1] = cost[1] dp[2] = cost[2] // State transition: gradually solve larger subproblems from smaller ones for i := 3; i <= n; i++ { dp[i] = min(dp[i-1], dp[i-2]) + cost[i] } return dp[n] } /* Minimum cost climbing stairs: Space-optimized dynamic programming */ func minCostClimbingStairsDPComp(cost []int) int { n := len(cost) - 1 if n == 1 || n == 2 { return cost[n] } min := func(a, b int) int { if a < b { return a } return b } // Initial state: preset the solution to the smallest subproblem a, b := cost[1], cost[2] // State transition: gradually solve larger subproblems from smaller ones for i := 3; i <= n; i++ { tmp := b b = min(a, tmp) + cost[i] a = tmp } return b } ================================================ FILE: en/codes/go/chapter_dynamic_programming/min_path_sum.go ================================================ // File: min_path_sum.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* Minimum path sum: Brute-force search */ func minPathSumDFS(grid [][]int, i, j int) int { // If it's the top-left cell, terminate the search if i == 0 && j == 0 { return grid[0][0] } // If row or column index is out of bounds, return +∞ cost if i < 0 || j < 0 { return math.MaxInt } // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1) up := minPathSumDFS(grid, i-1, j) left := minPathSumDFS(grid, i, j-1) // Return the minimum path cost from top-left to (i, j) return int(math.Min(float64(left), float64(up))) + grid[i][j] } /* Minimum path sum: Memoization search */ func minPathSumDFSMem(grid, mem [][]int, i, j int) int { // If it's the top-left cell, terminate the search if i == 0 && j == 0 { return grid[0][0] } // If row or column index is out of bounds, return +∞ cost if i < 0 || j < 0 { return math.MaxInt } // If there's a record, return it directly if mem[i][j] != -1 { return mem[i][j] } // Minimum path cost for left and upper cells up := minPathSumDFSMem(grid, mem, i-1, j) left := minPathSumDFSMem(grid, mem, i, j-1) // Record and return the minimum path cost from top-left to (i, j) mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j] return mem[i][j] } /* Minimum path sum: Dynamic programming */ func minPathSumDP(grid [][]int) int { n, m := len(grid), len(grid[0]) // Initialize dp table dp := make([][]int, n) for i := 0; i < n; i++ { dp[i] = make([]int, m) } dp[0][0] = grid[0][0] // State transition: first row for j := 1; j < m; j++ { dp[0][j] = dp[0][j-1] + grid[0][j] } // State transition: first column for i := 1; i < n; i++ { dp[i][0] = dp[i-1][0] + grid[i][0] } // State transition: rest of the rows and columns for i := 1; i < n; i++ { for j := 1; j < m; j++ { dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j] } } return dp[n-1][m-1] } /* Minimum path sum: Space-optimized dynamic programming */ func minPathSumDPComp(grid [][]int) int { n, m := len(grid), len(grid[0]) // Initialize dp table dp := make([]int, m) // State transition: first row dp[0] = grid[0][0] for j := 1; j < m; j++ { dp[j] = dp[j-1] + grid[0][j] } // State transition: rest of the rows and columns for i := 1; i < n; i++ { // State transition: first column dp[0] = dp[0] + grid[i][0] // State transition: rest of the columns for j := 1; j < m; j++ { dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j] } } return dp[m-1] } ================================================ FILE: en/codes/go/chapter_dynamic_programming/min_path_sum_test.go ================================================ // File: min_path_sum_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestMinPathSum(t *testing.T) { grid := [][]int{ {1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}, } n, m := len(grid), len(grid[0]) // Brute-force search res := minPathSumDFS(grid, n-1, m-1) fmt.Printf("Minimum path sum from top-left to bottom-right is %d\n", res) // Memoization search mem := make([][]int, n) for i := 0; i < n; i++ { mem[i] = make([]int, m) for j := 0; j < m; j++ { mem[i][j] = -1 } } res = minPathSumDFSMem(grid, mem, n-1, m-1) fmt.Printf("Minimum path sum from top-left to bottom-right is %d\n", res) // Dynamic programming res = minPathSumDP(grid) fmt.Printf("Minimum path sum from top-left to bottom-right is %d\n", res) // Space-optimized dynamic programming res = minPathSumDPComp(grid) fmt.Printf("Minimum path sum from top-left to bottom-right is %d\n", res) } ================================================ FILE: en/codes/go/chapter_dynamic_programming/unbounded_knapsack.go ================================================ // File: unbounded_knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* Unbounded knapsack: Dynamic programming */ func unboundedKnapsackDP(wgt, val []int, cap int) int { n := len(wgt) // Initialize dp table dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, cap+1) } // State transition for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i-1][c] } else { // The larger value between not selecting and selecting item i dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1]))) } } } return dp[n][cap] } /* Unbounded knapsack: Space-optimized dynamic programming */ func unboundedKnapsackDPComp(wgt, val []int, cap int) int { n := len(wgt) // Initialize dp table dp := make([]int, cap+1) // State transition for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // If exceeds knapsack capacity, don't select item i dp[c] = dp[c] } else { // The larger value between not selecting and selecting item i dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) } } } return dp[cap] } ================================================ FILE: en/codes/go/chapter_graph/graph_adjacency_list.go ================================================ // File: graph_adjacency_list.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "strconv" "strings" . "github.com/krahets/hello-algo/pkg" ) /* Undirected graph class based on adjacency list */ type graphAdjList struct { // Adjacency list, key: vertex, value: all adjacent vertices of that vertex adjList map[Vertex][]Vertex } /* Constructor */ func newGraphAdjList(edges [][]Vertex) *graphAdjList { g := &graphAdjList{ adjList: make(map[Vertex][]Vertex), } // Add all vertices and edges for _, edge := range edges { g.addVertex(edge[0]) g.addVertex(edge[1]) g.addEdge(edge[0], edge[1]) } return g } /* Get the number of vertices */ func (g *graphAdjList) size() int { return len(g.adjList) } /* Add edge */ func (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) { _, ok1 := g.adjList[vet1] _, ok2 := g.adjList[vet2] if !ok1 || !ok2 || vet1 == vet2 { panic("error") } // Add edge vet1 - vet2, add anonymous struct{}, g.adjList[vet1] = append(g.adjList[vet1], vet2) g.adjList[vet2] = append(g.adjList[vet2], vet1) } /* Remove edge */ func (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) { _, ok1 := g.adjList[vet1] _, ok2 := g.adjList[vet2] if !ok1 || !ok2 || vet1 == vet2 { panic("error") } // Remove edge vet1 - vet2 g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2) g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1) } /* Add vertex */ func (g *graphAdjList) addVertex(vet Vertex) { _, ok := g.adjList[vet] if ok { return } // Add a new linked list in the adjacency list g.adjList[vet] = make([]Vertex, 0) } /* Remove vertex */ func (g *graphAdjList) removeVertex(vet Vertex) { _, ok := g.adjList[vet] if !ok { panic("error") } // Remove the linked list corresponding to vertex vet in the adjacency list delete(g.adjList, vet) // Traverse the linked lists of other vertices and remove all edges containing vet for v, list := range g.adjList { g.adjList[v] = DeleteSliceElms(list, vet) } } /* Print adjacency list */ func (g *graphAdjList) print() { var builder strings.Builder fmt.Printf("Adjacency list = \n") for k, v := range g.adjList { builder.WriteString("\t\t" + strconv.Itoa(k.Val) + ": ") for _, vet := range v { builder.WriteString(strconv.Itoa(vet.Val) + " ") } fmt.Println(builder.String()) builder.Reset() } } ================================================ FILE: en/codes/go/chapter_graph/graph_adjacency_list_test.go ================================================ // File: graph_adjacency_list_test.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphAdjList(t *testing.T) { /* Add edge */ v := ValsToVets([]int{1, 3, 2, 5, 4}) edges := [][]Vertex{{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}} graph := newGraphAdjList(edges) fmt.Println("After initialization, graph is:") graph.print() /* Add edge */ // Vertices 1, 3 are v[0], v[1] graph.addEdge(v[0], v[2]) fmt.Println("\nAfter adding edge 1-2, graph is") graph.print() /* Remove edge */ // Vertex 3 is v[1] graph.removeEdge(v[0], v[1]) fmt.Println("\nAfter removing edge 1-3, graph is") graph.print() /* Add vertex */ v5 := NewVertex(6) graph.addVertex(v5) fmt.Println("\nAfter adding vertex 6, graph is") graph.print() /* Remove vertex */ // Vertex 3 is v[1] graph.removeVertex(v[1]) fmt.Println("\nAfter removing vertex 3, graph is") graph.print() } ================================================ FILE: en/codes/go/chapter_graph/graph_adjacency_matrix.go ================================================ // File: graph_adjacency_matrix.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import "fmt" /* Undirected graph class based on adjacency matrix */ type graphAdjMat struct { // Vertex list, where the element represents the "vertex value" and the index represents the "vertex index" vertices []int // Adjacency matrix, where the row and column indices correspond to the "vertex index" adjMat [][]int } /* Constructor */ func newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat { // Add vertex n := len(vertices) adjMat := make([][]int, n) for i := range adjMat { adjMat[i] = make([]int, n) } // Initialize graph g := &graphAdjMat{ vertices: vertices, adjMat: adjMat, } // Add edge // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices for i := range edges { g.addEdge(edges[i][0], edges[i][1]) } return g } /* Get the number of vertices */ func (g *graphAdjMat) size() int { return len(g.vertices) } /* Add vertex */ func (g *graphAdjMat) addVertex(val int) { n := g.size() // Add the value of the new vertex to the vertex list g.vertices = append(g.vertices, val) // Add a row to the adjacency matrix newRow := make([]int, n) g.adjMat = append(g.adjMat, newRow) // Add a column to the adjacency matrix for i := range g.adjMat { g.adjMat[i] = append(g.adjMat[i], 0) } } /* Remove vertex */ func (g *graphAdjMat) removeVertex(index int) { if index >= g.size() { return } // Remove the vertex at index from the vertex list g.vertices = append(g.vertices[:index], g.vertices[index+1:]...) // Remove the row at index from the adjacency matrix g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...) // Remove the column at index from the adjacency matrix for i := range g.adjMat { g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...) } } /* Add edge */ // Parameters i, j correspond to the vertices element indices func (g *graphAdjMat) addEdge(i, j int) { // Handle index out of bounds and equality if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { fmt.Errorf("%s", "Index Out Of Bounds Exception") } // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i) g.adjMat[i][j] = 1 g.adjMat[j][i] = 1 } /* Remove edge */ // Parameters i, j correspond to the vertices element indices func (g *graphAdjMat) removeEdge(i, j int) { // Handle index out of bounds and equality if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { fmt.Errorf("%s", "Index Out Of Bounds Exception") } g.adjMat[i][j] = 0 g.adjMat[j][i] = 0 } /* Print adjacency matrix */ func (g *graphAdjMat) print() { fmt.Printf("\tVertex list = %v\n", g.vertices) fmt.Printf("\tAdjacency matrix = \n") for i := range g.adjMat { fmt.Printf("\t\t\t%v\n", g.adjMat[i]) } } ================================================ FILE: en/codes/go/chapter_graph/graph_adjacency_matrix_test.go ================================================ // File: graph_adjacency_matrix_test.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" ) func TestGraphAdjMat(t *testing.T) { /* Add edge */ // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices vertices := []int{1, 3, 2, 5, 4} edges := [][]int{{0, 1}, {1, 2}, {2, 3}, {0, 3}, {2, 4}, {3, 4}} graph := newGraphAdjMat(vertices, edges) fmt.Println("After initialization, graph is:") graph.print() /* Add edge */ // Add vertex graph.addEdge(0, 2) fmt.Println("After adding edge 1-2, graph is") graph.print() /* Remove edge */ // Vertices 1, 3 have indices 0, 1 respectively graph.removeEdge(0, 1) fmt.Println("After removing edge 1-3, graph is") graph.print() /* Add vertex */ graph.addVertex(6) fmt.Println("After adding vertex 6, graph is") graph.print() /* Remove vertex */ // Vertex 3 has index 1 graph.removeVertex(1) fmt.Println("After removing vertex 3, graph is") graph.print() } ================================================ FILE: en/codes/go/chapter_graph/graph_bfs.go ================================================ // File: graph_bfs.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( . "github.com/krahets/hello-algo/pkg" ) /* Breadth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex func graphBFS(g *graphAdjList, startVet Vertex) []Vertex { // Vertex traversal sequence res := make([]Vertex, 0) // Hash set for recording vertices that have been visited visited := make(map[Vertex]struct{}) visited[startVet] = struct{}{} // Queue used to implement BFS, using slice to simulate queue queue := make([]Vertex, 0) queue = append(queue, startVet) // Starting from vertex vet, loop until all vertices are visited for len(queue) > 0 { // Dequeue the front vertex vet := queue[0] queue = queue[1:] // Record visited vertex res = append(res, vet) // Traverse all adjacent vertices of this vertex for _, adjVet := range g.adjList[vet] { _, isExist := visited[adjVet] // Only enqueue unvisited vertices if !isExist { queue = append(queue, adjVet) visited[adjVet] = struct{}{} } } } // Return vertex traversal sequence return res } ================================================ FILE: en/codes/go/chapter_graph/graph_bfs_test.go ================================================ // File: graph_bfs_test.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphBFS(t *testing.T) { /* Add edge */ vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) edges := [][]Vertex{ {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[1], vets[4]}, {vets[2], vets[5]}, {vets[3], vets[4]}, {vets[3], vets[6]}, {vets[4], vets[5]}, {vets[4], vets[7]}, {vets[5], vets[8]}, {vets[6], vets[7]}, {vets[7], vets[8]}} graph := newGraphAdjList(edges) fmt.Println("After initialization, graph is:") graph.print() /* Breadth-first traversal */ res := graphBFS(graph, vets[0]) fmt.Println("Breadth-first traversal (BFS) vertex sequence is:") PrintSlice(VetsToVals(res)) } ================================================ FILE: en/codes/go/chapter_graph/graph_dfs.go ================================================ // File: graph_dfs.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( . "github.com/krahets/hello-algo/pkg" ) /* Depth-first traversal helper function */ func dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) { // append operation returns a new reference, must reassign original reference to new slice's reference *res = append(*res, vet) visited[vet] = struct{}{} // Traverse all adjacent vertices of this vertex for _, adjVet := range g.adjList[vet] { _, isExist := visited[adjVet] // Recursively visit adjacent vertices if !isExist { dfs(g, visited, res, adjVet) } } } /* Depth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex func graphDFS(g *graphAdjList, startVet Vertex) []Vertex { // Vertex traversal sequence res := make([]Vertex, 0) // Hash set for recording vertices that have been visited visited := make(map[Vertex]struct{}) dfs(g, visited, &res, startVet) // Return vertex traversal sequence return res } ================================================ FILE: en/codes/go/chapter_graph/graph_dfs_test.go ================================================ // File: graph_dfs_test.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphDFS(t *testing.T) { /* Add edge */ vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6}) edges := [][]Vertex{ {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[2], vets[5]}, {vets[4], vets[5]}, {vets[5], vets[6]}} graph := newGraphAdjList(edges) fmt.Println("After initialization, graph is:") graph.print() /* Depth-first traversal */ res := graphDFS(graph, vets[0]) fmt.Println("Depth-first traversal (DFS) vertex sequence is:") PrintSlice(VetsToVals(res)) } ================================================ FILE: en/codes/go/chapter_greedy/coin_change_greedy.go ================================================ // File: coin_change_greedy.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy /* Coin change: Greedy algorithm */ func coinChangeGreedy(coins []int, amt int) int { // Assume coins list is sorted i := len(coins) - 1 count := 0 // Loop to make greedy choices until no remaining amount for amt > 0 { // Find the coin that is less than and closest to the remaining amount for i > 0 && coins[i] > amt { i-- } // Choose coins[i] amt -= coins[i] count++ } // If no feasible solution is found, return -1 if amt != 0 { return -1 } return count } ================================================ FILE: en/codes/go/chapter_greedy/coin_change_greedy_test.go ================================================ // File: coin_change_greedy_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestCoinChangeGreedy(t *testing.T) { // Greedy algorithm: Can guarantee finding the global optimal solution coins := []int{1, 5, 10, 20, 50, 100} amt := 186 res := coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("Minimum number of coins needed to make %d is %d\n", amt, res) // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = []int{1, 20, 50} amt = 60 res = coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("Minimum number of coins needed to make %d is %d\n", amt, res) fmt.Println("Actually the minimum number needed is 3, i.e., 20 + 20 + 20") // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = []int{1, 49, 50} amt = 98 res = coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("Minimum number of coins needed to make %d is %d\n", amt, res) fmt.Println("Actually the minimum number needed is 2, i.e., 49 + 49") } ================================================ FILE: en/codes/go/chapter_greedy/fractional_knapsack.go ================================================ // File: fractional_knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "sort" /* Item */ type Item struct { w int // Item weight v int // Item value } /* Fractional knapsack: Greedy algorithm */ func fractionalKnapsack(wgt []int, val []int, cap int) float64 { // Create item list with two attributes: weight, value items := make([]Item, len(wgt)) for i := 0; i < len(wgt); i++ { items[i] = Item{wgt[i], val[i]} } // Sort by unit value item.v / item.w from high to low sort.Slice(items, func(i, j int) bool { return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w) }) // Loop for greedy selection res := 0.0 for _, item := range items { if item.w <= cap { // If remaining capacity is sufficient, put the entire current item into the knapsack res += float64(item.v) cap -= item.w } else { // If remaining capacity is insufficient, put part of the current item into the knapsack res += float64(item.v) / float64(item.w) * float64(cap) // No remaining capacity, so break out of the loop break } } return res } ================================================ FILE: en/codes/go/chapter_greedy/fractional_knapsack_test.go ================================================ // File: fractional_knapsack_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestFractionalKnapsack(t *testing.T) { wgt := []int{10, 20, 30, 40, 50} val := []int{50, 120, 150, 210, 240} capacity := 50 // Greedy algorithm res := fractionalKnapsack(wgt, val, capacity) fmt.Println("Maximum item value not exceeding knapsack capacity is", res) } ================================================ FILE: en/codes/go/chapter_greedy/max_capacity.go ================================================ // File: max_capacity.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "math" /* Max capacity: Greedy algorithm */ func maxCapacity(ht []int) int { // Initialize i, j to be at both ends of the array i, j := 0, len(ht)-1 // Initial max capacity is 0 res := 0 // Loop for greedy selection until the two boards meet for i < j { // Update max capacity capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i) res = int(math.Max(float64(res), float64(capacity))) // Move the shorter board inward if ht[i] < ht[j] { i++ } else { j-- } } return res } ================================================ FILE: en/codes/go/chapter_greedy/max_capacity_test.go ================================================ // File: max_capacity_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestMaxCapacity(t *testing.T) { ht := []int{3, 8, 5, 2, 7, 7, 3, 4} // Greedy algorithm res := maxCapacity(ht) fmt.Println("Maximum capacity is", res) } ================================================ FILE: en/codes/go/chapter_greedy/max_product_cutting.go ================================================ // File: max_product_cutting.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "math" /* Max product cutting: Greedy algorithm */ func maxProductCutting(n int) int { // When n <= 3, must cut out a 1 if n <= 3 { return 1 * (n - 1) } // Greedily cut out 3, a is the number of 3s, b is the remainder a := n / 3 b := n % 3 if b == 1 { // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2 return int(math.Pow(3, float64(a-1))) * 2 * 2 } if b == 2 { // When the remainder is 2, do nothing return int(math.Pow(3, float64(a))) * 2 } // When the remainder is 0, do nothing return int(math.Pow(3, float64(a))) } ================================================ FILE: en/codes/go/chapter_greedy/max_product_cutting_test.go ================================================ // File: max_product_cutting_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestMaxProductCutting(t *testing.T) { n := 58 // Greedy algorithm res := maxProductCutting(n) fmt.Println("Maximum cutting product is", res) } ================================================ FILE: en/codes/go/chapter_hashing/array_hash_map.go ================================================ // File: array_hash_map.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import "fmt" /* Key-value pair */ type pair struct { key int val string } /* Hash table based on array implementation */ type arrayHashMap struct { buckets []*pair } /* Initialize hash table */ func newArrayHashMap() *arrayHashMap { // Initialize array with 100 buckets buckets := make([]*pair, 100) return &arrayHashMap{buckets: buckets} } /* Hash function */ func (a *arrayHashMap) hashFunc(key int) int { index := key % 100 return index } /* Query operation */ func (a *arrayHashMap) get(key int) string { index := a.hashFunc(key) pair := a.buckets[index] if pair == nil { return "Not Found" } return pair.val } /* Add operation */ func (a *arrayHashMap) put(key int, val string) { pair := &pair{key: key, val: val} index := a.hashFunc(key) a.buckets[index] = pair } /* Remove operation */ func (a *arrayHashMap) remove(key int) { index := a.hashFunc(key) // Set to nil to delete a.buckets[index] = nil } /* Get all key pairs */ func (a *arrayHashMap) pairSet() []*pair { var pairs []*pair for _, pair := range a.buckets { if pair != nil { pairs = append(pairs, pair) } } return pairs } /* Get all keys */ func (a *arrayHashMap) keySet() []int { var keys []int for _, pair := range a.buckets { if pair != nil { keys = append(keys, pair.key) } } return keys } /* Get all values */ func (a *arrayHashMap) valueSet() []string { var values []string for _, pair := range a.buckets { if pair != nil { values = append(values, pair.val) } } return values } /* Print hash table */ func (a *arrayHashMap) print() { for _, pair := range a.buckets { if pair != nil { fmt.Println(pair.key, "->", pair.val) } } } ================================================ FILE: en/codes/go/chapter_hashing/array_hash_map_test.go ================================================ // File: array_hash_map_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "testing" ) func TestArrayHashMap(t *testing.T) { /* Initialize hash table */ hmap := newArrayHashMap() /* Add operation */ // Add key-value pair (key, value) to the hash table hmap.put(12836, "Xiao Ha") hmap.put(15937, "Xiao Luo") hmap.put(16750, "Xiao Suan") hmap.put(13276, "Xiao Fa") hmap.put(10583, "Xiao Ya") fmt.Println("\nAfter adding is complete, hash table is\nKey -> Value") hmap.print() /* Query operation */ // Input key into hash table to get value name := hmap.get(15937) fmt.Println("\nInput student ID 15937, query name " + name) /* Remove operation */ // Remove key-value pair (key, value) from hash table hmap.remove(10583) fmt.Println("\nAfter removing 10583, hash table is\nKey -> Value") hmap.print() /* Traverse hash table */ fmt.Println("\nTraverse key-value pairs Key->Value") for _, kv := range hmap.pairSet() { fmt.Println(kv.key, " -> ", kv.val) } fmt.Println("\nTraverse keys only Key") for _, key := range hmap.keySet() { fmt.Println(key) } fmt.Println("\nTraverse values only Value") for _, val := range hmap.valueSet() { fmt.Println(val) } } ================================================ FILE: en/codes/go/chapter_hashing/hash_collision_test.go ================================================ // File: hash_collision_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "testing" ) func TestHashMapChaining(t *testing.T) { /* Initialize hash table */ hmap := newHashMapChaining() /* Add operation */ // Add key-value pair (key, value) to the hash table hmap.put(12836, "Xiao Ha") hmap.put(15937, "Xiao Luo") hmap.put(16750, "Xiao Suan") hmap.put(13276, "Xiao Fa") hmap.put(10583, "Xiao Ya") fmt.Println("\nAfter adding is complete, hash table is\nKey -> Value") hmap.print() /* Query operation */ // Input key into hash table to get value name := hmap.get(15937) fmt.Println("\nInput student ID 15937, found name", name) /* Remove operation */ // Remove key-value pair (key, value) from hash table hmap.remove(12836) fmt.Println("\nAfter removing 12836, hash table is\nKey -> Value") hmap.print() } func TestHashMapOpenAddressing(t *testing.T) { /* Initialize hash table */ hmap := newHashMapOpenAddressing() /* Add operation */ // Add key-value pair (key, value) to the hash table hmap.put(12836, "Xiao Ha") hmap.put(15937, "Xiao Luo") hmap.put(16750, "Xiao Suan") hmap.put(13276, "Xiao Fa") hmap.put(10583, "Xiao Ya") fmt.Println("\nAfter adding is complete, hash table is\nKey -> Value") hmap.print() /* Query operation */ // Input key into hash table to get value name := hmap.get(13276) fmt.Println("\nInput student ID 13276, query name ", name) /* Remove operation */ // Remove key-value pair (key, value) from hash table hmap.remove(16750) fmt.Println("\nAfter removing 16750, hash table is\nKey -> Value") hmap.print() } ================================================ FILE: en/codes/go/chapter_hashing/hash_map_chaining.go ================================================ // File: hash_map_chaining.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import ( "fmt" "strconv" "strings" ) /* Hash table with separate chaining */ type hashMapChaining struct { size int // Number of key-value pairs capacity int // Hash table capacity loadThres float64 // Load factor threshold for triggering expansion extendRatio int // Expansion multiplier buckets [][]pair // Bucket array } /* Constructor */ func newHashMapChaining() *hashMapChaining { buckets := make([][]pair, 4) for i := 0; i < 4; i++ { buckets[i] = make([]pair, 0) } return &hashMapChaining{ size: 0, capacity: 4, loadThres: 2.0 / 3.0, extendRatio: 2, buckets: buckets, } } /* Hash function */ func (m *hashMapChaining) hashFunc(key int) int { return key % m.capacity } /* Load factor */ func (m *hashMapChaining) loadFactor() float64 { return float64(m.size) / float64(m.capacity) } /* Query operation */ func (m *hashMapChaining) get(key int) string { idx := m.hashFunc(key) bucket := m.buckets[idx] // Traverse bucket, if key is found, return corresponding val for _, p := range bucket { if p.key == key { return p.val } } // Return empty string if key not found return "" } /* Add operation */ func (m *hashMapChaining) put(key int, val string) { // When load factor exceeds threshold, perform expansion if m.loadFactor() > m.loadThres { m.extend() } idx := m.hashFunc(key) // Traverse bucket, if specified key is encountered, update corresponding val and return for i := range m.buckets[idx] { if m.buckets[idx][i].key == key { m.buckets[idx][i].val = val return } } // If key does not exist, append key-value pair to the end p := pair{ key: key, val: val, } m.buckets[idx] = append(m.buckets[idx], p) m.size += 1 } /* Remove operation */ func (m *hashMapChaining) remove(key int) { idx := m.hashFunc(key) // Traverse bucket and remove key-value pair from it for i, p := range m.buckets[idx] { if p.key == key { // Slice deletion m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...) m.size -= 1 break } } } /* Expand hash table */ func (m *hashMapChaining) extend() { // Temporarily store the original hash table tmpBuckets := make([][]pair, len(m.buckets)) for i := 0; i < len(m.buckets); i++ { tmpBuckets[i] = make([]pair, len(m.buckets[i])) copy(tmpBuckets[i], m.buckets[i]) } // Initialize expanded new hash table m.capacity *= m.extendRatio m.buckets = make([][]pair, m.capacity) for i := 0; i < m.capacity; i++ { m.buckets[i] = make([]pair, 0) } m.size = 0 // Move key-value pairs from original hash table to new hash table for _, bucket := range tmpBuckets { for _, p := range bucket { m.put(p.key, p.val) } } } /* Print hash table */ func (m *hashMapChaining) print() { var builder strings.Builder for _, bucket := range m.buckets { builder.WriteString("[") for _, p := range bucket { builder.WriteString(strconv.Itoa(p.key) + " -> " + p.val + " ") } builder.WriteString("]") fmt.Println(builder.String()) builder.Reset() } } ================================================ FILE: en/codes/go/chapter_hashing/hash_map_open_addressing.go ================================================ // File: hash_map_open_addressing.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import ( "fmt" ) /* Hash table with open addressing */ type hashMapOpenAddressing struct { size int // Number of key-value pairs capacity int // Hash table capacity loadThres float64 // Load factor threshold for triggering expansion extendRatio int // Expansion multiplier buckets []*pair // Bucket array TOMBSTONE *pair // Removal marker } /* Constructor */ func newHashMapOpenAddressing() *hashMapOpenAddressing { return &hashMapOpenAddressing{ size: 0, capacity: 4, loadThres: 2.0 / 3.0, extendRatio: 2, buckets: make([]*pair, 4), TOMBSTONE: &pair{-1, "-1"}, } } /* Hash function */ func (h *hashMapOpenAddressing) hashFunc(key int) int { return key % h.capacity // Calculate hash value based on key } /* Load factor */ func (h *hashMapOpenAddressing) loadFactor() float64 { return float64(h.size) / float64(h.capacity) // Calculate current load factor } /* Search for bucket index corresponding to key */ func (h *hashMapOpenAddressing) findBucket(key int) int { index := h.hashFunc(key) // Get initial index firstTombstone := -1 // Record position of first TOMBSTONE encountered for h.buckets[index] != nil { if h.buckets[index].key == key { if firstTombstone != -1 { // If a removal marker was encountered before, move the key-value pair to that index h.buckets[firstTombstone] = h.buckets[index] h.buckets[index] = h.TOMBSTONE return firstTombstone // Return the moved bucket index } return index // Return found index } if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE { firstTombstone = index // Record position of first deletion marker encountered } index = (index + 1) % h.capacity // Linear probing, wrap around to head if past tail } // If key does not exist, return the index for insertion if firstTombstone != -1 { return firstTombstone } return index } /* Query operation */ func (h *hashMapOpenAddressing) get(key int) string { index := h.findBucket(key) // Search for bucket index corresponding to key if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { return h.buckets[index].val // If key-value pair is found, return corresponding val } return "" // Return "" if key-value pair does not exist } /* Add operation */ func (h *hashMapOpenAddressing) put(key int, val string) { if h.loadFactor() > h.loadThres { h.extend() // When load factor exceeds threshold, perform expansion } index := h.findBucket(key) // Search for bucket index corresponding to key if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE { h.buckets[index] = &pair{key, val} // If key-value pair does not exist, add the key-value pair h.size++ } else { h.buckets[index].val = val // If key-value pair found, overwrite val } } /* Remove operation */ func (h *hashMapOpenAddressing) remove(key int) { index := h.findBucket(key) // Search for bucket index corresponding to key if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { h.buckets[index] = h.TOMBSTONE // If key-value pair is found, overwrite it with removal marker h.size-- } } /* Expand hash table */ func (h *hashMapOpenAddressing) extend() { oldBuckets := h.buckets // Temporarily store the original hash table h.capacity *= h.extendRatio // Update capacity h.buckets = make([]*pair, h.capacity) // Initialize expanded new hash table h.size = 0 // Reset size // Move key-value pairs from original hash table to new hash table for _, pair := range oldBuckets { if pair != nil && pair != h.TOMBSTONE { h.put(pair.key, pair.val) } } } /* Print hash table */ func (h *hashMapOpenAddressing) print() { for _, pair := range h.buckets { if pair == nil { fmt.Println("nil") } else if pair == h.TOMBSTONE { fmt.Println("TOMBSTONE") } else { fmt.Printf("%d -> %s\n", pair.key, pair.val) } } } ================================================ FILE: en/codes/go/chapter_hashing/hash_map_test.go ================================================ // File: hash_map_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHashMap(t *testing.T) { /* Initialize hash table */ hmap := make(map[int]string) /* Add operation */ // Add key-value pair (key, value) to the hash table hmap[12836] = "Xiao Ha" hmap[15937] = "Xiao Luo" hmap[16750] = "Xiao Suan" hmap[13276] = "Xiao Fa" hmap[10583] = "Xiao Ya" fmt.Println("\nAfter adding is complete, hash table is\nKey -> Value") PrintMap(hmap) /* Query operation */ // Input key into hash table to get value name := hmap[15937] fmt.Println("\nInput student ID 15937, query name ", name) /* Remove operation */ // Remove key-value pair (key, value) from hash table delete(hmap, 10583) fmt.Println("\nAfter removing 10583, hash table is\nKey -> Value") PrintMap(hmap) /* Traverse hash table */ // Traverse key-value pairs fmt.Println("\nTraverse key-value pairs Key->Value") for key, value := range hmap { fmt.Println(key, "->", value) } // Traverse keys only fmt.Println("\nTraverse keys only Key") for key := range hmap { fmt.Println(key) } // Traverse values only fmt.Println("\nTraverse values only Value") for _, value := range hmap { fmt.Println(value) } } func TestSimpleHash(t *testing.T) { var hash int key := "Hello Algo" hash = addHash(key) fmt.Println("Additive hash value is " + strconv.Itoa(hash)) hash = mulHash(key) fmt.Println("Multiplicative hash value is " + strconv.Itoa(hash)) hash = xorHash(key) fmt.Println("XOR hash value is " + strconv.Itoa(hash)) hash = rotHash(key) fmt.Println("Rotational hash value is " + strconv.Itoa(hash)) } ================================================ FILE: en/codes/go/chapter_hashing/simple_hash.go ================================================ // File: simple_hash.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import "fmt" /* Additive hash */ func addHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = (hash + int64(b)) % modulus } return int(hash) } /* Multiplicative hash */ func mulHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = (31*hash + int64(b)) % modulus } return int(hash) } /* XOR hash */ func xorHash(key string) int { hash := 0 modulus := 1000000007 for _, b := range []byte(key) { fmt.Println(int(b)) hash ^= int(b) hash = (31*hash + int(b)) % modulus } return hash & modulus } /* Rotational hash */ func rotHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus } return int(hash) } ================================================ FILE: en/codes/go/chapter_heap/heap.go ================================================ // File: heap.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap // In Go, integer max heap can be built by implementing heap.Interface // Implementing heap.Interface requires also implementing sort.Interface type intHeap []any // Push function of heap.Interface, implements pushing element to heap func (h *intHeap) Push(x any) { // Push and Pop use pointer receiver as parameter // Because they not only adjust the slice content, but also modify the slice length. *h = append(*h, x.(int)) } // Pop function of heap.Interface, implements popping heap top element func (h *intHeap) Pop() any { // Element to be popped is stored at the end last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Len function of sort.Interface func (h *intHeap) Len() int { return len(*h) } // Less function of sort.Interface func (h *intHeap) Less(i, j int) bool { // If implementing min heap, need to change to less than sign return (*h)[i].(int) > (*h)[j].(int) } // Swap function of sort.Interface func (h *intHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Top gets heap top element func (h *intHeap) Top() any { return (*h)[0] } ================================================ FILE: en/codes/go/chapter_heap/heap_test.go ================================================ // File: heap_test.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap import ( "container/heap" "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func testPush(h *intHeap, val int) { // Call heap.Interface function to add element heap.Push(h, val) fmt.Printf("\nAfter element %d pushes to heap \n", val) PrintHeap(*h) } func testPop(h *intHeap) { // Call heap.Interface function to remove element val := heap.Pop(h) fmt.Printf("\nAfter heap top element %d pops from heap \n", val) PrintHeap(*h) } func TestHeap(t *testing.T) { /* Initialize heap */ // Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap maxHeap := &intHeap{} heap.Init(maxHeap) /* Element enters heap */ testPush(maxHeap, 1) testPush(maxHeap, 3) testPush(maxHeap, 2) testPush(maxHeap, 5) testPush(maxHeap, 4) /* Check if heap is empty */ top := maxHeap.Top() fmt.Printf("Heap top element is %d\n", top) /* Time complexity is O(n), not O(nlogn) */ testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) /* Get heap size */ size := len(*maxHeap) fmt.Printf("Heap size is %d\n", size) /* Check if heap is empty */ isEmpty := len(*maxHeap) == 0 fmt.Printf("Is heap empty %t\n", isEmpty) } func TestMyHeap(t *testing.T) { /* Initialize heap */ // Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap maxHeap := newMaxHeap([]any{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}) fmt.Printf("After input array and building heap\n") maxHeap.print() /* Check if heap is empty */ peek := maxHeap.peek() fmt.Printf("\nHeap top element is %d\n", peek) /* Element enters heap */ val := 7 maxHeap.push(val) fmt.Printf("\nAfter element %d enters heap\n", val) maxHeap.print() /* Time complexity is O(n), not O(nlogn) */ peek = maxHeap.pop() fmt.Printf("\nAfter heap top element %d exits heap\n", peek) maxHeap.print() /* Get heap size */ size := maxHeap.size() fmt.Printf("\nHeap element count is %d\n", size) /* Check if heap is empty */ isEmpty := maxHeap.isEmpty() fmt.Printf("\nIs heap empty %t\n", isEmpty) } func TestTopKHeap(t *testing.T) { /* Initialize heap */ // Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap nums := []int{1, 7, 6, 3, 2} k := 3 res := topKHeap(nums, k) fmt.Printf("The largest " + strconv.Itoa(k) + " elements are") PrintHeap(*res) } ================================================ FILE: en/codes/go/chapter_heap/my_heap.go ================================================ // File: my_heap.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap import ( "fmt" . "github.com/krahets/hello-algo/pkg" ) type maxHeap struct { // Use slice instead of array to avoid expansion issues data []any } /* Constructor, build empty heap */ func newHeap() *maxHeap { return &maxHeap{ data: make([]any, 0), } } /* Constructor, build heap from slice */ func newMaxHeap(nums []any) *maxHeap { // Add list elements to heap as is h := &maxHeap{data: nums} for i := h.parent(len(h.data) - 1); i >= 0; i-- { // Heapify all nodes except leaf nodes h.siftDown(i) } return h } /* Get index of left child node */ func (h *maxHeap) left(i int) int { return 2*i + 1 } /* Get index of right child node */ func (h *maxHeap) right(i int) int { return 2*i + 2 } /* Get index of parent node */ func (h *maxHeap) parent(i int) int { // Floor division return (i - 1) / 2 } /* Swap elements */ func (h *maxHeap) swap(i, j int) { h.data[i], h.data[j] = h.data[j], h.data[i] } /* Get heap size */ func (h *maxHeap) size() int { return len(h.data) } /* Check if heap is empty */ func (h *maxHeap) isEmpty() bool { return len(h.data) == 0 } /* Access top element */ func (h *maxHeap) peek() any { return h.data[0] } /* Element enters heap */ func (h *maxHeap) push(val any) { // Add node h.data = append(h.data, val) // Heapify from bottom to top h.siftUp(len(h.data) - 1) } /* Starting from node i, heapify from bottom to top */ func (h *maxHeap) siftUp(i int) { for true { // Get parent node of node i p := h.parent(i) // When "crossing root node" or "node needs no repair", end heapify if p < 0 || h.data[i].(int) <= h.data[p].(int) { break } // Swap two nodes h.swap(i, p) // Loop upward heapify i = p } } /* Element exits heap */ func (h *maxHeap) pop() any { // Handle empty case if h.isEmpty() { fmt.Println("error") return nil } // Delete node h.swap(0, h.size()-1) // Remove node val := h.data[len(h.data)-1] h.data = h.data[:len(h.data)-1] // Return top element h.siftDown(0) // Return heap top element return val } /* Starting from node i, heapify from top to bottom */ func (h *maxHeap) siftDown(i int) { for true { // Find node with maximum value among nodes i, l, r, denoted as max l, r, max := h.left(i), h.right(i), i if l < h.size() && h.data[l].(int) > h.data[max].(int) { max = l } if r < h.size() && h.data[r].(int) > h.data[max].(int) { max = r } // Swap two nodes if max == i { break } // Swap two nodes h.swap(i, max) // Loop downwards heapification i = max } } /* Driver Code */ func (h *maxHeap) print() { PrintHeap(h.data) } ================================================ FILE: en/codes/go/chapter_heap/top_k.go ================================================ // File: top_k.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_heap import "container/heap" type minHeap []any func (h *minHeap) Len() int { return len(*h) } func (h *minHeap) Less(i, j int) bool { return (*h)[i].(int) < (*h)[j].(int) } func (h *minHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Push method of heap.Interface, implements pushing element to heap func (h *minHeap) Push(x any) { *h = append(*h, x.(int)) } // Pop method of heap.Interface, implements popping heap top element func (h *minHeap) Pop() any { // Element to be popped is stored at the end last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Top gets heap top element func (h *minHeap) Top() any { return (*h)[0] } /* Find the largest k elements in array based on heap */ func topKHeap(nums []int, k int) *minHeap { // Python's heapq module implements min heap by default h := &minHeap{} heap.Init(h) // Enter the first k elements of array into heap for i := 0; i < k; i++ { heap.Push(h, nums[i]) } // Starting from the (k+1)th element, maintain heap length as k for i := k; i < len(nums); i++ { // If current element is greater than top element, top element exits heap, current element enters heap if nums[i] > h.Top().(int) { heap.Pop(h) heap.Push(h, nums[i]) } } return h } ================================================ FILE: en/codes/go/chapter_searching/binary_search.go ================================================ // File: binary_search.go // Created Time: 2022-12-05 // Author: Slone123c (274325721@qq.com) package chapter_searching /* Binary search (closed interval on both sides) */ func binarySearch(nums []int, target int) int { // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array i, j := 0, len(nums)-1 // Loop, exit when the search interval is empty (empty when i > j) for i <= j { m := i + (j-i)/2 // Calculate the midpoint index m if nums[m] < target { // This means target is in the interval [m+1, j] i = m + 1 } else if nums[m] > target { // This means target is in the interval [i, m-1] j = m - 1 } else { // Found the target element, return its index return m } } // Target element not found, return -1 return -1 } /* Binary search (left-closed right-open interval) */ func binarySearchLCRO(nums []int, target int) int { // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1 i, j := 0, len(nums) // Loop, exit when the search interval is empty (empty when i = j) for i < j { m := i + (j-i)/2 // Calculate the midpoint index m if nums[m] < target { // This means target is in the interval [m+1, j) i = m + 1 } else if nums[m] > target { // This means target is in the interval [i, m) j = m } else { // Found the target element, return its index return m } } // Target element not found, return -1 return -1 } ================================================ FILE: en/codes/go/chapter_searching/binary_search_edge.go ================================================ // File: binary_search_edge.go // Created Time: 2023-08-23 // Author: Reanon (793584285@qq.com) package chapter_searching /* Binary search for the leftmost target */ func binarySearchLeftEdge(nums []int, target int) int { // Equivalent to finding the insertion point of target i := binarySearchInsertion(nums, target) // Target not found, return -1 if i == len(nums) || nums[i] != target { return -1 } // Found target, return index i return i } /* Binary search for the rightmost target */ func binarySearchRightEdge(nums []int, target int) int { // Convert to finding the leftmost target + 1 i := binarySearchInsertion(nums, target+1) // j points to the rightmost target, i points to the first element greater than target j := i - 1 // Target not found, return -1 if j == -1 || nums[j] != target { return -1 } // Found target, return index j return j } ================================================ FILE: en/codes/go/chapter_searching/binary_search_insertion.go ================================================ // File: binary_search_insertion.go // Created Time: 2023-08-23 // Author: Reanon (793584285@qq.com) package chapter_searching /* Binary search for insertion point (no duplicate elements) */ func binarySearchInsertionSimple(nums []int, target int) int { // Initialize closed interval [0, n-1] i, j := 0, len(nums)-1 for i <= j { // Calculate the midpoint index m m := i + (j-i)/2 if nums[m] < target { // target is in the interval [m+1, j] i = m + 1 } else if nums[m] > target { // target is in the interval [i, m-1] j = m - 1 } else { // Found target, return insertion point m return m } } // Target not found, return insertion point i return i } /* Binary search for insertion point (with duplicate elements) */ func binarySearchInsertion(nums []int, target int) int { // Initialize closed interval [0, n-1] i, j := 0, len(nums)-1 for i <= j { // Calculate the midpoint index m m := i + (j-i)/2 if nums[m] < target { // target is in the interval [m+1, j] i = m + 1 } else if nums[m] > target { // target is in the interval [i, m-1] j = m - 1 } else { // The first element less than target is in the interval [i, m-1] j = m - 1 } } // Return insertion point i return i } ================================================ FILE: en/codes/go/chapter_searching/binary_search_test.go ================================================ // File: binary_search_test.go // Created Time: 2022-12-05 // Author: Slone123c (274325721@qq.com) package chapter_searching import ( "fmt" "testing" ) func TestBinarySearch(t *testing.T) { var ( target = 6 nums = []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} expected = 2 ) // Perform binary search in array actual := binarySearch(nums, target) fmt.Println("Index of target element 6 =", actual) if actual != expected { t.Errorf("Index of target element 6 = %d, should be %d", actual, expected) } } func TestBinarySearchEdge(t *testing.T) { // Array with duplicate elements nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} fmt.Println("\nArray nums = ", nums) // Binary search left and right boundaries for _, target := range []int{6, 7} { index := binarySearchLeftEdge(nums, target) fmt.Println("Leftmost element", target, " index is", index) index = binarySearchRightEdge(nums, target) fmt.Println("Rightmost element", target, " index is", index) } } func TestBinarySearchInsertion(t *testing.T) { // Array without duplicate elements nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} fmt.Println("Array nums =", nums) // Binary search for insertion point for _, target := range []int{6, 9} { index := binarySearchInsertionSimple(nums, target) fmt.Println("Element", target, " insertion point index is", index) } // Array with duplicate elements nums = []int{1, 3, 6, 6, 6, 6, 6, 10, 12, 15} fmt.Println("\nArray nums =", nums) // Binary search for insertion point for _, target := range []int{2, 6, 20} { index := binarySearchInsertion(nums, target) fmt.Println("Element", target, " insertion point index is", index) } } ================================================ FILE: en/codes/go/chapter_searching/hashing_search.go ================================================ // File: hashing_search.go // Created Time: 2022-12-12 // Author: Slone123c (274325721@qq.com) package chapter_searching import . "github.com/krahets/hello-algo/pkg" /* Hash search (array) */ func hashingSearchArray(m map[int]int, target int) int { // Hash table's key: target element, value: index // If this key does not exist in the hash table, return -1 if index, ok := m[target]; ok { return index } else { return -1 } } /* Hash search (linked list) */ func hashingSearchLinkedList(m map[int]*ListNode, target int) *ListNode { // Hash table key: target node value, value: node object // Return nil if key does not exist in hash table if node, ok := m[target]; ok { return node } else { return nil } } ================================================ FILE: en/codes/go/chapter_searching/hashing_search_test.go ================================================ // File: hashing_search_test.go // Created Time: 2022-12-12 // Author: Slone123c (274325721@qq.com) package chapter_searching import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHashingSearch(t *testing.T) { target := 3 /* Hash search (array) */ nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} // Initialize hash table m := make(map[int]int) for i := 0; i < len(nums); i++ { m[nums[i]] = i } index := hashingSearchArray(m, target) fmt.Println("Index of target element 3 = ", index) /* Hash search (linked list) */ head := ArrayToLinkedList(nums) // Initialize hash table m1 := make(map[int]*ListNode) for head != nil { m1[head.Val] = head head = head.Next } node := hashingSearchLinkedList(m1, target) fmt.Println("Node object corresponding to target node value 3 is ", node) } ================================================ FILE: en/codes/go/chapter_searching/linear_search.go ================================================ // File: linear_search.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_searching import ( . "github.com/krahets/hello-algo/pkg" ) /* Linear search (array) */ func linearSearchArray(nums []int, target int) int { // Traverse array for i := 0; i < len(nums); i++ { // Found the target element, return its index if nums[i] == target { return i } } // Target element not found, return -1 return -1 } /* Linear search (linked list) */ func linearSearchLinkedList(node *ListNode, target int) *ListNode { // Traverse the linked list for node != nil { // Found the target node, return it if node.Val == target { return node } node = node.Next } // Target element not found, return nil return nil } ================================================ FILE: en/codes/go/chapter_searching/linear_search_test.go ================================================ // File: linear_search_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_searching import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLinearSearch(t *testing.T) { target := 3 nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} // Perform linear search in array index := linearSearchArray(nums, target) fmt.Println("Index of target element 3 =", index) // Perform linear search in linked list head := ArrayToLinkedList(nums) node := linearSearchLinkedList(head, target) fmt.Println("Node object with target value 3 is", node) } ================================================ FILE: en/codes/go/chapter_searching/two_sum.go ================================================ // File: two_sum.go // Created Time: 2022-11-25 // Author: reanon (793584285@qq.com) package chapter_searching /* Method 1: Brute force enumeration */ func twoSumBruteForce(nums []int, target int) []int { size := len(nums) // Two nested loops, time complexity is O(n^2) for i := 0; i < size-1; i++ { for j := i + 1; j < size; j++ { if nums[i]+nums[j] == target { return []int{i, j} } } } return nil } /* Method 2: Auxiliary hash table */ func twoSumHashTable(nums []int, target int) []int { // Auxiliary hash table, space complexity is O(n) hashTable := map[int]int{} // Single loop, time complexity is O(n) for idx, val := range nums { if preIdx, ok := hashTable[target-val]; ok { return []int{preIdx, idx} } hashTable[val] = idx } return nil } ================================================ FILE: en/codes/go/chapter_searching/two_sum_test.go ================================================ // File: two_sum_test.go // Created Time: 2022-11-25 // Author: reanon (793584285@qq.com) package chapter_searching import ( "fmt" "testing" ) func TestTwoSum(t *testing.T) { // ======= Test Case ======= nums := []int{2, 7, 11, 15} target := 13 // ====== Driver Code ====== // Method 1: Brute-force approach res := twoSumBruteForce(nums, target) fmt.Println("Method 1 res =", res) // Method 2: Hash table res = twoSumHashTable(nums, target) fmt.Println("Method 2 res =", res) } ================================================ FILE: en/codes/go/chapter_sorting/bubble_sort.go ================================================ // File: bubble_sort.go // Created Time: 2022-12-06 // Author: Slone123c (274325721@qq.com) package chapter_sorting /* Bubble sort */ func bubbleSort(nums []int) { // Outer loop: unsorted range is [0, i] for i := len(nums) - 1; i > 0; i-- { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // Swap nums[j] and nums[j + 1] nums[j], nums[j+1] = nums[j+1], nums[j] } } } } /* Bubble sort (flag optimization) */ func bubbleSortWithFlag(nums []int) { // Outer loop: unsorted range is [0, i] for i := len(nums) - 1; i > 0; i-- { flag := false // Initialize flag // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // Swap nums[j] and nums[j + 1] nums[j], nums[j+1] = nums[j+1], nums[j] flag = true // Record element swap } } if flag == false { // No elements were swapped in this round of "bubbling", exit directly break } } } ================================================ FILE: en/codes/go/chapter_sorting/bubble_sort_test.go ================================================ // File: bubble_sort_test.go // Created Time: 2022-12-06 // Author: Slone123c (274325721@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestBubbleSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} bubbleSort(nums) fmt.Println("After bubble sort completes, nums = ", nums) nums1 := []int{4, 1, 3, 1, 5, 2} bubbleSortWithFlag(nums1) fmt.Println("After bubble sort completes, nums1 = ", nums1) } ================================================ FILE: en/codes/go/chapter_sorting/bucket_sort.go ================================================ // File: bucket_sort.go // Created Time: 2023-03-27 // Author: Reanon (793584285@qq.com) package chapter_sorting import "sort" /* Bucket sort */ func bucketSort(nums []float64) { // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket k := len(nums) / 2 buckets := make([][]float64, k) for i := 0; i < k; i++ { buckets[i] = make([]float64, 0) } // 1. Distribute array elements into various buckets for _, num := range nums { // Input data range is [0, 1), use num * k to map to index range [0, k-1] i := int(num * float64(k)) // Add num to bucket i buckets[i] = append(buckets[i], num) } // 2. Sort each bucket for i := 0; i < k; i++ { // Use built-in slice sorting function, can also be replaced with other sorting algorithms sort.Float64s(buckets[i]) } // 3. Traverse buckets to merge results i := 0 for _, bucket := range buckets { for _, num := range bucket { nums[i] = num i++ } } } ================================================ FILE: en/codes/go/chapter_sorting/bucket_sort_test.go ================================================ // File: bucket_sort_test.go // Created Time: 2023-03-27 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestBucketSort(t *testing.T) { // Assume input data is floating point, interval [0, 1) nums := []float64{0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37} bucketSort(nums) fmt.Println("After bucket sort completes, nums = ", nums) } ================================================ FILE: en/codes/go/chapter_sorting/counting_sort.go ================================================ // File: counting_sort.go // Created Time: 2023-03-20 // Author: Reanon (793584285@qq.com) package chapter_sorting type CountingSort struct{} /* Counting sort */ // Simple implementation, cannot be used for sorting objects func countingSortNaive(nums []int) { // 1. Count the maximum element m in the array m := 0 for _, num := range nums { if num > m { m = num } } // 2. Count the occurrence of each number // counter[num] represents the occurrence of num counter := make([]int, m+1) for _, num := range nums { counter[num]++ } // 3. Traverse counter, filling each element back into the original array nums for i, num := 0, 0; num < m+1; num++ { for j := 0; j < counter[num]; j++ { nums[i] = num i++ } } } /* Counting sort */ // Complete implementation, can sort objects and is a stable sort func countingSort(nums []int) { // 1. Count the maximum element m in the array m := 0 for _, num := range nums { if num > m { m = num } } // 2. Count the occurrence of each number // counter[num] represents the occurrence of num counter := make([]int, m+1) for _, num := range nums { counter[num]++ } // 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" // counter[num]-1 is the last index where num appears in res for i := 0; i < m; i++ { counter[i+1] += counter[i] } // 4. Traverse nums in reverse order, placing each element into the result array res // Initialize the array res to record results n := len(nums) res := make([]int, n) for i := n - 1; i >= 0; i-- { num := nums[i] // Place num at the corresponding index res[counter[num]-1] = num // Decrement the prefix sum by 1, getting the next index to place num counter[num]-- } // Use result array res to overwrite the original array nums copy(nums, res) } ================================================ FILE: en/codes/go/chapter_sorting/counting_sort_test.go ================================================ // File: counting_sort_test.go // Created Time: 2023-03-20 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestCountingSort(t *testing.T) { nums := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} countingSortNaive(nums) fmt.Println("After counting sort (cannot sort objects) completes, nums = ", nums) nums1 := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} countingSort(nums1) fmt.Println("After counting sort completes, nums1 = ", nums1) } ================================================ FILE: en/codes/go/chapter_sorting/heap_sort.go ================================================ // File: heap_sort.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting /* Heap length is n, start heapifying node i, from top to bottom */ func siftDown(nums *[]int, n, i int) { for true { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break l := 2*i + 1 r := 2*i + 2 ma := i if l < n && (*nums)[l] > (*nums)[ma] { ma = l } if r < n && (*nums)[r] > (*nums)[ma] { ma = r } // Swap two nodes if ma == i { break } // Swap two nodes (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i] // Loop downwards heapification i = ma } } /* Heap sort */ func heapSort(nums *[]int) { // Build heap operation: heapify all nodes except leaves for i := len(*nums)/2 - 1; i >= 0; i-- { siftDown(nums, len(*nums), i) } // Extract the largest element from the heap and repeat for n-1 rounds for i := len(*nums) - 1; i > 0; i-- { // Delete node (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0] // Start heapifying the root node, from top to bottom siftDown(nums, i, 0) } } ================================================ FILE: en/codes/go/chapter_sorting/heap_sort_test.go ================================================ // File: heap_sort_test.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestHeapSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} heapSort(&nums) fmt.Println("After heap sort completes, nums = ", nums) } ================================================ FILE: en/codes/go/chapter_sorting/insertion_sort.go ================================================ // File: insertion_sort.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting /* Insertion sort */ func insertionSort(nums []int) { // Outer loop: sorted interval is [0, i-1] for i := 1; i < len(nums); i++ { base := nums[i] j := i - 1 // Inner loop: insert base into the correct position within the sorted interval [0, i-1] for j >= 0 && nums[j] > base { nums[j+1] = nums[j] // Move nums[j] to the right by one position j-- } nums[j+1] = base // Assign base to the correct position } } ================================================ FILE: en/codes/go/chapter_sorting/insertion_sort_test.go ================================================ // File: insertion_sort_test.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) func TestInsertionSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} insertionSort(nums) fmt.Println("After insertion sort, nums =", nums) } ================================================ FILE: en/codes/go/chapter_sorting/merge_sort.go ================================================ // File: merge_sort.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting /* Merge left subarray and right subarray */ func merge(nums []int, left, mid, right int) { // Left subarray interval is [left, mid], right subarray interval is [mid+1, right] // Create a temporary array tmp to store the merged results tmp := make([]int, right-left+1) // Initialize the start indices of the left and right subarrays i, j, k := left, mid+1, 0 // While both subarrays still have elements, compare and copy the smaller element into the temporary array for i <= mid && j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i] i++ } else { tmp[k] = nums[j] j++ } k++ } // Copy the remaining elements of the left and right subarrays into the temporary array for i <= mid { tmp[k] = nums[i] i++ k++ } for j <= right { tmp[k] = nums[j] j++ k++ } // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval for k := 0; k < len(tmp); k++ { nums[left+k] = tmp[k] } } /* Merge sort */ func mergeSort(nums []int, left, right int) { // Termination condition if left >= right { return } // Divide and conquer stage mid := left + (right - left) / 2 mergeSort(nums, left, mid) mergeSort(nums, mid+1, right) // Merge stage merge(nums, left, mid, right) } ================================================ FILE: en/codes/go/chapter_sorting/merge_sort_test.go ================================================ // File: merge_sort_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) func TestMergeSort(t *testing.T) { nums := []int{7, 3, 2, 6, 0, 1, 5, 4} mergeSort(nums, 0, len(nums)-1) fmt.Println("After merge sort completes, nums = ", nums) } ================================================ FILE: en/codes/go/chapter_sorting/quick_sort.go ================================================ // File: quick_sort.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting // Quick sort type quickSort struct{} // Quick sort (recursion depth optimization) type quickSortMedian struct{} // Quick sort (recursion depth optimization) type quickSortTailCall struct{} /* Sentinel partition */ func (q *quickSort) partition(nums []int, left, right int) int { // Use nums[left] as the pivot i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- // Search from right to left for the first element smaller than the pivot } for i < j && nums[i] <= nums[left] { i++ // Search from left to right for the first element greater than the pivot } // Swap elements nums[i], nums[j] = nums[j], nums[i] } // Swap the pivot to the boundary between the two subarrays nums[i], nums[left] = nums[left], nums[i] return i // Return the index of the pivot } /* Quick sort */ func (q *quickSort) quickSort(nums []int, left, right int) { // Terminate recursion when subarray length is 1 if left >= right { return } // Sentinel partition pivot := q.partition(nums, left, right) // Recursively process the left subarray and right subarray q.quickSort(nums, left, pivot-1) q.quickSort(nums, pivot+1, right) } /* Select the median of three candidate elements */ func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int { l, m, r := nums[left], nums[mid], nums[right] if (l <= m && m <= r) || (r <= m && m <= l) { return mid // m is between l and r } if (m <= l && l <= r) || (r <= l && l <= m) { return left // l is between m and r } return right } /* Sentinel partition (median of three) */ func (q *quickSortMedian) partition(nums []int, left, right int) int { // Use nums[left] as the pivot med := q.medianThree(nums, left, (left+right)/2, right) // Swap the median to the array's leftmost position nums[left], nums[med] = nums[med], nums[left] // Use nums[left] as the pivot i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- // Search from right to left for the first element smaller than the pivot } for i < j && nums[i] <= nums[left] { i++ // Search from left to right for the first element greater than the pivot } // Swap elements nums[i], nums[j] = nums[j], nums[i] } // Swap the pivot to the boundary between the two subarrays nums[i], nums[left] = nums[left], nums[i] return i // Return the index of the pivot } /* Quick sort */ func (q *quickSortMedian) quickSort(nums []int, left, right int) { // Terminate recursion when subarray length is 1 if left >= right { return } // Sentinel partition pivot := q.partition(nums, left, right) // Recursively process the left subarray and right subarray q.quickSort(nums, left, pivot-1) q.quickSort(nums, pivot+1, right) } /* Sentinel partition */ func (q *quickSortTailCall) partition(nums []int, left, right int) int { // Use nums[left] as the pivot i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- // Search from right to left for the first element smaller than the pivot } for i < j && nums[i] <= nums[left] { i++ // Search from left to right for the first element greater than the pivot } // Swap elements nums[i], nums[j] = nums[j], nums[i] } // Swap the pivot to the boundary between the two subarrays nums[i], nums[left] = nums[left], nums[i] return i // Return the index of the pivot } /* Quick sort (recursion depth optimization) */ func (q *quickSortTailCall) quickSort(nums []int, left, right int) { // Terminate when subarray length is 1 for left < right { // Sentinel partition operation pivot := q.partition(nums, left, right) // Perform quick sort on the shorter of the two subarrays if pivot-left < right-pivot { q.quickSort(nums, left, pivot-1) // Recursively sort the left subarray left = pivot + 1 // Remaining unsorted interval is [pivot + 1, right] } else { q.quickSort(nums, pivot+1, right) // Recursively sort the right subarray right = pivot - 1 // Remaining unsorted interval is [left, pivot - 1] } } } ================================================ FILE: en/codes/go/chapter_sorting/quick_sort_test.go ================================================ // File: quick_sort_test.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) // Quick sort func TestQuickSort(t *testing.T) { q := quickSort{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("After quick sort completes, nums = ", nums) } // Quick sort (recursion depth optimization) func TestQuickSortMedian(t *testing.T) { q := quickSortMedian{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("After quick sort (median pivot optimization), nums = ", nums) } // Quick sort (recursion depth optimization) func TestQuickSortTailCall(t *testing.T) { q := quickSortTailCall{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("After quick sort (recursion depth optimization), nums = ", nums) } ================================================ FILE: en/codes/go/chapter_sorting/radix_sort.go ================================================ // File: radix_sort.go // Created Time: 2023-01-18 // Author: Reanon (793584285@qq.com) package chapter_sorting import "math" /* Get the k-th digit of element num, where exp = 10^(k-1) */ func digit(num, exp int) int { // Passing exp instead of k can avoid repeated expensive exponentiation here return (num / exp) % 10 } /* Counting sort (based on nums k-th digit) */ func countingSortDigit(nums []int, exp int) { // Decimal digit range is 0~9, therefore need a bucket array of length 10 counter := make([]int, 10) n := len(nums) // Count the occurrence of digits 0~9 for i := 0; i < n; i++ { d := digit(nums[i], exp) // Get the k-th digit of nums[i], noted as d counter[d]++ // Count the occurrence of digit d } // Calculate prefix sum, converting "occurrence count" into "array index" for i := 1; i < 10; i++ { counter[i] += counter[i-1] } // Traverse in reverse, based on bucket statistics, place each element into res res := make([]int, n) for i := n - 1; i >= 0; i-- { d := digit(nums[i], exp) j := counter[d] - 1 // Get the index j for d in the array res[j] = nums[i] // Place the current element at index j counter[d]-- // Decrease the count of d by 1 } // Use result to overwrite the original array nums for i := 0; i < n; i++ { nums[i] = res[i] } } /* Radix sort */ func radixSort(nums []int) { // Get the maximum element of the array, used to determine the maximum number of digits max := math.MinInt for _, num := range nums { if num > max { max = num } } // Traverse from the lowest to the highest digit for exp := 1; max >= exp; exp *= 10 { // Perform counting sort on the k-th digit of array elements // k = 1 -> exp = 1 // k = 2 -> exp = 10 // i.e., exp = 10^(k-1) countingSortDigit(nums, exp) } } ================================================ FILE: en/codes/go/chapter_sorting/radix_sort_test.go ================================================ // File: radix_sort_test.go // Created Time: 2023-01-18 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestRadixSort(t *testing.T) { /* Radix sort */ nums := []int{10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996} radixSort(nums) fmt.Println("After radix sort completes, nums = ", nums) } ================================================ FILE: en/codes/go/chapter_sorting/selection_sort.go ================================================ // File: selection_sort.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting /* Selection sort */ func selectionSort(nums []int) { n := len(nums) // Outer loop: unsorted interval is [i, n-1] for i := 0; i < n-1; i++ { // Inner loop: find the smallest element within the unsorted interval k := i for j := i + 1; j < n; j++ { if nums[j] < nums[k] { // Record the index of the smallest element k = j } } // Swap the smallest element with the first element of the unsorted interval nums[i], nums[k] = nums[k], nums[i] } } ================================================ FILE: en/codes/go/chapter_sorting/selection_sort_test.go ================================================ // File: selection_sort_test.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestSelectionSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} selectionSort(nums) fmt.Println("After selection sort completes, nums = ", nums) } ================================================ FILE: en/codes/go/chapter_stack_and_queue/array_deque.go ================================================ // File: array_deque.go // Created Time: 2023-03-13 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import "fmt" /* Double-ended queue based on circular array implementation */ type arrayDeque struct { nums []int // Array for storing double-ended queue elements front int // Front pointer, points to the front of the queue element queSize int // Double-ended queue length queCapacity int // Queue capacity (maximum number of elements) } /* Access front of the queue element */ func newArrayDeque(queCapacity int) *arrayDeque { return &arrayDeque{ nums: make([]int, queCapacity), queCapacity: queCapacity, front: 0, queSize: 0, } } /* Get the length of the double-ended queue */ func (q *arrayDeque) size() int { return q.queSize } /* Check if the double-ended queue is empty */ func (q *arrayDeque) isEmpty() bool { return q.queSize == 0 } /* Calculate circular array index */ func (q *arrayDeque) index(i int) int { // Use modulo operation to wrap the array head and tail together // When i passes the tail of the array, return to the head // When i passes the head of the array, return to the tail return (i + q.queCapacity) % q.queCapacity } /* Front of the queue enqueue */ func (q *arrayDeque) pushFirst(num int) { if q.queSize == q.queCapacity { fmt.Println("Double-ended queue is full") return } // Use modulo operation to wrap front around to the tail after passing the head of the array // Add num to the front of the queue q.front = q.index(q.front - 1) // Add num to front of queue q.nums[q.front] = num q.queSize++ } /* Rear of the queue enqueue */ func (q *arrayDeque) pushLast(num int) { if q.queSize == q.queCapacity { fmt.Println("Double-ended queue is full") return } // Use modulo operation to wrap rear around to the head after passing the tail of the array rear := q.index(q.front + q.queSize) // Front pointer moves one position backward q.nums[rear] = num q.queSize++ } /* Rear of the queue dequeue */ func (q *arrayDeque) popFirst() any { num := q.peekFirst() if num == nil { return nil } // Move front pointer backward by one position q.front = q.index(q.front + 1) q.queSize-- return num } /* Access rear of the queue element */ func (q *arrayDeque) popLast() any { num := q.peekLast() if num == nil { return nil } q.queSize-- return num } /* Return list for printing */ func (q *arrayDeque) peekFirst() any { if q.isEmpty() { return nil } return q.nums[q.front] } /* Driver Code */ func (q *arrayDeque) peekLast() any { if q.isEmpty() { return nil } // Initialize double-ended queue last := q.index(q.front + q.queSize - 1) return q.nums[last] } /* Get Slice for printing */ func (q *arrayDeque) toSlice() []int { // Elements enqueue res := make([]int, q.queSize) for i, j := 0, q.front; i < q.queSize; i++ { res[i] = q.nums[q.index(j)] j++ } return res } ================================================ FILE: en/codes/go/chapter_stack_and_queue/array_queue.go ================================================ // File: array_queue.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue /* Queue based on circular array implementation */ type arrayQueue struct { nums []int // Array for storing queue elements front int // Front pointer, points to the front of the queue element queSize int // Queue length queCapacity int // Queue capacity (maximum number of elements) } /* Access front of the queue element */ func newArrayQueue(queCapacity int) *arrayQueue { return &arrayQueue{ nums: make([]int, queCapacity), queCapacity: queCapacity, front: 0, queSize: 0, } } /* Get the length of the queue */ func (q *arrayQueue) size() int { return q.queSize } /* Check if the queue is empty */ func (q *arrayQueue) isEmpty() bool { return q.queSize == 0 } /* Enqueue */ func (q *arrayQueue) push(num int) { // When rear == queCapacity, queue is full if q.queSize == q.queCapacity { return } // Use modulo operation to wrap rear around to the head after passing the tail of the array // Add num to the rear of the queue rear := (q.front + q.queSize) % q.queCapacity // Front pointer moves one position backward q.nums[rear] = num q.queSize++ } /* Dequeue */ func (q *arrayQueue) pop() any { num := q.peek() if num == nil { return nil } // Move front pointer backward by one position, if it passes the tail, return to array head q.front = (q.front + 1) % q.queCapacity q.queSize-- return num } /* Return list for printing */ func (q *arrayQueue) peek() any { if q.isEmpty() { return nil } return q.nums[q.front] } /* Get Slice for printing */ func (q *arrayQueue) toSlice() []int { rear := (q.front + q.queSize) if rear >= q.queCapacity { rear %= q.queCapacity return append(q.nums[q.front:], q.nums[:rear]...) } return q.nums[q.front:rear] } ================================================ FILE: en/codes/go/chapter_stack_and_queue/array_stack.go ================================================ // File: array_stack.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue /* Stack based on array implementation */ type arrayStack struct { data []int // Data } /* Access top of the stack element */ func newArrayStack() *arrayStack { return &arrayStack{ // Set stack length to 0, capacity to 16 data: make([]int, 0, 16), } } /* Stack length */ func (s *arrayStack) size() int { return len(s.data) } /* Is stack empty */ func (s *arrayStack) isEmpty() bool { return s.size() == 0 } /* Push */ func (s *arrayStack) push(v int) { // Slice will automatically expand s.data = append(s.data, v) } /* Pop */ func (s *arrayStack) pop() any { val := s.peek() s.data = s.data[:len(s.data)-1] return val } /* Get stack top element */ func (s *arrayStack) peek() any { if s.isEmpty() { return nil } val := s.data[len(s.data)-1] return val } /* Get Slice for printing */ func (s *arrayStack) toSlice() []int { return s.data } ================================================ FILE: en/codes/go/chapter_stack_and_queue/deque_test.go ================================================ // File: deque_test.go // Created Time: 2022-11-29 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestDeque(t *testing.T) { /* Get the length of the double-ended queue */ // In Go, use list as deque deque := list.New() /* Elements enqueue */ deque.PushBack(2) deque.PushBack(5) deque.PushBack(4) deque.PushFront(3) deque.PushFront(1) fmt.Print("Double-ended queue deque = ") PrintList(deque) /* Update element */ front := deque.Front() fmt.Println("Front element front =", front.Value) rear := deque.Back() fmt.Println("Rear element rear =", rear.Value) /* Element dequeue */ deque.Remove(front) fmt.Print("Front dequeue element front = ", front.Value, ", after front dequeue, deque = ") PrintList(deque) deque.Remove(rear) fmt.Print("Rear dequeue element rear = ", rear.Value, ", after rear dequeue, deque = ") PrintList(deque) /* Get the length of the double-ended queue */ size := deque.Len() fmt.Println("Deque length size =", size) /* Check if the double-ended queue is empty */ isEmpty := deque.Len() == 0 fmt.Println("Is deque empty =", isEmpty) } func TestArrayDeque(t *testing.T) { /* Get the length of the double-ended queue */ // In Go, use list as deque deque := newArrayDeque(16) /* Elements enqueue */ deque.pushLast(3) deque.pushLast(2) deque.pushLast(5) fmt.Print("Double-ended queue deque = ") PrintSlice(deque.toSlice()) /* Update element */ peekFirst := deque.peekFirst() fmt.Println("Front element peekFirst =", peekFirst) peekLast := deque.peekLast() fmt.Println("Rear element peekLast =", peekLast) /* Elements enqueue */ deque.pushLast(4) fmt.Print("After element 4 enqueues at rear, deque = ") PrintSlice(deque.toSlice()) deque.pushFirst(1) fmt.Print("After element 1 enqueues at front, deque = ") PrintSlice(deque.toSlice()) /* Element dequeue */ popFirst := deque.popFirst() fmt.Print("Front dequeue element popFirst = ", popFirst, ", after front dequeue, deque = ") PrintSlice(deque.toSlice()) popLast := deque.popLast() fmt.Print("Back dequeue element popLast = ", popLast, ", after rear dequeue, deque = ") PrintSlice(deque.toSlice()) /* Get the length of the double-ended queue */ size := deque.size() fmt.Println("Deque length size =", size) /* Check if the double-ended queue is empty */ isEmpty := deque.isEmpty() fmt.Println("Is deque empty =", isEmpty) } func TestLinkedListDeque(t *testing.T) { // Access front of the queue element deque := newLinkedListDeque() // Elements enqueue deque.pushLast(2) deque.pushLast(5) deque.pushLast(4) deque.pushFirst(3) deque.pushFirst(1) fmt.Print("Deque deque = ") PrintList(deque.toList()) // Return list for printing front := deque.peekFirst() fmt.Println("Front element front =", front) rear := deque.peekLast() fmt.Println("Rear element rear =", rear) // Element dequeue popFirst := deque.popFirst() fmt.Print("Front dequeue element popFirst = ", popFirst, ", after front dequeue, deque = ") PrintList(deque.toList()) popLast := deque.popLast() fmt.Print("Back dequeue element popLast = ", popLast, ", after rear dequeue, deque = ") PrintList(deque.toList()) // Get queue length size := deque.size() fmt.Println("Queue length size =", size) // Check if empty isEmpty := deque.isEmpty() fmt.Println("Is queue empty =", isEmpty) } // BenchmarkLinkedListDeque 67.92 ns/op in Mac M1 Pro func BenchmarkLinkedListDeque(b *testing.B) { deque := newLinkedListDeque() // use b.N for looping for i := 0; i < b.N; i++ { deque.pushLast(777) } for i := 0; i < b.N; i++ { deque.popFirst() } } ================================================ FILE: en/codes/go/chapter_stack_and_queue/linkedlist_deque.go ================================================ // File: linkedlist_deque.go // Created Time: 2022-11-29 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* Double-ended queue based on doubly linked list implementation */ type linkedListDeque struct { // Use built-in package list data *list.List } /* Initialize deque */ func newLinkedListDeque() *linkedListDeque { return &linkedListDeque{ data: list.New(), } } /* Front element enqueue */ func (s *linkedListDeque) pushFirst(value any) { s.data.PushFront(value) } /* Rear element enqueue */ func (s *linkedListDeque) pushLast(value any) { s.data.PushBack(value) } /* Check if the double-ended queue is empty */ func (s *linkedListDeque) popFirst() any { if s.isEmpty() { return nil } e := s.data.Front() s.data.Remove(e) return e.Value } /* Rear element dequeue */ func (s *linkedListDeque) popLast() any { if s.isEmpty() { return nil } e := s.data.Back() s.data.Remove(e) return e.Value } /* Return list for printing */ func (s *linkedListDeque) peekFirst() any { if s.isEmpty() { return nil } e := s.data.Front() return e.Value } /* Driver Code */ func (s *linkedListDeque) peekLast() any { if s.isEmpty() { return nil } e := s.data.Back() return e.Value } /* Get the length of the queue */ func (s *linkedListDeque) size() int { return s.data.Len() } /* Check if the queue is empty */ func (s *linkedListDeque) isEmpty() bool { return s.data.Len() == 0 } /* Get List for printing */ func (s *linkedListDeque) toList() *list.List { return s.data } ================================================ FILE: en/codes/go/chapter_stack_and_queue/linkedlist_queue.go ================================================ // File: linkedlist_queue.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* Queue based on linked list implementation */ type linkedListQueue struct { // Use built-in package list to implement queue data *list.List } /* Access front of the queue element */ func newLinkedListQueue() *linkedListQueue { return &linkedListQueue{ data: list.New(), } } /* Enqueue */ func (s *linkedListQueue) push(value any) { s.data.PushBack(value) } /* Dequeue */ func (s *linkedListQueue) pop() any { if s.isEmpty() { return nil } e := s.data.Front() s.data.Remove(e) return e.Value } /* Return list for printing */ func (s *linkedListQueue) peek() any { if s.isEmpty() { return nil } e := s.data.Front() return e.Value } /* Get the length of the queue */ func (s *linkedListQueue) size() int { return s.data.Len() } /* Check if the queue is empty */ func (s *linkedListQueue) isEmpty() bool { return s.data.Len() == 0 } /* Get List for printing */ func (s *linkedListQueue) toList() *list.List { return s.data } ================================================ FILE: en/codes/go/chapter_stack_and_queue/linkedlist_stack.go ================================================ // File: linkedlist_stack.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* Stack based on linked list implementation */ type linkedListStack struct { // Use built-in package list to implement stack data *list.List } /* Access top of the stack element */ func newLinkedListStack() *linkedListStack { return &linkedListStack{ data: list.New(), } } /* Push */ func (s *linkedListStack) push(value int) { s.data.PushBack(value) } /* Pop */ func (s *linkedListStack) pop() any { if s.isEmpty() { return nil } e := s.data.Back() s.data.Remove(e) return e.Value } /* Return list for printing */ func (s *linkedListStack) peek() any { if s.isEmpty() { return nil } e := s.data.Back() return e.Value } /* Get the length of the stack */ func (s *linkedListStack) size() int { return s.data.Len() } /* Check if the stack is empty */ func (s *linkedListStack) isEmpty() bool { return s.data.Len() == 0 } /* Get List for printing */ func (s *linkedListStack) toList() *list.List { return s.data } ================================================ FILE: en/codes/go/chapter_stack_and_queue/queue_test.go ================================================ // File: queue_test.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestQueue(t *testing.T) { /* Access front of the queue element */ // In Go, use list as queue queue := list.New() /* Elements enqueue */ queue.PushBack(1) queue.PushBack(3) queue.PushBack(2) queue.PushBack(5) queue.PushBack(4) fmt.Print("Queue queue = ") PrintList(queue) /* Return list for printing */ peek := queue.Front() fmt.Println("Front element peek =", peek.Value) /* Element dequeue */ pop := queue.Front() queue.Remove(pop) fmt.Print("Dequeue element pop = ", pop.Value, ", after dequeue, queue = ") PrintList(queue) /* Get the length of the queue */ size := queue.Len() fmt.Println("Queue length size =", size) /* Check if the queue is empty */ isEmpty := queue.Len() == 0 fmt.Println("Is queue empty =", isEmpty) } func TestArrayQueue(t *testing.T) { // Initialize queue using queue's common interface capacity := 10 queue := newArrayQueue(capacity) if queue.pop() != nil { t.Errorf("want:%v,got:%v", nil, queue.pop()) } // Elements enqueue queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) fmt.Print("Queue queue = ") PrintSlice(queue.toSlice()) // Return list for printing peek := queue.peek() fmt.Println("Front element peek =", peek) // Element dequeue pop := queue.pop() fmt.Print("Dequeue element pop = ", pop, ", after dequeue, queue = ") PrintSlice(queue.toSlice()) // Get queue length size := queue.size() fmt.Println("Queue length size =", size) // Check if empty isEmpty := queue.isEmpty() fmt.Println("Is queue empty =", isEmpty) /* Test circular array */ for i := 0; i < 10; i++ { queue.push(i) queue.pop() fmt.Print("Round ", i, " enqueue + dequeue, queue =") PrintSlice(queue.toSlice()) } } func TestLinkedListQueue(t *testing.T) { // Initialize queue queue := newLinkedListQueue() // Elements enqueue queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) fmt.Print("Queue queue = ") PrintList(queue.toList()) // Return list for printing peek := queue.peek() fmt.Println("Front element peek =", peek) // Element dequeue pop := queue.pop() fmt.Print("Dequeue element pop = ", pop, ", after dequeue, queue = ") PrintList(queue.toList()) // Get queue length size := queue.size() fmt.Println("Queue length size =", size) // Check if empty isEmpty := queue.isEmpty() fmt.Println("Is queue empty =", isEmpty) } // BenchmarkArrayQueue 8 ns/op in Mac M1 Pro func BenchmarkArrayQueue(b *testing.B) { capacity := 1000 queue := newArrayQueue(capacity) // use b.N for looping for i := 0; i < b.N; i++ { queue.push(777) } for i := 0; i < b.N; i++ { queue.pop() } } // BenchmarkLinkedQueue 62.66 ns/op in Mac M1 Pro func BenchmarkLinkedQueue(b *testing.B) { queue := newLinkedListQueue() // use b.N for looping for i := 0; i < b.N; i++ { queue.push(777) } for i := 0; i < b.N; i++ { queue.pop() } } ================================================ FILE: en/codes/go/chapter_stack_and_queue/stack_test.go ================================================ // File: stack_test.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestStack(t *testing.T) { /* Access top of the stack element */ // In Go, recommended to use Slice as stack var stack []int /* Elements push onto stack */ stack = append(stack, 1) stack = append(stack, 3) stack = append(stack, 2) stack = append(stack, 5) stack = append(stack, 4) fmt.Print("Stack stack = ") PrintSlice(stack) /* Return list for printing */ peek := stack[len(stack)-1] fmt.Println("Stack top element peek =", peek) /* Element pop from stack */ pop := stack[len(stack)-1] stack = stack[:len(stack)-1] fmt.Print("Pop element pop = ", pop, ", after pop, stack = ") PrintSlice(stack) /* Get the length of the stack */ size := len(stack) fmt.Println("Stack length size =", size) /* Check if empty */ isEmpty := len(stack) == 0 fmt.Println("Is stack empty =", isEmpty) } func TestArrayStack(t *testing.T) { // Initialize stack using interface stack := newArrayStack() // Elements push onto stack stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) fmt.Print("Stack stack = ") PrintSlice(stack.toSlice()) // Return list for printing peek := stack.peek() fmt.Println("Stack top element peek =", peek) // Element pop from stack pop := stack.pop() fmt.Print("Pop element pop = ", pop, ", after pop, stack = ") PrintSlice(stack.toSlice()) // Get the length of the stack size := stack.size() fmt.Println("Stack length size =", size) // Check if empty isEmpty := stack.isEmpty() fmt.Println("Is stack empty =", isEmpty) } func TestLinkedListStack(t *testing.T) { // Access top of the stack element stack := newLinkedListStack() // Elements push onto stack stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) fmt.Print("Stack stack = ") PrintList(stack.toList()) // Return list for printing peek := stack.peek() fmt.Println("Stack top element peek =", peek) // Element pop from stack pop := stack.pop() fmt.Print("Pop element pop = ", pop, ", after pop, stack = ") PrintList(stack.toList()) // Get the length of the stack size := stack.size() fmt.Println("Stack length size =", size) // Check if empty isEmpty := stack.isEmpty() fmt.Println("Is stack empty =", isEmpty) } // BenchmarkArrayStack 8 ns/op in Mac M1 Pro func BenchmarkArrayStack(b *testing.B) { stack := newArrayStack() // use b.N for looping for i := 0; i < b.N; i++ { stack.push(777) } for i := 0; i < b.N; i++ { stack.pop() } } // BenchmarkLinkedListStack 65.02 ns/op in Mac M1 Pro func BenchmarkLinkedListStack(b *testing.B) { stack := newLinkedListStack() // use b.N for looping for i := 0; i < b.N; i++ { stack.push(777) } for i := 0; i < b.N; i++ { stack.pop() } } ================================================ FILE: en/codes/go/chapter_tree/array_binary_tree.go ================================================ // File: array_binary_tree.go // Created Time: 2023-07-24 // Author: Reanon (793584285@qq.com) package chapter_tree /* Binary tree class represented by array */ type arrayBinaryTree struct { tree []any } /* Constructor */ func newArrayBinaryTree(arr []any) *arrayBinaryTree { return &arrayBinaryTree{ tree: arr, } } /* List capacity */ func (abt *arrayBinaryTree) size() int { return len(abt.tree) } /* Get value of node at index i */ func (abt *arrayBinaryTree) val(i int) any { // If index out of bounds, return null to represent empty position if i < 0 || i >= abt.size() { return nil } return abt.tree[i] } /* Get index of left child node of node at index i */ func (abt *arrayBinaryTree) left(i int) int { return 2*i + 1 } /* Get index of right child node of node at index i */ func (abt *arrayBinaryTree) right(i int) int { return 2*i + 2 } /* Get index of parent node of node at index i */ func (abt *arrayBinaryTree) parent(i int) int { return (i - 1) / 2 } /* Level-order traversal */ func (abt *arrayBinaryTree) levelOrder() []any { var res []any // Traverse array directly for i := 0; i < abt.size(); i++ { if abt.val(i) != nil { res = append(res, abt.val(i)) } } return res } /* Depth-first traversal */ func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) { // If empty position, return if abt.val(i) == nil { return } // Preorder traversal if order == "pre" { *res = append(*res, abt.val(i)) } abt.dfs(abt.left(i), order, res) // Inorder traversal if order == "in" { *res = append(*res, abt.val(i)) } abt.dfs(abt.right(i), order, res) // Postorder traversal if order == "post" { *res = append(*res, abt.val(i)) } } /* Preorder traversal */ func (abt *arrayBinaryTree) preOrder() []any { var res []any abt.dfs(0, "pre", &res) return res } /* Inorder traversal */ func (abt *arrayBinaryTree) inOrder() []any { var res []any abt.dfs(0, "in", &res) return res } /* Postorder traversal */ func (abt *arrayBinaryTree) postOrder() []any { var res []any abt.dfs(0, "post", &res) return res } ================================================ FILE: en/codes/go/chapter_tree/array_binary_tree_test.go ================================================ // File: array_binary_tree_test.go // Created Time: 2023-07-24 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestArrayBinaryTree(t *testing.T) { // Initialize binary tree // Here we use a function to generate a binary tree directly from an array arr := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} root := SliceToTree(arr) fmt.Println("\nInitialize binary tree") fmt.Println("Array representation of binary tree:") fmt.Println(arr) fmt.Println("Linked list representation of binary tree:") PrintTree(root) // Binary tree class represented by array abt := newArrayBinaryTree(arr) // Access node i := 1 l := abt.left(i) r := abt.right(i) p := abt.parent(i) fmt.Println("\nCurrent node index is", i, ", value is", abt.val(i)) fmt.Println("Its left child node index is", l, ", value is", abt.val(l)) fmt.Println("Its right child node index is", r, ", value is", abt.val(r)) fmt.Println("Its parent node index is", p, ", value is", abt.val(p)) // Traverse tree res := abt.levelOrder() fmt.Println("\nLevel-order traversal is:", res) res = abt.preOrder() fmt.Println("Preorder traversal is:", res) res = abt.inOrder() fmt.Println("Inorder traversal is:", res) res = abt.postOrder() fmt.Println("Postorder traversal is:", res) } ================================================ FILE: en/codes/go/chapter_tree/avl_tree.go ================================================ // File: avl_tree.go // Created Time: 2023-01-08 // Author: Reanon (793584285@qq.com) package chapter_tree import . "github.com/krahets/hello-algo/pkg" /* AVL tree */ type aVLTree struct { // Root node root *TreeNode } func newAVLTree() *aVLTree { return &aVLTree{root: nil} } /* Get node height */ func (t *aVLTree) height(node *TreeNode) int { // Empty node height is -1, leaf node height is 0 if node != nil { return node.Height } return -1 } /* Update node height */ func (t *aVLTree) updateHeight(node *TreeNode) { lh := t.height(node.Left) rh := t.height(node.Right) // Node height equals the height of the tallest subtree + 1 if lh > rh { node.Height = lh + 1 } else { node.Height = rh + 1 } } /* Get balance factor */ func (t *aVLTree) balanceFactor(node *TreeNode) int { // Empty node balance factor is 0 if node == nil { return 0 } // Node balance factor = left subtree height - right subtree height return t.height(node.Left) - t.height(node.Right) } /* Right rotation operation */ func (t *aVLTree) rightRotate(node *TreeNode) *TreeNode { child := node.Left grandChild := child.Right // Using child as pivot, rotate node to the right child.Right = node node.Left = grandChild // Update node height t.updateHeight(node) t.updateHeight(child) // Return root node of subtree after rotation return child } /* Left rotation operation */ func (t *aVLTree) leftRotate(node *TreeNode) *TreeNode { child := node.Right grandChild := child.Left // Using child as pivot, rotate node to the left child.Left = node node.Right = grandChild // Update node height t.updateHeight(node) t.updateHeight(child) // Return root node of subtree after rotation return child } /* Perform rotation operation to restore balance to this subtree */ func (t *aVLTree) rotate(node *TreeNode) *TreeNode { // Get balance factor of node // Go recommends short variables, here bf refers to t.balanceFactor bf := t.balanceFactor(node) // Left-leaning tree if bf > 1 { if t.balanceFactor(node.Left) >= 0 { // Right rotation return t.rightRotate(node) } else { // First left rotation then right rotation node.Left = t.leftRotate(node.Left) return t.rightRotate(node) } } // Right-leaning tree if bf < -1 { if t.balanceFactor(node.Right) <= 0 { // Left rotation return t.leftRotate(node) } else { // First right rotation then left rotation node.Right = t.rightRotate(node.Right) return t.leftRotate(node) } } // Balanced tree, no rotation needed, return directly return node } /* Insert node */ func (t *aVLTree) insert(val int) { t.root = t.insertHelper(t.root, val) } /* Recursively insert node (helper function) */ func (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode { if node == nil { return NewTreeNode(val) } /* 1. Find insertion position and insert node */ if val < node.Val.(int) { node.Left = t.insertHelper(node.Left, val) } else if val > node.Val.(int) { node.Right = t.insertHelper(node.Right, val) } else { // Duplicate node not inserted, return directly return node } // Update node height t.updateHeight(node) /* 2. Perform rotation operation to restore balance to this subtree */ node = t.rotate(node) // Return root node of subtree return node } /* Remove node */ func (t *aVLTree) remove(val int) { t.root = t.removeHelper(t.root, val) } /* Recursively remove node (helper function) */ func (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode { if node == nil { return nil } /* 1. Find node and delete */ if val < node.Val.(int) { node.Left = t.removeHelper(node.Left, val) } else if val > node.Val.(int) { node.Right = t.removeHelper(node.Right, val) } else { if node.Left == nil || node.Right == nil { child := node.Left if node.Right != nil { child = node.Right } if child == nil { // Number of child nodes = 0, delete node directly and return return nil } else { // Number of child nodes = 1, delete node directly node = child } } else { // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it temp := node.Right for temp.Left != nil { temp = temp.Left } node.Right = t.removeHelper(node.Right, temp.Val.(int)) node.Val = temp.Val } } // Update node height t.updateHeight(node) /* 2. Perform rotation operation to restore balance to this subtree */ node = t.rotate(node) // Return root node of subtree return node } /* Search node */ func (t *aVLTree) search(val int) *TreeNode { cur := t.root // Loop search, exit after passing leaf node for cur != nil { if cur.Val.(int) < val { // Target node is in cur's right subtree cur = cur.Right } else if cur.Val.(int) > val { // Target node is in cur's left subtree cur = cur.Left } else { // Found target node, exit loop break } } // Return target node return cur } ================================================ FILE: en/codes/go/chapter_tree/avl_tree_test.go ================================================ // File: avl_tree_test.go // Created Time: 2023-01-08 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestAVLTree(t *testing.T) { /* Please pay attention to how the AVL tree maintains balance after inserting nodes */ tree := newAVLTree() /* Insert node */ // Delete nodes testInsert(tree, 1) testInsert(tree, 2) testInsert(tree, 3) testInsert(tree, 4) testInsert(tree, 5) testInsert(tree, 8) testInsert(tree, 7) testInsert(tree, 9) testInsert(tree, 10) testInsert(tree, 6) /* Please pay attention to how the AVL tree maintains balance after deleting nodes */ testInsert(tree, 7) /* Remove node */ // Delete node with degree 1 testRemove(tree, 8) // Delete node with degree 2 testRemove(tree, 5) // Remove node with degree 1 testRemove(tree, 4) // Remove node with degree 2 /* Search node */ node := tree.search(7) fmt.Printf("\nFound node object is %#v, node value = %d \n", node, node.Val) } func testInsert(tree *aVLTree, val int) { tree.insert(val) fmt.Printf("\nAfter inserting node %d, AVL tree is \n", val) PrintTree(tree.root) } func testRemove(tree *aVLTree, val int) { tree.remove(val) fmt.Printf("\nAfter removing node %d, AVL tree is \n", val) PrintTree(tree.root) } ================================================ FILE: en/codes/go/chapter_tree/binary_search_tree.go ================================================ // File: binary_search_tree.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( . "github.com/krahets/hello-algo/pkg" ) type binarySearchTree struct { root *TreeNode } func newBinarySearchTree() *binarySearchTree { bst := &binarySearchTree{} // Initialize empty tree bst.root = nil return bst } /* Get root node */ func (bst *binarySearchTree) getRoot() *TreeNode { return bst.root } /* Search node */ func (bst *binarySearchTree) search(num int) *TreeNode { node := bst.root // Loop search, exit after passing leaf node for node != nil { if node.Val.(int) < num { // Target node is in cur's right subtree node = node.Right } else if node.Val.(int) > num { // Target node is in cur's left subtree node = node.Left } else { // Found target node, exit loop break } } // Return target node return node } /* Insert node */ func (bst *binarySearchTree) insert(num int) { cur := bst.root // If tree is empty, initialize root node if cur == nil { bst.root = NewTreeNode(num) return } // Node position before the node to be inserted var pre *TreeNode = nil // Loop search, exit after passing leaf node for cur != nil { if cur.Val == num { return } pre = cur if cur.Val.(int) < num { cur = cur.Right } else { cur = cur.Left } } // Insert node node := NewTreeNode(num) if pre.Val.(int) < num { pre.Right = node } else { pre.Left = node } } /* Remove node */ func (bst *binarySearchTree) remove(num int) { cur := bst.root // If tree is empty, return directly if cur == nil { return } // Node position before the node to be removed var pre *TreeNode = nil // Loop search, exit after passing leaf node for cur != nil { if cur.Val == num { break } pre = cur if cur.Val.(int) < num { // Node to be removed is in right subtree cur = cur.Right } else { // Node to be removed is in left subtree cur = cur.Left } } // If no node to delete, return directly if cur == nil { return } // Number of child nodes is 0 or 1 if cur.Left == nil || cur.Right == nil { var child *TreeNode = nil // Get child node of node to be removed if cur.Left != nil { child = cur.Left } else { child = cur.Right } // Delete node cur if cur != bst.root { if pre.Left == cur { pre.Left = child } else { pre.Right = child } } else { // If deleted node is root node, reassign root node bst.root = child } // Number of child nodes is 2 } else { // Get next node of node cur to be removed in in-order traversal tmp := cur.Right for tmp.Left != nil { tmp = tmp.Left } // Recursively delete node tmp bst.remove(tmp.Val.(int)) // Replace cur with tmp cur.Val = tmp.Val } } /* Print binary search tree */ func (bst *binarySearchTree) print() { PrintTree(bst.root) } ================================================ FILE: en/codes/go/chapter_tree/binary_search_tree_test.go ================================================ // File: binary_search_tree_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" ) func TestBinarySearchTree(t *testing.T) { bst := newBinarySearchTree() nums := []int{8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15} // Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree for _, num := range nums { bst.insert(num) } fmt.Println("\nInitialized binary tree is:") bst.print() // Get root node node := bst.getRoot() fmt.Println("\nRoot node of binary tree is:", node.Val) // Search node node = bst.search(7) fmt.Println("Found node object is", node, ", node value =", node.Val) // Insert node bst.insert(16) fmt.Println("\nAfter inserting node 16, binary tree is:") bst.print() // Remove node bst.remove(1) fmt.Println("\nAfter removing node 1, binary tree is:") bst.print() bst.remove(2) fmt.Println("\nAfter removing node 2, binary tree is:") bst.print() bst.remove(4) fmt.Println("\nAfter removing node 4, binary tree is:") bst.print() } ================================================ FILE: en/codes/go/chapter_tree/binary_tree_bfs.go ================================================ // File: binary_tree_bfs.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "container/list" . "github.com/krahets/hello-algo/pkg" ) /* Level-order traversal */ func levelOrder(root *TreeNode) []any { // Initialize queue, add root node queue := list.New() queue.PushBack(root) // Initialize a slice to save traversal sequence nums := make([]any, 0) for queue.Len() > 0 { // Dequeue node := queue.Remove(queue.Front()).(*TreeNode) // Save node value nums = append(nums, node.Val) if node.Left != nil { // Left child node enqueue queue.PushBack(node.Left) } if node.Right != nil { // Right child node enqueue queue.PushBack(node.Right) } } return nums } ================================================ FILE: en/codes/go/chapter_tree/binary_tree_bfs_test.go ================================================ // File: binary_tree_bfs_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLevelOrder(t *testing.T) { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\nInitialize binary tree: ") PrintTree(root) // Level-order traversal nums := levelOrder(root) fmt.Println("\nLevel-order traversal node print sequence =", nums) } ================================================ FILE: en/codes/go/chapter_tree/binary_tree_dfs.go ================================================ // File: binary_tree_dfs.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( . "github.com/krahets/hello-algo/pkg" ) var nums []any /* Preorder traversal */ func preOrder(node *TreeNode) { if node == nil { return } // Visit priority: root node -> left subtree -> right subtree nums = append(nums, node.Val) preOrder(node.Left) preOrder(node.Right) } /* Inorder traversal */ func inOrder(node *TreeNode) { if node == nil { return } // Visit priority: left subtree -> root node -> right subtree inOrder(node.Left) nums = append(nums, node.Val) inOrder(node.Right) } /* Postorder traversal */ func postOrder(node *TreeNode) { if node == nil { return } // Visit priority: left subtree -> right subtree -> root node postOrder(node.Left) postOrder(node.Right) nums = append(nums, node.Val) } ================================================ FILE: en/codes/go/chapter_tree/binary_tree_dfs_test.go ================================================ // File: binary_tree_dfs_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPreInPostOrderTraversal(t *testing.T) { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\nInitialize binary tree: ") PrintTree(root) // Preorder traversal nums = nil preOrder(root) fmt.Println("\nPre-order traversal node print sequence =", nums) // Inorder traversal nums = nil inOrder(root) fmt.Println("\nIn-order traversal node print sequence =", nums) // Postorder traversal nums = nil postOrder(root) fmt.Println("\nPost-order traversal node print sequence =", nums) } ================================================ FILE: en/codes/go/chapter_tree/binary_tree_test.go ================================================ // File: binary_tree_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestBinaryTree(t *testing.T) { /* Initialize binary tree */ // Initialize nodes n1 := NewTreeNode(1) n2 := NewTreeNode(2) n3 := NewTreeNode(3) n4 := NewTreeNode(4) n5 := NewTreeNode(5) // Build references (pointers) between nodes n1.Left = n2 n1.Right = n3 n2.Left = n4 n2.Right = n5 fmt.Println("Initialize binary tree") PrintTree(n1) /* Insert node P between n1 -> n2 */ // Insert node p := NewTreeNode(0) n1.Left = p p.Left = n2 fmt.Println("After inserting node P") PrintTree(n1) // Remove node n1.Left = n2 fmt.Println("After removing node P") PrintTree(n1) } ================================================ FILE: en/codes/go/go.mod ================================================ module github.com/krahets/hello-algo go 1.19 ================================================ FILE: en/codes/go/pkg/list_node.go ================================================ // File: list_node.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg // ListNode linked list node type ListNode struct { Next *ListNode Val int } // NewListNode linked list node constructor func NewListNode(v int) *ListNode { return &ListNode{ Next: nil, Val: v, } } // ArrayToLinkedList deserialize array to linked list func ArrayToLinkedList(arr []int) *ListNode { // dummy header of linked list dummy := NewListNode(0) node := dummy for _, val := range arr { node.Next = NewListNode(val) node = node.Next } return dummy.Next } ================================================ FILE: en/codes/go/pkg/list_node_test.go ================================================ // File: list_node_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg import ( "testing" ) func TestListNode(t *testing.T) { arr := []int{2, 3, 5, 6, 7} head := ArrayToLinkedList(arr) PrintLinkedList(head) } ================================================ FILE: en/codes/go/pkg/print_utils.go ================================================ // File: print_utils.go // Created Time: 2022-12-03 // Author: Reanon (793584285@qq.com), krahets (krahets@163.com), msk397 (machangxinq@gmail.com) package pkg import ( "container/list" "fmt" "strconv" "strings" ) // PrintSlice print slice func PrintSlice[T any](nums []T) { fmt.Printf("%v", nums) fmt.Println() } // PrintList print list func PrintList(list *list.List) { if list.Len() == 0 { fmt.Print("[]\n") return } e := list.Front() // Force conversion to string will affect efficiency fmt.Print("[") for e.Next() != nil { fmt.Print(e.Value, " ") e = e.Next() } fmt.Print(e.Value, "]\n") } // PrintMap print hash table func PrintMap[K comparable, V any](m map[K]V) { for key, value := range m { fmt.Println(key, "->", value) } } // PrintHeap print heap func PrintHeap(h []any) { fmt.Printf("Heap array representation:") fmt.Printf("%v", h) fmt.Printf("\nTree representation of heap:\n") root := SliceToTree(h) PrintTree(root) } // PrintLinkedList print linked list func PrintLinkedList(node *ListNode) { if node == nil { return } var builder strings.Builder for node.Next != nil { builder.WriteString(strconv.Itoa(node.Val) + " -> ") node = node.Next } builder.WriteString(strconv.Itoa(node.Val)) fmt.Println(builder.String()) } // PrintTree print binary tree func PrintTree(root *TreeNode) { printTreeHelper(root, nil, false) } // printTreeHelper print binary tree // This tree printer is borrowed from TECHIE DELIGHT // https://www.techiedelight.com/c-program-print-binary-tree/ func printTreeHelper(root *TreeNode, prev *trunk, isRight bool) { if root == nil { return } prevStr := " " trunk := newTrunk(prev, prevStr) printTreeHelper(root.Right, trunk, true) if prev == nil { trunk.str = "———" } else if isRight { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev.str = prevStr } showTrunk(trunk) fmt.Println(root.Val) if prev != nil { prev.str = prevStr } trunk.str = " |" printTreeHelper(root.Left, trunk, false) } type trunk struct { prev *trunk str string } func newTrunk(prev *trunk, str string) *trunk { return &trunk{ prev: prev, str: str, } } func showTrunk(t *trunk) { if t == nil { return } showTrunk(t.prev) fmt.Print(t.str) } ================================================ FILE: en/codes/go/pkg/tree_node.go ================================================ // File: tree_node.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg // TreeNode binary tree node type TreeNode struct { Val any // Node value Height int // Node height Left *TreeNode // Reference to left child node Right *TreeNode // Reference to right child node } // NewTreeNode binary tree node constructor func NewTreeNode(v any) *TreeNode { return &TreeNode{ Val: v, Height: 0, Left: nil, Right: nil, } } // For the serialization encoding rules, please refer to: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Array representation of binary tree: // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] // Linked list representation of binary tree: // // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // // ——— 1 // // \——— 2 // | /——— 9 // \——— 4 // \——— 8 // SliceToTreeDFS deserialize list to binary tree: recursion func SliceToTreeDFS(arr []any, i int) *TreeNode { if i < 0 || i >= len(arr) || arr[i] == nil { return nil } root := NewTreeNode(arr[i]) root.Left = SliceToTreeDFS(arr, 2*i+1) root.Right = SliceToTreeDFS(arr, 2*i+2) return root } // SliceToTree deserialize slice to binary tree func SliceToTree(arr []any) *TreeNode { return SliceToTreeDFS(arr, 0) } // TreeToSliceDFS serialize binary tree to slice: recursion func TreeToSliceDFS(root *TreeNode, i int, res *[]any) { if root == nil { return } for i >= len(*res) { *res = append(*res, nil) } (*res)[i] = root.Val TreeToSliceDFS(root.Left, 2*i+1, res) TreeToSliceDFS(root.Right, 2*i+2, res) } // TreeToSlice serialize binary tree to slice func TreeToSlice(root *TreeNode) []any { var res []any TreeToSliceDFS(root, 0, &res) return res } ================================================ FILE: en/codes/go/pkg/tree_node_test.go ================================================ // File: tree_node_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg import ( "fmt" "testing" ) func TestTreeNode(t *testing.T) { arr := []any{1, 2, 3, nil, 5, 6, nil} node := SliceToTree(arr) // print tree PrintTree(node) // tree to arr fmt.Println(TreeToSlice(node)) } ================================================ FILE: en/codes/go/pkg/vertex.go ================================================ // File: vertex.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package pkg // Vertex vertex class type Vertex struct { Val int } // NewVertex vertex constructor func NewVertex(val int) Vertex { return Vertex{ Val: val, } } // ValsToVets deserialize value list to vertex list func ValsToVets(vals []int) []Vertex { vets := make([]Vertex, len(vals)) for i := 0; i < len(vals); i++ { vets[i] = NewVertex(vals[i]) } return vets } // VetsToVals serialize vertex list to value list func VetsToVals(vets []Vertex) []int { vals := make([]int, len(vets)) for i := range vets { vals[i] = vets[i].Val } return vals } // DeleteSliceElms delete specified elements from slice func DeleteSliceElms[T any](a []T, elms ...T) []T { if len(a) == 0 || len(elms) == 0 { return a } // First convert elements to set m := make(map[any]struct{}) for _, v := range elms { m[v] = struct{}{} } // Filter out specified elements res := make([]T, 0, len(a)) for _, v := range a { if _, ok := m[v]; !ok { res = append(res, v) } } return res } ================================================ FILE: en/codes/java/.gitignore ================================================ build ================================================ FILE: en/codes/java/chapter_array_and_linkedlist/array.java ================================================ /** * 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 { /* Random access to element */ static int randomAccess(int[] nums) { // Randomly select a number in the interval [0, nums.length) int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length); // Retrieve and return the random element int randomNum = nums[randomIndex]; return randomNum; } /* Extend array length */ static int[] extend(int[] nums, int enlarge) { // Initialize an array with extended length int[] res = new int[nums.length + enlarge]; // Copy all elements from the original array to the new array for (int i = 0; i < nums.length; i++) { res[i] = nums[i]; } // Return the extended new array return res; } /* Insert element num at index index in the array */ static void insert(int[] nums, int num, int index) { // Move all elements at and after index index backward by one position for (int i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Assign num to the element at index index nums[index] = num; } /* Remove the element at index index */ static void remove(int[] nums, int index) { // Move all elements after index index forward by one position for (int i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* Traverse array */ static void traverse(int[] nums) { int count = 0; // Traverse array by index for (int i = 0; i < nums.length; i++) { count += nums[i]; } // Direct traversal of array elements for (int num : nums) { count += num; } } /* Find the specified element in the array */ static int find(int[] nums, int target) { for (int i = 0; i < nums.length; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ public static void main(String[] args) { /* Initialize array */ int[] arr = new int[5]; System.out.println("Array arr = " + Arrays.toString(arr)); int[] nums = { 1, 3, 2, 5, 4 }; System.out.println("Array nums = " + Arrays.toString(nums)); /* Insert element */ int randomNum = randomAccess(nums); System.out.println("Get random element in nums " + randomNum); /* Traverse array */ nums = extend(nums, 3); System.out.println("Extend array length to 8, resulting in nums = " + Arrays.toString(nums)); /* Insert element */ insert(nums, 6, 3); System.out.println("Insert number 6 at index 3, resulting in nums = " + Arrays.toString(nums)); /* Remove element */ remove(nums, 2); System.out.println("Remove element at index 2, resulting in nums = " + Arrays.toString(nums)); /* Traverse array */ traverse(nums); /* Find element */ int index = find(nums, 3); System.out.println("Find element 3 in nums, get index = " + index); } } ================================================ FILE: en/codes/java/chapter_array_and_linkedlist/linked_list.java ================================================ /** * 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 { /* Insert node P after node n0 in the linked list */ static void insert(ListNode n0, ListNode P) { ListNode n1 = n0.next; P.next = n1; n0.next = P; } /* Remove the first node after node n0 in the linked list */ static void remove(ListNode n0) { if (n0.next == null) return; // n0 -> P -> n1 ListNode P = n0.next; ListNode n1 = P.next; n0.next = n1; } /* Access the node at index index in the linked list */ static ListNode access(ListNode head, int index) { for (int i = 0; i < index; i++) { if (head == null) return null; head = head.next; } return head; } /* Find the first node with value target in the linked list */ 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; } /* Driver Code */ public static void main(String[] args) { /* Initialize linked list */ // Initialize each node 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); // Build references between nodes n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; System.out.println("Initialized linked list is"); PrintUtil.printLinkedList(n0); /* Insert node */ insert(n0, new ListNode(0)); System.out.println("Linked list after inserting node is"); PrintUtil.printLinkedList(n0); /* Remove node */ remove(n0); System.out.println("Linked list after removing node is"); PrintUtil.printLinkedList(n0); /* Access node */ ListNode node = access(n0, 3); System.out.println("Value of node at index 3 in linked list = " + node.val); /* Search node */ int index = find(n0, 2); System.out.println("Index of node with value 2 in linked list = " + index); } } ================================================ FILE: en/codes/java/chapter_array_and_linkedlist/list.java ================================================ /** * 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) { /* Initialize list */ // Note that the array element type is Integer[], the wrapper class of int[] Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; List nums = new ArrayList<>(Arrays.asList(numbers)); System.out.println("List nums = " + nums); /* Update element */ int num = nums.get(1); System.out.println("Access element at index 1, get num = " + num); /* Add elements at the end */ nums.set(1, 0); System.out.println("Update element at index 1 to 0, resulting in nums = " + nums); /* Remove element */ nums.clear(); System.out.println("After clearing list, nums = " + nums); /* Direct traversal of list elements */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); System.out.println("After adding elements, nums = " + nums); /* Sort list */ nums.add(3, 6); System.out.println("Insert number 6 at index 3, resulting in nums = " + nums); /* Remove element */ nums.remove(3); System.out.println("Remove element at index 3, resulting in nums = " + nums); /* Traverse list by index */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums.get(i); } /* Directly traverse list elements */ for (int x : nums) { count += x; } /* Concatenate two lists */ List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); nums.addAll(nums1); System.out.println("Concatenate list nums1 to nums, resulting in nums = " + nums); /* Sort list */ Collections.sort(nums); System.out.println("After sorting list, nums = " + nums); } } ================================================ FILE: en/codes/java/chapter_array_and_linkedlist/my_list.java ================================================ /** * File: my_list.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_array_and_linkedlist; import java.util.*; /* List class */ class MyList { private int[] arr; // Array (stores list elements) private int capacity = 10; // List capacity private int size = 0; // List length (current number of elements) private int extendRatio = 2; // Multiple by which the list capacity is extended each time /* Constructor */ public MyList() { arr = new int[capacity]; } /* Get list length (current number of elements) */ public int size() { return size; } /* Get list capacity */ public int capacity() { return capacity; } /* Update element */ public int get(int index) { // If the index is out of bounds, throw an exception, as below if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Index out of bounds"); return arr[index]; } /* Add elements at the end */ public void set(int index, int num) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Index out of bounds"); arr[index] = num; } /* Direct traversal of list elements */ public void add(int num) { // When the number of elements exceeds capacity, trigger the extension mechanism if (size == capacity()) extendCapacity(); arr[size] = num; // Update the number of elements size++; } /* Sort list */ public void insert(int index, int num) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Index out of bounds"); // When the number of elements exceeds capacity, trigger the extension mechanism if (size == capacity()) extendCapacity(); // Move all elements after index index forward by one position for (int j = size - 1; j >= index; j--) { arr[j + 1] = arr[j]; } arr[index] = num; // Update the number of elements size++; } /* Remove element */ public int remove(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Index out of bounds"); int num = arr[index]; // Move all elements after index forward by one position for (int j = index; j < size - 1; j++) { arr[j] = arr[j + 1]; } // Update the number of elements size--; // Return the removed element return num; } /* Driver Code */ public void extendCapacity() { // Create a new array with length extendRatio times the original array and copy the original array to the new array arr = Arrays.copyOf(arr, capacity() * extendRatio); // Add elements at the end capacity = arr.length; } /* Convert list to array */ public int[] toArray() { int size = size(); // Elements enqueue int[] arr = new int[size]; for (int i = 0; i < size; i++) { arr[i] = get(i); } return arr; } } public class my_list { /* Driver Code */ public static void main(String[] args) { /* Initialize list */ MyList nums = new MyList(); /* Direct traversal of list elements */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); System.out.println("List nums = " + Arrays.toString(nums.toArray()) + ", capacity = " + nums.capacity() + ", length = " + nums.size()); /* Sort list */ nums.insert(3, 6); System.out.println("Insert number 6 at index 3, resulting in nums = " + Arrays.toString(nums.toArray())); /* Remove element */ nums.remove(3); System.out.println("Remove element at index 3, resulting in nums = " + Arrays.toString(nums.toArray())); /* Update element */ int num = nums.get(1); System.out.println("Access element at index 1, get num = " + num); /* Add elements at the end */ nums.set(1, 0); System.out.println("Update element at index 1 to 0, resulting in nums = " + Arrays.toString(nums.toArray())); /* Test capacity expansion mechanism */ for (int i = 0; i < 10; i++) { // At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism nums.add(i); } System.out.println("List nums after expansion = " + Arrays.toString(nums.toArray()) + ", capacity = " + nums.capacity() + ", length = " + nums.size()); } } ================================================ FILE: en/codes/java/chapter_backtracking/n_queens.java ================================================ /** * File: n_queens.java * Created Time: 2023-05-04 * Author: krahets (krahets@163.com) */ package chapter_backtracking; import java.util.*; public class n_queens { /* Backtracking algorithm: N queens */ public static void backtrack(int row, int n, List> state, List>> res, boolean[] cols, boolean[] diags1, boolean[] diags2) { // When all rows are placed, record the solution if (row == n) { List> copyState = new ArrayList<>(); for (List sRow : state) { copyState.add(new ArrayList<>(sRow)); } res.add(copyState); return; } // Traverse all columns for (int col = 0; col < n; col++) { // Calculate the main diagonal and anti-diagonal corresponding to this cell int diag1 = row - col + n - 1; int diag2 = row + col; // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Attempt: place the queen in this cell state.get(row).set(col, "Q"); cols[col] = diags1[diag1] = diags2[diag2] = true; // Place the next row backtrack(row + 1, n, state, res, cols, diags1, diags2); // Backtrack: restore this cell to an empty cell state.get(row).set(col, "#"); cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Solve N queens */ public static List>> nQueens(int n) { // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell 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]; // Record whether there is a queen in the column boolean[] diags1 = new boolean[2 * n - 1]; // Record whether there is a queen on the main diagonal boolean[] diags2 = new boolean[2 * n - 1]; // Record whether there is a queen on the anti-diagonal 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("Input board size is " + n); System.out.println("Total queen placement solutions: " + res.size() + ""); for (List> state : res) { System.out.println("--------------------"); for (List row : state) { System.out.println(row); } } } } ================================================ FILE: en/codes/java/chapter_backtracking/permutations_i.java ================================================ /** * File: permutations_i.java * Created Time: 2023-04-24 * Author: krahets (krahets@163.com) */ package chapter_backtracking; import java.util.*; public class permutations_i { /* Backtracking algorithm: Permutations I */ public static void backtrack(List state, int[] choices, boolean[] selected, List> res) { // When the state length equals the number of elements, record the solution if (state.size() == choices.length) { res.add(new ArrayList(state)); return; } // Traverse all choices for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state.add(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.remove(state.size() - 1); } } } /* Permutations 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("Input array nums = " + Arrays.toString(nums)); System.out.println("All permutations res = " + res); } } ================================================ FILE: en/codes/java/chapter_backtracking/permutations_ii.java ================================================ /** * File: permutations_ii.java * Created Time: 2023-04-24 * Author: krahets (krahets@163.com) */ package chapter_backtracking; import java.util.*; public class permutations_ii { /* Backtracking algorithm: Permutations II */ static void backtrack(List state, int[] choices, boolean[] selected, List> res) { // When the state length equals the number of elements, record the solution if (state.size() == choices.length) { res.add(new ArrayList(state)); return; } // Traverse all choices Set duplicated = new HashSet(); for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated.contains(choice)) { // Attempt: make choice, update state duplicated.add(choice); // Record the selected element value selected[i] = true; state.add(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.remove(state.size() - 1); } } } /* Permutations 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("Input array nums = " + Arrays.toString(nums)); System.out.println("All permutations res = " + res); } } ================================================ FILE: en/codes/java/chapter_backtracking/preorder_traversal_i_compact.java ================================================ /** * 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; /* Preorder traversal: Example 1 */ static void preOrder(TreeNode root) { if (root == null) { return; } if (root.val == 7) { // Record solution 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("\nInitialize binary tree"); PrintUtil.printTree(root); // Preorder traversal res = new ArrayList<>(); preOrder(root); System.out.println("\nOutput all nodes with value 7"); List vals = new ArrayList<>(); for (TreeNode node : res) { vals.add(node.val); } System.out.println(vals); } } ================================================ FILE: en/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java ================================================ /** * 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; /* Preorder traversal: Example 2 */ static void preOrder(TreeNode root) { if (root == null) { return; } // Attempt path.add(root); if (root.val == 7) { // Record solution res.add(new ArrayList<>(path)); } preOrder(root.left); preOrder(root.right); // Backtrack 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("\nInitialize binary tree"); PrintUtil.printTree(root); // Preorder traversal path = new ArrayList<>(); res = new ArrayList<>(); preOrder(root); System.out.println("\nOutput all paths from root node to node 7"); for (List path : res) { List vals = new ArrayList<>(); for (TreeNode node : path) { vals.add(node.val); } System.out.println(vals); } } } ================================================ FILE: en/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java ================================================ /** * 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; /* Preorder traversal: Example 3 */ static void preOrder(TreeNode root) { // Pruning if (root == null || root.val == 3) { return; } // Attempt path.add(root); if (root.val == 7) { // Record solution res.add(new ArrayList<>(path)); } preOrder(root.left); preOrder(root.right); // Backtrack 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("\nInitialize binary tree"); PrintUtil.printTree(root); // Preorder traversal path = new ArrayList<>(); res = new ArrayList<>(); preOrder(root); System.out.println("\nOutput all paths from root node to node 7, paths do not include nodes with value 3"); for (List path : res) { List vals = new ArrayList<>(); for (TreeNode node : path) { vals.add(node.val); } System.out.println(vals); } } } ================================================ FILE: en/codes/java/chapter_backtracking/preorder_traversal_iii_template.java ================================================ /** * 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 { /* Check if the current state is a solution */ static boolean isSolution(List state) { return !state.isEmpty() && state.get(state.size() - 1).val == 7; } /* Record solution */ static void recordSolution(List state, List> res) { res.add(new ArrayList<>(state)); } /* Check if the choice is valid under the current state */ static boolean isValid(List state, TreeNode choice) { return choice != null && choice.val != 3; } /* Update state */ static void makeChoice(List state, TreeNode choice) { state.add(choice); } /* Restore state */ static void undoChoice(List state, TreeNode choice) { state.remove(state.size() - 1); } /* Backtracking algorithm: Example 3 */ static void backtrack(List state, List choices, List> res) { // Check if it is a solution if (isSolution(state)) { // Record solution recordSolution(state, res); } // Traverse all choices for (TreeNode choice : choices) { // Pruning: check if the choice is valid if (isValid(state, choice)) { // Attempt: make choice, update state makeChoice(state, choice); // Proceed to the next round of selection backtrack(state, Arrays.asList(choice.left, choice.right), res); // Backtrack: undo choice, restore to previous state 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("\nInitialize binary tree"); PrintUtil.printTree(root); // Backtracking algorithm List> res = new ArrayList<>(); backtrack(new ArrayList<>(), Arrays.asList(root), res); System.out.println("\nOutput all paths from root node to node 7, requiring paths do not include nodes with value 3"); for (List path : res) { List vals = new ArrayList<>(); for (TreeNode node : path) { vals.add(node.val); } System.out.println(vals); } } } ================================================ FILE: en/codes/java/chapter_backtracking/subset_sum_i.java ================================================ /** * 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 { /* Backtracking algorithm: Subset sum I */ static void backtrack(List state, int target, int[] choices, int start, List> res) { // When the subset sum equals target, record the solution if (target == 0) { res.add(new ArrayList<>(state)); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets for (int i = start; i < choices.length; i++) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if (target - choices[i] < 0) { break; } // Attempt: make choice, update target, start state.add(choices[i]); // Proceed to the next round of selection backtrack(state, target - choices[i], choices, i, res); // Backtrack: undo choice, restore to previous state state.remove(state.size() - 1); } } /* Solve subset sum I */ static List> subsetSumI(int[] nums, int target) { List state = new ArrayList<>(); // State (subset) Arrays.sort(nums); // Sort nums int start = 0; // Start point for traversal List> res = new ArrayList<>(); // Result list (subset list) 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("Input array nums = " + Arrays.toString(nums) + ", target = " + target); System.out.println("All subsets with sum equal to " + target + " are res = " + res); } } ================================================ FILE: en/codes/java/chapter_backtracking/subset_sum_i_naive.java ================================================ /** * 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 { /* Backtracking algorithm: Subset sum I */ static void backtrack(List state, int target, int total, int[] choices, List> res) { // When the subset sum equals target, record the solution if (total == target) { res.add(new ArrayList<>(state)); return; } // Traverse all choices for (int i = 0; i < choices.length; i++) { // Pruning: if the subset sum exceeds target, skip this choice if (total + choices[i] > target) { continue; } // Attempt: make choice, update element sum total state.add(choices[i]); // Proceed to the next round of selection backtrack(state, target, total + choices[i], choices, res); // Backtrack: undo choice, restore to previous state state.remove(state.size() - 1); } } /* Solve subset sum I (including duplicate subsets) */ static List> subsetSumINaive(int[] nums, int target) { List state = new ArrayList<>(); // State (subset) int total = 0; // Subset sum List> res = new ArrayList<>(); // Result list (subset list) 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("Input array nums = " + Arrays.toString(nums) + ", target = " + target); System.out.println("All subsets with sum equal to " + target + " are res = " + res); System.out.println("Please note that this method outputs results containing duplicate sets"); } } ================================================ FILE: en/codes/java/chapter_backtracking/subset_sum_ii.java ================================================ /** * 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 { /* Backtracking algorithm: Subset sum II */ static void backtrack(List state, int target, int[] choices, int start, List> res) { // When the subset sum equals target, record the solution if (target == 0) { res.add(new ArrayList<>(state)); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets // Pruning 3: start traversing from start to avoid repeatedly selecting the same element for (int i = start; i < choices.length; i++) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if (target - choices[i] < 0) { break; } // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly if (i > start && choices[i] == choices[i - 1]) { continue; } // Attempt: make choice, update target, start state.add(choices[i]); // Proceed to the next round of selection backtrack(state, target - choices[i], choices, i + 1, res); // Backtrack: undo choice, restore to previous state state.remove(state.size() - 1); } } /* Solve subset sum II */ static List> subsetSumII(int[] nums, int target) { List state = new ArrayList<>(); // State (subset) Arrays.sort(nums); // Sort nums int start = 0; // Start point for traversal List> res = new ArrayList<>(); // Result list (subset list) 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("Input array nums = " + Arrays.toString(nums) + ", target = " + target); System.out.println("All subsets with sum equal to " + target + " are res = " + res); } } ================================================ FILE: en/codes/java/chapter_computational_complexity/iteration.java ================================================ /** * File: iteration.java * Created Time: 2023-08-24 * Author: krahets (krahets@163.com) */ package chapter_computational_complexity; public class iteration { /* for loop */ static int forLoop(int n) { int res = 0; // Sum 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } /* while loop */ static int whileLoop(int n) { int res = 0; int i = 1; // Initialize condition variable // Sum 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Update condition variable } return res; } /* while loop (two updates) */ static int whileLoopII(int n) { int res = 0; int i = 1; // Initialize condition variable // Sum 1, 4, 10, ... while (i <= n) { res += i; // Update condition variable i++; i *= 2; } return res; } /* Nested for loop */ static String nestedForLoop(int n) { StringBuilder res = new StringBuilder(); // Loop i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // Loop j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { res.append("(" + i + ", " + j + "), "); } } return res.toString(); } /* Driver Code */ public static void main(String[] args) { int n = 5; int res; res = forLoop(n); System.out.println("\nfor loop sum result res = " + res); res = whileLoop(n); System.out.println("\nwhile loop sum result res = " + res); res = whileLoopII(n); System.out.println("\nwhile loop (two updates) sum result res = " + res); String resStr = nestedForLoop(n); System.out.println("\nDouble for loop traversal result " + resStr); } } ================================================ FILE: en/codes/java/chapter_computational_complexity/recursion.java ================================================ /** * File: recursion.java * Created Time: 2023-08-24 * Author: krahets (krahets@163.com) */ package chapter_computational_complexity; import java.util.Stack; public class recursion { /* Recursion */ static int recur(int n) { // Termination condition if (n == 1) return 1; // Recurse: recursive call int res = recur(n - 1); // Return: return result return n + res; } /* Simulate recursion using iteration */ static int forLoopRecur(int n) { // Use an explicit stack to simulate the system call stack Stack stack = new Stack<>(); int res = 0; // Recurse: recursive call for (int i = n; i > 0; i--) { // Simulate "recurse" with "push" stack.push(i); } // Return: return result while (!stack.isEmpty()) { // Simulate "return" with "pop" res += stack.pop(); } // res = 1+2+3+...+n return res; } /* Tail recursion */ static int tailRecur(int n, int res) { // Termination condition if (n == 0) return res; // Tail recursive call return tailRecur(n - 1, res + n); } /* Fibonacci sequence: recursion */ static int fib(int n) { // Termination condition f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // Recursive call f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // Return result f(n) return res; } /* Driver Code */ public static void main(String[] args) { int n = 5; int res; res = recur(n); System.out.println("\nRecursive function sum result res = " + res); res = forLoopRecur(n); System.out.println("\nUsing iteration to simulate recursive sum result res = " + res); res = tailRecur(n, 0); System.out.println("\nTail recursive function sum result res = " + res); res = fib(n); System.out.println("\nThe " + n + "th term of the Fibonacci sequence is " + res); } } ================================================ FILE: en/codes/java/chapter_computational_complexity/space_complexity.java ================================================ /** * 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 { /* Function */ static int function() { // Perform some operations return 0; } /* Constant order */ static void constant(int n) { // Constants, variables, objects occupy O(1) space final int a = 0; int b = 0; int[] nums = new int[10000]; ListNode node = new ListNode(0); // Variables in the loop occupy O(1) space for (int i = 0; i < n; i++) { int c = 0; } // Functions in the loop occupy O(1) space for (int i = 0; i < n; i++) { function(); } } /* Linear order */ static void linear(int n) { // Array of length n uses O(n) space int[] nums = new int[n]; // A list of length n occupies O(n) space List nodes = new ArrayList<>(); for (int i = 0; i < n; i++) { nodes.add(new ListNode(i)); } // A hash table of length n occupies O(n) space Map map = new HashMap<>(); for (int i = 0; i < n; i++) { map.put(i, String.valueOf(i)); } } /* Linear order (recursive implementation) */ static void linearRecur(int n) { System.out.println("Recursion n = " + n); if (n == 1) return; linearRecur(n - 1); } /* Exponential order */ static void quadratic(int n) { // Matrix uses O(n^2) space int[][] numMatrix = new int[n][n]; // 2D list uses O(n^2) space 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); } } /* Quadratic order (recursive implementation) */ static int quadraticRecur(int n) { if (n <= 0) return 0; // Array nums has length n, n-1, ..., 2, 1 int[] nums = new int[n]; System.out.println("In recursion n = " + n + ", nums length = " + nums.length); return quadraticRecur(n - 1); } /* Driver Code */ 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; } /* Driver Code */ public static void main(String[] args) { int n = 5; // Constant order constant(n); // Linear order linear(n); linearRecur(n); // Exponential order quadratic(n); quadraticRecur(n); // Exponential order TreeNode root = buildTree(n); PrintUtil.printTree(root); } } ================================================ FILE: en/codes/java/chapter_computational_complexity/time_complexity.java ================================================ /** * File: time_complexity.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_computational_complexity; public class time_complexity { /* Constant order */ static int constant(int n) { int count = 0; int size = 100000; for (int i = 0; i < size; i++) count++; return count; } /* Linear order */ static int linear(int n) { int count = 0; for (int i = 0; i < n; i++) count++; return count; } /* Linear order (traversing array) */ static int arrayTraversal(int[] nums) { int count = 0; // Number of iterations is proportional to the array length for (int num : nums) { count++; } return count; } /* Exponential order */ static int quadratic(int n) { int count = 0; // Number of iterations is quadratically related to the data size n for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* Quadratic order (bubble sort) */ static int bubbleSort(int[] nums) { int count = 0; // Counter // Outer loop: unsorted range is [0, i] for (int i = nums.length - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // Element swap includes 3 unit operations } } } return count; } /* Exponential order (loop implementation) */ static int exponential(int n) { int count = 0, base = 1; // Cells divide into two every round, forming sequence 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; } /* Exponential order (recursive implementation) */ static int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* Logarithmic order (loop implementation) */ static int logarithmic(int n) { int count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* Logarithmic order (recursive implementation) */ static int logRecur(int n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* Linearithmic order */ 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; } /* Factorial order (recursive implementation) */ static int factorialRecur(int n) { if (n == 0) return 1; int count = 0; // Split from 1 into n for (int i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ public static void main(String[] args) { // You can modify n to run and observe the trend of the number of operations for various complexities int n = 8; System.out.println("Input data size n = " + n); int count = constant(n); System.out.println("Constant order operation count = " + count); count = linear(n); System.out.println("Linear order operation count = " + count); count = arrayTraversal(new int[n]); System.out.println("Linear order (array traversal) operation count = " + count); count = quadratic(n); System.out.println("Quadratic order operation count = " + 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("Quadratic order (bubble sort) operation count = " + count); count = exponential(n); System.out.println("Exponential order (loop implementation) operation count = " + count); count = expRecur(n); System.out.println("Exponential order (recursive implementation) operation count = " + count); count = logarithmic(n); System.out.println("Logarithmic order (loop implementation) operation count = " + count); count = logRecur(n); System.out.println("Logarithmic order (recursive implementation) operation count = " + count); count = linearLogRecur(n); System.out.println("Linearithmic order (recursive implementation) operation count = " + count); count = factorialRecur(n); System.out.println("Factorial order (recursive implementation) operation count = " + count); } } ================================================ FILE: en/codes/java/chapter_computational_complexity/worst_best_time_complexity.java ================================================ /** * 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 { /* Generate an array with elements { 1, 2, ..., n }, order shuffled */ static int[] randomNumbers(int n) { Integer[] nums = new Integer[n]; // Generate array nums = { 1, 2, 3, ..., n } for (int i = 0; i < n; i++) { nums[i] = i + 1; } // Randomly shuffle array elements 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; } /* Find the index of number 1 in array nums */ static int findOne(int[] nums) { for (int i = 0; i < nums.length; i++) { // When element 1 is at the head of the array, best time complexity O(1) is achieved // When element 1 is at the tail of the array, worst time complexity O(n) is achieved if (nums[i] == 1) return i; } return -1; } /* Driver Code */ public static void main(String[] args) { for (int i = 0; i < 10; i++) { int n = 100; int[] nums = randomNumbers(n); int index = findOne(nums); System.out.println("\nArray [ 1, 2, ..., n ] after shuffling = " + Arrays.toString(nums)); System.out.println("Index of number 1 is " + index); } } } ================================================ FILE: en/codes/java/chapter_divide_and_conquer/binary_search_recur.java ================================================ /** * 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 { /* Binary search: problem f(i, j) */ static int dfs(int[] nums, int target, int i, int j) { // If the interval is empty, it means there is no target element, return -1 if (i > j) { return -1; } // Calculate the midpoint index m int m = (i + j) / 2; if (nums[m] < target) { // Recursion subproblem f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // Recursion subproblem f(i, m-1) return dfs(nums, target, i, m - 1); } else { // Found the target element, return its index return m; } } /* Binary search */ static int binarySearch(int[] nums, int target) { int n = nums.length; // Solve the problem 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 }; // Binary search (closed interval on both sides) int index = binarySearch(nums, target); System.out.println("Index of target element 6 = " + index); } } ================================================ FILE: en/codes/java/chapter_divide_and_conquer/build_tree.java ================================================ /** * 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 { /* Build binary tree: divide and conquer */ static TreeNode dfs(int[] preorder, Map inorderMap, int i, int l, int r) { // Terminate when the subtree interval is empty if (r - l < 0) return null; // Initialize the root node TreeNode root = new TreeNode(preorder[i]); // Query m to divide the left and right subtrees int m = inorderMap.get(preorder[i]); // Subproblem: build the left subtree root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // Subproblem: build the right subtree root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // Return the root node return root; } /* Build binary tree */ static TreeNode buildTree(int[] preorder, int[] inorder) { // Initialize hash map, storing the mapping from inorder elements to indices 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("Preorder traversal = " + Arrays.toString(preorder)); System.out.println("Inorder traversal = " + Arrays.toString(inorder)); TreeNode root = buildTree(preorder, inorder); System.out.println("The constructed binary tree is:"); PrintUtil.printTree(root); } } ================================================ FILE: en/codes/java/chapter_divide_and_conquer/hanota.java ================================================ /** * File: hanota.java * Created Time: 2023-07-17 * Author: krahets (krahets@163.com) */ package chapter_divide_and_conquer; import java.util.*; public class hanota { /* Move a disk */ static void move(List src, List tar) { // Take out a disk from the top of src Integer pan = src.remove(src.size() - 1); // Place the disk on top of tar tar.add(pan); } /* Solve the Tower of Hanoi problem f(i) */ static void dfs(int i, List src, List buf, List tar) { // If there is only one disk left in src, move it directly to tar if (i == 1) { move(src, tar); return; } // Subproblem f(i-1): move the top i-1 disks from src to buf using tar dfs(i - 1, src, tar, buf); // Subproblem f(1): move the remaining disk from src to tar move(src, tar); // Subproblem f(i-1): move the top i-1 disks from buf to tar using src dfs(i - 1, buf, src, tar); } /* Solve the Tower of Hanoi problem */ static void solveHanota(List A, List B, List C) { int n = A.size(); // Move the top n disks from A to C using B dfs(n, A, B, C); } public static void main(String[] args) { // The tail of the list is the top of the rod List A = new ArrayList<>(Arrays.asList(5, 4, 3, 2, 1)); List B = new ArrayList<>(); List C = new ArrayList<>(); System.out.println("In initial state:"); System.out.println("A = " + A); System.out.println("B = " + B); System.out.println("C = " + C); solveHanota(A, B, C); System.out.println("After disk movement is complete:"); System.out.println("A = " + A); System.out.println("B = " + B); System.out.println("C = " + C); } } ================================================ FILE: en/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java ================================================ /** * 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 { /* Backtracking */ public static void backtrack(List choices, int state, int n, List res) { // When climbing to the n-th stair, add 1 to the solution count if (state == n) res.set(0, res.get(0) + 1); // Traverse all choices for (Integer choice : choices) { // Pruning: not allowed to go beyond the n-th stair if (state + choice > n) continue; // Attempt: make choice, update state backtrack(choices, state + choice, n, res); // Backtrack } } /* Climbing stairs: Backtracking */ public static int climbingStairsBacktrack(int n) { List choices = Arrays.asList(1, 2); // Can choose to climb up 1 or 2 stairs int state = 0; // Start climbing from the 0-th stair List res = new ArrayList<>(); res.add(0); // Use res[0] to record the solution count 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("Climbing %d stairs has %d solutions", n, res)); } } ================================================ FILE: en/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java ================================================ /** * 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 { /* Climbing stairs with constraint: Dynamic programming */ static int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // Initialize dp table, used to store solutions to subproblems int[][] dp = new int[n + 1][3]; // Initial state: preset the solution to the smallest subproblem dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // State transition: gradually solve larger subproblems from smaller ones 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("Climbing %d stairs has %d solutions", n, res)); } } ================================================ FILE: en/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java ================================================ /** * File: climbing_stairs_dfs.java * Created Time: 2023-06-30 * Author: krahets (krahets@163.com) */ package chapter_dynamic_programming; public class climbing_stairs_dfs { /* Search */ public static int dfs(int i) { // Known dp[1] and dp[2], return them 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; } /* Climbing stairs: Search */ 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("Climbing %d stairs has %d solutions", n, res)); } } ================================================ FILE: en/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java ================================================ /** * 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 { /* Memoization search */ public static int dfs(int i, int[] mem) { // Known dp[1] and dp[2], return them if (i == 1 || i == 2) return i; // If record dp[i] exists, return it directly 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); // Record dp[i] mem[i] = count; return count; } /* Climbing stairs: Memoization search */ public static int climbingStairsDFSMem(int n) { // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record 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("Climbing %d stairs has %d solutions", n, res)); } } ================================================ FILE: en/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java ================================================ /** * File: climbing_stairs_dp.java * Created Time: 2023-06-30 * Author: krahets (krahets@163.com) */ package chapter_dynamic_programming; public class climbing_stairs_dp { /* Climbing stairs: Dynamic programming */ public static int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; // Initialize dp table, used to store solutions to subproblems int[] dp = new int[n + 1]; // Initial state: preset the solution to the smallest subproblem dp[1] = 1; dp[2] = 2; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* Climbing stairs: Space-optimized dynamic programming */ 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("Climbing %d stairs has %d solutions", n, res)); res = climbingStairsDPComp(n); System.out.println(String.format("Climbing %d stairs has %d solutions", n, res)); } } ================================================ FILE: en/codes/java/chapter_dynamic_programming/coin_change.java ================================================ /** * 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 { /* Coin change: Dynamic programming */ static int coinChangeDP(int[] coins, int amt) { int n = coins.length; int MAX = amt + 1; // Initialize dp table int[][] dp = new int[n + 1][amt + 1]; // State transition: first row and first column for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // State transition: rest of the rows and columns for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // The smaller value between not selecting and selecting coin 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; } /* Coin change: Space-optimized dynamic programming */ static int coinChangeDPComp(int[] coins, int amt) { int n = coins.length; int MAX = amt + 1; // Initialize dp table int[] dp = new int[amt + 1]; Arrays.fill(dp, MAX); dp[0] = 0; // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // The smaller value between not selecting and selecting coin 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; // Dynamic programming int res = coinChangeDP(coins, amt); System.out.println("Minimum number of coins needed to make target amount is " + res); // Space-optimized dynamic programming res = coinChangeDPComp(coins, amt); System.out.println("Minimum number of coins needed to make target amount is " + res); } } ================================================ FILE: en/codes/java/chapter_dynamic_programming/coin_change_ii.java ================================================ /** * File: coin_change_ii.java * Created Time: 2023-07-11 * Author: krahets (krahets@163.com) */ package chapter_dynamic_programming; public class coin_change_ii { /* Coin change II: Dynamic programming */ static int coinChangeIIDP(int[] coins, int amt) { int n = coins.length; // Initialize dp table int[][] dp = new int[n + 1][amt + 1]; // Initialize first column for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // Sum of the two options: not selecting and selecting coin i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* Coin change II: Space-optimized dynamic programming */ static int coinChangeIIDPComp(int[] coins, int amt) { int n = coins.length; // Initialize dp table int[] dp = new int[amt + 1]; dp[0] = 1; // State transition for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // Sum of the two options: not selecting and selecting coin i 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; // Dynamic programming int res = coinChangeIIDP(coins, amt); System.out.println("Number of coin combinations to make target amount is " + res); // Space-optimized dynamic programming res = coinChangeIIDPComp(coins, amt); System.out.println("Number of coin combinations to make target amount is " + res); } } ================================================ FILE: en/codes/java/chapter_dynamic_programming/edit_distance.java ================================================ /** * 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 { /* Edit distance: Brute-force search */ static int editDistanceDFS(String s, String t, int i, int j) { // If both s and t are empty, return 0 if (i == 0 && j == 0) return 0; // If s is empty, return length of t if (i == 0) return j; // If t is empty, return length of s if (j == 0) return i; // If two characters are equal, skip both characters if (s.charAt(i - 1) == t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 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 minimum edit steps return Math.min(Math.min(insert, delete), replace) + 1; } /* Edit distance: Memoization search */ static int editDistanceDFSMem(String s, String t, int[][] mem, int i, int j) { // If both s and t are empty, return 0 if (i == 0 && j == 0) return 0; // If s is empty, return length of t if (i == 0) return j; // If t is empty, return length of s if (j == 0) return i; // If there's a record, return it directly if (mem[i][j] != -1) return mem[i][j]; // If two characters are equal, skip both characters if (s.charAt(i - 1) == t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 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); // Record and return minimum edit steps mem[i][j] = Math.min(Math.min(insert, delete), replace) + 1; return mem[i][j]; } /* Edit distance: Dynamic programming */ static int editDistanceDP(String s, String t) { int n = s.length(), m = t.length(); int[][] dp = new int[n + 1][m + 1]; // State transition: first row and first column for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // State transition: rest of the rows and columns for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s.charAt(i - 1) == t.charAt(j - 1)) { // If two characters are equal, skip both characters dp[i][j] = dp[i - 1][j - 1]; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 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]; } /* Edit distance: Space-optimized dynamic programming */ static int editDistanceDPComp(String s, String t) { int n = s.length(), m = t.length(); int[] dp = new int[m + 1]; // State transition: first row for (int j = 1; j <= m; j++) { dp[j] = j; } // State transition: rest of the rows for (int i = 1; i <= n; i++) { // State transition: first column int leftup = dp[0]; // Temporarily store dp[i-1, j-1] dp[0] = i; // State transition: rest of the columns for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s.charAt(i - 1) == t.charAt(j - 1)) { // If two characters are equal, skip both characters dp[j] = leftup; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // Update for next round's 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(); // Brute-force search int res = editDistanceDFS(s, t, n, m); System.out.println("Changing " + s + " to " + t + " requires a minimum of " + res + " edits"); // Memoization search 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("Changing " + s + " to " + t + " requires a minimum of " + res + " edits"); // Dynamic programming res = editDistanceDP(s, t); System.out.println("Changing " + s + " to " + t + " requires a minimum of " + res + " edits"); // Space-optimized dynamic programming res = editDistanceDPComp(s, t); System.out.println("Changing " + s + " to " + t + " requires a minimum of " + res + " edits"); } } ================================================ FILE: en/codes/java/chapter_dynamic_programming/knapsack.java ================================================ /** * 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 knapsack: Brute-force search */ static int knapsackDFS(int[] wgt, int[] val, int i, int c) { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i == 0 || c == 0) { return 0; } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // Calculate the maximum value of not putting in and putting in item i int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // Return the larger value of the two options return Math.max(no, yes); } /* 0-1 knapsack: Memoization search */ static int knapsackDFSMem(int[] wgt, int[] val, int[][] mem, int i, int c) { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i == 0 || c == 0) { return 0; } // If there's a record, return it directly if (mem[i][c] != -1) { return mem[i][c]; } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // Calculate the maximum value of not putting in and putting in item 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]; // Record and return the larger value of the two options mem[i][c] = Math.max(no, yes); return mem[i][c]; } /* 0-1 knapsack: Dynamic programming */ static int knapsackDP(int[] wgt, int[] val, int cap) { int n = wgt.length; // Initialize dp table int[][] dp = new int[n + 1][cap + 1]; // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item 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 knapsack: Space-optimized dynamic programming */ static int knapsackDPComp(int[] wgt, int[] val, int cap) { int n = wgt.length; // Initialize dp table int[] dp = new int[cap + 1]; // State transition for (int i = 1; i <= n; i++) { // Traverse in reverse order for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // The larger value between not selecting and selecting item 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; // Brute-force search int res = knapsackDFS(wgt, val, n, cap); System.out.println("Maximum item value not exceeding knapsack capacity is " + res); // Memoization search 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("Maximum item value not exceeding knapsack capacity is " + res); // Dynamic programming res = knapsackDP(wgt, val, cap); System.out.println("Maximum item value not exceeding knapsack capacity is " + res); // Space-optimized dynamic programming res = knapsackDPComp(wgt, val, cap); System.out.println("Maximum item value not exceeding knapsack capacity is " + res); } } ================================================ FILE: en/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java ================================================ /** * 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 { /* Minimum cost climbing stairs: Dynamic programming */ public static int minCostClimbingStairsDP(int[] cost) { int n = cost.length - 1; if (n == 1 || n == 2) return cost[n]; // Initialize dp table, used to store solutions to subproblems int[] dp = new int[n + 1]; // Initial state: preset the solution to the smallest subproblem dp[1] = cost[1]; dp[2] = cost[2]; // State transition: gradually solve larger subproblems from smaller ones for (int i = 3; i <= n; i++) { dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* Minimum cost climbing stairs: Space-optimized dynamic programming */ 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("Input staircase cost list is %s", Arrays.toString(cost))); int res = minCostClimbingStairsDP(cost); System.out.println(String.format("Minimum cost to climb staircase is %d", res)); res = minCostClimbingStairsDPComp(cost); System.out.println(String.format("Minimum cost to climb staircase is %d", res)); } } ================================================ FILE: en/codes/java/chapter_dynamic_programming/min_path_sum.java ================================================ /** * 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 { /* Minimum path sum: Brute-force search */ static int minPathSumDFS(int[][] grid, int i, int j) { // If it's the top-left cell, terminate the search if (i == 0 && j == 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return Integer.MAX_VALUE; } // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1) int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); // Return the minimum path cost from top-left to (i, j) return Math.min(left, up) + grid[i][j]; } /* Minimum path sum: Memoization search */ static int minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { // If it's the top-left cell, terminate the search if (i == 0 && j == 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return Integer.MAX_VALUE; } // If there's a record, return it directly if (mem[i][j] != -1) { return mem[i][j]; } // Minimum path cost for left and upper cells int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // Record and return the minimum path cost from top-left to (i, j) mem[i][j] = Math.min(left, up) + grid[i][j]; return mem[i][j]; } /* Minimum path sum: Dynamic programming */ static int minPathSumDP(int[][] grid) { int n = grid.length, m = grid[0].length; // Initialize dp table int[][] dp = new int[n][m]; dp[0][0] = grid[0][0]; // State transition: first row for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // State transition: first column for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // State transition: rest of the rows and columns 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]; } /* Minimum path sum: Space-optimized dynamic programming */ static int minPathSumDPComp(int[][] grid) { int n = grid.length, m = grid[0].length; // Initialize dp table int[] dp = new int[m]; // State transition: first row dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // State transition: rest of the rows for (int i = 1; i < n; i++) { // State transition: first column dp[0] = dp[0] + grid[i][0]; // State transition: rest of the columns 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; // Brute-force search int res = minPathSumDFS(grid, n - 1, m - 1); System.out.println("Minimum path sum from top-left to bottom-right is " + res); // Memoization search 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("Minimum path sum from top-left to bottom-right is " + res); // Dynamic programming res = minPathSumDP(grid); System.out.println("Minimum path sum from top-left to bottom-right is " + res); // Space-optimized dynamic programming res = minPathSumDPComp(grid); System.out.println("Minimum path sum from top-left to bottom-right is " + res); } } ================================================ FILE: en/codes/java/chapter_dynamic_programming/unbounded_knapsack.java ================================================ /** * File: unbounded_knapsack.java * Created Time: 2023-07-11 * Author: krahets (krahets@163.com) */ package chapter_dynamic_programming; public class unbounded_knapsack { /* Unbounded knapsack: Dynamic programming */ static int unboundedKnapsackDP(int[] wgt, int[] val, int cap) { int n = wgt.length; // Initialize dp table int[][] dp = new int[n + 1][cap + 1]; // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item i dp[i][c] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } /* Unbounded knapsack: Space-optimized dynamic programming */ static int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { int n = wgt.length; // Initialize dp table int[] dp = new int[cap + 1]; // State transition for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[c] = dp[c]; } else { // The larger value between not selecting and selecting item 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; // Dynamic programming int res = unboundedKnapsackDP(wgt, val, cap); System.out.println("Maximum item value not exceeding knapsack capacity is " + res); // Space-optimized dynamic programming res = unboundedKnapsackDPComp(wgt, val, cap); System.out.println("Maximum item value not exceeding knapsack capacity is " + res); } } ================================================ FILE: en/codes/java/chapter_graph/graph_adjacency_list.java ================================================ /** * File: graph_adjacency_list.java * Created Time: 2023-01-26 * Author: krahets (krahets@163.com) */ package chapter_graph; import java.util.*; import utils.*; /* Undirected graph class based on adjacency list */ class GraphAdjList { // Adjacency list, key: vertex, value: all adjacent vertices of that vertex Map> adjList; /* Constructor */ public GraphAdjList(Vertex[][] edges) { this.adjList = new HashMap<>(); // Add all vertices and edges for (Vertex[] edge : edges) { addVertex(edge[0]); addVertex(edge[1]); addEdge(edge[0], edge[1]); } } /* Get the number of vertices */ public int size() { return adjList.size(); } /* Add edge */ public void addEdge(Vertex vet1, Vertex vet2) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) throw new IllegalArgumentException(); // Add edge vet1 - vet2 adjList.get(vet1).add(vet2); adjList.get(vet2).add(vet1); } /* Remove edge */ public void removeEdge(Vertex vet1, Vertex vet2) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) throw new IllegalArgumentException(); // Remove edge vet1 - vet2 adjList.get(vet1).remove(vet2); adjList.get(vet2).remove(vet1); } /* Add vertex */ public void addVertex(Vertex vet) { if (adjList.containsKey(vet)) return; // Add a new linked list in the adjacency list adjList.put(vet, new ArrayList<>()); } /* Remove vertex */ public void removeVertex(Vertex vet) { if (!adjList.containsKey(vet)) throw new IllegalArgumentException(); // Remove the linked list corresponding to vertex vet in the adjacency list adjList.remove(vet); // Traverse the linked lists of other vertices and remove all edges containing vet for (List list : adjList.values()) { list.remove(vet); } } /* Print adjacency list */ public void print() { System.out.println("Adjacency list ="); 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) { /* Add edge */ 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("\nAfter initialization, graph is"); graph.print(); /* Add edge */ // Vertices 1, 3 are v[0], v[1] graph.addEdge(v[0], v[2]); System.out.println("\nAfter adding edge 1-2, graph is"); graph.print(); /* Remove edge */ // Vertex 3 is v[1] graph.removeEdge(v[0], v[1]); System.out.println("\nAfter removing edge 1-3, graph is"); graph.print(); /* Add vertex */ Vertex v5 = new Vertex(6); graph.addVertex(v5); System.out.println("\nAfter adding vertex 6, graph is"); graph.print(); /* Remove vertex */ // Vertex 3 is v[1] graph.removeVertex(v[1]); System.out.println("\nAfter removing vertex 3, graph is"); graph.print(); } } ================================================ FILE: en/codes/java/chapter_graph/graph_adjacency_matrix.java ================================================ /** * File: graph_adjacency_matrix.java * Created Time: 2023-01-26 * Author: krahets (krahets@163.com) */ package chapter_graph; import utils.*; import java.util.*; /* Undirected graph class based on adjacency matrix */ class GraphAdjMat { List vertices; // Vertex list, where the element represents the "vertex value" and the index represents the "vertex index" List> adjMat; // Adjacency matrix, where the row and column indices correspond to the "vertex index" /* Constructor */ public GraphAdjMat(int[] vertices, int[][] edges) { this.vertices = new ArrayList<>(); this.adjMat = new ArrayList<>(); // Add vertex for (int val : vertices) { addVertex(val); } // Add edge // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices for (int[] e : edges) { addEdge(e[0], e[1]); } } /* Get the number of vertices */ public int size() { return vertices.size(); } /* Add vertex */ public void addVertex(int val) { int n = size(); // Add the value of the new vertex to the vertex list vertices.add(val); // Add a row to the adjacency matrix List newRow = new ArrayList<>(n); for (int j = 0; j < n; j++) { newRow.add(0); } adjMat.add(newRow); // Add a column to the adjacency matrix for (List row : adjMat) { row.add(0); } } /* Remove vertex */ public void removeVertex(int index) { if (index >= size()) throw new IndexOutOfBoundsException(); // Remove the vertex at index from the vertex list vertices.remove(index); // Remove the row at index from the adjacency matrix adjMat.remove(index); // Remove the column at index from the adjacency matrix for (List row : adjMat) { row.remove(index); } } /* Add edge */ // Parameters i, j correspond to the vertices element indices public void addEdge(int i, int j) { // Handle index out of bounds and equality if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw new IndexOutOfBoundsException(); // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i) adjMat.get(i).set(j, 1); adjMat.get(j).set(i, 1); } /* Remove edge */ // Parameters i, j correspond to the vertices element indices public void removeEdge(int i, int j) { // Handle index out of bounds and equality 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); } /* Print adjacency matrix */ public void print() { System.out.print("Vertex list = "); System.out.println(vertices); System.out.println("Adjacency matrix ="); PrintUtil.printMatrix(adjMat); } } public class graph_adjacency_matrix { public static void main(String[] args) { /* Add edge */ // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices 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("\nAfter initialization, graph is"); graph.print(); /* Add edge */ // Add vertex graph.addEdge(0, 2); System.out.println("\nAfter adding edge 1-2, graph is"); graph.print(); /* Remove edge */ // Vertices 1, 3 have indices 0, 1 respectively graph.removeEdge(0, 1); System.out.println("\nAfter removing edge 1-3, graph is"); graph.print(); /* Add vertex */ graph.addVertex(6); System.out.println("\nAfter adding vertex 6, graph is"); graph.print(); /* Remove vertex */ // Vertex 3 has index 1 graph.removeVertex(1); System.out.println("\nAfter removing vertex 3, graph is"); graph.print(); } } ================================================ FILE: en/codes/java/chapter_graph/graph_bfs.java ================================================ /** * 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 { /* Breadth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex static List graphBFS(GraphAdjList graph, Vertex startVet) { // Vertex traversal sequence List res = new ArrayList<>(); // Hash set for recording vertices that have been visited Set visited = new HashSet<>(); visited.add(startVet); // Queue used to implement BFS Queue que = new LinkedList<>(); que.offer(startVet); // Starting from vertex vet, loop until all vertices are visited while (!que.isEmpty()) { Vertex vet = que.poll(); // Dequeue the front vertex res.add(vet); // Record visited vertex // Traverse all adjacent vertices of this vertex for (Vertex adjVet : graph.adjList.get(vet)) { if (visited.contains(adjVet)) continue; // Skip vertices that have been visited que.offer(adjVet); // Only enqueue unvisited vertices visited.add(adjVet); // Mark this vertex as visited } } // Return vertex traversal sequence return res; } public static void main(String[] args) { /* Add edge */ 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("\nAfter initialization, graph is"); graph.print(); /* Breadth-first traversal */ List res = graphBFS(graph, v[0]); System.out.println("\nBreadth-first traversal (BFS) vertex sequence is"); System.out.println(Vertex.vetsToVals(res)); } } ================================================ FILE: en/codes/java/chapter_graph/graph_dfs.java ================================================ /** * 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 { /* Depth-first traversal helper function */ static void dfs(GraphAdjList graph, Set visited, List res, Vertex vet) { res.add(vet); // Record visited vertex visited.add(vet); // Mark this vertex as visited // Traverse all adjacent vertices of this vertex for (Vertex adjVet : graph.adjList.get(vet)) { if (visited.contains(adjVet)) continue; // Skip vertices that have been visited // Recursively visit adjacent vertices dfs(graph, visited, res, adjVet); } } /* Depth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex static List graphDFS(GraphAdjList graph, Vertex startVet) { // Vertex traversal sequence List res = new ArrayList<>(); // Hash set for recording vertices that have been visited Set visited = new HashSet<>(); dfs(graph, visited, res, startVet); return res; } public static void main(String[] args) { /* Add edge */ 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("\nAfter initialization, graph is"); graph.print(); /* Depth-first traversal */ List res = graphDFS(graph, v[0]); System.out.println("\nDepth-first traversal (DFS) vertex sequence is"); System.out.println(Vertex.vetsToVals(res)); } } ================================================ FILE: en/codes/java/chapter_greedy/coin_change_greedy.java ================================================ /** * 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 { /* Coin change: Greedy algorithm */ static int coinChangeGreedy(int[] coins, int amt) { // Assume coins list is sorted int i = coins.length - 1; int count = 0; // Loop to make greedy choices until no remaining amount while (amt > 0) { // Find the coin that is less than and closest to the remaining amount while (i > 0 && coins[i] > amt) { i--; } // Choose coins[i] amt -= coins[i]; count++; } // If no feasible solution is found, return -1 return amt == 0 ? count : -1; } public static void main(String[] args) { // Greedy algorithm: Can guarantee finding the global optimal solution 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("Minimum number of coins needed to make " + amt + " is " + res); // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = new int[] { 1, 20, 50 }; amt = 60; res = coinChangeGreedy(coins, amt); System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); System.out.println("Minimum number of coins needed to make " + amt + " is " + res); System.out.println("Actually the minimum number needed is 3, i.e., 20 + 20 + 20"); // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = new int[] { 1, 49, 50 }; amt = 98; res = coinChangeGreedy(coins, amt); System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); System.out.println("Minimum number of coins needed to make " + amt + " is " + res); System.out.println("Actually the minimum number needed is 2, i.e., 49 + 49"); } } ================================================ FILE: en/codes/java/chapter_greedy/fractional_knapsack.java ================================================ /** * 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; /* Item */ class Item { int w; // Item weight int v; // Item value public Item(int w, int v) { this.w = w; this.v = v; } } public class fractional_knapsack { /* Fractional knapsack: Greedy algorithm */ static double fractionalKnapsack(int[] wgt, int[] val, int cap) { // Create item list with two attributes: weight, value Item[] items = new Item[wgt.length]; for (int i = 0; i < wgt.length; i++) { items[i] = new Item(wgt[i], val[i]); } // Sort by unit value item.v / item.w from high to low Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w))); // Loop for greedy selection double res = 0; for (Item item : items) { if (item.w <= cap) { // If remaining capacity is sufficient, put the entire current item into the knapsack res += item.v; cap -= item.w; } else { // If remaining capacity is insufficient, put part of the current item into the knapsack res += (double) item.v / item.w * cap; // No remaining capacity, so break out of the loop 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; // Greedy algorithm double res = fractionalKnapsack(wgt, val, cap); System.out.println("Maximum item value not exceeding knapsack capacity is " + res); } } ================================================ FILE: en/codes/java/chapter_greedy/max_capacity.java ================================================ /** * File: max_capacity.java * Created Time: 2023-07-21 * Author: krahets (krahets@163.com) */ package chapter_greedy; public class max_capacity { /* Max capacity: Greedy algorithm */ static int maxCapacity(int[] ht) { // Initialize i, j to be at both ends of the array int i = 0, j = ht.length - 1; // Initial max capacity is 0 int res = 0; // Loop for greedy selection until the two boards meet while (i < j) { // Update max capacity int cap = Math.min(ht[i], ht[j]) * (j - i); res = Math.max(res, cap); // Move the shorter board inward 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 }; // Greedy algorithm int res = maxCapacity(ht); System.out.println("Maximum capacity is " + res); } } ================================================ FILE: en/codes/java/chapter_greedy/max_product_cutting.java ================================================ /** * 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 { /* Max product cutting: Greedy algorithm */ public static int maxProductCutting(int n) { // When n <= 3, must cut out a 1 if (n <= 3) { return 1 * (n - 1); } // Greedily cut out 3, a is the number of 3s, b is the remainder int a = n / 3; int b = n % 3; if (b == 1) { // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2 return (int) Math.pow(3, a - 1) * 2 * 2; } if (b == 2) { // When the remainder is 2, do nothing return (int) Math.pow(3, a) * 2; } // When the remainder is 0, do nothing return (int) Math.pow(3, a); } public static void main(String[] args) { int n = 58; // Greedy algorithm int res = maxProductCutting(n); System.out.println("Maximum cutting product is " + res); } } ================================================ FILE: en/codes/java/chapter_hashing/array_hash_map.java ================================================ /** * File: array_hash_map.java * Created Time: 2022-12-04 * Author: krahets (krahets@163.com) */ package chapter_hashing; import java.util.*; /* Key-value pair */ class Pair { public int key; public String val; public Pair(int key, String val) { this.key = key; this.val = val; } } /* Hash table based on array implementation */ class ArrayHashMap { private List buckets; public ArrayHashMap() { // Initialize array with 100 buckets buckets = new ArrayList<>(); for (int i = 0; i < 100; i++) { buckets.add(null); } } /* Hash function */ private int hashFunc(int key) { int index = key % 100; return index; } /* Query operation */ public String get(int key) { int index = hashFunc(key); Pair pair = buckets.get(index); if (pair == null) return null; return pair.val; } /* Add operation */ public void put(int key, String val) { Pair pair = new Pair(key, val); int index = hashFunc(key); buckets.set(index, pair); } /* Remove operation */ public void remove(int key) { int index = hashFunc(key); // Set to null to represent deletion buckets.set(index, null); } /* Get all key-value pairs */ public List pairSet() { List pairSet = new ArrayList<>(); for (Pair pair : buckets) { if (pair != null) pairSet.add(pair); } return pairSet; } /* Get all keys */ public List keySet() { List keySet = new ArrayList<>(); for (Pair pair : buckets) { if (pair != null) keySet.add(pair.key); } return keySet; } /* Get all values */ public List valueSet() { List valueSet = new ArrayList<>(); for (Pair pair : buckets) { if (pair != null) valueSet.add(pair.val); } return valueSet; } /* Print hash table */ 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) { /* Initialize hash table */ ArrayHashMap map = new ArrayHashMap(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(12836, "Xiao Ha"); map.put(15937, "Xiao Luo"); map.put(16750, "Xiao Suan"); map.put(13276, "Xiao Fa"); map.put(10583, "Xiao Ya"); System.out.println("\nAfter adding is complete, hash table is\nKey -> Value"); map.print(); /* Query operation */ // Input key into hash table to get value String name = map.get(15937); System.out.println("\nInput student ID 15937, query name " + name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(10583); System.out.println("\nAfter removing 10583, hash table is\nKey -> Value"); map.print(); /* Traverse hash table */ System.out.println("\nTraverse key-value pairs Key->Value"); for (Pair kv : map.pairSet()) { System.out.println(kv.key + " -> " + kv.val); } System.out.println("\nTraverse keys only Key"); for (int key : map.keySet()) { System.out.println(key); } System.out.println("\nTraverse values only Value"); for (String val : map.valueSet()) { System.out.println(val); } } } ================================================ FILE: en/codes/java/chapter_hashing/built_in_hash.java ================================================ /** * 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("Hash value of integer " + num + " is " + hashNum); boolean bol = true; int hashBol = Boolean.hashCode(bol); System.out.println("Hash value of boolean " + bol + " is " + hashBol); double dec = 3.14159; int hashDec = Double.hashCode(dec); System.out.println("Hash value of decimal " + dec + " is " + hashDec); String str = "Hello Algo"; int hashStr = str.hashCode(); System.out.println("Hash value of string " + str + " is " + hashStr); Object[] arr = { 12836, "Xiao Ha" }; int hashTup = Arrays.hashCode(arr); System.out.println("Hash value of array " + Arrays.toString(arr) + " is " + hashTup); ListNode obj = new ListNode(0); int hashObj = obj.hashCode(); System.out.println("Hash value of node object " + obj + " is " + hashObj); } } ================================================ FILE: en/codes/java/chapter_hashing/hash_map.java ================================================ /** * 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) { /* Initialize hash table */ Map map = new HashMap<>(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(12836, "Xiao Ha"); map.put(15937, "Xiao Luo"); map.put(16750, "Xiao Suan"); map.put(13276, "Xiao Fa"); map.put(10583, "Xiao Ya"); System.out.println("\nAfter adding is complete, hash table is\nKey -> Value"); PrintUtil.printHashMap(map); /* Query operation */ // Input key into hash table to get value String name = map.get(15937); System.out.println("\nInput student ID 15937, query name " + name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(10583); System.out.println("\nAfter removing 10583, hash table is\nKey -> Value"); PrintUtil.printHashMap(map); /* Traverse hash table */ System.out.println("\nTraverse key-value pairs Key->Value"); for (Map.Entry kv : map.entrySet()) { System.out.println(kv.getKey() + " -> " + kv.getValue()); } System.out.println("\nTraverse keys only Key"); for (int key : map.keySet()) { System.out.println(key); } System.out.println("\nTraverse values only Value"); for (String val : map.values()) { System.out.println(val); } } } ================================================ FILE: en/codes/java/chapter_hashing/hash_map_chaining.java ================================================ /** * 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; /* Hash table with separate chaining */ class HashMapChaining { int size; // Number of key-value pairs int capacity; // Hash table capacity double loadThres; // Load factor threshold for triggering expansion int extendRatio; // Expansion multiplier List> buckets; // Bucket array /* Constructor */ 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<>()); } } /* Hash function */ int hashFunc(int key) { return key % capacity; } /* Load factor */ double loadFactor() { return (double) size / capacity; } /* Query operation */ String get(int key) { int index = hashFunc(key); List bucket = buckets.get(index); // Traverse bucket, if key is found, return corresponding val for (Pair pair : bucket) { if (pair.key == key) { return pair.val; } } // If key is not found, return null return null; } /* Add operation */ void put(int key, String val) { // When load factor exceeds threshold, perform expansion if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); List bucket = buckets.get(index); // Traverse bucket, if specified key is encountered, update corresponding val and return for (Pair pair : bucket) { if (pair.key == key) { pair.val = val; return; } } // If key does not exist, append key-value pair to the end Pair pair = new Pair(key, val); bucket.add(pair); size++; } /* Remove operation */ void remove(int key) { int index = hashFunc(key); List bucket = buckets.get(index); // Traverse bucket and remove key-value pair from it for (Pair pair : bucket) { if (pair.key == key) { bucket.remove(pair); size--; break; } } } /* Expand hash table */ void extend() { // Temporarily store the original hash table List> bucketsTmp = buckets; // Initialize expanded new hash table capacity *= extendRatio; buckets = new ArrayList<>(capacity); for (int i = 0; i < capacity; i++) { buckets.add(new ArrayList<>()); } size = 0; // Move key-value pairs from original hash table to new hash table for (List bucket : bucketsTmp) { for (Pair pair : bucket) { put(pair.key, pair.val); } } } /* Print hash table */ 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) { /* Initialize hash table */ HashMapChaining map = new HashMapChaining(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(12836, "Xiao Ha"); map.put(15937, "Xiao Luo"); map.put(16750, "Xiao Suan"); map.put(13276, "Xiao Fa"); map.put(10583, "Xiao Ya"); System.out.println("\nAfter adding is complete, hash table is\nKey -> Value"); map.print(); /* Query operation */ // Input key into hash table to get value String name = map.get(13276); System.out.println("\nInput student ID 13276, query name " + name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(12836); System.out.println("\nAfter removing 12836, hash table is\nKey -> Value"); map.print(); } } ================================================ FILE: en/codes/java/chapter_hashing/hash_map_open_addressing.java ================================================ /** * File: hash_map_open_addressing.java * Created Time: 2023-06-13 * Author: krahets (krahets@163.com) */ package chapter_hashing; /* Hash table with open addressing */ class HashMapOpenAddressing { private int size; // Number of key-value pairs private int capacity = 4; // Hash table capacity private final double loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion private final int extendRatio = 2; // Expansion multiplier private Pair[] buckets; // Bucket array private final Pair TOMBSTONE = new Pair(-1, "-1"); // Removal marker /* Constructor */ public HashMapOpenAddressing() { size = 0; buckets = new Pair[capacity]; } /* Hash function */ private int hashFunc(int key) { return key % capacity; } /* Load factor */ private double loadFactor() { return (double) size / capacity; } /* Search for bucket index corresponding to key */ private int findBucket(int key) { int index = hashFunc(key); int firstTombstone = -1; // Linear probing, break when encountering an empty bucket while (buckets[index] != null) { // If key is encountered, return the corresponding bucket index if (buckets[index].key == key) { // If a removal marker was encountered before, move the key-value pair to that index if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index]; buckets[index] = TOMBSTONE; return firstTombstone; // Return the moved bucket index } return index; // Return bucket index } // Record the first removal marker encountered if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index; } // Calculate bucket index, wrap around to the head if past the tail index = (index + 1) % capacity; } // If key does not exist, return the index for insertion return firstTombstone == -1 ? index : firstTombstone; } /* Query operation */ public String get(int key) { // Search for bucket index corresponding to key int index = findBucket(key); // If key-value pair is found, return corresponding val if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index].val; } // If key-value pair does not exist, return null return null; } /* Add operation */ public void put(int key, String val) { // When load factor exceeds threshold, perform expansion if (loadFactor() > loadThres) { extend(); } // Search for bucket index corresponding to key int index = findBucket(key); // If key-value pair is found, overwrite val and return if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index].val = val; return; } // If key-value pair does not exist, add the key-value pair buckets[index] = new Pair(key, val); size++; } /* Remove operation */ public void remove(int key) { // Search for bucket index corresponding to key int index = findBucket(key); // If key-value pair is found, overwrite it with removal marker if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE; size--; } } /* Expand hash table */ private void extend() { // Temporarily store the original hash table Pair[] bucketsTmp = buckets; // Initialize expanded new hash table capacity *= extendRatio; buckets = new Pair[capacity]; size = 0; // Move key-value pairs from original hash table to new hash table for (Pair pair : bucketsTmp) { if (pair != null && pair != TOMBSTONE) { put(pair.key, pair.val); } } } /* Print hash table */ 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) { // Initialize hash table HashMapOpenAddressing hashmap = new HashMapOpenAddressing(); // Add operation // Add key-value pair (key, val) to the hash table hashmap.put(12836, "Xiao Ha"); hashmap.put(15937, "Xiao Luo"); hashmap.put(16750, "Xiao Suan"); hashmap.put(13276, "Xiao Fa"); hashmap.put(10583, "Xiao Ya"); System.out.println("\nAfter adding is complete, hash table is\nKey -> Value"); hashmap.print(); // Query operation // Input key into hash table to get value val String name = hashmap.get(13276); System.out.println("\nInput student ID 13276, query name " + name); // Remove operation // Remove key-value pair (key, val) from hash table hashmap.remove(16750); System.out.println("\nAfter removing 16750, hash table is\nKey -> Value"); hashmap.print(); } } ================================================ FILE: en/codes/java/chapter_hashing/simple_hash.java ================================================ /** * File: simple_hash.java * Created Time: 2023-06-21 * Author: krahets (krahets@163.com) */ package chapter_hashing; public class simple_hash { /* Additive 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; } /* Multiplicative 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 hash */ static int xorHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (char c : key.toCharArray()) { hash ^= (int) c; } return hash & MODULUS; } /* Rotational hash */ 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 Algo"; int hash = addHash(key); System.out.println("Additive hash value is " + hash); hash = mulHash(key); System.out.println("Multiplicative hash value is " + hash); hash = xorHash(key); System.out.println("XOR hash value is " + hash); hash = rotHash(key); System.out.println("Rotational hash value is " + hash); } } ================================================ FILE: en/codes/java/chapter_heap/heap.java ================================================ /** * 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); // Element enters heap System.out.format("\nAfter element %d enters heap\n", val); PrintUtil.printHeap(heap); } public static void testPop(Queue heap) { int val = heap.poll(); // Time complexity is O(n), not O(nlogn) System.out.format("\nAfter heap top element %d exits heap\n", val); PrintUtil.printHeap(heap); } public static void main(String[] args) { /* Initialize heap */ // Python's heapq module implements min heap by default Queue minHeap = new PriorityQueue<>(); // Initialize max heap (modify Comparator using lambda expression) Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); System.out.println("\nThe following test cases are for max heap"); /* Element enters heap */ testPush(maxHeap, 1); testPush(maxHeap, 3); testPush(maxHeap, 2); testPush(maxHeap, 5); testPush(maxHeap, 4); /* Check if heap is empty */ int peek = maxHeap.peek(); System.out.format("\nHeap top element is %d\n", peek); /* Time complexity is O(n), not O(nlogn) */ testPop(maxHeap); testPop(maxHeap); testPop(maxHeap); testPop(maxHeap); testPop(maxHeap); /* Get heap size */ int size = maxHeap.size(); System.out.format("\nHeap element count is %d\n", size); /* Check if heap is empty */ boolean isEmpty = maxHeap.isEmpty(); System.out.format("\nHeap is empty %b\n", isEmpty); /* Input list and build heap */ // Time complexity is O(n), not O(nlogn) minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); System.out.println("\nAfter inputting list and building min heap"); PrintUtil.printHeap(minHeap); } } ================================================ FILE: en/codes/java/chapter_heap/my_heap.java ================================================ /** * File: my_heap.java * Created Time: 2023-01-07 * Author: krahets (krahets@163.com) */ package chapter_heap; import utils.*; import java.util.*; /* Max heap */ class MaxHeap { // Use list instead of array, no need to consider capacity expansion private List maxHeap; /* Constructor, build heap based on input list */ public MaxHeap(List nums) { // Add list elements to heap as is maxHeap = new ArrayList<>(nums); // Heapify all nodes except leaf nodes for (int i = parent(size() - 1); i >= 0; i--) { siftDown(i); } } /* Get index of left child node */ private int left(int i) { return 2 * i + 1; } /* Get index of right child node */ private int right(int i) { return 2 * i + 2; } /* Get index of parent node */ private int parent(int i) { return (i - 1) / 2; // Floor division } /* Swap elements */ private void swap(int i, int j) { int tmp = maxHeap.get(i); maxHeap.set(i, maxHeap.get(j)); maxHeap.set(j, tmp); } /* Get heap size */ public int size() { return maxHeap.size(); } /* Check if heap is empty */ public boolean isEmpty() { return size() == 0; } /* Access top element */ public int peek() { return maxHeap.get(0); } /* Element enters heap */ public void push(int val) { // Add node maxHeap.add(val); // Heapify from bottom to top siftUp(size() - 1); } /* Starting from node i, heapify from bottom to top */ private void siftUp(int i) { while (true) { // Get parent node of node i int p = parent(i); // When "crossing root node" or "node needs no repair", end heapify if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) break; // Swap two nodes swap(i, p); // Loop upward heapify i = p; } } /* Element exits heap */ public int pop() { // Handle empty case if (isEmpty()) throw new IndexOutOfBoundsException(); // Delete node swap(0, size() - 1); // Remove node int val = maxHeap.remove(size() - 1); // Return top element siftDown(0); // Return heap top element return val; } /* Starting from node i, heapify from top to bottom */ private void siftDown(int i) { while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break 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; // Swap two nodes if (ma == i) break; // Swap two nodes swap(i, ma); // Loop downwards heapification i = ma; } } /* Driver Code */ 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) { /* Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap */ MaxHeap maxHeap = new MaxHeap(Arrays.asList(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)); System.out.println("\nAfter inputting list and building heap"); maxHeap.print(); /* Check if heap is empty */ int peek = maxHeap.peek(); System.out.format("\nHeap top element is %d\n", peek); /* Element enters heap */ int val = 7; maxHeap.push(val); System.out.format("\nAfter element %d enters heap\n", val); maxHeap.print(); /* Time complexity is O(n), not O(nlogn) */ peek = maxHeap.pop(); System.out.format("\nAfter heap top element %d exits heap\n", peek); maxHeap.print(); /* Get heap size */ int size = maxHeap.size(); System.out.format("\nHeap element count is %d\n", size); /* Check if heap is empty */ boolean isEmpty = maxHeap.isEmpty(); System.out.format("\nHeap is empty %b\n", isEmpty); } } ================================================ FILE: en/codes/java/chapter_heap/top_k.java ================================================ /** * 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 { /* Find the largest k elements in array based on heap */ static Queue topKHeap(int[] nums, int k) { // Python's heapq module implements min heap by default Queue heap = new PriorityQueue(); // Enter the first k elements of array into heap for (int i = 0; i < k; i++) { heap.offer(nums[i]); } // Starting from the (k+1)th element, maintain heap length as k for (int i = k; i < nums.length; i++) { // If current element is greater than top element, top element exits heap, current element enters heap 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("The largest " + k + " elements are"); PrintUtil.printHeap(res); } } ================================================ FILE: en/codes/java/chapter_searching/binary_search.java ================================================ /** * File: binary_search.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_searching; public class binary_search { /* Binary search (closed interval on both sides) */ static int binarySearch(int[] nums, int target) { // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array int i = 0, j = nums.length - 1; // Loop, exit when the search interval is empty (empty when i > j) while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) // This means target is in the interval [m+1, j] i = m + 1; else if (nums[m] > target) // This means target is in the interval [i, m-1] j = m - 1; else // Found the target element, return its index return m; } // Target element not found, return -1 return -1; } /* Binary search (left-closed right-open interval) */ static int binarySearchLCRO(int[] nums, int target) { // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1 int i = 0, j = nums.length; // Loop, exit when the search interval is empty (empty when i = j) while (i < j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) // This means target is in the interval [m+1, j) i = m + 1; else if (nums[m] > target) // This means target is in the interval [i, m) j = m; else // Found the target element, return its index return m; } // Target element not found, return -1 return -1; } public static void main(String[] args) { int target = 6; int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; /* Binary search (closed interval on both sides) */ int index = binarySearch(nums, target); System.out.println("Index of target element 6 = " + index); /* Binary search (left-closed right-open interval) */ index = binarySearchLCRO(nums, target); System.out.println("Index of target element 6 = " + index); } } ================================================ FILE: en/codes/java/chapter_searching/binary_search_edge.java ================================================ /** * File: binary_search_edge.java * Created Time: 2023-08-04 * Author: krahets (krahets@163.com) */ package chapter_searching; public class binary_search_edge { /* Binary search for the leftmost target */ static int binarySearchLeftEdge(int[] nums, int target) { // Equivalent to finding the insertion point of target int i = binary_search_insertion.binarySearchInsertion(nums, target); // Target not found, return -1 if (i == nums.length || nums[i] != target) { return -1; } // Found target, return index i return i; } /* Binary search for the rightmost target */ static int binarySearchRightEdge(int[] nums, int target) { // Convert to finding the leftmost target + 1 int i = binary_search_insertion.binarySearchInsertion(nums, target + 1); // j points to the rightmost target, i points to the first element greater than target int j = i - 1; // Target not found, return -1 if (j == -1 || nums[j] != target) { return -1; } // Found target, return index j return j; } public static void main(String[] args) { // Array with duplicate elements int[] nums = { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; System.out.println("\nArray nums = " + java.util.Arrays.toString(nums)); // Binary search left and right boundaries for (int target : new int[] { 6, 7 }) { int index = binarySearchLeftEdge(nums, target); System.out.println("Index of leftmost element " + target + " is " + index); index = binarySearchRightEdge(nums, target); System.out.println("Index of rightmost element " + target + " is " + index); } } } ================================================ FILE: en/codes/java/chapter_searching/binary_search_insertion.java ================================================ /** * File: binary_search_insertion.java * Created Time: 2023-08-04 * Author: krahets (krahets@163.com) */ package chapter_searching; class binary_search_insertion { /* Binary search for insertion point (no duplicate elements) */ static int binarySearchInsertionSimple(int[] nums, int target) { int i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { return m; // Found target, return insertion point m } } // Target not found, return insertion point i return i; } /* Binary search for insertion point (with duplicate elements) */ static int binarySearchInsertion(int[] nums, int target) { int i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Calculate the midpoint index m if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { j = m - 1; // The first element less than target is in the interval [i, m-1] } } // Return insertion point i return i; } public static void main(String[] args) { // Array without duplicate elements int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; System.out.println("\nArray nums = " + java.util.Arrays.toString(nums)); // Binary search for insertion point for (int target : new int[] { 6, 9 }) { int index = binarySearchInsertionSimple(nums, target); System.out.println("Insertion point index for element " + target + " is " + index); } // Array with duplicate elements nums = new int[] { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; System.out.println("\nArray nums = " + java.util.Arrays.toString(nums)); // Binary search for insertion point for (int target : new int[] { 2, 6, 20 }) { int index = binarySearchInsertion(nums, target); System.out.println("Insertion point index for element " + target + " is " + index); } } } ================================================ FILE: en/codes/java/chapter_searching/hashing_search.java ================================================ /** * 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 { /* Hash search (array) */ static int hashingSearchArray(Map map, int target) { // Hash table's key: target element, value: index // If this key does not exist in the hash table, return -1 return map.getOrDefault(target, -1); } /* Hash search (linked list) */ static ListNode hashingSearchLinkedList(Map map, int target) { // Hash table key: target node value, value: node object // If key is not in hash table, return null return map.getOrDefault(target, null); } public static void main(String[] args) { int target = 3; /* Hash search (array) */ int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; // Initialize hash table Map map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { map.put(nums[i], i); // key: element, value: index } int index = hashingSearchArray(map, target); System.out.println("Index of target element 3 = " + index); /* Hash search (linked list) */ ListNode head = ListNode.arrToLinkedList(nums); // Initialize hash table Map map1 = new HashMap<>(); while (head != null) { map1.put(head.val, head); // key: node value, value: node head = head.next; } ListNode node = hashingSearchLinkedList(map1, target); System.out.println("Node object corresponding to target node value 3 is " + node); } } ================================================ FILE: en/codes/java/chapter_searching/linear_search.java ================================================ /** * File: linear_search.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_searching; import utils.*; public class linear_search { /* Linear search (array) */ static int linearSearchArray(int[] nums, int target) { // Traverse array for (int i = 0; i < nums.length; i++) { // Found the target element, return its index if (nums[i] == target) return i; } // Target element not found, return -1 return -1; } /* Linear search (linked list) */ static ListNode linearSearchLinkedList(ListNode head, int target) { // Traverse the linked list while (head != null) { // Found the target node, return it if (head.val == target) return head; head = head.next; } // Target node not found, return null return null; } public static void main(String[] args) { int target = 3; /* Perform linear search in array */ int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; int index = linearSearchArray(nums, target); System.out.println("Index of target element 3 = " + index); /* Perform linear search in linked list */ ListNode head = ListNode.arrToLinkedList(nums); ListNode node = linearSearchLinkedList(head, target); System.out.println("Node object corresponding to target node value 3 is " + node); } } ================================================ FILE: en/codes/java/chapter_searching/two_sum.java ================================================ /** * File: two_sum.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_searching; import java.util.*; public class two_sum { /* Method 1: Brute force enumeration */ static int[] twoSumBruteForce(int[] nums, int target) { int size = nums.length; // Two nested loops, time complexity is 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]; } /* Method 2: Auxiliary hash table */ static int[] twoSumHashTable(int[] nums, int target) { int size = nums.length; // Auxiliary hash table, space complexity is O(n) Map dic = new HashMap<>(); // Single loop, time complexity is 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) { // ======= Test Case ======= int[] nums = { 2, 7, 11, 15 }; int target = 13; // ====== Driver Code ====== // Method 1 int[] res = twoSumBruteForce(nums, target); System.out.println("Method 1 res = " + Arrays.toString(res)); // Method 2 res = twoSumHashTable(nums, target); System.out.println("Method 2 res = " + Arrays.toString(res)); } } ================================================ FILE: en/codes/java/chapter_sorting/bubble_sort.java ================================================ /** * File: bubble_sort.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_sorting; import java.util.*; public class bubble_sort { /* Bubble sort */ static void bubbleSort(int[] nums) { // Outer loop: unsorted range is [0, i] for (int i = nums.length - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* Bubble sort (flag optimization) */ static void bubbleSortWithFlag(int[] nums) { // Outer loop: unsorted range is [0, i] for (int i = nums.length - 1; i > 0; i--) { boolean flag = false; // Initialize flag // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // Record element swap } } if (!flag) break; // No elements were swapped in this round of "bubbling", exit directly } } public static void main(String[] args) { int[] nums = { 4, 1, 3, 1, 5, 2 }; bubbleSort(nums); System.out.println("After bubble sort completes, nums = " + Arrays.toString(nums)); int[] nums1 = { 4, 1, 3, 1, 5, 2 }; bubbleSortWithFlag(nums1); System.out.println("After bubble sort completes, nums1 = " + Arrays.toString(nums1)); } } ================================================ FILE: en/codes/java/chapter_sorting/bucket_sort.java ================================================ /** * File: bucket_sort.java * Created Time: 2023-03-17 * Author: krahets (krahets@163.com) */ package chapter_sorting; import java.util.*; public class bucket_sort { /* Bucket sort */ static void bucketSort(float[] nums) { // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket int k = nums.length / 2; List> buckets = new ArrayList<>(); for (int i = 0; i < k; i++) { buckets.add(new ArrayList<>()); } // 1. Distribute array elements into various buckets for (float num : nums) { // Input data range is [0, 1), use num * k to map to index range [0, k-1] int i = (int) (num * k); // Add num to bucket i buckets.get(i).add(num); } // 2. Sort each bucket for (List bucket : buckets) { // Use built-in sorting function, can also replace with other sorting algorithms Collections.sort(bucket); } // 3. Traverse buckets to merge results int i = 0; for (List bucket : buckets) { for (float num : bucket) { nums[i++] = num; } } } public static void main(String[] args) { // Assume input data is floating point, interval [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("After bucket sort completes, nums = " + Arrays.toString(nums)); } } ================================================ FILE: en/codes/java/chapter_sorting/counting_sort.java ================================================ /** * File: counting_sort.java * Created Time: 2023-03-17 * Author: krahets (krahets@163.com) */ package chapter_sorting; import java.util.*; public class counting_sort { /* Counting sort */ // Simple implementation, cannot be used for sorting objects static void countingSortNaive(int[] nums) { // 1. Count the maximum element m in the array int m = 0; for (int num : nums) { m = Math.max(m, num); } // 2. Count the occurrence of each number // counter[num] represents the occurrence of num int[] counter = new int[m + 1]; for (int num : nums) { counter[num]++; } // 3. Traverse counter, filling each element back into the original array nums int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* Counting sort */ // Complete implementation, can sort objects and is a stable sort static void countingSort(int[] nums) { // 1. Count the maximum element m in the array int m = 0; for (int num : nums) { m = Math.max(m, num); } // 2. Count the occurrence of each number // counter[num] represents the occurrence of num int[] counter = new int[m + 1]; for (int num : nums) { counter[num]++; } // 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" // counter[num]-1 is the last index where num appears in res for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. Traverse nums in reverse order, placing each element into the result array res // Initialize the array res to record results 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; // Place num at the corresponding index counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num } // Use result array res to overwrite the original array 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("After counting sort (cannot sort objects) completes, nums = " + Arrays.toString(nums)); int[] nums1 = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; countingSort(nums1); System.out.println("After counting sort completes, nums1 = " + Arrays.toString(nums1)); } } ================================================ FILE: en/codes/java/chapter_sorting/heap_sort.java ================================================ /** * 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 { /* Heap length is n, start heapifying node i, from top to bottom */ public static void siftDown(int[] nums, int n, int i) { while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break 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; // Swap two nodes if (ma == i) break; // Swap two nodes int temp = nums[i]; nums[i] = nums[ma]; nums[ma] = temp; // Loop downwards heapification i = ma; } } /* Heap sort */ public static void heapSort(int[] nums) { // Build heap operation: heapify all nodes except leaves for (int i = nums.length / 2 - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // Extract the largest element from the heap and repeat for n-1 rounds for (int i = nums.length - 1; i > 0; i--) { // Delete node int tmp = nums[0]; nums[0] = nums[i]; nums[i] = tmp; // Start heapifying the root node, from top to bottom siftDown(nums, i, 0); } } public static void main(String[] args) { int[] nums = { 4, 1, 3, 1, 5, 2 }; heapSort(nums); System.out.println("After heap sort completes, nums = " + Arrays.toString(nums)); } } ================================================ FILE: en/codes/java/chapter_sorting/insertion_sort.java ================================================ /** * File: insertion_sort.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_sorting; import java.util.*; public class insertion_sort { /* Insertion sort */ static void insertionSort(int[] nums) { // Outer loop: sorted interval is [0, i-1] for (int i = 1; i < nums.length; i++) { int base = nums[i], j = i - 1; // Inner loop: insert base into the correct position within the sorted interval [0, i-1] while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // Move nums[j] to the right by one position j--; } nums[j + 1] = base; // Assign base to the correct position } } public static void main(String[] args) { int[] nums = { 4, 1, 3, 1, 5, 2 }; insertionSort(nums); System.out.println("After insertion sort completes, nums = " + Arrays.toString(nums)); } } ================================================ FILE: en/codes/java/chapter_sorting/merge_sort.java ================================================ /** * File: merge_sort.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_sorting; import java.util.*; public class merge_sort { /* Merge left subarray and right subarray */ static void merge(int[] nums, int left, int mid, int right) { // Left subarray interval is [left, mid], right subarray interval is [mid+1, right] // Create a temporary array tmp to store the merged results int[] tmp = new int[right - left + 1]; // Initialize the start indices of the left and right subarrays int i = left, j = mid + 1, k = 0; // While both subarrays still have elements, compare and copy the smaller element into the temporary array while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } // Copy the remaining elements of the left and right subarrays into the temporary array while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* Merge sort */ static void mergeSort(int[] nums, int left, int right) { // Termination condition if (left >= right) return; // Terminate recursion when subarray length is 1 // Divide and conquer stage int mid = left + (right - left) / 2; // Calculate midpoint mergeSort(nums, left, mid); // Recursively process the left subarray mergeSort(nums, mid + 1, right); // Recursively process the right subarray // Merge stage merge(nums, left, mid, right); } public static void main(String[] args) { /* Merge sort */ int[] nums = { 7, 3, 2, 6, 0, 1, 5, 4 }; mergeSort(nums, 0, nums.length - 1); System.out.println("After merge sort completes, nums = " + Arrays.toString(nums)); } } ================================================ FILE: en/codes/java/chapter_sorting/quick_sort.java ================================================ /** * File: quick_sort.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_sorting; import java.util.*; /* Quick sort class */ class QuickSort { /* Swap elements */ static void swap(int[] nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Sentinel partition */ static int partition(int[] nums, int left, int right) { // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot swap(nums, i, j); // Swap these two elements } swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort */ public static void quickSort(int[] nums, int left, int right) { // Terminate recursion when subarray length is 1 if (left >= right) return; // Sentinel partition int pivot = partition(nums, left, right); // Recursively process the left subarray and right subarray quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } /* Quick sort class (median pivot optimization) */ class QuickSortMedian { /* Swap elements */ static void swap(int[] nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Select the median of three candidate elements */ 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 is between l and r if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l is between m and r return right; } /* Sentinel partition (median of three) */ static int partition(int[] nums, int left, int right) { // Select the median of three candidate elements int med = medianThree(nums, left, (left + right) / 2, right); // Swap the median to the array's leftmost position swap(nums, left, med); // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot swap(nums, i, j); // Swap these two elements } swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort */ public static void quickSort(int[] nums, int left, int right) { // Terminate recursion when subarray length is 1 if (left >= right) return; // Sentinel partition int pivot = partition(nums, left, right); // Recursively process the left subarray and right subarray quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } /* Quick sort class (recursion depth optimization) */ class QuickSortTailCall { /* Swap elements */ static void swap(int[] nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Sentinel partition */ static int partition(int[] nums, int left, int right) { // Use nums[left] as the pivot int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot swap(nums, i, j); // Swap these two elements } swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort (recursion depth optimization) */ public static void quickSort(int[] nums, int left, int right) { // Terminate when subarray length is 1 while (left < right) { // Sentinel partition operation int pivot = partition(nums, left, right); // Perform quick sort on the shorter of the two subarrays if (pivot - left < right - pivot) { quickSort(nums, left, pivot - 1); // Recursively sort the left subarray left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right] } else { quickSort(nums, pivot + 1, right); // Recursively sort the right subarray right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1] } } } } public class quick_sort { public static void main(String[] args) { /* Quick sort */ int[] nums = { 2, 4, 1, 0, 3, 5 }; QuickSort.quickSort(nums, 0, nums.length - 1); System.out.println("After quick sort completes, nums = " + Arrays.toString(nums)); /* Quick sort (recursion depth optimization) */ int[] nums1 = { 2, 4, 1, 0, 3, 5 }; QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); System.out.println("After quick sort (median pivot optimization) completes, nums1 = " + Arrays.toString(nums1)); /* Quick sort (recursion depth optimization) */ int[] nums2 = { 2, 4, 1, 0, 3, 5 }; QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); System.out.println("After quick sort (recursion depth optimization) completes, nums2 = " + Arrays.toString(nums2)); } } ================================================ FILE: en/codes/java/chapter_sorting/radix_sort.java ================================================ /** * File: radix_sort.java * Created Time: 2023-01-17 * Author: krahets (krahets@163.com) */ package chapter_sorting; import java.util.*; public class radix_sort { /* Get the k-th digit of element num, where exp = 10^(k-1) */ static int digit(int num, int exp) { // Passing exp instead of k can avoid repeated expensive exponentiation here return (num / exp) % 10; } /* Counting sort (based on nums k-th digit) */ static void countingSortDigit(int[] nums, int exp) { // Decimal digit range is 0~9, therefore need a bucket array of length 10 int[] counter = new int[10]; int n = nums.length; // Count the occurrence of digits 0~9 for (int i = 0; i < n; i++) { int d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d counter[d]++; // Count the occurrence of digit d } // Calculate prefix sum, converting "occurrence count" into "array index" for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // Traverse in reverse, based on bucket statistics, place each element into 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; // Get the index j for d in the array res[j] = nums[i]; // Place the current element at index j counter[d]--; // Decrease the count of d by 1 } // Use result to overwrite the original array nums for (int i = 0; i < n; i++) nums[i] = res[i]; } /* Radix sort */ static void radixSort(int[] nums) { // Get the maximum element of the array, used to determine the maximum number of digits int m = Integer.MIN_VALUE; for (int num : nums) if (num > m) m = num; // Traverse from the lowest to the highest digit for (int exp = 1; exp <= m; exp *= 10) { // Perform counting sort on the k-th digit of array elements // k = 1 -> exp = 1 // k = 2 -> exp = 10 // i.e., exp = 10^(k-1) countingSortDigit(nums, exp); } } public static void main(String[] args) { // Radix sort int[] nums = { 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 }; radixSort(nums); System.out.println("After radix sort completes, nums = " + Arrays.toString(nums)); } } ================================================ FILE: en/codes/java/chapter_sorting/selection_sort.java ================================================ /** * 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 { /* Selection sort */ public static void selectionSort(int[] nums) { int n = nums.length; // Outer loop: unsorted interval is [i, n-1] for (int i = 0; i < n - 1; i++) { // Inner loop: find the smallest element within the unsorted interval int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // Record the index of the smallest element } // Swap the smallest element with the first element of the unsorted interval 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("After selection sort completes, nums = " + Arrays.toString(nums)); } } ================================================ FILE: en/codes/java/chapter_stack_and_queue/array_deque.java ================================================ /** * 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.*; /* Double-ended queue based on circular array implementation */ class ArrayDeque { private int[] nums; // Array for storing double-ended queue elements private int front; // Front pointer, points to the front of the queue element private int queSize; // Double-ended queue length /* Constructor */ public ArrayDeque(int capacity) { this.nums = new int[capacity]; front = queSize = 0; } /* Get the capacity of the double-ended queue */ public int capacity() { return nums.length; } /* Get the length of the double-ended queue */ public int size() { return queSize; } /* Check if the double-ended queue is empty */ public boolean isEmpty() { return queSize == 0; } /* Calculate circular array index */ private int index(int i) { // Use modulo operation to wrap the array head and tail together // When i passes the tail of the array, return to the head // When i passes the head of the array, return to the tail return (i + capacity()) % capacity(); } /* Front of the queue enqueue */ public void pushFirst(int num) { if (queSize == capacity()) { System.out.println("Double-ended queue is full"); return; } // Use modulo operation to wrap front around to the tail after passing the head of the array // Add num to the front of the queue front = index(front - 1); // Add num to front of queue nums[front] = num; queSize++; } /* Rear of the queue enqueue */ public void pushLast(int num) { if (queSize == capacity()) { System.out.println("Double-ended queue is full"); return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array int rear = index(front + queSize); // Front pointer moves one position backward nums[rear] = num; queSize++; } /* Rear of the queue dequeue */ public int popFirst() { int num = peekFirst(); // Move front pointer backward by one position front = index(front + 1); queSize--; return num; } /* Access rear of the queue element */ public int popLast() { int num = peekLast(); queSize--; return num; } /* Return list for printing */ public int peekFirst() { if (isEmpty()) throw new IndexOutOfBoundsException(); return nums[front]; } /* Driver Code */ public int peekLast() { if (isEmpty()) throw new IndexOutOfBoundsException(); // Initialize double-ended queue int last = index(front + queSize - 1); return nums[last]; } /* Return array for printing */ public int[] toArray() { // Elements enqueue 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) { /* Get the length of the double-ended queue */ ArrayDeque deque = new ArrayDeque(10); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); System.out.println("Double-ended queue deque = " + Arrays.toString(deque.toArray())); /* Update element */ int peekFirst = deque.peekFirst(); System.out.println("Front element peekFirst = " + peekFirst); int peekLast = deque.peekLast(); System.out.println("Rear element peekLast = " + peekLast); /* Elements enqueue */ deque.pushLast(4); System.out.println("After element 4 enqueues at rear, deque = " + Arrays.toString(deque.toArray())); deque.pushFirst(1); System.out.println("After element 1 enqueues at front, deque = " + Arrays.toString(deque.toArray())); /* Element dequeue */ int popLast = deque.popLast(); System.out.println("Rear dequeue element = " + popLast + ", after rear dequeue, deque = " + Arrays.toString(deque.toArray())); int popFirst = deque.popFirst(); System.out.println("Front dequeue element = " + popFirst + ", after front dequeue, deque = " + Arrays.toString(deque.toArray())); /* Get the length of the double-ended queue */ int size = deque.size(); System.out.println("Double-ended queue length size = " + size); /* Check if the double-ended queue is empty */ boolean isEmpty = deque.isEmpty(); System.out.println("Double-ended queue is empty = " + isEmpty); } } ================================================ FILE: en/codes/java/chapter_stack_and_queue/array_queue.java ================================================ /** * File: array_queue.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_stack_and_queue; import java.util.*; /* Queue based on circular array implementation */ class ArrayQueue { private int[] nums; // Array for storing queue elements private int front; // Front pointer, points to the front of the queue element private int queSize; // Queue length public ArrayQueue(int capacity) { nums = new int[capacity]; front = queSize = 0; } /* Get the capacity of the queue */ public int capacity() { return nums.length; } /* Get the length of the queue */ public int size() { return queSize; } /* Check if the queue is empty */ public boolean isEmpty() { return queSize == 0; } /* Enqueue */ public void push(int num) { if (queSize == capacity()) { System.out.println("Queue is full"); return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array // Add num to the rear of the queue int rear = (front + queSize) % capacity(); // Front pointer moves one position backward nums[rear] = num; queSize++; } /* Dequeue */ public int pop() { int num = peek(); // Move front pointer backward by one position, if it passes the tail, return to array head front = (front + 1) % capacity(); queSize--; return num; } /* Return list for printing */ public int peek() { if (isEmpty()) throw new IndexOutOfBoundsException(); return nums[front]; } /* Return array */ public int[] toArray() { // Elements enqueue 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) { /* Access front of the queue element */ int capacity = 10; ArrayQueue queue = new ArrayQueue(capacity); /* Elements enqueue */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); System.out.println("Queue queue = " + Arrays.toString(queue.toArray())); /* Return list for printing */ int peek = queue.peek(); System.out.println("Front element peek = " + peek); /* Element dequeue */ int pop = queue.pop(); System.out.println("Dequeue element pop = " + pop + ", after dequeue, queue = " + Arrays.toString(queue.toArray())); /* Get the length of the queue */ int size = queue.size(); System.out.println("Queue length size = " + size); /* Check if the queue is empty */ boolean isEmpty = queue.isEmpty(); System.out.println("Queue is empty = " + isEmpty); /* Test circular array */ for (int i = 0; i < 10; i++) { queue.push(i); queue.pop(); System.out.println("After round " + i + " enqueue + dequeue, queue = " + Arrays.toString(queue.toArray())); } } } ================================================ FILE: en/codes/java/chapter_stack_and_queue/array_stack.java ================================================ /** * File: array_stack.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_stack_and_queue; import java.util.*; /* Stack based on array implementation */ class ArrayStack { private ArrayList stack; public ArrayStack() { // Initialize list (dynamic array) stack = new ArrayList<>(); } /* Get the length of the stack */ public int size() { return stack.size(); } /* Check if the stack is empty */ public boolean isEmpty() { return size() == 0; } /* Push */ public void push(int num) { stack.add(num); } /* Pop */ public int pop() { if (isEmpty()) throw new IndexOutOfBoundsException(); return stack.remove(size() - 1); } /* Return list for printing */ public int peek() { if (isEmpty()) throw new IndexOutOfBoundsException(); return stack.get(size() - 1); } /* Convert List to Array and return */ public Object[] toArray() { return stack.toArray(); } } public class array_stack { public static void main(String[] args) { /* Access top of the stack element */ ArrayStack stack = new ArrayStack(); /* Elements push onto stack */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); System.out.println("Stack stack = " + Arrays.toString(stack.toArray())); /* Return list for printing */ int peek = stack.peek(); System.out.println("Stack top element peek = " + peek); /* Element pop from stack */ int pop = stack.pop(); System.out.println("Pop element pop = " + pop + ", after pop, stack = " + Arrays.toString(stack.toArray())); /* Get the length of the stack */ int size = stack.size(); System.out.println("Stack length size = " + size); /* Check if empty */ boolean isEmpty = stack.isEmpty(); System.out.println("Stack is empty = " + isEmpty); } } ================================================ FILE: en/codes/java/chapter_stack_and_queue/deque.java ================================================ /** * 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) { /* Get the length of the double-ended queue */ Deque deque = new LinkedList<>(); deque.offerLast(3); deque.offerLast(2); deque.offerLast(5); System.out.println("Double-ended queue deque = " + deque); /* Update element */ int peekFirst = deque.peekFirst(); System.out.println("Front element peekFirst = " + peekFirst); int peekLast = deque.peekLast(); System.out.println("Rear element peekLast = " + peekLast); /* Elements enqueue */ deque.offerLast(4); System.out.println("After element 4 enqueues at rear, deque = " + deque); deque.offerFirst(1); System.out.println("After element 1 enqueues at front, deque = " + deque); /* Element dequeue */ int popLast = deque.pollLast(); System.out.println("Rear dequeue element = " + popLast + ", after rear dequeue, deque = " + deque); int popFirst = deque.pollFirst(); System.out.println("Front dequeue element = " + popFirst + ", after front dequeue, deque = " + deque); /* Get the length of the double-ended queue */ int size = deque.size(); System.out.println("Double-ended queue length size = " + size); /* Check if the double-ended queue is empty */ boolean isEmpty = deque.isEmpty(); System.out.println("Double-ended queue is empty = " + isEmpty); } } ================================================ FILE: en/codes/java/chapter_stack_and_queue/linkedlist_deque.java ================================================ /** * File: linkedlist_deque.java * Created Time: 2023-01-20 * Author: krahets (krahets@163.com) */ package chapter_stack_and_queue; import java.util.*; /* Doubly linked list node */ class ListNode { int val; // Node value ListNode next; // Successor node reference ListNode prev; // Predecessor node reference ListNode(int val) { this.val = val; prev = next = null; } } /* Double-ended queue based on doubly linked list implementation */ class LinkedListDeque { private ListNode front, rear; // Head node front, tail node rear private int queSize = 0; // Length of the double-ended queue public LinkedListDeque() { front = rear = null; } /* Get the length of the double-ended queue */ public int size() { return queSize; } /* Check if the double-ended queue is empty */ public boolean isEmpty() { return size() == 0; } /* Enqueue operation */ private void push(int num, boolean isFront) { ListNode node = new ListNode(num); // If the linked list is empty, make both front and rear point to node if (isEmpty()) front = rear = node; // Front of the queue enqueue operation else if (isFront) { // Add node to the head of the linked list front.prev = node; node.next = front; front = node; // Update head node // Rear of the queue enqueue operation } else { // Add node to the tail of the linked list rear.next = node; node.prev = rear; rear = node; // Update tail node } queSize++; // Update queue length } /* Front of the queue enqueue */ public void pushFirst(int num) { push(num, true); } /* Rear of the queue enqueue */ public void pushLast(int num) { push(num, false); } /* Dequeue operation */ private int pop(boolean isFront) { if (isEmpty()) throw new IndexOutOfBoundsException(); int val; // Temporarily store head node value if (isFront) { val = front.val; // Delete head node // Delete head node ListNode fNext = front.next; if (fNext != null) { fNext.prev = null; front.next = null; } front = fNext; // Update head node // Temporarily store tail node value } else { val = rear.val; // Delete tail node // Update tail node ListNode rPrev = rear.prev; if (rPrev != null) { rPrev.next = null; rear.prev = null; } rear = rPrev; // Update tail node } queSize--; // Update queue length return val; } /* Rear of the queue dequeue */ public int popFirst() { return pop(true); } /* Access rear of the queue element */ public int popLast() { return pop(false); } /* Return list for printing */ public int peekFirst() { if (isEmpty()) throw new IndexOutOfBoundsException(); return front.val; } /* Driver Code */ public int peekLast() { if (isEmpty()) throw new IndexOutOfBoundsException(); return rear.val; } /* Return array for printing */ 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) { /* Get the length of the double-ended queue */ LinkedListDeque deque = new LinkedListDeque(); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); System.out.println("Double-ended queue deque = " + Arrays.toString(deque.toArray())); /* Update element */ int peekFirst = deque.peekFirst(); System.out.println("Front element peekFirst = " + peekFirst); int peekLast = deque.peekLast(); System.out.println("Rear element peekLast = " + peekLast); /* Elements enqueue */ deque.pushLast(4); System.out.println("After element 4 enqueues at rear, deque = " + Arrays.toString(deque.toArray())); deque.pushFirst(1); System.out.println("After element 1 enqueues at front, deque = " + Arrays.toString(deque.toArray())); /* Element dequeue */ int popLast = deque.popLast(); System.out.println("Rear dequeue element = " + popLast + ", after rear dequeue, deque = " + Arrays.toString(deque.toArray())); int popFirst = deque.popFirst(); System.out.println("Front dequeue element = " + popFirst + ", after front dequeue, deque = " + Arrays.toString(deque.toArray())); /* Get the length of the double-ended queue */ int size = deque.size(); System.out.println("Double-ended queue length size = " + size); /* Check if the double-ended queue is empty */ boolean isEmpty = deque.isEmpty(); System.out.println("Double-ended queue is empty = " + isEmpty); } } ================================================ FILE: en/codes/java/chapter_stack_and_queue/linkedlist_queue.java ================================================ /** * File: linkedlist_queue.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_stack_and_queue; import java.util.*; /* Queue based on linked list implementation */ class LinkedListQueue { private ListNode front, rear; // Head node front, tail node rear private int queSize = 0; public LinkedListQueue() { front = null; rear = null; } /* Get the length of the queue */ public int size() { return queSize; } /* Check if the queue is empty */ public boolean isEmpty() { return size() == 0; } /* Enqueue */ public void push(int num) { // Add num after the tail node ListNode node = new ListNode(num); // If the queue is empty, make both front and rear point to the node if (front == null) { front = node; rear = node; // If the queue is not empty, add the node after the tail node } else { rear.next = node; rear = node; } queSize++; } /* Dequeue */ public int pop() { int num = peek(); // Delete head node front = front.next; queSize--; return num; } /* Return list for printing */ public int peek() { if (isEmpty()) throw new IndexOutOfBoundsException(); return front.val; } /* Convert linked list to Array and return */ 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) { /* Access front of the queue element */ LinkedListQueue queue = new LinkedListQueue(); /* Elements enqueue */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); System.out.println("Queue queue = " + Arrays.toString(queue.toArray())); /* Return list for printing */ int peek = queue.peek(); System.out.println("Front element peek = " + peek); /* Element dequeue */ int pop = queue.pop(); System.out.println("Dequeue element pop = " + pop + ", after dequeue, queue = " + Arrays.toString(queue.toArray())); /* Get the length of the queue */ int size = queue.size(); System.out.println("Queue length size = " + size); /* Check if the queue is empty */ boolean isEmpty = queue.isEmpty(); System.out.println("Queue is empty = " + isEmpty); } } ================================================ FILE: en/codes/java/chapter_stack_and_queue/linkedlist_stack.java ================================================ /** * File: linkedlist_stack.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_stack_and_queue; import java.util.*; import utils.*; /* Stack based on linked list implementation */ class LinkedListStack { private ListNode stackPeek; // Use head node as stack top private int stkSize = 0; // Stack length public LinkedListStack() { stackPeek = null; } /* Get the length of the stack */ public int size() { return stkSize; } /* Check if the stack is empty */ public boolean isEmpty() { return size() == 0; } /* Push */ public void push(int num) { ListNode node = new ListNode(num); node.next = stackPeek; stackPeek = node; stkSize++; } /* Pop */ public int pop() { int num = peek(); stackPeek = stackPeek.next; stkSize--; return num; } /* Return list for printing */ public int peek() { if (isEmpty()) throw new IndexOutOfBoundsException(); return stackPeek.val; } /* Convert List to Array and return */ 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) { /* Access top of the stack element */ LinkedListStack stack = new LinkedListStack(); /* Elements push onto stack */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); System.out.println("Stack stack = " + Arrays.toString(stack.toArray())); /* Return list for printing */ int peek = stack.peek(); System.out.println("Stack top element peek = " + peek); /* Element pop from stack */ int pop = stack.pop(); System.out.println("Pop element pop = " + pop + ", after pop, stack = " + Arrays.toString(stack.toArray())); /* Get the length of the stack */ int size = stack.size(); System.out.println("Stack length size = " + size); /* Check if empty */ boolean isEmpty = stack.isEmpty(); System.out.println("Stack is empty = " + isEmpty); } } ================================================ FILE: en/codes/java/chapter_stack_and_queue/queue.java ================================================ /** * 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) { /* Access front of the queue element */ Queue queue = new LinkedList<>(); /* Elements enqueue */ queue.offer(1); queue.offer(3); queue.offer(2); queue.offer(5); queue.offer(4); System.out.println("Queue queue = " + queue); /* Return list for printing */ int peek = queue.peek(); System.out.println("Front element peek = " + peek); /* Element dequeue */ int pop = queue.poll(); System.out.println("Dequeue element pop = " + pop + ", after dequeue, queue = " + queue); /* Get the length of the queue */ int size = queue.size(); System.out.println("Queue length size = " + size); /* Check if the queue is empty */ boolean isEmpty = queue.isEmpty(); System.out.println("Queue is empty = " + isEmpty); } } ================================================ FILE: en/codes/java/chapter_stack_and_queue/stack.java ================================================ /** * 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) { /* Access top of the stack element */ Stack stack = new Stack<>(); /* Elements push onto stack */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); System.out.println("Stack stack = " + stack); /* Return list for printing */ int peek = stack.peek(); System.out.println("Stack top element peek = " + peek); /* Element pop from stack */ int pop = stack.pop(); System.out.println("Pop element pop = " + pop + ", after pop, stack = " + stack); /* Get the length of the stack */ int size = stack.size(); System.out.println("Stack length size = " + size); /* Check if empty */ boolean isEmpty = stack.isEmpty(); System.out.println("Stack is empty = " + isEmpty); } } ================================================ FILE: en/codes/java/chapter_tree/array_binary_tree.java ================================================ /** * File: array_binary_tree.java * Created Time: 2023-07-19 * Author: krahets (krahets@163.com) */ package chapter_tree; import utils.*; import java.util.*; /* Binary tree class represented by array */ class ArrayBinaryTree { private List tree; /* Constructor */ public ArrayBinaryTree(List arr) { tree = new ArrayList<>(arr); } /* List capacity */ public int size() { return tree.size(); } /* Get value of node at index i */ public Integer val(int i) { // If index out of bounds, return null to represent empty position if (i < 0 || i >= size()) return null; return tree.get(i); } /* Get index of left child node of node at index i */ public Integer left(int i) { return 2 * i + 1; } /* Get index of right child node of node at index i */ public Integer right(int i) { return 2 * i + 2; } /* Get index of parent node of node at index i */ public Integer parent(int i) { return (i - 1) / 2; } /* Level-order traversal */ public List levelOrder() { List res = new ArrayList<>(); // Traverse array directly for (int i = 0; i < size(); i++) { if (val(i) != null) res.add(val(i)); } return res; } /* Depth-first traversal */ private void dfs(Integer i, String order, List res) { // If empty position, return if (val(i) == null) return; // Preorder traversal if ("pre".equals(order)) res.add(val(i)); dfs(left(i), order, res); // Inorder traversal if ("in".equals(order)) res.add(val(i)); dfs(right(i), order, res); // Postorder traversal if ("post".equals(order)) res.add(val(i)); } /* Preorder traversal */ public List preOrder() { List res = new ArrayList<>(); dfs(0, "pre", res); return res; } /* Inorder traversal */ public List inOrder() { List res = new ArrayList<>(); dfs(0, "in", res); return res; } /* Postorder traversal */ public List postOrder() { List res = new ArrayList<>(); dfs(0, "post", res); return res; } } public class array_binary_tree { public static void main(String[] args) { // Initialize binary tree // Here we use a function to generate a binary tree directly from an array 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("\nInitialize binary tree\n"); System.out.println("Array representation of binary tree:"); System.out.println(arr); System.out.println("Linked list representation of binary tree:"); PrintUtil.printTree(root); // Binary tree class represented by array ArrayBinaryTree abt = new ArrayBinaryTree(arr); // Access node int i = 1; Integer l = abt.left(i); Integer r = abt.right(i); Integer p = abt.parent(i); System.out.println("\nCurrent node index is " + i + ", value is " + abt.val(i)); System.out.println("Its left child node index is " + l + ", value is " + (l == null ? "null" : abt.val(l))); System.out.println("Its right child node index is " + r + ", value is " + (r == null ? "null" : abt.val(r))); System.out.println("Its parent node index is " + p + ", value is " + (p == null ? "null" : abt.val(p))); // Traverse tree List res = abt.levelOrder(); System.out.println("\nLevel-order traversal is:" + res); res = abt.preOrder(); System.out.println("Preorder traversal is:" + res); res = abt.inOrder(); System.out.println("Inorder traversal is:" + res); res = abt.postOrder(); System.out.println("Postorder traversal is:" + res); } } ================================================ FILE: en/codes/java/chapter_tree/avl_tree.java ================================================ /** * File: avl_tree.java * Created Time: 2022-12-10 * Author: krahets (krahets@163.com) */ package chapter_tree; import utils.*; /* AVL tree */ class AVLTree { TreeNode root; // Root node /* Get node height */ public int height(TreeNode node) { // Empty node height is -1, leaf node height is 0 return node == null ? -1 : node.height; } /* Update node height */ private void updateHeight(TreeNode node) { // Node height equals the height of the tallest subtree + 1 node.height = Math.max(height(node.left), height(node.right)) + 1; } /* Get balance factor */ public int balanceFactor(TreeNode node) { // Empty node balance factor is 0 if (node == null) return 0; // Node balance factor = left subtree height - right subtree height return height(node.left) - height(node.right); } /* Right rotation operation */ private TreeNode rightRotate(TreeNode node) { TreeNode child = node.left; TreeNode grandChild = child.right; // Using child as pivot, rotate node to the right child.right = node; node.left = grandChild; // Update node height updateHeight(node); updateHeight(child); // Return root node of subtree after rotation return child; } /* Left rotation operation */ private TreeNode leftRotate(TreeNode node) { TreeNode child = node.right; TreeNode grandChild = child.left; // Using child as pivot, rotate node to the left child.left = node; node.right = grandChild; // Update node height updateHeight(node); updateHeight(child); // Return root node of subtree after rotation return child; } /* Perform rotation operation to restore balance to this subtree */ private TreeNode rotate(TreeNode node) { // Get balance factor of node int balanceFactor = balanceFactor(node); // Left-leaning tree if (balanceFactor > 1) { if (balanceFactor(node.left) >= 0) { // Right rotation return rightRotate(node); } else { // First left rotation then right rotation node.left = leftRotate(node.left); return rightRotate(node); } } // Right-leaning tree if (balanceFactor < -1) { if (balanceFactor(node.right) <= 0) { // Left rotation return leftRotate(node); } else { // First right rotation then left rotation node.right = rightRotate(node.right); return leftRotate(node); } } // Balanced tree, no rotation needed, return directly return node; } /* Insert node */ public void insert(int val) { root = insertHelper(root, val); } /* Recursively insert node (helper method) */ private TreeNode insertHelper(TreeNode node, int val) { if (node == null) return new TreeNode(val); /* 1. Find insertion position and insert node */ if (val < node.val) node.left = insertHelper(node.left, val); else if (val > node.val) node.right = insertHelper(node.right, val); else return node; // Duplicate node not inserted, return directly updateHeight(node); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = rotate(node); // Return root node of subtree return node; } /* Remove node */ public void remove(int val) { root = removeHelper(root, val); } /* Recursively delete node (helper method) */ private TreeNode removeHelper(TreeNode node, int val) { if (node == null) return null; /* 1. Find node and delete */ 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; // Number of child nodes = 0, delete node directly and return if (child == null) return null; // Number of child nodes = 1, delete node directly else node = child; } else { // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it TreeNode temp = node.right; while (temp.left != null) { temp = temp.left; } node.right = removeHelper(node.right, temp.val); node.val = temp.val; } } updateHeight(node); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = rotate(node); // Return root node of subtree return node; } /* Search node */ public TreeNode search(int val) { TreeNode cur = root; // Loop search, exit after passing leaf node while (cur != null) { // Target node is in cur's right subtree if (cur.val < val) cur = cur.right; // Target node is in cur's left subtree else if (cur.val > val) cur = cur.left; // Found target node, exit loop else break; } // Return target node return cur; } } public class avl_tree { static void testInsert(AVLTree tree, int val) { tree.insert(val); System.out.println("\nAfter inserting node " + val + ", AVL tree is"); PrintUtil.printTree(tree.root); } static void testRemove(AVLTree tree, int val) { tree.remove(val); System.out.println("\nAfter removing node " + val + ", AVL tree is"); PrintUtil.printTree(tree.root); } public static void main(String[] args) { /* Please pay attention to how the AVL tree maintains balance after inserting nodes */ AVLTree avlTree = new AVLTree(); /* Insert node */ // Delete nodes 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); /* Please pay attention to how the AVL tree maintains balance after deleting nodes */ testInsert(avlTree, 7); /* Remove node */ // Delete node with degree 1 testRemove(avlTree, 8); // Delete node with degree 2 testRemove(avlTree, 5); // Remove node with degree 1 testRemove(avlTree, 4); // Remove node with degree 2 /* Search node */ TreeNode node = avlTree.search(7); System.out.println("\nFound node object is " + node + ", node value = " + node.val); } } ================================================ FILE: en/codes/java/chapter_tree/binary_search_tree.java ================================================ /** * File: binary_search_tree.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_tree; import utils.*; /* Binary search tree */ class BinarySearchTree { private TreeNode root; /* Constructor */ public BinarySearchTree() { // Initialize empty tree root = null; } /* Get binary tree root node */ public TreeNode getRoot() { return root; } /* Search node */ public TreeNode search(int num) { TreeNode cur = root; // Loop search, exit after passing leaf node while (cur != null) { // Target node is in cur's right subtree if (cur.val < num) cur = cur.right; // Target node is in cur's left subtree else if (cur.val > num) cur = cur.left; // Found target node, exit loop else break; } // Return target node return cur; } /* Insert node */ public void insert(int num) { // If tree is empty, initialize root node if (root == null) { root = new TreeNode(num); return; } TreeNode cur = root, pre = null; // Loop search, exit after passing leaf node while (cur != null) { // Found duplicate node, return directly if (cur.val == num) return; pre = cur; // Insertion position is in cur's right subtree if (cur.val < num) cur = cur.right; // Insertion position is in cur's left subtree else cur = cur.left; } // Insert node TreeNode node = new TreeNode(num); if (pre.val < num) pre.right = node; else pre.left = node; } /* Remove node */ public void remove(int num) { // If tree is empty, return directly if (root == null) return; TreeNode cur = root, pre = null; // Loop search, exit after passing leaf node while (cur != null) { // Found node to delete, exit loop if (cur.val == num) break; pre = cur; // Node to delete is in cur's right subtree if (cur.val < num) cur = cur.right; // Node to delete is in cur's left subtree else cur = cur.left; } // If no node to delete, return directly if (cur == null) return; // Number of child nodes = 0 or 1 if (cur.left == null || cur.right == null) { // When number of child nodes = 0 / 1, child = null / that child node TreeNode child = cur.left != null ? cur.left : cur.right; // Delete node cur if (cur != root) { if (pre.left == cur) pre.left = child; else pre.right = child; } else { // If deleted node is root node, reassign root node root = child; } } // Number of child nodes = 2 else { // Get next node of cur in inorder traversal TreeNode tmp = cur.right; while (tmp.left != null) { tmp = tmp.left; } // Recursively delete node tmp remove(tmp.val); // Replace cur with tmp cur.val = tmp.val; } } } public class binary_search_tree { public static void main(String[] args) { /* Initialize binary search tree */ BinarySearchTree bst = new BinarySearchTree(); // Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree 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("\nInitialized binary tree is\n"); PrintUtil.printTree(bst.getRoot()); /* Search node */ TreeNode node = bst.search(7); System.out.println("\nFound node object is " + node + ", node value = " + node.val); /* Insert node */ bst.insert(16); System.out.println("\nAfter inserting node 16, binary tree is\n"); PrintUtil.printTree(bst.getRoot()); /* Remove node */ bst.remove(1); System.out.println("\nAfter removing node 1, binary tree is\n"); PrintUtil.printTree(bst.getRoot()); bst.remove(2); System.out.println("\nAfter removing node 2, binary tree is\n"); PrintUtil.printTree(bst.getRoot()); bst.remove(4); System.out.println("\nAfter removing node 4, binary tree is\n"); PrintUtil.printTree(bst.getRoot()); } } ================================================ FILE: en/codes/java/chapter_tree/binary_tree.java ================================================ /** * 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) { /* Initialize binary tree */ // Initialize nodes 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); // Build references (pointers) between nodes n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; System.out.println("\nInitialize binary tree\n"); PrintUtil.printTree(n1); /* Insert node P between n1 -> n2 */ TreeNode P = new TreeNode(0); // Delete node n1.left = P; P.left = n2; System.out.println("\nAfter inserting node P\n"); PrintUtil.printTree(n1); // Remove node P n1.left = n2; System.out.println("\nAfter removing node P\n"); PrintUtil.printTree(n1); } } ================================================ FILE: en/codes/java/chapter_tree/binary_tree_bfs.java ================================================ /** * 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 { /* Level-order traversal */ static List levelOrder(TreeNode root) { // Initialize queue, add root node Queue queue = new LinkedList<>(); queue.add(root); // Initialize a list to save the traversal sequence List list = new ArrayList<>(); while (!queue.isEmpty()) { TreeNode node = queue.poll(); // Dequeue list.add(node.val); // Save node value if (node.left != null) queue.offer(node.left); // Left child node enqueue if (node.right != null) queue.offer(node.right); // Right child node enqueue } return list; } public static void main(String[] args) { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); System.out.println("\nInitialize binary tree\n"); PrintUtil.printTree(root); /* Level-order traversal */ List list = levelOrder(root); System.out.println("\nLevel-order traversal node print sequence = " + list); } } ================================================ FILE: en/codes/java/chapter_tree/binary_tree_dfs.java ================================================ /** * 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 { // Initialize list for storing traversal sequence static ArrayList list = new ArrayList<>(); /* Preorder traversal */ static void preOrder(TreeNode root) { if (root == null) return; // Visit priority: root node -> left subtree -> right subtree list.add(root.val); preOrder(root.left); preOrder(root.right); } /* Inorder traversal */ static void inOrder(TreeNode root) { if (root == null) return; // Visit priority: left subtree -> root node -> right subtree inOrder(root.left); list.add(root.val); inOrder(root.right); } /* Postorder traversal */ static void postOrder(TreeNode root) { if (root == null) return; // Visit priority: left subtree -> right subtree -> root node postOrder(root.left); postOrder(root.right); list.add(root.val); } public static void main(String[] args) { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); System.out.println("\nInitialize binary tree\n"); PrintUtil.printTree(root); /* Preorder traversal */ list.clear(); preOrder(root); System.out.println("\nPreorder traversal node print sequence = " + list); /* Inorder traversal */ list.clear(); inOrder(root); System.out.println("\nInorder traversal node print sequence = " + list); /* Postorder traversal */ list.clear(); postOrder(root); System.out.println("\nPostorder traversal node print sequence = " + list); } } ================================================ FILE: en/codes/java/utils/ListNode.java ================================================ /** * File: ListNode.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package utils; /* Linked list node */ public class ListNode { public int val; public ListNode next; public ListNode(int x) { val = x; } /* Deserialize a list into a linked list */ 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; } } ================================================ FILE: en/codes/java/utils/PrintUtil.java ================================================ /** * 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 { /* Print matrix (Array) */ public static void printMatrix(T[][] matrix) { System.out.println("["); for (T[] row : matrix) { System.out.println(" " + row + ","); } System.out.println("]"); } /* Print matrix (List) */ public static void printMatrix(List> matrix) { System.out.println("["); for (List row : matrix) { System.out.println(" " + row + ","); } System.out.println("]"); } /* Print linked list */ 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)); } /* Print binary tree */ public static void printTree(TreeNode root) { printTree(root, null, false); } /** * 打印二叉树 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ public static void printTree(TreeNode root, Trunk prev, boolean isRight) { if (root == null) { return; } 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); } /* Print hash table */ public static void printHashMap(Map map) { for (Map.Entry kv : map.entrySet()) { System.out.println(kv.getKey() + " -> " + kv.getValue()); } } /* Print heap (priority queue) */ public static void printHeap(Queue queue) { List list = new ArrayList<>(queue); System.out.print("Heap array representation:"); System.out.println(list); System.out.println("Heap tree representation:"); TreeNode root = TreeNode.listToTree(list); printTree(root); } } ================================================ FILE: en/codes/java/utils/TreeNode.java ================================================ /** * File: TreeNode.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package utils; import java.util.*; /* Binary tree node class */ public class TreeNode { public int val; // Node value public int height; // Node height public TreeNode left; // Reference to left child node public TreeNode right; // Reference to right child node /* Constructor */ public TreeNode(int x) { val = x; } // For the serialization encoding rules, please refer to: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Array representation of binary tree: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // Linked list representation of binary tree: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* Deserialize a list into a binary tree: recursion */ 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; } /* Deserialize a list into a binary tree */ public static TreeNode listToTree(List arr) { return listToTreeDFS(arr, 0); } /* Serialize a binary tree into a list: recursion */ 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); } /* Serialize a binary tree into a list */ public static List treeToList(TreeNode root) { List res = new ArrayList<>(); treeToListDFS(root, 0, res); return res; } } ================================================ FILE: en/codes/java/utils/Vertex.java ================================================ /** * File: Vertex.java * Created Time: 2023-02-15 * Author: krahets (krahets@163.com) */ package utils; import java.util.*; /* Vertex class */ public class Vertex { public int val; public Vertex(int val) { this.val = val; } /* Input value list vals, return vertex list 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; } /* Input vertex list vets, return value list vals */ public static List vetsToVals(List vets) { List vals = new ArrayList<>(); for (Vertex vet : vets) { vals.add(vet.val); } return vals; } } ================================================ FILE: en/codes/javascript/.prettierrc ================================================ { "tabWidth": 4, "useTabs": false, "semi": true, "singleQuote": true } ================================================ FILE: en/codes/javascript/chapter_array_and_linkedlist/array.js ================================================ /** * File: array.js * Created Time: 2022-11-27 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Random access to element */ function randomAccess(nums) { // Randomly select a number in the interval [0, nums.length) const random_index = Math.floor(Math.random() * nums.length); // Retrieve and return the random element const random_num = nums[random_index]; return random_num; } /* Extend array length */ // Note: JavaScript's Array is dynamic array, can be directly expanded // For learning purposes, this function treats Array as fixed-length array function extend(nums, enlarge) { // Initialize an array with extended length const res = new Array(nums.length + enlarge).fill(0); // Copy all elements from the original array to the new array for (let i = 0; i < nums.length; i++) { res[i] = nums[i]; } // Return the extended new array return res; } /* Insert element num at index index in the array */ function insert(nums, num, index) { // Move all elements at and after index index backward by one position for (let i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Assign num to the element at index index nums[index] = num; } /* Remove the element at index index */ function remove(nums, index) { // Move all elements after index index forward by one position for (let i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* Traverse array */ function traverse(nums) { let count = 0; // Traverse array by index for (let i = 0; i < nums.length; i++) { count += nums[i]; } // Direct traversal of array elements for (const num of nums) { count += num; } } /* Find the specified element in the array */ function find(nums, target) { for (let i = 0; i < nums.length; i++) { if (nums[i] === target) return i; } return -1; } /* Driver Code */ /* Initialize array */ const arr = new Array(5).fill(0); console.log('Array arr =', arr); let nums = [1, 3, 2, 5, 4]; console.log('Array nums =', nums); /* Insert element */ let random_num = randomAccess(nums); console.log('Get random element in nums', random_num); /* Traverse array */ nums = extend(nums, 3); console.log('Extend array length to 8, get nums =', nums); /* Insert element */ insert(nums, 6, 3); console.log('Insert number 6 at index 3, get nums =', nums); /* Remove element */ remove(nums, 2); console.log('Remove element at index 2, get nums =', nums); /* Traverse array */ traverse(nums); /* Find element */ let index = find(nums, 3); console.log('Find element 3 in nums, get index =', index); ================================================ FILE: en/codes/javascript/chapter_array_and_linkedlist/linked_list.js ================================================ /** * File: linked_list.js * Created Time: 2022-12-12 * Author: IsChristina (christinaxia77@foxmail.com), Justin (xiefahit@gmail.com) */ const { printLinkedList } = require('../modules/PrintUtil'); const { ListNode } = require('../modules/ListNode'); /* Insert node P after node n0 in the linked list */ function insert(n0, P) { const n1 = n0.next; P.next = n1; n0.next = P; } /* Remove the first node after node n0 in the linked list */ function remove(n0) { if (!n0.next) return; // n0 -> P -> n1 const P = n0.next; const n1 = P.next; n0.next = n1; } /* Access the node at index index in the linked list */ function access(head, index) { for (let i = 0; i < index; i++) { if (!head) { return null; } head = head.next; } return head; } /* Find the first node with value target in the linked list */ function find(head, target) { let index = 0; while (head !== null) { if (head.val === target) { return index; } head = head.next; index += 1; } return -1; } /* Driver Code */ /* Initialize linked list */ // Initialize each node const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // Build references between nodes n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; console.log('Initialized linked list is'); printLinkedList(n0); /* Insert node */ insert(n0, new ListNode(0)); console.log('Linked list after inserting node is'); printLinkedList(n0); /* Remove node */ remove(n0); console.log('Linked list after removing node is'); printLinkedList(n0); /* Access node */ const node = access(n0, 3); console.log('Value of node at index 3 in linked list = ' + node.val); /* Search node */ const index = find(n0, 2); console.log('Index of node with value 2 in linked list = ' + index); ================================================ FILE: en/codes/javascript/chapter_array_and_linkedlist/list.js ================================================ /** * File: list.js * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* Initialize list */ const nums = [1, 3, 2, 5, 4]; console.log(`List nums = ${nums}`); /* Update element */ const num = nums[1]; console.log(`Access element at index 1, get num = ${num}`); /* Add elements at the end */ nums[1] = 0; console.log(`Update element at index 1 to 0, get nums = ${nums}`); /* Remove element */ nums.length = 0; console.log(`After clearing list, nums = ${nums}`); /* Direct traversal of list elements */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); console.log(`After adding elements, nums = ${nums}`); /* Sort list */ nums.splice(3, 0, 6); console.log(`Insert number 6 at index 3, get nums = ${nums}`); /* Remove element */ nums.splice(3, 1); console.log(`Delete element at index 3, get nums = ${nums}`); /* Traverse list by index */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* Directly traverse list elements */ count = 0; for (const x of nums) { count += x; } /* Concatenate two lists */ const nums1 = [6, 8, 7, 10, 9]; nums.push(...nums1); console.log(`After concatenating list nums1 to nums, get nums = ${nums}`); /* Sort list */ nums.sort((a, b) => a - b); console.log(`After sorting list, nums = ${nums}`); ================================================ FILE: en/codes/javascript/chapter_array_and_linkedlist/my_list.js ================================================ /** * File: my_list.js * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* List class */ class MyList { #arr = new Array(); // Array (stores list elements) #capacity = 10; // List capacity #size = 0; // List length (current number of elements) #extendRatio = 2; // Multiple by which the list capacity is extended each time /* Constructor */ constructor() { this.#arr = new Array(this.#capacity); } /* Get list length (current number of elements) */ size() { return this.#size; } /* Get list capacity */ capacity() { return this.#capacity; } /* Update element */ get(index) { // If the index is out of bounds, throw an exception, as below if (index < 0 || index >= this.#size) throw new Error('Index out of bounds'); return this.#arr[index]; } /* Add elements at the end */ set(index, num) { if (index < 0 || index >= this.#size) throw new Error('Index out of bounds'); this.#arr[index] = num; } /* Direct traversal of list elements */ add(num) { // If length equals capacity, need to expand if (this.#size === this.#capacity) { this.extendCapacity(); } // Add new element to end of list this.#arr[this.#size] = num; this.#size++; } /* Sort list */ insert(index, num) { if (index < 0 || index >= this.#size) throw new Error('Index out of bounds'); // When the number of elements exceeds capacity, trigger the extension mechanism if (this.#size === this.#capacity) { this.extendCapacity(); } // Move all elements after index index forward by one position for (let j = this.#size - 1; j >= index; j--) { this.#arr[j + 1] = this.#arr[j]; } // Update the number of elements this.#arr[index] = num; this.#size++; } /* Remove element */ remove(index) { if (index < 0 || index >= this.#size) throw new Error('Index out of bounds'); let num = this.#arr[index]; // Create a new array with length _extend_ratio times the original array, and copy the original array to the new array for (let j = index; j < this.#size - 1; j++) { this.#arr[j] = this.#arr[j + 1]; } // Update the number of elements this.#size--; // Return the removed element return num; } /* Driver Code */ extendCapacity() { // Create a new array with length extendRatio times the original array and copy the original array to the new array this.#arr = this.#arr.concat( new Array(this.capacity() * (this.#extendRatio - 1)) ); // Add elements at the end this.#capacity = this.#arr.length; } /* Convert list to array */ toArray() { let size = this.size(); // Elements enqueue const arr = new Array(size); for (let i = 0; i < size; i++) { arr[i] = this.get(i); } return arr; } } /* Driver Code */ /* Initialize list */ const nums = new MyList(); /* Direct traversal of list elements */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); console.log( `List nums = ${nums.toArray()}, capacity = ${nums.capacity()}, length = ${nums.size()}` ); /* Sort list */ nums.insert(3, 6); console.log(`Insert number 6 at index 3, get nums = ${nums.toArray()}`); /* Remove element */ nums.remove(3); console.log(`Delete element at index 3, get nums = ${nums.toArray()}`); /* Update element */ const num = nums.get(1); console.log(`Access element at index 1, get num = ${num}`); /* Add elements at the end */ nums.set(1, 0); console.log(`Update element at index 1 to 0, get nums = ${nums.toArray()}`); /* Test capacity expansion mechanism */ for (let i = 0; i < 10; i++) { // At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism nums.add(i); } console.log( `After expansion, list nums = ${nums.toArray()}, capacity = ${nums.capacity()}, length = ${nums.size()}` ); ================================================ FILE: en/codes/javascript/chapter_backtracking/n_queens.js ================================================ /** * File: n_queens.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* Backtracking algorithm: N queens */ function backtrack(row, n, state, res, cols, diags1, diags2) { // When all rows are placed, record the solution if (row === n) { res.push(state.map((row) => row.slice())); return; } // Traverse all columns for (let col = 0; col < n; col++) { // Calculate the main diagonal and anti-diagonal corresponding to this cell const diag1 = row - col + n - 1; const diag2 = row + col; // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Attempt: place the queen in this cell state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // Place the next row backtrack(row + 1, n, state, res, cols, diags1, diags2); // Backtrack: restore this cell to an empty cell state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Solve N queens */ function nQueens(n) { // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell const state = Array.from({ length: n }, () => Array(n).fill('#')); const cols = Array(n).fill(false); // Record whether there is a queen in the column const diags1 = Array(2 * n - 1).fill(false); // Record whether there is a queen on the main diagonal const diags2 = Array(2 * n - 1).fill(false); // Record whether there is a queen on the anti-diagonal const res = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } // Driver Code const n = 4; const res = nQueens(n); console.log(`Input board size is ${n}`); console.log(`Total queen placement solutions: ${res.length}`); res.forEach((state) => { console.log('--------------------'); state.forEach((row) => console.log(row)); }); ================================================ FILE: en/codes/javascript/chapter_backtracking/permutations_i.js ================================================ /** * File: permutations_i.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* Backtracking algorithm: Permutations I */ function backtrack(state, choices, selected, res) { // When the state length equals the number of elements, record the solution if (state.length === choices.length) { res.push([...state]); return; } // Traverse all choices choices.forEach((choice, i) => { // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state.push(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop(); } }); } /* Permutations I */ function permutationsI(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums = [1, 2, 3]; const res = permutationsI(nums); console.log(`Input array nums = ${JSON.stringify(nums)}`); console.log(`All permutations res = ${JSON.stringify(res)}`); ================================================ FILE: en/codes/javascript/chapter_backtracking/permutations_ii.js ================================================ /** * File: permutations_ii.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* Backtracking algorithm: Permutations II */ function backtrack(state, choices, selected, res) { // When the state length equals the number of elements, record the solution if (state.length === choices.length) { res.push([...state]); return; } // Traverse all choices const duplicated = new Set(); choices.forEach((choice, i) => { // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated.has(choice)) { // Attempt: make choice, update state duplicated.add(choice); // Record the selected element value selected[i] = true; state.push(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop(); } }); } /* Permutations II */ function permutationsII(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums = [1, 2, 2]; const res = permutationsII(nums); console.log(`Input array nums = ${JSON.stringify(nums)}`); console.log(`All permutations res = ${JSON.stringify(res)}`); ================================================ FILE: en/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js ================================================ /** * File: preorder_traversal_i_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Preorder traversal: Example 1 */ function preOrder(root, res) { if (root === null) { return; } if (root.val === 7) { // Record solution res.push(root); } preOrder(root.left, res); preOrder(root.right, res); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nInitialize binary tree'); printTree(root); // Preorder traversal const res = []; preOrder(root, res); console.log('\nOutput all nodes with value 7'); console.log(res.map((node) => node.val)); ================================================ FILE: en/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js ================================================ /** * File: preorder_traversal_ii_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Preorder traversal: Example 2 */ function preOrder(root, path, res) { if (root === null) { return; } // Attempt path.push(root); if (root.val === 7) { // Record solution res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // Backtrack path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nInitialize binary tree'); printTree(root); // Preorder traversal const path = []; const res = []; preOrder(root, path, res); console.log('\nOutput all paths from root node to node 7'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: en/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js ================================================ /** * File: preorder_traversal_iii_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Preorder traversal: Example 3 */ function preOrder(root, path, res) { // Pruning if (root === null || root.val === 3) { return; } // Attempt path.push(root); if (root.val === 7) { // Record solution res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // Backtrack path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nInitialize binary tree'); printTree(root); // Preorder traversal const path = []; const res = []; preOrder(root, path, res); console.log('\nOutput all paths from root node to node 7, paths do not include nodes with value 3'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: en/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js ================================================ /** * File: preorder_traversal_iii_template.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Check if the current state is a solution */ function isSolution(state) { return state && state[state.length - 1]?.val === 7; } /* Record solution */ function recordSolution(state, res) { res.push([...state]); } /* Check if the choice is valid under the current state */ function isValid(state, choice) { return choice !== null && choice.val !== 3; } /* Update state */ function makeChoice(state, choice) { state.push(choice); } /* Restore state */ function undoChoice(state) { state.pop(); } /* Backtracking algorithm: Example 3 */ function backtrack(state, choices, res) { // Check if it is a solution if (isSolution(state)) { // Record solution recordSolution(state, res); } // Traverse all choices for (const choice of choices) { // Pruning: check if the choice is valid if (isValid(state, choice)) { // Attempt: make choice, update state makeChoice(state, choice); // Proceed to the next round of selection backtrack(state, [choice.left, choice.right], res); // Backtrack: undo choice, restore to previous state undoChoice(state); } } } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nInitialize binary tree'); printTree(root); // Backtracking algorithm const res = []; backtrack([], [root], res); console.log('\nOutput all paths from root node to node 7, requiring paths do not include nodes with value 3'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: en/codes/javascript/chapter_backtracking/subset_sum_i.js ================================================ /** * File: subset_sum_i.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Backtracking algorithm: Subset sum I */ function backtrack(state, target, choices, start, res) { // When the subset sum equals target, record the solution if (target === 0) { res.push([...state]); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets for (let i = start; i < choices.length; i++) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if (target - choices[i] < 0) { break; } // Attempt: make choice, update target, start state.push(choices[i]); // Proceed to the next round of selection backtrack(state, target - choices[i], choices, i, res); // Backtrack: undo choice, restore to previous state state.pop(); } } /* Solve subset sum I */ function subsetSumI(nums, target) { const state = []; // State (subset) nums.sort((a, b) => a - b); // Sort nums const start = 0; // Start point for traversal const res = []; // Result list (subset list) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumI(nums, target); console.log(`Input array nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`All subsets with sum equal to ${target} res = ${JSON.stringify(res)}`); ================================================ FILE: en/codes/javascript/chapter_backtracking/subset_sum_i_naive.js ================================================ /** * File: subset_sum_i_naive.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Backtracking algorithm: Subset sum I */ function backtrack(state, target, total, choices, res) { // When the subset sum equals target, record the solution if (total === target) { res.push([...state]); return; } // Traverse all choices for (let i = 0; i < choices.length; i++) { // Pruning: if the subset sum exceeds target, skip this choice if (total + choices[i] > target) { continue; } // Attempt: make choice, update element sum total state.push(choices[i]); // Proceed to the next round of selection backtrack(state, target, total + choices[i], choices, res); // Backtrack: undo choice, restore to previous state state.pop(); } } /* Solve subset sum I (including duplicate subsets) */ function subsetSumINaive(nums, target) { const state = []; // State (subset) const total = 0; // Subset sum const res = []; // Result list (subset list) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumINaive(nums, target); console.log(`Input array nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`All subsets with sum equal to ${target} res = ${JSON.stringify(res)}`); console.log('Please note that this method outputs results containing duplicate sets'); ================================================ FILE: en/codes/javascript/chapter_backtracking/subset_sum_ii.js ================================================ /** * File: subset_sum_ii.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Backtracking algorithm: Subset sum II */ function backtrack(state, target, choices, start, res) { // When the subset sum equals target, record the solution if (target === 0) { res.push([...state]); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets // Pruning 3: start traversing from start to avoid repeatedly selecting the same element for (let i = start; i < choices.length; i++) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if (target - choices[i] < 0) { break; } // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly if (i > start && choices[i] === choices[i - 1]) { continue; } // Attempt: make choice, update target, start state.push(choices[i]); // Proceed to the next round of selection backtrack(state, target - choices[i], choices, i + 1, res); // Backtrack: undo choice, restore to previous state state.pop(); } } /* Solve subset sum II */ function subsetSumII(nums, target) { const state = []; // State (subset) nums.sort((a, b) => a - b); // Sort nums const start = 0; // Start point for traversal const res = []; // Result list (subset list) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [4, 4, 5]; const target = 9; const res = subsetSumII(nums, target); console.log(`Input array nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`All subsets with sum equal to ${target} res = ${JSON.stringify(res)}`); ================================================ FILE: en/codes/javascript/chapter_computational_complexity/iteration.js ================================================ /** * File: iteration.js * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* for loop */ function forLoop(n) { let res = 0; // Sum 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { res += i; } return res; } /* while loop */ function whileLoop(n) { let res = 0; let i = 1; // Initialize condition variable // Sum 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Update condition variable } return res; } /* while loop (two updates) */ function whileLoopII(n) { let res = 0; let i = 1; // Initialize condition variable // Sum 1, 4, 10, ... while (i <= n) { res += i; // Update condition variable i++; i *= 2; } return res; } /* Nested for loop */ function nestedForLoop(n) { let res = ''; // Loop i = 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { // Loop j = 1, 2, ..., n-1, n for (let j = 1; j <= n; j++) { res += `(${i}, ${j}), `; } } return res; } /* Driver Code */ const n = 5; let res; res = forLoop(n); console.log(`For loop sum result res = ${res}`); res = whileLoop(n); console.log(`While loop sum result res = ${res}`); res = whileLoopII(n); console.log(`While loop (two updates) sum result res = ${res}`); const resStr = nestedForLoop(n); console.log(`Nested for loop traversal result ${resStr}`); ================================================ FILE: en/codes/javascript/chapter_computational_complexity/recursion.js ================================================ /** * File: recursion.js * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Recursion */ function recur(n) { // Termination condition if (n === 1) return 1; // Recurse: recursive call const res = recur(n - 1); // Return: return result return n + res; } /* Simulate recursion using iteration */ function forLoopRecur(n) { // Use an explicit stack to simulate the system call stack const stack = []; let res = 0; // Recurse: recursive call for (let i = n; i > 0; i--) { // Simulate "recurse" with "push" stack.push(i); } // Return: return result while (stack.length) { // Simulate "return" with "pop" res += stack.pop(); } // res = 1+2+3+...+n return res; } /* Tail recursion */ function tailRecur(n, res) { // Termination condition if (n === 0) return res; // Tail recursive call return tailRecur(n - 1, res + n); } /* Fibonacci sequence: recursion */ function fib(n) { // Termination condition f(1) = 0, f(2) = 1 if (n === 1 || n === 2) return n - 1; // Recursive call f(n) = f(n-1) + f(n-2) const res = fib(n - 1) + fib(n - 2); // Return result f(n) return res; } /* Driver Code */ const n = 5; let res; res = recur(n); console.log(`Recursion sum result res = ${res}`); res = forLoopRecur(n); console.log(`Using iteration to simulate recursion sum result res = ${res}`); res = tailRecur(n, 0); console.log(`Tail recursion sum result res = ${res}`); res = fib(n); console.log(`The ${n}th Fibonacci number is ${res}`); ================================================ FILE: en/codes/javascript/chapter_computational_complexity/space_complexity.js ================================================ /** * File: space_complexity.js * Created Time: 2023-02-05 * Author: Justin (xiefahit@gmail.com) */ const { ListNode } = require('../modules/ListNode'); const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Function */ function constFunc() { // Perform some operations return 0; } /* Constant order */ function constant(n) { // Constants, variables, objects occupy O(1) space const a = 0; const b = 0; const nums = new Array(10000); const node = new ListNode(0); // Variables in the loop occupy O(1) space for (let i = 0; i < n; i++) { const c = 0; } // Functions in the loop occupy O(1) space for (let i = 0; i < n; i++) { constFunc(); } } /* Linear order */ function linear(n) { // Array of length n uses O(n) space const nums = new Array(n); // A list of length n occupies O(n) space const nodes = []; for (let i = 0; i < n; i++) { nodes.push(new ListNode(i)); } // A hash table of length n occupies O(n) space const map = new Map(); for (let i = 0; i < n; i++) { map.set(i, i.toString()); } } /* Linear order (recursive implementation) */ function linearRecur(n) { console.log(`Recursion n = ${n}`); if (n === 1) return; linearRecur(n - 1); } /* Exponential order */ function quadratic(n) { // Matrix uses O(n^2) space const numMatrix = Array(n) .fill(null) .map(() => Array(n).fill(null)); // 2D list uses O(n^2) space const numList = []; for (let i = 0; i < n; i++) { const tmp = []; for (let j = 0; j < n; j++) { tmp.push(0); } numList.push(tmp); } } /* Quadratic order (recursive implementation) */ function quadraticRecur(n) { if (n <= 0) return 0; const nums = new Array(n); console.log(`In recursion n = ${n}, nums length = ${nums.length}`); return quadraticRecur(n - 1); } /* Driver Code */ function buildTree(n) { if (n === 0) return null; const root = new TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ const n = 5; // Constant order constant(n); // Linear order linear(n); linearRecur(n); // Exponential order quadratic(n); quadraticRecur(n); // Exponential order const root = buildTree(n); printTree(root); ================================================ FILE: en/codes/javascript/chapter_computational_complexity/time_complexity.js ================================================ /** * File: time_complexity.js * Created Time: 2023-01-02 * Author: RiverTwilight (contact@rene.wang) */ /* Constant order */ function constant(n) { let count = 0; const size = 100000; for (let i = 0; i < size; i++) count++; return count; } /* Linear order */ function linear(n) { let count = 0; for (let i = 0; i < n; i++) count++; return count; } /* Linear order (traversing array) */ function arrayTraversal(nums) { let count = 0; // Number of iterations is proportional to the array length for (let i = 0; i < nums.length; i++) { count++; } return count; } /* Exponential order */ function quadratic(n) { let count = 0; // Number of iterations is quadratically related to the data size n for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { count++; } } return count; } /* Quadratic order (bubble sort) */ function bubbleSort(nums) { let count = 0; // Counter // Outer loop: unsorted range is [0, i] for (let i = nums.length - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // Element swap includes 3 unit operations } } } return count; } /* Exponential order (loop implementation) */ function exponential(n) { let count = 0, base = 1; // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1) for (let i = 0; i < n; i++) { for (let j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* Exponential order (recursive implementation) */ function expRecur(n) { if (n === 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* Logarithmic order (loop implementation) */ function logarithmic(n) { let count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* Logarithmic order (recursive implementation) */ function logRecur(n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* Linearithmic order */ function linearLogRecur(n) { if (n <= 1) return 1; let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (let i = 0; i < n; i++) { count++; } return count; } /* Factorial order (recursive implementation) */ function factorialRecur(n) { if (n === 0) return 1; let count = 0; // Split from 1 into n for (let i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ // You can modify n to run and observe the trend of the number of operations for various complexities const n = 8; console.log('Input data size n = ' + n); let count = constant(n); console.log('Constant order operation count = ' + count); count = linear(n); console.log('Linear order operation count = ' + count); count = arrayTraversal(new Array(n)); console.log('Linear order (array traversal) operation count = ' + count); count = quadratic(n); console.log('Quadratic order operation count = ' + count); let nums = new Array(n); for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); console.log('Quadratic order (bubble sort) operation count = ' + count); count = exponential(n); console.log('Exponential order (loop implementation) operation count = ' + count); count = expRecur(n); console.log('Exponential order (recursive implementation) operation count = ' + count); count = logarithmic(n); console.log('Logarithmic order (loop implementation) operation count = ' + count); count = logRecur(n); console.log('Logarithmic order (recursive implementation) operation count = ' + count); count = linearLogRecur(n); console.log('Linearithmic order (recursive implementation) operation count = ' + count); count = factorialRecur(n); console.log('Factorial order (recursive implementation) operation count = ' + count); ================================================ FILE: en/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js ================================================ /** * File: worst_best_time_complexity.js * Created Time: 2023-01-05 * Author: RiverTwilight (contact@rene.wang) */ /* Generate an array with elements { 1, 2, ..., n }, order shuffled */ function randomNumbers(n) { const nums = Array(n); // Generate array nums = { 1, 2, 3, ..., n } for (let i = 0; i < n; i++) { nums[i] = i + 1; } // Randomly shuffle array elements for (let i = 0; i < n; i++) { const r = Math.floor(Math.random() * (i + 1)); const temp = nums[i]; nums[i] = nums[r]; nums[r] = temp; } return nums; } /* Find the index of number 1 in array nums */ function findOne(nums) { for (let i = 0; i < nums.length; i++) { // When element 1 is at the head of the array, best time complexity O(1) is achieved // When element 1 is at the tail of the array, worst time complexity O(n) is achieved if (nums[i] === 1) { return i; } } return -1; } /* Driver Code */ for (let i = 0; i < 10; i++) { const n = 100; const nums = randomNumbers(n); const index = findOne(nums); console.log('\nArray [ 1, 2, ..., n ] after shuffling = [' + nums.join(', ') + ']'); console.log('Index of number 1 is ' + index); } ================================================ FILE: en/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js ================================================ /** * File: binary_search_recur.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Binary search: problem f(i, j) */ function dfs(nums, target, i, j) { // If the interval is empty, it means there is no target element, return -1 if (i > j) { return -1; } // Calculate the midpoint index m const m = i + ((j - i) >> 1); if (nums[m] < target) { // Recursion subproblem f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // Recursion subproblem f(i, m-1) return dfs(nums, target, i, m - 1); } else { // Found the target element, return its index return m; } } /* Binary search */ function binarySearch(nums, target) { const n = nums.length; // Solve the problem f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // Binary search (closed interval on both sides) const index = binarySearch(nums, target); console.log(`Index of target element 6 is ${index}`); ================================================ FILE: en/codes/javascript/chapter_divide_and_conquer/build_tree.js ================================================ /** * File: build_tree.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ const { printTree } = require('../modules/PrintUtil'); const { TreeNode } = require('../modules/TreeNode'); /* Build binary tree: divide and conquer */ function dfs(preorder, inorderMap, i, l, r) { // Terminate when the subtree interval is empty if (r - l < 0) return null; // Initialize the root node const root = new TreeNode(preorder[i]); // Query m to divide the left and right subtrees const m = inorderMap.get(preorder[i]); // Subproblem: build the left subtree root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // Subproblem: build the right subtree root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // Return the root node return root; } /* Build binary tree */ function buildTree(preorder, inorder) { // Initialize hash map, storing the mapping from inorder elements to indices let inorderMap = new Map(); for (let i = 0; i < inorder.length; i++) { inorderMap.set(inorder[i], i); } const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ const preorder = [3, 9, 2, 1, 7]; const inorder = [9, 3, 1, 2, 7]; console.log('Preorder traversal = ' + JSON.stringify(preorder)); console.log('Inorder traversal = ' + JSON.stringify(inorder)); const root = buildTree(preorder, inorder); console.log('The constructed binary tree is:'); printTree(root); ================================================ FILE: en/codes/javascript/chapter_divide_and_conquer/hanota.js ================================================ /** * File: hanota.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Move a disk */ function move(src, tar) { // Take out a disk from the top of src const pan = src.pop(); // Place the disk on top of tar tar.push(pan); } /* Solve the Tower of Hanoi problem f(i) */ function dfs(i, src, buf, tar) { // If there is only one disk left in src, move it directly to tar if (i === 1) { move(src, tar); return; } // Subproblem f(i-1): move the top i-1 disks from src to buf using tar dfs(i - 1, src, tar, buf); // Subproblem f(1): move the remaining disk from src to tar move(src, tar); // Subproblem f(i-1): move the top i-1 disks from buf to tar using src dfs(i - 1, buf, src, tar); } /* Solve the Tower of Hanoi problem */ function solveHanota(A, B, C) { const n = A.length; // Move the top n disks from A to C using B dfs(n, A, B, C); } /* Driver Code */ // The tail of the list is the top of the rod const A = [5, 4, 3, 2, 1]; const B = []; const C = []; console.log('In initial state:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); solveHanota(A, B, C); console.log('After disk movement is complete:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); ================================================ FILE: en/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js ================================================ /** * File: climbing_stairs_backtrack.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Backtracking */ function backtrack(choices, state, n, res) { // When climbing to the n-th stair, add 1 to the solution count if (state === n) res.set(0, res.get(0) + 1); // Traverse all choices for (const choice of choices) { // Pruning: not allowed to go beyond the n-th stair if (state + choice > n) continue; // Attempt: make choice, update state backtrack(choices, state + choice, n, res); // Backtrack } } /* Climbing stairs: Backtracking */ function climbingStairsBacktrack(n) { const choices = [1, 2]; // Can choose to climb up 1 or 2 stairs const state = 0; // Start climbing from the 0-th stair const res = new Map(); res.set(0, 0); // Use res[0] to record the solution count backtrack(choices, state, n, res); return res.get(0); } /* Driver Code */ const n = 9; const res = climbingStairsBacktrack(n); console.log(`Climbing ${n} stairs has ${res} solutions`); ================================================ FILE: en/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js ================================================ /** * File: climbing_stairs_constraint_dp.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Climbing stairs with constraint: Dynamic programming */ function climbingStairsConstraintDP(n) { if (n === 1 || n === 2) { return 1; } // Initialize dp table, used to store solutions to subproblems const dp = Array.from(new Array(n + 1), () => new Array(3)); // Initial state: preset the solution to the smallest subproblem dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // State transition: gradually solve larger subproblems from smaller ones for (let i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ const n = 9; const res = climbingStairsConstraintDP(n); console.log(`Climbing ${n} stairs has ${res} solutions`); ================================================ FILE: en/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js ================================================ /** * File: climbing_stairs_dfs.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Search */ function dfs(i) { // Known dp[1] and dp[2], return them if (i === 1 || i === 2) return i; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1) + dfs(i - 2); return count; } /* Climbing stairs: Search */ function climbingStairsDFS(n) { return dfs(n); } /* Driver Code */ const n = 9; const res = climbingStairsDFS(n); console.log(`Climbing ${n} stairs has ${res} solutions`); ================================================ FILE: en/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js ================================================ /** * File: climbing_stairs_dfs_mem.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Memoization search */ function dfs(i, mem) { // Known dp[1] and dp[2], return them if (i === 1 || i === 2) return i; // If record dp[i] exists, return it directly if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1, mem) + dfs(i - 2, mem); // Record dp[i] mem[i] = count; return count; } /* Climbing stairs: Memoization search */ function climbingStairsDFSMem(n) { // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record const mem = new Array(n + 1).fill(-1); return dfs(n, mem); } /* Driver Code */ const n = 9; const res = climbingStairsDFSMem(n); console.log(`Climbing ${n} stairs has ${res} solutions`); ================================================ FILE: en/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js ================================================ /** * File: climbing_stairs_dp.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Climbing stairs: Dynamic programming */ function climbingStairsDP(n) { if (n === 1 || n === 2) return n; // Initialize dp table, used to store solutions to subproblems const dp = new Array(n + 1).fill(-1); // Initial state: preset the solution to the smallest subproblem dp[1] = 1; dp[2] = 2; // State transition: gradually solve larger subproblems from smaller ones for (let i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* Climbing stairs: Space-optimized dynamic programming */ function climbingStairsDPComp(n) { if (n === 1 || n === 2) return n; let a = 1, b = 2; for (let i = 3; i <= n; i++) { const tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ const n = 9; let res = climbingStairsDP(n); console.log(`Climbing ${n} stairs has ${res} solutions`); res = climbingStairsDPComp(n); console.log(`Climbing ${n} stairs has ${res} solutions`); ================================================ FILE: en/codes/javascript/chapter_dynamic_programming/coin_change.js ================================================ /** * File: coin_change.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Coin change: Dynamic programming */ function coinChangeDP(coins, amt) { const n = coins.length; const MAX = amt + 1; // Initialize dp table const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // State transition: first row and first column for (let a = 1; a <= amt; a++) { dp[0][a] = MAX; } // State transition: rest of the rows and columns for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // The smaller value between not selecting and selecting coin 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; } /* Coin change: Space-optimized dynamic programming */ function coinChangeDPComp(coins, amt) { const n = coins.length; const MAX = amt + 1; // Initialize dp table const dp = Array.from({ length: amt + 1 }, () => MAX); dp[0] = 0; // State transition for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // The smaller value between not selecting and selecting coin i dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] !== MAX ? dp[amt] : -1; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 4; // Dynamic programming let res = coinChangeDP(coins, amt); console.log(`Minimum coins needed to make target amount is ${res}`); // Space-optimized dynamic programming res = coinChangeDPComp(coins, amt); console.log(`Minimum coins needed to make target amount is ${res}`); ================================================ FILE: en/codes/javascript/chapter_dynamic_programming/coin_change_ii.js ================================================ /** * File: coin_change_ii.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Coin change II: Dynamic programming */ function coinChangeIIDP(coins, amt) { const n = coins.length; // Initialize dp table const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // Initialize first column for (let i = 0; i <= n; i++) { dp[i][0] = 1; } // State transition for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // Sum of the two options: not selecting and selecting coin i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* Coin change II: Space-optimized dynamic programming */ function coinChangeIIDPComp(coins, amt) { const n = coins.length; // Initialize dp table const dp = Array.from({ length: amt + 1 }, () => 0); dp[0] = 1; // State transition for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // Sum of the two options: not selecting and selecting coin i dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 5; // Dynamic programming let res = coinChangeIIDP(coins, amt); console.log(`Number of coin combinations to make target amount is ${res}`); // Space-optimized dynamic programming res = coinChangeIIDPComp(coins, amt); console.log(`Number of coin combinations to make target amount is ${res}`); ================================================ FILE: en/codes/javascript/chapter_dynamic_programming/edit_distance.js ================================================ /** * File: edit_distance.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Edit distance: Brute-force search */ function editDistanceDFS(s, t, i, j) { // If both s and t are empty, return 0 if (i === 0 && j === 0) return 0; // If s is empty, return length of t if (i === 0) return j; // If t is empty, return length of s if (j === 0) return i; // If two characters are equal, skip both characters if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 const insert = editDistanceDFS(s, t, i, j - 1); const del = editDistanceDFS(s, t, i - 1, j); const replace = editDistanceDFS(s, t, i - 1, j - 1); // Return minimum edit steps return Math.min(insert, del, replace) + 1; } /* Edit distance: Memoization search */ function editDistanceDFSMem(s, t, mem, i, j) { // If both s and t are empty, return 0 if (i === 0 && j === 0) return 0; // If s is empty, return length of t if (i === 0) return j; // If t is empty, return length of s if (j === 0) return i; // If there's a record, return it directly if (mem[i][j] !== -1) return mem[i][j]; // If two characters are equal, skip both characters if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 const insert = editDistanceDFSMem(s, t, mem, i, j - 1); const del = editDistanceDFSMem(s, t, mem, i - 1, j); const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Record and return minimum edit steps mem[i][j] = Math.min(insert, del, replace) + 1; return mem[i][j]; } /* Edit distance: Dynamic programming */ function editDistanceDP(s, t) { const n = s.length, m = t.length; const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); // State transition: first row and first column for (let i = 1; i <= n; i++) { dp[i][0] = i; } for (let j = 1; j <= m; j++) { dp[0][j] = j; } // State transition: rest of the rows and columns for (let i = 1; i <= n; i++) { for (let j = 1; j <= m; j++) { if (s.charAt(i - 1) === t.charAt(j - 1)) { // If two characters are equal, skip both characters dp[i][j] = dp[i - 1][j - 1]; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* Edit distance: Space-optimized dynamic programming */ function editDistanceDPComp(s, t) { const n = s.length, m = t.length; const dp = new Array(m + 1).fill(0); // State transition: first row for (let j = 1; j <= m; j++) { dp[j] = j; } // State transition: rest of the rows for (let i = 1; i <= n; i++) { // State transition: first column let leftup = dp[0]; // Temporarily store dp[i-1, j-1] dp[0] = i; // State transition: rest of the columns for (let j = 1; j <= m; j++) { const temp = dp[j]; if (s.charAt(i - 1) === t.charAt(j - 1)) { // If two characters are equal, skip both characters dp[j] = leftup; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; } leftup = temp; // Update for next round's dp[i-1, j-1] } } return dp[m]; } const s = 'bag'; const t = 'pack'; const n = s.length, m = t.length; // Brute-force search let res = editDistanceDFS(s, t, n, m); console.log(`Changing ${s} to ${t} requires minimum ${res} edits`); // Memoization search const mem = Array.from(new Array(n + 1), () => new Array(m + 1).fill(-1)); res = editDistanceDFSMem(s, t, mem, n, m); console.log(`Changing ${s} to ${t} requires minimum ${res} edits`); // Dynamic programming res = editDistanceDP(s, t); console.log(`Changing ${s} to ${t} requires minimum ${res} edits`); // Space-optimized dynamic programming res = editDistanceDPComp(s, t); console.log(`Changing ${s} to ${t} requires minimum ${res} edits`); ================================================ FILE: en/codes/javascript/chapter_dynamic_programming/knapsack.js ================================================ /** * File: knapsack.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 0-1 knapsack: Brute-force search */ function knapsackDFS(wgt, val, i, c) { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i === 0 || c === 0) { return 0; } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // Calculate the maximum value of not putting in and putting in item i const no = knapsackDFS(wgt, val, i - 1, c); const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // Return the larger value of the two options return Math.max(no, yes); } /* 0-1 knapsack: Memoization search */ function knapsackDFSMem(wgt, val, mem, i, c) { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i === 0 || c === 0) { return 0; } // If there's a record, return it directly if (mem[i][c] !== -1) { return mem[i][c]; } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // Calculate the maximum value of not putting in and putting in item i const no = knapsackDFSMem(wgt, val, mem, i - 1, c); const yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // Record and return the larger value of the two options mem[i][c] = Math.max(no, yes); return mem[i][c]; } /* 0-1 knapsack: Dynamic programming */ function knapsackDP(wgt, val, cap) { const n = wgt.length; // Initialize dp table const dp = Array(n + 1) .fill(0) .map(() => Array(cap + 1).fill(0)); // State transition for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item 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 knapsack: Space-optimized dynamic programming */ function knapsackDPComp(wgt, val, cap) { const n = wgt.length; // Initialize dp table const dp = Array(cap + 1).fill(0); // State transition for (let i = 1; i <= n; i++) { // Traverse in reverse order for (let c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // The larger value between not selecting and selecting item i dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // Brute-force search let res = knapsackDFS(wgt, val, n, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); // Memoization search const mem = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => -1) ); res = knapsackDFSMem(wgt, val, mem, n, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); // Dynamic programming res = knapsackDP(wgt, val, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); // Space-optimized dynamic programming res = knapsackDPComp(wgt, val, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); ================================================ FILE: en/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js ================================================ /** * File: min_cost_climbing_stairs_dp.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Minimum cost climbing stairs: Dynamic programming */ function minCostClimbingStairsDP(cost) { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } // Initialize dp table, used to store solutions to subproblems const dp = new Array(n + 1); // Initial state: preset the solution to the smallest subproblem dp[1] = cost[1]; dp[2] = cost[2]; // State transition: gradually solve larger subproblems from smaller ones for (let i = 3; i <= n; i++) { dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* Minimum cost climbing stairs: Space-optimized dynamic programming */ function minCostClimbingStairsDPComp(cost) { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } let a = cost[1], b = cost[2]; for (let i = 3; i <= n; i++) { const tmp = b; b = Math.min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; console.log('Input stair cost list is:', cost); let res = minCostClimbingStairsDP(cost); console.log(`Minimum cost to climb stairs is: ${res}`); res = minCostClimbingStairsDPComp(cost); console.log(`Minimum cost to climb stairs is: ${res}`); ================================================ FILE: en/codes/javascript/chapter_dynamic_programming/min_path_sum.js ================================================ /** * File: min_path_sum.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Minimum path sum: Brute-force search */ function minPathSumDFS(grid, i, j) { // If it's the top-left cell, terminate the search if (i === 0 && j === 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return Infinity; } // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1) const up = minPathSumDFS(grid, i - 1, j); const left = minPathSumDFS(grid, i, j - 1); // Return the minimum path cost from top-left to (i, j) return Math.min(left, up) + grid[i][j]; } /* Minimum path sum: Memoization search */ function minPathSumDFSMem(grid, mem, i, j) { // If it's the top-left cell, terminate the search if (i === 0 && j === 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return Infinity; } // If there's a record, return it directly if (mem[i][j] !== -1) { return mem[i][j]; } // Minimum path cost for left and upper cells const up = minPathSumDFSMem(grid, mem, i - 1, j); const left = minPathSumDFSMem(grid, mem, i, j - 1); // Record and return the minimum path cost from top-left to (i, j) mem[i][j] = Math.min(left, up) + grid[i][j]; return mem[i][j]; } /* Minimum path sum: Dynamic programming */ function minPathSumDP(grid) { const n = grid.length, m = grid[0].length; // Initialize dp table const dp = Array.from({ length: n }, () => Array.from({ length: m }, () => 0) ); dp[0][0] = grid[0][0]; // State transition: first row for (let j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // State transition: first column for (let i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // State transition: rest of the rows and columns for (let i = 1; i < n; i++) { for (let j = 1; j < m; j++) { dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* Minimum path sum: Space-optimized dynamic programming */ function minPathSumDPComp(grid) { const n = grid.length, m = grid[0].length; // Initialize dp table const dp = new Array(m); // State transition: first row dp[0] = grid[0][0]; for (let j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // State transition: rest of the rows for (let i = 1; i < n; i++) { // State transition: first column dp[0] = dp[0] + grid[i][0]; // State transition: rest of the columns for (let j = 1; j < m; j++) { dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ const grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; const n = grid.length, m = grid[0].length; // Brute-force search let res = minPathSumDFS(grid, n - 1, m - 1); console.log(`Minimum path sum from top-left to bottom-right is ${res}`); // Memoization search const mem = Array.from({ length: n }, () => Array.from({ length: m }, () => -1) ); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); console.log(`Minimum path sum from top-left to bottom-right is ${res}`); // Dynamic programming res = minPathSumDP(grid); console.log(`Minimum path sum from top-left to bottom-right is ${res}`); // Space-optimized dynamic programming res = minPathSumDPComp(grid); console.log(`Minimum path sum from top-left to bottom-right is ${res}`); ================================================ FILE: en/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js ================================================ /** * File: unbounded_knapsack.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Unbounded knapsack: Dynamic programming */ function unboundedKnapsackDP(wgt, val, cap) { const n = wgt.length; // Initialize dp table const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // State transition for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item i dp[i][c] = Math.max( dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* Unbounded knapsack: Space-optimized dynamic programming */ function unboundedKnapsackDPComp(wgt, val, cap) { const n = wgt.length; // Initialize dp table const dp = Array.from({ length: cap + 1 }, () => 0); // State transition for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[c] = dp[c]; } else { // The larger value between not selecting and selecting item i dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [1, 2, 3]; const val = [5, 11, 15]; const cap = 4; // Dynamic programming let res = unboundedKnapsackDP(wgt, val, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); // Space-optimized dynamic programming res = unboundedKnapsackDPComp(wgt, val, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); ================================================ FILE: en/codes/javascript/chapter_graph/graph_adjacency_list.js ================================================ /** * File: graph_adjacency_list.js * Created Time: 2023-02-09 * Author: Justin (xiefahit@gmail.com) */ const { Vertex } = require('../modules/Vertex'); /* Undirected graph class based on adjacency list */ class GraphAdjList { // Adjacency list, key: vertex, value: all adjacent vertices of that vertex adjList; /* Constructor */ constructor(edges) { this.adjList = new Map(); // Add all vertices and edges for (const edge of edges) { this.addVertex(edge[0]); this.addVertex(edge[1]); this.addEdge(edge[0], edge[1]); } } /* Get the number of vertices */ size() { return this.adjList.size; } /* Add edge */ addEdge(vet1, vet2) { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 ) { throw new Error('Illegal Argument Exception'); } // Add edge vet1 - vet2 this.adjList.get(vet1).push(vet2); this.adjList.get(vet2).push(vet1); } /* Remove edge */ removeEdge(vet1, vet2) { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 || this.adjList.get(vet1).indexOf(vet2) === -1 ) { throw new Error('Illegal Argument Exception'); } // Remove edge vet1 - vet2 this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); } /* Add vertex */ addVertex(vet) { if (this.adjList.has(vet)) return; // Add a new linked list in the adjacency list this.adjList.set(vet, []); } /* Remove vertex */ removeVertex(vet) { if (!this.adjList.has(vet)) { throw new Error('Illegal Argument Exception'); } // Remove the linked list corresponding to vertex vet in the adjacency list this.adjList.delete(vet); // Traverse the linked lists of other vertices and remove all edges containing vet for (const set of this.adjList.values()) { const index = set.indexOf(vet); if (index > -1) { set.splice(index, 1); } } } /* Print adjacency list */ print() { console.log('Adjacency list ='); for (const [key, value] of this.adjList) { const tmp = []; for (const vertex of value) { tmp.push(vertex.val); } console.log(key.val + ': ' + tmp.join()); } } } if (require.main === module) { /* Driver Code */ /* Add edge */ const v0 = new Vertex(1), v1 = new Vertex(3), v2 = new Vertex(2), v3 = new Vertex(5), v4 = new Vertex(4); const edges = [ [v0, v1], [v1, v2], [v2, v3], [v0, v3], [v2, v4], [v3, v4], ]; const graph = new GraphAdjList(edges); console.log('\nAfter initialization, graph is'); graph.print(); /* Add edge */ // Vertices 1, 2 are v0, v2 graph.addEdge(v0, v2); console.log('\nAfter adding edge 1-2, graph is'); graph.print(); /* Remove edge */ // Vertices 1, 3 are v0, v1 graph.removeEdge(v0, v1); console.log('\nAfter removing edge 1-3, graph is'); graph.print(); /* Add vertex */ const v5 = new Vertex(6); graph.addVertex(v5); console.log('\nAfter adding vertex 6, graph is'); graph.print(); /* Remove vertex */ // Vertex 3 is v1 graph.removeVertex(v1); console.log('\nAfter removing vertex 3, graph is'); graph.print(); } module.exports = { GraphAdjList, }; ================================================ FILE: en/codes/javascript/chapter_graph/graph_adjacency_matrix.js ================================================ /** * File: graph_adjacency_matrix.js * Created Time: 2023-02-09 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Undirected graph class based on adjacency matrix */ class GraphAdjMat { vertices; // Vertex list, where the element represents the "vertex value" and the index represents the "vertex index" adjMat; // Adjacency matrix, where the row and column indices correspond to the "vertex index" /* Constructor */ constructor(vertices, edges) { this.vertices = []; this.adjMat = []; // Add vertex for (const val of vertices) { this.addVertex(val); } // Add edge // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices for (const e of edges) { this.addEdge(e[0], e[1]); } } /* Get the number of vertices */ size() { return this.vertices.length; } /* Add vertex */ addVertex(val) { const n = this.size(); // Add the value of the new vertex to the vertex list this.vertices.push(val); // Add a row to the adjacency matrix const newRow = []; for (let j = 0; j < n; j++) { newRow.push(0); } this.adjMat.push(newRow); // Add a column to the adjacency matrix for (const row of this.adjMat) { row.push(0); } } /* Remove vertex */ removeVertex(index) { if (index >= this.size()) { throw new RangeError('Index Out Of Bounds Exception'); } // Remove the vertex at index from the vertex list this.vertices.splice(index, 1); // Remove the row at index from the adjacency matrix this.adjMat.splice(index, 1); // Remove the column at index from the adjacency matrix for (const row of this.adjMat) { row.splice(index, 1); } } /* Add edge */ // Parameters i, j correspond to the vertices element indices addEdge(i, j) { // Handle index out of bounds and equality if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } // In undirected graph, adjacency matrix is symmetric about main diagonal, i.e., satisfies (i, j) === (j, i) this.adjMat[i][j] = 1; this.adjMat[j][i] = 1; } /* Remove edge */ // Parameters i, j correspond to the vertices element indices removeEdge(i, j) { // Handle index out of bounds and equality if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } this.adjMat[i][j] = 0; this.adjMat[j][i] = 0; } /* Print adjacency matrix */ print() { console.log('Vertex list = ', this.vertices); console.log('Adjacency matrix =', this.adjMat); } } /* Driver Code */ /* Add edge */ // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices const vertices = [1, 3, 2, 5, 4]; const edges = [ [0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4], ]; const graph = new GraphAdjMat(vertices, edges); console.log('\nAfter initialization, graph is'); graph.print(); /* Add edge */ // Add vertex graph.addEdge(0, 2); console.log('\nAfter adding edge 1-2, graph is'); graph.print(); /* Remove edge */ // Vertices 1, 3 have indices 0, 1 respectively graph.removeEdge(0, 1); console.log('\nAfter removing edge 1-3, graph is'); graph.print(); /* Add vertex */ graph.addVertex(6); console.log('\nAfter adding vertex 6, graph is'); graph.print(); /* Remove vertex */ // Vertex 3 has index 1 graph.removeVertex(1); console.log('\nAfter removing vertex 3, graph is'); graph.print(); ================================================ FILE: en/codes/javascript/chapter_graph/graph_bfs.js ================================================ /** * File: graph_bfs.js * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { GraphAdjList } = require('./graph_adjacency_list'); const { Vertex } = require('../modules/Vertex'); /* Breadth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex function graphBFS(graph, startVet) { // Vertex traversal sequence const res = []; // Hash set for recording vertices that have been visited const visited = new Set(); visited.add(startVet); // Queue used to implement BFS const que = [startVet]; // Starting from vertex vet, loop until all vertices are visited while (que.length) { const vet = que.shift(); // Dequeue the front vertex res.push(vet); // Record visited vertex // Traverse all adjacent vertices of this vertex for (const adjVet of graph.adjList.get(vet) ?? []) { if (visited.has(adjVet)) { continue; // Skip vertices that have been visited } que.push(adjVet); // Only enqueue unvisited vertices visited.add(adjVet); // Mark this vertex as visited } } // Return vertex traversal sequence return res; } /* Driver Code */ /* Add edge */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; const graph = new GraphAdjList(edges); console.log('\nAfter initialization, graph is'); graph.print(); /* Breadth-first traversal */ const res = graphBFS(graph, v[0]); console.log('\nBreadth-first traversal (BFS) vertex sequence is'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: en/codes/javascript/chapter_graph/graph_dfs.js ================================================ /** * File: graph_dfs.js * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { Vertex } = require('../modules/Vertex'); const { GraphAdjList } = require('./graph_adjacency_list'); /* Depth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex function dfs(graph, visited, res, vet) { res.push(vet); // Record visited vertex visited.add(vet); // Mark this vertex as visited // Traverse all adjacent vertices of this vertex for (const adjVet of graph.adjList.get(vet)) { if (visited.has(adjVet)) { continue; // Skip vertices that have been visited } // Recursively visit adjacent vertices dfs(graph, visited, res, adjVet); } } /* Depth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex function graphDFS(graph, startVet) { // Vertex traversal sequence const res = []; // Hash set for recording vertices that have been visited const visited = new Set(); dfs(graph, visited, res, startVet); return res; } /* Driver Code */ /* Add edge */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; const graph = new GraphAdjList(edges); console.log('\nAfter initialization, graph is'); graph.print(); /* Depth-first traversal */ const res = graphDFS(graph, v[0]); console.log('\nDepth-first traversal (DFS) vertex sequence is'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: en/codes/javascript/chapter_greedy/coin_change_greedy.js ================================================ /** * File: coin_change_greedy.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Coin change: Greedy algorithm */ function coinChangeGreedy(coins, amt) { // Assume coins array is sorted let i = coins.length - 1; let count = 0; // Loop to make greedy choices until no remaining amount while (amt > 0) { // Find the coin that is less than and closest to the remaining amount while (i > 0 && coins[i] > amt) { i--; } // Choose coins[i] amt -= coins[i]; count++; } // If no feasible solution is found, return -1 return amt === 0 ? count : -1; } /* Driver Code */ // Greedy algorithm: Can guarantee finding the global optimal solution let coins = [1, 5, 10, 20, 50, 100]; let amt = 186; let res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`Minimum coins needed to make ${amt} is ${res}`); // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`Minimum coins needed to make ${amt} is ${res}`); console.log('Actually the minimum number needed is 3, i.e., 20 + 20 + 20'); // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`Minimum coins needed to make ${amt} is ${res}`); console.log('Actually the minimum number needed is 2, i.e., 49 + 49'); ================================================ FILE: en/codes/javascript/chapter_greedy/fractional_knapsack.js ================================================ /** * File: fractional_knapsack.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Item */ class Item { constructor(w, v) { this.w = w; // Item weight this.v = v; // Item value } } /* Fractional knapsack: Greedy algorithm */ function fractionalKnapsack(wgt, val, cap) { // Create item list with two attributes: weight, value const items = wgt.map((w, i) => new Item(w, val[i])); // Sort by unit value item.v / item.w from high to low items.sort((a, b) => b.v / b.w - a.v / a.w); // Loop for greedy selection let res = 0; for (const item of items) { if (item.w <= cap) { // If remaining capacity is sufficient, put the entire current item into the knapsack res += item.v; cap -= item.w; } else { // If remaining capacity is insufficient, put part of the current item into the knapsack res += (item.v / item.w) * cap; // No remaining capacity, so break out of the loop break; } } return res; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // Greedy algorithm const res = fractionalKnapsack(wgt, val, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); ================================================ FILE: en/codes/javascript/chapter_greedy/max_capacity.js ================================================ /** * File: max_capacity.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Max capacity: Greedy algorithm */ function maxCapacity(ht) { // Initialize i, j to be at both ends of the array let i = 0, j = ht.length - 1; // Initial max capacity is 0 let res = 0; // Loop for greedy selection until the two boards meet while (i < j) { // Update max capacity const cap = Math.min(ht[i], ht[j]) * (j - i); res = Math.max(res, cap); // Move the shorter board inward if (ht[i] < ht[j]) { i += 1; } else { j -= 1; } } return res; } /* Driver Code */ const ht = [3, 8, 5, 2, 7, 7, 3, 4]; // Greedy algorithm const res = maxCapacity(ht); console.log(`Maximum capacity is ${res}`); ================================================ FILE: en/codes/javascript/chapter_greedy/max_product_cutting.js ================================================ /** * File: max_product_cutting.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Max product cutting: Greedy algorithm */ function maxProductCutting(n) { // When n <= 3, must cut out a 1 if (n <= 3) { return 1 * (n - 1); } // Greedily cut out 3, a is the number of 3s, b is the remainder let a = Math.floor(n / 3); let b = n % 3; if (b === 1) { // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2 return Math.pow(3, a - 1) * 2 * 2; } if (b === 2) { // When the remainder is 2, do nothing return Math.pow(3, a) * 2; } // When the remainder is 0, do nothing return Math.pow(3, a); } /* Driver Code */ let n = 58; // Greedy algorithm let res = maxProductCutting(n); console.log(`Maximum cutting product is ${res}`); ================================================ FILE: en/codes/javascript/chapter_hashing/array_hash_map.js ================================================ /** * File: array_hash_map.js * Created Time: 2022-12-26 * Author: Justin (xiefahit@gmail.com) */ /* Key-value pair Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* Hash table based on array implementation */ class ArrayHashMap { #buckets; constructor() { // Initialize array with 100 buckets this.#buckets = new Array(100).fill(null); } /* Hash function */ #hashFunc(key) { return key % 100; } /* Query operation */ get(key) { let index = this.#hashFunc(key); let pair = this.#buckets[index]; if (pair === null) return null; return pair.val; } /* Add operation */ set(key, val) { let index = this.#hashFunc(key); this.#buckets[index] = new Pair(key, val); } /* Remove operation */ delete(key) { let index = this.#hashFunc(key); // Set to null to represent deletion this.#buckets[index] = null; } /* Get all key-value pairs */ entries() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i]); } } return arr; } /* Get all keys */ keys() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i].key); } } return arr; } /* Get all values */ values() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i].val); } } return arr; } /* Print hash table */ print() { let pairSet = this.entries(); for (const pair of pairSet) { console.info(`${pair.key} -> ${pair.val}`); } } } /* Driver Code */ /* Initialize hash table */ const map = new ArrayHashMap(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.set(12836, 'Xiao Ha'); map.set(15937, 'Xiao Luo'); map.set(16750, 'Xiao Suan'); map.set(13276, 'Xiao Fa'); map.set(10583, 'Xiao Ya'); console.info('\nAfter adding is complete, hash table is\nKey -> Value'); map.print(); /* Query operation */ // Input key into hash table to get value let name = map.get(15937); console.info('\nInput student ID 15937, query name ' + name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.delete(10583); console.info('\nAfter removing 10583, hash table is\nKey -> Value'); map.print(); /* Traverse hash table */ console.info('\nTraverse key-value pairs Key->Value'); for (const pair of map.entries()) { if (!pair) continue; console.info(pair.key + ' -> ' + pair.val); } console.info('\nTraverse keys only Key'); for (const key of map.keys()) { console.info(key); } console.info('\nTraverse values only Value'); for (const val of map.values()) { console.info(val); } ================================================ FILE: en/codes/javascript/chapter_hashing/hash_map.js ================================================ /** * File: hash_map.js * Created Time: 2022-12-26 * Author: Justin (xiefahit@gmail.com) */ /* Driver Code */ /* Initialize hash table */ const map = new Map(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.set(12836, 'Xiao Ha'); map.set(15937, 'Xiao Luo'); map.set(16750, 'Xiao Suan'); map.set(13276, 'Xiao Fa'); map.set(10583, 'Xiao Ya'); console.info('\nAfter adding is complete, hash table is\nKey -> Value'); console.info(map); /* Query operation */ // Input key into hash table to get value let name = map.get(15937); console.info('\nInput student ID 15937, query name ' + name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.delete(10583); console.info('\nAfter removing 10583, hash table is\nKey -> Value'); console.info(map); /* Traverse hash table */ console.info('\nTraverse key-value pairs Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\nTraverse keys only Key'); for (const k of map.keys()) { console.info(k); } console.info('\nTraverse values only Value'); for (const v of map.values()) { console.info(v); } ================================================ FILE: en/codes/javascript/chapter_hashing/hash_map_chaining.js ================================================ /** * File: hash_map_chaining.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Key-value pair Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* Hash table with separate chaining */ class HashMapChaining { #size; // Number of key-value pairs #capacity; // Hash table capacity #loadThres; // Load factor threshold for triggering expansion #extendRatio; // Expansion multiplier #buckets; // Bucket array /* Constructor */ constructor() { this.#size = 0; this.#capacity = 4; this.#loadThres = 2.0 / 3.0; this.#extendRatio = 2; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); } /* Hash function */ #hashFunc(key) { return key % this.#capacity; } /* Load factor */ #loadFactor() { return this.#size / this.#capacity; } /* Query operation */ get(key) { const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // Traverse bucket, if key is found, return corresponding val for (const pair of bucket) { if (pair.key === key) { return pair.val; } } // If key is not found, return null return null; } /* Add operation */ put(key, val) { // When load factor exceeds threshold, perform expansion if (this.#loadFactor() > this.#loadThres) { this.#extend(); } const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // Traverse bucket, if specified key is encountered, update corresponding val and return for (const pair of bucket) { if (pair.key === key) { pair.val = val; return; } } // If key does not exist, append key-value pair to the end const pair = new Pair(key, val); bucket.push(pair); this.#size++; } /* Remove operation */ remove(key) { const index = this.#hashFunc(key); let bucket = this.#buckets[index]; // Traverse bucket and remove key-value pair from it for (let i = 0; i < bucket.length; i++) { if (bucket[i].key === key) { bucket.splice(i, 1); this.#size--; break; } } } /* Expand hash table */ #extend() { // Temporarily store the original hash table const bucketsTmp = this.#buckets; // Initialize expanded new hash table this.#capacity *= this.#extendRatio; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); this.#size = 0; // Move key-value pairs from original hash table to new hash table for (const bucket of bucketsTmp) { for (const pair of bucket) { this.put(pair.key, pair.val); } } } /* Print hash table */ print() { for (const bucket of this.#buckets) { let res = []; for (const pair of bucket) { res.push(pair.key + ' -> ' + pair.val); } console.log(res); } } } /* Driver Code */ /* Initialize hash table */ const map = new HashMapChaining(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(12836, 'Xiao Ha'); map.put(15937, 'Xiao Luo'); map.put(16750, 'Xiao Suan'); map.put(13276, 'Xiao Fa'); map.put(10583, 'Xiao Ya'); console.log('\nAfter adding is complete, hash table is\nKey -> Value'); map.print(); /* Query operation */ // Input key into hash table to get value const name = map.get(13276); console.log('\nInput student ID 13276, query name ' + name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(12836); console.log('\nAfter removing 12836, hash table is\nKey -> Value'); map.print(); ================================================ FILE: en/codes/javascript/chapter_hashing/hash_map_open_addressing.js ================================================ /** * File: hashMapOpenAddressing.js * Created Time: 2023-06-13 * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) */ /* Key-value pair Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* Hash table with open addressing */ class HashMapOpenAddressing { #size; // Number of key-value pairs #capacity; // Hash table capacity #loadThres; // Load factor threshold for triggering expansion #extendRatio; // Expansion multiplier #buckets; // Bucket array #TOMBSTONE; // Removal marker /* Constructor */ constructor() { this.#size = 0; // Number of key-value pairs this.#capacity = 4; // Hash table capacity this.#loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion this.#extendRatio = 2; // Expansion multiplier this.#buckets = Array(this.#capacity).fill(null); // Bucket array this.#TOMBSTONE = new Pair(-1, '-1'); // Removal marker } /* Hash function */ #hashFunc(key) { return key % this.#capacity; } /* Load factor */ #loadFactor() { return this.#size / this.#capacity; } /* Search for bucket index corresponding to key */ #findBucket(key) { let index = this.#hashFunc(key); let firstTombstone = -1; // Linear probing, break when encountering an empty bucket while (this.#buckets[index] !== null) { // If key is encountered, return the corresponding bucket index if (this.#buckets[index].key === key) { // If a removal marker was encountered before, move the key-value pair to that index if (firstTombstone !== -1) { this.#buckets[firstTombstone] = this.#buckets[index]; this.#buckets[index] = this.#TOMBSTONE; return firstTombstone; // Return the moved bucket index } return index; // Return bucket index } // Record the first removal marker encountered if ( firstTombstone === -1 && this.#buckets[index] === this.#TOMBSTONE ) { firstTombstone = index; } // Calculate bucket index, wrap around to the head if past the tail index = (index + 1) % this.#capacity; } // If key does not exist, return the index for insertion return firstTombstone === -1 ? index : firstTombstone; } /* Query operation */ get(key) { // Search for bucket index corresponding to key const index = this.#findBucket(key); // If key-value pair is found, return corresponding val if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { return this.#buckets[index].val; } // If key-value pair does not exist, return null return null; } /* Add operation */ put(key, val) { // When load factor exceeds threshold, perform expansion if (this.#loadFactor() > this.#loadThres) { this.#extend(); } // Search for bucket index corresponding to key const index = this.#findBucket(key); // If key-value pair is found, overwrite val and return if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { this.#buckets[index].val = val; return; } // If key-value pair does not exist, add the key-value pair this.#buckets[index] = new Pair(key, val); this.#size++; } /* Remove operation */ remove(key) { // Search for bucket index corresponding to key const index = this.#findBucket(key); // If key-value pair is found, overwrite it with removal marker if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { this.#buckets[index] = this.#TOMBSTONE; this.#size--; } } /* Expand hash table */ #extend() { // Temporarily store the original hash table const bucketsTmp = this.#buckets; // Initialize expanded new hash table this.#capacity *= this.#extendRatio; this.#buckets = Array(this.#capacity).fill(null); this.#size = 0; // Move key-value pairs from original hash table to new hash table for (const pair of bucketsTmp) { if (pair !== null && pair !== this.#TOMBSTONE) { this.put(pair.key, pair.val); } } } /* Print hash table */ print() { for (const pair of this.#buckets) { if (pair === null) { console.log('null'); } else if (pair === this.#TOMBSTONE) { console.log('TOMBSTONE'); } else { console.log(pair.key + ' -> ' + pair.val); } } } } /* Driver Code */ // Initialize hash table const hashmap = new HashMapOpenAddressing(); // Add operation // Add key-value pair (key, val) to the hash table hashmap.put(12836, 'Xiao Ha'); hashmap.put(15937, 'Xiao Luo'); hashmap.put(16750, 'Xiao Suan'); hashmap.put(13276, 'Xiao Fa'); hashmap.put(10583, 'Xiao Ya'); console.log('\nAfter adding is complete, hash table is\nKey -> Value'); hashmap.print(); // Query operation // Input key into hash table to get value val const name = hashmap.get(13276); console.log('\nInput student ID 13276, query name ' + name); // Remove operation // Remove key-value pair (key, val) from hash table hashmap.remove(16750); console.log('\nAfter removing 16750, hash table is\nKey -> Value'); hashmap.print(); ================================================ FILE: en/codes/javascript/chapter_hashing/simple_hash.js ================================================ /** * File: simple_hash.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Additive hash */ function addHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* Multiplicative hash */ function mulHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (31 * hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* XOR hash */ function xorHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash ^= c.charCodeAt(0); } return hash % MODULUS; } /* Rotational hash */ function rotHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; } return hash; } /* Driver Code */ const key = 'Hello Algo'; let hash = addHash(key); console.log('Additive hash value is ' + hash); hash = mulHash(key); console.log('Multiplicative hash value is ' + hash); hash = xorHash(key); console.log('XOR hash value is ' + hash); hash = rotHash(key); console.log('Rotational hash value is ' + hash); ================================================ FILE: en/codes/javascript/chapter_heap/my_heap.js ================================================ /** * File: my_heap.js * Created Time: 2023-02-06 * Author: what-is-me (whatisme@outlook.jp) */ const { printHeap } = require('../modules/PrintUtil'); /* Max heap class */ class MaxHeap { #maxHeap; /* Constructor, build empty heap or build heap from input list */ constructor(nums) { // Add list elements to heap as is this.#maxHeap = nums === undefined ? [] : [...nums]; // Heapify all nodes except leaf nodes for (let i = this.#parent(this.size() - 1); i >= 0; i--) { this.#siftDown(i); } } /* Get index of left child node */ #left(i) { return 2 * i + 1; } /* Get index of right child node */ #right(i) { return 2 * i + 2; } /* Get index of parent node */ #parent(i) { return Math.floor((i - 1) / 2); // Floor division } /* Swap elements */ #swap(i, j) { const tmp = this.#maxHeap[i]; this.#maxHeap[i] = this.#maxHeap[j]; this.#maxHeap[j] = tmp; } /* Get heap size */ size() { return this.#maxHeap.length; } /* Check if heap is empty */ isEmpty() { return this.size() === 0; } /* Access top element */ peek() { return this.#maxHeap[0]; } /* Element enters heap */ push(val) { // Add node this.#maxHeap.push(val); // Heapify from bottom to top this.#siftUp(this.size() - 1); } /* Starting from node i, heapify from bottom to top */ #siftUp(i) { while (true) { // Get parent node of node i const p = this.#parent(i); // When "crossing root node" or "node needs no repair", end heapify if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break; // Swap two nodes this.#swap(i, p); // Loop upward heapify i = p; } } /* Element exits heap */ pop() { // Handle empty case if (this.isEmpty()) throw new Error('Heap is empty'); // Delete node this.#swap(0, this.size() - 1); // Remove node const val = this.#maxHeap.pop(); // Return top element this.#siftDown(0); // Return heap top element return val; } /* Starting from node i, heapify from top to bottom */ #siftDown(i) { while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break const l = this.#left(i), r = this.#right(i); let ma = i; if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l; if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r; // Swap two nodes if (ma === i) break; // Swap two nodes this.#swap(i, ma); // Loop downwards heapification i = ma; } } /* Driver Code */ print() { printHeap(this.#maxHeap); } /* Extract elements from heap */ getMaxHeap() { return this.#maxHeap; } } /* Driver Code */ if (require.main === module) { /* Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap */ const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); console.log('\nAfter inputting list and building heap'); maxHeap.print(); /* Check if heap is empty */ let peek = maxHeap.peek(); console.log(`\nHeap top element is ${peek}`); /* Element enters heap */ let val = 7; maxHeap.push(val); console.log(`\nAfter element ${val} pushes to heap`); maxHeap.print(); /* Time complexity is O(n), not O(nlogn) */ peek = maxHeap.pop(); console.log(`\nAfter heap top element ${peek} pops from heap`); maxHeap.print(); /* Get heap size */ let size = maxHeap.size(); console.log(`\nHeap size is ${size}`); /* Check if heap is empty */ let isEmpty = maxHeap.isEmpty(); console.log(`\nIs heap empty ${isEmpty}`); } module.exports = { MaxHeap, }; ================================================ FILE: en/codes/javascript/chapter_heap/top_k.js ================================================ /** * File: top_k.js * Created Time: 2023-08-13 * Author: Justin (xiefahit@gmail.com) */ const { MaxHeap } = require('./my_heap'); /* Element enters heap */ function pushMinHeap(maxHeap, val) { // Negate element maxHeap.push(-val); } /* Element exits heap */ function popMinHeap(maxHeap) { // Negate element return -maxHeap.pop(); } /* Access top element */ function peekMinHeap(maxHeap) { // Negate element return -maxHeap.peek(); } /* Extract elements from heap */ function getMinHeap(maxHeap) { // Negate element return maxHeap.getMaxHeap().map((num) => -num); } /* Find the largest k elements in array based on heap */ function topKHeap(nums, k) { // Python's heapq module implements min heap by default // Note: We negate all heap elements to simulate min heap using max heap const maxHeap = new MaxHeap([]); // Enter the first k elements of array into heap for (let i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // Starting from the (k+1)th element, maintain heap length as k for (let i = k; i < nums.length; i++) { // If current element is greater than top element, top element exits heap, current element enters heap if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } // Return elements in heap return getMinHeap(maxHeap); } /* Driver Code */ const nums = [1, 7, 6, 3, 2]; const k = 3; const res = topKHeap(nums, k); console.log(`The largest ${k} elements are`, res); ================================================ FILE: en/codes/javascript/chapter_searching/binary_search.js ================================================ /** * File: binary_search.js * Created Time: 2022-12-22 * Author: JoseHung (szhong@link.cuhk.edu.hk) */ /* Binary search (closed interval on both sides) */ function binarySearch(nums, target) { // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array let i = 0, j = nums.length - 1; // Loop, exit when the search interval is empty (empty when i > j) while (i <= j) { // Calculate midpoint index m, use parseInt() to round down const m = parseInt(i + (j - i) / 2); if (nums[m] < target) // This means target is in the interval [m+1, j] i = m + 1; else if (nums[m] > target) // This means target is in the interval [i, m-1] j = m - 1; else return m; // Found the target element, return its index } // Target element not found, return -1 return -1; } /* Binary search (left-closed right-open interval) */ function binarySearchLCRO(nums, target) { // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1 let i = 0, j = nums.length; // Loop, exit when the search interval is empty (empty when i = j) while (i < j) { // Calculate midpoint index m, use parseInt() to round down const m = parseInt(i + (j - i) / 2); if (nums[m] < target) // This means target is in the interval [m+1, j) i = m + 1; else if (nums[m] > target) // This means target is in the interval [i, m) j = m; // Found the target element, return its index else return m; } // Target element not found, return -1 return -1; } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* Binary search (closed interval on both sides) */ let index = binarySearch(nums, target); console.log('Index of target element 6 = ' + index); /* Binary search (left-closed right-open interval) */ index = binarySearchLCRO(nums, target); console.log('Index of target element 6 = ' + index); ================================================ FILE: en/codes/javascript/chapter_searching/binary_search_edge.js ================================================ /** * File: binary_search_edge.js * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ const { binarySearchInsertion } = require('./binary_search_insertion.js'); /* Binary search for the leftmost target */ function binarySearchLeftEdge(nums, target) { // Equivalent to finding the insertion point of target const i = binarySearchInsertion(nums, target); // Target not found, return -1 if (i === nums.length || nums[i] !== target) { return -1; } // Found target, return index i return i; } /* Binary search for the rightmost target */ function binarySearchRightEdge(nums, target) { // Convert to finding the leftmost target + 1 const i = binarySearchInsertion(nums, target + 1); // j points to the rightmost target, i points to the first element greater than target const j = i - 1; // Target not found, return -1 if (j === -1 || nums[j] !== target) { return -1; } // Found target, return index j return j; } /* Driver Code */ // Array with duplicate elements const nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\nArray nums = ' + nums); // Binary search left and right boundaries for (const target of [6, 7]) { let index = binarySearchLeftEdge(nums, target); console.log('Leftmost element ' + target + ' has index ' + index); index = binarySearchRightEdge(nums, target); console.log('Rightmost element ' + target + ' has index ' + index); } ================================================ FILE: en/codes/javascript/chapter_searching/binary_search_insertion.js ================================================ /** * File: binary_search_insertion.js * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Binary search for insertion point (no duplicate elements) */ function binarySearchInsertionSimple(nums, target) { let i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, use Math.floor() to round down if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { return m; // Found target, return insertion point m } } // Target not found, return insertion point i return i; } /* Binary search for insertion point (with duplicate elements) */ function binarySearchInsertion(nums, target) { let i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, use Math.floor() to round down if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { j = m - 1; // The first element less than target is in the interval [i, m-1] } } // Return insertion point i return i; } /* Driver Code */ // Array without duplicate elements let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; console.log('\nArray nums = ' + nums); // Binary search for insertion point for (const target of [6, 9]) { const index = binarySearchInsertionSimple(nums, target); console.log('Element ' + target + ''s insertion point index is ' + index); } // Array with duplicate elements nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\nArray nums = ' + nums); // Binary search for insertion point for (const target of [2, 6, 20]) { const index = binarySearchInsertion(nums, target); console.log('Element ' + target + ''s insertion point index is ' + index); } module.exports = { binarySearchInsertion, }; ================================================ FILE: en/codes/javascript/chapter_searching/hashing_search.js ================================================ /** * File: hashing_search.js * Created Time: 2022-12-29 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { arrToLinkedList } = require('../modules/ListNode'); /* Hash search (array) */ function hashingSearchArray(map, target) { // Hash table's key: target element, value: index // If this key does not exist in the hash table, return -1 return map.has(target) ? map.get(target) : -1; } /* Hash search (linked list) */ function hashingSearchLinkedList(map, target) { // Hash table key: target node value, value: node object // If key is not in hash table, return null return map.has(target) ? map.get(target) : null; } /* Driver Code */ const target = 3; /* Hash search (array) */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // Initialize hash table const map = new Map(); for (let i = 0; i < nums.length; i++) { map.set(nums[i], i); // key: element, value: index } const index = hashingSearchArray(map, target); console.log('Index of target element 3 = ' + index); /* Hash search (linked list) */ let head = arrToLinkedList(nums); // Initialize hash table const map1 = new Map(); while (head != null) { map1.set(head.val, head); // key: node value, value: node head = head.next; } const node = hashingSearchLinkedList(map1, target); console.log('Node object with target value 3 is', node); ================================================ FILE: en/codes/javascript/chapter_searching/linear_search.js ================================================ /** * File: linear_search.js * Created Time: 2022-12-22 * Author: JoseHung (szhong@link.cuhk.edu.hk) */ const { ListNode, arrToLinkedList } = require('../modules/ListNode'); /* Linear search (array) */ function linearSearchArray(nums, target) { // Traverse array for (let i = 0; i < nums.length; i++) { // Found the target element, return its index if (nums[i] === target) { return i; } } // Target element not found, return -1 return -1; } /* Linear search (linked list) */ function linearSearchLinkedList(head, target) { // Traverse the linked list while (head) { // Found the target node, return it if (head.val === target) { return head; } head = head.next; } // Target node not found, return null return null; } /* Driver Code */ const target = 3; /* Perform linear search in array */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; const index = linearSearchArray(nums, target); console.log('Index of target element 3 = ' + index); /* Perform linear search in linked list */ const head = arrToLinkedList(nums); const node = linearSearchLinkedList(head, target); console.log('Node object corresponding to target node value 3 is ', node); ================================================ FILE: en/codes/javascript/chapter_searching/two_sum.js ================================================ /** * File: two_sum.js * Created Time: 2022-12-15 * Author: gyt95 (gytkwan@gmail.com) */ /* Method 1: Brute force enumeration */ function twoSumBruteForce(nums, target) { const n = nums.length; // Two nested loops, time complexity is O(n^2) for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { if (nums[i] + nums[j] === target) { return [i, j]; } } } return []; } /* Method 2: Auxiliary hash table */ function twoSumHashTable(nums, target) { // Auxiliary hash table, space complexity is O(n) let m = {}; // Single loop, time complexity is O(n) for (let i = 0; i < nums.length; i++) { if (m[target - nums[i]] !== undefined) { return [m[target - nums[i]], i]; } else { m[nums[i]] = i; } } return []; } /* Driver Code */ // Method 1 const nums = [2, 7, 11, 15], target = 13; let res = twoSumBruteForce(nums, target); console.log('Method 1 res = ', res); // Method 2 res = twoSumHashTable(nums, target); console.log('Method 2 res = ', res); ================================================ FILE: en/codes/javascript/chapter_sorting/bubble_sort.js ================================================ /** * File: bubble_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Bubble sort */ function bubbleSort(nums) { // Outer loop: unsorted range is [0, i] for (let i = nums.length - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* Bubble sort (flag optimization) */ function bubbleSortWithFlag(nums) { // Outer loop: unsorted range is [0, i] for (let i = nums.length - 1; i > 0; i--) { let flag = false; // Initialize flag // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // Record element swap } } if (!flag) break; // No elements were swapped in this round of "bubbling", exit directly } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); console.log('After bubble sort, nums =', nums); const nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); console.log('After bubble sort, nums =', nums1); ================================================ FILE: en/codes/javascript/chapter_sorting/bucket_sort.js ================================================ /** * File: bucket_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* Bucket sort */ function bucketSort(nums) { // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket const k = nums.length / 2; const buckets = []; for (let i = 0; i < k; i++) { buckets.push([]); } // 1. Distribute array elements into various buckets for (const num of nums) { // Input data range is [0, 1), use num * k to map to index range [0, k-1] const i = Math.floor(num * k); // Add num to bucket i buckets[i].push(num); } // 2. Sort each bucket for (const bucket of buckets) { // Use built-in sorting function, can also replace with other sorting algorithms bucket.sort((a, b) => a - b); } // 3. Traverse buckets to merge results let i = 0; for (const bucket of buckets) { for (const num of bucket) { nums[i++] = num; } } } /* Driver Code */ const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); console.log('After bucket sort, nums =', nums); ================================================ FILE: en/codes/javascript/chapter_sorting/counting_sort.js ================================================ /** * File: counting_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* Counting sort */ // Simple implementation, cannot be used for sorting objects function countingSortNaive(nums) { // 1. Count the maximum element m in the array let m = Math.max(...nums); // 2. Count the occurrence of each number // counter[num] represents the occurrence of num const counter = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. Traverse counter, filling each element back into the original array nums let i = 0; for (let num = 0; num < m + 1; num++) { for (let j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* Counting sort */ // Complete implementation, can sort objects and is a stable sort function countingSort(nums) { // 1. Count the maximum element m in the array let m = Math.max(...nums); // 2. Count the occurrence of each number // counter[num] represents the occurrence of num const counter = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" // counter[num]-1 is the last index where num appears in res for (let i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. Traverse nums in reverse order, placing each element into the result array res // Initialize the array res to record results const n = nums.length; const res = new Array(n); for (let i = n - 1; i >= 0; i--) { const num = nums[i]; res[counter[num] - 1] = num; // Place num at the corresponding index counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num } // Use result array res to overwrite the original array nums for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Driver Code */ const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); console.log('After counting sort (cannot sort objects), nums =', nums); const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); console.log('After counting sort, nums1 =', nums1); ================================================ FILE: en/codes/javascript/chapter_sorting/heap_sort.js ================================================ /** * File: heap_sort.js * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* Heap length is n, start heapifying node i, from top to bottom */ function siftDown(nums, n, i) { while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break let l = 2 * i + 1; let r = 2 * i + 2; let ma = i; if (l < n && nums[l] > nums[ma]) { ma = l; } if (r < n && nums[r] > nums[ma]) { ma = r; } // Swap two nodes if (ma === i) { break; } // Swap two nodes [nums[i], nums[ma]] = [nums[ma], nums[i]]; // Loop downwards heapification i = ma; } } /* Heap sort */ function heapSort(nums) { // Build heap operation: heapify all nodes except leaves for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // Extract the largest element from the heap and repeat for n-1 rounds for (let i = nums.length - 1; i > 0; i--) { // Delete node [nums[0], nums[i]] = [nums[i], nums[0]]; // Start heapifying the root node, from top to bottom siftDown(nums, i, 0); } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; heapSort(nums); console.log('After heap sort, nums =', nums); ================================================ FILE: en/codes/javascript/chapter_sorting/insertion_sort.js ================================================ /** * File: insertion_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Insertion sort */ function insertionSort(nums) { // Outer loop: sorted interval is [0, i-1] for (let i = 1; i < nums.length; i++) { let base = nums[i], j = i - 1; // Inner loop: insert base into the correct position within the sorted interval [0, i-1] while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // Move nums[j] to the right by one position j--; } nums[j + 1] = base; // Assign base to the correct position } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); console.log('After insertion sort, nums =', nums); ================================================ FILE: en/codes/javascript/chapter_sorting/merge_sort.js ================================================ /** * File: merge_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Merge left subarray and right subarray */ function merge(nums, left, mid, right) { // Left subarray interval is [left, mid], right subarray interval is [mid+1, right] // Create a temporary array tmp to store the merged results const tmp = new Array(right - left + 1); // Initialize the start indices of the left and right subarrays let i = left, j = mid + 1, k = 0; // While both subarrays still have elements, compare and copy the smaller element into the temporary array while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // Copy the remaining elements of the left and right subarrays into the temporary array while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* Merge sort */ function mergeSort(nums, left, right) { // Termination condition if (left >= right) return; // Terminate recursion when subarray length is 1 // Divide and conquer stage let mid = Math.floor(left + (right - left) / 2); // Calculate midpoint mergeSort(nums, left, mid); // Recursively process the left subarray mergeSort(nums, mid + 1, right); // Recursively process the right subarray // Merge stage merge(nums, left, mid, right); } /* Driver Code */ const nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); console.log('After merge sort, nums =', nums); ================================================ FILE: en/codes/javascript/chapter_sorting/quick_sort.js ================================================ /** * File: quick_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Quick sort class */ class QuickSort { /* Swap elements */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Sentinel partition */ partition(nums, left, right) { // Use nums[left] as the pivot let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j -= 1; // Search from right to left for the first element smaller than the pivot } while (i < j && nums[i] <= nums[left]) { i += 1; // Search from left to right for the first element greater than the pivot } // Swap elements this.swap(nums, i, j); // Swap these two elements } this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort */ quickSort(nums, left, right) { // Terminate recursion when subarray length is 1 if (left >= right) return; // Sentinel partition const pivot = this.partition(nums, left, right); // Recursively process the left subarray and right subarray this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* Quick sort class (median pivot optimization) */ class QuickSortMedian { /* Swap elements */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Select the median of three candidate elements */ medianThree(nums, left, mid, right) { let l = nums[left], m = nums[mid], r = nums[right]; // m is between l and r if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // l is between m and r if ((m <= l && l <= r) || (r <= l && l <= m)) return left; return right; } /* Sentinel partition (median of three) */ partition(nums, left, right) { // Select the median of three candidate elements let med = this.medianThree( nums, left, Math.floor((left + right) / 2), right ); // Swap the median to the array's leftmost position this.swap(nums, left, med); // Use nums[left] as the pivot let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot this.swap(nums, i, j); // Swap these two elements } this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort */ quickSort(nums, left, right) { // Terminate recursion when subarray length is 1 if (left >= right) return; // Sentinel partition const pivot = this.partition(nums, left, right); // Recursively process the left subarray and right subarray this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* Quick sort class (recursion depth optimization) */ class QuickSortTailCall { /* Swap elements */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Sentinel partition */ partition(nums, left, right) { // Use nums[left] as the pivot let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot this.swap(nums, i, j); // Swap these two elements } this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort (recursion depth optimization) */ quickSort(nums, left, right) { // Terminate when subarray length is 1 while (left < right) { // Sentinel partition operation let pivot = this.partition(nums, left, right); // Perform quick sort on the shorter of the two subarrays if (pivot - left < right - pivot) { this.quickSort(nums, left, pivot - 1); // Recursively sort the left subarray left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right] } else { this.quickSort(nums, pivot + 1, right); // Recursively sort the right subarray right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1] } } } } /* Driver Code */ /* Quick sort */ const nums = [2, 4, 1, 0, 3, 5]; const quickSort = new QuickSort(); quickSort.quickSort(nums, 0, nums.length - 1); console.log('After quick sort, nums =', nums); /* Quick sort (recursion depth optimization) */ const nums1 = [2, 4, 1, 0, 3, 5]; const quickSortMedian = new QuickSortMedian(); quickSortMedian.quickSort(nums1, 0, nums1.length - 1); console.log('After quick sort (median pivot optimization), nums =', nums1); /* Quick sort (recursion depth optimization) */ const nums2 = [2, 4, 1, 0, 3, 5]; const quickSortTailCall = new QuickSortTailCall(); quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); console.log('After quick sort (recursion depth optimization), nums =', nums2); ================================================ FILE: en/codes/javascript/chapter_sorting/radix_sort.js ================================================ /** * File: radix_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* Get the k-th digit of element num, where exp = 10^(k-1) */ function digit(num, exp) { // Passing exp instead of k can avoid repeated expensive exponentiation here return Math.floor(num / exp) % 10; } /* Counting sort (based on nums k-th digit) */ function countingSortDigit(nums, exp) { // Decimal digit range is 0~9, therefore need a bucket array of length 10 const counter = new Array(10).fill(0); const n = nums.length; // Count the occurrence of digits 0~9 for (let i = 0; i < n; i++) { const d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d counter[d]++; // Count the occurrence of digit d } // Calculate prefix sum, converting "occurrence count" into "array index" for (let i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // Traverse in reverse, based on bucket statistics, place each element into res const res = new Array(n).fill(0); for (let i = n - 1; i >= 0; i--) { const d = digit(nums[i], exp); const j = counter[d] - 1; // Get the index j for d in the array res[j] = nums[i]; // Place the current element at index j counter[d]--; // Decrease the count of d by 1 } // Use result to overwrite the original array nums for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Radix sort */ function radixSort(nums) { // Get the maximum element of the array, used to determine the maximum number of digits let m = Math.max(... nums); // Traverse from the lowest to the highest digit for (let exp = 1; exp <= m; exp *= 10) { // Perform counting sort on the k-th digit of array elements // k = 1 -> exp = 1 // k = 2 -> exp = 10 // i.e., exp = 10^(k-1) countingSortDigit(nums, exp); } } /* Driver Code */ const nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radixSort(nums); console.log('After radix sort, nums =', nums); ================================================ FILE: en/codes/javascript/chapter_sorting/selection_sort.js ================================================ /** * File: selection_sort.js * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* Selection sort */ function selectionSort(nums) { let n = nums.length; // Outer loop: unsorted interval is [i, n-1] for (let i = 0; i < n - 1; i++) { // Inner loop: find the smallest element within the unsorted interval let k = i; for (let j = i + 1; j < n; j++) { if (nums[j] < nums[k]) { k = j; // Record the index of the smallest element } } // Swap the smallest element with the first element of the unsorted interval [nums[i], nums[k]] = [nums[k], nums[i]]; } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; selectionSort(nums); console.log('After selection sort, nums =', nums); ================================================ FILE: en/codes/javascript/chapter_stack_and_queue/array_deque.js ================================================ /** * File: array_deque.js * Created Time: 2023-02-28 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Double-ended queue based on circular array implementation */ class ArrayDeque { #nums; // Array for storing double-ended queue elements #front; // Front pointer, points to the front of the queue element #queSize; // Double-ended queue length /* Constructor */ constructor(capacity) { this.#nums = new Array(capacity); this.#front = 0; this.#queSize = 0; } /* Get the capacity of the double-ended queue */ capacity() { return this.#nums.length; } /* Get the length of the double-ended queue */ size() { return this.#queSize; } /* Check if the double-ended queue is empty */ isEmpty() { return this.#queSize === 0; } /* Calculate circular array index */ index(i) { // Use modulo operation to wrap the array head and tail together // When i passes the tail of the array, return to the head // When i passes the head of the array, return to the tail return (i + this.capacity()) % this.capacity(); } /* Front of the queue enqueue */ pushFirst(num) { if (this.#queSize === this.capacity()) { console.log('Double-ended queue is full'); return; } // Use modulo operation to wrap front around to the tail after passing the head of the array // Add num to the front of the queue this.#front = this.index(this.#front - 1); // Add num to front of queue this.#nums[this.#front] = num; this.#queSize++; } /* Rear of the queue enqueue */ pushLast(num) { if (this.#queSize === this.capacity()) { console.log('Double-ended queue is full'); return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array const rear = this.index(this.#front + this.#queSize); // Front pointer moves one position backward this.#nums[rear] = num; this.#queSize++; } /* Rear of the queue dequeue */ popFirst() { const num = this.peekFirst(); // Move front pointer backward by one position this.#front = this.index(this.#front + 1); this.#queSize--; return num; } /* Access rear of the queue element */ popLast() { const num = this.peekLast(); this.#queSize--; return num; } /* Return list for printing */ peekFirst() { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); return this.#nums[this.#front]; } /* Driver Code */ peekLast() { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); // Initialize double-ended queue const last = this.index(this.#front + this.#queSize - 1); return this.#nums[last]; } /* Return array for printing */ toArray() { // Elements enqueue const res = []; for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) { res[i] = this.#nums[this.index(j)]; } return res; } } /* Driver Code */ /* Get the length of the double-ended queue */ const capacity = 5; const deque = new ArrayDeque(capacity); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); console.log('Deque deque = [' + deque.toArray() + ']'); /* Update element */ const peekFirst = deque.peekFirst(); console.log('Front element peekFirst = ' + peekFirst); const peekLast = deque.peekLast(); console.log('Rear element peekLast = ' + peekLast); /* Elements enqueue */ deque.pushLast(4); console.log('After element 4 enqueues at rear, deque = [' + deque.toArray() + ']'); deque.pushFirst(1); console.log('After element 1 enqueues at front, deque = [' + deque.toArray() + ']'); /* Element dequeue */ const popLast = deque.popLast(); console.log( 'Rear dequeue element = ' + popLast + ', after rear dequeue deque = [' + deque.toArray() + ']' ); const popFirst = deque.popFirst(); console.log( 'Front dequeue element = ' + popFirst + ', after front dequeue deque = [' + deque.toArray() + ']' ); /* Get the length of the double-ended queue */ const size = deque.size(); console.log('Double-ended queue length size = ' + size); /* Check if the double-ended queue is empty */ const isEmpty = deque.isEmpty(); console.log('Double-ended queue is empty = ' + isEmpty); ================================================ FILE: en/codes/javascript/chapter_stack_and_queue/array_queue.js ================================================ /** * File: array_queue.js * Created Time: 2022-12-13 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Queue based on circular array implementation */ class ArrayQueue { #nums; // Array for storing queue elements #front = 0; // Front pointer, points to the front of the queue element #queSize = 0; // Queue length constructor(capacity) { this.#nums = new Array(capacity); } /* Get the capacity of the queue */ get capacity() { return this.#nums.length; } /* Get the length of the queue */ get size() { return this.#queSize; } /* Check if the queue is empty */ isEmpty() { return this.#queSize === 0; } /* Enqueue */ push(num) { if (this.size === this.capacity) { console.log('Queue is full'); return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array // Add num to the rear of the queue const rear = (this.#front + this.size) % this.capacity; // Front pointer moves one position backward this.#nums[rear] = num; this.#queSize++; } /* Dequeue */ pop() { const num = this.peek(); // Move front pointer backward by one position, if it passes the tail, return to array head this.#front = (this.#front + 1) % this.capacity; this.#queSize--; return num; } /* Return list for printing */ peek() { if (this.isEmpty()) throw new Error('Queue is empty'); return this.#nums[this.#front]; } /* Return Array */ toArray() { // Elements enqueue const arr = new Array(this.size); for (let i = 0, j = this.#front; i < this.size; i++, j++) { arr[i] = this.#nums[j % this.capacity]; } return arr; } } /* Driver Code */ /* Access front of the queue element */ const capacity = 10; const queue = new ArrayQueue(capacity); /* Elements enqueue */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('Queue queue =', queue.toArray()); /* Return list for printing */ const peek = queue.peek(); console.log('Front element peek = ' + peek); /* Element dequeue */ const pop = queue.pop(); console.log('Dequeue element pop = ' + pop + ', after dequeue queue =', queue.toArray()); /* Get the length of the queue */ const size = queue.size; console.log('Queue length size = ' + size); /* Check if the queue is empty */ const isEmpty = queue.isEmpty(); console.log('Queue is empty = ' + isEmpty); /* Test circular array */ for (let i = 0; i < 10; i++) { queue.push(i); queue.pop(); console.log('Round ' + i + ' rounds of enqueue + dequeue, queue =', queue.toArray()); } ================================================ FILE: en/codes/javascript/chapter_stack_and_queue/array_stack.js ================================================ /** * File: array_stack.js * Created Time: 2022-12-09 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Stack based on array implementation */ class ArrayStack { #stack; constructor() { this.#stack = []; } /* Get the length of the stack */ get size() { return this.#stack.length; } /* Check if the stack is empty */ isEmpty() { return this.#stack.length === 0; } /* Push */ push(num) { this.#stack.push(num); } /* Pop */ pop() { if (this.isEmpty()) throw new Error('Stack is empty'); return this.#stack.pop(); } /* Return list for printing */ top() { if (this.isEmpty()) throw new Error('Stack is empty'); return this.#stack[this.#stack.length - 1]; } /* Return Array */ toArray() { return this.#stack; } } /* Driver Code */ /* Access top of the stack element */ const stack = new ArrayStack(); /* Elements push onto stack */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('Stack stack = '); console.log(stack.toArray()); /* Return list for printing */ const top = stack.top(); console.log('Stack top element top = ' + top); /* Element pop from stack */ const pop = stack.pop(); console.log('Pop element pop = ' + pop + ', after pop, stack = '); console.log(stack.toArray()); /* Get the length of the stack */ const size = stack.size; console.log('Stack length size = ' + size); /* Check if empty */ const isEmpty = stack.isEmpty(); console.log('Stack is empty = ' + isEmpty); ================================================ FILE: en/codes/javascript/chapter_stack_and_queue/deque.js ================================================ /** * File: deque.js * Created Time: 2023-01-17 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Driver Code */ /* Get the length of the double-ended queue */ // JavaScript has no built-in deque, can only use Array as deque const deque = []; /* Elements enqueue */ deque.push(2); deque.push(5); deque.push(4); // Note: due to array, unshift() method has O(n) time complexity deque.unshift(3); deque.unshift(1); console.log('Double-ended queue deque = ', deque); /* Update element */ const peekFirst = deque[0]; console.log('Front element peekFirst = ' + peekFirst); const peekLast = deque[deque.length - 1]; console.log('Rear element peekLast = ' + peekLast); /* Element dequeue */ // Note: due to array, shift() method has O(n) time complexity const popFront = deque.shift(); console.log( 'Front dequeue element popFront = ' + popFront + ', after front dequeue, deque = ' + deque ); const popBack = deque.pop(); console.log( 'Dequeue rear element popBack = ' + popBack + ', after rear dequeue, deque = ' + deque ); /* Get the length of the double-ended queue */ const size = deque.length; console.log('Double-ended queue length size = ' + size); /* Check if the double-ended queue is empty */ const isEmpty = size === 0; console.log('Double-ended queue is empty = ' + isEmpty); ================================================ FILE: en/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js ================================================ /** * File: linkedlist_deque.js * Created Time: 2023-02-04 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Doubly linked list node */ class ListNode { prev; // Predecessor node reference (pointer) next; // Successor node reference (pointer) val; // Node value constructor(val) { this.val = val; this.next = null; this.prev = null; } } /* Double-ended queue based on doubly linked list implementation */ class LinkedListDeque { #front; // Head node front #rear; // Tail node rear #queSize; // Length of the double-ended queue constructor() { this.#front = null; this.#rear = null; this.#queSize = 0; } /* Rear of the queue enqueue operation */ pushLast(val) { const node = new ListNode(val); // If the linked list is empty, make both front and rear point to node if (this.#queSize === 0) { this.#front = node; this.#rear = node; } else { // Add node to the tail of the linked list this.#rear.next = node; node.prev = this.#rear; this.#rear = node; // Update tail node } this.#queSize++; } /* Front of the queue enqueue operation */ pushFirst(val) { const node = new ListNode(val); // If the linked list is empty, make both front and rear point to node if (this.#queSize === 0) { this.#front = node; this.#rear = node; } else { // Add node to the head of the linked list this.#front.prev = node; node.next = this.#front; this.#front = node; // Update head node } this.#queSize++; } /* Temporarily store tail node value */ popLast() { if (this.#queSize === 0) { return null; } const value = this.#rear.val; // Store tail node value // Update tail node let temp = this.#rear.prev; if (temp !== null) { temp.next = null; this.#rear.prev = null; } this.#rear = temp; // Update tail node this.#queSize--; return value; } /* Temporarily store head node value */ popFirst() { if (this.#queSize === 0) { return null; } const value = this.#front.val; // Store tail node value // Delete head node let temp = this.#front.next; if (temp !== null) { temp.prev = null; this.#front.next = null; } this.#front = temp; // Update head node this.#queSize--; return value; } /* Driver Code */ peekLast() { return this.#queSize === 0 ? null : this.#rear.val; } /* Return list for printing */ peekFirst() { return this.#queSize === 0 ? null : this.#front.val; } /* Get the length of the double-ended queue */ size() { return this.#queSize; } /* Check if the double-ended queue is empty */ isEmpty() { return this.#queSize === 0; } /* Print deque */ print() { const arr = []; let temp = this.#front; while (temp !== null) { arr.push(temp.val); temp = temp.next; } console.log('[' + arr.join(', ') + ']'); } } /* Driver Code */ /* Get the length of the double-ended queue */ const linkedListDeque = new LinkedListDeque(); linkedListDeque.pushLast(3); linkedListDeque.pushLast(2); linkedListDeque.pushLast(5); console.log('Deque linkedListDeque = '); linkedListDeque.print(); /* Update element */ const peekFirst = linkedListDeque.peekFirst(); console.log('Front element peekFirst = ' + peekFirst); const peekLast = linkedListDeque.peekLast(); console.log('Rear element peekLast = ' + peekLast); /* Elements enqueue */ linkedListDeque.pushLast(4); console.log('After element 4 enqueues at rear, linkedListDeque = '); linkedListDeque.print(); linkedListDeque.pushFirst(1); console.log('After element 1 enqueues at front, linkedListDeque = '); linkedListDeque.print(); /* Element dequeue */ const popLast = linkedListDeque.popLast(); console.log('Rear dequeue element = ' + popLast + ', after rear dequeue linkedListDeque = '); linkedListDeque.print(); const popFirst = linkedListDeque.popFirst(); console.log('Front dequeue element = ' + popFirst + ', after front dequeue linkedListDeque = '); linkedListDeque.print(); /* Get the length of the double-ended queue */ const size = linkedListDeque.size(); console.log('Double-ended queue length size = ' + size); /* Check if the double-ended queue is empty */ const isEmpty = linkedListDeque.isEmpty(); console.log('Double-ended queue is empty = ' + isEmpty); ================================================ FILE: en/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js ================================================ /** * File: linkedlist_queue.js * Created Time: 2022-12-20 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ const { ListNode } = require('../modules/ListNode'); /* Queue based on linked list implementation */ class LinkedListQueue { #front; // Front node #front #rear; // Rear node #rear #queSize = 0; constructor() { this.#front = null; this.#rear = null; } /* Get the length of the queue */ get size() { return this.#queSize; } /* Check if the queue is empty */ isEmpty() { return this.size === 0; } /* Enqueue */ push(num) { // Add num after the tail node const node = new ListNode(num); // If the queue is empty, make both front and rear point to the node if (!this.#front) { this.#front = node; this.#rear = node; // If the queue is not empty, add the node after the tail node } else { this.#rear.next = node; this.#rear = node; } this.#queSize++; } /* Dequeue */ pop() { const num = this.peek(); // Delete head node this.#front = this.#front.next; this.#queSize--; return num; } /* Return list for printing */ peek() { if (this.size === 0) throw new Error('Queue is empty'); return this.#front.val; } /* Convert linked list to Array and return */ toArray() { let node = this.#front; const res = new Array(this.size); for (let i = 0; i < res.length; i++) { res[i] = node.val; node = node.next; } return res; } } /* Driver Code */ /* Access front of the queue element */ const queue = new LinkedListQueue(); /* Elements enqueue */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('Queue queue = ' + queue.toArray()); /* Return list for printing */ const peek = queue.peek(); console.log('Front element peek = ' + peek); /* Element dequeue */ const pop = queue.pop(); console.log('Dequeue element pop = ' + pop + ', after dequeue, queue = ' + queue.toArray()); /* Get the length of the queue */ const size = queue.size; console.log('Queue length size = ' + size); /* Check if the queue is empty */ const isEmpty = queue.isEmpty(); console.log('Queue is empty = ' + isEmpty); ================================================ FILE: en/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js ================================================ /** * File: linkedlist_stack.js * Created Time: 2022-12-22 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ const { ListNode } = require('../modules/ListNode'); /* Stack based on linked list implementation */ class LinkedListStack { #stackPeek; // Use head node as stack top #stkSize = 0; // Stack length constructor() { this.#stackPeek = null; } /* Get the length of the stack */ get size() { return this.#stkSize; } /* Check if the stack is empty */ isEmpty() { return this.size === 0; } /* Push */ push(num) { const node = new ListNode(num); node.next = this.#stackPeek; this.#stackPeek = node; this.#stkSize++; } /* Pop */ pop() { const num = this.peek(); this.#stackPeek = this.#stackPeek.next; this.#stkSize--; return num; } /* Return list for printing */ peek() { if (!this.#stackPeek) throw new Error('Stack is empty'); return this.#stackPeek.val; } /* Convert linked list to Array and return */ toArray() { let node = this.#stackPeek; const res = new Array(this.size); for (let i = res.length - 1; i >= 0; i--) { res[i] = node.val; node = node.next; } return res; } } /* Driver Code */ /* Access top of the stack element */ const stack = new LinkedListStack(); /* Elements push onto stack */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('Stack stack = ' + stack.toArray()); /* Return list for printing */ const peek = stack.peek(); console.log('Stack top element peek = ' + peek); /* Element pop from stack */ const pop = stack.pop(); console.log('Pop element pop = ' + pop + ', after pop, stack = ' + stack.toArray()); /* Get the length of the stack */ const size = stack.size; console.log('Stack length size = ' + size); /* Check if empty */ const isEmpty = stack.isEmpty(); console.log('Stack is empty = ' + isEmpty); ================================================ FILE: en/codes/javascript/chapter_stack_and_queue/queue.js ================================================ /** * File: queue.js * Created Time: 2022-12-05 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* Access front of the queue element */ // JavaScript has no built-in queue, can use Array as queue const queue = []; /* Elements enqueue */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('Queue queue =', queue); /* Return list for printing */ const peek = queue[0]; console.log('Front element peek =', peek); /* Element dequeue */ // Underlying is array, so shift() method has O(n) time complexity const pop = queue.shift(); console.log('Dequeue element pop =', pop, ', after dequeue, queue = ', queue); /* Get the length of the queue */ const size = queue.length; console.log('Queue length size =', size); /* Check if the queue is empty */ const isEmpty = queue.length === 0; console.log('Queue is empty = ', isEmpty); ================================================ FILE: en/codes/javascript/chapter_stack_and_queue/stack.js ================================================ /** * File: stack.js * Created Time: 2022-12-04 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* Access top of the stack element */ // JavaScript has no built-in stack class, can use Array as stack const stack = []; /* Elements push onto stack */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('Stack stack =', stack); /* Return list for printing */ const peek = stack[stack.length - 1]; console.log('Stack top element peek =', peek); /* Element pop from stack */ const pop = stack.pop(); console.log('Pop element pop =', pop); console.log('After pop, stack =', stack); /* Get the length of the stack */ const size = stack.length; console.log('Stack length size =', size); /* Check if empty */ const isEmpty = stack.length === 0; console.log('Is stack empty =', isEmpty); ================================================ FILE: en/codes/javascript/chapter_tree/array_binary_tree.js ================================================ /** * File: array_binary_tree.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Binary tree class represented by array */ class ArrayBinaryTree { #tree; /* Constructor */ constructor(arr) { this.#tree = arr; } /* List capacity */ size() { return this.#tree.length; } /* Get value of node at index i */ val(i) { // If index out of bounds, return null to represent empty position if (i < 0 || i >= this.size()) return null; return this.#tree[i]; } /* Get index of left child node of node at index i */ left(i) { return 2 * i + 1; } /* Get index of right child node of node at index i */ right(i) { return 2 * i + 2; } /* Get index of parent node of node at index i */ parent(i) { return Math.floor((i - 1) / 2); // Floor division } /* Level-order traversal */ levelOrder() { let res = []; // Traverse array directly for (let i = 0; i < this.size(); i++) { if (this.val(i) !== null) res.push(this.val(i)); } return res; } /* Depth-first traversal */ #dfs(i, order, res) { // If empty position, return if (this.val(i) === null) return; // Preorder traversal if (order === 'pre') res.push(this.val(i)); this.#dfs(this.left(i), order, res); // Inorder traversal if (order === 'in') res.push(this.val(i)); this.#dfs(this.right(i), order, res); // Postorder traversal if (order === 'post') res.push(this.val(i)); } /* Preorder traversal */ preOrder() { const res = []; this.#dfs(0, 'pre', res); return res; } /* Inorder traversal */ inOrder() { const res = []; this.#dfs(0, 'in', res); return res; } /* Postorder traversal */ postOrder() { const res = []; this.#dfs(0, 'post', res); return res; } } /* Driver Code */ // Initialize binary tree // Here we use a function to generate a binary tree directly from an array const arr = Array.of( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ); const root = arrToTree(arr); console.log('\nInitialize binary tree\n'); console.log('Array representation of binary tree:'); console.log(arr); console.log('Linked list representation of binary tree:'); printTree(root); // Binary tree class represented by array const abt = new ArrayBinaryTree(arr); // Access node const i = 1; const l = abt.left(i); const r = abt.right(i); const p = abt.parent(i); console.log('\nCurrent node index is ' + i + ', value is ' + abt.val(i)); console.log( 'Its left child node index is ' + l + ', value is ' + (l === null ? 'null' : abt.val(l)) ); console.log( 'Its right child node index is ' + r + ', value is ' + (r === null ? 'null' : abt.val(r)) ); console.log( 'Its parent node index is ' + p + ', value is ' + (p === null ? 'null' : abt.val(p)) ); // Traverse tree let res = abt.levelOrder(); console.log('\nLevel-order traversal is:' + res); res = abt.preOrder(); console.log('Preorder traversal is:' + res); res = abt.inOrder(); console.log('Inorder traversal is:' + res); res = abt.postOrder(); console.log('Postorder traversal is:' + res); ================================================ FILE: en/codes/javascript/chapter_tree/avl_tree.js ================================================ /** * File: avl_tree.js * Created Time: 2023-02-05 * Author: what-is-me (whatisme@outlook.jp) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* AVL tree */ class AVLTree { /* Constructor */ constructor() { this.root = null; // Root node } /* Get node height */ height(node) { // Empty node height is -1, leaf node height is 0 return node === null ? -1 : node.height; } /* Update node height */ #updateHeight(node) { // Node height equals the height of the tallest subtree + 1 node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; } /* Get balance factor */ balanceFactor(node) { // Empty node balance factor is 0 if (node === null) return 0; // Node balance factor = left subtree height - right subtree height return this.height(node.left) - this.height(node.right); } /* Right rotation operation */ #rightRotate(node) { const child = node.left; const grandChild = child.right; // Using child as pivot, rotate node to the right child.right = node; node.left = grandChild; // Update node height this.#updateHeight(node); this.#updateHeight(child); // Return root node of subtree after rotation return child; } /* Left rotation operation */ #leftRotate(node) { const child = node.right; const grandChild = child.left; // Using child as pivot, rotate node to the left child.left = node; node.right = grandChild; // Update node height this.#updateHeight(node); this.#updateHeight(child); // Return root node of subtree after rotation return child; } /* Perform rotation operation to restore balance to this subtree */ #rotate(node) { // Get balance factor of node const balanceFactor = this.balanceFactor(node); // Left-leaning tree if (balanceFactor > 1) { if (this.balanceFactor(node.left) >= 0) { // Right rotation return this.#rightRotate(node); } else { // First left rotation then right rotation node.left = this.#leftRotate(node.left); return this.#rightRotate(node); } } // Right-leaning tree if (balanceFactor < -1) { if (this.balanceFactor(node.right) <= 0) { // Left rotation return this.#leftRotate(node); } else { // First right rotation then left rotation node.right = this.#rightRotate(node.right); return this.#leftRotate(node); } } // Balanced tree, no rotation needed, return directly return node; } /* Insert node */ insert(val) { this.root = this.#insertHelper(this.root, val); } /* Recursively insert node (helper method) */ #insertHelper(node, val) { if (node === null) return new TreeNode(val); /* 1. Find insertion position and insert node */ if (val < node.val) node.left = this.#insertHelper(node.left, val); else if (val > node.val) node.right = this.#insertHelper(node.right, val); else return node; // Duplicate node not inserted, return directly this.#updateHeight(node); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = this.#rotate(node); // Return root node of subtree return node; } /* Remove node */ remove(val) { this.root = this.#removeHelper(this.root, val); } /* Recursively delete node (helper method) */ #removeHelper(node, val) { if (node === null) return null; /* 1. Find node and delete */ if (val < node.val) node.left = this.#removeHelper(node.left, val); else if (val > node.val) node.right = this.#removeHelper(node.right, val); else { if (node.left === null || node.right === null) { const child = node.left !== null ? node.left : node.right; // Number of child nodes = 0, delete node directly and return if (child === null) return null; // Number of child nodes = 1, delete node directly else node = child; } else { // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it let temp = node.right; while (temp.left !== null) { temp = temp.left; } node.right = this.#removeHelper(node.right, temp.val); node.val = temp.val; } } this.#updateHeight(node); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = this.#rotate(node); // Return root node of subtree return node; } /* Search node */ search(val) { let cur = this.root; // Loop search, exit after passing leaf node while (cur !== null) { // Target node is in cur's right subtree if (cur.val < val) cur = cur.right; // Target node is in cur's left subtree else if (cur.val > val) cur = cur.left; // Found target node, exit loop else break; } // Return target node return cur; } } function testInsert(tree, val) { tree.insert(val); console.log('\nInsert node ' + val + ', AVL tree is'); printTree(tree.root); } function testRemove(tree, val) { tree.remove(val); console.log('\nRemove node ' + val + ', AVL tree is'); printTree(tree.root); } /* Driver Code */ /* Please pay attention to how the AVL tree maintains balance after inserting nodes */ const avlTree = new AVLTree(); /* Insert node */ // Delete nodes 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); /* Please pay attention to how the AVL tree maintains balance after deleting nodes */ testInsert(avlTree, 7); /* Remove node */ // Delete node with degree 1 testRemove(avlTree, 8); // Delete node with degree 2 testRemove(avlTree, 5); // Remove node with degree 1 testRemove(avlTree, 4); // Remove node with degree 2 /* Search node */ const node = avlTree.search(7); console.log('\nFound node object is', node, ', node value = ' + node.val); ================================================ FILE: en/codes/javascript/chapter_tree/binary_search_tree.js ================================================ /** * File: binary_search_tree.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Binary search tree */ class BinarySearchTree { /* Constructor */ constructor() { // Initialize empty tree this.root = null; } /* Get binary tree root node */ getRoot() { return this.root; } /* Search node */ search(num) { let cur = this.root; // Loop search, exit after passing leaf node while (cur !== null) { // Target node is in cur's right subtree if (cur.val < num) cur = cur.right; // Target node is in cur's left subtree else if (cur.val > num) cur = cur.left; // Found target node, exit loop else break; } // Return target node return cur; } /* Insert node */ insert(num) { // If tree is empty, initialize root node if (this.root === null) { this.root = new TreeNode(num); return; } let cur = this.root, pre = null; // Loop search, exit after passing leaf node while (cur !== null) { // Found duplicate node, return directly if (cur.val === num) return; pre = cur; // Insertion position is in cur's right subtree if (cur.val < num) cur = cur.right; // Insertion position is in cur's left subtree else cur = cur.left; } // Insert node const node = new TreeNode(num); if (pre.val < num) pre.right = node; else pre.left = node; } /* Remove node */ remove(num) { // If tree is empty, return directly if (this.root === null) return; let cur = this.root, pre = null; // Loop search, exit after passing leaf node while (cur !== null) { // Found node to delete, exit loop if (cur.val === num) break; pre = cur; // Node to delete is in cur's right subtree if (cur.val < num) cur = cur.right; // Node to delete is in cur's left subtree else cur = cur.left; } // If no node to delete, return directly if (cur === null) return; // Number of child nodes = 0 or 1 if (cur.left === null || cur.right === null) { // When number of child nodes = 0 / 1, child = null / that child node const child = cur.left !== null ? cur.left : cur.right; // Delete node cur if (cur !== this.root) { if (pre.left === cur) pre.left = child; else pre.right = child; } else { // If deleted node is root node, reassign root node this.root = child; } } // Number of child nodes = 2 else { // Get next node of cur in inorder traversal let tmp = cur.right; while (tmp.left !== null) { tmp = tmp.left; } // Recursively delete node tmp this.remove(tmp.val); // Replace cur with tmp cur.val = tmp.val; } } } /* Driver Code */ /* Initialize binary search tree */ const bst = new BinarySearchTree(); // Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (const num of nums) { bst.insert(num); } console.log('\nInitialized binary tree is\n'); printTree(bst.getRoot()); /* Search node */ const node = bst.search(7); console.log('\nFound node object is ' + node + ', node value = ' + node.val); /* Insert node */ bst.insert(16); console.log('\nAfter inserting node 16, binary tree is\n'); printTree(bst.getRoot()); /* Remove node */ bst.remove(1); console.log('\nAfter removing node 1, binary tree is\n'); printTree(bst.getRoot()); bst.remove(2); console.log('\nAfter removing node 2, binary tree is\n'); printTree(bst.getRoot()); bst.remove(4); console.log('\nAfter removing node 4, binary tree is\n'); printTree(bst.getRoot()); ================================================ FILE: en/codes/javascript/chapter_tree/binary_tree.js ================================================ /** * File: binary_tree.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Initialize binary tree */ // Initialize nodes let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // Build references (pointers) between nodes n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; console.log('\nInitialize binary tree\n'); printTree(n1); /* Insert node P between n1 -> n2 */ const P = new TreeNode(0); // Delete node n1.left = P; P.left = n2; console.log('\nAfter inserting node P\n'); printTree(n1); // Remove node P n1.left = n2; console.log('\nAfter removing node P\n'); printTree(n1); ================================================ FILE: en/codes/javascript/chapter_tree/binary_tree_bfs.js ================================================ /** * File: binary_tree_bfs.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Level-order traversal */ function levelOrder(root) { // Initialize queue, add root node const queue = [root]; // Initialize a list to save the traversal sequence const list = []; while (queue.length) { let node = queue.shift(); // Dequeue list.push(node.val); // Save node value if (node.left) queue.push(node.left); // Left child node enqueue if (node.right) queue.push(node.right); // Right child node enqueue } return list; } /* Driver Code */ /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\nInitialize binary tree\n'); printTree(root); /* Level-order traversal */ const list = levelOrder(root); console.log('\nLevel-order traversal node print sequence = ' + list); ================================================ FILE: en/codes/javascript/chapter_tree/binary_tree_dfs.js ================================================ /** * File: binary_tree_dfs.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); // Initialize list for storing traversal sequence const list = []; /* Preorder traversal */ function preOrder(root) { if (root === null) return; // Visit priority: root node -> left subtree -> right subtree list.push(root.val); preOrder(root.left); preOrder(root.right); } /* Inorder traversal */ function inOrder(root) { if (root === null) return; // Visit priority: left subtree -> root node -> right subtree inOrder(root.left); list.push(root.val); inOrder(root.right); } /* Postorder traversal */ function postOrder(root) { if (root === null) return; // Visit priority: left subtree -> right subtree -> root node postOrder(root.left); postOrder(root.right); list.push(root.val); } /* Driver Code */ /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\nInitialize binary tree\n'); printTree(root); /* Preorder traversal */ list.length = 0; preOrder(root); console.log('\nPreorder traversal node print sequence = ' + list); /* Inorder traversal */ list.length = 0; inOrder(root); console.log('\nInorder traversal node print sequence = ' + list); /* Postorder traversal */ list.length = 0; postOrder(root); console.log('\nPostorder traversal node print sequence = ' + list); ================================================ FILE: en/codes/javascript/modules/ListNode.js ================================================ /** * File: ListNode.js * Created Time: 2022-12-12 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Linked list node */ class ListNode { val; // Node value next; // Reference (pointer) to next node constructor(val, next) { this.val = val === undefined ? 0 : val; this.next = next === undefined ? null : next; } } /* Deserialize a list into a linked list */ function arrToLinkedList(arr) { const dum = new ListNode(0); let head = dum; for (const val of arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } module.exports = { ListNode, arrToLinkedList, }; ================================================ FILE: en/codes/javascript/modules/PrintUtil.js ================================================ /** * File: PrintUtil.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('./TreeNode'); /* Print linked list */ function printLinkedList(head) { let list = []; while (head !== null) { list.push(head.val.toString()); head = head.next; } console.log(list.join(' -> ')); } function Trunk(prev, str) { this.prev = prev; this.str = str; } /** * Print binary tree * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ function printTree(root) { printTree(root, null, false); } /* Print binary tree */ function printTree(root, prev, isRight) { if (root === null) { return; } let prev_str = ' '; let trunk = new Trunk(prev, prev_str); printTree(root.right, trunk, true); if (!prev) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); console.log(' ' + root.val); if (prev) { prev.str = prev_str; } trunk.str = ' |'; printTree(root.left, trunk, false); } function showTrunks(p) { if (!p) { return; } showTrunks(p.prev); process.stdout.write(p.str); } /* Print heap */ function printHeap(arr) { console.log('Heap array representation:'); console.log(arr); console.log('Heap tree representation:'); printTree(arrToTree(arr)); } module.exports = { printLinkedList, printTree, printHeap, }; ================================================ FILE: en/codes/javascript/modules/TreeNode.js ================================================ /** * File: TreeNode.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Binary tree node */ class TreeNode { val; // Node value left; // Left child pointer right; // Right child pointer height; // Node height constructor(val, left, right, height) { this.val = val === undefined ? 0 : val; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; this.height = height === undefined ? 0 : height; } } /* Deserialize array to binary tree */ function arrToTree(arr, i = 0) { if (i < 0 || i >= arr.length || arr[i] === null) { return null; } let root = new TreeNode(arr[i]); root.left = arrToTree(arr, 2 * i + 1); root.right = arrToTree(arr, 2 * i + 2); return root; } module.exports = { TreeNode, arrToTree, }; ================================================ FILE: en/codes/javascript/modules/Vertex.js ================================================ /** * File: Vertex.js * Created Time: 2023-02-15 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Vertex class */ class Vertex { val; constructor(val) { this.val = val; } /* Input value list vals, return vertex list vets */ static valsToVets(vals) { const vets = []; for (let i = 0; i < vals.length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* Input vertex list vets, return value list vals */ static vetsToVals(vets) { const vals = []; for (const vet of vets) { vals.push(vet.val); } return vals; } } module.exports = { Vertex, }; ================================================ FILE: en/codes/javascript/test_all.js ================================================ import { bold, brightRed } from 'jsr:@std/fmt/colors'; import { expandGlob } from 'jsr:@std/fs'; import { relative, resolve } from 'jsr:@std/path'; /** * @typedef {import('jsr:@std/fs').WalkEntry} WalkEntry * @type {WalkEntry[]} */ const entries = []; for await (const entry of expandGlob( resolve(import.meta.dirname, './chapter_*/*.js') )) { entries.push(entry); } /** @type {{ status: Promise; stderr: ReadableStream; }[]} */ const processes = []; for (const file of entries) { const execute = new Deno.Command('node', { args: [relative(import.meta.dirname, file.path)], cwd: import.meta.dirname, stdin: 'piped', stdout: 'piped', stderr: 'piped', }); const process = execute.spawn(); processes.push({ status: process.status, stderr: process.stderr }); } const results = await Promise.all( processes.map(async (item) => { const status = await item.status; return { status, stderr: item.stderr }; }) ); /** @type {ReadableStream[]} */ const errors = []; for (const result of results) { if (!result.status.success) { errors.push(result.stderr); } } console.log(`Tested ${entries.length} files`); console.log(`Found exception in ${errors.length} files`); if (errors.length) { console.log(); for (const error of errors) { const reader = error.getReader(); const { value } = await reader.read(); const decoder = new TextDecoder(); console.log(`${bold(brightRed('error'))}: ${decoder.decode(value)}`); } throw new Error('Test failed'); } ================================================ FILE: en/codes/kotlin/chapter_array_and_linkedlist/array.kt ================================================ /** * File: array.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_array_and_linkedlist import java.util.concurrent.ThreadLocalRandom /* Random access to element */ fun randomAccess(nums: IntArray): Int { // Randomly select a number in interval [0, nums.size) val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size) // Retrieve and return the random element val randomNum = nums[randomIndex] return randomNum } /* Extend array length */ fun extend(nums: IntArray, enlarge: Int): IntArray { // Initialize an array with extended length val res = IntArray(nums.size + enlarge) // Copy all elements from the original array to the new array for (i in nums.indices) { res[i] = nums[i] } // Return the extended new array return res } /* Insert element num at index index in the array */ fun insert(nums: IntArray, num: Int, index: Int) { // Move all elements at and after index index backward by one position for (i in nums.size - 1 downTo index + 1) { nums[i] = nums[i - 1] } // Assign num to the element at index index nums[index] = num } /* Remove the element at index index */ fun remove(nums: IntArray, index: Int) { // Move all elements after index index forward by one position for (i in index.. P -> n1 val p = n0.next val n1 = p?.next n0.next = n1 } /* Access the node at index index in the linked list */ fun access(head: ListNode?, index: Int): ListNode? { var h = head for (i in 0..= size) throw IndexOutOfBoundsException("Index out of bounds") return arr[index] } /* Add elements at the end */ fun set(index: Int, num: Int) { if (index < 0 || index >= size) throw IndexOutOfBoundsException("Index out of bounds") arr[index] = num } /* Direct traversal of list elements */ fun add(num: Int) { // When the number of elements exceeds capacity, trigger the extension mechanism if (size == capacity()) extendCapacity() arr[size] = num // Update the number of elements size++ } /* Sort list */ fun insert(index: Int, num: Int) { if (index < 0 || index >= size) throw IndexOutOfBoundsException("Index out of bounds") // When the number of elements exceeds capacity, trigger the extension mechanism if (size == capacity()) extendCapacity() // Move all elements after index index forward by one position for (j in size - 1 downTo index) arr[j + 1] = arr[j] arr[index] = num // Update the number of elements size++ } /* Remove element */ fun remove(index: Int): Int { if (index < 0 || index >= size) throw IndexOutOfBoundsException("Index out of bounds") val num = arr[index] // Move all elements after index forward by one position for (j in index..>, res: MutableList>?>, cols: BooleanArray, diags1: BooleanArray, diags2: BooleanArray ) { // When all rows are placed, record the solution if (row == n) { val copyState = mutableListOf>() for (sRow in state) { copyState.add(sRow.toMutableList()) } res.add(copyState) return } // Traverse all columns for (col in 0..>?> { // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell val state = mutableListOf>() for (i in 0..() for (j in 0..>?>() backtrack(0, n, state, res, cols, diags1, diags2) return res } /* Driver Code */ fun main() { val n = 4 val res = nQueens(n) println("Input board size is $n") println("Total queen placement solutions: ${res.size}") for (state in res) { println("--------------------") for (row in state!!) { println(row) } } } ================================================ FILE: en/codes/kotlin/chapter_backtracking/permutations_i.kt ================================================ /** * File: permutations_i.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.permutations_i /* Backtracking algorithm: Permutations I */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // When the state length equals the number of elements, record the solution if (state.size == choices.size) { res.add(state.toMutableList()) return } // Traverse all choices for (i in choices.indices) { val choice = choices[i] // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true state.add(choice) // Proceed to the next round of selection backtrack(state, choices, selected, res) // Backtrack: undo choice, restore to previous state selected[i] = false state.removeAt(state.size - 1) } } } /* Permutations I */ fun permutationsI(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(1, 2, 3) val res = permutationsI(nums) println("Input array nums = ${nums.contentToString()}") println("All permutations res = $res") } ================================================ FILE: en/codes/kotlin/chapter_backtracking/permutations_ii.kt ================================================ /** * File: permutations_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.permutations_ii /* Backtracking algorithm: Permutations II */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // When the state length equals the number of elements, record the solution if (state.size == choices.size) { res.add(state.toMutableList()) return } // Traverse all choices val duplicated = HashSet() for (i in choices.indices) { val choice = choices[i] // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated.contains(choice)) { // Attempt: make choice, update state duplicated.add(choice) // Record the selected element value selected[i] = true state.add(choice) // Proceed to the next round of selection backtrack(state, choices, selected, res) // Backtrack: undo choice, restore to previous state selected[i] = false state.removeAt(state.size - 1) } } } /* Permutations II */ fun permutationsII(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(1, 2, 2) val res = permutationsII(nums) println("Input array nums = ${nums.contentToString()}") println("All permutations res = $res") } ================================================ FILE: en/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt ================================================ /** * File: preorder_traversal_i_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_i_compact import utils.TreeNode import utils.printTree var res: MutableList? = null /* Preorder traversal: Example 1 */ fun preOrder(root: TreeNode?) { if (root == null) { return } if (root._val == 7) { // Record solution res!!.add(root) } preOrder(root.left) preOrder(root.right) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\nInitialize binary tree") printTree(root) // Preorder traversal res = mutableListOf() preOrder(root) println("\nOutput all nodes with value 7") val vals = mutableListOf() for (node in res!!) { vals.add(node._val) } println(vals) } ================================================ FILE: en/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt ================================================ /** * File: preorder_traversal_ii_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_ii_compact import utils.TreeNode import utils.printTree var path: MutableList? = null var res: MutableList>? = null /* Preorder traversal: Example 2 */ fun preOrder(root: TreeNode?) { if (root == null) { return } // Attempt path!!.add(root) if (root._val == 7) { // Record solution res!!.add(path!!.toMutableList()) } preOrder(root.left) preOrder(root.right) // Backtrack path!!.removeAt(path!!.size - 1) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\nInitialize binary tree") printTree(root) // Preorder traversal path = mutableListOf() res = mutableListOf() preOrder(root) println("\nOutput all paths from root node to node 7") for (path in res!!) { val _vals = mutableListOf() for (node in path) { _vals.add(node._val) } println(_vals) } } ================================================ FILE: en/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt ================================================ /** * File: preorder_traversal_iii_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_iii_compact import utils.TreeNode import utils.printTree var path: MutableList? = null var res: MutableList>? = null /* Preorder traversal: Example 3 */ fun preOrder(root: TreeNode?) { // Pruning if (root == null || root._val == 3) { return } // Attempt path!!.add(root) if (root._val == 7) { // Record solution res!!.add(path!!.toMutableList()) } preOrder(root.left) preOrder(root.right) // Backtrack path!!.removeAt(path!!.size - 1) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\nInitialize binary tree") printTree(root) // Preorder traversal path = mutableListOf() res = mutableListOf() preOrder(root) println("\nOutput all paths from root node to node 7, paths do not include nodes with value 3") for (path in res!!) { val _vals = mutableListOf() for (node in path) { _vals.add(node._val) } println(_vals) } } ================================================ FILE: en/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt ================================================ /** * File: preorder_traversal_iii_template.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_iii_template import utils.TreeNode import utils.printTree /* Check if the current state is a solution */ fun isSolution(state: MutableList): Boolean { return state.isNotEmpty() && state[state.size - 1]?._val == 7 } /* Record solution */ fun recordSolution(state: MutableList?, res: MutableList?>) { res.add(state!!.toMutableList()) } /* Check if the choice is valid under the current state */ fun isValid(state: MutableList?, choice: TreeNode?): Boolean { return choice != null && choice._val != 3 } /* Update state */ fun makeChoice(state: MutableList, choice: TreeNode?) { state.add(choice) } /* Restore state */ fun undoChoice(state: MutableList, choice: TreeNode?) { state.removeLast() } /* Backtracking algorithm: Example 3 */ fun backtrack( state: MutableList, choices: MutableList, res: MutableList?> ) { // Check if it is a solution if (isSolution(state)) { // Record solution recordSolution(state, res) } // Traverse all choices for (choice in choices) { // Pruning: check if the choice is valid if (isValid(state, choice)) { // Attempt: make choice, update state makeChoice(state, choice) // Proceed to the next round of selection backtrack(state, mutableListOf(choice!!.left, choice.right), res) // Backtrack: undo choice, restore to previous state undoChoice(state, choice) } } } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\nInitialize binary tree") printTree(root) // Backtracking algorithm val res = mutableListOf?>() backtrack(mutableListOf(), mutableListOf(root), res) println("\nOutput all paths from root node to node 7, requiring paths do not include nodes with value 3") for (path in res) { val vals = mutableListOf() for (node in path!!) { if (node != null) { vals.add(node._val) } } println(vals) } } ================================================ FILE: en/codes/kotlin/chapter_backtracking/subset_sum_i.kt ================================================ /** * File: subset_sum_i.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_i /* Backtracking algorithm: Subset sum I */ fun backtrack( state: MutableList, target: Int, choices: IntArray, start: Int, res: MutableList?> ) { // When the subset sum equals target, record the solution if (target == 0) { res.add(state.toMutableList()) return } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets for (i in start..?> { val state = mutableListOf() // State (subset) nums.sort() // Sort nums val start = 0 // Start point for traversal val res = mutableListOf?>() // Result list (subset list) backtrack(state, target, nums, start, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(3, 4, 5) val target = 9 val res = subsetSumI(nums, target) println("Input array nums = ${nums.contentToString()}, target = $target") println("All subsets with sum equal to $target res = $res") } ================================================ FILE: en/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt ================================================ /** * File: subset_sum_i_native.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_i_naive /* Backtracking algorithm: Subset sum I */ fun backtrack( state: MutableList, target: Int, total: Int, choices: IntArray, res: MutableList?> ) { // When the subset sum equals target, record the solution if (total == target) { res.add(state.toMutableList()) return } // Traverse all choices for (i in choices.indices) { // Pruning: if the subset sum exceeds target, skip this choice if (total + choices[i] > target) { continue } // Attempt: make choice, update element sum total state.add(choices[i]) // Proceed to the next round of selection backtrack(state, target, total + choices[i], choices, res) // Backtrack: undo choice, restore to previous state state.removeAt(state.size - 1) } } /* Solve subset sum I (including duplicate subsets) */ fun subsetSumINaive(nums: IntArray, target: Int): MutableList?> { val state = mutableListOf() // State (subset) val total = 0 // Subset sum val res = mutableListOf?>() // Result list (subset list) backtrack(state, target, total, nums, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(3, 4, 5) val target = 9 val res = subsetSumINaive(nums, target) println("Input array nums = ${nums.contentToString()}, target = $target") println("All subsets with sum equal to $target res = $res") println("Please note that this method outputs results containing duplicate sets") } ================================================ FILE: en/codes/kotlin/chapter_backtracking/subset_sum_ii.kt ================================================ /** * File: subset_sum_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_ii /* Backtracking algorithm: Subset sum II */ fun backtrack( state: MutableList, target: Int, choices: IntArray, start: Int, res: MutableList?> ) { // When the subset sum equals target, record the solution if (target == 0) { res.add(state.toMutableList()) return } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets // Pruning 3: start traversing from start to avoid repeatedly selecting the same element for (i in start.. start && choices[i] == choices[i - 1]) { continue } // Attempt: make choice, update target, start state.add(choices[i]) // Proceed to the next round of selection backtrack(state, target - choices[i], choices, i + 1, res) // Backtrack: undo choice, restore to previous state state.removeAt(state.size - 1) } } /* Solve subset sum II */ fun subsetSumII(nums: IntArray, target: Int): MutableList?> { val state = mutableListOf() // State (subset) nums.sort() // Sort nums val start = 0 // Start point for traversal val res = mutableListOf?>() // Result list (subset list) backtrack(state, target, nums, start, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(4, 4, 5) val target = 9 val res = subsetSumII(nums, target) println("Input array nums = ${nums.contentToString()}, target = $target") println("All subsets with sum equal to $target res = $res") } ================================================ FILE: en/codes/kotlin/chapter_computational_complexity/iteration.kt ================================================ /** * File: iteration.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.iteration /* for loop */ fun forLoop(n: Int): Int { var res = 0 // Sum 1, 2, ..., n-1, n for (i in 1..n) { res += i } return res } /* while loop */ fun whileLoop(n: Int): Int { var res = 0 var i = 1 // Initialize condition variable // Sum 1, 2, ..., n-1, n while (i <= n) { res += i i++ // Update condition variable } return res } /* while loop (two updates) */ fun whileLoopII(n: Int): Int { var res = 0 var i = 1 // Initialize condition variable // Sum 1, 4, 10, ... while (i <= n) { res += i // Update condition variable i++ i *= 2 } return res } /* Nested for loop */ fun nestedForLoop(n: Int): String { val res = StringBuilder() // Loop i = 1, 2, ..., n-1, n for (i in 1..n) { // Loop j = 1, 2, ..., n-1, n for (j in 1..n) { res.append(" ($i, $j), ") } } return res.toString() } /* Driver Code */ fun main() { val n = 5 var res: Int res = forLoop(n) println("\nFor loop sum result res = $res") res = whileLoop(n) println("\nWhile loop sum result res = $res") res = whileLoopII(n) println("\nWhile loop (two updates) sum result res = $res") val resStr = nestedForLoop(n) println("\nNested for loop traversal result $resStr") } ================================================ FILE: en/codes/kotlin/chapter_computational_complexity/recursion.kt ================================================ /** * File: recursion.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.recursion import java.util.* /* Recursion */ fun recur(n: Int): Int { // Termination condition if (n == 1) return 1 // Descend: recursive call val res = recur(n - 1) // Return: return result return n + res } /* Simulate recursion using iteration */ fun forLoopRecur(n: Int): Int { // Use an explicit stack to simulate the system call stack val stack = Stack() var res = 0 // Descend: recursive call for (i in n downTo 0) { // Simulate "recurse" with "push" stack.push(i) } // Return: return result while (stack.isNotEmpty()) { // Simulate "return" with "pop" res += stack.pop() } // res = 1+2+3+...+n return res } /* Tail recursion */ tailrec fun tailRecur(n: Int, res: Int): Int { // Add tailrec keyword to enable tail recursion optimization // Termination condition if (n == 0) return res // Tail recursive call return tailRecur(n - 1, res + n) } /* Fibonacci sequence: recursion */ fun fib(n: Int): Int { // Termination condition f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1 // Recursive call f(n) = f(n-1) + f(n-2) val res = fib(n - 1) + fib(n - 2) // Return result f(n) return res } /* Driver Code */ fun main() { val n = 5 var res: Int res = recur(n) println("\nRecursion sum result res = $res") res = forLoopRecur(n) println("\nUsing iteration to simulate recursion sum result res = $res") res = tailRecur(n, 0) println("\nTail recursion sum result res = $res") res = fib(n) println("\nThe ${n}th Fibonacci number is $res") } ================================================ FILE: en/codes/kotlin/chapter_computational_complexity/space_complexity.kt ================================================ /** * File: space_complexity.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.space_complexity import utils.ListNode import utils.TreeNode import utils.printTree /* Function */ fun function(): Int { // Perform some operations return 0 } /* Constant order */ fun constant(n: Int) { // Constants, variables, objects occupy O(1) space val a = 0 var b = 0 val nums = Array(10000) { 0 } val node = ListNode(0) // Variables in the loop occupy O(1) space for (i in 0..() for (i in 0..() for (i in 0..?>(n) // 2D list uses O(n^2) space val numList = mutableListOf>() for (i in 0..() for (j in 0.. nums[j + 1]) { // Swap nums[j] and nums[j + 1] val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp count += 3 // Element swap includes 3 unit operations } } } return count } /* Exponential order (loop implementation) */ fun exponential(n: Int): Int { var count = 0 var base = 1 // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1) for (i in 0.. 1) { n1 /= 2 count++ } return count } /* Logarithmic order (recursive implementation) */ fun logRecur(n: Int): Int { if (n <= 1) return 0 return logRecur(n / 2) + 1 } /* Linearithmic order */ fun linearLogRecur(n: Int): Int { if (n <= 1) return 1 var count = linearLogRecur(n / 2) + linearLogRecur(n / 2) for (i in 0.. { val nums = IntArray(n) // Generate array nums = { 1, 2, 3, ..., n } for (i in 0..(n) for (i in 0..): Int { for (i in nums.indices) { // When element 1 is at the head of the array, best time complexity O(1) is achieved // When element 1 is at the tail of the array, worst time complexity O(n) is achieved if (nums[i] == 1) return i } return -1 } /* Driver Code */ fun main() { for (i in 0..9) { val n = 100 val nums = randomNumbers(n) val index = findOne(nums) println("\nArray [ 1, 2, ..., n ] after shuffling = ${nums.contentToString()}") println("Index of number 1 is $index") } } ================================================ FILE: en/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt ================================================ /** * File: binary_search_recur.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.binary_search_recur /* Binary search: problem f(i, j) */ fun dfs( nums: IntArray, target: Int, i: Int, j: Int ): Int { // If the interval is empty, it means there is no target element, return -1 if (i > j) { return -1 } // Calculate the midpoint index m val m = (i + j) / 2 return if (nums[m] < target) { // Recursion subproblem f(m+1, j) dfs(nums, target, m + 1, j) } else if (nums[m] > target) { // Recursion subproblem f(i, m-1) dfs(nums, target, i, m - 1) } else { // Found the target element, return its index m } } /* Binary search */ fun binarySearch(nums: IntArray, target: Int): Int { val n = nums.size // Solve the problem f(0, n-1) return dfs(nums, target, 0, n - 1) } /* Driver Code */ fun main() { val target = 6 val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) // Binary search (closed interval on both sides) val index = binarySearch(nums, target) println("Index of target element 6 = $index") } ================================================ FILE: en/codes/kotlin/chapter_divide_and_conquer/build_tree.kt ================================================ /** * File: build_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.build_tree import utils.TreeNode import utils.printTree /* Build binary tree: divide and conquer */ fun dfs( preorder: IntArray, inorderMap: Map, i: Int, l: Int, r: Int ): TreeNode? { // Terminate when the subtree interval is empty if (r - l < 0) return null // Initialize the root node val root = TreeNode(preorder[i]) // Query m to divide the left and right subtrees val m = inorderMap[preorder[i]]!! // Subproblem: build the left subtree root.left = dfs(preorder, inorderMap, i + 1, l, m - 1) // Subproblem: build the right subtree root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r) // Return the root node return root } /* Build binary tree */ fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? { // Initialize hash map, storing the mapping from inorder elements to indices val inorderMap = HashMap() for (i in inorder.indices) { inorderMap[inorder[i]] = i } val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1) return root } /* Driver Code */ fun main() { val preorder = intArrayOf(3, 9, 2, 1, 7) val inorder = intArrayOf(9, 3, 1, 2, 7) println("Pre-order traversal = ${preorder.contentToString()}") println("In-order traversal = ${inorder.contentToString()}") val root = buildTree(preorder, inorder) println("The constructed binary tree is:") printTree(root) } ================================================ FILE: en/codes/kotlin/chapter_divide_and_conquer/hanota.kt ================================================ /** * File: hanota.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.hanota /* Move a disk */ fun move(src: MutableList, tar: MutableList) { // Take out a disk from the top of src val pan = src.removeAt(src.size - 1) // Place the disk on top of tar tar.add(pan) } /* Solve the Tower of Hanoi problem f(i) */ fun dfs(i: Int, src: MutableList, buf: MutableList, tar: MutableList) { // If there is only one disk left in src, move it directly to tar if (i == 1) { move(src, tar) return } // Subproblem f(i-1): move the top i-1 disks from src to buf using tar dfs(i - 1, src, tar, buf) // Subproblem f(1): move the remaining disk from src to tar move(src, tar) // Subproblem f(i-1): move the top i-1 disks from buf to tar using src dfs(i - 1, buf, src, tar) } /* Solve the Tower of Hanoi problem */ fun solveHanota(A: MutableList, B: MutableList, C: MutableList) { val n = A.size // Move the top n disks from A to C using B dfs(n, A, B, C) } /* Driver Code */ fun main() { // The tail of the list is the top of the rod val A = mutableListOf(5, 4, 3, 2, 1) val B = mutableListOf() val C = mutableListOf() println("In initial state:") println("A = $A") println("B = $B") println("C = $C") solveHanota(A, B, C) println("After disk movement is complete:") println("A = $A") println("B = $B") println("C = $C") } ================================================ FILE: en/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt ================================================ /** * File: climbing_stairs_backtrack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* Backtracking */ fun backtrack( choices: MutableList, state: Int, n: Int, res: MutableList ) { // When climbing to the n-th stair, add 1 to the solution count if (state == n) res[0] = res[0] + 1 // Traverse all choices for (choice in choices) { // Pruning: not allowed to go beyond the n-th stair if (state + choice > n) continue // Attempt: make choice, update state backtrack(choices, state + choice, n, res) // Backtrack } } /* Climbing stairs: Backtracking */ fun climbingStairsBacktrack(n: Int): Int { val choices = mutableListOf(1, 2) // Can choose to climb up 1 or 2 stairs val state = 0 // Start climbing from the 0-th stair val res = mutableListOf() res.add(0) // Use res[0] to record the solution count backtrack(choices, state, n, res) return res[0] } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsBacktrack(n) println("Climbing $n stairs has $res solutions") } ================================================ FILE: en/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt ================================================ /** * File: climbing_stairs_constraint_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* Climbing stairs with constraint: Dynamic programming */ fun climbingStairsConstraintDP(n: Int): Int { if (n == 1 || n == 2) { return 1 } // Initialize dp table, used to store solutions to subproblems val dp = Array(n + 1) { IntArray(3) } // Initial state: preset the solution to the smallest subproblem dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // State transition: gradually solve larger subproblems from smaller ones for (i in 3..n) { dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] } return dp[n][1] + dp[n][2] } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsConstraintDP(n) println("Climbing $n stairs has $res solutions") } ================================================ FILE: en/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt ================================================ /** * File: climbing_stairs_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* Search */ fun dfs(i: Int): Int { // Known dp[1] and dp[2], return them if (i == 1 || i == 2) return i // dp[i] = dp[i-1] + dp[i-2] val count = dfs(i - 1) + dfs(i - 2) return count } /* Climbing stairs: Search */ fun climbingStairsDFS(n: Int): Int { return dfs(n) } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsDFS(n) println("Climbing $n stairs has $res solutions") } ================================================ FILE: en/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt ================================================ /** * File: climbing_stairs_dfs_mem.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* Memoization search */ fun dfs(i: Int, mem: IntArray): Int { // Known dp[1] and dp[2], return them if (i == 1 || i == 2) return i // If record dp[i] exists, return it directly if (mem[i] != -1) return mem[i] // dp[i] = dp[i-1] + dp[i-2] val count = dfs(i - 1, mem) + dfs(i - 2, mem) // Record dp[i] mem[i] = count return count } /* Climbing stairs: Memoization search */ fun climbingStairsDFSMem(n: Int): Int { // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record val mem = IntArray(n + 1) mem.fill(-1) return dfs(n, mem) } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsDFSMem(n) println("Climbing $n stairs has $res solutions") } ================================================ FILE: en/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt ================================================ /** * File: climbing_stairs_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* Climbing stairs: Dynamic programming */ fun climbingStairsDP(n: Int): Int { if (n == 1 || n == 2) return n // Initialize dp table, used to store solutions to subproblems val dp = IntArray(n + 1) // Initial state: preset the solution to the smallest subproblem dp[1] = 1 dp[2] = 2 // State transition: gradually solve larger subproblems from smaller ones for (i in 3..n) { dp[i] = dp[i - 1] + dp[i - 2] } return dp[n] } /* Climbing stairs: Space-optimized dynamic programming */ fun climbingStairsDPComp(n: Int): Int { if (n == 1 || n == 2) return n var a = 1 var b = 2 for (i in 3..n) { val temp = b b += a a = temp } return b } /* Driver Code */ fun main() { val n = 9 var res = climbingStairsDP(n) println("Climbing $n stairs has $res solutions") res = climbingStairsDPComp(n) println("Climbing $n stairs has $res solutions") } ================================================ FILE: en/codes/kotlin/chapter_dynamic_programming/coin_change.kt ================================================ /** * File: coin_change.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* Coin change: Dynamic programming */ fun coinChangeDP(coins: IntArray, amt: Int): Int { val n = coins.size val MAX = amt + 1 // Initialize dp table val dp = Array(n + 1) { IntArray(amt + 1) } // State transition: first row and first column for (a in 1..amt) { dp[0][a] = MAX } // State transition: rest of the rows and columns for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a] } else { // The smaller value between not selecting and selecting coin i dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) } } } return if (dp[n][amt] != MAX) dp[n][amt] else -1 } /* Coin change: Space-optimized dynamic programming */ fun coinChangeDPComp(coins: IntArray, amt: Int): Int { val n = coins.size val MAX = amt + 1 // Initialize dp table val dp = IntArray(amt + 1) dp.fill(MAX) dp[0] = 0 // State transition for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a] } else { // The smaller value between not selecting and selecting coin i dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) } } } return if (dp[amt] != MAX) dp[amt] else -1 } /* Driver Code */ fun main() { val coins = intArrayOf(1, 2, 5) val amt = 4 // Dynamic programming var res = coinChangeDP(coins, amt) println("Minimum coins needed to make target amount is $res") // Space-optimized dynamic programming res = coinChangeDPComp(coins, amt) println("Minimum coins needed to make target amount is $res") } ================================================ FILE: en/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt ================================================ /** * File: coin_change_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* Coin change II: Dynamic programming */ fun coinChangeIIDP(coins: IntArray, amt: Int): Int { val n = coins.size // Initialize dp table val dp = Array(n + 1) { IntArray(amt + 1) } // Initialize first column for (i in 0..n) { dp[i][0] = 1 } // State transition for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a] } else { // Sum of the two options: not selecting and selecting coin i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] } } } return dp[n][amt] } /* Coin change II: Space-optimized dynamic programming */ fun coinChangeIIDPComp(coins: IntArray, amt: Int): Int { val n = coins.size // Initialize dp table val dp = IntArray(amt + 1) dp[0] = 1 // State transition for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a] } else { // Sum of the two options: not selecting and selecting coin i dp[a] = dp[a] + dp[a - coins[i - 1]] } } } return dp[amt] } /* Driver Code */ fun main() { val coins = intArrayOf(1, 2, 5) val amt = 5 // Dynamic programming var res = coinChangeIIDP(coins, amt) println("Number of coin combinations to make target amount is $res") // Space-optimized dynamic programming res = coinChangeIIDPComp(coins, amt) println("Number of coin combinations to make target amount is $res") } ================================================ FILE: en/codes/kotlin/chapter_dynamic_programming/edit_distance.kt ================================================ /** * File: edit_distance.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* Edit distance: Brute-force search */ fun editDistanceDFS( s: String, t: String, i: Int, j: Int ): Int { // If both s and t are empty, return 0 if (i == 0 && j == 0) return 0 // If s is empty, return length of t if (i == 0) return j // If t is empty, return length of s if (j == 0) return i // If two characters are equal, skip both characters if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1) // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 val insert = editDistanceDFS(s, t, i, j - 1) val delete = editDistanceDFS(s, t, i - 1, j) val replace = editDistanceDFS(s, t, i - 1, j - 1) // Return minimum edit steps return min(min(insert, delete), replace) + 1 } /* Edit distance: Memoization search */ fun editDistanceDFSMem( s: String, t: String, mem: Array, i: Int, j: Int ): Int { // If both s and t are empty, return 0 if (i == 0 && j == 0) return 0 // If s is empty, return length of t if (i == 0) return j // If t is empty, return length of s if (j == 0) return i // If there's a record, return it directly if (mem[i][j] != -1) return mem[i][j] // If two characters are equal, skip both characters if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1) // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 val insert = editDistanceDFSMem(s, t, mem, i, j - 1) val delete = editDistanceDFSMem(s, t, mem, i - 1, j) val replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1) // Record and return minimum edit steps mem[i][j] = min(min(insert, delete), replace) + 1 return mem[i][j] } /* Edit distance: Dynamic programming */ fun editDistanceDP(s: String, t: String): Int { val n = s.length val m = t.length val dp = Array(n + 1) { IntArray(m + 1) } // State transition: first row and first column for (i in 1..n) { dp[i][0] = i } for (j in 1..m) { dp[0][j] = j } // State transition: rest of the rows and columns for (i in 1..n) { for (j in 1..m) { if (s[i - 1] == t[j - 1]) { // If two characters are equal, skip both characters dp[i][j] = dp[i - 1][j - 1] } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 } } } return dp[n][m] } /* Edit distance: Space-optimized dynamic programming */ fun editDistanceDPComp(s: String, t: String): Int { val n = s.length val m = t.length val dp = IntArray(m + 1) // State transition: first row for (j in 1..m) { dp[j] = j } // State transition: rest of the rows for (i in 1..n) { // State transition: first column var leftup = dp[0] // Temporarily store dp[i-1, j-1] dp[0] = i // State transition: rest of the columns for (j in 1..m) { val temp = dp[j] if (s[i - 1] == t[j - 1]) { // If two characters are equal, skip both characters dp[j] = leftup } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 } leftup = temp // Update for next round's dp[i-1, j-1] } } return dp[m] } /* Driver Code */ fun main() { val s = "bag" val t = "pack" val n = s.length val m = t.length // Brute-force search var res = editDistanceDFS(s, t, n, m) println("Changing $s to $t requires minimum $res edits") // Memoization search val mem = Array(n + 1) { IntArray(m + 1) } for (row in mem) row.fill(-1) res = editDistanceDFSMem(s, t, mem, n, m) println("Changing $s to $t requires minimum $res edits") // Dynamic programming res = editDistanceDP(s, t) println("Changing $s to $t requires minimum $res edits") // Space-optimized dynamic programming res = editDistanceDPComp(s, t) println("Changing $s to $t requires minimum $res edits") } ================================================ FILE: en/codes/kotlin/chapter_dynamic_programming/knapsack.kt ================================================ /** * File: knapsack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.max /* 0-1 knapsack: Brute-force search */ fun knapsackDFS( wgt: IntArray, _val: IntArray, i: Int, c: Int ): Int { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i == 0 || c == 0) { return 0 } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFS(wgt, _val, i - 1, c) } // Calculate the maximum value of not putting in and putting in item i val no = knapsackDFS(wgt, _val, i - 1, c) val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1] // Return the larger value of the two options return max(no, yes) } /* 0-1 knapsack: Memoization search */ fun knapsackDFSMem( wgt: IntArray, _val: IntArray, mem: Array, i: Int, c: Int ): Int { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i == 0 || c == 0) { return 0 } // If there's a record, return it directly if (mem[i][c] != -1) { return mem[i][c] } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, _val, mem, i - 1, c) } // Calculate the maximum value of not putting in and putting in item i val no = knapsackDFSMem(wgt, _val, mem, i - 1, c) val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1] // Record and return the larger value of the two options mem[i][c] = max(no, yes) return mem[i][c] } /* 0-1 knapsack: Dynamic programming */ fun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int { val n = wgt.size // Initialize dp table val dp = Array(n + 1) { IntArray(cap + 1) } // State transition for (i in 1..n) { for (c in 1..cap) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c] } else { // The larger value between not selecting and selecting item 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 knapsack: Space-optimized dynamic programming */ fun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int { val n = wgt.size // Initialize dp table val dp = IntArray(cap + 1) // State transition for (i in 1..n) { // Traverse in reverse order for (c in cap downTo 1) { if (wgt[i - 1] <= c) { // The larger value between not selecting and selecting item i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) } } } return dp[cap] } /* Driver Code */ fun main() { val wgt = intArrayOf(10, 20, 30, 40, 50) val _val = intArrayOf(50, 120, 150, 210, 240) val cap = 50 val n = wgt.size // Brute-force search var res = knapsackDFS(wgt, _val, n, cap) println("Maximum item value not exceeding knapsack capacity is $res") // Memoization search val mem = Array(n + 1) { IntArray(cap + 1) } for (row in mem) { row.fill(-1) } res = knapsackDFSMem(wgt, _val, mem, n, cap) println("Maximum item value not exceeding knapsack capacity is $res") // Dynamic programming res = knapsackDP(wgt, _val, cap) println("Maximum item value not exceeding knapsack capacity is $res") // Space-optimized dynamic programming res = knapsackDPComp(wgt, _val, cap) println("Maximum item value not exceeding knapsack capacity is $res") } ================================================ FILE: en/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt ================================================ /** * File: min_cost_climbing_stairs_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* Minimum cost climbing stairs: Dynamic programming */ fun minCostClimbingStairsDP(cost: IntArray): Int { val n = cost.size - 1 if (n == 1 || n == 2) return cost[n] // Initialize dp table, used to store solutions to subproblems val dp = IntArray(n + 1) // Initial state: preset the solution to the smallest subproblem dp[1] = cost[1] dp[2] = cost[2] // State transition: gradually solve larger subproblems from smaller ones for (i in 3..n) { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] } return dp[n] } /* Minimum cost climbing stairs: Space-optimized dynamic programming */ fun minCostClimbingStairsDPComp(cost: IntArray): Int { val n = cost.size - 1 if (n == 1 || n == 2) return cost[n] var a = cost[1] var b = cost[2] for (i in 3..n) { val tmp = b b = min(a, tmp) + cost[i] a = tmp } return b } /* Driver Code */ fun main() { val cost = intArrayOf(0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1) println("Input stair cost list is ${cost.contentToString()}") var res = minCostClimbingStairsDP(cost) println("Minimum cost to climb stairs is $res") res = minCostClimbingStairsDPComp(cost) println("Minimum cost to climb stairs is $res") } ================================================ FILE: en/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt ================================================ /** * File: min_path_sum.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* Minimum path sum: Brute-force search */ fun minPathSumDFS(grid: Array, i: Int, j: Int): Int { // If it's the top-left cell, terminate the search if (i == 0 && j == 0) { return grid[0][0] } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return Int.MAX_VALUE } // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1) val up = minPathSumDFS(grid, i - 1, j) val left = minPathSumDFS(grid, i, j - 1) // Return the minimum path cost from top-left to (i, j) return min(left, up) + grid[i][j] } /* Minimum path sum: Memoization search */ fun minPathSumDFSMem( grid: Array, mem: Array, i: Int, j: Int ): Int { // If it's the top-left cell, terminate the search if (i == 0 && j == 0) { return grid[0][0] } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return Int.MAX_VALUE } // If there's a record, return it directly if (mem[i][j] != -1) { return mem[i][j] } // Minimum path cost for left and upper cells val up = minPathSumDFSMem(grid, mem, i - 1, j) val left = minPathSumDFSMem(grid, mem, i, j - 1) // Record and return the minimum path cost from top-left to (i, j) mem[i][j] = min(left, up) + grid[i][j] return mem[i][j] } /* Minimum path sum: Dynamic programming */ fun minPathSumDP(grid: Array): Int { val n = grid.size val m = grid[0].size // Initialize dp table val dp = Array(n) { IntArray(m) } dp[0][0] = grid[0][0] // State transition: first row for (j in 1..): Int { val n = grid.size val m = grid[0].size // Initialize dp table val dp = IntArray(m) // State transition: first row dp[0] = grid[0][0] for (j in 1.. c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c] } else { // The larger value between not selecting and selecting item i dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1]) } } } return dp[n][cap] } /* Unbounded knapsack: Space-optimized dynamic programming */ fun unboundedKnapsackDPComp( wgt: IntArray, _val: IntArray, cap: Int ): Int { val n = wgt.size // Initialize dp table val dp = IntArray(cap + 1) // State transition for (i in 1..n) { for (c in 1..cap) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[c] = dp[c] } else { // The larger value between not selecting and selecting item i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) } } } return dp[cap] } /* Driver Code */ fun main() { val wgt = intArrayOf(1, 2, 3) val _val = intArrayOf(5, 11, 15) val cap = 4 // Dynamic programming var res = unboundedKnapsackDP(wgt, _val, cap) println("Maximum item value not exceeding knapsack capacity is $res") // Space-optimized dynamic programming res = unboundedKnapsackDPComp(wgt, _val, cap) println("Maximum item value not exceeding knapsack capacity is $res") } ================================================ FILE: en/codes/kotlin/chapter_graph/graph_adjacency_list.kt ================================================ /** * File: graph_adjacency_list.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex /* Undirected graph class based on adjacency list */ class GraphAdjList(edges: Array>) { // Adjacency list, key: vertex, value: all adjacent vertices of that vertex val adjList = HashMap>() /* Constructor */ init { // Add all vertices and edges for (edge in edges) { addVertex(edge[0]!!) addVertex(edge[1]!!) addEdge(edge[0]!!, edge[1]!!) } } /* Get the number of vertices */ fun size(): Int { return adjList.size } /* Add edge */ fun addEdge(vet1: Vertex, vet2: Vertex) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) throw IllegalArgumentException() // Add edge vet1 - vet2 adjList[vet1]?.add(vet2) adjList[vet2]?.add(vet1) } /* Remove edge */ fun removeEdge(vet1: Vertex, vet2: Vertex) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) throw IllegalArgumentException() // Remove edge vet1 - vet2 adjList[vet1]?.remove(vet2) adjList[vet2]?.remove(vet1) } /* Add vertex */ fun addVertex(vet: Vertex) { if (adjList.containsKey(vet)) return // Add a new linked list in the adjacency list adjList[vet] = mutableListOf() } /* Remove vertex */ fun removeVertex(vet: Vertex) { if (!adjList.containsKey(vet)) throw IllegalArgumentException() // Remove the linked list corresponding to vertex vet in the adjacency list adjList.remove(vet) // Traverse the linked lists of other vertices and remove all edges containing vet for (list in adjList.values) { list.remove(vet) } } /* Print adjacency list */ fun print() { println("Adjacency list =") for (pair in adjList.entries) { val tmp = mutableListOf() for (vertex in pair.value) { tmp.add(vertex._val) } println("${pair.key._val}: $tmp,") } } } /* Driver Code */ fun main() { /* Add edge */ val v = Vertex.valsToVets(intArrayOf(1, 3, 2, 5, 4)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[2], v[3]), arrayOf(v[2], v[4]), arrayOf(v[3], v[4]) ) val graph = GraphAdjList(edges) println("\nAfter initialization, graph is") graph.print() /* Add edge */ // Vertices 1, 3 are v[0], v[1] graph.addEdge(v[0]!!, v[2]!!) println("\nAfter adding edge 1-2, graph is") graph.print() /* Remove edge */ // Vertex 3 is v[1] graph.removeEdge(v[0]!!, v[1]!!) println("\nAfter removing edge 1-3, graph is") graph.print() /* Add vertex */ val v5 = Vertex(6) graph.addVertex(v5) println("\nAfter adding vertex 6, graph is") graph.print() /* Remove vertex */ // Vertex 3 is v[1] graph.removeVertex(v[1]!!) println("\nAfter removing vertex 3, graph is") graph.print() } ================================================ FILE: en/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt ================================================ /** * File: graph_adjacency_matrix.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.printMatrix /* Undirected graph class based on adjacency matrix */ class GraphAdjMat(vertices: IntArray, edges: Array) { val vertices = mutableListOf() // Vertex list, where the element represents the "vertex value" and the index represents the "vertex index" val adjMat = mutableListOf>() // Adjacency matrix, where the row and column indices correspond to the "vertex index" /* Constructor */ init { // Add vertex for (vertex in vertices) { addVertex(vertex) } // Add edge // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices for (edge in edges) { addEdge(edge[0], edge[1]) } } /* Get the number of vertices */ fun size(): Int { return vertices.size } /* Add vertex */ fun addVertex(_val: Int) { val n = size() // Add the value of the new vertex to the vertex list vertices.add(_val) // Add a row to the adjacency matrix val newRow = mutableListOf() for (j in 0..= size()) throw IndexOutOfBoundsException() // Remove the vertex at index from the vertex list vertices.removeAt(index) // Remove the row at index from the adjacency matrix adjMat.removeAt(index) // Remove the column at index from the adjacency matrix for (row in adjMat) { row.removeAt(index) } } /* Add edge */ // Parameters i, j correspond to the vertices element indices fun addEdge(i: Int, j: Int) { // Handle index out of bounds and equality if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw IndexOutOfBoundsException() // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i) adjMat[i][j] = 1 adjMat[j][i] = 1 } /* Remove edge */ // Parameters i, j correspond to the vertices element indices fun removeEdge(i: Int, j: Int) { // Handle index out of bounds and equality if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw IndexOutOfBoundsException() adjMat[i][j] = 0 adjMat[j][i] = 0 } /* Print adjacency matrix */ fun print() { print("Vertex list = ") println(vertices) println("Adjacency matrix =") printMatrix(adjMat) } } /* Driver Code */ fun main() { /* Add edge */ // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices val vertices = intArrayOf(1, 3, 2, 5, 4) val edges = arrayOf( intArrayOf(0, 1), intArrayOf(0, 3), intArrayOf(1, 2), intArrayOf(2, 3), intArrayOf(2, 4), intArrayOf(3, 4) ) val graph = GraphAdjMat(vertices, edges) println("\nAfter initialization, graph is") graph.print() /* Add edge */ // Add vertex graph.addEdge(0, 2) println("\nAfter adding edge 1-2, graph is") graph.print() /* Remove edge */ // Vertices 1, 3 have indices 0, 1 respectively graph.removeEdge(0, 1) println("\nAfter removing edge 1-3, graph is") graph.print() /* Add vertex */ graph.addVertex(6) println("\nAfter adding vertex 6, graph is") graph.print() /* Remove vertex */ // Vertex 3 has index 1 graph.removeVertex(1) println("\nAfter removing vertex 3, graph is") graph.print() } ================================================ FILE: en/codes/kotlin/chapter_graph/graph_bfs.kt ================================================ /** * File: graph_bfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex import java.util.* /* Breadth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList { // Vertex traversal sequence val res = mutableListOf() // Hash set for recording vertices that have been visited val visited = HashSet() visited.add(startVet) // Queue used to implement BFS val que = LinkedList() que.offer(startVet) // Starting from vertex vet, loop until all vertices are visited while (!que.isEmpty()) { val vet = que.poll() // Dequeue the front vertex res.add(vet) // Record visited vertex // Traverse all adjacent vertices of this vertex for (adjVet in graph.adjList[vet]!!) { if (visited.contains(adjVet)) continue // Skip vertices that have been visited que.offer(adjVet) // Only enqueue unvisited vertices visited.add(adjVet) // Mark this vertex as visited } } // Return vertex traversal sequence return res } /* Driver Code */ fun main() { /* Add edge */ val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[1], v[4]), arrayOf(v[2], v[5]), arrayOf(v[3], v[4]), arrayOf(v[3], v[6]), arrayOf(v[4], v[5]), arrayOf(v[4], v[7]), arrayOf(v[5], v[8]), arrayOf(v[6], v[7]), arrayOf(v[7], v[8]) ) val graph = GraphAdjList(edges) println("\nAfter initialization, graph is") graph.print() /* Breadth-first traversal */ val res = graphBFS(graph, v[0]!!) println("\nBreadth-first traversal (BFS) vertex sequence is") println(Vertex.vetsToVals(res)) } ================================================ FILE: en/codes/kotlin/chapter_graph/graph_dfs.kt ================================================ /** * File: graph_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex /* Depth-first traversal helper function */ fun dfs( graph: GraphAdjList, visited: MutableSet, res: MutableList, vet: Vertex? ) { res.add(vet) // Record visited vertex visited.add(vet) // Mark this vertex as visited // Traverse all adjacent vertices of this vertex for (adjVet in graph.adjList[vet]!!) { if (visited.contains(adjVet)) continue // Skip vertices that have been visited // Recursively visit adjacent vertices dfs(graph, visited, res, adjVet) } } /* Depth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex fun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList { // Vertex traversal sequence val res = mutableListOf() // Hash set for recording vertices that have been visited val visited = HashSet() dfs(graph, visited, res, startVet) return res } /* Driver Code */ fun main() { /* Add edge */ val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[2], v[5]), arrayOf(v[4], v[5]), arrayOf(v[5], v[6]) ) val graph = GraphAdjList(edges) println("\nAfter initialization, graph is") graph.print() /* Depth-first traversal */ val res = graphDFS(graph, v[0]) println("\nDepth-first traversal (DFS) vertex sequence is") println(Vertex.vetsToVals(res)) } ================================================ FILE: en/codes/kotlin/chapter_greedy/coin_change_greedy.kt ================================================ /** * File: coin_change_greedy.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy /* Coin change: Greedy algorithm */ fun coinChangeGreedy(coins: IntArray, amt: Int): Int { // Assume coins list is sorted var am = amt var i = coins.size - 1 var count = 0 // Loop to make greedy choices until no remaining amount while (am > 0) { // Find the coin that is less than and closest to the remaining amount while (i > 0 && coins[i] > am) { i-- } // Choose coins[i] am -= coins[i] count++ } // If no feasible solution is found, return -1 return if (am == 0) count else -1 } /* Driver Code */ fun main() { // Greedy algorithm: Can guarantee finding the global optimal solution var coins = intArrayOf(1, 5, 10, 20, 50, 100) var amt = 186 var res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("Minimum coins needed to make $amt is $res") // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = intArrayOf(1, 20, 50) amt = 60 res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("Minimum coins needed to make $amt is $res") println("Actually the minimum number needed is 3, i.e., 20 + 20 + 20") // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = intArrayOf(1, 49, 50) amt = 98 res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("Minimum coins needed to make $amt is $res") println("Actually the minimum number needed is 2, i.e., 49 + 49") } ================================================ FILE: en/codes/kotlin/chapter_greedy/fractional_knapsack.kt ================================================ /** * File: fractional_knapsack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy /* Item */ class Item( val w: Int, // Item val v: Int // Item value ) /* Fractional knapsack: Greedy algorithm */ fun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double { // Create item list with two attributes: weight, value var cap = c val items = arrayOfNulls(wgt.size) for (i in wgt.indices) { items[i] = Item(wgt[i], _val[i]) } // Sort by unit value item.v / item.w from high to low items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) } // Loop for greedy selection var res = 0.0 for (item in items) { if (item!!.w <= cap) { // If remaining capacity is sufficient, put the entire current item into the knapsack res += item.v cap -= item.w } else { // If remaining capacity is insufficient, put part of the current item into the knapsack res += item.v.toDouble() / item.w * cap // No remaining capacity, so break out of the loop break } } return res } /* Driver Code */ fun main() { val wgt = intArrayOf(10, 20, 30, 40, 50) val _val = intArrayOf(50, 120, 150, 210, 240) val cap = 50 // Greedy algorithm val res = fractionalKnapsack(wgt, _val, cap) println("Maximum item value not exceeding knapsack capacity is $res") } ================================================ FILE: en/codes/kotlin/chapter_greedy/max_capacity.kt ================================================ /** * File: max_capacity.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy import kotlin.math.max import kotlin.math.min /* Max capacity: Greedy algorithm */ fun maxCapacity(ht: IntArray): Int { // Initialize i, j to be at both ends of the array var i = 0 var j = ht.size - 1 // Initial max capacity is 0 var res = 0 // Loop for greedy selection until the two boards meet while (i < j) { // Update max capacity val cap = min(ht[i], ht[j]) * (j - i) res = max(res, cap) // Move the shorter board inward if (ht[i] < ht[j]) { i++ } else { j-- } } return res } /* Driver Code */ fun main() { val ht = intArrayOf(3, 8, 5, 2, 7, 7, 3, 4) // Greedy algorithm val res = maxCapacity(ht) println("Maximum capacity is $res") } ================================================ FILE: en/codes/kotlin/chapter_greedy/max_product_cutting.kt ================================================ /** * File: max_product_cutting.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy import kotlin.math.pow /* Max product cutting: Greedy algorithm */ fun maxProductCutting(n: Int): Int { // When n <= 3, must cut out a 1 if (n <= 3) { return 1 * (n - 1) } // Greedily cut out 3, a is the number of 3s, b is the remainder val a = n / 3 val b = n % 3 if (b == 1) { // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2 return 3.0.pow((a - 1)).toInt() * 2 * 2 } if (b == 2) { // When the remainder is 2, do nothing return 3.0.pow(a).toInt() * 2 * 2 } // When the remainder is 0, do nothing return 3.0.pow(a).toInt() } /* Driver Code */ fun main() { val n = 58 // Greedy algorithm val res = maxProductCutting(n) println("Maximum cutting product is $res") } ================================================ FILE: en/codes/kotlin/chapter_hashing/array_hash_map.kt ================================================ /** * File: array_hash_map.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* Key-value pair */ class Pair( var key: Int, var _val: String ) /* Hash table based on array implementation */ class ArrayHashMap { // Initialize array with 100 buckets private val buckets = arrayOfNulls(100) /* Hash function */ fun hashFunc(key: Int): Int { val index = key % 100 return index } /* Query operation */ fun get(key: Int): String? { val index = hashFunc(key) val pair = buckets[index] ?: return null return pair._val } /* Add operation */ fun put(key: Int, _val: String) { val pair = Pair(key, _val) val index = hashFunc(key) buckets[index] = pair } /* Remove operation */ fun remove(key: Int) { val index = hashFunc(key) // Set to null to represent deletion buckets[index] = null } /* Get all key-value pairs */ fun pairSet(): MutableList { val pairSet = mutableListOf() for (pair in buckets) { if (pair != null) pairSet.add(pair) } return pairSet } /* Get all keys */ fun keySet(): MutableList { val keySet = mutableListOf() for (pair in buckets) { if (pair != null) keySet.add(pair.key) } return keySet } /* Get all values */ fun valueSet(): MutableList { val valueSet = mutableListOf() for (pair in buckets) { if (pair != null) valueSet.add(pair._val) } return valueSet } /* Print hash table */ fun print() { for (kv in pairSet()) { val key = kv.key val _val = kv._val println("$key -> $_val") } } } /* Driver Code */ fun main() { /* Initialize hash table */ val map = ArrayHashMap() /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(12836, "Xiao Ha") map.put(15937, "Xiao Luo") map.put(16750, "Xiao Suan") map.put(13276, "Xiao Fa") map.put(10583, "Xiao Ya") println("\nAfter adding is complete, hash table is\nKey -> Value") map.print() /* Query operation */ // Input key into hash table to get value val name = map.get(15937) println("\nInput student ID 15937, found name $name") /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(10583) println("\nAfter removing 10583, hash table is\nKey -> Value") map.print() /* Traverse hash table */ println("\nTraverse key-value pairs Key -> Value") for (kv in map.pairSet()) { println("${kv.key} -> ${kv._val}") } println("\nTraverse keys only Key") for (key in map.keySet()) { println(key) } println("\nTraverse values only Value") for (_val in map.valueSet()) { println(_val) } } ================================================ FILE: en/codes/kotlin/chapter_hashing/built_in_hash.kt ================================================ /** * File: built_in_hash.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing import utils.ListNode /* Driver Code */ fun main() { val num = 3 val hashNum = num.hashCode() println("Hash value of integer $num is $hashNum") val bol = true val hashBol = bol.hashCode() println("Hash value of boolean $bol is $hashBol") val dec = 3.14159 val hashDec = dec.hashCode() println("Hash value of decimal $dec is $hashDec") val str = "Hello Algo" val hashStr = str.hashCode() println("Hash value of string $str is $hashStr") val arr = arrayOf(12836, "Xiao Ha") val hashTup = arr.contentHashCode() println("Hash value of array ${arr.contentToString()} is $hashTup") val obj = ListNode(0) val hashObj = obj.hashCode() println("Hash value of node object $obj is $hashObj") } ================================================ FILE: en/codes/kotlin/chapter_hashing/hash_map.kt ================================================ /** * File: hash_map.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing import utils.printHashMap /* Driver Code */ fun main() { /* Initialize hash table */ val map = HashMap() /* Add operation */ // Add key-value pair (key, value) to the hash table map[12836] = "Xiao Ha" map[15937] = "Xiao Luo" map[16750] = "Xiao Suan" map[13276] = "Xiao Fa" map[10583] = "Xiao Ya" println("\nAfter adding is complete, hash table is\nKey -> Value") printHashMap(map) /* Query operation */ // Input key into hash table to get value val name = map[15937] println("\nInput student ID 15937, found name $name") /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(10583) println("\nAfter removing 10583, hash table is\nKey -> Value") printHashMap(map) /* Traverse hash table */ println("\nTraverse key-value pairs Key->Value") for ((key, value) in map) { println("$key -> $value") } println("\nTraverse keys only Key") for (key in map.keys) { println(key) } println("\nTraverse values only Value") for (_val in map.values) { println(_val) } } ================================================ FILE: en/codes/kotlin/chapter_hashing/hash_map_chaining.kt ================================================ /** * File: hash_map_chaining.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* Hash table with separate chaining */ class HashMapChaining { var size: Int // Number of key-value pairs var capacity: Int // Hash table capacity val loadThres: Double // Load factor threshold for triggering expansion val extendRatio: Int // Expansion multiplier var buckets: MutableList> // Bucket array /* Constructor */ init { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = mutableListOf() for (i in 0.. loadThres) { extend() } val index = hashFunc(key) val bucket = buckets[index] // Traverse bucket, if specified key is encountered, update corresponding val and return for (pair in bucket) { if (pair.key == key) { pair._val = _val return } } // If key does not exist, append key-value pair to the end val pair = Pair(key, _val) bucket.add(pair) size++ } /* Remove operation */ fun remove(key: Int) { val index = hashFunc(key) val bucket = buckets[index] // Traverse bucket and remove key-value pair from it for (pair in bucket) { if (pair.key == key) { bucket.remove(pair) size-- break } } } /* Expand hash table */ fun extend() { // Temporarily store the original hash table val bucketsTmp = buckets // Initialize expanded new hash table capacity *= extendRatio // mutablelist has no fixed size buckets = mutableListOf() for (i in 0..() for (pair in bucket) { val k = pair.key val v = pair._val res.add("$k -> $v") } println(res) } } } /* Driver Code */ fun main() { /* Initialize hash table */ val map = HashMapChaining() /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(12836, "Xiao Ha") map.put(15937, "Xiao Luo") map.put(16750, "Xiao Suan") map.put(13276, "Xiao Fa") map.put(10583, "Xiao Ya") println("\nAfter adding is complete, hash table is\nKey -> Value") map.print() /* Query operation */ // Input key into hash table to get value val name = map.get(13276) println("\nInput student ID 13276, found name $name") /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(12836) println("\nAfter removing 12836, hash table is\nKey -> Value") map.print() } ================================================ FILE: en/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt ================================================ /** * File: hash_map_open_addressing.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* Hash table with open addressing */ class HashMapOpenAddressing { private var size: Int // Number of key-value pairs private var capacity: Int // Hash table capacity private val loadThres: Double // Load factor threshold for triggering expansion private val extendRatio: Int // Expansion multiplier private var buckets: Array // Bucket array private val TOMBSTONE: Pair // Removal marker /* Constructor */ init { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = arrayOfNulls(capacity) TOMBSTONE = Pair(-1, "-1") } /* Hash function */ fun hashFunc(key: Int): Int { return key % capacity } /* Load factor */ fun loadFactor(): Double { return (size / capacity).toDouble() } /* Search for bucket index corresponding to key */ fun findBucket(key: Int): Int { var index = hashFunc(key) var firstTombstone = -1 // Linear probing, break when encountering an empty bucket while (buckets[index] != null) { // If key is encountered, return the corresponding bucket index if (buckets[index]?.key == key) { // If a removal marker was encountered before, move the key-value pair to that index if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index] buckets[index] = TOMBSTONE return firstTombstone // Return the moved bucket index } return index // Return bucket index } // Record the first removal marker encountered if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index } // Calculate bucket index, wrap around to the head if past the tail index = (index + 1) % capacity } // If key does not exist, return the index for insertion return if (firstTombstone == -1) index else firstTombstone } /* Query operation */ fun get(key: Int): String? { // Search for bucket index corresponding to key val index = findBucket(key) // If key-value pair is found, return corresponding val if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index]?._val } // If key-value pair does not exist, return null return null } /* Add operation */ fun put(key: Int, _val: String) { // When load factor exceeds threshold, perform expansion if (loadFactor() > loadThres) { extend() } // Search for bucket index corresponding to key val index = findBucket(key) // If key-value pair is found, overwrite val and return if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index]!!._val = _val return } // If key-value pair does not exist, add the key-value pair buckets[index] = Pair(key, _val) size++ } /* Remove operation */ fun remove(key: Int) { // Search for bucket index corresponding to key val index = findBucket(key) // If key-value pair is found, overwrite it with removal marker if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE size-- } } /* Expand hash table */ fun extend() { // Temporarily store the original hash table val bucketsTmp = buckets // Initialize expanded new hash table capacity *= extendRatio buckets = arrayOfNulls(capacity) size = 0 // Move key-value pairs from original hash table to new hash table for (pair in bucketsTmp) { if (pair != null && pair != TOMBSTONE) { put(pair.key, pair._val) } } } /* Print hash table */ fun print() { for (pair in buckets) { if (pair == null) { println("null") } else if (pair == TOMBSTONE) { println("TOMESTOME") } else { println("${pair.key} -> ${pair._val}") } } } } /* Driver Code */ fun main() { // Initialize hash table val hashmap = HashMapOpenAddressing() // Add operation // Add key-value pair (key, val) to the hash table hashmap.put(12836, "Xiao Ha") hashmap.put(15937, "Xiao Luo") hashmap.put(16750, "Xiao Suan") hashmap.put(13276, "Xiao Fa") hashmap.put(10583, "Xiao Ya") println("\nAfter adding is complete, hash table is\nKey -> Value") hashmap.print() // Query operation // Input key into hash table to get value val val name = hashmap.get(13276) println("\nInput student ID 13276, found name $name") // Remove operation // Remove key-value pair (key, val) from hash table hashmap.remove(16750) println("\nAfter removing 16750, hash table is\nKey -> Value") hashmap.print() } ================================================ FILE: en/codes/kotlin/chapter_hashing/simple_hash.kt ================================================ /** * File: simple_hash.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* Additive hash */ fun addHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = (hash + c.code) % MODULUS } return hash.toInt() } /* Multiplicative hash */ fun mulHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = (31 * hash + c.code) % MODULUS } return hash.toInt() } /* XOR hash */ fun xorHash(key: String): Int { var hash = 0 val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = hash xor c.code } return hash and MODULUS } /* Rotational hash */ fun rotHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS } return hash.toInt() } /* Driver Code */ fun main() { val key = "Hello Algo" var hash = addHash(key) println("Additive hash value is $hash") hash = mulHash(key) println("Multiplicative hash value is $hash") hash = xorHash(key) println("XOR hash value is $hash") hash = rotHash(key) println("Rotational hash value is $hash") } ================================================ FILE: en/codes/kotlin/chapter_heap/heap.kt ================================================ /** * File: heap.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* fun testPush(heap: Queue, _val: Int) { heap.offer(_val) // Element enters heap print("\nAfter element $_val pushes to heap\n") printHeap(heap) } fun testPop(heap: Queue) { val _val = heap.poll() // Time complexity is O(n), not O(nlogn) print("\nAfter heap top element $_val pops from heap\n") printHeap(heap) } /* Driver Code */ fun main() { /* Initialize heap */ // Python's heapq module implements min heap by default var minHeap = PriorityQueue() // Initialize max heap (modify Comparator using lambda expression) val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } println("\nThe following test cases are for max heap") /* Element enters heap */ testPush(maxHeap, 1) testPush(maxHeap, 3) testPush(maxHeap, 2) testPush(maxHeap, 5) testPush(maxHeap, 4) /* Check if heap is empty */ val peek = maxHeap.peek() print("\nHeap top element is $peek\n") /* Time complexity is O(n), not O(nlogn) */ testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) /* Get heap size */ val size = maxHeap.size print("\nHeap size is $size\n") /* Check if heap is empty */ val isEmpty = maxHeap.isEmpty() print("\nIs heap empty $isEmpty\n") /* Input list and build heap */ // Time complexity is O(n), not O(nlogn) minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) println("\nAfter inputting list and building min heap") printHeap(minHeap) } ================================================ FILE: en/codes/kotlin/chapter_heap/my_heap.kt ================================================ /** * File: my_heap.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* /* Max heap */ class MaxHeap(nums: MutableList?) { // Use list instead of array, no need to consider capacity expansion private val maxHeap = mutableListOf() /* Constructor, build heap based on input list */ init { // Add list elements to heap as is maxHeap.addAll(nums!!) // Heapify all nodes except leaf nodes for (i in parent(size() - 1) downTo 0) { siftDown(i) } } /* Get index of left child node */ private fun left(i: Int): Int { return 2 * i + 1 } /* Get index of right child node */ private fun right(i: Int): Int { return 2 * i + 2 } /* Get index of parent node */ private fun parent(i: Int): Int { return (i - 1) / 2 // Floor division } /* Swap elements */ private fun swap(i: Int, j: Int) { val temp = maxHeap[i] maxHeap[i] = maxHeap[j] maxHeap[j] = temp } /* Get heap size */ fun size(): Int { return maxHeap.size } /* Check if heap is empty */ fun isEmpty(): Boolean { /* Check if heap is empty */ return size() == 0 } /* Access top element */ fun peek(): Int { return maxHeap[0] } /* Element enters heap */ fun push(_val: Int) { // Add node maxHeap.add(_val) // Heapify from bottom to top siftUp(size() - 1) } /* Starting from node i, heapify from bottom to top */ private fun siftUp(it: Int) { // Kotlin function parameters are immutable, so create temporary variable var i = it while (true) { // Get parent node of node i val p = parent(i) // When "crossing root node" or "node needs no repair", end heapify if (p < 0 || maxHeap[i] <= maxHeap[p]) break // Swap two nodes swap(i, p) // Loop upward heapify i = p } } /* Element exits heap */ fun pop(): Int { // Handle empty case if (isEmpty()) throw IndexOutOfBoundsException() // Delete node swap(0, size() - 1) // Remove node val _val = maxHeap.removeAt(size() - 1) // Return top element siftDown(0) // Return heap top element return _val } /* Starting from node i, heapify from top to bottom */ private fun siftDown(it: Int) { // Kotlin function parameters are immutable, so create temporary variable var i = it while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break val l = left(i) val r = right(i) var ma = i if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r // Swap two nodes if (ma == i) break // Swap two nodes swap(i, ma) // Loop downwards heapification i = ma } } /* Driver Code */ fun print() { val queue = PriorityQueue { a: Int, b: Int -> b - a } queue.addAll(maxHeap) printHeap(queue) } } /* Driver Code */ fun main() { /* Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap */ val maxHeap = MaxHeap(mutableListOf(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)) println("\nAfter inputting list and building heap") maxHeap.print() /* Check if heap is empty */ var peek = maxHeap.peek() print("\nHeap top element is $peek\n") /* Element enters heap */ val _val = 7 maxHeap.push(_val) print("\nAfter element $_val pushes to heap\n") maxHeap.print() /* Time complexity is O(n), not O(nlogn) */ peek = maxHeap.pop() print("\nAfter heap top element $peek pops from heap\n") maxHeap.print() /* Get heap size */ val size = maxHeap.size() print("\nHeap size is $size\n") /* Check if heap is empty */ val isEmpty = maxHeap.isEmpty() print("\nIs heap empty $isEmpty\n") } ================================================ FILE: en/codes/kotlin/chapter_heap/top_k.kt ================================================ /** * File: top_k.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* /* Find the largest k elements in array based on heap */ fun topKHeap(nums: IntArray, k: Int): Queue { // Python's heapq module implements min heap by default val heap = PriorityQueue() // Enter the first k elements of array into heap for (i in 0.. heap.peek()) { heap.poll() heap.offer(nums[i]) } } return heap } /* Driver Code */ fun main() { val nums = intArrayOf(1, 7, 6, 3, 2) val k = 3 val res = topKHeap(nums, k) println("The largest $k elements are") printHeap(res) } ================================================ FILE: en/codes/kotlin/chapter_searching/binary_search.kt ================================================ /** * File: binary_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* Binary search (closed interval on both sides) */ fun binarySearch(nums: IntArray, target: Int): Int { // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array var i = 0 var j = nums.size - 1 // Loop, exit when the search interval is empty (empty when i > j) while (i <= j) { val m = i + (j - i) / 2 // Calculate the midpoint index m if (nums[m] < target) // This means target is in the interval [m+1, j] i = m + 1 else if (nums[m] > target) // This means target is in the interval [i, m-1] j = m - 1 else // Found the target element, return its index return m } // Target element not found, return -1 return -1 } /* Binary search (left-closed right-open interval) */ fun binarySearchLCRO(nums: IntArray, target: Int): Int { // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1 var i = 0 var j = nums.size // Loop, exit when the search interval is empty (empty when i = j) while (i < j) { val m = i + (j - i) / 2 // Calculate the midpoint index m if (nums[m] < target) // This means target is in the interval [m+1, j) i = m + 1 else if (nums[m] > target) // This means target is in the interval [i, m) j = m else // Found the target element, return its index return m } // Target element not found, return -1 return -1 } /* Driver Code */ fun main() { val target = 6 val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) /* Binary search (closed interval on both sides) */ var index = binarySearch(nums, target) println("Index of target element 6 = $index") /* Binary search (left-closed right-open interval) */ index = binarySearchLCRO(nums, target) println("Index of target element 6 = $index") } ================================================ FILE: en/codes/kotlin/chapter_searching/binary_search_edge.kt ================================================ /** * File: binary_search_edge.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* Binary search for the leftmost target */ fun binarySearchLeftEdge(nums: IntArray, target: Int): Int { // Equivalent to finding the insertion point of target val i = binarySearchInsertion(nums, target) // Target not found, return -1 if (i == nums.size || nums[i] != target) { return -1 } // Found target, return index i return i } /* Binary search for the rightmost target */ fun binarySearchRightEdge(nums: IntArray, target: Int): Int { // Convert to finding the leftmost target + 1 val i = binarySearchInsertion(nums, target + 1) // j points to the rightmost target, i points to the first element greater than target val j = i - 1 // Target not found, return -1 if (j == -1 || nums[j] != target) { return -1 } // Found target, return index j return j } /* Driver Code */ fun main() { // Array with duplicate elements val nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) println("\nArray nums = ${nums.contentToString()}") // Binary search left and right boundaries for (target in intArrayOf(6, 7)) { var index = binarySearchLeftEdge(nums, target) println("Leftmost element $target index is $index") index = binarySearchRightEdge(nums, target) println("Rightmost element $target index is $index") } } ================================================ FILE: en/codes/kotlin/chapter_searching/binary_search_insertion.kt ================================================ /** * File: binary_search_insertion.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* Binary search for insertion point (no duplicate elements) */ fun binarySearchInsertionSimple(nums: IntArray, target: Int): Int { var i = 0 var j = nums.size - 1 // Initialize closed interval [0, n-1] while (i <= j) { val m = i + (j - i) / 2 // Calculate the midpoint index m if (nums[m] < target) { i = m + 1 // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1 // target is in the interval [i, m-1] } else { return m // Found target, return insertion point m } } // Target not found, return insertion point i return i } /* Binary search for insertion point (with duplicate elements) */ fun binarySearchInsertion(nums: IntArray, target: Int): Int { var i = 0 var j = nums.size - 1 // Initialize closed interval [0, n-1] while (i <= j) { val m = i + (j - i) / 2 // Calculate the midpoint index m if (nums[m] < target) { i = m + 1 // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1 // target is in the interval [i, m-1] } else { j = m - 1 // The first element less than target is in the interval [i, m-1] } } // Return insertion point i return i } /* Driver Code */ fun main() { // Array without duplicate elements var nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) println("\nArray nums = ${nums.contentToString()}") // Binary search for insertion point for (target in intArrayOf(6, 9)) { val index = binarySearchInsertionSimple(nums, target) println("Insertion point index for element $target is $index") } // Array with duplicate elements nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) println("\nArray nums = ${nums.contentToString()}") // Binary search for insertion point for (target in intArrayOf(2, 6, 20)) { val index = binarySearchInsertion(nums, target) println("Insertion point index for element $target is $index") } } ================================================ FILE: en/codes/kotlin/chapter_searching/hashing_search.kt ================================================ /** * File: hashing_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching import utils.ListNode /* Hash search (array) */ fun hashingSearchArray(map: Map, target: Int): Int { // Hash table key: target element, _val: index // If this key does not exist in the hash table, return -1 return map.getOrDefault(target, -1) } /* Hash search (linked list) */ fun hashingSearchLinkedList(map: Map, target: Int): ListNode? { // Hash table key: target node value, _val: node object // If key is not in hash table, return null return map.getOrDefault(target, null) } /* Driver Code */ fun main() { val target = 3 /* Hash search (array) */ val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) // Initialize hash table val map = HashMap() for (i in nums.indices) { map[nums[i]] = i // key: element, _val: index } val index = hashingSearchArray(map, target) println("Index of target element 3 = $index") /* Hash search (linked list) */ var head = ListNode.arrToLinkedList(nums) // Initialize hash table val map1 = HashMap() while (head != null) { map1[head._val] = head // key: node value, _val: node head = head.next } val node = hashingSearchLinkedList(map1, target) println("Node object corresponding to target node value 3 is $node") } ================================================ FILE: en/codes/kotlin/chapter_searching/linear_search.kt ================================================ /** * File: linear_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching import utils.ListNode /* Linear search (array) */ fun linearSearchArray(nums: IntArray, target: Int): Int { // Traverse array for (i in nums.indices) { // Found the target element, return its index if (nums[i] == target) return i } // Target element not found, return -1 return -1 } /* Linear search (linked list) */ fun linearSearchLinkedList(h: ListNode?, target: Int): ListNode? { // Traverse the linked list var head = h while (head != null) { // Found the target node, return it if (head._val == target) return head head = head.next } // Target node not found, return null return null } /* Driver Code */ fun main() { val target = 3 /* Perform linear search in array */ val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) val index = linearSearchArray(nums, target) println("Index of target element 3 = $index") /* Perform linear search in linked list */ val head = ListNode.arrToLinkedList(nums) val node = linearSearchLinkedList(head, target) println("Node object corresponding to target node value 3 is $node") } ================================================ FILE: en/codes/kotlin/chapter_searching/two_sum.kt ================================================ /** * File: two_sum.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* Method 1: Brute force enumeration */ fun twoSumBruteForce(nums: IntArray, target: Int): IntArray { val size = nums.size // Two nested loops, time complexity is O(n^2) for (i in 0..() // Single loop, time complexity is O(n) for (i in 0.. nums[j + 1]) { // Swap nums[j] and nums[j + 1] val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp } } } } /* Bubble sort (flag optimization) */ fun bubbleSortWithFlag(nums: IntArray) { // Outer loop: unsorted range is [0, i] for (i in nums.size - 1 downTo 1) { var flag = false // Initialize flag // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (j in 0.. nums[j + 1]) { // Swap nums[j] and nums[j + 1] val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp flag = true // Record element swap } } if (!flag) break // No elements were swapped in this round of "bubbling", exit directly } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) bubbleSort(nums) println("After bubble sort, nums = ${nums.contentToString()}") val nums1 = intArrayOf(4, 1, 3, 1, 5, 2) bubbleSortWithFlag(nums1) println("After bubble sort, nums1 = ${nums1.contentToString()}") } ================================================ FILE: en/codes/kotlin/chapter_sorting/bucket_sort.kt ================================================ /** * File: bucket_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* Bucket sort */ fun bucketSort(nums: FloatArray) { // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket val k = nums.size / 2 val buckets = mutableListOf>() for (i in 0.. nums[ma]) ma = l if (r < n && nums[r] > nums[ma]) ma = r // Swap two nodes if (ma == i) break // Swap two nodes val temp = nums[i] nums[i] = nums[ma] nums[ma] = temp // Loop downwards heapification i = ma } } /* Heap sort */ fun heapSort(nums: IntArray) { // Build heap operation: heapify all nodes except leaves for (i in nums.size / 2 - 1 downTo 0) { siftDown(nums, nums.size, i) } // Extract the largest element from the heap and repeat for n-1 rounds for (i in nums.size - 1 downTo 1) { // Delete node val temp = nums[0] nums[0] = nums[i] nums[i] = temp // Start heapifying the root node, from top to bottom siftDown(nums, i, 0) } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) heapSort(nums) println("After heap sort, nums = ${nums.contentToString()}") } ================================================ FILE: en/codes/kotlin/chapter_sorting/insertion_sort.kt ================================================ /** * File: insertion_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* Insertion sort */ fun insertionSort(nums: IntArray) { // Outer loop: sorted elements are 1, 2, ..., n for (i in nums.indices) { val base = nums[i] var j = i - 1 // Inner loop: insert base into the correct position within the sorted interval [0, i-1] while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j] // Move nums[j] to the right by one position j-- } nums[j + 1] = base // Assign base to the correct position } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) insertionSort(nums) println("After insertion sort, nums = ${nums.contentToString()}") } ================================================ FILE: en/codes/kotlin/chapter_sorting/merge_sort.kt ================================================ /** * File: merge_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* Merge left subarray and right subarray */ fun merge(nums: IntArray, left: Int, mid: Int, right: Int) { // Left subarray interval is [left, mid], right subarray interval is [mid+1, right] // Create a temporary array tmp to store the merged results val tmp = IntArray(right - left + 1) // Initialize the start indices of the left and right subarrays var i = left var j = mid + 1 var k = 0 // While both subarrays still have elements, compare and copy the smaller element into the temporary array while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++] else tmp[k++] = nums[j++] } // Copy the remaining elements of the left and right subarrays into the temporary array while (i <= mid) { tmp[k++] = nums[i++] } while (j <= right) { tmp[k++] = nums[j++] } // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval for (l in tmp.indices) { nums[left + l] = tmp[l] } } /* Merge sort */ fun mergeSort(nums: IntArray, left: Int, right: Int) { // Termination condition if (left >= right) return // Terminate recursion when subarray length is 1 // Divide and conquer stage val mid = left + (right - left) / 2 // Calculate midpoint mergeSort(nums, left, mid) // Recursively process the left subarray mergeSort(nums, mid + 1, right) // Recursively process the right subarray // Merge stage merge(nums, left, mid, right) } /* Driver Code */ fun main() { /* Merge sort */ val nums = intArrayOf(7, 3, 2, 6, 0, 1, 5, 4) mergeSort(nums, 0, nums.size - 1) println("After merge sort, nums = ${nums.contentToString()}") } ================================================ FILE: en/codes/kotlin/chapter_sorting/quick_sort.kt ================================================ /** * File: quick_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* Swap elements */ fun swap(nums: IntArray, i: Int, j: Int) { val temp = nums[i] nums[i] = nums[j] nums[j] = temp } /* Sentinel partition */ fun partition(nums: IntArray, left: Int, right: Int): Int { // Use nums[left] as the pivot var i = left var j = right while (i < j) { while (i < j && nums[j] >= nums[left]) j-- // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++ // Search from left to right for the first element greater than the pivot swap(nums, i, j) // Swap these two elements } swap(nums, i, left) // Swap the pivot to the boundary between the two subarrays return i // Return the index of the pivot } /* Quick sort */ fun quickSort(nums: IntArray, left: Int, right: Int) { // Terminate recursion when subarray length is 1 if (left >= right) return // Sentinel partition val pivot = partition(nums, left, right) // Recursively process the left subarray and right subarray quickSort(nums, left, pivot - 1) quickSort(nums, pivot + 1, right) } /* Select the median of three candidate elements */ fun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int { val l = nums[left] val m = nums[mid] val r = nums[right] if ((m in l..r) || (m in r..l)) return mid // m is between l and r if ((l in m..r) || (l in r..m)) return left // l is between m and r return right } /* Sentinel partition (median of three) */ fun partitionMedian(nums: IntArray, left: Int, right: Int): Int { // Select the median of three candidate elements val med = medianThree(nums, left, (left + right) / 2, right) // Swap the median to the array's leftmost position swap(nums, left, med) // Use nums[left] as the pivot var i = left var j = right while (i < j) { while (i < j && nums[j] >= nums[left]) j-- // Search from right to left for the first element smaller than the pivot while (i < j && nums[i] <= nums[left]) i++ // Search from left to right for the first element greater than the pivot swap(nums, i, j) // Swap these two elements } swap(nums, i, left) // Swap the pivot to the boundary between the two subarrays return i // Return the index of the pivot } /* Quick sort */ fun quickSortMedian(nums: IntArray, left: Int, right: Int) { // Terminate recursion when subarray length is 1 if (left >= right) return // Sentinel partition val pivot = partitionMedian(nums, left, right) // Recursively process the left subarray and right subarray quickSort(nums, left, pivot - 1) quickSort(nums, pivot + 1, right) } /* Quick sort (recursion depth optimization) */ fun quickSortTailCall(nums: IntArray, left: Int, right: Int) { // Terminate when subarray length is 1 var l = left var r = right while (l < r) { // Sentinel partition operation val pivot = partition(nums, l, r) // Perform quick sort on the shorter of the two subarrays if (pivot - l < r - pivot) { quickSort(nums, l, pivot - 1) // Recursively sort the left subarray l = pivot + 1 // Remaining unsorted interval is [pivot + 1, right] } else { quickSort(nums, pivot + 1, r) // Recursively sort the right subarray r = pivot - 1 // Remaining unsorted interval is [left, pivot - 1] } } } /* Driver Code */ fun main() { /* Quick sort */ val nums = intArrayOf(2, 4, 1, 0, 3, 5) quickSort(nums, 0, nums.size - 1) println("After quick sort, nums = ${nums.contentToString()}") /* Quick sort (recursion depth optimization) */ val nums1 = intArrayOf(2, 4, 1, 0, 3, 5) quickSortMedian(nums1, 0, nums1.size - 1) println("After quick sort (median pivot optimization), nums1 = ${nums1.contentToString()}") /* Quick sort (recursion depth optimization) */ val nums2 = intArrayOf(2, 4, 1, 0, 3, 5) quickSortTailCall(nums2, 0, nums2.size - 1) println("After quick sort (recursion depth optimization), nums2 = ${nums2.contentToString()}") } ================================================ FILE: en/codes/kotlin/chapter_sorting/radix_sort.kt ================================================ /** * File: radix_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* Get the k-th digit of element num, where exp = 10^(k-1) */ fun digit(num: Int, exp: Int): Int { // Passing exp instead of k can avoid repeated expensive exponentiation here return (num / exp) % 10 } /* Counting sort (based on nums k-th digit) */ fun countingSortDigit(nums: IntArray, exp: Int) { // Decimal digit range is 0~9, therefore need a bucket array of length 10 val counter = IntArray(10) val n = nums.size // Count the occurrence of digits 0~9 for (i in 0.. m) m = num var exp = 1 // Traverse from the lowest to the highest digit while (exp <= m) { // Perform counting sort on the k-th digit of array elements // k = 1 -> exp = 1 // k = 2 -> exp = 10 // i.e., exp = 10^(k-1) countingSortDigit(nums, exp) exp *= 10 } } /* Driver Code */ fun main() { // Radix sort val nums = intArrayOf( 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ) radixSort(nums) println("After radix sort, nums = ${nums.contentToString()}") } ================================================ FILE: en/codes/kotlin/chapter_sorting/selection_sort.kt ================================================ /** * File: selection_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* Selection sort */ fun selectionSort(nums: IntArray) { val n = nums.size // Outer loop: unsorted interval is [i, n-1] for (i in 0..() /* Get the length of the stack */ fun size(): Int { return stack.size } /* Check if the stack is empty */ fun isEmpty(): Boolean { return size() == 0 } /* Push */ fun push(num: Int) { stack.add(num) } /* Pop */ fun pop(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return stack.removeAt(size() - 1) } /* Return list for printing */ fun peek(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return stack[size() - 1] } /* Convert List to Array and return */ fun toArray(): Array { return stack.toTypedArray() } } /* Driver Code */ fun main() { /* Access top of the stack element */ val stack = ArrayStack() /* Elements push onto stack */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("Stack stack = ${stack.toArray().contentToString()}") /* Return list for printing */ val peek = stack.peek() println("Top element peek = $peek") /* Element pop from stack */ val pop = stack.pop() println("Pop element pop = $pop, after pop stack = ${stack.toArray().contentToString()}") /* Get the length of the stack */ val size = stack.size() println("Stack length size = $size") /* Check if empty */ val isEmpty = stack.isEmpty() println("Is stack empty = $isEmpty") } ================================================ FILE: en/codes/kotlin/chapter_stack_and_queue/deque.kt ================================================ /** * File: deque.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* Get the length of the double-ended queue */ val deque = LinkedList() deque.offerLast(3) deque.offerLast(2) deque.offerLast(5) println("Deque deque = $deque") /* Update element */ val peekFirst = deque.peekFirst() println("Front element peekFirst = $peekFirst") val peekLast = deque.peekLast() println("Rear element peekLast = $peekLast") /* Elements enqueue */ deque.offerLast(4) println("After element 4 enqueues at rear, deque = $deque") deque.offerFirst(1) println("After element 1 enqueues at front, deque = $deque") /* Element dequeue */ val popLast = deque.pollLast() println("Dequeue rear element = $popLast, after rear dequeue deque = $deque") val popFirst = deque.pollFirst() println("Dequeue front element = $popFirst, after front dequeue deque = $deque") /* Get the length of the double-ended queue */ val size = deque.size println("Deque length size = $size") /* Check if the double-ended queue is empty */ val isEmpty = deque.isEmpty() println("Is deque empty = $isEmpty") } ================================================ FILE: en/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt ================================================ /** * File: linkedlist_deque.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* Doubly linked list node */ class ListNode(var _val: Int) { // Node value var next: ListNode? = null // Successor node reference var prev: ListNode? = null // Predecessor node reference } /* Double-ended queue based on doubly linked list implementation */ class LinkedListDeque { private var front: ListNode? = null // Head node front private var rear: ListNode? = null // Tail node rear private var queSize: Int = 0 // Length of the double-ended queue /* Get the length of the double-ended queue */ fun size(): Int { return queSize } /* Check if the double-ended queue is empty */ fun isEmpty(): Boolean { return size() == 0 } /* Enqueue operation */ fun push(num: Int, isFront: Boolean) { val node = ListNode(num) // If the linked list is empty, make both front and rear point to node if (isEmpty()) { rear = node front = rear // Front of the queue enqueue operation } else if (isFront) { // Add node to the head of the linked list front?.prev = node node.next = front front = node // Update head node // Rear of the queue enqueue operation } else { // Add node to the tail of the linked list rear?.next = node node.prev = rear rear = node // Update tail node } queSize++ // Update queue length } /* Front of the queue enqueue */ fun pushFirst(num: Int) { push(num, true) } /* Rear of the queue enqueue */ fun pushLast(num: Int) { push(num, false) } /* Dequeue operation */ fun pop(isFront: Boolean): Int { if (isEmpty()) throw IndexOutOfBoundsException() val _val: Int // Temporarily store head node value if (isFront) { _val = front!!._val // Delete head node // Delete head node val fNext = front!!.next if (fNext != null) { fNext.prev = null front!!.next = null } front = fNext // Update head node // Temporarily store tail node value } else { _val = rear!!._val // Delete tail node // Update tail node val rPrev = rear!!.prev if (rPrev != null) { rPrev.next = null rear!!.prev = null } rear = rPrev // Update tail node } queSize-- // Update queue length return _val } /* Rear of the queue dequeue */ fun popFirst(): Int { return pop(true) } /* Access rear of the queue element */ fun popLast(): Int { return pop(false) } /* Return list for printing */ fun peekFirst(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return front!!._val } /* Driver Code */ fun peekLast(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return rear!!._val } /* Return array for printing */ fun toArray(): IntArray { var node = front val res = IntArray(size()) for (i in res.indices) { res[i] = node!!._val node = node.next } return res } } /* Driver Code */ fun main() { /* Get the length of the double-ended queue */ val deque = LinkedListDeque() deque.pushLast(3) deque.pushLast(2) deque.pushLast(5) println("Deque deque = ${deque.toArray().contentToString()}") /* Update element */ val peekFirst = deque.peekFirst() println("Front element peekFirst = $peekFirst") val peekLast = deque.peekLast() println("Rear element peekLast = $peekLast") /* Elements enqueue */ deque.pushLast(4) println("After element 4 enqueues at rear, deque = ${deque.toArray().contentToString()}") deque.pushFirst(1) println("After element 1 enqueues at front, deque = ${deque.toArray().contentToString()}") /* Element dequeue */ val popLast = deque.popLast() println("Dequeue rear element = ${popLast}, after rear dequeue deque = ${deque.toArray().contentToString()}") val popFirst = deque.popFirst() println("Dequeue front element = ${popFirst}, after front dequeue deque = ${deque.toArray().contentToString()}") /* Get the length of the double-ended queue */ val size = deque.size() println("Deque length size = $size") /* Check if the double-ended queue is empty */ val isEmpty = deque.isEmpty() println("Is deque empty = $isEmpty") } ================================================ FILE: en/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt ================================================ /** * File: linkedlist_queue.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* Queue based on linked list implementation */ class LinkedListQueue( // Head node front, tail node rear private var front: ListNode? = null, private var rear: ListNode? = null, private var queSize: Int = 0 ) { /* Get the length of the queue */ fun size(): Int { return queSize } /* Check if the queue is empty */ fun isEmpty(): Boolean { return size() == 0 } /* Enqueue */ fun push(num: Int) { // Add num after the tail node val node = ListNode(num) // If the queue is empty, make both front and rear point to the node if (front == null) { front = node rear = node // If the queue is not empty, add the node after the tail node } else { rear?.next = node rear = node } queSize++ } /* Dequeue */ fun pop(): Int { val num = peek() // Delete head node front = front?.next queSize-- return num } /* Return list for printing */ fun peek(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return front!!._val } /* Convert linked list to Array and return */ fun toArray(): IntArray { var node = front val res = IntArray(size()) for (i in res.indices) { res[i] = node!!._val node = node.next } return res } } /* Driver Code */ fun main() { /* Access front of the queue element */ val queue = LinkedListQueue() /* Elements enqueue */ queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) println("Queue queue = ${queue.toArray().contentToString()}") /* Return list for printing */ val peek = queue.peek() println("Front element peek = $peek") /* Element dequeue */ val pop = queue.pop() println("Dequeue element pop = $pop, after dequeue queue = ${queue.toArray().contentToString()}") /* Get the length of the queue */ val size = queue.size() println("Queue length size = $size") /* Check if the queue is empty */ val isEmpty = queue.isEmpty() println("Is queue empty = $isEmpty") } ================================================ FILE: en/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt ================================================ /** * File: linkedlist_stack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* Stack based on linked list implementation */ class LinkedListStack( private var stackPeek: ListNode? = null, // Use head node as stack top private var stkSize: Int = 0 // Stack length ) { /* Get the length of the stack */ fun size(): Int { return stkSize } /* Check if the stack is empty */ fun isEmpty(): Boolean { return size() == 0 } /* Push */ fun push(num: Int) { val node = ListNode(num) node.next = stackPeek stackPeek = node stkSize++ } /* Pop */ fun pop(): Int? { val num = peek() stackPeek = stackPeek?.next stkSize-- return num } /* Return list for printing */ fun peek(): Int? { if (isEmpty()) throw IndexOutOfBoundsException() return stackPeek?._val } /* Convert List to Array and return */ fun toArray(): IntArray { var node = stackPeek val res = IntArray(size()) for (i in res.size - 1 downTo 0) { res[i] = node?._val!! node = node.next } return res } } /* Driver Code */ fun main() { /* Access top of the stack element */ val stack = LinkedListStack() /* Elements push onto stack */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("Stack stack = ${stack.toArray().contentToString()}") /* Return list for printing */ val peek = stack.peek()!! println("Top element peek = $peek") /* Element pop from stack */ val pop = stack.pop()!! println("Pop element pop = $pop, after pop stack = ${stack.toArray().contentToString()}") /* Get the length of the stack */ val size = stack.size() println("Stack length size = $size") /* Check if empty */ val isEmpty = stack.isEmpty() println("Is stack empty = $isEmpty") } ================================================ FILE: en/codes/kotlin/chapter_stack_and_queue/queue.kt ================================================ /** * File: queue.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* Access front of the queue element */ val queue = LinkedList() /* Elements enqueue */ queue.offer(1) queue.offer(3) queue.offer(2) queue.offer(5) queue.offer(4) println("Queue queue = $queue") /* Return list for printing */ val peek = queue.peek() println("Front element peek = $peek") /* Element dequeue */ val pop = queue.poll() println("Dequeue element pop = $pop, after dequeue queue = $queue") /* Get the length of the queue */ val size = queue.size println("Queue length size = $size") /* Check if the queue is empty */ val isEmpty = queue.isEmpty() println("Is queue empty = $isEmpty") } ================================================ FILE: en/codes/kotlin/chapter_stack_and_queue/stack.kt ================================================ /** * File: stack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* Access top of the stack element */ val stack = Stack() /* Elements push onto stack */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("Stack stack = $stack") /* Return list for printing */ val peek = stack.peek() println("Top element peek = $peek") /* Element pop from stack */ val pop = stack.pop() println("Pop element pop = $pop, after pop stack = $stack") /* Get the length of the stack */ val size = stack.size println("Stack length size = $size") /* Check if empty */ val isEmpty = stack.isEmpty() println("Is stack empty = $isEmpty") } ================================================ FILE: en/codes/kotlin/chapter_tree/array_binary_tree.kt ================================================ /** * File: array_binary_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* Binary tree class represented by array */ class ArrayBinaryTree(private val tree: MutableList) { /* List capacity */ fun size(): Int { return tree.size } /* Get value of node at index i */ fun _val(i: Int): Int? { // If index out of bounds, return null to represent empty position if (i < 0 || i >= size()) return null return tree[i] } /* Get index of left child node of node at index i */ fun left(i: Int): Int { return 2 * i + 1 } /* Get index of right child node of node at index i */ fun right(i: Int): Int { return 2 * i + 2 } /* Get index of parent node of node at index i */ fun parent(i: Int): Int { return (i - 1) / 2 } /* Level-order traversal */ fun levelOrder(): MutableList { val res = mutableListOf() // Traverse array directly for (i in 0..) { // If empty position, return if (_val(i) == null) return // Preorder traversal if ("pre" == order) res.add(_val(i)) dfs(left(i), order, res) // Inorder traversal if ("in" == order) res.add(_val(i)) dfs(right(i), order, res) // Postorder traversal if ("post" == order) res.add(_val(i)) } /* Preorder traversal */ fun preOrder(): MutableList { val res = mutableListOf() dfs(0, "pre", res) return res } /* Inorder traversal */ fun inOrder(): MutableList { val res = mutableListOf() dfs(0, "in", res) return res } /* Postorder traversal */ fun postOrder(): MutableList { val res = mutableListOf() dfs(0, "post", res) return res } } /* Driver Code */ fun main() { // Initialize binary tree // Here we use a function to generate binary tree directly from list val arr = mutableListOf(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15) val root = TreeNode.listToTree(arr) println("\nInitialize binary tree\n") println("Array representation of binary tree:") println(arr) println("Linked list representation of binary tree:") printTree(root) // Binary tree class represented by array val abt = ArrayBinaryTree(arr) // Access node val i = 1 val l = abt.left(i) val r = abt.right(i) val p = abt.parent(i) println("Current node index is $i, value is ${abt._val(i)}") println("Its left child index is $l, value is ${abt._val(l)}") println("Its right child index is $r, value is ${abt._val(r)}") println("Its parent node index is $p, value is ${abt._val(p)}") // Traverse tree var res = abt.levelOrder() println("\nLevel-order traversal is: $res") res = abt.preOrder() println("Pre-order traversal is: $res") res = abt.inOrder() println("In-order traversal is: $res") res = abt.postOrder() println("Post-order traversal is: $res") } ================================================ FILE: en/codes/kotlin/chapter_tree/avl_tree.kt ================================================ /** * File: avl_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree import kotlin.math.max /* AVL tree */ class AVLTree { var root: TreeNode? = null // Root node /* Get node height */ fun height(node: TreeNode?): Int { // Empty node height is -1, leaf node height is 0 return node?.height ?: -1 } /* Update node height */ private fun updateHeight(node: TreeNode?) { // Node height equals the height of the tallest subtree + 1 node?.height = max(height(node?.left), height(node?.right)) + 1 } /* Get balance factor */ fun balanceFactor(node: TreeNode?): Int { // Empty node balance factor is 0 if (node == null) return 0 // Node balance factor = left subtree height - right subtree height return height(node.left) - height(node.right) } /* Right rotation operation */ private fun rightRotate(node: TreeNode?): TreeNode { val child = node!!.left val grandChild = child!!.right // Using child as pivot, rotate node to the right child.right = node node.left = grandChild // Update node height updateHeight(node) updateHeight(child) // Return root node of subtree after rotation return child } /* Left rotation operation */ private fun leftRotate(node: TreeNode?): TreeNode { val child = node!!.right val grandChild = child!!.left // Using child as pivot, rotate node to the left child.left = node node.right = grandChild // Update node height updateHeight(node) updateHeight(child) // Return root node of subtree after rotation return child } /* Perform rotation operation to restore balance to this subtree */ private fun rotate(node: TreeNode): TreeNode { // Get balance factor of node val balanceFactor = balanceFactor(node) // Left-leaning tree if (balanceFactor > 1) { if (balanceFactor(node.left) >= 0) { // Right rotation return rightRotate(node) } else { // First left rotation then right rotation node.left = leftRotate(node.left) return rightRotate(node) } } // Right-leaning tree if (balanceFactor < -1) { if (balanceFactor(node.right) <= 0) { // Left rotation return leftRotate(node) } else { // First right rotation then left rotation node.right = rightRotate(node.right) return leftRotate(node) } } // Balanced tree, no rotation needed, return directly return node } /* Insert node */ fun insert(_val: Int) { root = insertHelper(root, _val) } /* Recursively insert node (helper method) */ private fun insertHelper(n: TreeNode?, _val: Int): TreeNode { if (n == null) return TreeNode(_val) var node = n /* 1. Find insertion position and insert node */ if (_val < node._val) node.left = insertHelper(node.left, _val) else if (_val > node._val) node.right = insertHelper(node.right, _val) else return node // Duplicate node not inserted, return directly updateHeight(node) // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = rotate(node) // Return root node of subtree return node } /* Remove node */ fun remove(_val: Int) { root = removeHelper(root, _val) } /* Recursively delete node (helper method) */ private fun removeHelper(n: TreeNode?, _val: Int): TreeNode? { var node = n ?: return null /* 1. Find node and delete */ if (_val < node._val) node.left = removeHelper(node.left, _val) else if (_val > node._val) node.right = removeHelper(node.right, _val) else { if (node.left == null || node.right == null) { val child = if (node.left != null) node.left else node.right // Number of child nodes = 0, delete node directly and return if (child == null) return null // Number of child nodes = 1, delete node directly else node = child } else { // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it var temp = node.right while (temp!!.left != null) { temp = temp.left } node.right = removeHelper(node.right, temp._val) node._val = temp._val } } updateHeight(node) // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = rotate(node) // Return root node of subtree return node } /* Search node */ fun search(_val: Int): TreeNode? { var cur = root // Loop search, exit after passing leaf node while (cur != null) { // Target node is in cur's right subtree cur = if (cur._val < _val) cur.right!! // Target node is in cur's left subtree else if (cur._val > _val) cur.left // Found target node, exit loop else break } // Return target node return cur } } fun testInsert(tree: AVLTree, _val: Int) { tree.insert(_val) println("\nAfter inserting node $_val, AVL tree is") printTree(tree.root) } fun testRemove(tree: AVLTree, _val: Int) { tree.remove(_val) println("\nAfter deleting node $_val, AVL tree is") printTree(tree.root) } /* Driver Code */ fun main() { /* Please pay attention to how the AVL tree maintains balance after inserting nodes */ val avlTree = AVLTree() /* Insert node */ // Delete nodes 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) /* Please pay attention to how the AVL tree maintains balance after deleting nodes */ testInsert(avlTree, 7) /* Remove node */ // Delete node with degree 1 testRemove(avlTree, 8) // Delete node with degree 2 testRemove(avlTree, 5) // Remove node with degree 1 testRemove(avlTree, 4) // Remove node with degree 2 /* Search node */ val node = avlTree.search(7) println("\n Found node object is $node, node value = ${node?._val}") } ================================================ FILE: en/codes/kotlin/chapter_tree/binary_search_tree.kt ================================================ /** * File: binary_search_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* Binary search tree */ class BinarySearchTree { // Initialize empty tree private var root: TreeNode? = null /* Get binary tree root node */ fun getRoot(): TreeNode? { return root } /* Search node */ fun search(num: Int): TreeNode? { var cur = root // Loop search, exit after passing leaf node while (cur != null) { // Target node is in cur's right subtree cur = if (cur._val < num) cur.right // Target node is in cur's left subtree else if (cur._val > num) cur.left // Found target node, exit loop else break } // Return target node return cur } /* Insert node */ fun insert(num: Int) { // If tree is empty, initialize root node if (root == null) { root = TreeNode(num) return } var cur = root var pre: TreeNode? = null // Loop search, exit after passing leaf node while (cur != null) { // Found duplicate node, return directly if (cur._val == num) return pre = cur // Insertion position is in cur's right subtree cur = if (cur._val < num) cur.right // Insertion position is in cur's left subtree else cur.left } // Insert node val node = TreeNode(num) if (pre?._val!! < num) pre.right = node else pre.left = node } /* Remove node */ fun remove(num: Int) { // If tree is empty, return directly if (root == null) return var cur = root var pre: TreeNode? = null // Loop search, exit after passing leaf node while (cur != null) { // Found node to delete, exit loop if (cur._val == num) break pre = cur // Node to delete is in cur's right subtree cur = if (cur._val < num) cur.right // Node to delete is in cur's left subtree else cur.left } // If no node to delete, return directly if (cur == null) return // Number of child nodes = 0 or 1 if (cur.left == null || cur.right == null) { // When number of child nodes = 0 / 1, child = null / that child node val child = if (cur.left != null) cur.left else cur.right // Delete node cur if (cur != root) { if (pre!!.left == cur) pre.left = child else pre.right = child } else { // If deleted node is root node, reassign root node root = child } // Number of child nodes = 2 } else { // Get next node of cur in inorder traversal var tmp = cur.right while (tmp!!.left != null) { tmp = tmp.left } // Recursively delete node tmp remove(tmp._val) // Replace cur with tmp cur._val = tmp._val } } } /* Driver Code */ fun main() { /* Initialize binary search tree */ val bst = BinarySearchTree() // Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree val nums = intArrayOf(8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15) for (num in nums) { bst.insert(num) } println("\nInitialized binary tree is\n") printTree(bst.getRoot()) /* Search node */ val node = bst.search(7) println("Found node object is $node, node value = ${node?._val}") /* Insert node */ bst.insert(16) println("\nAfter inserting node 16, binary tree is\n") printTree(bst.getRoot()) /* Remove node */ bst.remove(1) println("\nAfter removing node 1, binary tree is\n") printTree(bst.getRoot()) bst.remove(2) println("\nAfter removing node 2, binary tree is\n") printTree(bst.getRoot()) bst.remove(4) println("\nAfter removing node 4, binary tree is\n") printTree(bst.getRoot()) } ================================================ FILE: en/codes/kotlin/chapter_tree/binary_tree.kt ================================================ /** * File: binary_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* Driver Code */ fun main() { /* Initialize binary tree */ // Initialize nodes val n1 = TreeNode(1) val n2 = TreeNode(2) val n3 = TreeNode(3) val n4 = TreeNode(4) val n5 = TreeNode(5) // Build references (pointers) between nodes n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 println("\nInitialize binary tree\n") printTree(n1) /* Insert node P between n1 -> n2 */ val P = TreeNode(0) // Delete node n1.left = P P.left = n2 println("\nAfter inserting node P\n") printTree(n1) // Remove node P n1.left = n2 println("\nAfter removing node P\n") printTree(n1) } ================================================ FILE: en/codes/kotlin/chapter_tree/binary_tree_bfs.kt ================================================ /** * File: binary_tree_bfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree import java.util.* /* Level-order traversal */ fun levelOrder(root: TreeNode?): MutableList { // Initialize queue, add root node val queue = LinkedList() queue.add(root) // Initialize a list to save the traversal sequence val list = mutableListOf() while (queue.isNotEmpty()) { val node = queue.poll() // Dequeue list.add(node?._val!!) // Save node value if (node.left != null) queue.offer(node.left) // Left child node enqueue if (node.right != null) queue.offer(node.right) // Right child node enqueue } return list } /* Driver Code */ fun main() { /* Initialize binary tree */ // Here we use a function to generate binary tree directly from list val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) println("\nInitialize binary tree\n") printTree(root) /* Level-order traversal */ val list = levelOrder(root) println("\nLevel-order traversal node print sequence = $list") } ================================================ FILE: en/codes/kotlin/chapter_tree/binary_tree_dfs.kt ================================================ /** * File: binary_tree_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree // Initialize list for storing traversal sequence var list = mutableListOf() /* Preorder traversal */ fun preOrder(root: TreeNode?) { if (root == null) return // Visit priority: root node -> left subtree -> right subtree list.add(root._val) preOrder(root.left) preOrder(root.right) } /* Inorder traversal */ fun inOrder(root: TreeNode?) { if (root == null) return // Visit priority: left subtree -> root node -> right subtree inOrder(root.left) list.add(root._val) inOrder(root.right) } /* Postorder traversal */ fun postOrder(root: TreeNode?) { if (root == null) return // Visit priority: left subtree -> right subtree -> root node postOrder(root.left) postOrder(root.right) list.add(root._val) } /* Driver Code */ fun main() { /* Initialize binary tree */ // Here we use a function to generate binary tree directly from list val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) println("\nInitialize binary tree\n") printTree(root) /* Preorder traversal */ list.clear() preOrder(root) println("\nPre-order traversal node print sequence = $list") /* Inorder traversal */ list.clear() inOrder(root) println("\nIn-order traversal node print sequence = $list") /* Postorder traversal */ list.clear() postOrder(root) println("\nPost-order traversal node print sequence = $list") } ================================================ FILE: en/codes/kotlin/utils/ListNode.kt ================================================ /** * File: ListNode.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* Linked list node */ class ListNode(var _val: Int) { var next: ListNode? = null companion object { /* Deserialize a list into a linked list */ fun arrToLinkedList(arr: IntArray): ListNode? { val dum = ListNode(0) var head = dum for (_val in arr) { head.next = ListNode(_val) head = head.next!! } return dum.next } } } ================================================ FILE: en/codes/kotlin/utils/PrintUtil.kt ================================================ /** * File: PrintUtil.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils import java.util.* class Trunk(var prev: Trunk?, var str: String) /* Print matrix (Array) */ fun printMatrix(matrix: Array>) { println("[") for (row in matrix) { println(" $row,") } println("]") } /* Print matrix (List) */ fun printMatrix(matrix: MutableList>) { println("[") for (row in matrix) { println(" $row,") } println("]") } /* Print linked list */ fun printLinkedList(h: ListNode?) { var head = h val list = mutableListOf() while (head != null) { list.add(head._val.toString()) head = head.next } println(list.joinToString(separator = " -> ")) } /* Print binary tree */ fun printTree(root: TreeNode?) { printTree(root, null, false) } /** * Print binary tree * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ fun printTree(root: TreeNode?, prev: Trunk?, isRight: Boolean) { if (root == null) { return } var prevStr = " " val trunk = Trunk(prev, prevStr) printTree(root.right, trunk, true) if (prev == null) { trunk.str = "———" } else if (isRight) { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev.str = prevStr } showTrunks(trunk) println(" ${root._val}") if (prev != null) { prev.str = prevStr } trunk.str = " |" printTree(root.left, trunk, false) } fun showTrunks(p: Trunk?) { if (p == null) { return } showTrunks(p.prev) print(p.str) } /* Print hash table */ fun printHashMap(map: Map) { for ((key, value) in map) { println("${key.toString()} -> $value") } } /* Print heap */ fun printHeap(queue: Queue?) { val list = mutableListOf() queue?.let { list.addAll(it) } print("Heap array representation:") println(list) println("Heap tree representation:") val root = TreeNode.listToTree(list) printTree(root) } ================================================ FILE: en/codes/kotlin/utils/TreeNode.kt ================================================ /** * File: TreeNode.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* Binary tree node class */ /* Constructor */ class TreeNode( var _val: Int // Node value ) { var height: Int = 0 // Node height var left: TreeNode? = null // Reference to left child node var right: TreeNode? = null // Reference to right child node // For the serialization encoding rules, please refer to: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Array representation of binary tree: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // Linked list representation of binary tree: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* Deserialize a list into a binary tree: recursion */ companion object { private fun listToTreeDFS(arr: MutableList, i: Int): TreeNode? { if (i < 0 || i >= arr.size || arr[i] == null) { return null } val root = TreeNode(arr[i]!!) root.left = listToTreeDFS(arr, 2 * i + 1) root.right = listToTreeDFS(arr, 2 * i + 2) return root } /* Deserialize a list into a binary tree */ fun listToTree(arr: MutableList): TreeNode? { return listToTreeDFS(arr, 0) } /* Serialize a binary tree into a list: recursion */ private fun treeToListDFS(root: TreeNode?, i: Int, res: MutableList) { if (root == null) return while (i >= res.size) { res.add(null) } res[i] = root._val treeToListDFS(root.left, 2 * i + 1, res) treeToListDFS(root.right, 2 * i + 2, res) } /* Serialize a binary tree into a list */ fun treeToList(root: TreeNode?): MutableList { val res = mutableListOf() treeToListDFS(root, 0, res) return res } } } ================================================ FILE: en/codes/kotlin/utils/Vertex.kt ================================================ /** * File: Vertex.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* Vertex class */ class Vertex(val _val: Int) { companion object { /* Input value list vals, return vertex list vets */ fun valsToVets(vals: IntArray): Array { val vets = arrayOfNulls(vals.size) for (i in vals.indices) { vets[i] = Vertex(vals[i]) } return vets } /* Input vertex list vets, return value list vals */ fun vetsToVals(vets: MutableList): MutableList { val vals = mutableListOf() for (vet in vets) { vals.add(vet!!._val) } return vals } } } ================================================ FILE: en/codes/python/.gitignore ================================================ __pycache__ ================================================ FILE: en/codes/python/chapter_array_and_linkedlist/array.py ================================================ """ File: array.py Created Time: 2022-11-25 Author: krahets (krahets@163.com) """ import random def random_access(nums: list[int]) -> int: """Random access to element""" # Randomly select a number from the interval [0, len(nums)-1] random_index = random.randint(0, len(nums) - 1) # Retrieve and return the random element random_num = nums[random_index] return random_num # Please note that Python's list is a dynamic array and can be extended directly # For learning purposes, this function treats the list as an array with immutable length def extend(nums: list[int], enlarge: int) -> list[int]: """Extend array length""" # Initialize an array with extended length res = [0] * (len(nums) + enlarge) # Copy all elements from the original array to the new array for i in range(len(nums)): res[i] = nums[i] # Return the extended new array return res def insert(nums: list[int], num: int, index: int): """Insert element num at index index in the array""" # Move all elements at and after index index backward by one position for i in range(len(nums) - 1, index, -1): nums[i] = nums[i - 1] # Assign num to the element at index index nums[index] = num def remove(nums: list[int], index: int): """Remove the element at index index""" # Move all elements after index index forward by one position for i in range(index, len(nums) - 1): nums[i] = nums[i + 1] def traverse(nums: list[int]): """Traverse array""" count = 0 # Traverse array by index for i in range(len(nums)): count += nums[i] # Direct traversal of array elements for num in nums: count += num # Traverse simultaneously data index and elements for i, num in enumerate(nums): count += nums[i] count += num def find(nums: list[int], target: int) -> int: """Find the specified element in the array""" for i in range(len(nums)): if nums[i] == target: return i return -1 """Driver Code""" if __name__ == "__main__": # Initialize array arr = [0] * 5 print("Array arr =", arr) nums = [1, 3, 2, 5, 4] print("Array nums =", nums) # Random access random_num: int = random_access(nums) print("Get random element from nums", random_num) # Length extension nums: list[int] = extend(nums, 3) print("Extend the array length to 8, get nums =", nums) # Insert element insert(nums, 6, 3) print("Insert number 6 at index 3, get nums =", nums) # Remove element remove(nums, 2) print("Remove the element at index 2, get nums =", nums) # Traverse array traverse(nums) # Find element index: int = find(nums, 3) print("Search for element 3 in nums, get index =", index) ================================================ FILE: en/codes/python/chapter_array_and_linkedlist/linked_list.py ================================================ """ 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): """Insert node P after node n0 in the linked list""" n1 = n0.next P.next = n1 n0.next = P def remove(n0: ListNode): """Remove the first node after node n0 in the linked list""" 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: """Access the node at index index in the linked list""" for _ in range(index): if not head: return None head = head.next return head def find(head: ListNode, target: int) -> int: """Find the first node with value target in the linked list""" index = 0 while head: if head.val == target: return index head = head.next index += 1 return -1 """Driver Code""" if __name__ == "__main__": # Initialize linked list # Initialize each node n0 = ListNode(1) n1 = ListNode(3) n2 = ListNode(2) n3 = ListNode(5) n4 = ListNode(4) # Build references between nodes n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 print("The initialized linked list is") print_linked_list(n0) # Insert node p = ListNode(0) insert(n0, p) print("The linked list after inserting a node is") print_linked_list(n0) # Remove node remove(n0) print("The linked list after removing a node is") print_linked_list(n0) # Access node node: ListNode = access(n0, 3) print("The value of the node at index 3 in the linked list = {}".format(node.val)) # Find node index: int = find(n0, 2) print("The index of the node with value 2 in the linked list = {}".format(index)) ================================================ FILE: en/codes/python/chapter_array_and_linkedlist/list.py ================================================ """ File: list.py Created Time: 2022-11-25 Author: krahets (krahets@163.com) """ """Driver Code""" if __name__ == "__main__": # Initialize list nums: list[int] = [1, 3, 2, 5, 4] print("\nList nums =", nums) # Access element x: int = nums[1] print("\nAccess the element at index 1, get x =", x) # Update element nums[1] = 0 print("\nUpdate the element at index 1 to 0, get nums =", nums) # Clear list nums.clear() print("\nAfter clearing the list nums =", nums) # Add elements at the end nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) print("\nAfter adding elements nums =", nums) # Insert element in the middle nums.insert(3, 6) print("\nInsert number 6 at index 3, get nums =", nums) # Remove element nums.pop(3) print("\nRemove the element at index 3, get nums =", nums) # Traverse list by index count = 0 for i in range(len(nums)): count += nums[i] # Direct traversal of list elements for num in nums: count += num # Concatenate two lists nums1 = [6, 8, 7, 10, 9] nums += nums1 print("\nConcatenate list nums1 to nums, get nums =", nums) # Sort list nums.sort() print("\nAfter sorting the list nums =", nums) ================================================ FILE: en/codes/python/chapter_array_and_linkedlist/my_list.py ================================================ """ File: my_list.py Created Time: 2022-11-25 Author: krahets (krahets@163.com) """ class MyList: """List class""" def __init__(self): """Constructor""" self._capacity: int = 10 # List capacity self._arr: list[int] = [0] * self._capacity # Array (stores list elements) self._size: int = 0 # List length (current number of elements) self._extend_ratio: int = 2 # Multiple by which the list capacity is extended each time def size(self) -> int: """Get list length (current number of elements)""" return self._size def capacity(self) -> int: """Get list capacity""" return self._capacity def get(self, index: int) -> int: """Access element""" # If the index is out of bounds, throw an exception, as below if index < 0 or index >= self._size: raise IndexError("Index out of bounds") return self._arr[index] def set(self, num: int, index: int): """Update element""" if index < 0 or index >= self._size: raise IndexError("Index out of bounds") self._arr[index] = num def add(self, num: int): """Add element at the end""" # When the number of elements exceeds capacity, trigger the extension mechanism if self.size() == self.capacity(): self.extend_capacity() self._arr[self._size] = num self._size += 1 def insert(self, num: int, index: int): """Insert element in the middle""" if index < 0 or index >= self._size: raise IndexError("Index out of bounds") # When the number of elements exceeds capacity, trigger the extension mechanism if self._size == self.capacity(): self.extend_capacity() # Move all elements at and after index index backward by one position for j in range(self._size - 1, index - 1, -1): self._arr[j + 1] = self._arr[j] self._arr[index] = num # Update the number of elements self._size += 1 def remove(self, index: int) -> int: """Remove element""" if index < 0 or index >= self._size: raise IndexError("Index out of bounds") num = self._arr[index] # Move all elements after index index forward by one position for j in range(index, self._size - 1): self._arr[j] = self._arr[j + 1] # Update the number of elements self._size -= 1 # Return the removed element return num def extend_capacity(self): """Extend list capacity""" # Create a new array with length _extend_ratio times the original array, and copy the original array to the new array self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1) # Update list capacity self._capacity = len(self._arr) def to_array(self) -> list[int]: """Return list with valid length""" return self._arr[: self._size] """Driver Code""" if __name__ == "__main__": # Initialize list nums = MyList() # Add elements at the end nums.add(1) nums.add(3) nums.add(2) nums.add(5) nums.add(4) print(f"List nums = {nums.to_array()}, capacity = {nums.capacity()}, length = {nums.size()}") # Insert element in the middle nums.insert(6, index=3) print("Insert number 6 at index 3, get nums =", nums.to_array()) # Remove element nums.remove(3) print("Remove the element at index 3, get nums =", nums.to_array()) # Access element num = nums.get(1) print("Access the element at index 1, get num =", num) # Update element nums.set(0, 1) print("Update the element at index 1 to 0, get nums =", nums.to_array()) # Test extension mechanism for i in range(10): # At i = 5, the list length will exceed the list capacity, triggering the extension mechanism at this point nums.add(i) print(f"List after extension {nums.to_array()}, capacity = {nums.capacity()}, length = {nums.size()}") ================================================ FILE: en/codes/python/chapter_backtracking/n_queens.py ================================================ """ 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], ): """Backtracking algorithm: N queens""" # When all rows are placed, record the solution if row == n: res.append([list(row) for row in state]) return # Traverse all columns for col in range(n): # Calculate the main diagonal and anti-diagonal corresponding to this cell diag1 = row - col + n - 1 diag2 = row + col # Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell if not cols[col] and not diags1[diag1] and not diags2[diag2]: # Attempt: place the queen in this cell state[row][col] = "Q" cols[col] = diags1[diag1] = diags2[diag2] = True # Place the next row backtrack(row + 1, n, state, res, cols, diags1, diags2) # Backtrack: restore this cell to an empty cell state[row][col] = "#" cols[col] = diags1[diag1] = diags2[diag2] = False def n_queens(n: int) -> list[list[list[str]]]: """Solve N queens""" # Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell state = [["#" for _ in range(n)] for _ in range(n)] cols = [False] * n # Record whether there is a queen in the column diags1 = [False] * (2 * n - 1) # Record whether there is a queen on the main diagonal diags2 = [False] * (2 * n - 1) # Record whether there is a queen on the anti-diagonal res = [] backtrack(0, n, state, res, cols, diags1, diags2) return res """Driver Code""" if __name__ == "__main__": n = 4 res = n_queens(n) print(f"Input chessboard size is {n}") print(f"There are {len(res)} queen placement solutions") for state in res: print("--------------------") for row in state: print(row) ================================================ FILE: en/codes/python/chapter_backtracking/permutations_i.py ================================================ """ 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]] ): """Backtracking algorithm: Permutations I""" # When the state length equals the number of elements, record the solution if len(state) == len(choices): res.append(list(state)) return # Traverse all choices for i, choice in enumerate(choices): # Pruning: do not allow repeated selection of elements if not selected[i]: # Attempt: make choice, update state selected[i] = True state.append(choice) # Proceed to the next round of selection backtrack(state, choices, selected, res) # Backtrack: undo choice, restore to previous state selected[i] = False state.pop() def permutations_i(nums: list[int]) -> list[list[int]]: """Permutations I""" res = [] backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) return res """Driver Code""" if __name__ == "__main__": nums = [1, 2, 3] res = permutations_i(nums) print(f"Input array nums = {nums}") print(f"All permutations res = {res}") ================================================ FILE: en/codes/python/chapter_backtracking/permutations_ii.py ================================================ """ 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]] ): """Backtracking algorithm: Permutations II""" # When the state length equals the number of elements, record the solution if len(state) == len(choices): res.append(list(state)) return # Traverse all choices duplicated = set[int]() for i, choice in enumerate(choices): # Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if not selected[i] and choice not in duplicated: # Attempt: make choice, update state duplicated.add(choice) # Record the selected element value selected[i] = True state.append(choice) # Proceed to the next round of selection backtrack(state, choices, selected, res) # Backtrack: undo choice, restore to previous state selected[i] = False state.pop() def permutations_ii(nums: list[int]) -> list[list[int]]: """Permutations II""" res = [] backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) return res """Driver Code""" if __name__ == "__main__": nums = [1, 2, 2] res = permutations_ii(nums) print(f"Input array nums = {nums}") print(f"All permutations res = {res}") ================================================ FILE: en/codes/python/chapter_backtracking/preorder_traversal_i_compact.py ================================================ """ 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): """Preorder traversal: Example 1""" if root is None: return if root.val == 7: # Record solution res.append(root) pre_order(root.left) pre_order(root.right) """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\nInitialize binary tree") print_tree(root) # Preorder traversal res = list[TreeNode]() pre_order(root) print("\nOutput all nodes with value 7") print([node.val for node in res]) ================================================ FILE: en/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py ================================================ """ 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): """Preorder traversal: Example 2""" if root is None: return # Attempt path.append(root) if root.val == 7: # Record solution res.append(list(path)) pre_order(root.left) pre_order(root.right) # Backtrack path.pop() """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\nInitialize binary tree") print_tree(root) # Preorder traversal path = list[TreeNode]() res = list[list[TreeNode]]() pre_order(root) print("\nOutput all paths from root node to node 7") for path in res: print([node.val for node in path]) ================================================ FILE: en/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py ================================================ """ 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): """Preorder traversal: Example 3""" # Pruning if root is None or root.val == 3: return # Attempt path.append(root) if root.val == 7: # Record solution res.append(list(path)) pre_order(root.left) pre_order(root.right) # Backtrack path.pop() """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\nInitialize binary tree") print_tree(root) # Preorder traversal path = list[TreeNode]() res = list[list[TreeNode]]() pre_order(root) print("\nOutput all paths from root node to node 7, excluding paths with nodes of value 3") for path in res: print([node.val for node in path]) ================================================ FILE: en/codes/python/chapter_backtracking/preorder_traversal_iii_template.py ================================================ """ 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: """Check if the current state is a solution""" return state and state[-1].val == 7 def record_solution(state: list[TreeNode], res: list[list[TreeNode]]): """Record solution""" res.append(list(state)) def is_valid(state: list[TreeNode], choice: TreeNode) -> bool: """Check if the choice is valid under the current state""" return choice is not None and choice.val != 3 def make_choice(state: list[TreeNode], choice: TreeNode): """Update state""" state.append(choice) def undo_choice(state: list[TreeNode], choice: TreeNode): """Restore state""" state.pop() def backtrack( state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]] ): """Backtracking algorithm: Example 3""" # Check if it is a solution if is_solution(state): # Record solution record_solution(state, res) # Traverse all choices for choice in choices: # Pruning: check if the choice is valid if is_valid(state, choice): # Attempt: make choice, update state make_choice(state, choice) # Proceed to the next round of selection backtrack(state, [choice.left, choice.right], res) # Backtrack: undo choice, restore to previous state undo_choice(state, choice) """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\nInitialize binary tree") print_tree(root) # Backtracking algorithm res = [] backtrack(state=[], choices=[root], res=res) print("\nOutput all paths from root node to node 7, excluding paths with nodes of value 3") for path in res: print([node.val for node in path]) ================================================ FILE: en/codes/python/chapter_backtracking/subset_sum_i.py ================================================ """ 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]] ): """Backtracking algorithm: Subset sum I""" # When the subset sum equals target, record the solution if target == 0: res.append(list(state)) return # Traverse all choices # Pruning 2: start traversing from start to avoid generating duplicate subsets for i in range(start, len(choices)): # Pruning 1: if the subset sum exceeds target, end the loop directly # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if target - choices[i] < 0: break # Attempt: make choice, update target, start state.append(choices[i]) # Proceed to the next round of selection backtrack(state, target - choices[i], choices, i, res) # Backtrack: undo choice, restore to previous state state.pop() def subset_sum_i(nums: list[int], target: int) -> list[list[int]]: """Solve subset sum I""" state = [] # State (subset) nums.sort() # Sort nums start = 0 # Start point for traversal res = [] # Result list (subset list) backtrack(state, target, nums, start, res) return res """Driver Code""" if __name__ == "__main__": nums = [3, 4, 5] target = 9 res = subset_sum_i(nums, target) print(f"Input array nums = {nums}, target = {target}") print(f"All subsets with sum equal to {target} res = {res}") ================================================ FILE: en/codes/python/chapter_backtracking/subset_sum_i_naive.py ================================================ """ 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]], ): """Backtracking algorithm: Subset sum I""" # When the subset sum equals target, record the solution if total == target: res.append(list(state)) return # Traverse all choices for i in range(len(choices)): # Pruning: if the subset sum exceeds target, skip this choice if total + choices[i] > target: continue # Attempt: make choice, update element sum total state.append(choices[i]) # Proceed to the next round of selection backtrack(state, target, total + choices[i], choices, res) # Backtrack: undo choice, restore to previous state state.pop() def subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]: """Solve subset sum I (including duplicate subsets)""" state = [] # State (subset) total = 0 # Subset sum res = [] # Result list (subset list) backtrack(state, target, total, nums, res) return res """Driver Code""" if __name__ == "__main__": nums = [3, 4, 5] target = 9 res = subset_sum_i_naive(nums, target) print(f"Input array nums = {nums}, target = {target}") print(f"All subsets with sum equal to {target} res = {res}") print(f"Please note that the result output by this method contains duplicate sets") ================================================ FILE: en/codes/python/chapter_backtracking/subset_sum_ii.py ================================================ """ 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]] ): """Backtracking algorithm: Subset sum II""" # When the subset sum equals target, record the solution if target == 0: res.append(list(state)) return # Traverse all choices # Pruning 2: start traversing from start to avoid generating duplicate subsets # Pruning 3: start traversing from start to avoid repeatedly selecting the same element for i in range(start, len(choices)): # Pruning 1: if the subset sum exceeds target, end the loop directly # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if target - choices[i] < 0: break # Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly if i > start and choices[i] == choices[i - 1]: continue # Attempt: make choice, update target, start state.append(choices[i]) # Proceed to the next round of selection backtrack(state, target - choices[i], choices, i + 1, res) # Backtrack: undo choice, restore to previous state state.pop() def subset_sum_ii(nums: list[int], target: int) -> list[list[int]]: """Solve subset sum II""" state = [] # State (subset) nums.sort() # Sort nums start = 0 # Start point for traversal res = [] # Result list (subset list) backtrack(state, target, nums, start, res) return res """Driver Code""" if __name__ == "__main__": nums = [4, 4, 5] target = 9 res = subset_sum_ii(nums, target) print(f"Input array nums = {nums}, target = {target}") print(f"All subsets with sum equal to {target} res = {res}") ================================================ FILE: en/codes/python/chapter_computational_complexity/iteration.py ================================================ """ File: iteration.py Created Time: 2023-08-24 Author: krahets (krahets@163.com) """ def for_loop(n: int) -> int: """for loop""" res = 0 # Sum 1, 2, ..., n-1, n for i in range(1, n + 1): res += i return res def while_loop(n: int) -> int: """while loop""" res = 0 i = 1 # Initialize condition variable # Sum 1, 2, ..., n-1, n while i <= n: res += i i += 1 # Update condition variable return res def while_loop_ii(n: int) -> int: """while loop (two updates)""" res = 0 i = 1 # Initialize condition variable # Sum 1, 4, 10, ... while i <= n: res += i # Update condition variable i += 1 i *= 2 return res def nested_for_loop(n: int) -> str: """Nested for loop""" res = "" # Loop i = 1, 2, ..., n-1, n for i in range(1, n + 1): # Loop 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"\nSum result of for loop res = {res}") res = while_loop(n) print(f"\nSum result of while loop res = {res}") res = while_loop_ii(n) print(f"\nSum result of while loop (two updates) res = {res}") res = nested_for_loop(n) print(f"\nTraversal result of nested for loop {res}") ================================================ FILE: en/codes/python/chapter_computational_complexity/recursion.py ================================================ """ File: recursion.py Created Time: 2023-08-24 Author: krahets (krahets@163.com) """ def recur(n: int) -> int: """Recursion""" # Termination condition if n == 1: return 1 # Recurse: recursive call res = recur(n - 1) # Return: return result return n + res def for_loop_recur(n: int) -> int: """Simulate recursion using iteration""" # Use an explicit stack to simulate the system call stack stack = [] res = 0 # Recurse: recursive call for i in range(n, 0, -1): # Simulate "recurse" with "push" stack.append(i) # Return: return result while stack: # Simulate "return" with "pop" res += stack.pop() # res = 1+2+3+...+n return res def tail_recur(n, res): """Tail recursion""" # Termination condition if n == 0: return res # Tail recursive call return tail_recur(n - 1, res + n) def fib(n: int) -> int: """Fibonacci sequence: recursion""" # Termination condition f(1) = 0, f(2) = 1 if n == 1 or n == 2: return n - 1 # Recursive call f(n) = f(n-1) + f(n-2) res = fib(n - 1) + fib(n - 2) # Return result f(n) return res """Driver Code""" if __name__ == "__main__": n = 5 res = recur(n) print(f"\nSum result of recursive function res = {res}") res = for_loop_recur(n) print(f"\nSum result of simulating recursion using iteration res = {res}") res = tail_recur(n, 0) print(f"\nSum result of tail recursive function res = {res}") res = fib(n) print(f"\nThe {n}th term of the Fibonacci sequence is {res}") ================================================ FILE: en/codes/python/chapter_computational_complexity/space_complexity.py ================================================ """ 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: """Function""" # Perform some operations return 0 def constant(n: int): """Constant order""" # Constants, variables, objects occupy O(1) space a = 0 nums = [0] * 10000 node = ListNode(0) # Variables in the loop occupy O(1) space for _ in range(n): c = 0 # Functions in the loop occupy O(1) space for _ in range(n): function() def linear(n: int): """Linear order""" # A list of length n occupies O(n) space nums = [0] * n # A hash table of length n occupies O(n) space hmap = dict[int, str]() for i in range(n): hmap[i] = str(i) def linear_recur(n: int): """Linear order (recursive implementation)""" print("Recursion n =", n) if n == 1: return linear_recur(n - 1) def quadratic(n: int): """Quadratic order""" # A 2D list occupies O(n^2) space num_matrix = [[0] * n for _ in range(n)] def quadratic_recur(n: int) -> int: """Quadratic order (recursive implementation)""" if n <= 0: return 0 # Array nums length is n, n-1, ..., 2, 1 nums = [0] * n return quadratic_recur(n - 1) def build_tree(n: int) -> TreeNode | None: """Exponential order (build full binary tree)""" 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 order constant(n) # Linear order linear(n) linear_recur(n) # Quadratic order quadratic(n) quadratic_recur(n) # Exponential order root = build_tree(n) print_tree(root) ================================================ FILE: en/codes/python/chapter_computational_complexity/time_complexity.py ================================================ """ File: time_complexity.py Created Time: 2022-11-25 Author: krahets (krahets@163.com) """ def constant(n: int) -> int: """Constant order""" count = 0 size = 100000 for _ in range(size): count += 1 return count def linear(n: int) -> int: """Linear order""" count = 0 for _ in range(n): count += 1 return count def array_traversal(nums: list[int]) -> int: """Linear order (traversing array)""" count = 0 # Number of iterations is proportional to the array length for num in nums: count += 1 return count def quadratic(n: int) -> int: """Quadratic order""" count = 0 # Number of iterations is quadratically related to the data size n for i in range(n): for j in range(n): count += 1 return count def bubble_sort(nums: list[int]) -> int: """Quadratic order (bubble sort)""" count = 0 # Counter # Outer loop: unsorted range is [0, i] for i in range(len(nums) - 1, 0, -1): # Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for j in range(i): if nums[j] > nums[j + 1]: # Swap nums[j] and nums[j + 1] tmp: int = nums[j] nums[j] = nums[j + 1] nums[j + 1] = tmp count += 3 # Element swap includes 3 unit operations return count def exponential(n: int) -> int: """Exponential order (loop implementation)""" count = 0 base = 1 # Cells divide into two every round, forming sequence 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: """Exponential order (recursive implementation)""" if n == 1: return 1 return exp_recur(n - 1) + exp_recur(n - 1) + 1 def logarithmic(n: int) -> int: """Logarithmic order (loop implementation)""" count = 0 while n > 1: n = n / 2 count += 1 return count def log_recur(n: int) -> int: """Logarithmic order (recursive implementation)""" if n <= 1: return 0 return log_recur(n / 2) + 1 def linear_log_recur(n: int) -> int: """Linearithmic order""" if n <= 1: return 1 # Divide into two, the scale of subproblems is reduced by half count = linear_log_recur(n // 2) + linear_log_recur(n // 2) # Current subproblem contains n operations for _ in range(n): count += 1 return count def factorial_recur(n: int) -> int: """Factorial order (recursive implementation)""" if n == 0: return 1 count = 0 # Split from 1 into n for _ in range(n): count += factorial_recur(n - 1) return count """Driver Code""" if __name__ == "__main__": # You can modify n to run and observe the trend of the number of operations for various complexities n = 8 print("Input data size n =", n) count = constant(n) print("Number of operations of constant order =", count) count = linear(n) print("Number of operations of linear order =", count) count = array_traversal([0] * n) print("Number of operations of linear order (traversing array) =", count) count = quadratic(n) print("Number of operations of quadratic order =", count) nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1] count = bubble_sort(nums) print("Number of operations of quadratic order (bubble sort) =", count) count = exponential(n) print("Number of operations of exponential order (loop implementation) =", count) count = exp_recur(n) print("Number of operations of exponential order (recursive implementation) =", count) count = logarithmic(n) print("Number of operations of logarithmic order (loop implementation) =", count) count = log_recur(n) print("Number of operations of logarithmic order (recursive implementation) =", count) count = linear_log_recur(n) print("Number of operations of linearithmic order (recursive implementation) =", count) count = factorial_recur(n) print("Number of operations of factorial order (recursive implementation) =", count) ================================================ FILE: en/codes/python/chapter_computational_complexity/worst_best_time_complexity.py ================================================ """ 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]: """Generate an array with elements: 1, 2, ..., n, shuffled in order""" # Generate array nums =: 1, 2, 3, ..., n nums = [i for i in range(1, n + 1)] # Randomly shuffle array elements random.shuffle(nums) return nums def find_one(nums: list[int]) -> int: """Find the index of number 1 in array nums""" for i in range(len(nums)): # When element 1 is at the head of the array, best time complexity O(1) is achieved # When element 1 is at the tail of the array, worst time complexity O(n) is achieved 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("\nArray [ 1, 2, ..., n ] after being shuffled =", nums) print("The index of number 1 is", index) ================================================ FILE: en/codes/python/chapter_divide_and_conquer/binary_search_recur.py ================================================ """ 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: """Binary search: problem f(i, j)""" # If the interval is empty, it means there is no target element, return -1 if i > j: return -1 # Calculate the midpoint index m m = (i + j) // 2 if nums[m] < target: # Recursion subproblem f(m+1, j) return dfs(nums, target, m + 1, j) elif nums[m] > target: # Recursion subproblem f(i, m-1) return dfs(nums, target, i, m - 1) else: # Found the target element, return its index return m def binary_search(nums: list[int], target: int) -> int: """Binary search""" n = len(nums) # Solve the problem f(0, n-1) return dfs(nums, target, 0, n - 1) """Driver Code""" if __name__ == "__main__": target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # Binary search (closed interval on both sides) index = binary_search(nums, target) print("Index of target element 6 = ", index) ================================================ FILE: en/codes/python/chapter_divide_and_conquer/build_tree.py ================================================ """ 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: """Build binary tree: divide and conquer""" # Terminate when the subtree interval is empty if r - l < 0: return None # Initialize the root node root = TreeNode(preorder[i]) # Query m to divide the left and right subtrees m = inorder_map[preorder[i]] # Subproblem: build the left subtree root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) # Subproblem: build the right subtree root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) # Return the root node return root def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None: """Build binary tree""" # Initialize hash map, storing the mapping from inorder elements to indices inorder_map = {val: i for i, val in enumerate(inorder)} root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) return root """Driver Code""" if __name__ == "__main__": preorder = [3, 9, 2, 1, 7] inorder = [9, 3, 1, 2, 7] print(f"Preorder traversal = {preorder}") print(f"Inorder traversal = {inorder}") root = build_tree(preorder, inorder) print("The built binary tree is:") print_tree(root) ================================================ FILE: en/codes/python/chapter_divide_and_conquer/hanota.py ================================================ """ File: hanota.py Created Time: 2023-07-16 Author: krahets (krahets@163.com) """ def move(src: list[int], tar: list[int]): """Move a disk""" # Take out a disk from the top of src pan = src.pop() # Place the disk on top of tar tar.append(pan) def dfs(i: int, src: list[int], buf: list[int], tar: list[int]): """Solve the Tower of Hanoi problem f(i)""" # If there is only one disk left in src, move it directly to tar if i == 1: move(src, tar) return # Subproblem f(i-1): move the top i-1 disks from src to buf using tar dfs(i - 1, src, tar, buf) # Subproblem f(1): move the remaining disk from src to tar move(src, tar) # Subproblem f(i-1): move the top i-1 disks from buf to tar using src dfs(i - 1, buf, src, tar) def solve_hanota(A: list[int], B: list[int], C: list[int]): """Solve the Tower of Hanoi problem""" n = len(A) # Move the top n disks from A to C using B dfs(n, A, B, C) """Driver Code""" if __name__ == "__main__": # The tail of the list is the top of the rod A = [5, 4, 3, 2, 1] B = [] C = [] print("Initial state:") print(f"A = {A}") print(f"B = {B}") print(f"C = {C}") solve_hanota(A, B, C) print("After moving the disks:") print(f"A = {A}") print(f"B = {B}") print(f"C = {C}") ================================================ FILE: en/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py ================================================ """ 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: """Backtracking""" # When climbing to the n-th stair, add 1 to the solution count if state == n: res[0] += 1 # Traverse all choices for choice in choices: # Pruning: not allowed to go beyond the n-th stair if state + choice > n: continue # Attempt: make a choice, update state backtrack(choices, state + choice, n, res) # Backtrack def climbing_stairs_backtrack(n: int) -> int: """Climbing stairs: Backtracking""" choices = [1, 2] # Can choose to climb up 1 or 2 stairs state = 0 # Start climbing from the 0-th stair res = [0] # Use res[0] to record the solution count backtrack(choices, state, n, res) return res[0] """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_backtrack(n) print(f"Climbing {n} stairs has {res} solutions") ================================================ FILE: en/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py ================================================ """ File: climbing_stairs_constraint_dp.py Created Time: 2023-06-30 Author: krahets (krahets@163.com) """ def climbing_stairs_constraint_dp(n: int) -> int: """Climbing stairs with constraint: Dynamic programming""" if n == 1 or n == 2: return 1 # Initialize dp table, used to store solutions to subproblems dp = [[0] * 3 for _ in range(n + 1)] # Initial state: preset the solution to the smallest subproblem dp[1][1], dp[1][2] = 1, 0 dp[2][1], dp[2][2] = 0, 1 # State transition: gradually solve larger subproblems from smaller ones for i in range(3, n + 1): dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] return dp[n][1] + dp[n][2] """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_constraint_dp(n) print(f"Climbing {n} stairs has {res} solutions") ================================================ FILE: en/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py ================================================ """ File: climbing_stairs_dfs.py Created Time: 2023-06-30 Author: krahets (krahets@163.com) """ def dfs(i: int) -> int: """Search""" # Known dp[1] and dp[2], return them 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: """Climbing stairs: Search""" return dfs(n) """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dfs(n) print(f"Climbing {n} stairs has {res} solutions") ================================================ FILE: en/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py ================================================ """ File: climbing_stairs_dfs_mem.py Created Time: 2023-06-30 Author: krahets (krahets@163.com) """ def dfs(i: int, mem: list[int]) -> int: """Memoization search""" # Known dp[1] and dp[2], return them if i == 1 or i == 2: return i # If record dp[i] exists, return it directly if mem[i] != -1: return mem[i] # dp[i] = dp[i-1] + dp[i-2] count = dfs(i - 1, mem) + dfs(i - 2, mem) # Record dp[i] mem[i] = count return count def climbing_stairs_dfs_mem(n: int) -> int: """Climbing stairs: Memoization search""" # mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record mem = [-1] * (n + 1) return dfs(n, mem) """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dfs_mem(n) print(f"Climbing {n} stairs has {res} solutions") ================================================ FILE: en/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py ================================================ """ File: climbing_stairs_dp.py Created Time: 2023-06-30 Author: krahets (krahets@163.com) """ def climbing_stairs_dp(n: int) -> int: """Climbing stairs: Dynamic programming""" if n == 1 or n == 2: return n # Initialize dp table, used to store solutions to subproblems dp = [0] * (n + 1) # Initial state: preset the solution to the smallest subproblem dp[1], dp[2] = 1, 2 # State transition: gradually solve larger subproblems from smaller ones 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: """Climbing stairs: Space-optimized dynamic programming""" 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 """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dp(n) print(f"Climbing {n} stairs has {res} solutions") res = climbing_stairs_dp_comp(n) print(f"Climbing {n} stairs has {res} solutions") ================================================ FILE: en/codes/python/chapter_dynamic_programming/coin_change.py ================================================ """ File: coin_change.py Created Time: 2023-07-10 Author: krahets (krahets@163.com) """ def coin_change_dp(coins: list[int], amt: int) -> int: """Coin change: Dynamic programming""" n = len(coins) MAX = amt + 1 # Initialize dp table dp = [[0] * (amt + 1) for _ in range(n + 1)] # State transition: first row and first column for a in range(1, amt + 1): dp[0][a] = MAX # State transition: rest of the rows and columns for i in range(1, n + 1): for a in range(1, amt + 1): if coins[i - 1] > a: # If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a] else: # The smaller value between not selecting and selecting coin 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: """Coin change: Space-optimized dynamic programming""" n = len(coins) MAX = amt + 1 # Initialize dp table dp = [MAX] * (amt + 1) dp[0] = 0 # State transition for i in range(1, n + 1): # Traverse in forward order for a in range(1, amt + 1): if coins[i - 1] > a: # If exceeds target amount, don't select coin i dp[a] = dp[a] else: # The smaller value between not selecting and selecting coin i dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) return dp[amt] if dp[amt] != MAX else -1 """Driver Code""" if __name__ == "__main__": coins = [1, 2, 5] amt = 4 # Dynamic programming res = coin_change_dp(coins, amt) print(f"The minimum number of coins needed to make up the target amount is {res}") # Space-optimized dynamic programming res = coin_change_dp_comp(coins, amt) print(f"The minimum number of coins needed to make up the target amount is {res}") ================================================ FILE: en/codes/python/chapter_dynamic_programming/coin_change_ii.py ================================================ """ 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: """Coin change II: Dynamic programming""" n = len(coins) # Initialize dp table dp = [[0] * (amt + 1) for _ in range(n + 1)] # Initialize first column for i in range(n + 1): dp[i][0] = 1 # State transition for i in range(1, n + 1): for a in range(1, amt + 1): if coins[i - 1] > a: # If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a] else: # Sum of the two options: not selecting and selecting coin 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: """Coin change II: Space-optimized dynamic programming""" n = len(coins) # Initialize dp table dp = [0] * (amt + 1) dp[0] = 1 # State transition for i in range(1, n + 1): # Traverse in forward order for a in range(1, amt + 1): if coins[i - 1] > a: # If exceeds target amount, don't select coin i dp[a] = dp[a] else: # Sum of the two options: not selecting and selecting coin i dp[a] = dp[a] + dp[a - coins[i - 1]] return dp[amt] """Driver Code""" if __name__ == "__main__": coins = [1, 2, 5] amt = 5 # Dynamic programming res = coin_change_ii_dp(coins, amt) print(f"The number of coin combinations to make up the target amount is {res}") # Space-optimized dynamic programming res = coin_change_ii_dp_comp(coins, amt) print(f"The number of coin combinations to make up the target amount is {res}") ================================================ FILE: en/codes/python/chapter_dynamic_programming/edit_distance.py ================================================ """ File: edit_distancde.py Created Time: 2023-07-04 Author: krahets (krahets@163.com) """ def edit_distance_dfs(s: str, t: str, i: int, j: int) -> int: """Edit distance: Brute-force search""" # If both s and t are empty, return 0 if i == 0 and j == 0: return 0 # If s is empty, return length of t if i == 0: return j # If t is empty, return length of s if j == 0: return i # If two characters are equal, skip both characters if s[i - 1] == t[j - 1]: return edit_distance_dfs(s, t, i - 1, j - 1) # Minimum edit steps = minimum edit steps of insert, delete, replace + 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 minimum edit steps return min(insert, delete, replace) + 1 def edit_distance_dfs_mem(s: str, t: str, mem: list[list[int]], i: int, j: int) -> int: """Edit distance: Memoization search""" # If both s and t are empty, return 0 if i == 0 and j == 0: return 0 # If s is empty, return length of t if i == 0: return j # If t is empty, return length of s if j == 0: return i # If there's a record, return it directly if mem[i][j] != -1: return mem[i][j] # If two characters are equal, skip both characters if s[i - 1] == t[j - 1]: return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) # Minimum edit steps = minimum edit steps of insert, delete, replace + 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) # Record and return minimum edit steps mem[i][j] = min(insert, delete, replace) + 1 return mem[i][j] def edit_distance_dp(s: str, t: str) -> int: """Edit distance: Dynamic programming""" n, m = len(s), len(t) dp = [[0] * (m + 1) for _ in range(n + 1)] # State transition: first row and first column for i in range(1, n + 1): dp[i][0] = i for j in range(1, m + 1): dp[0][j] = j # State transition: rest of the rows and columns for i in range(1, n + 1): for j in range(1, m + 1): if s[i - 1] == t[j - 1]: # If two characters are equal, skip both characters dp[i][j] = dp[i - 1][j - 1] else: # Minimum edit steps = minimum edit steps of insert, delete, replace + 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: """Edit distance: Space-optimized dynamic programming""" n, m = len(s), len(t) dp = [0] * (m + 1) # State transition: first row for j in range(1, m + 1): dp[j] = j # State transition: rest of the rows for i in range(1, n + 1): # State transition: first column leftup = dp[0] # Temporarily store dp[i-1, j-1] dp[0] += 1 # State transition: rest of the columns for j in range(1, m + 1): temp = dp[j] if s[i - 1] == t[j - 1]: # If two characters are equal, skip both characters dp[j] = leftup else: # Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[j] = min(dp[j - 1], dp[j], leftup) + 1 leftup = temp # Update for next round's dp[i-1, j-1] return dp[m] """Driver Code""" if __name__ == "__main__": s = "bag" t = "pack" n, m = len(s), len(t) # Brute-force search res = edit_distance_dfs(s, t, n, m) print(f"Changing {s} to {t} requires a minimum of {res} edits") # Memoization search mem = [[-1] * (m + 1) for _ in range(n + 1)] res = edit_distance_dfs_mem(s, t, mem, n, m) print(f"Changing {s} to {t} requires a minimum of {res} edits") # Dynamic programming res = edit_distance_dp(s, t) print(f"Changing {s} to {t} requires a minimum of {res} edits") # Space-optimized dynamic programming res = edit_distance_dp_comp(s, t) print(f"Changing {s} to {t} requires a minimum of {res} edits") ================================================ FILE: en/codes/python/chapter_dynamic_programming/knapsack.py ================================================ """ 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 knapsack: Brute-force search""" # If all items have been selected or knapsack has no remaining capacity, return value 0 if i == 0 or c == 0: return 0 # If exceeds knapsack capacity, can only choose not to put it in if wgt[i - 1] > c: return knapsack_dfs(wgt, val, i - 1, c) # Calculate the maximum value of not putting in and putting in item i no = knapsack_dfs(wgt, val, i - 1, c) yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] # Return the larger value of the two options 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 knapsack: Memoization search""" # If all items have been selected or knapsack has no remaining capacity, return value 0 if i == 0 or c == 0: return 0 # If there's a record, return it directly if mem[i][c] != -1: return mem[i][c] # If exceeds knapsack capacity, can only choose not to put it in if wgt[i - 1] > c: return knapsack_dfs_mem(wgt, val, mem, i - 1, c) # Calculate the maximum value of not putting in and putting in item 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] # Record and return the larger value of the two options mem[i][c] = max(no, yes) return mem[i][c] def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: """0-1 knapsack: Dynamic programming""" n = len(wgt) # Initialize dp table dp = [[0] * (cap + 1) for _ in range(n + 1)] # State transition for i in range(1, n + 1): for c in range(1, cap + 1): if wgt[i - 1] > c: # If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c] else: # The larger value between not selecting and selecting item 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 knapsack: Space-optimized dynamic programming""" n = len(wgt) # Initialize dp table dp = [0] * (cap + 1) # State transition for i in range(1, n + 1): # Traverse in reverse order for c in range(cap, 0, -1): if wgt[i - 1] > c: # If exceeds knapsack capacity, don't select item i dp[c] = dp[c] else: # The larger value between not selecting and selecting item i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) return dp[cap] """Driver Code""" if __name__ == "__main__": wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = len(wgt) # Brute-force search res = knapsack_dfs(wgt, val, n, cap) print(f"The maximum item value not exceeding knapsack capacity is {res}") # Memoization search mem = [[-1] * (cap + 1) for _ in range(n + 1)] res = knapsack_dfs_mem(wgt, val, mem, n, cap) print(f"The maximum item value not exceeding knapsack capacity is {res}") # Dynamic programming res = knapsack_dp(wgt, val, cap) print(f"The maximum item value not exceeding knapsack capacity is {res}") # Space-optimized dynamic programming res = knapsack_dp_comp(wgt, val, cap) print(f"The maximum item value not exceeding knapsack capacity is {res}") ================================================ FILE: en/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py ================================================ """ 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: """Minimum cost climbing stairs: Dynamic programming""" n = len(cost) - 1 if n == 1 or n == 2: return cost[n] # Initialize dp table, used to store solutions to subproblems dp = [0] * (n + 1) # Initial state: preset the solution to the smallest subproblem dp[1], dp[2] = cost[1], cost[2] # State transition: gradually solve larger subproblems from smaller ones 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: """Minimum cost climbing stairs: Space-optimized dynamic programming""" 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 """Driver Code""" if __name__ == "__main__": cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] print(f"Input stair cost list is {cost}") res = min_cost_climbing_stairs_dp(cost) print(f"The minimum cost to finish climbing the stairs is {res}") res = min_cost_climbing_stairs_dp_comp(cost) print(f"The minimum cost to finish climbing the stairs is {res}") ================================================ FILE: en/codes/python/chapter_dynamic_programming/min_path_sum.py ================================================ """ 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: """Minimum path sum: Brute-force search""" # If it's the top-left cell, terminate the search if i == 0 and j == 0: return grid[0][0] # If row or column index is out of bounds, return +∞ cost if i < 0 or j < 0: return inf # Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1) up = min_path_sum_dfs(grid, i - 1, j) left = min_path_sum_dfs(grid, i, j - 1) # Return the minimum path cost from top-left to (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: """Minimum path sum: Memoization search""" # If it's the top-left cell, terminate the search if i == 0 and j == 0: return grid[0][0] # If row or column index is out of bounds, return +∞ cost if i < 0 or j < 0: return inf # If there's a record, return it directly if mem[i][j] != -1: return mem[i][j] # Minimum path cost for left and upper cells up = min_path_sum_dfs_mem(grid, mem, i - 1, j) left = min_path_sum_dfs_mem(grid, mem, i, j - 1) # Record and return the minimum path cost from top-left to (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: """Minimum path sum: Dynamic programming""" n, m = len(grid), len(grid[0]) # Initialize dp table dp = [[0] * m for _ in range(n)] dp[0][0] = grid[0][0] # State transition: first row for j in range(1, m): dp[0][j] = dp[0][j - 1] + grid[0][j] # State transition: first column for i in range(1, n): dp[i][0] = dp[i - 1][0] + grid[i][0] # State transition: rest of the rows and columns 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: """Minimum path sum: Space-optimized dynamic programming""" n, m = len(grid), len(grid[0]) # Initialize dp table dp = [0] * m # State transition: first row dp[0] = grid[0][0] for j in range(1, m): dp[j] = dp[j - 1] + grid[0][j] # State transition: rest of the rows for i in range(1, n): # State transition: first column dp[0] = dp[0] + grid[i][0] # State transition: rest of the columns for j in range(1, m): dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] return dp[m - 1] """Driver Code""" if __name__ == "__main__": grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] n, m = len(grid), len(grid[0]) # Brute-force search res = min_path_sum_dfs(grid, n - 1, m - 1) print(f"The minimum path sum from top-left to bottom-right is {res}") # Memoization search mem = [[-1] * m for _ in range(n)] res = min_path_sum_dfs_mem(grid, mem, n - 1, m - 1) print(f"The minimum path sum from top-left to bottom-right is {res}") # Dynamic programming res = min_path_sum_dp(grid) print(f"The minimum path sum from top-left to bottom-right is {res}") # Space-optimized dynamic programming res = min_path_sum_dp_comp(grid) print(f"The minimum path sum from top-left to bottom-right is {res}") ================================================ FILE: en/codes/python/chapter_dynamic_programming/unbounded_knapsack.py ================================================ """ 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: """Unbounded knapsack: Dynamic programming""" n = len(wgt) # Initialize dp table dp = [[0] * (cap + 1) for _ in range(n + 1)] # State transition for i in range(1, n + 1): for c in range(1, cap + 1): if wgt[i - 1] > c: # If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c] else: # The larger value between not selecting and selecting item 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: """Unbounded knapsack: Space-optimized dynamic programming""" n = len(wgt) # Initialize dp table dp = [0] * (cap + 1) # State transition for i in range(1, n + 1): # Traverse in forward order for c in range(1, cap + 1): if wgt[i - 1] > c: # If exceeds knapsack capacity, don't select item i dp[c] = dp[c] else: # The larger value between not selecting and selecting item i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) return dp[cap] """Driver Code""" if __name__ == "__main__": wgt = [1, 2, 3] val = [5, 11, 15] cap = 4 # Dynamic programming res = unbounded_knapsack_dp(wgt, val, cap) print(f"The maximum item value not exceeding knapsack capacity is {res}") # Space-optimized dynamic programming res = unbounded_knapsack_dp_comp(wgt, val, cap) print(f"The maximum item value not exceeding knapsack capacity is {res}") ================================================ FILE: en/codes/python/chapter_graph/graph_adjacency_list.py ================================================ """ 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: """Undirected graph class based on adjacency list""" def __init__(self, edges: list[list[Vertex]]): """Constructor""" # Adjacency list, key: vertex, value: all adjacent vertices of that vertex self.adj_list = dict[Vertex, list[Vertex]]() # Add all vertices and edges 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: """Get the number of vertices""" return len(self.adj_list) def add_edge(self, vet1: Vertex, vet2: Vertex): """Add edge""" if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: raise ValueError() # Add edge vet1 - vet2 self.adj_list[vet1].append(vet2) self.adj_list[vet2].append(vet1) def remove_edge(self, vet1: Vertex, vet2: Vertex): """Remove edge""" if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: raise ValueError() # Remove edge vet1 - vet2 self.adj_list[vet1].remove(vet2) self.adj_list[vet2].remove(vet1) def add_vertex(self, vet: Vertex): """Add vertex""" if vet in self.adj_list: return # Add a new linked list in the adjacency list self.adj_list[vet] = [] def remove_vertex(self, vet: Vertex): """Remove vertex""" if vet not in self.adj_list: raise ValueError() # Remove the linked list corresponding to vertex vet in the adjacency list self.adj_list.pop(vet) # Traverse the linked lists of other vertices and remove all edges containing vet for vertex in self.adj_list: if vet in self.adj_list[vertex]: self.adj_list[vertex].remove(vet) def print(self): """Print adjacency list""" print("Adjacency list =") for vertex in self.adj_list: tmp = [v.val for v in self.adj_list[vertex]] print(f"{vertex.val}: {tmp},") """Driver Code""" if __name__ == "__main__": # Initialize undirected graph 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("\nAfter initialization, the graph is") graph.print() # Add edge # Vertices 1, 2 are v[0], v[2] graph.add_edge(v[0], v[2]) print("\nAfter adding edge 1-2, the graph is") graph.print() # Remove edge # Vertices 1, 3 are v[0], v[1] graph.remove_edge(v[0], v[1]) print("\nAfter removing edge 1-3, the graph is") graph.print() # Add vertex v5 = Vertex(6) graph.add_vertex(v5) print("\nAfter adding vertex 6, the graph is") graph.print() # Remove vertex # Vertex 3 is v[1] graph.remove_vertex(v[1]) print("\nAfter removing vertex 3, the graph is") graph.print() ================================================ FILE: en/codes/python/chapter_graph/graph_adjacency_matrix.py ================================================ """ 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: """Undirected graph class based on adjacency matrix""" def __init__(self, vertices: list[int], edges: list[list[int]]): """Constructor""" # Vertex list, where the element represents the "vertex value" and the index represents the "vertex index" self.vertices: list[int] = [] # Adjacency matrix, where the row and column indices correspond to the "vertex index" self.adj_mat: list[list[int]] = [] # Add vertices for val in vertices: self.add_vertex(val) # Add edges # Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices for e in edges: self.add_edge(e[0], e[1]) def size(self) -> int: """Get the number of vertices""" return len(self.vertices) def add_vertex(self, val: int): """Add vertex""" n = self.size() # Add the value of the new vertex to the vertex list self.vertices.append(val) # Add a row to the adjacency matrix new_row = [0] * n self.adj_mat.append(new_row) # Add a column to the adjacency matrix for row in self.adj_mat: row.append(0) def remove_vertex(self, index: int): """Remove vertex""" if index >= self.size(): raise IndexError() # Remove the vertex at index from the vertex list self.vertices.pop(index) # Remove the row at index from the adjacency matrix self.adj_mat.pop(index) # Remove the column at index from the adjacency matrix for row in self.adj_mat: row.pop(index) def add_edge(self, i: int, j: int): """Add edge""" # Parameters i, j correspond to the vertices element indices # Handle index out of bounds and equality if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: raise IndexError() # In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i) self.adj_mat[i][j] = 1 self.adj_mat[j][i] = 1 def remove_edge(self, i: int, j: int): """Remove edge""" # Parameters i, j correspond to the vertices element indices # Handle index out of bounds and equality 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 adjacency matrix""" print("Vertex list =", self.vertices) print("Adjacency matrix =") print_matrix(self.adj_mat) """Driver Code""" if __name__ == "__main__": # Initialize undirected graph # Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices vertices = [1, 3, 2, 5, 4] edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] graph = GraphAdjMat(vertices, edges) print("\nAfter initialization, the graph is") graph.print() # Add edge # Vertices 1, 2 have indices 0, 2 respectively graph.add_edge(0, 2) print("\nAfter adding edge 1-2, the graph is") graph.print() # Remove edge # Vertices 1, 3 have indices 0, 1 respectively graph.remove_edge(0, 1) print("\nAfter removing edge 1-3, the graph is") graph.print() # Add vertex graph.add_vertex(6) print("\nAfter adding vertex 6, the graph is") graph.print() # Remove vertex # Vertex 3 has index 1 graph.remove_vertex(1) print("\nAfter removing vertex 3, the graph is") graph.print() ================================================ FILE: en/codes/python/chapter_graph/graph_bfs.py ================================================ """ 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]: """Breadth-first traversal""" # Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex # Vertex traversal sequence res = [] # Hash set for recording vertices that have been visited visited = set[Vertex]([start_vet]) # Queue used to implement BFS que = deque[Vertex]([start_vet]) # Starting from vertex vet, loop until all vertices are visited while len(que) > 0: vet = que.popleft() # Dequeue the front vertex res.append(vet) # Record visited vertex # Traverse all adjacent vertices of this vertex for adj_vet in graph.adj_list[vet]: if adj_vet in visited: continue # Skip vertices that have been visited que.append(adj_vet) # Only enqueue unvisited vertices visited.add(adj_vet) # Mark this vertex as visited # Return vertex traversal sequence return res """Driver Code""" if __name__ == "__main__": # Initialize undirected graph 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("\nAfter initialization, the graph is") graph.print() # Breadth-first traversal res = graph_bfs(graph, v[0]) print("\nBreadth-first traversal (BFS) vertex sequence is") print(vets_to_vals(res)) ================================================ FILE: en/codes/python/chapter_graph/graph_dfs.py ================================================ """ 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): """Depth-first traversal helper function""" res.append(vet) # Record visited vertex visited.add(vet) # Mark this vertex as visited # Traverse all adjacent vertices of this vertex for adjVet in graph.adj_list[vet]: if adjVet in visited: continue # Skip vertices that have been visited # Recursively visit adjacent vertices dfs(graph, visited, res, adjVet) def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: """Depth-first traversal""" # Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex # Vertex traversal sequence res = [] # Hash set for recording vertices that have been visited visited = set[Vertex]() dfs(graph, visited, res, start_vet) return res """Driver Code""" if __name__ == "__main__": # Initialize undirected graph 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("\nAfter initialization, the graph is") graph.print() # Depth-first traversal res = graph_dfs(graph, v[0]) print("\nDepth-first traversal (DFS) vertex sequence is") print(vets_to_vals(res)) ================================================ FILE: en/codes/python/chapter_greedy/coin_change_greedy.py ================================================ """ 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: """Coin change: Greedy algorithm""" # Assume coins list is sorted i = len(coins) - 1 count = 0 # Loop to make greedy choices until no remaining amount while amt > 0: # Find the coin that is less than and closest to the remaining amount while i > 0 and coins[i] > amt: i -= 1 # Choose coins[i] amt -= coins[i] count += 1 # If no feasible solution is found, return -1 return count if amt == 0 else -1 """Driver Code""" if __name__ == "__main__": # Greedy algorithm: Can guarantee finding the global optimal solution coins = [1, 5, 10, 20, 50, 100] amt = 186 res = coin_change_greedy(coins, amt) print(f"\ncoins = {coins}, amt = {amt}") print(f"The minimum number of coins needed to make {amt} is {res}") # Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 20, 50] amt = 60 res = coin_change_greedy(coins, amt) print(f"\ncoins = {coins}, amt = {amt}") print(f"The minimum number of coins needed to make {amt} is {res}") print(f"Actually the minimum number needed is 3, i.e., 20 + 20 + 20") # Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 49, 50] amt = 98 res = coin_change_greedy(coins, amt) print(f"\ncoins = {coins}, amt = {amt}") print(f"The minimum number of coins needed to make {amt} is {res}") print(f"Actually the minimum number needed is 2, i.e., 49 + 49") ================================================ FILE: en/codes/python/chapter_greedy/fractional_knapsack.py ================================================ """ File: fractional_knapsack.py Created Time: 2023-07-19 Author: krahets (krahets@163.com) """ class Item: """Item""" def __init__(self, w: int, v: int): self.w = w # Item weight self.v = v # Item value def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int: """Fractional knapsack: Greedy algorithm""" # Create item list with two attributes: weight, value items = [Item(w, v) for w, v in zip(wgt, val)] # Sort by unit value item.v / item.w from high to low items.sort(key=lambda item: item.v / item.w, reverse=True) # Loop for greedy selection res = 0 for item in items: if item.w <= cap: # If remaining capacity is sufficient, put the entire current item into the knapsack res += item.v cap -= item.w else: # If remaining capacity is insufficient, put part of the current item into the knapsack res += (item.v / item.w) * cap # No remaining capacity, so break out of the loop break return res """Driver Code""" if __name__ == "__main__": wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = len(wgt) # Greedy algorithm res = fractional_knapsack(wgt, val, cap) print(f"The maximum item value without exceeding knapsack capacity is {res}") ================================================ FILE: en/codes/python/chapter_greedy/max_capacity.py ================================================ """ File: max_capacity.py Created Time: 2023-07-21 Author: krahets (krahets@163.com) """ def max_capacity(ht: list[int]) -> int: """Max capacity: Greedy algorithm""" # Initialize i, j to be at both ends of the array i, j = 0, len(ht) - 1 # Initial max capacity is 0 res = 0 # Loop for greedy selection until the two boards meet while i < j: # Update max capacity cap = min(ht[i], ht[j]) * (j - i) res = max(res, cap) # Move the shorter board inward if ht[i] < ht[j]: i += 1 else: j -= 1 return res """Driver Code""" if __name__ == "__main__": ht = [3, 8, 5, 2, 7, 7, 3, 4] # Greedy algorithm res = max_capacity(ht) print(f"Max capacity is {res}") ================================================ FILE: en/codes/python/chapter_greedy/max_product_cutting.py ================================================ """ File: max_product_cutting.py Created Time: 2023-07-21 Author: krahets (krahets@163.com) """ import math def max_product_cutting(n: int) -> int: """Max product cutting: Greedy algorithm""" # When n <= 3, must cut out a 1 if n <= 3: return 1 * (n - 1) # Greedily cut out 3, a is the number of 3s, b is the remainder a, b = n // 3, n % 3 if b == 1: # When the remainder is 1, convert a pair of 1 * 3 to 2 * 2 return int(math.pow(3, a - 1)) * 2 * 2 if b == 2: # When the remainder is 2, do nothing return int(math.pow(3, a)) * 2 # When the remainder is 0, do nothing return int(math.pow(3, a)) """Driver Code""" if __name__ == "__main__": n = 58 # Greedy algorithm res = max_product_cutting(n) print(f"Max product cutting is {res}") ================================================ FILE: en/codes/python/chapter_hashing/array_hash_map.py ================================================ """ File: array_hash_map.py Created Time: 2022-12-14 Author: msk397 (machangxinq@gmail.com) """ class Pair: """Key-value pair""" def __init__(self, key: int, val: str): self.key = key self.val = val class ArrayHashMap: """Hash table based on array implementation""" def __init__(self): """Constructor""" # Initialize array with 100 buckets self.buckets: list[Pair | None] = [None] * 100 def hash_func(self, key: int) -> int: """Hash function""" index = key % 100 return index def get(self, key: int) -> str | None: """Query operation""" 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): """Add and update operation""" pair = Pair(key, val) index: int = self.hash_func(key) self.buckets[index] = pair def remove(self, key: int): """Remove operation""" index: int = self.hash_func(key) # Set to None to represent removal self.buckets[index] = None def entry_set(self) -> list[Pair]: """Get all key-value pairs""" result: list[Pair] = [] for pair in self.buckets: if pair is not None: result.append(pair) return result def key_set(self) -> list[int]: """Get all keys""" result = [] for pair in self.buckets: if pair is not None: result.append(pair.key) return result def value_set(self) -> list[str]: """Get all values""" result = [] for pair in self.buckets: if pair is not None: result.append(pair.val) return result def print(self): """Print hash table""" for pair in self.buckets: if pair is not None: print(pair.key, "->", pair.val) """Driver Code""" if __name__ == "__main__": # Initialize hash table hmap = ArrayHashMap() # Add operation # Add key-value pair (key, value) to the hash table hmap.put(12836, "Xiao Ha") hmap.put(15937, "Xiao Luo") hmap.put(16750, "Xiao Suan") hmap.put(13276, "Xiao Fa") hmap.put(10583, "Xiao Ya") print("\nAfter adding, the hash table is\nKey -> Value") hmap.print() # Query operation # Input key into the hash table to get value name = hmap.get(15937) print("\nInput student ID 15937, found name " + name) # Remove operation # Remove key-value pair (key, value) from the hash table hmap.remove(10583) print("\nAfter removing 10583, the hash table is\nKey -> Value") hmap.print() # Traverse hash table print("\nTraverse key-value pairs Key->Value") for pair in hmap.entry_set(): print(pair.key, "->", pair.val) print("\nTraverse keys only Key") for key in hmap.key_set(): print(key) print("\nTraverse values only Value") for val in hmap.value_set(): print(val) ================================================ FILE: en/codes/python/chapter_hashing/built_in_hash.py ================================================ """ 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"Hash value of integer {num} is {hash_num}") bol = True hash_bol = hash(bol) print(f"Hash value of boolean {bol} is {hash_bol}") dec = 3.14159 hash_dec = hash(dec) print(f"Hash value of decimal {dec} is {hash_dec}") str = "Hello algo" hash_str = hash(str) print(f"Hash value of string {str} is {hash_str}") tup = (12836, "Xiao Ha") hash_tup = hash(tup) print(f"Hash value of tuple {tup} is {hash(hash_tup)}") obj = ListNode(0) hash_obj = hash(obj) print(f"Hash value of node object {obj} is {hash_obj}") ================================================ FILE: en/codes/python/chapter_hashing/hash_map.py ================================================ """ 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__": # Initialize hash table hmap = dict[int, str]() # Add operation # Add key-value pair (key, value) to the hash table hmap[12836] = "Xiao Ha" hmap[15937] = "Xiao Luo" hmap[16750] = "Xiao Suan" hmap[13276] = "Xiao Fa" hmap[10583] = "Xiao Ya" print("\nAfter adding, the hash table is\nKey -> Value") print_dict(hmap) # Query operation # Input key into the hash table to get value name: str = hmap[15937] print("\nInput student ID 15937, found name " + name) # Remove operation # Remove key-value pair (key, value) from the hash table hmap.pop(10583) print("\nAfter removing 10583, the hash table is\nKey -> Value") print_dict(hmap) # Traverse hash table print("\nTraverse key-value pairs Key->Value") for key, value in hmap.items(): print(key, "->", value) print("\nTraverse keys only Key") for key in hmap.keys(): print(key) print("\nTraverse values only Value") for val in hmap.values(): print(val) ================================================ FILE: en/codes/python/chapter_hashing/hash_map_chaining.py ================================================ """ 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: """Hash table with separate chaining""" def __init__(self): """Constructor""" self.size = 0 # Number of key-value pairs self.capacity = 4 # Hash table capacity self.load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion self.extend_ratio = 2 # Expansion multiplier self.buckets = [[] for _ in range(self.capacity)] # Bucket array def hash_func(self, key: int) -> int: """Hash function""" return key % self.capacity def load_factor(self) -> float: """Load factor""" return self.size / self.capacity def get(self, key: int) -> str | None: """Query operation""" index = self.hash_func(key) bucket = self.buckets[index] # Traverse bucket, if key is found, return corresponding val for pair in bucket: if pair.key == key: return pair.val # If key is not found, return None return None def put(self, key: int, val: str): """Add operation""" # When load factor exceeds threshold, perform expansion if self.load_factor() > self.load_thres: self.extend() index = self.hash_func(key) bucket = self.buckets[index] # Traverse bucket, if specified key is encountered, update corresponding val and return for pair in bucket: if pair.key == key: pair.val = val return # If key does not exist, append key-value pair to the end pair = Pair(key, val) bucket.append(pair) self.size += 1 def remove(self, key: int): """Remove operation""" index = self.hash_func(key) bucket = self.buckets[index] # Traverse bucket and remove key-value pair from it for pair in bucket: if pair.key == key: bucket.remove(pair) self.size -= 1 break def extend(self): """Expand hash table""" # Temporarily store the original hash table buckets = self.buckets # Initialize expanded new hash table self.capacity *= self.extend_ratio self.buckets = [[] for _ in range(self.capacity)] self.size = 0 # Move key-value pairs from original hash table to new hash table for bucket in buckets: for pair in bucket: self.put(pair.key, pair.val) def print(self): """Print hash table""" for bucket in self.buckets: res = [] for pair in bucket: res.append(str(pair.key) + " -> " + pair.val) print(res) """Driver Code""" if __name__ == "__main__": # Initialize hash table hashmap = HashMapChaining() # Add operation # Add key-value pair (key, value) to the hash table hashmap.put(12836, "Xiao Ha") hashmap.put(15937, "Xiao Luo") hashmap.put(16750, "Xiao Suan") hashmap.put(13276, "Xiao Fa") hashmap.put(10583, "Xiao Ya") print("\nAfter adding, the hash table is\n[Key1 -> Value1, Key2 -> Value2, ...]") hashmap.print() # Query operation # Input key into the hash table to get value name = hashmap.get(13276) print("\nInput student ID 13276, found name " + name) # Remove operation # Remove key-value pair (key, value) from the hash table hashmap.remove(12836) print("\nAfter removing 12836, the hash table is\n[Key1 -> Value1, Key2 -> Value2, ...]") hashmap.print() ================================================ FILE: en/codes/python/chapter_hashing/hash_map_open_addressing.py ================================================ """ 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: """Hash table with open addressing""" def __init__(self): """Constructor""" self.size = 0 # Number of key-value pairs self.capacity = 4 # Hash table capacity self.load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion self.extend_ratio = 2 # Expansion multiplier self.buckets: list[Pair | None] = [None] * self.capacity # Bucket array self.TOMBSTONE = Pair(-1, "-1") # Removal marker def hash_func(self, key: int) -> int: """Hash function""" return key % self.capacity def load_factor(self) -> float: """Load factor""" return self.size / self.capacity def find_bucket(self, key: int) -> int: """Search for bucket index corresponding to key""" index = self.hash_func(key) first_tombstone = -1 # Linear probing, break when encountering an empty bucket while self.buckets[index] is not None: # If key is encountered, return the corresponding bucket index if self.buckets[index].key == key: # If a removal marker was encountered before, move the key-value pair to that index if first_tombstone != -1: self.buckets[first_tombstone] = self.buckets[index] self.buckets[index] = self.TOMBSTONE return first_tombstone # Return the moved bucket index return index # Return bucket index # Record the first removal marker encountered if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE: first_tombstone = index # Calculate bucket index, wrap around to the head if past the tail index = (index + 1) % self.capacity # If key does not exist, return the index for insertion return index if first_tombstone == -1 else first_tombstone def get(self, key: int) -> str: """Query operation""" # Search for bucket index corresponding to key index = self.find_bucket(key) # If key-value pair is found, return corresponding val if self.buckets[index] not in [None, self.TOMBSTONE]: return self.buckets[index].val # If key-value pair does not exist, return None return None def put(self, key: int, val: str): """Add operation""" # When load factor exceeds threshold, perform expansion if self.load_factor() > self.load_thres: self.extend() # Search for bucket index corresponding to key index = self.find_bucket(key) # If key-value pair is found, overwrite val and return if self.buckets[index] not in [None, self.TOMBSTONE]: self.buckets[index].val = val return # If key-value pair does not exist, add the key-value pair self.buckets[index] = Pair(key, val) self.size += 1 def remove(self, key: int): """Remove operation""" # Search for bucket index corresponding to key index = self.find_bucket(key) # If key-value pair is found, overwrite it with removal marker if self.buckets[index] not in [None, self.TOMBSTONE]: self.buckets[index] = self.TOMBSTONE self.size -= 1 def extend(self): """Expand hash table""" # Temporarily store the original hash table buckets_tmp = self.buckets # Initialize expanded new hash table self.capacity *= self.extend_ratio self.buckets = [None] * self.capacity self.size = 0 # Move key-value pairs from original hash table to new hash table for pair in buckets_tmp: if pair not in [None, self.TOMBSTONE]: self.put(pair.key, pair.val) def print(self): """Print hash table""" 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__": # Initialize hash table hashmap = HashMapOpenAddressing() # Add operation # Add key-value pair (key, val) to the hash table hashmap.put(12836, "Xiao Ha") hashmap.put(15937, "Xiao Luo") hashmap.put(16750, "Xiao Suan") hashmap.put(13276, "Xiao Fa") hashmap.put(10583, "Xiao Ya") print("\nAfter adding, the hash table is\nKey -> Value") hashmap.print() # Query operation # Input key into the hash table to get val name = hashmap.get(13276) print("\nInput student ID 13276, found name " + name) # Remove operation # Remove key-value pair (key, val) from the hash table hashmap.remove(16750) print("\nAfter removing 16750, the hash table is\nKey -> Value") hashmap.print() ================================================ FILE: en/codes/python/chapter_hashing/simple_hash.py ================================================ """ File: simple_hash.py Created Time: 2023-06-15 Author: krahets (krahets@163.com) """ def add_hash(key: str) -> int: """Additive hash""" hash = 0 modulus = 1000000007 for c in key: hash += ord(c) return hash % modulus def mul_hash(key: str) -> int: """Multiplicative hash""" hash = 0 modulus = 1000000007 for c in key: hash = 31 * hash + ord(c) return hash % modulus def xor_hash(key: str) -> int: """XOR hash""" hash = 0 modulus = 1000000007 for c in key: hash ^= ord(c) return hash % modulus def rot_hash(key: str) -> int: """Rotational hash""" 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 algo" hash = add_hash(key) print(f"Additive hash value is {hash}") hash = mul_hash(key) print(f"Multiplicative hash value is {hash}") hash = xor_hash(key) print(f"XOR hash value is {hash}") hash = rot_hash(key) print(f"Rotational hash value is {hash}") ================================================ FILE: en/codes/python/chapter_heap/heap.py ================================================ """ 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) # Element enters heap print(f"\nAfter element {val} enters heap") print_heap([flag * val for val in heap]) def test_pop(heap: list, flag: int = 1): val = flag * heapq.heappop(heap) # Top element exits heap print(f"\nAfter top element {val} exits heap") print_heap([flag * val for val in heap]) """Driver Code""" if __name__ == "__main__": # Initialize min heap min_heap, flag = [], 1 # Initialize max heap max_heap, flag = [], -1 print("\nThe following test cases are for max heap") # Python's heapq module implements min heap by default # Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap # In this example, flag = 1 corresponds to min heap, flag = -1 corresponds to max heap # Elements enter heap 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) # Get top element peek: int = flag * max_heap[0] print(f"\nTop element is {peek}") # Top element exits heap 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) # Get heap size size: int = len(max_heap) print(f"\nNumber of heap elements is {size}") # Check if heap is empty is_empty: bool = not max_heap print(f"\nIs heap empty {is_empty}") # Input list and build heap # Time complexity is O(n), not O(nlogn) min_heap = [1, 3, 2, 5, 4] heapq.heapify(min_heap) print("\nAfter inputting list and building min heap") print_heap(min_heap) ================================================ FILE: en/codes/python/chapter_heap/my_heap.py ================================================ """ 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: """Max heap""" def __init__(self, nums: list[int]): """Constructor, build heap based on input list""" # Add list elements to heap as is self.max_heap = nums # Heapify all nodes except leaf nodes for i in range(self.parent(self.size() - 1), -1, -1): self.sift_down(i) def left(self, i: int) -> int: """Get index of left child node""" return 2 * i + 1 def right(self, i: int) -> int: """Get index of right child node""" return 2 * i + 2 def parent(self, i: int) -> int: """Get index of parent node""" return (i - 1) // 2 # Floor division def swap(self, i: int, j: int): """Swap elements""" self.max_heap[i], self.max_heap[j] = self.max_heap[j], self.max_heap[i] def size(self) -> int: """Get heap size""" return len(self.max_heap) def is_empty(self) -> bool: """Check if heap is empty""" return self.size() == 0 def peek(self) -> int: """Access top element""" return self.max_heap[0] def push(self, val: int): """Element enters heap""" # Add node self.max_heap.append(val) # Heapify from bottom to top self.sift_up(self.size() - 1) def sift_up(self, i: int): """Starting from node i, heapify from bottom to top""" while True: # Get parent node of node i p = self.parent(i) # When "crossing root node" or "node needs no repair", end heapify if p < 0 or self.max_heap[i] <= self.max_heap[p]: break # Swap two nodes self.swap(i, p) # Loop upward heapify i = p def pop(self) -> int: """Element exits heap""" # Handle empty case if self.is_empty(): raise IndexError("Heap is empty") # Swap root node with rightmost leaf node (swap first element with last element) self.swap(0, self.size() - 1) # Delete node val = self.max_heap.pop() # Heapify from top to bottom self.sift_down(0) # Return top element return val def sift_down(self, i: int): """Starting from node i, heapify from top to bottom""" while True: # Find node with largest value among i, l, r, denoted as 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 # If node i is largest or indices l, r are out of bounds, no need to continue heapify, break if ma == i: break # Swap two nodes self.swap(i, ma) # Loop downward heapify i = ma def print(self): """Print heap (binary tree)""" print_heap(self.max_heap) """Driver Code""" if __name__ == "__main__": # Initialize max heap max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) print("\nAfter inputting list and building heap") max_heap.print() # Get top element peek = max_heap.peek() print(f"\nTop element is {peek}") # Element enters heap val = 7 max_heap.push(val) print(f"\nAfter element {val} enters heap") max_heap.print() # Top element exits heap peek = max_heap.pop() print(f"\nAfter top element {peek} exits heap") max_heap.print() # Get heap size size = max_heap.size() print(f"\nNumber of heap elements is {size}") # Check if heap is empty is_empty = max_heap.is_empty() print(f"\nIs heap empty {is_empty}") ================================================ FILE: en/codes/python/chapter_heap/top_k.py ================================================ """ 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]: """Find the largest k elements in array based on heap""" # Initialize min heap heap = [] # Enter the first k elements of array into heap for i in range(k): heapq.heappush(heap, nums[i]) # Starting from the (k+1)th element, maintain heap length as k for i in range(k, len(nums)): # If current element is greater than top element, top element exits heap, current element enters heap if nums[i] > heap[0]: heapq.heappop(heap) heapq.heappush(heap, nums[i]) return heap """Driver Code""" if __name__ == "__main__": nums = [1, 7, 6, 3, 2] k = 3 res = top_k_heap(nums, k) print(f"The largest {k} elements are") print_heap(res) ================================================ FILE: en/codes/python/chapter_searching/binary_search.py ================================================ """ File: binary_search.py Created Time: 2022-11-26 Author: timi (xisunyy@163.com) """ def binary_search(nums: list[int], target: int) -> int: """Binary search (closed interval)""" # Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array i, j = 0, len(nums) - 1 # Loop, exit when the search interval is empty (empty when i > j) while i <= j: # In theory, Python numbers can be infinitely large (depending on memory size), no need to consider large number overflow m = (i + j) // 2 # Calculate midpoint index m if nums[m] < target: i = m + 1 # This means target is in the interval [m+1, j] elif nums[m] > target: j = m - 1 # This means target is in the interval [i, m-1] else: return m # Found the target element, return its index return -1 # Target element not found, return -1 def binary_search_lcro(nums: list[int], target: int) -> int: """Binary search (left-closed right-open interval)""" # Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1 i, j = 0, len(nums) # Loop, exit when the search interval is empty (empty when i = j) while i < j: m = (i + j) // 2 # Calculate midpoint index m if nums[m] < target: i = m + 1 # This means target is in the interval [m+1, j) elif nums[m] > target: j = m # This means target is in the interval [i, m) else: return m # Found the target element, return its index return -1 # Target element not found, return -1 """Driver Code""" if __name__ == "__main__": target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # Binary search (closed interval) index = binary_search(nums, target) print("Index of target element 6 =", index) # Binary search (left-closed right-open interval) index = binary_search_lcro(nums, target) print("Index of target element 6 =", index) ================================================ FILE: en/codes/python/chapter_searching/binary_search_edge.py ================================================ """ 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: """Binary search for the leftmost target""" # Equivalent to finding the insertion point of target i = binary_search_insertion(nums, target) # Target not found, return -1 if i == len(nums) or nums[i] != target: return -1 # Found target, return index i return i def binary_search_right_edge(nums: list[int], target: int) -> int: """Binary search for the rightmost target""" # Convert to finding the leftmost target + 1 i = binary_search_insertion(nums, target + 1) # j points to the rightmost target, i points to the first element greater than target j = i - 1 # Target not found, return -1 if j == -1 or nums[j] != target: return -1 # Found target, return index j return j """Driver Code""" if __name__ == "__main__": # Array with duplicate elements nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print(f"\nArray nums = {nums}") # Binary search for left boundary and right boundary for target in [6, 7]: index = binary_search_left_edge(nums, target) print(f"Index of the leftmost element {target} is {index}") index = binary_search_right_edge(nums, target) print(f"Index of the rightmost element {target} is {index}") ================================================ FILE: en/codes/python/chapter_searching/binary_search_insertion.py ================================================ """ 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: """Binary search for insertion point (no duplicate elements)""" i, j = 0, len(nums) - 1 # Initialize closed interval [0, n-1] while i <= j: m = (i + j) // 2 # Calculate midpoint index m if nums[m] < target: i = m + 1 # target is in the interval [m+1, j] elif nums[m] > target: j = m - 1 # target is in the interval [i, m-1] else: return m # Found target, return insertion point m # Target not found, return insertion point i return i def binary_search_insertion(nums: list[int], target: int) -> int: """Binary search for insertion point (with duplicate elements)""" i, j = 0, len(nums) - 1 # Initialize closed interval [0, n-1] while i <= j: m = (i + j) // 2 # Calculate midpoint index m if nums[m] < target: i = m + 1 # target is in the interval [m+1, j] elif nums[m] > target: j = m - 1 # target is in the interval [i, m-1] else: j = m - 1 # The first element less than target is in the interval [i, m-1] # Return insertion point i return i """Driver Code""" if __name__ == "__main__": # Array without duplicate elements nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print(f"\nArray nums = {nums}") # Binary search for insertion point for target in [6, 9]: index = binary_search_insertion_simple(nums, target) print(f"Index of insertion point for element {target} is {index}") # Array with duplicate elements nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print(f"\nArray nums = {nums}") # Binary search for insertion point for target in [2, 6, 20]: index = binary_search_insertion(nums, target) print(f"Index of insertion point for element {target} is {index}") ================================================ FILE: en/codes/python/chapter_searching/hashing_search.py ================================================ """ 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: """Hash search (array)""" # Hash table's key: target element, value: index # If this key does not exist in the hash table, return -1 return hmap.get(target, -1) def hashing_search_linkedlist( hmap: dict[int, ListNode], target: int ) -> ListNode | None: """Hash search (linked list)""" # Hash table's key: target element, value: node object # If this key does not exist in the hash table, return None return hmap.get(target, None) """Driver Code""" if __name__ == "__main__": target = 3 # Hash search (array) nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] # Initialize hash table map0 = dict[int, int]() for i in range(len(nums)): map0[nums[i]] = i # key: element, value: index index: int = hashing_search_array(map0, target) print("Index of target element 3 =", index) # Hash search (linked list) head: ListNode = list_to_linked_list(nums) # Initialize hash table map1 = dict[int, ListNode]() while head: map1[head.val] = head # key: node value, value: node head = head.next node: ListNode = hashing_search_linkedlist(map1, target) print("The corresponding node object for target node value 3 is", node) ================================================ FILE: en/codes/python/chapter_searching/linear_search.py ================================================ """ 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: """Linear search (array)""" # Traverse the array for i in range(len(nums)): if nums[i] == target: # Found the target element, return its index return i return -1 # Target element not found, return -1 def linear_search_linkedlist(head: ListNode, target: int) -> ListNode | None: """Linear search (linked list)""" # Traverse the linked list while head: if head.val == target: # Found the target node, return it return head head = head.next return None # Target node not found, return None """Driver Code""" if __name__ == "__main__": target = 3 # Perform linear search in array nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] index: int = linear_search_array(nums, target) print("Index of target element 3 =", index) # Perform linear search in linked list head: ListNode = list_to_linked_list(nums) node: ListNode | None = linear_search_linkedlist(head, target) print("The corresponding node object for target node value 3 is", node) ================================================ FILE: en/codes/python/chapter_searching/two_sum.py ================================================ """ 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]: """Method 1: Brute force enumeration""" # Two nested loops, time complexity is 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]: """Method 2: Auxiliary hash table""" # Auxiliary hash table, space complexity is O(n) dic = {} # Single loop, time complexity is 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 [] """Driver Code""" if __name__ == "__main__": # ======= Test Case ======= nums = [2, 7, 11, 15] target = 13 # ====== Driver Code ====== # Method 1 res: list[int] = two_sum_brute_force(nums, target) print("Method 1 res =", res) # Method 2 res: list[int] = two_sum_hash_table(nums, target) print("Method 2 res =", res) ================================================ FILE: en/codes/python/chapter_sorting/bubble_sort.py ================================================ """ File: bubble_sort.py Created Time: 2022-11-25 Author: timi (xisunyy@163.com) """ def bubble_sort(nums: list[int]): """Bubble sort""" n = len(nums) # Outer loop: unsorted interval is [0, i] for i in range(n - 1, 0, -1): # Inner loop: swap the largest element in the unsorted interval [0, i] to the rightmost end of the interval for j in range(i): if nums[j] > nums[j + 1]: # Swap nums[j] and nums[j + 1] nums[j], nums[j + 1] = nums[j + 1], nums[j] def bubble_sort_with_flag(nums: list[int]): """Bubble sort (flag optimization)""" n = len(nums) # Outer loop: unsorted interval is [0, i] for i in range(n - 1, 0, -1): flag = False # Initialize flag # Inner loop: swap the largest element in the unsorted interval [0, i] to the rightmost end of the interval for j in range(i): if nums[j] > nums[j + 1]: # Swap nums[j] and nums[j + 1] nums[j], nums[j + 1] = nums[j + 1], nums[j] flag = True # Record element swap if not flag: break # No elements were swapped in this round of "bubbling", exit directly """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] bubble_sort(nums) print("After bubble sort, nums =", nums) nums1 = [4, 1, 3, 1, 5, 2] bubble_sort_with_flag(nums1) print("After bubble sort, nums =", nums1) ================================================ FILE: en/codes/python/chapter_sorting/bucket_sort.py ================================================ """ File: bucket_sort.py Created Time: 2023-03-30 Author: krahets (krahets@163.com) """ def bucket_sort(nums: list[float]): """Bucket sort""" # Initialize k = n/2 buckets, expected to allocate 2 elements per bucket k = len(nums) // 2 buckets = [[] for _ in range(k)] # 1. Distribute array elements into various buckets for num in nums: # Input data range is [0, 1), use num * k to map to index range [0, k-1] i = int(num * k) # Add num to bucket i buckets[i].append(num) # 2. Sort each bucket for bucket in buckets: # Use built-in sorting function, can also replace with other sorting algorithms bucket.sort() # 3. Traverse buckets to merge results i = 0 for bucket in buckets: for num in bucket: nums[i] = num i += 1 if __name__ == "__main__": # Assume input data is floating point, interval [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("After bucket sort, nums =", nums) ================================================ FILE: en/codes/python/chapter_sorting/counting_sort.py ================================================ """ File: counting_sort.py Created Time: 2023-03-21 Author: krahets (krahets@163.com) """ def counting_sort_naive(nums: list[int]): """Counting sort""" # Simple implementation, cannot be used for sorting objects # 1. Count the maximum element m in the array m = 0 for num in nums: m = max(m, num) # 2. Count the occurrence of each number # counter[num] represents the occurrence of num counter = [0] * (m + 1) for num in nums: counter[num] += 1 # 3. Traverse counter, filling each element back into the original array 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]): """Counting sort""" # Complete implementation, can sort objects and is a stable sort # 1. Count the maximum element m in the array m = max(nums) # 2. Count the occurrence of each number # counter[num] represents the occurrence of num counter = [0] * (m + 1) for num in nums: counter[num] += 1 # 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" # counter[num]-1 is the last index where num appears in res for i in range(m): counter[i + 1] += counter[i] # 4. Traverse nums in reverse order, placing each element into the result array res # Initialize the array res to record results n = len(nums) res = [0] * n for i in range(n - 1, -1, -1): num = nums[i] res[counter[num] - 1] = num # Place num at the corresponding index counter[num] -= 1 # Decrement the prefix sum by 1, getting the next index to place num # Use result array res to overwrite the original array nums for i in range(n): nums[i] = res[i] """Driver Code""" if __name__ == "__main__": nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort_naive(nums) print(f"After counting sort (unable to sort objects), nums = {nums}") nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort(nums1) print(f"After counting sort, nums1 = {nums1}") ================================================ FILE: en/codes/python/chapter_sorting/heap_sort.py ================================================ """ File: heap_sort.py Created Time: 2023-05-24 Author: krahets (krahets@163.com) """ def sift_down(nums: list[int], n: int, i: int): """Heap length is n, start heapifying node i, from top to bottom""" while True: # Determine the largest node among i, l, r, noted as 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 # If node i is the largest or indices l, r are out of bounds, no further heapification needed, break if ma == i: break # Swap two nodes nums[i], nums[ma] = nums[ma], nums[i] # Loop downwards heapification i = ma def heap_sort(nums: list[int]): """Heap sort""" # Build heap operation: heapify all nodes except leaves for i in range(len(nums) // 2 - 1, -1, -1): sift_down(nums, len(nums), i) # Extract the largest element from the heap and repeat for n-1 rounds for i in range(len(nums) - 1, 0, -1): # Swap the root node with the rightmost leaf node (swap the first element with the last element) nums[0], nums[i] = nums[i], nums[0] # Start heapifying the root node, from top to bottom sift_down(nums, i, 0) """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] heap_sort(nums) print("After heap sort, nums =", nums) ================================================ FILE: en/codes/python/chapter_sorting/insertion_sort.py ================================================ """ File: insertion_sort.py Created Time: 2022-11-25 Author: timi (xisunyy@163.com) """ def insertion_sort(nums: list[int]): """Insertion sort""" # Outer loop: sorted interval is [0, i-1] for i in range(1, len(nums)): base = nums[i] j = i - 1 # Inner loop: insert base into the correct position within the sorted interval [0, i-1] while j >= 0 and nums[j] > base: nums[j + 1] = nums[j] # Move nums[j] to the right by one position j -= 1 nums[j + 1] = base # Assign base to the correct position """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] insertion_sort(nums) print("After insertion sort, nums =", nums) ================================================ FILE: en/codes/python/chapter_sorting/merge_sort.py ================================================ """ 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): """Merge left subarray and right subarray""" # Left subarray interval is [left, mid], right subarray interval is [mid+1, right] # Create a temporary array tmp to store the merged results tmp = [0] * (right - left + 1) # Initialize the start indices of the left and right subarrays i, j, k = left, mid + 1, 0 # While both subarrays still have elements, compare and copy the smaller element into the temporary array 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 # Copy the remaining elements of the left and right subarrays into the temporary array while i <= mid: tmp[k] = nums[i] i += 1 k += 1 while j <= right: tmp[k] = nums[j] j += 1 k += 1 # Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval for k in range(0, len(tmp)): nums[left + k] = tmp[k] def merge_sort(nums: list[int], left: int, right: int): """Merge sort""" # Termination condition if left >= right: return # Terminate recursion when subarray length is 1 # Divide and conquer stage mid = (left + right) // 2 # Calculate midpoint merge_sort(nums, left, mid) # Recursively process the left subarray merge_sort(nums, mid + 1, right) # Recursively process the right subarray # Merge stage merge(nums, left, mid, right) """Driver Code""" if __name__ == "__main__": nums = [7, 3, 2, 6, 0, 1, 5, 4] merge_sort(nums, 0, len(nums) - 1) print("After merge sort, nums =", nums) ================================================ FILE: en/codes/python/chapter_sorting/quick_sort.py ================================================ """ File: quick_sort.py Created Time: 2022-11-25 Author: timi (xisunyy@163.com) """ class QuickSort: """Quick sort class""" def partition(self, nums: list[int], left: int, right: int) -> int: """Sentinel partition""" # Use nums[left] as the pivot i, j = left, right while i < j: while i < j and nums[j] >= nums[left]: j -= 1 # Search from right to left for the first element smaller than the pivot while i < j and nums[i] <= nums[left]: i += 1 # Search from left to right for the first element greater than the pivot # Swap elements nums[i], nums[j] = nums[j], nums[i] # Swap the pivot to the boundary between the two subarrays nums[i], nums[left] = nums[left], nums[i] return i # Return the index of the pivot def quick_sort(self, nums: list[int], left: int, right: int): """Quick sort""" # Terminate recursion when subarray length is 1 if left >= right: return # Sentinel partition pivot = self.partition(nums, left, right) # Recursively process the left subarray and right subarray self.quick_sort(nums, left, pivot - 1) self.quick_sort(nums, pivot + 1, right) class QuickSortMedian: """Quick sort class (median pivot optimization)""" def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int: """Select the median of three candidate elements""" l, m, r = nums[left], nums[mid], nums[right] if (l <= m <= r) or (r <= m <= l): return mid # m is between l and r if (m <= l <= r) or (r <= l <= m): return left # l is between m and r return right def partition(self, nums: list[int], left: int, right: int) -> int: """Sentinel partition (median of three)""" # Use nums[left] as the pivot med = self.median_three(nums, left, (left + right) // 2, right) # Swap the median to the array's leftmost position nums[left], nums[med] = nums[med], nums[left] # Use nums[left] as the pivot i, j = left, right while i < j: while i < j and nums[j] >= nums[left]: j -= 1 # Search from right to left for the first element smaller than the pivot while i < j and nums[i] <= nums[left]: i += 1 # Search from left to right for the first element greater than the pivot # Swap elements nums[i], nums[j] = nums[j], nums[i] # Swap the pivot to the boundary between the two subarrays nums[i], nums[left] = nums[left], nums[i] return i # Return the index of the pivot def quick_sort(self, nums: list[int], left: int, right: int): """Quick sort""" # Terminate recursion when subarray length is 1 if left >= right: return # Sentinel partition pivot = self.partition(nums, left, right) # Recursively process the left subarray and right subarray self.quick_sort(nums, left, pivot - 1) self.quick_sort(nums, pivot + 1, right) class QuickSortTailCall: """Quick sort class (recursion depth optimization)""" def partition(self, nums: list[int], left: int, right: int) -> int: """Sentinel partition""" # Use nums[left] as the pivot i, j = left, right while i < j: while i < j and nums[j] >= nums[left]: j -= 1 # Search from right to left for the first element smaller than the pivot while i < j and nums[i] <= nums[left]: i += 1 # Search from left to right for the first element greater than the pivot # Swap elements nums[i], nums[j] = nums[j], nums[i] # Swap the pivot to the boundary between the two subarrays nums[i], nums[left] = nums[left], nums[i] return i # Return the index of the pivot def quick_sort(self, nums: list[int], left: int, right: int): """Quick sort (recursion depth optimization)""" # Terminate when subarray length is 1 while left < right: # Sentinel partition operation pivot = self.partition(nums, left, right) # Perform quick sort on the shorter of the two subarrays if pivot - left < right - pivot: self.quick_sort(nums, left, pivot - 1) # Recursively sort the left subarray left = pivot + 1 # Remaining unsorted interval is [pivot + 1, right] else: self.quick_sort(nums, pivot + 1, right) # Recursively sort the right subarray right = pivot - 1 # Remaining unsorted interval is [left, pivot - 1] """Driver Code""" if __name__ == "__main__": # Quick sort nums = [2, 4, 1, 0, 3, 5] QuickSort().quick_sort(nums, 0, len(nums) - 1) print("After quick sort, nums =", nums) # Quick sort (median pivot optimization) nums1 = [2, 4, 1, 0, 3, 5] QuickSortMedian().quick_sort(nums1, 0, len(nums1) - 1) print("After quick sort (median pivot optimization), nums =", nums1) # Quick sort (recursion depth optimization) nums2 = [2, 4, 1, 0, 3, 5] QuickSortTailCall().quick_sort(nums2, 0, len(nums2) - 1) print("After quick sort (recursion depth optimization), nums =", nums2) ================================================ FILE: en/codes/python/chapter_sorting/radix_sort.py ================================================ """ File: radix_sort.py Created Time: 2023-03-26 Author: krahets (krahets@163.com) """ def digit(num: int, exp: int) -> int: """Get the k-th digit of element num, where exp = 10^(k-1)""" # Passing exp instead of k can avoid repeated expensive exponentiation here return (num // exp) % 10 def counting_sort_digit(nums: list[int], exp: int): """Counting sort (based on nums k-th digit)""" # Decimal digit range is 0~9, therefore need a bucket array of length 10 counter = [0] * 10 n = len(nums) # Count the occurrence of digits 0~9 for i in range(n): d = digit(nums[i], exp) # Get the k-th digit of nums[i], noted as d counter[d] += 1 # Count the occurrence of digit d # Calculate prefix sum, converting "occurrence count" into "array index" for i in range(1, 10): counter[i] += counter[i - 1] # Traverse in reverse, based on bucket statistics, place each element into res res = [0] * n for i in range(n - 1, -1, -1): d = digit(nums[i], exp) j = counter[d] - 1 # Get the index j for d in the array res[j] = nums[i] # Place the current element at index j counter[d] -= 1 # Decrease the count of d by 1 # Use result to overwrite the original array nums for i in range(n): nums[i] = res[i] def radix_sort(nums: list[int]): """Radix sort""" # Get the maximum element of the array, used to determine the maximum number of digits m = max(nums) # Traverse from the lowest to the highest digit exp = 1 while exp <= m: # Perform counting sort on the k-th digit of array elements # k = 1 -> exp = 1 # k = 2 -> exp = 10 # i.e., exp = 10^(k-1) counting_sort_digit(nums, exp) exp *= 10 """Driver Code""" if __name__ == "__main__": # Radix sort nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ] radix_sort(nums) print("After radix sort, nums =", nums) ================================================ FILE: en/codes/python/chapter_sorting/selection_sort.py ================================================ """ File: selection_sort.py Created Time: 2023-05-22 Author: krahets (krahets@163.com) """ def selection_sort(nums: list[int]): """Selection sort""" n = len(nums) # Outer loop: unsorted interval is [i, n-1] for i in range(n - 1): # Inner loop: find the smallest element within the unsorted interval k = i for j in range(i + 1, n): if nums[j] < nums[k]: k = j # Record the index of the smallest element # Swap the smallest element with the first element of the unsorted interval nums[i], nums[k] = nums[k], nums[i] """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] selection_sort(nums) print("After selection sort, nums =", nums) ================================================ FILE: en/codes/python/chapter_stack_and_queue/array_deque.py ================================================ """ File: array_deque.py Created Time: 2023-03-01 Author: krahets (krahets@163.com) """ class ArrayDeque: """Double-ended queue based on circular array implementation""" def __init__(self, capacity: int): """Constructor""" self._nums: list[int] = [0] * capacity self._front: int = 0 self._size: int = 0 def capacity(self) -> int: """Get the capacity of the double-ended queue""" return len(self._nums) def size(self) -> int: """Get the length of the double-ended queue""" return self._size def is_empty(self) -> bool: """Check if the double-ended queue is empty""" return self._size == 0 def index(self, i: int) -> int: """Calculate circular array index""" # Use modulo operation to wrap the array head and tail together # When i passes the tail of the array, return to the head # When i passes the head of the array, return to the tail return (i + self.capacity()) % self.capacity() def push_first(self, num: int): """Front of the queue enqueue""" if self._size == self.capacity(): print("Double-ended queue is full") return # Front pointer moves one position to the left # Use modulo operation to wrap front around to the tail after passing the head of the array self._front = self.index(self._front - 1) # Add num to the front of the queue self._nums[self._front] = num self._size += 1 def push_last(self, num: int): """Rear of the queue enqueue""" if self._size == self.capacity(): print("Double-ended queue is full") return # Calculate rear pointer, points to rear index + 1 rear = self.index(self._front + self._size) # Add num to the rear of the queue self._nums[rear] = num self._size += 1 def pop_first(self) -> int: """Front of the queue dequeue""" num = self.peek_first() # Front pointer moves one position backward self._front = self.index(self._front + 1) self._size -= 1 return num def pop_last(self) -> int: """Rear of the queue dequeue""" num = self.peek_last() self._size -= 1 return num def peek_first(self) -> int: """Access front of the queue element""" if self.is_empty(): raise IndexError("Double-ended queue is empty") return self._nums[self._front] def peek_last(self) -> int: """Access rear of the queue element""" if self.is_empty(): raise IndexError("Double-ended queue is empty") # Calculate tail element index last = self.index(self._front + self._size - 1) return self._nums[last] def to_array(self) -> list[int]: """Return array for printing""" # Only convert list elements within the valid length range res = [] for i in range(self._size): res.append(self._nums[self.index(self._front + i)]) return res """Driver Code""" if __name__ == "__main__": # Initialize double-ended queue deque = ArrayDeque(10) deque.push_last(3) deque.push_last(2) deque.push_last(5) print("double-ended queue deque =", deque.to_array()) # Access elements peek_first: int = deque.peek_first() print("Front of the queue element peek_first =", peek_first) peek_last: int = deque.peek_last() print("Rear of the queue element peek_last =", peek_last) # Elements enqueue deque.push_last(4) print("Element 4 rear enqueue after deque =", deque.to_array()) deque.push_first(1) print("Element 1 front enqueue after deque =", deque.to_array()) # Elements dequeue pop_last: int = deque.pop_last() print("Rear dequeued element =", pop_last, ", rear dequeue after deque =", deque.to_array()) pop_first: int = deque.pop_first() print("Front dequeued element =", pop_first, ", front dequeue after deque =", deque.to_array()) # Get the length of the double-ended queue size: int = deque.size() print("Length of the double-ended queue size =", size) # Check if the double-ended queue is empty is_empty: bool = deque.is_empty() print("Is the double-ended queue empty =", is_empty) ================================================ FILE: en/codes/python/chapter_stack_and_queue/array_queue.py ================================================ """ File: array_queue.py Created Time: 2022-12-01 Author: Peng Chen (pengchzn@gmail.com) """ class ArrayQueue: """Queue based on circular array implementation""" def __init__(self, size: int): """Constructor""" self._nums: list[int] = [0] * size # Array for storing queue elements self._front: int = 0 # Front pointer, points to the front of the queue element self._size: int = 0 # Queue length def capacity(self) -> int: """Get the capacity of the queue""" return len(self._nums) def size(self) -> int: """Get the length of the queue""" return self._size def is_empty(self) -> bool: """Check if the queue is empty""" return self._size == 0 def push(self, num: int): """Enqueue""" if self._size == self.capacity(): raise IndexError("Queue is full") # Calculate rear pointer, points to rear index + 1 # Use modulo operation to wrap rear around to the head after passing the tail of the array rear: int = (self._front + self._size) % self.capacity() # Add num to the rear of the queue self._nums[rear] = num self._size += 1 def pop(self) -> int: """Dequeue""" num: int = self.peek() # Front pointer moves one position backward, if it passes the tail, return to the head of the array self._front = (self._front + 1) % self.capacity() self._size -= 1 return num def peek(self) -> int: """Access front of the queue element""" if self.is_empty(): raise IndexError("Queue is empty") return self._nums[self._front] def to_list(self) -> list[int]: """Return list for printing""" 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__": # Initialize queue queue = ArrayQueue(10) # Elements enqueue queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) print("queue =", queue.to_list()) # Access front of the queue element peek: int = queue.peek() print("Front of the queue element peek =", peek) # Element dequeue pop: int = queue.pop() print("Dequeued element pop =", pop) print("After dequeue queue =", queue.to_list()) # Get the length of the queue size: int = queue.size() print("Length of the queue size =", size) # Check if the queue is empty is_empty: bool = queue.is_empty() print("Is the queue empty =", is_empty) # Test circular array for i in range(10): queue.push(i) queue.pop() print("Round", i, "enqueue + dequeue after queue = ", queue.to_list()) ================================================ FILE: en/codes/python/chapter_stack_and_queue/array_stack.py ================================================ """ File: array_stack.py Created Time: 2022-11-29 Author: Peng Chen (pengchzn@gmail.com) """ class ArrayStack: """Stack based on array implementation""" def __init__(self): """Constructor""" self._stack: list[int] = [] def size(self) -> int: """Get the length of the stack""" return len(self._stack) def is_empty(self) -> bool: """Check if the stack is empty""" return self.size() == 0 def push(self, item: int): """Push""" self._stack.append(item) def pop(self) -> int: """Pop""" if self.is_empty(): raise IndexError("Stack is empty") return self._stack.pop() def peek(self) -> int: """Access top of the stack element""" if self.is_empty(): raise IndexError("Stack is empty") return self._stack[-1] def to_list(self) -> list[int]: """Return list for printing""" return self._stack """Driver Code""" if __name__ == "__main__": # Initialize stack stack = ArrayStack() # Elements push onto stack stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) print("stack =", stack.to_list()) # Access top of the stack element peek: int = stack.peek() print("Top of the stack element peek =", peek) # Element pop from stack pop: int = stack.pop() print("Popped element pop =", pop) print("After pop stack =", stack.to_list()) # Get the length of the stack size: int = stack.size() print("Length of the stack size =", size) # Check if it is empty is_empty: bool = stack.is_empty() print("Is the stack empty =", is_empty) ================================================ FILE: en/codes/python/chapter_stack_and_queue/deque.py ================================================ """ File: deque.py Created Time: 2022-11-29 Author: Peng Chen (pengchzn@gmail.com) """ from collections import deque """Driver Code""" if __name__ == "__main__": # Initialize double-ended queue deq: deque[int] = deque() # Elements enqueue deq.append(2) # Add to the rear of the queue deq.append(5) deq.append(4) deq.appendleft(3) # Add to the front of the queue deq.appendleft(1) print("double-ended queue deque =", deq) # Access elements front: int = deq[0] # Front of the queue element print("Front of the queue element front =", front) rear: int = deq[-1] # Rear of the queue element print("Rear of the queue element rear =", rear) # Elements dequeue pop_front: int = deq.popleft() # Front of the queue element dequeues print("Front dequeued element pop_front =", pop_front) print("After front dequeue deque =", deq) pop_rear: int = deq.pop() # Rear of the queue element dequeues print("Rear dequeued element pop_rear =", pop_rear) print("After rear dequeue deque =", deq) # Get the length of the double-ended queue size: int = len(deq) print("Length of the double-ended queue size =", size) # Check if the double-ended queue is empty is_empty: bool = len(deq) == 0 print("Is the double-ended queue empty =", is_empty) ================================================ FILE: en/codes/python/chapter_stack_and_queue/linkedlist_deque.py ================================================ """ File: linkedlist_deque.py Created Time: 2023-03-01 Author: krahets (krahets@163.com) """ class ListNode: """Doubly linked list node""" def __init__(self, val: int): """Constructor""" self.val: int = val self.next: ListNode | None = None # Successor node reference self.prev: ListNode | None = None # Predecessor node reference class LinkedListDeque: """Double-ended queue based on doubly linked list implementation""" def __init__(self): """Constructor""" self._front: ListNode | None = None # Head node front self._rear: ListNode | None = None # Tail node rear self._size: int = 0 # Length of the double-ended queue def size(self) -> int: """Get the length of the double-ended queue""" return self._size def is_empty(self) -> bool: """Check if the double-ended queue is empty""" return self._size == 0 def push(self, num: int, is_front: bool): """Enqueue operation""" node = ListNode(num) # If the linked list is empty, make both front and rear point to node if self.is_empty(): self._front = self._rear = node # Front of the queue enqueue operation elif is_front: # Add node to the head of the linked list self._front.prev = node node.next = self._front self._front = node # Update head node # Rear of the queue enqueue operation else: # Add node to the tail of the linked list self._rear.next = node node.prev = self._rear self._rear = node # Update tail node self._size += 1 # Update queue length def push_first(self, num: int): """Front of the queue enqueue""" self.push(num, True) def push_last(self, num: int): """Rear of the queue enqueue""" self.push(num, False) def pop(self, is_front: bool) -> int: """Dequeue operation""" if self.is_empty(): raise IndexError("Double-ended queue is empty") # Front of the queue dequeue operation if is_front: val: int = self._front.val # Temporarily store head node value # Delete head node fnext: ListNode | None = self._front.next if fnext is not None: fnext.prev = None self._front.next = None self._front = fnext # Update head node # Rear of the queue dequeue operation else: val: int = self._rear.val # Temporarily store tail node value # Delete tail node rprev: ListNode | None = self._rear.prev if rprev is not None: rprev.next = None self._rear.prev = None self._rear = rprev # Update tail node self._size -= 1 # Update queue length return val def pop_first(self) -> int: """Front of the queue dequeue""" return self.pop(True) def pop_last(self) -> int: """Rear of the queue dequeue""" return self.pop(False) def peek_first(self) -> int: """Access front of the queue element""" if self.is_empty(): raise IndexError("Double-ended queue is empty") return self._front.val def peek_last(self) -> int: """Access rear of the queue element""" if self.is_empty(): raise IndexError("Double-ended queue is empty") return self._rear.val def to_array(self) -> list[int]: """Return array for printing""" 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__": # Initialize double-ended queue deque = LinkedListDeque() deque.push_last(3) deque.push_last(2) deque.push_last(5) print("double-ended queue deque =", deque.to_array()) # Access elements peek_first: int = deque.peek_first() print("Front of the queue element peek_first =", peek_first) peek_last: int = deque.peek_last() print("Rear of the queue element peek_last =", peek_last) # Elements enqueue deque.push_last(4) print("Element 4 rear enqueue after deque =", deque.to_array()) deque.push_first(1) print("Element 1 front enqueue after deque =", deque.to_array()) # Elements dequeue pop_last: int = deque.pop_last() print("Rear dequeued element =", pop_last, ", rear dequeue after deque =", deque.to_array()) pop_first: int = deque.pop_first() print("Front dequeued element =", pop_first, ", front dequeue after deque =", deque.to_array()) # Get the length of the double-ended queue size: int = deque.size() print("Length of the double-ended queue size =", size) # Check if the double-ended queue is empty is_empty: bool = deque.is_empty() print("Is the double-ended queue empty =", is_empty) ================================================ FILE: en/codes/python/chapter_stack_and_queue/linkedlist_queue.py ================================================ """ 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: """Queue based on linked list implementation""" def __init__(self): """Constructor""" self._front: ListNode | None = None # Head node front self._rear: ListNode | None = None # Tail node rear self._size: int = 0 def size(self) -> int: """Get the length of the queue""" return self._size def is_empty(self) -> bool: """Check if the queue is empty""" return self._size == 0 def push(self, num: int): """Enqueue""" # Add num after the tail node node = ListNode(num) # If the queue is empty, make both front and rear point to the node if self._front is None: self._front = node self._rear = node # If the queue is not empty, add the node after the tail node else: self._rear.next = node self._rear = node self._size += 1 def pop(self) -> int: """Dequeue""" num = self.peek() # Delete head node self._front = self._front.next self._size -= 1 return num def peek(self) -> int: """Access front of the queue element""" if self.is_empty(): raise IndexError("Queue is empty") return self._front.val def to_list(self) -> list[int]: """Convert to list for printing""" queue = [] temp = self._front while temp: queue.append(temp.val) temp = temp.next return queue """Driver Code""" if __name__ == "__main__": # Initialize queue queue = LinkedListQueue() # Elements enqueue queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) print("queue =", queue.to_list()) # Access front of the queue element peek: int = queue.peek() print("Front of the queue element front =", peek) # Element dequeue pop_front: int = queue.pop() print("Dequeued element pop =", pop_front) print("After dequeue queue =", queue.to_list()) # Get the length of the queue size: int = queue.size() print("Length of the queue size =", size) # Check if the queue is empty is_empty: bool = queue.is_empty() print("Is the queue empty =", is_empty) ================================================ FILE: en/codes/python/chapter_stack_and_queue/linkedlist_stack.py ================================================ """ 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: """Stack based on linked list implementation""" def __init__(self): """Constructor""" self._peek: ListNode | None = None self._size: int = 0 def size(self) -> int: """Get the length of the stack""" return self._size def is_empty(self) -> bool: """Check if the stack is empty""" return self._size == 0 def push(self, val: int): """Push""" node = ListNode(val) node.next = self._peek self._peek = node self._size += 1 def pop(self) -> int: """Pop""" num = self.peek() self._peek = self._peek.next self._size -= 1 return num def peek(self) -> int: """Access top of the stack element""" if self.is_empty(): raise IndexError("Stack is empty") return self._peek.val def to_list(self) -> list[int]: """Convert to list for printing""" arr = [] node = self._peek while node: arr.append(node.val) node = node.next arr.reverse() return arr """Driver Code""" if __name__ == "__main__": # Initialize stack stack = LinkedListStack() # Elements push onto stack stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) print("stack =", stack.to_list()) # Access top of the stack element peek: int = stack.peek() print("Top of the stack element peek =", peek) # Element pop from stack pop: int = stack.pop() print("Popped element pop =", pop) print("After pop stack =", stack.to_list()) # Get the length of the stack size: int = stack.size() print("Length of the stack size =", size) # Check if it is empty is_empty: bool = stack.is_empty() print("Is the stack empty =", is_empty) ================================================ FILE: en/codes/python/chapter_stack_and_queue/queue.py ================================================ """ File: queue.py Created Time: 2022-11-29 Author: Peng Chen (pengchzn@gmail.com) """ from collections import deque """Driver Code""" if __name__ == "__main__": # Initialize queue # In Python, we generally use the double-ended queue class deque as a queue # Although queue.Queue() is a proper queue class, it is not very convenient to use que: deque[int] = deque() # Elements enqueue que.append(1) que.append(3) que.append(2) que.append(5) que.append(4) print("queue que =", que) # Access front of the queue element front: int = que[0] print("Front of the queue element front =", front) # Element dequeue pop: int = que.popleft() print("Dequeued element pop =", pop) print("After dequeue que =", que) # Get the length of the queue size: int = len(que) print("Length of the queue size =", size) # Check if the queue is empty is_empty: bool = len(que) == 0 print("Is the queue empty =", is_empty) ================================================ FILE: en/codes/python/chapter_stack_and_queue/stack.py ================================================ """ File: stack.py Created Time: 2022-11-29 Author: Peng Chen (pengchzn@gmail.com) """ """Driver Code""" if __name__ == "__main__": # Initialize stack # Python does not have a built-in stack class, we can use list as a stack stack: list[int] = [] # Elements push onto stack stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) print("stack =", stack) # Access top of the stack element peek: int = stack[-1] print("Top of the stack element peek =", peek) # Element pop from stack pop: int = stack.pop() print("Popped element pop =", pop) print("After pop stack =", stack) # Get the length of the stack size: int = len(stack) print("Length of the stack size =", size) # Check if it is empty is_empty: bool = len(stack) == 0 print("Is the stack empty =", is_empty) ================================================ FILE: en/codes/python/chapter_tree/array_binary_tree.py ================================================ """ 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: """Binary tree class represented by array""" def __init__(self, arr: list[int | None]): """Constructor""" self._tree = list(arr) def size(self): """List capacity""" return len(self._tree) def val(self, i: int) -> int | None: """Get value of node at index i""" # If index is out of bounds, return None, representing empty position if i < 0 or i >= self.size(): return None return self._tree[i] def left(self, i: int) -> int | None: """Get index of left child node of node at index i""" return 2 * i + 1 def right(self, i: int) -> int | None: """Get index of right child node of node at index i""" return 2 * i + 2 def parent(self, i: int) -> int | None: """Get index of parent node of node at index i""" return (i - 1) // 2 def level_order(self) -> list[int]: """Level-order traversal""" self.res = [] # Traverse array directly 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): """Depth-first traversal""" if self.val(i) is None: return # Preorder traversal if order == "pre": self.res.append(self.val(i)) self.dfs(self.left(i), order) # Inorder traversal if order == "in": self.res.append(self.val(i)) self.dfs(self.right(i), order) # Postorder traversal if order == "post": self.res.append(self.val(i)) def pre_order(self) -> list[int]: """Preorder traversal""" self.res = [] self.dfs(0, order="pre") return self.res def in_order(self) -> list[int]: """Inorder traversal""" self.res = [] self.dfs(0, order="in") return self.res def post_order(self) -> list[int]: """Postorder traversal""" self.res = [] self.dfs(0, order="post") return self.res """Driver Code""" if __name__ == "__main__": # Initialize binary tree # Here we use a function to generate a binary tree directly from an array arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] root = list_to_tree(arr) print("\nInitialize binary tree\n") print("Array representation of binary tree:") print(arr) print("Linked list representation of binary tree:") print_tree(root) # Binary tree class represented by array abt = ArrayBinaryTree(arr) # Access nodes i = 1 l, r, p = abt.left(i), abt.right(i), abt.parent(i) print(f"\nCurrent node's index is {i}, value is {abt.val(i)}") print(f"Its left child node's index is {l}, value is {abt.val(l)}") print(f"Its right child node's index is {r}, value is {abt.val(r)}") print(f"Its parent node's index is {p}, value is {abt.val(p)}") # Traverse tree res = abt.level_order() print("\nLevel-order traversal is:", res) res = abt.pre_order() print("Preorder traversal is:", res) res = abt.in_order() print("Inorder traversal is:", res) res = abt.post_order() print("Postorder traversal is:", res) ================================================ FILE: en/codes/python/chapter_tree/avl_tree.py ================================================ """ 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 tree""" def __init__(self): """Constructor""" self._root = None def get_root(self) -> TreeNode | None: """Get binary tree root node""" return self._root def height(self, node: TreeNode | None) -> int: """Get node height""" # Empty node height is -1, leaf node height is 0 if node is not None: return node.height return -1 def update_height(self, node: TreeNode | None): """Update node height""" # Node height equals the height of the tallest subtree + 1 node.height = max([self.height(node.left), self.height(node.right)]) + 1 def balance_factor(self, node: TreeNode | None) -> int: """Get balance factor""" # Empty node balance factor is 0 if node is None: return 0 # Node balance factor = left subtree height - right subtree height return self.height(node.left) - self.height(node.right) def right_rotate(self, node: TreeNode | None) -> TreeNode | None: """Right rotation operation""" child = node.left grand_child = child.right # Using child as pivot, rotate node to the right child.right = node node.left = grand_child # Update node height self.update_height(node) self.update_height(child) # Return root node of subtree after rotation return child def left_rotate(self, node: TreeNode | None) -> TreeNode | None: """Left rotation operation""" child = node.right grand_child = child.left # Using child as pivot, rotate node to the left child.left = node node.right = grand_child # Update node height self.update_height(node) self.update_height(child) # Return root node of subtree after rotation return child def rotate(self, node: TreeNode | None) -> TreeNode | None: """Perform rotation operation to restore balance to this subtree""" # Get balance factor of node balance_factor = self.balance_factor(node) # Left-leaning tree if balance_factor > 1: if self.balance_factor(node.left) >= 0: # Right rotation return self.right_rotate(node) else: # First left rotation then right rotation node.left = self.left_rotate(node.left) return self.right_rotate(node) # Right-leaning tree elif balance_factor < -1: if self.balance_factor(node.right) <= 0: # Left rotation return self.left_rotate(node) else: # First right rotation then left rotation node.right = self.right_rotate(node.right) return self.left_rotate(node) # Balanced tree, no rotation needed, return directly return node def insert(self, val): """Insert node""" self._root = self.insert_helper(self._root, val) def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode: """Recursively insert node (helper method)""" if node is None: return TreeNode(val) # 1. Find insertion position and insert node 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: # Duplicate node not inserted, return directly return node # Update node height self.update_height(node) # 2. Perform rotation operation to restore balance to this subtree return self.rotate(node) def remove(self, val: int): """Delete node""" self._root = self.remove_helper(self._root, val) def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None: """Recursively delete node (helper method)""" if node is None: return None # 1. Find node and delete 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 # Number of child nodes = 0, delete node directly and return if child is None: return None # Number of child nodes = 1, delete node directly else: node = child else: # Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it 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 # Update node height self.update_height(node) # 2. Perform rotation operation to restore balance to this subtree return self.rotate(node) def search(self, val: int) -> TreeNode | None: """Search node""" cur = self._root # Loop search, exit after passing leaf node while cur is not None: # Target node is in cur's right subtree if cur.val < val: cur = cur.right # Target node is in cur's left subtree elif cur.val > val: cur = cur.left # Found target node, exit loop else: break # Return target node return cur """Driver Code""" if __name__ == "__main__": def test_insert(tree: AVLTree, val: int): tree.insert(val) print("\nAfter inserting node {}, AVL tree is".format(val)) print_tree(tree.get_root()) def test_remove(tree: AVLTree, val: int): tree.remove(val) print("\nAfter deleting node {}, AVL tree is".format(val)) print_tree(tree.get_root()) # Initialize empty AVL tree avl_tree = AVLTree() # Insert nodes # Please pay attention to how the AVL tree maintains balance after inserting nodes for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6]: test_insert(avl_tree, val) # Insert duplicate node test_insert(avl_tree, 7) # Delete nodes # Please pay attention to how the AVL tree maintains balance after deleting nodes test_remove(avl_tree, 8) # Delete node with degree 0 test_remove(avl_tree, 5) # Delete node with degree 1 test_remove(avl_tree, 4) # Delete node with degree 2 result_node = avl_tree.search(7) print("\nFound node object is {}, node value = {}".format(result_node, result_node.val)) ================================================ FILE: en/codes/python/chapter_tree/binary_search_tree.py ================================================ """ 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: """Binary search tree""" def __init__(self): """Constructor""" # Initialize empty tree self._root = None def get_root(self) -> TreeNode | None: """Get binary tree root node""" return self._root def search(self, num: int) -> TreeNode | None: """Search node""" cur = self._root # Loop search, exit after passing leaf node while cur is not None: # Target node is in cur's right subtree if cur.val < num: cur = cur.right # Target node is in cur's left subtree elif cur.val > num: cur = cur.left # Found target node, exit loop else: break return cur def insert(self, num: int): """Insert node""" # If tree is empty, initialize root node if self._root is None: self._root = TreeNode(num) return # Loop search, exit after passing leaf node cur, pre = self._root, None while cur is not None: # Found duplicate node, return directly if cur.val == num: return pre = cur # Insertion position is in cur's right subtree if cur.val < num: cur = cur.right # Insertion position is in cur's left subtree else: cur = cur.left # Insert node node = TreeNode(num) if pre.val < num: pre.right = node else: pre.left = node def remove(self, num: int): """Delete node""" # If tree is empty, return directly if self._root is None: return # Loop search, exit after passing leaf node cur, pre = self._root, None while cur is not None: # Found node to delete, exit loop if cur.val == num: break pre = cur # Node to delete is in cur's right subtree if cur.val < num: cur = cur.right # Node to delete is in cur's left subtree else: cur = cur.left # If no node to delete, return directly if cur is None: return # Number of child nodes = 0 or 1 if cur.left is None or cur.right is None: # When number of child nodes = 0 / 1, child = null / that child node child = cur.left or cur.right # Delete node cur if cur != self._root: if pre.left == cur: pre.left = child else: pre.right = child else: # If deleted node is root node, reassign root node self._root = child # Number of child nodes = 2 else: # Get next node of cur in inorder traversal tmp: TreeNode = cur.right while tmp.left is not None: tmp = tmp.left # Recursively delete node tmp self.remove(tmp.val) # Replace cur with tmp cur.val = tmp.val """Driver Code""" if __name__ == "__main__": # Initialize binary search tree bst = BinarySearchTree() nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] # Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree for num in nums: bst.insert(num) print("\nInitialized binary tree is\n") print_tree(bst.get_root()) # Search node node = bst.search(7) print("\nFound node object is: {}, node value = {}".format(node, node.val)) # Insert node bst.insert(16) print("\nAfter inserting node 16, binary tree is\n") print_tree(bst.get_root()) # Delete node bst.remove(1) print("\nAfter deleting node 1, binary tree is\n") print_tree(bst.get_root()) bst.remove(2) print("\nAfter deleting node 2, binary tree is\n") print_tree(bst.get_root()) bst.remove(4) print("\nAfter deleting node 4, binary tree is\n") print_tree(bst.get_root()) ================================================ FILE: en/codes/python/chapter_tree/binary_tree.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # Initialize binary tree # Initialize nodes n1 = TreeNode(val=1) n2 = TreeNode(val=2) n3 = TreeNode(val=3) n4 = TreeNode(val=4) n5 = TreeNode(val=5) # Build references (pointers) between nodes n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 print("\nInitialize binary tree\n") print_tree(n1) # Insert and delete nodes P = TreeNode(0) # Insert node P between n1 -> n2 n1.left = P P.left = n2 print("\nAfter inserting node P\n") print_tree(n1) # Delete node n1.left = n2 print("\nAfter deleting node P\n") print_tree(n1) ================================================ FILE: en/codes/python/chapter_tree/binary_tree_bfs.py ================================================ """ 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]: """Level-order traversal""" # Initialize queue, add root node queue: deque[TreeNode] = deque() queue.append(root) # Initialize a list to save the traversal sequence res = [] while queue: node: TreeNode = queue.popleft() # Dequeue res.append(node.val) # Save node value if node.left is not None: queue.append(node.left) # Left child node enqueue if node.right is not None: queue.append(node.right) # Right child node enqueue return res """Driver Code""" if __name__ == "__main__": # Initialize binary tree # Here we use a function to generate a binary tree directly from an array root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\nInitialize binary tree\n") print_tree(root) # Level-order traversal res: list[int] = level_order(root) print("\nLevel-order traversal node print sequence = ", res) ================================================ FILE: en/codes/python/chapter_tree/binary_tree_dfs.py ================================================ """ 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): """Preorder traversal""" if root is None: return # Visit priority: root node -> left subtree -> right subtree res.append(root.val) pre_order(root=root.left) pre_order(root=root.right) def in_order(root: TreeNode | None): """Inorder traversal""" if root is None: return # Visit priority: left subtree -> root node -> right subtree in_order(root=root.left) res.append(root.val) in_order(root=root.right) def post_order(root: TreeNode | None): """Postorder traversal""" if root is None: return # Visit priority: left subtree -> right subtree -> root node post_order(root=root.left) post_order(root=root.right) res.append(root.val) """Driver Code""" if __name__ == "__main__": # Initialize binary tree # Here we use a function to generate a binary tree directly from an array root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\nInitialize binary tree\n") print_tree(root) # Preorder traversal res = [] pre_order(root) print("\nPreorder traversal node print sequence = ", res) # Inorder traversal res.clear() in_order(root) print("\nInorder traversal node print sequence = ", res) # Postorder traversal res.clear() post_order(root) print("\nPostorder traversal node print sequence = ", res) ================================================ FILE: en/codes/python/modules/__init__.py ================================================ # Follow the PEP 585 - Type Hinting Generics In Standard Collections # https://peps.python.org/pep-0585/ from __future__ import annotations # Import common libs here to simplify the code by `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, ) ================================================ FILE: en/codes/python/modules/list_node.py ================================================ """ File: list_node.py Created Time: 2021-12-11 Author: krahets (krahets@163.com) """ class ListNode: """Linked list node class""" def __init__(self, val: int): self.val: int = val # Node value self.next: ListNode | None = None # Reference to next node def list_to_linked_list(arr: list[int]) -> ListNode | None: """Deserialize a list into a linked list""" 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]: """Serialize a linked list into a list""" arr: list[int] = [] while head: arr.append(head.val) head = head.next return arr ================================================ FILE: en/codes/python/modules/print_util.py ================================================ """ 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]]): """Print matrix""" s = [] for arr in mat: s.append(" " + str(arr)) print("[\n" + ",\n".join(s) + "\n]") def print_linked_list(head: ListNode | None): """Print linked list""" 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 ): """ Print binary tree This tree printer is borrowed from TECHIE DELIGHT https://www.techiedelight.com/c-program-print-binary-tree/ """ if root is None: return 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): """Print dictionary""" for key, value in hmap.items(): print(key, "->", value) def print_heap(heap: list[int]): """Print heap""" print("Array representation of the heap:", heap) print("Tree representation of the heap:") root: TreeNode | None = list_to_tree(heap) print_tree(root) ================================================ FILE: en/codes/python/modules/tree_node.py ================================================ """ File: tree_node.py Created Time: 2021-12-11 Author: krahets (krahets@163.com) """ from collections import deque class TreeNode: """Binary tree node class""" def __init__(self, val: int = 0): self.val: int = val # Node value self.height: int = 0 # Node height self.left: TreeNode | None = None # Reference to left child node self.right: TreeNode | None = None # Reference to right child node # For the serialization encoding rules, please refer to: # https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ # Array representation of binary tree: # [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] # Linked list representation of binary tree: # /——— 15 # /——— 7 # /——— 3 # | \——— 6 # | \——— 12 # ——— 1 # \——— 2 # | /——— 9 # \——— 4 # \——— 8 def list_to_tree_dfs(arr: list[int], i: int) -> TreeNode | None: """Deserialize a list into a binary tree: recursion""" # If the index exceeds the array length, or the corresponding element is None, return None if i < 0 or i >= len(arr) or arr[i] is None: return None # Build the current node root = TreeNode(arr[i]) # Recursively build the left and right subtrees 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: """Deserialize a list into a binary tree""" return list_to_tree_dfs(arr, 0) def tree_to_list_dfs(root: TreeNode, i: int, res: list[int]) -> list[int]: """Serialize a binary tree into a list: recursion""" 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]: """Serialize a binary tree into a list""" res = [] tree_to_list_dfs(root, 0, res) return res ================================================ FILE: en/codes/python/modules/vertex.py ================================================ # File: vertex.py # Created Time: 2023-02-23 # Author: krahets (krahets@163.com) class Vertex: """Vertex class""" def __init__(self, val: int): self.val = val def vals_to_vets(vals: list[int]) -> list["Vertex"]: """Input value list vals, return vertex list vets""" return [Vertex(val) for val in vals] def vets_to_vals(vets: list["Vertex"]) -> list[int]: """Input vertex list vets, return value list vals""" return [vet.val for vet in vets] ================================================ FILE: en/codes/python/test_all.py ================================================ import os import glob import subprocess env = os.environ.copy() env["PYTHONIOENCODING"] = "utf-8" if __name__ == "__main__": # find source code files src_paths = sorted(glob.glob("chapter_*/*.py")) errors = [] # run python code 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' ) # Wait for the process to complete, and get the output and error messages stdout, stderr = process.communicate() # Check the exit status exit_status = process.returncode if exit_status != 0: errors.append(stderr) print(f"Tested {len(src_paths)} files") print(f"Found exception in {len(errors)} files") if len(errors) > 0: raise RuntimeError("\n\n".join(errors)) ================================================ FILE: en/codes/ruby/chapter_array_and_linkedlist/array.rb ================================================ =begin File: array.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Random access element ### def random_access(nums) # Randomly select a number in the interval [0, nums.length) random_index = Random.rand(0...nums.length) # Retrieve and return the random element nums[random_index] end ### Extend array length ### # Note: Ruby's Array is dynamic array, can be directly expanded # For learning purposes, this function treats Array as fixed-length array def extend(nums, enlarge) # Initialize an array with extended length res = Array.new(nums.length + enlarge, 0) # Copy all elements from the original array to the new array for i in 0...nums.length res[i] = nums[i] end # Return the extended new array res end ### Insert element num at index in array ### def insert(nums, num, index) # Move all elements at and after index index backward by one position for i in (nums.length - 1).downto(index + 1) nums[i] = nums[i - 1] end # Assign num to the element at index index nums[index] = num end ### Delete element at index ### def remove(nums, index) # Move all elements after index index forward by one position for i in index...(nums.length - 1) nums[i] = nums[i + 1] end end ### Traverse array ### def traverse(nums) count = 0 # Traverse array by index for i in 0...nums.length count += nums[i] end # Direct traversal of array elements for num in nums count += num end end ### Find specified element in array ### def find(nums, target) for i in 0...nums.length return i if nums[i] == target end -1 end ### Driver Code ### if __FILE__ == $0 # Initialize array arr = Array.new(5, 0) puts "Array arr = #{arr}" nums = [1, 3, 2, 5, 4] puts "Array nums = #{nums}" # Insert element random_num = random_access(nums) puts "Get random element #{random_num} from nums" # Traverse array nums = extend(nums, 3) puts "Extend array length to 8, get nums = #{nums}" # Insert element insert(nums, 6, 3) puts "Insert number 6 at index 3, get nums = #{nums}" # Remove element remove(nums, 2) puts "Delete element at index 2, get nums = #{nums}" # Traverse array traverse(nums) # Find element index = find(nums, 3) puts "Find element 3 in nums, index = #{index}" end ================================================ FILE: en/codes/ruby/chapter_array_and_linkedlist/linked_list.rb ================================================ =begin File: linked_list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' require_relative '../utils/print_util' ### Insert node _p after node n0 in linked list ### # Ruby's `p` is a built-in function, `P` is a constant, so use `_p` instead def insert(n0, _p) n1 = n0.next _p.next = n1 n0.next = _p end ### Delete first node after node n0 in linked list ### def remove(n0) return if n0.next.nil? # n0 -> remove_node -> n1 remove_node = n0.next n1 = remove_node.next n0.next = n1 end ### Access node at index in linked list ### def access(head, index) for i in 0...index return nil if head.nil? head = head.next end head end ### Find first node with value target in linked list ### def find(head, target) index = 0 while head return index if head.val == target head = head.next index += 1 end -1 end ### Driver Code ### if __FILE__ == $0 # Initialize linked list # Initialize each node n0 = ListNode.new(1) n1 = ListNode.new(3) n2 = ListNode.new(2) n3 = ListNode.new(5) n4 = ListNode.new(4) # Build references between nodes n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 puts "Initialized linked list is" print_linked_list(n0) # Insert node insert(n0, ListNode.new(0)) print_linked_list n0 # Remove node remove(n0) puts "Linked list after removing node is" print_linked_list(n0) # Access node node = access(n0, 3) puts "Value of node at index 3 in linked list = #{node.val}" # Search node index = find(n0, 2) puts "Index of node with value 2 in linked list = #{index}" end ================================================ FILE: en/codes/ruby/chapter_array_and_linkedlist/list.rb ================================================ =begin File: list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # Initialize list nums = [1, 3, 2, 5, 4] puts "List nums = #{nums}" # Update element num = nums[1] puts "Access element at index 1, get num = #{num}" # Add elements at the end nums[1] = 0 puts "Update element at index 1 to 0, get nums = #{nums}" # Remove element nums.clear puts "After clearing list, nums = #{nums}" # Direct traversal of list elements nums << 1 nums << 3 nums << 2 nums << 5 nums << 4 puts "After adding elements, nums = #{nums}" # Sort list nums.insert(3, 6) puts "Insert element 6 at index 3, get nums = #{nums}" # Remove element nums.delete_at(3) puts "Delete element at index 3, get nums = #{nums}" # Traverse list by index count = 0 for i in 0...nums.length count += nums[i] end # Directly traverse list elements count = 0 nums.each do |x| count += x end # Concatenate two lists nums1 = [6, 8, 7, 10, 9] nums += nums1 puts "After concatenating list nums1 to nums, get nums = #{nums}" nums = nums.sort { |a, b| a <=> b } puts "After sorting list, nums = #{nums}" end ================================================ FILE: en/codes/ruby/chapter_array_and_linkedlist/my_list.rb ================================================ =begin File: my_list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### List class ### class MyList attr_reader :size # Get list length (current number of elements) attr_reader :capacity # Get list capacity ### Constructor ### def initialize @capacity = 10 @size = 0 @extend_ratio = 2 @arr = Array.new(capacity) end ### Access element ### def get(index) # If the index is out of bounds, throw an exception, as below raise IndexError, "Index out of bounds" if index < 0 || index >= size @arr[index] end ### Access element ### def set(index, num) raise IndexError, "Index out of bounds" if index < 0 || index >= size @arr[index] = num end ### Add element at end ### def add(num) # When the number of elements exceeds capacity, trigger the extension mechanism extend_capacity if size == capacity @arr[size] = num # Update the number of elements @size += 1 end ### Insert element in middle ### def insert(index, num) raise IndexError, "Index out of bounds" if index < 0 || index >= size # When the number of elements exceeds capacity, trigger the extension mechanism extend_capacity if size == capacity # Move all elements after index index forward by one position for j in (size - 1).downto(index) @arr[j + 1] = @arr[j] end @arr[index] = num # Update the number of elements @size += 1 end ### Delete element ### def remove(index) raise IndexError, "Index out of bounds" if index < 0 || index >= size num = @arr[index] # Move all elements after index forward by one position for j in index...size @arr[j] = @arr[j + 1] end # Update the number of elements @size -= 1 # Return the removed element num end ### Expand list capacity ### def extend_capacity # Create new array with length extend_ratio times original, copy original array to new array arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1)) # Add elements at the end @capacity = arr.length end ### Convert list to array ### def to_array sz = size # Elements enqueue arr = Array.new(sz) for i in 0...sz arr[i] = get(i) end arr end end ### Driver Code ### if __FILE__ == $0 # Initialize list nums = MyList.new # Direct traversal of list elements nums.add(1) nums.add(3) nums.add(2) nums.add(5) nums.add(4) puts "List nums = #{nums.to_array}, capacity = #{nums.capacity}, length = #{nums.size}" # Sort list nums.insert(3, 6) puts "Insert number 6 at index 3, get nums = #{nums.to_array}" # Remove element nums.remove(3) puts "Delete element at index 3, get nums = #{nums.to_array}" # Update element num = nums.get(1) puts "Access element at index 1, get num = #{num}" # Add elements at the end nums.set(1, 0) puts "Update element at index 1 to 0, get nums = #{nums.to_array}" # Test capacity expansion mechanism for i in 0...10 # At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism nums.add(i) end puts "After expansion, list nums = #{nums.to_array}, capacity = #{nums.capacity}, length = #{nums.size}" end ================================================ FILE: en/codes/ruby/chapter_backtracking/n_queens.rb ================================================ =begin File: n_queens.rb Created Time: 2024-05-21 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Backtracking: n queens ### def backtrack(row, n, state, res, cols, diags1, diags2) # When all rows are placed, record the solution if row == n res << state.map { |row| row.dup } return end # Traverse all columns for col in 0...n # Calculate the main diagonal and anti-diagonal corresponding to this cell diag1 = row - col + n - 1 diag2 = row + col # Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell if !cols[col] && !diags1[diag1] && !diags2[diag2] # Attempt: place the queen in this cell state[row][col] = "Q" cols[col] = diags1[diag1] = diags2[diag2] = true # Place the next row backtrack(row + 1, n, state, res, cols, diags1, diags2) # Backtrack: restore this cell to an empty cell state[row][col] = "#" cols[col] = diags1[diag1] = diags2[diag2] = false end end end ### Solve n queens ### def n_queens(n) # Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell state = Array.new(n) { Array.new(n, "#") } cols = Array.new(n, false) # Record whether there is a queen in the column diags1 = Array.new(2 * n - 1, false) # Record whether there is a queen on the main diagonal diags2 = Array.new(2 * n - 1, false) # Record whether there is a queen on the anti-diagonal res = [] backtrack(0, n, state, res, cols, diags1, diags2) res end ### Driver Code ### if __FILE__ == $0 n = 4 res = n_queens(n) puts "Input board size is #{n}" puts "Total queen placement solutions: #{res.length}" for state in res puts "--------------------" for row in state p row end end end ================================================ FILE: en/codes/ruby/chapter_backtracking/permutations_i.rb ================================================ =begin File: permutations_i.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Backtracking: permutations I ### def backtrack(state, choices, selected, res) # When the state length equals the number of elements, record the solution if state.length == choices.length res << state.dup return end # Traverse all choices choices.each_with_index do |choice, i| # Pruning: do not allow repeated selection of elements unless selected[i] # Attempt: make choice, update state selected[i] = true state << choice # Proceed to the next round of selection backtrack(state, choices, selected, res) # Backtrack: undo choice, restore to previous state selected[i] = false state.pop end end end ### Permutations I ### def permutations_i(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ### Driver Code ### if __FILE__ == $0 nums = [1, 2, 3] res = permutations_i(nums) puts "Input array nums = #{nums}" puts "All permutations res = #{res}" end ================================================ FILE: en/codes/ruby/chapter_backtracking/permutations_ii.rb ================================================ =begin File: permutations_ii.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Backtracking: permutations II ### def backtrack(state, choices, selected, res) # When the state length equals the number of elements, record the solution if state.length == choices.length res << state.dup return end # Traverse all choices duplicated = Set.new choices.each_with_index do |choice, i| # Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if !selected[i] && !duplicated.include?(choice) # Attempt: make choice, update state duplicated.add(choice) selected[i] = true state << choice # Proceed to the next round of selection backtrack(state, choices, selected, res) # Backtrack: undo choice, restore to previous state selected[i] = false state.pop end end end ### Permutations II ### def permutations_ii(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ### Driver Code ### if __FILE__ == $0 nums = [1, 2, 2] res = permutations_ii(nums) puts "Input array nums = #{nums}" puts "All permutations res = #{res}" end ================================================ FILE: en/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb ================================================ =begin File: preorder_traversal_i_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Pre-order traversal: example 1 ### def pre_order(root) return unless root # Record solution $res << root if root.val == 7 pre_order(root.left) pre_order(root.right) end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\nInitialize binary tree" print_tree(root) # Preorder traversal $res = [] pre_order(root) puts "\nOutput all nodes with value 7" p $res.map { |node| node.val } end ================================================ FILE: en/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb ================================================ =begin File: preorder_traversal_ii_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Pre-order traversal: example 2 ### def pre_order(root) return unless root # Attempt $path << root # Record solution $res << $path.dup if root.val == 7 pre_order(root.left) pre_order(root.right) # Backtrack $path.pop end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\nInitialize binary tree" print_tree(root) # Preorder traversal $path, $res = [], [] pre_order(root) puts "\nOutput all paths from root node to node 7" for path in $res p path.map { |node| node.val } end end ================================================ FILE: en/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb ================================================ =begin File: preorder_traversal_iii_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Pre-order traversal: example 3 ### def pre_order(root) # Pruning return if !root || root.val == 3 # Attempt $path.append(root) # Record solution $res << $path.dup if root.val == 7 pre_order(root.left) pre_order(root.right) # Backtrack $path.pop end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\nInitialize binary tree" print_tree(root) # Preorder traversal $path, $res = [], [] pre_order(root) puts "\nOutput all paths from root node to node 7, paths do not include nodes with value 3" for path in $res p path.map { |node| node.val } end end ================================================ FILE: en/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb ================================================ =begin File: preorder_traversal_iii_template.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Check if current state is solution ### def is_solution?(state) !state.empty? && state.last.val == 7 end ### Record solution ### def record_solution(state, res) res << state.dup end ### Check if choice is valid in current state ### def is_valid?(state, choice) choice && choice.val != 3 end ### Update state ### def make_choice(state, choice) state << choice end ### Restore state ### def undo_choice(state, choice) state.pop end ### Backtracking: example 3 ### def backtrack(state, choices, res) # Check if it is a solution record_solution(state, res) if is_solution?(state) # Traverse all choices for choice in choices # Pruning: check if the choice is valid if is_valid?(state, choice) # Attempt: make choice, update state make_choice(state, choice) # Proceed to the next round of selection backtrack(state, [choice.left, choice.right], res) # Backtrack: undo choice, restore to previous state undo_choice(state, choice) end end end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\nInitialize binary tree" print_tree(root) # Backtracking algorithm res = [] backtrack([], [root], res) puts "\nOutput all paths from root node to node 7, requiring paths do not include nodes with value 3" for path in res p path.map { |node| node.val } end end ================================================ FILE: en/codes/ruby/chapter_backtracking/subset_sum_i.rb ================================================ =begin File: subset_sum_i.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Backtracking: subset sum I ### def backtrack(state, target, choices, start, res) # When the subset sum equals target, record the solution if target.zero? res << state.dup return end # Traverse all choices # Pruning 2: start traversing from start to avoid generating duplicate subsets for i in start...choices.length # Pruning 1: if the subset sum exceeds target, end the loop directly # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target break if target - choices[i] < 0 # Attempt: make choice, update target, start state << choices[i] # Proceed to the next round of selection backtrack(state, target - choices[i], choices, i, res) # Backtrack: undo choice, restore to previous state state.pop end end ### Solve subset sum I ### def subset_sum_i(nums, target) state = [] # State (subset) nums.sort! # Sort nums start = 0 # Start point for traversal res = [] # Result list (subset list) backtrack(state, target, nums, start, res) res end ### Driver Code ### if __FILE__ == $0 nums = [3, 4, 5] target = 9 res = subset_sum_i(nums, target) puts "Input array = #{nums}, target = #{target}" puts "All subsets with sum equal to #{target} res = #{res}" end ================================================ FILE: en/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb ================================================ =begin File: subset_sum_i_naive.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Backtracking: subset sum I ### def backtrack(state, target, total, choices, res) # When the subset sum equals target, record the solution if total == target res << state.dup return end # Traverse all choices for i in 0...choices.length # Pruning: if the subset sum exceeds target, skip this choice next if total + choices[i] > target # Attempt: make choice, update element sum total state << choices[i] # Proceed to the next round of selection backtrack(state, target, total + choices[i], choices, res) # Backtrack: undo choice, restore to previous state state.pop end end ### Solve subset sum I (with duplicate subsets) ### def subset_sum_i_naive(nums, target) state = [] # State (subset) total = 0 # Subset sum res = [] # Result list (subset list) backtrack(state, target, total, nums, res) res end ### Driver Code ### if __FILE__ == $0 nums = [3, 4, 5] target = 9 res = subset_sum_i_naive(nums, target) puts "Input array nums = #{nums}, target = #{target}" puts "All subsets with sum equal to #{target} res = #{res}" puts "Please note that this method outputs results containing duplicate sets" end ================================================ FILE: en/codes/ruby/chapter_backtracking/subset_sum_ii.rb ================================================ =begin File: subset_sum_ii.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Backtracking: subset sum II ### def backtrack(state, target, choices, start, res) # When the subset sum equals target, record the solution if target.zero? res << state.dup return end # Traverse all choices # Pruning 2: start traversing from start to avoid generating duplicate subsets # Pruning 3: start traversing from start to avoid repeatedly selecting the same element for i in start...choices.length # Pruning 1: if the subset sum exceeds target, end the loop directly # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target break if target - choices[i] < 0 # Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly next if i > start && choices[i] == choices[i - 1] # Attempt: make choice, update target, start state << choices[i] # Proceed to the next round of selection backtrack(state, target - choices[i], choices, i + 1, res) # Backtrack: undo choice, restore to previous state state.pop end end ### Solve subset sum II ### def subset_sum_ii(nums, target) state = [] # State (subset) nums.sort! # Sort nums start = 0 # Start point for traversal res = [] # Result list (subset list) backtrack(state, target, nums, start, res) res end ### Driver Code ### if __FILE__ == $0 nums = [4, 4, 5] target = 9 res = subset_sum_ii(nums, target) puts "Input array nums = #{nums}, target = #{target}" puts "All subsets with sum equal to #{target} res = #{res}" end ================================================ FILE: en/codes/ruby/chapter_computational_complexity/iteration.rb ================================================ =begin File: iteration.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com), Cy (9738314@gmail.com) =end ### for loop ### def for_loop(n) res = 0 # Sum 1, 2, ..., n-1, n for i in 1..n res += i end res end ### while loop ### def while_loop(n) res = 0 i = 1 # Initialize condition variable # Sum 1, 2, ..., n-1, n while i <= n res += i i += 1 # Update condition variable end res end ### while loop (two updates) ### def while_loop_ii(n) res = 0 i = 1 # Initialize condition variable # Sum 1, 4, 10, ... while i <= n res += i # Update condition variable i += 1 i *= 2 end res end ### Nested for loop ### def nested_for_loop(n) res = "" # Loop i = 1, 2, ..., n-1, n for i in 1..n # Loop j = 1, 2, ..., n-1, n for j in 1..n res += "(#{i}, #{j}), " end end res end ### Driver Code ### if __FILE__ == $0 n = 5 res = for_loop(n) puts "\nFor loop sum result res = #{res}" res = while_loop(n) puts "\nWhile loop sum result res = #{res}" res = while_loop_ii(n) puts "\nWhile loop (two updates) sum result res = #{res}" res = nested_for_loop(n) puts "\nNested for loop traversal result #{res}" end ================================================ FILE: en/codes/ruby/chapter_computational_complexity/recursion.rb ================================================ =begin File: recursion.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Recursion ### def recur(n) # Termination condition return 1 if n == 1 # Recurse: recursive call res = recur(n - 1) # Return: return result n + res end ### Use iteration to simulate recursion ### def for_loop_recur(n) # Use an explicit stack to simulate the system call stack stack = [] res = 0 # Recurse: recursive call for i in n.downto(0) # Simulate "recurse" with "push" stack << i end # Return: return result while !stack.empty? res += stack.pop end # res = 1+2+3+...+n res end ### Tail recursion ### def tail_recur(n, res) # Termination condition return res if n == 0 # Tail recursive call tail_recur(n - 1, res + n) end ### Fibonacci sequence: recursion ### def fib(n) # Termination condition f(1) = 0, f(2) = 1 return n - 1 if n == 1 || n == 2 # Recursive call f(n) = f(n-1) + f(n-2) res = fib(n - 1) + fib(n - 2) # Return result f(n) res end ### Driver Code ### if __FILE__ == $0 n = 5 res = recur(n) puts "\nRecursion sum result res = #{res}" res = for_loop_recur(n) puts "\nUsing iteration to simulate recursion sum result res = #{res}" res = tail_recur(n, 0) puts "\nTail recursion sum result res = #{res}" res = fib(n) puts "\nThe #{n}th Fibonacci number is #{res}" end ================================================ FILE: en/codes/ruby/chapter_computational_complexity/space_complexity.rb ================================================ =begin File: space_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Function ### def function # Perform some operations 0 end ### Constant time ### def constant(n) # Constants, variables, objects occupy O(1) space a = 0 nums = [0] * 10000 node = ListNode.new # Variables in the loop occupy O(1) space (0...n).each { c = 0 } # Functions in the loop occupy O(1) space (0...n).each { function } end ### Linear time ### def linear(n) # A list of length n occupies O(n) space nums = Array.new(n, 0) # A hash table of length n occupies O(n) space hmap = {} for i in 0...n hmap[i] = i.to_s end end ### Linear space (recursive) ### def linear_recur(n) puts "Recursion n = #{n}" return if n == 1 linear_recur(n - 1) end ### Quadratic time ### def quadratic(n) # 2D list uses O(n^2) space Array.new(n) { Array.new(n, 0) } end ### Quadratic space (recursive) ### def quadratic_recur(n) return 0 unless n > 0 # Array nums has length n, n-1, ..., 2, 1 nums = Array.new(n, 0) quadratic_recur(n - 1) end ### Exponential space (build full binary tree) ### def build_tree(n) return if n == 0 TreeNode.new.tap do |root| root.left = build_tree(n - 1) root.right = build_tree(n - 1) end end ### Driver Code ### if __FILE__ == $0 n = 5 # Constant order constant(n) # Linear order linear(n) linear_recur(n) # Exponential order quadratic(n) quadratic_recur(n) # Exponential order root = build_tree(n) print_tree(root) end ================================================ FILE: en/codes/ruby/chapter_computational_complexity/time_complexity.rb ================================================ =begin File: time_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Constant time ### def constant(n) count = 0 size = 100000 (0...size).each { count += 1 } count end ### Linear time ### def linear(n) count = 0 (0...n).each { count += 1 } count end ### Linear time (array traversal) ### def array_traversal(nums) count = 0 # Number of iterations is proportional to the array length for num in nums count += 1 end count end ### Quadratic time ### def quadratic(n) count = 0 # Number of iterations is quadratically related to the data size n for i in 0...n for j in 0...n count += 1 end end count end ### Quadratic time (bubble sort) ### def bubble_sort(nums) count = 0 # Counter # Outer loop: unsorted range is [0, i] for i in (nums.length - 1).downto(0) # Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for j in 0...i if nums[j] > nums[j + 1] # Swap nums[j] and nums[j + 1] tmp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = tmp count += 3 # Element swap includes 3 unit operations end end end count end ### Exponential time (iterative) ### def exponential(n) count, base = 0, 1 # Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1) (0...n).each do (0...base).each { count += 1 } base *= 2 end # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 count end ### Exponential time (recursive) ### def exp_recur(n) return 1 if n == 1 exp_recur(n - 1) + exp_recur(n - 1) + 1 end ### Logarithmic time (iterative) ### def logarithmic(n) count = 0 while n > 1 n /= 2 count += 1 end count end ### Logarithmic time (recursive) ### def log_recur(n) return 0 unless n > 1 log_recur(n / 2) + 1 end ### Linearithmic time ### def linear_log_recur(n) return 1 unless n > 1 count = linear_log_recur(n / 2) + linear_log_recur(n / 2) (0...n).each { count += 1 } count end ### Factorial time (recursive) ### def factorial_recur(n) return 1 if n == 0 count = 0 # Split from 1 into n (0...n).each { count += factorial_recur(n - 1) } count end ### Driver Code ### if __FILE__ == $0 # You can modify n to run and observe the trend of the number of operations for various complexities n = 8 puts "Input data size n = #{n}" count = constant(n) puts "Constant-time operations count = #{count}" count = linear(n) puts "Linear-time operations count = #{count}" count = array_traversal(Array.new(n, 0)) puts "Linear-time (array traversal) operations count = #{count}" count = quadratic(n) puts "Quadratic-time operations count = #{count}" nums = Array.new(n) { |i| n - i } # [n, n-1, ..., 2, 1] count = bubble_sort(nums) puts "Quadratic-time (bubble sort) operations count = #{count}" count = exponential(n) puts "Exponential-time (iterative) operations count = #{count}" count = exp_recur(n) puts "Exponential-time (recursive) operations count = #{count}" count = logarithmic(n) puts "Logarithmic-time (iterative) operations count = #{count}" count = log_recur(n) puts "Logarithmic-time (recursive) operations count = #{count}" count = linear_log_recur(n) puts "Linearithmic-time (recursive) operations count = #{count}" count = factorial_recur(n) puts "Factorial-time (recursive) operations count = #{count}" end ================================================ FILE: en/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb ================================================ =begin File: worst_best_time_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Generate array with elements: 1, 2, ..., n, shuffled ### def random_numbers(n) # Generate array nums =: 1, 2, 3, ..., n nums = Array.new(n) { |i| i + 1 } # Randomly shuffle array elements nums.shuffle! end ### Find index of number 1 in array nums ### def find_one(nums) for i in 0...nums.length # When element 1 is at the head of the array, best time complexity O(1) is achieved # When element 1 is at the tail of the array, worst time complexity O(n) is achieved return i if nums[i] == 1 end -1 end ### Driver Code ### if __FILE__ == $0 for i in 0...10 n = 100 nums = random_numbers(n) index = find_one(nums) puts "\nArray [ 1, 2, ..., n ] after shuffling = #{nums}" puts "Index of number 1 is #{index}" end end ================================================ FILE: en/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb ================================================ =begin File: binary_search_recur.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Binary search: problem f(i, j) ### def dfs(nums, target, i, j) # If the interval is empty, it means there is no target element, return -1 return -1 if i > j # Calculate the midpoint index m m = (i + j) / 2 if nums[m] < target # Recursion subproblem f(m+1, j) return dfs(nums, target, m + 1, j) elsif nums[m] > target # Recursion subproblem f(i, m-1) return dfs(nums, target, i, m - 1) else # Found the target element, return its index return m end end ### Binary search ### def binary_search(nums, target) n = nums.length # Solve the problem f(0, n-1) dfs(nums, target, 0, n - 1) end ### Driver Code ### if __FILE__ == $0 target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # Binary search (closed interval on both sides) index = binary_search(nums, target) puts "Index of target element 6 is #{index}" end ================================================ FILE: en/codes/ruby/chapter_divide_and_conquer/build_tree.rb ================================================ =begin File: build_tree.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Build binary tree: divide and conquer ### def dfs(preorder, inorder_map, i, l, r) # Terminate when the subtree interval is empty return if r - l < 0 # Initialize the root node root = TreeNode.new(preorder[i]) # Query m to divide the left and right subtrees m = inorder_map[preorder[i]] # Subproblem: build the left subtree root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) # Subproblem: build the right subtree root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) # Return the root node root end ### Build binary tree ### def build_tree(preorder, inorder) # Initialize hash map, storing the mapping from inorder elements to indices inorder_map = {} inorder.each_with_index { |val, i| inorder_map[val] = i } dfs(preorder, inorder_map, 0, 0, inorder.length - 1) end ### Driver Code ### if __FILE__ == $0 preorder = [3, 9, 2, 1, 7] inorder = [9, 3, 1, 2, 7] puts "Pre-order traversal = #{preorder}" puts "In-order traversal = #{inorder}" root = build_tree(preorder, inorder) puts "The constructed binary tree is:" print_tree(root) end ================================================ FILE: en/codes/ruby/chapter_divide_and_conquer/hanota.rb ================================================ =begin File: hanota.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Move one disk ### def move(src, tar) # Take out a disk from the top of src pan = src.pop # Place the disk on top of tar tar << pan end ### Solve Tower of Hanoi f(i) ### def dfs(i, src, buf, tar) # If there is only one disk left in src, move it directly to tar if i == 1 move(src, tar) return end # Subproblem f(i-1): move the top i-1 disks from src to buf using tar dfs(i - 1, src, tar, buf) # Subproblem f(1): move the remaining disk from src to tar move(src, tar) # Subproblem f(i-1): move the top i-1 disks from buf to tar using src dfs(i - 1, buf, src, tar) end ### Solve Tower of Hanoi ### def solve_hanota(_A, _B, _C) n = _A.length # Move the top n disks from A to C using B dfs(n, _A, _B, _C) end ### Driver Code ### if __FILE__ == $0 # The tail of the list is the top of the rod A = [5, 4, 3, 2, 1] B = [] C = [] puts "In initial state:" puts "A = #{A}" puts "B = #{B}" puts "C = #{C}" solve_hanota(A, B, C) puts "After disk movement is complete:" puts "A = #{A}" puts "B = #{B}" puts "C = #{C}" end ================================================ FILE: en/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb ================================================ =begin File: climbing_stairs_backtrack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Backtracking ### def backtrack(choices, state, n, res) # When climbing to the n-th stair, add 1 to the solution count res[0] += 1 if state == n # Traverse all choices for choice in choices # Pruning: not allowed to go beyond the n-th stair next if state + choice > n # Attempt: make choice, update state backtrack(choices, state + choice, n, res) end # Backtrack end ### Climbing stairs: backtracking ### def climbing_stairs_backtrack(n) choices = [1, 2] # Can choose to climb up 1 or 2 stairs state = 0 # Start climbing from the 0-th stair res = [0] # Use res[0] to record the solution count backtrack(choices, state, n, res) res.first end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_backtrack(n) puts "Climbing #{n} stairs has #{res} solutions" end ================================================ FILE: en/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb ================================================ =begin File: climbing_stairs_constraint_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Climbing stairs with constraint: DP ### def climbing_stairs_constraint_dp(n) return 1 if n == 1 || n == 2 # Initialize dp table, used to store solutions to subproblems dp = Array.new(n + 1) { Array.new(3, 0) } # Initial state: preset the solution to the smallest subproblem dp[1][1], dp[1][2] = 1, 0 dp[2][1], dp[2][2] = 0, 1 # State transition: gradually solve larger subproblems from smaller ones for i in 3...(n + 1) dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] end dp[n][1] + dp[n][2] end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_constraint_dp(n) puts "Climbing #{n} stairs has #{res} solutions" end ================================================ FILE: en/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb ================================================ =begin File: climbing_stairs_dfs.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Search ### def dfs(i) # Known dp[1] and dp[2], return them return i if i == 1 || i == 2 # dp[i] = dp[i-1] + dp[i-2] dfs(i - 1) + dfs(i - 2) end ### Climbing stairs: search ### def climbing_stairs_dfs(n) dfs(n) end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dfs(n) puts "Climbing #{n} stairs has #{res} solutions" end ================================================ FILE: en/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb ================================================ =begin File: climbing_stairs_dfs_mem.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Memoization search ### def dfs(i, mem) # Known dp[1] and dp[2], return them return i if i == 1 || i == 2 # If record dp[i] exists, return it directly return mem[i] if mem[i] != -1 # dp[i] = dp[i-1] + dp[i-2] count = dfs(i - 1, mem) + dfs(i - 2, mem) # Record dp[i] mem[i] = count end ### Climbing stairs: memoization search ### def climbing_stairs_dfs_mem(n) # mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record mem = Array.new(n + 1, -1) dfs(n, mem) end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dfs_mem(n) puts "Climbing #{n} stairs has #{res} solutions" end ================================================ FILE: en/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb ================================================ =begin File: climbing_stairs_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Climbing stairs: dynamic programming ### def climbing_stairs_dp(n) return n if n == 1 || n == 2 # Initialize dp table, used to store solutions to subproblems dp = Array.new(n + 1, 0) # Initial state: preset the solution to the smallest subproblem dp[1], dp[2] = 1, 2 # State transition: gradually solve larger subproblems from smaller ones (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] } dp[n] end ### Climbing stairs: space-optimized DP ### def climbing_stairs_dp_comp(n) return n if n == 1 || n == 2 a, b = 1, 2 (3...(n + 1)).each { a, b = b, a + b } b end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dp(n) puts "Climbing #{n} stairs has #{res} solutions" res = climbing_stairs_dp_comp(n) puts "Climbing #{n} stairs has #{res} solutions" end ================================================ FILE: en/codes/ruby/chapter_dynamic_programming/coin_change.rb ================================================ =begin File: coin_change.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Coin change: dynamic programming ### def coin_change_dp(coins, amt) n = coins.length _MAX = amt + 1 # Initialize dp table dp = Array.new(n + 1) { Array.new(amt + 1, 0) } # State transition: first row and first column (1...(amt + 1)).each { |a| dp[0][a] = _MAX } # State transition: rest of the rows and columns for i in 1...(n + 1) for a in 1...(amt + 1) if coins[i - 1] > a # If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a] else # The smaller value between not selecting and selecting coin i dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min end end end dp[n][amt] != _MAX ? dp[n][amt] : -1 end ### Coin change: space-optimized DP ### def coin_change_dp_comp(coins, amt) n = coins.length _MAX = amt + 1 # Initialize dp table dp = Array.new(amt + 1, _MAX) dp[0] = 0 # State transition for i in 1...(n + 1) # Traverse in forward order for a in 1...(amt + 1) if coins[i - 1] > a # If exceeds target amount, don't select coin i dp[a] = dp[a] else # The smaller value between not selecting and selecting coin i dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min end end end dp[amt] != _MAX ? dp[amt] : -1 end ### Driver Code ### if __FILE__ == $0 coins = [1, 2, 5] amt = 4 # Dynamic programming res = coin_change_dp(coins, amt) puts "Minimum coins needed to make target amount is #{res}" # Space-optimized dynamic programming res = coin_change_dp_comp(coins, amt) puts "Minimum coins needed to make target amount is #{res}" end ================================================ FILE: en/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb ================================================ =begin File: coin_change_ii.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Coin change II: dynamic programming ### def coin_change_ii_dp(coins, amt) n = coins.length # Initialize dp table dp = Array.new(n + 1) { Array.new(amt + 1, 0) } # Initialize first column (0...(n + 1)).each { |i| dp[i][0] = 1 } # State transition for i in 1...(n + 1) for a in 1...(amt + 1) if coins[i - 1] > a # If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a] else # Sum of the two options: not selecting and selecting coin i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] end end end dp[n][amt] end ### Coin change II: space-optimized DP ### def coin_change_ii_dp_comp(coins, amt) n = coins.length # Initialize dp table dp = Array.new(amt + 1, 0) dp[0] = 1 # State transition for i in 1...(n + 1) # Traverse in forward order for a in 1...(amt + 1) if coins[i - 1] > a # If exceeds target amount, don't select coin i dp[a] = dp[a] else # Sum of the two options: not selecting and selecting coin i dp[a] = dp[a] + dp[a - coins[i - 1]] end end end dp[amt] end ### Driver Code ### if __FILE__ == $0 coins = [1, 2, 5] amt = 5 # Dynamic programming res = coin_change_ii_dp(coins, amt) puts "Number of coin combinations to make target amount is #{res}" # Space-optimized dynamic programming res = coin_change_ii_dp_comp(coins, amt) puts "Number of coin combinations to make target amount is #{res}" end ================================================ FILE: en/codes/ruby/chapter_dynamic_programming/edit_distance.rb ================================================ =begin File: edit_distance.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Edit distance: brute force search ### def edit_distance_dfs(s, t, i, j) # If both s and t are empty, return 0 return 0 if i == 0 && j == 0 # If s is empty, return length of t return j if i == 0 # If t is empty, return length of s return i if j == 0 # If two characters are equal, skip both characters return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1] # Minimum edit steps = minimum edit steps of insert, delete, replace + 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 minimum edit steps [insert, delete, replace].min + 1 end def edit_distance_dfs_mem(s, t, mem, i, j) # If both s and t are empty, return 0 return 0 if i == 0 && j == 0 # If s is empty, return length of t return j if i == 0 # If t is empty, return length of s return i if j == 0 # If there's a record, return it directly return mem[i][j] if mem[i][j] != -1 # If two characters are equal, skip both characters return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1] # Minimum edit steps = minimum edit steps of insert, delete, replace + 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) # Record and return minimum edit steps mem[i][j] = [insert, delete, replace].min + 1 end ### Edit distance: dynamic programming ### def edit_distance_dp(s, t) n, m = s.length, t.length dp = Array.new(n + 1) { Array.new(m + 1, 0) } # State transition: first row and first column (1...(n + 1)).each { |i| dp[i][0] = i } (1...(m + 1)).each { |j| dp[0][j] = j } # State transition: rest of the rows and columns for i in 1...(n + 1) for j in 1...(m +1) if s[i - 1] == t[j - 1] # If two characters are equal, skip both characters dp[i][j] = dp[i - 1][j - 1] else # Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1 end end end dp[n][m] end ### Edit distance: space-optimized DP ### def edit_distance_dp_comp(s, t) n, m = s.length, t.length dp = Array.new(m + 1, 0) # State transition: first row (1...(m + 1)).each { |j| dp[j] = j } # State transition: rest of the rows for i in 1...(n + 1) # State transition: first column leftup = dp.first # Temporarily store dp[i-1, j-1] dp[0] += 1 # State transition: rest of the columns for j in 1...(m + 1) temp = dp[j] if s[i - 1] == t[j - 1] # If two characters are equal, skip both characters dp[j] = leftup else # Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[j] = [dp[j - 1], dp[j], leftup].min + 1 end leftup = temp # Update for next round's dp[i-1, j-1] end end dp[m] end ### Driver Code ### if __FILE__ == $0 s = 'bag' t = 'pack' n, m = s.length, t.length # Brute-force search res = edit_distance_dfs(s, t, n, m) puts "Changing #{s} to #{t} requires minimum #{res} edits" # Memoization search mem = Array.new(n + 1) { Array.new(m + 1, -1) } res = edit_distance_dfs_mem(s, t, mem, n, m) puts "Changing #{s} to #{t} requires minimum #{res} edits" # Dynamic programming res = edit_distance_dp(s, t) puts "Changing #{s} to #{t} requires minimum #{res} edits" # Space-optimized dynamic programming res = edit_distance_dp_comp(s, t) puts "Changing #{s} to #{t} requires minimum #{res} edits" end ================================================ FILE: en/codes/ruby/chapter_dynamic_programming/knapsack.rb ================================================ =begin File: knapsack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 0-1 knapsack: brute force search ### def knapsack_dfs(wgt, val, i, c) # If all items have been selected or knapsack has no remaining capacity, return value 0 return 0 if i == 0 || c == 0 # If exceeds knapsack capacity, can only choose not to put it in return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c # Calculate the maximum value of not putting in and putting in item i no = knapsack_dfs(wgt, val, i - 1, c) yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] # Return the larger value of the two options [no, yes].max end ### 0-1 knapsack: memoization search ### def knapsack_dfs_mem(wgt, val, mem, i, c) # If all items have been selected or knapsack has no remaining capacity, return value 0 return 0 if i == 0 || c == 0 # If there's a record, return it directly return mem[i][c] if mem[i][c] != -1 # If exceeds knapsack capacity, can only choose not to put it in return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c # Calculate the maximum value of not putting in and putting in item 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] # Record and return the larger value of the two options mem[i][c] = [no, yes].max end ### 0-1 knapsack: dynamic programming ### def knapsack_dp(wgt, val, cap) n = wgt.length # Initialize dp table dp = Array.new(n + 1) { Array.new(cap + 1, 0) } # State transition for i in 1...(n + 1) for c in 1...(cap + 1) if wgt[i - 1] > c # If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c] else # The larger value between not selecting and selecting item i dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max end end end dp[n][cap] end ### 0-1 knapsack: space-optimized DP ### def knapsack_dp_comp(wgt, val, cap) n = wgt.length # Initialize dp table dp = Array.new(cap + 1, 0) # State transition for i in 1...(n + 1) # Traverse in reverse order for c in cap.downto(1) if wgt[i - 1] > c # If exceeds knapsack capacity, don't select item i dp[c] = dp[c] else # The larger value between not selecting and selecting item i dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max end end end dp[cap] end ### Driver Code ### if __FILE__ == $0 wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = wgt.length # Brute-force search res = knapsack_dfs(wgt, val, n, cap) puts "Maximum item value not exceeding knapsack capacity is #{res}" # Memoization search mem = Array.new(n + 1) { Array.new(cap + 1, -1) } res = knapsack_dfs_mem(wgt, val, mem, n, cap) puts "Maximum item value not exceeding knapsack capacity is #{res}" # Dynamic programming res = knapsack_dp(wgt, val, cap) puts "Maximum item value not exceeding knapsack capacity is #{res}" # Space-optimized dynamic programming res = knapsack_dp_comp(wgt, val, cap) puts "Maximum item value not exceeding knapsack capacity is #{res}" end ================================================ FILE: en/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb ================================================ =begin File: min_cost_climbing_stairs_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Minimum cost climbing stairs: DP ### def min_cost_climbing_stairs_dp(cost) n = cost.length - 1 return cost[n] if n == 1 || n == 2 # Initialize dp table, used to store solutions to subproblems dp = Array.new(n + 1, 0) # Initial state: preset the solution to the smallest subproblem dp[1], dp[2] = cost[1], cost[2] # State transition: gradually solve larger subproblems from smaller ones (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] } dp[n] end # Minimum cost climbing stairs: Space-optimized dynamic programming def min_cost_climbing_stairs_dp_comp(cost) n = cost.length - 1 return cost[n] if n == 1 || n == 2 a, b = cost[1], cost[2] (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] } b end ### Driver Code ### if __FILE__ == $0 cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] puts "Input stair cost list is #{cost}" res = min_cost_climbing_stairs_dp(cost) puts "Minimum cost to climb stairs is #{res}" res = min_cost_climbing_stairs_dp_comp(cost) puts "Minimum cost to climb stairs is #{res}" end ================================================ FILE: en/codes/ruby/chapter_dynamic_programming/min_path_sum.rb ================================================ =begin File: min_path_sum.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Minimum path sum: brute force search ### def min_path_sum_dfs(grid, i, j) # If it's the top-left cell, terminate the search return grid[i][j] if i == 0 && j == 0 # If row or column index is out of bounds, return +∞ cost return Float::INFINITY if i < 0 || j < 0 # Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1) up = min_path_sum_dfs(grid, i - 1, j) left = min_path_sum_dfs(grid, i, j - 1) # Return the minimum path cost from top-left to (i, j) [left, up].min + grid[i][j] end ### Minimum path sum: memoization search ### def min_path_sum_dfs_mem(grid, mem, i, j) # If it's the top-left cell, terminate the search return grid[0][0] if i == 0 && j == 0 # If row or column index is out of bounds, return +∞ cost return Float::INFINITY if i < 0 || j < 0 # If there's a record, return it directly return mem[i][j] if mem[i][j] != -1 # Minimum path cost for left and upper cells up = min_path_sum_dfs_mem(grid, mem, i - 1, j) left = min_path_sum_dfs_mem(grid, mem, i, j - 1) # Record and return the minimum path cost from top-left to (i, j) mem[i][j] = [left, up].min + grid[i][j] end ### Minimum path sum: dynamic programming ### def min_path_sum_dp(grid) n, m = grid.length, grid.first.length # Initialize dp table dp = Array.new(n) { Array.new(m, 0) } dp[0][0] = grid[0][0] # State transition: first row (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] } # State transition: first column (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] } # State transition: rest of the rows and columns for i in 1...n for j in 1...m dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j] end end dp[n -1][m -1] end ### Minimum path sum: space-optimized DP ### def min_path_sum_dp_comp(grid) n, m = grid.length, grid.first.length # Initialize dp table dp = Array.new(m, 0) # State transition: first row dp[0] = grid[0][0] (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] } # State transition: rest of the rows for i in 1...n # State transition: first column dp[0] = dp[0] + grid[i][0] # State transition: rest of the columns (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] } end dp[m - 1] end ### Driver Code ### if __FILE__ == $0 grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] n, m = grid.length, grid.first.length # Brute-force search res = min_path_sum_dfs(grid, n - 1, m - 1) puts "Minimum path sum from top-left to bottom-right is #{res}" # Memoization search mem = Array.new(n) { Array.new(m, - 1) } res = min_path_sum_dfs_mem(grid, mem, n - 1, m -1) puts "Minimum path sum from top-left to bottom-right is #{res}" # Dynamic programming res = min_path_sum_dp(grid) puts "Minimum path sum from top-left to bottom-right is #{res}" # Space-optimized dynamic programming res = min_path_sum_dp_comp(grid) puts "Minimum path sum from top-left to bottom-right is #{res}" end ================================================ FILE: en/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb ================================================ =begin File: unbounded_knapsack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Unbounded knapsack: dynamic programming ### def unbounded_knapsack_dp(wgt, val, cap) n = wgt.length # Initialize dp table dp = Array.new(n + 1) { Array.new(cap + 1, 0) } # State transition for i in 1...(n + 1) for c in 1...(cap + 1) if wgt[i - 1] > c # If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c] else # The larger value between not selecting and selecting item i dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max end end end dp[n][cap] end ### Unbounded knapsack: space-optimized DP ### def unbounded_knapsack_dp_comp(wgt, val, cap) n = wgt.length # Initialize dp table dp = Array.new(cap + 1, 0) # State transition for i in 1...(n + 1) # Traverse in forward order for c in 1...(cap + 1) if wgt[i -1] > c # If exceeds knapsack capacity, don't select item i dp[c] = dp[c] else # The larger value between not selecting and selecting item i dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max end end end dp[cap] end ### Driver Code ### if __FILE__ == $0 wgt = [1, 2, 3] val = [5, 11, 15] cap = 4 # Dynamic programming res = unbounded_knapsack_dp(wgt, val, cap) puts "Maximum item value not exceeding knapsack capacity is #{res}" # Space-optimized dynamic programming res = unbounded_knapsack_dp_comp(wgt, val, cap) puts "Maximum item value not exceeding knapsack capacity is #{res}" end ================================================ FILE: en/codes/ruby/chapter_graph/graph_adjacency_list.rb ================================================ =begin File: graph_adjacency_list.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/vertex' ### Undirected graph class based on adjacency list ### class GraphAdjList attr_reader :adj_list ### Constructor ### def initialize(edges) # Adjacency list, key: vertex, value: all adjacent vertices of that vertex @adj_list = {} # Add all vertices and edges for edge in edges add_vertex(edge[0]) add_vertex(edge[1]) add_edge(edge[0], edge[1]) end end ### Get number of vertices ### def size @adj_list.length end ### Add edge ### def add_edge(vet1, vet2) raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) @adj_list[vet1] << vet2 @adj_list[vet2] << vet1 end ### Delete edge ### def remove_edge(vet1, vet2) raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) # Remove edge vet1 - vet2 @adj_list[vet1].delete(vet2) @adj_list[vet2].delete(vet1) end ### Add vertex ### def add_vertex(vet) return if @adj_list.include?(vet) # Add a new linked list in the adjacency list @adj_list[vet] = [] end ### Delete vertex ### def remove_vertex(vet) raise ArgumentError unless @adj_list.include?(vet) # Remove the linked list corresponding to vertex vet in the adjacency list @adj_list.delete(vet) # Traverse the linked lists of other vertices and remove all edges containing vet for vertex in @adj_list @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet) end end ### Print adjacency list ### def __print__ puts 'Adjacency list =' for vertex in @adj_list tmp = @adj_list[vertex.first].map { |v| v.val } puts "#{vertex.first.val}: #{tmp}," end end end ### Driver Code ### if __FILE__ == $0 # Add edge v = vals_to_vets([1, 3, 2, 5, 4]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ] graph = GraphAdjList.new(edges) puts "\nAfter initialization, graph is" graph.__print__ # Add edge # Vertices 1, 2 are v[0], v[2] graph.add_edge(v[0], v[2]) puts "\nAfter adding edge 1-2, graph is" graph.__print__ # Remove edge # Vertices 1, 3 are v[0], v[1] graph.remove_edge(v[0], v[1]) puts "\nAfter removing edge 1-3, graph is" graph.__print__ # Add vertex v5 = Vertex.new(6) graph.add_vertex(v5) puts "\nAfter adding vertex 6, graph is" graph.__print__ # Remove vertex # Vertex 3 is v[1] graph.remove_vertex(v[1]) puts "\nAfter removing vertex 3, graph is" graph.__print__ end ================================================ FILE: en/codes/ruby/chapter_graph/graph_adjacency_matrix.rb ================================================ =begin File: graph_adjacency_matrix.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/print_util' ### Undirected graph class based on adjacency matrix ### class GraphAdjMat def initialize(vertices, edges) ### Constructor ### # Vertex list, where the element represents the "vertex value" and the index represents the "vertex index" @vertices = [] # Adjacency matrix, where the row and column indices correspond to the "vertex index" @adj_mat = [] # Add vertex vertices.each { |val| add_vertex(val) } # Add edge # Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices edges.each { |e| add_edge(e[0], e[1]) } end ### Get number of vertices ### def size @vertices.length end ### Add vertex ### def add_vertex(val) n = size # Add the value of the new vertex to the vertex list @vertices << val # Add a row to the adjacency matrix new_row = Array.new(n, 0) @adj_mat << new_row # Add a column to the adjacency matrix @adj_mat.each { |row| row << 0 } end ### Delete vertex ### def remove_vertex(index) raise IndexError if index >= size # Remove the vertex at index from the vertex list @vertices.delete_at(index) # Remove the row at index from the adjacency matrix @adj_mat.delete_at(index) # Remove the column at index from the adjacency matrix @adj_mat.each { |row| row.delete_at(index) } end ### Add edge ### def add_edge(i, j) # Parameters i, j correspond to the vertices element indices # Handle index out of bounds and equality if i < 0 || j < 0 || i >= size || j >= size || i == j raise IndexError end # In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i) @adj_mat[i][j] = 1 @adj_mat[j][i] = 1 end ### Delete edge ### def remove_edge(i, j) # Parameters i, j correspond to the vertices element indices # Handle index out of bounds and equality if i < 0 || j < 0 || i >= size || j >= size || i == j raise IndexError end @adj_mat[i][j] = 0 @adj_mat[j][i] = 0 end ### Print adjacency matrix ### def __print__ puts "Vertex list = #{@vertices}" puts 'Adjacency matrix =' print_matrix(@adj_mat) end end ### Driver Code ### if __FILE__ == $0 # Add edge # Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices vertices = [1, 3, 2, 5, 4] edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] graph = GraphAdjMat.new(vertices, edges) puts "\nAfter initialization, graph is" graph.__print__ # Add edge # Add vertex graph.add_edge(0, 2) puts "\nAfter adding edge 1-2, graph is" graph.__print__ # Remove edge # Vertices 1, 3 have indices 0, 1 respectively graph.remove_edge(0, 1) puts "\nAfter removing edge 1-3, graph is" graph.__print__ # Add vertex graph.add_vertex(6) puts "\nAfter adding vertex 6, graph is" graph.__print__ # Remove vertex # Vertex 3 has index 1 graph.remove_vertex(1) puts "\nAfter removing vertex 3, graph is" graph.__print__ end ================================================ FILE: en/codes/ruby/chapter_graph/graph_bfs.rb ================================================ =begin File: graph_bfs.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require 'set' require_relative './graph_adjacency_list' require_relative '../utils/vertex' ### Breadth-first traversal ### def graph_bfs(graph, start_vet) # Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex # Vertex traversal sequence res = [] # Hash set for recording vertices that have been visited visited = Set.new([start_vet]) # Queue used to implement BFS que = [start_vet] # Starting from vertex vet, loop until all vertices are visited while que.length > 0 vet = que.shift # Dequeue the front vertex res << vet # Record visited vertex # Traverse all adjacent vertices of this vertex for adj_vet in graph.adj_list[vet] next if visited.include?(adj_vet) # Skip vertices that have been visited que << adj_vet # Only enqueue unvisited vertices visited.add(adj_vet) # Mark this vertex as visited end end # Return vertex traversal sequence res end ### Driver Code ### if __FILE__ == $0 # Add edge v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ] graph = GraphAdjList.new(edges) puts "\nAfter initialization, graph is" graph.__print__ # Breadth-first traversal res = graph_bfs(graph, v.first) puts "\nBreadth-first traversal (BFS) vertex sequence is" p vets_to_vals(res) end ================================================ FILE: en/codes/ruby/chapter_graph/graph_dfs.rb ================================================ =begin File: graph_dfs.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require 'set' require_relative './graph_adjacency_list' require_relative '../utils/vertex' ### Depth-first traversal helper function ### def dfs(graph, visited, res, vet) res << vet # Record visited vertex visited.add(vet) # Mark this vertex as visited # Traverse all adjacent vertices of this vertex for adj_vet in graph.adj_list[vet] next if visited.include?(adj_vet) # Skip vertices that have been visited # Recursively visit adjacent vertices dfs(graph, visited, res, adj_vet) end end ### Depth-first traversal ### def graph_dfs(graph, start_vet) # Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex # Vertex traversal sequence res = [] # Hash set for recording vertices that have been visited visited = Set.new dfs(graph, visited, res, start_vet) res end ### Driver Code ### if __FILE__ == $0 # Add edge v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ] graph = GraphAdjList.new(edges) puts "\nAfter initialization, graph is" graph.__print__ # Depth-first traversal res = graph_dfs(graph, v[0]) puts "\nDepth-first traversal (DFS) vertex sequence is" p vets_to_vals(res) end ================================================ FILE: en/codes/ruby/chapter_greedy/coin_change_greedy.rb ================================================ =begin File: coin_change_greedy.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Coin change: greedy ### def coin_change_greedy(coins, amt) # Assume coins list is sorted i = coins.length - 1 count = 0 # Loop to make greedy choices until no remaining amount while amt > 0 # Find the coin that is less than and closest to the remaining amount while i > 0 && coins[i] > amt i -= 1 end # Choose coins[i] amt -= coins[i] count += 1 end # Return -1 if no solution found amt == 0 ? count : -1 end ### Driver Code ### if __FILE__ == $0 # Greedy algorithm: Can guarantee finding the global optimal solution coins = [1, 5, 10, 20, 50, 100] amt = 186 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "Minimum coins needed to make #{amt} is #{res}" # Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 20, 50] amt = 60 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "Minimum coins needed to make #{amt} is #{res}" puts "Actually minimum needed is 3, i.e., 20 + 20 + 20" # Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 49, 50] amt = 98 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "Minimum coins needed to make #{amt} is #{res}" puts "Actually minimum needed is 2, i.e., 49 + 49" end ================================================ FILE: en/codes/ruby/chapter_greedy/fractional_knapsack.rb ================================================ =begin File: fractional_knapsack.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Item ### class Item attr_accessor :w # Item weight attr_accessor :v # Item value def initialize(w, v) @w = w @v = v end end ### Fractional knapsack: greedy ### def fractional_knapsack(wgt, val, cap) # Create item list with two attributes: weight, value items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) } # Sort by unit value item.v / item.w from high to low items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) } # Loop for greedy selection res = 0 for item in items if item.w <= cap # If remaining capacity is sufficient, put the entire current item into the knapsack res += item.v cap -= item.w else # If remaining capacity is insufficient, put part of the current item into the knapsack res += (item.v.to_f / item.w) * cap # No remaining capacity, so break out of the loop break end end res end ### Driver Code ### if __FILE__ == $0 wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = wgt.length # Greedy algorithm res = fractional_knapsack(wgt, val, cap) puts "Maximum item value not exceeding knapsack capacity is #{res}" end ================================================ FILE: en/codes/ruby/chapter_greedy/max_capacity.rb ================================================ =begin File: max_capacity.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Maximum capacity: greedy ### def max_capacity(ht) # Initialize i, j to be at both ends of the array i, j = 0, ht.length - 1 # Initial max capacity is 0 res = 0 # Loop for greedy selection until the two boards meet while i < j # Update max capacity cap = [ht[i], ht[j]].min * (j - i) res = [res, cap].max # Move the shorter board inward if ht[i] < ht[j] i += 1 else j -= 1 end end res end ### Driver Code ### if __FILE__ == $0 ht = [3, 8, 5, 2, 7, 7, 3, 4] # Greedy algorithm res = max_capacity(ht) puts "Maximum capacity is #{res}" end ================================================ FILE: en/codes/ruby/chapter_greedy/max_product_cutting.rb ================================================ =begin File: max_product_cutting.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Maximum cutting product: greedy ### def max_product_cutting(n) # When n <= 3, must cut out a 1 return 1 * (n - 1) if n <= 3 # Greedily cut out 3, a is the number of 3s, b is the remainder a, b = n / 3, n % 3 # When the remainder is 1, convert a pair of 1 * 3 to 2 * 2 return (3.pow(a - 1) * 2 * 2).to_i if b == 1 # When the remainder is 2, do nothing return (3.pow(a) * 2).to_i if b == 2 # When the remainder is 0, do nothing 3.pow(a).to_i end ### Driver Code ### if __FILE__ == $0 n = 58 # Greedy algorithm res = max_product_cutting(n) puts "Maximum cutting product is #{res}" end ================================================ FILE: en/codes/ruby/chapter_hashing/array_hash_map.rb ================================================ =begin File: array_hash_map.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Key-value pair ### class Pair attr_accessor :key, :val def initialize(key, val) @key = key @val = val end end ### Hash map based on array ### class ArrayHashMap ### Constructor ### def initialize # Initialize array with 100 buckets @buckets = Array.new(100) end ### Hash function ### def hash_func(key) index = key % 100 end ### Query operation ### def get(key) index = hash_func(key) pair = @buckets[index] return if pair.nil? pair.val end ### Add operation ### def put(key, val) pair = Pair.new(key, val) index = hash_func(key) @buckets[index] = pair end ### Delete operation ### def remove(key) index = hash_func(key) # Set to nil to delete @buckets[index] = nil end ### Get all key-value pairs ### def entry_set result = [] @buckets.each { |pair| result << pair unless pair.nil? } result end ### Get all keys ### def key_set result = [] @buckets.each { |pair| result << pair.key unless pair.nil? } result end ### Get all values ### def value_set result = [] @buckets.each { |pair| result << pair.val unless pair.nil? } result end ### Print hash table ### def print @buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? } end end ### Driver Code ### if __FILE__ == $0 # Initialize hash table hmap = ArrayHashMap.new # Add operation # Add key-value pair (key, value) to the hash table hmap.put(12836, "Xiao Ha") hmap.put(15937, "Xiao Luo") hmap.put(16750, "Xiao Suan") hmap.put(13276, "Xiao Fa") hmap.put(10583, "Xiao Ya") puts "\nAfter adding is complete, hash table is\nKey -> Value" hmap.print # Query operation # Input key to hash table, get value name = hmap.get(15937) puts "\nInput student ID 15937, found name #{name}" # Remove operation # Delete key-value pair (key, value) from hash table hmap.remove(10583) puts "\nAfter removing 10583, hash table is\nKey -> Value" hmap.print # Traverse hash table puts "\nTraverse key-value pairs Key->Value" for pair in hmap.entry_set puts "#{pair.key} -> #{pair.val}" end puts "\nTraverse keys separately" for key in hmap.key_set puts key end puts "\nTraverse values only Value" for val in hmap.value_set puts val end end ================================================ FILE: en/codes/ruby/chapter_hashing/built_in_hash.rb ================================================ =begin File: built_in_hash.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### Driver Code ### if __FILE__ == $0 num = 3 hash_num = num.hash puts "Hash value of integer #{num} is #{hash_num}" bol = true hash_bol = bol.hash puts "Hash value of boolean #{bol} is #{hash_bol}" dec = 3.14159 hash_dec = dec.hash puts "Hash value of decimal #{dec} is #{hash_dec}" str = "Hello Algo" hash_str = str.hash puts "Hash value of string #{str} is #{hash_str}" tup = [12836, 'Xiao Ha'] hash_tup = tup.hash puts "Hash value of tuple #{tup} is #{hash_tup}" obj = ListNode.new(0) hash_obj = obj.hash puts "Hash value of object #{obj} is #{hash_obj}" end ================================================ FILE: en/codes/ruby/chapter_hashing/hash_map.rb ================================================ =begin File: hash_map.rb Created Time: 2024-04-14 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/print_util' ### Driver Code ### if __FILE__ == $0 # Initialize hash table hmap = {} # Add operation # Add key-value pair (key, value) to the hash table hmap[12836] = "Xiao Ha" hmap[15937] = "Xiao Luo" hmap[16750] = "Xiao Suan" hmap[13276] = "Xiao Fa" hmap[10583] = "Xiao Ya" puts "\nAfter adding is complete, hash table is\nKey -> Value" print_hash_map(hmap) # Query operation # Input key into hash table to get value name = hmap[15937] puts "\nInput student ID 15937, found name #{name}" # Remove operation # Remove key-value pair (key, value) from hash table hmap.delete(10583) puts "\nAfter removing 10583, hash table is\nKey -> Value" print_hash_map(hmap) # Traverse hash table puts "\nTraverse key-value pairs Key->Value" hmap.entries.each { |key, value| puts "#{key} -> #{value}" } puts "\nTraverse keys only Key" hmap.keys.each { |key| puts key } puts "\nTraverse values only Value" hmap.values.each { |val| puts val } end ================================================ FILE: en/codes/ruby/chapter_hashing/hash_map_chaining.rb ================================================ =begin File: hash_map_chaining.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative './array_hash_map' ### Hash map with chaining ### class HashMapChaining ### Constructor ### def initialize @size = 0 # Number of key-value pairs @capacity = 4 # Hash table capacity @load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion @extend_ratio = 2 # Expansion multiplier @buckets = Array.new(@capacity) { [] } # Bucket array end ### Hash function ### def hash_func(key) key % @capacity end ### Load factor ### def load_factor @size / @capacity end ### Query operation ### def get(key) index = hash_func(key) bucket = @buckets[index] # Traverse bucket, if key is found, return corresponding val for pair in bucket return pair.val if pair.key == key end # Return nil if key not found nil end ### Add operation ### def put(key, val) # When load factor exceeds threshold, perform expansion extend if load_factor > @load_thres index = hash_func(key) bucket = @buckets[index] # Traverse bucket, if specified key is encountered, update corresponding val and return for pair in bucket if pair.key == key pair.val = val return end end # If key does not exist, append key-value pair to the end pair = Pair.new(key, val) bucket << pair @size += 1 end ### Delete operation ### def remove(key) index = hash_func(key) bucket = @buckets[index] # Traverse bucket and remove key-value pair from it for pair in bucket if pair.key == key bucket.delete(pair) @size -= 1 break end end end ### Expand hash table ### def extend # Temporarily store original hash table buckets = @buckets # Initialize expanded new hash table @capacity *= @extend_ratio @buckets = Array.new(@capacity) { [] } @size = 0 # Move key-value pairs from original hash table to new hash table for bucket in buckets for pair in bucket put(pair.key, pair.val) end end end ### Print hash table ### def print for bucket in @buckets res = [] for pair in bucket res << "#{pair.key} -> #{pair.val}" end pp res end end end ### Driver Code ### if __FILE__ == $0 ### Initialize hash table hashmap = HashMapChaining.new # Add operation # Add key-value pair (key, value) to the hash table hashmap.put(12836, "Xiao Ha") hashmap.put(15937, "Xiao Luo") hashmap.put(16750, "Xiao Suan") hashmap.put(13276, "Xiao Fa") hashmap.put(10583, "Xiao Ya") puts "\nAfter adding, hash table is\n[Key1 -> Value1, Key2 -> Value2, ...]" hashmap.print # Query operation # Input key into hash table to get value name = hashmap.get(13276) puts "\nInput student ID 13276, found name #{name}" # Remove operation # Remove key-value pair (key, value) from hash table hashmap.remove(12836) puts "\nAfter deleting 12836, hash table is\n[Key1 -> Value1, Key2 -> Value2, ...]" hashmap.print end ================================================ FILE: en/codes/ruby/chapter_hashing/hash_map_open_addressing.rb ================================================ =begin File: hash_map_open_addressing.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative './array_hash_map' ### Hash map with open addressing ### class HashMapOpenAddressing TOMBSTONE = Pair.new(-1, '-1') # Removal marker ### Constructor ### def initialize @size = 0 # Number of key-value pairs @capacity = 4 # Hash table capacity @load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion @extend_ratio = 2 # Expansion multiplier @buckets = Array.new(@capacity) # Bucket array end ### Hash function ### def hash_func(key) key % @capacity end ### Load factor ### def load_factor @size / @capacity end ### Search bucket index for key ### def find_bucket(key) index = hash_func(key) first_tombstone = -1 # Linear probing, break when encountering an empty bucket while !@buckets[index].nil? # If key is encountered, return the corresponding bucket index if @buckets[index].key == key # If a removal marker was encountered before, move the key-value pair to that index if first_tombstone != -1 @buckets[first_tombstone] = @buckets[index] @buckets[index] = TOMBSTONE return first_tombstone # Return the moved bucket index end return index # Return bucket index end # Record the first removal marker encountered first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE # Calculate bucket index, wrap around to the head if past the tail index = (index + 1) % @capacity end # If key does not exist, return the index for insertion first_tombstone == -1 ? index : first_tombstone end ### Query operation ### def get(key) # Search for bucket index corresponding to key index = find_bucket(key) # If key-value pair is found, return corresponding val return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index]) # Return nil if key-value pair does not exist nil end ### Add operation ### def put(key, val) # When load factor exceeds threshold, perform expansion extend if load_factor > @load_thres # Search for bucket index corresponding to key index = find_bucket(key) # If key-value pair found, overwrite val and return unless [nil, TOMBSTONE].include?(@buckets[index]) @buckets[index].val = val return end # If key-value pair does not exist, add the key-value pair @buckets[index] = Pair.new(key, val) @size += 1 end ### Delete operation ### def remove(key) # Search for bucket index corresponding to key index = find_bucket(key) # If key-value pair is found, overwrite it with removal marker unless [nil, TOMBSTONE].include?(@buckets[index]) @buckets[index] = TOMBSTONE @size -= 1 end end ### Expand hash table ### def extend # Temporarily store the original hash table buckets_tmp = @buckets # Initialize expanded new hash table @capacity *= @extend_ratio @buckets = Array.new(@capacity) @size = 0 # Move key-value pairs from original hash table to new hash table for pair in buckets_tmp put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair) end end ### Print hash table ### def print for pair in @buckets if pair.nil? puts "Nil" elsif pair == TOMBSTONE puts "TOMBSTONE" else puts "#{pair.key} -> #{pair.val}" end end end end ### Driver Code ### if __FILE__ == $0 # Initialize hash table hashmap = HashMapOpenAddressing.new # Add operation # Add key-value pair (key, val) to the hash table hashmap.put(12836, "Xiao Ha") hashmap.put(15937, "Xiao Luo") hashmap.put(16750, "Xiao Suan") hashmap.put(13276, "Xiao Fa") hashmap.put(10583, "Xiao Ya") puts "\nAfter adding is complete, hash table is\nKey -> Value" hashmap.print # Query operation # Input key into hash table to get value val name = hashmap.get(13276) puts "\nInput student ID 13276, found name #{name}" # Remove operation # Remove key-value pair (key, val) from hash table hashmap.remove(16750) puts "\nAfter removing 16750, hash table is\nKey -> Value" hashmap.print end ================================================ FILE: en/codes/ruby/chapter_hashing/simple_hash.rb ================================================ =begin File: simple_hash.rb Created Time: 2024-04-14 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Additive hash ### def add_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash += c.ord } hash % modulus end ### Multiplicative hash ### def mul_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash = 31 * hash + c.ord } hash % modulus end ### XOR hash ### def xor_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash ^= c.ord } hash % modulus end ### Rotational hash ### def rot_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord } hash % modulus end ### Driver Code ### if __FILE__ == $0 key = "Hello Algo" hash = add_hash(key) puts "Additive hash value is #{hash}" hash = mul_hash(key) puts "Multiplicative hash value is #{hash}" hash = xor_hash(key) puts "XOR hash value is #{hash}" hash = rot_hash(key) puts "Rotational hash value is #{hash}" end ================================================ FILE: en/codes/ruby/chapter_heap/my_heap.rb ================================================ =begin File: my_heap.rb Created Time: 2024-04-19 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/print_util' ### Max heap ### class MaxHeap attr_reader :max_heap ### Constructor, build heap from input list ### def initialize(nums) # Add list elements to heap as is @max_heap = nums # Heapify all nodes except leaf nodes parent(size - 1).downto(0) do |i| sift_down(i) end end ### Get left child index ### def left(i) 2 * i + 1 end ### Get right child index ### def right(i) 2 * i + 2 end ### Get parent node index ### def parent(i) (i - 1) / 2 # Floor division end ### Swap elements ### def swap(i, j) @max_heap[i], @max_heap[j] = @max_heap[j], @max_heap[i] end ### Get heap size ### def size @max_heap.length end ### Check if heap is empty ### def is_empty? size == 0 end ### Access heap top element ### def peek @max_heap[0] end ### Push element to heap ### def push(val) # Add node @max_heap << val # Heapify from bottom to top sift_up(size - 1) end ### Heapify from node i, bottom to top ### def sift_up(i) loop do # Get parent node of node i p = parent(i) # When "crossing root node" or "node needs no repair", end heapify break if p < 0 || @max_heap[i] <= @max_heap[p] # Swap two nodes swap(i, p) # Loop upward heapify i = p end end ### Pop element from heap ### def pop # Handle empty case raise IndexError, "Heap is empty" if is_empty? # Delete node swap(0, size - 1) # Remove node val = @max_heap.pop # Return top element sift_down(0) # Return heap top element val end ### Heapify from node i, top to bottom ### def sift_down(i) loop do # If node i is largest or indices l, r are out of bounds, no need to continue heapify, break l, r, ma = left(i), right(i), i ma = l if l < size && @max_heap[l] > @max_heap[ma] ma = r if r < size && @max_heap[r] > @max_heap[ma] # Swap two nodes break if ma == i # Swap two nodes swap(i, ma) # Loop downwards heapification i = ma end end ### Print heap (binary tree) ### def __print__ print_heap(@max_heap) end end ### Driver Code ### if __FILE__ == $0 # Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap max_heap = MaxHeap.new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) puts "\nAfter inputting list and building heap" max_heap.__print__ # Check if heap is empty peek = max_heap.peek puts "\nHeap top element is #{peek}" # Element enters heap val = 7 max_heap.push(val) puts "\nAfter element #{val} pushes to heap" max_heap.__print__ # Time complexity is O(n), not O(nlogn) peek = max_heap.pop puts "\nAfter heap top element #{peek} pops from heap" max_heap.__print__ # Get heap size size = max_heap.size puts "\nHeap size is #{size}" # Check if heap is empty is_empty = max_heap.is_empty? puts "\nIs heap empty #{is_empty}" end ================================================ FILE: en/codes/ruby/chapter_heap/top_k.rb ================================================ =begin File: top_k.rb Created Time: 2024-04-19 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative "./my_heap" ### Push element to heap ### def push_min_heap(heap, val) # Negate element heap.push(-val) end ### Pop element from heap ### def pop_min_heap(heap) # Negate element -heap.pop end ### Access heap top element ### def peek_min_heap(heap) # Negate element -heap.peek end ### Get elements from heap ### def get_min_heap(heap) # Negate all elements in heap heap.max_heap.map { |x| -x } end ### Find largest k elements in array using heap ### def top_k_heap(nums, k) # Python's heapq module implements min heap by default # Note: We negate all heap elements to simulate min heap using max heap max_heap = MaxHeap.new([]) # Enter the first k elements of array into heap for i in 0...k push_min_heap(max_heap, nums[i]) end # Starting from the (k+1)th element, maintain heap length as k for i in k...nums.length # If current element is greater than top element, top element exits heap, current element enters heap if nums[i] > peek_min_heap(max_heap) pop_min_heap(max_heap) push_min_heap(max_heap, nums[i]) end end get_min_heap(max_heap) end ### Driver Code ### if __FILE__ == $0 nums = [1, 7, 6, 3, 2] k = 3 res = top_k_heap(nums, k) puts "The largest #{k} elements are" print_heap(res) end ================================================ FILE: en/codes/ruby/chapter_searching/binary_search.rb ================================================ =begin File: binary_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### Binary search (closed interval) ### def binary_search(nums, target) # Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array i, j = 0, nums.length - 1 # Loop, exit when the search interval is empty (empty when i > j) while i <= j # In theory, Ruby numbers can be infinitely large (limited by memory), no need to consider overflow m = (i + j) / 2 # Calculate the midpoint index m if nums[m] < target i = m + 1 # This means target is in the interval [m+1, j] elsif nums[m] > target j = m - 1 # This means target is in the interval [i, m-1] else return m # Found the target element, return its index end end -1 # Target element not found, return -1 end ### Binary search (left-closed right-open interval) ### def binary_search_lcro(nums, target) # Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1 i, j = 0, nums.length # Loop, exit when the search interval is empty (empty when i = j) while i < j # Calculate the midpoint index m m = (i + j) / 2 if nums[m] < target i = m + 1 # This means target is in the interval [m+1, j) elsif nums[m] > target j = m - 1 # This means target is in the interval [i, m) else return m # Found the target element, return its index end end -1 # Target element not found, return -1 end ### Driver Code ### if __FILE__ == $0 target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # Binary search (closed interval on both sides) index = binary_search(nums, target) puts "Index of target element 6 is #{index}" # Binary search (left-closed right-open interval) index = binary_search_lcro(nums, target) puts "Index of target element 6 is #{index}" end ================================================ FILE: en/codes/ruby/chapter_searching/binary_search_edge.rb ================================================ =begin File: binary_search_edge.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative './binary_search_insertion' ### Binary search leftmost target ### def binary_search_left_edge(nums, target) # Equivalent to finding the insertion point of target i = binary_search_insertion(nums, target) # Target not found, return -1 return -1 if i == nums.length || nums[i] != target i # Found target, return index i end ### Binary search rightmost target ### def binary_search_right_edge(nums, target) # Convert to finding the leftmost target + 1 i = binary_search_insertion(nums, target + 1) # j points to the rightmost target, i points to the first element greater than target j = i - 1 # Target not found, return -1 return -1 if j == -1 || nums[j] != target j # Found target, return index j end ### Driver Code ### if __FILE__ == $0 # Array with duplicate elements nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] puts "\nArray nums = #{nums}" # Binary search left and right boundaries for target in [6, 7] index = binary_search_left_edge(nums, target) puts "Leftmost element #{target} index is #{index}" index = binary_search_right_edge(nums, target) puts "Rightmost element #{target} index is #{index}" end end ================================================ FILE: en/codes/ruby/chapter_searching/binary_search_insertion.rb ================================================ =begin File: binary_search_insertion.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### Binary search insertion point (no duplicates) ### def binary_search_insertion_simple(nums, target) # Initialize closed interval [0, n-1] i, j = 0, nums.length - 1 while i <= j # Calculate the midpoint index m m = (i + j) / 2 if nums[m] < target i = m + 1 # target is in the interval [m+1, j] elsif nums[m] > target j = m - 1 # target is in the interval [i, m-1] else return m # Found target, return insertion point m end end i # Target not found, return insertion point i end ### Binary search insertion point (with duplicates) ### def binary_search_insertion(nums, target) # Initialize closed interval [0, n-1] i, j = 0, nums.length - 1 while i <= j # Calculate the midpoint index m m = (i + j) / 2 if nums[m] < target i = m + 1 # target is in the interval [m+1, j] elsif nums[m] > target j = m - 1 # target is in the interval [i, m-1] else j = m - 1 # The first element less than target is in the interval [i, m-1] end end i # Return insertion point i end ### Driver Code ### if __FILE__ == $0 # Array without duplicate elements nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] puts "\nArray nums = #{nums}" # Binary search for insertion point for target in [6, 9] index = binary_search_insertion_simple(nums, target) puts "Insertion point index for element #{target} is #{index}" end # Array with duplicate elements nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] puts "\nArray nums = #{nums}" # Binary search for insertion point for target in [2, 6, 20] index = binary_search_insertion(nums, target) puts "Insertion point index for element #{target} is #{index}" end end ================================================ FILE: en/codes/ruby/chapter_searching/hashing_search.rb ================================================ =begin File: hashing_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/list_node' ### Hash search (array) ### def hashing_search_array(hmap, target) # Hash table's key: target element, value: index # If this key does not exist in the hash table, return -1 hmap[target] || -1 end ### Hash search (linked list) ### def hashing_search_linkedlist(hmap, target) # Hash table's key: target element, value: node object # If this key does not exist in the hash table, return None hmap[target] || nil end ### Driver Code ### if __FILE__ == $0 target = 3 # Hash search (array) nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] # Initialize hash table map0 = {} for i in 0...nums.length map0[nums[i]] = i # key: element, value: index end index = hashing_search_array(map0, target) puts "Index of target element 3 = #{index}" # Hash search (linked list) head = arr_to_linked_list(nums) # Initialize hash table map1 = {} while head map1[head.val] = head head = head.next end node = hashing_search_linkedlist(map1, target) puts "Node object for target value 3 is #{node}" end ================================================ FILE: en/codes/ruby/chapter_searching/linear_search.rb ================================================ =begin File: linear_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/list_node' ### Linear search (array) ### def linear_search_array(nums, target) # Traverse array for i in 0...nums.length return i if nums[i] == target # Found the target element, return its index end -1 # Target element not found, return -1 end ### Linear search (linked list) ### def linear_search_linkedlist(head, target) # Traverse the linked list while head return head if head.val == target # Found the target node, return it head = head.next end nil # Target node not found, return None end ### Driver Code ### if __FILE__ == $0 target = 3 # Perform linear search in array nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] index = linear_search_array(nums, target) puts "Index of target element 3 = #{index}" # Perform linear search in linked list head = arr_to_linked_list(nums) node = linear_search_linkedlist(head, target) puts "Node object for target value 3 is #{node}" end ================================================ FILE: en/codes/ruby/chapter_searching/two_sum.rb ================================================ =begin File: two_sum.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### Method 1: Brute force enumeration ### def two_sum_brute_force(nums, target) # Two nested loops, time complexity is O(n^2) for i in 0...(nums.length - 1) for j in (i + 1)...nums.length return [i, j] if nums[i] + nums[j] == target end end [] end ### Method 2: Auxiliary hash table ### def two_sum_hash_table(nums, target) # Auxiliary hash table, space complexity is O(n) dic = {} # Single loop, time complexity is O(n) for i in 0...nums.length return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i]) dic[nums[i]] = i end [] end ### Driver Code ### if __FILE__ == $0 # ======= Test Case ======= nums = [2, 7, 11, 15] target = 13 # ====== Driver Code ====== # Method 1 res = two_sum_brute_force(nums, target) puts "Method 1 res = #{res}" # Method 2 res = two_sum_hash_table(nums, target) puts "Method 2 res = #{res}" end ================================================ FILE: en/codes/ruby/chapter_sorting/bubble_sort.rb ================================================ =begin File: bubble_sort.rb Created Time: 2024-05-02 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Bubble sort ### def bubble_sort(nums) n = nums.length # Outer loop: unsorted range is [0, i] for i in (n - 1).downto(1) # Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for j in 0...i if nums[j] > nums[j + 1] # Swap nums[j] and nums[j + 1] nums[j], nums[j + 1] = nums[j + 1], nums[j] end end end end ### Bubble sort (flag optimization) ### def bubble_sort_with_flag(nums) n = nums.length # Outer loop: unsorted range is [0, i] for i in (n - 1).downto(1) flag = false # Initialize flag # Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for j in 0...i if nums[j] > nums[j + 1] # Swap nums[j] and nums[j + 1] nums[j], nums[j + 1] = nums[j + 1], nums[j] flag = true # Record element swap end end break unless flag # No elements were swapped in this round of "bubbling", exit directly end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] bubble_sort(nums) puts "After bubble sort, nums = #{nums}" nums1 = [4, 1, 3, 1, 5, 2] bubble_sort_with_flag(nums1) puts "After bubble sort, nums = #{nums1}" end ================================================ FILE: en/codes/ruby/chapter_sorting/bucket_sort.rb ================================================ =begin File: bucket_sort.rb Created Time: 2024-04-17 Author: Martin Xu (martin.xus@gmail.com) =end ### Bucket sort ### def bucket_sort(nums) # Initialize k = n/2 buckets, expected to allocate 2 elements per bucket k = nums.length / 2 buckets = Array.new(k) { [] } # 1. Distribute array elements into various buckets nums.each do |num| # Input data range is [0, 1), use num * k to map to index range [0, k-1] i = (num * k).to_i # Add num to bucket i buckets[i] << num end # 2. Sort each bucket buckets.each do |bucket| # Use built-in sorting function, can also replace with other sorting algorithms bucket.sort! end # 3. Traverse buckets to merge results i = 0 buckets.each do |bucket| bucket.each do |num| nums[i] = num i += 1 end end end ### Driver Code ### if __FILE__ == $0 # Assume input data is floating point, interval [0, 1) nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] bucket_sort(nums) puts "After bucket sort, nums = #{nums}" end ================================================ FILE: en/codes/ruby/chapter_sorting/counting_sort.rb ================================================ =begin File: counting_sort.rb Created Time: 2024-05-02 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Counting sort ### def counting_sort_naive(nums) # Simple implementation, cannot be used for sorting objects # 1. Count the maximum element m in the array m = 0 nums.each { |num| m = [m, num].max } # 2. Count the occurrence of each number # counter[num] represents the occurrence of num counter = Array.new(m + 1, 0) nums.each { |num| counter[num] += 1 } # 3. Traverse counter, filling each element back into the original array nums i = 0 for num in 0...(m + 1) (0...counter[num]).each do nums[i] = num i += 1 end end end ### Counting sort ### def counting_sort(nums) # Complete implementation, can sort objects and is a stable sort # 1. Count the maximum element m in the array m = nums.max # 2. Count the occurrence of each number # counter[num] represents the occurrence of num counter = Array.new(m + 1, 0) nums.each { |num| counter[num] += 1 } # 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" # counter[num]-1 is the last index where num appears in res (0...m).each { |i| counter[i + 1] += counter[i] } # 4. Traverse nums in reverse, fill elements into result array res # Initialize the array res to record results n = nums.length res = Array.new(n, 0) (n - 1).downto(0).each do |i| num = nums[i] res[counter[num] - 1] = num # Place num at the corresponding index counter[num] -= 1 # Decrement the prefix sum by 1, getting the next index to place num end # Use result array res to overwrite the original array nums (0...n).each { |i| nums[i] = res[i] } end ### Driver Code ### if __FILE__ == $0 nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort_naive(nums) puts "After counting sort (cannot sort objects), nums = #{nums}" nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort(nums1) puts "After counting sort, nums1 = #{nums1}" end ================================================ FILE: en/codes/ruby/chapter_sorting/heap_sort.rb ================================================ =begin File: heap_sort.rb Created Time: 2024-04-10 Author: junminhong (junminhong1110@gmail.com) =end ### Heap length is n, heapify from node i, top to bottom ### def sift_down(nums, n, i) while true # If node i is largest or indices l, r are out of bounds, no need to continue heapify, break l = 2 * i + 1 r = 2 * i + 2 ma = i ma = l if l < n && nums[l] > nums[ma] ma = r if r < n && nums[r] > nums[ma] # Swap two nodes break if ma == i # Swap two nodes nums[i], nums[ma] = nums[ma], nums[i] # Loop downwards heapification i = ma end end ### Heap sort ### def heap_sort(nums) # Build heap operation: heapify all nodes except leaves (nums.length / 2 - 1).downto(0) do |i| sift_down(nums, nums.length, i) end # Extract the largest element from the heap and repeat for n-1 rounds (nums.length - 1).downto(1) do |i| # Delete node nums[0], nums[i] = nums[i], nums[0] # Start heapifying the root node, from top to bottom sift_down(nums, i, 0) end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] heap_sort(nums) puts "After heap sort, nums = #{nums.inspect}" end ================================================ FILE: en/codes/ruby/chapter_sorting/insertion_sort.rb ================================================ =begin File: insertion_sort.rb Created Time: 2024-04-02 Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Insertion sort ### def insertion_sort(nums) n = nums.length # Outer loop: sorted interval is [0, i-1] for i in 1...n base = nums[i] j = i - 1 # Inner loop: insert base into the correct position within the sorted interval [0, i-1] while j >= 0 && nums[j] > base nums[j + 1] = nums[j] # Move nums[j] to the right by one position j -= 1 end nums[j + 1] = base # Assign base to the correct position end end ### Driver Code ### nums = [4, 1, 3, 1, 5, 2] insertion_sort(nums) puts "After insertion sort, nums = #{nums}" ================================================ FILE: en/codes/ruby/chapter_sorting/merge_sort.rb ================================================ =begin File: merge_sort.rb Created Time: 2024-04-10 Author: junminhong (junminhong1110@gmail.com) =end ### Merge left and right subarrays ### def merge(nums, left, mid, right) # Left subarray interval is [left, mid], right subarray interval is [mid+1, right] # Create temporary array tmp to store merged result tmp = Array.new(right - left + 1, 0) # Initialize the start indices of the left and right subarrays i, j, k = left, mid + 1, 0 # While both subarrays still have elements, compare and copy the smaller element into the temporary array while i <= mid && j <= right if nums[i] <= nums[j] tmp[k] = nums[i] i += 1 else tmp[k] = nums[j] j += 1 end k += 1 end # Copy the remaining elements of the left and right subarrays into the temporary array while i <= mid tmp[k] = nums[i] i += 1 k += 1 end while j <= right tmp[k] = nums[j] j += 1 k += 1 end # Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval (0...tmp.length).each do |k| nums[left + k] = tmp[k] end end ### Merge sort ### def merge_sort(nums, left, right) # Termination condition # Terminate recursion when subarray length is 1 return if left >= right # Divide and conquer stage mid = left + (right - left) / 2 # Calculate midpoint merge_sort(nums, left, mid) # Recursively process the left subarray merge_sort(nums, mid + 1, right) # Recursively process the right subarray # Merge stage merge(nums, left, mid, right) end ### Driver Code ### if __FILE__ == $0 nums = [7, 3, 2, 6, 0, 1, 5, 4] merge_sort(nums, 0, nums.length - 1) puts "After merge sort, nums = #{nums.inspect}" end ================================================ FILE: en/codes/ruby/chapter_sorting/quick_sort.rb ================================================ =begin File: quick_sort.rb Created Time: 2024-04-01 Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Quick sort class ### class QuickSort class << self ### Sentinel partition ### def partition(nums, left, right) # Use nums[left] as the pivot i, j = left, right while i < j while i < j && nums[j] >= nums[left] j -= 1 # Search from right to left for the first element smaller than the pivot end while i < j && nums[i] <= nums[left] i += 1 # Search from left to right for the first element greater than the pivot end # Swap elements nums[i], nums[j] = nums[j], nums[i] end # Swap the pivot to the boundary between the two subarrays nums[i], nums[left] = nums[left], nums[i] i # Return the index of the pivot end ### Quick sort class ### def quick_sort(nums, left, right) # Recurse when subarray length is not 1 if left < right # Sentinel partition pivot = partition(nums, left, right) # Recursively process the left subarray and right subarray quick_sort(nums, left, pivot - 1) quick_sort(nums, pivot + 1, right) end nums end end end ### Quick sort class (median optimization) ### class QuickSortMedian class << self ### Select median of three candidate elements ### def median_three(nums, left, mid, right) # Select the median of three candidate elements _l, _m, _r = nums[left], nums[mid], nums[right] # m is between l and r return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l) # l is between m and r return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m) return right end ### Sentinel partition (median of three) ### def partition(nums, left, right) ### Use nums[left] as pivot med = median_three(nums, left, (left + right) / 2, right) # Swap median to leftmost position of array nums[left], nums[med] = nums[med], nums[left] i, j = left, right while i < j while i < j && nums[j] >= nums[left] j -= 1 # Search from right to left for the first element smaller than the pivot end while i < j && nums[i] <= nums[left] i += 1 # Search from left to right for the first element greater than the pivot end # Swap elements nums[i], nums[j] = nums[j], nums[i] end # Swap the pivot to the boundary between the two subarrays nums[i], nums[left] = nums[left], nums[i] i # Return the index of the pivot end ### Quick sort ### def quick_sort(nums, left, right) # Recurse when subarray length is not 1 if left < right # Sentinel partition pivot = partition(nums, left, right) # Recursively process the left subarray and right subarray quick_sort(nums, left, pivot - 1) quick_sort(nums, pivot + 1, right) end nums end end end ### Quick sort class (recursion depth optimization) ### class QuickSortTailCall class << self ### Sentinel partition ### def partition(nums, left, right) # Use nums[left] as pivot i = left j = right while i < j while i < j && nums[j] >= nums[left] j -= 1 # Search from right to left for the first element smaller than the pivot end while i < j && nums[i] <= nums[left] i += 1 # Search from left to right for the first element greater than the pivot end # Swap elements nums[i], nums[j] = nums[j], nums[i] end # Swap the pivot to the boundary between the two subarrays nums[i], nums[left] = nums[left], nums[i] i # Return the index of the pivot end ### Quick sort (recursion depth optimization) ### def quick_sort(nums, left, right) # Recurse when subarray length is not 1 while left < right # Sentinel partition pivot = partition(nums, left, right) # Perform quick sort on the shorter of the two subarrays if pivot - left < right - pivot quick_sort(nums, left, pivot - 1) left = pivot + 1 # Remaining unsorted interval is [pivot + 1, right] else quick_sort(nums, pivot + 1, right) right = pivot - 1 # Remaining unsorted interval is [left, pivot - 1] end end end end end ### Driver Code ### if __FILE__ == $0 # Quick sort nums = [2, 4, 1, 0, 3, 5] QuickSort.quick_sort(nums, 0, nums.length - 1) puts "After quick sort, nums = #{nums}" # Quick sort (recursion depth optimization) nums1 = [2, 4, 1, 0, 3, 5] QuickSortMedian.quick_sort(nums1, 0, nums1.length - 1) puts "After quick sort (median pivot optimization), nums1 = #{nums1}" # Quick sort (recursion depth optimization) nums2 = [2, 4, 1, 0, 3, 5] QuickSortTailCall.quick_sort(nums2, 0, nums2.length - 1) puts "After quick sort (recursion depth optimization), nums2 = #{nums2}" end ================================================ FILE: en/codes/ruby/chapter_sorting/radix_sort.rb ================================================ =begin File: radix_sort.rb Created Time: 2024-05-03 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Get k-th digit of element num, where exp = 10^(k-1) ### def digit(num, exp) # Passing exp instead of k avoids expensive exponentiation calculations (num / exp) % 10 end ### Counting sort (sort by k-th digit of nums) ### def counting_sort_digit(nums, exp) # Decimal digit range is 0~9, therefore need a bucket array of length 10 counter = Array.new(10, 0) n = nums.length # Count the occurrence of digits 0~9 for i in 0...n d = digit(nums[i], exp) # Get the k-th digit of nums[i], noted as d counter[d] += 1 # Count the occurrence of digit d end # Calculate prefix sum, converting "occurrence count" into "array index" (1...10).each { |i| counter[i] += counter[i - 1] } # Traverse in reverse, based on bucket statistics, place each element into res res = Array.new(n, 0) for i in (n - 1).downto(0) d = digit(nums[i], exp) j = counter[d] - 1 # Get the index j for d in the array res[j] = nums[i] # Place the current element at index j counter[d] -= 1 # Decrease the count of d by 1 end # Use result to overwrite the original array nums (0...n).each { |i| nums[i] = res[i] } end ### Radix sort ### def radix_sort(nums) # Get the maximum element of the array, used to determine the maximum number of digits m = nums.max # Traverse from the lowest to the highest digit exp = 1 while exp <= m # Perform counting sort on the k-th digit of array elements # k = 1 -> exp = 1 # k = 2 -> exp = 10 # i.e., exp = 10^(k-1) counting_sort_digit(nums, exp) exp *= 10 end end ### Driver Code ### if __FILE__ == $0 # Radix sort nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ] radix_sort(nums) puts "After radix sort, nums = #{nums}" end ================================================ FILE: en/codes/ruby/chapter_sorting/selection_sort.rb ================================================ =begin File: selection_sort.rb Created Time: 2024-05-03 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Selection sort ### def selection_sort(nums) n = nums.length # Outer loop: unsorted interval is [i, n-1] for i in 0...(n - 1) # Inner loop: find the smallest element within the unsorted interval k = i for j in (i + 1)...n if nums[j] < nums[k] k = j # Record the index of the smallest element end end # Swap the smallest element with the first element of the unsorted interval nums[i], nums[k] = nums[k], nums[i] end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] selection_sort(nums) puts "After selection sort, nums = #{nums}" end ================================================ FILE: en/codes/ruby/chapter_stack_and_queue/array_deque.rb ================================================ =begin File: array_deque.rb Created Time: 2024-04-05 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Deque based on circular array ### class ArrayDeque ### Get deque length ### attr_reader :size ### Constructor ### def initialize(capacity) @nums = Array.new(capacity, 0) @front = 0 @size = 0 end ### Get deque capacity ### def capacity @nums.length end ### Check if deque is empty ### def is_empty? size.zero? end ### Enqueue at front ### def push_first(num) if size == capacity puts 'Double-ended queue is full' return end # Use modulo operation to wrap front around to the tail after passing the head of the array # Add num to the front of the queue @front = index(@front - 1) # Add num to front of queue @nums[@front] = num @size += 1 end ### Enqueue at rear ### def push_last(num) if size == capacity puts 'Double-ended queue is full' return end # Use modulo operation to wrap rear around to the head after passing the tail of the array rear = index(@front + size) # Front pointer moves one position backward @nums[rear] = num @size += 1 end ### Dequeue from front ### def pop_first num = peek_first # Move front pointer backward by one position @front = index(@front + 1) @size -= 1 num end ### Dequeue from rear ### def pop_last num = peek_last @size -= 1 num end ### Access front element ### def peek_first raise IndexError, 'Deque is empty' if is_empty? @nums[@front] end ### Access rear element ### def peek_last raise IndexError, 'Deque is empty' if is_empty? # Initialize double-ended queue last = index(@front + size - 1) @nums[last] end ### Return array for printing ### def to_array # Elements enqueue res = [] for i in 0...size res << @nums[index(@front + i)] end res end private ### Calculate circular array index ### def index(i) # Use modulo operation to wrap the array head and tail together # When i passes the tail of the array, return to the head # When i passes the head of the array, return to the tail (i + capacity) % capacity end end ### Driver Code ### if __FILE__ == $0 # Get the length of the double-ended queue deque = ArrayDeque.new(10) deque.push_last(3) deque.push_last(2) deque.push_last(5) puts "Deque deque = #{deque.to_array}" # Update element peek_first = deque.peek_first puts "Front element peek_first = #{peek_first}" peek_last = deque.peek_last puts "Rear element peek_last = #{peek_last}" # Elements enqueue deque.push_last(4) puts "After element 4 enqueues at rear, deque = #{deque.to_array}" deque.push_first(1) puts "After element 1 enqueues at rear, deque = #{deque.to_array}" # Element dequeue pop_last = deque.pop_last puts "Dequeue rear element = #{pop_last}, after dequeue deque = #{deque.to_array}" pop_first = deque.pop_first puts "Dequeue front element = #{pop_first}, after dequeue deque = #{deque.to_array}" # Get the length of the double-ended queue size = deque.size puts "Deque length size = #{size}" # Check if the double-ended queue is empty is_empty = deque.is_empty? puts "Is deque empty = #{is_empty}" end ================================================ FILE: en/codes/ruby/chapter_stack_and_queue/array_queue.rb ================================================ =begin File: array_queue.rb Created Time: 2024-04-05 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Queue based on circular array ### class ArrayQueue ### Get queue length ### attr_reader :size ### Constructor ### def initialize(size) @nums = Array.new(size, 0) # Array for storing queue elements @front = 0 # Front pointer, points to the front of the queue element @size = 0 # Queue length end ### Get queue capacity ### def capacity @nums.length end ### Check if queue is empty ### def is_empty? size.zero? end ### Enqueue ### def push(num) raise IndexError, 'Queue is full' if size == capacity # Use modulo operation to wrap rear around to the head after passing the tail of the array # Add num to the rear of the queue rear = (@front + size) % capacity # Front pointer moves one position backward @nums[rear] = num @size += 1 end ### Dequeue ### def pop num = peek # Move front pointer backward by one position, if it passes the tail, return to array head @front = (@front + 1) % capacity @size -= 1 num end ### Access front element ### def peek raise IndexError, 'Queue is empty' if is_empty? @nums[@front] end ### Return list for printing ### def to_array res = Array.new(size, 0) j = @front for i in 0...size res[i] = @nums[j % capacity] j += 1 end res end end ### Driver Code ### if __FILE__ == $0 # Access front of the queue element queue = ArrayQueue.new(10) # Elements enqueue queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "Queue queue = #{queue.to_array}" # Return list for printing peek = queue.peek puts "Front element peek = #{peek}" # Element dequeue pop = queue.pop puts "Dequeue element pop = #{pop}" puts "After dequeue, queue = #{queue.to_array}" # Get the length of the queue size = queue.size puts "Queue length size = #{size}" # Check if the queue is empty is_empty = queue.is_empty? puts "Is queue empty = #{is_empty}" # Test circular array for i in 0...10 queue.push(i) queue.pop puts "After round #{i} of enqueue + dequeue, queue = #{queue.to_array}" end end ================================================ FILE: en/codes/ruby/chapter_stack_and_queue/array_stack.rb ================================================ =begin File: array_stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Stack based on array ### class ArrayStack ### Constructor ### def initialize @stack = [] end ### Get stack length ### def size @stack.length end ### Check if stack is empty ### def is_empty? @stack.empty? end ### Push ### def push(item) @stack << item end ### Pop ### def pop raise IndexError, 'Stack is empty' if is_empty? @stack.pop end ### Access top element ### def peek raise IndexError, 'Stack is empty' if is_empty? @stack.last end ### Return list for printing ### def to_array @stack end end ### Driver Code ### if __FILE__ == $0 # Access top of the stack element stack = ArrayStack.new # Elements push onto stack stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) puts "Stack stack = #{stack.to_array}" # Return list for printing peek = stack.peek puts "Top element peek = #{peek}" # Element pop from stack pop = stack.pop puts "Pop element pop = #{pop}" puts "After pop, stack = #{stack.to_array}" # Get the length of the stack size = stack.size puts "Stack length size = #{size}" # Check if empty is_empty = stack.is_empty? puts "Is stack empty = #{is_empty}" end ================================================ FILE: en/codes/ruby/chapter_stack_and_queue/deque.rb ================================================ =begin File: deque.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # Get the length of the double-ended queue # Ruby has no built-in deque, can only use Array as deque deque = [] # Element enqueues deque << 2 deque << 5 deque << 4 # Note: due to array, Array#unshift method has O(n) time complexity deque.unshift(3) deque.unshift(1) puts "Deque deque = #{deque}" # Update element peek_first = deque.first puts "Front element peek_first = #{peek_first}" peek_last = deque.last puts "Rear element peek_last = #{peek_last}" # Element dequeue # Note: due to array, Array#shift method has O(n) time complexity pop_front = deque.shift puts "Dequeue front element pop_front = #{pop_front}, after dequeue deque = #{deque}" pop_back = deque.pop puts "Dequeue rear element pop_back = #{pop_back}, after dequeue deque = #{deque}" # Get the length of the double-ended queue size = deque.length puts "Deque length size = #{size}" # Check if the double-ended queue is empty is_empty = size.zero? puts "Is deque empty = #{is_empty}" end ================================================ FILE: en/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb ================================================ =begin File: linkedlist_deque.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Doubly linked list node class ListNode attr_accessor :val attr_accessor :next # Successor node reference attr_accessor :prev # Predecessor node reference ### Constructor ### def initialize(val) @val = val end end ### Deque based on doubly linked list ### class LinkedListDeque ### Get deque length ### attr_reader :size ### Constructor ### def initialize @front = nil # Head node front @rear = nil # Tail node rear @size = 0 # Length of the double-ended queue end ### Check if deque is empty ### def is_empty? size.zero? end ### Enqueue operation ### def push(num, is_front) node = ListNode.new(num) # If list is empty, set both front and rear to node if is_empty? @front = @rear = node # Front of the queue enqueue operation elsif is_front # Add node to the head of the linked list @front.prev = node node.next = @front @front = node # Update head node # Rear of the queue enqueue operation else # Add node to the tail of the linked list @rear.next = node node.prev = @rear @rear = node # Update tail node end @size += 1 # Update queue length end ### Enqueue at front ### def push_first(num) push(num, true) end ### Enqueue at rear ### def push_last(num) push(num, false) end ### Dequeue operation ### def pop(is_front) raise IndexError, 'Deque is empty' if is_empty? # Temporarily store head node value if is_front val = @front.val # Delete head node # Delete head node fnext = @front.next unless fnext.nil? fnext.prev = nil @front.next = nil end @front = fnext # Update head node # Temporarily store tail node value else val = @rear.val # Delete tail node # Update tail node rprev = @rear.prev unless rprev.nil? rprev.next = nil @rear.prev = nil end @rear = rprev # Update tail node end @size -= 1 # Update queue length val end ### Dequeue from front ### def pop_first pop(true) end ### Dequeue from front ### def pop_last pop(false) end ### Access front element ### def peek_first raise IndexError, 'Deque is empty' if is_empty? @front.val end ### Access rear element ### def peek_last raise IndexError, 'Deque is empty' if is_empty? @rear.val end ### Return array for printing ### def to_array node = @front res = Array.new(size, 0) for i in 0...size res[i] = node.val node = node.next end res end end ### Driver Code ### if __FILE__ == $0 # Get the length of the double-ended queue deque = LinkedListDeque.new deque.push_last(3) deque.push_last(2) deque.push_last(5) puts "Deque deque = #{deque.to_array}" # Update element peek_first = deque.peek_first puts "Front element peek_first = #{peek_first}" peek_last = deque.peek_last puts "Rear element peek_last = #{peek_last}" # Elements enqueue deque.push_last(4) puts "After element 4 enqueues at rear, deque = #{deque.to_array}" deque.push_first(1) puts "After element 1 enqueues at front, deque = #{deque.to_array}" # Element dequeue pop_last = deque.pop_last puts "Dequeue rear element = #{pop_last}, after dequeue deque = #{deque.to_array}" pop_first = deque.pop_first puts "Dequeue front element = #{pop_first}, after dequeue deque = #{deque.to_array}" # Get the length of the double-ended queue size = deque.size puts "Deque length size = #{size}" # Check if the double-ended queue is empty is_empty = deque.is_empty? puts "Is deque empty = #{is_empty}" end ================================================ FILE: en/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb ================================================ =begin File: linkedlist_queue.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### Queue based on linked list ### class LinkedListQueue ### Get queue length ### attr_reader :size ### Constructor ### def initialize @front = nil # Head node front @rear = nil # Tail node rear @size = 0 end ### Check if queue is empty ### def is_empty? @front.nil? end ### Enqueue ### def push(num) # Add num after the tail node node = ListNode.new(num) # If queue is empty, set both front and rear to this node if @front.nil? @front = node @rear = node # If queue is not empty, add this node after rear else @rear.next = node @rear = node end @size += 1 end ### Dequeue ### def pop num = peek # Delete head node @front = @front.next @size -= 1 num end ### Access front element ### def peek raise IndexError, 'Queue is empty' if is_empty? @front.val end ### Convert linked list to Array and return ### def to_array queue = [] temp = @front while temp queue << temp.val temp = temp.next end queue end end ### Driver Code ### if __FILE__ == $0 # Access front of the queue element queue = LinkedListQueue.new # Element enqueues queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "Queue queue = #{queue.to_array}" # Return list for printing peek = queue.peek puts "Front element = #{peek}" # Element dequeue pop_front = queue.pop puts "Dequeue element pop = #{pop_front}" puts "After dequeue, queue = #{queue.to_array}" # Get the length of the queue size = queue.size puts "Queue length size = #{size}" # Check if the queue is empty is_empty = queue.is_empty? puts "Is queue empty = #{is_empty}" end ================================================ FILE: en/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb ================================================ =begin File: linkedlist_stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### Stack based on linked list ### class LinkedListStack attr_reader :size ### Constructor ### def initialize @size = 0 end ### Check if stack is empty ### def is_empty? @peek.nil? end ### Push ### def push(val) node = ListNode.new(val) node.next = @peek @peek = node @size += 1 end ### Pop ### def pop num = peek @peek = @peek.next @size -= 1 num end ### Access top element ### def peek raise IndexError, 'Stack is empty' if is_empty? @peek.val end ### Convert linked list to Array and return ### def to_array arr = [] node = @peek while node arr << node.val node = node.next end arr.reverse end end ### Driver Code ### if __FILE__ == $0 # Access top of the stack element stack = LinkedListStack.new # Elements push onto stack stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) puts "Stack stack = #{stack.to_array}" # Return list for printing peek = stack.peek puts "Top element peek = #{peek}" # Element pop from stack pop = stack.pop puts "Pop element pop = #{pop}" puts "After pop, stack = #{stack.to_array}" # Get the length of the stack size = stack.size puts "Stack length size = #{size}" # Check if empty is_empty = stack.is_empty? puts "Is stack empty = #{is_empty}" end ================================================ FILE: en/codes/ruby/chapter_stack_and_queue/queue.rb ================================================ =begin File: queue.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # Access front of the queue element # Ruby's built-in queue (Thread::Queue) has no peek and traversal methods, can use Array as queue queue = [] # Elements enqueue queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "Queue queue = #{queue}" # Access queue elements peek = queue.first puts "Front element peek = #{peek}" # Element dequeue # Note: due to array, Array#shift method has O(n) time complexity pop = queue.shift puts "Dequeue element pop = #{pop}" puts "After dequeue, queue = #{queue}" # Get the length of the queue size = queue.length puts "Queue length size = #{size}" # Check if the queue is empty is_empty = queue.empty? puts "Is queue empty = #{is_empty}" end ================================================ FILE: en/codes/ruby/chapter_stack_and_queue/stack.rb ================================================ =begin File: stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # Access top of the stack element # Ruby has no built-in stack class, can use Array as stack stack = [] # Elements push onto stack stack << 1 stack << 3 stack << 2 stack << 5 stack << 4 puts "Stack stack = #{stack}" # Return list for printing peek = stack.last puts "Top element peek = #{peek}" # Element pop from stack pop = stack.pop puts "Pop element pop = #{pop}" puts "After pop, stack = #{stack}" # Get the length of the stack size = stack.length puts "Stack length size = #{size}" # Check if empty is_empty = stack.empty? puts "Is stack empty = #{is_empty}" end ================================================ FILE: en/codes/ruby/chapter_tree/array_binary_tree.rb ================================================ =begin File: array_binary_tree.rb Created Time: 2024-04-17 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Array representation of binary tree class ### class ArrayBinaryTree ### Constructor ### def initialize(arr) @tree = arr.to_a end ### List capacity ### def size @tree.length end ### Get value of node at index i ### def val(i) # Return nil if index out of bounds, representing empty position return if i < 0 || i >= size @tree[i] end ### Get left child index of node at index i ### def left(i) 2 * i + 1 end ### Get right child index of node at index i ### def right(i) 2 * i + 2 end ### Get parent node index of node at index i ### def parent(i) (i - 1) / 2 end ### Level-order traversal ### def level_order @res = [] # Traverse array directly for i in 0...size @res << val(i) unless val(i).nil? end @res end ### Depth-first traversal ### def dfs(i, order) return if val(i).nil? # Preorder traversal @res << val(i) if order == :pre dfs(left(i), order) # Inorder traversal @res << val(i) if order == :in dfs(right(i), order) # Postorder traversal @res << val(i) if order == :post end ### Pre-order traversal ### def pre_order @res = [] dfs(0, :pre) @res end ### In-order traversal ### def in_order @res = [] dfs(0, :in) @res end ### Post-order traversal ### def post_order @res = [] dfs(0, :post) @res end end ### Driver Code ### if __FILE__ == $0 # Initialize binary tree # Here we use a function to generate a binary tree directly from an array arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] root = arr_to_tree(arr) puts "\nInitialize binary tree\n\n" puts 'Array representation of binary tree:' pp arr puts 'Linked list representation of binary tree:' print_tree(root) # Binary tree class represented by array abt = ArrayBinaryTree.new(arr) # Access node i = 1 l, r, _p = abt.left(i), abt.right(i), abt.parent(i) puts "\nCurrent node index is #{i}, value is #{abt.val(i).inspect}" puts "Left child index is #{l}, value is #{abt.val(l).inspect}" puts "Right child index is #{r}, value is #{abt.val(r).inspect}" puts "Parent node index is #{_p}, value is #{abt.val(_p).inspect}" # Traverse tree res = abt.level_order puts "\nLevel-order traversal is: #{res}" res = abt.pre_order puts "Pre-order traversal is: #{res}" res = abt.in_order puts "In-order traversal is: #{res}" res = abt.post_order puts "Post-order traversal is: #{res}" end ================================================ FILE: en/codes/ruby/chapter_tree/avl_tree.rb ================================================ =begin File: avl_tree.rb Created Time: 2024-04-17 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### AVL tree ### class AVLTree ### Constructor ### def initialize @root = nil end ### Get binary tree root node ### def get_root @root end ### Get node height ### def height(node) # Empty node height is -1, leaf node height is 0 return node.height unless node.nil? -1 end ### Update node height ### def update_height(node) # Node height equals the height of the tallest subtree + 1 node.height = [height(node.left), height(node.right)].max + 1 end ### Get balance factor ### def balance_factor(node) # Empty node balance factor is 0 return 0 if node.nil? # Node balance factor = left subtree height - right subtree height height(node.left) - height(node.right) end ### Right rotation ### def right_rotate(node) child = node.left grand_child = child.right # Using child as pivot, rotate node to the right child.right = node node.left = grand_child # Update node height update_height(node) update_height(child) # Return root node of subtree after rotation child end ### Left rotation ### def left_rotate(node) child = node.right grand_child = child.left # Using child as pivot, rotate node to the left child.left = node node.right = grand_child # Update node height update_height(node) update_height(child) # Return root node of subtree after rotation child end ### Perform rotation to rebalance subtree ### def rotate(node) # Get balance factor of node balance_factor = balance_factor(node) # Left-heavy tree if balance_factor > 1 if balance_factor(node.left) >= 0 # Right rotation return right_rotate(node) else # First left rotation then right rotation node.left = left_rotate(node.left) return right_rotate(node) end # Right-heavy tree elsif balance_factor < -1 if balance_factor(node.right) <= 0 # Left rotation return left_rotate(node) else # First right rotation then left rotation node.right = right_rotate(node.right) return left_rotate(node) end end # Balanced tree, no rotation needed, return directly node end ### Insert node ### def insert(val) @root = insert_helper(@root, val) end ### Recursively insert node (helper method) ### def insert_helper(node, val) return TreeNode.new(val) if node.nil? # 1. Find insertion position and insert node if val < node.val node.left = insert_helper(node.left, val) elsif val > node.val node.right = insert_helper(node.right, val) else # Duplicate node not inserted, return directly return node end # Update node height update_height(node) # 2. Perform rotation operation to restore balance to this subtree rotate(node) end ### Delete node ### def remove(val) @root = remove_helper(@root, val) end ### Recursively delete node (helper method) ### def remove_helper(node, val) return if node.nil? # 1. Find node and delete if val < node.val node.left = remove_helper(node.left, val) elsif val > node.val node.right = remove_helper(node.right, val) else if node.left.nil? || node.right.nil? child = node.left || node.right # Number of child nodes = 0, delete node directly and return return if child.nil? # Number of child nodes = 1, delete node directly node = child else # Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it temp = node.right while !temp.left.nil? temp = temp.left end node.right = remove_helper(node.right, temp.val) node.val = temp.val end end # Update node height update_height(node) # 2. Perform rotation operation to restore balance to this subtree rotate(node) end ### Search node ### def search(val) cur = @root # Loop search, exit after passing leaf node while !cur.nil? # Target node is in cur's right subtree if cur.val < val cur = cur.right # Target node is in cur's left subtree elsif cur.val > val cur = cur.left # Found target node, exit loop else break end end # Return target node cur end end ### Driver Code ### if __FILE__ == $0 def test_insert(tree, val) tree.insert(val) puts "\nAfter inserting node #{val}, AVL tree is" print_tree(tree.get_root) end def test_remove(tree, val) tree.remove(val) puts "\nAfter deleting node #{val}, AVL tree is" print_tree(tree.get_root) end # Please pay attention to how the AVL tree maintains balance after inserting nodes avl_tree = AVLTree.new # Insert node # Delete nodes for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6] test_insert(avl_tree, val) end # Please pay attention to how the AVL tree maintains balance after deleting nodes test_insert(avl_tree, 7) # Remove node # Delete node with degree 1 test_remove(avl_tree, 8) # Delete node with degree 2 test_remove(avl_tree, 5) # Remove node with degree 1 test_remove(avl_tree, 4) # Remove node with degree 2 result_node = avl_tree.search(7) puts "\nFound node object #{result_node}, node value = #{result_node.val}" end ================================================ FILE: en/codes/ruby/chapter_tree/binary_search_tree.rb ================================================ =begin File: binary_search_tree.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Binary search tree ### class BinarySearchTree ### Constructor ### def initialize # Initialize empty tree @root = nil end ### Get binary tree root node ### def get_root @root end ### Search node ### def search(num) cur = @root # Loop search, exit after passing leaf node while !cur.nil? # Target node is in cur's right subtree if cur.val < num cur = cur.right # Target node is in cur's left subtree elsif cur.val > num cur = cur.left # Found target node, exit loop else break end end cur end ### Insert node ### def insert(num) # If tree is empty, initialize root node if @root.nil? @root = TreeNode.new(num) return end # Loop search, exit after passing leaf node cur, pre = @root, nil while !cur.nil? # Found duplicate node, return directly return if cur.val == num pre = cur # Insertion position is in cur's right subtree if cur.val < num cur = cur.right # Insertion position is in cur's left subtree else cur = cur.left end end # Insert node node = TreeNode.new(num) if pre.val < num pre.right = node else pre.left = node end end ### Delete node ### def remove(num) # If tree is empty, return directly return if @root.nil? # Loop search, exit after passing leaf node cur, pre = @root, nil while !cur.nil? # Found node to delete, exit loop break if cur.val == num pre = cur # Node to delete is in cur's right subtree if cur.val < num cur = cur.right # Node to delete is in cur's left subtree else cur = cur.left end end # If no node to delete, return directly return if cur.nil? # Number of child nodes = 0 or 1 if cur.left.nil? || cur.right.nil? # When number of child nodes = 0 / 1, child = null / that child node child = cur.left || cur.right # Delete node cur if cur != @root if pre.left == cur pre.left = child else pre.right = child end else # If deleted node is root node, reassign root node @root = child end # Number of child nodes = 2 else # Get next node of cur in inorder traversal tmp = cur.right while !tmp.left.nil? tmp = tmp.left end # Recursively delete node tmp remove(tmp.val) # Replace cur with tmp cur.val = tmp.val end end end ### Driver Code ### if __FILE__ == $0 # Initialize binary search tree bst = BinarySearchTree.new nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] # Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree nums.each { |num| bst.insert(num) } puts "\nInitialized binary tree is\n" print_tree(bst.get_root) # Search node node = bst.search(7) puts "\nFound node object: #{node}, node value = #{node.val}" # Insert node bst.insert(16) puts "\nAfter inserting node 16, binary tree is\n" print_tree(bst.get_root) # Remove node bst.remove(1) puts "\nAfter removing node 1, binary tree is\n" print_tree(bst.get_root) bst.remove(2) puts "\nAfter removing node 2, binary tree is\n" print_tree(bst.get_root) bst.remove(4) puts "\nAfter removing node 4, binary tree is\n" print_tree(bst.get_root) end ================================================ FILE: en/codes/ruby/chapter_tree/binary_tree.rb ================================================ =begin File: binary_tree.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Driver Code ### if __FILE__ == $0 # Initialize binary tree # Initialize nodes n1 = TreeNode.new(1) n2 = TreeNode.new(2) n3 = TreeNode.new(3) n4 = TreeNode.new(4) n5 = TreeNode.new(5) # Build references (pointers) between nodes n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 puts "\nInitialize binary tree\n\n" print_tree(n1) # Insert node P between n1 -> n2 _p = TreeNode.new(0) # Insert node _p between n1 -> n2 n1.left = _p _p.left = n2 puts "\nAfter inserting node _p\n\n" print_tree(n1) # Remove node n1.left = n2 puts "\nAfter deleting node _p\n\n" print_tree(n1) end ================================================ FILE: en/codes/ruby/chapter_tree/binary_tree_bfs.rb ================================================ =begin File: binary_tree_bfs.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Level-order traversal ### def level_order(root) # Initialize queue, add root node queue = [root] # Initialize a list to save the traversal sequence res = [] while !queue.empty? node = queue.shift # Dequeue res << node.val # Save node value queue << node.left unless node.left.nil? # Left child node enqueue queue << node.right unless node.right.nil? # Right child node enqueue end res end ### Driver Code ### if __FILE__ == $0 # Initialize binary tree # Here we use a function to generate a binary tree directly from an array root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) puts "\nInitialize binary tree\n\n" print_tree(root) # Level-order traversal res = level_order(root) puts "\nLevel-order traversal node sequence = #{res}" end ================================================ FILE: en/codes/ruby/chapter_tree/binary_tree_dfs.rb ================================================ =begin File: binary_tree_dfs.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Pre-order traversal ### def pre_order(root) return if root.nil? # Visit priority: root node -> left subtree -> right subtree $res << root.val pre_order(root.left) pre_order(root.right) end ### In-order traversal ### def in_order(root) return if root.nil? # Visit priority: left subtree -> root node -> right subtree in_order(root.left) $res << root.val in_order(root.right) end ### Post-order traversal ### def post_order(root) return if root.nil? # Visit priority: left subtree -> right subtree -> root node post_order(root.left) post_order(root.right) $res << root.val end ### Driver Code ### if __FILE__ == $0 # Initialize binary tree # Here we use a function to generate a binary tree directly from an array root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) puts "\nInitialize binary tree\n\n" print_tree(root) # Preorder traversal $res = [] pre_order(root) puts "\nPre-order traversal node sequence = #{$res}" # Inorder traversal $res.clear in_order(root) puts "\nIn-order traversal node sequence = #{$res}" # Postorder traversal $res.clear post_order(root) puts "\nPost-order traversal node sequence = #{$res}" end ================================================ FILE: en/codes/ruby/test_all.rb ================================================ require 'open3' start_time = Time.now ruby_code_dir = File.dirname(__FILE__) files = Dir.glob("#{ruby_code_dir}/chapter_*/*.rb") errors = [] files.each do |file| stdout, stderr, status = Open3.capture3("ruby #{file}") errors << stderr unless status.success? end puts "\x1b[34mTested #{files.count} files\x1b[m" unless errors.empty? puts "\x1b[33mFound exception in #{errors.length} files\x1b[m" raise errors.join("\n\n") else puts "\x1b[32mPASS\x1b[m" end puts "Testing finishes after #{((Time.now - start_time) * 1000).round} ms" ================================================ FILE: en/codes/ruby/utils/list_node.rb ================================================ =begin File: list_node.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Linked list node class ### class ListNode attr_accessor :val # Node value attr_accessor :next # Reference to next node def initialize(val=0, next_node=nil) @val = val @next = next_node end end ### Deserialize list to linked list ### def arr_to_linked_list(arr) head = current = ListNode.new(arr[0]) for i in 1...arr.length current.next = ListNode.new(arr[i]) current = current.next end head end ### Serialize linked list to list ### def linked_list_to_arr(head) arr = [] while head arr << head.val head = head.next end end ================================================ FILE: en/codes/ruby/utils/print_util.rb ================================================ =begin File: print_util.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative "./tree_node" ### Print matrix ### def print_matrix(mat) s = [] mat.each { |arr| s << " #{arr.to_s}" } puts "[\n#{s.join(",\n")}\n]" end ### Print linked list ### def print_linked_list(head) list = [] while head list << head.val head = head.next end puts "#{list.join(" -> ")}" end class Trunk attr_accessor :prev, :str def initialize(prev, str) @prev = prev @str = str end end def show_trunk(p) return if p.nil? show_trunk(p.prev) print p.str end ### Print binary tree ### # This tree printer is borrowed from TECHIE DELIGHT # https://www.techiedelight.com/c-program-print-binary-tree/ def print_tree(root, prev=nil, is_right=false) return if root.nil? prev_str = " " trunk = Trunk.new(prev, prev_str) print_tree(root.right, trunk, true) if prev.nil? trunk.str = "———" elsif is_right trunk.str = "/———" prev_str = " |" else trunk.str = "\\———" prev.str = prev_str end show_trunk(trunk) puts " #{root.val}" prev.str = prev_str if prev trunk.str = " |" print_tree(root.left, trunk, false) end ### Print hash table ### def print_hash_map(hmap) hmap.entries.each { |key, value| puts "#{key} -> #{value}" } end ### Print heap ### def print_heap(heap) puts "Array representation of heap: #{heap}" puts "Heap tree representation:" root = arr_to_tree(heap) print_tree(root) end ================================================ FILE: en/codes/ruby/utils/tree_node.rb ================================================ =begin File: tree_node.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Binary tree node class ### class TreeNode attr_accessor :val # Node value attr_accessor :height # Node height attr_accessor :left # Reference to left child node attr_accessor :right # Reference to right child node def initialize(val=0) @val = val @height = 0 end end ### Deserialize list to binary tree: recursion ### def arr_to_tree_dfs(arr, i) # Return nil if index exceeds array length or element is nil return if i < 0 || i >= arr.length || arr[i].nil? # Build the current node root = TreeNode.new(arr[i]) # Recursively build the left and right subtrees root.left = arr_to_tree_dfs(arr, 2 * i + 1) root.right = arr_to_tree_dfs(arr, 2 * i + 2) root end ### Deserialize list to binary tree ### def arr_to_tree(arr) arr_to_tree_dfs(arr, 0) end ### Serialize binary tree to list: recursion ### def tree_to_arr_dfs(root, i, res) return if root.nil? res += Array.new(i - res.length + 1) if i >= res.length res[i] = root.val tree_to_arr_dfs(root.left, 2 * i + 1, res) tree_to_arr_dfs(root.right, 2 * i + 2, res) end ### Serialize binary tree to list ### def tree_to_arr(root) res = [] tree_to_arr_dfs(root, 0, res) res end ================================================ FILE: en/codes/ruby/utils/vertex.rb ================================================ =begin File: vertex.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Vertex class ### class Vertex attr_accessor :val def initialize(val) @val = val end end ### Input value list vals, return vertex list vets ### def vals_to_vets(vals) Array.new(vals.length) { |i| Vertex.new(vals[i]) } end ### Input vertex list vets, return value list vals ### def vets_to_vals(vets) Array.new(vets.length) { |i| vets[i].val } end ================================================ FILE: en/codes/rust/.gitignore ================================================ target/ Cargo.lock ================================================ FILE: en/codes/rust/Cargo.toml ================================================ [package] name = "hello-algo-rust" version = "0.1.0" edition = "2021" publish = false # Run Command: cargo run --bin time_complexity [[bin]] name = "time_complexity" path = "chapter_computational_complexity/time_complexity.rs" # Run Command: cargo run --bin worst_best_time_complexity [[bin]] name = "worst_best_time_complexity" path = "chapter_computational_complexity/worst_best_time_complexity.rs" # Run Command: cargo run --bin space_complexity [[bin]] name = "space_complexity" path = "chapter_computational_complexity/space_complexity.rs" # Run Command: cargo run --bin iteration [[bin]] name = "iteration" path = "chapter_computational_complexity/iteration.rs" # Run Command: cargo run --bin recursion [[bin]] name = "recursion" path = "chapter_computational_complexity/recursion.rs" # Run Command: cargo run --bin two_sum [[bin]] name = "two_sum" path = "chapter_searching/two_sum.rs" # Run Command: cargo run --bin array [[bin]] name = "array" path = "chapter_array_and_linkedlist/array.rs" # Run Command: cargo run --bin linked_list [[bin]] name = "linked_list" path = "chapter_array_and_linkedlist/linked_list.rs" # Run Command: cargo run --bin list [[bin]] name = "list" path = "chapter_array_and_linkedlist/list.rs" # Run Command: cargo run --bin my_list [[bin]] name = "my_list" path = "chapter_array_and_linkedlist/my_list.rs" # Run Command: cargo run --bin stack [[bin]] name = "stack" path = "chapter_stack_and_queue/stack.rs" # Run Command: cargo run --bin linkedlist_stack [[bin]] name = "linkedlist_stack" path = "chapter_stack_and_queue/linkedlist_stack.rs" # Run Command: cargo run --bin queue [[bin]] name = "queue" path = "chapter_stack_and_queue/queue.rs" # Run Command: cargo run --bin linkedlist_queue [[bin]] name = "linkedlist_queue" path = "chapter_stack_and_queue/linkedlist_queue.rs" # Run Command: cargo run --bin deque [[bin]] name = "deque" path = "chapter_stack_and_queue/deque.rs" # Run Command: cargo run --bin array_deque [[bin]] name = "array_deque" path = "chapter_stack_and_queue/array_deque.rs" # Run Command: cargo run --bin linkedlist_deque [[bin]] name = "linkedlist_deque" path = "chapter_stack_and_queue/linkedlist_deque.rs" # Run Command: cargo run --bin simple_hash [[bin]] name = "simple_hash" path = "chapter_hashing/simple_hash.rs" # Run Command: cargo run --bin hash_map [[bin]] name = "hash_map" path = "chapter_hashing/hash_map.rs" # Run Command: cargo run --bin array_hash_map [[bin]] name = "array_hash_map" path = "chapter_hashing/array_hash_map.rs" # Run Command: cargo run --bin build_in_hash [[bin]] name = "build_in_hash" path = "chapter_hashing/build_in_hash.rs" # Run Command: cargo run --bin hash_map_chaining [[bin]] name = "hash_map_chaining" path = "chapter_hashing/hash_map_chaining.rs" # Run Command: cargo run --bin hash_map_open_addressing [[bin]] name = "hash_map_open_addressing" path = "chapter_hashing/hash_map_open_addressing.rs" # Run Command: cargo run --bin binary_search [[bin]] name = "binary_search" path = "chapter_searching/binary_search.rs" # Run Command: cargo run --bin binary_search_edge [[bin]] name = "binary_search_edge" path = "chapter_searching/binary_search_edge.rs" # Run Command: cargo run --bin binary_search_insertion [[bin]] name = "binary_search_insertion" path = "chapter_searching/binary_search_insertion.rs" # Run Command: cargo run --bin bubble_sort [[bin]] name = "bubble_sort" path = "chapter_sorting/bubble_sort.rs" # Run Command: cargo run --bin insertion_sort [[bin]] name = "insertion_sort" path = "chapter_sorting/insertion_sort.rs" # Run Command: cargo run --bin quick_sort [[bin]] name = "quick_sort" path = "chapter_sorting/quick_sort.rs" # Run Command: cargo run --bin merge_sort [[bin]] name = "merge_sort" path = "chapter_sorting/merge_sort.rs" # Run Command: cargo run --bin selection_sort [[bin]] name = "selection_sort" path = "chapter_sorting/selection_sort.rs" # Run Command: cargo run --bin bucket_sort [[bin]] name = "bucket_sort" path = "chapter_sorting/bucket_sort.rs" # Run Command: cargo run --bin heap_sort [[bin]] name = "heap_sort" path = "chapter_sorting/heap_sort.rs" # Run Command: cargo run --bin counting_sort [[bin]] name = "counting_sort" path = "chapter_sorting/counting_sort.rs" # Run Command: cargo run --bin radix_sort [[bin]] name = "radix_sort" path = "chapter_sorting/radix_sort.rs" # Run Command: cargo run --bin array_stack [[bin]] name = "array_stack" path = "chapter_stack_and_queue/array_stack.rs" # Run Command: cargo run --bin array_queue [[bin]] name = "array_queue" path = "chapter_stack_and_queue/array_queue.rs" # Run Command: cargo run --bin array_binary_tree [[bin]] name = "array_binary_tree" path = "chapter_tree/array_binary_tree.rs" # Run Command: cargo run --bin avl_tree [[bin]] name = "avl_tree" path = "chapter_tree/avl_tree.rs" # Run Command: cargo run --bin binary_search_tree [[bin]] name = "binary_search_tree" path = "chapter_tree/binary_search_tree.rs" # Run Command: cargo run --bin binary_tree_bfs [[bin]] name = "binary_tree_bfs" path = "chapter_tree/binary_tree_bfs.rs" # Run Command: cargo run --bin binary_tree_dfs [[bin]] name = "binary_tree_dfs" path = "chapter_tree/binary_tree_dfs.rs" # Run Command: cargo run --bin binary_tree [[bin]] name = "binary_tree" path = "chapter_tree/binary_tree.rs" # Run Command: cargo run --bin heap [[bin]] name = "heap" path = "chapter_heap/heap.rs" # Run Command: cargo run --bin my_heap [[bin]] name = "my_heap" path = "chapter_heap/my_heap.rs" # Run Command: cargo run --bin top_k [[bin]] name = "top_k" path = "chapter_heap/top_k.rs" # Run Command: cargo run --bin graph_adjacency_list [[bin]] name = "graph_adjacency_list" path = "chapter_graph/graph_adjacency_list.rs" # Run Command: cargo run --bin graph_adjacency_matrix [[bin]] name = "graph_adjacency_matrix" path = "chapter_graph/graph_adjacency_matrix.rs" # Run Command: cargo run --bin graph_bfs [[bin]] name = "graph_bfs" path = "chapter_graph/graph_bfs.rs" # Run Command: cargo run --bin graph_dfs [[bin]] name = "graph_dfs" path = "chapter_graph/graph_dfs.rs" # Run Command: cargo run --bin linear_search [[bin]] name = "linear_search" path = "chapter_searching/linear_search.rs" # Run Command: cargo run --bin hashing_search [[bin]] name = "hashing_search" path = "chapter_searching/hashing_search.rs" # Run Command: cargo run --bin climbing_stairs_dfs [[bin]] name = "climbing_stairs_dfs" path = "chapter_dynamic_programming/climbing_stairs_dfs.rs" # Run Command: cargo run --bin climbing_stairs_dfs_mem [[bin]] name = "climbing_stairs_dfs_mem" path = "chapter_dynamic_programming/climbing_stairs_dfs_mem.rs" # Run Command: cargo run --bin climbing_stairs_dp [[bin]] name = "climbing_stairs_dp" path = "chapter_dynamic_programming/climbing_stairs_dp.rs" # Run Command: cargo run --bin min_cost_climbing_stairs_dp [[bin]] name = "min_cost_climbing_stairs_dp" path = "chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs" # Run Command: cargo run --bin climbing_stairs_constraint_dp [[bin]] name = "climbing_stairs_constraint_dp" path = "chapter_dynamic_programming/climbing_stairs_constraint_dp.rs" # Run Command: cargo run --bin climbing_stairs_backtrack [[bin]] name = "climbing_stairs_backtrack" path = "chapter_dynamic_programming/climbing_stairs_backtrack.rs" # Run Command: cargo run --bin subset_sum_i_naive [[bin]] name = "subset_sum_i_naive" path = "chapter_backtracking/subset_sum_i_naive.rs" # Run Command: cargo run --bin subset_sum_i [[bin]] name = "subset_sum_i" path = "chapter_backtracking/subset_sum_i.rs" # Run Command: cargo run --bin subset_sum_ii [[bin]] name = "subset_sum_ii" path = "chapter_backtracking/subset_sum_ii.rs" # Run Command: cargo run --bin coin_change [[bin]] name = "coin_change" path = "chapter_dynamic_programming/coin_change.rs" # Run Command: cargo run --bin coin_change_ii [[bin]] name = "coin_change_ii" path = "chapter_dynamic_programming/coin_change_ii.rs" # Run Command: cargo run --bin unbounded_knapsack [[bin]] name = "unbounded_knapsack" path = "chapter_dynamic_programming/unbounded_knapsack.rs" # Run Command: cargo run --bin knapsack [[bin]] name = "knapsack" path = "chapter_dynamic_programming/knapsack.rs" # Run Command: cargo run --bin min_path_sum [[bin]] name = "min_path_sum" path = "chapter_dynamic_programming/min_path_sum.rs" # Run Command: cargo run --bin edit_distance [[bin]] name = "edit_distance" path = "chapter_dynamic_programming/edit_distance.rs" # Run Command: cargo run --bin n_queens [[bin]] name = "n_queens" path = "chapter_backtracking/n_queens.rs" # Run Command: cargo run --bin permutations_i [[bin]] name = "permutations_i" path = "chapter_backtracking/permutations_i.rs" # Run Command: cargo run --bin permutations_ii [[bin]] name = "permutations_ii" path = "chapter_backtracking/permutations_ii.rs" # Run Command: cargo run --bin preorder_traversal_i_compact [[bin]] name = "preorder_traversal_i_compact" path = "chapter_backtracking/preorder_traversal_i_compact.rs" # Run Command: cargo run --bin preorder_traversal_ii_compact [[bin]] name = "preorder_traversal_ii_compact" path = "chapter_backtracking/preorder_traversal_ii_compact.rs" # Run Command: cargo run --bin preorder_traversal_iii_compact [[bin]] name = "preorder_traversal_iii_compact" path = "chapter_backtracking/preorder_traversal_iii_compact.rs" # Run Command: cargo run --bin preorder_traversal_iii_template [[bin]] name = "preorder_traversal_iii_template" path = "chapter_backtracking/preorder_traversal_iii_template.rs" # Run Command: cargo run --bin binary_search_recur [[bin]] name = "binary_search_recur" path = "chapter_divide_and_conquer/binary_search_recur.rs" # Run Command: cargo run --bin hanota [[bin]] name = "hanota" path = "chapter_divide_and_conquer/hanota.rs" # Run Command: cargo run --bin build_tree [[bin]] name = "build_tree" path = "chapter_divide_and_conquer/build_tree.rs" # Run Command: cargo run --bin coin_change_greedy [[bin]] name = "coin_change_greedy" path = "chapter_greedy/coin_change_greedy.rs" # Run Command: cargo run --bin fractional_knapsack [[bin]] name = "fractional_knapsack" path = "chapter_greedy/fractional_knapsack.rs" # Run Command: cargo run --bin max_capacity [[bin]] name = "max_capacity" path = "chapter_greedy/max_capacity.rs" # Run Command: cargo run --bin max_product_cutting [[bin]] name = "max_product_cutting" path = "chapter_greedy/max_product_cutting.rs" [dependencies] rand = "0.8.5" ================================================ FILE: en/codes/rust/chapter_array_and_linkedlist/array.rs ================================================ /* * File: array.rs * Created Time: 2023-01-15 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use rand::Rng; /* Random access to element */ fn random_access(nums: &[i32]) -> i32 { // Randomly select a number in interval [0, nums.len()) let random_index = rand::thread_rng().gen_range(0..nums.len()); // Retrieve and return the random element let random_num = nums[random_index]; random_num } /* Extend array length */ fn extend(nums: &[i32], enlarge: usize) -> Vec { // Initialize an array with extended length let mut res: Vec = vec![0; nums.len() + enlarge]; // Copy all elements from original array to new res[0..nums.len()].copy_from_slice(nums); // Return the extended new array res } /* Insert element num at index index in the array */ fn insert(nums: &mut [i32], num: i32, index: usize) { // Move all elements at and after index index backward by one position for i in (index + 1..nums.len()).rev() { nums[i] = nums[i - 1]; } // Assign num to the element at index index nums[index] = num; } /* Remove the element at index index */ fn remove(nums: &mut [i32], index: usize) { // Move all elements after index index forward by one position for i in index..nums.len() - 1 { nums[i] = nums[i + 1]; } } /* Traverse array */ fn traverse(nums: &[i32]) { let mut _count = 0; // Traverse array by index for i in 0..nums.len() { _count += nums[i]; } // Direct traversal of array elements _count = 0; for &num in nums { _count += num; } } /* Find the specified element in the array */ fn find(nums: &[i32], target: i32) -> Option { for i in 0..nums.len() { if nums[i] == target { return Some(i); } } None } /* Driver Code */ fn main() { /* Initialize array */ let arr: [i32; 5] = [0; 5]; print!("Array arr = "); print_util::print_array(&arr); // In Rust, specifying length ([i32; 5]) is an array, without length (&[i32]) is a slice // Since Rust arrays are designed to have compile-time determined length, only constants can specify length // Vector is the type Rust generally uses as a dynamic array // To facilitate implementing the extend() method, the following treats vector as array let nums: Vec = vec![1, 3, 2, 5, 4]; print!("\nArray nums = "); print_util::print_array(&nums); // Insert element let random_num = random_access(&nums); println!("\nGet random element {} from nums", random_num); // Traverse array let mut nums: Vec = extend(&nums, 3); print!("Extend array length to 8, resulting in nums = "); print_util::print_array(&nums); // Insert element insert(&mut nums, 6, 3); print!("\nInsert number 6 at index 3, get nums = "); print_util::print_array(&nums); // Remove element remove(&mut nums, 2); print!("\nDelete element at index 2, get nums = "); print_util::print_array(&nums); // Traverse array traverse(&nums); // Find element let index = find(&nums, 3).unwrap(); println!("\nFind element 3 in nums, index = {}", index); } ================================================ FILE: en/codes/rust/chapter_array_and_linkedlist/linked_list.rs ================================================ /* * File: linked_list.rs * Created Time: 2023-03-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* Insert node P after node n0 in the linked list */ #[allow(non_snake_case)] pub fn insert(n0: &Rc>>, P: Rc>>) { let n1 = n0.borrow_mut().next.take(); P.borrow_mut().next = n1; n0.borrow_mut().next = Some(P); } /* Remove the first node after node n0 in the linked list */ #[allow(non_snake_case)] pub fn remove(n0: &Rc>>) { // n0 -> P -> n1 let P = n0.borrow_mut().next.take(); if let Some(node) = P { let n1 = node.borrow_mut().next.take(); n0.borrow_mut().next = n1; } } /* Access the node at index index in the linked list */ pub fn access(head: Rc>>, index: i32) -> Option>>> { fn dfs( head: Option<&Rc>>>, index: i32, ) -> Option>>> { if index <= 0 { return head.cloned(); } if let Some(node) = head { dfs(node.borrow().next.as_ref(), index - 1) } else { None } } dfs(Some(head).as_ref(), index) } /* Find the first node with value target in the linked list */ pub fn find(head: Rc>>, target: T) -> i32 { fn find(head: Option<&Rc>>>, target: T, idx: i32) -> i32 { if let Some(node) = head { if node.borrow().val == target { return idx; } return find(node.borrow().next.as_ref(), target, idx + 1); } else { -1 } } find(Some(head).as_ref(), target, 0) } /* Driver Code */ fn main() { /* Initialize linked list */ // Initialize each node let n0 = ListNode::new(1); let n1 = ListNode::new(3); let n2 = ListNode::new(2); let n3 = ListNode::new(5); let n4 = ListNode::new(4); // Build references between nodes n0.borrow_mut().next = Some(n1.clone()); n1.borrow_mut().next = Some(n2.clone()); n2.borrow_mut().next = Some(n3.clone()); n3.borrow_mut().next = Some(n4.clone()); print!("Initialized linked list is "); print_util::print_linked_list(&n0); /* Insert node */ insert(&n0, ListNode::new(0)); print!("After inserting node, linked list is "); print_util::print_linked_list(&n0); /* Remove node */ remove(&n0); print!("After deleting node, linked list is "); print_util::print_linked_list(&n0); /* Access node */ let node = access(n0.clone(), 3); println!("Value of node at index 3 in linked list = {}", node.unwrap().borrow().val); /* Search node */ let index = find(n0.clone(), 2); println!("Index of node with value 2 in linked list = {}", index); } ================================================ FILE: en/codes/rust/chapter_array_and_linkedlist/list.rs ================================================ /* * File: list.rs * Created Time: 2023-01-18 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Driver Code */ fn main() { // Initialize list let mut nums: Vec = vec![1, 3, 2, 5, 4]; print!("List nums = "); print_util::print_array(&nums); // Update element let num = nums[1]; println!("\nAccess element at index 1, get num = {num}"); // Add elements at the end nums[1] = 0; print!("Update element at index 1 to 0, resulting in nums = "); print_util::print_array(&nums); // Remove element nums.clear(); print!("\nAfter clearing list, nums = "); print_util::print_array(&nums); // Direct traversal of list elements nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); print!("\nAfter adding elements, nums = "); print_util::print_array(&nums); // Sort list nums.insert(3, 6); print!("\nInsert number 6 at index 3, get nums = "); print_util::print_array(&nums); // Remove element nums.remove(3); print!("\nDelete element at index 3, get nums = "); print_util::print_array(&nums); // Traverse list by index let mut _count = 0; for i in 0..nums.len() { _count += nums[i]; } // Directly traverse list elements _count = 0; for x in &nums { _count += x; } // Concatenate two lists let mut nums1 = vec![6, 8, 7, 10, 9]; nums.append(&mut nums1); // After append (move), nums1 is empty! // nums.extend(&nums1); // extend (borrow) allows nums1 to continue being used print!("\nAfter concatenating list nums1 to nums, get nums = "); print_util::print_array(&nums); // Sort list nums.sort(); print!("\nAfter sorting list, nums = "); print_util::print_array(&nums); } ================================================ FILE: en/codes/rust/chapter_array_and_linkedlist/my_list.rs ================================================ /* * File: my_list.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* List class */ #[allow(dead_code)] struct MyList { arr: Vec, // Array (stores list elements) capacity: usize, // List capacity size: usize, // List length (current number of elements) extend_ratio: usize, // Multiple by which the list capacity is extended each time } #[allow(unused, unused_comparisons)] impl MyList { /* Constructor */ pub fn new(capacity: usize) -> Self { let mut vec = vec![0; capacity]; Self { arr: vec, capacity, size: 0, extend_ratio: 2, } } /* Get list length (current number of elements) */ pub fn size(&self) -> usize { return self.size; } /* Get list capacity */ pub fn capacity(&self) -> usize { return self.capacity; } /* Update element */ pub fn get(&self, index: usize) -> i32 { // If the index is out of bounds, throw an exception, as below if index >= self.size { panic!("Index out of bounds") }; return self.arr[index]; } /* Add elements at the end */ pub fn set(&mut self, index: usize, num: i32) { if index >= self.size { panic!("Index out of bounds") }; self.arr[index] = num; } /* Direct traversal of list elements */ pub fn add(&mut self, num: i32) { // When the number of elements exceeds capacity, trigger the extension mechanism if self.size == self.capacity() { self.extend_capacity(); } self.arr[self.size] = num; // Update the number of elements self.size += 1; } /* Sort list */ pub fn insert(&mut self, index: usize, num: i32) { if index >= self.size() { panic!("Index out of bounds") }; // When the number of elements exceeds capacity, trigger the extension mechanism if self.size == self.capacity() { self.extend_capacity(); } // Move all elements after index index forward by one position for j in (index..self.size).rev() { self.arr[j + 1] = self.arr[j]; } self.arr[index] = num; // Update the number of elements self.size += 1; } /* Remove element */ pub fn remove(&mut self, index: usize) -> i32 { if index >= self.size() { panic!("Index out of bounds") }; let num = self.arr[index]; // Create a new array with length _extend_ratio times the original array, and copy the original array to the new array for j in index..self.size - 1 { self.arr[j] = self.arr[j + 1]; } // Update the number of elements self.size -= 1; // Return the removed element return num; } /* Driver Code */ pub fn extend_capacity(&mut self) { // Create new array with length extend_ratio times original, copy original array to new array let new_capacity = self.capacity * self.extend_ratio; self.arr.resize(new_capacity, 0); // Add elements at the end self.capacity = new_capacity; } /* Convert list to array */ pub fn to_array(&self) -> Vec { // Elements enqueue let mut arr = Vec::new(); for i in 0..self.size { arr.push(self.get(i)); } arr } } /* Driver Code */ fn main() { /* Initialize list */ let mut nums = MyList::new(10); /* Direct traversal of list elements */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print!("List nums = "); print_util::print_array(&nums.to_array()); print!(", capacity = {}, length = {}", nums.capacity(), nums.size()); /* Sort list */ nums.insert(3, 6); print!("\nInsert number 6 at index 3, get nums = "); print_util::print_array(&nums.to_array()); /* Remove element */ nums.remove(3); print!("\nDelete element at index 3, get nums = "); print_util::print_array(&nums.to_array()); /* Update element */ let num = nums.get(1); println!("\nAccess element at index 1, get num = {num}"); /* Add elements at the end */ nums.set(1, 0); print!("Update element at index 1 to 0, resulting in nums = "); print_util::print_array(&nums.to_array()); /* Test capacity expansion mechanism */ for i in 0..10 { // At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism nums.add(i); } print!("\nAfter expanding list, nums = "); print_util::print_array(&nums.to_array()); print!(", capacity = {}, length = {}", nums.capacity(), nums.size()); } ================================================ FILE: en/codes/rust/chapter_backtracking/n_queens.rs ================================================ /* * File: n_queens.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* Backtracking algorithm: N queens */ fn backtrack( row: usize, n: usize, state: &mut Vec>, res: &mut Vec>>, cols: &mut [bool], diags1: &mut [bool], diags2: &mut [bool], ) { // When all rows are placed, record the solution if row == n { res.push(state.clone()); return; } // Traverse all columns for col in 0..n { // Calculate the main diagonal and anti-diagonal corresponding to this cell let diag1 = row + n - 1 - col; let diag2 = row + col; // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell if !cols[col] && !diags1[diag1] && !diags2[diag2] { // Attempt: place the queen in this cell state[row][col] = "Q".into(); (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true); // Place the next row backtrack(row + 1, n, state, res, cols, diags1, diags2); // Backtrack: restore this cell to an empty cell state[row][col] = "#".into(); (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false); } } } /* Solve N queens */ fn n_queens(n: usize) -> Vec>> { // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell let mut state: Vec> = vec![vec!["#".to_string(); n]; n]; let mut cols = vec![false; n]; // Record whether there is a queen in the column let mut diags1 = vec![false; 2 * n - 1]; // Record whether there is a queen on the main diagonal let mut diags2 = vec![false; 2 * n - 1]; // Record whether there is a queen on the anti-diagonal let mut res: Vec>> = Vec::new(); backtrack( 0, n, &mut state, &mut res, &mut cols, &mut diags1, &mut diags2, ); res } /* Driver Code */ pub fn main() { let n: usize = 4; let res = n_queens(n); println!("Input board size is {n}"); println!("Total queen placement solutions: {}", res.len()); for state in res.iter() { println!("--------------------"); for row in state.iter() { println!("{:?}", row); } } } ================================================ FILE: en/codes/rust/chapter_backtracking/permutations_i.rs ================================================ /* * File: permutations_i.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* Backtracking algorithm: Permutations I */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // When the state length equals the number of elements, record the solution if state.len() == choices.len() { res.push(state); return; } // Traverse all choices for i in 0..choices.len() { let choice = choices[i]; // Pruning: do not allow repeated selection of elements if !selected[i] { // Attempt: make choice, update state selected[i] = true; state.push(choice); // Proceed to the next round of selection backtrack(state.clone(), choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop(); } } } /* Permutations I */ fn permutations_i(nums: &mut [i32]) -> Vec> { let mut res = Vec::new(); // State (subset) backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [1, 2, 3]; let res = permutations_i(&mut nums); println!("Input array nums = {:?}", &nums); println!("All permutations res = {:?}", &res); } ================================================ FILE: en/codes/rust/chapter_backtracking/permutations_ii.rs ================================================ /* * File: permutations_ii.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use std::collections::HashSet; /* Backtracking algorithm: Permutations II */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // When the state length equals the number of elements, record the solution if state.len() == choices.len() { res.push(state); return; } // Traverse all choices let mut duplicated = HashSet::::new(); for i in 0..choices.len() { let choice = choices[i]; // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if !selected[i] && !duplicated.contains(&choice) { // Attempt: make choice, update state duplicated.insert(choice); // Record the selected element value selected[i] = true; state.push(choice); // Proceed to the next round of selection backtrack(state.clone(), choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop(); } } } /* Permutations II */ fn permutations_ii(nums: &mut [i32]) -> Vec> { let mut res = Vec::new(); backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [1, 2, 2]; let res = permutations_ii(&mut nums); println!("Input array nums = {:?}", &nums); println!("All permutations res = {:?}", &res); } ================================================ FILE: en/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs ================================================ /* * File: preorder_traversal_i_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* Preorder traversal: Example 1 */ fn pre_order(res: &mut Vec>>, root: Option<&Rc>>) { if root.is_none() { return; } if let Some(node) = root { if node.borrow().val == 7 { // Record solution res.push(node.clone()); } pre_order(res, node.borrow().left.as_ref()); pre_order(res, node.borrow().right.as_ref()); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("Initialize binary tree"); print_util::print_tree(root.as_ref().unwrap()); // Preorder traversal let mut res = Vec::new(); pre_order(&mut res, root.as_ref()); println!("\nOutput all nodes with value 7"); let mut vals = Vec::new(); for node in res { vals.push(node.borrow().val) } println!("{:?}", vals); } ================================================ FILE: en/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs ================================================ /* * File: preorder_traversal_ii_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* Preorder traversal: Example 2 */ fn pre_order( res: &mut Vec>>>, path: &mut Vec>>, root: Option<&Rc>>, ) { if root.is_none() { return; } if let Some(node) = root { // Attempt path.push(node.clone()); if node.borrow().val == 7 { // Record solution res.push(path.clone()); } pre_order(res, path, node.borrow().left.as_ref()); pre_order(res, path, node.borrow().right.as_ref()); // Backtrack path.pop(); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("Initialize binary tree"); print_util::print_tree(root.as_ref().unwrap()); // Preorder traversal let mut path = Vec::new(); let mut res = Vec::new(); pre_order(&mut res, &mut path, root.as_ref()); println!("\nOutput all paths from root node to node 7"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: en/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs ================================================ /* * File: preorder_traversal_iii_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* Preorder traversal: Example 3 */ fn pre_order( res: &mut Vec>>>, path: &mut Vec>>, root: Option<&Rc>>, ) { // Pruning if root.is_none() || root.as_ref().unwrap().borrow().val == 3 { return; } if let Some(node) = root { // Attempt path.push(node.clone()); if node.borrow().val == 7 { // Record solution res.push(path.clone()); } pre_order(res, path, node.borrow().left.as_ref()); pre_order(res, path, node.borrow().right.as_ref()); // Backtrack path.pop(); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("Initialize binary tree"); print_util::print_tree(root.as_ref().unwrap()); // Preorder traversal let mut path = Vec::new(); let mut res = Vec::new(); pre_order(&mut res, &mut path, root.as_ref()); println!("\nOutput all paths from root node to node 7, paths do not include nodes with value 3"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: en/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs ================================================ /* * File: preorder_traversal_iii_template.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* Check if the current state is a solution */ fn is_solution(state: &mut Vec>>) -> bool { return !state.is_empty() && state.last().unwrap().borrow().val == 7; } /* Record solution */ fn record_solution( state: &mut Vec>>, res: &mut Vec>>>, ) { res.push(state.clone()); } /* Check if the choice is valid under the current state */ fn is_valid(_: &mut Vec>>, choice: Option<&Rc>>) -> bool { return choice.is_some() && choice.unwrap().borrow().val != 3; } /* Update state */ fn make_choice(state: &mut Vec>>, choice: Rc>) { state.push(choice); } /* Restore state */ fn undo_choice(state: &mut Vec>>, _: Rc>) { state.pop(); } /* Backtracking algorithm: Example 3 */ fn backtrack( state: &mut Vec>>, choices: &Vec>>>, res: &mut Vec>>>, ) { // Check if it is a solution if is_solution(state) { // Record solution record_solution(state, res); } // Traverse all choices for &choice in choices.iter() { // Pruning: check if the choice is valid if is_valid(state, choice) { // Attempt: make choice, update state make_choice(state, choice.unwrap().clone()); // Proceed to the next round of selection backtrack( state, &vec![ choice.unwrap().borrow().left.as_ref(), choice.unwrap().borrow().right.as_ref(), ], res, ); // Backtrack: undo choice, restore to previous state undo_choice(state, choice.unwrap().clone()); } } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("Initialize binary tree"); print_util::print_tree(root.as_ref().unwrap()); // Backtracking algorithm let mut res = Vec::new(); backtrack(&mut Vec::new(), &mut vec![root.as_ref()], &mut res); println!("\nOutput all paths from root node to node 7, requiring paths do not include nodes with value 3"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: en/codes/rust/chapter_backtracking/subset_sum_i.rs ================================================ /* * File: subset_sum_i.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Backtracking algorithm: Subset sum I */ fn backtrack( state: &mut Vec, target: i32, choices: &[i32], start: usize, res: &mut Vec>, ) { // When the subset sum equals target, record the solution if target == 0 { res.push(state.clone()); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets for i in start..choices.len() { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if target - choices[i] < 0 { break; } // Attempt: make choice, update target, start state.push(choices[i]); // Proceed to the next round of selection backtrack(state, target - choices[i], choices, i, res); // Backtrack: undo choice, restore to previous state state.pop(); } } /* Solve subset sum I */ fn subset_sum_i(nums: &mut [i32], target: i32) -> Vec> { let mut state = Vec::new(); // State (subset) nums.sort(); // Sort nums let start = 0; // Start point for traversal let mut res = Vec::new(); // Result list (subset list) backtrack(&mut state, target, nums, start, &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [3, 4, 5]; let target = 9; let res = subset_sum_i(&mut nums, target); println!("Input array nums = {:?}, target = {}", &nums, target); println!("All subsets with sum equal to {} res = {:?}", target, &res); } ================================================ FILE: en/codes/rust/chapter_backtracking/subset_sum_i_naive.rs ================================================ /* * File: subset_sum_i_naive.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Backtracking algorithm: Subset sum I */ fn backtrack( state: &mut Vec, target: i32, total: i32, choices: &[i32], res: &mut Vec>, ) { // When the subset sum equals target, record the solution if total == target { res.push(state.clone()); return; } // Traverse all choices for i in 0..choices.len() { // Pruning: if the subset sum exceeds target, skip this choice if total + choices[i] > target { continue; } // Attempt: make choice, update element sum total state.push(choices[i]); // Proceed to the next round of selection backtrack(state, target, total + choices[i], choices, res); // Backtrack: undo choice, restore to previous state state.pop(); } } /* Solve subset sum I (including duplicate subsets) */ fn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec> { let mut state = Vec::new(); // State (subset) let total = 0; // Subset sum let mut res = Vec::new(); // Result list (subset list) backtrack(&mut state, target, total, nums, &mut res); res } /* Driver Code */ pub fn main() { let nums = [3, 4, 5]; let target = 9; let res = subset_sum_i_naive(&nums, target); println!("Input array nums = {:?}, target = {}", &nums, target); println!("All subsets with sum equal to {} res = {:?}", target, &res); println!("Please note that this method outputs results containing duplicate sets"); } ================================================ FILE: en/codes/rust/chapter_backtracking/subset_sum_ii.rs ================================================ /* * File: subset_sum_ii.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Backtracking algorithm: Subset sum II */ fn backtrack( state: &mut Vec, target: i32, choices: &[i32], start: usize, res: &mut Vec>, ) { // When the subset sum equals target, record the solution if target == 0 { res.push(state.clone()); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets // Pruning 3: start traversing from start to avoid repeatedly selecting the same element for i in start..choices.len() { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if target - choices[i] < 0 { break; } // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly if i > start && choices[i] == choices[i - 1] { continue; } // Attempt: make choice, update target, start state.push(choices[i]); // Proceed to the next round of selection backtrack(state, target - choices[i], choices, i + 1, res); // Backtrack: undo choice, restore to previous state state.pop(); } } /* Solve subset sum II */ fn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec> { let mut state = Vec::new(); // State (subset) nums.sort(); // Sort nums let start = 0; // Start point for traversal let mut res = Vec::new(); // Result list (subset list) backtrack(&mut state, target, nums, start, &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [4, 4, 5]; let target = 9; let res = subset_sum_ii(&mut nums, target); println!("Input array nums = {:?}, target = {}", &nums, target); println!("All subsets with sum equal to {} res = {:?}", target, &res); } ================================================ FILE: en/codes/rust/chapter_computational_complexity/iteration.rs ================================================ /* * File: iteration.rs * Created Time: 2023-09-02 * Author: night-cruise (2586447362@qq.com) */ /* for loop */ fn for_loop(n: i32) -> i32 { let mut res = 0; // Sum 1, 2, ..., n-1, n for i in 1..=n { res += i; } res } /* while loop */ fn while_loop(n: i32) -> i32 { let mut res = 0; let mut i = 1; // Initialize condition variable // Sum 1, 2, ..., n-1, n while i <= n { res += i; i += 1; // Update condition variable } res } /* while loop (two updates) */ fn while_loop_ii(n: i32) -> i32 { let mut res = 0; let mut i = 1; // Initialize condition variable // Sum 1, 4, 10, ... while i <= n { res += i; // Update condition variable i += 1; i *= 2; } res } /* Nested for loop */ fn nested_for_loop(n: i32) -> String { let mut res = vec![]; // Loop i = 1, 2, ..., n-1, n for i in 1..=n { // Loop j = 1, 2, ..., n-1, n for j in 1..=n { res.push(format!("({}, {}), ", i, j)); } } res.join("") } /* Driver Code */ fn main() { let n = 5; let mut res; res = for_loop(n); println!("\nFor loop sum result res = {res}"); res = while_loop(n); println!("\nWhile loop sum result res = {res}"); res = while_loop_ii(n); println!("\nWhile loop (two updates) sum result res = {}", res); let res = nested_for_loop(n); println!("\nNested for loop traversal result {res}"); } ================================================ FILE: en/codes/rust/chapter_computational_complexity/recursion.rs ================================================ /* * File: recursion.rs * Created Time: 2023-09-02 * Author: night-cruise (2586447362@qq.com) */ /* Recursion */ fn recur(n: i32) -> i32 { // Termination condition if n == 1 { return 1; } // Recurse: recursive call let res = recur(n - 1); // Return: return result n + res } /* Simulate recursion using iteration */ fn for_loop_recur(n: i32) -> i32 { // Use an explicit stack to simulate the system call stack let mut stack = Vec::new(); let mut res = 0; // Recurse: recursive call for i in (1..=n).rev() { // Simulate "recurse" with "push" stack.push(i); } // Return: return result while !stack.is_empty() { // Simulate "return" with "pop" res += stack.pop().unwrap(); } // res = 1+2+3+...+n res } /* Tail recursion */ fn tail_recur(n: i32, res: i32) -> i32 { // Termination condition if n == 0 { return res; } // Tail recursive call tail_recur(n - 1, res + n) } /* Fibonacci sequence: recursion */ fn fib(n: i32) -> i32 { // Termination condition f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1; } // Recursive call f(n) = f(n-1) + f(n-2) let res = fib(n - 1) + fib(n - 2); // Return result res } /* Driver Code */ fn main() { let n = 5; let mut res; res = recur(n); println!("\nRecursion sum result res = {res}"); res = for_loop_recur(n); println!("\nUsing iteration to simulate recursion sum result res = {res}"); res = tail_recur(n, 0); println!("\nTail recursion sum result res = {res}"); res = fib(n); println!("\nThe {n}th Fibonacci number is {res}"); } ================================================ FILE: en/codes/rust/chapter_computational_complexity/space_complexity.rs ================================================ /* * File: space_complexity.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode, TreeNode}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; /* Function */ fn function() -> i32 { // Perform some operations return 0; } /* Constant order */ #[allow(unused)] fn constant(n: i32) { // Constants, variables, objects occupy O(1) space const A: i32 = 0; let b = 0; let nums = vec![0; 10000]; let node = ListNode::new(0); // Variables in the loop occupy O(1) space for i in 0..n { let c = 0; } // Functions in the loop occupy O(1) space for i in 0..n { function(); } } /* Linear order */ #[allow(unused)] fn linear(n: i32) { // Array of length n uses O(n) space let mut nums = vec![0; n as usize]; // A list of length n occupies O(n) space let mut nodes = Vec::new(); for i in 0..n { nodes.push(ListNode::new(i)) } // A hash table of length n occupies O(n) space let mut map = HashMap::new(); for i in 0..n { map.insert(i, i.to_string()); } } /* Linear order (recursive implementation) */ fn linear_recur(n: i32) { println!("Recursion n = {}", n); if n == 1 { return; }; linear_recur(n - 1); } /* Exponential order */ #[allow(unused)] fn quadratic(n: i32) { // Matrix uses O(n^2) space let num_matrix = vec![vec![0; n as usize]; n as usize]; // 2D list uses O(n^2) space let mut num_list = Vec::new(); for i in 0..n { let mut tmp = Vec::new(); for j in 0..n { tmp.push(0); } num_list.push(tmp); } } /* Quadratic order (recursive implementation) */ fn quadratic_recur(n: i32) -> i32 { if n <= 0 { return 0; }; // Array nums has length n, n-1, ..., 2, 1 let nums = vec![0; n as usize]; println!("In recursion n = {}, nums length = {}", n, nums.len()); return quadratic_recur(n - 1); } /* Driver Code */ fn build_tree(n: i32) -> Option>> { if n == 0 { return None; }; let root = TreeNode::new(0); root.borrow_mut().left = build_tree(n - 1); root.borrow_mut().right = build_tree(n - 1); return Some(root); } /* Driver Code */ fn main() { let n = 5; // Constant order constant(n); // Linear order linear(n); linear_recur(n); // Exponential order quadratic(n); quadratic_recur(n); // Exponential order let root = build_tree(n); print_util::print_tree(&root.unwrap()); } ================================================ FILE: en/codes/rust/chapter_computational_complexity/time_complexity.rs ================================================ /* * File: time_complexity.rs * Created Time: 2023-01-10 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ /* Constant order */ fn constant(n: i32) -> i32 { _ = n; let mut count = 0; let size = 100_000; for _ in 0..size { count += 1; } count } /* Linear order */ fn linear(n: i32) -> i32 { let mut count = 0; for _ in 0..n { count += 1; } count } /* Linear order (traversing array) */ fn array_traversal(nums: &[i32]) -> i32 { let mut count = 0; // Number of iterations is proportional to the array length for _ in nums { count += 1; } count } /* Exponential order */ fn quadratic(n: i32) -> i32 { let mut count = 0; // Number of iterations is quadratically related to the data size n for _ in 0..n { for _ in 0..n { count += 1; } } count } /* Quadratic order (bubble sort) */ fn bubble_sort(nums: &mut [i32]) -> i32 { let mut count = 0; // Counter // Outer loop: unsorted range is [0, i] for i in (1..nums.len()).rev() { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for j in 0..i { if nums[j] > nums[j + 1] { // Swap nums[j] and nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // Element swap includes 3 unit operations } } } count } /* Exponential order (loop implementation) */ fn exponential(n: i32) -> i32 { let mut count = 0; let mut base = 1; // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1) for _ in 0..n { for _ in 0..base { count += 1 } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 count } /* Exponential order (recursive implementation) */ fn exp_recur(n: i32) -> i32 { if n == 1 { return 1; } exp_recur(n - 1) + exp_recur(n - 1) + 1 } /* Logarithmic order (loop implementation) */ fn logarithmic(mut n: i32) -> i32 { let mut count = 0; while n > 1 { n = n / 2; count += 1; } count } /* Logarithmic order (recursive implementation) */ fn log_recur(n: i32) -> i32 { if n <= 1 { return 0; } log_recur(n / 2) + 1 } /* Linearithmic order */ fn linear_log_recur(n: i32) -> i32 { if n <= 1 { return 1; } let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2); for _ in 0..n { count += 1; } return count; } /* Factorial order (recursive implementation) */ fn factorial_recur(n: i32) -> i32 { if n == 0 { return 1; } let mut count = 0; // Split from 1 into n for _ in 0..n { count += factorial_recur(n - 1); } count } /* Driver Code */ fn main() { // You can modify n to run and observe the trend of the number of operations for various complexities let n: i32 = 8; println!("Input data size n = {}", n); let mut count = constant(n); println!("Constant-time operations count = {}", count); count = linear(n); println!("Linear-time operations count = {}", count); count = array_traversal(&vec![0; n as usize]); println!("Linear-time (array traversal) operations count = {}", count); count = quadratic(n); println!("Quadratic-time operations count = {}", count); let mut nums = (1..=n).rev().collect::>(); // [n,n-1,...,2,1] count = bubble_sort(&mut nums); println!("Quadratic-time (bubble sort) operations count = {}", count); count = exponential(n); println!("Exponential-time (iterative) operations count = {}", count); count = exp_recur(n); println!("Exponential-time (recursive) operations count = {}", count); count = logarithmic(n); println!("Logarithmic-time (iterative) operations count = {}", count); count = log_recur(n); println!("Logarithmic-time (recursive) operations count = {}", count); count = linear_log_recur(n); println!("Linearithmic-time (recursive) operations count = {}", count); count = factorial_recur(n); println!("Factorial-time (recursive) operations count = {}", count); } ================================================ FILE: en/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs ================================================ /* * File: worst_best_time_complexity.rs * Created Time: 2023-01-13 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use rand::seq::SliceRandom; use rand::thread_rng; /* Generate an array with elements { 1, 2, ..., n }, order shuffled */ fn random_numbers(n: i32) -> Vec { // Generate array nums = { 1, 2, 3, ..., n } let mut nums = (1..=n).collect::>(); // Randomly shuffle array elements nums.shuffle(&mut thread_rng()); nums } /* Find the index of number 1 in array nums */ fn find_one(nums: &[i32]) -> Option { for i in 0..nums.len() { // When element 1 is at the head of the array, best time complexity O(1) is achieved // When element 1 is at the tail of the array, worst time complexity O(n) is achieved if nums[i] == 1 { return Some(i); } } None } /* Driver Code */ fn main() { for _ in 0..10 { let n = 100; let nums = random_numbers(n); let index = find_one(&nums).unwrap(); print!("\nArray [ 1, 2, ..., n ] after shuffling = "); print_util::print_array(&nums); println!("\nIndex of number 1 is {}", index); } } ================================================ FILE: en/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs ================================================ /* * File: binary_search_recur.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* Binary search: problem f(i, j) */ fn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 { // If the interval is empty, it means there is no target element, return -1 if i > j { return -1; } let m: i32 = i + (j - i) / 2; if nums[m as usize] < target { // Recursion subproblem f(m+1, j) return dfs(nums, target, m + 1, j); } else if nums[m as usize] > target { // Recursion subproblem f(i, m-1) return dfs(nums, target, i, m - 1); } else { // Found the target element, return its index return m; } } /* Binary search */ fn binary_search(nums: &[i32], target: i32) -> i32 { let n = nums.len() as i32; // Solve the problem f(0, n-1) dfs(nums, target, 0, n - 1) } /* Driver Code */ pub fn main() { let target = 6; let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // Binary search (closed interval on both sides) let index = binary_search(&nums, target); println!("Index of target element 6 is {index}"); } ================================================ FILE: en/codes/rust/chapter_divide_and_conquer/build_tree.rs ================================================ /* * File: build_tree.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, TreeNode}; use std::collections::HashMap; use std::{cell::RefCell, rc::Rc}; /* Build binary tree: divide and conquer */ fn dfs( preorder: &[i32], inorder_map: &HashMap, i: i32, l: i32, r: i32, ) -> Option>> { // Terminate when the subtree interval is empty if r - l < 0 { return None; } // Initialize the root node let root = TreeNode::new(preorder[i as usize]); // Query m to divide the left and right subtrees let m = inorder_map.get(&preorder[i as usize]).unwrap(); // Subproblem: build the left subtree root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1); // Subproblem: build the right subtree root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r); // Return the root node Some(root) } /* Build binary tree */ fn build_tree(preorder: &[i32], inorder: &[i32]) -> Option>> { // Initialize hash map, storing the mapping from inorder elements to indices let mut inorder_map: HashMap = HashMap::new(); for i in 0..inorder.len() { inorder_map.insert(inorder[i], i as i32); } let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1); root } /* Driver Code */ fn main() { let preorder = [3, 9, 2, 1, 7]; let inorder = [9, 3, 1, 2, 7]; println!("In-order traversal = {:?}", preorder); println!("Pre-order traversal = {:?}", inorder); let root = build_tree(&preorder, &inorder); println!("The constructed binary tree is:"); print_util::print_tree(root.as_ref().unwrap()); } ================================================ FILE: en/codes/rust/chapter_divide_and_conquer/hanota.rs ================================================ /* * File: hanota.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ #![allow(non_snake_case)] /* Move a disk */ fn move_pan(src: &mut Vec, tar: &mut Vec) { // Take out a disk from the top of src let pan = src.pop().unwrap(); // Place the disk on top of tar tar.push(pan); } /* Solve the Tower of Hanoi problem f(i) */ fn dfs(i: i32, src: &mut Vec, buf: &mut Vec, tar: &mut Vec) { // If there is only one disk left in src, move it directly to tar if i == 1 { move_pan(src, tar); return; } // Subproblem f(i-1): move the top i-1 disks from src to buf using tar dfs(i - 1, src, tar, buf); // Subproblem f(1): move the remaining disk from src to tar move_pan(src, tar); // Subproblem f(i-1): move the top i-1 disks from buf to tar using src dfs(i - 1, buf, src, tar); } /* Solve the Tower of Hanoi problem */ fn solve_hanota(A: &mut Vec, B: &mut Vec, C: &mut Vec) { let n = A.len() as i32; // Move the top n disks from A to C using B dfs(n, A, B, C); } /* Driver Code */ pub fn main() { let mut A = vec![5, 4, 3, 2, 1]; let mut B = Vec::new(); let mut C = Vec::new(); println!("In initial state:"); println!("A = {:?}", A); println!("B = {:?}", B); println!("C = {:?}", C); solve_hanota(&mut A, &mut B, &mut C); println!("After disk movement is complete:"); println!("A = {:?}", A); println!("B = {:?}", B); println!("C = {:?}", C); } ================================================ FILE: en/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs ================================================ /* * File: climbing_stairs_backtrack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Backtracking */ fn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) { // When climbing to the n-th stair, add 1 to the solution count if state == n { res[0] = res[0] + 1; } // Traverse all choices for &choice in choices { // Pruning: not allowed to go beyond the n-th stair if state + choice > n { continue; } // Attempt: make choice, update state backtrack(choices, state + choice, n, res); // Backtrack } } /* Climbing stairs: Backtracking */ fn climbing_stairs_backtrack(n: usize) -> i32 { let choices = vec![1, 2]; // Can choose to climb up 1 or 2 stairs let state = 0; // Start climbing from the 0-th stair let mut res = Vec::new(); res.push(0); // Use res[0] to record the solution count backtrack(&choices, state, n as i32, &mut res); res[0] } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_backtrack(n); println!("Climbing {n} stairs has {res} solutions"); } ================================================ FILE: en/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs ================================================ /* * File: climbing_stairs_constraint_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Climbing stairs with constraint: Dynamic programming */ fn climbing_stairs_constraint_dp(n: usize) -> i32 { if n == 1 || n == 2 { return 1; }; // Initialize dp table, used to store solutions to subproblems let mut dp = vec![vec![-1; 3]; n + 1]; // Initial state: preset the solution to the smallest subproblem dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // State transition: gradually solve larger subproblems from smaller ones for i in 3..=n { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } dp[n][1] + dp[n][2] } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_constraint_dp(n); println!("Climbing {n} stairs has {res} solutions"); } ================================================ FILE: en/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs ================================================ /* * File: climbing_stairs_dfs.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Search */ fn dfs(i: usize) -> i32 { // Known dp[1] and dp[2], return them if i == 1 || i == 2 { return i as i32; } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i - 1) + dfs(i - 2); count } /* Climbing stairs: Search */ fn climbing_stairs_dfs(n: usize) -> i32 { dfs(n) } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dfs(n); println!("Climbing {n} stairs has {res} solutions"); } ================================================ FILE: en/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs ================================================ /* * File: climbing_stairs_dfs_mem.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Memoization search */ fn dfs(i: usize, mem: &mut [i32]) -> i32 { // Known dp[1] and dp[2], return them if i == 1 || i == 2 { return i as i32; } // If record dp[i] exists, return it directly if mem[i] != -1 { return mem[i]; } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i - 1, mem) + dfs(i - 2, mem); // Record dp[i] mem[i] = count; count } /* Climbing stairs: Memoization search */ fn climbing_stairs_dfs_mem(n: usize) -> i32 { // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record let mut mem = vec![-1; n + 1]; dfs(n, &mut mem) } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dfs_mem(n); println!("Climbing {n} stairs has {res} solutions"); } ================================================ FILE: en/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs ================================================ /* * File: climbing_stairs_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Climbing stairs: Dynamic programming */ fn climbing_stairs_dp(n: usize) -> i32 { // Known dp[1] and dp[2], return them if n == 1 || n == 2 { return n as i32; } // Initialize dp table, used to store solutions to subproblems let mut dp = vec![-1; n + 1]; // Initial state: preset the solution to the smallest subproblem dp[1] = 1; dp[2] = 2; // State transition: gradually solve larger subproblems from smaller ones for i in 3..=n { dp[i] = dp[i - 1] + dp[i - 2]; } dp[n] } /* Climbing stairs: Space-optimized dynamic programming */ fn climbing_stairs_dp_comp(n: usize) -> i32 { if n == 1 || n == 2 { return n as i32; } let (mut a, mut b) = (1, 2); for _ in 3..=n { let tmp = b; b = a + b; a = tmp; } b } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dp(n); println!("Climbing {n} stairs has {res} solutions"); let res = climbing_stairs_dp_comp(n); println!("Climbing {n} stairs has {res} solutions"); } ================================================ FILE: en/codes/rust/chapter_dynamic_programming/coin_change.rs ================================================ /* * File: coin_change.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Coin change: Dynamic programming */ fn coin_change_dp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); let max = amt + 1; // Initialize dp table let mut dp = vec![vec![0; amt + 1]; n + 1]; // State transition: first row and first column for a in 1..=amt { dp[0][a] = max; } // State transition: rest of the rows and columns for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // The smaller value between not selecting and selecting coin i dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1); } } } if dp[n][amt] != max { return dp[n][amt] as i32; } else { -1 } } /* Coin change: Space-optimized dynamic programming */ fn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); let max = amt + 1; // Initialize dp table let mut dp = vec![0; amt + 1]; dp.fill(max); dp[0] = 0; // State transition for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // The smaller value between not selecting and selecting coin i dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1); } } } if dp[amt] != max { return dp[amt] as i32; } else { -1 } } /* Driver Code */ pub fn main() { let coins = [1, 2, 5]; let amt: usize = 4; // Dynamic programming let res = coin_change_dp(&coins, amt); println!("Minimum coins needed to make target amount is {res}"); // Space-optimized dynamic programming let res = coin_change_dp_comp(&coins, amt); println!("Minimum coins needed to make target amount is {res}"); } ================================================ FILE: en/codes/rust/chapter_dynamic_programming/coin_change_ii.rs ================================================ /* * File: coin_change_ii.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Coin change II: Dynamic programming */ fn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); // Initialize dp table let mut dp = vec![vec![0; amt + 1]; n + 1]; // Initialize first column for i in 0..=n { dp[i][0] = 1; } // State transition for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // Sum of the two options: not selecting and selecting coin i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize]; } } } dp[n][amt] } /* Coin change II: Space-optimized dynamic programming */ fn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); // Initialize dp table let mut dp = vec![0; amt + 1]; dp[0] = 1; // State transition for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // Sum of the two options: not selecting and selecting coin i dp[a] = dp[a] + dp[a - coins[i - 1] as usize]; } } } dp[amt] } /* Driver Code */ pub fn main() { let coins = [1, 2, 5]; let amt: usize = 5; // Dynamic programming let res = coin_change_ii_dp(&coins, amt); println!("Number of coin combinations to make target amount is {res}"); // Space-optimized dynamic programming let res = coin_change_ii_dp_comp(&coins, amt); println!("Number of coin combinations to make target amount is {res}"); } ================================================ FILE: en/codes/rust/chapter_dynamic_programming/edit_distance.rs ================================================ /* * File: edit_distance.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Edit distance: Brute-force search */ fn edit_distance_dfs(s: &str, t: &str, i: usize, j: usize) -> i32 { // If both s and t are empty, return 0 if i == 0 && j == 0 { return 0; } // If s is empty, return length of t if i == 0 { return j as i32; } // If t is empty, return length of s if j == 0 { return i as i32; } // If two characters are equal, skip both characters if s.chars().nth(i - 1) == t.chars().nth(j - 1) { return edit_distance_dfs(s, t, i - 1, j - 1); } // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 let insert = edit_distance_dfs(s, t, i, j - 1); let delete = edit_distance_dfs(s, t, i - 1, j); let replace = edit_distance_dfs(s, t, i - 1, j - 1); // Return minimum edit steps std::cmp::min(std::cmp::min(insert, delete), replace) + 1 } /* Edit distance: Memoization search */ fn edit_distance_dfs_mem(s: &str, t: &str, mem: &mut Vec>, i: usize, j: usize) -> i32 { // If both s and t are empty, return 0 if i == 0 && j == 0 { return 0; } // If s is empty, return length of t if i == 0 { return j as i32; } // If t is empty, return length of s if j == 0 { return i as i32; } // If there's a record, return it directly if mem[i][j] != -1 { return mem[i][j]; } // If two characters are equal, skip both characters if s.chars().nth(i - 1) == t.chars().nth(j - 1) { return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); } // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 let insert = edit_distance_dfs_mem(s, t, mem, i, j - 1); let delete = edit_distance_dfs_mem(s, t, mem, i - 1, j); let replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); // Record and return minimum edit steps mem[i][j] = std::cmp::min(std::cmp::min(insert, delete), replace) + 1; mem[i][j] } /* Edit distance: Dynamic programming */ fn edit_distance_dp(s: &str, t: &str) -> i32 { let (n, m) = (s.len(), t.len()); let mut dp = vec![vec![0; m + 1]; n + 1]; // State transition: first row and first column for i in 1..=n { dp[i][0] = i as i32; } for j in 1..m { dp[0][j] = j as i32; } // State transition: rest of the rows and columns for i in 1..=n { for j in 1..=m { if s.chars().nth(i - 1) == t.chars().nth(j - 1) { // If two characters are equal, skip both characters dp[i][j] = dp[i - 1][j - 1]; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[i][j] = std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } dp[n][m] } /* Edit distance: Space-optimized dynamic programming */ fn edit_distance_dp_comp(s: &str, t: &str) -> i32 { let (n, m) = (s.len(), t.len()); let mut dp = vec![0; m + 1]; // State transition: first row for j in 1..m { dp[j] = j as i32; } // State transition: rest of the rows for i in 1..=n { // State transition: first column let mut leftup = dp[0]; // Temporarily store dp[i-1, j-1] dp[0] = i as i32; // State transition: rest of the columns for j in 1..=m { let temp = dp[j]; if s.chars().nth(i - 1) == t.chars().nth(j - 1) { // If two characters are equal, skip both characters dp[j] = leftup; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // Update for next round's dp[i-1, j-1] } } dp[m] } /* Driver Code */ pub fn main() { let s = "bag"; let t = "pack"; let (n, m) = (s.len(), t.len()); // Brute-force search let res = edit_distance_dfs(s, t, n, m); println!("Changing {s} to {t} requires minimum {res} edits"); // Memoization search let mut mem = vec![vec![0; m + 1]; n + 1]; for row in mem.iter_mut() { row.fill(-1); } let res = edit_distance_dfs_mem(s, t, &mut mem, n, m); println!("Changing {s} to {t} requires minimum {res} edits"); // Dynamic programming let res = edit_distance_dp(s, t); println!("Changing {s} to {t} requires minimum {res} edits"); // Space-optimized dynamic programming let res = edit_distance_dp_comp(s, t); println!("Changing {s} to {t} requires minimum {res} edits"); } ================================================ FILE: en/codes/rust/chapter_dynamic_programming/knapsack.rs ================================================ /* * File: knapsack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 0-1 knapsack: Brute-force search */ fn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 { // If all items have been selected or knapsack has no remaining capacity, return value 0 if i == 0 || c == 0 { return 0; } // If exceeds knapsack capacity, can only choose not to put it in if wgt[i - 1] > c as i32 { return knapsack_dfs(wgt, val, i - 1, c); } // Calculate the maximum value of not putting in and putting in item i let no = knapsack_dfs(wgt, val, i - 1, c); let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; // Return the larger value of the two options std::cmp::max(no, yes) } /* 0-1 knapsack: Memoization search */ fn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec>, i: usize, c: usize) -> i32 { // If all items have been selected or knapsack has no remaining capacity, return value 0 if i == 0 || c == 0 { return 0; } // If there's a record, return it directly if mem[i][c] != -1 { return mem[i][c]; } // If exceeds knapsack capacity, can only choose not to put it in if wgt[i - 1] > c as i32 { return knapsack_dfs_mem(wgt, val, mem, i - 1, c); } // Calculate the maximum value of not putting in and putting in item i let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c); let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; // Record and return the larger value of the two options mem[i][c] = std::cmp::max(no, yes); mem[i][c] } /* 0-1 knapsack: Dynamic programming */ fn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // Initialize dp table let mut dp = vec![vec![0; cap + 1]; n + 1]; // State transition for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item i dp[i][c] = std::cmp::max( dp[i - 1][c], dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1], ); } } } dp[n][cap] } /* 0-1 knapsack: Space-optimized dynamic programming */ fn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // Initialize dp table let mut dp = vec![0; cap + 1]; // State transition for i in 1..=n { // Traverse in reverse order for c in (1..=cap).rev() { if wgt[i - 1] <= c as i32 { // The larger value between not selecting and selecting item i dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); } } } dp[cap] } /* Driver Code */ pub fn main() { let wgt = [10, 20, 30, 40, 50]; let val = [50, 120, 150, 210, 240]; let cap: usize = 50; let n = wgt.len(); // Brute-force search let res = knapsack_dfs(&wgt, &val, n, cap); println!("Maximum item value not exceeding knapsack capacity is {res}"); // Memoization search let mut mem = vec![vec![0; cap + 1]; n + 1]; for row in mem.iter_mut() { row.fill(-1); } let res = knapsack_dfs_mem(&wgt, &val, &mut mem, n, cap); println!("Maximum item value not exceeding knapsack capacity is {res}"); // Dynamic programming let res = knapsack_dp(&wgt, &val, cap); println!("Maximum item value not exceeding knapsack capacity is {res}"); // Space-optimized dynamic programming let res = knapsack_dp_comp(&wgt, &val, cap); println!("Maximum item value not exceeding knapsack capacity is {res}"); } ================================================ FILE: en/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs ================================================ /* * File: min_cost_climbing_stairs_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use std::cmp; /* Minimum cost climbing stairs: Dynamic programming */ fn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 { let n = cost.len() - 1; if n == 1 || n == 2 { return cost[n]; } // Initialize dp table, used to store solutions to subproblems let mut dp = vec![-1; n + 1]; // Initial state: preset the solution to the smallest subproblem dp[1] = cost[1]; dp[2] = cost[2]; // State transition: gradually solve larger subproblems from smaller ones for i in 3..=n { dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i]; } dp[n] } /* Minimum cost climbing stairs: Space-optimized dynamic programming */ fn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 { let n = cost.len() - 1; if n == 1 || n == 2 { return cost[n]; }; let (mut a, mut b) = (cost[1], cost[2]); for i in 3..=n { let tmp = b; b = cmp::min(a, tmp) + cost[i]; a = tmp; } b } /* Driver Code */ pub fn main() { let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; println!("Input stair cost list is {:?}", &cost); let res = min_cost_climbing_stairs_dp(&cost); println!("Minimum cost to climb stairs is {res}"); let res = min_cost_climbing_stairs_dp_comp(&cost); println!("Minimum cost to climb stairs is {res}"); } ================================================ FILE: en/codes/rust/chapter_dynamic_programming/min_path_sum.rs ================================================ /* * File: min_path_sum.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Minimum path sum: Brute-force search */ fn min_path_sum_dfs(grid: &Vec>, i: i32, j: i32) -> i32 { // If it's the top-left cell, terminate the search if i == 0 && j == 0 { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if i < 0 || j < 0 { return i32::MAX; } // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1) let up = min_path_sum_dfs(grid, i - 1, j); let left = min_path_sum_dfs(grid, i, j - 1); // Return the minimum path cost from top-left to (i, j) std::cmp::min(left, up) + grid[i as usize][j as usize] } /* Minimum path sum: Memoization search */ fn min_path_sum_dfs_mem(grid: &Vec>, mem: &mut Vec>, i: i32, j: i32) -> i32 { // If it's the top-left cell, terminate the search if i == 0 && j == 0 { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if i < 0 || j < 0 { return i32::MAX; } // If there's a record, return it directly if mem[i as usize][j as usize] != -1 { return mem[i as usize][j as usize]; } // Minimum path cost for left and upper cells let up = min_path_sum_dfs_mem(grid, mem, i - 1, j); let left = min_path_sum_dfs_mem(grid, mem, i, j - 1); // Record and return the minimum path cost from top-left to (i, j) mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize]; mem[i as usize][j as usize] } /* Minimum path sum: Dynamic programming */ fn min_path_sum_dp(grid: &Vec>) -> i32 { let (n, m) = (grid.len(), grid[0].len()); // Initialize dp table let mut dp = vec![vec![0; m]; n]; dp[0][0] = grid[0][0]; // State transition: first row for j in 1..m { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // State transition: first column for i in 1..n { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // State transition: rest of the rows and columns for i in 1..n { for j in 1..m { dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } dp[n - 1][m - 1] } /* Minimum path sum: Space-optimized dynamic programming */ fn min_path_sum_dp_comp(grid: &Vec>) -> i32 { let (n, m) = (grid.len(), grid[0].len()); // Initialize dp table let mut dp = vec![0; m]; // State transition: first row dp[0] = grid[0][0]; for j in 1..m { dp[j] = dp[j - 1] + grid[0][j]; } // State transition: rest of the rows for i in 1..n { // State transition: first column dp[0] = dp[0] + grid[i][0]; // State transition: rest of the columns for j in 1..m { dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j]; } } dp[m - 1] } /* Driver Code */ pub fn main() { let grid = vec![ vec![1, 3, 1, 5], vec![2, 2, 4, 2], vec![5, 3, 2, 1], vec![4, 3, 5, 2], ]; let (n, m) = (grid.len(), grid[0].len()); // Brute-force search let res = min_path_sum_dfs(&grid, n as i32 - 1, m as i32 - 1); println!("Minimum path sum from top-left to bottom-right is {res}"); // Memoization search let mut mem = vec![vec![0; m]; n]; for row in mem.iter_mut() { row.fill(-1); } let res = min_path_sum_dfs_mem(&grid, &mut mem, n as i32 - 1, m as i32 - 1); println!("Minimum path sum from top-left to bottom-right is {res}"); // Dynamic programming let res = min_path_sum_dp(&grid); println!("Minimum path sum from top-left to bottom-right is {res}"); // Space-optimized dynamic programming let res = min_path_sum_dp_comp(&grid); println!("Minimum path sum from top-left to bottom-right is {res}"); } ================================================ FILE: en/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs ================================================ /* * File: unbounded_knapsack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Unbounded knapsack: Dynamic programming */ fn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // Initialize dp table let mut dp = vec![vec![0; cap + 1]; n + 1]; // State transition for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item i dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]); } } } return dp[n][cap]; } /* Unbounded knapsack: Space-optimized dynamic programming */ fn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // Initialize dp table let mut dp = vec![0; cap + 1]; // State transition for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // If exceeds knapsack capacity, don't select item i dp[c] = dp[c]; } else { // The larger value between not selecting and selecting item i dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); } } } dp[cap] } /* Driver Code */ pub fn main() { let wgt = [1, 2, 3]; let val = [5, 11, 15]; let cap: usize = 4; // Dynamic programming let res = unbounded_knapsack_dp(&wgt, &val, cap); println!("Maximum item value not exceeding knapsack capacity is {res}"); // Space-optimized dynamic programming let res = unbounded_knapsack_dp_comp(&wgt, &val, cap); println!("Maximum item value not exceeding knapsack capacity is {res}"); } ================================================ FILE: en/codes/rust/chapter_graph/graph_adjacency_list.rs ================================================ /* * File: graph_adjacency_list.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ pub use hello_algo_rust::include::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::HashMap; /* Undirected graph type based on adjacency list */ pub struct GraphAdjList { // Adjacency list, key: vertex, value: all adjacent vertices of that vertex pub adj_list: HashMap>, // maybe HashSet for value part is better? } impl GraphAdjList { /* Constructor */ pub fn new(edges: Vec<[Vertex; 2]>) -> Self { let mut graph = GraphAdjList { adj_list: HashMap::new(), }; // Add all vertices and edges for edge in edges { graph.add_vertex(edge[0]); graph.add_vertex(edge[1]); graph.add_edge(edge[0], edge[1]); } graph } /* Get the number of vertices */ #[allow(unused)] pub fn size(&self) -> usize { self.adj_list.len() } /* Add edge */ pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) { if vet1 == vet2 { panic!("value error"); } // Add edge vet1 - vet2 self.adj_list.entry(vet1).or_default().push(vet2); self.adj_list.entry(vet2).or_default().push(vet1); } /* Remove edge */ #[allow(unused)] pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) { if vet1 == vet2 { panic!("value error"); } // Remove edge vet1 - vet2 self.adj_list .entry(vet1) .and_modify(|v| v.retain(|&e| e != vet2)); self.adj_list .entry(vet2) .and_modify(|v| v.retain(|&e| e != vet1)); } /* Add vertex */ pub fn add_vertex(&mut self, vet: Vertex) { if self.adj_list.contains_key(&vet) { return; } // Add a new linked list in the adjacency list self.adj_list.insert(vet, vec![]); } /* Remove vertex */ #[allow(unused)] pub fn remove_vertex(&mut self, vet: Vertex) { // Remove the linked list corresponding to vertex vet in the adjacency list self.adj_list.remove(&vet); // Traverse the linked lists of other vertices and remove all edges containing vet for list in self.adj_list.values_mut() { list.retain(|&v| v != vet); } } /* Print adjacency list */ pub fn print(&self) { println!("Adjacency list ="); for (vertex, list) in &self.adj_list { let list = list.iter().map(|vertex| vertex.val).collect::>(); println!("{}: {:?},", vertex.val, list); } } } /* Driver Code */ #[allow(unused)] fn main() { /* Add edge */ let v = vals_to_vets(vec![1, 3, 2, 5, 4]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ]; let mut graph = GraphAdjList::new(edges); println!("\nAfter initialization, graph is"); graph.print(); /* Add edge */ // Vertices 1, 3 are v[0], v[1] graph.add_edge(v[0], v[2]); println!("\nAfter adding edge 1-2, graph is"); graph.print(); /* Remove edge */ // Vertex 3 is v[1] graph.remove_edge(v[0], v[1]); println!("\nAfter removing edge 1-3, graph is"); graph.print(); /* Add vertex */ let v5 = Vertex { val: 6 }; graph.add_vertex(v5); println!("\nAfter adding vertex 6, graph is"); graph.print(); /* Remove vertex */ // Vertex 3 is v[1] graph.remove_vertex(v[1]); println!("\nAfter removing vertex 3, graph is"); graph.print(); } ================================================ FILE: en/codes/rust/chapter_graph/graph_adjacency_matrix.rs ================================================ /* * File: graph_adjacency_matrix.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ /* Undirected graph type based on adjacency matrix */ pub struct GraphAdjMat { // Vertex list, where the element represents the "vertex value" and the index represents the "vertex index" pub vertices: Vec, // Adjacency matrix, where the row and column indices correspond to the "vertex index" pub adj_mat: Vec>, } impl GraphAdjMat { /* Constructor */ pub fn new(vertices: Vec, edges: Vec<[usize; 2]>) -> Self { let mut graph = GraphAdjMat { vertices: vec![], adj_mat: vec![], }; // Add vertex for val in vertices { graph.add_vertex(val); } // Add edge // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices for edge in edges { graph.add_edge(edge[0], edge[1]) } graph } /* Get the number of vertices */ pub fn size(&self) -> usize { self.vertices.len() } /* Add vertex */ pub fn add_vertex(&mut self, val: i32) { let n = self.size(); // Add the value of the new vertex to the vertex list self.vertices.push(val); // Add a row to the adjacency matrix self.adj_mat.push(vec![0; n]); // Add a column to the adjacency matrix for row in self.adj_mat.iter_mut() { row.push(0); } } /* Remove vertex */ pub fn remove_vertex(&mut self, index: usize) { if index >= self.size() { panic!("index error") } // Remove the vertex at index from the vertex list self.vertices.remove(index); // Remove the row at index from the adjacency matrix self.adj_mat.remove(index); // Remove the column at index from the adjacency matrix for row in self.adj_mat.iter_mut() { row.remove(index); } } /* Add edge */ pub fn add_edge(&mut self, i: usize, j: usize) { // Parameters i, j correspond to the vertices element indices // Handle index out of bounds and equality if i >= self.size() || j >= self.size() || i == j { panic!("index error") } // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i) self.adj_mat[i][j] = 1; self.adj_mat[j][i] = 1; } /* Remove edge */ // Parameters i, j correspond to the vertices element indices pub fn remove_edge(&mut self, i: usize, j: usize) { // Parameters i, j correspond to the vertices element indices // Handle index out of bounds and equality if i >= self.size() || j >= self.size() || i == j { panic!("index error") } self.adj_mat[i][j] = 0; self.adj_mat[j][i] = 0; } /* Print adjacency matrix */ pub fn print(&self) { println!("Vertex list = {:?}", self.vertices); println!("Adjacency matrix ="); println!("["); for row in &self.adj_mat { println!(" {:?},", row); } println!("]") } } /* Driver Code */ fn main() { /* Add edge */ // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices let vertices = vec![1, 3, 2, 5, 4]; let edges = vec![[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]]; let mut graph = GraphAdjMat::new(vertices, edges); println!("\nAfter initialization, graph is"); graph.print(); /* Add edge */ // Add vertex graph.add_edge(0, 2); println!("\nAfter adding edge 1-2, graph is"); graph.print(); /* Remove edge */ // Vertices 1, 3 have indices 0, 1 respectively graph.remove_edge(0, 1); println!("\nAfter removing edge 1-3, graph is"); graph.print(); /* Add vertex */ graph.add_vertex(6); println!("\nAfter adding vertex 6, graph is"); graph.print(); /* Remove vertex */ // Vertex 3 has index 1 graph.remove_vertex(1); println!("\nAfter removing vertex 3, graph is"); graph.print(); } ================================================ FILE: en/codes/rust/chapter_graph/graph_bfs.rs ================================================ /* * File: graph_bfs.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ mod graph_adjacency_list; use graph_adjacency_list::GraphAdjList; use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::{HashSet, VecDeque}; /* Breadth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { // Vertex traversal sequence let mut res = vec![]; // Hash set for recording vertices that have been visited let mut visited = HashSet::new(); visited.insert(start_vet); // Queue used to implement BFS let mut que = VecDeque::new(); que.push_back(start_vet); // Starting from vertex vet, loop until all vertices are visited while let Some(vet) = que.pop_front() { res.push(vet); // Record visited vertex // Traverse all adjacent vertices of this vertex if let Some(adj_vets) = graph.adj_list.get(&vet) { for &adj_vet in adj_vets { if visited.contains(&adj_vet) { continue; // Skip vertices that have been visited } que.push_back(adj_vet); // Only enqueue unvisited vertices visited.insert(adj_vet); // Mark this vertex as visited } } } // Return vertex traversal sequence res } /* Driver Code */ fn main() { /* Add edge */ let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; let graph = GraphAdjList::new(edges); println!("\nAfter initialization, graph is"); graph.print(); /* Breadth-first traversal */ let res = graph_bfs(graph, v[0]); println!("\nBreadth-first traversal (BFS) vertex sequence is"); println!("{:?}", vets_to_vals(res)); } ================================================ FILE: en/codes/rust/chapter_graph/graph_dfs.rs ================================================ /* * File: graph_dfs.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ mod graph_adjacency_list; use graph_adjacency_list::GraphAdjList; use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::HashSet; /* Depth-first traversal helper function */ fn dfs(graph: &GraphAdjList, visited: &mut HashSet, res: &mut Vec, vet: Vertex) { res.push(vet); // Record visited vertex visited.insert(vet); // Mark this vertex as visited // Traverse all adjacent vertices of this vertex if let Some(adj_vets) = graph.adj_list.get(&vet) { for &adj_vet in adj_vets { if visited.contains(&adj_vet) { continue; // Skip vertices that have been visited } // Recursively visit adjacent vertices dfs(graph, visited, res, adj_vet); } } } /* Depth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { // Vertex traversal sequence let mut res = vec![]; // Hash set for recording vertices that have been visited let mut visited = HashSet::new(); dfs(&graph, &mut visited, &mut res, start_vet); res } /* Driver Code */ fn main() { /* Add edge */ let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; let graph = GraphAdjList::new(edges); println!("\nAfter initialization, graph is"); graph.print(); /* Depth-first traversal */ let res = graph_dfs(graph, v[0]); println!("\nDepth-first traversal (DFS) vertex sequence is"); println!("{:?}", vets_to_vals(res)); } ================================================ FILE: en/codes/rust/chapter_greedy/coin_change_greedy.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* Coin change: Greedy algorithm */ fn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 { // Assume coins list is sorted let mut i = coins.len() - 1; let mut count = 0; // Loop to make greedy choices until no remaining amount while amt > 0 { // Find the coin that is less than and closest to the remaining amount while i > 0 && coins[i] > amt { i -= 1; } // Choose coins[i] amt -= coins[i]; count += 1; } // If no feasible solution is found, return -1 if amt == 0 { count } else { -1 } } /* Driver Code */ fn main() { // Greedy algorithm: Can guarantee finding the global optimal solution let coins = [1, 5, 10, 20, 50, 100]; let amt = 186; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("Minimum coins needed to make {} is {}", amt, res); // Greedy algorithm: Cannot guarantee finding the global optimal solution let coins = [1, 20, 50]; let amt = 60; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("Minimum coins needed to make {} is {}", amt, res); println!("Actually the minimum number needed is 3, i.e., 20 + 20 + 20"); // Greedy algorithm: Cannot guarantee finding the global optimal solution let coins = [1, 49, 50]; let amt = 98; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("Minimum coins needed to make {} is {}", amt, res); println!("Actually the minimum number needed is 2, i.e., 49 + 49"); } ================================================ FILE: en/codes/rust/chapter_greedy/fractional_knapsack.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* Item */ struct Item { w: i32, // Item weight v: i32, // Item value } impl Item { fn new(w: i32, v: i32) -> Self { Self { w, v } } } /* Fractional knapsack: Greedy algorithm */ fn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 { // Create item list with two attributes: weight, value let mut items = wgt .iter() .zip(val.iter()) .map(|(&w, &v)| Item::new(w, v)) .collect::>(); // Sort by unit value item.v / item.w from high to low items.sort_by(|a, b| { (b.v as f64 / b.w as f64) .partial_cmp(&(a.v as f64 / a.w as f64)) .unwrap() }); // Loop for greedy selection let mut res = 0.0; for item in &items { if item.w <= cap { // If remaining capacity is sufficient, put the entire current item into the knapsack res += item.v as f64; cap -= item.w; } else { // If remaining capacity is insufficient, put part of the current item into the knapsack res += item.v as f64 / item.w as f64 * cap as f64; // No remaining capacity, so break out of the loop break; } } res } /* Driver Code */ fn main() { let wgt = [10, 20, 30, 40, 50]; let val = [50, 120, 150, 210, 240]; let cap = 50; // Greedy algorithm let res = fractional_knapsack(&wgt, &val, cap); println!("Maximum item value not exceeding knapsack capacity is {}", res); } ================================================ FILE: en/codes/rust/chapter_greedy/max_capacity.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* Max capacity: Greedy algorithm */ fn max_capacity(ht: &[i32]) -> i32 { // Initialize i, j to be at both ends of the array let mut i = 0; let mut j = ht.len() - 1; // Initial max capacity is 0 let mut res = 0; // Loop for greedy selection until the two boards meet while i < j { // Update max capacity let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32; res = std::cmp::max(res, cap); // Move the shorter board inward if ht[i] < ht[j] { i += 1; } else { j -= 1; } } res } /* Driver Code */ fn main() { let ht = [3, 8, 5, 2, 7, 7, 3, 4]; // Greedy algorithm let res = max_capacity(&ht); println!("Maximum capacity is {}", res); } ================================================ FILE: en/codes/rust/chapter_greedy/max_product_cutting.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* Max product cutting: Greedy algorithm */ fn max_product_cutting(n: i32) -> i32 { // When n <= 3, must cut out a 1 if n <= 3 { return 1 * (n - 1); } // Greedily cut out 3, a is the number of 3s, b is the remainder let a = n / 3; let b = n % 3; if b == 1 { // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2 3_i32.pow(a as u32 - 1) * 2 * 2 } else if b == 2 { // When the remainder is 2, do nothing 3_i32.pow(a as u32) * 2 } else { // When the remainder is 0, do nothing 3_i32.pow(a as u32) } } /* Driver Code */ fn main() { let n = 58; // Greedy algorithm let res = max_product_cutting(n); println!("Maximum cutting product is {}", res); } ================================================ FILE: en/codes/rust/chapter_hashing/array_hash_map.rs ================================================ /** * File: array_hash_map.rs * Created Time: 2023-2-18 * Author: xBLACICEx (xBLACKICEx@outlook.com) */ /* Key-value pair */ #[derive(Debug, Clone, PartialEq)] pub struct Pair { pub key: i32, pub val: String, } /* Hash table based on array implementation */ pub struct ArrayHashMap { buckets: Vec>, } impl ArrayHashMap { pub fn new() -> ArrayHashMap { // Initialize array with 100 buckets Self { buckets: vec![None; 100], } } /* Hash function */ fn hash_func(&self, key: i32) -> usize { key as usize % 100 } /* Query operation */ pub fn get(&self, key: i32) -> Option<&String> { let index = self.hash_func(key); self.buckets[index].as_ref().map(|pair| &pair.val) } /* Add operation */ pub fn put(&mut self, key: i32, val: &str) { let index = self.hash_func(key); self.buckets[index] = Some(Pair { key, val: val.to_string(), }); } /* Remove operation */ pub fn remove(&mut self, key: i32) { let index = self.hash_func(key); // Set to None to represent removal self.buckets[index] = None; } /* Get all key-value pairs */ pub fn entry_set(&self) -> Vec<&Pair> { self.buckets .iter() .filter_map(|pair| pair.as_ref()) .collect() } /* Get all keys */ pub fn key_set(&self) -> Vec<&i32> { self.buckets .iter() .filter_map(|pair| pair.as_ref().map(|pair| &pair.key)) .collect() } /* Get all values */ pub fn value_set(&self) -> Vec<&String> { self.buckets .iter() .filter_map(|pair| pair.as_ref().map(|pair| &pair.val)) .collect() } /* Print hash table */ pub fn print(&self) { for pair in self.entry_set() { println!("{} -> {}", pair.key, pair.val); } } } fn main() { /* Initialize hash table */ let mut map = ArrayHashMap::new(); /* Add operation */ // Add key-value pair (key, value) to hash table map.put(12836, "Xiao Ha"); map.put(15937, "Xiao Luo"); map.put(16750, "Xiao Suan"); map.put(13276, "Xiao Fa"); map.put(10583, "Xiao Ya"); println!("\nAfter adding is complete, hash table is\nKey -> Value"); map.print(); /* Query operation */ // Input key into hash table to get value let name = map.get(15937).unwrap(); println!("\nInput student ID 15937, found name {}", name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(10583); println!("\nAfter removing 10583, hash table is\nKey -> Value"); map.print(); /* Traverse hash table */ println!("\nTraverse key-value pairs Key->Value"); for pair in map.entry_set() { println!("{} -> {}", pair.key, pair.val); } println!("\nTraverse keys only Key"); for key in map.key_set() { println!("{}", key); } println!("\nTraverse values only Value"); for val in map.value_set() { println!("{}", val); } } ================================================ FILE: en/codes/rust/chapter_hashing/build_in_hash.rs ================================================ /* * File: build_in_hash.rs * Created Time: 2023-7-6 * Author: WSL0809 (wslzzy@outlook.com) */ use hello_algo_rust::include::ListNode; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; /* Driver Code */ fn main() { let num = 3; let mut num_hasher = DefaultHasher::new(); num.hash(&mut num_hasher); let hash_num = num_hasher.finish(); println!("Hash value of integer {} is {}", num, hash_num); let bol = true; let mut bol_hasher = DefaultHasher::new(); bol.hash(&mut bol_hasher); let hash_bol = bol_hasher.finish(); println!("Hash value of boolean {} is {}", bol, hash_bol); let dec: f32 = 3.14159; let mut dec_hasher = DefaultHasher::new(); dec.to_bits().hash(&mut dec_hasher); let hash_dec = dec_hasher.finish(); println!("Hash value of decimal {} is {}", dec, hash_dec); let str = "Hello Algo"; let mut str_hasher = DefaultHasher::new(); str.hash(&mut str_hasher); let hash_str = str_hasher.finish(); println!("Hash value of string {} is {}", str, hash_str); let arr = (&12836, &"Xiao Ha"); let mut tup_hasher = DefaultHasher::new(); arr.hash(&mut tup_hasher); let hash_tup = tup_hasher.finish(); println!("Hash value of tuple {:?} is {}", arr, hash_tup); let node = ListNode::new(42); let mut hasher = DefaultHasher::new(); node.borrow().val.hash(&mut hasher); let hash = hasher.finish(); println!("Hash value of node object {:?} is {}", node, hash); } ================================================ FILE: en/codes/rust/chapter_hashing/hash_map.rs ================================================ /* * File: hash_map.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::collections::HashMap; /* Driver Code */ pub fn main() { // Initialize hash table let mut map = HashMap::new(); // Add operation // Add key-value pair (key, value) to the hash table map.insert(12836, "Xiao Ha"); map.insert(15937, "Xiao Luo"); map.insert(16750, "Xiao Suan"); map.insert(13276, "Xiao Fa"); map.insert(10583, "Xiao Ya"); println!("\nAfter adding is complete, hash table is\nKey -> Value"); print_util::print_hash_map(&map); // Query operation // Input key into hash table to get value let name = map.get(&15937).copied().unwrap(); println!("\nInput student ID 15937, found name {name}"); // Remove operation // Remove key-value pair (key, value) from hash table _ = map.remove(&10583); println!("\nAfter removing 10583, hash table is\nKey -> Value"); print_util::print_hash_map(&map); // Traverse hash table println!("\nTraverse key-value pairs Key->Value"); print_util::print_hash_map(&map); println!("\nTraverse keys only Key"); for key in map.keys() { println!("{key}"); } println!("\nTraverse values separately"); for value in map.values() { println!("{value}"); } } ================================================ FILE: en/codes/rust/chapter_hashing/hash_map_chaining.rs ================================================ /* * File: hash_map_chaining.rs * Created Time: 2023-07-07 * Author: WSL0809 (wslzzy@outlook.com) */ #[derive(Clone)] /* Key-value pair */ struct Pair { key: i32, val: String, } /* Hash table with separate chaining */ struct HashMapChaining { size: usize, capacity: usize, load_thres: f32, extend_ratio: usize, buckets: Vec>, } impl HashMapChaining { /* Constructor */ fn new() -> Self { Self { size: 0, capacity: 4, load_thres: 2.0 / 3.0, extend_ratio: 2, buckets: vec![vec![]; 4], } } /* Hash function */ fn hash_func(&self, key: i32) -> usize { key as usize % self.capacity } /* Load factor */ fn load_factor(&self) -> f32 { self.size as f32 / self.capacity as f32 } /* Remove operation */ fn remove(&mut self, key: i32) -> Option { let index = self.hash_func(key); // Traverse bucket and remove key-value pair from it for (i, p) in self.buckets[index].iter_mut().enumerate() { if p.key == key { let pair = self.buckets[index].remove(i); self.size -= 1; return Some(pair.val); } } // If key is not found, return None None } /* Expand hash table */ fn extend(&mut self) { // Temporarily store the original hash table let buckets_tmp = std::mem::take(&mut self.buckets); // Initialize expanded new hash table self.capacity *= self.extend_ratio; self.buckets = vec![Vec::new(); self.capacity as usize]; self.size = 0; // Move key-value pairs from original hash table to new hash table for bucket in buckets_tmp { for pair in bucket { self.put(pair.key, pair.val); } } } /* Print hash table */ fn print(&self) { for bucket in &self.buckets { let mut res = Vec::new(); for pair in bucket { res.push(format!("{} -> {}", pair.key, pair.val)); } println!("{:?}", res); } } /* Add operation */ fn put(&mut self, key: i32, val: String) { // When load factor exceeds threshold, perform expansion if self.load_factor() > self.load_thres { self.extend(); } let index = self.hash_func(key); // Traverse bucket, if specified key is encountered, update corresponding val and return for pair in self.buckets[index].iter_mut() { if pair.key == key { pair.val = val; return; } } // If key does not exist, append key-value pair to the end let pair = Pair { key, val }; self.buckets[index].push(pair); self.size += 1; } /* Query operation */ fn get(&self, key: i32) -> Option<&str> { let index = self.hash_func(key); // Traverse bucket, if key is found, return corresponding val for pair in self.buckets[index].iter() { if pair.key == key { return Some(&pair.val); } } // If key is not found, return None None } } /* Driver Code */ pub fn main() { /* Initialize hash table */ let mut map = HashMapChaining::new(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(12836, "Xiao Ha".to_string()); map.put(15937, "Xiao Luo".to_string()); map.put(16750, "Xiao Suan".to_string()); map.put(13276, "Xiao Fa".to_string()); map.put(10583, "Xiao Ya".to_string()); println!("\nAfter adding is complete, hash table is\nKey -> Value"); map.print(); /* Query operation */ // Input key into hash table to get value println!( "\nInput student ID 13276, found name {}", match map.get(13276) { Some(value) => value, None => "Not a valid Key", } ); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(12836); println!("\nAfter removing 12836, hash table is\nKey -> Value"); map.print(); } ================================================ FILE: en/codes/rust/chapter_hashing/hash_map_open_addressing.rs ================================================ /* * File: hash_map_open_addressing.rs * Created Time: 2023-07-16 * Author: WSL0809 (wslzzy@outlook.com), night-cruise (2586447362@qq.com) */ #![allow(non_snake_case)] #![allow(unused)] mod array_hash_map; use array_hash_map::Pair; /* Hash table with open addressing */ struct HashMapOpenAddressing { size: usize, // Number of key-value pairs capacity: usize, // Hash table capacity load_thres: f64, // Load factor threshold for triggering expansion extend_ratio: usize, // Expansion multiplier buckets: Vec>, // Bucket array TOMBSTONE: Option, // Removal marker } impl HashMapOpenAddressing { /* Constructor */ fn new() -> Self { Self { size: 0, capacity: 4, load_thres: 2.0 / 3.0, extend_ratio: 2, buckets: vec![None; 4], TOMBSTONE: Some(Pair { key: -1, val: "-1".to_string(), }), } } /* Hash function */ fn hash_func(&self, key: i32) -> usize { (key % self.capacity as i32) as usize } /* Load factor */ fn load_factor(&self) -> f64 { self.size as f64 / self.capacity as f64 } /* Search for bucket index corresponding to key */ fn find_bucket(&mut self, key: i32) -> usize { let mut index = self.hash_func(key); let mut first_tombstone = -1; // Linear probing, break when encountering an empty bucket while self.buckets[index].is_some() { // If key is found, return corresponding bucket index if self.buckets[index].as_ref().unwrap().key == key { // If deletion marker was encountered before, move key-value pair to that index if first_tombstone != -1 { self.buckets[first_tombstone as usize] = self.buckets[index].take(); self.buckets[index] = self.TOMBSTONE.clone(); return first_tombstone as usize; // Return the moved bucket index } return index; // Return bucket index } // Record the first removal marker encountered if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE { first_tombstone = index as i32; } // Calculate bucket index, wrap around to the head if past the tail index = (index + 1) % self.capacity; } // If key does not exist, return the index for insertion if first_tombstone == -1 { index } else { first_tombstone as usize } } /* Query operation */ fn get(&mut self, key: i32) -> Option<&str> { // Search for bucket index corresponding to key let index = self.find_bucket(key); // If key-value pair is found, return corresponding val if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { return self.buckets[index].as_ref().map(|pair| &pair.val as &str); } // If key-value pair does not exist, return null None } /* Add operation */ fn put(&mut self, key: i32, val: String) { // When load factor exceeds threshold, perform expansion if self.load_factor() > self.load_thres { self.extend(); } // Search for bucket index corresponding to key let index = self.find_bucket(key); // If key-value pair is found, overwrite val and return if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { self.buckets[index].as_mut().unwrap().val = val; return; } // If key-value pair does not exist, add the key-value pair self.buckets[index] = Some(Pair { key, val }); self.size += 1; } /* Remove operation */ fn remove(&mut self, key: i32) { // Search for bucket index corresponding to key let index = self.find_bucket(key); // If key-value pair is found, overwrite it with removal marker if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { self.buckets[index] = self.TOMBSTONE.clone(); self.size -= 1; } } /* Expand hash table */ fn extend(&mut self) { // Temporarily store the original hash table let buckets_tmp = self.buckets.clone(); // Initialize expanded new hash table self.capacity *= self.extend_ratio; self.buckets = vec![None; self.capacity]; self.size = 0; // Move key-value pairs from original hash table to new hash table for pair in buckets_tmp { if pair.is_none() || pair == self.TOMBSTONE { continue; } let pair = pair.unwrap(); self.put(pair.key, pair.val); } } /* Print hash table */ fn print(&self) { for pair in &self.buckets { if pair.is_none() { println!("null"); } else if pair == &self.TOMBSTONE { println!("TOMBSTONE"); } else { let pair = pair.as_ref().unwrap(); println!("{} -> {}", pair.key, pair.val); } } } } /* Driver Code */ fn main() { /* Initialize hash table */ let mut hashmap = HashMapOpenAddressing::new(); /* Add operation */ // Add key-value pair (key, value) to the hash table hashmap.put(12836, "Xiao Ha".to_string()); hashmap.put(15937, "Xiao Luo".to_string()); hashmap.put(16750, "Xiao Suan".to_string()); hashmap.put(13276, "Xiao Fa".to_string()); hashmap.put(10583, "Xiao Ya".to_string()); println!("\nAfter adding is complete, hash table is\nKey -> Value"); hashmap.print(); /* Query operation */ // Input key into hash table to get value val let name = hashmap.get(13276).unwrap(); println!("\nInput student ID 13276, found name {}", name); /* Remove operation */ // Remove key-value pair (key, val) from hash table hashmap.remove(16750); println!("\nAfter removing 16750, hash table is\nKey -> Value"); hashmap.print(); } ================================================ FILE: en/codes/rust/chapter_hashing/simple_hash.rs ================================================ /* * File: simple_hash.rs * Created Time: 2023-09-07 * Author: night-cruise (2586447362@qq.com) */ /* Additive hash */ fn add_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = (hash + c as i64) % MODULUS; } hash as i32 } /* Multiplicative hash */ fn mul_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = (31 * hash + c as i64) % MODULUS; } hash as i32 } /* XOR hash */ fn xor_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash ^= c as i64; } (hash & MODULUS) as i32 } /* Rotational hash */ fn rot_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS; } hash as i32 } /* Driver Code */ fn main() { let key = "Hello Algo"; let hash = add_hash(key); println!("Additive hash value is {hash}"); let hash = mul_hash(key); println!("Multiplicative hash value is {hash}"); let hash = xor_hash(key); println!("XOR hash value is {hash}"); let hash = rot_hash(key); println!("Rotational hash value is {hash}"); } ================================================ FILE: en/codes/rust/chapter_heap/heap.rs ================================================ /* * File: heap.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::{cmp::Reverse, collections::BinaryHeap}; fn test_push_max(heap: &mut BinaryHeap, val: i32) { heap.push(val); // Element enters heap println!("\nAfter element {} pushes to heap", val); print_util::print_heap(heap.iter().map(|&val| val).collect()); } fn test_pop_max(heap: &mut BinaryHeap) { let val = heap.pop().unwrap(); println!("\nAfter heap top element {} pops from heap", val); print_util::print_heap(heap.iter().map(|&val| val).collect()); } /* Driver Code */ fn main() { /* Initialize heap */ // Python's heapq module implements min heap by default #[allow(unused_assignments)] let mut min_heap = BinaryHeap::new(); // Rust's BinaryHeap is a max heap, min heap typically wraps elements with Reverse // Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap let mut max_heap = BinaryHeap::new(); println!("\nThe following test cases are for max heap"); /* Element enters heap */ test_push_max(&mut max_heap, 1); test_push_max(&mut max_heap, 3); test_push_max(&mut max_heap, 2); test_push_max(&mut max_heap, 5); test_push_max(&mut max_heap, 4); /* Check if heap is empty */ let peek = max_heap.peek().unwrap(); println!("\nHeap top element is {}", peek); /* Time complexity is O(n), not O(nlogn) */ test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); /* Get heap size */ let size = max_heap.len(); println!("\nHeap size is {}", size); /* Check if heap is empty */ let is_empty = max_heap.is_empty(); println!("\nIs heap empty {}", is_empty); /* Input list and build heap */ // Time complexity is O(n), not O(nlogn) min_heap = BinaryHeap::from( vec![1, 3, 2, 5, 4] .into_iter() .map(|val| Reverse(val)) .collect::>>(), ); println!("\nAfter inputting list and building min heap"); print_util::print_heap(min_heap.iter().map(|&val| val.0).collect()); } ================================================ FILE: en/codes/rust/chapter_heap/my_heap.rs ================================================ /* * File: my_heap.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* Max heap */ struct MaxHeap { // Use vector instead of array to avoid capacity concerns max_heap: Vec, } impl MaxHeap { /* Constructor, build heap based on input list */ fn new(nums: Vec) -> Self { // Add list elements to heap as is let mut heap = MaxHeap { max_heap: nums }; // Heapify all nodes except leaf nodes for i in (0..=Self::parent(heap.size() - 1)).rev() { heap.sift_down(i); } heap } /* Get index of left child node */ fn left(i: usize) -> usize { 2 * i + 1 } /* Get index of right child node */ fn right(i: usize) -> usize { 2 * i + 2 } /* Get index of parent node */ fn parent(i: usize) -> usize { (i - 1) / 2 // Floor division } /* Swap elements */ fn swap(&mut self, i: usize, j: usize) { self.max_heap.swap(i, j); } /* Get heap size */ fn size(&self) -> usize { self.max_heap.len() } /* Check if heap is empty */ fn is_empty(&self) -> bool { self.max_heap.is_empty() } /* Access top element */ fn peek(&self) -> Option { self.max_heap.first().copied() } /* Element enters heap */ fn push(&mut self, val: i32) { // Add node self.max_heap.push(val); // Heapify from bottom to top self.sift_up(self.size() - 1); } /* Starting from node i, heapify from bottom to top */ fn sift_up(&mut self, mut i: usize) { loop { // Node i is already the heap root, end heapification if i == 0 { break; } // Get parent node of node i let p = Self::parent(i); // When "node needs no repair", end heapification if self.max_heap[i] <= self.max_heap[p] { break; } // Swap two nodes self.swap(i, p); // Loop upward heapify i = p; } } /* Element exits heap */ fn pop(&mut self) -> i32 { // Handle empty case if self.is_empty() { panic!("index out of bounds"); } // Delete node self.swap(0, self.size() - 1); // Remove node let val = self.max_heap.pop().unwrap(); // Return top element self.sift_down(0); // Return heap top element val } /* Starting from node i, heapify from top to bottom */ fn sift_down(&mut self, mut i: usize) { loop { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break let (l, r, mut ma) = (Self::left(i), Self::right(i), i); if l < self.size() && self.max_heap[l] > self.max_heap[ma] { ma = l; } if r < self.size() && self.max_heap[r] > self.max_heap[ma] { ma = r; } // Swap two nodes if ma == i { break; } // Swap two nodes self.swap(i, ma); // Loop downwards heapification i = ma; } } /* Driver Code */ fn print(&self) { print_util::print_heap(self.max_heap.clone()); } } /* Driver Code */ fn main() { /* Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap */ let mut max_heap = MaxHeap::new(vec![9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); println!("\nAfter inputting list and building heap"); max_heap.print(); /* Check if heap is empty */ let peek = max_heap.peek(); if let Some(peek) = peek { println!("\nHeap top element is {}", peek); } /* Element enters heap */ let val = 7; max_heap.push(val); println!("\nAfter element {} pushes to heap", val); max_heap.print(); /* Time complexity is O(n), not O(nlogn) */ let peek = max_heap.pop(); println!("\nAfter heap top element {} pops from heap", peek); max_heap.print(); /* Get heap size */ let size = max_heap.size(); println!("\nHeap size is {}", size); /* Check if heap is empty */ let is_empty = max_heap.is_empty(); println!("\nIs heap empty {}", is_empty); } ================================================ FILE: en/codes/rust/chapter_heap/top_k.rs ================================================ /* * File: top_k.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::cmp::Reverse; use std::collections::BinaryHeap; /* Find the largest k elements in array based on heap */ fn top_k_heap(nums: Vec, k: usize) -> BinaryHeap> { // BinaryHeap is a max heap, use Reverse to negate elements to implement min heap let mut heap = BinaryHeap::>::new(); // Enter the first k elements of array into heap for &num in nums.iter().take(k) { heap.push(Reverse(num)); } // Starting from the (k+1)th element, maintain heap length as k for &num in nums.iter().skip(k) { // If current element is greater than top element, top element exits heap, current element enters heap if num > heap.peek().unwrap().0 { heap.pop(); heap.push(Reverse(num)); } } heap } /* Driver Code */ fn main() { let nums = vec![1, 7, 6, 3, 2]; let k = 3; let res = top_k_heap(nums, k); println!("The largest {} elements are", k); print_util::print_heap(res.into_iter().map(|item| item.0).collect()); } ================================================ FILE: en/codes/rust/chapter_searching/binary_search.rs ================================================ /* * File: binary_search.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ /* Binary search (closed interval on both sides) */ fn binary_search(nums: &[i32], target: i32) -> i32 { // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array let mut i = 0; let mut j = nums.len() as i32 - 1; // Loop, exit when the search interval is empty (empty when i > j) while i <= j { let m = i + (j - i) / 2; // Calculate the midpoint index m if nums[m as usize] < target { // This means target is in the interval [m+1, j] i = m + 1; } else if nums[m as usize] > target { // This means target is in the interval [i, m-1] j = m - 1; } else { // Found the target element, return its index return m; } } // Target element not found, return -1 return -1; } /* Binary search (left-closed right-open interval) */ fn binary_search_lcro(nums: &[i32], target: i32) -> i32 { // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1 let mut i = 0; let mut j = nums.len() as i32; // Loop, exit when the search interval is empty (empty when i = j) while i < j { let m = i + (j - i) / 2; // Calculate the midpoint index m if nums[m as usize] < target { // This means target is in the interval [m+1, j) i = m + 1; } else if nums[m as usize] > target { // This means target is in the interval [i, m) j = m; } else { // Found the target element, return its index return m; } } // Target element not found, return -1 return -1; } /* Driver Code */ pub fn main() { let target = 6; let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // Binary search (closed interval on both sides) let mut index = binary_search(&nums, target); println!("Index of target element 6 is {index}"); // Binary search (left-closed right-open interval) index = binary_search_lcro(&nums, target); println!("Index of target element 6 is {index}"); } ================================================ FILE: en/codes/rust/chapter_searching/binary_search_edge.rs ================================================ /* * File: binary_search_edge.rs * Created Time: 2023-08-30 * Author: night-cruise (2586447362@qq.com) */ mod binary_search_insertion; use binary_search_insertion::binary_search_insertion; /* Binary search for the leftmost target */ fn binary_search_left_edge(nums: &[i32], target: i32) -> i32 { // Equivalent to finding the insertion point of target let i = binary_search_insertion(nums, target); // Target not found, return -1 if i == nums.len() as i32 || nums[i as usize] != target { return -1; } // Found target, return index i i } /* Binary search for the rightmost target */ fn binary_search_right_edge(nums: &[i32], target: i32) -> i32 { // Convert to finding the leftmost target + 1 let i = binary_search_insertion(nums, target + 1); // j points to the rightmost target, i points to the first element greater than target let j = i - 1; // Target not found, return -1 if j == -1 || nums[j as usize] != target { return -1; } // Found target, return index j j } /* Driver Code */ fn main() { // Array with duplicate elements let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; println!("\nArray nums = {:?}", nums); // Binary search left and right boundaries for target in [6, 7] { let index = binary_search_left_edge(&nums, target); println!("Leftmost element {} index is {}", target, index); let index = binary_search_right_edge(&nums, target); println!("Rightmost element {} index is {}", target, index); } } ================================================ FILE: en/codes/rust/chapter_searching/binary_search_insertion.rs ================================================ /* * File: binary_search_insertion.rs * Created Time: 2023-08-30 * Author: night-cruise (2586447362@qq.com) */ #![allow(unused)] /* Binary search for insertion point (no duplicate elements) */ fn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 { let (mut i, mut j) = (0, nums.len() as i32 - 1); // Initialize closed interval [0, n-1] while i <= j { let m = i + (j - i) / 2; // Calculate the midpoint index m if nums[m as usize] < target { i = m + 1; // target is in the interval [m+1, j] } else if nums[m as usize] > target { j = m - 1; // target is in the interval [i, m-1] } else { return m; } } // Target not found, return insertion point i i } /* Binary search for insertion point (with duplicate elements) */ pub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 { let (mut i, mut j) = (0, nums.len() as i32 - 1); // Initialize closed interval [0, n-1] while i <= j { let m = i + (j - i) / 2; // Calculate the midpoint index m if nums[m as usize] < target { i = m + 1; // target is in the interval [m+1, j] } else if nums[m as usize] > target { j = m - 1; // target is in the interval [i, m-1] } else { j = m - 1; // The first element less than target is in the interval [i, m-1] } } // Return insertion point i i } /* Driver Code */ fn main() { // Array without duplicate elements let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; println!("\nArray nums = {:?}", nums); // Binary search for insertion point for target in [6, 9] { let index = binary_search_insertion_simple(&nums, target); println!("Insertion point index for element {} is {}", target, index); } // Array with duplicate elements let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; println!("\nArray nums = {:?}", nums); // Binary search for insertion point for target in [2, 6, 20] { let index = binary_search_insertion(&nums, target); println!("Insertion point index for element {} is {}", target, index); } } ================================================ FILE: en/codes/rust/chapter_searching/hashing_search.rs ================================================ /* * File: hashing_search.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::ListNode; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; /* Hash search (array) */ fn hashing_search_array<'a>(map: &'a HashMap, target: i32) -> Option<&'a usize> { // Hash table's key: target element, value: index // If this key does not exist in the hash table, return None map.get(&target) } /* Hash search (linked list) */ fn hashing_search_linked_list( map: &HashMap>>>, target: i32, ) -> Option<&Rc>>> { // Hash table key: target node value, value: node object // If this key does not exist in the hash table, return None map.get(&target) } /* Driver Code */ pub fn main() { let target = 3; /* Hash search (array) */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // Initialize hash table let mut map = HashMap::new(); for (i, num) in nums.iter().enumerate() { map.insert(*num, i); // key: element, value: index } let index = hashing_search_array(&map, target); println!("Index of target element 3 = {}", index.unwrap()); /* Hash search (linked list) */ let head = ListNode::arr_to_linked_list(&nums); // Initialize hash table // let mut map1 = HashMap::new(); let map1 = ListNode::linked_list_to_hashmap(head); let node = hashing_search_linked_list(&map1, target); println!("Node object corresponding to target node value 3 is {:?}", node); } ================================================ FILE: en/codes/rust/chapter_searching/linear_search.rs ================================================ /* * File: linear_search.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::ListNode; use std::cell::RefCell; use std::rc::Rc; /* Linear search (array) */ fn linear_search_array(nums: &[i32], target: i32) -> i32 { // Traverse array for (i, num) in nums.iter().enumerate() { // Found the target element, return its index if num == &target { return i as i32; } } // Target element not found, return -1 return -1; } /* Linear search (linked list) */ fn linear_search_linked_list( head: Rc>>, target: i32, ) -> Option>>> { // Found the target node, return it if head.borrow().val == target { return Some(head); }; // Found the target node, return it if let Some(node) = &head.borrow_mut().next { return linear_search_linked_list(node.clone(), target); } // Target node not found, return None return None; } /* Driver Code */ pub fn main() { let target = 3; /* Perform linear search in array */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; let index = linear_search_array(&nums, target); println!("Index of target element 3 = {}", index); /* Perform linear search in linked list */ let head = ListNode::arr_to_linked_list(&nums); let node = linear_search_linked_list(head.unwrap(), target); println!("Node object corresponding to target node value 3 is {:?}", node); } ================================================ FILE: en/codes/rust/chapter_searching/two_sum.rs ================================================ /* * File: two_sum.rs * Created Time: 2023-01-14 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::collections::HashMap; /* Method 1: Brute force enumeration */ pub fn two_sum_brute_force(nums: &Vec, target: i32) -> Option> { let size = nums.len(); // Two nested loops, time complexity is O(n^2) for i in 0..size - 1 { for j in i + 1..size { if nums[i] + nums[j] == target { return Some(vec![i as i32, j as i32]); } } } None } /* Method 2: Auxiliary hash table */ pub fn two_sum_hash_table(nums: &Vec, target: i32) -> Option> { // Auxiliary hash table, space complexity is O(n) let mut dic = HashMap::new(); // Single loop, time complexity is O(n) for (i, num) in nums.iter().enumerate() { match dic.get(&(target - num)) { Some(v) => return Some(vec![*v as i32, i as i32]), None => dic.insert(num, i as i32), }; } None } fn main() { // ======= Test Case ======= let nums = vec![2, 7, 11, 15]; let target = 13; // ====== Driver Code ====== // Method 1 let res = two_sum_brute_force(&nums, target).unwrap(); print!("Method 1 res = "); print_util::print_array(&res); // Method 2 let res = two_sum_hash_table(&nums, target).unwrap(); print!("\nMethod 2 res = "); print_util::print_array(&res); } ================================================ FILE: en/codes/rust/chapter_sorting/bubble_sort.rs ================================================ /* * File: bubble_sort.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Bubble sort */ fn bubble_sort(nums: &mut [i32]) { // Outer loop: unsorted range is [0, i] for i in (1..nums.len()).rev() { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for j in 0..i { if nums[j] > nums[j + 1] { // Swap nums[j] and nums[j + 1] nums.swap(j, j + 1); } } } } /* Bubble sort (flag optimization) */ fn bubble_sort_with_flag(nums: &mut [i32]) { // Outer loop: unsorted range is [0, i] for i in (1..nums.len()).rev() { let mut flag = false; // Initialize flag // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for j in 0..i { if nums[j] > nums[j + 1] { // Swap nums[j] and nums[j + 1] nums.swap(j, j + 1); flag = true; // Record element swap } } if !flag { break; // No elements were swapped in this round of "bubbling", exit directly }; } } /* Driver Code */ pub fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; bubble_sort(&mut nums); print!("After bubble sort completes, nums = "); print_util::print_array(&nums); let mut nums1 = [4, 1, 3, 1, 5, 2]; bubble_sort_with_flag(&mut nums1); print!("\nAfter bubble sort, nums1 = "); print_util::print_array(&nums1); } ================================================ FILE: en/codes/rust/chapter_sorting/bucket_sort.rs ================================================ /* * File: bucket_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* Bucket sort */ fn bucket_sort(nums: &mut [f64]) { // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket let k = nums.len() / 2; let mut buckets = vec![vec![]; k]; // 1. Distribute array elements into various buckets for &num in nums.iter() { // Input data range is [0, 1), use num * k to map to index range [0, k-1] let i = (num * k as f64) as usize; // Add num to bucket i buckets[i].push(num); } // 2. Sort each bucket for bucket in &mut buckets { // Use built-in sorting function, can also replace with other sorting algorithms bucket.sort_by(|a, b| a.partial_cmp(b).unwrap()); } // 3. Traverse buckets to merge results let mut i = 0; for bucket in buckets.iter() { for &num in bucket.iter() { nums[i] = num; i += 1; } } } /* Driver Code */ fn main() { // Assume input data is floating point, interval [0, 1) let mut nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucket_sort(&mut nums); print!("After bucket sort completes, nums = "); print_util::print_array(&nums); } ================================================ FILE: en/codes/rust/chapter_sorting/counting_sort.rs ================================================ /* * File: counting_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* Counting sort */ // Simple implementation, cannot be used for sorting objects fn counting_sort_naive(nums: &mut [i32]) { // 1. Count the maximum element m in the array let m = *nums.iter().max().unwrap(); // 2. Count the occurrence of each number // counter[num] represents the occurrence of num let mut counter = vec![0; m as usize + 1]; for &num in nums.iter() { counter[num as usize] += 1; } // 3. Traverse counter, filling each element back into the original array nums let mut i = 0; for num in 0..m + 1 { for _ in 0..counter[num as usize] { nums[i] = num; i += 1; } } } /* Counting sort */ // Complete implementation, can sort objects and is a stable sort fn counting_sort(nums: &mut [i32]) { // 1. Count the maximum element m in the array let m = *nums.iter().max().unwrap() as usize; // 2. Count the occurrence of each number // counter[num] represents the occurrence of num let mut counter = vec![0; m + 1]; for &num in nums.iter() { counter[num as usize] += 1; } // 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" // counter[num]-1 is the last index where num appears in res for i in 0..m { counter[i + 1] += counter[i]; } // 4. Traverse nums in reverse order, placing each element into the result array res // Initialize the array res to record results let n = nums.len(); let mut res = vec![0; n]; for i in (0..n).rev() { let num = nums[i]; res[counter[num as usize] - 1] = num; // Place num at the corresponding index counter[num as usize] -= 1; // Decrement the prefix sum by 1, getting the next index to place num } // Use result array res to overwrite the original array nums nums.copy_from_slice(&res) } /* Driver Code */ fn main() { let mut nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; counting_sort_naive(&mut nums); print!("After counting sort (cannot sort objects) completes, nums = "); print_util::print_array(&nums); let mut nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; counting_sort(&mut nums1); print!("\nAfter counting sort, nums1 = "); print_util::print_array(&nums1); } ================================================ FILE: en/codes/rust/chapter_sorting/heap_sort.rs ================================================ /* * File: heap_sort.rs * Created Time: 2023-07-04 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* Heap length is n, start heapifying node i, from top to bottom */ fn sift_down(nums: &mut [i32], n: usize, mut i: usize) { loop { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break let l = 2 * i + 1; let r = 2 * i + 2; let mut ma = i; if l < n && nums[l] > nums[ma] { ma = l; } if r < n && nums[r] > nums[ma] { ma = r; } // Swap two nodes if ma == i { break; } // Swap two nodes nums.swap(i, ma); // Loop downwards heapification i = ma; } } /* Heap sort */ fn heap_sort(nums: &mut [i32]) { // Build heap operation: heapify all nodes except leaves for i in (0..nums.len() / 2).rev() { sift_down(nums, nums.len(), i); } // Extract the largest element from the heap and repeat for n-1 rounds for i in (1..nums.len()).rev() { // Delete node nums.swap(0, i); // Start heapifying the root node, from top to bottom sift_down(nums, i, 0); } } /* Driver Code */ fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; heap_sort(&mut nums); print!("After heap sort completes, nums = "); print_util::print_array(&nums); } ================================================ FILE: en/codes/rust/chapter_sorting/insertion_sort.rs ================================================ /* * File: insertion_sort.rs * Created Time: 2023-02-13 * Author: xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; /* Insertion sort */ fn insertion_sort(nums: &mut [i32]) { // Outer loop: sorted interval is [0, i-1] for i in 1..nums.len() { let (base, mut j) = (nums[i], (i - 1) as i32); // Inner loop: insert base into the correct position within the sorted interval [0, i-1] while j >= 0 && nums[j as usize] > base { nums[(j + 1) as usize] = nums[j as usize]; // Move nums[j] to the right by one position j -= 1; } nums[(j + 1) as usize] = base; // Assign base to the correct position } } /* Driver Code */ fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; insertion_sort(&mut nums); print!("After insertion sort completes, nums = "); print_util::print_array(&nums); } ================================================ FILE: en/codes/rust/chapter_sorting/merge_sort.rs ================================================ /** * File: merge_sort.rs * Created Time: 2023-02-14 * Author: xBLACKICEx (xBLACKICEx@outlook.com) */ /* Merge left subarray and right subarray */ fn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) { // Left subarray interval is [left, mid], right subarray interval is [mid+1, right] // Create a temporary array tmp to store the merged results let tmp_size = right - left + 1; let mut tmp = vec![0; tmp_size]; // Initialize the start indices of the left and right subarrays let (mut i, mut j, mut k) = (left, mid + 1, 0); // While both subarrays still have elements, compare and copy the smaller element into the temporary array while i <= mid && j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i]; i += 1; } else { tmp[k] = nums[j]; j += 1; } k += 1; } // Copy the remaining elements of the left and right subarrays into the temporary array while i <= mid { tmp[k] = nums[i]; k += 1; i += 1; } while j <= right { tmp[k] = nums[j]; k += 1; j += 1; } // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval for k in 0..tmp_size { nums[left + k] = tmp[k]; } } /* Merge sort */ fn merge_sort(nums: &mut [i32], left: usize, right: usize) { // Termination condition if left >= right { return; // Terminate recursion when subarray length is 1 } // Divide and conquer stage let mid = left + (right - left) / 2; // Calculate midpoint merge_sort(nums, left, mid); // Recursively process the left subarray merge_sort(nums, mid + 1, right); // Recursively process the right subarray // Merge stage merge(nums, left, mid, right); } /* Driver Code */ fn main() { /* Merge sort */ let mut nums = [7, 3, 2, 6, 0, 1, 5, 4]; let right = nums.len() - 1; merge_sort(&mut nums, 0, right); println!("After merge sort, nums = {:?}", nums); } ================================================ FILE: en/codes/rust/chapter_sorting/quick_sort.rs ================================================ /** * File: quick_sort.rs * Created Time: 2023-02-16 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ /* Quick sort */ struct QuickSort; impl QuickSort { /* Sentinel partition */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // Use nums[left] as the pivot let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // Search from right to left for the first element smaller than the pivot } while i < j && nums[i] <= nums[left] { i += 1; // Search from left to right for the first element greater than the pivot } nums.swap(i, j); // Swap these two elements } nums.swap(i, left); // Swap the pivot to the boundary between the two subarrays i // Return the index of the pivot } /* Quick sort */ pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { // Terminate recursion when subarray length is 1 if left >= right { return; } // Sentinel partition let pivot = Self::partition(nums, left as usize, right as usize) as i32; // Recursively process the left subarray and right subarray Self::quick_sort(left, pivot - 1, nums); Self::quick_sort(pivot + 1, right, nums); } } /* Quick sort (recursion depth optimization) */ struct QuickSortMedian; impl QuickSortMedian { /* Select the median of three candidate elements */ fn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize { let (l, m, r) = (nums[left], nums[mid], nums[right]); if (l <= m && m <= r) || (r <= m && m <= l) { return mid; // m is between l and r } if (m <= l && l <= r) || (r <= l && l <= m) { return left; // l is between m and r } right } /* Sentinel partition (median of three) */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // Select the median of three candidate elements let med = Self::median_three(nums, left, (left + right) / 2, right); // Swap the median to the array's leftmost position nums.swap(left, med); // Use nums[left] as the pivot let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // Search from right to left for the first element smaller than the pivot } while i < j && nums[i] <= nums[left] { i += 1; // Search from left to right for the first element greater than the pivot } nums.swap(i, j); // Swap these two elements } nums.swap(i, left); // Swap the pivot to the boundary between the two subarrays i // Return the index of the pivot } /* Quick sort */ pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { // Terminate recursion when subarray length is 1 if left >= right { return; } // Sentinel partition let pivot = Self::partition(nums, left as usize, right as usize) as i32; // Recursively process the left subarray and right subarray Self::quick_sort(left, pivot - 1, nums); Self::quick_sort(pivot + 1, right, nums); } } /* Quick sort (recursion depth optimization) */ struct QuickSortTailCall; impl QuickSortTailCall { /* Sentinel partition */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // Use nums[left] as the pivot let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // Search from right to left for the first element smaller than the pivot } while i < j && nums[i] <= nums[left] { i += 1; // Search from left to right for the first element greater than the pivot } nums.swap(i, j); // Swap these two elements } nums.swap(i, left); // Swap the pivot to the boundary between the two subarrays i // Return the index of the pivot } /* Quick sort (recursion depth optimization) */ pub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) { // Terminate when subarray length is 1 while left < right { // Sentinel partition operation let pivot = Self::partition(nums, left as usize, right as usize) as i32; // Perform quick sort on the shorter of the two subarrays if pivot - left < right - pivot { Self::quick_sort(left, pivot - 1, nums); // Recursively sort the left subarray left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right] } else { Self::quick_sort(pivot + 1, right, nums); // Recursively sort the right subarray right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1] } } } } /* Driver Code */ fn main() { /* Quick sort */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSort::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("After quick sort, nums = {:?}", nums); /* Quick sort (recursion depth optimization) */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSortMedian::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("After quick sort (median pivot optimization), nums = {:?}", nums); /* Quick sort (recursion depth optimization) */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSortTailCall::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("After quick sort (recursion depth optimization), nums = {:?}", nums); } ================================================ FILE: en/codes/rust/chapter_sorting/radix_sort.rs ================================================ /* * File: radix_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* Get the k-th digit of element num, where exp = 10^(k-1) */ fn digit(num: i32, exp: i32) -> usize { // Passing exp instead of k can avoid repeated expensive exponentiation here return ((num / exp) % 10) as usize; } /* Counting sort (based on nums k-th digit) */ fn counting_sort_digit(nums: &mut [i32], exp: i32) { // Decimal digit range is 0~9, therefore need a bucket array of length 10 let mut counter = [0; 10]; let n = nums.len(); // Count the occurrence of digits 0~9 for i in 0..n { let d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d counter[d] += 1; // Count the occurrence of digit d } // Calculate prefix sum, converting "occurrence count" into "array index" for i in 1..10 { counter[i] += counter[i - 1]; } // Traverse in reverse, based on bucket statistics, place each element into res let mut res = vec![0; n]; for i in (0..n).rev() { let d = digit(nums[i], exp); let j = counter[d] - 1; // Get the index j for d in the array res[j] = nums[i]; // Place the current element at index j counter[d] -= 1; // Decrease the count of d by 1 } // Use result to overwrite the original array nums nums.copy_from_slice(&res); } /* Radix sort */ fn radix_sort(nums: &mut [i32]) { // Get the maximum element of the array, used to determine the maximum number of digits let m = *nums.into_iter().max().unwrap(); // Traverse from the lowest to the highest digit let mut exp = 1; while exp <= m { counting_sort_digit(nums, exp); exp *= 10; } } /* Driver Code */ fn main() { // Radix sort let mut nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radix_sort(&mut nums); print!("After radix sort completes, nums = "); print_util::print_array(&nums); } ================================================ FILE: en/codes/rust/chapter_sorting/selection_sort.rs ================================================ /* * File: selection_sort.rs * Created Time: 2023-05-30 * Author: WSL0809 (wslzzy@outlook.com) */ use hello_algo_rust::include::print_util; /* Selection sort */ fn selection_sort(nums: &mut [i32]) { if nums.is_empty() { return; } let n = nums.len(); // Outer loop: unsorted interval is [i, n-1] for i in 0..n - 1 { // Inner loop: find the smallest element within the unsorted interval let mut k = i; for j in i + 1..n { if nums[j] < nums[k] { k = j; // Record the index of the smallest element } } // Swap the smallest element with the first element of the unsorted interval nums.swap(i, k); } } /* Driver Code */ pub fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; selection_sort(&mut nums); print!("\nAfter selection sort, nums = "); print_util::print_array(&nums); } ================================================ FILE: en/codes/rust/chapter_stack_and_queue/array_deque.rs ================================================ /* * File: array_deque.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Double-ended queue based on circular array implementation */ struct ArrayDeque { nums: Vec, // Array for storing double-ended queue elements front: usize, // Front pointer, points to the front of the queue element que_size: usize, // Double-ended queue length } impl ArrayDeque { /* Constructor */ pub fn new(capacity: usize) -> Self { Self { nums: vec![T::default(); capacity], front: 0, que_size: 0, } } /* Get the capacity of the double-ended queue */ pub fn capacity(&self) -> usize { self.nums.len() } /* Get the length of the double-ended queue */ pub fn size(&self) -> usize { self.que_size } /* Check if the double-ended queue is empty */ pub fn is_empty(&self) -> bool { self.que_size == 0 } /* Calculate circular array index */ fn index(&self, i: i32) -> usize { // Use modulo operation to wrap the array head and tail together // When i passes the tail of the array, return to the head // When i passes the head of the array, return to the tail ((i + self.capacity() as i32) % self.capacity() as i32) as usize } /* Front of the queue enqueue */ pub fn push_first(&mut self, num: T) { if self.que_size == self.capacity() { println!("Double-ended queue is full"); return; } // Use modulo operation to wrap front around to the tail after passing the head of the array // Add num to the front of the queue self.front = self.index(self.front as i32 - 1); // Add num to front of queue self.nums[self.front] = num; self.que_size += 1; } /* Rear of the queue enqueue */ pub fn push_last(&mut self, num: T) { if self.que_size == self.capacity() { println!("Double-ended queue is full"); return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array let rear = self.index(self.front as i32 + self.que_size as i32); // Front pointer moves one position backward self.nums[rear] = num; self.que_size += 1; } /* Rear of the queue dequeue */ fn pop_first(&mut self) -> T { let num = self.peek_first(); // Move front pointer backward by one position self.front = self.index(self.front as i32 + 1); self.que_size -= 1; num } /* Access rear of the queue element */ fn pop_last(&mut self) -> T { let num = self.peek_last(); self.que_size -= 1; num } /* Return list for printing */ fn peek_first(&self) -> T { if self.is_empty() { panic!("Deque is empty") }; self.nums[self.front] } /* Driver Code */ fn peek_last(&self) -> T { if self.is_empty() { panic!("Deque is empty") }; // Initialize double-ended queue let last = self.index(self.front as i32 + self.que_size as i32 - 1); self.nums[last] } /* Return array for printing */ fn to_array(&self) -> Vec { // Elements enqueue let mut res = vec![T::default(); self.que_size]; let mut j = self.front; for i in 0..self.que_size { res[i] = self.nums[self.index(j as i32)]; j += 1; } res } } /* Driver Code */ fn main() { /* Get the length of the double-ended queue */ let mut deque = ArrayDeque::new(10); deque.push_last(3); deque.push_last(2); deque.push_last(5); print!("Double-ended queue deque = "); print_util::print_array(&deque.to_array()); /* Update element */ let peek_first = deque.peek_first(); print!("\nFront element peek_first = {}", peek_first); let peek_last = deque.peek_last(); print!("\nRear element peek_last = {}", peek_last); /* Elements enqueue */ deque.push_last(4); print!("\nAfter element 4 enqueues at rear, deque = "); print_util::print_array(&deque.to_array()); deque.push_first(1); print!("\nAfter element 1 enqueues at front, deque = "); print_util::print_array(&deque.to_array()); /* Element dequeue */ let pop_last = deque.pop_last(); print!("\nDequeue rear element = {}, after dequeue deque = ", pop_last); print_util::print_array(&deque.to_array()); let pop_first = deque.pop_first(); print!("\nDequeue front element = {}, after dequeue deque = ", pop_first); print_util::print_array(&deque.to_array()); /* Get the length of the double-ended queue */ let size = deque.size(); print!("\nDeque length size = {}", size); /* Check if the double-ended queue is empty */ let is_empty = deque.is_empty(); print!("\nIs deque empty = {}", is_empty); } ================================================ FILE: en/codes/rust/chapter_stack_and_queue/array_queue.rs ================================================ /* * File: array_queue.rs * Created Time: 2023-02-06 * Author: WSL0809 (wslzzy@outlook.com) */ /* Queue based on circular array implementation */ struct ArrayQueue { nums: Vec, // Array for storing queue elements front: i32, // Front pointer, points to the front of the queue element que_size: i32, // Queue length que_capacity: i32, // Queue capacity } impl ArrayQueue { /* Constructor */ fn new(capacity: i32) -> ArrayQueue { ArrayQueue { nums: vec![T::default(); capacity as usize], front: 0, que_size: 0, que_capacity: capacity, } } /* Get the capacity of the queue */ fn capacity(&self) -> i32 { self.que_capacity } /* Get the length of the queue */ fn size(&self) -> i32 { self.que_size } /* Check if the queue is empty */ fn is_empty(&self) -> bool { self.que_size == 0 } /* Enqueue */ fn push(&mut self, num: T) { if self.que_size == self.capacity() { println!("Queue is full"); return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array // Add num to the rear of the queue let rear = (self.front + self.que_size) % self.que_capacity; // Front pointer moves one position backward self.nums[rear as usize] = num; self.que_size += 1; } /* Dequeue */ fn pop(&mut self) -> T { let num = self.peek(); // Move front pointer backward by one position, if it passes the tail, return to array head self.front = (self.front + 1) % self.que_capacity; self.que_size -= 1; num } /* Return list for printing */ fn peek(&self) -> T { if self.is_empty() { panic!("index out of bounds"); } self.nums[self.front as usize] } /* Return array */ fn to_vector(&self) -> Vec { let cap = self.que_capacity; let mut j = self.front; let mut arr = vec![T::default(); cap as usize]; for i in 0..self.que_size { arr[i as usize] = self.nums[(j % cap) as usize]; j += 1; } arr } } /* Driver Code */ fn main() { /* Access front of the queue element */ let capacity = 10; let mut queue = ArrayQueue::new(capacity); /* Elements enqueue */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); println!("Queue queue = {:?}", queue.to_vector()); /* Return list for printing */ let peek = queue.peek(); println!("Front element peek = {}", peek); /* Element dequeue */ let pop = queue.pop(); println!( "Dequeue element pop = {:?}, after dequeue queue = {:?}", pop, queue.to_vector() ); /* Get the length of the queue */ let size = queue.size(); println!("Queue length size = {}", size); /* Check if the queue is empty */ let is_empty = queue.is_empty(); println!("Is queue empty = {}", is_empty); /* Test circular array */ for i in 0..10 { queue.push(i); queue.pop(); println!("After round {:?} of enqueue + dequeue, queue = {:?}", i, queue.to_vector()); } } ================================================ FILE: en/codes/rust/chapter_stack_and_queue/array_stack.rs ================================================ /* * File: array_stack.rs * Created Time: 2023-02-05 * Author: WSL0809 (wslzzy@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Stack based on array implementation */ struct ArrayStack { stack: Vec, } impl ArrayStack { /* Access top of the stack element */ fn new() -> ArrayStack { ArrayStack:: { stack: Vec::::new(), } } /* Get the length of the stack */ fn size(&self) -> usize { self.stack.len() } /* Check if the stack is empty */ fn is_empty(&self) -> bool { self.size() == 0 } /* Push */ fn push(&mut self, num: T) { self.stack.push(num); } /* Pop */ fn pop(&mut self) -> Option { self.stack.pop() } /* Return list for printing */ fn peek(&self) -> Option<&T> { if self.is_empty() { panic!("Stack is empty") }; self.stack.last() } /* Return &Vec */ fn to_array(&self) -> &Vec { &self.stack } } /* Driver Code */ fn main() { // Access top of the stack element let mut stack = ArrayStack::::new(); // Elements push onto stack stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("Stack stack = "); print_util::print_array(stack.to_array()); // Return list for printing let peek = stack.peek().unwrap(); print!("\nTop element peek = {}", peek); // Element pop from stack let pop = stack.pop().unwrap(); print!("\nPop element pop = {pop}, after pop stack = "); print_util::print_array(stack.to_array()); // Get the length of the stack let size = stack.size(); print!("\nStack length size = {size}"); // Check if empty let is_empty = stack.is_empty(); print!("\nIs stack empty = {is_empty}"); } ================================================ FILE: en/codes/rust/chapter_stack_and_queue/deque.rs ================================================ /* * File: deque.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; use std::collections::VecDeque; /* Driver Code */ pub fn main() { // Get the length of the double-ended queue let mut deque: VecDeque = VecDeque::new(); deque.push_back(3); deque.push_back(2); deque.push_back(5); print!("Double-ended queue deque = "); print_util::print_queue(&deque); // Update element let peek_first = deque.front().unwrap(); print!("\nFront element peekFirst = {peek_first}"); let peek_last = deque.back().unwrap(); print!("\nRear element peekLast = {peek_last}"); /* Elements enqueue */ deque.push_back(4); print!("\nAfter element 4 enqueues at rear, deque = "); print_util::print_queue(&deque); deque.push_front(1); print!("\nAfter element 1 enqueues at front, deque = "); print_util::print_queue(&deque); // Element dequeue let pop_last = deque.pop_back().unwrap(); print!("\nDequeue rear element = {pop_last}, after dequeue deque = "); print_util::print_queue(&deque); let pop_first = deque.pop_front().unwrap(); print!("\nDequeue front element = {pop_first}, after dequeue deque = "); print_util::print_queue(&deque); // Get the length of the double-ended queue let size = deque.len(); print!("\nDeque length size = {size}"); // Check if the double-ended queue is empty let is_empty = deque.is_empty(); print!("\nIs deque empty = {is_empty}"); } ================================================ FILE: en/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs ================================================ /* * File: linkedlist_deque.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::cell::RefCell; use std::rc::Rc; /* Doubly linked list node */ pub struct ListNode { pub val: T, // Node value pub next: Option>>>, // Successor node pointer pub prev: Option>>>, // Predecessor node pointer } impl ListNode { pub fn new(val: T) -> Rc>> { Rc::new(RefCell::new(ListNode { val, next: None, prev: None, })) } } /* Double-ended queue based on doubly linked list implementation */ #[allow(dead_code)] pub struct LinkedListDeque { front: Option>>>, // Head node front rear: Option>>>, // Tail node rear que_size: usize, // Length of the double-ended queue } impl LinkedListDeque { pub fn new() -> Self { Self { front: None, rear: None, que_size: 0, } } /* Get the length of the double-ended queue */ pub fn size(&self) -> usize { return self.que_size; } /* Check if the double-ended queue is empty */ pub fn is_empty(&self) -> bool { return self.que_size == 0; } /* Enqueue operation */ fn push(&mut self, num: T, is_front: bool) { let node = ListNode::new(num); // Front of the queue enqueue operation if is_front { match self.front.take() { // If the linked list is empty, make both front and rear point to node None => { self.rear = Some(node.clone()); self.front = Some(node); } // Add node to the head of the linked list Some(old_front) => { old_front.borrow_mut().prev = Some(node.clone()); node.borrow_mut().next = Some(old_front); self.front = Some(node); // Update head node } } } // Rear of the queue enqueue operation else { match self.rear.take() { // If the linked list is empty, make both front and rear point to node None => { self.front = Some(node.clone()); self.rear = Some(node); } // Add node to the tail of the linked list Some(old_rear) => { old_rear.borrow_mut().next = Some(node.clone()); node.borrow_mut().prev = Some(old_rear); self.rear = Some(node); // Update tail node } } } self.que_size += 1; // Update queue length } /* Front of the queue enqueue */ pub fn push_first(&mut self, num: T) { self.push(num, true); } /* Rear of the queue enqueue */ pub fn push_last(&mut self, num: T) { self.push(num, false); } /* Dequeue operation */ fn pop(&mut self, is_front: bool) -> Option { // If queue is empty, return None directly if self.is_empty() { return None; }; // Temporarily store head node value if is_front { self.front.take().map(|old_front| { match old_front.borrow_mut().next.take() { Some(new_front) => { new_front.borrow_mut().prev.take(); self.front = Some(new_front); // Update head node } None => { self.rear.take(); } } self.que_size -= 1; // Update queue length old_front.borrow().val }) } // Temporarily store tail node value else { self.rear.take().map(|old_rear| { match old_rear.borrow_mut().prev.take() { Some(new_rear) => { new_rear.borrow_mut().next.take(); self.rear = Some(new_rear); // Update tail node } None => { self.front.take(); } } self.que_size -= 1; // Update queue length old_rear.borrow().val }) } } /* Rear of the queue dequeue */ pub fn pop_first(&mut self) -> Option { return self.pop(true); } /* Access rear of the queue element */ pub fn pop_last(&mut self) -> Option { return self.pop(false); } /* Return list for printing */ pub fn peek_first(&self) -> Option<&Rc>>> { self.front.as_ref() } /* Driver Code */ pub fn peek_last(&self) -> Option<&Rc>>> { self.rear.as_ref() } /* Return array for printing */ pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { let mut res: Vec = Vec::new(); fn recur(cur: Option<&Rc>>>, res: &mut Vec) { if let Some(cur) = cur { res.push(cur.borrow().val); recur(cur.borrow().next.as_ref(), res); } } recur(head, &mut res); res } } /* Driver Code */ fn main() { /* Get the length of the double-ended queue */ let mut deque = LinkedListDeque::new(); deque.push_last(3); deque.push_last(2); deque.push_last(5); print!("Double-ended queue deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); /* Update element */ let peek_first = deque.peek_first().unwrap().borrow().val; print!("\nFront element peek_first = {}", peek_first); let peek_last = deque.peek_last().unwrap().borrow().val; print!("\nRear element peek_last = {}", peek_last); /* Elements enqueue */ deque.push_last(4); print!("\nAfter element 4 enqueues at rear, deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); deque.push_first(1); print!("\nAfter element 1 enqueues at front, deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); /* Element dequeue */ let pop_last = deque.pop_last().unwrap(); print!("\nDequeue rear element = {}, after dequeue deque = ", pop_last); print_util::print_array(&deque.to_array(deque.peek_first())); let pop_first = deque.pop_first().unwrap(); print!("\nDequeue front element = {}, after dequeue deque = ", pop_first); print_util::print_array(&deque.to_array(deque.peek_first())); /* Get the length of the double-ended queue */ let size = deque.size(); print!("\nDeque length size = {}", size); /* Check if the double-ended queue is empty */ let is_empty = deque.is_empty(); print!("\nIs deque empty = {}", is_empty); } ================================================ FILE: en/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs ================================================ /* * File: linkedlist_queue.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* Queue based on linked list implementation */ #[allow(dead_code)] pub struct LinkedListQueue { front: Option>>>, // Head node front rear: Option>>>, // Tail node rear que_size: usize, // Queue length } impl LinkedListQueue { pub fn new() -> Self { Self { front: None, rear: None, que_size: 0, } } /* Get the length of the queue */ pub fn size(&self) -> usize { return self.que_size; } /* Check if the queue is empty */ pub fn is_empty(&self) -> bool { return self.que_size == 0; } /* Enqueue */ pub fn push(&mut self, num: T) { // Add num after the tail node let new_rear = ListNode::new(num); match self.rear.take() { // If the queue is not empty, add the node after the tail node Some(old_rear) => { old_rear.borrow_mut().next = Some(new_rear.clone()); self.rear = Some(new_rear); } // If the queue is empty, make both front and rear point to the node None => { self.front = Some(new_rear.clone()); self.rear = Some(new_rear); } } self.que_size += 1; } /* Dequeue */ pub fn pop(&mut self) -> Option { self.front.take().map(|old_front| { match old_front.borrow_mut().next.take() { Some(new_front) => { self.front = Some(new_front); } None => { self.rear.take(); } } self.que_size -= 1; old_front.borrow().val }) } /* Return list for printing */ pub fn peek(&self) -> Option<&Rc>>> { self.front.as_ref() } /* Convert linked list to Array and return */ pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { let mut res: Vec = Vec::new(); fn recur(cur: Option<&Rc>>>, res: &mut Vec) { if let Some(cur) = cur { res.push(cur.borrow().val); recur(cur.borrow().next.as_ref(), res); } } recur(head, &mut res); res } } /* Driver Code */ fn main() { /* Access front of the queue element */ let mut queue = LinkedListQueue::new(); /* Elements enqueue */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print!("Queue queue = "); print_util::print_array(&queue.to_array(queue.peek())); /* Return list for printing */ let peek = queue.peek().unwrap().borrow().val; print!("\nFront element peek = {}", peek); /* Element dequeue */ let pop = queue.pop().unwrap(); print!("\nDequeue element pop = {}, after dequeue queue = ", pop); print_util::print_array(&queue.to_array(queue.peek())); /* Get the length of the queue */ let size = queue.size(); print!("\nQueue length size = {}", size); /* Check if the queue is empty */ let is_empty = queue.is_empty(); print!("\nIs queue empty = {}", is_empty); } ================================================ FILE: en/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs ================================================ /* * File: linkedlist_stack.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* Stack based on linked list implementation */ #[allow(dead_code)] pub struct LinkedListStack { stack_peek: Option>>>, // Use head node as stack top stk_size: usize, // Stack length } impl LinkedListStack { pub fn new() -> Self { Self { stack_peek: None, stk_size: 0, } } /* Get the length of the stack */ pub fn size(&self) -> usize { return self.stk_size; } /* Check if the stack is empty */ pub fn is_empty(&self) -> bool { return self.size() == 0; } /* Push */ pub fn push(&mut self, num: T) { let node = ListNode::new(num); node.borrow_mut().next = self.stack_peek.take(); self.stack_peek = Some(node); self.stk_size += 1; } /* Pop */ pub fn pop(&mut self) -> Option { self.stack_peek.take().map(|old_head| { self.stack_peek = old_head.borrow_mut().next.take(); self.stk_size -= 1; old_head.borrow().val }) } /* Return list for printing */ pub fn peek(&self) -> Option<&Rc>>> { self.stack_peek.as_ref() } /* Convert List to Array and return */ pub fn to_array(&self) -> Vec { fn _to_array(head: Option<&Rc>>>) -> Vec { if let Some(node) = head { let mut nums = _to_array(node.borrow().next.as_ref()); nums.push(node.borrow().val); return nums; } return Vec::new(); } _to_array(self.peek()) } } /* Driver Code */ fn main() { /* Access top of the stack element */ let mut stack = LinkedListStack::new(); /* Elements push onto stack */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("Stack stack = "); print_util::print_array(&stack.to_array()); /* Return list for printing */ let peek = stack.peek().unwrap().borrow().val; print!("\nTop element peek = {}", peek); /* Element pop from stack */ let pop = stack.pop().unwrap(); print!("\nPop element pop = {}, after pop stack = ", pop); print_util::print_array(&stack.to_array()); /* Get the length of the stack */ let size = stack.size(); print!("\nStack length size = {}", size); /* Check if empty */ let is_empty = stack.is_empty(); print!("\nIs stack empty = {}", is_empty); } ================================================ FILE: en/codes/rust/chapter_stack_and_queue/queue.rs ================================================ /* * File: queue.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; use std::collections::VecDeque; /* Driver Code */ pub fn main() { // Access front of the queue element let mut queue: VecDeque = VecDeque::new(); // Elements enqueue queue.push_back(1); queue.push_back(3); queue.push_back(2); queue.push_back(5); queue.push_back(4); print!("Queue queue = "); print_util::print_queue(&queue); // Return list for printing let peek = queue.front().unwrap(); println!("\nFront element peek = {peek}"); // Element dequeue let pop = queue.pop_front().unwrap(); print!("Dequeue element pop = {pop}, after dequeue queue = "); print_util::print_queue(&queue); // Get the length of the queue let size = queue.len(); print!("\nQueue length size = {size}"); // Check if the queue is empty let is_empty = queue.is_empty(); print!("\nIs queue empty = {is_empty}"); } ================================================ FILE: en/codes/rust/chapter_stack_and_queue/stack.rs ================================================ /* * File: stack.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Driver Code */ pub fn main() { // Access top of the stack element // In Rust, it's recommended to use Vec as a stack let mut stack: Vec = Vec::new(); // Elements push onto stack stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("Stack stack = "); print_util::print_array(&stack); // Return list for printing let peek = stack.last().unwrap(); print!("\nTop element peek = {peek}"); // Element pop from stack let pop = stack.pop().unwrap(); print!("\nPop element pop = {pop}, after pop stack = "); print_util::print_array(&stack); // Get the length of the stack let size = stack.len(); print!("\nStack length size = {size}"); // Check if the stack is empty let is_empty = stack.is_empty(); print!("\nIs stack empty = {is_empty}"); } ================================================ FILE: en/codes/rust/chapter_tree/array_binary_tree.rs ================================================ /* * File: array_binary_tree.rs * Created Time: 2023-07-25 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::{print_util, tree_node}; /* Binary tree class represented by array */ struct ArrayBinaryTree { tree: Vec>, } impl ArrayBinaryTree { /* Constructor */ fn new(arr: Vec>) -> Self { Self { tree: arr } } /* List capacity */ fn size(&self) -> i32 { self.tree.len() as i32 } /* Get value of node at index i */ fn val(&self, i: i32) -> Option { // If index is out of bounds, return None, representing empty position if i < 0 || i >= self.size() { None } else { self.tree[i as usize] } } /* Get index of left child node of node at index i */ fn left(&self, i: i32) -> i32 { 2 * i + 1 } /* Get index of right child node of node at index i */ fn right(&self, i: i32) -> i32 { 2 * i + 2 } /* Get index of parent node of node at index i */ fn parent(&self, i: i32) -> i32 { (i - 1) / 2 } /* Level-order traversal */ fn level_order(&self) -> Vec { self.tree.iter().filter_map(|&x| x).collect() } /* Depth-first traversal */ fn dfs(&self, i: i32, order: &'static str, res: &mut Vec) { if self.val(i).is_none() { return; } let val = self.val(i).unwrap(); // Preorder traversal if order == "pre" { res.push(val); } self.dfs(self.left(i), order, res); // Inorder traversal if order == "in" { res.push(val); } self.dfs(self.right(i), order, res); // Postorder traversal if order == "post" { res.push(val); } } /* Preorder traversal */ fn pre_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "pre", &mut res); res } /* Inorder traversal */ fn in_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "in", &mut res); res } /* Postorder traversal */ fn post_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "post", &mut res); res } } /* Driver Code */ fn main() { // Initialize binary tree // Here we use a function to generate a binary tree directly from an array let arr = vec![ Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15), ]; let root = tree_node::vec_to_tree(arr.clone()).unwrap(); println!("\nInitialize binary tree\n"); println!("Array representation of binary tree:"); println!( "[{}]", arr.iter() .map(|&val| if let Some(val) = val { format!("{val}") } else { "null".to_string() }) .collect::>() .join(", ") ); println!("Linked list representation of binary tree:"); print_util::print_tree(&root); // Binary tree class represented by array let abt = ArrayBinaryTree::new(arr); // Access node let i = 1; let l = abt.left(i); let r = abt.right(i); let p = abt.parent(i); println!( "\nCurrent node index is {}, value is {}", i, if let Some(val) = abt.val(i) { format!("{val}") } else { "null".to_string() } ); println!( "Left child index is {}, value is {}", l, if let Some(val) = abt.val(l) { format!("{val}") } else { "null".to_string() } ); println!( "Right child index is {}, value is {}", r, if let Some(val) = abt.val(r) { format!("{val}") } else { "null".to_string() } ); println!( "Parent node index is {}, value is {}", p, if let Some(val) = abt.val(p) { format!("{val}") } else { "null".to_string() } ); // Traverse tree let mut res = abt.level_order(); println!("\nLevel-order traversal is: {:?}", res); res = abt.pre_order(); println!("Pre-order traversal is: {:?}", res); res = abt.in_order(); println!("In-order traversal is: {:?}", res); res = abt.post_order(); println!("Post-order traversal is: {:?}", res); } ================================================ FILE: en/codes/rust/chapter_tree/avl_tree.rs ================================================ /* * File: avl_tree.rs * Created Time: 2023-07-14 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::{print_util, TreeNode}; use std::cell::RefCell; use std::cmp::Ordering; use std::rc::Rc; type OptionTreeNodeRc = Option>>; /* AVL tree */ struct AVLTree { root: OptionTreeNodeRc, // Root node } impl AVLTree { /* Constructor */ fn new() -> Self { Self { root: None } } /* Get node height */ fn height(node: OptionTreeNodeRc) -> i32 { // Empty node height is -1, leaf node height is 0 match node { Some(node) => node.borrow().height, None => -1, } } /* Update node height */ fn update_height(node: OptionTreeNodeRc) { if let Some(node) = node { let left = node.borrow().left.clone(); let right = node.borrow().right.clone(); // Node height equals the height of the tallest subtree + 1 node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1; } } /* Get balance factor */ fn balance_factor(node: OptionTreeNodeRc) -> i32 { match node { // Empty node balance factor is 0 None => 0, // Node balance factor = left subtree height - right subtree height Some(node) => { Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone()) } } } /* Right rotation operation */ fn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { match node { Some(node) => { let child = node.borrow().left.clone().unwrap(); let grand_child = child.borrow().right.clone(); // Using child as pivot, rotate node to the right child.borrow_mut().right = Some(node.clone()); node.borrow_mut().left = grand_child; // Update node height Self::update_height(Some(node)); Self::update_height(Some(child.clone())); // Return root node of subtree after rotation Some(child) } None => None, } } /* Left rotation operation */ fn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { match node { Some(node) => { let child = node.borrow().right.clone().unwrap(); let grand_child = child.borrow().left.clone(); // Using child as pivot, rotate node to the left child.borrow_mut().left = Some(node.clone()); node.borrow_mut().right = grand_child; // Update node height Self::update_height(Some(node)); Self::update_height(Some(child.clone())); // Return root node of subtree after rotation Some(child) } None => None, } } /* Perform rotation operation to restore balance to this subtree */ fn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { // Get balance factor of node let balance_factor = Self::balance_factor(node.clone()); // Left-leaning tree if balance_factor > 1 { let node = node.unwrap(); if Self::balance_factor(node.borrow().left.clone()) >= 0 { // Right rotation Self::right_rotate(Some(node)) } else { // First left rotation then right rotation let left = node.borrow().left.clone(); node.borrow_mut().left = Self::left_rotate(left); Self::right_rotate(Some(node)) } } // Right-leaning tree else if balance_factor < -1 { let node = node.unwrap(); if Self::balance_factor(node.borrow().right.clone()) <= 0 { // Left rotation Self::left_rotate(Some(node)) } else { // First right rotation then left rotation let right = node.borrow().right.clone(); node.borrow_mut().right = Self::right_rotate(right); Self::left_rotate(Some(node)) } } else { // Balanced tree, no rotation needed, return directly node } } /* Insert node */ fn insert(&mut self, val: i32) { self.root = Self::insert_helper(self.root.clone(), val); } /* Recursively insert node (helper method) */ fn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { match node { Some(mut node) => { /* 1. Find insertion position and insert node */ match { let node_val = node.borrow().val; node_val } .cmp(&val) { Ordering::Greater => { let left = node.borrow().left.clone(); node.borrow_mut().left = Self::insert_helper(left, val); } Ordering::Less => { let right = node.borrow().right.clone(); node.borrow_mut().right = Self::insert_helper(right, val); } Ordering::Equal => { return Some(node); // Duplicate node not inserted, return directly } } Self::update_height(Some(node.clone())); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = Self::rotate(Some(node)).unwrap(); // Return root node of subtree Some(node) } None => Some(TreeNode::new(val)), } } /* Remove node */ fn remove(&self, val: i32) { Self::remove_helper(self.root.clone(), val); } /* Recursively delete node (helper method) */ fn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { match node { Some(mut node) => { /* 1. Find node and delete */ if val < node.borrow().val { let left = node.borrow().left.clone(); node.borrow_mut().left = Self::remove_helper(left, val); } else if val > node.borrow().val { let right = node.borrow().right.clone(); node.borrow_mut().right = Self::remove_helper(right, val); } else if node.borrow().left.is_none() || node.borrow().right.is_none() { let child = if node.borrow().left.is_some() { node.borrow().left.clone() } else { node.borrow().right.clone() }; match child { // Number of child nodes = 0, delete node directly and return None => { return None; } // Number of child nodes = 1, delete node directly Some(child) => node = child, } } else { // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it let mut temp = node.borrow().right.clone().unwrap(); loop { let temp_left = temp.borrow().left.clone(); if temp_left.is_none() { break; } temp = temp_left.unwrap(); } let right = node.borrow().right.clone(); node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val); node.borrow_mut().val = temp.borrow().val; } Self::update_height(Some(node.clone())); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = Self::rotate(Some(node)).unwrap(); // Return root node of subtree Some(node) } None => None, } } /* Search node */ fn search(&self, val: i32) -> OptionTreeNodeRc { let mut cur = self.root.clone(); // Loop search, exit after passing leaf node while let Some(current) = cur.clone() { match current.borrow().val.cmp(&val) { // Target node is in cur's right subtree Ordering::Less => { cur = current.borrow().right.clone(); } // Target node is in cur's left subtree Ordering::Greater => { cur = current.borrow().left.clone(); } // Found target node, exit loop Ordering::Equal => { break; } } } // Return target node cur } } /* Driver Code */ fn main() { fn test_insert(tree: &mut AVLTree, val: i32) { tree.insert(val); println!("\nAfter inserting node {}, AVL tree is", val); print_util::print_tree(&tree.root.clone().unwrap()); } fn test_remove(tree: &mut AVLTree, val: i32) { tree.remove(val); println!("\nAfter deleting node {}, AVL tree is", val); print_util::print_tree(&tree.root.clone().unwrap()); } /* Please pay attention to how the AVL tree maintains balance after inserting nodes */ let mut avl_tree = AVLTree::new(); /* Insert node */ // Delete nodes test_insert(&mut avl_tree, 1); test_insert(&mut avl_tree, 2); test_insert(&mut avl_tree, 3); test_insert(&mut avl_tree, 4); test_insert(&mut avl_tree, 5); test_insert(&mut avl_tree, 8); test_insert(&mut avl_tree, 7); test_insert(&mut avl_tree, 9); test_insert(&mut avl_tree, 10); test_insert(&mut avl_tree, 6); /* Please pay attention to how the AVL tree maintains balance after deleting nodes */ test_insert(&mut avl_tree, 7); /* Remove node */ // Delete node with degree 1 test_remove(&mut avl_tree, 8); // Delete node with degree 2 test_remove(&mut avl_tree, 5); // Remove node with degree 1 test_remove(&mut avl_tree, 4); // Remove node with degree 2 /* Search node */ let node = avl_tree.search(7); if let Some(node) = node { println!( "\nFound node object is {:?}, node value = {}", &*node.borrow(), node.borrow().val ); } } ================================================ FILE: en/codes/rust/chapter_tree/binary_search_tree.rs ================================================ /* * File: binary_search_tree.rs * Created Time: 2023-04-20 * Author: xBLACKICEx (xBLACKICE@outlook.com)、night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::cell::RefCell; use std::cmp::Ordering; use std::rc::Rc; use hello_algo_rust::include::TreeNode; type OptionTreeNodeRc = Option>>; /* Binary search tree */ pub struct BinarySearchTree { root: OptionTreeNodeRc, } impl BinarySearchTree { /* Constructor */ pub fn new() -> Self { // Initialize empty tree Self { root: None } } /* Get binary tree root node */ pub fn get_root(&self) -> OptionTreeNodeRc { self.root.clone() } /* Search node */ pub fn search(&self, num: i32) -> OptionTreeNodeRc { let mut cur = self.root.clone(); // Loop search, exit after passing leaf node while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // Target node is in cur's right subtree Ordering::Greater => cur = node.borrow().right.clone(), // Target node is in cur's left subtree Ordering::Less => cur = node.borrow().left.clone(), // Found target node, exit loop Ordering::Equal => break, } } // Return target node cur } /* Insert node */ pub fn insert(&mut self, num: i32) { // If tree is empty, initialize root node if self.root.is_none() { self.root = Some(TreeNode::new(num)); return; } let mut cur = self.root.clone(); let mut pre = None; // Loop search, exit after passing leaf node while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // Found duplicate node, return directly Ordering::Equal => return, // Insertion position is in cur's right subtree Ordering::Greater => { pre = cur.clone(); cur = node.borrow().right.clone(); } // Insertion position is in cur's left subtree Ordering::Less => { pre = cur.clone(); cur = node.borrow().left.clone(); } } } // Insert node let pre = pre.unwrap(); let node = Some(TreeNode::new(num)); if num > pre.borrow().val { pre.borrow_mut().right = node; } else { pre.borrow_mut().left = node; } } /* Remove node */ pub fn remove(&mut self, num: i32) { // If tree is empty, return directly if self.root.is_none() { return; } let mut cur = self.root.clone(); let mut pre = None; // Loop search, exit after passing leaf node while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // Found node to delete, exit loop Ordering::Equal => break, // Node to delete is in cur's right subtree Ordering::Greater => { pre = cur.clone(); cur = node.borrow().right.clone(); } // Node to delete is in cur's left subtree Ordering::Less => { pre = cur.clone(); cur = node.borrow().left.clone(); } } } // If no node to delete, return directly if cur.is_none() { return; } let cur = cur.unwrap(); let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone()); match (left_child.clone(), right_child.clone()) { // Number of child nodes = 0 or 1 (None, None) | (Some(_), None) | (None, Some(_)) => { // When number of child nodes = 0 / 1, child = nullptr / that child node let child = left_child.or(right_child); let pre = pre.unwrap(); // Delete node cur if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) { let left = pre.borrow().left.clone(); if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) { pre.borrow_mut().left = child; } else { pre.borrow_mut().right = child; } } else { // If deleted node is root node, reassign root node self.root = child; } } // Number of child nodes = 2 (Some(_), Some(_)) => { // Get next node of cur in inorder traversal let mut tmp = cur.borrow().right.clone(); while let Some(node) = tmp.clone() { if node.borrow().left.is_some() { tmp = node.borrow().left.clone(); } else { break; } } let tmp_val = tmp.unwrap().borrow().val; // Recursively delete node tmp self.remove(tmp_val); // Replace cur with tmp cur.borrow_mut().val = tmp_val; } } } } /* Driver Code */ fn main() { /* Initialize binary search tree */ let mut bst = BinarySearchTree::new(); // Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for &num in &nums { bst.insert(num); } println!("\nInitialized binary tree is\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); /* Search node */ let node = bst.search(7); println!( "\nFound node object is {:?}, node value = {}", node.clone().unwrap(), node.clone().unwrap().borrow().val ); /* Insert node */ bst.insert(16); println!("\nAfter inserting node 16, binary tree is\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); /* Remove node */ bst.remove(1); println!("\nAfter removing node 1, binary tree is\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); bst.remove(2); println!("\nAfter removing node 2, binary tree is\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); bst.remove(4); println!("\nAfter removing node 4, binary tree is\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); } ================================================ FILE: en/codes/rust/chapter_tree/binary_tree.rs ================================================ /** * File: binary_tree.rs * Created Time: 2023-02-27 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use std::rc::Rc; use hello_algo_rust::include::{print_util, TreeNode}; /* Driver Code */ fn main() { /* Initialize binary tree */ // Initialize nodes let n1 = TreeNode::new(1); let n2 = TreeNode::new(2); let n3 = TreeNode::new(3); let n4 = TreeNode::new(4); let n5 = TreeNode::new(5); // Build references (pointers) between nodes n1.borrow_mut().left = Some(Rc::clone(&n2)); n1.borrow_mut().right = Some(Rc::clone(&n3)); n2.borrow_mut().left = Some(Rc::clone(&n4)); n2.borrow_mut().right = Some(Rc::clone(&n5)); println!("\nInitialize binary tree\n"); print_util::print_tree(&n1); // Insert node and delete node let p = TreeNode::new(0); // Delete node p.borrow_mut().left = Some(Rc::clone(&n2)); n1.borrow_mut().left = Some(Rc::clone(&p)); println!("\nAfter inserting node P\n"); print_util::print_tree(&n1); // Remove node P drop(p); n1.borrow_mut().left = Some(Rc::clone(&n2)); println!("\nAfter removing node P\n"); print_util::print_tree(&n1); } ================================================ FILE: en/codes/rust/chapter_tree/binary_tree_bfs.rs ================================================ /* * File: binary_tree_bfs.rs * Created Time: 2023-04-07 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use hello_algo_rust::op_vec; use std::collections::VecDeque; use std::{cell::RefCell, rc::Rc}; /* Level-order traversal */ fn level_order(root: &Rc>) -> Vec { // Initialize queue, add root node let mut que = VecDeque::new(); que.push_back(root.clone()); // Initialize a list to save the traversal sequence let mut vec = Vec::new(); while let Some(node) = que.pop_front() { // Dequeue vec.push(node.borrow().val); // Save node value if let Some(left) = node.borrow().left.as_ref() { que.push_back(left.clone()); // Left child node enqueue } if let Some(right) = node.borrow().right.as_ref() { que.push_back(right.clone()); // Right child node enqueue }; } vec } /* Driver Code */ fn main() { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]).unwrap(); println!("Initialize binary tree\n"); print_util::print_tree(&root); /* Level-order traversal */ let vec = level_order(&root); print!("\nLevel-order traversal node sequence = {:?}", vec); } ================================================ FILE: en/codes/rust/chapter_tree/binary_tree_dfs.rs ================================================ /* * File: binary_tree_dfs.rs * Created Time: 2023-04-06 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use hello_algo_rust::op_vec; use std::cell::RefCell; use std::rc::Rc; /* Preorder traversal */ fn pre_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // Visit priority: root node -> left subtree -> right subtree let node = node.borrow(); res.push(node.val); dfs(node.left.as_ref(), res); dfs(node.right.as_ref(), res); } } dfs(root, &mut result); result } /* Inorder traversal */ fn in_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // Visit priority: left subtree -> root node -> right subtree let node = node.borrow(); dfs(node.left.as_ref(), res); res.push(node.val); dfs(node.right.as_ref(), res); } } dfs(root, &mut result); result } /* Postorder traversal */ fn post_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // Visit priority: left subtree -> right subtree -> root node let node = node.borrow(); dfs(node.left.as_ref(), res); dfs(node.right.as_ref(), res); res.push(node.val); } } dfs(root, &mut result); result } /* Driver Code */ fn main() { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]); println!("Initialize binary tree\n"); print_util::print_tree(root.as_ref().unwrap()); /* Preorder traversal */ let vec = pre_order(root.as_ref()); println!("\nPre-order traversal node sequence = {:?}", vec); /* Inorder traversal */ let vec = in_order(root.as_ref()); println!("\nIn-order traversal node sequence = {:?}", vec); /* Postorder traversal */ let vec = post_order(root.as_ref()); print!("\nPost-order traversal node sequence = {:?}", vec); } ================================================ FILE: en/codes/rust/src/include/list_node.rs ================================================ /* * File: list_node.rs * Created Time: 2023-03-05 * Author: codingonion (coderonion@gmail.com), rongyi (hiarongyi@gmail.com) */ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; #[derive(Debug)] pub struct ListNode { pub val: T, pub next: Option>>>, } impl ListNode { pub fn new(val: T) -> Rc>> { Rc::new(RefCell::new(ListNode { val, next: None })) } /* Deserialize array to linked list */ pub fn arr_to_linked_list(array: &[T]) -> Option>>> where T: Copy + Clone, { let mut head = None; // insert in reverse order for item in array.iter().rev() { let node = Rc::new(RefCell::new(ListNode { val: *item, next: head.take(), })); head = Some(node); } head } /* Convert linked list to hash table */ pub fn linked_list_to_hashmap( linked_list: Option>>>, ) -> HashMap>>> where T: std::hash::Hash + Eq + Copy + Clone, { let mut hashmap = HashMap::new(); let mut node = linked_list; while let Some(cur) = node { let borrow = cur.borrow(); hashmap.insert(borrow.val.clone(), cur.clone()); node = borrow.next.clone(); } hashmap } } ================================================ FILE: en/codes/rust/src/include/mod.rs ================================================ /* * File: include.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICE@outlook.com) */ pub mod list_node; pub mod print_util; pub mod tree_node; pub mod vertex; // rexport to include pub use list_node::*; pub use print_util::*; pub use tree_node::*; pub use vertex::*; ================================================ FILE: en/codes/rust/src/include/print_util.rs ================================================ /* * File: print_util.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use std::cell::{Cell, RefCell}; use std::fmt::Display; use std::collections::{HashMap, VecDeque}; use std::rc::Rc; use super::list_node::ListNode; use super::tree_node::{TreeNode, vec_to_tree}; struct Trunk<'a, 'b> { prev: Option<&'a Trunk<'a, 'b>>, str: Cell<&'b str>, } /* Print array */ pub fn print_array(nums: &[T]) { print!("["); if nums.len() > 0 { for (i, num) in nums.iter().enumerate() { print!("{}{}", num, if i == nums.len() - 1 {"]"} else {", "} ); } } else { print!("]"); } } /* Print hash table */ pub fn print_hash_map(map: &HashMap) { for (key, value) in map { println!("{key} -> {value}"); } } /* Print queue (deque) */ pub fn print_queue(queue: &VecDeque) { print!("["); let iter = queue.iter(); for (i, data) in iter.enumerate() { print!("{}{}", data, if i == queue.len() - 1 {"]"} else {", "} ); } } /* Print linked list */ pub fn print_linked_list(head: &Rc>>) { print!("{}{}", head.borrow().val, if head.borrow().next.is_none() {"\n"} else {" -> "}); if let Some(node) = &head.borrow().next { return print_linked_list(node); } } /* Print binary tree */ pub fn print_tree(root: &Rc>) { _print_tree(Some(root), None, false); } /* Print binary tree */ fn _print_tree(root: Option<&Rc>>, prev: Option<&Trunk>, is_right: bool) { if let Some(node) = root { let mut prev_str = " "; let trunk = Trunk { prev, str: Cell::new(prev_str) }; _print_tree(node.borrow().right.as_ref(), Some(&trunk), true); if prev.is_none() { trunk.str.set("———"); } else if is_right { trunk.str.set("/———"); prev_str = " |"; } else { trunk.str.set("\\———"); prev.as_ref().unwrap().str.set(prev_str); } show_trunks(Some(&trunk)); println!(" {}", node.borrow().val); if let Some(prev) = prev { prev.str.set(prev_str); } trunk.str.set(" |"); _print_tree(node.borrow().left.as_ref(), Some(&trunk), false); } } fn show_trunks(trunk: Option<&Trunk>) { if let Some(trunk) = trunk { show_trunks(trunk.prev); print!("{}", trunk.str.get()); } } /* Print heap */ pub fn print_heap(heap: Vec) { println!("Array representation of heap: {:?}", heap); println!("Heap tree representation:"); if let Some(root) = vec_to_tree(heap.into_iter().map(|val| Some(val)).collect()) { print_tree(&root); } } ================================================ FILE: en/codes/rust/src/include/tree_node.rs ================================================ /* * File: tree_node.rs * Created Time: 2023-02-27 * Author: xBLACKICEx (xBLACKICE@outlook.com), night-cruise (2586447362@qq.com) */ use std::cell::RefCell; use std::rc::Rc; /* Binary tree node type */ #[derive(Debug)] pub struct TreeNode { pub val: i32, pub height: i32, pub parent: Option>>, pub left: Option>>, pub right: Option>>, } impl TreeNode { /* Constructor */ pub fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, height: 0, parent: None, left: None, right: None, })) } } #[macro_export] macro_rules! op_vec { ( $( $x:expr ),* ) => { vec![ $(Option::from($x)),* ] }; } // For the serialization encoding rules, please refer to: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Array representation of binary tree: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // Linked list representation of binary tree: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* Deserialize a list into a binary tree: recursion */ fn vec_to_tree_dfs(arr: &[Option], i: usize) -> Option>> { if i >= arr.len() || arr[i].is_none() { return None; } let root = TreeNode::new(arr[i].unwrap()); root.borrow_mut().left = vec_to_tree_dfs(arr, 2 * i + 1); root.borrow_mut().right = vec_to_tree_dfs(arr, 2 * i + 2); Some(root) } /* Deserialize a list into a binary tree */ pub fn vec_to_tree(arr: Vec>) -> Option>> { vec_to_tree_dfs(&arr, 0) } /* Serialize a binary tree into a list: recursion */ fn tree_to_vec_dfs(root: Option<&Rc>>, i: usize, res: &mut Vec>) { if let Some(root) = root { // i + 1 is the minimum valid size to access index i while res.len() < i + 1 { res.push(None); } res[i] = Some(root.borrow().val); tree_to_vec_dfs(root.borrow().left.as_ref(), 2 * i + 1, res); tree_to_vec_dfs(root.borrow().right.as_ref(), 2 * i + 2, res); } } /* Serialize a binary tree into a list */ pub fn tree_to_vec(root: Option>>) -> Vec> { let mut res = vec![]; tree_to_vec_dfs(root.as_ref(), 0, &mut res); res } ================================================ FILE: en/codes/rust/src/include/vertex.rs ================================================ /* * File: vertex.rs * Created Time: 2023-07-13 * Author: night-cruise (2586447362@qq.com) */ /* Vertex type */ #[derive(Copy, Clone, Hash, PartialEq, Eq)] pub struct Vertex { pub val: i32, } impl From for Vertex { fn from(value: i32) -> Self { Self { val: value } } } /* Input value list vals, return vertex list vets */ pub fn vals_to_vets(vals: Vec) -> Vec { vals.into_iter().map(|val| val.into()).collect() } /* Input vertex list vets, return value list vals */ pub fn vets_to_vals(vets: Vec) -> Vec { vets.into_iter().map(|vet| vet.val).collect() } ================================================ FILE: en/codes/rust/src/lib.rs ================================================ pub mod include; ================================================ FILE: en/codes/swift/.gitignore ================================================ # Created by https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager # Edit at https://www.toptal.com/developers/gitignore?templates=objective-c,swift,swiftpackagemanager ### Objective-C ### # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## User settings xcuserdata/ ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint *.xccheckout ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ DerivedData/ *.moved-aside *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 ## Obj-C/Swift specific *.hmap ## App packaging *.ipa *.dSYM.zip *.dSYM # CocoaPods # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build/ # fastlane # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output # Code Injection # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ ### Objective-C Patch ### ### Swift ### # Xcode # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins # Package.resolved # *.xcodeproj # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project # .swiftpm .build/ # CocoaPods # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts # Accio dependency management Dependencies/ .accio/ # fastlane # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control # Code Injection # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode ### SwiftPackageManager ### Packages xcuserdata *.xcodeproj # End of https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager ================================================ FILE: en/codes/swift/Package.resolved ================================================ { "pins" : [ { "identity" : "swift-collections", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { "branch" : "release/1.1", "revision" : "4a1d92ba85027010d2c528c05576cde9a362254b" } } ], "version" : 2 } ================================================ FILE: en/codes/swift/Package.swift ================================================ // swift-tools-version: 5.7 import PackageDescription let package = Package( name: "HelloAlgo", products: [ // chapter_computational_complexity .executable(name: "iteration", targets: ["iteration"]), .executable(name: "recursion", targets: ["recursion"]), .executable(name: "time_complexity", targets: ["time_complexity"]), .executable(name: "worst_best_time_complexity", targets: ["worst_best_time_complexity"]), .executable(name: "space_complexity", targets: ["space_complexity"]), // chapter_array_and_linkedlist .executable(name: "array", targets: ["array"]), .executable(name: "linked_list", targets: ["linked_list"]), .executable(name: "list", targets: ["list"]), .executable(name: "my_list", targets: ["my_list"]), // chapter_stack_and_queue .executable(name: "stack", targets: ["stack"]), .executable(name: "linkedlist_stack", targets: ["linkedlist_stack"]), .executable(name: "array_stack", targets: ["array_stack"]), .executable(name: "queue", targets: ["queue"]), .executable(name: "linkedlist_queue", targets: ["linkedlist_queue"]), .executable(name: "array_queue", targets: ["array_queue"]), .executable(name: "deque", targets: ["deque"]), .executable(name: "linkedlist_deque", targets: ["linkedlist_deque"]), .executable(name: "array_deque", targets: ["array_deque"]), // chapter_hashing .executable(name: "hash_map", targets: ["hash_map"]), .executable(name: "array_hash_map", targets: ["array_hash_map"]), .executable(name: "hash_map_chaining", targets: ["hash_map_chaining"]), .executable(name: "hash_map_open_addressing", targets: ["hash_map_open_addressing"]), .executable(name: "simple_hash", targets: ["simple_hash"]), .executable(name: "built_in_hash", targets: ["built_in_hash"]), // chapter_tree .executable(name: "binary_tree", targets: ["binary_tree"]), .executable(name: "binary_tree_bfs", targets: ["binary_tree_bfs"]), .executable(name: "binary_tree_dfs", targets: ["binary_tree_dfs"]), .executable(name: "array_binary_tree", targets: ["array_binary_tree"]), .executable(name: "binary_search_tree", targets: ["binary_search_tree"]), .executable(name: "avl_tree", targets: ["avl_tree"]), // chapter_heap .executable(name: "heap", targets: ["heap"]), .executable(name: "my_heap", targets: ["my_heap"]), .executable(name: "top_k", targets: ["top_k"]), // chapter_graph .executable(name: "graph_adjacency_matrix", targets: ["graph_adjacency_matrix"]), .executable(name: "graph_adjacency_list", targets: ["graph_adjacency_list"]), .executable(name: "graph_bfs", targets: ["graph_bfs"]), .executable(name: "graph_dfs", targets: ["graph_dfs"]), // chapter_searching .executable(name: "binary_search", targets: ["binary_search"]), .executable(name: "binary_search_insertion", targets: ["binary_search_insertion"]), .executable(name: "binary_search_edge", targets: ["binary_search_edge"]), .executable(name: "two_sum", targets: ["two_sum"]), .executable(name: "linear_search", targets: ["linear_search"]), .executable(name: "hashing_search", targets: ["hashing_search"]), // chapter_sorting .executable(name: "selection_sort", targets: ["selection_sort"]), .executable(name: "bubble_sort", targets: ["bubble_sort"]), .executable(name: "insertion_sort", targets: ["insertion_sort"]), .executable(name: "quick_sort", targets: ["quick_sort"]), .executable(name: "merge_sort", targets: ["merge_sort"]), .executable(name: "heap_sort", targets: ["heap_sort"]), .executable(name: "bucket_sort", targets: ["bucket_sort"]), .executable(name: "counting_sort", targets: ["counting_sort"]), .executable(name: "radix_sort", targets: ["radix_sort"]), // chapter_divide_and_conquer .executable(name: "binary_search_recur", targets: ["binary_search_recur"]), .executable(name: "build_tree", targets: ["build_tree"]), .executable(name: "hanota", targets: ["hanota"]), // chapter_backtracking .executable(name: "preorder_traversal_i_compact", targets: ["preorder_traversal_i_compact"]), .executable(name: "preorder_traversal_ii_compact", targets: ["preorder_traversal_ii_compact"]), .executable(name: "preorder_traversal_iii_compact", targets: ["preorder_traversal_iii_compact"]), .executable(name: "preorder_traversal_iii_template", targets: ["preorder_traversal_iii_template"]), .executable(name: "permutations_i", targets: ["permutations_i"]), .executable(name: "permutations_ii", targets: ["permutations_ii"]), .executable(name: "subset_sum_i_naive", targets: ["subset_sum_i_naive"]), .executable(name: "subset_sum_i", targets: ["subset_sum_i"]), .executable(name: "subset_sum_ii", targets: ["subset_sum_ii"]), .executable(name: "n_queens", targets: ["n_queens"]), // chapter_dynamic_programming .executable(name: "climbing_stairs_backtrack", targets: ["climbing_stairs_backtrack"]), .executable(name: "climbing_stairs_dfs", targets: ["climbing_stairs_dfs"]), .executable(name: "climbing_stairs_dfs_mem", targets: ["climbing_stairs_dfs_mem"]), .executable(name: "climbing_stairs_dp", targets: ["climbing_stairs_dp"]), .executable(name: "min_cost_climbing_stairs_dp", targets: ["min_cost_climbing_stairs_dp"]), .executable(name: "climbing_stairs_constraint_dp", targets: ["climbing_stairs_constraint_dp"]), .executable(name: "min_path_sum", targets: ["min_path_sum"]), .executable(name: "knapsack", targets: ["knapsack"]), .executable(name: "unbounded_knapsack", targets: ["unbounded_knapsack"]), .executable(name: "coin_change", targets: ["coin_change"]), .executable(name: "coin_change_ii", targets: ["coin_change_ii"]), .executable(name: "edit_distance", targets: ["edit_distance"]), // chapter_greedy .executable(name: "coin_change_greedy", targets: ["coin_change_greedy"]), .executable(name: "fractional_knapsack", targets: ["fractional_knapsack"]), .executable(name: "max_capacity", targets: ["max_capacity"]), .executable(name: "max_product_cutting", targets: ["max_product_cutting"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-collections", branch: "release/1.1"), ], targets: [ // helper .target(name: "utils", path: "utils"), .target(name: "graph_adjacency_list_target", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list_target.swift"], swiftSettings: [.define("TARGET")]), .target(name: "binary_search_insertion_target", path: "chapter_searching", sources: ["binary_search_insertion_target.swift"], swiftSettings: [.define("TARGET")]), // chapter_computational_complexity .executableTarget(name: "iteration", path: "chapter_computational_complexity", sources: ["iteration.swift"]), .executableTarget(name: "recursion", path: "chapter_computational_complexity", sources: ["recursion.swift"]), .executableTarget(name: "time_complexity", path: "chapter_computational_complexity", sources: ["time_complexity.swift"]), .executableTarget(name: "worst_best_time_complexity", path: "chapter_computational_complexity", sources: ["worst_best_time_complexity.swift"]), .executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]), // chapter_array_and_linkedlist .executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]), .executableTarget(name: "linked_list", dependencies: ["utils"], path: "chapter_array_and_linkedlist", sources: ["linked_list.swift"]), .executableTarget(name: "list", path: "chapter_array_and_linkedlist", sources: ["list.swift"]), .executableTarget(name: "my_list", path: "chapter_array_and_linkedlist", sources: ["my_list.swift"]), // chapter_stack_and_queue .executableTarget(name: "stack", path: "chapter_stack_and_queue", sources: ["stack.swift"]), .executableTarget(name: "linkedlist_stack", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_stack.swift"]), .executableTarget(name: "array_stack", path: "chapter_stack_and_queue", sources: ["array_stack.swift"]), .executableTarget(name: "queue", path: "chapter_stack_and_queue", sources: ["queue.swift"]), .executableTarget(name: "linkedlist_queue", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_queue.swift"]), .executableTarget(name: "array_queue", path: "chapter_stack_and_queue", sources: ["array_queue.swift"]), .executableTarget(name: "deque", path: "chapter_stack_and_queue", sources: ["deque.swift"]), .executableTarget(name: "linkedlist_deque", path: "chapter_stack_and_queue", sources: ["linkedlist_deque.swift"]), .executableTarget(name: "array_deque", path: "chapter_stack_and_queue", sources: ["array_deque.swift"]), // chapter_hashing .executableTarget(name: "hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map.swift"]), .executableTarget(name: "array_hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["array_hash_map.swift"]), .executableTarget(name: "hash_map_chaining", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_chaining.swift"]), .executableTarget(name: "hash_map_open_addressing", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_open_addressing.swift"]), .executableTarget(name: "simple_hash", path: "chapter_hashing", sources: ["simple_hash.swift"]), .executableTarget(name: "built_in_hash", dependencies: ["utils"], path: "chapter_hashing", sources: ["built_in_hash.swift"]), // chapter_tree .executableTarget(name: "binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree.swift"]), .executableTarget(name: "binary_tree_bfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_bfs.swift"]), .executableTarget(name: "binary_tree_dfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_dfs.swift"]), .executableTarget(name: "array_binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["array_binary_tree.swift"]), .executableTarget(name: "binary_search_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_search_tree.swift"]), .executableTarget(name: "avl_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["avl_tree.swift"]), // chapter_heap .executableTarget(name: "heap", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["heap.swift"]), .executableTarget(name: "my_heap", dependencies: ["utils"], path: "chapter_heap", sources: ["my_heap.swift"]), .executableTarget(name: "top_k", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["top_k.swift"]), // chapter_graph .executableTarget(name: "graph_adjacency_matrix", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_matrix.swift"]), .executableTarget(name: "graph_adjacency_list", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list.swift"]), .executableTarget(name: "graph_bfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_bfs.swift"]), .executableTarget(name: "graph_dfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_dfs.swift"]), // chapter_searching .executableTarget(name: "binary_search", path: "chapter_searching", sources: ["binary_search.swift"]), .executableTarget(name: "binary_search_insertion", path: "chapter_searching", sources: ["binary_search_insertion.swift"]), .executableTarget(name: "binary_search_edge", dependencies: ["binary_search_insertion_target"], path: "chapter_searching", sources: ["binary_search_edge.swift"]), .executableTarget(name: "two_sum", path: "chapter_searching", sources: ["two_sum.swift"]), .executableTarget(name: "linear_search", dependencies: ["utils"], path: "chapter_searching", sources: ["linear_search.swift"]), .executableTarget(name: "hashing_search", dependencies: ["utils"], path: "chapter_searching", sources: ["hashing_search.swift"]), // chapter_sorting .executableTarget(name: "selection_sort", path: "chapter_sorting", sources: ["selection_sort.swift"]), .executableTarget(name: "bubble_sort", path: "chapter_sorting", sources: ["bubble_sort.swift"]), .executableTarget(name: "insertion_sort", path: "chapter_sorting", sources: ["insertion_sort.swift"]), .executableTarget(name: "quick_sort", path: "chapter_sorting", sources: ["quick_sort.swift"]), .executableTarget(name: "merge_sort", path: "chapter_sorting", sources: ["merge_sort.swift"]), .executableTarget(name: "heap_sort", path: "chapter_sorting", sources: ["heap_sort.swift"]), .executableTarget(name: "bucket_sort", path: "chapter_sorting", sources: ["bucket_sort.swift"]), .executableTarget(name: "counting_sort", path: "chapter_sorting", sources: ["counting_sort.swift"]), .executableTarget(name: "radix_sort", path: "chapter_sorting", sources: ["radix_sort.swift"]), // chapter_divide_and_conquer .executableTarget(name: "binary_search_recur", path: "chapter_divide_and_conquer", sources: ["binary_search_recur.swift"]), .executableTarget(name: "build_tree", dependencies: ["utils"], path: "chapter_divide_and_conquer", sources: ["build_tree.swift"]), .executableTarget(name: "hanota", path: "chapter_divide_and_conquer", sources: ["hanota.swift"]), // chapter_backtracking .executableTarget(name: "preorder_traversal_i_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_i_compact.swift"]), .executableTarget(name: "preorder_traversal_ii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_ii_compact.swift"]), .executableTarget(name: "preorder_traversal_iii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_compact.swift"]), .executableTarget(name: "preorder_traversal_iii_template", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_template.swift"]), .executableTarget(name: "permutations_i", path: "chapter_backtracking", sources: ["permutations_i.swift"]), .executableTarget(name: "permutations_ii", path: "chapter_backtracking", sources: ["permutations_ii.swift"]), .executableTarget(name: "subset_sum_i_naive", path: "chapter_backtracking", sources: ["subset_sum_i_naive.swift"]), .executableTarget(name: "subset_sum_i", path: "chapter_backtracking", sources: ["subset_sum_i.swift"]), .executableTarget(name: "subset_sum_ii", path: "chapter_backtracking", sources: ["subset_sum_ii.swift"]), .executableTarget(name: "n_queens", path: "chapter_backtracking", sources: ["n_queens.swift"]), // chapter_dynamic_programming .executableTarget(name: "climbing_stairs_backtrack", path: "chapter_dynamic_programming", sources: ["climbing_stairs_backtrack.swift"]), .executableTarget(name: "climbing_stairs_dfs", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs.swift"]), .executableTarget(name: "climbing_stairs_dfs_mem", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs_mem.swift"]), .executableTarget(name: "climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dp.swift"]), .executableTarget(name: "min_cost_climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["min_cost_climbing_stairs_dp.swift"]), .executableTarget(name: "climbing_stairs_constraint_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_constraint_dp.swift"]), .executableTarget(name: "min_path_sum", path: "chapter_dynamic_programming", sources: ["min_path_sum.swift"]), .executableTarget(name: "knapsack", path: "chapter_dynamic_programming", sources: ["knapsack.swift"]), .executableTarget(name: "unbounded_knapsack", path: "chapter_dynamic_programming", sources: ["unbounded_knapsack.swift"]), .executableTarget(name: "coin_change", path: "chapter_dynamic_programming", sources: ["coin_change.swift"]), .executableTarget(name: "coin_change_ii", path: "chapter_dynamic_programming", sources: ["coin_change_ii.swift"]), .executableTarget(name: "edit_distance", path: "chapter_dynamic_programming", sources: ["edit_distance.swift"]), // chapter_greedy .executableTarget(name: "coin_change_greedy", path: "chapter_greedy", sources: ["coin_change_greedy.swift"]), .executableTarget(name: "fractional_knapsack", path: "chapter_greedy", sources: ["fractional_knapsack.swift"]), .executableTarget(name: "max_capacity", path: "chapter_greedy", sources: ["max_capacity.swift"]), .executableTarget(name: "max_product_cutting", path: "chapter_greedy", sources: ["max_product_cutting.swift"]), ] ) ================================================ FILE: en/codes/swift/chapter_array_and_linkedlist/array.swift ================================================ /** * File: array.swift * Created Time: 2023-01-05 * Author: nuomi1 (nuomi1@qq.com) */ /* Random access to element */ func randomAccess(nums: [Int]) -> Int { // Randomly select a number in interval [0, nums.count) let randomIndex = nums.indices.randomElement()! // Retrieve and return the random element let randomNum = nums[randomIndex] return randomNum } /* Extend array length */ func extend(nums: [Int], enlarge: Int) -> [Int] { // Initialize an array with extended length var res = Array(repeating: 0, count: nums.count + enlarge) // Copy all elements from the original array to the new array for i in nums.indices { res[i] = nums[i] } // Return the extended new array return res } /* Insert element num at index index in the array */ func insert(nums: inout [Int], num: Int, index: Int) { // Move all elements at and after index index backward by one position for i in nums.indices.dropFirst(index).reversed() { nums[i] = nums[i - 1] } // Assign num to the element at index index nums[index] = num } /* Remove the element at index index */ func remove(nums: inout [Int], index: Int) { // Move all elements after index index forward by one position for i in nums.indices.dropFirst(index).dropLast() { nums[i] = nums[i + 1] } } /* Traverse array */ func traverse(nums: [Int]) { var count = 0 // Traverse array by index for i in nums.indices { count += nums[i] } // Direct traversal of array elements for num in nums { count += num } // Traverse simultaneously data index and elements for (i, num) in nums.enumerated() { count += nums[i] count += num } } /* Find the specified element in the array */ func find(nums: [Int], target: Int) -> Int { for i in nums.indices { if nums[i] == target { return i } } return -1 } @main enum _Array { /* Driver Code */ static func main() { /* Initialize array */ let arr = Array(repeating: 0, count: 5) print("Array arr = \(arr)") var nums = [1, 3, 2, 5, 4] print("Array nums = \(nums)") /* Insert element */ let randomNum = randomAccess(nums: nums) print("Get random element \(randomNum) from nums") /* Traverse array */ nums = extend(nums: nums, enlarge: 3) print("Extend array length to 8, get nums = \(nums)") /* Insert element */ insert(nums: &nums, num: 6, index: 3) print("Insert number 6 at index 3, get nums = \(nums)") /* Remove element */ remove(nums: &nums, index: 2) print("Delete element at index 2, get nums = \(nums)") /* Traverse array */ traverse(nums: nums) /* Find element */ let index = find(nums: nums, target: 3) print("Find element 3 in nums, index = \(index)") } } ================================================ FILE: en/codes/swift/chapter_array_and_linkedlist/linked_list.swift ================================================ /** * File: linked_list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Insert node P after node n0 in the linked list */ func insert(n0: ListNode, P: ListNode) { let n1 = n0.next P.next = n1 n0.next = P } /* Remove the first node after node n0 in the linked list */ func remove(n0: ListNode) { if n0.next == nil { return } // n0 -> P -> n1 let P = n0.next let n1 = P?.next n0.next = n1 } /* Access the node at index index in the linked list */ func access(head: ListNode, index: Int) -> ListNode? { var head: ListNode? = head for _ in 0 ..< index { if head == nil { return nil } head = head?.next } return head } /* Find the first node with value target in the linked list */ func find(head: ListNode, target: Int) -> Int { var head: ListNode? = head var index = 0 while head != nil { if head?.val == target { return index } head = head?.next index += 1 } return -1 } @main enum LinkedList { /* Driver Code */ static func main() { /* Initialize linked list */ // Initialize each node let n0 = ListNode(x: 1) let n1 = ListNode(x: 3) let n2 = ListNode(x: 2) let n3 = ListNode(x: 5) let n4 = ListNode(x: 4) // Build references between nodes n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 print("Initialized linked list is") PrintUtil.printLinkedList(head: n0) /* Insert node */ insert(n0: n0, P: ListNode(x: 0)) print("Linked list after inserting node is") PrintUtil.printLinkedList(head: n0) /* Remove node */ remove(n0: n0) print("Linked list after removing node is") PrintUtil.printLinkedList(head: n0) /* Access node */ let node = access(head: n0, index: 3) print("Value of node at index 3 in linked list = \(node!.val)") /* Search node */ let index = find(head: n0, target: 2) print("Index of node with value 2 in linked list = \(index)") } } ================================================ FILE: en/codes/swift/chapter_array_and_linkedlist/list.swift ================================================ /** * File: list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ @main enum List { /* Driver Code */ static func main() { /* Initialize list */ var nums = [1, 3, 2, 5, 4] print("List nums = \(nums)") /* Update element */ let num = nums[1] print("Access element at index 1, get num = \(num)") /* Add elements at the end */ nums[1] = 0 print("Update element at index 1 to 0, get nums = \(nums)") /* Remove element */ nums.removeAll() print("After clearing list, nums = \(nums)") /* Direct traversal of list elements */ nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) print("After adding elements, nums = \(nums)") /* Sort list */ nums.insert(6, at: 3) print("Insert number 6 at index 3, get nums = \(nums)") /* Remove element */ nums.remove(at: 3) print("Delete element at index 3, get nums = \(nums)") /* Traverse list by index */ var count = 0 for i in nums.indices { count += nums[i] } /* Directly traverse list elements */ count = 0 for x in nums { count += x } /* Concatenate two lists */ let nums1 = [6, 8, 7, 10, 9] nums.append(contentsOf: nums1) print("After concatenating list nums1 to nums, get nums = \(nums)") /* Sort list */ nums.sort() print("After sorting list, nums = \(nums)") } } ================================================ FILE: en/codes/swift/chapter_array_and_linkedlist/my_list.swift ================================================ /** * File: my_list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ /* List class */ class MyList { private var arr: [Int] // Array (stores list elements) private var _capacity: Int // List capacity private var _size: Int // List length (current number of elements) private let extendRatio: Int // Multiple by which the list capacity is extended each time /* Constructor */ init() { _capacity = 10 _size = 0 extendRatio = 2 arr = Array(repeating: 0, count: _capacity) } /* Get list length (current number of elements) */ func size() -> Int { _size } /* Get list capacity */ func capacity() -> Int { _capacity } /* Update element */ func get(index: Int) -> Int { // Throw error if index out of bounds, same below if index < 0 || index >= size() { fatalError("Index out of bounds") } return arr[index] } /* Add elements at the end */ func set(index: Int, num: Int) { if index < 0 || index >= size() { fatalError("Index out of bounds") } arr[index] = num } /* Direct traversal of list elements */ func add(num: Int) { // When the number of elements exceeds capacity, trigger the extension mechanism if size() == capacity() { extendCapacity() } arr[size()] = num // Update the number of elements _size += 1 } /* Sort list */ func insert(index: Int, num: Int) { if index < 0 || index >= size() { fatalError("Index out of bounds") } // When the number of elements exceeds capacity, trigger the extension mechanism if size() == capacity() { extendCapacity() } // Move all elements after index index forward by one position for j in (index ..< size()).reversed() { arr[j + 1] = arr[j] } arr[index] = num // Update the number of elements _size += 1 } /* Remove element */ @discardableResult func remove(index: Int) -> Int { if index < 0 || index >= size() { fatalError("Index out of bounds") } let num = arr[index] // Move all elements after index forward by one position for j in index ..< (size() - 1) { arr[j] = arr[j + 1] } // Update the number of elements _size -= 1 // Return the removed element return num } /* Driver Code */ func extendCapacity() { // Create a new array with length extendRatio times the original array and copy the original array to the new array arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1)) // Add elements at the end _capacity = arr.count } /* Convert list to array */ func toArray() -> [Int] { Array(arr.prefix(size())) } } @main enum _MyList { /* Driver Code */ static func main() { /* Initialize list */ let nums = MyList() /* Direct traversal of list elements */ nums.add(num: 1) nums.add(num: 3) nums.add(num: 2) nums.add(num: 5) nums.add(num: 4) print("List nums = \(nums.toArray()), capacity = \(nums.capacity()), length = \(nums.size())") /* Sort list */ nums.insert(index: 3, num: 6) print("Insert number 6 at index 3, get nums = \(nums.toArray())") /* Remove element */ nums.remove(index: 3) print("Delete element at index 3, get nums = \(nums.toArray())") /* Update element */ let num = nums.get(index: 1) print("Access element at index 1, get num = \(num)") /* Add elements at the end */ nums.set(index: 1, num: 0) print("Update element at index 1 to 0, get nums = \(nums.toArray())") /* Test capacity expansion mechanism */ for i in 0 ..< 10 { // At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism nums.add(num: i) } print("After expansion, list nums = \(nums.toArray()), capacity = \(nums.capacity()), length = \(nums.size())") } } ================================================ FILE: en/codes/swift/chapter_backtracking/n_queens.swift ================================================ /** * File: n_queens.swift * Created Time: 2023-05-14 * Author: nuomi1 (nuomi1@qq.com) */ /* Backtracking algorithm: N queens */ func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) { // When all rows are placed, record the solution if row == n { res.append(state) return } // Traverse all columns for col in 0 ..< n { // Calculate the main diagonal and anti-diagonal corresponding to this cell let diag1 = row - col + n - 1 let diag2 = row + col // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell if !cols[col] && !diags1[diag1] && !diags2[diag2] { // Attempt: place the queen in this cell state[row][col] = "Q" cols[col] = true diags1[diag1] = true diags2[diag2] = true // Place the next row backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) // Backtrack: restore this cell to an empty cell state[row][col] = "#" cols[col] = false diags1[diag1] = false diags2[diag2] = false } } } /* Solve N queens */ func nQueens(n: Int) -> [[[String]]] { // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell var state = Array(repeating: Array(repeating: "#", count: n), count: n) var cols = Array(repeating: false, count: n) // Record whether there is a queen in the column var diags1 = Array(repeating: false, count: 2 * n - 1) // Record whether there is a queen on the main diagonal var diags2 = Array(repeating: false, count: 2 * n - 1) // Record whether there is a queen on the anti-diagonal var res: [[[String]]] = [] backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) return res } @main enum NQueens { /* Driver Code */ static func main() { let n = 4 let res = nQueens(n: n) print("Input board size is \(n)") print("Total queen placement solutions: \(res.count)") for state in res { print("--------------------") for row in state { print(row) } } } } ================================================ FILE: en/codes/swift/chapter_backtracking/permutations_i.swift ================================================ /** * File: permutations_i.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ /* Backtracking algorithm: Permutations I */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // When the state length equals the number of elements, record the solution if state.count == choices.count { res.append(state) return } // Traverse all choices for (i, choice) in choices.enumerated() { // Pruning: do not allow repeated selection of elements if !selected[i] { // Attempt: make choice, update state selected[i] = true state.append(choice) // Proceed to the next round of selection backtrack(state: &state, choices: choices, selected: &selected, res: &res) // Backtrack: undo choice, restore to previous state selected[i] = false state.removeLast() } } } /* Permutations I */ func permutationsI(nums: [Int]) -> [[Int]] { var state: [Int] = [] var selected = Array(repeating: false, count: nums.count) var res: [[Int]] = [] backtrack(state: &state, choices: nums, selected: &selected, res: &res) return res } @main enum PermutationsI { /* Driver Code */ static func main() { let nums = [1, 2, 3] let res = permutationsI(nums: nums) print("Input array nums = \(nums)") print("All permutations res = \(res)") } } ================================================ FILE: en/codes/swift/chapter_backtracking/permutations_ii.swift ================================================ /** * File: permutations_ii.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ /* Backtracking algorithm: Permutations II */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // When the state length equals the number of elements, record the solution if state.count == choices.count { res.append(state) return } // Traverse all choices var duplicated: Set = [] for (i, choice) in choices.enumerated() { // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if !selected[i], !duplicated.contains(choice) { // Attempt: make choice, update state duplicated.insert(choice) // Record the selected element value selected[i] = true state.append(choice) // Proceed to the next round of selection backtrack(state: &state, choices: choices, selected: &selected, res: &res) // Backtrack: undo choice, restore to previous state selected[i] = false state.removeLast() } } } /* Permutations II */ func permutationsII(nums: [Int]) -> [[Int]] { var state: [Int] = [] var selected = Array(repeating: false, count: nums.count) var res: [[Int]] = [] backtrack(state: &state, choices: nums, selected: &selected, res: &res) return res } @main enum PermutationsII { /* Driver Code */ static func main() { let nums = [1, 2, 3] let res = permutationsII(nums: nums) print("Input array nums = \(nums)") print("All permutations res = \(res)") } } ================================================ FILE: en/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift ================================================ /** * File: preorder_traversal_i_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var res: [TreeNode] = [] /* Preorder traversal: Example 1 */ func preOrder(root: TreeNode?) { guard let root = root else { return } if root.val == 7 { // Record solution res.append(root) } preOrder(root: root.left) preOrder(root: root.right) } @main enum PreorderTraversalICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\nInitialize binary tree") PrintUtil.printTree(root: root) // Preorder traversal res = [] preOrder(root: root) print("\nOutput all nodes with value 7") var vals: [Int] = [] for node in res { vals.append(node.val) } print(vals) } } ================================================ FILE: en/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift ================================================ /** * File: preorder_traversal_ii_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var path: [TreeNode] = [] var res: [[TreeNode]] = [] /* Preorder traversal: Example 2 */ func preOrder(root: TreeNode?) { guard let root = root else { return } // Attempt path.append(root) if root.val == 7 { // Record solution res.append(path) } preOrder(root: root.left) preOrder(root: root.right) // Backtrack path.removeLast() } @main enum PreorderTraversalIICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\nInitialize binary tree") PrintUtil.printTree(root: root) // Preorder traversal path = [] res = [] preOrder(root: root) print("\nOutput all paths from root node to node 7") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: en/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift ================================================ /** * File: preorder_traversal_iii_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var path: [TreeNode] = [] var res: [[TreeNode]] = [] /* Preorder traversal: Example 3 */ func preOrder(root: TreeNode?) { // Pruning guard let root = root, root.val != 3 else { return } // Attempt path.append(root) if root.val == 7 { // Record solution res.append(path) } preOrder(root: root.left) preOrder(root: root.right) // Backtrack path.removeLast() } @main enum PreorderTraversalIIICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\nInitialize binary tree") PrintUtil.printTree(root: root) // Preorder traversal path = [] res = [] preOrder(root: root) print("\nOutput all paths from root node to node 7, paths do not include nodes with value 3") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: en/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift ================================================ /** * File: preorder_traversal_iii_template.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Check if the current state is a solution */ func isSolution(state: [TreeNode]) -> Bool { !state.isEmpty && state.last!.val == 7 } /* Record solution */ func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) { res.append(state) } /* Check if the choice is valid under the current state */ func isValid(state: [TreeNode], choice: TreeNode?) -> Bool { choice != nil && choice!.val != 3 } /* Update state */ func makeChoice(state: inout [TreeNode], choice: TreeNode) { state.append(choice) } /* Restore state */ func undoChoice(state: inout [TreeNode], choice: TreeNode) { state.removeLast() } /* Backtracking algorithm: Example 3 */ func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) { // Check if it is a solution if isSolution(state: state) { recordSolution(state: state, res: &res) } // Traverse all choices for choice in choices { // Pruning: check if the choice is valid if isValid(state: state, choice: choice) { // Attempt: make choice, update state makeChoice(state: &state, choice: choice) // Proceed to the next round of selection backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res) // Backtrack: undo choice, restore to previous state undoChoice(state: &state, choice: choice) } } } @main enum PreorderTraversalIIITemplate { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\nInitialize binary tree") PrintUtil.printTree(root: root) // Backtracking algorithm var state: [TreeNode] = [] var res: [[TreeNode]] = [] backtrack(state: &state, choices: [root].compactMap { $0 }, res: &res) print("\nOutput all paths from root node to node 7, paths do not include nodes with value 3") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: en/codes/swift/chapter_backtracking/subset_sum_i.swift ================================================ /** * File: subset_sum_i.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Backtracking algorithm: Subset sum I */ func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { // When the subset sum equals target, record the solution if target == 0 { res.append(state) return } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets for i in choices.indices.dropFirst(start) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if target - choices[i] < 0 { break } // Attempt: make choice, update target, start state.append(choices[i]) // Proceed to the next round of selection backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res) // Backtrack: undo choice, restore to previous state state.removeLast() } } /* Solve subset sum I */ func subsetSumI(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // State (subset) let nums = nums.sorted() // Sort nums let start = 0 // Start point for traversal var res: [[Int]] = [] // Result list (subset list) backtrack(state: &state, target: target, choices: nums, start: start, res: &res) return res } @main enum SubsetSumI { /* Driver Code */ static func main() { let nums = [3, 4, 5] let target = 9 let res = subsetSumI(nums: nums, target: target) print("Input array nums = \(nums), target = \(target)") print("All subsets with sum equal to \(target) res = \(res)") } } ================================================ FILE: en/codes/swift/chapter_backtracking/subset_sum_i_naive.swift ================================================ /** * File: subset_sum_i_naive.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Backtracking algorithm: Subset sum I */ func backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) { // When the subset sum equals target, record the solution if total == target { res.append(state) return } // Traverse all choices for i in choices.indices { // Pruning: if the subset sum exceeds target, skip this choice if total + choices[i] > target { continue } // Attempt: make choice, update element sum total state.append(choices[i]) // Proceed to the next round of selection backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res) // Backtrack: undo choice, restore to previous state state.removeLast() } } /* Solve subset sum I (including duplicate subsets) */ func subsetSumINaive(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // State (subset) let total = 0 // Subset sum var res: [[Int]] = [] // Result list (subset list) backtrack(state: &state, target: target, total: total, choices: nums, res: &res) return res } @main enum SubsetSumINaive { /* Driver Code */ static func main() { let nums = [3, 4, 5] let target = 9 let res = subsetSumINaive(nums: nums, target: target) print("Input array nums = \(nums), target = \(target)") print("All subsets with sum equal to \(target) res = \(res)") print("Please note that this method outputs results containing duplicate sets") } } ================================================ FILE: en/codes/swift/chapter_backtracking/subset_sum_ii.swift ================================================ /** * File: subset_sum_ii.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Backtracking algorithm: Subset sum II */ func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { // When the subset sum equals target, record the solution if target == 0 { res.append(state) return } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets // Pruning 3: start traversing from start to avoid repeatedly selecting the same element for i in choices.indices.dropFirst(start) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if target - choices[i] < 0 { break } // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly if i > start, choices[i] == choices[i - 1] { continue } // Attempt: make choice, update target, start state.append(choices[i]) // Proceed to the next round of selection backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res) // Backtrack: undo choice, restore to previous state state.removeLast() } } /* Solve subset sum II */ func subsetSumII(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // State (subset) let nums = nums.sorted() // Sort nums let start = 0 // Start point for traversal var res: [[Int]] = [] // Result list (subset list) backtrack(state: &state, target: target, choices: nums, start: start, res: &res) return res } @main enum SubsetSumII { /* Driver Code */ static func main() { let nums = [4, 4, 5] let target = 9 let res = subsetSumII(nums: nums, target: target) print("Input array nums = \(nums), target = \(target)") print("All subsets with sum equal to \(target) res = \(res)") } } ================================================ FILE: en/codes/swift/chapter_computational_complexity/iteration.swift ================================================ /** * File: iteration.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* for loop */ func forLoop(n: Int) -> Int { var res = 0 // Sum 1, 2, ..., n-1, n for i in 1 ... n { res += i } return res } /* while loop */ func whileLoop(n: Int) -> Int { var res = 0 var i = 1 // Initialize condition variable // Sum 1, 2, ..., n-1, n while i <= n { res += i i += 1 // Update condition variable } return res } /* while loop (two updates) */ func whileLoopII(n: Int) -> Int { var res = 0 var i = 1 // Initialize condition variable // Sum 1, 4, 10, ... while i <= n { res += i // Update condition variable i += 1 i *= 2 } return res } /* Nested for loop */ func nestedForLoop(n: Int) -> String { var res = "" // Loop i = 1, 2, ..., n-1, n for i in 1 ... n { // Loop j = 1, 2, ..., n-1, n for j in 1 ... n { res.append("(\(i), \(j)), ") } } return res } @main enum Iteration { /* Driver Code */ static func main() { let n = 5 var res = 0 res = forLoop(n: n) print("\nFor loop sum result res = \(res)") res = whileLoop(n: n) print("\nWhile loop sum result res = \(res)") res = whileLoopII(n: n) print("\nWhile loop (two updates) sum result res = \(res)") let resStr = nestedForLoop(n: n) print("\nNested for loop traversal result \(resStr)") } } ================================================ FILE: en/codes/swift/chapter_computational_complexity/recursion.swift ================================================ /** * File: recursion.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Recursion */ func recur(n: Int) -> Int { // Termination condition if n == 1 { return 1 } // Recurse: recursive call let res = recur(n: n - 1) // Return: return result return n + res } /* Simulate recursion using iteration */ func forLoopRecur(n: Int) -> Int { // Use an explicit stack to simulate the system call stack var stack: [Int] = [] var res = 0 // Recurse: recursive call for i in (1 ... n).reversed() { // Simulate "recurse" with "push" stack.append(i) } // Return: return result while !stack.isEmpty { // Simulate "return" with "pop" res += stack.removeLast() } // res = 1+2+3+...+n return res } /* Tail recursion */ func tailRecur(n: Int, res: Int) -> Int { // Termination condition if n == 0 { return res } // Tail recursive call return tailRecur(n: n - 1, res: res + n) } /* Fibonacci sequence: recursion */ func fib(n: Int) -> Int { // Termination condition f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1 } // Recursive call f(n) = f(n-1) + f(n-2) let res = fib(n: n - 1) + fib(n: n - 2) // Return result f(n) return res } @main enum Recursion { /* Driver Code */ static func main() { let n = 5 var res = 0 res = recursion.recur(n: n) print("\nRecursion sum result res = \(res)") res = recursion.forLoopRecur(n: n) print("\nUsing iteration to simulate recursion sum result res = \(res)") res = recursion.tailRecur(n: n, res: 0) print("\nTail recursion sum result res = \(res)") res = recursion.fib(n: n) print("\nThe \(n)th Fibonacci number is \(res)") } } ================================================ FILE: en/codes/swift/chapter_computational_complexity/space_complexity.swift ================================================ /** * File: space_complexity.swift * Created Time: 2023-01-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Function */ @discardableResult func function() -> Int { // Perform some operations return 0 } /* Constant order */ func constant(n: Int) { // Constants, variables, objects occupy O(1) space let a = 0 var b = 0 let nums = Array(repeating: 0, count: 10000) let node = ListNode(x: 0) // Variables in the loop occupy O(1) space for _ in 0 ..< n { let c = 0 } // Functions in the loop occupy O(1) space for _ in 0 ..< n { function() } } /* Linear order */ func linear(n: Int) { // Array of length n uses O(n) space let nums = Array(repeating: 0, count: n) // A list of length n occupies O(n) space let nodes = (0 ..< n).map { ListNode(x: $0) } // A hash table of length n occupies O(n) space let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") }) } /* Linear order (recursive implementation) */ func linearRecur(n: Int) { print("Recursion n = \(n)") if n == 1 { return } linearRecur(n: n - 1) } /* Exponential order */ func quadratic(n: Int) { // 2D list uses O(n^2) space let numList = Array(repeating: Array(repeating: 0, count: n), count: n) } /* Quadratic order (recursive implementation) */ @discardableResult func quadraticRecur(n: Int) -> Int { if n <= 0 { return 0 } // Array nums has length n, n-1, ..., 2, 1 let nums = Array(repeating: 0, count: n) print("In recursion n = \(n), nums length = \(nums.count)") return quadraticRecur(n: n - 1) } /* Driver Code */ func buildTree(n: Int) -> TreeNode? { if n == 0 { return nil } let root = TreeNode(x: 0) root.left = buildTree(n: n - 1) root.right = buildTree(n: n - 1) return root } @main enum SpaceComplexity { /* Driver Code */ static func main() { let n = 5 // Constant order constant(n: n) // Linear order linear(n: n) linearRecur(n: n) // Exponential order quadratic(n: n) quadraticRecur(n: n) // Exponential order let root = buildTree(n: n) PrintUtil.printTree(root: root) } } ================================================ FILE: en/codes/swift/chapter_computational_complexity/time_complexity.swift ================================================ /** * File: time_complexity.swift * Created Time: 2022-12-26 * Author: nuomi1 (nuomi1@qq.com) */ /* Constant order */ func constant(n: Int) -> Int { var count = 0 let size = 100_000 for _ in 0 ..< size { count += 1 } return count } /* Linear order */ func linear(n: Int) -> Int { var count = 0 for _ in 0 ..< n { count += 1 } return count } /* Linear order (traversing array) */ func arrayTraversal(nums: [Int]) -> Int { var count = 0 // Number of iterations is proportional to the array length for _ in nums { count += 1 } return count } /* Exponential order */ func quadratic(n: Int) -> Int { var count = 0 // Number of iterations is quadratically related to the data size n for _ in 0 ..< n { for _ in 0 ..< n { count += 1 } } return count } /* Quadratic order (bubble sort) */ func bubbleSort(nums: inout [Int]) -> Int { var count = 0 // Counter // Outer loop: unsorted range is [0, i] for i in nums.indices.dropFirst().reversed() { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for j in 0 ..< i { if nums[j] > nums[j + 1] { // Swap nums[j] and nums[j + 1] let tmp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = tmp count += 3 // Element swap includes 3 unit operations } } } return count } /* Exponential order (loop implementation) */ func exponential(n: Int) -> Int { var count = 0 var base = 1 // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1) for _ in 0 ..< n { for _ in 0 ..< base { count += 1 } base *= 2 } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count } /* Exponential order (recursive implementation) */ func expRecur(n: Int) -> Int { if n == 1 { return 1 } return expRecur(n: n - 1) + expRecur(n: n - 1) + 1 } /* Logarithmic order (loop implementation) */ func logarithmic(n: Int) -> Int { var count = 0 var n = n while n > 1 { n = n / 2 count += 1 } return count } /* Logarithmic order (recursive implementation) */ func logRecur(n: Int) -> Int { if n <= 1 { return 0 } return logRecur(n: n / 2) + 1 } /* Linearithmic order */ func linearLogRecur(n: Int) -> Int { if n <= 1 { return 1 } var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2) for _ in stride(from: 0, to: n, by: 1) { count += 1 } return count } /* Factorial order (recursive implementation) */ func factorialRecur(n: Int) -> Int { if n == 0 { return 1 } var count = 0 // Split from 1 into n for _ in 0 ..< n { count += factorialRecur(n: n - 1) } return count } @main enum TimeComplexity { /* Driver Code */ static func main() { // You can modify n to run and observe the trend of the number of operations for various complexities let n = 8 print("Input data size n = \(n)") var count = constant(n: n) print("Constant-time operations count = \(count)") count = linear(n: n) print("Linear-time operations count = \(count)") count = arrayTraversal(nums: Array(repeating: 0, count: n)) print("Linear-time (array traversal) operations count = \(count)") count = quadratic(n: n) print("Quadratic-time operations count = \(count)") var nums = Array(stride(from: n, to: 0, by: -1)) // [n,n-1,...,2,1] count = bubbleSort(nums: &nums) print("Quadratic-time (bubble sort) operations count = \(count)") count = exponential(n: n) print("Exponential-time (iterative) operations count = \(count)") count = expRecur(n: n) print("Exponential-time (recursive) operations count = \(count)") count = logarithmic(n: n) print("Logarithmic-time (iterative) operations count = \(count)") count = logRecur(n: n) print("Logarithmic-time (recursive) operations count = \(count)") count = linearLogRecur(n: n) print("Linearithmic-time (recursive) operations count = \(count)") count = factorialRecur(n: n) print("Factorial-time (recursive) operations count = \(count)") } } ================================================ FILE: en/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift ================================================ /** * File: worst_best_time_complexity.swift * Created Time: 2022-12-26 * Author: nuomi1 (nuomi1@qq.com) */ /* Generate an array with elements { 1, 2, ..., n }, order shuffled */ func randomNumbers(n: Int) -> [Int] { // Generate array nums = { 1, 2, 3, ..., n } var nums = Array(1 ... n) // Randomly shuffle array elements nums.shuffle() return nums } /* Find the index of number 1 in array nums */ func findOne(nums: [Int]) -> Int { for i in nums.indices { // When element 1 is at the head of the array, best time complexity O(1) is achieved // When element 1 is at the tail of the array, worst time complexity O(n) is achieved if nums[i] == 1 { return i } } return -1 } @main enum WorstBestTimeComplexity { /* Driver Code */ static func main() { for _ in 0 ..< 10 { let n = 100 let nums = randomNumbers(n: n) let index = findOne(nums: nums) print("Array [ 1, 2, ..., n ] after shuffling = \(nums)") print("Index of number 1 is \(index)") } } } ================================================ FILE: en/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift ================================================ /** * File: binary_search_recur.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Binary search: problem f(i, j) */ func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int { // If the interval is empty, it means there is no target element, return -1 if i > j { return -1 } // Calculate the midpoint index m let m = (i + j) / 2 if nums[m] < target { // Recursion subproblem f(m+1, j) return dfs(nums: nums, target: target, i: m + 1, j: j) } else if nums[m] > target { // Recursion subproblem f(i, m-1) return dfs(nums: nums, target: target, i: i, j: m - 1) } else { // Found the target element, return its index return m } } /* Binary search */ func binarySearch(nums: [Int], target: Int) -> Int { // Solve the problem f(0, n-1) dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1) } @main enum BinarySearchRecur { /* Driver Code */ static func main() { let target = 6 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] // Binary search (closed interval on both sides) let index = binarySearch(nums: nums, target: target) print("Index of target element 6 = \(index)") } } ================================================ FILE: en/codes/swift/chapter_divide_and_conquer/build_tree.swift ================================================ /** * File: build_tree.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Build binary tree: divide and conquer */ func dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? { // Terminate when the subtree interval is empty if r - l < 0 { return nil } // Initialize the root node let root = TreeNode(x: preorder[i]) // Query m to divide the left and right subtrees let m = inorderMap[preorder[i]]! // Subproblem: build the left subtree root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1) // Subproblem: build the right subtree root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r) // Return the root node return root } /* Build binary tree */ func buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? { // Initialize hash map, storing the mapping from inorder elements to indices let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset } return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1) } @main enum BuildTree { /* Driver Code */ static func main() { let preorder = [3, 9, 2, 1, 7] let inorder = [9, 3, 1, 2, 7] print("Pre-order traversal = \(preorder)") print("In-order traversal = \(inorder)") let root = buildTree(preorder: preorder, inorder: inorder) print("The constructed binary tree is:") PrintUtil.printTree(root: root) } } ================================================ FILE: en/codes/swift/chapter_divide_and_conquer/hanota.swift ================================================ /** * File: hanota.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Move a disk */ func move(src: inout [Int], tar: inout [Int]) { // Take out a disk from the top of src let pan = src.popLast()! // Place the disk on top of tar tar.append(pan) } /* Solve the Tower of Hanoi problem f(i) */ func dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) { // If there is only one disk left in src, move it directly to tar if i == 1 { move(src: &src, tar: &tar) return } // Subproblem f(i-1): move the top i-1 disks from src to buf using tar dfs(i: i - 1, src: &src, buf: &tar, tar: &buf) // Subproblem f(1): move the remaining disk from src to tar move(src: &src, tar: &tar) // Subproblem f(i-1): move the top i-1 disks from buf to tar using src dfs(i: i - 1, src: &buf, buf: &src, tar: &tar) } /* Solve the Tower of Hanoi problem */ func solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) { let n = A.count // The tail of the list is the top of the rod // Move top n disks from src to C using B dfs(i: n, src: &A, buf: &B, tar: &C) } @main enum Hanota { /* Driver Code */ static func main() { // The tail of the list is the top of the rod var A = [5, 4, 3, 2, 1] var B: [Int] = [] var C: [Int] = [] print("In initial state:") print("A = \(A)") print("B = \(B)") print("C = \(C)") solveHanota(A: &A, B: &B, C: &C) print("After disk movement is complete:") print("A = \(A)") print("B = \(B)") print("C = \(C)") } } ================================================ FILE: en/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift ================================================ /** * File: climbing_stairs_backtrack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Backtracking */ func backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) { // When climbing to the n-th stair, add 1 to the solution count if state == n { res[0] += 1 } // Traverse all choices for choice in choices { // Pruning: not allowed to go beyond the n-th stair if state + choice > n { continue } // Attempt: make choice, update state backtrack(choices: choices, state: state + choice, n: n, res: &res) // Backtrack } } /* Climbing stairs: Backtracking */ func climbingStairsBacktrack(n: Int) -> Int { let choices = [1, 2] // Can choose to climb up 1 or 2 stairs let state = 0 // Start climbing from the 0-th stair var res: [Int] = [] res.append(0) // Use res[0] to record the solution count backtrack(choices: choices, state: state, n: n, res: &res) return res[0] } @main enum ClimbingStairsBacktrack { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsBacktrack(n: n) print("Climbing \(n) stairs has \(res) solutions") } } ================================================ FILE: en/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift ================================================ /** * File: climbing_stairs_constraint_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Climbing stairs with constraint: Dynamic programming */ func climbingStairsConstraintDP(n: Int) -> Int { if n == 1 || n == 2 { return 1 } // Initialize dp table, used to store solutions to subproblems var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1) // Initial state: preset the solution to the smallest subproblem dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // State transition: gradually solve larger subproblems from smaller ones for i in 3 ... n { dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] } return dp[n][1] + dp[n][2] } @main enum ClimbingStairsConstraintDP { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsConstraintDP(n: n) print("Climbing \(n) stairs has \(res) solutions") } } ================================================ FILE: en/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift ================================================ /** * File: climbing_stairs_dfs.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Search */ func dfs(i: Int) -> Int { // Known dp[1] and dp[2], return them if i == 1 || i == 2 { return i } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i: i - 1) + dfs(i: i - 2) return count } /* Climbing stairs: Search */ func climbingStairsDFS(n: Int) -> Int { dfs(i: n) } @main enum ClimbingStairsDFS { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsDFS(n: n) print("Climbing \(n) stairs has \(res) solutions") } } ================================================ FILE: en/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift ================================================ /** * File: climbing_stairs_dfs_mem.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Memoization search */ func dfs(i: Int, mem: inout [Int]) -> Int { // Known dp[1] and dp[2], return them if i == 1 || i == 2 { return i } // If record dp[i] exists, return it directly if mem[i] != -1 { return mem[i] } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem) // Record dp[i] mem[i] = count return count } /* Climbing stairs: Memoization search */ func climbingStairsDFSMem(n: Int) -> Int { // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record var mem = Array(repeating: -1, count: n + 1) return dfs(i: n, mem: &mem) } @main enum ClimbingStairsDFSMem { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsDFSMem(n: n) print("Climbing \(n) stairs has \(res) solutions") } } ================================================ FILE: en/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift ================================================ /** * File: climbing_stairs_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Climbing stairs: Dynamic programming */ func climbingStairsDP(n: Int) -> Int { if n == 1 || n == 2 { return n } // Initialize dp table, used to store solutions to subproblems var dp = Array(repeating: 0, count: n + 1) // Initial state: preset the solution to the smallest subproblem dp[1] = 1 dp[2] = 2 // State transition: gradually solve larger subproblems from smaller ones for i in 3 ... n { dp[i] = dp[i - 1] + dp[i - 2] } return dp[n] } /* Climbing stairs: Space-optimized dynamic programming */ func climbingStairsDPComp(n: Int) -> Int { if n == 1 || n == 2 { return n } var a = 1 var b = 2 for _ in 3 ... n { (a, b) = (b, a + b) } return b } @main enum ClimbingStairsDP { /* Driver Code */ static func main() { let n = 9 var res = climbingStairsDP(n: n) print("Climbing \(n) stairs has \(res) solutions") res = climbingStairsDPComp(n: n) print("Climbing \(n) stairs has \(res) solutions") } } ================================================ FILE: en/codes/swift/chapter_dynamic_programming/coin_change.swift ================================================ /** * File: coin_change.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Coin change: Dynamic programming */ func coinChangeDP(coins: [Int], amt: Int) -> Int { let n = coins.count let MAX = amt + 1 // Initialize dp table var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) // State transition: first row and first column for a in 1 ... amt { dp[0][a] = MAX } // State transition: rest of the rows and columns for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a] } else { // The smaller value between not selecting and selecting coin 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 } /* Coin change: Space-optimized dynamic programming */ func coinChangeDPComp(coins: [Int], amt: Int) -> Int { let n = coins.count let MAX = amt + 1 // Initialize dp table var dp = Array(repeating: MAX, count: amt + 1) dp[0] = 0 // State transition for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // If exceeds target amount, don't select coin i dp[a] = dp[a] } else { // The smaller value between not selecting and selecting coin i dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) } } } return dp[amt] != MAX ? dp[amt] : -1 } @main enum CoinChange { /* Driver Code */ static func main() { let coins = [1, 2, 5] let amt = 4 // Dynamic programming var res = coinChangeDP(coins: coins, amt: amt) print("Minimum coins needed to make target amount is \(res)") // Space-optimized dynamic programming res = coinChangeDPComp(coins: coins, amt: amt) print("Minimum coins needed to make target amount is \(res)") } } ================================================ FILE: en/codes/swift/chapter_dynamic_programming/coin_change_ii.swift ================================================ /** * File: coin_change_ii.swift * Created Time: 2023-07-16 * Author: nuomi1 (nuomi1@qq.com) */ /* Coin change II: Dynamic programming */ func coinChangeIIDP(coins: [Int], amt: Int) -> Int { let n = coins.count // Initialize dp table var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) // Initialize first column for i in 0 ... n { dp[i][0] = 1 } // State transition for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a] } else { // Sum of the two options: not selecting and selecting coin i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] } } } return dp[n][amt] } /* Coin change II: Space-optimized dynamic programming */ func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { let n = coins.count // Initialize dp table var dp = Array(repeating: 0, count: amt + 1) dp[0] = 1 // State transition for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // If exceeds target amount, don't select coin i dp[a] = dp[a] } else { // Sum of the two options: not selecting and selecting coin i dp[a] = dp[a] + dp[a - coins[i - 1]] } } } return dp[amt] } @main enum CoinChangeII { /* Driver Code */ static func main() { let coins = [1, 2, 5] let amt = 5 // Dynamic programming var res = coinChangeIIDP(coins: coins, amt: amt) print("Number of coin combinations to make target amount is \(res)") // Space-optimized dynamic programming res = coinChangeIIDPComp(coins: coins, amt: amt) print("Number of coin combinations to make target amount is \(res)") } } ================================================ FILE: en/codes/swift/chapter_dynamic_programming/edit_distance.swift ================================================ /** * File: edit_distance.swift * Created Time: 2023-07-16 * Author: nuomi1 (nuomi1@qq.com) */ /* Edit distance: Brute-force search */ func editDistanceDFS(s: String, t: String, i: Int, j: Int) -> Int { // If both s and t are empty, return 0 if i == 0, j == 0 { return 0 } // If s is empty, return length of t if i == 0 { return j } // If t is empty, return length of s if j == 0 { return i } // If two characters are equal, skip both characters if s.utf8CString[i - 1] == t.utf8CString[j - 1] { return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) } // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) // Return minimum edit steps return min(min(insert, delete), replace) + 1 } /* Edit distance: Memoization search */ func editDistanceDFSMem(s: String, t: String, mem: inout [[Int]], i: Int, j: Int) -> Int { // If both s and t are empty, return 0 if i == 0, j == 0 { return 0 } // If s is empty, return length of t if i == 0 { return j } // If t is empty, return length of s if j == 0 { return i } // If there's a record, return it directly if mem[i][j] != -1 { return mem[i][j] } // If two characters are equal, skip both characters if s.utf8CString[i - 1] == t.utf8CString[j - 1] { return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) } // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) // Record and return minimum edit steps mem[i][j] = min(min(insert, delete), replace) + 1 return mem[i][j] } /* Edit distance: Dynamic programming */ func editDistanceDP(s: String, t: String) -> Int { let n = s.utf8CString.count let m = t.utf8CString.count var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1) // State transition: first row and first column for i in 1 ... n { dp[i][0] = i } for j in 1 ... m { dp[0][j] = j } // State transition: rest of the rows and columns for i in 1 ... n { for j in 1 ... m { if s.utf8CString[i - 1] == t.utf8CString[j - 1] { // If two characters are equal, skip both characters dp[i][j] = dp[i - 1][j - 1] } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 } } } return dp[n][m] } /* Edit distance: Space-optimized dynamic programming */ func editDistanceDPComp(s: String, t: String) -> Int { let n = s.utf8CString.count let m = t.utf8CString.count var dp = Array(repeating: 0, count: m + 1) // State transition: first row for j in 1 ... m { dp[j] = j } // State transition: rest of the rows for i in 1 ... n { // State transition: first column var leftup = dp[0] // Temporarily store dp[i-1, j-1] dp[0] = i // State transition: rest of the columns for j in 1 ... m { let temp = dp[j] if s.utf8CString[i - 1] == t.utf8CString[j - 1] { // If two characters are equal, skip both characters dp[j] = leftup } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 } leftup = temp // Update for next round's dp[i-1, j-1] } } return dp[m] } @main enum EditDistance { /* Driver Code */ static func main() { let s = "bag" let t = "pack" let n = s.utf8CString.count let m = t.utf8CString.count // Brute-force search var res = editDistanceDFS(s: s, t: t, i: n, j: m) print("Changing \(s) to \(t) requires minimum \(res) edits") // Memoization search var mem = Array(repeating: Array(repeating: -1, count: m + 1), count: n + 1) res = editDistanceDFSMem(s: s, t: t, mem: &mem, i: n, j: m) print("Changing \(s) to \(t) requires minimum \(res) edits") // Dynamic programming res = editDistanceDP(s: s, t: t) print("Changing \(s) to \(t) requires minimum \(res) edits") // Space-optimized dynamic programming res = editDistanceDPComp(s: s, t: t) print("Changing \(s) to \(t) requires minimum \(res) edits") } } ================================================ FILE: en/codes/swift/chapter_dynamic_programming/knapsack.swift ================================================ /** * File: knapsack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 0-1 knapsack: Brute-force search */ func knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int { // If all items have been selected or knapsack has no remaining capacity, return value 0 if i == 0 || c == 0 { return 0 } // If exceeds knapsack capacity, can only choose not to put it in if wgt[i - 1] > c { return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) } // Calculate the maximum value of not putting in and putting in item i let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] // Return the larger value of the two options return max(no, yes) } /* 0-1 knapsack: Memoization search */ func knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int { // If all items have been selected or knapsack has no remaining capacity, return value 0 if i == 0 || c == 0 { return 0 } // If there's a record, return it directly if mem[i][c] != -1 { return mem[i][c] } // If exceeds knapsack capacity, can only choose not to put it in if wgt[i - 1] > c { return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) } // Calculate the maximum value of not putting in and putting in item i let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] // Record and return the larger value of the two options mem[i][c] = max(no, yes) return mem[i][c] } /* 0-1 knapsack: Dynamic programming */ func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // Initialize dp table var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) // State transition for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c] } else { // The larger value between not selecting and selecting item 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 knapsack: Space-optimized dynamic programming */ func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // Initialize dp table var dp = Array(repeating: 0, count: cap + 1) // State transition for i in 1 ... n { // Traverse in reverse order for c in (1 ... cap).reversed() { if wgt[i - 1] <= c { // The larger value between not selecting and selecting item i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) } } } return dp[cap] } @main enum Knapsack { /* Driver Code */ static func main() { let wgt = [10, 20, 30, 40, 50] let val = [50, 120, 150, 210, 240] let cap = 50 let n = wgt.count // Brute-force search var res = knapsackDFS(wgt: wgt, val: val, i: n, c: cap) print("Maximum item value not exceeding knapsack capacity is \(res)") // Memoization search var mem = Array(repeating: Array(repeating: -1, count: cap + 1), count: n + 1) res = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: n, c: cap) print("Maximum item value not exceeding knapsack capacity is \(res)") // Dynamic programming res = knapsackDP(wgt: wgt, val: val, cap: cap) print("Maximum item value not exceeding knapsack capacity is \(res)") // Space-optimized dynamic programming res = knapsackDPComp(wgt: wgt, val: val, cap: cap) print("Maximum item value not exceeding knapsack capacity is \(res)") } } ================================================ FILE: en/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift ================================================ /** * File: min_cost_climbing_stairs_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Minimum cost climbing stairs: Dynamic programming */ func minCostClimbingStairsDP(cost: [Int]) -> Int { let n = cost.count - 1 if n == 1 || n == 2 { return cost[n] } // Initialize dp table, used to store solutions to subproblems var dp = Array(repeating: 0, count: n + 1) // Initial state: preset the solution to the smallest subproblem dp[1] = cost[1] dp[2] = cost[2] // State transition: gradually solve larger subproblems from smaller ones for i in 3 ... n { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] } return dp[n] } /* Minimum cost climbing stairs: Space-optimized dynamic programming */ func minCostClimbingStairsDPComp(cost: [Int]) -> Int { let n = cost.count - 1 if n == 1 || n == 2 { return cost[n] } var (a, b) = (cost[1], cost[2]) for i in 3 ... n { (a, b) = (b, min(a, b) + cost[i]) } return b } @main enum MinCostClimbingStairsDP { /* Driver Code */ static func main() { let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] print("Input stair cost list is \(cost)") var res = minCostClimbingStairsDP(cost: cost) print("Minimum cost to climb stairs is \(res)") res = minCostClimbingStairsDPComp(cost: cost) print("Minimum cost to climb stairs is \(res)") } } ================================================ FILE: en/codes/swift/chapter_dynamic_programming/min_path_sum.swift ================================================ /** * File: min_path_sum.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Minimum path sum: Brute-force search */ func minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int { // If it's the top-left cell, terminate the search if i == 0, j == 0 { return grid[0][0] } // If row or column index is out of bounds, return +∞ cost if i < 0 || j < 0 { return .max } // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1) let up = minPathSumDFS(grid: grid, i: i - 1, j: j) let left = minPathSumDFS(grid: grid, i: i, j: j - 1) // Return the minimum path cost from top-left to (i, j) return min(left, up) + grid[i][j] } /* Minimum path sum: Memoization search */ func minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int { // If it's the top-left cell, terminate the search if i == 0, j == 0 { return grid[0][0] } // If row or column index is out of bounds, return +∞ cost if i < 0 || j < 0 { return .max } // If there's a record, return it directly if mem[i][j] != -1 { return mem[i][j] } // Minimum path cost for left and upper cells let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j) let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1) // Record and return the minimum path cost from top-left to (i, j) mem[i][j] = min(left, up) + grid[i][j] return mem[i][j] } /* Minimum path sum: Dynamic programming */ func minPathSumDP(grid: [[Int]]) -> Int { let n = grid.count let m = grid[0].count // Initialize dp table var dp = Array(repeating: Array(repeating: 0, count: m), count: n) dp[0][0] = grid[0][0] // State transition: first row for j in 1 ..< m { dp[0][j] = dp[0][j - 1] + grid[0][j] } // State transition: first column for i in 1 ..< n { dp[i][0] = dp[i - 1][0] + grid[i][0] } // State transition: rest of the rows and columns for i in 1 ..< n { for j in 1 ..< m { dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] } } return dp[n - 1][m - 1] } /* Minimum path sum: Space-optimized dynamic programming */ func minPathSumDPComp(grid: [[Int]]) -> Int { let n = grid.count let m = grid[0].count // Initialize dp table var dp = Array(repeating: 0, count: m) // State transition: first row dp[0] = grid[0][0] for j in 1 ..< m { dp[j] = dp[j - 1] + grid[0][j] } // State transition: rest of the rows for i in 1 ..< n { // State transition: first column dp[0] = dp[0] + grid[i][0] // State transition: rest of the columns for j in 1 ..< m { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] } } return dp[m - 1] } @main enum MinPathSum { /* Driver Code */ static func main() { let grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ] let n = grid.count let m = grid[0].count // Brute-force search var res = minPathSumDFS(grid: grid, i: n - 1, j: m - 1) print("Minimum path sum from top-left to bottom-right is \(res)") // Memoization search var mem = Array(repeating: Array(repeating: -1, count: m), count: n) res = minPathSumDFSMem(grid: grid, mem: &mem, i: n - 1, j: m - 1) print("Minimum path sum from top-left to bottom-right is \(res)") // Dynamic programming res = minPathSumDP(grid: grid) print("Minimum path sum from top-left to bottom-right is \(res)") // Space-optimized dynamic programming res = minPathSumDPComp(grid: grid) print("Minimum path sum from top-left to bottom-right is \(res)") } } ================================================ FILE: en/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift ================================================ /** * File: unbounded_knapsack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Unbounded knapsack: Dynamic programming */ func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // Initialize dp table var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) // State transition for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c] } else { // The larger value between not selecting and selecting item i dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) } } } return dp[n][cap] } /* Unbounded knapsack: Space-optimized dynamic programming */ func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // Initialize dp table var dp = Array(repeating: 0, count: cap + 1) // State transition for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // If exceeds knapsack capacity, don't select item i dp[c] = dp[c] } else { // The larger value between not selecting and selecting item i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) } } } return dp[cap] } @main enum UnboundedKnapsack { /* Driver Code */ static func main() { let wgt = [1, 2, 3] let val = [5, 11, 15] let cap = 4 // Dynamic programming var res = unboundedKnapsackDP(wgt: wgt, val: val, cap: cap) print("Maximum item value not exceeding knapsack capacity is \(res)") // Space-optimized dynamic programming res = unboundedKnapsackDPComp(wgt: wgt, val: val, cap: cap) print("Maximum item value not exceeding knapsack capacity is \(res)") } } ================================================ FILE: en/codes/swift/chapter_graph/graph_adjacency_list.swift ================================================ /** * File: graph_adjacency_list.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Undirected graph class based on adjacency list */ public class GraphAdjList { // Adjacency list, key: vertex, value: all adjacent vertices of that vertex public private(set) var adjList: [Vertex: [Vertex]] /* Constructor */ public init(edges: [[Vertex]]) { adjList = [:] // Add all vertices and edges for edge in edges { addVertex(vet: edge[0]) addVertex(vet: edge[1]) addEdge(vet1: edge[0], vet2: edge[1]) } } /* Get the number of vertices */ public func size() -> Int { adjList.count } /* Add edge */ public func addEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("Invalid parameter") } // Add edge vet1 - vet2 adjList[vet1]?.append(vet2) adjList[vet2]?.append(vet1) } /* Remove edge */ public func removeEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("Invalid parameter") } // Remove edge vet1 - vet2 adjList[vet1]?.removeAll { $0 == vet2 } adjList[vet2]?.removeAll { $0 == vet1 } } /* Add vertex */ public func addVertex(vet: Vertex) { if adjList[vet] != nil { return } // Add a new linked list in the adjacency list adjList[vet] = [] } /* Remove vertex */ public func removeVertex(vet: Vertex) { if adjList[vet] == nil { fatalError("Invalid parameter") } // Remove the linked list corresponding to vertex vet in the adjacency list adjList.removeValue(forKey: vet) // Traverse the linked lists of other vertices and remove all edges containing vet for key in adjList.keys { adjList[key]?.removeAll { $0 == vet } } } /* Print adjacency list */ public func print() { Swift.print("Adjacency list =") for (vertex, list) in adjList { let list = list.map { $0.val } Swift.print("\(vertex.val): \(list),") } } } #if !TARGET @main enum GraphAdjacencyList { /* Driver Code */ static func main() { /* Add edge */ let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] let graph = GraphAdjList(edges: edges) print("\nAfter initialization, graph is") graph.print() /* Add edge */ // Vertices 1, 3 are v[0], v[1] graph.addEdge(vet1: v[0], vet2: v[2]) print("\nAfter adding edge 1-2, graph is") graph.print() /* Remove edge */ // Vertex 3 is v[1] graph.removeEdge(vet1: v[0], vet2: v[1]) print("\nAfter removing edge 1-3, graph is") graph.print() /* Add vertex */ let v5 = Vertex(val: 6) graph.addVertex(vet: v5) print("\nAfter adding vertex 6, graph is") graph.print() /* Remove vertex */ // Vertex 3 is v[1] graph.removeVertex(vet: v[1]) print("\nAfter removing vertex 3, graph is") graph.print() } } #endif ================================================ FILE: en/codes/swift/chapter_graph/graph_adjacency_list_target.swift ================================================ /** * File: graph_adjacency_list.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Undirected graph class based on adjacency list */ public class GraphAdjList { // Adjacency list, key: vertex, value: all adjacent vertices of that vertex public private(set) var adjList: [Vertex: [Vertex]] /* Constructor */ public init(edges: [[Vertex]]) { adjList = [:] // Add all vertices and edges for edge in edges { addVertex(vet: edge[0]) addVertex(vet: edge[1]) addEdge(vet1: edge[0], vet2: edge[1]) } } /* Get the number of vertices */ public func size() -> Int { adjList.count } /* Add edge */ public func addEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("Invalid parameter") } // Add edge vet1 - vet2 adjList[vet1]?.append(vet2) adjList[vet2]?.append(vet1) } /* Remove edge */ public func removeEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("Invalid parameter") } // Remove edge vet1 - vet2 adjList[vet1]?.removeAll { $0 == vet2 } adjList[vet2]?.removeAll { $0 == vet1 } } /* Add vertex */ public func addVertex(vet: Vertex) { if adjList[vet] != nil { return } // Add a new linked list in the adjacency list adjList[vet] = [] } /* Remove vertex */ public func removeVertex(vet: Vertex) { if adjList[vet] == nil { fatalError("Invalid parameter") } // Remove the linked list corresponding to vertex vet in the adjacency list adjList.removeValue(forKey: vet) // Traverse the linked lists of other vertices and remove all edges containing vet for key in adjList.keys { adjList[key]?.removeAll { $0 == vet } } } /* Print adjacency list */ public func print() { Swift.print("Adjacency list =") for (vertex, list) in adjList { let list = list.map { $0.val } Swift.print("\(vertex.val): \(list),") } } } #if !TARGET @main enum GraphAdjacencyList { /* Driver Code */ static func main() { /* Add edge */ let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] let graph = GraphAdjList(edges: edges) print("\nAfter initialization, graph is") graph.print() /* Add edge */ // Vertices 1, 3 are v[0], v[1] graph.addEdge(vet1: v[0], vet2: v[2]) print("\nAfter adding edge 1-2, graph is") graph.print() /* Remove edge */ // Vertex 3 is v[1] graph.removeEdge(vet1: v[0], vet2: v[1]) print("\nAfter removing edge 1-3, graph is") graph.print() /* Add vertex */ let v5 = Vertex(val: 6) graph.addVertex(vet: v5) print("\nAfter adding vertex 6, graph is") graph.print() /* Remove vertex */ // Vertex 3 is v[1] graph.removeVertex(vet: v[1]) print("\nAfter removing vertex 3, graph is") graph.print() } } #endif ================================================ FILE: en/codes/swift/chapter_graph/graph_adjacency_matrix.swift ================================================ /** * File: graph_adjacency_matrix.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Undirected graph class based on adjacency matrix */ class GraphAdjMat { private var vertices: [Int] // Vertex list, where the element represents the "vertex value" and the index represents the "vertex index" private var adjMat: [[Int]] // Adjacency matrix, where the row and column indices correspond to the "vertex index" /* Constructor */ init(vertices: [Int], edges: [[Int]]) { self.vertices = [] adjMat = [] // Add vertex for val in vertices { addVertex(val: val) } // Add edge // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices for e in edges { addEdge(i: e[0], j: e[1]) } } /* Get the number of vertices */ func size() -> Int { vertices.count } /* Add vertex */ func addVertex(val: Int) { let n = size() // Add the value of the new vertex to the vertex list vertices.append(val) // Add a row to the adjacency matrix let newRow = Array(repeating: 0, count: n) adjMat.append(newRow) // Add a column to the adjacency matrix for i in adjMat.indices { adjMat[i].append(0) } } /* Remove vertex */ func removeVertex(index: Int) { if index >= size() { fatalError("Out of bounds") } // Remove the vertex at index from the vertex list vertices.remove(at: index) // Remove the row at index from the adjacency matrix adjMat.remove(at: index) // Remove the column at index from the adjacency matrix for i in adjMat.indices { adjMat[i].remove(at: index) } } /* Add edge */ // Parameters i, j correspond to the vertices element indices func addEdge(i: Int, j: Int) { // Handle index out of bounds and equality if i < 0 || j < 0 || i >= size() || j >= size() || i == j { fatalError("Out of bounds") } // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i) adjMat[i][j] = 1 adjMat[j][i] = 1 } /* Remove edge */ // Parameters i, j correspond to the vertices element indices func removeEdge(i: Int, j: Int) { // Handle index out of bounds and equality if i < 0 || j < 0 || i >= size() || j >= size() || i == j { fatalError("Out of bounds") } adjMat[i][j] = 0 adjMat[j][i] = 0 } /* Print adjacency matrix */ func print() { Swift.print("Vertex list = ", terminator: "") Swift.print(vertices) Swift.print("Adjacency matrix =") PrintUtil.printMatrix(matrix: adjMat) } } @main enum GraphAdjacencyMatrix { /* Driver Code */ static func main() { /* Add edge */ // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices let vertices = [1, 3, 2, 5, 4] let edges = [[0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4]] let graph = GraphAdjMat(vertices: vertices, edges: edges) print("\nAfter initialization, graph is") graph.print() /* Add edge */ // Add vertex graph.addEdge(i: 0, j: 2) print("\nAfter adding edge 1-2, graph is") graph.print() /* Remove edge */ // Vertices 1, 3 have indices 0, 1 respectively graph.removeEdge(i: 0, j: 1) print("\nAfter removing edge 1-3, graph is") graph.print() /* Add vertex */ graph.addVertex(val: 6) print("\nAfter adding vertex 6, graph is") graph.print() /* Remove vertex */ // Vertex 3 has index 1 graph.removeVertex(index: 1) print("\nAfter removing vertex 3, graph is") graph.print() } } ================================================ FILE: en/codes/swift/chapter_graph/graph_bfs.swift ================================================ /** * File: graph_bfs.swift * Created Time: 2023-02-21 * Author: nuomi1 (nuomi1@qq.com) */ import graph_adjacency_list_target import utils /* Breadth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { // Vertex traversal sequence var res: [Vertex] = [] // Hash set for recording vertices that have been visited var visited: Set = [startVet] // Queue used to implement BFS var que: [Vertex] = [startVet] // Starting from vertex vet, loop until all vertices are visited while !que.isEmpty { let vet = que.removeFirst() // Dequeue the front vertex res.append(vet) // Record visited vertex // Traverse all adjacent vertices of this vertex for adjVet in graph.adjList[vet] ?? [] { if visited.contains(adjVet) { continue // Skip vertices that have been visited } que.append(adjVet) // Only enqueue unvisited vertices visited.insert(adjVet) // Mark this vertex as visited } } // Return vertex traversal sequence return res } @main enum GraphBFS { /* Driver Code */ static func main() { /* Add edge */ let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) let edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ] let graph = GraphAdjList(edges: edges) print("\nAfter initialization, graph is") graph.print() /* Breadth-first traversal */ let res = graphBFS(graph: graph, startVet: v[0]) print("\nBreadth-first traversal (BFS) vertex sequence is") print(Vertex.vetsToVals(vets: res)) } } ================================================ FILE: en/codes/swift/chapter_graph/graph_dfs.swift ================================================ /** * File: graph_dfs.swift * Created Time: 2023-02-21 * Author: nuomi1 (nuomi1@qq.com) */ import graph_adjacency_list_target import utils /* Depth-first traversal helper function */ func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { res.append(vet) // Record visited vertex visited.insert(vet) // Mark this vertex as visited // Traverse all adjacent vertices of this vertex for adjVet in graph.adjList[vet] ?? [] { if visited.contains(adjVet) { continue // Skip vertices that have been visited } // Recursively visit adjacent vertices dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) } } /* Depth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { // Vertex traversal sequence var res: [Vertex] = [] // Hash set for recording vertices that have been visited var visited: Set = [] dfs(graph: graph, visited: &visited, res: &res, vet: startVet) return res } @main enum GraphDFS { /* Driver Code */ static func main() { /* Add edge */ let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6]) let edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ] let graph = GraphAdjList(edges: edges) print("\nAfter initialization, graph is") graph.print() /* Depth-first traversal */ let res = graphDFS(graph: graph, startVet: v[0]) print("\nDepth-first traversal (DFS) vertex sequence is") print(Vertex.vetsToVals(vets: res)) } } ================================================ FILE: en/codes/swift/chapter_greedy/coin_change_greedy.swift ================================================ /** * File: coin_change_greedy.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* Coin change: Greedy algorithm */ func coinChangeGreedy(coins: [Int], amt: Int) -> Int { // Assume coins list is sorted var i = coins.count - 1 var count = 0 var amt = amt // Loop to make greedy choices until no remaining amount while amt > 0 { // Find the coin that is less than and closest to the remaining amount while i > 0 && coins[i] > amt { i -= 1 } // Choose coins[i] amt -= coins[i] count += 1 } // If no feasible solution is found, return -1 return amt == 0 ? count : -1 } @main enum CoinChangeGreedy { /* Driver Code */ static func main() { // Greedy algorithm: Can guarantee finding the global optimal solution var coins = [1, 5, 10, 20, 50, 100] var amt = 186 var res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("Minimum coins needed to make \(amt) is \(res)") // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 20, 50] amt = 60 res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("Minimum coins needed to make \(amt) is \(res)") print("Actually the minimum number needed is 3, i.e., 20 + 20 + 20") // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 49, 50] amt = 98 res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("Minimum coins needed to make \(amt) is \(res)") print("Actually the minimum number needed is 2, i.e., 49 + 49") } } ================================================ FILE: en/codes/swift/chapter_greedy/fractional_knapsack.swift ================================================ /** * File: fractional_knapsack.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* Item */ class Item { var w: Int // Item weight var v: Int // Item value init(w: Int, v: Int) { self.w = w self.v = v } } /* Fractional knapsack: Greedy algorithm */ func fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double { // Create item list with two attributes: weight, value var items = zip(wgt, val).map { Item(w: $0, v: $1) } // Sort by unit value item.v / item.w from high to low items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) } // Loop for greedy selection var res = 0.0 var cap = cap for item in items { if item.w <= cap { // If remaining capacity is sufficient, put the entire current item into the knapsack res += Double(item.v) cap -= item.w } else { // If remaining capacity is insufficient, put part of the current item into the knapsack res += Double(item.v) / Double(item.w) * Double(cap) // No remaining capacity, so break out of the loop break } } return res } @main enum FractionalKnapsack { /* Driver Code */ static func main() { // Item weight let wgt = [10, 20, 30, 40, 50] // Item value let val = [50, 120, 150, 210, 240] // Knapsack capacity let cap = 50 // Greedy algorithm let res = fractionalKnapsack(wgt: wgt, val: val, cap: cap) print("Maximum item value not exceeding knapsack capacity is \(res)") } } ================================================ FILE: en/codes/swift/chapter_greedy/max_capacity.swift ================================================ /** * File: max_capacity.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* Max capacity: Greedy algorithm */ func maxCapacity(ht: [Int]) -> Int { // Initialize i, j to be at both ends of the array var i = ht.startIndex, j = ht.endIndex - 1 // Initial max capacity is 0 var res = 0 // Loop for greedy selection until the two boards meet while i < j { // Update max capacity let cap = min(ht[i], ht[j]) * (j - i) res = max(res, cap) // Move the shorter board inward if ht[i] < ht[j] { i += 1 } else { j -= 1 } } return res } @main enum MaxCapacity { /* Driver Code */ static func main() { let ht = [3, 8, 5, 2, 7, 7, 3, 4] // Greedy algorithm let res = maxCapacity(ht: ht) print("Maximum capacity is \(res)") } } ================================================ FILE: en/codes/swift/chapter_greedy/max_product_cutting.swift ================================================ /** * File: max_product_cutting.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ import Foundation func pow(_ x: Int, _ y: Int) -> Int { Int(Double(truncating: pow(Decimal(x), y) as NSDecimalNumber)) } /* Max product cutting: Greedy algorithm */ func maxProductCutting(n: Int) -> Int { // When n <= 3, must cut out a 1 if n <= 3 { return 1 * (n - 1) } // Greedily cut out 3, a is the number of 3s, b is the remainder let a = n / 3 let b = n % 3 if b == 1 { // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2 return pow(3, a - 1) * 2 * 2 } if b == 2 { // When the remainder is 2, do nothing return pow(3, a) * 2 } // When the remainder is 0, do nothing return pow(3, a) } @main enum MaxProductCutting { static func main() { let n = 58 // Greedy algorithm let res = maxProductCutting(n: n) print("Maximum cutting product is \(res)") } } ================================================ FILE: en/codes/swift/chapter_hashing/array_hash_map.swift ================================================ /** * File: array_hash_map.swift * Created Time: 2023-01-16 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Hash table based on array implementation */ class ArrayHashMap { private var buckets: [Pair?] init() { // Initialize array with 100 buckets buckets = Array(repeating: nil, count: 100) } /* Hash function */ private func hashFunc(key: Int) -> Int { let index = key % 100 return index } /* Query operation */ func get(key: Int) -> String? { let index = hashFunc(key: key) let pair = buckets[index] return pair?.val } /* Add operation */ func put(key: Int, val: String) { let pair = Pair(key: key, val: val) let index = hashFunc(key: key) buckets[index] = pair } /* Remove operation */ func remove(key: Int) { let index = hashFunc(key: key) // Set to nil to delete buckets[index] = nil } /* Get all key-value pairs */ func pairSet() -> [Pair] { buckets.compactMap { $0 } } /* Get all keys */ func keySet() -> [Int] { buckets.compactMap { $0?.key } } /* Get all values */ func valueSet() -> [String] { buckets.compactMap { $0?.val } } /* Print hash table */ func print() { for pair in pairSet() { Swift.print("\(pair.key) -> \(pair.val)") } } } @main enum _ArrayHashMap { /* Driver Code */ static func main() { /* Initialize hash table */ let map = ArrayHashMap() /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(key: 12836, val: "Xiao Ha") map.put(key: 15937, val: "Xiao Luo") map.put(key: 16750, val: "Xiao Suan") map.put(key: 13276, val: "Xiao Fa") map.put(key: 10583, val: "Xiao Ya") print("\nAfter adding is complete, hash table is\nKey -> Value") map.print() /* Query operation */ // Input key into hash table to get value let name = map.get(key: 15937)! print("\nInput student ID 15937, found name \(name)") /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(key: 10583) print("\nAfter removing 10583, hash table is\nKey -> Value") map.print() /* Traverse hash table */ print("\nTraverse key-value pairs Key->Value") for pair in map.pairSet() { print("\(pair.key) -> \(pair.val)") } print("\nTraverse keys only Key") for key in map.keySet() { print(key) } print("\nTraverse values only Value") for val in map.valueSet() { print(val) } } } ================================================ FILE: en/codes/swift/chapter_hashing/built_in_hash.swift ================================================ /** * File: built_in_hash.swift * Created Time: 2023-07-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum BuiltInHash { /* Driver Code */ static func main() { let num = 3 let hashNum = num.hashValue print("Hash value of integer \(num) is \(hashNum)") let bol = true let hashBol = bol.hashValue print("Hash value of boolean \(bol) is \(hashBol)") let dec = 3.14159 let hashDec = dec.hashValue print("Hash value of decimal \(dec) is \(hashDec)") let str = "Hello Algo" let hashStr = str.hashValue print("Hash value of string \(str) is \(hashStr)") let arr = [AnyHashable(12836), AnyHashable("Xiao Ha")] let hashTup = arr.hashValue print("Hash value of array \(arr) is \(hashTup)") let obj = ListNode(x: 0) let hashObj = obj.hashValue print("Hash value of node object \(obj) is \(hashObj)") } } ================================================ FILE: en/codes/swift/chapter_hashing/hash_map.swift ================================================ /** * File: hash_map.swift * Created Time: 2023-01-16 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum HashMap { /* Driver Code */ static func main() { /* Initialize hash table */ var map: [Int: String] = [:] /* Add operation */ // Add key-value pair (key, value) to the hash table map[12836] = "Xiao Ha" map[15937] = "Xiao Luo" map[16750] = "Xiao Suan" map[13276] = "Xiao Fa" map[10583] = "Xiao Ya" print("\nAfter adding is complete, hash table is\nKey -> Value") PrintUtil.printHashMap(map: map) /* Query operation */ // Input key into hash table to get value let name = map[15937]! print("\nInput student ID 15937, found name \(name)") /* Remove operation */ // Remove key-value pair (key, value) from hash table map.removeValue(forKey: 10583) print("\nAfter removing 10583, hash table is\nKey -> Value") PrintUtil.printHashMap(map: map) /* Traverse hash table */ print("\nTraverse key-value pairs Key->Value") for (key, value) in map { print("\(key) -> \(value)") } print("\nTraverse keys only Key") for key in map.keys { print(key) } print("\nTraverse values only Value") for value in map.values { print(value) } } } ================================================ FILE: en/codes/swift/chapter_hashing/hash_map_chaining.swift ================================================ /** * File: hash_map_chaining.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Hash table with separate chaining */ class HashMapChaining { var size: Int // Number of key-value pairs var capacity: Int // Hash table capacity var loadThres: Double // Load factor threshold for triggering expansion var extendRatio: Int // Expansion multiplier var buckets: [[Pair]] // Bucket array /* Constructor */ init() { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = Array(repeating: [], count: capacity) } /* Hash function */ func hashFunc(key: Int) -> Int { key % capacity } /* Load factor */ func loadFactor() -> Double { Double(size) / Double(capacity) } /* Query operation */ func get(key: Int) -> String? { let index = hashFunc(key: key) let bucket = buckets[index] // Traverse bucket, if key is found, return corresponding val for pair in bucket { if pair.key == key { return pair.val } } // Return nil if key not found return nil } /* Add operation */ func put(key: Int, val: String) { // When load factor exceeds threshold, perform expansion if loadFactor() > loadThres { extend() } let index = hashFunc(key: key) let bucket = buckets[index] // Traverse bucket, if specified key is encountered, update corresponding val and return for pair in bucket { if pair.key == key { pair.val = val return } } // If key does not exist, append key-value pair to the end let pair = Pair(key: key, val: val) buckets[index].append(pair) size += 1 } /* Remove operation */ func remove(key: Int) { let index = hashFunc(key: key) let bucket = buckets[index] // Traverse bucket and remove key-value pair from it for (pairIndex, pair) in bucket.enumerated() { if pair.key == key { buckets[index].remove(at: pairIndex) size -= 1 break } } } /* Expand hash table */ func extend() { // Temporarily store the original hash table let bucketsTmp = buckets // Initialize expanded new hash table capacity *= extendRatio buckets = Array(repeating: [], count: capacity) size = 0 // Move key-value pairs from original hash table to new hash table for bucket in bucketsTmp { for pair in bucket { put(key: pair.key, val: pair.val) } } } /* Print hash table */ func print() { for bucket in buckets { let res = bucket.map { "\($0.key) -> \($0.val)" } Swift.print(res) } } } @main enum _HashMapChaining { /* Driver Code */ static func main() { /* Initialize hash table */ let map = HashMapChaining() /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(key: 12836, val: "Xiao Ha") map.put(key: 15937, val: "Xiao Luo") map.put(key: 16750, val: "Xiao Suan") map.put(key: 13276, val: "Xiao Fa") map.put(key: 10583, val: "Xiao Ya") print("\nAfter adding is complete, hash table is\nKey -> Value") map.print() /* Query operation */ // Input key into hash table to get value let name = map.get(key: 13276) print("\nInput student ID 13276, found name \(name!)") /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(key: 12836) print("\nAfter removing 12836, hash table is\nKey -> Value") map.print() } } ================================================ FILE: en/codes/swift/chapter_hashing/hash_map_open_addressing.swift ================================================ /** * File: hash_map_open_addressing.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Hash table with open addressing */ class HashMapOpenAddressing { var size: Int // Number of key-value pairs var capacity: Int // Hash table capacity var loadThres: Double // Load factor threshold for triggering expansion var extendRatio: Int // Expansion multiplier var buckets: [Pair?] // Bucket array var TOMBSTONE: Pair // Removal marker /* Constructor */ init() { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = Array(repeating: nil, count: capacity) TOMBSTONE = Pair(key: -1, val: "-1") } /* Hash function */ func hashFunc(key: Int) -> Int { key % capacity } /* Load factor */ func loadFactor() -> Double { Double(size) / Double(capacity) } /* Search for bucket index corresponding to key */ func findBucket(key: Int) -> Int { var index = hashFunc(key: key) var firstTombstone = -1 // Linear probing, break when encountering an empty bucket while buckets[index] != nil { // If key is encountered, return the corresponding bucket index if buckets[index]!.key == key { // If a removal marker was encountered before, move the key-value pair to that index if firstTombstone != -1 { buckets[firstTombstone] = buckets[index] buckets[index] = TOMBSTONE return firstTombstone // Return the moved bucket index } return index // Return bucket index } // Record the first removal marker encountered if firstTombstone == -1 && buckets[index] == TOMBSTONE { firstTombstone = index } // Calculate bucket index, wrap around to the head if past the tail index = (index + 1) % capacity } // If key does not exist, return the index for insertion return firstTombstone == -1 ? index : firstTombstone } /* Query operation */ func get(key: Int) -> String? { // Search for bucket index corresponding to key let index = findBucket(key: key) // If key-value pair is found, return corresponding val if buckets[index] != nil, buckets[index] != TOMBSTONE { return buckets[index]!.val } // If key-value pair does not exist, return null return nil } /* Add operation */ func put(key: Int, val: String) { // When load factor exceeds threshold, perform expansion if loadFactor() > loadThres { extend() } // Search for bucket index corresponding to key let index = findBucket(key: key) // If key-value pair is found, overwrite val and return if buckets[index] != nil, buckets[index] != TOMBSTONE { buckets[index]!.val = val return } // If key-value pair does not exist, add the key-value pair buckets[index] = Pair(key: key, val: val) size += 1 } /* Remove operation */ func remove(key: Int) { // Search for bucket index corresponding to key let index = findBucket(key: key) // If key-value pair is found, overwrite it with removal marker if buckets[index] != nil, buckets[index] != TOMBSTONE { buckets[index] = TOMBSTONE size -= 1 } } /* Expand hash table */ func extend() { // Temporarily store the original hash table let bucketsTmp = buckets // Initialize expanded new hash table capacity *= extendRatio buckets = Array(repeating: nil, count: capacity) size = 0 // Move key-value pairs from original hash table to new hash table for pair in bucketsTmp { if let pair, pair != TOMBSTONE { put(key: pair.key, val: pair.val) } } } /* Print hash table */ func print() { for pair in buckets { if pair == nil { Swift.print("null") } else if pair == TOMBSTONE { Swift.print("TOMBSTONE") } else { Swift.print("\(pair!.key) -> \(pair!.val)") } } } } @main enum _HashMapOpenAddressing { /* Driver Code */ static func main() { /* Initialize hash table */ let map = HashMapOpenAddressing() /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(key: 12836, val: "Xiao Ha") map.put(key: 15937, val: "Xiao Luo") map.put(key: 16750, val: "Xiao Suan") map.put(key: 13276, val: "Xiao Fa") map.put(key: 10583, val: "Xiao Ya") print("\nAfter adding is complete, hash table is\nKey -> Value") map.print() /* Query operation */ // Input key into hash table to get value let name = map.get(key: 13276) print("\nInput student ID 13276, found name \(name!)") /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(key: 16750) print("\nAfter removing 16750, hash table is\nKey -> Value") map.print() } } ================================================ FILE: en/codes/swift/chapter_hashing/simple_hash.swift ================================================ /** * File: simple_hash.swift * Created Time: 2023-07-01 * Author: nuomi1 (nuomi1@qq.com) */ /* Additive hash */ func addHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = (hash + Int(scalar.value)) % MODULUS } } return hash } /* Multiplicative hash */ func mulHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = (31 * hash + Int(scalar.value)) % MODULUS } } return hash } /* XOR hash */ func xorHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash ^= Int(scalar.value) } } return hash & MODULUS } /* Rotational hash */ func rotHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS } } return hash } @main enum SimpleHash { /* Driver Code */ static func main() { let key = "Hello Algo" var hash = addHash(key: key) print("Additive hash value is \(hash)") hash = mulHash(key: key) print("Multiplicative hash value is \(hash)") hash = xorHash(key: key) print("XOR hash value is \(hash)") hash = rotHash(key: key) print("Rotational hash value is \(hash)") } } ================================================ FILE: en/codes/swift/chapter_heap/heap.swift ================================================ /** * File: heap.swift * Created Time: 2024-03-17 * Author: nuomi1 (nuomi1@qq.com) */ import HeapModule import utils func testPush(heap: inout Heap, val: Int) { heap.insert(val) print("\nAfter element \(val) pushes to heap\n") PrintUtil.printHeap(queue: heap.unordered) } func testPop(heap: inout Heap) { let val = heap.removeMax() print("\nAfter heap top element \(val) pops from heap\n") PrintUtil.printHeap(queue: heap.unordered) } @main enum _Heap { /* Driver Code */ static func main() { /* Initialize heap */ // Swift's Heap type supports both max heap and min heap var heap = Heap() /* Element enters heap */ testPush(heap: &heap, val: 1) testPush(heap: &heap, val: 3) testPush(heap: &heap, val: 2) testPush(heap: &heap, val: 5) testPush(heap: &heap, val: 4) /* Check if heap is empty */ let peek = heap.max() print("\nHeap top element is \(peek!)\n") /* Time complexity is O(n), not O(nlogn) */ testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) /* Get heap size */ let size = heap.count print("\nHeap size is \(size)\n") /* Check if heap is empty */ let isEmpty = heap.isEmpty print("\nIs heap empty \(isEmpty)\n") /* Input list and build heap */ // Time complexity is O(n), not O(nlogn) let heap2 = Heap([1, 3, 2, 5, 4]) print("\nAfter input list and build heap") PrintUtil.printHeap(queue: heap2.unordered) } } ================================================ FILE: en/codes/swift/chapter_heap/my_heap.swift ================================================ /** * File: my_heap.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Max heap */ class MaxHeap { private var maxHeap: [Int] /* Constructor, build heap based on input list */ init(nums: [Int]) { // Add list elements to heap as is maxHeap = nums // Heapify all nodes except leaf nodes for i in (0 ... parent(i: size() - 1)).reversed() { siftDown(i: i) } } /* Get index of left child node */ private func left(i: Int) -> Int { 2 * i + 1 } /* Get index of right child node */ private func right(i: Int) -> Int { 2 * i + 2 } /* Get index of parent node */ private func parent(i: Int) -> Int { (i - 1) / 2 // Floor division } /* Swap elements */ private func swap(i: Int, j: Int) { maxHeap.swapAt(i, j) } /* Get heap size */ func size() -> Int { maxHeap.count } /* Check if heap is empty */ func isEmpty() -> Bool { size() == 0 } /* Access top element */ func peek() -> Int { maxHeap[0] } /* Element enters heap */ func push(val: Int) { // Add node maxHeap.append(val) // Heapify from bottom to top siftUp(i: size() - 1) } /* Starting from node i, heapify from bottom to top */ private func siftUp(i: Int) { var i = i while true { // Get parent node of node i let p = parent(i: i) // When "crossing root node" or "node needs no repair", end heapify if p < 0 || maxHeap[i] <= maxHeap[p] { break } // Swap two nodes swap(i: i, j: p) // Loop upward heapify i = p } } /* Element exits heap */ func pop() -> Int { // Handle empty case if isEmpty() { fatalError("Heap is empty") } // Delete node swap(i: 0, j: size() - 1) // Remove node let val = maxHeap.remove(at: size() - 1) // Return top element siftDown(i: 0) // Return heap top element return val } /* Starting from node i, heapify from top to bottom */ private func siftDown(i: Int) { var i = i while true { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break let l = left(i: i) let r = right(i: i) var ma = i if l < size(), maxHeap[l] > maxHeap[ma] { ma = l } if r < size(), maxHeap[r] > maxHeap[ma] { ma = r } // Swap two nodes if ma == i { break } // Swap two nodes swap(i: i, j: ma) // Loop downwards heapification i = ma } } /* Driver Code */ func print() { let queue = maxHeap PrintUtil.printHeap(queue: queue) } } @main enum MyHeap { /* Driver Code */ static func main() { /* Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap */ let maxHeap = MaxHeap(nums: [9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) print("\nAfter inputting list and building heap") maxHeap.print() /* Check if heap is empty */ var peek = maxHeap.peek() print("\nHeap top element is \(peek)") /* Element enters heap */ let val = 7 maxHeap.push(val: val) print("\nAfter element \(val) pushes to heap") maxHeap.print() /* Time complexity is O(n), not O(nlogn) */ peek = maxHeap.pop() print("\nAfter heap top element \(peek) pops from heap") maxHeap.print() /* Get heap size */ let size = maxHeap.size() print("\nHeap size is \(size)") /* Check if heap is empty */ let isEmpty = maxHeap.isEmpty() print("\nIs heap empty \(isEmpty)") } } ================================================ FILE: en/codes/swift/chapter_heap/top_k.swift ================================================ /** * File: top_k.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ import HeapModule import utils /* Find the largest k elements in array based on heap */ func topKHeap(nums: [Int], k: Int) -> [Int] { // Initialize min heap and build heap with first k elements var heap = Heap(nums.prefix(k)) // Starting from the (k+1)th element, maintain heap length as k for i in nums.indices.dropFirst(k) { // If current element is greater than top element, top element exits heap, current element enters heap if nums[i] > heap.min()! { _ = heap.removeMin() heap.insert(nums[i]) } } return heap.unordered } @main enum TopK { /* Driver Code */ static func main() { let nums = [1, 7, 6, 3, 2] let k = 3 let res = topKHeap(nums: nums, k: k) print("The largest \(k) elements are") PrintUtil.printHeap(queue: res) } } ================================================ FILE: en/codes/swift/chapter_searching/binary_search.swift ================================================ /** * File: binary_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ /* Binary search (closed interval on both sides) */ func binarySearch(nums: [Int], target: Int) -> Int { // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array var i = nums.startIndex var j = nums.endIndex - 1 // Loop, exit when the search interval is empty (empty when i > j) while i <= j { let m = i + (j - i) / 2 // Calculate the midpoint index m if nums[m] < target { // This means target is in the interval [m+1, j] i = m + 1 } else if nums[m] > target { // This means target is in the interval [i, m-1] j = m - 1 } else { // Found the target element, return its index return m } } // Target element not found, return -1 return -1 } /* Binary search (left-closed right-open interval) */ func binarySearchLCRO(nums: [Int], target: Int) -> Int { // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1 var i = nums.startIndex var j = nums.endIndex // Loop, exit when the search interval is empty (empty when i = j) while i < j { let m = i + (j - i) / 2 // Calculate the midpoint index m if nums[m] < target { // This means target is in the interval [m+1, j) i = m + 1 } else if nums[m] > target { // This means target is in the interval [i, m) j = m } else { // Found the target element, return its index return m } } // Target element not found, return -1 return -1 } @main enum BinarySearch { /* Driver Code */ static func main() { let target = 6 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] /* Binary search (closed interval on both sides) */ var index = binarySearch(nums: nums, target: target) print("Index of target element 6 = \(index)") /* Binary search (left-closed right-open interval) */ index = binarySearchLCRO(nums: nums, target: target) print("Index of target element 6 = \(index)") } } ================================================ FILE: en/codes/swift/chapter_searching/binary_search_edge.swift ================================================ /** * File: binary_search_edge.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ import binary_search_insertion_target /* Binary search for the leftmost target */ func binarySearchLeftEdge(nums: [Int], target: Int) -> Int { // Equivalent to finding the insertion point of target let i = binarySearchInsertion(nums: nums, target: target) // Target not found, return -1 if i == nums.endIndex || nums[i] != target { return -1 } // Found target, return index i return i } /* Binary search for the rightmost target */ func binarySearchRightEdge(nums: [Int], target: Int) -> Int { // Convert to finding the leftmost target + 1 let i = binarySearchInsertion(nums: nums, target: target + 1) // j points to the rightmost target, i points to the first element greater than target let j = i - 1 // Target not found, return -1 if j == -1 || nums[j] != target { return -1 } // Found target, return index j return j } @main enum BinarySearchEdge { /* Driver Code */ static func main() { // Array with duplicate elements let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\nArray nums = \(nums)") // Binary search left and right boundaries for target in [6, 7] { var index = binarySearchLeftEdge(nums: nums, target: target) print("Leftmost element \(target) index is \(index)") index = binarySearchRightEdge(nums: nums, target: target) print("Rightmost element \(target) index is \(index)") } } } ================================================ FILE: en/codes/swift/chapter_searching/binary_search_insertion.swift ================================================ /** * File: binary_search_insertion.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ /* Binary search for insertion point (no duplicate elements) */ func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { // Initialize closed interval [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // Calculate the midpoint index m if nums[m] < target { i = m + 1 // target is in the interval [m+1, j] } else if nums[m] > target { j = m - 1 // target is in the interval [i, m-1] } else { return m // Found target, return insertion point m } } // Target not found, return insertion point i return i } /* Binary search for insertion point (with duplicate elements) */ public func binarySearchInsertion(nums: [Int], target: Int) -> Int { // Initialize closed interval [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // Calculate the midpoint index m if nums[m] < target { i = m + 1 // target is in the interval [m+1, j] } else if nums[m] > target { j = m - 1 // target is in the interval [i, m-1] } else { j = m - 1 // The first element less than target is in the interval [i, m-1] } } // Return insertion point i return i } #if !TARGET @main enum BinarySearchInsertion { /* Driver Code */ static func main() { // Array without duplicate elements var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print("\nArray nums = \(nums)") // Binary search for insertion point for target in [6, 9] { let index = binarySearchInsertionSimple(nums: nums, target: target) print("Insertion point index for element \(target) is \(index)") } // Array with duplicate elements nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\nArray nums = \(nums)") // Binary search for insertion point for target in [2, 6, 20] { let index = binarySearchInsertion(nums: nums, target: target) print("Insertion point index for element \(target) is \(index)") } } } #endif ================================================ FILE: en/codes/swift/chapter_searching/binary_search_insertion_target.swift ================================================ /** * File: binary_search_insertion.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ /* Binary search for insertion point (no duplicate elements) */ func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { // Initialize closed interval [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // Calculate the midpoint index m if nums[m] < target { i = m + 1 // target is in the interval [m+1, j] } else if nums[m] > target { j = m - 1 // target is in the interval [i, m-1] } else { return m // Found target, return insertion point m } } // Target not found, return insertion point i return i } /* Binary search for insertion point (with duplicate elements) */ public func binarySearchInsertion(nums: [Int], target: Int) -> Int { // Initialize closed interval [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // Calculate the midpoint index m if nums[m] < target { i = m + 1 // target is in the interval [m+1, j] } else if nums[m] > target { j = m - 1 // target is in the interval [i, m-1] } else { j = m - 1 // The first element less than target is in the interval [i, m-1] } } // Return insertion point i return i } #if !TARGET @main enum BinarySearchInsertion { /* Driver Code */ static func main() { // Array without duplicate elements var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print("\nArray nums = \(nums)") // Binary search for insertion point for target in [6, 9] { let index = binarySearchInsertionSimple(nums: nums, target: target) print("Insertion point index for element \(target) is \(index)") } // Array with duplicate elements nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\nArray nums = \(nums)") // Binary search for insertion point for target in [2, 6, 20] { let index = binarySearchInsertion(nums: nums, target: target) print("Insertion point index for element \(target) is \(index)") } } } #endif ================================================ FILE: en/codes/swift/chapter_searching/hashing_search.swift ================================================ /** * File: hashing_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Hash search (array) */ func hashingSearchArray(map: [Int: Int], target: Int) -> Int { // Hash table's key: target element, value: index // If this key does not exist in the hash table, return -1 return map[target, default: -1] } /* Hash search (linked list) */ func hashingSearchLinkedList(map: [Int: ListNode], target: Int) -> ListNode? { // Hash table key: target node value, value: node object // If key is not in hash table, return null return map[target] } @main enum HashingSearch { /* Driver Code */ static func main() { let target = 3 /* Hash search (array) */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] // Initialize hash table var map: [Int: Int] = [:] for i in nums.indices { map[nums[i]] = i // key: element, value: index } let index = hashingSearchArray(map: map, target: target) print("Index of target element 3 = \(index)") /* Hash search (linked list) */ var head = ListNode.arrToLinkedList(arr: nums) // Initialize hash table var map1: [Int: ListNode] = [:] while head != nil { map1[head!.val] = head! // key: node value, value: node head = head?.next } let node = hashingSearchLinkedList(map: map1, target: target) print("Node object corresponding to target node value 3 is \(node!)") } } ================================================ FILE: en/codes/swift/chapter_searching/linear_search.swift ================================================ /** * File: linear_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Linear search (array) */ func linearSearchArray(nums: [Int], target: Int) -> Int { // Traverse array for i in nums.indices { // Found the target element, return its index if nums[i] == target { return i } } // Target element not found, return -1 return -1 } /* Linear search (linked list) */ func linearSearchLinkedList(head: ListNode?, target: Int) -> ListNode? { var head = head // Traverse the linked list while head != nil { // Found the target node, return it if head?.val == target { return head } head = head?.next } // Target node not found, return null return nil } @main enum LinearSearch { /* Driver Code */ static func main() { let target = 3 /* Perform linear search in array */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] let index = linearSearchArray(nums: nums, target: target) print("Index of target element 3 = \(index)") /* Perform linear search in linked list */ let head = ListNode.arrToLinkedList(arr: nums) let node = linearSearchLinkedList(head: head, target: target) print("Node object corresponding to target node value 3 is \(node!)") } } ================================================ FILE: en/codes/swift/chapter_searching/two_sum.swift ================================================ /** * File: two_sum.swift * Created Time: 2023-01-03 * Author: nuomi1 (nuomi1@qq.com) */ /* Method 1: Brute force enumeration */ func twoSumBruteForce(nums: [Int], target: Int) -> [Int] { // Two nested loops, time complexity is O(n^2) for i in nums.indices.dropLast() { for j in nums.indices.dropFirst(i + 1) { if nums[i] + nums[j] == target { return [i, j] } } } return [0] } /* Method 2: Auxiliary hash table */ func twoSumHashTable(nums: [Int], target: Int) -> [Int] { // Auxiliary hash table, space complexity is O(n) var dic: [Int: Int] = [:] // Single loop, time complexity is O(n) for i in nums.indices { if let j = dic[target - nums[i]] { return [j, i] } dic[nums[i]] = i } return [0] } @main enum LeetcodeTwoSum { /* Driver Code */ static func main() { // ======= Test Case ======= let nums = [2, 7, 11, 15] let target = 13 // ====== Driver Code ====== // Method 1 var res = twoSumBruteForce(nums: nums, target: target) print("Method 1 res = \(res)") // Method 2 res = twoSumHashTable(nums: nums, target: target) print("Method 2 res = \(res)") } } ================================================ FILE: en/codes/swift/chapter_sorting/bubble_sort.swift ================================================ /** * File: bubble_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* Bubble sort */ func bubbleSort(nums: inout [Int]) { // Outer loop: unsorted range is [0, i] for i in nums.indices.dropFirst().reversed() { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for j in 0 ..< i { if nums[j] > nums[j + 1] { // Swap nums[j] and nums[j + 1] nums.swapAt(j, j + 1) } } } } /* Bubble sort (flag optimization) */ func bubbleSortWithFlag(nums: inout [Int]) { // Outer loop: unsorted range is [0, i] for i in nums.indices.dropFirst().reversed() { var flag = false // Initialize flag for j in 0 ..< i { if nums[j] > nums[j + 1] { // Swap nums[j] and nums[j + 1] nums.swapAt(j, j + 1) flag = true // Record element swap } } if !flag { // No elements were swapped in this round of "bubbling", exit directly break } } } @main enum BubbleSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] bubbleSort(nums: &nums) print("After bubble sort, nums = \(nums)") var nums1 = [4, 1, 3, 1, 5, 2] bubbleSortWithFlag(nums: &nums1) print("After bubble sort, nums1 = \(nums1)") } } ================================================ FILE: en/codes/swift/chapter_sorting/bucket_sort.swift ================================================ /** * File: bucket_sort.swift * Created Time: 2023-03-27 * Author: nuomi1 (nuomi1@qq.com) */ /* Bucket sort */ func bucketSort(nums: inout [Double]) { // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket let k = nums.count / 2 var buckets = (0 ..< k).map { _ in [Double]() } // 1. Distribute array elements into various buckets for num in nums { // Input data range is [0, 1), use num * k to map to index range [0, k-1] let i = Int(num * Double(k)) // Add num to bucket i buckets[i].append(num) } // 2. Sort each bucket for i in buckets.indices { // Use built-in sorting function, can also replace with other sorting algorithms buckets[i].sort() } // 3. Traverse buckets to merge results var i = nums.startIndex for bucket in buckets { for num in bucket { nums[i] = num i += 1 } } } @main enum BucketSort { /* Driver Code */ static func main() { // Assume input data is floating point, interval [0, 1) var nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] bucketSort(nums: &nums) print("After bucket sort, nums = \(nums)") } } ================================================ FILE: en/codes/swift/chapter_sorting/counting_sort.swift ================================================ /** * File: counting_sort.swift * Created Time: 2023-03-22 * Author: nuomi1 (nuomi1@qq.com) */ /* Counting sort */ // Simple implementation, cannot be used for sorting objects func countingSortNaive(nums: inout [Int]) { // 1. Count the maximum element m in the array let m = nums.max()! // 2. Count the occurrence of each number // counter[num] represents the occurrence of num var counter = Array(repeating: 0, count: m + 1) for num in nums { counter[num] += 1 } // 3. Traverse counter, filling each element back into the original array nums var i = 0 for num in 0 ..< m + 1 { for _ in 0 ..< counter[num] { nums[i] = num i += 1 } } } /* Counting sort */ // Complete implementation, can sort objects and is a stable sort func countingSort(nums: inout [Int]) { // 1. Count the maximum element m in the array let m = nums.max()! // 2. Count the occurrence of each number // counter[num] represents the occurrence of num var counter = Array(repeating: 0, count: m + 1) for num in nums { counter[num] += 1 } // 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" // counter[num]-1 is the last index where num appears in res for i in 0 ..< m { counter[i + 1] += counter[i] } // 4. Traverse nums in reverse order, placing each element into the result array res // Initialize the array res to record results var res = Array(repeating: 0, count: nums.count) for i in nums.indices.reversed() { let num = nums[i] res[counter[num] - 1] = num // Place num at the corresponding index counter[num] -= 1 // Decrement the prefix sum by 1, getting the next index to place num } // Use result array res to overwrite the original array nums for i in nums.indices { nums[i] = res[i] } } @main enum CountingSort { /* Driver Code */ static func main() { var nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] countingSortNaive(nums: &nums) print("After counting sort (cannot sort objects), nums = \(nums)") var nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] countingSort(nums: &nums1) print("After counting sort, nums1 = \(nums1)") } } ================================================ FILE: en/codes/swift/chapter_sorting/heap_sort.swift ================================================ /** * File: heap_sort.swift * Created Time: 2023-05-28 * Author: nuomi1 (nuomi1@qq.com) */ /* Heap length is n, start heapifying node i, from top to bottom */ func siftDown(nums: inout [Int], n: Int, i: Int) { var i = i while true { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break let l = 2 * i + 1 let r = 2 * i + 2 var ma = i if l < n, nums[l] > nums[ma] { ma = l } if r < n, nums[r] > nums[ma] { ma = r } // Swap two nodes if ma == i { break } // Swap two nodes nums.swapAt(i, ma) // Loop downwards heapification i = ma } } /* Heap sort */ func heapSort(nums: inout [Int]) { // Build heap operation: heapify all nodes except leaves for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) { siftDown(nums: &nums, n: nums.count, i: i) } // Extract the largest element from the heap and repeat for n-1 rounds for i in nums.indices.dropFirst().reversed() { // Delete node nums.swapAt(0, i) // Start heapifying the root node, from top to bottom siftDown(nums: &nums, n: i, i: 0) } } @main enum HeapSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] heapSort(nums: &nums) print("After heap sort, nums = \(nums)") } } ================================================ FILE: en/codes/swift/chapter_sorting/insertion_sort.swift ================================================ /** * File: insertion_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* Insertion sort */ func insertionSort(nums: inout [Int]) { // Outer loop: sorted interval is [0, i-1] for i in nums.indices.dropFirst() { let base = nums[i] var j = i - 1 // Inner loop: insert base into the correct position within the sorted interval [0, i-1] while j >= 0, nums[j] > base { nums[j + 1] = nums[j] // Move nums[j] to the right by one position j -= 1 } nums[j + 1] = base // Assign base to the correct position } } @main enum InsertionSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] insertionSort(nums: &nums) print("After insertion sort, nums = \(nums)") } } ================================================ FILE: en/codes/swift/chapter_sorting/merge_sort.swift ================================================ /** * File: merge_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* Merge left subarray and right subarray */ func merge(nums: inout [Int], left: Int, mid: Int, right: Int) { // Left subarray interval is [left, mid], right subarray interval is [mid+1, right] // Create a temporary array tmp to store the merged results var tmp = Array(repeating: 0, count: right - left + 1) // Initialize the start indices of the left and right subarrays var i = left, j = mid + 1, k = 0 // While both subarrays still have elements, compare and copy the smaller element into the temporary array while i <= mid, j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i] i += 1 } else { tmp[k] = nums[j] j += 1 } k += 1 } // Copy the remaining elements of the left and right subarrays into the temporary array while i <= mid { tmp[k] = nums[i] i += 1 k += 1 } while j <= right { tmp[k] = nums[j] j += 1 k += 1 } // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval for k in tmp.indices { nums[left + k] = tmp[k] } } /* Merge sort */ func mergeSort(nums: inout [Int], left: Int, right: Int) { // Termination condition if left >= right { // Terminate recursion when subarray length is 1 return } // Divide and conquer stage let mid = left + (right - left) / 2 // Calculate midpoint mergeSort(nums: &nums, left: left, right: mid) // Recursively process the left subarray mergeSort(nums: &nums, left: mid + 1, right: right) // Recursively process the right subarray // Merge stage merge(nums: &nums, left: left, mid: mid, right: right) } @main enum MergeSort { /* Driver Code */ static func main() { /* Merge sort */ var nums = [7, 3, 2, 6, 0, 1, 5, 4] mergeSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) print("After merge sort, nums = \(nums)") } } ================================================ FILE: en/codes/swift/chapter_sorting/quick_sort.swift ================================================ /** * File: quick_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* Quick sort class */ /* Sentinel partition */ func partition(nums: inout [Int], left: Int, right: Int) -> Int { // Use nums[left] as the pivot var i = left var j = right while i < j { while i < j, nums[j] >= nums[left] { j -= 1 // Search from right to left for the first element smaller than the pivot } while i < j, nums[i] <= nums[left] { i += 1 // Search from left to right for the first element greater than the pivot } nums.swapAt(i, j) // Swap these two elements } nums.swapAt(i, left) // Swap the pivot to the boundary between the two subarrays return i // Return the index of the pivot } /* Quick sort */ func quickSort(nums: inout [Int], left: Int, right: Int) { // Terminate recursion when subarray length is 1 if left >= right { return } // Sentinel partition let pivot = partition(nums: &nums, left: left, right: right) // Recursively process the left subarray and right subarray quickSort(nums: &nums, left: left, right: pivot - 1) quickSort(nums: &nums, left: pivot + 1, right: right) } /* Quick sort class (median pivot optimization) */ /* Select the median of three candidate elements */ func medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int { let l = nums[left] let m = nums[mid] let r = nums[right] if (l <= m && m <= r) || (r <= m && m <= l) { return mid // m is between l and r } if (m <= l && l <= r) || (r <= l && l <= m) { return left // l is between m and r } return right } /* Sentinel partition (median of three) */ func partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int { // Select the median of three candidate elements let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right) // Swap the median to the array's leftmost position nums.swapAt(left, med) return partition(nums: &nums, left: left, right: right) } /* Quick sort (recursion depth optimization) */ func quickSortMedian(nums: inout [Int], left: Int, right: Int) { // Terminate recursion when subarray length is 1 if left >= right { return } // Sentinel partition let pivot = partitionMedian(nums: &nums, left: left, right: right) // Recursively process the left subarray and right subarray quickSortMedian(nums: &nums, left: left, right: pivot - 1) quickSortMedian(nums: &nums, left: pivot + 1, right: right) } /* Quick sort (recursion depth optimization) */ func quickSortTailCall(nums: inout [Int], left: Int, right: Int) { var left = left var right = right // Terminate when subarray length is 1 while left < right { // Sentinel partition operation let pivot = partition(nums: &nums, left: left, right: right) // Perform quick sort on the shorter of the two subarrays if (pivot - left) < (right - pivot) { quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // Recursively sort the left subarray left = pivot + 1 // Remaining unsorted interval is [pivot + 1, right] } else { quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // Recursively sort the right subarray right = pivot - 1 // Remaining unsorted interval is [left, pivot - 1] } } } @main enum QuickSort { /* Driver Code */ static func main() { /* Quick sort */ var nums = [2, 4, 1, 0, 3, 5] quickSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) print("After quick sort, nums = \(nums)") /* Quick sort (recursion depth optimization) */ var nums1 = [2, 4, 1, 0, 3, 5] quickSortMedian(nums: &nums1, left: nums1.startIndex, right: nums1.endIndex - 1) print("After quick sort (median pivot optimization), nums1 = \(nums1)") /* Quick sort (recursion depth optimization) */ var nums2 = [2, 4, 1, 0, 3, 5] quickSortTailCall(nums: &nums2, left: nums2.startIndex, right: nums2.endIndex - 1) print("After quick sort (recursion depth optimization), nums2 = \(nums2)") } } ================================================ FILE: en/codes/swift/chapter_sorting/radix_sort.swift ================================================ /** * File: radix_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* Get the k-th digit of element num, where exp = 10^(k-1) */ func digit(num: Int, exp: Int) -> Int { // Passing exp instead of k can avoid repeated expensive exponentiation here (num / exp) % 10 } /* Counting sort (based on nums k-th digit) */ func countingSortDigit(nums: inout [Int], exp: Int) { // Decimal digit range is 0~9, therefore need a bucket array of length 10 var counter = Array(repeating: 0, count: 10) // Count the occurrence of digits 0~9 for i in nums.indices { let d = digit(num: nums[i], exp: exp) // Get the k-th digit of nums[i], noted as d counter[d] += 1 // Count the occurrence of digit d } // Calculate prefix sum, converting "occurrence count" into "array index" for i in 1 ..< 10 { counter[i] += counter[i - 1] } // Traverse in reverse, based on bucket statistics, place each element into res var res = Array(repeating: 0, count: nums.count) for i in nums.indices.reversed() { let d = digit(num: nums[i], exp: exp) let j = counter[d] - 1 // Get the index j for d in the array res[j] = nums[i] // Place the current element at index j counter[d] -= 1 // Decrease the count of d by 1 } // Use result to overwrite the original array nums for i in nums.indices { nums[i] = res[i] } } /* Radix sort */ func radixSort(nums: inout [Int]) { // Get the maximum element of the array, used to determine the maximum number of digits var m = Int.min for num in nums { if num > m { m = num } } // Traverse from the lowest to the highest digit for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) { // Perform counting sort on the k-th digit of array elements // k = 1 -> exp = 1 // k = 2 -> exp = 10 // i.e., exp = 10^(k-1) countingSortDigit(nums: &nums, exp: exp) } } @main enum RadixSort { /* Driver Code */ static func main() { // Radix sort var nums = [ 10_546_151, 35_663_510, 42_865_989, 34_862_445, 81_883_077, 88_906_420, 72_429_244, 30_524_779, 82_060_337, 63_832_996, ] radixSort(nums: &nums) print("After radix sort, nums = \(nums)") } } ================================================ FILE: en/codes/swift/chapter_sorting/selection_sort.swift ================================================ /** * File: selection_sort.swift * Created Time: 2023-05-28 * Author: nuomi1 (nuomi1@qq.com) */ /* Selection sort */ func selectionSort(nums: inout [Int]) { // Outer loop: unsorted interval is [i, n-1] for i in nums.indices.dropLast() { // Inner loop: find the smallest element within the unsorted interval var k = i for j in nums.indices.dropFirst(i + 1) { if nums[j] < nums[k] { k = j // Record the index of the smallest element } } // Swap the smallest element with the first element of the unsorted interval nums.swapAt(i, k) } } @main enum SelectionSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] selectionSort(nums: &nums) print("After selection sort, nums = \(nums)") } } ================================================ FILE: en/codes/swift/chapter_stack_and_queue/array_deque.swift ================================================ /** * File: array_deque.swift * Created Time: 2023-02-22 * Author: nuomi1 (nuomi1@qq.com) */ /* Double-ended queue based on circular array implementation */ class ArrayDeque { private var nums: [Int] // Array for storing double-ended queue elements private var front: Int // Front pointer, points to the front of the queue element private var _size: Int // Double-ended queue length /* Constructor */ init(capacity: Int) { nums = Array(repeating: 0, count: capacity) front = 0 _size = 0 } /* Get the capacity of the double-ended queue */ func capacity() -> Int { nums.count } /* Get the length of the double-ended queue */ func size() -> Int { _size } /* Check if the double-ended queue is empty */ func isEmpty() -> Bool { size() == 0 } /* Calculate circular array index */ private func index(i: Int) -> Int { // Use modulo operation to wrap the array head and tail together // When i passes the tail of the array, return to the head // When i passes the head of the array, return to the tail (i + capacity()) % capacity() } /* Front of the queue enqueue */ func pushFirst(num: Int) { if size() == capacity() { print("Double-ended queue is full") return } // Use modulo operation to wrap front around to the tail after passing the head of the array // Add num to the front of the queue front = index(i: front - 1) // Add num to front of queue nums[front] = num _size += 1 } /* Rear of the queue enqueue */ func pushLast(num: Int) { if size() == capacity() { print("Double-ended queue is full") return } // Use modulo operation to wrap rear around to the head after passing the tail of the array let rear = index(i: front + size()) // Front pointer moves one position backward nums[rear] = num _size += 1 } /* Rear of the queue dequeue */ func popFirst() -> Int { let num = peekFirst() // Move front pointer backward by one position front = index(i: front + 1) _size -= 1 return num } /* Access rear of the queue element */ func popLast() -> Int { let num = peekLast() _size -= 1 return num } /* Return list for printing */ func peekFirst() -> Int { if isEmpty() { fatalError("Deque is empty") } return nums[front] } /* Driver Code */ func peekLast() -> Int { if isEmpty() { fatalError("Deque is empty") } // Initialize double-ended queue let last = index(i: front + size() - 1) return nums[last] } /* Return array for printing */ func toArray() -> [Int] { // Elements enqueue (front ..< front + size()).map { nums[index(i: $0)] } } } @main enum _ArrayDeque { /* Driver Code */ static func main() { /* Get the length of the double-ended queue */ let deque = ArrayDeque(capacity: 10) deque.pushLast(num: 3) deque.pushLast(num: 2) deque.pushLast(num: 5) print("Deque deque = \(deque.toArray())") /* Update element */ let peekFirst = deque.peekFirst() print("Front element peekFirst = \(peekFirst)") let peekLast = deque.peekLast() print("Rear element peekLast = \(peekLast)") /* Elements enqueue */ deque.pushLast(num: 4) print("After element 4 enqueues at rear, deque = \(deque.toArray())") deque.pushFirst(num: 1) print("After element 1 enqueues at front, deque = \(deque.toArray())") /* Element dequeue */ let popLast = deque.popLast() print("Dequeue rear element = \(popLast), after rear dequeue deque = \(deque.toArray())") let popFirst = deque.popFirst() print("Dequeue front element = \(popFirst), after front dequeue deque = \(deque.toArray())") /* Get the length of the double-ended queue */ let size = deque.size() print("Deque length size = \(size)") /* Check if the double-ended queue is empty */ let isEmpty = deque.isEmpty() print("Is deque empty = \(isEmpty)") } } ================================================ FILE: en/codes/swift/chapter_stack_and_queue/array_queue.swift ================================================ /** * File: array_queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ /* Queue based on circular array implementation */ class ArrayQueue { private var nums: [Int] // Array for storing queue elements private var front: Int // Front pointer, points to the front of the queue element private var _size: Int // Queue length init(capacity: Int) { // Initialize array nums = Array(repeating: 0, count: capacity) front = 0 _size = 0 } /* Get the capacity of the queue */ func capacity() -> Int { nums.count } /* Get the length of the queue */ func size() -> Int { _size } /* Check if the queue is empty */ func isEmpty() -> Bool { size() == 0 } /* Enqueue */ func push(num: Int) { if size() == capacity() { print("Queue is full") return } // Use modulo operation to wrap rear around to the head after passing the tail of the array // Add num to the rear of the queue let rear = (front + size()) % capacity() // Front pointer moves one position backward nums[rear] = num _size += 1 } /* Dequeue */ @discardableResult func pop() -> Int { let num = peek() // Move front pointer backward by one position, if it passes the tail, return to array head front = (front + 1) % capacity() _size -= 1 return num } /* Return list for printing */ func peek() -> Int { if isEmpty() { fatalError("Queue is empty") } return nums[front] } /* Return array */ func toArray() -> [Int] { // Elements enqueue (front ..< front + size()).map { nums[$0 % capacity()] } } } @main enum _ArrayQueue { /* Driver Code */ static func main() { /* Access front of the queue element */ let capacity = 10 let queue = ArrayQueue(capacity: capacity) /* Elements enqueue */ queue.push(num: 1) queue.push(num: 3) queue.push(num: 2) queue.push(num: 5) queue.push(num: 4) print("Queue queue = \(queue.toArray())") /* Return list for printing */ let peek = queue.peek() print("Front element peek = \(peek)") /* Element dequeue */ let pop = queue.pop() print("Dequeue element pop = \(pop), after dequeue queue = \(queue.toArray())") /* Get the length of the queue */ let size = queue.size() print("Queue length size = \(size)") /* Check if the queue is empty */ let isEmpty = queue.isEmpty() print("Is queue empty = \(isEmpty)") /* Test circular array */ for i in 0 ..< 10 { queue.push(num: i) queue.pop() print("After round \(i) enqueue + dequeue, queue = \(queue.toArray())") } } } ================================================ FILE: en/codes/swift/chapter_stack_and_queue/array_stack.swift ================================================ /** * File: array_stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ /* Stack based on array implementation */ class ArrayStack { private var stack: [Int] init() { // Initialize list (dynamic array) stack = [] } /* Get the length of the stack */ func size() -> Int { stack.count } /* Check if the stack is empty */ func isEmpty() -> Bool { stack.isEmpty } /* Push */ func push(num: Int) { stack.append(num) } /* Pop */ @discardableResult func pop() -> Int { if isEmpty() { fatalError("Stack is empty") } return stack.removeLast() } /* Return list for printing */ func peek() -> Int { if isEmpty() { fatalError("Stack is empty") } return stack.last! } /* Convert List to Array and return */ func toArray() -> [Int] { stack } } @main enum _ArrayStack { /* Driver Code */ static func main() { /* Access top of the stack element */ let stack = ArrayStack() /* Elements push onto stack */ stack.push(num: 1) stack.push(num: 3) stack.push(num: 2) stack.push(num: 5) stack.push(num: 4) print("Stack stack = \(stack.toArray())") /* Return list for printing */ let peek = stack.peek() print("Top element peek = \(peek)") /* Element pop from stack */ let pop = stack.pop() print("Pop element pop = \(pop), after pop stack = \(stack.toArray())") /* Get the length of the stack */ let size = stack.size() print("Stack length size = \(size)") /* Check if empty */ let isEmpty = stack.isEmpty() print("Is stack empty = \(isEmpty)") } } ================================================ FILE: en/codes/swift/chapter_stack_and_queue/deque.swift ================================================ /** * File: deque.swift * Created Time: 2023-01-14 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Deque { /* Driver Code */ static func main() { /* Get the length of the double-ended queue */ // Swift has no built-in deque class, can use Array as deque var deque: [Int] = [] /* Elements enqueue */ deque.append(2) deque.append(5) deque.append(4) deque.insert(3, at: 0) deque.insert(1, at: 0) print("Deque deque = \(deque)") /* Update element */ let peekFirst = deque.first! print("Front element peekFirst = \(peekFirst)") let peekLast = deque.last! print("Rear element peekLast = \(peekLast)") /* Element dequeue */ // When simulating with Array, popFirst complexity is O(n) let popFirst = deque.removeFirst() print("Dequeue front element popFirst = \(popFirst), after front dequeue deque = \(deque)") let popLast = deque.removeLast() print("Dequeue rear element popLast = \(popLast), after rear dequeue deque = \(deque)") /* Get the length of the double-ended queue */ let size = deque.count print("Deque length size = \(size)") /* Check if the double-ended queue is empty */ let isEmpty = deque.isEmpty print("Is deque empty = \(isEmpty)") } } ================================================ FILE: en/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift ================================================ /** * File: linkedlist_deque.swift * Created Time: 2023-02-22 * Author: nuomi1 (nuomi1@qq.com) */ /* Doubly linked list node */ class ListNode { var val: Int // Node value var next: ListNode? // Successor node reference weak var prev: ListNode? // Predecessor node reference init(val: Int) { self.val = val } } /* Double-ended queue based on doubly linked list implementation */ class LinkedListDeque { private var front: ListNode? // Head node front private var rear: ListNode? // Tail node rear private var _size: Int // Length of the double-ended queue init() { _size = 0 } /* Get the length of the double-ended queue */ func size() -> Int { _size } /* Check if the double-ended queue is empty */ func isEmpty() -> Bool { size() == 0 } /* Enqueue operation */ private func push(num: Int, isFront: Bool) { let node = ListNode(val: num) // If the linked list is empty, make both front and rear point to node if isEmpty() { front = node rear = node } // Front of the queue enqueue operation else if isFront { // Add node to the head of the linked list front?.prev = node node.next = front front = node // Update head node } // Rear of the queue enqueue operation else { // Add node to the tail of the linked list rear?.next = node node.prev = rear rear = node // Update tail node } _size += 1 // Update queue length } /* Front of the queue enqueue */ func pushFirst(num: Int) { push(num: num, isFront: true) } /* Rear of the queue enqueue */ func pushLast(num: Int) { push(num: num, isFront: false) } /* Dequeue operation */ private func pop(isFront: Bool) -> Int { if isEmpty() { fatalError("Deque is empty") } let val: Int // Temporarily store head node value if isFront { val = front!.val // Delete head node // Delete head node let fNext = front?.next if fNext != nil { fNext?.prev = nil front?.next = nil } front = fNext // Update head node } // Temporarily store tail node value else { val = rear!.val // Delete tail node // Update tail node let rPrev = rear?.prev if rPrev != nil { rPrev?.next = nil rear?.prev = nil } rear = rPrev // Update tail node } _size -= 1 // Update queue length return val } /* Rear of the queue dequeue */ func popFirst() -> Int { pop(isFront: true) } /* Access rear of the queue element */ func popLast() -> Int { pop(isFront: false) } /* Return list for printing */ func peekFirst() -> Int { if isEmpty() { fatalError("Deque is empty") } return front!.val } /* Driver Code */ func peekLast() -> Int { if isEmpty() { fatalError("Deque is empty") } return rear!.val } /* Return array for printing */ func toArray() -> [Int] { var node = front var res = Array(repeating: 0, count: size()) for i in res.indices { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListDeque { /* Driver Code */ static func main() { /* Get the length of the double-ended queue */ let deque = LinkedListDeque() deque.pushLast(num: 3) deque.pushLast(num: 2) deque.pushLast(num: 5) print("Deque deque = \(deque.toArray())") /* Update element */ let peekFirst = deque.peekFirst() print("Front element peekFirst = \(peekFirst)") let peekLast = deque.peekLast() print("Rear element peekLast = \(peekLast)") /* Elements enqueue */ deque.pushLast(num: 4) print("After element 4 enqueues at rear, deque = \(deque.toArray())") deque.pushFirst(num: 1) print("After element 1 enqueues at front, deque = \(deque.toArray())") /* Element dequeue */ let popLast = deque.popLast() print("Dequeue rear element = \(popLast), after rear dequeue deque = \(deque.toArray())") let popFirst = deque.popFirst() print("Dequeue front element = \(popFirst), after front dequeue deque = \(deque.toArray())") /* Get the length of the double-ended queue */ let size = deque.size() print("Deque length size = \(size)") /* Check if the double-ended queue is empty */ let isEmpty = deque.isEmpty() print("Is deque empty = \(isEmpty)") } } ================================================ FILE: en/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift ================================================ /** * File: linkedlist_queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Queue based on linked list implementation */ class LinkedListQueue { private var front: ListNode? // Head node private var rear: ListNode? // Tail node private var _size: Int init() { _size = 0 } /* Get the length of the queue */ func size() -> Int { _size } /* Check if the queue is empty */ func isEmpty() -> Bool { size() == 0 } /* Enqueue */ func push(num: Int) { // Add num after the tail node let node = ListNode(x: num) // If the queue is empty, make both front and rear point to the node if front == nil { front = node rear = node } // If the queue is not empty, add the node after the tail node else { rear?.next = node rear = node } _size += 1 } /* Dequeue */ @discardableResult func pop() -> Int { let num = peek() // Delete head node front = front?.next _size -= 1 return num } /* Return list for printing */ func peek() -> Int { if isEmpty() { fatalError("Queue is empty") } return front!.val } /* Convert linked list to Array and return */ func toArray() -> [Int] { var node = front var res = Array(repeating: 0, count: size()) for i in res.indices { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListQueue { /* Driver Code */ static func main() { /* Access front of the queue element */ let queue = LinkedListQueue() /* Elements enqueue */ queue.push(num: 1) queue.push(num: 3) queue.push(num: 2) queue.push(num: 5) queue.push(num: 4) print("Queue queue = \(queue.toArray())") /* Return list for printing */ let peek = queue.peek() print("Front element peek = \(peek)") /* Element dequeue */ let pop = queue.pop() print("Dequeue element pop = \(pop), after dequeue queue = \(queue.toArray())") /* Get the length of the queue */ let size = queue.size() print("Queue length size = \(size)") /* Check if the queue is empty */ let isEmpty = queue.isEmpty() print("Is queue empty = \(isEmpty)") } } ================================================ FILE: en/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift ================================================ /** * File: linkedlist_stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Stack based on linked list implementation */ class LinkedListStack { private var _peek: ListNode? // Use head node as stack top private var _size: Int // Stack length init() { _size = 0 } /* Get the length of the stack */ func size() -> Int { _size } /* Check if the stack is empty */ func isEmpty() -> Bool { size() == 0 } /* Push */ func push(num: Int) { let node = ListNode(x: num) node.next = _peek _peek = node _size += 1 } /* Pop */ @discardableResult func pop() -> Int { let num = peek() _peek = _peek?.next _size -= 1 return num } /* Return list for printing */ func peek() -> Int { if isEmpty() { fatalError("Stack is empty") } return _peek!.val } /* Convert List to Array and return */ func toArray() -> [Int] { var node = _peek var res = Array(repeating: 0, count: size()) for i in res.indices.reversed() { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListStack { /* Driver Code */ static func main() { /* Access top of the stack element */ let stack = LinkedListStack() /* Elements push onto stack */ stack.push(num: 1) stack.push(num: 3) stack.push(num: 2) stack.push(num: 5) stack.push(num: 4) print("Stack stack = \(stack.toArray())") /* Return list for printing */ let peek = stack.peek() print("Top element peek = \(peek)") /* Element pop from stack */ let pop = stack.pop() print("Pop element pop = \(pop), after pop stack = \(stack.toArray())") /* Get the length of the stack */ let size = stack.size() print("Stack length size = \(size)") /* Check if empty */ let isEmpty = stack.isEmpty() print("Is stack empty = \(isEmpty)") } } ================================================ FILE: en/codes/swift/chapter_stack_and_queue/queue.swift ================================================ /** * File: queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Queue { /* Driver Code */ static func main() { /* Access front of the queue element */ // Swift has no built-in queue class, can use Array as queue var queue: [Int] = [] /* Elements enqueue */ queue.append(1) queue.append(3) queue.append(2) queue.append(5) queue.append(4) print("Queue queue = \(queue)") /* Return list for printing */ let peek = queue.first! print("Front element peek = \(peek)") /* Element dequeue */ // When simulating with Array, pop complexity is O(n) let pool = queue.removeFirst() print("Dequeue element pop = \(pool), after dequeue queue = \(queue)") /* Get the length of the queue */ let size = queue.count print("Queue length size = \(size)") /* Check if the queue is empty */ let isEmpty = queue.isEmpty print("Is queue empty = \(isEmpty)") } } ================================================ FILE: en/codes/swift/chapter_stack_and_queue/stack.swift ================================================ /** * File: stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Stack { /* Driver Code */ static func main() { /* Access top of the stack element */ // Swift has no built-in stack class, can use Array as stack var stack: [Int] = [] /* Elements push onto stack */ stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) print("Stack stack = \(stack)") /* Return list for printing */ let peek = stack.last! print("Top element peek = \(peek)") /* Element pop from stack */ let pop = stack.removeLast() print("Pop element pop = \(pop), after pop stack = \(stack)") /* Get the length of the stack */ let size = stack.count print("Stack length size = \(size)") /* Check if empty */ let isEmpty = stack.isEmpty print("Is stack empty = \(isEmpty)") } } ================================================ FILE: en/codes/swift/chapter_tree/array_binary_tree.swift ================================================ /** * File: array_binary_tree.swift * Created Time: 2023-07-23 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Binary tree class represented by array */ class ArrayBinaryTree { private var tree: [Int?] /* Constructor */ init(arr: [Int?]) { tree = arr } /* List capacity */ func size() -> Int { tree.count } /* Get value of node at index i */ func val(i: Int) -> Int? { // If index out of bounds, return null to represent empty position if i < 0 || i >= size() { return nil } return tree[i] } /* Get index of left child node of node at index i */ func left(i: Int) -> Int { 2 * i + 1 } /* Get index of right child node of node at index i */ func right(i: Int) -> Int { 2 * i + 2 } /* Get index of parent node of node at index i */ func parent(i: Int) -> Int { (i - 1) / 2 } /* Level-order traversal */ func levelOrder() -> [Int] { var res: [Int] = [] // Traverse array directly for i in 0 ..< size() { if let val = val(i: i) { res.append(val) } } return res } /* Depth-first traversal */ private func dfs(i: Int, order: String, res: inout [Int]) { // If empty position, return guard let val = val(i: i) else { return } // Preorder traversal if order == "pre" { res.append(val) } dfs(i: left(i: i), order: order, res: &res) // Inorder traversal if order == "in" { res.append(val) } dfs(i: right(i: i), order: order, res: &res) // Postorder traversal if order == "post" { res.append(val) } } /* Preorder traversal */ func preOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "pre", res: &res) return res } /* Inorder traversal */ func inOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "in", res: &res) return res } /* Postorder traversal */ func postOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "post", res: &res) return res } } @main enum _ArrayBinaryTree { /* Driver Code */ static func main() { // Initialize binary tree // Here we use a function to generate a binary tree directly from an array let arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] let root = TreeNode.listToTree(arr: arr) print("\nInitialize binary tree\n") print("Array representation of binary tree:") print(arr) print("Linked list representation of binary tree:") PrintUtil.printTree(root: root) // Binary tree class represented by array let abt = ArrayBinaryTree(arr: arr) // Access node let i = 1 let l = abt.left(i: i) let r = abt.right(i: i) let p = abt.parent(i: i) print("\nCurrent node index is \(i), value is \(abt.val(i: i) as Any)") print("Its left child index is \(l), value is \(abt.val(i: l) as Any)") print("Its right child index is \(r), value is \(abt.val(i: r) as Any)") print("Its parent node index is \(p), value is \(abt.val(i: p) as Any)") // Traverse tree var res = abt.levelOrder() print("\nLevel-order traversal is: \(res)") res = abt.preOrder() print("Pre-order traversal is: \(res)") res = abt.inOrder() print("In-order traversal is: \(res)") res = abt.postOrder() print("Post-order traversal is: \(res)") } } ================================================ FILE: en/codes/swift/chapter_tree/avl_tree.swift ================================================ /** * File: avl_tree.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* AVL tree */ class AVLTree { fileprivate var root: TreeNode? // Root node init() {} /* Get node height */ func height(node: TreeNode?) -> Int { // Empty node height is -1, leaf node height is 0 node?.height ?? -1 } /* Update node height */ private func updateHeight(node: TreeNode?) { // Node height equals the height of the tallest subtree + 1 node?.height = max(height(node: node?.left), height(node: node?.right)) + 1 } /* Get balance factor */ func balanceFactor(node: TreeNode?) -> Int { // Empty node balance factor is 0 guard let node = node else { return 0 } // Node balance factor = left subtree height - right subtree height return height(node: node.left) - height(node: node.right) } /* Right rotation operation */ private func rightRotate(node: TreeNode?) -> TreeNode? { let child = node?.left let grandChild = child?.right // Using child as pivot, rotate node to the right child?.right = node node?.left = grandChild // Update node height updateHeight(node: node) updateHeight(node: child) // Return root node of subtree after rotation return child } /* Left rotation operation */ private func leftRotate(node: TreeNode?) -> TreeNode? { let child = node?.right let grandChild = child?.left // Using child as pivot, rotate node to the left child?.left = node node?.right = grandChild // Update node height updateHeight(node: node) updateHeight(node: child) // Return root node of subtree after rotation return child } /* Perform rotation operation to restore balance to this subtree */ private func rotate(node: TreeNode?) -> TreeNode? { // Get balance factor of node let balanceFactor = balanceFactor(node: node) // Left-leaning tree if balanceFactor > 1 { if self.balanceFactor(node: node?.left) >= 0 { // Right rotation return rightRotate(node: node) } else { // First left rotation then right rotation node?.left = leftRotate(node: node?.left) return rightRotate(node: node) } } // Right-leaning tree if balanceFactor < -1 { if self.balanceFactor(node: node?.right) <= 0 { // Left rotation return leftRotate(node: node) } else { // First right rotation then left rotation node?.right = rightRotate(node: node?.right) return leftRotate(node: node) } } // Balanced tree, no rotation needed, return directly return node } /* Insert node */ func insert(val: Int) { root = insertHelper(node: root, val: val) } /* Recursively insert node (helper method) */ private func insertHelper(node: TreeNode?, val: Int) -> TreeNode? { var node = node if node == nil { return TreeNode(x: val) } /* 1. Find insertion position and insert node */ if val < node!.val { node?.left = insertHelper(node: node?.left, val: val) } else if val > node!.val { node?.right = insertHelper(node: node?.right, val: val) } else { return node // Duplicate node not inserted, return directly } updateHeight(node: node) // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = rotate(node: node) // Return root node of subtree return node } /* Remove node */ func remove(val: Int) { root = removeHelper(node: root, val: val) } /* Recursively delete node (helper method) */ private func removeHelper(node: TreeNode?, val: Int) -> TreeNode? { var node = node if node == nil { return nil } /* 1. Find node and delete */ if val < node!.val { node?.left = removeHelper(node: node?.left, val: val) } else if val > node!.val { node?.right = removeHelper(node: node?.right, val: val) } else { if node?.left == nil || node?.right == nil { let child = node?.left ?? node?.right // Number of child nodes = 0, delete node directly and return if child == nil { return nil } // Number of child nodes = 1, delete node directly else { node = child } } else { // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it var temp = node?.right while temp?.left != nil { temp = temp?.left } node?.right = removeHelper(node: node?.right, val: temp!.val) node?.val = temp!.val } } updateHeight(node: node) // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = rotate(node: node) // Return root node of subtree return node } /* Search node */ func search(val: Int) -> TreeNode? { var cur = root while cur != nil { // Target node is in cur's right subtree if cur!.val < val { cur = cur?.right } // Target node is in cur's left subtree else if cur!.val > val { cur = cur?.left } // Found target node, exit loop else { break } } // Return target node return cur } } @main enum _AVLTree { static func testInsert(tree: AVLTree, val: Int) { tree.insert(val: val) print("\nAfter inserting node \(val), AVL tree is") PrintUtil.printTree(root: tree.root) } static func testRemove(tree: AVLTree, val: Int) { tree.remove(val: val) print("\nAfter deleting node \(val), AVL tree is") PrintUtil.printTree(root: tree.root) } /* Driver Code */ static func main() { /* Please pay attention to how the AVL tree maintains balance after inserting nodes */ let avlTree = AVLTree() /* Insert node */ // Delete nodes testInsert(tree: avlTree, val: 1) testInsert(tree: avlTree, val: 2) testInsert(tree: avlTree, val: 3) testInsert(tree: avlTree, val: 4) testInsert(tree: avlTree, val: 5) testInsert(tree: avlTree, val: 8) testInsert(tree: avlTree, val: 7) testInsert(tree: avlTree, val: 9) testInsert(tree: avlTree, val: 10) testInsert(tree: avlTree, val: 6) /* Please pay attention to how the AVL tree maintains balance after deleting nodes */ testInsert(tree: avlTree, val: 7) /* Remove node */ // Delete node with degree 1 testRemove(tree: avlTree, val: 8) // Delete node with degree 2 testRemove(tree: avlTree, val: 5) // Remove node with degree 1 testRemove(tree: avlTree, val: 4) // Remove node with degree 2 /* Search node */ let node = avlTree.search(val: 7) print("\nFound node object is \(node!), node value = \(node!.val)") } } ================================================ FILE: en/codes/swift/chapter_tree/binary_search_tree.swift ================================================ /** * File: binary_search_tree.swift * Created Time: 2023-01-26 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Binary search tree */ class BinarySearchTree { private var root: TreeNode? /* Constructor */ init() { // Initialize empty tree root = nil } /* Get binary tree root node */ func getRoot() -> TreeNode? { root } /* Search node */ func search(num: Int) -> TreeNode? { var cur = root // Loop search, exit after passing leaf node while cur != nil { // Target node is in cur's right subtree if cur!.val < num { cur = cur?.right } // Target node is in cur's left subtree else if cur!.val > num { cur = cur?.left } // Found target node, exit loop else { break } } // Return target node return cur } /* Insert node */ func insert(num: Int) { // If tree is empty, initialize root node if root == nil { root = TreeNode(x: num) return } var cur = root var pre: TreeNode? // Loop search, exit after passing leaf node while cur != nil { // Found duplicate node, return directly if cur!.val == num { return } pre = cur // Insertion position is in cur's right subtree if cur!.val < num { cur = cur?.right } // Insertion position is in cur's left subtree else { cur = cur?.left } } // Insert node let node = TreeNode(x: num) if pre!.val < num { pre?.right = node } else { pre?.left = node } } /* Remove node */ func remove(num: Int) { // If tree is empty, return directly if root == nil { return } var cur = root var pre: TreeNode? // Loop search, exit after passing leaf node while cur != nil { // Found node to delete, exit loop if cur!.val == num { break } pre = cur // Node to delete is in cur's right subtree if cur!.val < num { cur = cur?.right } // Node to delete is in cur's left subtree else { cur = cur?.left } } // If no node to delete, return directly if cur == nil { return } // Number of child nodes = 0 or 1 if cur?.left == nil || cur?.right == nil { // When number of child nodes = 0 / 1, child = null / that child node let child = cur?.left ?? cur?.right // Delete node cur if cur !== root { if pre?.left === cur { pre?.left = child } else { pre?.right = child } } else { // If deleted node is root node, reassign root node root = child } } // Number of child nodes = 2 else { // Get next node of cur in inorder traversal var tmp = cur?.right while tmp?.left != nil { tmp = tmp?.left } // Recursively delete node tmp remove(num: tmp!.val) // Replace cur with tmp cur?.val = tmp!.val } } } @main enum _BinarySearchTree { /* Driver Code */ static func main() { /* Initialize binary search tree */ let bst = BinarySearchTree() // Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] for num in nums { bst.insert(num: num) } print("\nInitialized binary tree is\n") PrintUtil.printTree(root: bst.getRoot()) /* Search node */ let node = bst.search(num: 7) print("\nFound node object is \(node!), node value = \(node!.val)") /* Insert node */ bst.insert(num: 16) print("\nAfter inserting node 16, binary tree is\n") PrintUtil.printTree(root: bst.getRoot()) /* Remove node */ bst.remove(num: 1) print("\nAfter removing node 1, binary tree is\n") PrintUtil.printTree(root: bst.getRoot()) bst.remove(num: 2) print("\nAfter removing node 2, binary tree is\n") PrintUtil.printTree(root: bst.getRoot()) bst.remove(num: 4) print("\nAfter removing node 4, binary tree is\n") PrintUtil.printTree(root: bst.getRoot()) } } ================================================ FILE: en/codes/swift/chapter_tree/binary_tree.swift ================================================ /** * File: binary_tree.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum BinaryTree { /* Driver Code */ static func main() { /* Initialize binary tree */ // Initialize nodes let n1 = TreeNode(x: 1) let n2 = TreeNode(x: 2) let n3 = TreeNode(x: 3) let n4 = TreeNode(x: 4) let n5 = TreeNode(x: 5) // Build references (pointers) between nodes n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 print("\nInitialize binary tree\n") PrintUtil.printTree(root: n1) /* Insert node P between n1 -> n2 */ let P = TreeNode(x: 0) // Delete node n1.left = P P.left = n2 print("\nAfter inserting node P\n") PrintUtil.printTree(root: n1) // Remove node P n1.left = n2 print("\nAfter removing node P\n") PrintUtil.printTree(root: n1) } } ================================================ FILE: en/codes/swift/chapter_tree/binary_tree_bfs.swift ================================================ /** * File: binary_tree_bfs.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Level-order traversal */ func levelOrder(root: TreeNode) -> [Int] { // Initialize queue, add root node var queue: [TreeNode] = [root] // Initialize a list to save the traversal sequence var list: [Int] = [] while !queue.isEmpty { let node = queue.removeFirst() // Dequeue list.append(node.val) // Save node value if let left = node.left { queue.append(left) // Left child node enqueue } if let right = node.right { queue.append(right) // Right child node enqueue } } return list } @main enum BinaryTreeBFS { /* Driver Code */ static func main() { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array let node = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! print("\nInitialize binary tree\n") PrintUtil.printTree(root: node) /* Level-order traversal */ let list = levelOrder(root: node) print("\nLevel-order traversal node print sequence = \(list)") } } ================================================ FILE: en/codes/swift/chapter_tree/binary_tree_dfs.swift ================================================ /** * File: binary_tree_dfs.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils // Initialize list for storing traversal sequence var list: [Int] = [] /* Preorder traversal */ func preOrder(root: TreeNode?) { guard let root = root else { return } // Visit priority: root node -> left subtree -> right subtree list.append(root.val) preOrder(root: root.left) preOrder(root: root.right) } /* Inorder traversal */ func inOrder(root: TreeNode?) { guard let root = root else { return } // Visit priority: left subtree -> root node -> right subtree inOrder(root: root.left) list.append(root.val) inOrder(root: root.right) } /* Postorder traversal */ func postOrder(root: TreeNode?) { guard let root = root else { return } // Visit priority: left subtree -> right subtree -> root node postOrder(root: root.left) postOrder(root: root.right) list.append(root.val) } @main enum BinaryTreeDFS { /* Driver Code */ static func main() { /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array let root = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! print("\nInitialize binary tree\n") PrintUtil.printTree(root: root) /* Preorder traversal */ list.removeAll() preOrder(root: root) print("\nPre-order traversal node print sequence = \(list)") /* Inorder traversal */ list.removeAll() inOrder(root: root) print("\nIn-order traversal node print sequence = \(list)") /* Postorder traversal */ list.removeAll() postOrder(root: root) print("\nPost-order traversal node print sequence = \(list)") } } ================================================ FILE: en/codes/swift/utils/ListNode.swift ================================================ /** * File: ListNode.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ public class ListNode: Hashable { public var val: Int // Node value public var next: ListNode? // Successor node reference public init(x: Int) { val = x } public static func == (lhs: ListNode, rhs: ListNode) -> Bool { lhs.val == rhs.val && lhs.next.map { ObjectIdentifier($0) } == rhs.next.map { ObjectIdentifier($0) } } public func hash(into hasher: inout Hasher) { hasher.combine(val) hasher.combine(next.map { ObjectIdentifier($0) }) } public static func arrToLinkedList(arr: [Int]) -> ListNode? { let dum = ListNode(x: 0) var head: ListNode? = dum for val in arr { head?.next = ListNode(x: val) head = head?.next } return dum.next } } ================================================ FILE: en/codes/swift/utils/Pair.swift ================================================ /** * File: Pair.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ /* Key-value pair */ public class Pair: Equatable { public var key: Int public var val: String public init(key: Int, val: String) { self.key = key self.val = val } public static func == (lhs: Pair, rhs: Pair) -> Bool { lhs.key == rhs.key && lhs.val == rhs.val } } ================================================ FILE: en/codes/swift/utils/PrintUtil.swift ================================================ /** * File: PrintUtil.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ public enum PrintUtil { private class Trunk { var prev: Trunk? var str: String init(prev: Trunk?, str: String) { self.prev = prev self.str = str } } public static func printLinkedList(head: ListNode) { var head: ListNode? = head var list: [String] = [] while head != nil { list.append("\(head!.val)") head = head?.next } print(list.joined(separator: " -> ")) } public static func printTree(root: TreeNode?) { printTree(root: root, prev: nil, isRight: false) } private static func printTree(root: TreeNode?, prev: Trunk?, isRight: Bool) { if root == nil { return } var prevStr = " " let trunk = Trunk(prev: prev, str: prevStr) printTree(root: root?.right, prev: trunk, isRight: true) if prev == nil { trunk.str = "———" } else if isRight { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev?.str = prevStr } showTrunks(p: trunk) print(" \(root!.val)") if prev != nil { prev?.str = prevStr } trunk.str = " |" printTree(root: root?.left, prev: trunk, isRight: false) } private static func showTrunks(p: Trunk?) { if p == nil { return } showTrunks(p: p?.prev) print(p!.str, terminator: "") } public static func printHashMap(map: [K: V]) { for (key, value) in map { print("\(key) -> \(value)") } } public static func printHeap(queue: [Int]) { print("Heap array representation:", terminator: "") print(queue) print("Heap tree representation:") let root = TreeNode.listToTree(arr: queue) printTree(root: root) } public static func printMatrix(matrix: [[T]]) { print("[") for row in matrix { print(" \(row),") } print("]") } } ================================================ FILE: en/codes/swift/utils/TreeNode.swift ================================================ /** * File: TreeNode.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Binary tree node class */ public class TreeNode { public var val: Int // Node value public var height: Int // Node height public var left: TreeNode? // Reference to left child node public var right: TreeNode? // Reference to right child node /* Constructor */ public init(x: Int) { val = x height = 0 } // For the serialization encoding rules, please refer to: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Array representation of binary tree: // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] // Linked list representation of binary tree: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* Deserialize a list into a binary tree: recursion */ private static func listToTreeDFS(arr: [Int?], i: Int) -> TreeNode? { if i < 0 || i >= arr.count || arr[i] == nil { return nil } let root = TreeNode(x: arr[i]!) root.left = listToTreeDFS(arr: arr, i: 2 * i + 1) root.right = listToTreeDFS(arr: arr, i: 2 * i + 2) return root } /* Deserialize a list into a binary tree */ public static func listToTree(arr: [Int?]) -> TreeNode? { listToTreeDFS(arr: arr, i: 0) } /* Serialize a binary tree into a list: recursion */ private static func treeToListDFS(root: TreeNode?, i: Int, res: inout [Int?]) { if root == nil { return } while i >= res.count { res.append(nil) } res[i] = root?.val treeToListDFS(root: root?.left, i: 2 * i + 1, res: &res) treeToListDFS(root: root?.right, i: 2 * i + 2, res: &res) } /* Serialize a binary tree into a list */ public static func treeToList(root: TreeNode?) -> [Int?] { var res: [Int?] = [] treeToListDFS(root: root, i: 0, res: &res) return res } } ================================================ FILE: en/codes/swift/utils/Vertex.swift ================================================ /** * File: Vertex.swift * Created Time: 2023-02-19 * Author: nuomi1 (nuomi1@qq.com) */ /* Vertex class */ public class Vertex: Hashable { public var val: Int public init(val: Int) { self.val = val } public static func == (lhs: Vertex, rhs: Vertex) -> Bool { lhs.val == rhs.val } public func hash(into hasher: inout Hasher) { hasher.combine(val) } /* Input value list vals, return vertex list vets */ public static func valsToVets(vals: [Int]) -> [Vertex] { vals.map { Vertex(val: $0) } } /* Input vertex list vets, return value list vals */ public static func vetsToVals(vets: [Vertex]) -> [Int] { vets.map { $0.val } } } ================================================ FILE: en/codes/typescript/.gitignore ================================================ node_modules package-lock.json ================================================ FILE: en/codes/typescript/.prettierrc ================================================ { "tabWidth": 4, "useTabs": false, "semi": true, "singleQuote": true } ================================================ FILE: en/codes/typescript/chapter_array_and_linkedlist/array.ts ================================================ /** * File: array.ts * Created Time: 2022-12-04 * Author: Justin (xiefahit@gmail.com) */ /* Random access to element */ function randomAccess(nums: number[]): number { // Randomly select a number in the interval [0, nums.length) const random_index = Math.floor(Math.random() * nums.length); // Retrieve and return the random element const random_num = nums[random_index]; return random_num; } /* Extend array length */ // Note: TypeScript's Array is dynamic array, can be directly expanded // For learning purposes, this function treats Array as fixed-length array function extend(nums: number[], enlarge: number): number[] { // Initialize an array with extended length const res = new Array(nums.length + enlarge).fill(0); // Copy all elements from the original array to the new array for (let i = 0; i < nums.length; i++) { res[i] = nums[i]; } // Return the extended new array return res; } /* Insert element num at index index in the array */ function insert(nums: number[], num: number, index: number): void { // Move all elements at and after index index backward by one position for (let i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Assign num to the element at index index nums[index] = num; } /* Remove the element at index index */ function remove(nums: number[], index: number): void { // Move all elements after index index forward by one position for (let i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* Traverse array */ function traverse(nums: number[]): void { let count = 0; // Traverse array by index for (let i = 0; i < nums.length; i++) { count += nums[i]; } // Direct traversal of array elements for (const num of nums) { count += num; } } /* Find the specified element in the array */ function find(nums: number[], target: number): number { for (let i = 0; i < nums.length; i++) { if (nums[i] === target) { return i; } } return -1; } /* Driver Code */ /* Initialize array */ const arr: number[] = new Array(5).fill(0); console.log('Array arr =', arr); let nums: number[] = [1, 3, 2, 5, 4]; console.log('Array nums =', nums); /* Insert element */ let random_num = randomAccess(nums); console.log('Get random element in nums', random_num); /* Traverse array */ nums = extend(nums, 3); console.log('Extend array length to 8, get nums =', nums); /* Insert element */ insert(nums, 6, 3); console.log('Insert number 6 at index 3, get nums =', nums); /* Remove element */ remove(nums, 2); console.log('Remove element at index 2, get nums =', nums); /* Traverse array */ traverse(nums); /* Find element */ let index = find(nums, 3); console.log('Find element 3 in nums, get index =', index); export {}; ================================================ FILE: en/codes/typescript/chapter_array_and_linkedlist/linked_list.ts ================================================ /** * File: linked_list.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from '../modules/ListNode'; import { printLinkedList } from '../modules/PrintUtil'; /* Insert node P after node n0 in the linked list */ function insert(n0: ListNode, P: ListNode): void { const n1 = n0.next; P.next = n1; n0.next = P; } /* Remove the first node after node n0 in the linked list */ function remove(n0: ListNode): void { if (!n0.next) { return; } // n0 -> P -> n1 const P = n0.next; const n1 = P.next; n0.next = n1; } /* Access the node at index index in the linked list */ function access(head: ListNode | null, index: number): ListNode | null { for (let i = 0; i < index; i++) { if (!head) { return null; } head = head.next; } return head; } /* Find the first node with value target in the linked list */ function find(head: ListNode | null, target: number): number { let index = 0; while (head !== null) { if (head.val === target) { return index; } head = head.next; index += 1; } return -1; } /* Driver Code */ /* Initialize linked list */ // Initialize each node const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // Build references between nodes n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; console.log('Initialized linked list is'); printLinkedList(n0); /* Insert node */ insert(n0, new ListNode(0)); console.log('Linked list after inserting node is'); printLinkedList(n0); /* Remove node */ remove(n0); console.log('Linked list after removing node is'); printLinkedList(n0); /* Access node */ const node = access(n0, 3); console.log(`Value of node at index 3 in linked list = ${node?.val}`); /* Search node */ const index = find(n0, 2); console.log(`Index of node with value 2 in linked list = ${index}`); export {}; ================================================ FILE: en/codes/typescript/chapter_array_and_linkedlist/list.ts ================================================ /** * File: list.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ /* Initialize list */ const nums: number[] = [1, 3, 2, 5, 4]; console.log(`List nums = ${nums}`); /* Update element */ const num: number = nums[1]; console.log(`Access element at index 1, get num = ${num}`); /* Add elements at the end */ nums[1] = 0; console.log(`Update element at index 1 to 0, get nums = ${nums}`); /* Remove element */ nums.length = 0; console.log(`After clearing list, nums = ${nums}`); /* Direct traversal of list elements */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); console.log(`After adding elements, nums = ${nums}`); /* Sort list */ nums.splice(3, 0, 6); console.log(`Insert number 6 at index 3, get nums = ${nums}`); /* Remove element */ nums.splice(3, 1); console.log(`Delete element at index 3, get nums = ${nums}`); /* Traverse list by index */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* Directly traverse list elements */ count = 0; for (const x of nums) { count += x; } /* Concatenate two lists */ const nums1: number[] = [6, 8, 7, 10, 9]; nums.push(...nums1); console.log(`After concatenating list nums1 to nums, get nums = ${nums}`); /* Sort list */ nums.sort((a, b) => a - b); console.log(`After sorting list, nums = ${nums}`); export {}; ================================================ FILE: en/codes/typescript/chapter_array_and_linkedlist/my_list.ts ================================================ /** * File: my_list.ts * Created Time: 2022-12-11 * Author: Justin (xiefahit@gmail.com) */ /* List class */ class MyList { private arr: Array; // Array (stores list elements) private _capacity: number = 10; // List capacity private _size: number = 0; // List length (current number of elements) private extendRatio: number = 2; // Multiple by which the list capacity is extended each time /* Constructor */ constructor() { this.arr = new Array(this._capacity); } /* Get list length (current number of elements) */ public size(): number { return this._size; } /* Get list capacity */ public capacity(): number { return this._capacity; } /* Update element */ public get(index: number): number { // If the index is out of bounds, throw an exception, as below if (index < 0 || index >= this._size) throw new Error('Index out of bounds'); return this.arr[index]; } /* Add elements at the end */ public set(index: number, num: number): void { if (index < 0 || index >= this._size) throw new Error('Index out of bounds'); this.arr[index] = num; } /* Direct traversal of list elements */ public add(num: number): void { // If length equals capacity, need to expand if (this._size === this._capacity) this.extendCapacity(); // Add new element to end of list this.arr[this._size] = num; this._size++; } /* Sort list */ public insert(index: number, num: number): void { if (index < 0 || index >= this._size) throw new Error('Index out of bounds'); // When the number of elements exceeds capacity, trigger the extension mechanism if (this._size === this._capacity) { this.extendCapacity(); } // Move all elements after index index forward by one position for (let j = this._size - 1; j >= index; j--) { this.arr[j + 1] = this.arr[j]; } // Update the number of elements this.arr[index] = num; this._size++; } /* Remove element */ public remove(index: number): number { if (index < 0 || index >= this._size) throw new Error('Index out of bounds'); let num = this.arr[index]; // Move all elements after index forward by one position for (let j = index; j < this._size - 1; j++) { this.arr[j] = this.arr[j + 1]; } // Update the number of elements this._size--; // Return the removed element return num; } /* Driver Code */ public extendCapacity(): void { // Create new array of length size and copy original array to new array this.arr = this.arr.concat( new Array(this.capacity() * (this.extendRatio - 1)) ); // Add elements at the end this._capacity = this.arr.length; } /* Convert list to array */ public toArray(): number[] { let size = this.size(); // Elements enqueue const arr = new Array(size); for (let i = 0; i < size; i++) { arr[i] = this.get(i); } return arr; } } /* Driver Code */ /* Initialize list */ const nums = new MyList(); /* Direct traversal of list elements */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); console.log( `List nums = ${nums.toArray()}, capacity = ${nums.capacity()}, length = ${nums.size()}` ); /* Sort list */ nums.insert(3, 6); console.log(`Insert number 6 at index 3, get nums = ${nums.toArray()}`); /* Remove element */ nums.remove(3); console.log(`Delete element at index 3, get nums = ${nums.toArray()}`); /* Update element */ const num = nums.get(1); console.log(`Access element at index 1, get num = ${num}`); /* Add elements at the end */ nums.set(1, 0); console.log(`Update element at index 1 to 0, get nums = ${nums.toArray()}`); /* Test capacity expansion mechanism */ for (let i = 0; i < 10; i++) { // At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism nums.add(i); } console.log( `After expansion, list nums = ${nums.toArray()}, capacity = ${nums.capacity()}, length = ${nums.size()}` ); export {}; ================================================ FILE: en/codes/typescript/chapter_backtracking/n_queens.ts ================================================ /** * File: n_queens.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* Backtracking algorithm: N queens */ function backtrack( row: number, n: number, state: string[][], res: string[][][], cols: boolean[], diags1: boolean[], diags2: boolean[] ): void { // When all rows are placed, record the solution if (row === n) { res.push(state.map((row) => row.slice())); return; } // Traverse all columns for (let col = 0; col < n; col++) { // Calculate the main diagonal and anti-diagonal corresponding to this cell const diag1 = row - col + n - 1; const diag2 = row + col; // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Attempt: place the queen in this cell state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // Place the next row backtrack(row + 1, n, state, res, cols, diags1, diags2); // Backtrack: restore this cell to an empty cell state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Solve N queens */ function nQueens(n: number): string[][][] { // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell const state = Array.from({ length: n }, () => Array(n).fill('#')); const cols = Array(n).fill(false); // Record whether there is a queen in the column const diags1 = Array(2 * n - 1).fill(false); // Record whether there is a queen on the main diagonal const diags2 = Array(2 * n - 1).fill(false); // Record whether there is a queen on the anti-diagonal const res: string[][][] = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } // Driver Code const n = 4; const res = nQueens(n); console.log(`Input board size is ${n}`); console.log(`Total queen placement solutions: ${res.length}`); res.forEach((state) => { console.log('--------------------'); state.forEach((row) => console.log(row)); }); export {}; ================================================ FILE: en/codes/typescript/chapter_backtracking/permutations_i.ts ================================================ /** * File: permutations_i.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* Backtracking algorithm: Permutations I */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // When the state length equals the number of elements, record the solution if (state.length === choices.length) { res.push([...state]); return; } // Traverse all choices choices.forEach((choice, i) => { // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state.push(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop(); } }); } /* Permutations I */ function permutationsI(nums: number[]): number[][] { const res: number[][] = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums: number[] = [1, 2, 3]; const res: number[][] = permutationsI(nums); console.log(`Input array nums = ${JSON.stringify(nums)}`); console.log(`All permutations res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: en/codes/typescript/chapter_backtracking/permutations_ii.ts ================================================ /** * File: permutations_ii.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* Backtracking algorithm: Permutations II */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // When the state length equals the number of elements, record the solution if (state.length === choices.length) { res.push([...state]); return; } // Traverse all choices const duplicated = new Set(); choices.forEach((choice, i) => { // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated.has(choice)) { // Attempt: make choice, update state duplicated.add(choice); // Record the selected element value selected[i] = true; state.push(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop(); } }); } /* Permutations II */ function permutationsII(nums: number[]): number[][] { const res: number[][] = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums: number[] = [1, 2, 2]; const res: number[][] = permutationsII(nums); console.log(`Input array nums = ${JSON.stringify(nums)}`); console.log(`All permutations res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: en/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts ================================================ /** * File: preorder_traversal_i_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Preorder traversal: Example 1 */ function preOrder(root: TreeNode | null, res: TreeNode[]): void { if (root === null) { return; } if (root.val === 7) { // Record solution res.push(root); } preOrder(root.left, res); preOrder(root.right, res); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nInitialize binary tree'); printTree(root); // Preorder traversal const res: TreeNode[] = []; preOrder(root, res); console.log('\nOutput all nodes with value 7'); console.log(res.map((node) => node.val)); export {}; ================================================ FILE: en/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts ================================================ /** * File: preorder_traversal_ii_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Preorder traversal: Example 2 */ function preOrder( root: TreeNode | null, path: TreeNode[], res: TreeNode[][] ): void { if (root === null) { return; } // Attempt path.push(root); if (root.val === 7) { // Record solution res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // Backtrack path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nInitialize binary tree'); printTree(root); // Preorder traversal const path: TreeNode[] = []; const res: TreeNode[][] = []; preOrder(root, path, res); console.log('\nOutput all paths from root node to node 7'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: en/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts ================================================ /** * File: preorder_traversal_iii_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Preorder traversal: Example 3 */ function preOrder( root: TreeNode | null, path: TreeNode[], res: TreeNode[][] ): void { // Pruning if (root === null || root.val === 3) { return; } // Attempt path.push(root); if (root.val === 7) { // Record solution res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // Backtrack path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nInitialize binary tree'); printTree(root); // Preorder traversal const path: TreeNode[] = []; const res: TreeNode[][] = []; preOrder(root, path, res); console.log('\nOutput all paths from root node to node 7, paths do not include nodes with value 3'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: en/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts ================================================ /** * File: preorder_traversal_iii_template.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Check if the current state is a solution */ function isSolution(state: TreeNode[]): boolean { return state && state[state.length - 1]?.val === 7; } /* Record solution */ function recordSolution(state: TreeNode[], res: TreeNode[][]): void { res.push([...state]); } /* Check if the choice is valid under the current state */ function isValid(state: TreeNode[], choice: TreeNode): boolean { return choice !== null && choice.val !== 3; } /* Update state */ function makeChoice(state: TreeNode[], choice: TreeNode): void { state.push(choice); } /* Restore state */ function undoChoice(state: TreeNode[]): void { state.pop(); } /* Backtracking algorithm: Example 3 */ function backtrack( state: TreeNode[], choices: TreeNode[], res: TreeNode[][] ): void { // Check if it is a solution if (isSolution(state)) { // Record solution recordSolution(state, res); } // Traverse all choices for (const choice of choices) { // Pruning: check if the choice is valid if (isValid(state, choice)) { // Attempt: make choice, update state makeChoice(state, choice); // Proceed to the next round of selection backtrack(state, [choice.left, choice.right], res); // Backtrack: undo choice, restore to previous state undoChoice(state); } } } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nInitialize binary tree'); printTree(root); // Backtracking algorithm const res: TreeNode[][] = []; backtrack([], [root], res); console.log('\nOutput all paths from root node to node 7, requiring paths do not include nodes with value 3'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: en/codes/typescript/chapter_backtracking/subset_sum_i.ts ================================================ /** * File: subset_sum_i.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Backtracking algorithm: Subset sum I */ function backtrack( state: number[], target: number, choices: number[], start: number, res: number[][] ): void { // When the subset sum equals target, record the solution if (target === 0) { res.push([...state]); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets for (let i = start; i < choices.length; i++) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if (target - choices[i] < 0) { break; } // Attempt: make choice, update target, start state.push(choices[i]); // Proceed to the next round of selection backtrack(state, target - choices[i], choices, i, res); // Backtrack: undo choice, restore to previous state state.pop(); } } /* Solve subset sum I */ function subsetSumI(nums: number[], target: number): number[][] { const state = []; // State (subset) nums.sort((a, b) => a - b); // Sort nums const start = 0; // Start point for traversal const res = []; // Result list (subset list) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumI(nums, target); console.log(`Input array nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`All subsets with sum equal to ${target} res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: en/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts ================================================ /** * File: subset_sum_i_naive.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Backtracking algorithm: Subset sum I */ function backtrack( state: number[], target: number, total: number, choices: number[], res: number[][] ): void { // When the subset sum equals target, record the solution if (total === target) { res.push([...state]); return; } // Traverse all choices for (let i = 0; i < choices.length; i++) { // Pruning: if the subset sum exceeds target, skip this choice if (total + choices[i] > target) { continue; } // Attempt: make choice, update element sum total state.push(choices[i]); // Proceed to the next round of selection backtrack(state, target, total + choices[i], choices, res); // Backtrack: undo choice, restore to previous state state.pop(); } } /* Solve subset sum I (including duplicate subsets) */ function subsetSumINaive(nums: number[], target: number): number[][] { const state = []; // State (subset) const total = 0; // Subset sum const res = []; // Result list (subset list) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumINaive(nums, target); console.log(`Input array nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`All subsets with sum equal to ${target} res = ${JSON.stringify(res)}`); console.log('Please note that this method outputs results containing duplicate sets'); export {}; ================================================ FILE: en/codes/typescript/chapter_backtracking/subset_sum_ii.ts ================================================ /** * File: subset_sum_ii.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Backtracking algorithm: Subset sum II */ function backtrack( state: number[], target: number, choices: number[], start: number, res: number[][] ): void { // When the subset sum equals target, record the solution if (target === 0) { res.push([...state]); return; } // Traverse all choices // Pruning 2: start traversing from start to avoid generating duplicate subsets // Pruning 3: start traversing from start to avoid repeatedly selecting the same element for (let i = start; i < choices.length; i++) { // Pruning 1: if the subset sum exceeds target, end the loop directly // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target if (target - choices[i] < 0) { break; } // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly if (i > start && choices[i] === choices[i - 1]) { continue; } // Attempt: make choice, update target, start state.push(choices[i]); // Proceed to the next round of selection backtrack(state, target - choices[i], choices, i + 1, res); // Backtrack: undo choice, restore to previous state state.pop(); } } /* Solve subset sum II */ function subsetSumII(nums: number[], target: number): number[][] { const state = []; // State (subset) nums.sort((a, b) => a - b); // Sort nums const start = 0; // Start point for traversal const res = []; // Result list (subset list) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [4, 4, 5]; const target = 9; const res = subsetSumII(nums, target); console.log(`Input array nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`All subsets with sum equal to ${target} res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: en/codes/typescript/chapter_computational_complexity/iteration.ts ================================================ /** * File: iteration.ts * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* for loop */ function forLoop(n: number): number { let res = 0; // Sum 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { res += i; } return res; } /* while loop */ function whileLoop(n: number): number { let res = 0; let i = 1; // Initialize condition variable // Sum 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Update condition variable } return res; } /* while loop (two updates) */ function whileLoopII(n: number): number { let res = 0; let i = 1; // Initialize condition variable // Sum 1, 4, 10, ... while (i <= n) { res += i; // Update condition variable i++; i *= 2; } return res; } /* Nested for loop */ function nestedForLoop(n: number): string { let res = ''; // Loop i = 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { // Loop j = 1, 2, ..., n-1, n for (let j = 1; j <= n; j++) { res += `(${i}, ${j}), `; } } return res; } /* Driver Code */ const n = 5; let res: number; res = forLoop(n); console.log(`For loop sum result res = ${res}`); res = whileLoop(n); console.log(`While loop sum result res = ${res}`); res = whileLoopII(n); console.log(`While loop (two updates) sum result res = ${res}`); const resStr = nestedForLoop(n); console.log(`Nested for loop traversal result ${resStr}`); export {}; ================================================ FILE: en/codes/typescript/chapter_computational_complexity/recursion.ts ================================================ /** * File: recursion.ts * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Recursion */ function recur(n: number): number { // Termination condition if (n === 1) return 1; // Recurse: recursive call const res = recur(n - 1); // Return: return result return n + res; } /* Simulate recursion using iteration */ function forLoopRecur(n: number): number { // Use an explicit stack to simulate the system call stack const stack: number[] = []; let res: number = 0; // Recurse: recursive call for (let i = n; i > 0; i--) { // Simulate "recurse" with "push" stack.push(i); } // Return: return result while (stack.length) { // Simulate "return" with "pop" res += stack.pop(); } // res = 1+2+3+...+n return res; } /* Tail recursion */ function tailRecur(n: number, res: number): number { // Termination condition if (n === 0) return res; // Tail recursive call return tailRecur(n - 1, res + n); } /* Fibonacci sequence: recursion */ function fib(n: number): number { // Termination condition f(1) = 0, f(2) = 1 if (n === 1 || n === 2) return n - 1; // Recursive call f(n) = f(n-1) + f(n-2) const res = fib(n - 1) + fib(n - 2); // Return result f(n) return res; } /* Driver Code */ const n = 5; let res: number; res = recur(n); console.log(`Recursion sum result res = ${res}`); res = forLoopRecur(n); console.log(`Using iteration to simulate recursion sum result res = ${res}`); res = tailRecur(n, 0); console.log(`Tail recursion sum result res = ${res}`); res = fib(n); console.log(`The ${n}th Fibonacci number is ${res}`); export {}; ================================================ FILE: en/codes/typescript/chapter_computational_complexity/space_complexity.ts ================================================ /** * File: space_complexity.ts * Created Time: 2023-02-05 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from '../modules/ListNode'; import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Function */ function constFunc(): number { // Perform some operations return 0; } /* Constant order */ function constant(n: number): void { // Constants, variables, objects occupy O(1) space const a = 0; const b = 0; const nums = new Array(10000); const node = new ListNode(0); // Variables in the loop occupy O(1) space for (let i = 0; i < n; i++) { const c = 0; } // Functions in the loop occupy O(1) space for (let i = 0; i < n; i++) { constFunc(); } } /* Linear order */ function linear(n: number): void { // Array of length n uses O(n) space const nums = new Array(n); // A list of length n occupies O(n) space const nodes: ListNode[] = []; for (let i = 0; i < n; i++) { nodes.push(new ListNode(i)); } // A hash table of length n occupies O(n) space const map = new Map(); for (let i = 0; i < n; i++) { map.set(i, i.toString()); } } /* Linear order (recursive implementation) */ function linearRecur(n: number): void { console.log(`Recursion n = ${n}`); if (n === 1) return; linearRecur(n - 1); } /* Exponential order */ function quadratic(n: number): void { // Matrix uses O(n^2) space const numMatrix = Array(n) .fill(null) .map(() => Array(n).fill(null)); // 2D list uses O(n^2) space const numList = []; for (let i = 0; i < n; i++) { const tmp = []; for (let j = 0; j < n; j++) { tmp.push(0); } numList.push(tmp); } } /* Quadratic order (recursive implementation) */ function quadraticRecur(n: number): number { if (n <= 0) return 0; const nums = new Array(n); console.log(`In recursion n = ${n}, nums length = ${nums.length}`); return quadraticRecur(n - 1); } /* Driver Code */ function buildTree(n: number): TreeNode | null { if (n === 0) return null; const root = new TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ const n = 5; // Constant order constant(n); // Linear order linear(n); linearRecur(n); // Exponential order quadratic(n); quadraticRecur(n); // Exponential order const root = buildTree(n); printTree(root); ================================================ FILE: en/codes/typescript/chapter_computational_complexity/time_complexity.ts ================================================ /** * File: time_complexity.ts * Created Time: 2023-01-02 * Author: RiverTwilight (contact@rene.wang) */ /* Constant order */ function constant(n: number): number { let count = 0; const size = 100000; for (let i = 0; i < size; i++) count++; return count; } /* Linear order */ function linear(n: number): number { let count = 0; for (let i = 0; i < n; i++) count++; return count; } /* Linear order (traversing array) */ function arrayTraversal(nums: number[]): number { let count = 0; // Number of iterations is proportional to the array length for (let i = 0; i < nums.length; i++) { count++; } return count; } /* Exponential order */ function quadratic(n: number): number { let count = 0; // Number of iterations is quadratically related to the data size n for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { count++; } } return count; } /* Quadratic order (bubble sort) */ function bubbleSort(nums: number[]): number { let count = 0; // Counter // Outer loop: unsorted range is [0, i] for (let i = nums.length - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // Element swap includes 3 unit operations } } } return count; } /* Exponential order (loop implementation) */ function exponential(n: number): number { let count = 0, base = 1; // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1) for (let i = 0; i < n; i++) { for (let j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* Exponential order (recursive implementation) */ function expRecur(n: number): number { if (n === 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* Logarithmic order (loop implementation) */ function logarithmic(n: number): number { let count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* Logarithmic order (recursive implementation) */ function logRecur(n: number): number { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* Linearithmic order */ function linearLogRecur(n: number): number { if (n <= 1) return 1; let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (let i = 0; i < n; i++) { count++; } return count; } /* Factorial order (recursive implementation) */ function factorialRecur(n: number): number { if (n === 0) return 1; let count = 0; // Split from 1 into n for (let i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ // You can modify n to run and observe the trend of the number of operations for various complexities const n = 8; console.log('Input data size n = ' + n); let count = constant(n); console.log('Constant order operation count = ' + count); count = linear(n); console.log('Linear order operation count = ' + count); count = arrayTraversal(new Array(n)); console.log('Linear order (array traversal) operation count = ' + count); count = quadratic(n); console.log('Quadratic order operation count = ' + count); var nums = new Array(n); for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); console.log('Quadratic order (bubble sort) operation count = ' + count); count = exponential(n); console.log('Exponential order (loop implementation) operation count = ' + count); count = expRecur(n); console.log('Exponential order (recursive implementation) operation count = ' + count); count = logarithmic(n); console.log('Logarithmic order (loop implementation) operation count = ' + count); count = logRecur(n); console.log('Logarithmic order (recursive implementation) operation count = ' + count); count = linearLogRecur(n); console.log('Linearithmic order (recursive implementation) operation count = ' + count); count = factorialRecur(n); console.log('Factorial order (recursive implementation) operation count = ' + count); export {}; ================================================ FILE: en/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts ================================================ /** * File: worst_best_time_complexity.ts * Created Time: 2023-01-05 * Author: RiverTwilight (contact@rene.wang) */ /* Generate an array with elements { 1, 2, ..., n }, order shuffled */ function randomNumbers(n: number): number[] { const nums = Array(n); // Generate array nums = { 1, 2, 3, ..., n } for (let i = 0; i < n; i++) { nums[i] = i + 1; } // Randomly shuffle array elements for (let i = 0; i < n; i++) { const r = Math.floor(Math.random() * (i + 1)); const temp = nums[i]; nums[i] = nums[r]; nums[r] = temp; } return nums; } /* Find the index of number 1 in array nums */ function findOne(nums: number[]): number { for (let i = 0; i < nums.length; i++) { // When element 1 is at the head of the array, best time complexity O(1) is achieved // When element 1 is at the tail of the array, worst time complexity O(n) is achieved if (nums[i] === 1) { return i; } } return -1; } /* Driver Code */ for (let i = 0; i < 10; i++) { const n = 100; const nums = randomNumbers(n); const index = findOne(nums); console.log('\nArray [ 1, 2, ..., n ] after shuffling = [' + nums.join(', ') + ']'); console.log('Index of number 1 is ' + index); } export {}; ================================================ FILE: en/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts ================================================ /** * File: binary_search_recur.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Binary search: problem f(i, j) */ function dfs(nums: number[], target: number, i: number, j: number): number { // If the interval is empty, it means there is no target element, return -1 if (i > j) { return -1; } // Calculate the midpoint index m const m = i + ((j - i) >> 1); if (nums[m] < target) { // Recursion subproblem f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // Recursion subproblem f(i, m-1) return dfs(nums, target, i, m - 1); } else { // Found the target element, return its index return m; } } /* Binary search */ function binarySearch(nums: number[], target: number): number { const n = nums.length; // Solve the problem f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // Binary search (closed interval on both sides) const index = binarySearch(nums, target); console.log(`Index of target element 6 is ${index}`); export {}; ================================================ FILE: en/codes/typescript/chapter_divide_and_conquer/build_tree.ts ================================================ /** * File: build_tree.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ import { printTree } from '../modules/PrintUtil'; import { TreeNode } from '../modules/TreeNode'; /* Build binary tree: divide and conquer */ function dfs( preorder: number[], inorderMap: Map, i: number, l: number, r: number ): TreeNode | null { // Terminate when the subtree interval is empty if (r - l < 0) return null; // Initialize the root node const root: TreeNode = new TreeNode(preorder[i]); // Query m to divide the left and right subtrees const m = inorderMap.get(preorder[i]); // Subproblem: build the left subtree root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // Subproblem: build the right subtree root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // Return the root node return root; } /* Build binary tree */ function buildTree(preorder: number[], inorder: number[]): TreeNode | null { // Initialize hash map, storing the mapping from inorder elements to indices let inorderMap = new Map(); for (let i = 0; i < inorder.length; i++) { inorderMap.set(inorder[i], i); } const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ const preorder = [3, 9, 2, 1, 7]; const inorder = [9, 3, 1, 2, 7]; console.log('Preorder traversal = ' + JSON.stringify(preorder)); console.log('Inorder traversal = ' + JSON.stringify(inorder)); const root = buildTree(preorder, inorder); console.log('The constructed binary tree is:'); printTree(root); ================================================ FILE: en/codes/typescript/chapter_divide_and_conquer/hanota.ts ================================================ /** * File: hanota.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Move a disk */ function move(src: number[], tar: number[]): void { // Take out a disk from the top of src const pan = src.pop(); // Place the disk on top of tar tar.push(pan); } /* Solve the Tower of Hanoi problem f(i) */ function dfs(i: number, src: number[], buf: number[], tar: number[]): void { // If there is only one disk left in src, move it directly to tar if (i === 1) { move(src, tar); return; } // Subproblem f(i-1): move the top i-1 disks from src to buf using tar dfs(i - 1, src, tar, buf); // Subproblem f(1): move the remaining disk from src to tar move(src, tar); // Subproblem f(i-1): move the top i-1 disks from buf to tar using src dfs(i - 1, buf, src, tar); } /* Solve the Tower of Hanoi problem */ function solveHanota(A: number[], B: number[], C: number[]): void { const n = A.length; // Move the top n disks from A to C using B dfs(n, A, B, C); } /* Driver Code */ // The tail of the list is the top of the rod const A = [5, 4, 3, 2, 1]; const B = []; const C = []; console.log('In initial state:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); solveHanota(A, B, C); console.log('After disk movement is complete:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); ================================================ FILE: en/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts ================================================ /** * File: climbing_stairs_backtrack.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Backtracking */ function backtrack( choices: number[], state: number, n: number, res: Map<0, any> ): void { // When climbing to the n-th stair, add 1 to the solution count if (state === n) res.set(0, res.get(0) + 1); // Traverse all choices for (const choice of choices) { // Pruning: not allowed to go beyond the n-th stair if (state + choice > n) continue; // Attempt: make choice, update state backtrack(choices, state + choice, n, res); // Backtrack } } /* Climbing stairs: Backtracking */ function climbingStairsBacktrack(n: number): number { const choices = [1, 2]; // Can choose to climb up 1 or 2 stairs const state = 0; // Start climbing from the 0-th stair const res = new Map(); res.set(0, 0); // Use res[0] to record the solution count backtrack(choices, state, n, res); return res.get(0); } /* Driver Code */ const n = 9; const res = climbingStairsBacktrack(n); console.log(`Climbing ${n} stairs has ${res} solutions`); export {}; ================================================ FILE: en/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts ================================================ /** * File: climbing_stairs_constraint_dp.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Climbing stairs with constraint: Dynamic programming */ function climbingStairsConstraintDP(n: number): number { if (n === 1 || n === 2) { return 1; } // Initialize dp table, used to store solutions to subproblems const dp = Array.from({ length: n + 1 }, () => new Array(3)); // Initial state: preset the solution to the smallest subproblem dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // State transition: gradually solve larger subproblems from smaller ones for (let i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ const n = 9; const res = climbingStairsConstraintDP(n); console.log(`Climbing ${n} stairs has ${res} solutions`); export {}; ================================================ FILE: en/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts ================================================ /** * File: climbing_stairs_dfs.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Search */ function dfs(i: number): number { // Known dp[1] and dp[2], return them if (i === 1 || i === 2) return i; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1) + dfs(i - 2); return count; } /* Climbing stairs: Search */ function climbingStairsDFS(n: number): number { return dfs(n); } /* Driver Code */ const n = 9; const res = climbingStairsDFS(n); console.log(`Climbing ${n} stairs has ${res} solutions`); export {}; ================================================ FILE: en/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts ================================================ /** * File: climbing_stairs_dfs_mem.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Memoization search */ function dfs(i: number, mem: number[]): number { // Known dp[1] and dp[2], return them if (i === 1 || i === 2) return i; // If record dp[i] exists, return it directly if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1, mem) + dfs(i - 2, mem); // Record dp[i] mem[i] = count; return count; } /* Climbing stairs: Memoization search */ function climbingStairsDFSMem(n: number): number { // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record const mem = new Array(n + 1).fill(-1); return dfs(n, mem); } /* Driver Code */ const n = 9; const res = climbingStairsDFSMem(n); console.log(`Climbing ${n} stairs has ${res} solutions`); export {}; ================================================ FILE: en/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts ================================================ /** * File: climbing_stairs_dp.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Climbing stairs: Dynamic programming */ function climbingStairsDP(n: number): number { if (n === 1 || n === 2) return n; // Initialize dp table, used to store solutions to subproblems const dp = new Array(n + 1).fill(-1); // Initial state: preset the solution to the smallest subproblem dp[1] = 1; dp[2] = 2; // State transition: gradually solve larger subproblems from smaller ones for (let i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* Climbing stairs: Space-optimized dynamic programming */ function climbingStairsDPComp(n: number): number { if (n === 1 || n === 2) return n; let a = 1, b = 2; for (let i = 3; i <= n; i++) { const tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ const n = 9; let res = climbingStairsDP(n); console.log(`Climbing ${n} stairs has ${res} solutions`); res = climbingStairsDPComp(n); console.log(`Climbing ${n} stairs has ${res} solutions`); export {}; ================================================ FILE: en/codes/typescript/chapter_dynamic_programming/coin_change.ts ================================================ /** * File: coin_change.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Coin change: Dynamic programming */ function coinChangeDP(coins: Array, amt: number): number { const n = coins.length; const MAX = amt + 1; // Initialize dp table const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // State transition: first row and first column for (let a = 1; a <= amt; a++) { dp[0][a] = MAX; } // State transition: rest of the rows and columns for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // The smaller value between not selecting and selecting coin 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; } /* Coin change: Space-optimized dynamic programming */ function coinChangeDPComp(coins: Array, amt: number): number { const n = coins.length; const MAX = amt + 1; // Initialize dp table const dp = Array.from({ length: amt + 1 }, () => MAX); dp[0] = 0; // State transition for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // The smaller value between not selecting and selecting coin i dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] !== MAX ? dp[amt] : -1; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 4; // Dynamic programming let res = coinChangeDP(coins, amt); console.log(`Minimum coins needed to make target amount is ${res}`); // Space-optimized dynamic programming res = coinChangeDPComp(coins, amt); console.log(`Minimum coins needed to make target amount is ${res}`); export {}; ================================================ FILE: en/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts ================================================ /** * File: coin_change_ii.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Coin change II: Dynamic programming */ function coinChangeIIDP(coins: Array, amt: number): number { const n = coins.length; // Initialize dp table const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // Initialize first column for (let i = 0; i <= n; i++) { dp[i][0] = 1; } // State transition for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[i][a] = dp[i - 1][a]; } else { // Sum of the two options: not selecting and selecting coin i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* Coin change II: Space-optimized dynamic programming */ function coinChangeIIDPComp(coins: Array, amt: number): number { const n = coins.length; // Initialize dp table const dp = Array.from({ length: amt + 1 }, () => 0); dp[0] = 1; // State transition for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // If exceeds target amount, don't select coin i dp[a] = dp[a]; } else { // Sum of the two options: not selecting and selecting coin i dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 5; // Dynamic programming let res = coinChangeIIDP(coins, amt); console.log(`Number of coin combinations to make target amount is ${res}`); // Space-optimized dynamic programming res = coinChangeIIDPComp(coins, amt); console.log(`Number of coin combinations to make target amount is ${res}`); export {}; ================================================ FILE: en/codes/typescript/chapter_dynamic_programming/edit_distance.ts ================================================ /** * File: edit_distance.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Edit distance: Brute-force search */ function editDistanceDFS(s: string, t: string, i: number, j: number): number { // If both s and t are empty, return 0 if (i === 0 && j === 0) return 0; // If s is empty, return length of t if (i === 0) return j; // If t is empty, return length of s if (j === 0) return i; // If two characters are equal, skip both characters if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 const insert = editDistanceDFS(s, t, i, j - 1); const del = editDistanceDFS(s, t, i - 1, j); const replace = editDistanceDFS(s, t, i - 1, j - 1); // Return minimum edit steps return Math.min(insert, del, replace) + 1; } /* Edit distance: Memoization search */ function editDistanceDFSMem( s: string, t: string, mem: Array>, i: number, j: number ): number { // If both s and t are empty, return 0 if (i === 0 && j === 0) return 0; // If s is empty, return length of t if (i === 0) return j; // If t is empty, return length of s if (j === 0) return i; // If there's a record, return it directly if (mem[i][j] !== -1) return mem[i][j]; // If two characters are equal, skip both characters if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 const insert = editDistanceDFSMem(s, t, mem, i, j - 1); const del = editDistanceDFSMem(s, t, mem, i - 1, j); const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Record and return minimum edit steps mem[i][j] = Math.min(insert, del, replace) + 1; return mem[i][j]; } /* Edit distance: Dynamic programming */ function editDistanceDP(s: string, t: string): number { const n = s.length, m = t.length; const dp = Array.from({ length: n + 1 }, () => Array.from({ length: m + 1 }, () => 0) ); // State transition: first row and first column for (let i = 1; i <= n; i++) { dp[i][0] = i; } for (let j = 1; j <= m; j++) { dp[0][j] = j; } // State transition: rest of the rows and columns for (let i = 1; i <= n; i++) { for (let j = 1; j <= m; j++) { if (s.charAt(i - 1) === t.charAt(j - 1)) { // If two characters are equal, skip both characters dp[i][j] = dp[i - 1][j - 1]; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* Edit distance: Space-optimized dynamic programming */ function editDistanceDPComp(s: string, t: string): number { const n = s.length, m = t.length; const dp = new Array(m + 1).fill(0); // State transition: first row for (let j = 1; j <= m; j++) { dp[j] = j; } // State transition: rest of the rows for (let i = 1; i <= n; i++) { // State transition: first column let leftup = dp[0]; // Temporarily store dp[i-1, j-1] dp[0] = i; // State transition: rest of the columns for (let j = 1; j <= m; j++) { const temp = dp[j]; if (s.charAt(i - 1) === t.charAt(j - 1)) { // If two characters are equal, skip both characters dp[j] = leftup; } else { // Minimum edit steps = minimum edit steps of insert, delete, replace + 1 dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; } leftup = temp; // Update for next round's dp[i-1, j-1] } } return dp[m]; } /* Driver Code */ const s = 'bag'; const t = 'pack'; const n = s.length, m = t.length; // Brute-force search let res = editDistanceDFS(s, t, n, m); console.log(`Changing ${s} to ${t} requires minimum ${res} edits`); // Memoization search const mem = Array.from({ length: n + 1 }, () => Array.from({ length: m + 1 }, () => -1) ); res = editDistanceDFSMem(s, t, mem, n, m); console.log(`Changing ${s} to ${t} requires minimum ${res} edits`); // Dynamic programming res = editDistanceDP(s, t); console.log(`Changing ${s} to ${t} requires minimum ${res} edits`); // Space-optimized dynamic programming res = editDistanceDPComp(s, t); console.log(`Changing ${s} to ${t} requires minimum ${res} edits`); export {}; ================================================ FILE: en/codes/typescript/chapter_dynamic_programming/knapsack.ts ================================================ /** * File: knapsack.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 0-1 knapsack: Brute-force search */ function knapsackDFS( wgt: Array, val: Array, i: number, c: number ): number { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i === 0 || c === 0) { return 0; } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // Calculate the maximum value of not putting in and putting in item i const no = knapsackDFS(wgt, val, i - 1, c); const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // Return the larger value of the two options return Math.max(no, yes); } /* 0-1 knapsack: Memoization search */ function knapsackDFSMem( wgt: Array, val: Array, mem: Array>, i: number, c: number ): number { // If all items have been selected or knapsack has no remaining capacity, return value 0 if (i === 0 || c === 0) { return 0; } // If there's a record, return it directly if (mem[i][c] !== -1) { return mem[i][c]; } // If exceeds knapsack capacity, can only choose not to put it in if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // Calculate the maximum value of not putting in and putting in item i const no = knapsackDFSMem(wgt, val, mem, i - 1, c); const yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // Record and return the larger value of the two options mem[i][c] = Math.max(no, yes); return mem[i][c]; } /* 0-1 knapsack: Dynamic programming */ function knapsackDP( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // Initialize dp table const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // State transition for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item 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 knapsack: Space-optimized dynamic programming */ function knapsackDPComp( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // Initialize dp table const dp = Array(cap + 1).fill(0); // State transition for (let i = 1; i <= n; i++) { // Traverse in reverse order for (let c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // The larger value between not selecting and selecting item i dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // Brute-force search let res = knapsackDFS(wgt, val, n, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); // Memoization search const mem = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => -1) ); res = knapsackDFSMem(wgt, val, mem, n, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); // Dynamic programming res = knapsackDP(wgt, val, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); // Space-optimized dynamic programming res = knapsackDPComp(wgt, val, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); export {}; ================================================ FILE: en/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts ================================================ /** * File: min_cost_climbing_stairs_dp.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Minimum cost climbing stairs: Dynamic programming */ function minCostClimbingStairsDP(cost: Array): number { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } // Initialize dp table, used to store solutions to subproblems const dp = new Array(n + 1); // Initial state: preset the solution to the smallest subproblem dp[1] = cost[1]; dp[2] = cost[2]; // State transition: gradually solve larger subproblems from smaller ones for (let i = 3; i <= n; i++) { dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* Minimum cost climbing stairs: Space-optimized dynamic programming */ function minCostClimbingStairsDPComp(cost: Array): number { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } let a = cost[1], b = cost[2]; for (let i = 3; i <= n; i++) { const tmp = b; b = Math.min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; console.log(`Input stair cost list is: ${cost}`); let res = minCostClimbingStairsDP(cost); console.log(`Minimum cost to climb stairs is: ${res}`); res = minCostClimbingStairsDPComp(cost); console.log(`Minimum cost to climb stairs is: ${res}`); export {}; ================================================ FILE: en/codes/typescript/chapter_dynamic_programming/min_path_sum.ts ================================================ /** * File: min_path_sum.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Minimum path sum: Brute-force search */ function minPathSumDFS( grid: Array>, i: number, j: number ): number { // If it's the top-left cell, terminate the search if (i === 0 && j == 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return Infinity; } // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1) const up = minPathSumDFS(grid, i - 1, j); const left = minPathSumDFS(grid, i, j - 1); // Return the minimum path cost from top-left to (i, j) return Math.min(left, up) + grid[i][j]; } /* Minimum path sum: Memoization search */ function minPathSumDFSMem( grid: Array>, mem: Array>, i: number, j: number ): number { // If it's the top-left cell, terminate the search if (i === 0 && j === 0) { return grid[0][0]; } // If row or column index is out of bounds, return +∞ cost if (i < 0 || j < 0) { return Infinity; } // If there's a record, return it directly if (mem[i][j] != -1) { return mem[i][j]; } // Minimum path cost for left and upper cells const up = minPathSumDFSMem(grid, mem, i - 1, j); const left = minPathSumDFSMem(grid, mem, i, j - 1); // Record and return the minimum path cost from top-left to (i, j) mem[i][j] = Math.min(left, up) + grid[i][j]; return mem[i][j]; } /* Minimum path sum: Dynamic programming */ function minPathSumDP(grid: Array>): number { const n = grid.length, m = grid[0].length; // Initialize dp table const dp = Array.from({ length: n }, () => Array.from({ length: m }, () => 0) ); dp[0][0] = grid[0][0]; // State transition: first row for (let j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // State transition: first column for (let i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // State transition: rest of the rows and columns for (let i = 1; i < n; i++) { for (let j: number = 1; j < m; j++) { dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* Minimum path sum: Space-optimized dynamic programming */ function minPathSumDPComp(grid: Array>): number { const n = grid.length, m = grid[0].length; // Initialize dp table const dp = new Array(m); // State transition: first row dp[0] = grid[0][0]; for (let j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // State transition: rest of the rows for (let i = 1; i < n; i++) { // State transition: first column dp[0] = dp[0] + grid[i][0]; // State transition: rest of the columns for (let j = 1; j < m; j++) { dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ const grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; const n = grid.length, m = grid[0].length; // Brute-force search let res = minPathSumDFS(grid, n - 1, m - 1); console.log(`Minimum path sum from top-left to bottom-right is ${res}`); // Memoization search const mem = Array.from({ length: n }, () => Array.from({ length: m }, () => -1) ); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); console.log(`Minimum path sum from top-left to bottom-right is ${res}`); // Dynamic programming res = minPathSumDP(grid); console.log(`Minimum path sum from top-left to bottom-right is ${res}`); // Space-optimized dynamic programming res = minPathSumDPComp(grid); console.log(`Minimum path sum from top-left to bottom-right is ${res}`); export {}; ================================================ FILE: en/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts ================================================ /** * File: unbounded_knapsack.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Unbounded knapsack: Dynamic programming */ function unboundedKnapsackDP( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // Initialize dp table const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // State transition for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[i][c] = dp[i - 1][c]; } else { // The larger value between not selecting and selecting item i dp[i][c] = Math.max( dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* Unbounded knapsack: Space-optimized dynamic programming */ function unboundedKnapsackDPComp( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // Initialize dp table const dp = Array.from({ length: cap + 1 }, () => 0); // State transition for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // If exceeds knapsack capacity, don't select item i dp[c] = dp[c]; } else { // The larger value between not selecting and selecting item i dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [1, 2, 3]; const val = [5, 11, 15]; const cap = 4; // Dynamic programming let res = unboundedKnapsackDP(wgt, val, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); // Space-optimized dynamic programming res = unboundedKnapsackDPComp(wgt, val, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); export {}; ================================================ FILE: en/codes/typescript/chapter_graph/graph_adjacency_list.ts ================================================ /** * File: graph_adjacency_list.ts * Created Time: 2023-02-09 * Author: Justin (xiefahit@gmail.com) */ import { Vertex } from '../modules/Vertex'; /* Undirected graph class based on adjacency list */ class GraphAdjList { // Adjacency list, key: vertex, value: all adjacent vertices of that vertex adjList: Map; /* Constructor */ constructor(edges: Vertex[][]) { this.adjList = new Map(); // Add all vertices and edges for (const edge of edges) { this.addVertex(edge[0]); this.addVertex(edge[1]); this.addEdge(edge[0], edge[1]); } } /* Get the number of vertices */ size(): number { return this.adjList.size; } /* Add edge */ addEdge(vet1: Vertex, vet2: Vertex): void { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 ) { throw new Error('Illegal Argument Exception'); } // Add edge vet1 - vet2 this.adjList.get(vet1).push(vet2); this.adjList.get(vet2).push(vet1); } /* Remove edge */ removeEdge(vet1: Vertex, vet2: Vertex): void { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 || this.adjList.get(vet1).indexOf(vet2) === -1 ) { throw new Error('Illegal Argument Exception'); } // Remove edge vet1 - vet2 this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); } /* Add vertex */ addVertex(vet: Vertex): void { if (this.adjList.has(vet)) return; // Add a new linked list in the adjacency list this.adjList.set(vet, []); } /* Remove vertex */ removeVertex(vet: Vertex): void { if (!this.adjList.has(vet)) { throw new Error('Illegal Argument Exception'); } // Remove the linked list corresponding to vertex vet in the adjacency list this.adjList.delete(vet); // Traverse the linked lists of other vertices and remove all edges containing vet for (const set of this.adjList.values()) { const index: number = set.indexOf(vet); if (index > -1) { set.splice(index, 1); } } } /* Print adjacency list */ print(): void { console.log('Adjacency list ='); for (const [key, value] of this.adjList.entries()) { const tmp = []; for (const vertex of value) { tmp.push(vertex.val); } console.log(key.val + ': ' + tmp.join()); } } } /* Driver Code */ if (import.meta.url.endsWith(process.argv[1])) { /* Add edge */ const v0 = new Vertex(1), v1 = new Vertex(3), v2 = new Vertex(2), v3 = new Vertex(5), v4 = new Vertex(4); const edges = [ [v0, v1], [v1, v2], [v2, v3], [v0, v3], [v2, v4], [v3, v4], ]; const graph = new GraphAdjList(edges); console.log('\nAfter initialization, graph is'); graph.print(); /* Add edge */ // Vertices 1, 2 are v0, v2 graph.addEdge(v0, v2); console.log('\nAfter adding edge 1-2, graph is'); graph.print(); /* Remove edge */ // Vertices 1, 3 are v0, v1 graph.removeEdge(v0, v1); console.log('\nAfter removing edge 1-3, graph is'); graph.print(); /* Add vertex */ const v5 = new Vertex(6); graph.addVertex(v5); console.log('\nAfter adding vertex 6, graph is'); graph.print(); /* Remove vertex */ // Vertex 3 is v1 graph.removeVertex(v1); console.log('\nAfter removing vertex 3, graph is'); graph.print(); } export { GraphAdjList }; ================================================ FILE: en/codes/typescript/chapter_graph/graph_adjacency_matrix.ts ================================================ /** * File: graph_adjacency_matrix.ts * Created Time: 2023-02-09 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Undirected graph class based on adjacency matrix */ class GraphAdjMat { vertices: number[]; // Vertex list, where the element represents the "vertex value" and the index represents the "vertex index" adjMat: number[][]; // Adjacency matrix, where the row and column indices correspond to the "vertex index" /* Constructor */ constructor(vertices: number[], edges: number[][]) { this.vertices = []; this.adjMat = []; // Add vertex for (const val of vertices) { this.addVertex(val); } // Add edge // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices for (const e of edges) { this.addEdge(e[0], e[1]); } } /* Get the number of vertices */ size(): number { return this.vertices.length; } /* Add vertex */ addVertex(val: number): void { const n: number = this.size(); // Add the value of the new vertex to the vertex list this.vertices.push(val); // Add a row to the adjacency matrix const newRow: number[] = []; for (let j: number = 0; j < n; j++) { newRow.push(0); } this.adjMat.push(newRow); // Add a column to the adjacency matrix for (const row of this.adjMat) { row.push(0); } } /* Remove vertex */ removeVertex(index: number): void { if (index >= this.size()) { throw new RangeError('Index Out Of Bounds Exception'); } // Remove the vertex at index from the vertex list this.vertices.splice(index, 1); // Remove the row at index from the adjacency matrix this.adjMat.splice(index, 1); // Remove the column at index from the adjacency matrix for (const row of this.adjMat) { row.splice(index, 1); } } /* Add edge */ // Parameters i, j correspond to the vertices element indices addEdge(i: number, j: number): void { // Handle index out of bounds and equality if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } // In undirected graph, adjacency matrix is symmetric about main diagonal, i.e., satisfies (i, j) === (j, i) this.adjMat[i][j] = 1; this.adjMat[j][i] = 1; } /* Remove edge */ // Parameters i, j correspond to the vertices element indices removeEdge(i: number, j: number): void { // Handle index out of bounds and equality if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } this.adjMat[i][j] = 0; this.adjMat[j][i] = 0; } /* Print adjacency matrix */ print(): void { console.log('Vertex list = ', this.vertices); console.log('Adjacency matrix =', this.adjMat); } } /* Driver Code */ /* Add edge */ // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices const vertices: number[] = [1, 3, 2, 5, 4]; const edges: number[][] = [ [0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4], ]; const graph: GraphAdjMat = new GraphAdjMat(vertices, edges); console.log('\nAfter initialization, graph is'); graph.print(); /* Add edge */ // Add vertex graph.addEdge(0, 2); console.log('\nAfter adding edge 1-2, graph is'); graph.print(); /* Remove edge */ // Vertices 1, 3 have indices 0, 1 respectively graph.removeEdge(0, 1); console.log('\nAfter removing edge 1-3, graph is'); graph.print(); /* Add vertex */ graph.addVertex(6); console.log('\nAfter adding vertex 6, graph is'); graph.print(); /* Remove vertex */ // Vertex 3 has index 1 graph.removeVertex(1); console.log('\nAfter removing vertex 3, graph is'); graph.print(); export {}; ================================================ FILE: en/codes/typescript/chapter_graph/graph_bfs.ts ================================================ /** * File: graph_bfs.ts * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { GraphAdjList } from './graph_adjacency_list'; import { Vertex } from '../modules/Vertex'; /* Breadth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { // Vertex traversal sequence const res: Vertex[] = []; // Hash set for recording vertices that have been visited const visited: Set = new Set(); visited.add(startVet); // Queue used to implement BFS const que = [startVet]; // Starting from vertex vet, loop until all vertices are visited while (que.length) { const vet = que.shift(); // Dequeue the front vertex res.push(vet); // Record visited vertex // Traverse all adjacent vertices of this vertex for (const adjVet of graph.adjList.get(vet) ?? []) { if (visited.has(adjVet)) { continue; // Skip vertices that have been visited } que.push(adjVet); // Only enqueue unvisited visited.add(adjVet); // Mark this vertex as visited } } // Return vertex traversal sequence return res; } /* Driver Code */ /* Add edge */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; const graph = new GraphAdjList(edges); console.log('\nAfter initialization, graph is'); graph.print(); /* Breadth-first traversal */ const res = graphBFS(graph, v[0]); console.log('\nBreadth-first traversal (BFS) vertex sequence is'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: en/codes/typescript/chapter_graph/graph_dfs.ts ================================================ /** * File: graph_dfs.ts * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { Vertex } from '../modules/Vertex'; import { GraphAdjList } from './graph_adjacency_list'; /* Depth-first traversal helper function */ function dfs( graph: GraphAdjList, visited: Set, res: Vertex[], vet: Vertex ): void { res.push(vet); // Record visited vertex visited.add(vet); // Mark this vertex as visited // Traverse all adjacent vertices of this vertex for (const adjVet of graph.adjList.get(vet)) { if (visited.has(adjVet)) { continue; // Skip vertices that have been visited } // Recursively visit adjacent vertices dfs(graph, visited, res, adjVet); } } /* Depth-first traversal */ // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { // Vertex traversal sequence const res: Vertex[] = []; // Hash set for recording vertices that have been visited const visited: Set = new Set(); dfs(graph, visited, res, startVet); return res; } /* Driver Code */ /* Add edge */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; const graph = new GraphAdjList(edges); console.log('\nAfter initialization, graph is'); graph.print(); /* Depth-first traversal */ const res = graphDFS(graph, v[0]); console.log('\nDepth-first traversal (DFS) vertex sequence is'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: en/codes/typescript/chapter_greedy/coin_change_greedy.ts ================================================ /** * File: coin_change_greedy.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Coin change: Greedy algorithm */ function coinChangeGreedy(coins: number[], amt: number): number { // Assume coins array is sorted let i = coins.length - 1; let count = 0; // Loop to make greedy choices until no remaining amount while (amt > 0) { // Find the coin that is less than and closest to the remaining amount while (i > 0 && coins[i] > amt) { i--; } // Choose coins[i] amt -= coins[i]; count++; } // If no feasible solution is found, return -1 return amt === 0 ? count : -1; } /* Driver Code */ // Greedy algorithm: Can guarantee finding the global optimal solution let coins: number[] = [1, 5, 10, 20, 50, 100]; let amt: number = 186; let res: number = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`Minimum coins needed to make ${amt} is ${res}`); // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`Minimum coins needed to make ${amt} is ${res}`); console.log('Actually the minimum number needed is 3, i.e., 20 + 20 + 20'); // Greedy algorithm: Cannot guarantee finding the global optimal solution coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`Minimum coins needed to make ${amt} is ${res}`); console.log('Actually the minimum number needed is 2, i.e., 49 + 49'); export {}; ================================================ FILE: en/codes/typescript/chapter_greedy/fractional_knapsack.ts ================================================ /** * File: fractional_knapsack.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Item */ class Item { w: number; // Item weight v: number; // Item value constructor(w: number, v: number) { this.w = w; this.v = v; } } /* Fractional knapsack: Greedy algorithm */ function fractionalKnapsack(wgt: number[], val: number[], cap: number): number { // Create item list with two attributes: weight, value const items: Item[] = wgt.map((w, i) => new Item(w, val[i])); // Sort by unit value item.v / item.w from high to low items.sort((a, b) => b.v / b.w - a.v / a.w); // Loop for greedy selection let res = 0; for (const item of items) { if (item.w <= cap) { // If remaining capacity is sufficient, put the entire current item into the knapsack res += item.v; cap -= item.w; } else { // If remaining capacity is insufficient, put part of the current item into the knapsack res += (item.v / item.w) * cap; // No remaining capacity, so break out of the loop break; } } return res; } /* Driver Code */ const wgt: number[] = [10, 20, 30, 40, 50]; const val: number[] = [50, 120, 150, 210, 240]; const cap: number = 50; // Greedy algorithm const res: number = fractionalKnapsack(wgt, val, cap); console.log(`Maximum item value not exceeding knapsack capacity is ${res}`); export {}; ================================================ FILE: en/codes/typescript/chapter_greedy/max_capacity.ts ================================================ /** * File: max_capacity.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Max capacity: Greedy algorithm */ function maxCapacity(ht: number[]): number { // Initialize i, j to be at both ends of the array let i = 0, j = ht.length - 1; // Initial max capacity is 0 let res = 0; // Loop for greedy selection until the two boards meet while (i < j) { // Update max capacity const cap: number = Math.min(ht[i], ht[j]) * (j - i); res = Math.max(res, cap); // Move the shorter board inward if (ht[i] < ht[j]) { i += 1; } else { j -= 1; } } return res; } /* Driver Code */ const ht: number[] = [3, 8, 5, 2, 7, 7, 3, 4]; // Greedy algorithm const res: number = maxCapacity(ht); console.log(`Maximum capacity is ${res}`); export {}; ================================================ FILE: en/codes/typescript/chapter_greedy/max_product_cutting.ts ================================================ /** * File: max_product_cutting.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Max product cutting: Greedy algorithm */ function maxProductCutting(n: number): number { // When n <= 3, must cut out a 1 if (n <= 3) { return 1 * (n - 1); } // Greedily cut out 3, a is the number of 3s, b is the remainder let a: number = Math.floor(n / 3); let b: number = n % 3; if (b === 1) { // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2 return Math.pow(3, a - 1) * 2 * 2; } if (b === 2) { // When the remainder is 2, do nothing return Math.pow(3, a) * 2; } // When the remainder is 0, do nothing return Math.pow(3, a); } /* Driver Code */ let n: number = 58; // Greedy algorithm let res: number = maxProductCutting(n); console.log(`Maximum cutting product is ${res}`); export {}; ================================================ FILE: en/codes/typescript/chapter_hashing/array_hash_map.ts ================================================ /** * File: array_hash_map.ts * Created Time: 2022-12-20 * Author: Daniel (better.sunjian@gmail.com) */ /* Key-value pair Number -> String */ class Pair { public key: number; public val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* Hash table based on array implementation */ class ArrayHashMap { private readonly buckets: (Pair | null)[]; constructor() { // Initialize array with 100 buckets this.buckets = new Array(100).fill(null); } /* Hash function */ private hashFunc(key: number): number { return key % 100; } /* Query operation */ public get(key: number): string | null { let index = this.hashFunc(key); let pair = this.buckets[index]; if (pair === null) return null; return pair.val; } /* Add operation */ public set(key: number, val: string) { let index = this.hashFunc(key); this.buckets[index] = new Pair(key, val); } /* Remove operation */ public delete(key: number) { let index = this.hashFunc(key); // Set to null to represent deletion this.buckets[index] = null; } /* Get all key-value pairs */ public entries(): (Pair | null)[] { let arr: (Pair | null)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i]); } } return arr; } /* Get all keys */ public keys(): (number | undefined)[] { let arr: (number | undefined)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i].key); } } return arr; } /* Get all values */ public values(): (string | undefined)[] { let arr: (string | undefined)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i].val); } } return arr; } /* Print hash table */ public print() { let pairSet = this.entries(); for (const pair of pairSet) { console.info(`${pair.key} -> ${pair.val}`); } } } /* Driver Code */ /* Initialize hash table */ const map = new ArrayHashMap(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.set(12836, 'Xiao Ha'); map.set(15937, 'Xiao Luo'); map.set(16750, 'Xiao Suan'); map.set(13276, 'Xiao Fa'); map.set(10583, 'Xiao Ya'); console.info('\nAfter adding is complete, hash table is\nKey -> Value'); map.print(); /* Query operation */ // Input key into hash table to get value let name = map.get(15937); console.info('\nInput student ID 15937, query name ' + name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.delete(10583); console.info('\nAfter removing 10583, hash table is\nKey -> Value'); map.print(); /* Traverse hash table */ console.info('\nTraverse key-value pairs Key->Value'); for (const pair of map.entries()) { if (!pair) continue; console.info(pair.key + ' -> ' + pair.val); } console.info('\nTraverse keys only Key'); for (const key of map.keys()) { console.info(key); } console.info('\nTraverse values only Value'); for (const val of map.values()) { console.info(val); } export {}; ================================================ FILE: en/codes/typescript/chapter_hashing/hash_map.ts ================================================ /** * File: hash_map.ts * Created Time: 2022-12-20 * Author: Daniel (better.sunjian@gmail.com) */ /* Driver Code */ /* Initialize hash table */ const map = new Map(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.set(12836, 'Xiao Ha'); map.set(15937, 'Xiao Luo'); map.set(16750, 'Xiao Suan'); map.set(13276, 'Xiao Fa'); map.set(10583, 'Xiao Ya'); console.info('\nAfter adding is complete, hash table is\nKey -> Value'); console.info(map); /* Query operation */ // Input key into hash table to get value let name = map.get(15937); console.info('\nInput student ID 15937, query name ' + name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.delete(10583); console.info('\nAfter removing 10583, hash table is\nKey -> Value'); console.info(map); /* Traverse hash table */ console.info('\nTraverse key-value pairs Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\nTraverse keys only Key'); for (const k of map.keys()) { console.info(k); } console.info('\nTraverse values only Value'); for (const v of map.values()) { console.info(v); } export {}; ================================================ FILE: en/codes/typescript/chapter_hashing/hash_map_chaining.ts ================================================ /** * File: hash_map_chaining.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Key-value pair Number -> String */ class Pair { key: number; val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* Hash table with separate chaining */ class HashMapChaining { #size: number; // Number of key-value pairs #capacity: number; // Hash table capacity #loadThres: number; // Load factor threshold for triggering expansion #extendRatio: number; // Expansion multiplier #buckets: Pair[][]; // Bucket array /* Constructor */ constructor() { this.#size = 0; this.#capacity = 4; this.#loadThres = 2.0 / 3.0; this.#extendRatio = 2; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); } /* Hash function */ #hashFunc(key: number): number { return key % this.#capacity; } /* Load factor */ #loadFactor(): number { return this.#size / this.#capacity; } /* Query operation */ get(key: number): string | null { const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // Traverse bucket, if key is found, return corresponding val for (const pair of bucket) { if (pair.key === key) { return pair.val; } } // If key is not found, return null return null; } /* Add operation */ put(key: number, val: string): void { // When load factor exceeds threshold, perform expansion if (this.#loadFactor() > this.#loadThres) { this.#extend(); } const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // Traverse bucket, if specified key is encountered, update corresponding val and return for (const pair of bucket) { if (pair.key === key) { pair.val = val; return; } } // If key does not exist, append key-value pair to the end const pair = new Pair(key, val); bucket.push(pair); this.#size++; } /* Remove operation */ remove(key: number): void { const index = this.#hashFunc(key); let bucket = this.#buckets[index]; // Traverse bucket and remove key-value pair from it for (let i = 0; i < bucket.length; i++) { if (bucket[i].key === key) { bucket.splice(i, 1); this.#size--; break; } } } /* Expand hash table */ #extend(): void { // Temporarily store the original hash table const bucketsTmp = this.#buckets; // Initialize expanded new hash table this.#capacity *= this.#extendRatio; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); this.#size = 0; // Move key-value pairs from original hash table to new hash table for (const bucket of bucketsTmp) { for (const pair of bucket) { this.put(pair.key, pair.val); } } } /* Print hash table */ print(): void { for (const bucket of this.#buckets) { let res = []; for (const pair of bucket) { res.push(pair.key + ' -> ' + pair.val); } console.log(res); } } } /* Driver Code */ /* Initialize hash table */ const map = new HashMapChaining(); /* Add operation */ // Add key-value pair (key, value) to the hash table map.put(12836, 'Xiao Ha'); map.put(15937, 'Xiao Luo'); map.put(16750, 'Xiao Suan'); map.put(13276, 'Xiao Fa'); map.put(10583, 'Xiao Ya'); console.log('\nAfter adding is complete, hash table is\nKey -> Value'); map.print(); /* Query operation */ // Input key into hash table to get value const name = map.get(13276); console.log('\nInput student ID 13276, query name ' + name); /* Remove operation */ // Remove key-value pair (key, value) from hash table map.remove(12836); console.log('\nAfter removing 12836, hash table is\nKey -> Value'); map.print(); export {}; ================================================ FILE: en/codes/typescript/chapter_hashing/hash_map_open_addressing.ts ================================================ /** * File: hash_map_open_addressing.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) */ /* Key-value pair Number -> String */ class Pair { key: number; val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* Hash table with open addressing */ class HashMapOpenAddressing { private size: number; // Number of key-value pairs private capacity: number; // Hash table capacity private loadThres: number; // Load factor threshold for triggering expansion private extendRatio: number; // Expansion multiplier private buckets: Array; // Bucket array private TOMBSTONE: Pair; // Removal marker /* Constructor */ constructor() { this.size = 0; // Number of key-value pairs this.capacity = 4; // Hash table capacity this.loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion this.extendRatio = 2; // Expansion multiplier this.buckets = Array(this.capacity).fill(null); // Bucket array this.TOMBSTONE = new Pair(-1, '-1'); // Removal marker } /* Hash function */ private hashFunc(key: number): number { return key % this.capacity; } /* Load factor */ private loadFactor(): number { return this.size / this.capacity; } /* Search for bucket index corresponding to key */ private findBucket(key: number): number { let index = this.hashFunc(key); let firstTombstone = -1; // Linear probing, break when encountering an empty bucket while (this.buckets[index] !== null) { // If key is encountered, return the corresponding bucket index if (this.buckets[index]!.key === key) { // If a removal marker was encountered before, move the key-value pair to that index if (firstTombstone !== -1) { this.buckets[firstTombstone] = this.buckets[index]; this.buckets[index] = this.TOMBSTONE; return firstTombstone; // Return the moved bucket index } return index; // Return bucket index } // Record the first removal marker encountered if ( firstTombstone === -1 && this.buckets[index] === this.TOMBSTONE ) { firstTombstone = index; } // Calculate bucket index, wrap around to the head if past the tail index = (index + 1) % this.capacity; } // If key does not exist, return the index for insertion return firstTombstone === -1 ? index : firstTombstone; } /* Query operation */ get(key: number): string | null { // Search for bucket index corresponding to key const index = this.findBucket(key); // If key-value pair is found, return corresponding val if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { return this.buckets[index]!.val; } // If key-value pair does not exist, return null return null; } /* Add operation */ put(key: number, val: string): void { // When load factor exceeds threshold, perform expansion if (this.loadFactor() > this.loadThres) { this.extend(); } // Search for bucket index corresponding to key const index = this.findBucket(key); // If key-value pair is found, overwrite val and return if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { this.buckets[index]!.val = val; return; } // If key-value pair does not exist, add the key-value pair this.buckets[index] = new Pair(key, val); this.size++; } /* Remove operation */ remove(key: number): void { // Search for bucket index corresponding to key const index = this.findBucket(key); // If key-value pair is found, overwrite it with removal marker if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { this.buckets[index] = this.TOMBSTONE; this.size--; } } /* Expand hash table */ private extend(): void { // Temporarily store the original hash table const bucketsTmp = this.buckets; // Initialize expanded new hash table this.capacity *= this.extendRatio; this.buckets = Array(this.capacity).fill(null); this.size = 0; // Move key-value pairs from original hash table to new hash table for (const pair of bucketsTmp) { if (pair !== null && pair !== this.TOMBSTONE) { this.put(pair.key, pair.val); } } } /* Print hash table */ print(): void { for (const pair of this.buckets) { if (pair === null) { console.log('null'); } else if (pair === this.TOMBSTONE) { console.log('TOMBSTONE'); } else { console.log(pair.key + ' -> ' + pair.val); } } } } /* Driver Code */ // Initialize hash table const hashmap = new HashMapOpenAddressing(); // Add operation // Add key-value pair (key, val) to the hash table hashmap.put(12836, 'Xiao Ha'); hashmap.put(15937, 'Xiao Luo'); hashmap.put(16750, 'Xiao Suan'); hashmap.put(13276, 'Xiao Fa'); hashmap.put(10583, 'Xiao Ya'); console.log('\nAfter adding is complete, hash table is\nKey -> Value'); hashmap.print(); // Query operation // Input key into hash table to get value val const name = hashmap.get(13276); console.log('\nInput student ID 13276, query name ' + name); // Remove operation // Remove key-value pair (key, val) from hash table hashmap.remove(16750); console.log('\nAfter removing 16750, hash table is\nKey -> Value'); hashmap.print(); export {}; ================================================ FILE: en/codes/typescript/chapter_hashing/simple_hash.ts ================================================ /** * File: simple_hash.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Additive hash */ function addHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* Multiplicative hash */ function mulHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (31 * hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* XOR hash */ function xorHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash ^= c.charCodeAt(0); } return hash % MODULUS; } /* Rotational hash */ function rotHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; } return hash; } /* Driver Code */ const key = 'Hello Algo'; let hash = addHash(key); console.log('Additive hash value is ' + hash); hash = mulHash(key); console.log('Multiplicative hash value is ' + hash); hash = xorHash(key); console.log('XOR hash value is ' + hash); hash = rotHash(key); console.log('Rotational hash value is ' + hash); ================================================ FILE: en/codes/typescript/chapter_heap/my_heap.ts ================================================ /** * File: my_heap.ts * Created Time: 2023-02-07 * Author: Justin (xiefahit@gmail.com) */ import { printHeap } from '../modules/PrintUtil'; /* Max heap class */ class MaxHeap { private maxHeap: number[]; /* Constructor, build empty heap or build heap from input list */ constructor(nums?: number[]) { // Add list elements to heap as is this.maxHeap = nums === undefined ? [] : [...nums]; // Heapify all nodes except leaf nodes for (let i = this.parent(this.size() - 1); i >= 0; i--) { this.siftDown(i); } } /* Get index of left child node */ private left(i: number): number { return 2 * i + 1; } /* Get index of right child node */ private right(i: number): number { return 2 * i + 2; } /* Get index of parent node */ private parent(i: number): number { return Math.floor((i - 1) / 2); // Floor division } /* Swap elements */ private swap(i: number, j: number): void { const tmp = this.maxHeap[i]; this.maxHeap[i] = this.maxHeap[j]; this.maxHeap[j] = tmp; } /* Get heap size */ public size(): number { return this.maxHeap.length; } /* Check if heap is empty */ public isEmpty(): boolean { return this.size() === 0; } /* Access top element */ public peek(): number { return this.maxHeap[0]; } /* Element enters heap */ public push(val: number): void { // Add node this.maxHeap.push(val); // Heapify from bottom to top this.siftUp(this.size() - 1); } /* Starting from node i, heapify from bottom to top */ private siftUp(i: number): void { while (true) { // Get parent node of node i const p = this.parent(i); // When "crossing root node" or "node needs no repair", end heapify if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break; // Swap two nodes this.swap(i, p); // Loop upward heapify i = p; } } /* Element exits heap */ public pop(): number { // Handle empty case if (this.isEmpty()) throw new RangeError('Heap is empty.'); // Delete node this.swap(0, this.size() - 1); // Remove node const val = this.maxHeap.pop(); // Return top element this.siftDown(0); // Return heap top element return val; } /* Starting from node i, heapify from top to bottom */ private siftDown(i: number): void { while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break const l = this.left(i), r = this.right(i); let ma = i; if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l; if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r; // Swap two nodes if (ma === i) break; // Swap two nodes this.swap(i, ma); // Loop downwards heapification i = ma; } } /* Driver Code */ public print(): void { printHeap(this.maxHeap); } /* Extract elements from heap */ public getMaxHeap(): number[] { return this.maxHeap; } } /* Driver Code */ if (import.meta.url.endsWith(process.argv[1])) { /* Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap */ const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); console.log('\nAfter inputting list and building heap'); maxHeap.print(); /* Check if heap is empty */ let peek = maxHeap.peek(); console.log(`\nHeap top element is ${peek}`); /* Element enters heap */ const val = 7; maxHeap.push(val); console.log(`\nAfter element ${val} pushes to heap`); maxHeap.print(); /* Time complexity is O(n), not O(nlogn) */ peek = maxHeap.pop(); console.log(`\nAfter heap top element ${peek} pops from heap`); maxHeap.print(); /* Get heap size */ const size = maxHeap.size(); console.log(`\nHeap size is ${size}`); /* Check if heap is empty */ const isEmpty = maxHeap.isEmpty(); console.log(`\nIs heap empty ${isEmpty}`); } export { MaxHeap }; ================================================ FILE: en/codes/typescript/chapter_heap/top_k.ts ================================================ /** * File: top_k.ts * Created Time: 2023-08-13 * Author: Justin (xiefahit@gmail.com) */ import { MaxHeap } from './my_heap'; /* Element enters heap */ function pushMinHeap(maxHeap: MaxHeap, val: number): void { // Negate element maxHeap.push(-val); } /* Element exits heap */ function popMinHeap(maxHeap: MaxHeap): number { // Negate element return -maxHeap.pop(); } /* Access top element */ function peekMinHeap(maxHeap: MaxHeap): number { // Negate element return -maxHeap.peek(); } /* Extract elements from heap */ function getMinHeap(maxHeap: MaxHeap): number[] { // Negate element return maxHeap.getMaxHeap().map((num: number) => -num); } /* Find the largest k elements in array based on heap */ function topKHeap(nums: number[], k: number): number[] { // Python's heapq module implements min heap by default // Note: We negate all heap elements to simulate min heap using max heap const maxHeap = new MaxHeap([]); // Enter the first k elements of array into heap for (let i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // Starting from the (k+1)th element, maintain heap length as k for (let i = k; i < nums.length; i++) { // If current element is greater than top element, top element exits heap, current element enters heap if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } // Return elements in heap return getMinHeap(maxHeap); } /* Driver Code */ const nums = [1, 7, 6, 3, 2]; const k = 3; const res = topKHeap(nums, k); console.log(`The largest ${k} elements are`, res); ================================================ FILE: en/codes/typescript/chapter_searching/binary_search.ts ================================================ /** * File: binary_search.ts * Created Time: 2022-12-27 * Author: Daniel (better.sunjian@gmail.com) */ /* Binary search (closed interval on both sides) */ function binarySearch(nums: number[], target: number): number { // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array let i = 0, j = nums.length - 1; // Loop, exit when the search interval is empty (empty when i > j) while (i <= j) { // Calculate the midpoint index m const m = Math.floor(i + (j - i) / 2); if (nums[m] < target) { // This means target is in the interval [m+1, j] i = m + 1; } else if (nums[m] > target) { // This means target is in the interval [i, m-1] j = m - 1; } else { // Found the target element, return its index return m; } } return -1; // Target element not found, return -1 } /* Binary search (left-closed right-open interval) */ function binarySearchLCRO(nums: number[], target: number): number { // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1 let i = 0, j = nums.length; // Loop, exit when the search interval is empty (empty when i = j) while (i < j) { // Calculate the midpoint index m const m = Math.floor(i + (j - i) / 2); if (nums[m] < target) { // This means target is in the interval [m+1, j) i = m + 1; } else if (nums[m] > target) { // This means target is in the interval [i, m) j = m; } else { // Found the target element, return its index return m; } } return -1; // Target element not found, return -1 } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* Binary search (closed interval on both sides) */ let index = binarySearch(nums, target); console.info('Index of target element 6 is %d', index); /* Binary search (left-closed right-open interval) */ index = binarySearchLCRO(nums, target); console.info('Index of target element 6 is %d', index); export {}; ================================================ FILE: en/codes/typescript/chapter_searching/binary_search_edge.ts ================================================ /** * File: binary_search_edge.ts * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ import { binarySearchInsertion } from './binary_search_insertion'; /* Binary search for the leftmost target */ function binarySearchLeftEdge(nums: Array, target: number): number { // Equivalent to finding the insertion point of target const i = binarySearchInsertion(nums, target); // Target not found, return -1 if (i === nums.length || nums[i] !== target) { return -1; } // Found target, return index i return i; } /* Binary search for the rightmost target */ function binarySearchRightEdge(nums: Array, target: number): number { // Convert to finding the leftmost target + 1 const i = binarySearchInsertion(nums, target + 1); // j points to the rightmost target, i points to the first element greater than target const j = i - 1; // Target not found, return -1 if (j === -1 || nums[j] !== target) { return -1; } // Found target, return index j return j; } /* Driver Code */ // Array with duplicate elements let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\nArray nums = ' + nums); // Binary search left and right boundaries for (const target of [6, 7]) { let index = binarySearchLeftEdge(nums, target); console.log('Leftmost element ' + target + ' has index ' + index); index = binarySearchRightEdge(nums, target); console.log('Rightmost element ' + target + ' has index ' + index); } export {}; ================================================ FILE: en/codes/typescript/chapter_searching/binary_search_insertion.ts ================================================ /** * File: binary_search_insertion.ts * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Binary search for insertion point (no duplicate elements) */ function binarySearchInsertionSimple( nums: Array, target: number ): number { let i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, use Math.floor() to round down if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { return m; // Found target, return insertion point m } } // Target not found, return insertion point i return i; } /* Binary search for insertion point (with duplicate elements) */ function binarySearchInsertion(nums: Array, target: number): number { let i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, use Math.floor() to round down if (nums[m] < target) { i = m + 1; // target is in the interval [m+1, j] } else if (nums[m] > target) { j = m - 1; // target is in the interval [i, m-1] } else { j = m - 1; // The first element less than target is in the interval [i, m-1] } } // Return insertion point i return i; } /* Driver Code */ // Array without duplicate elements let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; console.log('\nArray nums = ' + nums); // Binary search for insertion point for (const target of [6, 9]) { const index = binarySearchInsertionSimple(nums, target); console.log('Element ' + target + ''s insertion point index is ' + index); } // Array with duplicate elements nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\nArray nums = ' + nums); // Binary search for insertion point for (const target of [2, 6, 20]) { const index = binarySearchInsertion(nums, target); console.log('Element ' + target + ''s insertion point index is ' + index); } export { binarySearchInsertion }; ================================================ FILE: en/codes/typescript/chapter_searching/hashing_search.ts ================================================ /** * File: hashing_search.ts * Created Time: 2022-12-29 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { ListNode, arrToLinkedList } from '../modules/ListNode'; /* Hash search (array) */ function hashingSearchArray(map: Map, target: number): number { // Hash table's key: target element, value: index // If this key does not exist in the hash table, return -1 return map.has(target) ? (map.get(target) as number) : -1; } /* Hash search (linked list) */ function hashingSearchLinkedList( map: Map, target: number ): ListNode | null { // Hash table key: target node value, value: node object // If key is not in hash table, return null return map.has(target) ? (map.get(target) as ListNode) : null; } /* Driver Code */ const target = 3; /* Hash search (array) */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // Initialize hash table const map = new Map(); for (let i = 0; i < nums.length; i++) { map.set(nums[i], i); // key: element, value: index } const index = hashingSearchArray(map, target); console.log('Index of target element 3 = ' + index); /* Hash search (linked list) */ let head = arrToLinkedList(nums); // Initialize hash table const map1 = new Map(); while (head != null) { map1.set(head.val, head); // key: node value, value: node head = head.next; } const node = hashingSearchLinkedList(map1, target); console.log('Node object with target value 3 is', node); export {}; ================================================ FILE: en/codes/typescript/chapter_searching/linear_search.ts ================================================ /** * File: linear_search.ts * Created Time: 2023-01-07 * Author: Daniel (better.sunjian@gmail.com) */ import { ListNode, arrToLinkedList } from '../modules/ListNode'; /* Linear search (array) */ function linearSearchArray(nums: number[], target: number): number { // Traverse array for (let i = 0; i < nums.length; i++) { // Found the target element, return its index if (nums[i] === target) { return i; } } // Target element not found, return -1 return -1; } /* Linear search (linked list) */ function linearSearchLinkedList( head: ListNode | null, target: number ): ListNode | null { // Traverse the linked list while (head) { // Found the target node, return it if (head.val === target) { return head; } head = head.next; } // Target node not found, return null return null; } /* Driver Code */ const target = 3; /* Perform linear search in array */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; const index = linearSearchArray(nums, target); console.log('Index of target element 3 =', index); /* Perform linear search in linked list */ const head = arrToLinkedList(nums); const node = linearSearchLinkedList(head, target); console.log('Node object with target value 3 is', node); export {}; ================================================ FILE: en/codes/typescript/chapter_searching/two_sum.ts ================================================ /** * File: two_sum.ts * Created Time: 2022-12-15 * Author: gyt95 (gytkwan@gmail.com) */ /* Method 1: Brute force enumeration */ function twoSumBruteForce(nums: number[], target: number): number[] { const n = nums.length; // Two nested loops, time complexity is O(n^2) for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { if (nums[i] + nums[j] === target) { return [i, j]; } } } return []; } /* Method 2: Auxiliary hash table */ function twoSumHashTable(nums: number[], target: number): number[] { // Auxiliary hash table, space complexity is O(n) let m: Map = new Map(); // Single loop, time complexity is O(n) for (let i = 0; i < nums.length; i++) { let index = m.get(target - nums[i]); if (index !== undefined) { return [index, i]; } else { m.set(nums[i], i); } } return []; } /* Driver Code */ // Method 1 const nums = [2, 7, 11, 15], target = 13; let res = twoSumBruteForce(nums, target); console.log('Method 1 res = ', res); // Method 2 res = twoSumHashTable(nums, target); console.log('Method 2 res = ', res); export {}; ================================================ FILE: en/codes/typescript/chapter_sorting/bubble_sort.ts ================================================ /** * File: bubble_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* Bubble sort */ function bubbleSort(nums: number[]): void { // Outer loop: unsorted range is [0, i] for (let i = nums.length - 1; i > 0; i--) { // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* Bubble sort (flag optimization) */ function bubbleSortWithFlag(nums: number[]): void { // Outer loop: unsorted range is [0, i] for (let i = nums.length - 1; i > 0; i--) { let flag = false; // Initialize flag // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Swap nums[j] and nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // Record element swap } } if (!flag) break; // No elements were swapped in this round of "bubbling", exit directly } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); console.log('After bubble sort, nums =', nums); const nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); console.log('After bubble sort, nums =', nums1); export {}; ================================================ FILE: en/codes/typescript/chapter_sorting/bucket_sort.ts ================================================ /** * File: bucket_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* Bucket sort */ function bucketSort(nums: number[]): void { // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket const k = nums.length / 2; const buckets: number[][] = []; for (let i = 0; i < k; i++) { buckets.push([]); } // 1. Distribute array elements into various buckets for (const num of nums) { // Input data range is [0, 1), use num * k to map to index range [0, k-1] const i = Math.floor(num * k); // Add num to bucket i buckets[i].push(num); } // 2. Sort each bucket for (const bucket of buckets) { // Use built-in sorting function, can also replace with other sorting algorithms bucket.sort((a, b) => a - b); } // 3. Traverse buckets to merge results let i = 0; for (const bucket of buckets) { for (const num of bucket) { nums[i++] = num; } } } /* Driver Code */ const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); console.log('After bucket sort, nums =', nums); export {}; ================================================ FILE: en/codes/typescript/chapter_sorting/counting_sort.ts ================================================ /** * File: counting_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* Counting sort */ // Simple implementation, cannot be used for sorting objects function countingSortNaive(nums: number[]): void { // 1. Count the maximum element m in the array let m: number = Math.max(...nums); // 2. Count the occurrence of each number // counter[num] represents the occurrence of num const counter: number[] = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. Traverse counter, filling each element back into the original array nums let i = 0; for (let num = 0; num < m + 1; num++) { for (let j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* Counting sort */ // Complete implementation, can sort objects and is a stable sort function countingSort(nums: number[]): void { // 1. Count the maximum element m in the array let m: number = Math.max(...nums); // 2. Count the occurrence of each number // counter[num] represents the occurrence of num const counter: number[] = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" // counter[num]-1 is the last index where num appears in res for (let i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. Traverse nums in reverse order, placing each element into the result array res // Initialize the array res to record results const n = nums.length; const res: number[] = new Array(n); for (let i = n - 1; i >= 0; i--) { const num = nums[i]; res[counter[num] - 1] = num; // Place num at the corresponding index counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num } // Use result array res to overwrite the original array nums for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Driver Code */ const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); console.log('After counting sort (cannot sort objects), nums =', nums); const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); console.log('After counting sort, nums1 =', nums1); export {}; ================================================ FILE: en/codes/typescript/chapter_sorting/heap_sort.ts ================================================ /** * File: heap_sort.ts * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* Heap length is n, start heapifying node i, from top to bottom */ function siftDown(nums: number[], n: number, i: number): void { while (true) { // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break let l = 2 * i + 1; let r = 2 * i + 2; let ma = i; if (l < n && nums[l] > nums[ma]) { ma = l; } if (r < n && nums[r] > nums[ma]) { ma = r; } // Swap two nodes if (ma === i) { break; } // Swap two nodes [nums[i], nums[ma]] = [nums[ma], nums[i]]; // Loop downwards heapification i = ma; } } /* Heap sort */ function heapSort(nums: number[]): void { // Build heap operation: heapify all nodes except leaves for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // Extract the largest element from the heap and repeat for n-1 rounds for (let i = nums.length - 1; i > 0; i--) { // Delete node [nums[0], nums[i]] = [nums[i], nums[0]]; // Start heapifying the root node, from top to bottom siftDown(nums, i, 0); } } /* Driver Code */ const nums: number[] = [4, 1, 3, 1, 5, 2]; heapSort(nums); console.log('After heap sort, nums =', nums); export {}; ================================================ FILE: en/codes/typescript/chapter_sorting/insertion_sort.ts ================================================ /** * File: insertion_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* Insertion sort */ function insertionSort(nums: number[]): void { // Outer loop: sorted interval is [0, i-1] for (let i = 1; i < nums.length; i++) { const base = nums[i]; let j = i - 1; // Inner loop: insert base into the correct position within the sorted interval [0, i-1] while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // Move nums[j] to the right by one position j--; } nums[j + 1] = base; // Assign base to the correct position } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); console.log('After insertion sort, nums =', nums); export {}; ================================================ FILE: en/codes/typescript/chapter_sorting/merge_sort.ts ================================================ /** * File: merge_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* Merge left subarray and right subarray */ function merge(nums: number[], left: number, mid: number, right: number): void { // Left subarray interval is [left, mid], right subarray interval is [mid+1, right] // Create a temporary array tmp to store the merged results const tmp = new Array(right - left + 1); // Initialize the start indices of the left and right subarrays let i = left, j = mid + 1, k = 0; // While both subarrays still have elements, compare and copy the smaller element into the temporary array while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // Copy the remaining elements of the left and right subarrays into the temporary array while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* Merge sort */ function mergeSort(nums: number[], left: number, right: number): void { // Termination condition if (left >= right) return; // Terminate recursion when subarray length is 1 // Divide and conquer stage let mid = Math.floor(left + (right - left) / 2); // Calculate midpoint mergeSort(nums, left, mid); // Recursively process the left subarray mergeSort(nums, mid + 1, right); // Recursively process the right subarray // Merge stage merge(nums, left, mid, right); } /* Driver Code */ const nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); console.log('After merge sort, nums =', nums); export {}; ================================================ FILE: en/codes/typescript/chapter_sorting/quick_sort.ts ================================================ /** * File: quick_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* Quick sort class */ class QuickSort { /* Swap elements */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Sentinel partition */ partition(nums: number[], left: number, right: number): number { // Use nums[left] as the pivot let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j -= 1; // Search from right to left for the first element smaller than the pivot } while (i < j && nums[i] <= nums[left]) { i += 1; // Search from left to right for the first element greater than the pivot } // Swap elements this.swap(nums, i, j); // Swap these two elements } this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort */ quickSort(nums: number[], left: number, right: number): void { // Terminate recursion when subarray length is 1 if (left >= right) { return; } // Sentinel partition const pivot = this.partition(nums, left, right); // Recursively process the left subarray and right subarray this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* Quick sort class (median pivot optimization) */ class QuickSortMedian { /* Swap elements */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Select the median of three candidate elements */ medianThree( nums: number[], left: number, mid: number, right: number ): number { let l = nums[left], m = nums[mid], r = nums[right]; // m is between l and r if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // l is between m and r if ((m <= l && l <= r) || (r <= l && l <= m)) return left; return right; } /* Sentinel partition (median of three) */ partition(nums: number[], left: number, right: number): number { // Select the median of three candidate elements let med = this.medianThree( nums, left, Math.floor((left + right) / 2), right ); // Swap the median to the array's leftmost position this.swap(nums, left, med); // Use nums[left] as the pivot let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // Search from right to left for the first element smaller than the pivot } while (i < j && nums[i] <= nums[left]) { i++; // Search from left to right for the first element greater than the pivot } this.swap(nums, i, j); // Swap these two elements } this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort */ quickSort(nums: number[], left: number, right: number): void { // Terminate recursion when subarray length is 1 if (left >= right) { return; } // Sentinel partition const pivot = this.partition(nums, left, right); // Recursively process the left subarray and right subarray this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* Quick sort class (recursion depth optimization) */ class QuickSortTailCall { /* Swap elements */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Sentinel partition */ partition(nums: number[], left: number, right: number): number { // Use nums[left] as the pivot let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // Search from right to left for the first element smaller than the pivot } while (i < j && nums[i] <= nums[left]) { i++; // Search from left to right for the first element greater than the pivot } this.swap(nums, i, j); // Swap these two elements } this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays return i; // Return the index of the pivot } /* Quick sort (recursion depth optimization) */ quickSort(nums: number[], left: number, right: number): void { // Terminate when subarray length is 1 while (left < right) { // Sentinel partition operation let pivot = this.partition(nums, left, right); // Perform quick sort on the shorter of the two subarrays if (pivot - left < right - pivot) { this.quickSort(nums, left, pivot - 1); // Recursively sort the left subarray left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right] } else { this.quickSort(nums, pivot + 1, right); // Recursively sort the right subarray right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1] } } } } /* Driver Code */ /* Quick sort */ const nums = [2, 4, 1, 0, 3, 5]; const quickSort = new QuickSort(); quickSort.quickSort(nums, 0, nums.length - 1); console.log('After quick sort, nums =', nums); /* Quick sort (recursion depth optimization) */ const nums1 = [2, 4, 1, 0, 3, 5]; const quickSortMedian = new QuickSortMedian(); quickSortMedian.quickSort(nums1, 0, nums1.length - 1); console.log('After quick sort (median pivot optimization), nums =', nums1); /* Quick sort (recursion depth optimization) */ const nums2 = [2, 4, 1, 0, 3, 5]; const quickSortTailCall = new QuickSortTailCall(); quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); console.log('After quick sort (recursion depth optimization), nums =', nums2); export {}; ================================================ FILE: en/codes/typescript/chapter_sorting/radix_sort.ts ================================================ /** * File: radix_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* Get the k-th digit of element num, where exp = 10^(k-1) */ function digit(num: number, exp: number): number { // Passing exp instead of k can avoid repeated expensive exponentiation here return Math.floor(num / exp) % 10; } /* Counting sort (based on nums k-th digit) */ function countingSortDigit(nums: number[], exp: number): void { // Decimal digit range is 0~9, therefore need a bucket array of length 10 const counter = new Array(10).fill(0); const n = nums.length; // Count the occurrence of digits 0~9 for (let i = 0; i < n; i++) { const d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d counter[d]++; // Count the occurrence of digit d } // Calculate prefix sum, converting "occurrence count" into "array index" for (let i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // Traverse in reverse, based on bucket statistics, place each element into res const res = new Array(n).fill(0); for (let i = n - 1; i >= 0; i--) { const d = digit(nums[i], exp); const j = counter[d] - 1; // Get the index j for d in the array res[j] = nums[i]; // Place the current element at index j counter[d]--; // Decrease the count of d by 1 } // Use result to overwrite the original array nums for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Radix sort */ function radixSort(nums: number[]): void { // Get the maximum element of the array, used to determine the maximum number of digits let m: number = Math.max(... nums); // Traverse from the lowest to the highest digit for (let exp = 1; exp <= m; exp *= 10) { // Perform counting sort on the k-th digit of array elements // k = 1 -> exp = 1 // k = 2 -> exp = 10 // i.e., exp = 10^(k-1) countingSortDigit(nums, exp); } } /* Driver Code */ const nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radixSort(nums); console.log('After radix sort, nums =', nums); export {}; ================================================ FILE: en/codes/typescript/chapter_sorting/selection_sort.ts ================================================ /** * File: selection_sort.ts * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* Selection sort */ function selectionSort(nums: number[]): void { let n = nums.length; // Outer loop: unsorted interval is [i, n-1] for (let i = 0; i < n - 1; i++) { // Inner loop: find the smallest element within the unsorted interval let k = i; for (let j = i + 1; j < n; j++) { if (nums[j] < nums[k]) { k = j; // Record the index of the smallest element } } // Swap the smallest element with the first element of the unsorted interval [nums[i], nums[k]] = [nums[k], nums[i]]; } } /* Driver Code */ const nums: number[] = [4, 1, 3, 1, 5, 2]; selectionSort(nums); console.log('After selection sort, nums =', nums); export {}; ================================================ FILE: en/codes/typescript/chapter_stack_and_queue/array_deque.ts ================================================ /** * File: array_deque.ts * Created Time: 2023-02-28 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Double-ended queue based on circular array implementation */ class ArrayDeque { private nums: number[]; // Array for storing double-ended queue elements private front: number; // Front pointer, points to the front of the queue element private queSize: number; // Double-ended queue length /* Constructor */ constructor(capacity: number) { this.nums = new Array(capacity); this.front = 0; this.queSize = 0; } /* Get the capacity of the double-ended queue */ capacity(): number { return this.nums.length; } /* Get the length of the double-ended queue */ size(): number { return this.queSize; } /* Check if the double-ended queue is empty */ isEmpty(): boolean { return this.queSize === 0; } /* Calculate circular array index */ index(i: number): number { // Use modulo operation to wrap the array head and tail together // When i passes the tail of the array, return to the head // When i passes the head of the array, return to the tail return (i + this.capacity()) % this.capacity(); } /* Front of the queue enqueue */ pushFirst(num: number): void { if (this.queSize === this.capacity()) { console.log('Double-ended queue is full'); return; } // Use modulo operation to wrap front around to the tail after passing the head of the array // Add num to the front of the queue this.front = this.index(this.front - 1); // Add num to front of queue this.nums[this.front] = num; this.queSize++; } /* Rear of the queue enqueue */ pushLast(num: number): void { if (this.queSize === this.capacity()) { console.log('Double-ended queue is full'); return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array const rear: number = this.index(this.front + this.queSize); // Front pointer moves one position backward this.nums[rear] = num; this.queSize++; } /* Rear of the queue dequeue */ popFirst(): number { const num: number = this.peekFirst(); // Move front pointer backward by one position this.front = this.index(this.front + 1); this.queSize--; return num; } /* Access rear of the queue element */ popLast(): number { const num: number = this.peekLast(); this.queSize--; return num; } /* Return list for printing */ peekFirst(): number { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); return this.nums[this.front]; } /* Driver Code */ peekLast(): number { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); // Initialize double-ended queue const last = this.index(this.front + this.queSize - 1); return this.nums[last]; } /* Return array for printing */ toArray(): number[] { // Elements enqueue const res: number[] = []; for (let i = 0, j = this.front; i < this.queSize; i++, j++) { res[i] = this.nums[this.index(j)]; } return res; } } /* Driver Code */ /* Get the length of the double-ended queue */ const capacity = 5; const deque: ArrayDeque = new ArrayDeque(capacity); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); console.log('Deque deque = [' + deque.toArray() + ']'); /* Update element */ const peekFirst = deque.peekFirst(); console.log('Front element peekFirst = ' + peekFirst); const peekLast = deque.peekLast(); console.log('Rear element peekLast = ' + peekLast); /* Elements enqueue */ deque.pushLast(4); console.log('After element 4 enqueues at rear, deque = [' + deque.toArray() + ']'); deque.pushFirst(1); console.log('After element 1 enqueues at front, deque = [' + deque.toArray() + ']'); /* Element dequeue */ const popLast = deque.popLast(); console.log( 'Rear dequeue element = ' + popLast + ', after rear dequeue deque = [' + deque.toArray() + ']' ); const popFirst = deque.popFirst(); console.log( 'Front dequeue element = ' + popFirst + ', after front dequeue deque = [' + deque.toArray() + ']' ); /* Get the length of the double-ended queue */ const size = deque.size(); console.log('Double-ended queue length size = ' + size); /* Check if the double-ended queue is empty */ const isEmpty = deque.isEmpty(); console.log('Double-ended queue is empty = ' + isEmpty); export {}; ================================================ FILE: en/codes/typescript/chapter_stack_and_queue/array_queue.ts ================================================ /** * File: array_queue.ts * Created Time: 2022-12-11 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Queue based on circular array implementation */ class ArrayQueue { private nums: number[]; // Array for storing queue elements private front: number; // Front pointer, points to the front of the queue element private queSize: number; // Queue length constructor(capacity: number) { this.nums = new Array(capacity); this.front = this.queSize = 0; } /* Get the capacity of the queue */ get capacity(): number { return this.nums.length; } /* Get the length of the queue */ get size(): number { return this.queSize; } /* Check if the queue is empty */ isEmpty(): boolean { return this.queSize === 0; } /* Enqueue */ push(num: number): void { if (this.size === this.capacity) { console.log('Queue is full'); return; } // Use modulo operation to wrap rear around to the head after passing the tail of the array // Add num to the rear of the queue const rear = (this.front + this.queSize) % this.capacity; // Front pointer moves one position backward this.nums[rear] = num; this.queSize++; } /* Dequeue */ pop(): number { const num = this.peek(); // Move front pointer backward by one position, if it passes the tail, return to array head this.front = (this.front + 1) % this.capacity; this.queSize--; return num; } /* Return list for printing */ peek(): number { if (this.isEmpty()) throw new Error('Queue is empty'); return this.nums[this.front]; } /* Return Array */ toArray(): number[] { // Elements enqueue const arr = new Array(this.size); for (let i = 0, j = this.front; i < this.size; i++, j++) { arr[i] = this.nums[j % this.capacity]; } return arr; } } /* Driver Code */ /* Access front of the queue element */ const capacity = 10; const queue = new ArrayQueue(capacity); /* Elements enqueue */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('Queue queue =', queue.toArray()); /* Return list for printing */ const peek = queue.peek(); console.log('Front element peek = ' + peek); /* Element dequeue */ const pop = queue.pop(); console.log('Dequeue element pop = ' + pop + ', after dequeue queue =', queue.toArray()); /* Get the length of the queue */ const size = queue.size; console.log('Queue length size = ' + size); /* Check if the queue is empty */ const isEmpty = queue.isEmpty(); console.log('Queue is empty = ' + isEmpty); /* Test circular array */ for (let i = 0; i < 10; i++) { queue.push(i); queue.pop(); console.log('Round ' + i + ' rounds of enqueue + dequeue, queue =', queue.toArray()); } export {}; ================================================ FILE: en/codes/typescript/chapter_stack_and_queue/array_stack.ts ================================================ /** * File: array_stack.ts * Created Time: 2022-12-08 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Stack based on array implementation */ class ArrayStack { private stack: number[]; constructor() { this.stack = []; } /* Get the length of the stack */ get size(): number { return this.stack.length; } /* Check if the stack is empty */ isEmpty(): boolean { return this.stack.length === 0; } /* Push */ push(num: number): void { this.stack.push(num); } /* Pop */ pop(): number | undefined { if (this.isEmpty()) throw new Error('Stack is empty'); return this.stack.pop(); } /* Return list for printing */ top(): number | undefined { if (this.isEmpty()) throw new Error('Stack is empty'); return this.stack[this.stack.length - 1]; } /* Return Array */ toArray() { return this.stack; } } /* Driver Code */ /* Access top of the stack element */ const stack = new ArrayStack(); /* Elements push onto stack */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('Stack stack = '); console.log(stack.toArray()); /* Return list for printing */ const top = stack.top(); console.log('Stack top element top = ' + top); /* Element pop from stack */ const pop = stack.pop(); console.log('Pop element pop = ' + pop + ', after pop, stack = '); console.log(stack.toArray()); /* Get the length of the stack */ const size = stack.size; console.log('Stack length size = ' + size); /* Check if empty */ const isEmpty = stack.isEmpty(); console.log('Stack is empty = ' + isEmpty); export {}; ================================================ FILE: en/codes/typescript/chapter_stack_and_queue/deque.ts ================================================ /** * File: deque.ts * Created Time: 2023-01-17 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Driver Code */ /* Get the length of the double-ended queue */ // TypeScript has no built-in deque, can only use Array as deque const deque: number[] = []; /* Elements enqueue */ deque.push(2); deque.push(5); deque.push(4); // Note: due to array, unshift() method has O(n) time complexity deque.unshift(3); deque.unshift(1); console.log('Double-ended queue deque = ', deque); /* Update element */ const peekFirst: number = deque[0]; console.log('Front element peekFirst = ' + peekFirst); const peekLast: number = deque[deque.length - 1]; console.log('Rear element peekLast = ' + peekLast); /* Element dequeue */ // Note: due to array, shift() method has O(n) time complexity const popFront: number = deque.shift() as number; console.log( 'Front dequeue element popFront = ' + popFront + ', after front dequeue, deque = ' + deque ); const popBack: number = deque.pop() as number; console.log( 'Dequeue rear element popBack = ' + popBack + ', after rear dequeue, deque = ' + deque ); /* Get the length of the double-ended queue */ const size: number = deque.length; console.log('Double-ended queue length size = ' + size); /* Check if the double-ended queue is empty */ const isEmpty: boolean = size === 0; console.log('Double-ended queue is empty = ' + isEmpty); export {}; ================================================ FILE: en/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts ================================================ /** * File: linkedlist_deque.ts * Created Time: 2023-02-04 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Doubly linked list node */ class ListNode { prev: ListNode; // Predecessor node reference (pointer) next: ListNode; // Successor node reference (pointer) val: number; // Node value constructor(val: number) { this.val = val; this.next = null; this.prev = null; } } /* Double-ended queue based on doubly linked list implementation */ class LinkedListDeque { private front: ListNode; // Head node front private rear: ListNode; // Tail node rear private queSize: number; // Length of the double-ended queue constructor() { this.front = null; this.rear = null; this.queSize = 0; } /* Rear of the queue enqueue operation */ pushLast(val: number): void { const node: ListNode = new ListNode(val); // If the linked list is empty, make both front and rear point to node if (this.queSize === 0) { this.front = node; this.rear = node; } else { // Add node to the tail of the linked list this.rear.next = node; node.prev = this.rear; this.rear = node; // Update tail node } this.queSize++; } /* Front of the queue enqueue operation */ pushFirst(val: number): void { const node: ListNode = new ListNode(val); // If the linked list is empty, make both front and rear point to node if (this.queSize === 0) { this.front = node; this.rear = node; } else { // Add node to the head of the linked list this.front.prev = node; node.next = this.front; this.front = node; // Update head node } this.queSize++; } /* Temporarily store tail node value */ popLast(): number { if (this.queSize === 0) { return null; } const value: number = this.rear.val; // Store tail node value // Update tail node let temp: ListNode = this.rear.prev; if (temp !== null) { temp.next = null; this.rear.prev = null; } this.rear = temp; // Update tail node this.queSize--; return value; } /* Temporarily store head node value */ popFirst(): number { if (this.queSize === 0) { return null; } const value: number = this.front.val; // Store tail node value // Delete head node let temp: ListNode = this.front.next; if (temp !== null) { temp.prev = null; this.front.next = null; } this.front = temp; // Update head node this.queSize--; return value; } /* Driver Code */ peekLast(): number { return this.queSize === 0 ? null : this.rear.val; } /* Return list for printing */ peekFirst(): number { return this.queSize === 0 ? null : this.front.val; } /* Get the length of the double-ended queue */ size(): number { return this.queSize; } /* Check if the double-ended queue is empty */ isEmpty(): boolean { return this.queSize === 0; } /* Print deque */ print(): void { const arr: number[] = []; let temp: ListNode = this.front; while (temp !== null) { arr.push(temp.val); temp = temp.next; } console.log('[' + arr.join(', ') + ']'); } } /* Driver Code */ /* Get the length of the double-ended queue */ const linkedListDeque: LinkedListDeque = new LinkedListDeque(); linkedListDeque.pushLast(3); linkedListDeque.pushLast(2); linkedListDeque.pushLast(5); console.log('Deque linkedListDeque = '); linkedListDeque.print(); /* Update element */ const peekFirst: number = linkedListDeque.peekFirst(); console.log('Front element peekFirst = ' + peekFirst); const peekLast: number = linkedListDeque.peekLast(); console.log('Rear element peekLast = ' + peekLast); /* Elements enqueue */ linkedListDeque.pushLast(4); console.log('After element 4 enqueues at rear, linkedListDeque = '); linkedListDeque.print(); linkedListDeque.pushFirst(1); console.log('After element 1 enqueues at front, linkedListDeque = '); linkedListDeque.print(); /* Element dequeue */ const popLast: number = linkedListDeque.popLast(); console.log('Rear dequeue element = ' + popLast + ', after rear dequeue linkedListDeque = '); linkedListDeque.print(); const popFirst: number = linkedListDeque.popFirst(); console.log('Front dequeue element = ' + popFirst + ', after front dequeue linkedListDeque = '); linkedListDeque.print(); /* Get the length of the double-ended queue */ const size: number = linkedListDeque.size(); console.log('Double-ended queue length size = ' + size); /* Check if the double-ended queue is empty */ const isEmpty: boolean = linkedListDeque.isEmpty(); console.log('Double-ended queue is empty = ' + isEmpty); ================================================ FILE: en/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts ================================================ /** * File: linkedlist_queue.ts * Created Time: 2022-12-19 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ import { ListNode } from '../modules/ListNode'; /* Queue based on linked list implementation */ class LinkedListQueue { private front: ListNode | null; // Head node front private rear: ListNode | null; // Tail node rear private queSize: number = 0; constructor() { this.front = null; this.rear = null; } /* Get the length of the queue */ get size(): number { return this.queSize; } /* Check if the queue is empty */ isEmpty(): boolean { return this.size === 0; } /* Enqueue */ push(num: number): void { // Add num after the tail node const node = new ListNode(num); // If the queue is empty, make both front and rear point to the node if (!this.front) { this.front = node; this.rear = node; // If the queue is not empty, add the node after the tail node } else { this.rear!.next = node; this.rear = node; } this.queSize++; } /* Dequeue */ pop(): number { const num = this.peek(); if (!this.front) throw new Error('Queue is empty'); // Delete head node this.front = this.front.next; this.queSize--; return num; } /* Return list for printing */ peek(): number { if (this.size === 0) throw new Error('Queue is empty'); return this.front!.val; } /* Convert linked list to Array and return */ toArray(): number[] { let node = this.front; const res = new Array(this.size); for (let i = 0; i < res.length; i++) { res[i] = node!.val; node = node!.next; } return res; } } /* Driver Code */ /* Access front of the queue element */ const queue = new LinkedListQueue(); /* Elements enqueue */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('Queue queue = ' + queue.toArray()); /* Return list for printing */ const peek = queue.peek(); console.log('Front element peek = ' + peek); /* Element dequeue */ const pop = queue.pop(); console.log('Dequeue element pop = ' + pop + ', after dequeue, queue = ' + queue.toArray()); /* Get the length of the queue */ const size = queue.size; console.log('Queue length size = ' + size); /* Check if the queue is empty */ const isEmpty = queue.isEmpty(); console.log('Queue is empty = ' + isEmpty); export {}; ================================================ FILE: en/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts ================================================ /** * File: linkedlist_stack.ts * Created Time: 2022-12-21 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ import { ListNode } from '../modules/ListNode'; /* Stack based on linked list implementation */ class LinkedListStack { private stackPeek: ListNode | null; // Use head node as stack top private stkSize: number = 0; // Stack length constructor() { this.stackPeek = null; } /* Get the length of the stack */ get size(): number { return this.stkSize; } /* Check if the stack is empty */ isEmpty(): boolean { return this.size === 0; } /* Push */ push(num: number): void { const node = new ListNode(num); node.next = this.stackPeek; this.stackPeek = node; this.stkSize++; } /* Pop */ pop(): number { const num = this.peek(); if (!this.stackPeek) throw new Error('Stack is empty'); this.stackPeek = this.stackPeek.next; this.stkSize--; return num; } /* Return list for printing */ peek(): number { if (!this.stackPeek) throw new Error('Stack is empty'); return this.stackPeek.val; } /* Convert linked list to Array and return */ toArray(): number[] { let node = this.stackPeek; const res = new Array(this.size); for (let i = res.length - 1; i >= 0; i--) { res[i] = node!.val; node = node!.next; } return res; } } /* Driver Code */ /* Access top of the stack element */ const stack = new LinkedListStack(); /* Elements push onto stack */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('Stack stack = ' + stack.toArray()); /* Return list for printing */ const peek = stack.peek(); console.log('Stack top element peek = ' + peek); /* Element pop from stack */ const pop = stack.pop(); console.log('Pop element pop = ' + pop + ', after pop, stack = ' + stack.toArray()); /* Get the length of the stack */ const size = stack.size; console.log('Stack length size = ' + size); /* Check if empty */ const isEmpty = stack.isEmpty(); console.log('Stack is empty = ' + isEmpty); export {}; ================================================ FILE: en/codes/typescript/chapter_stack_and_queue/queue.ts ================================================ /** * File: queue.ts * Created Time: 2022-12-05 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* Access front of the queue element */ // TypeScript has no built-in queue, can use Array as queue const queue: number[] = []; /* Elements enqueue */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('Queue queue =', queue); /* Return list for printing */ const peek = queue[0]; console.log('Front element peek =', peek); /* Element dequeue */ // Underlying is array, so shift() method has O(n) time complexity const pop = queue.shift(); console.log('Dequeue element pop =', pop, ', after dequeue, queue = ', queue); /* Get the length of the queue */ const size = queue.length; console.log('Queue length size =', size); /* Check if the queue is empty */ const isEmpty = queue.length === 0; console.log('Queue is empty = ', isEmpty); export {}; ================================================ FILE: en/codes/typescript/chapter_stack_and_queue/stack.ts ================================================ /** * File: stack.ts * Created Time: 2022-12-04 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* Access top of the stack element */ // TypeScript has no built-in stack class, can use Array as stack const stack: number[] = []; /* Elements push onto stack */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('Stack stack =', stack); /* Return list for printing */ const peek = stack[stack.length - 1]; console.log('Stack top element peek =', peek); /* Element pop from stack */ const pop = stack.pop(); console.log('Pop element pop =', pop); console.log('After pop, stack =', stack); /* Get the length of the stack */ const size = stack.length; console.log('Stack length size =', size); /* Check if empty */ const isEmpty = stack.length === 0; console.log('Is stack empty =', isEmpty); export {}; ================================================ FILE: en/codes/typescript/chapter_tree/array_binary_tree.ts ================================================ /** * File: array_binary_tree.js * Created Time: 2023-08-09 * Author: yuan0221 (yl1452491917@gmail.com) */ import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; type Order = 'pre' | 'in' | 'post'; /* Binary tree class represented by array */ class ArrayBinaryTree { #tree: (number | null)[]; /* Constructor */ constructor(arr: (number | null)[]) { this.#tree = arr; } /* List capacity */ size(): number { return this.#tree.length; } /* Get value of node at index i */ val(i: number): number | null { // If index out of bounds, return null to represent empty position if (i < 0 || i >= this.size()) return null; return this.#tree[i]; } /* Get index of left child node of node at index i */ left(i: number): number { return 2 * i + 1; } /* Get index of right child node of node at index i */ right(i: number): number { return 2 * i + 2; } /* Get index of parent node of node at index i */ parent(i: number): number { return Math.floor((i - 1) / 2); // Floor division } /* Level-order traversal */ levelOrder(): number[] { let res = []; // Traverse array directly for (let i = 0; i < this.size(); i++) { if (this.val(i) !== null) res.push(this.val(i)); } return res; } /* Depth-first traversal */ #dfs(i: number, order: Order, res: (number | null)[]): void { // If empty position, return if (this.val(i) === null) return; // Preorder traversal if (order === 'pre') res.push(this.val(i)); this.#dfs(this.left(i), order, res); // Inorder traversal if (order === 'in') res.push(this.val(i)); this.#dfs(this.right(i), order, res); // Postorder traversal if (order === 'post') res.push(this.val(i)); } /* Preorder traversal */ preOrder(): (number | null)[] { const res = []; this.#dfs(0, 'pre', res); return res; } /* Inorder traversal */ inOrder(): (number | null)[] { const res = []; this.#dfs(0, 'in', res); return res; } /* Postorder traversal */ postOrder(): (number | null)[] { const res = []; this.#dfs(0, 'post', res); return res; } } /* Driver Code */ // Initialize binary tree // Here we use a function to generate a binary tree directly from an array const arr = Array.of( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ); const root = arrToTree(arr); console.log('\nInitialize binary tree\n'); console.log('Array representation of binary tree:'); console.log(arr); console.log('Linked list representation of binary tree:'); printTree(root); // Binary tree class represented by array const abt = new ArrayBinaryTree(arr); // Access node const i = 1; const l = abt.left(i); const r = abt.right(i); const p = abt.parent(i); console.log('\nCurrent node index is ' + i + ', value is ' + abt.val(i)); console.log( 'Its left child node index is ' + l + ', value is ' + (l === null ? 'null' : abt.val(l)) ); console.log( 'Its right child node index is ' + r + ', value is ' + (r === null ? 'null' : abt.val(r)) ); console.log( 'Its parent node index is ' + p + ', value is ' + (p === null ? 'null' : abt.val(p)) ); // Traverse tree let res = abt.levelOrder(); console.log('\nLevel-order traversal is:' + res); res = abt.preOrder(); console.log('Preorder traversal is:' + res); res = abt.inOrder(); console.log('Inorder traversal is:' + res); res = abt.postOrder(); console.log('Postorder traversal is:' + res); export {}; ================================================ FILE: en/codes/typescript/chapter_tree/avl_tree.ts ================================================ /** * File: avl_tree.ts * Created Time: 2023-02-06 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* AVL tree */ class AVLTree { root: TreeNode; /* Constructor */ constructor() { this.root = null; // Root node } /* Get node height */ height(node: TreeNode): number { // Empty node height is -1, leaf node height is 0 return node === null ? -1 : node.height; } /* Update node height */ private updateHeight(node: TreeNode): void { // Node height equals the height of the tallest subtree + 1 node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; } /* Get balance factor */ balanceFactor(node: TreeNode): number { // Empty node balance factor is 0 if (node === null) return 0; // Node balance factor = left subtree height - right subtree height return this.height(node.left) - this.height(node.right); } /* Right rotation operation */ private rightRotate(node: TreeNode): TreeNode { const child = node.left; const grandChild = child.right; // Using child as pivot, rotate node to the right child.right = node; node.left = grandChild; // Update node height this.updateHeight(node); this.updateHeight(child); // Return root node of subtree after rotation return child; } /* Left rotation operation */ private leftRotate(node: TreeNode): TreeNode { const child = node.right; const grandChild = child.left; // Using child as pivot, rotate node to the left child.left = node; node.right = grandChild; // Update node height this.updateHeight(node); this.updateHeight(child); // Return root node of subtree after rotation return child; } /* Perform rotation operation to restore balance to this subtree */ private rotate(node: TreeNode): TreeNode { // Get balance factor of node const balanceFactor = this.balanceFactor(node); // Left-leaning tree if (balanceFactor > 1) { if (this.balanceFactor(node.left) >= 0) { // Right rotation return this.rightRotate(node); } else { // First left rotation then right rotation node.left = this.leftRotate(node.left); return this.rightRotate(node); } } // Right-leaning tree if (balanceFactor < -1) { if (this.balanceFactor(node.right) <= 0) { // Left rotation return this.leftRotate(node); } else { // First right rotation then left rotation node.right = this.rightRotate(node.right); return this.leftRotate(node); } } // Balanced tree, no rotation needed, return directly return node; } /* Insert node */ insert(val: number): void { this.root = this.insertHelper(this.root, val); } /* Recursively insert node (helper method) */ private insertHelper(node: TreeNode, val: number): TreeNode { if (node === null) return new TreeNode(val); /* 1. Find insertion position and insert node */ if (val < node.val) { node.left = this.insertHelper(node.left, val); } else if (val > node.val) { node.right = this.insertHelper(node.right, val); } else { return node; // Duplicate node not inserted, return directly } this.updateHeight(node); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = this.rotate(node); // Return root node of subtree return node; } /* Remove node */ remove(val: number): void { this.root = this.removeHelper(this.root, val); } /* Recursively delete node (helper method) */ private removeHelper(node: TreeNode, val: number): TreeNode { if (node === null) return null; /* 1. Find node and delete */ if (val < node.val) { node.left = this.removeHelper(node.left, val); } else if (val > node.val) { node.right = this.removeHelper(node.right, val); } else { if (node.left === null || node.right === null) { const child = node.left !== null ? node.left : node.right; // Number of child nodes = 0, delete node directly and return if (child === null) { return null; } else { // Number of child nodes = 1, delete node directly node = child; } } else { // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it let temp = node.right; while (temp.left !== null) { temp = temp.left; } node.right = this.removeHelper(node.right, temp.val); node.val = temp.val; } } this.updateHeight(node); // Update node height /* 2. Perform rotation operation to restore balance to this subtree */ node = this.rotate(node); // Return root node of subtree return node; } /* Search node */ search(val: number): TreeNode { let cur = this.root; // Loop search, exit after passing leaf node while (cur !== null) { if (cur.val < val) { // Target node is in cur's right subtree cur = cur.right; } else if (cur.val > val) { // Target node is in cur's left subtree cur = cur.left; } else { // Found target node, exit loop break; } } // Return target node return cur; } } function testInsert(tree: AVLTree, val: number): void { tree.insert(val); console.log('\nInsert node ' + val + ', AVL tree is'); printTree(tree.root); } function testRemove(tree: AVLTree, val: number): void { tree.remove(val); console.log('\nRemove node ' + val + ', AVL tree is'); printTree(tree.root); } /* Driver Code */ /* Please pay attention to how the AVL tree maintains balance after inserting nodes */ const avlTree = new AVLTree(); /* Insert node */ // Delete nodes 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); /* Please pay attention to how the AVL tree maintains balance after deleting nodes */ testInsert(avlTree, 7); /* Remove node */ // Delete node with degree 1 testRemove(avlTree, 8); // Delete node with degree 2 testRemove(avlTree, 5); // Remove node with degree 1 testRemove(avlTree, 4); // Remove node with degree 2 /* Search node */ const node = avlTree.search(7); console.log('\nFound node object is', node, ', node value = ' + node.val); export {}; ================================================ FILE: en/codes/typescript/chapter_tree/binary_search_tree.ts ================================================ /** * File: binary_search_tree.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Binary search tree */ class BinarySearchTree { private root: TreeNode | null; /* Constructor */ constructor() { // Initialize empty tree this.root = null; } /* Get binary tree root node */ getRoot(): TreeNode | null { return this.root; } /* Search node */ search(num: number): TreeNode | null { let cur = this.root; // Loop search, exit after passing leaf node while (cur !== null) { // Target node is in cur's right subtree if (cur.val < num) cur = cur.right; // Target node is in cur's left subtree else if (cur.val > num) cur = cur.left; // Found target node, exit loop else break; } // Return target node return cur; } /* Insert node */ insert(num: number): void { // If tree is empty, initialize root node if (this.root === null) { this.root = new TreeNode(num); return; } let cur: TreeNode | null = this.root, pre: TreeNode | null = null; // Loop search, exit after passing leaf node while (cur !== null) { // Found duplicate node, return directly if (cur.val === num) return; pre = cur; // Insertion position is in cur's right subtree if (cur.val < num) cur = cur.right; // Insertion position is in cur's left subtree else cur = cur.left; } // Insert node const node = new TreeNode(num); if (pre!.val < num) pre!.right = node; else pre!.left = node; } /* Remove node */ remove(num: number): void { // If tree is empty, return directly if (this.root === null) return; let cur: TreeNode | null = this.root, pre: TreeNode | null = null; // Loop search, exit after passing leaf node while (cur !== null) { // Found node to delete, exit loop if (cur.val === num) break; pre = cur; // Node to delete is in cur's right subtree if (cur.val < num) cur = cur.right; // Node to delete is in cur's left subtree else cur = cur.left; } // If no node to delete, return directly if (cur === null) return; // Number of child nodes = 0 or 1 if (cur.left === null || cur.right === null) { // When number of child nodes = 0 / 1, child = null / that child node const child: TreeNode | null = cur.left !== null ? cur.left : cur.right; // Delete node cur if (cur !== this.root) { if (pre!.left === cur) pre!.left = child; else pre!.right = child; } else { // If deleted node is root node, reassign root node this.root = child; } } // Number of child nodes = 2 else { // Get next node of cur in inorder traversal let tmp: TreeNode | null = cur.right; while (tmp!.left !== null) { tmp = tmp!.left; } // Recursively delete node tmp this.remove(tmp!.val); // Replace cur with tmp cur.val = tmp!.val; } } } /* Driver Code */ /* Initialize binary search tree */ const bst = new BinarySearchTree(); // Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (const num of nums) { bst.insert(num); } console.log('\nInitialized binary tree is\n'); printTree(bst.getRoot()); /* Search node */ const node = bst.search(7); console.log( '\nFound node object is ' + node + ', node value = ' + (node ? node.val : 'null') ); /* Insert node */ bst.insert(16); console.log('\nAfter inserting node 16, binary tree is\n'); printTree(bst.getRoot()); /* Remove node */ bst.remove(1); console.log('\nAfter removing node 1, binary tree is\n'); printTree(bst.getRoot()); bst.remove(2); console.log('\nAfter removing node 2, binary tree is\n'); printTree(bst.getRoot()); bst.remove(4); console.log('\nAfter removing node 4, binary tree is\n'); printTree(bst.getRoot()); export {}; ================================================ FILE: en/codes/typescript/chapter_tree/binary_tree.ts ================================================ /** * File: binary_tree.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Initialize binary tree */ // Initialize nodes let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // Build references (pointers) between nodes n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; console.log('\nInitialize binary tree\n'); printTree(n1); /* Insert node P between n1 -> n2 */ const P = new TreeNode(0); // Delete node n1.left = P; P.left = n2; console.log('\nAfter inserting node P\n'); printTree(n1); // Remove node P n1.left = n2; console.log('\nAfter removing node P\n'); printTree(n1); export {}; ================================================ FILE: en/codes/typescript/chapter_tree/binary_tree_bfs.ts ================================================ /** * File: binary_tree_bfs.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Level-order traversal */ function levelOrder(root: TreeNode | null): number[] { // Initialize queue, add root node const queue = [root]; // Initialize a list to save the traversal sequence const list: number[] = []; while (queue.length) { let node = queue.shift() as TreeNode; // Dequeue list.push(node.val); // Save node value if (node.left) { queue.push(node.left); // Left child node enqueue } if (node.right) { queue.push(node.right); // Right child node enqueue } } return list; } /* Driver Code */ /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\nInitialize binary tree\n'); printTree(root); /* Level-order traversal */ const list = levelOrder(root); console.log('\nLevel-order traversal node print sequence = ' + list); export {}; ================================================ FILE: en/codes/typescript/chapter_tree/binary_tree_dfs.ts ================================================ /** * File: binary_tree_dfs.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; // Initialize list for storing traversal sequence const list: number[] = []; /* Preorder traversal */ function preOrder(root: TreeNode | null): void { if (root === null) { return; } // Visit priority: root node -> left subtree -> right subtree list.push(root.val); preOrder(root.left); preOrder(root.right); } /* Inorder traversal */ function inOrder(root: TreeNode | null): void { if (root === null) { return; } // Visit priority: left subtree -> root node -> right subtree inOrder(root.left); list.push(root.val); inOrder(root.right); } /* Postorder traversal */ function postOrder(root: TreeNode | null): void { if (root === null) { return; } // Visit priority: left subtree -> right subtree -> root node postOrder(root.left); postOrder(root.right); list.push(root.val); } /* Driver Code */ /* Initialize binary tree */ // Here we use a function to generate a binary tree directly from an array const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\nInitialize binary tree\n'); printTree(root); /* Preorder traversal */ list.length = 0; preOrder(root); console.log('\nPreorder traversal node print sequence = ' + list); /* Inorder traversal */ list.length = 0; inOrder(root); console.log('\nInorder traversal node print sequence = ' + list); /* Postorder traversal */ list.length = 0; postOrder(root); console.log('\nPostorder traversal node print sequence = ' + list); export {}; ================================================ FILE: en/codes/typescript/modules/ListNode.ts ================================================ /** * File: ListNode.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ /* Linked list node */ class ListNode { val: number; next: ListNode | null; constructor(val?: number, next?: ListNode | null) { this.val = val === undefined ? 0 : val; this.next = next === undefined ? null : next; } } /* Deserialize array to linked list */ function arrToLinkedList(arr: number[]): ListNode | null { const dum: ListNode = new ListNode(0); let head = dum; for (const val of arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } export { ListNode, arrToLinkedList }; ================================================ FILE: en/codes/typescript/modules/PrintUtil.ts ================================================ /** * File: PrintUtil.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from './ListNode'; import { TreeNode, arrToTree } from './TreeNode'; /* Print linked list */ function printLinkedList(head: ListNode | null): void { const list: string[] = []; while (head !== null) { list.push(head.val.toString()); head = head.next; } console.log(list.join(' -> ')); } class Trunk { prev: Trunk | null; str: string; constructor(prev: Trunk | null, str: string) { this.prev = prev; this.str = str; } } /** * Print binary tree * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ function printTree(root: TreeNode | null) { printTreeHelper(root, null, false); } /* Print binary tree */ function printTreeHelper( root: TreeNode | null, prev: Trunk | null, isRight: boolean ) { if (root === null) { return; } let prev_str = ' '; const trunk = new Trunk(prev, prev_str); printTreeHelper(root.right, trunk, true); if (prev === null) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); console.log(' ' + root.val); if (prev) { prev.str = prev_str; } trunk.str = ' |'; printTreeHelper(root.left, trunk, false); } function showTrunks(p: Trunk | null) { if (p === null) { return; } showTrunks(p.prev); process.stdout.write(p.str); } /* Print heap */ function printHeap(arr: number[]): void { console.log('Heap array representation:'); console.log(arr); console.log('Heap tree representation:'); const root = arrToTree(arr); printTree(root); } export { printLinkedList, printTree, printHeap }; ================================================ FILE: en/codes/typescript/modules/TreeNode.ts ================================================ /** * File: TreeNode.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ /* Binary tree node */ class TreeNode { val: number; // Node value height: number; // Node height left: TreeNode | null; // Left child pointer right: TreeNode | null; // Right child pointer constructor( val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null ) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } /* Deserialize array to binary tree */ function arrToTree(arr: (number | null)[], i: number = 0): TreeNode | null { if (i < 0 || i >= arr.length || arr[i] === null) { return null; } let root = new TreeNode(arr[i]); root.left = arrToTree(arr, 2 * i + 1); root.right = arrToTree(arr, 2 * i + 2); return root; } export { TreeNode, arrToTree }; ================================================ FILE: en/codes/typescript/modules/Vertex.ts ================================================ /** * File: Vertex.ts * Created Time: 2023-02-15 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Vertex class */ class Vertex { val: number; constructor(val: number) { this.val = val; } /* Input value list vals, return vertex list vets */ public static valsToVets(vals: number[]): Vertex[] { const vets: Vertex[] = []; for (let i = 0; i < vals.length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* Input vertex list vets, return value list vals */ public static vetsToVals(vets: Vertex[]): number[] { const vals: number[] = []; for (const vet of vets) { vals.push(vet.val); } return vals; } } export { Vertex }; ================================================ FILE: en/codes/typescript/package.json ================================================ { "private": true, "type": "module", "scripts": { "check": "tsc" }, "devDependencies": { "@types/node": "^24.9.2", "typescript": "^5.9.3" } } ================================================ FILE: en/codes/typescript/tsconfig.json ================================================ { "compilerOptions": { "baseUrl": ".", "module": "esnext", "moduleResolution": "node", "types": ["@types/node"], "noEmit": true, "target": "esnext", }, "include": ["chapter_*/*.ts"], "exclude": ["node_modules"] } ================================================ FILE: en/docs/chapter_appendix/contribution.md ================================================ # Contributing Together Due to limited capacity, there may be inevitable omissions and errors in this book. We appreciate your understanding and are grateful for your help in correcting them. If you discover typos, broken links, missing content, ambiguous wording, unclear explanations, or structural issues, please help us make corrections to provide readers with higher-quality learning resources. The GitHub IDs of all [contributors](https://github.com/krahets/hello-algo/graphs/contributors) will be displayed on the homepage of the book repository, the web version, and the PDF version to acknowledge their selfless contributions to the open source community. !!! success "The Charm of Open Source" The interval between two printings of a physical book is often quite long, making content updates very inconvenient. In this open source book, the time for content updates has been shortened to just days or even hours. ### Minor Content Adjustments As shown in the figure below, there is an "edit icon" in the top-right corner of each page. You can modify text or code by following these steps. 1. Click the "edit icon". If you encounter a prompt asking you to "Fork this repository", please approve the operation. 2. Modify the content of the Markdown source file, verify the correctness of the content, and maintain consistent formatting as much as possible. 3. Fill in a description of your changes at the bottom of the page, then click the "Propose file change" button. After the page transitions, click the "Create pull request" button to submit your pull request. ![Page edit button](contribution.assets/edit_markdown.png) Images cannot be directly modified. Please describe the issue by creating a new [Issue](https://github.com/krahets/hello-algo/issues) or leaving a comment. We will promptly redraw and replace the images. ### Content Creation If you are interested in contributing to this open source project, including translating code into other programming languages or expanding article content, you will need to follow the Pull Request workflow below. 1. Log in to GitHub and Fork the book's [code repository](https://github.com/krahets/hello-algo) to your personal account. 2. Enter your forked repository webpage and use the `git clone` command to clone the repository to your local machine. 3. Create content locally and conduct comprehensive tests to verify code correctness. 4. Commit your local changes and push them to the remote repository. 5. Refresh the repository webpage and click the "Create pull request" button to submit your pull request. ### Docker Deployment From the root directory of `hello-algo`, run the following Docker script to access the project at `http://localhost:8000`: ```shell docker-compose up -d ``` Use the following command to remove the deployment: ```shell docker-compose down ``` ================================================ FILE: en/docs/chapter_appendix/index.md ================================================ # Appendix ![Appendix](../assets/covers/chapter_appendix.jpg) ================================================ FILE: en/docs/chapter_appendix/installation.md ================================================ # Programming Environment Installation ## Installing Ide We recommend using the open-source and lightweight VS Code as the local integrated development environment (IDE). Visit the [VS Code official website](https://code.visualstudio.com/), and download and install the appropriate version of VS Code according to your operating system. ![Download VS Code from the Official Website](installation.assets/vscode_installation.png) VS Code has a powerful ecosystem of extensions that supports running and debugging most programming languages. For example, after installing the "Python Extension Pack" extension, you can debug Python code. The installation steps are shown in the following figure. ![Install VS Code Extensions](installation.assets/vscode_extension_installation.png) ## Installing Language Environments ### Python Environment 1. Download and install [Miniconda3](https://docs.conda.io/en/latest/miniconda.html), which requires Python 3.10 or newer. 2. Search for `python` in the VS Code extension marketplace and install the Python Extension Pack. 3. (Optional) Enter `pip install black` on the command line to install the code formatter. ### C/c++ Environment 1. Windows systems need to install [MinGW](https://sourceforge.net/projects/mingw-w64/files/) ([configuration tutorial](https://blog.csdn.net/qq_33698226/article/details/129031241)); macOS comes with Clang built-in and does not require installation. 2. Search for `c++` in the VS Code extension marketplace and install the C/C++ Extension Pack. 3. (Optional) Open the Settings page, search for the `Clang_format_fallback Style` code formatting option, and set it to `{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }`. ### Java Environment 1. Download and install [OpenJDK](https://jdk.java.net/18/) (version must be > JDK 9). 2. Search for `java` in the VS Code extension marketplace and install the Extension Pack for Java. ### C# Environment 1. Download and install [.Net 8.0](https://dotnet.microsoft.com/en-us/download). 2. Search for `C# Dev Kit` in the VS Code extension marketplace and install C# Dev Kit ([configuration tutorial](https://code.visualstudio.com/docs/csharp/get-started)). 3. You can also use Visual Studio ([installation tutorial](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022)). ### Go Environment 1. Download and install [Go](https://go.dev/dl/). 2. Search for `go` in the VS Code extension marketplace and install Go. 3. Press `Ctrl + Shift + P` to open the command palette, type `go`, select `Go: Install/Update Tools`, check all options and install. ### Swift Environment 1. Download and install [Swift](https://www.swift.org/download/). 2. Search for `swift` in the VS Code extension marketplace and install [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang). ### Javascript Environment 1. Download and install [Node.js](https://nodejs.org/en/). 2. (Optional) Search for `Prettier` in the VS Code extension marketplace and install the code formatter. ### Typescript Environment 1. Follow the same installation steps as the JavaScript environment. 2. Install [TypeScript Execute (tsx)](https://github.com/privatenumber/tsx?tab=readme-ov-file#global-installation). 3. Search for `typescript` in the VS Code extension marketplace and install [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors). ### Dart Environment 1. Download and install [Dart](https://dart.dev/get-dart). 2. Search for `dart` in the VS Code extension marketplace and install [Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code). ### Rust Environment 1. Download and install [Rust](https://www.rust-lang.org/tools/install). 2. Search for `rust` in the VS Code extension marketplace and install [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). ================================================ FILE: en/docs/chapter_appendix/terminology.md ================================================ # Terminology Table The following table lists important terms that appear in this book. It is worth noting the following points: - We recommend remembering the English names of terms to help with reading English literature. - Some terms have different names in Simplified Chinese and Traditional Chinese.

Table   Important Terms in Data Structures and Algorithms

| English | Simplified Chinese | Traditional Chinese | | ------------------------------ | ------------------ | ------------------- | | algorithm | 算法 | 演算法 | | data structure | 数据结构 | 資料結構 | | code | 代码 | 程式碼 | | file | 文件 | 檔案 | | function | 函数 | 函式 | | method | 方法 | 方法 | | variable | 变量 | 變數 | | asymptotic complexity analysis | 渐近复杂度分析 | 漸近複雜度分析 | | time complexity | 时间复杂度 | 時間複雜度 | | space complexity | 空间复杂度 | 空間複雜度 | | loop | 循环 | 迴圈 | | iteration | 迭代 | 迭代 | | recursion | 递归 | 遞迴 | | tail recursion | 尾递归 | 尾遞迴 | | recursion tree | 递归树 | 遞迴樹 | | big-$O$ notation | 大 $O$ 记号 | 大 $O$ 記號 | | asymptotic upper bound | 渐近上界 | 漸近上界 | | sign-magnitude | 原码 | 原碼 | | 1’s complement | 反码 | 一補數 | | 2’s complement | 补码 | 二補數 | | array | 数组 | 陣列 | | index | 索引 | 索引 | | linked list | 链表 | 鏈結串列 | | linked list node, list node | 链表节点 | 鏈結串列節點 | | head node | 头节点 | 頭節點 | | tail node | 尾节点 | 尾節點 | | list | 列表 | 串列 | | dynamic array | 动态数组 | 動態陣列 | | hard disk | 硬盘 | 硬碟 | | random-access memory (RAM) | 内存 | 記憶體 | | cache memory | 缓存 | 快取 | | cache miss | 缓存未命中 | 快取未命中 | | cache hit rate | 缓存命中率 | 快取命中率 | | stack | 栈 | 堆疊 | | top of the stack | 栈顶 | 堆疊頂 | | bottom of the stack | 栈底 | 堆疊底 | | queue | 队列 | 佇列 | | double-ended queue | 双向队列 | 雙向佇列 | | front of the queue | 队首 | 佇列首 | | rear of the queue | 队尾 | 佇列尾 | | hash table | 哈希表 | 雜湊表 | | hash set | 哈希集合 | 雜湊集合 | | bucket | 桶 | 桶 | | hash function | 哈希函数 | 雜湊函式 | | hash collision | 哈希冲突 | 雜湊衝突 | | load factor | 负载因子 | 負載因子 | | separate chaining | 链式地址 | 鏈結位址 | | open addressing | 开放寻址 | 開放定址 | | linear probing | 线性探测 | 線性探查 | | lazy deletion | 懒删除 | 懶刪除 | | binary tree | 二叉树 | 二元樹 | | tree node | 树节点 | 樹節點 | | left-child node | 左子节点 | 左子節點 | | right-child node | 右子节点 | 右子節點 | | parent node | 父节点 | 父節點 | | left subtree | 左子树 | 左子樹 | | right subtree | 右子树 | 右子樹 | | root node | 根节点 | 根節點 | | leaf node | 叶节点 | 葉節點 | | edge | 边 | 邊 | | level | 层 | 層 | | degree | 度 | 度 | | height | 高度 | 高度 | | depth | 深度 | 深度 | | perfect binary tree | 完美二叉树 | 完美二元樹 | | complete binary tree | 完全二叉树 | 完全二元樹 | | full binary tree | 完满二叉树 | 完滿二元樹 | | balanced binary tree | 平衡二叉树 | 平衡二元樹 | | binary search tree | 二叉搜索树 | 二元搜尋樹 | | AVL tree | AVL 树 | AVL 樹 | | red-black tree | 红黑树 | 紅黑樹 | | level-order traversal | 层序遍历 | 層序走訪 | | breadth-first traversal | 广度优先遍历 | 廣度優先走訪 | | depth-first traversal | 深度优先遍历 | 深度優先走訪 | | binary search tree | 二叉搜索树 | 二元搜尋樹 | | balanced binary search tree | 平衡二叉搜索树 | 平衡二元搜尋樹 | | balance factor | 平衡因子 | 平衡因子 | | heap | 堆 | 堆積 | | max heap | 大顶堆 | 大頂堆積 | | min heap | 小顶堆 | 小頂堆積 | | priority queue | 优先队列 | 優先佇列 | | heapify | 堆化 | 堆積化 | | top-$k$ problem | Top-$k$ 问题 | Top-$k$ 問題 | | graph | 图 | 圖 | | vertex | 顶点 | 頂點 | | undirected graph | 无向图 | 無向圖 | | directed graph | 有向图 | 有向圖 | | connected graph | 连通图 | 連通圖 | | disconnected graph | 非连通图 | 非連通圖 | | weighted graph | 有权图 | 有權圖 | | adjacency | 邻接 | 鄰接 | | path | 路径 | 路徑 | | in-degree | 入度 | 入度 | | out-degree | 出度 | 出度 | | adjacency matrix | 邻接矩阵 | 鄰接矩陣 | | adjacency list | 邻接表 | 鄰接表 | | breadth-first search | 广度优先搜索 | 廣度優先搜尋 | | depth-first search | 深度优先搜索 | 深度優先搜尋 | | binary search | 二分查找 | 二分搜尋 | | searching algorithm | 搜索算法 | 搜尋演算法 | | sorting algorithm | 排序算法 | 排序演算法 | | selection sort | 选择排序 | 選擇排序 | | bubble sort | 冒泡排序 | 泡沫排序 | | insertion sort | 插入排序 | 插入排序 | | quick sort | 快速排序 | 快速排序 | | merge sort | 归并排序 | 合併排序 | | heap sort | 堆排序 | 堆積排序 | | bucket sort | 桶排序 | 桶排序 | | counting sort | 计数排序 | 計數排序 | | radix sort | 基数排序 | 基數排序 | | divide and conquer | 分治 | 分治 | | hanota problem | 汉诺塔问题 | 河內塔問題 | | backtracking algorithm | 回溯算法 | 回溯演算法 | | constraint | 约束 | 約束 | | solution | 解 | 解 | | state | 状态 | 狀態 | | pruning | 剪枝 | 剪枝 | | permutations problem | 全排列问题 | 全排列問題 | | subset-sum problem | 子集和问题 | 子集合問題 | | $n$-queens problem | $n$ 皇后问题 | $n$ 皇后問題 | | dynamic programming | 动态规划 | 動態規劃 | | initial state | 初始状态 | 初始狀態 | | state-transition equation | 状态转移方程 | 狀態轉移方程 | | knapsack problem | 背包问题 | 背包問題 | | edit distance problem | 编辑距离问题 | 編輯距離問題 | | greedy algorithm | 贪心算法 | 貪婪演算法 | ================================================ FILE: en/docs/chapter_array_and_linkedlist/array.md ================================================ # Array An array is a linear data structure that stores elements of the same type in contiguous memory space. The position of an element in the array is called the element's index. The figure below illustrates the main concepts and storage method of arrays. ![Array definition and storage method](array.assets/array_definition.png) ## Common Array Operations ### Initializing Arrays We can choose between two array initialization methods based on our needs: without initial values or with given initial values. When no initial values are specified, most programming languages will initialize array elements to $0$: === "Python" ```python title="array.py" # Initialize array arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] nums: list[int] = [1, 3, 2, 5, 4] ``` === "C++" ```cpp title="array.cpp" /* Initialize array */ // Stored on stack int arr[5]; int nums[5] = { 1, 3, 2, 5, 4 }; // Stored on heap (requires manual memory release) int* arr1 = new int[5]; int* nums1 = new int[5] { 1, 3, 2, 5, 4 }; ``` === "Java" ```java title="array.java" /* Initialize array */ int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } int[] nums = { 1, 3, 2, 5, 4 }; ``` === "C#" ```csharp title="array.cs" /* Initialize array */ int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ] int[] nums = [1, 3, 2, 5, 4]; ``` === "Go" ```go title="array.go" /* Initialize array */ var arr [5]int // In Go, specifying length ([5]int) creates an array; not specifying length ([]int) creates a slice // Since Go's arrays are designed to have their length determined at compile time, only constants can be used to specify the length // For convenience in implementing the extend() method, slices are treated as arrays below nums := []int{1, 3, 2, 5, 4} ``` === "Swift" ```swift title="array.swift" /* Initialize array */ let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] let nums = [1, 3, 2, 5, 4] ``` === "JS" ```javascript title="array.js" /* Initialize array */ var arr = new Array(5).fill(0); var nums = [1, 3, 2, 5, 4]; ``` === "TS" ```typescript title="array.ts" /* Initialize array */ let arr: number[] = new Array(5).fill(0); let nums: number[] = [1, 3, 2, 5, 4]; ``` === "Dart" ```dart title="array.dart" /* Initialize array */ List arr = List.filled(5, 0); // [0, 0, 0, 0, 0] List nums = [1, 3, 2, 5, 4]; ``` === "Rust" ```rust title="array.rs" /* Initialize array */ let arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0] let slice: &[i32] = &[0; 5]; // In Rust, specifying length ([i32; 5]) creates an array; not specifying length (&[i32]) creates a slice // Since Rust's arrays are designed to have their length determined at compile time, only constants can be used to specify the length // Vector is the type generally used as a dynamic array in Rust // For convenience in implementing the extend() method, vectors are treated as arrays below let nums: Vec = vec![1, 3, 2, 5, 4]; ``` === "C" ```c title="array.c" /* Initialize array */ int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 } int nums[5] = { 1, 3, 2, 5, 4 }; ``` === "Kotlin" ```kotlin title="array.kt" /* Initialize array */ var arr = IntArray(5) // { 0, 0, 0, 0, 0 } var nums = intArrayOf(1, 3, 2, 5, 4) ``` === "Ruby" ```ruby title="array.rb" # Initialize array arr = Array.new(5, 0) nums = [1, 3, 2, 5, 4] ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0Aarr%20%3D%20%5B0%5D%20*%205%20%20%23%20%5B%200,%200,%200,%200,%200%20%5D%0Anums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Accessing Elements Array elements are stored in contiguous memory space, which means calculating the memory address of array elements is very easy. Given the array's memory address (the memory address of the first element) and an element's index, we can use the formula shown in the figure below to calculate the element's memory address and directly access that element. ![Memory address calculation for array elements](array.assets/array_memory_location_calculation.png) Observing the figure above, we find that the first element of an array has an index of $0$, which may seem counterintuitive since counting from $1$ would be more natural. However, from the perspective of the address calculation formula, **an index is essentially an offset from the memory address**. The address offset of the first element is $0$, so it is reasonable for its index to be $0$. Accessing elements in an array is highly efficient; we can randomly access any element in the array in $O(1)$ time. ```src [file]{array}-[class]{}-[func]{random_access} ``` ### Inserting Elements Array elements are stored "tightly adjacent" in memory, with no space between them to store any additional data. As shown in the figure below, if we want to insert an element in the middle of an array, we need to shift all elements after that position backward by one position, and then assign the value to that index. ![Example of inserting an element into an array](array.assets/array_insert_element.png) It is worth noting that since the length of an array is fixed, inserting an element will inevitably cause the element at the end of the array to be "lost". We will leave the solution to this problem for discussion in the "List" chapter. ```src [file]{array}-[class]{}-[func]{insert} ``` ### Removing Elements Similarly, as shown in the figure below, to delete the element at index $i$, we need to shift all elements after index $i$ forward by one position. ![Example of removing an element from an array](array.assets/array_remove_element.png) Note that after the deletion is complete, the original last element becomes "meaningless", so we do not need to specifically modify it. ```src [file]{array}-[class]{}-[func]{remove} ``` Overall, array insertion and deletion operations have the following drawbacks: - **High time complexity**: The average time complexity for both insertion and deletion in arrays is $O(n)$, where $n$ is the length of the array. - **Loss of elements**: Since the length of an array is immutable, after inserting an element, elements that exceed the array's length will be lost. - **Memory waste**: We can initialize a relatively long array and only use the front portion, so that when inserting data, the lost elements at the end are "meaningless", but this causes some memory space to be wasted. ### Traversing Arrays In most programming languages, we can traverse an array either by index or by directly iterating through each element in the array: ```src [file]{array}-[class]{}-[func]{traverse} ``` ### Finding Elements Finding a specified element in an array requires traversing the array and checking whether the element value matches in each iteration; if it matches, output the corresponding index. Since an array is a linear data structure, the above search operation is called a "linear search". ```src [file]{array}-[class]{}-[func]{find} ``` ### Expanding Arrays In complex system environments, programs cannot guarantee that the memory space after an array is available, making it unsafe to expand the array's capacity. Therefore, in most programming languages, **the length of an array is immutable**. If we want to expand an array, we need to create a new, larger array and then copy the original array elements to the new array one by one. This is an $O(n)$ operation, which is very time-consuming when the array is large. The code is shown below: ```src [file]{array}-[class]{}-[func]{extend} ``` ## Advantages and Limitations of Arrays Arrays are stored in contiguous memory space with elements of the same type. This approach contains rich prior information that the system can use to optimize the efficiency of data structure operations. - **High space efficiency**: Arrays allocate contiguous memory blocks for data without additional structural overhead. - **Support for random access**: Arrays allow accessing any element in $O(1)$ time. - **Cache locality**: When accessing array elements, the computer not only loads the element but also caches the surrounding data, thereby leveraging the cache to improve the execution speed of subsequent operations. Contiguous space storage is a double-edged sword with the following limitations: - **Low insertion and deletion efficiency**: When an array has many elements, insertion and deletion operations require shifting a large number of elements. - **Immutable length**: After an array is initialized, its length is fixed. Expanding the array requires copying all data to a new array, which is very costly. - **Space waste**: If the allocated size of an array exceeds what is actually needed, the extra space is wasted. ## Typical Applications of Arrays Arrays are a fundamental and common data structure, frequently used in various algorithms and for implementing various complex data structures. - **Random access**: If we want to randomly sample some items, we can use an array to store them and generate a random sequence to implement random sampling based on indices. - **Sorting and searching**: Arrays are the most commonly used data structure for sorting and searching algorithms. Quick sort, merge sort, binary search, and others are primarily performed on arrays. - **Lookup tables**: When we need to quickly find an element or its corresponding relationship, we can use an array as a lookup table. For example, if we want to implement a mapping from characters to ASCII codes, we can use the ASCII code value of a character as an index, with the corresponding element stored at that position in the array. - **Machine learning**: Neural networks make extensive use of linear algebra operations between vectors, matrices, and tensors, all of which are constructed in the form of arrays. Arrays are the most commonly used data structure in neural network programming. - **Data structure implementation**: Arrays can be used to implement stacks, queues, hash tables, heaps, graphs, and other data structures. For example, the adjacency matrix representation of a graph is essentially a two-dimensional array. ================================================ FILE: en/docs/chapter_array_and_linkedlist/index.md ================================================ # Array and Linked List ![Array and Linked List](../assets/covers/chapter_array_and_linkedlist.jpg) !!! abstract The world of data structures is like a solid brick wall. Array bricks are neatly arranged, tightly packed one by one. Linked list bricks are scattered everywhere, with connecting vines freely weaving through the gaps between bricks. ================================================ FILE: en/docs/chapter_array_and_linkedlist/linked_list.md ================================================ # Linked List Memory space is a shared resource for all programs. In a complex system runtime environment, available memory space may be scattered throughout the memory. We know that the memory space for storing an array must be contiguous, and when the array is very large, the memory may not be able to provide such a large contiguous space. This is where the flexibility advantage of linked lists becomes apparent. A linked list is a linear data structure in which each element is a node object, and the nodes are connected through "references". A reference records the memory address of the next node, through which the next node can be accessed from the current node. The design of linked lists allows nodes to be stored scattered throughout the memory, and their memory addresses do not need to be contiguous. ![Linked list definition and storage method](linked_list.assets/linkedlist_definition.png) Observing the figure above, the basic unit of a linked list is a node object. Each node contains two pieces of data: the node's "value" and a "reference" to the next node. - The first node of a linked list is called the "head node", and the last node is called the "tail node". - The tail node points to "null", which is denoted as `null`, `nullptr`, and `None` in Java, C++, and Python, respectively. - In languages that support pointers, such as C, C++, Go, and Rust, the aforementioned "reference" should be replaced with "pointer". As shown in the following code, a linked list node `ListNode` contains not only a value but also an additional reference (pointer). Therefore, **linked lists occupy more memory space than arrays when storing the same amount of data**. === "Python" ```python title="" class ListNode: """Linked list node class""" def __init__(self, val: int): self.val: int = val # Node value self.next: ListNode | None = None # Reference to the next node ``` === "C++" ```cpp title="" /* Linked list node structure */ struct ListNode { int val; // Node value ListNode *next; // Pointer to the next node ListNode(int x) : val(x), next(nullptr) {} // Constructor }; ``` === "Java" ```java title="" /* Linked list node class */ class ListNode { int val; // Node value ListNode next; // Reference to the next node ListNode(int x) { val = x; } // Constructor } ``` === "C#" ```csharp title="" /* Linked list node class */ class ListNode(int x) { // Constructor int val = x; // Node value ListNode? next; // Reference to the next node } ``` === "Go" ```go title="" /* Linked list node structure */ type ListNode struct { Val int // Node value Next *ListNode // Pointer to the next node } // NewListNode Constructor, creates a new linked list func NewListNode(val int) *ListNode { return &ListNode{ Val: val, Next: nil, } } ``` === "Swift" ```swift title="" /* Linked list node class */ class ListNode { var val: Int // Node value var next: ListNode? // Reference to the next node init(x: Int) { // Constructor val = x } } ``` === "JS" ```javascript title="" /* Linked list node class */ class ListNode { constructor(val, next) { this.val = (val === undefined ? 0 : val); // Node value this.next = (next === undefined ? null : next); // Reference to the next node } } ``` === "TS" ```typescript title="" /* Linked list node class */ class ListNode { val: number; next: ListNode | null; constructor(val?: number, next?: ListNode | null) { this.val = val === undefined ? 0 : val; // Node value this.next = next === undefined ? null : next; // Reference to the next node } } ``` === "Dart" ```dart title="" /* Linked list node class */ class ListNode { int val; // Node value ListNode? next; // Reference to the next node ListNode(this.val, [this.next]); // Constructor } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* Linked list node class */ #[derive(Debug)] struct ListNode { val: i32, // Node value next: Option>>, // Pointer to the next node } ``` === "C" ```c title="" /* Linked list node structure */ typedef struct ListNode { int val; // Node value struct ListNode *next; // Pointer to the next node } ListNode; /* Constructor */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *) malloc(sizeof(ListNode)); node->val = val; node->next = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* Linked list node class */ // Constructor class ListNode(x: Int) { val _val: Int = x // Node value val next: ListNode? = null // Reference to the next node } ``` === "Ruby" ```ruby title="" # Linked list node class class ListNode attr_accessor :val # Node value attr_accessor :next # Reference to the next node def initialize(val=0, next_node=nil) @val = val @next = next_node end end ``` ## Common Linked List Operations ### Initializing a Linked List Building a linked list involves two steps: first, initializing each node object; second, constructing the reference relationships between nodes. Once initialization is complete, we can traverse all nodes starting from the head node of the linked list through the reference `next`. === "Python" ```python title="linked_list.py" # Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 # Initialize each node n0 = ListNode(1) n1 = ListNode(3) n2 = ListNode(2) n3 = ListNode(5) n4 = ListNode(4) # Build references between nodes n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 ``` === "C++" ```cpp title="linked_list.cpp" /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */ // Initialize each node 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); // Build references between nodes n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; ``` === "Java" ```java title="linked_list.java" /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */ // Initialize each node 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); // Build references between nodes n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "C#" ```csharp title="linked_list.cs" /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */ // Initialize each node ListNode n0 = new(1); ListNode n1 = new(3); ListNode n2 = new(2); ListNode n3 = new(5); ListNode n4 = new(4); // Build references between nodes n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Go" ```go title="linked_list.go" /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */ // Initialize each node n0 := NewListNode(1) n1 := NewListNode(3) n2 := NewListNode(2) n3 := NewListNode(5) n4 := NewListNode(4) // Build references between nodes n0.Next = n1 n1.Next = n2 n2.Next = n3 n3.Next = n4 ``` === "Swift" ```swift title="linked_list.swift" /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */ // Initialize each node let n0 = ListNode(x: 1) let n1 = ListNode(x: 3) let n2 = ListNode(x: 2) let n3 = ListNode(x: 5) let n4 = ListNode(x: 4) // Build references between nodes n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 ``` === "JS" ```javascript title="linked_list.js" /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */ // Initialize each node const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // Build references between nodes n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "TS" ```typescript title="linked_list.ts" /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */ // Initialize each node const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // Build references between nodes n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Dart" ```dart title="linked_list.dart" /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\ // Initialize each node ListNode n0 = ListNode(1); ListNode n1 = ListNode(3); ListNode n2 = ListNode(2); ListNode n3 = ListNode(5); ListNode n4 = ListNode(4); // Build references between nodes n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Rust" ```rust title="linked_list.rs" /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */ // Initialize each node let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None })); let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None })); let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None })); let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None })); let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None })); // Build references between nodes n0.borrow_mut().next = Some(n1.clone()); n1.borrow_mut().next = Some(n2.clone()); n2.borrow_mut().next = Some(n3.clone()); n3.borrow_mut().next = Some(n4.clone()); ``` === "C" ```c title="linked_list.c" /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */ // Initialize each node ListNode* n0 = newListNode(1); ListNode* n1 = newListNode(3); ListNode* n2 = newListNode(2); ListNode* n3 = newListNode(5); ListNode* n4 = newListNode(4); // Build references between nodes n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; ``` === "Kotlin" ```kotlin title="linked_list.kt" /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */ // Initialize each node val n0 = ListNode(1) val n1 = ListNode(3) val n2 = ListNode(2) val n3 = ListNode(5) val n4 = ListNode(4) // Build references between nodes n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Ruby" ```ruby title="linked_list.rb" # Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 # Initialize each node n0 = ListNode.new(1) n1 = ListNode.new(3) n2 = ListNode.new(2) n3 = ListNode.new(5) n4 = ListNode.new(4) # Build references between nodes n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%201%20-%3E%203%20-%3E%202%20-%3E%205%20-%3E%204%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false An array is a single variable; for example, an array `nums` contains elements `nums[0]`, `nums[1]`, etc. A linked list, however, is composed of multiple independent node objects. **We typically use the head node as the reference to the linked list**; for example, the linked list in the above code can be referred to as linked list `n0`. ### Inserting a Node Inserting a node in a linked list is very easy. As shown in the figure below, suppose we want to insert a new node `P` between two adjacent nodes `n0` and `n1`. **We only need to change two node references (pointers)**, with a time complexity of $O(1)$. In contrast, the time complexity of inserting an element in an array is $O(n)$, which is inefficient when dealing with large amounts of data. ![Example of inserting a node into a linked list](linked_list.assets/linkedlist_insert_node.png) ```src [file]{linked_list}-[class]{}-[func]{insert} ``` ### Removing a Node As shown in the figure below, removing a node in a linked list is also very convenient. **We only need to change one node's reference (pointer)**. Note that although node `P` still points to `n1` after the deletion operation is complete, the linked list can no longer access `P` when traversing, which means `P` no longer belongs to this linked list. ![Removing a node from a linked list](linked_list.assets/linkedlist_remove_node.png) ```src [file]{linked_list}-[class]{}-[func]{remove} ``` ### Accessing a Node **Accessing nodes in a linked list is less efficient**. As mentioned in the previous section, we can access any element in an array in $O(1)$ time. This is not the case with linked lists. The program needs to start from the head node and traverse backward one by one until the target node is found. That is, accessing the $i$-th node in a linked list requires $i - 1$ iterations, with a time complexity of $O(n)$. ```src [file]{linked_list}-[class]{}-[func]{access} ``` ### Finding a Node Traverse the linked list to find a node with value `target`, and output the index of that node in the linked list. This process is also a linear search. The code is shown below: ```src [file]{linked_list}-[class]{}-[func]{find} ``` ## Arrays vs. Linked Lists The table below summarizes the characteristics of arrays and linked lists and compares their operational efficiencies. Since they employ two opposite storage strategies, their various properties and operational efficiencies also exhibit contrasting characteristics.

Table   Comparison of array and linked list efficiencies

| | Array | Linked List | | ---------------------- | --------------------------------------------- | -------------------------- | | Storage method | Contiguous memory space | Scattered memory space | | Capacity expansion | Immutable length | Flexible expansion | | Memory efficiency | Elements occupy less memory, but space may be wasted | Elements occupy more memory | | Accessing an element | $O(1)$ | $O(n)$ | | Adding an element | $O(n)$ | $O(1)$ | | Removing an element | $O(n)$ | $O(1)$ | ## Common Types of Linked Lists As shown in the figure below, there are three common types of linked lists: - **Singly linked list**: This is the ordinary linked list introduced earlier. The nodes of a singly linked list contain a value and a reference to the next node. We call the first node the head node and the last node the tail node, which points to null `None`. - **Circular linked list**: If we make the tail node of a singly linked list point to the head node (connecting the tail to the head), we get a circular linked list. In a circular linked list, any node can be viewed as the head node. - **Doubly linked list**: Compared to a singly linked list, a doubly linked list records references in both directions. The node definition of a doubly linked list includes references to both the successor node (next node) and the predecessor node (previous node). Compared to a singly linked list, a doubly linked list is more flexible and can traverse the linked list in both directions, but it also requires more memory space. === "Python" ```python title="" class ListNode: """Doubly linked list node class""" def __init__(self, val: int): self.val: int = val # Node value self.next: ListNode | None = None # Reference to the successor node self.prev: ListNode | None = None # Reference to the predecessor node ``` === "C++" ```cpp title="" /* Doubly linked list node structure */ struct ListNode { int val; // Node value ListNode *next; // Pointer to the successor node ListNode *prev; // Pointer to the predecessor node ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // Constructor }; ``` === "Java" ```java title="" /* Doubly linked list node class */ class ListNode { int val; // Node value ListNode next; // Reference to the successor node ListNode prev; // Reference to the predecessor node ListNode(int x) { val = x; } // Constructor } ``` === "C#" ```csharp title="" /* Doubly linked list node class */ class ListNode(int x) { // Constructor int val = x; // Node value ListNode next; // Reference to the successor node ListNode prev; // Reference to the predecessor node } ``` === "Go" ```go title="" /* Doubly linked list node structure */ type DoublyListNode struct { Val int // Node value Next *DoublyListNode // Pointer to the successor node Prev *DoublyListNode // Pointer to the predecessor node } // NewDoublyListNode Initialization func NewDoublyListNode(val int) *DoublyListNode { return &DoublyListNode{ Val: val, Next: nil, Prev: nil, } } ``` === "Swift" ```swift title="" /* Doubly linked list node class */ class ListNode { var val: Int // Node value var next: ListNode? // Reference to the successor node var prev: ListNode? // Reference to the predecessor node init(x: Int) { // Constructor val = x } } ``` === "JS" ```javascript title="" /* Doubly linked list node class */ class ListNode { constructor(val, next, prev) { this.val = val === undefined ? 0 : val; // Node value this.next = next === undefined ? null : next; // Reference to the successor node this.prev = prev === undefined ? null : prev; // Reference to the predecessor node } } ``` === "TS" ```typescript title="" /* Doubly linked list node class */ class ListNode { val: number; next: ListNode | null; prev: ListNode | null; constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) { this.val = val === undefined ? 0 : val; // Node value this.next = next === undefined ? null : next; // Reference to the successor node this.prev = prev === undefined ? null : prev; // Reference to the predecessor node } } ``` === "Dart" ```dart title="" /* Doubly linked list node class */ class ListNode { int val; // Node value ListNode? next; // Reference to the successor node ListNode? prev; // Reference to the predecessor node ListNode(this.val, [this.next, this.prev]); // Constructor } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* Doubly linked list node type */ #[derive(Debug)] struct ListNode { val: i32, // Node value next: Option>>, // Pointer to the successor node prev: Option>>, // Pointer to the predecessor node } /* Constructor */ impl ListNode { fn new(val: i32) -> Self { ListNode { val, next: None, prev: None, } } } ``` === "C" ```c title="" /* Doubly linked list node structure */ typedef struct ListNode { int val; // Node value struct ListNode *next; // Pointer to the successor node struct ListNode *prev; // Pointer to the predecessor node } ListNode; /* Constructor */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *) malloc(sizeof(ListNode)); node->val = val; node->next = NULL; node->prev = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* Doubly linked list node class */ // Constructor class ListNode(x: Int) { val _val: Int = x // Node value val next: ListNode? = null // Reference to the successor node val prev: ListNode? = null // Reference to the predecessor node } ``` === "Ruby" ```ruby title="" # Doubly linked list node class class ListNode attr_accessor :val # Node value attr_accessor :next # Reference to the successor node attr_accessor :prev # Reference to the predecessor node def initialize(val=0, next_node=nil, prev_node=nil) @val = val @next = next_node @prev = prev_node end end ``` ![Common types of linked lists](linked_list.assets/linkedlist_common_types.png) ## Typical Applications of Linked Lists Singly linked lists are commonly used to implement stacks, queues, hash tables, and graphs. - **Stacks and queues**: When insertion and deletion operations both occur at one end of the linked list, it exhibits last-in-first-out characteristics, corresponding to a stack. When insertion operations occur at one end of the linked list and deletion operations occur at the other end, it exhibits first-in-first-out characteristics, corresponding to a queue. - **Hash tables**: Separate chaining is one of the mainstream solutions for resolving hash collisions. In this approach, all colliding elements are placed in a linked list. - **Graphs**: An adjacency list is a common way to represent a graph, where each vertex in the graph is associated with a linked list, and each element in the linked list represents another vertex connected to that vertex. Doubly linked lists are commonly used in scenarios where quick access to the previous and next elements is needed. - **Advanced data structures**: For example, in red-black trees and B-trees, we need to access the parent node of a node, which can be achieved by saving a reference to the parent node in the node, similar to a doubly linked list. - **Browser history**: In web browsers, when a user clicks the forward or backward button, the browser needs to know the previous and next web pages the user visited. The characteristics of doubly linked lists make this operation simple. - **LRU algorithm**: In cache eviction (LRU) algorithms, we need to quickly find the least recently used data and support quick addition and deletion of nodes. Using a doubly linked list is very suitable for this. Circular linked lists are commonly used in scenarios that require periodic operations, such as operating system resource scheduling. - **Round-robin scheduling algorithm**: In operating systems, round-robin scheduling is a common CPU scheduling algorithm that needs to cycle through a set of processes. Each process is assigned a time slice, and when the time slice expires, the CPU switches to the next process. This cyclic operation can be implemented using a circular linked list. - **Data buffers**: In some data buffer implementations, circular linked lists may also be used. For example, in audio and video players, the data stream may be divided into multiple buffer blocks and placed in a circular linked list to achieve seamless playback. ================================================ FILE: en/docs/chapter_array_and_linkedlist/list.md ================================================ # List A list is an abstract data structure concept that represents an ordered collection of elements, supporting operations such as element access, modification, insertion, deletion, and traversal, without requiring users to consider capacity limitations. Lists can be implemented based on linked lists or arrays. - A linked list can naturally be viewed as a list, supporting element insertion, deletion, search, and modification operations, and can flexibly expand dynamically. - An array also supports element insertion, deletion, search, and modification, but since its length is immutable, it can only be viewed as a list with length limitations. When implementing lists using arrays, **the immutable length property reduces the practicality of the list**. This is because we usually cannot determine in advance how much data we need to store, making it difficult to choose an appropriate list length. If the length is too small, it may fail to meet usage requirements; if the length is too large, it will waste memory space. To solve this problem, we can use a dynamic array to implement a list. It inherits all the advantages of arrays and can dynamically expand during program execution. In fact, **the lists provided in the standard libraries of many programming languages are implemented based on dynamic arrays**, such as `list` in Python, `ArrayList` in Java, `vector` in C++, and `List` in C#. In the following discussion, we will treat "list" and "dynamic array" as equivalent concepts. ## Common List Operations ### Initialize a List We typically use two initialization methods: "without initial values" and "with initial values": === "Python" ```python title="list.py" # Initialize a list # Without initial values nums1: list[int] = [] # With initial values nums: list[int] = [1, 3, 2, 5, 4] ``` === "C++" ```cpp title="list.cpp" /* Initialize a list */ // Note that vector in C++ is equivalent to nums as described in this article // Without initial values vector nums1; // With initial values vector nums = { 1, 3, 2, 5, 4 }; ``` === "Java" ```java title="list.java" /* Initialize a list */ // Without initial values List nums1 = new ArrayList<>(); // With initial values (note that array elements should use the wrapper class Integer[] instead of int[]) Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; List nums = new ArrayList<>(Arrays.asList(numbers)); ``` === "C#" ```csharp title="list.cs" /* Initialize a list */ // Without initial values List nums1 = []; // With initial values int[] numbers = [1, 3, 2, 5, 4]; List nums = [.. numbers]; ``` === "Go" ```go title="list_test.go" /* Initialize a list */ // Without initial values nums1 := []int{} // With initial values nums := []int{1, 3, 2, 5, 4} ``` === "Swift" ```swift title="list.swift" /* Initialize a list */ // Without initial values let nums1: [Int] = [] // With initial values var nums = [1, 3, 2, 5, 4] ``` === "JS" ```javascript title="list.js" /* Initialize a list */ // Without initial values const nums1 = []; // With initial values const nums = [1, 3, 2, 5, 4]; ``` === "TS" ```typescript title="list.ts" /* Initialize a list */ // Without initial values const nums1: number[] = []; // With initial values const nums: number[] = [1, 3, 2, 5, 4]; ``` === "Dart" ```dart title="list.dart" /* Initialize a list */ // Without initial values List nums1 = []; // With initial values List nums = [1, 3, 2, 5, 4]; ``` === "Rust" ```rust title="list.rs" /* Initialize a list */ // Without initial values let nums1: Vec = Vec::new(); // With initial values let nums: Vec = vec![1, 3, 2, 5, 4]; ``` === "C" ```c title="list.c" // C does not provide built-in dynamic arrays ``` === "Kotlin" ```kotlin title="list.kt" /* Initialize a list */ // Without initial values var nums1 = listOf() // With initial values var numbers = arrayOf(1, 3, 2, 5, 4) var nums = numbers.toMutableList() ``` === "Ruby" ```ruby title="list.rb" # Initialize a list # Without initial values nums1 = [] # With initial values nums = [1, 3, 2, 5, 4] ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20%23%20%E6%97%A0%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums1%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Access Elements Since a list is essentially an array, we can access and update elements in $O(1)$ time complexity, which is very efficient. === "Python" ```python title="list.py" # Access an element num: int = nums[1] # Access element at index 1 # Update an element nums[1] = 0 # Update element at index 1 to 0 ``` === "C++" ```cpp title="list.cpp" /* Access an element */ int num = nums[1]; // Access element at index 1 /* Update an element */ nums[1] = 0; // Update element at index 1 to 0 ``` === "Java" ```java title="list.java" /* Access an element */ int num = nums.get(1); // Access element at index 1 /* Update an element */ nums.set(1, 0); // Update element at index 1 to 0 ``` === "C#" ```csharp title="list.cs" /* Access an element */ int num = nums[1]; // Access element at index 1 /* Update an element */ nums[1] = 0; // Update element at index 1 to 0 ``` === "Go" ```go title="list_test.go" /* Access an element */ num := nums[1] // Access element at index 1 /* Update an element */ nums[1] = 0 // Update element at index 1 to 0 ``` === "Swift" ```swift title="list.swift" /* Access an element */ let num = nums[1] // Access element at index 1 /* Update an element */ nums[1] = 0 // Update element at index 1 to 0 ``` === "JS" ```javascript title="list.js" /* Access an element */ const num = nums[1]; // Access element at index 1 /* Update an element */ nums[1] = 0; // Update element at index 1 to 0 ``` === "TS" ```typescript title="list.ts" /* Access an element */ const num: number = nums[1]; // Access element at index 1 /* Update an element */ nums[1] = 0; // Update element at index 1 to 0 ``` === "Dart" ```dart title="list.dart" /* Access an element */ int num = nums[1]; // Access element at index 1 /* Update an element */ nums[1] = 0; // Update element at index 1 to 0 ``` === "Rust" ```rust title="list.rs" /* Access an element */ let num: i32 = nums[1]; // Access element at index 1 /* Update an element */ nums[1] = 0; // Update element at index 1 to 0 ``` === "C" ```c title="list.c" // C does not provide built-in dynamic arrays ``` === "Kotlin" ```kotlin title="list.kt" /* Access an element */ val num = nums[1] // Access element at index 1 /* Update an element */ nums[1] = 0 // Update element at index 1 to 0 ``` === "Ruby" ```ruby title="list.rb" # Access an element num = nums[1] # Access element at index 1 # Update an element nums[1] = 0 # Update element at index 1 to 0 ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums%5B1%5D%20%20%23%20%E8%AE%BF%E9%97%AE%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5B1%5D%20%3D%200%20%20%20%20%23%20%E5%B0%86%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%E6%9B%B4%E6%96%B0%E4%B8%BA%200&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Insert and Delete Elements Compared to arrays, lists can freely add and delete elements. Adding an element at the end of a list has a time complexity of $O(1)$, but inserting and deleting elements still have the same efficiency as arrays, with a time complexity of $O(n)$. === "Python" ```python title="list.py" # Clear the list nums.clear() # Add elements at the end nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) # Insert an element in the middle nums.insert(3, 6) # Insert number 6 at index 3 # Delete an element nums.pop(3) # Delete element at index 3 ``` === "C++" ```cpp title="list.cpp" /* Clear the list */ nums.clear(); /* Add elements at the end */ nums.push_back(1); nums.push_back(3); nums.push_back(2); nums.push_back(5); nums.push_back(4); /* Insert an element in the middle */ nums.insert(nums.begin() + 3, 6); // Insert number 6 at index 3 /* Delete an element */ nums.erase(nums.begin() + 3); // Delete element at index 3 ``` === "Java" ```java title="list.java" /* Clear the list */ nums.clear(); /* Add elements at the end */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); /* Insert an element in the middle */ nums.add(3, 6); // Insert number 6 at index 3 /* Delete an element */ nums.remove(3); // Delete element at index 3 ``` === "C#" ```csharp title="list.cs" /* Clear the list */ nums.Clear(); /* Add elements at the end */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); /* Insert an element in the middle */ nums.Insert(3, 6); // Insert number 6 at index 3 /* Delete an element */ nums.RemoveAt(3); // Delete element at index 3 ``` === "Go" ```go title="list_test.go" /* Clear the list */ nums = nil /* Add elements at the end */ nums = append(nums, 1) nums = append(nums, 3) nums = append(nums, 2) nums = append(nums, 5) nums = append(nums, 4) /* Insert an element in the middle */ nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // Insert number 6 at index 3 /* Delete an element */ nums = append(nums[:3], nums[4:]...) // Delete element at index 3 ``` === "Swift" ```swift title="list.swift" /* Clear the list */ nums.removeAll() /* Add elements at the end */ nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) /* Insert an element in the middle */ nums.insert(6, at: 3) // Insert number 6 at index 3 /* Delete an element */ nums.remove(at: 3) // Delete element at index 3 ``` === "JS" ```javascript title="list.js" /* Clear the list */ nums.length = 0; /* Add elements at the end */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); /* Insert an element in the middle */ nums.splice(3, 0, 6); // Insert number 6 at index 3 /* Delete an element */ nums.splice(3, 1); // Delete element at index 3 ``` === "TS" ```typescript title="list.ts" /* Clear the list */ nums.length = 0; /* Add elements at the end */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); /* Insert an element in the middle */ nums.splice(3, 0, 6); // Insert number 6 at index 3 /* Delete an element */ nums.splice(3, 1); // Delete element at index 3 ``` === "Dart" ```dart title="list.dart" /* Clear the list */ nums.clear(); /* Add elements at the end */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); /* Insert an element in the middle */ nums.insert(3, 6); // Insert number 6 at index 3 /* Delete an element */ nums.removeAt(3); // Delete element at index 3 ``` === "Rust" ```rust title="list.rs" /* Clear the list */ nums.clear(); /* Add elements at the end */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); /* Insert an element in the middle */ nums.insert(3, 6); // Insert number 6 at index 3 /* Delete an element */ nums.remove(3); // Delete element at index 3 ``` === "C" ```c title="list.c" // C does not provide built-in dynamic arrays ``` === "Kotlin" ```kotlin title="list.kt" /* Clear the list */ nums.clear(); /* Add elements at the end */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); /* Insert an element in the middle */ nums.add(3, 6); // Insert number 6 at index 3 /* Delete an element */ nums.remove(3); // Delete element at index 3 ``` === "Ruby" ```ruby title="list.rb" # Clear the list nums.clear # Add elements at the end nums << 1 nums << 3 nums << 2 nums << 5 nums << 4 # Insert an element in the middle nums.insert(3, 6) # Insert number 6 at index 3 # Delete an element nums.delete_at(3) # Delete element at index 3 ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B8%85%E7%A9%BA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.clear%28%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.append%281%29%0A%20%20%20%20nums.append%283%29%0A%20%20%20%20nums.append%282%29%0A%20%20%20%20nums.append%285%29%0A%20%20%20%20nums.append%284%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%283,%206%29%20%20%23%20%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E6%8F%92%E5%85%A5%E6%95%B0%E5%AD%97%206%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.pop%283%29%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Traverse a List Like arrays, lists can be traversed by index or by directly iterating through elements. === "Python" ```python title="list.py" # Traverse the list by index count = 0 for i in range(len(nums)): count += nums[i] # Traverse list elements directly for num in nums: count += num ``` === "C++" ```cpp title="list.cpp" /* Traverse the list by index */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums[i]; } /* Traverse list elements directly */ count = 0; for (int num : nums) { count += num; } ``` === "Java" ```java title="list.java" /* Traverse the list by index */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums.get(i); } /* Traverse list elements directly */ for (int num : nums) { count += num; } ``` === "C#" ```csharp title="list.cs" /* Traverse the list by index */ int count = 0; for (int i = 0; i < nums.Count; i++) { count += nums[i]; } /* Traverse list elements directly */ count = 0; foreach (int num in nums) { count += num; } ``` === "Go" ```go title="list_test.go" /* Traverse the list by index */ count := 0 for i := 0; i < len(nums); i++ { count += nums[i] } /* Traverse list elements directly */ count = 0 for _, num := range nums { count += num } ``` === "Swift" ```swift title="list.swift" /* Traverse the list by index */ var count = 0 for i in nums.indices { count += nums[i] } /* Traverse list elements directly */ count = 0 for num in nums { count += num } ``` === "JS" ```javascript title="list.js" /* Traverse the list by index */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* Traverse list elements directly */ count = 0; for (const num of nums) { count += num; } ``` === "TS" ```typescript title="list.ts" /* Traverse the list by index */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* Traverse list elements directly */ count = 0; for (const num of nums) { count += num; } ``` === "Dart" ```dart title="list.dart" /* Traverse the list by index */ int count = 0; for (var i = 0; i < nums.length; i++) { count += nums[i]; } /* Traverse list elements directly */ count = 0; for (var num in nums) { count += num; } ``` === "Rust" ```rust title="list.rs" // Traverse the list by index let mut _count = 0; for i in 0..nums.len() { _count += nums[i]; } // Traverse list elements directly _count = 0; for num in &nums { _count += num; } ``` === "C" ```c title="list.c" // C does not provide built-in dynamic arrays ``` === "Kotlin" ```kotlin title="list.kt" /* Traverse the list by index */ var count = 0 for (i in nums.indices) { count += nums[i] } /* Traverse list elements directly */ for (num in nums) { count += num } ``` === "Ruby" ```ruby title="list.rb" # Traverse the list by index count = 0 for i in 0...nums.length count += nums[i] end # Traverse list elements directly count = 0 for num in nums count += num end ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E7%B4%A2%E5%BC%95%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Concatenate Lists Given a new list `nums1`, we can concatenate it to the end of the original list. === "Python" ```python title="list.py" # Concatenate two lists nums1: list[int] = [6, 8, 7, 10, 9] nums += nums1 # Concatenate list nums1 to the end of nums ``` === "C++" ```cpp title="list.cpp" /* Concatenate two lists */ vector nums1 = { 6, 8, 7, 10, 9 }; // Concatenate list nums1 to the end of nums nums.insert(nums.end(), nums1.begin(), nums1.end()); ``` === "Java" ```java title="list.java" /* Concatenate two lists */ List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); nums.addAll(nums1); // Concatenate list nums1 to the end of nums ``` === "C#" ```csharp title="list.cs" /* Concatenate two lists */ List nums1 = [6, 8, 7, 10, 9]; nums.AddRange(nums1); // Concatenate list nums1 to the end of nums ``` === "Go" ```go title="list_test.go" /* Concatenate two lists */ nums1 := []int{6, 8, 7, 10, 9} nums = append(nums, nums1...) // Concatenate list nums1 to the end of nums ``` === "Swift" ```swift title="list.swift" /* Concatenate two lists */ let nums1 = [6, 8, 7, 10, 9] nums.append(contentsOf: nums1) // Concatenate list nums1 to the end of nums ``` === "JS" ```javascript title="list.js" /* Concatenate two lists */ const nums1 = [6, 8, 7, 10, 9]; nums.push(...nums1); // Concatenate list nums1 to the end of nums ``` === "TS" ```typescript title="list.ts" /* Concatenate two lists */ const nums1: number[] = [6, 8, 7, 10, 9]; nums.push(...nums1); // Concatenate list nums1 to the end of nums ``` === "Dart" ```dart title="list.dart" /* Concatenate two lists */ List nums1 = [6, 8, 7, 10, 9]; nums.addAll(nums1); // Concatenate list nums1 to the end of nums ``` === "Rust" ```rust title="list.rs" /* Concatenate two lists */ let nums1: Vec = vec![6, 8, 7, 10, 9]; nums.extend(nums1); ``` === "C" ```c title="list.c" // C does not provide built-in dynamic arrays ``` === "Kotlin" ```kotlin title="list.kt" /* Concatenate two lists */ val nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList() nums.addAll(nums1) // Concatenate list nums1 to the end of nums ``` === "Ruby" ```ruby title="list.rb" # Concatenate two lists nums1 = [6, 8, 7, 10, 9] nums += nums1 ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8B%BC%E6%8E%A5%E4%B8%A4%E4%B8%AA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums1%20%3D%20%5B6,%208,%207,%2010,%209%5D%0A%20%20%20%20nums%20%2B%3D%20nums1%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%20nums1%20%E6%8B%BC%E6%8E%A5%E5%88%B0%20nums%20%E4%B9%8B%E5%90%8E&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Sort a List After sorting a list, we can use "binary search" and "two-pointer" algorithms, which are frequently tested in array algorithm problems. === "Python" ```python title="list.py" # Sort a list nums.sort() # After sorting, list elements are arranged from smallest to largest ``` === "C++" ```cpp title="list.cpp" /* Sort a list */ sort(nums.begin(), nums.end()); // After sorting, list elements are arranged from smallest to largest ``` === "Java" ```java title="list.java" /* Sort a list */ Collections.sort(nums); // After sorting, list elements are arranged from smallest to largest ``` === "C#" ```csharp title="list.cs" /* Sort a list */ nums.Sort(); // After sorting, list elements are arranged from smallest to largest ``` === "Go" ```go title="list_test.go" /* Sort a list */ sort.Ints(nums) // After sorting, list elements are arranged from smallest to largest ``` === "Swift" ```swift title="list.swift" /* Sort a list */ nums.sort() // After sorting, list elements are arranged from smallest to largest ``` === "JS" ```javascript title="list.js" /* Sort a list */ nums.sort((a, b) => a - b); // After sorting, list elements are arranged from smallest to largest ``` === "TS" ```typescript title="list.ts" /* Sort a list */ nums.sort((a, b) => a - b); // After sorting, list elements are arranged from smallest to largest ``` === "Dart" ```dart title="list.dart" /* Sort a list */ nums.sort(); // After sorting, list elements are arranged from smallest to largest ``` === "Rust" ```rust title="list.rs" /* Sort a list */ nums.sort(); // After sorting, list elements are arranged from smallest to largest ``` === "C" ```c title="list.c" // C does not provide built-in dynamic arrays ``` === "Kotlin" ```kotlin title="list.kt" /* Sort a list */ nums.sort() // After sorting, list elements are arranged from smallest to largest ``` === "Ruby" ```ruby title="list.rb" # Sort a list nums = nums.sort { |a, b| a <=> b } # After sorting, list elements are arranged from smallest to largest ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8E%92%E5%BA%8F%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E6%8E%92%E5%BA%8F%E5%90%8E%EF%BC%8C%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E4%BB%8E%E5%B0%8F%E5%88%B0%E5%A4%A7%E6%8E%92%E5%88%97&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## List Implementation Many programming languages have built-in lists, such as Java, C++, and Python. Their implementations are quite complex, and the parameters are carefully considered, such as initial capacity, expansion multiples, and so on. Interested readers can consult the source code to learn more. To deepen our understanding of how lists work, we attempt to implement a simple list with three key design considerations: - **Initial capacity**: Select a reasonable initial capacity for the underlying array. In this example, we choose 10 as the initial capacity. - **Size tracking**: Declare a variable `size` to record the current number of elements in the list and update it in real-time as elements are inserted and deleted. Based on this variable, we can locate the end of the list and determine whether expansion is needed. - **Expansion mechanism**: When the list capacity is full upon inserting an element, we need to expand. We create a larger array based on the expansion multiple and then move all elements from the current array to the new array in order. In this example, we specify that the array should be expanded to 2 times its previous size each time. ```src [file]{my_list}-[class]{my_list}-[func]{} ``` ================================================ FILE: en/docs/chapter_array_and_linkedlist/ram_and_cache.md ================================================ # Random-Access Memory and Cache * In the first two sections of this chapter, we explored arrays and linked lists, two fundamental and important data structures that represent "contiguous storage" and "distributed storage" as two physical structures, respectively. In fact, **physical structure largely determines the efficiency with which programs utilize memory and cache**, which in turn affects the overall performance of algorithmic programs. ## Computer Storage Devices Computers include three types of storage devices: hard disk, random-access memory (RAM), and cache memory. The following table shows their different roles and performance characteristics in a computer system.

Table   Computer Storage Devices

| | Hard Disk | RAM | Cache | | -------------- | ------------------------------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------- | | Purpose | Long-term storage of data, including operating systems, programs, and files | Temporary storage of currently running programs and data being processed | Storage of frequently accessed data and instructions to reduce CPU's accesses to memory | | Volatility | Data is not lost after power-off | Data is lost after power-off | Data is lost after power-off | | Capacity | Large, on the order of terabytes (TB) | Small, on the order of gigabytes (GB) | Very small, on the order of megabytes (MB) | | Speed | Slow, hundreds to thousands of MB/s | Fast, tens of GB/s | Very fast, tens to hundreds of GB/s | | Cost (USD/GB) | Inexpensive, fractions of a dollar to a few dollars per GB | Expensive, tens to hundreds of dollars per GB | Very expensive, priced as part of the CPU package | We can imagine the computer storage system as a pyramid structure as shown in the diagram below. Storage devices closer to the top of the pyramid are faster, have smaller capacity, and are more expensive. This multi-layered design is not by accident, but rather the result of careful consideration by computer scientists and engineers. - **Hard disk cannot be easily replaced by RAM**. First, data in memory is lost after power-off, making it unsuitable for long-term data storage. Second, memory is tens of times more expensive than hard disk, which makes it difficult to popularize in the consumer market. - **Cache cannot simultaneously achieve large capacity and high speed**. As the capacity of L1, L2, and L3 caches increases, their physical size becomes larger, and the physical distance between them and the CPU core increases, resulting in longer data transmission time and higher element access latency. With current technology, the multi-layered cache structure represents the best balance point between capacity, speed, and cost. ![Computer Storage System](ram_and_cache.assets/storage_pyramid.png) !!! tip The storage hierarchy of computers embodies a delicate balance among speed, capacity, and cost. In fact, such trade-offs are common across all industrial fields, requiring us to find the optimal balance point between different advantages and constraints. In summary, **hard disk is used for long-term storage of large amounts of data, RAM is used for temporary storage of data being processed during program execution, and cache is used for storage of frequently accessed data and instructions**, to improve program execution efficiency. The three work together to ensure efficient operation of the computer system. As shown in the diagram below, during program execution, data is read from the hard disk into RAM for CPU computation. Cache can be viewed as part of the CPU, **it intelligently loads data from RAM**, providing the CPU with high-speed data reading, thereby significantly improving program execution efficiency and reducing reliance on slower RAM. ![Data Flow Among Hard Disk, RAM, and Cache](ram_and_cache.assets/computer_storage_devices.png) ## Memory Efficiency of Data Structures In terms of memory space utilization, arrays and linked lists each have advantages and limitations. On one hand, **memory is limited, and the same memory cannot be shared by multiple programs**, so we hope data structures can utilize space as efficiently as possible. Array elements are tightly packed and do not require additional space to store references (pointers) between linked list nodes, thus having higher space efficiency. However, arrays need to allocate sufficient contiguous memory space at once, which may lead to memory waste, and array expansion requires additional time and space costs. In comparison, linked lists perform dynamic memory allocation and deallocation on a "node" basis, providing greater flexibility. On the other hand, during program execution, **as memory is repeatedly allocated and freed, the degree of fragmentation of free memory becomes increasingly severe**, leading to reduced memory utilization efficiency. Arrays, due to their contiguous storage approach, are relatively less prone to memory fragmentation. Conversely, linked list elements are distributed in storage, and frequent insertion and deletion operations are more likely to cause memory fragmentation. ## Cache Efficiency of Data Structures Although cache has much smaller space capacity than memory, it is much faster than memory and plays a crucial role in program execution speed. Since cache capacity is limited and can only store a small portion of frequently accessed data, when the CPU attempts to access data that is not in the cache, a cache miss occurs, and the CPU must load the required data from the slower memory. Clearly, **the fewer "cache misses," the higher the efficiency of CPU data reads and writes**, and the better the program performance. We call the proportion of data that the CPU successfully obtains from the cache the cache hit rate, a metric typically used to measure cache efficiency. To achieve the highest efficiency possible, cache employs the following data loading mechanisms. - **Cache lines**: The cache does not store and load data on a byte-by-byte basis, but rather as cache lines. Compared to byte-by-byte transmission, cache line transmission is more efficient. - **Prefetching mechanism**: The processor attempts to predict data access patterns (e.g., sequential access, fixed-stride jumping access, etc.) and loads data into the cache according to specific patterns, thereby improving hit rate. - **Spatial locality**: If a piece of data is accessed, nearby data may also be accessed in the near future. Therefore, when the cache loads a particular piece of data, it also loads nearby data to improve hit rate. - **Temporal locality**: If a piece of data is accessed, it is likely to be accessed again in the near future. Cache leverages this principle by retaining recently accessed data to improve hit rate. In fact, **arrays and linked lists have different efficiencies in utilizing cache**, manifested in the following aspects. - **Space occupied**: Linked list elements occupy more space than array elements, resulting in fewer effective data in the cache. - **Cache lines**: Linked list data are scattered throughout memory, while cache loads "by lines," so the proportion of invalid data loaded is higher. - **Prefetching mechanism**: Arrays have more "predictable" data access patterns than linked lists, making it easier for the system to guess which data will be loaded next. - **Spatial locality**: Arrays are stored in centralized memory space, so data near loaded data is more likely to be accessed soon. Overall, **arrays have higher cache hit rates, thus they usually outperform linked lists in operation efficiency**. This makes data structures implemented based on arrays more popular when solving algorithmic problems. It is important to note that **high cache efficiency does not mean arrays are superior to linked lists in all cases**. In practical applications, which data structure to choose should be determined based on specific requirements. For example, both arrays and linked lists can implement the "stack" data structure (which will be discussed in detail in the next chapter), but they are suitable for different scenarios. - When solving algorithm problems, we tend to prefer stack implementations based on arrays, because they provide higher operation efficiency and the ability of random access, at the cost of needing to pre-allocate a certain amount of memory space for the array. - If the data volume is very large, the dynamic nature is high, and the expected size of the stack is difficult to estimate, then a stack implementation based on linked lists is more suitable. Linked lists can distribute large amounts of data across different parts of memory and avoid the additional overhead produced by array expansion. ================================================ FILE: en/docs/chapter_array_and_linkedlist/summary.md ================================================ # Summary ### Key Review - Arrays and linked lists are two fundamental data structures, representing two different ways data can be stored in computer memory: contiguous memory storage and scattered memory storage. The characteristics of the two complement each other. - Arrays support random access and use less memory; however, inserting and deleting elements is inefficient, and the length is immutable after initialization. - Linked lists achieve efficient insertion and deletion of nodes by modifying references (pointers), and can flexibly adjust length; however, node access is inefficient and memory consumption is higher. Common linked list types include singly linked lists, circular linked lists, and doubly linked lists. - A list is an ordered collection of elements that supports insertion, deletion, search, and modification, typically implemented based on dynamic arrays. It retains the advantages of arrays while allowing flexible adjustment of length. - The emergence of lists has greatly improved the practicality of arrays, but may result in some wasted memory space. - During program execution, data is primarily stored in memory. Arrays provide higher memory space efficiency, while linked lists offer greater flexibility in memory usage. - Caches provide fast data access to the CPU through mechanisms such as cache lines, prefetching, and spatial and temporal locality, significantly improving program execution efficiency. - Because arrays have higher cache hit rates, they are generally more efficient than linked lists. When choosing a data structure, appropriate selection should be made based on specific requirements and scenarios. ### Q & A **Q**: Does storing an array on the stack versus on the heap affect time efficiency and space efficiency? Arrays stored on the stack and on the heap are both stored in contiguous memory space, so data operation efficiency is basically the same. However, the stack and heap have their own characteristics, leading to the following differences. 1. Allocation and deallocation efficiency: The stack is a relatively small piece of memory, with allocation automatically handled by the compiler; the heap is relatively larger and can be dynamically allocated in code, more prone to fragmentation. Therefore, allocation and deallocation operations on the heap are usually slower than on the stack. 2. Size limitations: Stack memory is relatively small, and the heap size is generally limited by available memory. Therefore, the heap is more suitable for storing large arrays. 3. Flexibility: The size of an array on the stack must be determined at compile time, while the size of an array on the heap can be determined dynamically at runtime. **Q**: Why do arrays require elements of the same type, while linked lists do not emphasize this requirement? Linked lists are composed of nodes, with nodes connected through references (pointers), and each node can store different types of data, such as `int`, `double`, `string`, `object`, etc. In contrast, array elements must be of the same type, so that the corresponding element position can be obtained by calculating the offset. For example, if an array contains both `int` and `long` types, with individual elements occupying 4 bytes and 8 bytes respectively, then the following formula cannot be used to calculate the offset, because the array contains two different "element lengths". ```shell # Element Memory Address = Array Memory Address (first Element Memory address) + Element Length * Element Index ``` **Q**: After deleting node `P`, do we need to set `P.next` to `None`? It is not necessary to modify `P.next`. From the perspective of the linked list, traversing from the head node to the tail node will no longer encounter `P`. This means that node `P` has been removed from the linked list, and it doesn't matter where node `P` points to at this time—it won't affect the linked list. From a data structures and algorithms perspective (problem-solving), not disconnecting the pointer doesn't matter as long as the program logic is correct. From the perspective of standard libraries, disconnecting is safer and the logic is clearer. If not disconnected, assuming the deleted node is not properly reclaimed, it may affect the memory reclamation of its successor nodes. **Q**: In a linked list, the time complexity of insertion and deletion operations is $O(1)$. However, both insertion and deletion require $O(n)$ time to find the element; why isn't the time complexity $O(n)$? If the element is first found and then deleted, the time complexity is indeed $O(n)$. However, the advantage of $O(1)$ insertion and deletion in linked lists can be demonstrated in other applications. For example, a deque is well-suited for linked list implementation, where we maintain pointer variables always pointing to the head and tail nodes, with each insertion and deletion operation being $O(1)$. **Q**: In the diagram "Linked List Definition and Storage Methods", does the light blue pointer node occupy a single memory address, or does it share equally with the node value? This diagram is a qualitative representation; a quantitative representation requires analysis based on the specific situation. - Different types of node values occupy different amounts of space, such as `int`, `long`, `double`, and instance objects, etc. - The amount of memory space occupied by pointer variables depends on the operating system and compilation environment used, usually 8 bytes or 4 bytes. **Q**: Is appending an element at the end of a list always $O(1)$? If appending an element exceeds the list length, the list must first be expanded before adding. The system allocates a new block of memory and moves all elements from the original list to it, in which case the time complexity becomes $O(n)$. **Q**: "The emergence of lists has greatly improved the practicality of arrays, but may result in some wasted memory space"—does this space waste refer to the memory occupied by additional variables such as capacity, length, and expansion factor? This space waste mainly has two aspects: on one hand, lists typically set an initial length, which we may not need to fully utilize; on the other hand, to prevent frequent expansion, expansion generally multiplies by a coefficient, such as $\times 1.5$. As a result, there will be many empty positions that we typically cannot completely fill. **Q**: In Python, after initializing `n = [1, 2, 3]`, the addresses of these 3 elements are contiguous, but initializing `m = [2, 1, 3]` reveals that each element's id is not continuous; rather, they are the same as those in `n`. Since the addresses of these elements are not contiguous, is `m` still an array? If we replace list elements with linked list nodes `n = [n1, n2, n3, n4, n5]`, usually these 5 node objects are also scattered throughout memory. However, given a list index, we can still obtain the node memory address in $O(1)$ time, thereby accessing the corresponding node. This is because the array stores references to nodes, not the nodes themselves. Unlike many languages, numbers in Python are wrapped as objects, and lists store not the numbers themselves, but references to the numbers. Therefore, we find that the same numbers in two arrays have the same id, and the memory addresses of these numbers need not be contiguous. **Q**: C++ STL has `std::list` which has already implemented a doubly linked list, but it seems that some algorithm books don't use it directly. Is there a limitation? On one hand, we often prefer to use arrays for implementing algorithms and only use linked lists when necessary, mainly for two reasons. - Space overhead: Since each element requires two additional pointers (one for the previous element and one for the next element), `std::list` typically consumes more space than `std::vector`. - Cache unfriendliness: Since data is not stored contiguously, `std::list` has lower cache utilization. In general, `std::vector` has better performance. On the other hand, cases where linked lists are necessary mainly involve binary trees and graphs. Stacks and queues usually use the `stack` and `queue` provided by the programming language, rather than linked lists. **Q**: Does the operation `res = [[0]] * n` create a 2D list where each `[0]` is independent? No, they are not independent. In this 2D list, all the `[0]` are actually references to the same object. If we modify one element, we will find that all corresponding elements change accordingly. If we want each `[0]` in the 2D list to be independent, we can use `res = [[0] for _ in range(n)]` to achieve this. The principle of this approach is to initialize $n$ independent `[0]` list objects. **Q**: Does the operation `res = [0] * n` create a list where each integer 0 is independent? In this list, all integer 0s are references to the same object. This is because Python uses a caching mechanism for small integers (typically -5 to 256) to maximize object reuse and improve performance. Although they point to the same object, we can still independently modify each element in the list. This is because Python integers are "immutable objects". When we modify an element, we are actually switching to a reference of another object, rather than changing the original object itself. However, when list elements are "mutable objects" (such as lists, dictionaries, or class instances), modifying an element directly changes the object itself, and all elements referencing that object will have the same change. ================================================ FILE: en/docs/chapter_backtracking/backtracking_algorithm.md ================================================ # Backtracking Algorithm The backtracking algorithm is a method for solving problems through exhaustive search. Its core idea is to start from an initial state and exhaustively search all possible solutions. When a correct solution is found, it is recorded. This process continues until a solution is found or all possible choices have been tried without finding a solution. The backtracking algorithm typically employs "depth-first search" to traverse the solution space. In the "Binary Tree" chapter, we mentioned that preorder, inorder, and postorder traversals all belong to depth-first search. Next, we will construct a backtracking problem using preorder traversal to progressively understand how the backtracking algorithm works. !!! question "Example 1" Given a binary tree, search and record all nodes with value $7$, and return a list of these nodes. For this problem, we perform a preorder traversal of the tree and check whether the current node's value is $7$. If it is, we add the node to the result list `res`. The relevant implementation is shown in the following figure and code: ```src [file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order} ``` ![Search for nodes in preorder traversal](backtracking_algorithm.assets/preorder_find_nodes.png) ## Attempt and Backtrack **The reason it is called a backtracking algorithm is that it employs "attempt" and "backtrack" strategies when searching the solution space**. When the algorithm encounters a state where it cannot continue forward or cannot find a solution that satisfies the constraints, it will undo the previous choice, return to a previous state, and try other possible choices. For Example 1, visiting each node represents an "attempt", while skipping over a leaf node or a function `return` from the parent node represents a "backtrack". It is worth noting that **backtracking is not limited to function returns alone**. To illustrate this, let's extend Example 1 slightly. !!! question "Example 2" In a binary tree, search all nodes with value $7$, **and return the paths from the root node to these nodes**. Based on the code from Example 1, we need to use a list `path` to record the visited node path. When we reach a node with value $7$, we copy `path` and add it to the result list `res`. After traversal is complete, `res` contains all the solutions. The code is as follows: ```src [file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order} ``` In each "attempt", we record the path by adding the current node to `path`; before "backtracking", we need to remove the node from `path`, **to restore the state before this attempt**. Observing the process shown in the following figure, **we can understand attempt and backtrack as "advance" and "undo"**, two operations that are the reverse of each other. === "<1>" ![Attempt and backtrack](backtracking_algorithm.assets/preorder_find_paths_step1.png) === "<2>" ![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png) === "<3>" ![preorder_find_paths_step3](backtracking_algorithm.assets/preorder_find_paths_step3.png) === "<4>" ![preorder_find_paths_step4](backtracking_algorithm.assets/preorder_find_paths_step4.png) === "<5>" ![preorder_find_paths_step5](backtracking_algorithm.assets/preorder_find_paths_step5.png) === "<6>" ![preorder_find_paths_step6](backtracking_algorithm.assets/preorder_find_paths_step6.png) === "<7>" ![preorder_find_paths_step7](backtracking_algorithm.assets/preorder_find_paths_step7.png) === "<8>" ![preorder_find_paths_step8](backtracking_algorithm.assets/preorder_find_paths_step8.png) === "<9>" ![preorder_find_paths_step9](backtracking_algorithm.assets/preorder_find_paths_step9.png) === "<10>" ![preorder_find_paths_step10](backtracking_algorithm.assets/preorder_find_paths_step10.png) === "<11>" ![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png) ## Pruning Complex backtracking problems usually contain one or more constraints. **Constraints can typically be used for "pruning"**. !!! question "Example 3" In a binary tree, search all nodes with value $7$ and return the paths from the root node to these nodes, **but require that the paths do not contain nodes with value $3$**. To satisfy the above constraints, **we need to add pruning operations**: during the search process, if we encounter a node with value $3$, we return early and do not continue searching. The code is as follows: ```src [file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order} ``` "Pruning" is a vivid term. As shown in the following figure, during the search process, **we "prune" search branches that do not satisfy the constraints**, avoiding many meaningless attempts and thus improving search efficiency. ![Pruning according to constraints](backtracking_algorithm.assets/preorder_find_constrained_paths.png) ## Framework Code Next, we attempt to extract the main framework of backtracking's "attempt, backtrack, and pruning", to improve code generality. In the following framework code, `state` represents the current state of the problem, and `choices` represents the choices available in the current state: === "Python" ```python title="" def backtrack(state: State, choices: list[choice], res: list[state]): """Backtracking algorithm framework""" # Check if it is a solution if is_solution(state): # Record the solution record_solution(state, res) # Stop searching return # Traverse all choices for choice in choices: # Pruning: check if the choice is valid if is_valid(state, choice): # Attempt: make a choice and update the state make_choice(state, choice) backtrack(state, choices, res) # Backtrack: undo the choice and restore to the previous state undo_choice(state, choice) ``` === "C++" ```cpp title="" /* Backtracking algorithm framework */ void backtrack(State *state, vector &choices, vector &res) { // Check if it is a solution if (isSolution(state)) { // Record the solution recordSolution(state, res); // Stop searching return; } // Traverse all choices for (Choice choice : choices) { // Pruning: check if the choice is valid if (isValid(state, choice)) { // Attempt: make a choice and update the state makeChoice(state, choice); backtrack(state, choices, res); // Backtrack: undo the choice and restore to the previous state undoChoice(state, choice); } } } ``` === "Java" ```java title="" /* Backtracking algorithm framework */ void backtrack(State state, List choices, List res) { // Check if it is a solution if (isSolution(state)) { // Record the solution recordSolution(state, res); // Stop searching return; } // Traverse all choices for (Choice choice : choices) { // Pruning: check if the choice is valid if (isValid(state, choice)) { // Attempt: make a choice and update the state makeChoice(state, choice); backtrack(state, choices, res); // Backtrack: undo the choice and restore to the previous state undoChoice(state, choice); } } } ``` === "C#" ```csharp title="" /* Backtracking algorithm framework */ void Backtrack(State state, List choices, List res) { // Check if it is a solution if (IsSolution(state)) { // Record the solution RecordSolution(state, res); // Stop searching return; } // Traverse all choices foreach (Choice choice in choices) { // Pruning: check if the choice is valid if (IsValid(state, choice)) { // Attempt: make a choice and update the state MakeChoice(state, choice); Backtrack(state, choices, res); // Backtrack: undo the choice and restore to the previous state UndoChoice(state, choice); } } } ``` === "Go" ```go title="" /* Backtracking algorithm framework */ func backtrack(state *State, choices []Choice, res *[]State) { // Check if it is a solution if isSolution(state) { // Record the solution recordSolution(state, res) // Stop searching return } // Traverse all choices for _, choice := range choices { // Pruning: check if the choice is valid if isValid(state, choice) { // Attempt: make a choice and update the state makeChoice(state, choice) backtrack(state, choices, res) // Backtrack: undo the choice and restore to the previous state undoChoice(state, choice) } } } ``` === "Swift" ```swift title="" /* Backtracking algorithm framework */ func backtrack(state: inout State, choices: [Choice], res: inout [State]) { // Check if it is a solution if isSolution(state: state) { // Record the solution recordSolution(state: state, res: &res) // Stop searching return } // Traverse all choices for choice in choices { // Pruning: check if the choice is valid if isValid(state: state, choice: choice) { // Attempt: make a choice and update the state makeChoice(state: &state, choice: choice) backtrack(state: &state, choices: choices, res: &res) // Backtrack: undo the choice and restore to the previous state undoChoice(state: &state, choice: choice) } } } ``` === "JS" ```javascript title="" /* Backtracking algorithm framework */ function backtrack(state, choices, res) { // Check if it is a solution if (isSolution(state)) { // Record the solution recordSolution(state, res); // Stop searching return; } // Traverse all choices for (let choice of choices) { // Pruning: check if the choice is valid if (isValid(state, choice)) { // Attempt: make a choice and update the state makeChoice(state, choice); backtrack(state, choices, res); // Backtrack: undo the choice and restore to the previous state undoChoice(state, choice); } } } ``` === "TS" ```typescript title="" /* Backtracking algorithm framework */ function backtrack(state: State, choices: Choice[], res: State[]): void { // Check if it is a solution if (isSolution(state)) { // Record the solution recordSolution(state, res); // Stop searching return; } // Traverse all choices for (let choice of choices) { // Pruning: check if the choice is valid if (isValid(state, choice)) { // Attempt: make a choice and update the state makeChoice(state, choice); backtrack(state, choices, res); // Backtrack: undo the choice and restore to the previous state undoChoice(state, choice); } } } ``` === "Dart" ```dart title="" /* Backtracking algorithm framework */ void backtrack(State state, List, List res) { // Check if it is a solution if (isSolution(state)) { // Record the solution recordSolution(state, res); // Stop searching return; } // Traverse all choices for (Choice choice in choices) { // Pruning: check if the choice is valid if (isValid(state, choice)) { // Attempt: make a choice and update the state makeChoice(state, choice); backtrack(state, choices, res); // Backtrack: undo the choice and restore to the previous state undoChoice(state, choice); } } } ``` === "Rust" ```rust title="" /* Backtracking algorithm framework */ fn backtrack(state: &mut State, choices: &Vec, res: &mut Vec) { // Check if it is a solution if is_solution(state) { // Record the solution record_solution(state, res); // Stop searching return; } // Traverse all choices for choice in choices { // Pruning: check if the choice is valid if is_valid(state, choice) { // Attempt: make a choice and update the state make_choice(state, choice); backtrack(state, choices, res); // Backtrack: undo the choice and restore to the previous state undo_choice(state, choice); } } } ``` === "C" ```c title="" /* Backtracking algorithm framework */ void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) { // Check if it is a solution if (isSolution(state)) { // Record the solution recordSolution(state, res, numRes); // Stop searching return; } // Traverse all choices for (int i = 0; i < numChoices; i++) { // Pruning: check if the choice is valid if (isValid(state, &choices[i])) { // Attempt: make a choice and update the state makeChoice(state, &choices[i]); backtrack(state, choices, numChoices, res, numRes); // Backtrack: undo the choice and restore to the previous state undoChoice(state, &choices[i]); } } } ``` === "Kotlin" ```kotlin title="" /* Backtracking algorithm framework */ fun backtrack(state: State?, choices: List, res: List?) { // Check if it is a solution if (isSolution(state)) { // Record the solution recordSolution(state, res) // Stop searching return } // Traverse all choices for (choice in choices) { // Pruning: check if the choice is valid if (isValid(state, choice)) { // Attempt: make a choice and update the state makeChoice(state, choice) backtrack(state, choices, res) // Backtrack: undo the choice and restore to the previous state undoChoice(state, choice) } } } ``` === "Ruby" ```ruby title="" ### Backtracking algorithm framework ### def backtrack(state, choices, res) # Check if it is a solution if is_solution?(state) # Record the solution record_solution(state, res) return end # Traverse all choices for choice in choices # Pruning: check if the choice is valid if is_valid?(state, choice) # Attempt: make a choice and update the state make_choice(state, choice) backtrack(state, choices, res) # Backtrack: undo the choice and restore to the previous state undo_choice(state, choice) end end end ``` Next, we solve Example 3 based on the framework code. The state `state` is the node traversal path, the choices `choices` are the left and right child nodes of the current node, and the result `res` is a list of paths: ```src [file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack} ``` As per the problem statement, we should continue searching after finding a node with value $7$. **Therefore, we need to remove the `return` statement after recording the solution**. The following figure compares the search process with and without the `return` statement. ![Comparison of search process with and without return statement](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) Compared to code based on preorder traversal, code based on the backtracking algorithm framework appears more verbose, but has better generality. In fact, **many backtracking problems can be solved within this framework**. We only need to define `state` and `choices` for the specific problem and implement each method in the framework. ## Common Terminology To analyze algorithmic problems more clearly, we summarize the meanings of common terminology used in backtracking algorithms and provide corresponding examples from Example 3, as shown in the following table.

Table   Common Backtracking Algorithm Terminology

| Term | Definition | Example 3 | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | Solution (solution) | A solution is an answer that satisfies the specific conditions of a problem; there may be one or more solutions | All paths from root to nodes with value $7$ that satisfy the constraint | | Constraint (constraint) | A constraint is a condition in the problem that limits the feasibility of solutions, typically used for pruning | Paths do not contain nodes with value $3$ | | State (state) | State represents the situation of a problem at a certain moment, including the choices already made | The currently visited node path, i.e., the `path` list of nodes | | Attempt (attempt) | An attempt is the process of exploring the solution space according to available choices, including making choices, updating state, and checking if it is a solution | Recursively visit left (right) child nodes, add nodes to `path`, check if node value is $7$ | | Backtrack (backtracking) | Backtracking refers to undoing previous choices and returning to a previous state when encountering a state that does not satisfy constraints | Stop searching when passing over leaf nodes, ending node visits, or encountering nodes with value $3$; function returns | | Pruning (pruning) | Pruning is a method of avoiding meaningless search paths according to problem characteristics and constraints, which can improve search efficiency | When encountering a node with value $3$, do not continue searching | !!! tip The concepts of problem, solution, state, etc. are universal and are involved in divide-and-conquer, backtracking, dynamic programming, greedy and other algorithms. ## Advantages and Limitations The backtracking algorithm is essentially a depth-first search algorithm that tries all possible solutions until it finds one that satisfies the conditions. The advantage of this approach is that it can find all possible solutions, and with reasonable pruning operations, it achieves high efficiency. However, when dealing with large-scale or complex problems, **the running efficiency of the backtracking algorithm may be unacceptable**. - **Time**: The backtracking algorithm usually needs to traverse all possibilities in the solution space, and the time complexity can reach exponential or factorial order. - **Space**: During recursive calls, the current state needs to be saved (such as paths, auxiliary variables used for pruning, etc.), and when the depth is large, the space requirement can become very large. Nevertheless, **the backtracking algorithm is still the best solution for certain search problems and constraint satisfaction problems**. For these problems, since we cannot predict which choices will generate valid solutions, we must traverse all possible choices. In this case, **the key is how to optimize efficiency**. There are two common efficiency optimization methods. - **Pruning**: Avoid searching paths that are guaranteed not to produce solutions, thereby saving time and space. - **Heuristic search**: Introduce certain strategies or estimation values during the search process to prioritize searching paths that are most likely to produce valid solutions. ## Typical Backtracking Examples The backtracking algorithm can be used to solve many search problems, constraint satisfaction problems, and combinatorial optimization problems. **Search problems**: The goal of these problems is to find solutions that satisfy specific conditions. - Permutation problem: Given a set, find all possible permutations and combinations. - Subset sum problem: Given a set and a target sum, find all subsets in the set whose elements sum to the target. - Tower of Hanoi: Given three pegs and a series of disks of different sizes, move all disks from one peg to another, moving only one disk at a time, and never placing a larger disk on a smaller disk. **Constraint satisfaction problems**: The goal of these problems is to find solutions that satisfy all constraints. - N-Queens: Place $n$ queens on an $n \times n$ chessboard such that they do not attack each other. - Sudoku: Fill numbers $1$ to $9$ in a $9 \times 9$ grid such that each row, column, and $3 \times 3$ subgrid contains no repeated digits. - Graph coloring: Given an undirected graph, color each vertex with the minimum number of colors such that adjacent vertices have different colors. **Combinatorial optimization problems**: The goal of these problems is to find an optimal solution that satisfies certain conditions in a combinatorial space. - 0-1 Knapsack: Given a set of items and a knapsack, each item has a value and weight. Under the knapsack capacity constraint, select items to maximize total value. - Traveling Salesman Problem: Starting from a point in a graph, visit all other points exactly once and return to the starting point, finding the shortest path. - Maximum Clique: Given an undirected graph, find the largest complete subgraph, i.e., a subgraph where any two vertices are connected by an edge. Note that for many combinatorial optimization problems, backtracking is not the optimal solution. - The 0-1 Knapsack problem is usually solved using dynamic programming to achieve higher time efficiency. - The Traveling Salesman Problem is a famous NP-Hard problem; common solutions include genetic algorithms and ant colony algorithms. - The Maximum Clique problem is a classical problem in graph theory and can be solved using heuristic algorithms such as greedy algorithms. ================================================ FILE: en/docs/chapter_backtracking/index.md ================================================ # Backtracking ![Backtracking](../assets/covers/chapter_backtracking.jpg) !!! abstract We are like explorers in a maze, and may encounter difficulties on the path forward. The power of backtracking allows us to start over, keep trying, and eventually find the exit leading to light. ================================================ FILE: en/docs/chapter_backtracking/n_queens_problem.md ================================================ # N-Queens Problem !!! question According to the rules of chess, a queen can attack pieces that share the same row, column, or diagonal line. Given $n$ queens and an $n \times n$ chessboard, find a placement scheme such that no two queens can attack each other. As shown in the figure below, when $n = 4$, there are two solutions that can be found. From the perspective of the backtracking algorithm, an $n \times n$ chessboard has $n^2$ squares, which provide all the choices `choices`. During the process of placing queens one by one, the chessboard state changes continuously, and the chessboard at each moment represents the state `state`. ![Solution to the 4-queens problem](n_queens_problem.assets/solution_4_queens.png) The figure below illustrates the three constraints of this problem: **multiple queens cannot be in the same row, the same column, or on the same diagonal**. It is worth noting that diagonals are divided into two types: the main diagonal `\` and the anti-diagonal `/`. ![Constraints of the n-queens problem](n_queens_problem.assets/n_queens_constraints.png) ### Row-By-Row Placement Strategy Since both the number of queens and the number of rows on the chessboard are $n$, we can easily derive a conclusion: **each row of the chessboard allows and only allows exactly one queen to be placed**. This means we can adopt a row-by-row placement strategy: starting from the first row, place one queen in each row until the last row is completed. The figure below shows the row-by-row placement process for the 4-queens problem. Due to space limitations, the figure only expands one search branch of the first row, and all schemes that do not satisfy the column constraint and diagonal constraints are pruned. ![Row-by-row placement strategy](n_queens_problem.assets/n_queens_placing.png) Essentially, **the row-by-row placement strategy serves a pruning function**, as it avoids all search branches where multiple queens appear in the same row. ### Column and Diagonal Pruning To satisfy the column constraint, we can use a boolean array `cols` of length $n$ to record whether each column has a queen. Before each placement decision, we use `cols` to prune columns that already have queens, and dynamically update the state of `cols` during backtracking. !!! tip Please note that the origin of the matrix is located in the upper-left corner, where the row index increases from top to bottom, and the column index increases from left to right. So how do we handle diagonal constraints? Consider a square on the chessboard with row and column indices $(row, col)$. If we select a specific main diagonal in the matrix, we find that all squares on that diagonal have the same difference between their row and column indices, **meaning that $row - col$ is a constant value for all squares on the main diagonal**. In other words, if two squares satisfy $row_1 - col_1 = row_2 - col_2$, they must be on the same main diagonal. Using this pattern, we can use the array `diags1` shown in the figure below to record whether there is a queen on each main diagonal. Similarly, **for all squares on an anti-diagonal, the sum $row + col$ is a constant value**. We can likewise use the array `diags2` to handle anti-diagonal constraints. ![Handling column and diagonal constraints](n_queens_problem.assets/n_queens_cols_diagonals.png) ### Code Implementation Please note that in an $n$-dimensional square matrix, the range of $row - col$ is $[-n + 1, n - 1]$, and the range of $row + col$ is $[0, 2n - 2]$. Therefore, the number of both main diagonals and anti-diagonals is $2n - 1$, meaning the length of both arrays `diags1` and `diags2` is $2n - 1$. ```src [file]{n_queens}-[class]{}-[func]{n_queens} ``` Placing $n$ queens row by row, considering the column constraint, from the first row to the last row there are $n$, $n-1$, $\dots$, $2$, $1$ choices, using $O(n!)$ time. When recording a solution, it is necessary to copy the matrix `state` and add it to `res`, and the copy operation uses $O(n^2)$ time. Therefore, **the overall time complexity is $O(n! \cdot n^2)$**. In practice, pruning based on diagonal constraints can also significantly reduce the search space, so the search efficiency is often better than the time complexity mentioned above. The array `state` uses $O(n^2)$ space, and the arrays `cols`, `diags1`, and `diags2` each use $O(n)$ space. The maximum recursion depth is $n$, using $O(n)$ stack frame space. Therefore, **the space complexity is $O(n^2)$**. ================================================ FILE: en/docs/chapter_backtracking/permutations_problem.md ================================================ # Permutations Problem The permutations problem is a classic application of backtracking algorithms. It is defined as finding all possible arrangements of elements in a given collection (such as an array or string). The table below shows several example datasets, including input arrays and their corresponding permutations.

Table   Permutations Examples

| Input Array | All Permutations | | :---------- | :----------------------------------------------------------------- | | $[1]$ | $[1]$ | | $[1, 2]$ | $[1, 2], [2, 1]$ | | $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ | ## Case with Distinct Elements !!! question Given an integer array with no duplicate elements, return all possible permutations. From the perspective of backtracking algorithms, **we can imagine the process of generating permutations as the result of a series of choices**. Suppose the input array is $[1, 2, 3]$. If we first choose $1$, then choose $3$, and finally choose $2$, we obtain the permutation $[1, 3, 2]$. Backtracking means undoing a choice and then trying other choices. From the perspective of backtracking code, the candidate set `choices` consists of all elements in the input array, and the state `state` is the elements that have been chosen so far. Note that each element can only be chosen once, **therefore all elements in `state` should be unique**. As shown in the figure below, we can unfold the search process into a recursion tree, where each node in the tree represents the current state `state`. Starting from the root node, after three rounds of choices, we reach a leaf node, and each leaf node corresponds to a permutation. ![Recursion tree of permutations](permutations_problem.assets/permutations_i.png) ### Pruning Duplicate Choices To ensure that each element is chosen only once, we consider introducing a boolean array `selected`, where `selected[i]` indicates whether `choices[i]` has been chosen. We implement the following pruning operation based on it. - After making a choice `choice[i]`, we set `selected[i]` to $\text{True}$, indicating that it has been chosen. - When traversing the candidate list `choices`, we skip all nodes that have been chosen, which is pruning. As shown in the figure below, suppose we choose $1$ in the first round, $3$ in the second round, and $2$ in the third round. Then we need to prune the branch of element $1$ in the second round and prune the branches of elements $1$ and $3$ in the third round. ![Pruning example of permutations](permutations_problem.assets/permutations_i_pruning.png) Observing the above figure, we find that this pruning operation reduces the search space size from $O(n^n)$ to $O(n!)$. ### Code Implementation After understanding the above information, we can fill in the blanks in the template code. To shorten the overall code, we do not implement each function in the template separately, but instead unfold them in the `backtrack()` function: ```src [file]{permutations_i}-[class]{}-[func]{permutations_i} ``` ## Case with Duplicate Elements !!! question Given an integer array that **may contain duplicate elements**, return all unique permutations. Suppose the input array is $[1, 1, 2]$. To distinguish the two duplicate elements $1$, we denote the second $1$ as $\hat{1}$. As shown in the figure below, the method described above generates permutations where half are duplicates. ![Duplicate permutations](permutations_problem.assets/permutations_ii.png) So how do we remove duplicate permutations? The most direct approach is to use a hash set to directly deduplicate the permutation results. However, this is not elegant because **the search branches that generate duplicate permutations are unnecessary and should be identified and pruned early**, which can further improve algorithm efficiency. ### Pruning Duplicate Elements Observe the figure below. In the first round, choosing $1$ or choosing $\hat{1}$ is equivalent. All permutations generated under these two choices are duplicates. Therefore, we should prune $\hat{1}$. Similarly, after choosing $2$ in the first round, the $1$ and $\hat{1}$ in the second round also produce duplicate branches, so the second round's $\hat{1}$ should also be pruned. Essentially, **our goal is to ensure that multiple equal elements are chosen only once in a certain round of choices**. ![Pruning duplicate permutations](permutations_problem.assets/permutations_ii_pruning.png) ### Code Implementation Building on the code from the previous problem, we consider opening a hash set `duplicated` in each round of choices to record which elements have been tried in this round, and prune duplicate elements: ```src [file]{permutations_ii}-[class]{}-[func]{permutations_ii} ``` Assuming elements are pairwise distinct, there are $n!$ (factorial) permutations of $n$ elements. When recording results, we need to copy a list of length $n$, using $O(n)$ time. **Therefore, the time complexity is $O(n! \cdot n)$**. The maximum recursion depth is $n$, using $O(n)$ stack frame space. `selected` uses $O(n)$ space. At most $n$ `duplicated` sets exist simultaneously, using $O(n^2)$ space. **Therefore, the space complexity is $O(n^2)$**. ### Comparison of Two Pruning Methods Note that although both `selected` and `duplicated` are used for pruning, they have different objectives. - **Pruning duplicate choices**: There is only one `selected` throughout the entire search process. It records which elements are included in the current state, and its purpose is to prevent an element from appearing repeatedly in `state`. - **Pruning duplicate elements**: Each round of choices (each `backtrack` function call) contains a `duplicated` set. It records which elements have been chosen in this round's iteration (the `for` loop), and its purpose is to ensure that equal elements are chosen only once. The figure below shows the effective scope of the two pruning conditions. Note that each node in the tree represents a choice, and the nodes on the path from the root to a leaf node form a permutation. ![Effective scope of two pruning conditions](permutations_problem.assets/permutations_ii_pruning_summary.png) ================================================ FILE: en/docs/chapter_backtracking/subset_sum_problem.md ================================================ # Subset-Sum Problem ## Without Duplicate Elements !!! question Given a positive integer array `nums` and a target positive integer `target`, find all possible combinations where the sum of elements in the combination equals `target`. The given array has no duplicate elements, and each element can be selected multiple times. Return these combinations in list form, where the list should not contain duplicate combinations. For example, given the set $\{3, 4, 5\}$ and target integer $9$, the solutions are $\{3, 3, 3\}, \{4, 5\}$. Note the following two points: - Elements in the input set can be selected repeatedly without limit. - Subsets do not distinguish element order; for example, $\{4, 5\}$ and $\{5, 4\}$ are the same subset. ### Reference to Full Permutation Solution Similar to the full permutation problem, we can imagine the process of generating subsets as a series of choices, and update the "sum of elements" in real-time during the selection process. When the sum equals `target`, we record the subset to the result list. Unlike the full permutation problem, **elements in this problem's set can be selected unlimited times**, so we do not need to use a `selected` boolean list to track whether an element has been selected. We can make minor modifications to the full permutation code and initially obtain the solution: ```src [file]{subset_sum_i_naive}-[class]{}-[func]{subset_sum_i_naive} ``` When we input array $[3, 4, 5]$ and target element $9$ to the above code, the output is $[3, 3, 3], [4, 5], [5, 4]$. **Although we successfully find all subsets that sum to $9$, there are duplicate subsets $[4, 5]$ and $[5, 4]$**. This is because the search process distinguishes the order of selections, but subsets do not distinguish selection order. As shown in the figure below, selecting 4 first and then 5 versus selecting 5 first and then 4 are different branches, but they correspond to the same subset. ![Subset search and boundary pruning](subset_sum_problem.assets/subset_sum_i_naive.png) To eliminate duplicate subsets, **one straightforward idea is to deduplicate the result list**. However, this approach is very inefficient for two reasons: - When there are many array elements, especially when `target` is large, the search process generates many duplicate subsets. - Comparing subsets (arrays) is very time-consuming, requiring sorting the arrays first, then comparing each element in them. ### Pruning Duplicate Subsets **We consider deduplication through pruning during the search process**. Observing the figure below, duplicate subsets occur when array elements are selected in different orders, as in the following cases: 1. When the first and second rounds select $3$ and $4$ respectively, all subsets containing these two elements are generated, denoted as $[3, 4, \dots]$. 2. Afterward, when the first round selects $4$, **the second round should skip $3$**, because the subset $[4, 3, \dots]$ generated by this choice is completely duplicate with the subset generated in step `1.` In the search process, each level's choices are tried from left to right, so the rightmost branches are pruned more. 1. The first two rounds select $3$ and $5$, generating subset $[3, 5, \dots]$. 2. The first two rounds select $4$ and $5$, generating subset $[4, 5, \dots]$. 3. If the first round selects $5$, **the second round should skip $3$ and $4$**, because subsets $[5, 3, \dots]$ and $[5, 4, \dots]$ are completely duplicate with the subsets described in steps `1.` and `2.` ![Different selection orders leading to duplicate subsets](subset_sum_problem.assets/subset_sum_i_pruning.png) In summary, given an input array $[x_1, x_2, \dots, x_n]$, let the selection sequence in the search process be $[x_{i_1}, x_{i_2}, \dots, x_{i_m}]$. This selection sequence must satisfy $i_1 \leq i_2 \leq \dots \leq i_m$; **any selection sequence that does not satisfy this condition will cause duplicates and should be pruned**. ### Code Implementation To implement this pruning, we initialize a variable `start` to indicate the starting point of traversal. **After making choice $x_{i}$, set the next round to start traversal from index $i$**. This ensures that the selection sequence satisfies $i_1 \leq i_2 \leq \dots \leq i_m$, guaranteeing subset uniqueness. In addition, we have made the following two optimizations to the code: - Before starting the search, first sort the array `nums`. When traversing all choices, **end the loop immediately when the subset sum exceeds `target`**, because subsequent elements are larger, and their subset sums must exceed `target`. - Omit the element sum variable `total` and **use subtraction on `target` to track the sum of elements**. Record the solution when `target` equals $0$. ```src [file]{subset_sum_i}-[class]{}-[func]{subset_sum_i} ``` The figure below shows the complete backtracking process when array $[3, 4, 5]$ and target element $9$ are input to the above code. ![Subset-sum I backtracking process](subset_sum_problem.assets/subset_sum_i.png) ## With Duplicate Elements in Array !!! question Given a positive integer array `nums` and a target positive integer `target`, find all possible combinations where the sum of elements in the combination equals `target`. **The given array may contain duplicate elements, and each element can be selected at most once**. Return these combinations in list form, where the list should not contain duplicate combinations. Compared to the previous problem, **the input array in this problem may contain duplicate elements**, which introduces new challenges. For example, given array $[4, \hat{4}, 5]$ and target element $9$, the output of the existing code is $[4, 5], [\hat{4}, 5]$, which contains duplicate subsets. **The reason for this duplication is that equal elements are selected multiple times in a certain round**. In the figure below, the first round has three choices, two of which are $4$, creating two duplicate search branches that output duplicate subsets. Similarly, the two $4$'s in the second round also produce duplicate subsets. ![Duplicate subsets caused by equal elements](subset_sum_problem.assets/subset_sum_ii_repeat.png) ### Pruning Equal Elements To solve this problem, **we need to limit equal elements to be selected only once in each round**. The implementation is quite clever: since the array is already sorted, equal elements are adjacent. This means that in a certain round of selection, if the current element equals the element to its left, it means this element has already been selected, so we skip the current element directly. At the same time, **this problem specifies that each array element can only be selected once**. Fortunately, we can also use the variable `start` to satisfy this constraint: after making choice $x_{i}$, set the next round to start traversal from index $i + 1$ onwards. This both eliminates duplicate subsets and avoids selecting elements multiple times. ### Code Implementation ```src [file]{subset_sum_ii}-[class]{}-[func]{subset_sum_ii} ``` The figure below shows the backtracking process for array $[4, 4, 5]$ and target element $9$, which includes four types of pruning operations. Combine the illustration with the code comments to understand the entire search process and how each pruning operation works. ![Subset-sum II backtracking process](subset_sum_problem.assets/subset_sum_ii.png) ================================================ FILE: en/docs/chapter_backtracking/summary.md ================================================ # Summary ### Key Review - The backtracking algorithm is fundamentally an exhaustive search method. It finds solutions that meet specified conditions by performing a depth-first traversal of the solution space. During the search process, when a solution satisfying the conditions is found, it is recorded. The search ends either after finding all solutions or when the traversal is complete. - The backtracking algorithm search process consists of two parts: attempting and backtracking. It tries various choices through depth-first search. When encountering situations that violate constraints, it reverts the previous choice, returns to the previous state, and continues exploring other options. Attempting and backtracking are operations in opposite directions. - Backtracking problems typically contain multiple constraints, which can be utilized to implement pruning operations. Pruning can terminate unnecessary search branches early, significantly improving search efficiency. - The backtracking algorithm is primarily used to solve search problems and constraint satisfaction problems. While combinatorial optimization problems can be solved with backtracking, there are often more efficient or better-performing solutions available. - The permutation problem aims to find all possible permutations of elements in a given set. We use an array to record whether each element has been selected, thereby pruning search branches that attempt to select the same element repeatedly, ensuring each element is selected exactly once. - In the permutation problem, if the set contains duplicate elements, the final result will contain duplicate permutations. We need to impose a constraint so that equal elements can only be selected once per round, which is typically achieved using a hash set. - The subset-sum problem aims to find all subsets of a given set that sum to a target value. Since the set is unordered but the search process outputs results in all orders, duplicate subsets are generated. We sort the data before backtracking and use a variable to indicate the starting point of each round's traversal, thereby pruning search branches that generate duplicate subsets. - For the subset-sum problem, equal elements in the array produce duplicate sets. We leverage the precondition that the array is sorted by checking whether adjacent elements are equal to implement pruning, ensuring that equal elements can only be selected once per round. - The $n$ queens problem aims to find placements of $n$ queens on an $n \times n$ chessboard such that no two queens can attack each other. The constraints of this problem include row constraints, column constraints, and main and anti-diagonal constraints. To satisfy row constraints, we adopt a row-by-row placement strategy, ensuring exactly one queen is placed in each row. - The handling of column constraints and diagonal constraints is similar. For column constraints, we use an array to record whether each column has a queen, thereby indicating whether a selected cell is valid. For diagonal constraints, we use two arrays to separately record whether queens exist on each main or anti-diagonal. The challenge lies in finding the row-column index pattern that characterizes cells on the same main (anti-)diagonal. ### Q & A **Q**: How should we understand the relationship between backtracking and recursion? Overall, backtracking is an "algorithm strategy", while recursion is more like a "tool". - The backtracking algorithm is typically implemented based on recursion. However, backtracking is one application scenario of recursion and represents the application of recursion in search problems. - The structure of recursion embodies the "subproblem decomposition" problem-solving paradigm, commonly used to solve problems involving divide-and-conquer, backtracking, and dynamic programming (memoized recursion). ================================================ FILE: en/docs/chapter_computational_complexity/index.md ================================================ # Complexity Analysis ![Complexity analysis](../assets/covers/chapter_complexity_analysis.jpg) !!! abstract Complexity analysis is like a space-time guide in the vast universe of algorithms. It leads us to explore deeply within the two dimensions of time and space, seeking more elegant solutions. ================================================ FILE: en/docs/chapter_computational_complexity/iteration_and_recursion.md ================================================ # Iteration and Recursion In algorithms, repeatedly executing a task is very common and closely related to complexity analysis. Therefore, before introducing time complexity and space complexity, let's first understand how to implement repeated task execution in programs, namely the two basic program control structures: iteration and recursion. ## Iteration Iteration is a control structure for repeatedly executing a task. In iteration, a program repeatedly executes a segment of code under certain conditions until those conditions are no longer satisfied. ### For Loop The `for` loop is one of the most common forms of iteration, **suitable for use when the number of iterations is known in advance**. The following function implements the summation $1 + 2 + \dots + n$ based on a `for` loop, with the sum result recorded using the variable `res`. Note that in Python, `range(a, b)` corresponds to a "left-closed, right-open" interval, with the traversal range being $a, a + 1, \dots, b-1$: ```src [file]{iteration}-[class]{}-[func]{for_loop} ``` The figure below shows the flowchart of this summation function. ![Flowchart of the summation function](iteration_and_recursion.assets/iteration.png) The number of operations in this summation function is proportional to the input data size $n$, or has a "linear relationship". In fact, **time complexity describes precisely this "linear relationship"**. Related content will be introduced in detail in the next section. ### While Loop Similar to the `for` loop, the `while` loop is also a method for implementing iteration. In a `while` loop, the program first checks the condition in each round; if the condition is true, it continues execution, otherwise it ends the loop. Below we use a `while` loop to implement the summation $1 + 2 + \dots + n$: ```src [file]{iteration}-[class]{}-[func]{while_loop} ``` **The `while` loop has greater flexibility than the `for` loop**. In a `while` loop, we can freely design the initialization and update steps of the condition variable. For example, in the following code, the condition variable $i$ is updated twice per round, which is not convenient to implement using a `for` loop: ```src [file]{iteration}-[class]{}-[func]{while_loop_ii} ``` Overall, **`for` loops have more compact code, while `while` loops are more flexible**; both can implement iterative structures. The choice of which to use should be determined based on the requirements of the specific problem. ### Nested Loops We can nest one loop structure inside another. Below is an example using `for` loops: ```src [file]{iteration}-[class]{}-[func]{nested_for_loop} ``` The figure below shows the flowchart of this nested loop. ![Flowchart of nested loops](iteration_and_recursion.assets/nested_iteration.png) In this case, the number of operations of the function is proportional to $n^2$, or the algorithm's running time has a "quadratic relationship" with the input data size $n$. We can continue adding nested loops, where each nesting is a "dimension increase", raising the time complexity to "cubic relationship", "quartic relationship", and so on. ## Recursion Recursion is an algorithmic strategy that solves problems by having a function call itself. It mainly consists of two phases. 1. **Descend**: The program continuously calls itself deeper, usually passing in smaller or more simplified parameters, until reaching a "termination condition". 2. **Ascend**: After triggering the "termination condition", the program returns layer by layer from the deepest recursive function, aggregating the result of each layer. From an implementation perspective, recursive code mainly consists of three elements. 1. **Termination condition**: Used to determine when to switch from "descending" to "ascending". 2. **Recursive call**: Corresponds to "descending", where the function calls itself, usually with smaller or more simplified parameters. 3. **Return result**: Corresponds to "ascending", returning the result of the current recursion level to the previous layer. Observe the following code. We only need to call the function `recur(n)` to complete the calculation of $1 + 2 + \dots + n$: ```src [file]{recursion}-[class]{}-[func]{recur} ``` The figure below shows the recursive process of this function. ![Recursive process of the summation function](iteration_and_recursion.assets/recursion_sum.png) Although from a computational perspective, iteration and recursion can achieve the same results, **they represent two completely different paradigms for thinking about and solving problems**. - **Iteration**: Solves problems "bottom-up". Starting from the most basic steps, these steps are then repeatedly executed or accumulated until the task is complete. - **Recursion**: Solves problems "top-down". The original problem is decomposed into smaller subproblems that have the same form as the original problem. These subproblems continue to be decomposed into even smaller subproblems until reaching the base case (where the solution is known). Taking the above summation function as an example, let the problem be $f(n) = 1 + 2 + \dots + n$. - **Iteration**: Simulates the summation process in a loop, traversing from $1$ to $n$, performing the summation operation in each round to obtain $f(n)$. - **Recursion**: Decomposes the problem into the subproblem $f(n) = n + f(n-1)$, continuously decomposing (recursively) until terminating at the base case $f(1) = 1$. ### Call Stack Each time a recursive function calls itself, the system allocates memory for the newly opened function to store local variables, call addresses, and other information. This leads to two consequences. - The function's context data is stored in a memory area called "stack frame space", which is not released until the function returns. Therefore, **recursion usually consumes more memory space than iteration**. - Recursive function calls incur additional overhead. **Therefore, recursion is usually less time-efficient than loops**. As shown in the figure below, before the termination condition is triggered, there are $n$ unreturned recursive functions existing simultaneously, with a **recursion depth of $n$**. ![Recursion call depth](iteration_and_recursion.assets/recursion_sum_depth.png) In practice, the recursion depth allowed by programming languages is usually limited, and excessively deep recursion may lead to stack overflow errors. ### Tail Recursion Interestingly, **if a function makes the recursive call as the very last step before returning**, the function can be optimized by the compiler or interpreter to have space efficiency comparable to iteration. This case is called tail recursion. - **Regular recursion**: When a function returns to the previous level, it needs to continue executing code, so the system needs to save the context of the previous layer's call. - **Tail recursion**: The recursive call is the last operation before the function returns, meaning that after returning to the previous level, there is no need to continue executing other operations, so the system does not need to save the context of the previous layer's function. Taking the calculation of $1 + 2 + \dots + n$ as an example, we can set the result variable `res` as a function parameter to implement tail recursion: ```src [file]{recursion}-[class]{}-[func]{tail_recur} ``` The execution process of tail recursion is shown in the figure below. Comparing regular recursion and tail recursion, the execution point of the summation operation is different. - **Regular recursion**: The summation operation is performed during the "ascending" process, requiring an additional summation operation after each layer returns. - **Tail recursion**: The summation operation is performed during the "descending" process; the "ascending" process only needs to return layer by layer. ![Tail recursion process](iteration_and_recursion.assets/tail_recursion_sum.png) !!! tip Please note that many compilers or interpreters do not support tail recursion optimization. For example, Python does not support tail recursion optimization by default, so even if a function is in tail recursive form, it may still encounter stack overflow issues. ### Recursion Tree When dealing with algorithmic problems related to "divide and conquer", recursion often provides a more intuitive approach and more readable code than iteration. Taking the "Fibonacci sequence" as an example. !!! question Given a Fibonacci sequence $0, 1, 1, 2, 3, 5, 8, 13, \dots$, find the $n$-th number in the sequence. Let the $n$-th number of the Fibonacci sequence be $f(n)$. Two conclusions can be easily obtained. - The first two numbers of the sequence are $f(1) = 0$ and $f(2) = 1$. - Each number in the sequence is the sum of the previous two numbers, i.e., $f(n) = f(n - 1) + f(n - 2)$. Following the recurrence relation to make recursive calls, with the first two numbers as termination conditions, we can write the recursive code. Calling `fib(n)` will give us the $n$-th number of the Fibonacci sequence: ```src [file]{recursion}-[class]{}-[func]{fib} ``` Observing the above code, we recursively call two functions within the function, **meaning that one call produces two call branches**. As shown in the figure below, such continuous recursive calling will eventually produce a recursion tree with $n$ levels. ![Recursion tree of the Fibonacci sequence](iteration_and_recursion.assets/recursion_tree.png) Fundamentally, recursion embodies the paradigm of "decomposing a problem into smaller subproblems", and this divide-and-conquer strategy is crucial. - From an algorithmic perspective, many important algorithmic strategies such as searching, sorting, backtracking, divide and conquer, and dynamic programming directly or indirectly apply this way of thinking. - From a data structure perspective, recursion is naturally suited for handling problems related to linked lists, trees, and graphs, because they are well-suited for analysis using divide-and-conquer thinking. ## Comparison of the Two Summarizing the above content, as shown in the table below, iteration and recursion differ in implementation, performance, and applicability.

Table   Comparison of iteration and recursion characteristics

| | Iteration | Recursion | | -------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------- | | Implementation | Loop structure | Function calls itself | | Time efficiency | Generally more efficient, no function call overhead | Each function call incurs overhead | | Memory usage | Usually uses a fixed amount of memory space | Accumulated function calls may use a large amount of stack frame space | | Suitable problems | Suitable for simple loop tasks, with intuitive and readable code | Suitable for subproblem decomposition, such as trees, graphs, divide and conquer, backtracking, etc., with concise and clear code structure | !!! tip If you find the following content difficult to understand, you can review it after reading the "Stack" chapter. What is the intrinsic relationship between iteration and recursion? Taking the above recursive function as an example, the summation operation is performed during the "ascending" phase of recursion. This means that the function called first actually completes its summation operation last, **and this working mechanism is similar to the "last-in, first-out" principle of stacks**. In fact, recursive terminology such as "call stack" and "stack frame space" already hints at the close relationship between recursion and stacks. 1. **Descend**: When a function is called, the system allocates a new stack frame on the "call stack" for that function to store the function's local variables, parameters, return address, and other data. 2. **Ascend**: When the function completes execution and returns, the corresponding stack frame is removed from the "call stack", restoring the execution environment of the previous function. Therefore, **we can use an explicit stack to simulate the behavior of the call stack**, thus transforming recursion into iterative form: ```src [file]{recursion}-[class]{}-[func]{for_loop_recur} ``` Observing the above code, when recursion is transformed into iteration, the code becomes more complex. Although iteration and recursion can be converted into each other in many cases, it may not be worthwhile to do so for the following two reasons. - The transformed code may be more difficult to understand and less readable. - For some complex problems, simulating the behavior of the system call stack can be very difficult. In summary, **choosing between iteration and recursion depends on the nature of the specific problem**. In programming practice, it is crucial to weigh the pros and cons of both and choose the appropriate method based on the context. ================================================ FILE: en/docs/chapter_computational_complexity/performance_evaluation.md ================================================ # Algorithm Efficiency Evaluation In algorithm design, we pursue the following two levels of objectives sequentially. 1. **Finding a solution to the problem**: The algorithm must reliably obtain the correct solution within the specified input range. 2. **Seeking the optimal solution**: Multiple solutions may exist for the same problem, and we hope to find an algorithm that is as efficient as possible. In other words, under the premise of being able to solve the problem, algorithm efficiency has become the primary evaluation criterion for measuring the quality of algorithms. It includes the following two dimensions. - **Time efficiency**: The length of time the algorithm runs. - **Space efficiency**: The size of memory space the algorithm occupies. In short, **our goal is to design data structures and algorithms that are "both fast and memory-efficient"**. Effectively evaluating algorithm efficiency is crucial, because only in this way can we compare various algorithms and guide the algorithm design and optimization process. Efficiency evaluation methods are mainly divided into two types: actual testing and theoretical estimation. ## Actual Testing Suppose we now have algorithm `A` and algorithm `B`, both of which can solve the same problem, and we need to compare the efficiency of these two algorithms. The most direct method is to find a computer, run these two algorithms, and monitor and record their running time and memory usage. This evaluation approach can reflect the real situation, but it also has considerable limitations. On one hand, **it is difficult to eliminate interference factors from the testing environment**. Hardware configuration affects the performance of algorithms. For example, if an algorithm has a high degree of parallelism, it is more suitable for running on multi-core CPUs; if an algorithm has intensive memory operations, it will perform better on high-performance memory. In other words, the test results of an algorithm on different machines may be inconsistent. This means we need to test on various machines and calculate average efficiency, which is impractical. On the other hand, **conducting complete testing is very resource-intensive**. As the input data volume changes, the algorithm will exhibit different efficiencies. For example, when the input data volume is small, the running time of algorithm `A` is shorter than algorithm `B`; but when the input data volume is large, the test results may be exactly the opposite. Therefore, to obtain convincing conclusions, we need to test input data of various scales, which requires a large amount of computational resources. ## Theoretical Estimation Since actual testing has considerable limitations, we can consider evaluating algorithm efficiency through calculations alone. This estimation method is called asymptotic complexity analysis, or complexity analysis for short. Complexity analysis can reflect the relationship between the time and space resources required for algorithm execution and the input data scale. **It describes the growth trend of the time and space required for algorithm execution as the input data scale increases**. This definition is somewhat convoluted, so we can break it down into three key points to understand. - "Time and space resources" correspond to time complexity and space complexity, respectively. - "As the input data scale increases" means that complexity reflects the relationship between algorithm running efficiency and input data scale. - "Growth trend of time and space" indicates that complexity analysis focuses not on the specific values of running time or occupied space, but on how "fast" time or space grows. **Complexity analysis overcomes the drawbacks of the actual testing method**, reflected in the following aspects. - It does not need to actually run the code, making it more environmentally friendly and energy-efficient. - It is independent of the testing environment, and the analysis results are applicable to all running platforms. - It can reflect algorithm efficiency at different data volumes, especially algorithm performance at large data volumes. !!! tip If you are still confused about the concept of complexity, don't worry—we will introduce it in detail in subsequent chapters. Complexity analysis provides us with a "ruler" for evaluating algorithm efficiency, allowing us to measure the time and space resources required to execute a certain algorithm and compare the efficiency between different algorithms. Complexity is a mathematical concept that may be relatively abstract for beginners, with a relatively high learning difficulty. From this perspective, complexity analysis may not be very suitable as the first content to be introduced. However, when we discuss the characteristics of a certain data structure or algorithm, it is difficult to avoid analyzing its running speed and space usage. In summary, it is recommended that before diving deep into data structures and algorithms, **you first establish a preliminary understanding of complexity analysis so that you can complete complexity analysis of simple algorithms**. ================================================ FILE: en/docs/chapter_computational_complexity/space_complexity.md ================================================ # Space Complexity Space complexity measures the growth trend of memory space occupied by an algorithm as the data size increases. This concept is very similar to time complexity, except that "running time" is replaced with "occupied memory space". ## Algorithm-Related Space The memory space used by an algorithm during execution mainly includes the following types. - **Input space**: Used to store the input data of the algorithm. - **Temporary space**: Used to store variables, objects, function contexts, and other data during the algorithm's execution. - **Output space**: Used to store the output data of the algorithm. In general, the scope of space complexity statistics is "temporary space" plus "output space". Temporary space can be further divided into three parts. - **Temporary data**: Used to save various constants, variables, objects, etc., during the algorithm's execution. - **Stack frame space**: Used to save the context data of called functions. The system creates a stack frame at the top of the stack each time a function is called, and the stack frame space is released after the function returns. - **Instruction space**: Used to save compiled program instructions, which are usually ignored in actual statistics. When analyzing the space complexity of a program, **we usually count three parts: temporary data, stack frame space, and output data**, as shown in the following figure. ![Algorithm-related space](space_complexity.assets/space_types.png) The related code is as follows: === "Python" ```python title="" class Node: """Class""" def __init__(self, x: int): self.val: int = x # Node value self.next: Node | None = None # Reference to the next node def function() -> int: """Function""" # Perform some operations... return 0 def algorithm(n) -> int: # Input data A = 0 # Temporary data (constant, usually represented by uppercase letters) b = 0 # Temporary data (variable) node = Node(0) # Temporary data (object) c = function() # Stack frame space (function call) return A + b + c # Output data ``` === "C++" ```cpp title="" /* Structure */ struct Node { int val; Node *next; Node(int x) : val(x), next(nullptr) {} }; /* Function */ int func() { // Perform some operations... return 0; } int algorithm(int n) { // Input data const int a = 0; // Temporary data (constant) int b = 0; // Temporary data (variable) Node* node = new Node(0); // Temporary data (object) int c = func(); // Stack frame space (function call) return a + b + c; // Output data } ``` === "Java" ```java title="" /* Class */ class Node { int val; Node next; Node(int x) { val = x; } } /* Function */ int function() { // Perform some operations... return 0; } int algorithm(int n) { // Input data final int a = 0; // Temporary data (constant) int b = 0; // Temporary data (variable) Node node = new Node(0); // Temporary data (object) int c = function(); // Stack frame space (function call) return a + b + c; // Output data } ``` === "C#" ```csharp title="" /* Class */ class Node(int x) { int val = x; Node next; } /* Function */ int Function() { // Perform some operations... return 0; } int Algorithm(int n) { // Input data const int a = 0; // Temporary data (constant) int b = 0; // Temporary data (variable) Node node = new(0); // Temporary data (object) int c = Function(); // Stack frame space (function call) return a + b + c; // Output data } ``` === "Go" ```go title="" /* Structure */ type node struct { val int next *node } /* Create node structure */ func newNode(val int) *node { return &node{val: val} } /* Function */ func function() int { // Perform some operations... return 0 } func algorithm(n int) int { // Input data const a = 0 // Temporary data (constant) b := 0 // Temporary data (variable) newNode(0) // Temporary data (object) c := function() // Stack frame space (function call) return a + b + c // Output data } ``` === "Swift" ```swift title="" /* Class */ class Node { var val: Int var next: Node? init(x: Int) { val = x } } /* Function */ func function() -> Int { // Perform some operations... return 0 } func algorithm(n: Int) -> Int { // Input data let a = 0 // Temporary data (constant) var b = 0 // Temporary data (variable) let node = Node(x: 0) // Temporary data (object) let c = function() // Stack frame space (function call) return a + b + c // Output data } ``` === "JS" ```javascript title="" /* Class */ class Node { val; next; constructor(val) { this.val = val === undefined ? 0 : val; // Node value this.next = null; // Reference to the next node } } /* Function */ function constFunc() { // Perform some operations return 0; } function algorithm(n) { // Input data const a = 0; // Temporary data (constant) let b = 0; // Temporary data (variable) const node = new Node(0); // Temporary data (object) const c = constFunc(); // Stack frame space (function call) return a + b + c; // Output data } ``` === "TS" ```typescript title="" /* Class */ class Node { val: number; next: Node | null; constructor(val?: number) { this.val = val === undefined ? 0 : val; // Node value this.next = null; // Reference to the next node } } /* Function */ function constFunc(): number { // Perform some operations return 0; } function algorithm(n: number): number { // Input data const a = 0; // Temporary data (constant) let b = 0; // Temporary data (variable) const node = new Node(0); // Temporary data (object) const c = constFunc(); // Stack frame space (function call) return a + b + c; // Output data } ``` === "Dart" ```dart title="" /* Class */ class Node { int val; Node next; Node(this.val, [this.next]); } /* Function */ int function() { // Perform some operations... return 0; } int algorithm(int n) { // Input data const int a = 0; // Temporary data (constant) int b = 0; // Temporary data (variable) Node node = Node(0); // Temporary data (object) int c = function(); // Stack frame space (function call) return a + b + c; // Output data } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* Structure */ struct Node { val: i32, next: Option>>, } /* Create Node structure */ impl Node { fn new(val: i32) -> Self { Self { val: val, next: None } } } /* Function */ fn function() -> i32 { // Perform some operations... return 0; } fn algorithm(n: i32) -> i32 { // Input data const a: i32 = 0; // Temporary data (constant) let mut b = 0; // Temporary data (variable) let node = Node::new(0); // Temporary data (object) let c = function(); // Stack frame space (function call) return a + b + c; // Output data } ``` === "C" ```c title="" /* Function */ int func() { // Perform some operations... return 0; } int algorithm(int n) { // Input data const int a = 0; // Temporary data (constant) int b = 0; // Temporary data (variable) int c = func(); // Stack frame space (function call) return a + b + c; // Output data } ``` === "Kotlin" ```kotlin title="" /* Class */ class Node(var _val: Int) { var next: Node? = null } /* Function */ fun function(): Int { // Perform some operations... return 0 } fun algorithm(n: Int): Int { // Input data val a = 0 // Temporary data (constant) var b = 0 // Temporary data (variable) val node = Node(0) // Temporary data (object) val c = function() // Stack frame space (function call) return a + b + c // Output data } ``` === "Ruby" ```ruby title="" ### Class ### class Node attr_accessor :val # Node value attr_accessor :next # Reference to the next node def initialize(x) @val = x end end ### Function ### def function # Perform some operations... 0 end ### Algorithm ### def algorithm(n) # Input data a = 0 # Temporary data (constant) b = 0 # Temporary data (variable) node = Node.new(0) # Temporary data (object) c = function # Stack frame space (function call) a + b + c # Output data end ``` ## Calculation Method The calculation method for space complexity is roughly the same as for time complexity, except that the statistical object is changed from "number of operations" to "size of space used". Unlike time complexity, **we usually only focus on the worst-case space complexity**. This is because memory space is a hard requirement, and we must ensure that sufficient memory space is reserved for all input data. Observe the following code. The "worst case" in worst-case space complexity has two meanings. 1. **Based on the worst input data**: When $n < 10$, the space complexity is $O(1)$; but when $n > 10$, the initialized array `nums` occupies $O(n)$ space, so the worst-case space complexity is $O(n)$. 2. **Based on the peak memory during algorithm execution**: For example, before executing the last line, the program occupies $O(1)$ space; when initializing the array `nums`, the program occupies $O(n)$ space, so the worst-case space complexity is $O(n)$. === "Python" ```python title="" def algorithm(n: int): a = 0 # O(1) b = [0] * 10000 # O(1) if n > 10: nums = [0] * n # O(n) ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 0; // O(1) vector b(10000); // O(1) if (n > 10) vector nums(n); // O(n) } ``` === "Java" ```java title="" void algorithm(int n) { int a = 0; // O(1) int[] b = new int[10000]; // O(1) if (n > 10) int[] nums = new int[n]; // O(n) } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 0; // O(1) int[] b = new int[10000]; // O(1) if (n > 10) { int[] nums = new int[n]; // O(n) } } ``` === "Go" ```go title="" func algorithm(n int) { a := 0 // O(1) b := make([]int, 10000) // O(1) var nums []int if n > 10 { nums := make([]int, n) // O(n) } fmt.Println(a, b, nums) } ``` === "Swift" ```swift title="" func algorithm(n: Int) { let a = 0 // O(1) let b = Array(repeating: 0, count: 10000) // O(1) if n > 10 { let nums = Array(repeating: 0, count: n) // O(n) } } ``` === "JS" ```javascript title="" function algorithm(n) { const a = 0; // O(1) const b = new Array(10000); // O(1) if (n > 10) { const nums = new Array(n); // O(n) } } ``` === "TS" ```typescript title="" function algorithm(n: number): void { const a = 0; // O(1) const b = new Array(10000); // O(1) if (n > 10) { const nums = new Array(n); // O(n) } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 0; // O(1) List b = List.filled(10000, 0); // O(1) if (n > 10) { List nums = List.filled(n, 0); // O(n) } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let a = 0; // O(1) let b = [0; 10000]; // O(1) if n > 10 { let nums = vec![0; n as usize]; // O(n) } } ``` === "C" ```c title="" void algorithm(int n) { int a = 0; // O(1) int b[10000]; // O(1) if (n > 10) int nums[n] = {0}; // O(n) } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { val a = 0 // O(1) val b = IntArray(10000) // O(1) if (n > 10) { val nums = IntArray(n) // O(n) } } ``` === "Ruby" ```ruby title="" def algorithm(n) a = 0 # O(1) b = Array.new(10000) # O(1) nums = Array.new(n) if n > 10 # O(n) end ``` **In recursive functions, it is necessary to count the stack frame space**. Observe the following code: === "Python" ```python title="" def function() -> int: # Perform some operations return 0 def loop(n: int): """Loop has space complexity of O(1)""" for _ in range(n): function() def recur(n: int): """Recursion has space complexity of O(n)""" if n == 1: return return recur(n - 1) ``` === "C++" ```cpp title="" int func() { // Perform some operations return 0; } /* Loop has space complexity of O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } /* Recursion has space complexity of O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Java" ```java title="" int function() { // Perform some operations return 0; } /* Loop has space complexity of O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } /* Recursion has space complexity of O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "C#" ```csharp title="" int Function() { // Perform some operations return 0; } /* Loop has space complexity of O(1) */ void Loop(int n) { for (int i = 0; i < n; i++) { Function(); } } /* Recursion has space complexity of O(n) */ int Recur(int n) { if (n == 1) return 1; return Recur(n - 1); } ``` === "Go" ```go title="" func function() int { // Perform some operations return 0 } /* Loop has space complexity of O(1) */ func loop(n int) { for i := 0; i < n; i++ { function() } } /* Recursion has space complexity of O(n) */ func recur(n int) { if n == 1 { return } recur(n - 1) } ``` === "Swift" ```swift title="" @discardableResult func function() -> Int { // Perform some operations return 0 } /* Loop has space complexity of O(1) */ func loop(n: Int) { for _ in 0 ..< n { function() } } /* Recursion has space complexity of O(n) */ func recur(n: Int) { if n == 1 { return } recur(n: n - 1) } ``` === "JS" ```javascript title="" function constFunc() { // Perform some operations return 0; } /* Loop has space complexity of O(1) */ function loop(n) { for (let i = 0; i < n; i++) { constFunc(); } } /* Recursion has space complexity of O(n) */ function recur(n) { if (n === 1) return; return recur(n - 1); } ``` === "TS" ```typescript title="" function constFunc(): number { // Perform some operations return 0; } /* Loop has space complexity of O(1) */ function loop(n: number): void { for (let i = 0; i < n; i++) { constFunc(); } } /* Recursion has space complexity of O(n) */ function recur(n: number): void { if (n === 1) return; return recur(n - 1); } ``` === "Dart" ```dart title="" int function() { // Perform some operations return 0; } /* Loop has space complexity of O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } /* Recursion has space complexity of O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Rust" ```rust title="" fn function() -> i32 { // Perform some operations return 0; } /* Loop has space complexity of O(1) */ fn loop(n: i32) { for i in 0..n { function(); } } /* Recursion has space complexity of O(n) */ fn recur(n: i32) { if n == 1 { return; } recur(n - 1); } ``` === "C" ```c title="" int func() { // Perform some operations return 0; } /* Loop has space complexity of O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } /* Recursion has space complexity of O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Kotlin" ```kotlin title="" fun function(): Int { // Perform some operations return 0 } /* Loop has space complexity of O(1) */ fun loop(n: Int) { for (i in 0..function can be executed independently, with all parameters passed explicitly. A method is associated with an object, is implicitly passed to the object that invokes it, and can operate on data contained in class instances. The following examples use several common programming languages for illustration. - C is a procedural programming language without object-oriented concepts, so it only has functions. However, we can simulate object-oriented programming by creating structures (struct), and functions associated with structures are equivalent to methods in other programming languages. - Java and C# are object-oriented programming languages where code blocks (methods) are typically part of a class. Static methods behave like functions because they are bound to the class and cannot access specific instance variables. - C++ and Python support both procedural programming (functions) and object-oriented programming (methods). **Q**: Does the diagram for "common space complexity types" reflect the absolute size of occupied space? No, the diagram shows space complexity, which reflects growth trends rather than the absolute size of occupied space. Assuming $n = 8$, you might find that the values of each curve do not correspond to the functions. This is because each curve contains a constant term used to compress the value range into a visually comfortable range. In practice, because we generally do not know what the "constant term" complexity of each method is, we usually cannot select the optimal solution for $n = 8$ based on complexity alone. But for $n = 8^5$, the choice is straightforward, as the growth trend already dominates. **Q**: Are there situations where algorithms are designed to sacrifice time (or space) based on actual use cases? In practical applications, most situations choose to sacrifice space for time. For example, with database indexes, we typically choose to build B+ trees or hash indexes, occupying substantial memory space in exchange for efficient queries of $O(\log n)$ or even $O(1)$. In scenarios where space resources are precious, time may be sacrificed for space. For example, in embedded development, device memory is precious, and engineers may forgo using hash tables and choose to use array sequential search to save memory usage, at the cost of slower searches. ================================================ FILE: en/docs/chapter_computational_complexity/time_complexity.md ================================================ # Time Complexity Runtime can intuitively and accurately reflect the efficiency of an algorithm. If we want to accurately estimate the runtime of a piece of code, how should we proceed? 1. **Determine the running platform**, including hardware configuration, programming language, system environment, etc., as these factors all affect code execution efficiency. 2. **Evaluate the runtime required for various computational operations**, for example, an addition operation `+` requires 1 ns, a multiplication operation `*` requires 10 ns, a print operation `print()` requires 5 ns, etc. 3. **Count all computational operations in the code**, and sum the execution times of all operations to obtain the runtime. For example, in the following code, the input data size is $n$: === "Python" ```python title="" # On a certain running platform def algorithm(n: int): a = 2 # 1 ns a = a + 1 # 1 ns a = a * 2 # 10 ns # Loop n times for _ in range(n): # 1 ns print(0) # 5 ns ``` === "C++" ```cpp title="" // On a certain running platform void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // Loop n times for (int i = 0; i < n; i++) { // 1 ns cout << 0 << endl; // 5 ns } } ``` === "Java" ```java title="" // On a certain running platform void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // Loop n times for (int i = 0; i < n; i++) { // 1 ns System.out.println(0); // 5 ns } } ``` === "C#" ```csharp title="" // On a certain running platform void Algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // Loop n times for (int i = 0; i < n; i++) { // 1 ns Console.WriteLine(0); // 5 ns } } ``` === "Go" ```go title="" // On a certain running platform func algorithm(n int) { a := 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns // Loop n times for i := 0; i < n; i++ { // 1 ns fmt.Println(a) // 5 ns } } ``` === "Swift" ```swift title="" // On a certain running platform func algorithm(n: Int) { var a = 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns // Loop n times for _ in 0 ..< n { // 1 ns print(0) // 5 ns } } ``` === "JS" ```javascript title="" // On a certain running platform function algorithm(n) { var a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // Loop n times for(let i = 0; i < n; i++) { // 1 ns console.log(0); // 5 ns } } ``` === "TS" ```typescript title="" // On a certain running platform function algorithm(n: number): void { var a: number = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // Loop n times for(let i = 0; i < n; i++) { // 1 ns console.log(0); // 5 ns } } ``` === "Dart" ```dart title="" // On a certain running platform void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // Loop n times for (int i = 0; i < n; i++) { // 1 ns print(0); // 5 ns } } ``` === "Rust" ```rust title="" // On a certain running platform fn algorithm(n: i32) { let mut a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // Loop n times for _ in 0..n { // 1 ns println!("{}", 0); // 5 ns } } ``` === "C" ```c title="" // On a certain running platform void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // Loop n times for (int i = 0; i < n; i++) { // 1 ns printf("%d", 0); // 5 ns } } ``` === "Kotlin" ```kotlin title="" // On a certain running platform fun algorithm(n: Int) { var a = 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns // Loop n times for (i in 0.. 1$ it is slower than algorithm `A`, and when $n > 1000000$ it is slower than algorithm `C`. In fact, as long as the input data size $n$ is sufficiently large, an algorithm with "constant order" complexity will always be superior to one with "linear order" complexity, which is precisely the meaning of time growth trend. - **The derivation method for time complexity is simpler**. Obviously, the running platform and the types of computational operations are both unrelated to the growth trend of the algorithm's runtime. Therefore, in time complexity analysis, we can simply treat the execution time of all computational operations as the same "unit time", thus simplifying "counting computational operation runtime" to "counting the number of computational operations", which greatly reduces the difficulty of estimation. - **Time complexity also has certain limitations**. For example, although algorithms `A` and `C` have the same time complexity, their actual runtimes differ significantly. Similarly, although algorithm `B` has a higher time complexity than `C`, when the input data size $n$ is small, algorithm `B` is clearly superior to algorithm `C`. In such cases, it is often difficult to judge the efficiency of algorithms based solely on time complexity. Of course, despite the above issues, complexity analysis remains the most effective and commonly used method for evaluating algorithm efficiency. ## Asymptotic Upper Bound of Functions Given a function with input size $n$: === "Python" ```python title="" def algorithm(n: int): a = 1 # +1 a = a + 1 # +1 a = a * 2 # +1 # Loop n times for i in range(n): # +1 print(0) # +1 ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // Loop n times for (int i = 0; i < n; i++) { // +1 (i++ is executed each round) cout << 0 << endl; // +1 } } ``` === "Java" ```java title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // Loop n times for (int i = 0; i < n; i++) { // +1 (i++ is executed each round) System.out.println(0); // +1 } } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // Loop n times for (int i = 0; i < n; i++) { // +1 (i++ is executed each round) Console.WriteLine(0); // +1 } } ``` === "Go" ```go title="" func algorithm(n int) { a := 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // Loop n times for i := 0; i < n; i++ { // +1 fmt.Println(a) // +1 } } ``` === "Swift" ```swift title="" func algorithm(n: Int) { var a = 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // Loop n times for _ in 0 ..< n { // +1 print(0) // +1 } } ``` === "JS" ```javascript title="" function algorithm(n) { var a = 1; // +1 a += 1; // +1 a *= 2; // +1 // Loop n times for(let i = 0; i < n; i++){ // +1 (i++ is executed each round) console.log(0); // +1 } } ``` === "TS" ```typescript title="" function algorithm(n: number): void{ var a: number = 1; // +1 a += 1; // +1 a *= 2; // +1 // Loop n times for(let i = 0; i < n; i++){ // +1 (i++ is executed each round) console.log(0); // +1 } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // Loop n times for (int i = 0; i < n; i++) { // +1 (i++ is executed each round) print(0); // +1 } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let mut a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // Loop n times for _ in 0..n { // +1 (i++ is executed each round) println!("{}", 0); // +1 } } ``` === "C" ```c title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // Loop n times for (int i = 0; i < n; i++) { // +1 (i++ is executed each round) printf("%d", 0); // +1 } } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { var a = 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // Loop n times for (i in 0..big-$O$ notation, representing the asymptotic upper bound of the function $T(n)$. Time complexity analysis essentially calculates the asymptotic upper bound of "the number of operations $T(n)$", which has a clear mathematical definition. !!! note "Asymptotic upper bound of functions" If there exist positive real numbers $c$ and $n_0$ such that for all $n > n_0$, we have $T(n) \leq c \cdot f(n)$, then $f(n)$ can be considered as an asymptotic upper bound of $T(n)$, denoted as $T(n) = O(f(n))$. As shown in the figure below, calculating the asymptotic upper bound is to find a function $f(n)$ such that when $n$ tends to infinity, $T(n)$ and $f(n)$ are at the same growth level, differing only by a constant coefficient $c$. ![Asymptotic upper bound of a function](time_complexity.assets/asymptotic_upper_bound.png) ## Derivation Method The asymptotic upper bound has a bit of mathematical flavor. If you feel you haven't fully understood it, don't worry. We can first master the derivation method, and gradually grasp its mathematical meaning through continuous practice. According to the definition, after determining $f(n)$, we can obtain the time complexity $O(f(n))$. So how do we determine the asymptotic upper bound $f(n)$? Overall, it is divided into two steps: first count the number of operations, then determine the asymptotic upper bound. ### Step 1: Count the Number of Operations For code, count from top to bottom line by line. However, since the constant coefficient $c$ in $c \cdot f(n)$ above can be of any size, **coefficients and constant terms in the number of operations $T(n)$ can all be ignored**. According to this principle, the following counting simplification techniques can be summarized. 1. **Ignore constants in $T(n)$**. Because they are all independent of $n$, they do not affect time complexity. 2. **Omit all coefficients**. For example, looping $2n$ times, $5n + 1$ times, etc., can all be simplified as $n$ times, because the coefficient before $n$ does not affect time complexity. 3. **Use multiplication for nested loops**. The total number of operations equals the product of the number of operations in the outer and inner loops, with each layer of loop still able to apply techniques `1.` and `2.` separately. Given a function, we can use the above techniques to count the number of operations: === "Python" ```python title="" def algorithm(n: int): a = 1 # +0 (Technique 1) a = a + n # +0 (Technique 1) # +n (Technique 2) for i in range(5 * n + 1): print(0) # +n*n (Technique 3) for i in range(2 * n): for j in range(n + 1): print(0) ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 1; // +0 (Technique 1) a = a + n; // +0 (Technique 1) // +n (Technique 2) for (int i = 0; i < 5 * n + 1; i++) { cout << 0 << endl; } // +n*n (Technique 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { cout << 0 << endl; } } } ``` === "Java" ```java title="" void algorithm(int n) { int a = 1; // +0 (Technique 1) a = a + n; // +0 (Technique 1) // +n (Technique 2) for (int i = 0; i < 5 * n + 1; i++) { System.out.println(0); } // +n*n (Technique 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { System.out.println(0); } } } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 1; // +0 (Technique 1) a = a + n; // +0 (Technique 1) // +n (Technique 2) for (int i = 0; i < 5 * n + 1; i++) { Console.WriteLine(0); } // +n*n (Technique 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { Console.WriteLine(0); } } } ``` === "Go" ```go title="" func algorithm(n int) { a := 1 // +0 (Technique 1) a = a + n // +0 (Technique 1) // +n (Technique 2) for i := 0; i < 5 * n + 1; i++ { fmt.Println(0) } // +n*n (Technique 3) for i := 0; i < 2 * n; i++ { for j := 0; j < n + 1; j++ { fmt.Println(0) } } } ``` === "Swift" ```swift title="" func algorithm(n: Int) { var a = 1 // +0 (Technique 1) a = a + n // +0 (Technique 1) // +n (Technique 2) for _ in 0 ..< (5 * n + 1) { print(0) } // +n*n (Technique 3) for _ in 0 ..< (2 * n) { for _ in 0 ..< (n + 1) { print(0) } } } ``` === "JS" ```javascript title="" function algorithm(n) { let a = 1; // +0 (Technique 1) a = a + n; // +0 (Technique 1) // +n (Technique 2) for (let i = 0; i < 5 * n + 1; i++) { console.log(0); } // +n*n (Technique 3) for (let i = 0; i < 2 * n; i++) { for (let j = 0; j < n + 1; j++) { console.log(0); } } } ``` === "TS" ```typescript title="" function algorithm(n: number): void { let a = 1; // +0 (Technique 1) a = a + n; // +0 (Technique 1) // +n (Technique 2) for (let i = 0; i < 5 * n + 1; i++) { console.log(0); } // +n*n (Technique 3) for (let i = 0; i < 2 * n; i++) { for (let j = 0; j < n + 1; j++) { console.log(0); } } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 1; // +0 (Technique 1) a = a + n; // +0 (Technique 1) // +n (Technique 2) for (int i = 0; i < 5 * n + 1; i++) { print(0); } // +n*n (Technique 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { print(0); } } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let mut a = 1; // +0 (Technique 1) a = a + n; // +0 (Technique 1) // +n (Technique 2) for i in 0..(5 * n + 1) { println!("{}", 0); } // +n*n (Technique 3) for i in 0..(2 * n) { for j in 0..(n + 1) { println!("{}", 0); } } } ``` === "C" ```c title="" void algorithm(int n) { int a = 1; // +0 (Technique 1) a = a + n; // +0 (Technique 1) // +n (Technique 2) for (int i = 0; i < 5 * n + 1; i++) { printf("%d", 0); } // +n*n (Technique 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { printf("%d", 0); } } } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { var a = 1 // +0 (Technique 1) a = a + n // +0 (Technique 1) // +n (Technique 2) for (i in 0..<5 * n + 1) { println(0) } // +n*n (Technique 3) for (i in 0..<2 * n) { for (j in 0.. Table   Time complexities corresponding to different numbers of operations

| Number of Operations $T(n)$ | Time Complexity $O(f(n))$ | | ---------------------- | -------------------- | | $100000$ | $O(1)$ | | $3n + 2$ | $O(n)$ | | $2n^2 + 3n + 2$ | $O(n^2)$ | | $n^3 + 10000n^2$ | $O(n^3)$ | | $2^n + 10000n^{10000}$ | $O(2^n)$ | ## Common Types Let the input data size be $n$. Common time complexity types are shown in the figure below (arranged in order from low to high). $$ \begin{aligned} O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline \text{Constant order} < \text{Logarithmic order} < \text{Linear order} < \text{Linearithmic order} < \text{Quadratic order} < \text{Exponential order} < \text{Factorial order} \end{aligned} $$ ![Common time complexity types](time_complexity.assets/time_complexity_common_types.png) ### Constant Order $O(1)$ The number of operations in constant order is independent of the input data size $n$, meaning it does not change as $n$ changes. In the following function, although the number of operations `size` may be large, since it is independent of the input data size $n$, the time complexity remains $O(1)$: ```src [file]{time_complexity}-[class]{}-[func]{constant} ``` ### Linear Order $O(n)$ The number of operations in linear order grows linearly relative to the input data size $n$. Linear order typically appears in single-layer loops: ```src [file]{time_complexity}-[class]{}-[func]{linear} ``` Operations such as traversing arrays and traversing linked lists have a time complexity of $O(n)$, where $n$ is the length of the array or linked list: ```src [file]{time_complexity}-[class]{}-[func]{array_traversal} ``` It is worth noting that **the input data size $n$ should be determined according to the type of input data**. For example, in the first example, the variable $n$ is the input data size; in the second example, the array length $n$ is the data size. ### Quadratic Order $O(n^2)$ The number of operations in quadratic order grows quadratically relative to the input data size $n$. Quadratic order typically appears in nested loops, where both the outer and inner loops have a time complexity of $O(n)$, resulting in an overall time complexity of $O(n^2)$: ```src [file]{time_complexity}-[class]{}-[func]{quadratic} ``` The figure below compares constant order, linear order, and quadratic order time complexities. ![Time complexities of constant, linear, and quadratic orders](time_complexity.assets/time_complexity_constant_linear_quadratic.png) Taking bubble sort as an example, the outer loop executes $n - 1$ times, and the inner loop executes $n-1$, $n-2$, $\dots$, $2$, $1$ times, averaging $n / 2$ times, resulting in a time complexity of $O((n - 1) n / 2) = O(n^2)$: ```src [file]{time_complexity}-[class]{}-[func]{bubble_sort} ``` ### Exponential Order $O(2^n)$ Biological "cell division" is a typical example of exponential order growth: the initial state is $1$ cell, after one round of division it becomes $2$, after two rounds it becomes $4$, and so on; after $n$ rounds of division there are $2^n$ cells. The figure below and the following code simulate the cell division process, with a time complexity of $O(2^n)$. Note that the input $n$ represents the number of division rounds, and the return value `count` represents the total number of divisions. ```src [file]{time_complexity}-[class]{}-[func]{exponential} ``` ![Time complexity of exponential order](time_complexity.assets/time_complexity_exponential.png) In actual algorithms, exponential order often appears in recursive functions. For example, in the following code, it recursively splits in two, stopping after $n$ splits: ```src [file]{time_complexity}-[class]{}-[func]{exp_recur} ``` Exponential order growth is very rapid and is common in exhaustive methods (brute force search, backtracking, etc.). For problems with large data scales, exponential order is unacceptable and typically requires dynamic programming or greedy algorithms to solve. ### Logarithmic Order $O(\log n)$ In contrast to exponential order, logarithmic order reflects the situation of "reducing to half each round". Let the input data size be $n$. Since it is reduced to half each round, the number of loops is $\log_2 n$, which is the inverse function of $2^n$. The figure below and the following code simulate the process of "reducing to half each round", with a time complexity of $O(\log_2 n)$, abbreviated as $O(\log n)$: ```src [file]{time_complexity}-[class]{}-[func]{logarithmic} ``` ![Time complexity of logarithmic order](time_complexity.assets/time_complexity_logarithmic.png) Like exponential order, logarithmic order also commonly appears in recursive functions. The following code forms a recursion tree of height $\log_2 n$: ```src [file]{time_complexity}-[class]{}-[func]{log_recur} ``` Logarithmic order commonly appears in algorithms based on the divide-and-conquer strategy, embodying the algorithmic thinking of "dividing into many" and "simplifying complexity". It grows slowly and is the ideal time complexity second only to constant order. !!! tip "What is the base of $O(\log n)$?" To be precise, "dividing into $m$" corresponds to a time complexity of $O(\log_m n)$. And through the logarithmic base change formula, we can obtain time complexities with different bases that are equal: $$ O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n) $$ That is to say, the base $m$ can be converted without affecting the complexity. Therefore, we usually omit the base $m$ and denote logarithmic order simply as $O(\log n)$. ### Linearithmic Order $O(n \log n)$ Linearithmic order commonly appears in nested loops, where the time complexities of the two layers of loops are $O(\log n)$ and $O(n)$ respectively. The relevant code is as follows: ```src [file]{time_complexity}-[class]{}-[func]{linear_log_recur} ``` The figure below shows how linearithmic order is generated. Each level of the binary tree has a total of $n$ operations, and the tree has $\log_2 n + 1$ levels, resulting in a time complexity of $O(n \log n)$. ![Time complexity of linearithmic order](time_complexity.assets/time_complexity_logarithmic_linear.png) Mainstream sorting algorithms typically have a time complexity of $O(n \log n)$, such as quicksort, merge sort, and heap sort. ### Factorial Order $O(n!)$ Factorial order corresponds to the mathematical "permutation" problem. Given $n$ distinct elements, find all possible permutation schemes; the number of schemes is: $$ n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1 $$ Factorials are typically implemented using recursion. As shown in the figure below and the following code, the first level splits into $n$ branches, the second level splits into $n - 1$ branches, and so on, until the $n$-th level when splitting stops: ```src [file]{time_complexity}-[class]{}-[func]{factorial_recur} ``` ![Time complexity of factorial order](time_complexity.assets/time_complexity_factorial.png) Note that because when $n \geq 4$ we always have $n! > 2^n$, factorial order grows faster than exponential order, and is also unacceptable for large $n$. ## Worst, Best, and Average Time Complexities **The time efficiency of an algorithm is often not fixed, but is related to the distribution of the input data**. Suppose we input an array `nums` of length $n$, where `nums` consists of numbers from $1$ to $n$, with each number appearing only once, but the element order is randomly shuffled. The task is to return the index of element $1$. We can draw the following conclusions. - When `nums = [?, ?, ..., 1]`, i.e., when the last element is $1$, it requires a complete traversal of the array, **reaching worst-case time complexity $O(n)$**. - When `nums = [1, ?, ?, ...]`, i.e., when the first element is $1$, no matter how long the array is, there is no need to continue traversing, **reaching best-case time complexity $\Omega(1)$**. The "worst-case time complexity" corresponds to the function's asymptotic upper bound, denoted using big-$O$ notation. Correspondingly, the "best-case time complexity" corresponds to the function's asymptotic lower bound, denoted using $\Omega$ notation: ```src [file]{worst_best_time_complexity}-[class]{}-[func]{find_one} ``` It is worth noting that we rarely use best-case time complexity in practice, because it can usually only be achieved with a very small probability and may be somewhat misleading. **The worst-case time complexity is more practical because it gives a safety value for efficiency**, allowing us to use the algorithm with confidence. From the above example, we can see that both worst-case and best-case time complexities only occur under "special data distributions", which may have a very small probability of occurrence and may not truly reflect the algorithm's running efficiency. In contrast, **average time complexity can reflect the algorithm's running efficiency under random input data**, denoted using the $\Theta$ notation. For some algorithms, we can simply derive the average case under random data distribution. For example, in the above example, since the input array is shuffled, the probability of element $1$ appearing at any index is equal, so the algorithm's average number of loops is half the array length $n / 2$, giving an average time complexity of $\Theta(n / 2) = \Theta(n)$. But for more complex algorithms, calculating average time complexity is often quite difficult, because it is hard to analyze the overall mathematical expectation under data distribution. In this case, we usually use worst-case time complexity as the criterion for judging algorithm efficiency. !!! question "Why is the $\Theta$ symbol rarely seen?" This may be because the $O$ symbol is too catchy, so we often use it to represent average time complexity. But strictly speaking, this practice is not standard. In this book and other materials, if you encounter expressions like "average time complexity $O(n)$", please understand it directly as $\Theta(n)$. ================================================ FILE: en/docs/chapter_data_structure/basic_data_types.md ================================================ # Basic Data Types When we talk about data in computers, we think of various forms such as text, images, videos, audio, 3D models, and more. Although these data are organized in different ways, they are all composed of various basic data types. **Basic data types are types that the CPU can directly operate on**, and they are directly used in algorithms, mainly including the following. - Integer types `byte`, `short`, `int`, `long`. - Floating-point types `float`, `double`, used to represent decimal numbers. - Character type `char`, used to represent letters, punctuation marks, and even emojis in various languages. - Boolean type `bool`, used to represent "yes" and "no" judgments. **Basic data types are stored in binary form in computers**. One binary bit is $1$ bit. In most modern operating systems, $1$ byte consists of $8$ bits. The range of values for basic data types depends on the size of the space they occupy. Below is an example using Java. - Integer type `byte` occupies $1$ byte = $8$ bits, and can represent $2^{8}$ numbers. - Integer type `int` occupies $4$ bytes = $32$ bits, and can represent $2^{32}$ numbers. The following table lists the space occupied, value ranges, and default values of various basic data types in Java. You don't need to memorize this table; a general understanding is sufficient, and you can refer to it when needed.

Table   Space occupied and value ranges of basic data types

| Type | Symbol | Space Occupied | Minimum Value | Maximum Value | Default Value | | ---------- | -------- | -------------- | ------------------------ | ----------------------- | -------------- | | Integer | `byte` | 1 byte | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | $0$ | | | `short` | 2 bytes | $-2^{15}$ | $2^{15} - 1$ | $0$ | | | `int` | 4 bytes | $-2^{31}$ | $2^{31} - 1$ | $0$ | | | `long` | 8 bytes | $-2^{63}$ | $2^{63} - 1$ | $0$ | | Float | `float` | 4 bytes | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ | | | `double` | 8 bytes | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | $0.0$ | | Character | `char` | 2 bytes | $0$ | $2^{16} - 1$ | $0$ | | Boolean | `bool` | 1 byte | $\text{false}$ | $\text{true}$ | $\text{false}$ | Please note that the above table is specific to Java's basic data types. Each programming language has its own data type definitions, and their space occupied, value ranges, and default values may vary. - In Python, the integer type `int` can be of any size, limited only by available memory; the floating-point type `float` is double-precision 64-bit; there is no `char` type, a single character is actually a string `str` of length 1. - C and C++ do not explicitly specify the size of basic data types, which varies by implementation and platform. The above table follows the LP64 [data model](https://en.cppreference.com/w/cpp/language/types#Properties), which is used in Unix 64-bit operating systems including Linux and macOS. - The size of character `char` is 1 byte in C and C++, and in most programming languages it depends on the specific character encoding method, as detailed in the "Character Encoding" section. - Even though representing a boolean value requires only 1 bit ($0$ or $1$), it is usually stored as 1 byte in memory. This is because modern computer CPUs typically use 1 byte as the minimum addressable memory unit. So, what is the relationship between basic data types and data structures? We know that data structures are ways of organizing and storing data in computers. The subject of this statement is "structure", not "data". If we want to represent "a row of numbers", we naturally think of using an array. This is because the linear structure of an array can represent the adjacency and order relationships of numbers, but the content stored—whether integer `int`, floating-point `float`, or character `char`—is unrelated to the "data structure". In other words, **basic data types provide the "content type" of data, while data structures provide the "organization method" of data**. For example, in the following code, we use the same data structure (array) to store and represent different basic data types, including `int`, `float`, `char`, `bool`, etc. === "Python" ```python title="" # Initialize arrays using various basic data types numbers: list[int] = [0] * 5 decimals: list[float] = [0.0] * 5 # In Python, characters are actually strings of length 1 characters: list[str] = ['0'] * 5 bools: list[bool] = [False] * 5 # Python lists can freely store various basic data types and object references data = [0, 0.0, 'a', False, ListNode(0)] ``` === "C++" ```cpp title="" // Initialize arrays using various basic data types int numbers[5]; float decimals[5]; char characters[5]; bool bools[5]; ``` === "Java" ```java title="" // Initialize arrays using various basic data types int[] numbers = new int[5]; float[] decimals = new float[5]; char[] characters = new char[5]; boolean[] bools = new boolean[5]; ``` === "C#" ```csharp title="" // Initialize arrays using various basic data types int[] numbers = new int[5]; float[] decimals = new float[5]; char[] characters = new char[5]; bool[] bools = new bool[5]; ``` === "Go" ```go title="" // Initialize arrays using various basic data types var numbers = [5]int{} var decimals = [5]float64{} var characters = [5]byte{} var bools = [5]bool{} ``` === "Swift" ```swift title="" // Initialize arrays using various basic data types let numbers = Array(repeating: 0, count: 5) let decimals = Array(repeating: 0.0, count: 5) let characters: [Character] = Array(repeating: "a", count: 5) let bools = Array(repeating: false, count: 5) ``` === "JS" ```javascript title="" // JavaScript arrays can freely store various basic data types and objects const array = [0, 0.0, 'a', false]; ``` === "TS" ```typescript title="" // Initialize arrays using various basic data types const numbers: number[] = []; const characters: string[] = []; const bools: boolean[] = []; ``` === "Dart" ```dart title="" // Initialize arrays using various basic data types List numbers = List.filled(5, 0); List decimals = List.filled(5, 0.0); List characters = List.filled(5, 'a'); List bools = List.filled(5, false); ``` === "Rust" ```rust title="" // Initialize arrays using various basic data types let numbers: Vec = vec![0; 5]; let decimals: Vec = vec![0.0; 5]; let characters: Vec = vec!['0'; 5]; let bools: Vec = vec![false; 5]; ``` === "C" ```c title="" // Initialize arrays using various basic data types int numbers[10]; float decimals[10]; char characters[10]; bool bools[10]; ``` === "Kotlin" ```kotlin title="" // Initialize arrays using various basic data types val numbers = IntArray(5) val decinals = FloatArray(5) val characters = CharArray(5) val bools = BooleanArray(5) ``` === "Ruby" ```ruby title="" # Ruby lists can freely store various basic data types and object references data = [0, 0.0, 'a', false, ListNode(0)] ``` ??? pythontutor "Visualized Execution" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%9D%A5%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20*%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%AD%97%E7%AC%A6%E5%AE%9E%E9%99%85%E4%B8%8A%E6%98%AF%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%0A%20%20%20%20characters%20%3D%20%5B'0'%5D%20*%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%88%97%E8%A1%A8%E5%8F%AF%E4%BB%A5%E8%87%AA%E7%94%B1%E5%AD%98%E5%82%A8%E5%90%84%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%0A%20%20%20%20data%20%3D%20%5B0,%200.0,%20'a',%20False,%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: en/docs/chapter_data_structure/character_encoding.md ================================================ # Character Encoding * In computers, all data is stored in binary form, and character `char` is no exception. To represent characters, we need to establish a "character set" that defines a one-to-one correspondence between each character and binary numbers. With a character set, computers can convert binary numbers to characters by looking up the table. ## Ascii Character Set ASCII code is the earliest character set, with the full name American Standard Code for Information Interchange. It uses 7 binary bits (the lower 7 bits of one byte) to represent a character, and can represent a maximum of 128 different characters. As shown in the figure below, ASCII code includes uppercase and lowercase English letters, numbers 0 ~ 9, some punctuation marks, and some control characters (such as newline and tab). ![ASCII code](character_encoding.assets/ascii_table.png) However, **ASCII code can only represent English**. With the globalization of computers, a character set called EASCII that can represent more languages emerged. It expands from the 7-bit basis of ASCII to 8 bits, and can represent 256 different characters. Worldwide, a batch of EASCII character sets suitable for different regions have appeared successively. The first 128 characters of these character sets are unified as ASCII code, and the last 128 characters are defined differently to adapt to the needs of different languages. ## Gbk Character Set Later, people found that **EASCII code still cannot meet the character quantity requirements of many languages**. For example, there are nearly one hundred thousand Chinese characters, and several thousand are used daily. In 1980, the China National Standardization Administration released the GB2312 character set, which included 6,763 Chinese characters, basically meeting the needs for computer processing of Chinese characters. However, GB2312 cannot handle some rare characters and traditional Chinese characters. The GBK character set is an extension based on GB2312, which includes a total of 21,886 Chinese characters. In the GBK encoding scheme, ASCII characters are represented using one byte, and Chinese characters are represented using two bytes. ## Unicode Character Set With the vigorous development of computer technology, character sets and encoding standards flourished, which brought many problems. On the one hand, these character sets generally only define characters for specific languages and cannot work normally in multilingual environments. On the other hand, multiple character set standards exist for the same language, and if two computers use different encoding standards, garbled characters will appear during information transmission. Researchers of that era thought: **If a sufficiently complete character set is released that includes all languages and symbols in the world, wouldn't it be possible to solve cross-language environment and garbled character problems**? Driven by this idea, a large and comprehensive character set, Unicode, was born. Unicode is called "统一码" (Unified Code) in Chinese and can theoretically accommodate over one million characters. It is committed to including characters from around the world into a unified character set, providing a universal character set to handle and display various language texts, reducing garbled character problems caused by different encoding standards. Since its release in 1991, Unicode has continuously expanded to include new languages and characters. As of September 2022, Unicode has included 149,186 characters, including characters, symbols, and even emojis from various languages. In the vast Unicode character set, commonly used characters occupy 2 bytes, and some rare characters occupy 3 bytes or even 4 bytes. Unicode is a universal character set that essentially assigns a number (called a "code point") to each character, **but it does not specify how to store these character code points in computers**. We can't help but ask: when Unicode code points of multiple lengths appear simultaneously in a text, how does the system parse the characters? For example, given an encoding with a length of 2 bytes, how does the system determine whether it is one 2-byte character or two 1-byte characters? For the above problem, **a straightforward solution is to store all characters as equal-length encodings**. As shown in the figure below, each character in "Hello" occupies 1 byte, and each character in "算法" (algorithm) occupies 2 bytes. We can encode all characters in "Hello 算法" as 2 bytes in length by padding the high bits with 0. In this way, the system can parse one character every 2 bytes and restore the content of this phrase. ![Unicode encoding example](character_encoding.assets/unicode_hello_algo.png) However, ASCII code has already proven to us that encoding English only requires 1 byte. If the above scheme is adopted, the size of English text will be twice that under ASCII encoding, which is very wasteful of memory space. Therefore, we need a more efficient Unicode encoding method. ## Utf-8 Encoding Currently, UTF-8 has become the most widely used Unicode encoding method internationally. **It is a variable-length encoding** that uses 1 to 4 bytes to represent a character, depending on the complexity of the character. ASCII characters only require 1 byte, Latin and Greek letters require 2 bytes, commonly used Chinese characters require 3 bytes, and some other rare characters require 4 bytes. The encoding rules of UTF-8 are not complicated and can be divided into the following two cases. - For 1-byte characters, set the highest bit to $0$, and set the remaining 7 bits to the Unicode code point. It is worth noting that ASCII characters occupy the first 128 code points in the Unicode character set. That is to say, **UTF-8 encoding is backward compatible with ASCII code**. This means we can use UTF-8 to parse very old ASCII code text. - For characters with a length of $n$ bytes (where $n > 1$), set the highest $n$ bits of the first byte to $1$, and set the $(n + 1)$-th bit to $0$; starting from the second byte, set the highest 2 bits of each byte to $10$; use all remaining bits to fill in the Unicode code point of the character. The figure below shows the UTF-8 encoding corresponding to "Hello算法". It can be observed that since the highest $n$ bits are all set to $1$, the system can parse the length of the character as $n$ by reading the number of highest bits that are $1$. But why set the highest 2 bits of all other bytes to $10$? In fact, this $10$ can serve as a check symbol. Assuming the system starts parsing text from an incorrect byte, the $10$ at the beginning of the byte can help the system quickly determine an anomaly. The reason for using $10$ as a check symbol is that under UTF-8 encoding rules, it is impossible for a character's highest two bits to be $10$. This conclusion can be proven by contradiction: assuming the highest two bits of a character are $10$, it means the length of the character is $1$, corresponding to ASCII code. However, the highest bit of ASCII code should be $0$, which contradicts the assumption. ![UTF-8 encoding example](character_encoding.assets/utf-8_hello_algo.png) In addition to UTF-8, common encoding methods also include the following two. - **UTF-16 encoding**: Uses 2 or 4 bytes to represent a character. All ASCII characters and commonly used non-English characters are represented with 2 bytes; a few characters need to use 4 bytes. For 2-byte characters, UTF-16 encoding is equal to the Unicode code point. - **UTF-32 encoding**: Every character uses 4 bytes. This means that UTF-32 takes up more space than UTF-8 and UTF-16, especially for text with a high proportion of ASCII characters. From the perspective of storage space occupation, using UTF-8 to represent English characters is very efficient because it only requires 1 byte; using UTF-16 encoding for some non-English characters (such as Chinese) will be more efficient because it only requires 2 bytes, while UTF-8 may require 3 bytes. From a compatibility perspective, UTF-8 has the best universality, and many tools and libraries support UTF-8 first. ## Character Encoding in Programming Languages For most past programming languages, strings during program execution use fixed-length encodings such as UTF-16 or UTF-32. Under fixed-length encoding, we can treat strings as arrays for processing, and this approach has the following advantages. - **Random access**: UTF-16 encoded strings can be easily accessed randomly. UTF-8 is a variable-length encoding. To find the $i$-th character, we need to traverse from the beginning of the string to the $i$-th character, which requires $O(n)$ time. - **Character counting**: Similar to random access, calculating the length of a UTF-16 encoded string is also an $O(1)$ operation. However, calculating the length of a UTF-8 encoded string requires traversing the entire string. - **String operations**: Many string operations (such as splitting, joining, inserting, deleting, etc.) on UTF-16 encoded strings are easier to perform. Performing these operations on UTF-8 encoded strings usually requires additional calculations to ensure that invalid UTF-8 encoding is not generated. In fact, the design of character encoding schemes for programming languages is a very interesting topic involving many factors. - Java's `String` type uses UTF-16 encoding, with each character occupying 2 bytes. This is because at the beginning of Java language design, people believed that 16 bits were sufficient to represent all possible characters. However, this was an incorrect judgment. Later, the Unicode specification expanded beyond 16 bits, so characters in Java may now be represented by a pair of 16-bit values (called "surrogate pairs"). - The strings of JavaScript and TypeScript use UTF-16 encoding for reasons similar to Java. When Netscape first introduced the JavaScript language in 1995, Unicode was still in its early stages of development, and at that time, using 16-bit encoding was sufficient to represent all Unicode characters. - C# uses UTF-16 encoding mainly because the .NET platform was designed by Microsoft, and many of Microsoft's technologies (including the Windows operating system) extensively use UTF-16 encoding. Due to the underestimation of character quantities by the above programming languages, they had to adopt the "surrogate pair" method to represent Unicode characters with lengths exceeding 16 bits. This is a reluctant compromise. On the one hand, in strings containing surrogate pairs, one character may occupy 2 bytes or 4 bytes, thus losing the advantage of fixed-length encoding. On the other hand, handling surrogate pairs requires additional code, which increases the complexity and difficulty of debugging in programming. For the above reasons, some programming languages have proposed different encoding schemes. - Python's `str` uses Unicode encoding and adopts a flexible string representation where the stored character length depends on the largest Unicode code point in the string. If all characters in the string are ASCII characters, each character occupies 1 byte; if there are characters exceeding the ASCII range but all within the Basic Multilingual Plane (BMP), each character occupies 2 bytes; if there are characters exceeding the BMP, each character occupies 4 bytes. - Go language's `string` type uses UTF-8 encoding internally. Go language also provides the `rune` type, which is used to represent a single Unicode code point. - Rust language's `str` and `String` types use UTF-8 encoding internally. Rust also provides the `char` type for representing a single Unicode code point. It should be noted that the above discussion is about how strings are stored in programming languages, **which is different from how strings are stored in files or transmitted over networks**. In file storage or network transmission, we usually encode strings into UTF-8 format to achieve optimal compatibility and space efficiency. ================================================ FILE: en/docs/chapter_data_structure/classification_of_data_structure.md ================================================ # Classification of Data Structures Common data structures include arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs. They can be classified from two dimensions: "logical structure" and "physical structure". ## Logical Structure: Linear and Non-Linear **Logical structure reveals the logical relationships between data elements**. In arrays and linked lists, data is arranged in a certain order, embodying the linear relationship between data; while in trees, data is arranged hierarchically from top to bottom, showing the derived relationship between "ancestors" and "descendants"; graphs are composed of nodes and edges, reflecting complex network relationships. As shown in the figure below, logical structures can be divided into two major categories: "linear" and "non-linear". Linear structures are more intuitive, indicating that data is linearly arranged in logical relationships; non-linear structures are the opposite, arranged non-linearly. - **Linear data structures**: Arrays, linked lists, stacks, queues, hash tables, where elements have a one-to-one sequential relationship. - **Non-linear data structures**: Trees, heaps, graphs, hash tables. Non-linear data structures can be further divided into tree structures and network structures. - **Tree structures**: Trees, heaps, hash tables, where elements have a one-to-many relationship. - **Network structures**: Graphs, where elements have a many-to-many relationship. ![Linear and non-linear data structures](classification_of_data_structure.assets/classification_logic_structure.png) ## Physical Structure: Contiguous and Dispersed **When an algorithm program runs, the data being processed is mainly stored in memory**. The figure below shows a computer memory stick, where each black square contains a memory space. We can imagine memory as a huge Excel spreadsheet, where each cell can store a certain amount of data. **The system accesses data at the target location through memory addresses**. As shown in the figure below, the computer assigns a number to each cell in the spreadsheet according to specific rules, ensuring that each memory space has a unique memory address. With these addresses, the program can access data in memory. ![Memory stick, memory space, memory address](classification_of_data_structure.assets/computer_memory_location.png) !!! tip It is worth noting that comparing memory to an Excel spreadsheet is a simplified analogy. The actual working mechanism of memory is quite complex, involving concepts such as address space, memory management, cache mechanisms, virtual memory, and physical memory. Memory is a shared resource for all programs. When a block of memory is occupied by a program, it usually cannot be used by other programs at the same time. **Therefore, in the design of data structures and algorithms, memory resources are an important consideration**. For example, the peak memory occupied by an algorithm should not exceed the remaining free memory of the system; if there is a lack of contiguous large memory blocks, then the data structure chosen must be able to be stored in dispersed memory spaces. As shown in the figure below, **physical structure reflects the way data is stored in computer memory**, and can be divided into contiguous space storage (arrays) and dispersed space storage (linked lists). The two physical structures exhibit complementary characteristics in terms of time efficiency and space efficiency. ![Contiguous space storage and dispersed space storage](classification_of_data_structure.assets/classification_phisical_structure.png) It is worth noting that **all data structures are implemented based on arrays, linked lists, or a combination of both**. For example, stacks and queues can be implemented using either arrays or linked lists; while the implementation of hash tables may include both arrays and linked lists. - **Can be implemented based on arrays**: Stacks, queues, hash tables, trees, heaps, graphs, matrices, tensors (arrays with dimensions $\geq 3$), etc. - **Can be implemented based on linked lists**: Stacks, queues, hash tables, trees, heaps, graphs, etc. After initialization, linked lists can still adjust their length during program execution, so they are also called "dynamic data structures". After initialization, the length of arrays cannot be changed, so they are also called "static data structures". It is worth noting that arrays can achieve length changes by reallocating memory, thus possessing a certain degree of "dynamism". !!! tip If you find it difficult to understand physical structure, it is recommended to read the next chapter first, and then review this section. ================================================ FILE: en/docs/chapter_data_structure/index.md ================================================ # Data Structures ![Data structures](../assets/covers/chapter_data_structure.jpg) !!! abstract Data structure is like a sturdy and diverse framework. It provides a blueprint for the orderly organization of data, upon which algorithms come to life. ================================================ FILE: en/docs/chapter_data_structure/number_encoding.md ================================================ # Number Encoding * !!! tip In this book, chapters marked with an asterisk * are optional readings. If you are short on time or find them challenging, you may skip these initially and return to them after completing the essential chapters. ## Sign-Magnitude, 1's Complement, and 2's Complement In the table from the previous section, we found that all integer types can represent one more negative number than positive numbers. For example, the `byte` range is $[-128, 127]$. This phenomenon is counterintuitive, and its underlying reason involves knowledge of sign-magnitude, 1's complement, and 2's complement. First, it should be noted that **numbers are stored in computers in the form of "2's complement"**. Before analyzing the reasons for this, let's first define these three concepts. - **Sign-magnitude**: We treat the highest bit of the binary representation of a number as the sign bit, where $0$ represents a positive number and $1$ represents a negative number, and the remaining bits represent the value of the number. - **1's complement**: The 1's complement of a positive number is the same as its sign-magnitude. For a negative number, the 1's complement is obtained by inverting all bits except the sign bit of its sign-magnitude. - **2's complement**: The 2's complement of a positive number is the same as its sign-magnitude. For a negative number, the 2's complement is obtained by adding $1$ to its 1's complement. The figure below shows the conversion methods among sign-magnitude, 1's complement, and 2's complement. ![Conversions among sign-magnitude, 1's complement, and 2's complement](number_encoding.assets/1s_2s_complement.png) Sign-magnitude, although the most intuitive, has some limitations. On one hand, **the sign-magnitude of negative numbers cannot be directly used in operations**. For example, calculating $1 + (-2)$ in sign-magnitude yields $-3$, which is clearly incorrect. $$ \begin{aligned} & 1 + (-2) \newline & \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline & = 1000 \; 0011 \newline & \rightarrow -3 \end{aligned} $$ To solve this problem, computers introduced 1's complement. If we first convert sign-magnitude to 1's complement and calculate $1 + (-2)$ in 1's complement, then convert the result back to sign-magnitude, we can obtain the correct result of $-1$. $$ \begin{aligned} & 1 + (-2) \newline & \rightarrow 0000 \; 0001 \; \text{(Sign-magnitude)} + 1000 \; 0010 \; \text{(Sign-magnitude)} \newline & = 0000 \; 0001 \; \text{(1's complement)} + 1111 \; 1101 \; \text{(1's complement)} \newline & = 1111 \; 1110 \; \text{(1's complement)} \newline & = 1000 \; 0001 \; \text{(Sign-magnitude)} \newline & \rightarrow -1 \end{aligned} $$ On the other hand, **the sign-magnitude of the number zero has two representations, $+0$ and $-0$**. This means that the number zero corresponds to two different binary encodings, which may cause ambiguity. For example, in conditional judgments, if we don't distinguish between positive zero and negative zero, it may lead to incorrect judgment results. If we want to handle the ambiguity of positive and negative zero, we need to introduce additional judgment operations, which may reduce the computational efficiency of the computer. $$ \begin{aligned} +0 & \rightarrow 0000 \; 0000 \newline -0 & \rightarrow 1000 \; 0000 \end{aligned} $$ Like sign-magnitude, 1's complement also has the problem of positive and negative zero ambiguity. Therefore, computers further introduced 2's complement. Let's first observe the conversion process of negative zero from sign-magnitude to 1's complement to 2's complement: $$ \begin{aligned} -0 \rightarrow \; & 1000 \; 0000 \; \text{(Sign-magnitude)} \newline = \; & 1111 \; 1111 \; \text{(1's complement)} \newline = 1 \; & 0000 \; 0000 \; \text{(2's complement)} \newline \end{aligned} $$ Adding $1$ to the 1's complement of negative zero produces a carry, but since the `byte` type has a length of only 8 bits, the $1$ that overflows to the 9th bit is discarded. That is to say, **the 2's complement of negative zero is $0000 \; 0000$, which is the same as the 2's complement of positive zero**. This means that in 2's complement representation, there is only one zero, and the positive and negative zero ambiguity is thus resolved. One last question remains: the range of the `byte` type is $[-128, 127]$, and how is the extra negative number $-128$ obtained? We notice that all integers in the interval $[-127, +127]$ have corresponding sign-magnitude, 1's complement, and 2's complement, and sign-magnitude and 2's complement can be converted to each other. However, **the 2's complement $1000 \; 0000$ is an exception, and it does not have a corresponding sign-magnitude**. According to the conversion method, we get that the sign-magnitude of this 2's complement is $0000 \; 0000$. This is clearly contradictory because this sign-magnitude represents the number $0$, and its 2's complement should be itself. The computer specifies that this special 2's complement $1000 \; 0000$ represents $-128$. In fact, the result of calculating $(-1) + (-127)$ in 2's complement is $-128$. $$ \begin{aligned} & (-127) + (-1) \newline & \rightarrow 1111 \; 1111 \; \text{(Sign-magnitude)} + 1000 \; 0001 \; \text{(Sign-magnitude)} \newline & = 1000 \; 0000 \; \text{(1's complement)} + 1111 \; 1110 \; \text{(1's complement)} \newline & = 1000 \; 0001 \; \text{(2's complement)} + 1111 \; 1111 \; \text{(2's complement)} \newline & = 1000 \; 0000 \; \text{(2's complement)} \newline & \rightarrow -128 \end{aligned} $$ You may have noticed that all the above calculations are addition operations. This hints at an important fact: **the hardware circuits inside computers are mainly designed based on addition operations**. This is because addition operations are simpler to implement in hardware compared to other operations (such as multiplication, division, and subtraction), easier to parallelize, and have faster operation speeds. Please note that this does not mean that computers can only perform addition. **By combining addition with some basic logical operations, computers can implement various other mathematical operations**. For example, calculating the subtraction $a - b$ can be converted to calculating the addition $a + (-b)$; calculating multiplication and division can be converted to calculating multiple additions or subtractions. Now we can summarize the reasons why computers use 2's complement: based on 2's complement representation, computers can use the same circuits and operations to handle the addition of positive and negative numbers, without the need to design special hardware circuits to handle subtraction, and without the need to specially handle the ambiguity problem of positive and negative zero. This greatly simplifies hardware design and improves operational efficiency. The design of 2's complement is very ingenious. Due to space limitations, we will stop here. Interested readers are encouraged to explore further. ## Floating-Point Number Encoding Careful readers may have noticed: `int` and `float` have the same length, both are 4 bytes, but why does `float` have a much larger range than `int`? This is very counterintuitive because it stands to reason that `float` needs to represent decimals, so the range should be smaller. In fact, **this is because floating-point number `float` uses a different representation method**. Let's denote a 32-bit binary number as: $$ b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0 $$ According to the IEEE 754 standard, a 32-bit `float` consists of the following three parts. - Sign bit $\mathrm{S}$: occupies 1 bit, corresponding to $b_{31}$. - Exponent bit $\mathrm{E}$: occupies 8 bits, corresponding to $b_{30} b_{29} \ldots b_{23}$. - Fraction bit $\mathrm{N}$: occupies 23 bits, corresponding to $b_{22} b_{21} \ldots b_0$. The calculation method for the value corresponding to the binary `float` is: $$ \text {val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2-127} \times\left(1 . b_{22} b_{21} \ldots b_0\right)_2 $$ Converted to decimal, the calculation formula is: $$ \text {val}=(-1)^{\mathrm{S}} \times 2^{\mathrm{E} -127} \times (1 + \mathrm{N}) $$ The range of each component is: $$ \begin{aligned} \mathrm{S} \in & \{ 0, 1\}, \quad \mathrm{E} \in \{ 1, 2, \dots, 254 \} \newline (1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} 2^{-i}) \subset [1, 2 - 2^{-23}] \end{aligned} $$ ![Calculation example of float under IEEE 754 standard](number_encoding.assets/ieee_754_float.png) Observing the figure above, given example data $\mathrm{S} = 0$, $\mathrm{E} = 124$, $\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$, we have: $$ \text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875 $$ Now we can answer the initial question: **the representation of `float` includes an exponent bit, resulting in a range far greater than `int`**. According to the above calculation, the maximum positive number that `float` can represent is $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$, and the minimum negative number can be obtained by switching the sign bit. **Although floating-point number `float` expands the range, its side effect is sacrificing precision**. The integer type `int` uses all 32 bits to represent numbers, and the numbers are evenly distributed; however, due to the existence of the exponent bit, the larger the value of floating-point number `float`, the larger the difference between two adjacent numbers tends to be. As shown in the table below, exponent bits $\mathrm{E} = 0$ and $\mathrm{E} = 255$ have special meanings, **used to represent zero, infinity, $\mathrm{NaN}$, etc.**

Table   Meaning of exponent bits

| Exponent Bit E | Fraction Bit $\mathrm{N} = 0$ | Fraction Bit $\mathrm{N} \ne 0$ | Calculation Formula | | ------------------ | ----------------------------- | ------------------------------- | ---------------------------------------------------------------------- | | $0$ | $\pm 0$ | Subnormal Number | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ | | $1, 2, \dots, 254$ | Normal Number | Normal Number | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ | | $255$ | $\pm \infty$ | $\mathrm{NaN}$ | | It is worth noting that subnormal numbers significantly improve the precision of floating-point numbers. The smallest positive normal number is $2^{-126}$, and the smallest positive subnormal number is $2^{-126} \times 2^{-23}$. Double-precision `double` also uses a representation method similar to `float`, which will not be elaborated here. ================================================ FILE: en/docs/chapter_data_structure/summary.md ================================================ # Summary ### Key Review - Data structures can be classified from two perspectives: logical structure and physical structure. Logical structure describes the logical relationships between data elements, while physical structure describes how data is stored in computer memory. - Common logical structures include linear, tree, and network structures. We typically classify data structures as linear (arrays, linked lists, stacks, queues) and non-linear (trees, graphs, heaps) based on their logical structure. The implementation of hash tables may involve both linear and non-linear data structures. - When a program runs, data is stored in computer memory. Each memory space has a corresponding memory address, and the program accesses data through these memory addresses. - Physical structures are primarily divided into contiguous space storage (arrays) and dispersed space storage (linked lists). All data structures are implemented using arrays, linked lists, or a combination of both. - Basic data types in computers include integers `byte`, `short`, `int`, `long`, floating-point numbers `float`, `double`, characters `char`, and booleans `bool`. Their value ranges depend on the size of space they occupy and their representation method. - Sign-magnitude, 1's complement, and 2's complement are three methods for encoding numbers in computers, and they can be converted into each other. The most significant bit of sign-magnitude is the sign bit, and the remaining bits represent the value of the number. - Integers are stored in computers in 2's complement form. Under 2's complement representation, computers can treat the addition of positive and negative numbers uniformly, without needing to design special hardware circuits for subtraction, and there is no ambiguity of positive and negative zero. - The encoding of floating-point numbers consists of 1 sign bit, 8 exponent bits, and 23 fraction bits. Due to the exponent bits, the range of floating-point numbers is much larger than that of integers, at the cost of sacrificing precision. - ASCII is the earliest English character set, with a length of 1 byte, containing a total of 127 characters. GBK is a commonly used Chinese character set, containing over 20,000 Chinese characters. Unicode is committed to providing a complete character set standard, collecting characters from various languages around the world, thereby solving the garbled text problem caused by inconsistent character encoding methods. - UTF-8 is the most popular Unicode encoding method, with excellent universality. It is a variable-length encoding method with good scalability, effectively improving storage space efficiency. UTF-16 and UTF-32 are fixed-length encoding methods. When encoding Chinese characters, UTF-16 occupies less space than UTF-8. Programming languages such as Java and C# use UTF-16 encoding by default. ### Q & A **Q**: Why do hash tables contain both linear and non-linear data structures? The underlying structure of a hash table is an array. To resolve hash collisions, we may use "chaining" (discussed in the subsequent "Hash Collision" section): each bucket in the array points to a linked list, which may be converted to a tree (usually a red-black tree) when the list length exceeds a certain threshold. From a storage perspective, the underlying structure of a hash table is an array, where each bucket slot may contain a value, a linked list, or a tree. Therefore, hash tables may contain both linear data structures (arrays, linked lists) and non-linear data structures (trees). **Q**: Is the length of the `char` type 1 byte? The length of the `char` type is determined by the encoding method used by the programming language. For example, Java, JavaScript, TypeScript, and C# all use UTF-16 encoding (to store Unicode code points), so the `char` type has a length of 2 bytes. **Q**: Is there ambiguity in referring to array-based data structures as "static data structures"? Stacks can also perform "dynamic" operations such as push and pop. Stacks can indeed implement dynamic data operations, but the data structure is still "static" (fixed length). Although array-based data structures can dynamically add or remove elements, their capacity is fixed. If the data volume exceeds the pre-allocated size, a new larger array needs to be created, and the contents of the old array must be copied to the new array. **Q**: When constructing a stack (queue), its size is not specified. Why are they "static data structures"? In high-level programming languages, we do not need to manually specify the initial capacity of a stack (queue); this work is automatically completed within the class. For example, the initial capacity of Java's `ArrayList` is typically 10. Additionally, the expansion operation is also automatically implemented. See the subsequent "List" section for details. **Q**: The method of converting sign-magnitude to 2's complement is "first negate then add 1". So converting 2's complement to sign-magnitude should be the inverse operation "first subtract 1 then negate". However, 2's complement can also be converted to sign-magnitude through "first negate then add 1". Why is this? This is because the mutual conversion between sign-magnitude and 2's complement is actually the process of computing the "complement". Let us first define the complement: assuming $a + b = c$, then we say that $a$ is the complement of $b$ to $c$, and conversely, $b$ is the complement of $a$ to $c$. Given an $n = 4$ bit binary number $0010$, if we treat this number as sign-magnitude (ignoring the sign bit), then its 2's complement can be obtained through "first negate then add 1": $$ 0010 \rightarrow 1101 \rightarrow 1110 $$ We find that the sum of sign-magnitude and 2's complement is $0010 + 1110 = 10000$, which means the 2's complement $1110$ is the "complement" of sign-magnitude $0010$ to $10000$. **This means the above "first negate then add 1" is actually the process of computing the complement to $10000$**. So, what is the "complement" of 2's complement $1110$ to $10000$? We can still use "first negate then add 1" to obtain it: $$ 1110 \rightarrow 0001 \rightarrow 0010 $$ In other words, sign-magnitude and 2's complement are each other's "complement" to $10000$, so "sign-magnitude to 2's complement" and "2's complement to sign-magnitude" can be implemented using the same operation (first negate then add 1). Of course, we can also use the inverse operation to find the sign-magnitude of 2's complement $1110$, that is, "first subtract 1 then negate": $$ 1110 \rightarrow 1101 \rightarrow 0010 $$ In summary, both "first negate then add 1" and "first subtract 1 then negate" are computing the complement to $10000$, and they are equivalent. Essentially, the "negate" operation is actually finding the complement to $1111$ (because `sign-magnitude + 1's complement = 1111` always holds); and adding 1 to the 1's complement yields the 2's complement, which is the complement to $10000$. The above uses $n = 4$ as an example, and it can be generalized to binary numbers of any number of bits. ================================================ FILE: en/docs/chapter_divide_and_conquer/binary_search_recur.md ================================================ # Divide and Conquer Search Strategy We have already learned that search algorithms are divided into two major categories. - **Brute-force search**: Implemented by traversing the data structure, with a time complexity of $O(n)$. - **Adaptive search**: Utilizes unique data organization forms or prior information, with time complexity reaching $O(\log n)$ or even $O(1)$. In fact, **search algorithms with time complexity of $O(\log n)$ are typically implemented based on the divide and conquer strategy**, such as binary search and trees. - Each step of binary search divides the problem (searching for a target element in an array) into a smaller problem (searching for the target element in half of the array), continuing until the array is empty or the target element is found. - Trees are representative of the divide and conquer idea. In data structures such as binary search trees, AVL trees, and heaps, the time complexity of various operations is $O(\log n)$. The divide and conquer strategy of binary search is as follows. - **The problem can be decomposed**: Binary search recursively decomposes the original problem (searching in an array) into subproblems (searching in half of the array), achieved by comparing the middle element with the target element. - **Subproblems are independent**: In binary search, each round only processes one subproblem, which is not affected by other subproblems. - **Solutions of subproblems do not need to be merged**: Binary search aims to find a specific element, so there is no need to merge the solutions of subproblems. When a subproblem is solved, the original problem is also solved. Divide and conquer can improve search efficiency because brute-force search can only eliminate one option per round, **while divide and conquer search can eliminate half of the options per round**. ### Implementing Binary Search Based on Divide and Conquer In previous sections, binary search was implemented based on iteration. Now we implement it based on divide and conquer (recursion). !!! question Given a sorted array `nums` of length $n$, where all elements are unique, find the element `target`. From a divide and conquer perspective, we denote the subproblem corresponding to the search interval $[i, j]$ as $f(i, j)$. Starting from the original problem $f(0, n-1)$, perform binary search through the following steps. 1. Calculate the midpoint $m$ of the search interval $[i, j]$, and use it to eliminate half of the search interval. 2. Recursively solve the subproblem reduced by half in size, which could be $f(i, m-1)$ or $f(m+1, j)$. 3. Repeat steps `1.` and `2.` until `target` is found or the interval is empty and return. The figure below shows the divide and conquer process of binary search for element $6$ in an array. ![Divide and conquer process of binary search](binary_search_recur.assets/binary_search_recur.png) In the implementation code, we declare a recursive function `dfs()` to solve the problem $f(i, j)$: ```src [file]{binary_search_recur}-[class]{}-[func]{binary_search} ``` ================================================ FILE: en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md ================================================ # Building a Binary Tree Problem !!! question Given the preorder traversal `preorder` and inorder traversal `inorder` of a binary tree, construct the binary tree and return the root node of the binary tree. Assume there are no duplicate node values in the binary tree (as shown in the figure below). ![Example data for building a binary tree](build_binary_tree_problem.assets/build_tree_example.png) ### Determining If It Is a Divide and Conquer Problem The original problem is defined as constructing a binary tree from `preorder` and `inorder`, which is a typical divide and conquer problem. - **The problem can be decomposed**: From a divide and conquer perspective, we can divide the original problem into two subproblems: constructing the left subtree and constructing the right subtree, plus one operation: initializing the root node. For each subtree (subproblem), we can still reuse the above division method, dividing it into smaller subtrees (subproblems) until the smallest subproblem (empty subtree) is reached. - **Subproblems are independent**: The left and right subtrees are independent of each other; there is no overlap between them. When constructing the left subtree, we only need to focus on the parts of the inorder and preorder traversals corresponding to the left subtree. The same applies to the right subtree. - **Solutions of subproblems can be merged**: Once we have the left and right subtrees (solutions of subproblems), we can link them to the root node to obtain the solution to the original problem. ### How to Divide Subtrees Based on the above analysis, this problem can be solved using divide and conquer, **but how do we divide the left and right subtrees through the preorder traversal `preorder` and inorder traversal `inorder`**? According to the definition, both `preorder` and `inorder` can be divided into three parts. - Preorder traversal: `[ Root Node | Left Subtree | Right Subtree ]`, for example, the tree in the figure above corresponds to `[ 3 | 9 | 2 1 7 ]`. - Inorder traversal: `[ Left Subtree | Root Node | Right Subtree ]`, for example, the tree in the figure above corresponds to `[ 9 | 3 | 1 2 7 ]`. Using the data from the figure above as an example, we can obtain the division results through the steps shown in the figure below. 1. The first element 3 in the preorder traversal is the value of the root node. 2. Find the index of root node 3 in `inorder`, and use this index to divide `inorder` into `[ 9 | 3 | 1 2 7 ]`. 3. Based on the division result of `inorder`, it is easy to determine that the left and right subtrees have 1 and 3 nodes respectively, allowing us to divide `preorder` into `[ 3 | 9 | 2 1 7 ]`. ![Dividing subtrees in preorder and inorder traversals](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png) ### Describing Subtree Intervals Based on Variables Based on the above division method, **we have obtained the index intervals of the root node, left subtree, and right subtree in `preorder` and `inorder`**. To describe these index intervals, we need to use several pointer variables. - Denote the index of the current tree's root node in `preorder` as $i$. - Denote the index of the current tree's root node in `inorder` as $m$. - Denote the index interval of the current tree in `inorder` as $[l, r]$. As shown in the table below, through these variables we can represent the index of the root node in `preorder` and the index intervals of the subtrees in `inorder`.

Table   Indices of root node and subtrees in preorder and inorder traversals

| | Root node index in `preorder` | Subtree index interval in `inorder` | | ------------ | ----------------------------- | ----------------------------------- | | Current tree | $i$ | $[l, r]$ | | Left subtree | $i + 1$ | $[l, m-1]$ | | Right subtree| $i + 1 + (m - l)$ | $[m+1, r]$ | Please note that $(m-l)$ in the right subtree root node index means "the number of nodes in the left subtree". It is recommended to understand this in conjunction with the figure below. ![Index interval representation of root node and left and right subtrees](build_binary_tree_problem.assets/build_tree_division_pointers.png) ### Code Implementation To improve the efficiency of querying $m$, we use a hash table `hmap` to store the mapping from elements in the `inorder` array to their indices: ```src [file]{build_tree}-[class]{}-[func]{build_tree} ``` The figure below shows the recursive process of building the binary tree. Each node is established during the downward "recursion" process, while each edge (reference) is established during the upward "return" process. === "<1>" ![Recursive process of building a binary tree](build_binary_tree_problem.assets/built_tree_step1.png) === "<2>" ![built_tree_step2](build_binary_tree_problem.assets/built_tree_step2.png) === "<3>" ![built_tree_step3](build_binary_tree_problem.assets/built_tree_step3.png) === "<4>" ![built_tree_step4](build_binary_tree_problem.assets/built_tree_step4.png) === "<5>" ![built_tree_step5](build_binary_tree_problem.assets/built_tree_step5.png) === "<6>" ![built_tree_step6](build_binary_tree_problem.assets/built_tree_step6.png) === "<7>" ![built_tree_step7](build_binary_tree_problem.assets/built_tree_step7.png) === "<8>" ![built_tree_step8](build_binary_tree_problem.assets/built_tree_step8.png) === "<9>" ![built_tree_step9](build_binary_tree_problem.assets/built_tree_step9.png) The division results of the preorder traversal `preorder` and inorder traversal `inorder` within each recursive function are shown in the figure below. ![Division results in each recursive function](build_binary_tree_problem.assets/built_tree_overall.png) Let the number of nodes in the tree be $n$. Initializing each node (executing one recursive function `dfs()`) takes $O(1)$ time. **Therefore, the overall time complexity is $O(n)$**. The hash table stores the mapping from `inorder` elements to their indices, with a space complexity of $O(n)$. In the worst case, when the binary tree degenerates into a linked list, the recursion depth reaches $n$, using $O(n)$ stack frame space. **Therefore, the overall space complexity is $O(n)$**. ================================================ FILE: en/docs/chapter_divide_and_conquer/divide_and_conquer.md ================================================ # Divide and Conquer Algorithms Divide and conquer is a very important and common algorithm strategy. Divide and conquer is typically implemented based on recursion, consisting of two steps: "divide" and "conquer". 1. **Divide (partition phase)**: Recursively divide the original problem into two or more subproblems until the smallest subproblem is reached. 2. **Conquer (merge phase)**: Starting from the smallest subproblems with known solutions, merge the solutions of subproblems from bottom to top to construct the solution to the original problem. As shown in the figure below, "merge sort" is one of the typical applications of the divide and conquer strategy. 1. **Divide**: Recursively divide the original array (original problem) into two subarrays (subproblems) until the subarray has only one element (smallest subproblem). 2. **Conquer**: Merge the sorted subarrays (solutions to subproblems) from bottom to top to obtain a sorted original array (solution to the original problem). ![Divide and conquer strategy of merge sort](divide_and_conquer.assets/divide_and_conquer_merge_sort.png) ## How to Determine Divide and Conquer Problems Whether a problem is suitable for solving with divide and conquer can usually be determined based on the following criteria. 1. **The problem can be decomposed**: The original problem can be divided into smaller, similar subproblems, and can be recursively divided in the same way. 2. **Subproblems are independent**: There is no overlap between subproblems, they are independent of each other and can be solved independently. 3. **Solutions of subproblems can be merged**: The solution to the original problem is obtained by merging the solutions of subproblems. Clearly, merge sort satisfies these three criteria. 1. **The problem can be decomposed**: Recursively divide the array (original problem) into two subarrays (subproblems). 2. **Subproblems are independent**: Each subarray can be sorted independently (subproblems can be solved independently). 3. **Solutions of subproblems can be merged**: Two sorted subarrays (solutions of subproblems) can be merged into one sorted array (solution of the original problem). ## Improving Efficiency Through Divide and Conquer **Divide and conquer can not only effectively solve algorithmic problems but often also improve algorithm efficiency**. In sorting algorithms, quick sort, merge sort, and heap sort are faster than selection, bubble, and insertion sort because they apply the divide and conquer strategy. This raises the question: **Why can divide and conquer improve algorithm efficiency, and what is the underlying logic**? In other words, why is dividing a large problem into multiple subproblems, solving the subproblems, and merging their solutions more efficient than directly solving the original problem? This question can be discussed from two aspects: operation count and parallel computation. ### Operation Count Optimization Taking "bubble sort" as an example, processing an array of length $n$ requires $O(n^2)$ time. Suppose we divide the array into two subarrays from the midpoint as shown in the figure below, the division requires $O(n)$ time, sorting each subarray requires $O((n / 2)^2)$ time, and merging the two subarrays requires $O(n)$ time, resulting in an overall time complexity of: $$ O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n) $$ ![Bubble sort before and after array division](divide_and_conquer.assets/divide_and_conquer_bubble_sort.png) Next, we compute the following inequality, where the left and right sides represent the total number of operations before and after division, respectively: $$ \begin{aligned} n^2 & > \frac{n^2}{2} + 2n \newline n^2 - \frac{n^2}{2} - 2n & > 0 \newline n(n - 4) & > 0 \end{aligned} $$ **This means that when $n > 4$, the number of operations after division is smaller, and sorting efficiency should be higher**. Note that the time complexity after division is still quadratic $O(n^2)$, but the constant term in the complexity has become smaller. Going further, **what if we continuously divide the subarrays from their midpoints into two subarrays** until the subarrays have only one element? This approach is actually "merge sort", with a time complexity of $O(n \log n)$. Thinking further, **what if we set multiple division points** and evenly divide the original array into $k$ subarrays? This situation is very similar to "bucket sort", which is well-suited for sorting massive amounts of data, with a theoretical time complexity of $O(n + k)$. ### Parallel Computation Optimization We know that the subproblems generated by divide and conquer are independent of each other, **so they can typically be solved in parallel**. This means divide and conquer can not only reduce the time complexity of algorithms, **but also benefits from parallel optimization by operating systems**. Parallel optimization is particularly effective in multi-core or multi-processor environments, as the system can simultaneously handle multiple subproblems, making fuller use of computing resources and significantly reducing overall runtime. For example, in the "bucket sort" shown in the figure below, we evenly distribute massive data into various buckets, and the sorting tasks for all buckets can be distributed to various computing units. After completion, the results are merged. ![Parallel computation in bucket sort](divide_and_conquer.assets/divide_and_conquer_parallel_computing.png) ## Common Applications of Divide and Conquer On one hand, divide and conquer can be used to solve many classic algorithmic problems. - **Finding the closest pair of points**: This algorithm first divides the point set into two parts, then finds the closest pair of points in each part separately, and finally finds the closest pair of points that spans both parts. - **Large integer multiplication**: For example, the Karatsuba algorithm, which decomposes large integer multiplication into several smaller integer multiplications and additions. - **Matrix multiplication**: For example, the Strassen algorithm, which decomposes large matrix multiplication into multiple small matrix multiplications and additions. - **Hanota problem**: The hanota problem can be solved through recursion, which is a typical application of the divide and conquer strategy. - **Solving inversion pairs**: In a sequence, if a preceding number is greater than a following number, these two numbers form an inversion pair. Solving the inversion pair problem can utilize the divide and conquer approach with the help of merge sort. On the other hand, divide and conquer is widely applied in the design of algorithms and data structures. - **Binary search**: Binary search divides a sorted array into two parts from the midpoint index, then decides which half to eliminate based on the comparison result between the target value and the middle element value, and performs the same binary operation on the remaining interval. - **Merge sort**: Already introduced at the beginning of this section, no further elaboration needed. - **Quick sort**: Quick sort selects a pivot value, then divides the array into two subarrays, one with elements smaller than the pivot and the other with elements larger than the pivot, then performs the same division operation on these two parts until the subarrays have only one element. - **Bucket sort**: The basic idea of bucket sort is to scatter data into multiple buckets, then sort the elements within each bucket, and finally extract the elements from each bucket in sequence to obtain a sorted array. - **Trees**: For example, binary search trees, AVL trees, red-black trees, B-trees, B+ trees, etc. Their search, insertion, and deletion operations can all be viewed as applications of the divide and conquer strategy. - **Heaps**: A heap is a special complete binary tree, and its various operations, such as insertion, deletion, and heapify, actually imply the divide and conquer idea. - **Hash tables**: Although hash tables do not directly apply divide and conquer, some hash collision resolution solutions indirectly apply the divide and conquer strategy. For example, long linked lists in chaining may be converted to red-black trees to improve query efficiency. It can be seen that **divide and conquer is a "subtly pervasive" algorithmic idea**, embedded in various algorithms and data structures. ================================================ FILE: en/docs/chapter_divide_and_conquer/hanota_problem.md ================================================ # Hanota Problem In merge sort and building binary trees, we decompose the original problem into two subproblems, each half the size of the original problem. However, for the hanota problem, we adopt a different decomposition strategy. !!! question Given three pillars, denoted as `A`, `B`, and `C`. Initially, pillar `A` has $n$ discs stacked on it, arranged from top to bottom in ascending order of size. Our task is to move these $n$ discs to pillar `C` while maintaining their original order (as shown in the figure below). The following rules must be followed when moving the discs. 1. A disc can only be taken from the top of one pillar and placed on top of another pillar. 2. Only one disc can be moved at a time. 3. A smaller disc must always be on top of a larger disc. ![Example of the hanota problem](hanota_problem.assets/hanota_example.png) **We denote the hanota problem of size $i$ as $f(i)$**. For example, $f(3)$ represents moving $3$ discs from `A` to `C`. ### Considering the Base Cases As shown in the figure below, for problem $f(1)$, when there is only one disc, we can move it directly from `A` to `C`. === "<1>" ![Solution for a problem of size 1](hanota_problem.assets/hanota_f1_step1.png) === "<2>" ![hanota_f1_step2](hanota_problem.assets/hanota_f1_step2.png) As shown in the figure below, for problem $f(2)$, when there are two discs, **since we must always keep the smaller disc on top of the larger disc, we need to use `B` to assist in the move**. 1. First, move the smaller disc from `A` to `B`. 2. Then move the larger disc from `A` to `C`. 3. Finally, move the smaller disc from `B` to `C`. === "<1>" ![Solution for a problem of size 2](hanota_problem.assets/hanota_f2_step1.png) === "<2>" ![hanota_f2_step2](hanota_problem.assets/hanota_f2_step2.png) === "<3>" ![hanota_f2_step3](hanota_problem.assets/hanota_f2_step3.png) === "<4>" ![hanota_f2_step4](hanota_problem.assets/hanota_f2_step4.png) The process of solving problem $f(2)$ can be summarized as: **moving two discs from `A` to `C` with the help of `B`**. Here, `C` is called the target pillar, and `B` is called the buffer pillar. ### Subproblem Decomposition For problem $f(3)$, when there are three discs, the situation becomes slightly more complex. Since we already know the solutions to $f(1)$ and $f(2)$, we can think from a divide and conquer perspective, **treating the top two discs on `A` as a whole**, and execute the steps shown in the figure below. This successfully moves the three discs from `A` to `C`. 1. Let `B` be the target pillar and `C` be the buffer pillar, and move two discs from `A` to `B`. 2. Move the remaining disc from `A` directly to `C`. 3. Let `C` be the target pillar and `A` be the buffer pillar, and move two discs from `B` to `C`. === "<1>" ![Solution for a problem of size 3](hanota_problem.assets/hanota_f3_step1.png) === "<2>" ![hanota_f3_step2](hanota_problem.assets/hanota_f3_step2.png) === "<3>" ![hanota_f3_step3](hanota_problem.assets/hanota_f3_step3.png) === "<4>" ![hanota_f3_step4](hanota_problem.assets/hanota_f3_step4.png) Essentially, **we divide problem $f(3)$ into two subproblems $f(2)$ and one subproblem $f(1)$**. By solving these three subproblems in order, the original problem is solved. This shows that the subproblems are independent and their solutions can be merged. From this, we can summarize the divide and conquer strategy for solving the hanota problem shown in the figure below: divide the original problem $f(n)$ into two subproblems $f(n-1)$ and one subproblem $f(1)$, and solve these three subproblems in the following order. 1. Move $n-1$ discs from `A` to `B` with the help of `C`. 2. Move the remaining $1$ disc directly from `A` to `C`. 3. Move $n-1$ discs from `B` to `C` with the help of `A`. For these two subproblems $f(n-1)$, **we can recursively divide them in the same way** until reaching the smallest subproblem $f(1)$. The solution to $f(1)$ is known and requires only one move operation. ![Divide and conquer strategy for solving the hanota problem](hanota_problem.assets/hanota_divide_and_conquer.png) ### Code Implementation In the code, we declare a recursive function `dfs(i, src, buf, tar)`, whose purpose is to move the top $i$ discs from pillar `src` to target pillar `tar` with the help of buffer pillar `buf`: ```src [file]{hanota}-[class]{}-[func]{solve_hanota} ``` As shown in the figure below, the hanota problem forms a recursion tree of height $n$, where each node represents a subproblem corresponding to an invocation of the `dfs()` function, **therefore the time complexity is $O(2^n)$ and the space complexity is $O(n)$**. ![Recursion tree of the hanota problem](hanota_problem.assets/hanota_recursive_tree.png) !!! quote The hanota problem originates from an ancient legend. In a temple in ancient India, monks had three tall diamond pillars and $64$ golden discs of different sizes. The monks continuously moved the discs, believing that when the last disc was correctly placed, the world would come to an end. However, even if the monks moved one disc per second, it would take approximately $2^{64} \approx 1.84×10^{19}$ seconds, which is about $5850$ billion years, far exceeding current estimates of the age of the universe. Therefore, if this legend is true, we should not need to worry about the end of the world. ================================================ FILE: en/docs/chapter_divide_and_conquer/index.md ================================================ # Divide and Conquer ![Divide and conquer](../assets/covers/chapter_divide_and_conquer.jpg) !!! abstract Difficult problems are decomposed layer by layer, with each decomposition making them simpler. Divide and conquer reveals an important truth: start with simplicity, and nothing remains complex. ================================================ FILE: en/docs/chapter_divide_and_conquer/summary.md ================================================ # Summary ### Key Review - Divide and conquer is a common algorithm design strategy, consisting of two phases: divide (partition) and conquer (merge), typically implemented based on recursion. - The criteria for determining whether a problem is a divide and conquer problem include: whether the problem can be decomposed, whether subproblems are independent, and whether subproblems can be merged. - Merge sort is a typical application of the divide and conquer strategy. It recursively divides an array into two equal-length subarrays until only one element remains, then merges them layer by layer to complete the sorting. - Introducing the divide and conquer strategy can often improve algorithm efficiency. On one hand, the divide and conquer strategy reduces the number of operations; on the other hand, it facilitates parallel optimization of the system after division. - Divide and conquer can both solve many algorithmic problems and is widely applied in data structure and algorithm design, appearing everywhere. - Compared to brute-force search, adaptive search is more efficient. Search algorithms with time complexity of $O(\log n)$ are typically implemented based on the divide and conquer strategy. - Binary search is another typical application of divide and conquer. It does not include the step of merging solutions of subproblems. We can implement binary search through recursive divide and conquer. - In the problem of building a binary tree, building the tree (original problem) can be divided into building the left subtree and right subtree (subproblems), which can be achieved by dividing the index intervals of the preorder and inorder traversals. - In the hanota problem, a problem of size $n$ can be divided into two subproblems of size $n-1$ and one subproblem of size $1$. After solving these three subproblems in order, the original problem is solved. ================================================ FILE: en/docs/chapter_dynamic_programming/dp_problem_features.md ================================================ # Characteristics of Dynamic Programming Problems In the previous section, we learned how dynamic programming solves the original problem by decomposing it into subproblems. In fact, subproblem decomposition is a general algorithmic approach, with different emphases in divide and conquer, dynamic programming, and backtracking. - Divide and conquer algorithms recursively divide the original problem into multiple independent subproblems until the smallest subproblems are reached, and merge the solutions to the subproblems during backtracking to ultimately obtain the solution to the original problem. - Dynamic programming also recursively decomposes problems, but the main difference from divide and conquer algorithms is that subproblems in dynamic programming are interdependent, and many overlapping subproblems appear during the decomposition process. - Backtracking algorithms enumerate all possible solutions through trial and error, and avoid unnecessary search branches through pruning. The solution to the original problem consists of a series of decision steps, and we can regard the subsequence before each decision step as a subproblem. In fact, dynamic programming is commonly used to solve optimization problems, which not only contain overlapping subproblems but also have two other major characteristics: optimal substructure and no aftereffects. ## Optimal Substructure We make a slight modification to the stair climbing problem to make it more suitable for demonstrating the concept of optimal substructure. !!! question "Climbing stairs with minimum cost" Given a staircase, where you can climb $1$ or $2$ steps at a time, and each step has a non-negative integer representing the cost you need to pay at that step. Given a non-negative integer array $cost$, where $cost[i]$ represents the cost at the $i$-th step, and $cost[0]$ is the ground (starting point). What is the minimum cost required to reach the top? As shown in the figure below, if the costs of the $1$st, $2$nd, and $3$rd steps are $1$, $10$, and $1$ respectively, then climbing from the ground to the $3$rd step requires a minimum cost of $2$. ![Minimum cost to climb to the 3rd step](dp_problem_features.assets/min_cost_cs_example.png) Let $dp[i]$ be the accumulated cost of climbing to the $i$-th step. Since the $i$-th step can only come from the $i-1$-th or $i-2$-th step, $dp[i]$ can only equal $dp[i-1] + cost[i]$ or $dp[i-2] + cost[i]$. To minimize the cost, we should choose the smaller of the two: $$ dp[i] = \min(dp[i-1], dp[i-2]) + cost[i] $$ This leads us to the meaning of optimal substructure: **the optimal solution to the original problem is constructed from the optimal solutions to the subproblems**. This problem clearly has optimal substructure: we select the better one from the optimal solutions to the two subproblems $dp[i-1]$ and $dp[i-2]$, and use it to construct the optimal solution to the original problem $dp[i]$. So, does the stair climbing problem from the previous section have optimal substructure? Its goal is to find the number of ways, which seems to be a counting problem, but if we change the question: "Find the maximum number of ways". We surprisingly discover that **although the problem before and after modification are equivalent, the optimal substructure has emerged**: the maximum number of ways for the $n$-th step equals the sum of the maximum number of ways for the $n-1$-th and $n-2$-th steps. Therefore, the interpretation of optimal substructure is quite flexible and will have different meanings in different problems. According to the state transition equation and the initial states $dp[1] = cost[1]$ and $dp[2] = cost[2]$, we can obtain the dynamic programming code: ```src [file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp} ``` The figure below shows the dynamic programming process for the above code. ![Dynamic programming process for climbing stairs with minimum cost](dp_problem_features.assets/min_cost_cs_dp.png) This problem can also be space-optimized, compressing from one dimension to zero, reducing the space complexity from $O(n)$ to $O(1)$: ```src [file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp_comp} ``` ## No Aftereffects No aftereffects is one of the important characteristics that enable dynamic programming to solve problems effectively. Its definition is: **given a certain state, its future development is only related to the current state and has nothing to do with all past states**. Taking the stair climbing problem as an example, given state $i$, it will develop into states $i+1$ and $i+2$, corresponding to jumping $1$ step and jumping $2$ steps, respectively. When making these two choices, we do not need to consider the states before state $i$, as they have no effect on the future of state $i$. However, if we add a constraint to the stair climbing problem, the situation changes. !!! question "Climbing stairs with constraint" Given a staircase with $n$ steps, where you can climb $1$ or $2$ steps at a time, **but you cannot jump $1$ step in two consecutive rounds**. How many ways are there to climb to the top? As shown in the figure below, there are only $2$ feasible ways to climb to the $3$rd step. The way of jumping $1$ step three consecutive times does not satisfy the constraint and is therefore discarded. ![Number of ways to climb to the 3rd step with constraint](dp_problem_features.assets/climbing_stairs_constraint_example.png) In this problem, if the previous round was a jump of $1$ step, then the next round must jump $2$ steps. This means that **the next choice cannot be determined solely by the current state (current stair step number), but also depends on the previous state (the stair step number from the previous round)**. It is not difficult to see that this problem no longer satisfies no aftereffects, and the state transition equation $dp[i] = dp[i-1] + dp[i-2]$ also fails, because $dp[i-1]$ represents jumping $1$ step in this round, but it includes many solutions where "the previous round was a jump of $1$ step", which cannot be directly counted in $dp[i]$ to satisfy the constraint. For this reason, we need to expand the state definition: **state $[i, j]$ represents being on the $i$-th step with the previous round having jumped $j$ steps**, where $j \in \{1, 2\}$. This state definition effectively distinguishes whether the previous round was a jump of $1$ step or $2$ steps, allowing us to determine where the current state came from. - When the previous round jumped $1$ step, the round before that could only choose to jump $2$ steps, i.e., $dp[i, 1]$ can only be transferred from $dp[i-1, 2]$. - When the previous round jumped $2$ steps, the round before that could choose to jump $1$ step or $2$ steps, i.e., $dp[i, 2]$ can be transferred from $dp[i-2, 1]$ or $dp[i-2, 2]$. As shown in the figure below, under this definition, $dp[i, j]$ represents the number of ways for state $[i, j]$. The state transition equation is then: $$ \begin{cases} dp[i, 1] = dp[i-1, 2] \\ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] \end{cases} $$ ![Recurrence relation considering constraints](dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png) Finally, return $dp[n, 1] + dp[n, 2]$, where the sum of the two represents the total number of ways to climb to the $n$-th step: ```src [file]{climbing_stairs_constraint_dp}-[class]{}-[func]{climbing_stairs_constraint_dp} ``` In the above case, since we only need to consider one more preceding state, we can still make the problem satisfy no aftereffects by expanding the state definition. However, some problems have very severe "aftereffects". !!! question "Climbing stairs with obstacle generation" Given a staircase with $n$ steps, where you can climb $1$ or $2$ steps at a time. **It is stipulated that when climbing to the $i$-th step, the system will automatically place an obstacle on the $2i$-th step, and thereafter no round is allowed to jump to the $2i$-th step**. For example, if the first two rounds jump to the $2$nd and $3$rd steps, then afterwards you cannot jump to the $4$th and $6$th steps. How many ways are there to climb to the top? In this problem, the next jump depends on all past states, because each jump places obstacles on higher steps, affecting future jumps. For such problems, dynamic programming is often difficult to solve. In fact, many complex combinatorial optimization problems (such as the traveling salesman problem) do not satisfy no aftereffects. For such problems, we usually choose to use other methods, such as heuristic search, genetic algorithms, reinforcement learning, etc., to obtain usable local optimal solutions within a limited time. ================================================ FILE: en/docs/chapter_dynamic_programming/dp_solution_pipeline.md ================================================ # Dynamic Programming Problem-Solving Approach The previous two sections introduced the main characteristics of dynamic programming problems. Next, let us explore two more practical issues together. 1. How to determine whether a problem is a dynamic programming problem? 2. What is the complete process for solving a dynamic programming problem, and where should we start? ## Problem Determination Generally speaking, if a problem contains overlapping subproblems, optimal substructure, and satisfies no aftereffects, then it is usually suitable for solving with dynamic programming. However, it is difficult to directly extract these characteristics from the problem description. Therefore, we usually relax the conditions and **first observe whether the problem is suitable for solving with backtracking (exhaustive search)**. **Problems suitable for solving with backtracking usually satisfy the "decision tree model"**, which means the problem can be described using a tree structure, where each node represents a decision and each path represents a sequence of decisions. In other words, if a problem contains an explicit concept of decisions, and the solution is generated through a series of decisions, then it satisfies the decision tree model and can usually be solved using backtracking. On this basis, dynamic programming problems also have some "bonus points" for determination. - The problem contains descriptions such as maximum (minimum) or most (least), indicating optimization. - The problem's state can be represented using a list, multi-dimensional matrix, or tree, and a state has a recurrence relation with its surrounding states. Correspondingly, there are also some "penalty points". - The goal of the problem is to find all possible solutions, rather than finding the optimal solution. - The problem description has obvious permutation and combination characteristics, requiring the return of specific multiple solutions. If a problem satisfies the decision tree model and has relatively obvious "bonus points", we can assume it is a dynamic programming problem and verify it during the solving process. ## Problem-Solving Steps The problem-solving process for dynamic programming varies depending on the nature and difficulty of the problem, but generally follows these steps: describe decisions, define states, establish the $dp$ table, derive state transition equations, determine boundary conditions, etc. To illustrate the problem-solving steps more vividly, we use a classic problem "minimum path sum" as an example. !!! question Given an $n \times m$ two-dimensional grid `grid`, where each cell in the grid contains a non-negative integer representing the cost of that cell. A robot starts from the top-left cell and can only move down or right at each step until reaching the bottom-right cell. Return the minimum path sum from the top-left to the bottom-right. The figure below shows an example where the minimum path sum for the given grid is $13$. ![Minimum path sum example data](dp_solution_pipeline.assets/min_path_sum_example.png) **Step 1: Think about the decisions in each round, define the state, and thus obtain the $dp$ table** The decision in each round of this problem is to move one step down or right from the current cell. Let the row and column indices of the current cell be $[i, j]$. After moving down or right, the indices become $[i+1, j]$ or $[i, j+1]$. Therefore, the state should include two variables, the row index and column index, denoted as $[i, j]$. State $[i, j]$ corresponds to the subproblem: **the minimum path sum from the starting point $[0, 0]$ to $[i, j]$**, denoted as $dp[i, j]$. From this, we obtain the two-dimensional $dp$ matrix shown in the figure below, whose size is the same as the input grid $grid$. ![State definition and dp table](dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png) !!! note The dynamic programming and backtracking processes can be described as a sequence of decisions, and the state consists of all decision variables. It should contain all variables describing the progress of problem-solving, and should contain sufficient information to derive the next state. Each state corresponds to a subproblem, and we define a $dp$ table to store the solutions to all subproblems. Each independent variable of the state is a dimension of the $dp$ table. Essentially, the $dp$ table is a mapping between states and solutions to subproblems. **Step 2: Identify the optimal substructure, and then derive the state transition equation** For state $[i, j]$, it can only be transferred from the cell above $[i-1, j]$ or the cell to the left $[i, j-1]$. Therefore, the optimal substructure is: the minimum path sum to reach $[i, j]$ is determined by the smaller of the minimum path sums of $[i, j-1]$ and $[i-1, j]$. Based on the above analysis, the state transition equation shown in the figure below can be derived: $$ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j] $$ ![Optimal substructure and state transition equation](dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png) !!! note Based on the defined $dp$ table, think about the relationship between the original problem and subproblems, and find the method to construct the optimal solution to the original problem from the optimal solutions to the subproblems, which is the optimal substructure. Once we identify the optimal substructure, we can use it to construct the state transition equation. **Step 3: Determine boundary conditions and state transition order** In this problem, states in the first row can only come from the state to their left, and states in the first column can only come from the state above them. Therefore, the first row $i = 0$ and first column $j = 0$ are boundary conditions. As shown in the figure below, since each cell is transferred from the cell to its left and the cell above it, we use loops to traverse the matrix, with the outer loop traversing rows and the inner loop traversing columns. ![Boundary conditions and state transition order](dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png) !!! note Boundary conditions in dynamic programming are used to initialize the $dp$ table, and in search are used for pruning. The core of state transition order is to ensure that when computing the solution to the current problem, all the smaller subproblems it depends on have already been computed correctly. Based on the above analysis, we can directly write the dynamic programming code. However, subproblem decomposition is a top-down approach, so implementing in the order "brute force search $\rightarrow$ memoization $\rightarrow$ dynamic programming" is more aligned with thinking habits. ### Method 1: Brute Force Search Starting from state $[i, j]$, continuously decompose into smaller states $[i-1, j]$ and $[i, j-1]$. The recursive function includes the following elements. - **Recursive parameters**: state $[i, j]$. - **Return value**: minimum path sum from $[0, 0]$ to $[i, j]$, which is $dp[i, j]$. - **Termination condition**: when $i = 0$ and $j = 0$, return cost $grid[0, 0]$. - **Pruning**: when $i < 0$ or $j < 0$, the index is out of bounds, return cost $+\infty$, representing infeasibility. The implementation code is as follows: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs} ``` The figure below shows the recursion tree rooted at $dp[2, 1]$, which includes some overlapping subproblems whose number will increase sharply as the size of grid `grid` grows. Essentially, the reason for overlapping subproblems is: **there are multiple paths from the top-left corner to reach a certain cell**. ![Brute force search recursion tree](dp_solution_pipeline.assets/min_path_sum_dfs.png) Each state has two choices, down and right, so the total number of steps from the top-left corner to the bottom-right corner is $m + n - 2$, giving a worst-case time complexity of $O(2^{m + n})$, where $n$ and $m$ are the number of rows and columns of the grid, respectively. Note that this calculation does not account for situations near the grid boundaries, where only one choice remains when reaching the grid boundary, so the actual number of paths will be somewhat less. ### Method 2: Memoization We introduce a memo list `mem` of the same size as grid `grid` to record the solutions to subproblems and prune overlapping subproblems: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs_mem} ``` As shown in the figure below, after introducing memoization, all subproblem solutions only need to be computed once, so the time complexity depends on the total number of states, which is the grid size $O(nm)$. ![Memoization recursion tree](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png) ### Method 3: Dynamic Programming Implement the dynamic programming solution based on iteration, as shown in the code below: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp} ``` The figure below shows the state transition process for minimum path sum, which traverses the entire grid, **thus the time complexity is $O(nm)$**. The array `dp` has size $n \times m$, **thus the space complexity is $O(nm)$**. === "<1>" ![Dynamic programming process for minimum path sum](dp_solution_pipeline.assets/min_path_sum_dp_step1.png) === "<2>" ![min_path_sum_dp_step2](dp_solution_pipeline.assets/min_path_sum_dp_step2.png) === "<3>" ![min_path_sum_dp_step3](dp_solution_pipeline.assets/min_path_sum_dp_step3.png) === "<4>" ![min_path_sum_dp_step4](dp_solution_pipeline.assets/min_path_sum_dp_step4.png) === "<5>" ![min_path_sum_dp_step5](dp_solution_pipeline.assets/min_path_sum_dp_step5.png) === "<6>" ![min_path_sum_dp_step6](dp_solution_pipeline.assets/min_path_sum_dp_step6.png) === "<7>" ![min_path_sum_dp_step7](dp_solution_pipeline.assets/min_path_sum_dp_step7.png) === "<8>" ![min_path_sum_dp_step8](dp_solution_pipeline.assets/min_path_sum_dp_step8.png) === "<9>" ![min_path_sum_dp_step9](dp_solution_pipeline.assets/min_path_sum_dp_step9.png) === "<10>" ![min_path_sum_dp_step10](dp_solution_pipeline.assets/min_path_sum_dp_step10.png) === "<11>" ![min_path_sum_dp_step11](dp_solution_pipeline.assets/min_path_sum_dp_step11.png) === "<12>" ![min_path_sum_dp_step12](dp_solution_pipeline.assets/min_path_sum_dp_step12.png) ### Space Optimization Since each cell is only related to the cell to its left and the cell above it, we can use a single-row array to implement the $dp$ table. Note that since the array `dp` can only represent the state of one row, we cannot initialize the first column state in advance, but rather update it when traversing each row: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp_comp} ``` ================================================ FILE: en/docs/chapter_dynamic_programming/edit_distance_problem.md ================================================ # Edit Distance Problem Edit distance, also known as Levenshtein distance, refers to the minimum number of edits required to transform one string into another, commonly used in information retrieval and natural language processing to measure the similarity between two sequences. !!! question Given two strings $s$ and $t$, return the minimum number of edits required to transform $s$ into $t$. You can perform three types of edit operations on a string: insert a character, delete a character, or replace a character with any other character. As shown in the figure below, transforming `kitten` into `sitting` requires 3 edits, including 2 replacements and 1 insertion; transforming `hello` into `algo` requires 3 steps, including 2 replacements and 1 deletion. ![Example data for edit distance](edit_distance_problem.assets/edit_distance_example.png) **The edit distance problem can be naturally explained using the decision tree model**. Strings correspond to tree nodes, and a round of decision (one edit operation) corresponds to an edge of the tree. As shown in the figure below, without restricting operations, each node can branch into many edges, with each edge corresponding to one operation, meaning there are many possible paths to transform `hello` into `algo`. From the perspective of the decision tree, the goal of this problem is to find the shortest path between node `hello` and node `algo`. ![Representing edit distance problem based on decision tree model](edit_distance_problem.assets/edit_distance_decision_tree.png) ### Dynamic Programming Approach **Step 1: Think about the decisions in each round, define the state, and thus obtain the $dp$ table** Each round of decision involves performing one edit operation on string $s$. We want the problem scale to gradually decrease during the editing process, which allows us to construct subproblems. Let the lengths of strings $s$ and $t$ be $n$ and $m$ respectively. We first consider the tail characters of the two strings, $s[n-1]$ and $t[m-1]$. - If $s[n-1]$ and $t[m-1]$ are the same, we can skip them and directly consider $s[n-2]$ and $t[m-2]$. - If $s[n-1]$ and $t[m-1]$ are different, we need to perform one edit on $s$ (insert, delete, or replace) to make the tail characters of the two strings the same, allowing us to skip them and consider a smaller-scale problem. In other words, each round of decision (edit operation) we make on string $s$ will change the remaining characters to be matched in $s$ and $t$. Therefore, the state is the $i$-th and $j$-th characters currently being considered in $s$ and $t$, denoted as $[i, j]$. State $[i, j]$ corresponds to the subproblem: **the minimum number of edits required to change the first $i$ characters of $s$ into the first $j$ characters of $t$**. From this, we obtain a two-dimensional $dp$ table of size $(i+1) \times (j+1)$. **Step 2: Identify the optimal substructure, and then derive the state transition equation** Consider subproblem $dp[i, j]$, where the tail characters of the corresponding two strings are $s[i-1]$ and $t[j-1]$, which can be divided into the three cases shown in the figure below based on different edit operations. 1. Insert $t[j-1]$ after $s[i-1]$, then the remaining subproblem is $dp[i, j-1]$. 2. Delete $s[i-1]$, then the remaining subproblem is $dp[i-1, j]$. 3. Replace $s[i-1]$ with $t[j-1]$, then the remaining subproblem is $dp[i-1, j-1]$. ![State transition for edit distance](edit_distance_problem.assets/edit_distance_state_transfer.png) Based on the above analysis, the optimal substructure can be obtained: the minimum number of edits for $dp[i, j]$ equals the minimum among the minimum edit steps of $dp[i, j-1]$, $dp[i-1, j]$, and $dp[i-1, j-1]$, plus the edit step $1$ for this time. The corresponding state transition equation is: $$ dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 $$ Please note that **when $s[i-1]$ and $t[j-1]$ are the same, no edit is required for the current character**, in which case the state transition equation is: $$ dp[i, j] = dp[i-1, j-1] $$ **Step 3: Determine boundary conditions and state transition order** When both strings are empty, the number of edit steps is $0$, i.e., $dp[0, 0] = 0$. When $s$ is empty but $t$ is not, the minimum number of edit steps equals the length of $t$, i.e., the first row $dp[0, j] = j$. When $s$ is not empty but $t$ is empty, the minimum number of edit steps equals the length of $s$, i.e., the first column $dp[i, 0] = i$. Observing the state transition equation, the solution $dp[i, j]$ depends on solutions to the left, above, and upper-left, so the entire $dp$ table can be traversed in order through two nested loops. ### Code Implementation ```src [file]{edit_distance}-[class]{}-[func]{edit_distance_dp} ``` As shown in the figure below, the state transition process for the edit distance problem is very similar to the knapsack problem and can both be viewed as the process of filling a two-dimensional grid. === "<1>" ![Dynamic programming process for edit distance](edit_distance_problem.assets/edit_distance_dp_step1.png) === "<2>" ![edit_distance_dp_step2](edit_distance_problem.assets/edit_distance_dp_step2.png) === "<3>" ![edit_distance_dp_step3](edit_distance_problem.assets/edit_distance_dp_step3.png) === "<4>" ![edit_distance_dp_step4](edit_distance_problem.assets/edit_distance_dp_step4.png) === "<5>" ![edit_distance_dp_step5](edit_distance_problem.assets/edit_distance_dp_step5.png) === "<6>" ![edit_distance_dp_step6](edit_distance_problem.assets/edit_distance_dp_step6.png) === "<7>" ![edit_distance_dp_step7](edit_distance_problem.assets/edit_distance_dp_step7.png) === "<8>" ![edit_distance_dp_step8](edit_distance_problem.assets/edit_distance_dp_step8.png) === "<9>" ![edit_distance_dp_step9](edit_distance_problem.assets/edit_distance_dp_step9.png) === "<10>" ![edit_distance_dp_step10](edit_distance_problem.assets/edit_distance_dp_step10.png) === "<11>" ![edit_distance_dp_step11](edit_distance_problem.assets/edit_distance_dp_step11.png) === "<12>" ![edit_distance_dp_step12](edit_distance_problem.assets/edit_distance_dp_step12.png) === "<13>" ![edit_distance_dp_step13](edit_distance_problem.assets/edit_distance_dp_step13.png) === "<14>" ![edit_distance_dp_step14](edit_distance_problem.assets/edit_distance_dp_step14.png) === "<15>" ![edit_distance_dp_step15](edit_distance_problem.assets/edit_distance_dp_step15.png) ### Space Optimization Since $dp[i, j]$ is transferred from the solutions above $dp[i-1, j]$, to the left $dp[i, j-1]$, and to the upper-left $dp[i-1, j-1]$, forward traversal will lose the upper-left solution $dp[i-1, j-1]$, and reverse traversal cannot build $dp[i, j-1]$ in advance, so neither traversal order is feasible. For this reason, we can use a variable `leftup` to temporarily store the upper-left solution $dp[i-1, j-1]$, so we only need to consider the solutions to the left and above. This situation is the same as the unbounded knapsack problem, allowing for forward traversal. The code is as follows: ```src [file]{edit_distance}-[class]{}-[func]{edit_distance_dp_comp} ``` ================================================ FILE: en/docs/chapter_dynamic_programming/index.md ================================================ # Dynamic Programming ![Dynamic programming](../assets/covers/chapter_dynamic_programming.jpg) !!! abstract Streams converge into rivers, rivers converge into the sea. Dynamic programming gathers solutions to small problems into answers to large problems, step by step guiding us to the shore of problem-solving. ================================================ FILE: en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md ================================================ # Introduction to Dynamic Programming Dynamic programming is an important algorithmic paradigm that decomposes a problem into a series of smaller subproblems and avoids redundant computation by storing the solutions to subproblems, thereby significantly improving time efficiency. In this section, we start with a classic example, first presenting its brute force backtracking solution, observing the overlapping subproblems within it, and then gradually deriving a more efficient dynamic programming solution. !!! question "Climbing stairs" Given a staircase with $n$ steps, where you can climb $1$ or $2$ steps at a time, how many different ways are there to reach the top? As shown in the figure below, for a $3$-step staircase, there are $3$ different ways to reach the top. ![Number of ways to reach the 3rd step](intro_to_dynamic_programming.assets/climbing_stairs_example.png) The goal of this problem is to find the number of ways, **we can consider using backtracking to enumerate all possibilities**. Specifically, imagine climbing stairs as a multi-round selection process: starting from the ground, choosing to go up $1$ or $2$ steps in each round, incrementing the count by $1$ whenever the top of the stairs is reached, and pruning when exceeding the top. The code is as follows: ```src [file]{climbing_stairs_backtrack}-[class]{}-[func]{climbing_stairs_backtrack} ``` ## Method 1: Brute Force Search Backtracking algorithms typically do not explicitly decompose problems, but rather treat solving the problem as a series of decision steps, searching for all possible solutions through trial and pruning. We can try to analyze this problem from the perspective of problem decomposition. Let the number of ways to climb to the $i$-th step be $dp[i]$, then $dp[i]$ is the original problem, and its subproblems include: $$ dp[i-1], dp[i-2], \dots, dp[2], dp[1] $$ Since we can only go up $1$ or $2$ steps in each round, when we stand on the $i$-th step, we could only have been on the $i-1$-th or $i-2$-th step in the previous round. In other words, we can only reach the $i$-th step from the $i-1$-th or $i-2$-th step. This leads to an important conclusion: **the number of ways to climb to the $i-1$-th step plus the number of ways to climb to the $i-2$-th step equals the number of ways to climb to the $i$-th step**. The formula is as follows: $$ dp[i] = dp[i-1] + dp[i-2] $$ This means that in the stair climbing problem, there exists a recurrence relation among the subproblems, **the solution to the original problem can be constructed from the solutions to the subproblems**. The figure below illustrates this recurrence relation. ![Recurrence relation for the number of ways](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png) We can obtain a brute force search solution based on the recurrence formula. Starting from $dp[n]$, **recursively decompose a larger problem into the sum of two smaller problems**, until reaching the smallest subproblems $dp[1]$ and $dp[2]$ and returning. Among them, the solutions to the smallest subproblems are known, namely $dp[1] = 1$ and $dp[2] = 2$, representing $1$ and $2$ ways to climb to the $1$st and $2$nd steps, respectively. Observe the following code, which, like standard backtracking code, belongs to depth-first search but is more concise: ```src [file]{climbing_stairs_dfs}-[class]{}-[func]{climbing_stairs_dfs} ``` The figure below shows the recursion tree formed by brute force search. For the problem $dp[n]$, the depth of its recursion tree is $n$, with a time complexity of $O(2^n)$. Exponential order represents explosive growth; if we input a relatively large $n$, we will fall into a long wait. ![Recursion tree for climbing stairs](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png) Observing the above figure, **the exponential time complexity is caused by "overlapping subproblems"**. For example, $dp[9]$ is decomposed into $dp[8]$ and $dp[7]$, and $dp[8]$ is decomposed into $dp[7]$ and $dp[6]$, both of which contain the subproblem $dp[7]$. And so on, subproblems contain smaller overlapping subproblems, ad infinitum. The vast majority of computational resources are wasted on these overlapping subproblems. ## Method 2: Memoization To improve algorithm efficiency, **we want all overlapping subproblems to be computed only once**. For this purpose, we declare an array `mem` to record the solution to each subproblem and prune overlapping subproblems during the search process. 1. When computing $dp[i]$ for the first time, we record it in `mem[i]` for later use. 2. When we need to compute $dp[i]$ again, we can directly retrieve the result from `mem[i]`, thereby avoiding redundant computation of that subproblem. The code is as follows: ```src [file]{climbing_stairs_dfs_mem}-[class]{}-[func]{climbing_stairs_dfs_mem} ``` Observe the figure below, **after memoization, all overlapping subproblems only need to be computed once, optimizing the time complexity to $O(n)$**, which is a tremendous leap. ![Recursion tree with memoization](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png) ## Method 3: Dynamic Programming **Memoization is a "top-down" method**: we start from the original problem (root node), recursively decompose larger subproblems into smaller ones, until reaching the smallest known subproblems (leaf nodes). Afterward, by backtracking, we collect the solutions to the subproblems layer by layer to construct the solution to the original problem. In contrast, **dynamic programming is a "bottom-up" method**: starting from the solutions to the smallest subproblems, iteratively constructing solutions to larger subproblems until obtaining the solution to the original problem. Since dynamic programming does not include a backtracking process, it only requires loop iteration for implementation and does not need recursion. In the following code, we initialize an array `dp` to store the solutions to subproblems, which serves the same recording function as the array `mem` in memoization: ```src [file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp} ``` The figure below simulates the execution process of the above code. ![Dynamic programming process for climbing stairs](intro_to_dynamic_programming.assets/climbing_stairs_dp.png) Like backtracking algorithms, dynamic programming also uses the "state" concept to represent specific stages of problem solving, with each state corresponding to a subproblem and its corresponding local optimal solution. For example, the state in the stair climbing problem is defined as the current stair step number $i$. Based on the above content, we can summarize the commonly used terminology in dynamic programming. - The array `dp` is called the dp table, where $dp[i]$ represents the solution to the subproblem corresponding to state $i$. - The states corresponding to the smallest subproblems (the $1$st and $2$nd steps) are called initial states. - The recurrence formula $dp[i] = dp[i-1] + dp[i-2]$ is called the state transition equation. ## Space Optimization Observant readers may have noticed that **since $dp[i]$ is only related to $dp[i-1]$ and $dp[i-2]$, we do not need to use an array `dp` to store the solutions to all subproblems**, but can simply use two variables to roll forward. The code is as follows: ```src [file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp_comp} ``` Observing the above code, since the space occupied by the array `dp` is saved, the space complexity is reduced from $O(n)$ to $O(1)$. In dynamic programming problems, the current state often depends only on a limited number of preceding states, allowing us to retain only the necessary states and save memory space through "dimension reduction". **This space optimization technique is called "rolling variable" or "rolling array"**. ================================================ FILE: en/docs/chapter_dynamic_programming/knapsack_problem.md ================================================ # 0-1 Knapsack Problem The knapsack problem is an excellent introductory problem for dynamic programming and is one of the most common problem forms in dynamic programming. It has many variants, such as the 0-1 knapsack problem, the unbounded knapsack problem, and the multiple knapsack problem. In this section, we will first solve the most common 0-1 knapsack problem. !!! question Given $n$ items, where the weight of the $i$-th item is $wgt[i-1]$ and its value is $val[i-1]$, and a knapsack with capacity $cap$. Each item can only be selected once. What is the maximum value that can be placed in the knapsack within the capacity limit? Observe the figure below. Since item number $i$ starts counting from $1$ and array indices start from $0$, item $i$ corresponds to weight $wgt[i-1]$ and value $val[i-1]$. ![Example data for 0-1 knapsack](knapsack_problem.assets/knapsack_example.png) We can view the 0-1 knapsack problem as a process consisting of $n$ rounds of decisions, where for each item there are two decisions: not putting it in and putting it in, thus the problem satisfies the decision tree model. The goal of this problem is to find "the maximum value that can be placed in the knapsack within the capacity limit", so it is more likely to be a dynamic programming problem. **Step 1: Think about the decisions in each round, define the state, and thus obtain the $dp$ table** For each item, if not placed in the knapsack, the knapsack capacity remains unchanged; if placed in, the knapsack capacity decreases. From this, we can derive the state definition: current item number $i$ and knapsack capacity $c$, denoted as $[i, c]$. State $[i, c]$ corresponds to the subproblem: **the maximum value among the first $i$ items in a knapsack of capacity $c$**, denoted as $dp[i, c]$. What we need to find is $dp[n, cap]$, so we need a two-dimensional $dp$ table of size $(n+1) \times (cap+1)$. **Step 2: Identify the optimal substructure, and then derive the state transition equation** After making the decision for item $i$, what remains is the subproblem of the first $i-1$ items, which can be divided into the following two cases. - **Not putting item $i$**: The knapsack capacity remains unchanged, and the state changes to $[i-1, c]$. - **Putting item $i$**: The knapsack capacity decreases by $wgt[i-1]$, the value increases by $val[i-1]$, and the state changes to $[i-1, c-wgt[i-1]]$. The above analysis reveals the optimal substructure of this problem: **the maximum value $dp[i, c]$ equals the larger value between not putting item $i$ and putting item $i$**. From this, the state transition equation can be derived: $$ dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) $$ Note that if the weight of the current item $wgt[i - 1]$ exceeds the remaining knapsack capacity $c$, then the only option is not to put it in the knapsack. **Step 3: Determine boundary conditions and state transition order** When there are no items or the knapsack capacity is $0$, the maximum value is $0$, i.e., the first column $dp[i, 0]$ and the first row $dp[0, c]$ are both equal to $0$. The current state $[i, c]$ is transferred from the state above $[i-1, c]$ and the state in the upper-left $[i-1, c-wgt[i-1]]$, so the entire $dp$ table is traversed in order through two nested loops. Based on the above analysis, we will next implement the brute force search, memoization, and dynamic programming solutions in order. ### Method 1: Brute Force Search The search code includes the following elements. - **Recursive parameters**: state $[i, c]$. - **Return value**: solution to the subproblem $dp[i, c]$. - **Termination condition**: when the item number is out of bounds $i = 0$ or the remaining knapsack capacity is $0$, terminate recursion and return value $0$. - **Pruning**: if the weight of the current item exceeds the remaining knapsack capacity, only the option of not putting it in is available. ```src [file]{knapsack}-[class]{}-[func]{knapsack_dfs} ``` As shown in the figure below, since each item generates two search branches of not selecting and selecting, the time complexity is $O(2^n)$. Observing the recursion tree, it is easy to see overlapping subproblems, such as $dp[1, 10]$. When there are many items, large knapsack capacity, and especially many items with the same weight, the number of overlapping subproblems will increase significantly. ![Brute force search recursion tree for 0-1 knapsack problem](knapsack_problem.assets/knapsack_dfs.png) ### Method 2: Memoization To ensure that overlapping subproblems are only computed once, we use a memo list `mem` to record the solutions to subproblems, where `mem[i][c]` corresponds to $dp[i, c]$. After introducing memoization, **the time complexity depends on the number of subproblems**, which is $O(n \times cap)$. The implementation code is as follows: ```src [file]{knapsack}-[class]{}-[func]{knapsack_dfs_mem} ``` The figure below shows the search branches pruned in memoization. ![Memoization recursion tree for 0-1 knapsack problem](knapsack_problem.assets/knapsack_dfs_mem.png) ### Method 3: Dynamic Programming Dynamic programming is essentially the process of filling the $dp$ table during state transitions. The code is as follows: ```src [file]{knapsack}-[class]{}-[func]{knapsack_dp} ``` As shown in the figure below, both time complexity and space complexity are determined by the size of the array `dp`, which is $O(n \times cap)$. === "<1>" ![Dynamic programming process for 0-1 knapsack problem](knapsack_problem.assets/knapsack_dp_step1.png) === "<2>" ![knapsack_dp_step2](knapsack_problem.assets/knapsack_dp_step2.png) === "<3>" ![knapsack_dp_step3](knapsack_problem.assets/knapsack_dp_step3.png) === "<4>" ![knapsack_dp_step4](knapsack_problem.assets/knapsack_dp_step4.png) === "<5>" ![knapsack_dp_step5](knapsack_problem.assets/knapsack_dp_step5.png) === "<6>" ![knapsack_dp_step6](knapsack_problem.assets/knapsack_dp_step6.png) === "<7>" ![knapsack_dp_step7](knapsack_problem.assets/knapsack_dp_step7.png) === "<8>" ![knapsack_dp_step8](knapsack_problem.assets/knapsack_dp_step8.png) === "<9>" ![knapsack_dp_step9](knapsack_problem.assets/knapsack_dp_step9.png) === "<10>" ![knapsack_dp_step10](knapsack_problem.assets/knapsack_dp_step10.png) === "<11>" ![knapsack_dp_step11](knapsack_problem.assets/knapsack_dp_step11.png) === "<12>" ![knapsack_dp_step12](knapsack_problem.assets/knapsack_dp_step12.png) === "<13>" ![knapsack_dp_step13](knapsack_problem.assets/knapsack_dp_step13.png) === "<14>" ![knapsack_dp_step14](knapsack_problem.assets/knapsack_dp_step14.png) ### Space Optimization Since each state is only related to the state in the row above it, we can use two arrays rolling forward to reduce the space complexity from $O(n^2)$ to $O(n)$. Further thinking, can we achieve space optimization using just one array? Observing, we can see that each state is transferred from the cell directly above or the cell in the upper-left. If there is only one array, when we start traversing row $i$, that array still stores the state of row $i-1$. - If using forward traversal, then when traversing to $dp[i, j]$, the values in the upper-left $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ may have already been overwritten, thus preventing correct state transition. - If using reverse traversal, there will be no overwriting issue, and state transition can proceed correctly. The figure below shows the transition process from row $i = 1$ to row $i = 2$ using a single array. Please consider the difference between forward and reverse traversal. === "<1>" ![Space-optimized dynamic programming process for 0-1 knapsack](knapsack_problem.assets/knapsack_dp_comp_step1.png) === "<2>" ![knapsack_dp_comp_step2](knapsack_problem.assets/knapsack_dp_comp_step2.png) === "<3>" ![knapsack_dp_comp_step3](knapsack_problem.assets/knapsack_dp_comp_step3.png) === "<4>" ![knapsack_dp_comp_step4](knapsack_problem.assets/knapsack_dp_comp_step4.png) === "<5>" ![knapsack_dp_comp_step5](knapsack_problem.assets/knapsack_dp_comp_step5.png) === "<6>" ![knapsack_dp_comp_step6](knapsack_problem.assets/knapsack_dp_comp_step6.png) In the code implementation, we simply need to delete the first dimension $i$ of the array `dp` and change the inner loop to reverse traversal: ```src [file]{knapsack}-[class]{}-[func]{knapsack_dp_comp} ``` ================================================ FILE: en/docs/chapter_dynamic_programming/summary.md ================================================ # Summary ### Key Review - Dynamic programming decomposes problems and avoids redundant computation by storing the solutions to subproblems, thereby significantly improving computational efficiency. - Without considering time constraints, all dynamic programming problems can be solved using backtracking (brute force search), but the recursion tree contains a large number of overlapping subproblems, resulting in extremely low efficiency. By introducing a memo list, we can store the solutions to all computed subproblems, ensuring that overlapping subproblems are only computed once. - Memoization is a top-down recursive solution, while the corresponding dynamic programming is a bottom-up iterative solution, similar to "filling in a table". Since the current state only depends on certain local states, we can eliminate one dimension of the $dp$ table to reduce space complexity. - Subproblem decomposition is a general algorithmic approach, with different properties in divide and conquer, dynamic programming, and backtracking. - Dynamic programming problems have three major characteristics: overlapping subproblems, optimal substructure, and no aftereffects. - If the optimal solution to the original problem can be constructed from the optimal solutions to the subproblems, then it has optimal substructure. - No aftereffects means that for a given state, its future development is only related to that state and has nothing to do with all past states. Many combinatorial optimization problems do not have no aftereffects and cannot be quickly solved using dynamic programming. **Knapsack problem** - The knapsack problem is one of the most typical dynamic programming problems, with variants such as the 0-1 knapsack, unbounded knapsack, and multiple knapsack. - The state definition for the 0-1 knapsack is the maximum value among the first $i$ items in a knapsack of capacity $c$. Based on the two decisions of not putting an item in the knapsack and putting it in, the optimal substructure can be identified and the state transition equation constructed. In space optimization, since each state depends on the state directly above and to the upper-left, the list needs to be traversed in reverse order to avoid overwriting the upper-left state. - The unbounded knapsack problem has no limit on the selection quantity of each type of item, so the state transition for choosing to put in an item differs from the 0-1 knapsack problem. Since the state depends on the state directly above and directly to the left, space optimization should use forward traversal. - The coin change problem is a variant of the unbounded knapsack problem. It changes from seeking the "maximum" value to seeking the "minimum" number of coins, so $\max()$ in the state transition equation should be changed to $\min()$. It changes from seeking "not exceeding" the knapsack capacity to seeking "exactly" making up the target amount, so $amt + 1$ is used to represent the invalid solution of "unable to make up the target amount". - Coin change problem II changes from seeking the "minimum number of coins" to seeking the "number of coin combinations", so the state transition equation correspondingly changes from $\min()$ to a summation operator. **Edit distance problem** - Edit distance (Levenshtein distance) is used to measure the similarity between two strings, defined as the minimum number of edit steps from one string to another, with edit operations including insert, delete, and replace. - The state definition for the edit distance problem is the minimum number of edit steps required to change the first $i$ characters of $s$ into the first $j$ characters of $t$. When $s[i] \ne t[j]$, there are three decisions: insert, delete, replace, each with corresponding remaining subproblems. From this, the optimal substructure can be identified and the state transition equation constructed. When $s[i] = t[j]$, no edit is required for the current character. - In edit distance, the state depends on the state directly above, directly to the left, and to the upper-left, so after space optimization, neither forward nor reverse traversal can correctly perform state transitions. For this reason, we use a variable to temporarily store the upper-left state, thus transforming to a situation equivalent to the unbounded knapsack problem, allowing for forward traversal after space optimization. ================================================ FILE: en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md ================================================ # Unbounded Knapsack Problem In this section, we first solve another common knapsack problem: the unbounded knapsack, and then explore a special case of it: the coin change problem. ## Unbounded Knapsack Problem !!! question Given $n$ items, where the weight of the $i$-th item is $wgt[i-1]$ and its value is $val[i-1]$, and a knapsack with capacity $cap$. **Each item can be selected multiple times**. What is the maximum value that can be placed in the knapsack within the capacity limit? An example is shown in the figure below. ![Example data for unbounded knapsack problem](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png) ### Dynamic Programming Approach The unbounded knapsack problem is very similar to the 0-1 knapsack problem, **differing only in that there is no limit on the number of times an item can be selected**. - In the 0-1 knapsack problem, there is only one of each type of item, so after placing item $i$ in the knapsack, we can only choose from the first $i-1$ items. - In the unbounded knapsack problem, the quantity of each type of item is unlimited, so after placing item $i$ in the knapsack, **we can still choose from the first $i$ items**. Under the rules of the unbounded knapsack problem, the changes in state $[i, c]$ are divided into two cases. - **Not putting item $i$**: Same as the 0-1 knapsack problem, transfer to $[i-1, c]$. - **Putting item $i$**: Different from the 0-1 knapsack problem, transfer to $[i, c-wgt[i-1]]$. Thus, the state transition equation becomes: $$ dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) $$ ### Code Implementation Comparing the code for the two problems, there is one change in state transition from $i-1$ to $i$, with everything else identical: ```src [file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp} ``` ### Space Optimization Since the current state is transferred from states on the left and above, **after space optimization, each row in the $dp$ table should be traversed in forward order**. This traversal order is exactly opposite to the 0-1 knapsack. Please refer to the figure below to understand the difference between the two. === "<1>" ![Space-optimized dynamic programming process for unbounded knapsack problem](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png) === "<2>" ![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png) === "<3>" ![unbounded_knapsack_dp_comp_step3](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png) === "<4>" ![unbounded_knapsack_dp_comp_step4](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png) === "<5>" ![unbounded_knapsack_dp_comp_step5](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png) === "<6>" ![unbounded_knapsack_dp_comp_step6](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png) The code implementation is relatively simple, just delete the first dimension of the array `dp`: ```src [file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp_comp} ``` ## Coin Change Problem The knapsack problem represents a large class of dynamic programming problems and has many variants, such as the coin change problem. !!! question Given $n$ types of coins, where the denomination of the $i$-th type of coin is $coins[i - 1]$, and the target amount is $amt$. **Each type of coin can be selected multiple times**. What is the minimum number of coins needed to make up the target amount? If it is impossible to make up the target amount, return $-1$. An example is shown in the figure below. ![Example data for coin change problem](unbounded_knapsack_problem.assets/coin_change_example.png) ### Dynamic Programming Approach **The coin change problem can be viewed as a special case of the unbounded knapsack problem**, with the following connections and differences. - The two problems can be converted to each other: "item" corresponds to "coin", "item weight" corresponds to "coin denomination", and "knapsack capacity" corresponds to "target amount". - The optimization goals are opposite: the unbounded knapsack problem aims to maximize item value, while the coin change problem aims to minimize the number of coins. - The unbounded knapsack problem seeks solutions "not exceeding" the knapsack capacity, while the coin change problem seeks solutions that "exactly" make up the target amount. **Step 1: Think about the decisions in each round, define the state, and thus obtain the $dp$ table** State $[i, a]$ corresponds to the subproblem: **the minimum number of coins among the first $i$ types of coins that can make up amount $a$**, denoted as $dp[i, a]$. The two-dimensional $dp$ table has size $(n+1) \times (amt+1)$. **Step 2: Identify the optimal substructure, and then derive the state transition equation** This problem differs from the unbounded knapsack problem in the following two aspects regarding the state transition equation. - This problem seeks the minimum value, so the operator $\max()$ needs to be changed to $\min()$. - The optimization target is the number of coins rather than item value, so when a coin is selected, simply execute $+1$. $$ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) $$ **Step 3: Determine boundary conditions and state transition order** When the target amount is $0$, the minimum number of coins needed to make it up is $0$, so all $dp[i, 0]$ in the first column equal $0$. When there are no coins, **it is impossible to make up any amount $> 0$**, which is an invalid solution. To enable the $\min()$ function in the state transition equation to identify and filter out invalid solutions, we consider using $+ \infty$ to represent them, i.e., set all $dp[0, a]$ in the first row to $+ \infty$. ### Code Implementation Most programming languages do not provide a $+ \infty$ variable, and can only use the maximum value of integer type `int` as a substitute. However, this can lead to large number overflow: the $+ 1$ operation in the state transition equation may cause overflow. For this reason, we use the number $amt + 1$ to represent invalid solutions, because the maximum number of coins needed to make up $amt$ is at most $amt$. Before returning, check whether $dp[n, amt]$ equals $amt + 1$; if so, return $-1$, indicating that the target amount cannot be made up. The code is as follows: ```src [file]{coin_change}-[class]{}-[func]{coin_change_dp} ``` The figure below shows the dynamic programming process for coin change, which is very similar to the unbounded knapsack problem. === "<1>" ![Dynamic programming process for coin change problem](unbounded_knapsack_problem.assets/coin_change_dp_step1.png) === "<2>" ![coin_change_dp_step2](unbounded_knapsack_problem.assets/coin_change_dp_step2.png) === "<3>" ![coin_change_dp_step3](unbounded_knapsack_problem.assets/coin_change_dp_step3.png) === "<4>" ![coin_change_dp_step4](unbounded_knapsack_problem.assets/coin_change_dp_step4.png) === "<5>" ![coin_change_dp_step5](unbounded_knapsack_problem.assets/coin_change_dp_step5.png) === "<6>" ![coin_change_dp_step6](unbounded_knapsack_problem.assets/coin_change_dp_step6.png) === "<7>" ![coin_change_dp_step7](unbounded_knapsack_problem.assets/coin_change_dp_step7.png) === "<8>" ![coin_change_dp_step8](unbounded_knapsack_problem.assets/coin_change_dp_step8.png) === "<9>" ![coin_change_dp_step9](unbounded_knapsack_problem.assets/coin_change_dp_step9.png) === "<10>" ![coin_change_dp_step10](unbounded_knapsack_problem.assets/coin_change_dp_step10.png) === "<11>" ![coin_change_dp_step11](unbounded_knapsack_problem.assets/coin_change_dp_step11.png) === "<12>" ![coin_change_dp_step12](unbounded_knapsack_problem.assets/coin_change_dp_step12.png) === "<13>" ![coin_change_dp_step13](unbounded_knapsack_problem.assets/coin_change_dp_step13.png) === "<14>" ![coin_change_dp_step14](unbounded_knapsack_problem.assets/coin_change_dp_step14.png) === "<15>" ![coin_change_dp_step15](unbounded_knapsack_problem.assets/coin_change_dp_step15.png) ### Space Optimization The space optimization for the coin change problem is handled in the same way as the unbounded knapsack problem: ```src [file]{coin_change}-[class]{}-[func]{coin_change_dp_comp} ``` ## Coin Change Problem Ii !!! question Given $n$ types of coins, where the denomination of the $i$-th type of coin is $coins[i - 1]$, and the target amount is $amt$. Each type of coin can be selected multiple times. **What is the number of coin combinations that can make up the target amount?** An example is shown in the figure below. ![Example data for coin change problem II](unbounded_knapsack_problem.assets/coin_change_ii_example.png) ### Dynamic Programming Approach Compared to the previous problem, this problem's goal is to find the number of combinations, so the subproblem becomes: **the number of combinations among the first $i$ types of coins that can make up amount $a$**. The $dp$ table remains a two-dimensional matrix of size $(n+1) \times (amt + 1)$. The number of combinations for the current state equals the sum of the combinations from not selecting the current coin and selecting the current coin. The state transition equation is: $$ dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] $$ When the target amount is $0$, no coins need to be selected to make up the target amount, so all $dp[i, 0]$ in the first column should be initialized to $1$. When there are no coins, it is impossible to make up any amount $>0$, so all $dp[0, a]$ in the first row equal $0$. ### Code Implementation ```src [file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp} ``` ### Space Optimization The space optimization is handled in the same way, just delete the coin dimension: ```src [file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp_comp} ``` ================================================ FILE: en/docs/chapter_graph/graph.md ================================================ # Graph A graph is a nonlinear data structure consisting of vertices and edges. We can abstractly represent a graph $G$ as a set of vertices $V$ and a set of edges $E$. The following example shows a graph containing 5 vertices and 7 edges. $$ \begin{aligned} V & = \{ 1, 2, 3, 4, 5 \} \newline E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline G & = \{ V, E \} \newline \end{aligned} $$ If we view vertices as nodes and edges as references (pointers) connecting the nodes, we can see graphs as a data structure extended from linked lists. As shown in the figure below, **compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) have a higher degree of freedom and are therefore more complex**. ![Relationships among linked lists, trees, and graphs](graph.assets/linkedlist_tree_graph.png) ## Common Types and Terminology of Graphs Graphs can be divided into undirected graphs and directed graphs based on whether edges have direction, as shown in the figure below. - In undirected graphs, edges represent a "bidirectional" connection between two vertices, such as the "friend relationship" on WeChat or QQ. - In directed graphs, edges have directionality, meaning edges $A \rightarrow B$ and $A \leftarrow B$ are independent of each other, such as the "follow" and "be followed" relationships on Weibo or TikTok. ![Directed and undirected graphs](graph.assets/directed_graph.png) Graphs can be divided into connected graphs and disconnected graphs based on whether all vertices are connected, as shown in the figure below. - For connected graphs, starting from any vertex, all other vertices can be reached. - For disconnected graphs, starting from a certain vertex, at least one vertex cannot be reached. ![Connected and disconnected graphs](graph.assets/connected_graph.png) We can also add a "weight" variable to edges, resulting in weighted graphs as shown in the figure below. For example, in mobile games like "Honor of Kings", the system calculates the "intimacy" between players based on their shared game time, and such intimacy networks can be represented using weighted graphs. ![Weighted and unweighted graphs](graph.assets/weighted_graph.png) Graph data structures include the following commonly used terms. - Adjacency: When two vertices are connected by an edge, these two vertices are said to be "adjacent". In the figure above, the adjacent vertices of vertex 1 are vertices 2, 3, and 5. - Path: The sequence of edges from vertex A to vertex B is called a "path" from A to B. In the figure above, the edge sequence 1-5-2-4 is a path from vertex 1 to vertex 4. - Degree: The number of edges a vertex has. For directed graphs, in-degree indicates how many edges point to the vertex, and out-degree indicates how many edges point out from the vertex. ## Representation of Graphs Common representations of graphs include "adjacency matrices" and "adjacency lists". The following uses undirected graphs as examples. ### Adjacency Matrix Given a graph with $n$ vertices, an adjacency matrix uses an $n \times n$ matrix to represent the graph, where each row (column) represents a vertex, and matrix elements represent edges, using $1$ or $0$ to indicate whether an edge exists between two vertices. As shown in the figure below, let the adjacency matrix be $M$ and the vertex list be $V$. Then matrix element $M[i, j] = 1$ indicates that an edge exists between vertex $V[i]$ and vertex $V[j]$, whereas $M[i, j] = 0$ indicates no edge between the two vertices. ![Adjacency matrix representation of a graph](graph.assets/adjacency_matrix.png) Adjacency matrices have the following properties. - In simple graphs, vertices cannot connect to themselves, so the elements on the main diagonal of the adjacency matrix are meaningless. - For undirected graphs, edges in both directions are equivalent, so the adjacency matrix is symmetric about the main diagonal. - Replacing the elements of the adjacency matrix from $1$ and $0$ to weights allows representation of weighted graphs. When using adjacency matrices to represent graphs, we can directly access matrix elements to obtain edges, resulting in highly efficient addition, deletion, lookup, and modification operations, all with a time complexity of $O(1)$. However, the space complexity of the matrix is $O(n^2)$, which consumes significant memory. ### Adjacency List An adjacency list uses $n$ linked lists to represent a graph, with linked list nodes representing vertices. The $i$-th linked list corresponds to vertex $i$ and stores all adjacent vertices of that vertex (vertices connected to that vertex). The figure below shows an example of a graph stored using an adjacency list. ![Adjacency list representation of a graph](graph.assets/adjacency_list.png) Adjacency lists only store edges that actually exist, and the total number of edges is typically much less than $n^2$, making them more space-efficient. However, finding edges in an adjacency list requires traversing the linked list, so its time efficiency is inferior to that of adjacency matrices. Observing the figure above, **the structure of adjacency lists is very similar to "chaining" in hash tables, so we can adopt similar methods to optimize efficiency**. For example, when linked lists are long, they can be converted to AVL trees or red-black trees, thereby optimizing time efficiency from $O(n)$ to $O(\log n)$; linked lists can also be converted to hash tables, thereby reducing time complexity to $O(1)$. ## Common Applications of Graphs As shown in the table below, many real-world systems can be modeled using graphs, and corresponding problems can be reduced to graph computation problems.

Table   Common graphs in real life

| | Vertices | Edges | Graph Computation Problem | | -------------- | --------------- | -------------------------------------- | ----------------------------- | | Social network | Users | Friend relationships | Potential friend recommendation | | Subway lines | Stations | Connectivity between stations | Shortest route recommendation | | Solar system | Celestial bodies | Gravitational forces between celestial bodies | Planetary orbit calculation | ================================================ FILE: en/docs/chapter_graph/graph_operations.md ================================================ # Basic Operations on Graphs Basic operations on graphs can be divided into operations on "edges" and operations on "vertices". Under the two representation methods of "adjacency matrix" and "adjacency list", the implementation methods differ. ## Implementation Based on Adjacency Matrix Given an undirected graph with $n$ vertices, the various operations are implemented as shown in the figure below. - **Adding or removing an edge**: Directly modify the specified edge in the adjacency matrix, using $O(1)$ time. Since it is an undirected graph, both directions of the edge need to be updated simultaneously. - **Adding a vertex**: Add a row and a column at the end of the adjacency matrix and fill them all with $0$s, using $O(n)$ time. - **Removing a vertex**: Delete a row and a column in the adjacency matrix. The worst case occurs when removing the first row and column, requiring $(n-1)^2$ elements to be "moved up and to the left", thus using $O(n^2)$ time. - **Initialization**: Pass in $n$ vertices, initialize a vertex list `vertices` of length $n$, using $O(n)$ time; initialize an adjacency matrix `adjMat` of size $n \times n$, using $O(n^2)$ time. === "<1>" ![Initialization, adding and removing edges, adding and removing vertices in adjacency matrix](graph_operations.assets/adjacency_matrix_step1_initialization.png) === "<2>" ![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png) === "<3>" ![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png) === "<4>" ![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png) === "<5>" ![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png) The following is the implementation code for graphs represented using an adjacency matrix: ```src [file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{} ``` ## Implementation Based on Adjacency List Given an undirected graph with a total of $n$ vertices and $m$ edges, the various operations can be implemented as shown in the figure below. - **Adding an edge**: Add the edge at the end of the corresponding vertex's linked list, using $O(1)$ time. Since it is an undirected graph, edges in both directions need to be added simultaneously. - **Removing an edge**: Find and remove the specified edge in the corresponding vertex's linked list, using $O(m)$ time. In an undirected graph, edges in both directions need to be removed simultaneously. - **Adding a vertex**: Add a linked list in the adjacency list and set the new vertex as the head node of the list, using $O(1)$ time. - **Removing a vertex**: Traverse the entire adjacency list and remove all edges containing the specified vertex, using $O(n + m)$ time. - **Initialization**: Create $n$ vertices and $2m$ edges in the adjacency list, using $O(n + m)$ time. === "<1>" ![Initialization, adding and removing edges, adding and removing vertices in adjacency list](graph_operations.assets/adjacency_list_step1_initialization.png) === "<2>" ![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png) === "<3>" ![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png) === "<4>" ![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png) === "<5>" ![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png) The following is the adjacency list code implementation. Compared to the figure above, the actual code has the following differences. - For convenience in adding and removing vertices, and to simplify the code, we use lists (dynamic arrays) instead of linked lists. - A hash table is used to store the adjacency list, where `key` is the vertex instance and `value` is the list (linked list) of adjacent vertices for that vertex. Additionally, we use the `Vertex` class to represent vertices in the adjacency list. The reason for this is: if we used list indices to distinguish different vertices as with adjacency matrices, then to delete the vertex at index $i$, we would need to traverse the entire adjacency list and decrement all indices greater than $i$ by $1$, which is very inefficient. However, if each vertex is a unique `Vertex` instance, deleting a vertex does not require modifying other vertices. ```src [file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{} ``` ## Efficiency Comparison Assuming the graph has $n$ vertices and $m$ edges, the table below compares the time efficiency and space efficiency of adjacency matrices and adjacency lists. Note that the adjacency list (linked list) corresponds to the implementation in this text, while the adjacency list (hash table) refers specifically to the implementation where all linked lists are replaced with hash tables.

Table   Comparison of adjacency matrix and adjacency list

| | Adjacency matrix | Adjacency list (linked list) | Adjacency list (hash table) | | ---------------------- | ---------------- | ---------------------------- | --------------------------- | | Determine adjacency | $O(1)$ | $O(n)$ | $O(1)$ | | Add an edge | $O(1)$ | $O(1)$ | $O(1)$ | | Remove an edge | $O(1)$ | $O(n)$ | $O(1)$ | | Add a vertex | $O(n)$ | $O(1)$ | $O(1)$ | | Remove a vertex | $O(n^2)$ | $O(n + m)$ | $O(n)$ | | Memory space usage | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | Observing the table above, it appears that the adjacency list (hash table) has the best time efficiency and space efficiency. However, in practice, operating on edges in the adjacency matrix is more efficient, requiring only a single array access or assignment operation. Overall, adjacency matrices embody the principle of "trading space for time", while adjacency lists embody "trading time for space". ================================================ FILE: en/docs/chapter_graph/graph_traversal.md ================================================ # Graph Traversal Trees represent "one-to-many" relationships, while graphs have a higher degree of freedom and can represent any "many-to-many" relationships. Therefore, we can view trees as a special case of graphs. Clearly, **tree traversal operations are also a special case of graph traversal operations**. Both graphs and trees require the application of search algorithms to implement traversal operations. Graph traversal methods can also be divided into two types: breadth-first traversal and depth-first traversal. ## Breadth-First Search **Breadth-first search is a near-to-far traversal method that, starting from a certain node, always prioritizes visiting the nearest vertices and expands outward layer by layer**. As shown in the figure below, starting from the top-left vertex, first traverse all adjacent vertices of that vertex, then traverse all adjacent vertices of the next vertex, and so on, until all vertices have been visited. ![Breadth-first search of a graph](graph_traversal.assets/graph_bfs.png) ### Algorithm Implementation BFS is typically implemented with the help of a queue, as shown in the code below. The queue has a "first in, first out" property, which aligns with the BFS idea of "near to far". 1. Add the starting vertex `startVet` to the queue and begin the loop. 2. In each iteration of the loop, pop the vertex at the front of the queue and record it as visited, then add all adjacent vertices of that vertex to the back of the queue. 3. Repeat step `2.` until all vertices have been visited. To prevent revisiting vertices, we use a hash set `visited` to record which nodes have been visited. !!! tip A hash set can be viewed as a hash table that stores only `key` without storing `value`. It can perform addition, deletion, lookup, and modification operations on `key` in $O(1)$ time complexity. Based on the uniqueness of `key`, hash sets are typically used for data deduplication and similar scenarios. ```src [file]{graph_bfs}-[class]{}-[func]{graph_bfs} ``` The code is relatively abstract; it is recommended to refer to the figure below to deepen understanding. === "<1>" ![Steps of breadth-first search of a graph](graph_traversal.assets/graph_bfs_step1.png) === "<2>" ![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png) === "<3>" ![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png) === "<4>" ![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png) === "<5>" ![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png) === "<6>" ![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png) === "<7>" ![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png) === "<8>" ![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png) === "<9>" ![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png) === "<10>" ![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png) === "<11>" ![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png) !!! question "Is the breadth-first traversal sequence unique?" Not unique. Breadth-first search only requires traversing in a "near to far" order, **and the traversal order of vertices at the same distance can be arbitrarily shuffled**. Taking the figure above as an example, the visit order of vertices $1$ and $3$ can be swapped, as can the visit order of vertices $2$, $4$, and $6$. ### Complexity Analysis **Time complexity**: All vertices will be enqueued and dequeued once, using $O(|V|)$ time; in the process of traversing adjacent vertices, since it is an undirected graph, all edges will be visited $2$ times, using $O(2|E|)$ time; overall using $O(|V| + |E|)$ time. **Space complexity**: The list `res`, hash set `visited`, and queue `que` can contain at most $|V|$ vertices, using $O(|V|)$ space. ## Depth-First Search **Depth-first search is a traversal method that prioritizes going as far as possible, then backtracks when no path remains**. As shown in the figure below, starting from the top-left vertex, visit an adjacent vertex of the current vertex, continuing until reaching a dead end, then return and continue going as far as possible before returning again, and so on, until all vertices have been traversed. ![Depth-first search of a graph](graph_traversal.assets/graph_dfs.png) ### Algorithm Implementation This "go as far as possible then return" algorithm paradigm is typically implemented using recursion. Similar to breadth-first search, in depth-first search we also need a hash set `visited` to record visited vertices and avoid revisiting. ```src [file]{graph_dfs}-[class]{}-[func]{graph_dfs} ``` The algorithm flow of depth-first search is shown in the figure below. - **Straight dashed lines represent downward recursion**, indicating that a new recursive method has been initiated to visit a new vertex. - **Curved dashed lines represent upward backtracking**, indicating that this recursive method has returned to the position where it was initiated. To deepen understanding, it is recommended to combine the figure below with the code to mentally simulate (or draw out) the entire DFS process, including when each recursive method is initiated and when it returns. === "<1>" ![Steps of depth-first search of a graph](graph_traversal.assets/graph_dfs_step1.png) === "<2>" ![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png) === "<3>" ![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png) === "<4>" ![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png) === "<5>" ![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png) === "<6>" ![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png) === "<7>" ![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png) === "<8>" ![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png) === "<9>" ![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png) === "<10>" ![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png) === "<11>" ![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png) !!! question "Is the depth-first traversal sequence unique?" Similar to breadth-first search, the order of depth-first traversal sequences is also not unique. Given a certain vertex, exploring in any direction first is valid, meaning the order of adjacent vertices can be arbitrarily shuffled, all being depth-first search. Taking tree traversal as an example, "root $\rightarrow$ left $\rightarrow$ right", "left $\rightarrow$ root $\rightarrow$ right", and "left $\rightarrow$ right $\rightarrow$ root" correspond to pre-order, in-order, and post-order traversals, respectively. They represent three different traversal priorities, yet all three belong to depth-first search. ### Complexity Analysis **Time complexity**: All vertices will be visited $1$ time, using $O(|V|)$ time; all edges will be visited $2$ times, using $O(2|E|)$ time; overall using $O(|V| + |E|)$ time. **Space complexity**: The list `res` and hash set `visited` can contain at most $|V|$ vertices, and the maximum recursion depth is $|V|$, therefore using $O(|V|)$ space. ================================================ FILE: en/docs/chapter_graph/index.md ================================================ # Graph ![Graph](../assets/covers/chapter_graph.jpg) !!! abstract In the journey of life, we are like nodes, connected by countless invisible edges. Each encounter and parting leaves a unique mark on this vast network graph. ================================================ FILE: en/docs/chapter_graph/summary.md ================================================ # Summary ### Key Review - Graphs consist of vertices and edges and can be represented as a set of vertices and a set of edges. - Compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) have a higher degree of freedom and are therefore more complex. - Directed graphs have edges with directionality, connected graphs have all vertices reachable from any vertex, and weighted graphs have edges that each contain a weight variable. - Adjacency matrices use matrices to represent graphs, where each row (column) represents a vertex, and matrix elements represent edges, using $1$ or $0$ to indicate whether two vertices have an edge or not. Adjacency matrices are highly efficient for addition, deletion, lookup, and modification operations, but consume significant space. - Adjacency lists use multiple linked lists to represent graphs, where the $i$-th linked list corresponds to vertex $i$ and stores all adjacent vertices of that vertex. Adjacency lists are more space-efficient than adjacency matrices, but have lower time efficiency because they require traversing linked lists to find edges. - When linked lists in adjacency lists become too long, they can be converted to red-black trees or hash tables, thereby improving lookup efficiency. - From an algorithmic perspective, adjacency matrices embody "trading space for time", while adjacency lists embody "trading time for space". - Graphs can be used to model various real-world systems, such as social networks and subway lines. - Trees are a special case of graphs, and tree traversal is a special case of graph traversal. - Breadth-first search of graphs is a near-to-far, layer-by-layer expansion search method, typically implemented using a queue. - Depth-first search of graphs is a search method that prioritizes going as far as possible and backtracks when no path remains, commonly implemented using recursion. ### Q & A **Q**: Is a path defined as a sequence of vertices or a sequence of edges? The definitions in different language versions of Wikipedia are inconsistent: the English version states "a path is a sequence of edges", while the Chinese version states "a path is a sequence of vertices". The following is the original English text: In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices. In this text, a path is viewed as a sequence of edges, not a sequence of vertices. This is because there may be multiple edges connecting two vertices, in which case each edge corresponds to a path. **Q**: In a disconnected graph, will there be unreachable vertices? In a disconnected graph, starting from a certain vertex, at least one vertex cannot be reached. Traversing a disconnected graph requires setting multiple starting points to traverse all connected components of the graph. **Q**: In an adjacency list, is there a requirement for the order of "all vertices connected to that vertex"? It can be in any order. However, in practical applications, it may be necessary to sort according to specified rules, such as the order in which vertices were added, or the order of vertex values, which helps quickly find vertices "with certain extreme values". ================================================ FILE: en/docs/chapter_greedy/fractional_knapsack_problem.md ================================================ # Fractional Knapsack Problem !!! question Given $n$ items, where the weight of the $i$-th item is $wgt[i-1]$ and its value is $val[i-1]$, and a knapsack with capacity $cap$. Each item can be selected only once, **but a portion of an item can be selected, with the value calculated based on the proportion of weight selected**, what is the maximum value of items in the knapsack under the limited capacity? An example is shown in the figure below. ![Example data for the fractional knapsack problem](fractional_knapsack_problem.assets/fractional_knapsack_example.png) The fractional knapsack problem is very similar overall to the 0-1 knapsack problem, with states including the current item $i$ and capacity $c$, and the goal being to maximize value under the limited knapsack capacity. The difference is that this problem allows selecting only a portion of an item. As shown in the figure below, **we can arbitrarily split items and calculate the corresponding value based on the weight proportion**. 1. For item $i$, its value per unit weight is $val[i-1] / wgt[i-1]$, referred to as unit value. 2. Suppose we put a portion of item $i$ with weight $w$ into the knapsack, then the value added to the knapsack is $w \times val[i-1] / wgt[i-1]$. ![Value of items per unit weight](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png) ### Greedy Strategy Determination Maximizing the total value of items in the knapsack **is essentially maximizing the value per unit weight of items**. From this, we can derive the greedy strategy shown in the figure below. 1. Sort items by unit value from high to low. 2. Iterate through all items, **greedily selecting the item with the highest unit value in each round**. 3. If the remaining knapsack capacity is insufficient, use a portion of the current item to fill the knapsack. ![Greedy strategy for the fractional knapsack problem](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png) ### Code Implementation We created an `Item` class to facilitate sorting items by unit value. We loop to make greedy selections, breaking when the knapsack is full and returning the solution: ```src [file]{fractional_knapsack}-[class]{}-[func]{fractional_knapsack} ``` The time complexity of built-in sorting algorithms is usually $O(\log n)$, and the space complexity is usually $O(\log n)$ or $O(n)$, depending on the specific implementation of the programming language. Apart from sorting, in the worst case the entire item list needs to be traversed, **therefore the time complexity is $O(n)$**, where $n$ is the number of items. Since an `Item` object list is initialized, **the space complexity is $O(n)$**. ### Correctness Proof Using proof by contradiction. Suppose item $x$ has the highest unit value, and some algorithm yields a maximum value of `res`, but this solution does not include item $x$. Now remove a unit weight of any item from the knapsack and replace it with a unit weight of item $x$. Since item $x$ has the highest unit value, the total value after replacement will definitely be greater than `res`. **This contradicts the assumption that `res` is the optimal solution, proving that the optimal solution must include item $x$**. For other items in this solution, we can also construct the above contradiction. In summary, **items with greater unit value are always better choices**, which proves that the greedy strategy is effective. As shown in the figure below, if we view item weight and item unit value as the horizontal and vertical axes of a two-dimensional chart respectively, then the fractional knapsack problem can be transformed into "finding the maximum area enclosed within a limited horizontal axis range". This analogy can help us understand the effectiveness of the greedy strategy from a geometric perspective. ![Geometric representation of the fractional knapsack problem](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png) ================================================ FILE: en/docs/chapter_greedy/greedy_algorithm.md ================================================ # Greedy Algorithm Greedy algorithm is a common algorithm for solving optimization problems. Its basic idea is to make the seemingly best choice at each decision stage of the problem, that is, to greedily make locally optimal decisions in hopes of obtaining a globally optimal solution. Greedy algorithms are simple and efficient, and are widely applied in many practical problems. Greedy algorithms and dynamic programming are both commonly used to solve optimization problems. They share some similarities, such as both relying on the optimal substructure property, but they work differently. - Dynamic programming considers all previous decisions when making the current decision, and uses solutions to past subproblems to construct the solution to the current subproblem. - Greedy algorithms do not consider past decisions, but instead make greedy choices moving forward, continually reducing the problem size until the problem is solved. We will first understand how greedy algorithms work through the example problem "coin change". This problem has already been introduced in the "Complete Knapsack Problem" chapter, so I believe you are not unfamiliar with it. !!! question Given $n$ types of coins, where the denomination of the $i$-th type of coin is $coins[i - 1]$, and the target amount is $amt$, with each type of coin available for repeated selection, what is the minimum number of coins needed to make up the target amount? If it is impossible to make up the target amount, return $-1$. The greedy strategy adopted for this problem is shown in the figure below. Given a target amount, **we greedily select the coin that is not greater than and closest to it**, and continuously repeat this step until the target amount is reached. ![Greedy strategy for coin change](greedy_algorithm.assets/coin_change_greedy_strategy.png) The implementation code is as follows: ```src [file]{coin_change_greedy}-[class]{}-[func]{coin_change_greedy} ``` You might exclaim: So clean! The greedy algorithm solves the coin change problem in about ten lines of code. ## Advantages and Limitations of Greedy Algorithms **Greedy algorithms are not only straightforward and simple to implement, but are also usually very efficient**. In the code above, if the smallest coin denomination is $\min(coins)$, the greedy choice loops at most $amt / \min(coins)$ times, giving a time complexity of $O(amt / \min(coins))$. This is an order of magnitude smaller than the time complexity of the dynamic programming solution $O(n \times amt)$. However, **for certain coin denomination combinations, greedy algorithms cannot find the optimal solution**. The figure below provides two examples. - **Positive example $coins = [1, 5, 10, 20, 50, 100]$**: With this coin combination, given any $amt$, the greedy algorithm can find the optimal solution. - **Negative example $coins = [1, 20, 50]$**: Suppose $amt = 60$, the greedy algorithm can only find the combination $50 + 1 \times 10$, totaling $11$ coins, but dynamic programming can find the optimal solution $20 + 20 + 20$, requiring only $3$ coins. - **Negative example $coins = [1, 49, 50]$**: Suppose $amt = 98$, the greedy algorithm can only find the combination $50 + 1 \times 48$, totaling $49$ coins, but dynamic programming can find the optimal solution $49 + 49$, requiring only $2$ coins. ![Examples where greedy algorithms cannot find the optimal solution](greedy_algorithm.assets/coin_change_greedy_vs_dp.png) In other words, for the coin change problem, greedy algorithms cannot guarantee finding the global optimal solution, and may even find very poor solutions. It is better suited for solving with dynamic programming. Generally, the applicability of greedy algorithms falls into the following two situations. 1. **Can guarantee finding the optimal solution**: In this situation, greedy algorithms are often the best choice, because they tend to be more efficient than backtracking and dynamic programming. 2. **Can find an approximate optimal solution**: Greedy algorithms are also applicable in this situation. For many complex problems, finding the global optimal solution is very difficult, and being able to find a suboptimal solution with high efficiency is also very good. ## Characteristics of Greedy Algorithms So the question arises: what kind of problems are suitable for solving with greedy algorithms? Or in other words, under what conditions can greedy algorithms guarantee finding the optimal solution? Compared to dynamic programming, the conditions for using greedy algorithms are stricter, mainly focusing on two properties of the problem. - **Greedy choice property**: Only when locally optimal choices can always lead to a globally optimal solution can greedy algorithms guarantee obtaining the optimal solution. - **Optimal substructure**: The optimal solution to the original problem contains the optimal solutions to subproblems. Optimal substructure has already been introduced in the "Dynamic Programming" chapter, so we won't elaborate on it here. It's worth noting that the optimal substructure of some problems is not obvious, but they can still be solved using greedy algorithms. We mainly explore methods for determining the greedy choice property. Although its description seems relatively simple, **in practice, for many problems, proving the greedy choice property is not easy**. For example, in the coin change problem, although we can easily provide counterexamples to disprove the greedy choice property, proving it is quite difficult. If asked: **what conditions must a coin combination satisfy to be solvable using a greedy algorithm**? We often can only rely on intuition or examples to give an ambiguous answer, and find it difficult to provide a rigorous mathematical proof. !!! quote There is a paper that presents an algorithm with $O(n^3)$ time complexity for determining whether a coin combination can use a greedy algorithm to find the optimal solution for any amount. Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234. ## Steps for Solving Problems with Greedy Algorithms The problem-solving process for greedy problems can generally be divided into the following three steps. 1. **Problem analysis**: Sort out and understand the problem characteristics, including state definition, optimization objectives, and constraints, etc. This step is also involved in backtracking and dynamic programming. 2. **Determine the greedy strategy**: Determine how to make greedy choices at each step. This strategy should be able to reduce the problem size at each step, ultimately solving the entire problem. 3. **Correctness proof**: It is usually necessary to prove that the problem has both greedy choice property and optimal substructure. This step may require mathematical proofs, such as mathematical induction or proof by contradiction. Determining the greedy strategy is the core step in solving the problem, but it may not be easy to implement, mainly for the following reasons. - **Greedy strategies differ greatly between different problems**. For many problems, the greedy strategy is relatively straightforward, and we can derive it through some general thinking and attempts. However, for some complex problems, the greedy strategy may be very elusive, which really tests one's problem-solving experience and algorithmic ability. - **Some greedy strategies are highly misleading**. When we confidently design a greedy strategy, write the solution code and submit it for testing, we may find that some test cases cannot pass. This is because the designed greedy strategy is only "partially correct", as exemplified by the coin change problem discussed above. To ensure correctness, we should rigorously mathematically prove the greedy strategy, **usually using proof by contradiction or mathematical induction**. However, correctness proofs may also not be easy. If we have no clue, we usually choose to debug the code based on test cases, step by step modifying and verifying the greedy strategy. ## Typical Problems Solved by Greedy Algorithms Greedy algorithms are often applied to optimization problems that satisfy greedy choice property and optimal substructure. Below are some typical greedy algorithm problems. - **Coin change problem**: With certain coin combinations, greedy algorithms can always obtain the optimal solution. - **Interval scheduling problem**: Suppose you have some tasks, each taking place during a period of time, and your goal is to complete as many tasks as possible. If you always choose the task that ends earliest, then the greedy algorithm can obtain the optimal solution. - **Fractional knapsack problem**: Given a set of items and a carrying capacity, your goal is to select a set of items such that the total weight does not exceed the carrying capacity and the total value is maximized. If you always choose the item with the highest value-to-weight ratio (value / weight), then the greedy algorithm can obtain the optimal solution in some cases. - **Stock trading problem**: Given a set of historical stock prices, you can make multiple trades, but if you already hold stocks, you cannot buy again before selling, and the goal is to obtain the maximum profit. - **Huffman coding**: Huffman coding is a greedy algorithm used for lossless data compression. By constructing a Huffman tree and always merging the two nodes with the lowest frequency, the resulting Huffman tree has the minimum weighted path length (encoding length). - **Dijkstra's algorithm**: It is a greedy algorithm for solving the shortest path problem from a given source vertex to all other vertices. ================================================ FILE: en/docs/chapter_greedy/index.md ================================================ # Greedy ![Greedy](../assets/covers/chapter_greedy.jpg) !!! abstract Sunflowers turn toward the sun, constantly pursuing the maximum potential for their own growth. Through rounds of simple choices, greedy strategies gradually lead to the best answer. ================================================ FILE: en/docs/chapter_greedy/max_capacity_problem.md ================================================ # Max Capacity Problem !!! question Input an array $ht$, where each element represents the height of a vertical partition. Any two partitions in the array, along with the space between them, can form a container. The capacity of the container equals the product of height and width (area), where the height is determined by the shorter partition, and the width is the difference in array indices between the two partitions. Please select two partitions in the array such that the capacity of the formed container is maximized, and return the maximum capacity. An example is shown in the figure below. ![Example data for the max capacity problem](max_capacity_problem.assets/max_capacity_example.png) The container is formed by any two partitions, **therefore the state of this problem is the indices of two partitions, denoted as $[i, j]$**. According to the problem description, capacity equals height multiplied by width, where height is determined by the shorter partition, and width is the difference in array indices between the two partitions. Let the capacity be $cap[i, j]$, then the calculation formula is: $$ cap[i, j] = \min(ht[i], ht[j]) \times (j - i) $$ Let the array length be $n$, then the number of combinations of two partitions (total number of states) is $C_n^2 = \frac{n(n - 1)}{2}$. Most directly, **we can exhaustively enumerate all states** to find the maximum capacity, with time complexity $O(n^2)$. ### Greedy Strategy Determination This problem has a more efficient solution. As shown in the figure below, select a state $[i, j]$ where index $i < j$ and height $ht[i] < ht[j]$, meaning $i$ is the short partition and $j$ is the long partition. ![Initial state](max_capacity_problem.assets/max_capacity_initial_state.png) As shown in the figure below, **if we now move the long partition $j$ closer to the short partition $i$, the capacity will definitely decrease**. This is because after moving the long partition $j$, the width $j-i$ definitely decreases; and since height is determined by the short partition, the height can only remain unchanged ($i$ is still the short partition) or decrease (the moved $j$ becomes the short partition). ![State after moving the long partition inward](max_capacity_problem.assets/max_capacity_moving_long_board.png) Conversely, **we can only possibly increase capacity by contracting the short partition $i$ inward**. Because although width will definitely decrease, **height may increase** (the moved short partition $i$ may become taller). For example, in the figure below, the area increases after moving the short partition. ![State after moving the short partition inward](max_capacity_problem.assets/max_capacity_moving_short_board.png) From this we can derive the greedy strategy for this problem: initialize two pointers at both ends of the container, and in each round contract the pointer corresponding to the short partition inward, until the two pointers meet. The figure below shows the execution process of the greedy strategy. 1. In the initial state, pointers $i$ and $j$ are at both ends of the array. 2. Calculate the capacity of the current state $cap[i, j]$, and update the maximum capacity. 3. Compare the heights of partition $i$ and partition $j$, and move the short partition inward by one position. 4. Loop through steps `2.` and `3.` until $i$ and $j$ meet. === "<1>" ![Greedy process for the max capacity problem](max_capacity_problem.assets/max_capacity_greedy_step1.png) === "<2>" ![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png) === "<3>" ![max_capacity_greedy_step3](max_capacity_problem.assets/max_capacity_greedy_step3.png) === "<4>" ![max_capacity_greedy_step4](max_capacity_problem.assets/max_capacity_greedy_step4.png) === "<5>" ![max_capacity_greedy_step5](max_capacity_problem.assets/max_capacity_greedy_step5.png) === "<6>" ![max_capacity_greedy_step6](max_capacity_problem.assets/max_capacity_greedy_step6.png) === "<7>" ![max_capacity_greedy_step7](max_capacity_problem.assets/max_capacity_greedy_step7.png) === "<8>" ![max_capacity_greedy_step8](max_capacity_problem.assets/max_capacity_greedy_step8.png) === "<9>" ![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png) ### Code Implementation The code loops at most $n$ rounds, **therefore the time complexity is $O(n)$**. Variables $i$, $j$, and $res$ use a constant amount of extra space, **therefore the space complexity is $O(1)$**. ```src [file]{max_capacity}-[class]{}-[func]{max_capacity} ``` ### Correctness Proof The reason greedy is faster than exhaustive enumeration is that each round of greedy selection "skips" some states. For example, in state $cap[i, j]$ where $i$ is the short partition and $j$ is the long partition, if we greedily move the short partition $i$ inward by one position, the states shown in the figure below will be "skipped". **This means that the capacities of these states cannot be verified later**. $$ cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1] $$ ![States skipped by moving the short partition](max_capacity_problem.assets/max_capacity_skipped_states.png) Observing carefully, **these skipped states are actually all the states obtained by moving the long partition $j$ inward**. We have already proven that moving the long partition inward will definitely decrease capacity. That is, the skipped states cannot possibly be the optimal solution, **skipping them will not cause us to miss the optimal solution**. The above analysis shows that the operation of moving the short partition is "safe", and the greedy strategy is effective. ================================================ FILE: en/docs/chapter_greedy/max_product_cutting_problem.md ================================================ # Max Product Cutting Problem !!! question Given a positive integer $n$, split it into the sum of at least two positive integers, and find the maximum product of all integers after splitting, as shown in the figure below. ![Problem definition of max product cutting](max_product_cutting_problem.assets/max_product_cutting_definition.png) Suppose we split $n$ into $m$ integer factors, where the $i$-th factor is denoted as $n_i$, that is $$ n = \sum_{i=1}^{m}n_i $$ The goal of this problem is to find the maximum product of all integer factors, namely $$ \max(\prod_{i=1}^{m}n_i) $$ We need to think about: how large should the splitting count $m$ be, and what should each $n_i$ be? ### Greedy Strategy Determination Based on experience, the product of two integers is often greater than their sum. Suppose we split out a factor of $2$ from $n$, then their product is $2(n-2)$. We compare this product with $n$: $$ \begin{aligned} 2(n-2) & \geq n \newline 2n - n - 4 & \geq 0 \newline n & \geq 4 \end{aligned} $$ As shown in the figure below, when $n \geq 4$, splitting out a $2$ will increase the product, **which indicates that integers greater than or equal to $4$ should all be split**. **Greedy strategy one**: If the splitting scheme includes factors $\geq 4$, then they should continue to be split. The final splitting scheme should only contain factors $1$, $2$, and $3$. ![Splitting causes product to increase](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png) Next, consider which factor is optimal. Among the three factors $1$, $2$, and $3$, clearly $1$ is the worst, because $1 \times (n-1) < n$ always holds, meaning splitting out $1$ will actually decrease the product. As shown in the figure below, when $n = 6$, we have $3 \times 3 > 2 \times 2 \times 2$. **This means that splitting out $3$ is better than splitting out $2$**. **Greedy strategy two**: In the splitting scheme, there should be at most two $2$s. Because three $2$s can always be replaced by two $3$s to obtain a larger product. ![Optimal splitting factor](max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png) In summary, the following greedy strategies can be derived. 1. Input integer $n$, continuously split out factor $3$ until the remainder is $0$, $1$, or $2$. 2. When the remainder is $0$, it means $n$ is a multiple of $3$, so no further action is needed. 3. When the remainder is $2$, do not continue splitting, keep it. 4. When the remainder is $1$, since $2 \times 2 > 1 \times 3$, the last $3$ should be replaced with $2$. ### Code Implementation As shown in the figure below, we don't need to use loops to split the integer, but can use integer division to get the count of $3$s as $a$, and modulo operation to get the remainder as $b$, at which point we have: $$ n = 3 a + b $$ Please note that for the edge case of $n \leq 3$, a $1$ must be split out, with product $1 \times (n - 1)$. ```src [file]{max_product_cutting}-[class]{}-[func]{max_product_cutting} ``` ![Calculation method for max product cutting](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png) **The time complexity depends on the implementation of the exponentiation operation in the programming language**. Taking Python as an example, there are three commonly used power calculation functions. - Both the operator `**` and the function `pow()` have time complexity $O(\log⁡ a)$. - The function `math.pow()` internally calls the C library's `pow()` function, which performs floating-point exponentiation, with time complexity $O(1)$. Variables $a$ and $b$ use a constant amount of extra space, **therefore the space complexity is $O(1)$**. ### Correctness Proof Using proof by contradiction, only analyzing the case where $n \geq 4$. 1. **All factors $\leq 3$**: Suppose the optimal splitting scheme includes a factor $x \geq 4$, then it can definitely continue to be split into $2(x-2)$ to obtain a larger (or equal) product. This contradicts the assumption. 2. **The splitting scheme does not contain $1$**: Suppose the optimal splitting scheme includes a factor of $1$, then it can definitely be merged into another factor to obtain a larger product. This contradicts the assumption. 3. **The splitting scheme contains at most two $2$s**: Suppose the optimal splitting scheme includes three $2$s, then they can definitely be replaced by two $3$s for a larger product. This contradicts the assumption. ================================================ FILE: en/docs/chapter_greedy/summary.md ================================================ # Summary ### Key Review - Greedy algorithms are typically used to solve optimization problems. The principle is to make locally optimal decisions at each decision stage in hopes of obtaining a globally optimal solution. - Greedy algorithms iteratively make one greedy choice after another, transforming the problem into a smaller subproblem in each round, until the problem is solved. - Greedy algorithms are not only simple to implement, but also have high problem-solving efficiency. Compared to dynamic programming, greedy algorithms typically have lower time complexity. - In the coin change problem, for certain coin combinations, greedy algorithms can guarantee finding the optimal solution; for other coin combinations, however, greedy algorithms may find very poor solutions. - Problems suitable for solving with greedy algorithms have two major properties: greedy choice property and optimal substructure. The greedy choice property represents the effectiveness of the greedy strategy. - For some complex problems, proving the greedy choice property is not simple. Relatively speaking, disproving it is easier, such as in the coin change problem. - Solving greedy problems mainly consists of three steps: problem analysis, determining the greedy strategy, and correctness proof. Among these, determining the greedy strategy is the core step, and correctness proof is often the difficult point. - The fractional knapsack problem, based on the 0-1 knapsack problem, allows selecting a portion of items, and therefore can be solved using greedy algorithms. The correctness of the greedy strategy can be proven using proof by contradiction. - The max capacity problem can be solved using exhaustive enumeration with time complexity $O(n^2)$. By designing a greedy strategy to move the short partition inward in each round, the time complexity can be optimized to $O(n)$. - In the max product cutting problem, we successively derive two greedy strategies: integers $\geq 4$ should all continue to be split, and the optimal splitting factor is $3$. The code includes exponentiation operations, and the time complexity depends on the implementation method of exponentiation, typically being $O(1)$ or $O(\log n)$. ================================================ FILE: en/docs/chapter_hashing/hash_algorithm.md ================================================ # Hash Algorithm The previous two sections introduced the working principle of hash tables and the methods to handle hash collisions. However, both open addressing and separate chaining **can only ensure that the hash table functions normally when hash collisions occur, but cannot reduce the frequency of hash collisions**. If hash collisions occur too frequently, the performance of the hash table will deteriorate drastically. As shown in the figure below, for a separate chaining hash table, in the ideal case, the key-value pairs are evenly distributed across the buckets, achieving optimal query efficiency; in the worst case, all key-value pairs are stored in the same bucket, degrading the time complexity to $O(n)$. ![Ideal and worst cases of hash collisions](hash_algorithm.assets/hash_collision_best_worst_condition.png) **The distribution of key-value pairs is determined by the hash function**. Recalling the calculation steps of the hash function, first compute the hash value, then take the modulo by the array length: ```shell index = hash(key) % capacity ``` Observing the above formula, when the hash table capacity `capacity` is fixed, **the hash algorithm `hash()` determines the output value**, thereby determining the distribution of key-value pairs in the hash table. This means that, to reduce the probability of hash collisions, we should focus on the design of the hash algorithm `hash()`. ## Goals of Hash Algorithms To achieve a "fast and stable" hash table data structure, hash algorithms should have the following characteristics: - **Determinism**: For the same input, the hash algorithm should always produce the same output. Only then can the hash table be reliable. - **High efficiency**: The process of computing the hash value should be fast enough. The smaller the computational overhead, the more practical the hash table. - **Uniform distribution**: The hash algorithm should ensure that key-value pairs are evenly distributed in the hash table. The more uniform the distribution, the lower the probability of hash collisions. In fact, hash algorithms are not only used to implement hash tables but are also widely applied in other fields. - **Password storage**: To protect the security of user passwords, systems usually do not store the plaintext passwords but rather the hash values of the passwords. When a user enters a password, the system calculates the hash value of the input and compares it with the stored hash value. If they match, the password is considered correct. - **Data integrity check**: The data sender can calculate the hash value of the data and send it along; the receiver can recalculate the hash value of the received data and compare it with the received hash value. If they match, the data is considered intact. For cryptographic applications, to prevent reverse engineering such as deducing the original password from the hash value, hash algorithms need higher-level security features. - **Unidirectionality**: It should be impossible to deduce any information about the input data from the hash value. - **Collision resistance**: It should be extremely difficult to find two different inputs that produce the same hash value. - **Avalanche effect**: Minor changes in the input should lead to significant and unpredictable changes in the output. Note that **"uniform distribution" and "collision resistance" are two independent concepts**. Satisfying uniform distribution does not necessarily mean collision resistance. For example, under random input `key`, the hash function `key % 100` can produce a uniformly distributed output. However, this hash algorithm is too simple, and all `key` with the same last two digits will have the same output, making it easy to deduce a usable `key` from the hash value, thereby cracking the password. ## Design of Hash Algorithms The design of hash algorithms is a complex issue that requires consideration of many factors. However, for some less demanding scenarios, we can also design some simple hash algorithms. - **Additive hash**: Add up the ASCII codes of each character in the input and use the total sum as the hash value. - **Multiplicative hash**: Utilize the non-correlation of multiplication, multiplying each round by a constant, accumulating the ASCII codes of each character into the hash value. - **XOR hash**: Accumulate the hash value by XORing each element of the input data. - **Rotating hash**: Accumulate the ASCII code of each character into a hash value, performing a rotation operation on the hash value before each accumulation. ```src [file]{simple_hash}-[class]{}-[func]{rot_hash} ``` It is observed that the last step of each hash algorithm is to take the modulus of the large prime number $1000000007$ to ensure that the hash value is within an appropriate range. It is worth pondering why emphasis is placed on modulo a prime number, or what are the disadvantages of modulo a composite number? This is an interesting question. To conclude: **Using a large prime number as the modulus can maximize the uniform distribution of hash values**. Since a prime number does not share common factors with other numbers, it can reduce the periodic patterns caused by the modulo operation, thus avoiding hash collisions. For example, suppose we choose the composite number $9$ as the modulus, which can be divided by $3$, then all `key` divisible by $3$ will be mapped to hash values $0$, $3$, $6$. $$ \begin{aligned} \text{modulus} & = 9 \newline \text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline \text{hash} & = \{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\dots \} \end{aligned} $$ If the input `key` happens to have this kind of arithmetic sequence distribution, then the hash values will cluster, thereby exacerbating hash collisions. Now, suppose we replace `modulus` with the prime number $13$, since there are no common factors between `key` and `modulus`, the uniformity of the output hash values will be significantly improved. $$ \begin{aligned} \text{modulus} & = 13 \newline \text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline \text{hash} & = \{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \dots \} \end{aligned} $$ It is worth noting that if the `key` is guaranteed to be randomly and uniformly distributed, then choosing a prime number or a composite number as the modulus can both produce uniformly distributed hash values. However, when the distribution of `key` has some periodicity, modulo a composite number is more likely to result in clustering. In summary, we usually choose a prime number as the modulus, and this prime number should be large enough to eliminate periodic patterns as much as possible, enhancing the robustness of the hash algorithm. ## Common Hash Algorithms It is not hard to see that the simple hash algorithms mentioned above are quite "fragile" and far from reaching the design goals of hash algorithms. For example, since addition and XOR obey the commutative law, additive hash and XOR hash cannot distinguish strings with the same content but in different order, which may exacerbate hash collisions and cause security issues. In practice, we usually use some standard hash algorithms, such as MD5, SHA-1, SHA-2, and SHA-3. They can map input data of any length to a fixed-length hash value. Over the past century, hash algorithms have been in a continuous process of upgrading and optimization. Some researchers strive to improve the performance of hash algorithms, while others, including hackers, are dedicated to finding security issues in hash algorithms. The table below shows hash algorithms commonly used in practical applications. - MD5 and SHA-1 have been successfully attacked multiple times and are thus abandoned in various security applications. - SHA-2 series, especially SHA-256, is one of the most secure hash algorithms to date, with no successful attacks reported, hence commonly used in various security applications and protocols. - SHA-3 has lower implementation costs and higher computational efficiency compared to SHA-2, but its current usage coverage is not as extensive as the SHA-2 series.

Table   Common hash algorithms

| | MD5 | SHA-1 | SHA-2 | SHA-3 | | --------------- | ----------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------- | ---------------------------- | | Release Year | 1992 | 1995 | 2002 | 2008 | | Output Length | 128 bit | 160 bit | 256/512 bit | 224/256/384/512 bit | | Hash Collisions | Frequent | Frequent | Rare | Rare | | Security Level | Low, has been successfully attacked | Low, has been successfully attacked | High | High | | Applications | Abandoned, still used for data integrity checks | Abandoned | Cryptocurrency transaction verification, digital signatures, etc. | Can be used to replace SHA-2 | # Hash Values in Data Structures We know that the keys in a hash table can be of various data types such as integers, decimals, or strings. Programming languages usually provide built-in hash algorithms for these data types to calculate the bucket indices in the hash table. Taking Python as an example, we can use the `hash()` function to compute the hash values for various data types. - The hash values of integers and booleans are their own values. - The calculation of hash values for floating-point numbers and strings is more complex, and interested readers are encouraged to study this on their own. - The hash value of a tuple is a combination of the hash values of each of its elements, resulting in a single hash value. - The hash value of an object is generated based on its memory address. By overriding the hash method of an object, hash values can be generated based on content. !!! tip Be aware that the definition and methods of the built-in hash value calculation functions in different programming languages vary. === "Python" ```python title="built_in_hash.py" num = 3 hash_num = hash(num) # Hash value of integer 3 is 3 bol = True hash_bol = hash(bol) # Hash value of boolean True is 1 dec = 3.14159 hash_dec = hash(dec) # Hash value of decimal 3.14159 is 326484311674566659 str = "Hello 算法" hash_str = hash(str) # Hash value of string "Hello 算法" is 4617003410720528961 tup = (12836, "小哈") hash_tup = hash(tup) # Hash value of tuple (12836, '小哈') is 1029005403108185979 obj = ListNode(0) hash_obj = hash(obj) # Hash value of ListNode object at 0x1058fd810 is 274267521 ``` === "C++" ```cpp title="built_in_hash.cpp" int num = 3; size_t hashNum = hash()(num); // Hash value of integer 3 is 3 bool bol = true; size_t hashBol = hash()(bol); // Hash value of boolean 1 is 1 double dec = 3.14159; size_t hashDec = hash()(dec); // Hash value of decimal 3.14159 is 4614256650576692846 string str = "Hello 算法"; size_t hashStr = hash()(str); // Hash value of string "Hello 算法" is 15466937326284535026 // In C++, built-in std::hash() only provides hash values for basic data types // Hash values for arrays and objects need to be implemented separately ``` === "Java" ```java title="built_in_hash.java" int num = 3; int hashNum = Integer.hashCode(num); // Hash value of integer 3 is 3 boolean bol = true; int hashBol = Boolean.hashCode(bol); // Hash value of boolean true is 1231 double dec = 3.14159; int hashDec = Double.hashCode(dec); // Hash value of decimal 3.14159 is -1340954729 String str = "Hello 算法"; int hashStr = str.hashCode(); // Hash value of string "Hello 算法" is -727081396 Object[] arr = { 12836, "小哈" }; int hashTup = Arrays.hashCode(arr); // Hash value of array [12836, 小哈] is 1151158 ListNode obj = new ListNode(0); int hashObj = obj.hashCode(); // Hash value of ListNode object utils.ListNode@7dc5e7b4 is 2110121908 ``` === "C#" ```csharp title="built_in_hash.cs" int num = 3; int hashNum = num.GetHashCode(); // Hash value of integer 3 is 3; bool bol = true; int hashBol = bol.GetHashCode(); // Hash value of boolean true is 1; double dec = 3.14159; int hashDec = dec.GetHashCode(); // Hash value of decimal 3.14159 is -1340954729; string str = "Hello 算法"; int hashStr = str.GetHashCode(); // Hash value of string "Hello 算法" is -586107568; object[] arr = [12836, "小哈"]; int hashTup = arr.GetHashCode(); // Hash value of array [12836, 小哈] is 42931033; ListNode obj = new(0); int hashObj = obj.GetHashCode(); // Hash value of ListNode object 0 is 39053774; ``` === "Go" ```go title="built_in_hash.go" // Go does not provide built-in hash code functions ``` === "Swift" ```swift title="built_in_hash.swift" let num = 3 let hashNum = num.hashValue // Hash value of integer 3 is 9047044699613009734 let bol = true let hashBol = bol.hashValue // Hash value of boolean true is -4431640247352757451 let dec = 3.14159 let hashDec = dec.hashValue // Hash value of decimal 3.14159 is -2465384235396674631 let str = "Hello 算法" let hashStr = str.hashValue // Hash value of string "Hello 算法" is -7850626797806988787 let arr = [AnyHashable(12836), AnyHashable("小哈")] let hashTup = arr.hashValue // Hash value of array [AnyHashable(12836), AnyHashable("小哈")] is -2308633508154532996 let obj = ListNode(x: 0) let hashObj = obj.hashValue // Hash value of ListNode object utils.ListNode is -2434780518035996159 ``` === "JS" ```javascript title="built_in_hash.js" // JavaScript does not provide built-in hash code functions ``` === "TS" ```typescript title="built_in_hash.ts" // TypeScript does not provide built-in hash code functions ``` === "Dart" ```dart title="built_in_hash.dart" int num = 3; int hashNum = num.hashCode; // Hash value of integer 3 is 34803 bool bol = true; int hashBol = bol.hashCode; // Hash value of boolean true is 1231 double dec = 3.14159; int hashDec = dec.hashCode; // Hash value of decimal 3.14159 is 2570631074981783 String str = "Hello 算法"; int hashStr = str.hashCode; // Hash value of string "Hello 算法" is 468167534 List arr = [12836, "小哈"]; int hashArr = arr.hashCode; // Hash value of array [12836, 小哈] is 976512528 ListNode obj = new ListNode(0); int hashObj = obj.hashCode; // Hash value of ListNode object Instance of 'ListNode' is 1033450432 ``` === "Rust" ```rust title="built_in_hash.rs" use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; let num = 3; let mut num_hasher = DefaultHasher::new(); num.hash(&mut num_hasher); let hash_num = num_hasher.finish(); // Hash value of integer 3 is 568126464209439262 let bol = true; let mut bol_hasher = DefaultHasher::new(); bol.hash(&mut bol_hasher); let hash_bol = bol_hasher.finish(); // Hash value of boolean true is 4952851536318644461 let dec: f32 = 3.14159; let mut dec_hasher = DefaultHasher::new(); dec.to_bits().hash(&mut dec_hasher); let hash_dec = dec_hasher.finish(); // Hash value of decimal 3.14159 is 2566941990314602357 let str = "Hello 算法"; let mut str_hasher = DefaultHasher::new(); str.hash(&mut str_hasher); let hash_str = str_hasher.finish(); // Hash value of string "Hello 算法" is 16092673739211250988 let arr = (&12836, &"小哈"); let mut tup_hasher = DefaultHasher::new(); arr.hash(&mut tup_hasher); let hash_tup = tup_hasher.finish(); // Hash value of tuple (12836, "小哈") is 1885128010422702749 let node = ListNode::new(42); let mut hasher = DefaultHasher::new(); node.borrow().val.hash(&mut hasher); let hash = hasher.finish(); // Hash value of ListNode object RefCell { value: ListNode { val: 42, next: None } } is 15387811073369036852 ``` === "C" ```c title="built_in_hash.c" // C does not provide built-in hash code functions ``` === "Kotlin" ```kotlin title="built_in_hash.kt" val num = 3 val hashNum = num.hashCode() // Hash value of integer 3 is 3 val bol = true val hashBol = bol.hashCode() // Hash value of boolean true is 1231 val dec = 3.14159 val hashDec = dec.hashCode() // Hash value of decimal 3.14159 is -1340954729 val str = "Hello 算法" val hashStr = str.hashCode() // Hash value of string "Hello 算法" is -727081396 val arr = arrayOf(12836, "小哈") val hashTup = arr.hashCode() // Hash value of array [12836, 小哈] is 189568618 val obj = ListNode(0) val hashObj = obj.hashCode() // Hash value of ListNode object utils.ListNode@1d81eb93 is 495053715 ``` === "Ruby" ```ruby title="built_in_hash.rb" num = 3 hash_num = num.hash # Hash value of integer 3 is -4385856518450339636 bol = true hash_bol = bol.hash # Hash value of boolean true is -1617938112149317027 dec = 3.14159 hash_dec = dec.hash # Hash value of decimal 3.14159 is -1479186995943067893 str = "Hello 算法" hash_str = str.hash # Hash value of string "Hello 算法" is -4075943250025831763 tup = [12836, '小哈'] hash_tup = tup.hash # Hash value of tuple (12836, '小哈') is 1999544809202288822 obj = ListNode.new(0) hash_obj = obj.hash # Hash value of ListNode object # is 4302940560806366381 ``` ??? pythontutor "Visualized Execution" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B0%203%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E5%B0%94%E9%87%8F%20True%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B0%203.14159%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E2%80%9CHello%20%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%BB%84%20%2812836,%20'%E5%B0%8F%E5%93%88'%29%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false In many programming languages, **only immutable objects can serve as the `key` in a hash table**. If we use a list (dynamic array) as a `key`, when the contents of the list change, its hash value also changes, and we would no longer be able to find the original `value` in the hash table. Although the member variables of a custom object (such as a linked list node) are mutable, it is hashable. **This is because the hash value of an object is usually generated based on its memory address**, and even if the contents of the object change, the memory address remains the same, so the hash value remains unchanged. You might have noticed that the hash values output in different consoles are different. **This is because the Python interpreter adds a random salt to the string hash function each time it starts up**. This approach effectively prevents HashDoS attacks and enhances the security of the hash algorithm. ================================================ FILE: en/docs/chapter_hashing/hash_collision.md ================================================ # Hash Collision The previous section mentioned that, **in most cases, the input space of a hash function is much larger than the output space**, so theoretically, hash collisions are inevitable. For example, if the input space is all integers and the output space is the array capacity size, then multiple integers will inevitably be mapped to the same bucket index. Hash collisions can lead to incorrect query results, severely impacting the usability of the hash table. To address this issue, whenever a hash collision occurs, we can perform hash table expansion until the collision disappears. This approach is simple, straightforward, and effective, but it is very inefficient because hash table expansion involves a large amount of data migration and hash value recalculation. To improve efficiency, we can adopt the following strategies: 1. Improve the hash table data structure so that **the hash table can function normally when hash collisions occur**. 2. Only expand when necessary, that is, only when hash collisions are severe. The main methods for improving the structure of hash tables include "separate chaining" and "open addressing". ## Separate Chaining In the original hash table, each bucket can store only one key-value pair. Separate chaining converts a single element into a linked list, treating key-value pairs as linked list nodes and storing all colliding key-value pairs in the same linked list. The figure below shows an example of a separate chaining hash table. ![Separate chaining hash table](hash_collision.assets/hash_table_chaining.png) The operations of a hash table implemented with separate chaining have changed as follows: - **Querying elements**: Input `key`, obtain the bucket index through the hash function, then access the head node of the linked list, then traverse the linked list and compare `key` to find the target key-value pair. - **Adding elements**: First access the linked list head node through the hash function, then append the node (key-value pair) to the linked list. - **Deleting elements**: Access the head of the linked list based on the result of the hash function, then traverse the linked list to find the target node and delete it. Separate chaining has the following limitations: - **Increased Space Usage**: The linked list contains node pointers, which consume more memory space than arrays. - **Reduced Query Efficiency**: This is because linear traversal of the linked list is required to find the corresponding element. The code below provides a simple implementation of a separate chaining hash table, with two things to note: - Lists (dynamic arrays) are used instead of linked lists to simplify the code. In this setup, the hash table (array) contains multiple buckets, each of which is a list. - This implementation includes a hash table expansion method. When the load factor exceeds $\frac{2}{3}$, we expand the hash table to $2$ times its original size. ```src [file]{hash_map_chaining}-[class]{hash_map_chaining}-[func]{} ``` It's worth noting that when the linked list is very long, the query efficiency $O(n)$ is poor. **In this case, the list can be converted to an "AVL tree" or "Red-Black tree"** to optimize the time complexity of the query operation to $O(\log n)$. ## Open Addressing Open addressing does not introduce additional data structures but instead handles hash collisions through "multiple probes". The probing methods mainly include linear probing, quadratic probing, and double hashing. Let's use linear probing as an example to introduce the mechanism of open addressing hash tables. ### Linear Probing Linear probing uses a fixed-step linear search for probing, and its operation method differs from ordinary hash tables. - **Inserting elements**: Calculate the bucket index using the hash function. If the bucket already contains an element, linearly traverse forward from the conflict position (usually with a step size of $1$) until an empty bucket is found, then insert the element. - **Searching for elements**: If a hash collision is encountered, use the same step size to linearly traverse forward until the corresponding element is found and return `value`; if an empty bucket is encountered, it means the target element is not in the hash table, so return `None`. The figure below shows the distribution of key-value pairs in an open addressing (linear probing) hash table. According to this hash function, keys with the same last two digits will be mapped to the same bucket. Through linear probing, they are stored sequentially in that bucket and the buckets below it. ![Distribution of key-value pairs in open addressing (linear probing) hash table](hash_collision.assets/hash_table_linear_probing.png) However, **linear probing is prone to create "clustering"**. Specifically, the longer the continuously occupied positions in the array, the greater the probability of hash collisions occurring in these continuous positions, further promoting clustering growth at that position, forming a vicious cycle, and ultimately leading to degraded efficiency of insertion, deletion, query, and update operations. It's important to note that **we cannot directly delete elements in an open addressing hash table**. Deleting an element creates an empty bucket `None` in the array. When searching for elements, if linear probing encounters this empty bucket, it will return, making the elements below this empty bucket inaccessible. The program may incorrectly assume these elements do not exist, as shown in the figure below. ![Query issues caused by deletion in open addressing](hash_collision.assets/hash_table_open_addressing_deletion.png) To solve this problem, we can adopt the lazy deletion mechanism: instead of directly removing elements from the hash table, **use a constant `TOMBSTONE` to mark the bucket**. In this mechanism, both `None` and `TOMBSTONE` represent empty buckets and can hold key-value pairs. However, when linear probing encounters `TOMBSTONE`, it should continue traversing since there may still be key-value pairs below it. However, **lazy deletion may accelerate the performance degradation of the hash table**. Every deletion operation produces a deletion mark, and as `TOMBSTONE` increases, the search time will also increase because linear probing may need to skip multiple `TOMBSTONE` to find the target element. To address this, consider recording the index of the first encountered `TOMBSTONE` during linear probing and swapping the searched target element with that `TOMBSTONE`. The benefit of doing this is that each time an element is queried or added, the element will be moved to a bucket closer to its ideal position (the starting point of probing), thereby optimizing query efficiency. The code below implements an open addressing (linear probing) hash table with lazy deletion. To make better use of the hash table space, we treat the hash table as a "circular array". When going beyond the end of the array, we return to the beginning and continue traversing. ```src [file]{hash_map_open_addressing}-[class]{hash_map_open_addressing}-[func]{} ``` ### Quadratic Probing Quadratic probing is similar to linear probing and is one of the common strategies for open addressing. When a collision occurs, quadratic probing does not simply skip a fixed number of steps but skips a number of steps equal to the "square of the number of probes", i.e., $1, 4, 9, \dots$ steps. Quadratic probing has the following advantages: - Quadratic probing attempts to alleviate the clustering effect of linear probing by skipping distances equal to the square of the probe count. - Quadratic probing skips larger distances to find empty positions, which helps to distribute data more evenly. However, quadratic probing is not perfect: - Clustering still exists, i.e., some positions are more likely to be occupied than others. - Due to the growth of squares, quadratic probing may not probe the entire hash table, meaning that even if there are empty buckets in the hash table, quadratic probing may not be able to access them. ### Double Hashing As the name suggests, the double hashing method uses multiple hash functions $f_1(x)$, $f_2(x)$, $f_3(x)$, $\dots$ for probing. - **Inserting elements**: If hash function $f_1(x)$ encounters a conflict, try $f_2(x)$, and so on, until an empty position is found and the element is inserted. - **Searching for elements**: Search in the same order of hash functions until the target element is found and return it; if an empty position is encountered or all hash functions have been tried, it indicates the element is not in the hash table, then return `None`. Compared to linear probing, the double hashing method is less prone to clustering, but multiple hash functions introduce additional computational overhead. !!! tip Please note that open addressing (linear probing, quadratic probing, and double hashing) hash tables all have the problem of "cannot directly delete elements". ## Choice of Programming Languages Different programming languages adopt different hash table implementation strategies. Here are a few examples: - Python uses open addressing. The `dict` dictionary uses pseudo-random numbers for probing. - Java uses separate chaining. Since JDK 1.8, when the array length in `HashMap` reaches 64 and the length of a linked list reaches 8, the linked list is converted to a red-black tree to improve search performance. - Go uses separate chaining. Go stipulates that each bucket can store up to 8 key-value pairs, and if the capacity is exceeded, an overflow bucket is linked; when there are too many overflow buckets, a special equal-capacity expansion operation is performed to ensure performance. ================================================ FILE: en/docs/chapter_hashing/hash_map.md ================================================ # Hash Table A hash table, also known as a hash map, establishes a mapping between keys `key` and values `value`, enabling efficient element retrieval. Specifically, when we input a key `key` into a hash table, we can retrieve the corresponding value `value` in $O(1)$ time. As shown in the figure below, given $n$ students, each with two pieces of data: "name" and "student ID". If we want to implement a query function that "inputs a student ID and returns the corresponding name", we can use the hash table shown below. ![Abstract representation of a hash table](hash_map.assets/hash_table_lookup.png) In addition to hash tables, arrays and linked lists can also implement query functionality. Their efficiency comparison is shown in the following table. - **Adding elements**: Simply add elements to the end of the array (linked list), using $O(1)$ time. - **Querying elements**: Since the array (linked list) is unordered, all elements need to be traversed, using $O(n)$ time. - **Deleting elements**: The element must first be located, then deleted from the array (linked list), using $O(n)$ time.

Table   Comparison of element query efficiency

| | Array | Linked List | Hash Table | | --------------- | ------ | ----------- | ---------- | | Find element | $O(n)$ | $O(n)$ | $O(1)$ | | Add element | $O(1)$ | $O(1)$ | $O(1)$ | | Delete element | $O(n)$ | $O(n)$ | $O(1)$ | As observed, **the time complexity for insertion, deletion, search, and modification operations in a hash table is $O(1)$**, which is very efficient. ## Common Hash Table Operations Common operations on hash tables include: initialization, query operations, adding key-value pairs, and deleting key-value pairs. Example code is as follows: === "Python" ```python title="hash_map.py" # Initialize hash table hmap: dict = {} # Add operation # Add key-value pair (key, value) to hash table hmap[12836] = "XiaoHa" hmap[15937] = "XiaoLuo" hmap[16750] = "XiaoSuan" hmap[13276] = "XiaoFa" hmap[10583] = "XiaoYa" # Query operation # Input key into hash table to get value name: str = hmap[15937] # Delete operation # Delete key-value pair (key, value) from hash table hmap.pop(10583) ``` === "C++" ```cpp title="hash_map.cpp" /* Initialize hash table */ unordered_map map; /* Add operation */ // Add key-value pair (key, value) to hash table map[12836] = "XiaoHa"; map[15937] = "XiaoLuo"; map[16750] = "XiaoSuan"; map[13276] = "XiaoFa"; map[10583] = "XiaoYa"; /* Query operation */ // Input key into hash table to get value string name = map[15937]; /* Delete operation */ // Delete key-value pair (key, value) from hash table map.erase(10583); ``` === "Java" ```java title="hash_map.java" /* Initialize hash table */ Map map = new HashMap<>(); /* Add operation */ // Add key-value pair (key, value) to hash table map.put(12836, "XiaoHa"); map.put(15937, "XiaoLuo"); map.put(16750, "XiaoSuan"); map.put(13276, "XiaoFa"); map.put(10583, "XiaoYa"); /* Query operation */ // Input key into hash table to get value String name = map.get(15937); /* Delete operation */ // Delete key-value pair (key, value) from hash table map.remove(10583); ``` === "C#" ```csharp title="hash_map.cs" /* Initialize hash table */ Dictionary map = new() { /* Add operation */ // Add key-value pair (key, value) to hash table { 12836, "XiaoHa" }, { 15937, "XiaoLuo" }, { 16750, "XiaoSuan" }, { 13276, "XiaoFa" }, { 10583, "XiaoYa" } }; /* Query operation */ // Input key into hash table to get value string name = map[15937]; /* Delete operation */ // Delete key-value pair (key, value) from hash table map.Remove(10583); ``` === "Go" ```go title="hash_map_test.go" /* Initialize hash table */ hmap := make(map[int]string) /* Add operation */ // Add key-value pair (key, value) to hash table hmap[12836] = "XiaoHa" hmap[15937] = "XiaoLuo" hmap[16750] = "XiaoSuan" hmap[13276] = "XiaoFa" hmap[10583] = "XiaoYa" /* Query operation */ // Input key into hash table to get value name := hmap[15937] /* Delete operation */ // Delete key-value pair (key, value) from hash table delete(hmap, 10583) ``` === "Swift" ```swift title="hash_map.swift" /* Initialize hash table */ var map: [Int: String] = [:] /* Add operation */ // Add key-value pair (key, value) to hash table map[12836] = "XiaoHa" map[15937] = "XiaoLuo" map[16750] = "XiaoSuan" map[13276] = "XiaoFa" map[10583] = "XiaoYa" /* Query operation */ // Input key into hash table to get value let name = map[15937]! /* Delete operation */ // Delete key-value pair (key, value) from hash table map.removeValue(forKey: 10583) ``` === "JS" ```javascript title="hash_map.js" /* Initialize hash table */ const map = new Map(); /* Add operation */ // Add key-value pair (key, value) to hash table map.set(12836, 'XiaoHa'); map.set(15937, 'XiaoLuo'); map.set(16750, 'XiaoSuan'); map.set(13276, 'XiaoFa'); map.set(10583, 'XiaoYa'); /* Query operation */ // Input key into hash table to get value let name = map.get(15937); /* Delete operation */ // Delete key-value pair (key, value) from hash table map.delete(10583); ``` === "TS" ```typescript title="hash_map.ts" /* Initialize hash table */ const map = new Map(); /* Add operation */ // Add key-value pair (key, value) to hash table map.set(12836, 'XiaoHa'); map.set(15937, 'XiaoLuo'); map.set(16750, 'XiaoSuan'); map.set(13276, 'XiaoFa'); map.set(10583, 'XiaoYa'); console.info('\nAfter adding, hash table is\nKey -> Value'); console.info(map); /* Query operation */ // Input key into hash table to get value let name = map.get(15937); console.info('\nInput student ID 15937, queried name ' + name); /* Delete operation */ // Delete key-value pair (key, value) from hash table map.delete(10583); console.info('\nAfter deleting 10583, hash table is\nKey -> Value'); console.info(map); ``` === "Dart" ```dart title="hash_map.dart" /* Initialize hash table */ Map map = {}; /* Add operation */ // Add key-value pair (key, value) to hash table map[12836] = "XiaoHa"; map[15937] = "XiaoLuo"; map[16750] = "XiaoSuan"; map[13276] = "XiaoFa"; map[10583] = "XiaoYa"; /* Query operation */ // Input key into hash table to get value String name = map[15937]; /* Delete operation */ // Delete key-value pair (key, value) from hash table map.remove(10583); ``` === "Rust" ```rust title="hash_map.rs" use std::collections::HashMap; /* Initialize hash table */ let mut map: HashMap = HashMap::new(); /* Add operation */ // Add key-value pair (key, value) to hash table map.insert(12836, "XiaoHa".to_string()); map.insert(15937, "XiaoLuo".to_string()); map.insert(16750, "XiaoSuan".to_string()); map.insert(13279, "XiaoFa".to_string()); map.insert(10583, "XiaoYa".to_string()); /* Query operation */ // Input key into hash table to get value let _name: Option<&String> = map.get(&15937); /* Delete operation */ // Delete key-value pair (key, value) from hash table let _removed_value: Option = map.remove(&10583); ``` === "C" ```c title="hash_map.c" // C does not provide a built-in hash table ``` === "Kotlin" ```kotlin title="hash_map.kt" /* Initialize hash table */ val map = HashMap() /* Add operation */ // Add key-value pair (key, value) to hash table map[12836] = "XiaoHa" map[15937] = "XiaoLuo" map[16750] = "XiaoSuan" map[13276] = "XiaoFa" map[10583] = "XiaoYa" /* Query operation */ // Input key into hash table to get value val name = map[15937] /* Delete operation */ // Delete key-value pair (key, value) from hash table map.remove(10583) ``` === "Ruby" ```ruby title="hash_map.rb" # Initialize hash table hmap = {} # Add operation # Add key-value pair (key, value) to hash table hmap[12836] = "XiaoHa" hmap[15937] = "XiaoLuo" hmap[16750] = "XiaoSuan" hmap[13276] = "XiaoFa" hmap[10583] = "XiaoYa" # Query operation # Input key into hash table to get value name = hmap[15937] # Delete operation # Delete key-value pair (key, value) from hash table hmap.delete(10583) ``` ??? pythontutor "Visualized Execution" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E8%BE%93%E5%85%A5%E9%94%AE%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false There are three common ways to traverse a hash table: traversing key-value pairs, traversing keys, and traversing values. Example code is as follows: === "Python" ```python title="hash_map.py" # Traverse hash table # Traverse key-value pairs key->value for key, value in hmap.items(): print(key, "->", value) # Traverse keys only for key in hmap.keys(): print(key) # Traverse values only for value in hmap.values(): print(value) ``` === "C++" ```cpp title="hash_map.cpp" /* Traverse hash table */ // Traverse key-value pairs key->value for (auto kv: map) { cout << kv.first << " -> " << kv.second << endl; } // Traverse using iterator key->value for (auto iter = map.begin(); iter != map.end(); iter++) { cout << iter->first << "->" << iter->second << endl; } ``` === "Java" ```java title="hash_map.java" /* Traverse hash table */ // Traverse key-value pairs key->value for (Map.Entry kv: map.entrySet()) { System.out.println(kv.getKey() + " -> " + kv.getValue()); } // Traverse keys only for (int key: map.keySet()) { System.out.println(key); } // Traverse values only for (String val: map.values()) { System.out.println(val); } ``` === "C#" ```csharp title="hash_map.cs" /* Traverse hash table */ // Traverse key-value pairs Key->Value foreach (var kv in map) { Console.WriteLine(kv.Key + " -> " + kv.Value); } // Traverse keys only foreach (int key in map.Keys) { Console.WriteLine(key); } // Traverse values only foreach (string val in map.Values) { Console.WriteLine(val); } ``` === "Go" ```go title="hash_map_test.go" /* Traverse hash table */ // Traverse key-value pairs key->value for key, value := range hmap { fmt.Println(key, "->", value) } // Traverse keys only for key := range hmap { fmt.Println(key) } // Traverse values only for _, value := range hmap { fmt.Println(value) } ``` === "Swift" ```swift title="hash_map.swift" /* Traverse hash table */ // Traverse key-value pairs Key->Value for (key, value) in map { print("\(key) -> \(value)") } // Traverse keys only for key in map.keys { print(key) } // Traverse values only for value in map.values { print(value) } ``` === "JS" ```javascript title="hash_map.js" /* Traverse hash table */ console.info('\nTraverse key-value pairs Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\nTraverse keys only Key'); for (const k of map.keys()) { console.info(k); } console.info('\nTraverse values only Value'); for (const v of map.values()) { console.info(v); } ``` === "TS" ```typescript title="hash_map.ts" /* Traverse hash table */ console.info('\nTraverse key-value pairs Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\nTraverse keys only Key'); for (const k of map.keys()) { console.info(k); } console.info('\nTraverse values only Value'); for (const v of map.values()) { console.info(v); } ``` === "Dart" ```dart title="hash_map.dart" /* Traverse hash table */ // Traverse key-value pairs Key->Value map.forEach((key, value) { print('$key -> $value'); }); // Traverse keys only map.keys.forEach((key) { print(key); }); // Traverse values only map.values.forEach((value) { print(value); }); ``` === "Rust" ```rust title="hash_map.rs" /* Traverse hash table */ // Traverse key-value pairs Key->Value for (key, value) in &map { println!("{key} -> {value}"); } // Traverse keys only for key in map.keys() { println!("{key}"); } // Traverse values only for value in map.values() { println!("{value}"); } ``` === "C" ```c title="hash_map.c" // C does not provide a built-in hash table ``` === "Kotlin" ```kotlin title="hash_map.kt" /* Traverse hash table */ // Traverse key-value pairs key->value for ((key, value) in map) { println("$key -> $value") } // Traverse keys only for (key in map.keys) { println(key) } // Traverse values only for (_val in map.values) { println(_val) } ``` === "Ruby" ```ruby title="hash_map.rb" # Traverse hash table # Traverse key-value pairs key->value hmap.entries.each { |key, value| puts "#{key} -> #{value}" } # Traverse keys only hmap.keys.each { |key| puts key } # Traverse values only hmap.values.each { |val| puts val } ``` ??? pythontutor "Visualized Execution" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20key-%3Evalue%0A%20%20%20%20for%20key,%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key,%20%22-%3E%22,%20value%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E9%94%AE%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E5%80%BC%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## Simple Hash Table Implementation Let's first consider the simplest case: **implementing a hash table using only an array**. In a hash table, each empty position in the array is called a bucket, and each bucket can store a key-value pair. Therefore, the query operation is to find the bucket corresponding to `key` and retrieve the `value` from the bucket. So how do we locate the corresponding bucket based on `key`? This is achieved through a hash function. The role of the hash function is to map a larger input space to a smaller output space. In a hash table, the input space is all `key`s, and the output space is all buckets (array indices). In other words, given a `key`, **we can use the hash function to obtain the storage location of the key-value pair corresponding to that `key` in the array**. When inputting a `key`, the hash function's calculation process consists of the following two steps: 1. Calculate the hash value through a hash algorithm `hash()`. 2. Take the modulo of the hash value by the number of buckets (array length) `capacity` to obtain the bucket (array index) `index` corresponding to that `key`. ```shell index = hash(key) % capacity ``` Subsequently, we can use `index` to access the corresponding bucket in the hash table and retrieve the `value`. Assuming the array length is `capacity = 100` and the hash algorithm is `hash(key) = key`, the hash function becomes `key % 100`. The figure below shows the working principle of the hash function using `key` as student ID and `value` as name. ![Working principle of hash function](hash_map.assets/hash_function.png) The following code implements a simple hash table. Here, we encapsulate `key` and `value` into a class `Pair` to represent a key-value pair. ```src [file]{array_hash_map}-[class]{array_hash_map}-[func]{} ``` ## Hash Collision and Resizing Fundamentally, the role of a hash function is to map the input space consisting of all `key`s to the output space consisting of all array indices, and the input space is often much larger than the output space. Therefore, **theoretically there must be cases where "multiple inputs correspond to the same output"**. For the hash function in the above example, when the input `key`s have the same last two digits, the hash function produces the same output. For example, when querying two students with IDs 12836 and 20336, we get: ```shell 12836 % 100 = 36 20336 % 100 = 36 ``` As shown in the figure below, two student IDs point to the same name, which is obviously incorrect. We call this situation where multiple inputs correspond to the same output a hash collision. ![Hash collision example](hash_map.assets/hash_collision.png) It's easy to see that the larger the hash table capacity $n$, the lower the probability that multiple `key`s will be assigned to the same bucket, and the fewer collisions. Therefore, **we can reduce hash collisions by expanding the hash table**. As shown in the figure below, before expansion, the key-value pairs `(136, A)` and `(236, D)` collided, but after expansion, the collision disappears. ![Hash table resizing](hash_map.assets/hash_table_reshash.png) Similar to array expansion, hash table expansion requires migrating all key-value pairs from the original hash table to the new hash table, which is very time-consuming. Moreover, since the hash table capacity `capacity` changes, we need to recalculate the storage locations of all key-value pairs through the hash function, further increasing the computational overhead of the expansion process. For this reason, programming languages typically reserve a sufficiently large hash table capacity to prevent frequent expansion. The load factor is an important concept for hash tables. It is defined as the number of elements in the hash table divided by the number of buckets, and is used to measure the severity of hash collisions. **It is also commonly used as a trigger condition for hash table expansion**. For example, in Java, when the load factor exceeds $0.75$, the system will expand the hash table to $2$ times its original size. ================================================ FILE: en/docs/chapter_hashing/index.md ================================================ # Hashing ![Hashing](../assets/covers/chapter_hashing.jpg) !!! abstract In the world of computing, a hash table is like a clever librarian. They know how to calculate call numbers, enabling them to quickly locate the target book. ================================================ FILE: en/docs/chapter_hashing/summary.md ================================================ # Summary ### Key Review - Given an input `key`, a hash table can retrieve the corresponding `value` in $O(1)$ time, which is highly efficient. - Common hash table operations include querying, adding key-value pairs, deleting key-value pairs, and traversing the hash table. - The hash function maps a `key` to an array index, allowing access to the corresponding bucket and retrieval of the `value`. - Two different keys may end up with the same array index after hashing, leading to erroneous query results. This phenomenon is known as hash collision. - The larger the capacity of the hash table, the lower the probability of hash collisions. Therefore, hash table expansion can mitigate hash collisions. Similar to array expansion, hash table expansion is costly. - The load factor, defined as the number of elements divided by the number of buckets, reflects the severity of hash collisions and is often used as a condition to trigger hash table expansion. - Separate chaining addresses hash collisions by converting each element into a linked list, storing all colliding elements in the same linked list. However, excessively long linked lists can reduce query efficiency, which can be improved by converting the linked lists into red-black trees. - Open addressing handles hash collisions through multiple probing. Linear probing uses a fixed step size but cannot delete elements and is prone to clustering. Double hashing uses multiple hash functions for probing, which reduces clustering compared to linear probing but increases computational overhead. - Different programming languages adopt various hash table implementations. For example, Java's `HashMap` uses separate chaining, while Python's `dict` employs open addressing. - In hash tables, we desire hash algorithms with determinism, high efficiency, and uniform distribution. In cryptography, hash algorithms should also possess collision resistance and the avalanche effect. - Hash algorithms typically use large prime numbers as moduli to maximize the uniform distribution of hash values and reduce hash collisions. - Common hash algorithms include MD5, SHA-1, SHA-2, and SHA-3. MD5 is often used for file integrity checks, while SHA-2 is commonly used in secure applications and protocols. - Programming languages usually provide built-in hash algorithms for data types to calculate bucket indices in hash tables. Generally, only immutable objects are hashable. ### Q & A **Q**: When does the time complexity of a hash table degrade to $O(n)$? The time complexity of a hash table can degrade to $O(n)$ when hash collisions are severe. When the hash function is well-designed, the capacity is set appropriately, and collisions are evenly distributed, the time complexity is $O(1)$. We usually consider the time complexity to be $O(1)$ when using built-in hash tables in programming languages. **Q**: Why not use the hash function $f(x) = x$? This would eliminate collisions. Under the hash function $f(x) = x$, each element corresponds to a unique bucket index, which is equivalent to an array. However, the input space is usually much larger than the output space (array length), so the last step of a hash function is often to take the modulo of the array length. In other words, the goal of a hash table is to map a larger state space to a smaller one while providing $O(1)$ query efficiency. **Q**: Why can hash tables be more efficient than arrays, linked lists, or binary trees, even though hash tables are implemented using these structures? Firstly, hash tables have higher time efficiency but lower space efficiency. A significant portion of memory in hash tables remains unused. Secondly, hash tables are only more time-efficient in specific use cases. If a feature can be implemented with the same time complexity using an array or a linked list, it's usually faster than using a hash table. This is because the computation of the hash function incurs overhead, making the constant factor in the time complexity larger. Lastly, the time complexity of hash tables can degrade. For example, in separate chaining, we perform search operations in a linked list or red-black tree, which still risks degrading to $O(n)$ time. **Q**: Does double hashing also have the flaw of not being able to delete elements directly? Can space marked as deleted be reused? Double hashing is a form of open addressing, and all open addressing methods have the drawback of not being able to delete elements directly; they require marking elements as deleted. Marked spaces can be reused. When inserting new elements into the hash table, and the hash function points to a position marked as deleted, that position can be used by the new element. This maintains the probing sequence of the hash table while ensuring efficient use of space. **Q**: Why do hash collisions occur during the search process in linear probing? During the search process, the hash function points to the corresponding bucket and key-value pair. If the `key` doesn't match, it indicates a hash collision. Therefore, linear probing will search downward at a predetermined step size until the correct key-value pair is found or the search fails. **Q**: Why can expanding a hash table alleviate hash collisions? The last step of a hash function often involves taking the modulo of the array length $n$, to keep the output within the array index range. When expanding, the array length $n$ changes, and the indices corresponding to the keys may also change. Keys that were previously mapped to the same bucket might be distributed across multiple buckets after expansion, thereby mitigating hash collisions. ================================================ FILE: en/docs/chapter_heap/build_heap.md ================================================ # Heap Construction Operation In some cases, we want to build a heap using all elements of a list, and this process is called "heap construction operation." ## Implementing with Element Insertion We first create an empty heap, then iterate through the list, performing the "element insertion operation" on each element in sequence. This means adding the element to the bottom of the heap and then performing "bottom-to-top" heapify on that element. Each time an element is inserted into the heap, the heap's length increases by one. Since nodes are added to the binary tree sequentially from top to bottom, the heap is constructed "from top to bottom." Given $n$ elements, each element's insertion operation takes $O(\log{n})$ time, so the time complexity of this heap construction method is $O(n \log n)$. ## Implementing Through Heapify Traversal In fact, we can implement a more efficient heap construction method in two steps. 1. Add all elements of the list as-is to the heap, at which point the heap property is not yet satisfied. 2. Traverse the heap in reverse order (reverse of level-order traversal), performing "top-to-bottom heapify" on each non-leaf node in sequence. **After heapifying a node, the subtree rooted at that node becomes a valid sub-heap**. Since we traverse in reverse order, the heap is constructed "from bottom to top." The reason for choosing reverse order traversal is that it ensures the subtree below the current node is already a valid sub-heap, making the heapification of the current node effective. It's worth noting that **since leaf nodes have no children, they are naturally valid sub-heaps and do not require heapification**. As shown in the code below, the last non-leaf node is the parent of the last node; we start from it and traverse in reverse order to perform heapification: ```src [file]{my_heap}-[class]{max_heap}-[func]{__init__} ``` ## Complexity Analysis Next, let's attempt to derive the time complexity of this second heap construction method. - Assuming the complete binary tree has $n$ nodes, then the number of leaf nodes is $(n + 1) / 2$, where $/$ is floor division. Therefore, the number of nodes that need heapification is $(n - 1) / 2$. - In the top-to-bottom heapify process, each node is heapified at most to the leaf nodes, so the maximum number of iterations is the binary tree height $\log n$. Multiplying these two together, we get a time complexity of $O(n \log n)$ for the heap construction process. **However, this estimate is not accurate because it doesn't account for the property that binary trees have far more nodes at lower levels than at upper levels**. Let's perform a more accurate calculation. To reduce calculation difficulty, assume a "perfect binary tree" with $n$ nodes and height $h$; this assumption does not affect the correctness of the result. ![Node count at each level of a perfect binary tree](build_heap.assets/heapify_operations_count.png) As shown in the figure above, the maximum number of iterations for a node's "top-to-bottom heapify" equals the distance from that node to the leaf nodes, which is precisely the "node height." Therefore, we can sum the "number of nodes $\times$ node height" at each level to **obtain the total number of heapify iterations for all nodes**. $$ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1 $$ To simplify the above expression, we need to use sequence knowledge from high school. First, multiply $T(h)$ by $2$ to get: $$ \begin{aligned} T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline 2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^{h}\times1 \newline \end{aligned} $$ Using the method of differences, subtract the first equation $T(h)$ from the second equation $2 T(h)$ to get: $$ 2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h $$ Observing the above expression, we find that $T(h)$ is a geometric series, which can be calculated directly using the sum formula, yielding a time complexity of: $$ \begin{aligned} T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline & = 2^{h+1} - h - 2 \newline & = O(2^h) \end{aligned} $$ Furthermore, a perfect binary tree with height $h$ has $n = 2^{h+1} - 1$ nodes, so the complexity is $O(2^h) = O(n)$. This derivation shows that **the time complexity of building a heap from an input list is $O(n)$, which is highly efficient**. ================================================ FILE: en/docs/chapter_heap/heap.md ================================================ # Heap A heap is a complete binary tree that satisfies specific conditions and can be mainly categorized into two types, as shown in the figure below. - min heap: The value of any node $\leq$ the values of its child nodes. - max heap: The value of any node $\geq$ the values of its child nodes. ![Min heap and max heap](heap.assets/min_heap_and_max_heap.png) As a special case of a complete binary tree, heaps have the following characteristics. - The bottom layer nodes are filled from left to right, and nodes in other layers are fully filled. - We call the root node of the binary tree the "heap top" and the bottom-rightmost node the "heap bottom." - For max heaps (min heaps), the value of the heap top element (root node) is the largest (smallest). ## Common Heap Operations It should be noted that many programming languages provide a priority queue, which is an abstract data structure defined as a queue with priority sorting. In fact, **heaps are typically used to implement priority queues, with max heaps corresponding to priority queues where elements are dequeued in descending order**. From a usage perspective, we can regard "priority queue" and "heap" as equivalent data structures. Therefore, this book does not make a special distinction between the two and uniformly refers to them as "heap." Common heap operations are shown in the table below, and method names need to be determined based on the programming language.

Table   Efficiency of Heap Operations

| Method name | Description | Time complexity | | ----------- | ----------------------------------------------------------------- | --------------- | | `push()` | Insert an element into the heap | $O(\log n)$ | | `pop()` | Remove the heap top element | $O(\log n)$ | | `peek()` | Access the heap top element (max/min value for max/min heap) | $O(1)$ | | `size()` | Get the number of elements in the heap | $O(1)$ | | `isEmpty()` | Check if the heap is empty | $O(1)$ | In practical applications, we can directly use the heap class (or priority queue class) provided by programming languages. Similar to "ascending order" and "descending order" in sorting algorithms, we can implement conversion between "min heap" and "max heap" by setting a `flag` or modifying the `Comparator`. The code is as follows: === "Python" ```python title="heap.py" # Initialize a min heap min_heap, flag = [], 1 # Initialize a max heap max_heap, flag = [], -1 # Python's heapq module implements a min heap by default # Consider negating elements before pushing them to the heap, which inverts the size relationship and thus implements a max heap # In this example, flag = 1 corresponds to a min heap, flag = -1 corresponds to a max heap # Push elements into the heap heapq.heappush(max_heap, flag * 1) heapq.heappush(max_heap, flag * 3) heapq.heappush(max_heap, flag * 2) heapq.heappush(max_heap, flag * 5) heapq.heappush(max_heap, flag * 4) # Get the heap top element peek: int = flag * max_heap[0] # 5 # Remove the heap top element # The removed elements will form a descending sequence val = flag * heapq.heappop(max_heap) # 5 val = flag * heapq.heappop(max_heap) # 4 val = flag * heapq.heappop(max_heap) # 3 val = flag * heapq.heappop(max_heap) # 2 val = flag * heapq.heappop(max_heap) # 1 # Get the heap size size: int = len(max_heap) # Check if the heap is empty is_empty: bool = not max_heap # Build a heap from an input list min_heap: list[int] = [1, 3, 2, 5, 4] heapq.heapify(min_heap) ``` === "C++" ```cpp title="heap.cpp" /* Initialize a heap */ // Initialize a min heap priority_queue, greater> minHeap; // Initialize a max heap priority_queue, less> maxHeap; /* Push elements into the heap */ maxHeap.push(1); maxHeap.push(3); maxHeap.push(2); maxHeap.push(5); maxHeap.push(4); /* Get the heap top element */ int peek = maxHeap.top(); // 5 /* Remove the heap top element */ // The removed elements will form a descending sequence maxHeap.pop(); // 5 maxHeap.pop(); // 4 maxHeap.pop(); // 3 maxHeap.pop(); // 2 maxHeap.pop(); // 1 /* Get the heap size */ int size = maxHeap.size(); /* Check if the heap is empty */ bool isEmpty = maxHeap.empty(); /* Build a heap from an input list */ vector input{1, 3, 2, 5, 4}; priority_queue, greater> minHeap(input.begin(), input.end()); ``` === "Java" ```java title="heap.java" /* Initialize a heap */ // Initialize a min heap Queue minHeap = new PriorityQueue<>(); // Initialize a max heap (use lambda expression to modify Comparator) Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); /* Push elements into the heap */ maxHeap.offer(1); maxHeap.offer(3); maxHeap.offer(2); maxHeap.offer(5); maxHeap.offer(4); /* Get the heap top element */ int peek = maxHeap.peek(); // 5 /* Remove the heap top element */ // The removed elements will form a descending sequence peek = maxHeap.poll(); // 5 peek = maxHeap.poll(); // 4 peek = maxHeap.poll(); // 3 peek = maxHeap.poll(); // 2 peek = maxHeap.poll(); // 1 /* Get the heap size */ int size = maxHeap.size(); /* Check if the heap is empty */ boolean isEmpty = maxHeap.isEmpty(); /* Build a heap from an input list */ minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); ``` === "C#" ```csharp title="heap.cs" /* Initialize a heap */ // Initialize a min heap PriorityQueue minHeap = new(); // Initialize a max heap (use lambda expression to modify Comparer) PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); /* Push elements into the heap */ maxHeap.Enqueue(1, 1); maxHeap.Enqueue(3, 3); maxHeap.Enqueue(2, 2); maxHeap.Enqueue(5, 5); maxHeap.Enqueue(4, 4); /* Get the heap top element */ int peek = maxHeap.Peek();//5 /* Remove the heap top element */ // The removed elements will form a descending sequence peek = maxHeap.Dequeue(); // 5 peek = maxHeap.Dequeue(); // 4 peek = maxHeap.Dequeue(); // 3 peek = maxHeap.Dequeue(); // 2 peek = maxHeap.Dequeue(); // 1 /* Get the heap size */ int size = maxHeap.Count; /* Check if the heap is empty */ bool isEmpty = maxHeap.Count == 0; /* Build a heap from an input list */ minHeap = new PriorityQueue([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]); ``` === "Go" ```go title="heap.go" // In Go, we can construct a max heap of integers by implementing heap.Interface // Implementing heap.Interface also requires implementing sort.Interface type intHeap []any // Push implements the heap.Interface method for pushing an element into the heap func (h *intHeap) Push(x any) { // Push and Pop use pointer receiver as parameters // because they not only adjust the slice contents but also modify the slice length *h = append(*h, x.(int)) } // Pop implements the heap.Interface method for popping the heap top element func (h *intHeap) Pop() any { // The element to be removed is stored at the end last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Len is a sort.Interface method func (h *intHeap) Len() int { return len(*h) } // Less is a sort.Interface method func (h *intHeap) Less(i, j int) bool { // To implement a min heap, change this to a less-than sign return (*h)[i].(int) > (*h)[j].(int) } // Swap is a sort.Interface method func (h *intHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Top gets the heap top element func (h *intHeap) Top() any { return (*h)[0] } /* Driver Code */ func TestHeap(t *testing.T) { /* Initialize a heap */ // Initialize a max heap maxHeap := &intHeap{} heap.Init(maxHeap) /* Push elements into the heap */ // Call heap.Interface methods to add elements heap.Push(maxHeap, 1) heap.Push(maxHeap, 3) heap.Push(maxHeap, 2) heap.Push(maxHeap, 4) heap.Push(maxHeap, 5) /* Get the heap top element */ top := maxHeap.Top() fmt.Printf("Heap top element is %d\n", top) /* Remove the heap top element */ // Call heap.Interface methods to remove elements heap.Pop(maxHeap) // 5 heap.Pop(maxHeap) // 4 heap.Pop(maxHeap) // 3 heap.Pop(maxHeap) // 2 heap.Pop(maxHeap) // 1 /* Get the heap size */ size := len(*maxHeap) fmt.Printf("Number of heap elements is %d\n", size) /* Check if the heap is empty */ isEmpty := len(*maxHeap) == 0 fmt.Printf("Is the heap empty? %t\n", isEmpty) } ``` === "Swift" ```swift title="heap.swift" /* Initialize a heap */ // Swift's Heap type supports both max heaps and min heaps, and requires importing swift-collections var heap = Heap() /* Push elements into the heap */ heap.insert(1) heap.insert(3) heap.insert(2) heap.insert(5) heap.insert(4) /* Get the heap top element */ var peek = heap.max()! /* Remove the heap top element */ peek = heap.removeMax() // 5 peek = heap.removeMax() // 4 peek = heap.removeMax() // 3 peek = heap.removeMax() // 2 peek = heap.removeMax() // 1 /* Get the heap size */ let size = heap.count /* Check if the heap is empty */ let isEmpty = heap.isEmpty /* Build a heap from an input list */ let heap2 = Heap([1, 3, 2, 5, 4]) ``` === "JS" ```javascript title="heap.js" // JavaScript does not provide a built-in Heap class ``` === "TS" ```typescript title="heap.ts" // TypeScript does not provide a built-in Heap class ``` === "Dart" ```dart title="heap.dart" // Dart does not provide a built-in Heap class ``` === "Rust" ```rust title="heap.rs" use std::collections::BinaryHeap; use std::cmp::Reverse; /* Initialize a heap */ // Initialize a min heap let mut min_heap = BinaryHeap::>::new(); // Initialize a max heap let mut max_heap = BinaryHeap::new(); /* Push elements into the heap */ max_heap.push(1); max_heap.push(3); max_heap.push(2); max_heap.push(5); max_heap.push(4); /* Get the heap top element */ let peek = max_heap.peek().unwrap(); // 5 /* Remove the heap top element */ // The removed elements will form a descending sequence let peek = max_heap.pop().unwrap(); // 5 let peek = max_heap.pop().unwrap(); // 4 let peek = max_heap.pop().unwrap(); // 3 let peek = max_heap.pop().unwrap(); // 2 let peek = max_heap.pop().unwrap(); // 1 /* Get the heap size */ let size = max_heap.len(); /* Check if the heap is empty */ let is_empty = max_heap.is_empty(); /* Build a heap from an input list */ let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]); ``` === "C" ```c title="heap.c" // C does not provide a built-in Heap class ``` === "Kotlin" ```kotlin title="heap.kt" /* Initialize a heap */ // Initialize a min heap var minHeap = PriorityQueue() // Initialize a max heap (use lambda expression to modify Comparator) val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } /* Push elements into the heap */ maxHeap.offer(1) maxHeap.offer(3) maxHeap.offer(2) maxHeap.offer(5) maxHeap.offer(4) /* Get the heap top element */ var peek = maxHeap.peek() // 5 /* Remove the heap top element */ // The removed elements will form a descending sequence peek = maxHeap.poll() // 5 peek = maxHeap.poll() // 4 peek = maxHeap.poll() // 3 peek = maxHeap.poll() // 2 peek = maxHeap.poll() // 1 /* Get the heap size */ val size = maxHeap.size /* Check if the heap is empty */ val isEmpty = maxHeap.isEmpty() /* Build a heap from an input list */ minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) ``` === "Ruby" ```ruby title="heap.rb" # Ruby does not provide a built-in Heap class ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20min_heap,%20flag%20%3D%20%5B%5D,%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20max_heap,%20flag%20%3D%20%5B%5D,%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20Python%20%E7%9A%84%20heapq%20%E6%A8%A1%E5%9D%97%E9%BB%98%E8%AE%A4%E5%AE%9E%E7%8E%B0%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%80%83%E8%99%91%E5%B0%86%E2%80%9C%E5%85%83%E7%B4%A0%E5%8F%96%E8%B4%9F%E2%80%9D%E5%90%8E%E5%86%8D%E5%85%A5%E5%A0%86%EF%BC%8C%E8%BF%99%E6%A0%B7%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%B0%86%E5%A4%A7%E5%B0%8F%E5%85%B3%E7%B3%BB%E9%A2%A0%E5%80%92%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%AE%9E%E7%8E%B0%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E5%9C%A8%E6%9C%AC%E7%A4%BA%E4%BE%8B%E4%B8%AD%EF%BC%8Cflag%20%3D%201%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%B0%8F%E9%A1%B6%E5%A0%86%EF%BC%8Cflag%20%3D%20-1%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%201%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%203%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%202%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%205%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20flag%20*%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%0A%20%20%20%20%23%20%E5%87%BA%E5%A0%86%E5%85%83%E7%B4%A0%E4%BC%9A%E5%BD%A2%E6%88%90%E4%B8%80%E4%B8%AA%E4%BB%8E%E5%A4%A7%E5%88%B0%E5%B0%8F%E7%9A%84%E5%BA%8F%E5%88%97%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E5%88%97%E8%A1%A8%E5%B9%B6%E5%BB%BA%E5%A0%86%0A%20%20%20%20min_heap%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## Implementation of the Heap The following implementation is of a max heap. To convert it to a min heap, simply invert all size logic comparisons (for example, replace $\geq$ with $\leq$). Interested readers are encouraged to implement this on their own. ### Heap Storage and Representation As mentioned in the "Binary Tree" chapter, complete binary trees are well-suited for array representation. Since heaps are a type of complete binary tree, **we will use arrays to store heaps**. When representing a binary tree with an array, elements represent node values, and indexes represent node positions in the binary tree. **Node pointers are implemented through index mapping formulas**. As shown in the figure below, given an index $i$, the index of its left child is $2i + 1$, the index of its right child is $2i + 2$, and the index of its parent is $(i - 1) / 2$ (floor division). When an index is out of bounds, it indicates a null node or that the node does not exist. ![Representation and storage of heaps](heap.assets/representation_of_heap.png) We can encapsulate the index mapping formula into functions for convenient subsequent use: ```src [file]{my_heap}-[class]{max_heap}-[func]{parent} ``` ### Accessing the Heap Top Element The heap top element is the root node of the binary tree, which is also the first element of the list: ```src [file]{my_heap}-[class]{max_heap}-[func]{peek} ``` ### Inserting an Element Into the Heap Given an element `val`, we first add it to the bottom of the heap. After addition, since `val` may be larger than other elements in the heap, the heap's property may be violated. **Therefore, it's necessary to repair the path from the inserted node to the root node**. This operation is called heapify. Starting from the inserted node, **perform heapify from bottom to top**. As shown in the figure below, we compare the inserted node with its parent node, and if the inserted node is larger, swap them. Then continue this operation, repairing nodes in the heap from bottom to top until we pass the root node or encounter a node that does not need swapping. === "<1>" ![Steps of inserting an element into the heap](heap.assets/heap_push_step1.png) === "<2>" ![heap_push_step2](heap.assets/heap_push_step2.png) === "<3>" ![heap_push_step3](heap.assets/heap_push_step3.png) === "<4>" ![heap_push_step4](heap.assets/heap_push_step4.png) === "<5>" ![heap_push_step5](heap.assets/heap_push_step5.png) === "<6>" ![heap_push_step6](heap.assets/heap_push_step6.png) === "<7>" ![heap_push_step7](heap.assets/heap_push_step7.png) === "<8>" ![heap_push_step8](heap.assets/heap_push_step8.png) === "<9>" ![heap_push_step9](heap.assets/heap_push_step9.png) Given a total of $n$ nodes, the tree height is $O(\log n)$. Thus, the number of loop iterations in the heapify operation is at most $O(\log n)$, **making the time complexity of the element insertion operation $O(\log n)$**. The code is as follows: ```src [file]{my_heap}-[class]{max_heap}-[func]{sift_up} ``` ### Removing the Heap Top Element The heap top element is the root node of the binary tree, which is the first element of the list. If we directly remove the first element from the list, all node indexes in the binary tree would change, making subsequent repair with heapify difficult. To minimize changes in element indexes, we use the following steps. 1. Swap the heap top element with the heap bottom element (swap the root node with the rightmost leaf node). 2. After swapping, remove the heap bottom from the list (note that since we've swapped, we're actually removing the original heap top element). 3. Starting from the root node, **perform heapify from top to bottom**. As shown in the figure below, **the direction of "top-to-bottom heapify" is opposite to "bottom-to-top heapify"**. We compare the root node's value with its two children and swap it with the largest child. Then loop this operation until we pass a leaf node or encounter a node that doesn't need swapping. === "<1>" ![Steps of removing the heap top element](heap.assets/heap_pop_step1.png) === "<2>" ![heap_pop_step2](heap.assets/heap_pop_step2.png) === "<3>" ![heap_pop_step3](heap.assets/heap_pop_step3.png) === "<4>" ![heap_pop_step4](heap.assets/heap_pop_step4.png) === "<5>" ![heap_pop_step5](heap.assets/heap_pop_step5.png) === "<6>" ![heap_pop_step6](heap.assets/heap_pop_step6.png) === "<7>" ![heap_pop_step7](heap.assets/heap_pop_step7.png) === "<8>" ![heap_pop_step8](heap.assets/heap_pop_step8.png) === "<9>" ![heap_pop_step9](heap.assets/heap_pop_step9.png) === "<10>" ![heap_pop_step10](heap.assets/heap_pop_step10.png) Similar to the element insertion operation, the time complexity of the heap top element removal operation is also $O(\log n)$. The code is as follows: ```src [file]{my_heap}-[class]{max_heap}-[func]{sift_down} ``` ## Common Applications of Heaps - **Priority queue**: Heaps are typically the preferred data structure for implementing priority queues, with both enqueue and dequeue operations having a time complexity of $O(\log n)$, and the heap construction operation having $O(n)$, all of which are highly efficient. - **Heap sort**: Given a set of data, we can build a heap with them and then continuously perform element removal operations to obtain sorted data. However, we usually use a more elegant approach to implement heap sort, as detailed in the "Heap Sort" chapter. - **Getting the largest $k$ elements**: This is a classic algorithm problem and also a typical application, such as selecting the top 10 trending news for Weibo hot search, selecting the top 10 best-selling products, etc. ================================================ FILE: en/docs/chapter_heap/index.md ================================================ # Heap ![Heap](../assets/covers/chapter_heap.jpg) !!! abstract Heaps are like mountain peaks, layered and undulating, each with its unique form. The peaks rise and fall at varying heights, yet the tallest peak always catches the eye first. ================================================ FILE: en/docs/chapter_heap/summary.md ================================================ # Summary ### Key Review - A heap is a complete binary tree that can be categorized as a max heap or min heap based on its property. The heap top element of a max heap (min heap) is the largest (smallest). - A priority queue is defined as a queue with priority sorting, typically implemented using heaps. - Common heap operations and their corresponding time complexities include: element insertion $O(\log n)$, heap top element removal $O(\log n)$, and accessing the heap top element $O(1)$. - Complete binary trees are well-suited for array representation, so we typically use arrays to store heaps. - Heapify operations are used to maintain the heap property and are employed in both element insertion and removal operations. - The time complexity of building a heap with $n$ input elements can be optimized to $O(n)$, which is highly efficient. - Top-k is a classic algorithm problem that can be efficiently solved using the heap data structure, with a time complexity of $O(n \log k)$. ### Q & A **Q**: Are the "heap" in data structures and the "heap" in memory management the same concept? The two are not the same concept; they just happen to share the name "heap." The heap in computer system memory is part of dynamic memory allocation, where programs can use it to store data during runtime. Programs can request a certain amount of heap memory to store complex structures such as objects and arrays. When this data is no longer needed, the program needs to release this memory to prevent memory leaks. Compared to stack memory, heap memory management and usage require more caution, as improper use can lead to issues such as memory leaks and dangling pointers. ================================================ FILE: en/docs/chapter_heap/top_k.md ================================================ # Top-K Problem !!! question Given an unordered array `nums` of length $n$, return the largest $k$ elements in the array. For this problem, we'll first introduce two solutions with relatively straightforward approaches, then introduce a more efficient heap-based solution. ## Method 1: Iterative Selection We can perform $k$ rounds of traversal as shown in the figure below, extracting the $1^{st}$, $2^{nd}$, $\dots$, $k^{th}$ largest elements in each round, with a time complexity of $O(nk)$. This method is only suitable when $k \ll n$, because when $k$ is close to $n$, the time complexity approaches $O(n^2)$, which is very time-consuming. ![Traversing to find the largest k elements](top_k.assets/top_k_traversal.png) !!! tip When $k = n$, we can obtain a complete sorted sequence, which is equivalent to the "selection sort" algorithm. ## Method 2: Sorting As shown in the figure below, we can first sort the array `nums`, then return the rightmost $k$ elements, with a time complexity of $O(n \log n)$. Clearly, this method "overachieves" the task, as we only need to find the largest $k$ elements, without needing to sort the other elements. ![Sorting to find the largest k elements](top_k.assets/top_k_sorting.png) ## Method 3: Heap We can solve the Top-k problem more efficiently using heaps, with the process shown in the figure below. 1. Initialize a min heap, where the heap top element is the smallest. 2. First, insert the first $k$ elements of the array into the heap in sequence. 3. Starting from the $(k + 1)^{th}$ element, if the current element is greater than the heap top element, remove the heap top element and insert the current element into the heap. 4. After traversal is complete, the heap contains the largest $k$ elements. === "<1>" ![Finding the largest k elements using a heap](top_k.assets/top_k_heap_step1.png) === "<2>" ![top_k_heap_step2](top_k.assets/top_k_heap_step2.png) === "<3>" ![top_k_heap_step3](top_k.assets/top_k_heap_step3.png) === "<4>" ![top_k_heap_step4](top_k.assets/top_k_heap_step4.png) === "<5>" ![top_k_heap_step5](top_k.assets/top_k_heap_step5.png) === "<6>" ![top_k_heap_step6](top_k.assets/top_k_heap_step6.png) === "<7>" ![top_k_heap_step7](top_k.assets/top_k_heap_step7.png) === "<8>" ![top_k_heap_step8](top_k.assets/top_k_heap_step8.png) === "<9>" ![top_k_heap_step9](top_k.assets/top_k_heap_step9.png) Example code is as follows: ```src [file]{top_k}-[class]{}-[func]{top_k_heap} ``` A total of $n$ rounds of heap insertions and removals are performed, with the heap's maximum length being $k$, so the time complexity is $O(n \log k)$. This method is very efficient; when $k$ is small, the time complexity approaches $O(n)$; when $k$ is large, the time complexity does not exceed $O(n \log n)$. Additionally, this method is suitable for dynamic data stream scenarios. By continuously adding data, we can maintain the elements in the heap, thus achieving dynamic updates of the largest $k$ elements. ================================================ FILE: en/docs/chapter_hello_algo/index.md ================================================ --- comments: true icon: material/rocket-launch-outline --- # Before Starting A few years ago, I shared the "Sword for Offer" problem solutions on LeetCode, receiving encouragement and support from many readers. During interactions with readers, the most frequently asked question I encountered was "how to get started with algorithms." Gradually, I developed a keen interest in this question. Diving straight into problem-solving seems to be the most popular approach—it's simple, direct, and effective. However, problem-solving is like playing Minesweeper: those with strong self-learning abilities can successfully defuse the mines one by one, while those with insufficient foundations may end up bruised and battered, retreating step by step in frustration. Reading through textbooks is also a common practice, but for job seekers, graduation theses, resume submissions, and preparations for written tests and interviews have already consumed most of their energy, making working through thick books an arduous challenge. If you're facing similar struggles, then it's fortunate that this book has "found" you. This book is my answer to this question—even if it may not be the optimal solution, it is at least a positive attempt. While this book alone won't directly land you a job offer, it will guide you to explore the "knowledge map" of data structures and algorithms, help you understand the shapes, sizes, and distributions of different "mines," and enable you to master various "mine-clearing methods." With these skills, I believe you can tackle problems and read technical literature more confidently, gradually building a complete knowledge system. I deeply agree with Professor Feynman's words: "Knowledge isn't free. You have to pay attention." In this sense, this book is not entirely "free." In order to live up to the precious "attention" you invest in this book, I will do my utmost and devote my greatest "attention" to completing this work. I'm acutely aware of my limited knowledge and shallow expertise. Although the content of this book has been refined over a period of time, there are certainly still many errors, and I sincerely welcome critiques and corrections from teachers and fellow students. ![Hello Algorithms](../assets/covers/chapter_hello_algo.jpg){ class="cover-image" }

Hello, Algorithms!

The advent of computers has brought tremendous changes to the world. With their high-speed computing capabilities and excellent programmability, they have become the ideal medium for executing algorithms and processing data. Whether it's the realistic graphics in video games, the intelligent decision-making in autonomous driving, AlphaGo's brilliant Go matches, or ChatGPT's natural interactions, these applications are all exquisite interpretations of algorithms on computers. In fact, before the advent of computers, algorithms and data structures already existed in every corner of the world. Early algorithms were relatively simple, such as ancient counting methods and tool-making procedures. As civilization progressed, algorithms gradually became more refined and complex. From the ingenious craftsmanship of master artisans, to industrial products that liberate productive forces, to the scientific laws governing the operation of the universe, behind almost every ordinary or astonishing thing lies ingenious algorithmic thinking. Similarly, data structures are everywhere: from large-scale social networks to small subway systems, many systems can be modeled as "graphs"; from a nation to a family, the primary organizational forms of society exhibit characteristics of "trees"; winter clothing is like a "stack," where the first item put on is the last to be taken off; a badminton tube is like a "queue," with items inserted at one end and retrieved from the other; a dictionary is like a "hash table," enabling quick lookup of target entries. This book aims to help readers understand the core concepts of algorithms and data structures through clear and accessible animated illustrations and runnable code examples, and to implement them through programming. Building on this foundation, the book endeavors to reveal the vivid manifestations of algorithms in the complex world and showcase the beauty of algorithms. I hope this book can be of help to you! ================================================ FILE: en/docs/chapter_introduction/algorithms_are_everywhere.md ================================================ # Algorithms Are Everywhere When we hear the term "algorithm," we naturally think of mathematics. However, many algorithms do not involve complex mathematics but rely more on basic logic, which can be seen everywhere in our daily lives. Before we start discussing about algorithms officially, there's an interesting fact worth sharing: **you've learned many algorithms unconsciously and are used to applying them in your daily life**. Here, I will give a few specific examples to prove this point. **Example 1: Looking Up a Dictionary**. In an English dictionary, words are listed alphabetically. Assuming we're searching for a word that starts with the letter $r$, this is typically done in the following way: 1. Open the dictionary to about halfway and check the first vocabulary of the page, let's say the letter starts with $m$. 2. Since $r$ comes after $m$ in the alphabet, the first half can be ignored and the search space is narrowed down to the second half. 3. Repeat steps `1.` and `2.` until you find the page where the word starts with $r$. === "<1>" ![Process of looking up a dictionary](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png) === "<2>" ![Binary search in dictionary step 2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png) === "<3>" ![Binary search in dictionary step 3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png) === "<4>" ![Binary search in dictionary step 4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png) === "<5>" ![Binary search in dictionary step 5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png) Looking up a dictionary, an essential skill for elementary school students is actually the famous "Binary Search" algorithm. From a data structure perspective, we can consider the dictionary as a sorted "array"; from an algorithmic perspective, the series of actions taken to look up a word in the dictionary can be viewed as the algorithm "Binary Search." **Example 2: Organizing Card Deck**. When playing cards, we need to arrange the cards in our hands in ascending order, as shown in the following process. 1. Divide the playing cards into "ordered" and "unordered" sections, assuming initially the leftmost card is already in order. 2. Take out a card from the unordered section and insert it into the correct position in the ordered section; after this, the leftmost two cards are in order. 3. Repeat step `2` until all cards are in order. ![Process of sorting a deck of cards](algorithms_are_everywhere.assets/playing_cards_sorting.png) The above method of organizing playing cards is practically the "Insertion Sort" algorithm, which is very efficient for small datasets. Many programming languages' sorting functions include the insertion sort. **Example 3: Making Change**. Assume making a purchase of $69$ at a supermarket. If you give the cashier $100$, they will need to provide you with $31$ in change. This process can be clearly understood as illustrated in the figure below. 1. The options are currencies valued below $31$, including $1$, $5$, $10$, and $20$. 2. Take out the largest $20$ from the options, leaving $31 - 20 = 11$. 3. Take out the largest $10$ from the remaining options, leaving $11 - 10 = 1$. 4. Take out the largest $1$ from the remaining options, leaving $1 - 1 = 0$. 5. Complete change-making, the solution is $20 + 10 + 1 = 31$. ![Process of making change](algorithms_are_everywhere.assets/greedy_change.png) In the steps described, we choose the best option at each stage by utilizing the largest denomination available, which leads to an effective change-making strategy. From a data structures and algorithms perspective, this approach is known as a "Greedy" algorithm. From cooking a meal to interstellar travel, almost all problem-solving involves algorithms. The advent of computers allows us to store data structures in memory and write code to call the CPU and GPU to execute algorithms. In this way, we can transfer real-life problems to computers and solve various complex issues in a more efficient way. !!! tip If you are still confused about concepts like data structures, algorithms, arrays, and binary searches, I encourage you to keep reading. This book will gently guide you into the realm of understanding data structures and algorithms. ================================================ FILE: en/docs/chapter_introduction/index.md ================================================ # Encounter with Algorithms ![Encounter with Algorithms](../assets/covers/chapter_introduction.jpg) !!! abstract A young girl dances gracefully, intertwined with data, her skirt flowing with the melody of algorithms. She invites you to dance with her. Follow her steps closely and enter the world of algorithms, full of logic and beauty. ================================================ FILE: en/docs/chapter_introduction/summary.md ================================================ # Summary ### Key Review - Algorithms are ubiquitous in daily life and are not distant, esoteric knowledge. In fact, we have already learned many algorithms unconsciously and use them to solve problems big and small in life. - The principle of looking up a dictionary is consistent with the binary search algorithm. Binary search embodies the important algorithmic idea of divide and conquer. - The process of organizing playing cards is very similar to the insertion sort algorithm. Insertion sort is suitable for sorting small datasets. - The steps of making change are essentially a greedy algorithm, where the best choice is made at each step based on the current situation. - An algorithm is a set of instructions or operational steps that solves a specific problem within a finite amount of time, while a data structure is the way computers organize and store data. - Data structures and algorithms are closely connected. Data structures are the foundation of algorithms, and algorithms breathe life into data structures. - We can compare data structures and algorithms to assembling building blocks. The blocks represent data, the shape and connection method of the blocks represent the data structure, and the steps to assemble the blocks correspond to the algorithm. ### Q & A **Q**: As a programmer, I have never used algorithms to solve problems in my daily work. Common algorithms are already encapsulated by programming languages and can be used directly. Does this mean that the problems in our work have not yet reached the level where algorithms are needed? If we compare specific work skills to "techniques" in martial arts, then fundamental subjects should be more like "internal skills". I believe the significance of learning algorithms (and other fundamental subjects) is not to implement them from scratch at work, but rather to be able to make professional reactions and judgments when solving problems based on the knowledge learned, thereby improving the overall quality of work. Here is a simple example. Every programming language has a built-in sorting function: - If we have not studied data structures and algorithms, we might simply feed any given data to this sorting function. It runs smoothly with good performance, and there doesn't seem to be any problem. - But if we have studied algorithms, we would know that the time complexity of the built-in sorting function is $O(n \log n)$. However, if the given data consists of integers with a fixed number of digits (such as student IDs), we can use the more efficient "radix sort", reducing the time complexity to $O(nk)$, where $k$ is the number of digits. When the data volume is very large, the saved running time can create significant value (reduced costs, improved experience, etc.). In the field of engineering, a large number of problems are difficult to reach optimal solutions, and many problems are only solved "approximately". The difficulty of a problem depends on one hand on the nature of the problem itself, and on the other hand on the knowledge reserve of the person observing the problem. The more complete a person's knowledge and the more experience they have, the deeper their analysis of the problem will be, and the more elegantly the problem can be solved. ================================================ FILE: en/docs/chapter_introduction/what_is_dsa.md ================================================ # What Is an Algorithm ## Algorithm Definition An algorithm is a set of instructions or operational steps that solves a specific problem within a finite amount of time. It has the following characteristics. - The problem is well-defined, with clear input and output definitions. - It is feasible and can be completed within a finite number of steps, time, and memory space. - Each step has a definite meaning, and under the same input and operating conditions, the output is always the same. ## Data Structure Definition A data structure is a way of organizing and storing data, covering the data content, relationships between data, and methods for data operations. It has the following design objectives. - Occupy as little space as possible to save computer memory. - Data operations should be as fast as possible, covering data access, addition, deletion, update, etc. - Provide a concise data representation and logical information so that algorithms can run efficiently. **Data structure design is a process full of trade-offs**. If we want to achieve improvements in one aspect, we often need to make compromises in another aspect. Here are two examples. - Compared to arrays, linked lists are more convenient for data addition and deletion operations but sacrifice data access speed. - Compared to linked lists, graphs provide richer logical information but require larger memory space. ## The Relationship Between Data Structures and Algorithms As shown in the figure below, data structures and algorithms are highly related and tightly coupled, specifically manifested in the following three aspects. - Data structures are the foundation of algorithms. Data structures provide algorithms with structured storage of data and methods for operating on data. - Algorithms breathe life into data structures. Data structures themselves only store data information; combined with algorithms, they can solve specific problems. - Algorithms can usually be implemented based on different data structures, but execution efficiency may vary greatly. Choosing the appropriate data structure is key. ![The relationship between data structures and algorithms](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png) Data structures and algorithms are like assembling building blocks as shown in the figure below. A set of building blocks, in addition to containing many parts, also comes with detailed assembly instructions. By following the instructions step by step, we can assemble an exquisite building block model. ![Assembling blocks](what_is_dsa.assets/assembling_blocks.png) The detailed correspondence between the two is shown in the table below.

Table   Comparing data structures and algorithms to assembling building blocks

| Data structures and algorithms | Assembling building blocks | | ------------------------------ | ------------------------------------------------------------------ | | Input data | Unassembled building blocks | | Data structure | Organization form of building blocks, including shape, size, connection method, etc. | | Algorithm | A series of operational steps to assemble the blocks into the target form | | Output data | Building block model | It is worth noting that data structures and algorithms are independent of programming languages. For this reason, this book is able to provide implementations based on multiple programming languages. !!! tip "Conventional abbreviation" In actual discussions, we usually abbreviate "data structures and algorithms" as "algorithms". For example, the well-known LeetCode algorithm problems actually examine knowledge of both data structures and algorithms. ================================================ FILE: en/docs/chapter_preface/about_the_book.md ================================================ # About This Book This project aims to create an open-source, free, beginner-friendly introductory tutorial on data structures and algorithms. - The entire book uses animated illustrations, with clear and easy-to-understand content and a smooth learning curve, guiding beginners to explore the knowledge map of data structures and algorithms. - The source code can be run with one click, helping readers improve their programming skills through practice and understand how algorithms work and the underlying implementation of data structures. - We encourage readers to learn from each other, and everyone is welcome to ask questions and share insights in the comments section, making progress together through discussion and exchange. ## Target Audience If you are an algorithm beginner who has never been exposed to algorithms, or if you already have some problem-solving experience and have a vague understanding of data structures and algorithms, oscillating between knowing and not knowing, then this book is tailor-made for you! If you have already accumulated a certain amount of problem-solving experience and are familiar with most question types, this book can help you review and organize your algorithm knowledge system, and the repository's source code can be used as a "problem-solving toolkit" or "algorithm dictionary." If you are an algorithm "expert," we look forward to receiving your valuable suggestions, or [participating in creation together](https://www.hello-algo.com/chapter_appendix/contribution/). !!! success "Prerequisites" You need to have at least a programming foundation in any language, and be able to read and write simple code. ## Content Structure The main content of this book is shown in the figure below. - **Complexity analysis**: Evaluation dimensions and methods for data structures and algorithms. Methods for calculating time complexity and space complexity, common types, examples, etc. - **Data structures**: Classification methods for basic data types and data structures. The definition, advantages and disadvantages, common operations, common types, typical applications, implementation methods, etc. of data structures such as arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs. - **Algorithms**: The definition, advantages and disadvantages, efficiency, application scenarios, problem-solving steps, and example problems of algorithms such as searching, sorting, divide and conquer, backtracking, dynamic programming, and greedy algorithms. ![Main content of this book](about_the_book.assets/hello_algo_mindmap.png) ## Acknowledgements This book has been continuously improved through the joint efforts of many contributors in the open-source community. Thanks to every contributor who invested time and effort, they are (in the order automatically generated by GitHub): krahets, coderonion, Gonglja, nuomi1, Reanon, justin-tse, hpstory, danielsss, curtishd, night-cruise, S-N-O-R-L-A-X, rongyi, msk397, gvenusleo, khoaxuantu, rivertwilight, K3v123, gyt95, zhuoqinyue, yuelinxin, Zuoxun, mingXta, Phoenix0415, FangYuan33, GN-Yu, longsizhuo, IsChristina, xBLACKICEx, guowei-gong, Cathay-Chen, pengchzn, QiLOL, magentaqin, hello-ikun, JoseHung, qualifier1024, thomasq0, sunshinesDL, L-Super, Guanngxu, Transmigration-zhou, WSL0809, Slone123c, lhxsm, yuan0221, what-is-me, Shyam-Chen, theNefelibatas, longranger2, codeberg-user, xiongsp, JeffersonHuang, prinpal, seven1240, Wonderdch, malone6, xiaomiusa87, gaofer, bluebean-cloud, a16su, SamJin98, hongyun-robot, nanlei, XiaChuerwu, yd-j, iron-irax, mgisr, steventimes, junminhong, heshuyue, danny900714, MolDuM, Nigh, Dr-XYZ, XC-Zero, reeswell, PXG-XPG, NI-SW, Horbin-Magician, Enlightenus, YangXuanyi, beatrix-chan, DullSword, xjr7670, jiaxianhua, qq909244296, iStig, boloboloda, hts0000, gledfish, wenjianmin, keshida, kilikilikid, lclc6, lwbaptx, linyejoe2, liuxjerry, llql1211, fbigm, echo1937, szu17dmy, dshlstarr, Yucao-cy, coderlef, czruby, bongbongbakudan, beintentional, ZongYangL, ZhongYuuu, ZhongGuanbin, hezhizhen, linzeyan, ZJKung, luluxia, xb534, ztkuaikuai, yw-1021, ElaBosak233, baagod, zhouLion, yishangzhang, yi427, yanedie, yabo083, weibk, wangwang105, th1nk3r-ing, tao363, 4yDX3906, syd168, sslmj2020, smilelsb, siqyka, selear, sdshaoda, Xi-Row, popozhu, nuquist19, noobcodemaker, XiaoK29, chadyi, lyl625760, lucaswangdev, 0130w, shanghai-Jerry, EJackYang, Javesun99, eltociear, lipusheng, KNChiu, BlindTerran, ShiMaRing, lovelock, FreddieLi, FloranceYeh, fanchenggang, gltianwen, goerll, nedchu, curly210102, CuB3y0nd, KraHsu, CarrotDLaw, youshaoXG, bubble9um, Asashishi, Asa0oo0o0o, fanenr, eagleanurag, akshiterate, 52coder, foursevenlove, KorsChen, GaochaoZhu, hopkings2008, yang-le, realwujing, Evilrabbit520, Umer-Jahangir, Turing-1024-Lee, Suremotoo, paoxiaomooo, Chieko-Seren, Allen-Scai, ymmmas, Risuntsy, Richard-Zhang1019, RafaelCaso, qingpeng9802, primexiao, Urbaner3, zhongfq, nidhoggfgg, MwumLi, CreatorMetaSky, martinx, ZnYang2018, hugtyftg, logan-qiu, psychelzh, Keynman, KeiichiKasai, and KawaiiAsh. The code review work for this book was completed by coderonion, curtishd, Gonglja, gvenusleo, hpstory, justin-tse, khoaxuantu, krahets, night-cruise, nuomi1, Reanon and rongyi (in alphabetical order). Thanks to them for the time and effort they put in, it is they who ensure the standardization and unity of code in various languages. The Traditional Chinese version of this book was reviewed by Shyam-Chen and Dr-XYZ, the English version was reviewed by yuelinxin, K3v123, QiLOL, Phoenix0415, SamJin98, yanedie, RafaelCaso, pengchzn, thomasq0 and magentaqin, and the Japanese edition was reviewed by eltociear. It is because of their continuous contributions that this book can serve a wider readership, and we thank them. The ePub ebook generation tool for this book was developed by zhongfq. We thank him for his contribution, which provides readers with a more flexible way to read. During the creation of this book, I received help from many people. - Thanks to my mentor at the company, Dr. Li Xi, who encouraged me to "take action quickly" during a conversation, strengthening my determination to write this book; - Thanks to my girlfriend Bubble as the first reader of this book, who provided many valuable suggestions from the perspective of an algorithm beginner, making this book more suitable for novices to read; - Thanks to Tengbao, Qibao, and Feibao for coming up with a creative name for this book, evoking everyone's fond memories of writing their first line of code "Hello World!"; - Thanks to Xiaoquan for providing professional help in intellectual property rights, which played an important role in the improvement of this open-source book; - Thanks to Sutong for designing the beautiful cover and logo for this book, and for patiently making revisions multiple times driven by my obsessive-compulsive disorder; - Thanks to @squidfunk for the typesetting suggestions, as well as for developing the open-source documentation theme [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master). During the writing process, I read many textbooks and articles on data structures and algorithms. These works provided excellent examples for this book and ensured the accuracy and quality of the book's content. I would like to thank all the teachers and predecessors for their outstanding contributions! This book advocates a learning method that combines hands and brain, and in this regard I was deeply inspired by [Dive into Deep Learning](https://github.com/d2l-ai/d2l-zh). I highly recommend this excellent work to all readers. **Heartfelt thanks to my parents, it is your support and encouragement that has given me the opportunity to do this interesting thing**. ================================================ FILE: en/docs/chapter_preface/index.md ================================================ # Preface ![Preface](../assets/covers/chapter_preface.jpg) !!! abstract Algorithms are like a beautiful symphony, each line of code flows like a melody. May this book gently resonate in your mind, leaving a unique and profound melody. ================================================ FILE: en/docs/chapter_preface/suggestions.md ================================================ # How to Use This Book !!! tip For the best reading experience, it is recommended that you read through this section. ## Writing Style Conventions - Titles marked with `*` are optional sections with relatively difficult content. If you have limited time, you can skip them first. - Technical terms will be in bold (in paper and PDF versions) or underlined (in web versions), such as array. It is recommended to memorize them for reading literature. - Key content and summary statements will be **bolded**, and such text deserves special attention. - Words and phrases with specific meanings will be marked with "quotation marks" to avoid ambiguity. - When it comes to nouns that are inconsistent between programming languages, this book uses Python as the standard, for example, using `None` to represent "null". - This book partially abandons the comment conventions of programming languages in favor of more compact content layout. Comments are mainly divided into three types: title comments, content comments, and multi-line comments. === "Python" ```python title="" """Title comment, used to label functions, classes, test cases, etc.""" # Content comment, used to explain code in detail """ Multi-line comment """ ``` === "C++" ```cpp title="" /* Title comment, used to label functions, classes, test cases, etc. */ // Content comment, used to explain code in detail /** * Multi-line * comment */ ``` === "Java" ```java title="" /* Title comment, used to label functions, classes, test cases, etc. */ // Content comment, used to explain code in detail /** * Multi-line * comment */ ``` === "C#" ```csharp title="" /* Title comment, used to label functions, classes, test cases, etc. */ // Content comment, used to explain code in detail /** * Multi-line * comment */ ``` === "Go" ```go title="" /* Title comment, used to label functions, classes, test cases, etc. */ // Content comment, used to explain code in detail /** * Multi-line * comment */ ``` === "Swift" ```swift title="" /* Title comment, used to label functions, classes, test cases, etc. */ // Content comment, used to explain code in detail /** * Multi-line * comment */ ``` === "JS" ```javascript title="" /* Title comment, used to label functions, classes, test cases, etc. */ // Content comment, used to explain code in detail /** * Multi-line * comment */ ``` === "TS" ```typescript title="" /* Title comment, used to label functions, classes, test cases, etc. */ // Content comment, used to explain code in detail /** * Multi-line * comment */ ``` === "Dart" ```dart title="" /* Title comment, used to label functions, classes, test cases, etc. */ // Content comment, used to explain code in detail /** * Multi-line * comment */ ``` === "Rust" ```rust title="" /* Title comment, used to label functions, classes, test cases, etc. */ // Content comment, used to explain code in detail // Multi-line // comment ``` === "C" ```c title="" /* Title comment, used to label functions, classes, test cases, etc. */ // Content comment, used to explain code in detail /** * Multi-line * comment */ ``` === "Kotlin" ```kotlin title="" /* Title comment, used to label functions, classes, test cases, etc. */ // Content comment, used to explain code in detail /** * Multi-line * comment */ ``` === "Ruby" ```ruby title="" ### Title comment, used to label functions, classes, test cases, etc. ### # Content comment, used to explain code in detail # Multi-line # comment ``` ## Learning Efficiently with Animated Illustrations Compared to text, videos and images have higher information density and structural organization, making them easier to understand. In this book, **key and difficult knowledge will mainly be presented in the form of animated illustrations**, with text serving as explanation and supplement. If you find that a section of content provides animated illustrations as shown in the figure below while reading this book, **please focus on the illustrations first, with text as a supplement**, and combine the two to understand the content. ![Example of animated illustrations](../index.assets/animation.gif) ## Deepening Understanding Through Code Practice The accompanying code for this book is hosted in the [GitHub repository](https://github.com/krahets/hello-algo). As shown in the figure below, **the source code comes with test cases and can be run with one click**. If time permits, **it is recommended that you type out the code yourself**. If you have limited study time, please at least read through and run all the code. Compared to reading code, the process of writing code often brings more rewards. **Learning by doing is the real learning**. ![Example of running code](../index.assets/running_code.gif) The preliminary work for running code is mainly divided into three steps. **Step 1: Install the local programming environment**. Please follow the [tutorial](https://www.hello-algo.com/chapter_appendix/installation/) shown in the appendix for installation. If already installed, you can skip this step. **Step 2: Clone or download the code repository**. Visit the [GitHub repository](https://github.com/krahets/hello-algo). If you have already installed [Git](https://git-scm.com/downloads), you can clone this repository with the following command: ```shell git clone https://github.com/krahets/hello-algo.git ``` Of course, you can also click the "Download ZIP" button at the location shown in the figure below to directly download the code compressed package, and then extract it locally. ![Clone repository and download code](suggestions.assets/download_code.png) **Step 3: Run the source code**. As shown in the figure below, for code blocks with file names at the top, we can find the corresponding source code files in the `codes` folder of the repository. The source code files can be run with one click, which will help you save unnecessary debugging time and allow you to focus on learning content. ![Code blocks and corresponding source code files](suggestions.assets/code_md_to_repo.png) In addition to running code locally, **the web version also supports visual running of Python code** (implemented based on [pythontutor](https://pythontutor.com/)). As shown in the figure below, you can click "Visual Run" below the code block to expand the view and observe the execution process of the algorithm code; you can also click "Full Screen View" for a better viewing experience. ![Visual running of Python code](suggestions.assets/pythontutor_example.png) ## Growing Together Through Questions and Discussions When reading this book, please do not easily skip knowledge points that you have not learned well. **Feel free to ask your questions in the comments section**, and my friends and I will do our best to answer you, and generally reply within two days. As shown in the figure below, the web version has a comments section at the bottom of each chapter. I hope you will pay more attention to the content of the comments section. On the one hand, you can learn about the problems that everyone encounters, thus checking for omissions and stimulating deeper thinking. On the other hand, I hope you can generously answer other friends' questions, share your insights, and help others progress. ![Example of comments section](../index.assets/comment.gif) ## Algorithm Learning Roadmap From an overall perspective, we can divide the process of learning data structures and algorithms into three stages. 1. **Stage 1: Algorithm introduction**. We need to familiarize ourselves with the characteristics and usage of various data structures, and learn the principles, processes, uses, and efficiency of different algorithms. 2. **Stage 2: Practice algorithm problems**. It is recommended to start with popular problems, and accumulate at least 100 problems first, to familiarize yourself with mainstream algorithm problems. When first practicing problems, "knowledge forgetting" may be a challenge, but rest assured, this is very normal. We can review problems according to the "Ebbinghaus forgetting curve", and usually after 3-5 rounds of repetition, we can firmly remember them. For recommended problem lists and practice plans, please see this [GitHub repository](https://github.com/krahets/LeetCode-Book). 3. **Stage 3: Building a knowledge system**. In terms of learning, we can read algorithm column articles, problem-solving frameworks, and algorithm textbooks to continuously enrich our knowledge system. In terms of practicing problems, we can try advanced problem-solving strategies, such as categorization by topic, one problem multiple solutions, one solution multiple problems, etc. Related problem-solving insights can be found in various communities. As shown in the figure below, the content of this book mainly covers "Stage 1", aiming to help you more efficiently carry out Stage 2 and Stage 3 learning. ![Algorithm learning roadmap](suggestions.assets/learning_route.png) ================================================ FILE: en/docs/chapter_preface/summary.md ================================================ # Summary ### Key Review - The main audience of this book is algorithm beginners. If you already have a certain foundation, this book can help you systematically review algorithm knowledge, and the source code in the book can also be used as a "problem-solving toolkit." - The content of the book mainly includes three parts: complexity analysis, data structures, and algorithms, covering most topics in this field. - For algorithm novices, reading an introductory book during the initial learning stage is crucial, as it can help you avoid many detours. - The animated illustrations in the book are usually used to introduce key and difficult knowledge. When reading this book, you should pay more attention to these contents. - Practice is the best way to learn programming. It is strongly recommended to run the source code and type the code yourself. - The web version of this book has a comments section for each chapter, where you are welcome to share your questions and insights at any time. ================================================ FILE: en/docs/chapter_reference/index.md ================================================ --- icon: material/bookshelf --- # References [1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition). [2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition). [3] Robert Sedgewick, et al. Algorithms (4th Edition). [4] Yan Weimin. Data Structures (C Language Version). [5] Deng Junhui. Data Structures (C++ Language Version, Third Edition). [6] Mark Allen Weiss, translated by Chen Yue. Data Structures and Algorithm Analysis in Java (Third Edition). [7] Cheng Jie. Conversational Data Structures. [8] Wang Zheng. The Beauty of Data Structures and Algorithms. [9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition). [10] Aston Zhang, et al. Dive into Deep Learning. ================================================ FILE: en/docs/chapter_searching/binary_search.md ================================================ # Binary Search Binary search is an efficient searching algorithm based on the divide-and-conquer strategy. It leverages the orderliness of data to reduce the search range by half in each round until the target element is found or the search interval becomes empty. !!! question Given an array `nums` of length $n$ with elements arranged in ascending order and no duplicates, search for and return the index of element `target` in the array. If the array does not contain the element, return $-1$. An example is shown in the figure below. ![Binary search example data](binary_search.assets/binary_search_example.png) As shown in the figure below, we first initialize pointers $i = 0$ and $j = n - 1$, pointing to the first and last elements of the array respectively, representing the search interval $[0, n - 1]$. Note that square brackets denote a closed interval, which includes the boundary values themselves. Next, perform the following two steps in a loop: 1. Calculate the midpoint index $m = \lfloor {(i + j) / 2} \rfloor$, where $\lfloor \: \rfloor$ denotes the floor operation. 2. Compare `nums[m]` and `target`, which results in three cases: 1. When `nums[m] < target`, it indicates that `target` is in the interval $[m + 1, j]$, so execute $i = m + 1$. 2. When `nums[m] > target`, it indicates that `target` is in the interval $[i, m - 1]$, so execute $j = m - 1$. 3. When `nums[m] = target`, it indicates that `target` has been found, so return index $m$. If the array does not contain the target element, the search interval will eventually shrink to empty. In this case, return $-1$. === "<1>" ![Binary search process](binary_search.assets/binary_search_step1.png) === "<2>" ![binary_search_step2](binary_search.assets/binary_search_step2.png) === "<3>" ![binary_search_step3](binary_search.assets/binary_search_step3.png) === "<4>" ![binary_search_step4](binary_search.assets/binary_search_step4.png) === "<5>" ![binary_search_step5](binary_search.assets/binary_search_step5.png) === "<6>" ![binary_search_step6](binary_search.assets/binary_search_step6.png) === "<7>" ![binary_search_step7](binary_search.assets/binary_search_step7.png) It's worth noting that since both $i$ and $j$ are of `int` type, **$i + j$ may exceed the range of the `int` type**. To avoid large number overflow, we typically use the formula $m = \lfloor {i + (j - i) / 2} \rfloor$ to calculate the midpoint. The code is shown below: ```src [file]{binary_search}-[class]{}-[func]{binary_search} ``` **Time complexity is $O(\log n)$**: In the binary loop, the interval is reduced by half each round, so the number of loops is $\log_2 n$. **Space complexity is $O(1)$**: Pointers $i$ and $j$ use constant-size space. ## Interval Representation Methods In addition to the closed interval mentioned above, another common interval representation is the "left-closed right-open" interval, defined as $[0, n)$, meaning the left boundary includes itself while the right boundary does not. Under this representation, the interval $[i, j)$ is empty when $i = j$. We can implement a binary search algorithm with the same functionality based on this representation: ```src [file]{binary_search}-[class]{}-[func]{binary_search_lcro} ``` As shown in the figure below, under the two interval representations, the initialization, loop condition, and interval narrowing operations of the binary search algorithm are all different. Since both the left and right boundaries in the "closed interval" representation are defined as closed, the operations to narrow the interval through pointers $i$ and $j$ are also symmetric. This makes it less error-prone, **so the "closed interval" approach is generally recommended**. ![Two interval definitions](binary_search.assets/binary_search_ranges.png) ## Advantages and Limitations Binary search performs well in both time and space aspects. - Binary search has high time efficiency. With large data volumes, the logarithmic time complexity has significant advantages. For example, when the data size $n = 2^{20}$, linear search requires $2^{20} = 1048576$ loop rounds, while binary search only needs $\log_2 2^{20} = 20$ rounds. - Binary search requires no extra space. Compared to searching algorithms that require additional space (such as hash-based search), binary search is more space-efficient. However, binary search is not suitable for all situations, mainly for the following reasons: - Binary search is only applicable to sorted data. If the input data is unsorted, sorting specifically to use binary search would be counterproductive, as sorting algorithms typically have a time complexity of $O(n \log n)$, which is higher than both linear search and binary search. For scenarios with frequent element insertions, maintaining array orderliness requires inserting elements at specific positions with a time complexity of $O(n)$, which is also very expensive. - Binary search is only applicable to arrays. Binary search requires jump-style (non-contiguous) element access, and jump-style access has low efficiency in linked lists, making it unsuitable for linked lists or data structures based on linked list implementations. - For small data volumes, linear search performs better. In linear search, each round requires only 1 comparison operation; while in binary search, it requires 1 addition, 1 division, 1-3 comparison operations, and 1 addition (subtraction), totaling 4-6 unit operations. Therefore, when the data volume $n$ is small, linear search is actually faster than binary search. ================================================ FILE: en/docs/chapter_searching/binary_search_edge.md ================================================ # Binary Search Edge Cases ## Finding the Left Boundary !!! question Given a sorted array `nums` of length $n$ that may contain duplicate elements, return the index of the leftmost element `target` in the array. If the array does not contain the element, return $-1$. Recall the method for finding the insertion point with binary search. After the search completes, $i$ points to the leftmost `target`, **so finding the insertion point is essentially finding the index of the leftmost `target`**. Consider implementing the left boundary search using the insertion point finding function. Note that the array may not contain `target`, which could result in the following two cases: - The insertion point index $i$ is out of bounds. - The element `nums[i]` is not equal to `target`. When either of these situations occurs, simply return $-1$. The code is shown below: ```src [file]{binary_search_edge}-[class]{}-[func]{binary_search_left_edge} ``` ## Finding the Right Boundary So how do we find the rightmost `target`? The most direct approach is to modify the code and replace the pointer shrinking operation in the `nums[m] == target` case. The code is omitted here; interested readers can implement it themselves. Below we introduce two more clever methods. ### Reusing Left Boundary Search In fact, we can use the function for finding the leftmost element to find the rightmost element. The specific method is: **Convert finding the rightmost `target` into finding the leftmost `target + 1`**. As shown in the figure below, after the search completes, pointer $i$ points to the leftmost `target + 1` (if it exists), while $j$ points to the rightmost `target`, **so we can simply return $j$**. ![Converting right boundary search to left boundary search](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png) Note that the returned insertion point is $i$, so we need to subtract $1$ from it to obtain $j$: ```src [file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge} ``` ### Converting to Element Search We know that when the array does not contain `target`, $i$ and $j$ will eventually point to the first elements greater than and less than `target`, respectively. Therefore, as shown in the figure below, we can construct an element that does not exist in the array to find the left and right boundaries. - Finding the leftmost `target`: Can be converted to finding `target - 0.5` and returning pointer $i$. - Finding the rightmost `target`: Can be converted to finding `target + 0.5` and returning pointer $j$. ![Converting boundary search to element search](binary_search_edge.assets/binary_search_edge_by_element.png) The code is omitted here, but the following two points are worth noting: - Since the given array does not contain decimals, we don't need to worry about how to handle equal cases. - Because this method introduces decimals, the variable `target` in the function needs to be changed to a floating-point type (Python does not require this change). ================================================ FILE: en/docs/chapter_searching/binary_search_insertion.md ================================================ # Binary Search Insertion Point Binary search can not only be used to search for target elements but also to solve many variant problems, such as searching for the insertion position of a target element. ## Case Without Duplicate Elements !!! question Given a sorted array `nums` of length $n$ and an element `target`, where the array contains no duplicate elements. Insert `target` into the array `nums` while maintaining its sorted order. If the array already contains the element `target`, insert it to its left. Return the index of `target` in the array after insertion. An example is shown in the figure below. ![Binary search insertion point example data](binary_search_insertion.assets/binary_search_insertion_example.png) If we want to reuse the binary search code from the previous section, we need to answer the following two questions. **Question 1**: When the array contains `target`, is the insertion point index the same as that element's index? The problem requires inserting `target` to the left of equal elements, which means the newly inserted `target` replaces the position of the original `target`. In other words, **when the array contains `target`, the insertion point index is the index of that `target`**. **Question 2**: When the array does not contain `target`, what is the insertion point index? Further consider the binary search process: When `nums[m] < target`, $i$ moves, which means pointer $i$ is approaching elements greater than or equal to `target`. Similarly, pointer $j$ is always approaching elements less than or equal to `target`. Therefore, when the binary search ends, we must have: $i$ points to the first element greater than `target`, and $j$ points to the first element less than `target`. **It's easy to see that when the array does not contain `target`, the insertion index is $i$**. The code is shown below: ```src [file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple} ``` ## Case with Duplicate Elements !!! question Based on the previous problem, assume the array may contain duplicate elements, with everything else remaining the same. Suppose there are multiple `target` elements in the array. Ordinary binary search can only return the index of one `target`, **and cannot determine how many `target` elements are to the left and right of that element**. The problem requires inserting the target element at the leftmost position, **so we need to find the index of the leftmost `target` in the array**. Initially, consider implementing this through the steps shown in the figure below: 1. Perform binary search to obtain the index of any `target`, denoted as $k$. 2. Starting from index $k$, perform linear traversal to the left, and return when the leftmost `target` is found. ![Linear search for insertion point of duplicate elements](binary_search_insertion.assets/binary_search_insertion_naive.png) Although this method works, it includes linear search, resulting in a time complexity of $O(n)$. When the array contains many duplicate `target` elements, this method is very inefficient. Now consider extending the binary search code. As shown in the figure below, the overall process remains unchanged: calculate the midpoint index $m$ in each round, then compare `target` with `nums[m]`, divided into the following cases: - When `nums[m] < target` or `nums[m] > target`, it means `target` has not been found yet, so use the ordinary binary search interval narrowing operation to **make pointers $i$ and $j$ approach `target`**. - When `nums[m] == target`, it means elements less than `target` are in the interval $[i, m - 1]$, so use $j = m - 1$ to narrow the interval, thereby **making pointer $j$ approach elements less than `target`**. After the loop completes, $i$ points to the leftmost `target`, and $j$ points to the first element less than `target`, **so index $i$ is the insertion point**. === "<1>" ![Steps for binary search insertion point of duplicate elements](binary_search_insertion.assets/binary_search_insertion_step1.png) === "<2>" ![binary_search_insertion_step2](binary_search_insertion.assets/binary_search_insertion_step2.png) === "<3>" ![binary_search_insertion_step3](binary_search_insertion.assets/binary_search_insertion_step3.png) === "<4>" ![binary_search_insertion_step4](binary_search_insertion.assets/binary_search_insertion_step4.png) === "<5>" ![binary_search_insertion_step5](binary_search_insertion.assets/binary_search_insertion_step5.png) === "<6>" ![binary_search_insertion_step6](binary_search_insertion.assets/binary_search_insertion_step6.png) === "<7>" ![binary_search_insertion_step7](binary_search_insertion.assets/binary_search_insertion_step7.png) === "<8>" ![binary_search_insertion_step8](binary_search_insertion.assets/binary_search_insertion_step8.png) Observe the following code: the operations for branches `nums[m] > target` and `nums[m] == target` are the same, so the two can be merged. Even so, we can still keep the conditional branches expanded, as the logic is clearer and more readable. ```src [file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion} ``` !!! tip The code in this section all uses the "closed interval" approach. Interested readers can implement the "left-closed right-open" approach themselves. Overall, binary search is simply about setting search targets for pointers $i$ and $j$ separately. The target could be a specific element (such as `target`) or a range of elements (such as elements less than `target`). Through continuous binary iterations, both pointers $i$ and $j$ gradually approach their preset targets. Ultimately, they either successfully find the answer or stop after crossing the boundaries. ================================================ FILE: en/docs/chapter_searching/index.md ================================================ # Searching ![Searching](../assets/covers/chapter_searching.jpg) !!! abstract Searching is an adventure into the unknown, where we may need to traverse every corner of the mysterious space, or we may be able to quickly lock onto the target. In this journey of discovery, each exploration may yield an unexpected answer. ================================================ FILE: en/docs/chapter_searching/replace_linear_by_hashing.md ================================================ # Hash Optimization Strategy In algorithm problems, **we often reduce the time complexity of algorithms by replacing linear search with hash-based search**. Let's use an algorithm problem to deepen our understanding. !!! question Given an integer array `nums` and a target element `target`, search for two elements in the array whose "sum" equals `target`, and return their array indices. Any solution will do. ## Linear Search: Trading Time for Space Consider directly traversing all possible combinations. As shown in the figure below, we open a two-layer loop and judge in each round whether the sum of two integers equals `target`. If so, return their indices. ![Linear search solution for two sum](replace_linear_by_hashing.assets/two_sum_brute_force.png) The code is shown below: ```src [file]{two_sum}-[class]{}-[func]{two_sum_brute_force} ``` This method has a time complexity of $O(n^2)$ and a space complexity of $O(1)$, which is very time-consuming with large data volumes. ## Hash-Based Search: Trading Space for Time Consider using a hash table where key-value pairs are array elements and element indices respectively. Loop through the array, performing the steps shown in the figure below in each round: 1. Check if the number `target - nums[i]` is in the hash table. If so, directly return the indices of these two elements. 2. Add the key-value pair `nums[i]` and index `i` to the hash table. === "<1>" ![Hash table solution for two sum](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) === "<2>" ![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png) === "<3>" ![two_sum_hashtable_step3](replace_linear_by_hashing.assets/two_sum_hashtable_step3.png) The implementation code is shown below, requiring only a single loop: ```src [file]{two_sum}-[class]{}-[func]{two_sum_hash_table} ``` This method reduces the time complexity from $O(n^2)$ to $O(n)$ through hash-based search, greatly improving runtime efficiency. Since an additional hash table needs to be maintained, the space complexity is $O(n)$. **Nevertheless, this method achieves a more balanced overall time-space efficiency, making it the optimal solution for this problem**. ================================================ FILE: en/docs/chapter_searching/searching_algorithm_revisited.md ================================================ # Searching Algorithms Revisited Searching algorithms are used to search for one or a group of elements that meet specific conditions in data structures (such as arrays, linked lists, trees, or graphs). Searching algorithms can be divided into the following two categories based on their implementation approach: - **Locating target elements by traversing the data structure**, such as traversing arrays, linked lists, trees, and graphs. - **Achieving efficient element search by utilizing data organization structure or prior information contained in the data**, such as binary search, hash-based search, and binary search tree search. It's not hard to see that these topics have all been covered in previous chapters, so searching algorithms are not unfamiliar to us. In this section, we will approach from a more systematic perspective and re-examine searching algorithms. ## Brute-Force Search Brute-force search locates target elements by traversing each element of the data structure. - "Linear search" is applicable to linear data structures such as arrays and linked lists. It starts from one end of the data structure and accesses elements one by one until the target element is found or the other end is reached without finding the target element. - "Breadth-first search" and "depth-first search" are two traversal strategies for graphs and trees. Breadth-first search starts from the initial node and searches layer by layer, visiting nodes from near to far. Depth-first search starts from the initial node, follows a path to the end, then backtracks and tries other paths until the entire data structure is traversed. The advantage of brute-force search is that it is simple and has good generality, **requiring no data preprocessing or additional data structures**. However, **the time complexity of such algorithms is $O(n)$**, where $n$ is the number of elements, so performance is poor when dealing with large amounts of data. ## Adaptive Search Adaptive search utilizes the unique properties of data (such as orderliness) to optimize the search process, thereby locating target elements more efficiently. - "Binary search" uses the orderliness of data to achieve efficient searching, applicable only to arrays. - "Hash-based search" uses hash tables to establish key-value pair mappings between search data and target data, thereby achieving query operations. - "Tree search" in specific tree structures (such as binary search trees), quickly eliminates nodes based on comparing node values to locate target elements. The advantage of such algorithms is high efficiency, **with time complexity reaching $O(\log n)$ or even $O(1)$**. However, **using these algorithms often requires data preprocessing**. For example, binary search requires pre-sorting the array, while hash-based search and tree search both require additional data structures, and maintaining these data structures also requires extra time and space overhead. !!! tip Adaptive search algorithms are often called lookup algorithms, **mainly used to quickly retrieve target elements in specific data structures**. ## Search Method Selection Given a dataset of size $n$, we can use linear search, binary search, tree search, hash-based search, and other methods to search for the target element. The working principles of each method are shown in the figure below. ![Multiple search strategies](searching_algorithm_revisited.assets/searching_algorithms.png) The operational efficiency and characteristics of the above methods are as follows:

Table   Comparison of search algorithm efficiency

| | Linear search | Binary search | Tree search | Hash-based search | | ------------------ | ------------- | --------------------- | --------------------------- | -------------------------- | | Search element | $O(n)$ | $O(\log n)$ | $O(\log n)$ | $O(1)$ | | Insert element | $O(1)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | | Delete element | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | | Extra space | $O(1)$ | $O(1)$ | $O(n)$ | $O(n)$ | | Data preprocessing | / | Sorting $O(n \log n)$ | Tree building $O(n \log n)$ | Hash table building $O(n)$ | | Data ordered | Unordered | Ordered | Ordered | Unordered | The choice of search algorithm also depends on data volume, search performance requirements, data query and update frequency, etc. **Linear search** - Good generality, requiring no data preprocessing operations. If we only need to query the data once, the data preprocessing time for the other three methods would be longer than linear search. - Suitable for small data volumes, where time complexity has less impact on efficiency. - Suitable for scenarios with high data update frequency, as this method does not require any additional data maintenance. **Binary search** - Suitable for large data volumes with stable efficiency performance, worst-case time complexity of $O(\log n)$. - Data volume cannot be too large, as storing arrays requires contiguous memory space. - Not suitable for scenarios with frequent data insertion and deletion, as maintaining a sorted array has high overhead. **Hash-based search** - Suitable for scenarios with high query performance requirements, with an average time complexity of $O(1)$. - Not suitable for scenarios requiring ordered data or range searches, as hash tables cannot maintain data orderliness. - High dependence on hash functions and hash collision handling strategies, with significant risk of performance degradation. - Not suitable for excessively large data volumes, as hash tables require extra space to minimize collisions and thus provide good query performance. **Tree search** - Suitable for massive data, as tree nodes are stored dispersedly in memory. - Suitable for scenarios requiring maintained ordered data or range searches. - During continuous node insertion and deletion, binary search trees may become skewed, degrading time complexity to $O(n)$. - If using AVL trees or red-black trees, all operations can run stably at $O(\log n)$ efficiency, but operations to maintain tree balance add extra overhead. ================================================ FILE: en/docs/chapter_searching/summary.md ================================================ # Summary ### Key Review - Binary search relies on data orderliness and progressively reduces the search interval by half through loops. It requires input data to be sorted and is only applicable to arrays or data structures based on array implementations. - Brute-force search locates data by traversing the data structure. Linear search is applicable to arrays and linked lists, while breadth-first search and depth-first search are applicable to graphs and trees. Such algorithms have good generality and require no data preprocessing, but have a relatively high time complexity of $O(n)$. - Hash-based search, tree search, and binary search are efficient search methods that can quickly locate target elements in specific data structures. Such algorithms are highly efficient with time complexity reaching $O(\log n)$ or even $O(1)$, but typically require additional data structures. - In practice, we need to analyze factors such as data scale, search performance requirements, and data query and update frequency to choose the appropriate search method. - Linear search is suitable for small-scale or frequently updated data; binary search is suitable for large-scale, sorted data; hash-based search is suitable for data with high query efficiency requirements and no need for range queries; tree search is suitable for large-scale dynamic data that needs to maintain order and support range queries. - Replacing linear search with hash-based search is a commonly used strategy to optimize runtime, reducing time complexity from $O(n)$ to $O(1)$. ================================================ FILE: en/docs/chapter_sorting/bubble_sort.md ================================================ # Bubble Sort Bubble sort (bubble sort) achieves sorting by continuously comparing and swapping adjacent elements. This process is like bubbles rising from the bottom to the top, hence the name bubble sort. As shown in the figure below, the bubbling process can be simulated using element swap operations: starting from the leftmost end of the array and traversing to the right, compare the size of adjacent elements, and if "left element > right element", swap them. After completing the traversal, the largest element will be moved to the rightmost end of the array. === "<1>" ![Simulating bubble using element swap operation](bubble_sort.assets/bubble_operation_step1.png) === "<2>" ![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png) === "<3>" ![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png) === "<4>" ![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png) === "<5>" ![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png) === "<6>" ![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png) === "<7>" ![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png) ## Algorithm Flow Assume the array has length $n$. The steps of bubble sort are shown in the figure below. 1. First, perform "bubbling" on $n$ elements, **swapping the largest element of the array to its correct position**. 2. Next, perform "bubbling" on the remaining $n - 1$ elements, **swapping the second largest element to its correct position**. 3. And so on. After $n - 1$ rounds of "bubbling", **the largest $n - 1$ elements have all been swapped to their correct positions**. 4. The only remaining element must be the smallest element, requiring no sorting, so the array sorting is complete. ![Bubble sort flow](bubble_sort.assets/bubble_sort_overview.png) Example code is as follows: ```src [file]{bubble_sort}-[class]{}-[func]{bubble_sort} ``` ## Efficiency Optimization We notice that if no swap operations are performed during a certain round of "bubbling", it means the array has already completed sorting and can directly return the result. Therefore, we can add a flag `flag` to monitor this situation and return immediately once it occurs. After optimization, the worst-case time complexity and average time complexity of bubble sort remain $O(n^2)$; but when the input array is completely ordered, the best-case time complexity can reach $O(n)$. ```src [file]{bubble_sort}-[class]{}-[func]{bubble_sort_with_flag} ``` ## Algorithm Characteristics - **Time complexity of $O(n^2)$, adaptive sorting**: The array lengths traversed in each round of "bubbling" are $n - 1$, $n - 2$, $\dots$, $2$, $1$, totaling $(n - 1) n / 2$. After introducing the `flag` optimization, the best-case time complexity can reach $O(n)$. - **Space complexity of $O(1)$, in-place sorting**: Pointers $i$ and $j$ use a constant amount of extra space. - **Stable sorting**: Since equal elements are not swapped during "bubbling". ================================================ FILE: en/docs/chapter_sorting/bucket_sort.md ================================================ # Bucket Sort The several sorting algorithms mentioned earlier all belong to "comparison-based sorting algorithms", which achieve sorting by comparing the size of elements. The time complexity of such sorting algorithms cannot exceed $O(n \log n)$. Next, we will explore several "non-comparison sorting algorithms", whose time complexity can reach linear order. Bucket sort (bucket sort) is a typical application of the divide-and-conquer strategy. It works by setting up buckets with size order, each bucket corresponding to a data range, evenly distributing data to each bucket; then, sorting within each bucket separately; finally, merging all data in the order of the buckets. ## Algorithm Flow Consider an array of length $n$, whose elements are floating-point numbers in the range $[0, 1)$. The flow of bucket sort is shown in the figure below. 1. Initialize $k$ buckets and distribute the $n$ elements into the $k$ buckets. 2. Sort each bucket separately (here we use the built-in sorting function of the programming language). 3. Merge the results in order from smallest to largest bucket. ![Bucket sort algorithm flow](bucket_sort.assets/bucket_sort_overview.png) The code is as follows: ```src [file]{bucket_sort}-[class]{}-[func]{bucket_sort} ``` ## Algorithm Characteristics Bucket sort is suitable for processing very large data volumes. For example, if the input data contains 1 million elements and system memory cannot load all the data at once, the data can be divided into 1000 buckets, each bucket sorted separately, and then the results merged. - **Time complexity of $O(n + k)$**: Assuming the elements are evenly distributed among the buckets, then the number of elements in each bucket is $\frac{n}{k}$. Assuming sorting a single bucket uses $O(\frac{n}{k} \log\frac{n}{k})$ time, then sorting all buckets uses $O(n \log\frac{n}{k})$ time. **When the number of buckets $k$ is relatively large, the time complexity approaches $O(n)$**. Merging results requires traversing all buckets and elements, taking $O(n + k)$ time. In the worst case, all data is distributed into one bucket, and sorting that bucket uses $O(n^2)$ time. - **Space complexity of $O(n + k)$, non-in-place sorting**: Additional space is required for $k$ buckets and a total of $n$ elements. - Whether bucket sort is stable depends on whether the algorithm for sorting elements within buckets is stable. ## How to Achieve Even Distribution Theoretically, bucket sort can achieve $O(n)$ time complexity. **The key is to evenly distribute elements to each bucket**, because real data is often not evenly distributed. For example, if we want to evenly distribute all products on Taobao into 10 buckets by price range, there may be very many products below 100 yuan and very few above 1000 yuan. If the price intervals are evenly divided into 10, the difference in the number of products in each bucket will be very large. To achieve even distribution, we can first set an approximate dividing line to roughly divide the data into 3 buckets. **After distribution is complete, continue dividing buckets with more products into 3 buckets until the number of elements in all buckets is roughly equal**. As shown in the figure below, this method essentially creates a recursion tree, with the goal of making the values of leaf nodes as even as possible. Of course, it is not necessary to divide the data into 3 buckets every round; the specific division method can be flexibly chosen according to data characteristics. ![Recursively dividing buckets](bucket_sort.assets/scatter_in_buckets_recursively.png) If we know the probability distribution of product prices in advance, **we can set the price dividing line for each bucket based on the data probability distribution**. It is worth noting that the data distribution does not necessarily need to be specifically calculated, but can also be approximated using a certain probability model based on data characteristics. As shown in the figure below, we assume that product prices follow a normal distribution, which allows us to reasonably set price intervals to evenly distribute products to each bucket. ![Dividing buckets based on probability distribution](bucket_sort.assets/scatter_in_buckets_distribution.png) ================================================ FILE: en/docs/chapter_sorting/counting_sort.md ================================================ # Counting Sort Counting sort (counting sort) achieves sorting by counting the number of elements, typically applied to integer arrays. ## Simple Implementation Let's start with a simple example. Given an array `nums` of length $n$, where the elements are all "non-negative integers", the overall flow of counting sort is shown in the figure below. 1. Traverse the array to find the largest number, denoted as $m$, and then create an auxiliary array `counter` of length $m + 1$. 2. **Use `counter` to count the number of occurrences of each number in `nums`**, where `counter[num]` corresponds to the number of occurrences of the number `num`. The counting method is simple: just traverse `nums` (let the current number be `num`), and increase `counter[num]` by $1$ in each round. 3. **Since each index of `counter` is naturally ordered, this is equivalent to all numbers being sorted**. Next, we traverse `counter` and fill in `nums` in ascending order based on the number of occurrences of each number. ![Counting sort flow](counting_sort.assets/counting_sort_overview.png) The code is as follows: ```src [file]{counting_sort}-[class]{}-[func]{counting_sort_naive} ``` !!! note "Connection between counting sort and bucket sort" From the perspective of bucket sort, we can regard each index of the counting array `counter` in counting sort as a bucket, and the process of counting quantities as distributing each element to the corresponding bucket. Essentially, counting sort is a special case of bucket sort for integer data. ## Complete Implementation Observant readers may have noticed that **if the input data is objects, step `3.` above becomes invalid**. Suppose the input data is product objects, and we want to sort the products by price (a member variable of the class), but the above algorithm can only give the sorting result of prices. So how can we obtain the sorting result of the original data? We first calculate the "prefix sum" of `counter`. As the name suggests, the prefix sum at index `i`, `prefix[i]`, equals the sum of the first `i` elements of the array: $$ \text{prefix}[i] = \sum_{j=0}^i \text{counter[j]} $$ **The prefix sum has a clear meaning: `prefix[num] - 1` represents the index of the last occurrence of element `num` in the result array `res`**. This information is very critical because it tells us where each element should appear in the result array. Next, we traverse each element `num` of the original array `nums` in reverse order, performing the following two steps in each iteration. 1. Fill `num` into the array `res` at index `prefix[num] - 1`. 2. Decrease the prefix sum `prefix[num]` by $1$ to get the index for the next placement of `num`. After the traversal is complete, the array `res` contains the sorted result, and finally `res` is used to overwrite the original array `nums`. The complete counting sort flow is shown in the figure below. === "<1>" ![Counting sort steps](counting_sort.assets/counting_sort_step1.png) === "<2>" ![counting_sort_step2](counting_sort.assets/counting_sort_step2.png) === "<3>" ![counting_sort_step3](counting_sort.assets/counting_sort_step3.png) === "<4>" ![counting_sort_step4](counting_sort.assets/counting_sort_step4.png) === "<5>" ![counting_sort_step5](counting_sort.assets/counting_sort_step5.png) === "<6>" ![counting_sort_step6](counting_sort.assets/counting_sort_step6.png) === "<7>" ![counting_sort_step7](counting_sort.assets/counting_sort_step7.png) === "<8>" ![counting_sort_step8](counting_sort.assets/counting_sort_step8.png) The implementation code of counting sort is as follows: ```src [file]{counting_sort}-[class]{}-[func]{counting_sort} ``` ## Algorithm Characteristics - **Time complexity of $O(n + m)$, non-adaptive sorting**: Involves traversing `nums` and traversing `counter`, both using linear time. Generally, $n \gg m$, and time complexity tends toward $O(n)$. - **Space complexity of $O(n + m)$, non-in-place sorting**: Uses arrays `res` and `counter` of lengths $n$ and $m$ respectively. - **Stable sorting**: Since elements are filled into `res` in a "right-to-left" order, traversing `nums` in reverse can avoid changing the relative positions of equal elements, thereby achieving stable sorting. In fact, traversing `nums` in forward order can also yield correct sorting results, but the result would be unstable. ## Limitations By this point, you might think counting sort is very clever, as it can achieve efficient sorting just by counting quantities. However, the prerequisites for using counting sort are relatively strict. **Counting sort is only suitable for non-negative integers**. If you want to apply it to other types of data, you need to ensure that the data can be converted to non-negative integers without changing the relative size relationships between elements. For example, for an integer array containing negative numbers, you can first add a constant to all numbers to convert them all to positive numbers, and then convert them back after sorting is complete. **Counting sort is suitable for situations where the data volume is large but the data range is small**. For example, in the above example, $m$ cannot be too large, otherwise it will occupy too much space. And when $n \ll m$, counting sort uses $O(m)$ time, which may be slower than $O(n \log n)$ sorting algorithms. ================================================ FILE: en/docs/chapter_sorting/heap_sort.md ================================================ # Heap Sort !!! tip Before reading this section, please ensure you have completed the "Heap" chapter. Heap sort (heap sort) is an efficient sorting algorithm based on the heap data structure. We can use the "build heap operation" and "element out-heap operation" that we have already learned to implement heap sort. 1. Input the array and build a min-heap, at which point the smallest element is at the heap top. 2. Continuously perform the out-heap operation, record the out-heap elements in sequence, and an ascending sorted sequence can be obtained. Although the above method is feasible, it requires an additional array to save the popped elements, which is quite wasteful of space. In practice, we usually use a more elegant implementation method. ## Algorithm Flow Assume the array length is $n$. The flow of heap sort is shown in the figure below. 1. Input the array and build a max-heap. After completion, the largest element is at the heap top. 2. Swap the heap top element (first element) with the heap bottom element (last element). After the swap is complete, reduce the heap length by $1$ and increase the count of sorted elements by $1$. 3. Starting from the heap top element, perform top-to-bottom heapify operation (sift down). After heapify is complete, the heap property is restored. 4. Loop through steps `2.` and `3.` After looping $n - 1$ rounds, the array sorting can be completed. !!! tip In fact, the element out-heap operation also includes steps `2.` and `3.`, with just an additional step to pop the element. === "<1>" ![Heap sort steps](heap_sort.assets/heap_sort_step1.png) === "<2>" ![heap_sort_step2](heap_sort.assets/heap_sort_step2.png) === "<3>" ![heap_sort_step3](heap_sort.assets/heap_sort_step3.png) === "<4>" ![heap_sort_step4](heap_sort.assets/heap_sort_step4.png) === "<5>" ![heap_sort_step5](heap_sort.assets/heap_sort_step5.png) === "<6>" ![heap_sort_step6](heap_sort.assets/heap_sort_step6.png) === "<7>" ![heap_sort_step7](heap_sort.assets/heap_sort_step7.png) === "<8>" ![heap_sort_step8](heap_sort.assets/heap_sort_step8.png) === "<9>" ![heap_sort_step9](heap_sort.assets/heap_sort_step9.png) === "<10>" ![heap_sort_step10](heap_sort.assets/heap_sort_step10.png) === "<11>" ![heap_sort_step11](heap_sort.assets/heap_sort_step11.png) === "<12>" ![heap_sort_step12](heap_sort.assets/heap_sort_step12.png) In the code implementation, we use the same top-to-bottom heapify function `sift_down()` from the "Heap" chapter. It is worth noting that since the heap length will decrease as the largest element is extracted, we need to add a length parameter $n$ to the `sift_down()` function to specify the current effective length of the heap. The code is as follows: ```src [file]{heap_sort}-[class]{}-[func]{heap_sort} ``` ## Algorithm Characteristics - **Time complexity of $O(n \log n)$, non-adaptive sorting**: The build heap operation uses $O(n)$ time. Extracting the largest element from the heap has a time complexity of $O(\log n)$, looping a total of $n - 1$ rounds. - **Space complexity of $O(1)$, in-place sorting**: A few pointer variables use $O(1)$ space. Element swapping and heapify operations are both performed on the original array. - **Non-stable sorting**: When swapping the heap top element and heap bottom element, the relative positions of equal elements may change. ================================================ FILE: en/docs/chapter_sorting/index.md ================================================ # Sorting ![Sorting](../assets/covers/chapter_sorting.jpg) !!! abstract Sorting is like a magic key that transforms chaos into order, enabling us to understand and process data more efficiently. Whether it's simple ascending order or complex categorized arrangements, sorting demonstrates the harmonious beauty of data. ================================================ FILE: en/docs/chapter_sorting/insertion_sort.md ================================================ # Insertion Sort Insertion sort (insertion sort) is a simple sorting algorithm that works very similarly to the process of manually organizing a deck of cards. Specifically, we select a base element from the unsorted interval, compare the element with elements in the sorted interval to its left one by one, and insert the element into the correct position. The figure below shows the operation flow of inserting an element into the array. Let the base element be `base`. We need to move all elements from the target index to `base` one position to the right, and then assign `base` to the target index. ![Single insertion operation](insertion_sort.assets/insertion_operation.png) ## Algorithm Flow The overall flow of insertion sort is shown in the figure below. 1. Initially, the first element of the array has completed sorting. 2. Select the second element of the array as `base`, and after inserting it into the correct position, **the first 2 elements of the array are sorted**. 3. Select the third element as `base`, and after inserting it into the correct position, **the first 3 elements of the array are sorted**. 4. And so on. In the last round, select the last element as `base`, and after inserting it into the correct position, **all elements are sorted**. ![Insertion sort flow](insertion_sort.assets/insertion_sort_overview.png) Example code is as follows: ```src [file]{insertion_sort}-[class]{}-[func]{insertion_sort} ``` ## Algorithm Characteristics - **Time complexity of $O(n^2)$, adaptive sorting**: In the worst case, each insertion operation requires loops of $n - 1$, $n-2$, $\dots$, $2$, $1$, summing to $(n - 1) n / 2$, so the time complexity is $O(n^2)$. When encountering ordered data, the insertion operation will terminate early. When the input array is completely ordered, insertion sort achieves the best-case time complexity of $O(n)$. - **Space complexity of $O(1)$, in-place sorting**: Pointers $i$ and $j$ use a constant amount of extra space. - **Stable sorting**: During the insertion operation process, we insert elements to the right of equal elements, without changing their order. ## Advantages of Insertion Sort The time complexity of insertion sort is $O(n^2)$, while the time complexity of quick sort, which we will learn about next, is $O(n \log n)$. Although insertion sort has a higher time complexity, **insertion sort is usually faster for smaller data volumes**. This conclusion is similar to the applicable situations of linear search and binary search. Algorithms like quick sort with $O(n \log n)$ complexity are sorting algorithms based on divide-and-conquer strategy and often contain more unit computation operations. When the data volume is small, $n^2$ and $n \log n$ are numerically close, and complexity does not dominate; the number of unit operations per round plays a decisive role. In fact, the built-in sorting functions in many programming languages (such as Java) adopt insertion sort. The general approach is: for long arrays, use sorting algorithms based on divide-and-conquer strategy, such as quick sort; for short arrays, directly use insertion sort. Although bubble sort, selection sort, and insertion sort all have a time complexity of $O(n^2)$, in actual situations, **insertion sort is used significantly more frequently than bubble sort and selection sort**, mainly for the following reasons. - Bubble sort is based on element swapping, requiring the use of a temporary variable, involving 3 unit operations; insertion sort is based on element assignment, requiring only 1 unit operation. Therefore, **the computational overhead of bubble sort is usually higher than that of insertion sort**. - Selection sort has a time complexity of $O(n^2)$ in any case. **If given a set of partially ordered data, insertion sort is usually more efficient than selection sort**. - Selection sort is unstable and cannot be applied to multi-level sorting. ================================================ FILE: en/docs/chapter_sorting/merge_sort.md ================================================ # Merge Sort Merge sort (merge sort) is a sorting algorithm based on the divide-and-conquer strategy, which includes the "divide" and "merge" phases shown in the figure below. 1. **Divide phase**: Recursively split the array from the midpoint, transforming the sorting problem of a long array into the sorting problems of shorter arrays. 2. **Merge phase**: When the sub-array length is 1, terminate the division and start merging, continuously merging two shorter sorted arrays into one longer sorted array until the process is complete. ![Divide and merge phases of merge sort](merge_sort.assets/merge_sort_overview.png) ## Algorithm Flow As shown in the figure below, the "divide phase" recursively splits the array from the midpoint into two sub-arrays from top to bottom. 1. Calculate the array midpoint `mid`, recursively divide the left sub-array (interval `[left, mid]`) and right sub-array (interval `[mid + 1, right]`). 2. Recursively execute step `1.` until the sub-array interval length is 1, then terminate. The "merge phase" merges the left sub-array and right sub-array into a sorted array from bottom to top. Note that merging starts from sub-arrays of length 1, and each sub-array in the merge phase is sorted. === "<1>" ![Merge sort steps](merge_sort.assets/merge_sort_step1.png) === "<2>" ![merge_sort_step2](merge_sort.assets/merge_sort_step2.png) === "<3>" ![merge_sort_step3](merge_sort.assets/merge_sort_step3.png) === "<4>" ![merge_sort_step4](merge_sort.assets/merge_sort_step4.png) === "<5>" ![merge_sort_step5](merge_sort.assets/merge_sort_step5.png) === "<6>" ![merge_sort_step6](merge_sort.assets/merge_sort_step6.png) === "<7>" ![merge_sort_step7](merge_sort.assets/merge_sort_step7.png) === "<8>" ![merge_sort_step8](merge_sort.assets/merge_sort_step8.png) === "<9>" ![merge_sort_step9](merge_sort.assets/merge_sort_step9.png) === "<10>" ![merge_sort_step10](merge_sort.assets/merge_sort_step10.png) It can be observed that the recursive order of merge sort is consistent with the post-order traversal of a binary tree. - **Post-order traversal**: First recursively traverse the left subtree, then recursively traverse the right subtree, and finally process the root node. - **Merge sort**: First recursively process the left sub-array, then recursively process the right sub-array, and finally perform the merge. The implementation of merge sort is shown in the code below. Note that the interval to be merged in `nums` is `[left, right]`, while the corresponding interval in `tmp` is `[0, right - left]`. ```src [file]{merge_sort}-[class]{}-[func]{merge_sort} ``` ## Algorithm Characteristics - **Time complexity of $O(n \log n)$, non-adaptive sorting**: The division produces a recursion tree of height $\log n$, and the total number of merge operations at each level is $n$, so the overall time complexity is $O(n \log n)$. - **Space complexity of $O(n)$, non-in-place sorting**: The recursion depth is $\log n$, using $O(\log n)$ size of stack frame space. The merge operation requires the aid of an auxiliary array, using $O(n)$ size of additional space. - **Stable sorting**: In the merge process, the order of equal elements remains unchanged. ## Linked List Sorting For linked lists, merge sort has significant advantages over other sorting algorithms, **and can optimize the space complexity of linked list sorting tasks to $O(1)$**. - **Divide phase**: "Iteration" can be used instead of "recursion" to implement linked list division work, thus saving the stack frame space used by recursion. - **Merge phase**: In linked lists, node insertion and deletion operations can be achieved by just changing references (pointers), so there is no need to create additional linked lists during the merge phase (merging two short ordered linked lists into one long ordered linked list). The specific implementation details are quite complex, and interested readers can consult related materials for learning. ================================================ FILE: en/docs/chapter_sorting/quick_sort.md ================================================ # Quick Sort Quick sort (quick sort) is a sorting algorithm based on the divide-and-conquer strategy, which operates efficiently and is widely applied. The core operation of quick sort is "sentinel partitioning", which aims to: select a certain element in the array as the "pivot", move all elements smaller than the pivot to its left, and move elements larger than the pivot to its right. Specifically, the flow of sentinel partitioning is shown in the figure below. 1. Select the leftmost element of the array as the pivot, and initialize two pointers `i` and `j` pointing to the two ends of the array. 2. Set up a loop in which `i` (`j`) is used in each round to find the first element larger (smaller) than the pivot, and then swap these two elements. 3. Loop through step `2.` until `i` and `j` meet, and finally swap the pivot to the boundary line of the two sub-arrays. === "<1>" ![Sentinel partitioning steps](quick_sort.assets/pivot_division_step1.png) === "<2>" ![pivot_division_step2](quick_sort.assets/pivot_division_step2.png) === "<3>" ![pivot_division_step3](quick_sort.assets/pivot_division_step3.png) === "<4>" ![pivot_division_step4](quick_sort.assets/pivot_division_step4.png) === "<5>" ![pivot_division_step5](quick_sort.assets/pivot_division_step5.png) === "<6>" ![pivot_division_step6](quick_sort.assets/pivot_division_step6.png) === "<7>" ![pivot_division_step7](quick_sort.assets/pivot_division_step7.png) === "<8>" ![pivot_division_step8](quick_sort.assets/pivot_division_step8.png) === "<9>" ![pivot_division_step9](quick_sort.assets/pivot_division_step9.png) After sentinel partitioning is complete, the original array is divided into three parts: left sub-array, pivot, right sub-array, satisfying "any element in left sub-array $\leq$ pivot $\leq$ any element in right sub-array". Therefore, we next only need to sort these two sub-arrays. !!! note "Divide-and-conquer strategy of quick sort" The essence of sentinel partitioning is to simplify the sorting problem of a longer array into the sorting problems of two shorter arrays. ```src [file]{quick_sort}-[class]{quick_sort}-[func]{partition} ``` ## Algorithm Flow The overall flow of quick sort is shown in the figure below. 1. First, perform one "sentinel partitioning" on the original array to obtain the unsorted left sub-array and right sub-array. 2. Then, recursively perform "sentinel partitioning" on the left sub-array and right sub-array respectively. 3. Continue recursively until the sub-array length is 1, at which point sorting of the entire array is complete. ![Quick sort flow](quick_sort.assets/quick_sort_overview.png) ```src [file]{quick_sort}-[class]{quick_sort}-[func]{quick_sort} ``` ## Algorithm Characteristics - **Time complexity of $O(n \log n)$, non-adaptive sorting**: In the average case, the number of recursive levels of sentinel partitioning is $\log n$, and the total number of loops at each level is $n$, using $O(n \log n)$ time overall. In the worst case, each round of sentinel partitioning divides an array of length $n$ into two sub-arrays of length $0$ and $n - 1$, at which point the number of recursive levels reaches $n$, the number of loops at each level is $n$, and the total time used is $O(n^2)$. - **Space complexity of $O(n)$, in-place sorting**: In the case where the input array is completely reversed, the worst recursive depth reaches $n$, using $O(n)$ stack frame space. The sorting operation is performed on the original array without the aid of an additional array. - **Non-stable sorting**: In the last step of sentinel partitioning, the pivot may be swapped to the right of equal elements. ## Why Is Quick Sort Fast From the name, we can see that quick sort should have certain advantages in terms of efficiency. Although the average time complexity of quick sort is the same as "merge sort" and "heap sort", quick sort is usually more efficient, mainly for the following reasons. - **The probability of the worst case occurring is very low**: Although the worst-case time complexity of quick sort is $O(n^2)$, which is not as stable as merge sort, in the vast majority of cases, quick sort can run with a time complexity of $O(n \log n)$. - **High cache utilization**: When performing sentinel partitioning operations, the system can load the entire sub-array into the cache, so element access efficiency is relatively high. Algorithms like "heap sort" require jump-style access to elements, thus lacking this characteristic. - **Small constant coefficient of complexity**: Among the three algorithms mentioned above, quick sort has the smallest total number of operations such as comparisons, assignments, and swaps. This is similar to the reason why "insertion sort" is faster than "bubble sort". ## Pivot Optimization **Quick sort may have reduced time efficiency for certain inputs**. Take an extreme example: suppose the input array is completely reversed. Since we select the leftmost element as the pivot, after sentinel partitioning is complete, the pivot is swapped to the rightmost end of the array, causing the left sub-array length to be $n - 1$ and the right sub-array length to be $0$. If we recurse down like this, each round of sentinel partitioning will have a sub-array length of $0$, the divide-and-conquer strategy fails, and quick sort degrades to a form approximate to "bubble sort". To avoid this situation as much as possible, **we can optimize the pivot selection strategy in sentinel partitioning**. For example, we can randomly select an element as the pivot. However, if luck is not good and we select a non-ideal pivot every time, efficiency is still not satisfactory. It should be noted that programming languages usually generate "pseudo-random numbers". If we construct a specific test case for a pseudo-random number sequence, the efficiency of quick sort may still degrade. For further improvement, we can select three candidate elements in the array (usually the first, last, and middle elements of the array), **and use the median of these three candidate elements as the pivot**. In this way, the probability that the pivot is "neither too small nor too large" will be greatly increased. Of course, we can also select more candidate elements to further improve the robustness of the algorithm. After adopting this method, the probability of time complexity degrading to $O(n^2)$ is greatly reduced. Example code is as follows: ```src [file]{quick_sort}-[class]{quick_sort_median}-[func]{partition} ``` ## Recursive Depth Optimization **For certain inputs, quick sort may occupy more space**. Taking a completely ordered input array as an example, let the length of the sub-array in recursion be $m$. Each round of sentinel partitioning will produce a left sub-array of length $0$ and a right sub-array of length $m - 1$, which means that the problem scale reduced per recursive call is very small (only one element is reduced), and the height of the recursion tree will reach $n - 1$, at which point $O(n)$ size of stack frame space is required. To prevent the accumulation of stack frame space, we can compare the lengths of the two sub-arrays after each round of sentinel sorting is complete, **and only recurse on the shorter sub-array**. Since the length of the shorter sub-array will not exceed $n / 2$, this method can ensure that the recursion depth does not exceed $\log n$, thus optimizing the worst-case space complexity to $O(\log n)$. The code is as follows: ```src [file]{quick_sort}-[class]{quick_sort_tail_call}-[func]{quick_sort} ``` ================================================ FILE: en/docs/chapter_sorting/radix_sort.md ================================================ # Radix Sort The previous section introduced counting sort, which is suitable for situations where the data volume $n$ is large but the data range $m$ is small. Suppose we need to sort $n = 10^6$ student IDs, and the student ID is an 8-digit number, which means the data range $m = 10^8$ is very large. Using counting sort would require allocating a large amount of memory space, whereas radix sort can avoid this situation. Radix sort (radix sort) has a core idea consistent with counting sort, which also achieves sorting by counting quantities. Building on this, radix sort utilizes the progressive relationship between the digits of numbers, sorting each digit in turn to obtain the final sorting result. ## Algorithm Flow Taking student ID data as an example, assume the lowest digit is the $1$st digit and the highest digit is the $8$th digit. The flow of radix sort is shown in the figure below. 1. Initialize the digit $k = 1$. 2. Perform "counting sort" on the $k$th digit of the student IDs. After completion, the data will be sorted from smallest to largest according to the $k$th digit. 3. Increase $k$ by $1$, then return to step `2.` and continue iterating until all digits are sorted, at which point the process ends. ![Radix sort algorithm flow](radix_sort.assets/radix_sort_overview.png) Below we analyze the code implementation. For a $d$-base number $x$, to get its $k$th digit $x_k$, the following calculation formula can be used: $$ x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d $$ Where $\lfloor a \rfloor$ denotes rounding down the floating-point number $a$, and $\bmod \: d$ denotes taking the modulo (remainder) with respect to $d$. For student ID data, $d = 10$ and $k \in [1, 8]$. Additionally, we need to slightly modify the counting sort code to make it sort based on the $k$th digit of the number: ```src [file]{radix_sort}-[class]{}-[func]{radix_sort} ``` !!! question "Why start sorting from the lowest digit?" In successive sorting rounds, the result of a later round will override the result of an earlier round. For example, if the first round result is $a < b$, while the second round result is $a > b$, then the second round's result will replace the first round's result. Since higher-order digits have higher priority than lower-order digits, we should sort the lower digits first and then sort the higher digits. ## Algorithm Characteristics Compared to counting sort, radix sort is suitable for larger numerical ranges, **but the prerequisite is that the data must be representable in a fixed number of digits, and the number of digits should not be too large**. For example, floating-point numbers are not suitable for radix sort because their number of digits $k$ may be too large, potentially leading to time complexity $O(nk) \gg O(n^2)$. - **Time complexity of $O(nk)$, non-adaptive sorting**: Let the data volume be $n$, the data be in base $d$, and the maximum number of digits be $k$. Then performing counting sort on a certain digit uses $O(n + d)$ time, and sorting all $k$ digits uses $O((n + d)k)$ time. Typically, both $d$ and $k$ are relatively small, and the time complexity approaches $O(n)$. - **Space complexity of $O(n + d)$, non-in-place sorting**: Same as counting sort, radix sort requires auxiliary arrays `res` and `counter` of lengths $n$ and $d$. - **Stable sorting**: When counting sort is stable, radix sort is also stable; when counting sort is unstable, radix sort cannot guarantee obtaining correct sorting results. ================================================ FILE: en/docs/chapter_sorting/selection_sort.md ================================================ # Selection Sort Selection sort (selection sort) works very simply: it opens a loop, and in each round, selects the smallest element from the unsorted interval and places it at the end of the sorted interval. Assume the array has length $n$. The algorithm flow of selection sort is shown in the figure below. 1. Initially, all elements are unsorted, i.e., the unsorted (index) interval is $[0, n-1]$. 2. Select the smallest element in the interval $[0, n-1]$ and swap it with the element at index $0$. After completion, the first element of the array is sorted. 3. Select the smallest element in the interval $[1, n-1]$ and swap it with the element at index $1$. After completion, the first 2 elements of the array are sorted. 4. And so on. After $n - 1$ rounds of selection and swapping, the first $n - 1$ elements of the array are sorted. 5. The only remaining element must be the largest element, requiring no sorting, so the array sorting is complete. === "<1>" ![Selection sort steps](selection_sort.assets/selection_sort_step1.png) === "<2>" ![selection_sort_step2](selection_sort.assets/selection_sort_step2.png) === "<3>" ![selection_sort_step3](selection_sort.assets/selection_sort_step3.png) === "<4>" ![selection_sort_step4](selection_sort.assets/selection_sort_step4.png) === "<5>" ![selection_sort_step5](selection_sort.assets/selection_sort_step5.png) === "<6>" ![selection_sort_step6](selection_sort.assets/selection_sort_step6.png) === "<7>" ![selection_sort_step7](selection_sort.assets/selection_sort_step7.png) === "<8>" ![selection_sort_step8](selection_sort.assets/selection_sort_step8.png) === "<9>" ![selection_sort_step9](selection_sort.assets/selection_sort_step9.png) === "<10>" ![selection_sort_step10](selection_sort.assets/selection_sort_step10.png) === "<11>" ![selection_sort_step11](selection_sort.assets/selection_sort_step11.png) In the code, we use $k$ to record the smallest element within the unsorted interval: ```src [file]{selection_sort}-[class]{}-[func]{selection_sort} ``` ## Algorithm Characteristics - **Time complexity of $O(n^2)$, non-adaptive sorting**: The outer loop has $n - 1$ rounds in total. The length of the unsorted interval in the first round is $n$, and the length of the unsorted interval in the last round is $2$. That is, each round of the outer loop contains $n$, $n - 1$, $\dots$, $3$, $2$ inner loop iterations, summing to $\frac{(n - 1)(n + 2)}{2}$. - **Space complexity of $O(1)$, in-place sorting**: Pointers $i$ and $j$ use a constant amount of extra space. - **Non-stable sorting**: As shown in the figure below, element `nums[i]` may be swapped to the right of an element equal to it, causing a change in their relative order. ![Selection sort non-stability example](selection_sort.assets/selection_sort_instability.png) ================================================ FILE: en/docs/chapter_sorting/sorting_algorithm.md ================================================ # Sorting Algorithm Sorting algorithm (sorting algorithm) is used to arrange a group of data in a specific order. Sorting algorithms have extensive applications because ordered data can usually be searched, analyzed, and processed more efficiently. As shown in the figure below, data types in sorting algorithms can be integers, floating-point numbers, characters, or strings, etc. The sorting criterion can be set according to requirements, such as numerical size, character ASCII code order, or custom rules. ![Data type and criterion examples](sorting_algorithm.assets/sorting_examples.png) ## Evaluation Dimensions **Execution efficiency**: We expect the time complexity of sorting algorithms to be as low as possible, with a smaller total number of operations (reducing the constant factor in time complexity). For large data volumes, execution efficiency is particularly important. **In-place property**: As the name implies, in-place sorting achieves sorting by operating directly on the original array without requiring additional auxiliary arrays, thus saving memory. Typically, in-place sorting involves fewer data movement operations and runs faster. **Stability**: Stable sorting ensures that the relative order of equal elements in the array does not change after sorting is completed. Stable sorting is a necessary condition for multi-level sorting scenarios. Suppose we have a table storing student information, where column 1 and column 2 are name and age, respectively. In this case, unstable sorting may cause the ordered nature of the input data to be lost: ```shell # Input Data Is Sorted by Name # (name, age) ('A', 19) ('B', 18) ('C', 21) ('D', 19) ('E', 23) # Assuming We Use an Unstable Sorting Algorithm to Sort the List by Age, # In the Result, the Relative Positions of ('D', 19) and ('A', 19) Are Changed, # And the Property That the Input Data Is Sorted by Name Is Lost ('B', 18) ('D', 19) ('A', 19) ('C', 21) ('E', 23) ``` **Adaptability**: Adaptive sorting can utilize the existing order information in the input data to reduce the amount of computation, achieving better time efficiency. The best-case time complexity of adaptive sorting algorithms is typically better than the average time complexity. **Comparison-based or not**: Comparison-based sorting relies on comparison operators ($<$, $=$, $>$) to determine the relative order of elements, thereby sorting the entire array, with a theoretical optimal time complexity of $O(n \log n)$. Non-comparison sorting does not use comparison operators and can achieve a time complexity of $O(n)$, but its versatility is relatively limited. ## Ideal Sorting Algorithm **Fast execution, in-place, stable, adaptive, good versatility**. Clearly, no sorting algorithm has been discovered to date that combines all of these characteristics. Therefore, when selecting a sorting algorithm, it is necessary to decide based on the specific characteristics of the data and the requirements of the problem. Next, we will learn about various sorting algorithms together and analyze the advantages and disadvantages of each sorting algorithm based on the above evaluation dimensions. ================================================ FILE: en/docs/chapter_sorting/summary.md ================================================ # Summary ### Key Review - Bubble sort achieves sorting by swapping adjacent elements. By adding a flag to enable early return, we can optimize the best-case time complexity of bubble sort to $O(n)$. - Insertion sort completes sorting by inserting elements from the unsorted interval into the correct position in the sorted interval each round. Although the time complexity of insertion sort is $O(n^2)$, it is very popular in small data volume sorting tasks because it involves relatively few unit operations. - Quick sort is implemented based on sentinel partitioning operations. In sentinel partitioning, it is possible to select the worst pivot every time, causing the time complexity to degrade to $O(n^2)$. Introducing median pivot or random pivot can reduce the probability of such degradation. By preferentially recursing on the shorter sub-interval, the recursion depth can be effectively reduced, optimizing the space complexity to $O(\log n)$. - Merge sort includes two phases: divide and merge, which typically embody the divide-and-conquer strategy. In merge sort, sorting an array requires creating auxiliary arrays, with a space complexity of $O(n)$; however, the space complexity of sorting a linked list can be optimized to $O(1)$. - Bucket sort consists of three steps: distributing data into buckets, sorting within buckets, and merging results. It also embodies the divide-and-conquer strategy and is suitable for very large data volumes. The key to bucket sort is distributing data evenly. - Counting sort is a special case of bucket sort, which achieves sorting by counting the number of occurrences of data. Counting sort is suitable for situations where the data volume is large but the data range is limited, and requires that data can be converted to positive integers. - Radix sort achieves data sorting by sorting digit by digit, requiring that data can be represented as fixed-digit numbers. - Overall, we hope to find a sorting algorithm that is efficient, stable, in-place, and adaptive, with good versatility. However, just like other data structures and algorithms, no sorting algorithm has been found so far that simultaneously possesses all these characteristics. In practical applications, we need to select the appropriate sorting algorithm based on the specific characteristics of the data. - The figure below compares mainstream sorting algorithms in terms of efficiency, stability, in-place property, and adaptability. ![Sorting algorithm comparison](summary.assets/sorting_algorithms_comparison.png) ### Q & A **Q**: In what situations is the stability of sorting algorithms necessary? In reality, we may sort based on a certain attribute of objects. For example, students have two attributes: name and height. We want to implement multi-level sorting: first sort by name to get `(A, 180) (B, 185) (C, 170) (D, 170)`; then sort by height. Because the sorting algorithm is unstable, we may get `(D, 170) (C, 170) (A, 180) (B, 185)`. It can be seen that the positions of students D and C have been swapped, and the orderliness of names has been disrupted, which is something we don't want to see. **Q**: Can the order of "searching from right to left" and "searching from left to right" in sentinel partitioning be swapped? No. When we use the leftmost element as the pivot, we must first "search from right to left" and then "search from left to right". This conclusion is somewhat counterintuitive; let's analyze the reason. The last step of sentinel partitioning `partition()` is to swap `nums[left]` and `nums[i]`. After the swap is complete, the elements to the left of the pivot are all `<=` the pivot, **which requires that `nums[left] >= nums[i]` must hold before the last swap**. Suppose we first "search from left to right", then if we cannot find an element larger than the pivot, **we will exit the loop when `i == j`, at which point it may be that `nums[j] == nums[i] > nums[left]`**. In other words, the last swap operation will swap an element larger than the pivot to the leftmost end of the array, causing sentinel partitioning to fail. For example, given the array `[0, 0, 0, 0, 1]`, if we first "search from left to right", the array after sentinel partitioning is `[1, 0, 0, 0, 0]`, which is incorrect. Thinking deeper, if we select `nums[right]` as the pivot, then it's exactly the opposite - we must first "search from left to right". **Q**: Regarding the optimization of recursion depth in quick sort, why can selecting the shorter array ensure that the recursion depth does not exceed $\log n$? The recursion depth is the number of currently unreturned recursive methods. Each round of sentinel partitioning divides the original array into two sub-arrays. After recursion depth optimization, the length of the sub-array to be recursively processed is at most half of the original array length. Assuming the worst case is always half the length, the final recursion depth will be $\log n$. Reviewing the original quick sort, we may continuously recurse on the longer array. In the worst case, it would be $n$, $n - 1$, $\dots$, $2$, $1$, with a recursion depth of $n$. Recursion depth optimization can avoid this situation. **Q**: When all elements in the array are equal, is the time complexity of quick sort $O(n^2)$? How should this degenerate case be handled? Yes. For this situation, consider partitioning the array into three parts through sentinel partitioning: less than, equal to, and greater than the pivot. Only recursively process the less than and greater than parts. Under this method, an array where all input elements are equal can complete sorting in just one round of sentinel partitioning. **Q**: Why is the worst-case time complexity of bucket sort $O(n^2)$? In the worst case, all elements are distributed into the same bucket. If we use an $O(n^2)$ algorithm to sort these elements, the time complexity will be $O(n^2)$. ================================================ FILE: en/docs/chapter_stack_and_queue/deque.md ================================================ # Deque In a queue, we can only remove elements from the front or add elements at the rear. As shown in the figure below, a double-ended queue (deque) provides greater flexibility, allowing the addition or removal of elements at both the front and rear. ![Operations of deque](deque.assets/deque_operations.png) ## Common Deque Operations The common operations on a deque are shown in the table below. The specific method names depend on the programming language used.

Table   Efficiency of Deque Operations

| Method | Description | Time Complexity | | -------------- | ------------------------- | --------------- | | `push_first()` | Add element to front | $O(1)$ | | `push_last()` | Add element to rear | $O(1)$ | | `pop_first()` | Remove front element | $O(1)$ | | `pop_last()` | Remove rear element | $O(1)$ | | `peek_first()` | Access front element | $O(1)$ | | `peek_last()` | Access rear element | $O(1)$ | Similarly, we can directly use the deque classes already implemented in programming languages: === "Python" ```python title="deque.py" from collections import deque # Initialize deque deq: deque[int] = deque() # Enqueue elements deq.append(2) # Add to rear deq.append(5) deq.append(4) deq.appendleft(3) # Add to front deq.appendleft(1) # Access elements front: int = deq[0] # Front element rear: int = deq[-1] # Rear element # Dequeue elements pop_front: int = deq.popleft() # Front element dequeue pop_rear: int = deq.pop() # Rear element dequeue # Get deque length size: int = len(deq) # Check if deque is empty is_empty: bool = len(deq) == 0 ``` === "C++" ```cpp title="deque.cpp" /* Initialize deque */ deque deque; /* Enqueue elements */ deque.push_back(2); // Add to rear deque.push_back(5); deque.push_back(4); deque.push_front(3); // Add to front deque.push_front(1); /* Access elements */ int front = deque.front(); // Front element int back = deque.back(); // Rear element /* Dequeue elements */ deque.pop_front(); // Front element dequeue deque.pop_back(); // Rear element dequeue /* Get deque length */ int size = deque.size(); /* Check if deque is empty */ bool empty = deque.empty(); ``` === "Java" ```java title="deque.java" /* Initialize deque */ Deque deque = new LinkedList<>(); /* Enqueue elements */ deque.offerLast(2); // Add to rear deque.offerLast(5); deque.offerLast(4); deque.offerFirst(3); // Add to front deque.offerFirst(1); /* Access elements */ int peekFirst = deque.peekFirst(); // Front element int peekLast = deque.peekLast(); // Rear element /* Dequeue elements */ int popFirst = deque.pollFirst(); // Front element dequeue int popLast = deque.pollLast(); // Rear element dequeue /* Get deque length */ int size = deque.size(); /* Check if deque is empty */ boolean isEmpty = deque.isEmpty(); ``` === "C#" ```csharp title="deque.cs" /* Initialize deque */ // In C#, use LinkedList as a deque LinkedList deque = new(); /* Enqueue elements */ deque.AddLast(2); // Add to rear deque.AddLast(5); deque.AddLast(4); deque.AddFirst(3); // Add to front deque.AddFirst(1); /* Access elements */ int peekFirst = deque.First.Value; // Front element int peekLast = deque.Last.Value; // Rear element /* Dequeue elements */ deque.RemoveFirst(); // Front element dequeue deque.RemoveLast(); // Rear element dequeue /* Get deque length */ int size = deque.Count; /* Check if deque is empty */ bool isEmpty = deque.Count == 0; ``` === "Go" ```go title="deque_test.go" /* Initialize deque */ // In Go, use list as a deque deque := list.New() /* Enqueue elements */ deque.PushBack(2) // Add to rear deque.PushBack(5) deque.PushBack(4) deque.PushFront(3) // Add to front deque.PushFront(1) /* Access elements */ front := deque.Front() // Front element rear := deque.Back() // Rear element /* Dequeue elements */ deque.Remove(front) // Front element dequeue deque.Remove(rear) // Rear element dequeue /* Get deque length */ size := deque.Len() /* Check if deque is empty */ isEmpty := deque.Len() == 0 ``` === "Swift" ```swift title="deque.swift" /* Initialize deque */ // Swift does not have a built-in deque class, can use Array as a deque var deque: [Int] = [] /* Enqueue elements */ deque.append(2) // Add to rear deque.append(5) deque.append(4) deque.insert(3, at: 0) // Add to front deque.insert(1, at: 0) /* Access elements */ let peekFirst = deque.first! // Front element let peekLast = deque.last! // Rear element /* Dequeue elements */ // When using Array simulation, popFirst has O(n) complexity let popFirst = deque.removeFirst() // Front element dequeue let popLast = deque.removeLast() // Rear element dequeue /* Get deque length */ let size = deque.count /* Check if deque is empty */ let isEmpty = deque.isEmpty ``` === "JS" ```javascript title="deque.js" /* Initialize deque */ // JavaScript does not have a built-in deque, can only use Array as a deque const deque = []; /* Enqueue elements */ deque.push(2); deque.push(5); deque.push(4); // Please note that since it's an array, unshift() has O(n) time complexity deque.unshift(3); deque.unshift(1); /* Access elements */ const peekFirst = deque[0]; const peekLast = deque[deque.length - 1]; /* Dequeue elements */ // Please note that since it's an array, shift() has O(n) time complexity const popFront = deque.shift(); const popBack = deque.pop(); /* Get deque length */ const size = deque.length; /* Check if deque is empty */ const isEmpty = size === 0; ``` === "TS" ```typescript title="deque.ts" /* Initialize deque */ // TypeScript does not have a built-in deque, can only use Array as a deque const deque: number[] = []; /* Enqueue elements */ deque.push(2); deque.push(5); deque.push(4); // Please note that since it's an array, unshift() has O(n) time complexity deque.unshift(3); deque.unshift(1); /* Access elements */ const peekFirst: number = deque[0]; const peekLast: number = deque[deque.length - 1]; /* Dequeue elements */ // Please note that since it's an array, shift() has O(n) time complexity const popFront: number = deque.shift() as number; const popBack: number = deque.pop() as number; /* Get deque length */ const size: number = deque.length; /* Check if deque is empty */ const isEmpty: boolean = size === 0; ``` === "Dart" ```dart title="deque.dart" /* Initialize deque */ // In Dart, Queue is defined as a deque Queue deque = Queue(); /* Enqueue elements */ deque.addLast(2); // Add to rear deque.addLast(5); deque.addLast(4); deque.addFirst(3); // Add to front deque.addFirst(1); /* Access elements */ int peekFirst = deque.first; // Front element int peekLast = deque.last; // Rear element /* Dequeue elements */ int popFirst = deque.removeFirst(); // Front element dequeue int popLast = deque.removeLast(); // Rear element dequeue /* Get deque length */ int size = deque.length; /* Check if deque is empty */ bool isEmpty = deque.isEmpty; ``` === "Rust" ```rust title="deque.rs" /* Initialize deque */ let mut deque: VecDeque = VecDeque::new(); /* Enqueue elements */ deque.push_back(2); // Add to rear deque.push_back(5); deque.push_back(4); deque.push_front(3); // Add to front deque.push_front(1); /* Access elements */ if let Some(front) = deque.front() { // Front element } if let Some(rear) = deque.back() { // Rear element } /* Dequeue elements */ if let Some(pop_front) = deque.pop_front() { // Front element dequeue } if let Some(pop_rear) = deque.pop_back() { // Rear element dequeue } /* Get deque length */ let size = deque.len(); /* Check if deque is empty */ let is_empty = deque.is_empty(); ``` === "C" ```c title="deque.c" // C does not provide a built-in deque ``` === "Kotlin" ```kotlin title="deque.kt" /* Initialize deque */ val deque = LinkedList() /* Enqueue elements */ deque.offerLast(2) // Add to rear deque.offerLast(5) deque.offerLast(4) deque.offerFirst(3) // Add to front deque.offerFirst(1) /* Access elements */ val peekFirst = deque.peekFirst() // Front element val peekLast = deque.peekLast() // Rear element /* Dequeue elements */ val popFirst = deque.pollFirst() // Front element dequeue val popLast = deque.pollLast() // Rear element dequeue /* Get deque length */ val size = deque.size /* Check if deque is empty */ val isEmpty = deque.isEmpty() ``` === "Ruby" ```ruby title="deque.rb" # Initialize deque # Ruby does not have a built-in deque, can only use Array as a deque deque = [] # Enqueue elements deque << 2 deque << 5 deque << 4 # Please note that since it's an array, Array#unshift has O(n) time complexity deque.unshift(3) deque.unshift(1) # Access elements peek_first = deque.first peek_last = deque.last # Dequeue elements # Please note that since it's an array, Array#shift has O(n) time complexity pop_front = deque.shift pop_back = deque.pop # Get deque length size = deque.length # Check if deque is empty is_empty = size.zero? ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22,%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22,%20pop_rear%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## Deque Implementation * The implementation of a deque is similar to that of a queue. You can choose either a linked list or an array as the underlying data structure. ### Doubly Linked List Implementation Reviewing the previous section, we used a regular singly linked list to implement a queue because it conveniently allows deleting the head node (corresponding to dequeue) and adding new nodes after the tail node (corresponding to enqueue). For a deque, both the front and rear can perform enqueue and dequeue operations. In other words, a deque needs to implement operations in the opposite direction as well. For this reason, we use a "doubly linked list" as the underlying data structure for the deque. As shown in the figure below, we treat the head and tail nodes of the doubly linked list as the front and rear of the deque, implementing functionality to add and remove nodes at both ends. === "<1>" ![Enqueue and dequeue operations in linked list implementation of deque](deque.assets/linkedlist_deque_step1.png) === "<2>" ![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png) === "<3>" ![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png) === "<4>" ![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png) === "<5>" ![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png) The implementation code is shown below: ```src [file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{} ``` ### Array Implementation As shown in the figure below, similar to implementing a queue based on an array, we can also use a circular array to implement a deque. === "<1>" ![Enqueue and dequeue operations in array implementation of deque](deque.assets/array_deque_step1.png) === "<2>" ![array_deque_push_last](deque.assets/array_deque_step2_push_last.png) === "<3>" ![array_deque_push_first](deque.assets/array_deque_step3_push_first.png) === "<4>" ![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png) === "<5>" ![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png) Based on the queue implementation, we only need to add methods for "enqueue at front" and "dequeue from rear": ```src [file]{array_deque}-[class]{array_deque}-[func]{} ``` ## Deque Applications A deque combines the logic of both stacks and queues. **Therefore, it can implement all application scenarios of both, while providing greater flexibility**. We know that the "undo" function in software is typically implemented using a stack: the system pushes each change operation onto the stack and then implements undo through pop. However, considering system resource limitations, software usually limits the number of undo steps (for example, only allowing 50 steps to be saved). When the stack length exceeds 50, the software needs to perform a deletion operation at the bottom of the stack (front of the queue). **But a stack cannot implement this functionality, so a deque is needed to replace the stack**. Note that the core logic of "undo" still follows the LIFO principle of a stack; it's just that the deque can more flexibly implement some additional logic. ================================================ FILE: en/docs/chapter_stack_and_queue/index.md ================================================ # Stack and Queue ![Stack and Queue](../assets/covers/chapter_stack_and_queue.jpg) !!! abstract Stacks are like stacking cats, while queues are like cats lining up. They represent LIFO (Last In First Out) and FIFO (First In First Out) logic, respectively. ================================================ FILE: en/docs/chapter_stack_and_queue/queue.md ================================================ # Queue A queue is a linear data structure that follows the First In First Out (FIFO) rule. As the name suggests, a queue simulates the phenomenon of lining up, where newcomers continuously join the end of the queue, while people at the front of the queue leave one by one. As shown in the figure below, we call the front of the queue the "front" and the end the "rear." The operation of adding an element to the rear is called "enqueue," and the operation of removing the front element is called "dequeue." ![FIFO rule of queue](queue.assets/queue_operations.png) ## Common Queue Operations The common operations on a queue are shown in the table below. Note that method names may vary across different programming languages. We adopt the same naming convention as for stacks here.

Table   Efficiency of Queue Operations

| Method | Description | Time Complexity | | -------- | ------------------------------------------ | --------------- | | `push()` | Enqueue element, add element to rear | $O(1)$ | | `pop()` | Dequeue front element | $O(1)$ | | `peek()` | Access front element | $O(1)$ | We can directly use the ready-made queue classes in programming languages: === "Python" ```python title="queue.py" from collections import deque # Initialize queue # In Python, we generally use the deque class as a queue # Although queue.Queue() is a pure queue class, it is not very user-friendly, so it is not recommended que: deque[int] = deque() # Enqueue elements que.append(1) que.append(3) que.append(2) que.append(5) que.append(4) # Access front element front: int = que[0] # Dequeue element pop: int = que.popleft() # Get queue length size: int = len(que) # Check if queue is empty is_empty: bool = len(que) == 0 ``` === "C++" ```cpp title="queue.cpp" /* Initialize queue */ queue queue; /* Enqueue elements */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); /* Access front element */ int front = queue.front(); /* Dequeue element */ queue.pop(); /* Get queue length */ int size = queue.size(); /* Check if queue is empty */ bool empty = queue.empty(); ``` === "Java" ```java title="queue.java" /* Initialize queue */ Queue queue = new LinkedList<>(); /* Enqueue elements */ queue.offer(1); queue.offer(3); queue.offer(2); queue.offer(5); queue.offer(4); /* Access front element */ int peek = queue.peek(); /* Dequeue element */ int pop = queue.poll(); /* Get queue length */ int size = queue.size(); /* Check if queue is empty */ boolean isEmpty = queue.isEmpty(); ``` === "C#" ```csharp title="queue.cs" /* Initialize queue */ Queue queue = new(); /* Enqueue elements */ queue.Enqueue(1); queue.Enqueue(3); queue.Enqueue(2); queue.Enqueue(5); queue.Enqueue(4); /* Access front element */ int peek = queue.Peek(); /* Dequeue element */ int pop = queue.Dequeue(); /* Get queue length */ int size = queue.Count; /* Check if queue is empty */ bool isEmpty = queue.Count == 0; ``` === "Go" ```go title="queue_test.go" /* Initialize queue */ // In Go, use list as a queue queue := list.New() /* Enqueue elements */ queue.PushBack(1) queue.PushBack(3) queue.PushBack(2) queue.PushBack(5) queue.PushBack(4) /* Access front element */ peek := queue.Front() /* Dequeue element */ pop := queue.Front() queue.Remove(pop) /* Get queue length */ size := queue.Len() /* Check if queue is empty */ isEmpty := queue.Len() == 0 ``` === "Swift" ```swift title="queue.swift" /* Initialize queue */ // Swift does not have a built-in queue class, can use Array as a queue var queue: [Int] = [] /* Enqueue elements */ queue.append(1) queue.append(3) queue.append(2) queue.append(5) queue.append(4) /* Access front element */ let peek = queue.first! /* Dequeue element */ // Since it's an array, removeFirst has O(n) complexity let pool = queue.removeFirst() /* Get queue length */ let size = queue.count /* Check if queue is empty */ let isEmpty = queue.isEmpty ``` === "JS" ```javascript title="queue.js" /* Initialize queue */ // JavaScript does not have a built-in queue, can use Array as a queue const queue = []; /* Enqueue elements */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); /* Access front element */ const peek = queue[0]; /* Dequeue element */ // The underlying structure is an array, so shift() has O(n) time complexity const pop = queue.shift(); /* Get queue length */ const size = queue.length; /* Check if queue is empty */ const empty = queue.length === 0; ``` === "TS" ```typescript title="queue.ts" /* Initialize queue */ // TypeScript does not have a built-in queue, can use Array as a queue const queue: number[] = []; /* Enqueue elements */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); /* Access front element */ const peek = queue[0]; /* Dequeue element */ // The underlying structure is an array, so shift() has O(n) time complexity const pop = queue.shift(); /* Get queue length */ const size = queue.length; /* Check if queue is empty */ const empty = queue.length === 0; ``` === "Dart" ```dart title="queue.dart" /* Initialize queue */ // In Dart, the Queue class is a deque and can also be used as a queue Queue queue = Queue(); /* Enqueue elements */ queue.add(1); queue.add(3); queue.add(2); queue.add(5); queue.add(4); /* Access front element */ int peek = queue.first; /* Dequeue element */ int pop = queue.removeFirst(); /* Get queue length */ int size = queue.length; /* Check if queue is empty */ bool isEmpty = queue.isEmpty; ``` === "Rust" ```rust title="queue.rs" /* Initialize deque */ // In Rust, use deque as a regular queue let mut deque: VecDeque = VecDeque::new(); /* Enqueue elements */ deque.push_back(1); deque.push_back(3); deque.push_back(2); deque.push_back(5); deque.push_back(4); /* Access front element */ if let Some(front) = deque.front() { } /* Dequeue element */ if let Some(pop) = deque.pop_front() { } /* Get queue length */ let size = deque.len(); /* Check if queue is empty */ let is_empty = deque.is_empty(); ``` === "C" ```c title="queue.c" // C does not provide a built-in queue ``` === "Kotlin" ```kotlin title="queue.kt" /* Initialize queue */ val queue = LinkedList() /* Enqueue elements */ queue.offer(1) queue.offer(3) queue.offer(2) queue.offer(5) queue.offer(4) /* Access front element */ val peek = queue.peek() /* Dequeue element */ val pop = queue.poll() /* Get queue length */ val size = queue.size /* Check if queue is empty */ val isEmpty = queue.isEmpty() ``` === "Ruby" ```ruby title="queue.rb" # Initialize queue # Ruby's built-in queue (Thread::Queue) does not have peek and traversal methods, can use Array as a queue queue = [] # Enqueue elements queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) # Access front element peek = queue.first # Dequeue element # Please note that since it's an array, Array#shift has O(n) time complexity pop = queue.shift # Get queue length size = queue.length # Check if queue is empty is_empty = queue.empty? ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20%23%20%E5%9C%A8%20Python%20%E4%B8%AD%EF%BC%8C%E6%88%91%E4%BB%AC%E4%B8%80%E8%88%AC%E5%B0%86%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%B1%BB%20deque%20%E7%9C%8B%E4%BD%9C%E9%98%9F%E5%88%97%E4%BD%BF%E7%94%A8%0A%20%20%20%20%23%20%E8%99%BD%E7%84%B6%20queue.Queue%28%29%20%E6%98%AF%E7%BA%AF%E6%AD%A3%E7%9A%84%E9%98%9F%E5%88%97%E7%B1%BB%EF%BC%8C%E4%BD%86%E4%B8%8D%E5%A4%AA%E5%A5%BD%E7%94%A8%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%90%8E%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## Queue Implementation To implement a queue, we need a data structure that allows adding elements at one end and removing elements at the other end. Both linked lists and arrays meet this requirement. ### Linked List Implementation As shown in the figure below, we can treat the "head node" and "tail node" of a linked list as the "front" and "rear" of the queue, respectively, with the rule that nodes can only be added at the rear and removed from the front. === "<1>" ![Enqueue and dequeue operations in linked list implementation of queue](queue.assets/linkedlist_queue_step1.png) === "<2>" ![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png) === "<3>" ![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png) Below is the code for implementing a queue using a linked list: ```src [file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{} ``` ### Array Implementation Deleting the first element in an array has a time complexity of $O(n)$, which would make the dequeue operation inefficient. However, we can use the following clever method to avoid this problem. We can use a variable `front` to point to the index of the front element and maintain a variable `size` to record the queue length. We define `rear = front + size`, which calculates the position right after the rear element. Based on this design, **the valid interval containing elements in the array is `[front, rear - 1]`**. The implementation methods for various operations are shown in the figure below: - Enqueue operation: Assign the input element to the `rear` index and increase `size` by 1. - Dequeue operation: Simply increase `front` by 1 and decrease `size` by 1. As you can see, both enqueue and dequeue operations require only one operation, with a time complexity of $O(1)$. === "<1>" ![Enqueue and dequeue operations in array implementation of queue](queue.assets/array_queue_step1.png) === "<2>" ![array_queue_push](queue.assets/array_queue_step2_push.png) === "<3>" ![array_queue_pop](queue.assets/array_queue_step3_pop.png) You may notice a problem: as we continuously enqueue and dequeue, both `front` and `rear` move to the right. **When they reach the end of the array, they cannot continue moving**. To solve this problem, we can treat the array as a "circular array" with head and tail connected. For a circular array, we need to let `front` or `rear` wrap around to the beginning of the array when they cross the end. This periodic pattern can be implemented using the "modulo operation," as shown in the code below: ```src [file]{array_queue}-[class]{array_queue}-[func]{} ``` The queue implemented above still has limitations: its length is immutable. However, this problem is not difficult to solve. We can replace the array with a dynamic array to introduce an expansion mechanism. Interested readers can try to implement this themselves. The comparison conclusions for the two implementations are consistent with those for stacks and will not be repeated here. ## Typical Applications of Queue - **Taobao orders**. After shoppers place orders, the orders are added to a queue, and the system subsequently processes the orders in the queue according to their sequence. During Double Eleven, massive orders are generated in a short time, and high concurrency becomes a key challenge that engineers need to tackle. - **Various to-do tasks**. Any scenario that needs to implement "first come, first served" functionality, such as a printer's task queue or a restaurant's order queue, can effectively maintain the processing order using queues. ================================================ FILE: en/docs/chapter_stack_and_queue/stack.md ================================================ # Stack A stack is a linear data structure that follows the Last In First Out (LIFO) logic. We can compare a stack to a pile of plates on a table. If we specify that only one plate can be moved at a time, then to get the bottom plate, we must first remove the plates above it one by one. If we replace the plates with various types of elements (such as integers, characters, objects, etc.), we get the stack data structure. As shown in the figure below, we call the top of the stacked elements the "top" and the bottom the "base." The operation of adding an element to the top is called "push," and the operation of removing the top element is called "pop." ![LIFO rule of stack](stack.assets/stack_operations.png) ## Common Stack Operations The common operations on a stack are shown in the table below. The specific method names depend on the programming language used. Here, we use the common naming convention of `push()`, `pop()`, and `peek()`.

Table   Efficiency of Stack Operations

| Method | Description | Time Complexity | | -------- | ---------------------------------------------- | --------------- | | `push()` | Push element onto stack (add to top) | $O(1)$ | | `pop()` | Pop top element from stack | $O(1)$ | | `peek()` | Access top element | $O(1)$ | Typically, we can directly use the built-in stack class provided by the programming language. However, some languages may not provide a dedicated stack class. In these cases, we can use the language's "array" or "linked list" as a stack and ignore operations unrelated to the stack in the program logic. === "Python" ```python title="stack.py" # Initialize stack # Python does not have a built-in stack class, can use list as a stack stack: list[int] = [] # Push elements stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) # Access top element peek: int = stack[-1] # Pop element pop: int = stack.pop() # Get stack length size: int = len(stack) # Check if empty is_empty: bool = len(stack) == 0 ``` === "C++" ```cpp title="stack.cpp" /* Initialize stack */ stack stack; /* Push elements */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* Access top element */ int top = stack.top(); /* Pop element */ stack.pop(); // No return value /* Get stack length */ int size = stack.size(); /* Check if empty */ bool empty = stack.empty(); ``` === "Java" ```java title="stack.java" /* Initialize stack */ Stack stack = new Stack<>(); /* Push elements */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* Access top element */ int peek = stack.peek(); /* Pop element */ int pop = stack.pop(); /* Get stack length */ int size = stack.size(); /* Check if empty */ boolean isEmpty = stack.isEmpty(); ``` === "C#" ```csharp title="stack.cs" /* Initialize stack */ Stack stack = new(); /* Push elements */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); /* Access top element */ int peek = stack.Peek(); /* Pop element */ int pop = stack.Pop(); /* Get stack length */ int size = stack.Count; /* Check if empty */ bool isEmpty = stack.Count == 0; ``` === "Go" ```go title="stack_test.go" /* Initialize stack */ // In Go, it is recommended to use Slice as a stack var stack []int /* Push elements */ stack = append(stack, 1) stack = append(stack, 3) stack = append(stack, 2) stack = append(stack, 5) stack = append(stack, 4) /* Access top element */ peek := stack[len(stack)-1] /* Pop element */ pop := stack[len(stack)-1] stack = stack[:len(stack)-1] /* Get stack length */ size := len(stack) /* Check if empty */ isEmpty := len(stack) == 0 ``` === "Swift" ```swift title="stack.swift" /* Initialize stack */ // Swift does not have a built-in stack class, can use Array as a stack var stack: [Int] = [] /* Push elements */ stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) /* Access top element */ let peek = stack.last! /* Pop element */ let pop = stack.removeLast() /* Get stack length */ let size = stack.count /* Check if empty */ let isEmpty = stack.isEmpty ``` === "JS" ```javascript title="stack.js" /* Initialize stack */ // JavaScript does not have a built-in stack class, can use Array as a stack const stack = []; /* Push elements */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* Access top element */ const peek = stack[stack.length-1]; /* Pop element */ const pop = stack.pop(); /* Get stack length */ const size = stack.length; /* Check if empty */ const is_empty = stack.length === 0; ``` === "TS" ```typescript title="stack.ts" /* Initialize stack */ // TypeScript does not have a built-in stack class, can use Array as a stack const stack: number[] = []; /* Push elements */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* Access top element */ const peek = stack[stack.length - 1]; /* Pop element */ const pop = stack.pop(); /* Get stack length */ const size = stack.length; /* Check if empty */ const is_empty = stack.length === 0; ``` === "Dart" ```dart title="stack.dart" /* Initialize stack */ // Dart does not have a built-in stack class, can use List as a stack List stack = []; /* Push elements */ stack.add(1); stack.add(3); stack.add(2); stack.add(5); stack.add(4); /* Access top element */ int peek = stack.last; /* Pop element */ int pop = stack.removeLast(); /* Get stack length */ int size = stack.length; /* Check if empty */ bool isEmpty = stack.isEmpty; ``` === "Rust" ```rust title="stack.rs" /* Initialize stack */ // Use Vec as a stack let mut stack: Vec = Vec::new(); /* Push elements */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* Access top element */ let top = stack.last().unwrap(); /* Pop element */ let pop = stack.pop().unwrap(); /* Get stack length */ let size = stack.len(); /* Check if empty */ let is_empty = stack.is_empty(); ``` === "C" ```c title="stack.c" // C does not provide a built-in stack ``` === "Kotlin" ```kotlin title="stack.kt" /* Initialize stack */ val stack = Stack() /* Push elements */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) /* Access top element */ val peek = stack.peek() /* Pop element */ val pop = stack.pop() /* Get stack length */ val size = stack.size /* Check if empty */ val isEmpty = stack.isEmpty() ``` === "Ruby" ```ruby title="stack.rb" # Initialize stack # Ruby does not have a built-in stack class, can use Array as a stack stack = [] # Push elements stack << 1 stack << 3 stack << 2 stack << 5 stack << 4 # Access top element peek = stack.last # Pop element pop = stack.pop # Get stack length size = stack.length # Check if empty is_empty = stack.empty? ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20%23%20Python%20%E6%B2%A1%E6%9C%89%E5%86%85%E7%BD%AE%E7%9A%84%E6%A0%88%E7%B1%BB%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E5%BD%93%E4%BD%9C%E6%A0%88%E6%9D%A5%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## Stack Implementation To gain a deeper understanding of how a stack operates, let's try implementing a stack class ourselves. A stack follows the LIFO principle, so we can only add or remove elements at the top. However, both arrays and linked lists allow adding and removing elements at any position. **Therefore, a stack can be viewed as a restricted array or linked list**. In other words, we can "shield" some irrelevant operations of arrays or linked lists so that their external logic conforms to the characteristics of a stack. ### Linked List Implementation When implementing a stack using a linked list, we can treat the head node of the linked list as the top of the stack and the tail node as the base. As shown in the figure below, for the push operation, we simply insert an element at the head of the linked list. This node insertion method is called the "head insertion method." For the pop operation, we just need to remove the head node from the linked list. === "<1>" ![Push and pop operations in linked list implementation of stack](stack.assets/linkedlist_stack_step1.png) === "<2>" ![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png) === "<3>" ![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png) Below is sample code for implementing a stack based on a linked list: ```src [file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{} ``` ### Array Implementation When implementing a stack using an array, we can treat the end of the array as the top of the stack. As shown in the figure below, push and pop operations correspond to adding and removing elements at the end of the array, both with a time complexity of $O(1)$. === "<1>" ![Push and pop operations in array implementation of stack](stack.assets/array_stack_step1.png) === "<2>" ![array_stack_push](stack.assets/array_stack_step2_push.png) === "<3>" ![array_stack_pop](stack.assets/array_stack_step3_pop.png) Since elements pushed onto the stack may increase continuously, we can use a dynamic array, which eliminates the need to handle array expansion ourselves. Here is the sample code: ```src [file]{array_stack}-[class]{array_stack}-[func]{} ``` ## Comparison of the Two Implementations **Supported Operations** Both implementations support all operations defined by the stack. The array implementation additionally supports random access, but this goes beyond the stack definition and is generally not used. **Time Efficiency** In the array-based implementation, both push and pop operations occur in pre-allocated contiguous memory, which has good cache locality and is therefore more efficient. However, if pushing exceeds the array capacity, it triggers an expansion mechanism, causing the time complexity of that particular push operation to become $O(n)$. In the linked list-based implementation, list expansion is very flexible, and there is no issue of reduced efficiency due to array expansion. However, the push operation requires initializing a node object and modifying pointers, so it is relatively less efficient. Nevertheless, if the pushed elements are already node objects, the initialization step can be omitted, thereby improving efficiency. In summary, when the elements pushed and popped are basic data types such as `int` or `double`, we can draw the following conclusions: - The array-based stack implementation has reduced efficiency when expansion is triggered, but since expansion is an infrequent operation, the average efficiency is higher. - The linked list-based stack implementation can provide more stable efficiency performance. **Space Efficiency** When initializing a list, the system allocates an "initial capacity" that may exceed the actual need. Additionally, the expansion mechanism typically expands at a specific ratio (e.g., 2x), and the capacity after expansion may also exceed actual needs. Therefore, **the array-based stack implementation may cause some space wastage**. However, since linked list nodes need to store additional pointers, **the space occupied by linked list nodes is relatively large**. In summary, we cannot simply determine which implementation is more memory-efficient and need to analyze the specific situation. ## Typical Applications of Stack - **Back and forward in browsers, undo and redo in software**. Every time we open a new webpage, the browser pushes the previous page onto the stack, allowing us to return to the previous page via the back operation. The back operation is essentially performing a pop. To support both back and forward, two stacks are needed to work together. - **Program memory management**. Each time a function is called, the system adds a stack frame to the top of the stack to record the function's context information. During recursion, the downward recursive phase continuously performs push operations, while the upward backtracking phase continuously performs pop operations. ================================================ FILE: en/docs/chapter_stack_and_queue/summary.md ================================================ # Summary ### Key Review - A stack is a data structure that follows the LIFO principle and can be implemented using arrays or linked lists. - In terms of time efficiency, the array implementation of a stack has higher average efficiency, but during expansion, the time complexity of a single push operation degrades to $O(n)$. In contrast, the linked list implementation of a stack provides more stable efficiency performance. - In terms of space efficiency, the array implementation of a stack may lead to some degree of space wastage. However, it should be noted that the memory space occupied by linked list nodes is larger than that of array elements. - A queue is a data structure that follows the FIFO principle and can also be implemented using arrays or linked lists. The conclusions regarding time efficiency and space efficiency comparisons for queues are similar to those for stacks mentioned above. - A deque is a queue with greater flexibility that allows adding and removing elements at both ends. ### Q & A **Q**: Is the browser's forward and backward functionality implemented with a doubly linked list? The forward and backward functionality of a browser is essentially a manifestation of a "stack." When a user visits a new page, that page is added to the top of the stack; when the user clicks the back button, that page is popped from the top of the stack. Using a deque can conveniently implement some additional operations, as mentioned in the "Deque" section. **Q**: After popping from the stack, do we need to free the memory of the popped node? If the popped node will still be needed later, then memory does not need to be freed. If it won't be used afterward, languages like Java and Python have automatic garbage collection, so manual memory deallocation is not required; in C and C++, manual memory deallocation is necessary. **Q**: A deque seems like two stacks joined together. What is its purpose? A deque is like a combination of a stack and a queue, or two stacks joined together. It exhibits the logic of both stack and queue, so it can implement all applications of stacks and queues, and is more flexible. **Q**: How are undo and redo specifically implemented? Use two stacks: stack `A` for undo and stack `B` for redo. 1. Whenever the user performs an operation, push this operation onto stack `A` and clear stack `B`. 2. When the user performs "undo," pop the most recent operation from stack `A` and push it onto stack `B`. 3. When the user performs "redo," pop the most recent operation from stack `B` and push it onto stack `A`. ================================================ FILE: en/docs/chapter_tree/array_representation_of_tree.md ================================================ # Array Representation of Binary Trees Under the linked list representation, the storage unit of a binary tree is a node `TreeNode`, and nodes are connected by pointers. The previous section introduced the basic operations of binary trees under the linked list representation. So, can we use an array to represent a binary tree? The answer is yes. ## Representing Perfect Binary Trees Let's analyze a simple case first. Given a perfect binary tree, we store all nodes in an array according to the order of level-order traversal, where each node corresponds to a unique array index. Based on the characteristics of level-order traversal, we can derive a "mapping formula" between parent node index and child node indices: **If a node's index is $i$, then its left child index is $2i + 1$ and its right child index is $2i + 2$**. The figure below shows the mapping relationships between various node indices. ![Array representation of a perfect binary tree](array_representation_of_tree.assets/array_representation_binary_tree.png) **The mapping formula plays a role similar to the node references (pointers) in linked lists**. Given any node in the array, we can access its left (right) child node using the mapping formula. ## Representing Any Binary Tree Perfect binary trees are a special case; in the middle levels of a binary tree, there are typically many `None` values. Since the level-order traversal sequence does not include these `None` values, we cannot infer the number and distribution of `None` values based on this sequence alone. **This means multiple binary tree structures can correspond to the same level-order traversal sequence**. As shown in the figure below, given a non-perfect binary tree, the above method of array representation fails. ![Level-order traversal sequence corresponds to multiple binary tree possibilities](array_representation_of_tree.assets/array_representation_without_empty.png) To solve this problem, **we can consider explicitly writing out all `None` values in the level-order traversal sequence**. As shown in the figure below, after this treatment, the level-order traversal sequence can uniquely represent a binary tree. Example code is as follows: === "Python" ```python title="" # Array representation of a binary tree # Using None to represent empty slots tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] ``` === "C++" ```cpp title="" /* Array representation of a binary tree */ // Using the maximum integer value INT_MAX to mark empty slots vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` === "Java" ```java title="" /* Array representation of a binary tree */ // Using the Integer wrapper class allows for using null to mark empty slots Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; ``` === "C#" ```csharp title="" /* Array representation of a binary tree */ // Using nullable int (int?) allows for using null to mark empty slots int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Go" ```go title="" /* Array representation of a binary tree */ // Using an any type slice, allowing for nil to mark empty slots tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} ``` === "Swift" ```swift title="" /* Array representation of a binary tree */ // Using optional Int (Int?) allows for using nil to mark empty slots let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` === "JS" ```javascript title="" /* Array representation of a binary tree */ // Using null to represent empty slots let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "TS" ```typescript title="" /* Array representation of a binary tree */ // Using null to represent empty slots let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Dart" ```dart title="" /* Array representation of a binary tree */ // Using nullable int (int?) allows for using null to mark empty slots List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Rust" ```rust title="" /* Array representation of a binary tree */ // Using None to mark empty slots let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)]; ``` === "C" ```c title="" /* Array representation of a binary tree */ // Using the maximum int value to mark empty slots, therefore, node values must not be INT_MAX int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` === "Kotlin" ```kotlin title="" /* Array representation of a binary tree */ // Using null to represent empty slots val tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) ``` === "Ruby" ```ruby title="" ### Array representation of a binary tree ### # Using nil to represent empty slots tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` ![Array representation of any type of binary tree](array_representation_of_tree.assets/array_representation_with_empty.png) It's worth noting that **complete binary trees are very well-suited for array representation**. Recalling the definition of a complete binary tree, `None` only appears at the bottom level and towards the right, **meaning all `None` values must appear at the end of the level-order traversal sequence**. This means that when using an array to represent a complete binary tree, it's possible to omit storing all `None` values, which is very convenient. The figure below gives an example. ![Array representation of a complete binary tree](array_representation_of_tree.assets/array_representation_complete_binary_tree.png) The following code implements a binary tree based on array representation, including the following operations: - Given a certain node, obtain its value, left (right) child node, and parent node. - Obtain the preorder, inorder, postorder, and level-order traversal sequences. ```src [file]{array_binary_tree}-[class]{array_binary_tree}-[func]{} ``` ## Advantages and Limitations The array representation of binary trees has the following advantages: - Arrays are stored in contiguous memory space, which is cache-friendly, allowing faster access and traversal. - It does not require storing pointers, which saves space. - It allows random access to nodes. However, the array representation also has some limitations: - Array storage requires contiguous memory space, so it is not suitable for storing trees with a large amount of data. - Adding or removing nodes requires array insertion and deletion operations, which have lower efficiency. - When there are many `None` values in the binary tree, the proportion of node data contained in the array is low, leading to lower space utilization. ================================================ FILE: en/docs/chapter_tree/avl_tree.md ================================================ # Avl Tree * In the "Binary Search Tree" section, we mentioned that after multiple insertion and removal operations, a binary search tree may degenerate into a linked list. In this case, the time complexity of all operations degrades from $O(\log n)$ to $O(n)$. As shown in the figure below, after two node removal operations, this binary search tree will degrade into a linked list. ![Degradation of an AVL tree after removing nodes](avl_tree.assets/avltree_degradation_from_removing_node.png) For example, in the perfect binary tree shown in the figure below, after inserting two nodes, the tree will lean heavily to the left, and the time complexity of search operations will also degrade. ![Degradation of an AVL tree after inserting nodes](avl_tree.assets/avltree_degradation_from_inserting_node.png) In 1962, G. M. Adelson-Velsky and E. M. Landis proposed the AVL tree in their paper "An algorithm for the organization of information". The paper described in detail a series of operations ensuring that after continuously adding and removing nodes, the AVL tree does not degenerate, thus keeping the time complexity of various operations at the $O(\log n)$ level. In other words, in scenarios requiring frequent insertions, deletions, searches, and modifications, the AVL tree can always maintain efficient data operation performance, making it very valuable in applications. ## Common Terminology in Avl Trees An AVL tree is both a binary search tree and a balanced binary tree, simultaneously satisfying all the properties of these two types of binary trees, hence it is a balanced binary search tree. ### Node Height Since the operations related to AVL trees require obtaining node heights, we need to add a `height` variable to the node class: === "Python" ```python title="" class TreeNode: """AVL tree node""" def __init__(self, val: int): self.val: int = val # Node value self.height: int = 0 # Node height self.left: TreeNode | None = None # Left child reference self.right: TreeNode | None = None # Right child reference ``` === "C++" ```cpp title="" /* AVL tree node */ struct TreeNode { int val{}; // Node value int height = 0; // Node height TreeNode *left{}; // Left child TreeNode *right{}; // Right child TreeNode() = default; explicit TreeNode(int x) : val(x){} }; ``` === "Java" ```java title="" /* AVL tree node */ class TreeNode { public int val; // Node value public int height; // Node height public TreeNode left; // Left child public TreeNode right; // Right child public TreeNode(int x) { val = x; } } ``` === "C#" ```csharp title="" /* AVL tree node */ class TreeNode(int? x) { public int? val = x; // Node value public int height; // Node height public TreeNode? left; // Left child reference public TreeNode? right; // Right child reference } ``` === "Go" ```go title="" /* AVL tree node */ type TreeNode struct { Val int // Node value Height int // Node height Left *TreeNode // Left child reference Right *TreeNode // Right child reference } ``` === "Swift" ```swift title="" /* AVL tree node */ class TreeNode { var val: Int // Node value var height: Int // Node height var left: TreeNode? // Left child var right: TreeNode? // Right child init(x: Int) { val = x height = 0 } } ``` === "JS" ```javascript title="" /* AVL tree node */ class TreeNode { val; // Node value height; // Node height left; // Left child pointer right; // Right child pointer constructor(val, left, right, height) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } ``` === "TS" ```typescript title="" /* AVL tree node */ class TreeNode { val: number; // Node value height: number; // Node height left: TreeNode | null; // Left child pointer right: TreeNode | null; // Right child pointer constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } ``` === "Dart" ```dart title="" /* AVL tree node */ class TreeNode { int val; // Node value int height; // Node height TreeNode? left; // Left child TreeNode? right; // Right child TreeNode(this.val, [this.height = 0, this.left, this.right]); } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* AVL tree node */ struct TreeNode { val: i32, // Node value height: i32, // Node height left: Option>>, // Left child right: Option>>, // Right child } impl TreeNode { /* Constructor */ fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, height: 0, left: None, right: None })) } } ``` === "C" ```c title="" /* AVL tree node */ typedef struct TreeNode { int val; int height; struct TreeNode *left; struct TreeNode *right; } TreeNode; /* Constructor */ TreeNode *newTreeNode(int val) { TreeNode *node; node = (TreeNode *)malloc(sizeof(TreeNode)); node->val = val; node->height = 0; node->left = NULL; node->right = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* AVL tree node */ class TreeNode(val _val: Int) { // Node value val height: Int = 0 // Node height val left: TreeNode? = null // Left child val right: TreeNode? = null // Right child } ``` === "Ruby" ```ruby title="" ### AVL tree node class ### class TreeNode attr_accessor :val # Node value attr_accessor :height # Node height attr_accessor :left # Left child reference attr_accessor :right # Right child reference def initialize(val) @val = val @height = 0 end end ``` The "node height" refers to the distance from that node to its farthest leaf node, i.e., the number of "edges" passed. It is important to note that the height of a leaf node is $0$, and the height of a null node is $-1$. We will create two utility functions for getting and updating the height of a node: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{update_height} ``` ### Node Balance Factor The balance factor of a node is defined as the height of the node's left subtree minus the height of its right subtree, and the balance factor of a null node is defined as $0$. We also encapsulate the function to obtain the node's balance factor for convenient subsequent use: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{balance_factor} ``` !!! tip Let the balance factor be $f$, then the balance factor of any node in an AVL tree satisfies $-1 \le f \le 1$. ## Rotations in Avl Trees The characteristic of AVL trees lies in the "rotation" operation, which can restore balance to unbalanced nodes without affecting the inorder traversal sequence of the binary tree. In other words, **rotation operations can both maintain the property of a "binary search tree" and make the tree return to a "balanced binary tree"**. We call nodes with a balance factor absolute value $> 1$ "unbalanced nodes". Depending on the imbalance situation, rotation operations are divided into four types: right rotation, left rotation, left rotation then right rotation, and right rotation then left rotation. Below we describe these rotation operations in detail. ### Right Rotation As shown in the figure below, the value below the node is the balance factor. From bottom to top, the first unbalanced node in the binary tree is "node 3". We focus on the subtree with this unbalanced node as the root, denoting the node as `node` and its left child as `child`, and perform a "right rotation" operation. After the right rotation is completed, the subtree regains balance and still maintains the properties of a binary search tree. === "<1>" ![Steps of right rotation](avl_tree.assets/avltree_right_rotate_step1.png) === "<2>" ![avltree_right_rotate_step2](avl_tree.assets/avltree_right_rotate_step2.png) === "<3>" ![avltree_right_rotate_step3](avl_tree.assets/avltree_right_rotate_step3.png) === "<4>" ![avltree_right_rotate_step4](avl_tree.assets/avltree_right_rotate_step4.png) As shown in the figure below, when the `child` node has a right child (denoted as `grand_child`), a step needs to be added in the right rotation: set `grand_child` as the left child of `node`. ![Right rotation with grand_child](avl_tree.assets/avltree_right_rotate_with_grandchild.png) "Right rotation" is a figurative term; in practice, it is achieved by modifying node pointers, as shown in the following code: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{right_rotate} ``` ### Left Rotation Correspondingly, if considering the "mirror" of the above unbalanced binary tree, the "left rotation" operation shown in the figure below needs to be performed. ![Left rotation operation](avl_tree.assets/avltree_left_rotate.png) Similarly, as shown in the figure below, when the `child` node has a left child (denoted as `grand_child`), a step needs to be added in the left rotation: set `grand_child` as the right child of `node`. ![Left rotation with grand_child](avl_tree.assets/avltree_left_rotate_with_grandchild.png) It can be observed that **right rotation and left rotation operations are mirror symmetric in logic, and the two imbalance cases they solve are also symmetric**. Based on symmetry, we only need to replace all `left` in the right rotation implementation code with `right`, and all `right` with `left`, to obtain the left rotation implementation code: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{left_rotate} ``` ### Left Rotation Then Right Rotation For the unbalanced node 3 in the figure below, using either left rotation or right rotation alone cannot restore the subtree to balance. In this case, a "left rotation" needs to be performed on `child` first, followed by a "right rotation" on `node`. ![Left-right rotation](avl_tree.assets/avltree_left_right_rotate.png) ### Right Rotation Then Left Rotation As shown in the figure below, for the mirror case of the above unbalanced binary tree, a "right rotation" needs to be performed on `child` first, then a "left rotation" on `node`. ![Right-left rotation](avl_tree.assets/avltree_right_left_rotate.png) ### Choice of Rotation The four imbalances shown in the figure below correspond one-to-one with the above cases, requiring right rotation, left rotation then right rotation, right rotation then left rotation, and left rotation operations respectively. ![The four rotation cases of AVL tree](avl_tree.assets/avltree_rotation_cases.png) As shown in the table below, we determine which case the unbalanced node belongs to by judging the signs of the balance factor of the unbalanced node and the balance factor of its taller-side child node.

Table   Conditions for Choosing Among the Four Rotation Cases

| Balance factor of the unbalanced node | Balance factor of the child node | Rotation method to apply | | -------------------------------------- | --------------------------------- | --------------------------------- | | $> 1$ (left-leaning tree) | $\geq 0$ | Right rotation | | $> 1$ (left-leaning tree) | $<0$ | Left rotation then right rotation | | $< -1$ (right-leaning tree) | $\leq 0$ | Left rotation | | $< -1$ (right-leaning tree) | $>0$ | Right rotation then left rotation | For ease of use, we encapsulate the rotation operations into a function. **With this function, we can perform rotations for various imbalance situations, restoring balance to unbalanced nodes**. The code is as follows: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{rotate} ``` ## Common Operations in Avl Trees ### Node Insertion The node insertion operation in AVL trees is similar in principle to that in binary search trees. The only difference is that after inserting a node in an AVL tree, a series of unbalanced nodes may appear on the path from that node to the root. Therefore, **we need to start from this node and perform rotation operations from bottom to top, restoring balance to all unbalanced nodes**. The code is as follows: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{insert_helper} ``` ### Node Removal Similarly, on the basis of the binary search tree's node removal method, rotation operations need to be performed from bottom to top to restore balance to all unbalanced nodes. The code is as follows: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{remove_helper} ``` ### Node Search The node search operation in AVL trees is consistent with that in binary search trees, and will not be elaborated here. ## Typical Applications of Avl Trees - Organizing and storing large-scale data, suitable for scenarios with high-frequency searches and low-frequency insertions and deletions. - Used to build index systems in databases. - Red-black trees are also a common type of balanced binary search tree. Compared to AVL trees, red-black trees have more relaxed balance conditions, require fewer rotation operations for node insertion and deletion, and have higher average efficiency for node addition and deletion operations. ================================================ FILE: en/docs/chapter_tree/binary_search_tree.md ================================================ # Binary Search Tree As shown in the figure below, a binary search tree satisfies the following conditions. 1. For the root node, the value of all nodes in the left subtree $<$ the value of the root node $<$ the value of all nodes in the right subtree. 2. The left and right subtrees of any node are also binary search trees, i.e., they satisfy condition `1.` as well. ![Binary search tree](binary_search_tree.assets/binary_search_tree.png) ## Operations on a Binary Search Tree We encapsulate the binary search tree as a class `BinarySearchTree` and declare a member variable `root` pointing to the tree's root node. ### Searching for a Node Given a target node value `num`, we can search according to the properties of the binary search tree. As shown in the figure below, we declare a node `cur` and start from the binary tree's root node `root`, looping to compare the node value `cur.val` with `num`. - If `cur.val < num`, it means the target node is in `cur`'s right subtree, thus execute `cur = cur.right`. - If `cur.val > num`, it means the target node is in `cur`'s left subtree, thus execute `cur = cur.left`. - If `cur.val = num`, it means the target node is found, exit the loop, and return the node. === "<1>" ![Example of searching for a node in a binary search tree](binary_search_tree.assets/bst_search_step1.png) === "<2>" ![bst_search_step2](binary_search_tree.assets/bst_search_step2.png) === "<3>" ![bst_search_step3](binary_search_tree.assets/bst_search_step3.png) === "<4>" ![bst_search_step4](binary_search_tree.assets/bst_search_step4.png) The search operation in a binary search tree works on the same principle as the binary search algorithm, both eliminating half of the cases in each round. The number of loop iterations is at most the height of the binary tree. When the binary tree is balanced, it uses $O(\log n)$ time. The example code is as follows: ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{search} ``` ### Inserting a Node Given an element `num` to be inserted, in order to maintain the property of the binary search tree "left subtree < root node < right subtree," the insertion process is as shown in the figure below. 1. **Finding the insertion position**: Similar to the search operation, start from the root node and loop downward searching according to the size relationship between the current node value and `num`, until passing the leaf node (traversing to `None`) and then exit the loop. 2. **Insert the node at that position**: Initialize node `num` and place it at the `None` position. ![Inserting a node into a binary search tree](binary_search_tree.assets/bst_insert.png) In the code implementation, note the following two points: - Binary search trees do not allow duplicate nodes; otherwise, it would violate its definition. Therefore, if the node to be inserted already exists in the tree, the insertion is not performed and it returns directly. - To implement the node insertion, we need to use node `pre` to save the node from the previous loop iteration. This way, when traversing to `None`, we can obtain its parent node, thereby completing the node insertion operation. ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{insert} ``` Similar to searching for a node, inserting a node uses $O(\log n)$ time. ### Removing a Node First, find the target node in the binary tree, then remove it. Similar to node insertion, we need to ensure that after the removal operation is completed, the binary search tree's property of "left subtree $<$ root node $<$ right subtree" is still maintained. Therefore, depending on the number of child nodes the target node has, we divide it into 0, 1, and 2 three cases, and execute the corresponding node removal operations. As shown in the figure below, when the degree of the node to be removed is $0$, it means the node is a leaf node and can be directly removed. ![Removing a node in a binary search tree (degree 0)](binary_search_tree.assets/bst_remove_case1.png) As shown in the figure below, when the degree of the node to be removed is $1$, replacing the node to be removed with its child node is sufficient. ![Removing a node in a binary search tree (degree 1)](binary_search_tree.assets/bst_remove_case2.png) When the degree of the node to be removed is $2$, we cannot directly remove it; instead, we need to use a node to replace it. To maintain the binary search tree's property of "left subtree $<$ root node $<$ right subtree," **this node can be either the smallest node in the right subtree or the largest node in the left subtree**. Assuming we choose the smallest node in the right subtree (the next node in the inorder traversal), the removal process is as shown in the figure below. 1. Find the next node of the node to be removed in the "inorder traversal sequence," denoted as `tmp`. 2. Replace the value of the node to be removed with the value of `tmp`, and recursively remove node `tmp` in the tree. === "<1>" ![Removing a node in a binary search tree (degree 2)](binary_search_tree.assets/bst_remove_case3_step1.png) === "<2>" ![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png) === "<3>" ![bst_remove_case3_step3](binary_search_tree.assets/bst_remove_case3_step3.png) === "<4>" ![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png) The node removal operation also uses $O(\log n)$ time, where finding the node to be removed requires $O(\log n)$ time, and obtaining the inorder successor node requires $O(\log n)$ time. Example code is as follows: ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{remove} ``` ### Inorder Traversal Is Ordered As shown in the figure below, the inorder traversal of a binary tree follows the "left $\rightarrow$ root $\rightarrow$ right" traversal order, while the binary search tree satisfies the "left child node $<$ root node $<$ right child node" size relationship. This means that when performing an inorder traversal in a binary search tree, the next smallest node is always traversed first, thus yielding an important property: **The inorder traversal sequence of a binary search tree is ascending**. Using the property of inorder traversal being ascending, we can obtain ordered data in a binary search tree in only $O(n)$ time, without the need for additional sorting operations, which is very efficient. ![Inorder traversal sequence of a binary search tree](binary_search_tree.assets/bst_inorder_traversal.png) ## Efficiency of Binary Search Trees Given a set of data, we consider using an array or a binary search tree for storage. Observing the table below, all operations in a binary search tree have logarithmic time complexity, providing stable and efficient performance. Arrays are more efficient than binary search trees only in scenarios with high-frequency additions and low-frequency searches and deletions.

Table   Efficiency comparison between arrays and search trees

| | Unsorted array | Binary search tree | | -------------- | -------------- | ------------------ | | Search element | $O(n)$ | $O(\log n)$ | | Insert element | $O(1)$ | $O(\log n)$ | | Remove element | $O(n)$ | $O(\log n)$ | In the ideal case, a binary search tree is "balanced," such that any node can be found within $\log n$ loop iterations. However, if we continuously insert and remove nodes in a binary search tree, it may degenerate into a linked list as shown in the figure below, where the time complexity of various operations also degrades to $O(n)$. ![Degradation of a binary search tree](binary_search_tree.assets/bst_degradation.png) ## Common Applications of Binary Search Trees - Used as multi-level indexes in systems to implement efficient search, insertion, and removal operations. - Serves as the underlying data structure for certain search algorithms. - Used to store data streams to maintain their ordered state. ================================================ FILE: en/docs/chapter_tree/binary_tree.md ================================================ # Binary Tree A binary tree is a non-linear data structure that represents the derivation relationship between "ancestors" and "descendants" and embodies the divide-and-conquer logic of "one divides into two". Similar to a linked list, the basic unit of a binary tree is a node, and each node contains a value, a reference to its left child node, and a reference to its right child node. === "Python" ```python title="" class TreeNode: """Binary tree node""" def __init__(self, val: int): self.val: int = val # Node value self.left: TreeNode | None = None # Reference to left child node self.right: TreeNode | None = None # Reference to right child node ``` === "C++" ```cpp title="" /* Binary tree node */ struct TreeNode { int val; // Node value TreeNode *left; // Pointer to left child node TreeNode *right; // Pointer to right child node TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; ``` === "Java" ```java title="" /* Binary tree node */ class TreeNode { int val; // Node value TreeNode left; // Reference to left child node TreeNode right; // Reference to right child node TreeNode(int x) { val = x; } } ``` === "C#" ```csharp title="" /* Binary tree node */ class TreeNode(int? x) { public int? val = x; // Node value public TreeNode? left; // Reference to left child node public TreeNode? right; // Reference to right child node } ``` === "Go" ```go title="" /* Binary tree node */ type TreeNode struct { Val int Left *TreeNode Right *TreeNode } /* Constructor */ func NewTreeNode(v int) *TreeNode { return &TreeNode{ Left: nil, // Pointer to left child node Right: nil, // Pointer to right child node Val: v, // Node value } } ``` === "Swift" ```swift title="" /* Binary tree node */ class TreeNode { var val: Int // Node value var left: TreeNode? // Reference to left child node var right: TreeNode? // Reference to right child node init(x: Int) { val = x } } ``` === "JS" ```javascript title="" /* Binary tree node */ class TreeNode { val; // Node value left; // Pointer to left child node right; // Pointer to right child node constructor(val, left, right) { this.val = val === undefined ? 0 : val; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } ``` === "TS" ```typescript title="" /* Binary tree node */ class TreeNode { val: number; left: TreeNode | null; right: TreeNode | null; constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { this.val = val === undefined ? 0 : val; // Node value this.left = left === undefined ? null : left; // Reference to left child node this.right = right === undefined ? null : right; // Reference to right child node } } ``` === "Dart" ```dart title="" /* Binary tree node */ class TreeNode { int val; // Node value TreeNode? left; // Reference to left child node TreeNode? right; // Reference to right child node TreeNode(this.val, [this.left, this.right]); } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* Binary tree node */ struct TreeNode { val: i32, // Node value left: Option>>, // Reference to left child node right: Option>>, // Reference to right child node } impl TreeNode { /* Constructor */ fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, left: None, right: None })) } } ``` === "C" ```c title="" /* Binary tree node */ typedef struct TreeNode { int val; // Node value int height; // Node height struct TreeNode *left; // Pointer to left child node struct TreeNode *right; // Pointer to right child node } TreeNode; /* Constructor */ TreeNode *newTreeNode(int val) { TreeNode *node; node = (TreeNode *)malloc(sizeof(TreeNode)); node->val = val; node->height = 0; node->left = NULL; node->right = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* Binary tree node */ class TreeNode(val _val: Int) { // Node value val left: TreeNode? = null // Reference to left child node val right: TreeNode? = null // Reference to right child node } ``` === "Ruby" ```ruby title="" ### Binary tree node class ### class TreeNode attr_accessor :val # Node value attr_accessor :left # Reference to left child node attr_accessor :right # Reference to right child node def initialize(val) @val = val end end ``` Each node has two references (pointers), pointing respectively to the left-child node and right-child node. This node is called the parent node of these two child nodes. When given a node of a binary tree, we call the tree formed by this node's left child and all nodes below it the left subtree of this node. Similarly, the right subtree can be defined. **In a binary tree, except leaf nodes, all other nodes contain child nodes and non-empty subtrees.** As shown in the figure below, if "Node 2" is regarded as a parent node, its left and right child nodes are "Node 4" and "Node 5" respectively. The left subtree is formed by "Node 4" and all nodes beneath it, while the right subtree is formed by "Node 5" and all nodes beneath it. ![Parent Node, child Node, subtree](binary_tree.assets/binary_tree_definition.png) ## Common Terminology of Binary Trees The commonly used terminology of binary trees is shown in the figure below. - Root node: The node at the top level of a binary tree, which does not have a parent node. - Leaf node: A node that does not have any child nodes, with both of its pointers pointing to `None`. - Edge: A line segment that connects two nodes, representing a reference (pointer) between the nodes. - The level of a node: It increases from top to bottom, with the root node being at level 1. - The degree of a node: The number of child nodes that a node has. In a binary tree, the degree can be 0, 1, or 2. - The height of a binary tree: The number of edges from the root node to the farthest leaf node. - The depth of a node: The number of edges from the root node to the node. - The height of a node: The number of edges from the farthest leaf node to the node. ![Common Terminology of Binary Trees](binary_tree.assets/binary_tree_terminology.png) !!! tip Please note that we usually define "height" and "depth" as "the number of edges traversed", but some questions or textbooks may define them as "the number of nodes traversed". In this case, both height and depth need to be incremented by 1. ## Basic Operations of Binary Trees ### Initializing a Binary Tree Similar to a linked list, the initialization of a binary tree involves first creating the nodes and then establishing the references (pointers) between them. === "Python" ```python title="binary_tree.py" # Initializing a binary tree # Initializing nodes n1 = TreeNode(val=1) n2 = TreeNode(val=2) n3 = TreeNode(val=3) n4 = TreeNode(val=4) n5 = TreeNode(val=5) # Linking references (pointers) between nodes n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` === "C++" ```cpp title="binary_tree.cpp" /* Initializing a binary tree */ // Initializing nodes 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); // Linking references (pointers) between nodes n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; ``` === "Java" ```java title="binary_tree.java" // Initializing nodes 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); // Linking references (pointers) between nodes n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "C#" ```csharp title="binary_tree.cs" /* Initializing a binary tree */ // Initializing nodes TreeNode n1 = new(1); TreeNode n2 = new(2); TreeNode n3 = new(3); TreeNode n4 = new(4); TreeNode n5 = new(5); // Linking references (pointers) between nodes n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "Go" ```go title="binary_tree.go" /* Initializing a binary tree */ // Initializing nodes n1 := NewTreeNode(1) n2 := NewTreeNode(2) n3 := NewTreeNode(3) n4 := NewTreeNode(4) n5 := NewTreeNode(5) // Linking references (pointers) between nodes n1.Left = n2 n1.Right = n3 n2.Left = n4 n2.Right = n5 ``` === "Swift" ```swift title="binary_tree.swift" // Initializing nodes let n1 = TreeNode(x: 1) let n2 = TreeNode(x: 2) let n3 = TreeNode(x: 3) let n4 = TreeNode(x: 4) let n5 = TreeNode(x: 5) // Linking references (pointers) between nodes n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` === "JS" ```javascript title="binary_tree.js" /* Initializing a binary tree */ // Initializing nodes let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // Linking references (pointers) between nodes n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "TS" ```typescript title="binary_tree.ts" /* Initializing a binary tree */ // Initializing nodes let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // Linking references (pointers) between nodes n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "Dart" ```dart title="binary_tree.dart" /* Initializing a binary tree */ // Initializing nodes 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); // Linking references (pointers) between nodes n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "Rust" ```rust title="binary_tree.rs" // Initializing nodes let n1 = TreeNode::new(1); let n2 = TreeNode::new(2); let n3 = TreeNode::new(3); let n4 = TreeNode::new(4); let n5 = TreeNode::new(5); // Linking references (pointers) between nodes n1.borrow_mut().left = Some(n2.clone()); n1.borrow_mut().right = Some(n3); n2.borrow_mut().left = Some(n4); n2.borrow_mut().right = Some(n5); ``` === "C" ```c title="binary_tree.c" /* Initializing a binary tree */ // Initializing nodes TreeNode *n1 = newTreeNode(1); TreeNode *n2 = newTreeNode(2); TreeNode *n3 = newTreeNode(3); TreeNode *n4 = newTreeNode(4); TreeNode *n5 = newTreeNode(5); // Linking references (pointers) between nodes n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; ``` === "Kotlin" ```kotlin title="binary_tree.kt" // Initializing nodes val n1 = TreeNode(1) val n2 = TreeNode(2) val n3 = TreeNode(3) val n4 = TreeNode(4) val n5 = TreeNode(5) // Linking references (pointers) between nodes n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` === "Ruby" ```ruby title="binary_tree.rb" # Initializing a binary tree # Initializing nodes n1 = TreeNode.new(1) n2 = TreeNode.new(2) n3 = TreeNode.new(3) n4 = TreeNode.new(4) n5 = TreeNode.new(5) # Linking references (pointers) between nodes n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Inserting and Removing Nodes Similar to a linked list, inserting and removing nodes in a binary tree can be achieved by modifying pointers. The figure below provides an example. ![Inserting and removing nodes in a binary tree](binary_tree.assets/binary_tree_add_remove.png) === "Python" ```python title="binary_tree.py" # Inserting and removing nodes p = TreeNode(0) # Inserting node P between n1 -> n2 n1.left = p p.left = n2 # Removing node P n1.left = n2 ``` === "C++" ```cpp title="binary_tree.cpp" /* Inserting and removing nodes */ TreeNode* P = new TreeNode(0); // Inserting node P between n1 and n2 n1->left = P; P->left = n2; // Removing node P n1->left = n2; ``` === "Java" ```java title="binary_tree.java" TreeNode P = new TreeNode(0); // Inserting node P between n1 and n2 n1.left = P; P.left = n2; // Removing node P n1.left = n2; ``` === "C#" ```csharp title="binary_tree.cs" /* Inserting and removing nodes */ TreeNode P = new(0); // Inserting node P between n1 and n2 n1.left = P; P.left = n2; // Removing node P n1.left = n2; ``` === "Go" ```go title="binary_tree.go" /* Inserting and removing nodes */ // Inserting node P between n1 and n2 p := NewTreeNode(0) n1.Left = p p.Left = n2 // Removing node P n1.Left = n2 ``` === "Swift" ```swift title="binary_tree.swift" let P = TreeNode(x: 0) // Inserting node P between n1 and n2 n1.left = P P.left = n2 // Removing node P n1.left = n2 ``` === "JS" ```javascript title="binary_tree.js" /* Inserting and removing nodes */ let P = new TreeNode(0); // Inserting node P between n1 and n2 n1.left = P; P.left = n2; // Removing node P n1.left = n2; ``` === "TS" ```typescript title="binary_tree.ts" /* Inserting and removing nodes */ const P = new TreeNode(0); // Inserting node P between n1 and n2 n1.left = P; P.left = n2; // Removing node P n1.left = n2; ``` === "Dart" ```dart title="binary_tree.dart" /* Inserting and removing nodes */ TreeNode P = new TreeNode(0); // Inserting node P between n1 and n2 n1.left = P; P.left = n2; // Removing node P n1.left = n2; ``` === "Rust" ```rust title="binary_tree.rs" let p = TreeNode::new(0); // Inserting node P between n1 and n2 n1.borrow_mut().left = Some(p.clone()); p.borrow_mut().left = Some(n2.clone()); // Removing node P n1.borrow_mut().left = Some(n2); ``` === "C" ```c title="binary_tree.c" /* Inserting and removing nodes */ TreeNode *P = newTreeNode(0); // Inserting node P between n1 and n2 n1->left = P; P->left = n2; // Removing node P n1->left = n2; ``` === "Kotlin" ```kotlin title="binary_tree.kt" val P = TreeNode(0) // Inserting node P between n1 and n2 n1.left = P P.left = n2 // Removing node P n1.left = n2 ``` === "Ruby" ```ruby title="binary_tree.rb" # Inserting and removing nodes _p = TreeNode.new(0) # Inserting node _p between n1 and n2 n1.left = _p _p.left = n2 # Removing node _p n1.left = n2 ``` ??? pythontutor "Code Visualization" https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%B8%8E%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%E5%9C%A8%20n1%20-%3E%20n2%20%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20n2&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false !!! tip It should be noted that inserting nodes may change the original logical structure of the binary tree, while removing nodes typically involves removing the node and all its subtrees. Therefore, in a binary tree, insertion and removal are usually performed through a set of operations to achieve meaningful outcomes. ## Common Types of Binary Trees ### Perfect Binary Tree As shown in the figure below, a perfect binary tree has all levels completely filled with nodes. In a perfect binary tree, leaf nodes have a degree of $0$, while all other nodes have a degree of $2$. If the tree height is $h$, the total number of nodes is $2^{h+1} - 1$, exhibiting a standard exponential relationship that reflects the common phenomenon of cell division in nature. !!! tip Please note that in the Chinese community, a perfect binary tree is often referred to as a full binary tree. ![Perfect binary tree](binary_tree.assets/perfect_binary_tree.png) ### Complete Binary Tree As shown in the figure below, a complete binary tree only allows the bottom level to be incompletely filled, and the nodes at the bottom level must be filled continuously from left to right. Note that a perfect binary tree is also a complete binary tree. ![Complete binary tree](binary_tree.assets/complete_binary_tree.png) ### Full Binary Tree As shown in the figure below, in a full binary tree, all nodes except leaf nodes have two child nodes. ![Full binary tree](binary_tree.assets/full_binary_tree.png) ### Balanced Binary Tree As shown in the figure below, in a balanced binary tree, the absolute difference between the height of the left and right subtrees of any node does not exceed 1. ![Balanced binary tree](binary_tree.assets/balanced_binary_tree.png) ## Degeneration of Binary Trees The figure below shows the ideal and degenerate structures of binary trees. When every level of a binary tree is filled, it reaches the "perfect binary tree" state; when all nodes are biased toward one side, the binary tree degenerates into a "linked list". - A perfect binary tree is the ideal case, fully leveraging the "divide and conquer" advantage of binary trees. - A linked list represents the other extreme, where all operations become linear operations with time complexity degrading to $O(n)$. ![The Best and Worst Structures of Binary Trees](binary_tree.assets/binary_tree_best_worst_cases.png) As shown in the table below, in the best and worst structures, the binary tree achieves either maximum or minimum values for leaf node count, total number of nodes, and height.

Table   The Best and Worst Structures of Binary Trees

| | Perfect binary tree | Linked list | | ----------------------------------------------- | ------------------- | ----------- | | Number of nodes at level $i$ | $2^{i-1}$ | $1$ | | Number of leaf nodes in a tree with height $h$ | $2^h$ | $1$ | | Total number of nodes in a tree with height $h$ | $2^{h+1} - 1$ | $h + 1$ | | Height of a tree with $n$ total nodes | $\log_2 (n+1) - 1$ | $n - 1$ | ================================================ FILE: en/docs/chapter_tree/binary_tree_traversal.md ================================================ # Binary Tree Traversal From a physical structure perspective, a tree is a data structure based on linked lists. Hence, its traversal method involves accessing nodes one by one through pointers. However, a tree is a non-linear data structure, which makes traversing a tree more complex than traversing a linked list, requiring the assistance of search algorithms. The common traversal methods for binary trees include level-order traversal, pre-order traversal, in-order traversal, and post-order traversal. ## Level-Order Traversal As shown in the figure below, level-order traversal traverses the binary tree from top to bottom, layer by layer. Within each level, it visits nodes from left to right. Level-order traversal is essentially breadth-first traversal, also known as breadth-first search (BFS), which embodies a "expanding outward circle by circle" layer-by-layer traversal method. ![Level-order traversal of a binary tree](binary_tree_traversal.assets/binary_tree_bfs.png) ### Code Implementation Breadth-first traversal is typically implemented with the help of a "queue". The queue follows the "first in, first out" rule, while breadth-first traversal follows the "layer-by-layer progression" rule; the underlying ideas of the two are consistent. The implementation code is as follows: ```src [file]{binary_tree_bfs}-[class]{}-[func]{level_order} ``` ### Complexity Analysis - **Time complexity is $O(n)$**: All nodes are visited once, using $O(n)$ time, where $n$ is the number of nodes. - **Space complexity is $O(n)$**: In the worst case, i.e., a full binary tree, before traversing to the bottom level, the queue contains at most $(n + 1) / 2$ nodes simultaneously, occupying $O(n)$ space. ## Preorder, Inorder, and Postorder Traversal Correspondingly, preorder, inorder, and postorder traversals all belong to depth-first traversal, also known as depth-first search (DFS), which embodies a "first go to the end, then backtrack and continue" traversal method. The figure below shows how depth-first traversal works on a binary tree. **Depth-first traversal is like "walking" around the perimeter of the entire binary tree**, encountering three positions at each node, corresponding to preorder, inorder, and postorder traversal. ![Preorder, inorder, and postorder traversal of a binary tree](binary_tree_traversal.assets/binary_tree_dfs.png) ### Code Implementation Depth-first search is usually implemented based on recursion: ```src [file]{binary_tree_dfs}-[class]{}-[func]{post_order} ``` !!! tip Depth-first search can also be implemented based on iteration, interested readers can study this on their own. The figure below shows the recursive process of preorder traversal of a binary tree, which can be divided into two opposite parts: "recursion" and "return". 1. "Recursion" means opening a new method, where the program accesses the next node in this process. 2. "Return" means the function returns, indicating that the current node has been fully visited. === "<1>" ![The recursive process of preorder traversal](binary_tree_traversal.assets/preorder_step1.png) === "<2>" ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png) === "<3>" ![preorder_step3](binary_tree_traversal.assets/preorder_step3.png) === "<4>" ![preorder_step4](binary_tree_traversal.assets/preorder_step4.png) === "<5>" ![preorder_step5](binary_tree_traversal.assets/preorder_step5.png) === "<6>" ![preorder_step6](binary_tree_traversal.assets/preorder_step6.png) === "<7>" ![preorder_step7](binary_tree_traversal.assets/preorder_step7.png) === "<8>" ![preorder_step8](binary_tree_traversal.assets/preorder_step8.png) === "<9>" ![preorder_step9](binary_tree_traversal.assets/preorder_step9.png) === "<10>" ![preorder_step10](binary_tree_traversal.assets/preorder_step10.png) === "<11>" ![preorder_step11](binary_tree_traversal.assets/preorder_step11.png) ### Complexity Analysis - **Time complexity is $O(n)$**: All nodes are visited once, using $O(n)$ time. - **Space complexity is $O(n)$**: In the worst case, i.e., the tree degenerates into a linked list, the recursion depth reaches $n$, and the system occupies $O(n)$ stack frame space. ================================================ FILE: en/docs/chapter_tree/index.md ================================================ # Tree ![Tree](../assets/covers/chapter_tree.jpg) !!! abstract Towering trees are full of vitality, with deep roots and lush leaves, spreading branches and flourishing. They show us the vivid form of divide and conquer in data. ================================================ FILE: en/docs/chapter_tree/summary.md ================================================ # Summary ### Key Review - A binary tree is a non-linear data structure that embodies the divide-and-conquer logic of "one divides into two". Each binary tree node contains a value and two pointers, which respectively point to its left and right child nodes. - For a certain node in a binary tree, the tree formed by its left (right) child node and all nodes below is called the left (right) subtree of that node. - Related terminology of binary trees includes root node, leaf node, level, degree, edge, height, and depth. - The initialization, node insertion, and node removal operations of binary trees are similar to those of linked lists. - Common types of binary trees include perfect binary trees, complete binary trees, full binary trees, and balanced binary trees. The perfect binary tree is the ideal state, while the linked list is the worst state after degradation. - A binary tree can be represented using an array by arranging node values and empty slots in level-order traversal sequence, and implementing pointers based on the index mapping relationship between parent and child nodes. - Level-order traversal of a binary tree is a breadth-first search method, embodying a layer-by-layer traversal approach of "expanding outward circle by circle", typically implemented using a queue. - Preorder, inorder, and postorder traversals all belong to depth-first search, embodying a traversal approach of "first go to the end, then backtrack and continue", typically implemented using recursion. - A binary search tree is an efficient data structure for element searching, with search, insertion, and removal operations all having time complexity of $O(\log n)$. When a binary search tree degenerates into a linked list, all time complexities degrade to $O(n)$. - An AVL tree, also known as a balanced binary search tree, ensures the tree remains balanced after continuous node insertions and removals through rotation operations. - Rotation operations in AVL trees include right rotation, left rotation, left rotation then right rotation, and right rotation then left rotation. After inserting or removing nodes, AVL trees perform rotation operations from bottom to top to restore the tree to balance. ### Q & A **Q**: For a binary tree with only one node, are both the height of the tree and the depth of the root node $0$? Yes, because height and depth are typically defined as "the number of edges passed." **Q**: The insertion and removal in a binary tree are generally accomplished by a set of operations. What does "a set of operations" refer to here? Does it imply releasing the resources of the child nodes? Taking the binary search tree as an example, the operation of removing a node needs to be handled in three different scenarios, each requiring multiple steps of node operations. **Q**: Why does DFS traversal of binary trees have three orders: preorder, inorder, and postorder, and what are their uses? Similar to forward and reverse traversal of arrays, preorder, inorder, and postorder traversals are three methods of binary tree traversal that allow us to obtain a traversal result in a specific order. For example, in a binary search tree, since nodes satisfy the relationship `left child node value < root node value < right child node value`, we only need to traverse the tree with the priority of "left $\rightarrow$ root $\rightarrow$ right" to obtain an ordered node sequence. **Q**: In a right rotation operation handling the relationship between unbalanced nodes `node`, `child`, and `grand_child`, doesn't the connection between `node` and its parent node get lost after the right rotation? We need to view this problem from a recursive perspective. The right rotation operation `right_rotate(root)` passes in the root node of the subtree and eventually returns the root node of the subtree after rotation with `return child`. The connection between the subtree's root node and its parent node is completed after the function returns, which is not within the maintenance scope of the right rotation operation. **Q**: In C++, functions are divided into `private` and `public` sections. What considerations are there for this? Why are the `height()` function and the `updateHeight()` function placed in `public` and `private`, respectively? It mainly depends on the method's usage scope. If a method is only used within the class, then it is designed as `private`. For example, calling `updateHeight()` alone by the user makes no sense, as it is only a step in insertion or removal operations. However, `height()` is used to access node height, similar to `vector.size()`, so it is set to `public` for ease of use. **Q**: How do you build a binary search tree from a set of input data? Is the choice of root node very important? Yes, the method for building a tree is provided in the `build_tree()` method in the binary search tree code. As for the choice of root node, we typically sort the input data, then select the middle element as the root node, and recursively build the left and right subtrees. This approach maximizes the tree's balance. **Q**: In Java, do you always have to use the `equals()` method for string comparison? In Java, for primitive data types, `==` is used to compare whether the values of two variables are equal. For reference types, the working principles of the two symbols are different. - `==`: Used to compare whether two variables point to the same object, i.e., whether their positions in memory are the same. - `equals()`: Used to compare whether the values of two objects are equal. Therefore, if we want to compare values, we should use `equals()`. However, strings initialized via `String a = "hi"; String b = "hi";` are stored in the string constant pool and point to the same object, so `a == b` can also be used to compare the contents of the two strings. **Q**: Before reaching the bottom level, is the number of nodes in the queue $2^h$ in breadth-first traversal? Yes, for example, a full binary tree with height $h = 2$ has a total of $n = 7$ nodes, then the bottom level has $4 = 2^h = (n + 1) / 2$ nodes. ================================================ FILE: en/docs/index.html ================================================

Data structures and algorithms crash course with animated illustrations and off-the-shelf code

Dive in GitHub

500 animated illustrations, 14 programming languages, and 3000 community Q&As to help you dive into data structures and algorithms.

Endorsements

“An easy-to-understand book on data structures and algorithms, which guides readers to learn by minds-on and hands-on. Strongly recommended for algorithm beginners!”

—— Junhui Deng, Professor, Department of computer science and technology, Tsinghua University

“If I had 'Hello Algo' when I was learning data structures and algorithms, it would have been 10 times easier!”

—— Mu Li, Senior principal scientist, Amazon

Animated illustrations

It’s crafted for a smooth learning experience.

"A picture is worth a thousand words."

Off-the-shelf code

One click to run code in multiple languages.

"Talk is cheap. Show me the code."

Learning together

Don’t hesitate to ask or share your thoughts.

"Learning by teaching."

Translators

The English version of this book was reviewed by the following translators. We thank them for their time and effort!

Code translators

The multilingual code versions of this book were made possible by the following translators. We appreciate their efforts and contributions!

Contributors

This book has been refined by the efforts of over 200 contributors. We sincerely thank them for their invaluable time and contributions!

Contributors
================================================ FILE: en/docs/index.md ================================================ # Hello Algo Data structures and algorithms crash course with animated illustrations and off-the-shelf code [Dive in](chapter_hello_algo/) ================================================ FILE: en/mkdocs.yml ================================================ # Config inheritance INHERIT: ../mkdocs.yml # Project information site_name: Hello Algo site_url: https://www.hello-algo.com/en/ site_description: "Data Structures and Algorithms Crash Course with Animated Illustrations and Off-the-Shelf Code" docs_dir: ../build/en/docs site_dir: ../site/en # Repository edit_uri: tree/main/en/docs version: 1.3.0 # Configuration theme: custom_dir: ../build/overrides language: en font: text: Inter palette: - scheme: default primary: white accent: teal toggle: icon: material/theme-light-dark name: Dark mode - scheme: slate primary: black accent: teal toggle: icon: material/theme-light-dark name: Light mode extra: status: new: Recently Added # Page tree nav: - Before Starting: - chapter_hello_algo/index.md - Chapter 0. Preface: # [icon: material/book-open-outline] - chapter_preface/index.md - 0.1 About This Book: chapter_preface/about_the_book.md - 0.2 How to Use This Book: chapter_preface/suggestions.md - 0.3 Summary: chapter_preface/summary.md - Chapter 1. Encounter With Algorithms: # [icon: material/calculator-variant-outline] - chapter_introduction/index.md - 1.1 Algorithms Are Everywhere: chapter_introduction/algorithms_are_everywhere.md - 1.2 What Is an Algorithm: chapter_introduction/what_is_dsa.md - 1.3 Summary: chapter_introduction/summary.md - Chapter 2. Complexity Analysis: # [icon: material/timer-sand] - chapter_computational_complexity/index.md - 2.1 Algorithm Efficiency Evaluation: chapter_computational_complexity/performance_evaluation.md - 2.2 Iteration and Recursion: chapter_computational_complexity/iteration_and_recursion.md - 2.3 Time Complexity: chapter_computational_complexity/time_complexity.md - 2.4 Space Complexity: chapter_computational_complexity/space_complexity.md - 2.5 Summary: chapter_computational_complexity/summary.md - Chapter 3. Data Structures: # [icon: material/shape-outline] - chapter_data_structure/index.md - 3.1 Classification of Data Structures: chapter_data_structure/classification_of_data_structure.md - 3.2 Basic Data Types: chapter_data_structure/basic_data_types.md - 3.3 Number Encoding *: chapter_data_structure/number_encoding.md - 3.4 Character Encoding *: chapter_data_structure/character_encoding.md - 3.5 Summary: chapter_data_structure/summary.md - Chapter 4. Array and Linked List: # [icon: material/view-list-outline] - chapter_array_and_linkedlist/index.md - 4.1 Array: chapter_array_and_linkedlist/array.md - 4.2 Linked List: chapter_array_and_linkedlist/linked_list.md - 4.3 List: chapter_array_and_linkedlist/list.md - 4.4 Memory and Cache *: chapter_array_and_linkedlist/ram_and_cache.md - 4.5 Summary: chapter_array_and_linkedlist/summary.md - Chapter 5. Stack and Queue: # [icon: material/stack-overflow] - chapter_stack_and_queue/index.md - 5.1 Stack: chapter_stack_and_queue/stack.md - 5.2 Queue: chapter_stack_and_queue/queue.md - 5.3 Double-Ended Queue: chapter_stack_and_queue/deque.md - 5.4 Summary: chapter_stack_and_queue/summary.md - Chapter 6. Hashing: # [icon: material/table-search] - chapter_hashing/index.md - 6.1 Hash Table: chapter_hashing/hash_map.md - 6.2 Hash Collision: chapter_hashing/hash_collision.md - 6.3 Hash Algorithm: chapter_hashing/hash_algorithm.md - 6.4 Summary: chapter_hashing/summary.md - Chapter 7. Tree: # [icon: material/graph-outline] - chapter_tree/index.md - 7.1 Binary Tree: chapter_tree/binary_tree.md - 7.2 Binary Tree Traversal: chapter_tree/binary_tree_traversal.md - 7.3 Array Representation of Tree: chapter_tree/array_representation_of_tree.md - 7.4 Binary Search Tree: chapter_tree/binary_search_tree.md - 7.5 AVL Tree *: chapter_tree/avl_tree.md - 7.6 Summary: chapter_tree/summary.md - Chapter 8. Heap: # [icon: material/family-tree] - chapter_heap/index.md - 8.1 Heap: chapter_heap/heap.md - 8.2 Building a Heap: chapter_heap/build_heap.md - 8.3 Top-K Problem: chapter_heap/top_k.md - 8.4 Summary: chapter_heap/summary.md - Chapter 9. Graph: # [icon: material/graphql] - chapter_graph/index.md - 9.1 Graph: chapter_graph/graph.md - 9.2 Basic Operations on Graphs: chapter_graph/graph_operations.md - 9.3 Graph Traversal: chapter_graph/graph_traversal.md - 9.4 Summary: chapter_graph/summary.md - Chapter 10. Searching: # [icon: material/text-search] - chapter_searching/index.md - 10.1 Binary Search: chapter_searching/binary_search.md - 10.2 Binary Search Insertion: chapter_searching/binary_search_insertion.md - 10.3 Binary Search Edge Cases: chapter_searching/binary_search_edge.md - 10.4 Hash Optimization Strategy: chapter_searching/replace_linear_by_hashing.md - 10.5 Search Algorithms Revisited: chapter_searching/searching_algorithm_revisited.md - 10.6 Summary: chapter_searching/summary.md - Chapter 11. Sorting: # [icon: material/sort-ascending] - chapter_sorting/index.md - 11.1 Sorting Algorithms: chapter_sorting/sorting_algorithm.md - 11.2 Selection Sort: chapter_sorting/selection_sort.md - 11.3 Bubble Sort: chapter_sorting/bubble_sort.md - 11.4 Insertion Sort: chapter_sorting/insertion_sort.md - 11.5 Quick Sort: chapter_sorting/quick_sort.md - 11.6 Merge Sort: chapter_sorting/merge_sort.md - 11.7 Heap Sort: chapter_sorting/heap_sort.md - 11.8 Bucket Sort: chapter_sorting/bucket_sort.md - 11.9 Counting Sort: chapter_sorting/counting_sort.md - 11.10 Radix Sort: chapter_sorting/radix_sort.md - 11.11 Summary: chapter_sorting/summary.md - Chapter 12. Divide and Conquer: # [icon: material/set-split] - chapter_divide_and_conquer/index.md - 12.1 Divide and Conquer Algorithms: chapter_divide_and_conquer/divide_and_conquer.md - 12.2 Divide and Conquer Search Strategy: chapter_divide_and_conquer/binary_search_recur.md - 12.3 Building a Binary Tree Problem: chapter_divide_and_conquer/build_binary_tree_problem.md - 12.4 Hanoi Tower Problem: chapter_divide_and_conquer/hanota_problem.md - 12.5 Summary: chapter_divide_and_conquer/summary.md - Chapter 13. Backtracking: # [icon: material/map-marker-path] - chapter_backtracking/index.md - 13.1 Backtracking Algorithm: chapter_backtracking/backtracking_algorithm.md - 13.2 Permutations Problem: chapter_backtracking/permutations_problem.md - 13.3 Subset-Sum Problem: chapter_backtracking/subset_sum_problem.md - 13.4 N-Queens Problem: chapter_backtracking/n_queens_problem.md - 13.5 Summary: chapter_backtracking/summary.md - Chapter 14. Dynamic Programming: # [icon: material/table-pivot] - chapter_dynamic_programming/index.md - 14.1 Introduction to Dynamic Programming: chapter_dynamic_programming/intro_to_dynamic_programming.md - 14.2 Characteristics of Dynamic Programming Problems: chapter_dynamic_programming/dp_problem_features.md - 14.3 Dynamic Programming Problem-Solving Approach: chapter_dynamic_programming/dp_solution_pipeline.md - 14.4 0-1 Knapsack Problem: chapter_dynamic_programming/knapsack_problem.md - 14.5 Unbounded Knapsack Problem: chapter_dynamic_programming/unbounded_knapsack_problem.md - 14.6 Edit Distance Problem: chapter_dynamic_programming/edit_distance_problem.md - 14.7 Summary: chapter_dynamic_programming/summary.md - Chapter 15. Greedy: # [icon: material/head-heart-outline] - chapter_greedy/index.md - 15.1 Greedy Algorithm: chapter_greedy/greedy_algorithm.md - 15.2 Fractional Knapsack Problem: chapter_greedy/fractional_knapsack_problem.md - 15.3 Maximum Capacity Problem: chapter_greedy/max_capacity_problem.md - 15.4 Maximum Product Cutting Problem: chapter_greedy/max_product_cutting_problem.md - 15.5 Summary: chapter_greedy/summary.md - Chapter 16. Appendix: # [icon: material/help-circle-outline] - chapter_appendix/index.md - 16.1 Programming Environment Installation: chapter_appendix/installation.md - 16.2 Contributing Together: chapter_appendix/contribution.md - 16.3 Terminology Table: chapter_appendix/terminology.md - References: - chapter_reference/index.md ================================================ FILE: giscus.json ================================================ { "defaultCommentOrder": "newest", "origins": [ "https://www.hello-algo.com", "https://hello-algo.com" ] } ================================================ FILE: ja/README.md ================================================

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

简体中文繁體中文English | 日本語 | Русский

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

Warp-Github-LG-02

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

## ライセンス このリポジトリに含まれるテキスト、コード、画像、写真、動画は、[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) の下でライセンスされています。 ================================================ FILE: ja/codes/c/.gitignore ================================================ # Ignore all * # Unignore all with extensions !*.* # Unignore all dirs !*/ *.dSYM/ build/ ================================================ FILE: ja/codes/c/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(hello_algo C) set(CMAKE_C_STANDARD 11) include_directories(./include) add_subdirectory(chapter_computational_complexity) add_subdirectory(chapter_array_and_linkedlist) add_subdirectory(chapter_stack_and_queue) add_subdirectory(chapter_hashing) add_subdirectory(chapter_tree) add_subdirectory(chapter_heap) add_subdirectory(chapter_graph) add_subdirectory(chapter_searching) add_subdirectory(chapter_sorting) add_subdirectory(chapter_divide_and_conquer) add_subdirectory(chapter_backtracking) add_subdirectory(chapter_dynamic_programming) add_subdirectory(chapter_greedy) ================================================ FILE: ja/codes/c/chapter_array_and_linkedlist/CMakeLists.txt ================================================ add_executable(array array.c) add_executable(linked_list linked_list.c) add_executable(my_list my_list.c) ================================================ FILE: ja/codes/c/chapter_array_and_linkedlist/array.c ================================================ /** * File: array.c * Created Time: 2022-12-20 * Author: MolDuM (moldum@163.com) */ #include "../utils/common.h" /* 要素へランダムアクセス */ int randomAccess(int *nums, int size) { // 区間 [0, size) からランダムに 1 つの数を選ぶ int randomIndex = rand() % size; // ランダムな要素を取得して返す int randomNum = nums[randomIndex]; return randomNum; } /* 配列長を拡張する */ int *extend(int *nums, int size, int enlarge) { // 拡張後の長さを持つ配列を初期化する int *res = (int *)malloc(sizeof(int) * (size + enlarge)); // 元の配列の全要素を新しい配列にコピー for (int i = 0; i < size; i++) { res[i] = nums[i]; } // 拡張後の領域を初期化する for (int i = size; i < size + enlarge; i++) { res[i] = 0; } // 拡張後の新しい配列を返す return res; } /* 配列の index 番目に要素 num を挿入 */ void insert(int *nums, int size, int num, int index) { // インデックス index 以降の全要素を 1 つ後ろへ移動する for (int i = size - 1; i > index; i--) { nums[i] = nums[i - 1]; } // index の要素に num を代入する nums[index] = num; } /* index の要素を削除する */ // 注意: stdio.h が remove 識別子を使用している void removeItem(int *nums, int size, int index) { // インデックス index より後ろの全要素を 1 つ前へ移動する for (int i = index; i < size - 1; i++) { nums[i] = nums[i + 1]; } } /* 配列を走査 */ void traverse(int *nums, int size) { int count = 0; // インデックスで配列を走査 for (int i = 0; i < size; i++) { count += nums[i]; } } /* 配列内で指定要素を探す */ int find(int *nums, int size, int target) { for (int i = 0; i < size; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ int main() { /* 配列を初期化 */ int size = 5; int arr[5]; printf("配列 arr = "); printArray(arr, size); int nums[] = {1, 3, 2, 5, 4}; printf("配列 nums = "); printArray(nums, size); /* ランダムアクセス */ int randomNum = randomAccess(nums, size); printf("nums からランダムな要素 %d を取得", randomNum); /* 長さを拡張 */ int enlarge = 3; int *res = extend(nums, size, enlarge); size += enlarge; printf("配列の長さを 8 に拡張し、nums = "); printArray(res, size); /* 要素を挿入する */ insert(res, size, 6, 3); printf("インデックス 3 に数字 6 を挿入し、nums = "); printArray(res, size); /* 要素を削除 */ removeItem(res, size, 2); printf("インデックス 2 の要素を削除し、nums = "); printArray(res, size); /* 配列を走査 */ traverse(res, size); /* 要素を探索する */ int index = find(res, size, 3); printf("res 内で要素 3 を検索し、インデックス = %d\n", index); /* メモリを解放する */ free(res); return 0; } ================================================ FILE: ja/codes/c/chapter_array_and_linkedlist/linked_list.c ================================================ /** * File: linked_list.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* 連結リストでノード n0 の後ろにノード P を挿入する */ void insert(ListNode *n0, ListNode *P) { ListNode *n1 = n0->next; P->next = n1; n0->next = P; } /* 連結リストでノード n0 の直後のノードを削除する */ // 注意: stdio.h が remove 識別子を使用している void removeItem(ListNode *n0) { if (!n0->next) return; // n0 -> P -> n1 ListNode *P = n0->next; ListNode *n1 = P->next; n0->next = n1; // メモリを解放する free(P); } /* 連結リスト内で index 番目のノードにアクセス */ ListNode *access(ListNode *head, int index) { for (int i = 0; i < index; i++) { if (head == NULL) return NULL; head = head->next; } return head; } /* 連結リストで値が target の最初のノードを探す */ int find(ListNode *head, int target) { int index = 0; while (head) { if (head->val == target) return index; head = head->next; index++; } return -1; } /* Driver Code */ int main() { /* 連結リストを初期化 */ // 各ノードを初期化 ListNode *n0 = newListNode(1); ListNode *n1 = newListNode(3); ListNode *n2 = newListNode(2); ListNode *n3 = newListNode(5); ListNode *n4 = newListNode(4); // ノード間の参照を構築する n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; printf("初期化後の連結リストは\r\n"); printLinkedList(n0); /* ノードを挿入 */ insert(n0, newListNode(0)); printf("ノード挿入後の連結リストは\r\n"); printLinkedList(n0); /* ノードを削除 */ removeItem(n0); printf("ノード削除後の連結リストは\r\n"); printLinkedList(n0); /* ノードにアクセス */ ListNode *node = access(n0, 3); printf("連結リストのインデックス 3 にあるノードの値 = %d\r\n", node->val); /* ノードを探索 */ int index = find(n0, 2); printf("連結リスト内で値が 2 のノードのインデックス = %d\r\n", index); // メモリを解放する freeMemoryLinkedList(n0); return 0; } ================================================ FILE: ja/codes/c/chapter_array_and_linkedlist/my_list.c ================================================ /** * File: my_list.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* リストクラス */ typedef struct { int *arr; // 配列(リスト要素を格納) int capacity; // リスト容量 int size; // リストのサイズ int extendRatio; // リストが拡張されるたびの倍率 } MyList; void extendCapacity(MyList *nums); /* コンストラクタ */ MyList *newMyList() { MyList *nums = malloc(sizeof(MyList)); nums->capacity = 10; nums->arr = malloc(sizeof(int) * nums->capacity); nums->size = 0; nums->extendRatio = 2; return nums; } /* デストラクタ */ void delMyList(MyList *nums) { free(nums->arr); free(nums); } /* リストの長さを取得 */ int size(MyList *nums) { return nums->size; } /* リスト容量を取得する */ int capacity(MyList *nums) { return nums->capacity; } /* 要素にアクセス */ int get(MyList *nums, int index) { assert(index >= 0 && index < nums->size); return nums->arr[index]; } /* 要素を更新 */ void set(MyList *nums, int index, int num) { assert(index >= 0 && index < nums->size); nums->arr[index] = num; } /* 末尾に要素を追加 */ void add(MyList *nums, int num) { if (size(nums) == capacity(nums)) { extendCapacity(nums); // 容量を拡張 } nums->arr[size(nums)] = num; nums->size++; } /* 中間に要素を挿入 */ void insert(MyList *nums, int index, int num) { assert(index >= 0 && index < size(nums)); // 要素数が容量を超えると、拡張機構が発動する if (size(nums) == capacity(nums)) { extendCapacity(nums); // 容量を拡張 } for (int i = size(nums); i > index; --i) { nums->arr[i] = nums->arr[i - 1]; } nums->arr[index] = num; nums->size++; } /* 要素を削除 */ // 注意: stdio.h が remove 識別子を使用している int removeItem(MyList *nums, int index) { assert(index >= 0 && index < size(nums)); int num = nums->arr[index]; for (int i = index; i < size(nums) - 1; i++) { nums->arr[i] = nums->arr[i + 1]; } nums->size--; return num; } /* リストの拡張 */ void extendCapacity(MyList *nums) { // 先に領域を確保する int newCapacity = capacity(nums) * nums->extendRatio; int *extend = (int *)malloc(sizeof(int) * newCapacity); int *temp = nums->arr; // 古いデータを新しいデータにコピー for (int i = 0; i < size(nums); i++) extend[i] = nums->arr[i]; // 古いデータを解放する free(temp); // 新しいデータに更新 nums->arr = extend; nums->capacity = newCapacity; } /* 出力用にリストを Array に変換 */ int *toArray(MyList *nums) { return nums->arr; } /* Driver Code */ int main() { /* リストを初期化 */ MyList *nums = newMyList(); /* 末尾に要素を追加 */ add(nums, 1); add(nums, 3); add(nums, 2); add(nums, 5); add(nums, 4); printf("リスト nums = "); printArray(toArray(nums), size(nums)); printf("容量 = %d ,長さ = %d\n", capacity(nums), size(nums)); /* 中間に要素を挿入 */ insert(nums, 3, 6); printf("インデックス 3 に数字 6 を挿入し、nums = "); printArray(toArray(nums), size(nums)); /* 要素を削除 */ removeItem(nums, 3); printf("インデックス 3 の要素を削除し、nums = "); printArray(toArray(nums), size(nums)); /* 要素にアクセス */ int num = get(nums, 1); printf("インデックス 1 の要素にアクセスし、num = %d\n", num); /* 要素を更新 */ set(nums, 1, 0); printf("インデックス 1 の要素を 0 に更新し、nums = "); printArray(toArray(nums), size(nums)); /* 拡張機構をテストする */ for (int i = 0; i < 10; i++) { // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する add(nums, i); } printf("拡張後のリスト nums = "); printArray(toArray(nums), size(nums)); printf("容量 = %d ,長さ = %d\n", capacity(nums), size(nums)); /* 確保したメモリを解放する */ delMyList(nums); return 0; } ================================================ FILE: ja/codes/c/chapter_backtracking/CMakeLists.txt ================================================ add_executable(permutations_i permutations_i.c) add_executable(permutations_ii permutations_ii.c) add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.c) add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.c) add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.c) add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.c) add_executable(subset_sum_i_naive subset_sum_i_naive.c) add_executable(subset_sum_i subset_sum_i.c) add_executable(subset_sum_ii subset_sum_ii.c) add_executable(n_queens n_queens.c) ================================================ FILE: ja/codes/c/chapter_backtracking/n_queens.c ================================================ /** * File : n_queens.c * Created Time: 2023-09-25 * Author : lucas (superrat6@gmail.com) */ #include "../utils/common.h" #define MAX_SIZE 100 /* バックトラッキング:N クイーン */ void backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE], bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) { // すべての行への配置が完了したら、解を記録する if (row == n) { res[*resSize] = (char **)malloc(sizeof(char *) * n); for (int i = 0; i < n; ++i) { res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1)); strcpy(res[*resSize][i], state[i]); } (*resSize)++; return; } // すべての列を走査 for (int col = 0; col < n; col++) { // このマスに対応する主対角線と副対角線を計算 int diag1 = row - col + n - 1; int diag2 = row + col; // 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 試行:そのマスにクイーンを置く state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // 次の行に配置する backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2); // 戻す:そのマスを空きマスに戻す state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* N クイーンを解く */ char ***nQueens(int n, int *returnSize) { char state[MAX_SIZE][MAX_SIZE]; // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { state[i][j] = '#'; } state[i][n] = '\0'; } bool cols[MAX_SIZE] = {false}; // 列にクイーンがあるか記録 bool diags1[2 * MAX_SIZE - 1] = {false}; // 主対角線にクイーンがあるかを記録 bool diags2[2 * MAX_SIZE - 1] = {false}; // 副対角線にクイーンがあるかを記録 char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE); *returnSize = 0; backtrack(0, n, state, res, returnSize, cols, diags1, diags2); return res; } /* Driver Code */ int main() { int n = 4; int returnSize; char ***res = nQueens(n, &returnSize); printf("盤面の縦横は%d\n", n); printf("クイーンの配置方法は全部で %d 通り\n", returnSize); for (int i = 0; i < returnSize; ++i) { for (int j = 0; j < n; ++j) { printf("["); for (int k = 0; res[i][j][k] != '\0'; ++k) { printf("%c", res[i][j][k]); if (res[i][j][k + 1] != '\0') { printf(", "); } } printf("]\n"); } printf("---------------------\n"); } // メモリを解放する for (int i = 0; i < returnSize; ++i) { for (int j = 0; j < n; ++j) { free(res[i][j]); } free(res[i]); } free(res); return 0; } ================================================ FILE: ja/codes/c/chapter_backtracking/permutations_i.c ================================================ /** * File: permutations_i.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com), krahets (krahets@163.com) */ #include "../utils/common.h" // 順列は最大 1000 個と仮定 #define MAX_SIZE 1000 /* バックトラッキング:順列 I */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // 状態の長さが要素数に等しければ、解を記録 if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // すべての選択肢を走査 for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // 枝刈り:要素の重複選択を許可しない if (!selected[i]) { // 試行: 選択を行い、状態を更新 selected[i] = true; state[stateSize] = choice; // 次の選択へ進む backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; } } } /* 全順列 I */ int **permutationsI(int *nums, int numsSize, int *returnSize) { int *state = (int *)malloc(numsSize * sizeof(int)); bool *selected = (bool *)malloc(numsSize * sizeof(bool)); for (int i = 0; i < numsSize; i++) { selected[i] = false; } int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); *returnSize = 0; backtrack(state, 0, nums, numsSize, selected, res, returnSize); free(state); free(selected); return res; } /* Driver Code */ int main() { int nums[] = {1, 2, 3}; int numsSize = sizeof(nums) / sizeof(nums[0]); int returnSize; int **res = permutationsI(nums, numsSize, &returnSize); printf("入力配列 nums = "); printArray(nums, numsSize); printf("\nすべての順列 res = \n"); for (int i = 0; i < returnSize; i++) { printArray(res[i], numsSize); } // メモリを解放する for (int i = 0; i < returnSize; i++) { free(res[i]); } free(res); return 0; } ================================================ FILE: ja/codes/c/chapter_backtracking/permutations_ii.c ================================================ /** * File: permutations_ii.c * Created Time: 2023-10-17 * Author: krahets (krahets@163.com) */ #include "../utils/common.h" // 順列は最大 1000 個、要素の最大値は 1000 と仮定する #define MAX_SIZE 1000 /* バックトラッキング:順列 II */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // 状態の長さが要素数に等しければ、解を記録 if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // すべての選択肢を走査 bool duplicated[MAX_SIZE] = {false}; for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない if (!selected[i] && !duplicated[choice]) { // 試行: 選択を行い、状態を更新 duplicated[choice] = true; // 選択済みの要素値を記録 selected[i] = true; state[stateSize] = choice; // 次の選択へ進む backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; } } } /* 全順列 II */ int **permutationsII(int *nums, int numsSize, int *returnSize) { int *state = (int *)malloc(numsSize * sizeof(int)); bool *selected = (bool *)malloc(numsSize * sizeof(bool)); for (int i = 0; i < numsSize; i++) { selected[i] = false; } int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); *returnSize = 0; backtrack(state, 0, nums, numsSize, selected, res, returnSize); free(state); free(selected); return res; } /* Driver Code */ int main() { int nums[] = {1, 1, 2}; int numsSize = sizeof(nums) / sizeof(nums[0]); int returnSize; int **res = permutationsII(nums, numsSize, &returnSize); printf("入力配列 nums = "); printArray(nums, numsSize); printf("\nすべての順列 res = \n"); for (int i = 0; i < returnSize; i++) { printArray(res[i], numsSize); } // メモリを解放する for (int i = 0; i < returnSize; i++) { free(res[i]); } free(res); return 0; } ================================================ FILE: ja/codes/c/chapter_backtracking/preorder_traversal_i_compact.c ================================================ /** * File: preorder_traversal_i_compact.c * Created Time: 2023-05-10 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // 結果の長さは 100 を超えないと仮定する #define MAX_SIZE 100 TreeNode *res[MAX_SIZE]; int resSize = 0; /* 前順走査:例題 1 */ void preOrder(TreeNode *root) { if (root == NULL) { return; } if (root->val == 7) { // 解を記録 res[resSize++] = root; } preOrder(root->left); preOrder(root->right); } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\n二分木を初期化\n"); printTree(root); // 先行順走査 preOrder(root); printf("\n値が 7 のすべてのノードを出力\n"); int *vals = malloc(resSize * sizeof(int)); for (int i = 0; i < resSize; i++) { vals[i] = res[i]->val; } printArray(vals, resSize); // メモリを解放する freeMemoryTree(root); free(vals); return 0; } ================================================ FILE: ja/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c ================================================ /** * File: preorder_traversal_ii_compact.c * Created Time: 2023-05-28 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // パスと結果の長さは 100 以下と仮定 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* 前順走査:例題 2 */ void preOrder(TreeNode *root) { if (root == NULL) { return; } // 試す path[pathSize++] = root; if (root->val == 7) { // 解を記録 for (int i = 0; i < pathSize; ++i) { res[resSize][i] = path[i]; } resSize++; } preOrder(root->left); preOrder(root->right); // バックトラック pathSize--; } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\n二分木を初期化\n"); printTree(root); // 先行順走査 preOrder(root); printf("\nルートノードからノード 7 までのすべての経路を出力\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // メモリを解放する freeMemoryTree(root); return 0; } ================================================ FILE: ja/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c ================================================ /** * File: preorder_traversal_iii_compact.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // パスと結果の長さは 100 以下と仮定 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* 前順走査:例題 3 */ void preOrder(TreeNode *root) { // 枝刈り if (root == NULL || root->val == 3) { return; } // 試す path[pathSize++] = root; if (root->val == 7) { // 解を記録 for (int i = 0; i < pathSize; i++) { res[resSize][i] = path[i]; } resSize++; } preOrder(root->left); preOrder(root->right); // バックトラック pathSize--; } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\n二分木を初期化\n"); printTree(root); // 先行順走査 preOrder(root); printf("\nルートノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含めない\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // メモリを解放する freeMemoryTree(root); return 0; } ================================================ FILE: ja/codes/c/chapter_backtracking/preorder_traversal_iii_template.c ================================================ /** * File: preorder_traversal_iii_template.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // パスと結果の長さは 100 以下と仮定 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* 現在の状態が解かどうかを判定 */ bool isSolution(void) { return pathSize > 0 && path[pathSize - 1]->val == 7; } /* 解を記録 */ void recordSolution(void) { for (int i = 0; i < pathSize; i++) { res[resSize][i] = path[i]; } resSize++; } /* 現在の状態で、この選択が有効かどうかを判定 */ bool isValid(TreeNode *choice) { return choice != NULL && choice->val != 3; } /* 状態を更新 */ void makeChoice(TreeNode *choice) { path[pathSize++] = choice; } /* 状態を元に戻す */ void undoChoice(void) { pathSize--; } /* バックトラッキング:例題 3 */ void backtrack(TreeNode *choices[2]) { // 解かどうかを確認 if (isSolution()) { // 解を記録 recordSolution(); } // すべての選択肢を走査 for (int i = 0; i < 2; i++) { TreeNode *choice = choices[i]; // 枝刈り:選択が妥当かを確認する if (isValid(choice)) { // 試行: 選択を行い、状態を更新 makeChoice(choice); // 次の選択へ進む TreeNode *nextChoices[2] = {choice->left, choice->right}; backtrack(nextChoices); // バックトラック:選択を取り消し、前の状態に戻す undoChoice(); } } } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\n二分木を初期化\n"); printTree(root); // バックトラッキング法 TreeNode *choices[2] = {root, NULL}; backtrack(choices); printf("\nルートノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含めない\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // メモリを解放する freeMemoryTree(root); return 0; } ================================================ FILE: ja/codes/c/chapter_backtracking/subset_sum_i.c ================================================ /** * File: subset_sum_i.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // 状態(部分集合) int state[MAX_SIZE]; int stateSize = 0; // 結果リスト(部分集合のリスト) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* バックトラッキング:部分和 I */ void backtrack(int target, int *choices, int choicesSize, int start) { // 部分集合の和が target に等しければ、解を記録 if (target == 0) { for (int i = 0; i < stateSize; ++i) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for (int i = start; i < choicesSize; i++) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } // 試す:選択を行い、target と start を更新 state[stateSize] = choices[i]; stateSize++; // 次の選択へ進む backtrack(target - choices[i], choices, choicesSize, i); // バックトラック:選択を取り消し、前の状態に戻す stateSize--; } } /* 比較関数 */ int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); } /* 部分和 I を解く */ void subsetSumI(int *nums, int numsSize, int target) { qsort(nums, numsSize, sizeof(int), cmp); // nums をソート int start = 0; // 開始点を走査 backtrack(target, nums, numsSize, start); } /* Driver Code */ int main() { int nums[] = {3, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumI(nums, numsSize, target); printf("入力配列 nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("合計が %d に等しいすべての部分集合 res = \n", target); for (int i = 0; i < resSize; ++i) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: ja/codes/c/chapter_backtracking/subset_sum_i_naive.c ================================================ /** * File: subset_sum_i_naive.c * Created Time: 2023-07-28 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // 状態(部分集合) int state[MAX_SIZE]; int stateSize = 0; // 結果リスト(部分集合のリスト) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* バックトラッキング:部分和 I */ void backtrack(int target, int total, int *choices, int choicesSize) { // 部分集合の和が target に等しければ、解を記録 if (total == target) { for (int i = 0; i < stateSize; i++) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // すべての選択肢を走査 for (int i = 0; i < choicesSize; i++) { // 枝刈り:部分和が target を超える場合はその選択をスキップする if (total + choices[i] > target) { continue; } // 試行:選択を行い、要素と total を更新する state[stateSize++] = choices[i]; // 次の選択へ進む backtrack(target, total + choices[i], choices, choicesSize); // バックトラック:選択を取り消し、前の状態に戻す stateSize--; } } /* 部分和 I を解く(重複部分集合を含む) */ void subsetSumINaive(int *nums, int numsSize, int target) { resSize = 0; // 解の個数を 0 に初期化する backtrack(target, 0, nums, numsSize); } /* Driver Code */ int main() { int nums[] = {3, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumINaive(nums, numsSize, target); printf("入力配列 nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("合計が %d に等しいすべての部分集合 res = \n", target); for (int i = 0; i < resSize; i++) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: ja/codes/c/chapter_backtracking/subset_sum_ii.c ================================================ /** * File: subset_sum_ii.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // 状態(部分集合) int state[MAX_SIZE]; int stateSize = 0; // 結果リスト(部分集合のリスト) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* バックトラッキング:部分和 II */ void backtrack(int target, int *choices, int choicesSize, int start) { // 部分集合の和が target に等しければ、解を記録 if (target == 0) { for (int i = 0; i < stateSize; i++) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for (int i = start; i < choicesSize; i++) { // 枝刈り 1: 部分集合の和が target を超えたら、そのままスキップする if (target - choices[i] < 0) { continue; } // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする if (i > start && choices[i] == choices[i - 1]) { continue; } // 試す:選択を行い、target と start を更新 state[stateSize] = choices[i]; stateSize++; // 次の選択へ進む backtrack(target - choices[i], choices, choicesSize, i + 1); // バックトラック:選択を取り消し、前の状態に戻す stateSize--; } } /* 比較関数 */ int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); } /* 部分和 II を解く */ void subsetSumII(int *nums, int numsSize, int target) { // nums をソート qsort(nums, numsSize, sizeof(int), cmp); // バックトラッキングを開始 backtrack(target, nums, numsSize, 0); } /* Driver Code */ int main() { int nums[] = {4, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumII(nums, numsSize, target); printf("入力配列 nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("合計が %d に等しいすべての部分集合 res = \n", target); for (int i = 0; i < resSize; ++i) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: ja/codes/c/chapter_computational_complexity/CMakeLists.txt ================================================ add_executable(iteration iteration.c) add_executable(recursion recursion.c) add_executable(time_complexity time_complexity.c) add_executable(worst_best_time_complexity worst_best_time_complexity.c) add_executable(space_complexity space_complexity.c) ================================================ FILE: ja/codes/c/chapter_computational_complexity/iteration.c ================================================ /** * File: iteration.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com), MwumLi (mwumli@hotmail.com) */ #include "../utils/common.h" /* for ループ */ int forLoop(int n) { int res = 0; // 1, 2, ..., n-1, n を順に加算する for (int i = 1; i <= n; i++) { res += i; } return res; } /* while ループ */ int whileLoop(int n) { int res = 0; int i = 1; // 条件変数を初期化する // 1, 2, ..., n-1, n を順に加算する while (i <= n) { res += i; i++; // 条件変数を更新する } return res; } /* while ループ(2回更新) */ int whileLoopII(int n) { int res = 0; int i = 1; // 条件変数を初期化する // 1, 4, 10, ... を順に加算する while (i <= n) { res += i; // 条件変数を更新する i++; i *= 2; } return res; } /* 二重 for ループ */ char *nestedForLoop(int n) { // n * n は対応する点の個数であり、"(i, j), " に対応する文字列長の最大は 6+10*2 で、さらに末尾の空文字 \0 のための追加領域が必要 int size = n * n * 26 + 1; char *res = malloc(size * sizeof(char)); // i = 1, 2, ..., n-1, n とループする for (int i = 1; i <= n; i++) { // j = 1, 2, ..., n-1, n とループする for (int j = 1; j <= n; j++) { char tmp[26]; snprintf(tmp, sizeof(tmp), "(%d, %d), ", i, j); strncat(res, tmp, size - strlen(res) - 1); } } return res; } /* Driver Code */ int main() { int n = 5; int res; res = forLoop(n); printf("\nfor ループの合計結果 res = %d\n", res); res = whileLoop(n); printf("\nwhile ループの合計結果 res = %d\n", res); res = whileLoopII(n); printf("\nwhile ループ(2回更新)の合計結果 res = %d\n", res); char *resStr = nestedForLoop(n); printf("\n二重 for ループの走査結果 %s\r\n", resStr); free(resStr); return 0; } ================================================ FILE: ja/codes/c/chapter_computational_complexity/recursion.c ================================================ /** * File: recursion.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 再帰 */ int recur(int n) { // 終了条件 if (n == 1) return 1; // 再帰:再帰呼び出し int res = recur(n - 1); // 帰りがけ:結果を返す return n + res; } /* 反復で再帰を模擬する */ int forLoopRecur(int n) { int stack[1000]; // 大きな配列を使ってスタックを実装する int top = -1; // スタックトップのインデックス int res = 0; // 再帰:再帰呼び出し for (int i = n; i > 0; i--) { // 「スタックへのプッシュ」で「再帰」を模擬する stack[1 + top++] = i; } // 帰りがけ:結果を返す while (top >= 0) { // 「スタックから取り出す操作」で「帰り」をシミュレート res += stack[top--]; } // res = 1+2+3+...+n return res; } /* 末尾再帰 */ int tailRecur(int n, int res) { // 終了条件 if (n == 0) return res; // 末尾再帰呼び出し return tailRecur(n - 1, res + n); } /* フィボナッチ数列:再帰 */ int fib(int n) { // 終了条件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す int res = fib(n - 1) + fib(n - 2); // 結果 f(n) を返す return res; } /* Driver Code */ int main() { int n = 5; int res; res = recur(n); printf("\n再帰関数の合計結果 res = %d\n", res); res = forLoopRecur(n); printf("\n反復で再帰をシミュレートした合計結果 res = %d\n", res); res = tailRecur(n, 0); printf("\n末尾再帰関数の合計結果 res = %d\n", res); res = fib(n); printf("\nフィボナッチ数列の第 %d 項は %d\n", n, res); return 0; } ================================================ FILE: ja/codes/c/chapter_computational_complexity/space_complexity.c ================================================ /** * File: space_complexity.c * Created Time: 2023-04-15 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 関数 */ int func() { // 何らかの処理を行う return 0; } /* 定数階 */ void constant(int n) { // 定数、変数、オブジェクトは O(1) の空間を占める const int a = 0; int b = 0; int nums[1000]; ListNode *node = newListNode(0); free(node); // ループ内の変数は O(1) の空間を占める for (int i = 0; i < n; i++) { int c = 0; } // ループ内の関数は O(1) の空間を占める for (int i = 0; i < n; i++) { func(); } } /* ハッシュテーブル */ typedef struct { int key; int val; UT_hash_handle hh; // uthash.h を用いて実装 } HashTable; /* 線形階 */ void linear(int n) { // 長さ n の配列は O(n) の空間を使用 int *nums = malloc(sizeof(int) * n); free(nums); // 長さ n のリストは O(n) の空間を使用 ListNode **nodes = malloc(sizeof(ListNode *) * n); for (int i = 0; i < n; i++) { nodes[i] = newListNode(i); } // メモリを解放する for (int i = 0; i < n; i++) { free(nodes[i]); } free(nodes); // 長さ n のハッシュテーブルは O(n) の空間を使用 HashTable *h = NULL; for (int i = 0; i < n; i++) { HashTable *tmp = malloc(sizeof(HashTable)); tmp->key = i; tmp->val = i; HASH_ADD_INT(h, key, tmp); } // メモリを解放する HashTable *curr, *tmp; HASH_ITER(hh, h, curr, tmp) { HASH_DEL(h, curr); free(curr); } } /* 線形時間(再帰実装) */ void linearRecur(int n) { printf("再帰 n = %d\r\n", n); if (n == 1) return; linearRecur(n - 1); } /* 二乗階 */ void quadratic(int n) { // 二次元リストは O(n^2) の空間を使用 int **numMatrix = malloc(sizeof(int *) * n); for (int i = 0; i < n; i++) { int *tmp = malloc(sizeof(int) * n); for (int j = 0; j < n; j++) { tmp[j] = 0; } numMatrix[i] = tmp; } // メモリを解放する for (int i = 0; i < n; i++) { free(numMatrix[i]); } free(numMatrix); } /* 二次時間(再帰実装) */ int quadraticRecur(int n) { if (n <= 0) return 0; int *nums = malloc(sizeof(int) * n); printf("再帰 n = %d における nums の長さ = %d\r\n", n, n); int res = quadraticRecur(n - 1); free(nums); return res; } /* 指数時間(完全二分木の構築) */ TreeNode *buildTree(int n) { if (n == 0) return NULL; TreeNode *root = newTreeNode(0); root->left = buildTree(n - 1); root->right = buildTree(n - 1); return root; } /* Driver Code */ int main() { int n = 5; // 定数階 constant(n); // 線形階 linear(n); linearRecur(n); // 二乗階 quadratic(n); quadraticRecur(n); // 指数オーダー TreeNode *root = buildTree(n); printTree(root); // メモリを解放する freeMemoryTree(root); return 0; } ================================================ FILE: ja/codes/c/chapter_computational_complexity/time_complexity.c ================================================ /** * File: time_complexity.c * Created Time: 2023-01-03 * Author: codingonion (coderonion@gmail.com) */ #include "../utils/common.h" /* 定数階 */ int constant(int n) { int count = 0; int size = 100000; int i = 0; for (int i = 0; i < size; i++) { count++; } return count; } /* 線形階 */ int linear(int n) { int count = 0; for (int i = 0; i < n; i++) { count++; } return count; } /* 線形時間(配列を走査) */ int arrayTraversal(int *nums, int n) { int count = 0; // ループ回数は配列長に比例する for (int i = 0; i < n; i++) { count++; } return count; } /* 二乗階 */ int quadratic(int n) { int count = 0; // ループ回数はデータサイズ n の二乗に比例する for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* 二次時間(バブルソート) */ int bubbleSort(int *nums, int n) { int count = 0; // カウンタ // 外側のループ:未ソート区間は [0, i] for (int i = n - 1; i > 0; i--) { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 要素交換には 3 回の単位操作が含まれる } } } return count; } /* 指数時間(ループ実装) */ int exponential(int n) { int count = 0; int bas = 1; // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する for (int i = 0; i < n; i++) { for (int j = 0; j < bas; j++) { count++; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指数時間(再帰実装) */ int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 対数時間(ループ実装) */ int logarithmic(int n) { int count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* 対数時間(再帰実装) */ int logRecur(int n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* 線形対数時間 */ int linearLogRecur(int n) { if (n <= 1) return 1; int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (int i = 0; i < n; i++) { count++; } return count; } /* 階乗時間(再帰実装) */ int factorialRecur(int n) { if (n == 0) return 1; int count = 0; for (int i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ int main(int argc, char *argv[]) { // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる int n = 8; printf("入力データサイズ n = %d\n", n); int count = constant(n); printf("定数オーダーの操作回数 = %d\n", count); count = linear(n); printf("線形オーダーの操作回数 = %d\n", count); // ヒープ領域にメモリを確保する(要素数 n、要素型 int の一次元可変長配列を作成) int *nums = (int *)malloc(n * sizeof(int)); count = arrayTraversal(nums, n); printf("線形オーダー(配列の走査)の操作回数 = %d\n", count); count = quadratic(n); printf("平方オーダーの操作回数 = %d\n", count); for (int i = 0; i < n; i++) { nums[i] = n - i; // [n,n-1,...,2,1] } count = bubbleSort(nums, n); printf("平方オーダー(バブルソート)の操作回数 = %d\n", count); count = exponential(n); printf("指数オーダー(ループ実装)の操作回数 = %d\n", count); count = expRecur(n); printf("指数オーダー(再帰実装)の操作回数 = %d\n", count); count = logarithmic(n); printf("対数オーダー(ループ実装)の操作回数 = %d\n", count); count = logRecur(n); printf("対数オーダー(再帰実装)の操作回数 = %d\n", count); count = linearLogRecur(n); printf("線形対数オーダー(再帰実装)の操作回数 = %d\n", count); count = factorialRecur(n); printf("階乗オーダー(再帰実装)の操作回数 = %d\n", count); // ヒープ領域のメモリを解放 if (nums != NULL) { free(nums); nums = NULL; } getchar(); return 0; } ================================================ FILE: ja/codes/c/chapter_computational_complexity/worst_best_time_complexity.c ================================================ /** * File: worst_best_time_complexity.c * Created Time: 2023-01-03 * Author: codingonion (coderonion@gmail.com) */ #include "../utils/common.h" /* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ int *randomNumbers(int n) { // ヒープ領域にメモリを確保する(要素数 n、要素型 int の一次元可変長配列を作成) int *nums = (int *)malloc(n * sizeof(int)); // 配列 nums = { 1, 2, 3, ..., n } を生成 for (int i = 0; i < n; i++) { nums[i] = i + 1; } // 配列要素をランダムにシャッフル for (int i = n - 1; i > 0; i--) { int j = rand() % (i + 1); int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } return nums; } /* 配列 nums 内で数値 1 のインデックスを探す */ int findOne(int *nums, int n) { for (int i = 0; i < n; i++) { // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる if (nums[i] == 1) return i; } return -1; } /* Driver Code */ int main(int argc, char *argv[]) { // 乱数シードを初期化する srand((unsigned int)time(NULL)); for (int i = 0; i < 10; i++) { int n = 100; int *nums = randomNumbers(n); int index = findOne(nums, n); printf("\n配列 [ 1, 2, ..., n ] をシャッフルした後 = "); printArray(nums, n); printf("数値 1 のインデックスは %d\n", index); // ヒープ領域のメモリを解放 if (nums != NULL) { free(nums); nums = NULL; } } return 0; } ================================================ FILE: ja/codes/c/chapter_divide_and_conquer/CMakeLists.txt ================================================ add_executable(binary_search_recur binary_search_recur.c) add_executable(build_tree build_tree.c) add_executable(hanota hanota.c) ================================================ FILE: ja/codes/c/chapter_divide_and_conquer/binary_search_recur.c ================================================ /** * File: binary_search_recur.c * Created Time: 2023-10-01 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 二分探索:問題 f(i, j) */ int dfs(int nums[], int target, int i, int j) { // 区間が空なら対象要素は存在しないので -1 を返す if (i > j) { return -1; } // 中点インデックス m を計算 int m = (i + j) / 2; if (nums[m] < target) { // 部分問題 f(m+1, j) を再帰的に解く return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 部分問題 f(i, m-1) を再帰的に解く return dfs(nums, target, i, m - 1); } else { // 目標要素が見つかったらそのインデックスを返す return m; } } /* 二分探索 */ int binarySearch(int nums[], int target, int numsSize) { int n = numsSize; // 問題 f(0, n-1) を解く return dfs(nums, target, 0, n - 1); } /* Driver Code */ int main() { int target = 6; int nums[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; int numsSize = sizeof(nums) / sizeof(nums[0]); // 二分探索(両閉区間) int index = binarySearch(nums, target, numsSize); printf("対象要素 6 のインデックス = %d\n", index); return 0; } ================================================ FILE: ja/codes/c/chapter_divide_and_conquer/build_tree.c ================================================ /** * File : build_tree.c * Created Time: 2023-10-16 * Author : lucas (superrat6@gmail.com) */ #include "../utils/common.h" // すべての要素が 1000 未満であると仮定する #define MAX_SIZE 1000 /* 二分木を構築:分割統治 */ TreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) { // 部分木区間が空なら終了する if (r - l < 0) return NULL; // ルートノードを初期化する TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); root->val = preorder[i]; root->left = NULL; root->right = NULL; // m を求めて左右部分木を分割する int m = inorderMap[preorder[i]]; // 部分問題:左部分木を構築する root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size); // 部分問題:右部分木を構築する root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size); // 根ノードを返す return root; } /* 二分木を構築 */ TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) { // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE); for (int i = 0; i < inorderSize; i++) { inorderMap[inorder[i]] = i; } TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize); free(inorderMap); return root; } /* Driver Code */ int main() { int preorder[] = {3, 9, 2, 1, 7}; int inorder[] = {9, 3, 1, 2, 7}; int preorderSize = sizeof(preorder) / sizeof(preorder[0]); int inorderSize = sizeof(inorder) / sizeof(inorder[0]); printf("前順走査 = "); printArray(preorder, preorderSize); printf("中順走査 = "); printArray(inorder, inorderSize); TreeNode *root = buildTree(preorder, preorderSize, inorder, inorderSize); printf("構築した二分木は:\n"); printTree(root); freeMemoryTree(root); return 0; } ================================================ FILE: ja/codes/c/chapter_divide_and_conquer/hanota.c ================================================ /** * File: hanota.c * Created Time: 2023-10-01 * Author: Zuoxun (845242523@qq.com), lucas(superrat6@gmail.com) */ #include "../utils/common.h" // 順列は最大 1000 個と仮定 #define MAX_SIZE 1000 /* 円盤を 1 枚移動 */ void move(int *src, int *srcSize, int *tar, int *tarSize) { // src の上から円盤を1枚取り出す int pan = src[*srcSize - 1]; src[*srcSize - 1] = 0; (*srcSize)--; // 円盤を tar の上に置く tar[*tarSize] = pan; (*tarSize)++; } /* ハノイの塔の問題 f(i) を解く */ void dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) { // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if (i == 1) { move(src, srcSize, tar, tarSize); return; } // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize); // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src, srcSize, tar, tarSize); // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize); } /* ハノイの塔を解く */ void solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) { // A の上から n 枚の円盤を B を介して C へ移す dfs(*ASize, A, ASize, B, BSize, C, CSize); } /* Driver Code */ int main() { // リスト末尾が柱の頂上 int a[] = {5, 4, 3, 2, 1}; int b[MAX_SIZE] = {0}; int c[MAX_SIZE] = {0}; int ASize = sizeof(a) / sizeof(a[0]); int BSize = 0; int CSize = 0; printf("\n初期状態:"); printf("\nA = "); printArray(a, ASize); printf("B = "); printArray(b, BSize); printf("C = "); printArray(c, CSize); solveHanota(a, &ASize, b, &BSize, c, &CSize); printf("\n円盤の移動完了後:"); printf("A = "); printArray(a, ASize); printf("B = "); printArray(b, BSize); printf("C = "); printArray(c, CSize); return 0; } ================================================ FILE: ja/codes/c/chapter_dynamic_programming/CMakeLists.txt ================================================ add_executable(climbing_stairs_constraint_dp climbing_stairs_constraint_dp.c) add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.c) add_executable(min_path_sum min_path_sum.c) add_executable(knapsack knapsack.c) add_executable(unbounded_knapsack unbounded_knapsack.c) add_executable(coin_change coin_change.c) add_executable(coin_change_ii coin_change_ii.c) add_executable(edit_distance edit_distance.c) ================================================ FILE: ja/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c ================================================ /** * File: climbing_stairs_backtrack.c * Created Time: 2023-09-22 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* バックトラッキング */ void backtrack(int *choices, int state, int n, int *res, int len) { // 第 n 段に到達したら、方法数を 1 増やす if (state == n) res[0]++; // すべての選択肢を走査 for (int i = 0; i < len; i++) { int choice = choices[i]; // 枝刈り: 第 n 段を超えないようにする if (state + choice > n) continue; // 試行: 選択を行い、状態を更新 backtrack(choices, state + choice, n, res, len); // バックトラック } } /* 階段登り:バックトラッキング */ int climbingStairsBacktrack(int n) { int choices[2] = {1, 2}; // 1 段または 2 段上ることを選べる int state = 0; // 第 0 段から上り始める int *res = (int *)malloc(sizeof(int)); *res = 0; // res[0] を使って方法数を記録する int len = sizeof(choices) / sizeof(int); backtrack(choices, state, n, res, len); int result = *res; free(res); return result; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsBacktrack(n); printf("%d 段の階段を登る方法は全部で %d 通りです\n", n, res); return 0; } ================================================ FILE: ja/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c ================================================ /** * File: climbing_stairs_constraint_dp.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 制約付き階段登り:動的計画法 */ int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // 部分問題の解を保存するために dp テーブルを初期化 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(3, sizeof(int)); } // 初期状態:最小部分問題の解をあらかじめ設定 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } int res = dp[n][1] + dp[n][2]; // メモリを解放する for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsConstraintDP(n); printf("%d 段の階段を登る方法は全部で %d 通りです\n", n, res); return 0; } ================================================ FILE: ja/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c ================================================ /** * File: climbing_stairs_dfs.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* 検索 */ int dfs(int i) { // dp[1] と dp[2] は既知なので返す if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1) + dfs(i - 2); return count; } /* 階段登り:探索 */ int climbingStairsDFS(int n) { return dfs(n); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFS(n); printf("%d 段の階段を登る方法は全部で %d 通りです\n", n, res); return 0; } ================================================ FILE: ja/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c ================================================ /** * File: climbing_stairs_dfs_mem.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* メモ化探索 */ int dfs(int i, int *mem) { // dp[1] と dp[2] は既知なので返す if (i == 1 || i == 2) return i; // dp[i] の記録があれば、それをそのまま返す if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1, mem) + dfs(i - 2, mem); // dp[i] を記録する mem[i] = count; return count; } /* 階段登り:メモ化探索 */ int climbingStairsDFSMem(int n) { // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す int *mem = (int *)malloc((n + 1) * sizeof(int)); for (int i = 0; i <= n; i++) { mem[i] = -1; } int result = dfs(n, mem); free(mem); return result; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFSMem(n); printf("%d 段の階段を登る方法は全部で %d 通りです\n", n, res); return 0; } ================================================ FILE: ja/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c ================================================ /** * File: climbing_stairs_dp.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* 階段登り:動的計画法 */ int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; // 部分問題の解を保存するために dp テーブルを初期化 int *dp = (int *)malloc((n + 1) * sizeof(int)); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = 1; dp[2] = 2; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } int result = dp[n]; free(dp); return result; } /* 階段登り:空間最適化した動的計画法 */ int climbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDP(n); printf("%d 段の階段を登る方法は全部で %d 通りです\n", n, res); res = climbingStairsDPComp(n); printf("%d 段の階段を登る方法は全部で %d 通りです\n", n, res); return 0; } ================================================ FILE: ja/codes/c/chapter_dynamic_programming/coin_change.c ================================================ /** * File: coin_change.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 最小値を求める */ int myMin(int a, int b) { return a < b ? a : b; } /* コイン両替:動的計画法 */ int coinChangeDP(int coins[], int amt, int coinsSize) { int n = coinsSize; int MAX = amt + 1; // dp テーブルを初期化 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(amt + 1, sizeof(int)); } // 状態遷移:先頭行と先頭列 for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状態遷移: 残りの行と列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } int res = dp[n][amt] != MAX ? dp[n][amt] : -1; // メモリを解放する for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* コイン交換:空間最適化後の動的計画法 */ int coinChangeDPComp(int coins[], int amt, int coinsSize) { int n = coinsSize; int MAX = amt + 1; // dp テーブルを初期化 int *dp = malloc((amt + 1) * sizeof(int)); for (int j = 1; j <= amt; j++) { dp[j] = MAX; } dp[0] = 0; // 状態遷移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1); } } } int res = dp[amt] != MAX ? dp[amt] : -1; // メモリを解放する free(dp); return res; } /* Driver code */ int main() { int coins[] = {1, 2, 5}; int coinsSize = sizeof(coins) / sizeof(coins[0]); int amt = 4; // 動的計画法 int res = coinChangeDP(coins, amt, coinsSize); printf("目標金額に必要な最小硬貨枚数は %d\n", res); // 空間最適化後の動的計画法 res = coinChangeDPComp(coins, amt, coinsSize); printf("目標金額に必要な最小硬貨枚数は %d\n", res); return 0; } ================================================ FILE: ja/codes/c/chapter_dynamic_programming/coin_change_ii.c ================================================ /** * File: coin_change_ii.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* コイン両替 II:動的計画法 */ int coinChangeIIDP(int coins[], int amt, int coinsSize) { int n = coinsSize; // dp テーブルを初期化 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(amt + 1, sizeof(int)); } // 先頭列を初期化する for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // 状態遷移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { // コイン i を選ばない場合と選ぶ場合の和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } int res = dp[n][amt]; // メモリを解放する for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* コイン両替 II:空間最適化した動的計画法 */ int coinChangeIIDPComp(int coins[], int amt, int coinsSize) { int n = coinsSize; // dp テーブルを初期化 int *dp = calloc(amt + 1, sizeof(int)); dp[0] = 1; // 状態遷移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // コイン i を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } int res = dp[amt]; // メモリを解放する free(dp); return res; } /* Driver code */ int main() { int coins[] = {1, 2, 5}; int coinsSize = sizeof(coins) / sizeof(coins[0]); int amt = 5; // 動的計画法 int res = coinChangeIIDP(coins, amt, coinsSize); printf("目標金額になる硬貨の組合せ数は %d\n", res); // 空間最適化後の動的計画法 res = coinChangeIIDPComp(coins, amt, coinsSize); printf("目標金額になる硬貨の組合せ数は %d\n", res); return 0; } ================================================ FILE: ja/codes/c/chapter_dynamic_programming/edit_distance.c ================================================ /** * File: edit_distance.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 最小値を求める */ int myMin(int a, int b) { return a < b ? a : b; } /* 編集距離:総当たり探索 */ int editDistanceDFS(char *s, char *t, int i, int j) { // s と t がともに空なら 0 を返す if (i == 0 && j == 0) return 0; // s が空なら t の長さを返す if (i == 0) return j; // t が空なら s の長さを返す if (j == 0) return i; // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 int insert = editDistanceDFS(s, t, i, j - 1); int del = editDistanceDFS(s, t, i - 1, j); int replace = editDistanceDFS(s, t, i - 1, j - 1); // 最小編集回数を返す return myMin(myMin(insert, del), replace) + 1; } /* 編集距離:メモ化探索 */ int editDistanceDFSMem(char *s, char *t, int memCols, int **mem, int i, int j) { // s と t がともに空なら 0 を返す if (i == 0 && j == 0) return 0; // s が空なら t の長さを返す if (i == 0) return j; // t が空なら s の長さを返す if (j == 0) return i; // 記録済みなら、それをそのまま返す if (mem[i][j] != -1) return mem[i][j]; // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 int insert = editDistanceDFSMem(s, t, memCols, mem, i, j - 1); int del = editDistanceDFSMem(s, t, memCols, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); // 最小編集回数を記録して返す mem[i][j] = myMin(myMin(insert, del), replace) + 1; return mem[i][j]; } /* 編集距離:動的計画法 */ int editDistanceDP(char *s, char *t, int n, int m) { int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(m + 1, sizeof(int)); } // 状態遷移:先頭行と先頭列 for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // 状態遷移: 残りの行と列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i - 1][j - 1]; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } int res = dp[n][m]; // メモリを解放する for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* 編集距離:空間最適化した動的計画法 */ int editDistanceDPComp(char *s, char *t, int n, int m) { int *dp = calloc(m + 1, sizeof(int)); // 状態遷移:先頭行 for (int j = 1; j <= m; j++) { dp[j] = j; } // 状態遷移:残りの行 for (int i = 1; i <= n; i++) { // 状態遷移:先頭列 int leftup = dp[0]; // dp[i-1, j-1] を一時保存する dp[0] = i; // 状態遷移:残りの列 for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftup; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 次の反復の dp[i-1, j-1] に更新する } } int res = dp[m]; // メモリを解放する free(dp); return res; } /* Driver Code */ int main() { char *s = "bag"; char *t = "pack"; int n = strlen(s), m = strlen(t); // 全探索 int res = editDistanceDFS(s, t, n, m); printf("%s を %s に変更するには最小で %d 回の編集が必要です\n", s, t, res); // メモ化探索 int **mem = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { mem[i] = malloc((m + 1) * sizeof(int)); memset(mem[i], -1, (m + 1) * sizeof(int)); } res = editDistanceDFSMem(s, t, m + 1, mem, n, m); printf("%s を %s に変更するには最小で %d 回の編集が必要です\n", s, t, res); // メモリを解放する for (int i = 0; i <= n; i++) { free(mem[i]); } free(mem); // 動的計画法 res = editDistanceDP(s, t, n, m); printf("%s を %s に変更するには最小で %d 回の編集が必要です\n", s, t, res); // 空間最適化後の動的計画法 res = editDistanceDPComp(s, t, n, m); printf("%s を %s に変更するには最小で %d 回の編集が必要です\n", s, t, res); return 0; } ================================================ FILE: ja/codes/c/chapter_dynamic_programming/knapsack.c ================================================ /** * File: knapsack.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 最大値を求める */ int myMax(int a, int b) { return a > b ? a : b; } /* 0-1 ナップサック:総当たり探索 */ int knapsackDFS(int wgt[], int val[], int i, int c) { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 || c == 0) { return 0; } // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // 2つの案のうち価値が大きいほうを返す return myMax(no, yes); } /* 0-1 ナップサック:メモ化探索 */ int knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 || c == 0) { return 0; } // 既に記録があればそのまま返す if (mem[i][c] != -1) { return mem[i][c]; } // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 2 つの案のうち価値が大きい方を記録して返す mem[i][c] = myMax(no, yes); return mem[i][c]; } /* 0-1 ナップサック:動的計画法 */ int knapsackDP(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // dp テーブルを初期化 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(cap + 1, sizeof(int)); } // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[n][cap]; // メモリを解放する for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* 0-1 ナップサック:空間最適化後の動的計画法 */ int knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // dp テーブルを初期化 int *dp = calloc(cap + 1, sizeof(int)); // 状態遷移 for (int i = 1; i <= n; i++) { // 逆順に走査する for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[cap]; // メモリを解放する free(dp); return res; } /* Driver Code */ int main() { int wgt[] = {10, 20, 30, 40, 50}; int val[] = {50, 120, 150, 210, 240}; int cap = 50; int n = sizeof(wgt) / sizeof(wgt[0]); int wgtSize = n; // 全探索 int res = knapsackDFS(wgt, val, n, cap); printf("ナップサック容量を超えない最大価値は %d\n", res); // メモ化探索 int **mem = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { mem[i] = malloc((cap + 1) * sizeof(int)); memset(mem[i], -1, (cap + 1) * sizeof(int)); } res = knapsackDFSMem(wgt, val, cap + 1, mem, n, cap); printf("ナップサック容量を超えない最大価値は %d\n", res); // メモリを解放する for (int i = 0; i <= n; i++) { free(mem[i]); } free(mem); // 動的計画法 res = knapsackDP(wgt, val, cap, wgtSize); printf("ナップサック容量を超えない最大価値は %d\n", res); // 空間最適化後の動的計画法 res = knapsackDPComp(wgt, val, cap, wgtSize); printf("ナップサック容量を超えない最大価値は %d\n", res); return 0; } ================================================ FILE: ja/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c ================================================ /** * File: min_cost_climbing_stairs_dp.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 最小値を求める */ int myMin(int a, int b) { return a < b ? a : b; } /* 階段登りの最小コスト:動的計画法 */ int minCostClimbingStairsDP(int cost[], int costSize) { int n = costSize - 1; if (n == 1 || n == 2) return cost[n]; // 部分問題の解を保存するために dp テーブルを初期化 int *dp = calloc(n + 1, sizeof(int)); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = cost[1]; dp[2] = cost[2]; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i]; } int res = dp[n]; // メモリを解放する free(dp); return res; } /* 階段昇りの最小コスト:空間最適化後の動的計画法 */ int minCostClimbingStairsDPComp(int cost[], int costSize) { int n = costSize - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = myMin(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ int main() { int cost[] = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; int costSize = sizeof(cost) / sizeof(cost[0]); printf("入力された階段コストのリストは:"); printArray(cost, costSize); int res = minCostClimbingStairsDP(cost, costSize); printf("階段を登り切る最小コストは %d\n", res); res = minCostClimbingStairsDPComp(cost, costSize); printf("階段を登り切る最小コストは %d\n", res); return 0; } ================================================ FILE: ja/codes/c/chapter_dynamic_programming/min_path_sum.c ================================================ /** * File: min_path_sum.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" // 行列の最大行数・列数を 100 と仮定する #define MAX_SIZE 100 /* 最小値を求める */ int myMin(int a, int b) { return a < b ? a : b; } /* 最小経路和:全探索 */ int minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) { // 左上のセルなら探索を終了する if (i == 0 && j == 0) { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return INT_MAX; } // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); // 左上隅から (i, j) までの最小経路コストを返す return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; } /* 最小経路和:メモ化探索 */ int minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) { // 左上のセルなら探索を終了する if (i == 0 && j == 0) { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return INT_MAX; } // 既に記録があればそのまま返す if (mem[i][j] != -1) { return mem[i][j]; } // 左と上のセルからの最小経路コスト int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // 左上から (i, j) までの最小経路コストを記録して返す mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; return mem[i][j]; } /* 最小経路和:動的計画法 */ int minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { // dp テーブルを初期化 int **dp = malloc(n * sizeof(int *)); for (int i = 0; i < n; i++) { dp[i] = calloc(m, sizeof(int)); } dp[0][0] = grid[0][0]; // 状態遷移:先頭行 for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 状態遷移:先頭列 for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 状態遷移: 残りの行と列 for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } int res = dp[n - 1][m - 1]; // メモリを解放する for (int i = 0; i < n; i++) { free(dp[i]); } return res; } /* 最小経路和:空間最適化後の動的計画法 */ int minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { // dp テーブルを初期化 int *dp = calloc(m, sizeof(int)); // 状態遷移:先頭行 dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 状態遷移:残りの行 for (int i = 1; i < n; i++) { // 状態遷移:先頭列 dp[0] = dp[0] + grid[i][0]; // 状態遷移:残りの列 for (int j = 1; j < m; j++) { dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j]; } } int res = dp[m - 1]; // メモリを解放する free(dp); return res; } /* Driver Code */ int main() { int grid[MAX_SIZE][MAX_SIZE] = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; int n = 4, m = 4; // 行列の容量は `MAX_SIZE * MAX_SIZE`、有効な行数と列数は `n * m` // 全探索 int res = minPathSumDFS(grid, n - 1, m - 1); printf("左上から右下までの最小経路和は %d\n", res); // メモ化探索 int mem[MAX_SIZE][MAX_SIZE]; memset(mem, -1, sizeof(mem)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); printf("左上から右下までの最小経路和は %d\n", res); // 動的計画法 res = minPathSumDP(grid, n, m); printf("左上から右下までの最小経路和は %d\n", res); // 空間最適化後の動的計画法 res = minPathSumDPComp(grid, n, m); printf("左上から右下までの最小経路和は %d\n", res); return 0; } ================================================ FILE: ja/codes/c/chapter_dynamic_programming/unbounded_knapsack.c ================================================ /** * File: unbounded_knapsack.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 最大値を求める */ int myMax(int a, int b) { return a > b ? a : b; } /* 完全ナップサック問題:動的計画法 */ int unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // dp テーブルを初期化 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(cap + 1, sizeof(int)); } // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[n][cap]; // メモリを解放する for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* 完全ナップサック問題:空間最適化後の動的計画法 */ int unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // dp テーブルを初期化 int *dp = calloc(cap + 1, sizeof(int)); // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[cap]; // メモリを解放する free(dp); return res; } /* Driver code */ int main() { int wgt[] = {1, 2, 3}; int val[] = {5, 11, 15}; int wgtSize = sizeof(wgt) / sizeof(wgt[0]); int cap = 4; // 動的計画法 int res = unboundedKnapsackDP(wgt, val, cap, wgtSize); printf("ナップサック容量を超えない最大価値は %d\n", res); // 空間最適化後の動的計画法 res = unboundedKnapsackDPComp(wgt, val, cap, wgtSize); printf("ナップサック容量を超えない最大価値は %d\n", res); return 0; } ================================================ FILE: ja/codes/c/chapter_graph/CMakeLists.txt ================================================ add_executable(graph_adjacency_matrix graph_adjacency_matrix.c) add_executable(graph_adjacency_list_test graph_adjacency_list_test.c) add_executable(graph_bfs graph_bfs.c) add_executable(graph_dfs graph_dfs.c) ================================================ FILE: ja/codes/c/chapter_graph/graph_adjacency_list.c ================================================ /** * File: graph_adjacency_list.c * Created Time: 2023-07-07 * Author: NI-SW (947743645@qq.com) */ #include "../utils/common.h" // ノード数の上限を 100 と仮定 #define MAX_SIZE 100 /* ノード構造体 */ typedef struct AdjListNode { Vertex *vertex; // 頂点 struct AdjListNode *next; // 後続ノード } AdjListNode; /* 隣接リストに基づく無向グラフクラス */ typedef struct { AdjListNode *heads[MAX_SIZE]; // ノード配列 int size; // ノード数 } GraphAdjList; /* コンストラクタ */ GraphAdjList *newGraphAdjList() { GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList)); if (!graph) { return NULL; } graph->size = 0; for (int i = 0; i < MAX_SIZE; i++) { graph->heads[i] = NULL; } return graph; } /* デストラクタ */ void delGraphAdjList(GraphAdjList *graph) { for (int i = 0; i < graph->size; i++) { AdjListNode *cur = graph->heads[i]; while (cur != NULL) { AdjListNode *next = cur->next; if (cur != graph->heads[i]) { free(cur); } cur = next; } free(graph->heads[i]->vertex); free(graph->heads[i]); } free(graph); } /* 頂点に対応するノードを検索 */ AdjListNode *findNode(GraphAdjList *graph, Vertex *vet) { for (int i = 0; i < graph->size; i++) { if (graph->heads[i]->vertex == vet) { return graph->heads[i]; } } return NULL; } /* 辺を追加する補助関数 */ void addEdgeHelper(AdjListNode *head, Vertex *vet) { AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode)); node->vertex = vet; // 先頭挿入法 node->next = head->next; head->next = node; } /* 辺を追加 */ void addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { AdjListNode *head1 = findNode(graph, vet1); AdjListNode *head2 = findNode(graph, vet2); assert(head1 != NULL && head2 != NULL && head1 != head2); // 辺 vet1 - vet2 を追加 addEdgeHelper(head1, vet2); addEdgeHelper(head2, vet1); } /* 辺削除の補助関数 */ void removeEdgeHelper(AdjListNode *head, Vertex *vet) { AdjListNode *pre = head; AdjListNode *cur = head->next; // 連結リスト内で vet に対応するノードを探索 while (cur != NULL && cur->vertex != vet) { pre = cur; cur = cur->next; } if (cur == NULL) return; // vet に対応するノードを連結リストから削除 pre->next = cur->next; // メモリを解放する free(cur); } /* 辺を削除 */ void removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { AdjListNode *head1 = findNode(graph, vet1); AdjListNode *head2 = findNode(graph, vet2); assert(head1 != NULL && head2 != NULL); // 辺 vet1 - vet2 を削除 removeEdgeHelper(head1, head2->vertex); removeEdgeHelper(head2, head1->vertex); } /* 頂点を追加 */ void addVertex(GraphAdjList *graph, Vertex *vet) { assert(graph != NULL && graph->size < MAX_SIZE); AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode)); head->vertex = vet; head->next = NULL; // 隣接リストに新しいリストを追加 graph->heads[graph->size++] = head; } /* 頂点を削除 */ void removeVertex(GraphAdjList *graph, Vertex *vet) { AdjListNode *node = findNode(graph, vet); assert(node != NULL); // 隣接リストから頂点 vet に対応するリストを削除 AdjListNode *cur = node, *pre = NULL; while (cur) { pre = cur; cur = cur->next; free(pre); } // 他の頂点のリストを走査し、vet を含むすべての辺を削除 for (int i = 0; i < graph->size; i++) { cur = graph->heads[i]; pre = NULL; while (cur) { pre = cur; cur = cur->next; if (cur && cur->vertex == vet) { pre->next = cur->next; free(cur); break; } } } // この頂点より後ろの頂点を前に詰めて欠損を埋める int i; for (i = 0; i < graph->size; i++) { if (graph->heads[i] == node) break; } for (int j = i; j < graph->size - 1; j++) { graph->heads[j] = graph->heads[j + 1]; } graph->size--; free(vet); } /* 隣接リストを出力 */ void printGraph(const GraphAdjList *graph) { printf("隣接リスト =\n"); for (int i = 0; i < graph->size; ++i) { AdjListNode *node = graph->heads[i]; printf("%d: [", node->vertex->val); node = node->next; while (node) { printf("%d, ", node->vertex->val); node = node->next; } printf("]\n"); } } ================================================ FILE: ja/codes/c/chapter_graph/graph_adjacency_list_test.c ================================================ /** * File: graph_adjacency_list_test.c * Created Time: 2023-07-11 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" /* Driver Code */ int main() { int vals[] = {1, 3, 2, 5, 4}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // すべての頂点と辺を追加 for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\n初期化後のグラフは\n"); printGraph(graph); /* 辺を追加 */ // 頂点 1, 2 は v[0], v[2] addEdge(graph, v[0], v[2]); printf("\n辺 1-2 を追加した後のグラフは\n"); printGraph(graph); /* 辺を削除 */ // 頂点 1, 3 は v[0], v[1] removeEdge(graph, v[0], v[1]); printf("\n辺 1-3 を削除した後のグラフは\n"); printGraph(graph); /* 頂点を追加 */ Vertex *v5 = newVertex(6); addVertex(graph, v5); printf("\n頂点 6 を追加した後のグラフは\n"); printGraph(graph); /* 頂点を削除 */ // 頂点 3 は v[1] removeVertex(graph, v[1]); printf("\n頂点 3 を削除すると、グラフは次のようになります:\n"); printGraph(graph); // メモリを解放する delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: ja/codes/c/chapter_graph/graph_adjacency_matrix.c ================================================ /** * File: graph_adjacency_matrix.c * Created Time: 2023-07-06 * Author: NI-SW (947743645@qq.com) */ #include "../utils/common.h" // 頂点数の最大値を 100 と仮定する #define MAX_SIZE 100 /* 隣接行列に基づく無向グラフ構造体 */ typedef struct { int vertices[MAX_SIZE]; int adjMat[MAX_SIZE][MAX_SIZE]; int size; } GraphAdjMat; /* コンストラクタ */ GraphAdjMat *newGraphAdjMat() { GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat)); graph->size = 0; for (int i = 0; i < MAX_SIZE; i++) { for (int j = 0; j < MAX_SIZE; j++) { graph->adjMat[i][j] = 0; } } return graph; } /* デストラクタ */ void delGraphAdjMat(GraphAdjMat *graph) { free(graph); } /* 頂点を追加 */ void addVertex(GraphAdjMat *graph, int val) { if (graph->size == MAX_SIZE) { fprintf(stderr, "グラフの頂点数が最大値に達しました\n"); return; } // n 番目の頂点を追加し、n 行目と n 列目を 0 にする int n = graph->size; graph->vertices[n] = val; for (int i = 0; i <= n; i++) { graph->adjMat[n][i] = graph->adjMat[i][n] = 0; } graph->size++; } /* 頂点を削除 */ void removeVertex(GraphAdjMat *graph, int index) { if (index < 0 || index >= graph->size) { fprintf(stderr, "頂点インデックスが範囲外です\n"); return; } // 頂点リストから index の頂点を削除する for (int i = index; i < graph->size - 1; i++) { graph->vertices[i] = graph->vertices[i + 1]; } // 隣接行列で index 行を削除する for (int i = index; i < graph->size - 1; i++) { for (int j = 0; j < graph->size; j++) { graph->adjMat[i][j] = graph->adjMat[i + 1][j]; } } // 隣接行列で index 列を削除する for (int i = 0; i < graph->size; i++) { for (int j = index; j < graph->size - 1; j++) { graph->adjMat[i][j] = graph->adjMat[i][j + 1]; } } graph->size--; } /* 辺を追加 */ // 引数 i, j は vertices の要素インデックスに対応する void addEdge(GraphAdjMat *graph, int i, int j) { if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { fprintf(stderr, "辺インデックスが範囲外であるか、同一です\n"); return; } graph->adjMat[i][j] = 1; graph->adjMat[j][i] = 1; } /* 辺を削除 */ // 引数 i, j は vertices の要素インデックスに対応する void removeEdge(GraphAdjMat *graph, int i, int j) { if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { fprintf(stderr, "辺インデックスが範囲外であるか、同一です\n"); return; } graph->adjMat[i][j] = 0; graph->adjMat[j][i] = 0; } /* 隣接行列を出力 */ void printGraphAdjMat(GraphAdjMat *graph) { printf("頂点リスト = "); printArray(graph->vertices, graph->size); printf("隣接行列 =\n"); for (int i = 0; i < graph->size; i++) { printArray(graph->adjMat[i], graph->size); } } /* Driver Code */ int main() { // 無向グラフを初期化 GraphAdjMat *graph = newGraphAdjMat(); int vertices[] = {1, 3, 2, 5, 4}; for (int i = 0; i < 5; i++) { addVertex(graph, vertices[i]); } int edges[][2] = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; for (int i = 0; i < 6; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\n初期化後のグラフは\n"); printGraphAdjMat(graph); /* 辺を追加 */ // 頂点 1, 2 のインデックスはそれぞれ 0, 2 addEdge(graph, 0, 2); printf("\n辺 1-2 を追加した後のグラフは\n"); printGraphAdjMat(graph); /* 辺を削除 */ // 頂点 1, 3 のインデックスはそれぞれ 0, 1 removeEdge(graph, 0, 1); printf("\n辺 1-3 を削除した後のグラフは\n"); printGraphAdjMat(graph); /* 頂点を追加 */ addVertex(graph, 6); printf("\n頂点 6 を追加した後のグラフは\n"); printGraphAdjMat(graph); /* 頂点を削除 */ // 頂点 3 のインデックスは 1 removeVertex(graph, 1); printf("\n頂点 3 を削除すると、グラフは次のようになります\n"); printGraphAdjMat(graph); // メモリを解放する delGraphAdjMat(graph); return 0; } ================================================ FILE: ja/codes/c/chapter_graph/graph_bfs.c ================================================ /** * File: graph_bfs.c * Created Time: 2023-07-11 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" // ノード数の上限を 100 と仮定 #define MAX_SIZE 100 /* ノードキュー構造体 */ typedef struct { Vertex *vertices[MAX_SIZE]; int front, rear, size; } Queue; /* コンストラクタ */ Queue *newQueue() { Queue *q = (Queue *)malloc(sizeof(Queue)); q->front = q->rear = q->size = 0; return q; } /* キューが空かどうかを判定 */ int isEmpty(Queue *q) { return q->size == 0; } /* エンキュー操作 */ void enqueue(Queue *q, Vertex *vet) { q->vertices[q->rear] = vet; q->rear = (q->rear + 1) % MAX_SIZE; q->size++; } /* デキュー操作 */ Vertex *dequeue(Queue *q) { Vertex *vet = q->vertices[q->front]; q->front = (q->front + 1) % MAX_SIZE; q->size--; return vet; } /* 頂点が訪問済みかを確認 */ int isVisited(Vertex **visited, int size, Vertex *vet) { // 走査してノードを探すため、O(n) 時間を要する for (int i = 0; i < size; i++) { if (visited[i] == vet) return 1; } return 0; } /* 幅優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする void graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) { // BFS の実装にキューを用いる Queue *queue = newQueue(); enqueue(queue, startVet); visited[(*visitedSize)++] = startVet; // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す while (!isEmpty(queue)) { Vertex *vet = dequeue(queue); // 先頭の頂点をデキュー res[(*resSize)++] = vet; // 訪問した頂点を記録 // この頂点のすべての隣接頂点を走査 AdjListNode *node = findNode(graph, vet); while (node != NULL) { // 訪問済みの頂点をスキップ if (!isVisited(visited, *visitedSize, node->vertex)) { enqueue(queue, node->vertex); // 未訪問の頂点のみをキューに追加 visited[(*visitedSize)++] = node->vertex; // この頂点を訪問済みにする } node = node->next; } } // メモリを解放する free(queue); } /* Driver Code */ int main() { // 無向グラフを初期化 int vals[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[1], v[4]}, {v[2], v[5]}, {v[3], v[4]}, {v[3], v[6]}, {v[4], v[5]}, {v[4], v[7]}, {v[5], v[8]}, {v[6], v[7]}, {v[7], v[8]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // すべての頂点と辺を追加 for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\n初期化後のグラフは\n"); printGraph(graph); // 幅優先探索 // 頂点の走査順序 Vertex *res[MAX_SIZE]; int resSize = 0; // 訪問済みの頂点を記録する Vertex *visited[MAX_SIZE]; int visitedSize = 0; graphBFS(graph, v[0], res, &resSize, visited, &visitedSize); printf("\n幅優先探索(BFS)の頂点列は次のとおりです\n"); printArray(vetsToVals(res, resSize), resSize); // メモリを解放する delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: ja/codes/c/chapter_graph/graph_dfs.c ================================================ /** * File: graph_dfs.c * Created Time: 2023-07-13 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" // ノード数の上限を 100 と仮定 #define MAX_SIZE 100 /* 頂点が訪問済みかを確認 */ int isVisited(Vertex **res, int size, Vertex *vet) { // 走査してノードを探すため、O(n) 時間を要する for (int i = 0; i < size; i++) { if (res[i] == vet) { return 1; } } return 0; } /* 深さ優先走査の補助関数 */ void dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) { // 訪問した頂点を記録 res[(*resSize)++] = vet; // この頂点のすべての隣接頂点を走査 AdjListNode *node = findNode(graph, vet); while (node != NULL) { // 訪問済みの頂点をスキップ if (!isVisited(res, *resSize, node->vertex)) { // 隣接頂点を再帰的に訪問 dfs(graph, res, resSize, node->vertex); } node = node->next; } } /* 深さ優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする void graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) { dfs(graph, res, resSize, startVet); } /* Driver Code */ int main() { // 無向グラフを初期化 int vals[] = {0, 1, 2, 3, 4, 5, 6}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // すべての頂点と辺を追加 for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\n初期化後のグラフは\n"); printGraph(graph); // 深さ優先探索 Vertex *res[MAX_SIZE]; int resSize = 0; graphDFS(graph, v[0], res, &resSize); printf("\n深さ優先探索(DFS)の頂点列は次のとおりです\n"); printArray(vetsToVals(res, resSize), resSize); // メモリを解放する delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: ja/codes/c/chapter_greedy/CMakeLists.txt ================================================ add_executable(coin_change_greedy coin_change_greedy.c) add_executable(fractional_knapsack fractional_knapsack.c) add_executable(max_capacity max_capacity.c) add_executable(max_product_cutting max_product_cutting.c) if (NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC") target_link_libraries(max_product_cutting m) endif() ================================================ FILE: ja/codes/c/chapter_greedy/coin_change_greedy.c ================================================ /** * File: coin_change_greedy.c * Created Time: 2023-09-07 * Author: lwbaptx (lwbaptx@gmail.com) */ #include "../utils/common.h" /* コイン交換:貪欲法 */ int coinChangeGreedy(int *coins, int size, int amt) { // coins リストはソート済みと仮定する int i = size - 1; int count = 0; // 残額がなくなるまで貪欲選択を繰り返す while (amt > 0) { // 残額以下で最も近い硬貨を見つける while (i > 0 && coins[i] > amt) { i--; } // coins[i] を選択する amt -= coins[i]; count++; } // 実行可能な解が見つからなければ -1 を返す return amt == 0 ? count : -1; } /* Driver Code */ int main() { // 貪欲法:大域最適解を保証できる int coins1[6] = {1, 5, 10, 20, 50, 100}; int amt = 186; int res = coinChangeGreedy(coins1, 6, amt); printf("\ncoins = "); printArray(coins1, 6); printf("amt = %d\n", amt); printf("%d を作るのに必要な最小硬貨枚数は %d です\n", amt, res); // 貪欲法:大域最適解を保証できない int coins2[3] = {1, 20, 50}; amt = 60; res = coinChangeGreedy(coins2, 3, amt); printf("\ncoins = "); printArray(coins2, 3); printf("amt = %d\n", amt); printf("%d を作るのに必要な最小硬貨枚数は %d です\n", amt, res); printf("実際に必要な最小枚数は 3、つまり 20 + 20 + 20 です\n"); // 貪欲法:大域最適解を保証できない int coins3[3] = {1, 49, 50}; amt = 98; res = coinChangeGreedy(coins3, 3, amt); printf("\ncoins = "); printArray(coins3, 3); printf("amt = %d\n", amt); printf("%d を作るのに必要な最小硬貨枚数は %d です\n", amt, res); printf("実際に必要な最小枚数は 2、つまり 49 + 49 です\n"); return 0; } ================================================ FILE: ja/codes/c/chapter_greedy/fractional_knapsack.c ================================================ /** * File: fractional_knapsack.c * Created Time: 2023-09-14 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* 品物 */ typedef struct { int w; // 品物の重さ int v; // 品物の価値 } Item; /* 価値密度でソート */ int sortByValueDensity(const void *a, const void *b) { Item *t1 = (Item *)a; Item *t2 = (Item *)b; return (float)(t1->v) / t1->w < (float)(t2->v) / t2->w; } /* 分数ナップサック:貪欲法 */ float fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) { // 重さと価値の 2 属性を持つ品物リストを作成 Item *items = malloc(sizeof(Item) * itemCount); for (int i = 0; i < itemCount; i++) { items[i] = (Item){.w = wgt[i], .v = val[i]}; } // 単位価値 item.v / item.w の高い順にソートする qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity); // 貪欲選択を繰り返す float res = 0.0; for (int i = 0; i < itemCount; i++) { if (items[i].w <= cap) { // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる res += items[i].v; cap -= items[i].w; } else { // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる res += (float)cap / items[i].w * items[i].v; cap = 0; break; } } free(items); return res; } /* Driver Code */ int main(void) { int wgt[] = {10, 20, 30, 40, 50}; int val[] = {50, 120, 150, 210, 240}; int capacity = 50; // 貪欲法 float res = fractionalKnapsack(wgt, val, sizeof(wgt) / sizeof(int), capacity); printf("ナップサック容量を超えない最大の品物価値は %0.2f です\n", res); return 0; } ================================================ FILE: ja/codes/c/chapter_greedy/max_capacity.c ================================================ /** * File: max_capacity.c * Created Time: 2023-09-15 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* 最小値を求める */ int myMin(int a, int b) { return a < b ? a : b; } /* 最大値を求める */ int myMax(int a, int b) { return a > b ? a : b; } /* 最大容量:貪欲法 */ int maxCapacity(int ht[], int htLength) { // i, j を初期化し、それぞれ配列の両端に置く int i = 0; int j = htLength - 1; // 初期の最大容量は 0 int res = 0; // 2 枚の板が出会うまで貪欲選択を繰り返す while (i < j) { // 最大容量を更新する int capacity = myMin(ht[i], ht[j]) * (j - i); res = myMax(res, capacity); // 短い方を内側へ動かす if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ int main(void) { int ht[] = {3, 8, 5, 2, 7, 7, 3, 4}; // 貪欲法 int res = maxCapacity(ht, sizeof(ht) / sizeof(int)); printf("最大容量は %d です\n", res); return 0; } ================================================ FILE: ja/codes/c/chapter_greedy/max_product_cutting.c ================================================ /** * File: max_product_cutting.c * Created Time: 2023-09-15 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* 最大切断積:貪欲法 */ int maxProductCutting(int n) { // n <= 3 のときは、必ず 1 を切り出す if (n <= 3) { return 1 * (n - 1); } // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする int a = n / 3; int b = n % 3; if (b == 1) { // 余りが 1 のときは、1 * 3 を 2 * 2 に変える return pow(3, a - 1) * 2 * 2; } if (b == 2) { // 余りが 2 のときは、そのままにする return pow(3, a) * 2; } // 余りが 0 のときは、そのままにする return pow(3, a); } /* Driver Code */ int main(void) { int n = 58; // 貪欲法 int res = maxProductCutting(n); printf("最大分割積は %d です\n", res); return 0; } ================================================ FILE: ja/codes/c/chapter_hashing/CMakeLists.txt ================================================ add_executable(array_hash_map array_hash_map.c) add_executable(hash_map_chaining hash_map_chaining.c) add_executable(hash_map_open_addressing hash_map_open_addressing.c) add_executable(simple_hash simple_hash.c) ================================================ FILE: ja/codes/c/chapter_hashing/array_hash_map.c ================================================ /** * File: array_hash_map.c * Created Time: 2023-03-18 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* ハッシュテーブルのデフォルトサイズ */ #define MAX_SIZE 100 /* キーと値の組 int->string */ typedef struct { int key; char *val; } Pair; /* キーと値の組の集合 */ typedef struct { void *set; int len; } MapSet; /* 配列ベースのハッシュテーブル */ typedef struct { Pair *buckets[MAX_SIZE]; } ArrayHashMap; /* コンストラクタ */ ArrayHashMap *newArrayHashMap() { ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap)); for (int i=0; i < MAX_SIZE; i++) { hmap->buckets[i] = NULL; } return hmap; } /* デストラクタ */ void delArrayHashMap(ArrayHashMap *hmap) { for (int i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { free(hmap->buckets[i]->val); free(hmap->buckets[i]); } } free(hmap); } /* ハッシュ関数 */ int hashFunc(int key) { int index = key % MAX_SIZE; return index; } /* 検索操作 */ const char *get(const ArrayHashMap *hmap, const int key) { int index = hashFunc(key); const Pair *Pair = hmap->buckets[index]; if (Pair == NULL) return NULL; return Pair->val; } /* 追加操作 */ void put(ArrayHashMap *hmap, const int key, const char *val) { Pair *Pair = malloc(sizeof(Pair)); Pair->key = key; Pair->val = malloc(strlen(val) + 1); strcpy(Pair->val, val); int index = hashFunc(key); hmap->buckets[index] = Pair; } /* 削除操作 */ void removeItem(ArrayHashMap *hmap, const int key) { int index = hashFunc(key); free(hmap->buckets[index]->val); free(hmap->buckets[index]); hmap->buckets[index] = NULL; } /* すべてのキーと値のペアを取得 */ void pairSet(ArrayHashMap *hmap, MapSet *set) { Pair *entries; int i = 0, index = 0; int total = 0; /* 有効なキーと値のペア数を集計 */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } entries = malloc(sizeof(Pair) * total); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { entries[index].key = hmap->buckets[i]->key; entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1); strcpy(entries[index].val, hmap->buckets[i]->val); index++; } } set->set = entries; set->len = total; } /* すべてのキーを取得 */ void keySet(ArrayHashMap *hmap, MapSet *set) { int *keys; int i = 0, index = 0; int total = 0; /* 有効なキーと値のペア数を集計 */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } keys = malloc(total * sizeof(int)); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { keys[index] = hmap->buckets[i]->key; index++; } } set->set = keys; set->len = total; } /* すべての値を取得 */ void valueSet(ArrayHashMap *hmap, MapSet *set) { char **vals; int i = 0, index = 0; int total = 0; /* 有効なキーと値のペア数を集計 */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } vals = malloc(total * sizeof(char *)); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { vals[index] = hmap->buckets[i]->val; index++; } } set->set = vals; set->len = total; } /* ハッシュテーブルを出力 */ void print(ArrayHashMap *hmap) { int i; MapSet set; pairSet(hmap, &set); Pair *entries = (Pair *)set.set; for (i = 0; i < set.len; i++) { printf("%d -> %s\n", entries[i].key, entries[i].val); } free(set.set); } /* Driver Code */ int main() { /* ハッシュテーブルを初期化 */ ArrayHashMap *hmap = newArrayHashMap(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 put(hmap, 12836, "シャオハー"); put(hmap, 15937, "シャオルオ"); put(hmap, 16750, "シャオスワン"); put(hmap, 13276, "シャオファー"); put(hmap, 10583, "シャオヤー"); printf("\n追加完了後、ハッシュテーブルは\nKey -> Value\n"); print(hmap); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 const char *name = get(hmap, 15937); printf("\n学籍番号 15937 を入力すると、名前 %s が見つかりました\n", name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 removeItem(hmap, 10583); printf("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value\n"); print(hmap); /* ハッシュテーブルを走査 */ int i; printf("\nキーと値のペア Key->Value を走査\n"); print(hmap); MapSet set; keySet(hmap, &set); int *keys = (int *)set.set; printf("\nキー Key のみを個別に走査\n"); for (i = 0; i < set.len; i++) { printf("%d\n", keys[i]); } free(set.set); valueSet(hmap, &set); char **vals = (char **)set.set; printf("\nValue のみを個別に走査\n"); for (i = 0; i < set.len; i++) { printf("%s\n", vals[i]); } free(set.set); delArrayHashMap(hmap); return 0; } ================================================ FILE: ja/codes/c/chapter_hashing/hash_map_chaining.c ================================================ /** * File: hash_map_chaining.c * Created Time: 2023-10-13 * Author: SenMing (1206575349@qq.com), krahets (krahets@163.com) */ #include #include #include // val の最大長を 100 と仮定する #define MAX_SIZE 100 /* キーと値の組 */ typedef struct { int key; char val[MAX_SIZE]; } Pair; /* 連結リストノード */ typedef struct Node { Pair *pair; struct Node *next; } Node; /* チェイン法ハッシュテーブル */ typedef struct { int size; // キーと値のペア数 int capacity; // ハッシュテーブル容量 double loadThres; // リサイズを発動する負荷率のしきい値 int extendRatio; // 拡張倍率 Node **buckets; // バケット配列 } HashMapChaining; /* コンストラクタ */ HashMapChaining *newHashMapChaining() { HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining)); hashMap->size = 0; hashMap->capacity = 4; hashMap->loadThres = 2.0 / 3.0; hashMap->extendRatio = 2; hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); for (int i = 0; i < hashMap->capacity; i++) { hashMap->buckets[i] = NULL; } return hashMap; } /* デストラクタ */ void delHashMapChaining(HashMapChaining *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Node *cur = hashMap->buckets[i]; while (cur) { Node *tmp = cur; cur = cur->next; free(tmp->pair); free(tmp); } } free(hashMap->buckets); free(hashMap); } /* ハッシュ関数 */ int hashFunc(HashMapChaining *hashMap, int key) { return key % hashMap->capacity; } /* 負荷率 */ double loadFactor(HashMapChaining *hashMap) { return (double)hashMap->size / (double)hashMap->capacity; } /* 検索操作 */ char *get(HashMapChaining *hashMap, int key) { int index = hashFunc(hashMap, key); // バケットを走査し、key が見つかれば対応する val を返す Node *cur = hashMap->buckets[index]; while (cur) { if (cur->pair->key == key) { return cur->pair->val; } cur = cur->next; } return ""; // key が見つからない場合は空文字列を返す } /* 追加操作 */ void put(HashMapChaining *hashMap, int key, const char *val); /* ハッシュテーブルを拡張 */ void extend(HashMapChaining *hashMap) { // 元のハッシュテーブルを一時保存 int oldCapacity = hashMap->capacity; Node **oldBuckets = hashMap->buckets; // リサイズ後の新しいハッシュテーブルを初期化 hashMap->capacity *= hashMap->extendRatio; hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); for (int i = 0; i < hashMap->capacity; i++) { hashMap->buckets[i] = NULL; } hashMap->size = 0; // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for (int i = 0; i < oldCapacity; i++) { Node *cur = oldBuckets[i]; while (cur) { put(hashMap, cur->pair->key, cur->pair->val); Node *temp = cur; cur = cur->next; // メモリを解放する free(temp->pair); free(temp); } } free(oldBuckets); } /* 追加操作 */ void put(HashMapChaining *hashMap, int key, const char *val) { // 負荷率がしきい値を超えたら、リサイズを実行 if (loadFactor(hashMap) > hashMap->loadThres) { extend(hashMap); } int index = hashFunc(hashMap, key); // バケットを走査し、指定した key が見つかれば対応する val を更新して返す Node *cur = hashMap->buckets[index]; while (cur) { if (cur->pair->key == key) { strcpy(cur->pair->val, val); // 指定した `key` に遭遇したら、対応する `val` を更新して返す return; } cur = cur->next; } // 該当する `key` がなければ、キーと値のペアを連結リストの先頭に追加する Pair *newPair = (Pair *)malloc(sizeof(Pair)); newPair->key = key; strcpy(newPair->val, val); Node *newNode = (Node *)malloc(sizeof(Node)); newNode->pair = newPair; newNode->next = hashMap->buckets[index]; hashMap->buckets[index] = newNode; hashMap->size++; } /* 削除操作 */ void removeItem(HashMapChaining *hashMap, int key) { int index = hashFunc(hashMap, key); Node *cur = hashMap->buckets[index]; Node *pre = NULL; while (cur) { if (cur->pair->key == key) { // そこからキーと値の組を削除する if (pre) { pre->next = cur->next; } else { hashMap->buckets[index] = cur->next; } // メモリを解放する free(cur->pair); free(cur); hashMap->size--; return; } pre = cur; cur = cur->next; } } /* ハッシュテーブルを出力 */ void print(HashMapChaining *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Node *cur = hashMap->buckets[i]; printf("["); while (cur) { printf("%d -> %s, ", cur->pair->key, cur->pair->val); cur = cur->next; } printf("]\n"); } } /* Driver Code */ int main() { /* ハッシュテーブルを初期化 */ HashMapChaining *hashMap = newHashMapChaining(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 put(hashMap, 12836, "シャオハー"); put(hashMap, 15937, "シャオルオ"); put(hashMap, 16750, "シャオスワン"); put(hashMap, 13276, "シャオファー"); put(hashMap, 10583, "シャオヤー"); printf("\n追加完了後、ハッシュテーブルは\nKey -> Value\n"); print(hashMap); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 char *name = get(hashMap, 13276); printf("\n学籍番号 13276 を入力すると、名前 %s が見つかりました\n", name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 removeItem(hashMap, 12836); printf("\n学籍番号 12836 を削除した後、ハッシュテーブルは\nKey -> Value\n"); print(hashMap); /* ハッシュテーブルの領域を解放する */ delHashMapChaining(hashMap); return 0; } ================================================ FILE: ja/codes/c/chapter_hashing/hash_map_open_addressing.c ================================================ /** * File: hash_map_open_addressing.c * Created Time: 2023-10-6 * Author: lclc6 (w1929522410@163.com) */ #include "../utils/common.h" /* オープンアドレス法ハッシュテーブル */ typedef struct { int key; char *val; } Pair; /* オープンアドレス法ハッシュテーブル */ typedef struct { int size; // キーと値のペア数 int capacity; // ハッシュテーブル容量 double loadThres; // リサイズを発動する負荷率のしきい値 int extendRatio; // 拡張倍率 Pair **buckets; // バケット配列 Pair *TOMBSTONE; // 削除済みマーク } HashMapOpenAddressing; // 関数宣言 void extend(HashMapOpenAddressing *hashMap); /* コンストラクタ */ HashMapOpenAddressing *newHashMapOpenAddressing() { HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing)); hashMap->size = 0; hashMap->capacity = 4; hashMap->loadThres = 2.0 / 3.0; hashMap->extendRatio = 2; hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair)); hashMap->TOMBSTONE->key = -1; hashMap->TOMBSTONE->val = "-1"; return hashMap; } /* デストラクタ */ void delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Pair *pair = hashMap->buckets[i]; if (pair != NULL && pair != hashMap->TOMBSTONE) { free(pair->val); free(pair); } } free(hashMap->buckets); free(hashMap->TOMBSTONE); free(hashMap); } /* ハッシュ関数 */ int hashFunc(HashMapOpenAddressing *hashMap, int key) { return key % hashMap->capacity; } /* 負荷率 */ double loadFactor(HashMapOpenAddressing *hashMap) { return (double)hashMap->size / (double)hashMap->capacity; } /* key に対応するバケットインデックスを探す */ int findBucket(HashMapOpenAddressing *hashMap, int key) { int index = hashFunc(hashMap, key); int firstTombstone = -1; // 線形プロービングを行い、空バケットに達したら終了 while (hashMap->buckets[index] != NULL) { // key が見つかったら、対応するバケットのインデックスを返す if (hashMap->buckets[index]->key == key) { // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 if (firstTombstone != -1) { hashMap->buckets[firstTombstone] = hashMap->buckets[index]; hashMap->buckets[index] = hashMap->TOMBSTONE; return firstTombstone; // 移動後のバケットインデックスを返す } return index; // バケットのインデックスを返す } // 最初に見つかった削除マークを記録 if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) { firstTombstone = index; } // バケットのインデックスを計算し、末尾を越えたら先頭に戻る index = (index + 1) % hashMap->capacity; } // key が存在しない場合は追加位置のインデックスを返す return firstTombstone == -1 ? index : firstTombstone; } /* 検索操作 */ char *get(HashMapOpenAddressing *hashMap, int key) { // key に対応するバケットインデックスを探す int index = findBucket(hashMap, key); // キーと値の組が見つかったら、対応する val を返す if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { return hashMap->buckets[index]->val; } // キーと値の組が存在しない場合は空文字列を返す return ""; } /* 追加操作 */ void put(HashMapOpenAddressing *hashMap, int key, char *val) { // 負荷率がしきい値を超えたら、リサイズを実行 if (loadFactor(hashMap) > hashMap->loadThres) { extend(hashMap); } // key に対応するバケットインデックスを探す int index = findBucket(hashMap, key); // キーと値の組が見つかったら、val を上書きして返す if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { free(hashMap->buckets[index]->val); hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1)); strcpy(hashMap->buckets[index]->val, val); hashMap->buckets[index]->val[strlen(val)] = '\0'; return; } // キーと値の組が存在しない場合は、その組を追加する Pair *pair = (Pair *)malloc(sizeof(Pair)); pair->key = key; pair->val = (char *)malloc(sizeof(strlen(val) + 1)); strcpy(pair->val, val); pair->val[strlen(val)] = '\0'; hashMap->buckets[index] = pair; hashMap->size++; } /* 削除操作 */ void removeItem(HashMapOpenAddressing *hashMap, int key) { // key に対応するバケットインデックスを探す int index = findBucket(hashMap, key); // キーと値の組が見つかったら、削除マーカーで上書きする if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { Pair *pair = hashMap->buckets[index]; free(pair->val); free(pair); hashMap->buckets[index] = hashMap->TOMBSTONE; hashMap->size--; } } /* ハッシュテーブルを拡張 */ void extend(HashMapOpenAddressing *hashMap) { // 元のハッシュテーブルを一時保存 Pair **bucketsTmp = hashMap->buckets; int oldCapacity = hashMap->capacity; // リサイズ後の新しいハッシュテーブルを初期化 hashMap->capacity *= hashMap->extendRatio; hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); hashMap->size = 0; // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for (int i = 0; i < oldCapacity; i++) { Pair *pair = bucketsTmp[i]; if (pair != NULL && pair != hashMap->TOMBSTONE) { put(hashMap, pair->key, pair->val); free(pair->val); free(pair); } } free(bucketsTmp); } /* ハッシュテーブルを出力 */ void print(HashMapOpenAddressing *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Pair *pair = hashMap->buckets[i]; if (pair == NULL) { printf("NULL\n"); } else if (pair == hashMap->TOMBSTONE) { printf("TOMBSTONE\n"); } else { printf("%d -> %s\n", pair->key, pair->val); } } } /* Driver Code */ int main() { // ハッシュテーブルを初期化 HashMapOpenAddressing *hashmap = newHashMapOpenAddressing(); // 追加操作 // ハッシュテーブルにキーと値の組 (key, val) を追加する put(hashmap, 12836, "シャオハー"); put(hashmap, 15937, "シャオルオ"); put(hashmap, 16750, "シャオスワン"); put(hashmap, 13276, "シャオファー"); put(hashmap, 10583, "シャオヤー"); printf("\n追加完了後、ハッシュテーブルは\nKey -> Value\n"); print(hashmap); // 検索操作 // ハッシュテーブルにキー key を入力し、値 val を得る char *name = get(hashmap, 13276); printf("\n学籍番号 13276 を入力すると、名前 %s が見つかりました\n", name); // 削除操作 // ハッシュテーブルからキーと値の組 (key, val) を削除する removeItem(hashmap, 16750); printf("\n16750 を削除した後、ハッシュテーブルは\nKey -> Value\n"); print(hashmap); // ハッシュテーブルを破棄する delHashMapOpenAddressing(hashmap); return 0; } ================================================ FILE: ja/codes/c/chapter_hashing/simple_hash.c ================================================ /** * File: simple_hash.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 加算ハッシュ */ int addHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = (hash + (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* 乗算ハッシュ */ int mulHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = (31 * hash + (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* XOR ハッシュ */ int xorHash(char *key) { int hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash ^= (unsigned char)key[i]; } return hash & MODULUS; } /* 回転ハッシュ */ int rotHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* Driver Code */ int main() { char *key = "Hello アルゴリズム"; int hash = addHash(key); printf("加算ハッシュ値は %d\n", hash); hash = mulHash(key); printf("乗算ハッシュ値は %d\n", hash); hash = xorHash(key); printf("XORハッシュ値は %d\n", hash); hash = rotHash(key); printf("回転ハッシュ値は %d\n", hash); return 0; } ================================================ FILE: ja/codes/c/chapter_heap/CMakeLists.txt ================================================ add_executable(my_heap_test my_heap_test.c) add_executable(top_k top_k.c) ================================================ FILE: ja/codes/c/chapter_heap/my_heap.c ================================================ /** * File: my_heap.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 5000 /* 最大ヒープ */ typedef struct { // size は実際の要素数を表す int size; // あらかじめメモリを確保した配列を使い、拡張を避ける int data[MAX_SIZE]; } MaxHeap; // 関数宣言 void siftDown(MaxHeap *maxHeap, int i); void siftUp(MaxHeap *maxHeap, int i); int parent(MaxHeap *maxHeap, int i); /* コンストラクタ。スライスからヒープを構築する */ MaxHeap *newMaxHeap(int nums[], int size) { // すべての要素をヒープに入れる MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap)); maxHeap->size = size; memcpy(maxHeap->data, nums, size * sizeof(int)); for (int i = parent(maxHeap, size - 1); i >= 0; i--) { // 葉ノード以外のすべてのノードをヒープ化 siftDown(maxHeap, i); } return maxHeap; } /* デストラクタ */ void delMaxHeap(MaxHeap *maxHeap) { // メモリを解放する free(maxHeap); } /* 左子ノードのインデックスを取得 */ int left(MaxHeap *maxHeap, int i) { return 2 * i + 1; } /* 右子ノードのインデックスを取得 */ int right(MaxHeap *maxHeap, int i) { return 2 * i + 2; } /* 親ノードのインデックスを取得 */ int parent(MaxHeap *maxHeap, int i) { return (i - 1) / 2; // 切り捨て } /* 要素を交換 */ void swap(MaxHeap *maxHeap, int i, int j) { int temp = maxHeap->data[i]; maxHeap->data[i] = maxHeap->data[j]; maxHeap->data[j] = temp; } /* ヒープのサイズを取得 */ int size(MaxHeap *maxHeap) { return maxHeap->size; } /* ヒープが空かどうかを判定 */ int isEmpty(MaxHeap *maxHeap) { return maxHeap->size == 0; } /* ヒープ先頭要素にアクセス */ int peek(MaxHeap *maxHeap) { return maxHeap->data[0]; } /* 要素をヒープに追加 */ void push(MaxHeap *maxHeap, int val) { // 通常は、これほど多くのノードを追加すべきではない if (maxHeap->size == MAX_SIZE) { printf("heap is full!"); return; } // ノードを追加 maxHeap->data[maxHeap->size] = val; maxHeap->size++; // 下から上へヒープ化 siftUp(maxHeap, maxHeap->size - 1); } /* 要素をヒープから取り出す */ int pop(MaxHeap *maxHeap) { // 空判定の処理 if (isEmpty(maxHeap)) { printf("heap is empty!"); return INT_MAX; } // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) swap(maxHeap, 0, size(maxHeap) - 1); // ノードを削除 int val = maxHeap->data[maxHeap->size - 1]; maxHeap->size--; // 上から下へヒープ化 siftDown(maxHeap, 0); // ヒープ先頭要素を返す return val; } /* ノード i から始めて、上から下へヒープ化 */ void siftDown(MaxHeap *maxHeap, int i) { while (true) { // ノード i, l, r のうち値が最大のノードを max とする int l = left(maxHeap, i); int r = right(maxHeap, i); int max = i; if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) { max = l; } if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) { max = r; } // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (max == i) { break; } // 2 つのノードを交換 swap(maxHeap, i, max); // ループで上から下へヒープ化 i = max; } } /* ノード i から始めて、下から上へヒープ化 */ void siftUp(MaxHeap *maxHeap, int i) { while (true) { // ノード i の親ノードを取得 int p = parent(maxHeap, i); // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) { break; } // 2 つのノードを交換 swap(maxHeap, i, p); // ループで下から上へヒープ化 i = p; } } ================================================ FILE: ja/codes/c/chapter_heap/my_heap_test.c ================================================ /** * File: my_heap_test.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "my_heap.c" /* Driver Code */ int main() { /* ヒープを初期化 */ // 最大ヒープを初期化 int nums[] = {9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; MaxHeap *maxHeap = newMaxHeap(nums, sizeof(nums) / sizeof(int)); printf("配列を入力してヒープ化した後\n"); printHeap(maxHeap->data, maxHeap->size); /* ヒープ頂点の要素を取得 */ printf("\nヒープの先頭要素は %d\n", peek(maxHeap)); /* 要素をヒープに追加 */ push(maxHeap, 7); printf("\n要素 7 をヒープに追加した後\n"); printHeap(maxHeap->data, maxHeap->size); /* ヒープ頂点の要素を取り出す */ int top = pop(maxHeap); printf("\nヒープの先頭要素 %d を取り出した後\n", top); printHeap(maxHeap->data, maxHeap->size); /* ヒープのサイズを取得 */ printf("\nヒープの要素数は %d\n", size(maxHeap)); /* ヒープが空かどうかを判定 */ printf("\nヒープが空かどうか %d\n", isEmpty(maxHeap)); // メモリを解放する delMaxHeap(maxHeap); return 0; } ================================================ FILE: ja/codes/c/chapter_heap/top_k.c ================================================ /** * File: top_k.c * Created Time: 2023-10-26 * Author: krahets (krahets163.com) */ #include "my_heap.c" /* 要素をヒープに追加 */ void pushMinHeap(MaxHeap *maxHeap, int val) { // 要素を反転する push(maxHeap, -val); } /* 要素をヒープから取り出す */ int popMinHeap(MaxHeap *maxHeap) { // 要素を反転する return -pop(maxHeap); } /* ヒープ先頭要素にアクセス */ int peekMinHeap(MaxHeap *maxHeap) { // 要素を反転する return -peek(maxHeap); } /* ヒープから要素を取り出す */ int *getMinHeap(MaxHeap *maxHeap) { // ヒープ内のすべての要素を反転して res 配列に格納 int *res = (int *)malloc(maxHeap->size * sizeof(int)); for (int i = 0; i < maxHeap->size; i++) { res[i] = -maxHeap->data[i]; } return res; } // ヒープに基づいて配列中の最大の k 個の要素を求める関数 int *topKHeap(int *nums, int sizeNums, int k) { // 最小ヒープを初期化する // 注意: ヒープ内の全要素を反転し、最大ヒープで最小ヒープをシミュレートする int *empty = (int *)malloc(0); MaxHeap *maxHeap = newMaxHeap(empty, 0); // 配列の先頭 k 個の要素をヒープに追加 for (int i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // k+1 番目の要素から開始し、ヒープ長を k に保つ for (int i = k; i < sizeNums; i++) { // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } int *res = getMinHeap(maxHeap); // メモリを解放する delMaxHeap(maxHeap); return res; } /* Driver Code */ int main() { int nums[] = {1, 7, 6, 3, 2}; int k = 3; int sizeNums = sizeof(nums) / sizeof(nums[0]); int *res = topKHeap(nums, sizeNums, k); printf("最大の %d 個の要素は: ", k); printArray(res, k); free(res); return 0; } ================================================ FILE: ja/codes/c/chapter_searching/CMakeLists.txt ================================================ add_executable(binary_search binary_search.c) add_executable(two_sum two_sum.c) add_executable(binary_search_edge binary_search_edge.c) add_executable(binary_search_insertion binary_search_insertion.c) ================================================ FILE: ja/codes/c/chapter_searching/binary_search.c ================================================ /** * File: binary_search.c * Created Time: 2023-03-18 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* 二分探索(両閉区間) */ int binarySearch(int *nums, int len, int target) { // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す int i = 0, j = len - 1; // ループし、探索区間が空になったら終了する(i > j で空) while (i <= j) { int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) // この場合、target は区間 [m+1, j] にある i = m + 1; else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある j = m - 1; else // 目標要素が見つかったらそのインデックスを返す return m; } // 目標要素が見つからなければ -1 を返す return -1; } /* 二分探索(左閉右開区間) */ int binarySearchLCRO(int *nums, int len, int target) { // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す int i = 0, j = len; // ループし、探索区間が空になったら終了する(i = j で空) while (i < j) { int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) // この場合、target は区間 [m+1, j) にある i = m + 1; else if (nums[m] > target) // この場合、target は区間 [i, m) にある j = m; else // 目標要素が見つかったらそのインデックスを返す return m; } // 目標要素が見つからなければ -1 を返す return -1; } /* Driver Code */ int main() { int target = 6; int nums[10] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; /* 二分探索(両閉区間) */ int index = binarySearch(nums, 10, target); printf("対象要素 6 のインデックス = %d\n", index); /* 二分探索(左閉右開区間) */ index = binarySearchLCRO(nums, 10, target); printf("対象要素 6 のインデックス = %d\n", index); return 0; } ================================================ FILE: ja/codes/c/chapter_searching/binary_search_edge.c ================================================ /** * File: binary_search_edge.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 二分探索で挿入位置を探す(重複要素あり) */ int binarySearchInsertion(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) { i = m + 1; // target は区間 [m+1, j] にある } else { j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある } } // 挿入位置 i を返す return i; } /* 最も左の target を二分探索 */ int binarySearchLeftEdge(int *nums, int numSize, int target) { // target の挿入位置を探すのと等価 int i = binarySearchInsertion(nums, numSize, target); // target が見つからなければ、-1 を返す if (i == numSize || nums[i] != target) { return -1; } // target が見つかったら、インデックス i を返す return i; } /* 最も右の target を二分探索 */ int binarySearchRightEdge(int *nums, int numSize, int target) { // 最左の target + 1 を探す問題に変換する int i = binarySearchInsertion(nums, numSize, target + 1); // j は最も右の target を指し、i は target より大きい最初の要素を指す int j = i - 1; // target が見つからなければ、-1 を返す if (j == -1 || nums[j] != target) { return -1; } // target が見つかったら、インデックス j を返す return j; } /* Driver Code */ int main() { // 重複要素を含む配列 int nums[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; printf("\n配列 nums = "); printArray(nums, sizeof(nums) / sizeof(nums[0])); // 二分探索で左端と右端を探す int targets[] = {6, 7}; for (int i = 0; i < sizeof(targets) / sizeof(targets[0]); i++) { int index = binarySearchLeftEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); printf("最も左の要素 %d のインデックスは %d\n", targets[i], index); index = binarySearchRightEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); printf("最も右の要素 %d のインデックスは %d\n", targets[i], index); } return 0; } ================================================ FILE: ja/codes/c/chapter_searching/binary_search_insertion.c ================================================ /** * File: binary_search_insertion.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 二分探索で挿入位置を探す(重複要素なし) */ int binarySearchInsertionSimple(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) { i = m + 1; // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1; // target は区間 [i, m-1] にある } else { return m; // target が見つかったら、挿入位置 m を返す } } // target が見つからなければ、挿入位置 i を返す return i; } /* 二分探索で挿入位置を探す(重複要素あり) */ int binarySearchInsertion(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) { i = m + 1; // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1; // target は区間 [i, m-1] にある } else { j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある } } // 挿入位置 i を返す return i; } /* Driver Code */ int main() { // 重複要素のない配列 int nums1[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; printf("\n配列 nums = "); printArray(nums1, sizeof(nums1) / sizeof(nums1[0])); // 二分探索で挿入位置を探す int targets1[] = {6, 9}; for (int i = 0; i < sizeof(targets1) / sizeof(targets1[0]); i++) { int index = binarySearchInsertionSimple(nums1, sizeof(nums1) / sizeof(nums1[0]), targets1[i]); printf("要素 %d の挿入位置のインデックスは %d\n", targets1[i], index); } // 重複要素を含む配列 int nums2[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; printf("\n配列 nums = "); printArray(nums2, sizeof(nums2) / sizeof(nums2[0])); // 二分探索で挿入位置を探す int targets2[] = {2, 6, 20}; for (int i = 0; i < sizeof(targets2) / sizeof(int); i++) { int index = binarySearchInsertion(nums2, sizeof(nums2) / sizeof(nums2[0]), targets2[i]); printf("要素 %d の挿入位置のインデックスは %d\n", targets2[i], index); } return 0; } ================================================ FILE: ja/codes/c/chapter_searching/two_sum.c ================================================ /** * File: two_sum.c * Created Time: 2023-01-19 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* 方法 1:総当たり列挙 */ int *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) { for (int i = 0; i < numsSize; ++i) { for (int j = i + 1; j < numsSize; ++j) { if (nums[i] + nums[j] == target) { int *res = malloc(sizeof(int) * 2); res[0] = i, res[1] = j; *returnSize = 2; return res; } } } *returnSize = 0; return NULL; } /* ハッシュテーブル */ typedef struct { int key; int val; UT_hash_handle hh; // uthash.h を用いて実装 } HashTable; /* ハッシュテーブルを検索する */ HashTable *find(HashTable *h, int key) { HashTable *tmp; HASH_FIND_INT(h, &key, tmp); return tmp; } /* ハッシュテーブルに要素を挿入する */ void insert(HashTable **h, int key, int val) { HashTable *t = find(*h, key); if (t == NULL) { HashTable *tmp = malloc(sizeof(HashTable)); tmp->key = key, tmp->val = val; HASH_ADD_INT(*h, key, tmp); } else { t->val = val; } } /* 方法 2:補助ハッシュテーブル */ int *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) { HashTable *hashtable = NULL; for (int i = 0; i < numsSize; i++) { HashTable *t = find(hashtable, target - nums[i]); if (t != NULL) { int *res = malloc(sizeof(int) * 2); res[0] = t->val, res[1] = i; *returnSize = 2; return res; } insert(&hashtable, nums[i], i); } *returnSize = 0; return NULL; } /* Driver Code */ int main() { // ======= Test Case ======= int nums[] = {2, 7, 11, 15}; int target = 13; // ====== Driver Code ====== int returnSize; int *res = twoSumBruteForce(nums, sizeof(nums) / sizeof(int), target, &returnSize); // 方法 1 printf("方法1 res = "); printArray(res, returnSize); // 方法 2 res = twoSumHashTable(nums, sizeof(nums) / sizeof(int), target, &returnSize); printf("方法2 res = "); printArray(res, returnSize); return 0; } ================================================ FILE: ja/codes/c/chapter_sorting/CMakeLists.txt ================================================ add_executable(bubble_sort bubble_sort.c) add_executable(insertion_sort insertion_sort.c) add_executable(quick_sort quick_sort.c) add_executable(counting_sort counting_sort.c) add_executable(radix_sort radix_sort.c) add_executable(merge_sort merge_sort.c) add_executable(heap_sort heap_sort.c) add_executable(bucket_sort bucket_sort.c) add_executable(selection_sort selection_sort.c) ================================================ FILE: ja/codes/c/chapter_sorting/bubble_sort.c ================================================ /** * File: bubble_sort.c * Created Time: 2022-12-26 * Author: Listening (https://github.com/L-Super) */ #include "../utils/common.h" /* バブルソート */ void bubbleSort(int nums[], int size) { // 外側のループ:未ソート区間は [0, i] for (int i = size - 1; i > 0; i--) { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; } } } } /* バブルソート(フラグ最適化) */ void bubbleSortWithFlag(int nums[], int size) { // 外側のループ:未ソート区間は [0, i] for (int i = size - 1; i > 0; i--) { bool flag = false; // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; flag = true; } } if (!flag) break; } } /* Driver Code */ int main() { int nums[6] = {4, 1, 3, 1, 5, 2}; printf("バブルソート後: "); bubbleSort(nums, 6); for (int i = 0; i < 6; i++) { printf("%d ", nums[i]); } int nums1[6] = {4, 1, 3, 1, 5, 2}; printf("\n最適化版バブルソート後: "); bubbleSortWithFlag(nums1, 6); for (int i = 0; i < 6; i++) { printf("%d ", nums1[i]); } printf("\n"); return 0; } ================================================ FILE: ja/codes/c/chapter_sorting/bucket_sort.c ================================================ /** * File: bucket_sort.c * Created Time: 2023-05-30 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define SIZE 10 /* `qsort` 用の比較関数 */ int compare(const void *a, const void *b) { float fa = *(const float *)a; float fb = *(const float *)b; return (fa > fb) - (fa < fb); } /* バケットソート */ void bucketSort(float nums[], int n) { int k = n / 2; // k = n/2 個のバケットを初期化する int *sizes = malloc(k * sizeof(int)); // 各バケットのサイズを記録する float **buckets = malloc(k * sizeof(float *)); // 動的配列の配列(バケット) // 各バケットに十分な容量を事前確保する for (int i = 0; i < k; ++i) { buckets[i] = (float *)malloc(n * sizeof(float)); sizes[i] = 0; } // 1. 配列要素を各バケットに振り分ける for (int i = 0; i < n; ++i) { int idx = (int)(nums[i] * k); buckets[idx][sizes[idx]++] = nums[i]; } // 2. 各バケットをソートする for (int i = 0; i < k; ++i) { qsort(buckets[i], sizes[i], sizeof(float), compare); } // 3. ソート済みのバケットを結合する int idx = 0; for (int i = 0; i < k; ++i) { for (int j = 0; j < sizes[i]; ++j) { nums[idx++] = buckets[i][j]; } // メモリを解放する free(buckets[i]); } } /* Driver Code */ int main() { // 入力データは範囲 [0, 1) の浮動小数点数とする float nums[SIZE] = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; bucketSort(nums, SIZE); printf("バケットソート完了後 nums = "); printArrayFloat(nums, SIZE); return 0; } ================================================ FILE: ja/codes/c/chapter_sorting/counting_sort.c ================================================ /** * File: counting_sort.c * Created Time: 2023-03-20 * Author: Reanon (793584285@qq.com), Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* 計数ソート */ // 簡易実装のため、オブジェクトのソートには使えない void countingSortNaive(int nums[], int size) { // 1. 配列の最大要素 m を求める int m = 0; for (int i = 0; i < size; i++) { if (nums[i] > m) { m = nums[i]; } } // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す int *counter = calloc(m + 1, sizeof(int)); for (int i = 0; i < size; i++) { counter[nums[i]]++; } // 3. counter を走査し、各要素を元の配列 nums に書き戻す int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } // 4. メモリを解放する free(counter); } /* 計数ソート */ // 完全な実装で、オブジェクトをソートでき、かつ安定ソートである void countingSort(int nums[], int size) { // 1. 配列の最大要素 m を求める int m = 0; for (int i = 0; i < size; i++) { if (nums[i] > m) { m = nums[i]; } } // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す int *counter = calloc(m, sizeof(int)); for (int i = 0; i < size; i++) { counter[nums[i]]++; } // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する // つまり counter[num]-1 は、num が res に最後に現れるインデックス for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. nums を逆順に走査し、各要素を結果配列 res に格納する // 結果を記録するための配列 res を初期化 int *res = malloc(sizeof(int) * size); for (int i = size - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // num を対応するインデックスに配置 counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る } // 結果配列 res で元の配列 nums を上書きする memcpy(nums, res, size * sizeof(int)); // 5. メモリを解放する free(res); free(counter); } /* Driver Code */ int main() { int nums[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; int size = sizeof(nums) / sizeof(int); countingSortNaive(nums, size); printf("計数ソート(オブジェクトはソート不可)完了後 nums = "); printArray(nums, size); int nums1[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; int size1 = sizeof(nums1) / sizeof(int); countingSort(nums1, size1); printf("計数ソート完了後 nums1 = "); printArray(nums1, size1); return 0; } ================================================ FILE: ja/codes/c/chapter_sorting/heap_sort.c ================================================ /** * File: heap_sort.c * Created Time: 2023-05-30 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* ヒープの長さは n。ノード i から下方向にヒープ化 */ void siftDown(int nums[], int n, int i) { while (1) { // ノード i, l, r のうち値が最大のノードを ma とする int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; if (l < n && nums[l] > nums[ma]) ma = l; if (r < n && nums[r] > nums[ma]) ma = r; // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma == i) { break; } // 2 つのノードを交換 int temp = nums[i]; nums[i] = nums[ma]; nums[ma] = temp; // ループで上から下へヒープ化 i = ma; } } /* ヒープソート */ void heapSort(int nums[], int n) { // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する for (int i = n / 2 - 1; i >= 0; --i) { siftDown(nums, n, i); } // ヒープから最大要素を取り出し、n-1 回繰り返す for (int i = n - 1; i > 0; --i) { // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) int tmp = nums[0]; nums[0] = nums[i]; nums[i] = tmp; // 根ノードを起点に、上から下へヒープ化 siftDown(nums, i, 0); } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; int n = sizeof(nums) / sizeof(nums[0]); heapSort(nums, n); printf("ヒープソート完了後 nums = "); printArray(nums, n); return 0; } ================================================ FILE: ja/codes/c/chapter_sorting/insertion_sort.c ================================================ /** * File: insertion_sort.c * Created Time: 2022-12-29 * Author: Listening (https://github.com/L-Super) */ #include "../utils/common.h" /* 挿入ソート */ void insertionSort(int nums[], int size) { // 外側ループ:整列済み区間は [0, i-1] for (int i = 1; i < size; i++) { int base = nums[i], j = i - 1; // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する while (j >= 0 && nums[j] > base) { // nums[j] を 1 つ右へ移動する nums[j + 1] = nums[j]; j--; } // base を正しい位置に配置する nums[j + 1] = base; } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; insertionSort(nums, 6); printf("挿入ソート完了後 nums = "); for (int i = 0; i < 6; i++) { printf("%d ", nums[i]); } printf("\n"); return 0; } ================================================ FILE: ja/codes/c/chapter_sorting/merge_sort.c ================================================ /** * File: merge_sort.c * Created Time: 2022-03-21 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* 左部分配列と右部分配列をマージ */ void merge(int *nums, int left, int mid, int right) { // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] // マージ結果を格納する一時配列 tmp を作成 int tmpSize = right - left + 1; int *tmp = (int *)malloc(tmpSize * sizeof(int)); // 左右の部分配列の開始インデックスを初期化する int i = left, j = mid + 1, k = 0; // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // 左右の部分配列の残り要素を一時配列にコピーする while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする for (k = 0; k < tmpSize; ++k) { nums[left + k] = tmp[k]; } // メモリを解放する free(tmp); } /* マージソート */ void mergeSort(int *nums, int left, int right) { // 終了条件 if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了 // 分割フェーズ int mid = left + (right - left) / 2; // 中点を計算 mergeSort(nums, left, mid); // 左部分配列を再帰処理 mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 // マージフェーズ merge(nums, left, mid, right); } /* Driver Code */ int main() { /* マージソート */ int nums[] = {7, 3, 2, 6, 0, 1, 5, 4}; int size = sizeof(nums) / sizeof(int); mergeSort(nums, 0, size - 1); printf("マージソート完了後 nums = "); printArray(nums, size); return 0; } ================================================ FILE: ja/codes/c/chapter_sorting/quick_sort.c ================================================ /** * File: quick_sort.c * Created Time: 2023-01-18 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* 要素の交換 */ void swap(int nums[], int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 番兵分割 */ int partition(int nums[], int left, int right) { // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // 右から左へ基準値未満の最初の要素を探す } while (i < j && nums[i] <= nums[left]) { i++; // 左から右へ基準値より大きい最初の要素を探す } // この 2 つの要素を交換 swap(nums, i, j); } // 基準値を 2 つの部分配列の境界へ交換する swap(nums, i, left); // 基準値のインデックスを返す return i; } /* クイックソート */ void quickSort(int nums[], int left, int right) { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) { return; } // 番兵分割 int pivot = partition(nums, left, right); // 左右の部分配列を再帰処理 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } // 以下は中央値最適化版のクイックソート /* 3つの候補要素の中央値を選ぶ */ int medianThree(int nums[], int left, int mid, int right) { int l = nums[left], m = nums[mid], r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // m は l と r の間 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l は m と r の間 return right; } /* 番兵による分割処理(3 点中央値) */ int partitionMedian(int nums[], int left, int right) { // 3つの候補要素の中央値を選ぶ int med = medianThree(nums, left, (left + right) / 2, right); // 中央値を配列の最左端に交換する swap(nums, left, med); // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す swap(nums, i, j); // この 2 つの要素を交換 } swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート(三点中央値法) */ void quickSortMedian(int nums[], int left, int right) { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return; // 番兵分割 int pivot = partitionMedian(nums, left, right); // 左右の部分配列を再帰処理 quickSortMedian(nums, left, pivot - 1); quickSortMedian(nums, pivot + 1, right); } // 以下は再帰の深さを最適化したクイックソート /* クイックソート(再帰深度最適化) */ void quickSortTailCall(int nums[], int left, int right) { // 部分配列の長さが 1 なら終了 while (left < right) { // 番兵による分割処理 int pivot = partition(nums, left, right); // 2 つの部分配列のうち短いほうにクイックソートを適用する if (pivot - left < right - pivot) { // 左部分配列を再帰的にソート quickSortTailCall(nums, left, pivot - 1); // 未ソート区間の残りは [pivot + 1, right] left = pivot + 1; } else { // 右部分配列を再帰的にソート quickSortTailCall(nums, pivot + 1, right); // 未ソート区間の残りは [left, pivot - 1] right = pivot - 1; } } } /* Driver Code */ int main() { /* クイックソート */ int nums[] = {2, 4, 1, 0, 3, 5}; int size = sizeof(nums) / sizeof(int); quickSort(nums, 0, size - 1); printf("クイックソート完了後 nums = "); printArray(nums, size); /* クイックソート(中央値の基準値で最適化) */ int nums1[] = {2, 4, 1, 0, 3, 5}; quickSortMedian(nums1, 0, size - 1); printf("クイックソート(中央値ピボット最適化)完了後 nums = "); printArray(nums1, size); /* クイックソート(再帰深度最適化) */ int nums2[] = {2, 4, 1, 0, 3, 5}; quickSortTailCall(nums2, 0, size - 1); printf("クイックソート(再帰深度最適化)完了後 nums = "); printArray(nums1, size); return 0; } ================================================ FILE: ja/codes/c/chapter_sorting/radix_sort.c ================================================ /** * File: radix_sort.c * Created Time: 2023-01-18 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ int digit(int num, int exp) { // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す return (num / exp) % 10; } /* 計数ソート(nums の k 桁目でソート) */ void countingSortDigit(int nums[], int size, int exp) { // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 int *counter = (int *)malloc((sizeof(int) * 10)); memset(counter, 0, sizeof(int) * 10); // 後続のメモリ解放に備えて 0 で初期化する // 0~9 の各数字の出現回数を集計する for (int i = 0; i < size; i++) { // nums[i] の第 k 位を取得し、d とする int d = digit(nums[i], exp); // 数字 d の出現回数を数える counter[d]++; } // 累積和を求め、「出現回数」を「配列インデックス」に変換する for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する int *res = (int *)malloc(sizeof(int) * size); for (int i = size - 1; i >= 0; i--) { int d = digit(nums[i], exp); int j = counter[d] - 1; // d の配列内インデックス j を取得する res[j] = nums[i]; // 現在の要素をインデックス j に格納する counter[d]--; // d の個数を 1 減らす } // 結果で元の配列 nums を上書きする for (int i = 0; i < size; i++) { nums[i] = res[i]; } // メモリを解放する free(res); free(counter); } /* 基数ソート */ void radixSort(int nums[], int size) { // 最大桁数の判定用に配列の最大要素を取得 int max = INT32_MIN; for (int i = 0; i < size; i++) { if (nums[i] > max) { max = nums[i]; } } // 下位桁から上位桁の順に走査する for (int exp = 1; max >= exp; exp *= 10) // 配列要素の k 桁目に対して計数ソートを行う // k = 1 -> exp = 1 // k = 2 -> exp = 10 // つまり exp = 10^(k-1) countingSortDigit(nums, size, exp); } /* Driver Code */ int main() { // 基数ソート int nums[] = {10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996}; int size = sizeof(nums) / sizeof(int); radixSort(nums, size); printf("基数ソート完了後 nums = "); printArray(nums, size); } ================================================ FILE: ja/codes/c/chapter_sorting/selection_sort.c ================================================ /** * File: selection_sort.c * Created Time: 2023-05-31 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 選択ソート */ void selectionSort(int nums[], int n) { // 外側ループ:未整列区間は [i, n-1] for (int i = 0; i < n - 1; i++) { // 内側のループ:未ソート区間の最小要素を見つける int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // 最小要素のインデックスを記録 } // その最小要素を未整列区間の先頭要素と交換する int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; int n = sizeof(nums) / sizeof(nums[0]); selectionSort(nums, n); printf("選択ソート完了後 nums = "); printArray(nums, n); return 0; } ================================================ FILE: ja/codes/c/chapter_stack_and_queue/CMakeLists.txt ================================================ add_executable(array_stack array_stack.c) add_executable(linkedlist_stack linkedlist_stack.c) add_executable(array_queue array_queue.c) add_executable(linkedlist_queue linkedlist_queue.c) add_executable(array_deque array_deque.c) add_executable(linkedlist_deque linkedlist_deque.c) ================================================ FILE: ja/codes/c/chapter_stack_and_queue/array_deque.c ================================================ /** * File: array_deque.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 循環配列ベースの両端キュー */ typedef struct { int *nums; // キュー要素を格納する配列 int front; // 先頭ポインタ。先頭要素を指す int queSize; // 末尾ポインタ。キューの末尾 + 1 を指す int queCapacity; // キューの容量 } ArrayDeque; /* コンストラクタ */ ArrayDeque *newArrayDeque(int capacity) { ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque)); // 配列を初期化 deque->queCapacity = capacity; deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity); deque->front = deque->queSize = 0; return deque; } /* デストラクタ */ void delArrayDeque(ArrayDeque *deque) { free(deque->nums); free(deque); } /* 両端キューの容量を取得 */ int capacity(ArrayDeque *deque) { return deque->queCapacity; } /* 両端キューの長さを取得 */ int size(ArrayDeque *deque) { return deque->queSize; } /* 両端キューが空かどうかを判定 */ bool empty(ArrayDeque *deque) { return deque->queSize == 0; } /* 循環配列のインデックスを計算 */ int dequeIndex(ArrayDeque *deque, int i) { // 剰余演算により配列の先頭と末尾をつなげる // i が配列の末尾を越えたら先頭に戻る // i が配列の先頭を越えたら末尾に戻る return ((i + capacity(deque)) % capacity(deque)); } /* キュー先頭にエンキュー */ void pushFirst(ArrayDeque *deque, int num) { if (deque->queSize == capacity(deque)) { printf("両端キューがいっぱいです\r\n"); return; } // 先頭ポインタを左に 1 つ移動する // 剰余演算により front が配列の先頭を越えたあと末尾に戻るようにする deque->front = dequeIndex(deque, deque->front - 1); // num をキューの先頭に追加 deque->nums[deque->front] = num; deque->queSize++; } /* キュー末尾にエンキュー */ void pushLast(ArrayDeque *deque, int num) { if (deque->queSize == capacity(deque)) { printf("両端キューがいっぱいです\r\n"); return; } // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す int rear = dequeIndex(deque, deque->front + deque->queSize); // num をキュー末尾に追加 deque->nums[rear] = num; deque->queSize++; } /* キュー先頭の要素にアクセス */ int peekFirst(ArrayDeque *deque) { // アクセス例外:双方向キューが空です assert(empty(deque) == 0); return deque->nums[deque->front]; } /* キュー末尾の要素にアクセス */ int peekLast(ArrayDeque *deque) { // アクセス例外:双方向キューが空です assert(empty(deque) == 0); int last = dequeIndex(deque, deque->front + deque->queSize - 1); return deque->nums[last]; } /* キュー先頭からデキュー */ int popFirst(ArrayDeque *deque) { int num = peekFirst(deque); // 先頭ポインタを 1 つ後ろへ進める deque->front = dequeIndex(deque, deque->front + 1); deque->queSize--; return num; } /* キュー末尾からデキュー */ int popLast(ArrayDeque *deque) { int num = peekLast(deque); deque->queSize--; return num; } /* 出力用の配列を返す */ int *toArray(ArrayDeque *deque, int *queSize) { *queSize = deque->queSize; int *res = (int *)calloc(deque->queSize, sizeof(int)); int j = deque->front; for (int i = 0; i < deque->queSize; i++) { res[i] = deque->nums[j % deque->queCapacity]; j++; } return res; } /* Driver Code */ int main() { /* キューを初期化 */ int capacity = 10; int queSize; ArrayDeque *deque = newArrayDeque(capacity); pushLast(deque, 3); pushLast(deque, 2); pushLast(deque, 5); printf("両端キュー deque = "); printArray(toArray(deque, &queSize), queSize); /* 要素にアクセス */ int peekFirstNum = peekFirst(deque); printf("先頭要素 peekFirst = %d\r\n", peekFirstNum); int peekLastNum = peekLast(deque); printf("末尾要素 peekLast = %d\r\n", peekLastNum); /* 要素をエンキュー */ pushLast(deque, 4); printf("要素 4 を末尾に追加後 deque = "); printArray(toArray(deque, &queSize), queSize); pushFirst(deque, 1); printf("要素 1 を先頭に追加後 deque = "); printArray(toArray(deque, &queSize), queSize); /* 要素をデキュー */ int popLastNum = popLast(deque); printf("末尾から取り出した要素 = %d ,末尾から取り出した後 deque= ", popLastNum); printArray(toArray(deque, &queSize), queSize); int popFirstNum = popFirst(deque); printf("先頭から取り出した要素 = %d ,先頭から取り出した後 deque= ", popFirstNum); printArray(toArray(deque, &queSize), queSize); /* キューの長さを取得 */ int dequeSize = size(deque); printf("両端キューの長さ size = %d\r\n", dequeSize); /* キューが空かどうかを判定 */ bool isEmpty = empty(deque); printf("キューが空かどうか = %s\r\n", isEmpty ? "true" : "false"); // メモリを解放する delArrayDeque(deque); return 0; } ================================================ FILE: ja/codes/c/chapter_stack_and_queue/array_queue.c ================================================ /** * File: array_queue.c * Created Time: 2023-01-28 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* 循環配列ベースのキュー */ typedef struct { int *nums; // キュー要素を格納する配列 int front; // 先頭ポインタ。先頭要素を指す int queSize; // 現在のキュー内の要素数 int queCapacity; // キューの容量 } ArrayQueue; /* コンストラクタ */ ArrayQueue *newArrayQueue(int capacity) { ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue)); // 配列を初期化 queue->queCapacity = capacity; queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity); queue->front = queue->queSize = 0; return queue; } /* デストラクタ */ void delArrayQueue(ArrayQueue *queue) { free(queue->nums); free(queue); } /* キューの容量を取得 */ int capacity(ArrayQueue *queue) { return queue->queCapacity; } /* キューの長さを取得 */ int size(ArrayQueue *queue) { return queue->queSize; } /* キューが空かどうかを判定 */ bool empty(ArrayQueue *queue) { return queue->queSize == 0; } /* キュー先頭の要素にアクセス */ int peek(ArrayQueue *queue) { assert(size(queue) != 0); return queue->nums[queue->front]; } /* エンキュー */ void push(ArrayQueue *queue, int num) { if (size(queue) == capacity(queue)) { printf("キューは満杯です\r\n"); return; } // 末尾ポインタを計算し、末尾インデックス + 1 を指す // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする int rear = (queue->front + queue->queSize) % queue->queCapacity; // num をキュー末尾に追加 queue->nums[rear] = num; queue->queSize++; } /* デキュー */ int pop(ArrayQueue *queue) { int num = peek(queue); // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す queue->front = (queue->front + 1) % queue->queCapacity; queue->queSize--; return num; } /* 出力用の配列を返す */ int *toArray(ArrayQueue *queue, int *queSize) { *queSize = queue->queSize; int *res = (int *)calloc(queue->queSize, sizeof(int)); int j = queue->front; for (int i = 0; i < queue->queSize; i++) { res[i] = queue->nums[j % queue->queCapacity]; j++; } return res; } /* Driver Code */ int main() { /* キューを初期化 */ int capacity = 10; int queSize; ArrayQueue *queue = newArrayQueue(capacity); /* 要素をエンキュー */ push(queue, 1); push(queue, 3); push(queue, 2); push(queue, 5); push(queue, 4); printf("キュー queue = "); printArray(toArray(queue, &queSize), queSize); /* キュー先頭の要素にアクセス */ int peekNum = peek(queue); printf("先頭要素 peek = %d\r\n", peekNum); /* 要素をデキュー */ peekNum = pop(queue); printf("デキューした要素 pop = %d ,デキュー後 queue = ", peekNum); printArray(toArray(queue, &queSize), queSize); /* キューの長さを取得 */ int queueSize = size(queue); printf("キューの長さ size = %d\r\n", queueSize); /* キューが空かどうかを判定 */ bool isEmpty = empty(queue); printf("キューが空かどうか = %s\r\n", isEmpty ? "true" : "false"); /* 循環配列をテストする */ for (int i = 0; i < 10; i++) { push(queue, i); pop(queue); printf("第 %d 回のエンキュー + デキュー後 queue = ", i); printArray(toArray(queue, &queSize), queSize); } // メモリを解放する delArrayQueue(queue); return 0; } ================================================ FILE: ja/codes/c/chapter_stack_and_queue/array_stack.c ================================================ /** * File: array_stack.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 5000 /* 配列ベースのスタック */ typedef struct { int *data; int size; } ArrayStack; /* コンストラクタ */ ArrayStack *newArrayStack() { ArrayStack *stack = malloc(sizeof(ArrayStack)); // 大きめの容量で初期化し、拡張を避ける stack->data = malloc(sizeof(int) * MAX_SIZE); stack->size = 0; return stack; } /* デストラクタ */ void delArrayStack(ArrayStack *stack) { free(stack->data); free(stack); } /* スタックの長さを取得 */ int size(ArrayStack *stack) { return stack->size; } /* スタックが空かどうかを判定 */ bool isEmpty(ArrayStack *stack) { return stack->size == 0; } /* プッシュ */ void push(ArrayStack *stack, int num) { if (stack->size == MAX_SIZE) { printf("スタックは満杯です\n"); return; } stack->data[stack->size] = num; stack->size++; } /* スタックトップの要素にアクセス */ int peek(ArrayStack *stack) { if (stack->size == 0) { printf("スタックは空です\n"); return INT_MAX; } return stack->data[stack->size - 1]; } /* ポップ */ int pop(ArrayStack *stack) { int val = peek(stack); stack->size--; return val; } /* Driver Code */ int main() { /* スタックを初期化 */ ArrayStack *stack = newArrayStack(); /* 要素をプッシュ */ push(stack, 1); push(stack, 3); push(stack, 2); push(stack, 5); push(stack, 4); printf("スタック stack = "); printArray(stack->data, stack->size); /* スタックトップの要素にアクセス */ int val = peek(stack); printf("先頭要素 top = %d\n", val); /* 要素をポップ */ val = pop(stack); printf("ポップした要素 pop = %d ,ポップ後 stack = ", val); printArray(stack->data, stack->size); /* スタックの長さを取得 */ int size = stack->size; printf("スタックの長さ size = %d\n", size); /* 空かどうかを判定 */ bool empty = isEmpty(stack); printf("スタックが空かどうか = %s\n", empty ? "true" : "false"); // メモリを解放する delArrayStack(stack); return 0; } ================================================ FILE: ja/codes/c/chapter_stack_and_queue/linkedlist_deque.c ================================================ /** * File: linkedlist_deque.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 双方向連結リストノード */ typedef struct DoublyListNode { int val; // ノード値 struct DoublyListNode *next; // 後続ノード struct DoublyListNode *prev; // 前駆ノード } DoublyListNode; /* コンストラクタ */ DoublyListNode *newDoublyListNode(int num) { DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode)); new->val = num; new->next = NULL; new->prev = NULL; return new; } /* デストラクタ */ void delDoublyListNode(DoublyListNode *node) { free(node); } /* 双方向連結リストベースの両端キュー */ typedef struct { DoublyListNode *front, *rear; // 先頭ノード front、末尾ノード rear int queSize; // 両端キューの長さ } LinkedListDeque; /* コンストラクタ */ LinkedListDeque *newLinkedListDeque() { LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque)); deque->front = NULL; deque->rear = NULL; deque->queSize = 0; return deque; } /* デストラクタ */ void delLinkedListdeque(LinkedListDeque *deque) { // すべてのノードを解放 for (int i = 0; i < deque->queSize && deque->front != NULL; i++) { DoublyListNode *tmp = deque->front; deque->front = deque->front->next; free(tmp); } // deque 構造体を解放する free(deque); } /* キューの長さを取得 */ int size(LinkedListDeque *deque) { return deque->queSize; } /* キューが空かどうかを判定 */ bool empty(LinkedListDeque *deque) { return (size(deque) == 0); } /* エンキュー */ void push(LinkedListDeque *deque, int num, bool isFront) { DoublyListNode *node = newDoublyListNode(num); // 連結リストが空なら、`front` と `rear` の両方を `node` に向ける if (empty(deque)) { deque->front = deque->rear = node; } // 先頭へのエンキュー操作 else if (isFront) { // node を連結リストの先頭に追加 deque->front->prev = node; node->next = deque->front; deque->front = node; // 先頭ノードを更新する } // 末尾へのエンキュー操作 else { // node を連結リストの末尾に追加 deque->rear->next = node; node->prev = deque->rear; deque->rear = node; } deque->queSize++; // キューの長さを更新 } /* キュー先頭にエンキュー */ void pushFirst(LinkedListDeque *deque, int num) { push(deque, num, true); } /* キュー末尾にエンキュー */ void pushLast(LinkedListDeque *deque, int num) { push(deque, num, false); } /* キュー先頭の要素にアクセス */ int peekFirst(LinkedListDeque *deque) { assert(size(deque) && deque->front); return deque->front->val; } /* キュー末尾の要素にアクセス */ int peekLast(LinkedListDeque *deque) { assert(size(deque) && deque->rear); return deque->rear->val; } /* デキュー */ int pop(LinkedListDeque *deque, bool isFront) { if (empty(deque)) return -1; int val; // キュー先頭からの取り出し if (isFront) { val = peekFirst(deque); // 先頭ノードの値を一時保存 DoublyListNode *fNext = deque->front->next; if (fNext) { fNext->prev = NULL; deque->front->next = NULL; } delDoublyListNode(deque->front); deque->front = fNext; // 先頭ノードを更新する } // キュー末尾からの取り出し else { val = peekLast(deque); // 末尾ノードの値を一時保存 DoublyListNode *rPrev = deque->rear->prev; if (rPrev) { rPrev->next = NULL; deque->rear->prev = NULL; } delDoublyListNode(deque->rear); deque->rear = rPrev; // 末尾ノードを更新する } deque->queSize--; // キューの長さを更新 return val; } /* キュー先頭からデキュー */ int popFirst(LinkedListDeque *deque) { return pop(deque, true); } /* キュー末尾からデキュー */ int popLast(LinkedListDeque *deque) { return pop(deque, false); } /* キューを出力する */ void printLinkedListDeque(LinkedListDeque *deque) { int *arr = malloc(sizeof(int) * deque->queSize); // 連結リスト内のデータを配列にコピー int i; DoublyListNode *node; for (i = 0, node = deque->front; i < deque->queSize; i++) { arr[i] = node->val; node = node->next; } printArray(arr, deque->queSize); free(arr); } /* Driver Code */ int main() { /* 両端キューを初期化 */ LinkedListDeque *deque = newLinkedListDeque(); pushLast(deque, 3); pushLast(deque, 2); pushLast(deque, 5); printf("両端キュー deque = "); printLinkedListDeque(deque); /* 要素にアクセス */ int peekFirstNum = peekFirst(deque); printf("先頭要素 peekFirst = %d\r\n", peekFirstNum); int peekLastNum = peekLast(deque); printf("先頭要素 peekLast = %d\r\n", peekLastNum); /* 要素をエンキュー */ pushLast(deque, 4); printf("要素 4 を末尾に追加した後 deque ="); printLinkedListDeque(deque); pushFirst(deque, 1); printf("要素 1 を先頭に追加した後 deque ="); printLinkedListDeque(deque); /* 要素をデキュー */ int popLastNum = popLast(deque); printf("末尾から取り出した要素 popLast = %d ,末尾から取り出した後 deque = ", popLastNum); printLinkedListDeque(deque); int popFirstNum = popFirst(deque); printf("先頭から取り出した要素 popFirst = %d ,先頭から取り出した後 deque = ", popFirstNum); printLinkedListDeque(deque); /* キューの長さを取得 */ int dequeSize = size(deque); printf("両端キューの長さ size = %d\r\n", dequeSize); /* キューが空かどうかを判定 */ bool isEmpty = empty(deque); printf("両端キューが空かどうか = %s\r\n", isEmpty ? "true" : "false"); // メモリを解放する delLinkedListdeque(deque); return 0; } ================================================ FILE: ja/codes/c/chapter_stack_and_queue/linkedlist_queue.c ================================================ /** * File: linkedlist_queue.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 連結リストベースのキュー */ typedef struct { ListNode *front, *rear; int queSize; } LinkedListQueue; /* コンストラクタ */ LinkedListQueue *newLinkedListQueue() { LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue)); queue->front = NULL; queue->rear = NULL; queue->queSize = 0; return queue; } /* デストラクタ */ void delLinkedListQueue(LinkedListQueue *queue) { // すべてのノードを解放 while (queue->front != NULL) { ListNode *tmp = queue->front; queue->front = queue->front->next; free(tmp); } // queue 構造体を解放する free(queue); } /* キューの長さを取得 */ int size(LinkedListQueue *queue) { return queue->queSize; } /* キューが空かどうかを判定 */ bool empty(LinkedListQueue *queue) { return (size(queue) == 0); } /* エンキュー */ void push(LinkedListQueue *queue, int num) { // 末尾ノードに node を追加 ListNode *node = newListNode(num); // キューが空なら、先頭・末尾ノードをともにそのノードに設定 if (queue->front == NULL) { queue->front = node; queue->rear = node; } // キューが空でなければ、そのノードを末尾ノードの後ろに追加 else { queue->rear->next = node; queue->rear = node; } queue->queSize++; } /* キュー先頭の要素にアクセス */ int peek(LinkedListQueue *queue) { assert(size(queue) && queue->front); return queue->front->val; } /* デキュー */ int pop(LinkedListQueue *queue) { int num = peek(queue); ListNode *tmp = queue->front; queue->front = queue->front->next; free(tmp); queue->queSize--; return num; } /* キューを出力する */ void printLinkedListQueue(LinkedListQueue *queue) { int *arr = malloc(sizeof(int) * queue->queSize); // 連結リスト内のデータを配列にコピー int i; ListNode *node; for (i = 0, node = queue->front; i < queue->queSize; i++) { arr[i] = node->val; node = node->next; } printArray(arr, queue->queSize); free(arr); } /* Driver Code */ int main() { /* キューを初期化 */ LinkedListQueue *queue = newLinkedListQueue(); /* 要素をエンキュー */ push(queue, 1); push(queue, 3); push(queue, 2); push(queue, 5); push(queue, 4); printf("キュー queue = "); printLinkedListQueue(queue); /* キュー先頭の要素にアクセス */ int peekNum = peek(queue); printf("先頭要素 peek = %d\r\n", peekNum); /* 要素をデキュー */ peekNum = pop(queue); printf("デキューした要素 pop = %d ,デキュー後 queue = ", peekNum); printLinkedListQueue(queue); /* キューの長さを取得 */ int queueSize = size(queue); printf("キューの長さ size = %d\r\n", queueSize); /* キューが空かどうかを判定 */ bool isEmpty = empty(queue); printf("キューが空かどうか = %s\r\n", isEmpty ? "true" : "false"); // メモリを解放する delLinkedListQueue(queue); return 0; } ================================================ FILE: ja/codes/c/chapter_stack_and_queue/linkedlist_stack.c ================================================ /** * File: linkedlist_stack.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* 連結リストベースのスタック */ typedef struct { ListNode *top; // 先頭ノードをスタックトップとする int size; // スタックの長さ } LinkedListStack; /* コンストラクタ */ LinkedListStack *newLinkedListStack() { LinkedListStack *s = malloc(sizeof(LinkedListStack)); s->top = NULL; s->size = 0; return s; } /* デストラクタ */ void delLinkedListStack(LinkedListStack *s) { while (s->top) { ListNode *n = s->top->next; free(s->top); s->top = n; } free(s); } /* スタックの長さを取得 */ int size(LinkedListStack *s) { return s->size; } /* スタックが空かどうかを判定 */ bool isEmpty(LinkedListStack *s) { return size(s) == 0; } /* プッシュ */ void push(LinkedListStack *s, int num) { ListNode *node = (ListNode *)malloc(sizeof(ListNode)); node->next = s->top; // 新しく追加したノードのポインタフィールドを更新 node->val = num; // 新しく追加したノードのデータフィールドを更新 s->top = node; // スタックトップを更新 s->size++; // スタックサイズを更新 } /* スタックトップの要素にアクセス */ int peek(LinkedListStack *s) { if (s->size == 0) { printf("スタックは空です\n"); return INT_MAX; } return s->top->val; } /* ポップ */ int pop(LinkedListStack *s) { int val = peek(s); ListNode *tmp = s->top; s->top = s->top->next; // メモリを解放する free(tmp); s->size--; return val; } /* Driver Code */ int main() { /* スタックを初期化 */ LinkedListStack *stack = newLinkedListStack(); /* 要素をプッシュ */ push(stack, 1); push(stack, 3); push(stack, 2); push(stack, 5); push(stack, 4); printf("スタック stack = "); printLinkedList(stack->top); /* スタックトップの要素にアクセス */ int val = peek(stack); printf("スタックトップ要素 top = %d\r\n", val); /* 要素をポップ */ val = pop(stack); printf("ポップした要素 pop = %d, ポップ後 stack = ", val); printLinkedList(stack->top); /* スタックの長さを取得 */ printf("スタックの長さ size = %d\n", size(stack)); /* 空かどうかを判定 */ bool empty = isEmpty(stack); printf("スタックが空かどうか = %s\n", empty ? "true" : "false"); // メモリを解放する delLinkedListStack(stack); return 0; } ================================================ FILE: ja/codes/c/chapter_tree/CMakeLists.txt ================================================ add_executable(avl_tree avl_tree.c) add_executable(binary_tree binary_tree.c) add_executable(binary_tree_bfs binary_tree_bfs.c) add_executable(binary_tree_dfs binary_tree_dfs.c) add_executable(binary_search_tree binary_search_tree.c) add_executable(array_binary_tree array_binary_tree.c) ================================================ FILE: ja/codes/c/chapter_tree/array_binary_tree.c ================================================ /** * File: array_binary_tree.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 配列表現による二分木の構造体 */ typedef struct { int *tree; int size; } ArrayBinaryTree; /* コンストラクタ */ ArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) { ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree)); abt->tree = malloc(sizeof(int) * arrSize); memcpy(abt->tree, arr, sizeof(int) * arrSize); abt->size = arrSize; return abt; } /* デストラクタ */ void delArrayBinaryTree(ArrayBinaryTree *abt) { free(abt->tree); free(abt); } /* リスト容量 */ int size(ArrayBinaryTree *abt) { return abt->size; } /* インデックス i のノードの値を取得 */ int val(ArrayBinaryTree *abt, int i) { // インデックスが範囲外なら、空きを表す INT_MAX を返す if (i < 0 || i >= size(abt)) return INT_MAX; return abt->tree[i]; } /* インデックス i のノードの左子ノードのインデックスを取得 */ int left(int i) { return 2 * i + 1; } /* インデックス i のノードの右子ノードのインデックスを取得 */ int right(int i) { return 2 * i + 2; } /* インデックス i のノードの親ノードのインデックスを取得 */ int parent(int i) { return (i - 1) / 2; } /* レベル順走査 */ int *levelOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; // 配列を直接走査する for (int i = 0; i < size(abt); i++) { if (val(abt, i) != INT_MAX) res[index++] = val(abt, i); } *returnSize = index; return res; } /* 深さ優先探索 */ void dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) { // 空きスロットなら返す if (val(abt, i) == INT_MAX) return; // 先行順走査 if (strcmp(order, "pre") == 0) res[(*index)++] = val(abt, i); dfs(abt, left(i), order, res, index); // 中順走査 if (strcmp(order, "in") == 0) res[(*index)++] = val(abt, i); dfs(abt, right(i), order, res, index); // 後順走査 if (strcmp(order, "post") == 0) res[(*index)++] = val(abt, i); } /* 先行順走査 */ int *preOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "pre", res, &index); *returnSize = index; return res; } /* 中順走査 */ int *inOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "in", res, &index); *returnSize = index; return res; } /* 後順走査 */ int *postOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "post", res, &index); *returnSize = index; return res; } /* Driver Code */ int main() { // 二分木を初期化する // 空き位置 NULL は INT_MAX で表す int arr[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; int arrSize = sizeof(arr) / sizeof(arr[0]); TreeNode *root = arrayToTree(arr, arrSize); printf("\n二分木を初期化\n"); printf("二分木の配列表現:\n"); printArray(arr, arrSize); printf("二分木の連結リスト表現:\n"); printTree(root); ArrayBinaryTree *abt = newArrayBinaryTree(arr, arrSize); // ノードにアクセス int i = 1; int l = left(i), r = right(i), p = parent(i); printf("\n現在のノードのインデックスは %d、値は %d\n", i, val(abt, i)); printf("左の子ノードのインデックスは %d、値は %d\n", l, l < arrSize ? val(abt, l) : INT_MAX); printf("右の子ノードのインデックスは %d、値は %d\n", r, r < arrSize ? val(abt, r) : INT_MAX); printf("親ノードのインデックスは %d、値は %d\n", p, p < arrSize ? val(abt, p) : INT_MAX); // 木を走査 int returnSize; int *res; res = levelOrder(abt, &returnSize); printf("\nレベル順走査: "); printArray(res, returnSize); free(res); res = preOrder(abt, &returnSize); printf("前順走査: "); printArray(res, returnSize); free(res); res = inOrder(abt, &returnSize); printf("中順走査: "); printArray(res, returnSize); free(res); res = postOrder(abt, &returnSize); printf("後順走査: "); printArray(res, returnSize); free(res); // メモリを解放する delArrayBinaryTree(abt); return 0; } ================================================ FILE: ja/codes/c/chapter_tree/avl_tree.c ================================================ /** * File: avl_tree.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* AVL 木構造体 */ typedef struct { TreeNode *root; } AVLTree; /* コンストラクタ */ AVLTree *newAVLTree() { AVLTree *tree = (AVLTree *)malloc(sizeof(AVLTree)); tree->root = NULL; return tree; } /* デストラクタ */ void delAVLTree(AVLTree *tree) { freeMemoryTree(tree->root); free(tree); } /* ノードの高さを取得 */ int height(TreeNode *node) { // 空ノードの高さは -1、葉ノードの高さは 0 if (node != NULL) { return node->height; } return -1; } /* ノードの高さを更新する */ void updateHeight(TreeNode *node) { int lh = height(node->left); int rh = height(node->right); // ノードの高さは最も高い部分木の高さ + 1 に等しい if (lh > rh) { node->height = lh + 1; } else { node->height = rh + 1; } } /* 平衡係数を取得 */ int balanceFactor(TreeNode *node) { // 空ノードの平衡係数は 0 if (node == NULL) { return 0; } // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ return height(node->left) - height(node->right); } /* 右回転 */ TreeNode *rightRotate(TreeNode *node) { TreeNode *child, *grandChild; child = node->left; grandChild = child->right; // child を支点として node を右回転させる child->right = node; node->left = grandChild; // ノードの高さを更新する updateHeight(node); updateHeight(child); // 回転後の部分木の根ノードを返す return child; } /* 左回転 */ TreeNode *leftRotate(TreeNode *node) { TreeNode *child, *grandChild; child = node->right; grandChild = child->left; // child を支点として node を左回転させる child->left = node; node->right = grandChild; // ノードの高さを更新する updateHeight(node); updateHeight(child); // 回転後の部分木の根ノードを返す return child; } /* 回転操作を行い、この部分木の平衡を回復する */ TreeNode *rotate(TreeNode *node) { // ノード node の平衡係数を取得 int bf = balanceFactor(node); // 左に偏った木 if (bf > 1) { if (balanceFactor(node->left) >= 0) { // 右回転 return rightRotate(node); } else { // 左回転してから右回転 node->left = leftRotate(node->left); return rightRotate(node); } } // 右に偏った木 if (bf < -1) { if (balanceFactor(node->right) <= 0) { // 左回転 return leftRotate(node); } else { // 右回転してから左回転 node->right = rightRotate(node->right); return leftRotate(node); } } // 平衡木なので回転不要、そのまま返す return node; } /* ノードを再帰的に挿入する(補助関数) */ TreeNode *insertHelper(TreeNode *node, int val) { if (node == NULL) { return newTreeNode(val); } /* 1. 挿入位置を探索してノードを挿入 */ if (val < node->val) { node->left = insertHelper(node->left, val); } else if (val > node->val) { node->right = insertHelper(node->right, val); } else { // 重複ノードは挿入せず、そのまま返す return node; } // ノードの高さを更新する updateHeight(node); /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node); // 部分木の根ノードを返す return node; } /* ノードを挿入 */ void insert(AVLTree *tree, int val) { tree->root = insertHelper(tree->root, val); } /* ノードを再帰的に削除する(補助関数) */ TreeNode *removeHelper(TreeNode *node, int val) { TreeNode *child, *grandChild; if (node == NULL) { return NULL; } /* 1. ノードを探索して削除 */ if (val < node->val) { node->left = removeHelper(node->left, val); } else if (val > node->val) { node->right = removeHelper(node->right, val); } else { if (node->left == NULL || node->right == NULL) { child = node->left; if (node->right != NULL) { child = node->right; } // 子ノード数 = 0 の場合、node をそのまま削除して返す if (child == NULL) { return NULL; } else { // 子ノード数 = 1 の場合、node をそのまま削除する node = child; } } else { // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える TreeNode *temp = node->right; while (temp->left != NULL) { temp = temp->left; } int tempVal = temp->val; node->right = removeHelper(node->right, temp->val); node->val = tempVal; } } // ノードの高さを更新する updateHeight(node); /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node); // 部分木の根ノードを返す return node; } /* ノードを削除 */ // stdio.h を導入しているため、ここでは remove 識別子を使えない void removeItem(AVLTree *tree, int val) { TreeNode *root = removeHelper(tree->root, val); } /* ノードを探索 */ TreeNode *search(AVLTree *tree, int val) { TreeNode *cur = tree->root; // ループで探索し、葉ノードを越えたら抜ける while (cur != NULL) { if (cur->val < val) { // 目標ノードは cur の右部分木にある cur = cur->right; } else if (cur->val > val) { // 目標ノードは cur の左部分木にある cur = cur->left; } else { // 目標ノードが見つかったらループを抜ける break; } } // 目標ノードが見つかったらループを抜ける return cur; } void testInsert(AVLTree *tree, int val) { insert(tree, val); printf("\nノード %d を挿入した後、AVL 木は \n", val); printTree(tree->root); } void testRemove(AVLTree *tree, int val) { removeItem(tree, val); printf("\nノード %d を削除した後、AVL 木は \n", val); printTree(tree->root); } /* Driver Code */ int main() { /* 空の AVL 木を初期化する */ AVLTree *tree = (AVLTree *)newAVLTree(); /* ノードを挿入 */ // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい testInsert(tree, 1); testInsert(tree, 2); testInsert(tree, 3); testInsert(tree, 4); testInsert(tree, 5); testInsert(tree, 8); testInsert(tree, 7); testInsert(tree, 9); testInsert(tree, 10); testInsert(tree, 6); /* 重複ノードを挿入する */ testInsert(tree, 7); /* ノードを削除 */ // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい testRemove(tree, 8); // 次数 0 のノードを削除する testRemove(tree, 5); // 次数 1 のノードを削除する testRemove(tree, 4); // 次数 2 のノードを削除する /* ノードを検索 */ TreeNode *node = search(tree, 7); printf("\n見つかったノードオブジェクトのノード値 = %d \n", node->val); // メモリを解放する delAVLTree(tree); return 0; } ================================================ FILE: ja/codes/c/chapter_tree/binary_search_tree.c ================================================ /** * File: binary_search_tree.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* 二分探索木構造体 */ typedef struct { TreeNode *root; } BinarySearchTree; /* コンストラクタ */ BinarySearchTree *newBinarySearchTree() { // 空の木を初期化する BinarySearchTree *bst = (BinarySearchTree *)malloc(sizeof(BinarySearchTree)); bst->root = NULL; return bst; } /* デストラクタ */ void delBinarySearchTree(BinarySearchTree *bst) { freeMemoryTree(bst->root); free(bst); } /* 二分木の根ノードを取得 */ TreeNode *getRoot(BinarySearchTree *bst) { return bst->root; } /* ノードを探索 */ TreeNode *search(BinarySearchTree *bst, int num) { TreeNode *cur = bst->root; // ループで探索し、葉ノードを越えたら抜ける while (cur != NULL) { if (cur->val < num) { // 目標ノードは cur の右部分木にある cur = cur->right; } else if (cur->val > num) { // 目標ノードは cur の左部分木にある cur = cur->left; } else { // 目標ノードが見つかったらループを抜ける break; } } // 目標ノードを返す return cur; } /* ノードを挿入 */ void insert(BinarySearchTree *bst, int num) { // 木が空なら、根ノードを初期化する if (bst->root == NULL) { bst->root = newTreeNode(num); return; } TreeNode *cur = bst->root, *pre = NULL; // ループで探索し、葉ノードを越えたら抜ける while (cur != NULL) { // 重複ノードが見つかったら、直ちに返す if (cur->val == num) { return; } pre = cur; if (cur->val < num) { // 挿入位置は cur の右部分木にある cur = cur->right; } else { // 挿入位置は cur の左部分木にある cur = cur->left; } } // ノードを挿入 TreeNode *node = newTreeNode(num); if (pre->val < num) { pre->right = node; } else { pre->left = node; } } /* ノードを削除 */ // stdio.h を導入しているため、ここでは remove 識別子を使えない void removeItem(BinarySearchTree *bst, int num) { // 木が空なら、そのまま早期リターンする if (bst->root == NULL) return; TreeNode *cur = bst->root, *pre = NULL; // ループで探索し、葉ノードを越えたら抜ける while (cur != NULL) { // 削除対象のノードが見つかったら、ループを抜ける if (cur->val == num) break; pre = cur; if (cur->val < num) { // 削除対象ノードは root の右部分木にある cur = cur->right; } else { // 削除対象ノードは root の左部分木にある cur = cur->left; } } // 削除対象ノードがなければそのまま返す if (cur == NULL) return; // 削除対象ノードに子ノードがあるかを判定する if (cur->left == NULL || cur->right == NULL) { /* 子ノード数 = 0 or 1 */ // 子ノード数 = 0 / 1 のとき、child = nullptr / その子ノード TreeNode *child = cur->left != NULL ? cur->left : cur->right; // ノード cur を削除する if (pre->left == cur) { pre->left = child; } else { pre->right = child; } // メモリを解放する free(cur); } else { /* 子ノード数 = 2 */ // 中順走査における cur の次ノードを取得 TreeNode *tmp = cur->right; while (tmp->left != NULL) { tmp = tmp->left; } int tmpVal = tmp->val; // ノード tmp を再帰的に削除 removeItem(bst, tmp->val); // tmp で cur を上書きする cur->val = tmpVal; } } /* Driver Code */ int main() { /* 二分探索木を初期化 */ int nums[] = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; BinarySearchTree *bst = newBinarySearchTree(); for (int i = 0; i < sizeof(nums) / sizeof(int); i++) { insert(bst, nums[i]); } printf("初期化した二分木は\n"); printTree(getRoot(bst)); /* ノードを探索 */ TreeNode *node = search(bst, 7); printf("見つかったノードオブジェクトのノード値 = %d\n", node->val); /* ノードを挿入 */ insert(bst, 16); printf("ノード 16 を挿入した後、二分木は\n"); printTree(getRoot(bst)); /* ノードを削除 */ removeItem(bst, 1); printf("ノード 1 を削除した後、二分木は\n"); printTree(getRoot(bst)); removeItem(bst, 2); printf("ノード 2 を削除した後、二分木は\n"); printTree(getRoot(bst)); removeItem(bst, 4); printf("ノード 4 を削除した後、二分木は\n"); printTree(getRoot(bst)); // メモリを解放する delBinarySearchTree(bst); return 0; } ================================================ FILE: ja/codes/c/chapter_tree/binary_tree.c ================================================ /** * File: binary_tree.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Driver Code */ int main() { /* 二分木を初期化 */ // ノードを初期化 TreeNode *n1 = newTreeNode(1); TreeNode *n2 = newTreeNode(2); TreeNode *n3 = newTreeNode(3); TreeNode *n4 = newTreeNode(4); TreeNode *n5 = newTreeNode(5); // ノード間の参照(ポインタ)を構築する n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; printf("二分木を初期化\n"); printTree(n1); /* ノードの挿入と削除 */ TreeNode *P = newTreeNode(0); // n1 -> n2 の間にノード P を挿入 n1->left = P; P->left = n2; printf("ノード P を挿入した後\n"); printTree(n1); // ノード P を削除 n1->left = n2; // メモリを解放する free(P); printf("ノード P を削除した後\n"); printTree(n1); freeMemoryTree(n1); return 0; } ================================================ FILE: ja/codes/c/chapter_tree/binary_tree_bfs.c ================================================ /** * File: binary_tree_bfs.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 100 /* レベル順走査 */ int *levelOrder(TreeNode *root, int *size) { /* 補助キュー */ int front, rear; int index, *arr; TreeNode *node; TreeNode **queue; /* 補助キュー */ queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE); // キューへのポインタ front = 0, rear = 0; // 根ノードを追加する queue[rear++] = root; // 走査順序を保存するためのリストを初期化する /* 補助配列 */ arr = (int *)malloc(sizeof(int) * MAX_SIZE); // 配列ポインタ index = 0; while (front < rear) { // デキュー node = queue[front++]; // ノードの値を保存する arr[index++] = node->val; if (node->left != NULL) { // 左子ノードをキューに追加 queue[rear++] = node->left; } if (node->right != NULL) { // 右子ノードをキューに追加 queue[rear++] = node->right; } } // 配列長の値を更新 *size = index; arr = realloc(arr, sizeof(int) * (*size)); // 補助配列の領域を解放する free(queue); return arr; } /* Driver Code */ int main() { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する int nums[] = {1, 2, 3, 4, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); printf("二分木を初期化\n"); printTree(root); /* レベル順走査 */ // 配列の長さを渡す必要がある int *arr = levelOrder(root, &size); printf("レベル順走査のノード出力シーケンス = "); printArray(arr, size); // メモリを解放する freeMemoryTree(root); free(arr); return 0; } ================================================ FILE: ja/codes/c/chapter_tree/binary_tree_dfs.c ================================================ /** * File: binary_tree_dfs.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 100 // 走査順序を格納するための補助配列 int arr[MAX_SIZE]; /* 先行順走査 */ void preOrder(TreeNode *root, int *size) { if (root == NULL) return; // 訪問順序:根ノード -> 左部分木 -> 右部分木 arr[(*size)++] = root->val; preOrder(root->left, size); preOrder(root->right, size); } /* 中順走査 */ void inOrder(TreeNode *root, int *size) { if (root == NULL) return; // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 inOrder(root->left, size); arr[(*size)++] = root->val; inOrder(root->right, size); } /* 後順走査 */ void postOrder(TreeNode *root, int *size) { if (root == NULL) return; // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード postOrder(root->left, size); postOrder(root->right, size); arr[(*size)++] = root->val; } /* Driver Code */ int main() { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する int nums[] = {1, 2, 3, 4, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); printf("二分木を初期化\n"); printTree(root); /* 先行順走査 */ // 補助配列を初期化する size = 0; preOrder(root, &size); printf("前順走査のノード出力シーケンス = "); printArray(arr, size); /* 中順走査 */ size = 0; inOrder(root, &size); printf("中順走査のノード出力シーケンス = "); printArray(arr, size); /* 後順走査 */ size = 0; postOrder(root, &size); printf("後順走査のノード出力シーケンス = "); printArray(arr, size); freeMemoryTree(root); return 0; } ================================================ FILE: ja/codes/c/utils/CMakeLists.txt ================================================ add_executable(utils common_test.c common.h print_util.h list_node.h tree_node.h uthash.h) ================================================ FILE: ja/codes/c/utils/common.h ================================================ /** * File: common.h * Created Time: 2022-12-20 * Author: MolDuM (moldum@163.com)、Reanon (793584285@qq.com) */ #ifndef COMMON_H #define COMMON_H #include #include #include #include #include #include #include #include "list_node.h" #include "print_util.h" #include "tree_node.h" #include "vertex.h" // hash table lib #include "uthash.h" #include "vector.h" #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif #endif // COMMON_H ================================================ FILE: ja/codes/c/utils/common_test.c ================================================ /** * File: include_test.c * Created Time: 2023-01-10 * Author: Reanon (793584285@qq.com) */ #include "common.h" void testListNode() { int nums[] = {2, 3, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); ListNode *head = arrToLinkedList(nums, size); printLinkedList(head); } void testTreeNode() { int nums[] = {1, 2, 3, INT_MAX, 5, 6, INT_MAX}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); // print tree printTree(root); // tree to arr int *arr = treeToArray(root, &size); printArray(arr, size); } int main(int argc, char *argv[]) { printf("==testListNode==\n"); testListNode(); printf("==testTreeNode==\n"); testTreeNode(); return 0; } ================================================ FILE: ja/codes/c/utils/list_node.h ================================================ /** * File: list_node.h * Created Time: 2023-01-09 * Author: Reanon (793584285@qq.com) */ #ifndef LIST_NODE_H #define LIST_NODE_H #ifdef __cplusplus extern "C" { #endif /* 連結リストノード構造体 */ typedef struct ListNode { int val; // ノード値 struct ListNode *next; // 次のノードへの参照 } ListNode; /* コンストラクタ。新しいノードを初期化する */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *)malloc(sizeof(ListNode)); node->val = val; node->next = NULL; return node; } /* 配列をデシリアライズして連結リストに変換する */ ListNode *arrToLinkedList(const int *arr, size_t size) { if (size <= 0) { return NULL; } ListNode *dummy = newListNode(0); ListNode *node = dummy; for (int i = 0; i < size; i++) { node->next = newListNode(arr[i]); node = node->next; } return dummy->next; } /* 連結リストに割り当てたメモリを解放する */ void freeMemoryLinkedList(ListNode *cur) { // メモリを解放する ListNode *pre; while (cur != NULL) { pre = cur; cur = cur->next; free(pre); } } #ifdef __cplusplus } #endif #endif // LIST_NODE_H ================================================ FILE: ja/codes/c/utils/print_util.h ================================================ /** * File: print_util.h * Created Time: 2022-12-21 * Author: MolDum (moldum@163.com), Reanon (793584285@qq.com) */ #ifndef PRINT_UTIL_H #define PRINT_UTIL_H #include #include #include #include "list_node.h" #include "tree_node.h" #ifdef __cplusplus extern "C" { #endif /* 配列を出力する */ void printArray(int arr[], int size) { if (arr == NULL || size == 0) { printf("[]"); return; } printf("["); for (int i = 0; i < size - 1; i++) { printf("%d, ", arr[i]); } printf("%d]\n", arr[size - 1]); } /* 配列を出力する */ void printArrayFloat(float arr[], int size) { if (arr == NULL || size == 0) { printf("[]"); return; } printf("["); for (int i = 0; i < size - 1; i++) { printf("%.2f, ", arr[i]); } printf("%.2f]\n", arr[size - 1]); } /* 連結リストを出力 */ void printLinkedList(ListNode *node) { if (node == NULL) { return; } while (node->next != NULL) { printf("%d -> ", node->val); node = node->next; } printf("%d\n", node->val); } typedef struct Trunk { struct Trunk *prev; char *str; } Trunk; Trunk *newTrunk(Trunk *prev, char *str) { Trunk *trunk = (Trunk *)malloc(sizeof(Trunk)); trunk->prev = prev; trunk->str = (char *)malloc(sizeof(char) * 10); strcpy(trunk->str, str); return trunk; } void showTrunks(Trunk *trunk) { if (trunk == NULL) { return; } showTrunks(trunk->prev); printf("%s", trunk->str); } /** * 二分木を出力 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTreeHelper(TreeNode *node, Trunk *prev, bool isRight) { if (node == NULL) { return; } char *prev_str = " "; Trunk *trunk = newTrunk(prev, prev_str); printTreeHelper(node->right, trunk, true); if (prev == NULL) { trunk->str = "———"; } else if (isRight) { trunk->str = "/———"; prev_str = " |"; } else { trunk->str = "\\———"; prev->str = prev_str; } showTrunks(trunk); printf("%d\n", node->val); if (prev != NULL) { prev->str = prev_str; } trunk->str = " |"; printTreeHelper(node->left, trunk, false); } /* 二分木を出力 */ void printTree(TreeNode *root) { printTreeHelper(root, NULL, false); } /* ヒープを出力 */ void printHeap(int arr[], int size) { TreeNode *root; printf("ヒープの配列表現:"); printArray(arr, size); printf("ヒープの木構造表現:\n"); root = arrayToTree(arr, size); printTree(root); } #ifdef __cplusplus } #endif #endif // PRINT_UTIL_H ================================================ FILE: ja/codes/c/utils/tree_node.h ================================================ /** * File: tree_node.h * Created Time: 2023-01-09 * Author: Reanon (793584285@qq.com) */ #ifndef TREE_NODE_H #define TREE_NODE_H #ifdef __cplusplus extern "C" { #endif #include #define MAX_NODE_SIZE 5000 /* 二分木ノード構造体 */ typedef struct TreeNode { int val; // ノード値 int height; // ノードの高さ struct TreeNode *left; // 左の子ノードへのポインタ struct TreeNode *right; // 右の子ノードへのポインタ } TreeNode; /* コンストラクタ */ TreeNode *newTreeNode(int val) { TreeNode *node; node = (TreeNode *)malloc(sizeof(TreeNode)); node->val = val; node->height = 0; node->left = NULL; node->right = NULL; return node; } // シリアライズの符号化規則は以下を参照: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二分木の配列表現: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // 二分木の連結リスト表現: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* リストを二分木にデシリアライズする: 再帰 */ TreeNode *arrayToTreeDFS(int *arr, int size, int i) { if (i < 0 || i >= size || arr[i] == INT_MAX) { return NULL; } TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); root->val = arr[i]; root->left = arrayToTreeDFS(arr, size, 2 * i + 1); root->right = arrayToTreeDFS(arr, size, 2 * i + 2); return root; } /* リストを二分木にデシリアライズする */ TreeNode *arrayToTree(int *arr, int size) { return arrayToTreeDFS(arr, size, 0); } /* 二分木をリストにシリアライズする: 再帰 */ void treeToArrayDFS(TreeNode *root, int i, int *res, int *size) { if (root == NULL) { return; } while (i >= *size) { res = realloc(res, (*size + 1) * sizeof(int)); res[*size] = INT_MAX; (*size)++; } res[i] = root->val; treeToArrayDFS(root->left, 2 * i + 1, res, size); treeToArrayDFS(root->right, 2 * i + 2, res, size); } /* 二分木をリストにシリアライズする */ int *treeToArray(TreeNode *root, int *size) { *size = 0; int *res = NULL; treeToArrayDFS(root, 0, res, size); return res; } /* 二分木のメモリを解放する */ void freeMemoryTree(TreeNode *root) { if (root == NULL) return; freeMemoryTree(root->left); freeMemoryTree(root->right); free(root); } #ifdef __cplusplus } #endif #endif // TREE_NODE_H ================================================ FILE: ja/codes/c/utils/uthash.h ================================================ /* Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTHASH_H #define UTHASH_H #define UTHASH_VERSION 2.3.0 #include /* memcmp, memset, strlen */ #include /* ptrdiff_t */ #include /* exit */ #if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT /* This codepath is provided for backward compatibility, but I plan to remove it. */ #warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" typedef unsigned int uint32_t; typedef unsigned char uint8_t; #elif defined(HASH_NO_STDINT) && HASH_NO_STDINT #else #include /* uint8_t, uint32_t */ #endif /* These macros use decltype or the earlier __typeof GNU extension. As decltype is only available in newer compilers (VS2010 or gcc 4.3+ when compiling c++ source) this code uses whatever method is needed or, for VS2008 where neither is available, uses casting workarounds. */ #if !defined(DECLTYPE) && !defined(NO_DECLTYPE) #if defined(_MSC_VER) /* MS compiler */ #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ #define DECLTYPE(x) (decltype(x)) #else /* VS2008 or older (or VS2010 in C mode) */ #define NO_DECLTYPE #endif #elif defined(__MCST__) /* Elbrus C Compiler */ #define DECLTYPE(x) (__typeof(x)) #elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) #define NO_DECLTYPE #else /* GNU, Sun and other compilers */ #define DECLTYPE(x) (__typeof(x)) #endif #endif #ifdef NO_DECLTYPE #define DECLTYPE(x) #define DECLTYPE_ASSIGN(dst,src) \ do { \ char **_da_dst = (char**)(&(dst)); \ *_da_dst = (char*)(src); \ } while (0) #else #define DECLTYPE_ASSIGN(dst,src) \ do { \ (dst) = DECLTYPE(dst)(src); \ } while (0) #endif #ifndef uthash_malloc #define uthash_malloc(sz) malloc(sz) /* malloc fcn */ #endif #ifndef uthash_free #define uthash_free(ptr,sz) free(ptr) /* free fcn */ #endif #ifndef uthash_bzero #define uthash_bzero(a,n) memset(a,'\0',n) #endif #ifndef uthash_strlen #define uthash_strlen(s) strlen(s) #endif #ifndef HASH_FUNCTION #define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) #endif #ifndef HASH_KEYCMP #define HASH_KEYCMP(a,b,n) memcmp(a,b,n) #endif #ifndef uthash_noexpand_fyi #define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ #endif #ifndef uthash_expand_fyi #define uthash_expand_fyi(tbl) /* can be defined to log expands */ #endif #ifndef HASH_NONFATAL_OOM #define HASH_NONFATAL_OOM 0 #endif #if HASH_NONFATAL_OOM /* malloc failures can be recovered from */ #ifndef uthash_nonfatal_oom #define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) #define IF_HASH_NONFATAL_OOM(x) x #else /* malloc failures result in lost memory, hash tables are unusable */ #ifndef uthash_fatal #define uthash_fatal(msg) exit(-1) /* fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") #define IF_HASH_NONFATAL_OOM(x) #endif /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ #define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ /* calculate the element whose hash handle address is hhp */ #define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) /* calculate the hash handle from element address elp */ #define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) #define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ do { \ struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ unsigned _hd_bkt; \ HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ (head)->hh.tbl->buckets[_hd_bkt].count++; \ _hd_hh_item->hh_next = NULL; \ _hd_hh_item->hh_prev = NULL; \ } while (0) #define HASH_VALUE(keyptr,keylen,hashv) \ do { \ HASH_FUNCTION(keyptr, keylen, hashv); \ } while (0) #define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_bkt; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ } \ } \ } while (0) #define HASH_FIND(hh,head,keyptr,keylen,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_hashv; \ HASH_VALUE(keyptr, keylen, _hf_hashv); \ HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ } \ } while (0) #ifdef HASH_BLOOM #define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) #define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) #define HASH_BLOOM_MAKE(tbl,oomed) \ do { \ (tbl)->bloom_nbits = HASH_BLOOM; \ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ if (!(tbl)->bloom_bv) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ } \ } while (0) #define HASH_BLOOM_FREE(tbl) \ do { \ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ } while (0) #define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) #define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) #define HASH_BLOOM_ADD(tbl,hashv) \ HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #define HASH_BLOOM_TEST(tbl,hashv) \ HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #else #define HASH_BLOOM_MAKE(tbl,oomed) #define HASH_BLOOM_FREE(tbl) #define HASH_BLOOM_ADD(tbl,hashv) #define HASH_BLOOM_TEST(tbl,hashv) (1) #define HASH_BLOOM_BYTELEN 0U #endif #define HASH_MAKE_TABLE(hh,head,oomed) \ do { \ (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ if (!(head)->hh.tbl) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ (head)->hh.tbl->tail = &((head)->hh); \ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ (head)->hh.tbl->signature = HASH_SIGNATURE; \ if (!(head)->hh.tbl->buckets) { \ HASH_RECORD_OOM(oomed); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } else { \ uthash_bzero((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ uthash_free((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } \ ) \ } \ } \ } while (0) #define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ } while (0) #define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ } while (0) #define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ } while (0) #define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ } while (0) #define HASH_APPEND_LIST(hh, head, add) \ do { \ (add)->hh.next = NULL; \ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ (head)->hh.tbl->tail->next = (add); \ (head)->hh.tbl->tail = &((add)->hh); \ } while (0) #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ do { \ if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ break; \ } \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #ifdef NO_DECLTYPE #undef HASH_AKBI_INNER_LOOP #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ char *_hs_saved_head = (char*)(head); \ do { \ DECLTYPE_ASSIGN(head, _hs_iter); \ if (cmpfcn(head, add) > 0) { \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ break; \ } \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #endif #if HASH_NONFATAL_OOM #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ if (!(oomed)) { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ if (oomed) { \ HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ HASH_DELETE_HH(hh, head, &(add)->hh); \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } else { \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } \ } else { \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } \ } while (0) #else #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } while (0) #endif #define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (char*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ void *_hs_iter = (head); \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ if (_hs_iter) { \ (add)->hh.next = _hs_iter; \ if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ } else { \ (head) = (add); \ } \ HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ } else { \ HASH_APPEND_LIST(hh, head, add); \ } \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ } while (0) #define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ do { \ unsigned _hs_hashv; \ HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ } while (0) #define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) #define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) #define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (const void*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_APPEND_LIST(hh, head, add); \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ } while (0) #define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ do { \ unsigned _ha_hashv; \ HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ } while (0) #define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) #define HASH_ADD(hh,head,fieldname,keylen_in,add) \ HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) #define HASH_TO_BKT(hashv,num_bkts,bkt) \ do { \ bkt = ((hashv) & ((num_bkts) - 1U)); \ } while (0) /* delete "delptr" from the hash table. * "the usual" patch-up process for the app-order doubly-linked-list. * The use of _hd_hh_del below deserves special explanation. * These used to be expressed using (delptr) but that led to a bug * if someone used the same symbol for the head and deletee, like * HASH_DELETE(hh,users,users); * We want that to work, but by changing the head (users) below * we were forfeiting our ability to further refer to the deletee (users) * in the patch-up process. Solution: use scratch space to * copy the deletee pointer, then the latter references are via that * scratch pointer rather than through the repointed (users) symbol. */ #define HASH_DELETE(hh,head,delptr) \ HASH_DELETE_HH(hh, head, &(delptr)->hh) #define HASH_DELETE_HH(hh,head,delptrhh) \ do { \ const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } else { \ unsigned _hd_bkt; \ if (_hd_hh_del == (head)->hh.tbl->tail) { \ (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ } \ if (_hd_hh_del->prev != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ } else { \ DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ } \ if (_hd_hh_del->next != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ } \ HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ (head)->hh.tbl->num_items--; \ } \ HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ } while (0) /* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ #define HASH_FIND_STR(head,findstr,out) \ do { \ unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ } while (0) #define HASH_ADD_STR(head,strfield,add) \ do { \ unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ } while (0) #define HASH_REPLACE_STR(head,strfield,add,replaced) \ do { \ unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ } while (0) #define HASH_FIND_INT(head,findint,out) \ HASH_FIND(hh,head,findint,sizeof(int),out) #define HASH_ADD_INT(head,intfield,add) \ HASH_ADD(hh,head,intfield,sizeof(int),add) #define HASH_REPLACE_INT(head,intfield,add,replaced) \ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) #define HASH_FIND_PTR(head,findptr,out) \ HASH_FIND(hh,head,findptr,sizeof(void *),out) #define HASH_ADD_PTR(head,ptrfield,add) \ HASH_ADD(hh,head,ptrfield,sizeof(void *),add) #define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) #define HASH_DEL(head,delptr) \ HASH_DELETE(hh,head,delptr) /* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. */ #ifdef HASH_DEBUG #include /* fprintf, stderr */ #define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) #define HASH_FSCK(hh,head,where) \ do { \ struct UT_hash_handle *_thh; \ if (head) { \ unsigned _bkt_i; \ unsigned _count = 0; \ char *_prev; \ for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ unsigned _bkt_count = 0; \ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ _prev = NULL; \ while (_thh) { \ if (_prev != (char*)(_thh->hh_prev)) { \ HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ (where), (void*)_thh->hh_prev, (void*)_prev); \ } \ _bkt_count++; \ _prev = (char*)(_thh); \ _thh = _thh->hh_next; \ } \ _count += _bkt_count; \ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ } \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ _count = 0; \ _prev = NULL; \ _thh = &(head)->hh; \ while (_thh) { \ _count++; \ if (_prev != (char*)_thh->prev) { \ HASH_OOPS("%s: invalid prev %p, actual %p\n", \ (where), (void*)_thh->prev, (void*)_prev); \ } \ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ } \ } while (0) #else #define HASH_FSCK(hh,head,where) #endif /* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to * the descriptor to which this macro is defined for tuning the hash function. * The app can #include to get the prototype for write(2). */ #ifdef HASH_EMIT_KEYS #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ do { \ unsigned _klen = fieldlen; \ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ } while (0) #else #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) #endif /* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ #define HASH_BER(key,keylen,hashv) \ do { \ unsigned _hb_keylen = (unsigned)keylen; \ const unsigned char *_hb_key = (const unsigned char*)(key); \ (hashv) = 0; \ while (_hb_keylen-- != 0U) { \ (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ } \ } while (0) /* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx * (archive link: https://archive.is/Ivcan ) */ #define HASH_SAX(key,keylen,hashv) \ do { \ unsigned _sx_i; \ const unsigned char *_hs_key = (const unsigned char*)(key); \ hashv = 0; \ for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ } \ } while (0) /* FNV-1a variation */ #define HASH_FNV(key,keylen,hashv) \ do { \ unsigned _fn_i; \ const unsigned char *_hf_key = (const unsigned char*)(key); \ (hashv) = 2166136261U; \ for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ hashv = hashv ^ _hf_key[_fn_i]; \ hashv = hashv * 16777619U; \ } \ } while (0) #define HASH_OAT(key,keylen,hashv) \ do { \ unsigned _ho_i; \ const unsigned char *_ho_key=(const unsigned char*)(key); \ hashv = 0; \ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ hashv += _ho_key[_ho_i]; \ hashv += (hashv << 10); \ hashv ^= (hashv >> 6); \ } \ hashv += (hashv << 3); \ hashv ^= (hashv >> 11); \ hashv += (hashv << 15); \ } while (0) #define HASH_JEN_MIX(a,b,c) \ do { \ a -= b; a -= c; a ^= ( c >> 13 ); \ b -= c; b -= a; b ^= ( a << 8 ); \ c -= a; c -= b; c ^= ( b >> 13 ); \ a -= b; a -= c; a ^= ( c >> 12 ); \ b -= c; b -= a; b ^= ( a << 16 ); \ c -= a; c -= b; c ^= ( b >> 5 ); \ a -= b; a -= c; a ^= ( c >> 3 ); \ b -= c; b -= a; b ^= ( a << 10 ); \ c -= a; c -= b; c ^= ( b >> 15 ); \ } while (0) #define HASH_JEN(key,keylen,hashv) \ do { \ unsigned _hj_i,_hj_j,_hj_k; \ unsigned const char *_hj_key=(unsigned const char*)(key); \ hashv = 0xfeedbeefu; \ _hj_i = _hj_j = 0x9e3779b9u; \ _hj_k = (unsigned)(keylen); \ while (_hj_k >= 12U) { \ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + ( (unsigned)_hj_key[2] << 16 ) \ + ( (unsigned)_hj_key[3] << 24 ) ); \ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + ( (unsigned)_hj_key[6] << 16 ) \ + ( (unsigned)_hj_key[7] << 24 ) ); \ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + ( (unsigned)_hj_key[10] << 16 ) \ + ( (unsigned)_hj_key[11] << 24 ) ); \ \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ \ _hj_key += 12; \ _hj_k -= 12U; \ } \ hashv += (unsigned)(keylen); \ switch ( _hj_k ) { \ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ default: ; \ } \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ } while (0) /* The Paul Hsieh hash function */ #undef get16bits #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) #define get16bits(d) (*((const uint16_t *) (d))) #endif #if !defined (get16bits) #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif #define HASH_SFH(key,keylen,hashv) \ do { \ unsigned const char *_sfh_key=(unsigned const char*)(key); \ uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ \ unsigned _sfh_rem = _sfh_len & 3U; \ _sfh_len >>= 2; \ hashv = 0xcafebabeu; \ \ /* Main loop */ \ for (;_sfh_len > 0U; _sfh_len--) { \ hashv += get16bits (_sfh_key); \ _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ hashv = (hashv << 16) ^ _sfh_tmp; \ _sfh_key += 2U*sizeof (uint16_t); \ hashv += hashv >> 11; \ } \ \ /* Handle end cases */ \ switch (_sfh_rem) { \ case 3: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 16; \ hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ hashv += hashv >> 11; \ break; \ case 2: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 11; \ hashv += hashv >> 17; \ break; \ case 1: hashv += *_sfh_key; \ hashv ^= hashv << 10; \ hashv += hashv >> 1; \ break; \ default: ; \ } \ \ /* Force "avalanching" of final 127 bits */ \ hashv ^= hashv << 3; \ hashv += hashv >> 5; \ hashv ^= hashv << 4; \ hashv += hashv >> 17; \ hashv ^= hashv << 25; \ hashv += hashv >> 6; \ } while (0) /* iterate over items in a known bucket to find desired item */ #define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ do { \ if ((head).hh_head != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ } else { \ (out) = NULL; \ } \ while ((out) != NULL) { \ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ break; \ } \ } \ if ((out)->hh.hh_next != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ } else { \ (out) = NULL; \ } \ } \ } while (0) /* add an item to a bucket */ #define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ do { \ UT_hash_bucket *_ha_head = &(head); \ _ha_head->count++; \ (addhh)->hh_next = _ha_head->hh_head; \ (addhh)->hh_prev = NULL; \ if (_ha_head->hh_head != NULL) { \ _ha_head->hh_head->hh_prev = (addhh); \ } \ _ha_head->hh_head = (addhh); \ if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ && !(addhh)->tbl->noexpand) { \ HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ HASH_DEL_IN_BKT(head,addhh); \ } \ ) \ } \ } while (0) /* remove an item from a given bucket */ #define HASH_DEL_IN_BKT(head,delhh) \ do { \ UT_hash_bucket *_hd_head = &(head); \ _hd_head->count--; \ if (_hd_head->hh_head == (delhh)) { \ _hd_head->hh_head = (delhh)->hh_next; \ } \ if ((delhh)->hh_prev) { \ (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ } \ if ((delhh)->hh_next) { \ (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ } \ } while (0) /* Bucket expansion has the effect of doubling the number of buckets * and redistributing the items into the new buckets. Ideally the * items will distribute more or less evenly into the new buckets * (the extent to which this is true is a measure of the quality of * the hash function as it applies to the key domain). * * With the items distributed into more buckets, the chain length * (item count) in each bucket is reduced. Thus by expanding buckets * the hash keeps a bound on the chain length. This bounded chain * length is the essence of how a hash provides constant time lookup. * * The calculation of tbl->ideal_chain_maxlen below deserves some * explanation. First, keep in mind that we're calculating the ideal * maximum chain length based on the *new* (doubled) bucket count. * In fractions this is just n/b (n=number of items,b=new num buckets). * Since the ideal chain length is an integer, we want to calculate * ceil(n/b). We don't depend on floating point arithmetic in this * hash, so to calculate ceil(n/b) with integers we could write * * ceil(n/b) = (n/b) + ((n%b)?1:0) * * and in fact a previous version of this hash did just that. * But now we have improved things a bit by recognizing that b is * always a power of two. We keep its base 2 log handy (call it lb), * so now we can write this with a bit shift and logical AND: * * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) * */ #define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ do { \ unsigned _he_bkt; \ unsigned _he_bkt_i; \ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ if (!_he_new_buckets) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero(_he_new_buckets, \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ (tbl)->ideal_chain_maxlen = \ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ (tbl)->nonideal_items = 0; \ for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ while (_he_thh != NULL) { \ _he_hh_nxt = _he_thh->hh_next; \ HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ _he_newbkt = &(_he_new_buckets[_he_bkt]); \ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ (tbl)->nonideal_items++; \ if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ _he_newbkt->expand_mult++; \ } \ } \ _he_thh->hh_prev = NULL; \ _he_thh->hh_next = _he_newbkt->hh_head; \ if (_he_newbkt->hh_head != NULL) { \ _he_newbkt->hh_head->hh_prev = _he_thh; \ } \ _he_newbkt->hh_head = _he_thh; \ _he_thh = _he_hh_nxt; \ } \ } \ uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ (tbl)->num_buckets *= 2U; \ (tbl)->log2_num_buckets++; \ (tbl)->buckets = _he_new_buckets; \ (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ ((tbl)->ineff_expands+1U) : 0U; \ if ((tbl)->ineff_expands > 1U) { \ (tbl)->noexpand = 1; \ uthash_noexpand_fyi(tbl); \ } \ uthash_expand_fyi(tbl); \ } \ } while (0) /* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ /* Note that HASH_SORT assumes the hash handle name to be hh. * HASH_SRT was added to allow the hash handle name to be passed in. */ #define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) #define HASH_SRT(hh,head,cmpfcn) \ do { \ unsigned _hs_i; \ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ if (head != NULL) { \ _hs_insize = 1; \ _hs_looping = 1; \ _hs_list = &((head)->hh); \ while (_hs_looping != 0U) { \ _hs_p = _hs_list; \ _hs_list = NULL; \ _hs_tail = NULL; \ _hs_nmerges = 0; \ while (_hs_p != NULL) { \ _hs_nmerges++; \ _hs_q = _hs_p; \ _hs_psize = 0; \ for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ _hs_psize++; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ if (_hs_q == NULL) { \ break; \ } \ } \ _hs_qsize = _hs_insize; \ while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ if (_hs_psize == 0U) { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else if ((cmpfcn( \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ )) <= 0) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } \ if ( _hs_tail != NULL ) { \ _hs_tail->next = ((_hs_e != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ } else { \ _hs_list = _hs_e; \ } \ if (_hs_e != NULL) { \ _hs_e->prev = ((_hs_tail != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ } \ _hs_tail = _hs_e; \ } \ _hs_p = _hs_q; \ } \ if (_hs_tail != NULL) { \ _hs_tail->next = NULL; \ } \ if (_hs_nmerges <= 1U) { \ _hs_looping = 0; \ (head)->hh.tbl->tail = _hs_tail; \ DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ } \ _hs_insize *= 2U; \ } \ HASH_FSCK(hh, head, "HASH_SRT"); \ } \ } while (0) /* This function selects items from one hash into another hash. * The end result is that the selected items have dual presence * in both hashes. There is no copy of the items made; rather * they are added into the new hash through a secondary hash * hash handle that must be present in the structure. */ #define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ do { \ unsigned _src_bkt, _dst_bkt; \ void *_last_elt = NULL, *_elt; \ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ if ((src) != NULL) { \ for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ _src_hh != NULL; \ _src_hh = _src_hh->hh_next) { \ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ if (cond(_elt)) { \ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ _dst_hh->key = _src_hh->key; \ _dst_hh->keylen = _src_hh->keylen; \ _dst_hh->hashv = _src_hh->hashv; \ _dst_hh->prev = _last_elt; \ _dst_hh->next = NULL; \ if (_last_elt_hh != NULL) { \ _last_elt_hh->next = _elt; \ } \ if ((dst) == NULL) { \ DECLTYPE_ASSIGN(dst, _elt); \ HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ uthash_nonfatal_oom(_elt); \ (dst) = NULL; \ continue; \ } \ ) \ } else { \ _dst_hh->tbl = (dst)->hh_dst.tbl; \ } \ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ (dst)->hh_dst.tbl->num_items++; \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ _dst_hh->tbl = NULL; \ uthash_nonfatal_oom(_elt); \ continue; \ } \ ) \ HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ _last_elt = _elt; \ _last_elt_hh = _dst_hh; \ } \ } \ } \ } \ HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ } while (0) #define HASH_CLEAR(hh,head) \ do { \ if ((head) != NULL) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } \ } while (0) #define HASH_OVERHEAD(hh,head) \ (((head) != NULL) ? ( \ (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ sizeof(UT_hash_table) + \ (HASH_BLOOM_BYTELEN))) : 0U) #ifdef NO_DECLTYPE #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) #else #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) #endif /* obtain a count of items in the hash */ #define HASH_COUNT(head) HASH_CNT(hh,head) #define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) typedef struct UT_hash_bucket { struct UT_hash_handle *hh_head; unsigned count; /* expand_mult is normally set to 0. In this situation, the max chain length * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If * the bucket's chain exceeds this length, bucket expansion is triggered). * However, setting expand_mult to a non-zero value delays bucket expansion * (that would be triggered by additions to this particular bucket) * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. * (The multiplier is simply expand_mult+1). The whole idea of this * multiplier is to reduce bucket expansions, since they are expensive, in * situations where we know that a particular bucket tends to be overused. * It is better to let its chain length grow to a longer yet-still-bounded * value, than to do an O(n) bucket expansion too often. */ unsigned expand_mult; } UT_hash_bucket; /* random signature used only to find hash tables in external analysis */ #define HASH_SIGNATURE 0xa0111fe1u #define HASH_BLOOM_SIGNATURE 0xb12220f2u typedef struct UT_hash_table { UT_hash_bucket *buckets; unsigned num_buckets, log2_num_buckets; unsigned num_items; struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ /* in an ideal situation (all buckets used equally), no bucket would have * more than ceil(#items/#buckets) items. that's the ideal chain length. */ unsigned ideal_chain_maxlen; /* nonideal_items is the number of items in the hash whose chain position * exceeds the ideal chain maxlen. these items pay the penalty for an uneven * hash distribution; reaching them in a chain traversal takes >ideal steps */ unsigned nonideal_items; /* ineffective expands occur when a bucket doubling was performed, but * afterward, more than half the items in the hash had nonideal chain * positions. If this happens on two consecutive expansions we inhibit any * further expansion, as it's not helping; this happens when the hash * function isn't a good fit for the key domain. When expansion is inhibited * the hash will still work, albeit no longer in constant time. */ unsigned ineff_expands, noexpand; uint32_t signature; /* used only to find hash tables in external analysis */ #ifdef HASH_BLOOM uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ uint8_t *bloom_bv; uint8_t bloom_nbits; #endif } UT_hash_table; typedef struct UT_hash_handle { struct UT_hash_table *tbl; void *prev; /* prev element in app order */ void *next; /* next element in app order */ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ struct UT_hash_handle *hh_next; /* next hh in bucket order */ const void *key; /* ptr to enclosing struct's key */ unsigned keylen; /* enclosing struct's key len */ unsigned hashv; /* result of hash-fcn(key) */ } UT_hash_handle; #endif /* UTHASH_H */ ================================================ FILE: ja/codes/c/utils/vector.h ================================================ /** * File: vector.h * Created Time: 2023-07-13 * Author: Zuoxun (845242523@qq.com)、Gonglja (glj0@outlook.com) */ #ifndef VECTOR_H #define VECTOR_H #ifdef __cplusplus extern "C" { #endif /* ベクタ型を定義 */ typedef struct vector { int size; // 現在のベクタのサイズ int capacity; // 現在のベクタの容量 int depth; // 現在のベクタの深さ void **data; // データを指すポインタ配列 } vector; /* ベクタを構築 */ vector *newVector() { vector *v = malloc(sizeof(vector)); v->size = 0; v->capacity = 4; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); return v; } /* ベクタを構築し、サイズと要素のデフォルト値を指定する */ vector *_newVector(int size, void *elem, int elemSize) { vector *v = malloc(sizeof(vector)); v->size = size; v->capacity = size; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); for (int i = 0; i < size; i++) { void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[i] = tmp; } return v; } /* ベクタを破棄 */ void delVector(vector *v) { if (v) { if (v->depth == 0) { return; } else if (v->depth == 1) { for (int i = 0; i < v->size; i++) { free(v->data[i]); } free(v); } else { for (int i = 0; i < v->size; i++) { delVector(v->data[i]); } v->depth--; } } } /* 要素をベクタの末尾に追加する(コピー方式) */ void vectorPushback(vector *v, void *elem, int elemSize) { if (v->size == v->capacity) { v->capacity *= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[v->size++] = tmp; } /* ベクタの末尾から要素を取り出す */ void vectorPopback(vector *v) { if (v->size != 0) { free(v->data[v->size - 1]); v->size--; } } /* ベクタをクリア */ void vectorClear(vector *v) { delVector(v); v->size = 0; v->capacity = 4; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); } /* ベクタのサイズを取得する */ int vectorSize(vector *v) { return v->size; } /* ベクタの末尾要素を取得する */ void *vectorBack(vector *v) { int n = v->size; return n > 0 ? v->data[n - 1] : NULL; } /* ベクタの先頭要素を取得する */ void *vectorFront(vector *v) { return v->size > 0 ? v->data[0] : NULL; } /* ベクタの添字 `pos` の要素を取得する */ void *vectorAt(vector *v, int pos) { if (pos < 0 || pos >= v->size) { printf("vectorAt: out of range\n"); return NULL; } return v->data[pos]; } /* ベクタの添字 `pos` の要素を設定する */ void vectorSet(vector *v, int pos, void *elem, int elemSize) { if (pos < 0 || pos >= v->size) { printf("vectorSet: out of range\n"); return; } free(v->data[pos]); void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[pos] = tmp; } /* ベクトルを拡張する */ void vectorExpand(vector *v) { v->capacity *= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } /* ベクトルを縮小する */ void vectorShrink(vector *v) { v->capacity /= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } /* ベクタの添字 pos に要素を挿入 */ void vectorInsert(vector *v, int pos, void *elem, int elemSize) { if (v->size == v->capacity) { vectorExpand(v); } for (int j = v->size; j > pos; j--) { v->data[j] = v->data[j - 1]; } void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[pos] = tmp; v->size++; } /* ベクトルの添字 pos の要素を削除する */ void vectorErase(vector *v, int pos) { if (v->size != 0) { free(v->data[pos]); for (int j = pos; j < v->size - 1; j++) { v->data[j] = v->data[j + 1]; } v->size--; } } /* ベクトルの要素を交換する */ void vectorSwap(vector *v, int i, int j) { void *tmp = v->data[i]; v->data[i] = v->data[j]; v->data[j] = tmp; } /* ベクトルが空かどうか */ bool vectorEmpty(vector *v) { return v->size == 0; } /* ベクトルが満杯かどうか */ bool vectorFull(vector *v) { return v->size == v->capacity; } /* ベクトルが等しいかどうか */ bool vectorEqual(vector *v1, vector *v2) { if (v1->size != v2->size) { printf("size not equal\n"); return false; } for (int i = 0; i < v1->size; i++) { void *a = v1->data[i]; void *b = v2->data[i]; if (memcmp(a, b, sizeof(a)) != 0) { printf("data %d not equal\n", i); return false; } } return true; } /* ベクタ内部をソート */ void vectorSort(vector *v, int (*cmp)(const void *, const void *)) { qsort(v->data, v->size, sizeof(void *), cmp); } /* 出力関数。出力対象の変数を表示する関数を渡す必要がある */ /* 現在は深さ 1 の vector のみ出力をサポート */ void printVector(vector *v, void (*printFunc)(vector *v, void *p)) { if (v) { if (v->depth == 0) { return; } else if (v->depth == 1) { if(v->size == 0) { printf("\n"); return; } for (int i = 0; i < v->size; i++) { if (i == 0) { printf("["); } else if (i == v->size - 1) { printFunc(v, v->data[i]); printf("]\r\n"); break; } printFunc(v, v->data[i]); printf(","); } } else { for (int i = 0; i < v->size; i++) { printVector(v->data[i], printFunc); } v->depth--; } } } /* 現在は深さ 2 の vector のみ出力をサポート */ void printVectorMatrix(vector *vv, void (*printFunc)(vector *v, void *p)) { printf("[\n"); for (int i = 0; i < vv->size; i++) { vector *v = (vector *)vv->data[i]; printf(" ["); for (int j = 0; j < v->size; j++) { printFunc(v, v->data[j]); if (j != v->size - 1) printf(","); } printf("],"); printf("\n"); } printf("]\n"); } #ifdef __cplusplus } #endif #endif // VECTOR_H ================================================ FILE: ja/codes/c/utils/vertex.h ================================================ /** * File: vertex.h * Created Time: 2023-10-28 * Author: krahets (krahets@163.com) */ #ifndef VERTEX_H #define VERTEX_H #ifdef __cplusplus extern "C" { #endif /* 頂点構造体 */ typedef struct { int val; } Vertex; /* コンストラクタ。新しいノードを初期化する */ Vertex *newVertex(int val) { Vertex *vet; vet = (Vertex *)malloc(sizeof(Vertex)); vet->val = val; return vet; } /* 値の配列を頂点配列に変換 */ Vertex **valsToVets(int *vals, int size) { Vertex **vertices = (Vertex **)malloc(size * sizeof(Vertex *)); for (int i = 0; i < size; ++i) { vertices[i] = newVertex(vals[i]); } return vertices; } /* 頂点配列を値配列に変換 */ int *vetsToVals(Vertex **vertices, int size) { int *vals = (int *)malloc(size * sizeof(int)); for (int i = 0; i < size; ++i) { vals[i] = vertices[i]->val; } return vals; } #ifdef __cplusplus } #endif #endif // VERTEX_H ================================================ FILE: ja/codes/cpp/.gitignore ================================================ # Ignore all * # Unignore all with extensions !*.* # Unignore all dirs !*/ *.dSYM/ build/ ================================================ FILE: ja/codes/cpp/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(hello_algo CXX) set(CMAKE_CXX_STANDARD 11) include_directories(./include) add_subdirectory(chapter_computational_complexity) add_subdirectory(chapter_array_and_linkedlist) add_subdirectory(chapter_stack_and_queue) add_subdirectory(chapter_hashing) add_subdirectory(chapter_tree) add_subdirectory(chapter_heap) add_subdirectory(chapter_graph) add_subdirectory(chapter_searching) add_subdirectory(chapter_sorting) add_subdirectory(chapter_divide_and_conquer) add_subdirectory(chapter_backtracking) add_subdirectory(chapter_dynamic_programming) add_subdirectory(chapter_greedy) ================================================ FILE: ja/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt ================================================ add_executable(array array.cpp) add_executable(linked_list linked_list.cpp) add_executable(list list.cpp) add_executable(my_list my_list.cpp) ================================================ FILE: ja/codes/cpp/chapter_array_and_linkedlist/array.cpp ================================================ /** * 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) からランダムに 1 つの数を選ぶ int randomIndex = rand() % size; // ランダムな要素を取得して返す int randomNum = nums[randomIndex]; return randomNum; } /* 配列長を拡張する */ int *extend(int *nums, int size, int enlarge) { // 拡張後の長さを持つ配列を初期化する int *res = new int[size + enlarge]; // 元の配列の全要素を新しい配列にコピー for (int i = 0; i < size; i++) { res[i] = nums[i]; } // メモリを解放する delete[] nums; // 拡張後の新しい配列を返す return res; } /* 配列の index 番目に要素 num を挿入 */ 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; } /* Driver Code */ int main() { /* 配列を初期化 */ int size = 5; int *arr = new int[size]; cout << "配列 arr = "; printArray(arr, size); int *nums = new int[size]{1, 3, 2, 5, 4}; cout << "配列 nums = "; printArray(nums, size); /* ランダムアクセス */ int randomNum = randomAccess(nums, size); cout << "nums から取得したランダム要素 " << randomNum << endl; /* 長さを拡張 */ int enlarge = 3; nums = extend(nums, size, enlarge); size += enlarge; cout << "配列長を 8 に拡張し、nums = "; printArray(nums, size); /* 要素を挿入する */ insert(nums, size, 6, 3); cout << "インデックス 3 に数値 6 を挿入し、nums = "; printArray(nums, size); /* 要素を削除 */ remove(nums, size, 2); cout << "インデックス 2 の要素を削除し、nums = "; printArray(nums, size); /* 配列を走査 */ traverse(nums, size); /* 要素を探索する */ int index = find(nums, size, 3); cout << "nums 内で要素 3 を検索し、インデックス = " << index << endl; // メモリを解放する delete[] arr; delete[] nums; return 0; } ================================================ FILE: ja/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp ================================================ /** * 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; } /* Driver Code */ 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 << "初期化した連結リストは" << endl; printLinkedList(n0); /* ノードを挿入 */ insert(n0, new ListNode(0)); cout << "ノード挿入後の連結リストは" << endl; printLinkedList(n0); /* ノードを削除 */ remove(n0); cout << "ノード削除後の連結リストは" << endl; printLinkedList(n0); /* ノードにアクセス */ ListNode *node = access(n0, 3); cout << "連結リストのインデックス 3 のノードの値 = " << node->val << endl; /* ノードを探索 */ int index = find(n0, 2); cout << "連結リスト内で値が 2 のノードのインデックス = " << index << endl; // メモリを解放する freeMemoryLinkedList(n0); return 0; } ================================================ FILE: ja/codes/cpp/chapter_array_and_linkedlist/list.cpp ================================================ /** * File: list.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* リストを初期化 */ vector nums = {1, 3, 2, 5, 4}; cout << "リスト nums = "; printVector(nums); /* 要素にアクセス */ int num = nums[1]; cout << "インデックス 1 の要素にアクセスすると、num = " << num << endl; /* 要素を更新 */ nums[1] = 0; cout << "インデックス 1 の要素を 0 に更新すると、nums = "; printVector(nums); /* リストを空にする */ nums.clear(); cout << "リストを空にした後の nums = "; printVector(nums); /* 末尾に要素を追加 */ nums.push_back(1); nums.push_back(3); nums.push_back(2); nums.push_back(5); nums.push_back(4); cout << "要素追加後の nums = "; printVector(nums); /* 中間に要素を挿入 */ nums.insert(nums.begin() + 3, 6); cout << "インデックス 3 に数値 6 を挿入し、nums = "; printVector(nums); /* 要素を削除 */ nums.erase(nums.begin() + 3); cout << "インデックス 3 の要素を削除すると、nums = "; printVector(nums); /* インデックスでリストを走査 */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums[i]; } /* リスト要素を直接走査 */ count = 0; for (int x : nums) { count += x; } /* 2 つのリストを連結する */ vector nums1 = {6, 8, 7, 10, 9}; nums.insert(nums.end(), nums1.begin(), nums1.end()); cout << "リスト nums1 を nums の後ろに連結すると、nums = "; printVector(nums); /* リストをソート */ sort(nums.begin(), nums.end()); cout << "リストをソートした後の nums = "; printVector(nums); return 0; } ================================================ FILE: ja/codes/cpp/chapter_array_and_linkedlist/my_list.cpp ================================================ /** * 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("インデックスが範囲外"); return arr[index]; } /* 要素を更新 */ void set(int index, int num) { if (index < 0 || index >= size()) throw out_of_range("インデックスが範囲外"); 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("インデックスが範囲外"); // 要素数が容量を超えると、拡張機構が発動する 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("インデックスが範囲外"); 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; } }; /* Driver Code */ int main() { /* リストを初期化 */ MyList *nums = new MyList(); /* 末尾に要素を追加 */ nums->add(1); nums->add(3); nums->add(2); nums->add(5); nums->add(4); cout << "リスト nums = "; vector vec = nums->toVector(); printVector(vec); cout << "容量 = " << nums->capacity() << " ,長さ = " << nums->size() << endl; /* 中間に要素を挿入 */ nums->insert(3, 6); cout << "インデックス 3 に数値 6 を挿入し、nums = "; vec = nums->toVector(); printVector(vec); /* 要素を削除 */ nums->remove(3); cout << "インデックス 3 の要素を削除すると、nums = "; vec = nums->toVector(); printVector(vec); /* 要素にアクセス */ int num = nums->get(1); cout << "インデックス 1 の要素にアクセスすると、num = " << num << endl; /* 要素を更新 */ nums->set(1, 0); cout << "インデックス 1 の要素を 0 に更新すると、nums = "; vec = nums->toVector(); printVector(vec); /* 拡張機構をテストする */ for (int i = 0; i < 10; i++) { // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する nums->add(i); } cout << "拡張後のリスト nums = "; vec = nums->toVector(); printVector(vec); cout << "容量 = " << nums->capacity() << " ,長さ = " << nums->size() << endl; // メモリを解放する delete nums; return 0; } ================================================ FILE: ja/codes/cpp/chapter_backtracking/CMakeLists.txt ================================================ add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.cpp) add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.cpp) add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.cpp) add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.cpp) add_executable(permutations_i permutations_i.cpp) add_executable(permutations_ii permutations_ii.cpp) add_executable(n_queens n_queens.cpp) add_executable(subset_sum_i_naive subset_sum_i_naive.cpp) add_executable(subset_sum_i subset_sum_i.cpp) add_executable(subset_sum_ii subset_sum_ii.cpp) ================================================ FILE: ja/codes/cpp/chapter_backtracking/n_queens.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_backtracking/permutations_i.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector nums = {1, 2, 3}; vector> res = permutationsI(nums); cout << "入力配列 nums = "; printVector(nums); cout << "すべての順列 res = "; printVectorMatrix(res); return 0; } ================================================ FILE: ja/codes/cpp/chapter_backtracking/permutations_ii.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector nums = {1, 1, 2}; vector> res = permutationsII(nums); cout << "入力配列 nums = "; printVector(nums); cout << "すべての順列 res = "; printVectorMatrix(res); return 0; } ================================================ FILE: ja/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp ================================================ /** * 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); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n二分木を初期化" << endl; printTree(root); // 先行順走査 preOrder(root); cout << "\n値が 7 のすべてのノードを出力" << endl; vector vals; for (TreeNode *node : res) { vals.push_back(node->val); } printVector(vals); } ================================================ FILE: ja/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp ================================================ /** * 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(); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n二分木を初期化" << endl; printTree(root); // 先行順走査 preOrder(root); cout << "\n根ノードからノード 7 までのすべての経路を出力" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp ================================================ /** * 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(); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n二分木を初期化" << endl; printTree(root); // 先行順走査 preOrder(root); cout << "\n根ノードからノード 7 までのすべての経路を出力し、経路に値 3 のノードを含めない" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp ================================================ /** * 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); } } } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n二分木を初期化" << endl; printTree(root); // バックトラッキング法 vector state; vector choices = {root}; vector> res; backtrack(state, choices, res); cout << "\n根ノードからノード 7 までのすべての経路を出力し、経路に値 3 のノードを含めない" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: ja/codes/cpp/chapter_backtracking/subset_sum_i.cpp ================================================ /** * 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; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for (int i = start; i < choices.size(); i++) { // 枝刈り1:部分集合の和が 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; } /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp ================================================ /** * 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 (size_t 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; } /* Driver Code */ 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); return 0; } ================================================ FILE: ja/codes/cpp/chapter_backtracking/subset_sum_ii.cpp ================================================ /** * 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; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for (int i = start; i < choices.size(); i++) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする 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; } /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_computational_complexity/CMakeLists.txt ================================================ add_executable(iteration iteration.cpp) add_executable(recursion recursion.cpp) add_executable(space_complexity space_complexity.cpp) add_executable(time_complexity time_complexity.cpp) add_executable(worst_best_time_complexity worst_best_time_complexity.cpp) ================================================ FILE: ja/codes/cpp/chapter_computational_complexity/iteration.cpp ================================================ /** * 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; } /* 二重 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(); } /* Driver Code */ 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 << "\n二重 for ループの走査結果 " << resStr << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_computational_complexity/recursion.cpp ================================================ /** * 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; } /* Driver Code */ 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フィボナッチ数列の第 " << n << " 項は " << res << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_computational_complexity/space_complexity.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int n = 5; // 定数階 constant(n); // 線形階 linear(n); linearRecur(n); // 二乗階 quadratic(n); quadraticRecur(n); // 指数オーダー TreeNode *root = buildTree(n); printTree(root); // メモリを解放する freeMemoryTree(root); return 0; } ================================================ FILE: ja/codes/cpp/chapter_computational_complexity/time_complexity.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp ================================================ /** * 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; } /* Driver Code */ int main() { for (int i = 0; i < 1000; i++) { int n = 100; vector nums = randomNumbers(n); int index = findOne(nums); cout << "\n配列 [ 1, 2, ..., n ] をシャッフルした後 = "; printVector(nums); cout << "数字 1 のインデックスは " << index << endl; } return 0; } ================================================ FILE: ja/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt ================================================ add_executable(binary_search_recur binary_search_recur.cpp) add_executable(build_tree build_tree.cpp) add_executable(hanota hanota.cpp) ================================================ FILE: ja/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp ================================================ /** * 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) / 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); } /* Driver Code */ int main() { int target = 6; vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; // 二分探索(両閉区間) int index = binarySearch(nums, target); cout << "対象要素 6 のインデックス = " << index << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_divide_and_conquer/build_tree.cpp ================================================ /** * 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) { // 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; } /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_divide_and_conquer/hanota.cpp ================================================ /** * File: hanota.cpp * Created Time: 2023-07-17 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* 円盤を 1 枚移動 */ void move(vector &src, vector &tar) { // src の上から円盤を1枚取り出す 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):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i - 1, src, tar, buf); // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src, tar); // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i - 1, buf, src, tar); } /* ハノイの塔を解く */ void solveHanota(vector &A, vector &B, vector &C) { int n = A.size(); // A の上から n 枚の円盤を B を介して C へ移す dfs(n, A, B, C); } /* Driver Code */ int main() { // リスト末尾が柱の頂上 vector A = {5, 4, 3, 2, 1}; vector B = {}; vector C = {}; cout << "初期状態:\n"; cout << "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; } ================================================ FILE: ja/codes/cpp/chapter_dynamic_programming/CMakeLists.txt ================================================ add_executable(climbing_stairs_backtrack climbing_stairs_backtrack.cpp) add_executable(climbing_stairs_dfs climbing_stairs_dfs.cpp) add_executable(climbing_stairs_dfs_mem climbing_stairs_dfs_mem.cpp) add_executable(climbing_stairs_dp climbing_stairs_dp.cpp) add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.cpp) add_executable(min_path_sum min_path_sum.cpp) add_executable(unbounded_knapsack unbounded_knapsack.cpp) add_executable(coin_change coin_change.cpp) add_executable(coin_change_ii coin_change_ii.cpp) add_executable(edit_distance edit_distance.cpp) ================================================ FILE: ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp ================================================ /** * 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]; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsBacktrack(n); cout << "階段を " << n << " 段上る方法は全部で " << res << " 通り" << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp ================================================ /** * 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]; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsConstraintDP(n); cout << "階段を " << n << " 段上る方法は全部で " << res << " 通り" << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp ================================================ /** * 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); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFS(n); cout << "階段を " << n << " 段上る方法は全部で " << res << " 通り" << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp ================================================ /** * 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); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFSMem(n); cout << "階段を " << n << " 段上る方法は全部で " << res << " 通り" << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDP(n); cout << "階段を " << n << " 段上る方法は全部で " << res << " 通り" << endl; res = climbingStairsDPComp(n); cout << "階段を " << n << " 段上る方法は全部で " << res << " 通り" << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_dynamic_programming/coin_change.cpp ================================================ /** * 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; } /* Driver code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp ================================================ /** * 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 を選ばない場合と選ぶ場合の和 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 を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_dynamic_programming/edit_distance.cpp ================================================ /** * 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 editDistanceDFSMem(string s, string t, vector> &mem, int i, int j) { // s と t がともに空なら 0 を返す if (i == 0 && j == 0) return 0; // s が空なら t の長さを返す if (i == 0) return j; // t が空なら s の長さを返す if (j == 0) return i; // 記録済みなら、それをそのまま返す if (mem[i][j] != -1) return mem[i][j]; // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 int insert = editDistanceDFSMem(s, t, mem, i, j - 1); int del = editDistanceDFSMem(s, t, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最小編集回数を記録して返す mem[i][j] = min(min(insert, del), replace) + 1; return mem[i][j]; } /* 編集距離:動的計画法 */ int editDistanceDP(string s, string t) { int n = s.length(), m = t.length(); vector> dp(n + 1, vector(m + 1, 0)); // 状態遷移:先頭行と先頭列 for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // 状態遷移: 残りの行と列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i - 1][j - 1]; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* 編集距離:空間最適化した動的計画法 */ int editDistanceDPComp(string s, string t) { int n = s.length(), m = t.length(); vector dp(m + 1, 0); // 状態遷移:先頭行 for (int j = 1; j <= m; j++) { dp[j] = j; } // 状態遷移:残りの行 for (int i = 1; i <= n; i++) { // 状態遷移:先頭列 int leftup = dp[0]; // dp[i-1, j-1] を一時保存する dp[0] = i; // 状態遷移:残りの列 for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftup; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 次の反復の dp[i-1, j-1] に更新する } } return dp[m]; } /* Driver Code */ int main() { string s = "bag"; string t = "pack"; int n = s.length(), m = t.length(); // 全探索 int res = editDistanceDFS(s, t, n, m); cout << s << " を " << t << " に変更するには最小で " << res << " 回の編集が必要\n"; // メモ化探索 vector> mem(n + 1, vector(m + 1, -1)); res = editDistanceDFSMem(s, t, mem, n, m); cout << s << " を " << t << " に変更するには最小で " << res << " 回の編集が必要\n"; // 動的計画法 res = editDistanceDP(s, t); cout << s << " を " << t << " に変更するには最小で " << res << " 回の編集が必要\n"; // 空間最適化後の動的計画法 res = editDistanceDPComp(s, t); cout << s << " を " << t << " に変更するには最小で " << res << " 回の編集が必要\n"; return 0; } ================================================ FILE: ja/codes/cpp/chapter_dynamic_programming/knapsack.cpp ================================================ #include #include #include using namespace std; /* 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 knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 || c == 0) { return 0; } // 既に記録があればそのまま返す if (mem[i][c] != -1) { return mem[i][c]; } // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する int no = knapsackDFSMem(wgt, val, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 2 つの案のうち価値が大きい方を記録して返す mem[i][c] = max(no, yes); return mem[i][c]; } /* 0-1 ナップサック:動的計画法 */ int knapsackDP(vector &wgt, vector &val, int cap) { int n = wgt.size(); // dp テーブルを初期化 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]; } /* Driver Code */ int main() { vector wgt = {10, 20, 30, 40, 50}; vector val = {50, 120, 150, 210, 240}; int cap = 50; int n = wgt.size(); // 全探索 int res = knapsackDFS(wgt, val, n, cap); cout << "ナップサック容量を超えない最大価値は " << res << endl; // メモ化探索 vector> mem(n + 1, vector(cap + 1, -1)); res = knapsackDFSMem(wgt, val, mem, n, cap); cout << "ナップサック容量を超えない最大価値は " << res << endl; // 動的計画法 res = knapsackDP(wgt, val, cap); cout << "ナップサック容量を超えない最大価値は " << res << endl; // 空間最適化後の動的計画法 res = knapsackDPComp(wgt, val, cap); cout << "ナップサック容量を超えない最大価値は " << res << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; cout << "入力された階段コストのリストは "; printVector(cost); int res = minCostClimbingStairsDP(cost); cout << "階段を上り切る最小コストは " << res << endl; res = minCostClimbingStairsDPComp(cost); cout << "階段を上り切る最小コストは " << res << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp ================================================ /** * 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) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; } /* 最小経路和:メモ化探索 */ int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) { // 左上のセルなら探索を終了する if (i == 0 && j == 0) { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return INT_MAX; } // 既に記録があればそのまま返す if (mem[i][j] != -1) { return mem[i][j]; } // 左と上のセルからの最小経路コスト int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // 左上から (i, j) までの最小経路コストを記録して返す mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; return mem[i][j]; } /* 最小経路和:動的計画法 */ int minPathSumDP(vector> &grid) { int n = grid.size(), m = grid[0].size(); // dp テーブルを初期化 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 minPathSumDPComp(vector> &grid) { int n = grid.size(), m = grid[0].size(); // dp テーブルを初期化 vector dp(m); // 状態遷移:先頭行 dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 状態遷移:残りの行 for (int i = 1; i < n; i++) { // 状態遷移:先頭列 dp[0] = dp[0] + grid[i][0]; // 状態遷移:残りの列 for (int j = 1; j < m; j++) { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ int main() { vector> grid = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; int n = grid.size(), m = grid[0].size(); // 全探索 int res = minPathSumDFS(grid, n - 1, m - 1); cout << "左上から右下までの最小経路和は " << res << endl; // メモ化探索 vector> mem(n, vector(m, -1)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); cout << "左上から右下までの最小経路和は " << res << endl; // 動的計画法 res = minPathSumDP(grid); cout << "左上から右下までの最小経路和は " << res << endl; // 空間最適化後の動的計画法 res = minPathSumDPComp(grid); cout << "左上から右下までの最小経路和は " << res << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp ================================================ /** * 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]; } /* Driver code */ int main() { vector wgt = {1, 2, 3}; vector val = {5, 11, 15}; int cap = 4; // 動的計画法 int res = unboundedKnapsackDP(wgt, val, cap); cout << "ナップサック容量を超えない最大価値は " << res << endl; // 空間最適化後の動的計画法 res = unboundedKnapsackDPComp(wgt, val, cap); cout << "ナップサック容量を超えない最大価値は " << res << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_graph/CMakeLists.txt ================================================ add_executable(graph_bfs graph_bfs.cpp) add_executable(graph_dfs graph_dfs.cpp) # add_executable(graph_adjacency_list graph_adjacency_list.cpp) add_executable(graph_adjacency_list_test graph_adjacency_list_test.cpp) add_executable(graph_adjacency_matrix graph_adjacency_matrix.cpp) ================================================ FILE: ja/codes/cpp/chapter_graph/graph_adjacency_list.cpp ================================================ /** * 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: // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 unordered_map> adjList; /* vector 内の指定ノードを削除 */ 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("頂点が存在しません"); // 辺 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("頂点が存在しません"); // 辺 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("頂点が存在しません"); // 隣接リストから頂点 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` を参照 ================================================ FILE: ja/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp ================================================ /** * File: graph_adjacency_list_test.cpp * Created Time: 2023-02-09 * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) */ #include "./graph_adjacency_list.cpp" /* Driver Code */ int main() { /* 無向グラフを初期化 */ vector v = valsToVets(vector{1, 3, 2, 5, 4}); vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; GraphAdjList graph(edges); cout << "\n初期化後、グラフは" << endl; graph.print(); /* 辺を追加 */ // 頂点 1, 2 は v[0], v[2] graph.addEdge(v[0], v[2]); cout << "\n辺 1-2 を追加した後、グラフは" << endl; graph.print(); /* 辺を削除 */ // 頂点 1, 3 は v[0], v[1] graph.removeEdge(v[0], v[1]); cout << "\n辺 1-3 を削除した後、グラフは" << endl; graph.print(); /* 頂点を追加 */ Vertex *v5 = new Vertex(6); graph.addVertex(v5); cout << "\n頂点 6 を追加した後、グラフは" << endl; graph.print(); /* 頂点を削除 */ // 頂点 3 は v[1] graph.removeVertex(v[1]); cout << "\n頂点 3 を削除した後、グラフは" << endl; graph.print(); // メモリを解放する for (Vertex *vet : v) { delete vet; } return 0; } ================================================ FILE: ja/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp ================================================ /** * 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); } // 辺を追加 // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する 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); // 隣接行列に 1 行追加 adjMat.emplace_back(vector(n, 0)); // 隣接行列に 1 列追加 for (vector &row : adjMat) { row.push_back(0); } } /* 頂点を削除 */ void removeVertex(int index) { if (index >= size()) { throw out_of_range("頂点が存在しません"); } // 頂点リストから index の頂点を削除する vertices.erase(vertices.begin() + index); // 隣接行列で index 行を削除する adjMat.erase(adjMat.begin() + index); // 隣接行列で index 列を削除する for (vector &row : adjMat) { row.erase(row.begin() + index); } } /* 辺を追加 */ // 引数 i, j は vertices の要素インデックスに対応する void addEdge(int i, int j) { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw out_of_range("頂点が存在しません"); } // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす adjMat[i][j] = 1; adjMat[j][i] = 1; } /* 辺を削除 */ // 引数 i, j は vertices の要素インデックスに対応する void removeEdge(int i, int j) { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw out_of_range("頂点が存在しません"); } adjMat[i][j] = 0; adjMat[j][i] = 0; } /* 隣接行列を出力 */ void print() { cout << "頂点リスト = "; printVector(vertices); cout << "隣接行列 =" << endl; printVectorMatrix(adjMat); } }; /* Driver Code */ int main() { /* 無向グラフを初期化 */ // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 vector vertices = {1, 3, 2, 5, 4}; vector> edges = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; GraphAdjMat graph(vertices, edges); 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; } ================================================ FILE: ja/codes/cpp/chapter_graph/graph_bfs.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_graph/graph_dfs.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_greedy/CMakeLists.txt ================================================ add_executable(coin_change_greedy coin_change_greedy.cpp) add_executable(fractional_knapsack fractional_knapsack.cpp) add_executable(max_capacity max_capacity.cpp) ================================================ FILE: ja/codes/cpp/chapter_greedy/coin_change_greedy.cpp ================================================ /** * 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) { // coins リストはソート済みと仮定する 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; } /* Driver Code */ int main() { // 貪欲法:大域最適解を保証できる vector coins = {1, 5, 10, 20, 50, 100}; int amt = 186; int res = coinChangeGreedy(coins, amt); cout << "\ncoins = "; printVector(coins); cout << "amt = " << amt << endl; cout << amt << " を作るのに必要な最小硬貨枚数は " << res << endl; // 貪欲法:大域最適解を保証できない 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; } ================================================ FILE: ja/codes/cpp/chapter_greedy/fractional_knapsack.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector wgt = {10, 20, 30, 40, 50}; vector val = {50, 120, 150, 210, 240}; int cap = 50; // 貪欲法 double res = fractionalKnapsack(wgt, val, cap); cout << "ナップサック容量を超えない最大価値は " << res << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_greedy/max_capacity.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector ht = {3, 8, 5, 2, 7, 7, 3, 4}; // 貪欲法 int res = maxCapacity(ht); cout << "最大容量は " << res << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_greedy/max_product_cutting.cpp ================================================ /** * 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); } /* Driver Code */ int main() { int n = 58; // 貪欲法 int res = maxProductCutting(n); cout << "最大分割積は" << res << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_hashing/CMakeLists.txt ================================================ add_executable(hash_map hash_map.cpp) add_executable(array_hash_map_test array_hash_map_test.cpp) add_executable(hash_map_chaining hash_map_chaining.cpp) add_executable(hash_map_open_addressing hash_map_open_addressing.cpp) add_executable(simple_hash simple_hash.cpp) add_executable(built_in_hash built_in_hash.cpp) ================================================ FILE: ja/codes/cpp/chapter_hashing/array_hash_map.cpp ================================================ /** * 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` を参照 ================================================ FILE: ja/codes/cpp/chapter_hashing/array_hash_map_test.cpp ================================================ /** * File: array_hash_map_test.cpp * Created Time: 2022-12-14 * Author: msk397 (machangxinq@gmail.com) */ #include "./array_hash_map.cpp" /* Driver Code */ int main() { /* ハッシュテーブルを初期化 */ ArrayHashMap map = ArrayHashMap(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.put(12836, "シャオハー"); map.put(15937, "シャオルオ"); map.put(16750, "シャオスワン"); map.put(13276, "シャオファー"); map.put(10583, "シャオヤー"); cout << "\n追加完了後、ハッシュテーブルは\nKey -> Value" << endl; map.print(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 string name = map.get(15937); cout << "\n学籍番号 15937 を入力すると、氏名 " << name << endl; /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(10583); cout << "\n10583 を削除した後、ハッシュテーブルは\nKey -> Value" << endl; map.print(); /* ハッシュテーブルを走査 */ cout << "\nキーと値のペア Key->Value を走査" << endl; for (auto kv : map.pairSet()) { cout << kv->key << " -> " << kv->val << endl; } cout << "\nキー Key のみを走査" << endl; for (auto key : map.keySet()) { cout << key << endl; } cout << "\n値 Value のみを走査" << endl; for (auto val : map.valueSet()) { cout << val << endl; } return 0; } ================================================ FILE: ja/codes/cpp/chapter_hashing/built_in_hash.cpp ================================================ /** * File: built_in_hash.cpp * Created Time: 2023-06-21 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { int num = 3; size_t hashNum = hash()(num); cout << "整数 " << num << " のハッシュ値は " << hashNum << "\n"; bool bol = true; size_t hashBol = hash()(bol); cout << "真偽値 " << bol << " のハッシュ値は " << hashBol << "\n"; double dec = 3.14159; size_t hashDec = hash()(dec); cout << "小数 " << dec << " のハッシュ値は " << hashDec << "\n"; string str = "Hello アルゴリズム"; size_t hashStr = hash()(str); cout << "文字列 " << str << " のハッシュ値は " << hashStr << "\n"; // C++ では、組み込みの std::hash() は基本データ型のハッシュ値計算しか提供しない // 配列やオブジェクトのハッシュ値計算は自分で実装する必要がある } ================================================ FILE: ja/codes/cpp/chapter_hashing/hash_map.cpp ================================================ /** * File: hash_map.cpp * Created Time: 2022-12-14 * Author: msk397 (machangxinq@gmail.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* ハッシュテーブルを初期化 */ unordered_map map; /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map[12836] = "シャオハー"; map[15937] = "シャオルオ"; map[16750] = "シャオスワン"; map[13276] = "シャオファー"; map[10583] = "シャオヤー"; cout << "\n追加完了後、ハッシュテーブルは\nKey -> Value" << endl; printHashMap(map); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 string name = map[15937]; cout << "\n学籍番号 15937 を入力すると、氏名 " << name << endl; /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.erase(10583); cout << "\n10583 を削除した後、ハッシュテーブルは\nKey -> Value" << endl; printHashMap(map); /* ハッシュテーブルを走査 */ cout << "\nキーと値のペア Key->Value を走査" << endl; for (auto kv : map) { cout << kv.first << " -> " << kv.second << endl; } cout << "\nイテレータで Key->Value を走査" << endl; for (auto iter = map.begin(); iter != map.end(); iter++) { cout << iter->first << "->" << iter->second << endl; } return 0; } ================================================ FILE: ja/codes/cpp/chapter_hashing/hash_map_chaining.cpp ================================================ /** * 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); // バケットを走査し、key が見つかれば対応する val を返す for (Pair *pair : buckets[index]) { if (pair->key == key) { return pair->val; } } // key が見つからない場合は空文字列を返す return ""; } /* 追加操作 */ void put(int key, string val) { // 負荷率がしきい値を超えたら、リサイズを実行 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); // バケットを走査し、指定した key が見つかれば対応する val を更新して返す for (Pair *pair : buckets[index]) { if (pair->key == key) { pair->val = val; return; } } // その key が存在しなければ、キーと値のペアを末尾に追加 buckets[index].push_back(new Pair(key, val)); size++; } /* 削除操作 */ 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"; } } }; /* Driver Code */ int main() { /* ハッシュテーブルを初期化 */ HashMapChaining map = HashMapChaining(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.put(12836, "シャオハー"); map.put(15937, "シャオルオ"); map.put(16750, "シャオスワン"); map.put(13276, "シャオファー"); map.put(10583, "シャオヤー"); cout << "\n追加完了後、ハッシュテーブルは\nKey -> Value" << endl; map.print(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 string name = map.get(13276); cout << "\n学籍番号 13276 を入力すると、氏名 " << name << endl; /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(12836); cout << "\n12836 を削除した後、ハッシュテーブルは\nKey -> Value" << endl; map.print(); return 0; } ================================================ FILE: ja/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp ================================================ /** * 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; } } } }; /* Driver Code */ int main() { // ハッシュテーブルを初期化 HashMapOpenAddressing hashmap; // 追加操作 // ハッシュテーブルにキーと値の組 (key, val) を追加する hashmap.put(12836, "シャオハー"); hashmap.put(15937, "シャオルオ"); hashmap.put(16750, "シャオスワン"); hashmap.put(13276, "シャオファー"); hashmap.put(10583, "シャオヤー"); cout << "\n追加完了後、ハッシュテーブルは\nKey -> Value" << endl; hashmap.print(); // 検索操作 // ハッシュテーブルにキー key を入力し、値 val を得る string name = hashmap.get(13276); cout << "\n学籍番号 13276 を入力すると、氏名 " << name << endl; // 削除操作 // ハッシュテーブルからキーと値の組 (key, val) を削除する hashmap.remove(16750); cout << "\n16750 を削除した後、ハッシュテーブルは\nKey -> Value" << endl; hashmap.print(); return 0; } ================================================ FILE: ja/codes/cpp/chapter_hashing/simple_hash.cpp ================================================ /** * 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; } /* Driver Code */ int main() { string key = "Hello アルゴリズム"; int hash = addHash(key); cout << "加算ハッシュ値は " << hash << endl; hash = mulHash(key); cout << "乗算ハッシュ値は " << hash << endl; hash = xorHash(key); cout << "XORハッシュ値は " << hash << endl; hash = rotHash(key); cout << "回転ハッシュ値は " << hash << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_heap/CMakeLists.txt ================================================ add_executable(heap heap.cpp) add_executable(my_heap my_heap.cpp) add_executable(top_k top_k.cpp) ================================================ FILE: ja/codes/cpp/chapter_heap/heap.cpp ================================================ /** * 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); } /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_heap/my_heap.cpp ================================================ /** * 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("ヒープが空です"); } // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) 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); } }; /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_heap/top_k.cpp ================================================ /** * 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; } // Driver Code 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; } ================================================ FILE: ja/codes/cpp/chapter_searching/CMakeLists.txt ================================================ add_executable(binary_search binary_search.cpp) add_executable(binary_search_insertion binary_search_insertion.cpp) add_executable(binary_search_edge binary_search_edge.cpp) add_executable(two_sum two_sum.cpp) ================================================ FILE: ja/codes/cpp/chapter_searching/binary_search.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int target = 6; vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; /* 二分探索(両閉区間) */ int index = binarySearch(nums, target); cout << "対象要素 6 のインデックス = " << index << endl; /* 二分探索(左閉右開区間) */ index = binarySearchLCRO(nums, target); cout << "対象要素 6 のインデックス = " << index << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_searching/binary_search_edge.cpp ================================================ /** * 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; // target は区間 [m+1, j] にある } else { j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある } } // 挿入位置 i を返す return i; } /* 最も左の target を二分探索 */ 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; } /* 最も右の target を二分探索 */ int binarySearchRightEdge(vector &nums, int target) { // 最左の target + 1 を探す問題に変換する int i = binarySearchInsertion(nums, target + 1); // j は最も右の target を指し、i は target より大きい最初の要素を指す int j = i - 1; // target が見つからなければ、-1 を返す if (j == -1 || nums[j] != target) { return -1; } // target が見つかったら、インデックス j を返す return j; } /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_searching/binary_search_insertion.cpp ================================================ /** * 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; // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1; // target は区間 [i, m-1] にある } else { return m; // target が見つかったら、挿入位置 m を返す } } // target が見つからなければ、挿入位置 i を返す return i; } /* 二分探索で挿入位置を探す(重複要素あり) */ int binarySearchInsertion(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; // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1; // target は区間 [i, m-1] にある } else { j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある } } // 挿入位置 i を返す return i; } /* Driver Code */ int main() { // 重複要素のない配列 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; } ================================================ FILE: ja/codes/cpp/chapter_searching/hashing_search.cpp ================================================ /** * 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) { // ハッシュテーブルの key: 目標要素、value: インデックス // ハッシュテーブルにこの key がなければ -1 を返す if (map.find(target) == map.end()) return -1; return map[target]; } /* ハッシュ探索(連結リスト) */ ListNode *hashingSearchLinkedList(unordered_map map, int target) { // ハッシュテーブルの key: 対象ノードの値、value: ノードオブジェクト // ハッシュテーブルにその key がなければ nullptr を返す if (map.find(target) == map.end()) return nullptr; return map[target]; } /* Driver Code */ 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; // key: 要素、value: インデックス } int index = hashingSearchArray(map, target); cout << "対象要素 3 のインデックス = " << index << endl; /* ハッシュ探索(連結リスト) */ ListNode *head = vecToLinkedList(nums); // ハッシュテーブルを初期化 unordered_map map1; while (head != nullptr) { map1[head->val] = head; // key: ノード値、value: ノード head = head->next; } ListNode *node = hashingSearchLinkedList(map1, target); cout << "対象ノード値 3 に対応するノードオブジェクトは " << node << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_searching/linear_search.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int target = 3; /* 配列で線形探索を行う */ vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; int index = linearSearchArray(nums, target); cout << "対象要素 3 のインデックス = " << index << endl; /* 連結リストで線形探索を行う */ ListNode *head = vecToLinkedList(nums); ListNode *node = linearSearchLinkedList(head, target); cout << "対象ノード値 3 に対応するノードオブジェクトは " << node << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_searching/two_sum.cpp ================================================ /** * File: two_sum.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* 方法 1:総当たり列挙 */ vector twoSumBruteForce(vector &nums, int target) { int size = nums.size(); // 2重ループのため、時間計算量は O(n^2) for (int i = 0; i < size - 1; i++) { for (int j = i + 1; j < size; j++) { if (nums[i] + nums[j] == target) return {i, j}; } } return {}; } /* 方法 2:補助ハッシュテーブル */ 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 {}; } /* Driver Code */ int main() { // ======= Test Case ======= vector nums = {2, 7, 11, 15}; int target = 13; // ====== Driver Code ====== // 方法 1 vector res = twoSumBruteForce(nums, target); cout << "方法1 res = "; printVector(res); // 方法 2 res = twoSumHashTable(nums, target); cout << "方法2 res = "; printVector(res); return 0; } ================================================ FILE: ja/codes/cpp/chapter_sorting/CMakeLists.txt ================================================ add_executable(selection_sort selection_sort.cpp) add_executable(bubble_sort bubble_sort.cpp) add_executable(insertion_sort insertion_sort.cpp) add_executable(merge_sort merge_sort.cpp) add_executable(quick_sort quick_sort.cpp) add_executable(heap_sort heap_sort.cpp) ================================================ FILE: ja/codes/cpp/chapter_sorting/bubble_sort.cpp ================================================ /** * 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; // このバブル処理で要素交換が一度もなければそのまま終了 } } /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_sorting/bucket_sort.cpp ================================================ /** * 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; // num をバケット 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; } } } /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_sorting/counting_sort.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_sorting/heap_sort.cpp ================================================ /** * 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; } // 2 つのノードを交換 swap(nums[i], nums[ma]); // ループで上から下へヒープ化 i = ma; } } /* ヒープソート */ void heapSort(vector &nums) { // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する for (int i = nums.size() / 2 - 1; i >= 0; --i) { siftDown(nums, nums.size(), i); } // ヒープから最大要素を取り出し、n-1 回繰り返す for (int i = nums.size() - 1; i > 0; --i) { // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) swap(nums[0], nums[i]); // 根ノードを起点に、上から下へヒープ化 siftDown(nums, i, 0); } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; heapSort(nums); cout << "ヒープソート完了後 nums = "; printVector(nums); return 0; } ================================================ FILE: ja/codes/cpp/chapter_sorting/insertion_sort.cpp ================================================ /** * 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] を 1 つ右へ移動する j--; } nums[j + 1] = base; // base を正しい位置に配置する } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; insertionSort(nums); cout << "挿入ソート完了後 nums = "; printVector(nums); return 0; } ================================================ FILE: ja/codes/cpp/chapter_sorting/merge_sort.cpp ================================================ /** * 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); } /* Driver Code */ int main() { /* マージソート */ vector nums = {7, 3, 2, 6, 0, 1, 5, 4}; mergeSort(nums, 0, nums.size() - 1); cout << "マージソート完了後 nums = "; printVector(nums); return 0; } ================================================ FILE: ja/codes/cpp/chapter_sorting/quick_sort.cpp ================================================ /** * File: quick_sort.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* クイックソートクラス */ class QuickSort { private: /* 番兵分割 */ 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], nums[j]); // この 2 つの要素を交換 } swap(nums[i], nums[left]); // 基準値を 2 つの部分配列の境界へ交換する 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: /* 3つの候補要素の中央値を選ぶ */ static int medianThree(vector &nums, int left, int mid, int right) { int l = nums[left], m = nums[mid], r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // m は l と r の間 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l は m と r の間 return right; } /* 番兵による分割処理(3 点中央値) */ static int partition(vector &nums, int left, int right) { // 3つの候補要素の中央値を選ぶ int med = medianThree(nums, left, (left + right) / 2, right); // 中央値を配列の最左端に交換する swap(nums[left], nums[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], nums[j]); // この 2 つの要素を交換 } swap(nums[i], nums[left]); // 基準値を 2 つの部分配列の境界へ交換する 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 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], nums[j]); // この 2 つの要素を交換 } swap(nums[i], nums[left]); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } public: /* クイックソート(再帰深度最適化) */ static void quickSort(vector &nums, int left, int right) { // 部分配列の長さが 1 なら終了 while (left < right) { // 番兵による分割処理 int pivot = partition(nums, left, right); // 2 つの部分配列のうち短いほうにクイックソートを適用する if (pivot - left < right - pivot) { quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] } else { quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] } } } }; /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_sorting/radix_sort.cpp ================================================ /** * 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); } /* Driver Code */ int main() { // 基数ソート vector nums = {10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996}; radixSort(nums); cout << "基数ソート完了後 nums = "; printVector(nums); return 0; } ================================================ FILE: ja/codes/cpp/chapter_sorting/selection_sort.cpp ================================================ /** * 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]); } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; selectionSort(nums); cout << "選択ソート完了後 nums = "; printVector(nums); return 0; } ================================================ FILE: ja/codes/cpp/chapter_stack_and_queue/CMakeLists.txt ================================================ add_executable(array_deque array_deque.cpp) add_executable(array_queue array_queue.cpp) add_executable(array_stack array_stack.cpp) add_executable(deque deque.cpp) add_executable(linkedlist_deque linkedlist_deque.cpp) add_executable(linkedlist_queue linkedlist_queue.cpp) add_executable(linkedlist_stack linkedlist_stack.cpp) add_executable(queue queue.cpp) add_executable(stack stack.cpp) ================================================ FILE: ja/codes/cpp/chapter_stack_and_queue/array_deque.cpp ================================================ /** * 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 << "両端キューがいっぱいです" << endl; return; } // 先頭ポインタを左に 1 つ移動する // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする front = index(front - 1); // num をキュー先頭に追加 nums[front] = num; queSize++; } /* キュー末尾にエンキュー */ void pushLast(int num) { if (queSize == capacity()) { cout << "両端キューがいっぱいです" << 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("両端キューが空です"); return nums[front]; } /* キュー末尾の要素にアクセス */ int peekLast() { if (isEmpty()) throw out_of_range("両端キューが空です"); // 末尾要素のインデックスを計算 int last = index(front + queSize - 1); return nums[last]; } /* 出力用の配列を返す */ vector toVector() { // 有効長の範囲内のリスト要素のみを変換 vector res(queSize); for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[index(j)]; } return res; } }; /* Driver Code */ int main() { /* 両端キューを初期化 */ ArrayDeque *deque = new ArrayDeque(10); deque->pushLast(3); deque->pushLast(2); deque->pushLast(5); cout << "両端キュー deque = "; printVector(deque->toVector()); /* 要素にアクセス */ int peekFirst = deque->peekFirst(); cout << "先頭要素 peekFirst = " << peekFirst << endl; int peekLast = deque->peekLast(); cout << "末尾要素 peekLast = " << peekLast << endl; /* 要素をエンキュー */ deque->pushLast(4); cout << "要素 4 を末尾に追加した後 deque = "; printVector(deque->toVector()); deque->pushFirst(1); cout << "要素 1 を先頭に追加した後 deque = "; printVector(deque->toVector()); /* 要素をデキュー */ int popLast = deque->popLast(); cout << "末尾から取り出した要素 = " << popLast << "、末尾から取り出した後 deque = "; printVector(deque->toVector()); int popFirst = deque->popFirst(); cout << "先頭から取り出した要素 = " << popFirst << "、先頭から取り出した後 deque = "; printVector(deque->toVector()); /* 両端キューの長さを取得 */ int size = deque->size(); cout << "両端キューの長さ size = " << size << endl; /* 両端キューが空かどうかを判定 */ bool isEmpty = deque->isEmpty(); cout << "両端キューが空かどうか = " << boolalpha << isEmpty << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_stack_and_queue/array_queue.cpp ================================================ /** * 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 << "キューがいっぱいです" << endl; return; } // 末尾ポインタを計算し、末尾インデックス + 1 を指す // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする 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("キューが空です"); 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; } }; /* Driver Code */ int main() { /* キューを初期化 */ int capacity = 10; ArrayQueue *queue = new ArrayQueue(capacity); /* 要素をエンキュー */ queue->push(1); queue->push(3); queue->push(2); queue->push(5); queue->push(4); cout << "キュー queue = "; printVector(queue->toVector()); /* キュー先頭の要素にアクセス */ int peek = queue->peek(); cout << "先頭要素 peek = " << peek << endl; /* 要素をデキュー */ peek = queue->pop(); cout << "取り出した要素 pop = " << peek << "、取り出し後の queue = "; printVector(queue->toVector()); /* キューの長さを取得 */ int size = queue->size(); cout << "キューの長さ size = " << size << endl; /* キューが空かどうかを判定 */ bool empty = queue->isEmpty(); cout << "キューが空かどうか = " << empty << endl; /* 循環配列をテストする */ for (int i = 0; i < 10; i++) { queue->push(i); queue->pop(); cout << "第 " << i << " 回のエンキュー + デキュー後の queue = "; printVector(queue->toVector()); } // メモリを解放する delete queue; return 0; } ================================================ FILE: ja/codes/cpp/chapter_stack_and_queue/array_stack.cpp ================================================ /** * 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("スタックが空です"); return stack.back(); } /* Vector を返す */ vector toVector() { return stack; } }; /* Driver Code */ int main() { /* スタックを初期化 */ ArrayStack *stack = new ArrayStack(); /* 要素をプッシュ */ stack->push(1); stack->push(3); stack->push(2); stack->push(5); stack->push(4); cout << "スタック stack = "; printVector(stack->toVector()); /* スタックトップの要素にアクセス */ int top = stack->top(); cout << "トップ要素 top = " << top << endl; /* 要素をポップ */ top = stack->pop(); cout << "取り出した要素 pop = " << top << "、取り出し後の stack = "; printVector(stack->toVector()); /* スタックの長さを取得 */ int size = stack->size(); cout << "スタックの長さ size = " << size << endl; /* 空かどうかを判定 */ bool empty = stack->isEmpty(); cout << "スタックが空かどうか = " << empty << endl; // メモリを解放する delete stack; return 0; } ================================================ FILE: ja/codes/cpp/chapter_stack_and_queue/deque.cpp ================================================ /** * File: deque.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* 両端キューを初期化 */ deque deque; /* 要素をエンキュー */ deque.push_back(2); deque.push_back(5); deque.push_back(4); deque.push_front(3); deque.push_front(1); cout << "両端キュー deque = "; printDeque(deque); /* 要素にアクセス */ int front = deque.front(); cout << "先頭要素 front = " << front << endl; int back = deque.back(); cout << "末尾要素 back = " << back << endl; /* 要素をデキュー */ deque.pop_front(); cout << "先頭から取り出した要素 popFront = " << front << "、先頭から取り出した後の deque = "; printDeque(deque); deque.pop_back(); cout << "末尾から取り出した要素 popLast = " << back << "、末尾から取り出した後の deque = "; printDeque(deque); /* 両端キューの長さを取得 */ int size = deque.size(); cout << "両端キューの長さ size = " << size << endl; /* 両端キューが空かどうかを判定 */ bool empty = deque.empty(); cout << "両端キューが空かどうか = " << empty << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp ================================================ /** * 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) { // node を連結リストの先頭に追加 front->prev = node; node->next = front; front = node; // 先頭ノードを更新する // 末尾へのエンキュー操作 } else { // node を連結リストの末尾に追加 rear->next = node; node->prev = rear; rear = node; // 末尾ノードを更新する } queSize++; // キューの長さを更新 } /* キュー先頭にエンキュー */ void pushFirst(int num) { push(num, true); } /* キュー末尾にエンキュー */ void pushLast(int num) { push(num, false); } /* デキュー操作 */ int pop(bool isFront) { if (isEmpty()) throw out_of_range("キューが空です"); 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("両端キューが空です"); return front->val; } /* キュー末尾の要素にアクセス */ int peekLast() { if (isEmpty()) throw out_of_range("両端キューが空です"); 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; } }; /* Driver Code */ int main() { /* 両端キューを初期化 */ LinkedListDeque *deque = new LinkedListDeque(); deque->pushLast(3); deque->pushLast(2); deque->pushLast(5); cout << "両端キュー deque = "; printVector(deque->toVector()); /* 要素にアクセス */ int peekFirst = deque->peekFirst(); cout << "先頭要素 peekFirst = " << peekFirst << endl; int peekLast = deque->peekLast(); cout << "末尾要素 peekLast = " << peekLast << endl; /* 要素をエンキュー */ deque->pushLast(4); cout << "要素 4 を末尾に追加した後の deque ="; printVector(deque->toVector()); deque->pushFirst(1); cout << "要素 1 を先頭に追加した後 deque = "; printVector(deque->toVector()); /* 要素をデキュー */ int popLast = deque->popLast(); cout << "末尾から取り出した要素 = " << popLast << "、末尾から取り出した後 deque = "; printVector(deque->toVector()); int popFirst = deque->popFirst(); cout << "先頭から取り出した要素 = " << popFirst << "、先頭から取り出した後 deque = "; printVector(deque->toVector()); /* 両端キューの長さを取得 */ int size = deque->size(); cout << "両端キューの長さ size = " << size << endl; /* 両端キューが空かどうかを判定 */ bool isEmpty = deque->isEmpty(); cout << "両端キューが空かどうか = " << boolalpha << isEmpty << endl; // メモリを解放する delete deque; return 0; } ================================================ FILE: ja/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp ================================================ /** * 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("キューが空です"); 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; } }; /* Driver Code */ int main() { /* キューを初期化 */ LinkedListQueue *queue = new LinkedListQueue(); /* 要素をエンキュー */ queue->push(1); queue->push(3); queue->push(2); queue->push(5); queue->push(4); cout << "キュー queue = "; printVector(queue->toVector()); /* キュー先頭の要素にアクセス */ int peek = queue->peek(); cout << "先頭要素 peek = " << peek << endl; /* 要素をデキュー */ peek = queue->pop(); cout << "取り出した要素 pop = " << peek << "、取り出し後の queue = "; printVector(queue->toVector()); /* キューの長さを取得 */ int size = queue->size(); cout << "キューの長さ size = " << size << endl; /* キューが空かどうかを判定 */ bool empty = queue->isEmpty(); cout << "キューが空かどうか = " << empty << endl; // メモリを解放する delete queue; return 0; } ================================================ FILE: ja/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp ================================================ /** * 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("スタックが空です"); return stackTop->val; } /* List を Array に変換して返す */ 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; } }; /* Driver Code */ int main() { /* スタックを初期化 */ LinkedListStack *stack = new LinkedListStack(); /* 要素をプッシュ */ stack->push(1); stack->push(3); stack->push(2); stack->push(5); stack->push(4); cout << "スタック stack = "; printVector(stack->toVector()); /* スタックトップの要素にアクセス */ int top = stack->top(); cout << "トップ要素 top = " << top << endl; /* 要素をポップ */ top = stack->pop(); cout << "取り出した要素 pop = " << top << "、取り出し後の stack = "; printVector(stack->toVector()); /* スタックの長さを取得 */ int size = stack->size(); cout << "スタックの長さ size = " << size << endl; /* 空かどうかを判定 */ bool empty = stack->isEmpty(); cout << "スタックが空かどうか = " << empty << endl; // メモリを解放する delete stack; return 0; } ================================================ FILE: ja/codes/cpp/chapter_stack_and_queue/queue.cpp ================================================ /** * File: queue.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* キューを初期化 */ queue queue; /* 要素をエンキュー */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); cout << "キュー queue = "; printQueue(queue); /* キュー先頭の要素にアクセス */ int front = queue.front(); cout << "先頭要素 front = " << front << endl; /* 要素をデキュー */ queue.pop(); cout << "取り出した要素 front = " << front << "、取り出し後の queue = "; printQueue(queue); /* キューの長さを取得 */ int size = queue.size(); cout << "キューの長さ size = " << size << endl; /* キューが空かどうかを判定 */ bool empty = queue.empty(); cout << "キューが空かどうか = " << empty << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_stack_and_queue/stack.cpp ================================================ /** * File: stack.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* スタックを初期化 */ stack stack; /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); cout << "スタック stack = "; printStack(stack); /* スタックトップの要素にアクセス */ int top = stack.top(); cout << "トップ要素 top = " << top << endl; /* 要素をポップ */ stack.pop(); // 戻り値なし cout << "取り出した要素 pop = " << top << "、取り出し後の stack = "; printStack(stack); /* スタックの長さを取得 */ int size = stack.size(); cout << "スタックの長さ size = " << size << endl; /* 空かどうかを判定 */ bool empty = stack.empty(); cout << "スタックが空かどうか = " << empty << endl; return 0; } ================================================ FILE: ja/codes/cpp/chapter_tree/CMakeLists.txt ================================================ add_executable(avl_tree avl_tree.cpp) add_executable(binary_search_tree binary_search_tree.cpp) add_executable(binary_tree binary_tree.cpp) add_executable(binary_tree_bfs binary_tree_bfs.cpp) add_executable(binary_tree_dfs binary_tree_dfs.cpp) add_executable(array_binary_tree array_binary_tree.cpp) ================================================ FILE: ja/codes/cpp/chapter_tree/array_binary_tree.cpp ================================================ /** * 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 を返す 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)); } }; /* Driver Code */ int main() { // 二分木を初期化する // 空き位置 nullptr は INT_MAX で表す vector arr = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; TreeNode *root = vectorToTree(arr); cout << "\n二分木を初期化\n"; cout << "二分木の配列表現:\n"; 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 << "、値は " << (abt.val(l) != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n"; cout << "右の子ノードのインデックスは " << r << "、値は " << (abt.val(r) != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n"; cout << "親ノードのインデックスは " << p << "、値は " << (abt.val(p) != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n"; // 木を走査 vector res = abt.levelOrder(); cout << "\nレベル順走査: "; printVector(res); res = abt.preOrder(); cout << "先行順走査: "; printVector(res); res = abt.inOrder(); cout << "中間順走査: "; printVector(res); res = abt.postOrder(); cout << "後行順走査: "; printVector(res); return 0; } ================================================ FILE: ja/codes/cpp/chapter_tree/avl_tree.cpp ================================================ /** * 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 の場合、node をそのまま削除して返す if (child == nullptr) { delete node; return nullptr; } // 子ノード数 = 1 の場合、node をそのまま削除する 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); } /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_tree/binary_search_tree.cpp ================================================ /** * 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 or 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); // tmp で cur を上書きする cur->val = tmpVal; } } }; /* Driver Code */ int main() { /* 二分探索木を初期化 */ BinarySearchTree *bst = new BinarySearchTree(); // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる vector nums = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; for (int num : nums) { bst->insert(num); } cout << endl << "初期化した二分木は\n" << endl; 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; } ================================================ FILE: ja/codes/cpp/chapter_tree/binary_tree.cpp ================================================ /** * File: binary_tree.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ 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; } ================================================ FILE: ja/codes/cpp/chapter_tree/binary_tree_bfs.cpp ================================================ /** * 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; } /* Driver Code */ int main() { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "二分木を初期化\n" << endl; printTree(root); /* レベル順走査 */ vector vec = levelOrder(root); cout << endl << "レベル順走査のノード出力列 = "; printVector(vec); return 0; } ================================================ FILE: ja/codes/cpp/chapter_tree/binary_tree_dfs.cpp ================================================ /** * 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); } /* Driver Code */ int main() { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "二分木を初期化\n" << endl; printTree(root); /* 先行順走査 */ vec.clear(); preOrder(root); cout << endl << "前順走査のノード出力列 = "; printVector(vec); /* 中順走査 */ vec.clear(); inOrder(root); cout << endl << "中順走査のノード出力列 = "; printVector(vec); /* 後順走査 */ vec.clear(); postOrder(root); cout << endl << "後順走査のノード出力列 = "; printVector(vec); return 0; } ================================================ FILE: ja/codes/cpp/utils/CMakeLists.txt ================================================ add_executable(utils common.hpp print_utils.hpp list_node.hpp tree_node.hpp vertex.hpp) ================================================ FILE: ja/codes/cpp/utils/common.hpp ================================================ /** * 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; ================================================ FILE: ja/codes/cpp/utils/list_node.hpp ================================================ /** * 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; } } ================================================ FILE: ja/codes/cpp/utils/print_utils.hpp ================================================ /** * 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 /* Find an element in a vector */ template int vecFind(const vector &vec, T ele) { int j = INT_MAX; for (int i = 0; i < vec.size(); i++) { if (vec[i] == ele) { j = i; } } return j; } /* Concatenate a vector with a delim */ 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(); } /* Repeat a string for n times */ 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; } /* Get the Vector String object */ template string getVectorString(vector &list) { return "[" + strJoin(", ", list) + "]"; } /* リストを出力する */ template void printVector(vector list) { cout << getVectorString(list) << '\n'; } /* 行列を出力する */ template void printVectorMatrix(vector> &matrix) { cout << "[" << '\n'; for (vector &list : matrix) 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; } /** * 二分木を出力 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTree(TreeNode *root, Trunk *prev, bool isRight) { 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) { // Reverse the input stack stack tmp; while (!stk.empty()) { tmp.push(stk.top()); stk.pop(); } // Generate the string to print ostringstream s; bool flag = true; while (!tmp.empty()) { if (flag) { s << tmp.top(); flag = false; } else s << ", " << tmp.top(); tmp.pop(); } cout << "[" + s.str() + "]" << '\n'; } /* キューを出力する */ template void printQueue(queue queue) { // Generate the string to print 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) { // Generate the string to print 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'; } } /* Expose the underlying storage of the priority_queue container */ template S &Container(priority_queue &pq) { struct HackedQueue : private priority_queue { static S &Container(priority_queue &pq) { 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); } ================================================ FILE: ja/codes/cpp/utils/tree_node.hpp ================================================ /** * 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; } ================================================ FILE: ja/codes/cpp/utils/vertex.hpp ================================================ /** * 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; } ================================================ FILE: ja/codes/csharp/.editorconfig ================================================ # CSharp formatting rules [*.cs] csharp_new_line_before_open_brace = none csharp_new_line_before_else = false csharp_new_line_before_catch = false csharp_new_line_before_finally = false csharp_indent_labels = one_less_than_current csharp_using_directive_placement = outside_namespace:silent csharp_prefer_simple_using_statement = true:suggestion csharp_prefer_braces = true:silent csharp_style_namespace_declarations = block_scoped:silent csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent csharp_style_prefer_primary_constructors = true:suggestion csharp_style_expression_bodied_methods = false:silent csharp_style_expression_bodied_constructors = false:silent csharp_style_expression_bodied_operators = false:silent csharp_style_expression_bodied_properties = true:silent csharp_style_expression_bodied_indexers = true:silent csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_lambdas = true:silent # CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language. dotnet_diagnostic.CS8981.severity = silent # IDE1006: Naming Styles dotnet_diagnostic.IDE1006.severity = silent # CA1822: Mark members as static dotnet_diagnostic.CA1822.severity = silent [*.{cs,vb}] #### Naming styles #### # Naming rules dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion dotnet_naming_rule.types_should_be_pascal_case.symbols = types dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case # Symbol specifications dotnet_naming_symbols.interface.applicable_kinds = interface dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.interface.required_modifiers = dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.types.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.non_field_members.required_modifiers = # Naming styles dotnet_naming_style.begins_with_i.required_prefix = I dotnet_naming_style.begins_with_i.required_suffix = dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_style_operator_placement_when_wrapping = beginning_of_line tab_width = 4 indent_size = 4 end_of_line = crlf # IDE0040: Add accessibility modifiers dotnet_diagnostic.IDE0040.severity = silent # IDE0044: Add readonly modifier dotnet_diagnostic.IDE0044.severity = silent ================================================ FILE: ja/codes/csharp/.gitignore ================================================ .idea/ .vs/ obj/ .Debug bin/ ================================================ FILE: ja/codes/csharp/GlobalUsing.cs ================================================ global using NUnit.Framework; global using hello_algo.utils; global using System.Text; ================================================ FILE: ja/codes/csharp/chapter_array_and_linkedlist/array.cs ================================================ // File: array.cs // Created Time: 2022-12-14 // Author: mingXta (1195669834@qq.com) namespace hello_algo.chapter_array_and_linkedlist; public class array { /* 要素へランダムアクセス */ int RandomAccess(int[] nums) { Random random = new(); // 区間 [0, nums.Length) からランダムに数字を 1 つ選ぶ int randomIndex = random.Next(nums.Length); // ランダムな要素を取得して返す int randomNum = nums[randomIndex]; return randomNum; } /* 配列長を拡張する */ int[] Extend(int[] nums, int enlarge) { // 拡張後の長さを持つ配列を初期化する int[] res = new int[nums.Length + enlarge]; // 元の配列の全要素を新しい配列にコピー for (int i = 0; i < nums.Length; i++) { res[i] = nums[i]; } // 拡張後の新しい配列を返す return res; } /* 配列の index 番目に要素 num を挿入 */ void Insert(int[] nums, int num, int index) { // インデックス index 以降の全要素を 1 つ後ろへ移動する for (int i = nums.Length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // index の要素に num を代入する nums[index] = num; } /* index の要素を削除する */ void Remove(int[] nums, int index) { // インデックス index より後ろの全要素を 1 つ前へ移動する for (int i = index; i < nums.Length - 1; i++) { nums[i] = nums[i + 1]; } } /* 配列を走査 */ void Traverse(int[] nums) { int count = 0; // インデックスで配列を走査 for (int i = 0; i < nums.Length; i++) { count += nums[i]; } // 配列要素を直接走査 foreach (int num in nums) { count += num; } } /* 配列内で指定要素を探す */ int Find(int[] nums, int target) { for (int i = 0; i < nums.Length; i++) { if (nums[i] == target) return i; } return -1; } /* 補助関数:配列を文字列に変換 */ string ToString(int[] nums) { return string.Join(",", nums); } [Test] public void Test() { // 配列を初期化 int[] arr = new int[5]; Console.WriteLine("配列 arr = " + ToString(arr)); int[] nums = [1, 3, 2, 5, 4]; Console.WriteLine("配列 nums = " + ToString(nums)); // ランダムアクセス int randomNum = RandomAccess(nums); Console.WriteLine("nums からランダムな要素を取得 " + randomNum); // 長さを拡張 nums = Extend(nums, 3); Console.WriteLine("配列の長さを 8 まで拡張すると nums = " + ToString(nums)); // 要素を挿入する Insert(nums, 6, 3); Console.WriteLine("インデックス 3 に数値 6 を挿入すると nums = " + ToString(nums)); // 要素を削除 Remove(nums, 2); Console.WriteLine("インデックス 2 の要素を削除すると nums = " + ToString(nums)); // 配列を走査 Traverse(nums); // 要素を探索する int index = Find(nums, 3); Console.WriteLine("nums 内で要素 3 を検索するとインデックス = " + index); } } ================================================ FILE: ja/codes/csharp/chapter_array_and_linkedlist/linked_list.cs ================================================ // File: linked_list.cs // Created Time: 2022-12-16 // Author: mingXta (1195669834@qq.com) namespace hello_algo.chapter_array_and_linkedlist; public class linked_list { /* 連結リストでノード n0 の後ろにノード P を挿入する */ void Insert(ListNode n0, ListNode P) { ListNode? n1 = n0.next; P.next = n1; n0.next = P; } /* 連結リストでノード n0 の直後のノードを削除する */ void Remove(ListNode n0) { if (n0.next == null) return; // n0 -> P -> n1 ListNode P = n0.next; ListNode? n1 = P.next; n0.next = n1; } /* 連結リスト内で index 番目のノードにアクセス */ ListNode? Access(ListNode? head, int index) { for (int i = 0; i < index; i++) { if (head == null) return null; head = head.next; } return head; } /* 連結リストで値が target の最初のノードを探す */ int Find(ListNode? head, int target) { int index = 0; while (head != null) { if (head.val == target) return index; head = head.next; index++; } return -1; } [Test] public void Test() { // 連結リストを初期化する // 各ノードを初期化する ListNode n0 = new(1); ListNode n1 = new(3); ListNode n2 = new(2); ListNode n3 = new(5); ListNode n4 = new(4); // ノード間の参照を構築する n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; Console.WriteLine($"初期化した連結リストは{n0}"); // ノードを挿入 Insert(n0, new ListNode(0)); Console.WriteLine($"ノード挿入後の連結リストは{n0}"); // ノードを削除 Remove(n0); Console.WriteLine($"ノード削除後の連結リストは{n0}"); // ノードにアクセス ListNode? node = Access(n0, 3); Console.WriteLine($"連結リストのインデックス 3 にあるノードの値 = {node?.val}"); // ノードを探索 int index = Find(n0, 2); Console.WriteLine($"連結リスト内で値が 2 のノードのインデックス = {index}"); } } ================================================ FILE: ja/codes/csharp/chapter_array_and_linkedlist/list.cs ================================================ /** * File: list.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_array_and_linkedlist; public class list { [Test] public void Test() { /* リストを初期化 */ int[] numbers = [1, 3, 2, 5, 4]; List nums = [.. numbers]; Console.WriteLine("リスト nums = " + string.Join(",", nums)); /* 要素にアクセス */ int num = nums[1]; Console.WriteLine("インデックス 1 の要素にアクセスすると num = " + num); /* 要素を更新 */ nums[1] = 0; Console.WriteLine("インデックス 1 の要素を 0 に更新すると nums = " + string.Join(",", nums)); /* リストを空にする */ nums.Clear(); Console.WriteLine("リストを空にした後 nums = " + string.Join(",", nums)); /* 末尾に要素を追加 */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); Console.WriteLine("要素を追加した後 nums = " + string.Join(",", nums)); /* 中間に要素を挿入 */ nums.Insert(3, 6); Console.WriteLine("インデックス 3 に数値 6 を挿入すると nums = " + string.Join(",", nums)); /* 要素を削除 */ nums.RemoveAt(3); Console.WriteLine("インデックス 3 の要素を削除すると nums = " + string.Join(",", nums)); /* インデックスでリストを走査 */ int count = 0; for (int i = 0; i < nums.Count; i++) { count += nums[i]; } /* リスト要素を直接走査 */ count = 0; foreach (int x in nums) { count += x; } /* 2 つのリストを連結する */ List nums1 = [6, 8, 7, 10, 9]; nums.AddRange(nums1); Console.WriteLine("リスト nums1 を nums の後ろに連結すると nums = " + string.Join(",", nums)); /* リストをソート */ nums.Sort(); // ソート後、リスト要素は小さい順に並ぶ Console.WriteLine("リストをソートした後 nums = " + string.Join(",", nums)); } } ================================================ FILE: ja/codes/csharp/chapter_array_and_linkedlist/my_list.cs ================================================ /** * File: my_list.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_array_and_linkedlist; /* リストクラス */ class MyList { private int[] arr; // 配列(リスト要素を格納) private int arrCapacity = 10; // リスト容量 private int arrSize = 0; // リストの長さ(現在の要素数) private readonly int extendRatio = 2; // リスト拡張時の増加倍率 /* コンストラクタ */ public MyList() { arr = new int[arrCapacity]; } /* リストの長さを取得(現在の要素数) */ public int Size() { return arrSize; } /* リスト容量を取得する */ public int Capacity() { return arrCapacity; } /* 要素にアクセス */ public int Get(int index) { // インデックスが範囲外なら例外を送出する。以下同様 if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("インデックスが範囲外です"); return arr[index]; } /* 要素を更新 */ public void Set(int index, int num) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("インデックスが範囲外です"); arr[index] = num; } /* 末尾に要素を追加 */ public void Add(int num) { // 要素数が容量を超えると、拡張機構が発動する if (arrSize == arrCapacity) ExtendCapacity(); arr[arrSize] = num; // 要素数を更新 arrSize++; } /* 中間に要素を挿入 */ public void Insert(int index, int num) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("インデックスが範囲外です"); // 要素数が容量を超えると、拡張機構が発動する if (arrSize == arrCapacity) ExtendCapacity(); // index 以降の要素をすべて 1 つ後ろへずらす for (int j = arrSize - 1; j >= index; j--) { arr[j + 1] = arr[j]; } arr[index] = num; // 要素数を更新 arrSize++; } /* 要素を削除 */ public int Remove(int index) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("インデックスが範囲外です"); int num = arr[index]; // インデックス index より後の要素をすべて 1 つ前に移動する for (int j = index; j < arrSize - 1; j++) { arr[j] = arr[j + 1]; } // 要素数を更新 arrSize--; // 削除された要素を返す return num; } /* リストの拡張 */ public void ExtendCapacity() { // `arrCapacity * extendRatio` の長さを持つ配列を新規作成し、元の配列を新しい配列にコピーする Array.Resize(ref arr, arrCapacity * extendRatio); // リストの容量を更新 arrCapacity = arr.Length; } /* リストを配列に変換する */ public int[] ToArray() { // 有効長の範囲内のリスト要素のみを変換 int[] arr = new int[arrSize]; for (int i = 0; i < arrSize; i++) { arr[i] = Get(i); } return arr; } } public class my_list { [Test] public void Test() { /* リストを初期化 */ MyList nums = new(); /* 末尾に要素を追加 */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); Console.WriteLine("リスト nums = " + string.Join(",", nums.ToArray()) + " ,容量 = " + nums.Capacity() + " ,長さ = " + nums.Size()); /* 中間に要素を挿入 */ nums.Insert(3, 6); Console.WriteLine("インデックス 3 に数値 6 を挿入すると nums = " + string.Join(",", nums.ToArray())); /* 要素を削除 */ nums.Remove(3); Console.WriteLine("インデックス 3 の要素を削除すると nums = " + string.Join(",", nums.ToArray())); /* 要素にアクセス */ int num = nums.Get(1); Console.WriteLine("インデックス 1 の要素にアクセスすると num = " + num); /* 要素を更新 */ nums.Set(1, 0); Console.WriteLine("インデックス 1 の要素を 0 に更新すると nums = " + string.Join(",", nums.ToArray())); /* 拡張機構をテストする */ for (int i = 0; i < 10; i++) { // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する nums.Add(i); } Console.WriteLine("拡張後のリスト nums = " + string.Join(",", nums.ToArray()) + " ,容量 = " + nums.Capacity() + " ,長さ = " + nums.Size()); } } ================================================ FILE: ja/codes/csharp/chapter_backtracking/n_queens.cs ================================================ /** * File: n_queens.cs * Created Time: 2023-05-04 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class n_queens { /* バックトラッキング:N クイーン */ void Backtrack(int row, int n, List> state, List>> res, bool[] cols, bool[] diags1, bool[] diags2) { // すべての行への配置が完了したら、解を記録する if (row == n) { List> copyState = []; foreach (List sRow in state) { copyState.Add(new List(sRow)); } res.Add(copyState); return; } // すべての列を走査 for (int col = 0; col < n; col++) { // このマスに対応する主対角線と副対角線を計算 int diag1 = row - col + n - 1; int diag2 = row + col; // 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 試行:そのマスにクイーンを置く state[row][col] = "Q"; cols[col] = diags1[diag1] = diags2[diag2] = true; // 次の行に配置する Backtrack(row + 1, n, state, res, cols, diags1, diags2); // 戻す:そのマスを空きマスに戻す state[row][col] = "#"; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* N クイーンを解く */ List>> NQueens(int n) { // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す List> state = []; for (int i = 0; i < n; i++) { List row = []; for (int j = 0; j < n; j++) { row.Add("#"); } state.Add(row); } bool[] cols = new bool[n]; // 列にクイーンがあるか記録 bool[] diags1 = new bool[2 * n - 1]; // 主対角線にクイーンがあるかを記録 bool[] diags2 = new bool[2 * n - 1]; // 副対角線にクイーンがあるかを記録 List>> res = []; Backtrack(0, n, state, res, cols, diags1, diags2); return res; } [Test] public void Test() { int n = 4; List>> res = NQueens(n); Console.WriteLine("盤面の縦横サイズの入力値は " + n); Console.WriteLine("クイーンの配置パターンは全部で " + res.Count + " 通り"); foreach (List> state in res) { Console.WriteLine("--------------------"); foreach (List row in state) { PrintUtil.PrintList(row); } } } } ================================================ FILE: ja/codes/csharp/chapter_backtracking/permutations_i.cs ================================================ /** * File: permutations_i.cs * Created Time: 2023-04-24 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class permutations_i { /* バックトラッキング:順列 I */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // 状態の長さが要素数に等しければ、解を記録 if (state.Count == choices.Length) { res.Add(new List(state)); return; } // すべての選択肢を走査 for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // 枝刈り:要素の重複選択を許可しない if (!selected[i]) { // 試行: 選択を行い、状態を更新 selected[i] = true; state.Add(choice); // 次の選択へ進む Backtrack(state, choices, selected, res); // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* 全順列 I */ List> PermutationsI(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } [Test] public void Test() { int[] nums = [1, 2, 3]; List> res = PermutationsI(nums); Console.WriteLine("入力配列 nums = " + string.Join(", ", nums)); Console.WriteLine("すべての順列 res = "); foreach (List permutation in res) { PrintUtil.PrintList(permutation); } } } ================================================ FILE: ja/codes/csharp/chapter_backtracking/permutations_ii.cs ================================================ /** * File: permutations_ii.cs * Created Time: 2023-04-24 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class permutations_ii { /* バックトラッキング:順列 II */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // 状態の長さが要素数に等しければ、解を記録 if (state.Count == choices.Length) { res.Add(new List(state)); return; } // すべての選択肢を走査 HashSet duplicated = []; for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない if (!selected[i] && !duplicated.Contains(choice)) { // 試行: 選択を行い、状態を更新 duplicated.Add(choice); // 選択済みの要素値を記録 selected[i] = true; state.Add(choice); // 次の選択へ進む Backtrack(state, choices, selected, res); // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* 全順列 II */ List> PermutationsII(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } [Test] public void Test() { int[] nums = [1, 2, 2]; List> res = PermutationsII(nums); Console.WriteLine("入力配列 nums = " + string.Join(", ", nums)); Console.WriteLine("すべての順列 res = "); foreach (List permutation in res) { PrintUtil.PrintList(permutation); } } } ================================================ FILE: ja/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs ================================================ /** * File: preorder_traversal_i_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_i_compact { List res = []; /* 前順走査:例題 1 */ void PreOrder(TreeNode? root) { if (root == null) { return; } if (root.val == 7) { // 解を記録 res.Add(root); } PreOrder(root.left); PreOrder(root.right); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\n二分木を初期化"); PrintUtil.PrintTree(root); // 先行順走査 PreOrder(root); Console.WriteLine("\n値が 7 のノードをすべて出力"); PrintUtil.PrintList(res.Select(p => p.val).ToList()); } } ================================================ FILE: ja/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs ================================================ /** * File: preorder_traversal_ii_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_ii_compact { List path = []; List> res = []; /* 前順走査:例題 2 */ void PreOrder(TreeNode? root) { if (root == null) { return; } // 試す path.Add(root); if (root.val == 7) { // 解を記録 res.Add(new List(path)); } PreOrder(root.left); PreOrder(root.right); // バックトラック path.RemoveAt(path.Count - 1); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\n二分木を初期化"); PrintUtil.PrintTree(root); // 先行順走査 PreOrder(root); Console.WriteLine("\nルートノードからノード 7 までのすべての経路を出力"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: ja/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs ================================================ /** * File: preorder_traversal_iii_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_iii_compact { List path = []; List> res = []; /* 前順走査:例題 3 */ void PreOrder(TreeNode? root) { // 枝刈り if (root == null || root.val == 3) { return; } // 試す path.Add(root); if (root.val == 7) { // 解を記録 res.Add(new List(path)); } PreOrder(root.left); PreOrder(root.right); // バックトラック path.RemoveAt(path.Count - 1); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\n二分木を初期化"); PrintUtil.PrintTree(root); // 先行順走査 PreOrder(root); Console.WriteLine("\nルートノードからノード 7 までのすべての経路を出力し、経路には値が 3 のノードを含めない"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: ja/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs ================================================ /** * File: preorder_traversal_iii_template.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_iii_template { /* 現在の状態が解かどうかを判定 */ bool IsSolution(List state) { return state.Count != 0 && state[^1].val == 7; } /* 解を記録 */ void RecordSolution(List state, List> res) { res.Add(new List(state)); } /* 現在の状態で、この選択が有効かどうかを判定 */ bool IsValid(List state, TreeNode choice) { return choice != null && choice.val != 3; } /* 状態を更新 */ void MakeChoice(List state, TreeNode choice) { state.Add(choice); } /* 状態を元に戻す */ void UndoChoice(List state, TreeNode choice) { state.RemoveAt(state.Count - 1); } /* バックトラッキング:例題 3 */ void Backtrack(List state, List choices, List> res) { // 解かどうかを確認 if (IsSolution(state)) { // 解を記録 RecordSolution(state, res); } // すべての選択肢を走査 foreach (TreeNode choice in choices) { // 枝刈り:選択が妥当かを確認する if (IsValid(state, choice)) { // 試行: 選択を行い、状態を更新 MakeChoice(state, choice); // 次の選択へ進む Backtrack(state, [choice.left!, choice.right!], res); // バックトラック:選択を取り消し、前の状態に戻す UndoChoice(state, choice); } } } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\n二分木を初期化"); PrintUtil.PrintTree(root); // バックトラッキング法 List> res = []; List choices = [root!]; Backtrack([], choices, res); Console.WriteLine("\nルートノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含まないことを条件とする"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: ja/codes/csharp/chapter_backtracking/subset_sum_i.cs ================================================ /** * File: subset_sum_i.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_i { /* バックトラッキング:部分和 I */ void Backtrack(List state, int target, int[] choices, int start, List> res) { // 部分集合の和が target に等しければ、解を記録 if (target == 0) { res.Add(new List(state)); return; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for (int i = start; i < choices.Length; i++) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } // 試す:選択を行い、target と start を更新 state.Add(choices[i]); // 次の選択へ進む Backtrack(state, target - choices[i], choices, i, res); // バックトラック:選択を取り消し、前の状態に戻す state.RemoveAt(state.Count - 1); } } /* 部分和 I を解く */ List> SubsetSumI(int[] nums, int target) { List state = []; // 状態(部分集合) Array.Sort(nums); // nums をソート int start = 0; // 開始点を走査 List> res = []; // 結果リスト(部分集合のリスト) Backtrack(state, target, nums, start, res); return res; } [Test] public void Test() { int[] nums = [3, 4, 5]; int target = 9; List> res = SubsetSumI(nums, target); Console.WriteLine("入力配列 nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("和が " + target + " に等しいすべての部分集合 res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } } } ================================================ FILE: ja/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs ================================================ /** * File: subset_sum_i_naive.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_i_naive { /* バックトラッキング:部分和 I */ void Backtrack(List state, int target, int total, int[] choices, List> res) { // 部分集合の和が target に等しければ、解を記録 if (total == target) { res.Add(new List(state)); return; } // すべての選択肢を走査 for (int i = 0; i < choices.Length; i++) { // 枝刈り:部分和が target を超える場合はその選択をスキップする if (total + choices[i] > target) { continue; } // 試行:選択を行い、要素と total を更新する state.Add(choices[i]); // 次の選択へ進む Backtrack(state, target, total + choices[i], choices, res); // バックトラック:選択を取り消し、前の状態に戻す state.RemoveAt(state.Count - 1); } } /* 部分和 I を解く(重複部分集合を含む) */ List> SubsetSumINaive(int[] nums, int target) { List state = []; // 状態(部分集合) int total = 0; // 部分和 List> res = []; // 結果リスト(部分集合のリスト) Backtrack(state, target, total, nums, res); return res; } [Test] public void Test() { int[] nums = [3, 4, 5]; int target = 9; List> res = SubsetSumINaive(nums, target); Console.WriteLine("入力配列 nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("和が " + target + " に等しいすべての部分集合 res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } Console.WriteLine("この方法の出力結果には重複した集合が含まれることに注意してください"); } } ================================================ FILE: ja/codes/csharp/chapter_backtracking/subset_sum_ii.cs ================================================ /** * File: subset_sum_ii.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_ii { /* バックトラッキング:部分和 II */ void Backtrack(List state, int target, int[] choices, int start, List> res) { // 部分集合の和が target に等しければ、解を記録 if (target == 0) { res.Add(new List(state)); return; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for (int i = start; i < choices.Length; i++) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする if (i > start && choices[i] == choices[i - 1]) { continue; } // 試す:選択を行い、target と start を更新 state.Add(choices[i]); // 次の選択へ進む Backtrack(state, target - choices[i], choices, i + 1, res); // バックトラック:選択を取り消し、前の状態に戻す state.RemoveAt(state.Count - 1); } } /* 部分和 II を解く */ List> SubsetSumII(int[] nums, int target) { List state = []; // 状態(部分集合) Array.Sort(nums); // nums をソート int start = 0; // 開始点を走査 List> res = []; // 結果リスト(部分集合のリスト) Backtrack(state, target, nums, start, res); return res; } [Test] public void Test() { int[] nums = [4, 4, 5]; int target = 9; List> res = SubsetSumII(nums, target); Console.WriteLine("入力配列 nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("和が " + target + " に等しいすべての部分集合 res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } } } ================================================ FILE: ja/codes/csharp/chapter_computational_complexity/iteration.cs ================================================ /** * File: iteration.cs * Created Time: 2023-08-28 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_computational_complexity; public class iteration { /* for ループ */ int ForLoop(int n) { int res = 0; // 1, 2, ..., n-1, n を順に加算する for (int i = 1; i <= n; i++) { res += i; } return res; } /* while ループ */ int WhileLoop(int n) { int res = 0; int i = 1; // 条件変数を初期化する // 1, 2, ..., n-1, n を順に加算する while (i <= n) { res += i; i += 1; // 条件変数を更新する } return res; } /* while ループ(2回更新) */ int WhileLoopII(int n) { int res = 0; int i = 1; // 条件変数を初期化する // 1, 4, 10, ... を順に加算する while (i <= n) { res += i; // 条件変数を更新する i += 1; i *= 2; } return res; } /* 二重 for ループ */ string NestedForLoop(int n) { StringBuilder res = new(); // i = 1, 2, ..., n-1, n とループする for (int i = 1; i <= n; i++) { // j = 1, 2, ..., n-1, n とループする for (int j = 1; j <= n; j++) { res.Append($"({i}, {j}), "); } } return res.ToString(); } /* Driver Code */ [Test] public void Test() { int n = 5; int res; res = ForLoop(n); Console.WriteLine("\nfor ループの合計結果 res = " + res); res = WhileLoop(n); Console.WriteLine("\nwhile ループの合計結果 res = " + res); res = WhileLoopII(n); Console.WriteLine("\nwhile ループ(2回更新)の合計結果 res = " + res); string resStr = NestedForLoop(n); Console.WriteLine("\n二重 for ループの走査結果 " + resStr); } } ================================================ FILE: ja/codes/csharp/chapter_computational_complexity/recursion.cs ================================================ /** * File: recursion.cs * Created Time: 2023-08-28 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_computational_complexity; public class recursion { /* 再帰 */ int Recur(int n) { // 終了条件 if (n == 1) return 1; // 再帰:再帰呼び出し int res = Recur(n - 1); // 帰りがけ:結果を返す return n + res; } /* 反復で再帰を模擬する */ int ForLoopRecur(int n) { // 明示的なスタックを使ってシステムコールスタックを模擬する Stack stack = new(); int res = 0; // 再帰:再帰呼び出し for (int i = n; i > 0; i--) { // 「スタックへのプッシュ」で「再帰」を模擬する stack.Push(i); } // 帰りがけ:結果を返す while (stack.Count > 0) { // 「スタックから取り出す操作」で「帰り」をシミュレート res += stack.Pop(); } // res = 1+2+3+...+n return res; } /* 末尾再帰 */ int TailRecur(int n, int res) { // 終了条件 if (n == 0) return res; // 末尾再帰呼び出し return TailRecur(n - 1, res + n); } /* フィボナッチ数列:再帰 */ int Fib(int n) { // 終了条件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す int res = Fib(n - 1) + Fib(n - 2); // 結果 f(n) を返す return res; } /* Driver Code */ [Test] public void Test() { int n = 5; int res; res = Recur(n); Console.WriteLine("\n再帰関数の合計結果 res = " + res); res = ForLoopRecur(n); Console.WriteLine("\n反復で再帰をシミュレートした合計結果 res = " + res); res = TailRecur(n, 0); Console.WriteLine("\n末尾再帰関数の合計結果 res = " + res); res = Fib(n); Console.WriteLine("\nフィボナッチ数列の第 " + n + " 項は " + res); } } ================================================ FILE: ja/codes/csharp/chapter_computational_complexity/space_complexity.cs ================================================ /** * File: space_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class space_complexity { /* 関数 */ int Function() { // 何らかの処理を行う return 0; } /* 定数階 */ void Constant(int n) { // 定数、変数、オブジェクトは O(1) の空間を占める int a = 0; int b = 0; int[] nums = new int[10000]; ListNode node = new(0); // ループ内の変数は O(1) の空間を占める for (int i = 0; i < n; i++) { int c = 0; } // ループ内の関数は O(1) の空間を占める for (int i = 0; i < n; i++) { Function(); } } /* 線形階 */ void Linear(int n) { // 長さ n の配列は O(n) の空間を使用 int[] nums = new int[n]; // 長さ n のリストは O(n) の空間を使用 List nodes = []; for (int i = 0; i < n; i++) { nodes.Add(new ListNode(i)); } // 長さ n のハッシュテーブルは O(n) の空間を使用 Dictionary map = []; for (int i = 0; i < n; i++) { map.Add(i, i.ToString()); } } /* 線形時間(再帰実装) */ void LinearRecur(int n) { Console.WriteLine("再帰 n = " + n); if (n == 1) return; LinearRecur(n - 1); } /* 二乗階 */ void Quadratic(int n) { // 行列は O(n^2) の空間を使用する int[,] numMatrix = new int[n, n]; // 二次元リストは O(n^2) の空間を使用 List> numList = []; for (int i = 0; i < n; i++) { List tmp = []; for (int j = 0; j < n; j++) { tmp.Add(0); } numList.Add(tmp); } } /* 二次時間(再帰実装) */ int QuadraticRecur(int n) { if (n <= 0) return 0; int[] nums = new int[n]; Console.WriteLine("再帰 n = " + n + " における nums の長さ = " + nums.Length); return QuadraticRecur(n - 1); } /* 指数時間(完全二分木の構築) */ TreeNode? BuildTree(int n) { if (n == 0) return null; TreeNode root = new(0) { left = BuildTree(n - 1), right = BuildTree(n - 1) }; return root; } [Test] public void Test() { int n = 5; // 定数階 Constant(n); // 線形階 Linear(n); LinearRecur(n); // 二乗階 Quadratic(n); QuadraticRecur(n); // 指数オーダー TreeNode? root = BuildTree(n); PrintUtil.PrintTree(root); } } ================================================ FILE: ja/codes/csharp/chapter_computational_complexity/time_complexity.cs ================================================ /** * File: time_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class time_complexity { void Algorithm(int n) { int a = 1; // +0(テクニック 1) a += n; // +0(テクニック 1) // +n(テクニック 2) for (int i = 0; i < 5 * n + 1; i++) { Console.WriteLine(0); } // +n*n(テクニック 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { Console.WriteLine(0); } } } // アルゴリズム A の時間計算量: 定数時間 void AlgorithmA(int n) { Console.WriteLine(0); } // アルゴリズム B の時間計算量: 線形時間 void AlgorithmB(int n) { for (int i = 0; i < n; i++) { Console.WriteLine(0); } } // アルゴリズム C の時間計算量: 定数時間 void AlgorithmC(int n) { for (int i = 0; i < 1000000; i++) { Console.WriteLine(0); } } /* 定数階 */ int Constant(int n) { int count = 0; int size = 100000; for (int i = 0; i < size; i++) count++; return count; } /* 線形階 */ int Linear(int n) { int count = 0; for (int i = 0; i < n; i++) count++; return count; } /* 線形時間(配列を走査) */ int ArrayTraversal(int[] nums) { int count = 0; // ループ回数は配列長に比例する foreach (int num in nums) { count++; } return count; } /* 二乗階 */ int Quadratic(int n) { int count = 0; // ループ回数はデータサイズ n の二乗に比例する for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* 二次時間(バブルソート) */ int BubbleSort(int[] nums) { int count = 0; // カウンタ // 外側のループ:未ソート区間は [0, i] for (int i = nums.Length - 1; i > 0; i--) { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); count += 3; // 要素交換には 3 回の単位操作が含まれる } } } return count; } /* 指数時間(ループ実装) */ int Exponential(int n) { int count = 0, bas = 1; // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する for (int i = 0; i < n; i++) { for (int j = 0; j < bas; j++) { count++; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指数時間(再帰実装) */ int ExpRecur(int n) { if (n == 1) return 1; return ExpRecur(n - 1) + ExpRecur(n - 1) + 1; } /* 対数時間(ループ実装) */ int Logarithmic(int n) { int count = 0; while (n > 1) { n /= 2; count++; } return count; } /* 対数時間(再帰実装) */ int LogRecur(int n) { if (n <= 1) return 0; return LogRecur(n / 2) + 1; } /* 線形対数時間 */ int LinearLogRecur(int n) { if (n <= 1) return 1; int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2); for (int i = 0; i < n; i++) { count++; } return count; } /* 階乗時間(再帰実装) */ int FactorialRecur(int n) { if (n == 0) return 1; int count = 0; // 1個から n 個に分裂 for (int i = 0; i < n; i++) { count += FactorialRecur(n - 1); } return count; } [Test] public void Test() { // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる int n = 8; Console.WriteLine("入力データサイズ n = " + n); int count = Constant(n); Console.WriteLine("定数時間の操作回数 = " + count); count = Linear(n); Console.WriteLine("線形時間の操作回数 = " + count); count = ArrayTraversal(new int[n]); Console.WriteLine("線形時間(配列の走査)の操作回数 = " + count); count = Quadratic(n); Console.WriteLine("二乗時間の操作回数 = " + count); int[] nums = new int[n]; for (int i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = BubbleSort(nums); Console.WriteLine("二乗時間(バブルソート)の操作回数 = " + count); count = Exponential(n); Console.WriteLine("指数時間(ループ実装)の操作回数 = " + count); count = ExpRecur(n); Console.WriteLine("指数時間(再帰実装)の操作回数 = " + count); count = Logarithmic(n); Console.WriteLine("対数時間(ループ実装)の操作回数 = " + count); count = LogRecur(n); Console.WriteLine("対数時間(再帰実装)の操作回数 = " + count); count = LinearLogRecur(n); Console.WriteLine("線形対数時間(再帰実装)の操作回数 = " + count); count = FactorialRecur(n); Console.WriteLine("階乗時間(再帰実装)の操作回数 = " + count); } } ================================================ FILE: ja/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs ================================================ /** * File: worst_best_time_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class worst_best_time_complexity { /* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ int[] RandomNumbers(int n) { int[] nums = new int[n]; // 配列 nums = { 1, 2, 3, ..., n } を生成 for (int i = 0; i < n; i++) { nums[i] = i + 1; } // 配列要素をランダムにシャッフル for (int i = 0; i < nums.Length; i++) { int index = new Random().Next(i, nums.Length); (nums[i], nums[index]) = (nums[index], nums[i]); } return nums; } /* 配列 nums 内で数値 1 のインデックスを探す */ int FindOne(int[] nums) { for (int i = 0; i < nums.Length; i++) { // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる if (nums[i] == 1) return i; } return -1; } /* Driver Code */ [Test] public void Test() { for (int i = 0; i < 10; i++) { int n = 100; int[] nums = RandomNumbers(n); int index = FindOne(nums); Console.WriteLine("\n配列 [ 1, 2, ..., n ] をシャッフルした後 = " + string.Join(",", nums)); Console.WriteLine("数字 1 のインデックスは " + index); } } } ================================================ FILE: ja/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs ================================================ /** * File: binary_search_recur.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class binary_search_recur { /* 二分探索:問題 f(i, j) */ int DFS(int[] nums, int target, int i, int j) { // 区間が空なら対象要素は存在しないので -1 を返す if (i > j) { return -1; } // 中点インデックス m を計算 int m = (i + j) / 2; if (nums[m] < target) { // 部分問題 f(m+1, j) を再帰的に解く return DFS(nums, target, m + 1, j); } else if (nums[m] > target) { // 部分問題 f(i, m-1) を再帰的に解く return DFS(nums, target, i, m - 1); } else { // 目標要素が見つかったらそのインデックスを返す return m; } } /* 二分探索 */ int BinarySearch(int[] nums, int target) { int n = nums.Length; // 問題 f(0, n-1) を解く return DFS(nums, target, 0, n - 1); } [Test] public void Test() { int target = 6; int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分探索(両閉区間) int index = BinarySearch(nums, target); Console.WriteLine("対象要素 6 のインデックス = " + index); } } ================================================ FILE: ja/codes/csharp/chapter_divide_and_conquer/build_tree.cs ================================================ /** * File: build_tree.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class build_tree { /* 二分木を構築:分割統治 */ TreeNode? DFS(int[] preorder, Dictionary inorderMap, int i, int l, int r) { // 部分木区間が空なら終了する if (r - l < 0) return null; // ルートノードを初期化する TreeNode root = new(preorder[i]); // m を求めて左右部分木を分割する int m = inorderMap[preorder[i]]; // 部分問題:左部分木を構築する root.left = DFS(preorder, inorderMap, i + 1, l, m - 1); // 部分問題:右部分木を構築する root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r); // 根ノードを返す return root; } /* 二分木を構築 */ TreeNode? BuildTree(int[] preorder, int[] inorder) { // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する Dictionary inorderMap = []; for (int i = 0; i < inorder.Length; i++) { inorderMap.TryAdd(inorder[i], i); } TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1); return root; } [Test] public void Test() { int[] preorder = [3, 9, 2, 1, 7]; int[] inorder = [9, 3, 1, 2, 7]; Console.WriteLine("前順走査 = " + string.Join(", ", preorder)); Console.WriteLine("中順走査 = " + string.Join(", ", inorder)); TreeNode? root = BuildTree(preorder, inorder); Console.WriteLine("構築した二分木は次のとおりです:"); PrintUtil.PrintTree(root); } } ================================================ FILE: ja/codes/csharp/chapter_divide_and_conquer/hanota.cs ================================================ /** * File: hanota.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class hanota { /* 円盤を 1 枚移動 */ void Move(List src, List tar) { // src の上から円盤を1枚取り出す int pan = src[^1]; src.RemoveAt(src.Count - 1); // 円盤を tar の上に置く tar.Add(pan); } /* ハノイの塔の問題 f(i) を解く */ void DFS(int i, List src, List buf, List tar) { // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if (i == 1) { Move(src, tar); return; } // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す DFS(i - 1, src, tar, buf); // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す Move(src, tar); // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す DFS(i - 1, buf, src, tar); } /* ハノイの塔を解く */ void SolveHanota(List A, List B, List C) { int n = A.Count; // A の上から n 枚の円盤を B を介して C へ移す DFS(n, A, B, C); } [Test] public void Test() { // リスト末尾が柱の頂上 List A = [5, 4, 3, 2, 1]; List B = []; List C = []; Console.WriteLine("初期状態:"); Console.WriteLine("A = " + string.Join(", ", A)); Console.WriteLine("B = " + string.Join(", ", B)); Console.WriteLine("C = " + string.Join(", ", C)); SolveHanota(A, B, C); Console.WriteLine("円盤の移動完了後:"); Console.WriteLine("A = " + string.Join(", ", A)); Console.WriteLine("B = " + string.Join(", ", B)); Console.WriteLine("C = " + string.Join(", ", C)); } } ================================================ FILE: ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs ================================================ /** * File: climbing_stairs_backtrack.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_backtrack { /* バックトラッキング */ void Backtrack(List choices, int state, int n, List res) { // 第 n 段に到達したら、方法数を 1 増やす if (state == n) res[0]++; // すべての選択肢を走査 foreach (int choice in choices) { // 枝刈り: 第 n 段を超えないようにする if (state + choice > n) continue; // 試行: 選択を行い、状態を更新 Backtrack(choices, state + choice, n, res); // バックトラック } } /* 階段登り:バックトラッキング */ int ClimbingStairsBacktrack(int n) { List choices = [1, 2]; // 1 段または 2 段上ることを選べる int state = 0; // 第 0 段から上り始める List res = [0]; // res[0] を使って方法数を記録する Backtrack(choices, state, n, res); return res[0]; } [Test] public void Test() { int n = 9; int res = ClimbingStairsBacktrack(n); Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り"); } } ================================================ FILE: ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs ================================================ /** * File: climbing_stairs_constraint_dp.cs * Created Time: 2023-07-03 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_constraint_dp { /* 制約付き階段登り:動的計画法 */ int ClimbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // 部分問題の解を保存するために dp テーブルを初期化 int[,] dp = new int[n + 1, 3]; // 初期状態:最小部分問題の解をあらかじめ設定 dp[1, 1] = 1; dp[1, 2] = 0; dp[2, 1] = 0; dp[2, 2] = 1; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i, 1] = dp[i - 1, 2]; dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2]; } return dp[n, 1] + dp[n, 2]; } [Test] public void Test() { int n = 9; int res = ClimbingStairsConstraintDP(n); Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り"); } } ================================================ FILE: ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs ================================================ /** * File: climbing_stairs_dfs.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dfs { /* 検索 */ int DFS(int i) { // dp[1] と dp[2] は既知なので返す if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] int count = DFS(i - 1) + DFS(i - 2); return count; } /* 階段登り:探索 */ int ClimbingStairsDFS(int n) { return DFS(n); } [Test] public void Test() { int n = 9; int res = ClimbingStairsDFS(n); Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り"); } } ================================================ FILE: ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs ================================================ /** * File: climbing_stairs_dfs_mem.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dfs_mem { /* メモ化探索 */ int DFS(int i, int[] mem) { // dp[1] と dp[2] は既知なので返す if (i == 1 || i == 2) return i; // dp[i] の記録があれば、それをそのまま返す if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = DFS(i - 1, mem) + DFS(i - 2, mem); // dp[i] を記録する mem[i] = count; return count; } /* 階段登り:メモ化探索 */ int ClimbingStairsDFSMem(int n) { // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す int[] mem = new int[n + 1]; Array.Fill(mem, -1); return DFS(n, mem); } [Test] public void Test() { int n = 9; int res = ClimbingStairsDFSMem(n); Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り"); } } ================================================ FILE: ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs ================================================ /** * File: climbing_stairs_dp.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dp { /* 階段登り:動的計画法 */ int ClimbingStairsDP(int n) { if (n == 1 || n == 2) return n; // 部分問題の解を保存するために dp テーブルを初期化 int[] dp = new int[n + 1]; // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = 1; dp[2] = 2; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* 階段登り:空間最適化した動的計画法 */ int ClimbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } [Test] public void Test() { int n = 9; int res = ClimbingStairsDP(n); Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り"); res = ClimbingStairsDPComp(n); Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り"); } } ================================================ FILE: ja/codes/csharp/chapter_dynamic_programming/coin_change.cs ================================================ /** * File: coin_change.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class coin_change { /* コイン両替:動的計画法 */ int CoinChangeDP(int[] coins, int amt) { int n = coins.Length; int MAX = amt + 1; // dp テーブルを初期化 int[,] dp = new int[n + 1, amt + 1]; // 状態遷移:先頭行と先頭列 for (int a = 1; a <= amt; a++) { dp[0, a] = MAX; } // 状態遷移: 残りの行と列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[i, a] = dp[i - 1, a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1); } } } return dp[n, amt] != MAX ? dp[n, amt] : -1; } /* コイン交換:空間最適化後の動的計画法 */ int CoinChangeDPComp(int[] coins, int amt) { int n = coins.Length; int MAX = amt + 1; // dp テーブルを初期化 int[] dp = new int[amt + 1]; Array.Fill(dp, MAX); dp[0] = 0; // 状態遷移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } [Test] public void Test() { int[] coins = [1, 2, 5]; int amt = 4; // 動的計画法 int res = CoinChangeDP(coins, amt); Console.WriteLine("目標金額にするために必要な最小の硬貨枚数は " + res); // 空間最適化後の動的計画法 res = CoinChangeDPComp(coins, amt); Console.WriteLine("目標金額にするために必要な最小の硬貨枚数は " + res); } } ================================================ FILE: ja/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs ================================================ /** * File: coin_change_ii.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class coin_change_ii { /* コイン両替 II:動的計画法 */ int CoinChangeIIDP(int[] coins, int amt) { int n = coins.Length; // dp テーブルを初期化 int[,] dp = new int[n + 1, amt + 1]; // 先頭列を初期化する for (int i = 0; i <= n; i++) { dp[i, 0] = 1; } // 状態遷移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[i, a] = dp[i - 1, a]; } else { // コイン i を選ばない場合と選ぶ場合の和 dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]]; } } } return dp[n, amt]; } /* コイン両替 II:空間最適化した動的計画法 */ int CoinChangeIIDPComp(int[] coins, int amt) { int n = coins.Length; // dp テーブルを初期化 int[] dp = new int[amt + 1]; dp[0] = 1; // 状態遷移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // コイン i を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } [Test] public void Test() { int[] coins = [1, 2, 5]; int amt = 5; // 動的計画法 int res = CoinChangeIIDP(coins, amt); Console.WriteLine("目標金額を作る硬貨の組み合わせ数は " + res); // 空間最適化後の動的計画法 res = CoinChangeIIDPComp(coins, amt); Console.WriteLine("目標金額を作る硬貨の組み合わせ数は " + res); } } ================================================ FILE: ja/codes/csharp/chapter_dynamic_programming/edit_distance.cs ================================================ /** * File: edit_distance.cs * Created Time: 2023-07-14 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class edit_distance { /* 編集距離:総当たり探索 */ int EditDistanceDFS(string s, string t, int i, int j) { // s と t がともに空なら 0 を返す if (i == 0 && j == 0) return 0; // s が空なら t の長さを返す if (i == 0) return j; // t が空なら s の長さを返す if (j == 0) return i; // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s[i - 1] == t[j - 1]) return EditDistanceDFS(s, t, i - 1, j - 1); // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 int insert = EditDistanceDFS(s, t, i, j - 1); int delete = EditDistanceDFS(s, t, i - 1, j); int replace = EditDistanceDFS(s, t, i - 1, j - 1); // 最小編集回数を返す return Math.Min(Math.Min(insert, delete), replace) + 1; } /* 編集距離:メモ化探索 */ int EditDistanceDFSMem(string s, string t, int[][] mem, int i, int j) { // s と t がともに空なら 0 を返す if (i == 0 && j == 0) return 0; // s が空なら t の長さを返す if (i == 0) return j; // t が空なら s の長さを返す if (j == 0) return i; // 記録済みなら、それをそのまま返す if (mem[i][j] != -1) return mem[i][j]; // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s[i - 1] == t[j - 1]) return EditDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 int insert = EditDistanceDFSMem(s, t, mem, i, j - 1); int delete = EditDistanceDFSMem(s, t, mem, i - 1, j); int replace = EditDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最小編集回数を記録して返す mem[i][j] = Math.Min(Math.Min(insert, delete), replace) + 1; return mem[i][j]; } /* 編集距離:動的計画法 */ int EditDistanceDP(string s, string t) { int n = s.Length, m = t.Length; int[,] dp = new int[n + 1, m + 1]; // 状態遷移:先頭行と先頭列 for (int i = 1; i <= n; i++) { dp[i, 0] = i; } for (int j = 1; j <= m; j++) { dp[0, j] = j; } // 状態遷移: 残りの行と列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i, j] = dp[i - 1, j - 1]; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1; } } } return dp[n, m]; } /* 編集距離:空間最適化した動的計画法 */ int EditDistanceDPComp(string s, string t) { int n = s.Length, m = t.Length; int[] dp = new int[m + 1]; // 状態遷移:先頭行 for (int j = 1; j <= m; j++) { dp[j] = j; } // 状態遷移:残りの行 for (int i = 1; i <= n; i++) { // 状態遷移:先頭列 int leftup = dp[0]; // dp[i-1, j-1] を一時保存する dp[0] = i; // 状態遷移:残りの列 for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftup; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 次の反復の dp[i-1, j-1] に更新する } } return dp[m]; } [Test] public void Test() { string s = "bag"; string t = "pack"; int n = s.Length, m = t.Length; // 全探索 int res = EditDistanceDFS(s, t, n, m); Console.WriteLine("" + s + " を " + t + " に変更するには少なくとも " + res + " 回の編集が必要"); // メモ化探索 int[][] mem = new int[n + 1][]; for (int i = 0; i <= n; i++) { mem[i] = new int[m + 1]; Array.Fill(mem[i], -1); } res = EditDistanceDFSMem(s, t, mem, n, m); Console.WriteLine("" + s + " を " + t + " に変更するには少なくとも " + res + " 回の編集が必要"); // 動的計画法 res = EditDistanceDP(s, t); Console.WriteLine("" + s + " を " + t + " に変更するには少なくとも " + res + " 回の編集が必要"); // 空間最適化後の動的計画法 res = EditDistanceDPComp(s, t); Console.WriteLine("" + s + " を " + t + " に変更するには少なくとも " + res + " 回の編集が必要"); } } ================================================ FILE: ja/codes/csharp/chapter_dynamic_programming/knapsack.cs ================================================ /** * File: knapsack.cs * Created Time: 2023-07-07 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class knapsack { /* 0-1 ナップサック:総当たり探索 */ int KnapsackDFS(int[] weight, int[] val, int i, int c) { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 || c == 0) { return 0; } // ナップサック容量を超える場合は、入れない選択しかできない if (weight[i - 1] > c) { return KnapsackDFS(weight, val, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する int no = KnapsackDFS(weight, val, i - 1, c); int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1]; // 2つの案のうち価値が大きいほうを返す return Math.Max(no, yes); } /* 0-1 ナップサック:メモ化探索 */ int KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 || c == 0) { return 0; } // 既に記録があればそのまま返す if (mem[i][c] != -1) { return mem[i][c]; } // ナップサック容量を超える場合は、入れない選択しかできない if (weight[i - 1] > c) { return KnapsackDFSMem(weight, val, mem, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する int no = KnapsackDFSMem(weight, val, mem, i - 1, c); int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1]; // 2 つの案のうち価値が大きい方を記録して返す mem[i][c] = Math.Max(no, yes); return mem[i][c]; } /* 0-1 ナップサック:動的計画法 */ int KnapsackDP(int[] weight, int[] val, int cap) { int n = weight.Length; // dp テーブルを初期化 int[,] dp = new int[n + 1, cap + 1]; // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (weight[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i, c] = dp[i - 1, c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]); } } } return dp[n, cap]; } /* 0-1 ナップサック:空間最適化後の動的計画法 */ int KnapsackDPComp(int[] weight, int[] val, int cap) { int n = weight.Length; // dp テーブルを初期化 int[] dp = new int[cap + 1]; // 状態遷移 for (int i = 1; i <= n; i++) { // 逆順に走査する for (int c = cap; c > 0; c--) { if (weight[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]); } } } return dp[cap]; } [Test] public void Test() { int[] weight = [10, 20, 30, 40, 50]; int[] val = [50, 120, 150, 210, 240]; int cap = 50; int n = weight.Length; // 全探索 int res = KnapsackDFS(weight, val, n, cap); Console.WriteLine("バックパック容量を超えない最大価値は " + res); // メモ化探索 int[][] mem = new int[n + 1][]; for (int i = 0; i <= n; i++) { mem[i] = new int[cap + 1]; Array.Fill(mem[i], -1); } res = KnapsackDFSMem(weight, val, mem, n, cap); Console.WriteLine("バックパック容量を超えない最大価値は " + res); // 動的計画法 res = KnapsackDP(weight, val, cap); Console.WriteLine("バックパック容量を超えない最大価値は " + res); // 空間最適化後の動的計画法 res = KnapsackDPComp(weight, val, cap); Console.WriteLine("バックパック容量を超えない最大価値は " + res); } } ================================================ FILE: ja/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs ================================================ /** * File: min_cost_climbing_stairs_dp.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class min_cost_climbing_stairs_dp { /* 階段登りの最小コスト:動的計画法 */ int MinCostClimbingStairsDP(int[] cost) { int n = cost.Length - 1; if (n == 1 || n == 2) return cost[n]; // 部分問題の解を保存するために dp テーブルを初期化 int[] dp = new int[n + 1]; // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = cost[1]; dp[2] = cost[2]; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* 階段昇りの最小コスト:空間最適化後の動的計画法 */ int MinCostClimbingStairsDPComp(int[] cost) { int n = cost.Length - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = Math.Min(a, tmp) + cost[i]; a = tmp; } return b; } [Test] public void Test() { int[] cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; Console.WriteLine("入力された階段コストのリストは"); PrintUtil.PrintList(cost); int res = MinCostClimbingStairsDP(cost); Console.WriteLine($"階段を上り切る最小コストは {res}"); res = MinCostClimbingStairsDPComp(cost); Console.WriteLine($"階段を上り切る最小コストは {res}"); } } ================================================ FILE: ja/codes/csharp/chapter_dynamic_programming/min_path_sum.cs ================================================ /** * File: min_path_sum.cs * Created Time: 2023-07-10 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class min_path_sum { /* 最小経路和:全探索 */ int MinPathSumDFS(int[][] grid, int i, int j) { // 左上のセルなら探索を終了する if (i == 0 && j == 0) { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return int.MaxValue; } // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する int up = MinPathSumDFS(grid, i - 1, j); int left = MinPathSumDFS(grid, i, j - 1); // 左上隅から (i, j) までの最小経路コストを返す return Math.Min(left, up) + grid[i][j]; } /* 最小経路和:メモ化探索 */ int MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { // 左上のセルなら探索を終了する if (i == 0 && j == 0) { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return int.MaxValue; } // 既に記録があればそのまま返す if (mem[i][j] != -1) { return mem[i][j]; } // 左と上のセルからの最小経路コスト int up = MinPathSumDFSMem(grid, mem, i - 1, j); int left = MinPathSumDFSMem(grid, mem, i, j - 1); // 左上から (i, j) までの最小経路コストを記録して返す mem[i][j] = Math.Min(left, up) + grid[i][j]; return mem[i][j]; } /* 最小経路和:動的計画法 */ int MinPathSumDP(int[][] grid) { int n = grid.Length, m = grid[0].Length; // dp テーブルを初期化 int[,] dp = new int[n, m]; dp[0, 0] = grid[0][0]; // 状態遷移:先頭行 for (int j = 1; j < m; j++) { dp[0, j] = dp[0, j - 1] + grid[0][j]; } // 状態遷移:先頭列 for (int i = 1; i < n; i++) { dp[i, 0] = dp[i - 1, 0] + grid[i][0]; } // 状態遷移: 残りの行と列 for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j]; } } return dp[n - 1, m - 1]; } /* 最小経路和:空間最適化後の動的計画法 */ int MinPathSumDPComp(int[][] grid) { int n = grid.Length, m = grid[0].Length; // dp テーブルを初期化 int[] dp = new int[m]; dp[0] = grid[0][0]; // 状態遷移:先頭行 for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 状態遷移:残りの行 for (int i = 1; i < n; i++) { // 状態遷移:先頭列 dp[0] = dp[0] + grid[i][0]; // 状態遷移:残りの列 for (int j = 1; j < m; j++) { dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } [Test] public void Test() { int[][] grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2] ]; int n = grid.Length, m = grid[0].Length; // 全探索 int res = MinPathSumDFS(grid, n - 1, m - 1); Console.WriteLine("左上から右下までの最小経路和は " + res); // メモ化探索 int[][] mem = new int[n][]; for (int i = 0; i < n; i++) { mem[i] = new int[m]; Array.Fill(mem[i], -1); } res = MinPathSumDFSMem(grid, mem, n - 1, m - 1); Console.WriteLine("左上から右下までの最小経路和は " + res); // 動的計画法 res = MinPathSumDP(grid); Console.WriteLine("左上から右下までの最小経路和は " + res); // 空間最適化後の動的計画法 res = MinPathSumDPComp(grid); Console.WriteLine("左上から右下までの最小経路和は " + res); } } ================================================ FILE: ja/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs ================================================ /** * File: unbounded_knapsack.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class unbounded_knapsack { /* 完全ナップサック問題:動的計画法 */ int UnboundedKnapsackDP(int[] wgt, int[] val, int cap) { int n = wgt.Length; // dp テーブルを初期化 int[,] dp = new int[n + 1, cap + 1]; // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i, c] = dp[i - 1, c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]); } } } return dp[n, cap]; } /* 完全ナップサック問題:空間最適化後の動的計画法 */ int UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { int n = wgt.Length; // dp テーブルを初期化 int[] dp = new int[cap + 1]; // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } [Test] public void Test() { int[] wgt = [1, 2, 3]; int[] val = [5, 11, 15]; int cap = 4; // 動的計画法 int res = UnboundedKnapsackDP(wgt, val, cap); Console.WriteLine("バックパック容量を超えない最大価値は " + res); // 空間最適化後の動的計画法 res = UnboundedKnapsackDPComp(wgt, val, cap); Console.WriteLine("バックパック容量を超えない最大価値は " + res); } } ================================================ FILE: ja/codes/csharp/chapter_graph/graph_adjacency_list.cs ================================================ /** * File: graph_adjacency_list.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_graph; /* 隣接リストに基づく無向グラフクラス */ public class GraphAdjList { // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 public Dictionary> adjList; /* コンストラクタ */ public GraphAdjList(Vertex[][] edges) { adjList = []; // すべての頂点と辺を追加 foreach (Vertex[] edge in edges) { AddVertex(edge[0]); AddVertex(edge[1]); AddEdge(edge[0], edge[1]); } } /* 頂点数を取得 */ int Size() { return adjList.Count; } /* 辺を追加 */ public void AddEdge(Vertex vet1, Vertex vet2) { if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) throw new InvalidOperationException(); // 辺 vet1 - vet2 を追加 adjList[vet1].Add(vet2); adjList[vet2].Add(vet1); } /* 辺を削除 */ public void RemoveEdge(Vertex vet1, Vertex vet2) { if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) throw new InvalidOperationException(); // 辺 vet1 - vet2 を削除 adjList[vet1].Remove(vet2); adjList[vet2].Remove(vet1); } /* 頂点を追加 */ public void AddVertex(Vertex vet) { if (adjList.ContainsKey(vet)) return; // 隣接リストに新しいリストを追加 adjList.Add(vet, []); } /* 頂点を削除 */ public void RemoveVertex(Vertex vet) { if (!adjList.ContainsKey(vet)) throw new InvalidOperationException(); // 隣接リストから頂点 vet に対応するリストを削除 adjList.Remove(vet); // 他の頂点のリストを走査し、vet を含むすべての辺を削除 foreach (List list in adjList.Values) { list.Remove(vet); } } /* 隣接リストを出力 */ public void Print() { Console.WriteLine("隣接リスト ="); foreach (KeyValuePair> pair in adjList) { List tmp = []; foreach (Vertex vertex in pair.Value) tmp.Add(vertex.val); Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); } } } public class graph_adjacency_list { [Test] public void Test() { /* 無向グラフを初期化 */ Vertex[] v = Vertex.ValsToVets([1, 3, 2, 5, 4]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]] ]; GraphAdjList graph = new(edges); Console.WriteLine("\n初期化後、グラフは"); graph.Print(); /* 辺を追加 */ // 頂点 1, 2 は v[0], v[2] graph.AddEdge(v[0], v[2]); Console.WriteLine("\n辺 1-2 を追加した後、グラフは"); graph.Print(); /* 辺を削除 */ // 頂点 1, 3 は v[0], v[1] graph.RemoveEdge(v[0], v[1]); Console.WriteLine("\n辺 1-3 を削除した後、グラフは"); graph.Print(); /* 頂点を追加 */ Vertex v5 = new(6); graph.AddVertex(v5); Console.WriteLine("\n頂点 6 を追加した後、グラフは"); graph.Print(); /* 頂点を削除 */ // 頂点 3 は v[1] graph.RemoveVertex(v[1]); Console.WriteLine("\n頂点 3 を削除した後、グラフは"); graph.Print(); } } ================================================ FILE: ja/codes/csharp/chapter_graph/graph_adjacency_matrix.cs ================================================ /** * File: graph_adjacency_matrix.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_graph; /* 隣接行列に基づく無向グラフクラス */ class GraphAdjMat { List vertices; // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す List> adjMat; // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 /* コンストラクタ */ public GraphAdjMat(int[] vertices, int[][] edges) { this.vertices = []; this.adjMat = []; // 頂点を追加 foreach (int val in vertices) { AddVertex(val); } // 辺を追加 // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する foreach (int[] e in edges) { AddEdge(e[0], e[1]); } } /* 頂点数を取得 */ int Size() { return vertices.Count; } /* 頂点を追加 */ public void AddVertex(int val) { int n = Size(); // 頂点リストに新しい頂点の値を追加 vertices.Add(val); // 隣接行列に 1 行追加 List newRow = new(n); for (int j = 0; j < n; j++) { newRow.Add(0); } adjMat.Add(newRow); // 隣接行列に 1 列追加 foreach (List row in adjMat) { row.Add(0); } } /* 頂点を削除 */ public void RemoveVertex(int index) { if (index >= Size()) throw new IndexOutOfRangeException(); // 頂点リストから index の頂点を削除する vertices.RemoveAt(index); // 隣接行列で index 行を削除する adjMat.RemoveAt(index); // 隣接行列で index 列を削除する foreach (List row in adjMat) { row.RemoveAt(index); } } /* 辺を追加 */ // 引数 i, j は vertices の要素インデックスに対応する public void AddEdge(int i, int j) { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) throw new IndexOutOfRangeException(); // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす adjMat[i][j] = 1; adjMat[j][i] = 1; } /* 辺を削除 */ // 引数 i, j は vertices の要素インデックスに対応する public void RemoveEdge(int i, int j) { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) throw new IndexOutOfRangeException(); adjMat[i][j] = 0; adjMat[j][i] = 0; } /* 隣接行列を出力 */ public void Print() { Console.Write("頂点リスト = "); PrintUtil.PrintList(vertices); Console.WriteLine("隣接行列 ="); PrintUtil.PrintMatrix(adjMat); } } public class graph_adjacency_matrix { [Test] public void Test() { /* 無向グラフを初期化 */ // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 int[] vertices = [1, 3, 2, 5, 4]; int[][] edges = [ [0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4] ]; GraphAdjMat graph = new(vertices, edges); Console.WriteLine("\n初期化後、グラフは"); graph.Print(); /* 辺を追加 */ // 頂点 1, 2 のインデックスはそれぞれ 0, 2 graph.AddEdge(0, 2); Console.WriteLine("\n辺 1-2 を追加した後、グラフは"); graph.Print(); /* 辺を削除 */ // 頂点 1, 3 のインデックスはそれぞれ 0, 1 graph.RemoveEdge(0, 1); Console.WriteLine("\n辺 1-3 を削除した後、グラフは"); graph.Print(); /* 頂点を追加 */ graph.AddVertex(6); Console.WriteLine("\n頂点 6 を追加した後、グラフは"); graph.Print(); /* 頂点を削除 */ // 頂点 3 のインデックスは 1 graph.RemoveVertex(1); Console.WriteLine("\n頂点 3 を削除した後、グラフは"); graph.Print(); } } ================================================ FILE: ja/codes/csharp/chapter_graph/graph_bfs.cs ================================================ /** * File: graph_bfs.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_graph; public class graph_bfs { /* 幅優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする List GraphBFS(GraphAdjList graph, Vertex startVet) { // 頂点の走査順序 List res = []; // 訪問済み頂点を記録するためのハッシュ集合 HashSet visited = [startVet]; // BFS の実装にキューを用いる Queue que = new(); que.Enqueue(startVet); // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す while (que.Count > 0) { Vertex vet = que.Dequeue(); // 先頭の頂点をデキュー res.Add(vet); // 訪問した頂点を記録 foreach (Vertex adjVet in graph.adjList[vet]) { if (visited.Contains(adjVet)) { continue; // 訪問済みの頂点をスキップ } que.Enqueue(adjVet); // 未訪問の頂点のみをキューに追加 visited.Add(adjVet); // この頂点を訪問済みにする } } // 頂点の走査順を返す return res; } [Test] public void Test() { /* 無向グラフを初期化 */ Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]] ]; GraphAdjList graph = new(edges); Console.WriteLine("\n初期化後、グラフは"); graph.Print(); /* 幅優先探索 */ List res = GraphBFS(graph, v[0]); Console.WriteLine("\n幅優先探索(BFS)の頂点順序は"); Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); } } ================================================ FILE: ja/codes/csharp/chapter_graph/graph_dfs.cs ================================================ /** * File: graph_dfs.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_graph; public class graph_dfs { /* 深さ優先走査の補助関数 */ void DFS(GraphAdjList graph, HashSet visited, List res, Vertex vet) { res.Add(vet); // 訪問した頂点を記録 visited.Add(vet); // この頂点を訪問済みにする // この頂点のすべての隣接頂点を走査 foreach (Vertex adjVet in graph.adjList[vet]) { if (visited.Contains(adjVet)) { continue; // 訪問済みの頂点をスキップ } // 隣接頂点を再帰的に訪問 DFS(graph, visited, res, adjVet); } } /* 深さ優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする List GraphDFS(GraphAdjList graph, Vertex startVet) { // 頂点の走査順序 List res = []; // 訪問済み頂点を記録するためのハッシュ集合 HashSet visited = []; DFS(graph, visited, res, startVet); return res; } [Test] public void Test() { /* 無向グラフを初期化 */ Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; GraphAdjList graph = new(edges); Console.WriteLine("\n初期化後、グラフは"); graph.Print(); /* 深さ優先探索 */ List res = GraphDFS(graph, v[0]); Console.WriteLine("\n深さ優先探索(DFS)の頂点順序は"); Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); } } ================================================ FILE: ja/codes/csharp/chapter_greedy/coin_change_greedy.cs ================================================ /** * File: coin_change_greedy.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class coin_change_greedy { /* コイン交換:貪欲法 */ int CoinChangeGreedy(int[] coins, int amt) { // coins リストはソート済みと仮定する int i = coins.Length - 1; int count = 0; // 残額がなくなるまで貪欲選択を繰り返す while (amt > 0) { // 残額以下で最も近い硬貨を見つける while (i > 0 && coins[i] > amt) { i--; } // coins[i] を選択する amt -= coins[i]; count++; } // 実行可能な解が見つからなければ -1 を返す return amt == 0 ? count : -1; } [Test] public void Test() { // 貪欲法:大域最適解を保証できる int[] coins = [1, 5, 10, 20, 50, 100]; int amt = 186; int res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("金額 " + amt + " を作るのに必要な最小硬貨枚数は " + res); // 貪欲法:大域最適解を保証できない coins = [1, 20, 50]; amt = 60; res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("金額 " + amt + " を作るのに必要な最小硬貨枚数は " + res); Console.WriteLine("実際に必要な最小枚数は 3、つまり 20 + 20 + 20"); // 貪欲法:大域最適解を保証できない coins = [1, 49, 50]; amt = 98; res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("金額 " + amt + " を作るのに必要な最小硬貨枚数は " + res); Console.WriteLine("実際に必要な最小枚数は 2、つまり 49 + 49"); } } ================================================ FILE: ja/codes/csharp/chapter_greedy/fractional_knapsack.cs ================================================ /** * File: fractional_knapsack.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; /* 品物 */ class Item(int w, int v) { public int w = w; // 品物の重さ public int v = v; // 品物の価値 } public class fractional_knapsack { /* 分数ナップサック:貪欲法 */ double FractionalKnapsack(int[] wgt, int[] val, int cap) { // 重さと価値の 2 属性を持つ品物リストを作成 Item[] items = new Item[wgt.Length]; for (int i = 0; i < wgt.Length; i++) { items[i] = new Item(wgt[i], val[i]); } // 単位価値 item.v / item.w の高い順にソートする Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w)); // 貪欲選択を繰り返す double res = 0; foreach (Item item in items) { if (item.w <= cap) { // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる res += item.v; cap -= item.w; } else { // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる res += (double)item.v / item.w * cap; // 残り容量がないため、ループを抜ける break; } } return res; } [Test] public void Test() { int[] wgt = [10, 20, 30, 40, 50]; int[] val = [50, 120, 150, 210, 240]; int cap = 50; // 貪欲法 double res = FractionalKnapsack(wgt, val, cap); Console.WriteLine("バックパック容量を超えない最大価値は " + res); } } ================================================ FILE: ja/codes/csharp/chapter_greedy/max_capacity.cs ================================================ /** * File: max_capacity.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class max_capacity { /* 最大容量:貪欲法 */ int MaxCapacity(int[] ht) { // i, j を初期化し、それぞれ配列の両端に置く int i = 0, j = ht.Length - 1; // 初期の最大容量は 0 int res = 0; // 2 枚の板が出会うまで貪欲選択を繰り返す while (i < j) { // 最大容量を更新する int cap = Math.Min(ht[i], ht[j]) * (j - i); res = Math.Max(res, cap); // 短い方を内側へ動かす if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } [Test] public void Test() { int[] ht = [3, 8, 5, 2, 7, 7, 3, 4]; // 貪欲法 int res = MaxCapacity(ht); Console.WriteLine("最大容量は " + res); } } ================================================ FILE: ja/codes/csharp/chapter_greedy/max_product_cutting.cs ================================================ /** * File: max_product_cutting.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class max_product_cutting { /* 最大切断積:貪欲法 */ int MaxProductCutting(int n) { // n <= 3 のときは、必ず 1 を切り出す if (n <= 3) { return 1 * (n - 1); } // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする int a = n / 3; int b = n % 3; if (b == 1) { // 余りが 1 のときは、1 * 3 を 2 * 2 に変える return (int)Math.Pow(3, a - 1) * 2 * 2; } if (b == 2) { // 余りが 2 のときは、そのままにする return (int)Math.Pow(3, a) * 2; } // 余りが 0 のときは、そのままにする return (int)Math.Pow(3, a); } [Test] public void Test() { int n = 58; // 貪欲法 int res = MaxProductCutting(n); Console.WriteLine("最大分割積は" + res); } } ================================================ FILE: ja/codes/csharp/chapter_hashing/array_hash_map.cs ================================================ /** * File: array_hash_map.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_hashing; /* キーと値の組 int->string */ class Pair(int key, string val) { public int key = key; public string val = val; } /* 配列ベースのハッシュテーブル */ class ArrayHashMap { List buckets; public ArrayHashMap() { // 100 個のバケットを含む配列を初期化 buckets = []; for (int i = 0; i < 100; i++) { buckets.Add(null); } } /* ハッシュ関数 */ int HashFunc(int key) { int index = key % 100; return index; } /* 検索操作 */ public string? Get(int key) { int index = HashFunc(key); Pair? pair = buckets[index]; if (pair == null) return null; return pair.val; } /* 追加操作 */ public void Put(int key, string val) { Pair pair = new(key, val); int index = HashFunc(key); buckets[index] = pair; } /* 削除操作 */ public void Remove(int key) { int index = HashFunc(key); // null に設定し、削除を表す buckets[index] = null; } /* すべてのキーと値のペアを取得 */ public List PairSet() { List pairSet = []; foreach (Pair? pair in buckets) { if (pair != null) pairSet.Add(pair); } return pairSet; } /* すべてのキーを取得 */ public List KeySet() { List keySet = []; foreach (Pair? pair in buckets) { if (pair != null) keySet.Add(pair.key); } return keySet; } /* すべての値を取得 */ public List ValueSet() { List valueSet = []; foreach (Pair? pair in buckets) { if (pair != null) valueSet.Add(pair.val); } return valueSet; } /* ハッシュテーブルを出力 */ public void Print() { foreach (Pair kv in PairSet()) { Console.WriteLine(kv.key + " -> " + kv.val); } } } public class array_hash_map { [Test] public void Test() { /* ハッシュテーブルを初期化 */ ArrayHashMap map = new(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.Put(12836, "シャオハー"); map.Put(15937, "シャオルオ"); map.Put(16750, "シャオスワン"); map.Put(13276, "シャオファー"); map.Put(10583, "シャオヤー"); Console.WriteLine("\n追加完了後、ハッシュテーブルは\nKey -> Value"); map.Print(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 string? name = map.Get(15937); Console.WriteLine("\n学籍番号 15937 を入力すると、氏名 " + name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.Remove(10583); Console.WriteLine("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value"); map.Print(); /* ハッシュテーブルを走査 */ Console.WriteLine("\nキーと値のペア Key->Value を走査"); foreach (Pair kv in map.PairSet()) { Console.WriteLine(kv.key + " -> " + kv.val); } Console.WriteLine("\nキー Key のみを走査"); foreach (int key in map.KeySet()) { Console.WriteLine(key); } Console.WriteLine("\n値 Value のみを走査"); foreach (string val in map.ValueSet()) { Console.WriteLine(val); } } } ================================================ FILE: ja/codes/csharp/chapter_hashing/built_in_hash.cs ================================================ /** * File: built_in_hash.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; public class built_in_hash { [Test] public void Test() { int num = 3; int hashNum = num.GetHashCode(); Console.WriteLine("整数 " + num + " のハッシュ値は " + hashNum); bool bol = true; int hashBol = bol.GetHashCode(); Console.WriteLine("真偽値 " + bol + " のハッシュ値は " + hashBol); double dec = 3.14159; int hashDec = dec.GetHashCode(); Console.WriteLine("小数 " + dec + " のハッシュ値は " + hashDec); string str = "Hello アルゴリズム"; int hashStr = str.GetHashCode(); Console.WriteLine("文字列 " + str + " のハッシュ値は " + hashStr); object[] arr = [12836, "シャオハー"]; int hashTup = arr.GetHashCode(); Console.WriteLine("配列 [" + string.Join(", ", arr) + "] のハッシュ値は " + hashTup); ListNode obj = new(0); int hashObj = obj.GetHashCode(); Console.WriteLine("ノードオブジェクト " + obj + " のハッシュ値は " + hashObj); } } ================================================ FILE: ja/codes/csharp/chapter_hashing/hash_map.cs ================================================ /** * File: hash_map.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_hashing; public class hash_map { [Test] public void Test() { /* ハッシュテーブルを初期化 */ Dictionary map = new() { /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 { 12836, "シャオハー" }, { 15937, "シャオルオ" }, { 16750, "シャオスワン" }, { 13276, "シャオファー" }, { 10583, "シャオヤー" } }; Console.WriteLine("\n追加完了後、ハッシュテーブルは\nKey -> Value"); PrintUtil.PrintHashMap(map); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 string name = map[15937]; Console.WriteLine("\n学籍番号 15937 を入力すると、氏名 " + name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.Remove(10583); Console.WriteLine("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value"); PrintUtil.PrintHashMap(map); /* ハッシュテーブルを走査 */ Console.WriteLine("\nキーと値のペア Key->Value を走査"); foreach (var kv in map) { Console.WriteLine(kv.Key + " -> " + kv.Value); } Console.WriteLine("\nキー Key のみを走査"); foreach (int key in map.Keys) { Console.WriteLine(key); } Console.WriteLine("\n値 Value のみを走査"); foreach (string val in map.Values) { Console.WriteLine(val); } } } ================================================ FILE: ja/codes/csharp/chapter_hashing/hash_map_chaining.cs ================================================ /** * File: hash_map_chaining.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; /* チェイン法ハッシュテーブル */ class HashMapChaining { int size; // キーと値のペア数 int capacity; // ハッシュテーブル容量 double loadThres; // リサイズを発動する負荷率のしきい値 int extendRatio; // 拡張倍率 List> buckets; // バケット配列 /* コンストラクタ */ public HashMapChaining() { size = 0; capacity = 4; loadThres = 2.0 / 3.0; extendRatio = 2; buckets = new List>(capacity); for (int i = 0; i < capacity; i++) { buckets.Add([]); } } /* ハッシュ関数 */ int HashFunc(int key) { return key % capacity; } /* 負荷率 */ double LoadFactor() { return (double)size / capacity; } /* 検索操作 */ public string? Get(int key) { int index = HashFunc(key); // バケットを走査し、key が見つかれば対応する val を返す foreach (Pair pair in buckets[index]) { if (pair.key == key) { return pair.val; } } // key が見つからない場合は null を返す return null; } /* 追加操作 */ public void Put(int key, string val) { // 負荷率がしきい値を超えたら、リサイズを実行 if (LoadFactor() > loadThres) { Extend(); } int index = HashFunc(key); // バケットを走査し、指定した key が見つかれば対応する val を更新して返す foreach (Pair pair in buckets[index]) { if (pair.key == key) { pair.val = val; return; } } // その key が存在しなければ、キーと値のペアを末尾に追加 buckets[index].Add(new Pair(key, val)); size++; } /* 削除操作 */ public void Remove(int key) { int index = HashFunc(key); // バケットを走査してキーと値のペアを削除 foreach (Pair pair in buckets[index].ToList()) { if (pair.key == key) { buckets[index].Remove(pair); size--; break; } } } /* ハッシュテーブルを拡張 */ void Extend() { // 元のハッシュテーブルを一時保存 List> bucketsTmp = buckets; // リサイズ後の新しいハッシュテーブルを初期化 capacity *= extendRatio; buckets = new List>(capacity); for (int i = 0; i < capacity; i++) { buckets.Add([]); } size = 0; // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す foreach (List bucket in bucketsTmp) { foreach (Pair pair in bucket) { Put(pair.key, pair.val); } } } /* ハッシュテーブルを出力 */ public void Print() { foreach (List bucket in buckets) { List res = []; foreach (Pair pair in bucket) { res.Add(pair.key + " -> " + pair.val); } foreach (string kv in res) { Console.WriteLine(kv); } } } } public class hash_map_chaining { [Test] public void Test() { /* ハッシュテーブルを初期化 */ HashMapChaining map = new(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.Put(12836, "シャオハー"); map.Put(15937, "シャオルオ"); map.Put(16750, "シャオスワン"); map.Put(13276, "シャオファー"); map.Put(10583, "シャオヤー"); Console.WriteLine("\n追加完了後、ハッシュテーブルは\nKey -> Value"); map.Print(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 string? name = map.Get(13276); Console.WriteLine("\n学籍番号 13276 を入力すると、氏名 " + name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.Remove(12836); Console.WriteLine("\n12836 を削除した後、ハッシュテーブルは\nKey -> Value"); map.Print(); } } ================================================ FILE: ja/codes/csharp/chapter_hashing/hash_map_open_addressing.cs ================================================ /** * File: hash_map_open_addressing.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; /* オープンアドレス法ハッシュテーブル */ class HashMapOpenAddressing { int size; // キーと値のペア数 int capacity = 4; // ハッシュテーブル容量 double loadThres = 2.0 / 3.0; // リサイズを発動する負荷率のしきい値 int extendRatio = 2; // 拡張倍率 Pair[] buckets; // バケット配列 Pair TOMBSTONE = new(-1, "-1"); // 削除済みマーク /* コンストラクタ */ public HashMapOpenAddressing() { size = 0; buckets = new Pair[capacity]; } /* ハッシュ関数 */ int HashFunc(int key) { return key % capacity; } /* 負荷率 */ double LoadFactor() { return (double)size / capacity; } /* key に対応するバケットインデックスを探す */ int FindBucket(int key) { int index = HashFunc(key); int firstTombstone = -1; // 線形プロービングを行い、空バケットに達したら終了 while (buckets[index] != null) { // key が見つかったら、対応するバケットのインデックスを返す if (buckets[index].key == key) { // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index]; buckets[index] = TOMBSTONE; return firstTombstone; // 移動後のバケットインデックスを返す } return index; // バケットのインデックスを返す } // 最初に見つかった削除マークを記録 if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index; } // バケットのインデックスを計算し、末尾を越えたら先頭に戻る index = (index + 1) % capacity; } // key が存在しない場合は追加位置のインデックスを返す return firstTombstone == -1 ? index : firstTombstone; } /* 検索操作 */ public string? Get(int key) { // key に対応するバケットインデックスを探す int index = FindBucket(key); // キーと値の組が見つかったら、対応する val を返す if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index].val; } // キーと値の組が存在しなければ null を返す return null; } /* 追加操作 */ public void Put(int key, string val) { // 負荷率がしきい値を超えたら、リサイズを実行 if (LoadFactor() > loadThres) { Extend(); } // key に対応するバケットインデックスを探す int index = FindBucket(key); // キーと値の組が見つかったら、val を上書きして返す if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index].val = val; return; } // キーと値の組が存在しない場合は、その組を追加する buckets[index] = new Pair(key, val); size++; } /* 削除操作 */ public void Remove(int key) { // key に対応するバケットインデックスを探す int index = FindBucket(key); // キーと値の組が見つかったら、削除マーカーで上書きする if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE; size--; } } /* ハッシュテーブルを拡張 */ void Extend() { // 元のハッシュテーブルを一時保存 Pair[] bucketsTmp = buckets; // リサイズ後の新しいハッシュテーブルを初期化 capacity *= extendRatio; buckets = new Pair[capacity]; size = 0; // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す foreach (Pair pair in bucketsTmp) { if (pair != null && pair != TOMBSTONE) { Put(pair.key, pair.val); } } } /* ハッシュテーブルを出力 */ public void Print() { foreach (Pair pair in buckets) { if (pair == null) { Console.WriteLine("null"); } else if (pair == TOMBSTONE) { Console.WriteLine("TOMBSTONE"); } else { Console.WriteLine(pair.key + " -> " + pair.val); } } } } public class hash_map_open_addressing { [Test] public void Test() { /* ハッシュテーブルを初期化 */ HashMapOpenAddressing map = new(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.Put(12836, "シャオハー"); map.Put(15937, "シャオルオ"); map.Put(16750, "シャオスワン"); map.Put(13276, "シャオファー"); map.Put(10583, "シャオヤー"); Console.WriteLine("\n追加完了後、ハッシュテーブルは\nKey -> Value"); map.Print(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 string? name = map.Get(13276); Console.WriteLine("\n学籍番号 13276 を入力すると、氏名 " + name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.Remove(16750); Console.WriteLine("\n16750 を削除した後、ハッシュテーブルは\nKey -> Value"); map.Print(); } } ================================================ FILE: ja/codes/csharp/chapter_hashing/simple_hash.cs ================================================ /** * File: simple_hash.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; public class simple_hash { /* 加算ハッシュ */ int AddHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = (hash + c) % MODULUS; } return (int)hash; } /* 乗算ハッシュ */ int MulHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = (31 * hash + c) % MODULUS; } return (int)hash; } /* XOR ハッシュ */ int XorHash(string key) { int hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash ^= c; } return hash & MODULUS; } /* 回転ハッシュ */ int RotHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS; } return (int)hash; } [Test] public void Test() { string key = "Hello アルゴリズム"; int hash = AddHash(key); Console.WriteLine("加算ハッシュ値は " + hash); hash = MulHash(key); Console.WriteLine("乗算ハッシュ値は " + hash); hash = XorHash(key); Console.WriteLine("XOR ハッシュ値は " + hash); hash = RotHash(key); Console.WriteLine("回転ハッシュ値は " + hash); } } ================================================ FILE: ja/codes/csharp/chapter_heap/heap.cs ================================================ /** * File: heap.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_heap; public class heap { void TestPush(PriorityQueue heap, int val) { heap.Enqueue(val, val); // 要素をヒープに追加 Console.WriteLine($"\n要素 {val} をヒープに追加した後\n"); PrintUtil.PrintHeap(heap); } void TestPop(PriorityQueue heap) { int val = heap.Dequeue(); // ヒープ頂点の要素を取り出す Console.WriteLine($"\nヒープの先頭要素 {val} を取り出した後\n"); PrintUtil.PrintHeap(heap); } [Test] public void Test() { /* ヒープを初期化 */ // 最小ヒープを初期化 PriorityQueue minHeap = new(); // 最大ヒープを初期化する(Comparer はラムダ式で変更できる) PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); Console.WriteLine("以下のテストケースは最大ヒープです"); /* 要素をヒープに追加 */ TestPush(maxHeap, 1); TestPush(maxHeap, 3); TestPush(maxHeap, 2); TestPush(maxHeap, 5); TestPush(maxHeap, 4); /* ヒープ頂点の要素を取得 */ int peek = maxHeap.Peek(); Console.WriteLine($"ヒープの先頭要素は {peek}"); /* ヒープ頂点の要素を取り出す */ // ヒープから取り出した要素は大きい順に並ぶ TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); /* ヒープのサイズを取得 */ int size = maxHeap.Count; Console.WriteLine($"ヒープの要素数は {size}"); /* ヒープが空かどうかを判定 */ bool isEmpty = maxHeap.Count == 0; Console.WriteLine($"ヒープは空か {isEmpty}"); /* リストを入力してヒープを構築 */ var list = new int[] { 1, 3, 2, 5, 4 }; minHeap = new PriorityQueue(list.Select(x => (x, x))); Console.WriteLine("リストを入力して最小ヒープを構築した後"); PrintUtil.PrintHeap(minHeap); } } ================================================ FILE: ja/codes/csharp/chapter_heap/my_heap.cs ================================================ /** * File: my_heap.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_heap; /* 最大ヒープ */ class MaxHeap { // 配列ではなくリストを使うことで、拡張を考慮する必要がない List maxHeap; /* コンストラクタ。空のヒープを作成する */ public MaxHeap() { maxHeap = []; } /* コンストラクタ。入力リストに基づいてヒープを構築 */ public MaxHeap(IEnumerable nums) { // リスト要素をそのままヒープに追加 maxHeap = new List(nums); // 葉ノード以外のすべてのノードをヒープ化 var size = Parent(this.Size() - 1); for (int i = size; i >= 0; i--) { SiftDown(i); } } /* 左子ノードのインデックスを取得 */ int Left(int i) { return 2 * i + 1; } /* 右子ノードのインデックスを取得 */ int Right(int i) { return 2 * i + 2; } /* 親ノードのインデックスを取得 */ int Parent(int i) { return (i - 1) / 2; // 切り捨て除算 } /* ヒープ先頭要素にアクセス */ public int Peek() { return maxHeap[0]; } /* 要素をヒープに追加 */ public void Push(int val) { // ノードを追加 maxHeap.Add(val); // 下から上へヒープ化 SiftUp(Size() - 1); } /* ヒープのサイズを取得 */ public int Size() { return maxHeap.Count; } /* ヒープが空かどうかを判定 */ public bool IsEmpty() { return Size() == 0; } /* ノード i から始めて、下から上へヒープ化 */ void SiftUp(int i) { while (true) { // ノード i の親ノードを取得 int p = Parent(i); // 「根ノードを越えた」または「ノードの修復が不要」な場合は、ヒープ化を終了する if (p < 0 || maxHeap[i] <= maxHeap[p]) break; // 2 つのノードを交換 Swap(i, p); // ループで下から上へヒープ化 i = p; } } /* 要素をヒープから取り出す */ public int Pop() { // 空判定の処理 if (IsEmpty()) throw new IndexOutOfRangeException(); // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) Swap(0, Size() - 1); // ノードを削除 int val = maxHeap.Last(); maxHeap.RemoveAt(Size() - 1); // 上から下へヒープ化 SiftDown(0); // ヒープ先頭要素を返す return val; } /* ノード i から始めて、上から下へヒープ化 */ void SiftDown(int i) { while (true) { // ノード i, l, r のうち値が最大のノードを ma とする int l = Left(i), r = Right(i), ma = i; if (l < Size() && maxHeap[l] > maxHeap[ma]) ma = l; if (r < Size() && maxHeap[r] > maxHeap[ma]) ma = r; // 「ノード i が最大」または「葉ノードを越えた」場合は、ヒープ化を終了する if (ma == i) break; // 2 つのノードを交換 Swap(i, ma); // ループで上から下へヒープ化 i = ma; } } /* 要素を交換 */ void Swap(int i, int p) { (maxHeap[i], maxHeap[p]) = (maxHeap[p], maxHeap[i]); } /* ヒープ(二分木)を出力 */ public void Print() { var queue = new Queue(maxHeap); PrintUtil.PrintHeap(queue); } } public class my_heap { [Test] public void Test() { /* 最大ヒープを初期化 */ MaxHeap maxHeap = new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); Console.WriteLine("\nリストを入力してヒープを構築した後"); maxHeap.Print(); /* ヒープ頂点の要素を取得 */ int peek = maxHeap.Peek(); Console.WriteLine($"ヒープの先頭要素は {peek}"); /* 要素をヒープに追加 */ int val = 7; maxHeap.Push(val); Console.WriteLine($"要素 {val} をヒープに追加した後"); maxHeap.Print(); /* ヒープ頂点の要素を取り出す */ peek = maxHeap.Pop(); Console.WriteLine($"ヒープの先頭要素 {peek} を取り出した後"); maxHeap.Print(); /* ヒープのサイズを取得 */ int size = maxHeap.Size(); Console.WriteLine($"ヒープの要素数は {size}"); /* ヒープが空かどうかを判定 */ bool isEmpty = maxHeap.IsEmpty(); Console.WriteLine($"ヒープは空か {isEmpty}"); } } ================================================ FILE: ja/codes/csharp/chapter_heap/top_k.cs ================================================ /** * File: top_k.cs * Created Time: 2023-06-14 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_heap; public class top_k { /* ヒープに基づいて配列中の最大の k 個の要素を探す */ PriorityQueue TopKHeap(int[] nums, int k) { // 最小ヒープを初期化 PriorityQueue heap = new(); // 配列の先頭 k 個の要素をヒープに追加 for (int i = 0; i < k; i++) { heap.Enqueue(nums[i], nums[i]); } // k+1 番目の要素から開始し、ヒープ長を k に保つ for (int i = k; i < nums.Length; i++) { // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する if (nums[i] > heap.Peek()) { heap.Dequeue(); heap.Enqueue(nums[i], nums[i]); } } return heap; } [Test] public void Test() { int[] nums = [1, 7, 6, 3, 2]; int k = 3; PriorityQueue res = TopKHeap(nums, k); Console.WriteLine("最大の " + k + " 個の要素は"); PrintUtil.PrintHeap(res); } } ================================================ FILE: ja/codes/csharp/chapter_searching/binary_search.cs ================================================ /** * File: binary_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class binary_search { /* 二分探索(両閉区間) */ int BinarySearch(int[] nums, int target) { // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す int i = 0, j = nums.Length - 1; // ループし、探索区間が空になったら終了する(i > j で空) while (i <= j) { int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) // この場合、target は区間 [m+1, j] にある i = m + 1; else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある j = m - 1; else // 目標要素が見つかったらそのインデックスを返す return m; } // 目標要素が見つからなければ -1 を返す return -1; } /* 二分探索(左閉右開区間) */ int BinarySearchLCRO(int[] nums, int target) { // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す int i = 0, j = nums.Length; // ループし、探索区間が空になったら終了する(i = j で空) while (i < j) { int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) // この場合、target は区間 [m+1, j) にある i = m + 1; else if (nums[m] > target) // この場合、target は区間 [i, m) にある j = m; else // 目標要素が見つかったらそのインデックスを返す return m; } // 目標要素が見つからなければ -1 を返す return -1; } [Test] public void Test() { int target = 6; int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分探索(両閉区間) */ int index = BinarySearch(nums, target); Console.WriteLine("対象要素 6 のインデックス = " + index); /* 二分探索(左閉右開区間) */ index = BinarySearchLCRO(nums, target); Console.WriteLine("対象要素 6 のインデックス = " + index); } } ================================================ FILE: ja/codes/csharp/chapter_searching/binary_search_edge.cs ================================================ /** * File: binary_search_edge.cs * Created Time: 2023-08-06 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_searching; public class binary_search_edge { /* 最も左の target を二分探索 */ int BinarySearchLeftEdge(int[] nums, int target) { // target の挿入位置を探すのと等価 int i = binary_search_insertion.BinarySearchInsertion(nums, target); // target が見つからなければ、-1 を返す if (i == nums.Length || nums[i] != target) { return -1; } // target が見つかったら、インデックス i を返す return i; } /* 最も右の target を二分探索 */ int BinarySearchRightEdge(int[] nums, int target) { // 最左の target + 1 を探す問題に変換する int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1); // j は最も右の target を指し、i は target より大きい最初の要素を指す int j = i - 1; // target が見つからなければ、-1 を返す if (j == -1 || nums[j] != target) { return -1; } // target が見つかったら、インデックス j を返す return j; } [Test] public void Test() { // 重複要素を含む配列 int[] nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; Console.WriteLine("\n配列 nums = " + nums.PrintList()); // 二分探索で左端と右端を探す foreach (int target in new int[] { 6, 7 }) { int index = BinarySearchLeftEdge(nums, target); Console.WriteLine("一番左の要素 " + target + " のインデックスは " + index); index = BinarySearchRightEdge(nums, target); Console.WriteLine("一番右の要素 " + target + " のインデックスは " + index); } } } ================================================ FILE: ja/codes/csharp/chapter_searching/binary_search_insertion.cs ================================================ /** * File: binary_search_insertion.cs * Created Time: 2023-08-06 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_searching; public class binary_search_insertion { /* 二分探索で挿入位置を探す(重複要素なし) */ public static int BinarySearchInsertionSimple(int[] nums, int target) { int i = 0, j = nums.Length - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) { i = m + 1; // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1; // target は区間 [i, m-1] にある } else { return m; // target が見つかったら、挿入位置 m を返す } } // target が見つからなければ、挿入位置 i を返す return i; } /* 二分探索で挿入位置を探す(重複要素あり) */ public static int BinarySearchInsertion(int[] nums, int target) { int i = 0, j = nums.Length - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) { i = m + 1; // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1; // target は区間 [i, m-1] にある } else { j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある } } // 挿入位置 i を返す return i; } [Test] public void Test() { // 重複要素のない配列 int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; Console.WriteLine("\n配列 nums = " + nums.PrintList()); // 二分探索で挿入位置を探す foreach (int target in new int[] { 6, 9 }) { int index = BinarySearchInsertionSimple(nums, target); Console.WriteLine("要素 " + target + " の挿入位置のインデックスは " + index); } // 重複要素を含む配列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; Console.WriteLine("\n配列 nums = " + nums.PrintList()); // 二分探索で挿入位置を探す foreach (int target in new int[] { 2, 6, 20 }) { int index = BinarySearchInsertion(nums, target); Console.WriteLine("要素 " + target + " の挿入位置のインデックスは " + index); } } } ================================================ FILE: ja/codes/csharp/chapter_searching/hashing_search.cs ================================================ /** * File: hashing_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class hashing_search { /* ハッシュ探索(配列) */ int HashingSearchArray(Dictionary map, int target) { // ハッシュテーブルの key: 目標要素、value: インデックス // ハッシュテーブルにこの key がなければ -1 を返す return map.GetValueOrDefault(target, -1); } /* ハッシュ探索(連結リスト) */ ListNode? HashingSearchLinkedList(Dictionary map, int target) { // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト // ハッシュテーブルにこの key がなければ null を返す return map.GetValueOrDefault(target); } [Test] public void Test() { int target = 3; /* ハッシュ探索(配列) */ int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // ハッシュテーブルを初期化 Dictionary map = []; for (int i = 0; i < nums.Length; i++) { map[nums[i]] = i; // key: 要素、value: インデックス } int index = HashingSearchArray(map, target); Console.WriteLine("対象要素 3 のインデックス = " + index); /* ハッシュ探索(連結リスト) */ ListNode? head = ListNode.ArrToLinkedList(nums); // ハッシュテーブルを初期化 Dictionary map1 = []; while (head != null) { map1[head.val] = head; // key: ノード値、value: ノード head = head.next; } ListNode? node = HashingSearchLinkedList(map1, target); Console.WriteLine("目標ノード値 3 に対応するノードオブジェクトは " + node); } } ================================================ FILE: ja/codes/csharp/chapter_searching/linear_search.cs ================================================ /** * File: linear_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class linear_search { /* 線形探索(配列) */ int LinearSearchArray(int[] nums, int target) { // 配列を走査 for (int i = 0; i < nums.Length; i++) { // 目標要素が見つかったらそのインデックスを返す if (nums[i] == target) return i; } // 目標要素が見つからなければ -1 を返す return -1; } /* 線形探索(連結リスト) */ ListNode? LinearSearchLinkedList(ListNode? head, int target) { // 連結リストを走査 while (head != null) { // 対象ノードが見つかったら、それを返す if (head.val == target) return head; head = head.next; } // 対象ノードが見つからない場合は null を返す return null; } [Test] public void Test() { int target = 3; /* 配列で線形探索を行う */ int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; int index = LinearSearchArray(nums, target); Console.WriteLine("対象要素 3 のインデックス = " + index); /* 連結リストで線形探索を行う */ ListNode? head = ListNode.ArrToLinkedList(nums); ListNode? node = LinearSearchLinkedList(head, target); Console.WriteLine("目標ノード値 3 に対応するノードオブジェクトは " + node); } } ================================================ FILE: ja/codes/csharp/chapter_searching/two_sum.cs ================================================ /** * File: two_sum.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class two_sum { /* 方法 1:総当たり列挙 */ int[] TwoSumBruteForce(int[] nums, int target) { int size = nums.Length; // 2重ループのため、時間計算量は O(n^2) for (int i = 0; i < size - 1; i++) { for (int j = i + 1; j < size; j++) { if (nums[i] + nums[j] == target) return [i, j]; } } return []; } /* 方法 2:補助ハッシュテーブル */ int[] TwoSumHashTable(int[] nums, int target) { int size = nums.Length; // 補助ハッシュテーブルを使用し、空間計算量は O(n) Dictionary dic = []; // 単一ループで、時間計算量は O(n) for (int i = 0; i < size; i++) { if (dic.ContainsKey(target - nums[i])) { return [dic[target - nums[i]], i]; } dic.Add(nums[i], i); } return []; } [Test] public void Test() { // ======= Test Case ======= int[] nums = [2, 7, 11, 15]; int target = 13; // ====== Driver Code ====== // 方法 1 int[] res = TwoSumBruteForce(nums, target); Console.WriteLine("方法1 res = " + string.Join(",", res)); // 方法 2 res = TwoSumHashTable(nums, target); Console.WriteLine("方法2 res = " + string.Join(",", res)); } } ================================================ FILE: ja/codes/csharp/chapter_sorting/bubble_sort.cs ================================================ /** * File: bubble_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class bubble_sort { /* バブルソート */ void BubbleSort(int[] nums) { // 外側のループ:未ソート区間は [0, i] for (int i = nums.Length - 1; i > 0; i--) { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); } } } } /* バブルソート(フラグ最適化) */ void BubbleSortWithFlag(int[] nums) { // 外側のループ:未ソート区間は [0, i] for (int i = nums.Length - 1; i > 0; i--) { bool flag = false; // フラグを初期化する // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); flag = true; // 交換する要素を記録 } } if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了 } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; BubbleSort(nums); Console.WriteLine("バブルソート完了後 nums = " + string.Join(",", nums)); int[] nums1 = [4, 1, 3, 1, 5, 2]; BubbleSortWithFlag(nums1); Console.WriteLine("バブルソート完了後 nums1 = " + string.Join(",", nums1)); } } ================================================ FILE: ja/codes/csharp/chapter_sorting/bucket_sort.cs ================================================ /** * File: bucket_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class bucket_sort { /* バケットソート */ void BucketSort(float[] nums) { // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする int k = nums.Length / 2; List> buckets = []; for (int i = 0; i < k; i++) { buckets.Add([]); } // 1. 配列要素を各バケットに振り分ける foreach (float num in nums) { // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する int i = (int)(num * k); // num をバケット i に追加 buckets[i].Add(num); } // 2. 各バケットをソートする foreach (List bucket in buckets) { // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい bucket.Sort(); } // 3. バケットを走査して結果を結合 int j = 0; foreach (List bucket in buckets) { foreach (float num in bucket) { nums[j++] = num; } } } [Test] public void Test() { // 入力データは範囲 [0, 1) の浮動小数点数とする float[] nums = [0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f]; BucketSort(nums); Console.WriteLine("バケットソート完了後 nums = " + string.Join(" ", nums)); } } ================================================ FILE: ja/codes/csharp/chapter_sorting/counting_sort.cs ================================================ /** * File: counting_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class counting_sort { /* 計数ソート */ // 簡易実装のため、オブジェクトのソートには使えない void CountingSortNaive(int[] nums) { // 1. 配列の最大要素 m を求める int m = 0; foreach (int num in nums) { m = Math.Max(m, num); } // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す int[] counter = new int[m + 1]; foreach (int num in nums) { counter[num]++; } // 3. counter を走査し、各要素を元の配列 nums に書き戻す int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* 計数ソート */ // 完全な実装で、オブジェクトをソートでき、かつ安定ソートである void CountingSort(int[] nums) { // 1. 配列の最大要素 m を求める int m = 0; foreach (int num in nums) { m = Math.Max(m, num); } // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す int[] counter = new int[m + 1]; foreach (int num in nums) { counter[num]++; } // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する // つまり counter[num]-1 は、num が res に最後に現れるインデックス for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. nums を逆順に走査し、各要素を結果配列 res に格納する // 結果を記録するための配列 res を初期化 int n = nums.Length; int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // num を対応するインデックスに配置 counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る } // 結果配列 res で元の配列 nums を上書きする for (int i = 0; i < n; i++) { nums[i] = res[i]; } } [Test] public void Test() { int[] nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; CountingSortNaive(nums); Console.WriteLine("カウントソート(オブジェクトはソート不可)完了後 nums = " + string.Join(" ", nums)); int[] nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; CountingSort(nums1); Console.WriteLine("カウントソート完了後 nums1 = " + string.Join(" ", nums)); } } ================================================ FILE: ja/codes/csharp/chapter_sorting/heap_sort.cs ================================================ /** * File: heap_sort.cs * Created Time: 2023-06-01 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class heap_sort { /* ヒープの長さは n。ノード i から下方向にヒープ化 */ void SiftDown(int[] nums, int n, int i) { while (true) { // ノード i, l, r のうち値が最大のノードを ma とする int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; if (l < n && nums[l] > nums[ma]) ma = l; if (r < n && nums[r] > nums[ma]) ma = r; // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma == i) break; // 2 つのノードを交換 (nums[ma], nums[i]) = (nums[i], nums[ma]); // ループで上から下へヒープ化 i = ma; } } /* ヒープソート */ void HeapSort(int[] nums) { // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する for (int i = nums.Length / 2 - 1; i >= 0; i--) { SiftDown(nums, nums.Length, i); } // ヒープから最大要素を取り出し、n-1 回繰り返す for (int i = nums.Length - 1; i > 0; i--) { // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) (nums[i], nums[0]) = (nums[0], nums[i]); // 根ノードを起点に、上から下へヒープ化 SiftDown(nums, i, 0); } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; HeapSort(nums); Console.WriteLine("ヒープソート完了後 nums = " + string.Join(" ", nums)); } } ================================================ FILE: ja/codes/csharp/chapter_sorting/insertion_sort.cs ================================================ /** * File: insertion_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class insertion_sort { /* 挿入ソート */ void InsertionSort(int[] nums) { // 外側ループ:整列済み区間は [0, i-1] for (int i = 1; i < nums.Length; i++) { int bas = nums[i], j = i - 1; // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する while (j >= 0 && nums[j] > bas) { nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する j--; } nums[j + 1] = bas; // base を正しい位置に配置する } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; InsertionSort(nums); Console.WriteLine("挿入ソート完了後 nums = " + string.Join(",", nums)); } } ================================================ FILE: ja/codes/csharp/chapter_sorting/merge_sort.cs ================================================ /** * File: merge_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class merge_sort { /* 左部分配列と右部分配列をマージ */ void Merge(int[] nums, int left, int mid, int right) { // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] // マージ結果を格納する一時配列 tmp を作成 int[] tmp = new int[right - left + 1]; // 左右の部分配列の開始インデックスを初期化する int i = left, j = mid + 1, k = 0; // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } // 左右の部分配列の残り要素を一時配列にコピーする while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする for (k = 0; k < tmp.Length; ++k) { nums[left + k] = tmp[k]; } } /* マージソート */ void MergeSort(int[] nums, int left, int right) { // 終了条件 if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了 // 分割フェーズ int mid = left + (right - left) / 2; // 中点を計算 MergeSort(nums, left, mid); // 左部分配列を再帰処理 MergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 // マージフェーズ Merge(nums, left, mid, right); } [Test] public void Test() { /* マージソート */ int[] nums = [7, 3, 2, 6, 0, 1, 5, 4]; MergeSort(nums, 0, nums.Length - 1); Console.WriteLine("マージソート完了後 nums = " + string.Join(",", nums)); } } ================================================ FILE: ja/codes/csharp/chapter_sorting/quick_sort.cs ================================================ /** * File: quick_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; class quickSort { /* 要素の交換 */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* 番兵分割 */ static int Partition(int[] nums, int left, int right) { // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す Swap(nums, i, j); // この 2 つの要素を交換 } Swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート */ public static void QuickSort(int[] nums, int left, int right) { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return; // 番兵分割 int pivot = Partition(nums, left, right); // 左右の部分配列を再帰処理 QuickSort(nums, left, pivot - 1); QuickSort(nums, pivot + 1, right); } } /* クイックソートクラス(中央値ピボット最適化) */ class QuickSortMedian { /* 要素の交換 */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* 3つの候補要素の中央値を選ぶ */ static int MedianThree(int[] nums, int left, int mid, int right) { int l = nums[left], m = nums[mid], r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // m は l と r の間 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l は m と r の間 return right; } /* 番兵による分割処理(3 点中央値) */ static int Partition(int[] nums, int left, int right) { // 3つの候補要素の中央値を選ぶ int med = MedianThree(nums, left, (left + right) / 2, right); // 中央値を配列の最左端に交換する Swap(nums, left, med); // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す Swap(nums, i, j); // この 2 つの要素を交換 } Swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート */ public static void QuickSort(int[] nums, int left, int right) { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return; // 番兵分割 int pivot = Partition(nums, left, right); // 左右の部分配列を再帰処理 QuickSort(nums, left, pivot - 1); QuickSort(nums, pivot + 1, right); } } /* クイックソートクラス(再帰深度最適化) */ class QuickSortTailCall { /* 要素の交換 */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* 番兵分割 */ static int Partition(int[] nums, int left, int right) { // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す Swap(nums, i, j); // この 2 つの要素を交換 } Swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート(再帰深度最適化) */ public static void QuickSort(int[] nums, int left, int right) { // 部分配列の長さが 1 なら終了 while (left < right) { // 番兵による分割処理 int pivot = Partition(nums, left, right); // 2 つの部分配列のうち短いほうにクイックソートを適用する if (pivot - left < right - pivot) { QuickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] } else { QuickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] } } } } public class quick_sort { [Test] public void Test() { /* クイックソート */ int[] nums = [2, 4, 1, 0, 3, 5]; quickSort.QuickSort(nums, 0, nums.Length - 1); Console.WriteLine("クイックソート完了後 nums = " + string.Join(",", nums)); /* クイックソート(中央値の基準値で最適化) */ int[] nums1 = [2, 4, 1, 0, 3, 5]; QuickSortMedian.QuickSort(nums1, 0, nums1.Length - 1); Console.WriteLine("クイックソート(中央値ピボット最適化)完了後 nums1 = " + string.Join(",", nums1)); /* クイックソート(再帰深度最適化) */ int[] nums2 = [2, 4, 1, 0, 3, 5]; QuickSortTailCall.QuickSort(nums2, 0, nums2.Length - 1); Console.WriteLine("クイックソート(再帰深度最適化)完了後 nums2 = " + string.Join(",", nums2)); } } ================================================ FILE: ja/codes/csharp/chapter_sorting/radix_sort.cs ================================================ /** * File: radix_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class radix_sort { /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ int Digit(int num, int exp) { // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す return (num / exp) % 10; } /* 計数ソート(nums の k 桁目でソート) */ void CountingSortDigit(int[] nums, int exp) { // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 int[] counter = new int[10]; int n = nums.Length; // 0~9 の各数字の出現回数を集計する for (int i = 0; i < n; i++) { int d = Digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする counter[d]++; // 数字 d の出現回数を数える } // 累積和を求め、「出現回数」を「配列インデックス」に変換する for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int d = Digit(nums[i], exp); int j = counter[d] - 1; // d の配列内インデックス j を取得する res[j] = nums[i]; // 現在の要素をインデックス j に格納する counter[d]--; // d の個数を 1 減らす } // 結果で元の配列 nums を上書きする for (int i = 0; i < n; i++) { nums[i] = res[i]; } } /* 基数ソート */ void RadixSort(int[] nums) { // 最大桁数の判定用に配列の最大要素を取得 int m = int.MinValue; foreach (int num in nums) { if (num > m) m = num; } // 下位桁から上位桁の順に走査する for (int exp = 1; exp <= m; exp *= 10) { // 配列要素の k 桁目に対して計数ソートを行う // k = 1 -> exp = 1 // k = 2 -> exp = 10 // つまり exp = 10^(k-1) CountingSortDigit(nums, exp); } } [Test] public void Test() { // 基数ソート int[] nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ]; RadixSort(nums); Console.WriteLine("基数ソート完了後 nums = " + string.Join(" ", nums)); } } ================================================ FILE: ja/codes/csharp/chapter_sorting/selection_sort.cs ================================================ /** * File: selection_sort.cs * Created Time: 2023-06-01 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class selection_sort { /* 選択ソート */ void SelectionSort(int[] nums) { int n = nums.Length; // 外側ループ:未整列区間は [i, n-1] for (int i = 0; i < n - 1; i++) { // 内側のループ:未ソート区間の最小要素を見つける int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // 最小要素のインデックスを記録 } // その最小要素を未整列区間の先頭要素と交換する (nums[k], nums[i]) = (nums[i], nums[k]); } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; SelectionSort(nums); Console.WriteLine("選択ソート完了後 nums = " + string.Join(" ", nums)); } } ================================================ FILE: ja/codes/csharp/chapter_stack_and_queue/array_deque.cs ================================================ /** * File: array_deque.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_stack_and_queue; /* 循環配列ベースの両端キュー */ public class ArrayDeque { int[] nums; // 両端キューの要素を格納する配列 int front; // 先頭ポインタ。先頭要素を指す int queSize; // 両端キューの長さ /* コンストラクタ */ public ArrayDeque(int capacity) { nums = new int[capacity]; front = queSize = 0; } /* 両端キューの容量を取得 */ int Capacity() { return nums.Length; } /* 両端キューの長さを取得 */ public int Size() { return queSize; } /* 両端キューが空かどうかを判定 */ public bool IsEmpty() { return queSize == 0; } /* 循環配列のインデックスを計算 */ int Index(int i) { // 剰余演算により配列の先頭と末尾をつなげる // i が配列の末尾を越えたら先頭に戻る // i が配列の先頭を越えて前に出たら末尾に戻る return (i + Capacity()) % Capacity(); } /* キュー先頭にエンキュー */ public void PushFirst(int num) { if (queSize == Capacity()) { Console.WriteLine("両端キューは満杯です"); return; } // 先頭ポインタを左に 1 つ移動する // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする front = Index(front - 1); // num をキュー先頭に追加 nums[front] = num; queSize++; } /* キュー末尾にエンキュー */ public void PushLast(int num) { if (queSize == Capacity()) { Console.WriteLine("両端キューは満杯です"); return; } // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す int rear = Index(front + queSize); // num をキュー末尾に追加 nums[rear] = num; queSize++; } /* キュー先頭からデキュー */ public int PopFirst() { int num = PeekFirst(); // 先頭ポインタを 1 つ後ろへ進める front = Index(front + 1); queSize--; return num; } /* キュー末尾からデキュー */ public int PopLast() { int num = PeekLast(); queSize--; return num; } /* キュー先頭の要素にアクセス */ public int PeekFirst() { if (IsEmpty()) { throw new InvalidOperationException(); } return nums[front]; } /* キュー末尾の要素にアクセス */ public int PeekLast() { if (IsEmpty()) { throw new InvalidOperationException(); } // 末尾要素のインデックスを計算 int last = Index(front + queSize - 1); return nums[last]; } /* 出力用の配列を返す */ public int[] ToArray() { // 有効長の範囲内のリスト要素のみを変換 int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[Index(j)]; } return res; } } public class array_deque { [Test] public void Test() { /* 両端キューを初期化 */ ArrayDeque deque = new(10); deque.PushLast(3); deque.PushLast(2); deque.PushLast(5); Console.WriteLine("双方向キュー deque = " + string.Join(" ", deque.ToArray())); /* 要素にアクセス */ int peekFirst = deque.PeekFirst(); Console.WriteLine("先頭要素 peekFirst = " + peekFirst); int peekLast = deque.PeekLast(); Console.WriteLine("末尾要素 peekLast = " + peekLast); /* 要素をエンキュー */ deque.PushLast(4); Console.WriteLine("要素 4 を末尾にエンキューした後 deque = " + string.Join(" ", deque.ToArray())); deque.PushFirst(1); Console.WriteLine("要素 1 を先頭にエンキューした後 deque = " + string.Join(" ", deque.ToArray())); /* 要素をデキュー */ int popLast = deque.PopLast(); Console.WriteLine("末尾からデキューした要素 = " + popLast + "、末尾からデキューした後 deque = " + string.Join(" ", deque.ToArray())); int popFirst = deque.PopFirst(); Console.WriteLine("先頭からデキューした要素 = " + popFirst + "、先頭からデキューした後 deque = " + string.Join(" ", deque.ToArray())); /* 両端キューの長さを取得 */ int size = deque.Size(); Console.WriteLine("双方向キューの長さ size = " + size); /* 両端キューが空かどうかを判定 */ bool isEmpty = deque.IsEmpty(); Console.WriteLine("双方向キューが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/csharp/chapter_stack_and_queue/array_queue.cs ================================================ /** * File: array_queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* 循環配列ベースのキュー */ class ArrayQueue { int[] nums; // キュー要素を格納する配列 int front; // 先頭ポインタ。先頭要素を指す int queSize; // キューの長さ public ArrayQueue(int capacity) { nums = new int[capacity]; front = queSize = 0; } /* キューの容量を取得 */ int Capacity() { return nums.Length; } /* キューの長さを取得 */ public int Size() { return queSize; } /* キューが空かどうかを判定 */ public bool IsEmpty() { return queSize == 0; } /* エンキュー */ public void Push(int num) { if (queSize == Capacity()) { Console.WriteLine("キューは満杯です"); return; } // 末尾ポインタを計算し、末尾インデックス + 1 を指す // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする int rear = (front + queSize) % Capacity(); // num をキュー末尾に追加 nums[rear] = num; queSize++; } /* デキュー */ public int Pop() { int num = Peek(); // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す front = (front + 1) % Capacity(); queSize--; return num; } /* キュー先頭の要素にアクセス */ public int Peek() { if (IsEmpty()) throw new Exception(); return nums[front]; } /* 配列を返す */ public int[] ToArray() { // 有効長の範囲内のリスト要素のみを変換 int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[j % this.Capacity()]; } return res; } } public class array_queue { [Test] public void Test() { /* キューを初期化 */ int capacity = 10; ArrayQueue queue = new(capacity); /* 要素をエンキュー */ queue.Push(1); queue.Push(3); queue.Push(2); queue.Push(5); queue.Push(4); Console.WriteLine("キュー queue = " + string.Join(",", queue.ToArray())); /* キュー先頭の要素にアクセス */ int peek = queue.Peek(); Console.WriteLine("先頭要素 peek = " + peek); /* 要素をデキュー */ int pop = queue.Pop(); Console.WriteLine("デキューした要素 pop = " + pop + "、デキュー後の queue = " + string.Join(",", queue.ToArray())); /* キューの長さを取得 */ int size = queue.Size(); Console.WriteLine("キューの長さ size = " + size); /* キューが空かどうかを判定 */ bool isEmpty = queue.IsEmpty(); Console.WriteLine("キューが空かどうか = " + isEmpty); /* 循環配列をテストする */ for (int i = 0; i < 10; i++) { queue.Push(i); queue.Pop(); Console.WriteLine("第 " + i + " 回のエンキュー + デキュー後 queue = " + string.Join(",", queue.ToArray())); } } } ================================================ FILE: ja/codes/csharp/chapter_stack_and_queue/array_stack.cs ================================================ /** * File: array_stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* 配列ベースのスタック */ class ArrayStack { List stack; public ArrayStack() { // リスト(動的配列)を初期化する stack = []; } /* スタックの長さを取得 */ public int Size() { return stack.Count; } /* スタックが空かどうかを判定 */ public bool IsEmpty() { return Size() == 0; } /* プッシュ */ public void Push(int num) { stack.Add(num); } /* ポップ */ public int Pop() { if (IsEmpty()) throw new Exception(); var val = Peek(); stack.RemoveAt(Size() - 1); return val; } /* スタックトップの要素にアクセス */ public int Peek() { if (IsEmpty()) throw new Exception(); return stack[Size() - 1]; } /* List を Array に変換して返す */ public int[] ToArray() { return [.. stack]; } } public class array_stack { [Test] public void Test() { /* スタックを初期化 */ ArrayStack stack = new(); /* 要素をプッシュ */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); Console.WriteLine("スタック stack = " + string.Join(",", stack.ToArray())); /* スタックトップの要素にアクセス */ int peek = stack.Peek(); Console.WriteLine("スタックトップ要素 peek = " + peek); /* 要素をポップ */ int pop = stack.Pop(); Console.WriteLine("ポップした要素 pop = " + pop + "、ポップ後の stack = " + string.Join(",", stack.ToArray())); /* スタックの長さを取得 */ int size = stack.Size(); Console.WriteLine("スタックの長さ size = " + size); /* 空かどうかを判定 */ bool isEmpty = stack.IsEmpty(); Console.WriteLine("スタックが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/csharp/chapter_stack_and_queue/deque.cs ================================================ /** * File: deque.cs * Created Time: 2022-12-30 * Author: moonache (microin1301@outlook.com) */ namespace hello_algo.chapter_stack_and_queue; public class deque { [Test] public void Test() { /* 両端キューを初期化 */ // C# では、LinkedList を両端キューとして使う LinkedList deque = new(); /* 要素をエンキュー */ deque.AddLast(2); // 末尾に追加する deque.AddLast(5); deque.AddLast(4); deque.AddFirst(3); // 先頭に追加する deque.AddFirst(1); Console.WriteLine("双方向キュー deque = " + string.Join(",", deque)); /* 要素にアクセス */ int? peekFirst = deque.First?.Value; // 先頭要素 Console.WriteLine("先頭要素 peekFirst = " + peekFirst); int? peekLast = deque.Last?.Value; // 末尾要素 Console.WriteLine("末尾要素 peekLast = " + peekLast); /* 要素をデキュー */ deque.RemoveFirst(); // 先頭要素を取り出す Console.WriteLine("先頭要素をデキューした後 deque = " + string.Join(",", deque)); deque.RemoveLast(); // 末尾要素を取り出す Console.WriteLine("末尾要素をデキューした後 deque = " + string.Join(",", deque)); /* 両端キューの長さを取得 */ int size = deque.Count; Console.WriteLine("双方向キューの長さ size = " + size); /* 両端キューが空かどうかを判定 */ bool isEmpty = deque.Count == 0; Console.WriteLine("双方向キューが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs ================================================ /** * File: linkedlist_deque.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_stack_and_queue; /* 双方向連結リストノード */ public class ListNode(int val) { public int val = val; // ノード値 public ListNode? next = null; // 後続ノードへの参照 public ListNode? prev = null; // 前駆ノードへの参照 } /* 双方向連結リストベースの両端キュー */ public class LinkedListDeque { ListNode? front, rear; // 先頭ノード front、末尾ノード rear int queSize = 0; // 両端キューの長さ public LinkedListDeque() { front = null; rear = null; } /* 両端キューの長さを取得 */ public int Size() { return queSize; } /* 両端キューが空かどうかを判定 */ public bool IsEmpty() { return Size() == 0; } /* エンキュー操作 */ void Push(int num, bool isFront) { ListNode node = new(num); // 連結リストが空なら、front と rear の両方を node に向ける if (IsEmpty()) { front = node; rear = node; } // 先頭へのエンキュー操作 else if (isFront) { // node を連結リストの先頭に追加 front!.prev = node; node.next = front; front = node; // 先頭ノードを更新する } // 末尾へのエンキュー操作 else { // node を連結リストの末尾に追加 rear!.next = node; node.prev = rear; rear = node; // 末尾ノードを更新する } queSize++; // キューの長さを更新 } /* キュー先頭にエンキュー */ public void PushFirst(int num) { Push(num, true); } /* キュー末尾にエンキュー */ public void PushLast(int num) { Push(num, false); } /* デキュー操作 */ int? Pop(bool isFront) { if (IsEmpty()) throw new Exception(); int? val; // キュー先頭からの取り出し if (isFront) { val = front?.val; // 先頭ノードの値を一時保存 // 先頭ノードを削除 ListNode? fNext = front?.next; if (fNext != null) { fNext.prev = null; front!.next = null; } front = fNext; // 先頭ノードを更新する } // キュー末尾からの取り出し else { val = rear?.val; // 末尾ノードの値を一時保存 // 末尾ノードを削除 ListNode? rPrev = rear?.prev; if (rPrev != null) { rPrev.next = null; rear!.prev = null; } rear = rPrev; // 末尾ノードを更新する } queSize--; // キューの長さを更新 return val; } /* キュー先頭からデキュー */ public int? PopFirst() { return Pop(true); } /* キュー末尾からデキュー */ public int? PopLast() { return Pop(false); } /* キュー先頭の要素にアクセス */ public int? PeekFirst() { if (IsEmpty()) throw new Exception(); return front?.val; } /* キュー末尾の要素にアクセス */ public int? PeekLast() { if (IsEmpty()) throw new Exception(); return rear?.val; } /* 出力用の配列を返す */ public int?[] ToArray() { ListNode? node = front; int?[] res = new int?[Size()]; for (int i = 0; i < res.Length; i++) { res[i] = node?.val; node = node?.next; } return res; } } public class linkedlist_deque { [Test] public void Test() { /* 両端キューを初期化 */ LinkedListDeque deque = new(); deque.PushLast(3); deque.PushLast(2); deque.PushLast(5); Console.WriteLine("双方向キュー deque = " + string.Join(" ", deque.ToArray())); /* 要素にアクセス */ int? peekFirst = deque.PeekFirst(); Console.WriteLine("先頭要素 peekFirst = " + peekFirst); int? peekLast = deque.PeekLast(); Console.WriteLine("末尾要素 peekLast = " + peekLast); /* 要素をエンキュー */ deque.PushLast(4); Console.WriteLine("要素 4 を末尾にエンキューした後 deque = " + string.Join(" ", deque.ToArray())); deque.PushFirst(1); Console.WriteLine("要素 1 を先頭にエンキューした後 deque = " + string.Join(" ", deque.ToArray())); /* 要素をデキュー */ int? popLast = deque.PopLast(); Console.WriteLine("末尾からデキューした要素 = " + popLast + "、末尾からデキューした後 deque = " + string.Join(" ", deque.ToArray())); int? popFirst = deque.PopFirst(); Console.WriteLine("先頭からデキューした要素 = " + popFirst + "、先頭からデキューした後 deque = " + string.Join(" ", deque.ToArray())); /* 両端キューの長さを取得 */ int size = deque.Size(); Console.WriteLine("双方向キューの長さ size = " + size); /* 両端キューが空かどうかを判定 */ bool isEmpty = deque.IsEmpty(); Console.WriteLine("双方向キューが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs ================================================ /** * File: linkedlist_queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* 連結リストベースのキュー */ class LinkedListQueue { ListNode? front, rear; // 先頭ノード front、末尾ノード rear int queSize = 0; public LinkedListQueue() { front = null; rear = null; } /* キューの長さを取得 */ public int Size() { return queSize; } /* キューが空かどうかを判定 */ public bool IsEmpty() { return Size() == 0; } /* エンキュー */ public void Push(int num) { // 末尾ノードの後ろに num を追加 ListNode node = new(num); // キューが空なら、先頭・末尾ノードをともにそのノードに設定 if (front == null) { front = node; rear = node; // キューが空でなければ、そのノードを末尾ノードの後ろに追加 } else if (rear != null) { rear.next = node; rear = node; } queSize++; } /* デキュー */ public int Pop() { int num = Peek(); // 先頭ノードを削除 front = front?.next; queSize--; return num; } /* キュー先頭の要素にアクセス */ public int Peek() { if (IsEmpty()) throw new Exception(); return front!.val; } /* 連結リストを Array に変換して返す */ public int[] ToArray() { if (front == null) return []; ListNode? node = front; int[] res = new int[Size()]; for (int i = 0; i < res.Length; i++) { res[i] = node!.val; node = node.next; } return res; } } public class linkedlist_queue { [Test] public void Test() { /* キューを初期化 */ LinkedListQueue queue = new(); /* 要素をエンキュー */ queue.Push(1); queue.Push(3); queue.Push(2); queue.Push(5); queue.Push(4); Console.WriteLine("キュー queue = " + string.Join(",", queue.ToArray())); /* キュー先頭の要素にアクセス */ int peek = queue.Peek(); Console.WriteLine("先頭要素 peek = " + peek); /* 要素をデキュー */ int pop = queue.Pop(); Console.WriteLine("デキューした要素 pop = " + pop + "、デキュー後の queue = " + string.Join(",", queue.ToArray())); /* キューの長さを取得 */ int size = queue.Size(); Console.WriteLine("キューの長さ size = " + size); /* キューが空かどうかを判定 */ bool isEmpty = queue.IsEmpty(); Console.WriteLine("キューが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs ================================================ /** * File: linkedlist_stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* 連結リストベースのスタック */ class LinkedListStack { ListNode? stackPeek; // 先頭ノードをスタックトップとする int stkSize = 0; // スタックの長さ public LinkedListStack() { stackPeek = null; } /* スタックの長さを取得 */ public int Size() { return stkSize; } /* スタックが空かどうかを判定 */ public bool IsEmpty() { return Size() == 0; } /* プッシュ */ public void Push(int num) { ListNode node = new(num) { next = stackPeek }; stackPeek = node; stkSize++; } /* ポップ */ public int Pop() { int num = Peek(); stackPeek = stackPeek!.next; stkSize--; return num; } /* スタックトップの要素にアクセス */ public int Peek() { if (IsEmpty()) throw new Exception(); return stackPeek!.val; } /* List を Array に変換して返す */ public int[] ToArray() { if (stackPeek == null) return []; ListNode? node = stackPeek; int[] res = new int[Size()]; for (int i = res.Length - 1; i >= 0; i--) { res[i] = node!.val; node = node.next; } return res; } } public class linkedlist_stack { [Test] public void Test() { /* スタックを初期化 */ LinkedListStack stack = new(); /* 要素をプッシュ */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); Console.WriteLine("スタック stack = " + string.Join(",", stack.ToArray())); /* スタックトップの要素にアクセス */ int peek = stack.Peek(); Console.WriteLine("スタックトップ要素 peek = " + peek); /* 要素をポップ */ int pop = stack.Pop(); Console.WriteLine("ポップした要素 pop = " + pop + "、ポップ後の stack = " + string.Join(",", stack.ToArray())); /* スタックの長さを取得 */ int size = stack.Size(); Console.WriteLine("スタックの長さ size = " + size); /* 空かどうかを判定 */ bool isEmpty = stack.IsEmpty(); Console.WriteLine("スタックが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/csharp/chapter_stack_and_queue/queue.cs ================================================ /** * File: queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; public class queue { [Test] public void Test() { /* キューを初期化 */ Queue queue = new(); /* 要素をエンキュー */ queue.Enqueue(1); queue.Enqueue(3); queue.Enqueue(2); queue.Enqueue(5); queue.Enqueue(4); Console.WriteLine("キュー queue = " + string.Join(",", queue)); /* キュー先頭の要素にアクセス */ int peek = queue.Peek(); Console.WriteLine("先頭要素 peek = " + peek); /* 要素をデキュー */ int pop = queue.Dequeue(); Console.WriteLine("デキューした要素 pop = " + pop + "、デキュー後の queue = " + string.Join(",", queue)); /* キューの長さを取得 */ int size = queue.Count; Console.WriteLine("キューの長さ size = " + size); /* キューが空かどうかを判定 */ bool isEmpty = queue.Count == 0; Console.WriteLine("キューが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/csharp/chapter_stack_and_queue/stack.cs ================================================ /** * File: stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; public class stack { [Test] public void Test() { /* スタックを初期化 */ Stack stack = new(); /* 要素をプッシュ */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); // 注意:stack.ToArray() で得られるのは逆順のシーケンスであり、インデックス 0 がスタックトップです Console.WriteLine("スタック stack = " + string.Join(",", stack)); /* スタックトップの要素にアクセス */ int peek = stack.Peek(); Console.WriteLine("スタックトップ要素 peek = " + peek); /* 要素をポップ */ int pop = stack.Pop(); Console.WriteLine("ポップした要素 pop = " + pop + "、ポップ後の stack = " + string.Join(",", stack)); /* スタックの長さを取得 */ int size = stack.Count; Console.WriteLine("スタックの長さ size = " + size); /* 空かどうかを判定 */ bool isEmpty = stack.Count == 0; Console.WriteLine("スタックが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/csharp/chapter_tree/array_binary_tree.cs ================================================ /** * File: array_binary_tree.cs * Created Time: 2023-07-20 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_tree; /* 配列表現による二分木クラス */ public class ArrayBinaryTree(List arr) { List tree = new(arr); /* リスト容量 */ public int Size() { return tree.Count; } /* インデックス i のノードの値を取得 */ public int? Val(int i) { // インデックスが範囲外なら、空きを表す null を返す if (i < 0 || i >= Size()) return null; return tree[i]; } /* インデックス i のノードの左子ノードのインデックスを取得 */ public int Left(int i) { return 2 * i + 1; } /* インデックス i のノードの右子ノードのインデックスを取得 */ public int Right(int i) { return 2 * i + 2; } /* インデックス i のノードの親ノードのインデックスを取得 */ public int Parent(int i) { return (i - 1) / 2; } /* レベル順走査 */ public List LevelOrder() { List res = []; // 配列を直接走査する for (int i = 0; i < Size(); i++) { if (Val(i).HasValue) res.Add(Val(i)!.Value); } return res; } /* 深さ優先探索 */ void DFS(int i, string order, List res) { // 空きスロットなら返す if (!Val(i).HasValue) return; // 先行順走査 if (order == "pre") res.Add(Val(i)!.Value); DFS(Left(i), order, res); // 中順走査 if (order == "in") res.Add(Val(i)!.Value); DFS(Right(i), order, res); // 後順走査 if (order == "post") res.Add(Val(i)!.Value); } /* 先行順走査 */ public List PreOrder() { List res = []; DFS(0, "pre", res); return res; } /* 中順走査 */ public List InOrder() { List res = []; DFS(0, "in", res); return res; } /* 後順走査 */ public List PostOrder() { List res = []; DFS(0, "post", res); return res; } } public class array_binary_tree { [Test] public void Test() { // 二分木を初期化 // ここでは、配列から直接二分木を生成する関数を利用する List arr = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; TreeNode? root = TreeNode.ListToTree(arr); Console.WriteLine("\n二分木を初期化\n"); Console.WriteLine("二分木の配列表現:"); Console.WriteLine(arr.PrintList()); Console.WriteLine("二分木のリンクドリスト表現:"); PrintUtil.PrintTree(root); // 配列表現による二分木クラス ArrayBinaryTree abt = new(arr); // ノードにアクセス int i = 1; int l = abt.Left(i); int r = abt.Right(i); int p = abt.Parent(i); Console.WriteLine("\n現在のノードのインデックスは " + i + " 、値は " + abt.Val(i)); Console.WriteLine("左子ノードのインデックスは " + l + " 、値は " + (abt.Val(l).HasValue ? abt.Val(l) : "null")); Console.WriteLine("右子ノードのインデックスは " + r + " 、値は " + (abt.Val(r).HasValue ? abt.Val(r) : "null")); Console.WriteLine("親ノードのインデックスは " + p + " 、値は " + (abt.Val(p).HasValue ? abt.Val(p) : "null")); // 木を走査 List res = abt.LevelOrder(); Console.WriteLine("\nレベル順走査:" + res.PrintList()); res = abt.PreOrder(); Console.WriteLine("前順走査:" + res.PrintList()); res = abt.InOrder(); Console.WriteLine("中順走査:" + res.PrintList()); res = abt.PostOrder(); Console.WriteLine("後順走査:" + res.PrintList()); } } ================================================ FILE: ja/codes/csharp/chapter_tree/avl_tree.cs ================================================ /** * File: avl_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; /* AVL 木 */ class AVLTree { public TreeNode? root; // 根ノード /* ノードの高さを取得 */ int Height(TreeNode? node) { // 空ノードの高さは -1、葉ノードの高さは 0 return node == null ? -1 : node.height; } /* ノードの高さを更新する */ void UpdateHeight(TreeNode node) { // ノードの高さは最も高い部分木の高さ + 1 に等しい node.height = Math.Max(Height(node.left), Height(node.right)) + 1; } /* 平衡係数を取得 */ public int BalanceFactor(TreeNode? node) { // 空ノードの平衡係数は 0 if (node == null) return 0; // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ return Height(node.left) - Height(node.right); } /* 右回転 */ TreeNode? RightRotate(TreeNode? node) { TreeNode? child = node?.left; TreeNode? grandChild = child?.right; // child を支点として node を右回転させる child.right = node; node.left = grandChild; // ノードの高さを更新する UpdateHeight(node); UpdateHeight(child); // 回転後の部分木の根ノードを返す return child; } /* 左回転 */ TreeNode? LeftRotate(TreeNode? node) { TreeNode? child = node?.right; TreeNode? grandChild = child?.left; // child を支点として node を左回転させる child.left = node; node.right = grandChild; // ノードの高さを更新する UpdateHeight(node); UpdateHeight(child); // 回転後の部分木の根ノードを返す return child; } /* 回転操作を行い、この部分木の平衡を回復する */ TreeNode? Rotate(TreeNode? node) { // ノード node の平衡係数を取得 int balanceFactorInt = BalanceFactor(node); // 左に偏った木 if (balanceFactorInt > 1) { if (BalanceFactor(node?.left) >= 0) { // 右回転 return RightRotate(node); } else { // 左回転してから右回転 node!.left = LeftRotate(node!.left); return RightRotate(node); } } // 右に偏った木 if (balanceFactorInt < -1) { if (BalanceFactor(node?.right) <= 0) { // 左回転 return LeftRotate(node); } else { // 右回転してから左回転 node!.right = RightRotate(node!.right); return LeftRotate(node); } } // 平衡木なので回転不要、そのまま返す return node; } /* ノードを挿入 */ public void Insert(int val) { root = InsertHelper(root, val); } /* ノードを再帰的に挿入する(補助メソッド) */ TreeNode? InsertHelper(TreeNode? node, int val) { if (node == null) return new TreeNode(val); /* 1. 挿入位置を探索してノードを挿入 */ if (val < node.val) node.left = InsertHelper(node.left, val); else if (val > node.val) node.right = InsertHelper(node.right, val); else return node; // 重複ノードは挿入せず、そのまま返す UpdateHeight(node); // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = Rotate(node); // 部分木の根ノードを返す return node; } /* ノードを削除 */ public void Remove(int val) { root = RemoveHelper(root, val); } /* ノードを再帰的に削除する(補助メソッド) */ TreeNode? RemoveHelper(TreeNode? node, int val) { if (node == null) return null; /* 1. ノードを探索して削除 */ if (val < node.val) node.left = RemoveHelper(node.left, val); else if (val > node.val) node.right = RemoveHelper(node.right, val); else { if (node.left == null || node.right == null) { TreeNode? child = node.left ?? node.right; // 子ノード数 = 0 の場合、node をそのまま削除して返す if (child == null) return null; // 子ノード数 = 1 の場合、node をそのまま削除する else node = child; } else { // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える TreeNode? temp = node.right; while (temp.left != null) { temp = temp.left; } node.right = RemoveHelper(node.right, temp.val!.Value); node.val = temp.val; } } UpdateHeight(node); // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = Rotate(node); // 部分木の根ノードを返す return node; } /* ノードを探索 */ public TreeNode? Search(int val) { TreeNode? cur = root; // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 目標ノードは cur の右部分木にある if (cur.val < val) cur = cur.right; // 目標ノードは cur の左部分木にある else if (cur.val > val) cur = cur.left; // 目標ノードが見つかったらループを抜ける else break; } // 目標ノードを返す return cur; } } public class avl_tree { static void TestInsert(AVLTree tree, int val) { tree.Insert(val); Console.WriteLine("\nノード " + val + " を挿入した後の AVL 木は"); PrintUtil.PrintTree(tree.root); } static void TestRemove(AVLTree tree, int val) { tree.Remove(val); Console.WriteLine("\nノード " + val + " を削除した後、AVL 木は"); PrintUtil.PrintTree(tree.root); } [Test] public void Test() { /* 空の AVL 木を初期化する */ AVLTree avlTree = new(); /* ノードを挿入 */ // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい TestInsert(avlTree, 1); TestInsert(avlTree, 2); TestInsert(avlTree, 3); TestInsert(avlTree, 4); TestInsert(avlTree, 5); TestInsert(avlTree, 8); TestInsert(avlTree, 7); TestInsert(avlTree, 9); TestInsert(avlTree, 10); TestInsert(avlTree, 6); /* 重複ノードを挿入する */ TestInsert(avlTree, 7); /* ノードを削除 */ // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい TestRemove(avlTree, 8); // 次数 0 のノードを削除する TestRemove(avlTree, 5); // 次数 1 のノードを削除する TestRemove(avlTree, 4); // 次数 2 のノードを削除する /* ノードを検索 */ TreeNode? node = avlTree.Search(7); Console.WriteLine("\n見つかったノードオブジェクトは " + node + "、ノード値 = " + node?.val); } } ================================================ FILE: ja/codes/csharp/chapter_tree/binary_search_tree.cs ================================================ /** * File: binary_search_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; class BinarySearchTree { TreeNode? root; public BinarySearchTree() { // 空の木を初期化する root = null; } /* 二分木の根ノードを取得 */ public TreeNode? GetRoot() { return root; } /* ノードを探索 */ public TreeNode? Search(int num) { TreeNode? cur = root; // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 目標ノードは cur の右部分木にある if (cur.val < num) cur = cur.right; // 目標ノードは cur の左部分木にある else if (cur.val > num) cur = cur.left; // 目標ノードが見つかったらループを抜ける else break; } // 目標ノードを返す return cur; } /* ノードを挿入 */ public void Insert(int num) { // 木が空なら、根ノードを初期化する if (root == null) { root = new TreeNode(num); return; } TreeNode? cur = root, pre = null; // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 重複ノードが見つかったら、直ちに返す if (cur.val == num) return; pre = cur; // 挿入位置は cur の右部分木にある if (cur.val < num) cur = cur.right; // 挿入位置は cur の左部分木にある else cur = cur.left; } // ノードを挿入 TreeNode node = new(num); if (pre != null) { if (pre.val < num) pre.right = node; else pre.left = node; } } /* ノードを削除 */ public void Remove(int num) { // 木が空なら、そのまま早期リターンする if (root == null) return; TreeNode? cur = root, pre = null; // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 削除対象のノードが見つかったら、ループを抜ける if (cur.val == num) break; pre = cur; // 削除対象ノードは cur の右部分木にある if (cur.val < num) cur = cur.right; // 削除対象ノードは cur の左部分木にある else cur = cur.left; } // 削除対象ノードがなければそのまま返す if (cur == null) return; // 子ノード数 = 0 or 1 if (cur.left == null || cur.right == null) { // 子ノード数が 0 / 1 のとき、child = null / その子ノード TreeNode? child = cur.left ?? cur.right; // ノード cur を削除する if (cur != root) { if (pre!.left == cur) pre.left = child; else pre.right = child; } else { // 削除ノードが根ノードなら、根ノードを再設定 root = child; } } // 子ノード数 = 2 else { // 中順走査における cur の次ノードを取得 TreeNode? tmp = cur.right; while (tmp.left != null) { tmp = tmp.left; } // ノード tmp を再帰的に削除 Remove(tmp.val!.Value); // tmp で cur を上書きする cur.val = tmp.val; } } } public class binary_search_tree { [Test] public void Test() { /* 二分探索木を初期化 */ BinarySearchTree bst = new(); // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる int[] nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; foreach (int num in nums) { bst.Insert(num); } Console.WriteLine("\n初期化した二分木は\n"); PrintUtil.PrintTree(bst.GetRoot()); /* ノードを探索 */ TreeNode? node = bst.Search(7); Console.WriteLine("\n見つかったノードオブジェクトは " + node + "、ノード値 = " + node?.val); /* ノードを挿入 */ bst.Insert(16); Console.WriteLine("\nノード 16 を挿入した後、二分木は\n"); PrintUtil.PrintTree(bst.GetRoot()); /* ノードを削除 */ bst.Remove(1); Console.WriteLine("\nノード 1 を削除した後、二分木は\n"); PrintUtil.PrintTree(bst.GetRoot()); bst.Remove(2); Console.WriteLine("\nノード 2 を削除した後、二分木は\n"); PrintUtil.PrintTree(bst.GetRoot()); bst.Remove(4); Console.WriteLine("\nノード 4 を削除した後、二分木は\n"); PrintUtil.PrintTree(bst.GetRoot()); } } ================================================ FILE: ja/codes/csharp/chapter_tree/binary_tree.cs ================================================ /** * File: binary_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree { [Test] public void Test() { /* 二分木を初期化 */ // ノードを初期化 TreeNode n1 = new(1); TreeNode n2 = new(2); TreeNode n3 = new(3); TreeNode n4 = new(4); TreeNode n5 = new(5); // ノード間の参照(ポインタ)を構築する n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; Console.WriteLine("\n二分木を初期化\n"); PrintUtil.PrintTree(n1); /* ノードの挿入と削除 */ TreeNode P = new(0); // n1 -> n2 の間にノード P を挿入 n1.left = P; P.left = n2; Console.WriteLine("\nノード P を挿入した後\n"); PrintUtil.PrintTree(n1); // ノード P を削除 n1.left = n2; Console.WriteLine("\nノード P を削除した後\n"); PrintUtil.PrintTree(n1); } } ================================================ FILE: ja/codes/csharp/chapter_tree/binary_tree_bfs.cs ================================================ /** * File: binary_tree_bfs.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree_bfs { /* レベル順走査 */ List LevelOrder(TreeNode root) { // キューを初期化し、ルートノードを追加する Queue queue = new(); queue.Enqueue(root); // 走査順序を保存するためのリストを初期化する List list = []; while (queue.Count != 0) { TreeNode node = queue.Dequeue(); // デキュー list.Add(node.val!.Value); // ノードの値を保存する if (node.left != null) queue.Enqueue(node.left); // 左子ノードをキューに追加 if (node.right != null) queue.Enqueue(node.right); // 右子ノードをキューに追加 } return list; } [Test] public void Test() { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); Console.WriteLine("\n二分木を初期化\n"); PrintUtil.PrintTree(root); List list = LevelOrder(root!); Console.WriteLine("\nレベル順走査のノード出力シーケンス = " + string.Join(",", list)); } } ================================================ FILE: ja/codes/csharp/chapter_tree/binary_tree_dfs.cs ================================================ /** * File: binary_tree_dfs.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree_dfs { List list = []; /* 先行順走査 */ void PreOrder(TreeNode? root) { if (root == null) return; // 訪問順序:根ノード -> 左部分木 -> 右部分木 list.Add(root.val!.Value); PreOrder(root.left); PreOrder(root.right); } /* 中順走査 */ void InOrder(TreeNode? root) { if (root == null) return; // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 InOrder(root.left); list.Add(root.val!.Value); InOrder(root.right); } /* 後順走査 */ void PostOrder(TreeNode? root) { if (root == null) return; // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード PostOrder(root.left); PostOrder(root.right); list.Add(root.val!.Value); } [Test] public void Test() { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); Console.WriteLine("\n二分木を初期化\n"); PrintUtil.PrintTree(root); list.Clear(); PreOrder(root); Console.WriteLine("\n前順走査のノード出力シーケンス = " + string.Join(",", list)); list.Clear(); InOrder(root); Console.WriteLine("\n中順走査のノード出力シーケンス = " + string.Join(",", list)); list.Clear(); PostOrder(root); Console.WriteLine("\n後順走査のノード出力シーケンス = " + string.Join(",", list)); } } ================================================ FILE: ja/codes/csharp/csharp.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.002.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hello-algo", "hello-algo.csproj", "{48B60439-EFDC-4C8F-AE8D-41979958C8AC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.Build.0 = Debug|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1E773F8A-FF66-4974-820B-FCE9032D19AE} EndGlobalSection EndGlobal ================================================ FILE: ja/codes/csharp/hello-algo.csproj ================================================  Exe net8.0 hello_algo enable enable all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: ja/codes/csharp/utils/ListNode.cs ================================================ // File: ListNode.cs // Created Time: 2022-12-16 // Author: mingXta (1195669834@qq.com) namespace hello_algo.utils; /* 連結リストノード */ public class ListNode(int x) { public int val = x; public ListNode? next; /* 配列をデシリアライズして連結リストに変換する */ public static ListNode? ArrToLinkedList(int[] arr) { ListNode dum = new(0); ListNode head = dum; foreach (int val in arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } public override string? ToString() { List list = []; var head = this; while (head != null) { list.Add(head.val.ToString()); head = head.next; } return string.Join("->", list); } } ================================================ FILE: ja/codes/csharp/utils/PrintUtil.cs ================================================ /** * File: PrintUtil.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com), krahets (krahets@163.com) */ namespace hello_algo.utils; public class Trunk(Trunk? prev, string str) { public Trunk? prev = prev; public string str = str; }; public static class PrintUtil { /* リストを出力する */ public static void PrintList(IList list) { Console.WriteLine("[" + string.Join(", ", list) + "]"); } public static string PrintList(this IEnumerable list) { return $"[ {string.Join(", ", list.Select(x => x?.ToString() ?? "null"))} ]"; } /* 行列を出力する (Array) */ public static void PrintMatrix(T[][] matrix) { Console.WriteLine("["); foreach (T[] row in matrix) { Console.WriteLine(" " + string.Join(", ", row) + ","); } Console.WriteLine("]"); } /* 行列を出力 (List) */ public static void PrintMatrix(List> matrix) { Console.WriteLine("["); foreach (List row in matrix) { Console.WriteLine(" " + string.Join(", ", row) + ","); } Console.WriteLine("]"); } /* 連結リストを出力 */ public static void PrintLinkedList(ListNode? head) { List list = []; while (head != null) { list.Add(head.val.ToString()); head = head.next; } Console.Write(string.Join(" -> ", list)); } /** * 二分木を出力 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ public static void PrintTree(TreeNode? root) { PrintTree(root, null, false); } /* 二分木を出力 */ public static void PrintTree(TreeNode? root, Trunk? prev, bool isRight) { if (root == null) { return; } string prev_str = " "; Trunk trunk = new(prev, prev_str); PrintTree(root.right, trunk, true); if (prev == null) { trunk.str = "———"; } else if (isRight) { trunk.str = "/———"; prev_str = " |"; } else { trunk.str = "\\———"; prev.str = prev_str; } ShowTrunks(trunk); Console.WriteLine(" " + root.val); if (prev != null) { prev.str = prev_str; } trunk.str = " |"; PrintTree(root.left, trunk, false); } public static void ShowTrunks(Trunk? p) { if (p == null) { return; } ShowTrunks(p.prev); Console.Write(p.str); } /* ハッシュテーブルを出力 */ public static void PrintHashMap(Dictionary map) where K : notnull { foreach (var kv in map.Keys) { Console.WriteLine(kv.ToString() + " -> " + map[kv]?.ToString()); } } /* ヒープを出力 */ public static void PrintHeap(Queue queue) { Console.Write("ヒープの配列表現:"); List list = [.. queue]; Console.WriteLine(string.Join(',', list)); Console.WriteLine("ヒープの木構造表示:"); TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); PrintTree(tree); } /* 優先キューを出力 */ public static void PrintHeap(PriorityQueue queue) { var newQueue = new PriorityQueue(queue.UnorderedItems, queue.Comparer); Console.Write("ヒープの配列表現:"); List list = []; while (newQueue.TryDequeue(out int element, out _)) { list.Add(element); } Console.WriteLine("ヒープの木構造表示:"); Console.WriteLine(string.Join(',', list.ToList())); TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); PrintTree(tree); } } ================================================ FILE: ja/codes/csharp/utils/TreeNode.cs ================================================ /** * File: TreeNode.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.utils; /* 二分木ノードクラス */ public class TreeNode(int? x) { public int? val = x; // ノード値 public int height; // ノードの高さ public TreeNode? left; // 左子ノードへの参照 public TreeNode? right; // 右子ノードへの参照 // シリアライズの符号化規則は以下を参照: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二分木の配列表現: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // 二分木の連結リスト表現: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* リストを二分木にデシリアライズする: 再帰 */ static TreeNode? ListToTreeDFS(List arr, int i) { if (i < 0 || i >= arr.Count || !arr[i].HasValue) { return null; } TreeNode root = new(arr[i]) { left = ListToTreeDFS(arr, 2 * i + 1), right = ListToTreeDFS(arr, 2 * i + 2) }; return root; } /* リストを二分木にデシリアライズする */ public static TreeNode? ListToTree(List arr) { return ListToTreeDFS(arr, 0); } /* 二分木をリストにシリアライズする: 再帰 */ static void TreeToListDFS(TreeNode? root, int i, List res) { if (root == null) return; while (i >= res.Count) { res.Add(null); } res[i] = root.val; TreeToListDFS(root.left, 2 * i + 1, res); TreeToListDFS(root.right, 2 * i + 2, res); } /* 二分木をリストにシリアライズする */ public static List TreeToList(TreeNode root) { List res = []; TreeToListDFS(root, 0, res); return res; } } ================================================ FILE: ja/codes/csharp/utils/Vertex.cs ================================================ /** * File: Vertex.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com), krahets (krahets@163.com) */ namespace hello_algo.utils; /* 頂点クラス */ public class Vertex(int val) { public int val = val; /* 値リスト vals を入力し、頂点リスト vets を返す */ public static Vertex[] ValsToVets(int[] vals) { Vertex[] vets = new Vertex[vals.Length]; for (int i = 0; i < vals.Length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* 頂点リスト vets を入力し、値リスト vals を返す */ public static List VetsToVals(List vets) { List vals = []; foreach (Vertex vet in vets) { vals.Add(vet.val); } return vals; } } ================================================ FILE: ja/codes/dart/build.dart ================================================ import 'dart:io'; void main() { Directory foldPath = Directory('codes/dart/'); List files = foldPath.listSync(); int totalCount = 0; int errorCount = 0; for (var file in files) { if (file.path.endsWith('build.dart')) continue; if (file is File && file.path.endsWith('.dart')) { totalCount++; try { Process.runSync('dart', [file.path]); } catch (e) { errorCount++; print('Error: $e'); print('File: ${file.path}'); } } else if (file is Directory) { List subFiles = file.listSync(); for (var subFile in subFiles) { if (subFile is File && subFile.path.endsWith('.dart')) { totalCount++; try { Process.runSync('dart', [subFile.path]); } catch (e) { errorCount++; print('Error: $e'); print('File: ${file.path}'); } } } } } print('===== Build Complete ====='); print('Total: $totalCount'); print('Error: $errorCount'); } ================================================ FILE: ja/codes/dart/chapter_array_and_linkedlist/array.dart ================================================ /** * File: array.dart * Created Time: 2023-01-20 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable import 'dart:math'; /* 要素へランダムアクセス */ int randomAccess(List nums) { // 区間 [0, nums.length) からランダムに 1 つの数を選ぶ int randomIndex = Random().nextInt(nums.length); // ランダムな要素を取得して返す int randomNum = nums[randomIndex]; return randomNum; } /* 配列長を拡張する */ List extend(List nums, int enlarge) { // 拡張後の長さを持つ配列を初期化する List res = List.filled(nums.length + enlarge, 0); // 元の配列の全要素を新しい配列にコピー for (var i = 0; i < nums.length; i++) { res[i] = nums[i]; } // 拡張後の新しい配列を返す return res; } /* 配列の添字 index に要素 _num を挿入 */ void insert(List nums, int _num, int index) { // インデックス index 以降の全要素を 1 つ後ろへ移動する for (var i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // _num を index の位置の要素に代入 nums[index] = _num; } /* index の要素を削除する */ void remove(List nums, int index) { // インデックス index より後ろの全要素を 1 つ前へ移動する for (var i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* 配列要素を走査する */ void traverse(List nums) { int count = 0; // インデックスで配列を走査 for (var i = 0; i < nums.length; i++) { count += nums[i]; } // 配列要素を直接走査 for (int _num in nums) { count += _num; } // forEach メソッドで配列を走査する nums.forEach((_num) { count += _num; }); } /* 配列内で指定要素を探す */ int find(List nums, int target) { for (var i = 0; i < nums.length; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ void main() { /* 配列を初期化 */ var arr = List.filled(5, 0); print('配列 arr = $arr'); List nums = [1, 3, 2, 5, 4]; print('配列 nums = $nums'); /* ランダムアクセス */ int randomNum = randomAccess(nums); print('nums からランダムな要素 $randomNum を取得'); /* 長さを拡張 */ nums = extend(nums, 3); print('配列の長さを 8 に拡張し、nums = $nums を取得'); /* 要素を挿入する */ insert(nums, 6, 3); print("インデックス 3 に数字 6 を挿入し、nums = $nums を取得"); /* 要素を削除 */ remove(nums, 2); print("インデックス 2 の要素を削除し、nums = $nums を取得"); /* 配列を走査 */ traverse(nums); /* 要素を探索する */ int index = find(nums, 3); print("nums で要素 3 を検索し、インデックス = $index を取得"); } ================================================ FILE: ja/codes/dart/chapter_array_and_linkedlist/linked_list.dart ================================================ /** * File: linked_list.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import '../utils/list_node.dart'; import '../utils/print_util.dart'; /* 連結リストでノード n0 の後ろにノード P を挿入する */ void insert(ListNode n0, ListNode P) { ListNode? n1 = n0.next; P.next = n1; n0.next = P; } /* 連結リストでノード n0 の直後のノードを削除する */ void remove(ListNode n0) { if (n0.next == null) return; // n0 -> P -> n1 ListNode P = n0.next!; ListNode? n1 = P.next; n0.next = n1; } /* 連結リスト内で index 番目のノードにアクセス */ ListNode? access(ListNode? head, int index) { for (var i = 0; i < index; i++) { if (head == null) return null; head = head.next; } return head; } /* 連結リストで値が target の最初のノードを探す */ int find(ListNode? head, int target) { int index = 0; while (head != null) { if (head.val == target) { return index; } head = head.next; index++; } return -1; } /* Driver Code */ void main() { // 連結リストを初期化する // 各ノードを初期化する ListNode n0 = ListNode(1); ListNode n1 = ListNode(3); ListNode n2 = ListNode(2); ListNode n3 = ListNode(5); ListNode n4 = ListNode(4); // ノード間の参照を構築する n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; print('初期化した連結リストは'); printLinkedList(n0); /* ノードを挿入 */ insert(n0, ListNode(0)); print('ノードを挿入した後の連結リストは'); printLinkedList(n0); /* ノードを削除 */ remove(n0); print('ノードを削除した後の連結リストは'); printLinkedList(n0); /* ノードにアクセス */ ListNode? node = access(n0, 3); print('連結リストのインデックス 3 にあるノードの値 = ${node!.val}'); /* ノードを探索 */ int index = find(n0, 2); print('連結リストで値 2 のノードのインデックス = $index'); } ================================================ FILE: ja/codes/dart/chapter_array_and_linkedlist/list.dart ================================================ /** * File: list.dart * Created Time: 2023-01-24 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable /* Driver Code */ void main() { /* リストを初期化 */ List nums = [1, 3, 2, 5, 4]; print('リスト nums = $nums'); /* 要素にアクセス */ int _num = nums[1]; print('インデックス 1 の要素にアクセスし、_num = $_num を取得'); /* 要素を更新 */ nums[1] = 0; print('インデックス 1 の要素を 0 に更新し、nums = $nums を取得'); /* リストを空にする */ nums.clear(); print('リストを空にした後 nums = $nums'); /* 末尾に要素を追加 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print('要素を追加した後 nums = $nums'); /* 中間に要素を挿入 */ nums.insert(3, 6); print('インデックス 3 に数字 6 を挿入し、nums = $nums を取得'); /* 要素を削除 */ nums.removeAt(3); print('インデックス 3 の要素を削除し、nums = $nums を取得'); /* インデックスでリストを走査 */ int count = 0; for (var i = 0; i < nums.length; i++) { count += nums[i]; } /* リスト要素を直接走査 */ count = 0; for (var x in nums) { count += x; } /* 2 つのリストを連結する */ List nums1 = [6, 8, 7, 10, 9]; nums.addAll(nums1); print('リスト nums1 を nums の後ろに連結し、nums = $nums を取得'); /* リストをソート */ nums.sort(); print('リストをソートした後 nums = $nums'); } ================================================ FILE: ja/codes/dart/chapter_array_and_linkedlist/my_list.dart ================================================ /** * File: my_list.dart * Created Time: 2023-02-05 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* リストクラス */ class MyList { late List _arr; // 配列(リスト要素を格納) int _capacity = 10; // リスト容量 int _size = 0; // リストの長さ(現在の要素数) int _extendRatio = 2; // リスト拡張時の増加倍率 /* コンストラクタ */ MyList() { _arr = List.filled(_capacity, 0); } /* リストの長さを取得(現在の要素数) */ int size() => _size; /* リスト容量を取得する */ int capacity() => _capacity; /* 要素にアクセス */ int get(int index) { if (index >= _size) throw RangeError('インデックスが範囲外です'); return _arr[index]; } /* 要素を更新 */ void set(int index, int _num) { if (index >= _size) throw RangeError('インデックスが範囲外です'); _arr[index] = _num; } /* 末尾に要素を追加 */ void add(int _num) { // 要素数が容量を超えると、拡張機構が発動する if (_size == _capacity) extendCapacity(); _arr[_size] = _num; // 要素数を更新 _size++; } /* 中間に要素を挿入 */ void insert(int index, int _num) { if (index >= _size) throw RangeError('インデックスが範囲外です'); // 要素数が容量を超えると、拡張機構が発動する if (_size == _capacity) extendCapacity(); // index 以降の要素をすべて 1 つ後ろへずらす for (var j = _size - 1; j >= index; j--) { _arr[j + 1] = _arr[j]; } _arr[index] = _num; // 要素数を更新 _size++; } /* 要素を削除 */ int remove(int index) { if (index >= _size) throw RangeError('インデックスが範囲外です'); int _num = _arr[index]; // インデックス index より後の要素をすべて 1 つ前に移動する for (var j = index; j < _size - 1; j++) { _arr[j] = _arr[j + 1]; } // 要素数を更新 _size--; // 削除された要素を返す return _num; } /* リストの拡張 */ void extendCapacity() { // 元の配列の `_extendRatio` 倍の長さを持つ新しい配列を作成する final _newNums = List.filled(_capacity * _extendRatio, 0); // 元の配列を新しい配列にコピー List.copyRange(_newNums, 0, _arr); // `_arr` の参照を更新 _arr = _newNums; // リストの容量を更新 _capacity = _arr.length; } /* リストを配列に変換する */ List toArray() { List arr = []; for (var i = 0; i < _size; i++) { arr.add(get(i)); } return arr; } } /* Driver Code */ void main() { /* リストを初期化 */ MyList nums = MyList(); /* 末尾に要素を追加 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print( 'リスト nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長さ = ${nums.size()}'); /* 中間に要素を挿入 */ nums.insert(3, 6); print('インデックス 3 に数字 6 を挿入し、nums = ${nums.toArray()} を取得'); /* 要素を削除 */ nums.remove(3); print('インデックス 3 の要素を削除し、nums = ${nums.toArray()} を取得'); /* 要素にアクセス */ int _num = nums.get(1); print('インデックス 1 の要素にアクセスし、_num = $_num を取得'); /* 要素を更新 */ nums.set(1, 0); print('インデックス 1 の要素を 0 に更新し、nums = ${nums.toArray()} を取得'); /* 拡張機構をテストする */ for (var i = 0; i < 10; i++) { // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する nums.add(i); } print( '容量拡張後のリスト nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長さ = ${nums.size()}'); } ================================================ FILE: ja/codes/dart/chapter_backtracking/n_queens.dart ================================================ /** * File: n_queens.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* バックトラッキング:N クイーン */ void backtrack( int row, int n, List> state, List>> res, List cols, List diags1, List diags2, ) { // すべての行への配置が完了したら、解を記録する if (row == n) { List> copyState = []; for (List sRow in state) { copyState.add(List.from(sRow)); } res.add(copyState); return; } // すべての列を走査 for (int col = 0; col < n; col++) { // このマスに対応する主対角線と副対角線を計算 int diag1 = row - col + n - 1; int diag2 = row + col; // 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 試行:そのマスにクイーンを置く state[row][col] = "Q"; cols[col] = true; diags1[diag1] = true; diags2[diag2] = true; // 次の行に配置する backtrack(row + 1, n, state, res, cols, diags1, diags2); // 戻す:そのマスを空きマスに戻す state[row][col] = "#"; cols[col] = false; diags1[diag1] = false; diags2[diag2] = false; } } } /* N クイーンを解く */ List>> nQueens(int n) { // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す List> state = List.generate(n, (index) => List.filled(n, "#")); List cols = List.filled(n, false); // 列にクイーンがあるか記録 List diags1 = List.filled(2 * n - 1, false); // 主対角線にクイーンがあるかを記録 List diags2 = List.filled(2 * n - 1, false); // 副対角線にクイーンがあるかを記録 List>> res = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } /* Driver Code */ void main() { int n = 4; List>> res = nQueens(n); print("チェス盤の縦横サイズの入力値は $n"); print("クイーンの配置パターンは全部で ${res.length} 通り"); for (List> state in res) { print("--------------------"); for (List row in state) { print(row); } } } ================================================ FILE: ja/codes/dart/chapter_backtracking/permutations_i.dart ================================================ /** * File: permutations_i.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* バックトラッキング:順列 I */ void backtrack( List state, List choices, List selected, List> res, ) { // 状態の長さが要素数に等しければ、解を記録 if (state.length == choices.length) { res.add(List.from(state)); return; } // すべての選択肢を走査 for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // 枝刈り:要素の重複選択を許可しない if (!selected[i]) { // 試行: 選択を行い、状態を更新 selected[i] = true; state.add(choice); // 次の選択へ進む backtrack(state, choices, selected, res); // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.removeLast(); } } } /* 全順列 I */ List> permutationsI(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } /* Driver Code */ void main() { List nums = [1, 2, 3]; List> res = permutationsI(nums); print("入力配列 nums = $nums"); print("すべての順列 res = $res"); } ================================================ FILE: ja/codes/dart/chapter_backtracking/permutations_ii.dart ================================================ /** * File: permutations_ii.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* バックトラッキング:順列 II */ void backtrack( List state, List choices, List selected, List> res, ) { // 状態の長さが要素数に等しければ、解を記録 if (state.length == choices.length) { res.add(List.from(state)); return; } // すべての選択肢を走査 Set duplicated = {}; for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない if (!selected[i] && !duplicated.contains(choice)) { // 試行: 選択を行い、状態を更新 duplicated.add(choice); // 選択済みの要素値を記録 selected[i] = true; state.add(choice); // 次の選択へ進む backtrack(state, choices, selected, res); // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.removeLast(); } } } /* 全順列 II */ List> permutationsII(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } /* Driver Code */ void main() { List nums = [1, 2, 2]; List> res = permutationsII(nums); print("入力配列 nums = $nums"); print("すべての順列 res = $res"); } ================================================ FILE: ja/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart ================================================ /** * File: preorder_traversal_i_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 前順走査:例題 1 */ void preOrder(TreeNode? root, List res) { if (root == null) { return; } if (root.val == 7) { // 解を記録 res.add(root); } preOrder(root.left, res); preOrder(root.right, res); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\n二分木を初期化"); printTree(root); // 先行順走査 List res = []; preOrder(root, res); print("\n値が 7 のノードをすべて出力"); print(List.generate(res.length, (i) => res[i].val)); } ================================================ FILE: ja/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart ================================================ /** * File: preorder_traversal_ii_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 前順走査:例題 2 */ void preOrder( TreeNode? root, List path, List> res, ) { if (root == null) { return; } // 試す path.add(root); if (root.val == 7) { // 解を記録 res.add(List.from(path)); } preOrder(root.left, path, res); preOrder(root.right, path, res); // バックトラック path.removeLast(); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\n二分木を初期化"); printTree(root); // 先行順走査 List path = []; List> res = []; preOrder(root, path, res); print("\nルートノードからノード 7 までのすべての経路を出力"); for (List vals in res) { print(List.generate(vals.length, (i) => vals[i].val)); } } ================================================ FILE: ja/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart ================================================ /** * File: preorder_traversal_iii_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 前順走査:例題 3 */ void preOrder( TreeNode? root, List path, List> res, ) { if (root == null || root.val == 3) { return; } // 試す path.add(root); if (root.val == 7) { // 解を記録 res.add(List.from(path)); } preOrder(root.left, path, res); preOrder(root.right, path, res); // バックトラック path.removeLast(); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\n二分木を初期化"); printTree(root); // 先行順走査 List path = []; List> res = []; preOrder(root, path, res); print("\nルートノードからノード 7 までのすべての経路を出力"); for (List vals in res) { print(List.generate(vals.length, (i) => vals[i].val)); } } ================================================ FILE: ja/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart ================================================ /** * File: preorder_traversal_iii_template.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 現在の状態が解かどうかを判定 */ bool isSolution(List state) { return state.isNotEmpty && state.last.val == 7; } /* 解を記録 */ void recordSolution(List state, List> res) { res.add(List.from(state)); } /* 現在の状態で、この選択が有効かどうかを判定 */ bool isValid(List state, TreeNode? choice) { return choice != null && choice.val != 3; } /* 状態を更新 */ void makeChoice(List state, TreeNode? choice) { state.add(choice!); } /* 状態を元に戻す */ void undoChoice(List state, TreeNode? choice) { state.removeLast(); } /* バックトラッキング:例題 3 */ void backtrack( List state, List choices, List> res, ) { // 解かどうかを確認 if (isSolution(state)) { // 解を記録 recordSolution(state, res); } // すべての選択肢を走査 for (TreeNode? choice in choices) { // 枝刈り:選択が妥当かを確認する if (isValid(state, choice)) { // 試行: 選択を行い、状態を更新 makeChoice(state, choice); // 次の選択へ進む backtrack(state, [choice!.left, choice.right], res); // バックトラック:選択を取り消し、前の状態に戻す undoChoice(state, choice); } } } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\n二分木を初期化"); printTree(root); // バックトラッキング法 List> res = []; backtrack([], [root!], res); print("\nルートノードからノード 7 までのすべての経路を出力し、経路には値が 3 のノードを含まない"); for (List path in res) { print(List.from(path.map((e) => e.val))); } } ================================================ FILE: ja/codes/dart/chapter_backtracking/subset_sum_i.dart ================================================ /** * File: subset_sum_i.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* バックトラッキング:部分和 I */ void backtrack( List state, int target, List choices, int start, List> res, ) { // 部分集合の和が target に等しければ、解を記録 if (target == 0) { res.add(List.from(state)); return; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for (int i = start; i < choices.length; i++) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } // 試す:選択を行い、target と start を更新 state.add(choices[i]); // 次の選択へ進む backtrack(state, target - choices[i], choices, i, res); // バックトラック:選択を取り消し、前の状態に戻す state.removeLast(); } } /* 部分和 I を解く */ List> subsetSumI(List nums, int target) { List state = []; // 状態(部分集合) nums.sort(); // nums をソート int start = 0; // 開始点を走査 List> res = []; // 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ void main() { List nums = [3, 4, 5]; int target = 9; List> res = subsetSumI(nums, target); print("入力配列 nums = $nums, target = $target"); print("和が $target に等しいすべての部分集合 res = $res"); } ================================================ FILE: ja/codes/dart/chapter_backtracking/subset_sum_i_naive.dart ================================================ /** * File: subset_sum_i_naive.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* バックトラッキング:部分和 I */ void backtrack( List state, int target, int total, List choices, List> res, ) { // 部分集合の和が target に等しければ、解を記録 if (total == target) { res.add(List.from(state)); return; } // すべての選択肢を走査 for (int i = 0; i < choices.length; i++) { // 枝刈り:部分和が target を超える場合はその選択をスキップする if (total + choices[i] > target) { continue; } // 試行:選択を行い、要素と total を更新する state.add(choices[i]); // 次の選択へ進む backtrack(state, target, total + choices[i], choices, res); // バックトラック:選択を取り消し、前の状態に戻す state.removeLast(); } } /* 部分和 I を解く(重複部分集合を含む) */ List> subsetSumINaive(List nums, int target) { List state = []; // 状態(部分集合) int total = 0; // 要素の合計 List> res = []; // 結果リスト(部分集合のリスト) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ void main() { List nums = [3, 4, 5]; int target = 9; List> res = subsetSumINaive(nums, target); print("入力配列 nums = $nums, target = $target"); print("和が $target に等しいすべての部分集合 res = $res"); print("この方法が出力する結果には重複する集合が含まれることに注意してください"); } ================================================ FILE: ja/codes/dart/chapter_backtracking/subset_sum_ii.dart ================================================ /** * File: subset_sum_ii.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* バックトラッキング:部分和 II */ void backtrack( List state, int target, List choices, int start, List> res, ) { // 部分集合の和が target に等しければ、解を記録 if (target == 0) { res.add(List.from(state)); return; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for (int i = start; i < choices.length; i++) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする if (i > start && choices[i] == choices[i - 1]) { continue; } // 試す:選択を行い、target と start を更新 state.add(choices[i]); // 次の選択へ進む backtrack(state, target - choices[i], choices, i + 1, res); // バックトラック:選択を取り消し、前の状態に戻す state.removeLast(); } } /* 部分和 II を解く */ List> subsetSumII(List nums, int target) { List state = []; // 状態(部分集合) nums.sort(); // nums をソート int start = 0; // 開始点を走査 List> res = []; // 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ void main() { List nums = [4, 4, 5]; int target = 9; List> res = subsetSumII(nums, target); print("入力配列 nums = $nums, target = $target"); print("和が $target に等しいすべての部分集合 res = $res"); } ================================================ FILE: ja/codes/dart/chapter_computational_complexity/iteration.dart ================================================ /** * File: iteration.dart * Created Time: 2023-08-27 * Author: liuyuxin (gvenusleo@gmail.com) */ /* for ループ */ int forLoop(int n) { int res = 0; // 1, 2, ..., n-1, n を順に加算する for (int i = 1; i <= n; i++) { res += i; } return res; } /* while ループ */ int whileLoop(int n) { int res = 0; int i = 1; // 条件変数を初期化する // 1, 2, ..., n-1, n を順に加算する while (i <= n) { res += i; i++; // 条件変数を更新する } return res; } /* while ループ(2回更新) */ int whileLoopII(int n) { int res = 0; int i = 1; // 条件変数を初期化する // 1, 4, 10, ... を順に加算する while (i <= n) { res += i; // 条件変数を更新する i++; i *= 2; } return res; } /* 二重 for ループ */ String nestedForLoop(int n) { String res = ""; // i = 1, 2, ..., n-1, n とループする for (int i = 1; i <= n; i++) { // j = 1, 2, ..., n-1, n とループする for (int j = 1; j <= n; j++) { res += "($i, $j), "; } } return res; } /* Driver Code */ void main() { int n = 5; int res; res = forLoop(n); print("\nfor ループの合計結果 res = $res"); res = whileLoop(n); print("\nwhile ループの合計結果 res = $res"); res = whileLoopII(n); print("\nwhile ループ(2 回更新)の合計結果 res = $res"); String resStr = nestedForLoop(n); print("\n二重 for ループの結果 $resStr"); } ================================================ FILE: ja/codes/dart/chapter_computational_complexity/recursion.dart ================================================ /** * File: recursion.dart * Created Time: 2023-08-27 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 再帰 */ int recur(int n) { // 終了条件 if (n == 1) return 1; // 再帰:再帰呼び出し int res = recur(n - 1); // 帰りがけ:結果を返す return n + res; } /* 反復で再帰を模擬する */ int forLoopRecur(int n) { // 明示的なスタックを使ってシステムコールスタックを模擬する List stack = []; int res = 0; // 再帰:再帰呼び出し for (int i = n; i > 0; i--) { // 「スタックへのプッシュ」で「再帰」を模擬する stack.add(i); } // 帰りがけ:結果を返す while (!stack.isEmpty) { // 「スタックから取り出す操作」で「帰り」をシミュレート res += stack.removeLast(); } // res = 1+2+3+...+n return res; } /* 末尾再帰 */ int tailRecur(int n, int res) { // 終了条件 if (n == 0) return res; // 末尾再帰呼び出し return tailRecur(n - 1, res + n); } /* フィボナッチ数列:再帰 */ int fib(int n) { // 終了条件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す int res = fib(n - 1) + fib(n - 2); // 結果 f(n) を返す return res; } /* Driver Code */ void main() { int n = 5; int res; res = recur(n); print("\n再帰関数の合計結果 res = $res"); res = tailRecur(n, 0); print("\n末尾再帰関数の合計結果 res = $res"); res = forLoopRecur(n); print("\n反復で再帰をシミュレートした合計結果 res = $res"); res = fib(n); print("\nフィボナッチ数列の第 $n 項は $res"); } ================================================ FILE: ja/codes/dart/chapter_computational_complexity/space_complexity.dart ================================================ /** * File: space_complexity.dart * Created Time: 2023-2-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable import 'dart:collection'; import '../utils/list_node.dart'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 関数 */ int function() { // 何らかの処理を行う return 0; } /* 定数階 */ void constant(int n) { // 定数、変数、オブジェクトは O(1) の空間を占める final int a = 0; int b = 0; List nums = List.filled(10000, 0); ListNode node = ListNode(0); // ループ内の変数は O(1) の空間を占める for (var i = 0; i < n; i++) { int c = 0; } // ループ内の関数は O(1) の空間を占める for (var i = 0; i < n; i++) { function(); } } /* 線形階 */ void linear(int n) { // 長さ n の配列は O(n) の空間を使用 List nums = List.filled(n, 0); // 長さ n のリストは O(n) の空間を使用 List nodes = []; for (var i = 0; i < n; i++) { nodes.add(ListNode(i)); } // 長さ n のハッシュテーブルは O(n) の空間を使用 Map map = HashMap(); for (var i = 0; i < n; i++) { map.putIfAbsent(i, () => i.toString()); } } /* 線形時間(再帰実装) */ void linearRecur(int n) { print('再帰 n = $n'); if (n == 1) return; linearRecur(n - 1); } /* 二乗階 */ void quadratic(int n) { // 行列は O(n^2) の空間を使用する List> numMatrix = List.generate(n, (_) => List.filled(n, 0)); // 二次元リストは O(n^2) の空間を使用 List> numList = []; for (var i = 0; i < n; i++) { List tmp = []; for (int j = 0; j < n; j++) { tmp.add(0); } numList.add(tmp); } } /* 二次時間(再帰実装) */ int quadraticRecur(int n) { if (n <= 0) return 0; List nums = List.filled(n, 0); print('再帰 n = $n における nums の長さ = ${nums.length}'); return quadraticRecur(n - 1); } /* 指数時間(完全二分木の構築) */ TreeNode? buildTree(int n) { if (n == 0) return null; TreeNode root = TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ void main() { int n = 5; // 定数階 constant(n); // 線形階 linear(n); linearRecur(n); // 二乗階 quadratic(n); quadraticRecur(n); // 指数オーダー TreeNode? root = buildTree(n); printTree(root); } ================================================ FILE: ja/codes/dart/chapter_computational_complexity/time_complexity.dart ================================================ /** * File: time_complexity.dart * Created Time: 2023-02-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable /* 定数階 */ int constant(int n) { int count = 0; int size = 100000; for (var i = 0; i < size; i++) { count++; } return count; } /* 線形階 */ int linear(int n) { int count = 0; for (var i = 0; i < n; i++) { count++; } return count; } /* 線形時間(配列を走査) */ int arrayTraversal(List nums) { int count = 0; // ループ回数は配列長に比例する for (var _num in nums) { count++; } return count; } /* 二乗階 */ int quadratic(int n) { int count = 0; // ループ回数はデータサイズ n の二乗に比例する for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* 二次時間(バブルソート) */ int bubbleSort(List nums) { int count = 0; // カウンタ // 外側のループ:未ソート区間は [0, i] for (var i = nums.length - 1; i > 0; i--) { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (var j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 要素交換には 3 回の単位操作が含まれる } } } return count; } /* 指数時間(ループ実装) */ int exponential(int n) { int count = 0, base = 1; // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する for (var i = 0; i < n; i++) { for (var j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指数時間(再帰実装) */ int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 対数時間(ループ実装) */ int logarithmic(int n) { int count = 0; while (n > 1) { n = n ~/ 2; count++; } return count; } /* 対数時間(再帰実装) */ int logRecur(int n) { if (n <= 1) return 0; return logRecur(n ~/ 2) + 1; } /* 線形対数時間 */ int linearLogRecur(int n) { if (n <= 1) return 1; int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2); for (var i = 0; i < n; i++) { count++; } return count; } /* 階乗時間(再帰実装) */ int factorialRecur(int n) { if (n == 0) return 1; int count = 0; // 1個から n 個に分裂 for (var i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ void main() { // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる int n = 8; print('入力データサイズ n = $n'); int count = constant(n); print('定数時間の操作回数 = $count'); count = linear(n); print('線形時間の操作回数 = $count'); count = arrayTraversal(List.filled(n, 0)); print('線形時間(配列走査)の操作回数 = $count'); count = quadratic(n); print('二次時間の操作回数 = $count'); final nums = List.filled(n, 0); for (int i = 0; i < n; i++) { nums[i] = n - i; // [n,n-1,...,2,1] } count = bubbleSort(nums); print('二次時間(バブルソート)の操作回数 = $count'); count = exponential(n); print('指数時間(ループ実装)の操作回数 = $count'); count = expRecur(n); print('指数時間(再帰実装)の操作回数 = $count'); count = logarithmic(n); print('対数時間(ループ実装)の操作回数 = $count'); count = logRecur(n); print('対数時間(再帰実装)の操作回数 = $count'); count = linearLogRecur(n); print('線形対数時間(再帰実装)の操作回数 = $count'); count = factorialRecur(n); print('階乗時間(再帰実装)の操作回数 = $count'); } ================================================ FILE: ja/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart ================================================ /** * File: worst_best_time_complexity.dart * Created Time: 2023-02-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ List randomNumbers(int n) { final nums = List.filled(n, 0); // 配列 nums = { 1, 2, 3, ..., n } を生成 for (var i = 0; i < n; i++) { nums[i] = i + 1; } // 配列要素をランダムにシャッフル nums.shuffle(); return nums; } /* 配列 nums 内で数値 1 のインデックスを探す */ int findOne(List nums) { for (var i = 0; i < nums.length; i++) { // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる if (nums[i] == 1) return i; } return -1; } /* Driver Code */ void main() { for (var i = 0; i < 10; i++) { int n = 100; final nums = randomNumbers(n); int index = findOne(nums); print('\n配列 [ 1, 2, ..., n ] をシャッフルした後 = $nums'); print('数字 1 のインデックスは + $index'); } } ================================================ FILE: ja/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart ================================================ /** * File: binary_search_recur.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 二分探索:問題 f(i, j) */ int dfs(List nums, int target, int i, int j) { // 区間が空なら対象要素は存在しないので -1 を返す if (i > j) { return -1; } // 中点インデックス m を計算 int m = (i + j) ~/ 2; if (nums[m] < target) { // 部分問題 f(m+1, j) を再帰的に解く return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 部分問題 f(i, m-1) を再帰的に解く return dfs(nums, target, i, m - 1); } else { // 目標要素が見つかったらそのインデックスを返す return m; } } /* 二分探索 */ int binarySearch(List nums, int target) { int n = nums.length; // 問題 f(0, n-1) を解く return dfs(nums, target, 0, n - 1); } /* Driver Code */ void main() { int target = 6; List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分探索(両閉区間) int index = binarySearch(nums, target); print("目標要素 6 のインデックス = $index"); } ================================================ FILE: ja/codes/dart/chapter_divide_and_conquer/build_tree.dart ================================================ /** * File: build_tree.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 二分木を構築:分割統治 */ TreeNode? dfs( List preorder, Map inorderMap, int i, int l, int r, ) { // 部分木区間が空なら終了する if (r - l < 0) { return null; } // ルートノードを初期化する TreeNode? root = TreeNode(preorder[i]); // m を求めて左右部分木を分割する int m = inorderMap[preorder[i]]!; // 部分問題:左部分木を構築する root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // 部分問題:右部分木を構築する root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // 根ノードを返す return root; } /* 二分木を構築 */ TreeNode? buildTree(List preorder, List inorder) { // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する Map inorderMap = {}; for (int i = 0; i < inorder.length; i++) { inorderMap[inorder[i]] = i; } TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ void main() { List preorder = [3, 9, 2, 1, 7]; List inorder = [9, 3, 1, 2, 7]; print("前順走査 = $preorder"); print("中順走査 = $inorder"); TreeNode? root = buildTree(preorder, inorder); print("構築した二分木は:"); printTree(root!); } ================================================ FILE: ja/codes/dart/chapter_divide_and_conquer/hanota.dart ================================================ /** * File: hanota.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 円盤を 1 枚移動 */ void move(List src, List tar) { // src の上から円盤を1枚取り出す int pan = src.removeLast(); // 円盤を tar の上に置く tar.add(pan); } /* ハノイの塔の問題 f(i) を解く */ void dfs(int i, List src, List buf, List tar) { // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if (i == 1) { move(src, tar); return; } // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i - 1, src, tar, buf); // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src, tar); // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i - 1, buf, src, tar); } /* ハノイの塔を解く */ void solveHanota(List A, List B, List C) { int n = A.length; // A の上から n 枚の円盤を B を介して C へ移す dfs(n, A, B, C); } /* Driver Code */ void main() { // リスト末尾が柱の頂上 List A = [5, 4, 3, 2, 1]; List B = []; List C = []; print("初期状態:"); print("A = $A"); print("B = $B"); print("C = $C"); solveHanota(A, B, C); print("円盤の移動完了後:"); print("A = $A"); print("B = $B"); print("C = $C"); } ================================================ FILE: ja/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart ================================================ /** * File: climbing_stairs_backtrack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* バックトラッキング */ void backtrack(List choices, int state, int n, List res) { // 第 n 段に到達したら、方法数を 1 増やす if (state == n) { res[0]++; } // すべての選択肢を走査 for (int choice in choices) { // 枝刈り: 第 n 段を超えないようにする if (state + choice > n) continue; // 試行: 選択を行い、状態を更新 backtrack(choices, state + choice, n, res); // バックトラック } } /* 階段登り:バックトラッキング */ int climbingStairsBacktrack(int n) { List choices = [1, 2]; // 1 段または 2 段上ることを選べる int state = 0; // 第 0 段から上り始める List res = []; res.add(0); // res[0] を使って方法数を記録する backtrack(choices, state, n, res); return res[0]; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsBacktrack(n); print("$n 段の階段の登り方は全部で $res 通り"); } ================================================ FILE: ja/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart ================================================ /** * File: climbing_stairs_constraint_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 制約付き階段登り:動的計画法 */ int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // 部分問題の解を保存するために dp テーブルを初期化 List> dp = List.generate(n + 1, (index) => List.filled(3, 0)); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsConstraintDP(n); print("$n 段の階段の登り方は全部で $res 通り"); } ================================================ FILE: ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart ================================================ /** * File: climbing_stairs_dfs.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 検索 */ int dfs(int i) { // dp[1] と dp[2] は既知なので返す if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1) + dfs(i - 2); return count; } /* 階段登り:探索 */ int climbingStairsDFS(int n) { return dfs(n); } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDFS(n); print("$n 段の階段の登り方は全部で $res 通り"); } ================================================ FILE: ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart ================================================ /** * File: climbing_stairs_dfs_mem.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* メモ化探索 */ int dfs(int i, List mem) { // dp[1] と dp[2] は既知なので返す if (i == 1 || i == 2) return i; // dp[i] の記録があれば、それをそのまま返す if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1, mem) + dfs(i - 2, mem); // dp[i] を記録する mem[i] = count; return count; } /* 階段登り:メモ化探索 */ int climbingStairsDFSMem(int n) { // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す List mem = List.filled(n + 1, -1); return dfs(n, mem); } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDFSMem(n); print("$n 段の階段の登り方は全部で $res 通り"); } ================================================ FILE: ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart ================================================ /** * File: climbing_stairs_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 階段登り:動的計画法 */ int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; // 部分問題の解を保存するために dp テーブルを初期化 List dp = List.filled(n + 1, 0); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = 1; dp[2] = 2; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* 階段登り:空間最適化した動的計画法 */ int climbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDP(n); print("$n 段の階段の登り方は全部で $res 通り"); res = climbingStairsDPComp(n); print("$n 段の階段の登り方は全部で $res 通り"); } ================================================ FILE: ja/codes/dart/chapter_dynamic_programming/coin_change.dart ================================================ /** * File: coin_change.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* コイン両替:動的計画法 */ int coinChangeDP(List coins, int amt) { int n = coins.length; int MAX = amt + 1; // dp テーブルを初期化 List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); // 状態遷移:先頭行と先頭列 for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状態遷移: 残りの行と列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] != MAX ? dp[n][amt] : -1; } /* コイン交換:空間最適化後の動的計画法 */ int coinChangeDPComp(List coins, int amt) { int n = coins.length; int MAX = amt + 1; // dp テーブルを初期化 List dp = List.filled(amt + 1, MAX); dp[0] = 0; // 状態遷移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } /* Driver Code */ void main() { List coins = [1, 2, 5]; int amt = 4; // 動的計画法 int res = coinChangeDP(coins, amt); print("目標金額を作るのに必要な最小硬貨枚数は $res"); // 空間最適化後の動的計画法 res = coinChangeDPComp(coins, amt); print("目標金額を作るのに必要な最小硬貨枚数は $res"); } ================================================ FILE: ja/codes/dart/chapter_dynamic_programming/coin_change_ii.dart ================================================ /** * File: coin_change_ii.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* コイン両替 II:動的計画法 */ int coinChangeIIDP(List coins, int amt) { int n = coins.length; // dp テーブルを初期化 List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); // 先頭列を初期化する for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // 状態遷移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { // コイン i を選ばない場合と選ぶ場合の和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* コイン両替 II:空間最適化した動的計画法 */ int coinChangeIIDPComp(List coins, int amt) { int n = coins.length; // dp テーブルを初期化 List dp = List.filled(amt + 1, 0); dp[0] = 1; // 状態遷移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // コイン i を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ void main() { List coins = [1, 2, 5]; int amt = 5; // 動的計画法 int res = coinChangeIIDP(coins, amt); print("目標金額を作る硬貨の組み合わせ数は $res"); // 空間最適化後の動的計画法 res = coinChangeIIDPComp(coins, amt); print("目標金額を作る硬貨の組み合わせ数は $res"); } ================================================ FILE: ja/codes/dart/chapter_dynamic_programming/edit_distance.dart ================================================ /** * File: edit_distance.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 編集距離:総当たり探索 */ int editDistanceDFS(String s, String t, int i, int j) { // s と t がともに空なら 0 を返す if (i == 0 && j == 0) return 0; // s が空なら t の長さを返す if (i == 0) return j; // t が空なら s の長さを返す if (j == 0) return i; // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 int insert = editDistanceDFS(s, t, i, j - 1); int delete = editDistanceDFS(s, t, i - 1, j); int replace = editDistanceDFS(s, t, i - 1, j - 1); // 最小編集回数を返す return min(min(insert, delete), replace) + 1; } /* 編集距離:メモ化探索 */ int editDistanceDFSMem(String s, String t, List> mem, int i, int j) { // s と t がともに空なら 0 を返す if (i == 0 && j == 0) return 0; // s が空なら t の長さを返す if (i == 0) return j; // t が空なら s の長さを返す if (j == 0) return i; // 記録済みなら、それをそのまま返す if (mem[i][j] != -1) return mem[i][j]; // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 int insert = editDistanceDFSMem(s, t, mem, i, j - 1); int delete = editDistanceDFSMem(s, t, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最小編集回数を記録して返す mem[i][j] = min(min(insert, delete), replace) + 1; return mem[i][j]; } /* 編集距離:動的計画法 */ int editDistanceDP(String s, String t) { int n = s.length, m = t.length; List> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0)); // 状態遷移:先頭行と先頭列 for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // 状態遷移: 残りの行と列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i - 1][j - 1]; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* 編集距離:空間最適化した動的計画法 */ int editDistanceDPComp(String s, String t) { int n = s.length, m = t.length; List dp = List.filled(m + 1, 0); // 状態遷移:先頭行 for (int j = 1; j <= m; j++) { dp[j] = j; } // 状態遷移:残りの行 for (int i = 1; i <= n; i++) { // 状態遷移:先頭列 int leftup = dp[0]; // dp[i-1, j-1] を一時保存する dp[0] = i; // 状態遷移:残りの列 for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftup; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 次の反復の dp[i-1, j-1] に更新する } } return dp[m]; } /* Driver Code */ void main() { String s = "bag"; String t = "pack"; int n = s.length, m = t.length; // 全探索 int res = editDistanceDFS(s, t, n, m); print("" + s + " を " + t + " に変更するには最小で $res 回の編集が必要"); // メモ化探索 List> mem = List.generate(n + 1, (_) => List.filled(m + 1, -1)); res = editDistanceDFSMem(s, t, mem, n, m); print("" + s + " を " + t + " に変更するには最小で $res 回の編集が必要"); // 動的計画法 res = editDistanceDP(s, t); print("" + s + " を " + t + " に変更するには最小で $res 回の編集が必要"); // 空間最適化後の動的計画法 res = editDistanceDPComp(s, t); print("" + s + " を " + t + " に変更するには最小で $res 回の編集が必要"); } ================================================ FILE: ja/codes/dart/chapter_dynamic_programming/knapsack.dart ================================================ /** * File: knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 0-1 ナップサック:総当たり探索 */ int knapsackDFS(List wgt, List val, int i, int c) { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 || c == 0) { return 0; } // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // 2つの案のうち価値が大きいほうを返す return max(no, yes); } /* 0-1 ナップサック:メモ化探索 */ int knapsackDFSMem( List wgt, List val, List> mem, int i, int c, ) { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 || c == 0) { return 0; } // 既に記録があればそのまま返す if (mem[i][c] != -1) { return mem[i][c]; } // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する int no = knapsackDFSMem(wgt, val, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 2 つの案のうち価値が大きい方を記録して返す mem[i][c] = max(no, yes); return mem[i][c]; } /* 0-1 ナップサック:動的計画法 */ int knapsackDP(List wgt, List val, int cap) { int n = wgt.length; // dp テーブルを初期化 List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } /* 0-1 ナップサック:空間最適化後の動的計画法 */ int knapsackDPComp(List wgt, List val, int cap) { int n = wgt.length; // dp テーブルを初期化 List dp = List.filled(cap + 1, 0); // 状態遷移 for (int i = 1; i <= n; i++) { // 逆順に走査する for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ void main() { List wgt = [10, 20, 30, 40, 50]; List val = [50, 120, 150, 210, 240]; int cap = 50; int n = wgt.length; // 全探索 int res = knapsackDFS(wgt, val, n, cap); print("ナップサック容量を超えない最大価値は $res"); // メモ化探索 List> mem = List.generate(n + 1, (index) => List.filled(cap + 1, -1)); res = knapsackDFSMem(wgt, val, mem, n, cap); print("ナップサック容量を超えない最大価値は $res"); // 動的計画法 res = knapsackDP(wgt, val, cap); print("ナップサック容量を超えない最大価値は $res"); // 空間最適化後の動的計画法 res = knapsackDPComp(wgt, val, cap); print("ナップサック容量を超えない最大価値は $res"); } ================================================ FILE: ja/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart ================================================ /** * File: min_cost_climbing_stairs_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 階段登りの最小コスト:動的計画法 */ int minCostClimbingStairsDP(List cost) { int n = cost.length - 1; if (n == 1 || n == 2) return cost[n]; // 部分問題の解を保存するために dp テーブルを初期化 List dp = List.filled(n + 1, 0); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = cost[1]; dp[2] = cost[2]; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* 階段昇りの最小コスト:空間最適化後の動的計画法 */ int minCostClimbingStairsDPComp(List cost) { int n = cost.length - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ void main() { List cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; print("入力された階段コストのリストは $cost"); int res = minCostClimbingStairsDP(cost); print("階段を登り切る最小コストは $res"); res = minCostClimbingStairsDPComp(cost); print("階段を登り切る最小コストは $res"); } ================================================ FILE: ja/codes/dart/chapter_dynamic_programming/min_path_sum.dart ================================================ /** * File: min_path_sum.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 最小経路和:全探索 */ int minPathSumDFS(List> grid, int i, int j) { // 左上のセルなら探索を終了する if (i == 0 && j == 0) { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { // Dart では、int 型は固定範囲の整数であり、「無限大」を表す値は存在しない return BigInt.from(2).pow(31).toInt(); } // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); // 左上隅から (i, j) までの最小経路コストを返す return min(left, up) + grid[i][j]; } /* 最小経路和:メモ化探索 */ int minPathSumDFSMem(List> grid, List> mem, int i, int j) { // 左上のセルなら探索を終了する if (i == 0 && j == 0) { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { // Dart では、int 型は固定範囲の整数であり、「無限大」を表す値は存在しない return BigInt.from(2).pow(31).toInt(); } // 既に記録があればそのまま返す if (mem[i][j] != -1) { return mem[i][j]; } // 左と上のセルからの最小経路コスト int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // 左上から (i, j) までの最小経路コストを記録して返す mem[i][j] = min(left, up) + grid[i][j]; return mem[i][j]; } /* 最小経路和:動的計画法 */ int minPathSumDP(List> grid) { int n = grid.length, m = grid[0].length; // dp テーブルを初期化 List> dp = List.generate(n, (i) => List.filled(m, 0)); dp[0][0] = grid[0][0]; // 状態遷移:先頭行 for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 状態遷移:先頭列 for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 状態遷移: 残りの行と列 for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* 最小経路和:空間最適化後の動的計画法 */ int minPathSumDPComp(List> grid) { int n = grid.length, m = grid[0].length; // dp テーブルを初期化 List dp = List.filled(m, 0); dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 状態遷移:残りの行 for (int i = 1; i < n; i++) { // 状態遷移:先頭列 dp[0] = dp[0] + grid[i][0]; // 状態遷移:残りの列 for (int j = 1; j < m; j++) { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ void main() { List> grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; int n = grid.length, m = grid[0].length; // 全探索 int res = minPathSumDFS(grid, n - 1, m - 1); print("左上から右下までの最小経路和は $res"); // メモ化探索 List> mem = List.generate(n, (i) => List.filled(m, -1)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); print("左上から右下までの最小経路和は $res"); // 動的計画法 res = minPathSumDP(grid); print("左上から右下までの最小経路和は $res"); // 空間最適化後の動的計画法 res = minPathSumDPComp(grid); print("左上から右下までの最小経路和は $res"); } ================================================ FILE: ja/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart ================================================ /** * File: unbounded_knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 完全ナップサック問題:動的計画法 */ int unboundedKnapsackDP(List wgt, List val, int cap) { int n = wgt.length; // dp テーブルを初期化 List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } /* 完全ナップサック問題:空間最適化後の動的計画法 */ int unboundedKnapsackDPComp(List wgt, List val, int cap) { int n = wgt.length; // dp テーブルを初期化 List dp = List.filled(cap + 1, 0); // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ void main() { List wgt = [1, 2, 3]; List val = [5, 11, 15]; int cap = 4; // 動的計画法 int res = unboundedKnapsackDP(wgt, val, cap); print("ナップサック容量を超えない最大価値は $res"); // 空間最適化後の動的計画法 int resComp = unboundedKnapsackDPComp(wgt, val, cap); print("ナップサック容量を超えない最大価値は $resComp"); } ================================================ FILE: ja/codes/dart/chapter_graph/graph_adjacency_list.dart ================================================ /** * File: graph_adjacency_list.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/vertex.dart'; /* 隣接リストに基づく無向グラフクラス */ class GraphAdjList { // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 Map> adjList = {}; /* コンストラクタ */ GraphAdjList(List> edges) { for (List edge in edges) { addVertex(edge[0]); addVertex(edge[1]); addEdge(edge[0], edge[1]); } } /* 頂点数を取得 */ int size() { return adjList.length; } /* 辺を追加 */ void addEdge(Vertex vet1, Vertex vet2) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) { throw ArgumentError; } // 辺 vet1 - vet2 を追加 adjList[vet1]!.add(vet2); adjList[vet2]!.add(vet1); } /* 辺を削除 */ void removeEdge(Vertex vet1, Vertex vet2) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) { throw ArgumentError; } // 辺 vet1 - vet2 を削除 adjList[vet1]!.remove(vet2); adjList[vet2]!.remove(vet1); } /* 頂点を追加 */ void addVertex(Vertex vet) { if (adjList.containsKey(vet)) return; // 隣接リストに新しいリストを追加 adjList[vet] = []; } /* 頂点を削除 */ void removeVertex(Vertex vet) { if (!adjList.containsKey(vet)) { throw ArgumentError; } // 隣接リストから頂点 vet に対応するリストを削除 adjList.remove(vet); // 他の頂点のリストを走査し、vet を含むすべての辺を削除 adjList.forEach((key, value) { value.remove(vet); }); } /* 隣接リストを出力 */ void printAdjList() { print("隣接リスト ="); adjList.forEach((key, value) { List tmp = []; for (Vertex vertex in value) { tmp.add(vertex.val); } print("${key.val}: $tmp,"); }); } } /* Driver Code */ void main() { /* 無向グラフを初期化 */ List v = Vertex.valsToVets([1, 3, 2, 5, 4]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ]; GraphAdjList graph = GraphAdjList(edges); print("\n初期化後、グラフは"); graph.printAdjList(); /* 辺を追加 */ // 頂点 1, 2 は v[0], v[2] graph.addEdge(v[0], v[2]); print("\n辺 1-2 を追加後、グラフは"); graph.printAdjList(); /* 辺を削除 */ // 頂点 1, 3 は v[0], v[1] graph.removeEdge(v[0], v[1]); print("\n辺 1-3 を削除後、グラフは"); graph.printAdjList(); /* 頂点を追加 */ Vertex v5 = Vertex(6); graph.addVertex(v5); print("\n頂点 6 を追加後、グラフは"); graph.printAdjList(); /* 頂点を削除 */ // 頂点 3 は v[1] graph.removeVertex(v[1]); print("\n頂点 3 を削除後、グラフは"); graph.printAdjList(); } ================================================ FILE: ja/codes/dart/chapter_graph/graph_adjacency_matrix.dart ================================================ /** * File: graph_adjacency_matrix.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* 隣接行列に基づく無向グラフクラス */ class GraphAdjMat { List vertices = []; // 頂点要素。要素は「頂点値」を表し、インデックスは「頂点インデックス」を表す List> adjMat = []; // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 /* コンストラクタ */ GraphAdjMat(List vertices, List> edges) { this.vertices = []; this.adjMat = []; // 頂点を追加 for (int val in vertices) { addVertex(val); } // 辺を追加 // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する for (List e in edges) { addEdge(e[0], e[1]); } } /* 頂点数を取得 */ int size() { return vertices.length; } /* 頂点を追加 */ void addVertex(int val) { int n = size(); // 頂点リストに新しい頂点の値を追加 vertices.add(val); // 隣接行列に 1 行追加 List newRow = List.filled(n, 0, growable: true); adjMat.add(newRow); // 隣接行列に 1 列追加 for (List row in adjMat) { row.add(0); } } /* 頂点を削除 */ void removeVertex(int index) { if (index >= size()) { throw IndexError; } // 頂点リストから index の頂点を削除する vertices.removeAt(index); // 隣接行列で index 行を削除する adjMat.removeAt(index); // 隣接行列で index 列を削除する for (List row in adjMat) { row.removeAt(index); } } /* 辺を追加 */ // 引数 i, j は vertices の要素インデックスに対応する void addEdge(int i, int j) { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw IndexError; } // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす adjMat[i][j] = 1; adjMat[j][i] = 1; } /* 辺を削除 */ // 引数 i, j は vertices の要素インデックスに対応する void removeEdge(int i, int j) { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw IndexError; } adjMat[i][j] = 0; adjMat[j][i] = 0; } /* 隣接行列を出力 */ void printAdjMat() { print("頂点リスト = $vertices"); print("隣接行列 = "); printMatrix(adjMat); } } /* Driver Code */ void main() { /* 無向グラフを初期化 */ // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 List vertices = [1, 3, 2, 5, 4]; List> edges = [ [0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4], ]; GraphAdjMat graph = GraphAdjMat(vertices, edges); print("\n初期化後、グラフは"); graph.printAdjMat(); /* 辺を追加 */ // 頂点 1, 2 のインデックスはそれぞれ 0, 2 graph.addEdge(0, 2); print("\n辺 1-2 を追加後、グラフは"); graph.printAdjMat(); /* 辺を削除 */ // 頂点 1, 3 のインデックスはそれぞれ 0, 1 graph.removeEdge(0, 1); print("\n辺 1-3 を削除後、グラフは"); graph.printAdjMat(); /* 頂点を追加 */ graph.addVertex(6); print("\n頂点 6 を追加後、グラフは"); graph.printAdjMat(); /* 頂点を削除 */ // 頂点 3 のインデックスは 1 graph.removeVertex(1); print("\n頂点 3 を削除後、グラフは"); graph.printAdjMat(); } ================================================ FILE: ja/codes/dart/chapter_graph/graph_bfs.dart ================================================ /** * File: graph_bfs.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; import '../utils/vertex.dart'; import 'graph_adjacency_list.dart'; /* 幅優先探索 */ List graphBFS(GraphAdjList graph, Vertex startVet) { // 指定した頂点の隣接頂点をすべて取得できるよう、隣接リストでグラフを表現する // 頂点の走査順序 List res = []; // 訪問済み頂点を記録するためのハッシュ集合 Set visited = {}; visited.add(startVet); // BFS の実装にキューを用いる Queue que = Queue(); que.add(startVet); // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す while (que.isNotEmpty) { Vertex vet = que.removeFirst(); // 先頭の頂点をデキュー res.add(vet); // 訪問した頂点を記録 // この頂点のすべての隣接頂点を走査 for (Vertex adjVet in graph.adjList[vet]!) { if (visited.contains(adjVet)) { continue; // 訪問済みの頂点をスキップ } que.add(adjVet); // 未訪問の頂点のみをキューに追加 visited.add(adjVet); // この頂点を訪問済みにする } } // 頂点の走査順を返す return res; } /* Dirver Code */ void main() { /* 無向グラフを初期化 */ List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; GraphAdjList graph = GraphAdjList(edges); print("\n初期化後、グラフは"); graph.printAdjList(); /* 幅優先探索 */ List res = graphBFS(graph, v[0]); print("\n幅優先探索(BFS)頂点列"); print(Vertex.vetsToVals(res)); } ================================================ FILE: ja/codes/dart/chapter_graph/graph_dfs.dart ================================================ /** * File: graph_dfs.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/vertex.dart'; import 'graph_adjacency_list.dart'; /* 深さ優先走査の補助関数 */ void dfs( GraphAdjList graph, Set visited, List res, Vertex vet, ) { res.add(vet); // 訪問した頂点を記録 visited.add(vet); // この頂点を訪問済みにする // この頂点のすべての隣接頂点を走査 for (Vertex adjVet in graph.adjList[vet]!) { if (visited.contains(adjVet)) { continue; // 訪問済みの頂点をスキップ } // 隣接頂点を再帰的に訪問 dfs(graph, visited, res, adjVet); } } /* 深さ優先探索 */ List graphDFS(GraphAdjList graph, Vertex startVet) { // 頂点の走査順序 List res = []; // 訪問済み頂点を記録するためのハッシュ集合 Set visited = {}; dfs(graph, visited, res, startVet); return res; } /* Driver Code */ void main() { /* 無向グラフを初期化 */ List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; GraphAdjList graph = GraphAdjList(edges); print("\n初期化後、グラフは"); graph.printAdjList(); /* 深さ優先探索 */ List res = graphDFS(graph, v[0]); print("\n深さ優先探索(DFS)頂点列"); print(Vertex.vetsToVals(res)); } ================================================ FILE: ja/codes/dart/chapter_greedy/coin_change_greedy.dart ================================================ /** * File: coin_change_greedy.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* コイン交換:貪欲法 */ int coinChangeGreedy(List coins, int amt) { // coins リストはソート済みと仮定する int i = coins.length - 1; int count = 0; // 残額がなくなるまで貪欲選択を繰り返す while (amt > 0) { // 残額以下で最も近い硬貨を見つける while (i > 0 && coins[i] > amt) { i--; } // coins[i] を選択する amt -= coins[i]; count++; } // 実行可能な解が見つからなければ -1 を返す return amt == 0 ? count : -1; } /* Driver Code */ void main() { // 貪欲法:大域最適解を保証できる List coins = [1, 5, 10, 20, 50, 100]; int amt = 186; int res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("$amt を作るのに必要な最小硬貨枚数は $res"); // 貪欲法:大域最適解を保証できない coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("$amt を作るのに必要な最小硬貨枚数は $res"); print("実際に必要な最小枚数は 3 、つまり 20 + 20 + 20"); // 貪欲法:大域最適解を保証できない coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("$amt を作るのに必要な最小硬貨枚数は $res"); print("実際に必要な最小枚数は 2 、つまり 49 + 49"); } ================================================ FILE: ja/codes/dart/chapter_greedy/fractional_knapsack.dart ================================================ /** * File: fractional_knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 品物 */ class Item { int w; // 品物の重さ int v; // 品物の価値 Item(this.w, this.v); } /* 分数ナップサック:貪欲法 */ double fractionalKnapsack(List wgt, List val, int cap) { // 重さと価値の 2 属性を持つ品物リストを作成 List items = List.generate(wgt.length, (i) => Item(wgt[i], val[i])); // 単位価値 item.v / item.w の高い順にソートする items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w)); // 貪欲選択を繰り返す double res = 0; for (Item item in items) { if (item.w <= cap) { // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる res += item.v; cap -= item.w; } else { // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる res += item.v / item.w * cap; // 残り容量がないため、ループを抜ける break; } } return res; } /* Driver Code */ void main() { List wgt = [10, 20, 30, 40, 50]; List val = [50, 120, 150, 210, 240]; int cap = 50; // 貪欲法 double res = fractionalKnapsack(wgt, val, cap); print("ナップサック容量を超えない最大価値は $res"); } ================================================ FILE: ja/codes/dart/chapter_greedy/max_capacity.dart ================================================ /** * File: max_capacity.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 最大容量:貪欲法 */ int maxCapacity(List ht) { // i, j を初期化し、それぞれ配列の両端に置く int i = 0, j = ht.length - 1; // 初期の最大容量は 0 int res = 0; // 2 枚の板が出会うまで貪欲選択を繰り返す while (i < j) { // 最大容量を更新する int cap = min(ht[i], ht[j]) * (j - i); res = max(res, cap); // 短い方を内側へ動かす if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ void main() { List ht = [3, 8, 5, 2, 7, 7, 3, 4]; // 貪欲法 int res = maxCapacity(ht); print("最大容量は $res"); } ================================================ FILE: ja/codes/dart/chapter_greedy/max_product_cutting.dart ================================================ /** * File: max_product_cutting.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 最大切断積:貪欲法 */ int maxProductCutting(int n) { // n <= 3 のときは、必ず 1 を切り出す if (n <= 3) { return 1 * (n - 1); } // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする int a = n ~/ 3; int b = n % 3; if (b == 1) { // 余りが 1 のときは、1 * 3 を 2 * 2 に変える return (pow(3, a - 1) * 2 * 2).toInt(); } if (b == 2) { // 余りが 2 のときは、そのままにする return (pow(3, a) * 2).toInt(); } // 余りが 0 のときは、そのままにする return pow(3, a).toInt(); } /* Driver Code */ void main() { int n = 58; // 貪欲法 int res = maxProductCutting(n); print("最大分割積は $res"); } ================================================ FILE: ja/codes/dart/chapter_hashing/array_hash_map.dart ================================================ /** * File: array_hash_map.dart * Created Time: 2023-03-29 * Author: liuyuxin (gvenusleo@gmail.com) */ /* キーと値の組 */ class Pair { int key; String val; Pair(this.key, this.val); } /* 配列ベースのハッシュテーブル */ class ArrayHashMap { late List _buckets; ArrayHashMap() { // 100 個のバケットを含む配列を初期化 _buckets = List.filled(100, null); } /* ハッシュ関数 */ int _hashFunc(int key) { final int index = key % 100; return index; } /* 検索操作 */ String? get(int key) { final int index = _hashFunc(key); final Pair? pair = _buckets[index]; if (pair == null) { return null; } return pair.val; } /* 追加操作 */ void put(int key, String val) { final Pair pair = Pair(key, val); final int index = _hashFunc(key); _buckets[index] = pair; } /* 削除操作 */ void remove(int key) { final int index = _hashFunc(key); _buckets[index] = null; } /* すべてのキーと値のペアを取得 */ List pairSet() { List pairSet = []; for (final Pair? pair in _buckets) { if (pair != null) { pairSet.add(pair); } } return pairSet; } /* すべてのキーを取得 */ List keySet() { List keySet = []; for (final Pair? pair in _buckets) { if (pair != null) { keySet.add(pair.key); } } return keySet; } /* すべての値を取得 */ List values() { List valueSet = []; for (final Pair? pair in _buckets) { if (pair != null) { valueSet.add(pair.val); } } return valueSet; } /* ハッシュテーブルを出力 */ void printHashMap() { for (final Pair kv in pairSet()) { print("${kv.key} -> ${kv.val}"); } } } /* Driver Code */ void main() { /* ハッシュテーブルを初期化 */ final ArrayHashMap map = ArrayHashMap(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.put(12836, "シャオハー"); map.put(15937, "シャオルオ"); map.put(16750, "シャオスワン"); map.put(13276, "シャオファー"); map.put(10583, "シャオヤー"); print("\n追加完了後、ハッシュテーブルは\nKey -> Value"); map.printHashMap(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 String? name = map.get(15937); print("\n学籍番号 15937 を入力すると、名前 $name が見つかりました"); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(10583); print("\n10583 を削除後、ハッシュテーブルは\nKey -> Value"); map.printHashMap(); /* ハッシュテーブルを走査 */ print("\nキーと値のペア Key->Value を走査"); map.pairSet().forEach((kv) => print("${kv.key} -> ${kv.val}")); print("\nキー Key のみを走査"); map.keySet().forEach((key) => print("$key")); print("\n値 Value のみを走査"); map.values().forEach((val) => print("$val")); } ================================================ FILE: ja/codes/dart/chapter_hashing/built_in_hash.dart ================================================ /** * File: built_in_hash.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../chapter_stack_and_queue/linkedlist_deque.dart'; /* Driver Code */ void main() { int _num = 3; int hashNum = _num.hashCode; print("整数 $_num のハッシュ値は $hashNum"); bool bol = true; int hashBol = bol.hashCode; print("真偽値 $bol のハッシュ値は $hashBol"); double dec = 3.14159; int hashDec = dec.hashCode; print("小数 $dec のハッシュ値は $hashDec です"); String str = "Hello アルゴリズム"; int hashStr = str.hashCode; print("文字列 $str のハッシュ値は $hashStr です"); List arr = [12836, "シャオハー"]; int hashArr = arr.hashCode; print("配列 $arr のハッシュ値は $hashArr です"); ListNode obj = new ListNode(0); int hashObj = obj.hashCode; print("ノードオブジェクト $obj のハッシュ値は $hashObj です"); } ================================================ FILE: ja/codes/dart/chapter_hashing/hash_map.dart ================================================ /** * File: hash_map.dart * Created Time: 2023-03-29 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Driver Code */ void main() { /* ハッシュテーブルを初期化 */ final Map map = {}; /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map[12836] = "シャオハー"; map[15937] = "シャオルオ"; map[16750] = "シャオスワン"; map[13276] = "シャオファー"; map[10583] = "シャオヤー"; print("\n追加完了後、ハッシュテーブルは\nKey -> Value"); map.forEach((key, value) => print("$key -> $value")); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 final String? name = map[15937]; print("\n学籍番号 15937 を入力すると、名前 $name が見つかりました"); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(10583); print("\n10583 を削除後、ハッシュテーブルは\nKey -> Value"); map.forEach((key, value) => print("$key -> $value")); /* ハッシュテーブルを走査 */ print("\nキーと値のペア Key->Value を走査"); map.forEach((key, value) => print("$key -> $value")); print("\nキー Key のみを走査"); map.keys.forEach((key) => print(key)); print("\n値 Value のみを走査"); map.forEach((key, value) => print("$value")); map.values.forEach((value) => print(value)); } ================================================ FILE: ja/codes/dart/chapter_hashing/hash_map_chaining.dart ================================================ /** * File: hash_map_chaining.dart * Created Time: 2023-06-24 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'array_hash_map.dart'; /* チェイン法ハッシュテーブル */ class HashMapChaining { late int size; // キーと値のペア数 late int capacity; // ハッシュテーブル容量 late double loadThres; // リサイズを発動する負荷率のしきい値 late int extendRatio; // 拡張倍率 late List> buckets; // バケット配列 /* コンストラクタ */ HashMapChaining() { size = 0; capacity = 4; loadThres = 2.0 / 3.0; extendRatio = 2; buckets = List.generate(capacity, (_) => []); } /* ハッシュ関数 */ int hashFunc(int key) { return key % capacity; } /* 負荷率 */ double loadFactor() { return size / capacity; } /* 検索操作 */ String? get(int key) { int index = hashFunc(key); List bucket = buckets[index]; // バケットを走査し、key が見つかれば対応する val を返す for (Pair pair in bucket) { if (pair.key == key) { return pair.val; } } // key が見つからない場合は null を返す return null; } /* 追加操作 */ void put(int key, String val) { // 負荷率がしきい値を超えたら、リサイズを実行 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); List bucket = buckets[index]; // バケットを走査し、指定した key が見つかれば対応する val を更新して返す for (Pair pair in bucket) { if (pair.key == key) { pair.val = val; return; } } // その key が存在しなければ、キーと値のペアを末尾に追加 Pair pair = Pair(key, val); bucket.add(pair); size++; } /* 削除操作 */ void remove(int key) { int index = hashFunc(key); List bucket = buckets[index]; // バケットを走査してキーと値のペアを削除 for (Pair pair in bucket) { if (pair.key == key) { bucket.remove(pair); size--; break; } } } /* ハッシュテーブルを拡張 */ void extend() { // 元のハッシュテーブルを一時保存 List> bucketsTmp = buckets; // リサイズ後の新しいハッシュテーブルを初期化 capacity *= extendRatio; buckets = List.generate(capacity, (_) => []); size = 0; // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for (List bucket in bucketsTmp) { for (Pair pair in bucket) { put(pair.key, pair.val); } } } /* ハッシュテーブルを出力 */ void printHashMap() { for (List bucket in buckets) { List res = []; for (Pair pair in bucket) { res.add("${pair.key} -> ${pair.val}"); } print(res); } } } /* Driver Code */ void main() { /* ハッシュテーブルを初期化 */ HashMapChaining map = HashMapChaining(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.put(12836, "シャオハー"); map.put(15937, "シャオルオ"); map.put(16750, "シャオスワン"); map.put(13276, "シャオファー"); map.put(10583, "シャオヤー"); print("\n追加完了後、ハッシュテーブルは\nKey -> Value"); map.printHashMap(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 String? name = map.get(13276); print("\n学籍番号 13276 を入力すると、氏名 ${name} が見つかります"); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(12836); print("\n12836 を削除した後、ハッシュテーブルは\nKey -> Value"); map.printHashMap(); } ================================================ FILE: ja/codes/dart/chapter_hashing/hash_map_open_addressing.dart ================================================ /** * File: hash_map_open_addressing.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'array_hash_map.dart'; /* オープンアドレス法ハッシュテーブル */ class HashMapOpenAddressing { late int _size; // キーと値のペア数 int _capacity = 4; // ハッシュテーブル容量 double _loadThres = 2.0 / 3.0; // リサイズを発動する負荷率のしきい値 int _extendRatio = 2; // 拡張倍率 late List _buckets; // バケット配列 Pair _TOMBSTONE = Pair(-1, "-1"); // 削除済みマーク /* コンストラクタ */ HashMapOpenAddressing() { _size = 0; _buckets = List.generate(_capacity, (index) => null); } /* ハッシュ関数 */ int hashFunc(int key) { return key % _capacity; } /* 負荷率 */ double loadFactor() { return _size / _capacity; } /* key に対応するバケットインデックスを探す */ int findBucket(int key) { int index = hashFunc(key); int firstTombstone = -1; // 線形プロービングを行い、空バケットに達したら終了 while (_buckets[index] != null) { // key が見つかったら、対応するバケットのインデックスを返す if (_buckets[index]!.key == key) { // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 if (firstTombstone != -1) { _buckets[firstTombstone] = _buckets[index]; _buckets[index] = _TOMBSTONE; return firstTombstone; // 移動後のバケットインデックスを返す } return index; // バケットのインデックスを返す } // 最初に見つかった削除マークを記録 if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) { firstTombstone = index; } // バケットのインデックスを計算し、末尾を越えたら先頭に戻る index = (index + 1) % _capacity; } // key が存在しない場合は追加位置のインデックスを返す return firstTombstone == -1 ? index : firstTombstone; } /* 検索操作 */ String? get(int key) { // key に対応するバケットインデックスを探す int index = findBucket(key); // キーと値の組が見つかったら、対応する val を返す if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { return _buckets[index]!.val; } // キーと値の組が存在しなければ null を返す return null; } /* 追加操作 */ void put(int key, String val) { // 負荷率がしきい値を超えたら、リサイズを実行 if (loadFactor() > _loadThres) { extend(); } // key に対応するバケットインデックスを探す int index = findBucket(key); // キーと値の組が見つかったら、val を上書きして返す if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { _buckets[index]!.val = val; return; } // キーと値の組が存在しない場合は、その組を追加する _buckets[index] = new Pair(key, val); _size++; } /* 削除操作 */ void remove(int key) { // key に対応するバケットインデックスを探す int index = findBucket(key); // キーと値の組が見つかったら、削除マーカーで上書きする if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { _buckets[index] = _TOMBSTONE; _size--; } } /* ハッシュテーブルを拡張 */ void extend() { // 元のハッシュテーブルを一時保存 List bucketsTmp = _buckets; // リサイズ後の新しいハッシュテーブルを初期化 _capacity *= _extendRatio; _buckets = List.generate(_capacity, (index) => null); _size = 0; // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for (Pair? pair in bucketsTmp) { if (pair != null && pair != _TOMBSTONE) { put(pair.key, pair.val); } } } /* ハッシュテーブルを出力 */ void printHashMap() { for (Pair? pair in _buckets) { if (pair == null) { print("null"); } else if (pair == _TOMBSTONE) { print("TOMBSTONE"); } else { print("${pair.key} -> ${pair.val}"); } } } } /* Driver Code */ void main() { /* ハッシュテーブルを初期化 */ HashMapOpenAddressing map = HashMapOpenAddressing(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.put(12836, "シャオハー"); map.put(15937, "シャオルオ"); map.put(16750, "シャオスワン"); map.put(13276, "シャオファー"); map.put(10583, "シャオヤー"); print("\n追加完了後、ハッシュテーブルは\nKey -> Value"); map.printHashMap(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 String? name = map.get(13276); print("\n学籍番号 13276 を入力すると、氏名 $name が見つかります"); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(16750); print("\n16750 を削除した後、ハッシュテーブルは\nKey -> Value"); map.printHashMap(); } ================================================ FILE: ja/codes/dart/chapter_hashing/simple_hash.dart ================================================ /** * File: simple_hash.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 加算ハッシュ */ int addHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = (hash + key.codeUnitAt(i)) % MODULUS; } return hash; } /* 乗算ハッシュ */ int mulHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = (31 * hash + key.codeUnitAt(i)) % MODULUS; } return hash; } /* XOR ハッシュ */ int xorHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash ^= key.codeUnitAt(i); } return hash & MODULUS; } /* 回転ハッシュ */ int rotHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS; } return hash; } /* Dirver Code */ void main() { String key = "Hello アルゴリズム"; int hash = addHash(key); print("加算ハッシュ値は $hash です"); hash = mulHash(key); print("乗算ハッシュ値は $hash です"); hash = xorHash(key); print("XOR ハッシュ値は $hash です"); hash = rotHash(key); print("回転ハッシュ値は $hash です"); } ================================================ FILE: ja/codes/dart/chapter_heap/my_heap.dart ================================================ /** * File: my_heap.dart * Created Time: 2023-04-09 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* 最大ヒープ */ class MaxHeap { late List _maxHeap; /* コンストラクタ。入力リストに基づいてヒープを構築する */ MaxHeap(List nums) { // リスト要素をそのままヒープに追加 _maxHeap = nums; // 葉ノード以外のすべてのノードをヒープ化 for (int i = _parent(size() - 1); i >= 0; i--) { siftDown(i); } } /* 左子ノードのインデックスを取得 */ int _left(int i) { return 2 * i + 1; } /* 右子ノードのインデックスを取得 */ int _right(int i) { return 2 * i + 2; } /* 親ノードのインデックスを取得 */ int _parent(int i) { return (i - 1) ~/ 2; // 切り捨て除算 } /* 要素を交換 */ void _swap(int i, int j) { int tmp = _maxHeap[i]; _maxHeap[i] = _maxHeap[j]; _maxHeap[j] = tmp; } /* ヒープのサイズを取得 */ int size() { return _maxHeap.length; } /* ヒープが空かどうかを判定 */ bool isEmpty() { return size() == 0; } /* ヒープ先頭要素にアクセス */ int peek() { return _maxHeap[0]; } /* 要素をヒープに追加 */ void push(int val) { // ノードを追加 _maxHeap.add(val); // 下から上へヒープ化 siftUp(size() - 1); } /* ノード i から始めて、下から上へヒープ化 */ void siftUp(int i) { while (true) { // ノード i の親ノードを取得 int p = _parent(i); // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 if (p < 0 || _maxHeap[i] <= _maxHeap[p]) { break; } // 2 つのノードを交換 _swap(i, p); // ループで下から上へヒープ化 i = p; } } /* 要素をヒープから取り出す */ int pop() { // 空判定の処理 if (isEmpty()) throw Exception('ヒープが空です'); // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) _swap(0, size() - 1); // ノードを削除 int val = _maxHeap.removeLast(); // 上から下へヒープ化 siftDown(0); // ヒープ先頭要素を返す return val; } /* ノード i から始めて、上から下へヒープ化 */ void siftDown(int i) { while (true) { // ノード i, l, r のうち値が最大のノードを ma とする int l = _left(i); int r = _right(i); int ma = i; if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l; if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r; // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma == i) break; // 2 つのノードを交換 _swap(i, ma); // ループで上から下へヒープ化 i = ma; } } /* ヒープ(二分木)を出力 */ void print() { printHeap(_maxHeap); } } /* Driver Code */ void main() { /* 最大ヒープを初期化 */ MaxHeap maxHeap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); print("\nリストを入力してヒープを構築した後"); maxHeap.print(); /* ヒープ頂点の要素を取得 */ int peek = maxHeap.peek(); print("\nヒープの先頭要素は $peek です"); /* 要素をヒープに追加 */ int val = 7; maxHeap.push(val); print("\n要素 $val をヒープに追加した後"); maxHeap.print(); /* ヒープ頂点の要素を取り出す */ peek = maxHeap.pop(); print("\nヒープの先頭要素 $peek を取り出した後"); maxHeap.print(); /* ヒープのサイズを取得 */ int size = maxHeap.size(); print("\nヒープ要素数は $size です"); /* ヒープが空かどうかを判定 */ bool isEmpty = maxHeap.isEmpty(); print("\nヒープが空かどうか $isEmpty"); } ================================================ FILE: ja/codes/dart/chapter_heap/top_k.dart ================================================ /** * File: top_k.dart * Created Time: 2023-08-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* ヒープに基づいて配列中の最大の k 個の要素を探す */ MinHeap topKHeap(List nums, int k) { // 最小ヒープを初期化し、配列の先頭 k 個の要素をヒープに入れる MinHeap heap = MinHeap(nums.sublist(0, k)); // k+1 番目の要素から開始し、ヒープ長を k に保つ for (int i = k; i < nums.length; i++) { // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する if (nums[i] > heap.peek()) { heap.pop(); heap.push(nums[i]); } } return heap; } /* Driver Code */ void main() { List nums = [1, 7, 6, 3, 2]; int k = 3; MinHeap res = topKHeap(nums, k); print("最大の $k 個の要素は"); res.print(); } /* 最小ヒープ */ class MinHeap { late List _minHeap; /* コンストラクタ。入力リストに基づいてヒープを構築する */ MinHeap(List nums) { // リスト要素をそのままヒープに追加 _minHeap = nums; // 葉ノード以外のすべてのノードをヒープ化 for (int i = _parent(size() - 1); i >= 0; i--) { siftDown(i); } } /* ヒープ内の要素を返す */ List getHeap() { return _minHeap; } /* 左子ノードのインデックスを取得 */ int _left(int i) { return 2 * i + 1; } /* 右子ノードのインデックスを取得 */ int _right(int i) { return 2 * i + 2; } /* 親ノードのインデックスを取得 */ int _parent(int i) { return (i - 1) ~/ 2; // 切り捨て除算 } /* 要素を交換 */ void _swap(int i, int j) { int tmp = _minHeap[i]; _minHeap[i] = _minHeap[j]; _minHeap[j] = tmp; } /* ヒープのサイズを取得 */ int size() { return _minHeap.length; } /* ヒープが空かどうかを判定 */ bool isEmpty() { return size() == 0; } /* ヒープ先頭要素にアクセス */ int peek() { return _minHeap[0]; } /* 要素をヒープに追加 */ void push(int val) { // ノードを追加 _minHeap.add(val); // 下から上へヒープ化 siftUp(size() - 1); } /* ノード i から始めて、下から上へヒープ化 */ void siftUp(int i) { while (true) { // ノード i の親ノードを取得 int p = _parent(i); // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 if (p < 0 || _minHeap[i] >= _minHeap[p]) { break; } // 2 つのノードを交換 _swap(i, p); // ループで下から上へヒープ化 i = p; } } /* 要素をヒープから取り出す */ int pop() { // 空判定の処理 if (isEmpty()) throw Exception('ヒープが空です'); // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) _swap(0, size() - 1); // ノードを削除 int val = _minHeap.removeLast(); // 上から下へヒープ化 siftDown(0); // ヒープ先頭要素を返す return val; } /* ノード i から始めて、上から下へヒープ化 */ void siftDown(int i) { while (true) { // ノード i, l, r のうち値が最大のノードを ma とする int l = _left(i); int r = _right(i); int mi = i; if (l < size() && _minHeap[l] < _minHeap[mi]) mi = l; if (r < size() && _minHeap[r] < _minHeap[mi]) mi = r; // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (mi == i) break; // 2 つのノードを交換 _swap(i, mi); // ループで上から下へヒープ化 i = mi; } } /* ヒープ(二分木)を出力 */ void print() { printHeap(_minHeap); } } ================================================ FILE: ja/codes/dart/chapter_searching/binary_search.dart ================================================ /** * File: binary_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 二分探索(両閉区間) */ int binarySearch(List nums, int target) { // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す int i = 0, j = nums.length - 1; // ループし、探索区間が空になったら終了する(i > j で空) while (i <= j) { int m = i + (j - i) ~/ 2; // 中点インデックス m を計算 if (nums[m] < target) { // この場合、target は区間 [m+1, j] にある i = m + 1; } else if (nums[m] > target) { // この場合、target は区間 [i, m-1] にある j = m - 1; } else { // 目標要素が見つかったらそのインデックスを返す return m; } } // 目標要素が見つからなければ -1 を返す return -1; } /* 二分探索(左閉右開区間) */ int binarySearchLCRO(List nums, int target) { // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す int i = 0, j = nums.length; // ループし、探索区間が空になったら終了する(i = j で空) while (i < j) { int m = i + (j - i) ~/ 2; // 中点インデックス m を計算 if (nums[m] < target) { // この場合、target は区間 [m+1, j) にある i = m + 1; } else if (nums[m] > target) { // この場合、target は区間 [i, m) にある j = m; } else { // 目標要素が見つかったらそのインデックスを返す return m; } } // 目標要素が見つからなければ -1 を返す return -1; } /* Driver Code*/ void main() { int target = 6; final nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分探索(両閉区間) */ int index = binarySearch(nums, target); print('対象要素 6 のインデックス = $index'); /* 二分探索(左閉右開区間) */ index = binarySearchLCRO(nums, target); print('対象要素 6 のインデックス = $index'); } ================================================ FILE: ja/codes/dart/chapter_searching/binary_search_edge.dart ================================================ /** * File: binary_search_edge.dart * Created Time: 2023-08-14 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'binary_search_insertion.dart'; /* 最も左の target を二分探索 */ int binarySearchLeftEdge(List nums, int target) { // target の挿入位置を探すのと等価 int i = binarySearchInsertion(nums, target); // target が見つからなければ、-1 を返す if (i == nums.length || nums[i] != target) { return -1; } // target が見つかったら、インデックス i を返す return i; } /* 最も右の target を二分探索 */ int binarySearchRightEdge(List nums, int target) { // 最左の target + 1 を探す問題に変換する int i = binarySearchInsertion(nums, target + 1); // j は最も右の target を指し、i は target より大きい最初の要素を指す int j = i - 1; // target が見つからなければ、-1 を返す if (j == -1 || nums[j] != target) { return -1; } // target が見つかったら、インデックス j を返す return j; } /* Driver Code */ void main() { // 重複要素を含む配列 List nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; print("\n配列 nums = $nums"); // 二分探索で左端と右端を探す for (int target in [6, 7]) { int index = binarySearchLeftEdge(nums, target); print("最も左の要素 $target のインデックスは $index です"); index = binarySearchRightEdge(nums, target); print("最も右の要素 $target のインデックスは $index です"); } } ================================================ FILE: ja/codes/dart/chapter_searching/binary_search_insertion.dart ================================================ /** * File: binary_search_insertion.dart * Created Time: 2023-08-14 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 二分探索で挿入位置を探す(重複要素なし) */ int binarySearchInsertionSimple(List nums, int target) { int i = 0, j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { int m = i + (j - i) ~/ 2; // 中点インデックス m を計算 if (nums[m] < target) { i = m + 1; // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1; // target は区間 [i, m-1] にある } else { return m; // target が見つかったら、挿入位置 m を返す } } // target が見つからなければ、挿入位置 i を返す return i; } /* 二分探索で挿入位置を探す(重複要素あり) */ int binarySearchInsertion(List nums, int target) { int i = 0, j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { int m = i + (j - i) ~/ 2; // 中点インデックス m を計算 if (nums[m] < target) { i = m + 1; // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1; // target は区間 [i, m-1] にある } else { j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある } } // 挿入位置 i を返す return i; } /* Driver Code */ void main() { // 重複要素のない配列 List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; print("\n配列 nums = $nums"); // 二分探索で挿入位置を探す for (int target in [6, 9]) { int index = binarySearchInsertionSimple(nums, target); print("要素 $target の挿入位置のインデックスは $index です"); } // 重複要素を含む配列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; print("\n配列 nums = $nums"); // 二分探索で挿入位置を探す for (int target in [2, 6, 20]) { int index = binarySearchInsertion(nums, target); print("要素 $target の挿入位置のインデックスは $index です"); } } ================================================ FILE: ja/codes/dart/chapter_searching/hashing_search.dart ================================================ /** * File: hashing_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:collection'; import '../utils/list_node.dart'; /* ハッシュ探索(配列) */ int hashingSearchArray(Map map, int target) { // ハッシュテーブルの key: 目標要素、value: インデックス // ハッシュテーブルにこの key がなければ -1 を返す if (!map.containsKey(target)) { return -1; } return map[target]!; } /* ハッシュ探索(連結リスト) */ ListNode? hashingSearchLinkedList(Map map, int target) { // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト // ハッシュテーブルにこの key がなければ null を返す if (!map.containsKey(target)) { return null; } return map[target]!; } /* Driver Code */ void main() { int target = 3; /* ハッシュ探索(配列) */ List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // ハッシュテーブルを初期化 Map map = HashMap(); for (int i = 0; i < nums.length; i++) { map.putIfAbsent(nums[i], () => i); // key: 要素、value: インデックス } int index = hashingSearchArray(map, target); print('対象要素 3 のインデックス = $index'); /* ハッシュ探索(連結リスト) */ ListNode? head = listToLinkedList(nums); // ハッシュテーブルを初期化 Map map1 = HashMap(); while (head != null) { map1.putIfAbsent(head.val, () => head!); // key: ノード値、value: ノード head = head.next; } ListNode? node = hashingSearchLinkedList(map1, target); print('目標ノード値 3 に対応するノードオブジェクトは $node です'); } ================================================ FILE: ja/codes/dart/chapter_searching/linear_search.dart ================================================ /** * File: linear_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import '../utils/list_node.dart'; /* 線形探索(配列) */ int linearSearchArray(List nums, int target) { // 配列を走査 for (int i = 0; i < nums.length; i++) { // 目標要素が見つかったらそのインデックスを返す if (nums[i] == target) { return i; } } // 目標要素が見つからなければ -1 を返す return -1; } /* 線形探索(連結リスト) */ ListNode? linearSearchList(ListNode? head, int target) { // 連結リストを走査 while (head != null) { // 対象ノードが見つかったら、それを返す if (head.val == target) return head; head = head.next; } // 対象要素が見つからない場合は `null` を返す return null; } /* Driver Code */ void main() { int target = 3; /* 配列で線形探索を行う */ List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; int index = linearSearchArray(nums, target); print('対象要素 3 のインデックス = $index'); /* 連結リストで線形探索を行う */ ListNode? head = listToLinkedList(nums); ListNode? node = linearSearchList(head, target); print('目標ノード値 3 に対応するノードオブジェクトは $node です'); } ================================================ FILE: ja/codes/dart/chapter_searching/two_sum.dart ================================================ /** * File: two_sum.dart * Created Time: 2023-2-11 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:collection'; /* 方法1: 総当たり列挙 */ List twoSumBruteForce(List nums, int target) { int size = nums.length; // 2重ループのため、時間計算量は O(n^2) for (var i = 0; i < size - 1; i++) { for (var j = i + 1; j < size; j++) { if (nums[i] + nums[j] == target) return [i, j]; } } return [0]; } /* 方法2: 補助ハッシュテーブル */ List twoSumHashTable(List nums, int target) { int size = nums.length; // 補助ハッシュテーブルを使用し、空間計算量は O(n) Map dic = HashMap(); // 単一ループで、時間計算量は O(n) for (var i = 0; i < size; i++) { if (dic.containsKey(target - nums[i])) { return [dic[target - nums[i]]!, i]; } dic.putIfAbsent(nums[i], () => i); } return [0]; } /* Driver Code */ void main() { // ======= Test Case ======= List nums = [2, 7, 11, 15]; int target = 13; // ====== Driver Code ====== // 方法 1 List res = twoSumBruteForce(nums, target); print('方法1 res = $res'); // 方法 2 res = twoSumHashTable(nums, target); print('方法2 res = $res'); } ================================================ FILE: ja/codes/dart/chapter_sorting/bubble_sort.dart ================================================ /** * File: bubble_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* バブルソート */ void bubbleSort(List nums) { // 外側のループ:未ソート区間は [0, i] for (int i = nums.length - 1; i > 0; i--) { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* バブルソート(フラグ最適化) */ void bubbleSortWithFlag(List nums) { // 外側のループ:未ソート区間は [0, i] for (int i = nums.length - 1; i > 0; i--) { bool flag = false; // フラグを初期化する // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // 交換する要素を記録 } } if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了 } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); print("バブルソート完了後 nums = $nums"); List nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); print("バブルソート完了後 nums1 = $nums1"); } ================================================ FILE: ja/codes/dart/chapter_sorting/bucket_sort.dart ================================================ /** * File: bucket_sort.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* バケットソート */ void bucketSort(List nums) { // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする int k = nums.length ~/ 2; List> buckets = List.generate(k, (index) => []); // 1. 配列要素を各バケットに振り分ける for (double _num in nums) { // 入力データの範囲は [0, 1) であり、_num * k を用いてインデックス範囲 [0, k-1] に写像する int i = (_num * k).toInt(); // _num をバケット bucket_idx に追加 buckets[i].add(_num); } // 2. 各バケットをソートする for (List bucket in buckets) { bucket.sort(); } // 3. バケットを走査して結果を結合 int i = 0; for (List bucket in buckets) { for (double _num in bucket) { nums[i++] = _num; } } } /* Driver Code*/ void main() { // 入力データは範囲 [0, 1) の浮動小数点数とする final nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); print('バケットソート完了後 nums = $nums'); } ================================================ FILE: ja/codes/dart/chapter_sorting/counting_sort.dart ================================================ /** * File: counting_sort.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:math'; /* 計数ソート */ // 簡易実装のため、オブジェクトのソートには使えない void countingSortNaive(List nums) { // 1. 配列の最大要素 m を求める int m = 0; for (int _num in nums) { m = max(m, _num); } // 2. 各数値の出現回数を数える // counter[_num] は _num の出現回数を表す List counter = List.filled(m + 1, 0); for (int _num in nums) { counter[_num]++; } // 3. counter を走査し、各要素を元の配列 nums に書き戻す int i = 0; for (int _num = 0; _num < m + 1; _num++) { for (int j = 0; j < counter[_num]; j++, i++) { nums[i] = _num; } } } /* 計数ソート */ // 完全な実装で、オブジェクトをソートでき、かつ安定ソートである void countingSort(List nums) { // 1. 配列の最大要素 m を求める int m = 0; for (int _num in nums) { m = max(m, _num); } // 2. 各数値の出現回数を数える // counter[_num] は _num の出現回数を表す List counter = List.filled(m + 1, 0); for (int _num in nums) { counter[_num]++; } // 3. counter の累積和を求め、「出現回数」を「末尾インデックス」に変換する // つまり counter[_num]-1 は、res において _num が最後に出現する位置のインデックスである for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. nums を逆順に走査し、各要素を結果配列 res に格納する // 結果を記録するための配列 res を初期化 int n = nums.length; List res = List.filled(n, 0); for (int i = n - 1; i >= 0; i--) { int _num = nums[i]; res[counter[_num] - 1] = _num; // _num を対応する添字に配置 counter[_num]--; // 累積和を 1 減らし、次に _num を配置するインデックスを得る } // 結果配列 res で元の配列 nums を上書きする nums.setAll(0, res); } /* Driver Code*/ void main() { final nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); print('カウントソート(オブジェクトはソート不可)完了後 nums = $nums'); final nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); print('カウントソート完了後 nums1 = $nums1'); } ================================================ FILE: ja/codes/dart/chapter_sorting/heap_sort.dart ================================================ /** * File: heap_sort.dart * Created Time: 2023-06-01 * Author: liuyuxin (gvenusleo@gmail.com) */ /* ヒープの長さは n。ノード i から下方向にヒープ化 */ void siftDown(List nums, int n, int i) { while (true) { // ノード i, l, r のうち値が最大のノードを ma とする int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; if (l < n && nums[l] > nums[ma]) ma = l; if (r < n && nums[r] > nums[ma]) ma = r; // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma == i) break; // 2 つのノードを交換 int temp = nums[i]; nums[i] = nums[ma]; nums[ma] = temp; // ループで上から下へヒープ化 i = ma; } } /* ヒープソート */ void heapSort(List nums) { // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する for (int i = nums.length ~/ 2 - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // ヒープから最大要素を取り出し、n-1 回繰り返す for (int i = nums.length - 1; i > 0; i--) { // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) int tmp = nums[0]; nums[0] = nums[i]; nums[i] = tmp; // 根ノードを起点に、上から下へヒープ化 siftDown(nums, i, 0); } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; heapSort(nums); print("ヒープソート完了後 nums = $nums"); } ================================================ FILE: ja/codes/dart/chapter_sorting/insertion_sort.dart ================================================ /** * File: insertion_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* 挿入ソート */ void insertionSort(List nums) { // 外側ループ:整列済み区間は [0, i-1] for (int i = 1; i < nums.length; i++) { int base = nums[i], j = i - 1; // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する j--; } nums[j + 1] = base; // base を正しい位置に配置する } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); print("挿入ソート完了後 nums = $nums"); } ================================================ FILE: ja/codes/dart/chapter_sorting/merge_sort.dart ================================================ /** * File: merge_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* 左部分配列と右部分配列をマージ */ void merge(List nums, int left, int mid, int right) { // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] // マージ結果を格納する一時配列 tmp を作成 List tmp = List.filled(right - left + 1, 0); // 左右の部分配列の開始インデックスを初期化する int i = left, j = mid + 1, k = 0; // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } // 左右の部分配列の残り要素を一時配列にコピーする while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* マージソート */ void mergeSort(List nums, int left, int right) { // 終了条件 if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了 // 分割フェーズ int mid = left + (right - left) ~/ 2; // 中点を計算 mergeSort(nums, left, mid); // 左部分配列を再帰処理 mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 // マージフェーズ merge(nums, left, mid, right); } /* Driver Code */ void main() { /* マージソート */ List nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); print("マージソート完了後 nums = $nums"); } ================================================ FILE: ja/codes/dart/chapter_sorting/quick_sort.dart ================================================ /** * File: quick_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* クイックソートクラス */ class QuickSort { /* 要素の交換 */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 番兵分割 */ static int _partition(List nums, int left, int right) { // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す _swap(nums, i, j); // この 2 つの要素を交換 } _swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート */ static void quickSort(List nums, int left, int right) { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return; // 番兵分割 int pivot = _partition(nums, left, right); // 左右の部分配列を再帰処理 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } /* クイックソートクラス(中央値ピボット最適化) */ class QuickSortMedian { /* 要素の交換 */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 3つの候補要素の中央値を選ぶ */ static int _medianThree(List nums, int left, int mid, int right) { int l = nums[left], m = nums[mid], r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // m は l と r の間 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l は m と r の間 return right; } /* 番兵による分割処理(3 点中央値) */ static int _partition(List nums, int left, int right) { // 3つの候補要素の中央値を選ぶ int med = _medianThree(nums, left, (left + right) ~/ 2, right); // 中央値を配列の最左端に交換する _swap(nums, left, med); // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す _swap(nums, i, j); // この 2 つの要素を交換 } _swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート */ static void quickSort(List nums, int left, int right) { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return; // 番兵分割 int pivot = _partition(nums, left, right); // 左右の部分配列を再帰処理 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } /* クイックソートクラス(再帰深度最適化) */ class QuickSortTailCall { /* 要素の交換 */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 番兵分割 */ static int _partition(List nums, int left, int right) { // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す _swap(nums, i, j); // この 2 つの要素を交換 } _swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート(再帰深度最適化) */ static void quickSort(List nums, int left, int right) { // 部分配列の長さが 1 なら終了 while (left < right) { // 番兵による分割処理 int pivot = _partition(nums, left, right); // 2 つの部分配列のうち短いほうにクイックソートを適用する if (pivot - left < right - pivot) { quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] } else { quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] } } } } /* Driver Code */ void main() { /* クイックソート */ List nums = [2, 4, 1, 0, 3, 5]; QuickSort.quickSort(nums, 0, nums.length - 1); print("クイックソート完了後 nums = $nums"); /* クイックソート(中央値の基準値で最適化) */ List nums1 = [2, 4, 1, 0, 3, 5]; QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); print("クイックソート(中央値ピボット最適化)完了後 nums1 = $nums1"); /* クイックソート(再帰深度最適化) */ List nums2 = [2, 4, 1, 0, 3, 5]; QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); print("クイックソート(再帰深度最適化)完了後 nums2 = $nums2"); } ================================================ FILE: ja/codes/dart/chapter_sorting/radix_sort.dart ================================================ /** * File: radix_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* 要素 `_num` の第 k 桁を取得する。ここで `exp = 10^(k-1)` */ int digit(int _num, int exp) { // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す return (_num ~/ exp) % 10; } /* 計数ソート(nums の k 桁目でソート) */ void countingSortDigit(List nums, int exp) { // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 List counter = List.filled(10, 0); int n = nums.length; // 0~9 の各数字の出現回数を集計する for (int i = 0; i < n; i++) { int d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする counter[d]++; // 数字 d の出現回数を数える } // 累積和を求め、「出現回数」を「配列インデックス」に変換する for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する List res = List.filled(n, 0); for (int i = n - 1; i >= 0; i--) { int d = digit(nums[i], exp); int j = counter[d] - 1; // d の配列内インデックス j を取得する res[j] = nums[i]; // 現在の要素をインデックス j に格納する counter[d]--; // d の個数を 1 減らす } // 結果で元の配列 nums を上書きする for (int i = 0; i < n; i++) nums[i] = res[i]; } /* 基数ソート */ void radixSort(List nums) { // 最大桁数の判定用に配列の最大要素を取得する // dart の `int` の長さは 64 ビット int m = -1 << 63; for (int _num in nums) if (_num > m) m = _num; // 下位桁から上位桁の順に走査する for (int exp = 1; exp <= m; exp *= 10) // 配列要素の k 桁目に対して計数ソートを行う // k = 1 -> exp = 1 // k = 2 -> exp = 10 // つまり exp = 10^(k-1) countingSortDigit(nums, exp); } /* Driver Code */ void main() { // 基数ソート List nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ]; radixSort(nums); print("基数ソート完了後 nums = $nums"); } ================================================ FILE: ja/codes/dart/chapter_sorting/selection_sort.dart ================================================ /** * File: selection_sort.dart * Created Time: 2023-06-01 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 選択ソート */ void selectionSort(List nums) { int n = nums.length; // 外側ループ:未整列区間は [i, n-1] for (int i = 0; i < n - 1; i++) { // 内側のループ:未ソート区間の最小要素を見つける int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // 最小要素のインデックスを記録 } // その最小要素を未整列区間の先頭要素と交換する int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; selectionSort(nums); print("選択ソート完了後 nums = $nums"); } ================================================ FILE: ja/codes/dart/chapter_stack_and_queue/array_deque.dart ================================================ /** * File: array_deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 循環配列ベースの両端キュー */ class ArrayDeque { late List _nums; // 両端キューの要素を格納する配列 late int _front; // 先頭ポインタ。先頭要素を指す late int _queSize; // 両端キューの長さ /* コンストラクタ */ ArrayDeque(int capacity) { this._nums = List.filled(capacity, 0); this._front = this._queSize = 0; } /* 両端キューの容量を取得 */ int capacity() { return _nums.length; } /* 両端キューの長さを取得 */ int size() { return _queSize; } /* 両端キューが空かどうかを判定 */ bool isEmpty() { return _queSize == 0; } /* 循環配列のインデックスを計算 */ int index(int i) { // 剰余演算により配列の先頭と末尾をつなげる // i が配列の末尾を越えたら先頭に戻る // i が配列の先頭を越えて前に出たら末尾に戻る return (i + capacity()) % capacity(); } /* キュー先頭にエンキュー */ void pushFirst(int _num) { if (_queSize == capacity()) { throw Exception("両端キューがいっぱいです"); } // 先頭ポインタを左に 1 つ移動する // 剰余演算により _front が配列の先頭を越えたあと末尾に戻るようにする _front = index(_front - 1); // _num をキューの先頭に追加 _nums[_front] = _num; _queSize++; } /* キュー末尾にエンキュー */ void pushLast(int _num) { if (_queSize == capacity()) { throw Exception("両端キューがいっぱいです"); } // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す int rear = index(_front + _queSize); // _num をキュー末尾に追加 _nums[rear] = _num; _queSize++; } /* キュー先頭からデキュー */ int popFirst() { int _num = peekFirst(); // 先頭ポインタを右に 1 つ移動する _front = index(_front + 1); _queSize--; return _num; } /* キュー末尾からデキュー */ int popLast() { int _num = peekLast(); _queSize--; return _num; } /* キュー先頭の要素にアクセス */ int peekFirst() { if (isEmpty()) { throw Exception("両端キューが空です"); } return _nums[_front]; } /* キュー末尾の要素にアクセス */ int peekLast() { if (isEmpty()) { throw Exception("両端キューが空です"); } // 末尾要素のインデックスを計算 int last = index(_front + _queSize - 1); return _nums[last]; } /* 出力用の配列を返す */ List toArray() { // 有効長の範囲内のリスト要素のみを変換 List res = List.filled(_queSize, 0); for (int i = 0, j = _front; i < _queSize; i++, j++) { res[i] = _nums[index(j)]; } return res; } } /* Driver Code */ void main() { /* 両端キューを初期化 */ final ArrayDeque deque = ArrayDeque(10); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); print("両端キュー deque = ${deque.toArray()}"); /* 要素にアクセス */ final int peekFirst = deque.peekFirst(); print("先頭要素 peekFirst = $peekFirst"); final int peekLast = deque.peekLast(); print("末尾要素 peekLast = $peekLast"); /* 要素をエンキュー */ deque.pushLast(4); print("要素 4 を末尾に追加した後 deque = ${deque.toArray()}"); deque.pushFirst(1); print("要素 1 を先頭に追加した後 deque = ${deque.toArray()}"); /* 要素をデキュー */ final int popLast = deque.popLast(); print("末尾から取り出した要素 = $popLast ,末尾から取り出した後 deque = ${deque.toArray()}"); final int popFirst = deque.popFirst(); print("先頭から取り出した要素 = $popFirst ,先頭から取り出した後 deque = ${deque.toArray()}"); /* 両端キューの長さを取得 */ final int size = deque.size(); print("両端キューの長さ size = $size"); /* 両端キューが空かどうかを判定 */ final bool isEmpty = deque.isEmpty(); print("両端キューが空かどうか = $isEmpty"); } ================================================ FILE: ja/codes/dart/chapter_stack_and_queue/array_queue.dart ================================================ /** * File: array_queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 循環配列ベースのキュー */ class ArrayQueue { late List _nums; // キュー要素を格納する配列 late int _front; // 先頭ポインタ。先頭要素を指す late int _queSize; // キューの長さ ArrayQueue(int capacity) { _nums = List.filled(capacity, 0); _front = _queSize = 0; } /* キューの容量を取得 */ int capaCity() { return _nums.length; } /* キューの長さを取得 */ int size() { return _queSize; } /* キューが空かどうかを判定 */ bool isEmpty() { return _queSize == 0; } /* エンキュー */ void push(int _num) { if (_queSize == capaCity()) { throw Exception("キューは満杯です"); } // 末尾ポインタを計算し、末尾インデックス + 1 を指す // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする int rear = (_front + _queSize) % capaCity(); // _num をキュー末尾に追加 _nums[rear] = _num; _queSize++; } /* デキュー */ int pop() { int _num = peek(); // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す _front = (_front + 1) % capaCity(); _queSize--; return _num; } /* キュー先頭の要素にアクセス */ int peek() { if (isEmpty()) { throw Exception("キューが空です"); } return _nums[_front]; } /* Array を返す */ List toArray() { // 有効長の範囲内のリスト要素のみを変換 final List res = List.filled(_queSize, 0); for (int i = 0, j = _front; i < _queSize; i++, j++) { res[i] = _nums[j % capaCity()]; } return res; } } /* Driver Code */ void main() { /* キューを初期化 */ final int capacity = 10; final ArrayQueue queue = ArrayQueue(capacity); /* 要素をエンキュー */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print("キュー queue = ${queue.toArray()}"); /* キュー先頭の要素にアクセス */ final int peek = queue.peek(); print("先頭要素 peek = $peek"); /* 要素をデキュー */ final int pop = queue.pop(); print("デキューした要素 pop = $pop ,デキュー後の queue = ${queue.toArray()}"); /* キューの長さを取得 */ final int size = queue.size(); print("キューの長さ size = $size"); /* キューが空かどうかを判定 */ final bool isEmpty = queue.isEmpty(); print("キューが空かどうか = $isEmpty"); /* 循環配列をテストする */ for (int i = 0; i < 10; i++) { queue.push(i); queue.pop(); print("$i 回目のエンキュー + デキュー後の queue = ${queue.toArray()}"); } } ================================================ FILE: ja/codes/dart/chapter_stack_and_queue/array_stack.dart ================================================ /** * File: array_stack.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 配列ベースのスタック */ class ArrayStack { late List _stack; ArrayStack() { _stack = []; } /* スタックの長さを取得 */ int size() { return _stack.length; } /* スタックが空かどうかを判定 */ bool isEmpty() { return _stack.isEmpty; } /* プッシュ */ void push(int _num) { _stack.add(_num); } /* ポップ */ int pop() { if (isEmpty()) { throw Exception("スタックが空です"); } return _stack.removeLast(); } /* スタックトップの要素にアクセス */ int peek() { if (isEmpty()) { throw Exception("スタックが空です"); } return _stack.last; } /* スタックを Array に変換して返す */ List toArray() => _stack; } /* Driver Code */ void main() { /* スタックを初期化 */ final ArrayStack stack = ArrayStack(); /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print("スタック stack = ${stack.toArray()}"); /* スタックトップの要素にアクセス */ final int peek = stack.peek(); print("スタックトップの要素 peek = $peek"); /* 要素をポップ */ final int pop = stack.pop(); print("ポップした要素 pop = $pop ,ポップ後の stack = ${stack.toArray()}"); /* スタックの長さを取得 */ final int size = stack.size(); print("スタックの長さ size = $size"); /* 空かどうかを判定 */ final bool isEmpty = stack.isEmpty(); print("スタックが空かどうか = $isEmpty"); } ================================================ FILE: ja/codes/dart/chapter_stack_and_queue/deque.dart ================================================ /** * File: deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; void main() { /* 両端キューを初期化 */ final Queue deque = Queue(); deque.addFirst(3); deque.addLast(2); deque.addLast(5); print("両端キュー deque = $deque"); /* 要素にアクセス */ final int peekFirst = deque.first; print("先頭要素 peekFirst = $peekFirst"); final int peekLast = deque.last; print("末尾要素 peekLast = $peekLast"); /* 要素をエンキュー */ deque.addLast(4); print("要素 4 を末尾にエンキューした後の deque = $deque"); deque.addFirst(1); print("要素 1 を先頭にエンキューした後の deque = $deque"); /* 要素をデキュー */ final int popLast = deque.removeLast(); print("末尾からデキューした要素 = $popLast ,末尾からデキュー後の deque = $deque"); final int popFirst = deque.removeFirst(); print("先頭からデキューした要素 = $popFirst ,先頭からデキュー後の deque = $deque"); /* 両端キューの長さを取得 */ final int size = deque.length; print("両端キューの長さ size = $size"); /* 両端キューが空かどうかを判定 */ final bool isEmpty = deque.isEmpty; print("両端キューが空かどうか = $isEmpty"); } ================================================ FILE: ja/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart ================================================ /** * File: linkedlist_deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 双方向連結リストノード */ class ListNode { int val; // ノード値 ListNode? next; // 後続ノードへの参照 ListNode? prev; // 前駆ノードへの参照 ListNode(this.val, {this.next, this.prev}); } /* 双方向連結リストに基づく双方向キュー */ class LinkedListDeque { late ListNode? _front; // 先頭ノード _front late ListNode? _rear; // 末尾ノード _rear int _queSize = 0; // 両端キューの長さ LinkedListDeque() { this._front = null; this._rear = null; } /* 両端キューの長さを取得 */ int size() { return this._queSize; } /* 両端キューが空かどうかを判定 */ bool isEmpty() { return size() == 0; } /* エンキュー操作 */ void push(int _num, bool isFront) { final ListNode node = ListNode(_num); if (isEmpty()) { // 連結リストが空なら、`_front` と `_rear` の両方を `node` に向ける _front = _rear = node; } else if (isFront) { // 先頭へのエンキュー操作 // node を連結リストの先頭に追加する _front!.prev = node; node.next = _front; _front = node; // 先頭ノードを更新する } else { // 末尾へのエンキュー操作 // node を連結リストの末尾に追加する _rear!.next = node; node.prev = _rear; _rear = node; // 末尾ノードを更新する } _queSize++; // キューの長さを更新 } /* キュー先頭にエンキュー */ void pushFirst(int _num) { push(_num, true); } /* キュー末尾にエンキュー */ void pushLast(int _num) { push(_num, false); } /* デキュー操作 */ int? pop(bool isFront) { // キューが空なら、そのまま `null` を返す if (isEmpty()) { return null; } final int val; if (isFront) { // キュー先頭からの取り出し val = _front!.val; // 先頭ノードの値を一時保存 // 先頭ノードを削除 ListNode? fNext = _front!.next; if (fNext != null) { fNext.prev = null; _front!.next = null; } _front = fNext; // 先頭ノードを更新する } else { // キュー末尾からの取り出し val = _rear!.val; // 末尾ノードの値を一時保存 // 末尾ノードを削除 ListNode? rPrev = _rear!.prev; if (rPrev != null) { rPrev.next = null; _rear!.prev = null; } _rear = rPrev; // 末尾ノードを更新する } _queSize--; // キューの長さを更新 return val; } /* キュー先頭からデキュー */ int? popFirst() { return pop(true); } /* キュー末尾からデキュー */ int? popLast() { return pop(false); } /* キュー先頭の要素にアクセス */ int? peekFirst() { return _front?.val; } /* キュー末尾の要素にアクセス */ int? peekLast() { return _rear?.val; } /* 出力用の配列を返す */ List toArray() { ListNode? node = _front; final List res = []; for (int i = 0; i < _queSize; i++) { res.add(node!.val); node = node.next; } return res; } } /* Driver Code */ void main() { /* 両端キューを初期化 */ final LinkedListDeque deque = LinkedListDeque(); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); print("両端キュー deque = ${deque.toArray()}"); /* 要素にアクセス */ int? peekFirst = deque.peekFirst(); print("先頭要素 peekFirst = $peekFirst"); int? peekLast = deque.peekLast(); print("末尾要素 peekLast = $peekLast"); /* 要素をエンキュー */ deque.pushLast(4); print("要素 4 を末尾に追加した後 deque = ${deque.toArray()}"); deque.pushFirst(1); print("要素 1 を先頭に追加した後 deque = ${deque.toArray()}"); /* 要素をデキュー */ int? popLast = deque.popLast(); print("末尾から取り出した要素 = $popLast ,末尾から取り出した後 deque = ${deque.toArray()}"); int? popFirst = deque.popFirst(); print("先頭から取り出した要素 = $popFirst ,先頭から取り出した後 deque = ${deque.toArray()}"); /* 両端キューの長さを取得 */ int size = deque.size(); print("両端キューの長さ size = $size"); /* 両端キューが空かどうかを判定 */ bool isEmpty = deque.isEmpty(); print("両端キューが空かどうか = $isEmpty"); } ================================================ FILE: ja/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart ================================================ /** * File: linkedlist_queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/list_node.dart'; /* 連結リストベースのキュー */ class LinkedListQueue { ListNode? _front; // 先頭ノード _front ListNode? _rear; // 末尾ノード _rear int _queSize = 0; // キューの長さ LinkedListQueue() { _front = null; _rear = null; } /* キューの長さを取得 */ int size() { return _queSize; } /* キューが空かどうかを判定 */ bool isEmpty() { return _queSize == 0; } /* エンキュー */ void push(int _num) { // 末尾ノードの後ろに _num を追加 final node = ListNode(_num); // キューが空なら、先頭・末尾ノードをともにそのノードに設定 if (_front == null) { _front = node; _rear = node; } else { // キューが空でなければ、そのノードを末尾ノードの後ろに追加 _rear!.next = node; _rear = node; } _queSize++; } /* デキュー */ int pop() { final int _num = peek(); // 先頭ノードを削除 _front = _front!.next; _queSize--; return _num; } /* キュー先頭の要素にアクセス */ int peek() { if (_queSize == 0) { throw Exception('キューが空です'); } return _front!.val; } /* 連結リストを Array に変換して返す */ List toArray() { ListNode? node = _front; final List queue = []; while (node != null) { queue.add(node.val); node = node.next; } return queue; } } /* Driver Code */ void main() { /* キューを初期化 */ final queue = LinkedListQueue(); /* 要素をエンキュー */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print("キュー queue = ${queue.toArray()}"); /* キュー先頭の要素にアクセス */ final int peek = queue.peek(); print("先頭要素 peek = $peek"); /* 要素をデキュー */ final int pop = queue.pop(); print("デキューした要素 pop = $pop ,デキュー後の queue = ${queue.toArray()}"); /* キューの長さを取得 */ final int size = queue.size(); print("キューの長さ size = $size"); /* キューが空かどうかを判定 */ final bool isEmpty = queue.isEmpty(); print("キューが空かどうか = $isEmpty"); } ================================================ FILE: ja/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart ================================================ /** * File: linkedlist_stack.dart * Created Time: 2023-03-27 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/list_node.dart'; /* 連結リストクラスに基づくスタック */ class LinkedListStack { ListNode? _stackPeek; // 先頭ノードをスタックトップとする int _stkSize = 0; // スタックの長さ LinkedListStack() { _stackPeek = null; } /* スタックの長さを取得 */ int size() { return _stkSize; } /* スタックが空かどうかを判定 */ bool isEmpty() { return _stkSize == 0; } /* プッシュ */ void push(int _num) { final ListNode node = ListNode(_num); node.next = _stackPeek; _stackPeek = node; _stkSize++; } /* ポップ */ int pop() { final int _num = peek(); _stackPeek = _stackPeek!.next; _stkSize--; return _num; } /* スタックトップの要素にアクセス */ int peek() { if (_stackPeek == null) { throw Exception("スタックが空です"); } return _stackPeek!.val; } /* 連結リストを List に変換して返す */ List toList() { ListNode? node = _stackPeek; List list = []; while (node != null) { list.add(node.val); node = node.next; } list = list.reversed.toList(); return list; } } /* Driver Code */ void main() { /* スタックを初期化 */ final LinkedListStack stack = LinkedListStack(); /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print("スタック stack = ${stack.toList()}"); /* スタックトップの要素にアクセス */ final int peek = stack.peek(); print("スタックトップの要素 peek = $peek"); /* 要素をポップ */ final int pop = stack.pop(); print("ポップした要素 pop = $pop ,ポップ後の stack = ${stack.toList()}"); /* スタックの長さを取得 */ final int size = stack.size(); print("スタックの長さ size = $size"); /* 空かどうかを判定 */ final bool isEmpty = stack.isEmpty(); print("スタックが空かどうか = $isEmpty"); } ================================================ FILE: ja/codes/dart/chapter_stack_and_queue/queue.dart ================================================ /** * File: queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; void main() { /* キューを初期化 */ // Dart では、通常は両端キュー Queue をキューとして使う final Queue queue = Queue(); /* 要素をエンキュー */ queue.add(1); queue.add(3); queue.add(2); queue.add(5); queue.add(4); print("キュー queue = $queue"); /* キュー先頭の要素にアクセス */ final int peek = queue.first; print("先頭要素 peek = $peek"); /* 要素をデキュー */ final int pop = queue.removeFirst(); print("デキューした要素 pop = $pop ,デキュー後の queue = $queue"); /* キューの長さを取得 */ final int size = queue.length; print("キューの長さ size = $size"); /* キューが空かどうかを判定 */ final bool isEmpty = queue.isEmpty; print("キューが空かどうか = $isEmpty"); } ================================================ FILE: ja/codes/dart/chapter_stack_and_queue/stack.dart ================================================ /** * File: stack.dart * Created Time: 2023-03-27 * Author: liuyuxin (gvenusleo@gmail.com) */ void main() { /* スタックを初期化 */ // Dart には組み込みのスタッククラスがないため、List をスタックとして使える final List stack = []; /* 要素をプッシュ */ stack.add(1); stack.add(3); stack.add(2); stack.add(5); stack.add(4); print("スタック stack = $stack"); /* スタックトップの要素にアクセス */ final int peek = stack.last; print("スタックトップの要素 peek = $peek"); /* 要素をポップ */ final int pop = stack.removeLast(); print("ポップした要素 pop = $pop ,ポップ後の stack = $stack"); /* スタックの長さを取得 */ final int size = stack.length; print("スタックの長さ size = $size"); /* 空かどうかを判定 */ final bool isEmpty = stack.isEmpty; print("スタックが空かどうか = $isEmpty"); } ================================================ FILE: ja/codes/dart/chapter_tree/array_binary_tree.dart ================================================ /** * File: array_binary_tree.dart * Created Time: 2023-08-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 配列表現による二分木クラス */ class ArrayBinaryTree { late List _tree; /* コンストラクタ */ ArrayBinaryTree(this._tree); /* リスト容量 */ int size() { return _tree.length; } /* インデックス i のノードの値を取得 */ int? val(int i) { // インデックスが範囲外なら、空きを表す null を返す if (i < 0 || i >= size()) { return null; } return _tree[i]; } /* インデックス i のノードの左子ノードのインデックスを取得 */ int? left(int i) { return 2 * i + 1; } /* インデックス i のノードの右子ノードのインデックスを取得 */ int? right(int i) { return 2 * i + 2; } /* インデックス i のノードの親ノードのインデックスを取得 */ int? parent(int i) { return (i - 1) ~/ 2; } /* レベル順走査 */ List levelOrder() { List res = []; for (int i = 0; i < size(); i++) { if (val(i) != null) { res.add(val(i)!); } } return res; } /* 深さ優先探索 */ void dfs(int i, String order, List res) { // 空きスロットなら返す if (val(i) == null) { return; } // 先行順走査 if (order == 'pre') { res.add(val(i)); } dfs(left(i)!, order, res); // 中順走査 if (order == 'in') { res.add(val(i)); } dfs(right(i)!, order, res); // 後順走査 if (order == 'post') { res.add(val(i)); } } /* 先行順走査 */ List preOrder() { List res = []; dfs(0, 'pre', res); return res; } /* 中順走査 */ List inOrder() { List res = []; dfs(0, 'in', res); return res; } /* 後順走査 */ List postOrder() { List res = []; dfs(0, 'post', res); return res; } } /* Driver Code */ void main() { // 二分木を初期化 // ここでは、配列から直接二分木を生成する関数を利用する List arr = [ 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ]; TreeNode? root = listToTree(arr); print("\n二分木を初期化\n"); print("二分木の配列表現:"); print(arr); print("二分木の連結リスト表現:"); printTree(root); // 配列表現による二分木クラス ArrayBinaryTree abt = ArrayBinaryTree(arr); // ノードにアクセス int i = 1; int? l = abt.left(i); int? r = abt.right(i); int? p = abt.parent(i); print("\n現在のノードのインデックスは $i ,値は ${abt.val(i)}"); print("その左子ノードのインデックスは $l ,値は ${(l == null ? "null" : abt.val(l))}"); print("その右子ノードのインデックスは $r ,値は ${(r == null ? "null" : abt.val(r))}"); print("その親ノードのインデックスは $p ,値は ${(p == null ? "null" : abt.val(p))}"); // 木を走査 List res = abt.levelOrder(); print("\nレベル順走査:$res"); res = abt.preOrder(); print("先行順走査は $res"); res = abt.inOrder(); print("中間順走査は $res"); res = abt.postOrder(); print("後行順走査は $res"); } ================================================ FILE: ja/codes/dart/chapter_tree/avl_tree.dart ================================================ /** * File: avl_tree.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; class AVLTree { TreeNode? root; /* コンストラクタ */ AVLTree() { root = null; } /* ノードの高さを取得 */ int height(TreeNode? node) { // 空ノードの高さは -1、葉ノードの高さは 0 return node == null ? -1 : node.height; } /* ノードの高さを更新する */ void updateHeight(TreeNode? node) { // ノードの高さは最も高い部分木の高さ + 1 に等しい node!.height = max(height(node.left), height(node.right)) + 1; } /* 平衡係数を取得 */ int balanceFactor(TreeNode? node) { // 空ノードの平衡係数は 0 if (node == null) return 0; // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ return height(node.left) - height(node.right); } /* 右回転 */ TreeNode? rightRotate(TreeNode? node) { TreeNode? child = node!.left; TreeNode? grandChild = child!.right; // child を支点として node を右回転させる child.right = node; node.left = grandChild; // ノードの高さを更新する updateHeight(node); updateHeight(child); // 回転後の部分木の根ノードを返す return child; } /* 左回転 */ TreeNode? leftRotate(TreeNode? node) { TreeNode? child = node!.right; TreeNode? grandChild = child!.left; // child を支点として node を左回転させる child.left = node; node.right = grandChild; // ノードの高さを更新する updateHeight(node); updateHeight(child); // 回転後の部分木の根ノードを返す return child; } /* 回転操作を行い、この部分木の平衡を回復する */ TreeNode? rotate(TreeNode? node) { // ノード node の平衡係数を取得 int factor = balanceFactor(node); // 左に偏った木 if (factor > 1) { if (balanceFactor(node!.left) >= 0) { // 右回転 return rightRotate(node); } else { // 左回転してから右回転 node.left = leftRotate(node.left); return rightRotate(node); } } // 右に偏った木 if (factor < -1) { if (balanceFactor(node!.right) <= 0) { // 左回転 return leftRotate(node); } else { // 右回転してから左回転 node.right = rightRotate(node.right); return leftRotate(node); } } // 平衡木なので回転不要、そのまま返す return node; } /* ノードを挿入 */ void insert(int val) { root = insertHelper(root, val); } /* ノードを再帰的に挿入する(補助メソッド) */ TreeNode? insertHelper(TreeNode? node, int val) { if (node == null) return TreeNode(val); /* 1. 挿入位置を探索してノードを挿入 */ if (val < node.val) node.left = insertHelper(node.left, val); else if (val > node.val) node.right = insertHelper(node.right, val); else return node; // 重複ノードは挿入せず、そのまま返す updateHeight(node); // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node); // 部分木の根ノードを返す return node; } /* ノードを削除 */ void remove(int val) { root = removeHelper(root, val); } /* ノードを再帰的に削除する(補助メソッド) */ TreeNode? removeHelper(TreeNode? node, int val) { if (node == null) return null; /* 1. ノードを探索して削除 */ if (val < node.val) node.left = removeHelper(node.left, val); else if (val > node.val) node.right = removeHelper(node.right, val); else { if (node.left == null || node.right == null) { TreeNode? child = node.left ?? node.right; // 子ノード数 = 0 の場合、node をそのまま削除して返す if (child == null) return null; // 子ノード数 = 1 の場合、node をそのまま削除する else node = child; } else { // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える TreeNode? temp = node.right; while (temp!.left != null) { temp = temp.left; } node.right = removeHelper(node.right, temp.val); node.val = temp.val; } } updateHeight(node); // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node); // 部分木の根ノードを返す return node; } /* ノードを探索 */ TreeNode? search(int val) { TreeNode? cur = root; // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 目標ノードは cur の右部分木にある if (val < cur.val) cur = cur.left; // 目標ノードは cur の左部分木にある else if (val > cur.val) cur = cur.right; // 対象ノードが現在のノードと等しい else break; } return cur; } } void testInsert(AVLTree tree, int val) { tree.insert(val); print("\nノード $val を挿入後、AVL 木は"); printTree(tree.root); } void testRemove(AVLTree tree, int val) { tree.remove(val); print("\nノード $val を削除後、AVL 木は"); printTree(tree.root); } /* Driver Code */ void main() { /* 空の AVL 木を初期化する */ AVLTree avlTree = AVLTree(); /* ノードを挿入 */ // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); testInsert(avlTree, 4); testInsert(avlTree, 5); testInsert(avlTree, 8); testInsert(avlTree, 7); testInsert(avlTree, 9); testInsert(avlTree, 10); testInsert(avlTree, 6); /* 重複ノードを挿入する */ testInsert(avlTree, 7); /* ノードを削除 */ // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい testRemove(avlTree, 8); // 次数 0 のノードを削除する testRemove(avlTree, 5); // 次数 1 のノードを削除する testRemove(avlTree, 4); // 次数 2 のノードを削除する /* ノードを検索 */ TreeNode? node = avlTree.search(7); print("\n見つかったノードオブジェクトは $node ,ノードの値 = ${node!.val}"); } ================================================ FILE: ja/codes/dart/chapter_tree/binary_search_tree.dart ================================================ /** * File: binary_search_tree.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 二分探索木 */ class BinarySearchTree { late TreeNode? _root; /* コンストラクタ */ BinarySearchTree() { // 空の木を初期化する _root = null; } /* 二分木の根ノードを取得する */ TreeNode? getRoot() { return _root; } /* ノードを探索 */ TreeNode? search(int _num) { TreeNode? cur = _root; // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 目標ノードは cur の右部分木にある if (cur.val < _num) cur = cur.right; // 目標ノードは cur の左部分木にある else if (cur.val > _num) cur = cur.left; // 目標ノードが見つかったらループを抜ける else break; } // 目標ノードを返す return cur; } /* ノードを挿入 */ void insert(int _num) { // 木が空なら、根ノードを初期化する if (_root == null) { _root = TreeNode(_num); return; } TreeNode? cur = _root; TreeNode? pre = null; // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 重複ノードが見つかったら、直ちに返す if (cur.val == _num) return; pre = cur; // 挿入位置は cur の右部分木にある if (cur.val < _num) cur = cur.right; // 挿入位置は cur の左部分木にある else cur = cur.left; } // ノードを挿入 TreeNode? node = TreeNode(_num); if (pre!.val < _num) pre.right = node; else pre.left = node; } /* ノードを削除 */ void remove(int _num) { // 木が空なら、そのまま早期リターンする if (_root == null) return; TreeNode? cur = _root; TreeNode? pre = null; // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 削除対象のノードが見つかったら、ループを抜ける if (cur.val == _num) break; pre = cur; // 削除対象ノードは cur の右部分木にある if (cur.val < _num) cur = cur.right; // 削除対象ノードは cur の左部分木にある else cur = cur.left; } // 削除対象ノードがない場合は、そのまま返す if (cur == null) return; // 子ノード数 = 0 or 1 if (cur.left == null || cur.right == null) { // 子ノード数が 0 / 1 のとき、child = null / その子ノード TreeNode? child = cur.left ?? cur.right; // ノード cur を削除する if (cur != _root) { if (pre!.left == cur) pre.left = child; else pre.right = child; } else { // 削除ノードが根ノードなら、根ノードを再設定 _root = child; } } else { // 子ノード数 = 2 // 中順走査における cur の次のノードを取得 TreeNode? tmp = cur.right; while (tmp!.left != null) { tmp = tmp.left; } // ノード tmp を再帰的に削除 remove(tmp.val); // tmp で cur を上書きする cur.val = tmp.val; } } } /* Driver Code */ void main() { /* 二分探索木を初期化 */ BinarySearchTree bst = BinarySearchTree(); // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる List nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (int _num in nums) { bst.insert(_num); } print("\n初期化した二分木は\n"); printTree(bst.getRoot()); /* ノードを探索 */ TreeNode? node = bst.search(7); print("\n見つかったノードオブジェクトは $node ,ノードの値 = ${node?.val}"); /* ノードを挿入 */ bst.insert(16); print("\nノード 16 を挿入後、二分木は\n"); printTree(bst.getRoot()); /* ノードを削除 */ bst.remove(1); print("\nノード 1 を削除後、二分木は\n"); printTree(bst.getRoot()); bst.remove(2); print("\nノード 2 を削除後、二分木は\n"); printTree(bst.getRoot()); bst.remove(4); print("\nノード 4 を削除後、二分木は\n"); printTree(bst.getRoot()); } ================================================ FILE: ja/codes/dart/chapter_tree/binary_tree.dart ================================================ /** * File: binary_tree.dart * Created Time: 2023-04-03 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; void main() { /* 二分木を初期化 */ // ノードをヒープ化 TreeNode n1 = TreeNode(1); TreeNode n2 = TreeNode(2); TreeNode n3 = TreeNode(3); TreeNode n4 = TreeNode(4); TreeNode n5 = TreeNode(5); // ノード間の参照(ポインタ)を構築する n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; print("\n二分木を初期化\n"); printTree(n1); /* ノードの挿入と削除 */ TreeNode p = TreeNode(0); // n1 -> n2 の間にノード p を挿入する n1.left = p; p.left = n2; print("\nノード P を挿入後\n"); printTree(n1); // ノード P を削除 n1.left = n2; print("\nノード P を削除後\n"); printTree(n1); } ================================================ FILE: ja/codes/dart/chapter_tree/binary_tree_bfs.dart ================================================ /** * File: binary_tree_bfs.dart * Created Time: 2023-04-03 * Author: liuyuxin (gvenusleo@gmai.com) */ import 'dart:collection'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* レベル順走査 */ List levelOrder(TreeNode? root) { // キューを初期化し、ルートノードを追加する Queue queue = Queue(); queue.add(root); // 走査順序を保存するためのリストを初期化する List res = []; while (queue.isNotEmpty) { TreeNode? node = queue.removeFirst(); // デキュー res.add(node!.val); // ノードの値を保存する if (node.left != null) queue.add(node.left); // 左子ノードをキューに追加 if (node.right != null) queue.add(node.right); // 右子ノードをキューに追加 } return res; } /* Driver Code */ void main() { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); print("\n二分木を初期化\n"); printTree(root); // レベル順走査 List res = levelOrder(root); print("\nレベル順走査のノード出力順 = $res"); } ================================================ FILE: ja/codes/dart/chapter_tree/binary_tree_dfs.dart ================================================ /** * File: binary_tree_dfs.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; // 走査順序を格納するリストを初期化 List list = []; /* 先行順走査 */ void preOrder(TreeNode? node) { if (node == null) return; // 訪問順序:根ノード -> 左部分木 -> 右部分木 list.add(node.val); preOrder(node.left); preOrder(node.right); } /* 中順走査 */ void inOrder(TreeNode? node) { if (node == null) return; // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 inOrder(node.left); list.add(node.val); inOrder(node.right); } /* 後順走査 */ void postOrder(TreeNode? node) { if (node == null) return; // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード postOrder(node.left); postOrder(node.right); list.add(node.val); } /* Driver Code */ void main() { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); print("\n二分木を初期化\n"); printTree(root); /* 先行順走査 */ list.clear(); preOrder(root); print("\n先行順走査のノード出力順 = $list"); /* 中順走査 */ list.clear(); inOrder(root); print("\n中間順走査のノード出力順 = $list"); /* 後順走査 */ list.clear(); postOrder(root); print("\n後行順走査のノード出力順 = $list"); } ================================================ FILE: ja/codes/dart/utils/list_node.dart ================================================ /** * File: list_node.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 連結リストノード */ class ListNode { int val; ListNode? next; ListNode(this.val, [this.next]); } /* リストを連結リストにデシリアライズする */ ListNode? listToLinkedList(List list) { ListNode dum = ListNode(0); ListNode? head = dum; for (int val in list) { head?.next = ListNode(val); head = head?.next; } return dum.next; } ================================================ FILE: ja/codes/dart/utils/print_util.dart ================================================ /** * File: print_util.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:io'; import 'list_node.dart'; import 'tree_node.dart'; class Trunk { Trunk? prev; String str; Trunk(this.prev, this.str); } /* 行列を出力する (Array) */ void printMatrix(List> matrix) { print("["); for (List row in matrix) { print(" $row,"); } print("]"); } /* 連結リストを出力 */ void printLinkedList(ListNode? head) { List list = []; while (head != null) { list.add('${head.val}'); head = head.next; } print(list.join(' -> ')); } /** * 二分木を出力 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTree(TreeNode? root, [Trunk? prev = null, bool isRight = false]) { if (root == null) { return; } String prev_str = ' '; Trunk trunk = Trunk(prev, prev_str); printTree(root.right, trunk, true); if (prev == null) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); print(' ${root.val}'); if (prev != null) { prev.str = prev_str; } trunk.str = ' |'; printTree(root.left, trunk, false); } void showTrunks(Trunk? p) { if (p == null) { return; } showTrunks(p.prev); stdout.write(p.str); } /* ヒープを出力 */ void printHeap(List heap) { print("ヒープの配列表現:$heap"); print("ヒープの木構造表示:"); TreeNode? root = listToTree(heap); printTree(root); } ================================================ FILE: ja/codes/dart/utils/tree_node.dart ================================================ /** * File: tree_node.dart * Created Time: 2023-2-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 二分木ノードクラス */ class TreeNode { int val; // ノード値 int height; // ノードの高さ TreeNode? left; // 左子ノードへの参照 TreeNode? right; // 右子ノードへの参照 /* コンストラクタ */ TreeNode(this.val, [this.height = 0, this.left, this.right]); } /* リストを二分木にデシリアライズする: 再帰 */ TreeNode? listToTreeDFS(List arr, int i) { if (i < 0 || i >= arr.length || arr[i] == null) { return null; } TreeNode? root = TreeNode(arr[i]!); root.left = listToTreeDFS(arr, 2 * i + 1); root.right = listToTreeDFS(arr, 2 * i + 2); return root; } /* リストを二分木にデシリアライズする */ TreeNode? listToTree(List arr) { return listToTreeDFS(arr, 0); } /* 二分木をリストにシリアライズする: 再帰 */ void treeToListDFS(TreeNode? root, int i, List res) { if (root == null) return; while (i >= res.length) { res.add(null); } res[i] = root.val; treeToListDFS(root.left, 2 * i + 1, res); treeToListDFS(root.right, 2 * i + 2, res); } /* 二分木をリストにシリアライズする */ List treeToList(TreeNode? root) { List res = []; treeToListDFS(root, 0, res); return res; } ================================================ FILE: ja/codes/dart/utils/vertex.dart ================================================ /** * File: Vertex.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 頂点クラス */ class Vertex { int val; Vertex(this.val); /* 値リスト vals を入力し、頂点リスト vets を返す */ static List valsToVets(List vals) { List vets = []; for (int i in vals) { vets.add(Vertex(i)); } return vets; } /* 頂点リスト vets を入力し、値リスト vals を返す */ static List vetsToVals(List vets) { List vals = []; for (Vertex vet in vets) { vals.add(vet.val); } return vals; } } ================================================ FILE: ja/codes/go/chapter_array_and_linkedlist/array.go ================================================ // File: array.go // Created Time: 2022-12-29 // Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( "math/rand" ) /* 要素へランダムアクセス */ func randomAccess(nums []int) (randomNum int) { // 区間 [0, nums.length) からランダムに 1 つの数を選ぶ randomIndex := rand.Intn(len(nums)) // ランダムな要素を取得して返す randomNum = nums[randomIndex] return } /* 配列長を拡張する */ func extend(nums []int, enlarge int) []int { // 拡張後の長さを持つ配列を初期化する res := make([]int, len(nums)+enlarge) // 元の配列の全要素を新しい配列にコピー for i, num := range nums { res[i] = num } // 拡張後の新しい配列を返す return res } /* 配列の index 番目に要素 num を挿入 */ func insert(nums []int, num int, index int) { // インデックス index 以降の全要素を 1 つ後ろへ移動する for i := len(nums) - 1; i > index; i-- { nums[i] = nums[i-1] } // index の要素に num を代入する nums[index] = num } /* index の要素を削除する */ func remove(nums []int, index int) { // インデックス index より後ろの全要素を 1 つ前へ移動する for i := index; i < len(nums)-1; i++ { nums[i] = nums[i+1] } } /* 配列を走査 */ func traverse(nums []int) { count := 0 // インデックスで配列を走査 for i := 0; i < len(nums); i++ { count += nums[i] } count = 0 // 配列要素を直接走査 for _, num := range nums { count += num } // データのインデックスと要素を同時に走査する for i, num := range nums { count += nums[i] count += num } } /* 配列内で指定要素を探す */ func find(nums []int, target int) (index int) { index = -1 for i := 0; i < len(nums); i++ { if nums[i] == target { index = i break } } return } ================================================ FILE: ja/codes/go/chapter_array_and_linkedlist/array_test.go ================================================ // File: array_test.go // Created Time: 2022-12-29 // Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist /** * ここでは Go の Slice を Array 配列とみなします。これは * 理解コストを下げ、データ構造とアルゴリズムに集中しやすくするためです。 */ import ( "fmt" "testing" ) /* Driver Code */ func TestArray(t *testing.T) { /* 配列を初期化 */ var arr [5]int fmt.Println("配列 arr =", arr) // Go では、長さを指定する場合([5]int)は配列、指定しない場合([]int)はスライスである // Go の配列はコンパイル時に長さが確定するよう設計されているため、長さには定数しか使えない // extend() 関数を実装しやすくするため、以下ではスライス(Slice)を配列(Array)として扱う nums := []int{1, 3, 2, 5, 4} fmt.Println("配列 nums =", nums) /* ランダムアクセス */ randomNum := randomAccess(nums) fmt.Println("nums からランダムな要素を取得", randomNum) /* 長さを拡張 */ nums = extend(nums, 3) fmt.Println("配列の長さを 8 に拡張し,nums =", nums) /* 要素を挿入する */ insert(nums, 6, 3) fmt.Println("インデックス 3 に数値 6 を挿入し,nums =", nums) /* 要素を削除 */ remove(nums, 2) fmt.Println("インデックス 2 の要素を削除すると、nums =", nums) /* 配列を走査 */ traverse(nums) /* 要素を探索する */ index := find(nums, 3) fmt.Println("nums 内で要素 3 を検索すると、インデックス =", index) } ================================================ FILE: ja/codes/go/chapter_array_and_linkedlist/linked_list.go ================================================ // File: linked_list.go // Created Time: 2022-12-29 // Author: cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( . "github.com/krahets/hello-algo/pkg" ) /* 連結リストでノード n0 の後ろにノード P を挿入する */ func insertNode(n0 *ListNode, P *ListNode) { n1 := n0.Next P.Next = n1 n0.Next = P } /* 連結リストでノード n0 の直後のノードを削除する */ func removeItem(n0 *ListNode) { if n0.Next == nil { return } // n0 -> P -> n1 P := n0.Next n1 := P.Next n0.Next = n1 } /* 連結リスト内で index 番目のノードにアクセス */ func access(head *ListNode, index int) *ListNode { for i := 0; i < index; i++ { if head == nil { return nil } head = head.Next } return head } /* 連結リストで値が target の最初のノードを探す */ func findNode(head *ListNode, target int) int { index := 0 for head != nil { if head.Val == target { return index } head = head.Next index++ } return -1 } ================================================ FILE: ja/codes/go/chapter_array_and_linkedlist/linked_list_test.go ================================================ // File: linked_list_test.go // Created Time: 2022-12-29 // Author: cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLinkedList(t *testing.T) { /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化する */ // 各ノードを初期化 n0 := NewListNode(1) n1 := NewListNode(3) n2 := NewListNode(2) n3 := NewListNode(5) n4 := NewListNode(4) // ノード間の参照を構築する n0.Next = n1 n1.Next = n2 n2.Next = n3 n3.Next = n4 fmt.Println("初期化した連結リストは") PrintLinkedList(n0) /* ノードを挿入 */ insertNode(n0, NewListNode(0)) fmt.Println("ノード挿入後の連結リストは") PrintLinkedList(n0) /* ノードを削除 */ removeItem(n0) fmt.Println("ノード削除後の連結リストは") PrintLinkedList(n0) /* ノードにアクセス */ node := access(n0, 3) fmt.Println("連結リスト内のインデックス 3 のノードの値 =", node) /* ノードを探索 */ index := findNode(n0, 2) fmt.Println("連結リスト内で値が 2 のノードのインデックス =", index) } ================================================ FILE: ja/codes/go/chapter_array_and_linkedlist/list_test.go ================================================ // File: list_test.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "sort" "testing" ) /* Driver Code */ func TestList(t *testing.T) { /* リストを初期化 */ nums := []int{1, 3, 2, 5, 4} fmt.Println("リスト nums =", nums) /* 要素にアクセス */ num := nums[1] // インデックス 1 の要素にアクセス fmt.Println("インデックス 1 の要素にアクセスすると、num =", num) /* 要素を更新 */ nums[1] = 0 // 添字 1 の要素を 0 に更新 fmt.Println("インデックス 1 の要素を 0 に更新すると、nums =", nums) /* リストを空にする */ nums = nil fmt.Println("リストを空にした後の nums =", nums) /* 末尾に要素を追加 */ nums = append(nums, 1) nums = append(nums, 3) nums = append(nums, 2) nums = append(nums, 5) nums = append(nums, 4) fmt.Println("要素追加後の nums =", nums) /* 中間に要素を挿入 */ nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // 添字 3 に数字 6 を挿入 fmt.Println("インデックス 3 に数値 6 を挿入し,nums =", nums) /* 要素を削除 */ nums = append(nums[:3], nums[4:]...) // インデックス 3 の要素を削除する fmt.Println("インデックス 3 の要素を削除すると、nums =", nums) /* インデックスでリストを走査 */ count := 0 for i := 0; i < len(nums); i++ { count += nums[i] } /* リスト要素を直接走査 */ count = 0 for _, x := range nums { count += x } /* 2 つのリストを連結する */ nums1 := []int{6, 8, 7, 10, 9} nums = append(nums, nums1...) // リスト nums1 を nums の後ろに連結 fmt.Println("リスト nums1 を nums の後ろに連結すると、nums =", nums) /* リストをソート */ sort.Ints(nums) // ソート後、リスト要素は小さい順に並ぶ fmt.Println("リストをソートすると、nums =", nums) } ================================================ FILE: ja/codes/go/chapter_array_and_linkedlist/my_list.go ================================================ // File: my_list.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist /* リストクラス */ type myList struct { arrCapacity int arr []int arrSize int extendRatio int } /* コンストラクタ */ func newMyList() *myList { return &myList{ arrCapacity: 10, // リスト容量 arr: make([]int, 10), // 配列(リスト要素を格納) arrSize: 0, // リストの長さ(現在の要素数) extendRatio: 2, // リスト拡張時の増加倍率 } } /* リストの長さを取得(現在の要素数) */ func (l *myList) size() int { return l.arrSize } /* リスト容量を取得する */ func (l *myList) capacity() int { return l.arrCapacity } /* 要素にアクセス */ func (l *myList) get(index int) int { // インデックスが範囲外なら例外を送出する。以下同様 if index < 0 || index >= l.arrSize { panic("インデックスが範囲外です") } return l.arr[index] } /* 要素を更新 */ func (l *myList) set(num, index int) { if index < 0 || index >= l.arrSize { panic("インデックスが範囲外です") } l.arr[index] = num } /* 末尾に要素を追加 */ func (l *myList) add(num int) { // 要素数が容量を超えると、拡張機構が発動する if l.arrSize == l.arrCapacity { l.extendCapacity() } l.arr[l.arrSize] = num // 要素数を更新 l.arrSize++ } /* 中間に要素を挿入 */ func (l *myList) insert(num, index int) { if index < 0 || index >= l.arrSize { panic("インデックスが範囲外です") } // 要素数が容量を超えると、拡張機構が発動する if l.arrSize == l.arrCapacity { l.extendCapacity() } // index 以降の要素をすべて 1 つ後ろへずらす for j := l.arrSize - 1; j >= index; j-- { l.arr[j+1] = l.arr[j] } l.arr[index] = num // 要素数を更新 l.arrSize++ } /* 要素を削除 */ func (l *myList) remove(index int) int { if index < 0 || index >= l.arrSize { panic("インデックスが範囲外です") } num := l.arr[index] // インデックス index より後の要素をすべて 1 つ前に移動する for j := index; j < l.arrSize-1; j++ { l.arr[j] = l.arr[j+1] } // 要素数を更新 l.arrSize-- // 削除された要素を返す return num } /* リストの拡張 */ func (l *myList) extendCapacity() { // 元の配列の extendRatio 倍の長さを持つ新しい配列を作成し、元の配列をコピーする l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...) // リストの容量を更新 l.arrCapacity = len(l.arr) } /* 有効長のリストを返す */ func (l *myList) toArray() []int { // 有効長の範囲内のリスト要素のみを変換 return l.arr[:l.arrSize] } ================================================ FILE: ja/codes/go/chapter_array_and_linkedlist/my_list_test.go ================================================ // File: my_list_test.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "testing" ) /* Driver Code */ func TestMyList(t *testing.T) { /* リストを初期化 */ nums := newMyList() /* 末尾に要素を追加 */ nums.add(1) nums.add(3) nums.add(2) nums.add(5) nums.add(4) fmt.Printf("リスト nums = %v 、容量 = %v 、長さ = %v\n", nums.toArray(), nums.capacity(), nums.size()) /* 中間に要素を挿入 */ nums.insert(6, 3) fmt.Printf("インデックス 3 に数字 6 を挿入すると、nums = %v\n", nums.toArray()) /* 要素を削除 */ nums.remove(3) fmt.Printf("インデックス 3 の要素を削除すると、nums = %v\n", nums.toArray()) /* 要素にアクセス */ num := nums.get(1) fmt.Printf("インデックス 1 の要素にアクセスすると、num = %v\n", num) /* 要素を更新 */ nums.set(0, 1) fmt.Printf("インデックス 1 の要素を 0 に更新すると、nums = %v\n", nums.toArray()) /* 拡張機構をテストする */ for i := 0; i < 10; i++ { // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する nums.add(i) } fmt.Printf("拡張後のリスト nums = %v 、容量 = %v 、長さ = %v\n", nums.toArray(), nums.capacity(), nums.size()) } ================================================ FILE: ja/codes/go/chapter_backtracking/n_queens.go ================================================ // File: n_queens.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* バックトラッキング:N クイーン */ func backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) { // すべての行への配置が完了したら、解を記録する if row == n { newState := make([][]string, len(*state)) for i, _ := range newState { newState[i] = make([]string, len((*state)[0])) copy(newState[i], (*state)[i]) } *res = append(*res, newState) return } // すべての列を走査 for col := 0; col < n; col++ { // このマスに対応する主対角線と副対角線を計算 diag1 := row - col + n - 1 diag2 := row + col // 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] { // 試行:そのマスにクイーンを置く (*state)[row][col] = "Q" (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true // 次の行に配置する backtrack(row+1, n, state, res, cols, diags1, diags2) // 戻す:そのマスを空きマスに戻す (*state)[row][col] = "#" (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false } } } /* N クイーンを解く */ func nQueens(n int) [][][]string { // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す state := make([][]string, n) for i := 0; i < n; i++ { row := make([]string, n) for i := 0; i < n; i++ { row[i] = "#" } state[i] = row } // 列にクイーンがあるか記録 cols := make([]bool, n) diags1 := make([]bool, 2*n-1) diags2 := make([]bool, 2*n-1) res := make([][][]string, 0) backtrack(0, n, &state, &res, &cols, &diags1, &diags2) return res } ================================================ FILE: ja/codes/go/chapter_backtracking/n_queens_test.go ================================================ // File: n_queens_test.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" ) func TestNQueens(t *testing.T) { n := 4 res := nQueens(n) fmt.Println("入力された盤面の縦横は ", n) fmt.Println("クイーンの配置パターンは ", len(res), " 通り") for _, state := range res { fmt.Println("--------------------") for _, row := range state { fmt.Println(row) } } } ================================================ FILE: ja/codes/go/chapter_backtracking/permutation_test.go ================================================ // File: permutation_test.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPermutationI(t *testing.T) { /* 全順列 I */ nums := []int{1, 2, 3} fmt.Printf("入力配列 nums = ") PrintSlice(nums) res := permutationsI(nums) fmt.Printf("すべての順列 res = ") fmt.Println(res) } func TestPermutationII(t *testing.T) { nums := []int{1, 2, 2} fmt.Printf("入力配列 nums = ") PrintSlice(nums) res := permutationsII(nums) fmt.Printf("すべての順列 res = ") fmt.Println(res) } ================================================ FILE: ja/codes/go/chapter_backtracking/permutations_i.go ================================================ // File: permutations_i.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* バックトラッキング:順列 I */ func backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // 状態の長さが要素数に等しければ、解を記録 if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // すべての選択肢を走査 for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // 枝刈り:要素の重複選択を許可しない if !(*selected)[i] { // 試行: 選択を行い、状態を更新 (*selected)[i] = true *state = append(*state, choice) // 次の選択へ進む backtrackI(state, choices, selected, res) // バックトラック:選択を取り消し、前の状態に戻す (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* 全順列 I */ func permutationsI(nums []int) [][]int { res := make([][]int, 0) state := make([]int, 0) selected := make([]bool, len(nums)) backtrackI(&state, &nums, &selected, &res) return res } ================================================ FILE: ja/codes/go/chapter_backtracking/permutations_ii.go ================================================ // File: permutations_ii.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* バックトラッキング:順列 II */ func backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // 状態の長さが要素数に等しければ、解を記録 if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // すべての選択肢を走査 duplicated := make(map[int]struct{}, 0) for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない if _, ok := duplicated[choice]; !ok && !(*selected)[i] { // 試す: 選択を行って状態を更新 // 選択済みの要素値を記録 duplicated[choice] = struct{}{} (*selected)[i] = true *state = append(*state, choice) // 次の選択へ進む backtrackII(state, choices, selected, res) // バックトラック:選択を取り消し、前の状態に戻す (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* 全順列 II */ func permutationsII(nums []int) [][]int { res := make([][]int, 0) state := make([]int, 0) selected := make([]bool, len(nums)) backtrackII(&state, &nums, &selected, &res) return res } ================================================ FILE: ja/codes/go/chapter_backtracking/preorder_traversal_i_compact.go ================================================ // File: preorder_traversal_i_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* 前順走査:例題 1 */ func preOrderI(root *TreeNode, res *[]*TreeNode) { if root == nil { return } if (root.Val).(int) == 7 { // 解を記録 *res = append(*res, root) } preOrderI(root.Left, res) preOrderI(root.Right, res) } ================================================ FILE: ja/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go ================================================ // File: preorder_traversal_ii_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* 前順走査:例題 2 */ func preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { if root == nil { return } // 試す *path = append(*path, root) if root.Val.(int) == 7 { // 解を記録 *res = append(*res, append([]*TreeNode{}, *path...)) } preOrderII(root.Left, res, path) preOrderII(root.Right, res, path) // バックトラック *path = (*path)[:len(*path)-1] } ================================================ FILE: ja/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go ================================================ // File: preorder_traversal_iii_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* 前順走査:例題 3 */ func preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { // 枝刈り if root == nil || root.Val == 3 { return } // 試す *path = append(*path, root) if root.Val.(int) == 7 { // 解を記録 *res = append(*res, append([]*TreeNode{}, *path...)) } preOrderIII(root.Left, res, path) preOrderIII(root.Right, res, path) // バックトラック *path = (*path)[:len(*path)-1] } ================================================ FILE: ja/codes/go/chapter_backtracking/preorder_traversal_iii_template.go ================================================ // File: preorder_traversal_iii_template.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* 現在の状態が解かどうかを判定 */ func isSolution(state *[]*TreeNode) bool { return len(*state) != 0 && (*state)[len(*state)-1].Val == 7 } /* 解を記録 */ func recordSolution(state *[]*TreeNode, res *[][]*TreeNode) { *res = append(*res, append([]*TreeNode{}, *state...)) } /* 現在の状態で、この選択が有効かどうかを判定 */ func isValid(state *[]*TreeNode, choice *TreeNode) bool { return choice != nil && choice.Val != 3 } /* 状態を更新 */ func makeChoice(state *[]*TreeNode, choice *TreeNode) { *state = append(*state, choice) } /* 状態を元に戻す */ func undoChoice(state *[]*TreeNode, choice *TreeNode) { *state = (*state)[:len(*state)-1] } /* バックトラッキング:例題 3 */ func backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) { // 解かどうかを確認 if isSolution(state) { // 解を記録 recordSolution(state, res) } // すべての選択肢を走査 for _, choice := range *choices { // 枝刈り:選択が妥当かを確認する if isValid(state, choice) { // 試行: 選択を行い、状態を更新 makeChoice(state, choice) // 次の選択へ進む temp := make([]*TreeNode, 0) temp = append(temp, choice.Left, choice.Right) backtrackIII(state, &temp, res) // バックトラック:選択を取り消し、前の状態に戻す undoChoice(state, choice) } } } ================================================ FILE: ja/codes/go/chapter_backtracking/preorder_traversal_test.go ================================================ // File: preorder_traversal_i_compact_test.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPreorderTraversalICompact(t *testing.T) { /* 二分木を初期化 */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\n二分木を初期化") PrintTree(root) // 先行順走査 res := make([]*TreeNode, 0) preOrderI(root, &res) fmt.Println("\n値が 7 のノードをすべて出力") for _, node := range res { fmt.Printf("%v ", node.Val) } fmt.Println() } func TestPreorderTraversalIICompact(t *testing.T) { /* 二分木を初期化 */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\n二分木を初期化") PrintTree(root) // 先行順走査 path := make([]*TreeNode, 0) res := make([][]*TreeNode, 0) preOrderII(root, &res, &path) fmt.Println("\n根ノードからノード 7 までの経路をすべて出力") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } func TestPreorderTraversalIIICompact(t *testing.T) { /* 二分木を初期化 */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\n二分木を初期化") PrintTree(root) // 先行順走査 path := make([]*TreeNode, 0) res := make([][]*TreeNode, 0) preOrderIII(root, &res, &path) fmt.Println("\n根ノードからノード 7 までの経路をすべて出力(経路に値が 3 のノードを含まない)") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } func TestPreorderTraversalIIITemplate(t *testing.T) { /* 二分木を初期化 */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\n二分木を初期化") PrintTree(root) // バックトラッキング法 res := make([][]*TreeNode, 0) state := make([]*TreeNode, 0) choices := make([]*TreeNode, 0) choices = append(choices, root) backtrackIII(&state, &choices, &res) fmt.Println("\n根ノードからノード 7 までの経路をすべて出力(経路に値が 3 のノードを含まない)") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } ================================================ FILE: ja/codes/go/chapter_backtracking/subset_sum_i.go ================================================ // File: subset_sum_i.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import "sort" /* バックトラッキング:部分和 I */ func backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) { // 部分集合の和が target に等しければ、解を記録 if target == 0 { newState := append([]int{}, *state...) *res = append(*res, newState) return } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for i := start; i < len(*choices); i++ { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if target-(*choices)[i] < 0 { break } // 試す:選択を行い、target と start を更新 *state = append(*state, (*choices)[i]) // 次の選択へ進む backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res) // バックトラック:選択を取り消し、前の状態に戻す *state = (*state)[:len(*state)-1] } } /* 部分和 I を解く */ func subsetSumI(nums []int, target int) [][]int { state := make([]int, 0) // 状態(部分集合) sort.Ints(nums) // nums をソート start := 0 // 開始点を走査 res := make([][]int, 0) // 結果リスト(部分集合のリスト) backtrackSubsetSumI(start, target, &state, &nums, &res) return res } ================================================ FILE: ja/codes/go/chapter_backtracking/subset_sum_i_naive.go ================================================ // File: subset_sum_i_naive.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* バックトラッキング:部分和 I */ func backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) { // 部分集合の和が target に等しければ、解を記録 if target == total { newState := append([]int{}, *state...) *res = append(*res, newState) return } // すべての選択肢を走査 for i := 0; i < len(*choices); i++ { // 枝刈り:部分和が target を超える場合はその選択をスキップする if total+(*choices)[i] > target { continue } // 試行:選択を行い、要素と total を更新する *state = append(*state, (*choices)[i]) // 次の選択へ進む backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res) // バックトラック:選択を取り消し、前の状態に戻す *state = (*state)[:len(*state)-1] } } /* 部分和 I を解く(重複部分集合を含む) */ func subsetSumINaive(nums []int, target int) [][]int { state := make([]int, 0) // 状態(部分集合) total := 0 // 部分和 res := make([][]int, 0) // 結果リスト(部分集合のリスト) backtrackSubsetSumINaive(total, target, &state, &nums, &res) return res } ================================================ FILE: ja/codes/go/chapter_backtracking/subset_sum_ii.go ================================================ // File: subset_sum_ii.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import "sort" /* バックトラッキング:部分和 II */ func backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) { // 部分集合の和が target に等しければ、解を記録 if target == 0 { newState := append([]int{}, *state...) *res = append(*res, newState) return } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for i := start; i < len(*choices); i++ { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if target-(*choices)[i] < 0 { break } // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする if i > start && (*choices)[i] == (*choices)[i-1] { continue } // 試す:選択を行い、target と start を更新 *state = append(*state, (*choices)[i]) // 次の選択へ進む backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res) // バックトラック:選択を取り消し、前の状態に戻す *state = (*state)[:len(*state)-1] } } /* 部分和 II を解く */ func subsetSumII(nums []int, target int) [][]int { state := make([]int, 0) // 状態(部分集合) sort.Ints(nums) // nums をソート start := 0 // 開始点を走査 res := make([][]int, 0) // 結果リスト(部分集合のリスト) backtrackSubsetSumII(start, target, &state, &nums, &res) return res } ================================================ FILE: ja/codes/go/chapter_backtracking/subset_sum_test.go ================================================ // File: subset_sum_test.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestSubsetSumINaive(t *testing.T) { nums := []int{3, 4, 5} target := 9 res := subsetSumINaive(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", 入力配列 nums = ") PrintSlice(nums) fmt.Println("合計が " + strconv.Itoa(target) + " に等しい部分集合 res = ") for i := range res { PrintSlice(res[i]) } fmt.Println("この方法の出力結果には重複した集合が含まれることに注意してください") } func TestSubsetSumI(t *testing.T) { nums := []int{3, 4, 5} target := 9 res := subsetSumI(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", 入力配列 nums = ") PrintSlice(nums) fmt.Println("合計が " + strconv.Itoa(target) + " に等しい部分集合 res = ") for i := range res { PrintSlice(res[i]) } } func TestSubsetSumII(t *testing.T) { nums := []int{4, 4, 5} target := 9 res := subsetSumII(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", 入力配列 nums = ") PrintSlice(nums) fmt.Println("合計が " + strconv.Itoa(target) + " に等しい部分集合 res = ") for i := range res { PrintSlice(res[i]) } } ================================================ FILE: ja/codes/go/chapter_computational_complexity/iteration.go ================================================ // File: iteration.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import "fmt" /* for ループ */ func forLoop(n int) int { res := 0 // 1, 2, ..., n-1, n を順に加算する for i := 1; i <= n; i++ { res += i } return res } /* while ループ */ func whileLoop(n int) int { res := 0 // 条件変数を初期化する i := 1 // 1, 2, ..., n-1, n を順に加算する for i <= n { res += i // 条件変数を更新する i++ } return res } /* while ループ(2回更新) */ func whileLoopII(n int) int { res := 0 // 条件変数を初期化する i := 1 // 1, 4, 10, ... を順に加算する for i <= n { res += i // 条件変数を更新する i++ i *= 2 } return res } /* 二重 for ループ */ func nestedForLoop(n int) string { res := "" // i = 1, 2, ..., n-1, n とループする for i := 1; i <= n; i++ { for j := 1; j <= n; j++ { // j = 1, 2, ..., n-1, n とループする res += fmt.Sprintf("(%d, %d), ", i, j) } } return res } ================================================ FILE: ja/codes/go/chapter_computational_complexity/iteration_test.go ================================================ // File: iteration_test.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import ( "fmt" "testing" ) /* Driver Code */ func TestIteration(t *testing.T) { n := 5 res := forLoop(n) fmt.Println("\nfor ループの合計結果 res = ", res) res = whileLoop(n) fmt.Println("\nwhile ループの合計結果 res = ", res) res = whileLoopII(n) fmt.Println("\nwhile ループ(2 回更新)の合計結果 res = ", res) resStr := nestedForLoop(n) fmt.Println("\n二重 for ループの走査結果 ", resStr) } ================================================ FILE: ja/codes/go/chapter_computational_complexity/recursion.go ================================================ // File: recursion.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import "container/list" /* 再帰 */ func recur(n int) int { // 終了条件 if n == 1 { return 1 } // 再帰:再帰呼び出し res := recur(n - 1) // 帰りがけ:結果を返す return n + res } /* 反復で再帰を模擬する */ func forLoopRecur(n int) int { // 明示的なスタックを使ってシステムコールスタックを模擬する stack := list.New() res := 0 // 再帰:再帰呼び出し for i := n; i > 0; i-- { // 「スタックへのプッシュ」で「再帰」を模擬する stack.PushBack(i) } // 帰りがけ:結果を返す for stack.Len() != 0 { // 「スタックから取り出す操作」で「帰り」をシミュレート res += stack.Back().Value.(int) stack.Remove(stack.Back()) } // res = 1+2+3+...+n return res } /* 末尾再帰 */ func tailRecur(n int, res int) int { // 終了条件 if n == 0 { return res } // 末尾再帰呼び出し return tailRecur(n-1, res+n) } /* フィボナッチ数列:再帰 */ func fib(n int) int { // 終了条件 f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1 } // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す res := fib(n-1) + fib(n-2) // 結果 f(n) を返す return res } ================================================ FILE: ja/codes/go/chapter_computational_complexity/recursion_test.go ================================================ // File: recursion_test.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import ( "fmt" "testing" ) /* Driver Code */ func TestRecursion(t *testing.T) { n := 5 res := recur(n) fmt.Println("\n再帰関数の合計結果 res = ", res) res = forLoopRecur(n) fmt.Println("\n反復で再帰をシミュレートした合計結果 res = ", res) res = tailRecur(n, 0) fmt.Println("\n末尾再帰関数の合計結果 res = ", res) res = fib(n) fmt.Println("\nフィボナッチ数列の第", n, "項は", res) } ================================================ FILE: ja/codes/go/chapter_computational_complexity/space_complexity.go ================================================ // File: space_complexity.go // Created Time: 2022-12-15 // Author: cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "fmt" "strconv" . "github.com/krahets/hello-algo/pkg" ) /* 構造体 */ type node struct { val int next *node } /* node 構造体を作成する */ func newNode(val int) *node { return &node{val: val} } /* 関数 */ func function() int { // いくつかの操作を実行... return 0 } /* 定数階 */ func spaceConstant(n int) { // 定数、変数、オブジェクトは O(1) の空間を占める const a = 0 b := 0 nums := make([]int, 10000) node := newNode(0) // ループ内の変数は O(1) の空間を占める var c int for i := 0; i < n; i++ { c = 0 } // ループ内の関数は O(1) の空間を占める for i := 0; i < n; i++ { function() } b += 0 c += 0 nums[0] = 0 node.val = 0 } /* 線形階 */ func spaceLinear(n int) { // 長さ n の配列は O(n) の空間を使用 _ = make([]int, n) // 長さ n のリストは O(n) の空間を使用 var nodes []*node for i := 0; i < n; i++ { nodes = append(nodes, newNode(i)) } // 長さ n のハッシュテーブルは O(n) の空間を使用 m := make(map[int]string, n) for i := 0; i < n; i++ { m[i] = strconv.Itoa(i) } } /* 線形時間(再帰実装) */ func spaceLinearRecur(n int) { fmt.Println("再帰 n =", n) if n == 1 { return } spaceLinearRecur(n - 1) } /* 二乗階 */ func spaceQuadratic(n int) { // 行列は O(n^2) の空間を使用する numMatrix := make([][]int, n) for i := 0; i < n; i++ { numMatrix[i] = make([]int, n) } } /* 二次時間(再帰実装) */ func spaceQuadraticRecur(n int) int { if n <= 0 { return 0 } nums := make([]int, n) fmt.Printf("再帰 n = %d における nums の長さ = %d \n", n, len(nums)) return spaceQuadraticRecur(n - 1) } /* 指数時間(完全二分木の構築) */ func buildTree(n int) *TreeNode { if n == 0 { return nil } root := NewTreeNode(0) root.Left = buildTree(n - 1) root.Right = buildTree(n - 1) return root } ================================================ FILE: ja/codes/go/chapter_computational_complexity/space_complexity_test.go ================================================ // File: space_complexity_test.go // Created Time: 2022-12-15 // Author: cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "testing" . "github.com/krahets/hello-algo/pkg" ) func TestSpaceComplexity(t *testing.T) { n := 5 // 定数階 spaceConstant(n) // 線形階 spaceLinear(n) spaceLinearRecur(n) // 二乗階 spaceQuadratic(n) spaceQuadraticRecur(n) // 指数オーダー root := buildTree(n) PrintTree(root) } ================================================ FILE: ja/codes/go/chapter_computational_complexity/time_complexity.go ================================================ // File: time_complexity.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_computational_complexity /* 定数階 */ func constant(n int) int { count := 0 size := 100000 for i := 0; i < size; i++ { count++ } return count } /* 線形階 */ func linear(n int) int { count := 0 for i := 0; i < n; i++ { count++ } return count } /* 線形時間(配列を走査) */ func arrayTraversal(nums []int) int { count := 0 // ループ回数は配列長に比例する for range nums { count++ } return count } /* 二乗階 */ func quadratic(n int) int { count := 0 // ループ回数はデータサイズ n の二乗に比例する for i := 0; i < n; i++ { for j := 0; j < n; j++ { count++ } } return count } /* 二次時間(バブルソート) */ func bubbleSort(nums []int) int { count := 0 // カウンタ // 外側のループ:未ソート区間は [0, i] for i := len(nums) - 1; i > 0; i-- { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // nums[j] と nums[j + 1] を交換 tmp := nums[j] nums[j] = nums[j+1] nums[j+1] = tmp count += 3 // 要素交換には 3 回の単位操作が含まれる } } } return count } /* 指数時間(ループ実装) */ func exponential(n int) int { count, base := 0, 1 // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する for i := 0; i < n; i++ { for j := 0; j < base; j++ { count++ } base *= 2 } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count } /* 指数時間(再帰実装) */ func expRecur(n int) int { if n == 1 { return 1 } return expRecur(n-1) + expRecur(n-1) + 1 } /* 対数時間(ループ実装) */ func logarithmic(n int) int { count := 0 for n > 1 { n = n / 2 count++ } return count } /* 対数時間(再帰実装) */ func logRecur(n int) int { if n <= 1 { return 0 } return logRecur(n/2) + 1 } /* 線形対数時間 */ func linearLogRecur(n int) int { if n <= 1 { return 1 } count := linearLogRecur(n/2) + linearLogRecur(n/2) for i := 0; i < n; i++ { count++ } return count } /* 階乗時間(再帰実装) */ func factorialRecur(n int) int { if n == 0 { return 1 } count := 0 // 1個から n 個に分裂 for i := 0; i < n; i++ { count += factorialRecur(n - 1) } return count } ================================================ FILE: ja/codes/go/chapter_computational_complexity/time_complexity_test.go ================================================ // File: time_complexity_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_computational_complexity import ( "fmt" "testing" ) func TestTimeComplexity(t *testing.T) { n := 8 fmt.Println("入力データサイズ n =", n) count := constant(n) fmt.Println("定数時間の操作回数 =", count) count = linear(n) fmt.Println("線形時間の操作回数 =", count) count = arrayTraversal(make([]int, n)) fmt.Println("線形時間(配列走査)の操作回数 =", count) count = quadratic(n) fmt.Println("二乗時間の操作回数 =", count) nums := make([]int, n) for i := 0; i < n; i++ { nums[i] = n - i } count = bubbleSort(nums) fmt.Println("二乗時間(バブルソート)の操作回数 =", count) count = exponential(n) fmt.Println("指数時間(ループ実装)の操作回数 =", count) count = expRecur(n) fmt.Println("指数時間(再帰実装)の操作回数 =", count) count = logarithmic(n) fmt.Println("対数時間(ループ実装)の操作回数 =", count) count = logRecur(n) fmt.Println("対数時間(再帰実装)の操作回数 =", count) count = linearLogRecur(n) fmt.Println("線形対数時間(再帰実装)の操作回数 =", count) count = factorialRecur(n) fmt.Println("階乗時間(再帰実装)の操作回数 =", count) } ================================================ FILE: ja/codes/go/chapter_computational_complexity/worst_best_time_complexity.go ================================================ // File: worst_best_time_complexity.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "math/rand" ) /* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ func randomNumbers(n int) []int { nums := make([]int, n) // 配列 nums = { 1, 2, 3, ..., n } を生成 for i := 0; i < n; i++ { nums[i] = i + 1 } // 配列要素をランダムにシャッフル rand.Shuffle(len(nums), func(i, j int) { nums[i], nums[j] = nums[j], nums[i] }) return nums } /* 配列 nums 内で数値 1 のインデックスを探す */ func findOne(nums []int) int { for i := 0; i < len(nums); i++ { // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる if nums[i] == 1 { return i } } return -1 } ================================================ FILE: ja/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go ================================================ // File: worst_best_time_complexity_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "fmt" "testing" ) func TestWorstBestTimeComplexity(t *testing.T) { for i := 0; i < 10; i++ { n := 100 nums := randomNumbers(n) index := findOne(nums) fmt.Println("\n配列 [ 1, 2, ..., n ] をシャッフルした後 =", nums) fmt.Println("数字 1 のインデックスは", index) } } ================================================ FILE: ja/codes/go/chapter_divide_and_conquer/binary_search_recur.go ================================================ // File: binary_search_recur.go // Created Time: 2023-07-19 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer /* 二分探索:問題 f(i, j) */ func dfs(nums []int, target, i, j int) int { // 区間が空なら対象要素は存在しないため、-1 を返す if i > j { return -1 } // 中点インデックスを計算する m := i + ((j - i) >> 1) // 中点の要素と目標要素の大小を判定する if nums[m] < target { // 小さければ右半分の配列を再帰 // 部分問題 f(m+1, j) を解く return dfs(nums, target, m+1, j) } else if nums[m] > target { // 大きければ左半分の配列を再帰 // 部分問題 f(i, m-1) を解く return dfs(nums, target, i, m-1) } else { // 目標要素が見つかったらそのインデックスを返す return m } } /* 二分探索 */ func binarySearch(nums []int, target int) int { n := len(nums) return dfs(nums, target, 0, n-1) } ================================================ FILE: ja/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go ================================================ // File: binary_search_recur_test.go // Created Time: 2023-07-19 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "fmt" "testing" ) func TestBinarySearch(t *testing.T) { nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} target := 6 noTarget := 99 targetIndex := binarySearch(nums, target) fmt.Println("対象要素 6 のインデックス = ", targetIndex) noTargetIndex := binarySearch(nums, noTarget) fmt.Println("対象要素が存在しないときのインデックス = ", noTargetIndex) } ================================================ FILE: ja/codes/go/chapter_divide_and_conquer/build_tree.go ================================================ // File: build_tree.go // Created Time: 2023-07-20 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import . "github.com/krahets/hello-algo/pkg" /* 二分木を構築:分割統治 */ func dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode { // 部分木区間が空なら終了する if r-l < 0 { return nil } // ルートノードを初期化する root := NewTreeNode(preorder[i]) // m を求めて左右部分木を分割する m := inorderMap[preorder[i]] // 部分問題:左部分木を構築する root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1) // 部分問題:右部分木を構築する root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r) // 根ノードを返す return root } /* 二分木を構築 */ func buildTree(preorder, inorder []int) *TreeNode { // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する inorderMap := make(map[int]int, len(inorder)) for i := 0; i < len(inorder); i++ { inorderMap[inorder[i]] = i } root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1) return root } ================================================ FILE: ja/codes/go/chapter_divide_and_conquer/build_tree_test.go ================================================ // File: build_tree_test.go // Created Time: 2023-07-20 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestBuildTree(t *testing.T) { preorder := []int{3, 9, 2, 1, 7} inorder := []int{9, 3, 1, 2, 7} fmt.Print("前順走査 = ") PrintSlice(preorder) fmt.Print("中順走査 = ") PrintSlice(inorder) root := buildTree(preorder, inorder) fmt.Println("構築した二分木は:") PrintTree(root) } ================================================ FILE: ja/codes/go/chapter_divide_and_conquer/hanota.go ================================================ // File: hanota.go // Created Time: 2023-07-21 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import "container/list" /* 円盤を 1 枚移動 */ func move(src, tar *list.List) { // src の上から円盤を1枚取り出す pan := src.Back() // 円盤を tar の上に置く tar.PushBack(pan.Value) // `src` の最上部の円盤を取り外す src.Remove(pan) } /* ハノイの塔の問題 f(i) を解く */ func dfsHanota(i int, src, buf, tar *list.List) { // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if i == 1 { move(src, tar) return } // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfsHanota(i-1, src, tar, buf) // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src, tar) // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfsHanota(i-1, buf, src, tar) } /* ハノイの塔を解く */ func solveHanota(A, B, C *list.List) { n := A.Len() // A の上から n 枚の円盤を B を介して C へ移す dfsHanota(n, A, B, C) } ================================================ FILE: ja/codes/go/chapter_divide_and_conquer/hanota_test.go ================================================ // File: hanota_test.go // Created Time: 2023-07-21 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHanota(t *testing.T) { // リスト末尾が柱の頂上 A := list.New() for i := 5; i > 0; i-- { A.PushBack(i) } B := list.New() C := list.New() fmt.Println("初期状態:") fmt.Print("A = ") PrintList(A) fmt.Print("B = ") PrintList(B) fmt.Print("C = ") PrintList(C) solveHanota(A, B, C) fmt.Println("円盤の移動完了後:") fmt.Print("A = ") PrintList(A) fmt.Print("B = ") PrintList(B) fmt.Print("C = ") PrintList(C) } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go ================================================ // File: climbing_stairs_backtrack.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* バックトラッキング */ func backtrack(choices []int, state, n int, res []int) { // 第 n 段に到達したら、方法数を 1 増やす if state == n { res[0] = res[0] + 1 } // すべての選択肢を走査 for _, choice := range choices { // 枝刈り: 第 n 段を超えないようにする if state+choice > n { continue } // 試行: 選択を行い、状態を更新 backtrack(choices, state+choice, n, res) // バックトラック } } /* 階段登り:バックトラッキング */ func climbingStairsBacktrack(n int) int { // 1 段または 2 段上ることを選べる choices := []int{1, 2} // 第 0 段から上り始める state := 0 res := make([]int, 1) // res[0] を使って方法数を記録する res[0] = 0 backtrack(choices, state, n, res) return res[0] } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go ================================================ // File: climbing_stairs_constraint_dp.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 制約付き階段登り:動的計画法 */ func climbingStairsConstraintDP(n int) int { if n == 1 || n == 2 { return 1 } // 部分問題の解を保存するために dp テーブルを初期化 dp := make([][3]int, n+1) // 初期状態:最小部分問題の解をあらかじめ設定 dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i := 3; i <= n; i++ { dp[i][1] = dp[i-1][2] dp[i][2] = dp[i-2][1] + dp[i-2][2] } return dp[n][1] + dp[n][2] } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go ================================================ // File: climbing_stairs_dfs.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 検索 */ func dfs(i int) int { // dp[1] と dp[2] は既知なので返す if i == 1 || i == 2 { return i } // dp[i] = dp[i-1] + dp[i-2] count := dfs(i-1) + dfs(i-2) return count } /* 階段登り:探索 */ func climbingStairsDFS(n int) int { return dfs(n) } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go ================================================ // File: climbing_stairs_dfs_mem.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* メモ化探索 */ func dfsMem(i int, mem []int) int { // dp[1] と dp[2] は既知なので返す if i == 1 || i == 2 { return i } // dp[i] の記録があれば、それをそのまま返す if mem[i] != -1 { return mem[i] } // dp[i] = dp[i-1] + dp[i-2] count := dfsMem(i-1, mem) + dfsMem(i-2, mem) // dp[i] を記録する mem[i] = count return count } /* 階段登り:メモ化探索 */ func climbingStairsDFSMem(n int) int { // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す mem := make([]int, n+1) for i := range mem { mem[i] = -1 } return dfsMem(n, mem) } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go ================================================ // File: climbing_stairs_dp.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 階段登り:動的計画法 */ func climbingStairsDP(n int) int { if n == 1 || n == 2 { return n } // 部分問題の解を保存するために dp テーブルを初期化 dp := make([]int, n+1) // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = 1 dp[2] = 2 // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i := 3; i <= n; i++ { dp[i] = dp[i-1] + dp[i-2] } return dp[n] } /* 階段登り:空間最適化した動的計画法 */ func climbingStairsDPComp(n int) int { if n == 1 || n == 2 { return n } a, b := 1, 2 // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i := 3; i <= n; i++ { a, b = b, a+b } return b } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/climbing_stairs_test.go ================================================ // File: climbing_stairs_test.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestClimbingStairsBacktrack(t *testing.T) { n := 9 res := climbingStairsBacktrack(n) fmt.Printf("%d 段の階段を登る方法は %d 通り\n", n, res) } func TestClimbingStairsDFS(t *testing.T) { n := 9 res := climbingStairsDFS(n) fmt.Printf("%d 段の階段を登る方法は %d 通り\n", n, res) } func TestClimbingStairsDFSMem(t *testing.T) { n := 9 res := climbingStairsDFSMem(n) fmt.Printf("%d 段の階段を登る方法は %d 通り\n", n, res) } func TestClimbingStairsDP(t *testing.T) { n := 9 res := climbingStairsDP(n) fmt.Printf("%d 段の階段を登る方法は %d 通り\n", n, res) } func TestClimbingStairsDPComp(t *testing.T) { n := 9 res := climbingStairsDPComp(n) fmt.Printf("%d 段の階段を登る方法は %d 通り\n", n, res) } func TestClimbingStairsConstraintDP(t *testing.T) { n := 9 res := climbingStairsConstraintDP(n) fmt.Printf("%d 段の階段を登る方法は %d 通り\n", n, res) } func TestMinCostClimbingStairsDPComp(t *testing.T) { cost := []int{0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1} fmt.Printf("入力された階段のコストリストは %v\n", cost) res := minCostClimbingStairsDP(cost) fmt.Printf("階段を登り切る最小コストは %d\n", res) res = minCostClimbingStairsDPComp(cost) fmt.Printf("階段を登り切る最小コストは %d\n", res) } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/coin_change.go ================================================ // File: coin_change.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* コイン両替:動的計画法 */ func coinChangeDP(coins []int, amt int) int { n := len(coins) max := amt + 1 // dp テーブルを初期化 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, amt+1) } // 状態遷移:先頭行と先頭列 for a := 1; a <= amt; a++ { dp[0][a] = max } // 状態遷移: 残りの行と列 for i := 1; i <= n; i++ { for a := 1; a <= amt; a++ { if coins[i-1] > a { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i-1][a] } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1))) } } } if dp[n][amt] != max { return dp[n][amt] } return -1 } /* コイン両替:動的計画法 */ func coinChangeDPComp(coins []int, amt int) int { n := len(coins) max := amt + 1 // dp テーブルを初期化 dp := make([]int, amt+1) for i := 1; i <= amt; i++ { dp[i] = max } // 状態遷移 for i := 1; i <= n; i++ { // 順方向に走査する for a := 1; a <= amt; a++ { if coins[i-1] > a { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a] } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1))) } } } if dp[amt] != max { return dp[amt] } return -1 } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/coin_change_ii.go ================================================ // File: coin_change_ii.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* コイン両替 II:動的計画法 */ func coinChangeIIDP(coins []int, amt int) int { n := len(coins) // dp テーブルを初期化 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, amt+1) } // 先頭列を初期化する for i := 0; i <= n; i++ { dp[i][0] = 1 } // 状態遷移: 残りの行と列 for i := 1; i <= n; i++ { for a := 1; a <= amt; a++ { if coins[i-1] > a { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i-1][a] } else { // コイン i を選ばない場合と選ぶ場合の和 dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]] } } } return dp[n][amt] } /* コイン両替 II:空間最適化した動的計画法 */ func coinChangeIIDPComp(coins []int, amt int) int { n := len(coins) // dp テーブルを初期化 dp := make([]int, amt+1) dp[0] = 1 // 状態遷移 for i := 1; i <= n; i++ { // 順方向に走査する for a := 1; a <= amt; a++ { if coins[i-1] > a { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a] } else { // コイン i を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a-coins[i-1]] } } } return dp[amt] } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/coin_change_test.go ================================================ // File: coin_change_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestCoinChange(t *testing.T) { coins := []int{1, 2, 5} amt := 4 // 動的計画法 res := coinChangeDP(coins, amt) fmt.Printf("目標金額を作るのに必要な最小硬貨枚数は %d\n", res) // 空間最適化後の動的計画法 res = coinChangeDPComp(coins, amt) fmt.Printf("目標金額を作るのに必要な最小硬貨枚数は %d\n", res) } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/edit_distance.go ================================================ // File: edit_distance.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 編集距離:総当たり探索 */ func editDistanceDFS(s string, t string, i int, j int) int { // s と t がともに空なら 0 を返す if i == 0 && j == 0 { return 0 } // s が空なら t の長さを返す if i == 0 { return j } // t が空なら s の長さを返す if j == 0 { return i } // 2 つの文字が等しければ、その 2 文字をそのままスキップする if s[i-1] == t[j-1] { return editDistanceDFS(s, t, i-1, j-1) } // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 insert := editDistanceDFS(s, t, i, j-1) deleted := editDistanceDFS(s, t, i-1, j) replace := editDistanceDFS(s, t, i-1, j-1) // 最小編集回数を返す return MinInt(MinInt(insert, deleted), replace) + 1 } /* 編集距離:メモ化探索 */ func editDistanceDFSMem(s string, t string, mem [][]int, i int, j int) int { // s と t がともに空なら 0 を返す if i == 0 && j == 0 { return 0 } // s が空なら t の長さを返す if i == 0 { return j } // t が空なら s の長さを返す if j == 0 { return i } // 記録済みなら、それをそのまま返す if mem[i][j] != -1 { return mem[i][j] } // 2 つの文字が等しければ、その 2 文字をそのままスキップする if s[i-1] == t[j-1] { return editDistanceDFSMem(s, t, mem, i-1, j-1) } // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 insert := editDistanceDFSMem(s, t, mem, i, j-1) deleted := editDistanceDFSMem(s, t, mem, i-1, j) replace := editDistanceDFSMem(s, t, mem, i-1, j-1) // 最小編集回数を記録して返す mem[i][j] = MinInt(MinInt(insert, deleted), replace) + 1 return mem[i][j] } /* 編集距離:動的計画法 */ func editDistanceDP(s string, t string) int { n := len(s) m := len(t) dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, m+1) } // 状態遷移:先頭行と先頭列 for i := 1; i <= n; i++ { dp[i][0] = i } for j := 1; j <= m; j++ { dp[0][j] = j } // 状態遷移: 残りの行と列 for i := 1; i <= n; i++ { for j := 1; j <= m; j++ { if s[i-1] == t[j-1] { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i-1][j-1] } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1 } } } return dp[n][m] } /* 編集距離:空間最適化した動的計画法 */ func editDistanceDPComp(s string, t string) int { n := len(s) m := len(t) dp := make([]int, m+1) // 状態遷移:先頭行 for j := 1; j <= m; j++ { dp[j] = j } // 状態遷移:残りの行 for i := 1; i <= n; i++ { // 状態遷移:先頭列 leftUp := dp[0] // dp[i-1, j-1] を一時保存する dp[0] = i // 状態遷移:残りの列 for j := 1; j <= m; j++ { temp := dp[j] if s[i-1] == t[j-1] { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftUp } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1 } leftUp = temp // 次の反復の dp[i-1, j-1] に更新する } } return dp[m] } func MinInt(a, b int) int { if a < b { return a } return b } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/edit_distance_test.go ================================================ // File: edit_distance_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestEditDistanceDFS(test *testing.T) { s := "bag" t := "pack" n := len(s) m := len(t) // 全探索 res := editDistanceDFS(s, t, n, m) fmt.Printf("%s を %s に変更するには最低 %d 回の編集が必要です\n", s, t, res) // メモ化探索 mem := make([][]int, n+1) for i := 0; i <= n; i++ { mem[i] = make([]int, m+1) for j := 0; j <= m; j++ { mem[i][j] = -1 } } res = editDistanceDFSMem(s, t, mem, n, m) fmt.Printf("%s を %s に変更するには最低 %d 回の編集が必要です\n", s, t, res) // 動的計画法 res = editDistanceDP(s, t) fmt.Printf("%s を %s に変更するには最低 %d 回の編集が必要です\n", s, t, res) // 空間最適化後の動的計画法 res = editDistanceDPComp(s, t) fmt.Printf("%s を %s に変更するには最低 %d 回の編集が必要です\n", s, t, res) } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/knapsack.go ================================================ // File: knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* 0-1 ナップサック:総当たり探索 */ func knapsackDFS(wgt, val []int, i, c int) int { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if i == 0 || c == 0 { return 0 } // ナップサック容量を超える場合は、入れない選択しかできない if wgt[i-1] > c { return knapsackDFS(wgt, val, i-1, c) } // 品物 i を入れない場合と入れる場合の最大価値を計算する no := knapsackDFS(wgt, val, i-1, c) yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1] // 2つの案のうち価値が大きいほうを返す return int(math.Max(float64(no), float64(yes))) } /* 0-1 ナップサック:メモ化探索 */ func knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if i == 0 || c == 0 { return 0 } // 既に記録があればそのまま返す if mem[i][c] != -1 { return mem[i][c] } // ナップサック容量を超える場合は、入れない選択しかできない if wgt[i-1] > c { return knapsackDFSMem(wgt, val, mem, i-1, c) } // 品物 i を入れない場合と入れる場合の最大価値を計算する no := knapsackDFSMem(wgt, val, mem, i-1, c) yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1] // 2つの案のうち価値が大きいほうを返す mem[i][c] = int(math.Max(float64(no), float64(yes))) return mem[i][c] } /* 0-1 ナップサック:動的計画法 */ func knapsackDP(wgt, val []int, cap int) int { n := len(wgt) // dp テーブルを初期化 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, cap+1) } // 状態遷移 for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i-1][c] } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1]))) } } } return dp[n][cap] } /* 0-1 ナップサック:空間最適化後の動的計画法 */ func knapsackDPComp(wgt, val []int, cap int) int { n := len(wgt) // dp テーブルを初期化 dp := make([]int, cap+1) // 状態遷移 for i := 1; i <= n; i++ { // 逆順に走査する for c := cap; c >= 1; c-- { if wgt[i-1] <= c { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) } } } return dp[cap] } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/knapsack_test.go ================================================ // File: knapsack_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestKnapsack(t *testing.T) { wgt := []int{10, 20, 30, 40, 50} val := []int{50, 120, 150, 210, 240} c := 50 n := len(wgt) // 全探索 res := knapsackDFS(wgt, val, n, c) fmt.Printf("ナップサック容量を超えない最大価値は %d\n", res) // メモ化探索 mem := make([][]int, n+1) for i := 0; i <= n; i++ { mem[i] = make([]int, c+1) for j := 0; j <= c; j++ { mem[i][j] = -1 } } res = knapsackDFSMem(wgt, val, mem, n, c) fmt.Printf("ナップサック容量を超えない最大価値は %d\n", res) // 動的計画法 res = knapsackDP(wgt, val, c) fmt.Printf("ナップサック容量を超えない最大価値は %d\n", res) // 空間最適化後の動的計画法 res = knapsackDPComp(wgt, val, c) fmt.Printf("ナップサック容量を超えない最大価値は %d\n", res) } func TestUnboundedKnapsack(t *testing.T) { wgt := []int{1, 2, 3} val := []int{5, 11, 15} c := 4 // 動的計画法 res := unboundedKnapsackDP(wgt, val, c) fmt.Printf("ナップサック容量を超えない最大価値は %d\n", res) // 空間最適化後の動的計画法 res = unboundedKnapsackDPComp(wgt, val, c) fmt.Printf("ナップサック容量を超えない最大価値は %d\n", res) } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go ================================================ // File: min_cost_climbing_stairs_dp.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 階段登りの最小コスト:動的計画法 */ func minCostClimbingStairsDP(cost []int) int { n := len(cost) - 1 if n == 1 || n == 2 { return cost[n] } min := func(a, b int) int { if a < b { return a } return b } // 部分問題の解を保存するために dp テーブルを初期化 dp := make([]int, n+1) // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = cost[1] dp[2] = cost[2] // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i := 3; i <= n; i++ { dp[i] = min(dp[i-1], dp[i-2]) + cost[i] } return dp[n] } /* 階段昇りの最小コスト:空間最適化後の動的計画法 */ func minCostClimbingStairsDPComp(cost []int) int { n := len(cost) - 1 if n == 1 || n == 2 { return cost[n] } min := func(a, b int) int { if a < b { return a } return b } // 初期状態:最小部分問題の解をあらかじめ設定 a, b := cost[1], cost[2] // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i := 3; i <= n; i++ { tmp := b b = min(a, tmp) + cost[i] a = tmp } return b } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/min_path_sum.go ================================================ // File: min_path_sum.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* 最小経路和:全探索 */ func minPathSumDFS(grid [][]int, i, j int) int { // 左上のセルなら探索を終了する if i == 0 && j == 0 { return grid[0][0] } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if i < 0 || j < 0 { return math.MaxInt } // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する up := minPathSumDFS(grid, i-1, j) left := minPathSumDFS(grid, i, j-1) // 左上隅から (i, j) までの最小経路コストを返す return int(math.Min(float64(left), float64(up))) + grid[i][j] } /* 最小経路和:メモ化探索 */ func minPathSumDFSMem(grid, mem [][]int, i, j int) int { // 左上のセルなら探索を終了する if i == 0 && j == 0 { return grid[0][0] } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if i < 0 || j < 0 { return math.MaxInt } // 既に記録があればそのまま返す if mem[i][j] != -1 { return mem[i][j] } // 左と上のセルからの最小経路コスト up := minPathSumDFSMem(grid, mem, i-1, j) left := minPathSumDFSMem(grid, mem, i, j-1) // 左上から (i, j) までの最小経路コストを記録して返す mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j] return mem[i][j] } /* 最小経路和:動的計画法 */ func minPathSumDP(grid [][]int) int { n, m := len(grid), len(grid[0]) // dp テーブルを初期化 dp := make([][]int, n) for i := 0; i < n; i++ { dp[i] = make([]int, m) } dp[0][0] = grid[0][0] // 状態遷移:先頭行 for j := 1; j < m; j++ { dp[0][j] = dp[0][j-1] + grid[0][j] } // 状態遷移:先頭列 for i := 1; i < n; i++ { dp[i][0] = dp[i-1][0] + grid[i][0] } // 状態遷移: 残りの行と列 for i := 1; i < n; i++ { for j := 1; j < m; j++ { dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j] } } return dp[n-1][m-1] } /* 最小経路和:空間最適化後の動的計画法 */ func minPathSumDPComp(grid [][]int) int { n, m := len(grid), len(grid[0]) // dp テーブルを初期化 dp := make([]int, m) // 状態遷移:先頭行 dp[0] = grid[0][0] for j := 1; j < m; j++ { dp[j] = dp[j-1] + grid[0][j] } // 状態遷移: 残りの行と列 for i := 1; i < n; i++ { // 状態遷移:先頭列 dp[0] = dp[0] + grid[i][0] // 状態遷移:残りの列 for j := 1; j < m; j++ { dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j] } } return dp[m-1] } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/min_path_sum_test.go ================================================ // File: min_path_sum_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestMinPathSum(t *testing.T) { grid := [][]int{ {1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}, } n, m := len(grid), len(grid[0]) // 全探索 res := minPathSumDFS(grid, n-1, m-1) fmt.Printf("左上から右下までの最小経路和は %d\n", res) // メモ化探索 mem := make([][]int, n) for i := 0; i < n; i++ { mem[i] = make([]int, m) for j := 0; j < m; j++ { mem[i][j] = -1 } } res = minPathSumDFSMem(grid, mem, n-1, m-1) fmt.Printf("左上から右下までの最小経路和は %d\n", res) // 動的計画法 res = minPathSumDP(grid) fmt.Printf("左上から右下までの最小経路和は %d\n", res) // 空間最適化後の動的計画法 res = minPathSumDPComp(grid) fmt.Printf("左上から右下までの最小経路和は %d\n", res) } ================================================ FILE: ja/codes/go/chapter_dynamic_programming/unbounded_knapsack.go ================================================ // File: unbounded_knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* 完全ナップサック問題:動的計画法 */ func unboundedKnapsackDP(wgt, val []int, cap int) int { n := len(wgt) // dp テーブルを初期化 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, cap+1) } // 状態遷移 for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i-1][c] } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1]))) } } } return dp[n][cap] } /* 完全ナップサック問題:空間最適化後の動的計画法 */ func unboundedKnapsackDPComp(wgt, val []int, cap int) int { n := len(wgt) // dp テーブルを初期化 dp := make([]int, cap+1) // 状態遷移 for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c] } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) } } } return dp[cap] } ================================================ FILE: ja/codes/go/chapter_graph/graph_adjacency_list.go ================================================ // File: graph_adjacency_list.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "strconv" "strings" . "github.com/krahets/hello-algo/pkg" ) /* 隣接リストに基づく無向グラフクラス */ type graphAdjList struct { // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 adjList map[Vertex][]Vertex } /* コンストラクタ */ func newGraphAdjList(edges [][]Vertex) *graphAdjList { g := &graphAdjList{ adjList: make(map[Vertex][]Vertex), } // すべての頂点と辺を追加 for _, edge := range edges { g.addVertex(edge[0]) g.addVertex(edge[1]) g.addEdge(edge[0], edge[1]) } return g } /* 頂点数を取得 */ func (g *graphAdjList) size() int { return len(g.adjList) } /* 辺を追加 */ func (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) { _, ok1 := g.adjList[vet1] _, ok2 := g.adjList[vet2] if !ok1 || !ok2 || vet1 == vet2 { panic("error") } // 辺 `vet1 - vet2` を追加し、無名 `struct{}` を追加する g.adjList[vet1] = append(g.adjList[vet1], vet2) g.adjList[vet2] = append(g.adjList[vet2], vet1) } /* 辺を削除 */ func (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) { _, ok1 := g.adjList[vet1] _, ok2 := g.adjList[vet2] if !ok1 || !ok2 || vet1 == vet2 { panic("error") } // 辺 vet1 - vet2 を削除 g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2) g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1) } /* 頂点を追加 */ func (g *graphAdjList) addVertex(vet Vertex) { _, ok := g.adjList[vet] if ok { return } // 隣接リストに新しいリストを追加 g.adjList[vet] = make([]Vertex, 0) } /* 頂点を削除 */ func (g *graphAdjList) removeVertex(vet Vertex) { _, ok := g.adjList[vet] if !ok { panic("error") } // 隣接リストから頂点 vet に対応するリストを削除 delete(g.adjList, vet) // 他の頂点のリストを走査し、vet を含むすべての辺を削除 for v, list := range g.adjList { g.adjList[v] = DeleteSliceElms(list, vet) } } /* 隣接リストを出力 */ func (g *graphAdjList) print() { var builder strings.Builder fmt.Printf("隣接リスト = \n") for k, v := range g.adjList { builder.WriteString("\t\t" + strconv.Itoa(k.Val) + ": ") for _, vet := range v { builder.WriteString(strconv.Itoa(vet.Val) + " ") } fmt.Println(builder.String()) builder.Reset() } } ================================================ FILE: ja/codes/go/chapter_graph/graph_adjacency_list_test.go ================================================ // File: graph_adjacency_list_test.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphAdjList(t *testing.T) { /* 無向グラフを初期化 */ v := ValsToVets([]int{1, 3, 2, 5, 4}) edges := [][]Vertex{{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}} graph := newGraphAdjList(edges) fmt.Println("初期化後、グラフは:") graph.print() /* 辺を追加 */ // 頂点 1, 2 は v[0], v[2] graph.addEdge(v[0], v[2]) fmt.Println("\n辺 1-2 を追加後、グラフは") graph.print() /* 辺を削除 */ // 頂点 1, 3 は v[0], v[1] graph.removeEdge(v[0], v[1]) fmt.Println("\n辺 1-3 を削除後、グラフは") graph.print() /* 頂点を追加 */ v5 := NewVertex(6) graph.addVertex(v5) fmt.Println("\n頂点 6 を追加後、グラフは") graph.print() /* 頂点を削除 */ // 頂点 3 は v[1] graph.removeVertex(v[1]) fmt.Println("\n頂点 3 を削除後、グラフは") graph.print() } ================================================ FILE: ja/codes/go/chapter_graph/graph_adjacency_matrix.go ================================================ // File: graph_adjacency_matrix.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import "fmt" /* 隣接行列に基づく無向グラフクラス */ type graphAdjMat struct { // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す vertices []int // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 adjMat [][]int } /* コンストラクタ */ func newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat { // 頂点を追加 n := len(vertices) adjMat := make([][]int, n) for i := range adjMat { adjMat[i] = make([]int, n) } // グラフを初期化する g := &graphAdjMat{ vertices: vertices, adjMat: adjMat, } // 辺を追加 // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する for i := range edges { g.addEdge(edges[i][0], edges[i][1]) } return g } /* 頂点数を取得 */ func (g *graphAdjMat) size() int { return len(g.vertices) } /* 頂点を追加 */ func (g *graphAdjMat) addVertex(val int) { n := g.size() // 頂点リストに新しい頂点の値を追加 g.vertices = append(g.vertices, val) // 隣接行列に 1 行追加 newRow := make([]int, n) g.adjMat = append(g.adjMat, newRow) // 隣接行列に 1 列追加 for i := range g.adjMat { g.adjMat[i] = append(g.adjMat[i], 0) } } /* 頂点を削除 */ func (g *graphAdjMat) removeVertex(index int) { if index >= g.size() { return } // 頂点リストから index の頂点を削除する g.vertices = append(g.vertices[:index], g.vertices[index+1:]...) // 隣接行列で index 行を削除する g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...) // 隣接行列で index 列を削除する for i := range g.adjMat { g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...) } } /* 辺を追加 */ // 引数 i, j は vertices の要素インデックスに対応する func (g *graphAdjMat) addEdge(i, j int) { // インデックスの範囲外と等値の処理 if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { fmt.Errorf("%s", "Index Out Of Bounds Exception") } // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす g.adjMat[i][j] = 1 g.adjMat[j][i] = 1 } /* 辺を削除 */ // 引数 i, j は vertices の要素インデックスに対応する func (g *graphAdjMat) removeEdge(i, j int) { // インデックスの範囲外と等値の処理 if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { fmt.Errorf("%s", "Index Out Of Bounds Exception") } g.adjMat[i][j] = 0 g.adjMat[j][i] = 0 } /* 隣接行列を出力 */ func (g *graphAdjMat) print() { fmt.Printf("\t頂点リスト = %v\n", g.vertices) fmt.Printf("\t隣接行列 = \n") for i := range g.adjMat { fmt.Printf("\t\t\t%v\n", g.adjMat[i]) } } ================================================ FILE: ja/codes/go/chapter_graph/graph_adjacency_matrix_test.go ================================================ // File: graph_adjacency_matrix_test.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" ) func TestGraphAdjMat(t *testing.T) { /* 無向グラフを初期化 */ // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 vertices := []int{1, 3, 2, 5, 4} edges := [][]int{{0, 1}, {1, 2}, {2, 3}, {0, 3}, {2, 4}, {3, 4}} graph := newGraphAdjMat(vertices, edges) fmt.Println("初期化後、グラフは:") graph.print() /* 辺を追加 */ // 頂点 1, 2 のインデックスはそれぞれ 0, 2 graph.addEdge(0, 2) fmt.Println("辺 1-2 を追加後、グラフは") graph.print() /* 辺を削除 */ // 頂点 1, 3 のインデックスはそれぞれ 0, 1 graph.removeEdge(0, 1) fmt.Println("辺 1-3 を削除後、グラフは") graph.print() /* 頂点を追加 */ graph.addVertex(6) fmt.Println("頂点 6 を追加後、グラフは") graph.print() /* 頂点を削除 */ // 頂点 3 のインデックスは 1 graph.removeVertex(1) fmt.Println("頂点 3 を削除後、グラフは") graph.print() } ================================================ FILE: ja/codes/go/chapter_graph/graph_bfs.go ================================================ // File: graph_bfs.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( . "github.com/krahets/hello-algo/pkg" ) /* 幅優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする func graphBFS(g *graphAdjList, startVet Vertex) []Vertex { // 頂点の走査順序 res := make([]Vertex, 0) // 訪問済み頂点を記録するためのハッシュ集合 visited := make(map[Vertex]struct{}) visited[startVet] = struct{}{} // キューは BFS の実装に用い、スライスでキューをシミュレートする queue := make([]Vertex, 0) queue = append(queue, startVet) // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す for len(queue) > 0 { // 先頭の頂点をデキュー vet := queue[0] queue = queue[1:] // 訪問した頂点を記録 res = append(res, vet) // この頂点のすべての隣接頂点を走査 for _, adjVet := range g.adjList[vet] { _, isExist := visited[adjVet] // 未訪問の頂点のみをキューに追加 if !isExist { queue = append(queue, adjVet) visited[adjVet] = struct{}{} } } } // 頂点の走査順を返す return res } ================================================ FILE: ja/codes/go/chapter_graph/graph_bfs_test.go ================================================ // File: graph_bfs_test.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphBFS(t *testing.T) { /* 無向グラフを初期化 */ vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) edges := [][]Vertex{ {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[1], vets[4]}, {vets[2], vets[5]}, {vets[3], vets[4]}, {vets[3], vets[6]}, {vets[4], vets[5]}, {vets[4], vets[7]}, {vets[5], vets[8]}, {vets[6], vets[7]}, {vets[7], vets[8]}} graph := newGraphAdjList(edges) fmt.Println("初期化後、グラフは:") graph.print() /* 幅優先探索 */ res := graphBFS(graph, vets[0]) fmt.Println("幅優先探索(BFS)の頂点列:") PrintSlice(VetsToVals(res)) } ================================================ FILE: ja/codes/go/chapter_graph/graph_dfs.go ================================================ // File: graph_dfs.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( . "github.com/krahets/hello-algo/pkg" ) /* 深さ優先走査の補助関数 */ func dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) { // append 操作は新しい参照を返すため、元の参照を新しい slice の参照で再代入する必要がある *res = append(*res, vet) visited[vet] = struct{}{} // この頂点のすべての隣接頂点を走査 for _, adjVet := range g.adjList[vet] { _, isExist := visited[adjVet] // 隣接頂点を再帰的に訪問 if !isExist { dfs(g, visited, res, adjVet) } } } /* 深さ優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする func graphDFS(g *graphAdjList, startVet Vertex) []Vertex { // 頂点の走査順序 res := make([]Vertex, 0) // 訪問済み頂点を記録するためのハッシュ集合 visited := make(map[Vertex]struct{}) dfs(g, visited, &res, startVet) // 頂点の走査順を返す return res } ================================================ FILE: ja/codes/go/chapter_graph/graph_dfs_test.go ================================================ // File: graph_dfs_test.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphDFS(t *testing.T) { /* 無向グラフを初期化 */ vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6}) edges := [][]Vertex{ {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[2], vets[5]}, {vets[4], vets[5]}, {vets[5], vets[6]}} graph := newGraphAdjList(edges) fmt.Println("初期化後、グラフは:") graph.print() /* 深さ優先探索 */ res := graphDFS(graph, vets[0]) fmt.Println("深さ優先探索(DFS)の頂点列:") PrintSlice(VetsToVals(res)) } ================================================ FILE: ja/codes/go/chapter_greedy/coin_change_greedy.go ================================================ // File: coin_change_greedy.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy /* コイン交換:貪欲法 */ func coinChangeGreedy(coins []int, amt int) int { // coins リストはソート済みと仮定する i := len(coins) - 1 count := 0 // 残額がなくなるまで貪欲選択を繰り返す for amt > 0 { // 残額以下で最も近い硬貨を見つける for i > 0 && coins[i] > amt { i-- } // coins[i] を選択する amt -= coins[i] count++ } // 実行可能な解が見つからなければ -1 を返す if amt != 0 { return -1 } return count } ================================================ FILE: ja/codes/go/chapter_greedy/coin_change_greedy_test.go ================================================ // File: coin_change_greedy_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestCoinChangeGreedy(t *testing.T) { // 貪欲法:大域最適解を保証できる coins := []int{1, 5, 10, 20, 50, 100} amt := 186 res := coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("%d を作るのに必要な最小硬貨枚数は %d\n", amt, res) // 貪欲法:大域最適解を保証できない coins = []int{1, 20, 50} amt = 60 res = coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("%d を作るのに必要な最小硬貨枚数は %d\n", amt, res) fmt.Println("実際に必要な最小枚数は 3、つまり 20 + 20 + 20") // 貪欲法:大域最適解を保証できない coins = []int{1, 49, 50} amt = 98 res = coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("%d を作るのに必要な最小硬貨枚数は %d\n", amt, res) fmt.Println("実際に必要な最小枚数は 2、つまり 49 + 49") } ================================================ FILE: ja/codes/go/chapter_greedy/fractional_knapsack.go ================================================ // File: fractional_knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "sort" /* 品物 */ type Item struct { w int // 品物の重さ v int // 品物の価値 } /* 分数ナップサック:貪欲法 */ func fractionalKnapsack(wgt []int, val []int, cap int) float64 { // 重さと価値の 2 属性を持つ品物リストを作成 items := make([]Item, len(wgt)) for i := 0; i < len(wgt); i++ { items[i] = Item{wgt[i], val[i]} } // 単位価値 item.v / item.w の高い順にソートする sort.Slice(items, func(i, j int) bool { return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w) }) // 貪欲選択を繰り返す res := 0.0 for _, item := range items { if item.w <= cap { // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる res += float64(item.v) cap -= item.w } else { // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる res += float64(item.v) / float64(item.w) * float64(cap) // 残り容量がないため、ループを抜ける break } } return res } ================================================ FILE: ja/codes/go/chapter_greedy/fractional_knapsack_test.go ================================================ // File: fractional_knapsack_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestFractionalKnapsack(t *testing.T) { wgt := []int{10, 20, 30, 40, 50} val := []int{50, 120, 150, 210, 240} capacity := 50 // 貪欲法 res := fractionalKnapsack(wgt, val, capacity) fmt.Println("ナップサック容量を超えない最大価値は", res) } ================================================ FILE: ja/codes/go/chapter_greedy/max_capacity.go ================================================ // File: max_capacity.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "math" /* 最大容量:貪欲法 */ func maxCapacity(ht []int) int { // i, j を初期化し、それぞれ配列の両端に置く i, j := 0, len(ht)-1 // 初期の最大容量は 0 res := 0 // 2 枚の板が出会うまで貪欲選択を繰り返す for i < j { // 最大容量を更新する capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i) res = int(math.Max(float64(res), float64(capacity))) // 短い方を内側へ動かす if ht[i] < ht[j] { i++ } else { j-- } } return res } ================================================ FILE: ja/codes/go/chapter_greedy/max_capacity_test.go ================================================ // File: max_capacity_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestMaxCapacity(t *testing.T) { ht := []int{3, 8, 5, 2, 7, 7, 3, 4} // 貪欲法 res := maxCapacity(ht) fmt.Println("最大容量は", res) } ================================================ FILE: ja/codes/go/chapter_greedy/max_product_cutting.go ================================================ // File: max_product_cutting.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "math" /* 最大切断積:貪欲法 */ func maxProductCutting(n int) int { // n <= 3 のときは、必ず 1 を切り出す if n <= 3 { return 1 * (n - 1) } // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする a := n / 3 b := n % 3 if b == 1 { // 余りが 1 のときは、1 * 3 を 2 * 2 に変える return int(math.Pow(3, float64(a-1))) * 2 * 2 } if b == 2 { // 余りが 2 のときは、そのままにする return int(math.Pow(3, float64(a))) * 2 } // 余りが 0 のときは、そのままにする return int(math.Pow(3, float64(a))) } ================================================ FILE: ja/codes/go/chapter_greedy/max_product_cutting_test.go ================================================ // File: max_product_cutting_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestMaxProductCutting(t *testing.T) { n := 58 // 貪欲法 res := maxProductCutting(n) fmt.Println("最大分割積は", res) } ================================================ FILE: ja/codes/go/chapter_hashing/array_hash_map.go ================================================ // File: array_hash_map.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import "fmt" /* キーと値の組 */ type pair struct { key int val string } /* 配列ベースのハッシュテーブル */ type arrayHashMap struct { buckets []*pair } /* ハッシュテーブルを初期化 */ func newArrayHashMap() *arrayHashMap { // 100 個のバケットを含む配列を初期化 buckets := make([]*pair, 100) return &arrayHashMap{buckets: buckets} } /* ハッシュ関数 */ func (a *arrayHashMap) hashFunc(key int) int { index := key % 100 return index } /* 検索操作 */ func (a *arrayHashMap) get(key int) string { index := a.hashFunc(key) pair := a.buckets[index] if pair == nil { return "Not Found" } return pair.val } /* 追加操作 */ func (a *arrayHashMap) put(key int, val string) { pair := &pair{key: key, val: val} index := a.hashFunc(key) a.buckets[index] = pair } /* 削除操作 */ func (a *arrayHashMap) remove(key int) { index := a.hashFunc(key) // nil に設定し、削除を表す a.buckets[index] = nil } /* すべてのキーのペアを取得する */ func (a *arrayHashMap) pairSet() []*pair { var pairs []*pair for _, pair := range a.buckets { if pair != nil { pairs = append(pairs, pair) } } return pairs } /* すべてのキーを取得 */ func (a *arrayHashMap) keySet() []int { var keys []int for _, pair := range a.buckets { if pair != nil { keys = append(keys, pair.key) } } return keys } /* すべての値を取得 */ func (a *arrayHashMap) valueSet() []string { var values []string for _, pair := range a.buckets { if pair != nil { values = append(values, pair.val) } } return values } /* ハッシュテーブルを出力 */ func (a *arrayHashMap) print() { for _, pair := range a.buckets { if pair != nil { fmt.Println(pair.key, "->", pair.val) } } } ================================================ FILE: ja/codes/go/chapter_hashing/array_hash_map_test.go ================================================ // File: array_hash_map_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "testing" ) func TestArrayHashMap(t *testing.T) { /* ハッシュテーブルを初期化 */ hmap := newArrayHashMap() /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 hmap.put(12836, "シャオハー") hmap.put(15937, "シャオルオ") hmap.put(16750, "シャオスワン") hmap.put(13276, "シャオファー") hmap.put(10583, "シャオヤ") fmt.Println("\n追加後、ハッシュ表は\nKey -> Value") hmap.print() /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 name := hmap.get(15937) fmt.Println("\n学籍番号 15937 を入力し、見つかった名前は " + name) /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 hmap.remove(10583) fmt.Println("\n10583 を削除後、ハッシュ表は\nKey -> Value") hmap.print() /* ハッシュテーブルを走査 */ fmt.Println("\nキーと値の組 Key->Value を走査") for _, kv := range hmap.pairSet() { fmt.Println(kv.key, " -> ", kv.val) } fmt.Println("\nキー Key を個別に走査") for _, key := range hmap.keySet() { fmt.Println(key) } fmt.Println("\n値 Value を個別に走査") for _, val := range hmap.valueSet() { fmt.Println(val) } } ================================================ FILE: ja/codes/go/chapter_hashing/hash_collision_test.go ================================================ // File: hash_collision_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "testing" ) func TestHashMapChaining(t *testing.T) { /* ハッシュテーブルを初期化 */ hmap := newHashMapChaining() /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 hmap.put(12836, "シャオハー") hmap.put(15937, "シャオルオ") hmap.put(16750, "シャオスワン") hmap.put(13276, "シャオファー") hmap.put(10583, "シャオヤ") fmt.Println("\n追加後、ハッシュ表は\nKey -> Value") hmap.print() /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 name := hmap.get(15937) fmt.Println("\n学籍番号 15937 を入力し、見つかった名前は", name) /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 hmap.remove(12836) fmt.Println("\n12836 を削除後、ハッシュ表は\nKey -> Value") hmap.print() } func TestHashMapOpenAddressing(t *testing.T) { /* ハッシュテーブルを初期化 */ hmap := newHashMapOpenAddressing() /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 hmap.put(12836, "シャオハー") hmap.put(15937, "シャオルオ") hmap.put(16750, "シャオスワン") hmap.put(13276, "シャオファー") hmap.put(10583, "シャオヤ") fmt.Println("\n追加後、ハッシュ表は\nKey -> Value") hmap.print() /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 name := hmap.get(13276) fmt.Println("\n学籍番号 13276 を入力し、見つかった名前は ", name) /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 hmap.remove(16750) fmt.Println("\n16750 を削除後、ハッシュ表は\nKey -> Value") hmap.print() } ================================================ FILE: ja/codes/go/chapter_hashing/hash_map_chaining.go ================================================ // File: hash_map_chaining.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import ( "fmt" "strconv" "strings" ) /* チェイン法ハッシュテーブル */ type hashMapChaining struct { size int // キーと値のペア数 capacity int // ハッシュテーブル容量 loadThres float64 // リサイズを発動する負荷率のしきい値 extendRatio int // 拡張倍率 buckets [][]pair // バケット配列 } /* コンストラクタ */ func newHashMapChaining() *hashMapChaining { buckets := make([][]pair, 4) for i := 0; i < 4; i++ { buckets[i] = make([]pair, 0) } return &hashMapChaining{ size: 0, capacity: 4, loadThres: 2.0 / 3.0, extendRatio: 2, buckets: buckets, } } /* ハッシュ関数 */ func (m *hashMapChaining) hashFunc(key int) int { return key % m.capacity } /* 負荷率 */ func (m *hashMapChaining) loadFactor() float64 { return float64(m.size) / float64(m.capacity) } /* 検索操作 */ func (m *hashMapChaining) get(key int) string { idx := m.hashFunc(key) bucket := m.buckets[idx] // バケットを走査し、key が見つかれば対応する val を返す for _, p := range bucket { if p.key == key { return p.val } } // key が見つからない場合は空文字列を返す return "" } /* 追加操作 */ func (m *hashMapChaining) put(key int, val string) { // 負荷率がしきい値を超えたら、リサイズを実行 if m.loadFactor() > m.loadThres { m.extend() } idx := m.hashFunc(key) // バケットを走査し、指定した key が見つかれば対応する val を更新して返す for i := range m.buckets[idx] { if m.buckets[idx][i].key == key { m.buckets[idx][i].val = val return } } // その key が存在しなければ、キーと値のペアを末尾に追加 p := pair{ key: key, val: val, } m.buckets[idx] = append(m.buckets[idx], p) m.size += 1 } /* 削除操作 */ func (m *hashMapChaining) remove(key int) { idx := m.hashFunc(key) // バケットを走査してキーと値のペアを削除 for i, p := range m.buckets[idx] { if p.key == key { // スライスから削除する m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...) m.size -= 1 break } } } /* ハッシュテーブルを拡張 */ func (m *hashMapChaining) extend() { // 元のハッシュテーブルを一時保存 tmpBuckets := make([][]pair, len(m.buckets)) for i := 0; i < len(m.buckets); i++ { tmpBuckets[i] = make([]pair, len(m.buckets[i])) copy(tmpBuckets[i], m.buckets[i]) } // リサイズ後の新しいハッシュテーブルを初期化 m.capacity *= m.extendRatio m.buckets = make([][]pair, m.capacity) for i := 0; i < m.capacity; i++ { m.buckets[i] = make([]pair, 0) } m.size = 0 // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for _, bucket := range tmpBuckets { for _, p := range bucket { m.put(p.key, p.val) } } } /* ハッシュテーブルを出力 */ func (m *hashMapChaining) print() { var builder strings.Builder for _, bucket := range m.buckets { builder.WriteString("[") for _, p := range bucket { builder.WriteString(strconv.Itoa(p.key) + " -> " + p.val + " ") } builder.WriteString("]") fmt.Println(builder.String()) builder.Reset() } } ================================================ FILE: ja/codes/go/chapter_hashing/hash_map_open_addressing.go ================================================ // File: hash_map_open_addressing.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import ( "fmt" ) /* オープンアドレス法ハッシュテーブル */ type hashMapOpenAddressing struct { size int // キーと値のペア数 capacity int // ハッシュテーブル容量 loadThres float64 // リサイズを発動する負荷率のしきい値 extendRatio int // 拡張倍率 buckets []*pair // バケット配列 TOMBSTONE *pair // 削除済みマーク } /* コンストラクタ */ func newHashMapOpenAddressing() *hashMapOpenAddressing { return &hashMapOpenAddressing{ size: 0, capacity: 4, loadThres: 2.0 / 3.0, extendRatio: 2, buckets: make([]*pair, 4), TOMBSTONE: &pair{-1, "-1"}, } } /* ハッシュ関数 */ func (h *hashMapOpenAddressing) hashFunc(key int) int { return key % h.capacity // キーに基づいてハッシュ値を計算 } /* 負荷率 */ func (h *hashMapOpenAddressing) loadFactor() float64 { return float64(h.size) / float64(h.capacity) // 現在の負荷率を計算 } /* key に対応するバケットインデックスを探す */ func (h *hashMapOpenAddressing) findBucket(key int) int { index := h.hashFunc(key) // 初期インデックスを取得 firstTombstone := -1 // 最初に遭遇した `TOMBSTONE` の位置を記録する for h.buckets[index] != nil { if h.buckets[index].key == key { if firstTombstone != -1 { // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 h.buckets[firstTombstone] = h.buckets[index] h.buckets[index] = h.TOMBSTONE return firstTombstone // 移動後のバケットインデックスを返す } return index // 見つかったインデックスを返す } if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE { firstTombstone = index // 最初に遭遇した削除マークの位置を記録する } index = (index + 1) % h.capacity // 線形探索を行い、末尾を越えたら先頭に戻る } // key が存在しない場合は追加位置のインデックスを返す if firstTombstone != -1 { return firstTombstone } return index } /* 検索操作 */ func (h *hashMapOpenAddressing) get(key int) string { index := h.findBucket(key) // key に対応するバケットインデックスを探す if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { return h.buckets[index].val // キーと値の組が見つかったら、対応する val を返す } return "" // キーと値のペアが存在しない場合は `""` を返す } /* 追加操作 */ func (h *hashMapOpenAddressing) put(key int, val string) { if h.loadFactor() > h.loadThres { h.extend() // 負荷率がしきい値を超えたら、リサイズを実行 } index := h.findBucket(key) // key に対応するバケットインデックスを探す if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE { h.buckets[index] = &pair{key, val} // キーと値の組が存在しない場合は、その組を追加する h.size++ } else { h.buckets[index].val = val // キーと値のペアが見つかった場合は、`val` を上書きする } } /* 削除操作 */ func (h *hashMapOpenAddressing) remove(key int) { index := h.findBucket(key) // key に対応するバケットインデックスを探す if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { h.buckets[index] = h.TOMBSTONE // キーと値の組が見つかったら、削除マーカーで上書きする h.size-- } } /* ハッシュテーブルを拡張 */ func (h *hashMapOpenAddressing) extend() { oldBuckets := h.buckets // 元のハッシュテーブルを一時保存 h.capacity *= h.extendRatio // 容量を更新 h.buckets = make([]*pair, h.capacity) // リサイズ後の新しいハッシュテーブルを初期化 h.size = 0 // サイズをリセットする // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for _, pair := range oldBuckets { if pair != nil && pair != h.TOMBSTONE { h.put(pair.key, pair.val) } } } /* ハッシュテーブルを出力 */ func (h *hashMapOpenAddressing) print() { for _, pair := range h.buckets { if pair == nil { fmt.Println("nil") } else if pair == h.TOMBSTONE { fmt.Println("TOMBSTONE") } else { fmt.Printf("%d -> %s\n", pair.key, pair.val) } } } ================================================ FILE: ja/codes/go/chapter_hashing/hash_map_test.go ================================================ // File: hash_map_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHashMap(t *testing.T) { /* ハッシュテーブルを初期化 */ hmap := make(map[int]string) /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 hmap[12836] = "シャオハー" hmap[15937] = "シャオルオ" hmap[16750] = "シャオスワン" hmap[13276] = "シャオファー" hmap[10583] = "シャオヤ" fmt.Println("\n追加後、ハッシュ表は\nKey -> Value") PrintMap(hmap) /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 name := hmap[15937] fmt.Println("\n学籍番号 15937 を入力し、見つかった名前は ", name) /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 delete(hmap, 10583) fmt.Println("\n10583 を削除後、ハッシュ表は\nKey -> Value") PrintMap(hmap) /* ハッシュテーブルを走査 */ // キーと値の組 key->value を走査する fmt.Println("\nキーと値の組 Key->Value を走査") for key, value := range hmap { fmt.Println(key, "->", value) } // キー key のみを走査する fmt.Println("\nキー Key を個別に走査") for key := range hmap { fmt.Println(key) } // 値 value のみを走査する fmt.Println("\n値 Value を個別に走査") for _, value := range hmap { fmt.Println(value) } } func TestSimpleHash(t *testing.T) { var hash int key := "Hello アルゴリズム" hash = addHash(key) fmt.Println("加算ハッシュ値は " + strconv.Itoa(hash)) hash = mulHash(key) fmt.Println("乗算ハッシュ値は " + strconv.Itoa(hash)) hash = xorHash(key) fmt.Println("排他的論理和ハッシュ値は " + strconv.Itoa(hash)) hash = rotHash(key) fmt.Println("回転ハッシュ値は " + strconv.Itoa(hash)) } ================================================ FILE: ja/codes/go/chapter_hashing/simple_hash.go ================================================ // File: simple_hash.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import "fmt" /* 加算ハッシュ */ func addHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = (hash + int64(b)) % modulus } return int(hash) } /* 乗算ハッシュ */ func mulHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = (31*hash + int64(b)) % modulus } return int(hash) } /* XOR ハッシュ */ func xorHash(key string) int { hash := 0 modulus := 1000000007 for _, b := range []byte(key) { fmt.Println(int(b)) hash ^= int(b) hash = (31*hash + int(b)) % modulus } return hash & modulus } /* 回転ハッシュ */ func rotHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus } return int(hash) } ================================================ FILE: ja/codes/go/chapter_heap/heap.go ================================================ // File: heap.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap // Go では heap.Interface を実装することで整数の最大ヒープを構築できる // heap.Interface を実装するには、同時に sort.Interface も実装する必要がある type intHeap []any // Push は heap.Interface の関数で、要素をヒープに追加する func (h *intHeap) Push(x any) { // Push と Pop は pointer receiver を使う // これらはスライスの内容を調整するだけでなく、スライスの長さも変更するためである。 *h = append(*h, x.(int)) } // Pop は heap.Interface の関数で、ヒープの先頭要素を取り出す func (h *intHeap) Pop() any { // ヒープから取り出す要素を末尾に置く last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Len は sort.Interface の関数 func (h *intHeap) Len() int { return len(*h) } // Less は sort.Interface の関数 func (h *intHeap) Less(i, j int) bool { // 最小ヒープを実装する場合は、不等号を < に調整する return (*h)[i].(int) > (*h)[j].(int) } // Swap は sort.Interface の関数 func (h *intHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Top ヒープ先頭要素を取得 func (h *intHeap) Top() any { return (*h)[0] } ================================================ FILE: ja/codes/go/chapter_heap/heap_test.go ================================================ // File: heap_test.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap import ( "container/heap" "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func testPush(h *intHeap, val int) { // heap.Interface の関数を呼び出して要素を追加する heap.Push(h, val) fmt.Printf("\n要素 %d をヒープに追加後 \n", val) PrintHeap(*h) } func testPop(h *intHeap) { // heap.Interface の関数を呼び出して要素を削除する val := heap.Pop(h) fmt.Printf("\nヒープ先頭要素 %d を取り出した後 \n", val) PrintHeap(*h) } func TestHeap(t *testing.T) { /* ヒープを初期化 */ // 最大ヒープを初期化 maxHeap := &intHeap{} heap.Init(maxHeap) /* 要素をヒープに追加 */ testPush(maxHeap, 1) testPush(maxHeap, 3) testPush(maxHeap, 2) testPush(maxHeap, 5) testPush(maxHeap, 4) /* ヒープ頂点の要素を取得 */ top := maxHeap.Top() fmt.Printf("ヒープ先頭要素は %d\n", top) /* ヒープ頂点の要素を取り出す */ testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) /* ヒープのサイズを取得 */ size := len(*maxHeap) fmt.Printf("ヒープの要素数は %d\n", size) /* ヒープが空かどうかを判定 */ isEmpty := len(*maxHeap) == 0 fmt.Printf("ヒープが空か %t\n", isEmpty) } func TestMyHeap(t *testing.T) { /* ヒープを初期化 */ // 最大ヒープを初期化 maxHeap := newMaxHeap([]any{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}) fmt.Printf("入力配列からヒープを構築した後\n") maxHeap.print() /* ヒープ頂点の要素を取得 */ peek := maxHeap.peek() fmt.Printf("\nヒープ先頭要素は %d\n", peek) /* 要素をヒープに追加 */ val := 7 maxHeap.push(val) fmt.Printf("\n要素 %d をヒープに追加した後\n", val) maxHeap.print() /* ヒープ頂点の要素を取り出す */ peek = maxHeap.pop() fmt.Printf("\nヒープ先頭要素 %d を取り出した後\n", peek) maxHeap.print() /* ヒープのサイズを取得 */ size := maxHeap.size() fmt.Printf("\nヒープの要素数は %d\n", size) /* ヒープが空かどうかを判定 */ isEmpty := maxHeap.isEmpty() fmt.Printf("\nヒープが空か %t\n", isEmpty) } func TestTopKHeap(t *testing.T) { /* ヒープを初期化 */ // 最大ヒープを初期化 nums := []int{1, 7, 6, 3, 2} k := 3 res := topKHeap(nums, k) fmt.Printf("最大の " + strconv.Itoa(k) + " 個の要素は") PrintHeap(*res) } ================================================ FILE: ja/codes/go/chapter_heap/my_heap.go ================================================ // File: my_heap.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap import ( "fmt" . "github.com/krahets/hello-algo/pkg" ) type maxHeap struct { // 配列ではなくスライスを使うことで、拡張を考慮せずに済む data []any } /* コンストラクタ。空のヒープを作成する */ func newHeap() *maxHeap { return &maxHeap{ data: make([]any, 0), } } /* コンストラクタ。スライスからヒープを構築する */ func newMaxHeap(nums []any) *maxHeap { // リスト要素をそのままヒープに追加 h := &maxHeap{data: nums} for i := h.parent(len(h.data) - 1); i >= 0; i-- { // 葉ノード以外のすべてのノードをヒープ化 h.siftDown(i) } return h } /* 左子ノードのインデックスを取得 */ func (h *maxHeap) left(i int) int { return 2*i + 1 } /* 右子ノードのインデックスを取得 */ func (h *maxHeap) right(i int) int { return 2*i + 2 } /* 親ノードのインデックスを取得 */ func (h *maxHeap) parent(i int) int { // 切り捨て除算 return (i - 1) / 2 } /* 要素を交換 */ func (h *maxHeap) swap(i, j int) { h.data[i], h.data[j] = h.data[j], h.data[i] } /* ヒープのサイズを取得 */ func (h *maxHeap) size() int { return len(h.data) } /* ヒープが空かどうかを判定 */ func (h *maxHeap) isEmpty() bool { return len(h.data) == 0 } /* ヒープ先頭要素にアクセス */ func (h *maxHeap) peek() any { return h.data[0] } /* 要素をヒープに追加 */ func (h *maxHeap) push(val any) { // ノードを追加 h.data = append(h.data, val) // 下から上へヒープ化 h.siftUp(len(h.data) - 1) } /* ノード i から始めて、下から上へヒープ化 */ func (h *maxHeap) siftUp(i int) { for true { // ノード i の親ノードを取得 p := h.parent(i) // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 if p < 0 || h.data[i].(int) <= h.data[p].(int) { break } // 2 つのノードを交換 h.swap(i, p) // ループで下から上へヒープ化 i = p } } /* 要素をヒープから取り出す */ func (h *maxHeap) pop() any { // 空判定の処理 if h.isEmpty() { fmt.Println("error") return nil } // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) h.swap(0, h.size()-1) // ノードを削除 val := h.data[len(h.data)-1] h.data = h.data[:len(h.data)-1] // 上から下へヒープ化 h.siftDown(0) // ヒープ先頭要素を返す return val } /* ノード i から始めて、上から下へヒープ化 */ func (h *maxHeap) siftDown(i int) { for true { // ノード i, l, r のうち値が最大のノードを max とする l, r, max := h.left(i), h.right(i), i if l < h.size() && h.data[l].(int) > h.data[max].(int) { max = l } if r < h.size() && h.data[r].(int) > h.data[max].(int) { max = r } // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if max == i { break } // 2 つのノードを交換 h.swap(i, max) // ループで上から下へヒープ化 i = max } } /* ヒープ(二分木)を出力 */ func (h *maxHeap) print() { PrintHeap(h.data) } ================================================ FILE: ja/codes/go/chapter_heap/top_k.go ================================================ // File: top_k.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_heap import "container/heap" type minHeap []any func (h *minHeap) Len() int { return len(*h) } func (h *minHeap) Less(i, j int) bool { return (*h)[i].(int) < (*h)[j].(int) } func (h *minHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Push は heap.Interface のメソッドで、要素をヒープに追加する func (h *minHeap) Push(x any) { *h = append(*h, x.(int)) } // Pop は heap.Interface のメソッドで、ヒープの先頭要素を取り出す func (h *minHeap) Pop() any { // ヒープから取り出す要素を末尾に置く last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Top ヒープ先頭要素を取得 func (h *minHeap) Top() any { return (*h)[0] } /* ヒープに基づいて配列中の最大の k 個の要素を探す */ func topKHeap(nums []int, k int) *minHeap { // 最小ヒープを初期化 h := &minHeap{} heap.Init(h) // 配列の先頭 k 個の要素をヒープに追加 for i := 0; i < k; i++ { heap.Push(h, nums[i]) } // k+1 番目の要素から開始し、ヒープ長を k に保つ for i := k; i < len(nums); i++ { // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する if nums[i] > h.Top().(int) { heap.Pop(h) heap.Push(h, nums[i]) } } return h } ================================================ FILE: ja/codes/go/chapter_searching/binary_search.go ================================================ // File: binary_search.go // Created Time: 2022-12-05 // Author: Slone123c (274325721@qq.com) package chapter_searching /* 二分探索(両閉区間) */ func binarySearch(nums []int, target int) int { // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す i, j := 0, len(nums)-1 // ループし、探索区間が空になったら終了する(i > j で空) for i <= j { m := i + (j-i)/2 // 中点インデックス m を計算 if nums[m] < target { // この場合、target は区間 [m+1, j] にある i = m + 1 } else if nums[m] > target { // この場合、target は区間 [i, m-1] にある j = m - 1 } else { // 目標要素が見つかったらそのインデックスを返す return m } } // 目標要素が見つからなければ -1 を返す return -1 } /* 二分探索(左閉右開区間) */ func binarySearchLCRO(nums []int, target int) int { // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す i, j := 0, len(nums) // ループし、探索区間が空になったら終了する(i = j で空) for i < j { m := i + (j-i)/2 // 中点インデックス m を計算 if nums[m] < target { // この場合、target は区間 [m+1, j) にある i = m + 1 } else if nums[m] > target { // この場合、target は区間 [i, m) にある j = m } else { // 目標要素が見つかったらそのインデックスを返す return m } } // 目標要素が見つからなければ -1 を返す return -1 } ================================================ FILE: ja/codes/go/chapter_searching/binary_search_edge.go ================================================ // File: binary_search_edge.go // Created Time: 2023-08-23 // Author: Reanon (793584285@qq.com) package chapter_searching /* 最も左の target を二分探索 */ func binarySearchLeftEdge(nums []int, target int) int { // target の挿入位置を探すのと等価 i := binarySearchInsertion(nums, target) // target が見つからなければ、-1 を返す if i == len(nums) || nums[i] != target { return -1 } // target が見つかったら、インデックス i を返す return i } /* 最も右の target を二分探索 */ func binarySearchRightEdge(nums []int, target int) int { // 最左の target + 1 を探す問題に変換する i := binarySearchInsertion(nums, target+1) // j は最も右の target を指し、i は target より大きい最初の要素を指す j := i - 1 // target が見つからなければ、-1 を返す if j == -1 || nums[j] != target { return -1 } // target が見つかったら、インデックス j を返す return j } ================================================ FILE: ja/codes/go/chapter_searching/binary_search_insertion.go ================================================ // File: binary_search_insertion.go // Created Time: 2023-08-23 // Author: Reanon (793584285@qq.com) package chapter_searching /* 二分探索で挿入位置を探す(重複要素なし) */ func binarySearchInsertionSimple(nums []int, target int) int { // 両閉区間 [0, n-1] を初期化 i, j := 0, len(nums)-1 for i <= j { // 中点インデックス m を計算 m := i + (j-i)/2 if nums[m] < target { // target は区間 [m+1, j] にある i = m + 1 } else if nums[m] > target { // target は区間 [i, m-1] にある j = m - 1 } else { // target が見つかったら、挿入位置 m を返す return m } } // target が見つからなければ、挿入位置 i を返す return i } /* 二分探索で挿入位置を探す(重複要素あり) */ func binarySearchInsertion(nums []int, target int) int { // 両閉区間 [0, n-1] を初期化 i, j := 0, len(nums)-1 for i <= j { // 中点インデックス m を計算 m := i + (j-i)/2 if nums[m] < target { // target は区間 [m+1, j] にある i = m + 1 } else if nums[m] > target { // target は区間 [i, m-1] にある j = m - 1 } else { // target より小さい最初の要素は区間 [i, m-1] にある j = m - 1 } } // 挿入位置 i を返す return i } ================================================ FILE: ja/codes/go/chapter_searching/binary_search_test.go ================================================ // File: binary_search_test.go // Created Time: 2022-12-05 // Author: Slone123c (274325721@qq.com) package chapter_searching import ( "fmt" "testing" ) func TestBinarySearch(t *testing.T) { var ( target = 6 nums = []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} expected = 2 ) // 配列で二分探索を実行 actual := binarySearch(nums, target) fmt.Println("対象要素 6 のインデックス =", actual) if actual != expected { t.Errorf("対象要素 6 のインデックス = %d, 想定値は %d", actual, expected) } } func TestBinarySearchEdge(t *testing.T) { // 重複要素を含む配列 nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} fmt.Println("\n配列 nums = ", nums) // 二分探索で左端と右端を探す for _, target := range []int{6, 7} { index := binarySearchLeftEdge(nums, target) fmt.Println("最も左の要素", target, "のインデックスは", index) index = binarySearchRightEdge(nums, target) fmt.Println("最も右の要素", target, "のインデックスは", index) } } func TestBinarySearchInsertion(t *testing.T) { // 重複要素のない配列 nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} fmt.Println("配列 nums =", nums) // 二分探索で挿入位置を探す for _, target := range []int{6, 9} { index := binarySearchInsertionSimple(nums, target) fmt.Println("要素", target, "の挿入位置のインデックスは", index) } // 重複要素を含む配列 nums = []int{1, 3, 6, 6, 6, 6, 6, 10, 12, 15} fmt.Println("\n配列 nums =", nums) // 二分探索で挿入位置を探す for _, target := range []int{2, 6, 20} { index := binarySearchInsertion(nums, target) fmt.Println("要素", target, "の挿入位置のインデックスは", index) } } ================================================ FILE: ja/codes/go/chapter_searching/hashing_search.go ================================================ // File: hashing_search.go // Created Time: 2022-12-12 // Author: Slone123c (274325721@qq.com) package chapter_searching import . "github.com/krahets/hello-algo/pkg" /* ハッシュ探索(配列) */ func hashingSearchArray(m map[int]int, target int) int { // ハッシュテーブルの key: 目標要素、value: インデックス // ハッシュテーブルにこの key がなければ -1 を返す if index, ok := m[target]; ok { return index } else { return -1 } } /* ハッシュ探索(連結リスト) */ func hashingSearchLinkedList(m map[int]*ListNode, target int) *ListNode { // ハッシュテーブルの key: 対象ノードの値、value: ノードオブジェクト // ハッシュテーブルにその key がなければ nil を返す if node, ok := m[target]; ok { return node } else { return nil } } ================================================ FILE: ja/codes/go/chapter_searching/hashing_search_test.go ================================================ // File: hashing_search_test.go // Created Time: 2022-12-12 // Author: Slone123c (274325721@qq.com) package chapter_searching import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHashingSearch(t *testing.T) { target := 3 /* ハッシュ探索(配列) */ nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} // ハッシュテーブルを初期化 m := make(map[int]int) for i := 0; i < len(nums); i++ { m[nums[i]] = i } index := hashingSearchArray(m, target) fmt.Println("対象要素 3 のインデックス = ", index) /* ハッシュ探索(連結リスト) */ head := ArrayToLinkedList(nums) // ハッシュテーブルを初期化 m1 := make(map[int]*ListNode) for head != nil { m1[head.Val] = head head = head.Next } node := hashingSearchLinkedList(m1, target) fmt.Println("対象ノード値 3 に対応するノードオブジェクト = ", node) } ================================================ FILE: ja/codes/go/chapter_searching/linear_search.go ================================================ // File: linear_search.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_searching import ( . "github.com/krahets/hello-algo/pkg" ) /* 線形探索(配列) */ func linearSearchArray(nums []int, target int) int { // 配列を走査 for i := 0; i < len(nums); i++ { // 目標要素が見つかったらそのインデックスを返す if nums[i] == target { return i } } // 目標要素が見つからなければ -1 を返す return -1 } /* 線形探索(連結リスト) */ func linearSearchLinkedList(node *ListNode, target int) *ListNode { // 連結リストを走査 for node != nil { // 対象ノードが見つかったら、それを返す if node.Val == target { return node } node = node.Next } // 対象要素が見つからない場合は `nil` を返す return nil } ================================================ FILE: ja/codes/go/chapter_searching/linear_search_test.go ================================================ // File: linear_search_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_searching import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLinearSearch(t *testing.T) { target := 3 nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} // 配列で線形探索を行う index := linearSearchArray(nums, target) fmt.Println("対象要素 3 のインデックス =", index) // 連結リストで線形探索を行う head := ArrayToLinkedList(nums) node := linearSearchLinkedList(head, target) fmt.Println("対象ノード値 3 に対応するノードオブジェクト =", node) } ================================================ FILE: ja/codes/go/chapter_searching/two_sum.go ================================================ // File: two_sum.go // Created Time: 2022-11-25 // Author: reanon (793584285@qq.com) package chapter_searching /* 方法 1:総当たり列挙 */ func twoSumBruteForce(nums []int, target int) []int { size := len(nums) // 2重ループのため、時間計算量は O(n^2) for i := 0; i < size-1; i++ { for j := i + 1; j < size; j++ { if nums[i]+nums[j] == target { return []int{i, j} } } } return nil } /* 方法 2:補助ハッシュテーブル */ func twoSumHashTable(nums []int, target int) []int { // 補助ハッシュテーブルを使用し、空間計算量は O(n) hashTable := map[int]int{} // 単一ループで、時間計算量は O(n) for idx, val := range nums { if preIdx, ok := hashTable[target-val]; ok { return []int{preIdx, idx} } hashTable[val] = idx } return nil } ================================================ FILE: ja/codes/go/chapter_searching/two_sum_test.go ================================================ // File: two_sum_test.go // Created Time: 2022-11-25 // Author: reanon (793584285@qq.com) package chapter_searching import ( "fmt" "testing" ) func TestTwoSum(t *testing.T) { // ======= Test Case ======= nums := []int{2, 7, 11, 15} target := 13 // ====== Driver Code ====== // 方法 1:総当たり法 res := twoSumBruteForce(nums, target) fmt.Println("方法1 res =", res) // 方法2: ハッシュテーブル res = twoSumHashTable(nums, target) fmt.Println("方法2 res =", res) } ================================================ FILE: ja/codes/go/chapter_sorting/bubble_sort.go ================================================ // File: bubble_sort.go // Created Time: 2022-12-06 // Author: Slone123c (274325721@qq.com) package chapter_sorting /* バブルソート */ func bubbleSort(nums []int) { // 外側のループ:未ソート区間は [0, i] for i := len(nums) - 1; i > 0; i-- { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // nums[j] と nums[j + 1] を交換 nums[j], nums[j+1] = nums[j+1], nums[j] } } } } /* バブルソート(フラグ最適化) */ func bubbleSortWithFlag(nums []int) { // 外側のループ:未ソート区間は [0, i] for i := len(nums) - 1; i > 0; i-- { flag := false // フラグを初期化する // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // nums[j] と nums[j + 1] を交換 nums[j], nums[j+1] = nums[j+1], nums[j] flag = true // 交換する要素を記録 } } if flag == false { // このバブル処理で要素交換が一度もなければそのまま終了 break } } } ================================================ FILE: ja/codes/go/chapter_sorting/bubble_sort_test.go ================================================ // File: bubble_sort_test.go // Created Time: 2022-12-06 // Author: Slone123c (274325721@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestBubbleSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} bubbleSort(nums) fmt.Println("バブルソート完了後 nums = ", nums) nums1 := []int{4, 1, 3, 1, 5, 2} bubbleSortWithFlag(nums1) fmt.Println("バブルソート完了後 nums1 = ", nums1) } ================================================ FILE: ja/codes/go/chapter_sorting/bucket_sort.go ================================================ // File: bucket_sort.go // Created Time: 2023-03-27 // Author: Reanon (793584285@qq.com) package chapter_sorting import "sort" /* バケットソート */ func bucketSort(nums []float64) { // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする k := len(nums) / 2 buckets := make([][]float64, k) for i := 0; i < k; i++ { buckets[i] = make([]float64, 0) } // 1. 配列要素を各バケットに振り分ける for _, num := range nums { // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する i := int(num * float64(k)) // num をバケット i に追加 buckets[i] = append(buckets[i], num) } // 2. 各バケットをソートする for i := 0; i < k; i++ { // 組み込みのスライスソート関数を使う。ほかのソートアルゴリズムに置き換えてもよい sort.Float64s(buckets[i]) } // 3. バケットを走査して結果を結合 i := 0 for _, bucket := range buckets { for _, num := range bucket { nums[i] = num i++ } } } ================================================ FILE: ja/codes/go/chapter_sorting/bucket_sort_test.go ================================================ // File: bucket_sort_test.go // Created Time: 2023-03-27 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestBucketSort(t *testing.T) { // 入力データは範囲 [0, 1) の浮動小数点数とする nums := []float64{0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37} bucketSort(nums) fmt.Println("バケットソート完了後 nums = ", nums) } ================================================ FILE: ja/codes/go/chapter_sorting/counting_sort.go ================================================ // File: counting_sort.go // Created Time: 2023-03-20 // Author: Reanon (793584285@qq.com) package chapter_sorting type CountingSort struct{} /* 計数ソート */ // 簡易実装のため、オブジェクトのソートには使えない func countingSortNaive(nums []int) { // 1. 配列の最大要素 m を求める m := 0 for _, num := range nums { if num > m { m = num } } // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す counter := make([]int, m+1) for _, num := range nums { counter[num]++ } // 3. counter を走査し、各要素を元の配列 nums に書き戻す for i, num := 0, 0; num < m+1; num++ { for j := 0; j < counter[num]; j++ { nums[i] = num i++ } } } /* 計数ソート */ // 完全な実装で、オブジェクトをソートでき、かつ安定ソートである func countingSort(nums []int) { // 1. 配列の最大要素 m を求める m := 0 for _, num := range nums { if num > m { m = num } } // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す counter := make([]int, m+1) for _, num := range nums { counter[num]++ } // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する // つまり counter[num]-1 は、num が res に最後に現れるインデックス for i := 0; i < m; i++ { counter[i+1] += counter[i] } // 4. nums を逆順に走査し、各要素を結果配列 res に格納する // 結果を記録するための配列 res を初期化 n := len(nums) res := make([]int, n) for i := n - 1; i >= 0; i-- { num := nums[i] // num を対応するインデックスに配置 res[counter[num]-1] = num // 累積和を 1 減らして、次に num を配置するインデックスを得る counter[num]-- } // 結果配列 res で元の配列 nums を上書きする copy(nums, res) } ================================================ FILE: ja/codes/go/chapter_sorting/counting_sort_test.go ================================================ // File: counting_sort_test.go // Created Time: 2023-03-20 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestCountingSort(t *testing.T) { nums := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} countingSortNaive(nums) fmt.Println("カウントソート(オブジェクトはソート不可)完了後 nums = ", nums) nums1 := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} countingSort(nums1) fmt.Println("カウントソート完了後 nums1 = ", nums1) } ================================================ FILE: ja/codes/go/chapter_sorting/heap_sort.go ================================================ // File: heap_sort.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting /* ヒープの長さは n。ノード i から下方向にヒープ化 */ func siftDown(nums *[]int, n, i int) { for true { // ノード i, l, r のうち値が最大のノードを ma とする l := 2*i + 1 r := 2*i + 2 ma := i if l < n && (*nums)[l] > (*nums)[ma] { ma = l } if r < n && (*nums)[r] > (*nums)[ma] { ma = r } // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if ma == i { break } // 2 つのノードを交換 (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i] // ループで上から下へヒープ化 i = ma } } /* ヒープソート */ func heapSort(nums *[]int) { // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する for i := len(*nums)/2 - 1; i >= 0; i-- { siftDown(nums, len(*nums), i) } // ヒープから最大要素を取り出し、n-1 回繰り返す for i := len(*nums) - 1; i > 0; i-- { // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0] // 根ノードを起点に、上から下へヒープ化 siftDown(nums, i, 0) } } ================================================ FILE: ja/codes/go/chapter_sorting/heap_sort_test.go ================================================ // File: heap_sort_test.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestHeapSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} heapSort(&nums) fmt.Println("ヒープソート完了後 nums = ", nums) } ================================================ FILE: ja/codes/go/chapter_sorting/insertion_sort.go ================================================ // File: insertion_sort.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting /* 挿入ソート */ func insertionSort(nums []int) { // 外側ループ:整列済み区間は [0, i-1] for i := 1; i < len(nums); i++ { base := nums[i] j := i - 1 // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する for j >= 0 && nums[j] > base { nums[j+1] = nums[j] // nums[j] を 1 つ右へ移動する j-- } nums[j+1] = base // base を正しい位置に配置する } } ================================================ FILE: ja/codes/go/chapter_sorting/insertion_sort_test.go ================================================ // File: insertion_sort_test.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) func TestInsertionSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} insertionSort(nums) fmt.Println("挿入ソート完了後 nums =", nums) } ================================================ FILE: ja/codes/go/chapter_sorting/merge_sort.go ================================================ // File: merge_sort.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting /* 左部分配列と右部分配列をマージ */ func merge(nums []int, left, mid, right int) { // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] // マージ結果を格納する一時配列 tmp を作成 tmp := make([]int, right-left+1) // 左右の部分配列の開始インデックスを初期化する i, j, k := left, mid+1, 0 // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする for i <= mid && j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i] i++ } else { tmp[k] = nums[j] j++ } k++ } // 左右の部分配列の残り要素を一時配列にコピーする for i <= mid { tmp[k] = nums[i] i++ k++ } for j <= right { tmp[k] = nums[j] j++ k++ } // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする for k := 0; k < len(tmp); k++ { nums[left+k] = tmp[k] } } /* マージソート */ func mergeSort(nums []int, left, right int) { // 終了条件 if left >= right { return } // 分割フェーズ mid := left + (right - left) / 2 mergeSort(nums, left, mid) mergeSort(nums, mid+1, right) // マージフェーズ merge(nums, left, mid, right) } ================================================ FILE: ja/codes/go/chapter_sorting/merge_sort_test.go ================================================ // File: merge_sort_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) func TestMergeSort(t *testing.T) { nums := []int{7, 3, 2, 6, 0, 1, 5, 4} mergeSort(nums, 0, len(nums)-1) fmt.Println("マージソート完了後 nums = ", nums) } ================================================ FILE: ja/codes/go/chapter_sorting/quick_sort.go ================================================ // File: quick_sort.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting // クイックソート type quickSort struct{} // クイックソート(中央値の基準値で最適化) type quickSortMedian struct{} // クイックソート(再帰深度最適化) type quickSortTailCall struct{} /* 番兵分割 */ func (q *quickSort) partition(nums []int, left, right int) int { // nums[left] を基準値とする i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- // 右から左へ基準値未満の最初の要素を探す } for i < j && nums[i] <= nums[left] { i++ // 左から右へ基準値より大きい最初の要素を探す } // 要素の交換 nums[i], nums[j] = nums[j], nums[i] } // 基準値を 2 つの部分配列の境界へ交換する nums[i], nums[left] = nums[left], nums[i] return i // 基準値のインデックスを返す } /* クイックソート */ func (q *quickSort) quickSort(nums []int, left, right int) { // 部分配列の長さが 1 なら再帰を終了する if left >= right { return } // 番兵分割 pivot := q.partition(nums, left, right) // 左右の部分配列を再帰処理 q.quickSort(nums, left, pivot-1) q.quickSort(nums, pivot+1, right) } /* 3つの候補要素の中央値を選ぶ */ func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int { l, m, r := nums[left], nums[mid], nums[right] if (l <= m && m <= r) || (r <= m && m <= l) { return mid // m は l と r の間 } if (m <= l && l <= r) || (r <= l && l <= m) { return left // l は m と r の間 } return right } /* 番兵による分割処理(3 点中央値) */ func (q *quickSortMedian) partition(nums []int, left, right int) int { // nums[left] を基準値とする med := q.medianThree(nums, left, (left+right)/2, right) // 中央値を配列の最左端に交換する nums[left], nums[med] = nums[med], nums[left] // nums[left] を基準値とする i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- // 右から左へ基準値未満の最初の要素を探す } for i < j && nums[i] <= nums[left] { i++ // 左から右へ基準値より大きい最初の要素を探す } // 要素の交換 nums[i], nums[j] = nums[j], nums[i] } // 基準値を 2 つの部分配列の境界へ交換する nums[i], nums[left] = nums[left], nums[i] return i // 基準値のインデックスを返す } /* クイックソート */ func (q *quickSortMedian) quickSort(nums []int, left, right int) { // 部分配列の長さが 1 なら再帰を終了する if left >= right { return } // 番兵分割 pivot := q.partition(nums, left, right) // 左右の部分配列を再帰処理 q.quickSort(nums, left, pivot-1) q.quickSort(nums, pivot+1, right) } /* 番兵分割 */ func (q *quickSortTailCall) partition(nums []int, left, right int) int { // nums[left] を基準値とする i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- // 右から左へ基準値未満の最初の要素を探す } for i < j && nums[i] <= nums[left] { i++ // 左から右へ基準値より大きい最初の要素を探す } // 要素の交換 nums[i], nums[j] = nums[j], nums[i] } // 基準値を 2 つの部分配列の境界へ交換する nums[i], nums[left] = nums[left], nums[i] return i // 基準値のインデックスを返す } /* クイックソート(再帰深度最適化) */ func (q *quickSortTailCall) quickSort(nums []int, left, right int) { // 部分配列の長さが 1 なら終了 for left < right { // 番兵による分割処理 pivot := q.partition(nums, left, right) // 2 つの部分配列のうち短いほうにクイックソートを適用する if pivot-left < right-pivot { q.quickSort(nums, left, pivot-1) // 左部分配列を再帰的にソート left = pivot + 1 // 未ソート区間の残りは [pivot + 1, right] } else { q.quickSort(nums, pivot+1, right) // 右部分配列を再帰的にソート right = pivot - 1 // 未ソート区間の残りは [left, pivot - 1] } } } ================================================ FILE: ja/codes/go/chapter_sorting/quick_sort_test.go ================================================ // File: quick_sort_test.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) // クイックソート func TestQuickSort(t *testing.T) { q := quickSort{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("クイックソート完了後 nums = ", nums) } // クイックソート(中央値の基準値で最適化) func TestQuickSortMedian(t *testing.T) { q := quickSortMedian{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("クイックソート(中央値ピボット最適化)完了後 nums = ", nums) } // クイックソート(再帰深度最適化) func TestQuickSortTailCall(t *testing.T) { q := quickSortTailCall{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("クイックソート(再帰深度最適化)完了後 nums = ", nums) } ================================================ FILE: ja/codes/go/chapter_sorting/radix_sort.go ================================================ // File: radix_sort.go // Created Time: 2023-01-18 // Author: Reanon (793584285@qq.com) package chapter_sorting import "math" /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ func digit(num, exp int) int { // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す return (num / exp) % 10 } /* 計数ソート(nums の k 桁目でソート) */ func countingSortDigit(nums []int, exp int) { // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 counter := make([]int, 10) n := len(nums) // 0~9 の各数字の出現回数を集計する for i := 0; i < n; i++ { d := digit(nums[i], exp) // nums[i] の第 k 位を取得し、d とする counter[d]++ // 数字 d の出現回数を数える } // 累積和を求め、「出現回数」を「配列インデックス」に変換する for i := 1; i < 10; i++ { counter[i] += counter[i-1] } // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する res := make([]int, n) for i := n - 1; i >= 0; i-- { d := digit(nums[i], exp) j := counter[d] - 1 // d の配列内インデックス j を取得する res[j] = nums[i] // 現在の要素をインデックス j に格納する counter[d]-- // d の個数を 1 減らす } // 結果で元の配列 nums を上書きする for i := 0; i < n; i++ { nums[i] = res[i] } } /* 基数ソート */ func radixSort(nums []int) { // 最大桁数の判定用に配列の最大要素を取得 max := math.MinInt for _, num := range nums { if num > max { max = num } } // 下位桁から上位桁の順に走査する for exp := 1; max >= exp; exp *= 10 { // 配列要素の k 桁目に対して計数ソートを行う // k = 1 -> exp = 1 // k = 2 -> exp = 10 // つまり exp = 10^(k-1) countingSortDigit(nums, exp) } } ================================================ FILE: ja/codes/go/chapter_sorting/radix_sort_test.go ================================================ // File: radix_sort_test.go // Created Time: 2023-01-18 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestRadixSort(t *testing.T) { /* 基数ソート */ nums := []int{10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996} radixSort(nums) fmt.Println("基数ソート完了後 nums = ", nums) } ================================================ FILE: ja/codes/go/chapter_sorting/selection_sort.go ================================================ // File: selection_sort.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting /* 選択ソート */ func selectionSort(nums []int) { n := len(nums) // 外側ループ:未整列区間は [i, n-1] for i := 0; i < n-1; i++ { // 内側のループ:未ソート区間の最小要素を見つける k := i for j := i + 1; j < n; j++ { if nums[j] < nums[k] { // 最小要素のインデックスを記録 k = j } } // その最小要素を未整列区間の先頭要素と交換する nums[i], nums[k] = nums[k], nums[i] } } ================================================ FILE: ja/codes/go/chapter_sorting/selection_sort_test.go ================================================ // File: selection_sort_test.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestSelectionSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} selectionSort(nums) fmt.Println("選択ソート完了後 nums = ", nums) } ================================================ FILE: ja/codes/go/chapter_stack_and_queue/array_deque.go ================================================ // File: array_deque.go // Created Time: 2023-03-13 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import "fmt" /* 循環配列ベースの両端キュー */ type arrayDeque struct { nums []int // 両端キューの要素を格納する配列 front int // 先頭ポインタ。先頭要素を指す queSize int // 両端キューの長さ queCapacity int // キュー容量(格納できる要素数の上限) } /* キューを初期化 */ func newArrayDeque(queCapacity int) *arrayDeque { return &arrayDeque{ nums: make([]int, queCapacity), queCapacity: queCapacity, front: 0, queSize: 0, } } /* 両端キューの長さを取得 */ func (q *arrayDeque) size() int { return q.queSize } /* 両端キューが空かどうかを判定 */ func (q *arrayDeque) isEmpty() bool { return q.queSize == 0 } /* 循環配列のインデックスを計算 */ func (q *arrayDeque) index(i int) int { // 剰余演算により配列の先頭と末尾をつなげる // i が配列の末尾を越えたら先頭に戻る // i が配列の先頭を越えて前に出たら末尾に戻る return (i + q.queCapacity) % q.queCapacity } /* キュー先頭にエンキュー */ func (q *arrayDeque) pushFirst(num int) { if q.queSize == q.queCapacity { fmt.Println("両端キューは満杯です") return } // 先頭ポインタを左に 1 つ移動する // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする q.front = q.index(q.front - 1) // num をキュー先頭に追加 q.nums[q.front] = num q.queSize++ } /* キュー末尾にエンキュー */ func (q *arrayDeque) pushLast(num int) { if q.queSize == q.queCapacity { fmt.Println("両端キューは満杯です") return } // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す rear := q.index(q.front + q.queSize) // num をキュー末尾に追加 q.nums[rear] = num q.queSize++ } /* キュー先頭からデキュー */ func (q *arrayDeque) popFirst() any { num := q.peekFirst() if num == nil { return nil } // 先頭ポインタを 1 つ後ろへ進める q.front = q.index(q.front + 1) q.queSize-- return num } /* キュー末尾からデキュー */ func (q *arrayDeque) popLast() any { num := q.peekLast() if num == nil { return nil } q.queSize-- return num } /* キュー先頭の要素にアクセス */ func (q *arrayDeque) peekFirst() any { if q.isEmpty() { return nil } return q.nums[q.front] } /* キュー末尾の要素にアクセス */ func (q *arrayDeque) peekLast() any { if q.isEmpty() { return nil } // 末尾要素のインデックスを計算 last := q.index(q.front + q.queSize - 1) return q.nums[last] } /* 表示用に Slice を取得 */ func (q *arrayDeque) toSlice() []int { // 有効長の範囲内のリスト要素のみを変換 res := make([]int, q.queSize) for i, j := 0, q.front; i < q.queSize; i++ { res[i] = q.nums[q.index(j)] j++ } return res } ================================================ FILE: ja/codes/go/chapter_stack_and_queue/array_queue.go ================================================ // File: array_queue.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue /* 循環配列ベースのキュー */ type arrayQueue struct { nums []int // キュー要素を格納する配列 front int // 先頭ポインタ。先頭要素を指す queSize int // キューの長さ queCapacity int // キュー容量(格納できる要素数の上限) } /* キューを初期化 */ func newArrayQueue(queCapacity int) *arrayQueue { return &arrayQueue{ nums: make([]int, queCapacity), queCapacity: queCapacity, front: 0, queSize: 0, } } /* キューの長さを取得 */ func (q *arrayQueue) size() int { return q.queSize } /* キューが空かどうかを判定 */ func (q *arrayQueue) isEmpty() bool { return q.queSize == 0 } /* エンキュー */ func (q *arrayQueue) push(num int) { // rear == queCapacity のときキューは満杯 if q.queSize == q.queCapacity { return } // 末尾ポインタを計算し、末尾インデックス + 1 を指す // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする rear := (q.front + q.queSize) % q.queCapacity // num をキュー末尾に追加 q.nums[rear] = num q.queSize++ } /* デキュー */ func (q *arrayQueue) pop() any { num := q.peek() if num == nil { return nil } // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す q.front = (q.front + 1) % q.queCapacity q.queSize-- return num } /* キュー先頭の要素にアクセス */ func (q *arrayQueue) peek() any { if q.isEmpty() { return nil } return q.nums[q.front] } /* 表示用に Slice を取得 */ func (q *arrayQueue) toSlice() []int { rear := (q.front + q.queSize) if rear >= q.queCapacity { rear %= q.queCapacity return append(q.nums[q.front:], q.nums[:rear]...) } return q.nums[q.front:rear] } ================================================ FILE: ja/codes/go/chapter_stack_and_queue/array_stack.go ================================================ // File: array_stack.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue /* 配列ベースのスタック */ type arrayStack struct { data []int // データ } /* スタックを初期化 */ func newArrayStack() *arrayStack { return &arrayStack{ // スタックの長さを 0、容量を 16 に設定 data: make([]int, 0, 16), } } /* スタックの長さ */ func (s *arrayStack) size() int { return len(s.data) } /* スタックが空かどうか */ func (s *arrayStack) isEmpty() bool { return s.size() == 0 } /* プッシュ */ func (s *arrayStack) push(v int) { // スライスは自動で拡張される s.data = append(s.data, v) } /* ポップ */ func (s *arrayStack) pop() any { val := s.peek() s.data = s.data[:len(s.data)-1] return val } /* スタックトップ要素を取得する */ func (s *arrayStack) peek() any { if s.isEmpty() { return nil } val := s.data[len(s.data)-1] return val } /* 表示用に Slice を取得 */ func (s *arrayStack) toSlice() []int { return s.data } ================================================ FILE: ja/codes/go/chapter_stack_and_queue/deque_test.go ================================================ // File: deque_test.go // Created Time: 2022-11-29 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestDeque(t *testing.T) { /* 両端キューを初期化 */ // Go では、list を両端キューとして使う deque := list.New() /* 要素をエンキュー */ deque.PushBack(2) deque.PushBack(5) deque.PushBack(4) deque.PushFront(3) deque.PushFront(1) fmt.Print("両端キュー deque = ") PrintList(deque) /* 要素にアクセス */ front := deque.Front() fmt.Println("先頭要素 front =", front.Value) rear := deque.Back() fmt.Println("末尾要素 rear =", rear.Value) /* 要素をデキュー */ deque.Remove(front) fmt.Print("先頭から取り出した要素 front = ", front.Value, "、取り出し後の deque = ") PrintList(deque) deque.Remove(rear) fmt.Print("末尾から取り出した要素 rear = ", rear.Value, "、取り出し後の deque = ") PrintList(deque) /* 両端キューの長さを取得 */ size := deque.Len() fmt.Println("両端キューの長さ size =", size) /* 両端キューが空かどうかを判定 */ isEmpty := deque.Len() == 0 fmt.Println("両端キューが空か =", isEmpty) } func TestArrayDeque(t *testing.T) { /* 両端キューを初期化 */ // Go では、list を両端キューとして使う deque := newArrayDeque(16) /* 要素をエンキュー */ deque.pushLast(3) deque.pushLast(2) deque.pushLast(5) fmt.Print("両端キュー deque = ") PrintSlice(deque.toSlice()) /* 要素にアクセス */ peekFirst := deque.peekFirst() fmt.Println("先頭要素 peekFirst =", peekFirst) peekLast := deque.peekLast() fmt.Println("末尾要素 peekLast =", peekLast) /* 要素をエンキュー */ deque.pushLast(4) fmt.Print("要素 4 を末尾に追加した後 deque = ") PrintSlice(deque.toSlice()) deque.pushFirst(1) fmt.Print("要素 1 を先頭に追加した後 deque = ") PrintSlice(deque.toSlice()) /* 要素をデキュー */ popFirst := deque.popFirst() fmt.Print("先頭から取り出した要素 popFirst = ", popFirst, "、取り出し後の deque = ") PrintSlice(deque.toSlice()) popLast := deque.popLast() fmt.Print("末尾から取り出した要素 popLast = ", popLast, "、取り出し後の deque = ") PrintSlice(deque.toSlice()) /* 両端キューの長さを取得 */ size := deque.size() fmt.Println("両端キューの長さ size =", size) /* 両端キューが空かどうかを判定 */ isEmpty := deque.isEmpty() fmt.Println("両端キューが空か =", isEmpty) } func TestLinkedListDeque(t *testing.T) { // キューを初期化 deque := newLinkedListDeque() // 要素をエンキュー deque.pushLast(2) deque.pushLast(5) deque.pushLast(4) deque.pushFirst(3) deque.pushFirst(1) fmt.Print("キュー deque = ") PrintList(deque.toList()) // キュー先頭の要素にアクセス front := deque.peekFirst() fmt.Println("先頭要素 front =", front) rear := deque.peekLast() fmt.Println("末尾要素 rear =", rear) // 要素をデキュー popFirst := deque.popFirst() fmt.Print("先頭から取り出した要素 popFirst = ", popFirst, "、取り出し後の deque = ") PrintList(deque.toList()) popLast := deque.popLast() fmt.Print("末尾から取り出した要素 popLast = ", popLast, "、取り出し後の deque = ") PrintList(deque.toList()) // キューの長さを取得 size := deque.size() fmt.Println("キューの長さ size =", size) // 空かどうかを判定 isEmpty := deque.isEmpty() fmt.Println("キューが空か =", isEmpty) } // BenchmarkLinkedListDeque 67.92 ns/op in Mac M1 Pro func BenchmarkLinkedListDeque(b *testing.B) { deque := newLinkedListDeque() // use b.N for looping for i := 0; i < b.N; i++ { deque.pushLast(777) } for i := 0; i < b.N; i++ { deque.popFirst() } } ================================================ FILE: ja/codes/go/chapter_stack_and_queue/linkedlist_deque.go ================================================ // File: linkedlist_deque.go // Created Time: 2022-11-29 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* 双方向連結リストベースの両端キュー */ type linkedListDeque struct { // 組み込みパッケージ list を使う data *list.List } /* 両端キューを初期化する */ func newLinkedListDeque() *linkedListDeque { return &linkedListDeque{ data: list.New(), } } /* キュー先頭に要素を追加する */ func (s *linkedListDeque) pushFirst(value any) { s.data.PushFront(value) } /* キュー末尾に要素を追加する */ func (s *linkedListDeque) pushLast(value any) { s.data.PushBack(value) } /* 先頭要素を取り出す */ func (s *linkedListDeque) popFirst() any { if s.isEmpty() { return nil } e := s.data.Front() s.data.Remove(e) return e.Value } /* 末尾要素を取り出す */ func (s *linkedListDeque) popLast() any { if s.isEmpty() { return nil } e := s.data.Back() s.data.Remove(e) return e.Value } /* キュー先頭の要素にアクセス */ func (s *linkedListDeque) peekFirst() any { if s.isEmpty() { return nil } e := s.data.Front() return e.Value } /* キュー末尾の要素にアクセス */ func (s *linkedListDeque) peekLast() any { if s.isEmpty() { return nil } e := s.data.Back() return e.Value } /* キューの長さを取得 */ func (s *linkedListDeque) size() int { return s.data.Len() } /* キューが空かどうかを判定 */ func (s *linkedListDeque) isEmpty() bool { return s.data.Len() == 0 } /* 表示用に List を取得 */ func (s *linkedListDeque) toList() *list.List { return s.data } ================================================ FILE: ja/codes/go/chapter_stack_and_queue/linkedlist_queue.go ================================================ // File: linkedlist_queue.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* 連結リストベースのキュー */ type linkedListQueue struct { // 組み込みパッケージ list でキューを実装する data *list.List } /* キューを初期化 */ func newLinkedListQueue() *linkedListQueue { return &linkedListQueue{ data: list.New(), } } /* エンキュー */ func (s *linkedListQueue) push(value any) { s.data.PushBack(value) } /* デキュー */ func (s *linkedListQueue) pop() any { if s.isEmpty() { return nil } e := s.data.Front() s.data.Remove(e) return e.Value } /* キュー先頭の要素にアクセス */ func (s *linkedListQueue) peek() any { if s.isEmpty() { return nil } e := s.data.Front() return e.Value } /* キューの長さを取得 */ func (s *linkedListQueue) size() int { return s.data.Len() } /* キューが空かどうかを判定 */ func (s *linkedListQueue) isEmpty() bool { return s.data.Len() == 0 } /* 表示用に List を取得 */ func (s *linkedListQueue) toList() *list.List { return s.data } ================================================ FILE: ja/codes/go/chapter_stack_and_queue/linkedlist_stack.go ================================================ // File: linkedlist_stack.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* 連結リストベースのスタック */ type linkedListStack struct { // 組み込みパッケージ list でスタックを実装する data *list.List } /* スタックを初期化 */ func newLinkedListStack() *linkedListStack { return &linkedListStack{ data: list.New(), } } /* プッシュ */ func (s *linkedListStack) push(value int) { s.data.PushBack(value) } /* ポップ */ func (s *linkedListStack) pop() any { if s.isEmpty() { return nil } e := s.data.Back() s.data.Remove(e) return e.Value } /* スタックトップの要素にアクセス */ func (s *linkedListStack) peek() any { if s.isEmpty() { return nil } e := s.data.Back() return e.Value } /* スタックの長さを取得 */ func (s *linkedListStack) size() int { return s.data.Len() } /* スタックが空かどうかを判定 */ func (s *linkedListStack) isEmpty() bool { return s.data.Len() == 0 } /* 表示用に List を取得 */ func (s *linkedListStack) toList() *list.List { return s.data } ================================================ FILE: ja/codes/go/chapter_stack_and_queue/queue_test.go ================================================ // File: queue_test.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestQueue(t *testing.T) { /* キューを初期化 */ // Go では、list をキューとして使う queue := list.New() /* 要素をエンキュー */ queue.PushBack(1) queue.PushBack(3) queue.PushBack(2) queue.PushBack(5) queue.PushBack(4) fmt.Print("キュー queue = ") PrintList(queue) /* キュー先頭の要素にアクセス */ peek := queue.Front() fmt.Println("先頭要素 peek =", peek.Value) /* 要素をデキュー */ pop := queue.Front() queue.Remove(pop) fmt.Print("取り出した要素 pop = ", pop.Value, "、取り出し後の queue = ") PrintList(queue) /* キューの長さを取得 */ size := queue.Len() fmt.Println("キューの長さ size =", size) /* キューが空かどうかを判定 */ isEmpty := queue.Len() == 0 fmt.Println("キューが空か =", isEmpty) } func TestArrayQueue(t *testing.T) { // キューを初期化し、キューの共通インターフェースを使う capacity := 10 queue := newArrayQueue(capacity) if queue.pop() != nil { t.Errorf("want:%v,got:%v", nil, queue.pop()) } // 要素をエンキュー queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) fmt.Print("キュー queue = ") PrintSlice(queue.toSlice()) // キュー先頭の要素にアクセス peek := queue.peek() fmt.Println("先頭要素 peek =", peek) // 要素をデキュー pop := queue.pop() fmt.Print("取り出した要素 pop = ", pop, ", 取り出し後 queue = ") PrintSlice(queue.toSlice()) // キューの長さを取得 size := queue.size() fmt.Println("キューの長さ size =", size) // 空かどうかを判定 isEmpty := queue.isEmpty() fmt.Println("キューが空か =", isEmpty) /* 循環配列をテストする */ for i := 0; i < 10; i++ { queue.push(i) queue.pop() fmt.Print("第", i, "回のエンキュー + デキュー後 queue =") PrintSlice(queue.toSlice()) } } func TestLinkedListQueue(t *testing.T) { // キューを初期化する queue := newLinkedListQueue() // 要素をエンキュー queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) fmt.Print("キュー queue = ") PrintList(queue.toList()) // キュー先頭の要素にアクセス peek := queue.peek() fmt.Println("先頭要素 peek =", peek) // 要素をデキュー pop := queue.pop() fmt.Print("取り出した要素 pop = ", pop, ", 取り出し後 queue = ") PrintList(queue.toList()) // キューの長さを取得 size := queue.size() fmt.Println("キューの長さ size =", size) // 空かどうかを判定 isEmpty := queue.isEmpty() fmt.Println("キューが空か =", isEmpty) } // BenchmarkArrayQueue 8 ns/op in Mac M1 Pro func BenchmarkArrayQueue(b *testing.B) { capacity := 1000 queue := newArrayQueue(capacity) // use b.N for looping for i := 0; i < b.N; i++ { queue.push(777) } for i := 0; i < b.N; i++ { queue.pop() } } // BenchmarkLinkedQueue 62.66 ns/op in Mac M1 Pro func BenchmarkLinkedQueue(b *testing.B) { queue := newLinkedListQueue() // use b.N for looping for i := 0; i < b.N; i++ { queue.push(777) } for i := 0; i < b.N; i++ { queue.pop() } } ================================================ FILE: ja/codes/go/chapter_stack_and_queue/stack_test.go ================================================ // File: stack_test.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestStack(t *testing.T) { /* スタックを初期化 */ // Go では、Slice をスタックとして使うことが推奨される var stack []int /* 要素をプッシュ */ stack = append(stack, 1) stack = append(stack, 3) stack = append(stack, 2) stack = append(stack, 5) stack = append(stack, 4) fmt.Print("スタック stack = ") PrintSlice(stack) /* スタックトップの要素にアクセス */ peek := stack[len(stack)-1] fmt.Println("スタックトップ要素 peek =", peek) /* 要素をポップ */ pop := stack[len(stack)-1] stack = stack[:len(stack)-1] fmt.Print("ポップした要素 pop = ", pop, ",ポップ後 stack = ") PrintSlice(stack) /* スタックの長さを取得 */ size := len(stack) fmt.Println("スタックの長さ size =", size) /* 空かどうかを判定 */ isEmpty := len(stack) == 0 fmt.Println("スタックが空かどうか =", isEmpty) } func TestArrayStack(t *testing.T) { // スタックを初期化し、インターフェース型で受ける stack := newArrayStack() // 要素をプッシュ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) fmt.Print("スタック stack = ") PrintSlice(stack.toSlice()) // スタックトップの要素にアクセス peek := stack.peek() fmt.Println("スタックトップ要素 peek =", peek) // 要素をポップ pop := stack.pop() fmt.Print("ポップした要素 pop = ", pop, ", ポップ後 stack = ") PrintSlice(stack.toSlice()) // スタックの長さを取得 size := stack.size() fmt.Println("スタックの長さ size =", size) // 空かどうかを判定 isEmpty := stack.isEmpty() fmt.Println("スタックが空かどうか =", isEmpty) } func TestLinkedListStack(t *testing.T) { // スタックを初期化 stack := newLinkedListStack() // 要素をプッシュ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) fmt.Print("スタック stack = ") PrintList(stack.toList()) // スタックトップの要素にアクセス peek := stack.peek() fmt.Println("スタックトップ要素 peek =", peek) // 要素をポップ pop := stack.pop() fmt.Print("ポップした要素 pop = ", pop, ", ポップ後 stack = ") PrintList(stack.toList()) // スタックの長さを取得 size := stack.size() fmt.Println("スタックの長さ size =", size) // 空かどうかを判定 isEmpty := stack.isEmpty() fmt.Println("スタックが空かどうか =", isEmpty) } // BenchmarkArrayStack 8 ns/op in Mac M1 Pro func BenchmarkArrayStack(b *testing.B) { stack := newArrayStack() // use b.N for looping for i := 0; i < b.N; i++ { stack.push(777) } for i := 0; i < b.N; i++ { stack.pop() } } // BenchmarkLinkedListStack 65.02 ns/op in Mac M1 Pro func BenchmarkLinkedListStack(b *testing.B) { stack := newLinkedListStack() // use b.N for looping for i := 0; i < b.N; i++ { stack.push(777) } for i := 0; i < b.N; i++ { stack.pop() } } ================================================ FILE: ja/codes/go/chapter_tree/array_binary_tree.go ================================================ // File: array_binary_tree.go // Created Time: 2023-07-24 // Author: Reanon (793584285@qq.com) package chapter_tree /* 配列表現による二分木クラス */ type arrayBinaryTree struct { tree []any } /* コンストラクタ */ func newArrayBinaryTree(arr []any) *arrayBinaryTree { return &arrayBinaryTree{ tree: arr, } } /* リスト容量 */ func (abt *arrayBinaryTree) size() int { return len(abt.tree) } /* インデックス i のノードの値を取得 */ func (abt *arrayBinaryTree) val(i int) any { // インデックスが範囲外なら、空きを表す null を返す if i < 0 || i >= abt.size() { return nil } return abt.tree[i] } /* インデックス i のノードの左子ノードのインデックスを取得 */ func (abt *arrayBinaryTree) left(i int) int { return 2*i + 1 } /* インデックス i のノードの右子ノードのインデックスを取得 */ func (abt *arrayBinaryTree) right(i int) int { return 2*i + 2 } /* インデックス i のノードの親ノードのインデックスを取得 */ func (abt *arrayBinaryTree) parent(i int) int { return (i - 1) / 2 } /* レベル順走査 */ func (abt *arrayBinaryTree) levelOrder() []any { var res []any // 配列を直接走査する for i := 0; i < abt.size(); i++ { if abt.val(i) != nil { res = append(res, abt.val(i)) } } return res } /* 深さ優先探索 */ func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) { // 空きスロットなら返す if abt.val(i) == nil { return } // 先行順走査 if order == "pre" { *res = append(*res, abt.val(i)) } abt.dfs(abt.left(i), order, res) // 中順走査 if order == "in" { *res = append(*res, abt.val(i)) } abt.dfs(abt.right(i), order, res) // 後順走査 if order == "post" { *res = append(*res, abt.val(i)) } } /* 先行順走査 */ func (abt *arrayBinaryTree) preOrder() []any { var res []any abt.dfs(0, "pre", &res) return res } /* 中順走査 */ func (abt *arrayBinaryTree) inOrder() []any { var res []any abt.dfs(0, "in", &res) return res } /* 後順走査 */ func (abt *arrayBinaryTree) postOrder() []any { var res []any abt.dfs(0, "post", &res) return res } ================================================ FILE: ja/codes/go/chapter_tree/array_binary_tree_test.go ================================================ // File: array_binary_tree_test.go // Created Time: 2023-07-24 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestArrayBinaryTree(t *testing.T) { // 二分木を初期化 // ここでは、配列から直接二分木を生成する関数を利用する arr := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} root := SliceToTree(arr) fmt.Println("\n二分木を初期化") fmt.Println("二分木の配列表現:") fmt.Println(arr) fmt.Println("二分木の連結リスト表現:") PrintTree(root) // 配列表現による二分木クラス abt := newArrayBinaryTree(arr) // ノードにアクセス i := 1 l := abt.left(i) r := abt.right(i) p := abt.parent(i) fmt.Println("\n現在のノードのインデックスは", i, ",値は", abt.val(i)) fmt.Println("左の子ノードのインデックスは", l, ",値は", abt.val(l)) fmt.Println("右の子ノードのインデックスは", r, ",値は", abt.val(r)) fmt.Println("親ノードのインデックスは", p, ",値は", abt.val(p)) // 木を走査 res := abt.levelOrder() fmt.Println("\nレベル順走査:", res) res = abt.preOrder() fmt.Println("前順走査:", res) res = abt.inOrder() fmt.Println("中順走査:", res) res = abt.postOrder() fmt.Println("後順走査:", res) } ================================================ FILE: ja/codes/go/chapter_tree/avl_tree.go ================================================ // File: avl_tree.go // Created Time: 2023-01-08 // Author: Reanon (793584285@qq.com) package chapter_tree import . "github.com/krahets/hello-algo/pkg" /* AVL 木 */ type aVLTree struct { // 根ノード root *TreeNode } func newAVLTree() *aVLTree { return &aVLTree{root: nil} } /* ノードの高さを取得 */ func (t *aVLTree) height(node *TreeNode) int { // 空ノードの高さは -1、葉ノードの高さは 0 if node != nil { return node.Height } return -1 } /* ノードの高さを更新する */ func (t *aVLTree) updateHeight(node *TreeNode) { lh := t.height(node.Left) rh := t.height(node.Right) // ノードの高さは最も高い部分木の高さ + 1 に等しい if lh > rh { node.Height = lh + 1 } else { node.Height = rh + 1 } } /* 平衡係数を取得 */ func (t *aVLTree) balanceFactor(node *TreeNode) int { // 空ノードの平衡係数は 0 if node == nil { return 0 } // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ return t.height(node.Left) - t.height(node.Right) } /* 右回転 */ func (t *aVLTree) rightRotate(node *TreeNode) *TreeNode { child := node.Left grandChild := child.Right // child を支点として node を右回転させる child.Right = node node.Left = grandChild // ノードの高さを更新する t.updateHeight(node) t.updateHeight(child) // 回転後の部分木の根ノードを返す return child } /* 左回転 */ func (t *aVLTree) leftRotate(node *TreeNode) *TreeNode { child := node.Right grandChild := child.Left // child を支点として node を左回転させる child.Left = node node.Right = grandChild // ノードの高さを更新する t.updateHeight(node) t.updateHeight(child) // 回転後の部分木の根ノードを返す return child } /* 回転操作を行い、この部分木の平衡を回復する */ func (t *aVLTree) rotate(node *TreeNode) *TreeNode { // ノード `node` の平衡係数を取得する // Go では短い変数名が推奨されるため、ここで `bf` は `t.balanceFactor` を表す bf := t.balanceFactor(node) // 左に偏った木 if bf > 1 { if t.balanceFactor(node.Left) >= 0 { // 右回転 return t.rightRotate(node) } else { // 左回転してから右回転 node.Left = t.leftRotate(node.Left) return t.rightRotate(node) } } // 右に偏った木 if bf < -1 { if t.balanceFactor(node.Right) <= 0 { // 左回転 return t.leftRotate(node) } else { // 右回転してから左回転 node.Right = t.rightRotate(node.Right) return t.leftRotate(node) } } // 平衡木なので回転不要、そのまま返す return node } /* ノードを挿入 */ func (t *aVLTree) insert(val int) { t.root = t.insertHelper(t.root, val) } /* ノードを再帰的に挿入する(補助関数) */ func (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode { if node == nil { return NewTreeNode(val) } /* 1. 挿入位置を探索してノードを挿入 */ if val < node.Val.(int) { node.Left = t.insertHelper(node.Left, val) } else if val > node.Val.(int) { node.Right = t.insertHelper(node.Right, val) } else { // 重複ノードは挿入せず、そのまま返す return node } // ノードの高さを更新する t.updateHeight(node) /* 2. 回転操作を行い、部分木の平衡を回復する */ node = t.rotate(node) // 部分木の根ノードを返す return node } /* ノードを削除 */ func (t *aVLTree) remove(val int) { t.root = t.removeHelper(t.root, val) } /* ノードを再帰的に削除する(補助関数) */ func (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode { if node == nil { return nil } /* 1. ノードを探索して削除 */ if val < node.Val.(int) { node.Left = t.removeHelper(node.Left, val) } else if val > node.Val.(int) { node.Right = t.removeHelper(node.Right, val) } else { if node.Left == nil || node.Right == nil { child := node.Left if node.Right != nil { child = node.Right } if child == nil { // 子ノード数 = 0 の場合、node をそのまま削除して返す return nil } else { // 子ノード数 = 1 の場合、node をそのまま削除する node = child } } else { // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える temp := node.Right for temp.Left != nil { temp = temp.Left } node.Right = t.removeHelper(node.Right, temp.Val.(int)) node.Val = temp.Val } } // ノードの高さを更新する t.updateHeight(node) /* 2. 回転操作を行い、部分木の平衡を回復する */ node = t.rotate(node) // 部分木の根ノードを返す return node } /* ノードを探索 */ func (t *aVLTree) search(val int) *TreeNode { cur := t.root // ループで探索し、葉ノードを越えたら抜ける for cur != nil { if cur.Val.(int) < val { // 目標ノードは cur の右部分木にある cur = cur.Right } else if cur.Val.(int) > val { // 目標ノードは cur の左部分木にある cur = cur.Left } else { // 目標ノードが見つかったらループを抜ける break } } // 目標ノードを返す return cur } ================================================ FILE: ja/codes/go/chapter_tree/avl_tree_test.go ================================================ // File: avl_tree_test.go // Created Time: 2023-01-08 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestAVLTree(t *testing.T) { /* 空の AVL 木を初期化する */ tree := newAVLTree() /* ノードを挿入 */ // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい testInsert(tree, 1) testInsert(tree, 2) testInsert(tree, 3) testInsert(tree, 4) testInsert(tree, 5) testInsert(tree, 8) testInsert(tree, 7) testInsert(tree, 9) testInsert(tree, 10) testInsert(tree, 6) /* 重複ノードを挿入する */ testInsert(tree, 7) /* ノードを削除 */ // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい testRemove(tree, 8) // 次数 0 のノードを削除する testRemove(tree, 5) // 次数 1 のノードを削除する testRemove(tree, 4) // 次数 2 のノードを削除する /* ノードを検索 */ node := tree.search(7) fmt.Printf("\n見つかったノードオブジェクトは %#v ,ノードの値 = %d \n", node, node.Val) } func testInsert(tree *aVLTree, val int) { tree.insert(val) fmt.Printf("\nノード %d を挿入後、AVL 木は \n", val) PrintTree(tree.root) } func testRemove(tree *aVLTree, val int) { tree.remove(val) fmt.Printf("\nノード %d を削除後、AVL 木は \n", val) PrintTree(tree.root) } ================================================ FILE: ja/codes/go/chapter_tree/binary_search_tree.go ================================================ // File: binary_search_tree.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( . "github.com/krahets/hello-algo/pkg" ) type binarySearchTree struct { root *TreeNode } func newBinarySearchTree() *binarySearchTree { bst := &binarySearchTree{} // 空の木を初期化する bst.root = nil return bst } /* 根ノードを取得 */ func (bst *binarySearchTree) getRoot() *TreeNode { return bst.root } /* ノードを探索 */ func (bst *binarySearchTree) search(num int) *TreeNode { node := bst.root // ループで探索し、葉ノードを越えたら抜ける for node != nil { if node.Val.(int) < num { // 目標ノードは cur の右部分木にある node = node.Right } else if node.Val.(int) > num { // 目標ノードは cur の左部分木にある node = node.Left } else { // 目標ノードが見つかったらループを抜ける break } } // 目標ノードを返す return node } /* ノードを挿入 */ func (bst *binarySearchTree) insert(num int) { cur := bst.root // 木が空なら、根ノードを初期化する if cur == nil { bst.root = NewTreeNode(num) return } // 挿入対象ノードの直前のノード位置 var pre *TreeNode = nil // ループで探索し、葉ノードを越えたら抜ける for cur != nil { if cur.Val == num { return } pre = cur if cur.Val.(int) < num { cur = cur.Right } else { cur = cur.Left } } // ノードを挿入 node := NewTreeNode(num) if pre.Val.(int) < num { pre.Right = node } else { pre.Left = node } } /* ノードを削除 */ func (bst *binarySearchTree) remove(num int) { cur := bst.root // 木が空なら、そのまま早期リターンする if cur == nil { return } // 削除対象ノードの直前のノード位置 var pre *TreeNode = nil // ループで探索し、葉ノードを越えたら抜ける for cur != nil { if cur.Val == num { break } pre = cur if cur.Val.(int) < num { // 削除対象ノードは右部分木にある cur = cur.Right } else { // 削除対象ノードは左部分木にある cur = cur.Left } } // 削除対象ノードがなければそのまま返す if cur == nil { return } // 子ノード数は 0 または 1 if cur.Left == nil || cur.Right == nil { var child *TreeNode = nil // 削除対象ノードの子ノードを取り出す if cur.Left != nil { child = cur.Left } else { child = cur.Right } // ノード cur を削除する if cur != bst.root { if pre.Left == cur { pre.Left = child } else { pre.Right = child } } else { // 削除ノードが根ノードなら、根ノードを再設定 bst.root = child } // 子ノード数は 2 } else { // 中順走査で削除対象ノード `cur` の次のノードを取得する tmp := cur.Right for tmp.Left != nil { tmp = tmp.Left } // ノード tmp を再帰的に削除 bst.remove(tmp.Val.(int)) // tmp で cur を上書きする cur.Val = tmp.Val } } /* 二分探索木を出力 */ func (bst *binarySearchTree) print() { PrintTree(bst.root) } ================================================ FILE: ja/codes/go/chapter_tree/binary_search_tree_test.go ================================================ // File: binary_search_tree_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" ) func TestBinarySearchTree(t *testing.T) { bst := newBinarySearchTree() nums := []int{8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15} // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる for _, num := range nums { bst.insert(num) } fmt.Println("\n初期化した二分木は:") bst.print() // 根ノードを取得 node := bst.getRoot() fmt.Println("\n二分木の根ノードは:", node.Val) // ノードを探索 node = bst.search(7) fmt.Println("見つかったノードオブジェクトは", node, ",ノードの値 =", node.Val) // ノードを挿入 bst.insert(16) fmt.Println("\nノード 16 を挿入した後の二分木は:") bst.print() // ノードを削除 bst.remove(1) fmt.Println("\nノード 1 を削除した後の二分木は:") bst.print() bst.remove(2) fmt.Println("\nノード 2 を削除した後の二分木は:") bst.print() bst.remove(4) fmt.Println("\nノード 4 を削除した後の二分木は:") bst.print() } ================================================ FILE: ja/codes/go/chapter_tree/binary_tree_bfs.go ================================================ // File: binary_tree_bfs.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "container/list" . "github.com/krahets/hello-algo/pkg" ) /* レベル順走査 */ func levelOrder(root *TreeNode) []any { // キューを初期化し、ルートノードを追加する queue := list.New() queue.PushBack(root) // 走査順を保存するためのスライスを初期化する nums := make([]any, 0) for queue.Len() > 0 { // デキュー node := queue.Remove(queue.Front()).(*TreeNode) // ノードの値を保存する nums = append(nums, node.Val) if node.Left != nil { // 左子ノードをキューに追加 queue.PushBack(node.Left) } if node.Right != nil { // 右子ノードをキューに追加 queue.PushBack(node.Right) } } return nums } ================================================ FILE: ja/codes/go/chapter_tree/binary_tree_bfs_test.go ================================================ // File: binary_tree_bfs_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLevelOrder(t *testing.T) { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\n二分木を初期化: ") PrintTree(root) // レベル順走査 nums := levelOrder(root) fmt.Println("\nレベル順走査のノード出力シーケンス =", nums) } ================================================ FILE: ja/codes/go/chapter_tree/binary_tree_dfs.go ================================================ // File: binary_tree_dfs.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( . "github.com/krahets/hello-algo/pkg" ) var nums []any /* 先行順走査 */ func preOrder(node *TreeNode) { if node == nil { return } // 訪問順序:根ノード -> 左部分木 -> 右部分木 nums = append(nums, node.Val) preOrder(node.Left) preOrder(node.Right) } /* 中順走査 */ func inOrder(node *TreeNode) { if node == nil { return } // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 inOrder(node.Left) nums = append(nums, node.Val) inOrder(node.Right) } /* 後順走査 */ func postOrder(node *TreeNode) { if node == nil { return } // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード postOrder(node.Left) postOrder(node.Right) nums = append(nums, node.Val) } ================================================ FILE: ja/codes/go/chapter_tree/binary_tree_dfs_test.go ================================================ // File: binary_tree_dfs_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPreInPostOrderTraversal(t *testing.T) { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\n二分木を初期化: ") PrintTree(root) // 先行順走査 nums = nil preOrder(root) fmt.Println("\n前順走査のノード出力シーケンス =", nums) // 中順走査 nums = nil inOrder(root) fmt.Println("\n中順走査のノード出力シーケンス =", nums) // 後順走査 nums = nil postOrder(root) fmt.Println("\n後順走査のノード出力シーケンス =", nums) } ================================================ FILE: ja/codes/go/chapter_tree/binary_tree_test.go ================================================ // File: binary_tree_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestBinaryTree(t *testing.T) { /* 二分木を初期化 */ // ノードを初期化 n1 := NewTreeNode(1) n2 := NewTreeNode(2) n3 := NewTreeNode(3) n4 := NewTreeNode(4) n5 := NewTreeNode(5) // ノード間の参照(ポインタ)を構築する n1.Left = n2 n1.Right = n3 n2.Left = n4 n2.Right = n5 fmt.Println("二分木を初期化") PrintTree(n1) /* ノードの挿入と削除 */ // ノードを挿入 p := NewTreeNode(0) n1.Left = p p.Left = n2 fmt.Println("ノード P を挿入後") PrintTree(n1) // ノードを削除 n1.Left = n2 fmt.Println("ノード P を削除後") PrintTree(n1) } ================================================ FILE: ja/codes/go/go.mod ================================================ module github.com/krahets/hello-algo go 1.19 ================================================ FILE: ja/codes/go/pkg/list_node.go ================================================ // File: list_node.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg // ListNode は連結リストノード type ListNode struct { Next *ListNode Val int } // NewListNode は連結リストノードのコンストラクタ func NewListNode(v int) *ListNode { return &ListNode{ Next: nil, Val: v, } } // ArrayToLinkedList は配列を連結リストにデシリアライズする func ArrayToLinkedList(arr []int) *ListNode { // dummy header of linked list dummy := NewListNode(0) node := dummy for _, val := range arr { node.Next = NewListNode(val) node = node.Next } return dummy.Next } ================================================ FILE: ja/codes/go/pkg/list_node_test.go ================================================ // File: list_node_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg import ( "testing" ) func TestListNode(t *testing.T) { arr := []int{2, 3, 5, 6, 7} head := ArrayToLinkedList(arr) PrintLinkedList(head) } ================================================ FILE: ja/codes/go/pkg/print_utils.go ================================================ // File: print_utils.go // Created Time: 2022-12-03 // Author: Reanon (793584285@qq.com), krahets (krahets@163.com), msk397 (machangxinq@gmail.com) package pkg import ( "container/list" "fmt" "strconv" "strings" ) // PrintSlice はスライスを出力する func PrintSlice[T any](nums []T) { fmt.Printf("%v", nums) fmt.Println() } // PrintList はリストを出力する func PrintList(list *list.List) { if list.Len() == 0 { fmt.Print("[]\n") return } e := list.Front() // string への強制変換は効率に影響する fmt.Print("[") for e.Next() != nil { fmt.Print(e.Value, " ") e = e.Next() } fmt.Print(e.Value, "]\n") } // PrintMap はハッシュテーブルを出力する func PrintMap[K comparable, V any](m map[K]V) { for key, value := range m { fmt.Println(key, "->", value) } } // PrintHeap はヒープを出力する func PrintHeap(h []any) { fmt.Printf("ヒープの配列表現:") fmt.Printf("%v", h) fmt.Printf("\nヒープの木構造表示:\n") root := SliceToTree(h) PrintTree(root) } // PrintLinkedList は連結リストを出力する func PrintLinkedList(node *ListNode) { if node == nil { return } var builder strings.Builder for node.Next != nil { builder.WriteString(strconv.Itoa(node.Val) + " -> ") node = node.Next } builder.WriteString(strconv.Itoa(node.Val)) fmt.Println(builder.String()) } // PrintTree は二分木を出力する func PrintTree(root *TreeNode) { printTreeHelper(root, nil, false) } // printTreeHelper は二分木を出力する // This tree printer is borrowed from TECHIE DELIGHT // https://www.techiedelight.com/c-program-print-binary-tree/ func printTreeHelper(root *TreeNode, prev *trunk, isRight bool) { if root == nil { return } prevStr := " " trunk := newTrunk(prev, prevStr) printTreeHelper(root.Right, trunk, true) if prev == nil { trunk.str = "———" } else if isRight { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev.str = prevStr } showTrunk(trunk) fmt.Println(root.Val) if prev != nil { prev.str = prevStr } trunk.str = " |" printTreeHelper(root.Left, trunk, false) } type trunk struct { prev *trunk str string } func newTrunk(prev *trunk, str string) *trunk { return &trunk{ prev: prev, str: str, } } func showTrunk(t *trunk) { if t == nil { return } showTrunk(t.prev) fmt.Print(t.str) } ================================================ FILE: ja/codes/go/pkg/tree_node.go ================================================ // File: tree_node.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg // TreeNode は二分木ノード type TreeNode struct { Val any // ノード値 Height int // ノードの高さ Left *TreeNode // 左子ノードへの参照 Right *TreeNode // 右子ノードへの参照 } // NewTreeNode は二分木ノードのコンストラクタ func NewTreeNode(v any) *TreeNode { return &TreeNode{ Val: v, Height: 0, Left: nil, Right: nil, } } // シリアライズの符号化規則は次を参照してください: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二分木の配列表現: // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] // 二分木の連結リスト表現: // // /——— 15 // /——— 7 // /——— 3 // | \\——— 6 // | \\——— 12 // // ——— 1 // // \\——— 2 // | /——— 9 // \\——— 4 // \\——— 8 // SliceToTreeDFS はリストを二分木にデシリアライズする:再帰 func SliceToTreeDFS(arr []any, i int) *TreeNode { if i < 0 || i >= len(arr) || arr[i] == nil { return nil } root := NewTreeNode(arr[i]) root.Left = SliceToTreeDFS(arr, 2*i+1) root.Right = SliceToTreeDFS(arr, 2*i+2) return root } // SliceToTree はスライスを二分木にデシリアライズする func SliceToTree(arr []any) *TreeNode { return SliceToTreeDFS(arr, 0) } // TreeToSliceDFS は二分木をスライスにシリアライズする:再帰 func TreeToSliceDFS(root *TreeNode, i int, res *[]any) { if root == nil { return } for i >= len(*res) { *res = append(*res, nil) } (*res)[i] = root.Val TreeToSliceDFS(root.Left, 2*i+1, res) TreeToSliceDFS(root.Right, 2*i+2, res) } // TreeToSlice は二分木をスライスにシリアライズする func TreeToSlice(root *TreeNode) []any { var res []any TreeToSliceDFS(root, 0, &res) return res } ================================================ FILE: ja/codes/go/pkg/tree_node_test.go ================================================ // File: tree_node_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg import ( "fmt" "testing" ) func TestTreeNode(t *testing.T) { arr := []any{1, 2, 3, nil, 5, 6, nil} node := SliceToTree(arr) // print tree PrintTree(node) // tree to arr fmt.Println(TreeToSlice(node)) } ================================================ FILE: ja/codes/go/pkg/vertex.go ================================================ // File: vertex.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package pkg // Vertex は頂点クラス type Vertex struct { Val int } // NewVertex は頂点のコンストラクタ func NewVertex(val int) Vertex { return Vertex{ Val: val, } } // ValsToVets は値リストを頂点リストにデシリアライズする func ValsToVets(vals []int) []Vertex { vets := make([]Vertex, len(vals)) for i := 0; i < len(vals); i++ { vets[i] = NewVertex(vals[i]) } return vets } // VetsToVals は頂点リストを値リストにシリアライズする func VetsToVals(vets []Vertex) []int { vals := make([]int, len(vets)) for i := range vets { vals[i] = vets[i].Val } return vals } // DeleteSliceElms はスライスの指定要素を削除する func DeleteSliceElms[T any](a []T, elms ...T) []T { if len(a) == 0 || len(elms) == 0 { return a } // まず要素を set に変換する m := make(map[any]struct{}) for _, v := range elms { m[v] = struct{}{} } // 指定した要素を除外する res := make([]T, 0, len(a)) for _, v := range a { if _, ok := m[v]; !ok { res = append(res, v) } } return res } ================================================ FILE: ja/codes/java/.gitignore ================================================ build ================================================ FILE: ja/codes/java/chapter_array_and_linkedlist/array.java ================================================ /** * 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) からランダムに 1 つの数を選ぶ int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length); // ランダムな要素を取得して返す int randomNum = nums[randomIndex]; return randomNum; } /* 配列長を拡張する */ static int[] extend(int[] nums, int enlarge) { // 拡張後の長さを持つ配列を初期化する int[] res = new int[nums.length + enlarge]; // 元の配列の全要素を新しい配列にコピー for (int i = 0; i < nums.length; i++) { res[i] = nums[i]; } // 拡張後の新しい配列を返す 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; } /* Driver Code */ 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); } } ================================================ FILE: ja/codes/java/chapter_array_and_linkedlist/linked_list.java ================================================ /** * 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; } /* Driver Code */ public static void main(String[] args) { /* 連結リストを初期化 */ // 各ノードを初期化 ListNode n0 = new ListNode(1); ListNode n1 = new ListNode(3); ListNode n2 = new ListNode(2); ListNode n3 = new ListNode(5); ListNode n4 = new ListNode(4); // ノード間の参照を構築する n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; System.out.println("初期化した連結リストは"); 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); } } ================================================ FILE: ja/codes/java/chapter_array_and_linkedlist/list.java ================================================ /** * 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) { /* リストを初期化 */ // 配列の要素型は `int[]` のラッパークラスである `Integer[]` である点に注意 Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; List nums = new ArrayList<>(Arrays.asList(numbers)); System.out.println("リスト nums = " + nums); /* 要素にアクセス */ int num = nums.get(1); System.out.println("インデックス 1 の要素にアクセスし、num = " + num); /* 要素を更新 */ 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); } } ================================================ FILE: ja/codes/java/chapter_array_and_linkedlist/my_list.java ================================================ /** * 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 { /* Driver Code */ 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()); } } ================================================ FILE: ja/codes/java/chapter_backtracking/n_queens.java ================================================ /** * 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); } } } } ================================================ FILE: ja/codes/java/chapter_backtracking/permutations_i.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_backtracking/permutations_ii.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_backtracking/preorder_traversal_i_compact.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java ================================================ /** * 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); } } } ================================================ FILE: ja/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java ================================================ /** * 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); } } } ================================================ FILE: ja/codes/java/chapter_backtracking/preorder_traversal_iii_template.java ================================================ /** * 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); } } } ================================================ FILE: ja/codes/java/chapter_backtracking/subset_sum_i.java ================================================ /** * 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; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for (int i = start; i < choices.length; i++) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } // 試す:選択を行い、target と start を更新 state.add(choices[i]); // 次の選択へ進む backtrack(state, target - choices[i], choices, i, res); // バックトラック:選択を取り消し、前の状態に戻す state.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); } } ================================================ FILE: ja/codes/java/chapter_backtracking/subset_sum_i_naive.java ================================================ /** * 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("注意: この方法の出力結果には重複した集合が含まれます"); } } ================================================ FILE: ja/codes/java/chapter_backtracking/subset_sum_ii.java ================================================ /** * 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; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for (int i = start; i < choices.length; i++) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする if (i > start && choices[i] == choices[i - 1]) { continue; } // 試す:選択を行い、target と start を更新 state.add(choices[i]); // 次の選択へ進む backtrack(state, target - choices[i], choices, i + 1, res); // バックトラック:選択を取り消し、前の状態に戻す state.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); } } ================================================ FILE: ja/codes/java/chapter_computational_complexity/iteration.java ================================================ /** * 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; } /* 二重 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(); } /* Driver Code */ 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("\n二重 for ループの走査結果 " + resStr); } } ================================================ FILE: ja/codes/java/chapter_computational_complexity/recursion.java ================================================ /** * 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; } /* Driver Code */ 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); } } ================================================ FILE: ja/codes/java/chapter_computational_complexity/space_complexity.java ================================================ /** * 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; } /* Driver Code */ public static void main(String[] args) { int n = 5; // 定数階 constant(n); // 線形階 linear(n); linearRecur(n); // 二乗階 quadratic(n); quadraticRecur(n); // 指数オーダー TreeNode root = buildTree(n); PrintUtil.printTree(root); } } ================================================ FILE: ja/codes/java/chapter_computational_complexity/time_complexity.java ================================================ /** * 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; } /* Driver Code */ 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("2 次時間の操作回数 = " + count); int[] nums = new int[n]; for (int i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); System.out.println("2 次時間(バブルソート)の操作回数 = " + 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); } } ================================================ FILE: ja/codes/java/chapter_computational_complexity/worst_best_time_complexity.java ================================================ /** * 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; } /* Driver Code */ public static void main(String[] args) { for (int i = 0; i < 10; i++) { int n = 100; int[] nums = randomNumbers(n); int index = findOne(nums); System.out.println("\n配列 [ 1, 2, ..., n ] をシャッフルした後 = " + Arrays.toString(nums)); System.out.println("数字 1 のインデックスは " + index); } } } ================================================ FILE: ja/codes/java/chapter_divide_and_conquer/binary_search_recur.java ================================================ /** * 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) / 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); } } ================================================ FILE: ja/codes/java/chapter_divide_and_conquer/build_tree.java ================================================ /** * 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) { // 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); } } ================================================ FILE: ja/codes/java/chapter_divide_and_conquer/hanota.java ================================================ /** * File: hanota.java * Created Time: 2023-07-17 * Author: krahets (krahets@163.com) */ package chapter_divide_and_conquer; import java.util.*; public class hanota { /* 円盤を 1 枚移動 */ static void move(List src, List tar) { // src の上から円盤を1枚取り出す 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):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i - 1, src, tar, buf); // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src, tar); // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i - 1, buf, src, tar); } /* ハノイの塔を解く */ static void solveHanota(List A, List B, List C) { int n = A.size(); // A の上から n 枚の円盤を B を介して C へ移す dfs(n, A, B, C); } public static void main(String[] args) { // リスト末尾が柱の頂上 List A = new ArrayList<>(Arrays.asList(5, 4, 3, 2, 1)); List B = new ArrayList<>(); List C = new ArrayList<>(); System.out.println("初期状態:"); System.out.println("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); } } ================================================ FILE: ja/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_dynamic_programming/coin_change.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_dynamic_programming/coin_change_ii.java ================================================ /** * 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 を選ばない場合と選ぶ場合の和 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 を選ばない場合と選ぶ場合の和 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); } } ================================================ FILE: ja/codes/java/chapter_dynamic_programming/edit_distance.java ================================================ /** * 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 + " 回の編集が必要"); } } ================================================ FILE: ja/codes/java/chapter_dynamic_programming/knapsack.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_dynamic_programming/min_path_sum.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_dynamic_programming/unbounded_knapsack.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_graph/graph_adjacency_list.java ================================================ /** * 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 { // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 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(); } } ================================================ FILE: ja/codes/java/chapter_graph/graph_adjacency_matrix.java ================================================ /** * 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); } // 辺を追加 // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する 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); // 隣接行列に 1 行追加 List newRow = new ArrayList<>(n); for (int j = 0; j < n; j++) { newRow.add(0); } adjMat.add(newRow); // 隣接行列に 1 列追加 for (List row : adjMat) { row.add(0); } } /* 頂点を削除 */ 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 は vertices の要素インデックスに対応する public void addEdge(int i, int j) { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw new IndexOutOfBoundsException(); // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす adjMat.get(i).set(j, 1); adjMat.get(j).set(i, 1); } /* 辺を削除 */ // 引数 i, j は vertices の要素インデックスに対応する public void removeEdge(int i, int j) { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw new 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) { /* 無向グラフを初期化 */ // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 int[] vertices = { 1, 3, 2, 5, 4 }; int[][] edges = { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; GraphAdjMat graph = new GraphAdjMat(vertices, edges); System.out.println("\n初期化後のグラフ"); 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(); } } ================================================ FILE: ja/codes/java/chapter_graph/graph_bfs.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_graph/graph_dfs.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_greedy/coin_change_greedy.java ================================================ /** * 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) { // coins リストはソート済みと仮定する int i = coins.length - 1; int count = 0; // 残額がなくなるまで貪欲選択を繰り返す while (amt > 0) { // 残額以下で最も近い硬貨を見つける while (i > 0 && coins[i] > amt) { i--; } // coins[i] を選択する amt -= coins[i]; count++; } // 実行可能な解が見つからなければ -1 を返す return amt == 0 ? count : -1; } 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"); } } ================================================ FILE: ja/codes/java/chapter_greedy/fractional_knapsack.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_greedy/max_capacity.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_greedy/max_product_cutting.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_hashing/array_hash_map.java ================================================ /** * 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, "シャオハー"); map.put(15937, "シャオルオ"); map.put(16750, "シャオスワン"); map.put(13276, "シャオファー"); map.put(10583, "シャオヤー"); System.out.println("\n追加後のハッシュ表は\nKey -> Value"); map.print(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 String name = map.get(15937); System.out.println("\n学籍番号 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); } } } ================================================ FILE: ja/codes/java/chapter_hashing/built_in_hash.java ================================================ /** * 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 アルゴリズム"; int hashStr = str.hashCode(); System.out.println("文字列 " + str + " のハッシュ値は " + hashStr); Object[] arr = { 12836, "シャオハー" }; 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); } } ================================================ FILE: ja/codes/java/chapter_hashing/hash_map.java ================================================ /** * 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, "シャオハー"); map.put(15937, "シャオルオ"); map.put(16750, "シャオスワン"); map.put(13276, "シャオファー"); map.put(10583, "シャオヤー"); System.out.println("\n追加後のハッシュ表は\nKey -> Value"); PrintUtil.printHashMap(map); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 String name = map.get(15937); System.out.println("\n学籍番号 15937 を入力すると、氏名 " + name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (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); } } } ================================================ FILE: ja/codes/java/chapter_hashing/hash_map_chaining.java ================================================ /** * 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); // バケットを走査し、key が見つかれば対応する val を返す for (Pair pair : bucket) { if (pair.key == key) { return pair.val; } } // key が見つからない場合は null を返す return null; } /* 追加操作 */ void put(int key, String val) { // 負荷率がしきい値を超えたら、リサイズを実行 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); List bucket = buckets.get(index); // バケットを走査し、指定した key が見つかれば対応する val を更新して返す for (Pair pair : bucket) { if (pair.key == key) { pair.val = val; return; } } // その key が存在しなければ、キーと値のペアを末尾に追加 Pair pair = new Pair(key, val); bucket.add(pair); size++; } /* 削除操作 */ 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, "シャオハー"); map.put(15937, "シャオルオ"); map.put(16750, "シャオスワン"); map.put(13276, "シャオファー"); map.put(10583, "シャオヤー"); System.out.println("\n追加後のハッシュ表は\nKey -> Value"); map.print(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 String name = map.get(13276); System.out.println("\n学籍番号 13276 を入力すると、氏名 " + name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(12836); System.out.println("\n12836 を削除すると、ハッシュ表は\nKey -> Value"); map.print(); } } ================================================ FILE: ja/codes/java/chapter_hashing/hash_map_open_addressing.java ================================================ /** * 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, "シャオハー"); hashmap.put(15937, "シャオルオ"); hashmap.put(16750, "シャオスワン"); hashmap.put(13276, "シャオファー"); hashmap.put(10583, "シャオヤー"); System.out.println("\n追加後のハッシュ表は\nKey -> Value"); hashmap.print(); // 検索操作 // ハッシュテーブルにキー key を入力し、値 val を得る String name = hashmap.get(13276); System.out.println("\n学籍番号 13276 を入力すると、氏名 " + name); // 削除操作 // ハッシュテーブルからキーと値の組 (key, val) を削除する hashmap.remove(16750); System.out.println("\n16750 を削除すると、ハッシュ表は\nKey -> Value"); hashmap.print(); } } ================================================ FILE: ja/codes/java/chapter_hashing/simple_hash.java ================================================ /** * 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 アルゴリズム"; 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); } } ================================================ FILE: ja/codes/java/chapter_heap/heap.java ================================================ /** * 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<>(); // 最大ヒープを初期化する(lambda 式で 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); } } ================================================ FILE: ja/codes/java/chapter_heap/my_heap.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_heap/top_k.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_searching/binary_search.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_searching/binary_search_edge.java ================================================ /** * 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); } } } ================================================ FILE: ja/codes/java/chapter_searching/binary_search_insertion.java ================================================ /** * 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); } } } ================================================ FILE: ja/codes/java/chapter_searching/hashing_search.java ================================================ /** * 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) { // ハッシュテーブルの key: 目標要素、value: インデックス // ハッシュテーブルにこの key がなければ -1 を返す return map.getOrDefault(target, -1); } /* ハッシュ探索(連結リスト) */ static ListNode hashingSearchLinkedList(Map map, int target) { // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト // ハッシュテーブルにこの key がなければ 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); // key: 要素、value: インデックス } 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); // key: ノード値、value: ノード head = head.next; } ListNode node = hashingSearchLinkedList(map1, target); System.out.println("対象ノード値 3 に対応するノードオブジェクトは " + node); } } ================================================ FILE: ja/codes/java/chapter_searching/linear_search.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_searching/two_sum.java ================================================ /** * File: two_sum.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_searching; import java.util.*; public class two_sum { /* 方法 1:総当たり列挙 */ static int[] twoSumBruteForce(int[] nums, int target) { int size = nums.length; // 2重ループのため、時間計算量は O(n^2) for (int i = 0; i < size - 1; i++) { for (int j = i + 1; j < size; j++) { if (nums[i] + nums[j] == target) return new int[] { i, j }; } } return new int[0]; } /* 方法 2:補助ハッシュテーブル */ 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) { // ======= Test Case ======= int[] nums = { 2, 7, 11, 15 }; int target = 13; // ====== Driver Code ====== // 方法 1 int[] res = twoSumBruteForce(nums, target); System.out.println("方法1 res = " + Arrays.toString(res)); // 方法 2 res = twoSumHashTable(nums, target); System.out.println("方法2 res = " + Arrays.toString(res)); } } ================================================ FILE: ja/codes/java/chapter_sorting/bubble_sort.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_sorting/bucket_sort.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_sorting/counting_sort.java ================================================ /** * 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 は、num が res に最後に現れるインデックス for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. nums を逆順に走査し、各要素を結果配列 res に格納する // 結果を記録するための配列 res を初期化 int n = nums.length; int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // num を対応するインデックスに配置 counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る } // 結果配列 res で元の配列 nums を上書きする for (int i = 0; i < n; i++) { nums[i] = res[i]; } } 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)); } } ================================================ FILE: ja/codes/java/chapter_sorting/heap_sort.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_sorting/insertion_sort.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_sorting/merge_sort.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_sorting/quick_sort.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_sorting/radix_sort.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_sorting/selection_sort.java ================================================ /** * 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)); } } ================================================ FILE: ja/codes/java/chapter_stack_and_queue/array_deque.java ================================================ /** * 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; } // 先頭ポインタを左に 1 つ移動する // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする front = index(front - 1); // num をキュー先頭に追加 nums[front] = num; queSize++; } /* キュー末尾にエンキュー */ public void pushLast(int num) { if (queSize == capacity()) { System.out.println("双方向キューは満杯です"); return; } // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す int rear = index(front + queSize); // num をキュー末尾に追加 nums[rear] = num; queSize++; } /* キュー先頭からデキュー */ public int popFirst() { int num = peekFirst(); // 先頭ポインタを 1 つ後ろへ進める front = index(front + 1); queSize--; return num; } /* キュー末尾からデキュー */ public int popLast() { int num = peekLast(); queSize--; return num; } /* キュー先頭の要素にアクセス */ public int peekFirst() { if (isEmpty()) throw new IndexOutOfBoundsException(); return nums[front]; } /* キュー末尾の要素にアクセス */ public int peekLast() { if (isEmpty()) throw new IndexOutOfBoundsException(); // 末尾要素のインデックスを計算 int last = index(front + queSize - 1); return nums[last]; } /* 出力用の配列を返す */ public int[] toArray() { // 有効長の範囲内のリスト要素のみを変換 int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[index(j)]; } return res; } } public class array_deque { public static void main(String[] args) { /* 両端キューを初期化 */ ArrayDeque deque = new ArrayDeque(10); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); 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); /* 要素をエンキュー */ deque.pushLast(4); System.out.println("要素 4 を末尾にエンキューした後の deque = " + Arrays.toString(deque.toArray())); deque.pushFirst(1); System.out.println("要素 1 を先頭にエンキューした後の deque = " + Arrays.toString(deque.toArray())); /* 要素をデキュー */ int popLast = deque.popLast(); System.out.println("末尾からデキューした要素 = " + popLast + "、末尾からデキューした後の deque = " + Arrays.toString(deque.toArray())); int popFirst = deque.popFirst(); System.out.println("先頭からデキューした要素 = " + popFirst + "、先頭からデキューした後の deque = " + Arrays.toString(deque.toArray())); /* 両端キューの長さを取得 */ int size = deque.size(); System.out.println("双方向キューの長さ size = " + size); /* 両端キューが空かどうかを判定 */ boolean isEmpty = deque.isEmpty(); System.out.println("双方向キューが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/java/chapter_stack_and_queue/array_queue.java ================================================ /** * 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; } // 末尾ポインタを計算し、末尾インデックス + 1 を指す // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする int rear = (front + queSize) % capacity(); // num をキュー末尾に追加 nums[rear] = num; queSize++; } /* デキュー */ public int pop() { int num = peek(); // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す front = (front + 1) % capacity(); queSize--; return num; } /* キュー先頭の要素にアクセス */ public int peek() { if (isEmpty()) throw new IndexOutOfBoundsException(); return nums[front]; } /* 配列を返す */ public int[] toArray() { // 有効長の範囲内のリスト要素のみを変換 int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[j % 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 = " + pop + "、デキュー後の queue = " + 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); queue.pop(); System.out.println("第 " + i + " ラウンドのエンキュー + デキュー後の queue = " + Arrays.toString(queue.toArray())); } } } ================================================ FILE: ja/codes/java/chapter_stack_and_queue/array_stack.java ================================================ /** * 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 = " + pop + "、ポップ後の stack = " + Arrays.toString(stack.toArray())); /* スタックの長さを取得 */ int size = stack.size(); System.out.println("スタックの長さ size = " + size); /* 空かどうかを判定 */ boolean isEmpty = stack.isEmpty(); System.out.println("スタックが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/java/chapter_stack_and_queue/deque.java ================================================ /** * 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 = " + deque); int popFirst = deque.pollFirst(); System.out.println("先頭からデキューした要素 = " + popFirst + "、先頭からデキューした後の deque = " + deque); /* 両端キューの長さを取得 */ int size = deque.size(); System.out.println("双方向キューの長さ size = " + size); /* 両端キューが空かどうかを判定 */ boolean isEmpty = deque.isEmpty(); System.out.println("双方向キューが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/java/chapter_stack_and_queue/linkedlist_deque.java ================================================ /** * 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; // 先頭ノードを更新する // 末尾へのエンキュー操作 } else { // node を連結リストの末尾に追加 rear.next = node; node.prev = rear; rear = node; // 末尾ノードを更新する } queSize++; // キューの長さを更新 } /* キュー先頭にエンキュー */ public void pushFirst(int num) { push(num, true); } /* キュー末尾にエンキュー */ public void pushLast(int num) { push(num, false); } /* デキュー操作 */ 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; // 先頭ノードを更新する // キュー末尾からの取り出し } else { val = rear.val; // 末尾ノードの値を一時保存 // 末尾ノードを削除 ListNode rPrev = rear.prev; if (rPrev != null) { rPrev.next = null; rear.prev = null; } rear = rPrev; // 末尾ノードを更新する } queSize--; // キューの長さを更新 return val; } /* キュー先頭からデキュー */ public int popFirst() { return pop(true); } /* キュー末尾からデキュー */ public int popLast() { return pop(false); } /* キュー先頭の要素にアクセス */ public int peekFirst() { if (isEmpty()) throw new 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())); /* 要素にアクセス */ int peekFirst = deque.peekFirst(); System.out.println("先頭要素 peekFirst = " + peekFirst); int peekLast = deque.peekLast(); System.out.println("末尾要素 peekLast = " + peekLast); /* 要素をエンキュー */ deque.pushLast(4); System.out.println("要素 4 を末尾にエンキューした後の deque = " + Arrays.toString(deque.toArray())); deque.pushFirst(1); System.out.println("要素 1 を先頭にエンキューした後の deque = " + Arrays.toString(deque.toArray())); /* 要素をデキュー */ int popLast = deque.popLast(); System.out.println("末尾からデキューした要素 = " + popLast + "、末尾からデキューした後の deque = " + Arrays.toString(deque.toArray())); int popFirst = deque.popFirst(); System.out.println("先頭からデキューした要素 = " + popFirst + "、先頭からデキューした後の deque = " + Arrays.toString(deque.toArray())); /* 両端キューの長さを取得 */ int size = deque.size(); System.out.println("双方向キューの長さ size = " + size); /* 両端キューが空かどうかを判定 */ boolean isEmpty = deque.isEmpty(); System.out.println("双方向キューが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/java/chapter_stack_and_queue/linkedlist_queue.java ================================================ /** * 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; } /* 連結リストを Array に変換して返す */ 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 = " + pop + "、デキュー後の queue = " + Arrays.toString(queue.toArray())); /* キューの長さを取得 */ int size = queue.size(); System.out.println("キューの長さ size = " + size); /* キューが空かどうかを判定 */ boolean isEmpty = queue.isEmpty(); System.out.println("キューが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/java/chapter_stack_and_queue/linkedlist_stack.java ================================================ /** * 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 = " + pop + "、ポップ後の stack = " + Arrays.toString(stack.toArray())); /* スタックの長さを取得 */ int size = stack.size(); System.out.println("スタックの長さ size = " + size); /* 空かどうかを判定 */ boolean isEmpty = stack.isEmpty(); System.out.println("スタックが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/java/chapter_stack_and_queue/queue.java ================================================ /** * 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 = " + pop + "、デキュー後の queue = " + queue); /* キューの長さを取得 */ int size = queue.size(); System.out.println("キューの長さ size = " + size); /* キューが空かどうかを判定 */ boolean isEmpty = queue.isEmpty(); System.out.println("キューが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/java/chapter_stack_and_queue/stack.java ================================================ /** * 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 = " + pop + "、ポップ後の stack = " + stack); /* スタックの長さを取得 */ int size = stack.size(); System.out.println("スタックの長さ size = " + size); /* 空かどうかを判定 */ boolean isEmpty = stack.isEmpty(); System.out.println("スタックが空かどうか = " + isEmpty); } } ================================================ FILE: ja/codes/java/chapter_tree/array_binary_tree.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_tree/avl_tree.java ================================================ /** * 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 の場合、node をそのまま削除して返す if (child == null) return null; // 子ノード数 = 1 の場合、node をそのまま削除する else node = child; } else { // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える TreeNode temp = node.right; while (temp.left != null) { temp = temp.left; } node.right = removeHelper(node.right, temp.val); node.val = temp.val; } } updateHeight(node); // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node); // 部分木の根ノードを返す return node; } /* ノードを探索 */ 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); } } ================================================ FILE: ja/codes/java/chapter_tree/binary_search_tree.java ================================================ /** * 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 or 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); // tmp で cur を上書きする 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()); } } ================================================ FILE: ja/codes/java/chapter_tree/binary_tree.java ================================================ /** * 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); // n1 -> n2 の間にノード P を挿入 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); } } ================================================ FILE: ja/codes/java/chapter_tree/binary_tree_bfs.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/chapter_tree/binary_tree_dfs.java ================================================ /** * 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); } } ================================================ FILE: ja/codes/java/utils/ListNode.java ================================================ /** * 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; } } ================================================ FILE: ja/codes/java/utils/PrintUtil.java ================================================ /** * 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 { /* 行列を出力する(Array) */ public static void printMatrix(T[][] matrix) { System.out.println("["); for (T[] row : matrix) { System.out.println(" " + row + ","); } System.out.println("]"); } /* 行列を出力する(List) */ 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); } /** * 二分木を出力 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ public static void printTree(TreeNode root, Trunk prev, boolean isRight) { if (root == null) { return; } 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); } } ================================================ FILE: ja/codes/java/utils/TreeNode.java ================================================ /** * 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; } } ================================================ FILE: ja/codes/java/utils/Vertex.java ================================================ /** * 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; } } ================================================ FILE: ja/codes/javascript/.prettierrc ================================================ { "tabWidth": 4, "useTabs": false, "semi": true, "singleQuote": true } ================================================ FILE: ja/codes/javascript/chapter_array_and_linkedlist/array.js ================================================ /** * File: array.js * Created Time: 2022-11-27 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 要素へランダムアクセス */ function randomAccess(nums) { // 区間 [0, nums.length) からランダムに 1 つの数を選ぶ const random_index = Math.floor(Math.random() * nums.length); // ランダムな要素を取得して返す const random_num = nums[random_index]; return random_num; } /* 配列長を拡張する */ // JavaScript の Array は動的配列であり、直接拡張できます // 学習しやすいよう、本関数では Array を長さ不変の配列として扱います function extend(nums, enlarge) { // 拡張後の長さを持つ配列を初期化する const res = new Array(nums.length + enlarge).fill(0); // 元の配列の全要素を新しい配列にコピー for (let i = 0; i < nums.length; i++) { res[i] = nums[i]; } // 拡張後の新しい配列を返す return res; } /* 配列の index 番目に要素 num を挿入 */ function insert(nums, num, index) { // インデックス index 以降の全要素を 1 つ後ろへ移動する for (let i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // index の要素に num を代入する nums[index] = num; } /* index の要素を削除する */ function remove(nums, index) { // インデックス index より後ろの全要素を 1 つ前へ移動する for (let i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* 配列を走査 */ function traverse(nums) { let count = 0; // インデックスで配列を走査 for (let i = 0; i < nums.length; i++) { count += nums[i]; } // 配列要素を直接走査 for (const num of nums) { count += num; } } /* 配列内で指定要素を探す */ function find(nums, target) { for (let i = 0; i < nums.length; i++) { if (nums[i] === target) return i; } return -1; } /* Driver Code */ /* 配列を初期化 */ const arr = new Array(5).fill(0); console.log('配列 arr =', arr); let nums = [1, 3, 2, 5, 4]; console.log('配列 nums =', nums); /* ランダムアクセス */ let random_num = randomAccess(nums); console.log('nums からランダム要素を取得', random_num); /* 長さを拡張 */ nums = extend(nums, 3); console.log('配列の長さを 8 に拡張し,nums =', nums); /* 要素を挿入する */ insert(nums, 6, 3); console.log('インデックス 3 に数字 6 を挿入し,nums =', nums); /* 要素を削除 */ remove(nums, 2); console.log('インデックス 2 の要素を削除し,nums =', nums); /* 配列を走査 */ traverse(nums); /* 要素を探索する */ let index = find(nums, 3); console.log('nums 内で要素 3 を検索し,インデックス =', index); ================================================ FILE: ja/codes/javascript/chapter_array_and_linkedlist/linked_list.js ================================================ /** * File: linked_list.js * Created Time: 2022-12-12 * Author: IsChristina (christinaxia77@foxmail.com), Justin (xiefahit@gmail.com) */ const { printLinkedList } = require('../modules/PrintUtil'); const { ListNode } = require('../modules/ListNode'); /* 連結リストでノード n0 の後ろにノード P を挿入する */ function insert(n0, P) { const n1 = n0.next; P.next = n1; n0.next = P; } /* 連結リストでノード n0 の直後のノードを削除する */ function remove(n0) { if (!n0.next) return; // n0 -> P -> n1 const P = n0.next; const n1 = P.next; n0.next = n1; } /* 連結リスト内で index 番目のノードにアクセス */ function access(head, index) { for (let i = 0; i < index; i++) { if (!head) { return null; } head = head.next; } return head; } /* 連結リストで値が target の最初のノードを探す */ function find(head, target) { let index = 0; while (head !== null) { if (head.val === target) { return index; } head = head.next; index += 1; } return -1; } /* Driver Code */ /* 連結リストを初期化 */ // 各ノードを初期化 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // ノード間の参照を構築する n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; console.log('初期化された連結リストは'); printLinkedList(n0); /* ノードを挿入 */ insert(n0, new ListNode(0)); console.log('ノード挿入後の連結リストは'); printLinkedList(n0); /* ノードを削除 */ remove(n0); console.log('ノード削除後の連結リストは'); printLinkedList(n0); /* ノードにアクセス */ const node = access(n0, 3); console.log('連結リストのインデックス 3 のノードの値 = ' + node.val); /* ノードを探索 */ const index = find(n0, 2); console.log('連結リスト内で値が 2 のノードのインデックス = ' + index); ================================================ FILE: ja/codes/javascript/chapter_array_and_linkedlist/list.js ================================================ /** * File: list.js * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* リストを初期化 */ const nums = [1, 3, 2, 5, 4]; console.log(`リスト nums = ${nums}`); /* 要素にアクセス */ const num = nums[1]; console.log(`インデックス 1 の要素にアクセスし,num = ${num}`); /* 要素を更新 */ nums[1] = 0; console.log(`インデックス 1 の要素を 0 に更新し,nums = ${nums}`); /* リストを空にする */ nums.length = 0; console.log(`リストを空にした後,nums = ${nums}`); /* 末尾に要素を追加 */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); console.log(`要素追加後,nums = ${nums}`); /* 中間に要素を挿入 */ nums.splice(3, 0, 6); console.log(`インデックス 3 に数字 6 を挿入し,nums = ${nums}`); /* 要素を削除 */ nums.splice(3, 1); console.log(`インデックス 3 の要素を削除し,nums = ${nums}`); /* インデックスでリストを走査 */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* リスト要素を直接走査 */ count = 0; for (const x of nums) { count += x; } /* 2 つのリストを連結する */ const nums1 = [6, 8, 7, 10, 9]; nums.push(...nums1); console.log(`リスト nums1 を nums の後ろに連結し,nums = ${nums}`); /* リストをソート */ nums.sort((a, b) => a - b); console.log(`リストをソート後,nums = ${nums}`); ================================================ FILE: ja/codes/javascript/chapter_array_and_linkedlist/my_list.js ================================================ /** * File: my_list.js * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* リストクラス */ class MyList { #arr = new Array(); // 配列(リスト要素を格納) #capacity = 10; // リスト容量 #size = 0; // リストの長さ(現在の要素数) #extendRatio = 2; // リスト拡張時の増加倍率 /* コンストラクタ */ constructor() { this.#arr = new Array(this.#capacity); } /* リストの長さを取得(現在の要素数) */ size() { return this.#size; } /* リスト容量を取得する */ capacity() { return this.#capacity; } /* 要素にアクセス */ get(index) { // インデックスが範囲外なら例外を送出する。以下同様 if (index < 0 || index >= this.#size) throw new Error('インデックスが範囲外です'); return this.#arr[index]; } /* 要素を更新 */ set(index, num) { if (index < 0 || index >= this.#size) throw new Error('インデックスが範囲外です'); this.#arr[index] = num; } /* 末尾に要素を追加 */ add(num) { // 長さが容量に等しい場合は拡張が必要 if (this.#size === this.#capacity) { this.extendCapacity(); } // 新しい要素をリストの末尾に追加する this.#arr[this.#size] = num; this.#size++; } /* 中間に要素を挿入 */ insert(index, num) { if (index < 0 || index >= this.#size) throw new Error('インデックスが範囲外です'); // 要素数が容量を超えると、拡張機構が発動する if (this.#size === this.#capacity) { this.extendCapacity(); } // index 以降の要素をすべて 1 つ後ろへずらす for (let j = this.#size - 1; j >= index; j--) { this.#arr[j + 1] = this.#arr[j]; } // 要素数を更新 this.#arr[index] = num; this.#size++; } /* 要素を削除 */ remove(index) { if (index < 0 || index >= this.#size) throw new Error('インデックスが範囲外です'); let num = this.#arr[index]; // インデックス index より後の要素をすべて 1 つ前に移動する for (let j = index; j < this.#size - 1; j++) { this.#arr[j] = this.#arr[j + 1]; } // 要素数を更新 this.#size--; // 削除された要素を返す return num; } /* リストの拡張 */ extendCapacity() { // 元の配列の extendRatio 倍の長さを持つ新しい配列を作成し、元の配列をコピーする this.#arr = this.#arr.concat( new Array(this.capacity() * (this.#extendRatio - 1)) ); // リストの容量を更新 this.#capacity = this.#arr.length; } /* リストを配列に変換する */ toArray() { let size = this.size(); // 有効長の範囲内のリスト要素のみを変換 const arr = new Array(size); for (let i = 0; i < size; i++) { arr[i] = this.get(i); } return arr; } } /* Driver Code */ /* リストを初期化 */ const nums = new MyList(); /* 末尾に要素を追加 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); console.log( `リスト nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長さ = ${nums.size()}` ); /* 中間に要素を挿入 */ nums.insert(3, 6); console.log(`インデックス 3 に数字 6 を挿入し,nums = ${nums.toArray()}`); /* 要素を削除 */ nums.remove(3); console.log(`インデックス 3 の要素を削除し,nums = ${nums.toArray()}`); /* 要素にアクセス */ const num = nums.get(1); console.log(`インデックス 1 の要素にアクセスし,num = ${num}`); /* 要素を更新 */ nums.set(1, 0); console.log(`インデックス 1 の要素を 0 に更新し,nums = ${nums.toArray()}`); /* 拡張機構をテストする */ for (let i = 0; i < 10; i++) { // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する nums.add(i); } console.log( `拡張後のリスト nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長さ = ${nums.size()}` ); ================================================ FILE: ja/codes/javascript/chapter_backtracking/n_queens.js ================================================ /** * File: n_queens.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* バックトラッキング:N クイーン */ function backtrack(row, n, state, res, cols, diags1, diags2) { // すべての行への配置が完了したら、解を記録する if (row === n) { res.push(state.map((row) => row.slice())); return; } // すべての列を走査 for (let col = 0; col < n; col++) { // このマスに対応する主対角線と副対角線を計算 const diag1 = row - col + n - 1; const diag2 = row + col; // 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 試行:そのマスにクイーンを置く state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // 次の行に配置する backtrack(row + 1, n, state, res, cols, diags1, diags2); // 戻す:そのマスを空きマスに戻す state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* N クイーンを解く */ function nQueens(n) { // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す const state = Array.from({ length: n }, () => Array(n).fill('#')); const cols = Array(n).fill(false); // 列にクイーンがあるか記録 const diags1 = Array(2 * n - 1).fill(false); // 主対角線にクイーンがあるかを記録 const diags2 = Array(2 * n - 1).fill(false); // 副対角線にクイーンがあるかを記録 const res = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } // Driver Code const n = 4; const res = nQueens(n); console.log(`入力する盤面の縦横は ${n}`); console.log(`クイーン配置の解法は全部で ${res.length} 通り`); res.forEach((state) => { console.log('--------------------'); state.forEach((row) => console.log(row)); }); ================================================ FILE: ja/codes/javascript/chapter_backtracking/permutations_i.js ================================================ /** * File: permutations_i.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* バックトラッキング:順列 I */ function backtrack(state, choices, selected, res) { // 状態の長さが要素数に等しければ、解を記録 if (state.length === choices.length) { res.push([...state]); return; } // すべての選択肢を走査 choices.forEach((choice, i) => { // 枝刈り:要素の重複選択を許可しない if (!selected[i]) { // 試行: 選択を行い、状態を更新 selected[i] = true; state.push(choice); // 次の選択へ進む backtrack(state, choices, selected, res); // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.pop(); } }); } /* 全順列 I */ function permutationsI(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums = [1, 2, 3]; const res = permutationsI(nums); console.log(`入力配列 nums = ${JSON.stringify(nums)}`); console.log(`すべての順列 res = ${JSON.stringify(res)}`); ================================================ FILE: ja/codes/javascript/chapter_backtracking/permutations_ii.js ================================================ /** * File: permutations_ii.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* バックトラッキング:順列 II */ function backtrack(state, choices, selected, res) { // 状態の長さが要素数に等しければ、解を記録 if (state.length === choices.length) { res.push([...state]); return; } // すべての選択肢を走査 const duplicated = new Set(); choices.forEach((choice, i) => { // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない if (!selected[i] && !duplicated.has(choice)) { // 試行: 選択を行い、状態を更新 duplicated.add(choice); // 選択済みの要素値を記録 selected[i] = true; state.push(choice); // 次の選択へ進む backtrack(state, choices, selected, res); // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.pop(); } }); } /* 全順列 II */ function permutationsII(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums = [1, 2, 2]; const res = permutationsII(nums); console.log(`入力配列 nums = ${JSON.stringify(nums)}`); console.log(`すべての順列 res = ${JSON.stringify(res)}`); ================================================ FILE: ja/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js ================================================ /** * File: preorder_traversal_i_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 前順走査:例題 1 */ function preOrder(root, res) { if (root === null) { return; } if (root.val === 7) { // 解を記録 res.push(root); } preOrder(root.left, res); preOrder(root.right, res); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n二分木を初期化'); printTree(root); // 先行順走査 const res = []; preOrder(root, res); console.log('\n値が 7 のすべてのノードを出力'); console.log(res.map((node) => node.val)); ================================================ FILE: ja/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js ================================================ /** * File: preorder_traversal_ii_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 前順走査:例題 2 */ function preOrder(root, path, res) { if (root === null) { return; } // 試す path.push(root); if (root.val === 7) { // 解を記録 res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // バックトラック path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n二分木を初期化'); printTree(root); // 先行順走査 const path = []; const res = []; preOrder(root, path, res); console.log('\n根ノードからノード 7 までのすべての経路を出力'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: ja/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js ================================================ /** * File: preorder_traversal_iii_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 前順走査:例題 3 */ function preOrder(root, path, res) { // 枝刈り if (root === null || root.val === 3) { return; } // 試す path.push(root); if (root.val === 7) { // 解を記録 res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // バックトラック path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n二分木を初期化'); printTree(root); // 先行順走査 const path = []; const res = []; preOrder(root, path, res); console.log('\n根ノードからノード 7 までのすべての経路を出力し,経路には値が 3 のノードを含まない'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: ja/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js ================================================ /** * File: preorder_traversal_iii_template.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 現在の状態が解かどうかを判定 */ function isSolution(state) { return state && state[state.length - 1]?.val === 7; } /* 解を記録 */ function recordSolution(state, res) { res.push([...state]); } /* 現在の状態で、この選択が有効かどうかを判定 */ function isValid(state, choice) { return choice !== null && choice.val !== 3; } /* 状態を更新 */ function makeChoice(state, choice) { state.push(choice); } /* 状態を元に戻す */ function undoChoice(state) { state.pop(); } /* バックトラッキング:例題 3 */ function backtrack(state, choices, res) { // 解かどうかを確認 if (isSolution(state)) { // 解を記録 recordSolution(state, res); } // すべての選択肢を走査 for (const choice of choices) { // 枝刈り:選択が妥当かを確認する if (isValid(state, choice)) { // 試行: 選択を行い、状態を更新 makeChoice(state, choice); // 次の選択へ進む backtrack(state, [choice.left, choice.right], res); // バックトラック:選択を取り消し、前の状態に戻す undoChoice(state); } } } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n二分木を初期化'); printTree(root); // バックトラッキング法 const res = []; backtrack([], [root], res); console.log('\n根ノードからノード 7 までのすべての経路を出力し,経路には値が 3 のノードを含まないことを条件とする'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: ja/codes/javascript/chapter_backtracking/subset_sum_i.js ================================================ /** * File: subset_sum_i.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* バックトラッキング:部分和 I */ function backtrack(state, target, choices, start, res) { // 部分集合の和が target に等しければ、解を記録 if (target === 0) { res.push([...state]); return; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for (let i = start; i < choices.length; i++) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } // 試す:選択を行い、target と start を更新 state.push(choices[i]); // 次の選択へ進む backtrack(state, target - choices[i], choices, i, res); // バックトラック:選択を取り消し、前の状態に戻す state.pop(); } } /* 部分和 I を解く */ function subsetSumI(nums, target) { const state = []; // 状態(部分集合) nums.sort((a, b) => a - b); // nums をソート const start = 0; // 開始点を走査 const res = []; // 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumI(nums, target); console.log(`入力配列 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`和が ${target} に等しいすべての部分集合 res = ${JSON.stringify(res)}`); ================================================ FILE: ja/codes/javascript/chapter_backtracking/subset_sum_i_naive.js ================================================ /** * File: subset_sum_i_naive.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* バックトラッキング:部分和 I */ function backtrack(state, target, total, choices, res) { // 部分集合の和が target に等しければ、解を記録 if (total === target) { res.push([...state]); return; } // すべての選択肢を走査 for (let i = 0; i < choices.length; i++) { // 枝刈り:部分和が target を超える場合はその選択をスキップする if (total + choices[i] > target) { continue; } // 試行:選択を行い、要素と total を更新する state.push(choices[i]); // 次の選択へ進む backtrack(state, target, total + choices[i], choices, res); // バックトラック:選択を取り消し、前の状態に戻す state.pop(); } } /* 部分和 I を解く(重複部分集合を含む) */ function subsetSumINaive(nums, target) { const state = []; // 状態(部分集合) const total = 0; // 部分和 const res = []; // 結果リスト(部分集合のリスト) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumINaive(nums, target); console.log(`入力配列 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`和が ${target} に等しいすべての部分集合 res = ${JSON.stringify(res)}`); console.log('注意してください。この方法の出力結果には重複した集合が含まれます'); ================================================ FILE: ja/codes/javascript/chapter_backtracking/subset_sum_ii.js ================================================ /** * File: subset_sum_ii.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* バックトラッキング:部分和 II */ function backtrack(state, target, choices, start, res) { // 部分集合の和が target に等しければ、解を記録 if (target === 0) { res.push([...state]); return; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for (let i = start; i < choices.length; i++) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする if (i > start && choices[i] === choices[i - 1]) { continue; } // 試す:選択を行い、target と start を更新 state.push(choices[i]); // 次の選択へ進む backtrack(state, target - choices[i], choices, i + 1, res); // バックトラック:選択を取り消し、前の状態に戻す state.pop(); } } /* 部分和 II を解く */ function subsetSumII(nums, target) { const state = []; // 状態(部分集合) nums.sort((a, b) => a - b); // nums をソート const start = 0; // 開始点を走査 const res = []; // 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [4, 4, 5]; const target = 9; const res = subsetSumII(nums, target); console.log(`入力配列 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`和が ${target} に等しいすべての部分集合 res = ${JSON.stringify(res)}`); ================================================ FILE: ja/codes/javascript/chapter_computational_complexity/iteration.js ================================================ /** * File: iteration.js * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* for ループ */ function forLoop(n) { let res = 0; // 1, 2, ..., n-1, n を順に加算する for (let i = 1; i <= n; i++) { res += i; } return res; } /* while ループ */ function whileLoop(n) { let res = 0; let i = 1; // 条件変数を初期化する // 1, 2, ..., n-1, n を順に加算する while (i <= n) { res += i; i++; // 条件変数を更新する } return res; } /* while ループ(2回更新) */ function whileLoopII(n) { let res = 0; let i = 1; // 条件変数を初期化する // 1, 4, 10, ... を順に加算する while (i <= n) { res += i; // 条件変数を更新する i++; i *= 2; } return res; } /* 二重 for ループ */ function nestedForLoop(n) { let res = ''; // i = 1, 2, ..., n-1, n とループする for (let i = 1; i <= n; i++) { // j = 1, 2, ..., n-1, n とループする for (let j = 1; j <= n; j++) { res += `(${i}, ${j}), `; } } return res; } /* Driver Code */ const n = 5; let res; res = forLoop(n); console.log(`for ループの合計結果 res = ${res}`); res = whileLoop(n); console.log(`while ループの合計結果 res = ${res}`); res = whileLoopII(n); console.log(`while ループ(2 回更新)の合計結果 res = ${res}`); const resStr = nestedForLoop(n); console.log(`二重 for ループの走査結果 ${resStr}`); ================================================ FILE: ja/codes/javascript/chapter_computational_complexity/recursion.js ================================================ /** * File: recursion.js * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 再帰 */ function recur(n) { // 終了条件 if (n === 1) return 1; // 再帰:再帰呼び出し const res = recur(n - 1); // 帰りがけ:結果を返す return n + res; } /* 反復で再帰を模擬する */ function forLoopRecur(n) { // 明示的なスタックを使ってシステムコールスタックを模擬する const stack = []; let res = 0; // 再帰:再帰呼び出し for (let i = n; i > 0; i--) { // 「スタックへのプッシュ」で「再帰」を模擬する stack.push(i); } // 帰りがけ:結果を返す while (stack.length) { // 「スタックから取り出す操作」で「帰り」をシミュレート res += stack.pop(); } // res = 1+2+3+...+n return res; } /* 末尾再帰 */ function tailRecur(n, res) { // 終了条件 if (n === 0) return res; // 末尾再帰呼び出し return tailRecur(n - 1, res + n); } /* フィボナッチ数列:再帰 */ function fib(n) { // 終了条件 f(1) = 0, f(2) = 1 if (n === 1 || n === 2) return n - 1; // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す const res = fib(n - 1) + fib(n - 2); // 結果 f(n) を返す return res; } /* Driver Code */ const n = 5; let res; res = recur(n); console.log(`再帰関数の合計結果 res = ${res}`); res = forLoopRecur(n); console.log(`反復で再帰を模擬した合計結果 res = ${res}`); res = tailRecur(n, 0); console.log(`末尾再帰関数の合計結果 res = ${res}`); res = fib(n); console.log(`フィボナッチ数列の第 ${n} 項は ${res}`); ================================================ FILE: ja/codes/javascript/chapter_computational_complexity/space_complexity.js ================================================ /** * File: space_complexity.js * Created Time: 2023-02-05 * Author: Justin (xiefahit@gmail.com) */ const { ListNode } = require('../modules/ListNode'); const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 関数 */ function constFunc() { // 何らかの処理を行う return 0; } /* 定数階 */ function constant(n) { // 定数、変数、オブジェクトは O(1) の空間を占める const a = 0; const b = 0; const nums = new Array(10000); const node = new ListNode(0); // ループ内の変数は O(1) の空間を占める for (let i = 0; i < n; i++) { const c = 0; } // ループ内の関数は O(1) の空間を占める for (let i = 0; i < n; i++) { constFunc(); } } /* 線形階 */ function linear(n) { // 長さ n の配列は O(n) の空間を使用 const nums = new Array(n); // 長さ n のリストは O(n) の空間を使用 const nodes = []; for (let i = 0; i < n; i++) { nodes.push(new ListNode(i)); } // 長さ n のハッシュテーブルは O(n) の空間を使用 const map = new Map(); for (let i = 0; i < n; i++) { map.set(i, i.toString()); } } /* 線形時間(再帰実装) */ function linearRecur(n) { console.log(`再帰 n = ${n}`); if (n === 1) return; linearRecur(n - 1); } /* 二乗階 */ function quadratic(n) { // 行列は O(n^2) の空間を使用する const numMatrix = Array(n) .fill(null) .map(() => Array(n).fill(null)); // 二次元リストは O(n^2) の空間を使用 const numList = []; for (let i = 0; i < n; i++) { const tmp = []; for (let j = 0; j < n; j++) { tmp.push(0); } numList.push(tmp); } } /* 二次時間(再帰実装) */ function quadraticRecur(n) { if (n <= 0) return 0; const nums = new Array(n); console.log(`再帰 n = ${n} における nums の長さ = ${nums.length}`); return quadraticRecur(n - 1); } /* 指数時間(完全二分木の構築) */ function buildTree(n) { if (n === 0) return null; const root = new TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ const n = 5; // 定数階 constant(n); // 線形階 linear(n); linearRecur(n); // 二乗階 quadratic(n); quadraticRecur(n); // 指数オーダー const root = buildTree(n); printTree(root); ================================================ FILE: ja/codes/javascript/chapter_computational_complexity/time_complexity.js ================================================ /** * File: time_complexity.js * Created Time: 2023-01-02 * Author: RiverTwilight (contact@rene.wang) */ /* 定数階 */ function constant(n) { let count = 0; const size = 100000; for (let i = 0; i < size; i++) count++; return count; } /* 線形階 */ function linear(n) { let count = 0; for (let i = 0; i < n; i++) count++; return count; } /* 線形時間(配列を走査) */ function arrayTraversal(nums) { let count = 0; // ループ回数は配列長に比例する for (let i = 0; i < nums.length; i++) { count++; } return count; } /* 二乗階 */ function quadratic(n) { let count = 0; // ループ回数はデータサイズ n の二乗に比例する for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { count++; } } return count; } /* 二次時間(バブルソート) */ function bubbleSort(nums) { let count = 0; // カウンタ // 外側のループ:未ソート区間は [0, i] for (let i = nums.length - 1; i > 0; i--) { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 要素交換には 3 回の単位操作が含まれる } } } return count; } /* 指数時間(ループ実装) */ function exponential(n) { let count = 0, base = 1; // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する for (let i = 0; i < n; i++) { for (let j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指数時間(再帰実装) */ function expRecur(n) { if (n === 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 対数時間(ループ実装) */ function logarithmic(n) { let count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* 対数時間(再帰実装) */ function logRecur(n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* 線形対数時間 */ function linearLogRecur(n) { if (n <= 1) return 1; let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (let i = 0; i < n; i++) { count++; } return count; } /* 階乗時間(再帰実装) */ function factorialRecur(n) { if (n === 0) return 1; let count = 0; // 1個から n 個に分裂 for (let i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる const n = 8; console.log('入力データサイズ n = ' + n); let count = constant(n); console.log('定数時間の操作回数 = ' + count); count = linear(n); console.log('線形時間の操作回数 = ' + count); count = arrayTraversal(new Array(n)); console.log('線形時間(配列の走査)の操作回数 = ' + count); count = quadratic(n); console.log('二乗時間の操作回数 = ' + count); let nums = new Array(n); for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); console.log('二乗時間(バブルソート)の操作回数 = ' + count); count = exponential(n); console.log('指数時間(ループ実装)の操作回数 = ' + count); count = expRecur(n); console.log('指数時間(再帰実装)の操作回数 = ' + count); count = logarithmic(n); console.log('対数時間(ループ実装)の操作回数 = ' + count); count = logRecur(n); console.log('対数時間(再帰実装)の操作回数 = ' + count); count = linearLogRecur(n); console.log('線形対数時間(再帰実装)の操作回数 = ' + count); count = factorialRecur(n); console.log('階乗時間(再帰実装)の操作回数 = ' + count); ================================================ FILE: ja/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js ================================================ /** * File: worst_best_time_complexity.js * Created Time: 2023-01-05 * Author: RiverTwilight (contact@rene.wang) */ /* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ function randomNumbers(n) { const nums = Array(n); // 配列 nums = { 1, 2, 3, ..., n } を生成 for (let i = 0; i < n; i++) { nums[i] = i + 1; } // 配列要素をランダムにシャッフル for (let i = 0; i < n; i++) { const r = Math.floor(Math.random() * (i + 1)); const temp = nums[i]; nums[i] = nums[r]; nums[r] = temp; } return nums; } /* 配列 nums 内で数値 1 のインデックスを探す */ function findOne(nums) { for (let i = 0; i < nums.length; i++) { // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる if (nums[i] === 1) { return i; } } return -1; } /* Driver Code */ for (let i = 0; i < 10; i++) { const n = 100; const nums = randomNumbers(n); const index = findOne(nums); console.log('\n配列 [ 1, 2, ..., n ] をシャッフルした後 = [' + nums.join(', ') + ']'); console.log('数字 1 のインデックスは ' + index); } ================================================ FILE: ja/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js ================================================ /** * File: binary_search_recur.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 二分探索:問題 f(i, j) */ function dfs(nums, target, i, j) { // 区間が空なら対象要素は存在しないので -1 を返す if (i > j) { return -1; } // 中点インデックス m を計算 const m = i + ((j - i) >> 1); if (nums[m] < target) { // 部分問題 f(m+1, j) を再帰的に解く return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 部分問題 f(i, m-1) を再帰的に解く return dfs(nums, target, i, m - 1); } else { // 目標要素が見つかったらそのインデックスを返す return m; } } /* 二分探索 */ function binarySearch(nums, target) { const n = nums.length; // 問題 f(0, n-1) を解く return dfs(nums, target, 0, n - 1); } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分探索(両閉区間) const index = binarySearch(nums, target); console.log(`対象要素 6 のインデックス = ${index}`); ================================================ FILE: ja/codes/javascript/chapter_divide_and_conquer/build_tree.js ================================================ /** * File: build_tree.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ const { printTree } = require('../modules/PrintUtil'); const { TreeNode } = require('../modules/TreeNode'); /* 二分木を構築:分割統治 */ function dfs(preorder, inorderMap, i, l, r) { // 部分木区間が空なら終了する if (r - l < 0) return null; // ルートノードを初期化する const root = new TreeNode(preorder[i]); // m を求めて左右部分木を分割する const m = inorderMap.get(preorder[i]); // 部分問題:左部分木を構築する root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // 部分問題:右部分木を構築する root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // 根ノードを返す return root; } /* 二分木を構築 */ function buildTree(preorder, inorder) { // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する let inorderMap = new Map(); for (let i = 0; i < inorder.length; i++) { inorderMap.set(inorder[i], i); } const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ const preorder = [3, 9, 2, 1, 7]; const inorder = [9, 3, 1, 2, 7]; console.log('前順走査 = ' + JSON.stringify(preorder)); console.log('中順走査 = ' + JSON.stringify(inorder)); const root = buildTree(preorder, inorder); console.log('構築した二分木は:'); printTree(root); ================================================ FILE: ja/codes/javascript/chapter_divide_and_conquer/hanota.js ================================================ /** * File: hanota.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 円盤を 1 枚移動 */ function move(src, tar) { // src の上から円盤を1枚取り出す const pan = src.pop(); // 円盤を tar の上に置く tar.push(pan); } /* ハノイの塔の問題 f(i) を解く */ function dfs(i, src, buf, tar) { // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if (i === 1) { move(src, tar); return; } // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i - 1, src, tar, buf); // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src, tar); // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i - 1, buf, src, tar); } /* ハノイの塔を解く */ function solveHanota(A, B, C) { const n = A.length; // A の上から n 枚の円盤を B を介して C へ移す dfs(n, A, B, C); } /* Driver Code */ // リスト末尾が柱の頂上 const A = [5, 4, 3, 2, 1]; const B = []; const C = []; console.log('初期状態:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); solveHanota(A, B, C); console.log('円盤の移動完了後:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); ================================================ FILE: ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js ================================================ /** * File: climbing_stairs_backtrack.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* バックトラッキング */ function backtrack(choices, state, n, res) { // 第 n 段に到達したら、方法数を 1 増やす if (state === n) res.set(0, res.get(0) + 1); // すべての選択肢を走査 for (const choice of choices) { // 枝刈り: 第 n 段を超えないようにする if (state + choice > n) continue; // 試行: 選択を行い、状態を更新 backtrack(choices, state + choice, n, res); // バックトラック } } /* 階段登り:バックトラッキング */ function climbingStairsBacktrack(n) { const choices = [1, 2]; // 1 段または 2 段上ることを選べる const state = 0; // 第 0 段から上り始める const res = new Map(); res.set(0, 0); // res[0] を使って方法数を記録する backtrack(choices, state, n, res); return res.get(0); } /* Driver Code */ const n = 9; const res = climbingStairsBacktrack(n); console.log(`${n} 階の階段を上る方法は全部で ${res} 通り`); ================================================ FILE: ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js ================================================ /** * File: climbing_stairs_constraint_dp.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 制約付き階段登り:動的計画法 */ function climbingStairsConstraintDP(n) { if (n === 1 || n === 2) { return 1; } // 部分問題の解を保存するために dp テーブルを初期化 const dp = Array.from(new Array(n + 1), () => new Array(3)); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (let i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ const n = 9; const res = climbingStairsConstraintDP(n); console.log(`${n} 階の階段を上る方法は全部で ${res} 通り`); ================================================ FILE: ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js ================================================ /** * File: climbing_stairs_dfs.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 検索 */ function dfs(i) { // dp[1] と dp[2] は既知なので返す if (i === 1 || i === 2) return i; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1) + dfs(i - 2); return count; } /* 階段登り:探索 */ function climbingStairsDFS(n) { return dfs(n); } /* Driver Code */ const n = 9; const res = climbingStairsDFS(n); console.log(`${n} 階の階段を上る方法は全部で ${res} 通り`); ================================================ FILE: ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js ================================================ /** * File: climbing_stairs_dfs_mem.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* メモ化探索 */ function dfs(i, mem) { // dp[1] と dp[2] は既知なので返す if (i === 1 || i === 2) return i; // dp[i] の記録があれば、それをそのまま返す if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1, mem) + dfs(i - 2, mem); // dp[i] を記録する mem[i] = count; return count; } /* 階段登り:メモ化探索 */ function climbingStairsDFSMem(n) { // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す const mem = new Array(n + 1).fill(-1); return dfs(n, mem); } /* Driver Code */ const n = 9; const res = climbingStairsDFSMem(n); console.log(`${n} 階の階段を上る方法は全部で ${res} 通り`); ================================================ FILE: ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js ================================================ /** * File: climbing_stairs_dp.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 階段登り:動的計画法 */ function climbingStairsDP(n) { if (n === 1 || n === 2) return n; // 部分問題の解を保存するために dp テーブルを初期化 const dp = new Array(n + 1).fill(-1); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = 1; dp[2] = 2; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (let i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* 階段登り:空間最適化した動的計画法 */ function climbingStairsDPComp(n) { if (n === 1 || n === 2) return n; let a = 1, b = 2; for (let i = 3; i <= n; i++) { const tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ const n = 9; let res = climbingStairsDP(n); console.log(`${n} 階の階段を上る方法は全部で ${res} 通り`); res = climbingStairsDPComp(n); console.log(`${n} 階の階段を上る方法は全部で ${res} 通り`); ================================================ FILE: ja/codes/javascript/chapter_dynamic_programming/coin_change.js ================================================ /** * File: coin_change.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* コイン両替:動的計画法 */ function coinChangeDP(coins, amt) { const n = coins.length; const MAX = amt + 1; // dp テーブルを初期化 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 状態遷移:先頭行と先頭列 for (let a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状態遷移: 残りの行と列 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] !== MAX ? dp[n][amt] : -1; } /* コイン交換:空間最適化後の動的計画法 */ function coinChangeDPComp(coins, amt) { const n = coins.length; const MAX = amt + 1; // dp テーブルを初期化 const dp = Array.from({ length: amt + 1 }, () => MAX); dp[0] = 0; // 状態遷移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] !== MAX ? dp[amt] : -1; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 4; // 動的計画法 let res = coinChangeDP(coins, amt); console.log(`目標金額を作るのに必要な最小硬貨枚数は ${res}`); // 空間最適化後の動的計画法 res = coinChangeDPComp(coins, amt); console.log(`目標金額を作るのに必要な最小硬貨枚数は ${res}`); ================================================ FILE: ja/codes/javascript/chapter_dynamic_programming/coin_change_ii.js ================================================ /** * File: coin_change_ii.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* コイン両替 II:動的計画法 */ function coinChangeIIDP(coins, amt) { const n = coins.length; // dp テーブルを初期化 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 先頭列を初期化する for (let i = 0; i <= n; i++) { dp[i][0] = 1; } // 状態遷移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { // コイン i を選ばない場合と選ぶ場合の和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* コイン両替 II:空間最適化した動的計画法 */ function coinChangeIIDPComp(coins, amt) { const n = coins.length; // dp テーブルを初期化 const dp = Array.from({ length: amt + 1 }, () => 0); dp[0] = 1; // 状態遷移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // コイン i を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 5; // 動的計画法 let res = coinChangeIIDP(coins, amt); console.log(`目標金額を作る硬貨の組み合わせ数は ${res}`); // 空間最適化後の動的計画法 res = coinChangeIIDPComp(coins, amt); console.log(`目標金額を作る硬貨の組み合わせ数は ${res}`); ================================================ FILE: ja/codes/javascript/chapter_dynamic_programming/edit_distance.js ================================================ /** * File: edit_distance.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 編集距離:総当たり探索 */ function editDistanceDFS(s, t, i, j) { // s と t がともに空なら 0 を返す if (i === 0 && j === 0) return 0; // s が空なら t の長さを返す if (i === 0) return j; // t が空なら s の長さを返す if (j === 0) return i; // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 const insert = editDistanceDFS(s, t, i, j - 1); const del = editDistanceDFS(s, t, i - 1, j); const replace = editDistanceDFS(s, t, i - 1, j - 1); // 最小編集回数を返す return Math.min(insert, del, replace) + 1; } /* 編集距離:メモ化探索 */ function editDistanceDFSMem(s, t, mem, i, j) { // s と t がともに空なら 0 を返す if (i === 0 && j === 0) return 0; // s が空なら t の長さを返す if (i === 0) return j; // t が空なら s の長さを返す if (j === 0) return i; // 記録済みなら、それをそのまま返す if (mem[i][j] !== -1) return mem[i][j]; // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 const insert = editDistanceDFSMem(s, t, mem, i, j - 1); const del = editDistanceDFSMem(s, t, mem, i - 1, j); const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最小編集回数を記録して返す mem[i][j] = Math.min(insert, del, replace) + 1; return mem[i][j]; } /* 編集距離:動的計画法 */ function editDistanceDP(s, t) { const n = s.length, m = t.length; const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); // 状態遷移:先頭行と先頭列 for (let i = 1; i <= n; i++) { dp[i][0] = i; } for (let j = 1; j <= m; j++) { dp[0][j] = j; } // 状態遷移: 残りの行と列 for (let i = 1; i <= n; i++) { for (let j = 1; j <= m; j++) { if (s.charAt(i - 1) === t.charAt(j - 1)) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i - 1][j - 1]; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* 編集距離:空間最適化した動的計画法 */ function editDistanceDPComp(s, t) { const n = s.length, m = t.length; const dp = new Array(m + 1).fill(0); // 状態遷移:先頭行 for (let j = 1; j <= m; j++) { dp[j] = j; } // 状態遷移:残りの行 for (let i = 1; i <= n; i++) { // 状態遷移:先頭列 let leftup = dp[0]; // dp[i-1, j-1] を一時保存する dp[0] = i; // 状態遷移:残りの列 for (let j = 1; j <= m; j++) { const temp = dp[j]; if (s.charAt(i - 1) === t.charAt(j - 1)) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftup; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; } leftup = temp; // 次の反復の dp[i-1, j-1] に更新する } } return dp[m]; } const s = 'bag'; const t = 'pack'; const n = s.length, m = t.length; // 全探索 let res = editDistanceDFS(s, t, n, m); console.log(`${s} を ${t} に変更するには最少で ${res} 回の編集が必要`); // メモ化探索 const mem = Array.from(new Array(n + 1), () => new Array(m + 1).fill(-1)); res = editDistanceDFSMem(s, t, mem, n, m); console.log(`${s} を ${t} に変更するには最少で ${res} 回の編集が必要`); // 動的計画法 res = editDistanceDP(s, t); console.log(`${s} を ${t} に変更するには最少で ${res} 回の編集が必要`); // 空間最適化後の動的計画法 res = editDistanceDPComp(s, t); console.log(`${s} を ${t} に変更するには最少で ${res} 回の編集が必要`); ================================================ FILE: ja/codes/javascript/chapter_dynamic_programming/knapsack.js ================================================ /** * File: knapsack.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 0-1 ナップサック:総当たり探索 */ function knapsackDFS(wgt, val, i, c) { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i === 0 || c === 0) { return 0; } // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する const no = knapsackDFS(wgt, val, i - 1, c); const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // 2つの案のうち価値が大きいほうを返す return Math.max(no, yes); } /* 0-1 ナップサック:メモ化探索 */ function knapsackDFSMem(wgt, val, mem, i, c) { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i === 0 || c === 0) { return 0; } // 既に記録があればそのまま返す if (mem[i][c] !== -1) { return mem[i][c]; } // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する const no = knapsackDFSMem(wgt, val, mem, i - 1, c); const yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 2 つの案のうち価値が大きい方を記録して返す mem[i][c] = Math.max(no, yes); return mem[i][c]; } /* 0-1 ナップサック:動的計画法 */ function knapsackDP(wgt, val, cap) { const n = wgt.length; // dp テーブルを初期化 const dp = Array(n + 1) .fill(0) .map(() => Array(cap + 1).fill(0)); // 状態遷移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = Math.max( dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* 0-1 ナップサック:空間最適化後の動的計画法 */ function knapsackDPComp(wgt, val, cap) { const n = wgt.length; // dp テーブルを初期化 const dp = Array(cap + 1).fill(0); // 状態遷移 for (let i = 1; i <= n; i++) { // 逆順に走査する for (let c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // 全探索 let res = knapsackDFS(wgt, val, n, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); // メモ化探索 const mem = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => -1) ); res = knapsackDFSMem(wgt, val, mem, n, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); // 動的計画法 res = knapsackDP(wgt, val, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); // 空間最適化後の動的計画法 res = knapsackDPComp(wgt, val, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); ================================================ FILE: ja/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js ================================================ /** * File: min_cost_climbing_stairs_dp.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 階段登りの最小コスト:動的計画法 */ function minCostClimbingStairsDP(cost) { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } // 部分問題の解を保存するために dp テーブルを初期化 const dp = new Array(n + 1); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = cost[1]; dp[2] = cost[2]; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (let i = 3; i <= n; i++) { dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* 階段昇りの最小コスト:空間最適化後の動的計画法 */ function minCostClimbingStairsDPComp(cost) { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } let a = cost[1], b = cost[2]; for (let i = 3; i <= n; i++) { const tmp = b; b = Math.min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; console.log('入力された階段コストのリストは:', cost); let res = minCostClimbingStairsDP(cost); console.log(`階段を上り切る最小コストは:${res}`); res = minCostClimbingStairsDPComp(cost); console.log(`階段を上り切る最小コストは:${res}`); ================================================ FILE: ja/codes/javascript/chapter_dynamic_programming/min_path_sum.js ================================================ /** * File: min_path_sum.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 最小経路和:全探索 */ function minPathSumDFS(grid, i, j) { // 左上のセルなら探索を終了する if (i === 0 && j === 0) { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return Infinity; } // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する const up = minPathSumDFS(grid, i - 1, j); const left = minPathSumDFS(grid, i, j - 1); // 左上隅から (i, j) までの最小経路コストを返す return Math.min(left, up) + grid[i][j]; } /* 最小経路和:メモ化探索 */ function minPathSumDFSMem(grid, mem, i, j) { // 左上のセルなら探索を終了する if (i === 0 && j === 0) { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return Infinity; } // 既に記録があればそのまま返す if (mem[i][j] !== -1) { return mem[i][j]; } // 左と上のセルからの最小経路コスト const up = minPathSumDFSMem(grid, mem, i - 1, j); const left = minPathSumDFSMem(grid, mem, i, j - 1); // 左上から (i, j) までの最小経路コストを記録して返す mem[i][j] = Math.min(left, up) + grid[i][j]; return mem[i][j]; } /* 最小経路和:動的計画法 */ function minPathSumDP(grid) { const n = grid.length, m = grid[0].length; // dp テーブルを初期化 const dp = Array.from({ length: n }, () => Array.from({ length: m }, () => 0) ); dp[0][0] = grid[0][0]; // 状態遷移:先頭行 for (let j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 状態遷移:先頭列 for (let i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 状態遷移: 残りの行と列 for (let i = 1; i < n; i++) { for (let j = 1; j < m; j++) { dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* 最小経路和:空間最適化後の動的計画法 */ function minPathSumDPComp(grid) { const n = grid.length, m = grid[0].length; // dp テーブルを初期化 const dp = new Array(m); // 状態遷移:先頭行 dp[0] = grid[0][0]; for (let j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 状態遷移:残りの行 for (let i = 1; i < n; i++) { // 状態遷移:先頭列 dp[0] = dp[0] + grid[i][0]; // 状態遷移:残りの列 for (let j = 1; j < m; j++) { dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ const grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; const n = grid.length, m = grid[0].length; // 全探索 let res = minPathSumDFS(grid, n - 1, m - 1); console.log(`左上から右下までの最小経路和は ${res}`); // メモ化探索 const mem = Array.from({ length: n }, () => Array.from({ length: m }, () => -1) ); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); console.log(`左上から右下までの最小経路和は ${res}`); // 動的計画法 res = minPathSumDP(grid); console.log(`左上から右下までの最小経路和は ${res}`); // 空間最適化後の動的計画法 res = minPathSumDPComp(grid); console.log(`左上から右下までの最小経路和は ${res}`); ================================================ FILE: ja/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js ================================================ /** * File: unbounded_knapsack.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 完全ナップサック問題:動的計画法 */ function unboundedKnapsackDP(wgt, val, cap) { const n = wgt.length; // dp テーブルを初期化 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // 状態遷移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = Math.max( dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* 完全ナップサック問題:空間最適化後の動的計画法 */ function unboundedKnapsackDPComp(wgt, val, cap) { const n = wgt.length; // dp テーブルを初期化 const dp = Array.from({ length: cap + 1 }, () => 0); // 状態遷移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [1, 2, 3]; const val = [5, 11, 15]; const cap = 4; // 動的計画法 let res = unboundedKnapsackDP(wgt, val, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); // 空間最適化後の動的計画法 res = unboundedKnapsackDPComp(wgt, val, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); ================================================ FILE: ja/codes/javascript/chapter_graph/graph_adjacency_list.js ================================================ /** * File: graph_adjacency_list.js * Created Time: 2023-02-09 * Author: Justin (xiefahit@gmail.com) */ const { Vertex } = require('../modules/Vertex'); /* 隣接リストに基づく無向グラフクラス */ class GraphAdjList { // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 adjList; /* コンストラクタ */ constructor(edges) { this.adjList = new Map(); // すべての頂点と辺を追加 for (const edge of edges) { this.addVertex(edge[0]); this.addVertex(edge[1]); this.addEdge(edge[0], edge[1]); } } /* 頂点数を取得 */ size() { return this.adjList.size; } /* 辺を追加 */ addEdge(vet1, vet2) { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 ) { throw new Error('Illegal Argument Exception'); } // 辺 vet1 - vet2 を追加 this.adjList.get(vet1).push(vet2); this.adjList.get(vet2).push(vet1); } /* 辺を削除 */ removeEdge(vet1, vet2) { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 || this.adjList.get(vet1).indexOf(vet2) === -1 ) { throw new Error('Illegal Argument Exception'); } // 辺 vet1 - vet2 を削除 this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); } /* 頂点を追加 */ addVertex(vet) { if (this.adjList.has(vet)) return; // 隣接リストに新しいリストを追加 this.adjList.set(vet, []); } /* 頂点を削除 */ removeVertex(vet) { if (!this.adjList.has(vet)) { throw new Error('Illegal Argument Exception'); } // 隣接リストから頂点 vet に対応するリストを削除 this.adjList.delete(vet); // 他の頂点のリストを走査し、vet を含むすべての辺を削除 for (const set of this.adjList.values()) { const index = set.indexOf(vet); if (index > -1) { set.splice(index, 1); } } } /* 隣接リストを出力 */ print() { console.log('隣接リスト ='); for (const [key, value] of this.adjList) { const tmp = []; for (const vertex of value) { tmp.push(vertex.val); } console.log(key.val + ': ' + tmp.join()); } } } if (require.main === module) { /* Driver Code */ /* 無向グラフを初期化 */ const v0 = new Vertex(1), v1 = new Vertex(3), v2 = new Vertex(2), v3 = new Vertex(5), v4 = new Vertex(4); const edges = [ [v0, v1], [v1, v2], [v2, v3], [v0, v3], [v2, v4], [v3, v4], ]; const graph = new GraphAdjList(edges); console.log('\n初期化後のグラフは'); graph.print(); /* 辺を追加 */ // 頂点 1, 2 は v0, v2 graph.addEdge(v0, v2); console.log('\n辺 1-2 を追加した後のグラフは'); graph.print(); /* 辺を削除 */ // 頂点 1, 3 は v0, v1 graph.removeEdge(v0, v1); console.log('\n辺 1-3 を削除した後のグラフは'); graph.print(); /* 頂点を追加 */ const v5 = new Vertex(6); graph.addVertex(v5); console.log('\n頂点 6 を追加した後のグラフは'); graph.print(); /* 頂点を削除 */ // 頂点 3 は v1 graph.removeVertex(v1); console.log('\n頂点 3 を削除した後のグラフは'); graph.print(); } module.exports = { GraphAdjList, }; ================================================ FILE: ja/codes/javascript/chapter_graph/graph_adjacency_matrix.js ================================================ /** * File: graph_adjacency_matrix.js * Created Time: 2023-02-09 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 隣接行列に基づく無向グラフクラス */ class GraphAdjMat { vertices; // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す adjMat; // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 /* コンストラクタ */ constructor(vertices, edges) { this.vertices = []; this.adjMat = []; // 頂点を追加 for (const val of vertices) { this.addVertex(val); } // 辺を追加 // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する for (const e of edges) { this.addEdge(e[0], e[1]); } } /* 頂点数を取得 */ size() { return this.vertices.length; } /* 頂点を追加 */ addVertex(val) { const n = this.size(); // 頂点リストに新しい頂点の値を追加 this.vertices.push(val); // 隣接行列に 1 行追加 const newRow = []; for (let j = 0; j < n; j++) { newRow.push(0); } this.adjMat.push(newRow); // 隣接行列に 1 列追加 for (const row of this.adjMat) { row.push(0); } } /* 頂点を削除 */ removeVertex(index) { if (index >= this.size()) { throw new RangeError('Index Out Of Bounds Exception'); } // 頂点リストから index の頂点を削除する this.vertices.splice(index, 1); // 隣接行列で index 行を削除する this.adjMat.splice(index, 1); // 隣接行列で index 列を削除する for (const row of this.adjMat) { row.splice(index, 1); } } /* 辺を追加 */ // 引数 i, j は vertices の要素インデックスに対応する addEdge(i, j) { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } // 無向グラフでは、隣接行列は主対角線に関して対称であり、(i, j) === (j, i) を満たす this.adjMat[i][j] = 1; this.adjMat[j][i] = 1; } /* 辺を削除 */ // 引数 i, j は vertices の要素インデックスに対応する removeEdge(i, j) { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } this.adjMat[i][j] = 0; this.adjMat[j][i] = 0; } /* 隣接行列を出力 */ print() { console.log('頂点リスト = ', this.vertices); console.log('隣接行列 =', this.adjMat); } } /* Driver Code */ /* 無向グラフを初期化 */ // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 const vertices = [1, 3, 2, 5, 4]; const edges = [ [0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4], ]; const graph = new GraphAdjMat(vertices, edges); console.log('\n初期化後のグラフは'); graph.print(); /* 辺を追加 */ // 頂点 1, 2 のインデックスはそれぞれ 0, 2 graph.addEdge(0, 2); console.log('\n辺 1-2 を追加した後のグラフは'); graph.print(); /* 辺を削除 */ // 頂点 1, 3 のインデックスはそれぞれ 0, 1 graph.removeEdge(0, 1); console.log('\n辺 1-3 を削除した後のグラフは'); graph.print(); /* 頂点を追加 */ graph.addVertex(6); console.log('\n頂点 6 を追加した後のグラフは'); graph.print(); /* 頂点を削除 */ // 頂点 3 のインデックスは 1 graph.removeVertex(1); console.log('\n頂点 3 を削除した後のグラフは'); graph.print(); ================================================ FILE: ja/codes/javascript/chapter_graph/graph_bfs.js ================================================ /** * File: graph_bfs.js * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { GraphAdjList } = require('./graph_adjacency_list'); const { Vertex } = require('../modules/Vertex'); /* 幅優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする function graphBFS(graph, startVet) { // 頂点の走査順序 const res = []; // 訪問済み頂点を記録するためのハッシュ集合 const visited = new Set(); visited.add(startVet); // BFS の実装にキューを用いる const que = [startVet]; // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す while (que.length) { const vet = que.shift(); // 先頭の頂点をデキュー res.push(vet); // 訪問した頂点を記録 // この頂点のすべての隣接頂点を走査 for (const adjVet of graph.adjList.get(vet) ?? []) { if (visited.has(adjVet)) { continue; // 訪問済みの頂点をスキップ } que.push(adjVet); // 未訪問の頂点のみをキューに追加 visited.add(adjVet); // この頂点を訪問済みにする } } // 頂点の走査順を返す return res; } /* Driver Code */ /* 無向グラフを初期化 */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; const graph = new GraphAdjList(edges); console.log('\n初期化後のグラフは'); graph.print(); /* 幅優先探索 */ const res = graphBFS(graph, v[0]); console.log('\n幅優先探索(BFS)の頂点列は'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: ja/codes/javascript/chapter_graph/graph_dfs.js ================================================ /** * File: graph_dfs.js * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { Vertex } = require('../modules/Vertex'); const { GraphAdjList } = require('./graph_adjacency_list'); /* 深さ優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする function dfs(graph, visited, res, vet) { res.push(vet); // 訪問した頂点を記録 visited.add(vet); // この頂点を訪問済みにする // この頂点のすべての隣接頂点を走査 for (const adjVet of graph.adjList.get(vet)) { if (visited.has(adjVet)) { continue; // 訪問済みの頂点をスキップ } // 隣接頂点を再帰的に訪問 dfs(graph, visited, res, adjVet); } } /* 深さ優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする function graphDFS(graph, startVet) { // 頂点の走査順序 const res = []; // 訪問済み頂点を記録するためのハッシュ集合 const visited = new Set(); dfs(graph, visited, res, startVet); return res; } /* Driver Code */ /* 無向グラフを初期化 */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; const graph = new GraphAdjList(edges); console.log('\n初期化後のグラフは'); graph.print(); /* 深さ優先探索 */ const res = graphDFS(graph, v[0]); console.log('\n深さ優先探索(DFS)の頂点列は'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: ja/codes/javascript/chapter_greedy/coin_change_greedy.js ================================================ /** * File: coin_change_greedy.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* コイン交換:貪欲法 */ function coinChangeGreedy(coins, amt) { // coins 配列はソート済みと仮定する let i = coins.length - 1; let count = 0; // 残額がなくなるまで貪欲選択を繰り返す while (amt > 0) { // 残額以下で最も近い硬貨を見つける while (i > 0 && coins[i] > amt) { i--; } // coins[i] を選択する amt -= coins[i]; count++; } // 実行可能な解が見つからなければ -1 を返す return amt === 0 ? count : -1; } /* Driver Code */ // 貪欲法:大域最適解を保証できる let coins = [1, 5, 10, 20, 50, 100]; let amt = 186; let res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`${amt} を作るのに必要な最小硬貨枚数は ${res}`); // 貪欲法:大域最適解を保証できない coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`${amt} を作るのに必要な最小硬貨枚数は ${res}`); console.log('実際に必要な最小枚数は 3、つまり 20 + 20 + 20'); // 貪欲法:大域最適解を保証できない coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`${amt} を作るのに必要な最小硬貨枚数は ${res}`); console.log('実際に必要な最小枚数は 2、つまり 49 + 49'); ================================================ FILE: ja/codes/javascript/chapter_greedy/fractional_knapsack.js ================================================ /** * File: fractional_knapsack.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 品物 */ class Item { constructor(w, v) { this.w = w; // 品物の重さ this.v = v; // 品物の価値 } } /* 分数ナップサック:貪欲法 */ function fractionalKnapsack(wgt, val, cap) { // 重さと価値の 2 属性を持つ品物リストを作成 const items = wgt.map((w, i) => new Item(w, val[i])); // 単位価値 item.v / item.w の高い順にソートする items.sort((a, b) => b.v / b.w - a.v / a.w); // 貪欲選択を繰り返す let res = 0; for (const item of items) { if (item.w <= cap) { // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる res += item.v; cap -= item.w; } else { // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる res += (item.v / item.w) * cap; // 残り容量がないため、ループを抜ける break; } } return res; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // 貪欲法 const res = fractionalKnapsack(wgt, val, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); ================================================ FILE: ja/codes/javascript/chapter_greedy/max_capacity.js ================================================ /** * File: max_capacity.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 最大容量:貪欲法 */ function maxCapacity(ht) { // i, j を初期化し、それぞれ配列の両端に置く let i = 0, j = ht.length - 1; // 初期の最大容量は 0 let res = 0; // 2 枚の板が出会うまで貪欲選択を繰り返す while (i < j) { // 最大容量を更新する const cap = Math.min(ht[i], ht[j]) * (j - i); res = Math.max(res, cap); // 短い方を内側へ動かす if (ht[i] < ht[j]) { i += 1; } else { j -= 1; } } return res; } /* Driver Code */ const ht = [3, 8, 5, 2, 7, 7, 3, 4]; // 貪欲法 const res = maxCapacity(ht); console.log(`最大容量は ${res}`); ================================================ FILE: ja/codes/javascript/chapter_greedy/max_product_cutting.js ================================================ /** * File: max_product_cutting.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 最大切断積:貪欲法 */ function maxProductCutting(n) { // n <= 3 のときは、必ず 1 を切り出す if (n <= 3) { return 1 * (n - 1); } // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする let a = Math.floor(n / 3); let b = n % 3; if (b === 1) { // 余りが 1 のときは、1 * 3 を 2 * 2 に変える return Math.pow(3, a - 1) * 2 * 2; } if (b === 2) { // 余りが 2 のときは、そのままにする return Math.pow(3, a) * 2; } // 余りが 0 のときは、そのままにする return Math.pow(3, a); } /* Driver Code */ let n = 58; // 貪欲法 let res = maxProductCutting(n); console.log(`最大分割積は ${res}`); ================================================ FILE: ja/codes/javascript/chapter_hashing/array_hash_map.js ================================================ /** * File: array_hash_map.js * Created Time: 2022-12-26 * Author: Justin (xiefahit@gmail.com) */ /* キーと値の組 Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* 配列ベースのハッシュテーブル */ class ArrayHashMap { #buckets; constructor() { // 100 個のバケットを含む配列を初期化 this.#buckets = new Array(100).fill(null); } /* ハッシュ関数 */ #hashFunc(key) { return key % 100; } /* 検索操作 */ get(key) { let index = this.#hashFunc(key); let pair = this.#buckets[index]; if (pair === null) return null; return pair.val; } /* 追加操作 */ set(key, val) { let index = this.#hashFunc(key); this.#buckets[index] = new Pair(key, val); } /* 削除操作 */ delete(key) { let index = this.#hashFunc(key); // null に設定し、削除を表す this.#buckets[index] = null; } /* すべてのキーと値のペアを取得 */ entries() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i]); } } return arr; } /* すべてのキーを取得 */ keys() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i].key); } } return arr; } /* すべての値を取得 */ values() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i].val); } } return arr; } /* ハッシュテーブルを出力 */ print() { let pairSet = this.entries(); for (const pair of pairSet) { console.info(`${pair.key} -> ${pair.val}`); } } } /* Driver Code */ /* ハッシュテーブルを初期化 */ const map = new ArrayHashMap(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.set(12836, 'シャオハー'); map.set(15937, 'シャオルオ'); map.set(16750, 'シャオスワン'); map.set(13276, 'シャオファー'); map.set(10583, 'シャオヤー'); console.info('\n追加完了後、ハッシュ表は\nKey -> Value'); map.print(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 let name = map.get(15937); console.info('\n学籍番号 15937 を入力すると、氏名 ' + name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.delete(10583); console.info('\n10583 を削除した後、ハッシュ表は\nKey -> Value'); map.print(); /* ハッシュテーブルを走査 */ console.info('\nキーと値のペア Key->Value を走査'); for (const pair of map.entries()) { if (!pair) continue; console.info(pair.key + ' -> ' + pair.val); } console.info('\nキー Key を個別に走査'); for (const key of map.keys()) { console.info(key); } console.info('\n値 Value を個別に走査'); for (const val of map.values()) { console.info(val); } ================================================ FILE: ja/codes/javascript/chapter_hashing/hash_map.js ================================================ /** * File: hash_map.js * Created Time: 2022-12-26 * Author: Justin (xiefahit@gmail.com) */ /* Driver Code */ /* ハッシュテーブルを初期化 */ const map = new Map(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.set(12836, 'シャオハー'); map.set(15937, 'シャオルオ'); map.set(16750, 'シャオスワン'); map.set(13276, 'シャオファー'); map.set(10583, 'シャオヤー'); console.info('\n追加完了後、ハッシュ表は\nKey -> Value'); console.info(map); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 let name = map.get(15937); console.info('\n学籍番号 15937 を入力すると、氏名 ' + name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.delete(10583); console.info('\n10583 を削除した後、ハッシュ表は\nKey -> Value'); console.info(map); /* ハッシュテーブルを走査 */ console.info('\nキーと値のペア Key->Value を走査'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\nキー Key を個別に走査'); for (const k of map.keys()) { console.info(k); } console.info('\n値 Value を個別に走査'); for (const v of map.values()) { console.info(v); } ================================================ FILE: ja/codes/javascript/chapter_hashing/hash_map_chaining.js ================================================ /** * File: hash_map_chaining.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* キーと値の組 Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* チェイン法ハッシュテーブル */ class HashMapChaining { #size; // キーと値のペア数 #capacity; // ハッシュテーブル容量 #loadThres; // リサイズを発動する負荷率のしきい値 #extendRatio; // 拡張倍率 #buckets; // バケット配列 /* コンストラクタ */ constructor() { this.#size = 0; this.#capacity = 4; this.#loadThres = 2.0 / 3.0; this.#extendRatio = 2; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); } /* ハッシュ関数 */ #hashFunc(key) { return key % this.#capacity; } /* 負荷率 */ #loadFactor() { return this.#size / this.#capacity; } /* 検索操作 */ get(key) { const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // バケットを走査し、key が見つかれば対応する val を返す for (const pair of bucket) { if (pair.key === key) { return pair.val; } } // key が見つからない場合は null を返す return null; } /* 追加操作 */ put(key, val) { // 負荷率がしきい値を超えたら、リサイズを実行 if (this.#loadFactor() > this.#loadThres) { this.#extend(); } const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // バケットを走査し、指定した key が見つかれば対応する val を更新して返す for (const pair of bucket) { if (pair.key === key) { pair.val = val; return; } } // その key が存在しなければ、キーと値のペアを末尾に追加 const pair = new Pair(key, val); bucket.push(pair); this.#size++; } /* 削除操作 */ remove(key) { const index = this.#hashFunc(key); let bucket = this.#buckets[index]; // バケットを走査してキーと値のペアを削除 for (let i = 0; i < bucket.length; i++) { if (bucket[i].key === key) { bucket.splice(i, 1); this.#size--; break; } } } /* ハッシュテーブルを拡張 */ #extend() { // 元のハッシュテーブルを一時保存 const bucketsTmp = this.#buckets; // リサイズ後の新しいハッシュテーブルを初期化 this.#capacity *= this.#extendRatio; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); this.#size = 0; // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for (const bucket of bucketsTmp) { for (const pair of bucket) { this.put(pair.key, pair.val); } } } /* ハッシュテーブルを出力 */ print() { for (const bucket of this.#buckets) { let res = []; for (const pair of bucket) { res.push(pair.key + ' -> ' + pair.val); } console.log(res); } } } /* Driver Code */ /* ハッシュテーブルを初期化 */ const map = new HashMapChaining(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.put(12836, 'シャオハー'); map.put(15937, 'シャオルオ'); map.put(16750, 'シャオスワン'); map.put(13276, 'シャオファー'); map.put(10583, 'シャオヤー'); console.log('\n追加完了後、ハッシュ表は\nKey -> Value'); map.print(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 const name = map.get(13276); console.log('\n学籍番号 13276 を入力すると、名前 ' + name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(12836); console.log('\n12836 を削除した後、ハッシュテーブルは\nKey -> Value'); map.print(); ================================================ FILE: ja/codes/javascript/chapter_hashing/hash_map_open_addressing.js ================================================ /** * File: hashMapOpenAddressing.js * Created Time: 2023-06-13 * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) */ /* キーと値の組 Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* オープンアドレス法ハッシュテーブル */ class HashMapOpenAddressing { #size; // キーと値のペア数 #capacity; // ハッシュテーブル容量 #loadThres; // リサイズを発動する負荷率のしきい値 #extendRatio; // 拡張倍率 #buckets; // バケット配列 #TOMBSTONE; // 削除済みマーク /* コンストラクタ */ constructor() { this.#size = 0; // キーと値のペア数 this.#capacity = 4; // ハッシュテーブル容量 this.#loadThres = 2.0 / 3.0; // リサイズを発動する負荷率のしきい値 this.#extendRatio = 2; // 拡張倍率 this.#buckets = Array(this.#capacity).fill(null); // バケット配列 this.#TOMBSTONE = new Pair(-1, '-1'); // 削除済みマーク } /* ハッシュ関数 */ #hashFunc(key) { return key % this.#capacity; } /* 負荷率 */ #loadFactor() { return this.#size / this.#capacity; } /* key に対応するバケットインデックスを探す */ #findBucket(key) { let index = this.#hashFunc(key); let firstTombstone = -1; // 線形プロービングを行い、空バケットに達したら終了 while (this.#buckets[index] !== null) { // key が見つかったら、対応するバケットのインデックスを返す if (this.#buckets[index].key === key) { // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 if (firstTombstone !== -1) { this.#buckets[firstTombstone] = this.#buckets[index]; this.#buckets[index] = this.#TOMBSTONE; return firstTombstone; // 移動後のバケットインデックスを返す } return index; // バケットのインデックスを返す } // 最初に見つかった削除マークを記録 if ( firstTombstone === -1 && this.#buckets[index] === this.#TOMBSTONE ) { firstTombstone = index; } // バケットのインデックスを計算し、末尾を越えたら先頭に戻る index = (index + 1) % this.#capacity; } // key が存在しない場合は追加位置のインデックスを返す return firstTombstone === -1 ? index : firstTombstone; } /* 検索操作 */ get(key) { // key に対応するバケットインデックスを探す const index = this.#findBucket(key); // キーと値の組が見つかったら、対応する val を返す if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { return this.#buckets[index].val; } // キーと値の組が存在しなければ null を返す return null; } /* 追加操作 */ put(key, val) { // 負荷率がしきい値を超えたら、リサイズを実行 if (this.#loadFactor() > this.#loadThres) { this.#extend(); } // key に対応するバケットインデックスを探す const index = this.#findBucket(key); // キーと値の組が見つかったら、val を上書きして返す if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { this.#buckets[index].val = val; return; } // キーと値の組が存在しない場合は、その組を追加する this.#buckets[index] = new Pair(key, val); this.#size++; } /* 削除操作 */ remove(key) { // key に対応するバケットインデックスを探す const index = this.#findBucket(key); // キーと値の組が見つかったら、削除マーカーで上書きする if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { this.#buckets[index] = this.#TOMBSTONE; this.#size--; } } /* ハッシュテーブルを拡張 */ #extend() { // 元のハッシュテーブルを一時保存 const bucketsTmp = this.#buckets; // リサイズ後の新しいハッシュテーブルを初期化 this.#capacity *= this.#extendRatio; this.#buckets = Array(this.#capacity).fill(null); this.#size = 0; // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for (const pair of bucketsTmp) { if (pair !== null && pair !== this.#TOMBSTONE) { this.put(pair.key, pair.val); } } } /* ハッシュテーブルを出力 */ print() { for (const pair of this.#buckets) { if (pair === null) { console.log('null'); } else if (pair === this.#TOMBSTONE) { console.log('TOMBSTONE'); } else { console.log(pair.key + ' -> ' + pair.val); } } } } /* Driver Code */ // ハッシュテーブルを初期化 const hashmap = new HashMapOpenAddressing(); // 追加操作 // ハッシュテーブルにキーと値の組 (key, val) を追加する hashmap.put(12836, 'シャオハー'); hashmap.put(15937, 'シャオルオ'); hashmap.put(16750, 'シャオスワン'); hashmap.put(13276, 'シャオファー'); hashmap.put(10583, 'シャオヤー'); console.log('\n追加完了後、ハッシュ表は\nKey -> Value'); hashmap.print(); // 検索操作 // ハッシュテーブルにキー key を入力し、値 val を得る const name = hashmap.get(13276); console.log('\n学籍番号 13276 を入力すると、名前 ' + name); // 削除操作 // ハッシュテーブルからキーと値の組 (key, val) を削除する hashmap.remove(16750); console.log('\n16750 を削除した後、ハッシュテーブルは\nKey -> Value'); hashmap.print(); ================================================ FILE: ja/codes/javascript/chapter_hashing/simple_hash.js ================================================ /** * File: simple_hash.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 加算ハッシュ */ function addHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* 乗算ハッシュ */ function mulHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (31 * hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* XOR ハッシュ */ function xorHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash ^= c.charCodeAt(0); } return hash % MODULUS; } /* 回転ハッシュ */ function rotHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; } return hash; } /* Driver Code */ const key = 'Hello アルゴリズム'; let hash = addHash(key); console.log('加算ハッシュ値は ' + hash); hash = mulHash(key); console.log('乗算ハッシュ値は ' + hash); hash = xorHash(key); console.log('XORハッシュ値は ' + hash); hash = rotHash(key); console.log('回転ハッシュ値は ' + hash); ================================================ FILE: ja/codes/javascript/chapter_heap/my_heap.js ================================================ /** * File: my_heap.js * Created Time: 2023-02-06 * Author: what-is-me (whatisme@outlook.jp) */ const { printHeap } = require('../modules/PrintUtil'); /* 最大ヒープクラス */ class MaxHeap { #maxHeap; /* コンストラクタ。空のヒープを作成するか、入力リストからヒープを構築する */ constructor(nums) { // リスト要素をそのままヒープに追加 this.#maxHeap = nums === undefined ? [] : [...nums]; // 葉ノード以外のすべてのノードをヒープ化 for (let i = this.#parent(this.size() - 1); i >= 0; i--) { this.#siftDown(i); } } /* 左子ノードのインデックスを取得 */ #left(i) { return 2 * i + 1; } /* 右子ノードのインデックスを取得 */ #right(i) { return 2 * i + 2; } /* 親ノードのインデックスを取得 */ #parent(i) { return Math.floor((i - 1) / 2); // 切り捨て除算 } /* 要素を交換 */ #swap(i, j) { const tmp = this.#maxHeap[i]; this.#maxHeap[i] = this.#maxHeap[j]; this.#maxHeap[j] = tmp; } /* ヒープのサイズを取得 */ size() { return this.#maxHeap.length; } /* ヒープが空かどうかを判定 */ isEmpty() { return this.size() === 0; } /* ヒープ先頭要素にアクセス */ peek() { return this.#maxHeap[0]; } /* 要素をヒープに追加 */ push(val) { // ノードを追加 this.#maxHeap.push(val); // 下から上へヒープ化 this.#siftUp(this.size() - 1); } /* ノード i から始めて、下から上へヒープ化 */ #siftUp(i) { while (true) { // ノード i の親ノードを取得 const p = this.#parent(i); // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break; // 2 つのノードを交換 this.#swap(i, p); // ループで下から上へヒープ化 i = p; } } /* 要素をヒープから取り出す */ pop() { // 空判定の処理 if (this.isEmpty()) throw new Error('ヒープが空です'); // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) this.#swap(0, this.size() - 1); // ノードを削除 const val = this.#maxHeap.pop(); // 上から下へヒープ化 this.#siftDown(0); // ヒープ先頭要素を返す return val; } /* ノード i から始めて、上から下へヒープ化 */ #siftDown(i) { while (true) { // ノード i, l, r のうち値が最大のノードを ma とする const l = this.#left(i), r = this.#right(i); let ma = i; if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l; if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r; // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma === i) break; // 2 つのノードを交換 this.#swap(i, ma); // ループで上から下へヒープ化 i = ma; } } /* ヒープ(二分木)を出力 */ print() { printHeap(this.#maxHeap); } /* ヒープから要素を取り出す */ getMaxHeap() { return this.#maxHeap; } } /* Driver Code */ if (require.main === module) { /* 最大ヒープを初期化 */ const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); console.log('\nリストを入力してヒープを構築した後'); maxHeap.print(); /* ヒープ頂点の要素を取得 */ let peek = maxHeap.peek(); console.log(`\nヒープの先頭要素は ${peek}`); /* 要素をヒープに追加 */ let val = 7; maxHeap.push(val); console.log(`\n要素 ${val} をヒープに追加した後`); maxHeap.print(); /* ヒープ頂点の要素を取り出す */ peek = maxHeap.pop(); console.log(`\nヒープ先頭要素 ${peek} を取り出した後`); maxHeap.print(); /* ヒープのサイズを取得 */ let size = maxHeap.size(); console.log(`\nヒープ要素数は ${size}`); /* ヒープが空かどうかを判定 */ let isEmpty = maxHeap.isEmpty(); console.log(`\nヒープが空かどうかは ${isEmpty}`); } module.exports = { MaxHeap, }; ================================================ FILE: ja/codes/javascript/chapter_heap/top_k.js ================================================ /** * File: top_k.js * Created Time: 2023-08-13 * Author: Justin (xiefahit@gmail.com) */ const { MaxHeap } = require('./my_heap'); /* 要素をヒープに追加 */ function pushMinHeap(maxHeap, val) { // 要素を反転する maxHeap.push(-val); } /* 要素をヒープから取り出す */ function popMinHeap(maxHeap) { // 要素を反転する return -maxHeap.pop(); } /* ヒープ先頭要素にアクセス */ function peekMinHeap(maxHeap) { // 要素を反転する return -maxHeap.peek(); } /* ヒープから要素を取り出す */ function getMinHeap(maxHeap) { // 要素を反転する return maxHeap.getMaxHeap().map((num) => -num); } /* ヒープに基づいて配列中の最大の k 個の要素を探す */ function topKHeap(nums, k) { // 最小ヒープを初期化する // 注意: ヒープ内の全要素を反転し、最大ヒープで最小ヒープをシミュレートする const maxHeap = new MaxHeap([]); // 配列の先頭 k 個の要素をヒープに追加 for (let i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // k+1 番目の要素から開始し、ヒープ長を k に保つ for (let i = k; i < nums.length; i++) { // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } // ヒープ内の要素を返す return getMinHeap(maxHeap); } /* Driver Code */ const nums = [1, 7, 6, 3, 2]; const k = 3; const res = topKHeap(nums, k); console.log(`最大の ${k} 個の要素は`, res); ================================================ FILE: ja/codes/javascript/chapter_searching/binary_search.js ================================================ /** * File: binary_search.js * Created Time: 2022-12-22 * Author: JoseHung (szhong@link.cuhk.edu.hk) */ /* 二分探索(両閉区間) */ function binarySearch(nums, target) { // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す let i = 0, j = nums.length - 1; // ループし、探索区間が空になったら終了する(i > j で空) while (i <= j) { // 中点インデックス `m` を計算し、`parseInt()` で切り捨てる const m = parseInt(i + (j - i) / 2); if (nums[m] < target) // この場合、target は区間 [m+1, j] にある i = m + 1; else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある j = m - 1; else return m; // 目標要素が見つかったらそのインデックスを返す } // 目標要素が見つからなければ -1 を返す return -1; } /* 二分探索(左閉右開区間) */ function binarySearchLCRO(nums, target) { // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す let i = 0, j = nums.length; // ループし、探索区間が空になったら終了する(i = j で空) while (i < j) { // 中点インデックス `m` を計算し、`parseInt()` で切り捨てる const m = parseInt(i + (j - i) / 2); if (nums[m] < target) // この場合、target は区間 [m+1, j) にある i = m + 1; else if (nums[m] > target) // この場合、target は区間 [i, m) にある j = m; // 目標要素が見つかったらそのインデックスを返す else return m; } // 目標要素が見つからなければ -1 を返す return -1; } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分探索(両閉区間) */ let index = binarySearch(nums, target); console.log('目標要素 6 のインデックス = ' + index); /* 二分探索(左閉右開区間) */ index = binarySearchLCRO(nums, target); console.log('目標要素 6 のインデックス = ' + index); ================================================ FILE: ja/codes/javascript/chapter_searching/binary_search_edge.js ================================================ /** * File: binary_search_edge.js * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ const { binarySearchInsertion } = require('./binary_search_insertion.js'); /* 最も左の target を二分探索 */ function binarySearchLeftEdge(nums, target) { // target の挿入位置を探すのと等価 const i = binarySearchInsertion(nums, target); // target が見つからなければ、-1 を返す if (i === nums.length || nums[i] !== target) { return -1; } // target が見つかったら、インデックス i を返す return i; } /* 最も右の target を二分探索 */ function binarySearchRightEdge(nums, target) { // 最左の target + 1 を探す問題に変換する const i = binarySearchInsertion(nums, target + 1); // j は最も右の target を指し、i は target より大きい最初の要素を指す const j = i - 1; // target が見つからなければ、-1 を返す if (j === -1 || nums[j] !== target) { return -1; } // target が見つかったら、インデックス j を返す return j; } /* Driver Code */ // 重複要素を含む配列 const nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\n配列 nums = ' + nums); // 二分探索で左端と右端を探す for (const target of [6, 7]) { let index = binarySearchLeftEdge(nums, target); console.log('最も左の要素 ' + target + ' のインデックスは ' + index); index = binarySearchRightEdge(nums, target); console.log('最も右の要素 ' + target + ' のインデックスは ' + index); } ================================================ FILE: ja/codes/javascript/chapter_searching/binary_search_insertion.js ================================================ /** * File: binary_search_insertion.js * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 二分探索で挿入位置を探す(重複要素なし) */ function binarySearchInsertionSimple(nums, target) { let i = 0, j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { const m = Math.floor(i + (j - i) / 2); // 中点インデックス m を計算し、Math.floor() で切り捨てる if (nums[m] < target) { i = m + 1; // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1; // target は区間 [i, m-1] にある } else { return m; // target が見つかったら、挿入位置 m を返す } } // target が見つからなければ、挿入位置 i を返す return i; } /* 二分探索で挿入位置を探す(重複要素あり) */ function binarySearchInsertion(nums, target) { let i = 0, j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { const m = Math.floor(i + (j - i) / 2); // 中点インデックス m を計算し、Math.floor() で切り捨てる if (nums[m] < target) { i = m + 1; // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1; // target は区間 [i, m-1] にある } else { j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある } } // 挿入位置 i を返す return i; } /* Driver Code */ // 重複要素のない配列 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; console.log('\n配列 nums = ' + nums); // 二分探索で挿入位置を探す for (const target of [6, 9]) { const index = binarySearchInsertionSimple(nums, target); console.log('要素 ' + target + ' の挿入位置のインデックスは ' + index); } // 重複要素を含む配列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\n配列 nums = ' + nums); // 二分探索で挿入位置を探す for (const target of [2, 6, 20]) { const index = binarySearchInsertion(nums, target); console.log('要素 ' + target + ' の挿入位置のインデックスは ' + index); } module.exports = { binarySearchInsertion, }; ================================================ FILE: ja/codes/javascript/chapter_searching/hashing_search.js ================================================ /** * File: hashing_search.js * Created Time: 2022-12-29 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { arrToLinkedList } = require('../modules/ListNode'); /* ハッシュ探索(配列) */ function hashingSearchArray(map, target) { // ハッシュテーブルの key: 目標要素、value: インデックス // ハッシュテーブルにこの key がなければ -1 を返す return map.has(target) ? map.get(target) : -1; } /* ハッシュ探索(連結リスト) */ function hashingSearchLinkedList(map, target) { // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト // ハッシュテーブルにこの key がなければ null を返す return map.has(target) ? map.get(target) : null; } /* Driver Code */ const target = 3; /* ハッシュ探索(配列) */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // ハッシュテーブルを初期化 const map = new Map(); for (let i = 0; i < nums.length; i++) { map.set(nums[i], i); // key: 要素、value: インデックス } const index = hashingSearchArray(map, target); console.log('目標要素 3 のインデックス = ' + index); /* ハッシュ探索(連結リスト) */ let head = arrToLinkedList(nums); // ハッシュテーブルを初期化 const map1 = new Map(); while (head != null) { map1.set(head.val, head); // key: ノード値、value: ノード head = head.next; } const node = hashingSearchLinkedList(map1, target); console.log('目標ノード値 3 に対応するノードオブジェクトは', node); ================================================ FILE: ja/codes/javascript/chapter_searching/linear_search.js ================================================ /** * File: linear_search.js * Created Time: 2022-12-22 * Author: JoseHung (szhong@link.cuhk.edu.hk) */ const { ListNode, arrToLinkedList } = require('../modules/ListNode'); /* 線形探索(配列) */ function linearSearchArray(nums, target) { // 配列を走査 for (let i = 0; i < nums.length; i++) { // 目標要素が見つかったらそのインデックスを返す if (nums[i] === target) { return i; } } // 目標要素が見つからなければ -1 を返す return -1; } /* 線形探索(連結リスト) */ function linearSearchLinkedList(head, target) { // 連結リストを走査 while (head) { // 対象ノードが見つかったら、それを返す if (head.val === target) { return head; } head = head.next; } // 対象ノードが見つからない場合は null を返す return null; } /* Driver Code */ const target = 3; /* 配列で線形探索を行う */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; const index = linearSearchArray(nums, target); console.log('目標要素 3 のインデックス = ' + index); /* 連結リストで線形探索を行う */ const head = arrToLinkedList(nums); const node = linearSearchLinkedList(head, target); console.log('目標ノード値 3 に対応するノードオブジェクトは ', node); ================================================ FILE: ja/codes/javascript/chapter_searching/two_sum.js ================================================ /** * File: two_sum.js * Created Time: 2022-12-15 * Author: gyt95 (gytkwan@gmail.com) */ /* 方法 1:総当たり列挙 */ function twoSumBruteForce(nums, target) { const n = nums.length; // 2重ループのため、時間計算量は O(n^2) for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { if (nums[i] + nums[j] === target) { return [i, j]; } } } return []; } /* 方法 2:補助ハッシュテーブル */ function twoSumHashTable(nums, target) { // 補助ハッシュテーブルを使用し、空間計算量は O(n) let m = {}; // 単一ループで、時間計算量は O(n) for (let i = 0; i < nums.length; i++) { if (m[target - nums[i]] !== undefined) { return [m[target - nums[i]], i]; } else { m[nums[i]] = i; } } return []; } /* Driver Code */ // 方法 1 const nums = [2, 7, 11, 15], target = 13; let res = twoSumBruteForce(nums, target); console.log('方法1 res = ', res); // 方法 2 res = twoSumHashTable(nums, target); console.log('方法2 res = ', res); ================================================ FILE: ja/codes/javascript/chapter_sorting/bubble_sort.js ================================================ /** * File: bubble_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* バブルソート */ function bubbleSort(nums) { // 外側のループ:未ソート区間は [0, i] for (let i = nums.length - 1; i > 0; i--) { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* バブルソート(フラグ最適化) */ function bubbleSortWithFlag(nums) { // 外側のループ:未ソート区間は [0, i] for (let i = nums.length - 1; i > 0; i--) { let flag = false; // フラグを初期化する // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // 交換する要素を記録 } } if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了 } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); console.log('バブルソート完了後 nums =', nums); const nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); console.log('バブルソート完了後 nums =', nums1); ================================================ FILE: ja/codes/javascript/chapter_sorting/bucket_sort.js ================================================ /** * File: bucket_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* バケットソート */ function bucketSort(nums) { // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする const k = nums.length / 2; const buckets = []; for (let i = 0; i < k; i++) { buckets.push([]); } // 1. 配列要素を各バケットに振り分ける for (const num of nums) { // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する const i = Math.floor(num * k); // num をバケット i に追加 buckets[i].push(num); } // 2. 各バケットをソートする for (const bucket of buckets) { // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい bucket.sort((a, b) => a - b); } // 3. バケットを走査して結果を結合 let i = 0; for (const bucket of buckets) { for (const num of bucket) { nums[i++] = num; } } } /* Driver Code */ const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); console.log('バケットソート完了後 nums =', nums); ================================================ FILE: ja/codes/javascript/chapter_sorting/counting_sort.js ================================================ /** * File: counting_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 計数ソート */ // 簡易実装のため、オブジェクトのソートには使えない function countingSortNaive(nums) { // 1. 配列の最大要素 m を求める let m = Math.max(...nums); // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す const counter = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. counter を走査し、各要素を元の配列 nums に書き戻す let i = 0; for (let num = 0; num < m + 1; num++) { for (let j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* 計数ソート */ // 完全な実装で、オブジェクトをソートでき、かつ安定ソートである function countingSort(nums) { // 1. 配列の最大要素 m を求める let m = Math.max(...nums); // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す const counter = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する // つまり counter[num]-1 は、num が res に最後に現れるインデックス for (let i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. nums を逆順に走査し、各要素を結果配列 res に格納する // 結果を記録するための配列 res を初期化 const n = nums.length; const res = new Array(n); for (let i = n - 1; i >= 0; i--) { const num = nums[i]; res[counter[num] - 1] = num; // num を対応するインデックスに配置 counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る } // 結果配列 res で元の配列 nums を上書きする for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Driver Code */ const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); console.log('カウントソート(オブジェクトはソート不可)完了後 nums =', nums); const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); console.log('カウントソート完了後 nums1 =', nums1); ================================================ FILE: ja/codes/javascript/chapter_sorting/heap_sort.js ================================================ /** * File: heap_sort.js * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* ヒープの長さは n。ノード i から下方向にヒープ化 */ function siftDown(nums, n, i) { while (true) { // ノード i, l, r のうち値が最大のノードを ma とする let l = 2 * i + 1; let r = 2 * i + 2; let ma = i; if (l < n && nums[l] > nums[ma]) { ma = l; } if (r < n && nums[r] > nums[ma]) { ma = r; } // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma === i) { break; } // 2 つのノードを交換 [nums[i], nums[ma]] = [nums[ma], nums[i]]; // ループで上から下へヒープ化 i = ma; } } /* ヒープソート */ function heapSort(nums) { // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // ヒープから最大要素を取り出し、n-1 回繰り返す for (let i = nums.length - 1; i > 0; i--) { // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) [nums[0], nums[i]] = [nums[i], nums[0]]; // 根ノードを起点に、上から下へヒープ化 siftDown(nums, i, 0); } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; heapSort(nums); console.log('ヒープソート完了後 nums =', nums); ================================================ FILE: ja/codes/javascript/chapter_sorting/insertion_sort.js ================================================ /** * File: insertion_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 挿入ソート */ function insertionSort(nums) { // 外側ループ:整列済み区間は [0, i-1] for (let i = 1; i < nums.length; i++) { let base = nums[i], j = i - 1; // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する j--; } nums[j + 1] = base; // base を正しい位置に配置する } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); console.log('挿入ソート完了後 nums =', nums); ================================================ FILE: ja/codes/javascript/chapter_sorting/merge_sort.js ================================================ /** * File: merge_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 左部分配列と右部分配列をマージ */ function merge(nums, left, mid, right) { // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] // マージ結果を格納する一時配列 tmp を作成 const tmp = new Array(right - left + 1); // 左右の部分配列の開始インデックスを初期化する let i = left, j = mid + 1, k = 0; // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // 左右の部分配列の残り要素を一時配列にコピーする while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* マージソート */ function mergeSort(nums, left, right) { // 終了条件 if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了 // 分割フェーズ let mid = Math.floor(left + (right - left) / 2); // 中点を計算 mergeSort(nums, left, mid); // 左部分配列を再帰処理 mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 // マージフェーズ merge(nums, left, mid, right); } /* Driver Code */ const nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); console.log('マージソート完了後 nums =', nums); ================================================ FILE: ja/codes/javascript/chapter_sorting/quick_sort.js ================================================ /** * File: quick_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* クイックソートクラス */ class QuickSort { /* 要素の交換 */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 番兵分割 */ partition(nums, left, right) { // nums[left] を基準値とする let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j -= 1; // 右から左へ基準値未満の最初の要素を探す } while (i < j && nums[i] <= nums[left]) { i += 1; // 左から右へ基準値より大きい最初の要素を探す } // 要素の交換 this.swap(nums, i, j); // この 2 つの要素を交換 } this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート */ quickSort(nums, left, right) { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return; // 番兵分割 const pivot = this.partition(nums, left, right); // 左右の部分配列を再帰処理 this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* クイックソートクラス(中央値ピボット最適化) */ class QuickSortMedian { /* 要素の交換 */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 3つの候補要素の中央値を選ぶ */ medianThree(nums, left, mid, right) { let l = nums[left], m = nums[mid], r = nums[right]; // m は l と r の間 if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // l は m と r の間 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; return right; } /* 番兵による分割処理(3 点中央値) */ partition(nums, left, right) { // 3つの候補要素の中央値を選ぶ let med = this.medianThree( nums, left, Math.floor((left + right) / 2), right ); // 中央値を配列の最左端に交換する this.swap(nums, left, med); // nums[left] を基準値とする let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す this.swap(nums, i, j); // この 2 つの要素を交換 } this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート */ quickSort(nums, left, right) { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return; // 番兵分割 const pivot = this.partition(nums, left, right); // 左右の部分配列を再帰処理 this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* クイックソートクラス(再帰深度最適化) */ class QuickSortTailCall { /* 要素の交換 */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 番兵分割 */ partition(nums, left, right) { // nums[left] を基準値とする let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す this.swap(nums, i, j); // この 2 つの要素を交換 } this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート(再帰深度最適化) */ quickSort(nums, left, right) { // 部分配列の長さが 1 なら終了 while (left < right) { // 番兵による分割処理 let pivot = this.partition(nums, left, right); // 2 つの部分配列のうち短いほうにクイックソートを適用する if (pivot - left < right - pivot) { this.quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] } else { this.quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] } } } } /* Driver Code */ /* クイックソート */ const nums = [2, 4, 1, 0, 3, 5]; const quickSort = new QuickSort(); quickSort.quickSort(nums, 0, nums.length - 1); console.log('クイックソート完了後 nums =', nums); /* クイックソート(中央値の基準値で最適化) */ const nums1 = [2, 4, 1, 0, 3, 5]; const quickSortMedian = new QuickSortMedian(); quickSortMedian.quickSort(nums1, 0, nums1.length - 1); console.log('クイックソート(中央値ピボット最適化)完了後 nums =', nums1); /* クイックソート(再帰深度最適化) */ const nums2 = [2, 4, 1, 0, 3, 5]; const quickSortTailCall = new QuickSortTailCall(); quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); console.log('クイックソート(再帰深度最適化)完了後 nums =', nums2); ================================================ FILE: ja/codes/javascript/chapter_sorting/radix_sort.js ================================================ /** * File: radix_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ function digit(num, exp) { // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す return Math.floor(num / exp) % 10; } /* 計数ソート(nums の k 桁目でソート) */ function countingSortDigit(nums, exp) { // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 const counter = new Array(10).fill(0); const n = nums.length; // 0~9 の各数字の出現回数を集計する for (let i = 0; i < n; i++) { const d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする counter[d]++; // 数字 d の出現回数を数える } // 累積和を求め、「出現回数」を「配列インデックス」に変換する for (let i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する const res = new Array(n).fill(0); for (let i = n - 1; i >= 0; i--) { const d = digit(nums[i], exp); const j = counter[d] - 1; // d の配列内インデックス j を取得する res[j] = nums[i]; // 現在の要素をインデックス j に格納する counter[d]--; // d の個数を 1 減らす } // 結果で元の配列 nums を上書きする for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* 基数ソート */ function radixSort(nums) { // 最大桁数の判定用に配列の最大要素を取得 let m = Math.max(... nums); // 下位桁から上位桁の順に走査する for (let exp = 1; exp <= m; exp *= 10) { // 配列要素の k 桁目に対して計数ソートを行う // k = 1 -> exp = 1 // k = 2 -> exp = 10 // つまり exp = 10^(k-1) countingSortDigit(nums, exp); } } /* Driver Code */ const nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radixSort(nums); console.log('基数ソート完了後 nums =', nums); ================================================ FILE: ja/codes/javascript/chapter_sorting/selection_sort.js ================================================ /** * File: selection_sort.js * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* 選択ソート */ function selectionSort(nums) { let n = nums.length; // 外側ループ:未整列区間は [i, n-1] for (let i = 0; i < n - 1; i++) { // 内側のループ:未ソート区間の最小要素を見つける let k = i; for (let j = i + 1; j < n; j++) { if (nums[j] < nums[k]) { k = j; // 最小要素のインデックスを記録 } } // その最小要素を未整列区間の先頭要素と交換する [nums[i], nums[k]] = [nums[k], nums[i]]; } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; selectionSort(nums); console.log('選択ソート完了後 nums =', nums); ================================================ FILE: ja/codes/javascript/chapter_stack_and_queue/array_deque.js ================================================ /** * File: array_deque.js * Created Time: 2023-02-28 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 循環配列ベースの両端キュー */ class ArrayDeque { #nums; // 両端キューの要素を格納する配列 #front; // 先頭ポインタ。先頭要素を指す #queSize; // 両端キューの長さ /* コンストラクタ */ constructor(capacity) { this.#nums = new Array(capacity); this.#front = 0; this.#queSize = 0; } /* 両端キューの容量を取得 */ capacity() { return this.#nums.length; } /* 両端キューの長さを取得 */ size() { return this.#queSize; } /* 両端キューが空かどうかを判定 */ isEmpty() { return this.#queSize === 0; } /* 循環配列のインデックスを計算 */ index(i) { // 剰余演算により配列の先頭と末尾をつなげる // i が配列の末尾を越えたら先頭に戻る // i が配列の先頭を越えて前に出たら末尾に戻る return (i + this.capacity()) % this.capacity(); } /* キュー先頭にエンキュー */ pushFirst(num) { if (this.#queSize === this.capacity()) { console.log('両端キューがいっぱいです'); return; } // 先頭ポインタを左に 1 つ移動する // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする this.#front = this.index(this.#front - 1); // num をキュー先頭に追加 this.#nums[this.#front] = num; this.#queSize++; } /* キュー末尾にエンキュー */ pushLast(num) { if (this.#queSize === this.capacity()) { console.log('両端キューがいっぱいです'); return; } // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す const rear = this.index(this.#front + this.#queSize); // num をキュー末尾に追加 this.#nums[rear] = num; this.#queSize++; } /* キュー先頭からデキュー */ popFirst() { const num = this.peekFirst(); // 先頭ポインタを 1 つ後ろへ進める this.#front = this.index(this.#front + 1); this.#queSize--; return num; } /* キュー末尾からデキュー */ popLast() { const num = this.peekLast(); this.#queSize--; return num; } /* キュー先頭の要素にアクセス */ peekFirst() { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); return this.#nums[this.#front]; } /* キュー末尾の要素にアクセス */ peekLast() { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); // 末尾要素のインデックスを計算 const last = this.index(this.#front + this.#queSize - 1); return this.#nums[last]; } /* 出力用の配列を返す */ toArray() { // 有効長の範囲内のリスト要素のみを変換 const res = []; for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) { res[i] = this.#nums[this.index(j)]; } return res; } } /* Driver Code */ /* 両端キューを初期化 */ const capacity = 5; const deque = new ArrayDeque(capacity); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); console.log('両端キュー deque = [' + deque.toArray() + ']'); /* 要素にアクセス */ const peekFirst = deque.peekFirst(); console.log('先頭要素 peekFirst = ' + peekFirst); const peekLast = deque.peekLast(); console.log('末尾要素 peekLast = ' + peekLast); /* 要素をエンキュー */ deque.pushLast(4); console.log('要素 4 を末尾に追加した後 deque = [' + deque.toArray() + ']'); deque.pushFirst(1); console.log('要素 1 を先頭に追加した後 deque = [' + deque.toArray() + ']'); /* 要素をデキュー */ const popLast = deque.popLast(); console.log( '末尾から取り出した要素 = ' + popLast + '、末尾から取り出した後 deque = [' + deque.toArray() + ']' ); const popFirst = deque.popFirst(); console.log( '先頭から取り出した要素 = ' + popFirst + '、先頭から取り出した後 deque = [' + deque.toArray() + ']' ); /* 両端キューの長さを取得 */ const size = deque.size(); console.log('両端キューの長さ size = ' + size); /* 両端キューが空かどうかを判定 */ const isEmpty = deque.isEmpty(); console.log('両端キューが空かどうか = ' + isEmpty); ================================================ FILE: ja/codes/javascript/chapter_stack_and_queue/array_queue.js ================================================ /** * File: array_queue.js * Created Time: 2022-12-13 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* 循環配列ベースのキュー */ class ArrayQueue { #nums; // キュー要素を格納する配列 #front = 0; // 先頭ポインタ。先頭要素を指す #queSize = 0; // キューの長さ constructor(capacity) { this.#nums = new Array(capacity); } /* キューの容量を取得 */ get capacity() { return this.#nums.length; } /* キューの長さを取得 */ get size() { return this.#queSize; } /* キューが空かどうかを判定 */ isEmpty() { return this.#queSize === 0; } /* エンキュー */ push(num) { if (this.size === this.capacity) { console.log('キューがいっぱいです'); return; } // 末尾ポインタを計算し、末尾インデックス + 1 を指す // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする const rear = (this.#front + this.size) % this.capacity; // num をキュー末尾に追加 this.#nums[rear] = num; this.#queSize++; } /* デキュー */ pop() { const num = this.peek(); // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す this.#front = (this.#front + 1) % this.capacity; this.#queSize--; return num; } /* キュー先頭の要素にアクセス */ peek() { if (this.isEmpty()) throw new Error('キューが空です'); return this.#nums[this.#front]; } /* Array を返す */ toArray() { // 有効長の範囲内のリスト要素のみを変換 const arr = new Array(this.size); for (let i = 0, j = this.#front; i < this.size; i++, j++) { arr[i] = this.#nums[j % this.capacity]; } return arr; } } /* Driver Code */ /* キューを初期化 */ const capacity = 10; const queue = new ArrayQueue(capacity); /* 要素をエンキュー */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('キュー queue =', queue.toArray()); /* キュー先頭の要素にアクセス */ const peek = queue.peek(); console.log('先頭要素 peek = ' + peek); /* 要素をデキュー */ const pop = queue.pop(); console.log('取り出した要素 pop = ' + pop + '、取り出した後 queue =', queue.toArray()); /* キューの長さを取得 */ const size = queue.size; console.log('キューの長さ size = ' + size); /* キューが空かどうかを判定 */ const isEmpty = queue.isEmpty(); console.log('キューは空か = ' + isEmpty); /* 循環配列をテストする */ for (let i = 0; i < 10; i++) { queue.push(i); queue.pop(); console.log('第 ' + i + ' 回エンキュー + デキュー後 queue =', queue.toArray()); } ================================================ FILE: ja/codes/javascript/chapter_stack_and_queue/array_stack.js ================================================ /** * File: array_stack.js * Created Time: 2022-12-09 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* 配列ベースのスタック */ class ArrayStack { #stack; constructor() { this.#stack = []; } /* スタックの長さを取得 */ get size() { return this.#stack.length; } /* スタックが空かどうかを判定 */ isEmpty() { return this.#stack.length === 0; } /* プッシュ */ push(num) { this.#stack.push(num); } /* ポップ */ pop() { if (this.isEmpty()) throw new Error('スタックが空'); return this.#stack.pop(); } /* スタックトップの要素にアクセス */ top() { if (this.isEmpty()) throw new Error('スタックが空'); return this.#stack[this.#stack.length - 1]; } /* Array を返す */ toArray() { return this.#stack; } } /* Driver Code */ /* スタックを初期化 */ const stack = new ArrayStack(); /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('スタック stack = '); console.log(stack.toArray()); /* スタックトップの要素にアクセス */ const top = stack.top(); console.log('スタックトップ要素 top = ' + top); /* 要素をポップ */ const pop = stack.pop(); console.log('ポップした要素 pop = ' + pop + ',ポップ後 stack = '); console.log(stack.toArray()); /* スタックの長さを取得 */ const size = stack.size; console.log('スタックの長さ size = ' + size); /* 空かどうかを判定 */ const isEmpty = stack.isEmpty(); console.log('スタックは空か = ' + isEmpty); ================================================ FILE: ja/codes/javascript/chapter_stack_and_queue/deque.js ================================================ /** * File: deque.js * Created Time: 2023-01-17 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Driver Code */ /* 両端キューを初期化 */ // JavaScript には組み込みの両端キューがないため、Array を両端キューとして使う const deque = []; /* 要素をエンキュー */ deque.push(2); deque.push(5); deque.push(4); // 注意: 配列であるため、unshift() メソッドの時間計算量は O(n) deque.unshift(3); deque.unshift(1); console.log('両端キュー deque = ', deque); /* 要素にアクセス */ const peekFirst = deque[0]; console.log('先頭要素 peekFirst = ' + peekFirst); const peekLast = deque[deque.length - 1]; console.log('末尾要素 peekLast = ' + peekLast); /* 要素をデキュー */ // 注意: 配列であるため、shift() メソッドの時間計算量は O(n) const popFront = deque.shift(); console.log( '先頭からデキューした要素 popFront = ' + popFront + ',先頭からデキュー後 deque = ' + deque ); const popBack = deque.pop(); console.log( '末尾からデキューした要素 popBack = ' + popBack + ',末尾からデキュー後 deque = ' + deque ); /* 両端キューの長さを取得 */ const size = deque.length; console.log('両端キューの長さ size = ' + size); /* 両端キューが空かどうかを判定 */ const isEmpty = size === 0; console.log('両端キューが空かどうか = ' + isEmpty); ================================================ FILE: ja/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js ================================================ /** * File: linkedlist_deque.js * Created Time: 2023-02-04 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 双方向連結リストノード */ class ListNode { prev; // 前駆ノードへの参照(ポインタ) next; // 後継ノードへの参照(ポインタ) val; // ノード値 constructor(val) { this.val = val; this.next = null; this.prev = null; } } /* 双方向連結リストベースの両端キュー */ class LinkedListDeque { #front; // 先頭ノード front #rear; // 末尾ノード rear #queSize; // 両端キューの長さ constructor() { this.#front = null; this.#rear = null; this.#queSize = 0; } /* 末尾へのエンキュー操作 */ pushLast(val) { const node = new ListNode(val); // 連結リストが空なら、front と rear の両方を node に向ける if (this.#queSize === 0) { this.#front = node; this.#rear = node; } else { // node を連結リストの末尾に追加 this.#rear.next = node; node.prev = this.#rear; this.#rear = node; // 末尾ノードを更新する } this.#queSize++; } /* 先頭へのエンキュー操作 */ pushFirst(val) { const node = new ListNode(val); // 連結リストが空なら、front と rear の両方を node に向ける if (this.#queSize === 0) { this.#front = node; this.#rear = node; } else { // node を連結リストの先頭に追加 this.#front.prev = node; node.next = this.#front; this.#front = node; // 先頭ノードを更新する } this.#queSize++; } /* キュー末尾からの取り出し */ popLast() { if (this.#queSize === 0) { return null; } const value = this.#rear.val; // 末尾ノードの値を保存する // 末尾ノードを削除 let temp = this.#rear.prev; if (temp !== null) { temp.next = null; this.#rear.prev = null; } this.#rear = temp; // 末尾ノードを更新する this.#queSize--; return value; } /* キュー先頭からの取り出し */ popFirst() { if (this.#queSize === 0) { return null; } const value = this.#front.val; // 末尾ノードの値を保存する // 先頭ノードを削除 let temp = this.#front.next; if (temp !== null) { temp.prev = null; this.#front.next = null; } this.#front = temp; // 先頭ノードを更新する this.#queSize--; return value; } /* キュー末尾の要素にアクセス */ peekLast() { return this.#queSize === 0 ? null : this.#rear.val; } /* キュー先頭の要素にアクセス */ peekFirst() { return this.#queSize === 0 ? null : this.#front.val; } /* 両端キューの長さを取得 */ size() { return this.#queSize; } /* 両端キューが空かどうかを判定 */ isEmpty() { return this.#queSize === 0; } /* 両端キューを出力する */ print() { const arr = []; let temp = this.#front; while (temp !== null) { arr.push(temp.val); temp = temp.next; } console.log('[' + arr.join(', ') + ']'); } } /* Driver Code */ /* 両端キューを初期化 */ const linkedListDeque = new LinkedListDeque(); linkedListDeque.pushLast(3); linkedListDeque.pushLast(2); linkedListDeque.pushLast(5); console.log('両端キュー linkedListDeque = '); linkedListDeque.print(); /* 要素にアクセス */ const peekFirst = linkedListDeque.peekFirst(); console.log('先頭要素 peekFirst = ' + peekFirst); const peekLast = linkedListDeque.peekLast(); console.log('末尾要素 peekLast = ' + peekLast); /* 要素をエンキュー */ linkedListDeque.pushLast(4); console.log('要素 4 を末尾にエンキュー後 linkedListDeque = '); linkedListDeque.print(); linkedListDeque.pushFirst(1); console.log('要素 1 を先頭にエンキュー後 linkedListDeque = '); linkedListDeque.print(); /* 要素をデキュー */ const popLast = linkedListDeque.popLast(); console.log('末尾からデキューした要素 = ' + popLast + ',末尾からデキュー後 linkedListDeque = '); linkedListDeque.print(); const popFirst = linkedListDeque.popFirst(); console.log('先頭からデキューした要素 = ' + popFirst + ',先頭からデキュー後 linkedListDeque = '); linkedListDeque.print(); /* 両端キューの長さを取得 */ const size = linkedListDeque.size(); console.log('両端キューの長さ size = ' + size); /* 両端キューが空かどうかを判定 */ const isEmpty = linkedListDeque.isEmpty(); console.log('両端キューが空かどうか = ' + isEmpty); ================================================ FILE: ja/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js ================================================ /** * File: linkedlist_queue.js * Created Time: 2022-12-20 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ const { ListNode } = require('../modules/ListNode'); /* 連結リストベースのキュー */ class LinkedListQueue { #front; // 先頭ノード #front #rear; // 末尾ノード #rear #queSize = 0; constructor() { this.#front = null; this.#rear = null; } /* キューの長さを取得 */ get size() { return this.#queSize; } /* キューが空かどうかを判定 */ isEmpty() { return this.size === 0; } /* エンキュー */ push(num) { // 末尾ノードの後ろに num を追加 const node = new ListNode(num); // キューが空なら、先頭・末尾ノードをともにそのノードに設定 if (!this.#front) { this.#front = node; this.#rear = node; // キューが空でなければ、そのノードを末尾ノードの後ろに追加 } else { this.#rear.next = node; this.#rear = node; } this.#queSize++; } /* デキュー */ pop() { const num = this.peek(); // 先頭ノードを削除 this.#front = this.#front.next; this.#queSize--; return num; } /* キュー先頭の要素にアクセス */ peek() { if (this.size === 0) throw new Error('キューが空'); return this.#front.val; } /* 連結リストを Array に変換して返す */ toArray() { let node = this.#front; const res = new Array(this.size); for (let i = 0; i < res.length; i++) { res[i] = node.val; node = node.next; } return res; } } /* Driver Code */ /* キューを初期化 */ const queue = new LinkedListQueue(); /* 要素をエンキュー */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('キュー queue = ' + queue.toArray()); /* キュー先頭の要素にアクセス */ const peek = queue.peek(); console.log('先頭要素 peek = ' + peek); /* 要素をデキュー */ const pop = queue.pop(); console.log('デキューした要素 pop = ' + pop + ',デキュー後 queue = ' + queue.toArray()); /* キューの長さを取得 */ const size = queue.size; console.log('キューの長さ size = ' + size); /* キューが空かどうかを判定 */ const isEmpty = queue.isEmpty(); console.log('キューは空か = ' + isEmpty); ================================================ FILE: ja/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js ================================================ /** * File: linkedlist_stack.js * Created Time: 2022-12-22 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ const { ListNode } = require('../modules/ListNode'); /* 連結リストベースのスタック */ class LinkedListStack { #stackPeek; // 先頭ノードをスタックトップとする #stkSize = 0; // スタックの長さ constructor() { this.#stackPeek = null; } /* スタックの長さを取得 */ get size() { return this.#stkSize; } /* スタックが空かどうかを判定 */ isEmpty() { return this.size === 0; } /* プッシュ */ push(num) { const node = new ListNode(num); node.next = this.#stackPeek; this.#stackPeek = node; this.#stkSize++; } /* ポップ */ pop() { const num = this.peek(); this.#stackPeek = this.#stackPeek.next; this.#stkSize--; return num; } /* スタックトップの要素にアクセス */ peek() { if (!this.#stackPeek) throw new Error('スタックが空'); return this.#stackPeek.val; } /* 連結リストを Array に変換して返す */ toArray() { let node = this.#stackPeek; const res = new Array(this.size); for (let i = res.length - 1; i >= 0; i--) { res[i] = node.val; node = node.next; } return res; } } /* Driver Code */ /* スタックを初期化 */ const stack = new LinkedListStack(); /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('スタック stack = ' + stack.toArray()); /* スタックトップの要素にアクセス */ const peek = stack.peek(); console.log('スタックトップ要素 peek = ' + peek); /* 要素をポップ */ const pop = stack.pop(); console.log('ポップした要素 pop = ' + pop + ',ポップ後 stack = ' + stack.toArray()); /* スタックの長さを取得 */ const size = stack.size; console.log('スタックの長さ size = ' + size); /* 空かどうかを判定 */ const isEmpty = stack.isEmpty(); console.log('スタックは空か = ' + isEmpty); ================================================ FILE: ja/codes/javascript/chapter_stack_and_queue/queue.js ================================================ /** * File: queue.js * Created Time: 2022-12-05 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* キューを初期化 */ // JavaScript には組み込みのキューがないため、Array をキューとして使う const queue = []; /* 要素をエンキュー */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('キュー queue =', queue); /* キュー先頭の要素にアクセス */ const peek = queue[0]; console.log('先頭要素 peek =', peek); /* 要素をデキュー */ // 基盤が配列であるため、shift() メソッドの時間計算量は O(n) const pop = queue.shift(); console.log('デキューした要素 pop =', pop, ',デキュー後 queue = ', queue); /* キューの長さを取得 */ const size = queue.length; console.log('キューの長さ size =', size); /* キューが空かどうかを判定 */ const isEmpty = queue.length === 0; console.log('キューは空か = ', isEmpty); ================================================ FILE: ja/codes/javascript/chapter_stack_and_queue/stack.js ================================================ /** * File: stack.js * Created Time: 2022-12-04 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* スタックを初期化 */ // JavaScript には組み込みのスタッククラスがないため、Array をスタックとして使う const stack = []; /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('スタック stack =', stack); /* スタックトップの要素にアクセス */ const peek = stack[stack.length - 1]; console.log('スタックトップ要素 peek =', peek); /* 要素をポップ */ const pop = stack.pop(); console.log('ポップした要素 pop =', pop); console.log('ポップ後 stack =', stack); /* スタックの長さを取得 */ const size = stack.length; console.log('スタックの長さ size =', size); /* 空かどうかを判定 */ const isEmpty = stack.length === 0; console.log('スタックは空か =', isEmpty); ================================================ FILE: ja/codes/javascript/chapter_tree/array_binary_tree.js ================================================ /** * File: array_binary_tree.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 配列表現による二分木クラス */ class ArrayBinaryTree { #tree; /* コンストラクタ */ constructor(arr) { this.#tree = arr; } /* リスト容量 */ size() { return this.#tree.length; } /* インデックス i のノードの値を取得 */ val(i) { // インデックスが範囲外なら、空きを表す null を返す if (i < 0 || i >= this.size()) return null; return this.#tree[i]; } /* インデックス i のノードの左子ノードのインデックスを取得 */ left(i) { return 2 * i + 1; } /* インデックス i のノードの右子ノードのインデックスを取得 */ right(i) { return 2 * i + 2; } /* インデックス i のノードの親ノードのインデックスを取得 */ parent(i) { return Math.floor((i - 1) / 2); // 切り捨て除算 } /* レベル順走査 */ levelOrder() { let res = []; // 配列を直接走査する for (let i = 0; i < this.size(); i++) { if (this.val(i) !== null) res.push(this.val(i)); } return res; } /* 深さ優先探索 */ #dfs(i, order, res) { // 空きスロットなら返す if (this.val(i) === null) return; // 先行順走査 if (order === 'pre') res.push(this.val(i)); this.#dfs(this.left(i), order, res); // 中順走査 if (order === 'in') res.push(this.val(i)); this.#dfs(this.right(i), order, res); // 後順走査 if (order === 'post') res.push(this.val(i)); } /* 先行順走査 */ preOrder() { const res = []; this.#dfs(0, 'pre', res); return res; } /* 中順走査 */ inOrder() { const res = []; this.#dfs(0, 'in', res); return res; } /* 後順走査 */ postOrder() { const res = []; this.#dfs(0, 'post', res); return res; } } /* Driver Code */ // 二分木を初期化 // ここでは、配列から直接二分木を生成する関数を利用する const arr = Array.of( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ); const root = arrToTree(arr); console.log('\n二分木を初期化\n'); console.log('二分木の配列表現:'); console.log(arr); console.log('二分木の連結リスト表現:'); printTree(root); // 配列表現による二分木クラス const abt = new ArrayBinaryTree(arr); // ノードにアクセス const i = 1; const l = abt.left(i); const r = abt.right(i); const p = abt.parent(i); console.log('\n現在のノードのインデックスは ' + i + ' ,値は ' + abt.val(i)); console.log( 'その左子ノードのインデックスは ' + l + ' ,値は ' + (l === null ? 'null' : abt.val(l)) ); console.log( 'その右子ノードのインデックスは ' + r + ' ,値は ' + (r === null ? 'null' : abt.val(r)) ); console.log( 'その親ノードのインデックスは ' + p + ' ,値は ' + (p === null ? 'null' : abt.val(p)) ); // 木を走査 let res = abt.levelOrder(); console.log('\nレベル順走査:' + res); res = abt.preOrder(); console.log('先行順走査:' + res); res = abt.inOrder(); console.log('中間順走査:' + res); res = abt.postOrder(); console.log('後行順走査:' + res); ================================================ FILE: ja/codes/javascript/chapter_tree/avl_tree.js ================================================ /** * File: avl_tree.js * Created Time: 2023-02-05 * Author: what-is-me (whatisme@outlook.jp) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* AVL 木 */ class AVLTree { /* コンストラクタ */ constructor() { this.root = null; // 根ノード } /* ノードの高さを取得 */ height(node) { // 空ノードの高さは -1、葉ノードの高さは 0 return node === null ? -1 : node.height; } /* ノードの高さを更新する */ #updateHeight(node) { // ノードの高さは最も高い部分木の高さ + 1 に等しい node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; } /* 平衡係数を取得 */ balanceFactor(node) { // 空ノードの平衡係数は 0 if (node === null) return 0; // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ return this.height(node.left) - this.height(node.right); } /* 右回転 */ #rightRotate(node) { const child = node.left; const grandChild = child.right; // child を支点として node を右回転させる child.right = node; node.left = grandChild; // ノードの高さを更新する this.#updateHeight(node); this.#updateHeight(child); // 回転後の部分木の根ノードを返す return child; } /* 左回転 */ #leftRotate(node) { const child = node.right; const grandChild = child.left; // child を支点として node を左回転させる child.left = node; node.right = grandChild; // ノードの高さを更新する this.#updateHeight(node); this.#updateHeight(child); // 回転後の部分木の根ノードを返す return child; } /* 回転操作を行い、この部分木の平衡を回復する */ #rotate(node) { // ノード node の平衡係数を取得 const balanceFactor = this.balanceFactor(node); // 左に偏った木 if (balanceFactor > 1) { if (this.balanceFactor(node.left) >= 0) { // 右回転 return this.#rightRotate(node); } else { // 左回転してから右回転 node.left = this.#leftRotate(node.left); return this.#rightRotate(node); } } // 右に偏った木 if (balanceFactor < -1) { if (this.balanceFactor(node.right) <= 0) { // 左回転 return this.#leftRotate(node); } else { // 右回転してから左回転 node.right = this.#rightRotate(node.right); return this.#leftRotate(node); } } // 平衡木なので回転不要、そのまま返す return node; } /* ノードを挿入 */ insert(val) { this.root = this.#insertHelper(this.root, val); } /* ノードを再帰的に挿入する(補助メソッド) */ #insertHelper(node, val) { if (node === null) return new TreeNode(val); /* 1. 挿入位置を探索してノードを挿入 */ if (val < node.val) node.left = this.#insertHelper(node.left, val); else if (val > node.val) node.right = this.#insertHelper(node.right, val); else return node; // 重複ノードは挿入せず、そのまま返す this.#updateHeight(node); // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = this.#rotate(node); // 部分木の根ノードを返す return node; } /* ノードを削除 */ remove(val) { this.root = this.#removeHelper(this.root, val); } /* ノードを再帰的に削除する(補助メソッド) */ #removeHelper(node, val) { if (node === null) return null; /* 1. ノードを探索して削除 */ if (val < node.val) node.left = this.#removeHelper(node.left, val); else if (val > node.val) node.right = this.#removeHelper(node.right, val); else { if (node.left === null || node.right === null) { const child = node.left !== null ? node.left : node.right; // 子ノード数 = 0 の場合、node をそのまま削除して返す if (child === null) return null; // 子ノード数 = 1 の場合、node をそのまま削除する else node = child; } else { // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える let temp = node.right; while (temp.left !== null) { temp = temp.left; } node.right = this.#removeHelper(node.right, temp.val); node.val = temp.val; } } this.#updateHeight(node); // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = this.#rotate(node); // 部分木の根ノードを返す return node; } /* ノードを探索 */ search(val) { let cur = this.root; // ループで探索し、葉ノードを越えたら抜ける while (cur !== null) { // 目標ノードは cur の右部分木にある if (cur.val < val) cur = cur.right; // 目標ノードは cur の左部分木にある else if (cur.val > val) cur = cur.left; // 目標ノードが見つかったらループを抜ける else break; } // 目標ノードを返す return cur; } } function testInsert(tree, val) { tree.insert(val); console.log('\nノード ' + val + ' を挿入後,AVL 木は'); printTree(tree.root); } function testRemove(tree, val) { tree.remove(val); console.log('\nノード ' + val + ' を削除後,AVL 木は'); printTree(tree.root); } /* Driver Code */ /* 空の AVL 木を初期化する */ const avlTree = new AVLTree(); /* ノードを挿入 */ // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); testInsert(avlTree, 4); testInsert(avlTree, 5); testInsert(avlTree, 8); testInsert(avlTree, 7); testInsert(avlTree, 9); testInsert(avlTree, 10); testInsert(avlTree, 6); /* 重複ノードを挿入する */ testInsert(avlTree, 7); /* ノードを削除 */ // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい testRemove(avlTree, 8); // 次数 0 のノードを削除する testRemove(avlTree, 5); // 次数 1 のノードを削除する testRemove(avlTree, 4); // 次数 2 のノードを削除する /* ノードを検索 */ const node = avlTree.search(7); console.log('\n見つかったノードオブジェクトは', node, ',ノード値 = ' + node.val); ================================================ FILE: ja/codes/javascript/chapter_tree/binary_search_tree.js ================================================ /** * File: binary_search_tree.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 二分探索木 */ class BinarySearchTree { /* コンストラクタ */ constructor() { // 空の木を初期化する this.root = null; } /* 二分木の根ノードを取得 */ getRoot() { return this.root; } /* ノードを探索 */ search(num) { let cur = this.root; // ループで探索し、葉ノードを越えたら抜ける while (cur !== null) { // 目標ノードは cur の右部分木にある if (cur.val < num) cur = cur.right; // 目標ノードは cur の左部分木にある else if (cur.val > num) cur = cur.left; // 目標ノードが見つかったらループを抜ける else break; } // 目標ノードを返す return cur; } /* ノードを挿入 */ insert(num) { // 木が空なら、根ノードを初期化する if (this.root === null) { this.root = new TreeNode(num); return; } let cur = this.root, pre = null; // ループで探索し、葉ノードを越えたら抜ける while (cur !== null) { // 重複ノードが見つかったら、直ちに返す if (cur.val === num) return; pre = cur; // 挿入位置は cur の右部分木にある if (cur.val < num) cur = cur.right; // 挿入位置は cur の左部分木にある else cur = cur.left; } // ノードを挿入 const node = new TreeNode(num); if (pre.val < num) pre.right = node; else pre.left = node; } /* ノードを削除 */ remove(num) { // 木が空なら、そのまま早期リターンする if (this.root === null) return; let cur = this.root, pre = null; // ループで探索し、葉ノードを越えたら抜ける while (cur !== null) { // 削除対象のノードが見つかったら、ループを抜ける if (cur.val === num) break; pre = cur; // 削除対象ノードは cur の右部分木にある if (cur.val < num) cur = cur.right; // 削除対象ノードは cur の左部分木にある else cur = cur.left; } // 削除対象ノードがなければそのまま返す if (cur === null) return; // 子ノード数 = 0 or 1 if (cur.left === null || cur.right === null) { // 子ノード数が 0 / 1 のとき、child = null / その子ノード const child = cur.left !== null ? cur.left : cur.right; // ノード cur を削除する if (cur !== this.root) { if (pre.left === cur) pre.left = child; else pre.right = child; } else { // 削除ノードが根ノードなら、根ノードを再設定 this.root = child; } } // 子ノード数 = 2 else { // 中順走査における cur の次ノードを取得 let tmp = cur.right; while (tmp.left !== null) { tmp = tmp.left; } // ノード tmp を再帰的に削除 this.remove(tmp.val); // tmp で cur を上書きする cur.val = tmp.val; } } } /* Driver Code */ /* 二分探索木を初期化 */ const bst = new BinarySearchTree(); // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (const num of nums) { bst.insert(num); } console.log('\n初期化した二分木は\n'); printTree(bst.getRoot()); /* ノードを探索 */ const node = bst.search(7); console.log('\n見つかったノードオブジェクトは ' + node + ',ノード値 = ' + node.val); /* ノードを挿入 */ bst.insert(16); console.log('\nノード 16 を挿入後,二分木は\n'); printTree(bst.getRoot()); /* ノードを削除 */ bst.remove(1); console.log('\nノード 1 を削除後,二分木は\n'); printTree(bst.getRoot()); bst.remove(2); console.log('\nノード 2 を削除後,二分木は\n'); printTree(bst.getRoot()); bst.remove(4); console.log('\nノード 4 を削除後,二分木は\n'); printTree(bst.getRoot()); ================================================ FILE: ja/codes/javascript/chapter_tree/binary_tree.js ================================================ /** * File: binary_tree.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 二分木を初期化 */ // ノードを初期化 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // ノード間の参照(ポインタ)を構築する n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; console.log('\n二分木を初期化\n'); printTree(n1); /* ノードの挿入と削除 */ const P = new TreeNode(0); // n1 -> n2 の間にノード P を挿入 n1.left = P; P.left = n2; console.log('\nノード P を挿入後\n'); printTree(n1); // ノード P を削除 n1.left = n2; console.log('\nノード P を削除後\n'); printTree(n1); ================================================ FILE: ja/codes/javascript/chapter_tree/binary_tree_bfs.js ================================================ /** * File: binary_tree_bfs.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* レベル順走査 */ function levelOrder(root) { // キューを初期化し、ルートノードを追加する const queue = [root]; // 走査順序を保存するためのリストを初期化する const list = []; while (queue.length) { let node = queue.shift(); // デキュー list.push(node.val); // ノードの値を保存する if (node.left) queue.push(node.left); // 左子ノードをキューに追加 if (node.right) queue.push(node.right); // 右子ノードをキューに追加 } return list; } /* Driver Code */ /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n二分木を初期化\n'); printTree(root); /* レベル順走査 */ const list = levelOrder(root); console.log('\nレベル順走査のノード出力シーケンス = ' + list); ================================================ FILE: ja/codes/javascript/chapter_tree/binary_tree_dfs.js ================================================ /** * File: binary_tree_dfs.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); // 走査順序を格納するリストを初期化 const list = []; /* 先行順走査 */ function preOrder(root) { if (root === null) return; // 訪問順序:根ノード -> 左部分木 -> 右部分木 list.push(root.val); preOrder(root.left); preOrder(root.right); } /* 中順走査 */ function inOrder(root) { if (root === null) return; // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 inOrder(root.left); list.push(root.val); inOrder(root.right); } /* 後順走査 */ function postOrder(root) { if (root === null) return; // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード postOrder(root.left); postOrder(root.right); list.push(root.val); } /* Driver Code */ /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n二分木を初期化\n'); printTree(root); /* 先行順走査 */ list.length = 0; preOrder(root); console.log('\n先行順走査のノード出力シーケンス = ' + list); /* 中順走査 */ list.length = 0; inOrder(root); console.log('\n中間順走査のノード出力シーケンス = ' + list); /* 後順走査 */ list.length = 0; postOrder(root); console.log('\n後行順走査のノード出力シーケンス = ' + list); ================================================ FILE: ja/codes/javascript/modules/ListNode.js ================================================ /** * File: ListNode.js * Created Time: 2022-12-12 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 連結リストノード */ class ListNode { val; // ノード値 next; // 次のノードを指す参照(ポインタ) constructor(val, next) { this.val = val === undefined ? 0 : val; this.next = next === undefined ? null : next; } } /* リストを連結リストにデシリアライズする */ function arrToLinkedList(arr) { const dum = new ListNode(0); let head = dum; for (const val of arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } module.exports = { ListNode, arrToLinkedList, }; ================================================ FILE: ja/codes/javascript/modules/PrintUtil.js ================================================ /** * File: PrintUtil.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('./TreeNode'); /* 連結リストを出力 */ function printLinkedList(head) { let list = []; while (head !== null) { list.push(head.val.toString()); head = head.next; } console.log(list.join(' -> ')); } function Trunk(prev, str) { this.prev = prev; this.str = str; } /** * 二分木を出力 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ function printTree(root) { printTree(root, null, false); } /* 二分木を出力 */ function printTree(root, prev, isRight) { if (root === null) { return; } let prev_str = ' '; let trunk = new Trunk(prev, prev_str); printTree(root.right, trunk, true); if (!prev) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); console.log(' ' + root.val); if (prev) { prev.str = prev_str; } trunk.str = ' |'; printTree(root.left, trunk, false); } function showTrunks(p) { if (!p) { return; } showTrunks(p.prev); process.stdout.write(p.str); } /* ヒープを出力 */ function printHeap(arr) { console.log('ヒープの配列表現:'); console.log(arr); console.log('ヒープの木構造表現:'); printTree(arrToTree(arr)); } module.exports = { printLinkedList, printTree, printHeap, }; ================================================ FILE: ja/codes/javascript/modules/TreeNode.js ================================================ /** * File: TreeNode.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 二分木ノード */ class TreeNode { val; // ノード値 left; // 左の子ノードへのポインタ right; // 右の子ノードへのポインタ height; // ノードの高さ constructor(val, left, right, height) { this.val = val === undefined ? 0 : val; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; this.height = height === undefined ? 0 : height; } } /* 配列をデシリアライズして二分木に変換する */ function arrToTree(arr, i = 0) { if (i < 0 || i >= arr.length || arr[i] === null) { return null; } let root = new TreeNode(arr[i]); root.left = arrToTree(arr, 2 * i + 1); root.right = arrToTree(arr, 2 * i + 2); return root; } module.exports = { TreeNode, arrToTree, }; ================================================ FILE: ja/codes/javascript/modules/Vertex.js ================================================ /** * File: Vertex.js * Created Time: 2023-02-15 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 頂点クラス */ class Vertex { val; constructor(val) { this.val = val; } /* 値リスト vals を入力し、頂点リスト vets を返す */ static valsToVets(vals) { const vets = []; for (let i = 0; i < vals.length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* 頂点リスト vets を入力し、値リスト vals を返す */ static vetsToVals(vets) { const vals = []; for (const vet of vets) { vals.push(vet.val); } return vals; } } module.exports = { Vertex, }; ================================================ FILE: ja/codes/javascript/test_all.js ================================================ import { bold, brightRed } from 'jsr:@std/fmt/colors'; import { expandGlob } from 'jsr:@std/fs'; import { relative, resolve } from 'jsr:@std/path'; /** * @typedef {import('jsr:@std/fs').WalkEntry} WalkEntry * @type {WalkEntry[]} */ const entries = []; for await (const entry of expandGlob( resolve(import.meta.dirname, './chapter_*/*.js') )) { entries.push(entry); } /** @type {{ status: Promise; stderr: ReadableStream; }[]} */ const processes = []; for (const file of entries) { const execute = new Deno.Command('node', { args: [relative(import.meta.dirname, file.path)], cwd: import.meta.dirname, stdin: 'piped', stdout: 'piped', stderr: 'piped', }); const process = execute.spawn(); processes.push({ status: process.status, stderr: process.stderr }); } const results = await Promise.all( processes.map(async (item) => { const status = await item.status; return { status, stderr: item.stderr }; }) ); /** @type {ReadableStream[]} */ const errors = []; for (const result of results) { if (!result.status.success) { errors.push(result.stderr); } } console.log(`Tested ${entries.length} files`); console.log(`Found exception in ${errors.length} files`); if (errors.length) { console.log(); for (const error of errors) { const reader = error.getReader(); const { value } = await reader.read(); const decoder = new TextDecoder(); console.log(`${bold(brightRed('error'))}: ${decoder.decode(value)}`); } throw new Error('Test failed'); } ================================================ FILE: ja/codes/kotlin/chapter_array_and_linkedlist/array.kt ================================================ /** * File: array.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_array_and_linkedlist import java.util.concurrent.ThreadLocalRandom /* 要素へランダムアクセス */ fun randomAccess(nums: IntArray): Int { // 区間 [0, nums.size) からランダムに数字を 1 つ選ぶ val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size) // ランダムな要素を取得して返す val randomNum = nums[randomIndex] return randomNum } /* 配列長を拡張する */ fun extend(nums: IntArray, enlarge: Int): IntArray { // 拡張後の長さを持つ配列を初期化する val res = IntArray(nums.size + enlarge) // 元の配列の全要素を新しい配列にコピー for (i in nums.indices) { res[i] = nums[i] } // 拡張後の新しい配列を返す return res } /* 配列の index 番目に要素 num を挿入 */ fun insert(nums: IntArray, num: Int, index: Int) { // インデックス index 以降の全要素を 1 つ後ろへ移動する for (i in nums.size - 1 downTo index + 1) { nums[i] = nums[i - 1] } // index の要素に num を代入する nums[index] = num } /* index の要素を削除する */ fun remove(nums: IntArray, index: Int) { // インデックス index より後ろの全要素を 1 つ前へ移動する for (i in index.. P -> n1 val p = n0.next val n1 = p?.next n0.next = n1 } /* 連結リスト内で index 番目のノードにアクセス */ fun access(head: ListNode?, index: Int): ListNode? { var h = head for (i in 0..= size) throw IndexOutOfBoundsException("インデックスが範囲外") return arr[index] } /* 要素を更新 */ fun set(index: Int, num: Int) { if (index < 0 || index >= size) throw IndexOutOfBoundsException("インデックスが範囲外") arr[index] = num } /* 末尾に要素を追加 */ fun add(num: Int) { // 要素数が容量を超えると、拡張機構が発動する if (size == capacity()) extendCapacity() arr[size] = num // 要素数を更新 size++ } /* 中間に要素を挿入 */ fun insert(index: Int, num: Int) { if (index < 0 || index >= size) throw IndexOutOfBoundsException("インデックスが範囲外") // 要素数が容量を超えると、拡張機構が発動する if (size == capacity()) extendCapacity() // index 以降の要素をすべて 1 つ後ろへずらす for (j in size - 1 downTo index) arr[j + 1] = arr[j] arr[index] = num // 要素数を更新 size++ } /* 要素を削除 */ fun remove(index: Int): Int { if (index < 0 || index >= size) throw IndexOutOfBoundsException("インデックスが範囲外") val num = arr[index] // インデックス index より後の要素をすべて 1 つ前に移動する for (j in index..>, res: MutableList>?>, cols: BooleanArray, diags1: BooleanArray, diags2: BooleanArray ) { // すべての行への配置が完了したら、解を記録する if (row == n) { val copyState = mutableListOf>() for (sRow in state) { copyState.add(sRow.toMutableList()) } res.add(copyState) return } // すべての列を走査 for (col in 0..>?> { // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す val state = mutableListOf>() for (i in 0..() for (j in 0..>?>() backtrack(0, n, state, res, cols, diags1, diags2) return res } /* Driver Code */ fun main() { val n = 4 val res = nQueens(n) println("入力された盤面の縦横は $n") println("クイーンの配置パターンは全部で ${res.size} 通り") for (state in res) { println("--------------------") for (row in state!!) { println(row) } } } ================================================ FILE: ja/codes/kotlin/chapter_backtracking/permutations_i.kt ================================================ /** * File: permutations_i.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.permutations_i /* バックトラッキング:順列 I */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // 状態の長さが要素数に等しければ、解を記録 if (state.size == choices.size) { res.add(state.toMutableList()) return } // すべての選択肢を走査 for (i in choices.indices) { val choice = choices[i] // 枝刈り:要素の重複選択を許可しない if (!selected[i]) { // 試行: 選択を行い、状態を更新 selected[i] = true state.add(choice) // 次の選択へ進む backtrack(state, choices, selected, res) // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false state.removeAt(state.size - 1) } } } /* 全順列 I */ fun permutationsI(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(1, 2, 3) val res = permutationsI(nums) println("入力配列 nums = ${nums.contentToString()}") println("すべての順列 res = $res") } ================================================ FILE: ja/codes/kotlin/chapter_backtracking/permutations_ii.kt ================================================ /** * File: permutations_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.permutations_ii /* バックトラッキング:順列 II */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // 状態の長さが要素数に等しければ、解を記録 if (state.size == choices.size) { res.add(state.toMutableList()) return } // すべての選択肢を走査 val duplicated = HashSet() for (i in choices.indices) { val choice = choices[i] // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない if (!selected[i] && !duplicated.contains(choice)) { // 試行: 選択を行い、状態を更新 duplicated.add(choice) // 選択済みの要素値を記録 selected[i] = true state.add(choice) // 次の選択へ進む backtrack(state, choices, selected, res) // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false state.removeAt(state.size - 1) } } } /* 全順列 II */ fun permutationsII(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(1, 2, 2) val res = permutationsII(nums) println("入力配列 nums = ${nums.contentToString()}") println("すべての順列 res = $res") } ================================================ FILE: ja/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt ================================================ /** * File: preorder_traversal_i_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_i_compact import utils.TreeNode import utils.printTree var res: MutableList? = null /* 前順走査:例題 1 */ fun preOrder(root: TreeNode?) { if (root == null) { return } if (root._val == 7) { // 解を記録 res!!.add(root) } preOrder(root.left) preOrder(root.right) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\n二分木を初期化") printTree(root) // 先行順走査 res = mutableListOf() preOrder(root) println("\n値が 7 のノードをすべて出力") val vals = mutableListOf() for (node in res!!) { vals.add(node._val) } println(vals) } ================================================ FILE: ja/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt ================================================ /** * File: preorder_traversal_ii_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_ii_compact import utils.TreeNode import utils.printTree var path: MutableList? = null var res: MutableList>? = null /* 前順走査:例題 2 */ fun preOrder(root: TreeNode?) { if (root == null) { return } // 試す path!!.add(root) if (root._val == 7) { // 解を記録 res!!.add(path!!.toMutableList()) } preOrder(root.left) preOrder(root.right) // バックトラック path!!.removeAt(path!!.size - 1) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\n二分木を初期化") printTree(root) // 先行順走査 path = mutableListOf() res = mutableListOf() preOrder(root) println("\n根ノードからノード 7 までのすべての経路を出力") for (path in res!!) { val _vals = mutableListOf() for (node in path) { _vals.add(node._val) } println(_vals) } } ================================================ FILE: ja/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt ================================================ /** * File: preorder_traversal_iii_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_iii_compact import utils.TreeNode import utils.printTree var path: MutableList? = null var res: MutableList>? = null /* 前順走査:例題 3 */ fun preOrder(root: TreeNode?) { // 枝刈り if (root == null || root._val == 3) { return } // 試す path!!.add(root) if (root._val == 7) { // 解を記録 res!!.add(path!!.toMutableList()) } preOrder(root.left) preOrder(root.right) // バックトラック path!!.removeAt(path!!.size - 1) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\n二分木を初期化") printTree(root) // 先行順走査 path = mutableListOf() res = mutableListOf() preOrder(root) println("\n根ノードからノード 7 までのすべての経路を出力し,経路には値が 3 のノードを含めない") for (path in res!!) { val _vals = mutableListOf() for (node in path) { _vals.add(node._val) } println(_vals) } } ================================================ FILE: ja/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt ================================================ /** * File: preorder_traversal_iii_template.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_iii_template import utils.TreeNode import utils.printTree /* 現在の状態が解かどうかを判定 */ fun isSolution(state: MutableList): Boolean { return state.isNotEmpty() && state[state.size - 1]?._val == 7 } /* 解を記録 */ fun recordSolution(state: MutableList?, res: MutableList?>) { res.add(state!!.toMutableList()) } /* 現在の状態で、この選択が有効かどうかを判定 */ fun isValid(state: MutableList?, choice: TreeNode?): Boolean { return choice != null && choice._val != 3 } /* 状態を更新 */ fun makeChoice(state: MutableList, choice: TreeNode?) { state.add(choice) } /* 状態を元に戻す */ fun undoChoice(state: MutableList, choice: TreeNode?) { state.removeLast() } /* バックトラッキング:例題 3 */ fun backtrack( state: MutableList, choices: MutableList, res: MutableList?> ) { // 解かどうかを確認 if (isSolution(state)) { // 解を記録 recordSolution(state, res) } // すべての選択肢を走査 for (choice in choices) { // 枝刈り:選択が妥当かを確認する if (isValid(state, choice)) { // 試行: 選択を行い、状態を更新 makeChoice(state, choice) // 次の選択へ進む backtrack(state, mutableListOf(choice!!.left, choice.right), res) // バックトラック:選択を取り消し、前の状態に戻す undoChoice(state, choice) } } } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\n二分木を初期化") printTree(root) // バックトラッキング法 val res = mutableListOf?>() backtrack(mutableListOf(), mutableListOf(root), res) println("\n根ノードからノード 7 までのすべての経路を出力し,経路に値が 3 のノードを含まないことを条件とする") for (path in res) { val vals = mutableListOf() for (node in path!!) { if (node != null) { vals.add(node._val) } } println(vals) } } ================================================ FILE: ja/codes/kotlin/chapter_backtracking/subset_sum_i.kt ================================================ /** * File: subset_sum_i.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_i /* バックトラッキング:部分和 I */ fun backtrack( state: MutableList, target: Int, choices: IntArray, start: Int, res: MutableList?> ) { // 部分集合の和が target に等しければ、解を記録 if (target == 0) { res.add(state.toMutableList()) return } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for (i in start..?> { val state = mutableListOf() // 状態(部分集合) nums.sort() // nums をソート val start = 0 // 開始点を走査 val res = mutableListOf?>() // 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(3, 4, 5) val target = 9 val res = subsetSumI(nums, target) println("入力配列 nums = ${nums.contentToString()}, target = $target") println("和が $target に等しいすべての部分集合 res = $res") } ================================================ FILE: ja/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt ================================================ /** * File: subset_sum_i_native.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_i_naive /* バックトラッキング:部分和 I */ fun backtrack( state: MutableList, target: Int, total: Int, choices: IntArray, res: MutableList?> ) { // 部分集合の和が target に等しければ、解を記録 if (total == target) { res.add(state.toMutableList()) return } // すべての選択肢を走査 for (i in choices.indices) { // 枝刈り:部分和が target を超える場合はその選択をスキップする if (total + choices[i] > target) { continue } // 試行:選択を行い、要素と total を更新する state.add(choices[i]) // 次の選択へ進む backtrack(state, target, total + choices[i], choices, res) // バックトラック:選択を取り消し、前の状態に戻す state.removeAt(state.size - 1) } } /* 部分和 I を解く(重複部分集合を含む) */ fun subsetSumINaive(nums: IntArray, target: Int): MutableList?> { val state = mutableListOf() // 状態(部分集合) val total = 0 // 部分和 val res = mutableListOf?>() // 結果リスト(部分集合のリスト) backtrack(state, target, total, nums, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(3, 4, 5) val target = 9 val res = subsetSumINaive(nums, target) println("入力配列 nums = ${nums.contentToString()}, target = $target") println("和が $target に等しいすべての部分集合 res = $res") println("この方法の出力結果には重複した集合が含まれるので注意") } ================================================ FILE: ja/codes/kotlin/chapter_backtracking/subset_sum_ii.kt ================================================ /** * File: subset_sum_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_ii /* バックトラッキング:部分和 II */ fun backtrack( state: MutableList, target: Int, choices: IntArray, start: Int, res: MutableList?> ) { // 部分集合の和が target に等しければ、解を記録 if (target == 0) { res.add(state.toMutableList()) return } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for (i in start.. start && choices[i] == choices[i - 1]) { continue } // 試す:選択を行い、target と start を更新 state.add(choices[i]) // 次の選択へ進む backtrack(state, target - choices[i], choices, i + 1, res) // バックトラック:選択を取り消し、前の状態に戻す state.removeAt(state.size - 1) } } /* 部分和 II を解く */ fun subsetSumII(nums: IntArray, target: Int): MutableList?> { val state = mutableListOf() // 状態(部分集合) nums.sort() // nums をソート val start = 0 // 開始点を走査 val res = mutableListOf?>() // 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(4, 4, 5) val target = 9 val res = subsetSumII(nums, target) println("入力配列 nums = ${nums.contentToString()}, target = $target") println("和が $target に等しいすべての部分集合 res = $res") } ================================================ FILE: ja/codes/kotlin/chapter_computational_complexity/iteration.kt ================================================ /** * File: iteration.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.iteration /* for ループ */ fun forLoop(n: Int): Int { var res = 0 // 1, 2, ..., n-1, n を順に加算する for (i in 1..n) { res += i } return res } /* while ループ */ fun whileLoop(n: Int): Int { var res = 0 var i = 1 // 条件変数を初期化する // 1, 2, ..., n-1, n を順に加算する while (i <= n) { res += i i++ // 条件変数を更新する } return res } /* while ループ(2回更新) */ fun whileLoopII(n: Int): Int { var res = 0 var i = 1 // 条件変数を初期化する // 1, 4, 10, ... を順に加算する while (i <= n) { res += i // 条件変数を更新する i++ i *= 2 } return res } /* 二重 for ループ */ fun nestedForLoop(n: Int): String { val res = StringBuilder() // i = 1, 2, ..., n-1, n とループする for (i in 1..n) { // j = 1, 2, ..., n-1, n とループする for (j in 1..n) { res.append(" ($i, $j), ") } } return res.toString() } /* Driver Code */ fun main() { val n = 5 var res: Int res = forLoop(n) println("\nfor ループの合計結果 res = $res") res = whileLoop(n) println("\nwhile ループの合計結果 res = $res") res = whileLoopII(n) println("\nwhile ループ (2 回更新) の合計結果 res = $res") val resStr = nestedForLoop(n) println("\n二重 for ループの走査結果 $resStr") } ================================================ FILE: ja/codes/kotlin/chapter_computational_complexity/recursion.kt ================================================ /** * File: recursion.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.recursion import java.util.* /* 再帰 */ fun recur(n: Int): Int { // 終了条件 if (n == 1) return 1 // 再帰: 再帰呼び出し val res = recur(n - 1) // 戻る: 結果を返す return n + res } /* 反復で再帰を模擬する */ fun forLoopRecur(n: Int): Int { // 明示的なスタックを使ってシステムコールスタックを模擬する val stack = Stack() var res = 0 // 再帰: 再帰呼び出し for (i in n downTo 0) { // 「スタックへのプッシュ」で「再帰」を模擬する stack.push(i) } // 戻る: 結果を返す while (stack.isNotEmpty()) { // 「スタックから取り出す操作」で「帰り」をシミュレート res += stack.pop() } // res = 1+2+3+...+n return res } /* 末尾再帰 */ tailrec fun tailRecur(n: Int, res: Int): Int { // `tailrec` キーワードを追加して末尾再帰最適化を有効にする // 終了条件 if (n == 0) return res // 末尾再帰呼び出し return tailRecur(n - 1, res + n) } /* フィボナッチ数列:再帰 */ fun fib(n: Int): Int { // 終了条件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1 // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す val res = fib(n - 1) + fib(n - 2) // 結果 f(n) を返す return res } /* Driver Code */ fun main() { val n = 5 var res: Int res = recur(n) println("\n再帰関数の合計結果 res = $res") res = forLoopRecur(n) println("\n反復で再帰をシミュレートした合計結果 res = $res") res = tailRecur(n, 0) println("\n末尾再帰関数の合計結果 res = $res") res = fib(n) println("\nフィボナッチ数列の第 $n 項は $res") } ================================================ FILE: ja/codes/kotlin/chapter_computational_complexity/space_complexity.kt ================================================ /** * File: space_complexity.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.space_complexity import utils.ListNode import utils.TreeNode import utils.printTree /* 関数 */ fun function(): Int { // 何らかの処理を行う return 0 } /* 定数階 */ fun constant(n: Int) { // 定数、変数、オブジェクトは O(1) の空間を占める val a = 0 var b = 0 val nums = Array(10000) { 0 } val node = ListNode(0) // ループ内の変数は O(1) の空間を占める for (i in 0..() for (i in 0..() for (i in 0..?>(n) // 二次元リストは O(n^2) の空間を使用 val numList = mutableListOf>() for (i in 0..() for (j in 0.. nums[j + 1]) { // nums[j] と nums[j + 1] を交換 val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp count += 3 // 要素交換には 3 回の単位操作が含まれる } } } return count } /* 指数時間(ループ実装) */ fun exponential(n: Int): Int { var count = 0 var base = 1 // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する for (i in 0.. 1) { n1 /= 2 count++ } return count } /* 対数時間(再帰実装) */ fun logRecur(n: Int): Int { if (n <= 1) return 0 return logRecur(n / 2) + 1 } /* 線形対数時間 */ fun linearLogRecur(n: Int): Int { if (n <= 1) return 1 var count = linearLogRecur(n / 2) + linearLogRecur(n / 2) for (i in 0.. { val nums = IntArray(n) // 配列 nums = { 1, 2, 3, ..., n } を生成 for (i in 0..(n) for (i in 0..): Int { for (i in nums.indices) { // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる if (nums[i] == 1) return i } return -1 } /* Driver Code */ fun main() { for (i in 0..9) { val n = 100 val nums = randomNumbers(n) val index = findOne(nums) println("\n配列 [ 1, 2, ..., n ] をシャッフルした後 = ${nums.contentToString()}") println("数字 1 のインデックスは $index") } } ================================================ FILE: ja/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt ================================================ /** * File: binary_search_recur.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.binary_search_recur /* 二分探索:問題 f(i, j) */ fun dfs( nums: IntArray, target: Int, i: Int, j: Int ): Int { // 区間が空なら対象要素は存在しないので -1 を返す if (i > j) { return -1 } // 中点インデックス m を計算 val m = (i + j) / 2 return if (nums[m] < target) { // 部分問題 f(m+1, j) を再帰的に解く dfs(nums, target, m + 1, j) } else if (nums[m] > target) { // 部分問題 f(i, m-1) を再帰的に解く dfs(nums, target, i, m - 1) } else { // 目標要素が見つかったらそのインデックスを返す m } } /* 二分探索 */ fun binarySearch(nums: IntArray, target: Int): Int { val n = nums.size // 問題 f(0, n-1) を解く return dfs(nums, target, 0, n - 1) } /* Driver Code */ fun main() { val target = 6 val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) // 二分探索(両閉区間) val index = binarySearch(nums, target) println("対象要素 6 のインデックス = $index") } ================================================ FILE: ja/codes/kotlin/chapter_divide_and_conquer/build_tree.kt ================================================ /** * File: build_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.build_tree import utils.TreeNode import utils.printTree /* 二分木を構築:分割統治 */ fun dfs( preorder: IntArray, inorderMap: Map, i: Int, l: Int, r: Int ): TreeNode? { // 部分木区間が空なら終了する if (r - l < 0) return null // ルートノードを初期化する val root = TreeNode(preorder[i]) // m を求めて左右部分木を分割する val m = inorderMap[preorder[i]]!! // 部分問題:左部分木を構築する root.left = dfs(preorder, inorderMap, i + 1, l, m - 1) // 部分問題:右部分木を構築する root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r) // 根ノードを返す return root } /* 二分木を構築 */ fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? { // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する val inorderMap = HashMap() for (i in inorder.indices) { inorderMap[inorder[i]] = i } val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1) return root } /* Driver Code */ fun main() { val preorder = intArrayOf(3, 9, 2, 1, 7) val inorder = intArrayOf(9, 3, 1, 2, 7) println("前順走査 = ${preorder.contentToString()}") println("中順走査 = ${inorder.contentToString()}") val root = buildTree(preorder, inorder) println("構築した二分木:") printTree(root) } ================================================ FILE: ja/codes/kotlin/chapter_divide_and_conquer/hanota.kt ================================================ /** * File: hanota.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.hanota /* 円盤を 1 枚移動 */ fun move(src: MutableList, tar: MutableList) { // src の上から円盤を1枚取り出す val pan = src.removeAt(src.size - 1) // 円盤を tar の上に置く tar.add(pan) } /* ハノイの塔の問題 f(i) を解く */ fun dfs(i: Int, src: MutableList, buf: MutableList, tar: MutableList) { // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if (i == 1) { move(src, tar) return } // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i - 1, src, tar, buf) // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src, tar) // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i - 1, buf, src, tar) } /* ハノイの塔を解く */ fun solveHanota(A: MutableList, B: MutableList, C: MutableList) { val n = A.size // A の上から n 枚の円盤を B を介して C へ移す dfs(n, A, B, C) } /* Driver Code */ fun main() { // リスト末尾が柱の頂上 val A = mutableListOf(5, 4, 3, 2, 1) val B = mutableListOf() val C = mutableListOf() println("初期状態:") println("A = $A") println("B = $B") println("C = $C") solveHanota(A, B, C) println("円盤の移動完了後:") println("A = $A") println("B = $B") println("C = $C") } ================================================ FILE: ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt ================================================ /** * File: climbing_stairs_backtrack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* バックトラッキング */ fun backtrack( choices: MutableList, state: Int, n: Int, res: MutableList ) { // 第 n 段に到達したら、方法数を 1 増やす if (state == n) res[0] = res[0] + 1 // すべての選択肢を走査 for (choice in choices) { // 枝刈り: 第 n 段を超えないようにする if (state + choice > n) continue // 試行: 選択を行い、状態を更新 backtrack(choices, state + choice, n, res) // バックトラック } } /* 階段登り:バックトラッキング */ fun climbingStairsBacktrack(n: Int): Int { val choices = mutableListOf(1, 2) // 1 段または 2 段上ることを選べる val state = 0 // 第 0 段から上り始める val res = mutableListOf() res.add(0) // res[0] を使って方法数を記録する backtrack(choices, state, n, res) return res[0] } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsBacktrack(n) println("$n 段の階段の登り方は全部で $res 通り") } ================================================ FILE: ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt ================================================ /** * File: climbing_stairs_constraint_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 制約付き階段登り:動的計画法 */ fun climbingStairsConstraintDP(n: Int): Int { if (n == 1 || n == 2) { return 1 } // 部分問題の解を保存するために dp テーブルを初期化 val dp = Array(n + 1) { IntArray(3) } // 初期状態:最小部分問題の解をあらかじめ設定 dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (i in 3..n) { dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] } return dp[n][1] + dp[n][2] } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsConstraintDP(n) println("$n 段の階段の登り方は全部で $res 通り") } ================================================ FILE: ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt ================================================ /** * File: climbing_stairs_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 検索 */ fun dfs(i: Int): Int { // dp[1] と dp[2] は既知なので返す if (i == 1 || i == 2) return i // dp[i] = dp[i-1] + dp[i-2] val count = dfs(i - 1) + dfs(i - 2) return count } /* 階段登り:探索 */ fun climbingStairsDFS(n: Int): Int { return dfs(n) } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsDFS(n) println("$n 段の階段の登り方は全部で $res 通り") } ================================================ FILE: ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt ================================================ /** * File: climbing_stairs_dfs_mem.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* メモ化探索 */ fun dfs(i: Int, mem: IntArray): Int { // dp[1] と dp[2] は既知なので返す if (i == 1 || i == 2) return i // dp[i] の記録があれば、それをそのまま返す if (mem[i] != -1) return mem[i] // dp[i] = dp[i-1] + dp[i-2] val count = dfs(i - 1, mem) + dfs(i - 2, mem) // dp[i] を記録する mem[i] = count return count } /* 階段登り:メモ化探索 */ fun climbingStairsDFSMem(n: Int): Int { // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す val mem = IntArray(n + 1) mem.fill(-1) return dfs(n, mem) } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsDFSMem(n) println("$n 段の階段の登り方は全部で $res 通り") } ================================================ FILE: ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt ================================================ /** * File: climbing_stairs_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 階段登り:動的計画法 */ fun climbingStairsDP(n: Int): Int { if (n == 1 || n == 2) return n // 部分問題の解を保存するために dp テーブルを初期化 val dp = IntArray(n + 1) // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = 1 dp[2] = 2 // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (i in 3..n) { dp[i] = dp[i - 1] + dp[i - 2] } return dp[n] } /* 階段登り:空間最適化した動的計画法 */ fun climbingStairsDPComp(n: Int): Int { if (n == 1 || n == 2) return n var a = 1 var b = 2 for (i in 3..n) { val temp = b b += a a = temp } return b } /* Driver Code */ fun main() { val n = 9 var res = climbingStairsDP(n) println("$n 段の階段の登り方は全部で $res 通り") res = climbingStairsDPComp(n) println("$n 段の階段の登り方は全部で $res 通り") } ================================================ FILE: ja/codes/kotlin/chapter_dynamic_programming/coin_change.kt ================================================ /** * File: coin_change.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* コイン両替:動的計画法 */ fun coinChangeDP(coins: IntArray, amt: Int): Int { val n = coins.size val MAX = amt + 1 // dp テーブルを初期化 val dp = Array(n + 1) { IntArray(amt + 1) } // 状態遷移:先頭行と先頭列 for (a in 1..amt) { dp[0][a] = MAX } // 状態遷移: 残りの行と列 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a] } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) } } } return if (dp[n][amt] != MAX) dp[n][amt] else -1 } /* コイン交換:空間最適化後の動的計画法 */ fun coinChangeDPComp(coins: IntArray, amt: Int): Int { val n = coins.size val MAX = amt + 1 // dp テーブルを初期化 val dp = IntArray(amt + 1) dp.fill(MAX) dp[0] = 0 // 状態遷移 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a] } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) } } } return if (dp[amt] != MAX) dp[amt] else -1 } /* Driver Code */ fun main() { val coins = intArrayOf(1, 2, 5) val amt = 4 // 動的計画法 var res = coinChangeDP(coins, amt) println("目標金額を作るのに必要な最小硬貨枚数は $res") // 空間最適化後の動的計画法 res = coinChangeDPComp(coins, amt) println("目標金額を作るのに必要な最小硬貨枚数は $res") } ================================================ FILE: ja/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt ================================================ /** * File: coin_change_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* コイン両替 II:動的計画法 */ fun coinChangeIIDP(coins: IntArray, amt: Int): Int { val n = coins.size // dp テーブルを初期化 val dp = Array(n + 1) { IntArray(amt + 1) } // 先頭列を初期化する for (i in 0..n) { dp[i][0] = 1 } // 状態遷移 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a] } else { // コイン i を選ばない場合と選ぶ場合の和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] } } } return dp[n][amt] } /* コイン両替 II:空間最適化した動的計画法 */ fun coinChangeIIDPComp(coins: IntArray, amt: Int): Int { val n = coins.size // dp テーブルを初期化 val dp = IntArray(amt + 1) dp[0] = 1 // 状態遷移 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a] } else { // コイン i を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a - coins[i - 1]] } } } return dp[amt] } /* Driver Code */ fun main() { val coins = intArrayOf(1, 2, 5) val amt = 5 // 動的計画法 var res = coinChangeIIDP(coins, amt) println("目標金額を作る硬貨の組み合わせ数は $res") // 空間最適化後の動的計画法 res = coinChangeIIDPComp(coins, amt) println("目標金額を作る硬貨の組み合わせ数は $res") } ================================================ FILE: ja/codes/kotlin/chapter_dynamic_programming/edit_distance.kt ================================================ /** * File: edit_distance.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* 編集距離:総当たり探索 */ fun editDistanceDFS( s: String, t: String, i: Int, j: Int ): Int { // s と t がともに空なら 0 を返す if (i == 0 && j == 0) return 0 // s が空なら t の長さを返す if (i == 0) return j // t が空なら s の長さを返す if (j == 0) return i // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1) // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 val insert = editDistanceDFS(s, t, i, j - 1) val delete = editDistanceDFS(s, t, i - 1, j) val replace = editDistanceDFS(s, t, i - 1, j - 1) // 最小編集回数を返す return min(min(insert, delete), replace) + 1 } /* 編集距離:メモ化探索 */ fun editDistanceDFSMem( s: String, t: String, mem: Array, i: Int, j: Int ): Int { // s と t がともに空なら 0 を返す if (i == 0 && j == 0) return 0 // s が空なら t の長さを返す if (i == 0) return j // t が空なら s の長さを返す if (j == 0) return i // 記録済みなら、それをそのまま返す if (mem[i][j] != -1) return mem[i][j] // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1) // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 val insert = editDistanceDFSMem(s, t, mem, i, j - 1) val delete = editDistanceDFSMem(s, t, mem, i - 1, j) val replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1) // 最小編集回数を記録して返す mem[i][j] = min(min(insert, delete), replace) + 1 return mem[i][j] } /* 編集距離:動的計画法 */ fun editDistanceDP(s: String, t: String): Int { val n = s.length val m = t.length val dp = Array(n + 1) { IntArray(m + 1) } // 状態遷移:先頭行と先頭列 for (i in 1..n) { dp[i][0] = i } for (j in 1..m) { dp[0][j] = j } // 状態遷移: 残りの行と列 for (i in 1..n) { for (j in 1..m) { if (s[i - 1] == t[j - 1]) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i - 1][j - 1] } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 } } } return dp[n][m] } /* 編集距離:空間最適化した動的計画法 */ fun editDistanceDPComp(s: String, t: String): Int { val n = s.length val m = t.length val dp = IntArray(m + 1) // 状態遷移:先頭行 for (j in 1..m) { dp[j] = j } // 状態遷移:残りの行 for (i in 1..n) { // 状態遷移:先頭列 var leftup = dp[0] // dp[i-1, j-1] を一時保存する dp[0] = i // 状態遷移:残りの列 for (j in 1..m) { val temp = dp[j] if (s[i - 1] == t[j - 1]) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftup } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 } leftup = temp // 次の反復の dp[i-1, j-1] に更新する } } return dp[m] } /* Driver Code */ fun main() { val s = "bag" val t = "pack" val n = s.length val m = t.length // 全探索 var res = editDistanceDFS(s, t, n, m) println("$s を $t に変更するには最小で $res 回の編集が必要") // メモ化探索 val mem = Array(n + 1) { IntArray(m + 1) } for (row in mem) row.fill(-1) res = editDistanceDFSMem(s, t, mem, n, m) println("$s を $t に変更するには最小で $res 回の編集が必要") // 動的計画法 res = editDistanceDP(s, t) println("$s を $t に変更するには最小で $res 回の編集が必要") // 空間最適化後の動的計画法 res = editDistanceDPComp(s, t) println("$s を $t に変更するには最小で $res 回の編集が必要") } ================================================ FILE: ja/codes/kotlin/chapter_dynamic_programming/knapsack.kt ================================================ /** * File: knapsack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.max /* 0-1 ナップサック:総当たり探索 */ fun knapsackDFS( wgt: IntArray, _val: IntArray, i: Int, c: Int ): Int { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 || c == 0) { return 0 } // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFS(wgt, _val, i - 1, c) } // 品物 i を入れない場合と入れる場合の最大価値を計算する val no = knapsackDFS(wgt, _val, i - 1, c) val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1] // 2つの案のうち価値が大きいほうを返す return max(no, yes) } /* 0-1 ナップサック:メモ化探索 */ fun knapsackDFSMem( wgt: IntArray, _val: IntArray, mem: Array, i: Int, c: Int ): Int { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 || c == 0) { return 0 } // 既に記録があればそのまま返す if (mem[i][c] != -1) { return mem[i][c] } // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, _val, mem, i - 1, c) } // 品物 i を入れない場合と入れる場合の最大価値を計算する val no = knapsackDFSMem(wgt, _val, mem, i - 1, c) val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1] // 2 つの案のうち価値が大きい方を記録して返す mem[i][c] = max(no, yes) return mem[i][c] } /* 0-1 ナップサック:動的計画法 */ fun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int { val n = wgt.size // dp テーブルを初期化 val dp = Array(n + 1) { IntArray(cap + 1) } // 状態遷移 for (i in 1..n) { for (c in 1..cap) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c] } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1]) } } } return dp[n][cap] } /* 0-1 ナップサック:空間最適化後の動的計画法 */ fun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int { val n = wgt.size // dp テーブルを初期化 val dp = IntArray(cap + 1) // 状態遷移 for (i in 1..n) { // 逆順に走査する for (c in cap downTo 1) { if (wgt[i - 1] <= c) { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) } } } return dp[cap] } /* Driver Code */ fun main() { val wgt = intArrayOf(10, 20, 30, 40, 50) val _val = intArrayOf(50, 120, 150, 210, 240) val cap = 50 val n = wgt.size // 全探索 var res = knapsackDFS(wgt, _val, n, cap) println("ナップサック容量を超えない最大価値は $res") // メモ化探索 val mem = Array(n + 1) { IntArray(cap + 1) } for (row in mem) { row.fill(-1) } res = knapsackDFSMem(wgt, _val, mem, n, cap) println("ナップサック容量を超えない最大価値は $res") // 動的計画法 res = knapsackDP(wgt, _val, cap) println("ナップサック容量を超えない最大価値は $res") // 空間最適化後の動的計画法 res = knapsackDPComp(wgt, _val, cap) println("ナップサック容量を超えない最大価値は $res") } ================================================ FILE: ja/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt ================================================ /** * File: min_cost_climbing_stairs_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* 階段登りの最小コスト:動的計画法 */ fun minCostClimbingStairsDP(cost: IntArray): Int { val n = cost.size - 1 if (n == 1 || n == 2) return cost[n] // 部分問題の解を保存するために dp テーブルを初期化 val dp = IntArray(n + 1) // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = cost[1] dp[2] = cost[2] // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (i in 3..n) { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] } return dp[n] } /* 階段昇りの最小コスト:空間最適化後の動的計画法 */ fun minCostClimbingStairsDPComp(cost: IntArray): Int { val n = cost.size - 1 if (n == 1 || n == 2) return cost[n] var a = cost[1] var b = cost[2] for (i in 3..n) { val tmp = b b = min(a, tmp) + cost[i] a = tmp } return b } /* Driver Code */ fun main() { val cost = intArrayOf(0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1) println("入力された階段コストのリストは ${cost.contentToString()}") var res = minCostClimbingStairsDP(cost) println("階段を上り切る最小コストは $res") res = minCostClimbingStairsDPComp(cost) println("階段を上り切る最小コストは $res") } ================================================ FILE: ja/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt ================================================ /** * File: min_path_sum.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* 最小経路和:全探索 */ fun minPathSumDFS(grid: Array, i: Int, j: Int): Int { // 左上のセルなら探索を終了する if (i == 0 && j == 0) { return grid[0][0] } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return Int.MAX_VALUE } // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する val up = minPathSumDFS(grid, i - 1, j) val left = minPathSumDFS(grid, i, j - 1) // 左上隅から (i, j) までの最小経路コストを返す return min(left, up) + grid[i][j] } /* 最小経路和:メモ化探索 */ fun minPathSumDFSMem( grid: Array, mem: Array, i: Int, j: Int ): Int { // 左上のセルなら探索を終了する if (i == 0 && j == 0) { return grid[0][0] } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return Int.MAX_VALUE } // 既に記録があればそのまま返す if (mem[i][j] != -1) { return mem[i][j] } // 左と上のセルからの最小経路コスト val up = minPathSumDFSMem(grid, mem, i - 1, j) val left = minPathSumDFSMem(grid, mem, i, j - 1) // 左上から (i, j) までの最小経路コストを記録して返す mem[i][j] = min(left, up) + grid[i][j] return mem[i][j] } /* 最小経路和:動的計画法 */ fun minPathSumDP(grid: Array): Int { val n = grid.size val m = grid[0].size // dp テーブルを初期化 val dp = Array(n) { IntArray(m) } dp[0][0] = grid[0][0] // 状態遷移:先頭行 for (j in 1..): Int { val n = grid.size val m = grid[0].size // dp テーブルを初期化 val dp = IntArray(m) // 状態遷移:先頭行 dp[0] = grid[0][0] for (j in 1.. c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c] } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1]) } } } return dp[n][cap] } /* 完全ナップサック問題:空間最適化後の動的計画法 */ fun unboundedKnapsackDPComp( wgt: IntArray, _val: IntArray, cap: Int ): Int { val n = wgt.size // dp テーブルを初期化 val dp = IntArray(cap + 1) // 状態遷移 for (i in 1..n) { for (c in 1..cap) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c] } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) } } } return dp[cap] } /* Driver Code */ fun main() { val wgt = intArrayOf(1, 2, 3) val _val = intArrayOf(5, 11, 15) val cap = 4 // 動的計画法 var res = unboundedKnapsackDP(wgt, _val, cap) println("ナップサック容量を超えない最大価値は $res") // 空間最適化後の動的計画法 res = unboundedKnapsackDPComp(wgt, _val, cap) println("ナップサック容量を超えない最大価値は $res") } ================================================ FILE: ja/codes/kotlin/chapter_graph/graph_adjacency_list.kt ================================================ /** * File: graph_adjacency_list.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex /* 隣接リストに基づく無向グラフクラス */ class GraphAdjList(edges: Array>) { // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 val adjList = HashMap>() /* コンストラクタ */ init { // すべての頂点と辺を追加 for (edge in edges) { addVertex(edge[0]!!) addVertex(edge[1]!!) addEdge(edge[0]!!, edge[1]!!) } } /* 頂点数を取得 */ fun size(): Int { return adjList.size } /* 辺を追加 */ fun addEdge(vet1: Vertex, vet2: Vertex) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) throw IllegalArgumentException() // 辺 vet1 - vet2 を追加 adjList[vet1]?.add(vet2) adjList[vet2]?.add(vet1) } /* 辺を削除 */ fun removeEdge(vet1: Vertex, vet2: Vertex) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) throw IllegalArgumentException() // 辺 vet1 - vet2 を削除 adjList[vet1]?.remove(vet2) adjList[vet2]?.remove(vet1) } /* 頂点を追加 */ fun addVertex(vet: Vertex) { if (adjList.containsKey(vet)) return // 隣接リストに新しいリストを追加 adjList[vet] = mutableListOf() } /* 頂点を削除 */ fun removeVertex(vet: Vertex) { if (!adjList.containsKey(vet)) throw IllegalArgumentException() // 隣接リストから頂点 vet に対応するリストを削除 adjList.remove(vet) // 他の頂点のリストを走査し、vet を含むすべての辺を削除 for (list in adjList.values) { list.remove(vet) } } /* 隣接リストを出力 */ fun print() { println("隣接リスト =") for (pair in adjList.entries) { val tmp = mutableListOf() for (vertex in pair.value) { tmp.add(vertex._val) } println("${pair.key._val}: $tmp,") } } } /* Driver Code */ fun main() { /* 無向グラフを初期化 */ val v = Vertex.valsToVets(intArrayOf(1, 3, 2, 5, 4)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[2], v[3]), arrayOf(v[2], v[4]), arrayOf(v[3], v[4]) ) val graph = GraphAdjList(edges) println("\n初期化後のグラフは") graph.print() /* 辺を追加 */ // 頂点 1, 2 は v[0], v[2] graph.addEdge(v[0]!!, v[2]!!) println("\n辺 1-2 を追加した後のグラフは") graph.print() /* 辺を削除 */ // 頂点 1, 3 は v[0], v[1] graph.removeEdge(v[0]!!, v[1]!!) println("\n辺 1-3 を削除した後のグラフは") graph.print() /* 頂点を追加 */ val v5 = Vertex(6) graph.addVertex(v5) println("\n頂点 6 を追加した後のグラフは") graph.print() /* 頂点を削除 */ // 頂点 3 は v[1] graph.removeVertex(v[1]!!) println("\n頂点 3 を削除した後のグラフは") graph.print() } ================================================ FILE: ja/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt ================================================ /** * File: graph_adjacency_matrix.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.printMatrix /* 隣接行列に基づく無向グラフクラス */ class GraphAdjMat(vertices: IntArray, edges: Array) { val vertices = mutableListOf() // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す val adjMat = mutableListOf>() // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 /* コンストラクタ */ init { // 頂点を追加 for (vertex in vertices) { addVertex(vertex) } // 辺を追加 // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する for (edge in edges) { addEdge(edge[0], edge[1]) } } /* 頂点数を取得 */ fun size(): Int { return vertices.size } /* 頂点を追加 */ fun addVertex(_val: Int) { val n = size() // 頂点リストに新しい頂点の値を追加 vertices.add(_val) // 隣接行列に 1 行追加 val newRow = mutableListOf() for (j in 0..= size()) throw IndexOutOfBoundsException() // 頂点リストから index の頂点を削除する vertices.removeAt(index) // 隣接行列で index 行を削除する adjMat.removeAt(index) // 隣接行列で index 列を削除する for (row in adjMat) { row.removeAt(index) } } /* 辺を追加 */ // 引数 i, j は vertices の要素インデックスに対応する fun addEdge(i: Int, j: Int) { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw IndexOutOfBoundsException() // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす adjMat[i][j] = 1 adjMat[j][i] = 1 } /* 辺を削除 */ // 引数 i, j は vertices の要素インデックスに対応する fun removeEdge(i: Int, j: Int) { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw IndexOutOfBoundsException() adjMat[i][j] = 0 adjMat[j][i] = 0 } /* 隣接行列を出力 */ fun print() { print("頂点リスト = ") println(vertices) println("隣接行列 =") printMatrix(adjMat) } } /* Driver Code */ fun main() { /* 無向グラフを初期化 */ // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 val vertices = intArrayOf(1, 3, 2, 5, 4) val edges = arrayOf( intArrayOf(0, 1), intArrayOf(0, 3), intArrayOf(1, 2), intArrayOf(2, 3), intArrayOf(2, 4), intArrayOf(3, 4) ) val graph = GraphAdjMat(vertices, edges) println("\n初期化後のグラフは") graph.print() /* 辺を追加 */ // 頂点 1, 2 のインデックスはそれぞれ 0, 2 graph.addEdge(0, 2) println("\n辺 1-2 を追加した後のグラフは") graph.print() /* 辺を削除 */ // 頂点 1, 3 のインデックスはそれぞれ 0, 1 graph.removeEdge(0, 1) println("\n辺 1-3 を削除した後のグラフは") graph.print() /* 頂点を追加 */ graph.addVertex(6) println("\n頂点 6 を追加した後のグラフは") graph.print() /* 頂点を削除 */ // 頂点 3 のインデックスは 1 graph.removeVertex(1) println("\n頂点 3 を削除した後のグラフは") graph.print() } ================================================ FILE: ja/codes/kotlin/chapter_graph/graph_bfs.kt ================================================ /** * File: graph_bfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex import java.util.* /* 幅優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList { // 頂点の走査順序 val res = mutableListOf() // 訪問済み頂点を記録するためのハッシュ集合 val visited = HashSet() visited.add(startVet) // BFS の実装にキューを用いる val que = LinkedList() que.offer(startVet) // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す while (!que.isEmpty()) { val vet = que.poll() // 先頭の頂点をデキュー res.add(vet) // 訪問した頂点を記録 // この頂点のすべての隣接頂点を走査 for (adjVet in graph.adjList[vet]!!) { if (visited.contains(adjVet)) continue // 訪問済みの頂点をスキップ que.offer(adjVet) // 未訪問の頂点のみをキューに追加 visited.add(adjVet) // この頂点を訪問済みにする } } // 頂点の走査順を返す return res } /* Driver Code */ fun main() { /* 無向グラフを初期化 */ val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[1], v[4]), arrayOf(v[2], v[5]), arrayOf(v[3], v[4]), arrayOf(v[3], v[6]), arrayOf(v[4], v[5]), arrayOf(v[4], v[7]), arrayOf(v[5], v[8]), arrayOf(v[6], v[7]), arrayOf(v[7], v[8]) ) val graph = GraphAdjList(edges) println("\n初期化後のグラフは") graph.print() /* 幅優先探索 */ val res = graphBFS(graph, v[0]!!) println("\n幅優先探索(BFS)の頂点順序は") println(Vertex.vetsToVals(res)) } ================================================ FILE: ja/codes/kotlin/chapter_graph/graph_dfs.kt ================================================ /** * File: graph_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex /* 深さ優先走査の補助関数 */ fun dfs( graph: GraphAdjList, visited: MutableSet, res: MutableList, vet: Vertex? ) { res.add(vet) // 訪問した頂点を記録 visited.add(vet) // この頂点を訪問済みにする // この頂点のすべての隣接頂点を走査 for (adjVet in graph.adjList[vet]!!) { if (visited.contains(adjVet)) continue // 訪問済みの頂点をスキップ // 隣接頂点を再帰的に訪問 dfs(graph, visited, res, adjVet) } } /* 深さ優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする fun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList { // 頂点の走査順序 val res = mutableListOf() // 訪問済み頂点を記録するためのハッシュ集合 val visited = HashSet() dfs(graph, visited, res, startVet) return res } /* Driver Code */ fun main() { /* 無向グラフを初期化 */ val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[2], v[5]), arrayOf(v[4], v[5]), arrayOf(v[5], v[6]) ) val graph = GraphAdjList(edges) println("\n初期化後のグラフは") graph.print() /* 深さ優先探索 */ val res = graphDFS(graph, v[0]) println("\n深さ優先探索(DFS)の頂点順序は") println(Vertex.vetsToVals(res)) } ================================================ FILE: ja/codes/kotlin/chapter_greedy/coin_change_greedy.kt ================================================ /** * File: coin_change_greedy.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy /* コイン交換:貪欲法 */ fun coinChangeGreedy(coins: IntArray, amt: Int): Int { // coins リストはソート済みと仮定する var am = amt var i = coins.size - 1 var count = 0 // 残額がなくなるまで貪欲選択を繰り返す while (am > 0) { // 残額以下で最も近い硬貨を見つける while (i > 0 && coins[i] > am) { i-- } // coins[i] を選択する am -= coins[i] count++ } // 実行可能な解が見つからなければ -1 を返す return if (am == 0) count else -1 } /* Driver Code */ fun main() { // 貪欲法:大域最適解を保証できる var coins = intArrayOf(1, 5, 10, 20, 50, 100) var amt = 186 var res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("$amt を作るのに必要な最小硬貨枚数は $res") // 貪欲法:大域最適解を保証できない coins = intArrayOf(1, 20, 50) amt = 60 res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("$amt を作るのに必要な最小硬貨枚数は $res") println("実際に必要な最小枚数は 3、つまり 20 + 20 + 20") // 貪欲法:大域最適解を保証できない coins = intArrayOf(1, 49, 50) amt = 98 res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("$amt を作るのに必要な最小硬貨枚数は $res") println("実際に必要な最小枚数は 2、つまり 49 + 49") } ================================================ FILE: ja/codes/kotlin/chapter_greedy/fractional_knapsack.kt ================================================ /** * File: fractional_knapsack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy /* 品物 */ class Item( val w: Int, // 品物 val v: Int // 品物の価値 ) /* 分数ナップサック:貪欲法 */ fun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double { // 重さと価値の 2 属性を持つ品物リストを作成 var cap = c val items = arrayOfNulls(wgt.size) for (i in wgt.indices) { items[i] = Item(wgt[i], _val[i]) } // 単位価値 item.v / item.w の高い順にソートする items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) } // 貪欲選択を繰り返す var res = 0.0 for (item in items) { if (item!!.w <= cap) { // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる res += item.v cap -= item.w } else { // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる res += item.v.toDouble() / item.w * cap // 残り容量がないため、ループを抜ける break } } return res } /* Driver Code */ fun main() { val wgt = intArrayOf(10, 20, 30, 40, 50) val _val = intArrayOf(50, 120, 150, 210, 240) val cap = 50 // 貪欲法 val res = fractionalKnapsack(wgt, _val, cap) println("ナップサック容量を超えない最大価値は $res") } ================================================ FILE: ja/codes/kotlin/chapter_greedy/max_capacity.kt ================================================ /** * File: max_capacity.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy import kotlin.math.max import kotlin.math.min /* 最大容量:貪欲法 */ fun maxCapacity(ht: IntArray): Int { // i, j を初期化し、それぞれ配列の両端に置く var i = 0 var j = ht.size - 1 // 初期の最大容量は 0 var res = 0 // 2 枚の板が出会うまで貪欲選択を繰り返す while (i < j) { // 最大容量を更新する val cap = min(ht[i], ht[j]) * (j - i) res = max(res, cap) // 短い方を内側へ動かす if (ht[i] < ht[j]) { i++ } else { j-- } } return res } /* Driver Code */ fun main() { val ht = intArrayOf(3, 8, 5, 2, 7, 7, 3, 4) // 貪欲法 val res = maxCapacity(ht) println("最大容量は $res") } ================================================ FILE: ja/codes/kotlin/chapter_greedy/max_product_cutting.kt ================================================ /** * File: max_product_cutting.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy import kotlin.math.pow /* 最大切断積:貪欲法 */ fun maxProductCutting(n: Int): Int { // n <= 3 のときは、必ず 1 を切り出す if (n <= 3) { return 1 * (n - 1) } // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする val a = n / 3 val b = n % 3 if (b == 1) { // 余りが 1 のときは、1 * 3 を 2 * 2 に変える return 3.0.pow((a - 1)).toInt() * 2 * 2 } if (b == 2) { // 余りが 2 のときは、そのままにする return 3.0.pow(a).toInt() * 2 * 2 } // 余りが 0 のときは、そのままにする return 3.0.pow(a).toInt() } /* Driver Code */ fun main() { val n = 58 // 貪欲法 val res = maxProductCutting(n) println("最大分割積は $res") } ================================================ FILE: ja/codes/kotlin/chapter_hashing/array_hash_map.kt ================================================ /** * File: array_hash_map.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* キーと値の組 */ class Pair( var key: Int, var _val: String ) /* 配列ベースのハッシュテーブル */ class ArrayHashMap { // 100 個のバケットを含む配列を初期化 private val buckets = arrayOfNulls(100) /* ハッシュ関数 */ fun hashFunc(key: Int): Int { val index = key % 100 return index } /* 検索操作 */ fun get(key: Int): String? { val index = hashFunc(key) val pair = buckets[index] ?: return null return pair._val } /* 追加操作 */ fun put(key: Int, _val: String) { val pair = Pair(key, _val) val index = hashFunc(key) buckets[index] = pair } /* 削除操作 */ fun remove(key: Int) { val index = hashFunc(key) // null に設定し、削除を表す buckets[index] = null } /* すべてのキーと値のペアを取得 */ fun pairSet(): MutableList { val pairSet = mutableListOf() for (pair in buckets) { if (pair != null) pairSet.add(pair) } return pairSet } /* すべてのキーを取得 */ fun keySet(): MutableList { val keySet = mutableListOf() for (pair in buckets) { if (pair != null) keySet.add(pair.key) } return keySet } /* すべての値を取得 */ fun valueSet(): MutableList { val valueSet = mutableListOf() for (pair in buckets) { if (pair != null) valueSet.add(pair._val) } return valueSet } /* ハッシュテーブルを出力 */ fun print() { for (kv in pairSet()) { val key = kv.key val _val = kv._val println("$key -> $_val") } } } /* Driver Code */ fun main() { /* ハッシュテーブルを初期化 */ val map = ArrayHashMap() /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.put(12836, "シャオハー") map.put(15937, "シャオルオ") map.put(16750, "シャオスワン") map.put(13276, "シャオファー") map.put(10583, "シャオヤ") println("\n追加完了後、ハッシュテーブルは\nKey -> Value") map.print() /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 val name = map.get(15937) println("\n学籍番号 15937 を入力すると、名前 $name が見つかりました") /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(10583) println("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value") map.print() /* ハッシュテーブルを走査 */ println("\nキーと値のペアを走査 Key -> Value") for (kv in map.pairSet()) { println("${kv.key} -> ${kv._val}") } println("\nキー Key を個別に走査") for (key in map.keySet()) { println(key) } println("\n値 Value を個別に走査") for (_val in map.valueSet()) { println(_val) } } ================================================ FILE: ja/codes/kotlin/chapter_hashing/built_in_hash.kt ================================================ /** * File: built_in_hash.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing import utils.ListNode /* Driver Code */ fun main() { val num = 3 val hashNum = num.hashCode() println("整数 $num のハッシュ値は $hashNum") val bol = true val hashBol = bol.hashCode() println("真偽値 $bol のハッシュ値は $hashBol") val dec = 3.14159 val hashDec = dec.hashCode() println("小数 $dec のハッシュ値は $hashDec") val str = "Hello アルゴリズム" val hashStr = str.hashCode() println("文字列 $str のハッシュ値は $hashStr") val arr = arrayOf(12836, "シャオハー") val hashTup = arr.contentHashCode() println("配列 ${arr.contentToString()} のハッシュ値は $hashTup") val obj = ListNode(0) val hashObj = obj.hashCode() println("ノードオブジェクト $obj のハッシュ値は $hashObj") } ================================================ FILE: ja/codes/kotlin/chapter_hashing/hash_map.kt ================================================ /** * File: hash_map.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing import utils.printHashMap /* Driver Code */ fun main() { /* ハッシュテーブルを初期化 */ val map = HashMap() /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map[12836] = "シャオハー" map[15937] = "シャオルオ" map[16750] = "シャオスワン" map[13276] = "シャオファー" map[10583] = "シャオヤ" println("\n追加完了後、ハッシュテーブルは\nKey -> Value") printHashMap(map) /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 val name = map[15937] println("\n学籍番号 15937 を入力すると、名前 $name が見つかりました") /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(10583) println("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value") printHashMap(map) /* ハッシュテーブルを走査 */ println("\nキーと値のペアを走査 Key->Value") for ((key, value) in map) { println("$key -> $value") } println("\nキー Key を個別に走査") for (key in map.keys) { println(key) } println("\n値 Value を個別に走査") for (_val in map.values) { println(_val) } } ================================================ FILE: ja/codes/kotlin/chapter_hashing/hash_map_chaining.kt ================================================ /** * File: hash_map_chaining.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* チェイン法ハッシュテーブル */ class HashMapChaining { var size: Int // キーと値のペア数 var capacity: Int // ハッシュテーブル容量 val loadThres: Double // リサイズを発動する負荷率のしきい値 val extendRatio: Int // 拡張倍率 var buckets: MutableList> // バケット配列 /* コンストラクタ */ init { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = mutableListOf() for (i in 0.. loadThres) { extend() } val index = hashFunc(key) val bucket = buckets[index] // バケットを走査し、指定した key が見つかれば対応する val を更新して返す for (pair in bucket) { if (pair.key == key) { pair._val = _val return } } // その key が存在しなければ、キーと値のペアを末尾に追加 val pair = Pair(key, _val) bucket.add(pair) size++ } /* 削除操作 */ fun remove(key: Int) { val index = hashFunc(key) val bucket = buckets[index] // バケットを走査してキーと値のペアを削除 for (pair in bucket) { if (pair.key == key) { bucket.remove(pair) size-- break } } } /* ハッシュテーブルを拡張 */ fun extend() { // 元のハッシュテーブルを一時保存 val bucketsTmp = buckets // リサイズ後の新しいハッシュテーブルを初期化 capacity *= extendRatio // mutablelist には固定サイズがない buckets = mutableListOf() for (i in 0..() for (pair in bucket) { val k = pair.key val v = pair._val res.add("$k -> $v") } println(res) } } } /* Driver Code */ fun main() { /* ハッシュテーブルを初期化 */ val map = HashMapChaining() /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.put(12836, "シャオハー") map.put(15937, "シャオルオ") map.put(16750, "シャオスワン") map.put(13276, "シャオファー") map.put(10583, "シャオヤ") println("\n追加完了後、ハッシュテーブルは\nKey -> Value") map.print() /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 val name = map.get(13276) println("\n学籍番号 13276 を入力すると、名前 $name が見つかりました") /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(12836) println("\n12836 を削除した後、ハッシュテーブルは\nKey -> Value") map.print() } ================================================ FILE: ja/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt ================================================ /** * File: hash_map_open_addressing.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* オープンアドレス法ハッシュテーブル */ class HashMapOpenAddressing { private var size: Int // キーと値のペア数 private var capacity: Int // ハッシュテーブル容量 private val loadThres: Double // リサイズを発動する負荷率のしきい値 private val extendRatio: Int // 拡張倍率 private var buckets: Array // バケット配列 private val TOMBSTONE: Pair // 削除済みマーク /* コンストラクタ */ init { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = arrayOfNulls(capacity) TOMBSTONE = Pair(-1, "-1") } /* ハッシュ関数 */ fun hashFunc(key: Int): Int { return key % capacity } /* 負荷率 */ fun loadFactor(): Double { return (size / capacity).toDouble() } /* key に対応するバケットインデックスを探す */ fun findBucket(key: Int): Int { var index = hashFunc(key) var firstTombstone = -1 // 線形プロービングを行い、空バケットに達したら終了 while (buckets[index] != null) { // key が見つかったら、対応するバケットのインデックスを返す if (buckets[index]?.key == key) { // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index] buckets[index] = TOMBSTONE return firstTombstone // 移動後のバケットインデックスを返す } return index // バケットのインデックスを返す } // 最初に見つかった削除マークを記録 if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index } // バケットのインデックスを計算し、末尾を越えたら先頭に戻る index = (index + 1) % capacity } // key が存在しない場合は追加位置のインデックスを返す return if (firstTombstone == -1) index else firstTombstone } /* 検索操作 */ fun get(key: Int): String? { // key に対応するバケットインデックスを探す val index = findBucket(key) // キーと値の組が見つかったら、対応する val を返す if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index]?._val } // キーと値の組が存在しなければ null を返す return null } /* 追加操作 */ fun put(key: Int, _val: String) { // 負荷率がしきい値を超えたら、リサイズを実行 if (loadFactor() > loadThres) { extend() } // key に対応するバケットインデックスを探す val index = findBucket(key) // キーと値の組が見つかったら、val を上書きして返す if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index]!!._val = _val return } // キーと値の組が存在しない場合は、その組を追加する buckets[index] = Pair(key, _val) size++ } /* 削除操作 */ fun remove(key: Int) { // key に対応するバケットインデックスを探す val index = findBucket(key) // キーと値の組が見つかったら、削除マーカーで上書きする if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE size-- } } /* ハッシュテーブルを拡張 */ fun extend() { // 元のハッシュテーブルを一時保存 val bucketsTmp = buckets // リサイズ後の新しいハッシュテーブルを初期化 capacity *= extendRatio buckets = arrayOfNulls(capacity) size = 0 // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for (pair in bucketsTmp) { if (pair != null && pair != TOMBSTONE) { put(pair.key, pair._val) } } } /* ハッシュテーブルを出力 */ fun print() { for (pair in buckets) { if (pair == null) { println("null") } else if (pair == TOMBSTONE) { println("TOMESTOME") } else { println("${pair.key} -> ${pair._val}") } } } } /* Driver Code */ fun main() { // ハッシュテーブルを初期化 val hashmap = HashMapOpenAddressing() // 追加操作 // ハッシュテーブルにキーと値の組 (key, val) を追加する hashmap.put(12836, "シャオハー") hashmap.put(15937, "シャオルオ") hashmap.put(16750, "シャオスワン") hashmap.put(13276, "シャオファー") hashmap.put(10583, "シャオヤー") println("\n追加完了後、ハッシュテーブルは\nKey -> Value") hashmap.print() // 検索操作 // ハッシュテーブルにキー key を入力し、値 val を得る val name = hashmap.get(13276) println("\n学籍番号 13276 を入力すると、名前 $name が見つかりました") // 削除操作 // ハッシュテーブルからキーと値の組 (key, val) を削除する hashmap.remove(16750) println("\n16750 を削除した後、ハッシュテーブルは\nKey -> Value") hashmap.print() } ================================================ FILE: ja/codes/kotlin/chapter_hashing/simple_hash.kt ================================================ /** * File: simple_hash.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* 加算ハッシュ */ fun addHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = (hash + c.code) % MODULUS } return hash.toInt() } /* 乗算ハッシュ */ fun mulHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = (31 * hash + c.code) % MODULUS } return hash.toInt() } /* XOR ハッシュ */ fun xorHash(key: String): Int { var hash = 0 val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = hash xor c.code } return hash and MODULUS } /* 回転ハッシュ */ fun rotHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS } return hash.toInt() } /* Driver Code */ fun main() { val key = "Hello アルゴリズム" var hash = addHash(key) println("加算ハッシュ値は $hash") hash = mulHash(key) println("乗算ハッシュ値は $hash") hash = xorHash(key) println("XORハッシュ値は $hash") hash = rotHash(key) println("回転ハッシュ値は $hash") } ================================================ FILE: ja/codes/kotlin/chapter_heap/heap.kt ================================================ /** * File: heap.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* fun testPush(heap: Queue, _val: Int) { heap.offer(_val) // 要素をヒープに追加 print("\n要素 $_val をヒープに追加した後\n") printHeap(heap) } fun testPop(heap: Queue) { val _val = heap.poll() // ヒープ頂点の要素を取り出す print("\nヒープ先頭要素 $_val を取り出した後\n") printHeap(heap) } /* Driver Code */ fun main() { /* ヒープを初期化 */ // 最小ヒープを初期化 var minHeap = PriorityQueue() // 最大ヒープを初期化する(lambda 式で Comparator を変更すればよい) val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } println("\n以下のテストケースは最大ヒープです") /* 要素をヒープに追加 */ testPush(maxHeap, 1) testPush(maxHeap, 3) testPush(maxHeap, 2) testPush(maxHeap, 5) testPush(maxHeap, 4) /* ヒープ頂点の要素を取得 */ val peek = maxHeap.peek() print("\nヒープ先頭要素は $peek\n") /* ヒープ頂点の要素を取り出す */ testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) /* ヒープのサイズを取得 */ val size = maxHeap.size print("\nヒープ要素数は $size\n") /* ヒープが空かどうかを判定 */ val isEmpty = maxHeap.isEmpty() print("\nヒープが空かどうか $isEmpty\n") /* リストを入力してヒープを構築 */ // 時間計算量は O(n) であり、O(nlogn) ではない minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) println("\n入力リストから最小ヒープを構築した後") printHeap(minHeap) } ================================================ FILE: ja/codes/kotlin/chapter_heap/my_heap.kt ================================================ /** * File: my_heap.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* /* 最大ヒープ */ class MaxHeap(nums: MutableList?) { // 配列ではなくリストを使うことで、拡張を考慮する必要がない private val maxHeap = mutableListOf() /* コンストラクタ。入力リストに基づいてヒープを構築する */ init { // リスト要素をそのままヒープに追加 maxHeap.addAll(nums!!) // 葉ノード以外のすべてのノードをヒープ化 for (i in parent(size() - 1) downTo 0) { siftDown(i) } } /* 左子ノードのインデックスを取得 */ private fun left(i: Int): Int { return 2 * i + 1 } /* 右子ノードのインデックスを取得 */ private fun right(i: Int): Int { return 2 * i + 2 } /* 親ノードのインデックスを取得 */ private fun parent(i: Int): Int { return (i - 1) / 2 // 切り捨て除算 } /* 要素を交換 */ private fun swap(i: Int, j: Int) { val temp = maxHeap[i] maxHeap[i] = maxHeap[j] maxHeap[j] = temp } /* ヒープのサイズを取得 */ fun size(): Int { return maxHeap.size } /* ヒープが空かどうかを判定 */ fun isEmpty(): Boolean { /* ヒープが空かどうかを判定 */ return size() == 0 } /* ヒープ先頭要素にアクセス */ fun peek(): Int { return maxHeap[0] } /* 要素をヒープに追加 */ fun push(_val: Int) { // ノードを追加 maxHeap.add(_val) // 下から上へヒープ化 siftUp(size() - 1) } /* ノード i から始めて、下から上へヒープ化 */ private fun siftUp(it: Int) { // Kotlin の関数引数は不変のため、一時変数を作成する var i = it while (true) { // ノード i の親ノードを取得 val p = parent(i) // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 if (p < 0 || maxHeap[i] <= maxHeap[p]) break // 2 つのノードを交換 swap(i, p) // ループで下から上へヒープ化 i = p } } /* 要素をヒープから取り出す */ fun pop(): Int { // 空判定の処理 if (isEmpty()) throw IndexOutOfBoundsException() // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) swap(0, size() - 1) // ノードを削除 val _val = maxHeap.removeAt(size() - 1) // 上から下へヒープ化 siftDown(0) // ヒープ先頭要素を返す return _val } /* ノード i から始めて、上から下へヒープ化 */ private fun siftDown(it: Int) { // Kotlin の関数引数は不変のため、一時変数を作成する var i = it while (true) { // ノード i, l, r のうち値が最大のノードを ma とする val l = left(i) val r = right(i) var ma = i if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma == i) break // 2 つのノードを交換 swap(i, ma) // ループで上から下へヒープ化 i = ma } } /* ヒープ(二分木)を出力 */ fun print() { val queue = PriorityQueue { a: Int, b: Int -> b - a } queue.addAll(maxHeap) printHeap(queue) } } /* Driver Code */ fun main() { /* 最大ヒープを初期化 */ val maxHeap = MaxHeap(mutableListOf(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)) println("\n入力リストからヒープを構築した後") maxHeap.print() /* ヒープ頂点の要素を取得 */ var peek = maxHeap.peek() print("\nヒープ先頭要素は $peek\n") /* 要素をヒープに追加 */ val _val = 7 maxHeap.push(_val) print("\n要素 $_val をヒープに追加した後\n") maxHeap.print() /* ヒープ頂点の要素を取り出す */ peek = maxHeap.pop() print("\nヒープ先頭要素 $peek を取り出した後\n") maxHeap.print() /* ヒープのサイズを取得 */ val size = maxHeap.size() print("\nヒープ要素数は $size\n") /* ヒープが空かどうかを判定 */ val isEmpty = maxHeap.isEmpty() print("\nヒープが空かどうか $isEmpty\n") } ================================================ FILE: ja/codes/kotlin/chapter_heap/top_k.kt ================================================ /** * File: top_k.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* /* ヒープに基づいて配列中の最大の k 個の要素を探す */ fun topKHeap(nums: IntArray, k: Int): Queue { // 最小ヒープを初期化 val heap = PriorityQueue() // 配列の先頭 k 個の要素をヒープに追加 for (i in 0.. heap.peek()) { heap.poll() heap.offer(nums[i]) } } return heap } /* Driver Code */ fun main() { val nums = intArrayOf(1, 7, 6, 3, 2) val k = 3 val res = topKHeap(nums, k) println("最大の $k 個の要素は") printHeap(res) } ================================================ FILE: ja/codes/kotlin/chapter_searching/binary_search.kt ================================================ /** * File: binary_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* 二分探索(両閉区間) */ fun binarySearch(nums: IntArray, target: Int): Int { // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す var i = 0 var j = nums.size - 1 // ループし、探索区間が空になったら終了する(i > j で空) while (i <= j) { val m = i + (j - i) / 2 // 中点インデックス m を計算 if (nums[m] < target) // この場合、target は区間 [m+1, j] にある i = m + 1 else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある j = m - 1 else // 目標要素が見つかったらそのインデックスを返す return m } // 目標要素が見つからなければ -1 を返す return -1 } /* 二分探索(左閉右開区間) */ fun binarySearchLCRO(nums: IntArray, target: Int): Int { // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す var i = 0 var j = nums.size // ループし、探索区間が空になったら終了する(i = j で空) while (i < j) { val m = i + (j - i) / 2 // 中点インデックス m を計算 if (nums[m] < target) // この場合、target は区間 [m+1, j) にある i = m + 1 else if (nums[m] > target) // この場合、target は区間 [i, m) にある j = m else // 目標要素が見つかったらそのインデックスを返す return m } // 目標要素が見つからなければ -1 を返す return -1 } /* Driver Code */ fun main() { val target = 6 val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) /* 二分探索(両閉区間) */ var index = binarySearch(nums, target) println("対象要素 6 のインデックス = $index") /* 二分探索(左閉右開区間) */ index = binarySearchLCRO(nums, target) println("対象要素 6 のインデックス = $index") } ================================================ FILE: ja/codes/kotlin/chapter_searching/binary_search_edge.kt ================================================ /** * File: binary_search_edge.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* 最も左の target を二分探索 */ fun binarySearchLeftEdge(nums: IntArray, target: Int): Int { // target の挿入位置を探すのと等価 val i = binarySearchInsertion(nums, target) // target が見つからなければ、-1 を返す if (i == nums.size || nums[i] != target) { return -1 } // target が見つかったら、インデックス i を返す return i } /* 最も右の target を二分探索 */ fun binarySearchRightEdge(nums: IntArray, target: Int): Int { // 最左の target + 1 を探す問題に変換する val i = binarySearchInsertion(nums, target + 1) // j は最も右の target を指し、i は target より大きい最初の要素を指す val j = i - 1 // target が見つからなければ、-1 を返す if (j == -1 || nums[j] != target) { return -1 } // target が見つかったら、インデックス j を返す return j } /* Driver Code */ fun main() { // 重複要素を含む配列 val nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) println("\n配列 nums = ${nums.contentToString()}") // 二分探索で左端と右端を探す for (target in intArrayOf(6, 7)) { var index = binarySearchLeftEdge(nums, target) println("最も左にある要素 $target のインデックスは $index") index = binarySearchRightEdge(nums, target) println("最も右にある要素 $target のインデックスは $index") } } ================================================ FILE: ja/codes/kotlin/chapter_searching/binary_search_insertion.kt ================================================ /** * File: binary_search_insertion.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* 二分探索で挿入位置を探す(重複要素なし) */ fun binarySearchInsertionSimple(nums: IntArray, target: Int): Int { var i = 0 var j = nums.size - 1 // 両閉区間 [0, n-1] を初期化 while (i <= j) { val m = i + (j - i) / 2 // 中点インデックス m を計算 if (nums[m] < target) { i = m + 1 // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1 // target は区間 [i, m-1] にある } else { return m // target が見つかったら、挿入位置 m を返す } } // target が見つからなければ、挿入位置 i を返す return i } /* 二分探索で挿入位置を探す(重複要素あり) */ fun binarySearchInsertion(nums: IntArray, target: Int): Int { var i = 0 var j = nums.size - 1 // 両閉区間 [0, n-1] を初期化 while (i <= j) { val m = i + (j - i) / 2 // 中点インデックス m を計算 if (nums[m] < target) { i = m + 1 // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1 // target は区間 [i, m-1] にある } else { j = m - 1 // target より小さい最初の要素は区間 [i, m-1] にある } } // 挿入位置 i を返す return i } /* Driver Code */ fun main() { // 重複要素のない配列 var nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) println("\n配列 nums = ${nums.contentToString()}") // 二分探索で挿入位置を探す for (target in intArrayOf(6, 9)) { val index = binarySearchInsertionSimple(nums, target) println("要素 $target の挿入位置のインデックスは $index") } // 重複要素を含む配列 nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) println("\n配列 nums = ${nums.contentToString()}") // 二分探索で挿入位置を探す for (target in intArrayOf(2, 6, 20)) { val index = binarySearchInsertion(nums, target) println("要素 $target の挿入位置のインデックスは $index") } } ================================================ FILE: ja/codes/kotlin/chapter_searching/hashing_search.kt ================================================ /** * File: hashing_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching import utils.ListNode /* ハッシュ探索(配列) */ fun hashingSearchArray(map: Map, target: Int): Int { // ハッシュテーブルの key: 対象要素、_val: インデックス // ハッシュテーブルにその key がなければ -1 を返す return map.getOrDefault(target, -1) } /* ハッシュ探索(連結リスト) */ fun hashingSearchLinkedList(map: Map, target: Int): ListNode? { // ハッシュテーブルの key: 対象ノードの値、_val: ノードオブジェクト // ハッシュテーブルにその key がなければ null を返す return map.getOrDefault(target, null) } /* Driver Code */ fun main() { val target = 3 /* ハッシュ探索(配列) */ val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) // ハッシュテーブルを初期化 val map = HashMap() for (i in nums.indices) { map[nums[i]] = i // key: 要素、_val: インデックス } val index = hashingSearchArray(map, target) println("目標要素 3 のインデックス = $index") /* ハッシュ探索(連結リスト) */ var head = ListNode.arrToLinkedList(nums) // ハッシュテーブルを初期化 val map1 = HashMap() while (head != null) { map1[head._val] = head // key: ノード値、_val: ノード head = head.next } val node = hashingSearchLinkedList(map1, target) println("目標ノード値 3 に対応するノードオブジェクトは $node") } ================================================ FILE: ja/codes/kotlin/chapter_searching/linear_search.kt ================================================ /** * File: linear_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching import utils.ListNode /* 線形探索(配列) */ fun linearSearchArray(nums: IntArray, target: Int): Int { // 配列を走査 for (i in nums.indices) { // 目標要素が見つかったらそのインデックスを返す if (nums[i] == target) return i } // 目標要素が見つからなければ -1 を返す return -1 } /* 線形探索(連結リスト) */ fun linearSearchLinkedList(h: ListNode?, target: Int): ListNode? { // 連結リストを走査 var head = h while (head != null) { // 対象ノードが見つかったら、それを返す if (head._val == target) return head head = head.next } // 対象ノードが見つからない場合は null を返す return null } /* Driver Code */ fun main() { val target = 3 /* 配列で線形探索を行う */ val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) val index = linearSearchArray(nums, target) println("目標要素 3 のインデックス = $index") /* 連結リストで線形探索を行う */ val head = ListNode.arrToLinkedList(nums) val node = linearSearchLinkedList(head, target) println("目標ノード値 3 に対応するノードオブジェクトは $node") } ================================================ FILE: ja/codes/kotlin/chapter_searching/two_sum.kt ================================================ /** * File: two_sum.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* 方法 1:総当たり列挙 */ fun twoSumBruteForce(nums: IntArray, target: Int): IntArray { val size = nums.size // 2重ループのため、時間計算量は O(n^2) for (i in 0..() // 単一ループで、時間計算量は O(n) for (i in 0.. nums[j + 1]) { // nums[j] と nums[j + 1] を交換 val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp } } } } /* バブルソート(フラグ最適化) */ fun bubbleSortWithFlag(nums: IntArray) { // 外側のループ:未ソート区間は [0, i] for (i in nums.size - 1 downTo 1) { var flag = false // フラグを初期化する // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (j in 0.. nums[j + 1]) { // nums[j] と nums[j + 1] を交換 val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp flag = true // 交換する要素を記録 } } if (!flag) break // このバブル処理で要素交換が一度もなければそのまま終了 } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) bubbleSort(nums) println("バブルソート完了後 nums = ${nums.contentToString()}") val nums1 = intArrayOf(4, 1, 3, 1, 5, 2) bubbleSortWithFlag(nums1) println("バブルソート完了後 nums1 = ${nums1.contentToString()}") } ================================================ FILE: ja/codes/kotlin/chapter_sorting/bucket_sort.kt ================================================ /** * File: bucket_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* バケットソート */ fun bucketSort(nums: FloatArray) { // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする val k = nums.size / 2 val buckets = mutableListOf>() for (i in 0.. nums[ma]) ma = l if (r < n && nums[r] > nums[ma]) ma = r // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma == i) break // 2 つのノードを交換 val temp = nums[i] nums[i] = nums[ma] nums[ma] = temp // ループで上から下へヒープ化 i = ma } } /* ヒープソート */ fun heapSort(nums: IntArray) { // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する for (i in nums.size / 2 - 1 downTo 0) { siftDown(nums, nums.size, i) } // ヒープから最大要素を取り出し、n-1 回繰り返す for (i in nums.size - 1 downTo 1) { // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) val temp = nums[0] nums[0] = nums[i] nums[i] = temp // 根ノードを起点に、上から下へヒープ化 siftDown(nums, i, 0) } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) heapSort(nums) println("ヒープソート完了後 nums = ${nums.contentToString()}") } ================================================ FILE: ja/codes/kotlin/chapter_sorting/insertion_sort.kt ================================================ /** * File: insertion_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 挿入ソート */ fun insertionSort(nums: IntArray) { // 外側ループ: ソート済み要素は 1, 2, ..., n for (i in nums.indices) { val base = nums[i] var j = i - 1 // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j] // nums[j] を 1 つ右へ移動する j-- } nums[j + 1] = base // base を正しい位置に配置する } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) insertionSort(nums) println("挿入ソート完了後 nums = ${nums.contentToString()}") } ================================================ FILE: ja/codes/kotlin/chapter_sorting/merge_sort.kt ================================================ /** * File: merge_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 左部分配列と右部分配列をマージ */ fun merge(nums: IntArray, left: Int, mid: Int, right: Int) { // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] // マージ結果を格納する一時配列 tmp を作成 val tmp = IntArray(right - left + 1) // 左右の部分配列の開始インデックスを初期化する var i = left var j = mid + 1 var k = 0 // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++] else tmp[k++] = nums[j++] } // 左右の部分配列の残り要素を一時配列にコピーする while (i <= mid) { tmp[k++] = nums[i++] } while (j <= right) { tmp[k++] = nums[j++] } // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする for (l in tmp.indices) { nums[left + l] = tmp[l] } } /* マージソート */ fun mergeSort(nums: IntArray, left: Int, right: Int) { // 終了条件 if (left >= right) return // 部分配列の長さが 1 になったら再帰を終了 // 分割フェーズ val mid = left + (right - left) / 2 // 中点を計算 mergeSort(nums, left, mid) // 左部分配列を再帰処理 mergeSort(nums, mid + 1, right) // 右部分配列を再帰処理 // マージフェーズ merge(nums, left, mid, right) } /* Driver Code */ fun main() { /* マージソート */ val nums = intArrayOf(7, 3, 2, 6, 0, 1, 5, 4) mergeSort(nums, 0, nums.size - 1) println("マージソート完了後 nums = ${nums.contentToString()}") } ================================================ FILE: ja/codes/kotlin/chapter_sorting/quick_sort.kt ================================================ /** * File: quick_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 要素の交換 */ fun swap(nums: IntArray, i: Int, j: Int) { val temp = nums[i] nums[i] = nums[j] nums[j] = temp } /* 番兵分割 */ fun partition(nums: IntArray, left: Int, right: Int): Int { // nums[left] を基準値とする var i = left var j = right while (i < j) { while (i < j && nums[j] >= nums[left]) j-- // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) i++ // 左から右へ基準値より大きい最初の要素を探す swap(nums, i, j) // この 2 つの要素を交換 } swap(nums, i, left) // 基準値を 2 つの部分配列の境界へ交換する return i // 基準値のインデックスを返す } /* クイックソート */ fun quickSort(nums: IntArray, left: Int, right: Int) { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return // 番兵分割 val pivot = partition(nums, left, right) // 左右の部分配列を再帰処理 quickSort(nums, left, pivot - 1) quickSort(nums, pivot + 1, right) } /* 3つの候補要素の中央値を選ぶ */ fun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int { val l = nums[left] val m = nums[mid] val r = nums[right] if ((m in l..r) || (m in r..l)) return mid // m は l と r の間 if ((l in m..r) || (l in r..m)) return left // l は m と r の間 return right } /* 番兵による分割処理(3 点中央値) */ fun partitionMedian(nums: IntArray, left: Int, right: Int): Int { // 3つの候補要素の中央値を選ぶ val med = medianThree(nums, left, (left + right) / 2, right) // 中央値を配列の最左端に交換する swap(nums, left, med) // nums[left] を基準値とする var i = left var j = right while (i < j) { while (i < j && nums[j] >= nums[left]) j-- // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) i++ // 左から右へ基準値より大きい最初の要素を探す swap(nums, i, j) // この 2 つの要素を交換 } swap(nums, i, left) // 基準値を 2 つの部分配列の境界へ交換する return i // 基準値のインデックスを返す } /* クイックソート */ fun quickSortMedian(nums: IntArray, left: Int, right: Int) { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return // 番兵分割 val pivot = partitionMedian(nums, left, right) // 左右の部分配列を再帰処理 quickSort(nums, left, pivot - 1) quickSort(nums, pivot + 1, right) } /* クイックソート(再帰深度最適化) */ fun quickSortTailCall(nums: IntArray, left: Int, right: Int) { // 部分配列の長さが 1 なら終了 var l = left var r = right while (l < r) { // 番兵による分割処理 val pivot = partition(nums, l, r) // 2 つの部分配列のうち短いほうにクイックソートを適用する if (pivot - l < r - pivot) { quickSort(nums, l, pivot - 1) // 左部分配列を再帰的にソート l = pivot + 1 // 未ソート区間の残りは [pivot + 1, right] } else { quickSort(nums, pivot + 1, r) // 右部分配列を再帰的にソート r = pivot - 1 // 未ソート区間の残りは [left, pivot - 1] } } } /* Driver Code */ fun main() { /* クイックソート */ val nums = intArrayOf(2, 4, 1, 0, 3, 5) quickSort(nums, 0, nums.size - 1) println("クイックソート完了後 nums = ${nums.contentToString()}") /* クイックソート(中央値の基準値で最適化) */ val nums1 = intArrayOf(2, 4, 1, 0, 3, 5) quickSortMedian(nums1, 0, nums1.size - 1) println("クイックソート(中央値ピボット最適化)完了後 nums1 = ${nums1.contentToString()}") /* クイックソート(再帰深度最適化) */ val nums2 = intArrayOf(2, 4, 1, 0, 3, 5) quickSortTailCall(nums2, 0, nums2.size - 1) println("クイックソート(再帰深度最適化)完了後 nums2 = ${nums2.contentToString()}") } ================================================ FILE: ja/codes/kotlin/chapter_sorting/radix_sort.kt ================================================ /** * File: radix_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ fun digit(num: Int, exp: Int): Int { // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す return (num / exp) % 10 } /* 計数ソート(nums の k 桁目でソート) */ fun countingSortDigit(nums: IntArray, exp: Int) { // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 val counter = IntArray(10) val n = nums.size // 0~9 の各数字の出現回数を集計する for (i in 0.. m) m = num var exp = 1 // 下位桁から上位桁の順に走査する while (exp <= m) { // 配列要素の k 桁目に対して計数ソートを行う // k = 1 -> exp = 1 // k = 2 -> exp = 10 // つまり exp = 10^(k-1) countingSortDigit(nums, exp) exp *= 10 } } /* Driver Code */ fun main() { // 基数ソート val nums = intArrayOf( 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ) radixSort(nums) println("基数ソート完了後 nums = ${nums.contentToString()}") } ================================================ FILE: ja/codes/kotlin/chapter_sorting/selection_sort.kt ================================================ /** * File: selection_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 選択ソート */ fun selectionSort(nums: IntArray) { val n = nums.size // 外側ループ:未整列区間は [i, n-1] for (i in 0..() /* スタックの長さを取得 */ fun size(): Int { return stack.size } /* スタックが空かどうかを判定 */ fun isEmpty(): Boolean { return size() == 0 } /* プッシュ */ fun push(num: Int) { stack.add(num) } /* ポップ */ fun pop(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return stack.removeAt(size() - 1) } /* スタックトップの要素にアクセス */ fun peek(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return stack[size() - 1] } /* List を Array に変換して返す */ fun toArray(): Array { return stack.toTypedArray() } } /* Driver Code */ fun main() { /* スタックを初期化 */ val stack = ArrayStack() /* 要素をプッシュ */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("スタック stack = ${stack.toArray().contentToString()}") /* スタックトップの要素にアクセス */ val peek = stack.peek() println("スタックトップ要素 peek = $peek") /* 要素をポップ */ val pop = stack.pop() println("ポップした要素 pop = $pop、ポップ後の stack = ${stack.toArray().contentToString()}") /* スタックの長さを取得 */ val size = stack.size() println("スタックの長さ size = $size") /* 空かどうかを判定 */ val isEmpty = stack.isEmpty() println("スタックが空かどうか = $isEmpty") } ================================================ FILE: ja/codes/kotlin/chapter_stack_and_queue/deque.kt ================================================ /** * File: deque.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* 両端キューを初期化 */ val deque = LinkedList() deque.offerLast(3) deque.offerLast(2) deque.offerLast(5) println("両端キュー deque = $deque") /* 要素にアクセス */ val peekFirst = deque.peekFirst() println("先頭要素 peekFirst = $peekFirst") val peekLast = deque.peekLast() println("末尾要素 peekLast = $peekLast") /* 要素をエンキュー */ deque.offerLast(4) println("要素 4 を末尾に追加後 deque = $deque") deque.offerFirst(1) println("要素 1 を先頭に追加後 deque = $deque") /* 要素をデキュー */ val popLast = deque.pollLast() println("末尾から取り出した要素 = $popLast、取り出し後 deque = $deque") val popFirst = deque.pollFirst() println("先頭から取り出した要素 = $popFirst、取り出し後 deque = $deque") /* 両端キューの長さを取得 */ val size = deque.size println("双方向キューの長さ size = $size") /* 両端キューが空かどうかを判定 */ val isEmpty = deque.isEmpty() println("双方向キューが空かどうか = $isEmpty") } ================================================ FILE: ja/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt ================================================ /** * File: linkedlist_deque.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* 双方向連結リストノード */ class ListNode(var _val: Int) { // ノード値 var next: ListNode? = null // 後続ノードへの参照 var prev: ListNode? = null // 前駆ノードへの参照 } /* 双方向連結リストベースの両端キュー */ class LinkedListDeque { private var front: ListNode? = null // 先頭ノード front private var rear: ListNode? = null // 末尾ノード rear private var queSize: Int = 0 // 両端キューの長さ /* 両端キューの長さを取得 */ fun size(): Int { return queSize } /* 両端キューが空かどうかを判定 */ fun isEmpty(): Boolean { return size() == 0 } /* エンキュー操作 */ fun push(num: Int, isFront: Boolean) { val node = ListNode(num) // 連結リストが空なら、front と rear の両方を node に向ける if (isEmpty()) { rear = node front = rear // 先頭へのエンキュー操作 } else if (isFront) { // node を連結リストの先頭に追加 front?.prev = node node.next = front front = node // 先頭ノードを更新する // 末尾へのエンキュー操作 } else { // node を連結リストの末尾に追加 rear?.next = node node.prev = rear rear = node // 末尾ノードを更新する } queSize++ // キューの長さを更新 } /* キュー先頭にエンキュー */ fun pushFirst(num: Int) { push(num, true) } /* キュー末尾にエンキュー */ fun pushLast(num: Int) { push(num, false) } /* デキュー操作 */ fun pop(isFront: Boolean): Int { if (isEmpty()) throw IndexOutOfBoundsException() val _val: Int // キュー先頭からの取り出し if (isFront) { _val = front!!._val // 先頭ノードの値を一時保存 // 先頭ノードを削除 val fNext = front!!.next if (fNext != null) { fNext.prev = null front!!.next = null } front = fNext // 先頭ノードを更新する // キュー末尾からの取り出し } else { _val = rear!!._val // 末尾ノードの値を一時保存 // 末尾ノードを削除 val rPrev = rear!!.prev if (rPrev != null) { rPrev.next = null rear!!.prev = null } rear = rPrev // 末尾ノードを更新する } queSize-- // キューの長さを更新 return _val } /* キュー先頭からデキュー */ fun popFirst(): Int { return pop(true) } /* キュー末尾からデキュー */ fun popLast(): Int { return pop(false) } /* キュー先頭の要素にアクセス */ fun peekFirst(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return front!!._val } /* キュー末尾の要素にアクセス */ fun peekLast(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return rear!!._val } /* 出力用の配列を返す */ fun toArray(): IntArray { var node = front val res = IntArray(size()) for (i in res.indices) { res[i] = node!!._val node = node.next } return res } } /* Driver Code */ fun main() { /* 両端キューを初期化 */ val deque = LinkedListDeque() deque.pushLast(3) deque.pushLast(2) deque.pushLast(5) println("双方向キュー deque = ${deque.toArray().contentToString()}") /* 要素にアクセス */ val peekFirst = deque.peekFirst() println("先頭要素 peekFirst = $peekFirst") val peekLast = deque.peekLast() println("末尾要素 peekLast = $peekLast") /* 要素をエンキュー */ deque.pushLast(4) println("要素 4 を末尾に追加した後 deque = ${deque.toArray().contentToString()}") deque.pushFirst(1) println("要素 1 を先頭に追加した後 deque = ${deque.toArray().contentToString()}") /* 要素をデキュー */ val popLast = deque.popLast() println("末尾から取り出した要素 = ${popLast}、末尾から取り出した後 deque = ${deque.toArray().contentToString()}") val popFirst = deque.popFirst() println("先頭から取り出した要素 = ${popFirst}、先頭から取り出した後 deque = ${deque.toArray().contentToString()}") /* 両端キューの長さを取得 */ val size = deque.size() println("双方向キューの長さ size = $size") /* 両端キューが空かどうかを判定 */ val isEmpty = deque.isEmpty() println("双方向キューが空かどうか = $isEmpty") } ================================================ FILE: ja/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt ================================================ /** * File: linkedlist_queue.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* 連結リストベースのキュー */ class LinkedListQueue( // 先頭ノード front、末尾ノード rear private var front: ListNode? = null, private var rear: ListNode? = null, private var queSize: Int = 0 ) { /* キューの長さを取得 */ fun size(): Int { return queSize } /* キューが空かどうかを判定 */ fun isEmpty(): Boolean { return size() == 0 } /* エンキュー */ fun push(num: Int) { // 末尾ノードの後ろに num を追加 val node = ListNode(num) // キューが空なら、先頭・末尾ノードをともにそのノードに設定 if (front == null) { front = node rear = node // キューが空でなければ、そのノードを末尾ノードの後ろに追加 } else { rear?.next = node rear = node } queSize++ } /* デキュー */ fun pop(): Int { val num = peek() // 先頭ノードを削除 front = front?.next queSize-- return num } /* キュー先頭の要素にアクセス */ fun peek(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return front!!._val } /* 連結リストを Array に変換して返す */ fun toArray(): IntArray { var node = front val res = IntArray(size()) for (i in res.indices) { res[i] = node!!._val node = node.next } return res } } /* Driver Code */ fun main() { /* キューを初期化 */ val queue = LinkedListQueue() /* 要素をエンキュー */ queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) println("キュー queue = ${queue.toArray().contentToString()}") /* キュー先頭の要素にアクセス */ val peek = queue.peek() println("先頭要素 peek = $peek") /* 要素をデキュー */ val pop = queue.pop() println("デキューした要素 pop = $pop、デキュー後 queue = ${queue.toArray().contentToString()}") /* キューの長さを取得 */ val size = queue.size() println("キューの長さ size = $size") /* キューが空かどうかを判定 */ val isEmpty = queue.isEmpty() println("キューが空かどうか = $isEmpty") } ================================================ FILE: ja/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt ================================================ /** * File: linkedlist_stack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* 連結リストベースのスタック */ class LinkedListStack( private var stackPeek: ListNode? = null, // 先頭ノードをスタックトップとする private var stkSize: Int = 0 // スタックの長さ ) { /* スタックの長さを取得 */ fun size(): Int { return stkSize } /* スタックが空かどうかを判定 */ fun isEmpty(): Boolean { return size() == 0 } /* プッシュ */ fun push(num: Int) { val node = ListNode(num) node.next = stackPeek stackPeek = node stkSize++ } /* ポップ */ fun pop(): Int? { val num = peek() stackPeek = stackPeek?.next stkSize-- return num } /* スタックトップの要素にアクセス */ fun peek(): Int? { if (isEmpty()) throw IndexOutOfBoundsException() return stackPeek?._val } /* List を Array に変換して返す */ fun toArray(): IntArray { var node = stackPeek val res = IntArray(size()) for (i in res.size - 1 downTo 0) { res[i] = node?._val!! node = node.next } return res } } /* Driver Code */ fun main() { /* スタックを初期化 */ val stack = LinkedListStack() /* 要素をプッシュ */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("スタック stack = ${stack.toArray().contentToString()}") /* スタックトップの要素にアクセス */ val peek = stack.peek()!! println("スタックトップ要素 peek = $peek") /* 要素をポップ */ val pop = stack.pop()!! println("ポップした要素 pop = $pop、ポップ後の stack = ${stack.toArray().contentToString()}") /* スタックの長さを取得 */ val size = stack.size() println("スタックの長さ size = $size") /* 空かどうかを判定 */ val isEmpty = stack.isEmpty() println("スタックが空かどうか = $isEmpty") } ================================================ FILE: ja/codes/kotlin/chapter_stack_and_queue/queue.kt ================================================ /** * File: queue.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* キューを初期化 */ val queue = LinkedList() /* 要素をエンキュー */ queue.offer(1) queue.offer(3) queue.offer(2) queue.offer(5) queue.offer(4) println("キュー queue = $queue") /* キュー先頭の要素にアクセス */ val peek = queue.peek() println("先頭要素 peek = $peek") /* 要素をデキュー */ val pop = queue.poll() println("デキューした要素 pop = $pop、デキュー後 queue = $queue") /* キューの長さを取得 */ val size = queue.size println("キューの長さ size = $size") /* キューが空かどうかを判定 */ val isEmpty = queue.isEmpty() println("キューが空かどうか = $isEmpty") } ================================================ FILE: ja/codes/kotlin/chapter_stack_and_queue/stack.kt ================================================ /** * File: stack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* スタックを初期化 */ val stack = Stack() /* 要素をプッシュ */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("スタック stack = $stack") /* スタックトップの要素にアクセス */ val peek = stack.peek() println("スタックトップ要素 peek = $peek") /* 要素をポップ */ val pop = stack.pop() println("ポップした要素 pop = $pop、ポップ後 stack = $stack") /* スタックの長さを取得 */ val size = stack.size println("スタックの長さ size = $size") /* 空かどうかを判定 */ val isEmpty = stack.isEmpty() println("スタックが空かどうか = $isEmpty") } ================================================ FILE: ja/codes/kotlin/chapter_tree/array_binary_tree.kt ================================================ /** * File: array_binary_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* 配列表現による二分木クラス */ class ArrayBinaryTree(private val tree: MutableList) { /* リスト容量 */ fun size(): Int { return tree.size } /* インデックス i のノードの値を取得 */ fun _val(i: Int): Int? { // インデックスが範囲外なら、空きを表す null を返す if (i < 0 || i >= size()) return null return tree[i] } /* インデックス i のノードの左子ノードのインデックスを取得 */ fun left(i: Int): Int { return 2 * i + 1 } /* インデックス i のノードの右子ノードのインデックスを取得 */ fun right(i: Int): Int { return 2 * i + 2 } /* インデックス i のノードの親ノードのインデックスを取得 */ fun parent(i: Int): Int { return (i - 1) / 2 } /* レベル順走査 */ fun levelOrder(): MutableList { val res = mutableListOf() // 配列を直接走査する for (i in 0..) { // 空きスロットなら返す if (_val(i) == null) return // 先行順走査 if ("pre" == order) res.add(_val(i)) dfs(left(i), order, res) // 中順走査 if ("in" == order) res.add(_val(i)) dfs(right(i), order, res) // 後順走査 if ("post" == order) res.add(_val(i)) } /* 先行順走査 */ fun preOrder(): MutableList { val res = mutableListOf() dfs(0, "pre", res) return res } /* 中順走査 */ fun inOrder(): MutableList { val res = mutableListOf() dfs(0, "in", res) return res } /* 後順走査 */ fun postOrder(): MutableList { val res = mutableListOf() dfs(0, "post", res) return res } } /* Driver Code */ fun main() { // 二分木を初期化する // ここでは、リストから二分木を直接生成する関数を利用する val arr = mutableListOf(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15) val root = TreeNode.listToTree(arr) println("\n二分木を初期化\n") println("二分木の配列表現:") println(arr) println("二分木の連結リスト表現:") printTree(root) // 配列表現による二分木クラス val abt = ArrayBinaryTree(arr) // ノードにアクセス val i = 1 val l = abt.left(i) val r = abt.right(i) val p = abt.parent(i) println("現在のノードのインデックスは $i 、値は ${abt._val(i)}") println("左の子ノードのインデックスは $l 、値は ${abt._val(l)}") println("右の子ノードのインデックスは $r 、値は ${abt._val(r)}") println("親ノードのインデックスは $p 、値は ${abt._val(p)}") // 木を走査 var res = abt.levelOrder() println("\nレベル順走査:$res") res = abt.preOrder() println("先行順走査:$res") res = abt.inOrder() println("中間順走査:$res") res = abt.postOrder() println("後行順走査:$res") } ================================================ FILE: ja/codes/kotlin/chapter_tree/avl_tree.kt ================================================ /** * File: avl_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree import kotlin.math.max /* AVL 木 */ class AVLTree { var root: TreeNode? = null // 根ノード /* ノードの高さを取得 */ fun height(node: TreeNode?): Int { // 空ノードの高さは -1、葉ノードの高さは 0 return node?.height ?: -1 } /* ノードの高さを更新する */ private fun updateHeight(node: TreeNode?) { // ノードの高さは最も高い部分木の高さ + 1 に等しい node?.height = max(height(node?.left), height(node?.right)) + 1 } /* 平衡係数を取得 */ fun balanceFactor(node: TreeNode?): Int { // 空ノードの平衡係数は 0 if (node == null) return 0 // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ return height(node.left) - height(node.right) } /* 右回転 */ private fun rightRotate(node: TreeNode?): TreeNode { val child = node!!.left val grandChild = child!!.right // child を支点として node を右回転させる child.right = node node.left = grandChild // ノードの高さを更新する updateHeight(node) updateHeight(child) // 回転後の部分木の根ノードを返す return child } /* 左回転 */ private fun leftRotate(node: TreeNode?): TreeNode { val child = node!!.right val grandChild = child!!.left // child を支点として node を左回転させる child.left = node node.right = grandChild // ノードの高さを更新する updateHeight(node) updateHeight(child) // 回転後の部分木の根ノードを返す return child } /* 回転操作を行い、この部分木の平衡を回復する */ private fun rotate(node: TreeNode): TreeNode { // ノード node の平衡係数を取得 val balanceFactor = balanceFactor(node) // 左に偏った木 if (balanceFactor > 1) { if (balanceFactor(node.left) >= 0) { // 右回転 return rightRotate(node) } else { // 左回転してから右回転 node.left = leftRotate(node.left) return rightRotate(node) } } // 右に偏った木 if (balanceFactor < -1) { if (balanceFactor(node.right) <= 0) { // 左回転 return leftRotate(node) } else { // 右回転してから左回転 node.right = rightRotate(node.right) return leftRotate(node) } } // 平衡木なので回転不要、そのまま返す return node } /* ノードを挿入 */ fun insert(_val: Int) { root = insertHelper(root, _val) } /* ノードを再帰的に挿入する(補助メソッド) */ private fun insertHelper(n: TreeNode?, _val: Int): TreeNode { if (n == null) return TreeNode(_val) var node = n /* 1. 挿入位置を探索してノードを挿入 */ if (_val < node._val) node.left = insertHelper(node.left, _val) else if (_val > node._val) node.right = insertHelper(node.right, _val) else return node // 重複ノードは挿入せず、そのまま返す updateHeight(node) // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node) // 部分木の根ノードを返す return node } /* ノードを削除 */ fun remove(_val: Int) { root = removeHelper(root, _val) } /* ノードを再帰的に削除する(補助メソッド) */ private fun removeHelper(n: TreeNode?, _val: Int): TreeNode? { var node = n ?: return null /* 1. ノードを探索して削除 */ if (_val < node._val) node.left = removeHelper(node.left, _val) else if (_val > node._val) node.right = removeHelper(node.right, _val) else { if (node.left == null || node.right == null) { val child = if (node.left != null) node.left else node.right // 子ノード数 = 0 の場合、node をそのまま削除して返す if (child == null) return null // 子ノード数 = 1 の場合、node をそのまま削除する else node = child } else { // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える var temp = node.right while (temp!!.left != null) { temp = temp.left } node.right = removeHelper(node.right, temp._val) node._val = temp._val } } updateHeight(node) // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node) // 部分木の根ノードを返す return node } /* ノードを探索 */ fun search(_val: Int): TreeNode? { var cur = root // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 目標ノードは cur の右部分木にある cur = if (cur._val < _val) cur.right!! // 目標ノードは cur の左部分木にある else if (cur._val > _val) cur.left // 目標ノードが見つかったらループを抜ける else break } // 目標ノードを返す return cur } } fun testInsert(tree: AVLTree, _val: Int) { tree.insert(_val) println("\nノード $_val を挿入後、AVL 木は") printTree(tree.root) } fun testRemove(tree: AVLTree, _val: Int) { tree.remove(_val) println("\nノード $_val を削除後、AVL 木は") printTree(tree.root) } /* Driver Code */ fun main() { /* 空の AVL 木を初期化する */ val avlTree = AVLTree() /* ノードを挿入 */ // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい testInsert(avlTree, 1) testInsert(avlTree, 2) testInsert(avlTree, 3) testInsert(avlTree, 4) testInsert(avlTree, 5) testInsert(avlTree, 8) testInsert(avlTree, 7) testInsert(avlTree, 9) testInsert(avlTree, 10) testInsert(avlTree, 6) /* 重複ノードを挿入する */ testInsert(avlTree, 7) /* ノードを削除 */ // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい testRemove(avlTree, 8) // 次数 0 のノードを削除する testRemove(avlTree, 5) // 次数 1 のノードを削除する testRemove(avlTree, 4) // 次数 2 のノードを削除する /* ノードを検索 */ val node = avlTree.search(7) println("\n 見つかったノードオブジェクトは $node、ノードの値 = ${node?._val}") } ================================================ FILE: ja/codes/kotlin/chapter_tree/binary_search_tree.kt ================================================ /** * File: binary_search_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* 二分探索木 */ class BinarySearchTree { // 空の木を初期化する private var root: TreeNode? = null /* 二分木の根ノードを取得 */ fun getRoot(): TreeNode? { return root } /* ノードを探索 */ fun search(num: Int): TreeNode? { var cur = root // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 目標ノードは cur の右部分木にある cur = if (cur._val < num) cur.right // 目標ノードは cur の左部分木にある else if (cur._val > num) cur.left // 目標ノードが見つかったらループを抜ける else break } // 目標ノードを返す return cur } /* ノードを挿入 */ fun insert(num: Int) { // 木が空なら、根ノードを初期化する if (root == null) { root = TreeNode(num) return } var cur = root var pre: TreeNode? = null // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 重複ノードが見つかったら、直ちに返す if (cur._val == num) return pre = cur // 挿入位置は cur の右部分木にある cur = if (cur._val < num) cur.right // 挿入位置は cur の左部分木にある else cur.left } // ノードを挿入 val node = TreeNode(num) if (pre?._val!! < num) pre.right = node else pre.left = node } /* ノードを削除 */ fun remove(num: Int) { // 木が空なら、そのまま早期リターンする if (root == null) return var cur = root var pre: TreeNode? = null // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 削除対象のノードが見つかったら、ループを抜ける if (cur._val == num) break pre = cur // 削除対象ノードは cur の右部分木にある cur = if (cur._val < num) cur.right // 削除対象ノードは cur の左部分木にある else cur.left } // 削除対象ノードがなければそのまま返す if (cur == null) return // 子ノード数 = 0 or 1 if (cur.left == null || cur.right == null) { // 子ノード数が 0 / 1 のとき、child = null / その子ノード val child = if (cur.left != null) cur.left else cur.right // ノード cur を削除する if (cur != root) { if (pre!!.left == cur) pre.left = child else pre.right = child } else { // 削除ノードが根ノードなら、根ノードを再設定 root = child } // 子ノード数 = 2 } else { // 中順走査における cur の次ノードを取得 var tmp = cur.right while (tmp!!.left != null) { tmp = tmp.left } // ノード tmp を再帰的に削除 remove(tmp._val) // tmp で cur を上書きする cur._val = tmp._val } } } /* Driver Code */ fun main() { /* 二分探索木を初期化 */ val bst = BinarySearchTree() // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる val nums = intArrayOf(8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15) for (num in nums) { bst.insert(num) } println("\n初期化した二分木は\n") printTree(bst.getRoot()) /* ノードを探索 */ val node = bst.search(7) println("見つかったノードオブジェクトは $node、ノードの値 = ${node?._val}") /* ノードを挿入 */ bst.insert(16) println("\nノード 16 を挿入後、二分木は\n") printTree(bst.getRoot()) /* ノードを削除 */ bst.remove(1) println("\nノード 1 を削除後、二分木は\n") printTree(bst.getRoot()) bst.remove(2) println("\nノード 2 を削除後、二分木は\n") printTree(bst.getRoot()) bst.remove(4) println("\nノード 4 を削除後、二分木は\n") printTree(bst.getRoot()) } ================================================ FILE: ja/codes/kotlin/chapter_tree/binary_tree.kt ================================================ /** * File: binary_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* Driver Code */ fun main() { /* 二分木を初期化 */ // ノードを初期化 val n1 = TreeNode(1) val n2 = TreeNode(2) val n3 = TreeNode(3) val n4 = TreeNode(4) val n5 = TreeNode(5) // ノード間の参照(ポインタ)を構築する n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 println("\n二分木を初期化\n") printTree(n1) /* ノードの挿入と削除 */ val P = TreeNode(0) // n1 -> n2 の間にノード P を挿入 n1.left = P P.left = n2 println("\nノード P を挿入後\n") printTree(n1) // ノード P を削除 n1.left = n2 println("\nノード P を削除後\n") printTree(n1) } ================================================ FILE: ja/codes/kotlin/chapter_tree/binary_tree_bfs.kt ================================================ /** * File: binary_tree_bfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree import java.util.* /* レベル順走査 */ fun levelOrder(root: TreeNode?): MutableList { // キューを初期化し、ルートノードを追加する val queue = LinkedList() queue.add(root) // 走査順序を保存するためのリストを初期化する val list = mutableListOf() while (queue.isNotEmpty()) { val node = queue.poll() // デキュー list.add(node?._val!!) // ノードの値を保存する if (node.left != null) queue.offer(node.left) // 左子ノードをキューに追加 if (node.right != null) queue.offer(node.right) // 右子ノードをキューに追加 } return list } /* Driver Code */ fun main() { /* 二分木を初期化 */ // ここではリストから直接二分木を生成する関数を利用する val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) println("\n二分木を初期化\n") printTree(root) /* レベル順走査 */ val list = levelOrder(root) println("\nレベル順走査のノード出力順 = $list") } ================================================ FILE: ja/codes/kotlin/chapter_tree/binary_tree_dfs.kt ================================================ /** * File: binary_tree_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree // 走査順序を格納するリストを初期化 var list = mutableListOf() /* 先行順走査 */ fun preOrder(root: TreeNode?) { if (root == null) return // 訪問順序:根ノード -> 左部分木 -> 右部分木 list.add(root._val) preOrder(root.left) preOrder(root.right) } /* 中順走査 */ fun inOrder(root: TreeNode?) { if (root == null) return // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 inOrder(root.left) list.add(root._val) inOrder(root.right) } /* 後順走査 */ fun postOrder(root: TreeNode?) { if (root == null) return // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード postOrder(root.left) postOrder(root.right) list.add(root._val) } /* Driver Code */ fun main() { /* 二分木を初期化 */ // ここではリストから直接二分木を生成する関数を利用する val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) println("\n二分木を初期化\n") printTree(root) /* 先行順走査 */ list.clear() preOrder(root) println("\n先行順走査のノード出力順 = $list") /* 中順走査 */ list.clear() inOrder(root) println("\n中間順走査のノード出力順 = $list") /* 後順走査 */ list.clear() postOrder(root) println("\n後行順走査のノード出力順 = $list") } ================================================ FILE: ja/codes/kotlin/utils/ListNode.kt ================================================ /** * File: ListNode.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* 連結リストノード */ class ListNode(var _val: Int) { var next: ListNode? = null companion object { /* リストを連結リストにデシリアライズする */ fun arrToLinkedList(arr: IntArray): ListNode? { val dum = ListNode(0) var head = dum for (_val in arr) { head.next = ListNode(_val) head = head.next!! } return dum.next } } } ================================================ FILE: ja/codes/kotlin/utils/PrintUtil.kt ================================================ /** * File: PrintUtil.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils import java.util.* class Trunk(var prev: Trunk?, var str: String) /* 行列を出力する(Array) */ fun printMatrix(matrix: Array>) { println("[") for (row in matrix) { println(" $row,") } println("]") } /* 行列を出力する(List) */ fun printMatrix(matrix: MutableList>) { println("[") for (row in matrix) { println(" $row,") } println("]") } /* 連結リストを出力 */ fun printLinkedList(h: ListNode?) { var head = h val list = mutableListOf() while (head != null) { list.add(head._val.toString()) head = head.next } println(list.joinToString(separator = " -> ")) } /* 二分木を出力 */ fun printTree(root: TreeNode?) { printTree(root, null, false) } /** * 二分木を出力 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ fun printTree(root: TreeNode?, prev: Trunk?, isRight: Boolean) { if (root == null) { return } var prevStr = " " val trunk = Trunk(prev, prevStr) printTree(root.right, trunk, true) if (prev == null) { trunk.str = "———" } else if (isRight) { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev.str = prevStr } showTrunks(trunk) println(" ${root._val}") if (prev != null) { prev.str = prevStr } trunk.str = " |" printTree(root.left, trunk, false) } fun showTrunks(p: Trunk?) { if (p == null) { return } showTrunks(p.prev) print(p.str) } /* ハッシュテーブルを出力 */ fun printHashMap(map: Map) { for ((key, value) in map) { println("${key.toString()} -> $value") } } /* ヒープを出力 */ fun printHeap(queue: Queue?) { val list = mutableListOf() queue?.let { list.addAll(it) } print("ヒープの配列表現:") println(list) println("ヒープの木構造表現:") val root = TreeNode.listToTree(list) printTree(root) } ================================================ FILE: ja/codes/kotlin/utils/TreeNode.kt ================================================ /** * File: TreeNode.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* 二分木ノードクラス */ /* コンストラクタ */ class TreeNode( var _val: Int // ノード値 ) { var height: Int = 0 // ノードの高さ var left: TreeNode? = null // 左子ノードへの参照 var right: TreeNode? = null // 右子ノードへの参照 // シリアライズの符号化規則は以下を参照: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二分木の配列表現: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // 二分木の連結リスト表現: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* リストを二分木にデシリアライズする: 再帰 */ companion object { private fun listToTreeDFS(arr: MutableList, i: Int): TreeNode? { if (i < 0 || i >= arr.size || arr[i] == null) { return null } val root = TreeNode(arr[i]!!) root.left = listToTreeDFS(arr, 2 * i + 1) root.right = listToTreeDFS(arr, 2 * i + 2) return root } /* リストを二分木にデシリアライズする */ fun listToTree(arr: MutableList): TreeNode? { return listToTreeDFS(arr, 0) } /* 二分木をリストにシリアライズする: 再帰 */ private fun treeToListDFS(root: TreeNode?, i: Int, res: MutableList) { if (root == null) return while (i >= res.size) { res.add(null) } res[i] = root._val treeToListDFS(root.left, 2 * i + 1, res) treeToListDFS(root.right, 2 * i + 2, res) } /* 二分木をリストにシリアライズする */ fun treeToList(root: TreeNode?): MutableList { val res = mutableListOf() treeToListDFS(root, 0, res) return res } } } ================================================ FILE: ja/codes/kotlin/utils/Vertex.kt ================================================ /** * File: Vertex.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* 頂点クラス */ class Vertex(val _val: Int) { companion object { /* 値リスト vals を入力し、頂点リスト vets を返す */ fun valsToVets(vals: IntArray): Array { val vets = arrayOfNulls(vals.size) for (i in vals.indices) { vets[i] = Vertex(vals[i]) } return vets } /* 頂点リスト vets を入力し、値リスト vals を返す */ fun vetsToVals(vets: MutableList): MutableList { val vals = mutableListOf() for (vet in vets) { vals.add(vet!!._val) } return vals } } } ================================================ FILE: ja/codes/python/.gitignore ================================================ __pycache__ ================================================ FILE: ja/codes/python/chapter_array_and_linkedlist/array.py ================================================ """ 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] からランダムに数字を 1 つ選ぶ random_index = random.randint(0, len(nums) - 1) # ランダムな要素を取得して返す random_num = nums[random_index] return random_num # Python の list は動的配列であり、直接拡張できます # 学習しやすいよう、本関数では list を長さ不変の配列として扱います def extend(nums: list[int], enlarge: int) -> list[int]: """配列長を拡張する""" # 拡張後の長さを持つ配列を初期化する res = [0] * (len(nums) + enlarge) # 元の配列の全要素を新しい配列にコピー for i in range(len(nums)): res[i] = nums[i] # 拡張後の新しい配列を返す 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] # index の要素に num を代入する 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) ================================================ FILE: ja/codes/python/chapter_array_and_linkedlist/linked_list.py ================================================ """ 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)) ================================================ FILE: ja/codes/python/chapter_array_and_linkedlist/list.py ================================================ """ 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) ================================================ FILE: ja/codes/python/chapter_array_and_linkedlist/my_list.py ================================================ """ 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("インデックスが範囲外です") return self._arr[index] def set(self, num: int, index: int): """要素を更新""" if index < 0 or index >= self._size: raise IndexError("インデックスが範囲外です") 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("インデックスが範囲外です") # 要素数が容量を超えると、拡張機構が発動する 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("インデックスが範囲外です") 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()}") ================================================ FILE: ja/codes/python/chapter_backtracking/n_queens.py ================================================ """ 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 """Driver Code""" 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) ================================================ FILE: ja/codes/python/chapter_backtracking/permutations_i.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [1, 2, 3] res = permutations_i(nums) print(f"入力配列 nums = {nums}") print(f"すべての順列 res = {res}") ================================================ FILE: ja/codes/python/chapter_backtracking/permutations_ii.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [1, 2, 2] res = permutations_ii(nums) print(f"入力配列 nums = {nums}") print(f"すべての順列 res = {res}") ================================================ FILE: ja/codes/python/chapter_backtracking/preorder_traversal_i_compact.py ================================================ """ 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): """前順走査:例題 1""" if root is None: return if root.val == 7: # 解を記録 res.append(root) pre_order(root.left) pre_order(root.right) """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n二分木を初期化") print_tree(root) # 先行順走査 res = list[TreeNode]() pre_order(root) print("\n値が 7 のノードをすべて出力") print([node.val for node in res]) ================================================ FILE: ja/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py ================================================ """ 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): """前順走査:例題 2""" if root is None: return # 試す path.append(root) if root.val == 7: # 解を記録 res.append(list(path)) pre_order(root.left) pre_order(root.right) # バックトラック path.pop() """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n二分木を初期化") print_tree(root) # 先行順走査 path = list[TreeNode]() res = list[list[TreeNode]]() pre_order(root) print("\n根ノードからノード 7 までの経路をすべて出力") for path in res: print([node.val for node in path]) ================================================ FILE: ja/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py ================================================ """ 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): """前順走査:例題 3""" # 枝刈り if root is None or root.val == 3: return # 試す path.append(root) if root.val == 7: # 解を記録 res.append(list(path)) pre_order(root.left) pre_order(root.right) # バックトラック path.pop() """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n二分木を初期化") print_tree(root) # 先行順走査 path = list[TreeNode]() res = list[list[TreeNode]]() pre_order(root) print("\n根ノードからノード 7 までの経路をすべて出力し、経路には値が 3 のノードを含めない") for path in res: print([node.val for node in path]) ================================================ FILE: ja/codes/python/chapter_backtracking/preorder_traversal_iii_template.py ================================================ """ 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]] ): """バックトラッキング:例題 3""" # 解かどうかを確認 if is_solution(state): # 解を記録 record_solution(state, res) # すべての選択肢を走査 for choice in choices: # 枝刈り:選択が妥当かを確認する if is_valid(state, choice): # 試行: 選択を行い、状態を更新 make_choice(state, choice) # 次の選択へ進む backtrack(state, [choice.left, choice.right], res) # バックトラック:選択を取り消し、前の状態に戻す undo_choice(state, choice) """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n二分木を初期化") print_tree(root) # バックトラッキング法 res = [] backtrack(state=[], choices=[root], res=res) print("\n根ノードからノード 7 までの経路をすべて出力し、経路には値が 3 のノードを含まないことを条件とする") for path in res: print([node.val for node in path]) ================================================ FILE: ja/codes/python/chapter_backtracking/subset_sum_i.py ================================================ """ 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 # すべての選択肢を走査 # 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for i in range(start, len(choices)): # 枝刈り1:部分集合の和が 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 """Driver Code""" if __name__ == "__main__": nums = [3, 4, 5] target = 9 res = subset_sum_i(nums, target) print(f"入力配列 nums = {nums}, target = {target}") print(f"和が {target} に等しいすべての部分集合 res = {res}") ================================================ FILE: ja/codes/python/chapter_backtracking/subset_sum_i_naive.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [3, 4, 5] target = 9 res = subset_sum_i_naive(nums, target) print(f"入力配列 nums = {nums}, target = {target}") print(f"和が {target} に等しいすべての部分集合 res = {res}") print(f"注意: この方法の出力結果には重複する集合が含まれます") ================================================ FILE: ja/codes/python/chapter_backtracking/subset_sum_ii.py ================================================ """ 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 # すべての選択肢を走査 # 枝刈り 2: start から走査し、重複する部分集合の生成を避ける # 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for i in range(start, len(choices)): # 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する # 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if target - choices[i] < 0: break # 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする 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 """Driver Code""" if __name__ == "__main__": nums = [4, 4, 5] target = 9 res = subset_sum_ii(nums, target) print(f"入力配列 nums = {nums}, target = {target}") print(f"和が {target} に等しいすべての部分集合 res = {res}") ================================================ FILE: ja/codes/python/chapter_computational_complexity/iteration.py ================================================ """ 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}") ================================================ FILE: ja/codes/python/chapter_computational_complexity/recursion.py ================================================ """ 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}") ================================================ FILE: ja/codes/python/chapter_computational_complexity/space_complexity.py ================================================ """ 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 の長さは n, n-1, ..., 2, 1 nums = [0] * n 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) ================================================ FILE: ja/codes/python/chapter_computational_complexity/time_complexity.py ================================================ """ 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 = linear_log_recur(n // 2) + linear_log_recur(n // 2) # 現在の部分問題には n 個の操作が含まれる for _ in range(n): count += 1 return count def factorial_recur(n: int) -> int: """階乗時間(再帰実装)""" if n == 0: return 1 count = 0 # 1個から n 個に分裂 for _ in range(n): count += factorial_recur(n - 1) return count """Driver Code""" if __name__ == "__main__": # n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる n = 8 print("入力データサイズ n =", n) count = constant(n) print("定数時間の操作回数 =", count) count = linear(n) print("線形時間の操作回数 =", count) count = array_traversal([0] * n) print("線形時間(配列走査)の操作回数 =", count) count = quadratic(n) print("二乗時間の操作回数 =", count) nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1] count = bubble_sort(nums) print("二乗時間(バブルソート)の操作回数 =", count) count = exponential(n) print("指数時間(ループ実装)の操作回数 =", count) count = exp_recur(n) print("指数時間(再帰実装)の操作回数 =", count) count = logarithmic(n) print("対数時間(ループ実装)の操作回数 =", count) count = log_recur(n) print("対数時間(再帰実装)の操作回数 =", count) count = linear_log_recur(n) print("線形対数時間(再帰実装)の操作回数 =", count) count = factorial_recur(n) print("階乗時間(再帰実装)の操作回数 =", count) ================================================ FILE: ja/codes/python/chapter_computational_complexity/worst_best_time_complexity.py ================================================ """ 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) ================================================ FILE: ja/codes/python/chapter_divide_and_conquer/binary_search_recur.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # 二分探索(両閉区間) index = binary_search(nums, target) print("対象要素 6 のインデックス = ", index) ================================================ FILE: ja/codes/python/chapter_divide_and_conquer/build_tree.py ================================================ """ 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 の要素からインデックスへの対応を格納するハッシュテーブルを初期化する inorder_map = {val: i for i, val in enumerate(inorder)} root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) return root """Driver Code""" if __name__ == "__main__": preorder = [3, 9, 2, 1, 7] inorder = [9, 3, 1, 2, 7] print(f"前順走査 = {preorder}") print(f"中順走査 = {inorder}") root = build_tree(preorder, inorder) print("構築した二分木:") print_tree(root) ================================================ FILE: ja/codes/python/chapter_divide_and_conquer/hanota.py ================================================ """ File: hanota.py Created Time: 2023-07-16 Author: krahets (krahets@163.com) """ def move(src: list[int], tar: list[int]): """円盤を 1 枚移動""" # src の上から円盤を1枚取り出す pan = src.pop() # 円盤を tar の上に置く tar.append(pan) def dfs(i: int, src: list[int], buf: list[int], tar: list[int]): """ハノイの塔の問題 f(i) を解く""" # src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if i == 1: move(src, tar) return # 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i - 1, src, tar, buf) # 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src, tar) # 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i - 1, buf, src, tar) def solve_hanota(A: list[int], B: list[int], C: list[int]): """ハノイの塔を解く""" n = len(A) # A の上から n 枚の円盤を B を介して C へ移す dfs(n, A, B, C) """Driver Code""" 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}") ================================================ FILE: ja/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_backtrack(n) print(f"{n} 段の階段を上る方法は全部で {res} 通りです") ================================================ FILE: ja/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_constraint_dp(n) print(f"{n} 段の階段を上る方法は全部で {res} 通りです") ================================================ FILE: ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dfs(n) print(f"{n} 段の階段を上る方法は全部で {res} 通りです") ================================================ FILE: ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dfs_mem(n) print(f"{n} 段の階段を上る方法は全部で {res} 通りです") ================================================ FILE: ja/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dp(n) print(f"{n} 段の階段を上る方法は全部で {res} 通りです") res = climbing_stairs_dp_comp(n) print(f"{n} 段の階段を上る方法は全部で {res} 通りです") ================================================ FILE: ja/codes/python/chapter_dynamic_programming/coin_change.py ================================================ """ 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 """Driver Code""" 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}") ================================================ FILE: ja/codes/python/chapter_dynamic_programming/coin_change_ii.py ================================================ """ 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] """Driver Code""" 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}") ================================================ FILE: ja/codes/python/chapter_dynamic_programming/edit_distance.py ================================================ """ File: edit_distancde.py Created Time: 2023-07-04 Author: krahets (krahets@163.com) """ def edit_distance_dfs(s: str, t: str, i: int, j: int) -> int: """編集距離:総当たり探索""" # s と t がともに空なら 0 を返す 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] """Driver Code""" if __name__ == "__main__": s = "bag" t = "pack" n, m = len(s), len(t) # 全探索 res = edit_distance_dfs(s, t, n, m) print(f"{s} を {t} に変更するには最小で {res} 回の編集が必要です") # メモ化探索 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} 回の編集が必要です") ================================================ FILE: ja/codes/python/chapter_dynamic_programming/knapsack.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = len(wgt) # 全探索 res = knapsack_dfs(wgt, val, n, cap) print(f"ナップサック容量を超えない最大価値は {res}") # メモ化探索 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}") ================================================ FILE: ja/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py ================================================ """ 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 """Driver Code""" 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}") ================================================ FILE: ja/codes/python/chapter_dynamic_programming/min_path_sum.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] n, m = len(grid), len(grid[0]) # 全探索 res = min_path_sum_dfs(grid, n - 1, m - 1) print(f"左上から右下までの最小経路和は {res}") # メモ化探索 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}") ================================================ FILE: ja/codes/python/chapter_dynamic_programming/unbounded_knapsack.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": wgt = [1, 2, 3] val = [5, 11, 15] cap = 4 # 動的計画法 res = unbounded_knapsack_dp(wgt, val, cap) print(f"ナップサック容量を超えない最大価値は {res}") # 空間最適化後の動的計画法 res = unbounded_knapsack_dp_comp(wgt, val, cap) print(f"ナップサック容量を超えない最大価値は {res}") ================================================ FILE: ja/codes/python/chapter_graph/graph_adjacency_list.py ================================================ """ 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]]): """コンストラクタ""" # 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 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},") """Driver Code""" 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() ================================================ FILE: ja/codes/python/chapter_graph/graph_adjacency_matrix.py ================================================ """ 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 の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する 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) # 隣接行列に 1 行追加 new_row = [0] * n self.adj_mat.append(new_row) # 隣接行列に 1 列追加 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 は vertices の要素インデックスに対応する # 範囲外と同値の場合の処理 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 は vertices の要素インデックスに対応する # 範囲外と同値の場合の処理 if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: raise IndexError() self.adj_mat[i][j] = 0 self.adj_mat[j][i] = 0 def print(self): """隣接行列を出力""" print("頂点リスト =", self.vertices) print("隣接行列 =") print_matrix(self.adj_mat) """Driver Code""" if __name__ == "__main__": # 無向グラフを初期化する # 注意: edges の要素は頂点インデックスであり、vertices の要素インデックスに対応する vertices = [1, 3, 2, 5, 4] edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] graph = GraphAdjMat(vertices, edges) print("\n初期化後、グラフは") graph.print() # 辺を追加する # 頂点 1, 2 のインデックスはそれぞれ 0, 2 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() ================================================ FILE: ja/codes/python/chapter_graph/graph_bfs.py ================================================ """ 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 """Driver Code""" 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)) ================================================ FILE: ja/codes/python/chapter_graph/graph_dfs.py ================================================ """ 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 """Driver Code""" 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)) ================================================ FILE: ja/codes/python/chapter_greedy/coin_change_greedy.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # 貪欲法:大域最適解を保証できる coins = [1, 5, 10, 20, 50, 100] amt = 186 res = coin_change_greedy(coins, amt) print(f"\ncoins = {coins}, amt = {amt}") print(f"{amt} を作るのに必要な最小の硬貨枚数は {res}") # 貪欲法:大域最適解を保証できない 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") ================================================ FILE: ja/codes/python/chapter_greedy/fractional_knapsack.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = len(wgt) # 貪欲法 res = fractional_knapsack(wgt, val, cap) print(f"ナップサック容量を超えない最大価値は {res}") ================================================ FILE: ja/codes/python/chapter_greedy/max_capacity.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": ht = [3, 8, 5, 2, 7, 7, 3, 4] # 貪欲法 res = max_capacity(ht) print(f"最大容量は {res}") ================================================ FILE: ja/codes/python/chapter_greedy/max_product_cutting.py ================================================ """ 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)) """Driver Code""" if __name__ == "__main__": n = 58 # 貪欲法 res = max_product_cutting(n) print(f"最大分割積は {res}") ================================================ FILE: ja/codes/python/chapter_hashing/array_hash_map.py ================================================ """ 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 | None: """検索操作""" 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, "シャオハー") hmap.put(15937, "シャオルオ") hmap.put(16750, "シャオスワン") hmap.put(13276, "シャオファー") hmap.put(10583, "シャオヤー") print("\n追加完了後、ハッシュテーブルは\nKey -> Value") hmap.print() # 検索操作 # ハッシュテーブルにキー key を入力し、値 value を取得する name = hmap.get(15937) print("\n学籍番号 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) ================================================ FILE: ja/codes/python/chapter_hashing/built_in_hash.py ================================================ """ 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 アルゴリズム" hash_str = hash(str) print(f"文字列 {str} のハッシュ値は {hash_str}") tup = (12836, "シャオハー") hash_tup = hash(tup) print(f"タプル {tup} のハッシュ値は {hash(hash_tup)}") obj = ListNode(0) hash_obj = hash(obj) print(f"ノードオブジェクト {obj} のハッシュ値は {hash_obj}") ================================================ FILE: ja/codes/python/chapter_hashing/hash_map.py ================================================ """ 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] = "シャオハー" hmap[15937] = "シャオルオ" hmap[16750] = "シャオスワン" hmap[13276] = "シャオファー" hmap[10583] = "シャオヤー" print("\n追加完了後、ハッシュテーブルは\nKey -> Value") print_dict(hmap) # 検索操作 # ハッシュテーブルにキー key を入力し、値 value を取得する name: str = hmap[15937] print("\n学籍番号 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) ================================================ FILE: ja/codes/python/chapter_hashing/hash_map_chaining.py ================================================ """ 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] # バケットを走査し、key が見つかれば対応する val を返す for pair in bucket: if pair.key == key: return pair.val # key が見つからない場合は None を返す return None def put(self, key: int, val: str): """追加操作""" # 負荷率がしきい値を超えたら、リサイズを実行 if self.load_factor() > self.load_thres: self.extend() index = self.hash_func(key) bucket = self.buckets[index] # バケットを走査し、指定した key が見つかれば対応する val を更新して返す for pair in bucket: if pair.key == key: pair.val = val return # その key が存在しなければ、キーと値のペアを末尾に追加 pair = Pair(key, val) bucket.append(pair) self.size += 1 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, "シャオハー") hashmap.put(15937, "シャオルオ") hashmap.put(16750, "シャオスワン") hashmap.put(13276, "シャオファー") hashmap.put(10583, "シャオヤー") print("\n追加完了後、ハッシュテーブルは\n[Key1 -> Value1, Key2 -> Value2, ...]") hashmap.print() # 検索操作 # ハッシュテーブルにキー key を入力し、値 value を取得する name = hashmap.get(13276) print("\n学籍番号 13276 を入力すると、氏名は " + name) # 削除操作 # ハッシュテーブルからキーと値の組 (key, value) を削除する hashmap.remove(12836) print("\n12836 を削除した後、ハッシュテーブルは\n[Key1 -> Value1, Key2 -> Value2, ...]") hashmap.print() ================================================ FILE: ja/codes/python/chapter_hashing/hash_map_open_addressing.py ================================================ """ 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: # key が見つかったら、対応するバケットのインデックスを返す if self.buckets[index].key == key: # 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 if first_tombstone != -1: self.buckets[first_tombstone] = self.buckets[index] self.buckets[index] = self.TOMBSTONE return first_tombstone # 移動後のバケットインデックスを返す return index # バケットのインデックスを返す # 最初に見つかった削除マークを記録 if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE: first_tombstone = index # バケットのインデックスを計算し、末尾を越えたら先頭に戻る index = (index + 1) % self.capacity # key が存在しない場合は追加位置のインデックスを返す return index if first_tombstone == -1 else first_tombstone def get(self, key: int) -> str: """検索操作""" # key に対応するバケットインデックスを探す 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, "シャオハー") hashmap.put(15937, "シャオルオ") hashmap.put(16750, "シャオスワン") hashmap.put(13276, "シャオファー") hashmap.put(10583, "シャオヤー") print("\n追加完了後、ハッシュテーブルは\nKey -> Value") hashmap.print() # 検索操作 # ハッシュテーブルにキー key を入力し、値 val を得る name = hashmap.get(13276) print("\n学籍番号 13276 を入力すると、氏名は " + name) # 削除操作 # ハッシュテーブルからキーと値の組 (key, val) を削除する hashmap.remove(16750) print("\n16750 を削除した後、ハッシュテーブルは\nKey -> Value") hashmap.print() ================================================ FILE: ja/codes/python/chapter_hashing/simple_hash.py ================================================ """ 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 アルゴリズム" 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}") ================================================ FILE: ja/codes/python/chapter_heap/heap.py ================================================ """ 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]) """Driver Code""" if __name__ == "__main__": # 最小ヒープを初期化 min_heap, flag = [], 1 # 最大ヒープを初期化 max_heap, flag = [], -1 print("\n以下のテストケースは最大ヒープ") # Python の heapq モジュールはデフォルトで最小ヒープを実装している # 要素を負にしてからヒープに入れると大小関係を反転でき、最大ヒープを実現できる # この例では、flag = 1 が最小ヒープ、flag = -1 が最大ヒープに対応する # 要素をヒープに追加 test_push(max_heap, 1, flag) test_push(max_heap, 3, flag) test_push(max_heap, 2, flag) test_push(max_heap, 5, flag) test_push(max_heap, 4, flag) # ヒープ頂点の要素を取得 peek: int = flag * max_heap[0] print(f"\nヒープ先頭要素は {peek}") # ヒープ頂点の要素を取り出す 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) ================================================ FILE: ja/codes/python/chapter_heap/my_heap.py ================================================ """ 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("ヒープが空です") # 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) 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) """Driver Code""" 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}") ================================================ FILE: ja/codes/python/chapter_heap/top_k.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [1, 7, 6, 3, 2] k = 3 res = top_k_heap(nums, k) print(f"最大の {k} 個の要素は") print_heap(res) ================================================ FILE: ja/codes/python/chapter_searching/binary_search.py ================================================ """ 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) // 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) // 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 を返す """Driver Code""" if __name__ == "__main__": target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # 二分探索(両閉区間) index = binary_search(nums, target) print("対象要素 6 のインデックス = ", index) # 二分探索(左閉右開区間) index = binary_search_lcro(nums, target) print("対象要素 6 のインデックス = ", index) ================================================ FILE: ja/codes/python/chapter_searching/binary_search_edge.py ================================================ """ 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: """最も左の target を二分探索""" # target の挿入位置を探すのと等価 i = binary_search_insertion(nums, target) # target が見つからなければ、-1 を返す if i == len(nums) or nums[i] != target: return -1 # target が見つかったら、インデックス i を返す return i def binary_search_right_edge(nums: list[int], target: int) -> int: """最も右の target を二分探索""" # 最左の target + 1 を探す問題に変換する i = binary_search_insertion(nums, target + 1) # j は最も右の target を指し、i は target より大きい最初の要素を指す j = i - 1 # target が見つからなければ、-1 を返す if j == -1 or nums[j] != target: return -1 # target が見つかったら、インデックス j を返す return j """Driver Code""" if __name__ == "__main__": # 重複要素を含む配列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print(f"\n配列 nums = {nums}") # 二分探索で左端と右端を探す for target in [6, 7]: index = binary_search_left_edge(nums, target) print(f"左端の要素 {target} のインデックスは {index}") index = binary_search_right_edge(nums, target) print(f"右端の要素 {target} のインデックスは {index}") ================================================ FILE: ja/codes/python/chapter_searching/binary_search_insertion.py ================================================ """ 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) // 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 # target が見つかったら、挿入位置 m を返す # target が見つからなければ、挿入位置 i を返す return i def binary_search_insertion(nums: list[int], target: int) -> int: """二分探索で挿入位置を探す(重複要素あり)""" i, j = 0, len(nums) - 1 # 両閉区間 [0, n-1] を初期化 while i <= j: m = (i + j) // 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: j = m - 1 # target より小さい最初の要素は区間 [i, m-1] にある # 挿入位置 i を返す return i """Driver Code""" if __name__ == "__main__": # 重複要素のない配列 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print(f"\n配列 nums = {nums}") # 二分探索で挿入位置を探す for target in [6, 9]: index = binary_search_insertion_simple(nums, target) print(f"要素 {target} の挿入位置のインデックスは {index}") # 重複要素を含む配列 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}") ================================================ FILE: ja/codes/python/chapter_searching/hashing_search.py ================================================ """ 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: """ハッシュ探索(配列)""" # ハッシュテーブルの key: 目標要素、value: インデックス # ハッシュテーブルにこの key がなければ -1 を返す return hmap.get(target, -1) def hashing_search_linkedlist( hmap: dict[int, ListNode], target: int ) -> ListNode | None: """ハッシュ探索(連結リスト)""" # ハッシュテーブルの key: 対象要素、value: ノードオブジェクト # ハッシュテーブルにこの key がなければ None を返す return hmap.get(target, None) """Driver Code""" 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 # key: 要素、value: インデックス 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 # key: ノード値、value: ノード head = head.next node: ListNode = hashing_search_linkedlist(map1, target) print("対象ノード値 3 に対応するノードオブジェクトは", node) ================================================ FILE: ja/codes/python/chapter_searching/linear_search.py ================================================ """ 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 を返す """Driver Code""" if __name__ == "__main__": target = 3 # 配列で線形探索を行う nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] index: int = linear_search_array(nums, target) print("対象要素 3 のインデックス =", index) # 連結リストで線形探索を行う head: ListNode = list_to_linked_list(nums) node: ListNode | None = linear_search_linkedlist(head, target) print("対象ノード値 3 に対応するノードオブジェクトは", node) ================================================ FILE: ja/codes/python/chapter_searching/two_sum.py ================================================ """ 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]: """方法 1:総当たり列挙""" # 2重ループのため、時間計算量は O(n^2) for i in range(len(nums) - 1): for j in range(i + 1, len(nums)): if nums[i] + nums[j] == target: return [i, j] return [] def two_sum_hash_table(nums: list[int], target: int) -> list[int]: """方法 2:補助ハッシュテーブル""" # 補助ハッシュテーブルを使用し、空間計算量は 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 [] """Driver Code""" if __name__ == "__main__": # ======= Test Case ======= nums = [2, 7, 11, 15] target = 13 # ====== Driver Code ====== # 方法 1 res: list[int] = two_sum_brute_force(nums, target) print("方法1 res =", res) # 方法 2 res: list[int] = two_sum_hash_table(nums, target) print("方法2 res =", res) ================================================ FILE: ja/codes/python/chapter_sorting/bubble_sort.py ================================================ """ 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 # このバブル処理で要素交換が一度もなければそのまま終了 """Driver Code""" 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) ================================================ FILE: ja/codes/python/chapter_sorting/bucket_sort.py ================================================ """ 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) ================================================ FILE: ja/codes/python/chapter_sorting/counting_sort.py ================================================ """ File: counting_sort.py Created Time: 2023-03-21 Author: krahets (krahets@163.com) """ def counting_sort_naive(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 を走査し、各要素を元の配列 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 は、num が res に最後に現れるインデックス 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] """Driver Code""" 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}") ================================================ FILE: ja/codes/python/chapter_sorting/heap_sort.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] heap_sort(nums) print("ヒープソート完了後 nums =", nums) ================================================ FILE: ja/codes/python/chapter_sorting/insertion_sort.py ================================================ """ 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 を正しい位置に配置する """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] insertion_sort(nums) print("挿入ソート完了後 nums =", nums) ================================================ FILE: ja/codes/python/chapter_sorting/merge_sort.py ================================================ """ 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) // 2 # 中点を計算 merge_sort(nums, left, mid) # 左部分配列を再帰処理 merge_sort(nums, mid + 1, right) # 右部分配列を再帰処理 # マージフェーズ merge(nums, left, mid, right) """Driver Code""" if __name__ == "__main__": nums = [7, 3, 2, 6, 0, 1, 5, 4] merge_sort(nums, 0, len(nums) - 1) print("マージソート完了後 nums =", nums) ================================================ FILE: ja/codes/python/chapter_sorting/quick_sort.py ================================================ """ 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: """番兵による分割処理(3 点中央値)""" # 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] """Driver Code""" 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) ================================================ FILE: ja/codes/python/chapter_sorting/radix_sort.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # 基数ソート nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ] radix_sort(nums) print("基数ソート完了後 nums =", nums) ================================================ FILE: ja/codes/python/chapter_sorting/selection_sort.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] selection_sort(nums) print("選択ソート完了後 nums =", nums) ================================================ FILE: ja/codes/python/chapter_stack_and_queue/array_deque.py ================================================ """ 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 つ移動する # 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする 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("両端キューが空です") return self._nums[self._front] def peek_last(self) -> int: """キュー末尾の要素にアクセス""" if self.is_empty(): raise IndexError("両端キューが空です") # 末尾要素のインデックスを計算 last = self.index(self._front + self._size - 1) return self._nums[last] def to_array(self) -> list[int]: """出力用の配列を返す""" # 有効長の範囲内のリスト要素のみを変換 res = [] for i in range(self._size): res.append(self._nums[self.index(self._front + i)]) 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) ================================================ FILE: ja/codes/python/chapter_stack_and_queue/array_queue.py ================================================ """ 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("キューがいっぱいです") # 末尾ポインタを計算し、末尾インデックス + 1 を指す # 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする 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("キューが空です") 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 =", 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()) ================================================ FILE: ja/codes/python/chapter_stack_and_queue/array_stack.py ================================================ """ 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("スタックが空です") return self._stack.pop() def peek(self) -> int: """スタックトップの要素にアクセス""" if self.is_empty(): raise IndexError("スタックが空です") 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 =", stack.to_list()) # スタックの長さを取得 size: int = stack.size() print("スタックの長さ size =", size) # 空かどうかを判定 is_empty: bool = stack.is_empty() print("スタックが空かどうか =", is_empty) ================================================ FILE: ja/codes/python/chapter_stack_and_queue/deque.py ================================================ """ 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("先頭から取り出した後 deque =", deq) pop_rear: int = deq.pop() # 末尾要素を取り出す print("末尾から取り出した要素 pop_rear =", pop_rear) print("末尾から取り出した後 deque =", deq) # 両端キューの長さを取得 size: int = len(deq) print("両端キューの長さ size =", size) # 両端キューが空かどうかを判定 is_empty: bool = len(deq) == 0 print("両端キューが空かどうか =", is_empty) ================================================ FILE: ja/codes/python/chapter_stack_and_queue/linkedlist_deque.py ================================================ """ 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: # node を連結リストの先頭に追加 self._front.prev = node node.next = self._front self._front = node # 先頭ノードを更新する # 末尾へのエンキュー操作 else: # node を連結リストの末尾に追加 self._rear.next = node node.prev = self._rear self._rear = node # 末尾ノードを更新する self._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("両端キューが空です") # キュー先頭からの取り出し 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("両端キューが空です") return self._front.val def peek_last(self) -> int: """キュー末尾の要素にアクセス""" if self.is_empty(): raise IndexError("両端キューが空です") 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) ================================================ FILE: ja/codes/python/chapter_stack_and_queue/linkedlist_queue.py ================================================ """ 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("キューが空です") 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 =", queue.to_list()) # キューの長さを取得 size: int = queue.size() print("キューの長さ size =", size) # キューが空かどうかを判定 is_empty: bool = queue.is_empty() print("キューが空かどうか =", is_empty) ================================================ FILE: ja/codes/python/chapter_stack_and_queue/linkedlist_stack.py ================================================ """ 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("スタックが空です") 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 =", stack.to_list()) # スタックの長さを取得 size: int = stack.size() print("スタックの長さ size =", size) # 空かどうかを判定 is_empty: bool = stack.is_empty() print("スタックが空かどうか =", is_empty) ================================================ FILE: ja/codes/python/chapter_stack_and_queue/queue.py ================================================ """ 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 =", que) # キューの長さを取得 size: int = len(que) print("キューの長さ size =", size) # キューが空かどうかを判定 is_empty: bool = len(que) == 0 print("キューが空かどうか =", is_empty) ================================================ FILE: ja/codes/python/chapter_stack_and_queue/stack.py ================================================ """ File: stack.py Created Time: 2022-11-29 Author: Peng Chen (pengchzn@gmail.com) """ """Driver Code""" if __name__ == "__main__": # スタックを初期化する # Python には組み込みのスタッククラスがないため、list をスタックとして使える 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 =", stack) # スタックの長さを取得 size: int = len(stack) print("スタックの長さ size =", size) # 空かどうかを判定 is_empty: bool = len(stack) == 0 print("スタックが空かどうか =", is_empty) ================================================ FILE: ja/codes/python/chapter_tree/array_binary_tree.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # 二分木を初期化 # ここでは、配列から直接二分木を生成する関数を利用する arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] root = list_to_tree(arr) print("\n二分木を初期化\n") print("二分木の配列表現:") print(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) ================================================ FILE: ja/codes/python/chapter_tree/avl_tree.py ================================================ """ 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 の場合、node をそのまま削除して返す if child is None: return None # 子ノード数 = 1 の場合、node をそのまま削除する 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 """Driver Code""" 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)) ================================================ FILE: ja/codes/python/chapter_tree/binary_search_tree.py ================================================ """ 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 or 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) # tmp で cur を上書きする cur.val = tmp.val """Driver Code""" if __name__ == "__main__": # 二分探索木を初期化 bst = BinarySearchTree() nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] # 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる for num in nums: bst.insert(num) print("\n初期化した二分木は\n") print_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()) ================================================ FILE: ja/codes/python/chapter_tree/binary_tree.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # 二分木を初期化する # ノードを初期化する n1 = TreeNode(val=1) n2 = TreeNode(val=2) n3 = TreeNode(val=3) n4 = TreeNode(val=4) n5 = TreeNode(val=5) # ノード間の参照(ポインタ)を構築する n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 print("\n二分木を初期化\n") print_tree(n1) # ノードの挿入と削除 P = TreeNode(0) # n1 -> n2 の間にノード P を挿入 n1.left = P P.left = n2 print("\nノード P を挿入した後\n") print_tree(n1) # ノードを削除 n1.left = n2 print("\nノード P を削除した後\n") print_tree(n1) ================================================ FILE: ja/codes/python/chapter_tree/binary_tree_bfs.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # 二分木を初期化 # ここでは、配列から直接二分木を生成する関数を利用する root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\n二分木を初期化\n") print_tree(root) # レベル順走査 res: list[int] = level_order(root) print("\nレベル順走査のノード出力シーケンス = ", res) ================================================ FILE: ja/codes/python/chapter_tree/binary_tree_dfs.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": # 二分木を初期化 # ここでは、配列から直接二分木を生成する関数を利用する root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\n二分木を初期化\n") print_tree(root) # 先行順走査 res = [] pre_order(root) print("\n先行順走査のノード出力シーケンス = ", res) # 中順走査 res.clear() in_order(root) print("\n中間順走査のノード出力シーケンス = ", res) # 後順走査 res.clear() post_order(root) print("\n後行順走査のノード出力シーケンス = ", res) ================================================ FILE: ja/codes/python/modules/__init__.py ================================================ # Follow the PEP 585 - Type Hinting Generics In Standard Collections # https://peps.python.org/pep-0585/ from __future__ import annotations # Import common libs here to simplify the code by `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, ) ================================================ FILE: ja/codes/python/modules/list_node.py ================================================ """ 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 ================================================ FILE: ja/codes/python/modules/print_util.py ================================================ """ 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 ): """ 二分木を出力 This tree printer is borrowed from 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) ================================================ FILE: ja/codes/python/modules/tree_node.py ================================================ """ 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 ================================================ FILE: ja/codes/python/modules/vertex.py ================================================ # 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] ================================================ FILE: ja/codes/python/test_all.py ================================================ import os import glob import subprocess env = os.environ.copy() env["PYTHONIOENCODING"] = "utf-8" if __name__ == "__main__": # find source code files src_paths = sorted(glob.glob("chapter_*/*.py")) errors = [] # run python code 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' ) # Wait for the process to complete, and get the output and error messages stdout, stderr = process.communicate() # Check the exit status exit_status = process.returncode if exit_status != 0: errors.append(stderr) print(f"Tested {len(src_paths)} files") print(f"Found exception in {len(errors)} files") if len(errors) > 0: raise RuntimeError("\n\n".join(errors)) ================================================ FILE: ja/codes/pythontutor/chapter_array_and_linkedlist/array.md ================================================ https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_access%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%A6%81%E7%B4%A0%E3%81%B8%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%22%22%22%0A%20%20%20%20%23%20%E5%8C%BA%E9%96%93%20%5B0%2C%20len%28nums%29-1%5D%20%E3%81%8B%E3%82%89%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%81%AB%E6%95%B0%E5%AD%97%E3%82%92%201%20%E3%81%A4%E9%81%B8%E3%81%B6%0A%20%20%20%20random_index%20%3D%20random.randint%280%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20%23%20%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%81%AA%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E5%BE%97%E3%81%97%E3%81%A6%E8%BF%94%E3%81%99%0A%20%20%20%20random_num%20%3D%20nums%5Brandom_index%5D%0A%20%20%20%20return%20random_num%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%0A%20%20%20%20random_num%3A%20int%20%3D%20random_access%28nums%29%0A%20%20%20%20print%28%22nums%20%E3%81%8B%E3%82%89%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%81%AA%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E5%BE%97%22%2C%20random_num%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20insert%28nums%3A%20list%5Bint%5D%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E9%85%8D%E5%88%97%E3%81%AE%20index%20%E7%95%AA%E7%9B%AE%E3%81%AB%E8%A6%81%E7%B4%A0%20num%20%E3%82%92%E6%8C%BF%E5%85%A5%22%22%22%0A%20%20%20%20%23%20%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20index%20%E4%BB%A5%E9%99%8D%E3%81%AE%E5%85%A8%E8%A6%81%E7%B4%A0%E3%82%92%201%20%E3%81%A4%E5%BE%8C%E3%82%8D%E3%81%B8%E7%A7%BB%E5%8B%95%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%20index%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20-%201%5D%0A%20%20%20%20%23%20index%20%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AB%20num%20%E3%82%92%E4%BB%A3%E5%85%A5%E3%81%99%E3%82%8B%0A%20%20%20%20nums%5Bindex%5D%20%3D%20num%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E6%8C%BF%E5%85%A5%E3%81%99%E3%82%8B%0A%20%20%20%20insert%28nums%2C%206%2C%203%29%0A%20%20%20%20print%28%22%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%203%20%E3%81%AB%E6%95%B0%E5%80%A4%206%20%E3%82%92%E6%8C%BF%E5%85%A5%E3%81%97%E3%80%81nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20remove%28nums%3A%20list%5Bint%5D%2C%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22index%20%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20%23%20%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20index%20%E3%82%88%E3%82%8A%E5%BE%8C%E3%82%8D%E3%81%AE%E5%85%A8%E8%A6%81%E7%B4%A0%E3%82%92%201%20%E3%81%A4%E5%89%8D%E3%81%B8%E7%A7%BB%E5%8B%95%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28index%2C%20len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20%2B%201%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E5%89%8A%E9%99%A4%0A%20%20%20%20remove%28nums%2C%202%29%0A%20%20%20%20print%28%22%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%202%20%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E5%89%8A%E9%99%A4%E3%81%97%E3%80%81nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20traverse%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%85%8D%E5%88%97%E3%82%92%E8%B5%B0%E6%9F%BB%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%A7%E9%85%8D%E5%88%97%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E8%A6%81%E7%B4%A0%E3%82%92%E7%9B%B4%E6%8E%A5%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%20%20%20%20%23%20%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%A8%E8%A6%81%E7%B4%A0%E3%82%92%E5%90%8C%E6%99%82%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%2C%20num%20in%20enumerate%28nums%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20traverse%28nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20find%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%85%8D%E5%88%97%E5%86%85%E3%81%A7%E6%8C%87%E5%AE%9A%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E7%B4%A2%E3%81%99%E3%82%8B%0A%20%20%20%20index%3A%20int%20%3D%20find%28nums%2C%203%29%0A%20%20%20%20print%28%22nums%20%E3%81%A7%E8%A6%81%E7%B4%A0%203%20%E3%82%92%E6%A4%9C%E7%B4%A2%E3%81%97%E3%80%81%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20%3D%22%2C%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=%23%20Python%20%E3%81%AE%20list%20%E3%81%AF%E5%8B%95%E7%9A%84%E9%85%8D%E5%88%97%E3%81%A7%E3%81%82%E3%82%8A%E3%80%81%E7%9B%B4%E6%8E%A5%E6%8B%A1%E5%BC%B5%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%0A%23%20%E5%AD%A6%E7%BF%92%E3%81%97%E3%82%84%E3%81%99%E3%81%84%E3%82%88%E3%81%86%E3%80%81%E6%9C%AC%E9%96%A2%E6%95%B0%E3%81%A7%E3%81%AF%20list%20%E3%82%92%E9%95%B7%E3%81%95%E4%B8%8D%E5%A4%89%E3%81%AE%E9%85%8D%E5%88%97%E3%81%A8%E3%81%97%E3%81%A6%E6%89%B1%E3%81%84%E3%81%BE%E3%81%99%0Adef%20extend%28nums%3A%20list%5Bint%5D%2C%20enlarge%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E9%85%8D%E5%88%97%E9%95%B7%E3%82%92%E6%8B%A1%E5%BC%B5%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20%23%20%E6%8B%A1%E5%BC%B5%E5%BE%8C%E3%81%AE%E9%95%B7%E3%81%95%E3%82%92%E6%8C%81%E3%81%A4%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20%28len%28nums%29%20%2B%20enlarge%29%0A%20%20%20%20%23%20%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%E3%81%AE%E5%85%A8%E8%A6%81%E7%B4%A0%E3%82%92%E6%96%B0%E3%81%97%E3%81%84%E9%85%8D%E5%88%97%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E6%8B%A1%E5%BC%B5%E5%BE%8C%E3%81%AE%E6%96%B0%E3%81%97%E3%81%84%E9%85%8D%E5%88%97%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E9%95%B7%E3%81%95%E3%82%92%E6%8B%A1%E5%BC%B5%0A%20%20%20%20nums%20%3D%20extend%28nums%2C%203%29%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%E3%81%AE%E9%95%B7%E3%81%95%E3%82%92%208%20%E3%81%AB%E6%8B%A1%E5%BC%B5%E3%81%97%E3%80%81nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B6%9A%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20insert%28n0%3A%20ListNode%2C%20P%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%81%A7%E3%83%8E%E3%83%BC%E3%83%89%20n0%20%E3%81%AE%E5%BE%8C%E3%82%8D%E3%81%AB%E3%83%8E%E3%83%BC%E3%83%89%20P%20%E3%82%92%E6%8C%BF%E5%85%A5%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20n1%20%3D%20n0.next%0A%20%20%20%20P.next%20%3D%20n1%0A%20%20%20%20n0.next%20%3D%20P%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E5%90%84%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E9%96%93%E3%81%AE%E5%8F%82%E7%85%A7%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%0A%20%20%20%20p%20%3D%20ListNode%280%29%0A%20%20%20%20insert%28n0%2C%20p%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B6%9A%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20remove%28n0%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%81%A7%E3%83%8E%E3%83%BC%E3%83%89%20n0%20%E3%81%AE%E7%9B%B4%E5%BE%8C%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20if%20not%20n0.next%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20n0%20-%3E%20P%20-%3E%20n1%0A%20%20%20%20P%20%3D%20n0.next%0A%20%20%20%20n1%20%3D%20P.next%0A%20%20%20%20n0.next%20%3D%20n1%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E5%90%84%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E9%96%93%E3%81%AE%E5%8F%82%E7%85%A7%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%89%8A%E9%99%A4%0A%20%20%20%20remove%28n0%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B6%9A%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20access%28head%3A%20ListNode%2C%20index%3A%20int%29%20-%3E%20ListNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E5%86%85%E3%81%A7%20index%20%E7%95%AA%E7%9B%AE%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%22%22%22%0A%20%20%20%20for%20_%20in%20range%28index%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20head%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20return%20head%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E5%90%84%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E9%96%93%E3%81%AE%E5%8F%82%E7%85%A7%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%0A%20%20%20%20node%20%3D%20access%28n0%2C%203%29%0A%20%20%20%20print%28%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%203%20%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E5%80%A4%20%3D%20%7B%7D%22.format%28node.val%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B6%9A%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20find%28head%3A%20ListNode%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%81%A7%E5%80%A4%E3%81%8C%20target%20%E3%81%AE%E6%9C%80%E5%88%9D%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8E%A2%E3%81%99%22%22%22%0A%20%20%20%20index%20%3D%200%0A%20%20%20%20while%20head%3A%0A%20%20%20%20%20%20%20%20if%20head.val%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20index%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20%20%20%20%20index%20%2B%3D%201%0A%20%20%20%20return%20-1%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E5%90%84%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E9%96%93%E3%81%AE%E5%8F%82%E7%85%A7%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8E%A2%E7%B4%A2%0A%20%20%20%20index%20%3D%20find%28n0%2C%202%29%0A%20%20%20%20print%28%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E5%86%85%E3%81%A7%E5%80%A4%E3%81%8C%202%20%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20%3D%20%7B%7D%22.format%28index%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_array_and_linkedlist/my_list.md ================================================ https://pythontutor.com/render.html#code=class%20MyList%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._capacity%3A%20int%20%3D%2010%0A%20%20%20%20%20%20%20%20self._arr%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20%2A%20self._capacity%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%20%20%20%20%20%20%20%20self._extend_ratio%3A%20int%20%3D%202%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._capacity%0A%0A%20%20%20%20def%20get%28self%2C%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20return%20self._arr%5Bindex%5D%0A%0A%20%20%20%20def%20set%28self%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%0A%20%20%20%20def%20add%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self.size%28%29%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20self._arr%5Bself._size%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28self._size%20-%201%2C%20index%20-%201%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%20%2B%201%5D%20%3D%20self._arr%5Bj%5D%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self%2C%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20num%20%3D%20self._arr%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28index%2C%20self._size%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%5D%20%3D%20self._arr%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20extend_capacity%28self%29%3A%0A%20%20%20%20%20%20%20%20self._arr%20%3D%20self._arr%20%2B%20%5B0%5D%20%2A%20self.capacity%28%29%20%2A%20%28self._extend_ratio%20-%201%29%0A%20%20%20%20%20%20%20%20self._capacity%20%3D%20len%28self._arr%29%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20nums%20%3D%20MyList%28%29%0A%20%20%20%20nums.add%281%29%0A%20%20%20%20nums.add%283%29%0A%20%20%20%20nums.add%282%29%0A%20%20%20%20nums.add%285%29%0A%20%20%20%20nums.add%284%29%0A%20%20%20%20nums.insert%286%2C%20index%3D3%29%0A%20%20%20%20nums.remove%283%29%0A%20%20%20%20num%20%3D%20nums.get%281%29%0A%20%20%20%20nums.set%280%2C%201%29%0A%20%20%20%20for%20i%20in%20range%2810%29%3A%0A%20%20%20%20%20%20%20%20nums.add%28i%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_backtracking/n_queens.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20row%3A%20int%2C%0A%20%20%20%20n%3A%20int%2C%0A%20%20%20%20state%3A%20list%5Blist%5Bstr%5D%5D%2C%0A%20%20%20%20res%3A%20list%5Blist%5Blist%5Bstr%5D%5D%5D%2C%0A%20%20%20%20cols%3A%20list%5Bbool%5D%2C%0A%20%20%20%20diags1%3A%20list%5Bbool%5D%2C%0A%20%20%20%20diags2%3A%20list%5Bbool%5D%2C%0A%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%EF%BC%9AN%20%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%22%22%22%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E8%A1%8C%E3%81%B8%E3%81%AE%E9%85%8D%E7%BD%AE%E3%81%8C%E5%AE%8C%E4%BA%86%E3%81%97%E3%81%9F%E3%82%89%E3%80%81%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%0A%20%20%20%20if%20row%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res.append%28%5Blist%28row%29%20for%20row%20in%20state%5D%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E5%88%97%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20col%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%81%93%E3%81%AE%E3%83%9E%E3%82%B9%E3%81%AB%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E4%B8%BB%E5%AF%BE%E8%A7%92%E7%B7%9A%E3%81%A8%E5%89%AF%E5%AF%BE%E8%A7%92%E7%B7%9A%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20diag1%20%3D%20row%20-%20col%20%2B%20n%20-%201%0A%20%20%20%20%20%20%20%20diag2%20%3D%20row%20%2B%20col%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%EF%BC%9A%E3%81%9D%E3%81%AE%E3%83%9E%E3%82%B9%E3%81%AE%E5%88%97%E3%80%81%E4%B8%BB%E5%AF%BE%E8%A7%92%E7%B7%9A%E3%80%81%E5%89%AF%E5%AF%BE%E8%A7%92%E7%B7%9A%E3%81%AB%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%81%8C%E3%81%82%E3%81%A3%E3%81%A6%E3%81%AF%E3%81%AA%E3%82%89%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20if%20not%20cols%5Bcol%5D%20and%20not%20diags1%5Bdiag1%5D%20and%20not%20diags2%5Bdiag2%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E8%A1%8C%EF%BC%9A%E3%81%9D%E3%81%AE%E3%83%9E%E3%82%B9%E3%81%AB%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%82%92%E7%BD%AE%E3%81%8F%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22Q%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%AC%A1%E3%81%AE%E8%A1%8C%E3%81%AB%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28row%20%2B%201%2C%20n%2C%20state%2C%20res%2C%20cols%2C%20diags1%2C%20diags2%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%88%BB%E3%81%99%EF%BC%9A%E3%81%9D%E3%81%AE%E3%83%9E%E3%82%B9%E3%82%92%E7%A9%BA%E3%81%8D%E3%83%9E%E3%82%B9%E3%81%AB%E6%88%BB%E3%81%99%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22%23%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20False%0A%0A%0Adef%20n_queens%28n%3A%20int%29%20-%3E%20list%5Blist%5Blist%5Bstr%5D%5D%5D%3A%0A%20%20%20%20%22%22%22N%20%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%82%92%E8%A7%A3%E3%81%8F%22%22%22%0A%20%20%20%20%23%20n%2An%20%E3%81%AE%E7%9B%A4%E9%9D%A2%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%E3%80%82%27Q%27%20%E3%81%AF%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%80%81%27%23%27%20%E3%81%AF%E7%A9%BA%E3%81%8D%E3%83%9E%E3%82%B9%E3%82%92%E8%A1%A8%E3%81%99%0A%20%20%20%20state%20%3D%20%5B%5B%22%23%22%20for%20_%20in%20range%28n%29%5D%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20cols%20%3D%20%5BFalse%5D%20%2A%20n%20%20%23%20%E5%88%97%E3%81%AB%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%81%8C%E3%81%82%E3%82%8B%E3%81%8B%E8%A8%98%E9%8C%B2%0A%20%20%20%20diags1%20%3D%20%5BFalse%5D%20%2A%20%282%20%2A%20n%20-%201%29%20%20%23%20%E4%B8%BB%E5%AF%BE%E8%A7%92%E7%B7%9A%E3%81%AB%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%81%8C%E3%81%82%E3%82%8B%E3%81%8B%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20diags2%20%3D%20%5BFalse%5D%20%2A%20%282%20%2A%20n%20-%201%29%20%20%23%20%E5%89%AF%E5%AF%BE%E8%A7%92%E7%B7%9A%E3%81%AB%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%81%8C%E3%81%82%E3%82%8B%E3%81%8B%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%280%2C%20n%2C%20state%2C%20res%2C%20cols%2C%20diags1%2C%20diags2%29%0A%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20res%20%3D%20n_queens%28n%29%0A%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E3%81%95%E3%82%8C%E3%81%9F%E7%9B%A4%E9%9D%A2%E3%81%AE%E7%B8%A6%E6%A8%AA%E3%81%AE%E9%95%B7%E3%81%95%E3%81%AF%20%7Bn%7D%20%E3%81%A7%E3%81%99%22%29%0A%20%20%20%20print%28f%22%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%81%AE%E9%85%8D%E7%BD%AE%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Blen%28res%29%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29%0A%20%20%20%20for%20state%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%22--------------------%22%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20state%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28row%29&cumulative=false&curInstr=61&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_backtracking/permutations_i.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%EF%BC%9A%E9%A0%86%E5%88%97%20I%22%22%22%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E3%81%AE%E9%95%B7%E3%81%95%E3%81%8C%E8%A6%81%E7%B4%A0%E6%95%B0%E3%81%AB%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%EF%BC%9A%E8%A6%81%E7%B4%A0%E3%81%AE%E9%87%8D%E8%A4%87%E9%81%B8%E6%8A%9E%E3%82%92%E8%A8%B1%E5%8F%AF%E3%81%97%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E8%A1%8C%3A%20%E9%81%B8%E6%8A%9E%E3%82%92%E8%A1%8C%E3%81%84%E3%80%81%E7%8A%B6%E6%85%8B%E3%82%92%E6%9B%B4%E6%96%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%AC%A1%E3%81%AE%E9%81%B8%E6%8A%9E%E3%81%B8%E9%80%B2%E3%82%80%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E5%8F%96%E3%82%8A%E6%B6%88%E3%81%97%E3%80%81%E5%89%8D%E3%81%AE%E7%8A%B6%E6%85%8B%E3%81%AB%E6%88%BB%E3%81%99%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_i%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E9%A0%86%E5%88%97%20I%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%202%2C%203%5D%0A%0A%20%20%20%20res%20%3D%20permutations_i%28nums%29%0A%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%A0%86%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_backtracking/permutations_ii.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%EF%BC%9A%E9%A0%86%E5%88%97%20II%22%22%22%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E3%81%AE%E9%95%B7%E3%81%95%E3%81%8C%E8%A6%81%E7%B4%A0%E6%95%B0%E3%81%AB%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20duplicated%20%3D%20set%5Bint%5D%28%29%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%EF%BC%9A%E8%A6%81%E7%B4%A0%E3%81%AE%E9%87%8D%E8%A4%87%E9%81%B8%E6%8A%9E%E3%82%92%E8%A8%B1%E5%8F%AF%E3%81%9B%E3%81%9A%E3%80%81%E5%90%8C%E5%80%A4%E8%A6%81%E7%B4%A0%E3%81%AE%E9%87%8D%E8%A4%87%E9%81%B8%E6%8A%9E%E3%82%82%E8%A8%B1%E5%8F%AF%E3%81%97%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%20and%20choice%20not%20in%20duplicated%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E8%A1%8C%3A%20%E9%81%B8%E6%8A%9E%E3%82%92%E8%A1%8C%E3%81%84%E3%80%81%E7%8A%B6%E6%85%8B%E3%82%92%E6%9B%B4%E6%96%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20duplicated.add%28choice%29%20%20%23%20%E9%81%B8%E6%8A%9E%E6%B8%88%E3%81%BF%E3%81%AE%E8%A6%81%E7%B4%A0%E5%80%A4%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%AC%A1%E3%81%AE%E9%81%B8%E6%8A%9E%E3%81%B8%E9%80%B2%E3%82%80%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E5%8F%96%E3%82%8A%E6%B6%88%E3%81%97%E3%80%81%E5%89%8D%E3%81%AE%E7%8A%B6%E6%85%8B%E3%81%AB%E6%88%BB%E3%81%99%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_ii%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E9%A0%86%E5%88%97%20II%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%202%2C%202%5D%0A%0A%20%20%20%20res%20%3D%20permutations_ii%28nums%29%0A%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%A0%86%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%3A%20%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E6%B7%BB%E5%AD%97%E3%81%8C%E9%85%8D%E5%88%97%E9%95%B7%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%8B%E3%80%81%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%81%8C%20None%20%E3%81%AA%E3%82%89%E3%80%81None%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E9%A0%86%E8%B5%B0%E6%9F%BB%EF%BC%9A%E4%BE%8B%E9%A1%8C%201%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20res.append%28root%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%85%88%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E5%80%A4%E3%81%8C%207%20%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E3%81%99%E3%81%B9%E3%81%A6%E5%87%BA%E5%8A%9B%22%29%0A%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20res%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%3A%20%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E6%B7%BB%E5%AD%97%E3%81%8C%E9%85%8D%E5%88%97%E9%95%B7%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%8B%E3%80%81%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%81%8C%20None%20%E3%81%AA%E3%82%89%E3%80%81None%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E9%A0%86%E8%B5%B0%E6%9F%BB%EF%BC%9A%E4%BE%8B%E9%A1%8C%202%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A9%A6%E3%81%99%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%0A%20%20%20%20path.pop%28%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%85%88%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%81%8B%E3%82%89%E3%83%8E%E3%83%BC%E3%83%89%207%20%E3%81%BE%E3%81%A7%E3%81%AE%E7%B5%8C%E8%B7%AF%E3%82%92%E3%81%99%E3%81%B9%E3%81%A6%E5%87%BA%E5%8A%9B%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%3A%20%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E6%B7%BB%E5%AD%97%E3%81%8C%E9%85%8D%E5%88%97%E9%95%B7%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%8B%E3%80%81%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%81%8C%20None%20%E3%81%AA%E3%82%89%E3%80%81None%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E9%A0%86%E8%B5%B0%E6%9F%BB%EF%BC%9A%E4%BE%8B%E9%A1%8C%203%22%22%22%0A%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%0A%20%20%20%20if%20root%20is%20None%20or%20root.val%20%3D%3D%203%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A9%A6%E3%81%99%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%0A%20%20%20%20path.pop%28%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%85%88%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%81%8B%E3%82%89%E3%83%8E%E3%83%BC%E3%83%89%207%20%E3%81%BE%E3%81%A7%E3%81%AE%E7%B5%8C%E8%B7%AF%E3%82%92%E3%81%99%E3%81%B9%E3%81%A6%E5%87%BA%E5%8A%9B%E3%81%97%E3%80%81%E7%B5%8C%E8%B7%AF%E3%81%AB%E3%81%AF%E5%80%A4%E3%81%8C%203%20%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%90%AB%E3%82%81%E3%81%AA%E3%81%84%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%3D0%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0Adef%20is_solution%28state%3A%20list%5BTreeNode%5D%29%20-%3E%20bool%3A%0A%20%20%20%20return%20state%20and%20state%5B-1%5D.val%20%3D%3D%207%0A%0Adef%20record_solution%28state%3A%20list%5BTreeNode%5D%2C%20res%3A%20list%5Blist%5BTreeNode%5D%5D%29%3A%0A%20%20%20%20res.append%28list%28state%29%29%0A%0Adef%20is_valid%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%20-%3E%20bool%3A%0A%20%20%20%20return%20choice%20is%20not%20None%20and%20choice.val%20%21%3D%203%0A%0Adef%20make_choice%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20state.append%28choice%29%0A%0Adef%20undo_choice%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20state.pop%28%29%0A%0Adef%20backtrack%28state%3A%20list%5BTreeNode%5D%2C%20choices%3A%20list%5BTreeNode%5D%2C%20res%3A%20list%5Blist%5BTreeNode%5D%5D%29%3A%0A%20%20%20%20if%20is_solution%28state%29%3A%0A%20%20%20%20%20%20%20%20record_solution%28state%2C%20res%29%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20if%20is_valid%28state%2C%20choice%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20make_choice%28state%2C%20choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20%5Bchoice.left%2C%20choice.right%5D%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20undo_choice%28state%2C%20choice%29%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3D%5Broot%5D%2C%20res%3Dres%29%0A%20%20%20%20print%28%27%5Cn%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E7%B5%8C%E8%B7%AF%E3%82%92%E5%87%BA%E5%8A%9B%27%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=138&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_backtracking/subset_sum_i.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20choices%3A%20list%5Bint%5D%2C%20start%3A%20int%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%EF%BC%9A%E9%83%A8%E5%88%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%8C%20target%20%E3%81%AB%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%202%3A%20start%20%E3%81%8B%E3%82%89%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E9%87%8D%E8%A4%87%E3%81%99%E3%82%8B%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E7%94%9F%E6%88%90%E3%82%92%E9%81%BF%E3%81%91%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28start%2C%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A1%EF%BC%9A%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%8C%20target%20%E3%82%92%E8%B6%85%E3%81%88%E3%81%9F%E3%82%89%E3%80%81%E7%9B%B4%E3%81%A1%E3%81%AB%E3%83%AB%E3%83%BC%E3%83%97%E3%82%92%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%81%AF%E3%82%BD%E3%83%BC%E3%83%88%E6%B8%88%E3%81%BF%E3%81%A7%E5%BE%8C%E7%B6%9A%E8%A6%81%E7%B4%A0%E3%81%AE%E3%81%BB%E3%81%86%E3%81%8C%E5%A4%A7%E3%81%8D%E3%81%8F%E3%80%81%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%AF%E5%BF%85%E3%81%9A%20target%20%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%9F%E3%82%81%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E3%81%99%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E8%A1%8C%E3%81%84%E3%80%81target%20%E3%81%A8%20start%20%E3%82%92%E6%9B%B4%E6%96%B0%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E6%AC%A1%E3%81%AE%E9%81%B8%E6%8A%9E%E3%81%B8%E9%80%B2%E3%82%80%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%20-%20choices%5Bi%5D%2C%20choices%2C%20i%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E5%8F%96%E3%82%8A%E6%B6%88%E3%81%97%E3%80%81%E5%89%8D%E3%81%AE%E7%8A%B6%E6%85%8B%E3%81%AB%E6%88%BB%E3%81%99%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E9%83%A8%E5%88%86%E5%92%8C%20I%20%E3%82%92%E8%A7%A3%E3%81%8F%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%85%8B%EF%BC%88%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20nums%20%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%0A%20%20%20%20start%20%3D%200%20%20%23%20%E9%96%8B%E5%A7%8B%E7%82%B9%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%B5%90%E6%9E%9C%E3%83%AA%E3%82%B9%E3%83%88%EF%BC%88%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%EF%BC%89%0A%20%20%20%20backtrack%28state%2C%20target%2C%20nums%2C%20start%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E5%92%8C%E3%81%8C%20%7Btarget%7D%20%E3%81%AB%E7%AD%89%E3%81%97%E3%81%84%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%0A%20%20%20%20target%3A%20int%2C%0A%20%20%20%20total%3A%20int%2C%0A%20%20%20%20choices%3A%20list%5Bint%5D%2C%0A%20%20%20%20res%3A%20list%5Blist%5Bint%5D%5D%2C%0A%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%EF%BC%9A%E9%83%A8%E5%88%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%8C%20target%20%E3%81%AB%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20if%20total%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20i%20in%20range%28len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%EF%BC%9A%E9%83%A8%E5%88%86%E5%92%8C%E3%81%8C%20target%20%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AF%E3%81%9D%E3%81%AE%E9%81%B8%E6%8A%9E%E3%82%92%E3%82%B9%E3%82%AD%E3%83%83%E3%83%97%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20total%20%2B%20choices%5Bi%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E8%A1%8C%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E8%A1%8C%E3%81%84%E3%80%81%E8%A6%81%E7%B4%A0%E3%81%A8%20total%20%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E6%AC%A1%E3%81%AE%E9%81%B8%E6%8A%9E%E3%81%B8%E9%80%B2%E3%82%80%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%2C%20total%20%2B%20choices%5Bi%5D%2C%20choices%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E5%8F%96%E3%82%8A%E6%B6%88%E3%81%97%E3%80%81%E5%89%8D%E3%81%AE%E7%8A%B6%E6%85%8B%E3%81%AB%E6%88%BB%E3%81%99%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i_naive%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E9%83%A8%E5%88%86%E5%92%8C%20I%20%E3%82%92%E8%A7%A3%E3%81%8F%EF%BC%88%E9%87%8D%E8%A4%87%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%82%92%E5%90%AB%E3%82%80%EF%BC%89%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%85%8B%EF%BC%88%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%EF%BC%89%0A%20%20%20%20total%20%3D%200%20%20%23%20%E9%83%A8%E5%88%86%E5%92%8C%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%B5%90%E6%9E%9C%E3%83%AA%E3%82%B9%E3%83%88%EF%BC%88%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%EF%BC%89%0A%20%20%20%20backtrack%28state%2C%20target%2C%20total%2C%20nums%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i_naive%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E5%92%8C%E3%81%8C%20%7Btarget%7D%20%E3%81%AB%E7%AD%89%E3%81%97%E3%81%84%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%20res%20%3D%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E6%B3%A8%E6%84%8F%3A%20%E3%81%93%E3%81%AE%E6%96%B9%E6%B3%95%E3%81%AE%E5%87%BA%E5%8A%9B%E7%B5%90%E6%9E%9C%E3%81%AB%E3%81%AF%E9%87%8D%E8%A4%87%E3%81%99%E3%82%8B%E9%9B%86%E5%90%88%E3%81%8C%E5%90%AB%E3%81%BE%E3%82%8C%E3%81%BE%E3%81%99%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_backtracking/subset_sum_ii.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20choices%3A%20list%5Bint%5D%2C%20start%3A%20int%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%EF%BC%9A%E9%83%A8%E5%88%86%E5%92%8C%20II%22%22%22%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%8C%20target%20%E3%81%AB%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%202%3A%20start%20%E3%81%8B%E3%82%89%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E9%87%8D%E8%A4%87%E3%81%99%E3%82%8B%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E7%94%9F%E6%88%90%E3%82%92%E9%81%BF%E3%81%91%E3%82%8B%0A%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%203%3A%20start%20%E3%81%8B%E3%82%89%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E5%90%8C%E3%81%98%E8%A6%81%E7%B4%A0%E3%81%AE%E9%87%8D%E8%A4%87%E9%81%B8%E6%8A%9E%E3%82%92%E9%81%BF%E3%81%91%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28start%2C%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A1%EF%BC%9A%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%8C%20target%20%E3%82%92%E8%B6%85%E3%81%88%E3%81%9F%E3%82%89%E3%80%81%E7%9B%B4%E3%81%A1%E3%81%AB%E3%83%AB%E3%83%BC%E3%83%97%E3%82%92%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%81%AF%E3%82%BD%E3%83%BC%E3%83%88%E6%B8%88%E3%81%BF%E3%81%A7%E5%BE%8C%E7%B6%9A%E8%A6%81%E7%B4%A0%E3%81%AE%E3%81%BB%E3%81%86%E3%81%8C%E5%A4%A7%E3%81%8D%E3%81%8F%E3%80%81%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%AF%E5%BF%85%E3%81%9A%20target%20%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%9F%E3%82%81%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A4%EF%BC%9A%E3%81%93%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%8C%E5%B7%A6%E9%9A%A3%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%A8%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E3%81%9D%E3%81%AE%E6%8E%A2%E7%B4%A2%E5%88%86%E5%B2%90%E3%81%AF%E9%87%8D%E8%A4%87%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E3%81%9F%E3%82%81%E3%82%B9%E3%82%AD%E3%83%83%E3%83%97%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20i%20%3E%20start%20and%20choices%5Bi%5D%20%3D%3D%20choices%5Bi%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E3%81%99%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E8%A1%8C%E3%81%84%E3%80%81target%20%E3%81%A8%20start%20%E3%82%92%E6%9B%B4%E6%96%B0%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E6%AC%A1%E3%81%AE%E9%81%B8%E6%8A%9E%E3%81%B8%E9%80%B2%E3%82%80%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%20-%20choices%5Bi%5D%2C%20choices%2C%20i%20%2B%201%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E5%8F%96%E3%82%8A%E6%B6%88%E3%81%97%E3%80%81%E5%89%8D%E3%81%AE%E7%8A%B6%E6%85%8B%E3%81%AB%E6%88%BB%E3%81%99%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_ii%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E9%83%A8%E5%88%86%E5%92%8C%20II%20%E3%82%92%E8%A7%A3%E3%81%8F%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%85%8B%EF%BC%88%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20nums%20%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%0A%20%20%20%20start%20%3D%200%20%20%23%20%E9%96%8B%E5%A7%8B%E7%82%B9%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%B5%90%E6%9E%9C%E3%83%AA%E3%82%B9%E3%83%88%EF%BC%88%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%EF%BC%89%0A%20%20%20%20backtrack%28state%2C%20target%2C%20nums%2C%20start%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_ii%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E5%92%8C%E3%81%8C%20%7Btarget%7D%20%E3%81%AB%E7%AD%89%E3%81%97%E3%81%84%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_computational_complexity/iteration.md ================================================ https://pythontutor.com/render.html#code=def%20for_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22for%20%E3%83%AB%E3%83%BC%E3%83%97%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%201%2C%202%2C%20...%2C%20n-1%2C%20n%20%E3%82%92%E9%A0%86%E3%81%AB%E5%8A%A0%E7%AE%97%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnfor%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D& https://pythontutor.com/render.html#code=def%20while_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E3%83%AB%E3%83%BC%E3%83%97%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E6%9D%A1%E4%BB%B6%E5%A4%89%E6%95%B0%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%23%201%2C%202%2C%20...%2C%20n-1%2C%20n%20%E3%82%92%E9%A0%86%E3%81%AB%E5%8A%A0%E7%AE%97%E3%81%99%E3%82%8B%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E6%9D%A1%E4%BB%B6%E5%A4%89%E6%95%B0%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20while_loop_ii%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%882%E5%9B%9E%E6%9B%B4%E6%96%B0%EF%BC%89%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E6%9D%A1%E4%BB%B6%E5%A4%89%E6%95%B0%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%23%201%2C%204%2C%2010%2C%20...%20%E3%82%92%E9%A0%86%E3%81%AB%E5%8A%A0%E7%AE%97%E3%81%99%E3%82%8B%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20%23%20%E6%9D%A1%E4%BB%B6%E5%A4%89%E6%95%B0%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20i%20%2A%3D%202%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop_ii%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%882%20%E5%9B%9E%E6%9B%B4%E6%96%B0%EF%BC%89%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20nested_for_loop%28n%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E9%87%8D%20for%20%E3%83%AB%E3%83%BC%E3%83%97%22%22%22%0A%20%20%20%20res%20%3D%20%22%22%0A%20%20%20%20%23%20i%20%3D%201%2C%202%2C%20...%2C%20n-1%2C%20n%20%E3%81%A8%E3%83%AB%E3%83%BC%E3%83%97%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20j%20%3D%201%2C%202%2C%20...%2C%20n-1%2C%20n%20%E3%81%A8%E3%83%AB%E3%83%BC%E3%83%97%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20f%22%28%7Bi%7D%2C%20%7Bj%7D%29%2C%20%22%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20nested_for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cn%E4%BA%8C%E9%87%8D%20for%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E8%B5%B0%E6%9F%BB%E7%B5%90%E6%9E%9C%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E5%86%8D%E5%B8%B0%EF%BC%9A%E5%86%8D%E5%B8%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E5%B8%B0%E3%82%8A%E3%81%8C%E3%81%91%EF%BC%9A%E7%B5%90%E6%9E%9C%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20n%20%2B%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E5%86%8D%E5%B8%B0%E9%96%A2%E6%95%B0%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20tail_recur%28n%2C%20res%29%3A%0A%20%20%20%20%22%22%22%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%0A%20%20%20%20return%20tail_recur%28n%20-%201%2C%20res%20%2B%20n%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n%2C%200%29%0A%20%20%20%20print%28f%22%5Cn%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E9%96%A2%E6%95%B0%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0%E5%88%97%EF%BC%9A%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%20f%281%29%20%3D%200%2C%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%20%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%20f%28n%29%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0%E5%88%97%E3%81%AE%E7%AC%AC%20%7Bn%7D%20%E9%A0%85%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%8F%8D%E5%BE%A9%E3%81%A7%E5%86%8D%E5%B8%B0%E3%82%92%E6%A8%A1%E6%93%AC%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20%23%20%E6%98%8E%E7%A4%BA%E7%9A%84%E3%81%AA%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%B3%E3%83%BC%E3%83%AB%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%92%E6%A8%A1%E6%93%AC%E3%81%99%E3%82%8B%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E5%86%8D%E5%B8%B0%EF%BC%9A%E5%86%8D%E5%B8%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%0A%20%20%20%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%80%8C%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%B8%E3%81%AE%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E3%80%8D%E3%81%A7%E3%80%8C%E5%86%8D%E5%B8%B0%E3%80%8D%E3%82%92%E6%A8%A1%E6%93%AC%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E5%B8%B0%E3%82%8A%E3%81%8C%E3%81%91%EF%BC%9A%E7%B5%90%E6%9E%9C%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%80%8C%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8B%E3%82%89%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%99%E6%93%8D%E4%BD%9C%E3%80%8D%E3%81%A7%E3%80%8C%E5%B8%B0%E3%82%8A%E3%80%8D%E3%82%92%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%83%88%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E5%8F%8D%E5%BE%A9%E3%81%A7%E5%86%8D%E5%B8%B0%E3%82%92%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%83%88%E3%81%97%E3%81%9F%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_computational_complexity/recursion.md ================================================ https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E5%86%8D%E5%B8%B0%EF%BC%9A%E5%86%8D%E5%B8%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E5%B8%B0%E3%82%8A%E3%81%8C%E3%81%91%EF%BC%9A%E7%B5%90%E6%9E%9C%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20n%20%2B%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E5%86%8D%E5%B8%B0%E9%96%A2%E6%95%B0%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20tail_recur%28n%2C%20res%29%3A%0A%20%20%20%20%22%22%22%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%0A%20%20%20%20return%20tail_recur%28n%20-%201%2C%20res%20%2B%20n%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n%2C%200%29%0A%20%20%20%20print%28f%22%5Cn%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E9%96%A2%E6%95%B0%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0%E5%88%97%EF%BC%9A%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%20f%281%29%20%3D%200%2C%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%20%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%20f%28n%29%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0%E5%88%97%E3%81%AE%E7%AC%AC%20%7Bn%7D%20%E9%A0%85%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%8F%8D%E5%BE%A9%E3%81%A7%E5%86%8D%E5%B8%B0%E3%82%92%E6%A8%A1%E6%93%AC%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20%23%20%E6%98%8E%E7%A4%BA%E7%9A%84%E3%81%AA%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%B3%E3%83%BC%E3%83%AB%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%92%E6%A8%A1%E6%93%AC%E3%81%99%E3%82%8B%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E5%86%8D%E5%B8%B0%EF%BC%9A%E5%86%8D%E5%B8%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%0A%20%20%20%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%80%8C%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%B8%E3%81%AE%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E3%80%8D%E3%81%A7%E3%80%8C%E5%86%8D%E5%B8%B0%E3%80%8D%E3%82%92%E6%A8%A1%E6%93%AC%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E5%B8%B0%E3%82%8A%E3%81%8C%E3%81%91%EF%BC%9A%E7%B5%90%E6%9E%9C%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%80%8C%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8B%E3%82%89%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%99%E6%93%8D%E4%BD%9C%E3%80%8D%E3%81%A7%E3%80%8C%E5%B8%B0%E3%82%8A%E3%80%8D%E3%82%92%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%83%88%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E5%8F%8D%E5%BE%A9%E3%81%A7%E5%86%8D%E5%B8%B0%E3%82%92%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%83%88%E3%81%97%E3%81%9F%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_computational_complexity/space_complexity.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B6%9A%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20function%28%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%96%A2%E6%95%B0%22%22%22%0A%20%20%20%20%23%20%E4%BD%95%E3%82%89%E3%81%8B%E3%81%AE%E5%87%A6%E7%90%86%E3%82%92%E8%A1%8C%E3%81%86%0A%20%20%20%20return%200%0A%0Adef%20constant%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%AE%9A%E6%95%B0%E9%9A%8E%22%22%22%0A%20%20%20%20%23%20%E5%AE%9A%E6%95%B0%E3%80%81%E5%A4%89%E6%95%B0%E3%80%81%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AF%20O%281%29%20%E3%81%AE%E7%A9%BA%E9%96%93%E3%82%92%E5%8D%A0%E3%82%81%E3%82%8B%0A%20%20%20%20a%20%3D%200%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%2010%0A%20%20%20%20node%20%3D%20ListNode%280%29%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E5%86%85%E3%81%AE%E5%A4%89%E6%95%B0%E3%81%AF%20O%281%29%20%E3%81%AE%E7%A9%BA%E9%96%93%E3%82%92%E5%8D%A0%E3%82%81%E3%82%8B%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20c%20%3D%200%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E5%86%85%E3%81%AE%E9%96%A2%E6%95%B0%E3%81%AF%20O%281%29%20%E3%81%AE%E7%A9%BA%E9%96%93%E3%82%92%E5%8D%A0%E3%82%81%E3%82%8B%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20function%28%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E5%AE%9A%E6%95%B0%E9%9A%8E%0A%20%20%20%20constant%28n%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E5%BD%A2%E9%9A%8E%22%22%22%0A%20%20%20%20%23%20%E9%95%B7%E3%81%95%20n%20%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AF%20O%28n%29%20%E3%81%AE%E7%A9%BA%E9%96%93%E3%82%92%E4%BD%BF%E7%94%A8%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20%23%20%E9%95%B7%E3%81%95%20n%20%E3%81%AE%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%AF%20O%28n%29%20%E3%81%AE%E7%A9%BA%E9%96%93%E3%82%92%E4%BD%BF%E7%94%A8%0A%20%20%20%20hmap%20%3D%20dict%5Bint%2C%20str%5D%28%29%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20hmap%5Bi%5D%20%3D%20str%28i%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E7%B7%9A%E5%BD%A2%E9%9A%8E%0A%20%20%20%20linear%28n%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear_recur%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E5%BD%A2%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20print%28%22%E5%86%8D%E5%B8%B0%20n%20%3D%22%2C%20n%29%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20linear_recur%28n%20-%201%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E7%B7%9A%E5%BD%A2%E9%9A%8E%0A%20%20%20%20linear_recur%28n%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20quadratic%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E4%B9%97%E9%9A%8E%22%22%22%0A%20%20%20%20%23%20%E4%BA%8C%E6%AC%A1%E5%85%83%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AF%20O%28n%5E2%29%20%E3%81%AE%E7%A9%BA%E9%96%93%E3%82%92%E4%BD%BF%E7%94%A8%0A%20%20%20%20num_matrix%20%3D%20%5B%5B0%5D%20%2A%20n%20for%20_%20in%20range%28n%29%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E4%BA%8C%E4%B9%97%E9%9A%8E%0A%20%20%20%20quadratic%28n%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20quadratic_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E6%AC%A1%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%20nums%20%E3%81%AE%E9%95%B7%E3%81%95%E3%81%AF%20n%2C%20n-1%2C%20...%2C%202%2C%201%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20return%20quadratic_recur%28n%20-%201%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E4%BA%8C%E4%B9%97%E9%9A%8E%0A%20%20%20%20quadratic_recur%28n%29&cumulative=false&curInstr=28&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20build_tree%28n%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AE%E6%A7%8B%E7%AF%89%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%280%29%0A%20%20%20%20root.left%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20root.right%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20return%20root%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E6%8C%87%E6%95%B0%E3%82%AA%E3%83%BC%E3%83%80%E3%83%BC%0A%20%20%20%20root%20%3D%20build_tree%28n%29&cumulative=false&curInstr=507&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_computational_complexity/time_complexity.md ================================================ https://pythontutor.com/render.html#code=def%20constant%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%9A%E6%95%B0%E9%9A%8E%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20size%20%3D%2010%0A%20%20%20%20for%20_%20in%20range%28size%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20constant%28n%29%0A%20%20%20%20print%28%22%E5%AE%9A%E6%95%B0%E6%99%82%E9%96%93%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E5%BD%A2%E9%9A%8E%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20linear%28n%29%0A%20%20%20%20print%28%22%E7%B7%9A%E5%BD%A2%E6%99%82%E9%96%93%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20array_traversal%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E5%BD%A2%E6%99%82%E9%96%93%EF%BC%88%E9%85%8D%E5%88%97%E3%82%92%E8%B5%B0%E6%9F%BB%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E5%9B%9E%E6%95%B0%E3%81%AF%E9%85%8D%E5%88%97%E9%95%B7%E3%81%AB%E6%AF%94%E4%BE%8B%E3%81%99%E3%82%8B%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20array_traversal%28%5B0%5D%20%2A%20n%29%0A%20%20%20%20print%28%22%E7%B7%9A%E5%BD%A2%E6%99%82%E9%96%93%EF%BC%88%E9%85%8D%E5%88%97%E8%B5%B0%E6%9F%BB%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20quadratic%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E4%B9%97%E9%9A%8E%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E5%9B%9E%E6%95%B0%E3%81%AF%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%E3%81%AE%E4%BA%8C%E4%B9%97%E3%81%AB%E6%AF%94%E4%BE%8B%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20quadratic%28n%29%0A%20%20%20%20print%28%22%E4%BA%8C%E4%B9%97%E6%99%82%E9%96%93%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E6%AC%A1%E6%99%82%E9%96%93%EF%BC%88%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%20%20%23%20%E3%82%AB%E3%82%A6%E3%83%B3%E3%82%BF%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%20%5B0%2C%20i%5D%20%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%8F%B3%E7%AB%AF%E3%81%B8%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20nums%5Bj%5D%20%E3%81%A8%20nums%5Bj%20%2B%201%5D%20%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%20%3D%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%203%20%20%23%20%E8%A6%81%E7%B4%A0%E4%BA%A4%E6%8F%9B%E3%81%AB%E3%81%AF%203%20%E5%9B%9E%E3%81%AE%E5%8D%98%E4%BD%8D%E6%93%8D%E4%BD%9C%E3%81%8C%E5%90%AB%E3%81%BE%E3%82%8C%E3%82%8B%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%5D%20%20%23%20%5Bn%2C%20n-1%2C%20...%2C%202%2C%201%5D%0A%20%20%20%20count%20%3D%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E4%BA%8C%E4%B9%97%E6%99%82%E9%96%93%EF%BC%88%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20exponential%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E3%83%AB%E3%83%BC%E3%83%97%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20base%20%3D%201%0A%20%20%20%20%23%20%E7%B4%B0%E8%83%9E%E3%81%AF%E5%90%84%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89%E3%81%A7%202%20%E3%81%A4%E3%81%AB%E5%88%86%E8%A3%82%E3%81%97%E3%80%81%E6%95%B0%E5%88%97%201%2C%202%2C%204%2C%208%2C%20...%2C%202%5E%28n-1%29%20%E3%82%92%E5%BD%A2%E6%88%90%E3%81%99%E3%82%8B%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28base%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%20%20%20%20base%20%2A%3D%202%0A%20%20%20%20%23%20count%20%3D%201%20%2B%202%20%2B%204%20%2B%208%20%2B%20..%20%2B%202%5E%28n-1%29%20%3D%202%5En%20-%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20exponential%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E3%83%AB%E3%83%BC%E3%83%97%E5%AE%9F%E8%A3%85%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20exp_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20return%20exp_recur%28n%20-%201%29%20%2B%20exp_recur%28n%20-%201%29%20%2B%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%207%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20exp_recur%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20logarithmic%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AF%BE%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E3%83%AB%E3%83%BC%E3%83%97%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20while%20n%20%3E%201%3A%0A%20%20%20%20%20%20%20%20n%20%3D%20n%20%2F%202%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20logarithmic%28n%29%0A%20%20%20%20print%28%22%E5%AF%BE%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E3%83%AB%E3%83%BC%E3%83%97%E5%AE%9F%E8%A3%85%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AF%BE%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20return%20log_recur%28n%20%2F%202%29%20%2B%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20log_recur%28n%29%0A%20%20%20%20print%28%22%E5%AF%BE%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear_log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E5%BD%A2%E5%AF%BE%E6%95%B0%E6%99%82%E9%96%93%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%20%2F%2F%202%29%20%2B%20linear_log_recur%28n%20%2F%2F%202%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%29%0A%20%20%20%20print%28%22%E7%B7%9A%E5%BD%A2%E5%AF%BE%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20factorial_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E4%B9%97%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%201%E5%80%8B%E3%81%8B%E3%82%89%20n%20%E5%80%8B%E3%81%AB%E5%88%86%E8%A3%82%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20factorial_recur%28n%20-%201%29%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20factorial_recur%28n%29%0A%20%20%20%20print%28%22%E9%9A%8E%E4%B9%97%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md ================================================ https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_numbers%28n%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E8%A6%81%E7%B4%A0%E3%81%8C%201%2C%202%2C%20...%2C%20n%20%E3%81%A7%E9%A0%86%E5%BA%8F%E3%81%8C%E3%82%B7%E3%83%A3%E3%83%83%E3%83%95%E3%83%AB%E3%81%95%E3%82%8C%E3%81%9F%E9%85%8D%E5%88%97%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%20nums%20%3D%3A%201%2C%202%2C%203%2C%20...%2C%20n%20%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%81%AB%E3%82%B7%E3%83%A3%E3%83%83%E3%83%95%E3%83%AB%0A%20%20%20%20random.shuffle%28nums%29%0A%20%20%20%20return%20nums%0A%0Adef%20find_one%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%85%8D%E5%88%97%20nums%20%E5%86%85%E3%81%A7%E6%95%B0%E5%80%A4%201%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E6%8E%A2%E3%81%99%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%201%20%E3%81%8C%E9%85%8D%E5%88%97%E3%81%AE%E5%85%88%E9%A0%AD%E3%81%AB%E3%81%82%E3%82%8B%E3%81%A8%E3%81%8D%E3%80%81%E6%9C%80%E8%89%AF%E6%99%82%E9%96%93%E8%A8%88%E7%AE%97%E9%87%8F%20O%281%29%20%E3%81%A8%E3%81%AA%E3%82%8B%0A%20%20%20%20%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%201%20%E3%81%8C%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%AB%E5%B0%BE%E3%81%AB%E3%81%82%E3%82%8B%E3%81%A8%E3%81%8D%E3%80%81%E6%9C%80%E6%82%AA%E6%99%82%E9%96%93%E8%A8%88%E7%AE%97%E9%87%8F%20O%28n%29%20%E3%81%A8%E3%81%AA%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2010%0A%20%20%20%20nums%20%3D%20random_numbers%28n%29%0A%20%20%20%20index%20%3D%20find_one%28nums%29%0A%20%20%20%20print%28%22%5Cn%E9%85%8D%E5%88%97%20%5B%201%2C%202%2C%20...%2C%20n%20%5D%20%E3%82%92%E3%82%B7%E3%83%A3%E3%83%83%E3%83%95%E3%83%AB%E3%81%99%E3%82%8B%E3%81%A8%20%3D%22%2C%20nums%29%0A%20%20%20%20print%28%22%E6%95%B0%E5%80%A4%201%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AF%22%2C%20index%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md ================================================ https://pythontutor.com/render.html#code=def%20dfs%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20i%3A%20int%2C%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%EF%BC%9A%E5%95%8F%E9%A1%8C%20f%28i%2C%20j%29%22%22%22%0A%20%20%20%20%23%20%E5%8C%BA%E9%96%93%E3%81%8C%E7%A9%BA%E3%81%AA%E3%82%89%E5%AF%BE%E8%B1%A1%E8%A6%81%E7%B4%A0%E3%81%AF%E5%AD%98%E5%9C%A8%E3%81%97%E3%81%AA%E3%81%84%E3%81%AE%E3%81%A7%20-1%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3E%20j%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%0A%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%20f%28m%2B1%2C%20j%29%20%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E8%A7%A3%E3%81%8F%0A%20%20%20%20%20%20%20%20return%20dfs%28nums%2C%20target%2C%20m%20%2B%201%2C%20j%29%0A%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%20f%28i%2C%20m-1%29%20%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E8%A7%A3%E3%81%8F%0A%20%20%20%20%20%20%20%20return%20dfs%28nums%2C%20target%2C%20i%2C%20m%20-%201%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E8%A6%81%E7%B4%A0%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%81%9D%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20%20%20%20%20return%20m%0A%0Adef%20binary_search%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%95%8F%E9%A1%8C%20f%280%2C%20n-1%29%20%E3%82%92%E8%A7%A3%E3%81%8F%0A%20%20%20%20return%20dfs%28nums%2C%20target%2C%200%2C%20n%20-%201%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%EF%BC%88%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums%2C%20target%29%0A%20%20%20%20print%28%22%E5%AF%BE%E8%B1%A1%E8%A6%81%E7%B4%A0%206%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_divide_and_conquer/build_tree.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20dfs%28%0A%20%20%20%20preorder%3A%20list%5Bint%5D%2C%0A%20%20%20%20inorder_map%3A%20dict%5Bint%2C%20int%5D%2C%0A%20%20%20%20i%3A%20int%2C%0A%20%20%20%20l%3A%20int%2C%0A%20%20%20%20r%3A%20int%2C%0A%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%82%92%E6%A7%8B%E7%AF%89%EF%BC%9A%E5%88%86%E5%89%B2%E7%B5%B1%E6%B2%BB%22%22%22%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E6%9C%A8%E5%8C%BA%E9%96%93%E3%81%8C%E7%A9%BA%E3%81%AA%E3%82%89%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%0A%20%20%20%20if%20r%20-%20l%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20TreeNode%28preorder%5Bi%5D%29%0A%20%20%20%20%23%20m%20%E3%82%92%E6%B1%82%E3%82%81%E3%81%A6%E5%B7%A6%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E5%88%86%E5%89%B2%E3%81%99%E3%82%8B%0A%20%20%20%20m%20%3D%20inorder_map%5Bpreorder%5Bi%5D%5D%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%EF%BC%9A%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.left%20%3D%20dfs%28preorder%2C%20inorder_map%2C%20i%20%2B%201%2C%20l%2C%20m%20-%201%29%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%EF%BC%9A%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.right%20%3D%20dfs%28preorder%2C%20inorder_map%2C%20i%20%2B%201%20%2B%20m%20-%20l%2C%20m%20%2B%201%2C%20r%29%0A%20%20%20%20%23%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20root%0A%0A%0Adef%20build_tree%28preorder%3A%20list%5Bint%5D%2C%20inorder%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%82%92%E6%A7%8B%E7%AF%89%22%22%22%0A%20%20%20%20%23%20inorder%20%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%8B%E3%82%89%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%B8%E3%81%AE%E5%AF%BE%E5%BF%9C%E3%82%92%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20inorder_map%20%3D%20%7Bval%3A%20i%20for%20i%2C%20val%20in%20enumerate%28inorder%29%7D%0A%20%20%20%20root%20%3D%20dfs%28preorder%2C%20inorder_map%2C%200%2C%200%2C%20len%28inorder%29%20-%201%29%0A%20%20%20%20return%20root%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20preorder%20%3D%20%5B3%2C%209%2C%202%2C%201%2C%207%5D%0A%20%20%20%20inorder%20%3D%20%5B9%2C%203%2C%201%2C%202%2C%207%5D%0A%20%20%20%20print%28f%22%E5%89%8D%E9%A0%86%E8%B5%B0%E6%9F%BB%20%3D%20%7Bpreorder%7D%22%29%0A%20%20%20%20print%28f%22%E4%B8%AD%E9%A0%86%E8%B5%B0%E6%9F%BB%20%3D%20%7Binorder%7D%22%29%0A%20%20%20%20root%20%3D%20build_tree%28preorder%2C%20inorder%29&cumulative=false&curInstr=21&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_divide_and_conquer/hanota.md ================================================ https://pythontutor.com/render.html#code=def%20move%28src%3A%20list%5Bint%5D%2C%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%86%86%E7%9B%A4%E3%82%92%201%20%E6%9E%9A%E7%A7%BB%E5%8B%95%22%22%22%0A%20%20%20%20%23%20src%20%E3%81%AE%E4%B8%8A%E3%81%8B%E3%82%89%E5%86%86%E7%9B%A4%E3%82%921%E6%9E%9A%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%99%0A%20%20%20%20pan%20%3D%20src.pop%28%29%0A%20%20%20%20%23%20%E5%86%86%E7%9B%A4%E3%82%92%20tar%20%E3%81%AE%E4%B8%8A%E3%81%AB%E7%BD%AE%E3%81%8F%0A%20%20%20%20tar.append%28pan%29%0A%0A%0Adef%20dfs%28i%3A%20int%2C%20src%3A%20list%5Bint%5D%2C%20buf%3A%20list%5Bint%5D%2C%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E3%83%8F%E3%83%8E%E3%82%A4%E3%81%AE%E5%A1%94%E3%81%AE%E5%95%8F%E9%A1%8C%20f%28i%29%20%E3%82%92%E8%A7%A3%E3%81%8F%22%22%22%0A%20%20%20%20%23%20src%20%E3%81%AB%E5%86%86%E7%9B%A4%E3%81%8C%201%20%E6%9E%9A%E3%81%A0%E3%81%91%E6%AE%8B%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AF%E3%80%81%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%20tar%20%E3%81%B8%E7%A7%BB%E3%81%99%0A%20%20%20%20if%20i%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20move%28src%2C%20tar%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%20f%28i-1%29%EF%BC%9Asrc%20%E3%81%AE%E4%B8%8A%E9%83%A8%20i-1%20%E6%9E%9A%E3%81%AE%E5%86%86%E7%9B%A4%E3%82%92%20tar%20%E3%82%92%E8%A3%9C%E5%8A%A9%E3%81%AB%E3%81%97%E3%81%A6%20buf%20%E3%81%B8%E7%A7%BB%E3%81%99%0A%20%20%20%20dfs%28i%20-%201%2C%20src%2C%20tar%2C%20buf%29%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%20f%281%29%EF%BC%9Asrc%20%E3%81%AB%E6%AE%8B%E3%82%8B%201%20%E6%9E%9A%E3%81%AE%E5%86%86%E7%9B%A4%E3%82%92%20tar%20%E3%81%AB%E7%A7%BB%E3%81%99%0A%20%20%20%20move%28src%2C%20tar%29%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%20f%28i-1%29%EF%BC%9Abuf%20%E3%81%AE%E4%B8%8A%E9%83%A8%20i-1%20%E6%9E%9A%E3%81%AE%E5%86%86%E7%9B%A4%E3%82%92%20src%20%E3%82%92%E8%A3%9C%E5%8A%A9%E3%81%AB%E3%81%97%E3%81%A6%20tar%20%E3%81%B8%E7%A7%BB%E3%81%99%0A%20%20%20%20dfs%28i%20-%201%2C%20buf%2C%20src%2C%20tar%29%0A%0A%0Adef%20solve_hanota%28A%3A%20list%5Bint%5D%2C%20B%3A%20list%5Bint%5D%2C%20C%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E3%83%8F%E3%83%8E%E3%82%A4%E3%81%AE%E5%A1%94%E3%82%92%E8%A7%A3%E3%81%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28A%29%0A%20%20%20%20%23%20A%20%E3%81%AE%E4%B8%8A%E3%81%8B%E3%82%89%20n%20%E6%9E%9A%E3%81%AE%E5%86%86%E7%9B%A4%E3%82%92%20B%20%E3%82%92%E4%BB%8B%E3%81%97%E3%81%A6%20C%20%E3%81%B8%E7%A7%BB%E3%81%99%0A%20%20%20%20dfs%28n%2C%20A%2C%20B%2C%20C%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E3%83%AA%E3%82%B9%E3%83%88%E6%9C%AB%E5%B0%BE%E3%81%8C%E6%9F%B1%E3%81%AE%E9%A0%82%E4%B8%8A%0A%20%20%20%20A%20%3D%20%5B5%2C%204%2C%203%2C%202%2C%201%5D%0A%20%20%20%20B%20%3D%20%5B%5D%0A%20%20%20%20C%20%3D%20%5B%5D%0A%20%20%20%20print%28%22%E5%88%9D%E6%9C%9F%E7%8A%B6%E6%85%8B%3A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29%0A%0A%20%20%20%20solve_hanota%28A%2C%20B%2C%20C%29%0A%0A%20%20%20%20print%28%22%E5%86%86%E7%9B%A4%E3%81%AE%E7%A7%BB%E5%8B%95%E5%AE%8C%E4%BA%86%E5%BE%8C%3A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28choices%3A%20list%5Bint%5D%2C%20state%3A%20int%2C%20n%3A%20int%2C%20res%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%22%22%22%0A%20%20%20%20%23%20%E7%AC%AC%20n%20%E6%AE%B5%E3%81%AB%E5%88%B0%E9%81%94%E3%81%97%E3%81%9F%E3%82%89%E3%80%81%E6%96%B9%E6%B3%95%E6%95%B0%E3%82%92%201%20%E5%A2%97%E3%82%84%E3%81%99%0A%20%20%20%20if%20state%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%5B0%5D%20%2B%3D%201%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%3A%20%E7%AC%AC%20n%20%E6%AE%B5%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20state%20%2B%20choice%20%3E%20n%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E8%A1%8C%3A%20%E9%81%B8%E6%8A%9E%E3%82%92%E8%A1%8C%E3%81%84%E3%80%81%E7%8A%B6%E6%85%8B%E3%82%92%E6%9B%B4%E6%96%B0%0A%20%20%20%20%20%20%20%20backtrack%28choices%2C%20state%20%2B%20choice%2C%20n%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%0A%0A%0Adef%20climbing_stairs_backtrack%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%EF%BC%9A%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%22%22%22%0A%20%20%20%20choices%20%3D%20%5B1%2C%202%5D%20%20%23%201%20%E6%AE%B5%E3%81%BE%E3%81%9F%E3%81%AF%202%20%E6%AE%B5%E4%B8%8A%E3%82%8B%E3%81%93%E3%81%A8%E3%82%92%E9%81%B8%E3%81%B9%E3%82%8B%0A%20%20%20%20state%20%3D%200%20%20%23%20%E7%AC%AC%200%20%E6%AE%B5%E3%81%8B%E3%82%89%E4%B8%8A%E3%82%8A%E5%A7%8B%E3%82%81%E3%82%8B%0A%20%20%20%20res%20%3D%20%5B0%5D%20%20%23%20res%5B0%5D%20%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E6%96%B9%E6%B3%95%E6%95%B0%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%0A%20%20%20%20backtrack%28choices%2C%20state%2C%20n%2C%20res%29%0A%20%20%20%20return%20res%5B0%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_backtrack%28n%29%0A%20%20%20%20print%28f%22%7Bn%7D%20%E6%AE%B5%E3%81%AE%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Bres%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md ================================================ https://pythontutor.com/render.html#code=def%20climbing_stairs_constraint_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%88%B6%E7%B4%84%E4%BB%98%E3%81%8D%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AB%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%203%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%88%9D%E6%9C%9F%E7%8A%B6%E6%85%8B%EF%BC%9A%E6%9C%80%E5%B0%8F%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E3%82%92%E3%81%82%E3%82%89%E3%81%8B%E3%81%98%E3%82%81%E8%A8%AD%E5%AE%9A%0A%20%20%20%20dp%5B1%5D%5B1%5D%2C%20dp%5B1%5D%5B2%5D%20%3D%201%2C%200%0A%20%20%20%20dp%5B2%5D%5B1%5D%2C%20dp%5B2%5D%5B2%5D%20%3D%200%2C%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%B0%8F%E3%81%95%E3%81%84%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%8B%E3%82%89%E5%A4%A7%E3%81%8D%E3%81%84%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%B8%E9%A0%86%E3%81%AB%E8%A7%A3%E3%81%8F%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B1%5D%20%3D%20dp%5Bi%20-%201%5D%5B2%5D%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B2%5D%20%3D%20dp%5Bi%20-%202%5D%5B1%5D%20%2B%20dp%5Bi%20-%202%5D%5B2%5D%0A%20%20%20%20return%20dp%5Bn%5D%5B1%5D%20%2B%20dp%5Bn%5D%5B2%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_constraint_dp%28n%29%0A%20%20%20%20print%28f%22%7Bn%7D%20%E6%AE%B5%E3%81%AE%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Bres%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md ================================================ https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%A4%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20dp%5B1%5D%20%E3%81%A8%20dp%5B2%5D%20%E3%81%AF%E6%97%A2%E7%9F%A5%E3%81%AA%E3%81%AE%E3%81%A7%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%29%20%2B%20dfs%28i%20-%202%29%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%EF%BC%9A%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20return%20dfs%28n%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs%28n%29%0A%20%20%20%20print%28f%22%7Bn%7D%20%E6%AE%B5%E3%81%AE%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Bres%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md ================================================ https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%2C%20mem%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%83%A1%E3%83%A2%E5%8C%96%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20dp%5B1%5D%20%E3%81%A8%20dp%5B2%5D%20%E3%81%AF%E6%97%A2%E7%9F%A5%E3%81%AA%E3%81%AE%E3%81%A7%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20dp%5Bi%5D%20%E3%81%AE%E8%A8%98%E9%8C%B2%E3%81%8C%E3%81%82%E3%82%8C%E3%81%B0%E3%80%81%E3%81%9D%E3%82%8C%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E8%BF%94%E3%81%99%0A%20%20%20%20if%20mem%5Bi%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%2C%20mem%29%20%2B%20dfs%28i%20-%202%2C%20mem%29%0A%20%20%20%20%23%20dp%5Bi%5D%20%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%0A%20%20%20%20mem%5Bi%5D%20%3D%20count%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs_mem%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%EF%BC%9A%E3%83%A1%E3%83%A2%E5%8C%96%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20mem%5Bi%5D%20%E3%81%AF%E7%AC%AC%20i%20%E6%AE%B5%E3%81%BE%E3%81%A7%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AE%E7%B7%8F%E6%95%B0%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%97%E3%80%81-1%20%E3%81%AF%E6%9C%AA%E8%A8%98%E9%8C%B2%E3%82%92%E8%A1%A8%E3%81%99%0A%20%20%20%20mem%20%3D%20%5B-1%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20return%20dfs%28n%2C%20mem%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs_mem%28n%29%0A%20%20%20%20print%28f%22%7Bn%7D%20%E6%AE%B5%E3%81%AE%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Bres%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md ================================================ https://pythontutor.com/render.html#code=def%20climbing_stairs_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AB%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E6%9C%9F%E7%8A%B6%E6%85%8B%EF%BC%9A%E6%9C%80%E5%B0%8F%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E3%82%92%E3%81%82%E3%82%89%E3%81%8B%E3%81%98%E3%82%81%E8%A8%AD%E5%AE%9A%0A%20%20%20%20dp%5B1%5D%2C%20dp%5B2%5D%20%3D%201%2C%202%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%B0%8F%E3%81%95%E3%81%84%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%8B%E3%82%89%E5%A4%A7%E3%81%8D%E3%81%84%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%B8%E9%A0%86%E3%81%AB%E8%A7%A3%E3%81%8F%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20dp%5Bi%20-%201%5D%20%2B%20dp%5Bi%20-%202%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp%28n%29%0A%20%20%20%20print%28f%22%7Bn%7D%20%E6%AE%B5%E3%81%AE%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Bres%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20climbing_stairs_dp_comp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%97%E3%81%9F%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20a%2C%20b%20%3D%201%2C%202%0A%20%20%20%20for%20_%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a%2C%20b%20%3D%20b%2C%20a%20%2B%20b%0A%20%20%20%20return%20b%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp_comp%28n%29%0A%20%20%20%20print%28f%22%7Bn%7D%20%E6%AE%B5%E3%81%AE%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Bres%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_dynamic_programming/coin_change.md ================================================ https://pythontutor.com/render.html#code=def%20coin_change_dp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%82%B3%E3%82%A4%E3%83%B3%E4%B8%A1%E6%9B%BF%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E8%A1%8C%E3%81%A8%E5%85%88%E9%A0%AD%E5%88%97%0A%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Ba%5D%20%3D%20MAX%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%3A%20%E6%AE%8B%E3%82%8A%E3%81%AE%E8%A1%8C%E3%81%A8%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E7%A1%AC%E8%B2%A8%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%A1%AC%E8%B2%A8%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%B0%8F%E3%81%95%E3%81%84%E6%96%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20min%28dp%5Bi%20-%201%5D%5Ba%5D%2C%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%20if%20dp%5Bn%5D%5Bamt%5D%20%21%3D%20MAX%20else%20-1%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20coin_change_dp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E4%BD%9C%E3%82%8B%E3%81%AE%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E6%9C%80%E5%B0%8F%E7%A1%AC%E8%B2%A8%E6%9E%9A%E6%95%B0%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20coin_change_dp_comp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%82%B3%E3%82%A4%E3%83%B3%E4%BA%A4%E6%8F%9B%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5BMAX%5D%20%2A%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%200%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%A0%86%E6%96%B9%E5%90%91%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E7%A1%AC%E8%B2%A8%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%A1%AC%E8%B2%A8%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%B0%8F%E3%81%95%E3%81%84%E6%96%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20min%28dp%5Ba%5D%2C%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bamt%5D%20if%20dp%5Bamt%5D%20%21%3D%20MAX%20else%20-1%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20coin_change_dp_comp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E4%BD%9C%E3%82%8B%E3%81%AE%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E6%9C%80%E5%B0%8F%E7%A1%AC%E8%B2%A8%E6%9E%9A%E6%95%B0%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md ================================================ https://pythontutor.com/render.html#code=def%20coin_change_ii_dp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%82%B3%E3%82%A4%E3%83%B3%E4%B8%A1%E6%9B%BF%20II%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%85%88%E9%A0%AD%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E7%A1%AC%E8%B2%A8%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%82%B3%E3%82%A4%E3%83%B3%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%20%2B%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20coin_change_ii_dp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E4%BD%9C%E3%82%8B%E7%A1%AC%E8%B2%A8%E3%81%AE%E7%B5%84%E3%81%BF%E5%90%88%E3%82%8F%E3%81%9B%E6%95%B0%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20coin_change_ii_dp_comp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%82%B3%E3%82%A4%E3%83%B3%E4%B8%A1%E6%9B%BF%20II%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%97%E3%81%9F%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%A0%86%E6%96%B9%E5%90%91%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E7%A1%AC%E8%B2%A8%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%82%B3%E3%82%A4%E3%83%B3%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%20%2B%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bamt%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20coin_change_ii_dp_comp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E4%BD%9C%E3%82%8B%E7%A1%AC%E8%B2%A8%E3%81%AE%E7%B5%84%E3%81%BF%E5%90%88%E3%82%8F%E3%81%9B%E6%95%B0%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_dynamic_programming/edit_distance.md ================================================ https://pythontutor.com/render.html#code=def%20edit_distance_dp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%A8%E9%9B%86%E8%B7%9D%E9%9B%A2%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28m%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E8%A1%8C%E3%81%A8%E5%85%88%E9%A0%AD%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20i%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%3A%20%E6%AE%8B%E3%82%8A%E3%81%AE%E8%A1%8C%E3%81%A8%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E6%96%87%E5%AD%97%E3%81%8C%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E3%81%9D%E3%81%AE%202%20%E6%96%87%E5%AD%97%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%82%B9%E3%82%AD%E3%83%83%E3%83%97%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%8F%E7%B7%A8%E9%9B%86%E5%9B%9E%E6%95%B0%20%3D%20%E6%8C%BF%E5%85%A5%E3%83%BB%E5%89%8A%E9%99%A4%E3%83%BB%E7%BD%AE%E6%8F%9B%E3%81%AE%203%20%E6%93%8D%E4%BD%9C%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E6%9C%80%E5%B0%8F%E7%B7%A8%E9%9B%86%E5%9B%9E%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%2C%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%29%20%2B%201%0A%20%20%20%20return%20dp%5Bn%5D%5Bm%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20edit_distance_dp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%7Bs%7D%20%E3%82%92%20%7Bt%7D%20%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF%E6%9C%80%E5%B0%8F%E3%81%A7%20%7Bres%7D%20%E5%9B%9E%E3%81%AE%E7%B7%A8%E9%9B%86%E3%81%8C%E5%BF%85%E8%A6%81%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20edit_distance_dp_comp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%A8%E9%9B%86%E8%B7%9D%E9%9B%A2%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%97%E3%81%9F%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E6%AE%8B%E3%82%8A%E3%81%AE%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E5%88%97%0A%20%20%20%20%20%20%20%20leftup%20%3D%20dp%5B0%5D%20%20%23%20dp%5Bi-1%2C%20j-1%5D%20%E3%82%92%E4%B8%80%E6%99%82%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E6%AE%8B%E3%82%8A%E3%81%AE%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20dp%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E6%96%87%E5%AD%97%E3%81%8C%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E3%81%9D%E3%81%AE%202%20%E6%96%87%E5%AD%97%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%82%B9%E3%82%AD%E3%83%83%E3%83%97%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20leftup%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%8F%E7%B7%A8%E9%9B%86%E5%9B%9E%E6%95%B0%20%3D%20%E6%8C%BF%E5%85%A5%E3%83%BB%E5%89%8A%E9%99%A4%E3%83%BB%E7%BD%AE%E6%8F%9B%E3%81%AE%203%20%E6%93%8D%E4%BD%9C%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E6%9C%80%E5%B0%8F%E7%B7%A8%E9%9B%86%E5%9B%9E%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D%2C%20dp%5Bj%5D%2C%20leftup%29%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20leftup%20%3D%20temp%20%20%23%20%E6%AC%A1%E3%81%AE%E5%8F%8D%E5%BE%A9%E3%81%AE%20dp%5Bi-1%2C%20j-1%5D%20%E3%81%AB%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B%0A%20%20%20%20return%20dp%5Bm%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20edit_distance_dp_comp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%7Bs%7D%20%E3%82%92%20%7Bt%7D%20%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF%E6%9C%80%E5%B0%8F%E3%81%A7%20%7Bres%7D%20%E5%9B%9E%E3%81%AE%E7%B7%A8%E9%9B%86%E3%81%8C%E5%BF%85%E8%A6%81%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_dynamic_programming/knapsack.md ================================================ https://pythontutor.com/render.html#code=def%20knapsack_dfs%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20i%3A%20int%2C%20c%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%EF%BC%9A%E7%B7%8F%E5%BD%93%E3%81%9F%E3%82%8A%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E5%93%81%E7%89%A9%E3%82%92%E9%81%B8%E3%81%B3%E7%B5%82%E3%81%88%E3%81%9F%E3%81%8B%E3%80%81%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E3%81%AB%E6%AE%8B%E3%82%8A%E5%AE%B9%E9%87%8F%E3%81%8C%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E4%BE%A1%E5%80%A4%200%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AF%E3%80%81%E5%85%A5%E3%82%8C%E3%81%AA%E3%81%84%E9%81%B8%E6%8A%9E%E3%81%97%E3%81%8B%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20%23%20%E5%93%81%E7%89%A9%20i%20%E3%82%92%E5%85%A5%E3%82%8C%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E5%85%A5%E3%82%8C%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%82%92%E8%A8%88%E7%AE%97%E3%81%99%E3%82%8B%0A%20%20%20%20no%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%202%E3%81%A4%E3%81%AE%E6%A1%88%E3%81%AE%E3%81%86%E3%81%A1%E4%BE%A1%E5%80%A4%E3%81%8C%E5%A4%A7%E3%81%8D%E3%81%84%E3%81%BB%E3%81%86%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20max%28no%2C%20yes%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E5%85%A8%E6%8E%A2%E7%B4%A2%0A%20%20%20%20res%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20n%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20knapsack_dfs_mem%28%0A%20%20%20%20wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20mem%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20c%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%EF%BC%9A%E3%83%A1%E3%83%A2%E5%8C%96%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E5%93%81%E7%89%A9%E3%82%92%E9%81%B8%E3%81%B3%E7%B5%82%E3%81%88%E3%81%9F%E3%81%8B%E3%80%81%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E3%81%AB%E6%AE%8B%E3%82%8A%E5%AE%B9%E9%87%8F%E3%81%8C%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E4%BE%A1%E5%80%A4%200%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E6%97%A2%E3%81%AB%E8%A8%98%E9%8C%B2%E3%81%8C%E3%81%82%E3%82%8C%E3%81%B0%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E8%BF%94%E3%81%99%0A%20%20%20%20if%20mem%5Bi%5D%5Bc%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%20%20%20%20%23%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AF%E3%80%81%E5%85%A5%E3%82%8C%E3%81%AA%E3%81%84%E9%81%B8%E6%8A%9E%E3%81%97%E3%81%8B%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20%23%20%E5%93%81%E7%89%A9%20i%20%E3%82%92%E5%85%A5%E3%82%8C%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E5%85%A5%E3%82%8C%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%82%92%E8%A8%88%E7%AE%97%E3%81%99%E3%82%8B%0A%20%20%20%20no%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E6%A1%88%E3%81%AE%E3%81%86%E3%81%A1%E4%BE%A1%E5%80%A4%E3%81%8C%E5%A4%A7%E3%81%8D%E3%81%84%E6%96%B9%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%97%E3%81%A6%E8%BF%94%E3%81%99%0A%20%20%20%20mem%5Bi%5D%5Bc%5D%20%3D%20max%28no%2C%20yes%29%0A%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E3%83%A1%E3%83%A2%E5%8C%96%E6%8E%A2%E7%B4%A2%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20res%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20n%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20knapsack_dp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E5%93%81%E7%89%A9%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%93%81%E7%89%A9%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%A4%A7%E3%81%8D%E3%81%84%E6%96%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D%2C%20dp%5Bi%20-%201%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20knapsack_dp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20knapsack_dp_comp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%86%E9%A0%86%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%28cap%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E5%93%81%E7%89%A9%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%93%81%E7%89%A9%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%A4%A7%E3%81%8D%E3%81%84%E6%96%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D%2C%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20knapsack_dp_comp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md ================================================ https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%E3%81%AE%E6%9C%80%E5%B0%8F%E3%82%B3%E3%82%B9%E3%83%88%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AB%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E6%9C%9F%E7%8A%B6%E6%85%8B%EF%BC%9A%E6%9C%80%E5%B0%8F%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E3%82%92%E3%81%82%E3%82%89%E3%81%8B%E3%81%98%E3%82%81%E8%A8%AD%E5%AE%9A%0A%20%20%20%20dp%5B1%5D%2C%20dp%5B2%5D%20%3D%20cost%5B1%5D%2C%20cost%5B2%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%B0%8F%E3%81%95%E3%81%84%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%8B%E3%82%89%E5%A4%A7%E3%81%8D%E3%81%84%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%B8%E9%A0%86%E3%81%AB%E8%A7%A3%E3%81%8F%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20min%28dp%5Bi%20-%201%5D%2C%20dp%5Bi%20-%202%5D%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0%2C%201%2C%2010%2C%201%2C%201%2C%201%2C%2010%2C%201%2C%201%2C%2010%2C%201%5D%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E3%81%95%E3%82%8C%E3%81%9F%E9%9A%8E%E6%AE%B5%E3%82%B3%E3%82%B9%E3%83%88%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AF%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp%28cost%29%0A%20%20%20%20print%28f%22%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8A%E5%88%87%E3%82%8B%E6%9C%80%E5%B0%8F%E3%82%B3%E3%82%B9%E3%83%88%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp_comp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E6%98%87%E3%82%8A%E3%81%AE%E6%9C%80%E5%B0%8F%E3%82%B3%E3%82%B9%E3%83%88%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20a%2C%20b%20%3D%20cost%5B1%5D%2C%20cost%5B2%5D%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a%2C%20b%20%3D%20b%2C%20min%28a%2C%20b%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20b%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0%2C%201%2C%2010%2C%201%2C%201%2C%201%2C%2010%2C%201%2C%201%2C%2010%2C%201%5D%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E3%81%95%E3%82%8C%E3%81%9F%E9%9A%8E%E6%AE%B5%E3%82%B3%E3%82%B9%E3%83%88%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AF%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp_comp%28cost%29%0A%20%20%20%20print%28f%22%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8A%E5%88%87%E3%82%8B%E6%9C%80%E5%B0%8F%E3%82%B3%E3%82%B9%E3%83%88%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md ================================================ https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs%28grid%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%EF%BC%9A%E5%85%A8%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E4%B8%8A%E3%81%AE%E3%82%BB%E3%83%AB%E3%81%AA%E3%82%89%E6%8E%A2%E7%B4%A2%E3%82%92%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%A1%8C%E3%81%BE%E3%81%9F%E3%81%AF%E5%88%97%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%AA%E3%82%89%E3%80%81%E3%82%B3%E3%82%B9%E3%83%88%20%2B%E2%88%9E%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E5%B7%A6%E4%B8%8A%E3%81%8B%E3%82%89%20%28i-1%2C%20j%29%20%E3%81%8A%E3%82%88%E3%81%B3%20%28i%2C%20j-1%29%20%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E3%82%B3%E3%82%B9%E3%83%88%E3%82%92%E8%A8%88%E7%AE%97%E3%81%99%E3%82%8B%0A%20%20%20%20up%20%3D%20min_path_sum_dfs%28grid%2C%20i%20-%201%2C%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs%28grid%2C%20i%2C%20j%20-%201%29%0A%20%20%20%20%23%20%E5%B7%A6%E4%B8%8A%E9%9A%85%E3%81%8B%E3%82%89%20%28i%2C%20j%29%20%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E3%82%B3%E3%82%B9%E3%83%88%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20min%28left%2C%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E5%85%A8%E6%8E%A2%E7%B4%A2%0A%20%20%20%20res%20%3D%20min_path_sum_dfs%28grid%2C%20n%20-%201%2C%20m%20-%201%29%0A%20%20%20%20print%28f%22%E5%B7%A6%E4%B8%8A%E3%81%8B%E3%82%89%E5%8F%B3%E4%B8%8B%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs_mem%28%0A%20%20%20%20grid%3A%20list%5Blist%5Bint%5D%5D%2C%20mem%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20j%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%EF%BC%9A%E3%83%A1%E3%83%A2%E5%8C%96%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E4%B8%8A%E3%81%AE%E3%82%BB%E3%83%AB%E3%81%AA%E3%82%89%E6%8E%A2%E7%B4%A2%E3%82%92%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%A1%8C%E3%81%BE%E3%81%9F%E3%81%AF%E5%88%97%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%AA%E3%82%89%E3%80%81%E3%82%B3%E3%82%B9%E3%83%88%20%2B%E2%88%9E%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E6%97%A2%E3%81%AB%E8%A8%98%E9%8C%B2%E3%81%8C%E3%81%82%E3%82%8C%E3%81%B0%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E8%BF%94%E3%81%99%0A%20%20%20%20if%20mem%5Bi%5D%5Bj%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%20%20%20%20%23%20%E5%B7%A6%E3%81%A8%E4%B8%8A%E3%81%AE%E3%82%BB%E3%83%AB%E3%81%8B%E3%82%89%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E3%82%B3%E3%82%B9%E3%83%88%0A%20%20%20%20up%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20i%20-%201%2C%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20i%2C%20j%20-%201%29%0A%20%20%20%20%23%20%E5%B7%A6%E4%B8%8A%E3%81%8B%E3%82%89%20%28i%2C%20j%29%20%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E3%82%B3%E3%82%B9%E3%83%88%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%97%E3%81%A6%E8%BF%94%E3%81%99%0A%20%20%20%20mem%5Bi%5D%5Bj%5D%20%3D%20min%28left%2C%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%23%20%E3%83%A1%E3%83%A2%E5%8C%96%E6%8E%A2%E7%B4%A2%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20%2A%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20res%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20n%20-%201%2C%20m%20-%201%29%0A%20%20%20%20print%28f%22%E5%B7%A6%E4%B8%8A%E3%81%8B%E3%82%89%E5%8F%B3%E4%B8%8B%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20dp%5B0%5D%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20dp%5B0%5D%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20dp%5Bi%20-%201%5D%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%3A%20%E6%AE%8B%E3%82%8A%E3%81%AE%E8%A1%8C%E3%81%A8%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bn%20-%201%5D%5Bm%20-%201%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20min_path_sum_dp%28grid%29%0A%20%20%20%20print%28f%22%E5%B7%A6%E4%B8%8A%E3%81%8B%E3%82%89%E5%8F%B3%E4%B8%8B%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp_comp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20m%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E8%A1%8C%0A%20%20%20%20dp%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20dp%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E6%AE%8B%E3%82%8A%E3%81%AE%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E5%88%97%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%3D%20dp%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E6%AE%8B%E3%82%8A%E3%81%AE%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D%2C%20dp%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bm%20-%201%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20min_path_sum_dp_comp%28grid%29%0A%20%20%20%20print%28f%22%E5%B7%A6%E4%B8%8A%E3%81%8B%E3%82%89%E5%8F%B3%E4%B8%8B%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md ================================================ https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%95%8F%E9%A1%8C%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E5%93%81%E7%89%A9%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%93%81%E7%89%A9%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%A4%A7%E3%81%8D%E3%81%84%E6%96%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D%2C%20dp%5Bi%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20val%20%3D%20%5B5%2C%2011%2C%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp_comp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%95%8F%E9%A1%8C%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%A0%86%E6%96%B9%E5%90%91%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E5%93%81%E7%89%A9%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%93%81%E7%89%A9%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%A4%A7%E3%81%8D%E3%81%84%E6%96%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D%2C%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20val%20%3D%20%5B5%2C%2011%2C%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp_comp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_graph/graph_adjacency_list.md ================================================ https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%27Vertex%27%5D%3A%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.adj_list%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20remove_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.remove%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.remove%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20remove_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet%20not%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list.pop%28vet%29%0A%20%20%20%20%20%20%20%20for%20vertex%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%5Bvertex%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.adj_list%5Bvertex%5D.remove%28vet%29%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B1%2C%203%2C%202%2C%205%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%20%5Bv%5B2%5D%2C%20v%5B3%5D%5D%2C%20%5Bv%5B2%5D%2C%20v%5B4%5D%5D%2C%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%20%20%20%20graph.add_edge%28v%5B0%5D%2C%20v%5B2%5D%29%0A%20%20%20%20graph.remove_edge%28v%5B0%5D%2C%20v%5B1%5D%29%0A%20%20%20%20v5%20%3D%20Vertex%286%29%0A%20%20%20%20graph.add_vertex%28v5%29%0A%20%20%20%20graph.remove_vertex%28v%5B1%5D%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md ================================================ https://pythontutor.com/render.html#code=class%20GraphAdjMat%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20vertices%3A%20list%5Bint%5D%2C%20edges%3A%20list%5Blist%5Bint%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20self.vertices%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.adj_mat%3A%20list%5Blist%5Bint%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20val%20in%20vertices%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28val%29%0A%20%20%20%20%20%20%20%20for%20e%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28e%5B0%5D%2C%20e%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.vertices%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20n%20%3D%20self.size%28%29%0A%20%20%20%20%20%20%20%20self.vertices.append%28val%29%0A%20%20%20%20%20%20%20%20new_row%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20%20%20%20%20self.adj_mat.append%28new_row%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.append%280%29%0A%0A%20%20%20%20def%20remove_vertex%28self%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.vertices.pop%28index%29%0A%20%20%20%20%20%20%20%20self.adj_mat.pop%28index%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.pop%28index%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20%28j%20%3E%3D%20self.size%28%29%29%20or%20%28i%20%3D%3D%20j%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%201%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%201%0A%0A%20%20%20%20def%20remove_edge%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20%28j%20%3E%3D%20self.size%28%29%29%20or%20%28i%20%3D%3D%20j%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%200%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%200%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20vertices%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20edges%20%3D%20%5B%5B0%2C%201%5D%2C%20%5B0%2C%203%5D%2C%20%5B1%2C%202%5D%2C%20%5B2%2C%203%5D%2C%20%5B2%2C%204%5D%2C%20%5B3%2C%204%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjMat%28vertices%2C%20edges%29%0A%20%20%20%20graph.add_edge%280%2C%202%29%0A%20%20%20%20graph.remove_edge%280%2C%201%29%0A%20%20%20%20graph.add_vertex%286%29%0A%20%20%20%20graph.remove_vertex%281%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_graph/graph_bfs.md ================================================ https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A0%82%E7%82%B9%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E5%80%A4%E3%83%AA%E3%82%B9%E3%83%88%20vals%20%E3%82%92%E5%85%A5%E5%8A%9B%E3%81%97%E3%80%81%E9%A0%82%E7%82%B9%E3%83%AA%E3%82%B9%E3%83%88%20vets%20%E3%82%92%E8%BF%94%E3%81%99%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E9%9A%A3%E6%8E%A5%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AB%E5%9F%BA%E3%81%A5%E3%81%8F%E7%84%A1%E5%90%91%E3%82%B0%E3%83%A9%E3%83%95%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BE%BA%E3%82%92%E8%BF%BD%E5%8A%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E9%A0%82%E7%82%B9%E3%82%92%E8%BF%BD%E5%8A%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20graph_bfs%28graph%3A%20GraphAdjList%2C%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E5%B9%85%E5%84%AA%E5%85%88%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E9%A0%82%E7%82%B9%E3%81%AE%E8%B5%B0%E6%9F%BB%E9%A0%86%E5%BA%8F%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E6%B8%88%E3%81%BF%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20BFS%20%E3%81%AE%E5%AE%9F%E8%A3%85%E3%81%AB%E3%82%AD%E3%83%A5%E3%83%BC%E3%82%92%E7%94%A8%E3%81%84%E3%82%8B%0A%20%20%20%20que%20%3D%20deque%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20%E9%A0%82%E7%82%B9%20vet%20%E3%82%92%E8%B5%B7%E7%82%B9%E3%81%AB%E3%80%81%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%AA%E5%95%8F%E3%81%97%E7%B5%82%E3%81%88%E3%82%8B%E3%81%BE%E3%81%A7%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20while%20len%28que%29%20%3E%200%3A%0A%20%20%20%20%20%20%20%20vet%20%3D%20que.popleft%28%29%20%20%23%20%E5%85%88%E9%A0%AD%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E3%83%87%E3%82%AD%E3%83%A5%E3%83%BC%0A%20%20%20%20%20%20%20%20res.append%28vet%29%20%20%23%20%E8%A8%AA%E5%95%8F%E3%81%97%E3%81%9F%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20%23%20%E3%81%93%E3%81%AE%E9%A0%82%E7%82%B9%E3%81%AE%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%9A%A3%E6%8E%A5%E9%A0%82%E7%82%B9%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20%20%20%20%20for%20adj_vet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20adj_vet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%A8%AA%E5%95%8F%E6%B8%88%E3%81%BF%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E3%82%B9%E3%82%AD%E3%83%83%E3%83%97%0A%20%20%20%20%20%20%20%20%20%20%20%20que.append%28adj_vet%29%20%20%23%20%E6%9C%AA%E8%A8%AA%E5%95%8F%E3%81%AE%E9%A0%82%E7%82%B9%E3%81%AE%E3%81%BF%E3%82%92%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20%20%20%20%20visited.add%28adj_vet%29%20%20%23%20%E3%81%93%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%AA%E5%95%8F%E6%B8%88%E3%81%BF%E3%81%AB%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E9%A0%82%E7%82%B9%E3%81%AE%E8%B5%B0%E6%9F%BB%E9%A0%86%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E7%84%A1%E5%90%91%E3%82%B0%E3%83%A9%E3%83%95%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0%2C%201%2C%202%2C%203%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%0A%20%20%20%20%23%20%E5%B9%85%E5%84%AA%E5%85%88%E6%8E%A2%E7%B4%A2%0A%20%20%20%20res%20%3D%20graph_bfs%28graph%2C%20v%5B0%5D%29&cumulative=false&curInstr=131&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_graph/graph_dfs.md ================================================ https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A0%82%E7%82%B9%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E5%80%A4%E3%83%AA%E3%82%B9%E3%83%88%20vals%20%E3%82%92%E5%85%A5%E5%8A%9B%E3%81%97%E3%80%81%E9%A0%82%E7%82%B9%E3%83%AA%E3%82%B9%E3%83%88%20vets%20%E3%82%92%E8%BF%94%E3%81%99%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E9%9A%A3%E6%8E%A5%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AB%E5%9F%BA%E3%81%A5%E3%81%8F%E7%84%A1%E5%90%91%E3%82%B0%E3%83%A9%E3%83%95%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BE%BA%E3%82%92%E8%BF%BD%E5%8A%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E9%A0%82%E7%82%B9%E3%82%92%E8%BF%BD%E5%8A%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20dfs%28graph%3A%20GraphAdjList%2C%20visited%3A%20set%5BVertex%5D%2C%20res%3A%20list%5BVertex%5D%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E3%81%95%E5%84%AA%E5%85%88%E8%B5%B0%E6%9F%BB%E3%81%AE%E8%A3%9C%E5%8A%A9%E9%96%A2%E6%95%B0%22%22%22%0A%20%20%20%20res.append%28vet%29%20%20%23%20%E8%A8%AA%E5%95%8F%E3%81%97%E3%81%9F%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20visited.add%28vet%29%20%20%23%20%E3%81%93%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%AA%E5%95%8F%E6%B8%88%E3%81%BF%E3%81%AB%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E3%81%93%E3%81%AE%E9%A0%82%E7%82%B9%E3%81%AE%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%9A%A3%E6%8E%A5%E9%A0%82%E7%82%B9%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20adjVet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20if%20adjVet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%A8%AA%E5%95%8F%E6%B8%88%E3%81%BF%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E3%82%B9%E3%82%AD%E3%83%83%E3%83%97%0A%20%20%20%20%20%20%20%20%23%20%E9%9A%A3%E6%8E%A5%E9%A0%82%E7%82%B9%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E8%A8%AA%E5%95%8F%0A%20%20%20%20%20%20%20%20dfs%28graph%2C%20visited%2C%20res%2C%20adjVet%29%0A%0A%0Adef%20graph_dfs%28graph%3A%20GraphAdjList%2C%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E3%81%95%E5%84%AA%E5%85%88%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%9F%E9%A0%82%E7%82%B9%E3%81%AE%E9%9A%A3%E6%8E%A5%E9%A0%82%E7%82%B9%E3%82%92%E3%81%99%E3%81%B9%E3%81%A6%E5%8F%96%E5%BE%97%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%80%81%E9%9A%A3%E6%8E%A5%E3%83%AA%E3%82%B9%E3%83%88%E3%81%A7%E3%82%B0%E3%83%A9%E3%83%95%E3%82%92%E8%A1%A8%E7%8F%BE%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E9%A0%82%E7%82%B9%E3%81%AE%E8%B5%B0%E6%9F%BB%E9%A0%86%E5%BA%8F%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E6%B8%88%E3%81%BF%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%29%0A%20%20%20%20dfs%28graph%2C%20visited%2C%20res%2C%20start_vet%29%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E7%84%A1%E5%90%91%E3%82%B0%E3%83%A9%E3%83%95%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0%2C%201%2C%202%2C%203%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%0A%20%20%20%20%23%20%E6%B7%B1%E3%81%95%E5%84%AA%E5%85%88%E6%8E%A2%E7%B4%A2%0A%20%20%20%20res%20%3D%20graph_dfs%28graph%2C%20v%5B0%5D%29&cumulative=false&curInstr=130&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_greedy/coin_change_greedy.md ================================================ https://pythontutor.com/render.html#code=def%20coin_change_greedy%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%82%B3%E3%82%A4%E3%83%B3%E4%BA%A4%E6%8F%9B%EF%BC%9A%E8%B2%AA%E6%AC%B2%E6%B3%95%22%22%22%0A%20%20%20%20%23%20coins%20%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AF%E3%82%BD%E3%83%BC%E3%83%88%E6%B8%88%E3%81%BF%E3%81%A8%E4%BB%AE%E5%AE%9A%E3%81%99%E3%82%8B%0A%20%20%20%20i%20%3D%20len%28coins%29%20-%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E6%AE%8B%E9%A1%8D%E3%81%8C%E3%81%AA%E3%81%8F%E3%81%AA%E3%82%8B%E3%81%BE%E3%81%A7%E8%B2%AA%E6%AC%B2%E9%81%B8%E6%8A%9E%E3%82%92%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20while%20amt%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AE%8B%E9%A1%8D%E4%BB%A5%E4%B8%8B%E3%81%A7%E6%9C%80%E3%82%82%E8%BF%91%E3%81%84%E7%A1%AC%E8%B2%A8%E3%82%92%E8%A6%8B%E3%81%A4%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20while%20i%20%3E%200%20and%20coins%5Bi%5D%20%3E%20amt%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20-%3D%201%0A%20%20%20%20%20%20%20%20%23%20coins%5Bi%5D%20%E3%82%92%E9%81%B8%E6%8A%9E%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20amt%20-%3D%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%23%20%E5%AE%9F%E8%A1%8C%E5%8F%AF%E8%83%BD%E3%81%AA%E8%A7%A3%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%82%89%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%20-1%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20count%20if%20amt%20%3D%3D%200%20else%20-1%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E6%B3%95%EF%BC%9A%E5%A4%A7%E5%9F%9F%E6%9C%80%E9%81%A9%E8%A7%A3%E3%82%92%E4%BF%9D%E8%A8%BC%E3%81%A7%E3%81%8D%E3%82%8B%0A%20%20%20%20coins%20%3D%20%5B1%2C%205%2C%2010%2C%2020%2C%2050%2C%20100%5D%0A%20%20%20%20amt%20%3D%20186%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%7Bamt%7D%20%E3%82%92%E4%BD%9C%E3%82%8B%E3%81%AE%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E6%9C%80%E5%B0%8F%E3%81%AE%E7%A1%AC%E8%B2%A8%E6%9E%9A%E6%95%B0%E3%81%AF%20%7Bres%7D%22%29%0A%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E6%B3%95%EF%BC%9A%E5%A4%A7%E5%9F%9F%E6%9C%80%E9%81%A9%E8%A7%A3%E3%82%92%E4%BF%9D%E8%A8%BC%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%0A%20%20%20%20coins%20%3D%20%5B1%2C%2020%2C%2050%5D%0A%20%20%20%20amt%20%3D%2060%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%7Bamt%7D%20%E3%82%92%E4%BD%9C%E3%82%8B%E3%81%AE%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E6%9C%80%E5%B0%8F%E3%81%AE%E7%A1%AC%E8%B2%A8%E6%9E%9A%E6%95%B0%E3%81%AF%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E5%AE%9F%E9%9A%9B%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E6%9C%80%E5%B0%8F%E6%9E%9A%E6%95%B0%E3%81%AF%203%20%EF%BC%8C%E3%81%A4%E3%81%BE%E3%82%8A%2020%20%2B%2020%20%2B%2020%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_greedy/fractional_knapsack.md ================================================ https://pythontutor.com/render.html#code=class%20Item%3A%0A%20%20%20%20%22%22%22%E5%93%81%E7%89%A9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20w%3A%20int%2C%20v%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.w%20%3D%20w%20%20%23%20%E5%93%81%E7%89%A9%E3%81%AE%E9%87%8D%E3%81%95%0A%20%20%20%20%20%20%20%20self.v%20%3D%20v%20%20%23%20%E5%93%81%E7%89%A9%E3%81%AE%E4%BE%A1%E5%80%A4%0A%0Adef%20fractional_knapsack%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%88%86%E6%95%B0%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%EF%BC%9A%E8%B2%AA%E6%AC%B2%E6%B3%95%22%22%22%0A%20%20%20%20%23%20%E9%87%8D%E3%81%95%E3%81%A8%E4%BE%A1%E5%80%A4%E3%81%AE%202%20%E5%B1%9E%E6%80%A7%E3%82%92%E6%8C%81%E3%81%A4%E5%93%81%E7%89%A9%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BD%9C%E6%88%90%0A%20%20%20%20items%20%3D%20%5BItem%28w%2C%20v%29%20for%20w%2C%20v%20in%20zip%28wgt%2C%20val%29%5D%0A%20%20%20%20%23%20%E5%8D%98%E4%BD%8D%E4%BE%A1%E5%80%A4%20item.v%20%2F%20item.w%20%E3%81%AE%E9%AB%98%E3%81%84%E9%A0%86%E3%81%AB%E3%82%BD%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B%0A%20%20%20%20items.sort%28key%3Dlambda%20item%3A%20item.v%20%2F%20item.w%2C%20reverse%3DTrue%29%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E9%81%B8%E6%8A%9E%E3%82%92%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20for%20item%20in%20items%3A%0A%20%20%20%20%20%20%20%20if%20item.w%20%3C%3D%20cap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%AE%8B%E3%82%8A%E5%AE%B9%E9%87%8F%E3%81%8C%E5%8D%81%E5%88%86%E3%81%AA%E3%82%89%E3%80%81%E7%8F%BE%E5%9C%A8%E3%81%AE%E5%93%81%E7%89%A9%E3%82%92%E4%B8%B8%E3%81%94%E3%81%A8%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E3%81%AB%E5%85%A5%E3%82%8C%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20item.v%0A%20%20%20%20%20%20%20%20%20%20%20%20cap%20-%3D%20item.w%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%AE%8B%E3%82%8A%E5%AE%B9%E9%87%8F%E3%81%8C%E8%B6%B3%E3%82%8A%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AF%E3%80%81%E7%8F%BE%E5%9C%A8%E3%81%AE%E5%93%81%E7%89%A9%E3%81%AE%E4%B8%80%E9%83%A8%E3%81%A0%E3%81%91%E3%82%92%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E3%81%AB%E5%85%A5%E3%82%8C%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20%28item.v%20%2F%20item.w%29%20%2A%20cap%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%AE%8B%E3%82%8A%E5%AE%B9%E9%87%8F%E3%81%8C%E3%81%AA%E3%81%84%E3%81%9F%E3%82%81%E3%80%81%E3%83%AB%E3%83%BC%E3%83%97%E3%82%92%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E6%B3%95%0A%20%20%20%20res%20%3D%20fractional_knapsack%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_greedy/max_capacity.md ================================================ https://pythontutor.com/render.html#code=def%20max_capacity%28ht%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%EF%BC%9A%E8%B2%AA%E6%AC%B2%E6%B3%95%22%22%22%0A%20%20%20%20%23%20i%2C%20j%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%97%E3%80%81%E3%81%9D%E3%82%8C%E3%81%9E%E3%82%8C%E9%85%8D%E5%88%97%E3%81%AE%E4%B8%A1%E7%AB%AF%E3%81%AB%E7%BD%AE%E3%81%8F%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28ht%29%20-%201%0A%20%20%20%20%23%20%E5%88%9D%E6%9C%9F%E3%81%AE%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E3%81%AF%200%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%202%20%E6%9E%9A%E3%81%AE%E6%9D%BF%E3%81%8C%E5%87%BA%E4%BC%9A%E3%81%86%E3%81%BE%E3%81%A7%E8%B2%AA%E6%AC%B2%E9%81%B8%E6%8A%9E%E3%82%92%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20cap%20%3D%20min%28ht%5Bi%5D%2C%20ht%5Bj%5D%29%20%2A%20%28j%20-%20i%29%0A%20%20%20%20%20%20%20%20res%20%3D%20max%28res%2C%20cap%29%0A%20%20%20%20%20%20%20%20%23%20%E7%9F%AD%E3%81%84%E6%96%B9%E3%82%92%E5%86%85%E5%81%B4%E3%81%B8%E5%8B%95%E3%81%8B%E3%81%99%0A%20%20%20%20%20%20%20%20if%20ht%5Bi%5D%20%3C%20ht%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20ht%20%3D%20%5B3%2C%208%2C%205%2C%202%2C%207%2C%207%2C%203%2C%204%5D%0A%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E6%B3%95%0A%20%20%20%20res%20%3D%20max_capacity%28ht%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_greedy/max_product_cutting.md ================================================ https://pythontutor.com/render.html#code=import%20math%0A%0Adef%20max_product_cutting%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%88%87%E6%96%AD%E7%A9%8D%EF%BC%9A%E8%B2%AA%E6%AC%B2%E6%B3%95%22%22%22%0A%20%20%20%20%23%20n%20%3C%3D%203%20%E3%81%AE%E3%81%A8%E3%81%8D%E3%81%AF%E3%80%81%E5%BF%85%E3%81%9A%201%20%E3%82%92%E5%88%87%E3%82%8A%E5%87%BA%E3%81%99%0A%20%20%20%20if%20n%20%3C%3D%203%3A%0A%20%20%20%20%20%20%20%20return%201%20%2A%20%28n%20-%201%29%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E3%81%AB%203%20%E3%82%92%E5%88%87%E3%82%8A%E5%87%BA%E3%81%97%E3%80%81a%20%E3%82%92%203%20%E3%81%AE%E5%80%8B%E6%95%B0%E3%80%81b%20%E3%82%92%E4%BD%99%E3%82%8A%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20a%2C%20b%20%3D%20n%20%2F%2F%203%2C%20n%20%25%203%0A%20%20%20%20if%20b%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BD%99%E3%82%8A%E3%81%8C%201%20%E3%81%AE%E3%81%A8%E3%81%8D%E3%81%AF%E3%80%811%20%2A%203%20%E3%82%92%202%20%2A%202%20%E3%81%AB%E5%A4%89%E3%81%88%E3%82%8B%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%20-%201%29%29%20%2A%202%20%2A%202%0A%20%20%20%20if%20b%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BD%99%E3%82%8A%E3%81%8C%202%20%E3%81%AE%E3%81%A8%E3%81%8D%E3%81%AF%E3%80%81%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%81%AB%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%20%2A%202%0A%20%20%20%20%23%20%E4%BD%99%E3%82%8A%E3%81%8C%200%20%E3%81%AE%E3%81%A8%E3%81%8D%E3%81%AF%E3%80%81%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%81%AB%E3%81%99%E3%82%8B%0A%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2058%0A%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E6%B3%95%0A%20%20%20%20res%20%3D%20max_product_cutting%28n%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%88%86%E5%89%B2%E7%A9%8D%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_hashing/array_hash_map.md ================================================ https://pythontutor.com/render.html#code=class%20Pair%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Aclass%20ArrayHashMap%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self.buckets%3A%20list%5BPair%20%7C%20None%5D%20%3D%20%5BNone%5D%20%2A%2020%0A%0A%20%20%20%20def%20hash_func%28self%2C%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20index%20%3D%20key%20%25%2020%0A%20%20%20%20%20%20%20%20return%20index%0A%0A%20%20%20%20def%20get%28self%2C%20key%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20pair%3A%20Pair%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20if%20pair%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20pair.val%0A%0A%20%20%20%20def%20put%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key%2C%20val%29%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20pair%0A%0A%20%20%20%20def%20remove%28self%2C%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20None%0A%0A%20%20%20%20def%20entry_set%28self%29%20-%3E%20list%5BPair%5D%3A%0A%20%20%20%20%20%20%20%20result%3A%20list%5BPair%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20key_set%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.key%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20value_set%28self%29%20-%3E%20list%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.val%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print%28pair.key%2C%20%27-%3E%27%2C%20pair.val%29%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20hmap%20%3D%20ArrayHashMap%28%29%0A%20%20%20%20hmap.put%2812836%2C%20%27%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%8F%E3%83%BC%27%29%0A%20%20%20%20hmap.put%2815937%2C%20%27%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%AB%E3%82%AA%27%29%0A%20%20%20%20hmap.put%2816750%2C%20%27%E3%82%B7%E3%83%A3%E3%82%AA%E3%82%B9%E3%83%AF%E3%83%B3%27%29%0A%20%20%20%20hmap.put%2813276%2C%20%27%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%95%E3%82%A1%E3%83%BC%27%29%0A%20%20%20%20hmap.put%2810583%2C%20%27%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%A4%E3%83%BC%27%29%0A%20%20%20%20name%20%3D%20hmap.get%2815937%29%0A%20%20%20%20hmap.remove%2810583%29%0A%20%20%20%20print%28%27%5Cn%E3%82%AD%E3%83%BC%E3%81%A8%E5%80%A4%E3%81%AE%E3%83%9A%E3%82%A2%20Key-%3EValue%20%E3%82%92%E8%B5%B0%E6%9F%BB%27%29%0A%20%20%20%20for%20pair%20in%20hmap.entry_set%28%29%3A%0A%20%20%20%20%20%20%20%20print%28pair.key%2C%20%27-%3E%27%2C%20pair.val%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_hashing/hash_map_chaining.md ================================================ https://pythontutor.com/render.html#code=class%20Pair%3A%0A%20%20%20%20%22%22%22%E3%82%AD%E3%83%BC%E3%81%A8%E5%80%A4%E3%81%AE%E7%B5%84%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Aclass%20HashMapChaining%3A%0A%20%20%20%20%22%22%22%E3%83%81%E3%82%A7%E3%82%A4%E3%83%B3%E6%B3%95%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20self.capacity%20%3D%204%0A%20%20%20%20%20%20%20%20self.load_thres%20%3D%202.0%20%2F%203.0%0A%20%20%20%20%20%20%20%20self.extend_ratio%20%3D%202%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%0A%20%20%20%20def%20hash_func%28self%2C%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E9%96%A2%E6%95%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20key%20%25%20self.capacity%0A%0A%20%20%20%20def%20load_factor%28self%29%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%B2%A0%E8%8D%B7%E7%8E%87%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%20%2F%20self.capacity%0A%0A%20%20%20%20def%20get%28self%2C%20key%3A%20int%29%20-%3E%20str%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%A4%9C%E7%B4%A2%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20pair.val%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20def%20put%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BF%BD%E5%8A%A0%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.load_factor%28%29%20%3E%20self.load_thres%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend%28%29%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pair.val%20%3D%20val%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key%2C%20val%29%0A%20%20%20%20%20%20%20%20bucket.append%28pair%29%0A%20%20%20%20%20%20%20%20self.size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self%2C%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%89%8A%E9%99%A4%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bucket.remove%28pair%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.size%20-%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20def%20extend%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E6%8B%A1%E5%BC%B5%22%22%22%0A%20%20%20%20%20%20%20%20buckets%20%3D%20self.buckets%0A%20%20%20%20%20%20%20%20self.capacity%20%2A%3D%20self.extend_ratio%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.put%28pair.key%2C%20pair.val%29%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%87%BA%E5%8A%9B%22%22%22%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20res.append%28str%28pair.key%29%20%2B%20%22%20-%3E%20%22%20%2B%20pair.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28res%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20hashmap%20%3D%20HashMapChaining%28%29%0A%0A%20%20%20%20%23%20%E8%BF%BD%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.put%2812836%2C%20%22%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%8F%E3%83%BC%22%29%0A%20%20%20%20hashmap.put%2815937%2C%20%22%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%AB%E3%82%AA%22%29%0A%20%20%20%20hashmap.put%2816750%2C%20%22%E3%82%B7%E3%83%A3%E3%82%AA%E3%82%B9%E3%83%AF%E3%83%B3%22%29%0A%20%20%20%20hashmap.put%2813276%2C%20%22%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%95%E3%82%A1%E3%83%BC%22%29%0A%20%20%20%20hashmap.put%2810583%2C%20%22%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%A4%E3%83%BC%22%29%0A%0A%20%20%20%20%23%20%E6%A4%9C%E7%B4%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20name%20%3D%20hashmap.get%2813276%29%0A%0A%20%20%20%20%23%20%E5%89%8A%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.remove%2812836%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_hashing/simple_hash.md ================================================ https://pythontutor.com/render.html#code=def%20add_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%8A%A0%E7%AE%97%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%2B%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20mul_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%B9%97%E7%AE%97%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%2031%20%2A%20hash%20%2B%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20xor_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22XOR%20%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%5E%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20rot_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E8%BB%A2%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%20%28hash%20%3C%3C%204%29%20%5E%20%28hash%20%3E%3E%2028%29%20%5E%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20key%20%3D%20%22Hello%20%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%22%0A%0A%20%20%20%20hash%20%3D%20add_hash%28key%29%0A%20%20%20%20print%28f%22%E5%8A%A0%E7%AE%97%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E5%80%A4%E3%81%AF%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20mul_hash%28key%29%0A%20%20%20%20print%28f%22%E4%B9%97%E7%AE%97%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E5%80%A4%E3%81%AF%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20xor_hash%28key%29%0A%20%20%20%20print%28f%22XOR%20%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E5%80%A4%E3%81%AF%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20rot_hash%28key%29%0A%20%20%20%20print%28f%22%E5%9B%9E%E8%BB%A2%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E5%80%A4%E3%81%AF%20%7Bhash%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_heap/my_heap.md ================================================ https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E3%83%92%E3%83%BC%E3%83%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%E3%80%82%E5%85%A5%E5%8A%9B%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AB%E5%9F%BA%E3%81%A5%E3%81%84%E3%81%A6%E3%83%92%E3%83%BC%E3%83%97%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AA%E3%82%B9%E3%83%88%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%20%20%20%20%20%20%20%20%23%20%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E4%BB%A5%E5%A4%96%E3%81%AE%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.parent%28self.size%28%29%20-%201%29%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.sift_down%28i%29%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A6%AA%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%20%20%23%20%E5%88%87%E3%82%8A%E6%8D%A8%E3%81%A6%E9%99%A4%E7%AE%97%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A6%81%E7%B4%A0%E3%82%92%E4%BA%A4%E6%8F%9B%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20sift_down%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8B%E3%82%89%E5%A7%8B%E3%82%81%E3%81%A6%E3%80%81%E4%B8%8A%E3%81%8B%E3%82%89%E4%B8%8B%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20i%2C%20l%2C%20r%20%E3%81%AE%E3%81%86%E3%81%A1%E5%80%A4%E3%81%8C%E6%9C%80%E5%A4%A7%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%20ma%20%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20l%2C%20r%2C%20ma%20%3D%20self.left%28i%29%2C%20self.right%28i%29%2C%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8C%E6%9C%80%E5%A4%A7%E3%80%81%E3%81%BE%E3%81%9F%E3%81%AF%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20l%2C%20r%20%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%AA%E3%82%89%E3%80%81%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%E3%81%AF%E4%B8%8D%E8%A6%81%E3%81%AA%E3%81%AE%E3%81%A7%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E4%B8%8A%E3%81%8B%E3%82%89%E4%B8%8B%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%80%E5%A4%A7%E3%83%92%E3%83%BC%E3%83%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B1%2C%202%2C%203%2C%204%2C%205%5D%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E3%83%92%E3%83%BC%E3%83%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AA%E3%82%B9%E3%83%88%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A6%AA%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%20%20%23%20%E5%88%87%E3%82%8A%E6%8D%A8%E3%81%A6%E9%99%A4%E7%AE%97%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%82%92%E5%88%A4%E5%AE%9A%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.max_heap%5B0%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%80%E5%A4%A7%E3%83%92%E3%83%BC%E3%83%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20%23%20%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%E3%81%AF%E3%81%99%E3%81%A7%E3%81%AB%E6%AD%A3%E5%BD%93%E3%81%AA%E3%83%92%E3%83%BC%E3%83%97%E3%81%A7%E3%81%82%E3%82%8B%E7%82%B9%E3%81%AB%E6%B3%A8%E6%84%8F%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%206%2C%206%2C%207%2C%205%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%5D%29%0A%0A%20%20%20%20%23%20%E3%83%92%E3%83%BC%E3%83%97%E9%A0%82%E7%82%B9%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E5%BE%97%0A%20%20%20%20peek%20%3D%20max_heap.peek%28%29%0A%20%20%20%20print%28f%22%5Cn%E3%83%92%E3%83%BC%E3%83%97%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%AF%20%7Bpeek%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E3%83%92%E3%83%BC%E3%83%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AA%E3%82%B9%E3%83%88%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A6%AA%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%20%20%23%20%E5%88%87%E3%82%8A%E6%8D%A8%E3%81%A6%E9%99%A4%E7%AE%97%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A6%81%E7%B4%A0%E3%82%92%E4%BA%A4%E6%8F%9B%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%82%92%E5%88%A4%E5%AE%9A%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E8%BF%BD%E5%8A%A0%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20self.max_heap.append%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E4%B8%8B%E3%81%8B%E3%82%89%E4%B8%8A%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20self.sift_up%28self.size%28%29%20-%201%29%0A%0A%20%20%20%20def%20sift_up%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8B%E3%82%89%E5%A7%8B%E3%82%81%E3%81%A6%E3%80%81%E4%B8%8B%E3%81%8B%E3%82%89%E4%B8%8A%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%AE%E8%A6%AA%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%8F%96%E5%BE%97%0A%20%20%20%20%20%20%20%20%20%20%20%20p%20%3D%20self.parent%28i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%80%8C%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%B6%8A%E3%81%88%E3%81%9F%E3%80%8D%E3%81%BE%E3%81%9F%E3%81%AF%E3%80%8C%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E4%BF%AE%E5%BE%A9%E3%81%8C%E4%B8%8D%E8%A6%81%E3%80%8D%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%82%89%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%E3%82%92%E7%B5%82%E4%BA%86%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20p%20%3C%200%20or%20self.max_heap%5Bi%5D%20%3C%3D%20self.max_heap%5Bp%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20p%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E4%B8%8B%E3%81%8B%E3%82%89%E4%B8%8A%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20p%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%80%E5%A4%A7%E3%83%92%E3%83%BC%E3%83%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20%23%20%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%E3%81%AF%E3%81%99%E3%81%A7%E3%81%AB%E6%AD%A3%E5%BD%93%E3%81%AA%E3%83%92%E3%83%BC%E3%83%97%E3%81%A7%E3%81%82%E3%82%8B%E7%82%B9%E3%81%AB%E6%B3%A8%E6%84%8F%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%206%2C%206%2C%207%2C%205%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%5D%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20val%20%3D%207%0A%20%20%20%20max_heap.push%28val%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%20%3D%20%28self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%83%92%E3%83%BC%E3%83%97%E3%81%8C%E7%A9%BA%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20self.swap%280%2C%20self.size%28%29%20-%201%29%0A%20%20%20%20%20%20%20%20val%20%3D%20self.max_heap.pop%28%29%0A%20%20%20%20%20%20%20%20self.sift_down%280%29%0A%20%20%20%20%20%20%20%20return%20val%0A%0A%20%20%20%20def%20sift_down%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20l%2C%20r%2C%20ma%20%3D%20%28self.left%28i%29%2C%20self.right%28i%29%2C%20i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%207%2C%206%2C%207%2C%206%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%2C%205%5D%29%0A%20%20%20%20peek%20%3D%20max_heap.pop%28%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_heap/top_k.md ================================================ https://pythontutor.com/render.html#code=import%20heapq%0A%0Adef%20top_k_heap%28nums%3A%20list%5Bint%5D%2C%20k%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E5%9F%BA%E3%81%A5%E3%81%84%E3%81%A6%E9%85%8D%E5%88%97%E4%B8%AD%E3%81%AE%E6%9C%80%E5%A4%A7%E3%81%AE%20k%20%E5%80%8B%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%22%22%22%0A%20%20%20%20%23%20%E6%9C%80%E5%B0%8F%E3%83%92%E3%83%BC%E3%83%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20heap%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%81%AE%E5%85%88%E9%A0%AD%20k%20%E5%80%8B%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20for%20i%20in%20range%28k%29%3A%0A%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20%23%20k%2B1%20%E7%95%AA%E7%9B%AE%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%8B%E3%82%89%E9%96%8B%E5%A7%8B%E3%81%97%E3%80%81%E3%83%92%E3%83%BC%E3%83%97%E9%95%B7%E3%82%92%20k%20%E3%81%AB%E4%BF%9D%E3%81%A4%0A%20%20%20%20for%20i%20in%20range%28k%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%8C%E3%83%92%E3%83%BC%E3%83%97%E5%85%88%E9%A0%AD%E3%82%88%E3%82%8A%E5%A4%A7%E3%81%8D%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E3%83%92%E3%83%BC%E3%83%97%E5%85%88%E9%A0%AD%E3%82%92%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%97%E3%81%A6%E7%8F%BE%E5%9C%A8%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3E%20heap%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappop%28heap%29%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20return%20heap%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%207%2C%206%2C%203%2C%202%5D%0A%20%20%20%20k%20%3D%203%0A%0A%20%20%20%20res%20%3D%20top_k_heap%28nums%2C%20k%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_searching/binary_search.md ================================================ https://pythontutor.com/render.html#code=def%20binary_search%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%EF%BC%88%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%20%5B0%2C%20n-1%5D%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%E3%80%82%E3%81%A4%E3%81%BE%E3%82%8A%20i%2C%20j%20%E3%81%AF%E3%81%9D%E3%82%8C%E3%81%9E%E3%82%8C%E9%85%8D%E5%88%97%E3%81%AE%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%A8%E6%9C%AB%E5%B0%BE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8C%87%E3%81%99%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%97%E3%80%81%E6%8E%A2%E7%B4%A2%E5%8C%BA%E9%96%93%E3%81%8C%E7%A9%BA%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%82%89%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%EF%BC%88i%20%3E%20j%20%E3%81%A7%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%90%86%E8%AB%96%E4%B8%8A%E3%80%81Python%20%E3%81%AE%E6%95%B0%E5%80%A4%E3%81%AF%E7%84%A1%E9%99%90%E3%81%AB%E5%A4%A7%E3%81%8D%E3%81%8F%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%9F%E3%82%81%EF%BC%88%E3%83%A1%E3%83%A2%E3%83%AA%E5%AE%B9%E9%87%8F%E3%81%AB%E4%BE%9D%E5%AD%98%EF%BC%89%E3%80%81%E5%A4%A7%E3%81%8D%E3%81%AA%E6%95%B0%E3%81%AE%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%95%E3%83%AD%E3%83%BC%E3%82%92%E8%80%83%E6%85%AE%E3%81%99%E3%82%8B%E5%BF%85%E8%A6%81%E3%81%AF%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E3%81%93%E3%81%AE%E5%A0%B4%E5%90%88%E3%80%81target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E3%81%93%E3%81%AE%E5%A0%B4%E5%90%88%E3%80%81target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E7%9B%AE%E6%A8%99%E8%A6%81%E7%B4%A0%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%81%9D%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20-1%20%20%23%20%E7%9B%AE%E6%A8%99%E8%A6%81%E7%B4%A0%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%82%89%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%20-1%20%E3%82%92%E8%BF%94%E3%81%99%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%EF%BC%88%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums%2C%20target%29%0A%20%20%20%20print%28%22%E5%AF%BE%E8%B1%A1%E8%A6%81%E7%B4%A0%206%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20binary_search_lcro%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%EF%BC%88%E5%B7%A6%E9%96%89%E5%8F%B3%E9%96%8B%E5%8C%BA%E9%96%93%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E9%96%89%E5%8F%B3%E9%96%8B%E5%8C%BA%E9%96%93%20%5B0%2C%20n%29%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%E3%80%82%E3%81%A4%E3%81%BE%E3%82%8A%20i%2C%20j%20%E3%81%AF%E3%81%9D%E3%82%8C%E3%81%9E%E3%82%8C%E9%85%8D%E5%88%97%E3%81%AE%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%A8%E6%9C%AB%E5%B0%BE%E8%A6%81%E7%B4%A0%2B1%E3%82%92%E6%8C%87%E3%81%99%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%97%E3%80%81%E6%8E%A2%E7%B4%A2%E5%8C%BA%E9%96%93%E3%81%8C%E7%A9%BA%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%82%89%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%EF%BC%88i%20%3D%20j%20%E3%81%A7%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E3%81%93%E3%81%AE%E5%A0%B4%E5%90%88%E3%80%81target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bm%2B1%2C%20j%29%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20%20%23%20%E3%81%93%E3%81%AE%E5%A0%B4%E5%90%88%E3%80%81target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m%29%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E7%9B%AE%E6%A8%99%E8%A6%81%E7%B4%A0%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%81%9D%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20-1%20%20%23%20%E7%9B%AE%E6%A8%99%E8%A6%81%E7%B4%A0%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%82%89%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%20-1%20%E3%82%92%E8%BF%94%E3%81%99%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%EF%BC%88%E5%B7%A6%E9%96%89%E5%8F%B3%E9%96%8B%E5%8C%BA%E9%96%93%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search_lcro%28nums%2C%20target%29%0A%20%20%20%20print%28%22%E5%AF%BE%E8%B1%A1%E8%A6%81%E7%B4%A0%206%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_searching/binary_search_edge.md ================================================ https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%EF%BC%88%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%81%82%E3%82%8A%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%20%5B0%2C%20n-1%5D%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%82%88%E3%82%8A%E5%B0%8F%E3%81%95%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%20i%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_left_edge%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E3%82%82%E5%B7%A6%E3%81%AE%20target%20%E3%82%92%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20target%20%E3%81%AE%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%E3%81%AE%E3%81%A8%E7%AD%89%E4%BE%A1%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums%2C%20target%29%0A%20%20%20%20%23%20target%20%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%82%89%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81-1%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3D%3D%20len%28nums%29%20or%20nums%5Bi%5D%20%21%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20target%20%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%80%81%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20i%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20i%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%82%92%E5%90%AB%E3%82%80%E9%85%8D%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E5%B7%A6%E7%AB%AF%E3%81%A8%E5%8F%B3%E7%AB%AF%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_left_edge%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E5%B7%A6%E7%AB%AF%E3%81%AE%E8%A6%81%E7%B4%A0%20%7Btarget%7D%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AF%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%EF%BC%88%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%81%82%E3%82%8A%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%20%5B0%2C%20n-1%5D%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%82%88%E3%82%8A%E5%B0%8F%E3%81%95%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%20i%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_right_edge%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E3%82%82%E5%8F%B3%E3%81%AE%20target%20%E3%82%92%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E6%9C%80%E5%B7%A6%E3%81%AE%20target%20%2B%201%20%E3%82%92%E6%8E%A2%E3%81%99%E5%95%8F%E9%A1%8C%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums%2C%20target%20%2B%201%29%0A%20%20%20%20%23%20j%20%E3%81%AF%E6%9C%80%E3%82%82%E5%8F%B3%E3%81%AE%20target%20%E3%82%92%E6%8C%87%E3%81%97%E3%80%81i%20%E3%81%AF%20target%20%E3%82%88%E3%82%8A%E5%A4%A7%E3%81%8D%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8C%87%E3%81%99%0A%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%23%20target%20%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%82%89%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81-1%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20j%20%3D%3D%20-1%20or%20nums%5Bj%5D%20%21%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20target%20%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%80%81%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20j%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20j%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%82%92%E5%90%AB%E3%82%80%E9%85%8D%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E5%B7%A6%E7%AB%AF%E3%81%A8%E5%8F%B3%E7%AB%AF%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_right_edge%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E5%8F%B3%E7%AB%AF%E3%81%AE%E8%A6%81%E7%B4%A0%20%7Btarget%7D%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AF%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_searching/binary_search_insertion.md ================================================ https://pythontutor.com/render.html#code=def%20binary_search_insertion_simple%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%EF%BC%88%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%81%AA%E3%81%97%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%20%5B0%2C%20n-1%5D%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20target%20%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%80%81%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%20m%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20%23%20target%20%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%82%89%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%20i%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20i%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%81%AE%E3%81%AA%E3%81%84%E9%85%8D%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion_simple%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E8%A6%81%E7%B4%A0%20%7Btarget%7D%20%E3%81%AE%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AF%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%EF%BC%88%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%81%82%E3%82%8A%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%20%5B0%2C%20n-1%5D%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%82%88%E3%82%8A%E5%B0%8F%E3%81%95%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%20i%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20i%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%82%92%E5%90%AB%E3%82%80%E9%85%8D%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E8%A6%81%E7%B4%A0%20%7Btarget%7D%20%E3%81%AE%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AF%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_searching/two_sum.md ================================================ https://pythontutor.com/render.html#code=def%20two_sum_brute_force%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%201%EF%BC%9A%E7%B7%8F%E5%BD%93%E3%81%9F%E3%82%8A%E5%88%97%E6%8C%99%22%22%22%0A%20%20%20%20%23%202%E9%87%8D%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E3%81%9F%E3%82%81%E3%80%81%E6%99%82%E9%96%93%E8%A8%88%E7%AE%97%E9%87%8F%E3%81%AF%20O%28n%5E2%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%2B%20nums%5Bj%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bi%2C%20j%5D%0A%20%20%20%20return%20%5B%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%207%2C%2011%2C%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_brute_force%28nums%2C%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20two_sum_hash_table%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%202%EF%BC%9A%E8%A3%9C%E5%8A%A9%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%22%22%22%0A%20%20%20%20%23%20%E8%A3%9C%E5%8A%A9%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%80%81%E7%A9%BA%E9%96%93%E8%A8%88%E7%AE%97%E9%87%8F%E3%81%AF%20O%28n%29%0A%20%20%20%20dic%20%3D%20%7B%7D%0A%20%20%20%20%23%20%E5%8D%98%E4%B8%80%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E3%80%81%E6%99%82%E9%96%93%E8%A8%88%E7%AE%97%E9%87%8F%E3%81%AF%20O%28n%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20target%20-%20nums%5Bi%5D%20in%20dic%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bdic%5Btarget%20-%20nums%5Bi%5D%5D%2C%20i%5D%0A%20%20%20%20%20%20%20%20dic%5Bnums%5Bi%5D%5D%20%3D%20i%0A%20%20%20%20return%20%5B%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%207%2C%2011%2C%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_hash_table%28nums%2C%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_sorting/bubble_sort.md ================================================ https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%20%5B0%2C%20i%5D%20%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%8F%B3%E7%AB%AF%E3%81%B8%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20nums%5Bj%5D%20%E3%81%A8%20nums%5Bj%20%2B%201%5D%20%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20bubble_sort_with_flag%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E3%83%95%E3%83%A9%E3%82%B0%E6%9C%80%E9%81%A9%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20flag%20%3D%20False%20%20%23%20%E3%83%95%E3%83%A9%E3%82%B0%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%20%5B0%2C%20i%5D%20%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%8F%B3%E7%AB%AF%E3%81%B8%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20nums%5Bj%5D%20%E3%81%A8%20nums%5Bj%20%2B%201%5D%20%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20flag%20%3D%20True%20%20%23%20%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20if%20not%20flag%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%20%20%23%20%E3%81%93%E3%81%AE%E3%83%90%E3%83%96%E3%83%AB%E5%87%A6%E7%90%86%E3%81%A7%E8%A6%81%E7%B4%A0%E4%BA%A4%E6%8F%9B%E3%81%8C%E4%B8%80%E5%BA%A6%E3%82%82%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E7%B5%82%E4%BA%86%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort_with_flag%28nums%29%0A%20%20%20%20print%28%22%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_sorting/bucket_sort.md ================================================ https://pythontutor.com/render.html#code=def%20bucket_sort%28nums%3A%20list%5Bfloat%5D%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20k%20%3D%20n%2F2%20%E5%80%8B%E3%81%AE%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%97%E3%80%81%E5%90%84%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%81%AB%202%20%E8%A6%81%E7%B4%A0%E3%81%9A%E3%81%A4%E5%89%B2%E3%82%8A%E5%BD%93%E3%81%A6%E3%82%8B%E6%83%B3%E5%AE%9A%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20k%20%3D%20len%28nums%29%20%2F%2F%202%0A%20%20%20%20buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28k%29%5D%0A%20%20%20%20%23%201.%20%E9%85%8D%E5%88%97%E8%A6%81%E7%B4%A0%E3%82%92%E5%90%84%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%81%AB%E6%8C%AF%E3%82%8A%E5%88%86%E3%81%91%E3%82%8B%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E7%AF%84%E5%9B%B2%E3%81%AF%20%5B0%2C%201%29%20%E3%81%A7%E3%81%82%E3%82%8A%E3%80%81num%20%2A%20k%20%E3%82%92%E7%94%A8%E3%81%84%E3%81%A6%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E7%AF%84%E5%9B%B2%20%5B0%2C%20k-1%5D%20%E3%81%AB%E5%86%99%E5%83%8F%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20i%20%3D%20int%28num%20%2A%20k%29%0A%20%20%20%20%20%20%20%20%23%20num%20%E3%82%92%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%20i%20%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20buckets%5Bi%5D.append%28num%29%0A%20%20%20%20%23%202.%20%E5%90%84%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%81%AE%E3%82%BD%E3%83%BC%E3%83%88%E9%96%A2%E6%95%B0%E3%82%92%E4%BD%BF%E3%81%86%E3%80%82%E4%BB%96%E3%81%AE%E3%82%BD%E3%83%BC%E3%83%88%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%81%AB%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88%E3%81%A6%E3%82%82%E3%82%88%E3%81%84%0A%20%20%20%20%20%20%20%20bucket.sort%28%29%0A%20%20%20%20%23%203.%20%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E8%B5%B0%E6%9F%BB%E3%81%97%E3%81%A6%E7%B5%90%E6%9E%9C%E3%82%92%E7%B5%90%E5%90%88%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20for%20num%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AF%E7%AF%84%E5%9B%B2%20%5B0%2C%201%29%20%E3%81%AE%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20nums%20%3D%20%5B0.49%2C%200.96%2C%200.82%2C%200.09%2C%200.57%2C%200.43%2C%200.91%2C%200.75%2C%200.15%2C%200.37%5D%0A%20%20%20%20bucket_sort%28nums%29%0A%20%20%20%20print%28%22%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_sorting/counting_sort.md ================================================ https://pythontutor.com/render.html#code=def%20counting_sort_naive%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E7%B0%A1%E6%98%93%E7%89%88%E3%80%82%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E3%82%BD%E3%83%BC%E3%83%88%E3%81%AB%E3%81%AF%E4%BD%BF%E3%81%88%E3%81%AA%E3%81%84%0A%20%20%20%20%23%201.%20%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%20m%20%E3%82%92%E6%B1%82%E3%82%81%E3%82%8B%0A%20%20%20%20m%20%3D%200%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20max%28m%2C%20num%29%0A%20%20%20%20%23%202.%20%E5%90%84%E6%95%B0%E5%80%A4%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E6%95%B0%E3%81%88%E3%82%8B%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E3%81%AF%20num%20%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E8%A1%A8%E3%81%99%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20counter%20%E3%82%92%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E5%90%84%E8%A6%81%E7%B4%A0%E3%82%92%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%81%AB%E6%9B%B8%E3%81%8D%E6%88%BB%E3%81%99%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20num%20in%20range%28m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28counter%5Bnum%5D%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort_naive%28nums%29%0A%20%20%20%20print%28f%22%E8%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AF%E3%82%BD%E3%83%BC%E3%83%88%E4%B8%8D%E5%8F%AF%EF%BC%89%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20counting_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E5%AE%8C%E5%85%A8%E7%89%88%E3%80%82%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E3%81%A7%E3%81%8D%E3%80%81%E3%81%8B%E3%81%A4%E5%AE%89%E5%AE%9A%E3%82%BD%E3%83%BC%E3%83%88%E3%81%A7%E3%81%82%E3%82%8B%0A%20%20%20%20%23%201.%20%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%20m%20%E3%82%92%E6%B1%82%E3%82%81%E3%82%8B%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%202.%20%E5%90%84%E6%95%B0%E5%80%A4%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E6%95%B0%E3%81%88%E3%82%8B%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E3%81%AF%20num%20%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E8%A1%A8%E3%81%99%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20counter%20%E3%81%AE%E7%B4%AF%E7%A9%8D%E5%92%8C%E3%82%92%E6%B1%82%E3%82%81%E3%81%A6%E3%80%81%E3%80%8C%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%80%8D%E3%82%92%E3%80%8C%E6%9C%AB%E5%B0%BE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%80%8D%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E3%81%A4%E3%81%BE%E3%82%8A%20counter%5Bnum%5D-1%20%E3%81%AF%E3%80%81num%20%E3%81%8C%20res%20%E3%81%AB%E6%9C%80%E5%BE%8C%E3%81%AB%E7%8F%BE%E3%82%8C%E3%82%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%0A%20%20%20%20for%20i%20in%20range%28m%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%20%2B%201%5D%20%2B%3D%20counter%5Bi%5D%0A%20%20%20%20%23%204.%20nums%20%E3%82%92%E9%80%86%E9%A0%86%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E5%90%84%E8%A6%81%E7%B4%A0%E3%82%92%E7%B5%90%E6%9E%9C%E9%85%8D%E5%88%97%20res%20%E3%81%AB%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E9%85%8D%E5%88%97%20res%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20res%5Bcounter%5Bnum%5D%20-%201%5D%20%3D%20num%20%20%23%20num%20%E3%82%92%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AB%E9%85%8D%E7%BD%AE%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20-%3D%201%20%20%23%20%E7%B4%AF%E7%A9%8D%E5%92%8C%E3%82%92%201%20%E6%B8%9B%E3%82%89%E3%81%97%E3%81%A6%E3%80%81%E6%AC%A1%E3%81%AB%20num%20%E3%82%92%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%BE%97%E3%82%8B%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%E9%85%8D%E5%88%97%20res%20%E3%81%A7%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%82%92%E4%B8%8A%E6%9B%B8%E3%81%8D%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort%28nums%29%0A%20%20%20%20print%28f%22%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_sorting/heap_sort.md ================================================ https://pythontutor.com/render.html#code=def%20sift_down%28nums%3A%20list%5Bint%5D%2C%20n%3A%20int%2C%20i%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%AE%E9%95%B7%E3%81%95%E3%81%AF%20n%E3%80%82%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8B%E3%82%89%E4%B8%8B%E6%96%B9%E5%90%91%E3%81%AB%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%22%22%22%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20i%2C%20l%2C%20r%20%E3%81%AE%E3%81%86%E3%81%A1%E5%80%A4%E3%81%8C%E6%9C%80%E5%A4%A7%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%20ma%20%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20l%20%3D%202%20%2A%20i%20%2B%201%0A%20%20%20%20%20%20%20%20r%20%3D%202%20%2A%20i%20%2B%202%0A%20%20%20%20%20%20%20%20ma%20%3D%20i%0A%20%20%20%20%20%20%20%20if%20l%20%3C%20n%20and%20nums%5Bl%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20if%20r%20%3C%20n%20and%20nums%5Br%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8C%E6%9C%80%E5%A4%A7%E3%80%81%E3%81%BE%E3%81%9F%E3%81%AF%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20l%2C%20r%20%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%AA%E3%82%89%E3%80%81%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%E3%81%AF%E4%B8%8D%E8%A6%81%E3%81%AA%E3%81%AE%E3%81%A7%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bma%5D%20%3D%20nums%5Bma%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E4%B8%8A%E3%81%8B%E3%82%89%E4%B8%8B%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0Adef%20heap_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E3%83%92%E3%83%BC%E3%83%97%E6%A7%8B%E7%AF%89%EF%BC%9A%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E4%BB%A5%E5%A4%96%E3%81%AE%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20%2F%2F%202%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20len%28nums%29%2C%20i%29%0A%20%20%20%20%23%20%E3%83%92%E3%83%BC%E3%83%97%E3%81%8B%E3%82%89%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%97%E3%80%81n-1%20%E5%9B%9E%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%81%A8%E6%9C%80%E3%82%82%E5%8F%B3%E3%81%AE%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E4%BA%A4%E6%8F%9B%EF%BC%88%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%A8%E6%9C%AB%E5%B0%BE%E8%A6%81%E7%B4%A0%E3%82%92%E4%BA%A4%E6%8F%9B%EF%BC%89%0A%20%20%20%20%20%20%20%20nums%5B0%5D%2C%20nums%5Bi%5D%20%3D%20nums%5Bi%5D%2C%20nums%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%B5%B7%E7%82%B9%E3%81%AB%E3%80%81%E4%B8%8A%E3%81%8B%E3%82%89%E4%B8%8B%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20i%2C%200%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20heap_sort%28nums%29%0A%20%20%20%20print%28%22%E3%83%92%E3%83%BC%E3%83%97%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_sorting/insertion_sort.md ================================================ https://pythontutor.com/render.html#code=def%20insertion_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%8C%BF%E5%85%A5%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%95%B4%E5%88%97%E6%B8%88%E3%81%BF%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i-1%5D%0A%20%20%20%20for%20i%20in%20range%281%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20base%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%83%AB%E3%83%BC%E3%83%97%3A%20base%20%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E6%B8%88%E3%81%BF%E5%8C%BA%E9%96%93%20%5B0%2C%20i-1%5D%20%E3%81%AE%E6%AD%A3%E3%81%97%E3%81%84%E4%BD%8D%E7%BD%AE%E3%81%AB%E6%8C%BF%E5%85%A5%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20while%20j%20%3E%3D%200%20and%20nums%5Bj%5D%20%3E%20base%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%5D%20%20%23%20nums%5Bj%5D%20%E3%82%92%201%20%E3%81%A4%E5%8F%B3%E3%81%B8%E7%A7%BB%E5%8B%95%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20base%20%20%23%20base%20%E3%82%92%E6%AD%A3%E3%81%97%E3%81%84%E4%BD%8D%E7%BD%AE%E3%81%AB%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20insertion_sort%28nums%29%0A%20%20%20%20print%28%22%E6%8C%BF%E5%85%A5%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_sorting/merge_sort.md ================================================ https://pythontutor.com/render.html#code=def%20merge%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%A8%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E3%83%9E%E3%83%BC%E3%82%B8%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AF%20%5Bleft%2C%20mid%5D%E3%80%81%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AF%20%5Bmid%2B1%2C%20right%5D%0A%20%20%20%20%23%20%E3%83%9E%E3%83%BC%E3%82%B8%E7%B5%90%E6%9E%9C%E3%82%92%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%20tmp%20%E3%82%92%E4%BD%9C%E6%88%90%0A%20%20%20%20tmp%20%3D%20%5B0%5D%20%2A%20%28right%20-%20left%20%2B%201%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E9%96%8B%E5%A7%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20i%2C%20j%2C%20k%20%3D%20left%2C%20mid%20%2B%201%2C%200%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AB%E3%81%BE%E3%81%A0%E8%A6%81%E7%B4%A0%E3%81%8C%E3%81%82%E3%82%8B%E9%96%93%E3%81%AF%E6%AF%94%E8%BC%83%E3%81%97%E3%80%81%E5%B0%8F%E3%81%95%E3%81%84%E3%81%BB%E3%81%86%E3%82%92%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B%0A%20%20%20%20while%20i%20%3C%3D%20mid%20and%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3C%3D%20nums%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E6%AE%8B%E3%82%8A%E8%A6%81%E7%B4%A0%E3%82%92%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B%0A%20%20%20%20while%20i%20%3C%3D%20mid%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20while%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%20tmp%20%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%81%AE%E5%AF%BE%E5%BF%9C%E5%8C%BA%E9%96%93%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B%0A%20%20%20%20for%20k%20in%20range%280%2C%20len%28tmp%29%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bleft%20%2B%20k%5D%20%3D%20tmp%5Bk%5D%0A%0A%0Adef%20merge_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E3%83%9E%E3%83%BC%E3%82%B8%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%20%20%23%20%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E9%95%B7%E3%81%95%E3%81%8C%201%20%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%82%89%E5%86%8D%E5%B8%B0%E3%82%92%E7%B5%82%E4%BA%86%0A%20%20%20%20%23%20%E5%88%86%E5%89%B2%E3%83%95%E3%82%A7%E3%83%BC%E3%82%BA%0A%20%20%20%20mid%20%3D%20%28left%20%2B%20right%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20merge_sort%28nums%2C%20left%2C%20mid%29%20%20%23%20%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E5%87%A6%E7%90%86%0A%20%20%20%20merge_sort%28nums%2C%20mid%20%2B%201%2C%20right%29%20%20%23%20%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E5%87%A6%E7%90%86%0A%20%20%20%20%23%20%E3%83%9E%E3%83%BC%E3%82%B8%E3%83%95%E3%82%A7%E3%83%BC%E3%82%BA%0A%20%20%20%20merge%28nums%2C%20left%2C%20mid%2C%20right%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B7%2C%203%2C%202%2C%206%2C%200%2C%201%2C%205%2C%204%5D%0A%20%20%20%20merge_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E3%83%9E%E3%83%BC%E3%82%B8%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_sorting/quick_sort.md ================================================ https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%95%AA%E5%85%B5%E5%88%86%E5%89%B2%22%22%22%0A%20%20%20%20%23%20nums%5Bleft%5D%20%E3%82%92%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%8F%B3%E3%81%8B%E3%82%89%E5%B7%A6%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E6%9C%AA%E6%BA%80%E3%81%AE%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%B7%A6%E3%81%8B%E3%82%89%E5%8F%B3%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%88%E3%82%8A%E5%A4%A7%E3%81%8D%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%81%AE%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%92%202%20%E3%81%A4%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%A2%83%E7%95%8C%E3%81%B8%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20partition%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E7%95%AA%E5%85%B5%E5%88%86%E5%89%B2%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%95%AA%E5%85%B5%E5%88%86%E5%89%B2%22%22%22%0A%20%20%20%20%23%20nums%5Bleft%5D%20%E3%82%92%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%8F%B3%E3%81%8B%E3%82%89%E5%B7%A6%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E6%9C%AA%E6%BA%80%E3%81%AE%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%B7%A6%E3%81%8B%E3%82%89%E5%8F%B3%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%88%E3%82%8A%E5%A4%A7%E3%81%8D%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%81%AE%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%92%202%20%E3%81%A4%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%A2%83%E7%95%8C%E3%81%B8%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E9%95%B7%E3%81%95%E3%81%8C%201%20%E3%81%AA%E3%82%89%E5%86%8D%E5%B8%B0%E3%82%92%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E7%95%AA%E5%85%B5%E5%88%86%E5%89%B2%0A%20%20%20%20pivot%20%3D%20partition%28nums%2C%20left%2C%20right%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E5%87%A6%E7%90%86%0A%20%20%20%20quick_sort%28nums%2C%20left%2C%20pivot%20-%201%29%0A%20%20%20%20quick_sort%28nums%2C%20pivot%20%2B%201%2C%20right%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20quick_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20median_three%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%223%E3%81%A4%E3%81%AE%E5%80%99%E8%A3%9C%E8%A6%81%E7%B4%A0%E3%81%AE%E4%B8%AD%E5%A4%AE%E5%80%A4%E3%82%92%E9%81%B8%E3%81%B6%22%22%22%0A%20%20%20%20l%2C%20m%2C%20r%20%3D%20nums%5Bleft%5D%2C%20nums%5Bmid%5D%2C%20nums%5Bright%5D%0A%20%20%20%20if%20%28l%20%3C%3D%20m%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20m%20%3C%3D%20l%29%3A%0A%20%20%20%20%20%20%20%20return%20mid%20%20%23%20m%20%E3%81%AF%20l%20%E3%81%A8%20r%20%E3%81%AE%E9%96%93%0A%20%20%20%20if%20%28m%20%3C%3D%20l%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20l%20%3C%3D%20m%29%3A%0A%20%20%20%20%20%20%20%20return%20left%20%20%23%20l%20%E3%81%AF%20m%20%E3%81%A8%20r%20%E3%81%AE%E9%96%93%0A%20%20%20%20return%20right%0A%0Adef%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%95%AA%E5%85%B5%E3%81%AB%E3%82%88%E3%82%8B%E5%88%86%E5%89%B2%E5%87%A6%E7%90%86%EF%BC%883%20%E7%82%B9%E4%B8%AD%E5%A4%AE%E5%80%A4%EF%BC%89%22%22%22%0A%20%20%20%20%23%20nums%5Bleft%5D%20%E3%82%92%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20med%20%3D%20median_three%28nums%2C%20left%2C%20%28left%20%2B%20right%29%20%2F%2F%202%2C%20right%29%0A%20%20%20%20%23%20%E4%B8%AD%E5%A4%AE%E5%80%A4%E3%82%92%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%80%E5%B7%A6%E7%AB%AF%E3%81%AB%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20nums%5Bleft%5D%2C%20nums%5Bmed%5D%20%3D%20nums%5Bmed%5D%2C%20nums%5Bleft%5D%0A%20%20%20%20%23%20nums%5Bleft%5D%20%E3%82%92%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%8F%B3%E3%81%8B%E3%82%89%E5%B7%A6%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E6%9C%AA%E6%BA%80%E3%81%AE%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%B7%A6%E3%81%8B%E3%82%89%E5%8F%B3%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%88%E3%82%8A%E5%A4%A7%E3%81%8D%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%81%AE%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%92%202%20%E3%81%A4%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%A2%83%E7%95%8C%E3%81%B8%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%B8%AD%E5%A4%AE%E5%80%A4%E3%83%94%E3%83%9C%E3%83%83%E3%83%88%E6%9C%80%E9%81%A9%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20partition%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E7%95%AA%E5%85%B5%E5%88%86%E5%89%B2%EF%BC%88%E4%B8%AD%E5%A4%AE%E5%80%A4%E3%83%94%E3%83%9C%E3%83%83%E3%83%88%E6%9C%80%E9%81%A9%E5%8C%96%EF%BC%89%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%95%AA%E5%85%B5%E5%88%86%E5%89%B2%22%22%22%0A%20%20%20%20%23%20nums%5Bleft%5D%20%E3%82%92%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%8F%B3%E3%81%8B%E3%82%89%E5%B7%A6%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E6%9C%AA%E6%BA%80%E3%81%AE%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%B7%A6%E3%81%8B%E3%82%89%E5%8F%B3%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%88%E3%82%8A%E5%A4%A7%E3%81%8D%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%81%AE%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%92%202%20%E3%81%A4%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%A2%83%E7%95%8C%E3%81%B8%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E6%9C%80%E9%81%A9%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E9%95%B7%E3%81%95%E3%81%8C%201%20%E3%81%AA%E3%82%89%E7%B5%82%E4%BA%86%0A%20%20%20%20while%20left%20%3C%20right%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%95%AA%E5%85%B5%E3%81%AB%E3%82%88%E3%82%8B%E5%88%86%E5%89%B2%E5%87%A6%E7%90%86%0A%20%20%20%20%20%20%20%20pivot%20%3D%20partition%28nums%2C%20left%2C%20right%29%0A%20%20%20%20%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E3%81%86%E3%81%A1%E7%9F%AD%E3%81%84%E3%81%BB%E3%81%86%E3%81%AB%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%E3%82%92%E9%81%A9%E7%94%A8%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20pivot%20-%20left%20%3C%20right%20-%20pivot%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums%2C%20left%2C%20pivot%20-%201%29%20%20%23%20%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E3%82%BD%E3%83%BC%E3%83%88%0A%20%20%20%20%20%20%20%20%20%20%20%20left%20%3D%20pivot%20%2B%201%20%20%23%20%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AE%E6%AE%8B%E3%82%8A%E3%81%AF%20%5Bpivot%20%2B%201%2C%20right%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums%2C%20pivot%20%2B%201%2C%20right%29%20%20%23%20%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E3%82%BD%E3%83%BC%E3%83%88%0A%20%20%20%20%20%20%20%20%20%20%20%20right%20%3D%20pivot%20-%201%20%20%23%20%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AE%E6%AE%8B%E3%82%8A%E3%81%AF%20%5Bleft%2C%20pivot%20-%201%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E6%9C%80%E9%81%A9%E5%8C%96%EF%BC%89%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20quick_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E6%9C%80%E9%81%A9%E5%8C%96%EF%BC%89%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_sorting/radix_sort.md ================================================ https://pythontutor.com/render.html#code=def%20digit%28num%3A%20int%2C%20exp%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%A6%81%E7%B4%A0%20num%20%E3%81%AE%E4%B8%8B%E3%81%8B%E3%82%89%20k%20%E6%A1%81%E7%9B%AE%E3%82%92%E5%8F%96%E5%BE%97%EF%BC%88exp%20%3D%2010%5E%28k-1%29%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E3%81%93%E3%81%93%E3%81%A7%E9%AB%98%E3%82%B3%E3%82%B9%E3%83%88%E3%81%AA%E7%B4%AF%E4%B9%97%E8%A8%88%E7%AE%97%E3%82%92%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%95%E3%81%AA%E3%81%84%E3%82%88%E3%81%86%E3%80%81k%20%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%8F%20exp%20%E3%82%92%E6%B8%A1%E3%81%99%0A%20%20%20%20return%20%28num%20%2F%2F%20exp%29%20%25%2010%0A%0Adef%20counting_sort_digit%28nums%3A%20list%5Bint%5D%2C%20exp%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E8%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88nums%20%E3%81%AE%20k%20%E6%A1%81%E7%9B%AE%E3%81%A7%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%89%22%22%22%0A%20%20%20%20%23%2010%20%E9%80%B2%E6%95%B0%E3%81%AE%E5%90%84%E6%A1%81%E3%81%AF%200~9%20%E3%81%AE%E7%AF%84%E5%9B%B2%E3%81%AA%E3%81%AE%E3%81%A7%E3%80%81%E9%95%B7%E3%81%95%2010%20%E3%81%AE%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E9%85%8D%E5%88%97%E3%81%8C%E5%BF%85%E8%A6%81%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%2010%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%200~9%20%E3%81%AE%E5%90%84%E6%95%B0%E5%AD%97%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E9%9B%86%E8%A8%88%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D%2C%20exp%29%20%20%23%20nums%5Bi%5D%20%E3%81%AE%E7%AC%AC%20k%20%E4%BD%8D%E3%82%92%E5%8F%96%E5%BE%97%E3%81%97%E3%80%81d%20%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20%2B%3D%201%20%20%23%20%E6%95%B0%E5%AD%97%20d%20%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E6%95%B0%E3%81%88%E3%82%8B%0A%20%20%20%20%23%20%E7%B4%AF%E7%A9%8D%E5%92%8C%E3%82%92%E6%B1%82%E3%82%81%E3%80%81%E3%80%8C%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%80%8D%E3%82%92%E3%80%8C%E9%85%8D%E5%88%97%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%80%8D%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%2010%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%5D%20%2B%3D%20counter%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E9%80%86%E9%A0%86%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E5%86%85%E3%81%AE%E9%9B%86%E8%A8%88%E7%B5%90%E6%9E%9C%E3%81%AB%E5%BE%93%E3%81%A3%E3%81%A6%E5%90%84%E8%A6%81%E7%B4%A0%E3%82%92%20res%20%E3%81%AB%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D%2C%20exp%29%0A%20%20%20%20%20%20%20%20j%20%3D%20counter%5Bd%5D%20-%201%20%20%23%20d%20%E3%81%AE%E9%85%8D%E5%88%97%E5%86%85%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20j%20%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20res%5Bj%5D%20%3D%20nums%5Bi%5D%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20j%20%E3%81%AB%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20-%3D%201%20%20%23%20d%20%E3%81%AE%E5%80%8B%E6%95%B0%E3%82%92%201%20%E6%B8%9B%E3%82%89%E3%81%99%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%E3%81%A7%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%82%92%E4%B8%8A%E6%9B%B8%E3%81%8D%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0Adef%20radix_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E6%9C%80%E5%A4%A7%E6%A1%81%E6%95%B0%E3%81%AE%E5%88%A4%E5%AE%9A%E7%94%A8%E3%81%AB%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E5%BE%97%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%20%E4%B8%8B%E4%BD%8D%E6%A1%81%E3%81%8B%E3%82%89%E4%B8%8A%E4%BD%8D%E6%A1%81%E3%81%AE%E9%A0%86%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%99%E3%82%8B%0A%20%20%20%20exp%20%3D%201%0A%20%20%20%20while%20exp%20%3C%3D%20m%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%85%8D%E5%88%97%E8%A6%81%E7%B4%A0%E3%81%AE%20k%20%E6%A1%81%E7%9B%AE%E3%81%AB%E5%AF%BE%E3%81%97%E3%81%A6%E8%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%E3%82%92%E8%A1%8C%E3%81%86%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%201%20-%3E%20exp%20%3D%201%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%202%20-%3E%20exp%20%3D%2010%0A%20%20%20%20%20%20%20%20%23%20%E3%81%A4%E3%81%BE%E3%82%8A%20exp%20%3D%2010%5E%28k-1%29%0A%20%20%20%20%20%20%20%20counting_sort_digit%28nums%2C%20exp%29%0A%20%20%20%20%20%20%20%20exp%20%2A%3D%2010%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%9F%BA%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%0A%20%20%20%20nums%20%3D%20%5B%0A%20%20%20%20%20%20%20%20105%2C%0A%20%20%20%20%20%20%20%20356%2C%0A%20%20%20%20%20%20%20%20428%2C%0A%20%20%20%20%20%20%20%20348%2C%0A%20%20%20%20%20%20%20%20818%2C%0A%20%20%20%20%5D%0A%20%20%20%20radix_sort%28nums%29%0A%20%20%20%20print%28%22%E5%9F%BA%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_sorting/selection_sort.md ================================================ https://pythontutor.com/render.html#code=def%20selection_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%81%B8%E6%8A%9E%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E6%95%B4%E5%88%97%E5%8C%BA%E9%96%93%E3%81%AF%20%5Bi%2C%20n-1%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%B0%8F%E8%A6%81%E7%B4%A0%E3%82%92%E8%A6%8B%E3%81%A4%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20k%20%3D%20i%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3C%20nums%5Bk%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20k%20%3D%20j%20%20%23%20%E6%9C%80%E5%B0%8F%E8%A6%81%E7%B4%A0%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20%23%20%E3%81%9D%E3%81%AE%E6%9C%80%E5%B0%8F%E8%A6%81%E7%B4%A0%E3%82%92%E6%9C%AA%E6%95%B4%E5%88%97%E5%8C%BA%E9%96%93%E3%81%AE%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%A8%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bk%5D%20%3D%20nums%5Bk%5D%2C%20nums%5Bi%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20selection_sort%28nums%29%0A%20%20%20%20print%28%22%E9%81%B8%E6%8A%9E%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_stack_and_queue/array_queue.md ================================================ https://pythontutor.com/render.html#code=class%20ArrayQueue%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20size%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self._nums%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20%2A%20size%0A%20%20%20%20%20%20%20%20self._front%3A%20int%20%3D%200%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self._nums%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20self._size%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self._size%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%8C%E3%81%84%E3%81%A3%E3%81%B1%E3%81%84%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20rear%3A%20int%20%3D%20%28self._front%20%2B%20self._size%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20self._nums%5Brear%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20num%3A%20int%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._front%20%3D%20%28self._front%20%2B%201%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%8C%E7%A9%BA%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20return%20self._nums%5Bself._front%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20self.size%28%29%0A%20%20%20%20%20%20%20%20j%3A%20int%20%3D%20self._front%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20self._nums%5Bj%20%25%20self.capacity%28%29%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20return%20res%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20queue%20%3D%20ArrayQueue%2810%29%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%27%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%20peek%20%3D%27%2C%20peek%29%0A%20%20%20%20pop%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%27%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%97%E3%81%9F%E8%A6%81%E7%B4%A0%20pop%20%3D%27%2C%20pop%29%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%AE%E9%95%B7%E3%81%95%20size%20%3D%27%2C%20size%29%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%20%3D%27%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_stack_and_queue/array_stack.md ================================================ https://pythontutor.com/render.html#code=class%20ArrayStack%3A%0A%20%20%20%20%22%22%22%E9%85%8D%E5%88%97%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20self._stack%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E9%95%B7%E3%81%95%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._stack%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%82%92%E5%88%A4%E5%AE%9A%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%20%3D%3D%20%5B%5D%0A%0A%20%20%20%20def%20push%28self%2C%20item%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%22%22%22%0A%20%20%20%20%20%20%20%20self._stack.append%28item%29%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%9D%E3%83%83%E3%83%97%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%A7%E3%81%99%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack.pop%28%29%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%A7%E3%81%99%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack%5B-1%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A1%A8%E7%A4%BA%E7%94%A8%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E8%BF%94%E3%81%99%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20stack%20%3D%20ArrayStack%28%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E8%A6%81%E7%B4%A0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%9D%E3%83%83%E3%83%97%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E3%83%9D%E3%83%83%E3%83%97%E3%81%97%E3%81%9F%E8%A6%81%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%E3%83%9D%E3%83%83%E3%83%97%E5%BE%8C%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E9%95%B7%E3%81%95%E3%82%92%E5%8F%96%E5%BE%97%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E9%95%B7%E3%81%95%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%82%92%E5%88%A4%E5%AE%9A%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%0A%0Aclass%20LinkedListQueue%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._front%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._rear%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20not%20self._front%0A%0A%20%20%20%20def%20push%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28num%29%0A%20%20%20%20%20%20%20%20if%20self._front%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._front%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear.next%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._front%20%3D%20self._front.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%8C%E7%A9%BA%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20return%20self._front.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20queue%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20temp%20%3D%20self._front%0A%20%20%20%20%20%20%20%20while%20temp%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28temp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20temp.next%0A%20%20%20%20%20%20%20%20return%20queue%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20queue%20%3D%20LinkedListQueue%28%29%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%20%20%20%20print%28%27%E3%82%AD%E3%83%A5%E3%83%BC%20queue%20%3D%27%2C%20queue.to_list%28%29%29%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%27%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%20front%20%3D%27%2C%20peek%29%0A%20%20%20%20pop_front%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%27%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%97%E3%81%9F%E8%A6%81%E7%B4%A0%20pop%20%3D%27%2C%20pop_front%29%0A%20%20%20%20print%28%27%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%97%E3%81%9F%E5%BE%8C%20queue%20%3D%27%2C%20queue.to_list%28%29%29%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%AE%E9%95%B7%E3%81%95%20size%20%3D%27%2C%20size%29%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%20%3D%27%2C%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B6%9A%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0A%0Aclass%20LinkedListStack%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20self._peek%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E9%95%B7%E3%81%95%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%82%92%E5%88%A4%E5%AE%9A%22%22%22%0A%20%20%20%20%20%20%20%20return%20not%20self._peek%0A%0A%20%20%20%20def%20push%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%22%22%22%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28val%29%0A%20%20%20%20%20%20%20%20node.next%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%9D%E3%83%83%E3%83%97%22%22%22%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20self._peek.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%A7%E3%81%99%22%29%0A%20%20%20%20%20%20%20%20return%20self._peek.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A1%A8%E7%A4%BA%E7%94%A8%E3%81%AB%E3%83%AA%E3%82%B9%E3%83%88%E3%81%B8%E5%A4%89%E6%8F%9B%22%22%22%0A%20%20%20%20%20%20%20%20arr%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20node%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20while%20node%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20arr.append%28node.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20node%20%3D%20node.next%0A%20%20%20%20%20%20%20%20arr.reverse%28%29%0A%20%20%20%20%20%20%20%20return%20arr%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20stack%20%3D%20LinkedListStack%28%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E8%A6%81%E7%B4%A0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%9D%E3%83%83%E3%83%97%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E3%83%9D%E3%83%83%E3%83%97%E3%81%97%E3%81%9F%E8%A6%81%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%E3%83%9D%E3%83%83%E3%83%97%E5%BE%8C%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E9%95%B7%E3%81%95%E3%82%92%E5%8F%96%E5%BE%97%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E9%95%B7%E3%81%95%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%82%92%E5%88%A4%E5%AE%9A%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_tree/array_binary_tree.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20ArrayBinaryTree%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20arr%3A%20list%5Bint%20%7C%20None%5D%29%3A%0A%20%20%20%20%20%20%20%20self._tree%20%3D%20list%28arr%29%0A%0A%20%20%20%20def%20size%28self%29%3A%0A%20%20%20%20%20%20%20%20return%20len%28self._tree%29%0A%0A%20%20%20%20def%20val%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20self._tree%5Bi%5D%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%0A%0A%20%20%20%20def%20level_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20dfs%28self%2C%20i%3A%20int%2C%20order%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%27pre%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.left%28i%29%2C%20order%29%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%27in%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.right%28i%29%2C%20order%29%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%27post%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%0A%20%20%20%20def%20pre_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%27pre%27%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20in_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%27in%27%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20post_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%27post%27%29%0A%20%20%20%20%20%20%20%20return%20self.res%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20arr%20%3D%20%5B1%2C%202%2C%203%2C%204%2C%20None%2C%206%2C%20None%5D%0A%20%20%20%20abt%20%3D%20ArrayBinaryTree%28arr%29%0A%20%20%20%20i%20%3D%201%0A%20%20%20%20l%2C%20r%2C%20p%20%3D%20%28abt.left%28i%29%2C%20abt.right%28i%29%2C%20abt.parent%28i%29%29%0A%20%20%20%20res%20%3D%20abt.level_order%28%29%0A%20%20%20%20res%20%3D%20abt.pre_order%28%29%0A%20%20%20%20res%20%3D%20abt.in_order%28%29%0A%20%20%20%20res%20%3D%20abt.post_order%28%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_tree/binary_search_tree.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E6%9C%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%A9%BA%E3%81%AE%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20search%28self%2C%20num%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%20%20%20%20cur%20%3D%20self._root%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E6%8E%A2%E7%B4%A2%E3%81%97%E3%80%81%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%B6%8A%E3%81%88%E3%81%9F%E3%82%89%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AF%20cur%20%E3%81%AE%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AF%20cur%20%E3%81%AE%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20cur.val%20%3E%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E3%83%8E%E3%83%BC%E3%83%89%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%83%AB%E3%83%BC%E3%83%97%E3%82%92%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20return%20cur%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E6%9C%A8%E3%81%8C%E7%A9%BA%E3%81%AA%E3%82%89%E3%80%81%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E6%8E%A2%E7%B4%A2%E3%81%97%E3%80%81%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%B6%8A%E3%81%88%E3%81%9F%E3%82%89%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%87%8D%E8%A4%87%E3%83%8E%E3%83%BC%E3%83%89%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%80%81%E7%9B%B4%E3%81%A1%E3%81%AB%E8%BF%94%E3%81%99%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%81%AF%20cur%20%E3%81%AE%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%81%AF%20cur%20%E3%81%AE%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8E%A2%E7%B4%A2%0A%20%20%20%20node%20%3D%20bst.search%287%29%0A%20%20%20%20print%28%22%5Cn%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AF%3A%20%7B%7D%E3%80%81%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E5%80%A4%20%3D%20%7B%7D%22.format%28node%2C%20node.val%29%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E6%9C%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%A9%BA%E3%81%AE%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E6%9C%A8%E3%81%8C%E7%A9%BA%E3%81%AA%E3%82%89%E3%80%81%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E6%8E%A2%E7%B4%A2%E3%81%97%E3%80%81%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%B6%8A%E3%81%88%E3%81%9F%E3%82%89%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%87%8D%E8%A4%87%E3%83%8E%E3%83%BC%E3%83%89%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%80%81%E7%9B%B4%E3%81%A1%E3%81%AB%E8%BF%94%E3%81%99%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%81%AF%20cur%20%E3%81%AE%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%81%AF%20cur%20%E3%81%AE%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%0A%20%20%20%20bst.insert%2816%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E6%9C%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%20%20%20%20def%20remove%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%89%8A%E9%99%A4%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8E%A2%E7%B4%A2%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20if%20cur%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E6%95%B0%20%3D%200%20or%201%0A%20%20%20%20%20%20%20%20if%20cur.left%20is%20None%20or%20cur.right%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E6%95%B0%E3%81%8C%200%20%2F%201%20%E3%81%AE%E3%81%A8%E3%81%8D%E3%80%81child%20%3D%20null%20%2F%20%E3%81%9D%E3%81%AE%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%0A%20%20%20%20%20%20%20%20%20%20%20%20child%20%3D%20cur.left%20or%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20cur%20%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur%20%21%3D%20self._root%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20pre.left%20%3D%3D%20cur%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20child%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E6%95%B0%20%3D%202%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%AD%E9%A0%86%E8%B5%B0%E6%9F%BB%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%20cur%20%E3%81%AE%E6%AC%A1%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%8F%96%E5%BE%97%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%3A%20TreeNode%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20tmp.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20tmp.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20tmp%20%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E5%89%8A%E9%99%A4%0A%20%20%20%20%20%20%20%20%20%20%20%20self.remove%28tmp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20tmp%20%E3%81%A7%20cur%20%E3%82%92%E4%B8%8A%E6%9B%B8%E3%81%8D%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20cur.val%20%3D%20tmp.val%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%89%8A%E9%99%A4%0A%20%20%20%20bst.remove%281%29%20%23%20%E6%AC%A1%E6%95%B0%200%0A%20%20%20%20bst.remove%282%29%20%23%20%E6%AC%A1%E6%95%B0%201%0A%20%20%20%20bst.remove%284%29%20%23%20%E6%AC%A1%E6%95%B0%202&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_tree/binary_tree_bfs.md ================================================ https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%3A%20%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E6%B7%BB%E5%AD%97%E3%81%8C%E9%85%8D%E5%88%97%E9%95%B7%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%8B%E3%80%81%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%81%8C%20None%20%E3%81%AA%E3%82%89%E3%80%81None%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20level_order%28root%3A%20TreeNode%20%7C%20None%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E3%83%AC%E3%83%99%E3%83%AB%E9%A0%86%E8%B5%B0%E6%9F%BB%22%22%22%0A%20%20%20%20%23%20%E3%82%AD%E3%83%A5%E3%83%BC%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%97%E3%80%81%E3%83%AB%E3%83%BC%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B%0A%20%20%20%20queue%3A%20deque%5BTreeNode%5D%20%3D%20deque%28%29%0A%20%20%20%20queue.append%28root%29%0A%20%20%20%20%23%20%E8%B5%B0%E6%9F%BB%E9%A0%86%E5%BA%8F%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20while%20queue%3A%0A%20%20%20%20%20%20%20%20node%3A%20TreeNode%20%3D%20queue.popleft%28%29%20%20%23%20%E3%83%87%E3%82%AD%E3%83%A5%E3%83%BC%0A%20%20%20%20%20%20%20%20res.append%28node.val%29%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E5%80%A4%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20node.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.left%29%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20if%20node.right%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.right%29%20%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20%23%20%E3%81%93%E3%81%93%E3%81%A7%E3%81%AF%E3%80%81%E9%85%8D%E5%88%97%E3%81%8B%E3%82%89%E7%9B%B4%E6%8E%A5%E4%BA%8C%E5%88%86%E6%9C%A8%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E3%83%AC%E3%83%99%E3%83%AB%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res%20%3D%20level_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E3%83%AC%E3%83%99%E3%83%AB%E9%A0%86%E8%B5%B0%E6%9F%BB%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E5%87%BA%E5%8A%9B%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%20%3D%20%22%2C%20res%29&cumulative=false&curInstr=127&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/pythontutor/chapter_tree/binary_tree_dfs.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%3A%20%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E6%B7%BB%E5%AD%97%E3%81%8C%E9%85%8D%E5%88%97%E9%95%B7%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%8B%E3%80%81%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%81%8C%20None%20%E3%81%AA%E3%82%89%E3%80%81None%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%85%88%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E9%A0%86%E5%BA%8F%EF%BC%9A%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%20-%3E%20%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%20-%3E%20%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20pre_order%28root%3Droot.left%29%0A%20%20%20%20pre_order%28root%3Droot.right%29%0A%0Adef%20in_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E4%B8%AD%E9%A0%86%E8%B5%B0%E6%9F%BB%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%84%AA%E5%85%88%E9%A0%86%3A%20%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%20-%3E%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%20-%3E%20%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%0A%20%20%20%20in_order%28root%3Droot.left%29%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20in_order%28root%3Droot.right%29%0A%0Adef%20post_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%BE%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%84%AA%E5%85%88%E9%A0%86%3A%20%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%20-%3E%20%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%20-%3E%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%0A%20%20%20%20post_order%28root%3Droot.left%29%0A%20%20%20%20post_order%28root%3Droot.right%29%0A%20%20%20%20res.append%28root.val%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20%23%20%E3%81%93%E3%81%93%E3%81%A7%E3%81%AF%E3%80%81%E9%85%8D%E5%88%97%E3%81%8B%E3%82%89%E7%9B%B4%E6%8E%A5%E4%BA%8C%E5%88%86%E6%9C%A8%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%85%88%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20pre_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%85%88%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E5%87%BA%E5%8A%9B%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%20%3D%20%22%2C%20res%29%0A%0A%20%20%20%20%23%20%E4%B8%AD%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20in_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E4%B8%AD%E9%96%93%E9%A0%86%E8%B5%B0%E6%9F%BB%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E5%87%BA%E5%8A%9B%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%20%3D%20%22%2C%20res%29%0A%0A%20%20%20%20%23%20%E5%BE%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20post_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%BE%8C%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E5%87%BA%E5%8A%9B%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%20%3D%20%22%2C%20res%29&cumulative=false&curInstr=129&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ja/codes/ruby/chapter_array_and_linkedlist/array.rb ================================================ =begin File: array.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 要素にランダムアクセス ### def random_access(nums) # 区間 [0, nums.length) からランダムに 1 つの数を選ぶ random_index = Random.rand(0...nums.length) # ランダムな要素を取得して返す nums[random_index] end ### 配列長を拡張 ### # Ruby の Array は動的配列であり、直接拡張できます # 学習しやすいよう、本関数では Array を長さ不変の配列として扱います def extend(nums, enlarge) # 拡張後の長さを持つ配列を初期化する res = Array.new(nums.length + enlarge, 0) # 元の配列の全要素を新しい配列にコピー for i in 0...nums.length res[i] = nums[i] end # 拡張後の新しい配列を返す res end ### 配列のインデックス index に要素 num を挿入 ### def insert(nums, num, index) # インデックス index 以降の全要素を 1 つ後ろへ移動する for i in (nums.length - 1).downto(index + 1) nums[i] = nums[i - 1] end # index の要素に num を代入する nums[index] = num end ### インデックス index の要素を削除 ### def remove(nums, index) # インデックス index より後ろの全要素を 1 つ前へ移動する for i in index...(nums.length - 1) nums[i] = nums[i + 1] end end ### 配列を走査 ### def traverse(nums) count = 0 # インデックスで配列を走査 for i in 0...nums.length count += nums[i] end # 配列要素を直接走査 for num in nums count += num end end ### 配列内の指定要素を検索 ### def find(nums, target) for i in 0...nums.length return i if nums[i] == target end -1 end ### Driver Code ### if __FILE__ == $0 # 配列を初期化 arr = Array.new(5, 0) puts "配列 arr = #{arr}" nums = [1, 3, 2, 5, 4] puts "配列 nums = #{nums}" # ランダムアクセス random_num = random_access(nums) puts "nums からランダムな要素 #{random_num} を取得" # 長さを拡張 nums = extend(nums, 3) puts "配列の長さを 8 に拡張し、nums = #{nums}" # 要素を挿入する insert(nums, 6, 3) puts "インデックス 3 に数値 6 を挿入し、nums = #{nums}" # 要素を削除 remove(nums, 2) puts "インデックス 2 の要素を削除し、nums = #{nums}" # 配列を走査 traverse(nums) # 要素を探索する index = find(nums, 3) puts "nums 内で要素 3 を検索し、インデックス = #{index}" end ================================================ FILE: ja/codes/ruby/chapter_array_and_linkedlist/linked_list.rb ================================================ =begin File: linked_list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' require_relative '../utils/print_util' ### 連結リストのノード n0 の後にノード _p を挿入 ### # Ruby の `p` は組み込み関数で、`P` は定数なので、代わりに `_p` を使える def insert(n0, _p) n1 = n0.next _p.next = n1 n0.next = _p end ### 連結リストのノード n0 の直後のノードを削除 ### def remove(n0) return if n0.next.nil? # n0 -> remove_node -> n1 remove_node = n0.next n1 = remove_node.next n0.next = n1 end ### 連結リスト内の index 番目のノードにアクセス ### def access(head, index) for i in 0...index return nil if head.nil? head = head.next end head end ### 連結リストで値が target の最初のノードを探す ### def find(head, target) index = 0 while head return index if head.val == target head = head.next index += 1 end -1 end ### Driver Code ### if __FILE__ == $0 # 連結リストを初期化する # 各ノードを初期化する n0 = ListNode.new(1) n1 = ListNode.new(3) n2 = ListNode.new(2) n3 = ListNode.new(5) n4 = ListNode.new(4) # ノード間の参照を構築する n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 puts "初期化した連結リストは" print_linked_list(n0) # ノードを挿入 insert(n0, ListNode.new(0)) print_linked_list n0 # ノードを削除 remove(n0) puts "ノード削除後の連結リストは" print_linked_list(n0) # ノードにアクセス node = access(n0, 3) puts "連結リストのインデックス 3 にあるノードの値 = #{node.val}" # ノードを探索 index = find(n0, 2) puts "連結リスト内で値が 2 のノードのインデックス = #{index}" end ================================================ FILE: ja/codes/ruby/chapter_array_and_linkedlist/list.rb ================================================ =begin File: list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # リストを初期化 nums = [1, 3, 2, 5, 4] puts "リスト nums = #{nums}" # 要素にアクセス num = nums[1] puts "インデックス 1 の要素にアクセスし、num = #{num}" # 要素を更新 nums[1] = 0 puts "インデックス 1 の要素を 0 に更新し、nums = #{nums}" # リストを空にする nums.clear puts "リストを空にした後 nums = #{nums}" # 末尾に要素を追加 nums << 1 nums << 3 nums << 2 nums << 5 nums << 4 puts "要素追加後 nums = #{nums}" # 中間に要素を挿入 nums.insert(3, 6) puts "インデックス 3 に要素 6 を挿入し、nums = #{nums}" # 要素を削除 nums.delete_at(3) puts "インデックス 3 の要素を削除し、nums = #{nums}" # インデックスでリストを走査 count = 0 for i in 0...nums.length count += nums[i] end # リスト要素を直接走査 count = 0 nums.each do |x| count += x end # 2 つのリストを連結する nums1 = [6, 8, 7, 10, 9] nums += nums1 puts "リスト nums1 を nums の後ろに連結し、nums = #{nums}" nums = nums.sort { |a, b| a <=> b } puts "リストをソートした後 nums = #{nums}" end ================================================ FILE: ja/codes/ruby/chapter_array_and_linkedlist/my_list.rb ================================================ =begin File: my_list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### リストクラス ### class MyList attr_reader :size # リストの長さを取得(現在の要素数) attr_reader :capacity # リスト容量を取得する ### コンストラクタ ### def initialize @capacity = 10 @size = 0 @extend_ratio = 2 @arr = Array.new(capacity) end ### 要素にアクセス ### def get(index) # インデックスが範囲外なら例外を送出する。以下同様 raise IndexError, "インデックスが範囲外です" if index < 0 || index >= size @arr[index] end ### 要素にアクセス ### def set(index, num) raise IndexError, "インデックスが範囲外です" if index < 0 || index >= size @arr[index] = num end ### 末尾に要素を追加 ### def add(num) # 要素数が容量を超えると、拡張機構が発動する extend_capacity if size == capacity @arr[size] = num # 要素数を更新 @size += 1 end ### 途中に要素を挿入 ### def insert(index, num) raise IndexError, "インデックスが範囲外です" if index < 0 || index >= size # 要素数が容量を超えると、拡張機構が発動する extend_capacity if size == capacity # index 以降の要素をすべて 1 つ後ろへずらす for j in (size - 1).downto(index) @arr[j + 1] = @arr[j] end @arr[index] = num # 要素数を更新 @size += 1 end ### 要素の削除 ### def remove(index) raise IndexError, "インデックスが範囲外です" if index < 0 || index >= size num = @arr[index] # インデックス index より後の要素をすべて 1 つ前に移動する for j in index...size @arr[j] = @arr[j + 1] end # 要素数を更新 @size -= 1 # 削除された要素を返す num end ### リストの容量拡張 ### def extend_capacity # 元の配列の extend_ratio 倍の長さを持つ新しい配列を作成し、元の配列をコピーする arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1)) # リストの容量を更新 @capacity = arr.length end ### リストを配列に変換 ### def to_array sz = size # 有効長の範囲内のリスト要素のみを変換 arr = Array.new(sz) for i in 0...sz arr[i] = get(i) end arr end end ### Driver Code ### if __FILE__ == $0 # リストを初期化 nums = MyList.new # 末尾に要素を追加 nums.add(1) nums.add(3) nums.add(2) nums.add(5) nums.add(4) puts "リスト nums = #{nums.to_array}、容量 = #{nums.capacity}、長さ = #{nums.size}" # 中間に要素を挿入 nums.insert(3, 6) puts "インデックス 3 に数値 6 を挿入し、nums = #{nums.to_array}" # 要素を削除 nums.remove(3) puts "インデックス 3 の要素を削除し、nums = #{nums.to_array}" # 要素にアクセス num = nums.get(1) puts "インデックス 1 の要素にアクセスし、num = #{num}" # 要素を更新 nums.set(1, 0) puts "インデックス 1 の要素を 0 に更新し、nums = #{nums.to_array}" # 拡張機構をテストする for i in 0...10 # i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する nums.add(i) end puts "拡張後のリスト nums = #{nums.to_array}、容量 = #{nums.capacity}、長さ = #{nums.size}" end ================================================ FILE: ja/codes/ruby/chapter_backtracking/n_queens.rb ================================================ =begin File: n_queens.rb Created Time: 2024-05-21 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### バックトラッキング法:Nクイーン ### def backtrack(row, n, state, res, cols, diags1, diags2) # すべての行への配置が完了したら、解を記録する if row == n res << state.map { |row| row.dup } return end # すべての列を走査 for col in 0...n # このマスに対応する主対角線と副対角線を計算 diag1 = row - col + n - 1 diag2 = row + col # 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない if !cols[col] && !diags1[diag1] && !diags2[diag2] # 試行:そのマスにクイーンを置く state[row][col] = "Q" cols[col] = diags1[diag1] = diags2[diag2] = true # 次の行に配置する backtrack(row + 1, n, state, res, cols, diags1, diags2) # 戻す:そのマスを空きマスに戻す state[row][col] = "#" cols[col] = diags1[diag1] = diags2[diag2] = false end end end ### Nクイーンを解く ### def n_queens(n) # n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す state = Array.new(n) { Array.new(n, "#") } cols = Array.new(n, false) # 列にクイーンがあるか記録 diags1 = Array.new(2 * n - 1, false) # 主対角線にクイーンがあるかを記録 diags2 = Array.new(2 * n - 1, false) # 副対角線にクイーンがあるかを記録 res = [] backtrack(0, n, state, res, cols, diags1, diags2) res end ### Driver Code ### if __FILE__ == $0 n = 4 res = n_queens(n) puts "入力した盤面の縦横は #{n}" puts "クイーンの配置方法は全部で #{res.length} 通り" for state in res puts "--------------------" for row in state p row end end end ================================================ FILE: ja/codes/ruby/chapter_backtracking/permutations_i.rb ================================================ =begin File: permutations_i.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### バックトラッキング法:全順列 I ### def backtrack(state, choices, selected, res) # 状態の長さが要素数に等しければ、解を記録 if state.length == choices.length res << state.dup return end # すべての選択肢を走査 choices.each_with_index do |choice, i| # 枝刈り:要素の重複選択を許可しない unless selected[i] # 試行: 選択を行い、状態を更新 selected[i] = true state << choice # 次の選択へ進む backtrack(state, choices, selected, res) # バックトラック:選択を取り消し、前の状態に戻す selected[i] = false state.pop end end end ### 全順列 I ### def permutations_i(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ### Driver Code ### if __FILE__ == $0 nums = [1, 2, 3] res = permutations_i(nums) puts "入力配列 nums = #{nums}" puts "すべての順列 res = #{res}" end ================================================ FILE: ja/codes/ruby/chapter_backtracking/permutations_ii.rb ================================================ =begin File: permutations_ii.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### バックトラッキング法:全順列 II ### def backtrack(state, choices, selected, res) # 状態の長さが要素数に等しければ、解を記録 if state.length == choices.length res << state.dup return end # すべての選択肢を走査 duplicated = Set.new choices.each_with_index do |choice, i| # 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない if !selected[i] && !duplicated.include?(choice) # 試行: 選択を行い、状態を更新 duplicated.add(choice) selected[i] = true state << choice # 次の選択へ進む backtrack(state, choices, selected, res) # バックトラック:選択を取り消し、前の状態に戻す selected[i] = false state.pop end end end ### 全順列 II ### def permutations_ii(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ### Driver Code ### if __FILE__ == $0 nums = [1, 2, 2] res = permutations_ii(nums) puts "入力配列 nums = #{nums}" puts "すべての順列 res = #{res}" end ================================================ FILE: ja/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb ================================================ =begin File: preorder_traversal_i_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 前順走査:例題1 ### def pre_order(root) return unless root # 解を記録 $res << root if root.val == 7 pre_order(root.left) pre_order(root.right) end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\n二分木を初期化" print_tree(root) # 先行順走査 $res = [] pre_order(root) puts "\n値が 7 のノードをすべて出力" p $res.map { |node| node.val } end ================================================ FILE: ja/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb ================================================ =begin File: preorder_traversal_ii_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 前順走査:例題2 ### def pre_order(root) return unless root # 試す $path << root # 解を記録 $res << $path.dup if root.val == 7 pre_order(root.left) pre_order(root.right) # バックトラック $path.pop end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\n二分木を初期化" print_tree(root) # 先行順走査 $path, $res = [], [] pre_order(root) puts "\n根ノードからノード 7 までのすべての経路を出力" for path in $res p path.map { |node| node.val } end end ================================================ FILE: ja/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb ================================================ =begin File: preorder_traversal_iii_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 前順走査:例題3 ### def pre_order(root) # 枝刈り return if !root || root.val == 3 # 試す $path.append(root) # 解を記録 $res << $path.dup if root.val == 7 pre_order(root.left) pre_order(root.right) # バックトラック $path.pop end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\n二分木を初期化" print_tree(root) # 先行順走査 $path, $res = [], [] pre_order(root) puts "\n根ノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含めない" for path in $res p path.map { |node| node.val } end end ================================================ FILE: ja/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb ================================================ =begin File: preorder_traversal_iii_template.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 現在の状態が解かどうかを判定 ### def is_solution?(state) !state.empty? && state.last.val == 7 end ### 解を記録 ### def record_solution(state, res) res << state.dup end ### 現在の状態で、この選択が妥当かを判定 ### def is_valid?(state, choice) choice && choice.val != 3 end ### 状態を更新 ### def make_choice(state, choice) state << choice end ### 状態を復元 ### def undo_choice(state, choice) state.pop end ### バックトラッキング法:例題3 ### def backtrack(state, choices, res) # 解かどうかを確認 record_solution(state, res) if is_solution?(state) # すべての選択肢を走査 for choice in choices # 枝刈り:選択が妥当かを確認する if is_valid?(state, choice) # 試行: 選択を行い、状態を更新 make_choice(state, choice) # 次の選択へ進む backtrack(state, [choice.left, choice.right], res) # バックトラック:選択を取り消し、前の状態に戻す undo_choice(state, choice) end end end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\n二分木を初期化" print_tree(root) # バックトラッキング法 res = [] backtrack([], [root], res) puts "\n根ノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含まないことを条件とする" for path in res p path.map { |node| node.val } end end ================================================ FILE: ja/codes/ruby/chapter_backtracking/subset_sum_i.rb ================================================ =begin File: subset_sum_i.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### バックトラッキング: 部分和 I ### def backtrack(state, target, choices, start, res) # 部分集合の和が target に等しければ、解を記録 if target.zero? res << state.dup return end # すべての選択肢を走査 # 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for i in start...choices.length # 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する # 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため break if target - choices[i] < 0 # 試す:選択を行い、target と start を更新 state << choices[i] # 次の選択へ進む backtrack(state, target - choices[i], choices, i, res) # バックトラック:選択を取り消し、前の状態に戻す state.pop end end ### 部分和 I を解く ### def subset_sum_i(nums, target) state = [] # 状態(部分集合) nums.sort! # nums をソート start = 0 # 開始点を走査 res = [] # 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res) res end ### Driver Code ### if __FILE__ == $0 nums = [3, 4, 5] target = 9 res = subset_sum_i(nums, target) puts "入力配列 = #{nums}, target = #{target}" puts "和が #{target} に等しいすべての部分集合 res = #{res}" end ================================================ FILE: ja/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb ================================================ =begin File: subset_sum_i_naive.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### バックトラッキング: 部分和 I ### def backtrack(state, target, total, choices, res) # 部分集合の和が target に等しければ、解を記録 if total == target res << state.dup return end # すべての選択肢を走査 for i in 0...choices.length # 枝刈り:部分和が target を超える場合はその選択をスキップする next if total + choices[i] > target # 試行:選択を行い、要素と total を更新する state << choices[i] # 次の選択へ進む backtrack(state, target, total + choices[i], choices, res) # バックトラック:選択を取り消し、前の状態に戻す state.pop end end # ## 部分和 I を解く(重複する部分集合を含む)### def subset_sum_i_naive(nums, target) state = [] # 状態(部分集合) total = 0 # 部分和 res = [] # 結果リスト(部分集合のリスト) backtrack(state, target, total, nums, res) res end ### Driver Code ### if __FILE__ == $0 nums = [3, 4, 5] target = 9 res = subset_sum_i_naive(nums, target) puts "入力配列 nums = #{nums}, target = #{target}" puts "和が #{target} に等しいすべての部分集合 res = #{res}" puts "この方法の出力結果には重複した集合が含まれることに注意してください" end ================================================ FILE: ja/codes/ruby/chapter_backtracking/subset_sum_ii.rb ================================================ =begin File: subset_sum_ii.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### バックトラッキング法:部分和 II ### def backtrack(state, target, choices, start, res) # 部分集合の和が target に等しければ、解を記録 if target.zero? res << state.dup return end # すべての選択肢を走査 # 枝刈り 2: start から走査し、重複する部分集合の生成を避ける # 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for i in start...choices.length # 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する # 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため break if target - choices[i] < 0 # 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする next if i > start && choices[i] == choices[i - 1] # 試す:選択を行い、target と start を更新 state << choices[i] # 次の選択へ進む backtrack(state, target - choices[i], choices, i + 1, res) # バックトラック:選択を取り消し、前の状態に戻す state.pop end end ### 部分和 II を解く ### def subset_sum_ii(nums, target) state = [] # 状態(部分集合) nums.sort! # nums をソート start = 0 # 開始点を走査 res = [] # 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res) res end ### Driver Code ### if __FILE__ == $0 nums = [4, 4, 5] target = 9 res = subset_sum_ii(nums, target) puts "入力配列 nums = #{nums}, target = #{target}" puts "和が #{target} に等しいすべての部分集合 res = #{res}" end ================================================ FILE: ja/codes/ruby/chapter_computational_complexity/iteration.rb ================================================ =begin File: iteration.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com), Cy (9738314@gmail.com) =end ### for ループ ### def for_loop(n) res = 0 # 1, 2, ..., n-1, n を順に加算する for i in 1..n res += i end res end ### while ループ ### def while_loop(n) res = 0 i = 1 # 条件変数を初期化する # 1, 2, ..., n-1, n を順に加算する while i <= n res += i i += 1 # 条件変数を更新する end res end # ## while ループ(2 回更新)### def while_loop_ii(n) res = 0 i = 1 # 条件変数を初期化する # 1, 4, 10, ... を順に加算する while i <= n res += i # 条件変数を更新する i += 1 i *= 2 end res end ### 二重 for ループ ### def nested_for_loop(n) res = "" # i = 1, 2, ..., n-1, n とループする for i in 1..n # j = 1, 2, ..., n-1, n とループする for j in 1..n res += "(#{i}, #{j}), " end end res end ### Driver Code ### if __FILE__ == $0 n = 5 res = for_loop(n) puts "\nfor ループの合計結果 res = #{res}" res = while_loop(n) puts "\nwhile ループの合計結果 res = #{res}" res = while_loop_ii(n) puts "\nwhile ループ(2 回更新)の合計結果 res = #{res}" res = nested_for_loop(n) puts "\n二重 for ループの走査結果 #{res}" end ================================================ FILE: ja/codes/ruby/chapter_computational_complexity/recursion.rb ================================================ =begin File: recursion.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 再帰 ### def recur(n) # 終了条件 return 1 if n == 1 # 再帰:再帰呼び出し res = recur(n - 1) # 帰りがけ:結果を返す n + res end ### 反復で再帰をシミュレート ### def for_loop_recur(n) # 明示的なスタックを使ってシステムコールスタックを模擬する stack = [] res = 0 # 再帰:再帰呼び出し for i in n.downto(0) # 「スタックへのプッシュ」で「再帰」を模擬する stack << i end # 帰りがけ:結果を返す while !stack.empty? res += stack.pop end # res = 1+2+3+...+n res end ### 末尾再帰 ### def tail_recur(n, res) # 終了条件 return res if n == 0 # 末尾再帰呼び出し tail_recur(n - 1, res + n) end ### フィボナッチ数列:再帰 ### def fib(n) # 終了条件 f(1) = 0, f(2) = 1 return n - 1 if n == 1 || n == 2 # f(n) = f(n-1) + f(n-2) を再帰的に呼び出す res = fib(n - 1) + fib(n - 2) # 結果 f(n) を返す res end ### Driver Code ### if __FILE__ == $0 n = 5 res = recur(n) puts "\n再帰関数の合計結果 res = #{res}" res = for_loop_recur(n) puts "\n反復で再帰をシミュレートした合計結果 res = #{res}" res = tail_recur(n, 0) puts "\n末尾再帰関数の合計結果 res = #{res}" res = fib(n) puts "\nフィボナッチ数列の第 #{n} 項は #{res}" end ================================================ FILE: ja/codes/ruby/chapter_computational_complexity/space_complexity.rb ================================================ =begin File: space_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 関数 ### def function # 何らかの処理を行う 0 end ### 定数階 ### def constant(n) # 定数、変数、オブジェクトは O(1) の空間を占める a = 0 nums = [0] * 10000 node = ListNode.new # ループ内の変数は O(1) の空間を占める (0...n).each { c = 0 } # ループ内の関数は O(1) の空間を占める (0...n).each { function } end ### 線形階 ### def linear(n) # 長さ n のリストは O(n) の空間を使用 nums = Array.new(n, 0) # 長さ n のハッシュテーブルは O(n) の空間を使用 hmap = {} for i in 0...n hmap[i] = i.to_s end end # ## 線形階(再帰実装)### def linear_recur(n) puts "再帰 n = #{n}" return if n == 1 linear_recur(n - 1) end ### 平方階 ### def quadratic(n) # 二次元リストは O(n^2) の空間を使用 Array.new(n) { Array.new(n, 0) } end # ## 平方階(再帰実装)### def quadratic_recur(n) return 0 unless n > 0 # 配列 nums の長さは n, n-1, ..., 2, 1 nums = Array.new(n, 0) quadratic_recur(n - 1) end # ## 指数階(満二分木を構築)### def build_tree(n) return if n == 0 TreeNode.new.tap do |root| root.left = build_tree(n - 1) root.right = build_tree(n - 1) end end ### Driver Code ### if __FILE__ == $0 n = 5 # 定数階 constant(n) # 線形階 linear(n) linear_recur(n) # 二乗階 quadratic(n) quadratic_recur(n) # 指数オーダー root = build_tree(n) print_tree(root) end ================================================ FILE: ja/codes/ruby/chapter_computational_complexity/time_complexity.rb ================================================ =begin File: time_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 定数階 ### def constant(n) count = 0 size = 100000 (0...size).each { count += 1 } count end ### 線形階 ### def linear(n) count = 0 (0...n).each { count += 1 } count end # ## 線形階(配列を走査)### def array_traversal(nums) count = 0 # ループ回数は配列長に比例する for num in nums count += 1 end count end ### 平方階 ### def quadratic(n) count = 0 # ループ回数はデータサイズ n の二乗に比例する for i in 0...n for j in 0...n count += 1 end end count end # ## 平方階(バブルソート)### def bubble_sort(nums) count = 0 # カウンタ # 外側のループ:未ソート区間は [0, i] for i in (nums.length - 1).downto(0) # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j in 0...i if nums[j] > nums[j + 1] # nums[j] と nums[j + 1] を交換 tmp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = tmp count += 3 # 要素交換には 3 回の単位操作が含まれる end end end count end # ## 指数階(ループ実装)### def exponential(n) count, base = 0, 1 # 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する (0...n).each do (0...base).each { count += 1 } base *= 2 end # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 count end # ## 指数階(再帰実装)### def exp_recur(n) return 1 if n == 1 exp_recur(n - 1) + exp_recur(n - 1) + 1 end # ## 対数階(ループ実装)### def logarithmic(n) count = 0 while n > 1 n /= 2 count += 1 end count end # ## 対数階(再帰実装)### def log_recur(n) return 0 unless n > 1 log_recur(n / 2) + 1 end ### 線形対数時間 ### def linear_log_recur(n) return 1 unless n > 1 count = linear_log_recur(n / 2) + linear_log_recur(n / 2) (0...n).each { count += 1 } count end # ## 階乗階(再帰実装)### def factorial_recur(n) return 1 if n == 0 count = 0 # 1個から n 個に分裂 (0...n).each { count += factorial_recur(n - 1) } count end ### Driver Code ### if __FILE__ == $0 # n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる n = 8 puts "入力データサイズ n = #{n}" count = constant(n) puts "定数時間の操作回数 = #{count}" count = linear(n) puts "線形時間の操作回数 = #{count}" count = array_traversal(Array.new(n, 0)) puts "線形時間(配列走査)の操作回数 = #{count}" count = quadratic(n) puts "二次時間の操作回数 = #{count}" nums = Array.new(n) { |i| n - i } # [n, n-1, ..., 2, 1] count = bubble_sort(nums) puts "二次時間(バブルソート)の操作回数 = #{count}" count = exponential(n) puts "指数時間(ループ実装)の操作回数 = #{count}" count = exp_recur(n) puts "指数時間(再帰実装)の操作回数 = #{count}" count = logarithmic(n) puts "対数時間(ループ実装)の操作回数 = #{count}" count = log_recur(n) puts "対数時間(再帰実装)の操作回数 = #{count}" count = linear_log_recur(n) puts "線形対数時間(再帰実装)の操作回数 = #{count}" count = factorial_recur(n) puts "階乗時間(再帰実装)の操作回数 = #{count}" end ================================================ FILE: ja/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb ================================================ =begin File: worst_best_time_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 1, 2, ..., n を要素とする配列を生成し、順序をシャッフルする ### def random_numbers(n) # 配列 nums =: 1, 2, 3, ..., n を生成する nums = Array.new(n) { |i| i + 1 } # 配列要素をランダムにシャッフル nums.shuffle! end ### 配列 nums 内の数値 1 のインデックスを探す ### def find_one(nums) for i in 0...nums.length # 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる # 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる return i if nums[i] == 1 end -1 end ### Driver Code ### if __FILE__ == $0 for i in 0...10 n = 100 nums = random_numbers(n) index = find_one(nums) puts "\n配列 [ 1, 2, ..., n ] をシャッフルした後 = #{nums}" puts "数字 1 のインデックスは #{index}" end end ================================================ FILE: ja/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb ================================================ =begin File: binary_search_recur.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 二分探索: 問題 f(i, j) ### def dfs(nums, target, i, j) # 区間が空なら対象要素は存在しないので -1 を返す return -1 if i > j # 中点インデックス m を計算 m = (i + j) / 2 if nums[m] < target # 部分問題 f(m+1, j) を再帰的に解く return dfs(nums, target, m + 1, j) elsif nums[m] > target # 部分問題 f(i, m-1) を再帰的に解く return dfs(nums, target, i, m - 1) else # 目標要素が見つかったらそのインデックスを返す return m end end ### 二分探索 ### def binary_search(nums, target) n = nums.length # 問題 f(0, n-1) を解く dfs(nums, target, 0, n - 1) end ### Driver Code ### if __FILE__ == $0 target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # 二分探索(両閉区間) index = binary_search(nums, target) puts "対象要素 6 のインデックス = #{index}" end ================================================ FILE: ja/codes/ruby/chapter_divide_and_conquer/build_tree.rb ================================================ =begin File: build_tree.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 二分木を構築:分割統治 ### def dfs(preorder, inorder_map, i, l, r) # 部分木区間が空なら終了する return if r - l < 0 # ルートノードを初期化する root = TreeNode.new(preorder[i]) # m を求めて左右部分木を分割する m = inorder_map[preorder[i]] # 部分問題:左部分木を構築する root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) # 部分問題:右部分木を構築する root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) # 根ノードを返す root end ### 二分木を構築 ### def build_tree(preorder, inorder) # inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する inorder_map = {} inorder.each_with_index { |val, i| inorder_map[val] = i } dfs(preorder, inorder_map, 0, 0, inorder.length - 1) end ### Driver Code ### if __FILE__ == $0 preorder = [3, 9, 2, 1, 7] inorder = [9, 3, 1, 2, 7] puts "前順走査 = #{preorder}" puts "中順走査 = #{inorder}" root = build_tree(preorder, inorder) puts "構築した二分木は:" print_tree(root) end ================================================ FILE: ja/codes/ruby/chapter_divide_and_conquer/hanota.rb ================================================ =begin File: hanota.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 円盤を1枚移動 ### def move(src, tar) # src の上から円盤を1枚取り出す pan = src.pop # 円盤を tar の上に置く tar << pan end ### ハノイの塔 f(i) を解く ### def dfs(i, src, buf, tar) # src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if i == 1 move(src, tar) return end # 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i - 1, src, tar, buf) # 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src, tar) # 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i - 1, buf, src, tar) end ### ハノイの塔を解く ### def solve_hanota(_A, _B, _C) n = _A.length # A の上から n 枚の円盤を B を介して C へ移す dfs(n, _A, _B, _C) end ### Driver Code ### if __FILE__ == $0 # リスト末尾が柱の頂上 A = [5, 4, 3, 2, 1] B = [] C = [] puts "初期状態:" puts "A = #{A}" puts "B = #{B}" puts "C = #{C}" solve_hanota(A, B, C) puts "円盤の移動完了後:" puts "A = #{A}" puts "B = #{B}" puts "C = #{C}" end ================================================ FILE: ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb ================================================ =begin File: climbing_stairs_backtrack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### バックトラッキング ### def backtrack(choices, state, n, res) # 第 n 段に到達したら、方法数を 1 増やす res[0] += 1 if state == n # すべての選択肢を走査 for choice in choices # 枝刈り: 第 n 段を超えないようにする next if state + choice > n # 試行: 選択を行い、状態を更新 backtrack(choices, state + choice, n, res) end # バックトラック end ### 階段登り:バックトラッキング ### def climbing_stairs_backtrack(n) choices = [1, 2] # 1 段または 2 段上ることを選べる state = 0 # 第 0 段から上り始める res = [0] # res[0] を使って方法数を記録する backtrack(choices, state, n, res) res.first end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_backtrack(n) puts "#{n} 段の階段を上る方法は全部で #{res} 通り" end ================================================ FILE: ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb ================================================ =begin File: climbing_stairs_constraint_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 制約付き階段登り:動的計画法 ### def climbing_stairs_constraint_dp(n) return 1 if n == 1 || n == 2 # 部分問題の解を保存するために dp テーブルを初期化 dp = Array.new(n + 1) { Array.new(3, 0) } # 初期状態:最小部分問題の解をあらかじめ設定 dp[1][1], dp[1][2] = 1, 0 dp[2][1], dp[2][2] = 0, 1 # 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i in 3...(n + 1) dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] end dp[n][1] + dp[n][2] end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_constraint_dp(n) puts "#{n} 段の階段を上る方法は全部で #{res} 通り" end ================================================ FILE: ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb ================================================ =begin File: climbing_stairs_dfs.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 探索 ### def dfs(i) # dp[1] と dp[2] は既知なので返す return i if i == 1 || i == 2 # dp[i] = dp[i-1] + dp[i-2] dfs(i - 1) + dfs(i - 2) end ### 階段登り:探索 ### def climbing_stairs_dfs(n) dfs(n) end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dfs(n) puts "#{n} 段の階段を上る方法は全部で #{res} 通り" end ================================================ FILE: ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb ================================================ =begin File: climbing_stairs_dfs_mem.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### メモ化探索 ### def dfs(i, mem) # dp[1] と dp[2] は既知なので返す return i if i == 1 || i == 2 # dp[i] の記録があれば、それをそのまま返す return mem[i] if mem[i] != -1 # dp[i] = dp[i-1] + dp[i-2] count = dfs(i - 1, mem) + dfs(i - 2, mem) # dp[i] を記録する mem[i] = count end ### 階段登り:メモ化探索 ### def climbing_stairs_dfs_mem(n) # mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す mem = Array.new(n + 1, -1) dfs(n, mem) end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dfs_mem(n) puts "#{n} 段の階段を上る方法は全部で #{res} 通り" end ================================================ FILE: ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb ================================================ =begin File: climbing_stairs_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 階段登り:動的計画法 ### def climbing_stairs_dp(n) return n if n == 1 || n == 2 # 部分問題の解を保存するために dp テーブルを初期化 dp = Array.new(n + 1, 0) # 初期状態:最小部分問題の解をあらかじめ設定 dp[1], dp[2] = 1, 2 # 状態遷移:小さい部分問題から大きい部分問題へ順に解く (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] } dp[n] end ### 階段登り:空間最適化後の動的計画法 ### def climbing_stairs_dp_comp(n) return n if n == 1 || n == 2 a, b = 1, 2 (3...(n + 1)).each { a, b = b, a + b } b end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dp(n) puts "#{n} 段の階段を上る方法は全部で #{res} 通り" res = climbing_stairs_dp_comp(n) puts "#{n} 段の階段を上る方法は全部で #{res} 通り" end ================================================ FILE: ja/codes/ruby/chapter_dynamic_programming/coin_change.rb ================================================ =begin File: coin_change.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### コイン両替:動的計画法 ### def coin_change_dp(coins, amt) n = coins.length _MAX = amt + 1 # dp テーブルを初期化 dp = Array.new(n + 1) { Array.new(amt + 1, 0) } # 状態遷移:先頭行と先頭列 (1...(amt + 1)).each { |a| dp[0][a] = _MAX } # 状態遷移: 残りの行と列 for i in 1...(n + 1) for a in 1...(amt + 1) if coins[i - 1] > a # 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a] else # 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min end end end dp[n][amt] != _MAX ? dp[n][amt] : -1 end ### コイン両替:空間最適化した動的計画法 ### def coin_change_dp_comp(coins, amt) n = coins.length _MAX = amt + 1 # dp テーブルを初期化 dp = Array.new(amt + 1, _MAX) dp[0] = 0 # 状態遷移 for i in 1...(n + 1) # 順方向に走査する for a in 1...(amt + 1) if coins[i - 1] > a # 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a] else # 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min end end end dp[amt] != _MAX ? dp[amt] : -1 end ### Driver Code ### if __FILE__ == $0 coins = [1, 2, 5] amt = 4 # 動的計画法 res = coin_change_dp(coins, amt) puts "目標金額にするために必要な最小硬貨枚数は #{res}" # 空間最適化後の動的計画法 res = coin_change_dp_comp(coins, amt) puts "目標金額にするために必要な最小硬貨枚数は #{res}" end ================================================ FILE: ja/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb ================================================ =begin File: coin_change_ii.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### コイン両替 II:動的計画法 ### def coin_change_ii_dp(coins, amt) n = coins.length # dp テーブルを初期化 dp = Array.new(n + 1) { Array.new(amt + 1, 0) } # 先頭列を初期化する (0...(n + 1)).each { |i| dp[i][0] = 1 } # 状態遷移 for i in 1...(n + 1) for a in 1...(amt + 1) if coins[i - 1] > a # 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a] else # コイン i を選ばない場合と選ぶ場合の和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] end end end dp[n][amt] end ### コイン両替 II:空間最適化した動的計画法 ### def coin_change_ii_dp_comp(coins, amt) n = coins.length # dp テーブルを初期化 dp = Array.new(amt + 1, 0) dp[0] = 1 # 状態遷移 for i in 1...(n + 1) # 順方向に走査する for a in 1...(amt + 1) if coins[i - 1] > a # 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a] else # コイン i を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a - coins[i - 1]] end end end dp[amt] end ### Driver Code ### if __FILE__ == $0 coins = [1, 2, 5] amt = 5 # 動的計画法 res = coin_change_ii_dp(coins, amt) puts "目標金額を作る硬貨の組み合わせ数は #{res}" # 空間最適化後の動的計画法 res = coin_change_ii_dp_comp(coins, amt) puts "目標金額を作る硬貨の組み合わせ数は #{res}" end ================================================ FILE: ja/codes/ruby/chapter_dynamic_programming/edit_distance.rb ================================================ =begin File: edit_distance.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 編集距離:総当たり探索 ### def edit_distance_dfs(s, t, i, j) # s と t がともに空なら 0 を返す return 0 if i == 0 && j == 0 # s が空なら t の長さを返す return j if i == 0 # t が空なら s の長さを返す return i if j == 0 # 2 つの文字が等しければ、その 2 文字をそのままスキップする return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1] # 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 insert = edit_distance_dfs(s, t, i, j - 1) delete = edit_distance_dfs(s, t, i - 1, j) replace = edit_distance_dfs(s, t, i - 1, j - 1) # 最小編集回数を返す [insert, delete, replace].min + 1 end def edit_distance_dfs_mem(s, t, mem, i, j) # s と t がともに空なら 0 を返す return 0 if i == 0 && j == 0 # s が空なら t の長さを返す return j if i == 0 # t が空なら s の長さを返す return i if j == 0 # 記録済みなら、それをそのまま返す return mem[i][j] if mem[i][j] != -1 # 2 つの文字が等しければ、その 2 文字をそのままスキップする return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1] # 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) # 最小編集回数を記録して返す mem[i][j] = [insert, delete, replace].min + 1 end ### 編集距離:動的計画法 ### def edit_distance_dp(s, t) n, m = s.length, t.length dp = Array.new(n + 1) { Array.new(m + 1, 0) } # 状態遷移:先頭行と先頭列 (1...(n + 1)).each { |i| dp[i][0] = i } (1...(m + 1)).each { |j| dp[0][j] = j } # 状態遷移: 残りの行と列 for i in 1...(n + 1) for j in 1...(m +1) if s[i - 1] == t[j - 1] # 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i - 1][j - 1] else # 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1 end end end dp[n][m] end ### 編集距離:空間最適化した動的計画法 ### def edit_distance_dp_comp(s, t) n, m = s.length, t.length dp = Array.new(m + 1, 0) # 状態遷移:先頭行 (1...(m + 1)).each { |j| dp[j] = j } # 状態遷移:残りの行 for i in 1...(n + 1) # 状態遷移:先頭列 leftup = dp.first # dp[i-1, j-1] を一時保存する dp[0] += 1 # 状態遷移:残りの列 for j in 1...(m + 1) temp = dp[j] if s[i - 1] == t[j - 1] # 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftup else # 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = [dp[j - 1], dp[j], leftup].min + 1 end leftup = temp # 次の反復の dp[i-1, j-1] に更新する end end dp[m] end ### Driver Code ### if __FILE__ == $0 s = 'bag' t = 'pack' n, m = s.length, t.length # 全探索 res = edit_distance_dfs(s, t, n, m) puts "#{s} を #{t} に変更するには最小で #{res} 回の編集が必要" # メモ化探索 mem = Array.new(n + 1) { Array.new(m + 1, -1) } res = edit_distance_dfs_mem(s, t, mem, n, m) puts "#{s} を #{t} に変更するには最小で #{res} 回の編集が必要" # 動的計画法 res = edit_distance_dp(s, t) puts "#{s} を #{t} に変更するには最小で #{res} 回の編集が必要" # 空間最適化後の動的計画法 res = edit_distance_dp_comp(s, t) puts "#{s} を #{t} に変更するには最小で #{res} 回の編集が必要" end ================================================ FILE: ja/codes/ruby/chapter_dynamic_programming/knapsack.rb ================================================ =begin File: knapsack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 0-1 ナップサック: 全探索 ### def knapsack_dfs(wgt, val, i, c) # すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す return 0 if i == 0 || c == 0 # ナップサック容量を超える場合は、入れない選択しかできない return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c # 品物 i を入れない場合と入れる場合の最大価値を計算する no = knapsack_dfs(wgt, val, i - 1, c) yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] # 2つの案のうち価値が大きいほうを返す [no, yes].max end ### 0-1 ナップサック: メモ化探索 ### def knapsack_dfs_mem(wgt, val, mem, i, c) # すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す return 0 if i == 0 || c == 0 # 既に記録があればそのまま返す return mem[i][c] if mem[i][c] != -1 # ナップサック容量を超える場合は、入れない選択しかできない return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c # 品物 i を入れない場合と入れる場合の最大価値を計算する no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] # 2 つの案のうち価値が大きい方を記録して返す mem[i][c] = [no, yes].max end ### 0-1 ナップサック: 動的計画法 ### def knapsack_dp(wgt, val, cap) n = wgt.length # dp テーブルを初期化 dp = Array.new(n + 1) { Array.new(cap + 1, 0) } # 状態遷移 for i in 1...(n + 1) for c in 1...(cap + 1) if wgt[i - 1] > c # ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c] else # 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max end end end dp[n][cap] end ### 0-1 ナップサック: 空間最適化後の動的計画法 ### def knapsack_dp_comp(wgt, val, cap) n = wgt.length # dp テーブルを初期化 dp = Array.new(cap + 1, 0) # 状態遷移 for i in 1...(n + 1) # 逆順に走査する for c in cap.downto(1) if wgt[i - 1] > c # ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c] else # 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max end end end dp[cap] end ### Driver Code ### if __FILE__ == $0 wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = wgt.length # 全探索 res = knapsack_dfs(wgt, val, n, cap) puts "ナップサック容量を超えない最大価値は #{res}" # メモ化探索 mem = Array.new(n + 1) { Array.new(cap + 1, -1) } res = knapsack_dfs_mem(wgt, val, mem, n, cap) puts "ナップサック容量を超えない最大価値は #{res}" # 動的計画法 res = knapsack_dp(wgt, val, cap) puts "ナップサック容量を超えない最大価値は #{res}" # 空間最適化後の動的計画法 res = knapsack_dp_comp(wgt, val, cap) puts "ナップサック容量を超えない最大価値は #{res}" end ================================================ FILE: ja/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb ================================================ =begin File: min_cost_climbing_stairs_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 階段登りの最小コスト:動的計画法 ### def min_cost_climbing_stairs_dp(cost) n = cost.length - 1 return cost[n] if n == 1 || n == 2 # 部分問題の解を保存するために dp テーブルを初期化 dp = Array.new(n + 1, 0) # 初期状態:最小部分問題の解をあらかじめ設定 dp[1], dp[2] = cost[1], cost[2] # 状態遷移:小さい部分問題から大きい部分問題へ順に解く (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] } dp[n] end # 階段昇りの最小コスト:空間最適化後の動的計画法 def min_cost_climbing_stairs_dp_comp(cost) n = cost.length - 1 return cost[n] if n == 1 || n == 2 a, b = cost[1], cost[2] (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] } b end ### Driver Code ### if __FILE__ == $0 cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] puts "入力された階段コストのリストは #{cost}" res = min_cost_climbing_stairs_dp(cost) puts "階段を上り切る最小コストは #{res}" res = min_cost_climbing_stairs_dp_comp(cost) puts "階段を上り切る最小コストは #{res}" end ================================================ FILE: ja/codes/ruby/chapter_dynamic_programming/min_path_sum.rb ================================================ =begin File: min_path_sum.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 最小経路和:全探索 ### def min_path_sum_dfs(grid, i, j) # 左上のセルなら探索を終了する return grid[i][j] if i == 0 && j == 0 # 行または列のインデックスが範囲外なら、コスト +∞ を返す return Float::INFINITY if i < 0 || j < 0 # 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する up = min_path_sum_dfs(grid, i - 1, j) left = min_path_sum_dfs(grid, i, j - 1) # 左上隅から (i, j) までの最小経路コストを返す [left, up].min + grid[i][j] end ### 最小経路和:メモ化探索 ### def min_path_sum_dfs_mem(grid, mem, i, j) # 左上のセルなら探索を終了する return grid[0][0] if i == 0 && j == 0 # 行または列のインデックスが範囲外なら、コスト +∞ を返す return Float::INFINITY if i < 0 || j < 0 # 既に記録があればそのまま返す return mem[i][j] if mem[i][j] != -1 # 左と上のセルからの最小経路コスト up = min_path_sum_dfs_mem(grid, mem, i - 1, j) left = min_path_sum_dfs_mem(grid, mem, i, j - 1) # 左上から (i, j) までの最小経路コストを記録して返す mem[i][j] = [left, up].min + grid[i][j] end ### 最小経路和:動的計画法 ### def min_path_sum_dp(grid) n, m = grid.length, grid.first.length # dp テーブルを初期化 dp = Array.new(n) { Array.new(m, 0) } dp[0][0] = grid[0][0] # 状態遷移:先頭行 (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] } # 状態遷移:先頭列 (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] } # 状態遷移: 残りの行と列 for i in 1...n for j in 1...m dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j] end end dp[n -1][m -1] end ### 最小経路和:空間最適化後の動的計画法 ### def min_path_sum_dp_comp(grid) n, m = grid.length, grid.first.length # dp テーブルを初期化 dp = Array.new(m, 0) # 状態遷移:先頭行 dp[0] = grid[0][0] (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] } # 状態遷移:残りの行 for i in 1...n # 状態遷移:先頭列 dp[0] = dp[0] + grid[i][0] # 状態遷移:残りの列 (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] } end dp[m - 1] end ### Driver Code ### if __FILE__ == $0 grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] n, m = grid.length, grid.first.length # 全探索 res = min_path_sum_dfs(grid, n - 1, m - 1) puts "左上から右下までの最小経路和は #{res}" # メモ化探索 mem = Array.new(n) { Array.new(m, - 1) } res = min_path_sum_dfs_mem(grid, mem, n - 1, m -1) puts "左上から右下までの最小経路和は #{res}" # 動的計画法 res = min_path_sum_dp(grid) puts "左上から右下までの最小経路和は #{res}" # 空間最適化後の動的計画法 res = min_path_sum_dp_comp(grid) puts "左上から右下までの最小経路和は #{res}" end ================================================ FILE: ja/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb ================================================ =begin File: unbounded_knapsack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 完全ナップサック:動的計画法 ### def unbounded_knapsack_dp(wgt, val, cap) n = wgt.length # dp テーブルを初期化 dp = Array.new(n + 1) { Array.new(cap + 1, 0) } # 状態遷移 for i in 1...(n + 1) for c in 1...(cap + 1) if wgt[i - 1] > c # ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c] else # 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max end end end dp[n][cap] end # ## 完全ナップサック: 空間最適化後の動的計画法 ##3 def unbounded_knapsack_dp_comp(wgt, val, cap) n = wgt.length # dp テーブルを初期化 dp = Array.new(cap + 1, 0) # 状態遷移 for i in 1...(n + 1) # 順方向に走査する for c in 1...(cap + 1) if wgt[i -1] > c # ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c] else # 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max end end end dp[cap] end ### Driver Code ### if __FILE__ == $0 wgt = [1, 2, 3] val = [5, 11, 15] cap = 4 # 動的計画法 res = unbounded_knapsack_dp(wgt, val, cap) puts "ナップサック容量を超えない最大価値は #{res}" # 空間最適化後の動的計画法 res = unbounded_knapsack_dp_comp(wgt, val, cap) puts "ナップサック容量を超えない最大価値は #{res}" end ================================================ FILE: ja/codes/ruby/chapter_graph/graph_adjacency_list.rb ================================================ =begin File: graph_adjacency_list.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/vertex' ### 隣接リストで実装した無向グラフクラス ### class GraphAdjList attr_reader :adj_list ### コンストラクタ ### def initialize(edges) # 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 @adj_list = {} # すべての頂点と辺を追加 for edge in edges add_vertex(edge[0]) add_vertex(edge[1]) add_edge(edge[0], edge[1]) end end ### 頂点数を取得 ### def size @adj_list.length end ### 辺を追加 ### def add_edge(vet1, vet2) raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) @adj_list[vet1] << vet2 @adj_list[vet2] << vet1 end ### 辺を削除 ### def remove_edge(vet1, vet2) raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) # 辺 vet1 - vet2 を削除 @adj_list[vet1].delete(vet2) @adj_list[vet2].delete(vet1) end ### 頂点を追加 ### def add_vertex(vet) return if @adj_list.include?(vet) # 隣接リストに新しいリストを追加 @adj_list[vet] = [] end ### 頂点を削除 ### def remove_vertex(vet) raise ArgumentError unless @adj_list.include?(vet) # 隣接リストから頂点 vet に対応するリストを削除 @adj_list.delete(vet) # 他の頂点のリストを走査し、vet を含むすべての辺を削除 for vertex in @adj_list @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet) end end ### 隣接リストを出力 ### def __print__ puts '隣接リスト =' for vertex in @adj_list tmp = @adj_list[vertex.first].map { |v| v.val } puts "#{vertex.first.val}: #{tmp}," end end end ### Driver Code ### if __FILE__ == $0 # 無向グラフを初期化 v = vals_to_vets([1, 3, 2, 5, 4]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ] graph = GraphAdjList.new(edges) puts "\n初期化後のグラフは" graph.__print__ # 辺を追加する # 頂点 1、2 は `v[0]`、`v[2]` graph.add_edge(v[0], v[2]) puts "\n辺 1-2 を追加した後のグラフは" graph.__print__ # 辺を削除する # 頂点 1, 3 はそれぞれ v[0], v[1] graph.remove_edge(v[0], v[1]) puts "\n辺 1-3 を削除した後のグラフは" graph.__print__ # 頂点を追加 v5 = Vertex.new(6) graph.add_vertex(v5) puts "\n頂点 6 を追加した後のグラフは" graph.__print__ # 頂点を削除する # 頂点 3 は v[1] graph.remove_vertex(v[1]) puts "\n頂点 3 を削除した後のグラフは" graph.__print__ end ================================================ FILE: ja/codes/ruby/chapter_graph/graph_adjacency_matrix.rb ================================================ =begin File: graph_adjacency_matrix.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/print_util' ### 隣接行列で実装した無向グラフクラス ### class GraphAdjMat def initialize(vertices, edges) ### コンストラクタ ### # 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す @vertices = [] # 隣接行列。行・列のインデックスは「頂点インデックス」に対応 @adj_mat = [] # 頂点を追加 vertices.each { |val| add_vertex(val) } # 辺を追加 # 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する edges.each { |e| add_edge(e[0], e[1]) } end ### 頂点数を取得 ### def size @vertices.length end ### 頂点を追加 ### def add_vertex(val) n = size # 頂点リストに新しい頂点の値を追加 @vertices << val # 隣接行列に 1 行追加 new_row = Array.new(n, 0) @adj_mat << new_row # 隣接行列に 1 列追加 @adj_mat.each { |row| row << 0 } end ### 頂点を削除 ### def remove_vertex(index) raise IndexError if index >= size # 頂点リストから index の頂点を削除する @vertices.delete_at(index) # 隣接行列で index 行を削除する @adj_mat.delete_at(index) # 隣接行列で index 列を削除する @adj_mat.each { |row| row.delete_at(index) } end ### 辺を追加 ### def add_edge(i, j) # パラメータ i, j は vertices の要素インデックスに対応する # 範囲外と同値の場合の処理 if i < 0 || j < 0 || i >= size || j >= size || i == j raise IndexError end # 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす @adj_mat[i][j] = 1 @adj_mat[j][i] = 1 end ### 辺を削除 ### def remove_edge(i, j) # パラメータ i, j は vertices の要素インデックスに対応する # 範囲外と同値の場合の処理 if i < 0 || j < 0 || i >= size || j >= size || i == j raise IndexError end @adj_mat[i][j] = 0 @adj_mat[j][i] = 0 end ### 隣接行列を出力 ### def __print__ puts "頂点リスト = #{@vertices}" puts '隣接行列 =' print_matrix(@adj_mat) end end ### Driver Code ### if __FILE__ == $0 # 無向グラフを初期化する # 注意: edges の要素は頂点インデックスであり、vertices の要素インデックスに対応する vertices = [1, 3, 2, 5, 4] edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] graph = GraphAdjMat.new(vertices, edges) puts "\n初期化後のグラフは" graph.__print__ # 辺を追加する # 頂点 1, 2 のインデックスはそれぞれ 0, 2 graph.add_edge(0, 2) puts "\n辺 1-2 を追加した後のグラフは" graph.__print__ # 辺を削除する # 頂点 1, 3 のインデックスはそれぞれ 0, 1 graph.remove_edge(0, 1) puts "\n辺 1-3 を削除した後のグラフは" graph.__print__ # 頂点を追加 graph.add_vertex(6) puts "\n頂点 6 を追加した後のグラフは" graph.__print__ # 頂点を削除する # 頂点 3 のインデックスは 1 graph.remove_vertex(1) puts "\n頂点 3 を削除した後のグラフは" graph.__print__ end ================================================ FILE: ja/codes/ruby/chapter_graph/graph_bfs.rb ================================================ =begin File: graph_bfs.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require 'set' require_relative './graph_adjacency_list' require_relative '../utils/vertex' ### 幅優先探索 ### def graph_bfs(graph, start_vet) # 指定した頂点の隣接頂点をすべて取得できるよう、隣接リストでグラフを表現する # 頂点の走査順序 res = [] # 訪問済み頂点を記録するためのハッシュ集合 visited = Set.new([start_vet]) # BFS の実装にキューを用いる que = [start_vet] # 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す while que.length > 0 vet = que.shift # 先頭の頂点をデキュー res << vet # 訪問した頂点を記録 # この頂点のすべての隣接頂点を走査 for adj_vet in graph.adj_list[vet] next if visited.include?(adj_vet) # 訪問済みの頂点をスキップ que << adj_vet # 未訪問の頂点のみをキューに追加 visited.add(adj_vet) # この頂点を訪問済みにする end end # 頂点の走査順を返す res end ### Driver Code ### if __FILE__ == $0 # 無向グラフを初期化 v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ] graph = GraphAdjList.new(edges) puts "\n初期化後のグラフは" graph.__print__ # 幅優先探索 res = graph_bfs(graph, v.first) puts "\n幅優先探索(BFS)の頂点順序は" p vets_to_vals(res) end ================================================ FILE: ja/codes/ruby/chapter_graph/graph_dfs.rb ================================================ =begin File: graph_dfs.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require 'set' require_relative './graph_adjacency_list' require_relative '../utils/vertex' ### 深さ優先探索の補助関数 ### def dfs(graph, visited, res, vet) res << vet # 訪問した頂点を記録 visited.add(vet) # この頂点を訪問済みにする # この頂点のすべての隣接頂点を走査 for adj_vet in graph.adj_list[vet] next if visited.include?(adj_vet) # 訪問済みの頂点をスキップ # 隣接頂点を再帰的に訪問 dfs(graph, visited, res, adj_vet) end end ### 深さ優先探索 ### def graph_dfs(graph, start_vet) # 指定した頂点の隣接頂点をすべて取得できるよう、隣接リストでグラフを表現する # 頂点の走査順序 res = [] # 訪問済み頂点を記録するためのハッシュ集合 visited = Set.new dfs(graph, visited, res, start_vet) res end ### Driver Code ### if __FILE__ == $0 # 無向グラフを初期化 v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ] graph = GraphAdjList.new(edges) puts "\n初期化後のグラフは" graph.__print__ # 深さ優先探索 res = graph_dfs(graph, v[0]) puts "\n深さ優先探索(DFS)の頂点順序は" p vets_to_vals(res) end ================================================ FILE: ja/codes/ruby/chapter_greedy/coin_change_greedy.rb ================================================ =begin File: coin_change_greedy.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### コイン両替:貪欲法 ### def coin_change_greedy(coins, amt) # coins リストはソート済みと仮定する i = coins.length - 1 count = 0 # 残額がなくなるまで貪欲選択を繰り返す while amt > 0 # 残額以下で最も近い硬貨を見つける while i > 0 && coins[i] > amt i -= 1 end # coins[i] を選択する amt -= coins[i] count += 1 end # 実行可能な解が見つからなければ `-1` を返す amt == 0 ? count : -1 end ### Driver Code ### if __FILE__ == $0 # 貪欲法:大域最適解を保証できる coins = [1, 5, 10, 20, 50, 100] amt = 186 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "#{amt} にするために必要な最小硬貨枚数は #{res}" # 貪欲法:大域最適解を保証できない coins = [1, 20, 50] amt = 60 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "#{amt} にするために必要な最小硬貨枚数は #{res}" puts "実際に必要な最小個数は 3 、つまり 20 + 20 + 20" # 貪欲法:大域最適解を保証できない coins = [1, 49, 50] amt = 98 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "#{amt} にするために必要な最小硬貨枚数は #{res}" puts "実際に必要な最小個数は 2 、つまり 49 + 49" end ================================================ FILE: ja/codes/ruby/chapter_greedy/fractional_knapsack.rb ================================================ =begin File: fractional_knapsack.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### アイテム ### class Item attr_accessor :w # 品物の重さ attr_accessor :v # 品物の価値 def initialize(w, v) @w = w @v = v end end ### 分数ナップサック:貪欲法 ### def fractional_knapsack(wgt, val, cap) # 重さと価値の 2 属性を持つ品物リストを作成する items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) } # 単位価値 item.v / item.w の高い順にソートする items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) } # 貪欲選択を繰り返す res = 0 for item in items if item.w <= cap # 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる res += item.v cap -= item.w else # 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる res += (item.v.to_f / item.w) * cap # 残り容量がないため、ループを抜ける break end end res end ### Driver Code ### if __FILE__ == $0 wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = wgt.length # 貪欲法 res = fractional_knapsack(wgt, val, cap) puts "ナップサック容量を超えない最大価値は #{res}" end ================================================ FILE: ja/codes/ruby/chapter_greedy/max_capacity.rb ================================================ =begin File: max_capacity.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 最大容量:貪欲法 ### def max_capacity(ht) # i, j を初期化し、それぞれ配列の両端に置く i, j = 0, ht.length - 1 # 初期の最大容量は 0 res = 0 # 2 枚の板が出会うまで貪欲選択を繰り返す while i < j # 最大容量を更新する cap = [ht[i], ht[j]].min * (j - i) res = [res, cap].max # 短い方を内側へ動かす if ht[i] < ht[j] i += 1 else j -= 1 end end res end ### Driver Code ### if __FILE__ == $0 ht = [3, 8, 5, 2, 7, 7, 3, 4] # 貪欲法 res = max_capacity(ht) puts "最大容量は #{res}" end ================================================ FILE: ja/codes/ruby/chapter_greedy/max_product_cutting.rb ================================================ =begin File: max_product_cutting.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 最大分割積:貪欲法 ### def max_product_cutting(n) # n <= 3 のときは、必ず 1 を切り出す return 1 * (n - 1) if n <= 3 # 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする a, b = n / 3, n % 3 # 余りが 1 のときは、1 * 3 を 2 * 2 に変える return (3.pow(a - 1) * 2 * 2).to_i if b == 1 # 余りが 2 のときは、そのままにする return (3.pow(a) * 2).to_i if b == 2 # 余りが 0 のときは、そのままにする 3.pow(a).to_i end ### Driver Code ### if __FILE__ == $0 n = 58 # 貪欲法 res = max_product_cutting(n) puts "最大分割積は #{res}" end ================================================ FILE: ja/codes/ruby/chapter_hashing/array_hash_map.rb ================================================ =begin File: array_hash_map.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### キーと値のペア ### class Pair attr_accessor :key, :val def initialize(key, val) @key = key @val = val end end ### 配列で実装したハッシュテーブル ### class ArrayHashMap ### コンストラクタ ### def initialize # 100 個のバケットを含む配列を初期化 @buckets = Array.new(100) end ### ハッシュ関数 ### def hash_func(key) index = key % 100 end ### 検索操作 ### def get(key) index = hash_func(key) pair = @buckets[index] return if pair.nil? pair.val end ### 追加操作 ### def put(key, val) pair = Pair.new(key, val) index = hash_func(key) @buckets[index] = pair end ### 削除操作 ### def remove(key) index = hash_func(key) # nil に設定し、削除を表す @buckets[index] = nil end ### すべてのキーと値のペアを取得 ### def entry_set result = [] @buckets.each { |pair| result << pair unless pair.nil? } result end ### すべてのキーを取得 ### def key_set result = [] @buckets.each { |pair| result << pair.key unless pair.nil? } result end ### すべての値を取得 ### def value_set result = [] @buckets.each { |pair| result << pair.val unless pair.nil? } result end ### ハッシュテーブルを出力 ### def print @buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? } end end ### Driver Code ### if __FILE__ == $0 # ハッシュテーブルを初期化 hmap = ArrayHashMap.new # 追加操作 # ハッシュテーブルにキーと値の組 (key, value) を追加する hmap.put(12836, "シャオハー") hmap.put(15937, "シャオルオ") hmap.put(16750, "シャオスワン") hmap.put(13276, "シャオファー") hmap.put(10583, "シャオヤー") puts "\n追加完了後、ハッシュテーブルは\nKey -> Value" hmap.print # 検索操作 # ハッシュテーブルにキー `key` を入力し、値 `value` を取得する name = hmap.get(15937) puts "\n学籍番号 15937 を入力すると、名前 #{name} が見つかりました" # 削除操作 # ハッシュテーブルからキーと値の組 (key, value) を削除する hmap.remove(10583) puts "\n10583 を削除した後、ハッシュテーブルは\nKey -> Value" hmap.print # ハッシュテーブルを走査 puts "\nキーと値のペア Key->Value を走査" for pair in hmap.entry_set puts "#{pair.key} -> #{pair.val}" end puts "\nキー Key のみを個別に走査" for key in hmap.key_set puts key end puts "\n値 Value のみを個別に走査" for val in hmap.value_set puts val end end ================================================ FILE: ja/codes/ruby/chapter_hashing/built_in_hash.rb ================================================ =begin File: built_in_hash.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### Driver Code ### if __FILE__ == $0 num = 3 hash_num = num.hash puts "整数 #{num} のハッシュ値は #{hash_num}" bol = true hash_bol = bol.hash puts "ブール値 #{bol} のハッシュ値は #{hash_bol}" dec = 3.14159 hash_dec = dec.hash puts "小数 #{dec} のハッシュ値は #{hash_dec}" str = "Hello アルゴリズム" hash_str = str.hash puts "文字列 #{str} のハッシュ値は #{hash_str}" tup = [12836, 'シャオハー'] hash_tup = tup.hash puts "タプル #{tup} のハッシュ値は #{hash_tup}" obj = ListNode.new(0) hash_obj = obj.hash puts "ノードオブジェクト #{obj} のハッシュ値は #{hash_obj}" end ================================================ FILE: ja/codes/ruby/chapter_hashing/hash_map.rb ================================================ =begin File: hash_map.rb Created Time: 2024-04-14 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/print_util' ### Driver Code ### if __FILE__ == $0 # ハッシュテーブルを初期化 hmap = {} # 追加操作 # ハッシュテーブルにキーと値の組 (key, value) を追加する hmap[12836] = "シャオハー" hmap[15937] = "シャオルオ" hmap[16750] = "シャオスワン" hmap[13276] = "シャオファー" hmap[10583] = "シャオヤー" puts "\n追加完了後、ハッシュテーブルは\nKey -> Value" print_hash_map(hmap) # 検索操作 # ハッシュテーブルにキー key を入力し、値 value を取得する name = hmap[15937] puts "\n学籍番号 15937 を入力すると、名前 #{name} が見つかりました" # 削除操作 # ハッシュテーブルからキーと値の組 (key, value) を削除する hmap.delete(10583) puts "\n10583 を削除した後、ハッシュテーブルは\nKey -> Value" print_hash_map(hmap) # ハッシュテーブルを走査 puts "\nキーと値のペア Key->Value を走査" hmap.entries.each { |key, value| puts "#{key} -> #{value}" } puts "\nキー Key のみを個別に走査" hmap.keys.each { |key| puts key } puts "\n値 Value のみを個別に走査" hmap.values.each { |val| puts val } end ================================================ FILE: ja/codes/ruby/chapter_hashing/hash_map_chaining.rb ================================================ =begin File: hash_map_chaining.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative './array_hash_map' ### キーアドレス法ハッシュテーブル ### class HashMapChaining ### コンストラクタ ### def initialize @size = 0 # キーと値のペア数 @capacity = 4 # ハッシュテーブル容量 @load_thres = 2.0 / 3.0 # リサイズを発動する負荷率のしきい値 @extend_ratio = 2 # 拡張倍率 @buckets = Array.new(@capacity) { [] } # バケット配列 end ### ハッシュ関数 ### def hash_func(key) key % @capacity end ### 負荷率 ### def load_factor @size / @capacity end ### 検索操作 ### def get(key) index = hash_func(key) bucket = @buckets[index] # バケットを走査し、key が見つかれば対応する val を返す for pair in bucket return pair.val if pair.key == key end # `key` が見つからなければ `nil` を返す nil end ### 追加操作 ### def put(key, val) # 負荷率がしきい値を超えたら、リサイズを実行 extend if load_factor > @load_thres index = hash_func(key) bucket = @buckets[index] # バケットを走査し、指定した key が見つかれば対応する val を更新して返す for pair in bucket if pair.key == key pair.val = val return end end # その key が存在しなければ、キーと値のペアを末尾に追加 pair = Pair.new(key, val) bucket << pair @size += 1 end ### 削除操作 ### def remove(key) index = hash_func(key) bucket = @buckets[index] # バケットを走査してキーと値のペアを削除 for pair in bucket if pair.key == key bucket.delete(pair) @size -= 1 break end end end ### ハッシュテーブルを拡張 ### def extend # 元のハッシュテーブルを一時保存 buckets = @buckets # リサイズ後の新しいハッシュテーブルを初期化 @capacity *= @extend_ratio @buckets = Array.new(@capacity) { [] } @size = 0 # キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for bucket in buckets for pair in bucket put(pair.key, pair.val) end end end ### ハッシュテーブルを出力 ### def print for bucket in @buckets res = [] for pair in bucket res << "#{pair.key} -> #{pair.val}" end pp res end end end ### Driver Code ### if __FILE__ == $0 # ## ハッシュテーブルを初期化 hashmap = HashMapChaining.new # 追加操作 # ハッシュテーブルにキーと値の組 (key, value) を追加する hashmap.put(12836, "シャオハー") hashmap.put(15937, "シャオルオ") hashmap.put(16750, "シャオスワン") hashmap.put(13276, "シャオファー") hashmap.put(10583, "シャオヤー") puts "\n追加完了後、ハッシュテーブルは\n[Key1 -> Value1, Key2 -> Value2, ...]" hashmap.print # 検索操作 # ハッシュテーブルにキー key を入力し、値 value を取得する name = hashmap.get(13276) puts "\n学籍番号 13276 を入力すると、名前 #{name} が見つかりました" # 削除操作 # ハッシュテーブルからキーと値の組 (key, value) を削除する hashmap.remove(12836) puts "\n12836 を削除した後、ハッシュテーブルは\n[Key1 -> Value1, Key2 -> Value2, ...]" hashmap.print end ================================================ FILE: ja/codes/ruby/chapter_hashing/hash_map_open_addressing.rb ================================================ =begin File: hash_map_open_addressing.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative './array_hash_map' ### オープンアドレス法ハッシュテーブル ### class HashMapOpenAddressing TOMBSTONE = Pair.new(-1, '-1') # 削除済みマーク ### コンストラクタ ### def initialize @size = 0 # キーと値のペア数 @capacity = 4 # ハッシュテーブル容量 @load_thres = 2.0 / 3.0 # リサイズを発動する負荷率のしきい値 @extend_ratio = 2 # 拡張倍率 @buckets = Array.new(@capacity) # バケット配列 end ### ハッシュ関数 ### def hash_func(key) key % @capacity end ### 負荷率 ### def load_factor @size / @capacity end ### key に対応するバケットインデックスを検索 ### def find_bucket(key) index = hash_func(key) first_tombstone = -1 # 線形プロービングを行い、空バケットに達したら終了 while !@buckets[index].nil? # key が見つかったら、対応するバケットのインデックスを返す if @buckets[index].key == key # 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 if first_tombstone != -1 @buckets[first_tombstone] = @buckets[index] @buckets[index] = TOMBSTONE return first_tombstone # 移動後のバケットインデックスを返す end return index # バケットのインデックスを返す end # 最初に見つかった削除マークを記録 first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE # バケットのインデックスを計算し、末尾を越えたら先頭に戻る index = (index + 1) % @capacity end # key が存在しない場合は追加位置のインデックスを返す first_tombstone == -1 ? index : first_tombstone end ### 検索操作 ### def get(key) # key に対応するバケットインデックスを探す index = find_bucket(key) # キーと値の組が見つかったら、対応する val を返す return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index]) # キーと値のペアが存在しない場合は `nil` を返す nil end ### 追加操作 ### def put(key, val) # 負荷率がしきい値を超えたら、リサイズを実行 extend if load_factor > @load_thres # key に対応するバケットインデックスを探す index = find_bucket(key) # キーと値のペアが見つかった場合は、`val` を上書きして返す unless [nil, TOMBSTONE].include?(@buckets[index]) @buckets[index].val = val return end # キーと値の組が存在しない場合は、その組を追加する @buckets[index] = Pair.new(key, val) @size += 1 end ### 削除操作 ### def remove(key) # key に対応するバケットインデックスを探す index = find_bucket(key) # キーと値の組が見つかったら、削除マーカーで上書きする unless [nil, TOMBSTONE].include?(@buckets[index]) @buckets[index] = TOMBSTONE @size -= 1 end end ### ハッシュテーブルを拡張 ### def extend # 元のハッシュテーブルを一時保存 buckets_tmp = @buckets # リサイズ後の新しいハッシュテーブルを初期化 @capacity *= @extend_ratio @buckets = Array.new(@capacity) @size = 0 # キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for pair in buckets_tmp put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair) end end ### ハッシュテーブルを出力 ### def print for pair in @buckets if pair.nil? puts "Nil" elsif pair == TOMBSTONE puts "TOMBSTONE" else puts "#{pair.key} -> #{pair.val}" end end end end ### Driver Code ### if __FILE__ == $0 # ハッシュテーブルを初期化 hashmap = HashMapOpenAddressing.new # 追加操作 # ハッシュテーブルにキーと値の組 (key, val) を追加する hashmap.put(12836, "シャオハー") hashmap.put(15937, "シャオルオ") hashmap.put(16750, "シャオスワン") hashmap.put(13276, "シャオファー") hashmap.put(10583, "シャオヤー") puts "\n追加完了後、ハッシュテーブルは\nKey -> Value" hashmap.print # 検索操作 # ハッシュテーブルにキー key を入力し、値 val を得る name = hashmap.get(13276) puts "\n学籍番号 13276 を入力すると、名前 #{name} が見つかりました" # 削除操作 # ハッシュテーブルからキーと値の組 (key, val) を削除する hashmap.remove(16750) puts "\n16750 を削除した後、ハッシュテーブルは\nKey -> Value" hashmap.print end ================================================ FILE: ja/codes/ruby/chapter_hashing/simple_hash.rb ================================================ =begin File: simple_hash.rb Created Time: 2024-04-14 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 加算ハッシュ ### def add_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash += c.ord } hash % modulus end ### 乗算ハッシュ ### def mul_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash = 31 * hash + c.ord } hash % modulus end ### XOR ハッシュ ### def xor_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash ^= c.ord } hash % modulus end ### 回転ハッシュ ### def rot_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord } hash % modulus end ### Driver Code ### if __FILE__ == $0 key = "Hello アルゴリズム" hash = add_hash(key) puts "加算ハッシュ値は #{hash}" hash = mul_hash(key) puts "乗算ハッシュ値は #{hash}" hash = xor_hash(key) puts "XORハッシュ値は #{hash}" hash = rot_hash(key) puts "ローテーションハッシュ値は #{hash}" end ================================================ FILE: ja/codes/ruby/chapter_heap/my_heap.rb ================================================ =begin File: my_heap.rb Created Time: 2024-04-19 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/print_util' ### 最大ヒープ ### class MaxHeap attr_reader :max_heap ### コンストラクタ。入力リストに基づいてヒープを構築 ### def initialize(nums) # リスト要素をそのままヒープに追加 @max_heap = nums # 葉ノード以外のすべてのノードをヒープ化 parent(size - 1).downto(0) do |i| sift_down(i) end end ### 左子ノードのインデックスを取得 ### def left(i) 2 * i + 1 end ### 右子ノードのインデックスを取得 ### def right(i) 2 * i + 2 end ### 親ノードのインデックスを取得 ### def parent(i) (i - 1) / 2 # 切り捨て除算 end ### 要素を交換 ### def swap(i, j) @max_heap[i], @max_heap[j] = @max_heap[j], @max_heap[i] end ### ヒープのサイズを取得 ### def size @max_heap.length end ### ヒープが空かどうかを判定 ### def is_empty? size == 0 end ### ヒープ先頭要素を参照 ### def peek @max_heap[0] end ### 要素をヒープに挿入 ### def push(val) # ノードを追加 @max_heap << val # 下から上へヒープ化 sift_up(size - 1) end ### ノード i から下から上へヒープ化 ### def sift_up(i) loop do # ノード i の親ノードを取得 p = parent(i) # 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 break if p < 0 || @max_heap[i] <= @max_heap[p] # 2 つのノードを交換 swap(i, p) # ループで下から上へヒープ化 i = p end end ### 要素をヒープから取り出す ### def pop # 空判定の処理 raise IndexError, "ヒープが空です" if is_empty? # 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) swap(0, size - 1) # ノードを削除 val = @max_heap.pop # 上から下へヒープ化 sift_down(0) # ヒープ先頭要素を返す val end ### ノード i から上から下へヒープ化 ### def sift_down(i) loop do # ノード i, l, r のうち値が最大のノードを ma とする l, r, ma = left(i), right(i), i ma = l if l < size && @max_heap[l] > @max_heap[ma] ma = r if r < size && @max_heap[r] > @max_heap[ma] # ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける break if ma == i # 2 つのノードを交換 swap(i, ma) # ループで上から下へヒープ化 i = ma end end # ## ヒープを表示(二分木)### def __print__ print_heap(@max_heap) end end ### Driver Code ### if __FILE__ == $0 # 最大ヒープを初期化 max_heap = MaxHeap.new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) puts "\nリストを入力してヒープを構築した後" max_heap.__print__ # ヒープ頂点の要素を取得 peek = max_heap.peek puts "\nヒープの先頭要素は #{peek}" # 要素をヒープに追加 val = 7 max_heap.push(val) puts "\n要素 #{val} をヒープに追加した後" max_heap.__print__ # ヒープ頂点の要素を取り出す peek = max_heap.pop puts "\nヒープの先頭要素 #{peek} を取り出した後" max_heap.__print__ # ヒープのサイズを取得 size = max_heap.size puts "\nヒープ要素数は #{size}" # ヒープが空かどうかを判定 is_empty = max_heap.is_empty? puts "\nヒープが空かどうかは #{is_empty}" end ================================================ FILE: ja/codes/ruby/chapter_heap/top_k.rb ================================================ =begin File: top_k.rb Created Time: 2024-04-19 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative "./my_heap" ### 要素をヒープに挿入 ### def push_min_heap(heap, val) # 要素を反転する heap.push(-val) end ### 要素をヒープから取り出す ### def pop_min_heap(heap) # 要素を反転する -heap.pop end ### ヒープ先頭要素を参照 ### def peek_min_heap(heap) # 要素を反転する -heap.peek end ### ヒープから要素を取り出す ### def get_min_heap(heap) # ヒープ内のすべての要素を反転 heap.max_heap.map { |x| -x } end ### ヒープに基づいて配列中の最大 k 個の要素を探す ### def top_k_heap(nums, k) # 最小ヒープを初期化する # 注意: ヒープ内の全要素を反転し、最大ヒープで最小ヒープをシミュレートする max_heap = MaxHeap.new([]) # 配列の先頭 k 個の要素をヒープに追加 for i in 0...k push_min_heap(max_heap, nums[i]) end # k+1 番目の要素から開始し、ヒープ長を k に保つ for i in k...nums.length # 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する if nums[i] > peek_min_heap(max_heap) pop_min_heap(max_heap) push_min_heap(max_heap, nums[i]) end end get_min_heap(max_heap) end ### Driver Code ### if __FILE__ == $0 nums = [1, 7, 6, 3, 2] k = 3 res = top_k_heap(nums, k) puts "最大の #{k} 個の要素は" print_heap(res) end ================================================ FILE: ja/codes/ruby/chapter_searching/binary_search.rb ================================================ =begin File: binary_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### 二分探索(両閉区間) ### def binary_search(nums, target) # 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す i, j = 0, nums.length - 1 # ループし、探索区間が空になったら終了する(i > j で空) while i <= j # 理論上、Ruby の数値は無限に大きくできるため(メモリ容量に依存)、大きな数のオーバーフローを考慮する必要はない m = (i + j) / 2 # 中点インデックス m を計算 if nums[m] < target i = m + 1 # この場合、target は区間 [m+1, j] にある elsif nums[m] > target j = m - 1 # この場合、target は区間 [i, m-1] にある else return m # 目標要素が見つかったらそのインデックスを返す end end -1 # 目標要素が見つからなければ -1 を返す end ### 二分探索(左閉右開区間) ### def binary_search_lcro(nums, target) # 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す i, j = 0, nums.length # ループし、探索区間が空になったら終了する(i = j で空) while i < j # 中点インデックス m を計算 m = (i + j) / 2 if nums[m] < target i = m + 1 # この場合、target は区間 [m+1, j) にある elsif nums[m] > target j = m - 1 # この場合、target は区間 [i, m) にある else return m # 目標要素が見つかったらそのインデックスを返す end end -1 # 目標要素が見つからなければ -1 を返す end ### Driver Code ### if __FILE__ == $0 target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # 二分探索(両閉区間) index = binary_search(nums, target) puts "対象要素 6 のインデックス = #{index}" # 二分探索(左閉右開区間) index = binary_search_lcro(nums, target) puts "対象要素 6 のインデックス = #{index}" end ================================================ FILE: ja/codes/ruby/chapter_searching/binary_search_edge.rb ================================================ =begin File: binary_search_edge.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative './binary_search_insertion' ### target の最左位置を二分探索 ### def binary_search_left_edge(nums, target) # target の挿入位置を探すのと等価 i = binary_search_insertion(nums, target) # target が見つからなければ、-1 を返す return -1 if i == nums.length || nums[i] != target i # target が見つかったら、インデックス i を返す end ### target の最右位置を二分探索 ### def binary_search_right_edge(nums, target) # 最左の target + 1 を探す問題に変換する i = binary_search_insertion(nums, target + 1) # j は最も右の target を指し、i は target より大きい最初の要素を指す j = i - 1 # target が見つからなければ、-1 を返す return -1 if j == -1 || nums[j] != target j # target が見つかったら、インデックス j を返す end ### Driver Code ### if __FILE__ == $0 # 重複要素を含む配列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] puts "\n配列 nums = #{nums}" # 二分探索で左端と右端を探す for target in [6, 7] index = binary_search_left_edge(nums, target) puts "最も左の要素 #{target} のインデックスは #{index}" index = binary_search_right_edge(nums, target) puts "最も右の要素 #{target} のインデックスは #{index}" end end ================================================ FILE: ja/codes/ruby/chapter_searching/binary_search_insertion.rb ================================================ =begin File: binary_search_insertion.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### 二分探索の挿入位置(重複要素なし) ### def binary_search_insertion_simple(nums, target) # 両閉区間 [0, n-1] を初期化 i, j = 0, nums.length - 1 while i <= j # 中点インデックス m を計算 m = (i + j) / 2 if nums[m] < target i = m + 1 # target は区間 [m+1, j] にある elsif nums[m] > target j = m - 1 # target は区間 [i, m-1] にある else return m # target が見つかったら、挿入位置 m を返す end end i # target が見つからなければ、挿入位置 i を返す end ### 二分探索の挿入位置(重複要素あり) ### def binary_search_insertion(nums, target) # 両閉区間 [0, n-1] を初期化 i, j = 0, nums.length - 1 while i <= j # 中点インデックス m を計算 m = (i + j) / 2 if nums[m] < target i = m + 1 # target は区間 [m+1, j] にある elsif nums[m] > target j = m - 1 # target は区間 [i, m-1] にある else j = m - 1 # target より小さい最初の要素は区間 [i, m-1] にある end end i # 挿入位置 i を返す end ### Driver Code ### if __FILE__ == $0 # 重複要素のない配列 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] puts "\n配列 nums = #{nums}" # 二分探索で挿入位置を探す for target in [6, 9] index = binary_search_insertion_simple(nums, target) puts "要素 #{target} の挿入位置のインデックスは #{index}" end # 重複要素を含む配列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] puts "\n配列 nums = #{nums}" # 二分探索で挿入位置を探す for target in [2, 6, 20] index = binary_search_insertion(nums, target) puts "要素 #{target} の挿入位置のインデックスは #{index}" end end ================================================ FILE: ja/codes/ruby/chapter_searching/hashing_search.rb ================================================ =begin File: hashing_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/list_node' ### ハッシュ検索(配列) ### def hashing_search_array(hmap, target) # ハッシュテーブルの key: 目標要素、value: インデックス # ハッシュテーブルにこの key がなければ -1 を返す hmap[target] || -1 end ### ハッシュ検索(連結リスト) ### def hashing_search_linkedlist(hmap, target) # ハッシュテーブルの key: 対象要素、value: ノードオブジェクト # ハッシュテーブルにこの key がなければ None を返す hmap[target] || nil end ### Driver Code ### if __FILE__ == $0 target = 3 # ハッシュ探索(配列) nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] # ハッシュテーブルを初期化 map0 = {} for i in 0...nums.length map0[nums[i]] = i # key: 要素、value: インデックス end index = hashing_search_array(map0, target) puts "対象要素 3 のインデックス = #{index}" # ハッシュ探索(連結リスト) head = arr_to_linked_list(nums) # ハッシュテーブルを初期化 map1 = {} while head map1[head.val] = head head = head.next end node = hashing_search_linkedlist(map1, target) puts "対象ノード値 3 に対応するノードオブジェクトは #{node}" end ================================================ FILE: ja/codes/ruby/chapter_searching/linear_search.rb ================================================ =begin File: linear_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/list_node' ### 線形探索(配列) ### def linear_search_array(nums, target) # 配列を走査 for i in 0...nums.length return i if nums[i] == target # 目標要素が見つかったらそのインデックスを返す end -1 # 目標要素が見つからなければ -1 を返す end ### 線形探索(連結リスト) ### def linear_search_linkedlist(head, target) # 連結リストを走査 while head return head if head.val == target # 対象ノードが見つかったら、それを返す head = head.next end nil # 対象ノードが見つからない場合は None を返す end ### Driver Code ### if __FILE__ == $0 target = 3 # 配列で線形探索を行う nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] index = linear_search_array(nums, target) puts "対象要素 3 のインデックス = #{index}" # 連結リストで線形探索を行う head = arr_to_linked_list(nums) node = linear_search_linkedlist(head, target) puts "対象ノード値 3 に対応するノードオブジェクトは #{node}" end ================================================ FILE: ja/codes/ruby/chapter_searching/two_sum.rb ================================================ =begin File: two_sum.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### 方法1:総当たり列挙 ### def two_sum_brute_force(nums, target) # 2重ループのため、時間計算量は O(n^2) for i in 0...(nums.length - 1) for j in (i + 1)...nums.length return [i, j] if nums[i] + nums[j] == target end end [] end ### 方法2:補助ハッシュテーブル ### def two_sum_hash_table(nums, target) # 補助ハッシュテーブルを使用し、空間計算量は O(n) dic = {} # 単一ループで、時間計算量は O(n) for i in 0...nums.length return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i]) dic[nums[i]] = i end [] end ### Driver Code ### if __FILE__ == $0 # ======= Test Case ======= nums = [2, 7, 11, 15] target = 13 # ====== Driver Code ====== # 方法 1 res = two_sum_brute_force(nums, target) puts "方法1 res = #{res}" # 方法 2 res = two_sum_hash_table(nums, target) puts "方法2 res = #{res}" end ================================================ FILE: ja/codes/ruby/chapter_sorting/bubble_sort.rb ================================================ =begin File: bubble_sort.rb Created Time: 2024-05-02 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### バブルソート ### def bubble_sort(nums) n = nums.length # 外側のループ:未ソート区間は [0, i] for i in (n - 1).downto(1) # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j in 0...i if nums[j] > nums[j + 1] # nums[j] と nums[j + 1] を交換 nums[j], nums[j + 1] = nums[j + 1], nums[j] end end end end # ## バブルソート(フラグ最適化)### def bubble_sort_with_flag(nums) n = nums.length # 外側のループ:未ソート区間は [0, i] for i in (n - 1).downto(1) flag = false # フラグを初期化する # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j in 0...i if nums[j] > nums[j + 1] # nums[j] と nums[j + 1] を交換 nums[j], nums[j + 1] = nums[j + 1], nums[j] flag = true # 交換する要素を記録 end end break unless flag # このバブル処理で要素交換が一度もなければそのまま終了 end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] bubble_sort(nums) puts "バブルソート完了後 nums = #{nums}" nums1 = [4, 1, 3, 1, 5, 2] bubble_sort_with_flag(nums1) puts "バブルソート完了後 nums = #{nums1}" end ================================================ FILE: ja/codes/ruby/chapter_sorting/bucket_sort.rb ================================================ =begin File: bucket_sort.rb Created Time: 2024-04-17 Author: Martin Xu (martin.xus@gmail.com) =end ### バケットソート ### def bucket_sort(nums) # k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする k = nums.length / 2 buckets = Array.new(k) { [] } # 1. 配列要素を各バケットに振り分ける nums.each do |num| # 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する i = (num * k).to_i # num をバケット i に追加 buckets[i] << num end # 2. 各バケットをソートする buckets.each do |bucket| # 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい bucket.sort! end # 3. バケットを走査して結果を結合 i = 0 buckets.each do |bucket| bucket.each do |num| nums[i] = num i += 1 end end end ### Driver Code ### if __FILE__ == $0 # 入力データは範囲 [0, 1) の浮動小数点数とする nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] bucket_sort(nums) puts "バケットソート完了後 nums = #{nums}" end ================================================ FILE: ja/codes/ruby/chapter_sorting/counting_sort.rb ================================================ =begin File: counting_sort.rb Created Time: 2024-05-02 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 計数ソート ### def counting_sort_naive(nums) # 簡易版。オブジェクトのソートには使えない # 1. 配列の最大要素 m を求める m = 0 nums.each { |num| m = [m, num].max } # 2. 各数値の出現回数を数える # counter[num] は num の出現回数を表す counter = Array.new(m + 1, 0) nums.each { |num| counter[num] += 1 } # 3. counter を走査し、各要素を元の配列 nums に書き戻す i = 0 for num in 0...(m + 1) (0...counter[num]).each do nums[i] = num i += 1 end end end ### 計数ソート ### def counting_sort(nums) # 完全版。オブジェクトをソートでき、かつ安定ソートである # 1. 配列の最大要素 m を求める m = nums.max # 2. 各数値の出現回数を数える # counter[num] は num の出現回数を表す counter = Array.new(m + 1, 0) nums.each { |num| counter[num] += 1 } # 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する # つまり counter[num]-1 は、num が res に最後に現れるインデックス (0...m).each { |i| counter[i + 1] += counter[i] } # 4. nums を逆順に走査し、各要素を結果配列 res に格納する # 結果を記録するための配列 res を初期化する n = nums.length res = Array.new(n, 0) (n - 1).downto(0).each do |i| num = nums[i] res[counter[num] - 1] = num # num を対応するインデックスに配置 counter[num] -= 1 # 累積和を 1 減らして、次に num を配置するインデックスを得る end # 結果配列 res で元の配列 nums を上書きする (0...n).each { |i| nums[i] = res[i] } end ### Driver Code ### if __FILE__ == $0 nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort_naive(nums) puts "カウントソート(オブジェクトをソートできない)完了後 nums = #{nums}" nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort(nums1) puts "カウントソート完了後 nums1 = #{nums1}" end ================================================ FILE: ja/codes/ruby/chapter_sorting/heap_sort.rb ================================================ =begin File: heap_sort.rb Created Time: 2024-04-10 Author: junminhong (junminhong1110@gmail.com) =end ### ヒープ長 n で、ノード i から上から下へヒープ化 ### def sift_down(nums, n, i) while true # ノード i, l, r のうち値が最大のノードを ma とする l = 2 * i + 1 r = 2 * i + 2 ma = i ma = l if l < n && nums[l] > nums[ma] ma = r if r < n && nums[r] > nums[ma] # ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける break if ma == i # 2 つのノードを交換 nums[i], nums[ma] = nums[ma], nums[i] # ループで上から下へヒープ化 i = ma end end ### ヒープソート ### def heap_sort(nums) # ヒープ構築:葉ノード以外のすべてのノードをヒープ化する (nums.length / 2 - 1).downto(0) do |i| sift_down(nums, nums.length, i) end # ヒープから最大要素を取り出し、n-1 回繰り返す (nums.length - 1).downto(1) do |i| # 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) nums[0], nums[i] = nums[i], nums[0] # 根ノードを起点に、上から下へヒープ化 sift_down(nums, i, 0) end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] heap_sort(nums) puts "ヒープソート完了後 nums = #{nums.inspect}" end ================================================ FILE: ja/codes/ruby/chapter_sorting/insertion_sort.rb ================================================ =begin File: insertion_sort.rb Created Time: 2024-04-02 Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 挿入ソート ### def insertion_sort(nums) n = nums.length # 外側ループ:整列済み区間は [0, i-1] for i in 1...n base = nums[i] j = i - 1 # 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する while j >= 0 && nums[j] > base nums[j + 1] = nums[j] # nums[j] を 1 つ右へ移動する j -= 1 end nums[j + 1] = base # base を正しい位置に配置する end end ### Driver Code ### nums = [4, 1, 3, 1, 5, 2] insertion_sort(nums) puts "挿入ソート完了後 nums = #{nums}" ================================================ FILE: ja/codes/ruby/chapter_sorting/merge_sort.rb ================================================ =begin File: merge_sort.rb Created Time: 2024-04-10 Author: junminhong (junminhong1110@gmail.com) =end ### 左部分配列と右部分配列をマージ ### def merge(nums, left, mid, right) # 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] # マージ結果を格納する一時配列 tmp を作成 tmp = Array.new(right - left + 1, 0) # 左右の部分配列の開始インデックスを初期化する i, j, k = left, mid + 1, 0 # 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする while i <= mid && j <= right if nums[i] <= nums[j] tmp[k] = nums[i] i += 1 else tmp[k] = nums[j] j += 1 end k += 1 end # 左右の部分配列の残り要素を一時配列にコピーする while i <= mid tmp[k] = nums[i] i += 1 k += 1 end while j <= right tmp[k] = nums[j] j += 1 k += 1 end # 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする (0...tmp.length).each do |k| nums[left + k] = tmp[k] end end ### マージソート ### def merge_sort(nums, left, right) # 終了条件 # 部分配列の長さが 1 になったら再帰を終了する return if left >= right # 分割フェーズ mid = left + (right - left) / 2 # 中点を計算 merge_sort(nums, left, mid) # 左部分配列を再帰処理 merge_sort(nums, mid + 1, right) # 右部分配列を再帰処理 # マージフェーズ merge(nums, left, mid, right) end ### Driver Code ### if __FILE__ == $0 nums = [7, 3, 2, 6, 0, 1, 5, 4] merge_sort(nums, 0, nums.length - 1) puts "マージソート完了後 nums = #{nums.inspect}" end ================================================ FILE: ja/codes/ruby/chapter_sorting/quick_sort.rb ================================================ =begin File: quick_sort.rb Created Time: 2024-04-01 Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### クイックソートクラス ### class QuickSort class << self ### 番兵分割 ### def partition(nums, left, right) # nums[left] を基準値とする i, j = left, right while i < j while i < j && nums[j] >= nums[left] j -= 1 # 右から左へ基準値未満の最初の要素を探す end while i < j && nums[i] <= nums[left] i += 1 # 左から右へ基準値より大きい最初の要素を探す end # 要素の交換 nums[i], nums[j] = nums[j], nums[i] end # 基準値を 2 つの部分配列の境界へ交換する nums[i], nums[left] = nums[left], nums[i] i # 基準値のインデックスを返す end ### クイックソートクラス ### def quick_sort(nums, left, right) # 部分配列の長さが 1 でない場合は再帰する if left < right # 番兵分割 pivot = partition(nums, left, right) # 左右の部分配列を再帰処理 quick_sort(nums, left, pivot - 1) quick_sort(nums, pivot + 1, right) end nums end end end # ## クイックソートクラス(中央値最適化)### class QuickSortMedian class << self ### 3 つの候補要素の中央値を選ぶ ### def median_three(nums, left, mid, right) # 3つの候補要素の中央値を選ぶ _l, _m, _r = nums[left], nums[mid], nums[right] # m は l と r の間 return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l) # l は m と r の間 return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m) return right end # ## 番兵分割(三数中央値)### def partition(nums, left, right) # ## nums[left] を基準値とする med = median_three(nums, left, (left + right) / 2, right) # 中央値を配列の最左端に交換する nums[left], nums[med] = nums[med], nums[left] i, j = left, right while i < j while i < j && nums[j] >= nums[left] j -= 1 # 右から左へ基準値未満の最初の要素を探す end while i < j && nums[i] <= nums[left] i += 1 # 左から右へ基準値より大きい最初の要素を探す end # 要素の交換 nums[i], nums[j] = nums[j], nums[i] end # 基準値を 2 つの部分配列の境界へ交換する nums[i], nums[left] = nums[left], nums[i] i # 基準値のインデックスを返す end ### クイックソート ### def quick_sort(nums, left, right) # 部分配列の長さが 1 でない場合は再帰する if left < right # 番兵分割 pivot = partition(nums, left, right) # 左右の部分配列を再帰処理 quick_sort(nums, left, pivot - 1) quick_sort(nums, pivot + 1, right) end nums end end end # ## クイックソートクラス(再帰深度最適化)### class QuickSortTailCall class << self ### 番兵分割 ### def partition(nums, left, right) # nums[left] を基準値とする i = left j = right while i < j while i < j && nums[j] >= nums[left] j -= 1 # 右から左へ基準値未満の最初の要素を探す end while i < j && nums[i] <= nums[left] i += 1 # 左から右へ基準値より大きい最初の要素を探す end # 要素の交換 nums[i], nums[j] = nums[j], nums[i] end # 基準値を 2 つの部分配列の境界へ交換する nums[i], nums[left] = nums[left], nums[i] i # 基準値のインデックスを返す end # ## クイックソート(再帰深度最適化)### def quick_sort(nums, left, right) # 部分配列の長さが 1 でない場合は再帰する while left < right # 番兵分割 pivot = partition(nums, left, right) # 2 つの部分配列のうち短いほうにクイックソートを適用する if pivot - left < right - pivot quick_sort(nums, left, pivot - 1) left = pivot + 1 # 未ソート区間の残りは [pivot + 1, right] else quick_sort(nums, pivot + 1, right) right = pivot - 1 # 未ソート区間の残りは [left, pivot - 1] end end end end end ### Driver Code ### if __FILE__ == $0 # クイックソート nums = [2, 4, 1, 0, 3, 5] QuickSort.quick_sort(nums, 0, nums.length - 1) puts "クイックソート完了後 nums = #{nums}" # クイックソート(中央値の基準値で最適化) nums1 = [2, 4, 1, 0, 3, 5] QuickSortMedian.quick_sort(nums1, 0, nums1.length - 1) puts "クイックソート(中央値ピボット最適化)完了後 nums1 = #{nums1}" # クイックソート(再帰深度最適化) nums2 = [2, 4, 1, 0, 3, 5] QuickSortTailCall.quick_sort(nums2, 0, nums2.length - 1) puts "クイックソート(再帰深度最適化)完了後 nums2 = #{nums2}" end ================================================ FILE: ja/codes/ruby/chapter_sorting/radix_sort.rb ================================================ =begin File: radix_sort.rb Created Time: 2024-05-03 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### num の第 k 桁を取得する。ここで exp = 10^(k-1) ### def digit(num, exp) # k ではなく exp を渡すことで、ここで高コストな累乗計算を繰り返し実行するのを避けられる (num / exp) % 10 end # ## 計数ソート(nums の k 桁目でソート)### def counting_sort_digit(nums, exp) # 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 counter = Array.new(10, 0) n = nums.length # 0~9 の各数字の出現回数を集計する for i in 0...n d = digit(nums[i], exp) # nums[i] の第 k 位を取得し、d とする counter[d] += 1 # 数字 d の出現回数を数える end # 累積和を求め、「出現回数」を「配列インデックス」に変換する (1...10).each { |i| counter[i] += counter[i - 1] } # 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する res = Array.new(n, 0) for i in (n - 1).downto(0) d = digit(nums[i], exp) j = counter[d] - 1 # d の配列内インデックス j を取得する res[j] = nums[i] # 現在の要素をインデックス j に格納する counter[d] -= 1 # d の個数を 1 減らす end # 結果で元の配列 nums を上書きする (0...n).each { |i| nums[i] = res[i] } end ### 基数ソート ### def radix_sort(nums) # 最大桁数の判定用に配列の最大要素を取得 m = nums.max # 下位桁から上位桁の順に走査する exp = 1 while exp <= m # 配列要素の k 桁目に対して計数ソートを行う # k = 1 -> exp = 1 # k = 2 -> exp = 10 # つまり exp = 10^(k-1) counting_sort_digit(nums, exp) exp *= 10 end end ### Driver Code ### if __FILE__ == $0 # 基数ソート nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ] radix_sort(nums) puts "基数ソート完了後 nums = #{nums}" end ================================================ FILE: ja/codes/ruby/chapter_sorting/selection_sort.rb ================================================ =begin File: selection_sort.rb Created Time: 2024-05-03 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 選択ソート ### def selection_sort(nums) n = nums.length # 外側ループ:未整列区間は [i, n-1] for i in 0...(n - 1) # 内側のループ:未ソート区間の最小要素を見つける k = i for j in (i + 1)...n if nums[j] < nums[k] k = j # 最小要素のインデックスを記録 end end # その最小要素を未整列区間の先頭要素と交換する nums[i], nums[k] = nums[k], nums[i] end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] selection_sort(nums) puts "選択ソート完了後 nums = #{nums}" end ================================================ FILE: ja/codes/ruby/chapter_stack_and_queue/array_deque.rb ================================================ =begin File: array_deque.rb Created Time: 2024-04-05 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 循環配列で実装した両端キュー ### class ArrayDeque ### 両端キューの長さを取得 ### attr_reader :size ### コンストラクタ ### def initialize(capacity) @nums = Array.new(capacity, 0) @front = 0 @size = 0 end ### 両端キューの容量を取得 ### def capacity @nums.length end ### 両端キューが空か判定 ### def is_empty? size.zero? end ### キュー先頭に追加 ### def push_first(num) if size == capacity puts '両端キューがいっぱいです' return end # 先頭ポインタを左に 1 つ移動する # 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする @front = index(@front - 1) # num をキュー先頭に追加 @nums[@front] = num @size += 1 end ### キュー末尾に追加 ### def push_last(num) if size == capacity puts '両端キューがいっぱいです' return end # キュー末尾ポインタを計算し、末尾インデックス + 1 を指す rear = index(@front + size) # num をキュー末尾に追加 @nums[rear] = num @size += 1 end ### キュー先頭から取り出す ### def pop_first num = peek_first # 先頭ポインタを 1 つ後ろへ進める @front = index(@front + 1) @size -= 1 num end ### キューの末尾から取り出す ### def pop_last num = peek_last @size -= 1 num end ### 先頭要素にアクセス ### def peek_first raise IndexError, '両端キューは空です' if is_empty? @nums[@front] end ### キュー末尾要素を参照 ### def peek_last raise IndexError, '両端キューは空です' if is_empty? # 末尾要素のインデックスを計算 last = index(@front + size - 1) @nums[last] end ### 表示用の配列を返す ### def to_array # 有効長の範囲内のリスト要素のみを変換 res = [] for i in 0...size res << @nums[index(@front + i)] end res end private ### 循環配列のインデックスを計算 ### def index(i) # 剰余演算により配列の先頭と末尾をつなげる # i が配列の末尾を越えたら先頭に戻る # i が配列の先頭を越えて前に出たら末尾に戻る (i + capacity) % capacity end end ### Driver Code ### if __FILE__ == $0 # 両端キューを初期化 deque = ArrayDeque.new(10) deque.push_last(3) deque.push_last(2) deque.push_last(5) puts "両端キュー deque = #{deque.to_array}" # 要素にアクセス peek_first = deque.peek_first puts "先頭要素 peek_first = #{peek_first}" peek_last = deque.peek_last puts "末尾要素 peek_last = #{peek_last}" # 要素をエンキュー deque.push_last(4) puts "要素 4 を末尾に追加後 deque = #{deque.to_array}" deque.push_first(1) puts "要素 1 を末尾に追加後 deque = #{deque.to_array}" # 要素をデキュー pop_last = deque.pop_last puts "末尾から取り出した要素 = #{pop_last},末尾から取り出した後 deque = #{deque.to_array}" pop_first = deque.pop_first puts "末尾から取り出した要素 = #{pop_first},末尾から取り出した後 deque = #{deque.to_array}" # 両端キューの長さを取得 size = deque.size puts "両端キューの長さ size = #{size}" # 両端キューが空かどうかを判定 is_empty = deque.is_empty? puts "両端キューが空かどうか = #{is_empty}" end ================================================ FILE: ja/codes/ruby/chapter_stack_and_queue/array_queue.rb ================================================ =begin File: array_queue.rb Created Time: 2024-04-05 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 循環配列で実装したキュー ### class ArrayQueue ### キューの長さを取得 ### attr_reader :size ### コンストラクタ ### def initialize(size) @nums = Array.new(size, 0) # キュー要素を格納する配列 @front = 0 # 先頭ポインタ。先頭要素を指す @size = 0 # キューの長さ end ### キューの容量を取得 ### def capacity @nums.length end ### キューが空か判定 ### def is_empty? size.zero? end ### エンキュー ### def push(num) raise IndexError, 'キューがいっぱいです' if size == capacity # 末尾ポインタを計算し、末尾インデックス + 1 を指す # 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする rear = (@front + size) % capacity # num をキュー末尾に追加 @nums[rear] = num @size += 1 end ### デキュー ### def pop num = peek # 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す @front = (@front + 1) % capacity @size -= 1 num end ### 先頭要素にアクセス ### def peek raise IndexError, 'キューは空です' if is_empty? @nums[@front] end ### 表示用のリストを返す ### def to_array res = Array.new(size, 0) j = @front for i in 0...size res[i] = @nums[j % capacity] j += 1 end res end end ### Driver Code ### if __FILE__ == $0 # キューを初期化 queue = ArrayQueue.new(10) # 要素をエンキュー queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "キュー queue = #{queue.to_array}" # キュー先頭の要素にアクセス peek = queue.peek puts "先頭要素 peek = #{peek}" # 要素をデキュー pop = queue.pop puts "取り出した要素 pop = #{pop}" puts "取り出し後 queue = #{queue.to_array}" # キューの長さを取得 size = queue.size puts "キューの長さ size = #{size}" # キューが空かどうかを判定 is_empty = queue.is_empty? puts "キューが空かどうか = #{is_empty}" # 循環配列をテストする for i in 0...10 queue.push(i) queue.pop puts "第 #{i} 回の追加 + 取り出し後 queue = #{queue.to_array}" end end ================================================ FILE: ja/codes/ruby/chapter_stack_and_queue/array_stack.rb ================================================ =begin File: array_stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 配列で実装したスタック ### class ArrayStack ### コンストラクタ ### def initialize @stack = [] end ### スタックの長さを取得 ### def size @stack.length end ### スタックが空か判定 ### def is_empty? @stack.empty? end ### プッシュ ### def push(item) @stack << item end ### ポップ ### def pop raise IndexError, 'スタックは空です' if is_empty? @stack.pop end ### スタックトップ要素を参照 ### def peek raise IndexError, 'スタックは空です' if is_empty? @stack.last end ### 表示用のリストを返す ### def to_array @stack end end ### Driver Code ### if __FILE__ == $0 # スタックを初期化 stack = ArrayStack.new # 要素をプッシュ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) puts "スタック stack = #{stack.to_array}" # スタックトップの要素にアクセス peek = stack.peek puts "スタックトップ要素 peek = #{peek}" # 要素をポップ pop = stack.pop puts "ポップした要素 pop = #{pop}" puts "ポップ後 stack = #{stack.to_array}" # スタックの長さを取得 size = stack.size puts "スタックの長さ size = #{size}" # 空かどうかを判定 is_empty = stack.is_empty? puts "スタックが空かどうか = #{is_empty}" end ================================================ FILE: ja/codes/ruby/chapter_stack_and_queue/deque.rb ================================================ =begin File: deque.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # 両端キューを初期化する # Ruby には組み込みの両端キューがないため、Array を両端キューとして使う deque = [] # 要素をキューに入れる deque << 2 deque << 5 deque << 4 # 注意:配列であるため、Array#unshift メソッドの時間計算量は O(n) です deque.unshift(3) deque.unshift(1) puts "両端キュー deque = #{deque}" # 要素にアクセス peek_first = deque.first puts "先頭要素 peek_first = #{peek_first}" peek_last = deque.last puts "末尾要素 peek_last = #{peek_last}" # 要素をキューから取り出す # 配列であるため、Array#shift メソッドの時間計算量は O(n) である pop_front = deque.shift puts "先頭から取り出した要素 pop_front = #{pop_front},先頭から取り出した後 deque = #{deque}" pop_back = deque.pop puts "末尾から取り出した要素 pop_back = #{pop_back}, 末尾から取り出した後 deque = #{deque}" # 両端キューの長さを取得 size = deque.length puts "両端キューの長さ size = #{size}" # 両端キューが空かどうかを判定 is_empty = size.zero? puts "両端キューが空かどうか = #{is_empty}" end ================================================ FILE: ja/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb ================================================ =begin File: linkedlist_deque.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end # ## 双方向連結リストノード class ListNode attr_accessor :val attr_accessor :next # 後続ノードへの参照 attr_accessor :prev # 前ノードへの参照 ### コンストラクタ ### def initialize(val) @val = val end end ### 双方向連結リストで実装した両端キュー ### class LinkedListDeque ### 両端キューの長さを取得 ### attr_reader :size ### コンストラクタ ### def initialize @front = nil # 先頭ノード front @rear = nil # 末尾ノード rear @size = 0 # 両端キューの長さ end ### 両端キューが空か判定 ### def is_empty? size.zero? end ### エンキュー操作 ### def push(num, is_front) node = ListNode.new(num) # 連結リストが空なら、`front` と `rear` の両方を `node` に向ける if is_empty? @front = @rear = node # 先頭へのエンキュー操作 elsif is_front # node を連結リストの先頭に追加 @front.prev = node node.next = @front @front = node # 先頭ノードを更新する # 末尾へのエンキュー操作 else # node を連結リストの末尾に追加 @rear.next = node node.prev = @rear @rear = node # 末尾ノードを更新する end @size += 1 # キューの長さを更新 end ### キュー先頭に追加 ### def push_first(num) push(num, true) end ### キュー末尾に追加 ### def push_last(num) push(num, false) end ### デキュー操作 ### def pop(is_front) raise IndexError, '両端キューは空です' if is_empty? # キュー先頭からの取り出し if is_front val = @front.val # 先頭ノードの値を一時保存 # 先頭ノードを削除 fnext = @front.next unless fnext.nil? fnext.prev = nil @front.next = nil end @front = fnext # 先頭ノードを更新する # キュー末尾からの取り出し else val = @rear.val # 末尾ノードの値を一時保存 # 末尾ノードを削除 rprev = @rear.prev unless rprev.nil? rprev.next = nil @rear.prev = nil end @rear = rprev # 末尾ノードを更新する end @size -= 1 # キューの長さを更新 val end ### キュー先頭から取り出す ### def pop_first pop(true) end ### キュー先頭から取り出す ### def pop_last pop(false) end ### 先頭要素にアクセス ### def peek_first raise IndexError, '両端キューは空です' if is_empty? @front.val end ### キュー末尾要素を参照 ### def peek_last raise IndexError, '両端キューは空です' if is_empty? @rear.val end ### 表示用の配列を返す ### def to_array node = @front res = Array.new(size, 0) for i in 0...size res[i] = node.val node = node.next end res end end ### Driver Code ### if __FILE__ == $0 # 両端キューを初期化 deque = LinkedListDeque.new deque.push_last(3) deque.push_last(2) deque.push_last(5) puts "両端キュー deque = #{deque.to_array}" # 要素にアクセス peek_first = deque.peek_first puts "先頭要素 peek_first = #{peek_first}" peek_last = deque.peek_last puts "先頭要素 peek_last = #{peek_last}" # 要素をエンキュー deque.push_last(4) puts "要素 4 を末尾に追加後 deque = #{deque.to_array}" deque.push_first(1) puts "要素 1 を先頭に追加後 deque = #{deque.to_array}" # 要素をデキュー pop_last = deque.pop_last puts "末尾から取り出した要素 = #{pop_last}, 末尾から取り出した後 deque = #{deque.to_array}" pop_first = deque.pop_first puts "先頭から取り出した要素 = #{pop_first},先頭から取り出した後 deque = #{deque.to_array}" # 両端キューの長さを取得 size = deque.size puts "両端キューの長さ size = #{size}" # 両端キューが空かどうかを判定 is_empty = deque.is_empty? puts "両端キューが空かどうか = #{is_empty}" end ================================================ FILE: ja/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb ================================================ =begin File: linkedlist_queue.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### 連結リストで実装したキュー ### class LinkedListQueue ### キューの長さを取得 ### attr_reader :size ### コンストラクタ ### def initialize @front = nil # 先頭ノード front @rear = nil # 末尾ノード rear @size = 0 end ### キューが空か判定 ### def is_empty? @front.nil? end ### エンキュー ### def push(num) # 末尾ノードの後ろに num を追加 node = ListNode.new(num) # キューが空なら、先頭ノードと末尾ノードの両方をそのノードに向ける if @front.nil? @front = node @rear = node # キューが空でなければ、そのノードを末尾ノードの後ろに追加する else @rear.next = node @rear = node end @size += 1 end ### デキュー ### def pop num = peek # 先頭ノードを削除 @front = @front.next @size -= 1 num end ### 先頭要素にアクセス ### def peek raise IndexError, 'キューは空です' if is_empty? @front.val end ### 連結リストを Array に変換して返す ### def to_array queue = [] temp = @front while temp queue << temp.val temp = temp.next end queue end end ### Driver Code ### if __FILE__ == $0 # キューを初期化 queue = LinkedListQueue.new # 要素をキューに入れる queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "キュー queue = #{queue.to_array}" # キュー先頭の要素にアクセス peek = queue.peek puts "先頭要素 front = #{peek}" # 要素をデキュー pop_front = queue.pop puts "取り出した要素 pop = #{pop_front}" puts "取り出し後 queue = #{queue.to_array}" # キューの長さを取得 size = queue.size puts "キューの長さ size = #{size}" # キューが空かどうかを判定 is_empty = queue.is_empty? puts "キューが空かどうか = #{is_empty}" end ================================================ FILE: ja/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb ================================================ =begin File: linkedlist_stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### 連結リストで実装したスタック ### class LinkedListStack attr_reader :size ### コンストラクタ ### def initialize @size = 0 end ### スタックが空か判定 ### def is_empty? @peek.nil? end ### プッシュ ### def push(val) node = ListNode.new(val) node.next = @peek @peek = node @size += 1 end ### ポップ ### def pop num = peek @peek = @peek.next @size -= 1 num end ### スタックトップ要素を参照 ### def peek raise IndexError, 'スタックは空です' if is_empty? @peek.val end ### 連結リストを Array に変換して返す ### def to_array arr = [] node = @peek while node arr << node.val node = node.next end arr.reverse end end ### Driver Code ### if __FILE__ == $0 # スタックを初期化 stack = LinkedListStack.new # 要素をプッシュ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) puts "スタック stack = #{stack.to_array}" # スタックトップの要素にアクセス peek = stack.peek puts "スタックトップ要素 peek = #{peek}" # 要素をポップ pop = stack.pop puts "ポップした要素 pop = #{pop}" puts "ポップ後 stack = #{stack.to_array}" # スタックの長さを取得 size = stack.size puts "スタックの長さ size = #{size}" # 空かどうかを判定 is_empty = stack.is_empty? puts "スタックが空かどうか = #{is_empty}" end ================================================ FILE: ja/codes/ruby/chapter_stack_and_queue/queue.rb ================================================ =begin File: queue.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # キューを初期化する # Ruby 組み込みのキュー(Thread::Queue)には peek と走査メソッドがないため、Array をキューとして使える queue = [] # 要素をエンキュー queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "キュー queue = #{queue}" # キューの要素にアクセス peek = queue.first puts "先頭要素 peek = #{peek}" # 要素をキューから取り出す # 配列であるため、Array#shift メソッドの時間計算量は O(n) である pop = queue.shift puts "取り出した要素 pop = #{pop}" puts "取り出し後 queue = #{queue}" # キューの長さを取得 size = queue.length puts "キューの長さ size = #{size}" # キューが空かどうかを判定 is_empty = queue.empty? puts "キューが空かどうか = #{is_empty}" end ================================================ FILE: ja/codes/ruby/chapter_stack_and_queue/stack.rb ================================================ =begin File: stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # スタックを初期化する # Ruby には組み込みのスタッククラスがないため、Array をスタックとして使える stack = [] # 要素をプッシュ stack << 1 stack << 3 stack << 2 stack << 5 stack << 4 puts "スタック stack = #{stack}" # スタックトップの要素にアクセス peek = stack.last puts "スタックトップ要素 peek = #{peek}" # 要素をポップ pop = stack.pop puts "ポップした要素 pop = #{pop}" puts "ポップ後 stack = #{stack}" # スタックの長さを取得 size = stack.length puts "スタックの長さ size = #{size}" # 空かどうかを判定 is_empty = stack.empty? puts "スタックが空かどうか = #{is_empty}" end ================================================ FILE: ja/codes/ruby/chapter_tree/array_binary_tree.rb ================================================ =begin File: array_binary_tree.rb Created Time: 2024-04-17 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 配列表現の二分木クラス ### class ArrayBinaryTree ### コンストラクタ ### def initialize(arr) @tree = arr.to_a end ### リスト容量 ### def size @tree.length end ### インデックス i のノードの値を取得 ### def val(i) # インデックスが範囲外なら `nil` を返し、空きスロットを表す return if i < 0 || i >= size @tree[i] end ### インデックス i のノードの左子ノードのインデックスを取得 ### def left(i) 2 * i + 1 end ### インデックス i のノードの右子ノードのインデックスを取得 ### def right(i) 2 * i + 2 end ### インデックス i のノードの親ノードのインデックスを取得 ### def parent(i) (i - 1) / 2 end ### レベル順走査 ### def level_order @res = [] # 配列を直接走査する for i in 0...size @res << val(i) unless val(i).nil? end @res end ### 深さ優先探索 ### def dfs(i, order) return if val(i).nil? # 先行順走査 @res << val(i) if order == :pre dfs(left(i), order) # 中順走査 @res << val(i) if order == :in dfs(right(i), order) # 後順走査 @res << val(i) if order == :post end ### 前順走査 ### def pre_order @res = [] dfs(0, :pre) @res end ### 中順走査 ### def in_order @res = [] dfs(0, :in) @res end ### 後順走査 ### def post_order @res = [] dfs(0, :post) @res end end ### Driver Code ### if __FILE__ == $0 # 二分木を初期化 # ここでは、配列から直接二分木を生成する関数を利用する arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] root = arr_to_tree(arr) puts "\n二分木を初期化\n\n" puts '二分木の配列表現:' pp arr puts '二分木の連結リスト表現:' print_tree(root) # 配列表現による二分木クラス abt = ArrayBinaryTree.new(arr) # ノードにアクセス i = 1 l, r, _p = abt.left(i), abt.right(i), abt.parent(i) puts "\n現在のノードのインデックスは #{i} ,値は #{abt.val(i).inspect}" puts "左の子ノードのインデックスは #{l} ,値は #{abt.val(l).inspect}" puts "右の子ノードのインデックスは #{r} ,値は #{abt.val(r).inspect}" puts "親ノードのインデックスは #{_p} ,値は #{abt.val(_p).inspect}" # 木を走査 res = abt.level_order puts "\nレベル順走査の結果: #{res}" res = abt.pre_order puts "前順走査の結果: #{res}" res = abt.in_order puts "中順走査の結果: #{res}" res = abt.post_order puts "後順走査の結果: #{res}" end ================================================ FILE: ja/codes/ruby/chapter_tree/avl_tree.rb ================================================ =begin File: avl_tree.rb Created Time: 2024-04-17 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### AVL 木 ### class AVLTree ### コンストラクタ ### def initialize @root = nil end ### 二分木の根ノードを取得 ### def get_root @root end ### ノードの高さを取得 ### def height(node) # 空ノードの高さは -1、葉ノードの高さは 0 return node.height unless node.nil? -1 end ### ノードの高さを更新 ### def update_height(node) # ノードの高さは最も高い部分木の高さ + 1 に等しい node.height = [height(node.left), height(node.right)].max + 1 end ### 平衡係数を取得 ### def balance_factor(node) # 空ノードの平衡係数は 0 return 0 if node.nil? # ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ height(node.left) - height(node.right) end ### 右回転操作 ### def right_rotate(node) child = node.left grand_child = child.right # child を支点として node を右回転させる child.right = node node.left = grand_child # ノードの高さを更新する update_height(node) update_height(child) # 回転後の部分木の根ノードを返す child end ### 左回転操作 ### def left_rotate(node) child = node.right grand_child = child.left # child を支点として node を左回転させる child.left = node node.right = grand_child # ノードの高さを更新する update_height(node) update_height(child) # 回転後の部分木の根ノードを返す child end ### 回転操作を行い、この部分木の平衡を回復する ### def rotate(node) # ノード node の平衡係数を取得 balance_factor = balance_factor(node) # 左部分木をたどる if balance_factor > 1 if balance_factor(node.left) >= 0 # 右回転 return right_rotate(node) else # 左回転してから右回転 node.left = left_rotate(node.left) return right_rotate(node) end # 右に偏った木 elsif balance_factor < -1 if balance_factor(node.right) <= 0 # 左回転 return left_rotate(node) else # 右回転してから左回転 node.right = right_rotate(node.right) return left_rotate(node) end end # 平衡木なので回転不要、そのまま返す node end ### ノードを挿入 ### def insert(val) @root = insert_helper(@root, val) end # ## ノードを再帰的に挿入(補助メソッド)### def insert_helper(node, val) return TreeNode.new(val) if node.nil? # 1. 挿入位置を探索してノードを挿入 if val < node.val node.left = insert_helper(node.left, val) elsif val > node.val node.right = insert_helper(node.right, val) else # 重複ノードは挿入せず、そのまま返す return node end # ノードの高さを更新する update_height(node) # 2. 回転操作を行い、部分木の平衡を回復する rotate(node) end ### ノードを削除 ### def remove(val) @root = remove_helper(@root, val) end # ## ノードを再帰的に削除(補助メソッド)### def remove_helper(node, val) return if node.nil? # 1. ノードを探索して削除 if val < node.val node.left = remove_helper(node.left, val) elsif val > node.val node.right = remove_helper(node.right, val) else if node.left.nil? || node.right.nil? child = node.left || node.right # 子ノード数 = 0 の場合、node をそのまま削除して返す return if child.nil? # 子ノード数 = 1 の場合、node をそのまま削除する node = child else # 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える temp = node.right while !temp.left.nil? temp = temp.left end node.right = remove_helper(node.right, temp.val) node.val = temp.val end end # ノードの高さを更新する update_height(node) # 2. 回転操作を行い、部分木の平衡を回復する rotate(node) end ### ノードを検索 ### def search(val) cur = @root # ループで探索し、葉ノードを越えたら抜ける while !cur.nil? # 目標ノードは cur の右部分木にある if cur.val < val cur = cur.right # 目標ノードは cur の左部分木にある elsif cur.val > val cur = cur.left # 目標ノードが見つかったらループを抜ける else break end end # 目標ノードを返す cur end end ### Driver Code ### if __FILE__ == $0 def test_insert(tree, val) tree.insert(val) puts "\nノード #{val} を挿入した後、AVL 木は" print_tree(tree.get_root) end def test_remove(tree, val) tree.remove(val) puts "\nノード #{val} を削除した後、AVL 木は" print_tree(tree.get_root) end # 空の AVL 木を初期化する avl_tree = AVLTree.new # ノードを挿入する # ノード挿入後に AVL 木がどのように平衡を保つかに注目 for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6] test_insert(avl_tree, val) end # 重複ノードを挿入する test_insert(avl_tree, 7) # ノードを削除する # ノード削除後に AVL 木がどのように平衡を保つかに注目 test_remove(avl_tree, 8) # 次数 0 のノードを削除する test_remove(avl_tree, 5) # 次数 1 のノードを削除する test_remove(avl_tree, 4) # 次数 2 のノードを削除する result_node = avl_tree.search(7) puts "\n見つかったノードオブジェクトは #{result_node}、ノードの値 = #{result_node.val}" end ================================================ FILE: ja/codes/ruby/chapter_tree/binary_search_tree.rb ================================================ =begin File: binary_search_tree.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 二分探索木 ### class BinarySearchTree ### コンストラクタ ### def initialize # 空の木を初期化する @root = nil end ### 二分木の根ノードを取得 ### def get_root @root end ### ノードを検索 ### def search(num) cur = @root # ループで探索し、葉ノードを越えたら抜ける while !cur.nil? # 目標ノードは cur の右部分木にある if cur.val < num cur = cur.right # 目標ノードは cur の左部分木にある elsif cur.val > num cur = cur.left # 目標ノードが見つかったらループを抜ける else break end end cur end ### ノードを挿入 ### def insert(num) # 木が空なら、根ノードを初期化する if @root.nil? @root = TreeNode.new(num) return end # ループで探索し、葉ノードを越えたら抜ける cur, pre = @root, nil while !cur.nil? # 重複ノードが見つかったら、直ちに返す return if cur.val == num pre = cur # 挿入位置は cur の右部分木にある if cur.val < num cur = cur.right # 挿入位置は cur の左部分木にある else cur = cur.left end end # ノードを挿入 node = TreeNode.new(num) if pre.val < num pre.right = node else pre.left = node end end ### ノードを削除 ### def remove(num) # 木が空なら、そのまま早期リターンする return if @root.nil? # ループで探索し、葉ノードを越えたら抜ける cur, pre = @root, nil while !cur.nil? # 削除対象のノードが見つかったら、ループを抜ける break if cur.val == num pre = cur # 削除対象ノードは cur の右部分木にある if cur.val < num cur = cur.right # 削除対象ノードは cur の左部分木にある else cur = cur.left end end # 削除対象ノードがなければそのまま返す return if cur.nil? # 子ノード数 = 0 or 1 if cur.left.nil? || cur.right.nil? # 子ノード数が 0 / 1 のとき、child = null / その子ノード child = cur.left || cur.right # ノード cur を削除する if cur != @root if pre.left == cur pre.left = child else pre.right = child end else # 削除ノードが根ノードなら、根ノードを再設定 @root = child end # 子ノード数 = 2 else # 中順走査における cur の次ノードを取得 tmp = cur.right while !tmp.left.nil? tmp = tmp.left end # ノード tmp を再帰的に削除 remove(tmp.val) # tmp で cur を上書きする cur.val = tmp.val end end end ### Driver Code ### if __FILE__ == $0 # 二分探索木を初期化 bst = BinarySearchTree.new nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] # 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる nums.each { |num| bst.insert(num) } puts "\n初期化された二分木は\n" print_tree(bst.get_root) # ノードを探索 node = bst.search(7) puts "\n見つかったノードオブジェクトは: #{node}、ノードの値 = #{node.val}" # ノードを挿入 bst.insert(16) puts "\nノード 16 を挿入した後、二分木は\n" print_tree(bst.get_root) # ノードを削除 bst.remove(1) puts "\nノード 1 を削除した後、二分木は\n" print_tree(bst.get_root) bst.remove(2) puts "\nノード 2 を削除した後、二分木は\n" print_tree(bst.get_root) bst.remove(4) puts "\nノード 4 を削除した後、二分木は\n" print_tree(bst.get_root) end ================================================ FILE: ja/codes/ruby/chapter_tree/binary_tree.rb ================================================ =begin File: binary_tree.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Driver Code ### if __FILE__ == $0 # 二分木を初期化する # ノードを初期化する n1 = TreeNode.new(1) n2 = TreeNode.new(2) n3 = TreeNode.new(3) n4 = TreeNode.new(4) n5 = TreeNode.new(5) # ノード間の参照(ポインタ)を構築する n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 puts "\n二分木を初期化\n\n" print_tree(n1) # ノードの挿入と削除 _p = TreeNode.new(0) # n1 -> n2 の間にノード _p を挿入する n1.left = _p _p.left = n2 puts "\nノード _p を挿入した後\n\n" print_tree(n1) # ノードを削除 n1.left = n2 puts "\nノード _p を削除した後\n\n" print_tree(n1) end ================================================ FILE: ja/codes/ruby/chapter_tree/binary_tree_bfs.rb ================================================ =begin File: binary_tree_bfs.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### レベル順走査 ### def level_order(root) # キューを初期化し、ルートノードを追加する queue = [root] # 走査順序を保存するためのリストを初期化する res = [] while !queue.empty? node = queue.shift # デキュー res << node.val # ノードの値を保存する queue << node.left unless node.left.nil? # 左子ノードをキューに追加 queue << node.right unless node.right.nil? # 右子ノードをキューに追加 end res end ### Driver Code ### if __FILE__ == $0 # 二分木を初期化 # ここでは、配列から直接二分木を生成する関数を利用する root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) puts "\n二分木を初期化\n\n" print_tree(root) # レベル順走査 res = level_order(root) puts "\nレベル順走査のノード出力順 = #{res}" end ================================================ FILE: ja/codes/ruby/chapter_tree/binary_tree_dfs.rb ================================================ =begin File: binary_tree_dfs.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 前順走査 ### def pre_order(root) return if root.nil? # 訪問順序:根ノード -> 左部分木 -> 右部分木 $res << root.val pre_order(root.left) pre_order(root.right) end ### 中順走査 ### def in_order(root) return if root.nil? # 訪問優先順: 左部分木 -> 根ノード -> 右部分木 in_order(root.left) $res << root.val in_order(root.right) end ### 後順走査 ### def post_order(root) return if root.nil? # 訪問優先順: 左部分木 -> 右部分木 -> 根ノード post_order(root.left) post_order(root.right) $res << root.val end ### Driver Code ### if __FILE__ == $0 # 二分木を初期化 # ここでは、配列から直接二分木を生成する関数を利用する root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) puts "\n二分木を初期化\n\n" print_tree(root) # 先行順走査 $res = [] pre_order(root) puts "\n前順走査のノード出力順 = #{$res}" # 中順走査 $res.clear in_order(root) puts "\nn中順走査のノード出力順 = #{$res}" # 後順走査 $res.clear post_order(root) puts "\nn後順走査のノード出力順 = #{$res}" end ================================================ FILE: ja/codes/ruby/test_all.rb ================================================ require 'open3' start_time = Time.now ruby_code_dir = File.dirname(__FILE__) files = Dir.glob("#{ruby_code_dir}/chapter_*/*.rb") errors = [] files.each do |file| stdout, stderr, status = Open3.capture3("ruby #{file}") errors << stderr unless status.success? end puts "\x1b[34mTested #{files.count} files\x1b[m" unless errors.empty? puts "\x1b[33mFound exception in #{errors.length} files\x1b[m" raise errors.join("\n\n") else puts "\x1b[32mPASS\x1b[m" end puts "Testing finishes after #{((Time.now - start_time) * 1000).round} ms" ================================================ FILE: ja/codes/ruby/utils/list_node.rb ================================================ =begin File: list_node.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 連結リストノードクラス ### class ListNode attr_accessor :val # ノード値 attr_accessor :next # 次のノードへの参照 def initialize(val=0, next_node=nil) @val = val @next = next_node end end ### リストを連結リストにデシリアライズ ### def arr_to_linked_list(arr) head = current = ListNode.new(arr[0]) for i in 1...arr.length current.next = ListNode.new(arr[i]) current = current.next end head end ### 連結リストをリストにシリアライズ ### def linked_list_to_arr(head) arr = [] while head arr << head.val head = head.next end end ================================================ FILE: ja/codes/ruby/utils/print_util.rb ================================================ =begin File: print_util.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative "./tree_node" ### 行列を出力 ### def print_matrix(mat) s = [] mat.each { |arr| s << " #{arr.to_s}" } puts "[\n#{s.join(",\n")}\n]" end ### 連結リストを出力 ### def print_linked_list(head) list = [] while head list << head.val head = head.next end puts "#{list.join(" -> ")}" end class Trunk attr_accessor :prev, :str def initialize(prev, str) @prev = prev @str = str end end def show_trunk(p) return if p.nil? show_trunk(p.prev) print p.str end ### 二分木を出力 ### # This tree printer is borrowed from TECHIE DELIGHT # https://www.techiedelight.com/c-program-print-binary-tree/ def print_tree(root, prev=nil, is_right=false) return if root.nil? prev_str = " " trunk = Trunk.new(prev, prev_str) print_tree(root.right, trunk, true) if prev.nil? trunk.str = "———" elsif is_right trunk.str = "/———" prev_str = " |" else trunk.str = "\\———" prev.str = prev_str end show_trunk(trunk) puts " #{root.val}" prev.str = prev_str if prev trunk.str = " |" print_tree(root.left, trunk, false) end ### ハッシュテーブルを出力 ### def print_hash_map(hmap) hmap.entries.each { |key, value| puts "#{key} -> #{value}" } end ### ヒープを出力 ### def print_heap(heap) puts "ヒープの配列表現:#{heap}" puts "ヒープの木構造表現:" root = arr_to_tree(heap) print_tree(root) end ================================================ FILE: ja/codes/ruby/utils/tree_node.rb ================================================ =begin File: tree_node.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 二分木ノードクラス ### class TreeNode attr_accessor :val # ノード値 attr_accessor :height # ノードの高さ attr_accessor :left # 左子ノードへの参照 attr_accessor :right # 右子ノードへの参照 def initialize(val=0) @val = val @height = 0 end end ### リストを二分木にデシリアライズ:再帰 ### def arr_to_tree_dfs(arr, i) # 添字が配列長を超えるか、対応する要素が nil なら、nil を返す return if i < 0 || i >= arr.length || arr[i].nil? # 現在のノードを構築する root = TreeNode.new(arr[i]) # 左右の部分木を再帰的に構築する root.left = arr_to_tree_dfs(arr, 2 * i + 1) root.right = arr_to_tree_dfs(arr, 2 * i + 2) root end ### リストを二分木にデシリアライズ ### def arr_to_tree(arr) arr_to_tree_dfs(arr, 0) end ### 二分木をリストにシリアライズ:再帰 ### def tree_to_arr_dfs(root, i, res) return if root.nil? res += Array.new(i - res.length + 1) if i >= res.length res[i] = root.val tree_to_arr_dfs(root.left, 2 * i + 1, res) tree_to_arr_dfs(root.right, 2 * i + 2, res) end ### 二分木をリストにシリアライズ ### def tree_to_arr(root) res = [] tree_to_arr_dfs(root, 0, res) res end ================================================ FILE: ja/codes/ruby/utils/vertex.rb ================================================ =begin File: vertex.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 頂点クラス ### class Vertex attr_accessor :val def initialize(val) @val = val end end ### 値リスト vals を入力し、頂点リスト vets を返す ### def vals_to_vets(vals) Array.new(vals.length) { |i| Vertex.new(vals[i]) } end ### 頂点リスト vets を入力し、値リスト vals を返す ### def vets_to_vals(vets) Array.new(vets.length) { |i| vets[i].val } end ================================================ FILE: ja/codes/rust/.gitignore ================================================ target/ Cargo.lock ================================================ FILE: ja/codes/rust/Cargo.toml ================================================ [package] name = "hello-algo-rust" version = "0.1.0" edition = "2021" publish = false # Run Command: cargo run --bin time_complexity [[bin]] name = "time_complexity" path = "chapter_computational_complexity/time_complexity.rs" # Run Command: cargo run --bin worst_best_time_complexity [[bin]] name = "worst_best_time_complexity" path = "chapter_computational_complexity/worst_best_time_complexity.rs" # Run Command: cargo run --bin space_complexity [[bin]] name = "space_complexity" path = "chapter_computational_complexity/space_complexity.rs" # Run Command: cargo run --bin iteration [[bin]] name = "iteration" path = "chapter_computational_complexity/iteration.rs" # Run Command: cargo run --bin recursion [[bin]] name = "recursion" path = "chapter_computational_complexity/recursion.rs" # Run Command: cargo run --bin two_sum [[bin]] name = "two_sum" path = "chapter_searching/two_sum.rs" # Run Command: cargo run --bin array [[bin]] name = "array" path = "chapter_array_and_linkedlist/array.rs" # Run Command: cargo run --bin linked_list [[bin]] name = "linked_list" path = "chapter_array_and_linkedlist/linked_list.rs" # Run Command: cargo run --bin list [[bin]] name = "list" path = "chapter_array_and_linkedlist/list.rs" # Run Command: cargo run --bin my_list [[bin]] name = "my_list" path = "chapter_array_and_linkedlist/my_list.rs" # Run Command: cargo run --bin stack [[bin]] name = "stack" path = "chapter_stack_and_queue/stack.rs" # Run Command: cargo run --bin linkedlist_stack [[bin]] name = "linkedlist_stack" path = "chapter_stack_and_queue/linkedlist_stack.rs" # Run Command: cargo run --bin queue [[bin]] name = "queue" path = "chapter_stack_and_queue/queue.rs" # Run Command: cargo run --bin linkedlist_queue [[bin]] name = "linkedlist_queue" path = "chapter_stack_and_queue/linkedlist_queue.rs" # Run Command: cargo run --bin deque [[bin]] name = "deque" path = "chapter_stack_and_queue/deque.rs" # Run Command: cargo run --bin array_deque [[bin]] name = "array_deque" path = "chapter_stack_and_queue/array_deque.rs" # Run Command: cargo run --bin linkedlist_deque [[bin]] name = "linkedlist_deque" path = "chapter_stack_and_queue/linkedlist_deque.rs" # Run Command: cargo run --bin simple_hash [[bin]] name = "simple_hash" path = "chapter_hashing/simple_hash.rs" # Run Command: cargo run --bin hash_map [[bin]] name = "hash_map" path = "chapter_hashing/hash_map.rs" # Run Command: cargo run --bin array_hash_map [[bin]] name = "array_hash_map" path = "chapter_hashing/array_hash_map.rs" # Run Command: cargo run --bin build_in_hash [[bin]] name = "build_in_hash" path = "chapter_hashing/build_in_hash.rs" # Run Command: cargo run --bin hash_map_chaining [[bin]] name = "hash_map_chaining" path = "chapter_hashing/hash_map_chaining.rs" # Run Command: cargo run --bin hash_map_open_addressing [[bin]] name = "hash_map_open_addressing" path = "chapter_hashing/hash_map_open_addressing.rs" # Run Command: cargo run --bin binary_search [[bin]] name = "binary_search" path = "chapter_searching/binary_search.rs" # Run Command: cargo run --bin binary_search_edge [[bin]] name = "binary_search_edge" path = "chapter_searching/binary_search_edge.rs" # Run Command: cargo run --bin binary_search_insertion [[bin]] name = "binary_search_insertion" path = "chapter_searching/binary_search_insertion.rs" # Run Command: cargo run --bin bubble_sort [[bin]] name = "bubble_sort" path = "chapter_sorting/bubble_sort.rs" # Run Command: cargo run --bin insertion_sort [[bin]] name = "insertion_sort" path = "chapter_sorting/insertion_sort.rs" # Run Command: cargo run --bin quick_sort [[bin]] name = "quick_sort" path = "chapter_sorting/quick_sort.rs" # Run Command: cargo run --bin merge_sort [[bin]] name = "merge_sort" path = "chapter_sorting/merge_sort.rs" # Run Command: cargo run --bin selection_sort [[bin]] name = "selection_sort" path = "chapter_sorting/selection_sort.rs" # Run Command: cargo run --bin bucket_sort [[bin]] name = "bucket_sort" path = "chapter_sorting/bucket_sort.rs" # Run Command: cargo run --bin heap_sort [[bin]] name = "heap_sort" path = "chapter_sorting/heap_sort.rs" # Run Command: cargo run --bin counting_sort [[bin]] name = "counting_sort" path = "chapter_sorting/counting_sort.rs" # Run Command: cargo run --bin radix_sort [[bin]] name = "radix_sort" path = "chapter_sorting/radix_sort.rs" # Run Command: cargo run --bin array_stack [[bin]] name = "array_stack" path = "chapter_stack_and_queue/array_stack.rs" # Run Command: cargo run --bin array_queue [[bin]] name = "array_queue" path = "chapter_stack_and_queue/array_queue.rs" # Run Command: cargo run --bin array_binary_tree [[bin]] name = "array_binary_tree" path = "chapter_tree/array_binary_tree.rs" # Run Command: cargo run --bin avl_tree [[bin]] name = "avl_tree" path = "chapter_tree/avl_tree.rs" # Run Command: cargo run --bin binary_search_tree [[bin]] name = "binary_search_tree" path = "chapter_tree/binary_search_tree.rs" # Run Command: cargo run --bin binary_tree_bfs [[bin]] name = "binary_tree_bfs" path = "chapter_tree/binary_tree_bfs.rs" # Run Command: cargo run --bin binary_tree_dfs [[bin]] name = "binary_tree_dfs" path = "chapter_tree/binary_tree_dfs.rs" # Run Command: cargo run --bin binary_tree [[bin]] name = "binary_tree" path = "chapter_tree/binary_tree.rs" # Run Command: cargo run --bin heap [[bin]] name = "heap" path = "chapter_heap/heap.rs" # Run Command: cargo run --bin my_heap [[bin]] name = "my_heap" path = "chapter_heap/my_heap.rs" # Run Command: cargo run --bin top_k [[bin]] name = "top_k" path = "chapter_heap/top_k.rs" # Run Command: cargo run --bin graph_adjacency_list [[bin]] name = "graph_adjacency_list" path = "chapter_graph/graph_adjacency_list.rs" # Run Command: cargo run --bin graph_adjacency_matrix [[bin]] name = "graph_adjacency_matrix" path = "chapter_graph/graph_adjacency_matrix.rs" # Run Command: cargo run --bin graph_bfs [[bin]] name = "graph_bfs" path = "chapter_graph/graph_bfs.rs" # Run Command: cargo run --bin graph_dfs [[bin]] name = "graph_dfs" path = "chapter_graph/graph_dfs.rs" # Run Command: cargo run --bin linear_search [[bin]] name = "linear_search" path = "chapter_searching/linear_search.rs" # Run Command: cargo run --bin hashing_search [[bin]] name = "hashing_search" path = "chapter_searching/hashing_search.rs" # Run Command: cargo run --bin climbing_stairs_dfs [[bin]] name = "climbing_stairs_dfs" path = "chapter_dynamic_programming/climbing_stairs_dfs.rs" # Run Command: cargo run --bin climbing_stairs_dfs_mem [[bin]] name = "climbing_stairs_dfs_mem" path = "chapter_dynamic_programming/climbing_stairs_dfs_mem.rs" # Run Command: cargo run --bin climbing_stairs_dp [[bin]] name = "climbing_stairs_dp" path = "chapter_dynamic_programming/climbing_stairs_dp.rs" # Run Command: cargo run --bin min_cost_climbing_stairs_dp [[bin]] name = "min_cost_climbing_stairs_dp" path = "chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs" # Run Command: cargo run --bin climbing_stairs_constraint_dp [[bin]] name = "climbing_stairs_constraint_dp" path = "chapter_dynamic_programming/climbing_stairs_constraint_dp.rs" # Run Command: cargo run --bin climbing_stairs_backtrack [[bin]] name = "climbing_stairs_backtrack" path = "chapter_dynamic_programming/climbing_stairs_backtrack.rs" # Run Command: cargo run --bin subset_sum_i_naive [[bin]] name = "subset_sum_i_naive" path = "chapter_backtracking/subset_sum_i_naive.rs" # Run Command: cargo run --bin subset_sum_i [[bin]] name = "subset_sum_i" path = "chapter_backtracking/subset_sum_i.rs" # Run Command: cargo run --bin subset_sum_ii [[bin]] name = "subset_sum_ii" path = "chapter_backtracking/subset_sum_ii.rs" # Run Command: cargo run --bin coin_change [[bin]] name = "coin_change" path = "chapter_dynamic_programming/coin_change.rs" # Run Command: cargo run --bin coin_change_ii [[bin]] name = "coin_change_ii" path = "chapter_dynamic_programming/coin_change_ii.rs" # Run Command: cargo run --bin unbounded_knapsack [[bin]] name = "unbounded_knapsack" path = "chapter_dynamic_programming/unbounded_knapsack.rs" # Run Command: cargo run --bin knapsack [[bin]] name = "knapsack" path = "chapter_dynamic_programming/knapsack.rs" # Run Command: cargo run --bin min_path_sum [[bin]] name = "min_path_sum" path = "chapter_dynamic_programming/min_path_sum.rs" # Run Command: cargo run --bin edit_distance [[bin]] name = "edit_distance" path = "chapter_dynamic_programming/edit_distance.rs" # Run Command: cargo run --bin n_queens [[bin]] name = "n_queens" path = "chapter_backtracking/n_queens.rs" # Run Command: cargo run --bin permutations_i [[bin]] name = "permutations_i" path = "chapter_backtracking/permutations_i.rs" # Run Command: cargo run --bin permutations_ii [[bin]] name = "permutations_ii" path = "chapter_backtracking/permutations_ii.rs" # Run Command: cargo run --bin preorder_traversal_i_compact [[bin]] name = "preorder_traversal_i_compact" path = "chapter_backtracking/preorder_traversal_i_compact.rs" # Run Command: cargo run --bin preorder_traversal_ii_compact [[bin]] name = "preorder_traversal_ii_compact" path = "chapter_backtracking/preorder_traversal_ii_compact.rs" # Run Command: cargo run --bin preorder_traversal_iii_compact [[bin]] name = "preorder_traversal_iii_compact" path = "chapter_backtracking/preorder_traversal_iii_compact.rs" # Run Command: cargo run --bin preorder_traversal_iii_template [[bin]] name = "preorder_traversal_iii_template" path = "chapter_backtracking/preorder_traversal_iii_template.rs" # Run Command: cargo run --bin binary_search_recur [[bin]] name = "binary_search_recur" path = "chapter_divide_and_conquer/binary_search_recur.rs" # Run Command: cargo run --bin hanota [[bin]] name = "hanota" path = "chapter_divide_and_conquer/hanota.rs" # Run Command: cargo run --bin build_tree [[bin]] name = "build_tree" path = "chapter_divide_and_conquer/build_tree.rs" # Run Command: cargo run --bin coin_change_greedy [[bin]] name = "coin_change_greedy" path = "chapter_greedy/coin_change_greedy.rs" # Run Command: cargo run --bin fractional_knapsack [[bin]] name = "fractional_knapsack" path = "chapter_greedy/fractional_knapsack.rs" # Run Command: cargo run --bin max_capacity [[bin]] name = "max_capacity" path = "chapter_greedy/max_capacity.rs" # Run Command: cargo run --bin max_product_cutting [[bin]] name = "max_product_cutting" path = "chapter_greedy/max_product_cutting.rs" [dependencies] rand = "0.8.5" ================================================ FILE: ja/codes/rust/chapter_array_and_linkedlist/array.rs ================================================ /* * File: array.rs * Created Time: 2023-01-15 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use rand::Rng; /* 要素へランダムアクセス */ fn random_access(nums: &[i32]) -> i32 { // 区間 [0, nums.len()) からランダムに数字を 1 つ選ぶ let random_index = rand::thread_rng().gen_range(0..nums.len()); // ランダムな要素を取得して返す let random_num = nums[random_index]; random_num } /* 配列長を拡張する */ fn extend(nums: &[i32], enlarge: usize) -> Vec { // 拡張後の長さを持つ配列を初期化する let mut res: Vec = vec![0; nums.len() + enlarge]; // 元の配列の全要素を新しい配列にコピー res[0..nums.len()].copy_from_slice(nums); // 拡張後の新しい配列を返す res } /* 配列の index 番目に要素 num を挿入 */ fn insert(nums: &mut [i32], num: i32, index: usize) { // インデックス index 以降の全要素を 1 つ後ろへ移動する for i in (index + 1..nums.len()).rev() { nums[i] = nums[i - 1]; } // index の要素に num を代入する nums[index] = num; } /* index の要素を削除する */ fn remove(nums: &mut [i32], index: usize) { // インデックス index より後ろの全要素を 1 つ前へ移動する for i in index..nums.len() - 1 { nums[i] = nums[i + 1]; } } /* 配列を走査 */ fn traverse(nums: &[i32]) { let mut _count = 0; // インデックスで配列を走査 for i in 0..nums.len() { _count += nums[i]; } // 配列要素を直接走査 _count = 0; for &num in nums { _count += num; } } /* 配列内で指定要素を探す */ fn find(nums: &[i32], target: i32) -> Option { for i in 0..nums.len() { if nums[i] == target { return Some(i); } } None } /* Driver Code */ fn main() { /* 配列を初期化 */ let arr: [i32; 5] = [0; 5]; print!("配列 arr = "); print_util::print_array(&arr); // Rust では、長さを指定する場合([i32; 5])は配列、指定しない場合(&[i32])はスライスである // Rust の配列はコンパイル時に長さが確定するよう設計されているため、長さには定数しか使えない // Vector は Rust で通常動的配列として使われる型である // extend() メソッドを実装しやすくするため、以下では vector を配列(array)として扱う let nums: Vec = vec![1, 3, 2, 5, 4]; print!("\n配列 nums = "); print_util::print_array(&nums); // ランダムアクセス let random_num = random_access(&nums); println!("\nnums からランダムな要素 {} を取得", random_num); // 長さを拡張 let mut nums: Vec = extend(&nums, 3); print!("配列の長さを 8 に拡張すると、nums = "); print_util::print_array(&nums); // 要素を挿入する insert(&mut nums, 6, 3); print!("\nインデックス 3 に数値 6 を挿入すると、nums = "); print_util::print_array(&nums); // 要素を削除 remove(&mut nums, 2); print!("\nインデックス 2 の要素を削除すると、nums = "); print_util::print_array(&nums); // 配列を走査 traverse(&nums); // 要素を探索する let index = find(&nums, 3).unwrap(); println!("\nnums 内で要素 3 を検索すると、インデックス = {}", index); } ================================================ FILE: ja/codes/rust/chapter_array_and_linkedlist/linked_list.rs ================================================ /* * File: linked_list.rs * Created Time: 2023-03-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* 連結リストでノード n0 の後ろにノード P を挿入する */ #[allow(non_snake_case)] pub fn insert(n0: &Rc>>, P: Rc>>) { let n1 = n0.borrow_mut().next.take(); P.borrow_mut().next = n1; n0.borrow_mut().next = Some(P); } /* 連結リストでノード n0 の直後のノードを削除する */ #[allow(non_snake_case)] pub fn remove(n0: &Rc>>) { // n0 -> P -> n1 let P = n0.borrow_mut().next.take(); if let Some(node) = P { let n1 = node.borrow_mut().next.take(); n0.borrow_mut().next = n1; } } /* 連結リスト内で index 番目のノードにアクセス */ pub fn access(head: Rc>>, index: i32) -> Option>>> { fn dfs( head: Option<&Rc>>>, index: i32, ) -> Option>>> { if index <= 0 { return head.cloned(); } if let Some(node) = head { dfs(node.borrow().next.as_ref(), index - 1) } else { None } } dfs(Some(head).as_ref(), index) } /* 連結リストで値が target の最初のノードを探す */ pub fn find(head: Rc>>, target: T) -> i32 { fn find(head: Option<&Rc>>>, target: T, idx: i32) -> i32 { if let Some(node) = head { if node.borrow().val == target { return idx; } return find(node.borrow().next.as_ref(), target, idx + 1); } else { -1 } } find(Some(head).as_ref(), target, 0) } /* Driver Code */ fn main() { /* 連結リストを初期化 */ // 各ノードを初期化 let n0 = ListNode::new(1); let n1 = ListNode::new(3); let n2 = ListNode::new(2); let n3 = ListNode::new(5); let n4 = ListNode::new(4); // ノード間の参照を構築する n0.borrow_mut().next = Some(n1.clone()); n1.borrow_mut().next = Some(n2.clone()); n2.borrow_mut().next = Some(n3.clone()); n3.borrow_mut().next = Some(n4.clone()); print!("初期化された連結リストは "); print_util::print_linked_list(&n0); /* ノードを挿入 */ insert(&n0, ListNode::new(0)); print!("ノード挿入後の連結リストは "); print_util::print_linked_list(&n0); /* ノードを削除 */ remove(&n0); print!("ノード削除後の連結リストは "); print_util::print_linked_list(&n0); /* ノードにアクセス */ let node = access(n0.clone(), 3); println!("連結リストのインデックス 3 にあるノードの値 = {}", node.unwrap().borrow().val); /* ノードを探索 */ let index = find(n0.clone(), 2); println!("連結リスト内で値が 2 のノードのインデックス = {}", index); } ================================================ FILE: ja/codes/rust/chapter_array_and_linkedlist/list.rs ================================================ /* * File: list.rs * Created Time: 2023-01-18 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Driver Code */ fn main() { // リストを初期化 let mut nums: Vec = vec![1, 3, 2, 5, 4]; print!("リスト nums = "); print_util::print_array(&nums); // 要素にアクセス let num = nums[1]; println!("\nインデックス 1 の要素にアクセスすると、num = {num}"); // 要素を更新 nums[1] = 0; print!("インデックス 1 の要素を 0 に更新すると、nums = "); print_util::print_array(&nums); // リストを空にする nums.clear(); print!("\nリストを空にした後、nums = "); print_util::print_array(&nums); // 末尾に要素を追加 nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); print!("\n要素を追加した後、nums = "); print_util::print_array(&nums); // 中間に要素を挿入 nums.insert(3, 6); print!("\nインデックス 3 に数値 6 を挿入すると、nums = "); print_util::print_array(&nums); // 要素を削除 nums.remove(3); print!("\nインデックス 3 の要素を削除すると、nums = "); print_util::print_array(&nums); // インデックスでリストを走査 let mut _count = 0; for i in 0..nums.len() { _count += nums[i]; } // リスト要素を直接走査 _count = 0; for x in &nums { _count += x; } // 2 つのリストを連結する let mut nums1 = vec![6, 8, 7, 10, 9]; nums.append(&mut nums1); // append(move)の後では nums1 は空になる! // nums.extend(&nums1); // extend(借用)の後も nums1 は引き続き使える print!("\nリスト nums1 を nums の後ろに連結すると、nums = "); print_util::print_array(&nums); // リストをソート nums.sort(); print!("\nリストをソートした後、nums = "); print_util::print_array(&nums); } ================================================ FILE: ja/codes/rust/chapter_array_and_linkedlist/my_list.rs ================================================ /* * File: my_list.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* リストクラス */ #[allow(dead_code)] struct MyList { arr: Vec, // 配列(リスト要素を格納) capacity: usize, // リスト容量 size: usize, // リストの長さ(現在の要素数) extend_ratio: usize, // リスト拡張時の増加倍率 } #[allow(unused, unused_comparisons)] impl MyList { /* コンストラクタ */ pub fn new(capacity: usize) -> Self { let mut vec = vec![0; capacity]; Self { arr: vec, capacity, size: 0, extend_ratio: 2, } } /* リストの長さを取得(現在の要素数) */ pub fn size(&self) -> usize { return self.size; } /* リスト容量を取得する */ pub fn capacity(&self) -> usize { return self.capacity; } /* 要素にアクセス */ pub fn get(&self, index: usize) -> i32 { // インデックスが範囲外なら例外を送出する。以下同様 if index >= self.size { panic!("インデックスが範囲外です") }; return self.arr[index]; } /* 要素を更新 */ pub fn set(&mut self, index: usize, num: i32) { if index >= self.size { panic!("インデックスが範囲外です") }; self.arr[index] = num; } /* 末尾に要素を追加 */ pub fn add(&mut self, num: i32) { // 要素数が容量を超えると、拡張機構が発動する if self.size == self.capacity() { self.extend_capacity(); } self.arr[self.size] = num; // 要素数を更新 self.size += 1; } /* 中間に要素を挿入 */ pub fn insert(&mut self, index: usize, num: i32) { if index >= self.size() { panic!("インデックスが範囲外です") }; // 要素数が容量を超えると、拡張機構が発動する if self.size == self.capacity() { self.extend_capacity(); } // index 以降の要素をすべて 1 つ後ろへずらす for j in (index..self.size).rev() { self.arr[j + 1] = self.arr[j]; } self.arr[index] = num; // 要素数を更新 self.size += 1; } /* 要素を削除 */ pub fn remove(&mut self, index: usize) -> i32 { if index >= self.size() { panic!("インデックスが範囲外です") }; let num = self.arr[index]; // インデックス index より後の要素をすべて 1 つ前に移動する for j in index..self.size - 1 { self.arr[j] = self.arr[j + 1]; } // 要素数を更新 self.size -= 1; // 削除された要素を返す return num; } /* リストの拡張 */ pub fn extend_capacity(&mut self) { // 元の配列の extend_ratio 倍の長さを持つ新しい配列を作成し、元の配列をコピーする let new_capacity = self.capacity * self.extend_ratio; self.arr.resize(new_capacity, 0); // リストの容量を更新 self.capacity = new_capacity; } /* リストを配列に変換する */ pub fn to_array(&self) -> Vec { // 有効長の範囲内のリスト要素のみを変換 let mut arr = Vec::new(); for i in 0..self.size { arr.push(self.get(i)); } arr } } /* Driver Code */ fn main() { /* リストを初期化 */ let mut nums = MyList::new(10); /* 末尾に要素を追加 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print!("リスト nums = "); print_util::print_array(&nums.to_array()); print!(" 、容量 = {} 、長さ = {}", nums.capacity(), nums.size()); /* 中間に要素を挿入 */ nums.insert(3, 6); print!("\nインデックス 3 に数値 6 を挿入すると、nums = "); print_util::print_array(&nums.to_array()); /* 要素を削除 */ nums.remove(3); print!("\nインデックス 3 の要素を削除すると、nums = "); print_util::print_array(&nums.to_array()); /* 要素にアクセス */ let num = nums.get(1); println!("\nインデックス 1 の要素にアクセスすると、num = {num}"); /* 要素を更新 */ nums.set(1, 0); print!("インデックス 1 の要素を 0 に更新すると、nums = "); print_util::print_array(&nums.to_array()); /* 拡張機構をテストする */ for i in 0..10 { // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する nums.add(i); } print!("\n拡張後のリスト nums = "); print_util::print_array(&nums.to_array()); print!(" 、容量 = {} 、長さ = {}", nums.capacity(), nums.size()); } ================================================ FILE: ja/codes/rust/chapter_backtracking/n_queens.rs ================================================ /* * File: n_queens.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* バックトラッキング:N クイーン */ fn backtrack( row: usize, n: usize, state: &mut Vec>, res: &mut Vec>>, cols: &mut [bool], diags1: &mut [bool], diags2: &mut [bool], ) { // すべての行への配置が完了したら、解を記録する if row == n { res.push(state.clone()); return; } // すべての列を走査 for col in 0..n { // このマスに対応する主対角線と副対角線を計算 let diag1 = row + n - 1 - col; let diag2 = row + col; // 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない if !cols[col] && !diags1[diag1] && !diags2[diag2] { // 試行:そのマスにクイーンを置く state[row][col] = "Q".into(); (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true); // 次の行に配置する backtrack(row + 1, n, state, res, cols, diags1, diags2); // 戻す:そのマスを空きマスに戻す state[row][col] = "#".into(); (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false); } } } /* N クイーンを解く */ fn n_queens(n: usize) -> Vec>> { // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す let mut state: Vec> = vec![vec!["#".to_string(); n]; n]; let mut cols = vec![false; n]; // 列にクイーンがあるか記録 let mut diags1 = vec![false; 2 * n - 1]; // 主対角線にクイーンがあるかを記録 let mut diags2 = vec![false; 2 * n - 1]; // 副対角線にクイーンがあるかを記録 let mut res: Vec>> = Vec::new(); backtrack( 0, n, &mut state, &mut res, &mut cols, &mut diags1, &mut diags2, ); res } /* Driver Code */ pub fn main() { let n: usize = 4; let res = n_queens(n); println!("盤面の縦横は {n}"); println!("クイーンの配置方法は全部で {} 通り", res.len()); for state in res.iter() { println!("--------------------"); for row in state.iter() { println!("{:?}", row); } } } ================================================ FILE: ja/codes/rust/chapter_backtracking/permutations_i.rs ================================================ /* * File: permutations_i.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* バックトラッキング:順列 I */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // 状態の長さが要素数に等しければ、解を記録 if state.len() == choices.len() { res.push(state); return; } // すべての選択肢を走査 for i in 0..choices.len() { let choice = choices[i]; // 枝刈り:要素の重複選択を許可しない if !selected[i] { // 試行: 選択を行い、状態を更新 selected[i] = true; state.push(choice); // 次の選択へ進む backtrack(state.clone(), choices, selected, res); // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.pop(); } } } /* 全順列 I */ fn permutations_i(nums: &mut [i32]) -> Vec> { let mut res = Vec::new(); // 状態(部分集合) backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [1, 2, 3]; let res = permutations_i(&mut nums); println!("入力配列 nums = {:?}", &nums); println!("すべての順列 res = {:?}", &res); } ================================================ FILE: ja/codes/rust/chapter_backtracking/permutations_ii.rs ================================================ /* * File: permutations_ii.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use std::collections::HashSet; /* バックトラッキング:順列 II */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // 状態の長さが要素数に等しければ、解を記録 if state.len() == choices.len() { res.push(state); return; } // すべての選択肢を走査 let mut duplicated = HashSet::::new(); for i in 0..choices.len() { let choice = choices[i]; // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない if !selected[i] && !duplicated.contains(&choice) { // 試行: 選択を行い、状態を更新 duplicated.insert(choice); // 選択済みの要素値を記録 selected[i] = true; state.push(choice); // 次の選択へ進む backtrack(state.clone(), choices, selected, res); // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.pop(); } } } /* 全順列 II */ fn permutations_ii(nums: &mut [i32]) -> Vec> { let mut res = Vec::new(); backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [1, 2, 2]; let res = permutations_ii(&mut nums); println!("入力配列 nums = {:?}", &nums); println!("すべての順列 res = {:?}", &res); } ================================================ FILE: ja/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs ================================================ /* * File: preorder_traversal_i_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* 前順走査:例題 1 */ fn pre_order(res: &mut Vec>>, root: Option<&Rc>>) { if root.is_none() { return; } if let Some(node) = root { if node.borrow().val == 7 { // 解を記録 res.push(node.clone()); } pre_order(res, node.borrow().left.as_ref()); pre_order(res, node.borrow().right.as_ref()); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("二分木を初期化"); print_util::print_tree(root.as_ref().unwrap()); // 先行順走査 let mut res = Vec::new(); pre_order(&mut res, root.as_ref()); println!("\n値が 7 のノードをすべて出力"); let mut vals = Vec::new(); for node in res { vals.push(node.borrow().val) } println!("{:?}", vals); } ================================================ FILE: ja/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs ================================================ /* * File: preorder_traversal_ii_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* 前順走査:例題 2 */ fn pre_order( res: &mut Vec>>>, path: &mut Vec>>, root: Option<&Rc>>, ) { if root.is_none() { return; } if let Some(node) = root { // 試す path.push(node.clone()); if node.borrow().val == 7 { // 解を記録 res.push(path.clone()); } pre_order(res, path, node.borrow().left.as_ref()); pre_order(res, path, node.borrow().right.as_ref()); // バックトラック path.pop(); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("二分木を初期化"); print_util::print_tree(root.as_ref().unwrap()); // 先行順走査 let mut path = Vec::new(); let mut res = Vec::new(); pre_order(&mut res, &mut path, root.as_ref()); println!("\n根ノードからノード 7 までのすべての経路を出力"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: ja/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs ================================================ /* * File: preorder_traversal_iii_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* 前順走査:例題 3 */ fn pre_order( res: &mut Vec>>>, path: &mut Vec>>, root: Option<&Rc>>, ) { // 枝刈り if root.is_none() || root.as_ref().unwrap().borrow().val == 3 { return; } if let Some(node) = root { // 試す path.push(node.clone()); if node.borrow().val == 7 { // 解を記録 res.push(path.clone()); } pre_order(res, path, node.borrow().left.as_ref()); pre_order(res, path, node.borrow().right.as_ref()); // バックトラック path.pop(); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("二分木を初期化"); print_util::print_tree(root.as_ref().unwrap()); // 先行順走査 let mut path = Vec::new(); let mut res = Vec::new(); pre_order(&mut res, &mut path, root.as_ref()); println!("\n根ノードからノード 7 までのすべての経路を出力し、経路には値が 3 のノードを含めない"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: ja/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs ================================================ /* * File: preorder_traversal_iii_template.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* 現在の状態が解かどうかを判定 */ fn is_solution(state: &mut Vec>>) -> bool { return !state.is_empty() && state.last().unwrap().borrow().val == 7; } /* 解を記録 */ fn record_solution( state: &mut Vec>>, res: &mut Vec>>>, ) { res.push(state.clone()); } /* 現在の状態で、この選択が有効かどうかを判定 */ fn is_valid(_: &mut Vec>>, choice: Option<&Rc>>) -> bool { return choice.is_some() && choice.unwrap().borrow().val != 3; } /* 状態を更新 */ fn make_choice(state: &mut Vec>>, choice: Rc>) { state.push(choice); } /* 状態を元に戻す */ fn undo_choice(state: &mut Vec>>, _: Rc>) { state.pop(); } /* バックトラッキング:例題 3 */ fn backtrack( state: &mut Vec>>, choices: &Vec>>>, res: &mut Vec>>>, ) { // 解かどうかを確認 if is_solution(state) { // 解を記録 record_solution(state, res); } // すべての選択肢を走査 for &choice in choices.iter() { // 枝刈り:選択が妥当かを確認する if is_valid(state, choice) { // 試行: 選択を行い、状態を更新 make_choice(state, choice.unwrap().clone()); // 次の選択へ進む backtrack( state, &vec![ choice.unwrap().borrow().left.as_ref(), choice.unwrap().borrow().right.as_ref(), ], res, ); // バックトラック:選択を取り消し、前の状態に戻す undo_choice(state, choice.unwrap().clone()); } } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("二分木を初期化"); print_util::print_tree(root.as_ref().unwrap()); // バックトラッキング法 let mut res = Vec::new(); backtrack(&mut Vec::new(), &mut vec![root.as_ref()], &mut res); println!("\n根ノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含まないことを条件とする"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: ja/codes/rust/chapter_backtracking/subset_sum_i.rs ================================================ /* * File: subset_sum_i.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* バックトラッキング:部分和 I */ fn backtrack( state: &mut Vec, target: i32, choices: &[i32], start: usize, res: &mut Vec>, ) { // 部分集合の和が target に等しければ、解を記録 if target == 0 { res.push(state.clone()); return; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for i in start..choices.len() { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if target - choices[i] < 0 { break; } // 試す:選択を行い、target と start を更新 state.push(choices[i]); // 次の選択へ進む backtrack(state, target - choices[i], choices, i, res); // バックトラック:選択を取り消し、前の状態に戻す state.pop(); } } /* 部分和 I を解く */ fn subset_sum_i(nums: &mut [i32], target: i32) -> Vec> { let mut state = Vec::new(); // 状態(部分集合) nums.sort(); // nums をソート let start = 0; // 開始点を走査 let mut res = Vec::new(); // 結果リスト(部分集合のリスト) backtrack(&mut state, target, nums, start, &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [3, 4, 5]; let target = 9; let res = subset_sum_i(&mut nums, target); println!("入力配列 nums = {:?}, target = {}", &nums, target); println!("和が {} に等しいすべての部分集合 res = {:?}", target, &res); } ================================================ FILE: ja/codes/rust/chapter_backtracking/subset_sum_i_naive.rs ================================================ /* * File: subset_sum_i_naive.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* バックトラッキング:部分和 I */ fn backtrack( state: &mut Vec, target: i32, total: i32, choices: &[i32], res: &mut Vec>, ) { // 部分集合の和が target に等しければ、解を記録 if total == target { res.push(state.clone()); return; } // すべての選択肢を走査 for i in 0..choices.len() { // 枝刈り:部分和が target を超える場合はその選択をスキップする if total + choices[i] > target { continue; } // 試行:選択を行い、要素と total を更新する state.push(choices[i]); // 次の選択へ進む backtrack(state, target, total + choices[i], choices, res); // バックトラック:選択を取り消し、前の状態に戻す state.pop(); } } /* 部分和 I を解く(重複部分集合を含む) */ fn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec> { let mut state = Vec::new(); // 状態(部分集合) let total = 0; // 部分和 let mut res = Vec::new(); // 結果リスト(部分集合のリスト) backtrack(&mut state, target, total, nums, &mut res); res } /* Driver Code */ pub fn main() { let nums = [3, 4, 5]; let target = 9; let res = subset_sum_i_naive(&nums, target); println!("入力配列 nums = {:?}, target = {}", &nums, target); println!("和が {} に等しいすべての部分集合 res = {:?}", target, &res); println!("注意: この方法の出力結果には重複した集合が含まれます"); } ================================================ FILE: ja/codes/rust/chapter_backtracking/subset_sum_ii.rs ================================================ /* * File: subset_sum_ii.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* バックトラッキング:部分和 II */ fn backtrack( state: &mut Vec, target: i32, choices: &[i32], start: usize, res: &mut Vec>, ) { // 部分集合の和が target に等しければ、解を記録 if target == 0 { res.push(state.clone()); return; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for i in start..choices.len() { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if target - choices[i] < 0 { break; } // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする if i > start && choices[i] == choices[i - 1] { continue; } // 試す:選択を行い、target と start を更新 state.push(choices[i]); // 次の選択へ進む backtrack(state, target - choices[i], choices, i + 1, res); // バックトラック:選択を取り消し、前の状態に戻す state.pop(); } } /* 部分和 II を解く */ fn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec> { let mut state = Vec::new(); // 状態(部分集合) nums.sort(); // nums をソート let start = 0; // 開始点を走査 let mut res = Vec::new(); // 結果リスト(部分集合のリスト) backtrack(&mut state, target, nums, start, &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [4, 4, 5]; let target = 9; let res = subset_sum_ii(&mut nums, target); println!("入力配列 nums = {:?}, target = {}", &nums, target); println!("和が {} に等しいすべての部分集合 res = {:?}", target, &res); } ================================================ FILE: ja/codes/rust/chapter_computational_complexity/iteration.rs ================================================ /* * File: iteration.rs * Created Time: 2023-09-02 * Author: night-cruise (2586447362@qq.com) */ /* for ループ */ fn for_loop(n: i32) -> i32 { let mut res = 0; // 1, 2, ..., n-1, n を順に加算する for i in 1..=n { res += i; } res } /* while ループ */ fn while_loop(n: i32) -> i32 { let mut res = 0; let mut i = 1; // 条件変数を初期化する // 1, 2, ..., n-1, n を順に加算する while i <= n { res += i; i += 1; // 条件変数を更新する } res } /* while ループ(2回更新) */ fn while_loop_ii(n: i32) -> i32 { let mut res = 0; let mut i = 1; // 条件変数を初期化する // 1, 4, 10, ... を順に加算する while i <= n { res += i; // 条件変数を更新する i += 1; i *= 2; } res } /* 二重 for ループ */ fn nested_for_loop(n: i32) -> String { let mut res = vec![]; // i = 1, 2, ..., n-1, n とループする for i in 1..=n { // j = 1, 2, ..., n-1, n とループする for j in 1..=n { res.push(format!("({}, {}), ", i, j)); } } res.join("") } /* Driver Code */ fn main() { let n = 5; let mut res; res = for_loop(n); println!("\nfor ループの合計結果 res = {res}"); res = while_loop(n); println!("\nwhile ループの合計結果 res = {res}"); res = while_loop_ii(n); println!("\nwhile ループ(2 回更新)の合計結果 res = {}", res); let res = nested_for_loop(n); println!("\n二重 for ループの走査結果 {res}"); } ================================================ FILE: ja/codes/rust/chapter_computational_complexity/recursion.rs ================================================ /* * File: recursion.rs * Created Time: 2023-09-02 * Author: night-cruise (2586447362@qq.com) */ /* 再帰 */ fn recur(n: i32) -> i32 { // 終了条件 if n == 1 { return 1; } // 再帰:再帰呼び出し let res = recur(n - 1); // 帰りがけ:結果を返す n + res } /* 反復で再帰を模擬する */ fn for_loop_recur(n: i32) -> i32 { // 明示的なスタックを使ってシステムコールスタックを模擬する let mut stack = Vec::new(); let mut res = 0; // 再帰:再帰呼び出し for i in (1..=n).rev() { // 「スタックへのプッシュ」で「再帰」を模擬する stack.push(i); } // 帰りがけ:結果を返す while !stack.is_empty() { // 「スタックから取り出す操作」で「帰り」をシミュレート res += stack.pop().unwrap(); } // res = 1+2+3+...+n res } /* 末尾再帰 */ fn tail_recur(n: i32, res: i32) -> i32 { // 終了条件 if n == 0 { return res; } // 末尾再帰呼び出し tail_recur(n - 1, res + n) } /* フィボナッチ数列:再帰 */ fn fib(n: i32) -> i32 { // 終了条件 f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1; } // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す let res = fib(n - 1) + fib(n - 2); // 結果を返す res } /* Driver Code */ fn main() { let n = 5; let mut res; res = recur(n); println!("\n再帰関数による合計結果 res = {res}"); res = for_loop_recur(n); println!("\n反復で再帰を模擬した合計結果 res = {res}"); res = tail_recur(n, 0); println!("\n末尾再帰関数による合計結果 res = {res}"); res = fib(n); println!("\nフィボナッチ数列の第 {n} 項は {res}"); } ================================================ FILE: ja/codes/rust/chapter_computational_complexity/space_complexity.rs ================================================ /* * File: space_complexity.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode, TreeNode}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; /* 関数 */ fn function() -> i32 { // 何らかの処理を行う return 0; } /* 定数階 */ #[allow(unused)] fn constant(n: i32) { // 定数、変数、オブジェクトは O(1) の空間を占める const A: i32 = 0; let b = 0; let nums = vec![0; 10000]; let node = ListNode::new(0); // ループ内の変数は O(1) の空間を占める for i in 0..n { let c = 0; } // ループ内の関数は O(1) の空間を占める for i in 0..n { function(); } } /* 線形階 */ #[allow(unused)] fn linear(n: i32) { // 長さ n の配列は O(n) の空間を使用 let mut nums = vec![0; n as usize]; // 長さ n のリストは O(n) の空間を使用 let mut nodes = Vec::new(); for i in 0..n { nodes.push(ListNode::new(i)) } // 長さ n のハッシュテーブルは O(n) の空間を使用 let mut map = HashMap::new(); for i in 0..n { map.insert(i, i.to_string()); } } /* 線形時間(再帰実装) */ fn linear_recur(n: i32) { println!("再帰 n = {}", n); if n == 1 { return; }; linear_recur(n - 1); } /* 二乗階 */ #[allow(unused)] fn quadratic(n: i32) { // 行列は O(n^2) の空間を使用する let num_matrix = vec![vec![0; n as usize]; n as usize]; // 二次元リストは O(n^2) の空間を使用 let mut num_list = Vec::new(); for i in 0..n { let mut tmp = Vec::new(); for j in 0..n { tmp.push(0); } num_list.push(tmp); } } /* 二次時間(再帰実装) */ fn quadratic_recur(n: i32) -> i32 { if n <= 0 { return 0; }; // 配列 nums の長さは n, n-1, ..., 2, 1 let nums = vec![0; n as usize]; println!("再帰 n = {} における nums の長さ = {}", n, nums.len()); return quadratic_recur(n - 1); } /* 指数時間(完全二分木の構築) */ fn build_tree(n: i32) -> Option>> { if n == 0 { return None; }; let root = TreeNode::new(0); root.borrow_mut().left = build_tree(n - 1); root.borrow_mut().right = build_tree(n - 1); return Some(root); } /* Driver Code */ fn main() { let n = 5; // 定数階 constant(n); // 線形階 linear(n); linear_recur(n); // 二乗階 quadratic(n); quadratic_recur(n); // 指数オーダー let root = build_tree(n); print_util::print_tree(&root.unwrap()); } ================================================ FILE: ja/codes/rust/chapter_computational_complexity/time_complexity.rs ================================================ /* * File: time_complexity.rs * Created Time: 2023-01-10 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ /* 定数階 */ fn constant(n: i32) -> i32 { _ = n; let mut count = 0; let size = 100_000; for _ in 0..size { count += 1; } count } /* 線形階 */ fn linear(n: i32) -> i32 { let mut count = 0; for _ in 0..n { count += 1; } count } /* 線形時間(配列を走査) */ fn array_traversal(nums: &[i32]) -> i32 { let mut count = 0; // ループ回数は配列長に比例する for _ in nums { count += 1; } count } /* 二乗階 */ fn quadratic(n: i32) -> i32 { let mut count = 0; // ループ回数はデータサイズ n の二乗に比例する for _ in 0..n { for _ in 0..n { count += 1; } } count } /* 二次時間(バブルソート) */ fn bubble_sort(nums: &mut [i32]) -> i32 { let mut count = 0; // カウンタ // 外側のループ:未ソート区間は [0, i] for i in (1..nums.len()).rev() { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j in 0..i { if nums[j] > nums[j + 1] { // nums[j] と nums[j + 1] を交換 let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 要素交換には 3 回の単位操作が含まれる } } } count } /* 指数時間(ループ実装) */ fn exponential(n: i32) -> i32 { let mut count = 0; let mut base = 1; // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する for _ in 0..n { for _ in 0..base { count += 1 } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 count } /* 指数時間(再帰実装) */ fn exp_recur(n: i32) -> i32 { if n == 1 { return 1; } exp_recur(n - 1) + exp_recur(n - 1) + 1 } /* 対数時間(ループ実装) */ fn logarithmic(mut n: i32) -> i32 { let mut count = 0; while n > 1 { n = n / 2; count += 1; } count } /* 対数時間(再帰実装) */ fn log_recur(n: i32) -> i32 { if n <= 1 { return 0; } log_recur(n / 2) + 1 } /* 線形対数時間 */ fn linear_log_recur(n: i32) -> i32 { if n <= 1 { return 1; } let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2); for _ in 0..n { count += 1; } return count; } /* 階乗時間(再帰実装) */ fn factorial_recur(n: i32) -> i32 { if n == 0 { return 1; } let mut count = 0; // 1個から n 個に分裂 for _ in 0..n { count += factorial_recur(n - 1); } count } /* Driver Code */ fn main() { // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる let n: i32 = 8; println!("入力データサイズ n = {}", n); let mut count = constant(n); println!("定数時間の操作回数 = {}", count); count = linear(n); println!("線形時間の操作回数 = {}", count); count = array_traversal(&vec![0; n as usize]); println!("線形時間(配列の走査)の操作回数 = {}", count); count = quadratic(n); println!("二乗時間の操作回数 = {}", count); let mut nums = (1..=n).rev().collect::>(); // [n,n-1,...,2,1] count = bubble_sort(&mut nums); println!("二乗時間(バブルソート)の操作回数 = {}", count); count = exponential(n); println!("指数時間(ループ実装)の操作回数 = {}", count); count = exp_recur(n); println!("指数時間(再帰実装)の操作回数 = {}", count); count = logarithmic(n); println!("対数時間(ループ実装)の操作回数 = {}", count); count = log_recur(n); println!("対数時間(再帰実装)の操作回数 = {}", count); count = linear_log_recur(n); println!("線形対数時間(再帰実装)の操作回数 = {}", count); count = factorial_recur(n); println!("階乗時間(再帰実装)の操作回数 = {}", count); } ================================================ FILE: ja/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs ================================================ /* * File: worst_best_time_complexity.rs * Created Time: 2023-01-13 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use rand::seq::SliceRandom; use rand::thread_rng; /* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ fn random_numbers(n: i32) -> Vec { // 配列 nums = { 1, 2, 3, ..., n } を生成 let mut nums = (1..=n).collect::>(); // 配列要素をランダムにシャッフル nums.shuffle(&mut thread_rng()); nums } /* 配列 nums 内で数値 1 のインデックスを探す */ fn find_one(nums: &[i32]) -> Option { for i in 0..nums.len() { // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる if nums[i] == 1 { return Some(i); } } None } /* Driver Code */ fn main() { for _ in 0..10 { let n = 100; let nums = random_numbers(n); let index = find_one(&nums).unwrap(); print!("\n配列 [ 1, 2, ..., n ] をシャッフルした後 = "); print_util::print_array(&nums); println!("\n数字 1 のインデックスは {}", index); } } ================================================ FILE: ja/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs ================================================ /* * File: binary_search_recur.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* 二分探索:問題 f(i, j) */ fn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 { // 区間が空なら対象要素は存在しないので -1 を返す if i > j { return -1; } let m: i32 = i + (j - i) / 2; if nums[m as usize] < target { // 部分問題 f(m+1, j) を再帰的に解く return dfs(nums, target, m + 1, j); } else if nums[m as usize] > target { // 部分問題 f(i, m-1) を再帰的に解く return dfs(nums, target, i, m - 1); } else { // 目標要素が見つかったらそのインデックスを返す return m; } } /* 二分探索 */ fn binary_search(nums: &[i32], target: i32) -> i32 { let n = nums.len() as i32; // 問題 f(0, n-1) を解く dfs(nums, target, 0, n - 1) } /* Driver Code */ pub fn main() { let target = 6; let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分探索(両閉区間) let index = binary_search(&nums, target); println!("目的の要素 6 のインデックス = {index}"); } ================================================ FILE: ja/codes/rust/chapter_divide_and_conquer/build_tree.rs ================================================ /* * File: build_tree.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, TreeNode}; use std::collections::HashMap; use std::{cell::RefCell, rc::Rc}; /* 二分木を構築:分割統治 */ fn dfs( preorder: &[i32], inorder_map: &HashMap, i: i32, l: i32, r: i32, ) -> Option>> { // 部分木区間が空なら終了する if r - l < 0 { return None; } // ルートノードを初期化する let root = TreeNode::new(preorder[i as usize]); // m を求めて左右部分木を分割する let m = inorder_map.get(&preorder[i as usize]).unwrap(); // 部分問題:左部分木を構築する root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1); // 部分問題:右部分木を構築する root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r); // 根ノードを返す Some(root) } /* 二分木を構築 */ fn build_tree(preorder: &[i32], inorder: &[i32]) -> Option>> { // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する let mut inorder_map: HashMap = HashMap::new(); for i in 0..inorder.len() { inorder_map.insert(inorder[i], i as i32); } let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1); root } /* Driver Code */ fn main() { let preorder = [3, 9, 2, 1, 7]; let inorder = [9, 3, 1, 2, 7]; println!("中順走査 = {:?}", preorder); println!("前順走査 = {:?}", inorder); let root = build_tree(&preorder, &inorder); println!("構築した二分木は:"); print_util::print_tree(root.as_ref().unwrap()); } ================================================ FILE: ja/codes/rust/chapter_divide_and_conquer/hanota.rs ================================================ /* * File: hanota.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ #![allow(non_snake_case)] /* 円盤を 1 枚移動 */ fn move_pan(src: &mut Vec, tar: &mut Vec) { // src の上から円盤を1枚取り出す let pan = src.pop().unwrap(); // 円盤を tar の上に置く tar.push(pan); } /* ハノイの塔の問題 f(i) を解く */ fn dfs(i: i32, src: &mut Vec, buf: &mut Vec, tar: &mut Vec) { // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if i == 1 { move_pan(src, tar); return; } // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i - 1, src, tar, buf); // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move_pan(src, tar); // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i - 1, buf, src, tar); } /* ハノイの塔を解く */ fn solve_hanota(A: &mut Vec, B: &mut Vec, C: &mut Vec) { let n = A.len() as i32; // A の上から n 枚の円盤を B を介して C へ移す dfs(n, A, B, C); } /* Driver Code */ pub fn main() { let mut A = vec![5, 4, 3, 2, 1]; let mut B = Vec::new(); let mut C = Vec::new(); println!("初期状態:"); println!("A = {:?}", A); println!("B = {:?}", B); println!("C = {:?}", C); solve_hanota(&mut A, &mut B, &mut C); println!("円盤の移動完了後:"); println!("A = {:?}", A); println!("B = {:?}", B); println!("C = {:?}", C); } ================================================ FILE: ja/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs ================================================ /* * File: climbing_stairs_backtrack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* バックトラッキング */ fn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) { // 第 n 段に到達したら、方法数を 1 増やす if state == n { res[0] = res[0] + 1; } // すべての選択肢を走査 for &choice in choices { // 枝刈り: 第 n 段を超えないようにする if state + choice > n { continue; } // 試行: 選択を行い、状態を更新 backtrack(choices, state + choice, n, res); // バックトラック } } /* 階段登り:バックトラッキング */ fn climbing_stairs_backtrack(n: usize) -> i32 { let choices = vec![1, 2]; // 1 段または 2 段上ることを選べる let state = 0; // 第 0 段から上り始める let mut res = Vec::new(); res.push(0); // res[0] を使って方法数を記録する backtrack(&choices, state, n as i32, &mut res); res[0] } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_backtrack(n); println!("{n} 段の階段を上る方法は全部で {res} 通り"); } ================================================ FILE: ja/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs ================================================ /* * File: climbing_stairs_constraint_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 制約付き階段登り:動的計画法 */ fn climbing_stairs_constraint_dp(n: usize) -> i32 { if n == 1 || n == 2 { return 1; }; // 部分問題の解を保存するために dp テーブルを初期化 let mut dp = vec![vec![-1; 3]; n + 1]; // 初期状態:最小部分問題の解をあらかじめ設定 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i in 3..=n { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } dp[n][1] + dp[n][2] } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_constraint_dp(n); println!("{n} 段の階段を上る方法は全部で {res} 通り"); } ================================================ FILE: ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs ================================================ /* * File: climbing_stairs_dfs.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 検索 */ fn dfs(i: usize) -> i32 { // dp[1] と dp[2] は既知なので返す if i == 1 || i == 2 { return i as i32; } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i - 1) + dfs(i - 2); count } /* 階段登り:探索 */ fn climbing_stairs_dfs(n: usize) -> i32 { dfs(n) } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dfs(n); println!("{n} 段の階段を上る方法は全部で {res} 通り"); } ================================================ FILE: ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs ================================================ /* * File: climbing_stairs_dfs_mem.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* メモ化探索 */ fn dfs(i: usize, mem: &mut [i32]) -> i32 { // dp[1] と dp[2] は既知なので返す if i == 1 || i == 2 { return i as i32; } // dp[i] の記録があれば、それをそのまま返す if mem[i] != -1 { return mem[i]; } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i - 1, mem) + dfs(i - 2, mem); // dp[i] を記録する mem[i] = count; count } /* 階段登り:メモ化探索 */ fn climbing_stairs_dfs_mem(n: usize) -> i32 { // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す let mut mem = vec![-1; n + 1]; dfs(n, &mut mem) } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dfs_mem(n); println!("{n} 段の階段を上る方法は全部で {res} 通り"); } ================================================ FILE: ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs ================================================ /* * File: climbing_stairs_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 階段登り:動的計画法 */ fn climbing_stairs_dp(n: usize) -> i32 { // dp[1] と dp[2] は既知なので返す if n == 1 || n == 2 { return n as i32; } // 部分問題の解を保存するために dp テーブルを初期化 let mut dp = vec![-1; n + 1]; // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = 1; dp[2] = 2; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i in 3..=n { dp[i] = dp[i - 1] + dp[i - 2]; } dp[n] } /* 階段登り:空間最適化した動的計画法 */ fn climbing_stairs_dp_comp(n: usize) -> i32 { if n == 1 || n == 2 { return n as i32; } let (mut a, mut b) = (1, 2); for _ in 3..=n { let tmp = b; b = a + b; a = tmp; } b } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dp(n); println!("{n} 段の階段を上る方法は全部で {res} 通り"); let res = climbing_stairs_dp_comp(n); println!("{n} 段の階段を上る方法は全部で {res} 通り"); } ================================================ FILE: ja/codes/rust/chapter_dynamic_programming/coin_change.rs ================================================ /* * File: coin_change.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* コイン両替:動的計画法 */ fn coin_change_dp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); let max = amt + 1; // dp テーブルを初期化 let mut dp = vec![vec![0; amt + 1]; n + 1]; // 状態遷移:先頭行と先頭列 for a in 1..=amt { dp[0][a] = max; } // 状態遷移: 残りの行と列 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1); } } } if dp[n][amt] != max { return dp[n][amt] as i32; } else { -1 } } /* コイン交換:空間最適化後の動的計画法 */ fn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); let max = amt + 1; // dp テーブルを初期化 let mut dp = vec![0; amt + 1]; dp.fill(max); dp[0] = 0; // 状態遷移 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1); } } } if dp[amt] != max { return dp[amt] as i32; } else { -1 } } /* Driver Code */ pub fn main() { let coins = [1, 2, 5]; let amt: usize = 4; // 動的計画法 let res = coin_change_dp(&coins, amt); println!("目標金額にするために必要な最小硬貨枚数は {res}"); // 空間最適化後の動的計画法 let res = coin_change_dp_comp(&coins, amt); println!("目標金額にするために必要な最小硬貨枚数は {res}"); } ================================================ FILE: ja/codes/rust/chapter_dynamic_programming/coin_change_ii.rs ================================================ /* * File: coin_change_ii.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* コイン両替 II:動的計画法 */ fn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); // dp テーブルを初期化 let mut dp = vec![vec![0; amt + 1]; n + 1]; // 先頭列を初期化する for i in 0..=n { dp[i][0] = 1; } // 状態遷移 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { // コイン i を選ばない場合と選ぶ場合の和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize]; } } } dp[n][amt] } /* コイン両替 II:空間最適化した動的計画法 */ fn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); // dp テーブルを初期化 let mut dp = vec![0; amt + 1]; dp[0] = 1; // 状態遷移 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // コイン i を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a - coins[i - 1] as usize]; } } } dp[amt] } /* Driver Code */ pub fn main() { let coins = [1, 2, 5]; let amt: usize = 5; // 動的計画法 let res = coin_change_ii_dp(&coins, amt); println!("目標金額を作る硬貨の組み合わせ数は {res}"); // 空間最適化後の動的計画法 let res = coin_change_ii_dp_comp(&coins, amt); println!("目標金額を作る硬貨の組み合わせ数は {res}"); } ================================================ FILE: ja/codes/rust/chapter_dynamic_programming/edit_distance.rs ================================================ /* * File: edit_distance.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 編集距離:総当たり探索 */ fn edit_distance_dfs(s: &str, t: &str, i: usize, j: usize) -> i32 { // s と t がともに空なら 0 を返す if i == 0 && j == 0 { return 0; } // s が空なら t の長さを返す if i == 0 { return j as i32; } // t が空なら s の長さを返す if j == 0 { return i as i32; } // 2 つの文字が等しければ、その 2 文字をそのままスキップする if s.chars().nth(i - 1) == t.chars().nth(j - 1) { return edit_distance_dfs(s, t, i - 1, j - 1); } // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 let insert = edit_distance_dfs(s, t, i, j - 1); let delete = edit_distance_dfs(s, t, i - 1, j); let replace = edit_distance_dfs(s, t, i - 1, j - 1); // 最小編集回数を返す std::cmp::min(std::cmp::min(insert, delete), replace) + 1 } /* 編集距離:メモ化探索 */ fn edit_distance_dfs_mem(s: &str, t: &str, mem: &mut Vec>, i: usize, j: usize) -> i32 { // s と t がともに空なら 0 を返す if i == 0 && j == 0 { return 0; } // s が空なら t の長さを返す if i == 0 { return j as i32; } // t が空なら s の長さを返す if j == 0 { return i as i32; } // 記録済みなら、それをそのまま返す if mem[i][j] != -1 { return mem[i][j]; } // 2 つの文字が等しければ、その 2 文字をそのままスキップする if s.chars().nth(i - 1) == t.chars().nth(j - 1) { return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); } // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 let insert = edit_distance_dfs_mem(s, t, mem, i, j - 1); let delete = edit_distance_dfs_mem(s, t, mem, i - 1, j); let replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); // 最小編集回数を記録して返す mem[i][j] = std::cmp::min(std::cmp::min(insert, delete), replace) + 1; mem[i][j] } /* 編集距離:動的計画法 */ fn edit_distance_dp(s: &str, t: &str) -> i32 { let (n, m) = (s.len(), t.len()); let mut dp = vec![vec![0; m + 1]; n + 1]; // 状態遷移:先頭行と先頭列 for i in 1..=n { dp[i][0] = i as i32; } for j in 1..m { dp[0][j] = j as i32; } // 状態遷移: 残りの行と列 for i in 1..=n { for j in 1..=m { if s.chars().nth(i - 1) == t.chars().nth(j - 1) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i - 1][j - 1]; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } dp[n][m] } /* 編集距離:空間最適化した動的計画法 */ fn edit_distance_dp_comp(s: &str, t: &str) -> i32 { let (n, m) = (s.len(), t.len()); let mut dp = vec![0; m + 1]; // 状態遷移:先頭行 for j in 1..m { dp[j] = j as i32; } // 状態遷移:残りの行 for i in 1..=n { // 状態遷移:先頭列 let mut leftup = dp[0]; // dp[i-1, j-1] を一時保存する dp[0] = i as i32; // 状態遷移:残りの列 for j in 1..=m { let temp = dp[j]; if s.chars().nth(i - 1) == t.chars().nth(j - 1) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftup; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 次の反復の dp[i-1, j-1] に更新する } } dp[m] } /* Driver Code */ pub fn main() { let s = "bag"; let t = "pack"; let (n, m) = (s.len(), t.len()); // 全探索 let res = edit_distance_dfs(s, t, n, m); println!("{s} を {t} に変更するには最小で {res} 回の編集が必要"); // メモ化探索 let mut mem = vec![vec![0; m + 1]; n + 1]; for row in mem.iter_mut() { row.fill(-1); } let res = edit_distance_dfs_mem(s, t, &mut mem, n, m); println!("{s} を {t} に変更するには最小で {res} 回の編集が必要"); // 動的計画法 let res = edit_distance_dp(s, t); println!("{s} を {t} に変更するには最小で {res} 回の編集が必要"); // 空間最適化後の動的計画法 let res = edit_distance_dp_comp(s, t); println!("{s} を {t} に変更するには最小で {res} 回の編集が必要"); } ================================================ FILE: ja/codes/rust/chapter_dynamic_programming/knapsack.rs ================================================ /* * File: knapsack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 0-1 ナップサック:総当たり探索 */ fn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if i == 0 || c == 0 { return 0; } // ナップサック容量を超える場合は、入れない選択しかできない if wgt[i - 1] > c as i32 { return knapsack_dfs(wgt, val, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する let no = knapsack_dfs(wgt, val, i - 1, c); let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; // 2つの案のうち価値が大きいほうを返す std::cmp::max(no, yes) } /* 0-1 ナップサック:メモ化探索 */ fn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec>, i: usize, c: usize) -> i32 { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if i == 0 || c == 0 { return 0; } // 既に記録があればそのまま返す if mem[i][c] != -1 { return mem[i][c]; } // ナップサック容量を超える場合は、入れない選択しかできない if wgt[i - 1] > c as i32 { return knapsack_dfs_mem(wgt, val, mem, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c); let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; // 2 つの案のうち価値が大きい方を記録して返す mem[i][c] = std::cmp::max(no, yes); mem[i][c] } /* 0-1 ナップサック:動的計画法 */ fn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // dp テーブルを初期化 let mut dp = vec![vec![0; cap + 1]; n + 1]; // 状態遷移 for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = std::cmp::max( dp[i - 1][c], dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1], ); } } } dp[n][cap] } /* 0-1 ナップサック:空間最適化後の動的計画法 */ fn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // dp テーブルを初期化 let mut dp = vec![0; cap + 1]; // 状態遷移 for i in 1..=n { // 逆順に走査する for c in (1..=cap).rev() { if wgt[i - 1] <= c as i32 { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); } } } dp[cap] } /* Driver Code */ pub fn main() { let wgt = [10, 20, 30, 40, 50]; let val = [50, 120, 150, 210, 240]; let cap: usize = 50; let n = wgt.len(); // 全探索 let res = knapsack_dfs(&wgt, &val, n, cap); println!("ナップサック容量を超えない最大価値は {res}"); // メモ化探索 let mut mem = vec![vec![0; cap + 1]; n + 1]; for row in mem.iter_mut() { row.fill(-1); } let res = knapsack_dfs_mem(&wgt, &val, &mut mem, n, cap); println!("ナップサック容量を超えない最大価値は {res}"); // 動的計画法 let res = knapsack_dp(&wgt, &val, cap); println!("ナップサック容量を超えない最大価値は {res}"); // 空間最適化後の動的計画法 let res = knapsack_dp_comp(&wgt, &val, cap); println!("ナップサック容量を超えない最大価値は {res}"); } ================================================ FILE: ja/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs ================================================ /* * File: min_cost_climbing_stairs_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use std::cmp; /* 階段登りの最小コスト:動的計画法 */ fn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 { let n = cost.len() - 1; if n == 1 || n == 2 { return cost[n]; } // 部分問題の解を保存するために dp テーブルを初期化 let mut dp = vec![-1; n + 1]; // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = cost[1]; dp[2] = cost[2]; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i in 3..=n { dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i]; } dp[n] } /* 階段昇りの最小コスト:空間最適化後の動的計画法 */ fn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 { let n = cost.len() - 1; if n == 1 || n == 2 { return cost[n]; }; let (mut a, mut b) = (cost[1], cost[2]); for i in 3..=n { let tmp = b; b = cmp::min(a, tmp) + cost[i]; a = tmp; } b } /* Driver Code */ pub fn main() { let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; println!("入力された階段コストのリストは {:?}", &cost); let res = min_cost_climbing_stairs_dp(&cost); println!("階段を上り切る最小コストは {res}"); let res = min_cost_climbing_stairs_dp_comp(&cost); println!("階段を上り切る最小コストは {res}"); } ================================================ FILE: ja/codes/rust/chapter_dynamic_programming/min_path_sum.rs ================================================ /* * File: min_path_sum.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 最小経路和:全探索 */ fn min_path_sum_dfs(grid: &Vec>, i: i32, j: i32) -> i32 { // 左上のセルなら探索を終了する if i == 0 && j == 0 { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if i < 0 || j < 0 { return i32::MAX; } // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する let up = min_path_sum_dfs(grid, i - 1, j); let left = min_path_sum_dfs(grid, i, j - 1); // 左上隅から (i, j) までの最小経路コストを返す std::cmp::min(left, up) + grid[i as usize][j as usize] } /* 最小経路和:メモ化探索 */ fn min_path_sum_dfs_mem(grid: &Vec>, mem: &mut Vec>, i: i32, j: i32) -> i32 { // 左上のセルなら探索を終了する if i == 0 && j == 0 { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if i < 0 || j < 0 { return i32::MAX; } // 既に記録があればそのまま返す if mem[i as usize][j as usize] != -1 { return mem[i as usize][j as usize]; } // 左と上のセルからの最小経路コスト let up = min_path_sum_dfs_mem(grid, mem, i - 1, j); let left = min_path_sum_dfs_mem(grid, mem, i, j - 1); // 左上から (i, j) までの最小経路コストを記録して返す mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize]; mem[i as usize][j as usize] } /* 最小経路和:動的計画法 */ fn min_path_sum_dp(grid: &Vec>) -> i32 { let (n, m) = (grid.len(), grid[0].len()); // dp テーブルを初期化 let mut dp = vec![vec![0; m]; n]; dp[0][0] = grid[0][0]; // 状態遷移:先頭行 for j in 1..m { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 状態遷移:先頭列 for i in 1..n { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 状態遷移: 残りの行と列 for i in 1..n { for j in 1..m { dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } dp[n - 1][m - 1] } /* 最小経路和:空間最適化後の動的計画法 */ fn min_path_sum_dp_comp(grid: &Vec>) -> i32 { let (n, m) = (grid.len(), grid[0].len()); // dp テーブルを初期化 let mut dp = vec![0; m]; // 状態遷移:先頭行 dp[0] = grid[0][0]; for j in 1..m { dp[j] = dp[j - 1] + grid[0][j]; } // 状態遷移:残りの行 for i in 1..n { // 状態遷移:先頭列 dp[0] = dp[0] + grid[i][0]; // 状態遷移:残りの列 for j in 1..m { dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j]; } } dp[m - 1] } /* Driver Code */ pub fn main() { let grid = vec![ vec![1, 3, 1, 5], vec![2, 2, 4, 2], vec![5, 3, 2, 1], vec![4, 3, 5, 2], ]; let (n, m) = (grid.len(), grid[0].len()); // 全探索 let res = min_path_sum_dfs(&grid, n as i32 - 1, m as i32 - 1); println!("左上から右下までの最小経路和は {res}"); // メモ化探索 let mut mem = vec![vec![0; m]; n]; for row in mem.iter_mut() { row.fill(-1); } let res = min_path_sum_dfs_mem(&grid, &mut mem, n as i32 - 1, m as i32 - 1); println!("左上から右下までの最小経路和は {res}"); // 動的計画法 let res = min_path_sum_dp(&grid); println!("左上から右下までの最小経路和は {res}"); // 空間最適化後の動的計画法 let res = min_path_sum_dp_comp(&grid); println!("左上から右下までの最小経路和は {res}"); } ================================================ FILE: ja/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs ================================================ /* * File: unbounded_knapsack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 完全ナップサック問題:動的計画法 */ fn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // dp テーブルを初期化 let mut dp = vec![vec![0; cap + 1]; n + 1]; // 状態遷移 for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]); } } } return dp[n][cap]; } /* 完全ナップサック問題:空間最適化後の動的計画法 */ fn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // dp テーブルを初期化 let mut dp = vec![0; cap + 1]; // 状態遷移 for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); } } } dp[cap] } /* Driver Code */ pub fn main() { let wgt = [1, 2, 3]; let val = [5, 11, 15]; let cap: usize = 4; // 動的計画法 let res = unbounded_knapsack_dp(&wgt, &val, cap); println!("ナップサック容量を超えない最大価値は {res}"); // 空間最適化後の動的計画法 let res = unbounded_knapsack_dp_comp(&wgt, &val, cap); println!("ナップサック容量を超えない最大価値は {res}"); } ================================================ FILE: ja/codes/rust/chapter_graph/graph_adjacency_list.rs ================================================ /* * File: graph_adjacency_list.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ pub use hello_algo_rust::include::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::HashMap; /* 隣接リストに基づく無向グラフ型 */ pub struct GraphAdjList { // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 pub adj_list: HashMap>, // maybe HashSet for value part is better? } impl GraphAdjList { /* コンストラクタ */ pub fn new(edges: Vec<[Vertex; 2]>) -> Self { let mut graph = GraphAdjList { adj_list: HashMap::new(), }; // すべての頂点と辺を追加 for edge in edges { graph.add_vertex(edge[0]); graph.add_vertex(edge[1]); graph.add_edge(edge[0], edge[1]); } graph } /* 頂点数を取得 */ #[allow(unused)] pub fn size(&self) -> usize { self.adj_list.len() } /* 辺を追加 */ pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) { if vet1 == vet2 { panic!("value error"); } // 辺 vet1 - vet2 を追加 self.adj_list.entry(vet1).or_default().push(vet2); self.adj_list.entry(vet2).or_default().push(vet1); } /* 辺を削除 */ #[allow(unused)] pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) { if vet1 == vet2 { panic!("value error"); } // 辺 vet1 - vet2 を削除 self.adj_list .entry(vet1) .and_modify(|v| v.retain(|&e| e != vet2)); self.adj_list .entry(vet2) .and_modify(|v| v.retain(|&e| e != vet1)); } /* 頂点を追加 */ pub fn add_vertex(&mut self, vet: Vertex) { if self.adj_list.contains_key(&vet) { return; } // 隣接リストに新しいリストを追加 self.adj_list.insert(vet, vec![]); } /* 頂点を削除 */ #[allow(unused)] pub fn remove_vertex(&mut self, vet: Vertex) { // 隣接リストから頂点 vet に対応するリストを削除 self.adj_list.remove(&vet); // 他の頂点のリストを走査し、vet を含むすべての辺を削除 for list in self.adj_list.values_mut() { list.retain(|&v| v != vet); } } /* 隣接リストを出力 */ pub fn print(&self) { println!("隣接リスト ="); for (vertex, list) in &self.adj_list { let list = list.iter().map(|vertex| vertex.val).collect::>(); println!("{}: {:?},", vertex.val, list); } } } /* Driver Code */ #[allow(unused)] fn main() { /* 無向グラフを初期化 */ let v = vals_to_vets(vec![1, 3, 2, 5, 4]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ]; let mut graph = GraphAdjList::new(edges); println!("\n初期化後、グラフは"); graph.print(); /* 辺を追加 */ // 頂点 1, 2 は v[0], v[2] graph.add_edge(v[0], v[2]); println!("\n辺 1-2 を追加した後、グラフは"); graph.print(); /* 辺を削除 */ // 頂点 1, 3 は v[0], v[1] graph.remove_edge(v[0], v[1]); println!("\n辺 1-3 を削除した後、グラフは"); graph.print(); /* 頂点を追加 */ let v5 = Vertex { val: 6 }; graph.add_vertex(v5); println!("\n頂点 6 を追加した後、グラフは"); graph.print(); /* 頂点を削除 */ // 頂点 3 は v[1] graph.remove_vertex(v[1]); println!("\n頂点 3 を削除した後、グラフは"); graph.print(); } ================================================ FILE: ja/codes/rust/chapter_graph/graph_adjacency_matrix.rs ================================================ /* * File: graph_adjacency_matrix.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ /* 隣接行列に基づく無向グラフ型 */ pub struct GraphAdjMat { // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す pub vertices: Vec, // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 pub adj_mat: Vec>, } impl GraphAdjMat { /* コンストラクタ */ pub fn new(vertices: Vec, edges: Vec<[usize; 2]>) -> Self { let mut graph = GraphAdjMat { vertices: vec![], adj_mat: vec![], }; // 頂点を追加 for val in vertices { graph.add_vertex(val); } // 辺を追加 // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する for edge in edges { graph.add_edge(edge[0], edge[1]) } graph } /* 頂点数を取得 */ pub fn size(&self) -> usize { self.vertices.len() } /* 頂点を追加 */ pub fn add_vertex(&mut self, val: i32) { let n = self.size(); // 頂点リストに新しい頂点の値を追加 self.vertices.push(val); // 隣接行列に 1 行追加 self.adj_mat.push(vec![0; n]); // 隣接行列に 1 列追加 for row in self.adj_mat.iter_mut() { row.push(0); } } /* 頂点を削除 */ pub fn remove_vertex(&mut self, index: usize) { if index >= self.size() { panic!("index error") } // 頂点リストから index の頂点を削除する self.vertices.remove(index); // 隣接行列で index 行を削除する self.adj_mat.remove(index); // 隣接行列で index 列を削除する for row in self.adj_mat.iter_mut() { row.remove(index); } } /* 辺を追加 */ pub fn add_edge(&mut self, i: usize, j: usize) { // パラメータ i, j は vertices の要素インデックスに対応する // 範囲外と同値の場合の処理 if i >= self.size() || j >= self.size() || i == j { panic!("index error") } // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす self.adj_mat[i][j] = 1; self.adj_mat[j][i] = 1; } /* 辺を削除 */ // 引数 i, j は vertices の要素インデックスに対応する pub fn remove_edge(&mut self, i: usize, j: usize) { // パラメータ i, j は vertices の要素インデックスに対応する // 範囲外と同値の場合の処理 if i >= self.size() || j >= self.size() || i == j { panic!("index error") } self.adj_mat[i][j] = 0; self.adj_mat[j][i] = 0; } /* 隣接行列を出力 */ pub fn print(&self) { println!("頂点リスト = {:?}", self.vertices); println!("隣接行列 ="); println!("["); for row in &self.adj_mat { println!(" {:?},", row); } println!("]") } } /* Driver Code */ fn main() { /* 無向グラフを初期化 */ // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 let vertices = vec![1, 3, 2, 5, 4]; let edges = vec![[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]]; let mut graph = GraphAdjMat::new(vertices, edges); println!("\n初期化後、グラフは"); graph.print(); /* 辺を追加 */ // 頂点 1, 2 のインデックスはそれぞれ 0, 2 graph.add_edge(0, 2); println!("\n辺 1-2 を追加した後、グラフは"); graph.print(); /* 辺を削除 */ // 頂点 1, 3 のインデックスはそれぞれ 0, 1 graph.remove_edge(0, 1); println!("\n辺 1-3 を削除した後、グラフは"); graph.print(); /* 頂点を追加 */ graph.add_vertex(6); println!("\n頂点 6 を追加した後、グラフは"); graph.print(); /* 頂点を削除 */ // 頂点 3 のインデックスは 1 graph.remove_vertex(1); println!("\n頂点 3 を削除した後、グラフは"); graph.print(); } ================================================ FILE: ja/codes/rust/chapter_graph/graph_bfs.rs ================================================ /* * File: graph_bfs.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ mod graph_adjacency_list; use graph_adjacency_list::GraphAdjList; use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::{HashSet, VecDeque}; /* 幅優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { // 頂点の走査順序 let mut res = vec![]; // 訪問済み頂点を記録するためのハッシュ集合 let mut visited = HashSet::new(); visited.insert(start_vet); // BFS の実装にキューを用いる let mut que = VecDeque::new(); que.push_back(start_vet); // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す while let Some(vet) = que.pop_front() { res.push(vet); // 訪問した頂点を記録 // この頂点のすべての隣接頂点を走査 if let Some(adj_vets) = graph.adj_list.get(&vet) { for &adj_vet in adj_vets { if visited.contains(&adj_vet) { continue; // 訪問済みの頂点をスキップ } que.push_back(adj_vet); // 未訪問の頂点のみをキューに追加 visited.insert(adj_vet); // この頂点を訪問済みにする } } } // 頂点の走査順を返す res } /* Driver Code */ fn main() { /* 無向グラフを初期化 */ let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; let graph = GraphAdjList::new(edges); println!("\n初期化後、グラフは"); graph.print(); /* 幅優先探索 */ let res = graph_bfs(graph, v[0]); println!("\n幅優先探索(BFS)の頂点順序は"); println!("{:?}", vets_to_vals(res)); } ================================================ FILE: ja/codes/rust/chapter_graph/graph_dfs.rs ================================================ /* * File: graph_dfs.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ mod graph_adjacency_list; use graph_adjacency_list::GraphAdjList; use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::HashSet; /* 深さ優先走査の補助関数 */ fn dfs(graph: &GraphAdjList, visited: &mut HashSet, res: &mut Vec, vet: Vertex) { res.push(vet); // 訪問した頂点を記録 visited.insert(vet); // この頂点を訪問済みにする // この頂点のすべての隣接頂点を走査 if let Some(adj_vets) = graph.adj_list.get(&vet) { for &adj_vet in adj_vets { if visited.contains(&adj_vet) { continue; // 訪問済みの頂点をスキップ } // 隣接頂点を再帰的に訪問 dfs(graph, visited, res, adj_vet); } } } /* 深さ優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { // 頂点の走査順序 let mut res = vec![]; // 訪問済み頂点を記録するためのハッシュ集合 let mut visited = HashSet::new(); dfs(&graph, &mut visited, &mut res, start_vet); res } /* Driver Code */ fn main() { /* 無向グラフを初期化 */ let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; let graph = GraphAdjList::new(edges); println!("\n初期化後、グラフは"); graph.print(); /* 深さ優先探索 */ let res = graph_dfs(graph, v[0]); println!("\n深さ優先探索(DFS)の頂点順序は"); println!("{:?}", vets_to_vals(res)); } ================================================ FILE: ja/codes/rust/chapter_greedy/coin_change_greedy.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* コイン交換:貪欲法 */ fn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 { // coins リストはソート済みと仮定する let mut i = coins.len() - 1; let mut count = 0; // 残額がなくなるまで貪欲選択を繰り返す while amt > 0 { // 残額以下で最も近い硬貨を見つける while i > 0 && coins[i] > amt { i -= 1; } // coins[i] を選択する amt -= coins[i]; count += 1; } // 実行可能な解が見つからなければ -1 を返す if amt == 0 { count } else { -1 } } /* Driver Code */ fn main() { // 貪欲法:大域最適解を保証できる let coins = [1, 5, 10, 20, 50, 100]; let amt = 186; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("{} にするために必要な最小硬貨枚数は {}", amt, res); // 貪欲法:大域最適解を保証できない let coins = [1, 20, 50]; let amt = 60; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("{} にするために必要な最小硬貨枚数は {}", amt, res); println!("実際に必要な最小枚数は 3、つまり 20 + 20 + 20"); // 貪欲法:大域最適解を保証できない let coins = [1, 49, 50]; let amt = 98; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("{} にするために必要な最小硬貨枚数は {}", amt, res); println!("実際に必要な最小枚数は 2、つまり 49 + 49"); } ================================================ FILE: ja/codes/rust/chapter_greedy/fractional_knapsack.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* 品物 */ struct Item { w: i32, // 品物の重さ v: i32, // 品物の価値 } impl Item { fn new(w: i32, v: i32) -> Self { Self { w, v } } } /* 分数ナップサック:貪欲法 */ fn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 { // 重さと価値の 2 属性を持つ品物リストを作成 let mut items = wgt .iter() .zip(val.iter()) .map(|(&w, &v)| Item::new(w, v)) .collect::>(); // 単位価値 item.v / item.w の高い順にソートする items.sort_by(|a, b| { (b.v as f64 / b.w as f64) .partial_cmp(&(a.v as f64 / a.w as f64)) .unwrap() }); // 貪欲選択を繰り返す let mut res = 0.0; for item in &items { if item.w <= cap { // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる res += item.v as f64; cap -= item.w; } else { // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる res += item.v as f64 / item.w as f64 * cap as f64; // 残り容量がないため、ループを抜ける break; } } res } /* Driver Code */ fn main() { let wgt = [10, 20, 30, 40, 50]; let val = [50, 120, 150, 210, 240]; let cap = 50; // 貪欲法 let res = fractional_knapsack(&wgt, &val, cap); println!("ナップサック容量を超えない最大価値は {}", res); } ================================================ FILE: ja/codes/rust/chapter_greedy/max_capacity.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* 最大容量:貪欲法 */ fn max_capacity(ht: &[i32]) -> i32 { // i, j を初期化し、それぞれ配列の両端に置く let mut i = 0; let mut j = ht.len() - 1; // 初期の最大容量は 0 let mut res = 0; // 2 枚の板が出会うまで貪欲選択を繰り返す while i < j { // 最大容量を更新する let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32; res = std::cmp::max(res, cap); // 短い方を内側へ動かす if ht[i] < ht[j] { i += 1; } else { j -= 1; } } res } /* Driver Code */ fn main() { let ht = [3, 8, 5, 2, 7, 7, 3, 4]; // 貪欲法 let res = max_capacity(&ht); println!("最大容量は {}", res); } ================================================ FILE: ja/codes/rust/chapter_greedy/max_product_cutting.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* 最大切断積:貪欲法 */ fn max_product_cutting(n: i32) -> i32 { // n <= 3 のときは、必ず 1 を切り出す if n <= 3 { return 1 * (n - 1); } // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする let a = n / 3; let b = n % 3; if b == 1 { // 余りが 1 のときは、1 * 3 を 2 * 2 に変える 3_i32.pow(a as u32 - 1) * 2 * 2 } else if b == 2 { // 余りが 2 のときは、そのままにする 3_i32.pow(a as u32) * 2 } else { // 余りが 0 のときは、そのままにする 3_i32.pow(a as u32) } } /* Driver Code */ fn main() { let n = 58; // 貪欲法 let res = max_product_cutting(n); println!("最大分割積は {}", res); } ================================================ FILE: ja/codes/rust/chapter_hashing/array_hash_map.rs ================================================ /** * File: array_hash_map.rs * Created Time: 2023-2-18 * Author: xBLACICEx (xBLACKICEx@outlook.com) */ /* キーと値の組 */ #[derive(Debug, Clone, PartialEq)] pub struct Pair { pub key: i32, pub val: String, } /* 配列ベースのハッシュテーブル */ pub struct ArrayHashMap { buckets: Vec>, } impl ArrayHashMap { pub fn new() -> ArrayHashMap { // 100 個のバケットを含む配列を初期化 Self { buckets: vec![None; 100], } } /* ハッシュ関数 */ fn hash_func(&self, key: i32) -> usize { key as usize % 100 } /* 検索操作 */ pub fn get(&self, key: i32) -> Option<&String> { let index = self.hash_func(key); self.buckets[index].as_ref().map(|pair| &pair.val) } /* 追加操作 */ pub fn put(&mut self, key: i32, val: &str) { let index = self.hash_func(key); self.buckets[index] = Some(Pair { key, val: val.to_string(), }); } /* 削除操作 */ pub fn remove(&mut self, key: i32) { let index = self.hash_func(key); // None に設定し、削除を表す self.buckets[index] = None; } /* すべてのキーと値のペアを取得 */ pub fn entry_set(&self) -> Vec<&Pair> { self.buckets .iter() .filter_map(|pair| pair.as_ref()) .collect() } /* すべてのキーを取得 */ pub fn key_set(&self) -> Vec<&i32> { self.buckets .iter() .filter_map(|pair| pair.as_ref().map(|pair| &pair.key)) .collect() } /* すべての値を取得 */ pub fn value_set(&self) -> Vec<&String> { self.buckets .iter() .filter_map(|pair| pair.as_ref().map(|pair| &pair.val)) .collect() } /* ハッシュテーブルを出力 */ pub fn print(&self) { for pair in self.entry_set() { println!("{} -> {}", pair.key, pair.val); } } } fn main() { /* ハッシュテーブルを初期化 */ let mut map = ArrayHashMap::new(); /* 追加操作 */ // ハッシュテーブルにキーと値の組(key, value)を追加 map.put(12836, "シャオハー"); map.put(15937, "シャオルオ"); map.put(16750, "シャオスワン"); map.put(13276, "シャオファー"); map.put(10583, "シャオヤー"); println!("\n追加後、ハッシュテーブルは\nKey -> Value"); map.print(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 let name = map.get(15937).unwrap(); println!("\n学籍番号 15937 を入力すると、名前 {} が見つかりました", name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(10583); println!("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value"); map.print(); /* ハッシュテーブルを走査 */ println!("\nキーと値のペア Key->Value を走査"); for pair in map.entry_set() { println!("{} -> {}", pair.key, pair.val); } println!("\nキー Key のみを走査"); for key in map.key_set() { println!("{}", key); } println!("\n値 Value のみを走査"); for val in map.value_set() { println!("{}", val); } } ================================================ FILE: ja/codes/rust/chapter_hashing/build_in_hash.rs ================================================ /* * File: build_in_hash.rs * Created Time: 2023-7-6 * Author: WSL0809 (wslzzy@outlook.com) */ use hello_algo_rust::include::ListNode; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; /* Driver Code */ fn main() { let num = 3; let mut num_hasher = DefaultHasher::new(); num.hash(&mut num_hasher); let hash_num = num_hasher.finish(); println!("整数 {} のハッシュ値は {}", num, hash_num); let bol = true; let mut bol_hasher = DefaultHasher::new(); bol.hash(&mut bol_hasher); let hash_bol = bol_hasher.finish(); println!("真偽値 {} のハッシュ値は {}", bol, hash_bol); let dec: f32 = 3.14159; let mut dec_hasher = DefaultHasher::new(); dec.to_bits().hash(&mut dec_hasher); let hash_dec = dec_hasher.finish(); println!("小数 {} のハッシュ値は {}", dec, hash_dec); let str = "Hello アルゴリズム"; let mut str_hasher = DefaultHasher::new(); str.hash(&mut str_hasher); let hash_str = str_hasher.finish(); println!("文字列 {} のハッシュ値は {}", str, hash_str); let arr = (&12836, &"シャオハー"); let mut tup_hasher = DefaultHasher::new(); arr.hash(&mut tup_hasher); let hash_tup = tup_hasher.finish(); println!("タプル {:?} のハッシュ値は {}", arr, hash_tup); let node = ListNode::new(42); let mut hasher = DefaultHasher::new(); node.borrow().val.hash(&mut hasher); let hash = hasher.finish(); println!("ノードオブジェクト {:?} のハッシュ値は{}", node, hash); } ================================================ FILE: ja/codes/rust/chapter_hashing/hash_map.rs ================================================ /* * File: hash_map.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::collections::HashMap; /* Driver Code */ pub fn main() { // ハッシュテーブルを初期化 let mut map = HashMap::new(); // 追加操作 // ハッシュテーブルにキーと値の組 (key, value) を追加する map.insert(12836, "シャオハー"); map.insert(15937, "シャオロー"); map.insert(16750, "シャオスワン"); map.insert(13276, "シャオファー"); map.insert(10583, "シャオヤー"); println!("\n追加後、ハッシュテーブルは\nKey -> Value"); print_util::print_hash_map(&map); // 検索操作 // ハッシュテーブルにキー key を入力し、値 value を取得する let name = map.get(&15937).copied().unwrap(); println!("\n学籍番号 15937 を入力すると、名前 {name} が見つかります"); // 削除操作 // ハッシュテーブルからキーと値の組 (key, value) を削除する _ = map.remove(&10583); println!("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value"); print_util::print_hash_map(&map); // ハッシュテーブルを走査 println!("\nキーと値のペア Key->Value を走査"); print_util::print_hash_map(&map); println!("\nキー Key のみを走査"); for key in map.keys() { println!("{key}"); } println!("\n値 value のみを走査"); for value in map.values() { println!("{value}"); } } ================================================ FILE: ja/codes/rust/chapter_hashing/hash_map_chaining.rs ================================================ /* * File: hash_map_chaining.rs * Created Time: 2023-07-07 * Author: WSL0809 (wslzzy@outlook.com) */ #[derive(Clone)] /* キーと値の組 */ struct Pair { key: i32, val: String, } /* チェイン法ハッシュテーブル */ struct HashMapChaining { size: usize, capacity: usize, load_thres: f32, extend_ratio: usize, buckets: Vec>, } impl HashMapChaining { /* コンストラクタ */ fn new() -> Self { Self { size: 0, capacity: 4, load_thres: 2.0 / 3.0, extend_ratio: 2, buckets: vec![vec![]; 4], } } /* ハッシュ関数 */ fn hash_func(&self, key: i32) -> usize { key as usize % self.capacity } /* 負荷率 */ fn load_factor(&self) -> f32 { self.size as f32 / self.capacity as f32 } /* 削除操作 */ fn remove(&mut self, key: i32) -> Option { let index = self.hash_func(key); // バケットを走査してキーと値のペアを削除 for (i, p) in self.buckets[index].iter_mut().enumerate() { if p.key == key { let pair = self.buckets[index].remove(i); self.size -= 1; return Some(pair.val); } } // key が見つからない場合は None を返す None } /* ハッシュテーブルを拡張 */ fn extend(&mut self) { // 元のハッシュテーブルを一時保存 let buckets_tmp = std::mem::take(&mut self.buckets); // リサイズ後の新しいハッシュテーブルを初期化 self.capacity *= self.extend_ratio; self.buckets = vec![Vec::new(); self.capacity as usize]; self.size = 0; // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for bucket in buckets_tmp { for pair in bucket { self.put(pair.key, pair.val); } } } /* ハッシュテーブルを出力 */ fn print(&self) { for bucket in &self.buckets { let mut res = Vec::new(); for pair in bucket { res.push(format!("{} -> {}", pair.key, pair.val)); } println!("{:?}", res); } } /* 追加操作 */ fn put(&mut self, key: i32, val: String) { // 負荷率がしきい値を超えたら、リサイズを実行 if self.load_factor() > self.load_thres { self.extend(); } let index = self.hash_func(key); // バケットを走査し、指定した key が見つかれば対応する val を更新して返す for pair in self.buckets[index].iter_mut() { if pair.key == key { pair.val = val; return; } } // その key が存在しなければ、キーと値のペアを末尾に追加 let pair = Pair { key, val }; self.buckets[index].push(pair); self.size += 1; } /* 検索操作 */ fn get(&self, key: i32) -> Option<&str> { let index = self.hash_func(key); // バケットを走査し、key が見つかれば対応する val を返す for pair in self.buckets[index].iter() { if pair.key == key { return Some(&pair.val); } } // key が見つからない場合は None を返す None } } /* Driver Code */ pub fn main() { /* ハッシュテーブルを初期化 */ let mut map = HashMapChaining::new(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.put(12836, "シャオハー".to_string()); map.put(15937, "シャオロー".to_string()); map.put(16750, "シャオスワン".to_string()); map.put(13276, "シャオファー".to_string()); map.put(10583, "シャオヤー".to_string()); println!("\n追加後、ハッシュテーブルは\nKey -> Value"); map.print(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 println!( "\n学籍番号 13276 を入力すると、名前 {} が見つかります", match map.get(13276) { Some(value) => value, None => "Not a valid Key", } ); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(12836); println!("\n12836 を削除すると、ハッシュテーブルは\nKey -> Value"); map.print(); } ================================================ FILE: ja/codes/rust/chapter_hashing/hash_map_open_addressing.rs ================================================ /* * File: hash_map_open_addressing.rs * Created Time: 2023-07-16 * Author: WSL0809 (wslzzy@outlook.com), night-cruise (2586447362@qq.com) */ #![allow(non_snake_case)] #![allow(unused)] mod array_hash_map; use array_hash_map::Pair; /* オープンアドレス法ハッシュテーブル */ struct HashMapOpenAddressing { size: usize, // キーと値のペア数 capacity: usize, // ハッシュテーブル容量 load_thres: f64, // リサイズを発動する負荷率のしきい値 extend_ratio: usize, // 拡張倍率 buckets: Vec>, // バケット配列 TOMBSTONE: Option, // 削除済みマーク } impl HashMapOpenAddressing { /* コンストラクタ */ fn new() -> Self { Self { size: 0, capacity: 4, load_thres: 2.0 / 3.0, extend_ratio: 2, buckets: vec![None; 4], TOMBSTONE: Some(Pair { key: -1, val: "-1".to_string(), }), } } /* ハッシュ関数 */ fn hash_func(&self, key: i32) -> usize { (key % self.capacity as i32) as usize } /* 負荷率 */ fn load_factor(&self) -> f64 { self.size as f64 / self.capacity as f64 } /* key に対応するバケットインデックスを探す */ fn find_bucket(&mut self, key: i32) -> usize { let mut index = self.hash_func(key); let mut first_tombstone = -1; // 線形プロービングを行い、空バケットに達したら終了 while self.buckets[index].is_some() { // `key` に遭遇したら、対応するバケットのインデックスを返す if self.buckets[index].as_ref().unwrap().key == key { // 以前に削除マークに遭遇していた場合は、キーと値のペアをそのインデックスへ移動する if first_tombstone != -1 { self.buckets[first_tombstone as usize] = self.buckets[index].take(); self.buckets[index] = self.TOMBSTONE.clone(); return first_tombstone as usize; // 移動後のバケットインデックスを返す } return index; // バケットのインデックスを返す } // 最初に見つかった削除マークを記録 if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE { first_tombstone = index as i32; } // バケットのインデックスを計算し、末尾を越えたら先頭に戻る index = (index + 1) % self.capacity; } // key が存在しない場合は追加位置のインデックスを返す if first_tombstone == -1 { index } else { first_tombstone as usize } } /* 検索操作 */ fn get(&mut self, key: i32) -> Option<&str> { // key に対応するバケットインデックスを探す let index = self.find_bucket(key); // キーと値の組が見つかったら、対応する val を返す if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { return self.buckets[index].as_ref().map(|pair| &pair.val as &str); } // キーと値の組が存在しなければ null を返す None } /* 追加操作 */ fn put(&mut self, key: i32, val: String) { // 負荷率がしきい値を超えたら、リサイズを実行 if self.load_factor() > self.load_thres { self.extend(); } // key に対応するバケットインデックスを探す let index = self.find_bucket(key); // キーと値の組が見つかったら、val を上書きして返す if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { self.buckets[index].as_mut().unwrap().val = val; return; } // キーと値の組が存在しない場合は、その組を追加する self.buckets[index] = Some(Pair { key, val }); self.size += 1; } /* 削除操作 */ fn remove(&mut self, key: i32) { // key に対応するバケットインデックスを探す let index = self.find_bucket(key); // キーと値の組が見つかったら、削除マーカーで上書きする if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { self.buckets[index] = self.TOMBSTONE.clone(); self.size -= 1; } } /* ハッシュテーブルを拡張 */ fn extend(&mut self) { // 元のハッシュテーブルを一時保存 let buckets_tmp = self.buckets.clone(); // リサイズ後の新しいハッシュテーブルを初期化 self.capacity *= self.extend_ratio; self.buckets = vec![None; self.capacity]; self.size = 0; // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for pair in buckets_tmp { if pair.is_none() || pair == self.TOMBSTONE { continue; } let pair = pair.unwrap(); self.put(pair.key, pair.val); } } /* ハッシュテーブルを出力 */ fn print(&self) { for pair in &self.buckets { if pair.is_none() { println!("null"); } else if pair == &self.TOMBSTONE { println!("TOMBSTONE"); } else { let pair = pair.as_ref().unwrap(); println!("{} -> {}", pair.key, pair.val); } } } } /* Driver Code */ fn main() { /* ハッシュテーブルを初期化 */ let mut hashmap = HashMapOpenAddressing::new(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 hashmap.put(12836, "シャオハー".to_string()); hashmap.put(15937, "シャオロー".to_string()); hashmap.put(16750, "シャオスワン".to_string()); hashmap.put(13276, "シャオファー".to_string()); hashmap.put(10583, "シャオヤー".to_string()); println!("\n追加後、ハッシュテーブルは\nKey -> Value"); hashmap.print(); /* 検索操作 */ // ハッシュテーブルにキー key を入力し、値 val を得る let name = hashmap.get(13276).unwrap(); println!("\n学籍番号 13276 を入力すると、名前 {} が見つかります", name); /* 削除操作 */ // ハッシュテーブルからキーと値の組 (key, val) を削除 hashmap.remove(16750); println!("\n16750 を削除すると、ハッシュテーブルは\nKey -> Value"); hashmap.print(); } ================================================ FILE: ja/codes/rust/chapter_hashing/simple_hash.rs ================================================ /* * File: simple_hash.rs * Created Time: 2023-09-07 * Author: night-cruise (2586447362@qq.com) */ /* 加算ハッシュ */ fn add_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = (hash + c as i64) % MODULUS; } hash as i32 } /* 乗算ハッシュ */ fn mul_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = (31 * hash + c as i64) % MODULUS; } hash as i32 } /* XOR ハッシュ */ fn xor_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash ^= c as i64; } (hash & MODULUS) as i32 } /* 回転ハッシュ */ fn rot_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS; } hash as i32 } /* Driver Code */ fn main() { let key = "Hello アルゴリズム"; let hash = add_hash(key); println!("加算ハッシュ値は {hash}"); let hash = mul_hash(key); println!("乗算ハッシュ値は {hash}"); let hash = xor_hash(key); println!("XORハッシュ値は {hash}"); let hash = rot_hash(key); println!("回転ハッシュ値は {hash}"); } ================================================ FILE: ja/codes/rust/chapter_heap/heap.rs ================================================ /* * File: heap.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::{cmp::Reverse, collections::BinaryHeap}; fn test_push_max(heap: &mut BinaryHeap, val: i32) { heap.push(val); // 要素をヒープに追加 println!("\n要素 {} をヒープに追加した後", val); print_util::print_heap(heap.iter().map(|&val| val).collect()); } fn test_pop_max(heap: &mut BinaryHeap) { let val = heap.pop().unwrap(); println!("\nヒープ先頭要素 {} を取り出した後", val); print_util::print_heap(heap.iter().map(|&val| val).collect()); } /* Driver Code */ fn main() { /* ヒープを初期化 */ // 最小ヒープを初期化 #[allow(unused_assignments)] let mut min_heap = BinaryHeap::new(); // Rust の BinaryHeap は最大ヒープであり、最小ヒープには通常 Reverse を使う // 最大ヒープを初期化する let mut max_heap = BinaryHeap::new(); println!("\n以下のテストケースは最大ヒープです"); /* 要素をヒープに追加 */ test_push_max(&mut max_heap, 1); test_push_max(&mut max_heap, 3); test_push_max(&mut max_heap, 2); test_push_max(&mut max_heap, 5); test_push_max(&mut max_heap, 4); /* ヒープ頂点の要素を取得 */ let peek = max_heap.peek().unwrap(); println!("\nヒープ先頭要素は {}", peek); /* ヒープ頂点の要素を取り出す */ test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); /* ヒープのサイズを取得 */ let size = max_heap.len(); println!("\nヒープ要素数は {}", size); /* ヒープが空かどうかを判定 */ let is_empty = max_heap.is_empty(); println!("\nヒープは空か {}", is_empty); /* リストを入力してヒープを構築 */ // 時間計算量は O(n) であり、O(nlogn) ではない min_heap = BinaryHeap::from( vec![1, 3, 2, 5, 4] .into_iter() .map(|val| Reverse(val)) .collect::>>(), ); println!("\nリストを入力して最小ヒープを構築した後"); print_util::print_heap(min_heap.iter().map(|&val| val.0).collect()); } ================================================ FILE: ja/codes/rust/chapter_heap/my_heap.rs ================================================ /* * File: my_heap.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* 最大ヒープ */ struct MaxHeap { // 配列ではなく vector を使うことで、拡張を考慮せずに済む max_heap: Vec, } impl MaxHeap { /* コンストラクタ。入力リストに基づいてヒープを構築する */ fn new(nums: Vec) -> Self { // リスト要素をそのままヒープに追加 let mut heap = MaxHeap { max_heap: nums }; // 葉ノード以外のすべてのノードをヒープ化 for i in (0..=Self::parent(heap.size() - 1)).rev() { heap.sift_down(i); } heap } /* 左子ノードのインデックスを取得 */ fn left(i: usize) -> usize { 2 * i + 1 } /* 右子ノードのインデックスを取得 */ fn right(i: usize) -> usize { 2 * i + 2 } /* 親ノードのインデックスを取得 */ fn parent(i: usize) -> usize { (i - 1) / 2 // 切り捨て除算 } /* 要素を交換 */ fn swap(&mut self, i: usize, j: usize) { self.max_heap.swap(i, j); } /* ヒープのサイズを取得 */ fn size(&self) -> usize { self.max_heap.len() } /* ヒープが空かどうかを判定 */ fn is_empty(&self) -> bool { self.max_heap.is_empty() } /* ヒープ先頭要素にアクセス */ fn peek(&self) -> Option { self.max_heap.first().copied() } /* 要素をヒープに追加 */ fn push(&mut self, val: i32) { // ノードを追加 self.max_heap.push(val); // 下から上へヒープ化 self.sift_up(self.size() - 1); } /* ノード i から始めて、下から上へヒープ化 */ fn sift_up(&mut self, mut i: usize) { loop { // ノード i はすでにヒープの先頭ノードなので、ヒープ化を終了する if i == 0 { break; } // ノード i の親ノードを取得 let p = Self::parent(i); // 「ノードの修復が不要」になったら、ヒープ化を終了 if self.max_heap[i] <= self.max_heap[p] { break; } // 2 つのノードを交換 self.swap(i, p); // ループで下から上へヒープ化 i = p; } } /* 要素をヒープから取り出す */ fn pop(&mut self) -> i32 { // 空判定の処理 if self.is_empty() { panic!("index out of bounds"); } // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) self.swap(0, self.size() - 1); // ノードを削除 let val = self.max_heap.pop().unwrap(); // 上から下へヒープ化 self.sift_down(0); // ヒープ先頭要素を返す val } /* ノード i から始めて、上から下へヒープ化 */ fn sift_down(&mut self, mut i: usize) { loop { // ノード i, l, r のうち値が最大のノードを ma とする let (l, r, mut ma) = (Self::left(i), Self::right(i), i); if l < self.size() && self.max_heap[l] > self.max_heap[ma] { ma = l; } if r < self.size() && self.max_heap[r] > self.max_heap[ma] { ma = r; } // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if ma == i { break; } // 2 つのノードを交換 self.swap(i, ma); // ループで上から下へヒープ化 i = ma; } } /* ヒープ(二分木)を出力 */ fn print(&self) { print_util::print_heap(self.max_heap.clone()); } } /* Driver Code */ fn main() { /* 最大ヒープを初期化 */ let mut max_heap = MaxHeap::new(vec![9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); println!("\nリストを入力してヒープを構築した後"); max_heap.print(); /* ヒープ頂点の要素を取得 */ let peek = max_heap.peek(); if let Some(peek) = peek { println!("\nヒープ先頭要素は {}", peek); } /* 要素をヒープに追加 */ let val = 7; max_heap.push(val); println!("\n要素 {} をヒープに追加した後", val); max_heap.print(); /* ヒープ頂点の要素を取り出す */ let peek = max_heap.pop(); println!("\nヒープ先頭要素 {} を取り出した後", peek); max_heap.print(); /* ヒープのサイズを取得 */ let size = max_heap.size(); println!("\nヒープ要素数は {}", size); /* ヒープが空かどうかを判定 */ let is_empty = max_heap.is_empty(); println!("\nヒープは空か {}", is_empty); } ================================================ FILE: ja/codes/rust/chapter_heap/top_k.rs ================================================ /* * File: top_k.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::cmp::Reverse; use std::collections::BinaryHeap; /* ヒープに基づいて配列中の最大の k 個の要素を探す */ fn top_k_heap(nums: Vec, k: usize) -> BinaryHeap> { // BinaryHeap は最大ヒープであり、Reverse で要素の順序を反転することで最小ヒープを実現する let mut heap = BinaryHeap::>::new(); // 配列の先頭 k 個の要素をヒープに追加 for &num in nums.iter().take(k) { heap.push(Reverse(num)); } // k+1 番目の要素から開始し、ヒープ長を k に保つ for &num in nums.iter().skip(k) { // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する if num > heap.peek().unwrap().0 { heap.pop(); heap.push(Reverse(num)); } } heap } /* Driver Code */ fn main() { let nums = vec![1, 7, 6, 3, 2]; let k = 3; let res = top_k_heap(nums, k); println!("最大の {} 個の要素は", k); print_util::print_heap(res.into_iter().map(|item| item.0).collect()); } ================================================ FILE: ja/codes/rust/chapter_searching/binary_search.rs ================================================ /* * File: binary_search.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ /* 二分探索(両閉区間) */ fn binary_search(nums: &[i32], target: i32) -> i32 { // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す let mut i = 0; let mut j = nums.len() as i32 - 1; // ループし、探索区間が空になったら終了する(i > j で空) while i <= j { let m = i + (j - i) / 2; // 中点インデックス m を計算 if nums[m as usize] < target { // この場合、target は区間 [m+1, j] にある i = m + 1; } else if nums[m as usize] > target { // この場合、target は区間 [i, m-1] にある j = m - 1; } else { // 目標要素が見つかったらそのインデックスを返す return m; } } // 目標要素が見つからなければ -1 を返す return -1; } /* 二分探索(左閉右開区間) */ fn binary_search_lcro(nums: &[i32], target: i32) -> i32 { // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す let mut i = 0; let mut j = nums.len() as i32; // ループし、探索区間が空になったら終了する(i = j で空) while i < j { let m = i + (j - i) / 2; // 中点インデックス m を計算 if nums[m as usize] < target { // この場合、target は区間 [m+1, j) にある i = m + 1; } else if nums[m as usize] > target { // この場合、target は区間 [i, m) にある j = m; } else { // 目標要素が見つかったらそのインデックスを返す return m; } } // 目標要素が見つからなければ -1 を返す return -1; } /* Driver Code */ pub fn main() { let target = 6; let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分探索(両閉区間) let mut index = binary_search(&nums, target); println!("目的の要素 6 のインデックス = {index}"); // 二分探索(左閉右開区間) index = binary_search_lcro(&nums, target); println!("目的の要素 6 のインデックス = {index}"); } ================================================ FILE: ja/codes/rust/chapter_searching/binary_search_edge.rs ================================================ /* * File: binary_search_edge.rs * Created Time: 2023-08-30 * Author: night-cruise (2586447362@qq.com) */ mod binary_search_insertion; use binary_search_insertion::binary_search_insertion; /* 最も左の target を二分探索 */ fn binary_search_left_edge(nums: &[i32], target: i32) -> i32 { // target の挿入位置を探すのと等価 let i = binary_search_insertion(nums, target); // target が見つからなければ、-1 を返す if i == nums.len() as i32 || nums[i as usize] != target { return -1; } // target が見つかったら、インデックス i を返す i } /* 最も右の target を二分探索 */ fn binary_search_right_edge(nums: &[i32], target: i32) -> i32 { // 最左の target + 1 を探す問題に変換する let i = binary_search_insertion(nums, target + 1); // j は最も右の target を指し、i は target より大きい最初の要素を指す let j = i - 1; // target が見つからなければ、-1 を返す if j == -1 || nums[j as usize] != target { return -1; } // target が見つかったら、インデックス j を返す j } /* Driver Code */ fn main() { // 重複要素を含む配列 let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; println!("\n配列 nums = {:?}", nums); // 二分探索で左端と右端を探す for target in [6, 7] { let index = binary_search_left_edge(&nums, target); println!("最も左にある要素 {} のインデックスは {}", target, index); let index = binary_search_right_edge(&nums, target); println!("最も右にある要素 {} のインデックスは {}", target, index); } } ================================================ FILE: ja/codes/rust/chapter_searching/binary_search_insertion.rs ================================================ /* * File: binary_search_insertion.rs * Created Time: 2023-08-30 * Author: night-cruise (2586447362@qq.com) */ #![allow(unused)] /* 二分探索で挿入位置を探す(重複要素なし) */ fn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 { let (mut i, mut j) = (0, nums.len() as i32 - 1); // 両閉区間 [0, n-1] を初期化 while i <= j { let m = i + (j - i) / 2; // 中点インデックス m を計算 if nums[m as usize] < target { i = m + 1; // target は区間 [m+1, j] にある } else if nums[m as usize] > target { j = m - 1; // target は区間 [i, m-1] にある } else { return m; } } // target が見つからなければ、挿入位置 i を返す i } /* 二分探索で挿入位置を探す(重複要素あり) */ pub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 { let (mut i, mut j) = (0, nums.len() as i32 - 1); // 両閉区間 [0, n-1] を初期化 while i <= j { let m = i + (j - i) / 2; // 中点インデックス m を計算 if nums[m as usize] < target { i = m + 1; // target は区間 [m+1, j] にある } else if nums[m as usize] > target { j = m - 1; // target は区間 [i, m-1] にある } else { j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある } } // 挿入位置 i を返す i } /* Driver Code */ fn main() { // 重複要素のない配列 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; println!("\n配列 nums = {:?}", nums); // 二分探索で挿入位置を探す for target in [6, 9] { let index = binary_search_insertion_simple(&nums, target); println!("要素 {} の挿入位置のインデックスは {}", target, index); } // 重複要素を含む配列 let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; println!("\n配列 nums = {:?}", nums); // 二分探索で挿入位置を探す for target in [2, 6, 20] { let index = binary_search_insertion(&nums, target); println!("要素 {} の挿入位置のインデックスは {}", target, index); } } ================================================ FILE: ja/codes/rust/chapter_searching/hashing_search.rs ================================================ /* * File: hashing_search.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::ListNode; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; /* ハッシュ探索(配列) */ fn hashing_search_array<'a>(map: &'a HashMap, target: i32) -> Option<&'a usize> { // ハッシュテーブルの key: 対象要素、value: インデックス // ハッシュテーブルにその key がなければ None を返す map.get(&target) } /* ハッシュ探索(連結リスト) */ fn hashing_search_linked_list( map: &HashMap>>>, target: i32, ) -> Option<&Rc>>> { // ハッシュテーブルの key: 対象ノードの値、value: ノードオブジェクト // ハッシュテーブルにその key がなければ None を返す map.get(&target) } /* Driver Code */ pub fn main() { let target = 3; /* ハッシュ探索(配列) */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // ハッシュテーブルを初期化 let mut map = HashMap::new(); for (i, num) in nums.iter().enumerate() { map.insert(*num, i); // key: 要素、value: インデックス } let index = hashing_search_array(&map, target); println!("対象要素 3 のインデックス = {}", index.unwrap()); /* ハッシュ探索(連結リスト) */ let head = ListNode::arr_to_linked_list(&nums); // ハッシュテーブルを初期化する // let mut map1 = HashMap::new(); let map1 = ListNode::linked_list_to_hashmap(head); let node = hashing_search_linked_list(&map1, target); println!("対象ノード値 3 に対応するノードオブジェクトは {:?}", node); } ================================================ FILE: ja/codes/rust/chapter_searching/linear_search.rs ================================================ /* * File: linear_search.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::ListNode; use std::cell::RefCell; use std::rc::Rc; /* 線形探索(配列) */ fn linear_search_array(nums: &[i32], target: i32) -> i32 { // 配列を走査 for (i, num) in nums.iter().enumerate() { // 目標要素が見つかったらそのインデックスを返す if num == &target { return i as i32; } } // 目標要素が見つからなければ -1 を返す return -1; } /* 線形探索(連結リスト) */ fn linear_search_linked_list( head: Rc>>, target: i32, ) -> Option>>> { // 対象ノードが見つかったら、それを返す if head.borrow().val == target { return Some(head); }; // 対象ノードが見つかったら、それを返す if let Some(node) = &head.borrow_mut().next { return linear_search_linked_list(node.clone(), target); } // 対象ノードが見つからない場合は None を返す return None; } /* Driver Code */ pub fn main() { let target = 3; /* 配列で線形探索を行う */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; let index = linear_search_array(&nums, target); println!("対象要素 3 のインデックス = {}", index); /* 連結リストで線形探索を行う */ let head = ListNode::arr_to_linked_list(&nums); let node = linear_search_linked_list(head.unwrap(), target); println!("対象ノード値 3 に対応するノードオブジェクトは {:?}", node); } ================================================ FILE: ja/codes/rust/chapter_searching/two_sum.rs ================================================ /* * File: two_sum.rs * Created Time: 2023-01-14 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::collections::HashMap; /* 方法 1:総当たり列挙 */ pub fn two_sum_brute_force(nums: &Vec, target: i32) -> Option> { let size = nums.len(); // 2重ループのため、時間計算量は O(n^2) for i in 0..size - 1 { for j in i + 1..size { if nums[i] + nums[j] == target { return Some(vec![i as i32, j as i32]); } } } None } /* 方法 2:補助ハッシュテーブル */ pub fn two_sum_hash_table(nums: &Vec, target: i32) -> Option> { // 補助ハッシュテーブルを使用し、空間計算量は O(n) let mut dic = HashMap::new(); // 単一ループで、時間計算量は O(n) for (i, num) in nums.iter().enumerate() { match dic.get(&(target - num)) { Some(v) => return Some(vec![*v as i32, i as i32]), None => dic.insert(num, i as i32), }; } None } fn main() { // ======= Test Case ======= let nums = vec![2, 7, 11, 15]; let target = 13; // ====== Driver Code ====== // 方法 1 let res = two_sum_brute_force(&nums, target).unwrap(); print!("方法1 res = "); print_util::print_array(&res); // 方法 2 let res = two_sum_hash_table(&nums, target).unwrap(); print!("\n方法2 res = "); print_util::print_array(&res); } ================================================ FILE: ja/codes/rust/chapter_sorting/bubble_sort.rs ================================================ /* * File: bubble_sort.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* バブルソート */ fn bubble_sort(nums: &mut [i32]) { // 外側のループ:未ソート区間は [0, i] for i in (1..nums.len()).rev() { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j in 0..i { if nums[j] > nums[j + 1] { // nums[j] と nums[j + 1] を交換 nums.swap(j, j + 1); } } } } /* バブルソート(フラグ最適化) */ fn bubble_sort_with_flag(nums: &mut [i32]) { // 外側のループ:未ソート区間は [0, i] for i in (1..nums.len()).rev() { let mut flag = false; // フラグを初期化する // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j in 0..i { if nums[j] > nums[j + 1] { // nums[j] と nums[j + 1] を交換 nums.swap(j, j + 1); flag = true; // 交換する要素を記録 } } if !flag { break; // このバブル処理で要素交換が一度もなければそのまま終了 }; } } /* Driver Code */ pub fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; bubble_sort(&mut nums); print!("バブルソート完了後 nums = "); print_util::print_array(&nums); let mut nums1 = [4, 1, 3, 1, 5, 2]; bubble_sort_with_flag(&mut nums1); print!("\nバブルソート完了後 nums1 = "); print_util::print_array(&nums1); } ================================================ FILE: ja/codes/rust/chapter_sorting/bucket_sort.rs ================================================ /* * File: bucket_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* バケットソート */ fn bucket_sort(nums: &mut [f64]) { // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする let k = nums.len() / 2; let mut buckets = vec![vec![]; k]; // 1. 配列要素を各バケットに振り分ける for &num in nums.iter() { // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する let i = (num * k as f64) as usize; // num をバケット i に追加 buckets[i].push(num); } // 2. 各バケットをソートする for bucket in &mut buckets { // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい bucket.sort_by(|a, b| a.partial_cmp(b).unwrap()); } // 3. バケットを走査して結果を結合 let mut i = 0; for bucket in buckets.iter() { for &num in bucket.iter() { nums[i] = num; i += 1; } } } /* Driver Code */ fn main() { // 入力データは範囲 [0, 1) の浮動小数点数とする let mut nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucket_sort(&mut nums); print!("バケットソート完了後 nums = "); print_util::print_array(&nums); } ================================================ FILE: ja/codes/rust/chapter_sorting/counting_sort.rs ================================================ /* * File: counting_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* 計数ソート */ // 簡易実装のため、オブジェクトのソートには使えない fn counting_sort_naive(nums: &mut [i32]) { // 1. 配列の最大要素 m を求める let m = *nums.iter().max().unwrap(); // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す let mut counter = vec![0; m as usize + 1]; for &num in nums.iter() { counter[num as usize] += 1; } // 3. counter を走査し、各要素を元の配列 nums に書き戻す let mut i = 0; for num in 0..m + 1 { for _ in 0..counter[num as usize] { nums[i] = num; i += 1; } } } /* 計数ソート */ // 完全な実装で、オブジェクトをソートでき、かつ安定ソートである fn counting_sort(nums: &mut [i32]) { // 1. 配列の最大要素 m を求める let m = *nums.iter().max().unwrap() as usize; // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す let mut counter = vec![0; m + 1]; for &num in nums.iter() { counter[num as usize] += 1; } // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する // つまり counter[num]-1 は、num が res に最後に現れるインデックス for i in 0..m { counter[i + 1] += counter[i]; } // 4. nums を逆順に走査し、各要素を結果配列 res に格納する // 結果を記録するための配列 res を初期化 let n = nums.len(); let mut res = vec![0; n]; for i in (0..n).rev() { let num = nums[i]; res[counter[num as usize] - 1] = num; // num を対応するインデックスに配置 counter[num as usize] -= 1; // 累積和を 1 減らして、次に num を配置するインデックスを得る } // 結果配列 res で元の配列 nums を上書きする nums.copy_from_slice(&res) } /* Driver Code */ fn main() { let mut nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; counting_sort_naive(&mut nums); print!("カウントソート(オブジェクトはソート不可)完了後 nums = "); print_util::print_array(&nums); let mut nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; counting_sort(&mut nums1); print!("\nカウントソート完了後 nums1 = "); print_util::print_array(&nums1); } ================================================ FILE: ja/codes/rust/chapter_sorting/heap_sort.rs ================================================ /* * File: heap_sort.rs * Created Time: 2023-07-04 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* ヒープの長さは n。ノード i から下方向にヒープ化 */ fn sift_down(nums: &mut [i32], n: usize, mut i: usize) { loop { // ノード i, l, r のうち値が最大のノードを ma とする let l = 2 * i + 1; let r = 2 * i + 2; let mut ma = i; if l < n && nums[l] > nums[ma] { ma = l; } if r < n && nums[r] > nums[ma] { ma = r; } // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if ma == i { break; } // 2 つのノードを交換 nums.swap(i, ma); // ループで上から下へヒープ化 i = ma; } } /* ヒープソート */ fn heap_sort(nums: &mut [i32]) { // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する for i in (0..nums.len() / 2).rev() { sift_down(nums, nums.len(), i); } // ヒープから最大要素を取り出し、n-1 回繰り返す for i in (1..nums.len()).rev() { // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) nums.swap(0, i); // 根ノードを起点に、上から下へヒープ化 sift_down(nums, i, 0); } } /* Driver Code */ fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; heap_sort(&mut nums); print!("ヒープソート完了後 nums = "); print_util::print_array(&nums); } ================================================ FILE: ja/codes/rust/chapter_sorting/insertion_sort.rs ================================================ /* * File: insertion_sort.rs * Created Time: 2023-02-13 * Author: xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; /* 挿入ソート */ fn insertion_sort(nums: &mut [i32]) { // 外側ループ:整列済み区間は [0, i-1] for i in 1..nums.len() { let (base, mut j) = (nums[i], (i - 1) as i32); // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する while j >= 0 && nums[j as usize] > base { nums[(j + 1) as usize] = nums[j as usize]; // nums[j] を 1 つ右へ移動する j -= 1; } nums[(j + 1) as usize] = base; // base を正しい位置に配置する } } /* Driver Code */ fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; insertion_sort(&mut nums); print!("挿入ソート完了後 nums = "); print_util::print_array(&nums); } ================================================ FILE: ja/codes/rust/chapter_sorting/merge_sort.rs ================================================ /** * File: merge_sort.rs * Created Time: 2023-02-14 * Author: xBLACKICEx (xBLACKICEx@outlook.com) */ /* 左部分配列と右部分配列をマージ */ fn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) { // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] // マージ結果を格納する一時配列 tmp を作成 let tmp_size = right - left + 1; let mut tmp = vec![0; tmp_size]; // 左右の部分配列の開始インデックスを初期化する let (mut i, mut j, mut k) = (left, mid + 1, 0); // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする while i <= mid && j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i]; i += 1; } else { tmp[k] = nums[j]; j += 1; } k += 1; } // 左右の部分配列の残り要素を一時配列にコピーする while i <= mid { tmp[k] = nums[i]; k += 1; i += 1; } while j <= right { tmp[k] = nums[j]; k += 1; j += 1; } // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする for k in 0..tmp_size { nums[left + k] = tmp[k]; } } /* マージソート */ fn merge_sort(nums: &mut [i32], left: usize, right: usize) { // 終了条件 if left >= right { return; // 部分配列の長さが 1 になったら再帰を終了 } // 分割フェーズ let mid = left + (right - left) / 2; // 中点を計算 merge_sort(nums, left, mid); // 左部分配列を再帰処理 merge_sort(nums, mid + 1, right); // 右部分配列を再帰処理 // マージフェーズ merge(nums, left, mid, right); } /* Driver Code */ fn main() { /* マージソート */ let mut nums = [7, 3, 2, 6, 0, 1, 5, 4]; let right = nums.len() - 1; merge_sort(&mut nums, 0, right); println!("マージソート完了後 nums = {:?}", nums); } ================================================ FILE: ja/codes/rust/chapter_sorting/quick_sort.rs ================================================ /** * File: quick_sort.rs * Created Time: 2023-02-16 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ /* クイックソート */ struct QuickSort; impl QuickSort { /* 番兵分割 */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // nums[left] を基準値とする let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // 右から左へ基準値未満の最初の要素を探す } while i < j && nums[i] <= nums[left] { i += 1; // 左から右へ基準値より大きい最初の要素を探す } nums.swap(i, j); // この 2 つの要素を交換 } nums.swap(i, left); // 基準値を 2 つの部分配列の境界へ交換する i // 基準値のインデックスを返す } /* クイックソート */ pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { // 部分配列の長さが 1 なら再帰を終了する if left >= right { return; } // 番兵分割 let pivot = Self::partition(nums, left as usize, right as usize) as i32; // 左右の部分配列を再帰処理 Self::quick_sort(left, pivot - 1, nums); Self::quick_sort(pivot + 1, right, nums); } } /* クイックソート(中央値の基準値で最適化) */ struct QuickSortMedian; impl QuickSortMedian { /* 3つの候補要素の中央値を選ぶ */ fn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize { let (l, m, r) = (nums[left], nums[mid], nums[right]); if (l <= m && m <= r) || (r <= m && m <= l) { return mid; // m は l と r の間 } if (m <= l && l <= r) || (r <= l && l <= m) { return left; // l は m と r の間 } right } /* 番兵による分割処理(3 点中央値) */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // 3つの候補要素の中央値を選ぶ let med = Self::median_three(nums, left, (left + right) / 2, right); // 中央値を配列の最左端に交換する nums.swap(left, med); // nums[left] を基準値とする let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // 右から左へ基準値未満の最初の要素を探す } while i < j && nums[i] <= nums[left] { i += 1; // 左から右へ基準値より大きい最初の要素を探す } nums.swap(i, j); // この 2 つの要素を交換 } nums.swap(i, left); // 基準値を 2 つの部分配列の境界へ交換する i // 基準値のインデックスを返す } /* クイックソート */ pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { // 部分配列の長さが 1 なら再帰を終了する if left >= right { return; } // 番兵分割 let pivot = Self::partition(nums, left as usize, right as usize) as i32; // 左右の部分配列を再帰処理 Self::quick_sort(left, pivot - 1, nums); Self::quick_sort(pivot + 1, right, nums); } } /* クイックソート(再帰深度最適化) */ struct QuickSortTailCall; impl QuickSortTailCall { /* 番兵分割 */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // nums[left] を基準値とする let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // 右から左へ基準値未満の最初の要素を探す } while i < j && nums[i] <= nums[left] { i += 1; // 左から右へ基準値より大きい最初の要素を探す } nums.swap(i, j); // この 2 つの要素を交換 } nums.swap(i, left); // 基準値を 2 つの部分配列の境界へ交換する i // 基準値のインデックスを返す } /* クイックソート(再帰深度最適化) */ pub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) { // 部分配列の長さが 1 なら終了 while left < right { // 番兵による分割処理 let pivot = Self::partition(nums, left as usize, right as usize) as i32; // 2 つの部分配列のうち短いほうにクイックソートを適用する if pivot - left < right - pivot { Self::quick_sort(left, pivot - 1, nums); // 左部分配列を再帰的にソート left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] } else { Self::quick_sort(pivot + 1, right, nums); // 右部分配列を再帰的にソート right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] } } } } /* Driver Code */ fn main() { /* クイックソート */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSort::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("クイックソート完了後 nums = {:?}", nums); /* クイックソート(中央値の基準値で最適化) */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSortMedian::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("クイックソート(中央値ピボット最適化)完了後 nums = {:?}", nums); /* クイックソート(再帰深度最適化) */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSortTailCall::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("クイックソート(再帰深度最適化)完了後 nums = {:?}", nums); } ================================================ FILE: ja/codes/rust/chapter_sorting/radix_sort.rs ================================================ /* * File: radix_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ fn digit(num: i32, exp: i32) -> usize { // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す return ((num / exp) % 10) as usize; } /* 計数ソート(nums の k 桁目でソート) */ fn counting_sort_digit(nums: &mut [i32], exp: i32) { // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 let mut counter = [0; 10]; let n = nums.len(); // 0~9 の各数字の出現回数を集計する for i in 0..n { let d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする counter[d] += 1; // 数字 d の出現回数を数える } // 累積和を求め、「出現回数」を「配列インデックス」に変換する for i in 1..10 { counter[i] += counter[i - 1]; } // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する let mut res = vec![0; n]; for i in (0..n).rev() { let d = digit(nums[i], exp); let j = counter[d] - 1; // d の配列内インデックス j を取得する res[j] = nums[i]; // 現在の要素をインデックス j に格納する counter[d] -= 1; // d の個数を 1 減らす } // 結果で元の配列 nums を上書きする nums.copy_from_slice(&res); } /* 基数ソート */ fn radix_sort(nums: &mut [i32]) { // 最大桁数の判定用に配列の最大要素を取得 let m = *nums.into_iter().max().unwrap(); // 下位桁から上位桁の順に走査する let mut exp = 1; while exp <= m { counting_sort_digit(nums, exp); exp *= 10; } } /* Driver Code */ fn main() { // 基数ソート let mut nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radix_sort(&mut nums); print!("基数ソート完了後 nums = "); print_util::print_array(&nums); } ================================================ FILE: ja/codes/rust/chapter_sorting/selection_sort.rs ================================================ /* * File: selection_sort.rs * Created Time: 2023-05-30 * Author: WSL0809 (wslzzy@outlook.com) */ use hello_algo_rust::include::print_util; /* 選択ソート */ fn selection_sort(nums: &mut [i32]) { if nums.is_empty() { return; } let n = nums.len(); // 外側ループ:未整列区間は [i, n-1] for i in 0..n - 1 { // 内側のループ:未ソート区間の最小要素を見つける let mut k = i; for j in i + 1..n { if nums[j] < nums[k] { k = j; // 最小要素のインデックスを記録 } } // その最小要素を未整列区間の先頭要素と交換する nums.swap(i, k); } } /* Driver Code */ pub fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; selection_sort(&mut nums); print!("\n選択ソート完了後 nums = "); print_util::print_array(&nums); } ================================================ FILE: ja/codes/rust/chapter_stack_and_queue/array_deque.rs ================================================ /* * File: array_deque.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* 循環配列ベースの両端キュー */ struct ArrayDeque { nums: Vec, // 両端キューの要素を格納する配列 front: usize, // 先頭ポインタ。先頭要素を指す que_size: usize, // 両端キューの長さ } impl ArrayDeque { /* コンストラクタ */ pub fn new(capacity: usize) -> Self { Self { nums: vec![T::default(); capacity], front: 0, que_size: 0, } } /* 両端キューの容量を取得 */ pub fn capacity(&self) -> usize { self.nums.len() } /* 両端キューの長さを取得 */ pub fn size(&self) -> usize { self.que_size } /* 両端キューが空かどうかを判定 */ pub fn is_empty(&self) -> bool { self.que_size == 0 } /* 循環配列のインデックスを計算 */ fn index(&self, i: i32) -> usize { // 剰余演算により配列の先頭と末尾をつなげる // i が配列の末尾を越えたら先頭に戻る // i が配列の先頭を越えて前に出たら末尾に戻る ((i + self.capacity() as i32) % self.capacity() as i32) as usize } /* キュー先頭にエンキュー */ pub fn push_first(&mut self, num: T) { if self.que_size == self.capacity() { println!("両端キューがいっぱいです"); return; } // 先頭ポインタを左に 1 つ移動する // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする self.front = self.index(self.front as i32 - 1); // num をキュー先頭に追加 self.nums[self.front] = num; self.que_size += 1; } /* キュー末尾にエンキュー */ pub fn push_last(&mut self, num: T) { if self.que_size == self.capacity() { println!("両端キューがいっぱいです"); return; } // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す let rear = self.index(self.front as i32 + self.que_size as i32); // num をキュー末尾に追加 self.nums[rear] = num; self.que_size += 1; } /* キュー先頭からデキュー */ fn pop_first(&mut self) -> T { let num = self.peek_first(); // 先頭ポインタを 1 つ後ろへ進める self.front = self.index(self.front as i32 + 1); self.que_size -= 1; num } /* キュー末尾からデキュー */ fn pop_last(&mut self) -> T { let num = self.peek_last(); self.que_size -= 1; num } /* キュー先頭の要素にアクセス */ fn peek_first(&self) -> T { if self.is_empty() { panic!("両端キューが空です") }; self.nums[self.front] } /* キュー末尾の要素にアクセス */ fn peek_last(&self) -> T { if self.is_empty() { panic!("両端キューが空です") }; // 末尾要素のインデックスを計算 let last = self.index(self.front as i32 + self.que_size as i32 - 1); self.nums[last] } /* 出力用の配列を返す */ fn to_array(&self) -> Vec { // 有効長の範囲内のリスト要素のみを変換 let mut res = vec![T::default(); self.que_size]; let mut j = self.front; for i in 0..self.que_size { res[i] = self.nums[self.index(j as i32)]; j += 1; } res } } /* Driver Code */ fn main() { /* 両端キューを初期化 */ let mut deque = ArrayDeque::new(10); deque.push_last(3); deque.push_last(2); deque.push_last(5); print!("両端キュー deque = "); print_util::print_array(&deque.to_array()); /* 要素にアクセス */ let peek_first = deque.peek_first(); print!("\n先頭要素 peek_first = {}", peek_first); let peek_last = deque.peek_last(); print!("\n末尾要素 peek_last = {}", peek_last); /* 要素をエンキュー */ deque.push_last(4); print!("\n要素 4 を末尾に追加後 deque = "); print_util::print_array(&deque.to_array()); deque.push_first(1); print!("\n要素 1 を先頭に追加後 deque = "); print_util::print_array(&deque.to_array()); /* 要素をデキュー */ let pop_last = deque.pop_last(); print!("\n末尾から取り出した要素 = {}、取り出し後 deque = ", pop_last); print_util::print_array(&deque.to_array()); let pop_first = deque.pop_first(); print!("\n先頭から取り出した要素 = {}、取り出し後 deque = ", pop_first); print_util::print_array(&deque.to_array()); /* 両端キューの長さを取得 */ let size = deque.size(); print!("\n両端キューの長さ size = {}", size); /* 両端キューが空かどうかを判定 */ let is_empty = deque.is_empty(); print!("\n両端キューが空かどうか = {}", is_empty); } ================================================ FILE: ja/codes/rust/chapter_stack_and_queue/array_queue.rs ================================================ /* * File: array_queue.rs * Created Time: 2023-02-06 * Author: WSL0809 (wslzzy@outlook.com) */ /* 循環配列ベースのキュー */ struct ArrayQueue { nums: Vec, // キュー要素を格納する配列 front: i32, // 先頭ポインタ。先頭要素を指す que_size: i32, // キューの長さ que_capacity: i32, // キューの容量 } impl ArrayQueue { /* コンストラクタ */ fn new(capacity: i32) -> ArrayQueue { ArrayQueue { nums: vec![T::default(); capacity as usize], front: 0, que_size: 0, que_capacity: capacity, } } /* キューの容量を取得 */ fn capacity(&self) -> i32 { self.que_capacity } /* キューの長さを取得 */ fn size(&self) -> i32 { self.que_size } /* キューが空かどうかを判定 */ fn is_empty(&self) -> bool { self.que_size == 0 } /* エンキュー */ fn push(&mut self, num: T) { if self.que_size == self.capacity() { println!("キューがいっぱいです"); return; } // 末尾ポインタを計算し、末尾インデックス + 1 を指す // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする let rear = (self.front + self.que_size) % self.que_capacity; // num をキュー末尾に追加 self.nums[rear as usize] = num; self.que_size += 1; } /* デキュー */ fn pop(&mut self) -> T { let num = self.peek(); // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す self.front = (self.front + 1) % self.que_capacity; self.que_size -= 1; num } /* キュー先頭の要素にアクセス */ fn peek(&self) -> T { if self.is_empty() { panic!("index out of bounds"); } self.nums[self.front as usize] } /* 配列を返す */ fn to_vector(&self) -> Vec { let cap = self.que_capacity; let mut j = self.front; let mut arr = vec![T::default(); cap as usize]; for i in 0..self.que_size { arr[i as usize] = self.nums[(j % cap) as usize]; j += 1; } arr } } /* Driver Code */ fn main() { /* キューを初期化 */ let capacity = 10; let mut queue = ArrayQueue::new(capacity); /* 要素をエンキュー */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); println!("キュー queue = {:?}", queue.to_vector()); /* キュー先頭の要素にアクセス */ let peek = queue.peek(); println!("先頭要素 peek = {}", peek); /* 要素をデキュー */ let pop = queue.pop(); println!( "取り出した要素 pop = {:?},取り出し後 queue = {:?}", pop, queue.to_vector() ); /* キューの長さを取得 */ let size = queue.size(); println!("キューの長さ size = {}", size); /* キューが空かどうかを判定 */ let is_empty = queue.is_empty(); println!("キューが空かどうか = {}", is_empty); /* 循環配列をテストする */ for i in 0..10 { queue.push(i); queue.pop(); println!("第 {:?} 回のエンキュー + デキュー後 queue = {:?}", i, queue.to_vector()); } } ================================================ FILE: ja/codes/rust/chapter_stack_and_queue/array_stack.rs ================================================ /* * File: array_stack.rs * Created Time: 2023-02-05 * Author: WSL0809 (wslzzy@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* 配列ベースのスタック */ struct ArrayStack { stack: Vec, } impl ArrayStack { /* スタックを初期化 */ fn new() -> ArrayStack { ArrayStack:: { stack: Vec::::new(), } } /* スタックの長さを取得 */ fn size(&self) -> usize { self.stack.len() } /* スタックが空かどうかを判定 */ fn is_empty(&self) -> bool { self.size() == 0 } /* プッシュ */ fn push(&mut self, num: T) { self.stack.push(num); } /* ポップ */ fn pop(&mut self) -> Option { self.stack.pop() } /* スタックトップの要素にアクセス */ fn peek(&self) -> Option<&T> { if self.is_empty() { panic!("スタックが空です") }; self.stack.last() } /* &Vec を返す */ fn to_array(&self) -> &Vec { &self.stack } } /* Driver Code */ fn main() { // スタックを初期化 let mut stack = ArrayStack::::new(); // 要素をプッシュ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("スタック stack = "); print_util::print_array(stack.to_array()); // スタックトップの要素にアクセス let peek = stack.peek().unwrap(); print!("\nスタックトップ要素 peek = {}", peek); // 要素をポップ let pop = stack.pop().unwrap(); print!("\n取り出した要素 pop = {pop}、取り出し後 stack = "); print_util::print_array(stack.to_array()); // スタックの長さを取得 let size = stack.size(); print!("\nスタックの長さ size = {size}"); // 空かどうかを判定 let is_empty = stack.is_empty(); print!("\nスタックが空かどうか = {is_empty}"); } ================================================ FILE: ja/codes/rust/chapter_stack_and_queue/deque.rs ================================================ /* * File: deque.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; use std::collections::VecDeque; /* Driver Code */ pub fn main() { // 両端キューを初期化 let mut deque: VecDeque = VecDeque::new(); deque.push_back(3); deque.push_back(2); deque.push_back(5); print!("両端キュー deque = "); print_util::print_queue(&deque); // 要素にアクセス let peek_first = deque.front().unwrap(); print!("\n先頭要素 peekFirst = {peek_first}"); let peek_last = deque.back().unwrap(); print!("\n末尾要素 peekLast = {peek_last}"); /* 要素をエンキュー */ deque.push_back(4); print!("\n要素 4 を末尾に追加後 deque = "); print_util::print_queue(&deque); deque.push_front(1); print!("\n要素 1 を先頭に追加後 deque = "); print_util::print_queue(&deque); // 要素をデキュー let pop_last = deque.pop_back().unwrap(); print!("\n末尾から取り出した要素 = {pop_last}、取り出し後 deque = "); print_util::print_queue(&deque); let pop_first = deque.pop_front().unwrap(); print!("\n先頭から取り出した要素 = {pop_first}、取り出し後 deque = "); print_util::print_queue(&deque); // 両端キューの長さを取得 let size = deque.len(); print!("\n両端キューの長さ size = {size}"); // 両端キューが空かどうかを判定 let is_empty = deque.is_empty(); print!("\n両端キューが空かどうか = {is_empty}"); } ================================================ FILE: ja/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs ================================================ /* * File: linkedlist_deque.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::cell::RefCell; use std::rc::Rc; /* 双方向連結リストノード */ pub struct ListNode { pub val: T, // ノード値 pub next: Option>>>, // 後継ノードへのポインタ pub prev: Option>>>, // 前駆ノードへのポインタ } impl ListNode { pub fn new(val: T) -> Rc>> { Rc::new(RefCell::new(ListNode { val, next: None, prev: None, })) } } /* 双方向連結リストベースの両端キュー */ #[allow(dead_code)] pub struct LinkedListDeque { front: Option>>>, // 先頭ノード front rear: Option>>>, // 末尾ノード rear que_size: usize, // 両端キューの長さ } impl LinkedListDeque { pub fn new() -> Self { Self { front: None, rear: None, que_size: 0, } } /* 両端キューの長さを取得 */ pub fn size(&self) -> usize { return self.que_size; } /* 両端キューが空かどうかを判定 */ pub fn is_empty(&self) -> bool { return self.que_size == 0; } /* エンキュー操作 */ fn push(&mut self, num: T, is_front: bool) { let node = ListNode::new(num); // 先頭へのエンキュー操作 if is_front { match self.front.take() { // 連結リストが空なら、front と rear の両方を node に向ける None => { self.rear = Some(node.clone()); self.front = Some(node); } // node を連結リストの先頭に追加 Some(old_front) => { old_front.borrow_mut().prev = Some(node.clone()); node.borrow_mut().next = Some(old_front); self.front = Some(node); // 先頭ノードを更新する } } } // 末尾へのエンキュー操作 else { match self.rear.take() { // 連結リストが空なら、front と rear の両方を node に向ける None => { self.front = Some(node.clone()); self.rear = Some(node); } // node を連結リストの末尾に追加 Some(old_rear) => { old_rear.borrow_mut().next = Some(node.clone()); node.borrow_mut().prev = Some(old_rear); self.rear = Some(node); // 末尾ノードを更新する } } } self.que_size += 1; // キューの長さを更新 } /* キュー先頭にエンキュー */ pub fn push_first(&mut self, num: T) { self.push(num, true); } /* キュー末尾にエンキュー */ pub fn push_last(&mut self, num: T) { self.push(num, false); } /* デキュー操作 */ fn pop(&mut self, is_front: bool) -> Option { // キューが空なら、そのまま `None` を返す if self.is_empty() { return None; }; // キュー先頭からの取り出し if is_front { self.front.take().map(|old_front| { match old_front.borrow_mut().next.take() { Some(new_front) => { new_front.borrow_mut().prev.take(); self.front = Some(new_front); // 先頭ノードを更新する } None => { self.rear.take(); } } self.que_size -= 1; // キューの長さを更新 old_front.borrow().val }) } // キュー末尾からの取り出し else { self.rear.take().map(|old_rear| { match old_rear.borrow_mut().prev.take() { Some(new_rear) => { new_rear.borrow_mut().next.take(); self.rear = Some(new_rear); // 末尾ノードを更新する } None => { self.front.take(); } } self.que_size -= 1; // キューの長さを更新 old_rear.borrow().val }) } } /* キュー先頭からデキュー */ pub fn pop_first(&mut self) -> Option { return self.pop(true); } /* キュー末尾からデキュー */ pub fn pop_last(&mut self) -> Option { return self.pop(false); } /* キュー先頭の要素にアクセス */ pub fn peek_first(&self) -> Option<&Rc>>> { self.front.as_ref() } /* キュー末尾の要素にアクセス */ pub fn peek_last(&self) -> Option<&Rc>>> { self.rear.as_ref() } /* 出力用の配列を返す */ pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { let mut res: Vec = Vec::new(); fn recur(cur: Option<&Rc>>>, res: &mut Vec) { if let Some(cur) = cur { res.push(cur.borrow().val); recur(cur.borrow().next.as_ref(), res); } } recur(head, &mut res); res } } /* Driver Code */ fn main() { /* 両端キューを初期化 */ let mut deque = LinkedListDeque::new(); deque.push_last(3); deque.push_last(2); deque.push_last(5); print!("両端キュー deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); /* 要素にアクセス */ let peek_first = deque.peek_first().unwrap().borrow().val; print!("\n先頭要素 peek_first = {}", peek_first); let peek_last = deque.peek_last().unwrap().borrow().val; print!("\n末尾要素 peek_last = {}", peek_last); /* 要素をエンキュー */ deque.push_last(4); print!("\n要素 4 を末尾に追加後 deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); deque.push_first(1); print!("\n要素 1 を先頭に追加後 deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); /* 要素をデキュー */ let pop_last = deque.pop_last().unwrap(); print!("\n末尾から取り出した要素 = {}、取り出し後 deque = ", pop_last); print_util::print_array(&deque.to_array(deque.peek_first())); let pop_first = deque.pop_first().unwrap(); print!("\n先頭から取り出した要素 = {}、取り出し後 deque = ", pop_first); print_util::print_array(&deque.to_array(deque.peek_first())); /* 両端キューの長さを取得 */ let size = deque.size(); print!("\n両端キューの長さ size = {}", size); /* 両端キューが空かどうかを判定 */ let is_empty = deque.is_empty(); print!("\n両端キューが空かどうか = {}", is_empty); } ================================================ FILE: ja/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs ================================================ /* * File: linkedlist_queue.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* 連結リストベースのキュー */ #[allow(dead_code)] pub struct LinkedListQueue { front: Option>>>, // 先頭ノード front rear: Option>>>, // 末尾ノード rear que_size: usize, // キューの長さ } impl LinkedListQueue { pub fn new() -> Self { Self { front: None, rear: None, que_size: 0, } } /* キューの長さを取得 */ pub fn size(&self) -> usize { return self.que_size; } /* キューが空かどうかを判定 */ pub fn is_empty(&self) -> bool { return self.que_size == 0; } /* エンキュー */ pub fn push(&mut self, num: T) { // 末尾ノードの後ろに num を追加 let new_rear = ListNode::new(num); match self.rear.take() { // キューが空でなければ、そのノードを末尾ノードの後ろに追加 Some(old_rear) => { old_rear.borrow_mut().next = Some(new_rear.clone()); self.rear = Some(new_rear); } // キューが空なら、先頭・末尾ノードをともにそのノードに設定 None => { self.front = Some(new_rear.clone()); self.rear = Some(new_rear); } } self.que_size += 1; } /* デキュー */ pub fn pop(&mut self) -> Option { self.front.take().map(|old_front| { match old_front.borrow_mut().next.take() { Some(new_front) => { self.front = Some(new_front); } None => { self.rear.take(); } } self.que_size -= 1; old_front.borrow().val }) } /* キュー先頭の要素にアクセス */ pub fn peek(&self) -> Option<&Rc>>> { self.front.as_ref() } /* 連結リストを Array に変換して返す */ pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { let mut res: Vec = Vec::new(); fn recur(cur: Option<&Rc>>>, res: &mut Vec) { if let Some(cur) = cur { res.push(cur.borrow().val); recur(cur.borrow().next.as_ref(), res); } } recur(head, &mut res); res } } /* Driver Code */ fn main() { /* キューを初期化 */ let mut queue = LinkedListQueue::new(); /* 要素をエンキュー */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print!("キュー queue = "); print_util::print_array(&queue.to_array(queue.peek())); /* キュー先頭の要素にアクセス */ let peek = queue.peek().unwrap().borrow().val; print!("\n先頭要素 peek = {}", peek); /* 要素をデキュー */ let pop = queue.pop().unwrap(); print!("\n取り出した要素 pop = {}、取り出し後 queue = ", pop); print_util::print_array(&queue.to_array(queue.peek())); /* キューの長さを取得 */ let size = queue.size(); print!("\nキューの長さ size = {}", size); /* キューが空かどうかを判定 */ let is_empty = queue.is_empty(); print!("\nキューが空かどうか = {}", is_empty); } ================================================ FILE: ja/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs ================================================ /* * File: linkedlist_stack.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* 連結リストベースのスタック */ #[allow(dead_code)] pub struct LinkedListStack { stack_peek: Option>>>, // 先頭ノードをスタックトップとする stk_size: usize, // スタックの長さ } impl LinkedListStack { pub fn new() -> Self { Self { stack_peek: None, stk_size: 0, } } /* スタックの長さを取得 */ pub fn size(&self) -> usize { return self.stk_size; } /* スタックが空かどうかを判定 */ pub fn is_empty(&self) -> bool { return self.size() == 0; } /* プッシュ */ pub fn push(&mut self, num: T) { let node = ListNode::new(num); node.borrow_mut().next = self.stack_peek.take(); self.stack_peek = Some(node); self.stk_size += 1; } /* ポップ */ pub fn pop(&mut self) -> Option { self.stack_peek.take().map(|old_head| { self.stack_peek = old_head.borrow_mut().next.take(); self.stk_size -= 1; old_head.borrow().val }) } /* スタックトップの要素にアクセス */ pub fn peek(&self) -> Option<&Rc>>> { self.stack_peek.as_ref() } /* List を Array に変換して返す */ pub fn to_array(&self) -> Vec { fn _to_array(head: Option<&Rc>>>) -> Vec { if let Some(node) = head { let mut nums = _to_array(node.borrow().next.as_ref()); nums.push(node.borrow().val); return nums; } return Vec::new(); } _to_array(self.peek()) } } /* Driver Code */ fn main() { /* スタックを初期化 */ let mut stack = LinkedListStack::new(); /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("スタック stack = "); print_util::print_array(&stack.to_array()); /* スタックトップの要素にアクセス */ let peek = stack.peek().unwrap().borrow().val; print!("\nスタックトップ要素 peek = {}", peek); /* 要素をポップ */ let pop = stack.pop().unwrap(); print!("\n取り出した要素 pop = {}、取り出し後 stack = ", pop); print_util::print_array(&stack.to_array()); /* スタックの長さを取得 */ let size = stack.size(); print!("\nスタックの長さ size = {}", size); /* 空かどうかを判定 */ let is_empty = stack.is_empty(); print!("\nスタックが空かどうか = {}", is_empty); } ================================================ FILE: ja/codes/rust/chapter_stack_and_queue/queue.rs ================================================ /* * File: queue.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; use std::collections::VecDeque; /* Driver Code */ pub fn main() { // キューを初期化 let mut queue: VecDeque = VecDeque::new(); // 要素をエンキュー queue.push_back(1); queue.push_back(3); queue.push_back(2); queue.push_back(5); queue.push_back(4); print!("キュー queue = "); print_util::print_queue(&queue); // キュー先頭の要素にアクセス let peek = queue.front().unwrap(); println!("\n先頭要素 peek = {peek}"); // 要素をデキュー let pop = queue.pop_front().unwrap(); print!("取り出した要素 pop = {pop}、取り出し後 queue = "); print_util::print_queue(&queue); // キューの長さを取得 let size = queue.len(); print!("\nキューの長さ size = {size}"); // キューが空かどうかを判定 let is_empty = queue.is_empty(); print!("\nキューが空かどうか = {is_empty}"); } ================================================ FILE: ja/codes/rust/chapter_stack_and_queue/stack.rs ================================================ /* * File: stack.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Driver Code */ pub fn main() { // スタックを初期化する // Rust では、Vec をスタックとして使うことが推奨される let mut stack: Vec = Vec::new(); // 要素をプッシュ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("スタック stack = "); print_util::print_array(&stack); // スタックトップの要素にアクセス let peek = stack.last().unwrap(); print!("\nスタックトップ要素 peek = {peek}"); // 要素をポップ let pop = stack.pop().unwrap(); print!("\n取り出した要素 pop = {pop}、取り出し後 stack = "); print_util::print_array(&stack); // スタックの長さを取得 let size = stack.len(); print!("\nスタックの長さ size = {size}"); // スタックが空かどうかを判定 let is_empty = stack.is_empty(); print!("\nスタックが空かどうか = {is_empty}"); } ================================================ FILE: ja/codes/rust/chapter_tree/array_binary_tree.rs ================================================ /* * File: array_binary_tree.rs * Created Time: 2023-07-25 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::{print_util, tree_node}; /* 配列表現による二分木クラス */ struct ArrayBinaryTree { tree: Vec>, } impl ArrayBinaryTree { /* コンストラクタ */ fn new(arr: Vec>) -> Self { Self { tree: arr } } /* リスト容量 */ fn size(&self) -> i32 { self.tree.len() as i32 } /* インデックス i のノードの値を取得 */ fn val(&self, i: i32) -> Option { // インデックスが範囲外なら、空きを表す None を返す if i < 0 || i >= self.size() { None } else { self.tree[i as usize] } } /* インデックス i のノードの左子ノードのインデックスを取得 */ fn left(&self, i: i32) -> i32 { 2 * i + 1 } /* インデックス i のノードの右子ノードのインデックスを取得 */ fn right(&self, i: i32) -> i32 { 2 * i + 2 } /* インデックス i のノードの親ノードのインデックスを取得 */ fn parent(&self, i: i32) -> i32 { (i - 1) / 2 } /* レベル順走査 */ fn level_order(&self) -> Vec { self.tree.iter().filter_map(|&x| x).collect() } /* 深さ優先探索 */ fn dfs(&self, i: i32, order: &'static str, res: &mut Vec) { if self.val(i).is_none() { return; } let val = self.val(i).unwrap(); // 先行順走査 if order == "pre" { res.push(val); } self.dfs(self.left(i), order, res); // 中順走査 if order == "in" { res.push(val); } self.dfs(self.right(i), order, res); // 後順走査 if order == "post" { res.push(val); } } /* 先行順走査 */ fn pre_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "pre", &mut res); res } /* 中順走査 */ fn in_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "in", &mut res); res } /* 後順走査 */ fn post_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "post", &mut res); res } } /* Driver Code */ fn main() { // 二分木を初期化 // ここでは、配列から直接二分木を生成する関数を利用する let arr = vec![ Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15), ]; let root = tree_node::vec_to_tree(arr.clone()).unwrap(); println!("\n二分木を初期化\n"); println!("二分木の配列表現:"); println!( "[{}]", arr.iter() .map(|&val| if let Some(val) = val { format!("{val}") } else { "null".to_string() }) .collect::>() .join(", ") ); println!("二分木の連結リスト表現:"); print_util::print_tree(&root); // 配列表現による二分木クラス let abt = ArrayBinaryTree::new(arr); // ノードにアクセス let i = 1; let l = abt.left(i); let r = abt.right(i); let p = abt.parent(i); println!( "\n現在のノードのインデックスは {}、値は {}", i, if let Some(val) = abt.val(i) { format!("{val}") } else { "null".to_string() } ); println!( "その左子ノードのインデックスは {}、値は {}", l, if let Some(val) = abt.val(l) { format!("{val}") } else { "null".to_string() } ); println!( "その右子ノードのインデックスは {}、値は {}", r, if let Some(val) = abt.val(r) { format!("{val}") } else { "null".to_string() } ); println!( "その親ノードのインデックスは {}、値は {}", p, if let Some(val) = abt.val(p) { format!("{val}") } else { "null".to_string() } ); // 木を走査 let mut res = abt.level_order(); println!("\nレベル順走査:{:?}", res); res = abt.pre_order(); println!("前順走査:{:?}", res); res = abt.in_order(); println!("中順走査:{:?}", res); res = abt.post_order(); println!("後順走査は:{:?}", res); } ================================================ FILE: ja/codes/rust/chapter_tree/avl_tree.rs ================================================ /* * File: avl_tree.rs * Created Time: 2023-07-14 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::{print_util, TreeNode}; use std::cell::RefCell; use std::cmp::Ordering; use std::rc::Rc; type OptionTreeNodeRc = Option>>; /* AVL 木 */ struct AVLTree { root: OptionTreeNodeRc, // 根ノード } impl AVLTree { /* コンストラクタ */ fn new() -> Self { Self { root: None } } /* ノードの高さを取得 */ fn height(node: OptionTreeNodeRc) -> i32 { // 空ノードの高さは -1、葉ノードの高さは 0 match node { Some(node) => node.borrow().height, None => -1, } } /* ノードの高さを更新する */ fn update_height(node: OptionTreeNodeRc) { if let Some(node) = node { let left = node.borrow().left.clone(); let right = node.borrow().right.clone(); // ノードの高さは最も高い部分木の高さ + 1 に等しい node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1; } } /* 平衡係数を取得 */ fn balance_factor(node: OptionTreeNodeRc) -> i32 { match node { // 空ノードの平衡係数は 0 None => 0, // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ Some(node) => { Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone()) } } } /* 右回転 */ fn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { match node { Some(node) => { let child = node.borrow().left.clone().unwrap(); let grand_child = child.borrow().right.clone(); // child を支点として node を右回転させる child.borrow_mut().right = Some(node.clone()); node.borrow_mut().left = grand_child; // ノードの高さを更新する Self::update_height(Some(node)); Self::update_height(Some(child.clone())); // 回転後の部分木の根ノードを返す Some(child) } None => None, } } /* 左回転 */ fn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { match node { Some(node) => { let child = node.borrow().right.clone().unwrap(); let grand_child = child.borrow().left.clone(); // child を支点として node を左回転させる child.borrow_mut().left = Some(node.clone()); node.borrow_mut().right = grand_child; // ノードの高さを更新する Self::update_height(Some(node)); Self::update_height(Some(child.clone())); // 回転後の部分木の根ノードを返す Some(child) } None => None, } } /* 回転操作を行い、この部分木の平衡を回復する */ fn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { // ノード node の平衡係数を取得 let balance_factor = Self::balance_factor(node.clone()); // 左に偏った木 if balance_factor > 1 { let node = node.unwrap(); if Self::balance_factor(node.borrow().left.clone()) >= 0 { // 右回転 Self::right_rotate(Some(node)) } else { // 左回転してから右回転 let left = node.borrow().left.clone(); node.borrow_mut().left = Self::left_rotate(left); Self::right_rotate(Some(node)) } } // 右に偏った木 else if balance_factor < -1 { let node = node.unwrap(); if Self::balance_factor(node.borrow().right.clone()) <= 0 { // 左回転 Self::left_rotate(Some(node)) } else { // 右回転してから左回転 let right = node.borrow().right.clone(); node.borrow_mut().right = Self::right_rotate(right); Self::left_rotate(Some(node)) } } else { // 平衡木なので回転不要、そのまま返す node } } /* ノードを挿入 */ fn insert(&mut self, val: i32) { self.root = Self::insert_helper(self.root.clone(), val); } /* ノードを再帰的に挿入する(補助メソッド) */ fn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { match node { Some(mut node) => { /* 1. 挿入位置を探索してノードを挿入 */ match { let node_val = node.borrow().val; node_val } .cmp(&val) { Ordering::Greater => { let left = node.borrow().left.clone(); node.borrow_mut().left = Self::insert_helper(left, val); } Ordering::Less => { let right = node.borrow().right.clone(); node.borrow_mut().right = Self::insert_helper(right, val); } Ordering::Equal => { return Some(node); // 重複ノードは挿入せず、そのまま返す } } Self::update_height(Some(node.clone())); // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = Self::rotate(Some(node)).unwrap(); // 部分木の根ノードを返す Some(node) } None => Some(TreeNode::new(val)), } } /* ノードを削除 */ fn remove(&self, val: i32) { Self::remove_helper(self.root.clone(), val); } /* ノードを再帰的に削除する(補助メソッド) */ fn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { match node { Some(mut node) => { /* 1. ノードを探索して削除 */ if val < node.borrow().val { let left = node.borrow().left.clone(); node.borrow_mut().left = Self::remove_helper(left, val); } else if val > node.borrow().val { let right = node.borrow().right.clone(); node.borrow_mut().right = Self::remove_helper(right, val); } else if node.borrow().left.is_none() || node.borrow().right.is_none() { let child = if node.borrow().left.is_some() { node.borrow().left.clone() } else { node.borrow().right.clone() }; match child { // 子ノード数 = 0 の場合、node をそのまま削除して返す None => { return None; } // 子ノード数 = 1 の場合、node をそのまま削除する Some(child) => node = child, } } else { // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える let mut temp = node.borrow().right.clone().unwrap(); loop { let temp_left = temp.borrow().left.clone(); if temp_left.is_none() { break; } temp = temp_left.unwrap(); } let right = node.borrow().right.clone(); node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val); node.borrow_mut().val = temp.borrow().val; } Self::update_height(Some(node.clone())); // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = Self::rotate(Some(node)).unwrap(); // 部分木の根ノードを返す Some(node) } None => None, } } /* ノードを探索 */ fn search(&self, val: i32) -> OptionTreeNodeRc { let mut cur = self.root.clone(); // ループで探索し、葉ノードを越えたら抜ける while let Some(current) = cur.clone() { match current.borrow().val.cmp(&val) { // 目標ノードは cur の右部分木にある Ordering::Less => { cur = current.borrow().right.clone(); } // 目標ノードは cur の左部分木にある Ordering::Greater => { cur = current.borrow().left.clone(); } // 目標ノードが見つかったらループを抜ける Ordering::Equal => { break; } } } // 目標ノードを返す cur } } /* Driver Code */ fn main() { fn test_insert(tree: &mut AVLTree, val: i32) { tree.insert(val); println!("\nノード {} を挿入した後、AVL 木は", val); print_util::print_tree(&tree.root.clone().unwrap()); } fn test_remove(tree: &mut AVLTree, val: i32) { tree.remove(val); println!("\nノード {} を削除した後、AVL 木は", val); print_util::print_tree(&tree.root.clone().unwrap()); } /* 空の AVL 木を初期化する */ let mut avl_tree = AVLTree::new(); /* ノードを挿入 */ // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい test_insert(&mut avl_tree, 1); test_insert(&mut avl_tree, 2); test_insert(&mut avl_tree, 3); test_insert(&mut avl_tree, 4); test_insert(&mut avl_tree, 5); test_insert(&mut avl_tree, 8); test_insert(&mut avl_tree, 7); test_insert(&mut avl_tree, 9); test_insert(&mut avl_tree, 10); test_insert(&mut avl_tree, 6); /* 重複ノードを挿入する */ test_insert(&mut avl_tree, 7); /* ノードを削除 */ // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい test_remove(&mut avl_tree, 8); // 次数 0 のノードを削除する test_remove(&mut avl_tree, 5); // 次数 1 のノードを削除する test_remove(&mut avl_tree, 4); // 次数 2 のノードを削除する /* ノードを検索 */ let node = avl_tree.search(7); if let Some(node) = node { println!( "\n見つかったノードオブジェクトは {:?}、ノードの値 = {}", &*node.borrow(), node.borrow().val ); } } ================================================ FILE: ja/codes/rust/chapter_tree/binary_search_tree.rs ================================================ /* * File: binary_search_tree.rs * Created Time: 2023-04-20 * Author: xBLACKICEx (xBLACKICE@outlook.com)、night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::cell::RefCell; use std::cmp::Ordering; use std::rc::Rc; use hello_algo_rust::include::TreeNode; type OptionTreeNodeRc = Option>>; /* 二分探索木 */ pub struct BinarySearchTree { root: OptionTreeNodeRc, } impl BinarySearchTree { /* コンストラクタ */ pub fn new() -> Self { // 空の木を初期化する Self { root: None } } /* 二分木の根ノードを取得 */ pub fn get_root(&self) -> OptionTreeNodeRc { self.root.clone() } /* ノードを探索 */ pub fn search(&self, num: i32) -> OptionTreeNodeRc { let mut cur = self.root.clone(); // ループで探索し、葉ノードを越えたら抜ける while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // 目標ノードは cur の右部分木にある Ordering::Greater => cur = node.borrow().right.clone(), // 目標ノードは cur の左部分木にある Ordering::Less => cur = node.borrow().left.clone(), // 目標ノードが見つかったらループを抜ける Ordering::Equal => break, } } // 目標ノードを返す cur } /* ノードを挿入 */ pub fn insert(&mut self, num: i32) { // 木が空なら、根ノードを初期化する if self.root.is_none() { self.root = Some(TreeNode::new(num)); return; } let mut cur = self.root.clone(); let mut pre = None; // ループで探索し、葉ノードを越えたら抜ける while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // 重複ノードが見つかったら、直ちに返す Ordering::Equal => return, // 挿入位置は cur の右部分木にある Ordering::Greater => { pre = cur.clone(); cur = node.borrow().right.clone(); } // 挿入位置は cur の左部分木にある Ordering::Less => { pre = cur.clone(); cur = node.borrow().left.clone(); } } } // ノードを挿入 let pre = pre.unwrap(); let node = Some(TreeNode::new(num)); if num > pre.borrow().val { pre.borrow_mut().right = node; } else { pre.borrow_mut().left = node; } } /* ノードを削除 */ pub fn remove(&mut self, num: i32) { // 木が空なら、そのまま早期リターンする if self.root.is_none() { return; } let mut cur = self.root.clone(); let mut pre = None; // ループで探索し、葉ノードを越えたら抜ける while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // 削除対象のノードが見つかったら、ループを抜ける Ordering::Equal => break, // 削除対象ノードは cur の右部分木にある Ordering::Greater => { pre = cur.clone(); cur = node.borrow().right.clone(); } // 削除対象ノードは cur の左部分木にある Ordering::Less => { pre = cur.clone(); cur = node.borrow().left.clone(); } } } // 削除対象ノードがなければそのまま返す if cur.is_none() { return; } let cur = cur.unwrap(); let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone()); match (left_child.clone(), right_child.clone()) { // 子ノード数 = 0 or 1 (None, None) | (Some(_), None) | (None, Some(_)) => { // 子ノード数 = 0 / 1 のとき、child = nullptr / その子ノード let child = left_child.or(right_child); let pre = pre.unwrap(); // ノード cur を削除する if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) { let left = pre.borrow().left.clone(); if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) { pre.borrow_mut().left = child; } else { pre.borrow_mut().right = child; } } else { // 削除ノードが根ノードなら、根ノードを再設定 self.root = child; } } // 子ノード数 = 2 (Some(_), Some(_)) => { // 中順走査における cur の次ノードを取得 let mut tmp = cur.borrow().right.clone(); while let Some(node) = tmp.clone() { if node.borrow().left.is_some() { tmp = node.borrow().left.clone(); } else { break; } } let tmp_val = tmp.unwrap().borrow().val; // ノード tmp を再帰的に削除 self.remove(tmp_val); // tmp で cur を上書きする cur.borrow_mut().val = tmp_val; } } } } /* Driver Code */ fn main() { /* 二分探索木を初期化 */ let mut bst = BinarySearchTree::new(); // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for &num in &nums { bst.insert(num); } println!("\n初期化した二分木は\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); /* ノードを検索 */ let node = bst.search(7); println!( "\n見つかったノードオブジェクトは {:?}、ノードの値 = {}", node.clone().unwrap(), node.clone().unwrap().borrow().val ); /* ノードを挿入 */ bst.insert(16); println!("\nノード 16 を挿入した後、二分木は\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); /* ノードを削除 */ bst.remove(1); println!("\nノード 1 を削除した後、二分木は\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); bst.remove(2); println!("\nノード 2 を削除した後、二分木は\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); bst.remove(4); println!("\nノード 4 を削除した後、二分木は\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); } ================================================ FILE: ja/codes/rust/chapter_tree/binary_tree.rs ================================================ /** * File: binary_tree.rs * Created Time: 2023-02-27 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use std::rc::Rc; use hello_algo_rust::include::{print_util, TreeNode}; /* Driver Code */ fn main() { /* 二分木を初期化 */ // ノードを初期化 let n1 = TreeNode::new(1); let n2 = TreeNode::new(2); let n3 = TreeNode::new(3); let n4 = TreeNode::new(4); let n5 = TreeNode::new(5); // ノード間の参照(ポインタ)を構築する n1.borrow_mut().left = Some(Rc::clone(&n2)); n1.borrow_mut().right = Some(Rc::clone(&n3)); n2.borrow_mut().left = Some(Rc::clone(&n4)); n2.borrow_mut().right = Some(Rc::clone(&n5)); println!("\n二分木を初期化\n"); print_util::print_tree(&n1); // ノードの挿入と削除 let p = TreeNode::new(0); // n1 -> n2 の間にノード P を挿入 p.borrow_mut().left = Some(Rc::clone(&n2)); n1.borrow_mut().left = Some(Rc::clone(&p)); println!("\nノード P を挿入した後\n"); print_util::print_tree(&n1); // ノード P を削除 drop(p); n1.borrow_mut().left = Some(Rc::clone(&n2)); println!("\nノード P を削除した後\n"); print_util::print_tree(&n1); } ================================================ FILE: ja/codes/rust/chapter_tree/binary_tree_bfs.rs ================================================ /* * File: binary_tree_bfs.rs * Created Time: 2023-04-07 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use hello_algo_rust::op_vec; use std::collections::VecDeque; use std::{cell::RefCell, rc::Rc}; /* レベル順走査 */ fn level_order(root: &Rc>) -> Vec { // キューを初期化し、ルートノードを追加する let mut que = VecDeque::new(); que.push_back(root.clone()); // 走査順序を保存するためのリストを初期化する let mut vec = Vec::new(); while let Some(node) = que.pop_front() { // デキュー vec.push(node.borrow().val); // ノードの値を保存する if let Some(left) = node.borrow().left.as_ref() { que.push_back(left.clone()); // 左子ノードをキューに追加 } if let Some(right) = node.borrow().right.as_ref() { que.push_back(right.clone()); // 右子ノードをキューに追加 }; } vec } /* Driver Code */ fn main() { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]).unwrap(); println!("二分木を初期化\n"); print_util::print_tree(&root); /* レベル順走査 */ let vec = level_order(&root); print!("\nレベル順走査のノード出力シーケンス = {:?}", vec); } ================================================ FILE: ja/codes/rust/chapter_tree/binary_tree_dfs.rs ================================================ /* * File: binary_tree_dfs.rs * Created Time: 2023-04-06 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use hello_algo_rust::op_vec; use std::cell::RefCell; use std::rc::Rc; /* 先行順走査 */ fn pre_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // 訪問順序:根ノード -> 左部分木 -> 右部分木 let node = node.borrow(); res.push(node.val); dfs(node.left.as_ref(), res); dfs(node.right.as_ref(), res); } } dfs(root, &mut result); result } /* 中順走査 */ fn in_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 let node = node.borrow(); dfs(node.left.as_ref(), res); res.push(node.val); dfs(node.right.as_ref(), res); } } dfs(root, &mut result); result } /* 後順走査 */ fn post_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード let node = node.borrow(); dfs(node.left.as_ref(), res); dfs(node.right.as_ref(), res); res.push(node.val); } } dfs(root, &mut result); result } /* Driver Code */ fn main() { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]); println!("二分木を初期化\n"); print_util::print_tree(root.as_ref().unwrap()); /* 先行順走査 */ let vec = pre_order(root.as_ref()); println!("\n前順走査のノード出力シーケンス = {:?}", vec); /* 中順走査 */ let vec = in_order(root.as_ref()); println!("\n中順走査のノード出力シーケンス = {:?}", vec); /* 後順走査 */ let vec = post_order(root.as_ref()); print!("\n後順走査のノード出力シーケンス = {:?}", vec); } ================================================ FILE: ja/codes/rust/src/include/list_node.rs ================================================ /* * File: list_node.rs * Created Time: 2023-03-05 * Author: codingonion (coderonion@gmail.com), rongyi (hiarongyi@gmail.com) */ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; #[derive(Debug)] pub struct ListNode { pub val: T, pub next: Option>>>, } impl ListNode { pub fn new(val: T) -> Rc>> { Rc::new(RefCell::new(ListNode { val, next: None })) } /* 配列をデシリアライズして連結リストに変換する */ pub fn arr_to_linked_list(array: &[T]) -> Option>>> where T: Copy + Clone, { let mut head = None; // insert in reverse order for item in array.iter().rev() { let node = Rc::new(RefCell::new(ListNode { val: *item, next: head.take(), })); head = Some(node); } head } /* 連結リストをハッシュテーブルに変換 */ pub fn linked_list_to_hashmap( linked_list: Option>>>, ) -> HashMap>>> where T: std::hash::Hash + Eq + Copy + Clone, { let mut hashmap = HashMap::new(); let mut node = linked_list; while let Some(cur) = node { let borrow = cur.borrow(); hashmap.insert(borrow.val.clone(), cur.clone()); node = borrow.next.clone(); } hashmap } } ================================================ FILE: ja/codes/rust/src/include/mod.rs ================================================ /* * File: include.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICE@outlook.com) */ pub mod list_node; pub mod print_util; pub mod tree_node; pub mod vertex; // rexport to include pub use list_node::*; pub use print_util::*; pub use tree_node::*; pub use vertex::*; ================================================ FILE: ja/codes/rust/src/include/print_util.rs ================================================ /* * File: print_util.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use std::cell::{Cell, RefCell}; use std::fmt::Display; use std::collections::{HashMap, VecDeque}; use std::rc::Rc; use super::list_node::ListNode; use super::tree_node::{TreeNode, vec_to_tree}; struct Trunk<'a, 'b> { prev: Option<&'a Trunk<'a, 'b>>, str: Cell<&'b str>, } /* 配列を出力する */ pub fn print_array(nums: &[T]) { print!("["); if nums.len() > 0 { for (i, num) in nums.iter().enumerate() { print!("{}{}", num, if i == nums.len() - 1 {"]"} else {", "} ); } } else { print!("]"); } } /* ハッシュテーブルを出力 */ pub fn print_hash_map(map: &HashMap) { for (key, value) in map { println!("{key} -> {value}"); } } /* キューを出力(双方向キュー) */ pub fn print_queue(queue: &VecDeque) { print!("["); let iter = queue.iter(); for (i, data) in iter.enumerate() { print!("{}{}", data, if i == queue.len() - 1 {"]"} else {", "} ); } } /* 連結リストを出力 */ pub fn print_linked_list(head: &Rc>>) { print!("{}{}", head.borrow().val, if head.borrow().next.is_none() {"\n"} else {" -> "}); if let Some(node) = &head.borrow().next { return print_linked_list(node); } } /* 二分木を出力 */ pub fn print_tree(root: &Rc>) { _print_tree(Some(root), None, false); } /* 二分木を出力 */ fn _print_tree(root: Option<&Rc>>, prev: Option<&Trunk>, is_right: bool) { if let Some(node) = root { let mut prev_str = " "; let trunk = Trunk { prev, str: Cell::new(prev_str) }; _print_tree(node.borrow().right.as_ref(), Some(&trunk), true); if prev.is_none() { trunk.str.set("———"); } else if is_right { trunk.str.set("/———"); prev_str = " |"; } else { trunk.str.set("\\———"); prev.as_ref().unwrap().str.set(prev_str); } show_trunks(Some(&trunk)); println!(" {}", node.borrow().val); if let Some(prev) = prev { prev.str.set(prev_str); } trunk.str.set(" |"); _print_tree(node.borrow().left.as_ref(), Some(&trunk), false); } } fn show_trunks(trunk: Option<&Trunk>) { if let Some(trunk) = trunk { show_trunks(trunk.prev); print!("{}", trunk.str.get()); } } /* ヒープを出力 */ pub fn print_heap(heap: Vec) { println!("ヒープの配列表現:{:?}", heap); println!("ヒープの木構造表現:"); if let Some(root) = vec_to_tree(heap.into_iter().map(|val| Some(val)).collect()) { print_tree(&root); } } ================================================ FILE: ja/codes/rust/src/include/tree_node.rs ================================================ /* * File: tree_node.rs * Created Time: 2023-02-27 * Author: xBLACKICEx (xBLACKICE@outlook.com), night-cruise (2586447362@qq.com) */ use std::cell::RefCell; use std::rc::Rc; /* 二分木ノード型 */ #[derive(Debug)] pub struct TreeNode { pub val: i32, pub height: i32, pub parent: Option>>, pub left: Option>>, pub right: Option>>, } impl TreeNode { /* コンストラクタ */ pub fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, height: 0, parent: None, left: None, right: None, })) } } #[macro_export] macro_rules! op_vec { ( $( $x:expr ),* ) => { vec![ $(Option::from($x)),* ] }; } // シリアライズの符号化規則は以下を参照: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二分木の配列表現: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // 二分木の連結リスト表現: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* リストを二分木にデシリアライズする: 再帰 */ fn vec_to_tree_dfs(arr: &[Option], i: usize) -> Option>> { if i >= arr.len() || arr[i].is_none() { return None; } let root = TreeNode::new(arr[i].unwrap()); root.borrow_mut().left = vec_to_tree_dfs(arr, 2 * i + 1); root.borrow_mut().right = vec_to_tree_dfs(arr, 2 * i + 2); Some(root) } /* リストを二分木にデシリアライズする */ pub fn vec_to_tree(arr: Vec>) -> Option>> { vec_to_tree_dfs(&arr, 0) } /* 二分木をリストにシリアライズする: 再帰 */ fn tree_to_vec_dfs(root: Option<&Rc>>, i: usize, res: &mut Vec>) { if let Some(root) = root { // i + 1 is the minimum valid size to access index i while res.len() < i + 1 { res.push(None); } res[i] = Some(root.borrow().val); tree_to_vec_dfs(root.borrow().left.as_ref(), 2 * i + 1, res); tree_to_vec_dfs(root.borrow().right.as_ref(), 2 * i + 2, res); } } /* 二分木をリストにシリアライズする */ pub fn tree_to_vec(root: Option>>) -> Vec> { let mut res = vec![]; tree_to_vec_dfs(root.as_ref(), 0, &mut res); res } ================================================ FILE: ja/codes/rust/src/include/vertex.rs ================================================ /* * File: vertex.rs * Created Time: 2023-07-13 * Author: night-cruise (2586447362@qq.com) */ /* 頂点の型 */ #[derive(Copy, Clone, Hash, PartialEq, Eq)] pub struct Vertex { pub val: i32, } impl From for Vertex { fn from(value: i32) -> Self { Self { val: value } } } /* 値リスト vals を入力し、頂点リスト vets を返す */ pub fn vals_to_vets(vals: Vec) -> Vec { vals.into_iter().map(|val| val.into()).collect() } /* 頂点リスト vets を入力し、値リスト vals を返す */ pub fn vets_to_vals(vets: Vec) -> Vec { vets.into_iter().map(|vet| vet.val).collect() } ================================================ FILE: ja/codes/rust/src/lib.rs ================================================ pub mod include; ================================================ FILE: ja/codes/swift/.gitignore ================================================ # Created by https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager # Edit at https://www.toptal.com/developers/gitignore?templates=objective-c,swift,swiftpackagemanager ### Objective-C ### # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## User settings xcuserdata/ ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint *.xccheckout ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ DerivedData/ *.moved-aside *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 ## Obj-C/Swift specific *.hmap ## App packaging *.ipa *.dSYM.zip *.dSYM # CocoaPods # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build/ # fastlane # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output # Code Injection # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ ### Objective-C Patch ### ### Swift ### # Xcode # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins # Package.resolved # *.xcodeproj # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project # .swiftpm .build/ # CocoaPods # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts # Accio dependency management Dependencies/ .accio/ # fastlane # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control # Code Injection # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode ### SwiftPackageManager ### Packages xcuserdata *.xcodeproj # End of https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager ================================================ FILE: ja/codes/swift/Package.resolved ================================================ { "pins" : [ { "identity" : "swift-collections", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { "branch" : "release/1.1", "revision" : "4a1d92ba85027010d2c528c05576cde9a362254b" } } ], "version" : 2 } ================================================ FILE: ja/codes/swift/Package.swift ================================================ // swift-tools-version: 5.7 import PackageDescription let package = Package( name: "HelloAlgo", products: [ // chapter_computational_complexity .executable(name: "iteration", targets: ["iteration"]), .executable(name: "recursion", targets: ["recursion"]), .executable(name: "time_complexity", targets: ["time_complexity"]), .executable(name: "worst_best_time_complexity", targets: ["worst_best_time_complexity"]), .executable(name: "space_complexity", targets: ["space_complexity"]), // chapter_array_and_linkedlist .executable(name: "array", targets: ["array"]), .executable(name: "linked_list", targets: ["linked_list"]), .executable(name: "list", targets: ["list"]), .executable(name: "my_list", targets: ["my_list"]), // chapter_stack_and_queue .executable(name: "stack", targets: ["stack"]), .executable(name: "linkedlist_stack", targets: ["linkedlist_stack"]), .executable(name: "array_stack", targets: ["array_stack"]), .executable(name: "queue", targets: ["queue"]), .executable(name: "linkedlist_queue", targets: ["linkedlist_queue"]), .executable(name: "array_queue", targets: ["array_queue"]), .executable(name: "deque", targets: ["deque"]), .executable(name: "linkedlist_deque", targets: ["linkedlist_deque"]), .executable(name: "array_deque", targets: ["array_deque"]), // chapter_hashing .executable(name: "hash_map", targets: ["hash_map"]), .executable(name: "array_hash_map", targets: ["array_hash_map"]), .executable(name: "hash_map_chaining", targets: ["hash_map_chaining"]), .executable(name: "hash_map_open_addressing", targets: ["hash_map_open_addressing"]), .executable(name: "simple_hash", targets: ["simple_hash"]), .executable(name: "built_in_hash", targets: ["built_in_hash"]), // chapter_tree .executable(name: "binary_tree", targets: ["binary_tree"]), .executable(name: "binary_tree_bfs", targets: ["binary_tree_bfs"]), .executable(name: "binary_tree_dfs", targets: ["binary_tree_dfs"]), .executable(name: "array_binary_tree", targets: ["array_binary_tree"]), .executable(name: "binary_search_tree", targets: ["binary_search_tree"]), .executable(name: "avl_tree", targets: ["avl_tree"]), // chapter_heap .executable(name: "heap", targets: ["heap"]), .executable(name: "my_heap", targets: ["my_heap"]), .executable(name: "top_k", targets: ["top_k"]), // chapter_graph .executable(name: "graph_adjacency_matrix", targets: ["graph_adjacency_matrix"]), .executable(name: "graph_adjacency_list", targets: ["graph_adjacency_list"]), .executable(name: "graph_bfs", targets: ["graph_bfs"]), .executable(name: "graph_dfs", targets: ["graph_dfs"]), // chapter_searching .executable(name: "binary_search", targets: ["binary_search"]), .executable(name: "binary_search_insertion", targets: ["binary_search_insertion"]), .executable(name: "binary_search_edge", targets: ["binary_search_edge"]), .executable(name: "two_sum", targets: ["two_sum"]), .executable(name: "linear_search", targets: ["linear_search"]), .executable(name: "hashing_search", targets: ["hashing_search"]), // chapter_sorting .executable(name: "selection_sort", targets: ["selection_sort"]), .executable(name: "bubble_sort", targets: ["bubble_sort"]), .executable(name: "insertion_sort", targets: ["insertion_sort"]), .executable(name: "quick_sort", targets: ["quick_sort"]), .executable(name: "merge_sort", targets: ["merge_sort"]), .executable(name: "heap_sort", targets: ["heap_sort"]), .executable(name: "bucket_sort", targets: ["bucket_sort"]), .executable(name: "counting_sort", targets: ["counting_sort"]), .executable(name: "radix_sort", targets: ["radix_sort"]), // chapter_divide_and_conquer .executable(name: "binary_search_recur", targets: ["binary_search_recur"]), .executable(name: "build_tree", targets: ["build_tree"]), .executable(name: "hanota", targets: ["hanota"]), // chapter_backtracking .executable(name: "preorder_traversal_i_compact", targets: ["preorder_traversal_i_compact"]), .executable(name: "preorder_traversal_ii_compact", targets: ["preorder_traversal_ii_compact"]), .executable(name: "preorder_traversal_iii_compact", targets: ["preorder_traversal_iii_compact"]), .executable(name: "preorder_traversal_iii_template", targets: ["preorder_traversal_iii_template"]), .executable(name: "permutations_i", targets: ["permutations_i"]), .executable(name: "permutations_ii", targets: ["permutations_ii"]), .executable(name: "subset_sum_i_naive", targets: ["subset_sum_i_naive"]), .executable(name: "subset_sum_i", targets: ["subset_sum_i"]), .executable(name: "subset_sum_ii", targets: ["subset_sum_ii"]), .executable(name: "n_queens", targets: ["n_queens"]), // chapter_dynamic_programming .executable(name: "climbing_stairs_backtrack", targets: ["climbing_stairs_backtrack"]), .executable(name: "climbing_stairs_dfs", targets: ["climbing_stairs_dfs"]), .executable(name: "climbing_stairs_dfs_mem", targets: ["climbing_stairs_dfs_mem"]), .executable(name: "climbing_stairs_dp", targets: ["climbing_stairs_dp"]), .executable(name: "min_cost_climbing_stairs_dp", targets: ["min_cost_climbing_stairs_dp"]), .executable(name: "climbing_stairs_constraint_dp", targets: ["climbing_stairs_constraint_dp"]), .executable(name: "min_path_sum", targets: ["min_path_sum"]), .executable(name: "knapsack", targets: ["knapsack"]), .executable(name: "unbounded_knapsack", targets: ["unbounded_knapsack"]), .executable(name: "coin_change", targets: ["coin_change"]), .executable(name: "coin_change_ii", targets: ["coin_change_ii"]), .executable(name: "edit_distance", targets: ["edit_distance"]), // chapter_greedy .executable(name: "coin_change_greedy", targets: ["coin_change_greedy"]), .executable(name: "fractional_knapsack", targets: ["fractional_knapsack"]), .executable(name: "max_capacity", targets: ["max_capacity"]), .executable(name: "max_product_cutting", targets: ["max_product_cutting"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-collections", branch: "release/1.1"), ], targets: [ // helper .target(name: "utils", path: "utils"), .target(name: "graph_adjacency_list_target", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list_target.swift"], swiftSettings: [.define("TARGET")]), .target(name: "binary_search_insertion_target", path: "chapter_searching", sources: ["binary_search_insertion_target.swift"], swiftSettings: [.define("TARGET")]), // chapter_computational_complexity .executableTarget(name: "iteration", path: "chapter_computational_complexity", sources: ["iteration.swift"]), .executableTarget(name: "recursion", path: "chapter_computational_complexity", sources: ["recursion.swift"]), .executableTarget(name: "time_complexity", path: "chapter_computational_complexity", sources: ["time_complexity.swift"]), .executableTarget(name: "worst_best_time_complexity", path: "chapter_computational_complexity", sources: ["worst_best_time_complexity.swift"]), .executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]), // chapter_array_and_linkedlist .executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]), .executableTarget(name: "linked_list", dependencies: ["utils"], path: "chapter_array_and_linkedlist", sources: ["linked_list.swift"]), .executableTarget(name: "list", path: "chapter_array_and_linkedlist", sources: ["list.swift"]), .executableTarget(name: "my_list", path: "chapter_array_and_linkedlist", sources: ["my_list.swift"]), // chapter_stack_and_queue .executableTarget(name: "stack", path: "chapter_stack_and_queue", sources: ["stack.swift"]), .executableTarget(name: "linkedlist_stack", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_stack.swift"]), .executableTarget(name: "array_stack", path: "chapter_stack_and_queue", sources: ["array_stack.swift"]), .executableTarget(name: "queue", path: "chapter_stack_and_queue", sources: ["queue.swift"]), .executableTarget(name: "linkedlist_queue", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_queue.swift"]), .executableTarget(name: "array_queue", path: "chapter_stack_and_queue", sources: ["array_queue.swift"]), .executableTarget(name: "deque", path: "chapter_stack_and_queue", sources: ["deque.swift"]), .executableTarget(name: "linkedlist_deque", path: "chapter_stack_and_queue", sources: ["linkedlist_deque.swift"]), .executableTarget(name: "array_deque", path: "chapter_stack_and_queue", sources: ["array_deque.swift"]), // chapter_hashing .executableTarget(name: "hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map.swift"]), .executableTarget(name: "array_hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["array_hash_map.swift"]), .executableTarget(name: "hash_map_chaining", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_chaining.swift"]), .executableTarget(name: "hash_map_open_addressing", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_open_addressing.swift"]), .executableTarget(name: "simple_hash", path: "chapter_hashing", sources: ["simple_hash.swift"]), .executableTarget(name: "built_in_hash", dependencies: ["utils"], path: "chapter_hashing", sources: ["built_in_hash.swift"]), // chapter_tree .executableTarget(name: "binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree.swift"]), .executableTarget(name: "binary_tree_bfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_bfs.swift"]), .executableTarget(name: "binary_tree_dfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_dfs.swift"]), .executableTarget(name: "array_binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["array_binary_tree.swift"]), .executableTarget(name: "binary_search_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_search_tree.swift"]), .executableTarget(name: "avl_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["avl_tree.swift"]), // chapter_heap .executableTarget(name: "heap", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["heap.swift"]), .executableTarget(name: "my_heap", dependencies: ["utils"], path: "chapter_heap", sources: ["my_heap.swift"]), .executableTarget(name: "top_k", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["top_k.swift"]), // chapter_graph .executableTarget(name: "graph_adjacency_matrix", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_matrix.swift"]), .executableTarget(name: "graph_adjacency_list", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list.swift"]), .executableTarget(name: "graph_bfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_bfs.swift"]), .executableTarget(name: "graph_dfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_dfs.swift"]), // chapter_searching .executableTarget(name: "binary_search", path: "chapter_searching", sources: ["binary_search.swift"]), .executableTarget(name: "binary_search_insertion", path: "chapter_searching", sources: ["binary_search_insertion.swift"]), .executableTarget(name: "binary_search_edge", dependencies: ["binary_search_insertion_target"], path: "chapter_searching", sources: ["binary_search_edge.swift"]), .executableTarget(name: "two_sum", path: "chapter_searching", sources: ["two_sum.swift"]), .executableTarget(name: "linear_search", dependencies: ["utils"], path: "chapter_searching", sources: ["linear_search.swift"]), .executableTarget(name: "hashing_search", dependencies: ["utils"], path: "chapter_searching", sources: ["hashing_search.swift"]), // chapter_sorting .executableTarget(name: "selection_sort", path: "chapter_sorting", sources: ["selection_sort.swift"]), .executableTarget(name: "bubble_sort", path: "chapter_sorting", sources: ["bubble_sort.swift"]), .executableTarget(name: "insertion_sort", path: "chapter_sorting", sources: ["insertion_sort.swift"]), .executableTarget(name: "quick_sort", path: "chapter_sorting", sources: ["quick_sort.swift"]), .executableTarget(name: "merge_sort", path: "chapter_sorting", sources: ["merge_sort.swift"]), .executableTarget(name: "heap_sort", path: "chapter_sorting", sources: ["heap_sort.swift"]), .executableTarget(name: "bucket_sort", path: "chapter_sorting", sources: ["bucket_sort.swift"]), .executableTarget(name: "counting_sort", path: "chapter_sorting", sources: ["counting_sort.swift"]), .executableTarget(name: "radix_sort", path: "chapter_sorting", sources: ["radix_sort.swift"]), // chapter_divide_and_conquer .executableTarget(name: "binary_search_recur", path: "chapter_divide_and_conquer", sources: ["binary_search_recur.swift"]), .executableTarget(name: "build_tree", dependencies: ["utils"], path: "chapter_divide_and_conquer", sources: ["build_tree.swift"]), .executableTarget(name: "hanota", path: "chapter_divide_and_conquer", sources: ["hanota.swift"]), // chapter_backtracking .executableTarget(name: "preorder_traversal_i_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_i_compact.swift"]), .executableTarget(name: "preorder_traversal_ii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_ii_compact.swift"]), .executableTarget(name: "preorder_traversal_iii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_compact.swift"]), .executableTarget(name: "preorder_traversal_iii_template", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_template.swift"]), .executableTarget(name: "permutations_i", path: "chapter_backtracking", sources: ["permutations_i.swift"]), .executableTarget(name: "permutations_ii", path: "chapter_backtracking", sources: ["permutations_ii.swift"]), .executableTarget(name: "subset_sum_i_naive", path: "chapter_backtracking", sources: ["subset_sum_i_naive.swift"]), .executableTarget(name: "subset_sum_i", path: "chapter_backtracking", sources: ["subset_sum_i.swift"]), .executableTarget(name: "subset_sum_ii", path: "chapter_backtracking", sources: ["subset_sum_ii.swift"]), .executableTarget(name: "n_queens", path: "chapter_backtracking", sources: ["n_queens.swift"]), // chapter_dynamic_programming .executableTarget(name: "climbing_stairs_backtrack", path: "chapter_dynamic_programming", sources: ["climbing_stairs_backtrack.swift"]), .executableTarget(name: "climbing_stairs_dfs", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs.swift"]), .executableTarget(name: "climbing_stairs_dfs_mem", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs_mem.swift"]), .executableTarget(name: "climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dp.swift"]), .executableTarget(name: "min_cost_climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["min_cost_climbing_stairs_dp.swift"]), .executableTarget(name: "climbing_stairs_constraint_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_constraint_dp.swift"]), .executableTarget(name: "min_path_sum", path: "chapter_dynamic_programming", sources: ["min_path_sum.swift"]), .executableTarget(name: "knapsack", path: "chapter_dynamic_programming", sources: ["knapsack.swift"]), .executableTarget(name: "unbounded_knapsack", path: "chapter_dynamic_programming", sources: ["unbounded_knapsack.swift"]), .executableTarget(name: "coin_change", path: "chapter_dynamic_programming", sources: ["coin_change.swift"]), .executableTarget(name: "coin_change_ii", path: "chapter_dynamic_programming", sources: ["coin_change_ii.swift"]), .executableTarget(name: "edit_distance", path: "chapter_dynamic_programming", sources: ["edit_distance.swift"]), // chapter_greedy .executableTarget(name: "coin_change_greedy", path: "chapter_greedy", sources: ["coin_change_greedy.swift"]), .executableTarget(name: "fractional_knapsack", path: "chapter_greedy", sources: ["fractional_knapsack.swift"]), .executableTarget(name: "max_capacity", path: "chapter_greedy", sources: ["max_capacity.swift"]), .executableTarget(name: "max_product_cutting", path: "chapter_greedy", sources: ["max_product_cutting.swift"]), ] ) ================================================ FILE: ja/codes/swift/chapter_array_and_linkedlist/array.swift ================================================ /** * File: array.swift * Created Time: 2023-01-05 * Author: nuomi1 (nuomi1@qq.com) */ /* 要素へランダムアクセス */ func randomAccess(nums: [Int]) -> Int { // 区間 [0, nums.count) からランダムに数字を 1 つ選ぶ let randomIndex = nums.indices.randomElement()! // ランダムな要素を取得して返す let randomNum = nums[randomIndex] return randomNum } /* 配列長を拡張する */ func extend(nums: [Int], enlarge: Int) -> [Int] { // 拡張後の長さを持つ配列を初期化する var res = Array(repeating: 0, count: nums.count + enlarge) // 元の配列の全要素を新しい配列にコピー for i in nums.indices { res[i] = nums[i] } // 拡張後の新しい配列を返す return res } /* 配列の index 番目に要素 num を挿入 */ func insert(nums: inout [Int], num: Int, index: Int) { // インデックス index 以降の全要素を 1 つ後ろへ移動する for i in nums.indices.dropFirst(index).reversed() { nums[i] = nums[i - 1] } // index の要素に num を代入する nums[index] = num } /* index の要素を削除する */ func remove(nums: inout [Int], index: Int) { // インデックス index より後ろの全要素を 1 つ前へ移動する for i in nums.indices.dropFirst(index).dropLast() { nums[i] = nums[i + 1] } } /* 配列を走査 */ func traverse(nums: [Int]) { var count = 0 // インデックスで配列を走査 for i in nums.indices { count += nums[i] } // 配列要素を直接走査 for num in nums { count += num } // データのインデックスと要素を同時に走査する for (i, num) in nums.enumerated() { count += nums[i] count += num } } /* 配列内で指定要素を探す */ func find(nums: [Int], target: Int) -> Int { for i in nums.indices { if nums[i] == target { return i } } return -1 } @main enum _Array { /* Driver Code */ static func main() { /* 配列を初期化 */ let arr = Array(repeating: 0, count: 5) print("配列 arr = \(arr)") var nums = [1, 3, 2, 5, 4] print("配列 nums = \(nums)") /* ランダムアクセス */ let randomNum = randomAccess(nums: nums) print("nums からランダムな要素を取得 \(randomNum)") /* 長さを拡張 */ nums = extend(nums: nums, enlarge: 3) print("配列の長さを 8 に拡張すると、nums = \(nums)") /* 要素を挿入する */ insert(nums: &nums, num: 6, index: 3) print("インデックス 3 に数値 6 を挿入すると、nums = \(nums)") /* 要素を削除 */ remove(nums: &nums, index: 2) print("インデックス 2 の要素を削除すると、nums = \(nums)") /* 配列を走査 */ traverse(nums: nums) /* 要素を探索する */ let index = find(nums: nums, target: 3) print("nums 内で要素 3 を検索すると、インデックス = \(index)") } } ================================================ FILE: ja/codes/swift/chapter_array_and_linkedlist/linked_list.swift ================================================ /** * File: linked_list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 連結リストでノード n0 の後ろにノード P を挿入する */ func insert(n0: ListNode, P: ListNode) { let n1 = n0.next P.next = n1 n0.next = P } /* 連結リストでノード n0 の直後のノードを削除する */ func remove(n0: ListNode) { if n0.next == nil { return } // n0 -> P -> n1 let P = n0.next let n1 = P?.next n0.next = n1 } /* 連結リスト内で index 番目のノードにアクセス */ func access(head: ListNode, index: Int) -> ListNode? { var head: ListNode? = head for _ in 0 ..< index { if head == nil { return nil } head = head?.next } return head } /* 連結リストで値が target の最初のノードを探す */ func find(head: ListNode, target: Int) -> Int { var head: ListNode? = head var index = 0 while head != nil { if head?.val == target { return index } head = head?.next index += 1 } return -1 } @main enum LinkedList { /* Driver Code */ static func main() { /* 連結リストを初期化 */ // 各ノードを初期化 let n0 = ListNode(x: 1) let n1 = ListNode(x: 3) let n2 = ListNode(x: 2) let n3 = ListNode(x: 5) let n4 = ListNode(x: 4) // ノード間の参照を構築する n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 print("初期化した連結リストは") PrintUtil.printLinkedList(head: n0) /* ノードを挿入 */ insert(n0: n0, P: ListNode(x: 0)) print("ノード挿入後の連結リストは") PrintUtil.printLinkedList(head: n0) /* ノードを削除 */ remove(n0: n0) print("ノード削除後の連結リストは") PrintUtil.printLinkedList(head: n0) /* ノードにアクセス */ let node = access(head: n0, index: 3) print("連結リストのインデックス 3 にあるノードの値 = \(node!.val)") /* ノードを探索 */ let index = find(head: n0, target: 2) print("連結リスト内で値が 2 のノードのインデックス = \(index)") } } ================================================ FILE: ja/codes/swift/chapter_array_and_linkedlist/list.swift ================================================ /** * File: list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ @main enum List { /* Driver Code */ static func main() { /* リストを初期化 */ var nums = [1, 3, 2, 5, 4] print("リスト nums = \(nums)") /* 要素にアクセス */ let num = nums[1] print("インデックス 1 の要素にアクセスすると、num = \(num)") /* 要素を更新 */ nums[1] = 0 print("インデックス 1 の要素を 0 に更新すると、nums = \(nums)") /* リストを空にする */ nums.removeAll() print("リストを空にした後、nums = \(nums)") /* 末尾に要素を追加 */ nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) print("要素追加後、nums = \(nums)") /* 中間に要素を挿入 */ nums.insert(6, at: 3) print("インデックス 3 に数値 6 を挿入すると、nums = \(nums)") /* 要素を削除 */ nums.remove(at: 3) print("インデックス 3 の要素を削除すると、nums = \(nums)") /* インデックスでリストを走査 */ var count = 0 for i in nums.indices { count += nums[i] } /* リスト要素を直接走査 */ count = 0 for x in nums { count += x } /* 2 つのリストを連結する */ let nums1 = [6, 8, 7, 10, 9] nums.append(contentsOf: nums1) print("リスト nums1 を nums の後ろに連結すると、nums = \(nums)") /* リストをソート */ nums.sort() print("リストをソートした後、nums = \(nums)") } } ================================================ FILE: ja/codes/swift/chapter_array_and_linkedlist/my_list.swift ================================================ /** * File: my_list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ /* リストクラス */ class MyList { private var arr: [Int] // 配列(リスト要素を格納) private var _capacity: Int // リスト容量 private var _size: Int // リストの長さ(現在の要素数) private let extendRatio: Int // リスト拡張時の増加倍率 /* コンストラクタ */ init() { _capacity = 10 _size = 0 extendRatio = 2 arr = Array(repeating: 0, count: _capacity) } /* リストの長さを取得(現在の要素数) */ func size() -> Int { _size } /* リスト容量を取得する */ func capacity() -> Int { _capacity } /* 要素にアクセス */ func get(index: Int) -> Int { // インデックスが範囲外ならエラーを投げる。以下同様 if index < 0 || index >= size() { fatalError("インデックスが範囲外") } return arr[index] } /* 要素を更新 */ func set(index: Int, num: Int) { if index < 0 || index >= size() { fatalError("インデックスが範囲外") } arr[index] = num } /* 末尾に要素を追加 */ func add(num: Int) { // 要素数が容量を超えると、拡張機構が発動する if size() == capacity() { extendCapacity() } arr[size()] = num // 要素数を更新 _size += 1 } /* 中間に要素を挿入 */ func insert(index: Int, num: Int) { if index < 0 || index >= size() { fatalError("インデックスが範囲外") } // 要素数が容量を超えると、拡張機構が発動する if size() == capacity() { extendCapacity() } // index 以降の要素をすべて 1 つ後ろへずらす for j in (index ..< size()).reversed() { arr[j + 1] = arr[j] } arr[index] = num // 要素数を更新 _size += 1 } /* 要素を削除 */ @discardableResult func remove(index: Int) -> Int { if index < 0 || index >= size() { fatalError("インデックスが範囲外") } let num = arr[index] // インデックス index より後の要素をすべて 1 つ前に移動する for j in index ..< (size() - 1) { arr[j] = arr[j + 1] } // 要素数を更新 _size -= 1 // 削除された要素を返す return num } /* リストの拡張 */ func extendCapacity() { // 元の配列の extendRatio 倍の長さを持つ新しい配列を作成し、元の配列をコピーする arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1)) // リストの容量を更新 _capacity = arr.count } /* リストを配列に変換する */ func toArray() -> [Int] { Array(arr.prefix(size())) } } @main enum _MyList { /* Driver Code */ static func main() { /* リストを初期化 */ let nums = MyList() /* 末尾に要素を追加 */ nums.add(num: 1) nums.add(num: 3) nums.add(num: 2) nums.add(num: 5) nums.add(num: 4) print("リスト nums = \(nums.toArray()) 、容量 = \(nums.capacity()) 、長さ = \(nums.size())") /* 中間に要素を挿入 */ nums.insert(index: 3, num: 6) print("インデックス 3 に数値 6 を挿入すると、nums = \(nums.toArray())") /* 要素を削除 */ nums.remove(index: 3) print("インデックス 3 の要素を削除すると、nums = \(nums.toArray())") /* 要素にアクセス */ let num = nums.get(index: 1) print("インデックス 1 の要素にアクセスすると、num = \(num)") /* 要素を更新 */ nums.set(index: 1, num: 0) print("インデックス 1 の要素を 0 に更新すると、nums = \(nums.toArray())") /* 拡張機構をテストする */ for i in 0 ..< 10 { // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する nums.add(num: i) } print("拡張後のリスト nums = \(nums.toArray()) 、容量 = \(nums.capacity()) 、長さ = \(nums.size())") } } ================================================ FILE: ja/codes/swift/chapter_backtracking/n_queens.swift ================================================ /** * File: n_queens.swift * Created Time: 2023-05-14 * Author: nuomi1 (nuomi1@qq.com) */ /* バックトラッキング:N クイーン */ func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) { // すべての行への配置が完了したら、解を記録する if row == n { res.append(state) return } // すべての列を走査 for col in 0 ..< n { // このマスに対応する主対角線と副対角線を計算 let diag1 = row - col + n - 1 let diag2 = row + col // 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない if !cols[col] && !diags1[diag1] && !diags2[diag2] { // 試行:そのマスにクイーンを置く state[row][col] = "Q" cols[col] = true diags1[diag1] = true diags2[diag2] = true // 次の行に配置する backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) // 戻す:そのマスを空きマスに戻す state[row][col] = "#" cols[col] = false diags1[diag1] = false diags2[diag2] = false } } } /* N クイーンを解く */ func nQueens(n: Int) -> [[[String]]] { // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す var state = Array(repeating: Array(repeating: "#", count: n), count: n) var cols = Array(repeating: false, count: n) // 列にクイーンがあるか記録 var diags1 = Array(repeating: false, count: 2 * n - 1) // 主対角線にクイーンがあるかを記録 var diags2 = Array(repeating: false, count: 2 * n - 1) // 副対角線にクイーンがあるかを記録 var res: [[[String]]] = [] backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) return res } @main enum NQueens { /* Driver Code */ static func main() { let n = 4 let res = nQueens(n: n) print("入力された盤面の縦横は \(n)") print("クイーンの配置方法は全部で \(res.count) 通り") for state in res { print("--------------------") for row in state { print(row) } } } } ================================================ FILE: ja/codes/swift/chapter_backtracking/permutations_i.swift ================================================ /** * File: permutations_i.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ /* バックトラッキング:順列 I */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // 状態の長さが要素数に等しければ、解を記録 if state.count == choices.count { res.append(state) return } // すべての選択肢を走査 for (i, choice) in choices.enumerated() { // 枝刈り:要素の重複選択を許可しない if !selected[i] { // 試行: 選択を行い、状態を更新 selected[i] = true state.append(choice) // 次の選択へ進む backtrack(state: &state, choices: choices, selected: &selected, res: &res) // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false state.removeLast() } } } /* 全順列 I */ func permutationsI(nums: [Int]) -> [[Int]] { var state: [Int] = [] var selected = Array(repeating: false, count: nums.count) var res: [[Int]] = [] backtrack(state: &state, choices: nums, selected: &selected, res: &res) return res } @main enum PermutationsI { /* Driver Code */ static func main() { let nums = [1, 2, 3] let res = permutationsI(nums: nums) print("入力配列 nums = \(nums)") print("すべての順列 res = \(res)") } } ================================================ FILE: ja/codes/swift/chapter_backtracking/permutations_ii.swift ================================================ /** * File: permutations_ii.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ /* バックトラッキング:順列 II */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // 状態の長さが要素数に等しければ、解を記録 if state.count == choices.count { res.append(state) return } // すべての選択肢を走査 var duplicated: Set = [] for (i, choice) in choices.enumerated() { // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない if !selected[i], !duplicated.contains(choice) { // 試行: 選択を行い、状態を更新 duplicated.insert(choice) // 選択済みの要素値を記録 selected[i] = true state.append(choice) // 次の選択へ進む backtrack(state: &state, choices: choices, selected: &selected, res: &res) // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false state.removeLast() } } } /* 全順列 II */ func permutationsII(nums: [Int]) -> [[Int]] { var state: [Int] = [] var selected = Array(repeating: false, count: nums.count) var res: [[Int]] = [] backtrack(state: &state, choices: nums, selected: &selected, res: &res) return res } @main enum PermutationsII { /* Driver Code */ static func main() { let nums = [1, 2, 3] let res = permutationsII(nums: nums) print("入力配列 nums = \(nums)") print("すべての順列 res = \(res)") } } ================================================ FILE: ja/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift ================================================ /** * File: preorder_traversal_i_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var res: [TreeNode] = [] /* 前順走査:例題 1 */ func preOrder(root: TreeNode?) { guard let root = root else { return } if root.val == 7 { // 解を記録 res.append(root) } preOrder(root: root.left) preOrder(root: root.right) } @main enum PreorderTraversalICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\n二分木を初期化") PrintUtil.printTree(root: root) // 先行順走査 res = [] preOrder(root: root) print("\n値が 7 のすべてのノードを出力") var vals: [Int] = [] for node in res { vals.append(node.val) } print(vals) } } ================================================ FILE: ja/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift ================================================ /** * File: preorder_traversal_ii_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var path: [TreeNode] = [] var res: [[TreeNode]] = [] /* 前順走査:例題 2 */ func preOrder(root: TreeNode?) { guard let root = root else { return } // 試す path.append(root) if root.val == 7 { // 解を記録 res.append(path) } preOrder(root: root.left) preOrder(root: root.right) // バックトラック path.removeLast() } @main enum PreorderTraversalIICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\n二分木を初期化") PrintUtil.printTree(root: root) // 先行順走査 path = [] res = [] preOrder(root: root) print("\n根ノードからノード 7 までのすべての経路を出力") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: ja/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift ================================================ /** * File: preorder_traversal_iii_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var path: [TreeNode] = [] var res: [[TreeNode]] = [] /* 前順走査:例題 3 */ func preOrder(root: TreeNode?) { // 枝刈り guard let root = root, root.val != 3 else { return } // 試す path.append(root) if root.val == 7 { // 解を記録 res.append(path) } preOrder(root: root.left) preOrder(root: root.right) // バックトラック path.removeLast() } @main enum PreorderTraversalIIICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\n二分木を初期化") PrintUtil.printTree(root: root) // 先行順走査 path = [] res = [] preOrder(root: root) print("\n根ノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含まない") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: ja/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift ================================================ /** * File: preorder_traversal_iii_template.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 現在の状態が解かどうかを判定 */ func isSolution(state: [TreeNode]) -> Bool { !state.isEmpty && state.last!.val == 7 } /* 解を記録 */ func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) { res.append(state) } /* 現在の状態で、この選択が有効かどうかを判定 */ func isValid(state: [TreeNode], choice: TreeNode?) -> Bool { choice != nil && choice!.val != 3 } /* 状態を更新 */ func makeChoice(state: inout [TreeNode], choice: TreeNode) { state.append(choice) } /* 状態を元に戻す */ func undoChoice(state: inout [TreeNode], choice: TreeNode) { state.removeLast() } /* バックトラッキング:例題 3 */ func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) { // 解かどうかを確認 if isSolution(state: state) { recordSolution(state: state, res: &res) } // すべての選択肢を走査 for choice in choices { // 枝刈り:選択が妥当かを確認する if isValid(state: state, choice: choice) { // 試行: 選択を行い、状態を更新 makeChoice(state: &state, choice: choice) // 次の選択へ進む backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res) // バックトラック:選択を取り消し、前の状態に戻す undoChoice(state: &state, choice: choice) } } } @main enum PreorderTraversalIIITemplate { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\n二分木を初期化") PrintUtil.printTree(root: root) // バックトラッキング法 var state: [TreeNode] = [] var res: [[TreeNode]] = [] backtrack(state: &state, choices: [root].compactMap { $0 }, res: &res) print("\n根ノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含まない") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: ja/codes/swift/chapter_backtracking/subset_sum_i.swift ================================================ /** * File: subset_sum_i.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* バックトラッキング:部分和 I */ func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { // 部分集合の和が target に等しければ、解を記録 if target == 0 { res.append(state) return } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for i in choices.indices.dropFirst(start) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if target - choices[i] < 0 { break } // 試す:選択を行い、target と start を更新 state.append(choices[i]) // 次の選択へ進む backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res) // バックトラック:選択を取り消し、前の状態に戻す state.removeLast() } } /* 部分和 I を解く */ func subsetSumI(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // 状態(部分集合) let nums = nums.sorted() // nums をソート let start = 0 // 開始点を走査 var res: [[Int]] = [] // 結果リスト(部分集合のリスト) backtrack(state: &state, target: target, choices: nums, start: start, res: &res) return res } @main enum SubsetSumI { /* Driver Code */ static func main() { let nums = [3, 4, 5] let target = 9 let res = subsetSumI(nums: nums, target: target) print("入力配列 nums = \(nums), target = \(target)") print("和が \(target) に等しいすべての部分集合 res = \(res)") } } ================================================ FILE: ja/codes/swift/chapter_backtracking/subset_sum_i_naive.swift ================================================ /** * File: subset_sum_i_naive.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* バックトラッキング:部分和 I */ func backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) { // 部分集合の和が target に等しければ、解を記録 if total == target { res.append(state) return } // すべての選択肢を走査 for i in choices.indices { // 枝刈り:部分和が target を超える場合はその選択をスキップする if total + choices[i] > target { continue } // 試行:選択を行い、要素と total を更新する state.append(choices[i]) // 次の選択へ進む backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res) // バックトラック:選択を取り消し、前の状態に戻す state.removeLast() } } /* 部分和 I を解く(重複部分集合を含む) */ func subsetSumINaive(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // 状態(部分集合) let total = 0 // 部分和 var res: [[Int]] = [] // 結果リスト(部分集合のリスト) backtrack(state: &state, target: target, total: total, choices: nums, res: &res) return res } @main enum SubsetSumINaive { /* Driver Code */ static func main() { let nums = [3, 4, 5] let target = 9 let res = subsetSumINaive(nums: nums, target: target) print("入力配列 nums = \(nums), target = \(target)") print("和が \(target) に等しいすべての部分集合 res = \(res)") print("この方法の出力結果には重複した集合が含まれることに注意してください") } } ================================================ FILE: ja/codes/swift/chapter_backtracking/subset_sum_ii.swift ================================================ /** * File: subset_sum_ii.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* バックトラッキング:部分和 II */ func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { // 部分集合の和が target に等しければ、解を記録 if target == 0 { res.append(state) return } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for i in choices.indices.dropFirst(start) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if target - choices[i] < 0 { break } // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする if i > start, choices[i] == choices[i - 1] { continue } // 試す:選択を行い、target と start を更新 state.append(choices[i]) // 次の選択へ進む backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res) // バックトラック:選択を取り消し、前の状態に戻す state.removeLast() } } /* 部分和 II を解く */ func subsetSumII(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // 状態(部分集合) let nums = nums.sorted() // nums をソート let start = 0 // 開始点を走査 var res: [[Int]] = [] // 結果リスト(部分集合のリスト) backtrack(state: &state, target: target, choices: nums, start: start, res: &res) return res } @main enum SubsetSumII { /* Driver Code */ static func main() { let nums = [4, 4, 5] let target = 9 let res = subsetSumII(nums: nums, target: target) print("入力配列 nums = \(nums), target = \(target)") print("和が \(target) に等しいすべての部分集合 res = \(res)") } } ================================================ FILE: ja/codes/swift/chapter_computational_complexity/iteration.swift ================================================ /** * File: iteration.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* for ループ */ func forLoop(n: Int) -> Int { var res = 0 // 1, 2, ..., n-1, n を順に加算する for i in 1 ... n { res += i } return res } /* while ループ */ func whileLoop(n: Int) -> Int { var res = 0 var i = 1 // 条件変数を初期化する // 1, 2, ..., n-1, n を順に加算する while i <= n { res += i i += 1 // 条件変数を更新する } return res } /* while ループ(2回更新) */ func whileLoopII(n: Int) -> Int { var res = 0 var i = 1 // 条件変数を初期化する // 1, 4, 10, ... を順に加算する while i <= n { res += i // 条件変数を更新する i += 1 i *= 2 } return res } /* 二重 for ループ */ func nestedForLoop(n: Int) -> String { var res = "" // i = 1, 2, ..., n-1, n とループする for i in 1 ... n { // j = 1, 2, ..., n-1, n とループする for j in 1 ... n { res.append("(\(i), \(j)), ") } } return res } @main enum Iteration { /* Driver Code */ static func main() { let n = 5 var res = 0 res = forLoop(n: n) print("\nfor ループの合計結果 res = \(res)") res = whileLoop(n: n) print("\nwhile ループの合計結果 res = \(res)") res = whileLoopII(n: n) print("\nwhile ループ(2 回更新)の合計結果 res = \(res)") let resStr = nestedForLoop(n: n) print("\n二重 for ループの走査結果 \(resStr)") } } ================================================ FILE: ja/codes/swift/chapter_computational_complexity/recursion.swift ================================================ /** * File: recursion.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 再帰 */ func recur(n: Int) -> Int { // 終了条件 if n == 1 { return 1 } // 再帰:再帰呼び出し let res = recur(n: n - 1) // 帰りがけ:結果を返す return n + res } /* 反復で再帰を模擬する */ func forLoopRecur(n: Int) -> Int { // 明示的なスタックを使ってシステムコールスタックを模擬する var stack: [Int] = [] var res = 0 // 再帰:再帰呼び出し for i in (1 ... n).reversed() { // 「スタックへのプッシュ」で「再帰」を模擬する stack.append(i) } // 帰りがけ:結果を返す while !stack.isEmpty { // 「スタックから取り出す操作」で「帰り」をシミュレート res += stack.removeLast() } // res = 1+2+3+...+n return res } /* 末尾再帰 */ func tailRecur(n: Int, res: Int) -> Int { // 終了条件 if n == 0 { return res } // 末尾再帰呼び出し return tailRecur(n: n - 1, res: res + n) } /* フィボナッチ数列:再帰 */ func fib(n: Int) -> Int { // 終了条件 f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1 } // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す let res = fib(n: n - 1) + fib(n: n - 2) // 結果 f(n) を返す return res } @main enum Recursion { /* Driver Code */ static func main() { let n = 5 var res = 0 res = recursion.recur(n: n) print("\n再帰関数による総和結果 res = \(res)") res = recursion.forLoopRecur(n: n) print("\n反復で再帰をシミュレートした総和結果 res = \(res)") res = recursion.tailRecur(n: n, res: 0) print("\n末尾再帰関数による総和結果 res = \(res)") res = recursion.fib(n: n) print("\nフィボナッチ数列の第 \(n) 項は \(res)") } } ================================================ FILE: ja/codes/swift/chapter_computational_complexity/space_complexity.swift ================================================ /** * File: space_complexity.swift * Created Time: 2023-01-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 関数 */ @discardableResult func function() -> Int { // 何らかの処理を行う return 0 } /* 定数階 */ func constant(n: Int) { // 定数、変数、オブジェクトは O(1) の空間を占める let a = 0 var b = 0 let nums = Array(repeating: 0, count: 10000) let node = ListNode(x: 0) // ループ内の変数は O(1) の空間を占める for _ in 0 ..< n { let c = 0 } // ループ内の関数は O(1) の空間を占める for _ in 0 ..< n { function() } } /* 線形階 */ func linear(n: Int) { // 長さ n の配列は O(n) の空間を使用 let nums = Array(repeating: 0, count: n) // 長さ n のリストは O(n) の空間を使用 let nodes = (0 ..< n).map { ListNode(x: $0) } // 長さ n のハッシュテーブルは O(n) の空間を使用 let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") }) } /* 線形時間(再帰実装) */ func linearRecur(n: Int) { print("再帰 n = \(n)") if n == 1 { return } linearRecur(n: n - 1) } /* 二乗階 */ func quadratic(n: Int) { // 二次元リストは O(n^2) の空間を使用 let numList = Array(repeating: Array(repeating: 0, count: n), count: n) } /* 二次時間(再帰実装) */ @discardableResult func quadraticRecur(n: Int) -> Int { if n <= 0 { return 0 } // 配列 nums の長さは n, n-1, ..., 2, 1 let nums = Array(repeating: 0, count: n) print("再帰 n = \(n) における nums の長さ = \(nums.count)") return quadraticRecur(n: n - 1) } /* 指数時間(完全二分木の構築) */ func buildTree(n: Int) -> TreeNode? { if n == 0 { return nil } let root = TreeNode(x: 0) root.left = buildTree(n: n - 1) root.right = buildTree(n: n - 1) return root } @main enum SpaceComplexity { /* Driver Code */ static func main() { let n = 5 // 定数階 constant(n: n) // 線形階 linear(n: n) linearRecur(n: n) // 二乗階 quadratic(n: n) quadraticRecur(n: n) // 指数オーダー let root = buildTree(n: n) PrintUtil.printTree(root: root) } } ================================================ FILE: ja/codes/swift/chapter_computational_complexity/time_complexity.swift ================================================ /** * File: time_complexity.swift * Created Time: 2022-12-26 * Author: nuomi1 (nuomi1@qq.com) */ /* 定数階 */ func constant(n: Int) -> Int { var count = 0 let size = 100_000 for _ in 0 ..< size { count += 1 } return count } /* 線形階 */ func linear(n: Int) -> Int { var count = 0 for _ in 0 ..< n { count += 1 } return count } /* 線形時間(配列を走査) */ func arrayTraversal(nums: [Int]) -> Int { var count = 0 // ループ回数は配列長に比例する for _ in nums { count += 1 } return count } /* 二乗階 */ func quadratic(n: Int) -> Int { var count = 0 // ループ回数はデータサイズ n の二乗に比例する for _ in 0 ..< n { for _ in 0 ..< n { count += 1 } } return count } /* 二次時間(バブルソート) */ func bubbleSort(nums: inout [Int]) -> Int { var count = 0 // カウンタ // 外側のループ:未ソート区間は [0, i] for i in nums.indices.dropFirst().reversed() { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j in 0 ..< i { if nums[j] > nums[j + 1] { // nums[j] と nums[j + 1] を交換 let tmp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = tmp count += 3 // 要素交換には 3 回の単位操作が含まれる } } } return count } /* 指数時間(ループ実装) */ func exponential(n: Int) -> Int { var count = 0 var base = 1 // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する for _ in 0 ..< n { for _ in 0 ..< base { count += 1 } base *= 2 } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count } /* 指数時間(再帰実装) */ func expRecur(n: Int) -> Int { if n == 1 { return 1 } return expRecur(n: n - 1) + expRecur(n: n - 1) + 1 } /* 対数時間(ループ実装) */ func logarithmic(n: Int) -> Int { var count = 0 var n = n while n > 1 { n = n / 2 count += 1 } return count } /* 対数時間(再帰実装) */ func logRecur(n: Int) -> Int { if n <= 1 { return 0 } return logRecur(n: n / 2) + 1 } /* 線形対数時間 */ func linearLogRecur(n: Int) -> Int { if n <= 1 { return 1 } var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2) for _ in stride(from: 0, to: n, by: 1) { count += 1 } return count } /* 階乗時間(再帰実装) */ func factorialRecur(n: Int) -> Int { if n == 0 { return 1 } var count = 0 // 1個から n 個に分裂 for _ in 0 ..< n { count += factorialRecur(n: n - 1) } return count } @main enum TimeComplexity { /* Driver Code */ static func main() { // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる let n = 8 print("入力データサイズ n = \(n)") var count = constant(n: n) print("定数時間の操作回数 = \(count)") count = linear(n: n) print("線形時間の操作回数 = \(count)") count = arrayTraversal(nums: Array(repeating: 0, count: n)) print("線形時間(配列の走査)の操作回数 = \(count)") count = quadratic(n: n) print("二乗時間の操作回数 = \(count)") var nums = Array(stride(from: n, to: 0, by: -1)) // [n,n-1,...,2,1] count = bubbleSort(nums: &nums) print("二乗時間(バブルソート)の操作回数 = \(count)") count = exponential(n: n) print("指数時間(ループ実装)の操作回数 = \(count)") count = expRecur(n: n) print("指数時間(再帰実装)の操作回数 = \(count)") count = logarithmic(n: n) print("対数時間(ループ実装)の操作回数 = \(count)") count = logRecur(n: n) print("対数時間(再帰実装)の操作回数 = \(count)") count = linearLogRecur(n: n) print("線形対数時間(再帰実装)の操作回数 = \(count)") count = factorialRecur(n: n) print("階乗時間(再帰実装)の操作回数 = \(count)") } } ================================================ FILE: ja/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift ================================================ /** * File: worst_best_time_complexity.swift * Created Time: 2022-12-26 * Author: nuomi1 (nuomi1@qq.com) */ /* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ func randomNumbers(n: Int) -> [Int] { // 配列 nums = { 1, 2, 3, ..., n } を生成 var nums = Array(1 ... n) // 配列要素をランダムにシャッフル nums.shuffle() return nums } /* 配列 nums 内で数値 1 のインデックスを探す */ func findOne(nums: [Int]) -> Int { for i in nums.indices { // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる if nums[i] == 1 { return i } } return -1 } @main enum WorstBestTimeComplexity { /* Driver Code */ static func main() { for _ in 0 ..< 10 { let n = 100 let nums = randomNumbers(n: n) let index = findOne(nums: nums) print("配列 [ 1, 2, ..., n ] をシャッフルした後 = \(nums)") print("数値 1 のインデックスは \(index)") } } } ================================================ FILE: ja/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift ================================================ /** * File: binary_search_recur.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 二分探索:問題 f(i, j) */ func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int { // 区間が空なら対象要素は存在しないので -1 を返す if i > j { return -1 } // 中点インデックス m を計算 let m = (i + j) / 2 if nums[m] < target { // 部分問題 f(m+1, j) を再帰的に解く return dfs(nums: nums, target: target, i: m + 1, j: j) } else if nums[m] > target { // 部分問題 f(i, m-1) を再帰的に解く return dfs(nums: nums, target: target, i: i, j: m - 1) } else { // 目標要素が見つかったらそのインデックスを返す return m } } /* 二分探索 */ func binarySearch(nums: [Int], target: Int) -> Int { // 問題 f(0, n-1) を解く dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1) } @main enum BinarySearchRecur { /* Driver Code */ static func main() { let target = 6 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] // 二分探索(両閉区間) let index = binarySearch(nums: nums, target: target) print("対象要素 6 のインデックス = \(index)") } } ================================================ FILE: ja/codes/swift/chapter_divide_and_conquer/build_tree.swift ================================================ /** * File: build_tree.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 二分木を構築:分割統治 */ func dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? { // 部分木区間が空なら終了する if r - l < 0 { return nil } // ルートノードを初期化する let root = TreeNode(x: preorder[i]) // m を求めて左右部分木を分割する let m = inorderMap[preorder[i]]! // 部分問題:左部分木を構築する root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1) // 部分問題:右部分木を構築する root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r) // 根ノードを返す return root } /* 二分木を構築 */ func buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? { // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset } return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1) } @main enum BuildTree { /* Driver Code */ static func main() { let preorder = [3, 9, 2, 1, 7] let inorder = [9, 3, 1, 2, 7] print("前順走査 = \(preorder)") print("中順走査 = \(inorder)") let root = buildTree(preorder: preorder, inorder: inorder) print("構築した二分木は:") PrintUtil.printTree(root: root) } } ================================================ FILE: ja/codes/swift/chapter_divide_and_conquer/hanota.swift ================================================ /** * File: hanota.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 円盤を 1 枚移動 */ func move(src: inout [Int], tar: inout [Int]) { // src の上から円盤を1枚取り出す let pan = src.popLast()! // 円盤を tar の上に置く tar.append(pan) } /* ハノイの塔の問題 f(i) を解く */ func dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) { // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if i == 1 { move(src: &src, tar: &tar) return } // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i: i - 1, src: &src, buf: &tar, tar: &buf) // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src: &src, tar: &tar) // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i: i - 1, src: &buf, buf: &src, tar: &tar) } /* ハノイの塔を解く */ func solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) { let n = A.count // リストの末尾が柱の上端に対応する // src の上から n 個の円盤を、B を介して C に移動する dfs(i: n, src: &A, buf: &B, tar: &C) } @main enum Hanota { /* Driver Code */ static func main() { // リスト末尾が柱の頂上 var A = [5, 4, 3, 2, 1] var B: [Int] = [] var C: [Int] = [] print("初期状態:") print("A = \(A)") print("B = \(B)") print("C = \(C)") solveHanota(A: &A, B: &B, C: &C) print("円盤の移動完了後:") print("A = \(A)") print("B = \(B)") print("C = \(C)") } } ================================================ FILE: ja/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift ================================================ /** * File: climbing_stairs_backtrack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* バックトラッキング */ func backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) { // 第 n 段に到達したら、方法数を 1 増やす if state == n { res[0] += 1 } // すべての選択肢を走査 for choice in choices { // 枝刈り: 第 n 段を超えないようにする if state + choice > n { continue } // 試行: 選択を行い、状態を更新 backtrack(choices: choices, state: state + choice, n: n, res: &res) // バックトラック } } /* 階段登り:バックトラッキング */ func climbingStairsBacktrack(n: Int) -> Int { let choices = [1, 2] // 1 段または 2 段上ることを選べる let state = 0 // 第 0 段から上り始める var res: [Int] = [] res.append(0) // res[0] を使って方法数を記録する backtrack(choices: choices, state: state, n: n, res: &res) return res[0] } @main enum ClimbingStairsBacktrack { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsBacktrack(n: n) print("\(n) 段の階段を上る方法は全部で \(res) 通り") } } ================================================ FILE: ja/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift ================================================ /** * File: climbing_stairs_constraint_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 制約付き階段登り:動的計画法 */ func climbingStairsConstraintDP(n: Int) -> Int { if n == 1 || n == 2 { return 1 } // 部分問題の解を保存するために dp テーブルを初期化 var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1) // 初期状態:最小部分問題の解をあらかじめ設定 dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i in 3 ... n { dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] } return dp[n][1] + dp[n][2] } @main enum ClimbingStairsConstraintDP { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsConstraintDP(n: n) print("\(n) 段の階段を上る方法は全部で \(res) 通り") } } ================================================ FILE: ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift ================================================ /** * File: climbing_stairs_dfs.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 検索 */ func dfs(i: Int) -> Int { // dp[1] と dp[2] は既知なので返す if i == 1 || i == 2 { return i } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i: i - 1) + dfs(i: i - 2) return count } /* 階段登り:探索 */ func climbingStairsDFS(n: Int) -> Int { dfs(i: n) } @main enum ClimbingStairsDFS { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsDFS(n: n) print("\(n) 段の階段を上る方法は全部で \(res) 通り") } } ================================================ FILE: ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift ================================================ /** * File: climbing_stairs_dfs_mem.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* メモ化探索 */ func dfs(i: Int, mem: inout [Int]) -> Int { // dp[1] と dp[2] は既知なので返す if i == 1 || i == 2 { return i } // dp[i] の記録があれば、それをそのまま返す if mem[i] != -1 { return mem[i] } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem) // dp[i] を記録する mem[i] = count return count } /* 階段登り:メモ化探索 */ func climbingStairsDFSMem(n: Int) -> Int { // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す var mem = Array(repeating: -1, count: n + 1) return dfs(i: n, mem: &mem) } @main enum ClimbingStairsDFSMem { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsDFSMem(n: n) print("\(n) 段の階段を上る方法は全部で \(res) 通り") } } ================================================ FILE: ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift ================================================ /** * File: climbing_stairs_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 階段登り:動的計画法 */ func climbingStairsDP(n: Int) -> Int { if n == 1 || n == 2 { return n } // 部分問題の解を保存するために dp テーブルを初期化 var dp = Array(repeating: 0, count: n + 1) // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = 1 dp[2] = 2 // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i in 3 ... n { dp[i] = dp[i - 1] + dp[i - 2] } return dp[n] } /* 階段登り:空間最適化した動的計画法 */ func climbingStairsDPComp(n: Int) -> Int { if n == 1 || n == 2 { return n } var a = 1 var b = 2 for _ in 3 ... n { (a, b) = (b, a + b) } return b } @main enum ClimbingStairsDP { /* Driver Code */ static func main() { let n = 9 var res = climbingStairsDP(n: n) print("\(n) 段の階段を上る方法は全部で \(res) 通り") res = climbingStairsDPComp(n: n) print("\(n) 段の階段を上る方法は全部で \(res) 通り") } } ================================================ FILE: ja/codes/swift/chapter_dynamic_programming/coin_change.swift ================================================ /** * File: coin_change.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* コイン両替:動的計画法 */ func coinChangeDP(coins: [Int], amt: Int) -> Int { let n = coins.count let MAX = amt + 1 // dp テーブルを初期化 var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) // 状態遷移:先頭行と先頭列 for a in 1 ... amt { dp[0][a] = MAX } // 状態遷移: 残りの行と列 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a] } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) } } } return dp[n][amt] != MAX ? dp[n][amt] : -1 } /* コイン交換:空間最適化後の動的計画法 */ func coinChangeDPComp(coins: [Int], amt: Int) -> Int { let n = coins.count let MAX = amt + 1 // dp テーブルを初期化 var dp = Array(repeating: MAX, count: amt + 1) dp[0] = 0 // 状態遷移 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a] } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) } } } return dp[amt] != MAX ? dp[amt] : -1 } @main enum CoinChange { /* Driver Code */ static func main() { let coins = [1, 2, 5] let amt = 4 // 動的計画法 var res = coinChangeDP(coins: coins, amt: amt) print("目標金額に必要な最小硬貨枚数は \(res)") // 空間最適化後の動的計画法 res = coinChangeDPComp(coins: coins, amt: amt) print("目標金額に必要な最小硬貨枚数は \(res)") } } ================================================ FILE: ja/codes/swift/chapter_dynamic_programming/coin_change_ii.swift ================================================ /** * File: coin_change_ii.swift * Created Time: 2023-07-16 * Author: nuomi1 (nuomi1@qq.com) */ /* コイン両替 II:動的計画法 */ func coinChangeIIDP(coins: [Int], amt: Int) -> Int { let n = coins.count // dp テーブルを初期化 var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) // 先頭列を初期化する for i in 0 ... n { dp[i][0] = 1 } // 状態遷移 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a] } else { // コイン i を選ばない場合と選ぶ場合の和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] } } } return dp[n][amt] } /* コイン両替 II:空間最適化した動的計画法 */ func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { let n = coins.count // dp テーブルを初期化 var dp = Array(repeating: 0, count: amt + 1) dp[0] = 1 // 状態遷移 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a] } else { // コイン i を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a - coins[i - 1]] } } } return dp[amt] } @main enum CoinChangeII { /* Driver Code */ static func main() { let coins = [1, 2, 5] let amt = 5 // 動的計画法 var res = coinChangeIIDP(coins: coins, amt: amt) print("目標金額を作る硬貨の組み合わせ数は \(res)") // 空間最適化後の動的計画法 res = coinChangeIIDPComp(coins: coins, amt: amt) print("目標金額を作る硬貨の組み合わせ数は \(res)") } } ================================================ FILE: ja/codes/swift/chapter_dynamic_programming/edit_distance.swift ================================================ /** * File: edit_distance.swift * Created Time: 2023-07-16 * Author: nuomi1 (nuomi1@qq.com) */ /* 編集距離:総当たり探索 */ func editDistanceDFS(s: String, t: String, i: Int, j: Int) -> Int { // s と t がともに空なら 0 を返す if i == 0, j == 0 { return 0 } // s が空なら t の長さを返す if i == 0 { return j } // t が空なら s の長さを返す if j == 0 { return i } // 2 つの文字が等しければ、その 2 文字をそのままスキップする if s.utf8CString[i - 1] == t.utf8CString[j - 1] { return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) } // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) // 最小編集回数を返す return min(min(insert, delete), replace) + 1 } /* 編集距離:メモ化探索 */ func editDistanceDFSMem(s: String, t: String, mem: inout [[Int]], i: Int, j: Int) -> Int { // s と t がともに空なら 0 を返す if i == 0, j == 0 { return 0 } // s が空なら t の長さを返す if i == 0 { return j } // t が空なら s の長さを返す if j == 0 { return i } // 記録済みなら、それをそのまま返す if mem[i][j] != -1 { return mem[i][j] } // 2 つの文字が等しければ、その 2 文字をそのままスキップする if s.utf8CString[i - 1] == t.utf8CString[j - 1] { return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) } // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) // 最小編集回数を記録して返す mem[i][j] = min(min(insert, delete), replace) + 1 return mem[i][j] } /* 編集距離:動的計画法 */ func editDistanceDP(s: String, t: String) -> Int { let n = s.utf8CString.count let m = t.utf8CString.count var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1) // 状態遷移:先頭行と先頭列 for i in 1 ... n { dp[i][0] = i } for j in 1 ... m { dp[0][j] = j } // 状態遷移: 残りの行と列 for i in 1 ... n { for j in 1 ... m { if s.utf8CString[i - 1] == t.utf8CString[j - 1] { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i - 1][j - 1] } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 } } } return dp[n][m] } /* 編集距離:空間最適化した動的計画法 */ func editDistanceDPComp(s: String, t: String) -> Int { let n = s.utf8CString.count let m = t.utf8CString.count var dp = Array(repeating: 0, count: m + 1) // 状態遷移:先頭行 for j in 1 ... m { dp[j] = j } // 状態遷移:残りの行 for i in 1 ... n { // 状態遷移:先頭列 var leftup = dp[0] // dp[i-1, j-1] を一時保存する dp[0] = i // 状態遷移:残りの列 for j in 1 ... m { let temp = dp[j] if s.utf8CString[i - 1] == t.utf8CString[j - 1] { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftup } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 } leftup = temp // 次の反復の dp[i-1, j-1] に更新する } } return dp[m] } @main enum EditDistance { /* Driver Code */ static func main() { let s = "bag" let t = "pack" let n = s.utf8CString.count let m = t.utf8CString.count // 全探索 var res = editDistanceDFS(s: s, t: t, i: n, j: m) print("\(s) を \(t) に変更するには最小で \(res) 回の編集が必要") // メモ化探索 var mem = Array(repeating: Array(repeating: -1, count: m + 1), count: n + 1) res = editDistanceDFSMem(s: s, t: t, mem: &mem, i: n, j: m) print("\(s) を \(t) に変更するには最小で \(res) 回の編集が必要") // 動的計画法 res = editDistanceDP(s: s, t: t) print("\(s) を \(t) に変更するには最小で \(res) 回の編集が必要") // 空間最適化後の動的計画法 res = editDistanceDPComp(s: s, t: t) print("\(s) を \(t) に変更するには最小で \(res) 回の編集が必要") } } ================================================ FILE: ja/codes/swift/chapter_dynamic_programming/knapsack.swift ================================================ /** * File: knapsack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 0-1 ナップサック:総当たり探索 */ func knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if i == 0 || c == 0 { return 0 } // ナップサック容量を超える場合は、入れない選択しかできない if wgt[i - 1] > c { return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) } // 品物 i を入れない場合と入れる場合の最大価値を計算する let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] // 2つの案のうち価値が大きいほうを返す return max(no, yes) } /* 0-1 ナップサック:メモ化探索 */ func knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if i == 0 || c == 0 { return 0 } // 既に記録があればそのまま返す if mem[i][c] != -1 { return mem[i][c] } // ナップサック容量を超える場合は、入れない選択しかできない if wgt[i - 1] > c { return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) } // 品物 i を入れない場合と入れる場合の最大価値を計算する let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] // 2 つの案のうち価値が大きい方を記録して返す mem[i][c] = max(no, yes) return mem[i][c] } /* 0-1 ナップサック:動的計画法 */ func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // dp テーブルを初期化 var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) // 状態遷移 for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c] } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) } } } return dp[n][cap] } /* 0-1 ナップサック:空間最適化後の動的計画法 */ func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // dp テーブルを初期化 var dp = Array(repeating: 0, count: cap + 1) // 状態遷移 for i in 1 ... n { // 逆順に走査する for c in (1 ... cap).reversed() { if wgt[i - 1] <= c { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) } } } return dp[cap] } @main enum Knapsack { /* Driver Code */ static func main() { let wgt = [10, 20, 30, 40, 50] let val = [50, 120, 150, 210, 240] let cap = 50 let n = wgt.count // 全探索 var res = knapsackDFS(wgt: wgt, val: val, i: n, c: cap) print("ナップサック容量を超えない最大価値は \(res)") // メモ化探索 var mem = Array(repeating: Array(repeating: -1, count: cap + 1), count: n + 1) res = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: n, c: cap) print("ナップサック容量を超えない最大価値は \(res)") // 動的計画法 res = knapsackDP(wgt: wgt, val: val, cap: cap) print("ナップサック容量を超えない最大価値は \(res)") // 空間最適化後の動的計画法 res = knapsackDPComp(wgt: wgt, val: val, cap: cap) print("ナップサック容量を超えない最大価値は \(res)") } } ================================================ FILE: ja/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift ================================================ /** * File: min_cost_climbing_stairs_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 階段登りの最小コスト:動的計画法 */ func minCostClimbingStairsDP(cost: [Int]) -> Int { let n = cost.count - 1 if n == 1 || n == 2 { return cost[n] } // 部分問題の解を保存するために dp テーブルを初期化 var dp = Array(repeating: 0, count: n + 1) // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = cost[1] dp[2] = cost[2] // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i in 3 ... n { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] } return dp[n] } /* 階段昇りの最小コスト:空間最適化後の動的計画法 */ func minCostClimbingStairsDPComp(cost: [Int]) -> Int { let n = cost.count - 1 if n == 1 || n == 2 { return cost[n] } var (a, b) = (cost[1], cost[2]) for i in 3 ... n { (a, b) = (b, min(a, b) + cost[i]) } return b } @main enum MinCostClimbingStairsDP { /* Driver Code */ static func main() { let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] print("入力された階段のコスト一覧は \(cost)") var res = minCostClimbingStairsDP(cost: cost) print("階段を上り切る最小コストは \(res)") res = minCostClimbingStairsDPComp(cost: cost) print("階段を上り切る最小コストは \(res)") } } ================================================ FILE: ja/codes/swift/chapter_dynamic_programming/min_path_sum.swift ================================================ /** * File: min_path_sum.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 最小経路和:全探索 */ func minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int { // 左上のセルなら探索を終了する if i == 0, j == 0 { return grid[0][0] } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if i < 0 || j < 0 { return .max } // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する let up = minPathSumDFS(grid: grid, i: i - 1, j: j) let left = minPathSumDFS(grid: grid, i: i, j: j - 1) // 左上隅から (i, j) までの最小経路コストを返す return min(left, up) + grid[i][j] } /* 最小経路和:メモ化探索 */ func minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int { // 左上のセルなら探索を終了する if i == 0, j == 0 { return grid[0][0] } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if i < 0 || j < 0 { return .max } // 既に記録があればそのまま返す if mem[i][j] != -1 { return mem[i][j] } // 左と上のセルからの最小経路コスト let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j) let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1) // 左上から (i, j) までの最小経路コストを記録して返す mem[i][j] = min(left, up) + grid[i][j] return mem[i][j] } /* 最小経路和:動的計画法 */ func minPathSumDP(grid: [[Int]]) -> Int { let n = grid.count let m = grid[0].count // dp テーブルを初期化 var dp = Array(repeating: Array(repeating: 0, count: m), count: n) dp[0][0] = grid[0][0] // 状態遷移:先頭行 for j in 1 ..< m { dp[0][j] = dp[0][j - 1] + grid[0][j] } // 状態遷移:先頭列 for i in 1 ..< n { dp[i][0] = dp[i - 1][0] + grid[i][0] } // 状態遷移: 残りの行と列 for i in 1 ..< n { for j in 1 ..< m { dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] } } return dp[n - 1][m - 1] } /* 最小経路和:空間最適化後の動的計画法 */ func minPathSumDPComp(grid: [[Int]]) -> Int { let n = grid.count let m = grid[0].count // dp テーブルを初期化 var dp = Array(repeating: 0, count: m) // 状態遷移:先頭行 dp[0] = grid[0][0] for j in 1 ..< m { dp[j] = dp[j - 1] + grid[0][j] } // 状態遷移:残りの行 for i in 1 ..< n { // 状態遷移:先頭列 dp[0] = dp[0] + grid[i][0] // 状態遷移:残りの列 for j in 1 ..< m { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] } } return dp[m - 1] } @main enum MinPathSum { /* Driver Code */ static func main() { let grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ] let n = grid.count let m = grid[0].count // 全探索 var res = minPathSumDFS(grid: grid, i: n - 1, j: m - 1) print("左上から右下までの最小経路和は \(res)") // メモ化探索 var mem = Array(repeating: Array(repeating: -1, count: m), count: n) res = minPathSumDFSMem(grid: grid, mem: &mem, i: n - 1, j: m - 1) print("左上から右下までの最小経路和は \(res)") // 動的計画法 res = minPathSumDP(grid: grid) print("左上から右下までの最小経路和は \(res)") // 空間最適化後の動的計画法 res = minPathSumDPComp(grid: grid) print("左上から右下までの最小経路和は \(res)") } } ================================================ FILE: ja/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift ================================================ /** * File: unbounded_knapsack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 完全ナップサック問題:動的計画法 */ func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // dp テーブルを初期化 var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) // 状態遷移 for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c] } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) } } } return dp[n][cap] } /* 完全ナップサック問題:空間最適化後の動的計画法 */ func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // dp テーブルを初期化 var dp = Array(repeating: 0, count: cap + 1) // 状態遷移 for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c] } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) } } } return dp[cap] } @main enum UnboundedKnapsack { /* Driver Code */ static func main() { let wgt = [1, 2, 3] let val = [5, 11, 15] let cap = 4 // 動的計画法 var res = unboundedKnapsackDP(wgt: wgt, val: val, cap: cap) print("ナップサック容量を超えない最大価値は \(res)") // 空間最適化後の動的計画法 res = unboundedKnapsackDPComp(wgt: wgt, val: val, cap: cap) print("ナップサック容量を超えない最大価値は \(res)") } } ================================================ FILE: ja/codes/swift/chapter_graph/graph_adjacency_list.swift ================================================ /** * File: graph_adjacency_list.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 隣接リストに基づく無向グラフクラス */ public class GraphAdjList { // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 public private(set) var adjList: [Vertex: [Vertex]] /* コンストラクタ */ public init(edges: [[Vertex]]) { adjList = [:] // すべての頂点と辺を追加 for edge in edges { addVertex(vet: edge[0]) addVertex(vet: edge[1]) addEdge(vet1: edge[0], vet2: edge[1]) } } /* 頂点数を取得 */ public func size() -> Int { adjList.count } /* 辺を追加 */ public func addEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("引数エラー") } // 辺 vet1 - vet2 を追加 adjList[vet1]?.append(vet2) adjList[vet2]?.append(vet1) } /* 辺を削除 */ public func removeEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("引数エラー") } // 辺 vet1 - vet2 を削除 adjList[vet1]?.removeAll { $0 == vet2 } adjList[vet2]?.removeAll { $0 == vet1 } } /* 頂点を追加 */ public func addVertex(vet: Vertex) { if adjList[vet] != nil { return } // 隣接リストに新しいリストを追加 adjList[vet] = [] } /* 頂点を削除 */ public func removeVertex(vet: Vertex) { if adjList[vet] == nil { fatalError("引数エラー") } // 隣接リストから頂点 vet に対応するリストを削除 adjList.removeValue(forKey: vet) // 他の頂点のリストを走査し、vet を含むすべての辺を削除 for key in adjList.keys { adjList[key]?.removeAll { $0 == vet } } } /* 隣接リストを出力 */ public func print() { Swift.print("隣接リスト =") for (vertex, list) in adjList { let list = list.map { $0.val } Swift.print("\(vertex.val): \(list),") } } } #if !TARGET @main enum GraphAdjacencyList { /* Driver Code */ static func main() { /* 無向グラフを初期化 */ let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] let graph = GraphAdjList(edges: edges) print("\n初期化後のグラフ") graph.print() /* 辺を追加 */ // 頂点 1, 2 は v[0], v[2] graph.addEdge(vet1: v[0], vet2: v[2]) print("\n辺 1-2 を追加後のグラフ") graph.print() /* 辺を削除 */ // 頂点 1, 3 は v[0], v[1] graph.removeEdge(vet1: v[0], vet2: v[1]) print("\n辺 1-3 を削除後のグラフ") graph.print() /* 頂点を追加 */ let v5 = Vertex(val: 6) graph.addVertex(vet: v5) print("\n頂点 6 を追加後のグラフ") graph.print() /* 頂点を削除 */ // 頂点 3 は v[1] graph.removeVertex(vet: v[1]) print("\n頂点 3 を削除後のグラフ") graph.print() } } #endif ================================================ FILE: ja/codes/swift/chapter_graph/graph_adjacency_list_target.swift ================================================ /** * File: graph_adjacency_list.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 隣接リストに基づく無向グラフクラス */ public class GraphAdjList { // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 public private(set) var adjList: [Vertex: [Vertex]] /* コンストラクタ */ public init(edges: [[Vertex]]) { adjList = [:] // すべての頂点と辺を追加 for edge in edges { addVertex(vet: edge[0]) addVertex(vet: edge[1]) addEdge(vet1: edge[0], vet2: edge[1]) } } /* 頂点数を取得 */ public func size() -> Int { adjList.count } /* 辺を追加 */ public func addEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("引数エラー") } // 辺 vet1 - vet2 を追加 adjList[vet1]?.append(vet2) adjList[vet2]?.append(vet1) } /* 辺を削除 */ public func removeEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("引数エラー") } // 辺 vet1 - vet2 を削除 adjList[vet1]?.removeAll { $0 == vet2 } adjList[vet2]?.removeAll { $0 == vet1 } } /* 頂点を追加 */ public func addVertex(vet: Vertex) { if adjList[vet] != nil { return } // 隣接リストに新しいリストを追加 adjList[vet] = [] } /* 頂点を削除 */ public func removeVertex(vet: Vertex) { if adjList[vet] == nil { fatalError("引数エラー") } // 隣接リストから頂点 vet に対応するリストを削除 adjList.removeValue(forKey: vet) // 他の頂点のリストを走査し、vet を含むすべての辺を削除 for key in adjList.keys { adjList[key]?.removeAll { $0 == vet } } } /* 隣接リストを出力 */ public func print() { Swift.print("隣接リスト =") for (vertex, list) in adjList { let list = list.map { $0.val } Swift.print("\(vertex.val): \(list),") } } } #if !TARGET @main enum GraphAdjacencyList { /* Driver Code */ static func main() { /* 無向グラフを初期化 */ let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] let graph = GraphAdjList(edges: edges) print("\n初期化後のグラフ") graph.print() /* 辺を追加 */ // 頂点 1, 2 は v[0], v[2] graph.addEdge(vet1: v[0], vet2: v[2]) print("\n辺 1-2 を追加後のグラフ") graph.print() /* 辺を削除 */ // 頂点 1, 3 は v[0], v[1] graph.removeEdge(vet1: v[0], vet2: v[1]) print("\n辺 1-3 を削除後のグラフ") graph.print() /* 頂点を追加 */ let v5 = Vertex(val: 6) graph.addVertex(vet: v5) print("\n頂点 6 を追加後のグラフ") graph.print() /* 頂点を削除 */ // 頂点 3 は v[1] graph.removeVertex(vet: v[1]) print("\n頂点 3 を削除後のグラフ") graph.print() } } #endif ================================================ FILE: ja/codes/swift/chapter_graph/graph_adjacency_matrix.swift ================================================ /** * File: graph_adjacency_matrix.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 隣接行列に基づく無向グラフクラス */ class GraphAdjMat { private var vertices: [Int] // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す private var adjMat: [[Int]] // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 /* コンストラクタ */ init(vertices: [Int], edges: [[Int]]) { self.vertices = [] adjMat = [] // 頂点を追加 for val in vertices { addVertex(val: val) } // 辺を追加 // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する for e in edges { addEdge(i: e[0], j: e[1]) } } /* 頂点数を取得 */ func size() -> Int { vertices.count } /* 頂点を追加 */ func addVertex(val: Int) { let n = size() // 頂点リストに新しい頂点の値を追加 vertices.append(val) // 隣接行列に 1 行追加 let newRow = Array(repeating: 0, count: n) adjMat.append(newRow) // 隣接行列に 1 列追加 for i in adjMat.indices { adjMat[i].append(0) } } /* 頂点を削除 */ func removeVertex(index: Int) { if index >= size() { fatalError("範囲外") } // 頂点リストから index の頂点を削除する vertices.remove(at: index) // 隣接行列で index 行を削除する adjMat.remove(at: index) // 隣接行列で index 列を削除する for i in adjMat.indices { adjMat[i].remove(at: index) } } /* 辺を追加 */ // 引数 i, j は vertices の要素インデックスに対応する func addEdge(i: Int, j: Int) { // インデックスの範囲外と等値の処理 if i < 0 || j < 0 || i >= size() || j >= size() || i == j { fatalError("範囲外") } // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす adjMat[i][j] = 1 adjMat[j][i] = 1 } /* 辺を削除 */ // 引数 i, j は vertices の要素インデックスに対応する func removeEdge(i: Int, j: Int) { // インデックスの範囲外と等値の処理 if i < 0 || j < 0 || i >= size() || j >= size() || i == j { fatalError("範囲外") } adjMat[i][j] = 0 adjMat[j][i] = 0 } /* 隣接行列を出力 */ func print() { Swift.print("頂点リスト = ", terminator: "") Swift.print(vertices) Swift.print("隣接行列 =") PrintUtil.printMatrix(matrix: adjMat) } } @main enum GraphAdjacencyMatrix { /* Driver Code */ static func main() { /* 無向グラフを初期化 */ // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 let vertices = [1, 3, 2, 5, 4] let edges = [[0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4]] let graph = GraphAdjMat(vertices: vertices, edges: edges) print("\n初期化後のグラフ") graph.print() /* 辺を追加 */ // 頂点 1, 2 のインデックスはそれぞれ 0, 2 graph.addEdge(i: 0, j: 2) print("\n辺 1-2 を追加後のグラフ") graph.print() /* 辺を削除 */ // 頂点 1, 3 のインデックスはそれぞれ 0, 1 graph.removeEdge(i: 0, j: 1) print("\n辺 1-3 を削除後のグラフ") graph.print() /* 頂点を追加 */ graph.addVertex(val: 6) print("\n頂点 6 を追加後のグラフ") graph.print() /* 頂点を削除 */ // 頂点 3 のインデックスは 1 graph.removeVertex(index: 1) print("\n頂点 3 を削除後のグラフ") graph.print() } } ================================================ FILE: ja/codes/swift/chapter_graph/graph_bfs.swift ================================================ /** * File: graph_bfs.swift * Created Time: 2023-02-21 * Author: nuomi1 (nuomi1@qq.com) */ import graph_adjacency_list_target import utils /* 幅優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { // 頂点の走査順序 var res: [Vertex] = [] // 訪問済み頂点を記録するためのハッシュ集合 var visited: Set = [startVet] // BFS の実装にキューを用いる var que: [Vertex] = [startVet] // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す while !que.isEmpty { let vet = que.removeFirst() // 先頭の頂点をデキュー res.append(vet) // 訪問した頂点を記録 // この頂点のすべての隣接頂点を走査 for adjVet in graph.adjList[vet] ?? [] { if visited.contains(adjVet) { continue // 訪問済みの頂点をスキップ } que.append(adjVet) // 未訪問の頂点のみをキューに追加 visited.insert(adjVet) // この頂点を訪問済みにする } } // 頂点の走査順を返す return res } @main enum GraphBFS { /* Driver Code */ static func main() { /* 無向グラフを初期化 */ let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) let edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ] let graph = GraphAdjList(edges: edges) print("\n初期化後のグラフ") graph.print() /* 幅優先探索 */ let res = graphBFS(graph: graph, startVet: v[0]) print("\n幅優先探索(BFS)の頂点列") print(Vertex.vetsToVals(vets: res)) } } ================================================ FILE: ja/codes/swift/chapter_graph/graph_dfs.swift ================================================ /** * File: graph_dfs.swift * Created Time: 2023-02-21 * Author: nuomi1 (nuomi1@qq.com) */ import graph_adjacency_list_target import utils /* 深さ優先走査の補助関数 */ func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { res.append(vet) // 訪問した頂点を記録 visited.insert(vet) // この頂点を訪問済みにする // この頂点のすべての隣接頂点を走査 for adjVet in graph.adjList[vet] ?? [] { if visited.contains(adjVet) { continue // 訪問済みの頂点をスキップ } // 隣接頂点を再帰的に訪問 dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) } } /* 深さ優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { // 頂点の走査順序 var res: [Vertex] = [] // 訪問済み頂点を記録するためのハッシュ集合 var visited: Set = [] dfs(graph: graph, visited: &visited, res: &res, vet: startVet) return res } @main enum GraphDFS { /* Driver Code */ static func main() { /* 無向グラフを初期化 */ let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6]) let edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ] let graph = GraphAdjList(edges: edges) print("\n初期化後のグラフ") graph.print() /* 深さ優先探索 */ let res = graphDFS(graph: graph, startVet: v[0]) print("\n深さ優先探索(DFS)の頂点列") print(Vertex.vetsToVals(vets: res)) } } ================================================ FILE: ja/codes/swift/chapter_greedy/coin_change_greedy.swift ================================================ /** * File: coin_change_greedy.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* コイン交換:貪欲法 */ func coinChangeGreedy(coins: [Int], amt: Int) -> Int { // coins リストはソート済みと仮定する var i = coins.count - 1 var count = 0 var amt = amt // 残額がなくなるまで貪欲選択を繰り返す while amt > 0 { // 残額以下で最も近い硬貨を見つける while i > 0 && coins[i] > amt { i -= 1 } // coins[i] を選択する amt -= coins[i] count += 1 } // 実行可能な解が見つからなければ -1 を返す return amt == 0 ? count : -1 } @main enum CoinChangeGreedy { /* Driver Code */ static func main() { // 貪欲法:大域最適解を保証できる var coins = [1, 5, 10, 20, 50, 100] var amt = 186 var res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("\(amt) を作るのに必要な最小硬貨枚数は \(res)") // 貪欲法:大域最適解を保証できない coins = [1, 20, 50] amt = 60 res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("\(amt) を作るのに必要な最小硬貨枚数は \(res)") print("実際に必要な最小枚数は 3、つまり 20 + 20 + 20") // 貪欲法:大域最適解を保証できない coins = [1, 49, 50] amt = 98 res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("\(amt) を作るのに必要な最小硬貨枚数は \(res)") print("実際に必要な最小枚数は 2、つまり 49 + 49") } } ================================================ FILE: ja/codes/swift/chapter_greedy/fractional_knapsack.swift ================================================ /** * File: fractional_knapsack.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* 品物 */ class Item { var w: Int // 品物の重さ var v: Int // 品物の価値 init(w: Int, v: Int) { self.w = w self.v = v } } /* 分数ナップサック:貪欲法 */ func fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double { // 重さと価値の 2 属性を持つ品物リストを作成 var items = zip(wgt, val).map { Item(w: $0, v: $1) } // 単位価値 item.v / item.w の高い順にソートする items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) } // 貪欲選択を繰り返す var res = 0.0 var cap = cap for item in items { if item.w <= cap { // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる res += Double(item.v) cap -= item.w } else { // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる res += Double(item.v) / Double(item.w) * Double(cap) // 残り容量がないため、ループを抜ける break } } return res } @main enum FractionalKnapsack { /* Driver Code */ static func main() { // 品物の重さ let wgt = [10, 20, 30, 40, 50] // 品物の価値 let val = [50, 120, 150, 210, 240] // ナップサック容量 let cap = 50 // 貪欲法 let res = fractionalKnapsack(wgt: wgt, val: val, cap: cap) print("ナップサック容量を超えない最大価値は \(res)") } } ================================================ FILE: ja/codes/swift/chapter_greedy/max_capacity.swift ================================================ /** * File: max_capacity.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* 最大容量:貪欲法 */ func maxCapacity(ht: [Int]) -> Int { // i, j を初期化し、それぞれ配列の両端に置く var i = ht.startIndex, j = ht.endIndex - 1 // 初期の最大容量は 0 var res = 0 // 2 枚の板が出会うまで貪欲選択を繰り返す while i < j { // 最大容量を更新する let cap = min(ht[i], ht[j]) * (j - i) res = max(res, cap) // 短い方を内側へ動かす if ht[i] < ht[j] { i += 1 } else { j -= 1 } } return res } @main enum MaxCapacity { /* Driver Code */ static func main() { let ht = [3, 8, 5, 2, 7, 7, 3, 4] // 貪欲法 let res = maxCapacity(ht: ht) print("最大容量は \(res)") } } ================================================ FILE: ja/codes/swift/chapter_greedy/max_product_cutting.swift ================================================ /** * File: max_product_cutting.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ import Foundation func pow(_ x: Int, _ y: Int) -> Int { Int(Double(truncating: pow(Decimal(x), y) as NSDecimalNumber)) } /* 最大切断積:貪欲法 */ func maxProductCutting(n: Int) -> Int { // n <= 3 のときは、必ず 1 を切り出す if n <= 3 { return 1 * (n - 1) } // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする let a = n / 3 let b = n % 3 if b == 1 { // 余りが 1 のときは、1 * 3 を 2 * 2 に変える return pow(3, a - 1) * 2 * 2 } if b == 2 { // 余りが 2 のときは、そのままにする return pow(3, a) * 2 } // 余りが 0 のときは、そのままにする return pow(3, a) } @main enum MaxProductCutting { static func main() { let n = 58 // 貪欲法 let res = maxProductCutting(n: n) print("最大分割積は \(res)") } } ================================================ FILE: ja/codes/swift/chapter_hashing/array_hash_map.swift ================================================ /** * File: array_hash_map.swift * Created Time: 2023-01-16 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 配列ベースのハッシュテーブル */ class ArrayHashMap { private var buckets: [Pair?] init() { // 100 個のバケットを含む配列を初期化 buckets = Array(repeating: nil, count: 100) } /* ハッシュ関数 */ private func hashFunc(key: Int) -> Int { let index = key % 100 return index } /* 検索操作 */ func get(key: Int) -> String? { let index = hashFunc(key: key) let pair = buckets[index] return pair?.val } /* 追加操作 */ func put(key: Int, val: String) { let pair = Pair(key: key, val: val) let index = hashFunc(key: key) buckets[index] = pair } /* 削除操作 */ func remove(key: Int) { let index = hashFunc(key: key) // nil に設定し、削除を表す buckets[index] = nil } /* すべてのキーと値のペアを取得 */ func pairSet() -> [Pair] { buckets.compactMap { $0 } } /* すべてのキーを取得 */ func keySet() -> [Int] { buckets.compactMap { $0?.key } } /* すべての値を取得 */ func valueSet() -> [String] { buckets.compactMap { $0?.val } } /* ハッシュテーブルを出力 */ func print() { for pair in pairSet() { Swift.print("\(pair.key) -> \(pair.val)") } } } @main enum _ArrayHashMap { /* Driver Code */ static func main() { /* ハッシュテーブルを初期化 */ let map = ArrayHashMap() /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.put(key: 12836, val: "シャオハー") map.put(key: 15937, val: "シャオルオ") map.put(key: 16750, val: "シャオスワン") map.put(key: 13276, val: "シャオファー") map.put(key: 10583, val: "シャオヤー") print("\n追加完了後のハッシュテーブルは\nKey -> Value") map.print() /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 let name = map.get(key: 15937)! print("\n学籍番号 15937 を入力すると、名前 \(name) が見つかりました") /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(key: 10583) print("\n10583 を削除後のハッシュテーブルは\nKey -> Value") map.print() /* ハッシュテーブルを走査 */ print("\nキーと値の組 Key->Value を走査") for pair in map.pairSet() { print("\(pair.key) -> \(pair.val)") } print("\nキー Key を個別に走査") for key in map.keySet() { print(key) } print("\n値 Value を個別に走査") for val in map.valueSet() { print(val) } } } ================================================ FILE: ja/codes/swift/chapter_hashing/built_in_hash.swift ================================================ /** * File: built_in_hash.swift * Created Time: 2023-07-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum BuiltInHash { /* Driver Code */ static func main() { let num = 3 let hashNum = num.hashValue print("整数 \(num) のハッシュ値は \(hashNum)") let bol = true let hashBol = bol.hashValue print("真偽値 \(bol) のハッシュ値は \(hashBol)") let dec = 3.14159 let hashDec = dec.hashValue print("小数 \(dec) のハッシュ値は \(hashDec)") let str = "Hello アルゴリズム" let hashStr = str.hashValue print("文字列 \(str) のハッシュ値は \(hashStr)") let arr = [AnyHashable(12836), AnyHashable("シャオハー")] let hashTup = arr.hashValue print("配列 \(arr) のハッシュ値は \(hashTup)") let obj = ListNode(x: 0) let hashObj = obj.hashValue print("ノードオブジェクト \(obj) のハッシュ値は \(hashObj)") } } ================================================ FILE: ja/codes/swift/chapter_hashing/hash_map.swift ================================================ /** * File: hash_map.swift * Created Time: 2023-01-16 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum HashMap { /* Driver Code */ static func main() { /* ハッシュテーブルを初期化 */ var map: [Int: String] = [:] /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map[12836] = "シャオハー" map[15937] = "シャオルオ" map[16750] = "シャオスワン" map[13276] = "シャオファー" map[10583] = "シャオヤー" print("\n追加完了後のハッシュテーブルは\nKey -> Value") PrintUtil.printHashMap(map: map) /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 let name = map[15937]! print("\n学籍番号 15937 を入力すると、名前 \(name) が見つかりました") /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.removeValue(forKey: 10583) print("\n10583 を削除後のハッシュテーブルは\nKey -> Value") PrintUtil.printHashMap(map: map) /* ハッシュテーブルを走査 */ print("\nキーと値の組 Key->Value を走査") for (key, value) in map { print("\(key) -> \(value)") } print("\nキー Key を個別に走査") for key in map.keys { print(key) } print("\n値 Value を個別に走査") for value in map.values { print(value) } } } ================================================ FILE: ja/codes/swift/chapter_hashing/hash_map_chaining.swift ================================================ /** * File: hash_map_chaining.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* チェイン法ハッシュテーブル */ class HashMapChaining { var size: Int // キーと値のペア数 var capacity: Int // ハッシュテーブル容量 var loadThres: Double // リサイズを発動する負荷率のしきい値 var extendRatio: Int // 拡張倍率 var buckets: [[Pair]] // バケット配列 /* コンストラクタ */ init() { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = Array(repeating: [], count: capacity) } /* ハッシュ関数 */ func hashFunc(key: Int) -> Int { key % capacity } /* 負荷率 */ func loadFactor() -> Double { Double(size) / Double(capacity) } /* 検索操作 */ func get(key: Int) -> String? { let index = hashFunc(key: key) let bucket = buckets[index] // バケットを走査し、key が見つかれば対応する val を返す for pair in bucket { if pair.key == key { return pair.val } } // `key` が見つからなければ `nil` を返す return nil } /* 追加操作 */ func put(key: Int, val: String) { // 負荷率がしきい値を超えたら、リサイズを実行 if loadFactor() > loadThres { extend() } let index = hashFunc(key: key) let bucket = buckets[index] // バケットを走査し、指定した key が見つかれば対応する val を更新して返す for pair in bucket { if pair.key == key { pair.val = val return } } // その key が存在しなければ、キーと値のペアを末尾に追加 let pair = Pair(key: key, val: val) buckets[index].append(pair) size += 1 } /* 削除操作 */ func remove(key: Int) { let index = hashFunc(key: key) let bucket = buckets[index] // バケットを走査してキーと値のペアを削除 for (pairIndex, pair) in bucket.enumerated() { if pair.key == key { buckets[index].remove(at: pairIndex) size -= 1 break } } } /* ハッシュテーブルを拡張 */ func extend() { // 元のハッシュテーブルを一時保存 let bucketsTmp = buckets // リサイズ後の新しいハッシュテーブルを初期化 capacity *= extendRatio buckets = Array(repeating: [], count: capacity) size = 0 // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for bucket in bucketsTmp { for pair in bucket { put(key: pair.key, val: pair.val) } } } /* ハッシュテーブルを出力 */ func print() { for bucket in buckets { let res = bucket.map { "\($0.key) -> \($0.val)" } Swift.print(res) } } } @main enum _HashMapChaining { /* Driver Code */ static func main() { /* ハッシュテーブルを初期化 */ let map = HashMapChaining() /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.put(key: 12836, val: "シャオハー") map.put(key: 15937, val: "シャオルオ") map.put(key: 16750, val: "シャオスワン") map.put(key: 13276, val: "シャオファー") map.put(key: 10583, val: "シャオヤー") print("\n追加完了後のハッシュテーブルは\nKey -> Value") map.print() /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 let name = map.get(key: 13276) print("\n学籍番号 13276 を入力すると、名前 \(name!) が見つかりました") /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(key: 12836) print("\n12836 を削除後、ハッシュテーブルは\nKey -> Value") map.print() } } ================================================ FILE: ja/codes/swift/chapter_hashing/hash_map_open_addressing.swift ================================================ /** * File: hash_map_open_addressing.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* オープンアドレス法ハッシュテーブル */ class HashMapOpenAddressing { var size: Int // キーと値のペア数 var capacity: Int // ハッシュテーブル容量 var loadThres: Double // リサイズを発動する負荷率のしきい値 var extendRatio: Int // 拡張倍率 var buckets: [Pair?] // バケット配列 var TOMBSTONE: Pair // 削除済みマーク /* コンストラクタ */ init() { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = Array(repeating: nil, count: capacity) TOMBSTONE = Pair(key: -1, val: "-1") } /* ハッシュ関数 */ func hashFunc(key: Int) -> Int { key % capacity } /* 負荷率 */ func loadFactor() -> Double { Double(size) / Double(capacity) } /* key に対応するバケットインデックスを探す */ func findBucket(key: Int) -> Int { var index = hashFunc(key: key) var firstTombstone = -1 // 線形プロービングを行い、空バケットに達したら終了 while buckets[index] != nil { // key が見つかったら、対応するバケットのインデックスを返す if buckets[index]!.key == key { // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 if firstTombstone != -1 { buckets[firstTombstone] = buckets[index] buckets[index] = TOMBSTONE return firstTombstone // 移動後のバケットインデックスを返す } return index // バケットのインデックスを返す } // 最初に見つかった削除マークを記録 if firstTombstone == -1 && buckets[index] == TOMBSTONE { firstTombstone = index } // バケットのインデックスを計算し、末尾を越えたら先頭に戻る index = (index + 1) % capacity } // key が存在しない場合は追加位置のインデックスを返す return firstTombstone == -1 ? index : firstTombstone } /* 検索操作 */ func get(key: Int) -> String? { // key に対応するバケットインデックスを探す let index = findBucket(key: key) // キーと値の組が見つかったら、対応する val を返す if buckets[index] != nil, buckets[index] != TOMBSTONE { return buckets[index]!.val } // キーと値の組が存在しなければ null を返す return nil } /* 追加操作 */ func put(key: Int, val: String) { // 負荷率がしきい値を超えたら、リサイズを実行 if loadFactor() > loadThres { extend() } // key に対応するバケットインデックスを探す let index = findBucket(key: key) // キーと値の組が見つかったら、val を上書きして返す if buckets[index] != nil, buckets[index] != TOMBSTONE { buckets[index]!.val = val return } // キーと値の組が存在しない場合は、その組を追加する buckets[index] = Pair(key: key, val: val) size += 1 } /* 削除操作 */ func remove(key: Int) { // key に対応するバケットインデックスを探す let index = findBucket(key: key) // キーと値の組が見つかったら、削除マーカーで上書きする if buckets[index] != nil, buckets[index] != TOMBSTONE { buckets[index] = TOMBSTONE size -= 1 } } /* ハッシュテーブルを拡張 */ func extend() { // 元のハッシュテーブルを一時保存 let bucketsTmp = buckets // リサイズ後の新しいハッシュテーブルを初期化 capacity *= extendRatio buckets = Array(repeating: nil, count: capacity) size = 0 // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for pair in bucketsTmp { if let pair, pair != TOMBSTONE { put(key: pair.key, val: pair.val) } } } /* ハッシュテーブルを出力 */ func print() { for pair in buckets { if pair == nil { Swift.print("null") } else if pair == TOMBSTONE { Swift.print("TOMBSTONE") } else { Swift.print("\(pair!.key) -> \(pair!.val)") } } } } @main enum _HashMapOpenAddressing { /* Driver Code */ static func main() { /* ハッシュテーブルを初期化 */ let map = HashMapOpenAddressing() /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.put(key: 12836, val: "シャオハー") map.put(key: 15937, val: "シャオルオ") map.put(key: 16750, val: "シャオスワン") map.put(key: 13276, val: "シャオファー") map.put(key: 10583, val: "シャオヤー") print("\n追加完了後のハッシュテーブルは\nKey -> Value") map.print() /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 let name = map.get(key: 13276) print("\n学籍番号 13276 を入力すると、名前 \(name!) が見つかりました") /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(key: 16750) print("\n16750 を削除後、ハッシュテーブルは\nKey -> Value") map.print() } } ================================================ FILE: ja/codes/swift/chapter_hashing/simple_hash.swift ================================================ /** * File: simple_hash.swift * Created Time: 2023-07-01 * Author: nuomi1 (nuomi1@qq.com) */ /* 加算ハッシュ */ func addHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = (hash + Int(scalar.value)) % MODULUS } } return hash } /* 乗算ハッシュ */ func mulHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = (31 * hash + Int(scalar.value)) % MODULUS } } return hash } /* XOR ハッシュ */ func xorHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash ^= Int(scalar.value) } } return hash & MODULUS } /* 回転ハッシュ */ func rotHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS } } return hash } @main enum SimpleHash { /* Driver Code */ static func main() { let key = "Hello アルゴリズム" var hash = addHash(key: key) print("加算ハッシュ値は \(hash)") hash = mulHash(key: key) print("乗算ハッシュ値は \(hash)") hash = xorHash(key: key) print("XORハッシュ値は \(hash)") hash = rotHash(key: key) print("回転ハッシュ値は \(hash)") } } ================================================ FILE: ja/codes/swift/chapter_heap/heap.swift ================================================ /** * File: heap.swift * Created Time: 2024-03-17 * Author: nuomi1 (nuomi1@qq.com) */ import HeapModule import utils func testPush(heap: inout Heap, val: Int) { heap.insert(val) print("\n要素 \(val) をヒープに追加した後\n") PrintUtil.printHeap(queue: heap.unordered) } func testPop(heap: inout Heap) { let val = heap.removeMax() print("\nヒープ先頭要素 \(val) を取り出した後\n") PrintUtil.printHeap(queue: heap.unordered) } @main enum _Heap { /* Driver Code */ static func main() { /* ヒープを初期化 */ // Swift の Heap 型は最大ヒープと最小ヒープの両方をサポートする var heap = Heap() /* 要素をヒープに追加 */ testPush(heap: &heap, val: 1) testPush(heap: &heap, val: 3) testPush(heap: &heap, val: 2) testPush(heap: &heap, val: 5) testPush(heap: &heap, val: 4) /* ヒープ頂点の要素を取得 */ let peek = heap.max() print("\nヒープ先頭要素は \(peek!)\n") /* ヒープ頂点の要素を取り出す */ testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) /* ヒープのサイズを取得 */ let size = heap.count print("\nヒープ内の要素数は \(size)\n") /* ヒープが空かどうかを判定 */ let isEmpty = heap.isEmpty print("\nヒープが空かどうか \(isEmpty)\n") /* リストを入力してヒープを構築 */ // 時間計算量は O(n) であり、O(nlogn) ではない let heap2 = Heap([1, 3, 2, 5, 4]) print("\nリストを入力してヒープを構築した後") PrintUtil.printHeap(queue: heap2.unordered) } } ================================================ FILE: ja/codes/swift/chapter_heap/my_heap.swift ================================================ /** * File: my_heap.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 最大ヒープ */ class MaxHeap { private var maxHeap: [Int] /* コンストラクタ。入力リストに基づいてヒープを構築する */ init(nums: [Int]) { // リスト要素をそのままヒープに追加 maxHeap = nums // 葉ノード以外のすべてのノードをヒープ化 for i in (0 ... parent(i: size() - 1)).reversed() { siftDown(i: i) } } /* 左子ノードのインデックスを取得 */ private func left(i: Int) -> Int { 2 * i + 1 } /* 右子ノードのインデックスを取得 */ private func right(i: Int) -> Int { 2 * i + 2 } /* 親ノードのインデックスを取得 */ private func parent(i: Int) -> Int { (i - 1) / 2 // 切り捨て除算 } /* 要素を交換 */ private func swap(i: Int, j: Int) { maxHeap.swapAt(i, j) } /* ヒープのサイズを取得 */ func size() -> Int { maxHeap.count } /* ヒープが空かどうかを判定 */ func isEmpty() -> Bool { size() == 0 } /* ヒープ先頭要素にアクセス */ func peek() -> Int { maxHeap[0] } /* 要素をヒープに追加 */ func push(val: Int) { // ノードを追加 maxHeap.append(val) // 下から上へヒープ化 siftUp(i: size() - 1) } /* ノード i から始めて、下から上へヒープ化 */ private func siftUp(i: Int) { var i = i while true { // ノード i の親ノードを取得 let p = parent(i: i) // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 if p < 0 || maxHeap[i] <= maxHeap[p] { break } // 2 つのノードを交換 swap(i: i, j: p) // ループで下から上へヒープ化 i = p } } /* 要素をヒープから取り出す */ func pop() -> Int { // 空判定の処理 if isEmpty() { fatalError("ヒープが空です") } // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) swap(i: 0, j: size() - 1) // ノードを削除 let val = maxHeap.remove(at: size() - 1) // 上から下へヒープ化 siftDown(i: 0) // ヒープ先頭要素を返す return val } /* ノード i から始めて、上から下へヒープ化 */ private func siftDown(i: Int) { var i = i while true { // ノード i, l, r のうち値が最大のノードを ma とする let l = left(i: i) let r = right(i: i) var ma = i if l < size(), maxHeap[l] > maxHeap[ma] { ma = l } if r < size(), maxHeap[r] > maxHeap[ma] { ma = r } // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if ma == i { break } // 2 つのノードを交換 swap(i: i, j: ma) // ループで上から下へヒープ化 i = ma } } /* ヒープ(二分木)を出力 */ func print() { let queue = maxHeap PrintUtil.printHeap(queue: queue) } } @main enum MyHeap { /* Driver Code */ static func main() { /* 最大ヒープを初期化 */ let maxHeap = MaxHeap(nums: [9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) print("\nリストを入力してヒープを構築した後") maxHeap.print() /* ヒープ頂点の要素を取得 */ var peek = maxHeap.peek() print("\nヒープ先頭要素は \(peek)") /* 要素をヒープに追加 */ let val = 7 maxHeap.push(val: val) print("\n要素 \(val) をヒープに追加した後") maxHeap.print() /* ヒープ頂点の要素を取り出す */ peek = maxHeap.pop() print("\nヒープ先頭要素 \(peek) を取り出した後") maxHeap.print() /* ヒープのサイズを取得 */ let size = maxHeap.size() print("\nヒープ内の要素数は \(size)") /* ヒープが空かどうかを判定 */ let isEmpty = maxHeap.isEmpty() print("\nヒープが空かどうか \(isEmpty)") } } ================================================ FILE: ja/codes/swift/chapter_heap/top_k.swift ================================================ /** * File: top_k.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ import HeapModule import utils /* ヒープに基づいて配列中の最大の k 個の要素を探す */ func topKHeap(nums: [Int], k: Int) -> [Int] { // 最小ヒープを初期化し、先頭 k 個の要素でヒープを構築する var heap = Heap(nums.prefix(k)) // k+1 番目の要素から開始し、ヒープ長を k に保つ for i in nums.indices.dropFirst(k) { // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する if nums[i] > heap.min()! { _ = heap.removeMin() heap.insert(nums[i]) } } return heap.unordered } @main enum TopK { /* Driver Code */ static func main() { let nums = [1, 7, 6, 3, 2] let k = 3 let res = topKHeap(nums: nums, k: k) print("最大の \(k) 個の要素は") PrintUtil.printHeap(queue: res) } } ================================================ FILE: ja/codes/swift/chapter_searching/binary_search.swift ================================================ /** * File: binary_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ /* 二分探索(両閉区間) */ func binarySearch(nums: [Int], target: Int) -> Int { // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す var i = nums.startIndex var j = nums.endIndex - 1 // ループし、探索区間が空になったら終了する(i > j で空) while i <= j { let m = i + (j - i) / 2 // 中点インデックス m を計算 if nums[m] < target { // この場合、target は区間 [m+1, j] にある i = m + 1 } else if nums[m] > target { // この場合、target は区間 [i, m-1] にある j = m - 1 } else { // 目標要素が見つかったらそのインデックスを返す return m } } // 目標要素が見つからなければ -1 を返す return -1 } /* 二分探索(左閉右開区間) */ func binarySearchLCRO(nums: [Int], target: Int) -> Int { // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す var i = nums.startIndex var j = nums.endIndex // ループし、探索区間が空になったら終了する(i = j で空) while i < j { let m = i + (j - i) / 2 // 中点インデックス m を計算 if nums[m] < target { // この場合、target は区間 [m+1, j) にある i = m + 1 } else if nums[m] > target { // この場合、target は区間 [i, m) にある j = m } else { // 目標要素が見つかったらそのインデックスを返す return m } } // 目標要素が見つからなければ -1 を返す return -1 } @main enum BinarySearch { /* Driver Code */ static func main() { let target = 6 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] /* 二分探索(両閉区間) */ var index = binarySearch(nums: nums, target: target) print("対象要素 6 のインデックス = \(index)") /* 二分探索(左閉右開区間) */ index = binarySearchLCRO(nums: nums, target: target) print("対象要素 6 のインデックス = \(index)") } } ================================================ FILE: ja/codes/swift/chapter_searching/binary_search_edge.swift ================================================ /** * File: binary_search_edge.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ import binary_search_insertion_target /* 最も左の target を二分探索 */ func binarySearchLeftEdge(nums: [Int], target: Int) -> Int { // target の挿入位置を探すのと等価 let i = binarySearchInsertion(nums: nums, target: target) // target が見つからなければ、-1 を返す if i == nums.endIndex || nums[i] != target { return -1 } // target が見つかったら、インデックス i を返す return i } /* 最も右の target を二分探索 */ func binarySearchRightEdge(nums: [Int], target: Int) -> Int { // 最左の target + 1 を探す問題に変換する let i = binarySearchInsertion(nums: nums, target: target + 1) // j は最も右の target を指し、i は target より大きい最初の要素を指す let j = i - 1 // target が見つからなければ、-1 を返す if j == -1 || nums[j] != target { return -1 } // target が見つかったら、インデックス j を返す return j } @main enum BinarySearchEdge { /* Driver Code */ static func main() { // 重複要素を含む配列 let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\n配列 nums = \(nums)") // 二分探索で左端と右端を探す for target in [6, 7] { var index = binarySearchLeftEdge(nums: nums, target: target) print("最も左にある要素 \(target) のインデックスは \(index)") index = binarySearchRightEdge(nums: nums, target: target) print("最も右にある要素 \(target) のインデックスは \(index)") } } } ================================================ FILE: ja/codes/swift/chapter_searching/binary_search_insertion.swift ================================================ /** * File: binary_search_insertion.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ /* 二分探索で挿入位置を探す(重複要素なし) */ func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { // 両閉区間 [0, n-1] を初期化 var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // 中点インデックス m を計算 if nums[m] < target { i = m + 1 // target は区間 [m+1, j] にある } else if nums[m] > target { j = m - 1 // target は区間 [i, m-1] にある } else { return m // target が見つかったら、挿入位置 m を返す } } // target が見つからなければ、挿入位置 i を返す return i } /* 二分探索で挿入位置を探す(重複要素あり) */ public func binarySearchInsertion(nums: [Int], target: Int) -> Int { // 両閉区間 [0, n-1] を初期化 var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // 中点インデックス m を計算 if nums[m] < target { i = m + 1 // target は区間 [m+1, j] にある } else if nums[m] > target { j = m - 1 // target は区間 [i, m-1] にある } else { j = m - 1 // target より小さい最初の要素は区間 [i, m-1] にある } } // 挿入位置 i を返す return i } #if !TARGET @main enum BinarySearchInsertion { /* Driver Code */ static func main() { // 重複要素のない配列 var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print("\n配列 nums = \(nums)") // 二分探索で挿入位置を探す for target in [6, 9] { let index = binarySearchInsertionSimple(nums: nums, target: target) print("要素 \(target) の挿入位置のインデックスは \(index)") } // 重複要素を含む配列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\n配列 nums = \(nums)") // 二分探索で挿入位置を探す for target in [2, 6, 20] { let index = binarySearchInsertion(nums: nums, target: target) print("要素 \(target) の挿入位置のインデックスは \(index)") } } } #endif ================================================ FILE: ja/codes/swift/chapter_searching/binary_search_insertion_target.swift ================================================ /** * File: binary_search_insertion.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ /* 二分探索で挿入位置を探す(重複要素なし) */ func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { // 両閉区間 [0, n-1] を初期化 var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // 中点インデックス m を計算 if nums[m] < target { i = m + 1 // target は区間 [m+1, j] にある } else if nums[m] > target { j = m - 1 // target は区間 [i, m-1] にある } else { return m // target が見つかったら、挿入位置 m を返す } } // target が見つからなければ、挿入位置 i を返す return i } /* 二分探索で挿入位置を探す(重複要素あり) */ public func binarySearchInsertion(nums: [Int], target: Int) -> Int { // 両閉区間 [0, n-1] を初期化 var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // 中点インデックス m を計算 if nums[m] < target { i = m + 1 // target は区間 [m+1, j] にある } else if nums[m] > target { j = m - 1 // target は区間 [i, m-1] にある } else { j = m - 1 // target より小さい最初の要素は区間 [i, m-1] にある } } // 挿入位置 i を返す return i } #if !TARGET @main enum BinarySearchInsertion { /* Driver Code */ static func main() { // 重複要素のない配列 var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print("\n配列 nums = \(nums)") // 二分探索で挿入位置を探す for target in [6, 9] { let index = binarySearchInsertionSimple(nums: nums, target: target) print("要素 \(target) の挿入位置のインデックスは \(index)") } // 重複要素を含む配列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\n配列 nums = \(nums)") // 二分探索で挿入位置を探す for target in [2, 6, 20] { let index = binarySearchInsertion(nums: nums, target: target) print("要素 \(target) の挿入位置のインデックスは \(index)") } } } #endif ================================================ FILE: ja/codes/swift/chapter_searching/hashing_search.swift ================================================ /** * File: hashing_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* ハッシュ探索(配列) */ func hashingSearchArray(map: [Int: Int], target: Int) -> Int { // ハッシュテーブルの key: 目標要素、value: インデックス // ハッシュテーブルにこの key がなければ -1 を返す return map[target, default: -1] } /* ハッシュ探索(連結リスト) */ func hashingSearchLinkedList(map: [Int: ListNode], target: Int) -> ListNode? { // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト // ハッシュテーブルにこの key がなければ null を返す return map[target] } @main enum HashingSearch { /* Driver Code */ static func main() { let target = 3 /* ハッシュ探索(配列) */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] // ハッシュテーブルを初期化 var map: [Int: Int] = [:] for i in nums.indices { map[nums[i]] = i // key: 要素、value: インデックス } let index = hashingSearchArray(map: map, target: target) print("目標要素 3 のインデックス = \(index)") /* ハッシュ探索(連結リスト) */ var head = ListNode.arrToLinkedList(arr: nums) // ハッシュテーブルを初期化 var map1: [Int: ListNode] = [:] while head != nil { map1[head!.val] = head! // key: ノード値、value: ノード head = head?.next } let node = hashingSearchLinkedList(map: map1, target: target) print("目標ノード値 3 に対応するノードオブジェクトは \(node!)") } } ================================================ FILE: ja/codes/swift/chapter_searching/linear_search.swift ================================================ /** * File: linear_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 線形探索(配列) */ func linearSearchArray(nums: [Int], target: Int) -> Int { // 配列を走査 for i in nums.indices { // 目標要素が見つかったらそのインデックスを返す if nums[i] == target { return i } } // 目標要素が見つからなければ -1 を返す return -1 } /* 線形探索(連結リスト) */ func linearSearchLinkedList(head: ListNode?, target: Int) -> ListNode? { var head = head // 連結リストを走査 while head != nil { // 対象ノードが見つかったら、それを返す if head?.val == target { return head } head = head?.next } // 対象ノードが見つからない場合は null を返す return nil } @main enum LinearSearch { /* Driver Code */ static func main() { let target = 3 /* 配列で線形探索を行う */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] let index = linearSearchArray(nums: nums, target: target) print("目標要素 3 のインデックス = \(index)") /* 連結リストで線形探索を行う */ let head = ListNode.arrToLinkedList(arr: nums) let node = linearSearchLinkedList(head: head, target: target) print("目標ノード値 3 に対応するノードオブジェクトは \(node!)") } } ================================================ FILE: ja/codes/swift/chapter_searching/two_sum.swift ================================================ /** * File: two_sum.swift * Created Time: 2023-01-03 * Author: nuomi1 (nuomi1@qq.com) */ /* 方法 1:総当たり列挙 */ func twoSumBruteForce(nums: [Int], target: Int) -> [Int] { // 2重ループのため、時間計算量は O(n^2) for i in nums.indices.dropLast() { for j in nums.indices.dropFirst(i + 1) { if nums[i] + nums[j] == target { return [i, j] } } } return [0] } /* 方法 2:補助ハッシュテーブル */ func twoSumHashTable(nums: [Int], target: Int) -> [Int] { // 補助ハッシュテーブルを使用し、空間計算量は O(n) var dic: [Int: Int] = [:] // 単一ループで、時間計算量は O(n) for i in nums.indices { if let j = dic[target - nums[i]] { return [j, i] } dic[nums[i]] = i } return [0] } @main enum LeetcodeTwoSum { /* Driver Code */ static func main() { // ======= Test Case ======= let nums = [2, 7, 11, 15] let target = 13 // ====== Driver Code ====== // 方法 1 var res = twoSumBruteForce(nums: nums, target: target) print("方法1 res = \(res)") // 方法 2 res = twoSumHashTable(nums: nums, target: target) print("方法2 res = \(res)") } } ================================================ FILE: ja/codes/swift/chapter_sorting/bubble_sort.swift ================================================ /** * File: bubble_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* バブルソート */ func bubbleSort(nums: inout [Int]) { // 外側のループ:未ソート区間は [0, i] for i in nums.indices.dropFirst().reversed() { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j in 0 ..< i { if nums[j] > nums[j + 1] { // nums[j] と nums[j + 1] を交換 nums.swapAt(j, j + 1) } } } } /* バブルソート(フラグ最適化) */ func bubbleSortWithFlag(nums: inout [Int]) { // 外側のループ:未ソート区間は [0, i] for i in nums.indices.dropFirst().reversed() { var flag = false // フラグを初期化する for j in 0 ..< i { if nums[j] > nums[j + 1] { // nums[j] と nums[j + 1] を交換 nums.swapAt(j, j + 1) flag = true // 交換する要素を記録 } } if !flag { // このバブル処理で要素交換が一度もなければそのまま終了 break } } } @main enum BubbleSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] bubbleSort(nums: &nums) print("バブルソート完了後 nums = \(nums)") var nums1 = [4, 1, 3, 1, 5, 2] bubbleSortWithFlag(nums: &nums1) print("バブルソート完了後 nums1 = \(nums1)") } } ================================================ FILE: ja/codes/swift/chapter_sorting/bucket_sort.swift ================================================ /** * File: bucket_sort.swift * Created Time: 2023-03-27 * Author: nuomi1 (nuomi1@qq.com) */ /* バケットソート */ func bucketSort(nums: inout [Double]) { // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする let k = nums.count / 2 var buckets = (0 ..< k).map { _ in [Double]() } // 1. 配列要素を各バケットに振り分ける for num in nums { // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する let i = Int(num * Double(k)) // num をバケット i に追加 buckets[i].append(num) } // 2. 各バケットをソートする for i in buckets.indices { // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい buckets[i].sort() } // 3. バケットを走査して結果を結合 var i = nums.startIndex for bucket in buckets { for num in bucket { nums[i] = num i += 1 } } } @main enum BucketSort { /* Driver Code */ static func main() { // 入力データは範囲 [0, 1) の浮動小数点数とする var nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] bucketSort(nums: &nums) print("バケットソート完了後 nums = \(nums)") } } ================================================ FILE: ja/codes/swift/chapter_sorting/counting_sort.swift ================================================ /** * File: counting_sort.swift * Created Time: 2023-03-22 * Author: nuomi1 (nuomi1@qq.com) */ /* 計数ソート */ // 簡易実装のため、オブジェクトのソートには使えない func countingSortNaive(nums: inout [Int]) { // 1. 配列の最大要素 m を求める let m = nums.max()! // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す var counter = Array(repeating: 0, count: m + 1) for num in nums { counter[num] += 1 } // 3. counter を走査し、各要素を元の配列 nums に書き戻す var i = 0 for num in 0 ..< m + 1 { for _ in 0 ..< counter[num] { nums[i] = num i += 1 } } } /* 計数ソート */ // 完全な実装で、オブジェクトをソートでき、かつ安定ソートである func countingSort(nums: inout [Int]) { // 1. 配列の最大要素 m を求める let m = nums.max()! // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す var counter = Array(repeating: 0, count: m + 1) for num in nums { counter[num] += 1 } // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する // つまり counter[num]-1 は、num が res に最後に現れるインデックス for i in 0 ..< m { counter[i + 1] += counter[i] } // 4. nums を逆順に走査し、各要素を結果配列 res に格納する // 結果を記録するための配列 res を初期化 var res = Array(repeating: 0, count: nums.count) for i in nums.indices.reversed() { let num = nums[i] res[counter[num] - 1] = num // num を対応するインデックスに配置 counter[num] -= 1 // 累積和を 1 減らして、次に num を配置するインデックスを得る } // 結果配列 res で元の配列 nums を上書きする for i in nums.indices { nums[i] = res[i] } } @main enum CountingSort { /* Driver Code */ static func main() { var nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] countingSortNaive(nums: &nums) print("カウントソート(オブジェクトはソート不可)完了後 nums = \(nums)") var nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] countingSort(nums: &nums1) print("カウントソート完了後 nums1 = \(nums1)") } } ================================================ FILE: ja/codes/swift/chapter_sorting/heap_sort.swift ================================================ /** * File: heap_sort.swift * Created Time: 2023-05-28 * Author: nuomi1 (nuomi1@qq.com) */ /* ヒープの長さは n。ノード i から下方向にヒープ化 */ func siftDown(nums: inout [Int], n: Int, i: Int) { var i = i while true { // ノード i, l, r のうち値が最大のノードを ma とする let l = 2 * i + 1 let r = 2 * i + 2 var ma = i if l < n, nums[l] > nums[ma] { ma = l } if r < n, nums[r] > nums[ma] { ma = r } // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if ma == i { break } // 2 つのノードを交換 nums.swapAt(i, ma) // ループで上から下へヒープ化 i = ma } } /* ヒープソート */ func heapSort(nums: inout [Int]) { // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) { siftDown(nums: &nums, n: nums.count, i: i) } // ヒープから最大要素を取り出し、n-1 回繰り返す for i in nums.indices.dropFirst().reversed() { // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) nums.swapAt(0, i) // 根ノードを起点に、上から下へヒープ化 siftDown(nums: &nums, n: i, i: 0) } } @main enum HeapSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] heapSort(nums: &nums) print("ヒープソート完了後 nums = \(nums)") } } ================================================ FILE: ja/codes/swift/chapter_sorting/insertion_sort.swift ================================================ /** * File: insertion_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* 挿入ソート */ func insertionSort(nums: inout [Int]) { // 外側ループ:整列済み区間は [0, i-1] for i in nums.indices.dropFirst() { let base = nums[i] var j = i - 1 // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する while j >= 0, nums[j] > base { nums[j + 1] = nums[j] // nums[j] を 1 つ右へ移動する j -= 1 } nums[j + 1] = base // base を正しい位置に配置する } } @main enum InsertionSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] insertionSort(nums: &nums) print("挿入ソート完了後 nums = \(nums)") } } ================================================ FILE: ja/codes/swift/chapter_sorting/merge_sort.swift ================================================ /** * File: merge_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* 左部分配列と右部分配列をマージ */ func merge(nums: inout [Int], left: Int, mid: Int, right: Int) { // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] // マージ結果を格納する一時配列 tmp を作成 var tmp = Array(repeating: 0, count: right - left + 1) // 左右の部分配列の開始インデックスを初期化する var i = left, j = mid + 1, k = 0 // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする while i <= mid, j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i] i += 1 } else { tmp[k] = nums[j] j += 1 } k += 1 } // 左右の部分配列の残り要素を一時配列にコピーする while i <= mid { tmp[k] = nums[i] i += 1 k += 1 } while j <= right { tmp[k] = nums[j] j += 1 k += 1 } // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする for k in tmp.indices { nums[left + k] = tmp[k] } } /* マージソート */ func mergeSort(nums: inout [Int], left: Int, right: Int) { // 終了条件 if left >= right { // 部分配列の長さが 1 になったら再帰を終了 return } // 分割フェーズ let mid = left + (right - left) / 2 // 中点を計算 mergeSort(nums: &nums, left: left, right: mid) // 左部分配列を再帰処理 mergeSort(nums: &nums, left: mid + 1, right: right) // 右部分配列を再帰処理 // マージフェーズ merge(nums: &nums, left: left, mid: mid, right: right) } @main enum MergeSort { /* Driver Code */ static func main() { /* マージソート */ var nums = [7, 3, 2, 6, 0, 1, 5, 4] mergeSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) print("マージソート完了後 nums = \(nums)") } } ================================================ FILE: ja/codes/swift/chapter_sorting/quick_sort.swift ================================================ /** * File: quick_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* クイックソートクラス */ /* 番兵分割 */ func partition(nums: inout [Int], left: Int, right: Int) -> Int { // nums[left] を基準値とする var i = left var j = right while i < j { while i < j, nums[j] >= nums[left] { j -= 1 // 右から左へ基準値未満の最初の要素を探す } while i < j, nums[i] <= nums[left] { i += 1 // 左から右へ基準値より大きい最初の要素を探す } nums.swapAt(i, j) // この 2 つの要素を交換 } nums.swapAt(i, left) // 基準値を 2 つの部分配列の境界へ交換する return i // 基準値のインデックスを返す } /* クイックソート */ func quickSort(nums: inout [Int], left: Int, right: Int) { // 部分配列の長さが 1 なら再帰を終了する if left >= right { return } // 番兵分割 let pivot = partition(nums: &nums, left: left, right: right) // 左右の部分配列を再帰処理 quickSort(nums: &nums, left: left, right: pivot - 1) quickSort(nums: &nums, left: pivot + 1, right: right) } /* クイックソートクラス(中央値ピボット最適化) */ /* 3つの候補要素の中央値を選ぶ */ func medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int { let l = nums[left] let m = nums[mid] let r = nums[right] if (l <= m && m <= r) || (r <= m && m <= l) { return mid // m は l と r の間 } if (m <= l && l <= r) || (r <= l && l <= m) { return left // l は m と r の間 } return right } /* 番兵による分割処理(3 点中央値) */ func partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int { // 3つの候補要素の中央値を選ぶ let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right) // 中央値を配列の最左端に交換する nums.swapAt(left, med) return partition(nums: &nums, left: left, right: right) } /* クイックソート(中央値の基準値で最適化) */ func quickSortMedian(nums: inout [Int], left: Int, right: Int) { // 部分配列の長さが 1 なら再帰を終了する if left >= right { return } // 番兵分割 let pivot = partitionMedian(nums: &nums, left: left, right: right) // 左右の部分配列を再帰処理 quickSortMedian(nums: &nums, left: left, right: pivot - 1) quickSortMedian(nums: &nums, left: pivot + 1, right: right) } /* クイックソート(再帰深度最適化) */ func quickSortTailCall(nums: inout [Int], left: Int, right: Int) { var left = left var right = right // 部分配列の長さが 1 なら終了 while left < right { // 番兵による分割処理 let pivot = partition(nums: &nums, left: left, right: right) // 2 つの部分配列のうち短いほうにクイックソートを適用する if (pivot - left) < (right - pivot) { quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // 左部分配列を再帰的にソート left = pivot + 1 // 未ソート区間の残りは [pivot + 1, right] } else { quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // 右部分配列を再帰的にソート right = pivot - 1 // 未ソート区間の残りは [left, pivot - 1] } } } @main enum QuickSort { /* Driver Code */ static func main() { /* クイックソート */ var nums = [2, 4, 1, 0, 3, 5] quickSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) print("クイックソート完了後 nums = \(nums)") /* クイックソート(中央値の基準値で最適化) */ var nums1 = [2, 4, 1, 0, 3, 5] quickSortMedian(nums: &nums1, left: nums1.startIndex, right: nums1.endIndex - 1) print("クイックソート(中央値ピボット最適化)完了後 nums1 = \(nums1)") /* クイックソート(再帰深度最適化) */ var nums2 = [2, 4, 1, 0, 3, 5] quickSortTailCall(nums: &nums2, left: nums2.startIndex, right: nums2.endIndex - 1) print("クイックソート(再帰深度最適化)完了後 nums2 = \(nums2)") } } ================================================ FILE: ja/codes/swift/chapter_sorting/radix_sort.swift ================================================ /** * File: radix_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ func digit(num: Int, exp: Int) -> Int { // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す (num / exp) % 10 } /* 計数ソート(nums の k 桁目でソート) */ func countingSortDigit(nums: inout [Int], exp: Int) { // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 var counter = Array(repeating: 0, count: 10) // 0~9 の各数字の出現回数を集計する for i in nums.indices { let d = digit(num: nums[i], exp: exp) // nums[i] の第 k 位を取得し、d とする counter[d] += 1 // 数字 d の出現回数を数える } // 累積和を求め、「出現回数」を「配列インデックス」に変換する for i in 1 ..< 10 { counter[i] += counter[i - 1] } // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する var res = Array(repeating: 0, count: nums.count) for i in nums.indices.reversed() { let d = digit(num: nums[i], exp: exp) let j = counter[d] - 1 // d の配列内インデックス j を取得する res[j] = nums[i] // 現在の要素をインデックス j に格納する counter[d] -= 1 // d の個数を 1 減らす } // 結果で元の配列 nums を上書きする for i in nums.indices { nums[i] = res[i] } } /* 基数ソート */ func radixSort(nums: inout [Int]) { // 最大桁数の判定用に配列の最大要素を取得 var m = Int.min for num in nums { if num > m { m = num } } // 下位桁から上位桁の順に走査する for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) { // 配列要素の k 桁目に対して計数ソートを行う // k = 1 -> exp = 1 // k = 2 -> exp = 10 // つまり exp = 10^(k-1) countingSortDigit(nums: &nums, exp: exp) } } @main enum RadixSort { /* Driver Code */ static func main() { // 基数ソート var nums = [ 10_546_151, 35_663_510, 42_865_989, 34_862_445, 81_883_077, 88_906_420, 72_429_244, 30_524_779, 82_060_337, 63_832_996, ] radixSort(nums: &nums) print("基数ソート完了後 nums = \(nums)") } } ================================================ FILE: ja/codes/swift/chapter_sorting/selection_sort.swift ================================================ /** * File: selection_sort.swift * Created Time: 2023-05-28 * Author: nuomi1 (nuomi1@qq.com) */ /* 選択ソート */ func selectionSort(nums: inout [Int]) { // 外側ループ:未整列区間は [i, n-1] for i in nums.indices.dropLast() { // 内側のループ:未ソート区間の最小要素を見つける var k = i for j in nums.indices.dropFirst(i + 1) { if nums[j] < nums[k] { k = j // 最小要素のインデックスを記録 } } // その最小要素を未整列区間の先頭要素と交換する nums.swapAt(i, k) } } @main enum SelectionSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] selectionSort(nums: &nums) print("選択ソート完了後 nums = \(nums)") } } ================================================ FILE: ja/codes/swift/chapter_stack_and_queue/array_deque.swift ================================================ /** * File: array_deque.swift * Created Time: 2023-02-22 * Author: nuomi1 (nuomi1@qq.com) */ /* 循環配列ベースの両端キュー */ class ArrayDeque { private var nums: [Int] // 両端キューの要素を格納する配列 private var front: Int // 先頭ポインタ。先頭要素を指す private var _size: Int // 両端キューの長さ /* コンストラクタ */ init(capacity: Int) { nums = Array(repeating: 0, count: capacity) front = 0 _size = 0 } /* 両端キューの容量を取得 */ func capacity() -> Int { nums.count } /* 両端キューの長さを取得 */ func size() -> Int { _size } /* 両端キューが空かどうかを判定 */ func isEmpty() -> Bool { size() == 0 } /* 循環配列のインデックスを計算 */ private func index(i: Int) -> Int { // 剰余演算により配列の先頭と末尾をつなげる // i が配列の末尾を越えたら先頭に戻る // i が配列の先頭を越えて前に出たら末尾に戻る (i + capacity()) % capacity() } /* キュー先頭にエンキュー */ func pushFirst(num: Int) { if size() == capacity() { print("両端キューがいっぱいです") return } // 先頭ポインタを左に 1 つ移動する // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする front = index(i: front - 1) // num をキュー先頭に追加 nums[front] = num _size += 1 } /* キュー末尾にエンキュー */ func pushLast(num: Int) { if size() == capacity() { print("両端キューがいっぱいです") return } // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す let rear = index(i: front + size()) // num をキュー末尾に追加 nums[rear] = num _size += 1 } /* キュー先頭からデキュー */ func popFirst() -> Int { let num = peekFirst() // 先頭ポインタを 1 つ後ろへ進める front = index(i: front + 1) _size -= 1 return num } /* キュー末尾からデキュー */ func popLast() -> Int { let num = peekLast() _size -= 1 return num } /* キュー先頭の要素にアクセス */ func peekFirst() -> Int { if isEmpty() { fatalError("両端キューが空です") } return nums[front] } /* キュー末尾の要素にアクセス */ func peekLast() -> Int { if isEmpty() { fatalError("両端キューが空です") } // 末尾要素のインデックスを計算 let last = index(i: front + size() - 1) return nums[last] } /* 出力用の配列を返す */ func toArray() -> [Int] { // 有効長の範囲内のリスト要素のみを変換 (front ..< front + size()).map { nums[index(i: $0)] } } } @main enum _ArrayDeque { /* Driver Code */ static func main() { /* 両端キューを初期化 */ let deque = ArrayDeque(capacity: 10) deque.pushLast(num: 3) deque.pushLast(num: 2) deque.pushLast(num: 5) print("両端キュー deque = \(deque.toArray())") /* 要素にアクセス */ let peekFirst = deque.peekFirst() print("先頭要素 peekFirst = \(peekFirst)") let peekLast = deque.peekLast() print("末尾要素 peekLast = \(peekLast)") /* 要素をエンキュー */ deque.pushLast(num: 4) print("要素 4 を末尾に追加した後 deque = \(deque.toArray())") deque.pushFirst(num: 1) print("要素 1 を先頭に追加した後 deque = \(deque.toArray())") /* 要素をデキュー */ let popLast = deque.popLast() print("末尾から取り出した要素 = \(popLast),末尾から取り出した後 deque = \(deque.toArray())") let popFirst = deque.popFirst() print("先頭から取り出した要素 = \(popFirst),先頭から取り出した後 deque = \(deque.toArray())") /* 両端キューの長さを取得 */ let size = deque.size() print("両端キューのサイズ size = \(size)") /* 両端キューが空かどうかを判定 */ let isEmpty = deque.isEmpty() print("両端キューが空かどうか = \(isEmpty)") } } ================================================ FILE: ja/codes/swift/chapter_stack_and_queue/array_queue.swift ================================================ /** * File: array_queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ /* 循環配列ベースのキュー */ class ArrayQueue { private var nums: [Int] // キュー要素を格納する配列 private var front: Int // 先頭ポインタ。先頭要素を指す private var _size: Int // キューの長さ init(capacity: Int) { // 配列を初期化 nums = Array(repeating: 0, count: capacity) front = 0 _size = 0 } /* キューの容量を取得 */ func capacity() -> Int { nums.count } /* キューの長さを取得 */ func size() -> Int { _size } /* キューが空かどうかを判定 */ func isEmpty() -> Bool { size() == 0 } /* エンキュー */ func push(num: Int) { if size() == capacity() { print("キューがいっぱいです") return } // 末尾ポインタを計算し、末尾インデックス + 1 を指す // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする let rear = (front + size()) % capacity() // num をキュー末尾に追加 nums[rear] = num _size += 1 } /* デキュー */ @discardableResult func pop() -> Int { let num = peek() // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す front = (front + 1) % capacity() _size -= 1 return num } /* キュー先頭の要素にアクセス */ func peek() -> Int { if isEmpty() { fatalError("キューが空です") } return nums[front] } /* 配列を返す */ func toArray() -> [Int] { // 有効長の範囲内のリスト要素のみを変換 (front ..< front + size()).map { nums[$0 % capacity()] } } } @main enum _ArrayQueue { /* Driver Code */ static func main() { /* キューを初期化 */ let capacity = 10 let queue = ArrayQueue(capacity: capacity) /* 要素をエンキュー */ queue.push(num: 1) queue.push(num: 3) queue.push(num: 2) queue.push(num: 5) queue.push(num: 4) print("キュー queue = \(queue.toArray())") /* キュー先頭の要素にアクセス */ let peek = queue.peek() print("先頭要素 peek = \(peek)") /* 要素をデキュー */ let pop = queue.pop() print("取り出した要素 pop = \(pop),取り出し後 queue = \(queue.toArray())") /* キューの長さを取得 */ let size = queue.size() print("キューのサイズ size = \(size)") /* キューが空かどうかを判定 */ let isEmpty = queue.isEmpty() print("キューが空かどうか = \(isEmpty)") /* 循環配列をテストする */ for i in 0 ..< 10 { queue.push(num: i) queue.pop() print("第 \(i) 回のエンキュー + デキュー後 queue = \(queue.toArray())") } } } ================================================ FILE: ja/codes/swift/chapter_stack_and_queue/array_stack.swift ================================================ /** * File: array_stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ /* 配列ベースのスタック */ class ArrayStack { private var stack: [Int] init() { // リスト(動的配列)を初期化する stack = [] } /* スタックの長さを取得 */ func size() -> Int { stack.count } /* スタックが空かどうかを判定 */ func isEmpty() -> Bool { stack.isEmpty } /* プッシュ */ func push(num: Int) { stack.append(num) } /* ポップ */ @discardableResult func pop() -> Int { if isEmpty() { fatalError("スタックが空です") } return stack.removeLast() } /* スタックトップの要素にアクセス */ func peek() -> Int { if isEmpty() { fatalError("スタックが空です") } return stack.last! } /* List を Array に変換して返す */ func toArray() -> [Int] { stack } } @main enum _ArrayStack { /* Driver Code */ static func main() { /* スタックを初期化 */ let stack = ArrayStack() /* 要素をプッシュ */ stack.push(num: 1) stack.push(num: 3) stack.push(num: 2) stack.push(num: 5) stack.push(num: 4) print("スタック stack = \(stack.toArray())") /* スタックトップの要素にアクセス */ let peek = stack.peek() print("スタックトップ要素 peek = \(peek)") /* 要素をポップ */ let pop = stack.pop() print("ポップした要素 pop = \(pop)、ポップ後の stack = \(stack.toArray())") /* スタックの長さを取得 */ let size = stack.size() print("スタックの長さ size = \(size)") /* 空かどうかを判定 */ let isEmpty = stack.isEmpty() print("スタックが空かどうか = \(isEmpty)") } } ================================================ FILE: ja/codes/swift/chapter_stack_and_queue/deque.swift ================================================ /** * File: deque.swift * Created Time: 2023-01-14 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Deque { /* Driver Code */ static func main() { /* 両端キューを初期化 */ // Swift には組み込みの両端キュークラスがないため、Array を両端キューとして使う var deque: [Int] = [] /* 要素をエンキュー */ deque.append(2) deque.append(5) deque.append(4) deque.insert(3, at: 0) deque.insert(1, at: 0) print("両端キュー deque = \(deque)") /* 要素にアクセス */ let peekFirst = deque.first! print("先頭要素 peekFirst = \(peekFirst)") let peekLast = deque.last! print("末尾要素 peekLast = \(peekLast)") /* 要素をデキュー */ // Array を用いる場合、popFirst の計算量は O(n) let popFirst = deque.removeFirst() print("先頭からデキューした要素 popFirst = \(popFirst)、先頭からデキュー後の deque = \(deque)") let popLast = deque.removeLast() print("末尾からデキューした要素 popLast = \(popLast)、末尾からデキュー後の deque = \(deque)") /* 両端キューの長さを取得 */ let size = deque.count print("両端キューのサイズ size = \(size)") /* 両端キューが空かどうかを判定 */ let isEmpty = deque.isEmpty print("両端キューが空かどうか = \(isEmpty)") } } ================================================ FILE: ja/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift ================================================ /** * File: linkedlist_deque.swift * Created Time: 2023-02-22 * Author: nuomi1 (nuomi1@qq.com) */ /* 双方向連結リストノード */ class ListNode { var val: Int // ノード値 var next: ListNode? // 後続ノードへの参照 weak var prev: ListNode? // 前駆ノードへの参照 init(val: Int) { self.val = val } } /* 双方向連結リストベースの両端キュー */ class LinkedListDeque { private var front: ListNode? // 先頭ノード front private var rear: ListNode? // 末尾ノード rear private var _size: Int // 両端キューの長さ init() { _size = 0 } /* 両端キューの長さを取得 */ func size() -> Int { _size } /* 両端キューが空かどうかを判定 */ func isEmpty() -> Bool { size() == 0 } /* エンキュー操作 */ private func push(num: Int, isFront: Bool) { let node = ListNode(val: num) // 連結リストが空なら、front と rear の両方を node に向ける if isEmpty() { front = node rear = node } // 先頭へのエンキュー操作 else if isFront { // node を連結リストの先頭に追加 front?.prev = node node.next = front front = node // 先頭ノードを更新する } // 末尾へのエンキュー操作 else { // node を連結リストの末尾に追加 rear?.next = node node.prev = rear rear = node // 末尾ノードを更新する } _size += 1 // キューの長さを更新 } /* キュー先頭にエンキュー */ func pushFirst(num: Int) { push(num: num, isFront: true) } /* キュー末尾にエンキュー */ func pushLast(num: Int) { push(num: num, isFront: false) } /* デキュー操作 */ private func pop(isFront: Bool) -> Int { if isEmpty() { fatalError("両端キューが空です") } let val: Int // キュー先頭からの取り出し if isFront { val = front!.val // 先頭ノードの値を一時保存 // 先頭ノードを削除 let fNext = front?.next if fNext != nil { fNext?.prev = nil front?.next = nil } front = fNext // 先頭ノードを更新する } // キュー末尾からの取り出し else { val = rear!.val // 末尾ノードの値を一時保存 // 末尾ノードを削除 let rPrev = rear?.prev if rPrev != nil { rPrev?.next = nil rear?.prev = nil } rear = rPrev // 末尾ノードを更新する } _size -= 1 // キューの長さを更新 return val } /* キュー先頭からデキュー */ func popFirst() -> Int { pop(isFront: true) } /* キュー末尾からデキュー */ func popLast() -> Int { pop(isFront: false) } /* キュー先頭の要素にアクセス */ func peekFirst() -> Int { if isEmpty() { fatalError("両端キューが空です") } return front!.val } /* キュー末尾の要素にアクセス */ func peekLast() -> Int { if isEmpty() { fatalError("両端キューが空です") } return rear!.val } /* 出力用の配列を返す */ func toArray() -> [Int] { var node = front var res = Array(repeating: 0, count: size()) for i in res.indices { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListDeque { /* Driver Code */ static func main() { /* 両端キューを初期化 */ let deque = LinkedListDeque() deque.pushLast(num: 3) deque.pushLast(num: 2) deque.pushLast(num: 5) print("両端キュー deque = \(deque.toArray())") /* 要素にアクセス */ let peekFirst = deque.peekFirst() print("先頭要素 peekFirst = \(peekFirst)") let peekLast = deque.peekLast() print("末尾要素 peekLast = \(peekLast)") /* 要素をエンキュー */ deque.pushLast(num: 4) print("要素 4 を末尾に追加した後 deque = \(deque.toArray())") deque.pushFirst(num: 1) print("要素 1 を先頭に追加した後 deque = \(deque.toArray())") /* 要素をデキュー */ let popLast = deque.popLast() print("末尾から取り出した要素 = \(popLast),末尾から取り出した後 deque = \(deque.toArray())") let popFirst = deque.popFirst() print("先頭から取り出した要素 = \(popFirst),先頭から取り出した後 deque = \(deque.toArray())") /* 両端キューの長さを取得 */ let size = deque.size() print("両端キューのサイズ size = \(size)") /* 両端キューが空かどうかを判定 */ let isEmpty = deque.isEmpty() print("両端キューが空かどうか = \(isEmpty)") } } ================================================ FILE: ja/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift ================================================ /** * File: linkedlist_queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 連結リストベースのキュー */ class LinkedListQueue { private var front: ListNode? // 先頭ノード private var rear: ListNode? // 末尾ノード private var _size: Int init() { _size = 0 } /* キューの長さを取得 */ func size() -> Int { _size } /* キューが空かどうかを判定 */ func isEmpty() -> Bool { size() == 0 } /* エンキュー */ func push(num: Int) { // 末尾ノードの後ろに num を追加 let node = ListNode(x: num) // キューが空なら、先頭・末尾ノードをともにそのノードに設定 if front == nil { front = node rear = node } // キューが空でなければ、そのノードを末尾ノードの後ろに追加 else { rear?.next = node rear = node } _size += 1 } /* デキュー */ @discardableResult func pop() -> Int { let num = peek() // 先頭ノードを削除 front = front?.next _size -= 1 return num } /* キュー先頭の要素にアクセス */ func peek() -> Int { if isEmpty() { fatalError("キューが空です") } return front!.val } /* 連結リストを Array に変換して返す */ func toArray() -> [Int] { var node = front var res = Array(repeating: 0, count: size()) for i in res.indices { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListQueue { /* Driver Code */ static func main() { /* キューを初期化 */ let queue = LinkedListQueue() /* 要素をエンキュー */ queue.push(num: 1) queue.push(num: 3) queue.push(num: 2) queue.push(num: 5) queue.push(num: 4) print("キュー queue = \(queue.toArray())") /* キュー先頭の要素にアクセス */ let peek = queue.peek() print("先頭要素 peek = \(peek)") /* 要素をデキュー */ let pop = queue.pop() print("取り出した要素 pop = \(pop),取り出し後 queue = \(queue.toArray())") /* キューの長さを取得 */ let size = queue.size() print("キューのサイズ size = \(size)") /* キューが空かどうかを判定 */ let isEmpty = queue.isEmpty() print("キューが空かどうか = \(isEmpty)") } } ================================================ FILE: ja/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift ================================================ /** * File: linkedlist_stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 連結リストベースのスタック */ class LinkedListStack { private var _peek: ListNode? // 先頭ノードをスタックトップとする private var _size: Int // スタックの長さ init() { _size = 0 } /* スタックの長さを取得 */ func size() -> Int { _size } /* スタックが空かどうかを判定 */ func isEmpty() -> Bool { size() == 0 } /* プッシュ */ func push(num: Int) { let node = ListNode(x: num) node.next = _peek _peek = node _size += 1 } /* ポップ */ @discardableResult func pop() -> Int { let num = peek() _peek = _peek?.next _size -= 1 return num } /* スタックトップの要素にアクセス */ func peek() -> Int { if isEmpty() { fatalError("スタックが空です") } return _peek!.val } /* List を Array に変換して返す */ func toArray() -> [Int] { var node = _peek var res = Array(repeating: 0, count: size()) for i in res.indices.reversed() { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListStack { /* Driver Code */ static func main() { /* スタックを初期化 */ let stack = LinkedListStack() /* 要素をプッシュ */ stack.push(num: 1) stack.push(num: 3) stack.push(num: 2) stack.push(num: 5) stack.push(num: 4) print("スタック stack = \(stack.toArray())") /* スタックトップの要素にアクセス */ let peek = stack.peek() print("スタックトップ要素 peek = \(peek)") /* 要素をポップ */ let pop = stack.pop() print("ポップした要素 pop = \(pop)、ポップ後の stack = \(stack.toArray())") /* スタックの長さを取得 */ let size = stack.size() print("スタックの長さ size = \(size)") /* 空かどうかを判定 */ let isEmpty = stack.isEmpty() print("スタックが空かどうか = \(isEmpty)") } } ================================================ FILE: ja/codes/swift/chapter_stack_and_queue/queue.swift ================================================ /** * File: queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Queue { /* Driver Code */ static func main() { /* キューを初期化 */ // Swift には組み込みのキュークラスがないため、Array をキューとして使う var queue: [Int] = [] /* 要素をエンキュー */ queue.append(1) queue.append(3) queue.append(2) queue.append(5) queue.append(4) print("キュー queue = \(queue)") /* キュー先頭の要素にアクセス */ let peek = queue.first! print("先頭要素 peek = \(peek)") /* 要素をデキュー */ // Array を用いる場合、pop の計算量は O(n) let pool = queue.removeFirst() print("デキューした要素 pop = \(pool)、デキュー後の queue = \(queue)") /* キューの長さを取得 */ let size = queue.count print("キューのサイズ size = \(size)") /* キューが空かどうかを判定 */ let isEmpty = queue.isEmpty print("キューが空かどうか = \(isEmpty)") } } ================================================ FILE: ja/codes/swift/chapter_stack_and_queue/stack.swift ================================================ /** * File: stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Stack { /* Driver Code */ static func main() { /* スタックを初期化 */ // Swift には組み込みのスタッククラスがないため、Array をスタックとして使う var stack: [Int] = [] /* 要素をプッシュ */ stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) print("スタック stack = \(stack)") /* スタックトップの要素にアクセス */ let peek = stack.last! print("スタックトップ要素 peek = \(peek)") /* 要素をポップ */ let pop = stack.removeLast() print("ポップした要素 pop = \(pop)、ポップ後の stack = \(stack)") /* スタックの長さを取得 */ let size = stack.count print("スタックの長さ size = \(size)") /* 空かどうかを判定 */ let isEmpty = stack.isEmpty print("スタックが空かどうか = \(isEmpty)") } } ================================================ FILE: ja/codes/swift/chapter_tree/array_binary_tree.swift ================================================ /** * File: array_binary_tree.swift * Created Time: 2023-07-23 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 配列表現による二分木クラス */ class ArrayBinaryTree { private var tree: [Int?] /* コンストラクタ */ init(arr: [Int?]) { tree = arr } /* リスト容量 */ func size() -> Int { tree.count } /* インデックス i のノードの値を取得 */ func val(i: Int) -> Int? { // インデックスが範囲外なら、空きを表す null を返す if i < 0 || i >= size() { return nil } return tree[i] } /* インデックス i のノードの左子ノードのインデックスを取得 */ func left(i: Int) -> Int { 2 * i + 1 } /* インデックス i のノードの右子ノードのインデックスを取得 */ func right(i: Int) -> Int { 2 * i + 2 } /* インデックス i のノードの親ノードのインデックスを取得 */ func parent(i: Int) -> Int { (i - 1) / 2 } /* レベル順走査 */ func levelOrder() -> [Int] { var res: [Int] = [] // 配列を直接走査する for i in 0 ..< size() { if let val = val(i: i) { res.append(val) } } return res } /* 深さ優先探索 */ private func dfs(i: Int, order: String, res: inout [Int]) { // 空きスロットなら返す guard let val = val(i: i) else { return } // 先行順走査 if order == "pre" { res.append(val) } dfs(i: left(i: i), order: order, res: &res) // 中順走査 if order == "in" { res.append(val) } dfs(i: right(i: i), order: order, res: &res) // 後順走査 if order == "post" { res.append(val) } } /* 先行順走査 */ func preOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "pre", res: &res) return res } /* 中順走査 */ func inOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "in", res: &res) return res } /* 後順走査 */ func postOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "post", res: &res) return res } } @main enum _ArrayBinaryTree { /* Driver Code */ static func main() { // 二分木を初期化 // ここでは、配列から直接二分木を生成する関数を利用する let arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] let root = TreeNode.listToTree(arr: arr) print("\n二分木を初期化\n") print("二分木の配列表現:") print(arr) print("二分木の連結リスト表現:") PrintUtil.printTree(root: root) // 配列表現による二分木クラス let abt = ArrayBinaryTree(arr: arr) // ノードにアクセス let i = 1 let l = abt.left(i: i) let r = abt.right(i: i) let p = abt.parent(i: i) print("\n現在のノードのインデックスは \(i) 、値は \(abt.val(i: i) as Any)") print("左の子ノードのインデックスは \(l) 、値は \(abt.val(i: l) as Any)") print("右の子ノードのインデックスは \(r) 、値は \(abt.val(i: r) as Any)") print("親ノードのインデックスは \(p) 、値は \(abt.val(i: p) as Any)") // 木を走査 var res = abt.levelOrder() print("\nレベル順走査:\(res)") res = abt.preOrder() print("先行順走査:\(res)") res = abt.inOrder() print("中間順走査:\(res)") res = abt.postOrder() print("後順走査は:\(res)") } } ================================================ FILE: ja/codes/swift/chapter_tree/avl_tree.swift ================================================ /** * File: avl_tree.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* AVL 木 */ class AVLTree { fileprivate var root: TreeNode? // 根ノード init() {} /* ノードの高さを取得 */ func height(node: TreeNode?) -> Int { // 空ノードの高さは -1、葉ノードの高さは 0 node?.height ?? -1 } /* ノードの高さを更新する */ private func updateHeight(node: TreeNode?) { // ノードの高さは最も高い部分木の高さ + 1 に等しい node?.height = max(height(node: node?.left), height(node: node?.right)) + 1 } /* 平衡係数を取得 */ func balanceFactor(node: TreeNode?) -> Int { // 空ノードの平衡係数は 0 guard let node = node else { return 0 } // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ return height(node: node.left) - height(node: node.right) } /* 右回転 */ private func rightRotate(node: TreeNode?) -> TreeNode? { let child = node?.left let grandChild = child?.right // child を支点として node を右回転させる child?.right = node node?.left = grandChild // ノードの高さを更新する updateHeight(node: node) updateHeight(node: child) // 回転後の部分木の根ノードを返す return child } /* 左回転 */ private func leftRotate(node: TreeNode?) -> TreeNode? { let child = node?.right let grandChild = child?.left // child を支点として node を左回転させる child?.left = node node?.right = grandChild // ノードの高さを更新する updateHeight(node: node) updateHeight(node: child) // 回転後の部分木の根ノードを返す return child } /* 回転操作を行い、この部分木の平衡を回復する */ private func rotate(node: TreeNode?) -> TreeNode? { // ノード node の平衡係数を取得 let balanceFactor = balanceFactor(node: node) // 左に偏った木 if balanceFactor > 1 { if self.balanceFactor(node: node?.left) >= 0 { // 右回転 return rightRotate(node: node) } else { // 左回転してから右回転 node?.left = leftRotate(node: node?.left) return rightRotate(node: node) } } // 右に偏った木 if balanceFactor < -1 { if self.balanceFactor(node: node?.right) <= 0 { // 左回転 return leftRotate(node: node) } else { // 右回転してから左回転 node?.right = rightRotate(node: node?.right) return leftRotate(node: node) } } // 平衡木なので回転不要、そのまま返す return node } /* ノードを挿入 */ func insert(val: Int) { root = insertHelper(node: root, val: val) } /* ノードを再帰的に挿入する(補助メソッド) */ private func insertHelper(node: TreeNode?, val: Int) -> TreeNode? { var node = node if node == nil { return TreeNode(x: val) } /* 1. 挿入位置を探索してノードを挿入 */ if val < node!.val { node?.left = insertHelper(node: node?.left, val: val) } else if val > node!.val { node?.right = insertHelper(node: node?.right, val: val) } else { return node // 重複ノードは挿入せず、そのまま返す } updateHeight(node: node) // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node: node) // 部分木の根ノードを返す return node } /* ノードを削除 */ func remove(val: Int) { root = removeHelper(node: root, val: val) } /* ノードを再帰的に削除する(補助メソッド) */ private func removeHelper(node: TreeNode?, val: Int) -> TreeNode? { var node = node if node == nil { return nil } /* 1. ノードを探索して削除 */ if val < node!.val { node?.left = removeHelper(node: node?.left, val: val) } else if val > node!.val { node?.right = removeHelper(node: node?.right, val: val) } else { if node?.left == nil || node?.right == nil { let child = node?.left ?? node?.right // 子ノード数 = 0 の場合、node をそのまま削除して返す if child == nil { return nil } // 子ノード数 = 1 の場合、node をそのまま削除する else { node = child } } else { // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える var temp = node?.right while temp?.left != nil { temp = temp?.left } node?.right = removeHelper(node: node?.right, val: temp!.val) node?.val = temp!.val } } updateHeight(node: node) // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node: node) // 部分木の根ノードを返す return node } /* ノードを探索 */ func search(val: Int) -> TreeNode? { var cur = root while cur != nil { // 目標ノードは cur の右部分木にある if cur!.val < val { cur = cur?.right } // 目標ノードは cur の左部分木にある else if cur!.val > val { cur = cur?.left } // 目標ノードが見つかったらループを抜ける else { break } } // 目標ノードを返す return cur } } @main enum _AVLTree { static func testInsert(tree: AVLTree, val: Int) { tree.insert(val: val) print("\nノード \(val) を挿入後、AVL 木は") PrintUtil.printTree(root: tree.root) } static func testRemove(tree: AVLTree, val: Int) { tree.remove(val: val) print("\nノード \(val) を削除後、AVL 木は") PrintUtil.printTree(root: tree.root) } /* Driver Code */ static func main() { /* 空の AVL 木を初期化する */ let avlTree = AVLTree() /* ノードを挿入 */ // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい testInsert(tree: avlTree, val: 1) testInsert(tree: avlTree, val: 2) testInsert(tree: avlTree, val: 3) testInsert(tree: avlTree, val: 4) testInsert(tree: avlTree, val: 5) testInsert(tree: avlTree, val: 8) testInsert(tree: avlTree, val: 7) testInsert(tree: avlTree, val: 9) testInsert(tree: avlTree, val: 10) testInsert(tree: avlTree, val: 6) /* 重複ノードを挿入する */ testInsert(tree: avlTree, val: 7) /* ノードを削除 */ // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい testRemove(tree: avlTree, val: 8) // 次数 0 のノードを削除する testRemove(tree: avlTree, val: 5) // 次数 1 のノードを削除する testRemove(tree: avlTree, val: 4) // 次数 2 のノードを削除する /* ノードを検索 */ let node = avlTree.search(val: 7) print("\n見つかったノードオブジェクトは \(node!)、ノード値 = \(node!.val)") } } ================================================ FILE: ja/codes/swift/chapter_tree/binary_search_tree.swift ================================================ /** * File: binary_search_tree.swift * Created Time: 2023-01-26 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 二分探索木 */ class BinarySearchTree { private var root: TreeNode? /* コンストラクタ */ init() { // 空の木を初期化する root = nil } /* 二分木の根ノードを取得 */ func getRoot() -> TreeNode? { root } /* ノードを探索 */ func search(num: Int) -> TreeNode? { var cur = root // ループで探索し、葉ノードを越えたら抜ける while cur != nil { // 目標ノードは cur の右部分木にある if cur!.val < num { cur = cur?.right } // 目標ノードは cur の左部分木にある else if cur!.val > num { cur = cur?.left } // 目標ノードが見つかったらループを抜ける else { break } } // 目標ノードを返す return cur } /* ノードを挿入 */ func insert(num: Int) { // 木が空なら、根ノードを初期化する if root == nil { root = TreeNode(x: num) return } var cur = root var pre: TreeNode? // ループで探索し、葉ノードを越えたら抜ける while cur != nil { // 重複ノードが見つかったら、直ちに返す if cur!.val == num { return } pre = cur // 挿入位置は cur の右部分木にある if cur!.val < num { cur = cur?.right } // 挿入位置は cur の左部分木にある else { cur = cur?.left } } // ノードを挿入 let node = TreeNode(x: num) if pre!.val < num { pre?.right = node } else { pre?.left = node } } /* ノードを削除 */ func remove(num: Int) { // 木が空なら、そのまま早期リターンする if root == nil { return } var cur = root var pre: TreeNode? // ループで探索し、葉ノードを越えたら抜ける while cur != nil { // 削除対象のノードが見つかったら、ループを抜ける if cur!.val == num { break } pre = cur // 削除対象ノードは cur の右部分木にある if cur!.val < num { cur = cur?.right } // 削除対象ノードは cur の左部分木にある else { cur = cur?.left } } // 削除対象ノードがなければそのまま返す if cur == nil { return } // 子ノード数 = 0 or 1 if cur?.left == nil || cur?.right == nil { // 子ノード数が 0 / 1 のとき、child = null / その子ノード let child = cur?.left ?? cur?.right // ノード cur を削除する if cur !== root { if pre?.left === cur { pre?.left = child } else { pre?.right = child } } else { // 削除ノードが根ノードなら、根ノードを再設定 root = child } } // 子ノード数 = 2 else { // 中順走査における cur の次ノードを取得 var tmp = cur?.right while tmp?.left != nil { tmp = tmp?.left } // ノード tmp を再帰的に削除 remove(num: tmp!.val) // tmp で cur を上書きする cur?.val = tmp!.val } } } @main enum _BinarySearchTree { /* Driver Code */ static func main() { /* 二分探索木を初期化 */ let bst = BinarySearchTree() // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] for num in nums { bst.insert(num: num) } print("\n初期化された二分木は\n") PrintUtil.printTree(root: bst.getRoot()) /* ノードを探索 */ let node = bst.search(num: 7) print("\n見つかったノードオブジェクトは \(node!)、ノード値 = \(node!.val)") /* ノードを挿入 */ bst.insert(num: 16) print("\nノード 16 を挿入後、二分木は\n") PrintUtil.printTree(root: bst.getRoot()) /* ノードを削除 */ bst.remove(num: 1) print("\nノード 1 を削除後、二分木は\n") PrintUtil.printTree(root: bst.getRoot()) bst.remove(num: 2) print("\nノード 2 を削除後、二分木は\n") PrintUtil.printTree(root: bst.getRoot()) bst.remove(num: 4) print("\nノード 4 を削除後、二分木は\n") PrintUtil.printTree(root: bst.getRoot()) } } ================================================ FILE: ja/codes/swift/chapter_tree/binary_tree.swift ================================================ /** * File: binary_tree.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum BinaryTree { /* Driver Code */ static func main() { /* 二分木を初期化 */ // ノードを初期化 let n1 = TreeNode(x: 1) let n2 = TreeNode(x: 2) let n3 = TreeNode(x: 3) let n4 = TreeNode(x: 4) let n5 = TreeNode(x: 5) // ノード間の参照(ポインタ)を構築する n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 print("\n二分木を初期化\n") PrintUtil.printTree(root: n1) /* ノードの挿入と削除 */ let P = TreeNode(x: 0) // n1 -> n2 の間にノード P を挿入 n1.left = P P.left = n2 print("\nノード P を挿入後\n") PrintUtil.printTree(root: n1) // ノード P を削除 n1.left = n2 print("\nノード P を削除後\n") PrintUtil.printTree(root: n1) } } ================================================ FILE: ja/codes/swift/chapter_tree/binary_tree_bfs.swift ================================================ /** * File: binary_tree_bfs.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* レベル順走査 */ func levelOrder(root: TreeNode) -> [Int] { // キューを初期化し、ルートノードを追加する var queue: [TreeNode] = [root] // 走査順序を保存するためのリストを初期化する var list: [Int] = [] while !queue.isEmpty { let node = queue.removeFirst() // デキュー list.append(node.val) // ノードの値を保存する if let left = node.left { queue.append(left) // 左子ノードをキューに追加 } if let right = node.right { queue.append(right) // 右子ノードをキューに追加 } } return list } @main enum BinaryTreeBFS { /* Driver Code */ static func main() { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する let node = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! print("\n二分木を初期化\n") PrintUtil.printTree(root: node) /* レベル順走査 */ let list = levelOrder(root: node) print("\nレベル順走査のノード出力シーケンス = \(list)") } } ================================================ FILE: ja/codes/swift/chapter_tree/binary_tree_dfs.swift ================================================ /** * File: binary_tree_dfs.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils // 走査順序を格納するリストを初期化 var list: [Int] = [] /* 先行順走査 */ func preOrder(root: TreeNode?) { guard let root = root else { return } // 訪問順序:根ノード -> 左部分木 -> 右部分木 list.append(root.val) preOrder(root: root.left) preOrder(root: root.right) } /* 中順走査 */ func inOrder(root: TreeNode?) { guard let root = root else { return } // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 inOrder(root: root.left) list.append(root.val) inOrder(root: root.right) } /* 後順走査 */ func postOrder(root: TreeNode?) { guard let root = root else { return } // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード postOrder(root: root.left) postOrder(root: root.right) list.append(root.val) } @main enum BinaryTreeDFS { /* Driver Code */ static func main() { /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する let root = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! print("\n二分木を初期化\n") PrintUtil.printTree(root: root) /* 先行順走査 */ list.removeAll() preOrder(root: root) print("\n前順走査のノード出力シーケンス = \(list)") /* 中順走査 */ list.removeAll() inOrder(root: root) print("\n中順走査のノード出力シーケンス = \(list)") /* 後順走査 */ list.removeAll() postOrder(root: root) print("\n後順走査のノード出力シーケンス = \(list)") } } ================================================ FILE: ja/codes/swift/utils/ListNode.swift ================================================ /** * File: ListNode.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ public class ListNode: Hashable { public var val: Int // ノード値 public var next: ListNode? // 後続ノードへの参照 public init(x: Int) { val = x } public static func == (lhs: ListNode, rhs: ListNode) -> Bool { lhs.val == rhs.val && lhs.next.map { ObjectIdentifier($0) } == rhs.next.map { ObjectIdentifier($0) } } public func hash(into hasher: inout Hasher) { hasher.combine(val) hasher.combine(next.map { ObjectIdentifier($0) }) } public static func arrToLinkedList(arr: [Int]) -> ListNode? { let dum = ListNode(x: 0) var head: ListNode? = dum for val in arr { head?.next = ListNode(x: val) head = head?.next } return dum.next } } ================================================ FILE: ja/codes/swift/utils/Pair.swift ================================================ /** * File: Pair.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ /* キーと値の組 */ public class Pair: Equatable { public var key: Int public var val: String public init(key: Int, val: String) { self.key = key self.val = val } public static func == (lhs: Pair, rhs: Pair) -> Bool { lhs.key == rhs.key && lhs.val == rhs.val } } ================================================ FILE: ja/codes/swift/utils/PrintUtil.swift ================================================ /** * File: PrintUtil.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ public enum PrintUtil { private class Trunk { var prev: Trunk? var str: String init(prev: Trunk?, str: String) { self.prev = prev self.str = str } } public static func printLinkedList(head: ListNode) { var head: ListNode? = head var list: [String] = [] while head != nil { list.append("\(head!.val)") head = head?.next } print(list.joined(separator: " -> ")) } public static func printTree(root: TreeNode?) { printTree(root: root, prev: nil, isRight: false) } private static func printTree(root: TreeNode?, prev: Trunk?, isRight: Bool) { if root == nil { return } var prevStr = " " let trunk = Trunk(prev: prev, str: prevStr) printTree(root: root?.right, prev: trunk, isRight: true) if prev == nil { trunk.str = "———" } else if isRight { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev?.str = prevStr } showTrunks(p: trunk) print(" \(root!.val)") if prev != nil { prev?.str = prevStr } trunk.str = " |" printTree(root: root?.left, prev: trunk, isRight: false) } private static func showTrunks(p: Trunk?) { if p == nil { return } showTrunks(p: p?.prev) print(p!.str, terminator: "") } public static func printHashMap(map: [K: V]) { for (key, value) in map { print("\(key) -> \(value)") } } public static func printHeap(queue: [Int]) { print("ヒープの配列表現:", terminator: "") print(queue) print("ヒープの木構造表現:") let root = TreeNode.listToTree(arr: queue) printTree(root: root) } public static func printMatrix(matrix: [[T]]) { print("[") for row in matrix { print(" \(row),") } print("]") } } ================================================ FILE: ja/codes/swift/utils/TreeNode.swift ================================================ /** * File: TreeNode.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 二分木ノードクラス */ public class TreeNode { public var val: Int // ノード値 public var height: Int // ノードの高さ public var left: TreeNode? // 左子ノードへの参照 public var right: TreeNode? // 右子ノードへの参照 /* コンストラクタ */ public init(x: Int) { val = x height = 0 } // シリアライズの符号化規則は次を参照してください: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二分木の配列表現: // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] // 二分木の連結リスト表現: // /——— 15 // /——— 7 // /——— 3 // | \\——— 6 // | \\——— 12 // ——— 1 // \\——— 2 // | /——— 9 // \\——— 4 // \\——— 8 /* リストを二分木にデシリアライズする: 再帰 */ private static func listToTreeDFS(arr: [Int?], i: Int) -> TreeNode? { if i < 0 || i >= arr.count || arr[i] == nil { return nil } let root = TreeNode(x: arr[i]!) root.left = listToTreeDFS(arr: arr, i: 2 * i + 1) root.right = listToTreeDFS(arr: arr, i: 2 * i + 2) return root } /* リストを二分木にデシリアライズする */ public static func listToTree(arr: [Int?]) -> TreeNode? { listToTreeDFS(arr: arr, i: 0) } /* 二分木をリストにシリアライズする: 再帰 */ private static func treeToListDFS(root: TreeNode?, i: Int, res: inout [Int?]) { if root == nil { return } while i >= res.count { res.append(nil) } res[i] = root?.val treeToListDFS(root: root?.left, i: 2 * i + 1, res: &res) treeToListDFS(root: root?.right, i: 2 * i + 2, res: &res) } /* 二分木をリストにシリアライズする */ public static func treeToList(root: TreeNode?) -> [Int?] { var res: [Int?] = [] treeToListDFS(root: root, i: 0, res: &res) return res } } ================================================ FILE: ja/codes/swift/utils/Vertex.swift ================================================ /** * File: Vertex.swift * Created Time: 2023-02-19 * Author: nuomi1 (nuomi1@qq.com) */ /* 頂点クラス */ public class Vertex: Hashable { public var val: Int public init(val: Int) { self.val = val } public static func == (lhs: Vertex, rhs: Vertex) -> Bool { lhs.val == rhs.val } public func hash(into hasher: inout Hasher) { hasher.combine(val) } /* 値リスト vals を入力し、頂点リスト vets を返す */ public static func valsToVets(vals: [Int]) -> [Vertex] { vals.map { Vertex(val: $0) } } /* 頂点リスト vets を入力し、値リスト vals を返す */ public static func vetsToVals(vets: [Vertex]) -> [Int] { vets.map { $0.val } } } ================================================ FILE: ja/codes/typescript/.gitignore ================================================ node_modules package-lock.json ================================================ FILE: ja/codes/typescript/.prettierrc ================================================ { "tabWidth": 4, "useTabs": false, "semi": true, "singleQuote": true } ================================================ FILE: ja/codes/typescript/chapter_array_and_linkedlist/array.ts ================================================ /** * File: array.ts * Created Time: 2022-12-04 * Author: Justin (xiefahit@gmail.com) */ /* 要素へランダムアクセス */ function randomAccess(nums: number[]): number { // 区間 [0, nums.length) からランダムに 1 つの数を選ぶ const random_index = Math.floor(Math.random() * nums.length); // ランダムな要素を取得して返す const random_num = nums[random_index]; return random_num; } /* 配列長を拡張する */ // TypeScript の Array は動的配列であり、直接拡張できます // 学習しやすいよう、本関数では Array を長さ不変の配列として扱います function extend(nums: number[], enlarge: number): number[] { // 拡張後の長さを持つ配列を初期化する const res = new Array(nums.length + enlarge).fill(0); // 元の配列の全要素を新しい配列にコピー for (let i = 0; i < nums.length; i++) { res[i] = nums[i]; } // 拡張後の新しい配列を返す return res; } /* 配列の index 番目に要素 num を挿入 */ function insert(nums: number[], num: number, index: number): void { // インデックス index 以降の全要素を 1 つ後ろへ移動する for (let i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // index の要素に num を代入する nums[index] = num; } /* index の要素を削除する */ function remove(nums: number[], index: number): void { // インデックス index より後ろの全要素を 1 つ前へ移動する for (let i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* 配列を走査 */ function traverse(nums: number[]): void { let count = 0; // インデックスで配列を走査 for (let i = 0; i < nums.length; i++) { count += nums[i]; } // 配列要素を直接走査 for (const num of nums) { count += num; } } /* 配列内で指定要素を探す */ function find(nums: number[], target: number): number { for (let i = 0; i < nums.length; i++) { if (nums[i] === target) { return i; } } return -1; } /* Driver Code */ /* 配列を初期化 */ const arr: number[] = new Array(5).fill(0); console.log('配列 arr =', arr); let nums: number[] = [1, 3, 2, 5, 4]; console.log('配列 nums =', nums); /* ランダムアクセス */ let random_num = randomAccess(nums); console.log('nums からランダムな要素を取得', random_num); /* 長さを拡張 */ nums = extend(nums, 3); console.log('配列の長さを 8 に拡張すると、nums =', nums); /* 要素を挿入する */ insert(nums, 6, 3); console.log('インデックス 3 に数字 6 を挿入すると、nums =', nums); /* 要素を削除 */ remove(nums, 2); console.log('インデックス 2 の要素を削除すると、nums =', nums); /* 配列を走査 */ traverse(nums); /* 要素を探索する */ let index = find(nums, 3); console.log('nums 内で要素 3 を検索すると、インデックス =', index); export {}; ================================================ FILE: ja/codes/typescript/chapter_array_and_linkedlist/linked_list.ts ================================================ /** * File: linked_list.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from '../modules/ListNode'; import { printLinkedList } from '../modules/PrintUtil'; /* 連結リストでノード n0 の後ろにノード P を挿入する */ function insert(n0: ListNode, P: ListNode): void { const n1 = n0.next; P.next = n1; n0.next = P; } /* 連結リストでノード n0 の直後のノードを削除する */ function remove(n0: ListNode): void { if (!n0.next) { return; } // n0 -> P -> n1 const P = n0.next; const n1 = P.next; n0.next = n1; } /* 連結リスト内で index 番目のノードにアクセス */ function access(head: ListNode | null, index: number): ListNode | null { for (let i = 0; i < index; i++) { if (!head) { return null; } head = head.next; } return head; } /* 連結リストで値が target の最初のノードを探す */ function find(head: ListNode | null, target: number): number { let index = 0; while (head !== null) { if (head.val === target) { return index; } head = head.next; index += 1; } return -1; } /* Driver Code */ /* 連結リストを初期化 */ // 各ノードを初期化 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // ノード間の参照を構築する n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; console.log('初期化された連結リストは'); printLinkedList(n0); /* ノードを挿入 */ insert(n0, new ListNode(0)); console.log('ノード挿入後の連結リストは'); printLinkedList(n0); /* ノードを削除 */ remove(n0); console.log('ノード削除後の連結リストは'); printLinkedList(n0); /* ノードにアクセス */ const node = access(n0, 3); console.log(`連結リストのインデックス 3 にあるノードの値 = ${node?.val}`); /* ノードを探索 */ const index = find(n0, 2); console.log(`連結リスト内で値が 2 のノードのインデックス = ${index}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_array_and_linkedlist/list.ts ================================================ /** * File: list.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ /* リストを初期化 */ const nums: number[] = [1, 3, 2, 5, 4]; console.log(`リスト nums = ${nums}`); /* 要素にアクセス */ const num: number = nums[1]; console.log(`インデックス 1 の要素にアクセスすると、num = ${num}`); /* 要素を更新 */ nums[1] = 0; console.log(`インデックス 1 の要素を 0 に更新すると、nums = ${nums}`); /* リストを空にする */ nums.length = 0; console.log(`リストを空にすると nums = ${nums}`); /* 末尾に要素を追加 */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); console.log(`要素を追加すると nums = ${nums}`); /* 中間に要素を挿入 */ nums.splice(3, 0, 6); console.log(`インデックス 3 に数字 6 を挿入すると、nums = ${nums}`); /* 要素を削除 */ nums.splice(3, 1); console.log(`インデックス 3 の要素を削除すると、nums = ${nums}`); /* インデックスでリストを走査 */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* リスト要素を直接走査 */ count = 0; for (const x of nums) { count += x; } /* 2 つのリストを連結する */ const nums1: number[] = [6, 8, 7, 10, 9]; nums.push(...nums1); console.log(`リスト nums1 を nums の後ろに連結すると、nums = ${nums}`); /* リストをソート */ nums.sort((a, b) => a - b); console.log(`リストをソートすると nums = ${nums}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_array_and_linkedlist/my_list.ts ================================================ /** * File: my_list.ts * Created Time: 2022-12-11 * Author: Justin (xiefahit@gmail.com) */ /* リストクラス */ class MyList { private arr: Array; // 配列(リスト要素を格納) private _capacity: number = 10; // リスト容量 private _size: number = 0; // リストの長さ(現在の要素数) private extendRatio: number = 2; // リスト拡張時の増加倍率 /* コンストラクタ */ constructor() { this.arr = new Array(this._capacity); } /* リストの長さを取得(現在の要素数) */ public size(): number { return this._size; } /* リスト容量を取得する */ public capacity(): number { return this._capacity; } /* 要素にアクセス */ public get(index: number): number { // インデックスが範囲外なら例外を送出する。以下同様 if (index < 0 || index >= this._size) throw new Error('インデックスが範囲外です'); return this.arr[index]; } /* 要素を更新 */ public set(index: number, num: number): void { if (index < 0 || index >= this._size) throw new Error('インデックスが範囲外です'); this.arr[index] = num; } /* 末尾に要素を追加 */ public add(num: number): void { // 長さが容量に等しい場合は拡張が必要 if (this._size === this._capacity) this.extendCapacity(); // 新しい要素をリストの末尾に追加する this.arr[this._size] = num; this._size++; } /* 中間に要素を挿入 */ public insert(index: number, num: number): void { if (index < 0 || index >= this._size) throw new Error('インデックスが範囲外です'); // 要素数が容量を超えると、拡張機構が発動する if (this._size === this._capacity) { this.extendCapacity(); } // index 以降の要素をすべて 1 つ後ろへずらす for (let j = this._size - 1; j >= index; j--) { this.arr[j + 1] = this.arr[j]; } // 要素数を更新 this.arr[index] = num; this._size++; } /* 要素を削除 */ public remove(index: number): number { if (index < 0 || index >= this._size) throw new Error('インデックスが範囲外です'); let num = this.arr[index]; // インデックス index より後の要素をすべて 1 つ前に移動する for (let j = index; j < this._size - 1; j++) { this.arr[j] = this.arr[j + 1]; } // 要素数を更新 this._size--; // 削除された要素を返す return num; } /* リストの拡張 */ public extendCapacity(): void { // `size` の長さを持つ配列を新規作成し、元の配列を新しい配列にコピーする this.arr = this.arr.concat( new Array(this.capacity() * (this.extendRatio - 1)) ); // リストの容量を更新 this._capacity = this.arr.length; } /* リストを配列に変換する */ public toArray(): number[] { let size = this.size(); // 有効長の範囲内のリスト要素のみを変換 const arr = new Array(size); for (let i = 0; i < size; i++) { arr[i] = this.get(i); } return arr; } } /* Driver Code */ /* リストを初期化 */ const nums = new MyList(); /* 末尾に要素を追加 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); console.log( `リスト nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長さ = ${nums.size()}` ); /* 中間に要素を挿入 */ nums.insert(3, 6); console.log(`インデックス 3 に数字 6 を挿入すると ,nums = ${nums.toArray()}`); /* 要素を削除 */ nums.remove(3); console.log(`インデックス 3 の要素を削除すると,nums = ${nums.toArray()}`); /* 要素にアクセス */ const num = nums.get(1); console.log(`インデックス 1 の要素にアクセスすると、num = ${num}`); /* 要素を更新 */ nums.set(1, 0); console.log(`インデックス 1 の要素を 0 に更新すると ,nums = ${nums.toArray()}`); /* 拡張機構をテストする */ for (let i = 0; i < 10; i++) { // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する nums.add(i); } console.log( `拡張後のリスト nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長さ = ${nums.size()}` ); export {}; ================================================ FILE: ja/codes/typescript/chapter_backtracking/n_queens.ts ================================================ /** * File: n_queens.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* バックトラッキング:N クイーン */ function backtrack( row: number, n: number, state: string[][], res: string[][][], cols: boolean[], diags1: boolean[], diags2: boolean[] ): void { // すべての行への配置が完了したら、解を記録する if (row === n) { res.push(state.map((row) => row.slice())); return; } // すべての列を走査 for (let col = 0; col < n; col++) { // このマスに対応する主対角線と副対角線を計算 const diag1 = row - col + n - 1; const diag2 = row + col; // 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 試行:そのマスにクイーンを置く state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // 次の行に配置する backtrack(row + 1, n, state, res, cols, diags1, diags2); // 戻す:そのマスを空きマスに戻す state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* N クイーンを解く */ function nQueens(n: number): string[][][] { // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す const state = Array.from({ length: n }, () => Array(n).fill('#')); const cols = Array(n).fill(false); // 列にクイーンがあるか記録 const diags1 = Array(2 * n - 1).fill(false); // 主対角線にクイーンがあるかを記録 const diags2 = Array(2 * n - 1).fill(false); // 副対角線にクイーンがあるかを記録 const res: string[][][] = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } // Driver Code const n = 4; const res = nQueens(n); console.log(`入力する盤面の縦横は ${n}`); console.log(`クイーンの配置方法は全部で ${res.length} 種`); res.forEach((state) => { console.log('--------------------'); state.forEach((row) => console.log(row)); }); export {}; ================================================ FILE: ja/codes/typescript/chapter_backtracking/permutations_i.ts ================================================ /** * File: permutations_i.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* バックトラッキング:順列 I */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // 状態の長さが要素数に等しければ、解を記録 if (state.length === choices.length) { res.push([...state]); return; } // すべての選択肢を走査 choices.forEach((choice, i) => { // 枝刈り:要素の重複選択を許可しない if (!selected[i]) { // 試行: 選択を行い、状態を更新 selected[i] = true; state.push(choice); // 次の選択へ進む backtrack(state, choices, selected, res); // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.pop(); } }); } /* 全順列 I */ function permutationsI(nums: number[]): number[][] { const res: number[][] = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums: number[] = [1, 2, 3]; const res: number[][] = permutationsI(nums); console.log(`入力配列 nums = ${JSON.stringify(nums)}`); console.log(`すべての順列 res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_backtracking/permutations_ii.ts ================================================ /** * File: permutations_ii.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* バックトラッキング:順列 II */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // 状態の長さが要素数に等しければ、解を記録 if (state.length === choices.length) { res.push([...state]); return; } // すべての選択肢を走査 const duplicated = new Set(); choices.forEach((choice, i) => { // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない if (!selected[i] && !duplicated.has(choice)) { // 試行: 選択を行い、状態を更新 duplicated.add(choice); // 選択済みの要素値を記録 selected[i] = true; state.push(choice); // 次の選択へ進む backtrack(state, choices, selected, res); // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.pop(); } }); } /* 全順列 II */ function permutationsII(nums: number[]): number[][] { const res: number[][] = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums: number[] = [1, 2, 2]; const res: number[][] = permutationsII(nums); console.log(`入力配列 nums = ${JSON.stringify(nums)}`); console.log(`すべての順列 res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts ================================================ /** * File: preorder_traversal_i_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 前順走査:例題 1 */ function preOrder(root: TreeNode | null, res: TreeNode[]): void { if (root === null) { return; } if (root.val === 7) { // 解を記録 res.push(root); } preOrder(root.left, res); preOrder(root.right, res); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n二分木を初期化'); printTree(root); // 先行順走査 const res: TreeNode[] = []; preOrder(root, res); console.log('\n値が 7 のノードをすべて出力'); console.log(res.map((node) => node.val)); export {}; ================================================ FILE: ja/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts ================================================ /** * File: preorder_traversal_ii_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 前順走査:例題 2 */ function preOrder( root: TreeNode | null, path: TreeNode[], res: TreeNode[][] ): void { if (root === null) { return; } // 試す path.push(root); if (root.val === 7) { // 解を記録 res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // バックトラック path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n二分木を初期化'); printTree(root); // 先行順走査 const path: TreeNode[] = []; const res: TreeNode[][] = []; preOrder(root, path, res); console.log('\nすべての根ノードからノード 7 への経路を出力'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: ja/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts ================================================ /** * File: preorder_traversal_iii_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 前順走査:例題 3 */ function preOrder( root: TreeNode | null, path: TreeNode[], res: TreeNode[][] ): void { // 枝刈り if (root === null || root.val === 3) { return; } // 試す path.push(root); if (root.val === 7) { // 解を記録 res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // バックトラック path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n二分木を初期化'); printTree(root); // 先行順走査 const path: TreeNode[] = []; const res: TreeNode[][] = []; preOrder(root, path, res); console.log('\nすべての根ノードからノード 7 への経路を出力,経路に値が 3 のノードを含まない'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: ja/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts ================================================ /** * File: preorder_traversal_iii_template.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 現在の状態が解かどうかを判定 */ function isSolution(state: TreeNode[]): boolean { return state && state[state.length - 1]?.val === 7; } /* 解を記録 */ function recordSolution(state: TreeNode[], res: TreeNode[][]): void { res.push([...state]); } /* 現在の状態で、この選択が有効かどうかを判定 */ function isValid(state: TreeNode[], choice: TreeNode): boolean { return choice !== null && choice.val !== 3; } /* 状態を更新 */ function makeChoice(state: TreeNode[], choice: TreeNode): void { state.push(choice); } /* 状態を元に戻す */ function undoChoice(state: TreeNode[]): void { state.pop(); } /* バックトラッキング:例題 3 */ function backtrack( state: TreeNode[], choices: TreeNode[], res: TreeNode[][] ): void { // 解かどうかを確認 if (isSolution(state)) { // 解を記録 recordSolution(state, res); } // すべての選択肢を走査 for (const choice of choices) { // 枝刈り:選択が妥当かを確認する if (isValid(state, choice)) { // 試行: 選択を行い、状態を更新 makeChoice(state, choice); // 次の選択へ進む backtrack(state, [choice.left, choice.right], res); // バックトラック:選択を取り消し、前の状態に戻す undoChoice(state); } } } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n二分木を初期化'); printTree(root); // バックトラッキング法 const res: TreeNode[][] = []; backtrack([], [root], res); console.log('\nすべての根ノードからノード 7 への経路を出力,経路に値が 3 のノードを含まないことを条件とする'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: ja/codes/typescript/chapter_backtracking/subset_sum_i.ts ================================================ /** * File: subset_sum_i.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* バックトラッキング:部分和 I */ function backtrack( state: number[], target: number, choices: number[], start: number, res: number[][] ): void { // 部分集合の和が target に等しければ、解を記録 if (target === 0) { res.push([...state]); return; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for (let i = start; i < choices.length; i++) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } // 試す:選択を行い、target と start を更新 state.push(choices[i]); // 次の選択へ進む backtrack(state, target - choices[i], choices, i, res); // バックトラック:選択を取り消し、前の状態に戻す state.pop(); } } /* 部分和 I を解く */ function subsetSumI(nums: number[], target: number): number[][] { const state = []; // 状態(部分集合) nums.sort((a, b) => a - b); // nums をソート const start = 0; // 開始点を走査 const res = []; // 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumI(nums, target); console.log(`入力配列 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`合計が ${target} に等しいすべての部分集合 res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts ================================================ /** * File: subset_sum_i_naive.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* バックトラッキング:部分和 I */ function backtrack( state: number[], target: number, total: number, choices: number[], res: number[][] ): void { // 部分集合の和が target に等しければ、解を記録 if (total === target) { res.push([...state]); return; } // すべての選択肢を走査 for (let i = 0; i < choices.length; i++) { // 枝刈り:部分和が target を超える場合はその選択をスキップする if (total + choices[i] > target) { continue; } // 試行:選択を行い、要素と total を更新する state.push(choices[i]); // 次の選択へ進む backtrack(state, target, total + choices[i], choices, res); // バックトラック:選択を取り消し、前の状態に戻す state.pop(); } } /* 部分和 I を解く(重複部分集合を含む) */ function subsetSumINaive(nums: number[], target: number): number[][] { const state = []; // 状態(部分集合) const total = 0; // 部分和 const res = []; // 結果リスト(部分集合のリスト) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumINaive(nums, target); console.log(`入力配列 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`合計が ${target} に等しいすべての部分集合 res = ${JSON.stringify(res)}`); console.log('この方法で出力される結果には重複する集合が含まれます'); export {}; ================================================ FILE: ja/codes/typescript/chapter_backtracking/subset_sum_ii.ts ================================================ /** * File: subset_sum_ii.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* バックトラッキング:部分和 II */ function backtrack( state: number[], target: number, choices: number[], start: number, res: number[][] ): void { // 部分集合の和が target に等しければ、解を記録 if (target === 0) { res.push([...state]); return; } // すべての選択肢を走査 // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for (let i = start; i < choices.length; i++) { // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする if (i > start && choices[i] === choices[i - 1]) { continue; } // 試す:選択を行い、target と start を更新 state.push(choices[i]); // 次の選択へ進む backtrack(state, target - choices[i], choices, i + 1, res); // バックトラック:選択を取り消し、前の状態に戻す state.pop(); } } /* 部分和 II を解く */ function subsetSumII(nums: number[], target: number): number[][] { const state = []; // 状態(部分集合) nums.sort((a, b) => a - b); // nums をソート const start = 0; // 開始点を走査 const res = []; // 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [4, 4, 5]; const target = 9; const res = subsetSumII(nums, target); console.log(`入力配列 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`合計が ${target} に等しいすべての部分集合 res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_computational_complexity/iteration.ts ================================================ /** * File: iteration.ts * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* for ループ */ function forLoop(n: number): number { let res = 0; // 1, 2, ..., n-1, n を順に加算する for (let i = 1; i <= n; i++) { res += i; } return res; } /* while ループ */ function whileLoop(n: number): number { let res = 0; let i = 1; // 条件変数を初期化する // 1, 2, ..., n-1, n を順に加算する while (i <= n) { res += i; i++; // 条件変数を更新する } return res; } /* while ループ(2回更新) */ function whileLoopII(n: number): number { let res = 0; let i = 1; // 条件変数を初期化する // 1, 4, 10, ... を順に加算する while (i <= n) { res += i; // 条件変数を更新する i++; i *= 2; } return res; } /* 二重 for ループ */ function nestedForLoop(n: number): string { let res = ''; // i = 1, 2, ..., n-1, n とループする for (let i = 1; i <= n; i++) { // j = 1, 2, ..., n-1, n とループする for (let j = 1; j <= n; j++) { res += `(${i}, ${j}), `; } } return res; } /* Driver Code */ const n = 5; let res: number; res = forLoop(n); console.log(`for ループの合計結果 res = ${res}`); res = whileLoop(n); console.log(`while ループの合計結果 res = ${res}`); res = whileLoopII(n); console.log(`while ループ(2 回更新)の合計結果 res = ${res}`); const resStr = nestedForLoop(n); console.log(`二重 for ループの走査結果 ${resStr}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_computational_complexity/recursion.ts ================================================ /** * File: recursion.ts * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 再帰 */ function recur(n: number): number { // 終了条件 if (n === 1) return 1; // 再帰:再帰呼び出し const res = recur(n - 1); // 帰りがけ:結果を返す return n + res; } /* 反復で再帰を模擬する */ function forLoopRecur(n: number): number { // 明示的なスタックを使ってシステムコールスタックを模擬する const stack: number[] = []; let res: number = 0; // 再帰:再帰呼び出し for (let i = n; i > 0; i--) { // 「スタックへのプッシュ」で「再帰」を模擬する stack.push(i); } // 帰りがけ:結果を返す while (stack.length) { // 「スタックから取り出す操作」で「帰り」をシミュレート res += stack.pop(); } // res = 1+2+3+...+n return res; } /* 末尾再帰 */ function tailRecur(n: number, res: number): number { // 終了条件 if (n === 0) return res; // 末尾再帰呼び出し return tailRecur(n - 1, res + n); } /* フィボナッチ数列:再帰 */ function fib(n: number): number { // 終了条件 f(1) = 0, f(2) = 1 if (n === 1 || n === 2) return n - 1; // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す const res = fib(n - 1) + fib(n - 2); // 結果 f(n) を返す return res; } /* Driver Code */ const n = 5; let res: number; res = recur(n); console.log(`再帰関数の合計結果 res = ${res}`); res = forLoopRecur(n); console.log(`反復で再帰をシミュレートした合計結果 res = ${res}`); res = tailRecur(n, 0); console.log(`末尾再帰関数の合計結果 res = ${res}`); res = fib(n); console.log(`フィボナッチ数列の第 ${n} 項は ${res}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_computational_complexity/space_complexity.ts ================================================ /** * File: space_complexity.ts * Created Time: 2023-02-05 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from '../modules/ListNode'; import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 関数 */ function constFunc(): number { // 何らかの処理を行う return 0; } /* 定数階 */ function constant(n: number): void { // 定数、変数、オブジェクトは O(1) の空間を占める const a = 0; const b = 0; const nums = new Array(10000); const node = new ListNode(0); // ループ内の変数は O(1) の空間を占める for (let i = 0; i < n; i++) { const c = 0; } // ループ内の関数は O(1) の空間を占める for (let i = 0; i < n; i++) { constFunc(); } } /* 線形階 */ function linear(n: number): void { // 長さ n の配列は O(n) の空間を使用 const nums = new Array(n); // 長さ n のリストは O(n) の空間を使用 const nodes: ListNode[] = []; for (let i = 0; i < n; i++) { nodes.push(new ListNode(i)); } // 長さ n のハッシュテーブルは O(n) の空間を使用 const map = new Map(); for (let i = 0; i < n; i++) { map.set(i, i.toString()); } } /* 線形時間(再帰実装) */ function linearRecur(n: number): void { console.log(`再帰 n = ${n}`); if (n === 1) return; linearRecur(n - 1); } /* 二乗階 */ function quadratic(n: number): void { // 行列は O(n^2) の空間を使用する const numMatrix = Array(n) .fill(null) .map(() => Array(n).fill(null)); // 二次元リストは O(n^2) の空間を使用 const numList = []; for (let i = 0; i < n; i++) { const tmp = []; for (let j = 0; j < n; j++) { tmp.push(0); } numList.push(tmp); } } /* 二次時間(再帰実装) */ function quadraticRecur(n: number): number { if (n <= 0) return 0; const nums = new Array(n); console.log(`再帰 n = ${n} における nums の長さ = ${nums.length}`); return quadraticRecur(n - 1); } /* 指数時間(完全二分木の構築) */ function buildTree(n: number): TreeNode | null { if (n === 0) return null; const root = new TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ const n = 5; // 定数階 constant(n); // 線形階 linear(n); linearRecur(n); // 二乗階 quadratic(n); quadraticRecur(n); // 指数オーダー const root = buildTree(n); printTree(root); ================================================ FILE: ja/codes/typescript/chapter_computational_complexity/time_complexity.ts ================================================ /** * File: time_complexity.ts * Created Time: 2023-01-02 * Author: RiverTwilight (contact@rene.wang) */ /* 定数階 */ function constant(n: number): number { let count = 0; const size = 100000; for (let i = 0; i < size; i++) count++; return count; } /* 線形階 */ function linear(n: number): number { let count = 0; for (let i = 0; i < n; i++) count++; return count; } /* 線形時間(配列を走査) */ function arrayTraversal(nums: number[]): number { let count = 0; // ループ回数は配列長に比例する for (let i = 0; i < nums.length; i++) { count++; } return count; } /* 二乗階 */ function quadratic(n: number): number { let count = 0; // ループ回数はデータサイズ n の二乗に比例する for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { count++; } } return count; } /* 二次時間(バブルソート) */ function bubbleSort(nums: number[]): number { let count = 0; // カウンタ // 外側のループ:未ソート区間は [0, i] for (let i = nums.length - 1; i > 0; i--) { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 要素交換には 3 回の単位操作が含まれる } } } return count; } /* 指数時間(ループ実装) */ function exponential(n: number): number { let count = 0, base = 1; // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する for (let i = 0; i < n; i++) { for (let j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指数時間(再帰実装) */ function expRecur(n: number): number { if (n === 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 対数時間(ループ実装) */ function logarithmic(n: number): number { let count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* 対数時間(再帰実装) */ function logRecur(n: number): number { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* 線形対数時間 */ function linearLogRecur(n: number): number { if (n <= 1) return 1; let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (let i = 0; i < n; i++) { count++; } return count; } /* 階乗時間(再帰実装) */ function factorialRecur(n: number): number { if (n === 0) return 1; let count = 0; // 1個から n 個に分裂 for (let i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる const n = 8; console.log('入力データサイズ n = ' + n); let count = constant(n); console.log('定数時間の操作回数 = ' + count); count = linear(n); console.log('線形時間の操作回数 = ' + count); count = arrayTraversal(new Array(n)); console.log('線形時間(配列の走査)の操作回数 = ' + count); count = quadratic(n); console.log('二乗時間の操作回数 = ' + count); var nums = new Array(n); for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); console.log('二乗時間(バブルソート)の操作回数 = ' + count); count = exponential(n); console.log('指数時間(ループ実装)の操作回数 = ' + count); count = expRecur(n); console.log('指数時間(再帰実装)の操作回数 = ' + count); count = logarithmic(n); console.log('対数時間(ループ実装)の操作回数 = ' + count); count = logRecur(n); console.log('対数時間(再帰実装)の操作回数 = ' + count); count = linearLogRecur(n); console.log('線形対数時間(再帰実装)の操作回数 = ' + count); count = factorialRecur(n); console.log('階乗時間(再帰実装)の操作回数 = ' + count); export {}; ================================================ FILE: ja/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts ================================================ /** * File: worst_best_time_complexity.ts * Created Time: 2023-01-05 * Author: RiverTwilight (contact@rene.wang) */ /* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ function randomNumbers(n: number): number[] { const nums = Array(n); // 配列 nums = { 1, 2, 3, ..., n } を生成 for (let i = 0; i < n; i++) { nums[i] = i + 1; } // 配列要素をランダムにシャッフル for (let i = 0; i < n; i++) { const r = Math.floor(Math.random() * (i + 1)); const temp = nums[i]; nums[i] = nums[r]; nums[r] = temp; } return nums; } /* 配列 nums 内で数値 1 のインデックスを探す */ function findOne(nums: number[]): number { for (let i = 0; i < nums.length; i++) { // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる if (nums[i] === 1) { return i; } } return -1; } /* Driver Code */ for (let i = 0; i < 10; i++) { const n = 100; const nums = randomNumbers(n); const index = findOne(nums); console.log('\n配列 [ 1, 2, ..., n ] をシャッフルした後 = [' + nums.join(', ') + ']'); console.log('数字 1 のインデックスは ' + index); } export {}; ================================================ FILE: ja/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts ================================================ /** * File: binary_search_recur.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 二分探索:問題 f(i, j) */ function dfs(nums: number[], target: number, i: number, j: number): number { // 区間が空なら対象要素は存在しないので -1 を返す if (i > j) { return -1; } // 中点インデックス m を計算 const m = i + ((j - i) >> 1); if (nums[m] < target) { // 部分問題 f(m+1, j) を再帰的に解く return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 部分問題 f(i, m-1) を再帰的に解く return dfs(nums, target, i, m - 1); } else { // 目標要素が見つかったらそのインデックスを返す return m; } } /* 二分探索 */ function binarySearch(nums: number[], target: number): number { const n = nums.length; // 問題 f(0, n-1) を解く return dfs(nums, target, 0, n - 1); } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分探索(両閉区間) const index = binarySearch(nums, target); console.log(`対象要素 6 のインデックス = ${index}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_divide_and_conquer/build_tree.ts ================================================ /** * File: build_tree.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ import { printTree } from '../modules/PrintUtil'; import { TreeNode } from '../modules/TreeNode'; /* 二分木を構築:分割統治 */ function dfs( preorder: number[], inorderMap: Map, i: number, l: number, r: number ): TreeNode | null { // 部分木区間が空なら終了する if (r - l < 0) return null; // ルートノードを初期化する const root: TreeNode = new TreeNode(preorder[i]); // m を求めて左右部分木を分割する const m = inorderMap.get(preorder[i]); // 部分問題:左部分木を構築する root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // 部分問題:右部分木を構築する root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // 根ノードを返す return root; } /* 二分木を構築 */ function buildTree(preorder: number[], inorder: number[]): TreeNode | null { // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する let inorderMap = new Map(); for (let i = 0; i < inorder.length; i++) { inorderMap.set(inorder[i], i); } const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ const preorder = [3, 9, 2, 1, 7]; const inorder = [9, 3, 1, 2, 7]; console.log('前順走査 = ' + JSON.stringify(preorder)); console.log('中順走査 = ' + JSON.stringify(inorder)); const root = buildTree(preorder, inorder); console.log('構築した二分木:'); printTree(root); ================================================ FILE: ja/codes/typescript/chapter_divide_and_conquer/hanota.ts ================================================ /** * File: hanota.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 円盤を 1 枚移動 */ function move(src: number[], tar: number[]): void { // src の上から円盤を1枚取り出す const pan = src.pop(); // 円盤を tar の上に置く tar.push(pan); } /* ハノイの塔の問題 f(i) を解く */ function dfs(i: number, src: number[], buf: number[], tar: number[]): void { // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if (i === 1) { move(src, tar); return; } // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i - 1, src, tar, buf); // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src, tar); // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i - 1, buf, src, tar); } /* ハノイの塔を解く */ function solveHanota(A: number[], B: number[], C: number[]): void { const n = A.length; // A の上から n 枚の円盤を B を介して C へ移す dfs(n, A, B, C); } /* Driver Code */ // リスト末尾が柱の頂上 const A = [5, 4, 3, 2, 1]; const B = []; const C = []; console.log('初期状態:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); solveHanota(A, B, C); console.log('円盤の移動完了後:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); ================================================ FILE: ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts ================================================ /** * File: climbing_stairs_backtrack.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* バックトラッキング */ function backtrack( choices: number[], state: number, n: number, res: Map<0, any> ): void { // 第 n 段に到達したら、方法数を 1 増やす if (state === n) res.set(0, res.get(0) + 1); // すべての選択肢を走査 for (const choice of choices) { // 枝刈り: 第 n 段を超えないようにする if (state + choice > n) continue; // 試行: 選択を行い、状態を更新 backtrack(choices, state + choice, n, res); // バックトラック } } /* 階段登り:バックトラッキング */ function climbingStairsBacktrack(n: number): number { const choices = [1, 2]; // 1 段または 2 段上ることを選べる const state = 0; // 第 0 段から上り始める const res = new Map(); res.set(0, 0); // res[0] を使って方法数を記録する backtrack(choices, state, n, res); return res.get(0); } /* Driver Code */ const n = 9; const res = climbingStairsBacktrack(n); console.log(`${n} 段の階段を登る方法は ${res} 通り`); export {}; ================================================ FILE: ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts ================================================ /** * File: climbing_stairs_constraint_dp.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 制約付き階段登り:動的計画法 */ function climbingStairsConstraintDP(n: number): number { if (n === 1 || n === 2) { return 1; } // 部分問題の解を保存するために dp テーブルを初期化 const dp = Array.from({ length: n + 1 }, () => new Array(3)); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (let i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ const n = 9; const res = climbingStairsConstraintDP(n); console.log(`${n} 段の階段を登る方法は ${res} 通り`); export {}; ================================================ FILE: ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts ================================================ /** * File: climbing_stairs_dfs.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 検索 */ function dfs(i: number): number { // dp[1] と dp[2] は既知なので返す if (i === 1 || i === 2) return i; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1) + dfs(i - 2); return count; } /* 階段登り:探索 */ function climbingStairsDFS(n: number): number { return dfs(n); } /* Driver Code */ const n = 9; const res = climbingStairsDFS(n); console.log(`${n} 段の階段を登る方法は ${res} 通り`); export {}; ================================================ FILE: ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts ================================================ /** * File: climbing_stairs_dfs_mem.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* メモ化探索 */ function dfs(i: number, mem: number[]): number { // dp[1] と dp[2] は既知なので返す if (i === 1 || i === 2) return i; // dp[i] の記録があれば、それをそのまま返す if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1, mem) + dfs(i - 2, mem); // dp[i] を記録する mem[i] = count; return count; } /* 階段登り:メモ化探索 */ function climbingStairsDFSMem(n: number): number { // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す const mem = new Array(n + 1).fill(-1); return dfs(n, mem); } /* Driver Code */ const n = 9; const res = climbingStairsDFSMem(n); console.log(`${n} 段の階段を登る方法は ${res} 通り`); export {}; ================================================ FILE: ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts ================================================ /** * File: climbing_stairs_dp.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 階段登り:動的計画法 */ function climbingStairsDP(n: number): number { if (n === 1 || n === 2) return n; // 部分問題の解を保存するために dp テーブルを初期化 const dp = new Array(n + 1).fill(-1); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = 1; dp[2] = 2; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (let i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* 階段登り:空間最適化した動的計画法 */ function climbingStairsDPComp(n: number): number { if (n === 1 || n === 2) return n; let a = 1, b = 2; for (let i = 3; i <= n; i++) { const tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ const n = 9; let res = climbingStairsDP(n); console.log(`${n} 段の階段を登る方法は ${res} 通り`); res = climbingStairsDPComp(n); console.log(`${n} 段の階段を登る方法は ${res} 通り`); export {}; ================================================ FILE: ja/codes/typescript/chapter_dynamic_programming/coin_change.ts ================================================ /** * File: coin_change.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* コイン両替:動的計画法 */ function coinChangeDP(coins: Array, amt: number): number { const n = coins.length; const MAX = amt + 1; // dp テーブルを初期化 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 状態遷移:先頭行と先頭列 for (let a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状態遷移: 残りの行と列 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] !== MAX ? dp[n][amt] : -1; } /* コイン交換:空間最適化後の動的計画法 */ function coinChangeDPComp(coins: Array, amt: number): number { const n = coins.length; const MAX = amt + 1; // dp テーブルを初期化 const dp = Array.from({ length: amt + 1 }, () => MAX); dp[0] = 0; // 状態遷移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] !== MAX ? dp[amt] : -1; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 4; // 動的計画法 let res = coinChangeDP(coins, amt); console.log(`目標金額を作るのに必要な最小コイン枚数は ${res}`); // 空間最適化後の動的計画法 res = coinChangeDPComp(coins, amt); console.log(`目標金額を作るのに必要な最小コイン枚数は ${res}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts ================================================ /** * File: coin_change_ii.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* コイン両替 II:動的計画法 */ function coinChangeIIDP(coins: Array, amt: number): number { const n = coins.length; // dp テーブルを初期化 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 先頭列を初期化する for (let i = 0; i <= n; i++) { dp[i][0] = 1; } // 状態遷移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { // コイン i を選ばない場合と選ぶ場合の和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* コイン両替 II:空間最適化した動的計画法 */ function coinChangeIIDPComp(coins: Array, amt: number): number { const n = coins.length; // dp テーブルを初期化 const dp = Array.from({ length: amt + 1 }, () => 0); dp[0] = 1; // 状態遷移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // コイン i を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 5; // 動的計画法 let res = coinChangeIIDP(coins, amt); console.log(`目標金額を作るコインの組み合わせ数は ${res}`); // 空間最適化後の動的計画法 res = coinChangeIIDPComp(coins, amt); console.log(`目標金額を作るコインの組み合わせ数は ${res}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_dynamic_programming/edit_distance.ts ================================================ /** * File: edit_distance.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 編集距離:総当たり探索 */ function editDistanceDFS(s: string, t: string, i: number, j: number): number { // s と t がともに空なら 0 を返す if (i === 0 && j === 0) return 0; // s が空なら t の長さを返す if (i === 0) return j; // t が空なら s の長さを返す if (j === 0) return i; // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 const insert = editDistanceDFS(s, t, i, j - 1); const del = editDistanceDFS(s, t, i - 1, j); const replace = editDistanceDFS(s, t, i - 1, j - 1); // 最小編集回数を返す return Math.min(insert, del, replace) + 1; } /* 編集距離:メモ化探索 */ function editDistanceDFSMem( s: string, t: string, mem: Array>, i: number, j: number ): number { // s と t がともに空なら 0 を返す if (i === 0 && j === 0) return 0; // s が空なら t の長さを返す if (i === 0) return j; // t が空なら s の長さを返す if (j === 0) return i; // 記録済みなら、それをそのまま返す if (mem[i][j] !== -1) return mem[i][j]; // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 const insert = editDistanceDFSMem(s, t, mem, i, j - 1); const del = editDistanceDFSMem(s, t, mem, i - 1, j); const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最小編集回数を記録して返す mem[i][j] = Math.min(insert, del, replace) + 1; return mem[i][j]; } /* 編集距離:動的計画法 */ function editDistanceDP(s: string, t: string): number { const n = s.length, m = t.length; const dp = Array.from({ length: n + 1 }, () => Array.from({ length: m + 1 }, () => 0) ); // 状態遷移:先頭行と先頭列 for (let i = 1; i <= n; i++) { dp[i][0] = i; } for (let j = 1; j <= m; j++) { dp[0][j] = j; } // 状態遷移: 残りの行と列 for (let i = 1; i <= n; i++) { for (let j = 1; j <= m; j++) { if (s.charAt(i - 1) === t.charAt(j - 1)) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i - 1][j - 1]; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* 編集距離:空間最適化した動的計画法 */ function editDistanceDPComp(s: string, t: string): number { const n = s.length, m = t.length; const dp = new Array(m + 1).fill(0); // 状態遷移:先頭行 for (let j = 1; j <= m; j++) { dp[j] = j; } // 状態遷移:残りの行 for (let i = 1; i <= n; i++) { // 状態遷移:先頭列 let leftup = dp[0]; // dp[i-1, j-1] を一時保存する dp[0] = i; // 状態遷移:残りの列 for (let j = 1; j <= m; j++) { const temp = dp[j]; if (s.charAt(i - 1) === t.charAt(j - 1)) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftup; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; } leftup = temp; // 次の反復の dp[i-1, j-1] に更新する } } return dp[m]; } /* Driver Code */ const s = 'bag'; const t = 'pack'; const n = s.length, m = t.length; // 全探索 let res = editDistanceDFS(s, t, n, m); console.log(`${s} を ${t} に変更するには最小で ${res} 回の編集が必要`); // メモ化探索 const mem = Array.from({ length: n + 1 }, () => Array.from({ length: m + 1 }, () => -1) ); res = editDistanceDFSMem(s, t, mem, n, m); console.log(`${s} を ${t} に変更するには最小で ${res} 回の編集が必要`); // 動的計画法 res = editDistanceDP(s, t); console.log(`${s} を ${t} に変更するには最小で ${res} 回の編集が必要`); // 空間最適化後の動的計画法 res = editDistanceDPComp(s, t); console.log(`${s} を ${t} に変更するには最小で ${res} 回の編集が必要`); export {}; ================================================ FILE: ja/codes/typescript/chapter_dynamic_programming/knapsack.ts ================================================ /** * File: knapsack.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 0-1 ナップサック:総当たり探索 */ function knapsackDFS( wgt: Array, val: Array, i: number, c: number ): number { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i === 0 || c === 0) { return 0; } // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する const no = knapsackDFS(wgt, val, i - 1, c); const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // 2つの案のうち価値が大きいほうを返す return Math.max(no, yes); } /* 0-1 ナップサック:メモ化探索 */ function knapsackDFSMem( wgt: Array, val: Array, mem: Array>, i: number, c: number ): number { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i === 0 || c === 0) { return 0; } // 既に記録があればそのまま返す if (mem[i][c] !== -1) { return mem[i][c]; } // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する const no = knapsackDFSMem(wgt, val, mem, i - 1, c); const yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 2 つの案のうち価値が大きい方を記録して返す mem[i][c] = Math.max(no, yes); return mem[i][c]; } /* 0-1 ナップサック:動的計画法 */ function knapsackDP( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // dp テーブルを初期化 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // 状態遷移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = Math.max( dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* 0-1 ナップサック:空間最適化後の動的計画法 */ function knapsackDPComp( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // dp テーブルを初期化 const dp = Array(cap + 1).fill(0); // 状態遷移 for (let i = 1; i <= n; i++) { // 逆順に走査する for (let c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // 全探索 let res = knapsackDFS(wgt, val, n, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); // メモ化探索 const mem = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => -1) ); res = knapsackDFSMem(wgt, val, mem, n, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); // 動的計画法 res = knapsackDP(wgt, val, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); // 空間最適化後の動的計画法 res = knapsackDPComp(wgt, val, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts ================================================ /** * File: min_cost_climbing_stairs_dp.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 階段登りの最小コスト:動的計画法 */ function minCostClimbingStairsDP(cost: Array): number { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } // 部分問題の解を保存するために dp テーブルを初期化 const dp = new Array(n + 1); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = cost[1]; dp[2] = cost[2]; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (let i = 3; i <= n; i++) { dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* 階段昇りの最小コスト:空間最適化後の動的計画法 */ function minCostClimbingStairsDPComp(cost: Array): number { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } let a = cost[1], b = cost[2]; for (let i = 3; i <= n; i++) { const tmp = b; b = Math.min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; console.log(`入力された階段のコスト一覧:${cost}`); let res = minCostClimbingStairsDP(cost); console.log(`階段を登り切るための最小コスト:${res}`); res = minCostClimbingStairsDPComp(cost); console.log(`階段を登り切るための最小コスト:${res}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_dynamic_programming/min_path_sum.ts ================================================ /** * File: min_path_sum.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 最小経路和:全探索 */ function minPathSumDFS( grid: Array>, i: number, j: number ): number { // 左上のセルなら探索を終了する if (i === 0 && j == 0) { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return Infinity; } // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する const up = minPathSumDFS(grid, i - 1, j); const left = minPathSumDFS(grid, i, j - 1); // 左上隅から (i, j) までの最小経路コストを返す return Math.min(left, up) + grid[i][j]; } /* 最小経路和:メモ化探索 */ function minPathSumDFSMem( grid: Array>, mem: Array>, i: number, j: number ): number { // 左上のセルなら探索を終了する if (i === 0 && j === 0) { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return Infinity; } // 既に記録があればそのまま返す if (mem[i][j] != -1) { return mem[i][j]; } // 左と上のセルからの最小経路コスト const up = minPathSumDFSMem(grid, mem, i - 1, j); const left = minPathSumDFSMem(grid, mem, i, j - 1); // 左上から (i, j) までの最小経路コストを記録して返す mem[i][j] = Math.min(left, up) + grid[i][j]; return mem[i][j]; } /* 最小経路和:動的計画法 */ function minPathSumDP(grid: Array>): number { const n = grid.length, m = grid[0].length; // dp テーブルを初期化 const dp = Array.from({ length: n }, () => Array.from({ length: m }, () => 0) ); dp[0][0] = grid[0][0]; // 状態遷移:先頭行 for (let j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 状態遷移:先頭列 for (let i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 状態遷移: 残りの行と列 for (let i = 1; i < n; i++) { for (let j: number = 1; j < m; j++) { dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* 最小経路和:空間最適化後の動的計画法 */ function minPathSumDPComp(grid: Array>): number { const n = grid.length, m = grid[0].length; // dp テーブルを初期化 const dp = new Array(m); // 状態遷移:先頭行 dp[0] = grid[0][0]; for (let j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 状態遷移:残りの行 for (let i = 1; i < n; i++) { // 状態遷移:先頭列 dp[0] = dp[0] + grid[i][0]; // 状態遷移:残りの列 for (let j = 1; j < m; j++) { dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ const grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; const n = grid.length, m = grid[0].length; // 全探索 let res = minPathSumDFS(grid, n - 1, m - 1); console.log(`左上から右下までの最小経路和は ${res}`); // メモ化探索 const mem = Array.from({ length: n }, () => Array.from({ length: m }, () => -1) ); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); console.log(`左上から右下までの最小経路和は ${res}`); // 動的計画法 res = minPathSumDP(grid); console.log(`左上から右下までの最小経路和は ${res}`); // 空間最適化後の動的計画法 res = minPathSumDPComp(grid); console.log(`左上から右下までの最小経路和は ${res}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts ================================================ /** * File: unbounded_knapsack.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 完全ナップサック問題:動的計画法 */ function unboundedKnapsackDP( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // dp テーブルを初期化 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // 状態遷移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = Math.max( dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* 完全ナップサック問題:空間最適化後の動的計画法 */ function unboundedKnapsackDPComp( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // dp テーブルを初期化 const dp = Array.from({ length: cap + 1 }, () => 0); // 状態遷移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [1, 2, 3]; const val = [5, 11, 15]; const cap = 4; // 動的計画法 let res = unboundedKnapsackDP(wgt, val, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); // 空間最適化後の動的計画法 res = unboundedKnapsackDPComp(wgt, val, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_graph/graph_adjacency_list.ts ================================================ /** * File: graph_adjacency_list.ts * Created Time: 2023-02-09 * Author: Justin (xiefahit@gmail.com) */ import { Vertex } from '../modules/Vertex'; /* 隣接リストに基づく無向グラフクラス */ class GraphAdjList { // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 adjList: Map; /* コンストラクタ */ constructor(edges: Vertex[][]) { this.adjList = new Map(); // すべての頂点と辺を追加 for (const edge of edges) { this.addVertex(edge[0]); this.addVertex(edge[1]); this.addEdge(edge[0], edge[1]); } } /* 頂点数を取得 */ size(): number { return this.adjList.size; } /* 辺を追加 */ addEdge(vet1: Vertex, vet2: Vertex): void { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 ) { throw new Error('Illegal Argument Exception'); } // 辺 vet1 - vet2 を追加 this.adjList.get(vet1).push(vet2); this.adjList.get(vet2).push(vet1); } /* 辺を削除 */ removeEdge(vet1: Vertex, vet2: Vertex): void { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 || this.adjList.get(vet1).indexOf(vet2) === -1 ) { throw new Error('Illegal Argument Exception'); } // 辺 vet1 - vet2 を削除 this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); } /* 頂点を追加 */ addVertex(vet: Vertex): void { if (this.adjList.has(vet)) return; // 隣接リストに新しいリストを追加 this.adjList.set(vet, []); } /* 頂点を削除 */ removeVertex(vet: Vertex): void { if (!this.adjList.has(vet)) { throw new Error('Illegal Argument Exception'); } // 隣接リストから頂点 vet に対応するリストを削除 this.adjList.delete(vet); // 他の頂点のリストを走査し、vet を含むすべての辺を削除 for (const set of this.adjList.values()) { const index: number = set.indexOf(vet); if (index > -1) { set.splice(index, 1); } } } /* 隣接リストを出力 */ print(): void { console.log('隣接リスト ='); for (const [key, value] of this.adjList.entries()) { const tmp = []; for (const vertex of value) { tmp.push(vertex.val); } console.log(key.val + ': ' + tmp.join()); } } } /* Driver Code */ if (import.meta.url.endsWith(process.argv[1])) { /* 無向グラフを初期化 */ const v0 = new Vertex(1), v1 = new Vertex(3), v2 = new Vertex(2), v3 = new Vertex(5), v4 = new Vertex(4); const edges = [ [v0, v1], [v1, v2], [v2, v3], [v0, v3], [v2, v4], [v3, v4], ]; const graph = new GraphAdjList(edges); console.log('\n初期化後、グラフは'); graph.print(); /* 辺を追加 */ // 頂点 1, 2 は v0, v2 graph.addEdge(v0, v2); console.log('\n辺 1-2 を追加した後、グラフは'); graph.print(); /* 辺を削除 */ // 頂点 1, 3 は v0, v1 graph.removeEdge(v0, v1); console.log('\n辺 1-3 を削除した後、グラフは'); graph.print(); /* 頂点を追加 */ const v5 = new Vertex(6); graph.addVertex(v5); console.log('\n頂点 6 を追加した後、グラフは'); graph.print(); /* 頂点を削除 */ // 頂点 3 は v1 graph.removeVertex(v1); console.log('\n頂点 3 を削除した後、グラフは'); graph.print(); } export { GraphAdjList }; ================================================ FILE: ja/codes/typescript/chapter_graph/graph_adjacency_matrix.ts ================================================ /** * File: graph_adjacency_matrix.ts * Created Time: 2023-02-09 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 隣接行列に基づく無向グラフクラス */ class GraphAdjMat { vertices: number[]; // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す adjMat: number[][]; // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 /* コンストラクタ */ constructor(vertices: number[], edges: number[][]) { this.vertices = []; this.adjMat = []; // 頂点を追加 for (const val of vertices) { this.addVertex(val); } // 辺を追加 // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する for (const e of edges) { this.addEdge(e[0], e[1]); } } /* 頂点数を取得 */ size(): number { return this.vertices.length; } /* 頂点を追加 */ addVertex(val: number): void { const n: number = this.size(); // 頂点リストに新しい頂点の値を追加 this.vertices.push(val); // 隣接行列に 1 行追加 const newRow: number[] = []; for (let j: number = 0; j < n; j++) { newRow.push(0); } this.adjMat.push(newRow); // 隣接行列に 1 列追加 for (const row of this.adjMat) { row.push(0); } } /* 頂点を削除 */ removeVertex(index: number): void { if (index >= this.size()) { throw new RangeError('Index Out Of Bounds Exception'); } // 頂点リストから index の頂点を削除する this.vertices.splice(index, 1); // 隣接行列で index 行を削除する this.adjMat.splice(index, 1); // 隣接行列で index 列を削除する for (const row of this.adjMat) { row.splice(index, 1); } } /* 辺を追加 */ // 引数 i, j は vertices の要素インデックスに対応する addEdge(i: number, j: number): void { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } // 無向グラフでは、隣接行列は主対角線に関して対称であり、(i, j) === (j, i) を満たす this.adjMat[i][j] = 1; this.adjMat[j][i] = 1; } /* 辺を削除 */ // 引数 i, j は vertices の要素インデックスに対応する removeEdge(i: number, j: number): void { // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } this.adjMat[i][j] = 0; this.adjMat[j][i] = 0; } /* 隣接行列を出力 */ print(): void { console.log('頂点リスト = ', this.vertices); console.log('隣接行列 =', this.adjMat); } } /* Driver Code */ /* 無向グラフを初期化 */ // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 const vertices: number[] = [1, 3, 2, 5, 4]; const edges: number[][] = [ [0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4], ]; const graph: GraphAdjMat = new GraphAdjMat(vertices, edges); console.log('\n初期化後、グラフは'); graph.print(); /* 辺を追加 */ // 頂点 1, 2 のインデックスはそれぞれ 0, 2 graph.addEdge(0, 2); console.log('\n辺 1-2 を追加した後、グラフは'); graph.print(); /* 辺を削除 */ // 頂点 1, 3 のインデックスはそれぞれ 0, 1 graph.removeEdge(0, 1); console.log('\n辺 1-3 を削除した後、グラフは'); graph.print(); /* 頂点を追加 */ graph.addVertex(6); console.log('\n頂点 6 を追加した後、グラフは'); graph.print(); /* 頂点を削除 */ // 頂点 3 のインデックスは 1 graph.removeVertex(1); console.log('\n頂点 3 を削除した後、グラフは'); graph.print(); export {}; ================================================ FILE: ja/codes/typescript/chapter_graph/graph_bfs.ts ================================================ /** * File: graph_bfs.ts * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { GraphAdjList } from './graph_adjacency_list'; import { Vertex } from '../modules/Vertex'; /* 幅優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { // 頂点の走査順序 const res: Vertex[] = []; // 訪問済み頂点を記録するためのハッシュ集合 const visited: Set = new Set(); visited.add(startVet); // BFS の実装にキューを用いる const que = [startVet]; // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す while (que.length) { const vet = que.shift(); // 先頭の頂点をデキュー res.push(vet); // 訪問した頂点を記録 // この頂点のすべての隣接頂点を走査 for (const adjVet of graph.adjList.get(vet) ?? []) { if (visited.has(adjVet)) { continue; // 訪問済みの頂点をスキップ } que.push(adjVet); // 未訪問のものだけをキューに入れる visited.add(adjVet); // この頂点を訪問済みにする } } // 頂点の走査順を返す return res; } /* Driver Code */ /* 無向グラフを初期化 */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; const graph = new GraphAdjList(edges); console.log('\n初期化後、グラフは'); graph.print(); /* 幅優先探索 */ const res = graphBFS(graph, v[0]); console.log('\n幅優先探索(BFS)の頂点列は'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: ja/codes/typescript/chapter_graph/graph_dfs.ts ================================================ /** * File: graph_dfs.ts * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { Vertex } from '../modules/Vertex'; import { GraphAdjList } from './graph_adjacency_list'; /* 深さ優先走査の補助関数 */ function dfs( graph: GraphAdjList, visited: Set, res: Vertex[], vet: Vertex ): void { res.push(vet); // 訪問した頂点を記録 visited.add(vet); // この頂点を訪問済みにする // この頂点のすべての隣接頂点を走査 for (const adjVet of graph.adjList.get(vet)) { if (visited.has(adjVet)) { continue; // 訪問済みの頂点をスキップ } // 隣接頂点を再帰的に訪問 dfs(graph, visited, res, adjVet); } } /* 深さ優先探索 */ // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { // 頂点の走査順序 const res: Vertex[] = []; // 訪問済み頂点を記録するためのハッシュ集合 const visited: Set = new Set(); dfs(graph, visited, res, startVet); return res; } /* Driver Code */ /* 無向グラフを初期化 */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; const graph = new GraphAdjList(edges); console.log('\n初期化後、グラフは'); graph.print(); /* 深さ優先探索 */ const res = graphDFS(graph, v[0]); console.log('\n深さ優先探索(DFS)の頂点列は'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: ja/codes/typescript/chapter_greedy/coin_change_greedy.ts ================================================ /** * File: coin_change_greedy.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* コイン交換:貪欲法 */ function coinChangeGreedy(coins: number[], amt: number): number { // coins 配列はソート済みと仮定する let i = coins.length - 1; let count = 0; // 残額がなくなるまで貪欲選択を繰り返す while (amt > 0) { // 残額以下で最も近い硬貨を見つける while (i > 0 && coins[i] > amt) { i--; } // coins[i] を選択する amt -= coins[i]; count++; } // 実行可能な解が見つからなければ -1 を返す return amt === 0 ? count : -1; } /* Driver Code */ // 貪欲法:大域最適解を保証できる let coins: number[] = [1, 5, 10, 20, 50, 100]; let amt: number = 186; let res: number = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`金額 ${amt} を作るのに必要な最小硬貨枚数は ${res}`); // 貪欲法:大域最適解を保証できない coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`金額 ${amt} を作るのに必要な最小硬貨枚数は ${res}`); console.log('実際に必要な最小枚数は 3 ,つまり 20 + 20 + 20'); // 貪欲法:大域最適解を保証できない coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`金額 ${amt} を作るのに必要な最小硬貨枚数は ${res}`); console.log('実際に必要な最小枚数は 2 ,つまり 49 + 49'); export {}; ================================================ FILE: ja/codes/typescript/chapter_greedy/fractional_knapsack.ts ================================================ /** * File: fractional_knapsack.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 品物 */ class Item { w: number; // 品物の重さ v: number; // 品物の価値 constructor(w: number, v: number) { this.w = w; this.v = v; } } /* 分数ナップサック:貪欲法 */ function fractionalKnapsack(wgt: number[], val: number[], cap: number): number { // 重さと価値の 2 属性を持つ品物リストを作成 const items: Item[] = wgt.map((w, i) => new Item(w, val[i])); // 単位価値 item.v / item.w の高い順にソートする items.sort((a, b) => b.v / b.w - a.v / a.w); // 貪欲選択を繰り返す let res = 0; for (const item of items) { if (item.w <= cap) { // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる res += item.v; cap -= item.w; } else { // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる res += (item.v / item.w) * cap; // 残り容量がないため、ループを抜ける break; } } return res; } /* Driver Code */ const wgt: number[] = [10, 20, 30, 40, 50]; const val: number[] = [50, 120, 150, 210, 240]; const cap: number = 50; // 貪欲法 const res: number = fractionalKnapsack(wgt, val, cap); console.log(`ナップサック容量を超えない最大価値は ${res}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_greedy/max_capacity.ts ================================================ /** * File: max_capacity.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 最大容量:貪欲法 */ function maxCapacity(ht: number[]): number { // i, j を初期化し、それぞれ配列の両端に置く let i = 0, j = ht.length - 1; // 初期の最大容量は 0 let res = 0; // 2 枚の板が出会うまで貪欲選択を繰り返す while (i < j) { // 最大容量を更新する const cap: number = Math.min(ht[i], ht[j]) * (j - i); res = Math.max(res, cap); // 短い方を内側へ動かす if (ht[i] < ht[j]) { i += 1; } else { j -= 1; } } return res; } /* Driver Code */ const ht: number[] = [3, 8, 5, 2, 7, 7, 3, 4]; // 貪欲法 const res: number = maxCapacity(ht); console.log(`最大容量は ${res}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_greedy/max_product_cutting.ts ================================================ /** * File: max_product_cutting.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 最大切断積:貪欲法 */ function maxProductCutting(n: number): number { // n <= 3 のときは、必ず 1 を切り出す if (n <= 3) { return 1 * (n - 1); } // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする let a: number = Math.floor(n / 3); let b: number = n % 3; if (b === 1) { // 余りが 1 のときは、1 * 3 を 2 * 2 に変える return Math.pow(3, a - 1) * 2 * 2; } if (b === 2) { // 余りが 2 のときは、そのままにする return Math.pow(3, a) * 2; } // 余りが 0 のときは、そのままにする return Math.pow(3, a); } /* Driver Code */ let n: number = 58; // 貪欲法 let res: number = maxProductCutting(n); console.log(`最大分割積は ${res}`); export {}; ================================================ FILE: ja/codes/typescript/chapter_hashing/array_hash_map.ts ================================================ /** * File: array_hash_map.ts * Created Time: 2022-12-20 * Author: Daniel (better.sunjian@gmail.com) */ /* キーと値の組 Number -> String */ class Pair { public key: number; public val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* 配列ベースのハッシュテーブル */ class ArrayHashMap { private readonly buckets: (Pair | null)[]; constructor() { // 100 個のバケットを含む配列を初期化 this.buckets = new Array(100).fill(null); } /* ハッシュ関数 */ private hashFunc(key: number): number { return key % 100; } /* 検索操作 */ public get(key: number): string | null { let index = this.hashFunc(key); let pair = this.buckets[index]; if (pair === null) return null; return pair.val; } /* 追加操作 */ public set(key: number, val: string) { let index = this.hashFunc(key); this.buckets[index] = new Pair(key, val); } /* 削除操作 */ public delete(key: number) { let index = this.hashFunc(key); // null に設定し、削除を表す this.buckets[index] = null; } /* すべてのキーと値のペアを取得 */ public entries(): (Pair | null)[] { let arr: (Pair | null)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i]); } } return arr; } /* すべてのキーを取得 */ public keys(): (number | undefined)[] { let arr: (number | undefined)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i].key); } } return arr; } /* すべての値を取得 */ public values(): (string | undefined)[] { let arr: (string | undefined)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i].val); } } return arr; } /* ハッシュテーブルを出力 */ public print() { let pairSet = this.entries(); for (const pair of pairSet) { console.info(`${pair.key} -> ${pair.val}`); } } } /* Driver Code */ /* ハッシュテーブルを初期化 */ const map = new ArrayHashMap(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.set(12836, 'シャオハー'); map.set(15937, 'シャオルオ'); map.set(16750, 'シャオスワン'); map.set(13276, 'シャオファー'); map.set(10583, 'シャオヤー'); console.info('\n追加完了後,ハッシュテーブルは\nKey -> Value'); map.print(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 let name = map.get(15937); console.info('\n学籍番号 15937 を入力し,見つかった氏名 ' + name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.delete(10583); console.info('\n10583 を削除した後,ハッシュテーブルは\nKey -> Value'); map.print(); /* ハッシュテーブルを走査 */ console.info('\nキーと値の組 Key->Value を走査'); for (const pair of map.entries()) { if (!pair) continue; console.info(pair.key + ' -> ' + pair.val); } console.info('\nキー Key を個別に走査'); for (const key of map.keys()) { console.info(key); } console.info('\n値 Value を個別に走査'); for (const val of map.values()) { console.info(val); } export {}; ================================================ FILE: ja/codes/typescript/chapter_hashing/hash_map.ts ================================================ /** * File: hash_map.ts * Created Time: 2022-12-20 * Author: Daniel (better.sunjian@gmail.com) */ /* Driver Code */ /* ハッシュテーブルを初期化 */ const map = new Map(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.set(12836, 'シャオハー'); map.set(15937, 'シャオルオ'); map.set(16750, 'シャオスワン'); map.set(13276, 'シャオファー'); map.set(10583, 'シャオヤー'); console.info('\n追加完了後,ハッシュテーブルは\nKey -> Value'); console.info(map); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 let name = map.get(15937); console.info('\n学籍番号 15937 を入力し,見つかった氏名 ' + name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.delete(10583); console.info('\n10583 を削除した後,ハッシュテーブルは\nKey -> Value'); console.info(map); /* ハッシュテーブルを走査 */ console.info('\nキーと値の組 Key->Value を走査'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\nキー Key を個別に走査'); for (const k of map.keys()) { console.info(k); } console.info('\n値 Value を個別に走査'); for (const v of map.values()) { console.info(v); } export {}; ================================================ FILE: ja/codes/typescript/chapter_hashing/hash_map_chaining.ts ================================================ /** * File: hash_map_chaining.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* キーと値の組 Number -> String */ class Pair { key: number; val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* チェイン法ハッシュテーブル */ class HashMapChaining { #size: number; // キーと値のペア数 #capacity: number; // ハッシュテーブル容量 #loadThres: number; // リサイズを発動する負荷率のしきい値 #extendRatio: number; // 拡張倍率 #buckets: Pair[][]; // バケット配列 /* コンストラクタ */ constructor() { this.#size = 0; this.#capacity = 4; this.#loadThres = 2.0 / 3.0; this.#extendRatio = 2; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); } /* ハッシュ関数 */ #hashFunc(key: number): number { return key % this.#capacity; } /* 負荷率 */ #loadFactor(): number { return this.#size / this.#capacity; } /* 検索操作 */ get(key: number): string | null { const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // バケットを走査し、key が見つかれば対応する val を返す for (const pair of bucket) { if (pair.key === key) { return pair.val; } } // key が見つからない場合は null を返す return null; } /* 追加操作 */ put(key: number, val: string): void { // 負荷率がしきい値を超えたら、リサイズを実行 if (this.#loadFactor() > this.#loadThres) { this.#extend(); } const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // バケットを走査し、指定した key が見つかれば対応する val を更新して返す for (const pair of bucket) { if (pair.key === key) { pair.val = val; return; } } // その key が存在しなければ、キーと値のペアを末尾に追加 const pair = new Pair(key, val); bucket.push(pair); this.#size++; } /* 削除操作 */ remove(key: number): void { const index = this.#hashFunc(key); let bucket = this.#buckets[index]; // バケットを走査してキーと値のペアを削除 for (let i = 0; i < bucket.length; i++) { if (bucket[i].key === key) { bucket.splice(i, 1); this.#size--; break; } } } /* ハッシュテーブルを拡張 */ #extend(): void { // 元のハッシュテーブルを一時保存 const bucketsTmp = this.#buckets; // リサイズ後の新しいハッシュテーブルを初期化 this.#capacity *= this.#extendRatio; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); this.#size = 0; // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for (const bucket of bucketsTmp) { for (const pair of bucket) { this.put(pair.key, pair.val); } } } /* ハッシュテーブルを出力 */ print(): void { for (const bucket of this.#buckets) { let res = []; for (const pair of bucket) { res.push(pair.key + ' -> ' + pair.val); } console.log(res); } } } /* Driver Code */ /* ハッシュテーブルを初期化 */ const map = new HashMapChaining(); /* 追加操作 */ // ハッシュテーブルにキーと値のペア (key, value) を追加 map.put(12836, 'シャオハー'); map.put(15937, 'シャオルオ'); map.put(16750, 'シャオスワン'); map.put(13276, 'シャオファー'); map.put(10583, 'シャオヤー'); console.log('\n追加完了後,ハッシュテーブルは\nKey -> Value'); map.print(); /* 検索操作 */ // キー key をハッシュテーブルに渡し、値 value を取得 const name = map.get(13276); console.log('\n学籍番号 13276 を入力し,見つかった氏名 ' + name); /* 削除操作 */ // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(12836); console.log('\n12836 を削除した後,ハッシュテーブルは\nKey -> Value'); map.print(); export {}; ================================================ FILE: ja/codes/typescript/chapter_hashing/hash_map_open_addressing.ts ================================================ /** * File: hash_map_open_addressing.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) */ /* キーと値の組 Number -> String */ class Pair { key: number; val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* オープンアドレス法ハッシュテーブル */ class HashMapOpenAddressing { private size: number; // キーと値のペア数 private capacity: number; // ハッシュテーブル容量 private loadThres: number; // リサイズを発動する負荷率のしきい値 private extendRatio: number; // 拡張倍率 private buckets: Array; // バケット配列 private TOMBSTONE: Pair; // 削除済みマーク /* コンストラクタ */ constructor() { this.size = 0; // キーと値のペア数 this.capacity = 4; // ハッシュテーブル容量 this.loadThres = 2.0 / 3.0; // リサイズを発動する負荷率のしきい値 this.extendRatio = 2; // 拡張倍率 this.buckets = Array(this.capacity).fill(null); // バケット配列 this.TOMBSTONE = new Pair(-1, '-1'); // 削除済みマーク } /* ハッシュ関数 */ private hashFunc(key: number): number { return key % this.capacity; } /* 負荷率 */ private loadFactor(): number { return this.size / this.capacity; } /* key に対応するバケットインデックスを探す */ private findBucket(key: number): number { let index = this.hashFunc(key); let firstTombstone = -1; // 線形プロービングを行い、空バケットに達したら終了 while (this.buckets[index] !== null) { // key が見つかったら、対応するバケットのインデックスを返す if (this.buckets[index]!.key === key) { // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 if (firstTombstone !== -1) { this.buckets[firstTombstone] = this.buckets[index]; this.buckets[index] = this.TOMBSTONE; return firstTombstone; // 移動後のバケットインデックスを返す } return index; // バケットのインデックスを返す } // 最初に見つかった削除マークを記録 if ( firstTombstone === -1 && this.buckets[index] === this.TOMBSTONE ) { firstTombstone = index; } // バケットのインデックスを計算し、末尾を越えたら先頭に戻る index = (index + 1) % this.capacity; } // key が存在しない場合は追加位置のインデックスを返す return firstTombstone === -1 ? index : firstTombstone; } /* 検索操作 */ get(key: number): string | null { // key に対応するバケットインデックスを探す const index = this.findBucket(key); // キーと値の組が見つかったら、対応する val を返す if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { return this.buckets[index]!.val; } // キーと値の組が存在しなければ null を返す return null; } /* 追加操作 */ put(key: number, val: string): void { // 負荷率がしきい値を超えたら、リサイズを実行 if (this.loadFactor() > this.loadThres) { this.extend(); } // key に対応するバケットインデックスを探す const index = this.findBucket(key); // キーと値の組が見つかったら、val を上書きして返す if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { this.buckets[index]!.val = val; return; } // キーと値の組が存在しない場合は、その組を追加する this.buckets[index] = new Pair(key, val); this.size++; } /* 削除操作 */ remove(key: number): void { // key に対応するバケットインデックスを探す const index = this.findBucket(key); // キーと値の組が見つかったら、削除マーカーで上書きする if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { this.buckets[index] = this.TOMBSTONE; this.size--; } } /* ハッシュテーブルを拡張 */ private extend(): void { // 元のハッシュテーブルを一時保存 const bucketsTmp = this.buckets; // リサイズ後の新しいハッシュテーブルを初期化 this.capacity *= this.extendRatio; this.buckets = Array(this.capacity).fill(null); this.size = 0; // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for (const pair of bucketsTmp) { if (pair !== null && pair !== this.TOMBSTONE) { this.put(pair.key, pair.val); } } } /* ハッシュテーブルを出力 */ print(): void { for (const pair of this.buckets) { if (pair === null) { console.log('null'); } else if (pair === this.TOMBSTONE) { console.log('TOMBSTONE'); } else { console.log(pair.key + ' -> ' + pair.val); } } } } /* Driver Code */ // ハッシュテーブルを初期化 const hashmap = new HashMapOpenAddressing(); // 追加操作 // ハッシュテーブルにキーと値の組 (key, val) を追加する hashmap.put(12836, 'シャオハー'); hashmap.put(15937, 'シャオルオ'); hashmap.put(16750, 'シャオスワン'); hashmap.put(13276, 'シャオファー'); hashmap.put(10583, 'シャオヤー'); console.log('\n追加完了後,ハッシュテーブルは\nKey -> Value'); hashmap.print(); // 検索操作 // ハッシュテーブルにキー key を入力し、値 val を得る const name = hashmap.get(13276); console.log('\n学籍番号 13276 を入力し,見つかった氏名 ' + name); // 削除操作 // ハッシュテーブルからキーと値の組 (key, val) を削除する hashmap.remove(16750); console.log('\n16750 を削除した後,ハッシュテーブルは\nKey -> Value'); hashmap.print(); export {}; ================================================ FILE: ja/codes/typescript/chapter_hashing/simple_hash.ts ================================================ /** * File: simple_hash.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 加算ハッシュ */ function addHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* 乗算ハッシュ */ function mulHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (31 * hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* XOR ハッシュ */ function xorHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash ^= c.charCodeAt(0); } return hash % MODULUS; } /* 回転ハッシュ */ function rotHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; } return hash; } /* Driver Code */ const key = 'Hello アルゴリズム'; let hash = addHash(key); console.log('加算ハッシュ値は ' + hash); hash = mulHash(key); console.log('乗算ハッシュ値は ' + hash); hash = xorHash(key); console.log('XOR ハッシュ値は ' + hash); hash = rotHash(key); console.log('回転ハッシュ値は ' + hash); ================================================ FILE: ja/codes/typescript/chapter_heap/my_heap.ts ================================================ /** * File: my_heap.ts * Created Time: 2023-02-07 * Author: Justin (xiefahit@gmail.com) */ import { printHeap } from '../modules/PrintUtil'; /* 最大ヒープクラス */ class MaxHeap { private maxHeap: number[]; /* コンストラクタ。空のヒープを作成するか、入力リストからヒープを構築する */ constructor(nums?: number[]) { // リスト要素をそのままヒープに追加 this.maxHeap = nums === undefined ? [] : [...nums]; // 葉ノード以外のすべてのノードをヒープ化 for (let i = this.parent(this.size() - 1); i >= 0; i--) { this.siftDown(i); } } /* 左子ノードのインデックスを取得 */ private left(i: number): number { return 2 * i + 1; } /* 右子ノードのインデックスを取得 */ private right(i: number): number { return 2 * i + 2; } /* 親ノードのインデックスを取得 */ private parent(i: number): number { return Math.floor((i - 1) / 2); // 切り捨て除算 } /* 要素を交換 */ private swap(i: number, j: number): void { const tmp = this.maxHeap[i]; this.maxHeap[i] = this.maxHeap[j]; this.maxHeap[j] = tmp; } /* ヒープのサイズを取得 */ public size(): number { return this.maxHeap.length; } /* ヒープが空かどうかを判定 */ public isEmpty(): boolean { return this.size() === 0; } /* ヒープ先頭要素にアクセス */ public peek(): number { return this.maxHeap[0]; } /* 要素をヒープに追加 */ public push(val: number): void { // ノードを追加 this.maxHeap.push(val); // 下から上へヒープ化 this.siftUp(this.size() - 1); } /* ノード i から始めて、下から上へヒープ化 */ private siftUp(i: number): void { while (true) { // ノード i の親ノードを取得 const p = this.parent(i); // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break; // 2 つのノードを交換 this.swap(i, p); // ループで下から上へヒープ化 i = p; } } /* 要素をヒープから取り出す */ public pop(): number { // 空判定の処理 if (this.isEmpty()) throw new RangeError('Heap is empty.'); // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) this.swap(0, this.size() - 1); // ノードを削除 const val = this.maxHeap.pop(); // 上から下へヒープ化 this.siftDown(0); // ヒープ先頭要素を返す return val; } /* ノード i から始めて、上から下へヒープ化 */ private siftDown(i: number): void { while (true) { // ノード i, l, r のうち値が最大のノードを ma とする const l = this.left(i), r = this.right(i); let ma = i; if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l; if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r; // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma === i) break; // 2 つのノードを交換 this.swap(i, ma); // ループで上から下へヒープ化 i = ma; } } /* ヒープ(二分木)を出力 */ public print(): void { printHeap(this.maxHeap); } /* ヒープから要素を取り出す */ public getMaxHeap(): number[] { return this.maxHeap; } } /* Driver Code */ if (import.meta.url.endsWith(process.argv[1])) { /* 最大ヒープを初期化 */ const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); console.log('\nリストを入力してヒープを構築した後'); maxHeap.print(); /* ヒープ頂点の要素を取得 */ let peek = maxHeap.peek(); console.log(`\nヒープの先頭要素は ${peek}`); /* 要素をヒープに追加 */ const val = 7; maxHeap.push(val); console.log(`\n要素 ${val} をヒープに追加した後`); maxHeap.print(); /* ヒープ頂点の要素を取り出す */ peek = maxHeap.pop(); console.log(`\nヒープの先頭要素 ${peek} を取り出した後`); maxHeap.print(); /* ヒープのサイズを取得 */ const size = maxHeap.size(); console.log(`\nヒープの要素数は ${size}`); /* ヒープが空かどうかを判定 */ const isEmpty = maxHeap.isEmpty(); console.log(`\nヒープが空かどうか ${isEmpty}`); } export { MaxHeap }; ================================================ FILE: ja/codes/typescript/chapter_heap/top_k.ts ================================================ /** * File: top_k.ts * Created Time: 2023-08-13 * Author: Justin (xiefahit@gmail.com) */ import { MaxHeap } from './my_heap'; /* 要素をヒープに追加 */ function pushMinHeap(maxHeap: MaxHeap, val: number): void { // 要素を反転する maxHeap.push(-val); } /* 要素をヒープから取り出す */ function popMinHeap(maxHeap: MaxHeap): number { // 要素を反転する return -maxHeap.pop(); } /* ヒープ先頭要素にアクセス */ function peekMinHeap(maxHeap: MaxHeap): number { // 要素を反転する return -maxHeap.peek(); } /* ヒープから要素を取り出す */ function getMinHeap(maxHeap: MaxHeap): number[] { // 要素を反転する return maxHeap.getMaxHeap().map((num: number) => -num); } /* ヒープに基づいて配列中の最大の k 個の要素を探す */ function topKHeap(nums: number[], k: number): number[] { // 最小ヒープを初期化する // 注意: ヒープ内の全要素を反転し、最大ヒープで最小ヒープをシミュレートする const maxHeap = new MaxHeap([]); // 配列の先頭 k 個の要素をヒープに追加 for (let i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // k+1 番目の要素から開始し、ヒープ長を k に保つ for (let i = k; i < nums.length; i++) { // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } // ヒープ内の要素を返す return getMinHeap(maxHeap); } /* Driver Code */ const nums = [1, 7, 6, 3, 2]; const k = 3; const res = topKHeap(nums, k); console.log(`最大の ${k} 個の要素は`, res); ================================================ FILE: ja/codes/typescript/chapter_searching/binary_search.ts ================================================ /** * File: binary_search.ts * Created Time: 2022-12-27 * Author: Daniel (better.sunjian@gmail.com) */ /* 二分探索(両閉区間) */ function binarySearch(nums: number[], target: number): number { // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す let i = 0, j = nums.length - 1; // ループし、探索区間が空になったら終了する(i > j で空) while (i <= j) { // 中点インデックス m を計算 const m = Math.floor(i + (j - i) / 2); if (nums[m] < target) { // この場合、target は区間 [m+1, j] にある i = m + 1; } else if (nums[m] > target) { // この場合、target は区間 [i, m-1] にある j = m - 1; } else { // 目標要素が見つかったらそのインデックスを返す return m; } } return -1; // 目標要素が見つからなければ -1 を返す } /* 二分探索(左閉右開区間) */ function binarySearchLCRO(nums: number[], target: number): number { // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す let i = 0, j = nums.length; // ループし、探索区間が空になったら終了する(i = j で空) while (i < j) { // 中点インデックス m を計算 const m = Math.floor(i + (j - i) / 2); if (nums[m] < target) { // この場合、target は区間 [m+1, j) にある i = m + 1; } else if (nums[m] > target) { // この場合、target は区間 [i, m) にある j = m; } else { // 目標要素が見つかったらそのインデックスを返す return m; } } return -1; // 目標要素が見つからなければ -1 を返す } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分探索(両閉区間) */ let index = binarySearch(nums, target); console.info('対象要素 6 のインデックス = %d', index); /* 二分探索(左閉右開区間) */ index = binarySearchLCRO(nums, target); console.info('対象要素 6 のインデックス = %d', index); export {}; ================================================ FILE: ja/codes/typescript/chapter_searching/binary_search_edge.ts ================================================ /** * File: binary_search_edge.ts * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ import { binarySearchInsertion } from './binary_search_insertion'; /* 最も左の target を二分探索 */ function binarySearchLeftEdge(nums: Array, target: number): number { // target の挿入位置を探すのと等価 const i = binarySearchInsertion(nums, target); // target が見つからなければ、-1 を返す if (i === nums.length || nums[i] !== target) { return -1; } // target が見つかったら、インデックス i を返す return i; } /* 最も右の target を二分探索 */ function binarySearchRightEdge(nums: Array, target: number): number { // 最左の target + 1 を探す問題に変換する const i = binarySearchInsertion(nums, target + 1); // j は最も右の target を指し、i は target より大きい最初の要素を指す const j = i - 1; // target が見つからなければ、-1 を返す if (j === -1 || nums[j] !== target) { return -1; } // target が見つかったら、インデックス j を返す return j; } /* Driver Code */ // 重複要素を含む配列 let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\n配列 nums = ' + nums); // 二分探索で左端と右端を探す for (const target of [6, 7]) { let index = binarySearchLeftEdge(nums, target); console.log('一番左の要素 ' + target + ' のインデックスは ' + index); index = binarySearchRightEdge(nums, target); console.log('一番右の要素 ' + target + ' のインデックスは ' + index); } export {}; ================================================ FILE: ja/codes/typescript/chapter_searching/binary_search_insertion.ts ================================================ /** * File: binary_search_insertion.ts * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 二分探索で挿入位置を探す(重複要素なし) */ function binarySearchInsertionSimple( nums: Array, target: number ): number { let i = 0, j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { const m = Math.floor(i + (j - i) / 2); // 中点インデックス m を計算し、Math.floor() で切り捨てる if (nums[m] < target) { i = m + 1; // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1; // target は区間 [i, m-1] にある } else { return m; // target が見つかったら、挿入位置 m を返す } } // target が見つからなければ、挿入位置 i を返す return i; } /* 二分探索で挿入位置を探す(重複要素あり) */ function binarySearchInsertion(nums: Array, target: number): number { let i = 0, j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { const m = Math.floor(i + (j - i) / 2); // 中点インデックス m を計算し、Math.floor() で切り捨てる if (nums[m] < target) { i = m + 1; // target は区間 [m+1, j] にある } else if (nums[m] > target) { j = m - 1; // target は区間 [i, m-1] にある } else { j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある } } // 挿入位置 i を返す return i; } /* Driver Code */ // 重複要素のない配列 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; console.log('\n配列 nums = ' + nums); // 二分探索で挿入位置を探す for (const target of [6, 9]) { const index = binarySearchInsertionSimple(nums, target); console.log('要素 ' + target + ' の挿入位置のインデックスは ' + index); } // 重複要素を含む配列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\n配列 nums = ' + nums); // 二分探索で挿入位置を探す for (const target of [2, 6, 20]) { const index = binarySearchInsertion(nums, target); console.log('要素 ' + target + ' の挿入位置のインデックスは ' + index); } export { binarySearchInsertion }; ================================================ FILE: ja/codes/typescript/chapter_searching/hashing_search.ts ================================================ /** * File: hashing_search.ts * Created Time: 2022-12-29 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { ListNode, arrToLinkedList } from '../modules/ListNode'; /* ハッシュ探索(配列) */ function hashingSearchArray(map: Map, target: number): number { // ハッシュテーブルの key: 目標要素、value: インデックス // ハッシュテーブルにこの key がなければ -1 を返す return map.has(target) ? (map.get(target) as number) : -1; } /* ハッシュ探索(連結リスト) */ function hashingSearchLinkedList( map: Map, target: number ): ListNode | null { // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト // ハッシュテーブルにこの key がなければ null を返す return map.has(target) ? (map.get(target) as ListNode) : null; } /* Driver Code */ const target = 3; /* ハッシュ探索(配列) */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // ハッシュテーブルを初期化 const map = new Map(); for (let i = 0; i < nums.length; i++) { map.set(nums[i], i); // key: 要素、value: インデックス } const index = hashingSearchArray(map, target); console.log('対象要素 3 のインデックス = ' + index); /* ハッシュ探索(連結リスト) */ let head = arrToLinkedList(nums); // ハッシュテーブルを初期化 const map1 = new Map(); while (head != null) { map1.set(head.val, head); // key: ノード値、value: ノード head = head.next; } const node = hashingSearchLinkedList(map1, target); console.log('対象ノード値 3 に対応するノードオブジェクトは', node); export {}; ================================================ FILE: ja/codes/typescript/chapter_searching/linear_search.ts ================================================ /** * File: linear_search.ts * Created Time: 2023-01-07 * Author: Daniel (better.sunjian@gmail.com) */ import { ListNode, arrToLinkedList } from '../modules/ListNode'; /* 線形探索(配列) */ function linearSearchArray(nums: number[], target: number): number { // 配列を走査 for (let i = 0; i < nums.length; i++) { // 目標要素が見つかったらそのインデックスを返す if (nums[i] === target) { return i; } } // 目標要素が見つからなければ -1 を返す return -1; } /* 線形探索(連結リスト) */ function linearSearchLinkedList( head: ListNode | null, target: number ): ListNode | null { // 連結リストを走査 while (head) { // 対象ノードが見つかったら、それを返す if (head.val === target) { return head; } head = head.next; } // 対象ノードが見つからない場合は null を返す return null; } /* Driver Code */ const target = 3; /* 配列で線形探索を行う */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; const index = linearSearchArray(nums, target); console.log('対象要素 3 のインデックス =', index); /* 連結リストで線形探索を行う */ const head = arrToLinkedList(nums); const node = linearSearchLinkedList(head, target); console.log('対象ノード値 3 に対応するノードオブジェクトは', node); export {}; ================================================ FILE: ja/codes/typescript/chapter_searching/two_sum.ts ================================================ /** * File: two_sum.ts * Created Time: 2022-12-15 * Author: gyt95 (gytkwan@gmail.com) */ /* 方法 1:総当たり列挙 */ function twoSumBruteForce(nums: number[], target: number): number[] { const n = nums.length; // 2重ループのため、時間計算量は O(n^2) for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { if (nums[i] + nums[j] === target) { return [i, j]; } } } return []; } /* 方法 2:補助ハッシュテーブル */ function twoSumHashTable(nums: number[], target: number): number[] { // 補助ハッシュテーブルを使用し、空間計算量は O(n) let m: Map = new Map(); // 単一ループで、時間計算量は O(n) for (let i = 0; i < nums.length; i++) { let index = m.get(target - nums[i]); if (index !== undefined) { return [index, i]; } else { m.set(nums[i], i); } } return []; } /* Driver Code */ // 方法 1 const nums = [2, 7, 11, 15], target = 13; let res = twoSumBruteForce(nums, target); console.log('方法1 res = ', res); // 方法 2 res = twoSumHashTable(nums, target); console.log('方法2 res = ', res); export {}; ================================================ FILE: ja/codes/typescript/chapter_sorting/bubble_sort.ts ================================================ /** * File: bubble_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* バブルソート */ function bubbleSort(nums: number[]): void { // 外側のループ:未ソート区間は [0, i] for (let i = nums.length - 1; i > 0; i--) { // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* バブルソート(フラグ最適化) */ function bubbleSortWithFlag(nums: number[]): void { // 外側のループ:未ソート区間は [0, i] for (let i = nums.length - 1; i > 0; i--) { let flag = false; // フラグを初期化する // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // 交換する要素を記録 } } if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了 } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); console.log('バブルソート完了後 nums =', nums); const nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); console.log('バブルソート完了後 nums =', nums1); export {}; ================================================ FILE: ja/codes/typescript/chapter_sorting/bucket_sort.ts ================================================ /** * File: bucket_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* バケットソート */ function bucketSort(nums: number[]): void { // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする const k = nums.length / 2; const buckets: number[][] = []; for (let i = 0; i < k; i++) { buckets.push([]); } // 1. 配列要素を各バケットに振り分ける for (const num of nums) { // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する const i = Math.floor(num * k); // num をバケット i に追加 buckets[i].push(num); } // 2. 各バケットをソートする for (const bucket of buckets) { // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい bucket.sort((a, b) => a - b); } // 3. バケットを走査して結果を結合 let i = 0; for (const bucket of buckets) { for (const num of bucket) { nums[i++] = num; } } } /* Driver Code */ const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); console.log('バケットソート完了後 nums =', nums); export {}; ================================================ FILE: ja/codes/typescript/chapter_sorting/counting_sort.ts ================================================ /** * File: counting_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 計数ソート */ // 簡易実装のため、オブジェクトのソートには使えない function countingSortNaive(nums: number[]): void { // 1. 配列の最大要素 m を求める let m: number = Math.max(...nums); // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す const counter: number[] = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. counter を走査し、各要素を元の配列 nums に書き戻す let i = 0; for (let num = 0; num < m + 1; num++) { for (let j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* 計数ソート */ // 完全な実装で、オブジェクトをソートでき、かつ安定ソートである function countingSort(nums: number[]): void { // 1. 配列の最大要素 m を求める let m: number = Math.max(...nums); // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す const counter: number[] = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する // つまり counter[num]-1 は、num が res に最後に現れるインデックス for (let i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. nums を逆順に走査し、各要素を結果配列 res に格納する // 結果を記録するための配列 res を初期化 const n = nums.length; const res: number[] = new Array(n); for (let i = n - 1; i >= 0; i--) { const num = nums[i]; res[counter[num] - 1] = num; // num を対応するインデックスに配置 counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る } // 結果配列 res で元の配列 nums を上書きする for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Driver Code */ const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); console.log('カウントソート(オブジェクトはソート不可)完了後 nums =', nums); const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); console.log('計数ソート完了後 nums1 =', nums1); export {}; ================================================ FILE: ja/codes/typescript/chapter_sorting/heap_sort.ts ================================================ /** * File: heap_sort.ts * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* ヒープの長さは n。ノード i から下方向にヒープ化 */ function siftDown(nums: number[], n: number, i: number): void { while (true) { // ノード i, l, r のうち値が最大のノードを ma とする let l = 2 * i + 1; let r = 2 * i + 2; let ma = i; if (l < n && nums[l] > nums[ma]) { ma = l; } if (r < n && nums[r] > nums[ma]) { ma = r; } // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma === i) { break; } // 2 つのノードを交換 [nums[i], nums[ma]] = [nums[ma], nums[i]]; // ループで上から下へヒープ化 i = ma; } } /* ヒープソート */ function heapSort(nums: number[]): void { // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // ヒープから最大要素を取り出し、n-1 回繰り返す for (let i = nums.length - 1; i > 0; i--) { // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) [nums[0], nums[i]] = [nums[i], nums[0]]; // 根ノードを起点に、上から下へヒープ化 siftDown(nums, i, 0); } } /* Driver Code */ const nums: number[] = [4, 1, 3, 1, 5, 2]; heapSort(nums); console.log('ヒープソート完了後 nums =', nums); export {}; ================================================ FILE: ja/codes/typescript/chapter_sorting/insertion_sort.ts ================================================ /** * File: insertion_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 挿入ソート */ function insertionSort(nums: number[]): void { // 外側ループ:整列済み区間は [0, i-1] for (let i = 1; i < nums.length; i++) { const base = nums[i]; let j = i - 1; // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する j--; } nums[j + 1] = base; // base を正しい位置に配置する } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); console.log('挿入ソート完了後 nums =', nums); export {}; ================================================ FILE: ja/codes/typescript/chapter_sorting/merge_sort.ts ================================================ /** * File: merge_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 左部分配列と右部分配列をマージ */ function merge(nums: number[], left: number, mid: number, right: number): void { // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] // マージ結果を格納する一時配列 tmp を作成 const tmp = new Array(right - left + 1); // 左右の部分配列の開始インデックスを初期化する let i = left, j = mid + 1, k = 0; // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // 左右の部分配列の残り要素を一時配列にコピーする while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* マージソート */ function mergeSort(nums: number[], left: number, right: number): void { // 終了条件 if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了 // 分割フェーズ let mid = Math.floor(left + (right - left) / 2); // 中点を計算 mergeSort(nums, left, mid); // 左部分配列を再帰処理 mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 // マージフェーズ merge(nums, left, mid, right); } /* Driver Code */ const nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); console.log('マージソート完了後 nums =', nums); export {}; ================================================ FILE: ja/codes/typescript/chapter_sorting/quick_sort.ts ================================================ /** * File: quick_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* クイックソートクラス */ class QuickSort { /* 要素の交換 */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 番兵分割 */ partition(nums: number[], left: number, right: number): number { // nums[left] を基準値とする let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j -= 1; // 右から左へ基準値未満の最初の要素を探す } while (i < j && nums[i] <= nums[left]) { i += 1; // 左から右へ基準値より大きい最初の要素を探す } // 要素の交換 this.swap(nums, i, j); // この 2 つの要素を交換 } this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート */ quickSort(nums: number[], left: number, right: number): void { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) { return; } // 番兵分割 const pivot = this.partition(nums, left, right); // 左右の部分配列を再帰処理 this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* クイックソートクラス(中央値ピボット最適化) */ class QuickSortMedian { /* 要素の交換 */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 3つの候補要素の中央値を選ぶ */ medianThree( nums: number[], left: number, mid: number, right: number ): number { let l = nums[left], m = nums[mid], r = nums[right]; // m は l と r の間 if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // l は m と r の間 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; return right; } /* 番兵による分割処理(3 点中央値) */ partition(nums: number[], left: number, right: number): number { // 3つの候補要素の中央値を選ぶ let med = this.medianThree( nums, left, Math.floor((left + right) / 2), right ); // 中央値を配列の最左端に交換する this.swap(nums, left, med); // nums[left] を基準値とする let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // 右から左へ基準値未満の最初の要素を探す } while (i < j && nums[i] <= nums[left]) { i++; // 左から右へ基準値より大きい最初の要素を探す } this.swap(nums, i, j); // この 2 つの要素を交換 } this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート */ quickSort(nums: number[], left: number, right: number): void { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) { return; } // 番兵分割 const pivot = this.partition(nums, left, right); // 左右の部分配列を再帰処理 this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* クイックソートクラス(再帰深度最適化) */ class QuickSortTailCall { /* 要素の交換 */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 番兵分割 */ partition(nums: number[], left: number, right: number): number { // nums[left] を基準値とする let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // 右から左へ基準値未満の最初の要素を探す } while (i < j && nums[i] <= nums[left]) { i++; // 左から右へ基準値より大きい最初の要素を探す } this.swap(nums, i, j); // この 2 つの要素を交換 } this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート(再帰深度最適化) */ quickSort(nums: number[], left: number, right: number): void { // 部分配列の長さが 1 なら終了 while (left < right) { // 番兵による分割処理 let pivot = this.partition(nums, left, right); // 2 つの部分配列のうち短いほうにクイックソートを適用する if (pivot - left < right - pivot) { this.quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] } else { this.quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] } } } } /* Driver Code */ /* クイックソート */ const nums = [2, 4, 1, 0, 3, 5]; const quickSort = new QuickSort(); quickSort.quickSort(nums, 0, nums.length - 1); console.log('クイックソート完了後 nums =', nums); /* クイックソート(中央値の基準値で最適化) */ const nums1 = [2, 4, 1, 0, 3, 5]; const quickSortMedian = new QuickSortMedian(); quickSortMedian.quickSort(nums1, 0, nums1.length - 1); console.log('クイックソート(中央値ピボット最適化)完了後 nums =', nums1); /* クイックソート(再帰深度最適化) */ const nums2 = [2, 4, 1, 0, 3, 5]; const quickSortTailCall = new QuickSortTailCall(); quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); console.log('クイックソート(再帰深度最適化)完了後 nums =', nums2); export {}; ================================================ FILE: ja/codes/typescript/chapter_sorting/radix_sort.ts ================================================ /** * File: radix_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ function digit(num: number, exp: number): number { // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す return Math.floor(num / exp) % 10; } /* 計数ソート(nums の k 桁目でソート) */ function countingSortDigit(nums: number[], exp: number): void { // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 const counter = new Array(10).fill(0); const n = nums.length; // 0~9 の各数字の出現回数を集計する for (let i = 0; i < n; i++) { const d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする counter[d]++; // 数字 d の出現回数を数える } // 累積和を求め、「出現回数」を「配列インデックス」に変換する for (let i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する const res = new Array(n).fill(0); for (let i = n - 1; i >= 0; i--) { const d = digit(nums[i], exp); const j = counter[d] - 1; // d の配列内インデックス j を取得する res[j] = nums[i]; // 現在の要素をインデックス j に格納する counter[d]--; // d の個数を 1 減らす } // 結果で元の配列 nums を上書きする for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* 基数ソート */ function radixSort(nums: number[]): void { // 最大桁数の判定用に配列の最大要素を取得 let m: number = Math.max(... nums); // 下位桁から上位桁の順に走査する for (let exp = 1; exp <= m; exp *= 10) { // 配列要素の k 桁目に対して計数ソートを行う // k = 1 -> exp = 1 // k = 2 -> exp = 10 // つまり exp = 10^(k-1) countingSortDigit(nums, exp); } } /* Driver Code */ const nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radixSort(nums); console.log('基数ソート完了後 nums =', nums); export {}; ================================================ FILE: ja/codes/typescript/chapter_sorting/selection_sort.ts ================================================ /** * File: selection_sort.ts * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* 選択ソート */ function selectionSort(nums: number[]): void { let n = nums.length; // 外側ループ:未整列区間は [i, n-1] for (let i = 0; i < n - 1; i++) { // 内側のループ:未ソート区間の最小要素を見つける let k = i; for (let j = i + 1; j < n; j++) { if (nums[j] < nums[k]) { k = j; // 最小要素のインデックスを記録 } } // その最小要素を未整列区間の先頭要素と交換する [nums[i], nums[k]] = [nums[k], nums[i]]; } } /* Driver Code */ const nums: number[] = [4, 1, 3, 1, 5, 2]; selectionSort(nums); console.log('選択ソート完了後 nums =', nums); export {}; ================================================ FILE: ja/codes/typescript/chapter_stack_and_queue/array_deque.ts ================================================ /** * File: array_deque.ts * Created Time: 2023-02-28 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 循環配列ベースの両端キュー */ class ArrayDeque { private nums: number[]; // 両端キューの要素を格納する配列 private front: number; // 先頭ポインタ。先頭要素を指す private queSize: number; // 両端キューの長さ /* コンストラクタ */ constructor(capacity: number) { this.nums = new Array(capacity); this.front = 0; this.queSize = 0; } /* 両端キューの容量を取得 */ capacity(): number { return this.nums.length; } /* 両端キューの長さを取得 */ size(): number { return this.queSize; } /* 両端キューが空かどうかを判定 */ isEmpty(): boolean { return this.queSize === 0; } /* 循環配列のインデックスを計算 */ index(i: number): number { // 剰余演算により配列の先頭と末尾をつなげる // i が配列の末尾を越えたら先頭に戻る // i が配列の先頭を越えて前に出たら末尾に戻る return (i + this.capacity()) % this.capacity(); } /* キュー先頭にエンキュー */ pushFirst(num: number): void { if (this.queSize === this.capacity()) { console.log('両端キューは満杯です'); return; } // 先頭ポインタを左に 1 つ移動する // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする this.front = this.index(this.front - 1); // num をキュー先頭に追加 this.nums[this.front] = num; this.queSize++; } /* キュー末尾にエンキュー */ pushLast(num: number): void { if (this.queSize === this.capacity()) { console.log('両端キューは満杯です'); return; } // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す const rear: number = this.index(this.front + this.queSize); // num をキュー末尾に追加 this.nums[rear] = num; this.queSize++; } /* キュー先頭からデキュー */ popFirst(): number { const num: number = this.peekFirst(); // 先頭ポインタを 1 つ後ろへ進める this.front = this.index(this.front + 1); this.queSize--; return num; } /* キュー末尾からデキュー */ popLast(): number { const num: number = this.peekLast(); this.queSize--; return num; } /* キュー先頭の要素にアクセス */ peekFirst(): number { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); return this.nums[this.front]; } /* キュー末尾の要素にアクセス */ peekLast(): number { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); // 末尾要素のインデックスを計算 const last = this.index(this.front + this.queSize - 1); return this.nums[last]; } /* 出力用の配列を返す */ toArray(): number[] { // 有効長の範囲内のリスト要素のみを変換 const res: number[] = []; for (let i = 0, j = this.front; i < this.queSize; i++, j++) { res[i] = this.nums[this.index(j)]; } return res; } } /* Driver Code */ /* 両端キューを初期化 */ const capacity = 5; const deque: ArrayDeque = new ArrayDeque(capacity); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); console.log('両端キュー deque = [' + deque.toArray() + ']'); /* 要素にアクセス */ const peekFirst = deque.peekFirst(); console.log('先頭要素 peekFirst = ' + peekFirst); const peekLast = deque.peekLast(); console.log('末尾要素 peekLast = ' + peekLast); /* 要素をエンキュー */ deque.pushLast(4); console.log('要素 4 を末尾に追加後 deque = [' + deque.toArray() + ']'); deque.pushFirst(1); console.log('要素 1 を先頭に追加後 deque = [' + deque.toArray() + ']'); /* 要素をデキュー */ const popLast = deque.popLast(); console.log( '末尾から取り出した要素 = ' + popLast + '、末尾から取り出し後 deque = [' + deque.toArray() + ']' ); const popFirst = deque.popFirst(); console.log( '先頭から取り出した要素 = ' + popFirst + '、先頭から取り出し後 deque = [' + deque.toArray() + ']' ); /* 両端キューの長さを取得 */ const size = deque.size(); console.log('両端キューの長さ size = ' + size); /* 両端キューが空かどうかを判定 */ const isEmpty = deque.isEmpty(); console.log('両端キューが空かどうか = ' + isEmpty); export {}; ================================================ FILE: ja/codes/typescript/chapter_stack_and_queue/array_queue.ts ================================================ /** * File: array_queue.ts * Created Time: 2022-12-11 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* 循環配列ベースのキュー */ class ArrayQueue { private nums: number[]; // キュー要素を格納する配列 private front: number; // 先頭ポインタ。先頭要素を指す private queSize: number; // キューの長さ constructor(capacity: number) { this.nums = new Array(capacity); this.front = this.queSize = 0; } /* キューの容量を取得 */ get capacity(): number { return this.nums.length; } /* キューの長さを取得 */ get size(): number { return this.queSize; } /* キューが空かどうかを判定 */ isEmpty(): boolean { return this.queSize === 0; } /* エンキュー */ push(num: number): void { if (this.size === this.capacity) { console.log('キューは満杯です'); return; } // 末尾ポインタを計算し、末尾インデックス + 1 を指す // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする const rear = (this.front + this.queSize) % this.capacity; // num をキュー末尾に追加 this.nums[rear] = num; this.queSize++; } /* デキュー */ pop(): number { const num = this.peek(); // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す this.front = (this.front + 1) % this.capacity; this.queSize--; return num; } /* キュー先頭の要素にアクセス */ peek(): number { if (this.isEmpty()) throw new Error('キューが空です'); return this.nums[this.front]; } /* Array を返す */ toArray(): number[] { // 有効長の範囲内のリスト要素のみを変換 const arr = new Array(this.size); for (let i = 0, j = this.front; i < this.size; i++, j++) { arr[i] = this.nums[j % this.capacity]; } return arr; } } /* Driver Code */ /* キューを初期化 */ const capacity = 10; const queue = new ArrayQueue(capacity); /* 要素をエンキュー */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('キュー queue =', queue.toArray()); /* キュー先頭の要素にアクセス */ const peek = queue.peek(); console.log('先頭要素 peek = ' + peek); /* 要素をデキュー */ const pop = queue.pop(); console.log('デキューした要素 pop = ' + pop + ',デキュー後 queue =', queue.toArray()); /* キューの長さを取得 */ const size = queue.size; console.log('キューの長さ size = ' + size); /* キューが空かどうかを判定 */ const isEmpty = queue.isEmpty(); console.log('キューが空かどうか = ' + isEmpty); /* 循環配列をテストする */ for (let i = 0; i < 10; i++) { queue.push(i); queue.pop(); console.log('第 ' + i + ' 回目のエンキュー + デキュー後 queue =', queue.toArray()); } export {}; ================================================ FILE: ja/codes/typescript/chapter_stack_and_queue/array_stack.ts ================================================ /** * File: array_stack.ts * Created Time: 2022-12-08 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* 配列ベースのスタック */ class ArrayStack { private stack: number[]; constructor() { this.stack = []; } /* スタックの長さを取得 */ get size(): number { return this.stack.length; } /* スタックが空かどうかを判定 */ isEmpty(): boolean { return this.stack.length === 0; } /* プッシュ */ push(num: number): void { this.stack.push(num); } /* ポップ */ pop(): number | undefined { if (this.isEmpty()) throw new Error('スタックが空です'); return this.stack.pop(); } /* スタックトップの要素にアクセス */ top(): number | undefined { if (this.isEmpty()) throw new Error('スタックが空です'); return this.stack[this.stack.length - 1]; } /* Array を返す */ toArray() { return this.stack; } } /* Driver Code */ /* スタックを初期化 */ const stack = new ArrayStack(); /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('スタック stack = '); console.log(stack.toArray()); /* スタックトップの要素にアクセス */ const top = stack.top(); console.log('スタックトップ要素 top = ' + top); /* 要素をポップ */ const pop = stack.pop(); console.log('ポップした要素 pop = ' + pop + ',ポップ後 stack = '); console.log(stack.toArray()); /* スタックの長さを取得 */ const size = stack.size; console.log('スタックの長さ size = ' + size); /* 空かどうかを判定 */ const isEmpty = stack.isEmpty(); console.log('スタックが空かどうか = ' + isEmpty); export {}; ================================================ FILE: ja/codes/typescript/chapter_stack_and_queue/deque.ts ================================================ /** * File: deque.ts * Created Time: 2023-01-17 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Driver Code */ /* 両端キューを初期化 */ // TypeScript には組み込みの両端キューがないため、Array を両端キューとして使う const deque: number[] = []; /* 要素をエンキュー */ deque.push(2); deque.push(5); deque.push(4); // 注意: 配列であるため、unshift() メソッドの時間計算量は O(n) deque.unshift(3); deque.unshift(1); console.log('両端キュー deque = ', deque); /* 要素にアクセス */ const peekFirst: number = deque[0]; console.log('先頭要素 peekFirst = ' + peekFirst); const peekLast: number = deque[deque.length - 1]; console.log('末尾要素 peekLast = ' + peekLast); /* 要素をデキュー */ // 注意: 配列であるため、shift() メソッドの時間計算量は O(n) const popFront: number = deque.shift() as number; console.log( '先頭からデキューした要素 popFront = ' + popFront + ',先頭からデキュー後 deque = ' + deque ); const popBack: number = deque.pop() as number; console.log( '末尾からデキューした要素 popBack = ' + popBack + ',末尾からデキュー後 deque = ' + deque ); /* 両端キューの長さを取得 */ const size: number = deque.length; console.log('両端キューの長さ size = ' + size); /* 両端キューが空かどうかを判定 */ const isEmpty: boolean = size === 0; console.log('両端キューが空かどうか = ' + isEmpty); export {}; ================================================ FILE: ja/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts ================================================ /** * File: linkedlist_deque.ts * Created Time: 2023-02-04 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 双方向連結リストノード */ class ListNode { prev: ListNode; // 前駆ノードへの参照(ポインタ) next: ListNode; // 後継ノードへの参照(ポインタ) val: number; // ノード値 constructor(val: number) { this.val = val; this.next = null; this.prev = null; } } /* 双方向連結リストベースの両端キュー */ class LinkedListDeque { private front: ListNode; // 先頭ノード front private rear: ListNode; // 末尾ノード rear private queSize: number; // 両端キューの長さ constructor() { this.front = null; this.rear = null; this.queSize = 0; } /* 末尾へのエンキュー操作 */ pushLast(val: number): void { const node: ListNode = new ListNode(val); // 連結リストが空なら、front と rear の両方を node に向ける if (this.queSize === 0) { this.front = node; this.rear = node; } else { // node を連結リストの末尾に追加 this.rear.next = node; node.prev = this.rear; this.rear = node; // 末尾ノードを更新する } this.queSize++; } /* 先頭へのエンキュー操作 */ pushFirst(val: number): void { const node: ListNode = new ListNode(val); // 連結リストが空なら、front と rear の両方を node に向ける if (this.queSize === 0) { this.front = node; this.rear = node; } else { // node を連結リストの先頭に追加 this.front.prev = node; node.next = this.front; this.front = node; // 先頭ノードを更新する } this.queSize++; } /* キュー末尾からの取り出し */ popLast(): number { if (this.queSize === 0) { return null; } const value: number = this.rear.val; // 末尾ノードの値を保存する // 末尾ノードを削除 let temp: ListNode = this.rear.prev; if (temp !== null) { temp.next = null; this.rear.prev = null; } this.rear = temp; // 末尾ノードを更新する this.queSize--; return value; } /* キュー先頭からの取り出し */ popFirst(): number { if (this.queSize === 0) { return null; } const value: number = this.front.val; // 末尾ノードの値を保存する // 先頭ノードを削除 let temp: ListNode = this.front.next; if (temp !== null) { temp.prev = null; this.front.next = null; } this.front = temp; // 先頭ノードを更新する this.queSize--; return value; } /* キュー末尾の要素にアクセス */ peekLast(): number { return this.queSize === 0 ? null : this.rear.val; } /* キュー先頭の要素にアクセス */ peekFirst(): number { return this.queSize === 0 ? null : this.front.val; } /* 両端キューの長さを取得 */ size(): number { return this.queSize; } /* 両端キューが空かどうかを判定 */ isEmpty(): boolean { return this.queSize === 0; } /* 両端キューを出力する */ print(): void { const arr: number[] = []; let temp: ListNode = this.front; while (temp !== null) { arr.push(temp.val); temp = temp.next; } console.log('[' + arr.join(', ') + ']'); } } /* Driver Code */ /* 両端キューを初期化 */ const linkedListDeque: LinkedListDeque = new LinkedListDeque(); linkedListDeque.pushLast(3); linkedListDeque.pushLast(2); linkedListDeque.pushLast(5); console.log('両端キュー linkedListDeque = '); linkedListDeque.print(); /* 要素にアクセス */ const peekFirst: number = linkedListDeque.peekFirst(); console.log('先頭要素 peekFirst = ' + peekFirst); const peekLast: number = linkedListDeque.peekLast(); console.log('末尾要素 peekLast = ' + peekLast); /* 要素をエンキュー */ linkedListDeque.pushLast(4); console.log('要素 4 を末尾にエンキュー後 linkedListDeque = '); linkedListDeque.print(); linkedListDeque.pushFirst(1); console.log('要素 1 を先頭にエンキュー後 linkedListDeque = '); linkedListDeque.print(); /* 要素をデキュー */ const popLast: number = linkedListDeque.popLast(); console.log('末尾からデキューした要素 = ' + popLast + ',末尾からデキュー後 linkedListDeque = '); linkedListDeque.print(); const popFirst: number = linkedListDeque.popFirst(); console.log('先頭から取り出した要素 = ' + popFirst + ',先頭を取り出した後の linkedListDeque = '); linkedListDeque.print(); /* 両端キューの長さを取得 */ const size: number = linkedListDeque.size(); console.log('両端キューの長さ size = ' + size); /* 両端キューが空かどうかを判定 */ const isEmpty: boolean = linkedListDeque.isEmpty(); console.log('両端キューが空かどうか = ' + isEmpty); ================================================ FILE: ja/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts ================================================ /** * File: linkedlist_queue.ts * Created Time: 2022-12-19 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ import { ListNode } from '../modules/ListNode'; /* 連結リストベースのキュー */ class LinkedListQueue { private front: ListNode | null; // 先頭ノード front private rear: ListNode | null; // 末尾ノード rear private queSize: number = 0; constructor() { this.front = null; this.rear = null; } /* キューの長さを取得 */ get size(): number { return this.queSize; } /* キューが空かどうかを判定 */ isEmpty(): boolean { return this.size === 0; } /* エンキュー */ push(num: number): void { // 末尾ノードの後ろに num を追加 const node = new ListNode(num); // キューが空なら、先頭・末尾ノードをともにそのノードに設定 if (!this.front) { this.front = node; this.rear = node; // キューが空でなければ、そのノードを末尾ノードの後ろに追加 } else { this.rear!.next = node; this.rear = node; } this.queSize++; } /* デキュー */ pop(): number { const num = this.peek(); if (!this.front) throw new Error('キューが空です'); // 先頭ノードを削除 this.front = this.front.next; this.queSize--; return num; } /* キュー先頭の要素にアクセス */ peek(): number { if (this.size === 0) throw new Error('キューが空です'); return this.front!.val; } /* 連結リストを Array に変換して返す */ toArray(): number[] { let node = this.front; const res = new Array(this.size); for (let i = 0; i < res.length; i++) { res[i] = node!.val; node = node!.next; } return res; } } /* Driver Code */ /* キューを初期化 */ const queue = new LinkedListQueue(); /* 要素をエンキュー */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('キュー queue = ' + queue.toArray()); /* キュー先頭の要素にアクセス */ const peek = queue.peek(); console.log('先頭要素 peek = ' + peek); /* 要素をデキュー */ const pop = queue.pop(); console.log('デキューした要素 pop = ' + pop + ',デキュー後の queue = ' + queue.toArray()); /* キューの長さを取得 */ const size = queue.size; console.log('キューの長さ size = ' + size); /* キューが空かどうかを判定 */ const isEmpty = queue.isEmpty(); console.log('キューが空かどうか = ' + isEmpty); export {}; ================================================ FILE: ja/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts ================================================ /** * File: linkedlist_stack.ts * Created Time: 2022-12-21 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ import { ListNode } from '../modules/ListNode'; /* 連結リストベースのスタック */ class LinkedListStack { private stackPeek: ListNode | null; // 先頭ノードをスタックトップとする private stkSize: number = 0; // スタックの長さ constructor() { this.stackPeek = null; } /* スタックの長さを取得 */ get size(): number { return this.stkSize; } /* スタックが空かどうかを判定 */ isEmpty(): boolean { return this.size === 0; } /* プッシュ */ push(num: number): void { const node = new ListNode(num); node.next = this.stackPeek; this.stackPeek = node; this.stkSize++; } /* ポップ */ pop(): number { const num = this.peek(); if (!this.stackPeek) throw new Error('スタックが空です'); this.stackPeek = this.stackPeek.next; this.stkSize--; return num; } /* スタックトップの要素にアクセス */ peek(): number { if (!this.stackPeek) throw new Error('スタックが空です'); return this.stackPeek.val; } /* 連結リストを Array に変換して返す */ toArray(): number[] { let node = this.stackPeek; const res = new Array(this.size); for (let i = res.length - 1; i >= 0; i--) { res[i] = node!.val; node = node!.next; } return res; } } /* Driver Code */ /* スタックを初期化 */ const stack = new LinkedListStack(); /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('スタック stack = ' + stack.toArray()); /* スタックトップの要素にアクセス */ const peek = stack.peek(); console.log('スタックトップ要素 peek = ' + peek); /* 要素をポップ */ const pop = stack.pop(); console.log('ポップした要素 pop = ' + pop + ',ポップ後の stack = ' + stack.toArray()); /* スタックの長さを取得 */ const size = stack.size; console.log('スタックの長さ size = ' + size); /* 空かどうかを判定 */ const isEmpty = stack.isEmpty(); console.log('スタックが空かどうか = ' + isEmpty); export {}; ================================================ FILE: ja/codes/typescript/chapter_stack_and_queue/queue.ts ================================================ /** * File: queue.ts * Created Time: 2022-12-05 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* キューを初期化 */ // TypeScript には組み込みのキューがないため、Array をキューとして使う const queue: number[] = []; /* 要素をエンキュー */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('キュー queue =', queue); /* キュー先頭の要素にアクセス */ const peek = queue[0]; console.log('先頭要素 peek =', peek); /* 要素をデキュー */ // 基盤が配列であるため、shift() メソッドの時間計算量は O(n) const pop = queue.shift(); console.log('デキューした要素 pop =', pop, ',デキュー後の queue = ', queue); /* キューの長さを取得 */ const size = queue.length; console.log('キューの長さ size =', size); /* キューが空かどうかを判定 */ const isEmpty = queue.length === 0; console.log('キューが空かどうか = ', isEmpty); export {}; ================================================ FILE: ja/codes/typescript/chapter_stack_and_queue/stack.ts ================================================ /** * File: stack.ts * Created Time: 2022-12-04 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* スタックを初期化 */ // TypeScript には組み込みのスタッククラスがないため、Array をスタックとして使う const stack: number[] = []; /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('スタック stack =', stack); /* スタックトップの要素にアクセス */ const peek = stack[stack.length - 1]; console.log('スタックトップ要素 peek =', peek); /* 要素をポップ */ const pop = stack.pop(); console.log('ポップした要素 pop =', pop); console.log('ポップ後の stack =', stack); /* スタックの長さを取得 */ const size = stack.length; console.log('スタックの長さ size =', size); /* 空かどうかを判定 */ const isEmpty = stack.length === 0; console.log('スタックが空かどうか =', isEmpty); export {}; ================================================ FILE: ja/codes/typescript/chapter_tree/array_binary_tree.ts ================================================ /** * File: array_binary_tree.js * Created Time: 2023-08-09 * Author: yuan0221 (yl1452491917@gmail.com) */ import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; type Order = 'pre' | 'in' | 'post'; /* 配列表現による二分木クラス */ class ArrayBinaryTree { #tree: (number | null)[]; /* コンストラクタ */ constructor(arr: (number | null)[]) { this.#tree = arr; } /* リスト容量 */ size(): number { return this.#tree.length; } /* インデックス i のノードの値を取得 */ val(i: number): number | null { // インデックスが範囲外なら、空きを表す null を返す if (i < 0 || i >= this.size()) return null; return this.#tree[i]; } /* インデックス i のノードの左子ノードのインデックスを取得 */ left(i: number): number { return 2 * i + 1; } /* インデックス i のノードの右子ノードのインデックスを取得 */ right(i: number): number { return 2 * i + 2; } /* インデックス i のノードの親ノードのインデックスを取得 */ parent(i: number): number { return Math.floor((i - 1) / 2); // 切り捨て除算 } /* レベル順走査 */ levelOrder(): number[] { let res = []; // 配列を直接走査する for (let i = 0; i < this.size(); i++) { if (this.val(i) !== null) res.push(this.val(i)); } return res; } /* 深さ優先探索 */ #dfs(i: number, order: Order, res: (number | null)[]): void { // 空きスロットなら返す if (this.val(i) === null) return; // 先行順走査 if (order === 'pre') res.push(this.val(i)); this.#dfs(this.left(i), order, res); // 中順走査 if (order === 'in') res.push(this.val(i)); this.#dfs(this.right(i), order, res); // 後順走査 if (order === 'post') res.push(this.val(i)); } /* 先行順走査 */ preOrder(): (number | null)[] { const res = []; this.#dfs(0, 'pre', res); return res; } /* 中順走査 */ inOrder(): (number | null)[] { const res = []; this.#dfs(0, 'in', res); return res; } /* 後順走査 */ postOrder(): (number | null)[] { const res = []; this.#dfs(0, 'post', res); return res; } } /* Driver Code */ // 二分木を初期化 // ここでは、配列から直接二分木を生成する関数を利用する const arr = Array.of( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ); const root = arrToTree(arr); console.log('\n二分木を初期化する\n'); console.log('二分木の配列表現:'); console.log(arr); console.log('二分木の連結リスト表現:'); printTree(root); // 配列表現による二分木クラス const abt = new ArrayBinaryTree(arr); // ノードにアクセス const i = 1; const l = abt.left(i); const r = abt.right(i); const p = abt.parent(i); console.log('\n現在のノードのインデックスは ' + i + ' 、値は ' + abt.val(i)); console.log( 'その左子ノードのインデックスは ' + l + ' 、値は ' + (l === null ? 'null' : abt.val(l)) ); console.log( 'その右子ノードのインデックスは ' + r + ' 、値は ' + (r === null ? 'null' : abt.val(r)) ); console.log( 'その親ノードのインデックスは ' + p + ' 、値は ' + (p === null ? 'null' : abt.val(p)) ); // 木を走査 let res = abt.levelOrder(); console.log('\nレベル順走査:' + res); res = abt.preOrder(); console.log('先行順走査:' + res); res = abt.inOrder(); console.log('中間順走査:' + res); res = abt.postOrder(); console.log('後行順走査:' + res); export {}; ================================================ FILE: ja/codes/typescript/chapter_tree/avl_tree.ts ================================================ /** * File: avl_tree.ts * Created Time: 2023-02-06 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* AVL 木 */ class AVLTree { root: TreeNode; /* コンストラクタ */ constructor() { this.root = null; // 根ノード } /* ノードの高さを取得 */ height(node: TreeNode): number { // 空ノードの高さは -1、葉ノードの高さは 0 return node === null ? -1 : node.height; } /* ノードの高さを更新する */ private updateHeight(node: TreeNode): void { // ノードの高さは最も高い部分木の高さ + 1 に等しい node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; } /* 平衡係数を取得 */ balanceFactor(node: TreeNode): number { // 空ノードの平衡係数は 0 if (node === null) return 0; // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ return this.height(node.left) - this.height(node.right); } /* 右回転 */ private rightRotate(node: TreeNode): TreeNode { const child = node.left; const grandChild = child.right; // child を支点として node を右回転させる child.right = node; node.left = grandChild; // ノードの高さを更新する this.updateHeight(node); this.updateHeight(child); // 回転後の部分木の根ノードを返す return child; } /* 左回転 */ private leftRotate(node: TreeNode): TreeNode { const child = node.right; const grandChild = child.left; // child を支点として node を左回転させる child.left = node; node.right = grandChild; // ノードの高さを更新する this.updateHeight(node); this.updateHeight(child); // 回転後の部分木の根ノードを返す return child; } /* 回転操作を行い、この部分木の平衡を回復する */ private rotate(node: TreeNode): TreeNode { // ノード node の平衡係数を取得 const balanceFactor = this.balanceFactor(node); // 左に偏った木 if (balanceFactor > 1) { if (this.balanceFactor(node.left) >= 0) { // 右回転 return this.rightRotate(node); } else { // 左回転してから右回転 node.left = this.leftRotate(node.left); return this.rightRotate(node); } } // 右に偏った木 if (balanceFactor < -1) { if (this.balanceFactor(node.right) <= 0) { // 左回転 return this.leftRotate(node); } else { // 右回転してから左回転 node.right = this.rightRotate(node.right); return this.leftRotate(node); } } // 平衡木なので回転不要、そのまま返す return node; } /* ノードを挿入 */ insert(val: number): void { this.root = this.insertHelper(this.root, val); } /* ノードを再帰的に挿入する(補助メソッド) */ private insertHelper(node: TreeNode, val: number): TreeNode { if (node === null) return new TreeNode(val); /* 1. 挿入位置を探索してノードを挿入 */ if (val < node.val) { node.left = this.insertHelper(node.left, val); } else if (val > node.val) { node.right = this.insertHelper(node.right, val); } else { return node; // 重複ノードは挿入せず、そのまま返す } this.updateHeight(node); // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = this.rotate(node); // 部分木の根ノードを返す return node; } /* ノードを削除 */ remove(val: number): void { this.root = this.removeHelper(this.root, val); } /* ノードを再帰的に削除する(補助メソッド) */ private removeHelper(node: TreeNode, val: number): TreeNode { if (node === null) return null; /* 1. ノードを探索して削除 */ if (val < node.val) { node.left = this.removeHelper(node.left, val); } else if (val > node.val) { node.right = this.removeHelper(node.right, val); } else { if (node.left === null || node.right === null) { const child = node.left !== null ? node.left : node.right; // 子ノード数 = 0 の場合、node をそのまま削除して返す if (child === null) { return null; } else { // 子ノード数 = 1 の場合、node をそのまま削除する node = child; } } else { // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える let temp = node.right; while (temp.left !== null) { temp = temp.left; } node.right = this.removeHelper(node.right, temp.val); node.val = temp.val; } } this.updateHeight(node); // ノードの高さを更新する /* 2. 回転操作を行い、部分木の平衡を回復する */ node = this.rotate(node); // 部分木の根ノードを返す return node; } /* ノードを探索 */ search(val: number): TreeNode { let cur = this.root; // ループで探索し、葉ノードを越えたら抜ける while (cur !== null) { if (cur.val < val) { // 目標ノードは cur の右部分木にある cur = cur.right; } else if (cur.val > val) { // 目標ノードは cur の左部分木にある cur = cur.left; } else { // 目標ノードが見つかったらループを抜ける break; } } // 目標ノードを返す return cur; } } function testInsert(tree: AVLTree, val: number): void { tree.insert(val); console.log('\nノード ' + val + ' を挿入した後、AVL 木は'); printTree(tree.root); } function testRemove(tree: AVLTree, val: number): void { tree.remove(val); console.log('\nノード ' + val + ' を削除した後、AVL 木は'); printTree(tree.root); } /* Driver Code */ /* 空の AVL 木を初期化する */ const avlTree = new AVLTree(); /* ノードを挿入 */ // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); testInsert(avlTree, 4); testInsert(avlTree, 5); testInsert(avlTree, 8); testInsert(avlTree, 7); testInsert(avlTree, 9); testInsert(avlTree, 10); testInsert(avlTree, 6); /* 重複ノードを挿入する */ testInsert(avlTree, 7); /* ノードを削除 */ // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい testRemove(avlTree, 8); // 次数 0 のノードを削除する testRemove(avlTree, 5); // 次数 1 のノードを削除する testRemove(avlTree, 4); // 次数 2 のノードを削除する /* ノードを検索 */ const node = avlTree.search(7); console.log('\n見つかったノードオブジェクトは', node, '、ノードの値 = ' + node.val); export {}; ================================================ FILE: ja/codes/typescript/chapter_tree/binary_search_tree.ts ================================================ /** * File: binary_search_tree.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 二分探索木 */ class BinarySearchTree { private root: TreeNode | null; /* コンストラクタ */ constructor() { // 空の木を初期化する this.root = null; } /* 二分木の根ノードを取得 */ getRoot(): TreeNode | null { return this.root; } /* ノードを探索 */ search(num: number): TreeNode | null { let cur = this.root; // ループで探索し、葉ノードを越えたら抜ける while (cur !== null) { // 目標ノードは cur の右部分木にある if (cur.val < num) cur = cur.right; // 目標ノードは cur の左部分木にある else if (cur.val > num) cur = cur.left; // 目標ノードが見つかったらループを抜ける else break; } // 目標ノードを返す return cur; } /* ノードを挿入 */ insert(num: number): void { // 木が空なら、根ノードを初期化する if (this.root === null) { this.root = new TreeNode(num); return; } let cur: TreeNode | null = this.root, pre: TreeNode | null = null; // ループで探索し、葉ノードを越えたら抜ける while (cur !== null) { // 重複ノードが見つかったら、直ちに返す if (cur.val === num) return; pre = cur; // 挿入位置は cur の右部分木にある if (cur.val < num) cur = cur.right; // 挿入位置は cur の左部分木にある else cur = cur.left; } // ノードを挿入 const node = new TreeNode(num); if (pre!.val < num) pre!.right = node; else pre!.left = node; } /* ノードを削除 */ remove(num: number): void { // 木が空なら、そのまま早期リターンする if (this.root === null) return; let cur: TreeNode | null = this.root, pre: TreeNode | null = null; // ループで探索し、葉ノードを越えたら抜ける while (cur !== null) { // 削除対象のノードが見つかったら、ループを抜ける if (cur.val === num) break; pre = cur; // 削除対象ノードは cur の右部分木にある if (cur.val < num) cur = cur.right; // 削除対象ノードは cur の左部分木にある else cur = cur.left; } // 削除対象ノードがなければそのまま返す if (cur === null) return; // 子ノード数 = 0 or 1 if (cur.left === null || cur.right === null) { // 子ノード数が 0 / 1 のとき、child = null / その子ノード const child: TreeNode | null = cur.left !== null ? cur.left : cur.right; // ノード cur を削除する if (cur !== this.root) { if (pre!.left === cur) pre!.left = child; else pre!.right = child; } else { // 削除ノードが根ノードなら、根ノードを再設定 this.root = child; } } // 子ノード数 = 2 else { // 中順走査における cur の次ノードを取得 let tmp: TreeNode | null = cur.right; while (tmp!.left !== null) { tmp = tmp!.left; } // ノード tmp を再帰的に削除 this.remove(tmp!.val); // tmp で cur を上書きする cur.val = tmp!.val; } } } /* Driver Code */ /* 二分探索木を初期化 */ const bst = new BinarySearchTree(); // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (const num of nums) { bst.insert(num); } console.log('\n初期化された二分木は\n'); printTree(bst.getRoot()); /* ノードを探索 */ const node = bst.search(7); console.log( '\n見つかったノードオブジェクトは ' + node + '、ノードの値 = ' + (node ? node.val : 'null') ); /* ノードを挿入 */ bst.insert(16); console.log('\nノード 16 を挿入した後、二分木は\n'); printTree(bst.getRoot()); /* ノードを削除 */ bst.remove(1); console.log('\nノード 1 を削除した後、二分木は\n'); printTree(bst.getRoot()); bst.remove(2); console.log('\nノード 2 を削除した後、二分木は\n'); printTree(bst.getRoot()); bst.remove(4); console.log('\nノード 4 を削除した後、二分木は\n'); printTree(bst.getRoot()); export {}; ================================================ FILE: ja/codes/typescript/chapter_tree/binary_tree.ts ================================================ /** * File: binary_tree.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 二分木を初期化 */ // ノードを初期化 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // ノード間の参照(ポインタ)を構築する n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; console.log('\n二分木を初期化する\n'); printTree(n1); /* ノードの挿入と削除 */ const P = new TreeNode(0); // n1 -> n2 の間にノード P を挿入 n1.left = P; P.left = n2; console.log('\nノード P を挿入した後\n'); printTree(n1); // ノード P を削除 n1.left = n2; console.log('\nノード P を削除した後\n'); printTree(n1); export {}; ================================================ FILE: ja/codes/typescript/chapter_tree/binary_tree_bfs.ts ================================================ /** * File: binary_tree_bfs.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* レベル順走査 */ function levelOrder(root: TreeNode | null): number[] { // キューを初期化し、ルートノードを追加する const queue = [root]; // 走査順序を保存するためのリストを初期化する const list: number[] = []; while (queue.length) { let node = queue.shift() as TreeNode; // デキュー list.push(node.val); // ノードの値を保存する if (node.left) { queue.push(node.left); // 左子ノードをキューに追加 } if (node.right) { queue.push(node.right); // 右子ノードをキューに追加 } } return list; } /* Driver Code */ /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n二分木を初期化する\n'); printTree(root); /* レベル順走査 */ const list = levelOrder(root); console.log('\nレベル順走査のノード出力シーケンス = ' + list); export {}; ================================================ FILE: ja/codes/typescript/chapter_tree/binary_tree_dfs.ts ================================================ /** * File: binary_tree_dfs.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; // 走査順序を格納するリストを初期化 const list: number[] = []; /* 先行順走査 */ function preOrder(root: TreeNode | null): void { if (root === null) { return; } // 訪問順序:根ノード -> 左部分木 -> 右部分木 list.push(root.val); preOrder(root.left); preOrder(root.right); } /* 中順走査 */ function inOrder(root: TreeNode | null): void { if (root === null) { return; } // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 inOrder(root.left); list.push(root.val); inOrder(root.right); } /* 後順走査 */ function postOrder(root: TreeNode | null): void { if (root === null) { return; } // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード postOrder(root.left); postOrder(root.right); list.push(root.val); } /* Driver Code */ /* 二分木を初期化 */ // ここでは、配列から直接二分木を生成する関数を利用する const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n二分木を初期化する\n'); printTree(root); /* 先行順走査 */ list.length = 0; preOrder(root); console.log('\n前順走査のノード出力シーケンス = ' + list); /* 中順走査 */ list.length = 0; inOrder(root); console.log('\n中順走査のノード出力シーケンス = ' + list); /* 後順走査 */ list.length = 0; postOrder(root); console.log('\n後順走査のノード出力シーケンス = ' + list); export {}; ================================================ FILE: ja/codes/typescript/modules/ListNode.ts ================================================ /** * File: ListNode.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ /* 連結リストノード */ class ListNode { val: number; next: ListNode | null; constructor(val?: number, next?: ListNode | null) { this.val = val === undefined ? 0 : val; this.next = next === undefined ? null : next; } } /* 配列をデシリアライズして連結リストに変換する */ function arrToLinkedList(arr: number[]): ListNode | null { const dum: ListNode = new ListNode(0); let head = dum; for (const val of arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } export { ListNode, arrToLinkedList }; ================================================ FILE: ja/codes/typescript/modules/PrintUtil.ts ================================================ /** * File: PrintUtil.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from './ListNode'; import { TreeNode, arrToTree } from './TreeNode'; /* 連結リストを出力 */ function printLinkedList(head: ListNode | null): void { const list: string[] = []; while (head !== null) { list.push(head.val.toString()); head = head.next; } console.log(list.join(' -> ')); } class Trunk { prev: Trunk | null; str: string; constructor(prev: Trunk | null, str: string) { this.prev = prev; this.str = str; } } /** * 二分木を出力 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ function printTree(root: TreeNode | null) { printTreeHelper(root, null, false); } /* 二分木を出力 */ function printTreeHelper( root: TreeNode | null, prev: Trunk | null, isRight: boolean ) { if (root === null) { return; } let prev_str = ' '; const trunk = new Trunk(prev, prev_str); printTreeHelper(root.right, trunk, true); if (prev === null) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); console.log(' ' + root.val); if (prev) { prev.str = prev_str; } trunk.str = ' |'; printTreeHelper(root.left, trunk, false); } function showTrunks(p: Trunk | null) { if (p === null) { return; } showTrunks(p.prev); process.stdout.write(p.str); } /* ヒープを出力 */ function printHeap(arr: number[]): void { console.log('ヒープの配列表現:'); console.log(arr); console.log('ヒープの木構造表現:'); const root = arrToTree(arr); printTree(root); } export { printLinkedList, printTree, printHeap }; ================================================ FILE: ja/codes/typescript/modules/TreeNode.ts ================================================ /** * File: TreeNode.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ /* 二分木ノード */ class TreeNode { val: number; // ノード値 height: number; // ノードの高さ left: TreeNode | null; // 左の子ノードへのポインタ right: TreeNode | null; // 右の子ノードへのポインタ constructor( val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null ) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } /* 配列をデシリアライズして二分木に変換する */ function arrToTree(arr: (number | null)[], i: number = 0): TreeNode | null { if (i < 0 || i >= arr.length || arr[i] === null) { return null; } let root = new TreeNode(arr[i]); root.left = arrToTree(arr, 2 * i + 1); root.right = arrToTree(arr, 2 * i + 2); return root; } export { TreeNode, arrToTree }; ================================================ FILE: ja/codes/typescript/modules/Vertex.ts ================================================ /** * File: Vertex.ts * Created Time: 2023-02-15 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 頂点クラス */ class Vertex { val: number; constructor(val: number) { this.val = val; } /* 値リスト vals を入力し、頂点リスト vets を返す */ public static valsToVets(vals: number[]): Vertex[] { const vets: Vertex[] = []; for (let i = 0; i < vals.length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* 頂点リスト vets を入力し、値リスト vals を返す */ public static vetsToVals(vets: Vertex[]): number[] { const vals: number[] = []; for (const vet of vets) { vals.push(vet.val); } return vals; } } export { Vertex }; ================================================ FILE: ja/codes/typescript/package.json ================================================ { "private": true, "type": "module", "scripts": { "check": "tsc" }, "devDependencies": { "@types/node": "^24.9.2", "typescript": "^5.9.3" } } ================================================ FILE: ja/codes/typescript/tsconfig.json ================================================ { "compilerOptions": { "baseUrl": ".", "module": "esnext", "moduleResolution": "node", "types": ["@types/node"], "noEmit": true, "target": "esnext", }, "include": ["chapter_*/*.ts"], "exclude": ["node_modules"] } ================================================ FILE: ja/codes/zig/.gitignore ================================================ zig-out zig-cache .zig-cache !/.vscode/ ================================================ FILE: ja/codes/zig/build.zig ================================================ // File: build.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) //! Zig Version: 0.14.1 //! Build Command: zig build //! Run Command: zig build run | zig build run_* //! Test Command: zig build test | zig build test -Dtest-filter=* const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const chapters = [_][]const u8{ "chapter_computational_complexity", "chapter_array_and_linkedlist", "chapter_stack_and_queue", "chapter_hashing", "chapter_tree", "chapter_heap", "chapter_searching", "chapter_sorting", "chapter_dynamic_programming", }; const test_step = b.step("test", "Run unit tests"); const test_filters = b.option([]const []const u8, "test-filter", "Skip tests that do not match any filter") orelse &[0][]const u8{}; buildChapterExeModules(b, target, optimize, &chapters, test_step, test_filters); buildMainExeModule(b, target, optimize); } fn buildChapterExeModules( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, chapter_dirs: []const []const u8, test_step: *std.Build.Step, test_filters: []const []const u8, ) void { for (chapter_dirs) |chapter_dir_name| { const chapter_dir_path = std.fs.path.join(b.allocator, &[_][]const u8{chapter_dir_name}) catch continue; var chapter_dir = std.fs.cwd().openDir(chapter_dir_path, .{ .iterate = true }) catch continue; defer chapter_dir.close(); var it = chapter_dir.iterate(); while (it.next() catch continue) |chapter_dir_entry| { if (chapter_dir_entry.kind != .file or !std.mem.endsWith(u8, chapter_dir_entry.name, ".zig")) continue; const exe_mod = buildExeModuleFromChapterDirEntry(b, target, optimize, chapter_dir_name, chapter_dir_entry) catch continue; addTestStepToExeModule(b, test_step, exe_mod, test_filters); } } } fn buildExeModuleFromChapterDirEntry( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, chapter_dir_name: []const u8, chapter_dir_entry: std.fs.Dir.Entry, ) !*std.Build.Module { const zig_file_path = try std.fs.path.join(b.allocator, &[_][]const u8{ chapter_dir_name, chapter_dir_entry.name }); const zig_file_name = chapter_dir_entry.name[0 .. chapter_dir_entry.name.len - 4]; // abstract zig file name from xxx.zig // ここでは一時的に配列と連結リストの章のみを追加し、後続の修正が終わったらすべて有効にする const new_algo_names = [_][]const u8{ "array", "linked_list", "list", "my_list", "iteration", "recursion", "space_complexity", "time_complexity", "worst_best_time_complexity", }; var can_run = false; for (new_algo_names) |name| { if (std.mem.eql(u8, zig_file_name, name)) { can_run = true; } } if (!can_run) { return error.CanNotRunUseOldZigCodes; } // std.debug.print("now run zig file name = {s}\n", .{zig_file_name}); const exe_mod = b.createModule(.{ .root_source_file = b.path(zig_file_path), .target = target, .optimize = optimize, }); const exe = b.addExecutable(.{ .name = zig_file_name, .root_module = exe_mod, }); const utils_mod = createUtilsModule(b, target, optimize); exe_mod.addImport("utils", utils_mod); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const step_name = try std.fmt.allocPrint(b.allocator, "run_{s}", .{zig_file_name}); const step_desc = try std.fmt.allocPrint(b.allocator, "Run {s}/{s}.zig", .{ chapter_dir_name, zig_file_name }); const run_step = b.step(step_name, step_desc); run_step.dependOn(&run_cmd.step); return exe_mod; } fn buildMainExeModule( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, ) void { const exe_mod = b.createModule(.{ .root_source_file = b.path("main.zig"), .target = target, .optimize = optimize, }); const utils_mod = createUtilsModule(b, target, optimize); exe_mod.addImport("utils", utils_mod); const exe = b.addExecutable(.{ .name = "main", .root_module = exe_mod, }); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run all hello algo zig"); run_step.dependOn(&run_cmd.step); } fn createUtilsModule(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Module { const utils_mod = b.createModule(.{ .root_source_file = b.path("utils/utils.zig"), .target = target, .optimize = optimize, }); return utils_mod; } fn addTestStepToExeModule(b: *std.Build, test_step: *std.Build.Step, exe_mod: *std.Build.Module, test_filters: []const []const u8) void { const exe_unit_tests = b.addTest(.{ .root_module = exe_mod, .filters = test_filters, }); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); test_step.dependOn(&run_exe_unit_tests.step); } ================================================ FILE: ja/codes/zig/chapter_array_and_linkedlist/array.zig ================================================ // File: array.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // 要素へランダムアクセス pub fn randomAccess(nums: []const i32) i32 { // 区間 [0, nums.len) からランダムに整数を 1 つ選ぶ const random_index = std.crypto.random.intRangeLessThan(usize, 0, nums.len); // ランダムな要素を取得して返す const randomNum = nums[random_index]; return randomNum; } // 配列長を拡張する pub fn extend(allocator: std.mem.Allocator, nums: []const i32, enlarge: usize) ![]i32 { // 拡張後の長さを持つ配列を初期化する const res = try allocator.alloc(i32, nums.len + enlarge); @memset(res, 0); // 元の配列の全要素を新しい配列にコピー std.mem.copyForwards(i32, res, nums); // 拡張後の新しい配列を返す return res; } // 配列の index 番目に要素 num を挿入 pub fn insert(nums: []i32, num: i32, index: usize) void { // インデックス index 以降の全要素を 1 つ後ろへ移動する var i = nums.len - 1; while (i > index) : (i -= 1) { nums[i] = nums[i - 1]; } // index の要素に num を代入する nums[index] = num; } // index の要素を削除する pub fn remove(nums: []i32, index: usize) void { // インデックス index より後ろの全要素を 1 つ前へ移動する var i = index; while (i < nums.len - 1) : (i += 1) { nums[i] = nums[i + 1]; } } // 配列を走査 pub fn traverse(nums: []const i32) void { var count: i32 = 0; // インデックスで配列を走査 var i: usize = 0; while (i < nums.len) : (i += 1) { count += nums[i]; } // 配列要素を直接走査 count = 0; for (nums) |num| { count += num; } // データのインデックスと要素を同時に走査する for (nums, 0..) |num, index| { count += nums[index]; count += num; } } // 配列内で指定要素を探す pub fn find(nums: []i32, target: i32) i32 { for (nums, 0..) |num, i| { if (num == target) return @intCast(i); } return -1; } // Driver Code pub fn run() !void { // 配列を初期化 const arr = [_]i32{0} ** 5; std.debug.print("配列 arr = {}\n", .{utils.fmt.slice(&arr)}); // 配列スライス var array = [_]i32{ 1, 3, 2, 5, 4 }; var known_at_runtime_zero: usize = 0; _ = &known_at_runtime_zero; var nums = array[known_at_runtime_zero..array.len]; // 実行時変数 known_at_runtime_zero を用いてポインタをスライスに変換する std.debug.print("配列 nums = {}\n", .{utils.fmt.slice(nums)}); // ランダムアクセス const randomNum = randomAccess(nums); std.debug.print("nums からランダムな要素 {} を取得\n", .{randomNum}); // メモリアロケータを初期化する var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); // 長さを拡張 nums = try extend(allocator, nums, 3); std.debug.print("配列の長さを 8 に拡張し、nums = {} を得る\n", .{utils.fmt.slice(nums)}); // 要素を挿入する insert(nums, 6, 3); std.debug.print("インデックス 3 に数値 6 を挿入し、nums = {} を得る\n", .{utils.fmt.slice(nums)}); // 要素を削除 remove(nums, 2); std.debug.print("インデックス 2 の要素を削除し、nums = {} を得る\n", .{utils.fmt.slice(nums)}); // 配列を走査 traverse(nums); // 要素を探索する const index = find(nums, 3); std.debug.print("nums で要素 3 を検索し、インデックス = {} を得る\n", .{index}); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "array" { try run(); } ================================================ FILE: ja/codes/zig/chapter_array_and_linkedlist/linked_list.zig ================================================ // File: linked_list.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); const ListNode = utils.ListNode; // 連結リストでノード n0 の後ろにノード P を挿入する pub fn insert(comptime T: type, n0: *ListNode(T), P: *ListNode(T)) void { const n1 = n0.next; P.next = n1; n0.next = P; } // 連結リストでノード n0 の直後のノードを削除する pub fn remove(comptime T: type, n0: *ListNode(T)) void { // n0 -> P -> n1 => n0 -> n1 const P = n0.next; const n1 = P.?.next; n0.next = n1; } // 連結リスト内で index 番目のノードにアクセス pub fn access(comptime T: type, node: *ListNode(T), index: i32) ?*ListNode(T) { var head: ?*ListNode(T) = node; var i: i32 = 0; while (i < index) : (i += 1) { if (head) |cur| { head = cur.next; } else { return null; } } return head; } // 連結リストで値が target の最初のノードを探す pub fn find(comptime T: type, node: *ListNode(T), target: T) i32 { var head: ?*ListNode(T) = node; var index: i32 = 0; while (head) |cur| { if (cur.val == target) return index; head = cur.next; index += 1; } return -1; } // Driver Code pub fn run() void { // 各ノードを初期化 var n0 = ListNode(i32){ .val = 1 }; var n1 = ListNode(i32){ .val = 3 }; var n2 = ListNode(i32){ .val = 2 }; var n3 = ListNode(i32){ .val = 5 }; var n4 = ListNode(i32){ .val = 4 }; // ノード間の参照を構築する n0.next = &n1; n1.next = &n2; n2.next = &n3; n3.next = &n4; std.debug.print( "初期化後の連結リストは {}\n", .{utils.fmt.linkedList(i32, &n0)}, ); // ノードを挿入 var tmp = ListNode(i32){ .val = 0 }; insert(i32, &n0, &tmp); std.debug.print( "ノード挿入後の連結リストは {}\n", .{utils.fmt.linkedList(i32, &n0)}, ); // ノードを削除 remove(i32, &n0); std.debug.print( "ノード削除後の連結リストは{}\n", .{utils.fmt.linkedList(i32, &n0)}, ); // ノードにアクセス const node = access(i32, &n0, 3); std.debug.print( "連結リストのインデックス 3 にあるノードの値 = {}\n", .{node.?.val}, ); // ノードを探索 const index = find(i32, &n0, 2); std.debug.print( "連結リスト内の値 2 のノードのインデックス = {}\n", .{index}, ); std.debug.print("\n", .{}); } pub fn main() void { run(); } test "linked_list" { run(); } ================================================ FILE: ja/codes/zig/chapter_array_and_linkedlist/list.zig ================================================ // File: list.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // Driver Code pub fn run() !void { // リストを初期化 var nums = std.ArrayList(i32).init(std.heap.page_allocator); defer nums.deinit(); // メモリの遅延解放 try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); std.debug.print("リスト nums = {}\n", .{utils.fmt.slice(nums.items)}); // 要素にアクセス const num = nums.items[1]; std.debug.print("インデックス 1 の要素にアクセスすると、num = {}\n", .{num}); // 要素を更新 nums.items[1] = 0; std.debug.print("インデックス 1 の要素を 0 に更新すると、nums = {}\n", .{utils.fmt.slice(nums.items)}); // リストを空にする nums.clearRetainingCapacity(); std.debug.print("リストを空にした後 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 末尾に要素を追加 try nums.append(1); try nums.append(3); try nums.append(2); try nums.append(5); try nums.append(4); std.debug.print("要素を追加すると nums = {}\n", .{utils.fmt.slice(nums.items)}); // 中間に要素を挿入 try nums.insert(3, 6); std.debug.print("インデックス 3 に数値 6 を挿入すると、nums = {}\n", .{utils.fmt.slice(nums.items)}); // 要素を削除 _ = nums.orderedRemove(3); std.debug.print("インデックス 3 の要素を削除すると、nums = {}\n", .{utils.fmt.slice(nums.items)}); // インデックスでリストを走査 var count: i32 = 0; var i: usize = 0; while (i < nums.items.len) : (i += 1) { count += nums.items[i]; } // リスト要素を直接走査 count = 0; for (nums.items) |x| { count += x; } // 2 つのリストを連結する var nums1 = std.ArrayList(i32).init(std.heap.page_allocator); defer nums1.deinit(); try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); try nums.insertSlice(nums.items.len, nums1.items); std.debug.print("リスト nums1 を nums の後ろに連結すると、nums = {}\n", .{utils.fmt.slice(nums.items)}); // リストをソート std.mem.sort(i32, nums.items, {}, comptime std.sort.asc(i32)); std.debug.print("リストをソートすると nums = {}\n", .{utils.fmt.slice(nums.items)}); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "list" { try run(); } ================================================ FILE: ja/codes/zig/chapter_array_and_linkedlist/my_list.zig ================================================ // File: my_list.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // リストクラス const MyList = struct { const Self = @This(); items: []i32, // 配列(リスト要素を格納) capacity: usize, // リスト容量 allocator: std.mem.Allocator, // メモリアロケータ extend_ratio: usize = 2, // リスト拡張時の増加倍率 // コンストラクタ(メモリを確保してリストを初期化) pub fn init(allocator: std.mem.Allocator) Self { return Self{ .items = &[_]i32{}, .capacity = 0, .allocator = allocator, }; } // デストラクタ(メモリを解放する) pub fn deinit(self: Self) void { self.allocator.free(self.allocatedSlice()); } // 末尾に要素を追加 pub fn add(self: *Self, item: i32) !void { // 要素数が容量を超えると、拡張機構が発動する const newlen = self.items.len + 1; try self.ensureTotalCapacity(newlen); // 要素を更新 self.items.len += 1; const new_item_ptr = &self.items[self.items.len - 1]; new_item_ptr.* = item; } // リストの長さを取得(現在の要素数) pub fn getSize(self: *Self) usize { return self.items.len; } // リスト容量を取得する pub fn getCapacity(self: *Self) usize { return self.capacity; } // 要素にアクセス pub fn get(self: *Self, index: usize) i32 { // インデックスが範囲外なら例外を送出する。以下同様 if (index < 0 or index >= self.items.len) { @panic("インデックスが範囲外です"); } return self.items[index]; } // 要素を更新 pub fn set(self: *Self, index: usize, num: i32) void { // インデックスが範囲外なら例外を送出する。以下同様 if (index < 0 or index >= self.items.len) { @panic("インデックスが範囲外です"); } self.items[index] = num; } // 中間に要素を挿入 pub fn insert(self: *Self, index: usize, item: i32) !void { if (index < 0 or index >= self.items.len) { @panic("インデックスが範囲外です"); } // 要素数が容量を超えると、拡張機構が発動する const newlen = self.items.len + 1; try self.ensureTotalCapacity(newlen); // index 以降の要素をすべて 1 つ後ろへずらす self.items.len += 1; var i = self.items.len - 1; while (i >= index) : (i -= 1) { self.items[i] = self.items[i - 1]; } self.items[index] = item; } // 要素を削除 pub fn remove(self: *Self, index: usize) i32 { if (index < 0 or index >= self.getSize()) { @panic("インデックスが範囲外です"); } // インデックス index より後の要素をすべて 1 つ前に移動する const item = self.items[index]; var i = index; while (i < self.items.len - 1) : (i += 1) { self.items[i] = self.items[i + 1]; } self.items.len -= 1; // 削除された要素を返す return item; } // リストを配列に変換する pub fn toArraySlice(self: *Self) ![]i32 { return self.toOwnedSlice(false); } // 新しいスライスを返し、リストコンテナをリセットまたはクリアするかどうかを設定する pub fn toOwnedSlice(self: *Self, clear: bool) ![]i32 { const allocator = self.allocator; const old_memory = self.allocatedSlice(); if (allocator.remap(old_memory, self.items.len)) |new_items| { if (clear) { self.* = init(allocator); } return new_items; } const new_memory = try allocator.alloc(i32, self.items.len); @memcpy(new_memory, self.items); if (clear) { self.clearAndFree(); } return new_memory; } // リストの拡張 fn ensureTotalCapacity(self: *Self, new_capacity: usize) !void { if (self.capacity >= new_capacity) return; const capcacity = if (self.capacity == 0) 10 else self.capacity; const better_capacity = capcacity * self.extend_ratio; const old_memory = self.allocatedSlice(); if (self.allocator.remap(old_memory, better_capacity)) |new_memory| { self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } else { const new_memory = try self.allocator.alloc(i32, better_capacity); @memcpy(new_memory[0..self.items.len], self.items); self.allocator.free(old_memory); self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } } fn clearAndFree(self: *Self, allocator: std.mem.Allocator) void { allocator.free(self.allocatedSlice()); self.items.len = 0; self.capacity = 0; } fn allocatedSlice(self: Self) []i32 { return self.items.ptr[0..self.capacity]; } }; // Driver Code pub fn run() !void { var gpa = std.heap.DebugAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // リストを初期化 var nums = MyList.init(allocator); // メモリの遅延解放 defer nums.deinit(); // 末尾に要素を追加 try nums.add(1); try nums.add(3); try nums.add(2); try nums.add(5); try nums.add(4); std.debug.print("リスト nums = {} ,容量 = {} ,長さ = {}\n", .{ utils.fmt.slice(nums.items), nums.getCapacity(), nums.getSize(), }); // 中間に要素を挿入 try nums.insert(3, 6); std.debug.print( "インデックス 3 に数値 6 を挿入すると、nums = {}\n", .{utils.fmt.slice(nums.items)}, ); // 要素を削除 _ = nums.remove(3); std.debug.print( "インデックス 3 の要素を削除すると、nums = {}\n", .{utils.fmt.slice(nums.items)}, ); // 要素にアクセス const num = nums.get(1); std.debug.print("インデックス 1 の要素にアクセスすると、num = {}\n", .{num}); // 要素を更新 nums.set(1, 0); std.debug.print( "インデックス 1 の要素を 0 に更新すると、nums = {}\n", .{utils.fmt.slice(nums.items)}, ); // 拡張機構をテストする var i: i32 = 0; while (i < 10) : (i += 1) { // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する try nums.add(i); } std.debug.print( "拡張後のリスト nums = {} ,容量 = {} ,長さ = {}\n", .{ utils.fmt.slice(nums.items), nums.getCapacity(), nums.getSize(), }, ); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "my_list" { try run(); } ================================================ FILE: ja/codes/zig/chapter_computational_complexity/iteration.zig ================================================ // File: iteration.zig // Created Time: 2023-09-27 // Author: QiLOL (pikaqqpika@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const Allocator = std.mem.Allocator; // for ループ fn forLoop(n: usize) i32 { var res: i32 = 0; // 1, 2, ..., n-1, n を順に加算する for (1..n + 1) |i| { res += @intCast(i); } return res; } // while ループ fn whileLoop(n: i32) i32 { var res: i32 = 0; var i: i32 = 1; // 条件変数を初期化する // 1, 2, ..., n-1, n を順に加算する while (i <= n) : (i += 1) { res += @intCast(i); } return res; } // while ループ(2回更新) fn whileLoopII(n: i32) i32 { var res: i32 = 0; var i: i32 = 1; // 条件変数を初期化する // 1, 4, 10, ... を順に加算する while (i <= n) : ({ // 条件変数を更新する i += 1; i *= 2; }) { res += @intCast(i); } return res; } // 二重 for ループ fn nestedForLoop(allocator: Allocator, n: usize) ![]const u8 { var res = std.ArrayList(u8).init(allocator); defer res.deinit(); var buffer: [20]u8 = undefined; // i = 1, 2, ..., n-1, n とループする for (1..n + 1) |i| { // j = 1, 2, ..., n-1, n とループする for (1..n + 1) |j| { const str = try std.fmt.bufPrint(&buffer, "({d}, {d}), ", .{ i, j }); try res.appendSlice(str); } } return res.toOwnedSlice(); } // Driver Code pub fn run() !void { var gpa = std.heap.DebugAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const n: i32 = 5; var res: i32 = 0; res = forLoop(n); std.debug.print("for ループの合計結果 res = {}\n", .{res}); res = whileLoop(n); std.debug.print("while ループの合計結果 res = {}\n", .{res}); res = whileLoopII(n); std.debug.print("while ループ(2 回更新)の合計結果 res = {}\n", .{res}); const resStr = try nestedForLoop(allocator, n); std.debug.print("二重 for ループの走査結果 {s}\n", .{resStr}); allocator.free(resStr); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "interation" { try run(); } ================================================ FILE: ja/codes/zig/chapter_computational_complexity/recursion.zig ================================================ // File: recursion.zig // Created Time: 2023-09-27 // Author: QiLOL (pikaqqpika@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // 再帰関数 fn recur(n: i32) i32 { // 終了条件 if (n == 1) { return 1; } // 再帰:再帰呼び出し const res = recur(n - 1); // 帰りがけ:結果を返す return n + res; } // 反復で再帰を模擬する fn forLoopRecur(comptime n: i32) i32 { // 明示的なスタックを使ってシステムコールスタックを模擬する var stack: [n]i32 = undefined; var res: i32 = 0; // 再帰:再帰呼び出し var i: usize = n; while (i > 0) { stack[i - 1] = @intCast(i); i -= 1; } // 帰りがけ:結果を返す var index: usize = n; while (index > 0) { index -= 1; res += stack[index]; } // res = 1+2+3+...+n return res; } // 末尾再帰関数 fn tailRecur(n: i32, res: i32) i32 { // 終了条件 if (n == 0) { return res; } // 末尾再帰呼び出し return tailRecur(n - 1, res + n); } // フィボナッチ数列 fn fib(n: i32) i32 { // 終了条件 f(1) = 0, f(2) = 1 if (n == 1 or n == 2) { return n - 1; } // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す const res: i32 = fib(n - 1) + fib(n - 2); // 結果 f(n) を返す return res; } // Driver Code pub fn run() void { const n: i32 = 5; var res: i32 = 0; res = recur(n); std.debug.print("再帰関数の合計結果 res = {}\n", .{recur(n)}); res = forLoopRecur(n); std.debug.print("反復で再帰をシミュレートした合計結果 res = {}\n", .{forLoopRecur(n)}); res = tailRecur(n, 0); std.debug.print("末尾再帰関数の合計結果 res = {}\n", .{tailRecur(n, 0)}); res = fib(n); std.debug.print("フィボナッチ数列の第 {} 項は {}\n", .{ n, fib(n) }); std.debug.print("\n", .{}); } pub fn main() void { run(); } test "recursion" { run(); } ================================================ FILE: ja/codes/zig/chapter_computational_complexity/space_complexity.zig ================================================ // File: space_complexity.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); const ListNode = utils.ListNode; const TreeNode = utils.TreeNode; // 関数 fn function() i32 { // 何らかの処理を行う return 0; } // 定数階 fn constant(n: i32) void { // 定数、変数、オブジェクトは O(1) の空間を占める const a: i32 = 0; const b: i32 = 0; const nums = [_]i32{0} ** 10000; const node = ListNode(i32){ .val = 0 }; var i: i32 = 0; // ループ内の変数は O(1) の空間を占める while (i < n) : (i += 1) { const c: i32 = 0; _ = c; } // ループ内の関数は O(1) の空間を占める i = 0; while (i < n) : (i += 1) { _ = function(); } _ = a; _ = b; _ = nums; _ = node; } // 線形階 fn linear(comptime n: i32) !void { // 長さ n の配列は O(n) の空間を使用 const nums = [_]i32{0} ** n; // 長さ n のリストは O(n) の空間を使用 var nodes = std.ArrayList(i32).init(std.heap.page_allocator); defer nodes.deinit(); var i: i32 = 0; while (i < n) : (i += 1) { try nodes.append(i); } // 長さ n のハッシュテーブルは O(n) の空間を使用 var map = std.AutoArrayHashMap(i32, []const u8).init(std.heap.page_allocator); defer map.deinit(); var j: i32 = 0; while (j < n) : (j += 1) { const string = try std.fmt.allocPrint(std.heap.page_allocator, "{d}", .{j}); defer std.heap.page_allocator.free(string); try map.put(i, string); } _ = nums; } // 線形時間(再帰実装) fn linearRecur(comptime n: i32) void { std.debug.print("再帰 n = {}\n", .{n}); if (n == 1) return; linearRecur(n - 1); } // 二乗階 fn quadratic(n: i32) !void { // 二次元リストは O(n^2) の空間を使用 var nodes = std.ArrayList(std.ArrayList(i32)).init(std.heap.page_allocator); defer nodes.deinit(); var i: i32 = 0; while (i < n) : (i += 1) { var tmp = std.ArrayList(i32).init(std.heap.page_allocator); defer tmp.deinit(); var j: i32 = 0; while (j < n) : (j += 1) { try tmp.append(0); } try nodes.append(tmp); } } // 二次時間(再帰実装) fn quadraticRecur(comptime n: i32) i32 { if (n <= 0) return 0; const nums = [_]i32{0} ** n; std.debug.print("再帰 n = {} における nums の長さ = {}\n", .{ n, nums.len }); return quadraticRecur(n - 1); } // 指数時間(完全二分木の構築) fn buildTree(allocator: std.mem.Allocator, n: i32) !?*TreeNode(i32) { if (n == 0) return null; const root = try allocator.create(TreeNode(i32)); root.init(0); root.left = try buildTree(allocator, n - 1); root.right = try buildTree(allocator, n - 1); return root; } // 木のメモリを解放する fn freeTree(allocator: std.mem.Allocator, root: ?*const TreeNode(i32)) void { if (root == null) return; freeTree(allocator, root.?.left); freeTree(allocator, root.?.right); allocator.destroy(root.?); } // Driver Code pub fn run() !void { var gpa = std.heap.DebugAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const n: i32 = 5; // 定数階 constant(n); // 線形階 try linear(n); linearRecur(n); // 二乗階 try quadratic(n); _ = quadraticRecur(n); // 指数オーダー const root = try buildTree(allocator, n); defer freeTree(allocator, root); std.debug.print("{}\n", .{utils.fmt.tree(i32, root)}); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "space_complexity" { try run(); } ================================================ FILE: ja/codes/zig/chapter_computational_complexity/time_complexity.zig ================================================ // File: time_complexity.zig // Created Time: 2022-12-28 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // 定数階 fn constant(n: i32) i32 { _ = n; var count: i32 = 0; const size: i32 = 100_000; var i: i32 = 0; while (i < size) : (i += 1) { count += 1; } return count; } // 線形階 fn linear(n: i32) i32 { var count: i32 = 0; var i: i32 = 0; while (i < n) : (i += 1) { count += 1; } return count; } // 線形時間(配列を走査) fn arrayTraversal(nums: []i32) i32 { var count: i32 = 0; // ループ回数は配列長に比例する for (nums) |_| { count += 1; } return count; } // 二乗階 fn quadratic(n: i32) i32 { var count: i32 = 0; var i: i32 = 0; // ループ回数はデータサイズ n の二乗に比例する while (i < n) : (i += 1) { var j: i32 = 0; while (j < n) : (j += 1) { count += 1; } } return count; } // 二次時間(バブルソート) fn bubbleSort(nums: []i32) i32 { var count: i32 = 0; // カウンタ // 外側のループ:未ソート区間は [0, i] var i: i32 = @as(i32, @intCast(nums.len)) - 1; while (i > 0) : (i -= 1) { var j: usize = 0; // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 const tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 要素交換には 3 回の単位操作が含まれる } } } return count; } // 指数時間(ループ実装) fn exponential(n: i32) i32 { var count: i32 = 0; var bas: i32 = 1; var i: i32 = 0; // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する while (i < n) : (i += 1) { var j: i32 = 0; while (j < bas) : (j += 1) { count += 1; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } // 指数時間(再帰実装) fn expRecur(n: i32) i32 { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } // 対数時間(ループ実装) fn logarithmic(n: i32) i32 { var count: i32 = 0; var n_var: i32 = n; while (n_var > 1) : (n_var = @divTrunc(n_var, 2)) { count += 1; } return count; } // 対数時間(再帰実装) fn logRecur(n: i32) i32 { if (n <= 1) return 0; return logRecur(@divTrunc(n, 2)) + 1; } // 線形対数時間 fn linearLogRecur(n: i32) i32 { if (n <= 1) return 1; var count: i32 = linearLogRecur(@divTrunc(n, 2)) + linearLogRecur(@divTrunc(n, 2)); var i: i32 = 0; while (i < n) : (i += 1) { count += 1; } return count; } // 階乗時間(再帰実装) fn factorialRecur(n: i32) i32 { if (n == 0) return 1; var count: i32 = 0; var i: i32 = 0; // 1個から n 個に分裂 while (i < n) : (i += 1) { count += factorialRecur(n - 1); } return count; } // Driver Code pub fn run() void { // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる const n: i32 = 8; std.debug.print("入力データサイズ n = {}\n", .{n}); var count = constant(n); std.debug.print("定数オーダーの操作数 = {}\n", .{count}); count = linear(n); std.debug.print("線形オーダーの操作数 = {}\n", .{count}); var nums = [_]i32{0} ** n; count = arrayTraversal(&nums); std.debug.print("線形オーダー(配列走査)の操作数 = {}\n", .{count}); count = quadratic(n); std.debug.print("二乗オーダーの操作数 = {}\n", .{count}); for (&nums, 0..) |*num, i| { num.* = n - @as(i32, @intCast(i)); // [n,n-1,...,2,1] } count = bubbleSort(&nums); std.debug.print("二乗オーダー(バブルソート)の操作数 = {}\n", .{count}); count = exponential(n); std.debug.print("指数オーダー(ループ実装)の操作数 = {}\n", .{count}); count = expRecur(n); std.debug.print("指数オーダー(再帰実装)の操作数 = {}\n", .{count}); count = logarithmic(n); std.debug.print("対数オーダー(ループ実装)の操作数 = {}\n", .{count}); count = logRecur(n); std.debug.print("対数オーダー(再帰実装)の操作数 = {}\n", .{count}); count = linearLogRecur(n); std.debug.print("線形対数オーダー(再帰実装)の操作数 = {}\n", .{count}); count = factorialRecur(n); std.debug.print("階乗オーダー(再帰実装)の操作数 = {}\n", .{count}); std.debug.print("\n", .{}); } pub fn main() !void { run(); } test "time_complexity" { run(); } ================================================ FILE: ja/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig ================================================ // File: worst_best_time_complexity.zig // Created Time: 2022-12-28 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 pub fn randomNumbers(comptime n: usize) [n]i32 { var nums: [n]i32 = undefined; // 配列 nums = { 1, 2, 3, ..., n } を生成 for (&nums, 0..) |*num, i| { num.* = @as(i32, @intCast(i)) + 1; } // 配列要素をランダムにシャッフル const rand = std.crypto.random; rand.shuffle(i32, &nums); return nums; } // 配列 nums 内で数値 1 のインデックスを探す pub fn findOne(nums: []i32) i32 { for (nums, 0..) |num, i| { // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる if (num == 1) return @intCast(i); } return -1; } // Driver Code pub fn run() void { var i: i32 = 0; while (i < 10) : (i += 1) { const n: usize = 100; var nums = randomNumbers(n); const index = findOne(&nums); std.debug.print("配列 [ 1, 2, ..., n ] をシャッフルした後 = ", .{}); std.debug.print("{}\n", .{utils.fmt.slice(nums)}); std.debug.print("数字 1 のインデックスは {}\n", .{index}); } std.debug.print("\n", .{}); } pub fn main() !void { run(); } test "worst_best_time_complexity" { run(); } ================================================ FILE: ja/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig ================================================ // File: climbing_stairs_backtrack.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // バックトラッキング fn backtrack(choices: []i32, state: i32, n: i32, res: std.ArrayList(i32)) void { // 第 n 段に到達したら、方法数を 1 増やす if (state == n) { res.items[0] = res.items[0] + 1; } // すべての選択肢を走査 for (choices) |choice| { // 枝刈り: 第 n 段を超えないようにする if (state + choice > n) { continue; } // 試行: 選択を行い、状態を更新 backtrack(choices, state + choice, n, res); // バックトラック } } // 階段登り:バックトラッキング fn climbingStairsBacktrack(n: usize) !i32 { var choices = [_]i32{ 1, 2 }; // 1 段または 2 段上ることを選べる var state: i32 = 0; // 第 0 段から上り始める var res = std.ArrayList(i32).init(std.heap.page_allocator); defer res.deinit(); try res.append(0); // res[0] を使って方法数を記録する backtrack(&choices, state, @intCast(n), res); return res.items[0]; } // Driver Code pub fn main() !void { var n: usize = 9; var res = try climbingStairsBacktrack(n); std.debug.print("{} 段の階段を上る方法は全部で {} 通り\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig ================================================ // File: climbing_stairs_constraint_dp.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 制約付き階段登り:動的計画法 fn climbingStairsConstraintDP(comptime n: usize) i32 { if (n == 1 or n == 2) { return 1; } // 部分問題の解を保存するために dp テーブルを初期化 var dp = [_][3]i32{ [_]i32{ -1, -1, -1 } } ** (n + 1); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (3..n + 1) |i| { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsConstraintDP(n); std.debug.print("{} 段の階段を上る方法は全部で {} 通り\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig ================================================ // File: climbing_stairs_dfs.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 検索 fn dfs(i: usize) i32 { // dp[1] と dp[2] は既知なので返す if (i == 1 or i == 2) { return @intCast(i); } // dp[i] = dp[i-1] + dp[i-2] var count = dfs(i - 1) + dfs(i - 2); return count; } // 階段登り:探索 fn climbingStairsDFS(comptime n: usize) i32 { return dfs(n); } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsDFS(n); std.debug.print("{} 段の階段を上る方法は全部で {} 通り\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig ================================================ // File: climbing_stairs_dfs_mem.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // メモ化探索 fn dfs(i: usize, mem: []i32) i32 { // dp[1] と dp[2] は既知なので返す if (i == 1 or i == 2) { return @intCast(i); } // dp[i] の記録があれば、それをそのまま返す if (mem[i] != -1) { return mem[i]; } // dp[i] = dp[i-1] + dp[i-2] var count = dfs(i - 1, mem) + dfs(i - 2, mem); // dp[i] を記録する mem[i] = count; return count; } // 階段登り:メモ化探索 fn climbingStairsDFSMem(comptime n: usize) i32 { // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す var mem = [_]i32{ -1 } ** (n + 1); return dfs(n, &mem); } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsDFSMem(n); std.debug.print("{} 段の階段を上る方法は全部で {} 通り\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig ================================================ // File: climbing_stairs_dp.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 階段登り:動的計画法 fn climbingStairsDP(comptime n: usize) i32 { // dp[1] と dp[2] は既知なので返す if (n == 1 or n == 2) { return @intCast(n); } // 部分問題の解を保存するために dp テーブルを初期化 var dp = [_]i32{-1} ** (n + 1); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = 1; dp[2] = 2; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (3..n + 1) |i| { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } // 階段登り:空間最適化した動的計画法 fn climbingStairsDPComp(comptime n: usize) i32 { if (n == 1 or n == 2) { return @intCast(n); } var a: i32 = 1; var b: i32 = 2; for (3..n + 1) |_| { var tmp = b; b = a + b; a = tmp; } return b; } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsDP(n); std.debug.print("{} 段の階段を上る方法は全部で {} 通り\n", .{ n, res }); res = climbingStairsDPComp(n); std.debug.print("{} 段の階段を上る方法は全部で {} 通り\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_dynamic_programming/coin_change.zig ================================================ // File: coin_change.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // コイン両替:動的計画法 fn coinChangeDP(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; comptime var max = amt + 1; // dp テーブルを初期化 var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); // 状態遷移:先頭行と先頭列 for (1..amt + 1) |a| { dp[0][a] = max; } // 状態遷移: 残りの行と列 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = @min(dp[i - 1][a], dp[i][a - @as(usize, @intCast(coins[i - 1]))] + 1); } } } if (dp[n][amt] != max) { return @intCast(dp[n][amt]); } else { return -1; } } // コイン交換:空間最適化後の動的計画法 fn coinChangeDPComp(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; comptime var max = amt + 1; // dp テーブルを初期化 var dp = [_]i32{0} ** (amt + 1); @memset(&dp, max); dp[0] = 0; // 状態遷移 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = @min(dp[a], dp[a - @as(usize, @intCast(coins[i - 1]))] + 1); } } } if (dp[amt] != max) { return @intCast(dp[amt]); } else { return -1; } } // Driver Code pub fn main() !void { comptime var coins = [_]i32{ 1, 2, 5 }; comptime var amt: usize = 4; // 動的計画法 var res = coinChangeDP(&coins, amt); std.debug.print("目標金額にするために必要な最小の硬貨枚数は {}\n", .{res}); // 空間最適化後の動的計画法 res = coinChangeDPComp(&coins, amt); std.debug.print("目標金額にするために必要な最小の硬貨枚数は {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_dynamic_programming/coin_change_ii.zig ================================================ // File: coin_change_ii.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // コイン両替 II:動的計画法 fn coinChangeIIDP(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; // dp テーブルを初期化 var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); // 先頭列を初期化する for (0..n + 1) |i| { dp[i][0] = 1; } // 状態遷移 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = dp[i - 1][a] + dp[i][a - @as(usize, @intCast(coins[i - 1]))]; } } } return dp[n][amt]; } // コイン両替 II:空間最適化した動的計画法 fn coinChangeIIDPComp(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; // dp テーブルを初期化 var dp = [_]i32{0} ** (amt + 1); dp[0] = 1; // 状態遷移 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = dp[a] + dp[a - @as(usize, @intCast(coins[i - 1]))]; } } } return dp[amt]; } // Driver Code pub fn main() !void { comptime var coins = [_]i32{ 1, 2, 5 }; comptime var amt: usize = 5; // 動的計画法 var res = coinChangeIIDP(&coins, amt); std.debug.print("目標金額を作る硬貨の組合せ数は {}\n", .{res}); // 空間最適化後の動的計画法 res = coinChangeIIDPComp(&coins, amt); std.debug.print("目標金額を作る硬貨の組合せ数は {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_dynamic_programming/edit_distance.zig ================================================ // File: edit_distance.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 編集距離:総当たり探索 fn editDistanceDFS(comptime s: []const u8, comptime t: []const u8, i: usize, j: usize) i32 { // s と t がともに空なら 0 を返す if (i == 0 and j == 0) { return 0; } // s が空なら t の長さを返す if (i == 0) { return @intCast(j); } // t が空なら s の長さを返す if (j == 0) { return @intCast(i); } // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s[i - 1] == t[j - 1]) { return editDistanceDFS(s, t, i - 1, j - 1); } // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 var insert = editDistanceDFS(s, t, i, j - 1); var delete = editDistanceDFS(s, t, i - 1, j); var replace = editDistanceDFS(s, t, i - 1, j - 1); // 最小編集回数を返す return @min(@min(insert, delete), replace) + 1; } // 編集距離:メモ化探索 fn editDistanceDFSMem(comptime s: []const u8, comptime t: []const u8, mem: anytype, i: usize, j: usize) i32 { // s と t がともに空なら 0 を返す if (i == 0 and j == 0) { return 0; } // s が空なら t の長さを返す if (i == 0) { return @intCast(j); } // t が空なら s の長さを返す if (j == 0) { return @intCast(i); } // 記録済みなら、それをそのまま返す if (mem[i][j] != -1) { return mem[i][j]; } // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s[i - 1] == t[j - 1]) { return editDistanceDFSMem(s, t, mem, i - 1, j - 1); } // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 var insert = editDistanceDFSMem(s, t, mem, i, j - 1); var delete = editDistanceDFSMem(s, t, mem, i - 1, j); var replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最小編集回数を記録して返す mem[i][j] = @min(@min(insert, delete), replace) + 1; return mem[i][j]; } // 編集距離:動的計画法 fn editDistanceDP(comptime s: []const u8, comptime t: []const u8) i32 { comptime var n = s.len; comptime var m = t.len; var dp = [_][m + 1]i32{[_]i32{0} ** (m + 1)} ** (n + 1); // 状態遷移:先頭行と先頭列 for (1..n + 1) |i| { dp[i][0] = @intCast(i); } for (1..m + 1) |j| { dp[0][j] = @intCast(j); } // 状態遷移: 残りの行と列 for (1..n + 1) |i| { for (1..m + 1) |j| { if (s[i - 1] == t[j - 1]) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i - 1][j - 1]; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = @min(@min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } // 編集距離:空間最適化した動的計画法 fn editDistanceDPComp(comptime s: []const u8, comptime t: []const u8) i32 { comptime var n = s.len; comptime var m = t.len; var dp = [_]i32{0} ** (m + 1); // 状態遷移:先頭行 for (1..m + 1) |j| { dp[j] = @intCast(j); } // 状態遷移:残りの行 for (1..n + 1) |i| { // 状態遷移:先頭列 var leftup = dp[0]; // dp[i-1, j-1] を一時保存する dp[0] = @intCast(i); // 状態遷移:残りの列 for (1..m + 1) |j| { var temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftup; } else { // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = @min(@min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 次の反復の dp[i-1, j-1] に更新する } } return dp[m]; } // Driver Code pub fn main() !void { const s = "bag"; const t = "pack"; comptime var n = s.len; comptime var m = t.len; // 全探索 var res = editDistanceDFS(s, t, n, m); std.debug.print("{s} を {s} に変更するには最小で {} 回の編集が必要です\n", .{ s, t, res }); // メモ化探索 var mem = [_][m + 1]i32{[_]i32{-1} ** (m + 1)} ** (n + 1); res = editDistanceDFSMem(s, t, @constCast(&mem), n, m); std.debug.print("{s} を {s} に変更するには最小で {} 回の編集が必要です\n", .{ s, t, res }); // 動的計画法 res = editDistanceDP(s, t); std.debug.print("{s} を {s} に変更するには最小で {} 回の編集が必要です\n", .{ s, t, res }); // 空間最適化後の動的計画法 res = editDistanceDPComp(s, t); std.debug.print("{s} を {s} に変更するには最小で {} 回の編集が必要です\n", .{ s, t, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_dynamic_programming/knapsack.zig ================================================ // File: knapsack.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 0-1 ナップサック:総当たり探索 fn knapsackDFS(wgt: []i32, val: []i32, i: usize, c: usize) i32 { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 or c == 0) { return 0; } // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する var no = knapsackDFS(wgt, val, i - 1, c); var yes = knapsackDFS(wgt, val, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; // 2つの案のうち価値が大きいほうを返す return @max(no, yes); } // 0-1 ナップサック:メモ化探索 fn knapsackDFSMem(wgt: []i32, val: []i32, mem: anytype, i: usize, c: usize) i32 { // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 or c == 0) { return 0; } // 既に記録があればそのまま返す if (mem[i][c] != -1) { return mem[i][c]; } // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 品物 i を入れない場合と入れる場合の最大価値を計算する var no = knapsackDFSMem(wgt, val, mem, i - 1, c); var yes = knapsackDFSMem(wgt, val, mem, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; // 2 つの案のうち価値が大きい方を記録して返す mem[i][c] = @max(no, yes); return mem[i][c]; } // 0-1 ナップサック:動的計画法 fn knapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // dp テーブルを初期化 var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); // 状態遷移 for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = @max(dp[i - 1][c], dp[i - 1][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[n][cap]; } // 0-1 ナップサック:空間最適化後の動的計画法 fn knapsackDPComp(wgt: []i32, val: []i32, comptime cap: usize) i32 { var n = wgt.len; // dp テーブルを初期化 var dp = [_]i32{0} ** (cap + 1); // 状態遷移 for (1..n + 1) |i| { // 逆順に走査する var c = cap; while (c > 0) : (c -= 1) { if (wgt[i - 1] < c) { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[cap]; } // Driver Code pub fn main() !void { comptime var wgt = [_]i32{ 10, 20, 30, 40, 50 }; comptime var val = [_]i32{ 50, 120, 150, 210, 240 }; comptime var cap = 50; comptime var n = wgt.len; // 全探索 var res = knapsackDFS(&wgt, &val, n, cap); std.debug.print("ナップサック容量を超えない最大価値は {}\n", .{res}); // メモ化探索 var mem = [_][cap + 1]i32{[_]i32{-1} ** (cap + 1)} ** (n + 1); res = knapsackDFSMem(&wgt, &val, @constCast(&mem), n, cap); std.debug.print("ナップサック容量を超えない最大価値は {}\n", .{res}); // 動的計画法 res = knapsackDP(&wgt, &val, cap); std.debug.print("ナップサック容量を超えない最大価値は {}\n", .{res}); // 空間最適化後の動的計画法 res = knapsackDPComp(&wgt, &val, cap); std.debug.print("ナップサック容量を超えない最大価値は {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig ================================================ // File: min_cost_climbing_stairs_dp.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 階段登りの最小コスト:動的計画法 fn minCostClimbingStairsDP(comptime cost: []i32) i32 { comptime var n = cost.len - 1; if (n == 1 or n == 2) { return cost[n]; } // 部分問題の解を保存するために dp テーブルを初期化 var dp = [_]i32{-1} ** (n + 1); // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = cost[1]; dp[2] = cost[2]; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (3..n + 1) |i| { dp[i] = @min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } // 階段昇りの最小コスト:空間最適化後の動的計画法 fn minCostClimbingStairsDPComp(cost: []i32) i32 { var n = cost.len - 1; if (n == 1 or n == 2) { return cost[n]; } var a = cost[1]; var b = cost[2]; // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (3..n + 1) |i| { var tmp = b; b = @min(a, tmp) + cost[i]; a = tmp; } return b; } // Driver Code pub fn main() !void { comptime var cost = [_]i32{ 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; std.debug.print("入力された階段のコストのリストは {any}\n", .{cost}); var res = minCostClimbingStairsDP(&cost); std.debug.print("入力された階段のコストのリストは {}\n", .{res}); res = minCostClimbingStairsDPComp(&cost); std.debug.print("入力された階段のコストのリストは {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_dynamic_programming/min_path_sum.zig ================================================ // File: min_path_sum.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 最小経路和:全探索 fn minPathSumDFS(grid: anytype, i: i32, j: i32) i32 { // 左上のセルなら探索を終了する if (i == 0 and j == 0) { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 or j < 0) { return std.math.maxInt(i32); } // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する var up = minPathSumDFS(grid, i - 1, j); var left = minPathSumDFS(grid, i, j - 1); // 左上隅から (i, j) までの最小経路コストを返す return @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; } // 最小経路和:メモ化探索 fn minPathSumDFSMem(grid: anytype, mem: anytype, i: i32, j: i32) i32 { // 左上のセルなら探索を終了する if (i == 0 and j == 0) { return grid[0][0]; } // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 or j < 0) { return std.math.maxInt(i32); } // 既に記録があればそのまま返す if (mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] != -1) { return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; } // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する var up = minPathSumDFSMem(grid, mem, i - 1, j); var left = minPathSumDFSMem(grid, mem, i, j - 1); // 左上隅から (i, j) までの最小経路コストを返す // 左上隅から (i, j) までの最小経路コストを記録して返す mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] = @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; } // 最小経路和:動的計画法 fn minPathSumDP(comptime grid: anytype) i32 { comptime var n = grid.len; comptime var m = grid[0].len; // dp テーブルを初期化 var dp = [_][m]i32{[_]i32{0} ** m} ** n; dp[0][0] = grid[0][0]; // 状態遷移:先頭行 for (1..m) |j| { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 状態遷移:先頭列 for (1..n) |i| { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 状態遷移: 残りの行と列 for (1..n) |i| { for (1..m) |j| { dp[i][j] = @min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } // 最小経路和:空間最適化後の動的計画法 fn minPathSumDPComp(comptime grid: anytype) i32 { comptime var n = grid.len; comptime var m = grid[0].len; // dp テーブルを初期化 var dp = [_]i32{0} ** m; // 状態遷移:先頭行 dp[0] = grid[0][0]; for (1..m) |j| { dp[j] = dp[j - 1] + grid[0][j]; } // 状態遷移:残りの行 for (1..n) |i| { // 状態遷移:先頭列 dp[0] = dp[0] + grid[i][0]; for (1..m) |j| { dp[j] = @min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } // Driver Code pub fn main() !void { comptime var grid = [_][4]i32{ [_]i32{ 1, 3, 1, 5 }, [_]i32{ 2, 2, 4, 2 }, [_]i32{ 5, 3, 2, 1 }, [_]i32{ 4, 3, 5, 2 }, }; comptime var n = grid.len; comptime var m = grid[0].len; // 全探索 var res = minPathSumDFS(&grid, n - 1, m - 1); std.debug.print("左上から右下までの最小経路和は {}\n", .{res}); // メモ化探索 var mem = [_][m]i32{[_]i32{-1} ** m} ** n; res = minPathSumDFSMem(&grid, &mem, n - 1, m - 1); std.debug.print("左上から右下までの最小経路和は {}\n", .{res}); // 動的計画法 res = minPathSumDP(&grid); std.debug.print("左上から右下までの最小経路和は {}\n", .{res}); // 空間最適化後の動的計画法 res = minPathSumDPComp(&grid); std.debug.print("左上から右下までの最小経路和は {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig ================================================ // File: unbounded_knapsack.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 完全ナップサック問題:動的計画法 fn unboundedKnapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // dp テーブルを初期化 var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); // 状態遷移 for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = @max(dp[i - 1][c], dp[i][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[n][cap]; } // 完全ナップサック問題:空間最適化後の動的計画法 fn unboundedKnapsackDPComp(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // dp テーブルを初期化 var dp = [_]i32{0} ** (cap + 1); // 状態遷移 for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c]; } else { // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[cap]; } // Driver Code pub fn main() !void { comptime var wgt = [_]i32{ 1, 2, 3 }; comptime var val = [_]i32{ 5, 11, 15 }; comptime var cap = 4; // 動的計画法 var res = unboundedKnapsackDP(&wgt, &val, cap); std.debug.print("ナップサック容量を超えない最大価値は {}\n", .{res}); // 空間最適化後の動的計画法 res = unboundedKnapsackDPComp(&wgt, &val, cap); std.debug.print("ナップサック容量を超えない最大価値は {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_hashing/array_hash_map.zig ================================================ // File: array_hash_map.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // キーと値の組 const Pair = struct { key: usize = undefined, val: []const u8 = undefined, pub fn init(key: usize, val: []const u8) Pair { return Pair { .key = key, .val = val, }; } }; // 配列ベースのハッシュテーブル pub fn ArrayHashMap(comptime T: type) type { return struct { bucket: ?std.ArrayList(?T) = null, mem_allocator: std.mem.Allocator = undefined, const Self = @This(); // コンストラクタ pub fn init(self: *Self, allocator: std.mem.Allocator) !void { self.mem_allocator = allocator; // 長さ 100 のバケット(配列)を初期化する self.bucket = std.ArrayList(?T).init(self.mem_allocator); var i: i32 = 0; while (i < 100) : (i += 1) { try self.bucket.?.append(null); } } // デストラクタ pub fn deinit(self: *Self) void { if (self.bucket != null) self.bucket.?.deinit(); } // ハッシュ関数 fn hashFunc(key: usize) usize { var index = key % 100; return index; } // 検索操作 pub fn get(self: *Self, key: usize) []const u8 { var index = hashFunc(key); var pair = self.bucket.?.items[index]; return pair.?.val; } // 追加操作 pub fn put(self: *Self, key: usize, val: []const u8) !void { var pair = Pair.init(key, val); var index = hashFunc(key); self.bucket.?.items[index] = pair; } // 削除操作 pub fn remove(self: *Self, key: usize) !void { var index = hashFunc(key); // null に設定し、削除を表す self.bucket.?.items[index] = null; } // すべてのキーと値のペアを取得 pub fn pairSet(self: *Self) !std.ArrayList(T) { var entry_set = std.ArrayList(T).init(self.mem_allocator); for (self.bucket.?.items) |item| { if (item == null) continue; try entry_set.append(item.?); } return entry_set; } // すべてのキーを取得 pub fn keySet(self: *Self) !std.ArrayList(usize) { var key_set = std.ArrayList(usize).init(self.mem_allocator); for (self.bucket.?.items) |item| { if (item == null) continue; try key_set.append(item.?.key); } return key_set; } // すべての値を取得 pub fn valueSet(self: *Self) !std.ArrayList([]const u8) { var value_set = std.ArrayList([]const u8).init(self.mem_allocator); for (self.bucket.?.items) |item| { if (item == null) continue; try value_set.append(item.?.val); } return value_set; } // ハッシュテーブルを出力 pub fn print(self: *Self) !void { var entry_set = try self.pairSet(); defer entry_set.deinit(); for (entry_set.items) |item| { std.debug.print("{} -> {s}\n", .{item.key, item.val}); } } }; } // Driver Code pub fn main() !void { // ハッシュテーブルを初期化 var map = ArrayHashMap(Pair){}; try map.init(std.heap.page_allocator); defer map.deinit(); // 追加操作 // ハッシュテーブルにキーと値の組 (key, value) を追加する try map.put(12836, "シャオハー"); try map.put(15937, "シャオルオ"); try map.put(16750, "シャオスワン"); try map.put(13276, "シャオファー"); try map.put(10583, "シャオヤー"); std.debug.print("\n追加完了後、ハッシュテーブルは\nKey -> Value\n", .{}); try map.print(); // 検索操作 // ハッシュテーブルにキー key を入力し、値 value を取得する var name = map.get(15937); std.debug.print("\n学籍番号 15937 を入力すると、名前 {s} が見つかります\n", .{name}); // 削除操作 // ハッシュテーブルからキーと値の組 (key, value) を削除する try map.remove(10583); std.debug.print("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value\n", .{}); try map.print(); // ハッシュテーブルを走査 std.debug.print("\nキーと値の組 Key->Value を順に表示\n", .{}); var entry_set = try map.pairSet(); for (entry_set.items) |kv| { std.debug.print("{} -> {s}\n", .{kv.key, kv.val}); } defer entry_set.deinit(); std.debug.print("\nキー Key のみを順に表示\n", .{}); var key_set = try map.keySet(); for (key_set.items) |key| { std.debug.print("{}\n", .{key}); } defer key_set.deinit(); std.debug.print("\n値 value のみを順に表示\n", .{}); var value_set = try map.valueSet(); for (value_set.items) |val| { std.debug.print("{s}\n", .{val}); } defer value_set.deinit(); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_hashing/hash_map.zig ================================================ // File: hash_map.zig // Created Time: 2023-01-13 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // ハッシュテーブルを初期化 var map = std.AutoHashMap(i32, []const u8).init(std.heap.page_allocator); // メモリの遅延解放 defer map.deinit(); // 追加操作 // ハッシュテーブルにキーと値の組 (key, value) を追加する try map.put(12836, "シャオハー"); try map.put(15937, "シャオルオ"); try map.put(16750, "シャオスワン"); try map.put(13276, "シャオファー"); try map.put(10583, "シャオヤー"); std.debug.print("\n追加完了後、ハッシュテーブルは\nKey -> Value\n", .{}); inc.PrintUtil.printHashMap(i32, []const u8, map); // 検索操作 // ハッシュテーブルにキー key を入力し、値 value を取得する var name = map.get(15937).?; std.debug.print("\n学籍番号 15937 を入力すると、名前 {s} が見つかります\n", .{name}); // 削除操作 // ハッシュテーブルからキーと値の組 (key, value) を削除する _ = map.remove(10583); std.debug.print("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value\n", .{}); inc.PrintUtil.printHashMap(i32, []const u8, map); // ハッシュテーブルを走査 std.debug.print("\nキーと値の組 Key->Value を順に表示\n", .{}); inc.PrintUtil.printHashMap(i32, []const u8, map); std.debug.print("\nキー Key のみを順に表示\n", .{}); var it = map.iterator(); while (it.next()) |kv| { std.debug.print("{}\n", .{kv.key_ptr.*}); } std.debug.print("\n値 value のみを順に表示\n", .{}); it = map.iterator(); while (it.next()) |kv| { std.debug.print("{s}\n", .{kv.value_ptr.*}); } _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_heap/heap.zig ================================================ // File: heap.zig // Created Time: 2023-01-14 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); fn lessThan(context: void, a: i32, b: i32) std.math.Order { _ = context; return std.math.order(a, b); } fn greaterThan(context: void, a: i32, b: i32) std.math.Order { return lessThan(context, a, b).invert(); } fn testPush(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype, val: T) !void { try heap.add(val); // 要素をヒープに追加 std.debug.print("\n要素 {} をヒープに追加した後\n", .{val}); try inc.PrintUtil.printHeap(T, mem_allocator, heap); } fn testPop(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype) !void { var val = heap.remove(); // ヒープ頂点の要素を取り出す std.debug.print("\nヒープの先頭要素 {} を取り出した後\n", .{val}); try inc.PrintUtil.printHeap(T, mem_allocator, heap); } // Driver Code pub fn main() !void { // メモリアロケータを初期化する var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // ヒープを初期化する // 最小ヒープを初期化する const PQlt = std.PriorityQueue(i32, void, lessThan); var min_heap = PQlt.init(std.heap.page_allocator, {}); defer min_heap.deinit(); // 最大ヒープを初期化 const PQgt = std.PriorityQueue(i32, void, greaterThan); var max_heap = PQgt.init(std.heap.page_allocator, {}); defer max_heap.deinit(); std.debug.print("\n以下のテストケースは最大ヒープ", .{}); // 要素をヒープに追加 try testPush(i32, mem_allocator, &max_heap, 1); try testPush(i32, mem_allocator, &max_heap, 3); try testPush(i32, mem_allocator, &max_heap, 2); try testPush(i32, mem_allocator, &max_heap, 5); try testPush(i32, mem_allocator, &max_heap, 4); // ヒープ頂点の要素を取得 var peek = max_heap.peek().?; std.debug.print("\nヒープの先頭要素は {}\n", .{peek}); // ヒープ頂点の要素を取り出す try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); // ヒープのサイズを取得 var size = max_heap.len; std.debug.print("\nヒープ要素数は {}\n", .{size}); // ヒープが空かどうかを判定 var is_empty = if (max_heap.len == 0) true else false; std.debug.print("\nヒープが空かどうか {}\n", .{is_empty}); // リストを入力してヒープを構築 try min_heap.addSlice(&[_]i32{ 1, 3, 2, 5, 4 }); std.debug.print("\nリストを入力して最小ヒープを構築した後\n", .{}); try inc.PrintUtil.printHeap(i32, mem_allocator, min_heap); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_heap/my_heap.zig ================================================ // File: my_heap.zig // Created Time: 2023-01-14 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // ヒープクラスの簡易実装 pub fn MaxHeap(comptime T: type) type { return struct { const Self = @This(); max_heap: ?std.ArrayList(T) = null, // 配列ではなくリストを使うことで、拡張を考慮する必要がない // コンストラクタ。入力リストに基づいてヒープを構築する pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []const T) !void { if (self.max_heap != null) return; self.max_heap = std.ArrayList(T).init(allocator); // リスト要素をそのままヒープに追加 try self.max_heap.?.appendSlice(nums); // 葉ノード以外のすべてのノードをヒープ化 var i: usize = parent(self.size() - 1) + 1; while (i > 0) : (i -= 1) { try self.siftDown(i - 1); } } // デストラクタ。メモリを解放 pub fn deinit(self: *Self) void { if (self.max_heap != null) self.max_heap.?.deinit(); } // 左子ノードのインデックスを取得 fn left(i: usize) usize { return 2 * i + 1; } // 右子ノードのインデックスを取得 fn right(i: usize) usize { return 2 * i + 2; } // 親ノードのインデックスを取得 fn parent(i: usize) usize { // return (i - 1) / 2; // 切り捨て除算 return @divFloor(i - 1, 2); } // 要素を交換 fn swap(self: *Self, i: usize, j: usize) !void { var tmp = self.max_heap.?.items[i]; try self.max_heap.?.replaceRange(i, 1, &[_]T{self.max_heap.?.items[j]}); try self.max_heap.?.replaceRange(j, 1, &[_]T{tmp}); } // ヒープのサイズを取得 pub fn size(self: *Self) usize { return self.max_heap.?.items.len; } // ヒープが空かどうかを判定 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // ヒープ先頭要素にアクセス pub fn peek(self: *Self) T { return self.max_heap.?.items[0]; } // 要素をヒープに追加 pub fn push(self: *Self, val: T) !void { // ノードを追加 try self.max_heap.?.append(val); // 下から上へヒープ化 try self.siftUp(self.size() - 1); } // ノード i から始めて、下から上へヒープ化 fn siftUp(self: *Self, i_: usize) !void { var i = i_; while (true) { // ノード i の親ノードを取得 var p = parent(i); // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 if (p < 0 or self.max_heap.?.items[i] <= self.max_heap.?.items[p]) break; // 2 つのノードを交換 try self.swap(i, p); // ループで下から上へヒープ化 i = p; } } // 要素をヒープから取り出す pub fn pop(self: *Self) !T { // 判定処理 if (self.isEmpty()) unreachable; // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) try self.swap(0, self.size() - 1); // ノードを削除 var val = self.max_heap.?.pop(); // 上から下へヒープ化 try self.siftDown(0); // ヒープ先頭要素を返す return val; } // ノード i から始めて、上から下へヒープ化 fn siftDown(self: *Self, i_: usize) !void { var i = i_; while (true) { // ノード i, l, r のうち値が最大のノードを ma とする var l = left(i); var r = right(i); var ma = i; if (l < self.size() and self.max_heap.?.items[l] > self.max_heap.?.items[ma]) ma = l; if (r < self.size() and self.max_heap.?.items[r] > self.max_heap.?.items[ma]) ma = r; // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma == i) break; // 2 つのノードを交換 try self.swap(i, ma); // ループで上から下へヒープ化 i = ma; } } fn lessThan(context: void, a: T, b: T) std.math.Order { _ = context; return std.math.order(a, b); } fn greaterThan(context: void, a: T, b: T) std.math.Order { return lessThan(context, a, b).invert(); } // ヒープ(二分木)を出力 pub fn print(self: *Self, mem_allocator: std.mem.Allocator) !void { const PQgt = std.PriorityQueue(T, void, greaterThan); var queue = PQgt.init(std.heap.page_allocator, {}); defer queue.deinit(); try queue.addSlice(self.max_heap.?.items); try inc.PrintUtil.printHeap(T, mem_allocator, queue); } }; } // Driver Code pub fn main() !void { // メモリアロケータを初期化する var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // 最大ヒープを初期化 var max_heap = MaxHeap(i32){}; try max_heap.init(std.heap.page_allocator, &[_]i32{ 9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2 }); defer max_heap.deinit(); std.debug.print("\nリストを入力してヒープを構築した後\n", .{}); try max_heap.print(mem_allocator); // ヒープ頂点の要素を取得 var peek = max_heap.peek(); std.debug.print("\nヒープの先頭要素は {}\n", .{peek}); // 要素をヒープに追加 const val = 7; try max_heap.push(val); std.debug.print("\n要素 {} をヒープに追加した後\n", .{val}); try max_heap.print(mem_allocator); // ヒープ頂点の要素を取り出す peek = try max_heap.pop(); std.debug.print("\nヒープの先頭要素 {} を取り出した後\n", .{peek}); try max_heap.print(mem_allocator); // ヒープのサイズを取得 var size = max_heap.size(); std.debug.print("\nヒープ要素数は {}", .{size}); // ヒープが空かどうかを判定 var is_empty = max_heap.isEmpty(); std.debug.print("\nヒープが空かどうか {}\n", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_searching/binary_search.zig ================================================ // File: binary_search.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 二分探索(両閉区間) fn binarySearch(comptime T: type, nums: std.ArrayList(T), target: T) T { // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す var i: usize = 0; var j: usize = nums.items.len - 1; // ループし、探索区間が空になったら終了する(i > j で空) while (i <= j) { var m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums.items[m] < target) { // この場合、target は区間 [m+1, j] にある i = m + 1; } else if (nums.items[m] > target) { // この場合、target は区間 [i, m-1] にある j = m - 1; } else { // 目標要素が見つかったらそのインデックスを返す return @intCast(m); } } // 目標要素が見つからなければ -1 を返す return -1; } // 二分探索(左閉右開区間) fn binarySearchLCRO(comptime T: type, nums: std.ArrayList(T), target: T) T { // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す var i: usize = 0; var j: usize = nums.items.len; // ループし、探索区間が空になったら終了する(i = j で空) while (i <= j) { var m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums.items[m] < target) { // この場合、target は区間 [m+1, j) にある i = m + 1; } else if (nums.items[m] > target) { // この場合、target は区間 [i, m) にある j = m; } else { // 目標要素が見つかったらそのインデックスを返す return @intCast(m); } } // 目標要素が見つからなければ -1 を返す return -1; } // Driver Code pub fn main() !void { var target: i32 = 6; var nums = std.ArrayList(i32).init(std.heap.page_allocator); defer nums.deinit(); try nums.appendSlice(&[_]i32{ 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }); // 二分探索(両閉区間) var index = binarySearch(i32, nums, target); std.debug.print("対象要素 6 のインデックス = {}\n", .{index}); // 二分探索(左閉右開区間) index = binarySearchLCRO(i32, nums, target); std.debug.print("対象要素 6 のインデックス = {}\n", .{index}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_searching/hashing_search.zig ================================================ // File: hashing_search.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // ハッシュ探索(配列) fn hashingSearchArray(comptime T: type, map: std.AutoHashMap(T, T), target: T) T { // ハッシュテーブルの key: 目標要素、value: インデックス // ハッシュテーブルにこの key がなければ -1 を返す if (map.getKey(target) == null) return -1; return map.get(target).?; } // ハッシュ探索(連結リスト) fn hashingSearchLinkedList(comptime T: type, map: std.AutoHashMap(T, *inc.ListNode(T)), target: T) ?*inc.ListNode(T) { // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト // ハッシュテーブルにこの key がなければ null を返す if (map.getKey(target) == null) return null; return map.get(target); } // Driver Code pub fn main() !void { var target: i32 = 3; // ハッシュ探索(配列) var nums = [_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; // ハッシュテーブルを初期化 var map = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); defer map.deinit(); for (nums, 0..) |num, i| { try map.put(num, @as(i32, @intCast(i))); // key: 要素、value: インデックス } var index = hashingSearchArray(i32, map, target); std.debug.print("対象要素 3 のインデックス = {}\n", .{index}); // ハッシュ探索(連結リスト) var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var head = try inc.ListUtil.arrToLinkedList(i32, mem_allocator, &nums); // ハッシュテーブルを初期化 var map1 = std.AutoHashMap(i32, *inc.ListNode(i32)).init(std.heap.page_allocator); defer map1.deinit(); while (head != null) { try map1.put(head.?.val, head.?); head = head.?.next; } var node = hashingSearchLinkedList(i32, map1, target); std.debug.print("対象ノード値 3 に対応するノードオブジェクトは ", .{}); try inc.PrintUtil.printLinkedList(i32, node); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_searching/linear_search.zig ================================================ // File: linear_search.zig // Created Time: 2023-01-13 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 線形探索(配列) fn linearSearchArray(comptime T: type, nums: std.ArrayList(T), target: T) T { // 配列を走査 for (nums.items, 0..) |num, i| { // 対象要素が見つかったら、その添字を返す if (num == target) { return @intCast(i); } } // 目標要素が見つからなければ -1 を返す return -1; } // 線形探索(連結リスト) pub fn linearSearchLinkedList(comptime T: type, node: ?*inc.ListNode(T), target: T) ?*inc.ListNode(T) { var head = node; // 連結リストを走査 while (head != null) { // 対象ノードが見つかったら、それを返す if (head.?.val == target) return head; head = head.?.next; } return null; } // Driver Code pub fn main() !void { var target: i32 = 3; // 配列で線形探索を行う var nums = std.ArrayList(i32).init(std.heap.page_allocator); defer nums.deinit(); try nums.appendSlice(&[_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }); var index = linearSearchArray(i32, nums, target); std.debug.print("対象要素 3 のインデックス = {}\n", .{index}); // 連結リストで線形探索を行う var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var head = try inc.ListUtil.listToLinkedList(i32, mem_allocator, nums); var node = linearSearchLinkedList(i32, head, target); std.debug.print("対象ノード値 3 に対応するノードオブジェクトは ", .{}); try inc.PrintUtil.printLinkedList(i32, node); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_searching/two_sum.zig ================================================ // File: two_sum.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 方法 1:総当たり列挙 pub fn twoSumBruteForce(nums: []i32, target: i32) ?[2]i32 { var size: usize = nums.len; var i: usize = 0; // 2重ループのため、時間計算量は O(n^2) while (i < size - 1) : (i += 1) { var j = i + 1; while (j < size) : (j += 1) { if (nums[i] + nums[j] == target) { return [_]i32{@intCast(i), @intCast(j)}; } } } return null; } // 方法 2:補助ハッシュテーブル pub fn twoSumHashTable(nums: []i32, target: i32) !?[2]i32 { var size: usize = nums.len; // 補助ハッシュテーブルを使用し、空間計算量は O(n) var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); defer dic.deinit(); var i: usize = 0; // 単一ループで、時間計算量は O(n) while (i < size) : (i += 1) { if (dic.contains(target - nums[i])) { return [_]i32{dic.get(target - nums[i]).?, @intCast(i)}; } try dic.put(nums[i], @intCast(i)); } return null; } pub fn main() !void { // ======= Test Case ======= var nums = [_]i32{ 2, 7, 11, 15 }; var target: i32 = 9; // ====== Driver Code ====== // 方法 1 var res = twoSumBruteForce(&nums, target).?; std.debug.print("方法1 res = ", .{}); inc.PrintUtil.printArray(i32, &res); // 方法 2 res = (try twoSumHashTable(&nums, target)).?; std.debug.print("\n方法2 res = ", .{}); inc.PrintUtil.printArray(i32, &res); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_sorting/bubble_sort.zig ================================================ // File: bubble_sort.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // バブルソート fn bubbleSort(nums: []i32) void { // 外側のループ:未ソート区間は [0, i] var i: usize = nums.len - 1; while (i > 0) : (i -= 1) { var j: usize = 0; // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 var tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } // バブルソート(フラグ最適化) fn bubbleSortWithFlag(nums: []i32) void { // 外側のループ:未ソート区間は [0, i] var i: usize = nums.len - 1; while (i > 0) : (i -= 1) { var flag = false; // フラグを初期化する var j: usize = 0; // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 var tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; } } if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了 } } // Driver Code pub fn main() !void { var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; bubbleSort(&nums); std.debug.print("バブルソート完了後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); var nums1 = [_]i32{ 4, 1, 3, 1, 5, 2 }; bubbleSortWithFlag(&nums1); std.debug.print("\nバブルソート完了後 nums1 = ", .{}); inc.PrintUtil.printArray(i32, &nums1); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_sorting/insertion_sort.zig ================================================ // File: insertion_sort.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 挿入ソート fn insertionSort(nums: []i32) void { // 外側ループ:整列済み区間は [0, i-1] var i: usize = 1; while (i < nums.len) : (i += 1) { var base = nums[i]; var j: usize = i; // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する while (j >= 1 and nums[j - 1] > base) : (j -= 1) { nums[j] = nums[j - 1]; // nums[j] を 1 つ右へ移動する } nums[j] = base; // base を正しい位置に配置する } } // Driver Code pub fn main() !void { var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; insertionSort(&nums); std.debug.print("挿入ソート完了後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_sorting/merge_sort.zig ================================================ // File: merge_sort.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 左部分配列と右部分配列をマージする // 左部分配列の区間は [left, mid] // 右部分配列の区間は [mid + 1, right] fn merge(nums: []i32, left: usize, mid: usize, right: usize) !void { // 補助配列を初期化する var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var tmp = try mem_allocator.alloc(i32, right + 1 - left); std.mem.copy(i32, tmp, nums[left..right+1]); // 左部分配列の開始添字と終了添字 var leftStart = left - left; var leftEnd = mid - left; // 右部分配列の開始インデックスと終了インデックス var rightStart = mid + 1 - left; var rightEnd = right - left; // i, j はそれぞれ左部分配列と右部分配列の先頭要素を指す var i = leftStart; var j = rightStart; // 元の配列 nums を上書きして左部分配列と右部分配列をマージする var k = left; while (k <= right) : (k += 1) { // 「左部分配列のマージがすべて完了している」場合は、右部分配列の要素を選び、`j++` する if (i > leftEnd) { nums[k] = tmp[j]; j += 1; // そうでなければ、「右部分配列のマージがすべて完了している」または「左部分配列の要素 <= 右部分配列の要素」の場合、左部分配列の要素を選び、i++ する } else if (j > rightEnd or tmp[i] <= tmp[j]) { nums[k] = tmp[i]; i += 1; // そうでなければ、「左右の部分配列のマージがどちらも完了しておらず」かつ「左部分配列の要素 > 右部分配列の要素」の場合、右部分配列の要素を選び、j++ する } else { nums[k] = tmp[j]; j += 1; } } } // マージソート fn mergeSort(nums: []i32, left: usize, right: usize) !void { // 終了条件 if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了 // 分割フェーズ var mid = left + (right - left) / 2; // 中点を計算 try mergeSort(nums, left, mid); // 左部分配列を再帰処理 try mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 // マージフェーズ try merge(nums, left, mid, right); } // Driver Code pub fn main() !void { // マージソート var nums = [_]i32{ 7, 3, 2, 6, 0, 1, 5, 4 }; try mergeSort(&nums, 0, nums.len - 1); std.debug.print("マージソート完了後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_sorting/quick_sort.zig ================================================ // File: quick_sort.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // クイックソートクラス const QuickSort = struct { // 要素の交換 pub fn swap(nums: []i32, i: usize, j: usize) void { var tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } // 番兵分割 pub fn partition(nums: []i32, left: usize, right: usize) usize { // nums[left] を基準値とする var i = left; var j = right; while (i < j) { while (i < j and nums[j] >= nums[left]) j -= 1; // 右から左へ基準値未満の最初の要素を探す while (i < j and nums[i] <= nums[left]) i += 1; // 左から右へ基準値より大きい最初の要素を探す swap(nums, i, j); // この 2 つの要素を交換 } swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } // クイックソート pub fn quickSort(nums: []i32, left: usize, right: usize) void { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return; // 番兵分割 var pivot = partition(nums, left, right); // 左右の部分配列を再帰処理 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } }; // クイックソートクラス(中央値ピボット最適化) const QuickSortMedian = struct { // 要素の交換 pub fn swap(nums: []i32, i: usize, j: usize) void { var tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } // 3つの候補要素の中央値を選ぶ pub fn medianThree(nums: []i32, left: usize, mid: usize, right: usize) usize { var l = nums[left]; var m = nums[mid]; var r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // m は l と r の間 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l は m と r の間 return right; } // 番兵による分割処理(3 点中央値) pub fn partition(nums: []i32, left: usize, right: usize) usize { // 3つの候補要素の中央値を選ぶ var med = medianThree(nums, left, (left + right) / 2, right); // 中央値を配列の最左端に交換する swap(nums, left, med); // nums[left] を基準値とする var i = left; var j = right; while (i < j) { while (i < j and nums[j] >= nums[left]) j -= 1; // 右から左へ基準値未満の最初の要素を探す while (i < j and nums[i] <= nums[left]) i += 1; // 左から右へ基準値より大きい最初の要素を探す swap(nums, i, j); // この 2 つの要素を交換 } swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } // クイックソート pub fn quickSort(nums: []i32, left: usize, right: usize) void { // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return; // 番兵分割 var pivot = partition(nums, left, right); if (pivot == 0) return; // 左右の部分配列を再帰処理 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } }; // クイックソートクラス(再帰深度最適化) const QuickSortTailCall = struct { // 要素の交換 pub fn swap(nums: []i32, i: usize, j: usize) void { var tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } // 番兵分割 pub fn partition(nums: []i32, left: usize, right: usize) usize { // nums[left] を基準値とする var i = left; var j = right; while (i < j) { while (i < j and nums[j] >= nums[left]) j -= 1; // 右から左へ基準値未満の最初の要素を探す while (i < j and nums[i] <= nums[left]) i += 1; // 左から右へ基準値より大きい最初の要素を探す swap(nums, i, j); // この 2 つの要素を交換 } swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } // クイックソート(再帰深度最適化) pub fn quickSort(nums: []i32, left_: usize, right_: usize) void { var left = left_; var right = right_; // 部分配列の長さが 1 なら再帰を終了する while (left < right) { // 番兵による分割処理 var pivot = partition(nums, left, right); // 2 つの部分配列のうち短いほうにクイックソートを適用する if (pivot - left < right - pivot) { quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] } else { quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] } } } }; // Driver Code pub fn main() !void { // クイックソート var nums = [_]i32{ 2, 4, 1, 0, 3, 5 }; QuickSort.quickSort(&nums, 0, nums.len - 1); std.debug.print("クイックソート完了後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); // クイックソート(中央値の基準値で最適化) var nums1 = [_]i32{ 2, 4, 1, 0, 3, 5 }; QuickSortMedian.quickSort(&nums1, 0, nums1.len - 1); std.debug.print("\nクイックソート(中央値ピボット最適化)完了後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums1); // クイックソート(再帰深度最適化) var nums2 = [_]i32{ 2, 4, 1, 0, 3, 5 }; QuickSortTailCall.quickSort(&nums2, 0, nums2.len - 1); std.debug.print("\nクイックソート(再帰深度最適化)完了後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums2); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_sorting/radix_sort.zig ================================================ // File: radix_sort.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 要素 num の下から k 桁目を取得(exp = 10^(k-1)) fn digit(num: i32, exp: i32) i32 { // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す return @mod(@divFloor(num, exp), 10); } // 計数ソート(nums の k 桁目でソート) fn countingSortDigit(nums: []i32, exp: i32) !void { // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); // defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var counter = try mem_allocator.alloc(usize, 10); @memset(counter, 0); var n = nums.len; // 0~9 の各数字の出現回数を集計する for (nums) |num| { var d: u32 = @bitCast(digit(num, exp)); // nums[i] の第 k 位を取得し、d とする counter[d] += 1; // 数字 d の出現回数を数える } // 累積和を求め、「出現回数」を「配列インデックス」に変換する var i: usize = 1; while (i < 10) : (i += 1) { counter[i] += counter[i - 1]; } // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する var res = try mem_allocator.alloc(i32, n); i = n - 1; while (i >= 0) : (i -= 1) { var d: u32 = @bitCast(digit(nums[i], exp)); var j = counter[d] - 1; // d の配列内インデックス j を取得する res[j] = nums[i]; // 現在の要素をインデックス j に格納する counter[d] -= 1; // d の個数を 1 減らす if (i == 0) break; } // 結果で元の配列 nums を上書きする i = 0; while (i < n) : (i += 1) { nums[i] = res[i]; } } // 基数ソート fn radixSort(nums: []i32) !void { // 最大桁数の判定用に配列の最大要素を取得 var m: i32 = std.math.minInt(i32); for (nums) |num| { if (num > m) m = num; } // 下位桁から上位桁の順に走査する var exp: i32 = 1; while (exp <= m) : (exp *= 10) { // 配列要素の k 桁目に対して計数ソートを行う // k = 1 -> exp = 1 // k = 2 -> exp = 10 // つまり exp = 10^(k-1) try countingSortDigit(nums, exp); } } // Driver Code pub fn main() !void { // 基数ソート var nums = [_]i32{ 23, 12, 3, 4, 788, 192 }; try radixSort(&nums); std.debug.print("基数ソート完了後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_stack_and_queue/array_queue.zig ================================================ // File: array_queue.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 循環配列ベースのキュー pub fn ArrayQueue(comptime T: type) type { return struct { const Self = @This(); nums: []T = undefined, // キュー要素を格納する配列 cap: usize = 0, // キューの容量 front: usize = 0, // 先頭ポインタ。先頭要素を指す queSize: usize = 0, // 末尾ポインタ。キューの末尾 + 1 を指す mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // メモリアロケータ // コンストラクタ(メモリを確保して配列を初期化) pub fn init(self: *Self, allocator: std.mem.Allocator, cap: usize) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.cap = cap; self.nums = try self.mem_allocator.alloc(T, self.cap); @memset(self.nums, @as(T, 0)); } // デストラクタ(メモリを解放する) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // キューの容量を取得 pub fn capacity(self: *Self) usize { return self.cap; } // キューの長さを取得 pub fn size(self: *Self) usize { return self.queSize; } // キューが空かどうかを判定 pub fn isEmpty(self: *Self) bool { return self.queSize == 0; } // エンキュー pub fn push(self: *Self, num: T) !void { if (self.size() == self.capacity()) { std.debug.print("キューがいっぱいです\n", .{}); return; } // 末尾ポインタを計算し、末尾インデックス + 1 を指す // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする var rear = (self.front + self.queSize) % self.capacity(); // 末尾ノードの後ろに num を追加 self.nums[rear] = num; self.queSize += 1; } // デキュー pub fn pop(self: *Self) T { var num = self.peek(); // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す self.front = (self.front + 1) % self.capacity(); self.queSize -= 1; return num; } // キュー先頭の要素にアクセス pub fn peek(self: *Self) T { if (self.isEmpty()) @panic("キューが空です"); return self.nums[self.front]; } // 配列を返す pub fn toArray(self: *Self) ![]T { // 有効長の範囲内のリスト要素のみを変換 var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; var j: usize = self.front; while (i < self.size()) : ({ i += 1; j += 1; }) { res[i] = self.nums[j % self.capacity()]; } return res; } }; } // Driver Code pub fn main() !void { // キューを初期化 var capacity: usize = 10; var queue = ArrayQueue(i32){}; try queue.init(std.heap.page_allocator, capacity); defer queue.deinit(); // 要素をエンキュー try queue.push(1); try queue.push(3); try queue.push(2); try queue.push(5); try queue.push(4); std.debug.print("キュー queue = ", .{}); inc.PrintUtil.printArray(i32, try queue.toArray()); // キュー先頭の要素にアクセス var peek = queue.peek(); std.debug.print("\n先頭要素 peek = {}", .{peek}); // 要素をデキュー var pop = queue.pop(); std.debug.print("\nデキューした要素 pop = {}、デキュー後 queue = ", .{pop}); inc.PrintUtil.printArray(i32, try queue.toArray()); // キューの長さを取得 var size = queue.size(); std.debug.print("\nキューの長さ size = {}", .{size}); // キューが空かどうかを判定 var is_empty = queue.isEmpty(); std.debug.print("\nキューが空かどうか = {}", .{is_empty}); // 循環配列をテストする var i: i32 = 0; while (i < 10) : (i += 1) { try queue.push(i); _ = queue.pop(); std.debug.print("\n第 {} 回のエンキュー + デキュー後 queue = ", .{i}); inc.PrintUtil.printArray(i32, try queue.toArray()); } _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_stack_and_queue/array_stack.zig ================================================ // File: array_stack.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 配列ベースのスタック pub fn ArrayStack(comptime T: type) type { return struct { const Self = @This(); stack: ?std.ArrayList(T) = null, // コンストラクタ(メモリを確保してスタックを初期化) pub fn init(self: *Self, allocator: std.mem.Allocator) void { if (self.stack == null) { self.stack = std.ArrayList(T).init(allocator); } } // デストラクタ(メモリを解放) pub fn deinit(self: *Self) void { if (self.stack == null) return; self.stack.?.deinit(); } // スタックの長さを取得 pub fn size(self: *Self) usize { return self.stack.?.items.len; } // スタックが空かどうかを判定 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // スタックトップの要素にアクセス pub fn peek(self: *Self) T { if (self.isEmpty()) @panic("スタックが空です"); return self.stack.?.items[self.size() - 1]; } // プッシュ pub fn push(self: *Self, num: T) !void { try self.stack.?.append(num); } // ポップ pub fn pop(self: *Self) T { var num = self.stack.?.pop(); return num; } // ArrayList を返す pub fn toList(self: *Self) std.ArrayList(T) { return self.stack.?; } }; } // Driver Code pub fn main() !void { // スタックを初期化 var stack = ArrayStack(i32){}; stack.init(std.heap.page_allocator); // メモリの遅延解放 defer stack.deinit(); // 要素をプッシュ try stack.push(1); try stack.push(3); try stack.push(2); try stack.push(5); try stack.push(4); std.debug.print("スタック stack = ", .{}); inc.PrintUtil.printList(i32, stack.toList()); // スタックトップの要素にアクセス var peek = stack.peek(); std.debug.print("\nスタックトップ要素 peek = {}", .{peek}); // 要素をポップ var top = stack.pop(); std.debug.print("\nポップした要素 pop = {}、ポップ後 stack = ", .{top}); inc.PrintUtil.printList(i32, stack.toList()); // スタックの長さを取得 var size = stack.size(); std.debug.print("\nスタックの長さ size = {}", .{size}); // スタックが空かどうかを判定 var is_empty = stack.isEmpty(); std.debug.print("\nスタックが空かどうか = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_stack_and_queue/deque.zig ================================================ // File: deque.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // 両端キューを初期化 const L = std.TailQueue(i32); var deque = L{}; // 要素をエンキュー var node1 = L.Node{ .data = 2 }; var node2 = L.Node{ .data = 5 }; var node3 = L.Node{ .data = 4 }; var node4 = L.Node{ .data = 3 }; var node5 = L.Node{ .data = 1 }; deque.append(&node1); // 末尾に追加する deque.append(&node2); deque.append(&node3); deque.prepend(&node4); // 先頭に追加する deque.prepend(&node5); std.debug.print("両端キュー deque = ", .{}); inc.PrintUtil.printQueue(i32, deque); // 要素にアクセス var peek_first = deque.first.?.data; // 先頭要素 std.debug.print("\n先頭要素 peek_first = {}", .{peek_first}); var peek_last = deque.last.?.data; // 末尾要素 std.debug.print("\n末尾要素 peek_last = {}", .{peek_last}); // 要素をデキュー var pop_first = deque.popFirst().?.data; // 先頭要素を取り出す std.debug.print("\n先頭からデキューした要素 pop_first = {}、先頭からデキュー後 deque = ", .{pop_first}); inc.PrintUtil.printQueue(i32, deque); var pop_last = deque.pop().?.data; // 末尾要素を取り出す std.debug.print("\n末尾からデキューした要素 pop_last = {}、末尾からデキュー後 deque = ", .{pop_last}); inc.PrintUtil.printQueue(i32, deque); // 両端キューの長さを取得 var size = deque.len; std.debug.print("\n両端キューの長さ size = {}", .{size}); // 両端キューが空かどうかを判定 var is_empty = if (deque.len == 0) true else false; std.debug.print("\n両端キューが空かどうか = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig ================================================ // File: linkedlist_deque.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 双方向連結リストノード pub fn ListNode(comptime T: type) type { return struct { const Self = @This(); val: T = undefined, // ノード値 next: ?*Self = null, // 後継ノードへのポインタ prev: ?*Self = null, // 前駆ノードへのポインタ // Initialize a list node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.next = null; self.prev = null; } }; } // 双方向連結リストベースの両端キュー pub fn LinkedListDeque(comptime T: type) type { return struct { const Self = @This(); front: ?*ListNode(T) = null, // 先頭ノード front rear: ?*ListNode(T) = null, // 末尾ノード rear que_size: usize = 0, // 両端キューの長さ mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // メモリアロケータ // コンストラクタ(メモリ確保 + キューを初期化) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.front = null; self.rear = null; self.que_size = 0; } // デストラクタ(メモリを解放する) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 両端キューの長さを取得 pub fn size(self: *Self) usize { return self.que_size; } // 両端キューが空かどうかを判定 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // エンキュー操作 pub fn push(self: *Self, num: T, is_front: bool) !void { var node = try self.mem_allocator.create(ListNode(T)); node.init(num); // 連結リストが空なら、front と rear の両方を node に向ける if (self.isEmpty()) { self.front = node; self.rear = node; // 先頭へのエンキュー操作 } else if (is_front) { // node を連結リストの先頭に追加 self.front.?.prev = node; node.next = self.front; self.front = node; // 先頭ノードを更新する // 末尾へのエンキュー操作 } else { // node を連結リストの末尾に追加 self.rear.?.next = node; node.prev = self.rear; self.rear = node; // 末尾ノードを更新する } self.que_size += 1; // キューの長さを更新 } // キュー先頭にエンキュー pub fn pushFirst(self: *Self, num: T) !void { try self.push(num, true); } // キュー末尾にエンキュー pub fn pushLast(self: *Self, num: T) !void { try self.push(num, false); } // デキュー操作 pub fn pop(self: *Self, is_front: bool) T { if (self.isEmpty()) @panic("両端キューが空です"); var val: T = undefined; // キュー先頭からの取り出し if (is_front) { val = self.front.?.val; // 先頭ノードの値を一時保存 // 先頭ノードを削除 var fNext = self.front.?.next; if (fNext != null) { fNext.?.prev = null; self.front.?.next = null; } self.front = fNext; // 先頭ノードを更新する // キュー末尾からの取り出し } else { val = self.rear.?.val; // 末尾ノードの値を一時保存 // 末尾ノードを削除 var rPrev = self.rear.?.prev; if (rPrev != null) { rPrev.?.next = null; self.rear.?.prev = null; } self.rear = rPrev; // 末尾ノードを更新する } self.que_size -= 1; // キューの長さを更新 return val; } // キュー先頭からデキュー pub fn popFirst(self: *Self) T { return self.pop(true); } // キュー末尾からデキュー pub fn popLast(self: *Self) T { return self.pop(false); } // キュー先頭の要素にアクセス pub fn peekFirst(self: *Self) T { if (self.isEmpty()) @panic("両端キューが空です"); return self.front.?.val; } // キュー末尾の要素にアクセス pub fn peekLast(self: *Self) T { if (self.isEmpty()) @panic("両端キューが空です"); return self.rear.?.val; } // 出力用の配列を返す pub fn toArray(self: *Self) ![]T { var node = self.front; var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; while (i < res.len) : (i += 1) { res[i] = node.?.val; node = node.?.next; } return res; } }; } // Driver Code pub fn main() !void { // 両端キューを初期化 var deque = LinkedListDeque(i32){}; try deque.init(std.heap.page_allocator); defer deque.deinit(); try deque.pushLast(3); try deque.pushLast(2); try deque.pushLast(5); std.debug.print("両端キュー deque = ", .{}); inc.PrintUtil.printArray(i32, try deque.toArray()); // 要素にアクセス var peek_first = deque.peekFirst(); std.debug.print("\n先頭要素 peek_first = {}", .{peek_first}); var peek_last = deque.peekLast(); std.debug.print("\n末尾要素 peek_last = {}", .{peek_last}); // 要素をエンキュー try deque.pushLast(4); std.debug.print("\n要素 4 を末尾からエンキューした後 deque = ", .{}); inc.PrintUtil.printArray(i32, try deque.toArray()); try deque.pushFirst(1); std.debug.print("\n要素 1 を先頭からエンキューした後 deque = ", .{}); inc.PrintUtil.printArray(i32, try deque.toArray()); // 要素をデキュー var pop_last = deque.popLast(); std.debug.print("\n末尾から取り出した要素 = {},末尾から取り出した後 deque = ", .{pop_last}); inc.PrintUtil.printArray(i32, try deque.toArray()); var pop_first = deque.popFirst(); std.debug.print("\n先頭から取り出した要素 = {},先頭から取り出した後 deque = ", .{pop_first}); inc.PrintUtil.printArray(i32, try deque.toArray()); // 両端キューの長さを取得 var size = deque.size(); std.debug.print("\n両端キューの長さ size = {}", .{size}); // 両端キューが空かどうかを判定 var is_empty = deque.isEmpty(); std.debug.print("\n両端キューが空かどうか = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig ================================================ // File: linkedlist_queue.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 連結リストベースのキュー pub fn LinkedListQueue(comptime T: type) type { return struct { const Self = @This(); front: ?*inc.ListNode(T) = null, // 先頭ノード front rear: ?*inc.ListNode(T) = null, // 末尾ノード rear que_size: usize = 0, // キューの長さ mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // メモリアロケータ // コンストラクタ(メモリ確保 + キューを初期化) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.front = null; self.rear = null; self.que_size = 0; } // デストラクタ(メモリを解放する) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // キューの長さを取得 pub fn size(self: *Self) usize { return self.que_size; } // キューが空かどうかを判定 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // キュー先頭の要素にアクセス pub fn peek(self: *Self) T { if (self.size() == 0) @panic("キューが空です"); return self.front.?.val; } // エンキュー pub fn push(self: *Self, num: T) !void { // 末尾ノードの後ろに num を追加 var node = try self.mem_allocator.create(inc.ListNode(T)); node.init(num); // キューが空なら、先頭・末尾ノードをともにそのノードに設定 if (self.front == null) { self.front = node; self.rear = node; // キューが空でなければ、そのノードを末尾ノードの後ろに追加 } else { self.rear.?.next = node; self.rear = node; } self.que_size += 1; } // デキュー pub fn pop(self: *Self) T { var num = self.peek(); // 先頭ノードを削除 self.front = self.front.?.next; self.que_size -= 1; return num; } // 連結リストを配列に変換 pub fn toArray(self: *Self) ![]T { var node = self.front; var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; while (i < res.len) : (i += 1) { res[i] = node.?.val; node = node.?.next; } return res; } }; } // Driver Code pub fn main() !void { // キューを初期化 var queue = LinkedListQueue(i32){}; try queue.init(std.heap.page_allocator); defer queue.deinit(); // 要素をエンキュー try queue.push(1); try queue.push(3); try queue.push(2); try queue.push(5); try queue.push(4); std.debug.print("キュー queue = ", .{}); inc.PrintUtil.printArray(i32, try queue.toArray()); // キュー先頭の要素にアクセス var peek = queue.peek(); std.debug.print("\n先頭要素 peek = {}", .{peek}); // 要素をデキュー var pop = queue.pop(); std.debug.print("\nデキューした要素 pop = {}、デキュー後 queue = ", .{pop}); inc.PrintUtil.printArray(i32, try queue.toArray()); // キューの長さを取得 var size = queue.size(); std.debug.print("\nキューの長さ size = {}", .{size}); // キューが空かどうかを判定 var is_empty = queue.isEmpty(); std.debug.print("\nキューが空かどうか = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig ================================================ // File: linkedlist_stack.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 連結リストベースのスタック pub fn LinkedListStack(comptime T: type) type { return struct { const Self = @This(); stack_top: ?*inc.ListNode(T) = null, // 先頭ノードをスタックトップとする stk_size: usize = 0, // スタックの長さ mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // メモリアロケータ // コンストラクタ(メモリを確保してスタックを初期化) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.stack_top = null; self.stk_size = 0; } // デストラクタ(メモリを解放する) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // スタックの長さを取得 pub fn size(self: *Self) usize { return self.stk_size; } // スタックが空かどうかを判定 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // スタックトップの要素にアクセス pub fn peek(self: *Self) T { if (self.size() == 0) @panic("スタックが空です"); return self.stack_top.?.val; } // プッシュ pub fn push(self: *Self, num: T) !void { var node = try self.mem_allocator.create(inc.ListNode(T)); node.init(num); node.next = self.stack_top; self.stack_top = node; self.stk_size += 1; } // ポップ pub fn pop(self: *Self) T { var num = self.peek(); self.stack_top = self.stack_top.?.next; self.stk_size -= 1; return num; } // スタックを配列に変換 pub fn toArray(self: *Self) ![]T { var node = self.stack_top; var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; while (i < res.len) : (i += 1) { res[res.len - i - 1] = node.?.val; node = node.?.next; } return res; } }; } // Driver Code pub fn main() !void { // スタックを初期化 var stack = LinkedListStack(i32){}; try stack.init(std.heap.page_allocator); // メモリの遅延解放 defer stack.deinit(); // 要素をプッシュ try stack.push(1); try stack.push(3); try stack.push(2); try stack.push(5); try stack.push(4); std.debug.print("スタック stack = ", .{}); inc.PrintUtil.printArray(i32, try stack.toArray()); // スタックトップの要素にアクセス var peek = stack.peek(); std.debug.print("\nスタック頂部の要素 top = {}", .{peek}); // 要素をポップ var pop = stack.pop(); std.debug.print("\nポップした要素 pop = {},ポップ後の stack = ", .{pop}); inc.PrintUtil.printArray(i32, try stack.toArray()); // スタックの長さを取得 var size = stack.size(); std.debug.print("\nスタックの長さ size = {}", .{size}); // スタックが空かどうかを判定 var is_empty = stack.isEmpty(); std.debug.print("\nスタックが空かどうか = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_stack_and_queue/queue.zig ================================================ // File: queue.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // キューを初期化 const L = std.TailQueue(i32); var queue = L{}; // 要素をエンキュー var node1 = L.Node{ .data = 1 }; var node2 = L.Node{ .data = 3 }; var node3 = L.Node{ .data = 2 }; var node4 = L.Node{ .data = 5 }; var node5 = L.Node{ .data = 4 }; queue.append(&node1); queue.append(&node2); queue.append(&node3); queue.append(&node4); queue.append(&node5); std.debug.print("キュー queue = ", .{}); inc.PrintUtil.printQueue(i32, queue); // キュー先頭の要素にアクセス var peek = queue.first.?.data; std.debug.print("\n先頭要素 peek = {}", .{peek}); // 要素をデキュー var pop = queue.popFirst().?.data; std.debug.print("\nデキューした要素 pop = {}、デキュー後 queue = ", .{pop}); inc.PrintUtil.printQueue(i32, queue); // キューの長さを取得 var size = queue.len; std.debug.print("\nキューの長さ size = {}", .{size}); // キューが空かどうかを判定 var is_empty = if (queue.len == 0) true else false; std.debug.print("\nキューが空かどうか = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_stack_and_queue/stack.zig ================================================ // File: stack.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // スタックを初期化する // zig では、ArrayList をスタックとして使うことが推奨される var stack = std.ArrayList(i32).init(std.heap.page_allocator); // メモリの遅延解放 defer stack.deinit(); // 要素をプッシュ try stack.append(1); try stack.append(3); try stack.append(2); try stack.append(5); try stack.append(4); std.debug.print("スタック stack = ", .{}); inc.PrintUtil.printList(i32, stack); // スタックトップの要素にアクセス var peek = stack.items[stack.items.len - 1]; std.debug.print("\nスタックトップ要素 peek = {}", .{peek}); // 要素をポップ var pop = stack.pop(); std.debug.print("\nポップした要素 pop = {},ポップ後の stack = ", .{pop}); inc.PrintUtil.printList(i32, stack); // スタックの長さを取得 var size = stack.items.len; std.debug.print("\nスタックの長さ size = {}", .{size}); // スタックが空かどうかを判定 var is_empty = if (stack.items.len == 0) true else false; std.debug.print("\nスタックが空かどうか = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_tree/avl_tree.zig ================================================ // File: avl_tree.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // AVL 木 pub fn AVLTree(comptime T: type) type { return struct { const Self = @This(); root: ?*inc.TreeNode(T) = null, // 根ノード mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // メモリアロケータ // コンストラクタ pub fn init(self: *Self, allocator: std.mem.Allocator) void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } } // デストラクタメソッド pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // ノードの高さを取得 fn height(self: *Self, node: ?*inc.TreeNode(T)) i32 { _ = self; // 空ノードの高さは -1、葉ノードの高さは 0 return if (node == null) -1 else node.?.height; } // ノードの高さを更新する fn updateHeight(self: *Self, node: ?*inc.TreeNode(T)) void { // ノードの高さは最も高い部分木の高さ + 1 に等しい node.?.height = @max(self.height(node.?.left), self.height(node.?.right)) + 1; } // 平衡係数を取得 fn balanceFactor(self: *Self, node: ?*inc.TreeNode(T)) i32 { // 空ノードの平衡係数は 0 if (node == null) return 0; // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ return self.height(node.?.left) - self.height(node.?.right); } // 右回転 fn rightRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { var child = node.?.left; var grandChild = child.?.right; // child を支点として node を右回転させる child.?.right = node; node.?.left = grandChild; // ノードの高さを更新する self.updateHeight(node); self.updateHeight(child); // 回転後の部分木の根ノードを返す return child; } // 左回転 fn leftRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { var child = node.?.right; var grandChild = child.?.left; // child を支点として node を左回転させる child.?.left = node; node.?.right = grandChild; // ノードの高さを更新する self.updateHeight(node); self.updateHeight(child); // 回転後の部分木の根ノードを返す return child; } // 回転操作を行い、この部分木の平衡を回復する fn rotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { // ノード node の平衡係数を取得 var balance_factor = self.balanceFactor(node); // 左に偏った木 if (balance_factor > 1) { if (self.balanceFactor(node.?.left) >= 0) { // 右回転 return self.rightRotate(node); } else { // 左回転してから右回転 node.?.left = self.leftRotate(node.?.left); return self.rightRotate(node); } } // 右に偏った木 if (balance_factor < -1) { if (self.balanceFactor(node.?.right) <= 0) { // 左回転 return self.leftRotate(node); } else { // 右回転してから左回転 node.?.right = self.rightRotate(node.?.right); return self.leftRotate(node); } } // 平衡木なので回転不要、そのまま返す return node; } // ノードを挿入 fn insert(self: *Self, val: T) !void { self.root = (try self.insertHelper(self.root, val)).?; } // ノードを再帰的に挿入する(補助メソッド) fn insertHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) !?*inc.TreeNode(T) { var node = node_; if (node == null) { var tmp_node = try self.mem_allocator.create(inc.TreeNode(T)); tmp_node.init(val); return tmp_node; } // 1. 挿入位置を探索してノードを挿入 if (val < node.?.val) { node.?.left = try self.insertHelper(node.?.left, val); } else if (val > node.?.val) { node.?.right = try self.insertHelper(node.?.right, val); } else { return node; // 重複ノードは挿入せず、そのまま返す } self.updateHeight(node); // ノードの高さを更新する // 2. 回転操作を行い、部分木の平衡を回復する node = self.rotate(node); // 部分木の根ノードを返す return node; } // ノードを削除 fn remove(self: *Self, val: T) void { self.root = self.removeHelper(self.root, val).?; } // ノードを再帰的に削除する(補助メソッド) fn removeHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) ?*inc.TreeNode(T) { var node = node_; if (node == null) return null; // 1. ノードを探索して削除 if (val < node.?.val) { node.?.left = self.removeHelper(node.?.left, val); } else if (val > node.?.val) { node.?.right = self.removeHelper(node.?.right, val); } else { if (node.?.left == null or node.?.right == null) { var child = if (node.?.left != null) node.?.left else node.?.right; // 子ノード数 = 0 の場合、node をそのまま削除して返す if (child == null) { return null; // 子ノード数 = 1 の場合、node をそのまま削除する } else { node = child; } } else { // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える var temp = node.?.right; while (temp.?.left != null) { temp = temp.?.left; } node.?.right = self.removeHelper(node.?.right, temp.?.val); node.?.val = temp.?.val; } } self.updateHeight(node); // ノードの高さを更新する // 2. 回転操作を行い、部分木の平衡を回復する node = self.rotate(node); // 部分木の根ノードを返す return node; } // ノードを探索 fn search(self: *Self, val: T) ?*inc.TreeNode(T) { var cur = self.root; // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 目標ノードは cur の右部分木にある if (cur.?.val < val) { cur = cur.?.right; // 目標ノードは cur の左部分木にある } else if (cur.?.val > val) { cur = cur.?.left; // 目標ノードが見つかったらループを抜ける } else { break; } } // 目標ノードを返す return cur; } }; } pub fn testInsert(comptime T: type, tree_: *AVLTree(T), val: T) !void { var tree = tree_; try tree.insert(val); std.debug.print("\nノード {} を挿入した後,AVL 木は\n", .{val}); try inc.PrintUtil.printTree(tree.root, null, false); } pub fn testRemove(comptime T: type, tree_: *AVLTree(T), val: T) void { var tree = tree_; tree.remove(val); std.debug.print("\nノード {} を削除した後,AVL 木は\n", .{val}); try inc.PrintUtil.printTree(tree.root, null, false); } // Driver Code pub fn main() !void { // 空の AVL 木を初期化する var avl_tree = AVLTree(i32){}; avl_tree.init(std.heap.page_allocator); defer avl_tree.deinit(); // ノードを挿入する // ノード挿入後に AVL 木がどのように平衡を保つかに注目 try testInsert(i32, &avl_tree, 1); try testInsert(i32, &avl_tree, 2); try testInsert(i32, &avl_tree, 3); try testInsert(i32, &avl_tree, 4); try testInsert(i32, &avl_tree, 5); try testInsert(i32, &avl_tree, 8); try testInsert(i32, &avl_tree, 7); try testInsert(i32, &avl_tree, 9); try testInsert(i32, &avl_tree, 10); try testInsert(i32, &avl_tree, 6); // 重複ノードを挿入する try testInsert(i32, &avl_tree, 7); // ノードを削除する // ノード削除後に AVL 木がどのように平衡を保つかに注目 testRemove(i32, &avl_tree, 8); // 次数 0 のノードを削除する testRemove(i32, &avl_tree, 5); // 次数 1 のノードを削除する testRemove(i32, &avl_tree, 4); // 次数 2 のノードを削除する // ノードを探索 var node = avl_tree.search(7).?; std.debug.print("\n見つかったノードオブジェクトは {any},ノードの値 = {}\n", .{node, node.val}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_tree/binary_search_tree.zig ================================================ // File: binary_search_tree.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 二分探索木 pub fn BinarySearchTree(comptime T: type) type { return struct { const Self = @This(); root: ?*inc.TreeNode(T) = null, mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // メモリアロケータ // コンストラクタ pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []T) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } std.mem.sort(T, nums, {}, comptime std.sort.asc(T)); // 配列をソート self.root = try self.buildTree(nums, 0, nums.len - 1); // 二分探索木を構築 } // デストラクタメソッド pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 二分探索木を構築 fn buildTree(self: *Self, nums: []T, i: usize, j: usize) !?*inc.TreeNode(T) { if (i > j) return null; // 配列の中央ノードを根ノードとする var mid = i + (j - i) / 2; var node = try self.mem_allocator.create(inc.TreeNode(T)); node.init(nums[mid]); // 左部分木と右部分木を再帰的に構築する if (mid >= 1) node.left = try self.buildTree(nums, i, mid - 1); node.right = try self.buildTree(nums, mid + 1, j); return node; } // 二分木の根ノードを取得 fn getRoot(self: *Self) ?*inc.TreeNode(T) { return self.root; } // ノードを探索 fn search(self: *Self, num: T) ?*inc.TreeNode(T) { var cur = self.root; // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 目標ノードは cur の右部分木にある if (cur.?.val < num) { cur = cur.?.right; // 目標ノードは cur の左部分木にある } else if (cur.?.val > num) { cur = cur.?.left; // 目標ノードが見つかったらループを抜ける } else { break; } } // 目標ノードを返す return cur; } // ノードを挿入 fn insert(self: *Self, num: T) !void { // 木が空なら、根ノードを初期化する if (self.root == null) { self.root = try self.mem_allocator.create(inc.TreeNode(T)); return; } var cur = self.root; var pre: ?*inc.TreeNode(T) = null; // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 重複ノードが見つかったら、直ちに返す if (cur.?.val == num) return; pre = cur; // 挿入位置は cur の右部分木にある if (cur.?.val < num) { cur = cur.?.right; // 挿入位置は cur の左部分木にある } else { cur = cur.?.left; } } // ノードを挿入 var node = try self.mem_allocator.create(inc.TreeNode(T)); node.init(num); if (pre.?.val < num) { pre.?.right = node; } else { pre.?.left = node; } } // ノードを削除 fn remove(self: *Self, num: T) void { // 木が空なら、そのまま早期リターンする if (self.root == null) return; var cur = self.root; var pre: ?*inc.TreeNode(T) = null; // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { // 削除対象のノードが見つかったら、ループを抜ける if (cur.?.val == num) break; pre = cur; // 削除対象ノードは cur の右部分木にある if (cur.?.val < num) { cur = cur.?.right; // 削除対象ノードは cur の左部分木にある } else { cur = cur.?.left; } } // 削除対象ノードがなければそのまま返す if (cur == null) return; // 子ノード数 = 0 or 1 if (cur.?.left == null or cur.?.right == null) { // 子ノード数が 0 / 1 のとき、child = null / その子ノード var child = if (cur.?.left != null) cur.?.left else cur.?.right; // ノード cur を削除する if (pre.?.left == cur) { pre.?.left = child; } else { pre.?.right = child; } // 子ノード数 = 2 } else { // 中順走査における cur の次ノードを取得 var tmp = cur.?.right; while (tmp.?.left != null) { tmp = tmp.?.left; } var tmp_val = tmp.?.val; // ノード tmp を再帰的に削除 self.remove(tmp.?.val); // tmp で cur を上書きする cur.?.val = tmp_val; } } }; } // Driver Code pub fn main() !void { // 二分木を初期化 var nums = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; var bst = BinarySearchTree(i32){}; try bst.init(std.heap.page_allocator, &nums); defer bst.deinit(); std.debug.print("初期化された二分木は\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); // ノードを探索 var node = bst.search(7); std.debug.print("\n見つかったノードオブジェクトは {any},ノードの値 = {}\n", .{node, node.?.val}); // ノードを挿入 try bst.insert(16); std.debug.print("\nノード 16 を挿入した後,二分木は\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); // ノードを削除 bst.remove(1); std.debug.print("\nノード 1 を削除した後,二分木は\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); bst.remove(2); std.debug.print("\nノード 2 を削除した後,二分木は\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); bst.remove(4); std.debug.print("\nノード 4 を削除した後,二分木は\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_tree/binary_tree.zig ================================================ // File: binary_tree.zig // Created Time: 2023-01-14 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // 二分木を初期化する // ノードを初期化する var n1 = inc.TreeNode(i32){ .val = 1 }; var n2 = inc.TreeNode(i32){ .val = 2 }; var n3 = inc.TreeNode(i32){ .val = 3 }; var n4 = inc.TreeNode(i32){ .val = 4 }; var n5 = inc.TreeNode(i32){ .val = 5 }; // ノード間の参照(ポインタ)を構築する n1.left = &n2; n1.right = &n3; n2.left = &n4; n2.right = &n5; std.debug.print("二分木を初期化\n", .{}); try inc.PrintUtil.printTree(&n1, null, false); // ノードの挿入と削除 var p = inc.TreeNode(i32){ .val = 0 }; // n1 -> n2 の間にノード P を挿入 n1.left = &p; p.left = &n2; std.debug.print("ノード P を挿入した後\n", .{}); try inc.PrintUtil.printTree(&n1, null, false); // ノードを削除 n1.left = &n2; std.debug.print("ノード P を削除した後\n", .{}); try inc.PrintUtil.printTree(&n1, null, false); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_tree/binary_tree_bfs.zig ================================================ // File: binary_tree_bfs.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // レベル順走査 fn levelOrder(comptime T: type, mem_allocator: std.mem.Allocator, root: *inc.TreeNode(T)) !std.ArrayList(T) { // キューを初期化し、ルートノードを追加する const L = std.TailQueue(*inc.TreeNode(T)); var queue = L{}; var root_node = try mem_allocator.create(L.Node); root_node.data = root; queue.append(root_node); // 走査順序を保存するためのリストを初期化する var list = std.ArrayList(T).init(std.heap.page_allocator); while (queue.len > 0) { var queue_node = queue.popFirst().?; // デキュー var node = queue_node.data; try list.append(node.val); // ノードの値を保存する if (node.left != null) { var tmp_node = try mem_allocator.create(L.Node); tmp_node.data = node.left.?; queue.append(tmp_node); // 左子ノードをキューに追加 } if (node.right != null) { var tmp_node = try mem_allocator.create(L.Node); tmp_node.data = node.right.?; queue.append(tmp_node); // 右子ノードをキューに追加 } } return list; } // Driver Code pub fn main() !void { // メモリアロケータを初期化する var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // 二分木を初期化 // ここでは、配列から直接二分木を生成する関数を利用する var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); std.debug.print("二分木を初期化\n", .{}); try inc.PrintUtil.printTree(root, null, false); // レベル順走査 var list = try levelOrder(i32, mem_allocator, root.?); defer list.deinit(); std.debug.print("\nレベル順走査のノード出力シーケンス = ", .{}); inc.PrintUtil.printList(i32, list); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/chapter_tree/binary_tree_dfs.zig ================================================ // File: binary_tree_dfs.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); var list = std.ArrayList(i32).init(std.heap.page_allocator); // 先行順走査 fn preOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { if (root == null) return; // 訪問順序:根ノード -> 左部分木 -> 右部分木 try list.append(root.?.val); try preOrder(T, root.?.left); try preOrder(T, root.?.right); } // 中順走査 fn inOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { if (root == null) return; // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 try inOrder(T, root.?.left); try list.append(root.?.val); try inOrder(T, root.?.right); } // 後順走査 fn postOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { if (root == null) return; // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード try postOrder(T, root.?.left); try postOrder(T, root.?.right); try list.append(root.?.val); } // Driver Code pub fn main() !void { // メモリアロケータを初期化する var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // 二分木を初期化 // ここでは、配列から直接二分木を生成する関数を利用する var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); std.debug.print("二分木を初期化\n", .{}); try inc.PrintUtil.printTree(root, null, false); // 先行順走査 list.clearRetainingCapacity(); try preOrder(i32, root); std.debug.print("\n前順走査のノード出力シーケンス = ", .{}); inc.PrintUtil.printList(i32, list); // 中順走査 list.clearRetainingCapacity(); try inOrder(i32, root); std.debug.print("\n中順走査のノード出力シーケンス = ", .{}); inc.PrintUtil.printList(i32, list); // 後順走査 list.clearRetainingCapacity(); try postOrder(i32, root); std.debug.print("\n後順走査のノード出力シーケンス = ", .{}); inc.PrintUtil.printList(i32, list); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ja/codes/zig/include/PrintUtil.zig ================================================ // File: PrintUtil.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); pub const ListUtil = @import("ListNode.zig"); pub const ListNode = ListUtil.ListNode; pub const TreeUtil = @import("TreeNode.zig"); pub const TreeNode = TreeUtil.TreeNode; // キューを出力する pub fn printQueue(comptime T: type, queue: std.TailQueue(T)) void { var node = queue.first; std.debug.print("[", .{}); var i: i32 = 0; while (node != null) : (i += 1) { var data = node.?.data; std.debug.print("{}{s}", .{ data, if (i == queue.len - 1) "]" else ", " }); node = node.?.next; } } // ハッシュテーブルを出力 pub fn printHashMap(comptime TKey: type, comptime TValue: type, map: std.AutoHashMap(TKey, TValue)) void { var it = map.iterator(); while (it.next()) |kv| { var key = kv.key_ptr.*; var value = kv.value_ptr.*; std.debug.print("{} -> {s}\n", .{ key, value }); } } // ヒープを出力 pub fn printHeap(comptime T: type, mem_allocator: std.mem.Allocator, queue: anytype) !void { var arr = queue.items; var len = queue.len; std.debug.print("ヒープの配列表現:", .{}); printArray(T, arr[0..len]); std.debug.print("\nヒープの木構造表現:\n", .{}); var root = try TreeUtil.arrToTree(T, mem_allocator, arr[0..len]); try printTree(root, null, false); } ================================================ FILE: ja/codes/zig/include/include.zig ================================================ // File: include.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) pub const PrintUtil = @import("PrintUtil.zig"); pub const TreeUtil = @import("TreeNode.zig"); pub const TreeNode = TreeUtil.TreeNode; ================================================ FILE: ja/codes/zig/main.zig ================================================ const std = @import("std"); const iteration = @import("chapter_computational_complexity/iteration.zig"); const recursion = @import("chapter_computational_complexity/recursion.zig"); const time_complexity = @import("chapter_computational_complexity/time_complexity.zig"); const space_complexity = @import("chapter_computational_complexity/space_complexity.zig"); const worst_best_time_complexity = @import("chapter_computational_complexity/worst_best_time_complexity.zig"); const array = @import("chapter_array_and_linkedlist/array.zig"); const linked_list = @import("chapter_array_and_linkedlist/linked_list.zig"); const list = @import("chapter_array_and_linkedlist/list.zig"); const my_list = @import("chapter_array_and_linkedlist/my_list.zig"); pub fn main() !void { try iteration.run(); recursion.run(); time_complexity.run(); try space_complexity.run(); worst_best_time_complexity.run(); try array.run(); linked_list.run(); try list.run(); try my_list.run(); } ================================================ FILE: ja/codes/zig/utils/ListNode.zig ================================================ // File: ListNode.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // 連結リストノード pub fn ListNode(comptime T: type) type { return struct { const Self = @This(); val: T = 0, next: ?*Self = null, // Initialize a list node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.next = null; } }; } // リストを連結リストにデシリアライズする pub fn listToLinkedList(comptime T: type, allocator: std.mem.Allocator, list: std.ArrayList(T)) !?*ListNode(T) { var dum = try allocator.create(ListNode(T)); dum.init(0); var head = dum; for (list.items) |val| { var tmp = try allocator.create(ListNode(T)); tmp.init(val); head.next = tmp; head = head.next.?; } return dum.next; } // 配列をデシリアライズして連結リストに変換する pub fn arrToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*ListNode(T) { var dum = try mem_allocator.create(ListNode(T)); dum.init(0); var head = dum; for (arr) |val| { var tmp = try mem_allocator.create(ListNode(T)); tmp.init(val); head.next = tmp; head = head.next.?; } return dum.next; } ================================================ FILE: ja/codes/zig/utils/TreeNode.zig ================================================ // File: TreeNode.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // 二分木ノード pub fn TreeNode(comptime T: type) type { return struct { const Self = @This(); val: T = undefined, // ノード値 height: i32 = undefined, // ノードの高さ left: ?*Self = null, // 左の子ノードへのポインタ right: ?*Self = null, // 右の子ノードへのポインタ // Initialize a tree node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.height = 0; self.left = null; self.right = null; } }; } // 配列をデシリアライズして二分木に変換する pub fn arrToTree(comptime T: type, allocator: std.mem.Allocator, arr: []T) !?*TreeNode(T) { if (arr.len == 0) return null; var root = try allocator.create(TreeNode(T)); root.init(arr[0]); const L = std.TailQueue(*TreeNode(T)); var que = L{}; var root_node = try allocator.create(L.Node); root_node.data = root; que.append(root_node); var index: usize = 0; while (que.len > 0) { const que_node = que.popFirst().?; var node = que_node.data; index += 1; if (index >= arr.len) break; if (index < arr.len) { var tmp = try allocator.create(TreeNode(T)); tmp.init(arr[index]); node.left = tmp; var tmp_node = try allocator.create(L.Node); tmp_node.data = node.left.?; que.append(tmp_node); } index += 1; if (index >= arr.len) break; if (index < arr.len) { var tmp = try allocator.create(TreeNode(T)); tmp.init(arr[index]); node.right = tmp; var tmp_node = try allocator.create(L.Node); tmp_node.data = node.right.?; que.append(tmp_node); } } return root; } ================================================ FILE: ja/codes/zig/utils/format.zig ================================================ // File: format.zig // Created Time: 2025-07-19 // Author: CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const ListNode = @import("ListNode.zig").ListNode; const TreeNode = @import("TreeNode.zig").TreeNode; pub fn slice(items: anytype) SliceFormatter(@TypeOf(items)) { return .{ .items = items }; } pub fn SliceFormatter(comptime SliceType: type) type { return struct { const Self = @This(); items: SliceType, pub fn format( self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) !void { try writer.writeAll("["); if (self.items.len > 0) { for (self.items, 0..) |item, i| { try std.fmt.format(writer, "{}", .{item}); if (i != self.items.len - 1) { try writer.writeAll(", "); } } } try writer.writeAll("]"); } }; } pub fn linkedList(comptime T: type, head: *const ListNode(T)) LinkedListFormatter(T) { return .{ .head = head }; } pub fn LinkedListFormatter(comptime T: type) type { return struct { const Self = @This(); head: *const ListNode(T), pub fn format( self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) !void { try printLinkedList(self.head, writer); } pub fn printLinkedList(head: *const ListNode(T), writer: anytype) !void { try std.fmt.format(writer, "{}", .{head.val}); if (head.next) |next_node| { try writer.writeAll("->"); try printLinkedList(next_node, writer); } } }; } pub fn tree(comptime T: type, root: ?*const TreeNode(T)) TreeFormatter(T) { return .{ .root = root }; } pub fn TreeFormatter(comptime T: type) type { return struct { const Self = @This(); root: ?*const TreeNode(T), pub fn format( self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) !void { try printTree(self.root, null, false, writer); } // 二分木を出力 fn printTree(root: ?*const TreeNode(T), prev: ?*Trunk, isRight: bool, writer: anytype) !void { if (root == null) { return; } var prev_str = " "; var trunk = Trunk{ .prev = prev, .str = prev_str }; try printTree(root.?.right, &trunk, true, writer); if (prev == null) { trunk.str = "———"; } else if (isRight) { trunk.str = "/———"; prev_str = " |"; } else { trunk.str = "\\———"; prev.?.str = prev_str; } try showTrunks(&trunk, writer); try std.fmt.format(writer, "{d}\n", .{root.?.val}); if (prev) |_| { prev.?.str = prev_str; } trunk.str = " |"; try printTree(root.?.left, &trunk, false, writer); } // 二分木を出力 // This tree printer is borrowed from TECHIE DELIGHT // https://www.techiedelight.com/c-program-print-binary-tree/ const Trunk = struct { prev: ?*Trunk = null, str: []const u8 = undefined, pub fn init(self: *Trunk, prev: ?*Trunk, str: []const u8) void { self.prev = prev; self.str = str; } }; pub fn showTrunks(p: ?*Trunk, writer: anytype) !void { if (p == null) return; try showTrunks(p.?.prev, writer); try std.fmt.format(writer, "{s}", .{p.?.str}); } }; } ================================================ FILE: ja/codes/zig/utils/utils.zig ================================================ // File: format.zig // Created Time: 2025-07-15 // Author: CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); pub const fmt = @import("format.zig"); pub const ListNode = @import("ListNode.zig").ListNode; pub const TreeNode = @import("TreeNode.zig").TreeNode; ================================================ FILE: ja/docs/chapter_appendix/contribution.md ================================================ # 一緒に制作に参加しましょう 著者の力には限りがあるため、本書にはどうしても一部の漏れや誤りが含まれる可能性があります。ご了承ください。誤字、リンク切れ、内容の欠落、表現の曖昧さ、説明の不明瞭さ、文章構成の不適切さなどの問題を見つけた場合は、ぜひ修正にご協力ください。読者により良い学習リソースを提供できます。 すべての[寄稿者](https://github.com/krahets/hello-algo/graphs/contributors)の GitHub ID は、本書のリポジトリ、Web 版、PDF 版のホームページに掲載され、オープンソースコミュニティへの惜しみない貢献に感謝を表します。 !!! success "オープンソースの魅力" 紙の書籍では、2 回の増刷の間隔が長くなりがちで、内容更新は非常に不便です。 一方、このオープンソース書籍では、内容更新のサイクルは数日、場合によっては数時間にまで短縮されています。 ### 内容の微調整 以下の図のように、各ページの右上には「編集アイコン」があります。次の手順で本文やコードを修正できます。 1. 「編集アイコン」をクリックし、「このリポジトリを Fork する必要があります」と表示された場合は、その操作を承認してください。 2. Markdown のソースファイルを修正し、内容が正しいことを確認したうえで、できるだけ書式の統一を保ってください。 3. ページ下部に修正内容の説明を入力し、その後「Propose file change」ボタンをクリックします。ページ遷移後、「Create pull request」ボタンをクリックするとプルリクエストを作成できます。 ![ページ編集ボタン](contribution.assets/edit_markdown.png) 画像は直接修正できないため、新しい [Issue](https://github.com/krahets/hello-algo/issues) を作成するかコメントで問題を説明してください。できるだけ早く描き直して差し替えます。 ### コンテンツ制作 コードを他のプログラミング言語へ翻訳することや、記事内容を拡充することなど、このオープンソースプロジェクトへの参加に興味がある場合は、以下の Pull Request ワークフローに従ってください。 1. GitHub にログインし、本書の[コードリポジトリ](https://github.com/krahets/hello-algo)を個人アカウントに Fork します。 2. Fork したリポジトリのページに入り、`git clone` コマンドを使ってリポジトリをローカルにクローンします。 3. ローカルでコンテンツを作成し、完全なテストを行ってコードの正しさを確認します。 4. ローカルで行った変更を Commit し、その後リモートリポジトリへ Push します。 5. リポジトリのページを更新し、「Create pull request」ボタンをクリックするとプルリクエストを作成できます。 ### Docker デプロイ `hello-algo` のルートディレクトリで以下の Docker スクリプトを実行すると、`http://localhost:8000` で本プロジェクトにアクセスできます。 ```shell docker-compose up -d ``` 以下のコマンドでデプロイを削除できます。 ```shell docker-compose down ``` ================================================ FILE: ja/docs/chapter_appendix/index.md ================================================ # 付録 ![付録](../assets/covers/chapter_appendix.jpg) ================================================ FILE: ja/docs/chapter_appendix/installation.md ================================================ # プログラミング環境のインストール ## IDE のインストール オープンソースで軽量な VS Code をローカルの統合開発環境(IDE)として使用することを推奨します。[VS Code 公式サイト](https://code.visualstudio.com/) にアクセスし、使用している OS に応じたバージョンの VS Code をダウンロードしてインストールしてください。 ![公式サイトから VS Code をダウンロード](installation.assets/vscode_installation.png) VS Code には強力な拡張機能のエコシステムがあり、ほとんどのプログラミング言語の実行とデバッグをサポートしています。Python を例にすると、「Python Extension Pack」拡張機能をインストールした後、Python コードをデバッグできるようになります。インストール手順を以下に示します。 ![VS Code 拡張機能のインストール](installation.assets/vscode_extension_installation.png) ## 言語環境のインストール ### Python 環境 1. [Miniconda3](https://docs.conda.io/en/latest/miniconda.html) をダウンロードしてインストールします。Python 3.10 以降が必要です。 2. VS Code の拡張機能マーケットプレイスで `python` を検索し、Python Extension Pack をインストールします。 3. (任意)コマンドラインで `pip install black` を入力し、コード整形ツールをインストールします。 ### C/C++ 環境 1. Windows システムでは [MinGW](https://sourceforge.net/projects/mingw-w64/files/) をインストールする必要があります([設定チュートリアル](https://blog.csdn.net/qq_33698226/article/details/129031241))。MacOS には Clang が標準搭載されているため、追加インストールは不要です。 2. VS Code の拡張機能マーケットプレイスで `c++` を検索し、C/C++ Extension Pack をインストールします。 3. (任意)Settings ページを開き、コード整形オプション `Clang_format_fallback Style` を検索して、`{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }` に設定します。 ### Java 環境 1. [OpenJDK](https://jdk.java.net/18/) をダウンロードしてインストールします(バージョンは JDK 9 より新しい必要があります)。 2. VS Code の拡張機能マーケットプレイスで `java` を検索し、Extension Pack for Java をインストールします。 ### C# 環境 1. [.Net 8.0](https://dotnet.microsoft.com/en-us/download) をダウンロードしてインストールします。 2. VS Code の拡張機能マーケットプレイスで `C# Dev Kit` を検索し、C# Dev Kit をインストールします([設定チュートリアル](https://code.visualstudio.com/docs/csharp/get-started))。 3. Visual Studio を使用することもできます([インストール手順](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022))。 ### Go 環境 1. [go](https://go.dev/dl/) をダウンロードしてインストールします。 2. VS Code の拡張機能マーケットプレイスで `go` を検索し、Go をインストールします。 3. ショートカットキー `Ctrl + Shift + P` を押してコマンドパレットを開き、go と入力して `Go: Install/Update Tools` を選択し、すべてにチェックを入れてインストールします。 ### Swift 環境 1. [Swift](https://www.swift.org/download/) をダウンロードしてインストールします。 2. VS Code の拡張機能マーケットプレイスで `swift` を検索し、[Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang) をインストールします。 ### JavaScript 環境 1. [Node.js](https://nodejs.org/en/) をダウンロードしてインストールします。 2. (任意)VS Code の拡張機能マーケットプレイスで `Prettier` を検索し、コード整形ツールをインストールします。 ### TypeScript 環境 1. JavaScript 環境と同じ手順でインストールします。 2. [TypeScript Execute (tsx)](https://github.com/privatenumber/tsx?tab=readme-ov-file#global-installation) をインストールします。 3. VS Code の拡張機能マーケットプレイスで `typescript` を検索し、[Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) をインストールします。 ### Dart 環境 1. [Dart](https://dart.dev/get-dart) をダウンロードしてインストールします。 2. VS Code の拡張機能マーケットプレイスで `dart` を検索し、[Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code) をインストールします。 ### Rust 環境 1. [Rust](https://www.rust-lang.org/tools/install) をダウンロードしてインストールします。 2. VS Code の拡張機能マーケットプレイスで `rust` を検索し、[rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) をインストールします。 ================================================ FILE: ja/docs/chapter_appendix/terminology.md ================================================ # 用語集 以下の表は、本書に登場する重要な用語を一覧にしたものです。特に次の点に注意してください。 - 名詞の英語表現も覚えておくと、英語文献を読む際に役立ちます。 - 一部の名詞は、簡体字中国語と繁体字中国語で呼び方が異なります。

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

| English | 日本語 | 日本語 | | ------------------------------ | -------------- | -------------- | | algorithm | アルゴリズム | アルゴリズム | | data structure | データ構造 | データ構造 | | code | コード | コード | | file | ファイル | ファイル | | function | 関数 | 関数 | | method | メソッド | メソッド | | variable | 変数 | 変数 | | asymptotic complexity analysis | 漸近計算量解析 | 漸近計算量解析 | | time complexity | 時間計算量 | 時間計算量 | | space complexity | 空間計算量 | 空間計算量 | | loop | ループ | ループ | | iteration | 反復 | 反復 | | recursion | 再帰 | 再帰 | | tail recursion | 末尾再帰 | 末尾再帰 | | recursion tree | 再帰木 | 再帰木 | | big-$O$ notation | ビッグオー記法 | ビッグオー記法 | | asymptotic upper bound | 漸近上界 | 漸近上界 | | sign-magnitude | 符号絶対値表現 | 符号絶対値表現 | | 1’s complement | 1の補数 | 1の補数 | | 2’s complement | 2の補数 | 2の補数 | | array | 配列 | 配列 | | index | インデックス | インデックス | | linked list | 連結リスト | 連結リスト | | linked list node, list node | 連結リストノード | 連結リストノード | | head node | 先頭ノード | 先頭ノード | | tail node | 末尾ノード | 末尾ノード | | list | リスト | リスト | | dynamic array | 動的配列 | 動的配列 | | hard disk | ハードディスク | ハードディスク | | random-access memory (RAM) | メモリ | メモリ | | cache memory | キャッシュ | キャッシュ | | cache miss | キャッシュミス | キャッシュミス | | cache hit rate | キャッシュヒット率 | キャッシュヒット率 | | stack | スタック | スタック | | top of the stack | スタックトップ | スタックトップ | | bottom of the stack | スタックボトム | スタックボトム | | queue | キュー | キュー | | double-ended queue | 両端キュー | 両端キュー | | front of the queue | キュー先頭 | キュー先頭 | | rear of the queue | キュー末尾 | キュー末尾 | | hash table | ハッシュテーブル | ハッシュテーブル | | hash set | ハッシュ集合 | ハッシュ集合 | | bucket | バケット | バケット | | hash function | ハッシュ関数 | ハッシュ関数 | | hash collision | ハッシュ衝突 | ハッシュ衝突 | | load factor | 負荷率 | 負荷率 | | separate chaining | 連鎖アドレス法 | 連鎖アドレス法 | | open addressing | オープンアドレス法 | オープンアドレス法 | | linear probing | 線形探索 | 線形探索 | | lazy deletion | 遅延削除 | 遅延削除 | | binary tree | 二分木 | 二分木 | | tree node | ノード | ノード | | left-child node | 左子ノード | 左子ノード | | right-child node | 右子ノード | 右子ノード | | parent node | 親ノード | 親ノード | | left subtree | 左部分木 | 左部分木 | | right subtree | 右部分木 | 右部分木 | | root node | 根ノード | 根ノード | | leaf node | 葉ノード | 葉ノード | | edge | 辺 | 辺 | | level | レベル | レベル | | degree | 次数 | 次数 | | height | 高さ | 高さ | | depth | 深さ | 深さ | | perfect binary tree | 完備二分木 | 完備二分木 | | complete binary tree | 完全二分木 | 完全二分木 | | full binary tree | 満二分木 | 満二分木 | | balanced binary tree | 平衡二分木 | 平衡二分木 | | binary search tree | 二分探索木 | 二分探索木 | | AVL tree | AVL 木 | AVL 木 | | red-black tree | 赤黒木 | 赤黒木 | | level-order traversal | レベル順走査 | レベル順走査 | | breadth-first traversal | 幅優先走査 | 幅優先走査 | | depth-first traversal | 深さ優先走査 | 深さ優先走査 | | binary search tree | 二分探索木 | 二分探索木 | | balanced binary search tree | 平衡二分探索木 | 平衡二分探索木 | | balance factor | 平衡係数 | 平衡係数 | | heap | ヒープ | ヒープ | | max heap | 最大ヒープ | 最大ヒープ | | min heap | 最小ヒープ | 最小ヒープ | | priority queue | 優先度付きキュー | 優先度付きキュー | | heapify | ヒープ化 | ヒープ化 | | top-$k$ problem | Top-$k$ 問題 | Top-$k$ 問題 | | graph | グラフ | グラフ | | vertex | 頂点 | 頂点 | | undirected graph | 無向グラフ | 無向グラフ | | directed graph | 有向グラフ | 有向グラフ | | connected graph | 連結グラフ | 連結グラフ | | disconnected graph | 非連結グラフ | 非連結グラフ | | weighted graph | 重み付きグラフ | 重み付きグラフ | | adjacency | 隣接 | 隣接 | | path | 経路 | 経路 | | in-degree | 入次数 | 入次数 | | out-degree | 出次数 | 出次数 | | adjacency matrix | 隣接行列 | 隣接行列 | | adjacency list | 隣接リスト | 隣接リスト | | breadth-first search | 幅優先探索 | 幅優先探索 | | depth-first search | 深さ優先探索 | 深さ優先探索 | | binary search | 二分探索 | 二分探索 | | searching algorithm | 探索アルゴリズム | 探索アルゴリズム | | sorting algorithm | ソートアルゴリズム | ソートアルゴリズム | | selection sort | 選択ソート | 選択ソート | | bubble sort | バブルソート | バブルソート | | insertion sort | 挿入ソート | 挿入ソート | | quick sort | クイックソート | クイックソート | | merge sort | マージソート | マージソート | | heap sort | ヒープソート | ヒープソート | | bucket sort | バケットソート | バケットソート | | counting sort | 計数ソート | 計数ソート | | radix sort | 基数ソート | 基数ソート | | divide and conquer | 分割統治 | 分割統治 | | hanota problem | ハノイの塔問題 | ハノイの塔問題 | | backtracking algorithm | バックトラッキングアルゴリズム | バックトラッキングアルゴリズム | | constraint | 制約 | 制約 | | solution | 解 | 解 | | state | 状態 | 状態 | | pruning | 枝刈り | 枝刈り | | permutations problem | 全順列問題 | 全順列問題 | | subset-sum problem | 部分和問題 | 部分和問題 | | $n$-queens problem | $n$ クイーン問題 | $n$ クイーン問題 | | dynamic programming | 動的計画法 | 動的計画法 | | initial state | 初期状態 | 初期状態 | | state-transition equation | 状態遷移方程式 | 状態遷移方程式 | | knapsack problem | ナップサック問題 | ナップサック問題 | | edit distance problem | 編集距離問題 | 編集距離問題 | | greedy algorithm | 貪欲法 | 貪欲法 | ================================================ FILE: ja/docs/chapter_array_and_linkedlist/array.md ================================================ # 配列 配列(array)は線形データ構造の一種であり、同じ型の要素を連続したメモリ領域に格納します。要素が配列内にある位置を、その要素のインデックス(index)と呼びます。下図は、配列の主要な概念と格納方式を示しています。 ![配列の定義と格納方式](array.assets/array_definition.png) ## 配列の一般的な操作 ### 配列の初期化 必要に応じて、配列の初期化方法として初期値なしと初期値ありの 2 種類を使い分けられます。初期値を指定しない場合、多くのプログラミング言語では配列要素は $0$ に初期化されます。 === "Python" ```python title="array.py" # 配列を初期化する arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] nums: list[int] = [1, 3, 2, 5, 4] ``` === "C++" ```cpp title="array.cpp" /* 配列を初期化する */ // スタック上に格納 int arr[5]; int nums[5] = { 1, 3, 2, 5, 4 }; // ヒープ上に格納(手動で領域を解放する必要がある) int* arr1 = new int[5]; int* nums1 = new int[5] { 1, 3, 2, 5, 4 }; ``` === "Java" ```java title="array.java" /* 配列を初期化する */ int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } int[] nums = { 1, 3, 2, 5, 4 }; ``` === "C#" ```csharp title="array.cs" /* 配列を初期化する */ int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ] int[] nums = [1, 3, 2, 5, 4]; ``` === "Go" ```go title="array.go" /* 配列を初期化する */ var arr [5]int // Go では、長さを指定する場合([5]int)は配列であり、長さを指定しない場合([]int)はスライス // Go の配列はコンパイル時に長さが確定するよう設計されているため、長さの指定には定数しか使用できない // 拡張 extend() メソッドを実装しやすくするため、以下ではスライス(Slice)を配列(Array)として扱う nums := []int{1, 3, 2, 5, 4} ``` === "Swift" ```swift title="array.swift" /* 配列を初期化する */ let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] let nums = [1, 3, 2, 5, 4] ``` === "JS" ```javascript title="array.js" /* 配列を初期化する */ var arr = new Array(5).fill(0); var nums = [1, 3, 2, 5, 4]; ``` === "TS" ```typescript title="array.ts" /* 配列を初期化する */ let arr: number[] = new Array(5).fill(0); let nums: number[] = [1, 3, 2, 5, 4]; ``` === "Dart" ```dart title="array.dart" /* 配列を初期化する */ List arr = List.filled(5, 0); // [0, 0, 0, 0, 0] List nums = [1, 3, 2, 5, 4]; ``` === "Rust" ```rust title="array.rs" /* 配列を初期化する */ let arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0] let slice: &[i32] = &[0; 5]; // Rust では、長さを指定する場合([i32; 5])は配列であり、長さを指定しない場合(&[i32])はスライス // Rust の配列はコンパイル時に長さが確定するよう設計されているため、長さの指定には定数しか使用できない // Vector は Rust で一般に動的配列として使われる型 // 拡張 extend() メソッドを実装しやすくするため、以下では vector を配列(array)として扱う let nums: Vec = vec![1, 3, 2, 5, 4]; ``` === "C" ```c title="array.c" /* 配列を初期化する */ int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 } int nums[5] = { 1, 3, 2, 5, 4 }; ``` === "Kotlin" ```kotlin title="array.kt" /* 配列を初期化する */ var arr = IntArray(5) // { 0, 0, 0, 0, 0 } var nums = intArrayOf(1, 3, 2, 5, 4) ``` === "Ruby" ```ruby title="array.rb" # 配列を初期化する arr = Array.new(5, 0) nums = [1, 3, 2, 5, 4] ``` ??? pythontutor "実行の可視化" https://pythontutor.com/render.html#code=%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0Aarr%20%3D%20%5B0%5D%20*%205%20%20%23%20%5B%200,%200,%200,%200,%200%20%5D%0Anums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 要素へのアクセス 配列要素は連続したメモリ領域に格納されるため、要素のメモリアドレスの計算は非常に容易です。配列のメモリアドレス(先頭要素のメモリアドレス)とある要素のインデックスが与えられれば、下図の式を使ってその要素のメモリアドレスを計算でき、直接その要素にアクセスできます。 ![配列要素のメモリアドレスの計算](array.assets/array_memory_location_calculation.png) 上図を見ると、配列の最初の要素のインデックスは $0$ であり、これは少し直感に反するように思えます。というのも、$1$ から数え始めるほうが自然だからです。しかし、アドレス計算式の観点では、**インデックスの本質はメモリアドレスのオフセット**です。先頭要素のアドレスのオフセットは $0$ であるため、そのインデックスが $0$ なのは妥当です。 配列では要素へのアクセスは非常に効率的であり、$O(1)$ 時間で任意の要素にランダムアクセスできます。 ```src [file]{array}-[class]{}-[func]{random_access} ``` ### 要素の挿入 配列要素はメモリ内で「ぴったり隣接して」おり、その間にほかのデータを格納する余地はありません。下図のように、配列の途中に要素を挿入したい場合は、その要素より後ろにあるすべての要素を 1 つずつ後ろへずらし、その後でそのインデックスに要素を代入する必要があります。 ![配列への要素挿入の例](array.assets/array_insert_element.png) 注意すべき点として、配列の長さは固定であるため、要素を 1 つ挿入すると配列末尾の要素が必ず「失われ」ます。この問題の解決策は「リスト」の章で扱います。 ```src [file]{array}-[class]{}-[func]{insert} ``` ### 要素の削除 同様に、下図のように、インデックス $i$ の要素を削除したい場合は、インデックス $i$ より後ろの要素をすべて 1 つずつ前へずらす必要があります。 ![配列からの要素削除の例](array.assets/array_remove_element.png) 注意してください。要素の削除が完了すると、もともとの末尾要素は「意味を持たない」状態になるため、わざわざ変更する必要はありません。 ```src [file]{array}-[class]{}-[func]{remove} ``` 全体として見ると、配列の挿入と削除には次の欠点があります。 - **時間計算量が高い**:配列の挿入と削除の平均時間計算量はいずれも $O(n)$ であり、ここで $n$ は配列長です。 - **要素が失われる**:配列の長さは不変であるため、要素を挿入すると配列長の範囲を超えた要素は失われます。 - **メモリの浪費**:やや長めの配列を初期化して先頭部分だけを使うこともでき、この場合データ挿入時に失われる末尾要素はすべて「無意味」ですが、その代わり一部のメモリ領域が無駄になります。 ### 配列の走査 ほとんどのプログラミング言語では、インデックスを使って配列を走査することも、各要素を直接取り出しながら走査することもできます。 ```src [file]{array}-[class]{}-[func]{traverse} ``` ### 要素の検索 配列内で指定した要素を探すには、配列を走査し、各反復で要素値が一致するかを判定し、一致したら対応するインデックスを出力します。 配列は線形データ構造であるため、上記の検索操作は「線形探索」と呼ばれます。 ```src [file]{array}-[class]{}-[func]{find} ``` ### 配列の拡張 複雑なシステム環境では、配列の後方にあるメモリ領域が利用可能であることをプログラム側で保証できず、そのため安全に配列容量を拡張できません。したがって、ほとんどのプログラミング言語では、**配列の長さは不変です**。 配列を拡張したい場合は、より大きな新しい配列を作り、元の配列の要素を順に新配列へコピーする必要があります。これは $O(n)$ の操作であり、配列が大きい場合は非常に時間がかかります。コードは次のとおりです。 ```src [file]{array}-[class]{}-[func]{extend} ``` ## 配列の利点と限界 配列は連続したメモリ領域に格納され、要素の型も同一です。この方法には豊富な事前情報が含まれており、システムはそれらを利用してデータ構造の操作効率を最適化できます。 - **空間効率が高い**:配列はデータに連続したメモリブロックを割り当てるため、追加の構造オーバーヘッドが不要です。 - **ランダムアクセスをサポートする**:配列では任意の要素に $O(1)$ 時間でアクセスできます。 - **キャッシュ局所性**:配列要素にアクセスする際、コンピュータはその要素だけでなく周囲のデータもキャッシュするため、高速キャッシュを利用して後続操作の実行速度を高められます。 連続領域への格納は諸刃の剣であり、次のような制約があります。 - **挿入と削除の効率が低い**:配列内の要素が多い場合、挿入や削除では大量の要素を移動する必要があります。 - **長さが不変**:配列は初期化後に長さが固定され、拡張するにはすべてのデータを新しい配列へコピーする必要があり、コストが大きくなります。 - **空間の浪費**:配列に割り当てたサイズが実際の必要量を上回る場合、余分な領域は無駄になります。 ## 配列の典型的な応用 配列は基礎的で一般的なデータ構造であり、さまざまなアルゴリズムで頻繁に使われるだけでなく、多様な複雑データ構造の実装にも利用できます。 - **ランダムアクセス**:いくつかのサンプルをランダムに抽出したい場合、配列に格納してランダムな系列を生成し、インデックスに基づいてランダムサンプリングを行えます。 - **ソートと探索**:配列はソートアルゴリズムと探索アルゴリズムで最もよく使われるデータ構造です。クイックソート、マージソート、二分探索などは主に配列上で行われます。 - **ルックアップテーブル**:ある要素やその対応関係を高速に調べる必要がある場合、配列をルックアップテーブルとして使えます。たとえば文字から ASCII コードへの対応を実装したいなら、文字の ASCII コード値をインデックスとし、対応する要素を配列の対応位置に格納できます。 - **機械学習**:ニューラルネットワークでは、ベクトル、行列、テンソル間の線形代数演算が大量に使われ、これらのデータはいずれも配列の形で構築されます。配列はニューラルネットワークプログラミングで最もよく使われるデータ構造です。 - **データ構造の実装**:配列はスタック、キュー、ハッシュテーブル、ヒープ、グラフなどのデータ構造の実装に利用できます。たとえば、グラフの隣接行列表現は実際には 2 次元配列です。 ================================================ FILE: ja/docs/chapter_array_and_linkedlist/index.md ================================================ # 配列と連結リスト ![配列と連結リスト](../assets/covers/chapter_array_and_linkedlist.jpg) !!! abstract データ構造の世界は、まるで重厚なれんがの壁のようです。 配列のれんがは整然と並び、一つひとつがぴったりと接しています。連結リストのれんがはあちこちに分散し、それらをつなぐつるがれんがのすき間を自由に行き交います。 ================================================ FILE: ja/docs/chapter_array_and_linkedlist/linked_list.md ================================================ # 連結リスト メモリ空間はすべてのプログラムに共通の資源であり、複雑なシステム実行環境では、空きメモリがメモリの各所に散在している可能性があります。配列を格納するメモリ空間は連続していなければなりませんが、配列が非常に大きい場合、メモリはそのような大きな連続領域を提供できないことがあります。このとき、連結リストの柔軟性という利点が現れます。 連結リスト(linked list)は線形データ構造の一種であり、各要素はノードオブジェクトです。各ノードは「参照」によって接続されます。参照には次のノードのメモリアドレスが記録されており、これによって現在のノードから次のノードへアクセスできます。 連結リストの設計では、各ノードをメモリの各所に分散して格納でき、それらのメモリアドレスは連続している必要がありません。 ![連結リストの定義と格納方式](linked_list.assets/linkedlist_definition.png) 上図を見ると、連結リストの構成単位はノード(node)オブジェクトです。各ノードは 2 つのデータ、すなわちノードの「値」と次のノードを指す「参照」を含みます。 - 連結リストの最初のノードを「先頭ノード」、最後のノードを「末尾ノード」と呼びます。 - 末尾ノードが指す先は「空」であり、Java、C++、Python ではそれぞれ `null`、`nullptr`、`None` と表記します。 - C、C++、Go、Rust などポインタをサポートする言語では、上記の「参照」は「ポインタ」に置き換えるべきです。 以下のコードが示すように、連結リストノード `ListNode` は値のほかに、追加で 1 つの参照(ポインタ)を保持する必要があります。そのため、同じデータ量であれば、**連結リストは配列より多くのメモリ空間を消費します**。 === "Python" ```python title="" class ListNode: """連結リストノードクラス""" def __init__(self, val: int): self.val: int = val # ノードの値 self.next: ListNode | None = None # 次のノードへの参照 ``` === "C++" ```cpp title="" /* 連結リストノード構造体 */ struct ListNode { int val; // ノードの値 ListNode *next; // 次のノードへのポインタ ListNode(int x) : val(x), next(nullptr) {} // コンストラクタ }; ``` === "Java" ```java title="" /* 連結リストノードクラス */ class ListNode { int val; // ノードの値 ListNode next; // 次のノードへの参照 ListNode(int x) { val = x; } // コンストラクタ } ``` === "C#" ```csharp title="" /* 連結リストノードクラス */ class ListNode(int x) { //コンストラクタ int val = x; // ノードの値 ListNode? next; // 次のノードへの参照 } ``` === "Go" ```go title="" /* 連結リストノード構造体 */ type ListNode struct { Val int // ノードの値 Next *ListNode // 次のノードへのポインタ } // NewListNode コンストラクタ。新しい連結リストを作成する func NewListNode(val int) *ListNode { return &ListNode{ Val: val, Next: nil, } } ``` === "Swift" ```swift title="" /* 連結リストノードクラス */ class ListNode { var val: Int // ノードの値 var next: ListNode? // 次のノードへの参照 init(x: Int) { // コンストラクタ val = x } } ``` === "JS" ```javascript title="" /* 連結リストノードクラス */ class ListNode { constructor(val, next) { this.val = (val === undefined ? 0 : val); // ノードの値 this.next = (next === undefined ? null : next); // 次のノードへの参照 } } ``` === "TS" ```typescript title="" /* 連結リストノードクラス */ class ListNode { val: number; next: ListNode | null; constructor(val?: number, next?: ListNode | null) { this.val = val === undefined ? 0 : val; // ノードの値 this.next = next === undefined ? null : next; // 次のノードへの参照 } } ``` === "Dart" ```dart title="" /* 連結リストノードクラス */ class ListNode { int val; // ノードの値 ListNode? next; // 次のノードへの参照 ListNode(this.val, [this.next]); // コンストラクタ } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* 連結リストノードクラス */ #[derive(Debug)] struct ListNode { val: i32, // ノードの値 next: Option>>, // 次のノードへのポインタ } ``` === "C" ```c title="" /* 連結リストノード構造体 */ typedef struct ListNode { int val; // ノードの値 struct ListNode *next; // 次のノードへのポインタ } ListNode; /* コンストラクタ */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *) malloc(sizeof(ListNode)); node->val = val; node->next = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* 連結リストノードクラス */ // コンストラクタ class ListNode(x: Int) { val _val: Int = x // ノードの値 val next: ListNode? = null // 次のノードへの参照 } ``` === "Ruby" ```ruby title="" # 連結リストノードクラス class ListNode attr_accessor :val # ノードの値 attr_accessor :next # 次のノードへの参照 def initialize(val=0, next_node=nil) @val = val @next = next_node end end ``` ## 連結リストの基本操作 ### 連結リストの初期化 連結リストの構築は 2 つの手順に分かれます。第 1 に各ノードオブジェクトを初期化し、第 2 にノード間の参照関係を構築します。初期化が完了したら、連結リストの先頭ノードから出発し、参照で `next` をたどってすべてのノードに順にアクセスできます。 === "Python" ```python title="linked_list.py" # 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 # 各ノードを初期化 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 ``` === "C++" ```cpp title="linked_list.cpp" /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 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; ``` === "Java" ```java title="linked_list.java" /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 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; ``` === "C#" ```csharp title="linked_list.cs" /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 ListNode n0 = new(1); ListNode n1 = new(3); ListNode n2 = new(2); ListNode n3 = new(5); ListNode n4 = new(4); // ノード間の参照を構築 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Go" ```go title="linked_list.go" /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 n0 := NewListNode(1) n1 := NewListNode(3) n2 := NewListNode(2) n3 := NewListNode(5) n4 := NewListNode(4) // ノード間の参照を構築 n0.Next = n1 n1.Next = n2 n2.Next = n3 n3.Next = n4 ``` === "Swift" ```swift title="linked_list.swift" /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 let n0 = ListNode(x: 1) let n1 = ListNode(x: 3) let n2 = ListNode(x: 2) let n3 = ListNode(x: 5) let n4 = ListNode(x: 4) // ノード間の参照を構築 n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 ``` === "JS" ```javascript title="linked_list.js" /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // ノード間の参照を構築 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "TS" ```typescript title="linked_list.ts" /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // ノード間の参照を構築 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Dart" ```dart title="linked_list.dart" /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */\ // 各ノードを初期化 ListNode n0 = ListNode(1); ListNode n1 = ListNode(3); ListNode n2 = ListNode(2); ListNode n3 = ListNode(5); ListNode n4 = ListNode(4); // ノード間の参照を構築 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Rust" ```rust title="linked_list.rs" /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None })); let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None })); let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None })); let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None })); let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None })); // ノード間の参照を構築 n0.borrow_mut().next = Some(n1.clone()); n1.borrow_mut().next = Some(n2.clone()); n2.borrow_mut().next = Some(n3.clone()); n3.borrow_mut().next = Some(n4.clone()); ``` === "C" ```c title="linked_list.c" /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 ListNode* n0 = newListNode(1); ListNode* n1 = newListNode(3); ListNode* n2 = newListNode(2); ListNode* n3 = newListNode(5); ListNode* n4 = newListNode(4); // ノード間の参照を構築 n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; ``` === "Kotlin" ```kotlin title="linked_list.kt" /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 val n0 = ListNode(1) val n1 = ListNode(3) val n2 = ListNode(2) val n3 = ListNode(5) val n4 = ListNode(4) // ノード間の参照を構築 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Ruby" ```ruby title="linked_list.rb" # 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 # 各ノードを初期化 n0 = ListNode.new(1) n1 = ListNode.new(3) n2 = ListNode.new(2) n3 = ListNode.new(5) n4 = ListNode.new(4) # ノード間の参照を構築 n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 ``` ??? pythontutor "可視化実行" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%201%20-%3E%203%20-%3E%202%20-%3E%205%20-%3E%204%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false 配列全体は 1 つの変数であり、たとえば配列 `nums` には `nums[0]` や `nums[1]` などの要素が含まれます。一方、連結リストは複数の独立したノードオブジェクトで構成されます。**通常、先頭ノードを連結リストの代名詞として扱います**。たとえば上記のコードの連結リストは `n0` と表せます。 ### ノードの挿入 連結リストへのノード挿入は非常に簡単です。下図に示すように、隣り合う 2 つのノード `n0` と `n1` の間に新しいノード `P` を挿入したいとします。**このとき 2 つのノードの参照(ポインタ)を変更するだけでよく**、時間計算量は $O(1)$ です。 これに対して、配列に要素を挿入する時間計算量は $O(n)$ であり、データ量が大きい場合の効率は低くなります。 ![連結リストへのノード挿入例](linked_list.assets/linkedlist_insert_node.png) ```src [file]{linked_list}-[class]{}-[func]{insert} ``` ### ノードの削除 下図に示すように、連結リストでのノード削除も非常に簡単で、**1 つのノードの参照(ポインタ)を変更するだけで済みます**。 なお、削除操作が完了した後もノード `P` は依然として `n1` を指していますが、実際にはこの連結リストをたどっても `P` へはアクセスできません。つまり、`P` はすでにこの連結リストには属していません。 ![連結リストのノード削除](linked_list.assets/linkedlist_remove_node.png) ```src [file]{linked_list}-[class]{}-[func]{remove} ``` ### ノードへのアクセス **連結リストでノードにアクセスする効率は低い**です。前節で述べたように、配列では任意の要素へ $O(1)$ 時間でアクセスできます。これに対して連結リストでは、プログラムは先頭ノードから出発し、1 つずつ後ろへたどって目的のノードを見つける必要があります。つまり、連結リストの第 $i$ ノードにアクセスするには $i - 1$ 回のループが必要であり、時間計算量は $O(n)$ です。 ```src [file]{linked_list}-[class]{}-[func]{access} ``` ### ノードの探索 連結リストを走査し、その中から値が `target` のノードを探し、そのノードの連結リスト内でのインデックスを出力します。この処理も線形探索に属します。コードは次のとおりです。 ```src [file]{linked_list}-[class]{}-[func]{find} ``` ## 配列 vs. 連結リスト 次の表は、配列と連結リストの各種特徴と操作効率をまとめたものです。両者は互いに逆の格納戦略を採用しているため、各種性質や操作効率にも対照的な特徴が現れます。

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

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

  コンピュータの記憶装置

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

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

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

  全順列の例

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

  反復と再帰の特徴の比較

| | 反復 | 再帰 | | -------- | -------------------------------------- | ------------------------------------------------------------ | | 実装方法 | ループ構造 | 関数が自分自身を呼び出す | | 時間効率 | 通常は効率が高く、関数呼び出しの負荷がない | 関数呼び出しのたびにオーバーヘッドが発生する | | メモリ使用 | 通常は固定サイズのメモリ空間を使う | 関数呼び出しの蓄積により大量のスタックフレーム領域を使う可能性がある | | 適用対象 | 単純な反復処理に適し、コードが直感的で読みやすい | 木、グラフ、分割統治、バックトラッキングなどの部分問題分解に適し、コード構造が簡潔で明快 | !!! tip 以下の内容が難しいと感じる場合は、「スタック」の章を読み終えた後に改めて復習してください。 では、反復と再帰にはどのような内在的な関係があるのでしょうか。前述の再帰関数を例にすると、加算処理は再帰の復帰段階で行われます。これは、最初に呼び出された関数が実際には最後に加算を完了することを意味しており、**この動作の仕組みはスタックの「後入れ先出し」の原則とよく似ています**。 実際、「呼び出しスタック」や「スタックフレーム領域」といった再帰の用語自体が、再帰とスタックの密接な関係を示唆しています。 1. **再帰呼び出し**:関数が呼び出されると、システムは「呼び出しスタック」上にその関数のための新しいスタックフレームを割り当て、局所変数、引数、返却先アドレスなどのデータを保存します。 2. **復帰**:関数の実行が完了して戻ると、対応するスタックフレームは「呼び出しスタック」から取り除かれ、前の関数の実行環境が復元されます。 したがって、**明示的なスタックを使って呼び出しスタックの振る舞いを模擬することができ**、その結果として再帰を反復形式へ変換できます。 ```src [file]{recursion}-[class]{}-[func]{for_loop_recur} ``` 上のコードを見ると、再帰を反復へ変換すると、コードはより複雑になります。反復と再帰は多くの場合に相互変換できますが、常にそうする価値があるとは限りません。理由は次の 2 点です。 - 変換後のコードは理解しにくくなり、可読性が下がる可能性があります。 - 複雑な問題によっては、システムの呼び出しスタックの振る舞いを模擬すること自体が非常に難しい場合があります。 要するに、**反復を選ぶか再帰を選ぶかは、対象となる問題の性質によって決まります**。実際のプログラミングでは、両者の長所と短所を見極め、状況に応じて適切な方法を選ぶことが重要です。 ================================================ FILE: ja/docs/chapter_computational_complexity/performance_evaluation.md ================================================ # アルゴリズム効率の評価 アルゴリズム設計では、次の 2 つのレベルの目標を順に追求します。 1. **問題の解法を見つける**:アルゴリズムは、定められた入力範囲内で問題の正しい解を確実に求められる必要があります。 2. **最適な解法を追求する**:同じ問題に対して複数の解法が存在する場合があり、私たちはできるだけ効率的なアルゴリズムを見つけたいと考えます。 つまり、問題を解けることを前提として、アルゴリズム効率はその良し悪しを測る主要な評価指標となっており、次の 2 つの観点を含みます。 - **時間効率**:アルゴリズムの実行時間の長さ。 - **空間効率**:アルゴリズムが使用するメモリ空間の大きさ。 簡単に言えば、**私たちの目標は「高速で省メモリ」なデータ構造とアルゴリズムを設計すること**です。そして、アルゴリズム効率を効果的に評価することは非常に重要です。そうすることで初めて、さまざまなアルゴリズムを比較し、さらにアルゴリズム設計と最適化の過程を導けるからです。 効率の評価方法は主に 2 種類に分けられます。実測と理論的な見積もりです。 ## 実測 いまアルゴリズム `A` とアルゴリズム `B` があり、どちらも同じ問題を解けるとします。この 2 つのアルゴリズムの効率を比較する必要がある場合、最も直接的な方法は 1 台のコンピュータで両者を実行し、その実行時間とメモリ使用量を監視して記録することです。この評価方法は実際の状況を反映できますが、大きな制約もあります。 一方では、**テスト環境による干渉要因を排除しにくい**という問題があります。ハードウェア構成はアルゴリズムの性能に影響します。たとえば、並列度の高いアルゴリズムはマルチコア CPU での実行により適しており、メモリアクセスが集中的なアルゴリズムは高性能メモリ上でより良い性能を示します。つまり、異なるマシンでのテスト結果は一致しない可能性があります。これは、さまざまなマシンでテストして平均効率を統計的に求める必要があることを意味しますが、それは現実的ではありません。 他方では、**完全なテストを実施するには非常に多くの資源が必要**です。入力データ量が変化すると、アルゴリズムは異なる効率を示します。たとえば、入力データ量が小さいときはアルゴリズム `A` の実行時間がアルゴリズム `B` より短くても、入力データ量が大きいときには結果がちょうど逆になるかもしれません。そのため、説得力のある結論を得るには、さまざまな規模の入力データでテストする必要があり、それには大量の計算資源を要します。 ## 理論的な見積もり 実測には大きな制約があるため、いくつかの計算だけによってアルゴリズムの効率を評価することを考えられます。この見積もり方法は漸近計算量解析(asymptotic complexity analysis)と呼ばれ、略して計算量解析といいます。 計算量解析は、アルゴリズムの実行に必要な時間資源と空間資源が入力データ規模とどのような関係にあるかを表します。**これは、入力データ規模が増加するにつれて、アルゴリズムの実行に必要な時間と空間がどのように増加するかという傾向を記述するものです**。この定義はややわかりにくいので、次の 3 つのポイントに分けて理解できます。 - 「時間資源と空間資源」は、それぞれ時間計算量(time complexity)空間計算量(space complexity)に対応します。 - 「入力データ規模が増加するにつれて」とは、計算量がアルゴリズムの実行効率と入力データ規模との関係を反映していることを意味します。 - 「時間と空間の増加傾向」とは、計算量解析が注目するのは実行時間や使用空間の具体的な値ではなく、時間や空間の増加の「速さ」であることを示します。 **計算量解析は実測という方法の欠点を克服しています**。その点は次のように表れます。 - 実際にコードを動かす必要がなく、より環境にやさしく省エネルギーです。 - テスト環境から独立しており、解析結果はすべての実行プラットフォームに適用できます。 - 異なるデータ量におけるアルゴリズム効率を表せ、とくに大規模データ量での性能を反映できます。 !!! tip それでも計算量の概念がまだわかりにくくても、心配はいりません。後続の章で詳しく説明します。 計算量解析は、アルゴリズム効率を評価するための「物差し」を私たちに与えてくれます。これにより、あるアルゴリズムの実行に必要な時間資源と空間資源を測り、異なるアルゴリズム同士の効率を比較できます。 計算量は数学的な概念であり、初学者にとってはやや抽象的で、学習の難度も比較的高いかもしれません。この観点から見ると、計算量解析は最初に紹介する内容としてはあまり適していない可能性があります。しかし、あるデータ構造やアルゴリズムの特徴を議論する際には、その実行速度や空間使用状況の分析を避けることはできません。 以上を踏まえると、データ構造とアルゴリズムを深く学ぶ前に、**まず計算量解析について初歩的な理解を持ち、簡単なアルゴリズムの計算量解析ができるようにしておくこと**を勧めます。 ================================================ FILE: ja/docs/chapter_computational_complexity/space_complexity.md ================================================ # 空間計算量 空間計算量(space complexity)は、アルゴリズムが占有するメモリ空間がデータ量の増加に伴ってどのように増えるかを測る指標です。この概念は時間計算量と非常によく似ており、「実行時間」を「占有メモリ空間」に置き換えるだけです。 ## アルゴリズムに関連する空間 アルゴリズムが実行中に使用するメモリ空間には、主に次の種類があります。 - **入力空間**:アルゴリズムの入力データを格納するための空間。 - **一時空間**:アルゴリズムの実行中に使用する変数、オブジェクト、関数コンテキストなどのデータを格納するための空間。 - **出力空間**:アルゴリズムの出力データを格納するための空間。 一般に、空間計算量の集計範囲は「一時空間」と「出力空間」を合わせたものです。 一時空間はさらに三つに分けられます。 - **一時データ**:アルゴリズム実行中の各種定数、変数、オブジェクトなどを保存するための空間。 - **スタックフレーム空間**:呼び出された関数のコンテキストデータを保存するための空間。システムは関数を呼び出すたびにスタックの先頭にスタックフレームを作成し、関数が戻るとその空間を解放します。 - **命令空間**:コンパイル後のプログラム命令を保存するための空間で、実際の集計では通常無視されます。 プログラムの空間計算量を分析する際には、**通常、一時データ、スタックフレーム空間、出力データの三つを数えます**。以下の図に示すとおりです。 ![アルゴリズムで使用される関連空間](space_complexity.assets/space_types.png) 関連するコードを以下に示します。 === "Python" ```python title="" class Node: """クラス""" def __init__(self, x: int): self.val: int = x # ノードの値 self.next: Node | None = None # 次のノードへの参照 def function() -> int: """関数""" # いくつかの処理を実行... return 0 def algorithm(n) -> int: # 入力データ A = 0 # 一時データ(定数。一般に大文字で表す) b = 0 # 一時データ(変数) node = Node(0) # 一時データ(オブジェクト) c = function() # スタックフレーム空間(関数呼び出し) return A + b + c # 出力データ ``` === "C++" ```cpp title="" /* 構造体 */ struct Node { int val; Node *next; Node(int x) : val(x), next(nullptr) {} }; /* 関数 */ int func() { // いくつかの処理を実行... return 0; } int algorithm(int n) { // 入力データ const int a = 0; // 一時データ(定数) int b = 0; // 一時データ(変数) Node* node = new Node(0); // 一時データ(オブジェクト) int c = func(); // スタックフレーム空間(関数呼び出し) return a + b + c; // 出力データ } ``` === "Java" ```java title="" /* クラス */ class Node { int val; Node next; Node(int x) { val = x; } } /* 関数 */ int function() { // いくつかの処理を実行... return 0; } int algorithm(int n) { // 入力データ final int a = 0; // 一時データ(定数) int b = 0; // 一時データ(変数) Node node = new Node(0); // 一時データ(オブジェクト) int c = function(); // スタックフレーム空間(関数呼び出し) return a + b + c; // 出力データ } ``` === "C#" ```csharp title="" /* クラス */ class Node(int x) { int val = x; Node next; } /* 関数 */ int Function() { // いくつかの処理を実行... return 0; } int Algorithm(int n) { // 入力データ const int a = 0; // 一時データ(定数) int b = 0; // 一時データ(変数) Node node = new(0); // 一時データ(オブジェクト) int c = Function(); // スタックフレーム空間(関数呼び出し) return a + b + c; // 出力データ } ``` === "Go" ```go title="" /* 構造体 */ type node struct { val int next *node } /* node 構造体を作成 */ func newNode(val int) *node { return &node{val: val} } /* 関数 */ func function() int { // いくつかの処理を実行... return 0 } func algorithm(n int) int { // 入力データ const a = 0 // 一時データ(定数) b := 0 // 一時データ(変数) newNode(0) // 一時データ(オブジェクト) c := function() // スタックフレーム空間(関数呼び出し) return a + b + c // 出力データ } ``` === "Swift" ```swift title="" /* クラス */ class Node { var val: Int var next: Node? init(x: Int) { val = x } } /* 関数 */ func function() -> Int { // いくつかの処理を実行... return 0 } func algorithm(n: Int) -> Int { // 入力データ let a = 0 // 一時データ(定数) var b = 0 // 一時データ(変数) let node = Node(x: 0) // 一時データ(オブジェクト) let c = function() // スタックフレーム空間(関数呼び出し) return a + b + c // 出力データ } ``` === "JS" ```javascript title="" /* クラス */ class Node { val; next; constructor(val) { this.val = val === undefined ? 0 : val; // ノードの値 this.next = null; // 次のノードへの参照 } } /* 関数 */ function constFunc() { // いくつかの処理を実行 return 0; } function algorithm(n) { // 入力データ const a = 0; // 一時データ(定数) let b = 0; // 一時データ(変数) const node = new Node(0); // 一時データ(オブジェクト) const c = constFunc(); // スタックフレーム空間(関数呼び出し) return a + b + c; // 出力データ } ``` === "TS" ```typescript title="" /* クラス */ class Node { val: number; next: Node | null; constructor(val?: number) { this.val = val === undefined ? 0 : val; // ノードの値 this.next = null; // 次のノードへの参照 } } /* 関数 */ function constFunc(): number { // いくつかの処理を実行 return 0; } function algorithm(n: number): number { // 入力データ const a = 0; // 一時データ(定数) let b = 0; // 一時データ(変数) const node = new Node(0); // 一時データ(オブジェクト) const c = constFunc(); // スタックフレーム空間(関数呼び出し) return a + b + c; // 出力データ } ``` === "Dart" ```dart title="" /* クラス */ class Node { int val; Node next; Node(this.val, [this.next]); } /* 関数 */ int function() { // いくつかの処理を実行... return 0; } int algorithm(int n) { // 入力データ const int a = 0; // 一時データ(定数) int b = 0; // 一時データ(変数) Node node = Node(0); // 一時データ(オブジェクト) int c = function(); // スタックフレーム空間(関数呼び出し) return a + b + c; // 出力データ } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* 構造体 */ struct Node { val: i32, next: Option>>, } /* Node 構造体を作成 */ impl Node { fn new(val: i32) -> Self { Self { val: val, next: None } } } /* 関数 */ fn function() -> i32 { // いくつかの処理を実行... return 0; } fn algorithm(n: i32) -> i32 { // 入力データ const a: i32 = 0; // 一時データ(定数) let mut b = 0; // 一時データ(変数) let node = Node::new(0); // 一時データ(オブジェクト) let c = function(); // スタックフレーム空間(関数呼び出し) return a + b + c; // 出力データ } ``` === "C" ```c title="" /* 関数 */ int func() { // いくつかの処理を実行... return 0; } int algorithm(int n) { // 入力データ const int a = 0; // 一時データ(定数) int b = 0; // 一時データ(変数) int c = func(); // スタックフレーム空間(関数呼び出し) return a + b + c; // 出力データ } ``` === "Kotlin" ```kotlin title="" /* クラス */ class Node(var _val: Int) { var next: Node? = null } /* 関数 */ fun function(): Int { // いくつかの処理を実行... return 0 } fun algorithm(n: Int): Int { // 入力データ val a = 0 // 一時データ(定数) var b = 0 // 一時データ(変数) val node = Node(0) // 一時データ(オブジェクト) val c = function() // スタックフレーム空間(関数呼び出し) return a + b + c // 出力データ } ``` === "Ruby" ```ruby title="" ### クラス ### class Node attr_accessor :val # ノードの値 attr_accessor :next # 次のノードへの参照 def initialize(x) @val = x end end ### 関数 ### def function # いくつかの処理を実行... 0 end ### アルゴリズム ### def algorithm(n) # 入力データ a = 0 # 一時データ(定数) b = 0 # 一時データ(変数) node = Node.new(0) # 一時データ(オブジェクト) c = function # スタックフレーム空間(関数呼び出し) a + b + c # 出力データ end ``` ## 推定方法 空間計算量の推定方法は時間計算量とおおむね同じで、数える対象を「操作回数」から「使用空間の大きさ」に変えるだけです。 ただし時間計算量と異なり、**通常は最悪空間計算量だけに注目します**。メモリ空間は厳格な要件であり、どの入力データに対しても十分なメモリを確保できることを保証しなければならないからです。 以下のコードを見ると、最悪空間計算量における「最悪」には二つの意味があります。 1. **最悪の入力データを基準にする**:$n < 10$ のとき空間計算量は $O(1)$ ですが、$n > 10$ のとき初期化される配列 `nums` が $O(n)$ の空間を占有するため、最悪空間計算量は $O(n)$ です。 2. **アルゴリズム実行中のメモリ使用量のピークを基準にする**:例えば、プログラムは最後の行を実行する前までは $O(1)$ の空間しか使いませんが、配列 `nums` を初期化するときには $O(n)$ の空間を占有するため、最悪空間計算量は $O(n)$ です。 === "Python" ```python title="" def algorithm(n: int): a = 0 # O(1) b = [0] * 10000 # O(1) if n > 10: nums = [0] * n # O(n) ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 0; // O(1) vector b(10000); // O(1) if (n > 10) vector nums(n); // O(n) } ``` === "Java" ```java title="" void algorithm(int n) { int a = 0; // O(1) int[] b = new int[10000]; // O(1) if (n > 10) int[] nums = new int[n]; // O(n) } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 0; // O(1) int[] b = new int[10000]; // O(1) if (n > 10) { int[] nums = new int[n]; // O(n) } } ``` === "Go" ```go title="" func algorithm(n int) { a := 0 // O(1) b := make([]int, 10000) // O(1) var nums []int if n > 10 { nums := make([]int, n) // O(n) } fmt.Println(a, b, nums) } ``` === "Swift" ```swift title="" func algorithm(n: Int) { let a = 0 // O(1) let b = Array(repeating: 0, count: 10000) // O(1) if n > 10 { let nums = Array(repeating: 0, count: n) // O(n) } } ``` === "JS" ```javascript title="" function algorithm(n) { const a = 0; // O(1) const b = new Array(10000); // O(1) if (n > 10) { const nums = new Array(n); // O(n) } } ``` === "TS" ```typescript title="" function algorithm(n: number): void { const a = 0; // O(1) const b = new Array(10000); // O(1) if (n > 10) { const nums = new Array(n); // O(n) } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 0; // O(1) List b = List.filled(10000, 0); // O(1) if (n > 10) { List nums = List.filled(n, 0); // O(n) } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let a = 0; // O(1) let b = [0; 10000]; // O(1) if n > 10 { let nums = vec![0; n as usize]; // O(n) } } ``` === "C" ```c title="" void algorithm(int n) { int a = 0; // O(1) int b[10000]; // O(1) if (n > 10) int nums[n] = {0}; // O(n) } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { val a = 0 // O(1) val b = IntArray(10000) // O(1) if (n > 10) { val nums = IntArray(n) // O(n) } } ``` === "Ruby" ```ruby title="" def algorithm(n) a = 0 # O(1) b = Array.new(10000) # O(1) nums = Array.new(n) if n > 10 # O(n) end ``` **再帰関数では、スタックフレーム空間の集計に注意が必要です**。以下のコードを見てみましょう。 === "Python" ```python title="" def function() -> int: # いくつかの処理を実行 return 0 def loop(n: int): """ループの空間計算量は O(1)""" for _ in range(n): function() def recur(n: int): """再帰の空間計算量は O(n)""" if n == 1: return return recur(n - 1) ``` === "C++" ```cpp title="" int func() { // いくつかの処理を実行 return 0; } /* ループの空間計算量は O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } /* 再帰の空間計算量は O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Java" ```java title="" int function() { // いくつかの処理を実行 return 0; } /* ループの空間計算量は O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } /* 再帰の空間計算量は O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "C#" ```csharp title="" int Function() { // いくつかの処理を実行 return 0; } /* ループの空間計算量は O(1) */ void Loop(int n) { for (int i = 0; i < n; i++) { Function(); } } /* 再帰の空間計算量は O(n) */ int Recur(int n) { if (n == 1) return 1; return Recur(n - 1); } ``` === "Go" ```go title="" func function() int { // いくつかの処理を実行 return 0 } /* ループの空間計算量は O(1) */ func loop(n int) { for i := 0; i < n; i++ { function() } } /* 再帰の空間計算量は O(n) */ func recur(n int) { if n == 1 { return } recur(n - 1) } ``` === "Swift" ```swift title="" @discardableResult func function() -> Int { // いくつかの処理を実行 return 0 } /* ループの空間計算量は O(1) */ func loop(n: Int) { for _ in 0 ..< n { function() } } /* 再帰の空間計算量は O(n) */ func recur(n: Int) { if n == 1 { return } recur(n: n - 1) } ``` === "JS" ```javascript title="" function constFunc() { // いくつかの処理を実行 return 0; } /* ループの空間計算量は O(1) */ function loop(n) { for (let i = 0; i < n; i++) { constFunc(); } } /* 再帰の空間計算量は O(n) */ function recur(n) { if (n === 1) return; return recur(n - 1); } ``` === "TS" ```typescript title="" function constFunc(): number { // いくつかの処理を実行 return 0; } /* ループの空間計算量は O(1) */ function loop(n: number): void { for (let i = 0; i < n; i++) { constFunc(); } } /* 再帰の空間計算量は O(n) */ function recur(n: number): void { if (n === 1) return; return recur(n - 1); } ``` === "Dart" ```dart title="" int function() { // いくつかの処理を実行 return 0; } /* ループの空間計算量は O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } /* 再帰の空間計算量は O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Rust" ```rust title="" fn function() -> i32 { // いくつかの処理を実行 return 0; } /* ループの空間計算量は O(1) */ fn loop(n: i32) { for i in 0..n { function(); } } /* 再帰の空間計算量は O(n) */ fn recur(n: i32) { if n == 1 { return; } recur(n - 1); } ``` === "C" ```c title="" int func() { // いくつかの処理を実行 return 0; } /* ループの空間計算量は O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } /* 再帰の空間計算量は O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Kotlin" ```kotlin title="" fun function(): Int { // いくつかの処理を実行 return 0 } /* ループの空間計算量は O(1) */ fun loop(n: Int) { for (i in 0..関数(function)は独立して実行でき、すべての引数は明示的に渡されます。メソッド(method)はオブジェクトに関連付けられ、それを呼び出すオブジェクトが暗黙的に渡され、クラスのインスタンスに含まれるデータを操作できます。 以下では、いくつかの一般的なプログラミング言語を例に説明します。 - C 言語は手続き型プログラミング言語であり、オブジェクト指向の概念がないため、関数しかありません。ただし、構造体(struct)を作成してオブジェクト指向プログラミングを模倣でき、構造体に関連付けられた関数は、他のプログラミング言語におけるメソッドに相当します。 - Java と C# はオブジェクト指向のプログラミング言語であり、コードブロック(メソッド)は通常あるクラスの一部です。静的メソッドの振る舞いは関数に似ており、クラスに束縛され、特定のインスタンス変数にはアクセスできません。 - C++ と Python は、手続き型プログラミング(関数)にもオブジェクト指向プログラミング(メソッド)にも対応しています。 **Q**:「一般的な空間計算量の種類」の図が表しているのは、使用空間の絶対量ですか? いいえ。この図が示しているのは空間計算量であり、表しているのは増加傾向であって、使用空間の絶対量ではありません。 $n = 8$ と仮定すると、各曲線の値が対応する関数と一致していないように見えるかもしれません。これは、各曲線に定数項が含まれており、値の範囲を視覚的に見やすい範囲へ圧縮しているためです。 実際には、各手法の「定数項」の複雑度がどれほどか通常は分からないため、一般に複雑度だけを根拠に $n = 8$ 以下で最適解を選ぶことはできません。ただし、$n = 8^5$ であれば選びやすく、このときは増加傾向がすでに支配的になっています。 **Q** 実際の利用場面に応じて、時間(または空間)を犠牲にしてアルゴリズムを設計することはありますか? 実際の応用では、多くの場合、空間を犠牲にして時間を得る選択をします。たとえばデータベースのインデックスでは、通常 B+ 木やハッシュインデックスを構築し、大量のメモリ空間を使う代わりに、$O(\log n)$ あるいは $O(1)$ の高速な検索を実現します。 空間資源が貴重な場面では、時間を犠牲にして空間を得ることもあります。たとえば組み込み開発では、デバイスのメモリが非常に貴重なため、エンジニアはハッシュテーブルの使用をやめ、配列による順次探索を選んでメモリ使用量を節約することがあります。その代償として探索は遅くなります。 ================================================ FILE: ja/docs/chapter_computational_complexity/time_complexity.md ================================================ # 時間計算量 実行時間はアルゴリズムの効率を直感的かつ正確に反映します。あるコードの実行時間を正確に見積もりたい場合、どのようにすればよいでしょうか? 1. **実行プラットフォームを特定する**。ハードウェア構成、プログラミング言語、システム環境などが含まれ、これらの要因はいずれもコードの実行効率に影響します。 2. **各種計算操作に必要な実行時間を評価する**。例えば加算 `+` には 1 ns 、乗算 `*` には 10 ns 、出力 `print()` には 5 ns などが必要です。 3. **コード中のすべての計算操作を数える**。そして各操作の実行時間を合計することで、実行時間を得ます。 例えば次のコードでは、入力データサイズを $n$ とします: === "Python" ```python title="" # ある実行プラットフォーム上で def algorithm(n: int): a = 2 # 1 ns a = a + 1 # 1 ns a = a * 2 # 10 ns # n 回ループ for _ in range(n): # 1 ns print(0) # 5 ns ``` === "C++" ```cpp title="" // ある実行プラットフォーム上で void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // n 回ループ for (int i = 0; i < n; i++) { // 1 ns cout << 0 << endl; // 5 ns } } ``` === "Java" ```java title="" // ある実行プラットフォーム上で void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // n 回ループ for (int i = 0; i < n; i++) { // 1 ns System.out.println(0); // 5 ns } } ``` === "C#" ```csharp title="" // ある実行プラットフォーム上で void Algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // n 回ループ for (int i = 0; i < n; i++) { // 1 ns Console.WriteLine(0); // 5 ns } } ``` === "Go" ```go title="" // ある実行プラットフォーム上で func algorithm(n int) { a := 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns // n 回ループ for i := 0; i < n; i++ { // 1 ns fmt.Println(a) // 5 ns } } ``` === "Swift" ```swift title="" // ある実行プラットフォーム上で func algorithm(n: Int) { var a = 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns // n 回ループ for _ in 0 ..< n { // 1 ns print(0) // 5 ns } } ``` === "JS" ```javascript title="" // ある実行プラットフォーム上で function algorithm(n) { var a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // n 回ループ for(let i = 0; i < n; i++) { // 1 ns console.log(0); // 5 ns } } ``` === "TS" ```typescript title="" // ある実行プラットフォーム上で function algorithm(n: number): void { var a: number = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // n 回ループ for(let i = 0; i < n; i++) { // 1 ns console.log(0); // 5 ns } } ``` === "Dart" ```dart title="" // ある実行プラットフォーム上で void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // n 回ループ for (int i = 0; i < n; i++) { // 1 ns print(0); // 5 ns } } ``` === "Rust" ```rust title="" // ある実行プラットフォーム上で fn algorithm(n: i32) { let mut a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // n 回ループ for _ in 0..n { // 1 ns println!("{}", 0); // 5 ns } } ``` === "C" ```c title="" // ある実行プラットフォーム上で void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // n 回ループ for (int i = 0; i < n; i++) { // 1 ns printf("%d", 0); // 5 ns } } ``` === "Kotlin" ```kotlin title="" // ある実行プラットフォーム上で fun algorithm(n: Int) { var a = 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns // n 回ループ for (i in 0.. 1$ ではアルゴリズム `A` より遅く、$n > 1000000$ ではアルゴリズム `C` より遅くなります。実際、入力データサイズ $n$ が十分に大きければ、「定数階」のアルゴリズムは必ず「線形階」のアルゴリズムより優れます。これが実行時間の増加傾向の意味です。 - **時間計算量の見積もり方法はより簡潔です**。実行プラットフォームや計算操作の種類は、アルゴリズム実行時間の増加傾向とは無関係です。そのため時間計算量分析では、すべての計算操作の実行時間を同じ「単位時間」とみなしてよく、「計算操作の実行時間を数える」作業を「計算操作の個数を数える」作業へ簡略化できます。これにより見積もりの難易度は大きく下がります。 - **時間計算量には一定の限界もあります**。例えばアルゴリズム `A` と `C` の時間計算量は同じでも、実際の実行時間には大きな差があります。同様に、アルゴリズム `B` の時間計算量は `C` より高いものの、入力データサイズ $n$ が小さい場合にはアルゴリズム `B` のほうが明らかに優れます。このような場合、時間計算量だけでアルゴリズム効率の高低を判断するのは難しいことがあります。もっとも、こうした問題があっても、複雑度分析は依然としてアルゴリズム効率を評価する最も有効で一般的な方法です。 ## 関数の漸近上界 入力サイズが $n$ の次の関数を考えます: === "Python" ```python title="" def algorithm(n: int): a = 1 # +1 a = a + 1 # +1 a = a * 2 # +1 # n 回ループ for i in range(n): # +1 print(0) # +1 ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // n 回ループ for (int i = 0; i < n; i++) { // +1(各反復で i ++ を実行) cout << 0 << endl; // +1 } } ``` === "Java" ```java title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // n 回ループ for (int i = 0; i < n; i++) { // +1(各反復で i ++ を実行) System.out.println(0); // +1 } } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // n 回ループ for (int i = 0; i < n; i++) { // +1(各反復で i ++ を実行) Console.WriteLine(0); // +1 } } ``` === "Go" ```go title="" func algorithm(n int) { a := 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // n 回ループ for i := 0; i < n; i++ { // +1 fmt.Println(a) // +1 } } ``` === "Swift" ```swift title="" func algorithm(n: Int) { var a = 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // n 回ループ for _ in 0 ..< n { // +1 print(0) // +1 } } ``` === "JS" ```javascript title="" function algorithm(n) { var a = 1; // +1 a += 1; // +1 a *= 2; // +1 // n 回ループ for(let i = 0; i < n; i++){ // +1(各反復で i ++ を実行) console.log(0); // +1 } } ``` === "TS" ```typescript title="" function algorithm(n: number): void{ var a: number = 1; // +1 a += 1; // +1 a *= 2; // +1 // n 回ループ for(let i = 0; i < n; i++){ // +1(各反復で i ++ を実行) console.log(0); // +1 } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // n 回ループ for (int i = 0; i < n; i++) { // +1(各反復で i ++ を実行) print(0); // +1 } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let mut a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // n 回ループ for _ in 0..n { // +1(各反復で i ++ を実行) println!("{}", 0); // +1 } } ``` === "C" ```c title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // n 回ループ for (int i = 0; i < n; i++) { // +1(各反復で i ++ を実行) printf("%d", 0); // +1 } } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { var a = 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // n 回ループ for (i in 0..ビッグ $O$ 記法(big-$O$ notation)と呼ばれ、関数 $T(n)$ の漸近上界(asymptotic upper bound)を表します。 時間計算量の分析は本質的に「操作回数 $T(n)$」の漸近上界を求めることであり、明確な数学的定義があります。 !!! note "関数の漸近上界" 正の実数 $c$ と実数 $n_0$ が存在し、すべての $n > n_0$ について $T(n) \leq c \cdot f(n)$ が成り立つならば、$f(n)$ は $T(n)$ の漸近上界の 1 つであるとみなせます。これを $T(n) = O(f(n))$ と記します。 下図のように、漸近上界を求めるとは関数 $f(n)$ を探すことであり、$n$ が無限大へ近づくときに $T(n)$ と $f(n)$ が同じ増加オーダーにあり、定数係数 $c$ だけが異なる状態を表します。 ![関数の漸近上界](time_complexity.assets/asymptotic_upper_bound.png) ## 求め方 漸近上界はやや数学色が強い概念ですが、完全に理解できていなくても心配はいりません。まずは求め方を押さえ、実践を重ねる中で徐々にその数学的意味をつかめば十分です。 定義より、$f(n)$ が定まれば時間計算量 $O(f(n))$ が得られます。では、漸近上界 $f(n)$ をどのように決めればよいのでしょうか。大きく 2 段階あります。まず操作回数を数え、その後で漸近上界を判断します。 ### 第 1 ステップ:操作回数を数える コードについては、上から下へ 1 行ずつ数えれば十分です。しかし、前述の $c \cdot f(n)$ における定数係数 $c$ は任意に大きく取れるため、**操作回数 $T(n)$ に含まれるさまざまな係数や定数項は無視できます**。この原則から、次のような簡略化のコツが得られます。 1. **$T(n)$ 中の定数を無視する**。それらはすべて $n$ と無関係なので、時間計算量には影響しません。 2. **すべての係数を省略する**。例えば $2n$ 回や $5n + 1$ 回のループは、いずれも $n$ 回と簡略化できます。$n$ の前の係数は時間計算量に影響しないからです。 3. **ループが入れ子のときは乗算を使う**。総操作回数は外側のループと内側のループの操作回数の積に等しく、各ループ層には引き続き `1.` と `2.` のコツをそれぞれ適用できます。 次の関数では、上記のコツを使って操作回数を数えられます: === "Python" ```python title="" def algorithm(n: int): a = 1 # +0(コツ 1) a = a + n # +0(コツ 1) # +n(コツ 2) for i in range(5 * n + 1): print(0) # +n*n(コツ 3) for i in range(2 * n): for j in range(n + 1): print(0) ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 1; // +0(コツ 1) a = a + n; // +0(コツ 1) // +n(コツ 2) for (int i = 0; i < 5 * n + 1; i++) { cout << 0 << endl; } // +n*n(コツ 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { cout << 0 << endl; } } } ``` === "Java" ```java title="" void algorithm(int n) { int a = 1; // +0(コツ 1) a = a + n; // +0(コツ 1) // +n(コツ 2) for (int i = 0; i < 5 * n + 1; i++) { System.out.println(0); } // +n*n(コツ 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { System.out.println(0); } } } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 1; // +0(コツ 1) a = a + n; // +0(コツ 1) // +n(コツ 2) for (int i = 0; i < 5 * n + 1; i++) { Console.WriteLine(0); } // +n*n(コツ 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { Console.WriteLine(0); } } } ``` === "Go" ```go title="" func algorithm(n int) { a := 1 // +0(コツ 1) a = a + n // +0(コツ 1) // +n(コツ 2) for i := 0; i < 5 * n + 1; i++ { fmt.Println(0) } // +n*n(コツ 3) for i := 0; i < 2 * n; i++ { for j := 0; j < n + 1; j++ { fmt.Println(0) } } } ``` === "Swift" ```swift title="" func algorithm(n: Int) { var a = 1 // +0(コツ 1) a = a + n // +0(コツ 1) // +n(コツ 2) for _ in 0 ..< (5 * n + 1) { print(0) } // +n*n(コツ 3) for _ in 0 ..< (2 * n) { for _ in 0 ..< (n + 1) { print(0) } } } ``` === "JS" ```javascript title="" function algorithm(n) { let a = 1; // +0(コツ 1) a = a + n; // +0(コツ 1) // +n(コツ 2) for (let i = 0; i < 5 * n + 1; i++) { console.log(0); } // +n*n(コツ 3) for (let i = 0; i < 2 * n; i++) { for (let j = 0; j < n + 1; j++) { console.log(0); } } } ``` === "TS" ```typescript title="" function algorithm(n: number): void { let a = 1; // +0(コツ 1) a = a + n; // +0(コツ 1) // +n(コツ 2) for (let i = 0; i < 5 * n + 1; i++) { console.log(0); } // +n*n(コツ 3) for (let i = 0; i < 2 * n; i++) { for (let j = 0; j < n + 1; j++) { console.log(0); } } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 1; // +0(コツ 1) a = a + n; // +0(コツ 1) // +n(コツ 2) for (int i = 0; i < 5 * n + 1; i++) { print(0); } // +n*n(コツ 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { print(0); } } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let mut a = 1; // +0(コツ 1) a = a + n; // +0(コツ 1) // +n(コツ 2) for i in 0..(5 * n + 1) { println!("{}", 0); } // +n*n(コツ 3) for i in 0..(2 * n) { for j in 0..(n + 1) { println!("{}", 0); } } } ``` === "C" ```c title="" void algorithm(int n) { int a = 1; // +0(コツ 1) a = a + n; // +0(コツ 1) // +n(コツ 2) for (int i = 0; i < 5 * n + 1; i++) { printf("%d", 0); } // +n*n(コツ 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { printf("%d", 0); } } } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { var a = 1 // +0(コツ 1) a = a + n // +0(コツ 1) // +n(コツ 2) for (i in 0..<5 * n + 1) { println(0) } // +n*n(コツ 3) for (i in 0..<2 * n) { for (j in 0..   異なる操作回数に対応する時間計算量

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

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

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

  指数部の意味

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

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

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

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

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

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

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

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

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

  要素検索効率の比較

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

  ヒープの操作効率

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

Hello、アルゴリズム!

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

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

| データ構造とアルゴリズム | 組み立てブロック | | -------------- | ---------------------------------------- | | 入力データ | まだ組み立てていないブロック | | データ構造 | ブロックの構成形式。形状、大きさ、接続方法などを含む | | アルゴリズム | ブロックを目標の形に組み上げる一連の操作手順 | | 出力データ | ブロック模型 | 特筆すべき点として、データ構造とアルゴリズムはプログラミング言語から独立しています。だからこそ、本書では複数のプログラミング言語に基づく実装を提供できます。 !!! tip "慣習的な略称" 実際の議論では、私たちは通常「データ構造とアルゴリズム」を略して「アルゴリズム」と呼びます。たとえば広く知られている LeetCode のアルゴリズム問題は、実際にはデータ構造とアルゴリズムの両方の知識を同時に問うています。 ================================================ FILE: ja/docs/chapter_paperbook/index.md ================================================ --- comments: true icon: material/book-open-page-variant status: new --- # 紙の書籍 長い時間をかけて磨き上げた『Hello アルゴリズム』の紙の書籍が、ついに発売されました!今の気持ちは、次の一節で表せます:

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

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

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

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

  両端キューの操作効率

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

  キュー操作の効率

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

  スタックの操作効率

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

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

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

  配列と探索木の効率比較

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

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

| | 充足二分木 | 連結リスト | | --------------------------- | ------------------ | ------- | | 第 $i$ レベルのノード数 | $2^{i-1}$ | $1$ | | 高さ $h$ の木の葉ノード数 | $2^h$ | $1$ | | 高さ $h$ の木のノード総数 | $2^{h+1} - 1$ | $h + 1$ | | ノード総数 $n$ の木の高さ | $\log_2 (n+1) - 1$ | $n - 1$ | ================================================ FILE: ja/docs/chapter_tree/binary_tree_traversal.md ================================================ # 二分木の走査 物理構造の観点から見ると、木は連結リストを基盤としたデータ構造であり、その走査はポインタを通じてノードへ順にアクセスすることで行われます。しかし、木は非線形データ構造であるため、木の走査は連結リストの走査よりも複雑であり、検索アルゴリズムを用いて実現する必要があります。 二分木の一般的な走査方法には、レベル順走査、先行順走査、中間順走査、後行順走査などがあります。 ## レベル順走査 次の図に示すように、レベル順走査(level-order traversal)では、二分木を上から下へ層ごとに走査し、各層では左から右の順にノードへアクセスします。 レベル順走査は本質的に幅優先走査(breadth-first traversal)に属し、幅優先探索(breadth-first search, BFS)とも呼ばれます。これは「同心円状に外側へ広がる」ような、層ごとの走査方法を表しています。 ![二分木のレベル順走査](binary_tree_traversal.assets/binary_tree_bfs.png) ### コードの実装 幅優先走査は通常「キュー」を用いて実装します。キューは「先入れ先出し」の規則に従い、幅優先走査は「層ごとに進む」という規則に従います。両者の背後にある考え方は一致しています。実装コードは次のとおりです: ```src [file]{binary_tree_bfs}-[class]{}-[func]{level_order} ``` ### 計算量 - **時間計算量は $O(n)$** :すべてのノードを1回ずつ訪問するため、計算量は $O(n)$ です。ここで、$n$ はノード数です。 - **空間計算量は $O(n)$** :最悪の場合、すなわち完全二分木では、最下層に到達する前に、キュー内には最大で同時に $(n + 1) / 2$ 個のノードが存在し、$O(n)$ の空間を使用します。 ## 先行順・中間順・後行順走査 同様に、先行順・中間順・後行順走査はいずれも深度優先走査(depth-first traversal)に属し、深度優先探索(depth-first search, DFS)とも呼ばれます。これは「まず行き止まりまで進み、その後で戻って続ける」という走査方法を表しています。 次の図は、二分木に対して深度優先走査を行う仕組みを示しています。**深度優先走査は、二分木全体の外周をぐるりと「一周する」ようなものです**。各ノードでは3つの位置に出会い、それぞれが先行順走査・中間順走査・後行順走査に対応します。 ![二分探索木の先行順・中間順・後行順走査](binary_tree_traversal.assets/binary_tree_dfs.png) ### コードの実装 深度優先探索は通常、再帰に基づいて実装されます: ```src [file]{binary_tree_dfs}-[class]{}-[func]{post_order} ``` !!! tip 深度優先探索は反復によって実装することもできます。興味のある読者は自身で調べてみてください。 次の図は、二分木の先行順走査における再帰の過程を示しており、「行き」と「帰り」という2つの逆向きの部分に分けられます。 1. 「行き」は新しいメソッドの開始を表し、この過程でプログラムは次のノードにアクセスします。 2. 「帰り」は関数の復帰を表し、現在のノードへのアクセスが完了したことを意味します。 === "<1>" ![先行順走査の再帰過程](binary_tree_traversal.assets/preorder_step1.png) === "<2>" ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png) === "<3>" ![preorder_step3](binary_tree_traversal.assets/preorder_step3.png) === "<4>" ![preorder_step4](binary_tree_traversal.assets/preorder_step4.png) === "<5>" ![preorder_step5](binary_tree_traversal.assets/preorder_step5.png) === "<6>" ![preorder_step6](binary_tree_traversal.assets/preorder_step6.png) === "<7>" ![preorder_step7](binary_tree_traversal.assets/preorder_step7.png) === "<8>" ![preorder_step8](binary_tree_traversal.assets/preorder_step8.png) === "<9>" ![preorder_step9](binary_tree_traversal.assets/preorder_step9.png) === "<10>" ![preorder_step10](binary_tree_traversal.assets/preorder_step10.png) === "<11>" ![preorder_step11](binary_tree_traversal.assets/preorder_step11.png) ### 計算量 - **時間計算量は $O(n)$** :すべてのノードを1回ずつ訪問するため、計算量は $O(n)$ です。 - **空間計算量は $O(n)$** :最悪の場合、すなわち木が連結リストに退化したとき、再帰の深さは $n$ に達し、システムは $O(n)$ のスタックフレーム空間を使用します。 ================================================ FILE: ja/docs/chapter_tree/index.md ================================================ # 木 ![木](../assets/covers/chapter_tree.jpg) !!! abstract 大樹は生命力に満ち、根は深く葉は生い茂り、枝は豊かに広がる。 それはデータ分割統治の生き生きとした姿を私たちに示してくれる。 ================================================ FILE: ja/docs/chapter_tree/summary.md ================================================ # まとめ ### 要点の振り返り - 二分木は非線形データ構造の一種であり、「二分する」分割統治の考え方を体現している。各二分木ノードは 1 つの値と 2 本のポインタを持ち、それぞれ左子ノードと右子ノードを指す。 - 二分木のあるノードについて、その左(右)子ノードおよびその配下から構成される木を、そのノードの左(右)部分木と呼ぶ。 - 二分木に関する用語には、根ノード、葉ノード、レベル、次数、辺、高さ、深さなどがある。 - 二分木の初期化、ノードの挿入、ノードの削除は、連結リストの操作方法と似ている。 - 一般的な二分木の種類には、perfect 二分木、complete 二分木、full 二分木、平衡二分木がある。perfect 二分木が最も理想的な状態であり、連結リストは退化後の最悪の状態である。 - 二分木は配列で表現できる。方法としては、ノード値と空き位置をレベル順走査の順に並べ、親ノードと子ノードのインデックス対応関係に基づいてポインタを実現する。 - 二分木のレベル順走査は幅優先探索の一種であり、「同心円状に外へ広がる」ような逐次的な走査方式を表しており、通常はキューによって実装される。 - 前順、中順、後順走査はいずれも深さ優先探索に属し、「まず末端まで進み、その後バックトラックして続ける」という走査方式を体現しており、通常は再帰で実装される。 - 二分探索木は効率的な要素探索データ構造であり、探索、挿入、削除の時間計算量はいずれも $O(\log n)$ である。二分探索木が連結リストへ退化すると、各操作の時間計算量は $O(n)$ まで悪化する。 - AVL 木は平衡二分探索木とも呼ばれ、回転操作によって、ノードの挿入と削除を繰り返した後も木が平衡を保つようにしている。 - AVL 木の回転操作には、右回転、左回転、右回転してから左回転、左回転してから右回転がある。ノードの挿入または削除の後、AVL 木は下から上へ回転操作を行い、木を再び平衡状態に戻す。 ### Q & A **Q**:ノードが 1 つしかない二分木では、木の高さと根ノードの深さはどちらも $0$ ですか? はい。高さと深さは通常「通過した辺の本数」として定義されるからです。 **Q**:二分木における挿入と削除は通常一連の操作を組み合わせて完了しますが、ここでいう「一連の操作」とは何を指すのでしょうか?リソースの子ノードに対するリソース解放と理解できますか? 二分探索木を例にすると、ノード削除は 3 つのケースに分けて処理する必要があり、各ケースで複数段階のノード操作が必要になります。 **Q**:なぜ DFS による二分木走査には前順・中順・後順の 3 種類があり、それぞれどのような用途があるのですか? 配列の順方向走査と逆方向走査に似て、前順・中順・後順走査は二分木の 3 つの走査方法であり、特定の順序で走査結果を得るために使えます。たとえば二分探索木では、ノードの大小関係が `左子ノードの値 < 根ノードの値 < 右子ノードの値` を満たすため、「左 $\rightarrow$ 根 $\rightarrow$ 右」の優先順で木を走査すれば、整列済みのノード列を得られます。 **Q**:右回転操作は不平衡ノード `node`、`child`、`grand_child` の関係を処理するものですが、`node` の親ノードと `node` の元の接続は維持しなくてよいのですか?右回転後に切れてしまいませんか? この問題は再帰の視点から考える必要があります。右回転操作 `right_rotate(root)` に渡されるのは部分木の根ノードであり、最終的に `return child` によって回転後の部分木の根ノードを返します。部分木の根ノードとその親ノードの接続は、この関数の返却後に行われるため、右回転操作自身が管理する範囲には含まれません。 **Q**:C++ では関数を `private` と `public` に分けますが、この設計にはどのような考えがありますか?なぜ `height()` 関数と `updateHeight()` 関数をそれぞれ `public` と `private` に置くのですか? 主に、そのメソッドの利用範囲を見て決めます。メソッドがクラス内部でしか使われないなら、`private` に設計します。たとえば、利用者が `updateHeight()` を単独で呼び出しても意味はなく、これは挿入や削除の途中の 1 ステップにすぎません。一方で `height()` はノードの高さにアクセスするためのもので、`vector.size()` に似た役割を持つため、使いやすいように `public` に設定します。 **Q**:入力データの集合から二分探索木をどのように構築しますか?根ノードの選び方は重要ですか? はい。木の構築方法は、二分探索木のコード中の `build_tree()` メソッドですでに示されています。根ノードの選択については、通常は入力データをソートし、その中央の要素を根ノードにしてから、左右の部分木を再帰的に構築します。こうすることで、木の平衡性を最大限に保てます。 **Q**:Java では、文字列比較には必ず `equals()` メソッドを使うべきですか? Java では、基本データ型については `==` を使って 2 つの変数の値が等しいかどうかを比較します。参照型については、この 2 つの記法の働き方は異なります。 - `==` :2 つの変数が同じオブジェクトを指しているか、つまりメモリ上の位置が同じかどうかを比較するために使います。 - `equals()`:2 つのオブジェクトの値が等しいかどうかを比較するために使います。 したがって、値を比較したい場合は `equals()` を使うべきです。ただし、`String a = "hi"; String b = "hi";` によって初期化された文字列は文字列定数プールに格納され、同じオブジェクトを指すため、`a == b` でも 2 つの文字列の内容を比較できます。 **Q**:幅優先走査で最下層に到達する前、キュー内のノード数は $2^h$ ですか? はい。たとえば高さ $h = 2$ の充足二分木では、ノード総数は $n = 7$ であり、最下層のノード数は $4 = 2^h = (n + 1) / 2$ です。 ================================================ FILE: ja/docs/index.html ================================================

アニメーション図解とワンクリック実行で学ぶデータ構造とアルゴリズムのチュートリアル

読み始める GitHub

500枚のアニメーション図解、14種類のプログラミング言語コード、3000件のコミュニティQ&Aで、データ構造とアルゴリズムの学習をすばやく始めよう

推薦文

「わかりやすいデータ構造とアルゴリズムの入門書で、読者を手と頭の両方を使う学びへと導いてくれます。アルゴリズムを初めて学ぶ人に強くおすすめします。」

—— Junhui Deng、清華大学コンピュータ科学科教授

「もし私がデータ構造とアルゴリズムを学んでいた当時に『Hello アルゴリズム』があったなら、学ぶのは10倍は簡単だったはずです!」

—— Mu Li、Amazon シニアプリンシパルサイエンティスト

アニメーション図解

内容は明快で理解しやすく、学習曲線もなだらかです

"A picture is worth a thousand words."
「一枚の絵は千の言葉に値する」

ワンクリック実行

10種類以上のプログラミング言語に対応し、コードを可視化して実行できます

"Talk is cheap. Show me the code."
「口で言うよりコードを見せよ」

相互学習

議論や質問を歓迎し、読者同士が支え合いながら学べます

"Learning by teaching."
「教えることで学ぶ」

翻訳者

本書の日本語版は、以下の翻訳者によってレビューされました。ご貢献に感謝します!

コード翻訳者

本書の多言語コード版は、以下の開発者の協力によって実現しました。ご尽力に感謝します!

貢献者

本書は、オープンソースコミュニティの200名を超える貢献者の共同作業によって継続的に改善されています。皆さんの時間と労力に感謝します!

Contributors
================================================ FILE: ja/docs/index.md ================================================ # Hello アルゴリズム アニメーション図解、ワンクリックで実行できるデータ構造とアルゴリズムのチュートリアル。 [読み始める](chapter_hello_algo/) ================================================ FILE: ja/mkdocs.yml ================================================ # Config inheritance INHERIT: ../mkdocs.yml # Project information site_name: Hello アルゴリズム site_url: https://www.hello-algo.com/ja/ site_description: "アニメーション図解とワンクリック実行コードで学べるデータ構造とアルゴリズムの入門書" docs_dir: ../build/ja/docs site_dir: ../site/ja # Repository edit_uri: tree/main/ja/docs version: 1.3.0 # Configuration theme: custom_dir: ../build/overrides language: ja font: text: Noto Sans JP palette: - scheme: default primary: white accent: teal toggle: icon: material/theme-light-dark name: ダークモード - scheme: slate primary: black accent: teal toggle: icon: material/theme-light-dark name: ライトモード extra: status: new: 最近追加 # Page tree nav: - はじめに: - chapter_hello_algo/index.md - 第 0 章   前書き: # [icon: material/book-open-outline] - chapter_preface/index.md - 0.1   本書について: chapter_preface/about_the_book.md - 0.2   本書の使い方: chapter_preface/suggestions.md - 0.3   まとめ: chapter_preface/summary.md - 第 1 章   アルゴリズムを知る: # [icon: material/calculator-variant-outline] - chapter_introduction/index.md - 1.1   アルゴリズムは至るところにある: chapter_introduction/algorithms_are_everywhere.md - 1.2   アルゴリズムとは: chapter_introduction/what_is_dsa.md - 1.3   まとめ: chapter_introduction/summary.md - 第 2 章   計算量解析: # [icon: material/timer-sand] - chapter_computational_complexity/index.md - 2.1   アルゴリズム効率の評価: chapter_computational_complexity/performance_evaluation.md - 2.2   反復と再帰: chapter_computational_complexity/iteration_and_recursion.md - 2.3   時間計算量: chapter_computational_complexity/time_complexity.md - 2.4   空間計算量: chapter_computational_complexity/space_complexity.md - 2.5   まとめ: chapter_computational_complexity/summary.md - 第 3 章   データ構造: # [icon: material/shape-outline] - chapter_data_structure/index.md - 3.1   データ構造の分類: chapter_data_structure/classification_of_data_structure.md - 3.2   基本データ型: chapter_data_structure/basic_data_types.md - 3.3   数値エンコーディング *: chapter_data_structure/number_encoding.md - 3.4   文字エンコーディング *: chapter_data_structure/character_encoding.md - 3.5   まとめ: chapter_data_structure/summary.md - 第 4 章   配列と連結リスト: # [icon: material/view-list-outline] - chapter_array_and_linkedlist/index.md - 4.1   配列: chapter_array_and_linkedlist/array.md - 4.2   連結リスト: chapter_array_and_linkedlist/linked_list.md - 4.3   リスト: chapter_array_and_linkedlist/list.md - 4.4   メモリとキャッシュ *: chapter_array_and_linkedlist/ram_and_cache.md - 4.5   まとめ: chapter_array_and_linkedlist/summary.md - 第 5 章   スタックとキュー: # [icon: material/stack-overflow] - chapter_stack_and_queue/index.md - 5.1   スタック: chapter_stack_and_queue/stack.md - 5.2   キュー: chapter_stack_and_queue/queue.md - 5.3   両端キュー: chapter_stack_and_queue/deque.md - 5.4   まとめ: chapter_stack_and_queue/summary.md - 第 6 章   ハッシュテーブル: # [icon: material/table-search] - chapter_hashing/index.md - 6.1   ハッシュテーブル: chapter_hashing/hash_map.md - 6.2   ハッシュ衝突: chapter_hashing/hash_collision.md - 6.3   ハッシュアルゴリズム: chapter_hashing/hash_algorithm.md - 6.4   まとめ: chapter_hashing/summary.md - 第 7 章   木: # [icon: material/graph-outline] - chapter_tree/index.md - 7.1   二分木: chapter_tree/binary_tree.md - 7.2   二分木の走査: chapter_tree/binary_tree_traversal.md - 7.3   二分木の配列表現: chapter_tree/array_representation_of_tree.md - 7.4   二分探索木: chapter_tree/binary_search_tree.md - 7.5   AVL 木 *: chapter_tree/avl_tree.md - 7.6   まとめ: chapter_tree/summary.md - 第 8 章   ヒープ: # [icon: material/family-tree] - chapter_heap/index.md - 8.1   ヒープ: chapter_heap/heap.md - 8.2   ヒープ構築: chapter_heap/build_heap.md - 8.3   Top-k 問題: chapter_heap/top_k.md - 8.4   まとめ: chapter_heap/summary.md - 第 9 章   グラフ: # [icon: material/graphql] - chapter_graph/index.md - 9.1   グラフ: chapter_graph/graph.md - 9.2   グラフの基本操作: chapter_graph/graph_operations.md - 9.3   グラフの走査: chapter_graph/graph_traversal.md - 9.4   まとめ: chapter_graph/summary.md - 第 10 章   探索: # [icon: material/text-search] - chapter_searching/index.md - 10.1   二分探索: chapter_searching/binary_search.md - 10.2   二分探索の挿入位置: chapter_searching/binary_search_insertion.md - 10.3   二分探索の境界: chapter_searching/binary_search_edge.md - 10.4   ハッシュによる最適化戦略: chapter_searching/replace_linear_by_hashing.md - 10.5   探索アルゴリズム再考: chapter_searching/searching_algorithm_revisited.md - 10.6   まとめ: chapter_searching/summary.md - 第 11 章   ソート: # [icon: material/sort-ascending] - chapter_sorting/index.md - 11.1   ソートアルゴリズム: chapter_sorting/sorting_algorithm.md - 11.2   選択ソート: chapter_sorting/selection_sort.md - 11.3   バブルソート: chapter_sorting/bubble_sort.md - 11.4   挿入ソート: chapter_sorting/insertion_sort.md - 11.5   クイックソート: chapter_sorting/quick_sort.md - 11.6   マージソート: chapter_sorting/merge_sort.md - 11.7   ヒープソート: chapter_sorting/heap_sort.md - 11.8   バケットソート: chapter_sorting/bucket_sort.md - 11.9   計数ソート: chapter_sorting/counting_sort.md - 11.10   基数ソート: chapter_sorting/radix_sort.md - 11.11   まとめ: chapter_sorting/summary.md - 第 12 章   分割統治: # [icon: material/set-split] - chapter_divide_and_conquer/index.md - 12.1   分割統治法: chapter_divide_and_conquer/divide_and_conquer.md - 12.2   分割統治探索戦略: chapter_divide_and_conquer/binary_search_recur.md - 12.3   二分木の構築問題: chapter_divide_and_conquer/build_binary_tree_problem.md - 12.4   ハノイの塔の問題: chapter_divide_and_conquer/hanota_problem.md - 12.5   まとめ: chapter_divide_and_conquer/summary.md - 第 13 章   バックトラッキング: # [icon: material/map-marker-path] - chapter_backtracking/index.md - 13.1   バックトラッキングアルゴリズム: chapter_backtracking/backtracking_algorithm.md - 13.2   全順列問題: chapter_backtracking/permutations_problem.md - 13.3   部分和問題: chapter_backtracking/subset_sum_problem.md - 13.4   n クイーン問題: chapter_backtracking/n_queens_problem.md - 13.5   まとめ: chapter_backtracking/summary.md - 第 14 章   動的計画法: # [icon: material/table-pivot] - chapter_dynamic_programming/index.md - 14.1   動的計画法入門: chapter_dynamic_programming/intro_to_dynamic_programming.md - 14.2   動的計画法の問題特性: chapter_dynamic_programming/dp_problem_features.md - 14.3   動的計画法の問題解決の考え方: chapter_dynamic_programming/dp_solution_pipeline.md - 14.4   0-1 ナップサック問題: chapter_dynamic_programming/knapsack_problem.md - 14.5   完全ナップサック問題: chapter_dynamic_programming/unbounded_knapsack_problem.md - 14.6   編集距離問題: chapter_dynamic_programming/edit_distance_problem.md - 14.7   まとめ: chapter_dynamic_programming/summary.md - 第 15 章   貪欲法: # [icon: material/head-heart-outline] - chapter_greedy/index.md - 15.1   貪欲法: chapter_greedy/greedy_algorithm.md - 15.2   分数ナップサック問題: chapter_greedy/fractional_knapsack_problem.md - 15.3   最大容量問題: chapter_greedy/max_capacity_problem.md - 15.4   最大積分割問題: chapter_greedy/max_product_cutting_problem.md - 15.5   まとめ: chapter_greedy/summary.md - 第 16 章   付録: # [icon: material/help-circle-outline] - chapter_appendix/index.md - 16.1   プログラミング環境のインストール: chapter_appendix/installation.md - 16.2   一緒に制作に参加しましょう: chapter_appendix/contribution.md - 16.3   用語集: chapter_appendix/terminology.md - 参考文献: - chapter_reference/index.md ================================================ FILE: mkdocs.yml ================================================ # Project information site_name: Hello 算法 site_url: https://www.hello-algo.com/ site_author: krahets site_description: 动画图解、一键运行的数据结构与算法教程 docs_dir: build/docs site_dir: site # Repository repo_name: krahets/hello-algo repo_url: https://github.com/krahets/hello-algo edit_uri: tree/main/docs version: 1.3.0 # Copyright copyright: Copyright © 2026 krahets
The website content is licensed under CC BY-NC-SA 4.0 # Configuration theme: name: material custom_dir: build/overrides language: zh font: text: Noto Sans SC code: JetBrains Mono features: # - announce.dismiss - content.action.edit # - content.action.view - content.code.annotate - content.code.copy - content.tabs.link - content.tooltips # - header.autohide # - navigation.expand - navigation.indexes # - navigation.instant # - navigation.prune # - navigation.sections # - navigation.tabs # - navigation.tabs.sticky - navigation.top - navigation.footer - navigation.tracking - search.highlight - search.share - search.suggest - toc.follow # - toc.integrate palette: - scheme: default primary: white accent: teal toggle: icon: material/theme-light-dark name: 深色模式 - scheme: slate primary: black accent: teal toggle: icon: material/theme-light-dark name: 浅色模式 favicon: assets/images/favicon.png logo: assets/images/logo.svg icon: logo: logo repo: fontawesome/brands/github edit: fontawesome/regular/pen-to-square extra: alternate: - name: 简体中文 link: / lang: zh - name: 繁體中文 link: /zh-hant/ lang: zh-Hant - name: English link: /en/ lang: en - name: 日本語 link: /ja/ lang: ja - name: Русский link: /ru/ lang: ru social: - icon: fontawesome/brands/github link: https://github.com/krahets - icon: fontawesome/brands/x-twitter link: https://twitter.com/krahets - icon: fontawesome/solid/code link: https://leetcode.cn/u/jyd/ generator: false status: new: 最近添加 # Plugins plugins: - search - glightbox: touchNavigation: true loop: false effect: zoom slide_effect: none width: 100% height: auto zoomable: true draggable: false auto_caption: false caption_position: bottom # Extensions markdown_extensions: - abbr - admonition - attr_list - def_list - footnotes - md_in_html - toc: permalink: true - pymdownx.arithmatex: generic: true - pymdownx.betterem: smart_enable: all - pymdownx.caret - pymdownx.details # - pymdownx.emoji: # emoji_index: !!python/name:materialx.emoji.twemoji # emoji_generator: !!python/name:materialx.emoji.to_svg - pymdownx.highlight: anchor_linenums: true - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences - pymdownx.keys # - pymdownx.magiclink: # repo_url_shorthand: true # user: squidfunk # repo: mkdocs-material - pymdownx.mark - pymdownx.smartsymbols - pymdownx.tabbed: alternate_style: true - pymdownx.tasklist: custom_checkbox: true - pymdownx.tilde extra_javascript: - javascripts/mathjax.js - https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-mml-chtml.min.js # - javascripts/katex.js # - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.js # - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/contrib/auto-render.min.js extra_css: - stylesheets/extra.css # - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css # Page tree nav: - 序: - chapter_hello_algo/index.md - 第 0 章   前言: # [icon: material/book-open-outline] - chapter_preface/index.md - 0.1   关于本书: chapter_preface/about_the_book.md - 0.2   如何使用本书: chapter_preface/suggestions.md - 0.3   小结: chapter_preface/summary.md - 第 1 章   初识算法: # [icon: material/calculator-variant-outline] - chapter_introduction/index.md - 1.1   算法无处不在: chapter_introduction/algorithms_are_everywhere.md - 1.2   算法是什么: chapter_introduction/what_is_dsa.md - 1.3   小结: chapter_introduction/summary.md - 第 2 章   复杂度分析: # [icon: material/timer-sand] - chapter_computational_complexity/index.md - 2.1   算法效率评估: chapter_computational_complexity/performance_evaluation.md - 2.2   迭代与递归: chapter_computational_complexity/iteration_and_recursion.md - 2.3   时间复杂度: chapter_computational_complexity/time_complexity.md - 2.4   空间复杂度: chapter_computational_complexity/space_complexity.md - 2.5   小结: chapter_computational_complexity/summary.md - 第 3 章   数据结构: # [icon: material/shape-outline] - chapter_data_structure/index.md - 3.1   数据结构分类: chapter_data_structure/classification_of_data_structure.md - 3.2   基本数据类型: chapter_data_structure/basic_data_types.md - 3.3   数字编码 *: chapter_data_structure/number_encoding.md - 3.4   字符编码 *: chapter_data_structure/character_encoding.md - 3.5   小结: chapter_data_structure/summary.md - 第 4 章   数组与链表: # [icon: material/view-list-outline] - chapter_array_and_linkedlist/index.md - 4.1   数组: chapter_array_and_linkedlist/array.md - 4.2   链表: chapter_array_and_linkedlist/linked_list.md - 4.3   列表: chapter_array_and_linkedlist/list.md - 4.4   内存与缓存 *: chapter_array_and_linkedlist/ram_and_cache.md - 4.5   小结: chapter_array_and_linkedlist/summary.md - 第 5 章   栈与队列: # [icon: material/stack-overflow] - chapter_stack_and_queue/index.md - 5.1   栈: chapter_stack_and_queue/stack.md - 5.2   队列: chapter_stack_and_queue/queue.md - 5.3   双向队列: chapter_stack_and_queue/deque.md - 5.4   小结: chapter_stack_and_queue/summary.md - 第 6 章   哈希表: # [icon: material/table-search] - chapter_hashing/index.md - 6.1   哈希表: chapter_hashing/hash_map.md - 6.2   哈希冲突: chapter_hashing/hash_collision.md - 6.3   哈希算法: chapter_hashing/hash_algorithm.md - 6.4   小结: chapter_hashing/summary.md - 第 7 章   树: # [icon: material/graph-outline] - chapter_tree/index.md - 7.1   二叉树: chapter_tree/binary_tree.md - 7.2   二叉树遍历: chapter_tree/binary_tree_traversal.md - 7.3   二叉树数组表示: chapter_tree/array_representation_of_tree.md - 7.4   二叉搜索树: chapter_tree/binary_search_tree.md - 7.5   AVL 树 *: chapter_tree/avl_tree.md - 7.6   小结: chapter_tree/summary.md - 第 8 章   堆: # [icon: material/family-tree] - chapter_heap/index.md - 8.1   堆: chapter_heap/heap.md - 8.2   建堆操作: chapter_heap/build_heap.md - 8.3   Top-k 问题: chapter_heap/top_k.md - 8.4   小结: chapter_heap/summary.md - 第 9 章   图: # [icon: material/graphql] - chapter_graph/index.md - 9.1   图: chapter_graph/graph.md - 9.2   图基础操作: chapter_graph/graph_operations.md - 9.3   图的遍历: chapter_graph/graph_traversal.md - 9.4   小结: chapter_graph/summary.md - 第 10 章   搜索: # [icon: material/text-search] - chapter_searching/index.md - 10.1   二分查找: chapter_searching/binary_search.md - 10.2   二分查找插入点: chapter_searching/binary_search_insertion.md - 10.3   二分查找边界: chapter_searching/binary_search_edge.md - 10.4   哈希优化策略: chapter_searching/replace_linear_by_hashing.md - 10.5   重识搜索算法: chapter_searching/searching_algorithm_revisited.md - 10.6   小结: chapter_searching/summary.md - 第 11 章   排序: # [icon: material/sort-ascending] - chapter_sorting/index.md - 11.1   排序算法: chapter_sorting/sorting_algorithm.md - 11.2   选择排序: chapter_sorting/selection_sort.md - 11.3   冒泡排序: chapter_sorting/bubble_sort.md - 11.4   插入排序: chapter_sorting/insertion_sort.md - 11.5   快速排序: chapter_sorting/quick_sort.md - 11.6   归并排序: chapter_sorting/merge_sort.md - 11.7   堆排序: chapter_sorting/heap_sort.md - 11.8   桶排序: chapter_sorting/bucket_sort.md - 11.9   计数排序: chapter_sorting/counting_sort.md - 11.10   基数排序: chapter_sorting/radix_sort.md - 11.11   小结: chapter_sorting/summary.md - 第 12 章   分治: # [icon: material/set-split] - chapter_divide_and_conquer/index.md - 12.1   分治算法: chapter_divide_and_conquer/divide_and_conquer.md - 12.2   分治搜索策略: chapter_divide_and_conquer/binary_search_recur.md - 12.3   构建树问题: chapter_divide_and_conquer/build_binary_tree_problem.md - 12.4   汉诺塔问题: chapter_divide_and_conquer/hanota_problem.md - 12.5   小结: chapter_divide_and_conquer/summary.md - 第 13 章   回溯: # [icon: material/map-marker-path] - chapter_backtracking/index.md - 13.1   回溯算法: chapter_backtracking/backtracking_algorithm.md - 13.2   全排列问题: chapter_backtracking/permutations_problem.md - 13.3   子集和问题: chapter_backtracking/subset_sum_problem.md - 13.4   N 皇后问题: chapter_backtracking/n_queens_problem.md - 13.5   小结: chapter_backtracking/summary.md - 第 14 章   动态规划: # [icon: material/table-pivot] - chapter_dynamic_programming/index.md - 14.1   初探动态规划: chapter_dynamic_programming/intro_to_dynamic_programming.md - 14.2   DP 问题特性: chapter_dynamic_programming/dp_problem_features.md - 14.3   DP 解题思路: chapter_dynamic_programming/dp_solution_pipeline.md - 14.4   0-1 背包问题: chapter_dynamic_programming/knapsack_problem.md - 14.5   完全背包问题: chapter_dynamic_programming/unbounded_knapsack_problem.md - 14.6   编辑距离问题: chapter_dynamic_programming/edit_distance_problem.md - 14.7   小结: chapter_dynamic_programming/summary.md - 第 15 章   贪心: # [icon: material/head-heart-outline] - chapter_greedy/index.md - 15.1   贪心算法: chapter_greedy/greedy_algorithm.md - 15.2   分数背包问题: chapter_greedy/fractional_knapsack_problem.md - 15.3   最大容量问题: chapter_greedy/max_capacity_problem.md - 15.4   最大切分乘积问题: chapter_greedy/max_product_cutting_problem.md - 15.5   小结: chapter_greedy/summary.md - 第 16 章   附录: # [icon: material/help-circle-outline] - chapter_appendix/index.md - 16.1   编程环境安装: chapter_appendix/installation.md - 16.2   一起参与创作: chapter_appendix/contribution.md - 16.3   术语表: chapter_appendix/terminology.md - 参考文献: - chapter_reference/index.md - 纸质书: - chapter_paperbook/index.md ================================================ FILE: overrides/javascripts/katex.js ================================================ document$.subscribe(({ body }) => { renderMathInElement(body, { delimiters: [ { left: "$$", right: "$$", display: true }, { left: "$", right: "$", display: false }, { left: "\\(", right: "\\)", display: false }, { left: "\\[", right: "\\]", display: true }, ], }); }); ================================================ FILE: overrides/javascripts/mathjax.js ================================================ window.MathJax = { tex: { inlineMath: [["\\(", "\\)"]], displayMath: [["\\[", "\\]"]], processEscapes: true, processEnvironments: true, }, options: { ignoreHtmlClass: ".*|", processHtmlClass: "arithmatex", enableMenu: false, }, }; document$.subscribe(() => { MathJax.typesetPromise(); }); ================================================ FILE: overrides/javascripts/starfield.js ================================================ /* * starfield.js * * Version: 1.5.0 * Description: Interactive starfield background * * Usage: * Starfield.setup({ * // options * }); */ (function (root, factory) { if (typeof define === "function" && define.amd) { define([], factory); } else if (typeof module === "object" && module.exports) { module.exports = factory(); } else { root.Starfield = factory(); } })(this, function () { const Starfield = {}; const config = { numStars: 250, // Number of stars baseSpeed: 1, // Base speed of stars (will affect acceleration) trailLength: 0.8, // Length of star trail (0-1) starColor: "rgb(255, 255, 255)", // Color of stars (only rgb) canvasColor: "rgb(30, 30, 30)", // Canvas background color (only rgb) hueJitter: 0, // Maximum hue variation in degrees (0-360) maxAcceleration: 10, // Maximum acceleration accelerationRate: 0.2, // Rate of acceleration decelerationRate: 0.2, // Rate of deceleration minSpawnRadius: 80, // Minimum spawn distance from origin maxSpawnRadius: 500, // Maximum spawn distance from origin auto: true, originX: null, originY: null, container: null, originElement: null, }; let stars = []; let accelerate = false; let accelerationFactor = 0; let originX = 0; let originY = 0; let prevOriginX = 0; let prevOriginY = 0; let canvas, ctx; let width, height; let lastTimestamp = 0; let canvasRGB = [0, 0, 0]; let lastCanvasColor = config.canvasColor; let origin; let container; const mouseEnterHandler = () => (accelerate = true); const mouseLeaveHandler = () => (accelerate = false); const resizeHandler = () => windowResized(container, origin); function visibilityHandler() { if (document.visibilityState === "visible") { lastTimestamp = performance.now(); } } function getOriginY(origin, container) { const originRect = origin.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); return originRect.top - containerRect.top + originRect.height / 2; } function getOriginX(origin, container) { const originRect = origin.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); return originRect.left - containerRect.left + originRect.width / 2; } /** * Set up and start the starfield animation. * @param {Object} userConfig Configuration options. */ function setup(userConfig = {}) { Object.assign(config, userConfig); container = config.container || document.querySelector(".starfield"); if (!container) { throw new Error("Starfield: No container element found."); } // container.style.position = "relative"; width = container.clientWidth; height = container.clientHeight; canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; canvas.style.position = "absolute"; canvas.style.top = "0"; canvas.style.left = "0"; canvas.style.width = "100%"; canvas.style.height = "100%"; canvas.style.zIndex = "-1"; canvasRGB = parseRGBA(config.canvasColor); container.appendChild(canvas); ctx = canvas.getContext("2d"); if (config.auto) { origin = config.originElement || document.querySelector(".starfield-origin"); if (!origin) { throw new Error("Starfield: No origin element found."); } originX = getOriginX(origin, container); originY = getOriginY(origin, container); origin.addEventListener("mouseenter", mouseEnterHandler); origin.addEventListener("mouseleave", mouseLeaveHandler); window.addEventListener("resize", resizeHandler); } else { originX = config.originX !== null ? config.originX : width / 2; originY = config.originY !== null ? config.originY : height / 2; } for (let i = 0; i < config.numStars; i++) { const star = createRandomStar(); stars.push(star); } document.addEventListener("visibilitychange", visibilityHandler); requestAnimationFrame(draw); } function windowResized(container, origin) { width = container.clientWidth; height = container.clientHeight; canvas.width = width; canvas.height = height; originX = getOriginX(origin, container); originY = getOriginY(origin, container); stars.forEach((star) => star.reset()); } function createRandomStar() { const angle = random(0, Math.PI * 2); const radius = random(config.minSpawnRadius, config.maxSpawnRadius); const x = originX + Math.cos(angle) * radius; const y = originY + Math.sin(angle) * radius; return new Star(x, y); } class Star { constructor(x, y) { this.pos = { x: x, y: y, }; this.prevpos = { x: x, y: y, }; this.vel = { x: 0, y: 0, }; this.angle = Math.atan2(y - originY, x - originX); this.baseSpeed = random(config.baseSpeed * 0.5, config.baseSpeed * 1.5); this.hueOffset = random(-config.hueJitter, config.hueJitter); } reset() { const newStar = createRandomStar(); this.pos.x = newStar.pos.x; this.pos.y = newStar.pos.y; this.prevpos.x = this.pos.x; this.prevpos.y = this.pos.y; this.vel.x = 0; this.vel.y = 0; this.angle = Math.atan2(this.pos.y - originY, this.pos.x - originX); this.baseSpeed = random(config.baseSpeed * 0.5, config.baseSpeed * 1.5); this.hueOffset = random(-config.hueJitter, config.hueJitter); } update(acc, deltaTime) { const adjustedAcc = acc * this.baseSpeed; this.vel.x += Math.cos(this.angle) * adjustedAcc * deltaTime; this.vel.y += Math.sin(this.angle) * adjustedAcc * deltaTime; this.prevpos.x = this.pos.x; this.prevpos.y = this.pos.y; this.pos.x += this.vel.x * deltaTime; this.pos.y += this.vel.y * deltaTime; } draw() { let velMag = Math.sqrt( this.vel.x * this.vel.x + this.vel.y * this.vel.y ); velMag = velMag * 3; const alpha = map(velMag, 0, 10, 0, 1); const weight = map(velMag, 0, 10, 1, 3); ctx.lineWidth = weight; const [r, g, b] = parseRGBA(config.starColor); const [h, s, l] = rgbToHsl(r, g, b); const adjustedH = (h + this.hueOffset + 360) % 360; const [newR, newG, newB] = hslToRgb(adjustedH, s, l).map((v) => Math.round(v) ); ctx.strokeStyle = `rgba(${newR}, ${newG}, ${newB}, ${alpha})`; ctx.beginPath(); ctx.moveTo(this.prevpos.x, this.prevpos.y); ctx.lineTo(this.pos.x, this.pos.y); ctx.stroke(); } isActive() { return onScreen(this.pos.x, this.pos.y); } updateAngle() { this.angle = Math.atan2(this.pos.y - originY, this.pos.x - originX); } } function draw(timestamp) { if (!lastTimestamp) lastTimestamp = timestamp; const deltaTime = (timestamp - lastTimestamp) / 16.67; lastTimestamp = timestamp; if (config.auto) { originX = getOriginX(origin, container); originY = getOriginY(origin, container); if (originX !== prevOriginX || originY !== prevOriginY) { stars.forEach((star) => { star.updateAngle(); }); prevOriginX = originX; prevOriginY = originY; } } if (lastCanvasColor !== config.canvasColor) { canvasRGB = parseRGBA(config.canvasColor); lastCanvasColor = config.canvasColor; } const [bgR, bgG, bgB] = canvasRGB; ctx.fillStyle = `rgba(${bgR}, ${bgG}, ${bgB}, ${1 - config.trailLength})`; ctx.fillRect(0, 0, width, height); if (accelerate) { accelerationFactor = Math.min( accelerationFactor + config.accelerationRate * deltaTime, config.maxAcceleration ); } else { accelerationFactor = Math.max( accelerationFactor - config.decelerationRate * deltaTime, 0 ); } const baseAcc = 0.01; const currentAcc = baseAcc * (1 + accelerationFactor * 10); for (let star of stars) { star.update(currentAcc, deltaTime); star.draw(); if (!star.isActive()) { star.reset(); } } requestAnimationFrame(draw); } function onScreen(x, y) { return x >= 0 && x <= width && y >= 0 && y <= height; } function random(min, max) { return Math.random() * (max - min) + min; } // https://gist.github.com/mjackson/5311256 function rgbToHsl(r, g, b) { r /= 255; g /= 255; b /= 255; const max = Math.max(r, g, b), min = Math.min(r, g, b); let h, s, l = (max + min) / 2; if (max === min) { h = s = 0; } else { const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [h * 360, s, l]; } // https://gist.github.com/mjackson/5311256 function hslToRgb(h, s, l) { let r, g, b; h = h / 360; if (s === 0) { r = g = b = l; } else { const hue2rgb = (p, q, t) => { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }; const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; r = hue2rgb(p, q, h + 1 / 3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1 / 3); } return [r * 255, g * 255, b * 255]; } function parseRGBA(color) { const rgbaRegex = /rgba?\((\d+),\s*(\d+),\s*(\d+)/; const match = color.match(rgbaRegex); if (match) { return [ parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10), ]; } return [255, 255, 255]; } function map(value, start1, stop1, start2, stop2) { return ((value - start1) / (stop1 - start1)) * (stop2 - start2) + start2; } /** * Set the acceleration state of the starfield. * @param {boolean} state The acceleration state. */ function setAccelerate(state) { accelerate = state; } /** * Set the x-coordinate of the origin of the starfield. * @param {number} x The x-coordinate of the origin. */ function setOriginX(x) { originX = x; stars.forEach((star) => { star.angle = Math.atan2(star.pos.y - originY, star.pos.x - originX); }); } /** * Set the y-coordinate of the origin of the starfield. * @param {number} y The y-coordinate of the origin. */ function setOriginY(y) { originY = y; stars.forEach((star) => { star.angle = Math.atan2(star.pos.y - originY, star.pos.x - originX); }); } /** * Set the origin of the starfield to a specific point. * @param {number} x The x-coordinate of the origin. * @param {number} y The y-coordinate of the origin. */ function setOrigin(x, y) { originX = x; originY = y; stars.forEach((star) => { star.angle = Math.atan2(star.pos.y - originY, star.pos.x - originX); }); } /** * Resize the starfield to a new width and height. * @param {number} newWidth The new width of the starfield. * @param {number} newHeight The new height of the starfield. */ function resize(newWidth, newHeight) { width = newWidth; height = newHeight; canvas.width = width; canvas.height = height; if (config.originY !== null) { originY = config.originY; } else { originY = height / 2; } stars.forEach((star) => star.reset()); } function cleanup() { if (origin) { origin.removeEventListener("mouseenter", mouseEnterHandler); origin.removeEventListener("mouseleave", mouseLeaveHandler); } window.removeEventListener("resize", resizeHandler); document.removeEventListener("visibilitychange", visibilityHandler); if (canvas && canvas.parentNode) { canvas.parentNode.removeChild(canvas); } stars = []; accelerate = false; accelerationFactor = 0; originX = 0; originY = 0; prevOriginX = 0; prevOriginY = 0; lastTimestamp = 0; } Starfield.setup = setup; Starfield.setAccelerate = setAccelerate; Starfield.setOrigin = setOrigin; Starfield.setOriginX = setOriginX; Starfield.setOriginY = setOriginY; Starfield.resize = resize; Starfield.config = config; Starfield.cleanup = cleanup; return Starfield; }); ================================================ FILE: overrides/main.html ================================================ {% extends "base.html" %} {% block announce %} {% if config.theme.language == 'zh' %} {% set announcements = '纸质书已发行,详情请见这里' %} {% elif config.theme.language == 'zh-Hant' %} {% set announcements = '紙質書(簡體中文版)已發行,詳情請見這裡' %} {% elif config.theme.language == 'en' %} {% set announcements = 'Welcome to contribute to Chinese-to-English translation! For more details, please refer to CONTRIBUTING.md.' %} {% elif config.theme.language == 'ja' %} {% set announcements = '日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。' %} {% elif config.theme.language == 'ru' %} {% set announcements = 'Приглашаем вас участвовать в развитии русской версии! Подробнее здесь.' %} {% endif %} {% endblock %} ================================================ FILE: overrides/partials/LICENSE ================================================ Copyright (c) 2016-2023 Martin Donath Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: overrides/partials/actions.html ================================================ {% if page.edit_url %} {% if "content.action.edit" in features and "edit" not in page.meta.hide %} {% set icon = config.theme.icon.edit or "material/file-edit-outline" %} {% include ".icons/" ~ icon ~ ".svg" %} {% endif %} {% if "content.action.view" in features %} {% if "/blob/" in page.edit_url %} {% set part = "blob" %} {% else %} {% set part = "edit" %} {% endif %} {% set icon = config.theme.icon.view or "material/file-eye-outline" %} {% include ".icons/" ~ icon ~ ".svg" %} {% endif %} {% endif %} ================================================ FILE: overrides/partials/comments.html ================================================ {% if page.meta.comments %} {% if config.theme.language == 'zh' %} {% set comm = "欢迎在评论区留下你的见解、问题或建议" %} {% set lang = "zh-CN" %} {% elif config.theme.language == 'zh-Hant' %} {% set comm = "歡迎在評論區留下你的見解、問題或建議" %} {% set lang = "zh-TW" %} {% elif config.theme.language == 'en' %} {% set comm = "Feel free to drop your insights, questions or suggestions" %} {% set lang = "en" %} {% elif config.theme.language == 'ja' %} {% set comm = "ご意見、ご質問、ご提案があればぜひコメントしてください" %} {% set lang = "ja" %} {% elif config.theme.language == 'ru' %} {% set comm = "Оставляйте свои идеи, вопросы и предложения в комментариях" %} {% set lang = "ru" %} {% endif %} {% include "partials/checkin.html" ignore missing %}
{{ comm }}
{% endif %} ================================================ FILE: overrides/partials/content.html ================================================ {% if "material/tags" in config.plugins and tags %} {% include "partials/tags.html" %} {% endif %} {% include "partials/actions.html" %} {{ page.content }} {% if page.meta and ( page.meta.git_revision_date_localized or page.meta.revision_date ) %} {% include "partials/source-file.html" %} {% endif %} {% include "partials/feedback.html" %} {% include "partials/comments.html" %} ================================================ FILE: overrides/stylesheets/extra.css ================================================ /* Color Settings */ /* https://github.com/squidfunk/mkdocs-material/blob/6b5035f5580f97532d664e3d1babf5f320e88ee9/src/assets/stylesheets/main/_colors.scss */ /* https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/#custom-colors */ :root>* { --md-primary-fg-color: #ffffff; --md-primary-bg-color: #1d1d20; --md-default-fg-color: #1d1d20; --md-default-bg-color: #ffffff; --md-body-bg-color: #22272e; --md-header-bg-color: rgba(255, 255, 255, 0.6); --md-code-fg-color: #1d1d20; --md-code-bg-color: #f5f5f5; --md-accent-fg-color: #999; --md-admonition-fg-color: #1d1d20; --md-typeset-color: #1d1d20; --md-typeset-a-color: #349890; --md-typeset-btn-color: #55aea6; --md-typeset-btn-hover-color: #52bbb1; --md-admonition-icon--pythontutor: url('data:image/svg+xml;charset=utf-8,'); --md-admonition-pythontutor-color: #eee; } [data-md-color-scheme="slate"] { --theme-dark-base: #1E1E1E; --theme-dark-mantle: #1A1A1A; --theme-dark-crust: #171717; --hero-starfield-bg-color: var(--theme-dark-base); --md-primary-fg-color: var(--theme-dark-base); --md-primary-bg-color: #adbac7; --md-default-fg-color: #adbac7; --md-default-bg-color: var(--theme-dark-base); --md-body-bg-color: var(--theme-dark-mantle); --md-header-bg-color: rgba(26, 26, 26, 0.8); --md-code-fg-color: #adbac7; --md-code-bg-color: var(--theme-dark-crust); --md-accent-fg-color: #aaa; --md-admonition-fg-color: #adbac7; --md-footer-fg-color: #adbac7; --md-footer-bg-color: var(--theme-dark-mantle); --md-typeset-color: #adbac7; --md-typeset-a-color: #52bbb1; --md-typeset-btn-color: #52bbb1; --md-typeset-btn-hover-color: #55aea6; --md-admonition-pythontutor-color: var(--theme-dark-crust); } [data-md-color-scheme="slate"][data-md-color-primary="black"], [data-md-color-scheme="slate"][data-md-color-primary="white"] { --md-typeset-a-color: #52bbb1; } [data-md-color-primary="black"] .md-header { background-color: var(--md-header-bg-color); } .md-header--shadow, .md-header--landing { box-shadow: none; transition: none; backdrop-filter: saturate(180%) blur(20px); /* Gaussian blur */ -webkit-backdrop-filter: saturate(180%) blur(20px); /* Safari */ background-color: var(--md-header-bg-color); } [data-md-color-scheme="slate"] .md-footer, [data-md-color-scheme="slate"] .md-footer__inner { background-color: var(--theme-dark-mantle); color: var(--md-footer-fg-color); } [data-md-color-scheme="slate"] .md-footer-meta { background-color: var(--theme-dark-crust); color: var(--md-footer-fg-color); } [data-md-color-scheme="slate"] .md-footer__link { background-color: var(--theme-dark-crust); color: var(--md-footer-fg-color); } [data-md-color-scheme="slate"] .md-footer__link:hover { background-color: var(--theme-dark-base); } [data-md-color-scheme="slate"] .md-footer__title, [data-md-color-scheme="slate"] .md-footer__direction, [data-md-color-scheme="slate"] .md-footer__button, [data-md-color-scheme="slate"] .md-copyright, [data-md-color-scheme="slate"] .md-copyright a, [data-md-color-scheme="slate"] .md-social, [data-md-color-scheme="slate"] .md-social__link { color: var(--md-footer-fg-color); } /* https://github.com/squidfunk/mkdocs-material/issues/4832#issuecomment-1374891676 */ .md-nav__link[for] { color: var(--md-default-fg-color) !important; } /* Figure class */ .animation-figure { border-radius: 0.3rem; display: block; margin: 0 auto; box-shadow: var(--md-shadow-z2); } /* Cover image class */ .cover-image { width: 28rem; height: auto; border-radius: 0.3rem; display: block; margin: 0 auto; box-shadow: var(--md-shadow-z2); } /* Center Markdown Tables (requires md_in_html extension) */ .center-table { text-align: center; } /* Reset alignment for table cells */ .md-typeset .center-table :is(td, th):not([align]) { text-align: initial; } /* Font size */ .md-typeset { font-size: 0.75rem; line-height: 1.5; } .md-typeset pre { font-size: 0.95em; } /* Markdown Header */ /* https://github.com/squidfunk/mkdocs-material/blob/dcab57dd1cced4b77875c1aa1b53467c62709d31/src/assets/stylesheets/main/_typeset.scss */ .md-typeset h1 { font-weight: 400; color: var(--md-default-fg-color); } .md-typeset h2 { font-weight: 400; } .md-typeset h3 { font-weight: 500; } .md-typeset h5 { text-transform: none; } .md-typeset a:hover { color: var(--md-typeset-a-color); text-decoration: underline; } .md-typeset code { border-radius: 0.2rem; } .highlight span.filename { font-weight: normal; } /* font-family setting for Win10 */ body { --md-text-font-family: -apple-system, BlinkMacSystemFont, var(--md-text-font, _), Helvetica, Arial, sans-serif; --md-code-font-family: var(--md-code-font, _), SFMono-Regular, Consolas, Menlo, -apple-system, BlinkMacSystemFont, var(--md-text-font, _), monospace; } /* max height of code block */ /* https://github.com/squidfunk/mkdocs-material/issues/3444 */ .md-typeset pre>code { max-height: 25rem; } /* Make the picture not glare in dark theme */ [data-md-color-scheme="slate"] .md-typeset img, [data-md-color-scheme="slate"] .md-typeset svg, [data-md-color-scheme="slate"] .md-typeset video { filter: brightness(0.85) invert(0.05); } /* landing page */ .header-img-div { display: flex; align-items: center; justify-content: center; margin: 0 auto; width: 100%; /* Default to full width */ } /* Admonition for python tutor */ .md-typeset .admonition.pythontutor, .md-typeset details.pythontutor { border-color: var(--md-default-fg-color--lightest); margin-top: 0; margin-bottom: 1.5625em; } .md-typeset .admonition:focus-within, .md-typeset details:focus-within { box-shadow: var(--md-shadow-z1); } .md-typeset .pythontutor>.admonition-title, .md-typeset .pythontutor>summary { background-color: var(--md-code-bg-color); } .md-typeset .pythontutor>.admonition-title::before, .md-typeset .pythontutor>summary::before { background-color: rgb(55, 118, 171); -webkit-mask-image: var(--md-admonition-icon--pythontutor); mask-image: var(--md-admonition-icon--pythontutor); } .md-typeset .admonition-title:before, .md-typeset summary:before { width: 1.25em; } /* code block tabs */ .md-typeset .tabbed-labels>label { font-size: 0.61rem; } .md-typeset .tabbed-labels--linked>label>a { padding: .78125em 1.0em .625em; } /* header banner */ .md-banner { background-color: var(--md-code-bg-color); color: var(--md-default-fg-color); font-size: 0.75rem; } .md-banner .banner-svg svg { margin-right: 0.3rem; height: 0.63rem; fill: var(--md-default-fg-color); } .pythontutor-iframe { width: 125%; height: 125%; max-width: 125% !important; max-height: 125% !important; transform: scale(0.8); transform-origin: top left; border: none; } /* landing page container */ .home-div { width: 100%; height: auto; display: flex; justify-content: center; align-items: center; background-color: var(--md-default-bg-color); color: var(--md-default-fg-color); font-size: 0.9rem; padding: 3em 2em; text-align: center; } .section-content { width: 100%; height: auto; max-width: 70vw; } /* rounded button */ .rounded-button { display: inline-flex; align-items: center; justify-content: center; border-radius: 10em; margin: 0 0.1em; padding: 0.6em 1.3em; border: none; background-color: var(--md-typeset-btn-color); color: var(--md-primary-fg-color) !important; text-align: center; text-decoration: none; cursor: pointer; } .rounded-button:hover { background-color: var(--md-typeset-btn-hover-color); } .rounded-button span { margin: 0; margin-bottom: 0.07em; white-space: nowrap; } .rounded-button svg { fill: var(--md-primary-fg-color); width: auto; height: 1.2em; margin-right: 0.5em; } /* device image */ .device-on-hover { width: auto; transition: transform 0.3s ease-in-out, filter 0.3s ease-in-out; } a:hover .device-on-hover { filter: drop-shadow(0 0 0.2rem rgba(0, 0, 0, 0.15)); transform: scale(1.01); } /* text button */ .reading-media { display: flex; justify-content: center; align-items: flex-end; height: 32vw; } .media-block { height: 100%; margin: 0 0.2em; } .text-button { width: auto; color: var(--md-typeset-btn-color); text-decoration: none; text-align: center; margin: 2.7em auto; } .text-button span { white-space: nowrap; } .text-button svg { display: inline-block; fill: var(--md-typeset-btn-color); width: auto; height: 0.9em; background-size: cover; padding-top: 0.17em; margin-left: 0.15em; } a:hover .text-button span { text-decoration: underline; } /* hero image */ .hero-div { height: min(84vh, 75vw); width: min(112vh, 100vw); margin: 0 auto; margin-top: -2.4rem; padding: 0; position: relative; font-size: min(1.8vh, 2.5vw); font-weight: normal; } .hero-div--en { font-size: min(1.65vh, 2.3vw); } .hero-div--ru { font-size: min(1.45vh, 2vw); } .hero-bg { height: 100%; width: 100%; object-fit: cover; position: absolute; } /* hover on the planets */ .hero-div>a>img { width: auto; position: absolute; transition: transform 0.3s ease-in-out, filter 0.3s ease-in-out; } .hero-div>a>span { margin: 0; position: absolute; transform: translateX(-50%) translateY(-50%); white-space: nowrap; /* prevent line breaks */ color: white; } .hero-div>a:hover>img { filter: brightness(1.15) saturate(1.1) drop-shadow(0 0 0.5rem rgba(255, 255, 255, 0.2)); transform: scale(1.03); } .hero-div>a:hover>span { text-decoration: underline; color: var(--md-typeset-btn-color); } .heading-div { width: 100%; position: absolute; transform: translateX(-50%); left: 50%; bottom: min(2vh, 3vw); pointer-events: none; color: #fff; } /* code badge */ .code-badge { width: 100%; height: auto; margin: 1em auto; } .code-badge img { height: 1.07em; width: auto; } /* brief intro */ .intro-container { display: flex; align-items: center; margin: 2em auto; } .intro-image { flex-shrink: 0; flex-grow: 0; width: 50%; border-radius: 0.5em; box-shadow: var(--md-shadow-z2); } .intro-text { flex-grow: 1; /* fill the space */ display: flex; flex-direction: column; justify-content: center; text-align: left; align-items: flex-start; width: fit-content; margin: 2em; } .intro-text>div { align-self: flex-start; width: auto; margin: 0 auto; } .endor-text { width: 50%; } .intro-quote { color: var(--md-accent-fg-color); font-weight: bold; } /* contributors table */ .profile-div { display: flex; flex-wrap: wrap; justify-content: center; max-width: 40em; margin: 1em auto; } .profile-cell { flex: 1 1 15%; margin: 1em 0; text-align: center; } .profile-cell a:hover b, .profile-cell a:focus-visible b { text-decoration: underline; } .profile-img { width: 5em; border-radius: 50%; margin-bottom: 0.5em; } .translator-profile-div { gap: 0.5em; } .translator-profile-cell { flex: 0 0 auto; margin: 0.5em 0; min-width: 8.5em; } .giscus-container { width: 40em; max-width: 100%; margin: 0 auto; } /* Hide navigation */ @media screen and (max-width: 76.25em) { .section-content { max-width: 95vw; } .reading-media { height: 33vw; } .contrib-image { width: 100%; } } /* mobile devices */ @media screen and (max-width: 60em) { .home-div { font-size: 0.75rem; } .hero-div { margin-top: -4rem; } .intro-container { flex-direction: column; } .intro-text { width: auto; order: 2; margin: 0 auto; } .endor-text { width: auto; margin: 0 auto; } .intro-image { width: 100%; order: 1; margin-bottom: 1em; } .text-button { margin: 0.7em auto; } .profile-cell { flex: 1 1 30%; } } .video-container { position: relative; padding-bottom: 56.25%; /* 16:9 */ height: 0; } .video-container iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } /* starfield */ .starfield { position: absolute; width: 100%; height: 100%; z-index: 0; background-color: var(--hero-starfield-bg-color, transparent); } .starfield-origin { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } ================================================ FILE: overrides/stylesheets/giscus-dark.css ================================================ @import url("https://giscus.app/themes/noborder_dark.css"); .gsc-comment-box-buttons .btn-primary { border-radius: 999px !important; } /* Limit long giscus comments so they don't dominate the page. */ .gsc-comment-content, .gsc-reply-content { max-height: 32rem; overflow-y: auto; overscroll-behavior: contain; padding-inline-end: 0.5rem; } .gsc-comment-content::-webkit-scrollbar, .gsc-reply-content::-webkit-scrollbar { width: 0.35rem; } .gsc-comment-content::-webkit-scrollbar-thumb, .gsc-reply-content::-webkit-scrollbar-thumb { background: rgba(230, 237, 243, 0.24); border-radius: 999px; } .gsc-comment-content::-webkit-scrollbar-track, .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } ================================================ FILE: overrides/stylesheets/giscus-light.css ================================================ @import url("https://giscus.app/themes/light.css"); main { --color-border-default: rgba(29, 29, 32, 0.08); --color-border-muted: rgba(29, 29, 32, 0.05); --color-accent-fg: #349890; --color-btn-primary-text: #ffffff; --color-btn-primary-bg: #52bbb1; --color-btn-primary-hover-bg: #7acfc7; --color-btn-primary-border: rgba(52, 152, 144, 0.2); --color-btn-primary-hover-border: rgba(52, 152, 144, 0.24); } .border, .color-border-primary { border-color: rgba(29, 29, 32, 0.07) !important; } .gsc-comment-box-buttons .btn-primary { border-radius: 999px !important; border-color: transparent !important; background-color: #52bbb1 !important; color: #ffffff !important; box-shadow: none !important; } .gsc-comment-box-buttons .btn-primary:hover:not(:disabled) { background-color: #7acfc7 !important; } .gsc-comment-box-buttons .btn-primary:disabled { opacity: 0.62; } /* Limit long giscus comments so they don't dominate the page. */ .gsc-comment-content, .gsc-reply-content { max-height: 32rem; overflow-y: auto; overscroll-behavior: contain; padding-inline-end: 0.5rem; } .gsc-comment-content::-webkit-scrollbar, .gsc-reply-content::-webkit-scrollbar { width: 0.35rem; } .gsc-comment-content::-webkit-scrollbar-thumb, .gsc-reply-content::-webkit-scrollbar-thumb { background: rgba(31, 35, 40, 0.22); border-radius: 999px; } .gsc-comment-content::-webkit-scrollbar-track, .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } ================================================ FILE: overrides/zensical/javascripts/animation_player.js ================================================ (() => { const ANIMATION_LABEL_PATTERN = /^<\d+>$/; const AUTO_SLIDE_INITIAL_DELAY_MS = 1000; const AUTO_SLIDE_INTERVAL_MS = 1500; const PLAY_LABEL = "播放幻灯片"; const PAUSE_LABEL = "暂停幻灯片"; const SVG_NS = "http://www.w3.org/2000/svg"; const FA_PLAY_PATH = "M91.2 36.9c-12.4-6.8-27.4-6.5-39.6.7S32 57.9 32 72v368c0 14.1 7.5 27.2 19.6 34.4s27.2 7.5 39.6.7l336-184c12.8-7 20.8-20.5 20.8-35.1s-8-28.1-20.8-35.1z"; const FA_PAUSE_PATH = "M48 32C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h64c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm224 0c-26.5 0-48 21.5-48 48v352c0 26.5 21.5 48 48 48h64c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48z"; const FA_ARROW_LEFT_PATH = "M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 288H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H109.3l105.4-105.4c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"; const FA_ARROW_RIGHT_PATH = "M502.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L402.7 224H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h370.7L297.3 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"; const initializedSets = new WeakSet(); const controllers = new Set(); const getCheckedIndex = (inputs) => { const checkedIndex = inputs.findIndex((input) => input.checked); return checkedIndex === -1 ? 0 : checkedIndex; }; const setCheckedIndex = (inputs, index) => { const normalizedIndex = ((index % inputs.length) + inputs.length) % inputs.length; inputs[normalizedIndex].checked = true; return normalizedIndex; }; const isAnimationSet = (tabbedSet) => { const labels = Array.from(tabbedSet.querySelectorAll(":scope > .tabbed-labels > label")); if (labels.length < 2) { return false; } return labels.every((label) => ANIMATION_LABEL_PATTERN.test(label.textContent.trim())); }; const createSvgIcon = ({ className, path, viewBox, width, height }) => { const icon = document.createElementNS(SVG_NS, "svg"); const iconPath = document.createElementNS(SVG_NS, "path"); icon.setAttribute("class", className); icon.setAttribute("aria-hidden", "true"); icon.setAttribute("viewBox", viewBox); icon.setAttribute("focusable", "false"); if (width) { icon.setAttribute("width", width); } if (height) { icon.setAttribute("height", height); } iconPath.setAttribute("d", path); icon.append(iconPath); return { icon, iconPath }; }; const createIconButton = ({ className, ariaLabel, path }) => { const button = document.createElement("button"); const { icon } = createSvgIcon({ className: "animation-controls__nav-icon", path, viewBox: "0 0 512 512", }); button.type = "button"; button.className = `animation-controls__button ${className}`; button.setAttribute("aria-label", ariaLabel); button.append(icon); return button; }; const createPlayButton = () => { const button = document.createElement("button"); const glyph = document.createElement("span"); const { icon, iconPath } = createSvgIcon({ className: "animation-controls__play-icon", path: FA_PLAY_PATH, viewBox: "0 0 448 512", width: "12", height: "12", }); const srOnly = document.createElement("span"); button.type = "button"; button.className = "animation-controls__button animation-controls__play"; button.setAttribute("aria-label", PLAY_LABEL); glyph.className = "animation-controls__play-glyph"; icon.setAttribute("preserveAspectRatio", "xMidYMid meet"); glyph.append(icon); srOnly.className = "animation-controls__sr-only"; srOnly.textContent = PLAY_LABEL; button.append(glyph, srOnly); return { button, srOnly, icon, iconPath }; }; const initAnimationSet = (tabbedSet) => { if (initializedSets.has(tabbedSet) || !isAnimationSet(tabbedSet)) { return; } const inputs = Array.from(tabbedSet.querySelectorAll(':scope > input[type="radio"]')); const tabbedContent = tabbedSet.querySelector(":scope > .tabbed-content"); if (inputs.length < 2 || !tabbedContent) { return; } initializedSets.add(tabbedSet); tabbedSet.dataset.autoSlide = "true"; const controls = document.createElement("div"); controls.className = "animation-controls"; const playControl = createPlayButton(); const playButton = playControl.button; const nav = document.createElement("div"); nav.className = "animation-controls__nav"; const prevButton = createIconButton({ className: "animation-controls__prev", ariaLabel: "上一页", path: FA_ARROW_LEFT_PATH, }); const pageIndicator = document.createElement("span"); pageIndicator.className = "animation-controls__page"; pageIndicator.setAttribute("aria-live", "polite"); const nextButton = createIconButton({ className: "animation-controls__next", ariaLabel: "下一页", path: FA_ARROW_RIGHT_PATH, }); nav.append(prevButton, pageIndicator, nextButton); controls.append(playButton, nav); tabbedContent.insertAdjacentElement("afterend", controls); const state = { inputs, currentIndex: getCheckedIndex(inputs), intervalId: null, timeoutId: null, isPlaying: false, }; const updatePlayButton = () => { const label = state.isPlaying ? PAUSE_LABEL : PLAY_LABEL; playButton.setAttribute("aria-label", label); playButton.classList.toggle("is-playing", state.isPlaying); playControl.srOnly.textContent = label; playControl.icon.setAttribute("viewBox", state.isPlaying ? "0 0 384 512" : "0 0 448 512"); playControl.icon.setAttribute("width", state.isPlaying ? "10" : "12"); playControl.iconPath.setAttribute("d", state.isPlaying ? FA_PAUSE_PATH : FA_PLAY_PATH); }; const updatePageIndicator = () => { pageIndicator.textContent = `${state.currentIndex + 1} / ${inputs.length}`; }; const stop = () => { if (state.timeoutId !== null) { window.clearTimeout(state.timeoutId); state.timeoutId = null; } if (state.intervalId !== null) { window.clearInterval(state.intervalId); state.intervalId = null; } state.isPlaying = false; updatePlayButton(); }; const syncCurrentIndex = () => { state.currentIndex = getCheckedIndex(inputs); }; const moveTo = (index) => { state.currentIndex = setCheckedIndex(inputs, index); updatePageIndicator(); }; const step = (delta) => { moveTo(state.currentIndex + delta); }; const start = () => { if (state.isPlaying) { return; } state.isPlaying = true; updatePlayButton(); state.timeoutId = window.setTimeout(() => { step(1); state.timeoutId = null; state.intervalId = window.setInterval(() => { step(1); }, AUTO_SLIDE_INTERVAL_MS); }, AUTO_SLIDE_INITIAL_DELAY_MS); }; playButton.addEventListener("click", () => { if (state.isPlaying) { stop(); return; } syncCurrentIndex(); start(); }); prevButton.addEventListener("click", () => { syncCurrentIndex(); step(-1); }); nextButton.addEventListener("click", () => { syncCurrentIndex(); step(1); }); inputs.forEach((input, index) => { input.addEventListener("change", () => { if (input.checked) { state.currentIndex = index; updatePageIndicator(); } }); }); controllers.add(stop); updatePlayButton(); updatePageIndicator(); }; const initAutoSlide = () => { document.querySelectorAll(".tabbed-set").forEach(initAnimationSet); }; document.addEventListener("visibilitychange", () => { if (!document.hidden) { return; } controllers.forEach((stop) => stop()); }); if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initAutoSlide, { once: true }); } else { initAutoSlide(); } })(); ================================================ FILE: overrides/zensical/stylesheets/animation_player.css ================================================ .md-typeset .tabbed-set[data-auto-slide="true"] { margin-bottom: 1.5em; overflow: hidden; border-radius: 0.45rem; background: var(--md-default-bg-color); box-shadow: 0 0.12rem 0.45rem var(--md-default-fg-color--lightest); } .md-typeset .tabbed-set[data-auto-slide="true"] > .tabbed-labels { display: none; } .md-typeset .tabbed-set[data-auto-slide="true"] > .tabbed-content { margin-top: 0; margin-bottom: 0; background: var(--md-default-bg-color); } .md-typeset .tabbed-set[data-auto-slide="true"] > .tabbed-content > .tabbed-block > p { margin: 0; } .md-typeset .tabbed-set[data-auto-slide="true"] .animation-figure { border-radius: 0; box-shadow: none; } .animation-controls { display: flex; align-items: center; justify-content: space-between; gap: 0.55rem; width: 100%; margin: 0 auto; padding: 0.14rem 0.3rem; border-radius: 0; background: color-mix(in srgb, var(--md-code-bg-color) 97.5%, var(--md-default-fg-color) 2.5%); box-sizing: border-box; } .animation-controls__nav { display: flex; align-items: center; gap: 0.03rem; margin-left: auto; padding-left: 0.36rem; } .animation-controls__button { display: inline-flex; align-items: center; justify-content: center; min-height: 1.64rem; min-width: 1.64rem; padding: 0.1rem; border: 0; background: transparent; border-radius: 999px; color: var(--md-default-fg-color); cursor: pointer; transition: color 0.15s ease, background-color 0.15s ease; } .animation-controls__button:hover, .animation-controls__button:focus-visible { background: color-mix(in srgb, var(--md-code-bg-color) 92%, var(--md-default-fg-color) 8%); color: var(--md-accent-fg-color); } .animation-controls__play { flex: 0 0 auto; line-height: 0; } .animation-controls__play-glyph { display: inline-flex; align-items: center; justify-content: center; width: 0.72rem; height: 0.72rem; flex: 0 0 auto; overflow: hidden; } .animation-controls__play-icon { display: block; width: 100%; height: 100%; fill: currentColor; flex: 0 0 auto; } .animation-controls__play-glyph .animation-controls__play-icon { transform: translateX(0.04rem); } .animation-controls__play.is-playing .animation-controls__play-glyph { width: 0.6rem; } .animation-controls__play.is-playing .animation-controls__play-glyph .animation-controls__play-icon { transform: none; } .animation-controls__nav-icon { width: 0.72rem; height: 0.72rem; fill: currentColor; flex: 0 0 auto; } .animation-controls__page { min-width: 2.25rem; padding: 0 0.01rem; color: var(--md-typeset-color); font-size: 0.75rem; font-variant-numeric: tabular-nums; line-height: 1; text-align: center; } .animation-controls__sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; } [data-md-color-scheme="slate"] .animation-controls { background: color-mix(in srgb, var(--md-code-bg-color) 91%, black 9%); } [data-md-color-scheme="slate"] .animation-controls__button:hover, [data-md-color-scheme="slate"] .animation-controls__button:focus-visible { background: color-mix(in srgb, var(--md-code-bg-color) 92%, white 8%); } [data-md-color-scheme="slate"] .md-typeset .tabbed-set[data-auto-slide="true"] { background: var(--md-code-bg-color); box-shadow: 0 0.2rem 0.75rem rgb(0 0 0 / 0.18); } [data-md-color-scheme="slate"] .md-typeset .tabbed-set[data-auto-slide="true"] > .tabbed-content { background: var(--md-code-bg-color); } @media screen and (max-width: 44.9375em) { .animation-controls { flex-wrap: nowrap; justify-content: space-between; gap: 0.32rem; padding: 0.11rem 0.22rem; } .animation-controls__nav { gap: 0.02rem; padding-left: 0.16rem; } .animation-controls__button { min-width: 1.48rem; min-height: 1.48rem; padding: 0.08rem; } .animation-controls__page { min-width: 2rem; padding: 0; font-size: 0.7rem; } } ================================================ FILE: overrides/zensical/stylesheets/extra.css ================================================ /* Theme tokens */ /* https://github.com/squidfunk/mkdocs-material/blob/6b5035f5580f97532d664e3d1babf5f320e88ee9/src/assets/stylesheets/main/_colors.scss */ /* https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/#custom-colors */ :root>* { --md-primary-fg-color: #ffffff; --md-primary-bg-color: #1d1d20; --md-default-fg-color: #1d1d20; --md-default-bg-color: #ffffff; --md-body-bg-color: #22272e; --md-header-bg-color: rgba(255, 255, 255, 0.6); --md-code-fg-color: #1d1d20; --md-code-bg-color: #f5f5f5; --md-typeset-color: #1d1d20; --md-typeset-a-color: #349890; --md-accent-fg-color: var(--md-typeset-a-color); --md-typeset-btn-color: #55aea6; --md-typeset-btn-hover-color: #52bbb1; --md-admonition-icon--pythontutor: url('data:image/svg+xml;charset=utf-8,'); --md-admonition-pythontutor-color: var(--md-code-bg-color); --hello-algo-sidebar-width: 13rem; } [data-md-color-scheme="slate"] { --theme-dark-base: #1e1e1e; --theme-dark-mantle: #1a1a1a; --theme-dark-crust: #171717; --hero-starfield-bg-color: var(--theme-dark-base); --md-primary-fg-color: var(--theme-dark-base); --md-primary-bg-color: #adbac7; --md-default-fg-color: #adbac7; --md-default-bg-color: var(--theme-dark-base); --md-default-bg-color--light: rgb(30 30 30 / 0.8); --md-body-bg-color: var(--md-default-bg-color); --md-header-bg-color: rgba(26, 26, 26, 0.8); --md-code-fg-color: #adbac7; --md-code-bg-color: var(--theme-dark-crust); --md-typeset-color: #adbac7; --md-typeset-a-color: #52bbb1; --md-accent-fg-color: var(--md-typeset-a-color); --md-typeset-btn-color: #52bbb1; --md-typeset-btn-hover-color: #55aea6; --md-footer-fg-color: #adbac7; --md-footer-bg-color: var(--theme-dark-mantle); --md-admonition-pythontutor-color: var(--md-code-bg-color); } [data-md-color-scheme="slate"][data-md-color-primary="black"], [data-md-color-scheme="slate"][data-md-color-primary="white"] { --md-typeset-a-color: #52bbb1; } /* Base layout */ body { background-color: var(--md-default-bg-color); --md-text-font-family: -apple-system, BlinkMacSystemFont, var(--md-text-font, _), Helvetica, Arial, sans-serif; --md-code-font-family: var(--md-code-font, _), SFMono-Regular, Consolas, Menlo, -apple-system, BlinkMacSystemFont, var(--md-text-font, _), monospace; } html:has(body[data-md-color-scheme="slate"]) { background-color: #1e1e1e; } html:has(body[data-md-color-scheme="default"]) { background-color: #ffffff; } @media screen and (min-width: 76.25em) { .md-grid { max-width: calc(61rem + 2 * (var(--hello-algo-sidebar-width) - 12.1rem)); } .md-sidebar--primary, .md-sidebar--secondary { width: var(--hello-algo-sidebar-width); } [dir="ltr"] .md-sidebar__inner { padding-right: calc(100% - (var(--hello-algo-sidebar-width) - 0.6rem)); } [dir="rtl"] .md-sidebar__inner { padding-left: calc(100% - (var(--hello-algo-sidebar-width) - 0.6rem)); } } .md-sidebar--primary .md-sidebar__scrollwrap { scrollbar-color: var(--md-default-fg-color--lighter) transparent; } .md-sidebar--primary .md-sidebar__scrollwrap::-webkit-scrollbar-thumb, .md-sidebar--primary .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover { background-color: var(--md-default-fg-color--lighter); } /* Banner and footer */ .md-banner { background-color: var(--md-default-bg-color); color: var(--md-default-fg-color); font-size: 0.75rem; } .md-banner .banner-svg svg { margin-right: 0.3rem; height: 0.63rem; fill: var(--md-default-fg-color); } .md-footer, .md-footer__inner, .md-footer-meta, .md-footer__link, .md-footer__link:hover { background-color: var(--md-default-bg-color); } .md-footer { border-top: 0.05rem solid var(--md-default-fg-color--lightest); } [data-md-color-scheme="slate"] .md-footer, [data-md-color-scheme="slate"] .md-footer__inner, [data-md-color-scheme="slate"] .md-footer-meta, [data-md-color-scheme="slate"] .md-footer__link, [data-md-color-scheme="slate"] .md-footer__link:hover { color: var(--md-footer-fg-color); } [data-md-color-scheme="slate"] .md-footer__title, [data-md-color-scheme="slate"] .md-footer__direction, [data-md-color-scheme="slate"] .md-footer__button, [data-md-color-scheme="slate"] .md-copyright, [data-md-color-scheme="slate"] .md-copyright a, [data-md-color-scheme="slate"] .md-social, [data-md-color-scheme="slate"] .md-social__link { color: var(--md-footer-fg-color); } /* Shared content elements */ .animation-figure { border-radius: 0.3rem; display: block; margin: 0 auto; box-shadow: 0 0.03rem 0.16rem rgb(0 0 0 / 0.07); } .cover-image { width: 28rem; height: auto; border-radius: 0.3rem; display: block; margin: 0 auto; box-shadow: 0 0.03rem 0.16rem rgb(0 0 0 / 0.07); } .center-table { text-align: center; } .md-typeset .center-table :is(td, th):not([align]) { text-align: initial; } .md-typeset { font-size: 0.75rem; line-height: 1.5; } .md-typeset pre { font-size: 0.95em; } /* https://github.com/squidfunk/mkdocs-material/blob/dcab57dd1cced4b77875c1aa1b53467c62709d31/src/assets/stylesheets/main/_typeset.scss */ .md-typeset h1 { font-weight: 400; color: var(--md-default-fg-color); } .md-typeset h2 { font-weight: 400; } .md-typeset h3 { font-weight: 500; } .md-typeset h5 { text-transform: none; } .md-typeset code { border-radius: 0.2rem; } .highlight span.filename { font-weight: normal; } /* https://github.com/squidfunk/mkdocs-material/issues/3444 */ .md-typeset pre>code { max-height: 25rem; } .md-typeset pre>code:hover { scrollbar-color: var(--md-default-fg-color--lighter) transparent; } .md-typeset pre>code::-webkit-scrollbar-thumb:hover { background-color: var(--md-default-fg-color--lighter); } [data-md-color-scheme="slate"] .md-typeset img, [data-md-color-scheme="slate"] .md-typeset svg, [data-md-color-scheme="slate"] .md-typeset video { filter: brightness(0.85) invert(0.05); } .md-typeset a:not(.md-button) { text-decoration: none; } .md-typeset a:not(.md-button):hover, .md-typeset a:not(.md-button):focus-visible { color: var(--md-typeset-a-color); text-decoration: underline; } /* Admonitions and tabs */ .md-typeset .admonition-title:before, .md-typeset summary:before, .md-typeset summary:after { top: 50%; } .md-typeset .admonition-title:before, .md-typeset summary:before { transform: translateY(-50%); } .md-typeset summary:after { transform: translateY(-50%) rotate(0deg); } .md-typeset details[open]>summary:after { transform: translateY(-50%) rotate(90deg); } .md-typeset .admonition.pythontutor, .md-typeset details.pythontutor { border-color: var(--md-default-fg-color--lightest); margin-top: 0; margin-bottom: 1.5625em; background-color: var(--md-code-bg-color); } .md-typeset .pythontutor>.admonition-title, .md-typeset .pythontutor>summary { background-color: var(--md-code-bg-color); } .md-typeset .pythontutor>.admonition-title::before, .md-typeset .pythontutor>summary::before { background-color: rgb(55, 118, 171); -webkit-mask-image: var(--md-admonition-icon--pythontutor); mask-image: var(--md-admonition-icon--pythontutor); } [data-md-color-scheme="slate"] .md-typeset details.pythontutor[open]> :not(summary), [data-md-color-scheme="slate"] .md-typeset details.pythontutor[open]> :not(summary) :is(p, li, strong, em, sub, sup, code, a) { background-color: #f5f5f5; color: #1d1d20; } .md-typeset .tabbed-labels>label { font-size: 0.61rem; } .md-typeset .tabbed-labels--linked>label>a { padding: 0.78125em 1em 0.625em; } .pythontutor-iframe { width: 125%; height: 125%; max-width: 125% !important; max-height: 125% !important; transform: scale(0.8); transform-origin: top left; border: none; } /* Landing page layout */ .header-img-div { display: flex; align-items: center; justify-content: center; margin: 0 auto; width: 100%; } .home-div { width: 100%; height: auto; display: flex; justify-content: center; align-items: center; padding: 3em 2em; background-color: var(--md-default-bg-color); color: var(--md-default-fg-color); font-size: 0.9rem; text-align: center; } .home-div--black { background-color: #101010; } .home-div[data-md-color-scheme="default"], .home-div[data-md-color-scheme="default"] h1, .home-div[data-md-color-scheme="default"] h2, .home-div[data-md-color-scheme="default"] h3, .home-div[data-md-color-scheme="default"] h4, .home-div[data-md-color-scheme="default"] h5, .home-div[data-md-color-scheme="default"] h6, .home-div[data-md-color-scheme="slate"], .home-div[data-md-color-scheme="slate"] h1, .home-div[data-md-color-scheme="slate"] h2, .home-div[data-md-color-scheme="slate"] h3, .home-div[data-md-color-scheme="slate"] h4, .home-div[data-md-color-scheme="slate"] h5, .home-div[data-md-color-scheme="slate"] h6 { color: var(--md-default-fg-color); } .section-content { width: 100%; height: auto; max-width: 70vw; } .hero-div { height: min(84vh, 75vw); width: min(112vh, 100vw); margin: -2.4rem auto 0; padding: 0; position: relative; font-size: min(1.8vh, 2.5vw); font-weight: normal; } .hero-div--en { font-size: min(1.65vh, 2.3vw); } .hero-div--ru { font-size: min(1.45vh, 2vw); } .hero-bg { width: 100%; height: 100%; object-fit: cover; position: absolute; } .hero-div>a>img { width: auto; position: absolute; transition: transform 0.3s ease-in-out, filter 0.3s ease-in-out; } .hero-div>a>span { margin: 0; position: absolute; transform: translateX(-50%) translateY(-50%); white-space: nowrap; color: white; } .hero-div>a:hover>img { filter: brightness(1.15) saturate(1.1) drop-shadow(0 0 0.5rem rgba(255, 255, 255, 0.2)); transform: scale(1.03); } .hero-div>a:hover>span { color: var(--md-typeset-btn-color); text-decoration: underline; } .heading-div { width: 100%; position: absolute; left: 50%; bottom: min(2vh, 3vw); transform: translateX(-50%); pointer-events: none; color: #fff; } /* Landing page CTAs */ .rounded-button { display: inline-flex; align-items: center; justify-content: center; gap: 0.38em; margin: 0 0.1em; padding: 0.72em 1.18em; border: 1px solid rgb(255 255 255 / 0.24); border-radius: 10em; background-color: rgb(24 24 24 / 0.2); color: rgb(232 241 240) !important; font-weight: 400; letter-spacing: 0.01em; line-height: 1.2; min-width: 7.2em; text-align: center; text-decoration: none; cursor: pointer; box-shadow: 0 0.3rem 1rem rgb(0 0 0 / 0.16); transition: color 0.15s ease-out, background-color 0.15s ease-out, border-color 0.15s ease-out, box-shadow 0.15s ease-out; backdrop-filter: saturate(115%) blur(0.18rem); -webkit-backdrop-filter: saturate(115%) blur(0.18rem); } .rounded-button:hover { background-color: rgb(255 255 255 / 0.2); border-color: rgb(255 255 255 / 0.34); color: rgb(244 249 248) !important; box-shadow: 0 0.4rem 1.2rem rgb(0 0 0 / 0.18); text-decoration: none; } .heading-div .rounded-button:first-of-type { border-color: rgb(160 223 217 / 0.42); background-color: rgb(42 104 99 / 0.2); color: rgb(233 245 243) !important; } .heading-div .rounded-button:first-of-type:hover { background-color: rgb(82 187 177 / 0.28); border-color: rgb(186 228 223 / 0.38); color: rgb(242 249 248) !important; } .rounded-button span { margin: 0 0 0.07em; white-space: nowrap; } .rounded-button svg { width: auto; height: 1.2em; margin-right: 0.5em; fill: currentColor; } .reading-media { display: flex; justify-content: center; align-items: flex-end; height: 32vw; } .reading-media+p { margin-top: 1em !important; } .media-block { height: 100%; margin: 0 0.2em; } .text-button { width: auto; margin: 2.7em auto; color: var(--md-typeset-btn-color); text-align: center; text-decoration: none; } .text-button span { white-space: nowrap; } .text-button svg { display: inline-block; width: auto; height: 0.9em; margin-left: 0.15em; padding-top: 0.17em; fill: var(--md-typeset-btn-color); background-size: cover; } a:hover .text-button span { text-decoration: underline; } .device-on-hover { width: auto; transition: transform 0.3s ease-in-out, filter 0.3s ease-in-out; } a:hover .device-on-hover { filter: drop-shadow(0 0 0.2rem rgba(0, 0, 0, 0.15)); transform: scale(1.01); } /* Landing page content blocks */ .code-badge { width: 100%; height: auto; margin: 1em auto; } .code-badge img { width: auto; height: 1.07em; } .intro-container { display: flex; align-items: center; margin: 2em auto; } .intro-image { flex-shrink: 0; flex-grow: 0; width: 50%; border-radius: 0.5em; box-shadow: var(--md-shadow-z2); } .intro-text { flex-grow: 1; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; width: fit-content; margin: 2em; text-align: left; } .intro-text>div { align-self: flex-start; width: auto; margin: 0 auto; } .intro-text svg path { fill: var(--md-primary-bg-color); } .endor-text { width: 50%; } .intro-quote { color: var(--md-accent-fg-color); font-weight: bold; } .home-div .intro-quote { color: var(--md-default-fg-color--light) !important; } .profile-div { display: flex; flex-wrap: wrap; justify-content: center; max-width: 40em; margin: 1em auto; } .profile-cell { flex: 1 1 15%; margin: 1em 0; text-align: center; } .profile-cell a:hover b, .profile-cell a:focus-visible b { text-decoration: underline; } .profile-img { width: 5em; border-radius: 50%; margin-bottom: 0.5em; } .translator-profile-div { gap: 0.5em; } .translator-profile-cell { flex: 0 0 auto; margin: 0.5em 0; min-width: 8.5em; } .giscus-container { width: 40em; max-width: 100%; margin: 0 auto; } /* Embedded media */ .video-container { position: relative; padding-bottom: 56.25%; height: 0; } .video-container iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .starfield { position: absolute; width: 100%; height: 100%; z-index: 0; background-color: var(--hero-starfield-bg-color, transparent); } .starfield-origin { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } /* Responsive adjustments */ @media screen and (max-width: 76.25em) { .section-content { max-width: 95vw; } .reading-media { height: 33vw; } .contrib-image { width: 100%; } } @media screen and (max-width: 60em) { .home-div { font-size: 0.75rem; } .hero-div { margin-top: -4rem; } .intro-container { flex-direction: column; } .intro-text { width: auto; order: 2; margin: 0 auto; } .endor-text { width: auto; margin: 0 auto; } .intro-image { width: 100%; order: 1; margin-bottom: 1em; } .text-button { margin: 0.7em auto; } .profile-cell { flex: 1 1 30%; } } ================================================ FILE: overrides/zensical/zensical.toml ================================================ [project] extra_css = [ "stylesheets/extra.css", "stylesheets/animation_player.css", ] extra_javascript = [ "javascripts/mathjax.js", "https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-mml-chtml.min.js", "javascripts/animation_player.js", ] [project.theme] features = [ "content.action.edit", "content.code.annotate", "content.code.copy", "content.tabs.link", "content.tooltips", "navigation.indexes", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow", ] ================================================ FILE: ru/README.md ================================================

hello-algo-typing-svg
Учебник по структурам данных и алгоритмам с анимированными схемами и кодом, готовым к запуску в один клик

Читать онлайнСкачать PDF/EPUB

简体中文繁體中文English日本語 | Русский

## О книге Этот проект призван создать бесплатный, открытый и дружелюбный к начинающим учебник по структурам данных и алгоритмам. - Книга построена на анимированных схемах, понятном изложении и плавной кривой обучения, помогая начинающим выстроить карту знаний по структурам данных и алгоритмам. - Исходный код можно запускать в один клик, чтобы на практике развивать навыки программирования и понимать, как работают алгоритмы и как устроены структуры данных внутри. - Мы поддерживаем совместное обучение: задавайте вопросы, делитесь идеями и продвигайтесь вперед через обсуждение. Если книга оказалась вам полезной, пожалуйста, поставьте Star :star: в правом верхнем углу страницы. Спасибо! ## Рекомендации > «Понятная вводная книга по структурам данных и алгоритмам, которая направляет читателя к обучению и умом, и руками. Настоятельно рекомендую начинающим изучать алгоритмы именно с нее.» > > **—— Junhui Deng, профессор факультета компьютерных наук Университета Цинхуа** > «Если бы у меня была “Hello Algo”, когда я изучал структуры данных и алгоритмы, учиться было бы в десять раз проще!» > > **—— Mu Li, Senior Principal Scientist, Amazon** ## Благодарности

Warp-Github-LG-02

[Warp создан для программирования с несколькими AI-агентами.](https://go.warp.dev/hello-algo) Очень рекомендуем терминал Warp: красивый интерфейс, полезные AI-возможности и отличное общее впечатление от работы. ## Участие Эта открытая книга продолжает активно развиваться, и мы будем рады вашему участию, чтобы сделать обучение для читателей еще качественнее. - [Исправление содержания](https://www.hello-algo.com/ru/chapter_appendix/contribution/): помогайте исправлять или указывать в комментариях грамматические ошибки, пропуски в содержании, двусмысленные формулировки, неработающие ссылки и баги в коде. - [Перевод кода на другие языки](https://github.com/krahets/hello-algo/issues/15): приглашаем вносить вклад в код на разных языках программирования; сейчас уже поддерживаются Python, Java, C++, Go, JavaScript и другие. Будем рады вашим замечаниям и предложениям. Если у вас есть вопросы, создайте Issue или свяжитесь через WeChat: `krahets-jyd`. Мы благодарим каждого участника, работавшего над этой книгой. Именно их самоотверженный вклад делает ее лучше:

## License The texts, code, images, photos, and videos in this repository are licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). ================================================ FILE: ru/codes/Dockerfile ================================================ FROM ubuntu:latest # Use Ubuntu image from Aliyun RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \ sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \ sed -i 's/ports.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list RUN apt-get update && apt-get install -y wget # Install languages environment ARG LANGS RUN for LANG in $LANGS; do \ case $LANG in \ python) \ apt-get install -y python3.10 && \ update-alternatives --install /usr/bin/python python /usr/bin/python3.10 1 ;; \ cpp) \ apt-get install -y g++ gdb ;; \ java) \ apt-get install -y openjdk-17-jdk ;; \ csharp) \ wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \ dpkg -i packages-microsoft-prod.deb && \ apt-get update && \ apt-get install -y dotnet-sdk-8.0 ;; \ # More languages... *) \ echo "Warning: No installation workflow for $LANG" ;; \ esac \ done WORKDIR /codes COPY ./ ./ CMD ["/bin/bash"] ================================================ FILE: ru/codes/c/.gitignore ================================================ # Ignore all * # Unignore all with extensions !*.* # Unignore all dirs !*/ *.dSYM/ build/ ================================================ FILE: ru/codes/c/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(hello_algo C) set(CMAKE_C_STANDARD 11) include_directories(./include) add_subdirectory(chapter_computational_complexity) add_subdirectory(chapter_array_and_linkedlist) add_subdirectory(chapter_stack_and_queue) add_subdirectory(chapter_hashing) add_subdirectory(chapter_tree) add_subdirectory(chapter_heap) add_subdirectory(chapter_graph) add_subdirectory(chapter_searching) add_subdirectory(chapter_sorting) add_subdirectory(chapter_divide_and_conquer) add_subdirectory(chapter_backtracking) add_subdirectory(chapter_dynamic_programming) add_subdirectory(chapter_greedy) ================================================ FILE: ru/codes/c/chapter_array_and_linkedlist/CMakeLists.txt ================================================ add_executable(array array.c) add_executable(linked_list linked_list.c) add_executable(my_list my_list.c) ================================================ FILE: ru/codes/c/chapter_array_and_linkedlist/array.c ================================================ /** * File: array.c * Created Time: 2022-12-20 * Author: MolDuM (moldum@163.com) */ #include "../utils/common.h" /* Случайный доступ к элементу */ int randomAccess(int *nums, int size) { // Случайным образом выбрать число из интервала [0, size) int randomIndex = rand() % size; // Получить и вернуть случайный элемент int randomNum = nums[randomIndex]; return randomNum; } /* Увеличить длину массива */ int *extend(int *nums, int size, int enlarge) { // Инициализировать массив увеличенной длины int *res = (int *)malloc(sizeof(int) * (size + enlarge)); // Скопировать все элементы исходного массива в новый массив for (int i = 0; i < size; i++) { res[i] = nums[i]; } // Инициализировать расширенное пространство for (int i = size; i < size + enlarge; i++) { res[i] = 0; } // Вернуть новый массив после расширения return res; } /* Вставить элемент num по индексу index в массив */ void insert(int *nums, int size, int num, int index) { // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад for (int i = size - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Присвоить num элементу по индексу index nums[index] = num; } /* Удалить элемент по индексу index */ // Внимание: stdio.h уже использует ключевое слово remove void removeItem(int *nums, int size, int index) { // Сдвинуть все элементы после индекса index на одну позицию вперед for (int i = index; i < size - 1; i++) { nums[i] = nums[i + 1]; } } /* Обход массива */ void traverse(int *nums, int size) { int count = 0; // Обход массива по индексам for (int i = 0; i < size; i++) { count += nums[i]; } } /* Найти заданный элемент в массиве */ int find(int *nums, int size, int target) { for (int i = 0; i < size; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ int main() { /* Инициализация массива */ int size = 5; int arr[5]; printf("Массив arr = "); printArray(arr, size); int nums[] = {1, 3, 2, 5, 4}; printf("Массив nums = "); printArray(nums, size); /* Случайный доступ */ int randomNum = randomAccess(nums, size); printf("Случайный элемент из nums = %d", randomNum); /* Расширение длины */ int enlarge = 3; int *res = extend(nums, size, enlarge); size += enlarge; printf("После увеличения длины массива до 8 nums = "); printArray(res, size); /* Вставка элемента */ insert(res, size, 6, 3); printf("После вставки числа 6 по индексу 3 nums = "); printArray(res, size); /* Удаление элемента */ removeItem(res, size, 2); printf("После удаления элемента по индексу 2 nums = "); printArray(res, size); /* Обход массива */ traverse(res, size); /* Поиск элемента */ int index = find(res, size, 3); printf("Индекс элемента 3 в res = %d\n", index); /* Освободить память */ free(res); return 0; } ================================================ FILE: ru/codes/c/chapter_array_and_linkedlist/linked_list.c ================================================ /** * File: linked_list.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* Вставить узел P после узла n0 в связном списке */ void insert(ListNode *n0, ListNode *P) { ListNode *n1 = n0->next; P->next = n1; n0->next = P; } /* Удалить первый узел после узла n0 в связном списке */ // Внимание: stdio.h уже использует ключевое слово remove void removeItem(ListNode *n0) { if (!n0->next) return; // n0 -> P -> n1 ListNode *P = n0->next; ListNode *n1 = P->next; n0->next = n1; // Освободить память free(P); } /* Доступ к узлу связного списка по индексу index */ ListNode *access(ListNode *head, int index) { for (int i = 0; i < index; i++) { if (head == NULL) return NULL; head = head->next; } return head; } /* Найти в связном списке первый узел со значением target */ int find(ListNode *head, int target) { int index = 0; while (head) { if (head->val == target) return index; head = head->next; index++; } return -1; } /* Driver Code */ int main() { /* Инициализация связного списка */ // Инициализация всех узлов ListNode *n0 = newListNode(1); ListNode *n1 = newListNode(3); ListNode *n2 = newListNode(2); ListNode *n3 = newListNode(5); ListNode *n4 = newListNode(4); // Построить ссылки между узлами n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; printf("Инициализированный связный список\r\n"); printLinkedList(n0); /* Вставка узла */ insert(n0, newListNode(0)); printf("Связный список после вставки узла\r\n"); printLinkedList(n0); /* Удаление узла */ removeItem(n0); printf("Связный список после удаления узла\r\n"); printLinkedList(n0); /* Доступ к узлу */ ListNode *node = access(n0, 3); printf("Значение узла по индексу 3 в связном списке = %d\r\n", node->val); /* Поиск узла */ int index = find(n0, 2); printf("Индекс узла со значением 2 в связном списке = %d\r\n", index); // Освободить память freeMemoryLinkedList(n0); return 0; } ================================================ FILE: ru/codes/c/chapter_array_and_linkedlist/my_list.c ================================================ /** * File: my_list.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* Класс списка */ typedef struct { int *arr; // Массив (для хранения элементов списка) int capacity; // Вместимость списка int size; // Размер списка int extendRatio; // Коэффициент расширения списка при каждом увеличении } MyList; void extendCapacity(MyList *nums); /* Конструктор */ MyList *newMyList() { MyList *nums = malloc(sizeof(MyList)); nums->capacity = 10; nums->arr = malloc(sizeof(int) * nums->capacity); nums->size = 0; nums->extendRatio = 2; return nums; } /* Деструктор */ void delMyList(MyList *nums) { free(nums->arr); free(nums); } /* Получить длину списка */ int size(MyList *nums) { return nums->size; } /* Получить вместимость списка */ int capacity(MyList *nums) { return nums->capacity; } /* Доступ к элементу */ int get(MyList *nums, int index) { assert(index >= 0 && index < nums->size); return nums->arr[index]; } /* Обновление элемента */ void set(MyList *nums, int index, int num) { assert(index >= 0 && index < nums->size); nums->arr[index] = num; } /* Добавление элемента в конец */ void add(MyList *nums, int num) { if (size(nums) == capacity(nums)) { extendCapacity(nums); // Расширение емкости } nums->arr[size(nums)] = num; nums->size++; } /* Вставка элемента в середину */ void insert(MyList *nums, int index, int num) { assert(index >= 0 && index < size(nums)); // При превышении вместимости по числу элементов запускается расширение if (size(nums) == capacity(nums)) { extendCapacity(nums); // Расширение емкости } for (int i = size(nums); i > index; --i) { nums->arr[i] = nums->arr[i - 1]; } nums->arr[index] = num; nums->size++; } /* Удаление элемента */ // Внимание: stdio.h уже использует ключевое слово remove int removeItem(MyList *nums, int index) { assert(index >= 0 && index < size(nums)); int num = nums->arr[index]; for (int i = index; i < size(nums) - 1; i++) { nums->arr[i] = nums->arr[i + 1]; } nums->size--; return num; } /* Расширение списка */ void extendCapacity(MyList *nums) { // Сначала выделить память int newCapacity = capacity(nums) * nums->extendRatio; int *extend = (int *)malloc(sizeof(int) * newCapacity); int *temp = nums->arr; // Скопировать старые данные в новые for (int i = 0; i < size(nums); i++) extend[i] = nums->arr[i]; // Освободить старые данные free(temp); // Обновить новые данные nums->arr = extend; nums->capacity = newCapacity; } /* Преобразовать список в Array для вывода */ int *toArray(MyList *nums) { return nums->arr; } /* Driver Code */ int main() { /* Инициализация списка */ MyList *nums = newMyList(); /* Добавление элемента в конец */ add(nums, 1); add(nums, 3); add(nums, 2); add(nums, 5); add(nums, 4); printf("Список nums = "); printArray(toArray(nums), size(nums)); printf("Вместимость = %d, длина = %d\n", capacity(nums), size(nums)); /* Вставка элемента в середину */ insert(nums, 3, 6); printf("После вставки числа 6 по индексу 3 nums = "); printArray(toArray(nums), size(nums)); /* Удаление элемента */ removeItem(nums, 3); printf("После удаления элемента по индексу 3 nums = "); printArray(toArray(nums), size(nums)); /* Доступ к элементу */ int num = get(nums, 1); printf("Элемент по индексу 1: num = %d\n", num); /* Обновление элемента */ set(nums, 1, 0); printf("После обновления элемента по индексу 1 на 0 nums = "); printArray(toArray(nums), size(nums)); /* Проверка механизма расширения */ for (int i = 0; i < 10; i++) { // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения add(nums, i); } printf("После расширения список nums = "); printArray(toArray(nums), size(nums)); printf("Вместимость = %d, длина = %d\n", capacity(nums), size(nums)); /* Освободить выделенную память */ delMyList(nums); return 0; } ================================================ FILE: ru/codes/c/chapter_backtracking/CMakeLists.txt ================================================ add_executable(permutations_i permutations_i.c) add_executable(permutations_ii permutations_ii.c) add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.c) add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.c) add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.c) add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.c) add_executable(subset_sum_i_naive subset_sum_i_naive.c) add_executable(subset_sum_i subset_sum_i.c) add_executable(subset_sum_ii subset_sum_ii.c) add_executable(n_queens n_queens.c) ================================================ FILE: ru/codes/c/chapter_backtracking/n_queens.c ================================================ /** * File : n_queens.c * Created Time: 2023-09-25 * Author : lucas (superrat6@gmail.com) */ #include "../utils/common.h" #define MAX_SIZE 100 /* Алгоритм бэктрекинга: n ферзей */ void backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE], bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) { // Когда все строки уже обработаны, записать решение if (row == n) { res[*resSize] = (char **)malloc(sizeof(char *) * n); for (int i = 0; i < n; ++i) { res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1)); strcpy(res[*resSize][i], state[i]); } (*resSize)++; return; } // Обойти все столбцы for (int col = 0; col < n; col++) { // Вычислить главную и побочную диагонали, соответствующие этой клетке int diag1 = row - col + n - 1; int diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Попытка: поставить ферзя в эту клетку state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // Перейти к размещению следующей строки backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Решить задачу о n ферзях */ char ***nQueens(int n, int *returnSize) { char state[MAX_SIZE][MAX_SIZE]; // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { state[i][j] = '#'; } state[i][n] = '\0'; } bool cols[MAX_SIZE] = {false}; // Отмечать, есть ли ферзь в столбце bool diags1[2 * MAX_SIZE - 1] = {false}; // Отмечать наличие ферзя на главной диагонали bool diags2[2 * MAX_SIZE - 1] = {false}; // Отмечать наличие ферзя на побочной диагонали char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE); *returnSize = 0; backtrack(0, n, state, res, returnSize, cols, diags1, diags2); return res; } /* Driver Code */ int main() { int n = 4; int returnSize; char ***res = nQueens(n, &returnSize); printf("Размер входной доски = %d\n", n); printf("Количество способов расстановки ферзей: %d\n", returnSize); for (int i = 0; i < returnSize; ++i) { for (int j = 0; j < n; ++j) { printf("["); for (int k = 0; res[i][j][k] != '\0'; ++k) { printf("%c", res[i][j][k]); if (res[i][j][k + 1] != '\0') { printf(", "); } } printf("]\n"); } printf("---------------------\n"); } // Освободить память for (int i = 0; i < returnSize; ++i) { for (int j = 0; j < n; ++j) { free(res[i][j]); } free(res[i]); } free(res); return 0; } ================================================ FILE: ru/codes/c/chapter_backtracking/permutations_i.c ================================================ /** * File: permutations_i.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com), krahets (krahets@163.com) */ #include "../utils/common.h" // Предположим, что существует не более 1000 перестановок #define MAX_SIZE 1000 /* Алгоритм бэктрекинга: все перестановки I */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // Когда длина состояния равна числу элементов, записать решение if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // Перебор всех вариантов выбора for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true; state[stateSize] = choice; // Перейти к следующему выбору backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; } } } /* Все перестановки I */ int **permutationsI(int *nums, int numsSize, int *returnSize) { int *state = (int *)malloc(numsSize * sizeof(int)); bool *selected = (bool *)malloc(numsSize * sizeof(bool)); for (int i = 0; i < numsSize; i++) { selected[i] = false; } int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); *returnSize = 0; backtrack(state, 0, nums, numsSize, selected, res, returnSize); free(state); free(selected); return res; } /* Driver Code */ int main() { int nums[] = {1, 2, 3}; int numsSize = sizeof(nums) / sizeof(nums[0]); int returnSize; int **res = permutationsI(nums, numsSize, &returnSize); printf("Входной массив nums = "); printArray(nums, numsSize); printf("\nВсе перестановки res = \n"); for (int i = 0; i < returnSize; i++) { printArray(res[i], numsSize); } // Освободить память for (int i = 0; i < returnSize; i++) { free(res[i]); } free(res); return 0; } ================================================ FILE: ru/codes/c/chapter_backtracking/permutations_ii.c ================================================ /** * File: permutations_ii.c * Created Time: 2023-10-17 * Author: krahets (krahets@163.com) */ #include "../utils/common.h" // Предположить, что существует не более 1000 перестановок, а максимальный элемент равен 1000 #define MAX_SIZE 1000 /* Алгоритм бэктрекинга: все перестановки II */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // Когда длина состояния равна числу элементов, записать решение if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // Перебор всех вариантов выбора bool duplicated[MAX_SIZE] = {false}; for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && !duplicated[choice]) { // Попытка: сделать выбор и обновить состояние duplicated[choice] = true; // Записать значения уже выбранных элементов selected[i] = true; state[stateSize] = choice; // Перейти к следующему выбору backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; } } } /* Все перестановки II */ int **permutationsII(int *nums, int numsSize, int *returnSize) { int *state = (int *)malloc(numsSize * sizeof(int)); bool *selected = (bool *)malloc(numsSize * sizeof(bool)); for (int i = 0; i < numsSize; i++) { selected[i] = false; } int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); *returnSize = 0; backtrack(state, 0, nums, numsSize, selected, res, returnSize); free(state); free(selected); return res; } /* Driver Code */ int main() { int nums[] = {1, 1, 2}; int numsSize = sizeof(nums) / sizeof(nums[0]); int returnSize; int **res = permutationsII(nums, numsSize, &returnSize); printf("Входной массив nums = "); printArray(nums, numsSize); printf("\nВсе перестановки res = \n"); for (int i = 0; i < returnSize; i++) { printArray(res[i], numsSize); } // Освободить память for (int i = 0; i < returnSize; i++) { free(res[i]); } free(res); return 0; } ================================================ FILE: ru/codes/c/chapter_backtracking/preorder_traversal_i_compact.c ================================================ /** * File: preorder_traversal_i_compact.c * Created Time: 2023-05-10 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // Предположить, что длина результата не превышает 100 #define MAX_SIZE 100 TreeNode *res[MAX_SIZE]; int resSize = 0; /* Предварительный обход: пример 1 */ void preOrder(TreeNode *root) { if (root == NULL) { return; } if (root->val == 7) { // Записать решение res[resSize++] = root; } preOrder(root->left); preOrder(root->right); } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\nИнициализация двоичного дерева\n"); printTree(root); // Предварительный обход preOrder(root); printf("\nВывести все узлы со значением 7\n"); int *vals = malloc(resSize * sizeof(int)); for (int i = 0; i < resSize; i++) { vals[i] = res[i]->val; } printArray(vals, resSize); // Освободить память freeMemoryTree(root); free(vals); return 0; } ================================================ FILE: ru/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c ================================================ /** * File: preorder_traversal_ii_compact.c * Created Time: 2023-05-28 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // Предположим, что длина пути и результата не превышает 100 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* Предварительный обход: пример 2 */ void preOrder(TreeNode *root) { if (root == NULL) { return; } // Попытка path[pathSize++] = root; if (root->val == 7) { // Записать решение for (int i = 0; i < pathSize; ++i) { res[resSize][i] = path[i]; } resSize++; } preOrder(root->left); preOrder(root->right); // Откат pathSize--; } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\nИнициализация двоичного дерева\n"); printTree(root); // Предварительный обход preOrder(root); printf("\nВывести все пути от корня к узлу 7\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // Освободить память freeMemoryTree(root); return 0; } ================================================ FILE: ru/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c ================================================ /** * File: preorder_traversal_iii_compact.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // Предположим, что длина пути и результата не превышает 100 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* Предварительный обход: пример 3 */ void preOrder(TreeNode *root) { // Отсечение if (root == NULL || root->val == 3) { return; } // Попытка path[pathSize++] = root; if (root->val == 7) { // Записать решение for (int i = 0; i < pathSize; i++) { res[resSize][i] = path[i]; } resSize++; } preOrder(root->left); preOrder(root->right); // Откат pathSize--; } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\nИнициализация двоичного дерева\n"); printTree(root); // Предварительный обход preOrder(root); printf("\nВывести все пути от корня к узлу 7, не содержащие узлов со значением 3\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // Освободить память freeMemoryTree(root); return 0; } ================================================ FILE: ru/codes/c/chapter_backtracking/preorder_traversal_iii_template.c ================================================ /** * File: preorder_traversal_iii_template.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // Предположим, что длина пути и результата не превышает 100 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* Проверить, является ли текущее состояние решением */ bool isSolution(void) { return pathSize > 0 && path[pathSize - 1]->val == 7; } /* Записать решение */ void recordSolution(void) { for (int i = 0; i < pathSize; i++) { res[resSize][i] = path[i]; } resSize++; } /* Проверить, допустим ли этот выбор в текущем состоянии */ bool isValid(TreeNode *choice) { return choice != NULL && choice->val != 3; } /* Обновить состояние */ void makeChoice(TreeNode *choice) { path[pathSize++] = choice; } /* Восстановить состояние */ void undoChoice(void) { pathSize--; } /* Алгоритм бэктрекинга: пример 3 */ void backtrack(TreeNode *choices[2]) { // Проверить, является ли текущее состояние решением if (isSolution()) { // Записать решение recordSolution(); } // Перебор всех вариантов выбора for (int i = 0; i < 2; i++) { TreeNode *choice = choices[i]; // Отсечение: проверить допустимость выбора if (isValid(choice)) { // Попытка: сделать выбор и обновить состояние makeChoice(choice); // Перейти к следующему выбору TreeNode *nextChoices[2] = {choice->left, choice->right}; backtrack(nextChoices); // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(); } } } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\nИнициализация двоичного дерева\n"); printTree(root); // Алгоритм бэктрекинга TreeNode *choices[2] = {root, NULL}; backtrack(choices); printf("\nВывести все пути от корня к узлу 7, не содержащие узлов со значением 3\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // Освободить память freeMemoryTree(root); return 0; } ================================================ FILE: ru/codes/c/chapter_backtracking/subset_sum_i.c ================================================ /** * File: subset_sum_i.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // Состояние (подмножество) int state[MAX_SIZE]; int stateSize = 0; // Список результатов (список подмножеств) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* Алгоритм бэктрекинга: сумма подмножеств I */ void backtrack(int target, int *choices, int choicesSize, int start) { // Если сумма подмножества равна target, записать решение if (target == 0) { for (int i = 0; i < stateSize; ++i) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств for (int i = start; i < choicesSize; i++) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if (target - choices[i] < 0) { break; } // Попытка: сделать выбор и обновить target и start state[stateSize] = choices[i]; stateSize++; // Перейти к следующему выбору backtrack(target - choices[i], choices, choicesSize, i); // Откат: отменить выбор и восстановить предыдущее состояние stateSize--; } } /* Функция сравнения */ int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); } /* Решить задачу суммы подмножеств I */ void subsetSumI(int *nums, int numsSize, int target) { qsort(nums, numsSize, sizeof(int), cmp); // Отсортировать nums int start = 0; // Стартовая вершина обхода backtrack(target, nums, numsSize, start); } /* Driver Code */ int main() { int nums[] = {3, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumI(nums, numsSize, target); printf("Входной массив nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("Все подмножества с суммой %d: \n", target); for (int i = 0; i < resSize; ++i) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: ru/codes/c/chapter_backtracking/subset_sum_i_naive.c ================================================ /** * File: subset_sum_i_naive.c * Created Time: 2023-07-28 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // Состояние (подмножество) int state[MAX_SIZE]; int stateSize = 0; // Список результатов (список подмножеств) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* Алгоритм бэктрекинга: сумма подмножеств I */ void backtrack(int target, int total, int *choices, int choicesSize) { // Если сумма подмножества равна target, записать решение if (total == target) { for (int i = 0; i < stateSize; i++) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // Перебор всех вариантов выбора for (int i = 0; i < choicesSize; i++) { // Отсечение: если сумма подмножества превышает target, пропустить этот выбор if (total + choices[i] > target) { continue; } // Попытка: сделать выбор и обновить элемент и total state[stateSize++] = choices[i]; // Перейти к следующему выбору backtrack(target, total + choices[i], choices, choicesSize); // Откат: отменить выбор и восстановить предыдущее состояние stateSize--; } } /* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ void subsetSumINaive(int *nums, int numsSize, int target) { resSize = 0; // Инициализировать число решений нулем backtrack(target, 0, nums, numsSize); } /* Driver Code */ int main() { int nums[] = {3, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumINaive(nums, numsSize, target); printf("Входной массив nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("Все подмножества с суммой %d: \n", target); for (int i = 0; i < resSize; i++) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: ru/codes/c/chapter_backtracking/subset_sum_ii.c ================================================ /** * File: subset_sum_ii.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // Состояние (подмножество) int state[MAX_SIZE]; int stateSize = 0; // Список результатов (список подмножеств) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* Алгоритм бэктрекинга: сумма подмножеств II */ void backtrack(int target, int *choices, int choicesSize, int start) { // Если сумма подмножества равна target, записать решение if (target == 0) { for (int i = 0; i < stateSize; i++) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента for (int i = start; i < choicesSize; i++) { // Отсечение 1: если сумма подмножества превышает target, сразу пропустить if (target - choices[i] < 0) { continue; } // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить if (i > start && choices[i] == choices[i - 1]) { continue; } // Попытка: сделать выбор и обновить target и start state[stateSize] = choices[i]; stateSize++; // Перейти к следующему выбору backtrack(target - choices[i], choices, choicesSize, i + 1); // Откат: отменить выбор и восстановить предыдущее состояние stateSize--; } } /* Функция сравнения */ int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); } /* Решить задачу суммы подмножеств II */ void subsetSumII(int *nums, int numsSize, int target) { // Отсортировать nums qsort(nums, numsSize, sizeof(int), cmp); // Начать бэктрекинг backtrack(target, nums, numsSize, 0); } /* Driver Code */ int main() { int nums[] = {4, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumII(nums, numsSize, target); printf("Входной массив nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("Все подмножества с суммой %d: \n", target); for (int i = 0; i < resSize; ++i) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: ru/codes/c/chapter_computational_complexity/CMakeLists.txt ================================================ add_executable(iteration iteration.c) add_executable(recursion recursion.c) add_executable(time_complexity time_complexity.c) add_executable(worst_best_time_complexity worst_best_time_complexity.c) add_executable(space_complexity space_complexity.c) ================================================ FILE: ru/codes/c/chapter_computational_complexity/iteration.c ================================================ /** * File: iteration.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com), MwumLi (mwumli@hotmail.com) */ #include "../utils/common.h" /* Цикл for */ int forLoop(int n) { int res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } /* Цикл while */ int whileLoop(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Обновить условную переменную } return res; } /* Цикл while (двойное обновление) */ int whileLoopII(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i++; i *= 2; } return res; } /* Двойной цикл for */ char *nestedForLoop(int n) { // n * n — это число соответствующих точек, а максимальная длина строки "(i, j), " равна 6+10*2, плюс дополнительное место для завершающего нулевого символа \0 int size = n * n * 26 + 1; char *res = malloc(size * sizeof(char)); // Цикл по i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // Цикл по j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { char tmp[26]; snprintf(tmp, sizeof(tmp), "(%d, %d), ", i, j); strncat(res, tmp, size - strlen(res) - 1); } } return res; } /* Driver Code */ int main() { int n = 5; int res; res = forLoop(n); printf("\nРезультат суммирования в цикле for res = %d\n", res); res = whileLoop(n); printf("\nРезультат суммирования в цикле while res = %d\n", res); res = whileLoopII(n); printf("\nРезультат суммирования в цикле while (двойное обновление) res = %d\n", res); char *resStr = nestedForLoop(n); printf("\nРезультат двойного цикла for %s\r\n", resStr); free(resStr); return 0; } ================================================ FILE: ru/codes/c/chapter_computational_complexity/recursion.c ================================================ /** * File: recursion.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Рекурсия */ int recur(int n) { // Условие завершения if (n == 1) return 1; // Рекурсия: рекурсивный вызов int res = recur(n - 1); // Возврат: вернуть результат return n + res; } /* Имитация рекурсии итерацией */ int forLoopRecur(int n) { int stack[1000]; // Использовать большой массив для имитации стека int top = -1; // Индекс вершины стека int res = 0; // Рекурсия: рекурсивный вызов for (int i = n; i > 0; i--) { // Имитировать «рекурсию» с помощью операции помещения в стек stack[1 + top++] = i; } // Возврат: вернуть результат while (top >= 0) { // Имитировать «возврат» с помощью операции извлечения из стека res += stack[top--]; } // res = 1+2+3+...+n return res; } /* Хвостовая рекурсия */ int tailRecur(int n, int res) { // Условие завершения if (n == 0) return res; // Хвостовой рекурсивный вызов return tailRecur(n - 1, res + n); } /* Последовательность Фибоначчи: рекурсия */ int fib(int n) { // Условие завершения: f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // Рекурсивный вызов f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // Вернуть результат f(n) return res; } /* Driver Code */ int main() { int n = 5; int res; res = recur(n); printf("\nРезультат суммирования в рекурсивной функции res = %d\n", res); res = forLoopRecur(n); printf("\nРезультат суммирования с использованием итерации для имитации рекурсии res = %d\n", res); res = tailRecur(n, 0); printf("\nРезультат суммирования в хвостовой рекурсии res = %d\n", res); res = fib(n); printf("\nЭлемент последовательности Фибоначчи с индексом %d = %d\n", n, res); return 0; } ================================================ FILE: ru/codes/c/chapter_computational_complexity/space_complexity.c ================================================ /** * File: space_complexity.c * Created Time: 2023-04-15 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Функция */ int func() { // Выполнить некоторые операции return 0; } /* Постоянная сложность */ void constant(int n) { // Константы, переменные и объекты занимают O(1) памяти const int a = 0; int b = 0; int nums[1000]; ListNode *node = newListNode(0); free(node); // Переменные в цикле занимают O(1) памяти for (int i = 0; i < n; i++) { int c = 0; } // Функции в цикле занимают O(1) памяти for (int i = 0; i < n; i++) { func(); } } /* Хеш-таблица */ typedef struct { int key; int val; UT_hash_handle hh; // Реализовано на основе uthash.h } HashTable; /* Линейная сложность */ void linear(int n) { // Массив длины n занимает O(n) памяти int *nums = malloc(sizeof(int) * n); free(nums); // Список длины n занимает O(n) памяти ListNode **nodes = malloc(sizeof(ListNode *) * n); for (int i = 0; i < n; i++) { nodes[i] = newListNode(i); } // Освобождение памяти for (int i = 0; i < n; i++) { free(nodes[i]); } free(nodes); // Хеш-таблица длины n занимает O(n) памяти HashTable *h = NULL; for (int i = 0; i < n; i++) { HashTable *tmp = malloc(sizeof(HashTable)); tmp->key = i; tmp->val = i; HASH_ADD_INT(h, key, tmp); } // Освобождение памяти HashTable *curr, *tmp; HASH_ITER(hh, h, curr, tmp) { HASH_DEL(h, curr); free(curr); } } /* Линейная сложность (рекурсивная реализация) */ void linearRecur(int n) { printf("Рекурсия n = %d\r\n", n); if (n == 1) return; linearRecur(n - 1); } /* Квадратичная сложность */ void quadratic(int n) { // Двумерный список занимает O(n^2) памяти int **numMatrix = malloc(sizeof(int *) * n); for (int i = 0; i < n; i++) { int *tmp = malloc(sizeof(int) * n); for (int j = 0; j < n; j++) { tmp[j] = 0; } numMatrix[i] = tmp; } // Освобождение памяти for (int i = 0; i < n; i++) { free(numMatrix[i]); } free(numMatrix); } /* Квадратичная сложность (рекурсивная реализация) */ int quadraticRecur(int n) { if (n <= 0) return 0; int *nums = malloc(sizeof(int) * n); printf("Рекурсия n = %d, длина nums = %d\r\n", n, n); int res = quadraticRecur(n - 1); free(nums); return res; } /* Экспоненциальная сложность (построение полного двоичного дерева) */ TreeNode *buildTree(int n) { if (n == 0) return NULL; TreeNode *root = newTreeNode(0); root->left = buildTree(n - 1); root->right = buildTree(n - 1); return root; } /* Driver Code */ int main() { int n = 5; // Постоянная сложность constant(n); // Линейная сложность linear(n); linearRecur(n); // Квадратичная сложность quadratic(n); quadraticRecur(n); // Экспоненциальная сложность TreeNode *root = buildTree(n); printTree(root); // Освободить память freeMemoryTree(root); return 0; } ================================================ FILE: ru/codes/c/chapter_computational_complexity/time_complexity.c ================================================ /** * File: time_complexity.c * Created Time: 2023-01-03 * Author: codingonion (coderonion@gmail.com) */ #include "../utils/common.h" /* Постоянная сложность */ int constant(int n) { int count = 0; int size = 100000; int i = 0; for (int i = 0; i < size; i++) { count++; } return count; } /* Линейная сложность */ int linear(int n) { int count = 0; for (int i = 0; i < n; i++) { count++; } return count; } /* Линейная сложность (обход массива) */ int arrayTraversal(int *nums, int n) { int count = 0; // Число итераций пропорционально длине массива for (int i = 0; i < n; i++) { count++; } return count; } /* Квадратичная сложность */ int quadratic(int n) { int count = 0; // Число итераций квадратично зависит от размера данных n for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* Квадратичная сложность (пузырьковая сортировка) */ int bubbleSort(int *nums, int n) { int count = 0; // Счетчик // Внешний цикл: неотсортированный диапазон [0, i] for (int i = n - 1; i > 0; i--) { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // Обмен элементов включает 3 элементарные операции } } } return count; } /* Экспоненциальная сложность (итеративная реализация) */ int exponential(int n) { int count = 0; int bas = 1; // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) for (int i = 0; i < n; i++) { for (int j = 0; j < bas; j++) { count++; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* Экспоненциальная сложность (рекурсивная реализация) */ int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* Логарифмическая сложность (итеративная реализация) */ int logarithmic(int n) { int count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* Логарифмическая сложность (рекурсивная реализация) */ int logRecur(int n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* Линейно-логарифмическая сложность */ int linearLogRecur(int n) { if (n <= 1) return 1; int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (int i = 0; i < n; i++) { count++; } return count; } /* Факториальная сложность (рекурсивная реализация) */ int factorialRecur(int n) { if (n == 0) return 1; int count = 0; for (int i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ int main(int argc, char *argv[]) { // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях int n = 8; printf("Размер входных данных n = %d\n", n); int count = constant(n); printf("Количество операций постоянной сложности = %d\n", count); count = linear(n); printf("Количество операций линейной сложности = %d\n", count); // Выделить память в куче (создать одномерный массив переменной длины: число элементов равно n, тип элементов — int) int *nums = (int *)malloc(n * sizeof(int)); count = arrayTraversal(nums, n); printf("Количество операций линейной сложности (обход массива) = %d\n", count); count = quadratic(n); printf("Количество операций квадратичной сложности = %d\n", count); for (int i = 0; i < n; i++) { nums[i] = n - i; // [n,n-1,...,2,1] } count = bubbleSort(nums, n); printf("Количество операций квадратичной сложности (пузырьковая сортировка) = %d\n", count); count = exponential(n); printf("Количество операций экспоненциальной сложности (итерация) = %d\n", count); count = expRecur(n); printf("Количество операций экспоненциальной сложности (рекурсия) = %d\n", count); count = logarithmic(n); printf("Количество операций логарифмической сложности (итерация) = %d\n", count); count = logRecur(n); printf("Количество операций логарифмической сложности (рекурсия) = %d\n", count); count = linearLogRecur(n); printf("Количество операций линейно-логарифмической сложности (рекурсия) = %d\n", count); count = factorialRecur(n); printf("Количество операций факториальной сложности (рекурсия) = %d\n", count); // Освободить память в куче if (nums != NULL) { free(nums); nums = NULL; } getchar(); return 0; } ================================================ FILE: ru/codes/c/chapter_computational_complexity/worst_best_time_complexity.c ================================================ /** * File: worst_best_time_complexity.c * Created Time: 2023-01-03 * Author: codingonion (coderonion@gmail.com) */ #include "../utils/common.h" /* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ int *randomNumbers(int n) { // Выделить память в куче (создать одномерный массив переменной длины: число элементов равно n, тип элементов — int) int *nums = (int *)malloc(n * sizeof(int)); // Создать массив nums = { 1, 2, 3, ..., n } for (int i = 0; i < n; i++) { nums[i] = i + 1; } // Случайно перемешать элементы массива for (int i = n - 1; i > 0; i--) { int j = rand() % (i + 1); int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } return nums; } /* Найти индекс числа 1 в массиве nums */ int findOne(int *nums, int n) { for (int i = 0; i < n; i++) { // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) if (nums[i] == 1) return i; } return -1; } /* Driver Code */ int main(int argc, char *argv[]) { // Инициализировать seed генератора случайных чисел srand((unsigned int)time(NULL)); for (int i = 0; i < 10; i++) { int n = 100; int *nums = randomNumbers(n); int index = findOne(nums, n); printf("\nПосле перемешивания массива [ 1, 2, ..., n ] nums = "); printArray(nums, n); printf("Индекс числа 1 = %d\n", index); // Освободить память в куче if (nums != NULL) { free(nums); nums = NULL; } } return 0; } ================================================ FILE: ru/codes/c/chapter_divide_and_conquer/CMakeLists.txt ================================================ add_executable(binary_search_recur binary_search_recur.c) add_executable(build_tree build_tree.c) add_executable(hanota hanota.c) ================================================ FILE: ru/codes/c/chapter_divide_and_conquer/binary_search_recur.c ================================================ /** * File: binary_search_recur.c * Created Time: 2023-10-01 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Бинарный поиск: задача f(i, j) */ int dfs(int nums[], int target, int i, int j) { // Если интервал пуст, целевой элемент отсутствует, вернуть -1 if (i > j) { return -1; } // Вычислить индекс середины m int m = (i + j) / 2; if (nums[m] < target) { // Рекурсивная подзадача f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // Рекурсивная подзадача f(i, m-1) return dfs(nums, target, i, m - 1); } else { // Целевой элемент найден, вернуть его индекс return m; } } /* Бинарный поиск */ int binarySearch(int nums[], int target, int numsSize) { int n = numsSize; // Решить задачу f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ int main() { int target = 6; int nums[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; int numsSize = sizeof(nums) / sizeof(nums[0]); // Бинарный поиск (двусторонне замкнутый интервал) int index = binarySearch(nums, target, numsSize); printf("Индекс целевого элемента 6 = %d\n", index); return 0; } ================================================ FILE: ru/codes/c/chapter_divide_and_conquer/build_tree.c ================================================ /** * File : build_tree.c * Created Time: 2023-10-16 * Author : lucas (superrat6@gmail.com) */ #include "../utils/common.h" // Предположить, что все элементы меньше 1000 #define MAX_SIZE 1000 /* Построить двоичное дерево: разделяй и властвуй */ TreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) { // Завершить при пустом диапазоне поддерева if (r - l < 0) return NULL; // Инициализировать корневой узел TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); root->val = preorder[i]; root->left = NULL; root->right = NULL; // Найти m, чтобы разделить левое и правое поддеревья int m = inorderMap[preorder[i]]; // Подзадача: построить левое поддерево root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size); // Подзадача: построить правое поддерево root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size); // Вернуть корневой узел return root; } /* Построить двоичное дерево */ TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) { // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE); for (int i = 0; i < inorderSize; i++) { inorderMap[inorder[i]] = i; } TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize); free(inorderMap); return root; } /* Driver Code */ int main() { int preorder[] = {3, 9, 2, 1, 7}; int inorder[] = {9, 3, 1, 2, 7}; int preorderSize = sizeof(preorder) / sizeof(preorder[0]); int inorderSize = sizeof(inorder) / sizeof(inorder[0]); printf("Предварительный обход = "); printArray(preorder, preorderSize); printf("Симметричный обход = "); printArray(inorder, inorderSize); TreeNode *root = buildTree(preorder, preorderSize, inorder, inorderSize); printf("Построенное двоичное дерево:\n"); printTree(root); freeMemoryTree(root); return 0; } ================================================ FILE: ru/codes/c/chapter_divide_and_conquer/hanota.c ================================================ /** * File: hanota.c * Created Time: 2023-10-01 * Author: Zuoxun (845242523@qq.com), lucas(superrat6@gmail.com) */ #include "../utils/common.h" // Предположим, что существует не более 1000 перестановок #define MAX_SIZE 1000 /* Переместить один диск */ void move(int *src, int *srcSize, int *tar, int *tarSize) { // Снять диск с вершины src int pan = src[*srcSize - 1]; src[*srcSize - 1] = 0; (*srcSize)--; // Положить диск на вершину tar tar[*tarSize] = pan; (*tarSize)++; } /* Решить задачу Ханойской башни f(i) */ void dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) { // Если в src остался только один диск, сразу переместить его в tar if (i == 1) { move(src, srcSize, tar, tarSize); return; } // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize); // Подзадача f(1): переместить оставшийся один диск из src в tar move(src, srcSize, tar, tarSize); // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize); } /* Решить задачу Ханойской башни */ void solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) { // Переместить верхние n дисков из A в C с помощью B dfs(*ASize, A, ASize, B, BSize, C, CSize); } /* Driver Code */ int main() { // Хвост списка соответствует вершине столбца int a[] = {5, 4, 3, 2, 1}; int b[MAX_SIZE] = {0}; int c[MAX_SIZE] = {0}; int ASize = sizeof(a) / sizeof(a[0]); int BSize = 0; int CSize = 0; printf("\nНачальное состояние:"); printf("\nA = "); printArray(a, ASize); printf("B = "); printArray(b, BSize); printf("C = "); printArray(c, CSize); solveHanota(a, &ASize, b, &BSize, c, &CSize); printf("\nПосле завершения перемещения дисков:"); printf("A = "); printArray(a, ASize); printf("B = "); printArray(b, BSize); printf("C = "); printArray(c, CSize); return 0; } ================================================ FILE: ru/codes/c/chapter_dynamic_programming/CMakeLists.txt ================================================ add_executable(climbing_stairs_constraint_dp climbing_stairs_constraint_dp.c) add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.c) add_executable(min_path_sum min_path_sum.c) add_executable(knapsack knapsack.c) add_executable(unbounded_knapsack unbounded_knapsack.c) add_executable(coin_change coin_change.c) add_executable(coin_change_ii coin_change_ii.c) add_executable(edit_distance edit_distance.c) ================================================ FILE: ru/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c ================================================ /** * File: climbing_stairs_backtrack.c * Created Time: 2023-09-22 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* Бэктрекинг */ void backtrack(int *choices, int state, int n, int *res, int len) { // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 if (state == n) res[0]++; // Перебор всех вариантов выбора for (int i = 0; i < len; i++) { int choice = choices[i]; // Отсечение: нельзя выходить за n-ю ступень if (state + choice > n) continue; // Попытка: сделать выбор и обновить состояние backtrack(choices, state + choice, n, res, len); // Откат } } /* Подъем по лестнице: бэктрекинг */ int climbingStairsBacktrack(int n) { int choices[2] = {1, 2}; // Можно подняться на 1 или 2 ступени int state = 0; // Начать подъем с 0-й ступени int *res = (int *)malloc(sizeof(int)); *res = 0; // Использовать res[0] для хранения числа решений int len = sizeof(choices) / sizeof(int); backtrack(choices, state, n, res, len); int result = *res; free(res); return result; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsBacktrack(n); printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res); return 0; } ================================================ FILE: ru/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c ================================================ /** * File: climbing_stairs_constraint_dp.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Подъем по лестнице с ограничениями: динамическое программирование */ int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // Инициализация таблицы dp для хранения решений подзадач int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(3, sizeof(int)); } // Начальное состояние: заранее задать решения наименьших подзадач dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // Переход состояний: постепенное решение больших подзадач через меньшие for (int i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } int res = dp[n][1] + dp[n][2]; // Освободить память for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsConstraintDP(n); printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res); return 0; } ================================================ FILE: ru/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c ================================================ /** * File: climbing_stairs_dfs.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* Поиск */ int dfs(int i) { // dp[1] и dp[2] уже известны, вернуть их if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1) + dfs(i - 2); return count; } /* Подъем по лестнице: поиск */ int climbingStairsDFS(int n) { return dfs(n); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFS(n); printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res); return 0; } ================================================ FILE: ru/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c ================================================ /** * File: climbing_stairs_dfs_mem.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* Поиск с мемоизацией */ int dfs(int i, int *mem) { // dp[1] и dp[2] уже известны, вернуть их if (i == 1 || i == 2) return i; // Если запись dp[i] существует, сразу вернуть ее if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1, mem) + dfs(i - 2, mem); // Сохранить dp[i] mem[i] = count; return count; } /* Подъем по лестнице: поиск с мемоизацией */ int climbingStairsDFSMem(int n) { // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи int *mem = (int *)malloc((n + 1) * sizeof(int)); for (int i = 0; i <= n; i++) { mem[i] = -1; } int result = dfs(n, mem); free(mem); return result; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFSMem(n); printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res); return 0; } ================================================ FILE: ru/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c ================================================ /** * File: climbing_stairs_dp.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* Подъем по лестнице: динамическое программирование */ int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; // Инициализация таблицы dp для хранения решений подзадач int *dp = (int *)malloc((n + 1) * sizeof(int)); // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = 1; dp[2] = 2; // Переход состояний: постепенное решение больших подзадач через меньшие for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } int result = dp[n]; free(dp); return result; } /* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ int climbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDP(n); printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res); res = climbingStairsDPComp(n); printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res); return 0; } ================================================ FILE: ru/codes/c/chapter_dynamic_programming/coin_change.c ================================================ /** * File: coin_change.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Найти минимум */ int myMin(int a, int b) { return a < b ? a : b; } /* Размен монет: динамическое программирование */ int coinChangeDP(int coins[], int amt, int coinsSize) { int n = coinsSize; int MAX = amt + 1; // Инициализация таблицы dp int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(amt + 1, sizeof(int)); } // Переход состояний: первая строка и первый столбец for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // Переход состояний: остальные строки и столбцы for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } int res = dp[n][amt] != MAX ? dp[n][amt] : -1; // Освободить память for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* Размен монет: динамическое программирование с оптимизацией памяти */ int coinChangeDPComp(int coins[], int amt, int coinsSize) { int n = coinsSize; int MAX = amt + 1; // Инициализация таблицы dp int *dp = malloc((amt + 1) * sizeof(int)); for (int j = 1; j <= amt; j++) { dp[j] = MAX; } dp[0] = 0; // Переход состояний for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1); } } } int res = dp[amt] != MAX ? dp[amt] : -1; // Освободить память free(dp); return res; } /* Driver code */ int main() { int coins[] = {1, 2, 5}; int coinsSize = sizeof(coins) / sizeof(coins[0]); int amt = 4; // Динамическое программирование int res = coinChangeDP(coins, amt, coinsSize); printf("Минимальное количество монет для целевой суммы = %d\n", res); // Динамическое программирование с оптимизацией памяти res = coinChangeDPComp(coins, amt, coinsSize); printf("Минимальное количество монет для целевой суммы = %d\n", res); return 0; } ================================================ FILE: ru/codes/c/chapter_dynamic_programming/coin_change_ii.c ================================================ /** * File: coin_change_ii.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Размен монет II: динамическое программирование */ int coinChangeIIDP(int coins[], int amt, int coinsSize) { int n = coinsSize; // Инициализация таблицы dp int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(amt + 1, sizeof(int)); } // Инициализация первого столбца for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // Переход состояний for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a]; } else { // Сумма двух решений: не брать или взять монету i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } int res = dp[n][amt]; // Освободить память for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* Размен монет II: динамическое программирование с оптимизацией памяти */ int coinChangeIIDPComp(int coins[], int amt, int coinsSize) { int n = coinsSize; // Инициализация таблицы dp int *dp = calloc(amt + 1, sizeof(int)); dp[0] = 1; // Переход состояний for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Сумма двух решений: не брать или взять монету i dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } int res = dp[amt]; // Освободить память free(dp); return res; } /* Driver code */ int main() { int coins[] = {1, 2, 5}; int coinsSize = sizeof(coins) / sizeof(coins[0]); int amt = 5; // Динамическое программирование int res = coinChangeIIDP(coins, amt, coinsSize); printf("Количество комбинаций монет для набора целевой суммы = %d\n", res); // Динамическое программирование с оптимизацией памяти res = coinChangeIIDPComp(coins, amt, coinsSize); printf("Количество комбинаций монет для набора целевой суммы = %d\n", res); return 0; } ================================================ FILE: ru/codes/c/chapter_dynamic_programming/edit_distance.c ================================================ /** * File: edit_distance.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Найти минимум */ int myMin(int a, int b) { return a < b ? a : b; } /* Редакционное расстояние: полный перебор */ int editDistanceDFS(char *s, char *t, int i, int j) { // Если s и t пусты, вернуть 0 if (i == 0 && j == 0) return 0; // Если s пусто, вернуть длину t if (i == 0) return j; // Если t пусто, вернуть длину s if (j == 0) return i; // Если два символа равны, сразу пропустить их if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 int insert = editDistanceDFS(s, t, i, j - 1); int del = editDistanceDFS(s, t, i - 1, j); int replace = editDistanceDFS(s, t, i - 1, j - 1); // Вернуть минимальное число шагов редактирования return myMin(myMin(insert, del), replace) + 1; } /* Редакционное расстояние: поиск с мемоизацией */ int editDistanceDFSMem(char *s, char *t, int memCols, int **mem, int i, int j) { // Если s и t пусты, вернуть 0 if (i == 0 && j == 0) return 0; // Если s пусто, вернуть длину t if (i == 0) return j; // Если t пусто, вернуть длину s if (j == 0) return i; // Если запись уже есть, сразу вернуть ее if (mem[i][j] != -1) return mem[i][j]; // Если два символа равны, сразу пропустить их if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 int insert = editDistanceDFSMem(s, t, memCols, mem, i, j - 1); int del = editDistanceDFSMem(s, t, memCols, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); // Сохранить и вернуть минимальное число шагов редактирования mem[i][j] = myMin(myMin(insert, del), replace) + 1; return mem[i][j]; } /* Редакционное расстояние: динамическое программирование */ int editDistanceDP(char *s, char *t, int n, int m) { int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(m + 1, sizeof(int)); } // Переход состояний: первая строка и первый столбец for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // Переход состояний: остальные строки и столбцы for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // Если два символа равны, сразу пропустить их dp[i][j] = dp[i - 1][j - 1]; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } int res = dp[n][m]; // Освободить память for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ int editDistanceDPComp(char *s, char *t, int n, int m) { int *dp = calloc(m + 1, sizeof(int)); // Переход состояний: первая строка for (int j = 1; j <= m; j++) { dp[j] = j; } // Переход состояний: остальные строки for (int i = 1; i <= n; i++) { // Переход состояний: первый столбец int leftup = dp[0]; // Временно сохранить dp[i-1, j-1] dp[0] = i; // Переход состояний: остальные столбцы for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // Если два символа равны, сразу пропустить их dp[j] = leftup; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации } } int res = dp[m]; // Освободить память free(dp); return res; } /* Driver Code */ int main() { char *s = "bag"; char *t = "pack"; int n = strlen(s), m = strlen(t); // Полный перебор int res = editDistanceDFS(s, t, n, m); printf("Чтобы заменить %s на %s, требуется минимум %d операций редактирования\n", s, t, res); // Поиск с мемоизацией int **mem = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { mem[i] = malloc((m + 1) * sizeof(int)); memset(mem[i], -1, (m + 1) * sizeof(int)); } res = editDistanceDFSMem(s, t, m + 1, mem, n, m); printf("Чтобы заменить %s на %s, требуется минимум %d операций редактирования\n", s, t, res); // Освободить память for (int i = 0; i <= n; i++) { free(mem[i]); } free(mem); // Динамическое программирование res = editDistanceDP(s, t, n, m); printf("Чтобы заменить %s на %s, требуется минимум %d операций редактирования\n", s, t, res); // Динамическое программирование с оптимизацией памяти res = editDistanceDPComp(s, t, n, m); printf("Чтобы заменить %s на %s, требуется минимум %d операций редактирования\n", s, t, res); return 0; } ================================================ FILE: ru/codes/c/chapter_dynamic_programming/knapsack.c ================================================ /** * File: knapsack.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Найти максимум */ int myMax(int a, int b) { return a > b ? a : b; } /* Рюкзак 0-1: полный перебор */ int knapsackDFS(int wgt[], int val[], int i, int c) { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i == 0 || c == 0) { return 0; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // Вернуть вариант с большей стоимостью из двух возможных return myMax(no, yes); } /* Рюкзак 0-1: поиск с мемоизацией */ int knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i == 0 || c == 0) { return 0; } // Если запись уже есть, вернуть сразу if (mem[i][c] != -1) { return mem[i][c]; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // Сохранить и вернуть вариант с большей стоимостью из двух решений mem[i][c] = myMax(no, yes); return mem[i][c]; } /* Рюкзак 0-1: динамическое программирование */ int knapsackDP(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // Инициализация таблицы dp int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(cap + 1, sizeof(int)); } // Переход состояний for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[n][cap]; // Освободить память for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ int knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // Инициализация таблицы dp int *dp = calloc(cap + 1, sizeof(int)); // Переход состояний for (int i = 1; i <= n; i++) { // Обход в обратном порядке for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // Большее из двух решений: не брать или взять предмет i dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[cap]; // Освободить память free(dp); return res; } /* Driver Code */ int main() { int wgt[] = {10, 20, 30, 40, 50}; int val[] = {50, 120, 150, 210, 240}; int cap = 50; int n = sizeof(wgt) / sizeof(wgt[0]); int wgtSize = n; // Полный перебор int res = knapsackDFS(wgt, val, n, cap); printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %d\n", res); // Поиск с мемоизацией int **mem = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { mem[i] = malloc((cap + 1) * sizeof(int)); memset(mem[i], -1, (cap + 1) * sizeof(int)); } res = knapsackDFSMem(wgt, val, cap + 1, mem, n, cap); printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %d\n", res); // Освободить память for (int i = 0; i <= n; i++) { free(mem[i]); } free(mem); // Динамическое программирование res = knapsackDP(wgt, val, cap, wgtSize); printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %d\n", res); // Динамическое программирование с оптимизацией памяти res = knapsackDPComp(wgt, val, cap, wgtSize); printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %d\n", res); return 0; } ================================================ FILE: ru/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c ================================================ /** * File: min_cost_climbing_stairs_dp.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Найти минимум */ int myMin(int a, int b) { return a < b ? a : b; } /* Минимальная стоимость подъема по лестнице: динамическое программирование */ int minCostClimbingStairsDP(int cost[], int costSize) { int n = costSize - 1; if (n == 1 || n == 2) return cost[n]; // Инициализация таблицы dp для хранения решений подзадач int *dp = calloc(n + 1, sizeof(int)); // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = cost[1]; dp[2] = cost[2]; // Переход состояний: постепенное решение больших подзадач через меньшие for (int i = 3; i <= n; i++) { dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i]; } int res = dp[n]; // Освободить память free(dp); return res; } /* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ int minCostClimbingStairsDPComp(int cost[], int costSize) { int n = costSize - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = myMin(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ int main() { int cost[] = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; int costSize = sizeof(cost) / sizeof(cost[0]); printf("Список стоимостей ступеней = "); printArray(cost, costSize); int res = minCostClimbingStairsDP(cost, costSize); printf("Минимальная стоимость подъема по лестнице = %d\n", res); res = minCostClimbingStairsDPComp(cost, costSize); printf("Минимальная стоимость подъема по лестнице = %d\n", res); return 0; } ================================================ FILE: ru/codes/c/chapter_dynamic_programming/min_path_sum.c ================================================ /** * File: min_path_sum.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" // Предположить, что максимальное число строк и столбцов матрицы равно 100 #define MAX_SIZE 100 /* Найти минимум */ int myMin(int a, int b) { return a < b ? a : b; } /* Минимальная сумма пути: полный перебор */ int minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) { // Если это верхняя левая ячейка, завершить поиск if (i == 0 && j == 0) { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 || j < 0) { return INT_MAX; } // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; } /* Минимальная сумма пути: поиск с мемоизацией */ int minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) { // Если это верхняя левая ячейка, завершить поиск if (i == 0 && j == 0) { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 || j < 0) { return INT_MAX; } // Если запись уже есть, вернуть сразу if (mem[i][j] != -1) { return mem[i][j]; } // Минимальная стоимость пути для левой и верхней ячеек int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; return mem[i][j]; } /* Минимальная сумма пути: динамическое программирование */ int minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { // Инициализация таблицы dp int **dp = malloc(n * sizeof(int *)); for (int i = 0; i < n; i++) { dp[i] = calloc(m, sizeof(int)); } dp[0][0] = grid[0][0]; // Переход состояний: первая строка for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // Переход состояний: первый столбец for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // Переход состояний: остальные строки и столбцы for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } int res = dp[n - 1][m - 1]; // Освободить память for (int i = 0; i < n; i++) { free(dp[i]); } return res; } /* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ int minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { // Инициализация таблицы dp int *dp = calloc(m, sizeof(int)); // Переход состояний: первая строка dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // Переход состояний: остальные строки for (int i = 1; i < n; i++) { // Переход состояний: первый столбец dp[0] = dp[0] + grid[i][0]; // Переход состояний: остальные столбцы for (int j = 1; j < m; j++) { dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j]; } } int res = dp[m - 1]; // Освободить память free(dp); return res; } /* Driver Code */ int main() { int grid[MAX_SIZE][MAX_SIZE] = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; int n = 4, m = 4; // Емкость матрицы равна MAX_SIZE * MAX_SIZE, число эффективных строк и столбцов — n * m // Полный перебор int res = minPathSumDFS(grid, n - 1, m - 1); printf("Минимальная сумма пути из левого верхнего в правый нижний угол = %d\n", res); // Поиск с мемоизацией int mem[MAX_SIZE][MAX_SIZE]; memset(mem, -1, sizeof(mem)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); printf("Минимальная сумма пути из левого верхнего в правый нижний угол = %d\n", res); // Динамическое программирование res = minPathSumDP(grid, n, m); printf("Минимальная сумма пути из левого верхнего в правый нижний угол = %d\n", res); // Динамическое программирование с оптимизацией памяти res = minPathSumDPComp(grid, n, m); printf("Минимальная сумма пути из левого верхнего в правый нижний угол = %d\n", res); return 0; } ================================================ FILE: ru/codes/c/chapter_dynamic_programming/unbounded_knapsack.c ================================================ /** * File: unbounded_knapsack.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* Найти максимум */ int myMax(int a, int b) { return a > b ? a : b; } /* Полный рюкзак: динамическое программирование */ int unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // Инициализация таблицы dp int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(cap + 1, sizeof(int)); } // Переход состояний for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[n][cap]; // Освободить память for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* Полный рюкзак: динамическое программирование с оптимизацией памяти */ int unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // Инициализация таблицы dp int *dp = calloc(cap + 1, sizeof(int)); // Переход состояний for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[c] = dp[c]; } else { // Большее из двух решений: не брать или взять предмет i dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[cap]; // Освободить память free(dp); return res; } /* Driver code */ int main() { int wgt[] = {1, 2, 3}; int val[] = {5, 11, 15}; int wgtSize = sizeof(wgt) / sizeof(wgt[0]); int cap = 4; // Динамическое программирование int res = unboundedKnapsackDP(wgt, val, cap, wgtSize); printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %d\n", res); // Динамическое программирование с оптимизацией памяти res = unboundedKnapsackDPComp(wgt, val, cap, wgtSize); printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %d\n", res); return 0; } ================================================ FILE: ru/codes/c/chapter_graph/CMakeLists.txt ================================================ add_executable(graph_adjacency_matrix graph_adjacency_matrix.c) add_executable(graph_adjacency_list_test graph_adjacency_list_test.c) add_executable(graph_bfs graph_bfs.c) add_executable(graph_dfs graph_dfs.c) ================================================ FILE: ru/codes/c/chapter_graph/graph_adjacency_list.c ================================================ /** * File: graph_adjacency_list.c * Created Time: 2023-07-07 * Author: NI-SW (947743645@qq.com) */ #include "../utils/common.h" // Предположим, что максимальное число узлов равно 100 #define MAX_SIZE 100 /* Структура узла */ typedef struct AdjListNode { Vertex *vertex; // Вершина struct AdjListNode *next; // Узел-преемник } AdjListNode; /* Класс неориентированного графа на основе списка смежности */ typedef struct { AdjListNode *heads[MAX_SIZE]; // Массив узлов int size; // Количество узлов } GraphAdjList; /* Конструктор */ GraphAdjList *newGraphAdjList() { GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList)); if (!graph) { return NULL; } graph->size = 0; for (int i = 0; i < MAX_SIZE; i++) { graph->heads[i] = NULL; } return graph; } /* Деструктор */ void delGraphAdjList(GraphAdjList *graph) { for (int i = 0; i < graph->size; i++) { AdjListNode *cur = graph->heads[i]; while (cur != NULL) { AdjListNode *next = cur->next; if (cur != graph->heads[i]) { free(cur); } cur = next; } free(graph->heads[i]->vertex); free(graph->heads[i]); } free(graph); } /* Найти узел, соответствующий вершине */ AdjListNode *findNode(GraphAdjList *graph, Vertex *vet) { for (int i = 0; i < graph->size; i++) { if (graph->heads[i]->vertex == vet) { return graph->heads[i]; } } return NULL; } /* Вспомогательная функция добавления ребра */ void addEdgeHelper(AdjListNode *head, Vertex *vet) { AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode)); node->vertex = vet; // Вставка в голову node->next = head->next; head->next = node; } /* Добавление ребра */ void addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { AdjListNode *head1 = findNode(graph, vet1); AdjListNode *head2 = findNode(graph, vet2); assert(head1 != NULL && head2 != NULL && head1 != head2); // Добавить ребро vet1 - vet2 addEdgeHelper(head1, vet2); addEdgeHelper(head2, vet1); } /* Вспомогательная функция удаления ребра */ void removeEdgeHelper(AdjListNode *head, Vertex *vet) { AdjListNode *pre = head; AdjListNode *cur = head->next; // Искать в связном списке узел, соответствующий vet while (cur != NULL && cur->vertex != vet) { pre = cur; cur = cur->next; } if (cur == NULL) return; // Удалить из связного списка узел, соответствующий vet pre->next = cur->next; // Освободить память free(cur); } /* Удаление ребра */ void removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { AdjListNode *head1 = findNode(graph, vet1); AdjListNode *head2 = findNode(graph, vet2); assert(head1 != NULL && head2 != NULL); // Удалить ребро vet1 - vet2 removeEdgeHelper(head1, head2->vertex); removeEdgeHelper(head2, head1->vertex); } /* Добавление вершины */ void addVertex(GraphAdjList *graph, Vertex *vet) { assert(graph != NULL && graph->size < MAX_SIZE); AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode)); head->vertex = vet; head->next = NULL; // Добавить новый список в список смежности graph->heads[graph->size++] = head; } /* Удаление вершины */ void removeVertex(GraphAdjList *graph, Vertex *vet) { AdjListNode *node = findNode(graph, vet); assert(node != NULL); // Удалить из списка смежности список, соответствующий вершине vet AdjListNode *cur = node, *pre = NULL; while (cur) { pre = cur; cur = cur->next; free(pre); } // Обойти списки других вершин и удалить все ребра, содержащие vet for (int i = 0; i < graph->size; i++) { cur = graph->heads[i]; pre = NULL; while (cur) { pre = cur; cur = cur->next; if (cur && cur->vertex == vet) { pre->next = cur->next; free(cur); break; } } } // Сдвинуть вершины после данной вперед, чтобы заполнить образовавшийся пробел int i; for (i = 0; i < graph->size; i++) { if (graph->heads[i] == node) break; } for (int j = i; j < graph->size - 1; j++) { graph->heads[j] = graph->heads[j + 1]; } graph->size--; free(vet); } /* Вывести список смежности */ void printGraph(const GraphAdjList *graph) { printf("Список смежности =\n"); for (int i = 0; i < graph->size; ++i) { AdjListNode *node = graph->heads[i]; printf("%d: [", node->vertex->val); node = node->next; while (node) { printf("%d, ", node->vertex->val); node = node->next; } printf("]\n"); } } ================================================ FILE: ru/codes/c/chapter_graph/graph_adjacency_list_test.c ================================================ /** * File: graph_adjacency_list_test.c * Created Time: 2023-07-11 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" /* Driver Code */ int main() { int vals[] = {1, 3, 2, 5, 4}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // Добавить все вершины и ребра for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\nПосле инициализации граф имеет вид\n"); printGraph(graph); /* Добавление ребра */ // Вершины 1 и 2 соответствуют v[0] и v[2] addEdge(graph, v[0], v[2]); printf("\nПосле добавления ребра 1-2 граф имеет вид\n"); printGraph(graph); /* Удаление ребра */ // Вершины 1 и 3 соответствуют v[0] и v[1] removeEdge(graph, v[0], v[1]); printf("\nПосле удаления ребра 1-3 граф имеет вид\n"); printGraph(graph); /* Добавление вершины */ Vertex *v5 = newVertex(6); addVertex(graph, v5); printf("\nПосле добавления вершины 6 граф имеет вид\n"); printGraph(graph); /* Удаление вершины */ // Вершина 3 соответствует v[1] removeVertex(graph, v[1]); printf("\nПосле удаления вершины 3 граф имеет вид:\n"); printGraph(graph); // Освободить память delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: ru/codes/c/chapter_graph/graph_adjacency_matrix.c ================================================ /** * File: graph_adjacency_matrix.c * Created Time: 2023-07-06 * Author: NI-SW (947743645@qq.com) */ #include "../utils/common.h" // Предположить, что максимальное число вершин равно 100 #define MAX_SIZE 100 /* Структура неориентированного графа на основе матрицы смежности */ typedef struct { int vertices[MAX_SIZE]; int adjMat[MAX_SIZE][MAX_SIZE]; int size; } GraphAdjMat; /* Конструктор */ GraphAdjMat *newGraphAdjMat() { GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat)); graph->size = 0; for (int i = 0; i < MAX_SIZE; i++) { for (int j = 0; j < MAX_SIZE; j++) { graph->adjMat[i][j] = 0; } } return graph; } /* Деструктор */ void delGraphAdjMat(GraphAdjMat *graph) { free(graph); } /* Добавление вершины */ void addVertex(GraphAdjMat *graph, int val) { if (graph->size == MAX_SIZE) { fprintf(stderr, "Количество вершин графа уже достигло максимума\n"); return; } // Добавить n-ю вершину и обнулить n-ю строку и столбец int n = graph->size; graph->vertices[n] = val; for (int i = 0; i <= n; i++) { graph->adjMat[n][i] = graph->adjMat[i][n] = 0; } graph->size++; } /* Удаление вершины */ void removeVertex(GraphAdjMat *graph, int index) { if (index < 0 || index >= graph->size) { fprintf(stderr, "индекс вершины выходит за границы\n"); return; } // Удалить вершину с индексом index из списка вершин for (int i = index; i < graph->size - 1; i++) { graph->vertices[i] = graph->vertices[i + 1]; } // Удалить строку с индексом index из матрицы смежности for (int i = index; i < graph->size - 1; i++) { for (int j = 0; j < graph->size; j++) { graph->adjMat[i][j] = graph->adjMat[i + 1][j]; } } // Удалить столбец с индексом index из матрицы смежности for (int i = 0; i < graph->size; i++) { for (int j = index; j < graph->size - 1; j++) { graph->adjMat[i][j] = graph->adjMat[i][j + 1]; } } graph->size--; } /* Добавление ребра */ // Параметры i и j соответствуют индексам элементов vertices void addEdge(GraphAdjMat *graph, int i, int j) { if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { fprintf(stderr, "индексы ребра выходят за границы или совпадают\n"); return; } graph->adjMat[i][j] = 1; graph->adjMat[j][i] = 1; } /* Удаление ребра */ // Параметры i и j соответствуют индексам элементов vertices void removeEdge(GraphAdjMat *graph, int i, int j) { if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { fprintf(stderr, "индексы ребра выходят за границы или совпадают\n"); return; } graph->adjMat[i][j] = 0; graph->adjMat[j][i] = 0; } /* Вывести матрицу смежности */ void printGraphAdjMat(GraphAdjMat *graph) { printf("Список вершин = "); printArray(graph->vertices, graph->size); printf("Матрица смежности =\n"); for (int i = 0; i < graph->size; i++) { printArray(graph->adjMat[i], graph->size); } } /* Driver Code */ int main() { // Инициализация неориентированного графа GraphAdjMat *graph = newGraphAdjMat(); int vertices[] = {1, 3, 2, 5, 4}; for (int i = 0; i < 5; i++) { addVertex(graph, vertices[i]); } int edges[][2] = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; for (int i = 0; i < 6; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\nПосле инициализации граф имеет вид\n"); printGraphAdjMat(graph); /* Добавление ребра */ // Индексы вершин 1 и 2 равны 0 и 2 соответственно addEdge(graph, 0, 2); printf("\nПосле добавления ребра 1-2 граф имеет вид\n"); printGraphAdjMat(graph); /* Удаление ребра */ // Индексы вершин 1 и 3 равны 0 и 1 соответственно removeEdge(graph, 0, 1); printf("\nПосле удаления ребра 1-3 граф имеет вид\n"); printGraphAdjMat(graph); /* Добавление вершины */ addVertex(graph, 6); printf("\nПосле добавления вершины 6 граф имеет вид\n"); printGraphAdjMat(graph); /* Удаление вершины */ // Индекс вершины 3 равен 1 removeVertex(graph, 1); printf("\nПосле удаления вершины 3 граф имеет вид\n"); printGraphAdjMat(graph); // Освободить память delGraphAdjMat(graph); return 0; } ================================================ FILE: ru/codes/c/chapter_graph/graph_bfs.c ================================================ /** * File: graph_bfs.c * Created Time: 2023-07-11 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" // Предположим, что максимальное число узлов равно 100 #define MAX_SIZE 100 /* Структура очереди узлов */ typedef struct { Vertex *vertices[MAX_SIZE]; int front, rear, size; } Queue; /* Конструктор */ Queue *newQueue() { Queue *q = (Queue *)malloc(sizeof(Queue)); q->front = q->rear = q->size = 0; return q; } /* Проверка, пуста ли очередь */ int isEmpty(Queue *q) { return q->size == 0; } /* Операция добавления в очередь */ void enqueue(Queue *q, Vertex *vet) { q->vertices[q->rear] = vet; q->rear = (q->rear + 1) % MAX_SIZE; q->size++; } /* Операция извлечения из очереди */ Vertex *dequeue(Queue *q) { Vertex *vet = q->vertices[q->front]; q->front = (q->front + 1) % MAX_SIZE; q->size--; return vet; } /* Проверить, была ли вершина уже посещена */ int isVisited(Vertex **visited, int size, Vertex *vet) { // Искать узел обходом за O(n) времени for (int i = 0; i < size; i++) { if (visited[i] == vet) return 1; } return 0; } /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины void graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) { // Очередь используется для реализации BFS Queue *queue = newQueue(); enqueue(queue, startVet); visited[(*visitedSize)++] = startVet; // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (!isEmpty(queue)) { Vertex *vet = dequeue(queue); // Извлечь головную вершину из очереди res[(*resSize)++] = vet; // Отметить посещенную вершину // Обойти все смежные вершины данной вершины AdjListNode *node = findNode(graph, vet); while (node != NULL) { // Пропустить уже посещенную вершину if (!isVisited(visited, *visitedSize, node->vertex)) { enqueue(queue, node->vertex); // Помещать в очередь только непосещенные вершины visited[(*visitedSize)++] = node->vertex; // Отметить эту вершину как посещенную } node = node->next; } } // Освободить память free(queue); } /* Driver Code */ int main() { // Инициализация неориентированного графа int vals[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[1], v[4]}, {v[2], v[5]}, {v[3], v[4]}, {v[3], v[6]}, {v[4], v[5]}, {v[4], v[7]}, {v[5], v[8]}, {v[6], v[7]}, {v[7], v[8]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // Добавить все вершины и ребра for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\nПосле инициализации граф имеет вид\n"); printGraph(graph); // Обход в ширину // Последовательность обхода вершин Vertex *res[MAX_SIZE]; int resSize = 0; // Используется для записи уже посещенных вершин Vertex *visited[MAX_SIZE]; int visitedSize = 0; graphBFS(graph, v[0], res, &resSize, visited, &visitedSize); printf("\nПоследовательность вершин при обходе в ширину (BFS)\n"); printArray(vetsToVals(res, resSize), resSize); // Освободить память delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: ru/codes/c/chapter_graph/graph_dfs.c ================================================ /** * File: graph_dfs.c * Created Time: 2023-07-13 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" // Предположим, что максимальное число узлов равно 100 #define MAX_SIZE 100 /* Проверить, была ли вершина уже посещена */ int isVisited(Vertex **res, int size, Vertex *vet) { // Искать узел обходом за O(n) времени for (int i = 0; i < size; i++) { if (res[i] == vet) { return 1; } } return 0; } /* Вспомогательная функция обхода в глубину */ void dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) { // Отметить посещенную вершину res[(*resSize)++] = vet; // Обойти все смежные вершины данной вершины AdjListNode *node = findNode(graph, vet); while (node != NULL) { // Пропустить уже посещенную вершину if (!isVisited(res, *resSize, node->vertex)) { // Рекурсивно обходить смежные вершины dfs(graph, res, resSize, node->vertex); } node = node->next; } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины void graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) { dfs(graph, res, resSize, startVet); } /* Driver Code */ int main() { // Инициализация неориентированного графа int vals[] = {0, 1, 2, 3, 4, 5, 6}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // Добавить все вершины и ребра for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\nПосле инициализации граф имеет вид\n"); printGraph(graph); // Обход в глубину Vertex *res[MAX_SIZE]; int resSize = 0; graphDFS(graph, v[0], res, &resSize); printf("\nПоследовательность вершин при обходе в глубину (DFS)\n"); printArray(vetsToVals(res, resSize), resSize); // Освободить память delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: ru/codes/c/chapter_greedy/CMakeLists.txt ================================================ add_executable(coin_change_greedy coin_change_greedy.c) add_executable(fractional_knapsack fractional_knapsack.c) add_executable(max_capacity max_capacity.c) add_executable(max_product_cutting max_product_cutting.c) if (NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC") target_link_libraries(max_product_cutting m) endif() ================================================ FILE: ru/codes/c/chapter_greedy/coin_change_greedy.c ================================================ /** * File: coin_change_greedy.c * Created Time: 2023-09-07 * Author: lwbaptx (lwbaptx@gmail.com) */ #include "../utils/common.h" /* Размен монет: жадный алгоритм */ int coinChangeGreedy(int *coins, int size, int amt) { // Предположить, что список coins упорядочен int i = size - 1; int count = 0; // Циклически выполнять жадный выбор, пока не останется суммы while (amt > 0) { // Найти монету, которая меньше остатка суммы и наиболее к нему близка while (i > 0 && coins[i] > amt) { i--; } // Выбрать coins[i] amt -= coins[i]; count++; } // Если допустимое решение не найдено, вернуть -1 return amt == 0 ? count : -1; } /* Driver Code */ int main() { // Жадный подход: гарантирует нахождение глобально оптимального решения int coins1[6] = {1, 5, 10, 20, 50, 100}; int amt = 186; int res = coinChangeGreedy(coins1, 6, amt); printf("\ncoins = "); printArray(coins1, 6); printf("amt = %d\n", amt); printf("Минимальное количество монет для набора суммы %d = %d\n", amt, res); // Жадный подход: не гарантирует нахождение глобально оптимального решения int coins2[3] = {1, 20, 50}; amt = 60; res = coinChangeGreedy(coins2, 3, amt); printf("\ncoins = "); printArray(coins2, 3); printf("amt = %d\n", amt); printf("Минимальное количество монет для набора суммы %d = %d\n", amt, res); printf("На самом деле минимальное количество равно 3, а именно 20 + 20 + 20\n"); // Жадный подход: не гарантирует нахождение глобально оптимального решения int coins3[3] = {1, 49, 50}; amt = 98; res = coinChangeGreedy(coins3, 3, amt); printf("\ncoins = "); printArray(coins3, 3); printf("amt = %d\n", amt); printf("Минимальное количество монет для набора суммы %d = %d\n", amt, res); printf("На самом деле минимальное количество равно 2, а именно 49 + 49\n"); return 0; } ================================================ FILE: ru/codes/c/chapter_greedy/fractional_knapsack.c ================================================ /** * File: fractional_knapsack.c * Created Time: 2023-09-14 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* Предмет */ typedef struct { int w; // Вес предмета int v; // Стоимость предмета } Item; /* Отсортировать по удельной стоимости */ int sortByValueDensity(const void *a, const void *b) { Item *t1 = (Item *)a; Item *t2 = (Item *)b; return (float)(t1->v) / t1->w < (float)(t2->v) / t2->w; } /* Дробный рюкзак: жадный алгоритм */ float fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) { // Создать список предметов с двумя свойствами: вес и стоимость Item *items = malloc(sizeof(Item) * itemCount); for (int i = 0; i < itemCount; i++) { items[i] = (Item){.w = wgt[i], .v = val[i]}; } // Отсортировать по удельной стоимости item.v / item.w в порядке убывания qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity); // Циклический жадный выбор float res = 0.0; for (int i = 0; i < itemCount; i++) { if (items[i].w <= cap) { // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком res += items[i].v; cap -= items[i].w; } else { // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета res += (float)cap / items[i].w * items[i].v; cap = 0; break; } } free(items); return res; } /* Driver Code */ int main(void) { int wgt[] = {10, 20, 30, 40, 50}; int val[] = {50, 120, 150, 210, 240}; int capacity = 50; // Жадный алгоритм float res = fractionalKnapsack(wgt, val, sizeof(wgt) / sizeof(int), capacity); printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %0.2f\n", res); return 0; } ================================================ FILE: ru/codes/c/chapter_greedy/max_capacity.c ================================================ /** * File: max_capacity.c * Created Time: 2023-09-15 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* Найти минимум */ int myMin(int a, int b) { return a < b ? a : b; } /* Найти максимум */ int myMax(int a, int b) { return a > b ? a : b; } /* Максимальная вместимость: жадный алгоритм */ int maxCapacity(int ht[], int htLength) { // Инициализировать i и j так, чтобы они располагались по двум концам массива int i = 0; int j = htLength - 1; // Начальная максимальная вместимость равна 0 int res = 0; // Выполнять жадный выбор в цикле, пока две доски не встретятся while (i < j) { // Обновить максимальную вместимость int capacity = myMin(ht[i], ht[j]) * (j - i); res = myMax(res, capacity); // Сдвигать внутрь более короткую сторону if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ int main(void) { int ht[] = {3, 8, 5, 2, 7, 7, 3, 4}; // Жадный алгоритм int res = maxCapacity(ht, sizeof(ht) / sizeof(int)); printf("Максимальная вместимость = %d\n", res); return 0; } ================================================ FILE: ru/codes/c/chapter_greedy/max_product_cutting.c ================================================ /** * File: max_product_cutting.c * Created Time: 2023-09-15 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* Максимальное произведение разрезания: жадный алгоритм */ int maxProductCutting(int n) { // Когда n <= 3, обязательно нужно выделить одну 1 if (n <= 3) { return 1 * (n - 1); } // Жадно выделить множители 3, где a — число троек, а b — остаток int a = n / 3; int b = n % 3; if (b == 1) { // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 return pow(3, a - 1) * 2 * 2; } if (b == 2) { // Если остаток равен 2, ничего не делать return pow(3, a) * 2; } // Если остаток равен 0, ничего не делать return pow(3, a); } /* Driver Code */ int main(void) { int n = 58; // Жадный алгоритм int res = maxProductCutting(n); printf("Максимальное произведение после разрезания = %d\n", res); return 0; } ================================================ FILE: ru/codes/c/chapter_hashing/CMakeLists.txt ================================================ add_executable(array_hash_map array_hash_map.c) add_executable(hash_map_chaining hash_map_chaining.c) add_executable(hash_map_open_addressing hash_map_open_addressing.c) add_executable(simple_hash simple_hash.c) ================================================ FILE: ru/codes/c/chapter_hashing/array_hash_map.c ================================================ /** * File: array_hash_map.c * Created Time: 2023-03-18 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* Размер хеш-таблицы по умолчанию */ #define MAX_SIZE 100 /* Пара ключ-значение int->string */ typedef struct { int key; char *val; } Pair; /* Набор пар ключ-значение */ typedef struct { void *set; int len; } MapSet; /* Хеш-таблица на основе массива */ typedef struct { Pair *buckets[MAX_SIZE]; } ArrayHashMap; /* Конструктор */ ArrayHashMap *newArrayHashMap() { ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap)); for (int i=0; i < MAX_SIZE; i++) { hmap->buckets[i] = NULL; } return hmap; } /* Деструктор */ void delArrayHashMap(ArrayHashMap *hmap) { for (int i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { free(hmap->buckets[i]->val); free(hmap->buckets[i]); } } free(hmap); } /* Хеш-функция */ int hashFunc(int key) { int index = key % MAX_SIZE; return index; } /* Операция поиска */ const char *get(const ArrayHashMap *hmap, const int key) { int index = hashFunc(key); const Pair *Pair = hmap->buckets[index]; if (Pair == NULL) return NULL; return Pair->val; } /* Операция добавления */ void put(ArrayHashMap *hmap, const int key, const char *val) { Pair *Pair = malloc(sizeof(Pair)); Pair->key = key; Pair->val = malloc(strlen(val) + 1); strcpy(Pair->val, val); int index = hashFunc(key); hmap->buckets[index] = Pair; } /* Операция удаления */ void removeItem(ArrayHashMap *hmap, const int key) { int index = hashFunc(key); free(hmap->buckets[index]->val); free(hmap->buckets[index]); hmap->buckets[index] = NULL; } /* Получить все пары ключ-значение */ void pairSet(ArrayHashMap *hmap, MapSet *set) { Pair *entries; int i = 0, index = 0; int total = 0; /* Подсчитать число действительных пар ключ-значение */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } entries = malloc(sizeof(Pair) * total); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { entries[index].key = hmap->buckets[i]->key; entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1); strcpy(entries[index].val, hmap->buckets[i]->val); index++; } } set->set = entries; set->len = total; } /* Получить все ключи */ void keySet(ArrayHashMap *hmap, MapSet *set) { int *keys; int i = 0, index = 0; int total = 0; /* Подсчитать число действительных пар ключ-значение */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } keys = malloc(total * sizeof(int)); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { keys[index] = hmap->buckets[i]->key; index++; } } set->set = keys; set->len = total; } /* Получить все значения */ void valueSet(ArrayHashMap *hmap, MapSet *set) { char **vals; int i = 0, index = 0; int total = 0; /* Подсчитать число действительных пар ключ-значение */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } vals = malloc(total * sizeof(char *)); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { vals[index] = hmap->buckets[i]->val; index++; } } set->set = vals; set->len = total; } /* Вывести хеш-таблицу */ void print(ArrayHashMap *hmap) { int i; MapSet set; pairSet(hmap, &set); Pair *entries = (Pair *)set.set; for (i = 0; i < set.len; i++) { printf("%d -> %s\n", entries[i].key, entries[i].val); } free(set.set); } /* Driver Code */ int main() { /* Инициализация хеш-таблицы */ ArrayHashMap *hmap = newArrayHashMap(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу put(hmap, 12836, "Сяо Ха"); put(hmap, 15937, "Сяо Ло"); put(hmap, 16750, "Сяо Суань"); put(hmap, 13276, "Сяо Фа"); put(hmap, 10583, "Сяо Я"); printf("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение\n"); print(hmap); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value const char *name = get(hmap, 15937); printf("\nДля студенческого номера 15937 найдено имя %s\n", name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы removeItem(hmap, 10583); printf("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение\n"); print(hmap); /* Обход хеш-таблицы */ int i; printf("\nОтдельный обход пар ключ-значение\n"); print(hmap); MapSet set; keySet(hmap, &set); int *keys = (int *)set.set; printf("\nОтдельный обход ключей\n"); for (i = 0; i < set.len; i++) { printf("%d\n", keys[i]); } free(set.set); valueSet(hmap, &set); char **vals = (char **)set.set; printf("\nОбход только значений Value\n"); for (i = 0; i < set.len; i++) { printf("%s\n", vals[i]); } free(set.set); delArrayHashMap(hmap); return 0; } ================================================ FILE: ru/codes/c/chapter_hashing/hash_map_chaining.c ================================================ /** * File: hash_map_chaining.c * Created Time: 2023-10-13 * Author: SenMing (1206575349@qq.com), krahets (krahets@163.com) */ #include #include #include // Предположить, что максимальная длина val равна 100 #define MAX_SIZE 100 /* Пара ключ-значение */ typedef struct { int key; char val[MAX_SIZE]; } Pair; /* Узел связного списка */ typedef struct Node { Pair *pair; struct Node *next; } Node; /* Хеш-таблица с цепочками */ typedef struct { int size; // Число пар ключ-значение int capacity; // Вместимость хеш-таблицы double loadThres; // Порог коэффициента загрузки для запуска расширения int extendRatio; // Коэффициент расширения Node **buckets; // Массив корзин } HashMapChaining; /* Конструктор */ HashMapChaining *newHashMapChaining() { HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining)); hashMap->size = 0; hashMap->capacity = 4; hashMap->loadThres = 2.0 / 3.0; hashMap->extendRatio = 2; hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); for (int i = 0; i < hashMap->capacity; i++) { hashMap->buckets[i] = NULL; } return hashMap; } /* Деструктор */ void delHashMapChaining(HashMapChaining *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Node *cur = hashMap->buckets[i]; while (cur) { Node *tmp = cur; cur = cur->next; free(tmp->pair); free(tmp); } } free(hashMap->buckets); free(hashMap); } /* Хеш-функция */ int hashFunc(HashMapChaining *hashMap, int key) { return key % hashMap->capacity; } /* Коэффициент загрузки */ double loadFactor(HashMapChaining *hashMap) { return (double)hashMap->size / (double)hashMap->capacity; } /* Операция поиска */ char *get(HashMapChaining *hashMap, int key) { int index = hashFunc(hashMap, key); // Обойти корзину; если найден key, вернуть соответствующее val Node *cur = hashMap->buckets[index]; while (cur) { if (cur->pair->key == key) { return cur->pair->val; } cur = cur->next; } return ""; // Если key не найден, вернуть пустую строку } /* Операция добавления */ void put(HashMapChaining *hashMap, int key, const char *val); /* Расширить хеш-таблицу */ void extend(HashMapChaining *hashMap) { // Временно сохранить исходную хеш-таблицу int oldCapacity = hashMap->capacity; Node **oldBuckets = hashMap->buckets; // Инициализация новой хеш-таблицы после расширения hashMap->capacity *= hashMap->extendRatio; hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); for (int i = 0; i < hashMap->capacity; i++) { hashMap->buckets[i] = NULL; } hashMap->size = 0; // Перенести пары ключ-значение из исходной хеш-таблицы в новую for (int i = 0; i < oldCapacity; i++) { Node *cur = oldBuckets[i]; while (cur) { put(hashMap, cur->pair->key, cur->pair->val); Node *temp = cur; cur = cur->next; // Освободить память free(temp->pair); free(temp); } } free(oldBuckets); } /* Операция добавления */ void put(HashMapChaining *hashMap, int key, const char *val) { // Когда коэффициент загрузки превышает порог, выполнить расширение if (loadFactor(hashMap) > hashMap->loadThres) { extend(hashMap); } int index = hashFunc(hashMap, key); // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть Node *cur = hashMap->buckets[index]; while (cur) { if (cur->pair->key == key) { strcpy(cur->pair->val, val); // Если встретился указанный key, обновить соответствующий val и вернуть return; } cur = cur->next; } // Если такого key нет, добавить пару ключ-значение в голову связного списка Pair *newPair = (Pair *)malloc(sizeof(Pair)); newPair->key = key; strcpy(newPair->val, val); Node *newNode = (Node *)malloc(sizeof(Node)); newNode->pair = newPair; newNode->next = hashMap->buckets[index]; hashMap->buckets[index] = newNode; hashMap->size++; } /* Операция удаления */ void removeItem(HashMapChaining *hashMap, int key) { int index = hashFunc(hashMap, key); Node *cur = hashMap->buckets[index]; Node *pre = NULL; while (cur) { if (cur->pair->key == key) { // Удалить из него пару ключ-значение if (pre) { pre->next = cur->next; } else { hashMap->buckets[index] = cur->next; } // Освободить память free(cur->pair); free(cur); hashMap->size--; return; } pre = cur; cur = cur->next; } } /* Вывести хеш-таблицу */ void print(HashMapChaining *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Node *cur = hashMap->buckets[i]; printf("["); while (cur) { printf("%d -> %s, ", cur->pair->key, cur->pair->val); cur = cur->next; } printf("]\n"); } } /* Driver Code */ int main() { /* Инициализация хеш-таблицы */ HashMapChaining *hashMap = newHashMapChaining(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу put(hashMap, 12836, "Сяо Ха"); put(hashMap, 15937, "Сяо Ло"); put(hashMap, 16750, "Сяо Суань"); put(hashMap, 13276, "Сяо Фа"); put(hashMap, 10583, "Сяо Я"); printf("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение\n"); print(hashMap); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value char *name = get(hashMap, 13276); printf("\nДля студенческого номера 13276 найдено имя %s\n", name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы removeItem(hashMap, 12836); printf("\nПосле удаления студенческого номера 12836 хеш-таблица имеет вид\nКлюч -> Значение\n"); print(hashMap); /* Освободить память хеш-таблицы */ delHashMapChaining(hashMap); return 0; } ================================================ FILE: ru/codes/c/chapter_hashing/hash_map_open_addressing.c ================================================ /** * File: hash_map_open_addressing.c * Created Time: 2023-10-6 * Author: lclc6 (w1929522410@163.com) */ #include "../utils/common.h" /* Хеш-таблица с открытой адресацией */ typedef struct { int key; char *val; } Pair; /* Хеш-таблица с открытой адресацией */ typedef struct { int size; // Число пар ключ-значение int capacity; // Вместимость хеш-таблицы double loadThres; // Порог коэффициента загрузки для запуска расширения int extendRatio; // Коэффициент расширения Pair **buckets; // Массив корзин Pair *TOMBSTONE; // Удалить метку } HashMapOpenAddressing; // Объявление функции void extend(HashMapOpenAddressing *hashMap); /* Конструктор */ HashMapOpenAddressing *newHashMapOpenAddressing() { HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing)); hashMap->size = 0; hashMap->capacity = 4; hashMap->loadThres = 2.0 / 3.0; hashMap->extendRatio = 2; hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair)); hashMap->TOMBSTONE->key = -1; hashMap->TOMBSTONE->val = "-1"; return hashMap; } /* Деструктор */ void delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Pair *pair = hashMap->buckets[i]; if (pair != NULL && pair != hashMap->TOMBSTONE) { free(pair->val); free(pair); } } free(hashMap->buckets); free(hashMap->TOMBSTONE); free(hashMap); } /* Хеш-функция */ int hashFunc(HashMapOpenAddressing *hashMap, int key) { return key % hashMap->capacity; } /* Коэффициент загрузки */ double loadFactor(HashMapOpenAddressing *hashMap) { return (double)hashMap->size / (double)hashMap->capacity; } /* Найти индекс корзины, соответствующий key */ int findBucket(HashMapOpenAddressing *hashMap, int key) { int index = hashFunc(hashMap, key); int firstTombstone = -1; // Выполнять линейное пробирование и завершить при встрече с пустой корзиной while (hashMap->buckets[index] != NULL) { // Если встретился key, вернуть соответствующий индекс корзины if (hashMap->buckets[index]->key == key) { // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс if (firstTombstone != -1) { hashMap->buckets[firstTombstone] = hashMap->buckets[index]; hashMap->buckets[index] = hashMap->TOMBSTONE; return firstTombstone; // Вернуть индекс корзины после перемещения } return index; // Вернуть индекс корзины } // Записать первую встретившуюся метку удаления if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) { firstTombstone = index; } // Вычислить индекс корзины; при выходе за конец вернуться к началу index = (index + 1) % hashMap->capacity; } // Если key не существует, вернуть индекс точки добавления return firstTombstone == -1 ? index : firstTombstone; } /* Операция поиска */ char *get(HashMapOpenAddressing *hashMap, int key) { // Найти индекс корзины, соответствующий key int index = findBucket(hashMap, key); // Если пара ключ-значение найдена, вернуть соответствующее val if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { return hashMap->buckets[index]->val; } // Если пары ключ-значение не существует, вернуть пустую строку return ""; } /* Операция добавления */ void put(HashMapOpenAddressing *hashMap, int key, char *val) { // Когда коэффициент загрузки превышает порог, выполнить расширение if (loadFactor(hashMap) > hashMap->loadThres) { extend(hashMap); } // Найти индекс корзины, соответствующий key int index = findBucket(hashMap, key); // Если пара ключ-значение найдена, перезаписать val и вернуть if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { free(hashMap->buckets[index]->val); hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1)); strcpy(hashMap->buckets[index]->val, val); hashMap->buckets[index]->val[strlen(val)] = '\0'; return; } // Если пары ключ-значение нет, добавить ее Pair *pair = (Pair *)malloc(sizeof(Pair)); pair->key = key; pair->val = (char *)malloc(sizeof(strlen(val) + 1)); strcpy(pair->val, val); pair->val[strlen(val)] = '\0'; hashMap->buckets[index] = pair; hashMap->size++; } /* Операция удаления */ void removeItem(HashMapOpenAddressing *hashMap, int key) { // Найти индекс корзины, соответствующий key int index = findBucket(hashMap, key); // Если пара ключ-значение найдена, заменить ее меткой удаления if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { Pair *pair = hashMap->buckets[index]; free(pair->val); free(pair); hashMap->buckets[index] = hashMap->TOMBSTONE; hashMap->size--; } } /* Расширить хеш-таблицу */ void extend(HashMapOpenAddressing *hashMap) { // Временно сохранить исходную хеш-таблицу Pair **bucketsTmp = hashMap->buckets; int oldCapacity = hashMap->capacity; // Инициализация новой хеш-таблицы после расширения hashMap->capacity *= hashMap->extendRatio; hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); hashMap->size = 0; // Перенести пары ключ-значение из исходной хеш-таблицы в новую for (int i = 0; i < oldCapacity; i++) { Pair *pair = bucketsTmp[i]; if (pair != NULL && pair != hashMap->TOMBSTONE) { put(hashMap, pair->key, pair->val); free(pair->val); free(pair); } } free(bucketsTmp); } /* Вывести хеш-таблицу */ void print(HashMapOpenAddressing *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Pair *pair = hashMap->buckets[i]; if (pair == NULL) { printf("NULL\n"); } else if (pair == hashMap->TOMBSTONE) { printf("TOMBSTONE\n"); } else { printf("%d -> %s\n", pair->key, pair->val); } } } /* Driver Code */ int main() { // Инициализация хеш-таблицы HashMapOpenAddressing *hashmap = newHashMapOpenAddressing(); // Операция добавления // Добавить пару (key, val) в хеш-таблицу put(hashmap, 12836, "Сяо Ха"); put(hashmap, 15937, "Сяо Ло"); put(hashmap, 16750, "Сяо Суань"); put(hashmap, 13276, "Сяо Фа"); put(hashmap, 10583, "Сяо Я"); printf("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение\n"); print(hashmap); // Операция поиска // Передать ключ key в хеш-таблицу и получить значение val char *name = get(hashmap, 13276); printf("\nДля студенческого номера 13276 найдено имя %s\n", name); // Операция удаления // Удалить пару (key, val) из хеш-таблицы removeItem(hashmap, 16750); printf("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение\n"); print(hashmap); // Уничтожить хеш-таблицу delHashMapOpenAddressing(hashmap); return 0; } ================================================ FILE: ru/codes/c/chapter_hashing/simple_hash.c ================================================ /** * File: simple_hash.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Аддитивное хеширование */ int addHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = (hash + (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* Мультипликативное хеширование */ int mulHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = (31 * hash + (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* XOR-хеширование */ int xorHash(char *key) { int hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash ^= (unsigned char)key[i]; } return hash & MODULUS; } /* Хеширование с циклическим сдвигом */ int rotHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* Driver Code */ int main() { char *key = "Hello Algo"; int hash = addHash(key); printf("Хеш суммы = %d\n", hash); hash = mulHash(key); printf("Хеш произведения = %d\n", hash); hash = xorHash(key); printf("XOR-хеш = %d\n", hash); hash = rotHash(key); printf("Хеш с циклическим сдвигом = %d\n", hash); return 0; } ================================================ FILE: ru/codes/c/chapter_heap/CMakeLists.txt ================================================ add_executable(my_heap_test my_heap_test.c) add_executable(top_k top_k.c) ================================================ FILE: ru/codes/c/chapter_heap/my_heap.c ================================================ /** * File: my_heap.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 5000 /* Максимальная куча */ typedef struct { // size обозначает фактическое число элементов int size; // Использовать массив с заранее выделенной памятью, чтобы избежать расширения int data[MAX_SIZE]; } MaxHeap; // Объявление функции void siftDown(MaxHeap *maxHeap, int i); void siftUp(MaxHeap *maxHeap, int i); int parent(MaxHeap *maxHeap, int i); /* Конструктор, строящий кучу по срезу */ MaxHeap *newMaxHeap(int nums[], int size) { // Поместить все элементы в кучу MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap)); maxHeap->size = size; memcpy(maxHeap->data, nums, size * sizeof(int)); for (int i = parent(maxHeap, size - 1); i >= 0; i--) { // Выполнить heapify для всех узлов, кроме листовых siftDown(maxHeap, i); } return maxHeap; } /* Деструктор */ void delMaxHeap(MaxHeap *maxHeap) { // Освободить память free(maxHeap); } /* Получить индекс левого дочернего узла */ int left(MaxHeap *maxHeap, int i) { return 2 * i + 1; } /* Получить индекс правого дочернего узла */ int right(MaxHeap *maxHeap, int i) { return 2 * i + 2; } /* Получить индекс родительского узла */ int parent(MaxHeap *maxHeap, int i) { return (i - 1) / 2; // Округление вниз } /* Поменять элементы местами */ void swap(MaxHeap *maxHeap, int i, int j) { int temp = maxHeap->data[i]; maxHeap->data[i] = maxHeap->data[j]; maxHeap->data[j] = temp; } /* Получение размера кучи */ int size(MaxHeap *maxHeap) { return maxHeap->size; } /* Проверка, пуста ли куча */ int isEmpty(MaxHeap *maxHeap) { return maxHeap->size == 0; } /* Доступ к элементу на вершине кучи */ int peek(MaxHeap *maxHeap) { return maxHeap->data[0]; } /* Добавление элемента в кучу */ void push(MaxHeap *maxHeap, int val) { // По умолчанию не следует добавлять так много узлов if (maxHeap->size == MAX_SIZE) { printf("heap is full!"); return; } // Добавление узла maxHeap->data[maxHeap->size] = val; maxHeap->size++; // Просеивание снизу вверх siftUp(maxHeap, maxHeap->size - 1); } /* Извлечение элемента из кучи */ int pop(MaxHeap *maxHeap) { // Обработка пустого случая if (isEmpty(maxHeap)) { printf("heap is empty!"); return INT_MAX; } // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) swap(maxHeap, 0, size(maxHeap) - 1); // Удаление узла int val = maxHeap->data[maxHeap->size - 1]; maxHeap->size--; // Просеивание сверху вниз siftDown(maxHeap, 0); // Вернуть элемент с вершины кучи return val; } /* Начиная с узла i, выполнить просеивание сверху вниз */ void siftDown(MaxHeap *maxHeap, int i) { while (true) { // Определить узел с максимальным значением среди i, l и r и обозначить его как max int l = left(maxHeap, i); int r = right(maxHeap, i); int max = i; if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) { max = l; } if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) { max = r; } // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if (max == i) { break; } // Поменять два узла местами swap(maxHeap, i, max); // Циклическое просеивание вниз i = max; } } /* Начиная с узла i, выполнить просеивание снизу вверх */ void siftUp(MaxHeap *maxHeap, int i) { while (true) { // Получение родительского узла для узла i int p = parent(maxHeap, i); // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) { break; } // Поменять два узла местами swap(maxHeap, i, p); // Циклическое просеивание вверх i = p; } } ================================================ FILE: ru/codes/c/chapter_heap/my_heap_test.c ================================================ /** * File: my_heap_test.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "my_heap.c" /* Driver Code */ int main() { /* Инициализация кучи */ // Инициализация максимальной кучи int nums[] = {9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; MaxHeap *maxHeap = newMaxHeap(nums, sizeof(nums) / sizeof(int)); printf("После построения кучи из входного массива\n"); printHeap(maxHeap->data, maxHeap->size); /* Получение элемента с вершины кучи */ printf("\nВерхний элемент кучи = %d\n", peek(maxHeap)); /* Добавление элемента в кучу */ push(maxHeap, 7); printf("\nПосле добавления элемента 7 в кучу\n"); printHeap(maxHeap->data, maxHeap->size); /* Извлечение элемента с вершины кучи */ int top = pop(maxHeap); printf("\nПосле извлечения верхнего элемента %d из кучи\n", top); printHeap(maxHeap->data, maxHeap->size); /* Получение размера кучи */ printf("\nКоличество элементов в куче = %d\n", size(maxHeap)); /* Проверка, пуста ли куча */ printf("\nПуста ли куча: %d\n", isEmpty(maxHeap)); // Освободить память delMaxHeap(maxHeap); return 0; } ================================================ FILE: ru/codes/c/chapter_heap/top_k.c ================================================ /** * File: top_k.c * Created Time: 2023-10-26 * Author: krahets (krahets163.com) */ #include "my_heap.c" /* Добавление элемента в кучу */ void pushMinHeap(MaxHeap *maxHeap, int val) { // Инвертировать знак элемента push(maxHeap, -val); } /* Извлечение элемента из кучи */ int popMinHeap(MaxHeap *maxHeap) { // Инвертировать знак элемента return -pop(maxHeap); } /* Доступ к элементу на вершине кучи */ int peekMinHeap(MaxHeap *maxHeap) { // Инвертировать знак элемента return -peek(maxHeap); } /* Извлечь элементы из кучи */ int *getMinHeap(MaxHeap *maxHeap) { // Инвертировать все элементы кучи и записать их в массив res int *res = (int *)malloc(maxHeap->size * sizeof(int)); for (int i = 0; i < maxHeap->size; i++) { res[i] = -maxHeap->data[i]; } return res; } // Функция поиска k наибольших элементов массива на основе кучи int *topKHeap(int *nums, int sizeNums, int k) { // Инициализация минимальной кучи // Обратите внимание: мы инвертируем все элементы кучи, чтобы с помощью максимальной кучи имитировать минимальную int *empty = (int *)malloc(0); MaxHeap *maxHeap = newMaxHeap(empty, 0); // Поместить первые k элементов массива в кучу for (int i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // Начиная с элемента k+1, поддерживать длину кучи равной k for (int i = k; i < sizeNums; i++) { // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } int *res = getMinHeap(maxHeap); // Освободить память delMaxHeap(maxHeap); return res; } /* Driver Code */ int main() { int nums[] = {1, 7, 6, 3, 2}; int k = 3; int sizeNums = sizeof(nums) / sizeof(nums[0]); int *res = topKHeap(nums, sizeNums, k); printf("Наибольшие %d элементов: ", k); printArray(res, k); free(res); return 0; } ================================================ FILE: ru/codes/c/chapter_searching/CMakeLists.txt ================================================ add_executable(binary_search binary_search.c) add_executable(two_sum two_sum.c) add_executable(binary_search_edge binary_search_edge.c) add_executable(binary_search_insertion binary_search_insertion.c) ================================================ FILE: ru/codes/c/chapter_searching/binary_search.c ================================================ /** * File: binary_search.c * Created Time: 2023-03-18 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* Бинарный поиск (двусторонне замкнутый интервал) */ int binarySearch(int *nums, int len, int target) { // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно int i = 0, j = len - 1; // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) while (i <= j) { int m = i + (j - i) / 2; // Вычислить индекс середины m if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j] i = m + 1; else if (nums[m] > target) // Это означает, что target находится в интервале [i, m-1] j = m - 1; else // Целевой элемент найден, вернуть его индекс return m; } // Целевой элемент не найден, вернуть -1 return -1; } /* Бинарный поиск (лево замкнутый, право открытый интервал) */ int binarySearchLCRO(int *nums, int len, int target) { // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно int i = 0, j = len; // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) while (i < j) { int m = i + (j - i) / 2; // Вычислить индекс середины m if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j) i = m + 1; else if (nums[m] > target) // Это означает, что target находится в интервале [i, m) j = m; else // Целевой элемент найден, вернуть его индекс return m; } // Целевой элемент не найден, вернуть -1 return -1; } /* Driver Code */ int main() { int target = 6; int nums[10] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; /* Бинарный поиск (двусторонне замкнутый интервал) */ int index = binarySearch(nums, 10, target); printf("Индекс целевого элемента 6 = %d\n", index); /* Бинарный поиск (лево замкнутый, право открытый интервал) */ index = binarySearchLCRO(nums, 10, target); printf("Индекс целевого элемента 6 = %d\n", index); return 0; } ================================================ FILE: ru/codes/c/chapter_searching/binary_search_edge.c ================================================ /** * File: binary_search_edge.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Бинарный поиск точки вставки (с повторяющимися элементами) */ int binarySearchInsertion(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Вычислить индекс середины m if (nums[m] < target) { i = m + 1; // target находится в интервале [m+1, j] } else { j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] } } // Вернуть точку вставки i return i; } /* Бинарный поиск самого левого target */ int binarySearchLeftEdge(int *nums, int numSize, int target) { // Эквивалентно поиску точки вставки target int i = binarySearchInsertion(nums, numSize, target); // target не найден, вернуть -1 if (i == numSize || nums[i] != target) { return -1; } // Найти target и вернуть индекс i return i; } /* Бинарный поиск самого правого target */ int binarySearchRightEdge(int *nums, int numSize, int target) { // Преобразовать задачу в поиск самого левого target + 1 int i = binarySearchInsertion(nums, numSize, target + 1); // j указывает на самый правый target, а i — на первый элемент больше target int j = i - 1; // target не найден, вернуть -1 if (j == -1 || nums[j] != target) { return -1; } // Найти target и вернуть индекс j return j; } /* Driver Code */ int main() { // Массив с повторяющимися элементами int nums[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; printf("\nМассив nums = "); printArray(nums, sizeof(nums) / sizeof(nums[0])); // Бинарный поиск левой и правой границы int targets[] = {6, 7}; for (int i = 0; i < sizeof(targets) / sizeof(targets[0]); i++) { int index = binarySearchLeftEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); printf("Индекс самого левого элемента %d = %d\n", targets[i], index); index = binarySearchRightEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); printf("Индекс самого правого элемента %d = %d\n", targets[i], index); } return 0; } ================================================ FILE: ru/codes/c/chapter_searching/binary_search_insertion.c ================================================ /** * File: binary_search_insertion.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Бинарный поиск точки вставки (без повторяющихся элементов) */ int binarySearchInsertionSimple(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Вычислить индекс середины m if (nums[m] < target) { i = m + 1; // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1; // target находится в интервале [i, m-1] } else { return m; // Найти target и вернуть точку вставки m } } // target не найден, вернуть точку вставки i return i; } /* Бинарный поиск точки вставки (с повторяющимися элементами) */ int binarySearchInsertion(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Вычислить индекс середины m if (nums[m] < target) { i = m + 1; // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1; // target находится в интервале [i, m-1] } else { j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] } } // Вернуть точку вставки i return i; } /* Driver Code */ int main() { // Массив без повторяющихся элементов int nums1[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; printf("\nМассив nums = "); printArray(nums1, sizeof(nums1) / sizeof(nums1[0])); // Бинарный поиск точки вставки int targets1[] = {6, 9}; for (int i = 0; i < sizeof(targets1) / sizeof(targets1[0]); i++) { int index = binarySearchInsertionSimple(nums1, sizeof(nums1) / sizeof(nums1[0]), targets1[i]); printf("Индекс позиции вставки для элемента %d = %d\n", targets1[i], index); } // Массив с повторяющимися элементами int nums2[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; printf("\nМассив nums = "); printArray(nums2, sizeof(nums2) / sizeof(nums2[0])); // Бинарный поиск точки вставки int targets2[] = {2, 6, 20}; for (int i = 0; i < sizeof(targets2) / sizeof(int); i++) { int index = binarySearchInsertion(nums2, sizeof(nums2) / sizeof(nums2[0]), targets2[i]); printf("Индекс позиции вставки для элемента %d = %d\n", targets2[i], index); } return 0; } ================================================ FILE: ru/codes/c/chapter_searching/two_sum.c ================================================ /** * File: two_sum.c * Created Time: 2023-01-19 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Метод 1: полный перебор */ int *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) { for (int i = 0; i < numsSize; ++i) { for (int j = i + 1; j < numsSize; ++j) { if (nums[i] + nums[j] == target) { int *res = malloc(sizeof(int) * 2); res[0] = i, res[1] = j; *returnSize = 2; return res; } } } *returnSize = 0; return NULL; } /* Хеш-таблица */ typedef struct { int key; int val; UT_hash_handle hh; // Реализовано на основе uthash.h } HashTable; /* Поиск в хеш-таблице */ HashTable *find(HashTable *h, int key) { HashTable *tmp; HASH_FIND_INT(h, &key, tmp); return tmp; } /* Вставка элемента в хеш-таблицу */ void insert(HashTable **h, int key, int val) { HashTable *t = find(*h, key); if (t == NULL) { HashTable *tmp = malloc(sizeof(HashTable)); tmp->key = key, tmp->val = val; HASH_ADD_INT(*h, key, tmp); } else { t->val = val; } } /* Метод 2: вспомогательная хеш-таблица */ int *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) { HashTable *hashtable = NULL; for (int i = 0; i < numsSize; i++) { HashTable *t = find(hashtable, target - nums[i]); if (t != NULL) { int *res = malloc(sizeof(int) * 2); res[0] = t->val, res[1] = i; *returnSize = 2; return res; } insert(&hashtable, nums[i], i); } *returnSize = 0; return NULL; } /* Driver Code */ int main() { // ======= Test Case ======= int nums[] = {2, 7, 11, 15}; int target = 13; // ====== Driver Code ====== int returnSize; int *res = twoSumBruteForce(nums, sizeof(nums) / sizeof(int), target, &returnSize); // Метод 1 printf("Способ 1: res = "); printArray(res, returnSize); // Метод 2 res = twoSumHashTable(nums, sizeof(nums) / sizeof(int), target, &returnSize); printf("Способ 2: res = "); printArray(res, returnSize); return 0; } ================================================ FILE: ru/codes/c/chapter_sorting/CMakeLists.txt ================================================ add_executable(bubble_sort bubble_sort.c) add_executable(insertion_sort insertion_sort.c) add_executable(quick_sort quick_sort.c) add_executable(counting_sort counting_sort.c) add_executable(radix_sort radix_sort.c) add_executable(merge_sort merge_sort.c) add_executable(heap_sort heap_sort.c) add_executable(bucket_sort bucket_sort.c) add_executable(selection_sort selection_sort.c) ================================================ FILE: ru/codes/c/chapter_sorting/bubble_sort.c ================================================ /** * File: bubble_sort.c * Created Time: 2022-12-26 * Author: Listening (https://github.com/L-Super) */ #include "../utils/common.h" /* Пузырьковая сортировка */ void bubbleSort(int nums[], int size) { // Внешний цикл: неотсортированный диапазон [0, i] for (int i = size - 1; i > 0; i--) { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; } } } } /* Пузырьковая сортировка (оптимизация флагом) */ void bubbleSortWithFlag(int nums[], int size) { // Внешний цикл: неотсортированный диапазон [0, i] for (int i = size - 1; i > 0; i--) { bool flag = false; // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; flag = true; } } if (!flag) break; } } /* Driver Code */ int main() { int nums[6] = {4, 1, 3, 1, 5, 2}; printf("После пузырьковой сортировки: "); bubbleSort(nums, 6); for (int i = 0; i < 6; i++) { printf("%d ", nums[i]); } int nums1[6] = {4, 1, 3, 1, 5, 2}; printf("\nПосле оптимизированной пузырьковой сортировки: "); bubbleSortWithFlag(nums1, 6); for (int i = 0; i < 6; i++) { printf("%d ", nums1[i]); } printf("\n"); return 0; } ================================================ FILE: ru/codes/c/chapter_sorting/bucket_sort.c ================================================ /** * File: bucket_sort.c * Created Time: 2023-05-30 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define SIZE 10 /* Функция сравнения для qsort */ int compare(const void *a, const void *b) { float fa = *(const float *)a; float fb = *(const float *)b; return (fa > fb) - (fa < fb); } /* Сортировка корзинами */ void bucketSort(float nums[], int n) { int k = n / 2; // Инициализировать k = n/2 корзин int *sizes = malloc(k * sizeof(int)); // Записать размер каждой корзины float **buckets = malloc(k * sizeof(float *)); // Массив динамических массивов (корзины) // Предварительно выделить достаточно места для каждой корзины for (int i = 0; i < k; ++i) { buckets[i] = (float *)malloc(n * sizeof(float)); sizes[i] = 0; } // 1. Распределить элементы массива по корзинам for (int i = 0; i < n; ++i) { int idx = (int)(nums[i] * k); buckets[idx][sizes[idx]++] = nums[i]; } // 2. Выполнить сортировку внутри каждой корзины for (int i = 0; i < k; ++i) { qsort(buckets[i], sizes[i], sizeof(float), compare); } // 3. Объединить отсортированные корзины int idx = 0; for (int i = 0; i < k; ++i) { for (int j = 0; j < sizes[i]; ++j) { nums[idx++] = buckets[i][j]; } // Освободить память free(buckets[i]); } } /* Driver Code */ int main() { // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) float nums[SIZE] = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; bucketSort(nums, SIZE); printf("После сортировки корзинами nums = "); printArrayFloat(nums, SIZE); return 0; } ================================================ FILE: ru/codes/c/chapter_sorting/counting_sort.c ================================================ /** * File: counting_sort.c * Created Time: 2023-03-20 * Author: Reanon (793584285@qq.com), Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* Сортировка подсчетом */ // Простая реализация, не подходит для сортировки объектов void countingSortNaive(int nums[], int size) { // 1. Найти максимальный элемент массива m int m = 0; for (int i = 0; i < size; i++) { if (nums[i] > m) { m = nums[i]; } } // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num int *counter = calloc(m + 1, sizeof(int)); for (int i = 0; i < size; i++) { counter[nums[i]]++; } // 3. Обойти counter и заполнить исходный массив nums элементами int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } // 4. Освободить память free(counter); } /* Сортировка подсчетом */ // Полная реализация, позволяет сортировать объекты и является стабильной сортировкой void countingSort(int nums[], int size) { // 1. Найти максимальный элемент массива m int m = 0; for (int i = 0; i < size; i++) { if (nums[i] > m) { m = nums[i]; } } // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num int *counter = calloc(m, sizeof(int)); for (int i = 0; i < size; i++) { counter[nums[i]]++; } // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» // То есть counter[num]-1 — это индекс последнего появления num в res for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res // Инициализировать массив res для хранения результата int *res = malloc(sizeof(int) * size); for (int i = size - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // Поместить num по соответствующему индексу counter[num]--; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num } // Перезаписать исходный массив nums массивом результата res memcpy(nums, res, size * sizeof(int)); // 5. Освободить память free(res); free(counter); } /* Driver Code */ int main() { int nums[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; int size = sizeof(nums) / sizeof(int); countingSortNaive(nums, size); printf("После сортировки подсчетом (объекты не поддерживаются) nums = "); printArray(nums, size); int nums1[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; int size1 = sizeof(nums1) / sizeof(int); countingSort(nums1, size1); printf("После сортировки подсчетом nums1 = "); printArray(nums1, size1); return 0; } ================================================ FILE: ru/codes/c/chapter_sorting/heap_sort.c ================================================ /** * File: heap_sort.c * Created Time: 2023-05-30 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ void siftDown(int nums[], int n, int i) { while (1) { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; if (l < n && nums[l] > nums[ma]) ma = l; if (r < n && nums[r] > nums[ma]) ma = r; // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if (ma == i) { break; } // Поменять два узла местами int temp = nums[i]; nums[i] = nums[ma]; nums[ma] = temp; // Циклическое просеивание вниз i = ma; } } /* Сортировка кучей */ void heapSort(int nums[], int n) { // Построение кучи: выполнить heapify для всех узлов, кроме листовых for (int i = n / 2 - 1; i >= 0; --i) { siftDown(nums, n, i); } // Извлекать максимальный элемент из кучи в течение n-1 итераций for (int i = n - 1; i > 0; --i) { // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) int tmp = nums[0]; nums[0] = nums[i]; nums[i] = tmp; // Начиная с корневого узла, выполнить просеивание сверху вниз siftDown(nums, i, 0); } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; int n = sizeof(nums) / sizeof(nums[0]); heapSort(nums, n); printf("После сортировки кучей nums = "); printArray(nums, n); return 0; } ================================================ FILE: ru/codes/c/chapter_sorting/insertion_sort.c ================================================ /** * File: insertion_sort.c * Created Time: 2022-12-29 * Author: Listening (https://github.com/L-Super) */ #include "../utils/common.h" /* Сортировка вставками */ void insertionSort(int nums[], int size) { // Внешний цикл: отсортированный диапазон [0, i-1] for (int i = 1; i < size; i++) { int base = nums[i], j = i - 1; // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] while (j >= 0 && nums[j] > base) { // Сдвинуть nums[j] на одну позицию вправо nums[j + 1] = nums[j]; j--; } // Поместить base в правильную позицию nums[j + 1] = base; } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; insertionSort(nums, 6); printf("После сортировки вставками nums = "); for (int i = 0; i < 6; i++) { printf("%d ", nums[i]); } printf("\n"); return 0; } ================================================ FILE: ru/codes/c/chapter_sorting/merge_sort.c ================================================ /** * File: merge_sort.c * Created Time: 2022-03-21 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* Объединить левый и правый подмассивы */ void merge(int *nums, int left, int mid, int right) { // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] // Создать временный массив tmp для хранения результата слияния int tmpSize = right - left + 1; int *tmp = (int *)malloc(tmpSize * sizeof(int)); // Инициализировать начальные индексы левого и правого подмассивов int i = left, j = mid + 1, k = 0; // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums for (k = 0; k < tmpSize; ++k) { nums[left + k] = tmp[k]; } // Освободить память free(tmp); } /* Сортировка слиянием */ void mergeSort(int *nums, int left, int right) { // Условие завершения if (left >= right) return; // Завершить рекурсию, когда длина подмассива равна 1 // Этап разбиения int mid = left + (right - left) / 2; // Вычислить середину mergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив mergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив // Этап слияния merge(nums, left, mid, right); } /* Driver Code */ int main() { /* Сортировка слиянием */ int nums[] = {7, 3, 2, 6, 0, 1, 5, 4}; int size = sizeof(nums) / sizeof(int); mergeSort(nums, 0, size - 1); printf("После сортировки слиянием nums = "); printArray(nums, size); return 0; } ================================================ FILE: ru/codes/c/chapter_sorting/quick_sort.c ================================================ /** * File: quick_sort.c * Created Time: 2023-01-18 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Обмен элементов */ void swap(int nums[], int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Разбиение с опорными указателями */ int partition(int nums[], int left, int right) { // Взять nums[left] в качестве опорного элемента int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // Идти справа налево в поисках первого элемента меньше опорного } while (i < j && nums[i] <= nums[left]) { i++; // Идти слева направо в поисках первого элемента больше опорного } // Поменять эти два элемента местами swap(nums, i, j); } // Переместить опорный элемент на границу двух подмассивов swap(nums, i, left); // Вернуть индекс опорного элемента return i; } /* Быстрая сортировка */ void quickSort(int nums[], int left, int right) { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) { return; } // Разбиение с опорными указателями int pivot = partition(nums, left, right); // Рекурсивно обработать левый и правый подмассивы quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } // Ниже приведена быстрая сортировка с оптимизацией медианой /* Выбрать медиану из трех кандидатов */ 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; } /* Разбиение с опорными указателями (медиана трех) */ int partitionMedian(int 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; // Вернуть индекс опорного элемента } /* Быстрая сортировка (медиана трех) */ void quickSortMedian(int nums[], int left, int right) { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) return; // Разбиение с опорными указателями int pivot = partitionMedian(nums, left, right); // Рекурсивно обработать левый и правый подмассивы quickSortMedian(nums, left, pivot - 1); quickSortMedian(nums, pivot + 1, right); } // Ниже приведена быстрая сортировка с оптимизацией глубины рекурсии /* Быстрая сортировка (оптимизация глубины рекурсии) */ void quickSortTailCall(int nums[], int left, int right) { // Завершить, когда длина подмассива равна 1 while (left < right) { // Операция разбиения с опорными указателями int pivot = partition(nums, left, right); // Выполнить быструю сортировку для более короткого из двух подмассивов if (pivot - left < right - pivot) { // Рекурсивно отсортировать левый подмассив quickSortTailCall(nums, left, pivot - 1); // Оставшийся неотсортированный диапазон: [pivot + 1, right] left = pivot + 1; } else { // Рекурсивно отсортировать правый подмассив quickSortTailCall(nums, pivot + 1, right); // Оставшийся неотсортированный диапазон: [left, pivot - 1] right = pivot - 1; } } } /* Driver Code */ int main() { /* Быстрая сортировка */ int nums[] = {2, 4, 1, 0, 3, 5}; int size = sizeof(nums) / sizeof(int); quickSort(nums, 0, size - 1); printf("После быстрой сортировки nums = "); printArray(nums, size); /* Быстрая сортировка (оптимизация медианным опорным элементом) */ int nums1[] = {2, 4, 1, 0, 3, 5}; quickSortMedian(nums1, 0, size - 1); printf("После быстрой сортировки (оптимизация медианным опорным элементом) nums = "); printArray(nums1, size); /* Быстрая сортировка (оптимизация глубины рекурсии) */ int nums2[] = {2, 4, 1, 0, 3, 5}; quickSortTailCall(nums2, 0, size - 1); printf("После быстрой сортировки (оптимизация глубины рекурсии) nums = "); printArray(nums1, size); return 0; } ================================================ FILE: ru/codes/c/chapter_sorting/radix_sort.c ================================================ /** * File: radix_sort.c * Created Time: 2023-01-18 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Получить k-й разряд элемента num, где exp = 10^(k-1) */ int digit(int num, int exp) { // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени return (num / exp) % 10; } /* Сортировка подсчетом (сортировка по k-му разряду nums) */ void countingSortDigit(int nums[], int size, int exp) { // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 int *counter = (int *)malloc((sizeof(int) * 10)); memset(counter, 0, sizeof(int) * 10); // Инициализировать нулем для последующего освобождения памяти // Подсчитать число появлений каждой цифры от 0 до 9 for (int i = 0; i < size; i++) { // Получить k-й разряд nums[i], обозначив его как d int d = digit(nums[i], exp); // Подсчитать число появлений цифры d counter[d]++; } // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // Выполняя обратный проход, заполнить res элементами по статистике в корзинах int *res = (int *)malloc(sizeof(int) * size); for (int i = size - 1; i >= 0; i--) { int d = digit(nums[i], exp); int j = counter[d] - 1; // Получить индекс j цифры d в массиве res[j] = nums[i]; // Поместить текущий элемент по индексу j counter[d]--; // Уменьшить количество d на 1 } // Перезаписать исходный массив nums результатом for (int i = 0; i < size; i++) { nums[i] = res[i]; } // Освободить память free(res); free(counter); } /* Поразрядная сортировка */ void radixSort(int nums[], int size) { // Получить максимальный элемент массива, чтобы определить максимальное число разрядов int max = INT32_MIN; for (int i = 0; i < size; i++) { if (nums[i] > max) { max = nums[i]; } } // Проходить разряды от младшего к старшему for (int exp = 1; max >= exp; exp *= 10) // Выполнить сортировку подсчетом по k-му разряду элементов массива // k = 1 -> exp = 1 // k = 2 -> exp = 10 // то есть exp = 10^(k-1) countingSortDigit(nums, size, exp); } /* Driver Code */ int main() { // Поразрядная сортировка int nums[] = {10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996}; int size = sizeof(nums) / sizeof(int); radixSort(nums, size); printf("После поразрядной сортировки nums = "); printArray(nums, size); } ================================================ FILE: ru/codes/c/chapter_sorting/selection_sort.c ================================================ /** * File: selection_sort.c * Created Time: 2023-05-31 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Сортировка выбором */ void selectionSort(int nums[], int n) { // Внешний цикл: неотсортированный диапазон [i, n-1] for (int i = 0; i < n - 1; i++) { // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // Записать индекс минимального элемента } // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; int n = sizeof(nums) / sizeof(nums[0]); selectionSort(nums, n); printf("После сортировки выбором nums = "); printArray(nums, n); return 0; } ================================================ FILE: ru/codes/c/chapter_stack_and_queue/CMakeLists.txt ================================================ add_executable(array_stack array_stack.c) add_executable(linkedlist_stack linkedlist_stack.c) add_executable(array_queue array_queue.c) add_executable(linkedlist_queue linkedlist_queue.c) add_executable(array_deque array_deque.c) add_executable(linkedlist_deque linkedlist_deque.c) ================================================ FILE: ru/codes/c/chapter_stack_and_queue/array_deque.c ================================================ /** * File: array_deque.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Двусторонняя очередь на основе кольцевого массива */ typedef struct { int *nums; // Массив для хранения элементов очереди int front; // Указатель head, указывающий на первый элемент очереди int queSize; // Указатель хвоста, указывающий на позицию после хвоста int queCapacity; // Вместимость очереди } ArrayDeque; /* Конструктор */ ArrayDeque *newArrayDeque(int capacity) { ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque)); // Инициализация массива deque->queCapacity = capacity; deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity); deque->front = deque->queSize = 0; return deque; } /* Деструктор */ void delArrayDeque(ArrayDeque *deque) { free(deque->nums); free(deque); } /* Получить вместимость двусторонней очереди */ int capacity(ArrayDeque *deque) { return deque->queCapacity; } /* Получение длины двусторонней очереди */ int size(ArrayDeque *deque) { return deque->queSize; } /* Проверка, пуста ли двусторонняя очередь */ bool empty(ArrayDeque *deque) { return deque->queSize == 0; } /* Вычислить индекс в кольцевом массиве */ int dequeIndex(ArrayDeque *deque, int i) { // С помощью операции взятия остатка соединить начало и конец массива // Когда i выходит за хвост массива, вернуться к началу // Когда i выходит за голову массива, вернуться к концу return ((i + capacity(deque)) % capacity(deque)); } /* Добавление в голову очереди */ void pushFirst(ArrayDeque *deque, int num) { if (deque->queSize == capacity(deque)) { printf("Дек заполнен\r\n"); return; } // Указатель головы сместить влево на одну позицию // С помощью операции взятия остатка реализовать возврат front к хвосту после выхода за начало массива deque->front = dequeIndex(deque, deque->front - 1); // Добавить num в голову очереди deque->nums[deque->front] = num; deque->queSize++; } /* Добавление в хвост очереди */ void pushLast(ArrayDeque *deque, int num) { if (deque->queSize == capacity(deque)) { printf("Дек заполнен\r\n"); return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 int rear = dequeIndex(deque, deque->front + deque->queSize); // Добавить num в хвост очереди deque->nums[rear] = num; deque->queSize++; } /* Доступ к элементу в начале очереди */ int peekFirst(ArrayDeque *deque) { // Ошибка доступа: двусторонняя очередь пуста assert(empty(deque) == 0); return deque->nums[deque->front]; } /* Доступ к элементу в конце очереди */ int peekLast(ArrayDeque *deque) { // Ошибка доступа: двусторонняя очередь пуста assert(empty(deque) == 0); int last = dequeIndex(deque, deque->front + deque->queSize - 1); return deque->nums[last]; } /* Извлечение из головы очереди */ int popFirst(ArrayDeque *deque) { int num = peekFirst(deque); // Указатель головы сдвигается на одну позицию назад deque->front = dequeIndex(deque, deque->front + 1); deque->queSize--; return num; } /* Извлечение из хвоста очереди */ int popLast(ArrayDeque *deque) { int num = peekLast(deque); deque->queSize--; return num; } /* Вернуть массив для вывода */ int *toArray(ArrayDeque *deque, int *queSize) { *queSize = deque->queSize; int *res = (int *)calloc(deque->queSize, sizeof(int)); int j = deque->front; for (int i = 0; i < deque->queSize; i++) { res[i] = deque->nums[j % deque->queCapacity]; j++; } return res; } /* Driver Code */ int main() { /* Инициализация очереди */ int capacity = 10; int queSize; ArrayDeque *deque = newArrayDeque(capacity); pushLast(deque, 3); pushLast(deque, 2); pushLast(deque, 5); printf("Дек deque = "); printArray(toArray(deque, &queSize), queSize); /* Доступ к элементу */ int peekFirstNum = peekFirst(deque); printf("Элемент в голове peekFirst = %d\r\n", peekFirstNum); int peekLastNum = peekLast(deque); printf("Элемент в хвосте peekLast = %d\r\n", peekLastNum); /* Добавление элемента в очередь */ pushLast(deque, 4); printf("После вставки элемента 4 в хвост дек = "); printArray(toArray(deque, &queSize), queSize); pushFirst(deque, 1); printf("После вставки элемента 1 в голову дек = "); printArray(toArray(deque, &queSize), queSize); /* Извлечение элемента из очереди */ int popLastNum = popLast(deque); printf("Извлечен элемент из хвоста = %d, дек после извлечения из хвоста = ", popLastNum); printArray(toArray(deque, &queSize), queSize); int popFirstNum = popFirst(deque); printf("Извлечен элемент из головы = %d, дек после извлечения из головы = ", popFirstNum); printArray(toArray(deque, &queSize), queSize); /* Получение длины очереди */ int dequeSize = size(deque); printf("Длина дека size = %d\r\n", dequeSize); /* Проверка, пуста ли очередь */ bool isEmpty = empty(deque); printf("Пуста ли очередь = %s\r\n", isEmpty ? "true" : "false"); // Освободить память delArrayDeque(deque); return 0; } ================================================ FILE: ru/codes/c/chapter_stack_and_queue/array_queue.c ================================================ /** * File: array_queue.c * Created Time: 2023-01-28 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* Очередь на основе кольцевого массива */ typedef struct { int *nums; // Массив для хранения элементов очереди int front; // Указатель head, указывающий на первый элемент очереди int queSize; // Текущее количество элементов в очереди int queCapacity; // Вместимость очереди } ArrayQueue; /* Конструктор */ ArrayQueue *newArrayQueue(int capacity) { ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue)); // Инициализация массива queue->queCapacity = capacity; queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity); queue->front = queue->queSize = 0; return queue; } /* Деструктор */ void delArrayQueue(ArrayQueue *queue) { free(queue->nums); free(queue); } /* Получить вместимость очереди */ int capacity(ArrayQueue *queue) { return queue->queCapacity; } /* Получение длины очереди */ int size(ArrayQueue *queue) { return queue->queSize; } /* Проверка, пуста ли очередь */ bool empty(ArrayQueue *queue) { return queue->queSize == 0; } /* Доступ к элементу в начале очереди */ int peek(ArrayQueue *queue) { assert(size(queue) != 0); return queue->nums[queue->front]; } /* Поместить в очередь */ void push(ArrayQueue *queue, int num) { if (size(queue) == capacity(queue)) { printf("Очередь заполнена\r\n"); return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива int rear = (queue->front + queue->queSize) % queue->queCapacity; // Добавить num в хвост очереди queue->nums[rear] = num; queue->queSize++; } /* Извлечь из очереди */ int pop(ArrayQueue *queue) { int num = peek(queue); // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива queue->front = (queue->front + 1) % queue->queCapacity; queue->queSize--; return num; } /* Вернуть массив для вывода */ int *toArray(ArrayQueue *queue, int *queSize) { *queSize = queue->queSize; int *res = (int *)calloc(queue->queSize, sizeof(int)); int j = queue->front; for (int i = 0; i < queue->queSize; i++) { res[i] = queue->nums[j % queue->queCapacity]; j++; } return res; } /* Driver Code */ int main() { /* Инициализация очереди */ int capacity = 10; int queSize; ArrayQueue *queue = newArrayQueue(capacity); /* Добавление элемента в очередь */ push(queue, 1); push(queue, 3); push(queue, 2); push(queue, 5); push(queue, 4); printf("Очередь queue = "); printArray(toArray(queue, &queSize), queSize); /* Доступ к элементу в начале очереди */ int peekNum = peek(queue); printf("Элемент в голове peek = %d\r\n", peekNum); /* Извлечение элемента из очереди */ peekNum = pop(queue); printf("Извлечен элемент из очереди pop = %d, очередь после извлечения = ", peekNum); printArray(toArray(queue, &queSize), queSize); /* Получение длины очереди */ int queueSize = size(queue); printf("Длина очереди size = %d\r\n", queueSize); /* Проверка, пуста ли очередь */ bool isEmpty = empty(queue); printf("Пуста ли очередь = %s\r\n", isEmpty ? "true" : "false"); /* Проверка кольцевого массива */ for (int i = 0; i < 10; i++) { push(queue, i); pop(queue); printf("После %d-го цикла enqueue + dequeue queue = ", i); printArray(toArray(queue, &queSize), queSize); } // Освободить память delArrayQueue(queue); return 0; } ================================================ FILE: ru/codes/c/chapter_stack_and_queue/array_stack.c ================================================ /** * File: array_stack.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 5000 /* Стек на основе массива */ typedef struct { int *data; int size; } ArrayStack; /* Конструктор */ ArrayStack *newArrayStack() { ArrayStack *stack = malloc(sizeof(ArrayStack)); // Инициализировать большую вместимость, чтобы избежать расширения stack->data = malloc(sizeof(int) * MAX_SIZE); stack->size = 0; return stack; } /* Деструктор */ void delArrayStack(ArrayStack *stack) { free(stack->data); free(stack); } /* Получение длины стека */ int size(ArrayStack *stack) { return stack->size; } /* Проверка, пуст ли стек */ bool isEmpty(ArrayStack *stack) { return stack->size == 0; } /* Поместить в стек */ void push(ArrayStack *stack, int num) { if (stack->size == MAX_SIZE) { printf("Стек заполнен\n"); return; } stack->data[stack->size] = num; stack->size++; } /* Доступ к верхнему элементу стека */ int peek(ArrayStack *stack) { if (stack->size == 0) { printf("стек пуст\n"); return INT_MAX; } return stack->data[stack->size - 1]; } /* Извлечь из стека */ int pop(ArrayStack *stack) { int val = peek(stack); stack->size--; return val; } /* Driver Code */ int main() { /* Инициализация стека */ ArrayStack *stack = newArrayStack(); /* Помещение элемента в стек */ push(stack, 1); push(stack, 3); push(stack, 2); push(stack, 5); push(stack, 4); printf("Стек stack = "); printArray(stack->data, stack->size); /* Доступ к верхнему элементу стека */ int val = peek(stack); printf("Верхний элемент стека top = %d\n", val); /* Извлечение элемента из стека */ val = pop(stack); printf("Извлечен элемент из стека pop = %d, стек после извлечения = ", val); printArray(stack->data, stack->size); /* Получение длины стека */ int size = stack->size; printf("Длина стека size = %d\n", size); /* Проверка на пустоту */ bool empty = isEmpty(stack); printf("Пуст ли стек = %s\n", empty ? "true" : "false"); // Освободить память delArrayStack(stack); return 0; } ================================================ FILE: ru/codes/c/chapter_stack_and_queue/linkedlist_deque.c ================================================ /** * File: linkedlist_deque.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Узел двусвязного списка */ typedef struct DoublyListNode { int val; // Значение узла struct DoublyListNode *next; // Узел-преемник struct DoublyListNode *prev; // Узел-предшественник } DoublyListNode; /* Конструктор */ DoublyListNode *newDoublyListNode(int num) { DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode)); new->val = num; new->next = NULL; new->prev = NULL; return new; } /* Деструктор */ void delDoublyListNode(DoublyListNode *node) { free(node); } /* Двусторонняя очередь на основе двусвязного списка */ typedef struct { DoublyListNode *front, *rear; // Головной узел front, хвостовой узел rear int queSize; // Длина двусторонней очереди } LinkedListDeque; /* Конструктор */ LinkedListDeque *newLinkedListDeque() { LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque)); deque->front = NULL; deque->rear = NULL; deque->queSize = 0; return deque; } /* Деструктор */ void delLinkedListdeque(LinkedListDeque *deque) { // Освободить все узлы for (int i = 0; i < deque->queSize && deque->front != NULL; i++) { DoublyListNode *tmp = deque->front; deque->front = deque->front->next; free(tmp); } // Освободить структуру deque free(deque); } /* Получение длины очереди */ int size(LinkedListDeque *deque) { return deque->queSize; } /* Проверка, пуста ли очередь */ bool empty(LinkedListDeque *deque) { return (size(deque) == 0); } /* Поместить в очередь */ void push(LinkedListDeque *deque, int num, bool isFront) { DoublyListNode *node = newDoublyListNode(num); // Если связный список пуст, пусть front и rear оба указывают на node if (empty(deque)) { deque->front = deque->rear = node; } // Операция добавления в голову очереди else if (isFront) { // Добавить node в голову списка deque->front->prev = node; node->next = deque->front; deque->front = node; // Обновить головной узел } // Операция добавления в хвост очереди else { // Добавить node в хвост списка deque->rear->next = node; node->prev = deque->rear; deque->rear = node; } deque->queSize++; // Обновить длину очереди } /* Добавление в голову очереди */ void pushFirst(LinkedListDeque *deque, int num) { push(deque, num, true); } /* Добавление в хвост очереди */ void pushLast(LinkedListDeque *deque, int num) { push(deque, num, false); } /* Доступ к элементу в начале очереди */ int peekFirst(LinkedListDeque *deque) { assert(size(deque) && deque->front); return deque->front->val; } /* Доступ к элементу в конце очереди */ int peekLast(LinkedListDeque *deque) { assert(size(deque) && deque->rear); return deque->rear->val; } /* Извлечь из очереди */ int pop(LinkedListDeque *deque, bool isFront) { if (empty(deque)) return -1; int val; // Операция извлечения из головы очереди if (isFront) { val = peekFirst(deque); // Временно сохранить значение головного узла DoublyListNode *fNext = deque->front->next; if (fNext) { fNext->prev = NULL; deque->front->next = NULL; } delDoublyListNode(deque->front); deque->front = fNext; // Обновить головной узел } // Операция извлечения из хвоста очереди else { val = peekLast(deque); // Временно сохранить значение хвостового узла DoublyListNode *rPrev = deque->rear->prev; if (rPrev) { rPrev->next = NULL; deque->rear->prev = NULL; } delDoublyListNode(deque->rear); deque->rear = rPrev; // Обновить хвостовой узел } deque->queSize--; // Обновить длину очереди return val; } /* Извлечение из головы очереди */ int popFirst(LinkedListDeque *deque) { return pop(deque, true); } /* Извлечение из хвоста очереди */ int popLast(LinkedListDeque *deque) { return pop(deque, false); } /* Вывести очередь */ void printLinkedListDeque(LinkedListDeque *deque) { int *arr = malloc(sizeof(int) * deque->queSize); // Скопировать данные связного списка в массив int i; DoublyListNode *node; for (i = 0, node = deque->front; i < deque->queSize; i++) { arr[i] = node->val; node = node->next; } printArray(arr, deque->queSize); free(arr); } /* Driver Code */ int main() { /* Инициализация двусторонней очереди */ LinkedListDeque *deque = newLinkedListDeque(); pushLast(deque, 3); pushLast(deque, 2); pushLast(deque, 5); printf("Дек deque = "); printLinkedListDeque(deque); /* Доступ к элементу */ int peekFirstNum = peekFirst(deque); printf("Элемент в голове peekFirst = %d\r\n", peekFirstNum); int peekLastNum = peekLast(deque); printf("Элемент в хвосте peekLast = %d\r\n", peekLastNum); /* Добавление элемента в очередь */ pushLast(deque, 4); printf("После вставки элемента 4 в хвост дек ="); printLinkedListDeque(deque); pushFirst(deque, 1); printf("После вставки элемента 1 в голову дек ="); printLinkedListDeque(deque); /* Извлечение элемента из очереди */ int popLastNum = popLast(deque); printf("Извлечен элемент из хвоста popLast = %d, дек после извлечения из хвоста = ", popLastNum); printLinkedListDeque(deque); int popFirstNum = popFirst(deque); printf("Извлечен элемент из головы popFirst = %d, дек после извлечения из головы = ", popFirstNum); printLinkedListDeque(deque); /* Получение длины очереди */ int dequeSize = size(deque); printf("Длина дека size = %d\r\n", dequeSize); /* Проверка, пуста ли очередь */ bool isEmpty = empty(deque); printf("Пуст ли дек = %s\r\n", isEmpty ? "true" : "false"); // Освободить память delLinkedListdeque(deque); return 0; } ================================================ FILE: ru/codes/c/chapter_stack_and_queue/linkedlist_queue.c ================================================ /** * File: linkedlist_queue.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Очередь на основе связного списка */ typedef struct { ListNode *front, *rear; int queSize; } LinkedListQueue; /* Конструктор */ LinkedListQueue *newLinkedListQueue() { LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue)); queue->front = NULL; queue->rear = NULL; queue->queSize = 0; return queue; } /* Деструктор */ void delLinkedListQueue(LinkedListQueue *queue) { // Освободить все узлы while (queue->front != NULL) { ListNode *tmp = queue->front; queue->front = queue->front->next; free(tmp); } // Освободить структуру queue free(queue); } /* Получение длины очереди */ int size(LinkedListQueue *queue) { return queue->queSize; } /* Проверка, пуста ли очередь */ bool empty(LinkedListQueue *queue) { return (size(queue) == 0); } /* Поместить в очередь */ void push(LinkedListQueue *queue, int num) { // Добавить node в хвост ListNode *node = newListNode(num); // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел if (queue->front == NULL) { queue->front = node; queue->rear = node; } // Если очередь не пуста, добавить этот узел после хвостового узла else { queue->rear->next = node; queue->rear = node; } queue->queSize++; } /* Доступ к элементу в начале очереди */ int peek(LinkedListQueue *queue) { assert(size(queue) && queue->front); return queue->front->val; } /* Извлечь из очереди */ int pop(LinkedListQueue *queue) { int num = peek(queue); ListNode *tmp = queue->front; queue->front = queue->front->next; free(tmp); queue->queSize--; return num; } /* Вывести очередь */ void printLinkedListQueue(LinkedListQueue *queue) { int *arr = malloc(sizeof(int) * queue->queSize); // Скопировать данные связного списка в массив int i; ListNode *node; for (i = 0, node = queue->front; i < queue->queSize; i++) { arr[i] = node->val; node = node->next; } printArray(arr, queue->queSize); free(arr); } /* Driver Code */ int main() { /* Инициализация очереди */ LinkedListQueue *queue = newLinkedListQueue(); /* Добавление элемента в очередь */ push(queue, 1); push(queue, 3); push(queue, 2); push(queue, 5); push(queue, 4); printf("Очередь queue = "); printLinkedListQueue(queue); /* Доступ к элементу в начале очереди */ int peekNum = peek(queue); printf("Элемент в голове peek = %d\r\n", peekNum); /* Извлечение элемента из очереди */ peekNum = pop(queue); printf("Извлечен элемент из очереди pop = %d, очередь после извлечения = ", peekNum); printLinkedListQueue(queue); /* Получение длины очереди */ int queueSize = size(queue); printf("Длина очереди size = %d\r\n", queueSize); /* Проверка, пуста ли очередь */ bool isEmpty = empty(queue); printf("Пуста ли очередь = %s\r\n", isEmpty ? "true" : "false"); // Освободить память delLinkedListQueue(queue); return 0; } ================================================ FILE: ru/codes/c/chapter_stack_and_queue/linkedlist_stack.c ================================================ /** * File: linkedlist_stack.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* Стек на основе связного списка */ typedef struct { ListNode *top; // Использовать головной узел как вершину стека int size; // Длина стека } LinkedListStack; /* Конструктор */ LinkedListStack *newLinkedListStack() { LinkedListStack *s = malloc(sizeof(LinkedListStack)); s->top = NULL; s->size = 0; return s; } /* Деструктор */ void delLinkedListStack(LinkedListStack *s) { while (s->top) { ListNode *n = s->top->next; free(s->top); s->top = n; } free(s); } /* Получение длины стека */ int size(LinkedListStack *s) { return s->size; } /* Проверка, пуст ли стек */ bool isEmpty(LinkedListStack *s) { return size(s) == 0; } /* Поместить в стек */ void push(LinkedListStack *s, int num) { ListNode *node = (ListNode *)malloc(sizeof(ListNode)); node->next = s->top; // Обновить поле указателя нового узла node->val = num; // Обновить поле данных нового узла s->top = node; // Обновить вершину стека s->size++; // Обновить размер стека } /* Доступ к верхнему элементу стека */ int peek(LinkedListStack *s) { if (s->size == 0) { printf("стек пуст\n"); return INT_MAX; } return s->top->val; } /* Извлечь из стека */ int pop(LinkedListStack *s) { int val = peek(s); ListNode *tmp = s->top; s->top = s->top->next; // Освободить память free(tmp); s->size--; return val; } /* Driver Code */ int main() { /* Инициализация стека */ LinkedListStack *stack = newLinkedListStack(); /* Помещение элемента в стек */ push(stack, 1); push(stack, 3); push(stack, 2); push(stack, 5); push(stack, 4); printf("Стек stack = "); printLinkedList(stack->top); /* Доступ к верхнему элементу стека */ int val = peek(stack); printf("Верхний элемент стека top = %d\r\n", val); /* Извлечение элемента из стека */ val = pop(stack); printf("Извлечен элемент из стека pop = %d, стек после извлечения = ", val); printLinkedList(stack->top); /* Получение длины стека */ printf("Длина стека size = %d\n", size(stack)); /* Проверка на пустоту */ bool empty = isEmpty(stack); printf("Пуст ли стек = %s\n", empty ? "true" : "false"); // Освободить память delLinkedListStack(stack); return 0; } ================================================ FILE: ru/codes/c/chapter_tree/CMakeLists.txt ================================================ add_executable(avl_tree avl_tree.c) add_executable(binary_tree binary_tree.c) add_executable(binary_tree_bfs binary_tree_bfs.c) add_executable(binary_tree_dfs binary_tree_dfs.c) add_executable(binary_search_tree binary_search_tree.c) add_executable(array_binary_tree array_binary_tree.c) ================================================ FILE: ru/codes/c/chapter_tree/array_binary_tree.c ================================================ /** * File: array_binary_tree.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* Структура двоичного дерева в представлении массивом */ typedef struct { int *tree; int size; } ArrayBinaryTree; /* Конструктор */ ArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) { ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree)); abt->tree = malloc(sizeof(int) * arrSize); memcpy(abt->tree, arr, sizeof(int) * arrSize); abt->size = arrSize; return abt; } /* Деструктор */ void delArrayBinaryTree(ArrayBinaryTree *abt) { free(abt->tree); free(abt); } /* Вместимость списка */ int size(ArrayBinaryTree *abt) { return abt->size; } /* Получить значение узла с индексом i */ int val(ArrayBinaryTree *abt, int i) { // Если индекс выходит за границы, вернуть INT_MAX, обозначающий пустую позицию if (i < 0 || i >= size(abt)) return INT_MAX; return abt->tree[i]; } /* Получить индекс левого дочернего узла узла с индексом i */ int left(int i) { return 2 * i + 1; } /* Получить индекс правого дочернего узла узла с индексом i */ int right(int i) { return 2 * i + 2; } /* Получить индекс родительского узла узла с индексом i */ int parent(int i) { return (i - 1) / 2; } /* Обход в ширину */ int *levelOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; // Непосредственно обходить массив for (int i = 0; i < size(abt); i++) { if (val(abt, i) != INT_MAX) res[index++] = val(abt, i); } *returnSize = index; return res; } /* Обход в глубину */ void dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) { // Если это пустая позиция, вернуть if (val(abt, i) == INT_MAX) return; // Предварительный обход if (strcmp(order, "pre") == 0) res[(*index)++] = val(abt, i); dfs(abt, left(i), order, res, index); // Симметричный обход if (strcmp(order, "in") == 0) res[(*index)++] = val(abt, i); dfs(abt, right(i), order, res, index); // Обратный обход if (strcmp(order, "post") == 0) res[(*index)++] = val(abt, i); } /* Предварительный обход */ int *preOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "pre", res, &index); *returnSize = index; return res; } /* Симметричный обход */ int *inOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "in", res, &index); *returnSize = index; return res; } /* Обратный обход */ int *postOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "post", res, &index); *returnSize = index; return res; } /* Driver Code */ int main() { // Инициализировать двоичное дерево // Использовать INT_MAX для обозначения пустой позиции NULL int arr[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; int arrSize = sizeof(arr) / sizeof(arr[0]); TreeNode *root = arrayToTree(arr, arrSize); printf("\nИнициализация двоичного дерева\n"); printf("Массивное представление двоичного дерева:\n"); printArray(arr, arrSize); printf("Связное представление двоичного дерева:\n"); printTree(root); ArrayBinaryTree *abt = newArrayBinaryTree(arr, arrSize); // Доступ к узлу int i = 1; int l = left(i), r = right(i), p = parent(i); printf("\nТекущий индекс узла = %d, значение = %d\n", i, val(abt, i)); printf("Индекс левого дочернего узла = %d, значение = %d\n", l, l < arrSize ? val(abt, l) : INT_MAX); printf("Индекс правого дочернего узла = %d, значение = %d\n", r, r < arrSize ? val(abt, r) : INT_MAX); printf("Индекс родительского узла = %d, значение = %d\n", p, p < arrSize ? val(abt, p) : INT_MAX); // Обходить дерево int returnSize; int *res; res = levelOrder(abt, &returnSize); printf("\nОбход по уровням: "); printArray(res, returnSize); free(res); res = preOrder(abt, &returnSize); printf("Предварительный обход: "); printArray(res, returnSize); free(res); res = inOrder(abt, &returnSize); printf("Симметричный обход: "); printArray(res, returnSize); free(res); res = postOrder(abt, &returnSize); printf("Обратный обход: "); printArray(res, returnSize); free(res); // Освободить память delArrayBinaryTree(abt); return 0; } ================================================ FILE: ru/codes/c/chapter_tree/avl_tree.c ================================================ /** * File: avl_tree.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Структура AVL-дерева */ typedef struct { TreeNode *root; } AVLTree; /* Конструктор */ AVLTree *newAVLTree() { AVLTree *tree = (AVLTree *)malloc(sizeof(AVLTree)); tree->root = NULL; return tree; } /* Деструктор */ void delAVLTree(AVLTree *tree) { freeMemoryTree(tree->root); free(tree); } /* Получить высоту узла */ int height(TreeNode *node) { // Высота пустого узла равна -1, высота листового узла равна 0 if (node != NULL) { return node->height; } return -1; } /* Обновить высоту узла */ void updateHeight(TreeNode *node) { int lh = height(node->left); int rh = height(node->right); // Высота узла равна высоте более высокого поддерева + 1 if (lh > rh) { node->height = lh + 1; } else { node->height = rh + 1; } } /* Получить коэффициент баланса */ int balanceFactor(TreeNode *node) { // Коэффициент баланса пустого узла равен 0 if (node == NULL) { return 0; } // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева return height(node->left) - height(node->right); } /* Операция правого вращения */ TreeNode *rightRotate(TreeNode *node) { TreeNode *child, *grandChild; child = node->left; grandChild = child->right; // Выполнить правое вращение узла node вокруг child child->right = node; node->left = grandChild; // Обновить высоту узла updateHeight(node); updateHeight(child); // Вернуть корневой узел поддерева после вращения return child; } /* Операция левого вращения */ TreeNode *leftRotate(TreeNode *node) { TreeNode *child, *grandChild; child = node->right; grandChild = child->left; // Выполнить левое вращение узла node вокруг child child->left = node; node->right = grandChild; // Обновить высоту узла updateHeight(node); updateHeight(child); // Вернуть корневой узел поддерева после вращения return child; } /* Выполнить вращение, чтобы снова сбалансировать поддерево */ TreeNode *rotate(TreeNode *node) { // Получить коэффициент баланса узла node int bf = balanceFactor(node); // Левосторонне перекошенное дерево if (bf > 1) { if (balanceFactor(node->left) >= 0) { // Правое вращение return rightRotate(node); } else { // Сначала левое вращение, затем правое node->left = leftRotate(node->left); return rightRotate(node); } } // Правосторонне перекошенное дерево if (bf < -1) { if (balanceFactor(node->right) <= 0) { // Левое вращение return leftRotate(node); } else { // Сначала правое вращение, затем левое node->right = rightRotate(node->right); return leftRotate(node); } } // Дерево сбалансировано, вращение не требуется, вернуть сразу return node; } /* Рекурсивная вставка узла (вспомогательная функция) */ TreeNode *insertHelper(TreeNode *node, int val) { if (node == NULL) { return newTreeNode(val); } /* 1. Найти позицию вставки и вставить узел */ if (val < node->val) { node->left = insertHelper(node->left, val); } else if (val > node->val) { node->right = insertHelper(node->right, val); } else { // Повторяющийся узел не вставлять, сразу вернуть return node; } // Обновить высоту узла updateHeight(node); /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = rotate(node); // Вернуть корневой узел поддерева return node; } /* Вставка узла */ void insert(AVLTree *tree, int val) { tree->root = insertHelper(tree->root, val); } /* Рекурсивное удаление узла (вспомогательная функция) */ TreeNode *removeHelper(TreeNode *node, int val) { TreeNode *child, *grandChild; if (node == NULL) { return NULL; } /* 1. Найти узел и удалить его */ if (val < node->val) { node->left = removeHelper(node->left, val); } else if (val > node->val) { node->right = removeHelper(node->right, val); } else { if (node->left == NULL || node->right == NULL) { child = node->left; if (node->right != NULL) { child = node->right; } // Число дочерних узлов = 0, удалить node и сразу вернуть if (child == NULL) { return NULL; } else { // Число дочерних узлов = 1, удалить node напрямую node = child; } } else { // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел TreeNode *temp = node->right; while (temp->left != NULL) { temp = temp->left; } int tempVal = temp->val; node->right = removeHelper(node->right, temp->val); node->val = tempVal; } } // Обновить высоту узла updateHeight(node); /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = rotate(node); // Вернуть корневой узел поддерева return node; } /* Удаление узла */ // Из-за подключения stdio.h здесь нельзя использовать ключевое слово remove void removeItem(AVLTree *tree, int val) { TreeNode *root = removeHelper(tree->root, val); } /* Поиск узла */ TreeNode *search(AVLTree *tree, int val) { TreeNode *cur = tree->root; // Искать в цикле и выйти после прохода за листовой узел while (cur != NULL) { if (cur->val < val) { // Целевой узел находится в правом поддереве cur cur = cur->right; } else if (cur->val > val) { // Целевой узел находится в левом поддереве cur cur = cur->left; } else { // Найти целевой узел и выйти из цикла break; } } // Найти целевой узел и выйти из цикла return cur; } void testInsert(AVLTree *tree, int val) { insert(tree, val); printf("\nПосле вставки узла %d AVL-дерево имеет вид \n", val); printTree(tree->root); } void testRemove(AVLTree *tree, int val) { removeItem(tree, val); printf("\nПосле удаления узла %d AVL-дерево имеет вид \n", val); printTree(tree->root); } /* Driver Code */ int main() { /* Инициализация пустого AVL-дерева */ AVLTree *tree = (AVLTree *)newAVLTree(); /* Вставка узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла testInsert(tree, 1); testInsert(tree, 2); testInsert(tree, 3); testInsert(tree, 4); testInsert(tree, 5); testInsert(tree, 8); testInsert(tree, 7); testInsert(tree, 9); testInsert(tree, 10); testInsert(tree, 6); /* Вставка повторяющегося узла */ testInsert(tree, 7); /* Удаление узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла testRemove(tree, 8); // Удаление узла степени 0 testRemove(tree, 5); // Удаление узла степени 1 testRemove(tree, 4); // Удаление узла степени 2 /* Поиск узла */ TreeNode *node = search(tree, 7); printf("\nНайденный объект узла, значение узла = %d \n", node->val); // Освободить память delAVLTree(tree); return 0; } ================================================ FILE: ru/codes/c/chapter_tree/binary_search_tree.c ================================================ /** * File: binary_search_tree.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Структура двоичного дерева поиска */ typedef struct { TreeNode *root; } BinarySearchTree; /* Конструктор */ BinarySearchTree *newBinarySearchTree() { // Инициализировать пустое дерево BinarySearchTree *bst = (BinarySearchTree *)malloc(sizeof(BinarySearchTree)); bst->root = NULL; return bst; } /* Деструктор */ void delBinarySearchTree(BinarySearchTree *bst) { freeMemoryTree(bst->root); free(bst); } /* Получить корневой узел двоичного дерева */ TreeNode *getRoot(BinarySearchTree *bst) { return bst->root; } /* Поиск узла */ TreeNode *search(BinarySearchTree *bst, int num) { TreeNode *cur = bst->root; // Искать в цикле и выйти после прохода за листовой узел while (cur != NULL) { if (cur->val < num) { // Целевой узел находится в правом поддереве cur cur = cur->right; } else if (cur->val > num) { // Целевой узел находится в левом поддереве cur cur = cur->left; } else { // Найти целевой узел и выйти из цикла break; } } // Вернуть целевой узел return cur; } /* Вставка узла */ void insert(BinarySearchTree *bst, int num) { // Если дерево пусто, инициализировать корневой узел if (bst->root == NULL) { bst->root = newTreeNode(num); return; } TreeNode *cur = bst->root, *pre = NULL; // Искать в цикле и выйти после прохода за листовой узел while (cur != NULL) { // Найти повторяющийся узел и сразу вернуть if (cur->val == num) { return; } pre = cur; if (cur->val < num) { // Позиция вставки находится в правом поддереве cur cur = cur->right; } else { // Позиция вставки находится в левом поддереве cur cur = cur->left; } } // Вставка узла TreeNode *node = newTreeNode(num); if (pre->val < num) { pre->right = node; } else { pre->left = node; } } /* Удаление узла */ // Из-за подключения stdio.h здесь нельзя использовать ключевое слово remove void removeItem(BinarySearchTree *bst, int num) { // Если дерево пусто, сразу вернуть if (bst->root == NULL) return; TreeNode *cur = bst->root, *pre = NULL; // Искать в цикле и выйти после прохода за листовой узел while (cur != NULL) { // Найти узел для удаления и выйти из цикла if (cur->val == num) break; pre = cur; if (cur->val < num) { // Удаляемый узел находится в правом поддереве root cur = cur->right; } else { // Удаляемый узел находится в левом поддереве root cur = cur->left; } } // Если узел для удаления отсутствует, сразу вернуть if (cur == NULL) return; // Проверить, есть ли дочерние узлы у удаляемого узла if (cur->left == NULL || cur->right == NULL) { /* Число дочерних узлов = 0 или 1 */ // Когда число дочерних узлов = 0 / 1, child = nullptr / этот дочерний узел TreeNode *child = cur->left != NULL ? cur->left : cur->right; // Удалить узел cur if (pre->left == cur) { pre->left = child; } else { pre->right = child; } // Освободить память free(cur); } else { /* Число дочерних узлов = 2 */ // Получить следующий узел после cur в симметричном обходе TreeNode *tmp = cur->right; while (tmp->left != NULL) { tmp = tmp->left; } int tmpVal = tmp->val; // Рекурсивно удалить узел tmp removeItem(bst, tmp->val); // Перезаписать cur значением tmp cur->val = tmpVal; } } /* Driver Code */ int main() { /* Инициализация двоичного дерева поиска */ int nums[] = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; BinarySearchTree *bst = newBinarySearchTree(); for (int i = 0; i < sizeof(nums) / sizeof(int); i++) { insert(bst, nums[i]); } printf("Инициализированное двоичное дерево\n"); printTree(getRoot(bst)); /* Поиск узла */ TreeNode *node = search(bst, 7); printf("Значение найденного объекта узла = %d\n", node->val); /* Вставка узла */ insert(bst, 16); printf("После вставки узла 16 двоичное дерево имеет вид\n"); printTree(getRoot(bst)); /* Удаление узла */ removeItem(bst, 1); printf("После удаления узла 1 двоичное дерево имеет вид\n"); printTree(getRoot(bst)); removeItem(bst, 2); printf("После удаления узла 2 двоичное дерево имеет вид\n"); printTree(getRoot(bst)); removeItem(bst, 4); printf("После удаления узла 4 двоичное дерево имеет вид\n"); printTree(getRoot(bst)); // Освободить память delBinarySearchTree(bst); return 0; } ================================================ FILE: ru/codes/c/chapter_tree/binary_tree.c ================================================ /** * File: binary_tree.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Driver Code */ int main() { /* Инициализация двоичного дерева */ // Инициализация узла TreeNode *n1 = newTreeNode(1); TreeNode *n2 = newTreeNode(2); TreeNode *n3 = newTreeNode(3); TreeNode *n4 = newTreeNode(4); TreeNode *n5 = newTreeNode(5); // Построить связи между узлами (указатели) n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; printf("Инициализация двоичного дерева\n"); printTree(n1); /* Вставка и удаление узлов */ TreeNode *P = newTreeNode(0); // Вставить узел P между n1 -> n2 n1->left = P; P->left = n2; printf("После вставки узла P\n"); printTree(n1); // Удалить узел P n1->left = n2; // Освободить память free(P); printf("После удаления узла P\n"); printTree(n1); freeMemoryTree(n1); return 0; } ================================================ FILE: ru/codes/c/chapter_tree/binary_tree_bfs.c ================================================ /** * File: binary_tree_bfs.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 100 /* Обход в ширину */ int *levelOrder(TreeNode *root, int *size) { /* Вспомогательная очередь */ int front, rear; int index, *arr; TreeNode *node; TreeNode **queue; /* Вспомогательная очередь */ queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE); // Указатель очереди front = 0, rear = 0; // Добавить корневой узел queue[rear++] = root; // Инициализировать список для хранения последовательности обхода /* Вспомогательный массив */ arr = (int *)malloc(sizeof(int) * MAX_SIZE); // Указатель на массив index = 0; while (front < rear) { // Извлечение из очереди node = queue[front++]; // Сохранить значение узла arr[index++] = node->val; if (node->left != NULL) { // Поместить левый дочерний узел в очередь queue[rear++] = node->left; } if (node->right != NULL) { // Поместить правый дочерний узел в очередь queue[rear++] = node->right; } } // Обновить значение длины массива *size = index; arr = realloc(arr, sizeof(int) * (*size)); // Освободить память вспомогательного массива free(queue); return arr; } /* Driver Code */ int main() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива int nums[] = {1, 2, 3, 4, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); printf("Инициализация двоичного дерева\n"); printTree(root); /* Обход в ширину */ // Нужно передать длину массива int *arr = levelOrder(root, &size); printf("Последовательность узлов при обходе по уровням = "); printArray(arr, size); // Освободить память freeMemoryTree(root); free(arr); return 0; } ================================================ FILE: ru/codes/c/chapter_tree/binary_tree_dfs.c ================================================ /** * File: binary_tree_dfs.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 100 // Вспомогательный массив для хранения последовательности обхода int arr[MAX_SIZE]; /* Предварительный обход */ void preOrder(TreeNode *root, int *size) { if (root == NULL) return; // Порядок обхода: корень -> левое поддерево -> правое поддерево arr[(*size)++] = root->val; preOrder(root->left, size); preOrder(root->right, size); } /* Симметричный обход */ void inOrder(TreeNode *root, int *size) { if (root == NULL) return; // Порядок обхода: левое поддерево -> корень -> правое поддерево inOrder(root->left, size); arr[(*size)++] = root->val; inOrder(root->right, size); } /* Обратный обход */ void postOrder(TreeNode *root, int *size) { if (root == NULL) return; // Порядок обхода: левое поддерево -> правое поддерево -> корень postOrder(root->left, size); postOrder(root->right, size); arr[(*size)++] = root->val; } /* Driver Code */ int main() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива int nums[] = {1, 2, 3, 4, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); printf("Инициализация двоичного дерева\n"); printTree(root); /* Предварительный обход */ // Инициализация вспомогательного массива size = 0; preOrder(root, &size); printf("Последовательность узлов при предварительном обходе = "); printArray(arr, size); /* Симметричный обход */ size = 0; inOrder(root, &size); printf("Последовательность узлов при симметричном обходе = "); printArray(arr, size); /* Обратный обход */ size = 0; postOrder(root, &size); printf("Последовательность узлов при обратном обходе = "); printArray(arr, size); freeMemoryTree(root); return 0; } ================================================ FILE: ru/codes/c/utils/CMakeLists.txt ================================================ add_executable(utils common_test.c common.h print_util.h list_node.h tree_node.h uthash.h) ================================================ FILE: ru/codes/c/utils/common.h ================================================ /** * File: common.h * Created Time: 2022-12-20 * Author: MolDuM (moldum@163.com)、Reanon (793584285@qq.com) */ #ifndef COMMON_H #define COMMON_H #include #include #include #include #include #include #include #include "list_node.h" #include "print_util.h" #include "tree_node.h" #include "vertex.h" // hash table lib #include "uthash.h" #include "vector.h" #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif #endif // COMMON_H ================================================ FILE: ru/codes/c/utils/common_test.c ================================================ /** * File: include_test.c * Created Time: 2023-01-10 * Author: Reanon (793584285@qq.com) */ #include "common.h" void testListNode() { int nums[] = {2, 3, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); ListNode *head = arrToLinkedList(nums, size); printLinkedList(head); } void testTreeNode() { int nums[] = {1, 2, 3, INT_MAX, 5, 6, INT_MAX}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); // print tree printTree(root); // tree to arr int *arr = treeToArray(root, &size); printArray(arr, size); } int main(int argc, char *argv[]) { printf("==testListNode==\n"); testListNode(); printf("==testTreeNode==\n"); testTreeNode(); return 0; } ================================================ FILE: ru/codes/c/utils/list_node.h ================================================ /** * File: list_node.h * Created Time: 2023-01-09 * Author: Reanon (793584285@qq.com) */ #ifndef LIST_NODE_H #define LIST_NODE_H #ifdef __cplusplus extern "C" { #endif /* Структура узла связного списка */ typedef struct ListNode { int val; // Значение узла struct ListNode *next; // Ссылка на следующий узел } ListNode; /* Конструктор, инициализирующий новый узел */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *)malloc(sizeof(ListNode)); node->val = val; node->next = NULL; return node; } /* Десериализовать массив в связный список */ ListNode *arrToLinkedList(const int *arr, size_t size) { if (size <= 0) { return NULL; } ListNode *dummy = newListNode(0); ListNode *node = dummy; for (int i = 0; i < size; i++) { node->next = newListNode(arr[i]); node = node->next; } return dummy->next; } /* Освободить память, выделенную под связный список */ void freeMemoryLinkedList(ListNode *cur) { // Освободить память ListNode *pre; while (cur != NULL) { pre = cur; cur = cur->next; free(pre); } } #ifdef __cplusplus } #endif #endif // LIST_NODE_H ================================================ FILE: ru/codes/c/utils/print_util.h ================================================ /** * File: print_util.h * Created Time: 2022-12-21 * Author: MolDum (moldum@163.com), Reanon (793584285@qq.com) */ #ifndef PRINT_UTIL_H #define PRINT_UTIL_H #include #include #include #include "list_node.h" #include "tree_node.h" #ifdef __cplusplus extern "C" { #endif /* Вывести массив */ void printArray(int arr[], int size) { if (arr == NULL || size == 0) { printf("[]"); return; } printf("["); for (int i = 0; i < size - 1; i++) { printf("%d, ", arr[i]); } printf("%d]\n", arr[size - 1]); } /* Вывести массив */ void printArrayFloat(float arr[], int size) { if (arr == NULL || size == 0) { printf("[]"); return; } printf("["); for (int i = 0; i < size - 1; i++) { printf("%.2f, ", arr[i]); } printf("%.2f]\n", arr[size - 1]); } /* Вывести связный список */ void printLinkedList(ListNode *node) { if (node == NULL) { return; } while (node->next != NULL) { printf("%d -> ", node->val); node = node->next; } printf("%d\n", node->val); } typedef struct Trunk { struct Trunk *prev; char *str; } Trunk; Trunk *newTrunk(Trunk *prev, char *str) { Trunk *trunk = (Trunk *)malloc(sizeof(Trunk)); trunk->prev = prev; trunk->str = (char *)malloc(sizeof(char) * 10); strcpy(trunk->str, str); return trunk; } void showTrunks(Trunk *trunk) { if (trunk == NULL) { return; } showTrunks(trunk->prev); printf("%s", trunk->str); } /** * Вывести двоичное дерево * Этот вывод дерева заимствован из TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTreeHelper(TreeNode *node, Trunk *prev, bool isRight) { if (node == NULL) { return; } char *prev_str = " "; Trunk *trunk = newTrunk(prev, prev_str); printTreeHelper(node->right, trunk, true); if (prev == NULL) { trunk->str = "———"; } else if (isRight) { trunk->str = "/———"; prev_str = " |"; } else { trunk->str = "\\———"; prev->str = prev_str; } showTrunks(trunk); printf("%d\n", node->val); if (prev != NULL) { prev->str = prev_str; } trunk->str = " |"; printTreeHelper(node->left, trunk, false); } /* Вывести двоичное дерево */ void printTree(TreeNode *root) { printTreeHelper(root, NULL, false); } /* Вывести кучу */ void printHeap(int arr[], int size) { TreeNode *root; printf("Массивное представление кучи:"); printArray(arr, size); printf("Древовидное представление кучи:\n"); root = arrayToTree(arr, size); printTree(root); } #ifdef __cplusplus } #endif #endif // PRINT_UTIL_H ================================================ FILE: ru/codes/c/utils/tree_node.h ================================================ /** * File: tree_node.h * Created Time: 2023-01-09 * Author: Reanon (793584285@qq.com) */ #ifndef TREE_NODE_H #define TREE_NODE_H #ifdef __cplusplus extern "C" { #endif #include #define MAX_NODE_SIZE 5000 /* Структура узла двоичного дерева */ typedef struct TreeNode { int val; // Значение узла int height; // Высота узла struct TreeNode *left; // Указатель на левый дочерний узел struct TreeNode *right; // Указатель на правый дочерний узел } TreeNode; /* Конструктор */ TreeNode *newTreeNode(int val) { TreeNode *node; node = (TreeNode *)malloc(sizeof(TreeNode)); node->val = val; node->height = 0; node->left = NULL; node->right = NULL; return node; } // Правила кодирования сериализации см.: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Массивное представление двоичного дерева: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // Связное представление двоичного дерева: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* Десериализовать список в двоичное дерево: рекурсия */ TreeNode *arrayToTreeDFS(int *arr, int size, int i) { if (i < 0 || i >= size || arr[i] == INT_MAX) { return NULL; } TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); root->val = arr[i]; root->left = arrayToTreeDFS(arr, size, 2 * i + 1); root->right = arrayToTreeDFS(arr, size, 2 * i + 2); return root; } /* Десериализовать список в двоичное дерево */ TreeNode *arrayToTree(int *arr, int size) { return arrayToTreeDFS(arr, size, 0); } /* Сериализовать двоичное дерево в список: рекурсия */ void treeToArrayDFS(TreeNode *root, int i, int *res, int *size) { if (root == NULL) { return; } while (i >= *size) { res = realloc(res, (*size + 1) * sizeof(int)); res[*size] = INT_MAX; (*size)++; } res[i] = root->val; treeToArrayDFS(root->left, 2 * i + 1, res, size); treeToArrayDFS(root->right, 2 * i + 2, res, size); } /* Сериализовать двоичное дерево в список */ int *treeToArray(TreeNode *root, int *size) { *size = 0; int *res = NULL; treeToArrayDFS(root, 0, res, size); return res; } /* Освободить память двоичного дерева */ void freeMemoryTree(TreeNode *root) { if (root == NULL) return; freeMemoryTree(root->left); freeMemoryTree(root->right); free(root); } #ifdef __cplusplus } #endif #endif // TREE_NODE_H ================================================ FILE: ru/codes/c/utils/uthash.h ================================================ /* Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTHASH_H #define UTHASH_H #define UTHASH_VERSION 2.3.0 #include /* memcmp, memset, strlen */ #include /* ptrdiff_t */ #include /* exit */ #if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT /* This codepath is provided for backward compatibility, but I plan to remove it. */ #warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" typedef unsigned int uint32_t; typedef unsigned char uint8_t; #elif defined(HASH_NO_STDINT) && HASH_NO_STDINT #else #include /* uint8_t, uint32_t */ #endif /* These macros use decltype or the earlier __typeof GNU extension. As decltype is only available in newer compilers (VS2010 or gcc 4.3+ when compiling c++ source) this code uses whatever method is needed or, for VS2008 where neither is available, uses casting workarounds. */ #if !defined(DECLTYPE) && !defined(NO_DECLTYPE) #if defined(_MSC_VER) /* MS compiler */ #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ #define DECLTYPE(x) (decltype(x)) #else /* VS2008 or older (or VS2010 in C mode) */ #define NO_DECLTYPE #endif #elif defined(__MCST__) /* Elbrus C Compiler */ #define DECLTYPE(x) (__typeof(x)) #elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) #define NO_DECLTYPE #else /* GNU, Sun and other compilers */ #define DECLTYPE(x) (__typeof(x)) #endif #endif #ifdef NO_DECLTYPE #define DECLTYPE(x) #define DECLTYPE_ASSIGN(dst,src) \ do { \ char **_da_dst = (char**)(&(dst)); \ *_da_dst = (char*)(src); \ } while (0) #else #define DECLTYPE_ASSIGN(dst,src) \ do { \ (dst) = DECLTYPE(dst)(src); \ } while (0) #endif #ifndef uthash_malloc #define uthash_malloc(sz) malloc(sz) /* malloc fcn */ #endif #ifndef uthash_free #define uthash_free(ptr,sz) free(ptr) /* free fcn */ #endif #ifndef uthash_bzero #define uthash_bzero(a,n) memset(a,'\0',n) #endif #ifndef uthash_strlen #define uthash_strlen(s) strlen(s) #endif #ifndef HASH_FUNCTION #define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) #endif #ifndef HASH_KEYCMP #define HASH_KEYCMP(a,b,n) memcmp(a,b,n) #endif #ifndef uthash_noexpand_fyi #define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ #endif #ifndef uthash_expand_fyi #define uthash_expand_fyi(tbl) /* can be defined to log expands */ #endif #ifndef HASH_NONFATAL_OOM #define HASH_NONFATAL_OOM 0 #endif #if HASH_NONFATAL_OOM /* malloc failures can be recovered from */ #ifndef uthash_nonfatal_oom #define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) #define IF_HASH_NONFATAL_OOM(x) x #else /* malloc failures result in lost memory, hash tables are unusable */ #ifndef uthash_fatal #define uthash_fatal(msg) exit(-1) /* fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") #define IF_HASH_NONFATAL_OOM(x) #endif /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ #define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ /* calculate the element whose hash handle address is hhp */ #define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) /* calculate the hash handle from element address elp */ #define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) #define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ do { \ struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ unsigned _hd_bkt; \ HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ (head)->hh.tbl->buckets[_hd_bkt].count++; \ _hd_hh_item->hh_next = NULL; \ _hd_hh_item->hh_prev = NULL; \ } while (0) #define HASH_VALUE(keyptr,keylen,hashv) \ do { \ HASH_FUNCTION(keyptr, keylen, hashv); \ } while (0) #define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_bkt; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ } \ } \ } while (0) #define HASH_FIND(hh,head,keyptr,keylen,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_hashv; \ HASH_VALUE(keyptr, keylen, _hf_hashv); \ HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ } \ } while (0) #ifdef HASH_BLOOM #define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) #define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) #define HASH_BLOOM_MAKE(tbl,oomed) \ do { \ (tbl)->bloom_nbits = HASH_BLOOM; \ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ if (!(tbl)->bloom_bv) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ } \ } while (0) #define HASH_BLOOM_FREE(tbl) \ do { \ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ } while (0) #define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) #define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) #define HASH_BLOOM_ADD(tbl,hashv) \ HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #define HASH_BLOOM_TEST(tbl,hashv) \ HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #else #define HASH_BLOOM_MAKE(tbl,oomed) #define HASH_BLOOM_FREE(tbl) #define HASH_BLOOM_ADD(tbl,hashv) #define HASH_BLOOM_TEST(tbl,hashv) (1) #define HASH_BLOOM_BYTELEN 0U #endif #define HASH_MAKE_TABLE(hh,head,oomed) \ do { \ (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ if (!(head)->hh.tbl) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ (head)->hh.tbl->tail = &((head)->hh); \ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ (head)->hh.tbl->signature = HASH_SIGNATURE; \ if (!(head)->hh.tbl->buckets) { \ HASH_RECORD_OOM(oomed); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } else { \ uthash_bzero((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ uthash_free((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } \ ) \ } \ } \ } while (0) #define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ } while (0) #define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ } while (0) #define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ } while (0) #define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ } while (0) #define HASH_APPEND_LIST(hh, head, add) \ do { \ (add)->hh.next = NULL; \ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ (head)->hh.tbl->tail->next = (add); \ (head)->hh.tbl->tail = &((add)->hh); \ } while (0) #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ do { \ if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ break; \ } \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #ifdef NO_DECLTYPE #undef HASH_AKBI_INNER_LOOP #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ char *_hs_saved_head = (char*)(head); \ do { \ DECLTYPE_ASSIGN(head, _hs_iter); \ if (cmpfcn(head, add) > 0) { \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ break; \ } \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #endif #if HASH_NONFATAL_OOM #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ if (!(oomed)) { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ if (oomed) { \ HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ HASH_DELETE_HH(hh, head, &(add)->hh); \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } else { \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } \ } else { \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } \ } while (0) #else #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } while (0) #endif #define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (char*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ void *_hs_iter = (head); \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ if (_hs_iter) { \ (add)->hh.next = _hs_iter; \ if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ } else { \ (head) = (add); \ } \ HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ } else { \ HASH_APPEND_LIST(hh, head, add); \ } \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ } while (0) #define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ do { \ unsigned _hs_hashv; \ HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ } while (0) #define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) #define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) #define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (const void*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_APPEND_LIST(hh, head, add); \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ } while (0) #define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ do { \ unsigned _ha_hashv; \ HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ } while (0) #define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) #define HASH_ADD(hh,head,fieldname,keylen_in,add) \ HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) #define HASH_TO_BKT(hashv,num_bkts,bkt) \ do { \ bkt = ((hashv) & ((num_bkts) - 1U)); \ } while (0) /* delete "delptr" from the hash table. * "the usual" patch-up process for the app-order doubly-linked-list. * The use of _hd_hh_del below deserves special explanation. * These used to be expressed using (delptr) but that led to a bug * if someone used the same symbol for the head and deletee, like * HASH_DELETE(hh,users,users); * We want that to work, but by changing the head (users) below * we were forfeiting our ability to further refer to the deletee (users) * in the patch-up process. Solution: use scratch space to * copy the deletee pointer, then the latter references are via that * scratch pointer rather than through the repointed (users) symbol. */ #define HASH_DELETE(hh,head,delptr) \ HASH_DELETE_HH(hh, head, &(delptr)->hh) #define HASH_DELETE_HH(hh,head,delptrhh) \ do { \ const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } else { \ unsigned _hd_bkt; \ if (_hd_hh_del == (head)->hh.tbl->tail) { \ (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ } \ if (_hd_hh_del->prev != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ } else { \ DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ } \ if (_hd_hh_del->next != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ } \ HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ (head)->hh.tbl->num_items--; \ } \ HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ } while (0) /* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ #define HASH_FIND_STR(head,findstr,out) \ do { \ unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ } while (0) #define HASH_ADD_STR(head,strfield,add) \ do { \ unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ } while (0) #define HASH_REPLACE_STR(head,strfield,add,replaced) \ do { \ unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ } while (0) #define HASH_FIND_INT(head,findint,out) \ HASH_FIND(hh,head,findint,sizeof(int),out) #define HASH_ADD_INT(head,intfield,add) \ HASH_ADD(hh,head,intfield,sizeof(int),add) #define HASH_REPLACE_INT(head,intfield,add,replaced) \ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) #define HASH_FIND_PTR(head,findptr,out) \ HASH_FIND(hh,head,findptr,sizeof(void *),out) #define HASH_ADD_PTR(head,ptrfield,add) \ HASH_ADD(hh,head,ptrfield,sizeof(void *),add) #define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) #define HASH_DEL(head,delptr) \ HASH_DELETE(hh,head,delptr) /* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. */ #ifdef HASH_DEBUG #include /* fprintf, stderr */ #define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) #define HASH_FSCK(hh,head,where) \ do { \ struct UT_hash_handle *_thh; \ if (head) { \ unsigned _bkt_i; \ unsigned _count = 0; \ char *_prev; \ for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ unsigned _bkt_count = 0; \ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ _prev = NULL; \ while (_thh) { \ if (_prev != (char*)(_thh->hh_prev)) { \ HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ (where), (void*)_thh->hh_prev, (void*)_prev); \ } \ _bkt_count++; \ _prev = (char*)(_thh); \ _thh = _thh->hh_next; \ } \ _count += _bkt_count; \ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ } \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ _count = 0; \ _prev = NULL; \ _thh = &(head)->hh; \ while (_thh) { \ _count++; \ if (_prev != (char*)_thh->prev) { \ HASH_OOPS("%s: invalid prev %p, actual %p\n", \ (where), (void*)_thh->prev, (void*)_prev); \ } \ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ } \ } while (0) #else #define HASH_FSCK(hh,head,where) #endif /* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to * the descriptor to which this macro is defined for tuning the hash function. * The app can #include to get the prototype for write(2). */ #ifdef HASH_EMIT_KEYS #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ do { \ unsigned _klen = fieldlen; \ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ } while (0) #else #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) #endif /* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ #define HASH_BER(key,keylen,hashv) \ do { \ unsigned _hb_keylen = (unsigned)keylen; \ const unsigned char *_hb_key = (const unsigned char*)(key); \ (hashv) = 0; \ while (_hb_keylen-- != 0U) { \ (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ } \ } while (0) /* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx * (archive link: https://archive.is/Ivcan ) */ #define HASH_SAX(key,keylen,hashv) \ do { \ unsigned _sx_i; \ const unsigned char *_hs_key = (const unsigned char*)(key); \ hashv = 0; \ for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ } \ } while (0) /* FNV-1a variation */ #define HASH_FNV(key,keylen,hashv) \ do { \ unsigned _fn_i; \ const unsigned char *_hf_key = (const unsigned char*)(key); \ (hashv) = 2166136261U; \ for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ hashv = hashv ^ _hf_key[_fn_i]; \ hashv = hashv * 16777619U; \ } \ } while (0) #define HASH_OAT(key,keylen,hashv) \ do { \ unsigned _ho_i; \ const unsigned char *_ho_key=(const unsigned char*)(key); \ hashv = 0; \ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ hashv += _ho_key[_ho_i]; \ hashv += (hashv << 10); \ hashv ^= (hashv >> 6); \ } \ hashv += (hashv << 3); \ hashv ^= (hashv >> 11); \ hashv += (hashv << 15); \ } while (0) #define HASH_JEN_MIX(a,b,c) \ do { \ a -= b; a -= c; a ^= ( c >> 13 ); \ b -= c; b -= a; b ^= ( a << 8 ); \ c -= a; c -= b; c ^= ( b >> 13 ); \ a -= b; a -= c; a ^= ( c >> 12 ); \ b -= c; b -= a; b ^= ( a << 16 ); \ c -= a; c -= b; c ^= ( b >> 5 ); \ a -= b; a -= c; a ^= ( c >> 3 ); \ b -= c; b -= a; b ^= ( a << 10 ); \ c -= a; c -= b; c ^= ( b >> 15 ); \ } while (0) #define HASH_JEN(key,keylen,hashv) \ do { \ unsigned _hj_i,_hj_j,_hj_k; \ unsigned const char *_hj_key=(unsigned const char*)(key); \ hashv = 0xfeedbeefu; \ _hj_i = _hj_j = 0x9e3779b9u; \ _hj_k = (unsigned)(keylen); \ while (_hj_k >= 12U) { \ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + ( (unsigned)_hj_key[2] << 16 ) \ + ( (unsigned)_hj_key[3] << 24 ) ); \ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + ( (unsigned)_hj_key[6] << 16 ) \ + ( (unsigned)_hj_key[7] << 24 ) ); \ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + ( (unsigned)_hj_key[10] << 16 ) \ + ( (unsigned)_hj_key[11] << 24 ) ); \ \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ \ _hj_key += 12; \ _hj_k -= 12U; \ } \ hashv += (unsigned)(keylen); \ switch ( _hj_k ) { \ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ default: ; \ } \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ } while (0) /* The Paul Hsieh hash function */ #undef get16bits #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) #define get16bits(d) (*((const uint16_t *) (d))) #endif #if !defined (get16bits) #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif #define HASH_SFH(key,keylen,hashv) \ do { \ unsigned const char *_sfh_key=(unsigned const char*)(key); \ uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ \ unsigned _sfh_rem = _sfh_len & 3U; \ _sfh_len >>= 2; \ hashv = 0xcafebabeu; \ \ /* Main loop */ \ for (;_sfh_len > 0U; _sfh_len--) { \ hashv += get16bits (_sfh_key); \ _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ hashv = (hashv << 16) ^ _sfh_tmp; \ _sfh_key += 2U*sizeof (uint16_t); \ hashv += hashv >> 11; \ } \ \ /* Handle end cases */ \ switch (_sfh_rem) { \ case 3: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 16; \ hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ hashv += hashv >> 11; \ break; \ case 2: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 11; \ hashv += hashv >> 17; \ break; \ case 1: hashv += *_sfh_key; \ hashv ^= hashv << 10; \ hashv += hashv >> 1; \ break; \ default: ; \ } \ \ /* Force "avalanching" of final 127 bits */ \ hashv ^= hashv << 3; \ hashv += hashv >> 5; \ hashv ^= hashv << 4; \ hashv += hashv >> 17; \ hashv ^= hashv << 25; \ hashv += hashv >> 6; \ } while (0) /* iterate over items in a known bucket to find desired item */ #define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ do { \ if ((head).hh_head != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ } else { \ (out) = NULL; \ } \ while ((out) != NULL) { \ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ break; \ } \ } \ if ((out)->hh.hh_next != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ } else { \ (out) = NULL; \ } \ } \ } while (0) /* add an item to a bucket */ #define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ do { \ UT_hash_bucket *_ha_head = &(head); \ _ha_head->count++; \ (addhh)->hh_next = _ha_head->hh_head; \ (addhh)->hh_prev = NULL; \ if (_ha_head->hh_head != NULL) { \ _ha_head->hh_head->hh_prev = (addhh); \ } \ _ha_head->hh_head = (addhh); \ if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ && !(addhh)->tbl->noexpand) { \ HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ HASH_DEL_IN_BKT(head,addhh); \ } \ ) \ } \ } while (0) /* remove an item from a given bucket */ #define HASH_DEL_IN_BKT(head,delhh) \ do { \ UT_hash_bucket *_hd_head = &(head); \ _hd_head->count--; \ if (_hd_head->hh_head == (delhh)) { \ _hd_head->hh_head = (delhh)->hh_next; \ } \ if ((delhh)->hh_prev) { \ (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ } \ if ((delhh)->hh_next) { \ (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ } \ } while (0) /* Bucket expansion has the effect of doubling the number of buckets * and redistributing the items into the new buckets. Ideally the * items will distribute more or less evenly into the new buckets * (the extent to which this is true is a measure of the quality of * the hash function as it applies to the key domain). * * With the items distributed into more buckets, the chain length * (item count) in each bucket is reduced. Thus by expanding buckets * the hash keeps a bound on the chain length. This bounded chain * length is the essence of how a hash provides constant time lookup. * * The calculation of tbl->ideal_chain_maxlen below deserves some * explanation. First, keep in mind that we're calculating the ideal * maximum chain length based on the *new* (doubled) bucket count. * In fractions this is just n/b (n=number of items,b=new num buckets). * Since the ideal chain length is an integer, we want to calculate * ceil(n/b). We don't depend on floating point arithmetic in this * hash, so to calculate ceil(n/b) with integers we could write * * ceil(n/b) = (n/b) + ((n%b)?1:0) * * and in fact a previous version of this hash did just that. * But now we have improved things a bit by recognizing that b is * always a power of two. We keep its base 2 log handy (call it lb), * so now we can write this with a bit shift and logical AND: * * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) * */ #define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ do { \ unsigned _he_bkt; \ unsigned _he_bkt_i; \ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ if (!_he_new_buckets) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero(_he_new_buckets, \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ (tbl)->ideal_chain_maxlen = \ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ (tbl)->nonideal_items = 0; \ for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ while (_he_thh != NULL) { \ _he_hh_nxt = _he_thh->hh_next; \ HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ _he_newbkt = &(_he_new_buckets[_he_bkt]); \ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ (tbl)->nonideal_items++; \ if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ _he_newbkt->expand_mult++; \ } \ } \ _he_thh->hh_prev = NULL; \ _he_thh->hh_next = _he_newbkt->hh_head; \ if (_he_newbkt->hh_head != NULL) { \ _he_newbkt->hh_head->hh_prev = _he_thh; \ } \ _he_newbkt->hh_head = _he_thh; \ _he_thh = _he_hh_nxt; \ } \ } \ uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ (tbl)->num_buckets *= 2U; \ (tbl)->log2_num_buckets++; \ (tbl)->buckets = _he_new_buckets; \ (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ ((tbl)->ineff_expands+1U) : 0U; \ if ((tbl)->ineff_expands > 1U) { \ (tbl)->noexpand = 1; \ uthash_noexpand_fyi(tbl); \ } \ uthash_expand_fyi(tbl); \ } \ } while (0) /* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ /* Note that HASH_SORT assumes the hash handle name to be hh. * HASH_SRT was added to allow the hash handle name to be passed in. */ #define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) #define HASH_SRT(hh,head,cmpfcn) \ do { \ unsigned _hs_i; \ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ if (head != NULL) { \ _hs_insize = 1; \ _hs_looping = 1; \ _hs_list = &((head)->hh); \ while (_hs_looping != 0U) { \ _hs_p = _hs_list; \ _hs_list = NULL; \ _hs_tail = NULL; \ _hs_nmerges = 0; \ while (_hs_p != NULL) { \ _hs_nmerges++; \ _hs_q = _hs_p; \ _hs_psize = 0; \ for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ _hs_psize++; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ if (_hs_q == NULL) { \ break; \ } \ } \ _hs_qsize = _hs_insize; \ while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ if (_hs_psize == 0U) { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else if ((cmpfcn( \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ )) <= 0) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } \ if ( _hs_tail != NULL ) { \ _hs_tail->next = ((_hs_e != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ } else { \ _hs_list = _hs_e; \ } \ if (_hs_e != NULL) { \ _hs_e->prev = ((_hs_tail != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ } \ _hs_tail = _hs_e; \ } \ _hs_p = _hs_q; \ } \ if (_hs_tail != NULL) { \ _hs_tail->next = NULL; \ } \ if (_hs_nmerges <= 1U) { \ _hs_looping = 0; \ (head)->hh.tbl->tail = _hs_tail; \ DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ } \ _hs_insize *= 2U; \ } \ HASH_FSCK(hh, head, "HASH_SRT"); \ } \ } while (0) /* This function selects items from one hash into another hash. * The end result is that the selected items have dual presence * in both hashes. There is no copy of the items made; rather * they are added into the new hash through a secondary hash * hash handle that must be present in the structure. */ #define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ do { \ unsigned _src_bkt, _dst_bkt; \ void *_last_elt = NULL, *_elt; \ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ if ((src) != NULL) { \ for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ _src_hh != NULL; \ _src_hh = _src_hh->hh_next) { \ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ if (cond(_elt)) { \ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ _dst_hh->key = _src_hh->key; \ _dst_hh->keylen = _src_hh->keylen; \ _dst_hh->hashv = _src_hh->hashv; \ _dst_hh->prev = _last_elt; \ _dst_hh->next = NULL; \ if (_last_elt_hh != NULL) { \ _last_elt_hh->next = _elt; \ } \ if ((dst) == NULL) { \ DECLTYPE_ASSIGN(dst, _elt); \ HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ uthash_nonfatal_oom(_elt); \ (dst) = NULL; \ continue; \ } \ ) \ } else { \ _dst_hh->tbl = (dst)->hh_dst.tbl; \ } \ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ (dst)->hh_dst.tbl->num_items++; \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ _dst_hh->tbl = NULL; \ uthash_nonfatal_oom(_elt); \ continue; \ } \ ) \ HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ _last_elt = _elt; \ _last_elt_hh = _dst_hh; \ } \ } \ } \ } \ HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ } while (0) #define HASH_CLEAR(hh,head) \ do { \ if ((head) != NULL) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } \ } while (0) #define HASH_OVERHEAD(hh,head) \ (((head) != NULL) ? ( \ (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ sizeof(UT_hash_table) + \ (HASH_BLOOM_BYTELEN))) : 0U) #ifdef NO_DECLTYPE #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) #else #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) #endif /* obtain a count of items in the hash */ #define HASH_COUNT(head) HASH_CNT(hh,head) #define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) typedef struct UT_hash_bucket { struct UT_hash_handle *hh_head; unsigned count; /* expand_mult is normally set to 0. In this situation, the max chain length * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If * the bucket's chain exceeds this length, bucket expansion is triggered). * However, setting expand_mult to a non-zero value delays bucket expansion * (that would be triggered by additions to this particular bucket) * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. * (The multiplier is simply expand_mult+1). The whole idea of this * multiplier is to reduce bucket expansions, since they are expensive, in * situations where we know that a particular bucket tends to be overused. * It is better to let its chain length grow to a longer yet-still-bounded * value, than to do an O(n) bucket expansion too often. */ unsigned expand_mult; } UT_hash_bucket; /* random signature used only to find hash tables in external analysis */ #define HASH_SIGNATURE 0xa0111fe1u #define HASH_BLOOM_SIGNATURE 0xb12220f2u typedef struct UT_hash_table { UT_hash_bucket *buckets; unsigned num_buckets, log2_num_buckets; unsigned num_items; struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ /* in an ideal situation (all buckets used equally), no bucket would have * more than ceil(#items/#buckets) items. that's the ideal chain length. */ unsigned ideal_chain_maxlen; /* nonideal_items is the number of items in the hash whose chain position * exceeds the ideal chain maxlen. these items pay the penalty for an uneven * hash distribution; reaching them in a chain traversal takes >ideal steps */ unsigned nonideal_items; /* ineffective expands occur when a bucket doubling was performed, but * afterward, more than half the items in the hash had nonideal chain * positions. If this happens on two consecutive expansions we inhibit any * further expansion, as it's not helping; this happens when the hash * function isn't a good fit for the key domain. When expansion is inhibited * the hash will still work, albeit no longer in constant time. */ unsigned ineff_expands, noexpand; uint32_t signature; /* used only to find hash tables in external analysis */ #ifdef HASH_BLOOM uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ uint8_t *bloom_bv; uint8_t bloom_nbits; #endif } UT_hash_table; typedef struct UT_hash_handle { struct UT_hash_table *tbl; void *prev; /* prev element in app order */ void *next; /* next element in app order */ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ struct UT_hash_handle *hh_next; /* next hh in bucket order */ const void *key; /* ptr to enclosing struct's key */ unsigned keylen; /* enclosing struct's key len */ unsigned hashv; /* result of hash-fcn(key) */ } UT_hash_handle; #endif /* UTHASH_H */ ================================================ FILE: ru/codes/c/utils/vector.h ================================================ /** * File: vector.h * Created Time: 2023-07-13 * Author: Zuoxun (845242523@qq.com)、Gonglja (glj0@outlook.com) */ #ifndef VECTOR_H #define VECTOR_H #ifdef __cplusplus extern "C" { #endif /* Определить тип вектора */ typedef struct vector { int size; // Текущий размер вектора int capacity; // Текущая емкость вектора int depth; // Текущая глубина вектора void **data; // Массив указателей на данные } vector; /* Создать вектор */ vector *newVector() { vector *v = malloc(sizeof(vector)); v->size = 0; v->capacity = 4; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); return v; } /* Создать вектор, указав размер и значение элементов по умолчанию */ vector *_newVector(int size, void *elem, int elemSize) { vector *v = malloc(sizeof(vector)); v->size = size; v->capacity = size; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); for (int i = 0; i < size; i++) { void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[i] = tmp; } return v; } /* Уничтожить вектор */ void delVector(vector *v) { if (v) { if (v->depth == 0) { return; } else if (v->depth == 1) { for (int i = 0; i < v->size; i++) { free(v->data[i]); } free(v); } else { for (int i = 0; i < v->size; i++) { delVector(v->data[i]); } v->depth--; } } } /* Добавить элемент в конец вектора (копированием) */ void vectorPushback(vector *v, void *elem, int elemSize) { if (v->size == v->capacity) { v->capacity *= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[v->size++] = tmp; } /* Извлечь элемент из конца вектора */ void vectorPopback(vector *v) { if (v->size != 0) { free(v->data[v->size - 1]); v->size--; } } /* Очистить вектор */ void vectorClear(vector *v) { delVector(v); v->size = 0; v->capacity = 4; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); } /* Получить размер вектора */ int vectorSize(vector *v) { return v->size; } /* Получить последний элемент вектора */ void *vectorBack(vector *v) { int n = v->size; return n > 0 ? v->data[n - 1] : NULL; } /* Получить первый элемент вектора */ void *vectorFront(vector *v) { return v->size > 0 ? v->data[0] : NULL; } /* Получить элемент вектора по индексу pos */ void *vectorAt(vector *v, int pos) { if (pos < 0 || pos >= v->size) { printf("vectorAt: out of range\n"); return NULL; } return v->data[pos]; } /* Установить элемент вектора по индексу pos */ void vectorSet(vector *v, int pos, void *elem, int elemSize) { if (pos < 0 || pos >= v->size) { printf("vectorSet: out of range\n"); return; } free(v->data[pos]); void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[pos] = tmp; } /* Расширение вектора */ void vectorExpand(vector *v) { v->capacity *= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } /* Сжатие вектора */ void vectorShrink(vector *v) { v->capacity /= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } /* Вставить элемент по индексу pos в вектор */ void vectorInsert(vector *v, int pos, void *elem, int elemSize) { if (v->size == v->capacity) { vectorExpand(v); } for (int j = v->size; j > pos; j--) { v->data[j] = v->data[j - 1]; } void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[pos] = tmp; v->size++; } /* Удалить элемент вектора по индексу pos */ void vectorErase(vector *v, int pos) { if (v->size != 0) { free(v->data[pos]); for (int j = pos; j < v->size - 1; j++) { v->data[j] = v->data[j + 1]; } v->size--; } } /* Обмен элементов вектора */ void vectorSwap(vector *v, int i, int j) { void *tmp = v->data[i]; v->data[i] = v->data[j]; v->data[j] = tmp; } /* Пуст ли вектор */ bool vectorEmpty(vector *v) { return v->size == 0; } /* Заполнен ли вектор */ bool vectorFull(vector *v) { return v->size == v->capacity; } /* Равны ли векторы */ bool vectorEqual(vector *v1, vector *v2) { if (v1->size != v2->size) { printf("size not equal\n"); return false; } for (int i = 0; i < v1->size; i++) { void *a = v1->data[i]; void *b = v2->data[i]; if (memcmp(a, b, sizeof(a)) != 0) { printf("data %d not equal\n", i); return false; } } return true; } /* Отсортировать содержимое вектора */ void vectorSort(vector *v, int (*cmp)(const void *, const void *)) { qsort(v->data, v->size, sizeof(void *), cmp); } /* Функция печати: нужно передать функцию для вывода значения переменной */ /* В настоящее время поддерживается только вывод vector глубины 1 */ void printVector(vector *v, void (*printFunc)(vector *v, void *p)) { if (v) { if (v->depth == 0) { return; } else if (v->depth == 1) { if(v->size == 0) { printf("\n"); return; } for (int i = 0; i < v->size; i++) { if (i == 0) { printf("["); } else if (i == v->size - 1) { printFunc(v, v->data[i]); printf("]\r\n"); break; } printFunc(v, v->data[i]); printf(","); } } else { for (int i = 0; i < v->size; i++) { printVector(v->data[i], printFunc); } v->depth--; } } } /* В настоящее время поддерживается только вывод vector глубины 2 */ void printVectorMatrix(vector *vv, void (*printFunc)(vector *v, void *p)) { printf("[\n"); for (int i = 0; i < vv->size; i++) { vector *v = (vector *)vv->data[i]; printf(" ["); for (int j = 0; j < v->size; j++) { printFunc(v, v->data[j]); if (j != v->size - 1) printf(","); } printf("],"); printf("\n"); } printf("]\n"); } #ifdef __cplusplus } #endif #endif // VECTOR_H ================================================ FILE: ru/codes/c/utils/vertex.h ================================================ /** * File: vertex.h * Created Time: 2023-10-28 * Author: krahets (krahets@163.com) */ #ifndef VERTEX_H #define VERTEX_H #ifdef __cplusplus extern "C" { #endif /* Структура вершины */ typedef struct { int val; } Vertex; /* Конструктор, инициализирующий новый узел */ Vertex *newVertex(int val) { Vertex *vet; vet = (Vertex *)malloc(sizeof(Vertex)); vet->val = val; return vet; } /* Преобразовать массив значений в массив вершин */ Vertex **valsToVets(int *vals, int size) { Vertex **vertices = (Vertex **)malloc(size * sizeof(Vertex *)); for (int i = 0; i < size; ++i) { vertices[i] = newVertex(vals[i]); } return vertices; } /* Преобразовать массив вершин в массив значений */ int *vetsToVals(Vertex **vertices, int size) { int *vals = (int *)malloc(size * sizeof(int)); for (int i = 0; i < size; ++i) { vals[i] = vertices[i]->val; } return vals; } #ifdef __cplusplus } #endif #endif // VERTEX_H ================================================ FILE: ru/codes/cpp/.gitignore ================================================ # Ignore all * # Unignore all with extensions !*.* # Unignore all dirs !*/ *.dSYM/ build/ ================================================ FILE: ru/codes/cpp/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(hello_algo CXX) set(CMAKE_CXX_STANDARD 11) include_directories(./include) add_subdirectory(chapter_computational_complexity) add_subdirectory(chapter_array_and_linkedlist) add_subdirectory(chapter_stack_and_queue) add_subdirectory(chapter_hashing) add_subdirectory(chapter_tree) add_subdirectory(chapter_heap) add_subdirectory(chapter_graph) add_subdirectory(chapter_searching) add_subdirectory(chapter_sorting) add_subdirectory(chapter_divide_and_conquer) add_subdirectory(chapter_backtracking) add_subdirectory(chapter_dynamic_programming) add_subdirectory(chapter_greedy) ================================================ FILE: ru/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt ================================================ add_executable(array array.cpp) add_executable(linked_list linked_list.cpp) add_executable(list list.cpp) add_executable(my_list my_list.cpp) ================================================ FILE: ru/codes/cpp/chapter_array_and_linkedlist/array.cpp ================================================ /** * 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; } /* Вставить элемент num по индексу index в массив */ void insert(int *nums, int size, int num, int index) { // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад for (int i = size - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Присвоить num элементу по индексу index nums[index] = num; } /* Удалить элемент по индексу index */ void remove(int *nums, int size, int index) { // Сдвинуть все элементы после индекса index на одну позицию вперед for (int i = index; i < size - 1; i++) { nums[i] = nums[i + 1]; } } /* Обход массива */ void traverse(int *nums, int size) { int count = 0; // Обход массива по индексам for (int i = 0; i < size; i++) { count += nums[i]; } } /* Найти заданный элемент в массиве */ int find(int *nums, int size, int target) { for (int i = 0; i < size; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ int main() { /* Инициализация массива */ int size = 5; int *arr = new int[size]; cout << "Массив arr = "; printArray(arr, size); int *nums = new int[size]{1, 3, 2, 5, 4}; cout << "Массив nums = "; printArray(nums, size); /* Случайный доступ */ int randomNum = randomAccess(nums, size); cout << "Случайный элемент из nums = " << randomNum << endl; /* Расширение длины */ int enlarge = 3; nums = extend(nums, size, enlarge); size += enlarge; cout << "После увеличения длины массива до 8 nums = "; printArray(nums, size); /* Вставка элемента */ insert(nums, size, 6, 3); cout << "После вставки числа 6 по индексу 3 nums = "; printArray(nums, size); /* Удаление элемента */ remove(nums, size, 2); cout << "После удаления элемента по индексу 2 nums = "; printArray(nums, size); /* Обход массива */ traverse(nums, size); /* Поиск элемента */ int index = find(nums, size, 3); cout << "Индекс элемента 3 в nums = " << index << endl; // Освободить память delete[] arr; delete[] nums; return 0; } ================================================ FILE: ru/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp ================================================ /** * File: linked_list.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Вставить узел P после узла n0 в связном списке */ 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; } /* Driver Code */ 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 << "Инициализированный связный список" << endl; printLinkedList(n0); /* Вставка узла */ insert(n0, new ListNode(0)); cout << "Связный список после вставки узла" << endl; printLinkedList(n0); /* Удаление узла */ remove(n0); cout << "Связный список после удаления узла" << endl; printLinkedList(n0); /* Доступ к узлу */ ListNode *node = access(n0, 3); cout << "Значение узла по индексу 3 в связном списке = " << node->val << endl; /* Поиск узла */ int index = find(n0, 2); cout << "Индекс узла со значением 2 в связном списке = " << index << endl; // Освободить память freeMemoryLinkedList(n0); return 0; } ================================================ FILE: ru/codes/cpp/chapter_array_and_linkedlist/list.cpp ================================================ /** * File: list.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* Инициализация списка */ vector nums = {1, 3, 2, 5, 4}; cout << "Список nums = "; printVector(nums); /* Доступ к элементу */ int num = nums[1]; cout << "Элемент по индексу 1: num = " << num << endl; /* Обновление элемента */ nums[1] = 0; cout << "После обновления элемента по индексу 1 на 0 nums = "; printVector(nums); /* Очистить список */ nums.clear(); cout << "После очистки списка nums = "; printVector(nums); /* Добавление элемента в конец */ nums.push_back(1); nums.push_back(3); nums.push_back(2); nums.push_back(5); nums.push_back(4); cout << "После добавления элемента nums = "; printVector(nums); /* Вставка элемента в середину */ nums.insert(nums.begin() + 3, 6); cout << "После вставки числа 6 по индексу 3 nums = "; printVector(nums); /* Удаление элемента */ nums.erase(nums.begin() + 3); cout << "После удаления элемента по индексу 3 nums = "; printVector(nums); /* Обходить список по индексам */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums[i]; } /* Непосредственно обходить элементы списка */ count = 0; for (int x : nums) { count += x; } /* Объединить два списка */ vector nums1 = {6, 8, 7, 10, 9}; nums.insert(nums.end(), nums1.begin(), nums1.end()); cout << "После присоединения списка nums1 к nums nums = "; printVector(nums); /* Отсортировать список */ sort(nums.begin(), nums.end()); cout << "После сортировки списка nums = "; printVector(nums); return 0; } ================================================ FILE: ru/codes/cpp/chapter_array_and_linkedlist/my_list.cpp ================================================ /** * 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("индекс выходит за границы"); return arr[index]; } /* Обновление элемента */ void set(int index, int num) { if (index < 0 || index >= size()) throw out_of_range("индекс выходит за границы"); 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("индекс выходит за границы"); // При превышении вместимости по числу элементов запускается расширение if (size() == capacity()) extendCapacity(); // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад 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("индекс выходит за границы"); int num = arr[index]; // Сдвинуть все элементы после индекса index на одну позицию вперед 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; } }; /* Driver Code */ int main() { /* Инициализация списка */ MyList *nums = new MyList(); /* Добавление элемента в конец */ nums->add(1); nums->add(3); nums->add(2); nums->add(5); nums->add(4); cout << "Список nums = "; vector vec = nums->toVector(); printVector(vec); cout << "Вместимость = " << nums->capacity() << ", длина = " << nums->size() << endl; /* Вставка элемента в середину */ nums->insert(3, 6); cout << "После вставки числа 6 по индексу 3 nums = "; vec = nums->toVector(); printVector(vec); /* Удаление элемента */ nums->remove(3); cout << "После удаления элемента по индексу 3 nums = "; vec = nums->toVector(); printVector(vec); /* Доступ к элементу */ int num = nums->get(1); cout << "Элемент по индексу 1: num = " << num << endl; /* Обновление элемента */ nums->set(1, 0); cout << "После обновления элемента по индексу 1 на 0 nums = "; vec = nums->toVector(); printVector(vec); /* Проверка механизма расширения */ for (int i = 0; i < 10; i++) { // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения nums->add(i); } cout << "После расширения список nums = "; vec = nums->toVector(); printVector(vec); cout << "Вместимость = " << nums->capacity() << ", длина = " << nums->size() << endl; // Освободить память delete nums; return 0; } ================================================ FILE: ru/codes/cpp/chapter_backtracking/CMakeLists.txt ================================================ add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.cpp) add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.cpp) add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.cpp) add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.cpp) add_executable(permutations_i permutations_i.cpp) add_executable(permutations_ii permutations_ii.cpp) add_executable(n_queens n_queens.cpp) add_executable(subset_sum_i_naive subset_sum_i_naive.cpp) add_executable(subset_sum_i subset_sum_i.cpp) add_executable(subset_sum_ii subset_sum_ii.cpp) ================================================ FILE: ru/codes/cpp/chapter_backtracking/n_queens.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_backtracking/permutations_i.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector nums = {1, 2, 3}; vector> res = permutationsI(nums); cout << "Входной массив nums = "; printVector(nums); cout << "Все перестановки res = "; printVectorMatrix(res); return 0; } ================================================ FILE: ru/codes/cpp/chapter_backtracking/permutations_ii.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector nums = {1, 1, 2}; vector> res = permutationsII(nums); cout << "Входной массив nums = "; printVector(nums); cout << "Все перестановки res = "; printVectorMatrix(res); return 0; } ================================================ FILE: ru/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp ================================================ /** * 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); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\nИнициализация двоичного дерева" << endl; printTree(root); // Предварительный обход preOrder(root); cout << "\nВывести все узлы со значением 7" << endl; vector vals; for (TreeNode *node : res) { vals.push_back(node->val); } printVector(vals); } ================================================ FILE: ru/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp ================================================ /** * 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(); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\nИнициализация двоичного дерева" << endl; printTree(root); // Предварительный обход preOrder(root); cout << "\nВывести все пути от корня к узлу 7" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: ru/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp ================================================ /** * 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(); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\nИнициализация двоичного дерева" << endl; printTree(root); // Предварительный обход preOrder(root); cout << "\nВывести все пути от корня к узлу 7, не содержащие узлов со значением 3" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: ru/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp ================================================ /** * 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); } } } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\nИнициализация двоичного дерева" << endl; printTree(root); // Алгоритм бэктрекинга vector state; vector choices = {root}; vector> res; backtrack(state, choices, res); cout << "\nВывести все пути от корня к узлу 7, не содержащие узлов со значением 3" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: ru/codes/cpp/chapter_backtracking/subset_sum_i.cpp ================================================ /** * 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; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств for (int i = start; i < choices.size(); i++) { // Отсечение 1: если сумма подмножества превышает 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; } /* Driver Code */ 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 << ": " << endl; printVectorMatrix(res); return 0; } ================================================ FILE: ru/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp ================================================ /** * 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 (size_t 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; } /* Driver Code */ 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 << ": " << endl; printVectorMatrix(res); return 0; } ================================================ FILE: ru/codes/cpp/chapter_backtracking/subset_sum_ii.cpp ================================================ /** * 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; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента for (int i = start; i < choices.size(); i++) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if (target - choices[i] < 0) { break; } // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить 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; } /* Driver Code */ 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 << ": " << endl; printVectorMatrix(res); return 0; } ================================================ FILE: ru/codes/cpp/chapter_computational_complexity/CMakeLists.txt ================================================ add_executable(iteration iteration.cpp) add_executable(recursion recursion.cpp) add_executable(space_complexity space_complexity.cpp) add_executable(time_complexity time_complexity.cpp) add_executable(worst_best_time_complexity worst_best_time_complexity.cpp) ================================================ FILE: ru/codes/cpp/chapter_computational_complexity/iteration.cpp ================================================ /** * 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 (двойное обновление) */ int whileLoopII(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i++; i *= 2; } return res; } /* Двойной цикл for */ string nestedForLoop(int n) { 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(); } /* Driver Code */ int main() { int n = 5; int res; res = forLoop(n); cout << "\nРезультат суммирования в цикле for res = " << res << endl; res = whileLoop(n); cout << "\nРезультат суммирования в цикле while res = " << res << endl; res = whileLoopII(n); cout << "\nРезультат суммирования в цикле while (двойное обновление) res = " << res << endl; string resStr = nestedForLoop(n); cout << "\nРезультат двойного цикла for " << resStr << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_computational_complexity/recursion.cpp ================================================ /** * 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; } /* Driver Code */ 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Элемент последовательности Фибоначчи с индексом " << n << " = " << res << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_computational_complexity/space_complexity.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int n = 5; // Постоянная сложность constant(n); // Линейная сложность linear(n); linearRecur(n); // Квадратичная сложность quadratic(n); quadraticRecur(n); // Экспоненциальная сложность TreeNode *root = buildTree(n); printTree(root); // Освободить память freeMemoryTree(root); return 0; } ================================================ FILE: ru/codes/cpp/chapter_computational_complexity/time_complexity.cpp ================================================ /** * 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; // На каждом шаге клетка делится надвое, образуя последовательность 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; // Из одного получается n for (int i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp ================================================ /** * 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; } // Использовать системное время для генерации случайного seed unsigned seed = chrono::system_clock::now().time_since_epoch().count(); // Случайно перемешать элементы массива shuffle(nums.begin(), nums.end(), default_random_engine(seed)); return nums; } /* Найти индекс числа 1 в массиве nums */ 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; } /* Driver Code */ int main() { for (int i = 0; i < 1000; i++) { int n = 100; vector nums = randomNumbers(n); int index = findOne(nums); cout << "\nПосле перемешивания массива [ 1, 2, ..., n ] nums = "; printVector(nums); cout << "Индекс числа 1 = " << index << endl; } return 0; } ================================================ FILE: ru/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt ================================================ add_executable(binary_search_recur binary_search_recur.cpp) add_executable(build_tree build_tree.cpp) add_executable(hanota hanota.cpp) ================================================ FILE: ru/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp ================================================ /** * 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) / 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); } /* Driver Code */ int main() { int target = 6; vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; // Бинарный поиск (двусторонне замкнутый интервал) int index = binarySearch(nums, target); cout << "Индекс целевого элемента 6 = " << index << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_divide_and_conquer/build_tree.cpp ================================================ /** * 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) { // Инициализировать хеш-таблицу для хранения соответствия элементов 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; } /* Driver Code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_divide_and_conquer/hanota.cpp ================================================ /** * 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 остался только один диск, сразу переместить его в tar if (i == 1) { move(src, tar); return; } // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar dfs(i - 1, src, tar, buf); // Подзадача f(1): переместить оставшийся один диск из src в tar move(src, tar); // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src dfs(i - 1, buf, src, tar); } /* Решить задачу Ханойской башни */ void solveHanota(vector &A, vector &B, vector &C) { int n = A.size(); // Переместить верхние n дисков из A в C с помощью B dfs(n, A, B, C); } /* Driver Code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_dynamic_programming/CMakeLists.txt ================================================ add_executable(climbing_stairs_backtrack climbing_stairs_backtrack.cpp) add_executable(climbing_stairs_dfs climbing_stairs_dfs.cpp) add_executable(climbing_stairs_dfs_mem climbing_stairs_dfs_mem.cpp) add_executable(climbing_stairs_dp climbing_stairs_dp.cpp) add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.cpp) add_executable(min_path_sum min_path_sum.cpp) add_executable(unbounded_knapsack unbounded_knapsack.cpp) add_executable(coin_change coin_change.cpp) add_executable(coin_change_ii coin_change_ii.cpp) add_executable(edit_distance edit_distance.cpp) ================================================ FILE: ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp ================================================ /** * 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]; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsBacktrack(n); cout << "Количество способов подняться по лестнице из " << n << " ступеней: " << res << " вариантов" << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp ================================================ /** * 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]; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsConstraintDP(n); cout << "Количество способов подняться по лестнице из " << n << " ступеней: " << res << " вариантов" << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp ================================================ /** * 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); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFS(n); cout << "Количество способов подняться по лестнице из " << n << " ступеней: " << res << " вариантов" << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp ================================================ /** * 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); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFSMem(n); cout << "Количество способов подняться по лестнице из " << n << " ступеней: " << res << " вариантов" << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDP(n); cout << "Количество способов подняться по лестнице из " << n << " ступеней: " << res << " вариантов" << endl; res = climbingStairsDPComp(n); cout << "Количество способов подняться по лестнице из " << n << " ступеней: " << res << " вариантов" << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_dynamic_programming/coin_change.cpp ================================================ /** * 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; } /* Driver code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp ================================================ /** * 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 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 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_dynamic_programming/edit_distance.cpp ================================================ /** * 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; // Если два символа равны, сразу пропустить их if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 int insert = editDistanceDFS(s, t, i, j - 1); int del = editDistanceDFS(s, t, i - 1, j); int replace = editDistanceDFS(s, t, i - 1, j - 1); // Вернуть минимальное число шагов редактирования return min(min(insert, del), replace) + 1; } /* Редакционное расстояние: поиск с мемоизацией */ int editDistanceDFSMem(string s, string t, vector> &mem, int i, int j) { // Если s и t пусты, вернуть 0 if (i == 0 && j == 0) return 0; // Если s пусто, вернуть длину t if (i == 0) return j; // Если t пусто, вернуть длину s if (j == 0) return i; // Если запись уже есть, сразу вернуть ее if (mem[i][j] != -1) return mem[i][j]; // Если два символа равны, сразу пропустить их if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 int insert = editDistanceDFSMem(s, t, mem, i, j - 1); int del = editDistanceDFSMem(s, t, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Сохранить и вернуть минимальное число шагов редактирования mem[i][j] = min(min(insert, del), replace) + 1; return mem[i][j]; } /* Редакционное расстояние: динамическое программирование */ int editDistanceDP(string s, string t) { int n = s.length(), m = t.length(); vector> dp(n + 1, vector(m + 1, 0)); // Переход состояний: первая строка и первый столбец for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // Переход состояний: остальные строки и столбцы for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // Если два символа равны, сразу пропустить их dp[i][j] = dp[i - 1][j - 1]; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ int editDistanceDPComp(string s, string t) { int n = s.length(), m = t.length(); vector dp(m + 1, 0); // Переход состояний: первая строка for (int j = 1; j <= m; j++) { dp[j] = j; } // Переход состояний: остальные строки for (int i = 1; i <= n; i++) { // Переход состояний: первый столбец int leftup = dp[0]; // Временно сохранить dp[i-1, j-1] dp[0] = i; // Переход состояний: остальные столбцы for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // Если два символа равны, сразу пропустить их dp[j] = leftup; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации } } return dp[m]; } /* Driver Code */ int main() { string s = "bag"; string t = "pack"; int n = s.length(), m = t.length(); // Полный перебор int res = editDistanceDFS(s, t, n, m); cout << "Чтобы заменить " << s << " на " << t << " , требуется минимум " << res << " операций редактирования\n"; // Поиск с мемоизацией vector> mem(n + 1, vector(m + 1, -1)); res = editDistanceDFSMem(s, t, mem, n, m); cout << "Чтобы заменить " << s << " на " << t << " , требуется минимум " << res << " операций редактирования\n"; // Динамическое программирование res = editDistanceDP(s, t); cout << "Чтобы заменить " << s << " на " << t << " , требуется минимум " << res << " операций редактирования\n"; // Динамическое программирование с оптимизацией памяти res = editDistanceDPComp(s, t); cout << "Чтобы заменить " << s << " на " << t << " , требуется минимум " << res << " операций редактирования\n"; return 0; } ================================================ FILE: ru/codes/cpp/chapter_dynamic_programming/knapsack.cpp ================================================ #include #include #include using namespace std; /* Рюкзак 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]; // Вернуть вариант с большей стоимостью из двух возможных return max(no, yes); } /* Рюкзак 0-1: поиск с мемоизацией */ int knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i == 0 || c == 0) { return 0; } // Если запись уже есть, вернуть сразу if (mem[i][c] != -1) { return mem[i][c]; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут int no = knapsackDFSMem(wgt, val, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // Сохранить и вернуть вариант с большей стоимостью из двух решений mem[i][c] = max(no, yes); return mem[i][c]; } /* Рюкзак 0-1: динамическое программирование */ int knapsackDP(vector &wgt, vector &val, int cap) { int n = wgt.size(); // Инициализация таблицы dp 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]; } /* Driver Code */ int main() { vector wgt = {10, 20, 30, 40, 50}; vector val = {50, 120, 150, 210, 240}; int cap = 50; int n = wgt.size(); // Полный перебор int res = knapsackDFS(wgt, val, n, cap); cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; // Поиск с мемоизацией vector> mem(n + 1, vector(cap + 1, -1)); res = knapsackDFSMem(wgt, val, mem, n, cap); cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; // Динамическое программирование res = knapsackDP(wgt, val, cap); cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; // Динамическое программирование с оптимизацией памяти res = knapsackDPComp(wgt, val, cap); cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; cout << "Список стоимостей ступеней = "; printVector(cost); int res = minCostClimbingStairsDP(cost); cout << "Минимальная стоимость подъема по лестнице = " << res << endl; res = minCostClimbingStairsDPComp(cost); cout << "Минимальная стоимость подъема по лестнице = " << res << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp ================================================ /** * 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) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; } /* Минимальная сумма пути: поиск с мемоизацией */ int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) { // Если это верхняя левая ячейка, завершить поиск if (i == 0 && j == 0) { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 || j < 0) { return INT_MAX; } // Если запись уже есть, вернуть сразу if (mem[i][j] != -1) { return mem[i][j]; } // Минимальная стоимость пути для левой и верхней ячеек int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; return mem[i][j]; } /* Минимальная сумма пути: динамическое программирование */ int minPathSumDP(vector> &grid) { int n = grid.size(), m = grid[0].size(); // Инициализация таблицы dp 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 minPathSumDPComp(vector> &grid) { int n = grid.size(), m = grid[0].size(); // Инициализация таблицы dp vector dp(m); // Переход состояний: первая строка dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // Переход состояний: остальные строки for (int i = 1; i < n; i++) { // Переход состояний: первый столбец dp[0] = dp[0] + grid[i][0]; // Переход состояний: остальные столбцы for (int j = 1; j < m; j++) { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ int main() { vector> grid = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; int n = grid.size(), m = grid[0].size(); // Полный перебор int res = minPathSumDFS(grid, n - 1, m - 1); cout << "Минимальная сумма пути из левого верхнего в правый нижний угол = " << res << endl; // Поиск с мемоизацией vector> mem(n, vector(m, -1)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); cout << "Минимальная сумма пути из левого верхнего в правый нижний угол = " << res << endl; // Динамическое программирование res = minPathSumDP(grid); cout << "Минимальная сумма пути из левого верхнего в правый нижний угол = " << res << endl; // Динамическое программирование с оптимизацией памяти res = minPathSumDPComp(grid); cout << "Минимальная сумма пути из левого верхнего в правый нижний угол = " << res << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp ================================================ /** * 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]; } /* Driver code */ int main() { vector wgt = {1, 2, 3}; vector val = {5, 11, 15}; int cap = 4; // Динамическое программирование int res = unboundedKnapsackDP(wgt, val, cap); cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; // Динамическое программирование с оптимизацией памяти res = unboundedKnapsackDPComp(wgt, val, cap); cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_graph/CMakeLists.txt ================================================ add_executable(graph_bfs graph_bfs.cpp) add_executable(graph_dfs graph_dfs.cpp) # add_executable(graph_adjacency_list graph_adjacency_list.cpp) add_executable(graph_adjacency_list_test graph_adjacency_list_test.cpp) add_executable(graph_adjacency_matrix graph_adjacency_matrix.cpp) ================================================ FILE: ru/codes/cpp/chapter_graph/graph_adjacency_list.cpp ================================================ /** * 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: // Список смежности, где key — вершина, а value — все смежные ей вершины unordered_map> adjList; /* Удалить указанный узел из vector */ 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("вершина не существует"); // Добавить ребро 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("вершина не существует"); // Удалить ребро 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("вершина не существует"); // Удалить из списка смежности список, соответствующий вершине 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 ================================================ FILE: ru/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp ================================================ /** * File: graph_adjacency_list_test.cpp * Created Time: 2023-02-09 * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) */ #include "./graph_adjacency_list.cpp" /* Driver Code */ int main() { /* Инициализация неориентированного графа */ vector v = valsToVets(vector{1, 3, 2, 5, 4}); vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; GraphAdjList graph(edges); cout << "\nПосле инициализации граф имеет вид" << endl; graph.print(); /* Добавление ребра */ // Вершины 1 и 2 соответствуют v[0] и v[2] graph.addEdge(v[0], v[2]); cout << "\nПосле добавления ребра 1-2 граф имеет вид" << endl; graph.print(); /* Удаление ребра */ // Вершины 1 и 3 соответствуют v[0] и v[1] graph.removeEdge(v[0], v[1]); cout << "\nПосле удаления ребра 1-3 граф имеет вид" << endl; graph.print(); /* Добавление вершины */ Vertex *v5 = new Vertex(6); graph.addVertex(v5); cout << "\nПосле добавления вершины 6 граф имеет вид" << endl; graph.print(); /* Удаление вершины */ // Вершина 3 соответствует v[1] graph.removeVertex(v[1]); cout << "\nПосле удаления вершины 3 граф имеет вид" << endl; graph.print(); // Освободить память for (Vertex *vet : v) { delete vet; } return 0; } ================================================ FILE: ru/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp ================================================ /** * 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); } // Добавить ребра // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices 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("вершина не существует"); } // Удалить вершину с индексом index из списка вершин vertices.erase(vertices.begin() + index); // Удалить строку с индексом index из матрицы смежности adjMat.erase(adjMat.begin() + index); // Удалить столбец с индексом index из матрицы смежности for (vector &row : adjMat) { row.erase(row.begin() + index); } } /* Добавление ребра */ // Параметры i и j соответствуют индексам элементов vertices void addEdge(int i, int j) { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw out_of_range("вершина не существует"); } // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) adjMat[i][j] = 1; adjMat[j][i] = 1; } /* Удаление ребра */ // Параметры i и j соответствуют индексам элементов vertices void removeEdge(int i, int j) { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw out_of_range("вершина не существует"); } adjMat[i][j] = 0; adjMat[j][i] = 0; } /* Вывести матрицу смежности */ void print() { cout << "Список вершин = "; printVector(vertices); cout << "Матрица смежности =" << endl; printVectorMatrix(adjMat); } }; /* Driver Code */ int main() { /* Инициализация неориентированного графа */ // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices vector vertices = {1, 3, 2, 5, 4}; vector> edges = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; GraphAdjMat graph(vertices, edges); 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; } ================================================ FILE: ru/codes/cpp/chapter_graph/graph_bfs.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_graph/graph_dfs.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_greedy/CMakeLists.txt ================================================ add_executable(coin_change_greedy coin_change_greedy.cpp) add_executable(fractional_knapsack fractional_knapsack.cpp) add_executable(max_capacity max_capacity.cpp) ================================================ FILE: ru/codes/cpp/chapter_greedy/coin_change_greedy.cpp ================================================ /** * 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) { // Предположить, что список coins упорядочен 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; } /* Driver Code */ int main() { // Жадный подход: гарантирует нахождение глобально оптимального решения vector coins = {1, 5, 10, 20, 50, 100}; int amt = 186; int res = coinChangeGreedy(coins, amt); cout << "\ncoins = "; printVector(coins); cout << "amt = " << amt << endl; cout << "Минимальное количество монет для набора суммы " << amt << " = " << res << endl; // Жадный подход: не гарантирует нахождение глобально оптимального решения 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; } ================================================ FILE: ru/codes/cpp/chapter_greedy/fractional_knapsack.cpp ================================================ /** * 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) { // Создать список предметов с двумя свойствами: вес и стоимость 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; } /* Driver Code */ int main() { vector wgt = {10, 20, 30, 40, 50}; vector val = {50, 120, 150, 210, 240}; int cap = 50; // Жадный алгоритм double res = fractionalKnapsack(wgt, val, cap); cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_greedy/max_capacity.cpp ================================================ /** * 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; // Выполнять жадный выбор в цикле, пока две доски не встретятся while (i < j) { // Обновить максимальную вместимость int cap = min(ht[i], ht[j]) * (j - i); res = max(res, cap); // Сдвигать внутрь более короткую сторону if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ int main() { vector ht = {3, 8, 5, 2, 7, 7, 3, 4}; // Жадный алгоритм int res = maxCapacity(ht); cout << "Максимальная вместимость = " << res << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_greedy/max_product_cutting.cpp ================================================ /** * 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 — число троек, а 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); } /* Driver Code */ int main() { int n = 58; // Жадный алгоритм int res = maxProductCutting(n); cout << "Максимальное произведение после разрезания = " << res << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_hashing/CMakeLists.txt ================================================ add_executable(hash_map hash_map.cpp) add_executable(array_hash_map_test array_hash_map_test.cpp) add_executable(hash_map_chaining hash_map_chaining.cpp) add_executable(hash_map_open_addressing hash_map_open_addressing.cpp) add_executable(simple_hash simple_hash.cpp) add_executable(built_in_hash built_in_hash.cpp) ================================================ FILE: ru/codes/cpp/chapter_hashing/array_hash_map.cpp ================================================ /** * 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 ================================================ FILE: ru/codes/cpp/chapter_hashing/array_hash_map_test.cpp ================================================ /** * File: array_hash_map_test.cpp * Created Time: 2022-12-14 * Author: msk397 (machangxinq@gmail.com) */ #include "./array_hash_map.cpp" /* Driver Code */ int main() { /* Инициализация хеш-таблицы */ ArrayHashMap map = ArrayHashMap(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(12836, "Сяо Ха"); map.put(15937, "Сяо Ло"); map.put(16750, "Сяо Суань"); map.put(13276, "Сяо Фа"); map.put(10583, "Сяо Я"); cout << "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" << endl; map.print(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value string name = map.get(15937); cout << "\nДля студенческого номера 15937 найдено имя " << name << endl; /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(10583); cout << "\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение" << endl; map.print(); /* Обход хеш-таблицы */ cout << "\nОтдельный обход пар ключ-значение" << endl; for (auto kv : map.pairSet()) { cout << kv->key << " -> " << kv->val << endl; } cout << "\nОтдельный обход ключей" << endl; for (auto key : map.keySet()) { cout << key << endl; } cout << "\nОтдельный обход значений" << endl; for (auto val : map.valueSet()) { cout << val << endl; } return 0; } ================================================ FILE: ru/codes/cpp/chapter_hashing/built_in_hash.cpp ================================================ /** * File: built_in_hash.cpp * Created Time: 2023-06-21 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { int num = 3; size_t hashNum = hash()(num); cout << "Хеш-значение целого числа " << num << " = " << hashNum << "\n"; bool bol = true; size_t hashBol = hash()(bol); cout << "Хеш-значение булева значения " << bol << " = " << hashBol << "\n"; double dec = 3.14159; size_t hashDec = hash()(dec); cout << "Хеш-значение десятичного числа " << dec << " = " << hashDec << "\n"; string str = "Hello Algo"; size_t hashStr = hash()(str); cout << "Хеш-значение строки " << str << " = " << hashStr << "\n"; // В C++ встроенный std::hash() предоставляет вычисление хеша только для базовых типов данных // Вычисление хеша для массивов и объектов нужно реализовывать самостоятельно } ================================================ FILE: ru/codes/cpp/chapter_hashing/hash_map.cpp ================================================ /** * File: hash_map.cpp * Created Time: 2022-12-14 * Author: msk397 (machangxinq@gmail.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* Инициализация хеш-таблицы */ unordered_map map; /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map[12836] = "Сяо Ха"; map[15937] = "Сяо Ло"; map[16750] = "Сяо Суань"; map[13276] = "Сяо Фа"; map[10583] = "Сяо Я"; cout << "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" << endl; printHashMap(map); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value string name = map[15937]; cout << "\nДля студенческого номера 15937 найдено имя " << name << endl; /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.erase(10583); cout << "\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение" << endl; printHashMap(map); /* Обход хеш-таблицы */ cout << "\nОтдельный обход пар ключ-значение" << endl; for (auto kv : map) { cout << kv.first << " -> " << kv.second << endl; } cout << "\nОбход пар Key->Value с помощью итератора" << endl; for (auto iter = map.begin(); iter != map.end(); iter++) { cout << iter->first << "->" << iter->second << endl; } return 0; } ================================================ FILE: ru/codes/cpp/chapter_hashing/hash_map_chaining.cpp ================================================ /** * 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); // Обойти корзину; если найден key, вернуть соответствующее val for (Pair *pair : buckets[index]) { if (pair->key == key) { return pair->val; } } // Если key не найден, вернуть пустую строку return ""; } /* Операция добавления */ void put(int key, string val) { // Когда коэффициент загрузки превышает порог, выполнить расширение if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть for (Pair *pair : buckets[index]) { if (pair->key == key) { pair->val = val; return; } } // Если такого key нет, добавить пару ключ-значение в конец buckets[index].push_back(new Pair(key, val)); size++; } /* Операция удаления */ 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"; } } }; /* Driver Code */ int main() { /* Инициализация хеш-таблицы */ HashMapChaining map = HashMapChaining(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(12836, "Сяо Ха"); map.put(15937, "Сяо Ло"); map.put(16750, "Сяо Суань"); map.put(13276, "Сяо Фа"); map.put(10583, "Сяо Я"); cout << "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" << endl; map.print(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value string name = map.get(13276); cout << "\nДля студенческого номера 13276 найдено имя " << name << endl; /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(12836); cout << "\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение" << endl; map.print(); return 0; } ================================================ FILE: ru/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp ================================================ /** * 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; } } } }; /* Driver Code */ int main() { // Инициализация хеш-таблицы HashMapOpenAddressing hashmap; // Операция добавления // Добавить пару (key, val) в хеш-таблицу hashmap.put(12836, "Сяо Ха"); hashmap.put(15937, "Сяо Ло"); hashmap.put(16750, "Сяо Суань"); hashmap.put(13276, "Сяо Фа"); hashmap.put(10583, "Сяо Я"); cout << "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" << endl; hashmap.print(); // Операция поиска // Передать ключ key в хеш-таблицу и получить значение val string name = hashmap.get(13276); cout << "\nДля студенческого номера 13276 найдено имя " << name << endl; // Операция удаления // Удалить пару (key, val) из хеш-таблицы hashmap.remove(16750); cout << "\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение" << endl; hashmap.print(); return 0; } ================================================ FILE: ru/codes/cpp/chapter_hashing/simple_hash.cpp ================================================ /** * 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; } /* Driver Code */ int main() { string key = "Hello Algo"; int hash = addHash(key); cout << "Хеш суммы = " << hash << endl; hash = mulHash(key); cout << "Хеш произведения = " << hash << endl; hash = xorHash(key); cout << "XOR-хеш = " << hash << endl; hash = rotHash(key); cout << "Хеш с циклическим сдвигом = " << hash << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_heap/CMakeLists.txt ================================================ add_executable(heap heap.cpp) add_executable(my_heap my_heap.cpp) add_executable(top_k top_k.cpp) ================================================ FILE: ru/codes/cpp/chapter_heap/heap.cpp ================================================ /** * 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); } /* Driver Code */ int main() { /* Инициализация кучи */ // Инициализировать минимальную кучу // priority_queue, greater> minHeap; // Инициализировать максимальную кучу priority_queue, less> maxHeap; cout << "\nНиже приведены тестовые примеры для max-heap" << 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 << "После построения min-heap из входного списка" << endl; printHeap(minHeap); return 0; } ================================================ FILE: ru/codes/cpp/chapter_heap/my_heap.cpp ================================================ /** * 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); // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» if (p < 0 || maxHeap[i] <= maxHeap[p]) break; // Поменять два узла местами 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; // Выполнить heapify для всех узлов, кроме листовых 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("куча пуста"); } // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) 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); } }; /* Driver Code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_heap/top_k.cpp ================================================ /** * 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; } // Driver Code 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; } ================================================ FILE: ru/codes/cpp/chapter_searching/CMakeLists.txt ================================================ add_executable(binary_search binary_search.cpp) add_executable(binary_search_insertion binary_search_insertion.cpp) add_executable(binary_search_edge binary_search_edge.cpp) add_executable(two_sum two_sum.cpp) ================================================ FILE: ru/codes/cpp/chapter_searching/binary_search.cpp ================================================ /** * 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 указывают на первый элемент массива и позицию сразу за последним элементом соответственно 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; } /* Driver Code */ int main() { int target = 6; vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; /* Бинарный поиск (двусторонне замкнутый интервал) */ int index = binarySearch(nums, target); cout << "Индекс целевого элемента 6 = " << index << endl; /* Бинарный поиск (лево замкнутый, право открытый интервал) */ index = binarySearchLCRO(nums, target); cout << "Индекс целевого элемента 6 = " << index << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_searching/binary_search_edge.cpp ================================================ /** * 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; // target находится в интервале [m+1, j] } else { j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] } } // Вернуть точку вставки i return i; } /* Бинарный поиск самого левого target */ 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; } /* Бинарный поиск самого правого target */ int binarySearchRightEdge(vector &nums, int target) { // Преобразовать задачу в поиск самого левого target + 1 int i = binarySearchInsertion(nums, target + 1); // j указывает на самый правый target, а i — на первый элемент больше target int j = i - 1; // target не найден, вернуть -1 if (j == -1 || nums[j] != target) { return -1; } // Найти target и вернуть индекс j return j; } /* Driver Code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_searching/binary_search_insertion.cpp ================================================ /** * 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; // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1; // target находится в интервале [i, m-1] } else { return m; // Найти target и вернуть точку вставки m } } // target не найден, вернуть точку вставки i return i; } /* Бинарный поиск точки вставки (с повторяющимися элементами) */ int binarySearchInsertion(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; // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1; // target находится в интервале [i, m-1] } else { j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] } } // Вернуть точку вставки i return i; } /* Driver Code */ int main() { // Массив без повторяющихся элементов 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; } ================================================ FILE: ru/codes/cpp/chapter_searching/hashing_search.cpp ================================================ /** * 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) { // key хеш-таблицы: целевой элемент, value: индекс // Если такого key нет в хеш-таблице, вернуть -1 if (map.find(target) == map.end()) return -1; return map[target]; } /* Хеш-поиск (связный список) */ ListNode *hashingSearchLinkedList(unordered_map map, int target) { // key хеш-таблицы: значение целевого узла, value: объект узла // Если такого key нет в хеш-таблице, вернуть nullptr if (map.find(target) == map.end()) return nullptr; return map[target]; } /* Driver Code */ 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; // key: элемент, value: индекс } int index = hashingSearchArray(map, target); cout << "Индекс целевого элемента 3 = " << index << endl; /* Хеш-поиск (связный список) */ ListNode *head = vecToLinkedList(nums); // Инициализация хеш-таблицы unordered_map map1; while (head != nullptr) { map1[head->val] = head; // key: значение узла, value: узел head = head->next; } ListNode *node = hashingSearchLinkedList(map1, target); cout << "Объект узла со значением 3 = " << node << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_searching/linear_search.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int target = 3; /* Выполнить линейный поиск в массиве */ vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; int index = linearSearchArray(nums, target); cout << "Индекс целевого элемента 3 = " << index << endl; /* Выполнить линейный поиск в связном списке */ ListNode *head = vecToLinkedList(nums); ListNode *node = linearSearchLinkedList(head, target); cout << "Объект узла со значением 3 = " << node << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_searching/two_sum.cpp ================================================ /** * File: two_sum.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Метод 1: полный перебор */ 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 {}; } /* Метод 2: вспомогательная хеш-таблица */ 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 {}; } /* Driver Code */ int main() { // ======= Test Case ======= vector nums = {2, 7, 11, 15}; int target = 13; // ====== Основной код ====== // Метод 1 vector res = twoSumBruteForce(nums, target); cout << "Результат метода 1 res = "; printVector(res); // Метод 2 res = twoSumHashTable(nums, target); cout << "Результат метода 2 res = "; printVector(res); return 0; } ================================================ FILE: ru/codes/cpp/chapter_sorting/CMakeLists.txt ================================================ add_executable(selection_sort selection_sort.cpp) add_executable(bubble_sort bubble_sort.cpp) add_executable(insertion_sort insertion_sort.cpp) add_executable(merge_sort merge_sort.cpp) add_executable(quick_sort quick_sort.cpp) add_executable(heap_sort heap_sort.cpp) ================================================ FILE: ru/codes/cpp/chapter_sorting/bubble_sort.cpp ================================================ /** * 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; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти } } /* Driver Code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_sorting/bucket_sort.cpp ================================================ /** * 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; // Добавить num в корзину 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; } } } /* Driver Code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_sorting/counting_sort.cpp ================================================ /** * 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 } // Перезаписать исходный массив nums массивом результата res nums = res; } /* Driver Code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_sorting/heap_sort.cpp ================================================ /** * 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) { // Построение кучи: выполнить heapify для всех узлов, кроме листовых 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); } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; heapSort(nums); cout << "После сортировки кучей nums = "; printVector(nums); return 0; } ================================================ FILE: ru/codes/cpp/chapter_sorting/insertion_sort.cpp ================================================ /** * 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 в правильную позицию } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; insertionSort(nums); cout << "После сортировки вставками nums = "; printVector(nums); return 0; } ================================================ FILE: ru/codes/cpp/chapter_sorting/merge_sort.cpp ================================================ /** * 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); } /* Driver Code */ int main() { /* Сортировка слиянием */ vector nums = {7, 3, 2, 6, 0, 1, 5, 4}; mergeSort(nums, 0, nums.size() - 1); cout << "После сортировки слиянием nums = "; printVector(nums); return 0; } ================================================ FILE: ru/codes/cpp/chapter_sorting/quick_sort.cpp ================================================ /** * File: quick_sort.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Класс быстрой сортировки */ class QuickSort { private: /* Разбиение с опорными указателями */ 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], nums[j]); // Поменять эти два элемента местами } swap(nums[i], nums[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 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], nums[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], nums[j]); // Поменять эти два элемента местами } swap(nums[i], nums[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 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], nums[j]); // Поменять эти два элемента местами } swap(nums[i], nums[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] } } } }; /* Driver Code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_sorting/radix_sort.cpp ================================================ /** * File: radix_sort.cpp * Created Time: 2023-03-26 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Получить k-й разряд элемента num, где exp = 10^(k-1) */ int digit(int num, int exp) { // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени return (num / exp) % 10; } /* Сортировка подсчетом (сортировка по k-му разряду nums) */ void countingSortDigit(vector &nums, int exp) { // Разряды десятичной системы лежат в диапазоне 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); // Получить k-й разряд nums[i], обозначив его как 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; // Получить индекс j цифры d в массиве 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); } /* Driver Code */ int main() { // Поразрядная сортировка vector nums = {10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996}; radixSort(nums); cout << "После поразрядной сортировки nums = "; printVector(nums); return 0; } ================================================ FILE: ru/codes/cpp/chapter_sorting/selection_sort.cpp ================================================ /** * 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]); } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; selectionSort(nums); cout << "После сортировки выбором nums = "; printVector(nums); return 0; } ================================================ FILE: ru/codes/cpp/chapter_stack_and_queue/CMakeLists.txt ================================================ add_executable(array_deque array_deque.cpp) add_executable(array_queue array_queue.cpp) add_executable(array_stack array_stack.cpp) add_executable(deque deque.cpp) add_executable(linkedlist_deque linkedlist_deque.cpp) add_executable(linkedlist_queue linkedlist_queue.cpp) add_executable(linkedlist_stack linkedlist_stack.cpp) add_executable(queue queue.cpp) add_executable(stack stack.cpp) ================================================ FILE: ru/codes/cpp/chapter_stack_and_queue/array_deque.cpp ================================================ /** * 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; // Указатель head, указывающий на первый элемент очереди 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 << "Двусторонняя очередь заполнена" << endl; return; } // Указатель головы сдвигается на одну позицию влево // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост front = index(front - 1); // Добавить num в голову очереди nums[front] = num; queSize++; } /* Добавление в хвост очереди */ void pushLast(int num) { if (queSize == capacity()) { cout << "Двусторонняя очередь заполнена" << endl; return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 int rear = index(front + queSize); // Добавить num в хвост очереди nums[rear] = num; queSize++; } /* Извлечение из головы очереди */ int popFirst() { int num = peekFirst(); // Указатель головы сдвигается на одну позицию назад front = index(front + 1); queSize--; return num; } /* Извлечение из хвоста очереди */ int popLast() { int num = peekLast(); queSize--; return num; } /* Доступ к элементу в начале очереди */ int peekFirst() { if (isEmpty()) throw out_of_range("двусторонняя очередь пуста"); return nums[front]; } /* Доступ к элементу в конце очереди */ int peekLast() { if (isEmpty()) throw out_of_range("двусторонняя очередь пуста"); // Вычислить индекс хвостового элемента int last = index(front + queSize - 1); return nums[last]; } /* Вернуть массив для вывода */ vector toVector() { // Преобразовывать только элементы списка в пределах фактической длины vector res(queSize); for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[index(j)]; } return res; } }; /* Driver Code */ int main() { /* Инициализация двусторонней очереди */ ArrayDeque *deque = new ArrayDeque(10); deque->pushLast(3); deque->pushLast(2); deque->pushLast(5); cout << "Двусторонняя очередь deque = "; printVector(deque->toVector()); /* Доступ к элементу */ int peekFirst = deque->peekFirst(); cout << "Первый элемент peekFirst = " << peekFirst << endl; int peekLast = deque->peekLast(); cout << "Последний элемент peekLast = " << peekLast << endl; /* Добавление элемента в очередь */ deque->pushLast(4); cout << "После добавления элемента 4 в хвост deque = "; printVector(deque->toVector()); deque->pushFirst(1); cout << "После добавления элемента 1 в голову deque = "; printVector(deque->toVector()); /* Извлечение элемента из очереди */ int popLast = deque->popLast(); cout << "Извлеченный из хвоста элемент = " << popLast << ", deque после извлечения из хвоста = "; printVector(deque->toVector()); int popFirst = deque->popFirst(); cout << "Извлеченный из головы элемент = " << popFirst << ", deque после извлечения из головы = "; printVector(deque->toVector()); /* Получение длины двусторонней очереди */ int size = deque->size(); cout << "Длина двусторонней очереди size = " << size << endl; /* Проверка, пуста ли двусторонняя очередь */ bool isEmpty = deque->isEmpty(); cout << "Пуста ли двусторонняя очередь = " << boolalpha << isEmpty << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_stack_and_queue/array_queue.cpp ================================================ /** * 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; // Указатель head, указывающий на первый элемент очереди 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 << "Очередь заполнена" << endl; return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива int rear = (front + queSize) % queCapacity; // Добавить num в хвост очереди nums[rear] = num; queSize++; } /* Извлечь из очереди */ int pop() { int num = peek(); // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива front = (front + 1) % queCapacity; queSize--; return num; } /* Доступ к элементу в начале очереди */ int peek() { if (isEmpty()) throw out_of_range("очередь пуста"); 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; } }; /* Driver Code */ int main() { /* Инициализация очереди */ int capacity = 10; ArrayQueue *queue = new ArrayQueue(capacity); /* Добавление элемента в очередь */ queue->push(1); queue->push(3); queue->push(2); queue->push(5); queue->push(4); cout << "Очередь queue = "; printVector(queue->toVector()); /* Доступ к элементу в начале очереди */ int peek = queue->peek(); cout << "Первый элемент peek = " << peek << endl; /* Извлечение элемента из очереди */ peek = queue->pop(); cout << "Извлеченный элемент pop = " << peek << ", queue после извлечения = "; printVector(queue->toVector()); /* Получение длины очереди */ int size = queue->size(); cout << "Длина очереди size = " << size << endl; /* Проверка, пуста ли очередь */ bool empty = queue->isEmpty(); cout << "Пуста ли очередь = " << empty << endl; /* Проверка кольцевого массива */ for (int i = 0; i < 10; i++) { queue->push(i); queue->pop(); cout << "После " << i << "-го раунда операций enqueue и dequeue queue = "; printVector(queue->toVector()); } // Освободить память delete queue; return 0; } ================================================ FILE: ru/codes/cpp/chapter_stack_and_queue/array_stack.cpp ================================================ /** * 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("стек пуст"); return stack.back(); } /* Вернуть Vector */ vector toVector() { return stack; } }; /* Driver Code */ int main() { /* Инициализация стека */ ArrayStack *stack = new ArrayStack(); /* Помещение элемента в стек */ stack->push(1); stack->push(3); stack->push(2); stack->push(5); stack->push(4); cout << "Стек stack = "; printVector(stack->toVector()); /* Доступ к верхнему элементу стека */ int top = stack->top(); cout << "Верхний элемент top = " << top << endl; /* Извлечение элемента из стека */ top = stack->pop(); cout << "Извлеченный элемент pop = " << top << ", stack после извлечения = "; printVector(stack->toVector()); /* Получение длины стека */ int size = stack->size(); cout << "Длина стека size = " << size << endl; /* Проверка на пустоту */ bool empty = stack->isEmpty(); cout << "Пуст ли стек = " << empty << endl; // Освободить память delete stack; return 0; } ================================================ FILE: ru/codes/cpp/chapter_stack_and_queue/deque.cpp ================================================ /** * File: deque.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* Инициализация двусторонней очереди */ deque deque; /* Добавление элемента в очередь */ deque.push_back(2); deque.push_back(5); deque.push_back(4); deque.push_front(3); deque.push_front(1); cout << "Двусторонняя очередь deque = "; printDeque(deque); /* Доступ к элементу */ int front = deque.front(); cout << "Первый элемент front = " << front << endl; int back = deque.back(); cout << "Последний элемент back = " << back << endl; /* Извлечение элемента из очереди */ deque.pop_front(); cout << "Извлеченный из головы элемент popFront = " << front << ", deque после извлечения из головы = "; printDeque(deque); deque.pop_back(); cout << "Извлеченный из хвоста элемент popLast = " << back << ", deque после извлечения из хвоста = "; printDeque(deque); /* Получение длины двусторонней очереди */ int size = deque.size(); cout << "Длина двусторонней очереди size = " << size << endl; /* Проверка, пуста ли двусторонняя очередь */ bool empty = deque.empty(); cout << "Пуста ли двусторонняя очередь = " << empty << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp ================================================ /** * 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) { // Добавить node в голову списка front->prev = node; node->next = front; front = node; // Обновить головной узел // Операция добавления в хвост очереди } else { // Добавить node в хвост списка rear->next = node; node->prev = rear; rear = node; // Обновить хвостовой узел } queSize++; // Обновить длину очереди } /* Добавление в голову очереди */ void pushFirst(int num) { push(num, true); } /* Добавление в хвост очереди */ void pushLast(int num) { push(num, false); } /* Операция извлечения из очереди */ int pop(bool isFront) { if (isEmpty()) throw out_of_range("очередь пуста"); 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("двусторонняя очередь пуста"); return front->val; } /* Доступ к элементу в конце очереди */ int peekLast() { if (isEmpty()) throw out_of_range("двусторонняя очередь пуста"); 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; } }; /* Driver Code */ int main() { /* Инициализация двусторонней очереди */ LinkedListDeque *deque = new LinkedListDeque(); deque->pushLast(3); deque->pushLast(2); deque->pushLast(5); cout << "Двусторонняя очередь deque = "; printVector(deque->toVector()); /* Доступ к элементу */ int peekFirst = deque->peekFirst(); cout << "Первый элемент peekFirst = " << peekFirst << endl; int peekLast = deque->peekLast(); cout << "Последний элемент peekLast = " << peekLast << endl; /* Добавление элемента в очередь */ deque->pushLast(4); cout << "После добавления элемента 4 в хвост deque ="; printVector(deque->toVector()); deque->pushFirst(1); cout << "После добавления элемента 1 в голову deque = "; printVector(deque->toVector()); /* Извлечение элемента из очереди */ int popLast = deque->popLast(); cout << "Извлеченный из хвоста элемент = " << popLast << ", deque после извлечения из хвоста = "; printVector(deque->toVector()); int popFirst = deque->popFirst(); cout << "Извлеченный из головы элемент = " << popFirst << ", deque после извлечения из головы = "; printVector(deque->toVector()); /* Получение длины двусторонней очереди */ int size = deque->size(); cout << "Длина двусторонней очереди size = " << size << endl; /* Проверка, пуста ли двусторонняя очередь */ bool isEmpty = deque->isEmpty(); cout << "Пуста ли двусторонняя очередь = " << boolalpha << isEmpty << endl; // Освободить память delete deque; return 0; } ================================================ FILE: ru/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp ================================================ /** * 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); // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел 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("очередь пуста"); 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; } }; /* Driver Code */ int main() { /* Инициализация очереди */ LinkedListQueue *queue = new LinkedListQueue(); /* Добавление элемента в очередь */ queue->push(1); queue->push(3); queue->push(2); queue->push(5); queue->push(4); cout << "Очередь queue = "; printVector(queue->toVector()); /* Доступ к элементу в начале очереди */ int peek = queue->peek(); cout << "Первый элемент peek = " << peek << endl; /* Извлечение элемента из очереди */ peek = queue->pop(); cout << "Извлеченный элемент pop = " << peek << ", queue после извлечения = "; printVector(queue->toVector()); /* Получение длины очереди */ int size = queue->size(); cout << "Длина очереди size = " << size << endl; /* Проверка, пуста ли очередь */ bool empty = queue->isEmpty(); cout << "Пуста ли очередь = " << empty << endl; // Освободить память delete queue; return 0; } ================================================ FILE: ru/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp ================================================ /** * 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("стек пуст"); return stackTop->val; } /* Преобразовать List в Array и вернуть */ 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; } }; /* Driver Code */ int main() { /* Инициализация стека */ LinkedListStack *stack = new LinkedListStack(); /* Помещение элемента в стек */ stack->push(1); stack->push(3); stack->push(2); stack->push(5); stack->push(4); cout << "Стек stack = "; printVector(stack->toVector()); /* Доступ к верхнему элементу стека */ int top = stack->top(); cout << "Верхний элемент top = " << top << endl; /* Извлечение элемента из стека */ top = stack->pop(); cout << "Извлеченный элемент pop = " << top << ", stack после извлечения = "; printVector(stack->toVector()); /* Получение длины стека */ int size = stack->size(); cout << "Длина стека size = " << size << endl; /* Проверка на пустоту */ bool empty = stack->isEmpty(); cout << "Пуст ли стек = " << empty << endl; // Освободить память delete stack; return 0; } ================================================ FILE: ru/codes/cpp/chapter_stack_and_queue/queue.cpp ================================================ /** * File: queue.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* Инициализация очереди */ queue queue; /* Добавление элемента в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); cout << "Очередь queue = "; printQueue(queue); /* Доступ к элементу в начале очереди */ int front = queue.front(); cout << "Первый элемент front = " << front << endl; /* Извлечение элемента из очереди */ queue.pop(); cout << "Извлеченный элемент front = " << front << ", queue после извлечения = "; printQueue(queue); /* Получение длины очереди */ int size = queue.size(); cout << "Длина очереди size = " << size << endl; /* Проверка, пуста ли очередь */ bool empty = queue.empty(); cout << "Пуста ли очередь = " << empty << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_stack_and_queue/stack.cpp ================================================ /** * File: stack.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* Инициализация стека */ stack stack; /* Помещение элемента в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); cout << "Стек stack = "; printStack(stack); /* Доступ к верхнему элементу стека */ int top = stack.top(); cout << "Верхний элемент top = " << top << endl; /* Извлечение элемента из стека */ stack.pop(); // Без возвращаемого значения cout << "Извлеченный элемент pop = " << top << ", stack после извлечения = "; printStack(stack); /* Получение длины стека */ int size = stack.size(); cout << "Длина стека size = " << size << endl; /* Проверка на пустоту */ bool empty = stack.empty(); cout << "Пуст ли стек = " << empty << endl; return 0; } ================================================ FILE: ru/codes/cpp/chapter_tree/CMakeLists.txt ================================================ add_executable(avl_tree avl_tree.cpp) add_executable(binary_search_tree binary_search_tree.cpp) add_executable(binary_tree binary_tree.cpp) add_executable(binary_tree_bfs binary_tree_bfs.cpp) add_executable(binary_tree_dfs binary_tree_dfs.cpp) add_executable(array_binary_tree array_binary_tree.cpp) ================================================ FILE: ru/codes/cpp/chapter_tree/array_binary_tree.cpp ================================================ /** * 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, обозначающий пустую позицию 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)); } }; /* Driver Code */ 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 << ", значение = " << (abt.val(l) != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n"; cout << "Индекс правого дочернего узла = " << r << ", значение = " << (abt.val(r) != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n"; cout << "Индекс родительского узла = " << p << ", значение = " << (abt.val(p) != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n"; // Обходить дерево vector res = abt.levelOrder(); cout << "\nОбход в ширину: "; printVector(res); res = abt.preOrder(); cout << "Предварительный обход: "; printVector(res); res = abt.inOrder(); cout << "Симметричный обход: "; printVector(res); res = abt.postOrder(); cout << "Обратный обход: "; printVector(res); return 0; } ================================================ FILE: ru/codes/cpp/chapter_tree/avl_tree.cpp ================================================ /** * 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; // Выполнить правое вращение узла node вокруг child child->right = node; node->left = grandChild; // Обновить высоту узла updateHeight(node); updateHeight(child); // Вернуть корневой узел поддерева после вращения return child; } /* Операция левого вращения */ TreeNode *leftRotate(TreeNode *node) { TreeNode *child = node->right; TreeNode *grandChild = child->left; // Выполнить левое вращение узла node вокруг child 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, удалить node и сразу вернуть if (child == nullptr) { delete node; return nullptr; } // Число дочерних узлов = 1, удалить node напрямую 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); } /* Driver Code */ 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; } ================================================ FILE: ru/codes/cpp/chapter_tree/binary_search_tree.cpp ================================================ /** * 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; } } }; /* Driver Code */ int main() { /* Инициализация двоичного дерева поиска */ BinarySearchTree *bst = new BinarySearchTree(); // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево vector nums = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; for (int num : nums) { bst->insert(num); } cout << endl << "Исходное двоичное дерево\n" << endl; 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; } ================================================ FILE: ru/codes/cpp/chapter_tree/binary_tree.cpp ================================================ /** * File: binary_tree.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ 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); // Вставить узел P между n1 -> n2 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; } ================================================ FILE: ru/codes/cpp/chapter_tree/binary_tree_bfs.cpp ================================================ /** * 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; } /* Driver Code */ int main() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "Инициализация двоичного дерева\n" << endl; printTree(root); /* Обход в ширину */ vector vec = levelOrder(root); cout << endl << "Последовательность печати узлов при обходе в ширину = "; printVector(vec); return 0; } ================================================ FILE: ru/codes/cpp/chapter_tree/binary_tree_dfs.cpp ================================================ /** * 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); } /* Driver Code */ int main() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "Инициализация двоичного дерева\n" << endl; printTree(root); /* Предварительный обход */ vec.clear(); preOrder(root); cout << endl << "Последовательность печати узлов при предварительном обходе = "; printVector(vec); /* Симметричный обход */ vec.clear(); inOrder(root); cout << endl << "Последовательность печати узлов при симметричном обходе = "; printVector(vec); /* Обратный обход */ vec.clear(); postOrder(root); cout << endl << "Последовательность печати узлов при обратном обходе = "; printVector(vec); return 0; } ================================================ FILE: ru/codes/cpp/utils/CMakeLists.txt ================================================ add_executable(utils common.hpp print_utils.hpp list_node.hpp tree_node.hpp vertex.hpp) ================================================ FILE: ru/codes/cpp/utils/common.hpp ================================================ /** * 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; ================================================ FILE: ru/codes/cpp/utils/list_node.hpp ================================================ /** * 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; } } ================================================ FILE: ru/codes/cpp/utils/print_utils.hpp ================================================ /** * 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 /* Find an element in a vector */ template int vecFind(const vector &vec, T ele) { int j = INT_MAX; for (int i = 0; i < vec.size(); i++) { if (vec[i] == ele) { j = i; } } return j; } /* Concatenate a vector with a delim */ 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(); } /* Repeat a string for n times */ 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; } /* Get the Vector String object */ template string getVectorString(vector &list) { return "[" + strJoin(", ", list) + "]"; } /* Вывести список */ template void printVector(vector list) { cout << getVectorString(list) << '\n'; } /* Вывести матрицу */ template void printVectorMatrix(vector> &matrix) { cout << "[" << '\n'; for (vector &list : matrix) 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) { // Reverse the input stack stack tmp; while (!stk.empty()) { tmp.push(stk.top()); stk.pop(); } // Generate the string to print ostringstream s; bool flag = true; while (!tmp.empty()) { if (flag) { s << tmp.top(); flag = false; } else s << ", " << tmp.top(); tmp.pop(); } cout << "[" + s.str() + "]" << '\n'; } /* Вывести очередь */ template void printQueue(queue queue) { // Generate the string to print 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) { // Generate the string to print 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'; } } /* Expose the underlying storage of the priority_queue container */ template S &Container(priority_queue &pq) { struct HackedQueue : private priority_queue { static S &Container(priority_queue &pq) { 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); } ================================================ FILE: ru/codes/cpp/utils/tree_node.hpp ================================================ /** * 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; } ================================================ FILE: ru/codes/cpp/utils/vertex.hpp ================================================ /** * 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; } ================================================ FILE: ru/codes/csharp/.editorconfig ================================================ # CSharp formatting rules [*.cs] csharp_new_line_before_open_brace = none csharp_new_line_before_else = false csharp_new_line_before_catch = false csharp_new_line_before_finally = false csharp_indent_labels = one_less_than_current csharp_using_directive_placement = outside_namespace:silent csharp_prefer_simple_using_statement = true:suggestion csharp_prefer_braces = true:silent csharp_style_namespace_declarations = block_scoped:silent csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent csharp_style_prefer_primary_constructors = true:suggestion csharp_style_expression_bodied_methods = false:silent csharp_style_expression_bodied_constructors = false:silent csharp_style_expression_bodied_operators = false:silent csharp_style_expression_bodied_properties = true:silent csharp_style_expression_bodied_indexers = true:silent csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_lambdas = true:silent # CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language. dotnet_diagnostic.CS8981.severity = silent # IDE1006: Naming Styles dotnet_diagnostic.IDE1006.severity = silent # CA1822: Mark members as static dotnet_diagnostic.CA1822.severity = silent [*.{cs,vb}] #### Naming styles #### # Naming rules dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion dotnet_naming_rule.types_should_be_pascal_case.symbols = types dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case # Symbol specifications dotnet_naming_symbols.interface.applicable_kinds = interface dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.interface.required_modifiers = dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.types.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.non_field_members.required_modifiers = # Naming styles dotnet_naming_style.begins_with_i.required_prefix = I dotnet_naming_style.begins_with_i.required_suffix = dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_style_operator_placement_when_wrapping = beginning_of_line tab_width = 4 indent_size = 4 end_of_line = crlf # IDE0040: Add accessibility modifiers dotnet_diagnostic.IDE0040.severity = silent # IDE0044: Add readonly modifier dotnet_diagnostic.IDE0044.severity = silent ================================================ FILE: ru/codes/csharp/.gitignore ================================================ .idea/ .vs/ obj/ .Debug bin/ ================================================ FILE: ru/codes/csharp/GlobalUsing.cs ================================================ global using NUnit.Framework; global using hello_algo.utils; global using System.Text; ================================================ FILE: ru/codes/csharp/chapter_array_and_linkedlist/array.cs ================================================ // File: array.cs // Created Time: 2022-12-14 // Author: mingXta (1195669834@qq.com) namespace hello_algo.chapter_array_and_linkedlist; public class array { /* Случайный доступ к элементу */ int RandomAccess(int[] nums) { Random random = new(); // Случайным образом выбрать число из интервала [0, nums.Length) int randomIndex = random.Next(nums.Length); // Получить и вернуть случайный элемент int randomNum = nums[randomIndex]; return randomNum; } /* Увеличить длину массива */ int[] Extend(int[] nums, int enlarge) { // Инициализировать массив увеличенной длины int[] res = new int[nums.Length + enlarge]; // Скопировать все элементы исходного массива в новый массив for (int i = 0; i < nums.Length; i++) { res[i] = nums[i]; } // Вернуть новый массив после расширения return res; } /* Вставить элемент num по индексу index в массив */ void Insert(int[] nums, int num, int index) { // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад for (int i = nums.Length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Присвоить num элементу по индексу index nums[index] = num; } /* Удалить элемент по индексу index */ void Remove(int[] nums, int index) { // Сдвинуть все элементы после индекса index на одну позицию вперед for (int i = index; i < nums.Length - 1; i++) { nums[i] = nums[i + 1]; } } /* Обход массива */ void Traverse(int[] nums) { int count = 0; // Обход массива по индексам for (int i = 0; i < nums.Length; i++) { count += nums[i]; } // Непосредственно обходить элементы массива foreach (int num in nums) { count += num; } } /* Найти заданный элемент в массиве */ int Find(int[] nums, int target) { for (int i = 0; i < nums.Length; i++) { if (nums[i] == target) return i; } return -1; } /* Вспомогательная функция: преобразовать массив в строку */ string ToString(int[] nums) { return string.Join(",", nums); } [Test] public void Test() { // Инициализация массива int[] arr = new int[5]; Console.WriteLine("Массив arr = " + ToString(arr)); int[] nums = [1, 3, 2, 5, 4]; Console.WriteLine("Массив nums = " + ToString(nums)); // Случайный доступ int randomNum = RandomAccess(nums); Console.WriteLine("Случайный элемент из nums = " + randomNum); // Расширение длины nums = Extend(nums, 3); Console.WriteLine("После увеличения длины массива до 8 nums = " + ToString(nums)); // Вставка элемента Insert(nums, 6, 3); Console.WriteLine("После вставки числа 6 по индексу 3 nums = " + ToString(nums)); // Удаление элемента Remove(nums, 2); Console.WriteLine("После удаления элемента по индексу 2 nums = " + ToString(nums)); // Обход массива Traverse(nums); // Поиск элемента int index = Find(nums, 3); Console.WriteLine("Поиск элемента 3 в nums: индекс = " + index); } } ================================================ FILE: ru/codes/csharp/chapter_array_and_linkedlist/linked_list.cs ================================================ // File: linked_list.cs // Created Time: 2022-12-16 // Author: mingXta (1195669834@qq.com) namespace hello_algo.chapter_array_and_linkedlist; public class linked_list { /* Вставить узел P после узла n0 в связном списке */ void Insert(ListNode n0, ListNode P) { ListNode? n1 = n0.next; P.next = n1; n0.next = P; } /* Удалить первый узел после узла n0 в связном списке */ void Remove(ListNode n0) { if (n0.next == null) return; // n0 -> P -> n1 ListNode P = n0.next; ListNode? n1 = P.next; n0.next = n1; } /* Доступ к узлу связного списка по индексу index */ ListNode? Access(ListNode? head, int index) { for (int i = 0; i < index; i++) { if (head == null) return null; head = head.next; } return head; } /* Найти в связном списке первый узел со значением target */ int Find(ListNode? head, int target) { int index = 0; while (head != null) { if (head.val == target) return index; head = head.next; index++; } return -1; } [Test] public void Test() { // Инициализация связного списка // Инициализация всех узлов ListNode n0 = new(1); ListNode n1 = new(3); ListNode n2 = new(2); ListNode n3 = new(5); ListNode n4 = new(4); // Построить ссылки между узлами n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; Console.WriteLine($"Исходный связный список: {n0}"); // Вставка узла Insert(n0, new ListNode(0)); Console.WriteLine($"Связный список после вставки узла: {n0}"); // Удаление узла Remove(n0); Console.WriteLine($"Связный список после удаления узла: {n0}"); // Доступ к узлу ListNode? node = Access(n0, 3); Console.WriteLine($"Значение узла по индексу 3 в связном списке = {node?.val}"); // Поиск узла int index = Find(n0, 2); Console.WriteLine($"Индекс узла со значением 2 в связном списке = {index}"); } } ================================================ FILE: ru/codes/csharp/chapter_array_and_linkedlist/list.cs ================================================ /** * File: list.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_array_and_linkedlist; public class list { [Test] public void Test() { /* Инициализация списка */ int[] numbers = [1, 3, 2, 5, 4]; List nums = [.. numbers]; Console.WriteLine("Список nums = " + string.Join(",", nums)); /* Доступ к элементу */ int num = nums[1]; Console.WriteLine("Элемент по индексу 1: num = " + num); /* Обновление элемента */ nums[1] = 0; Console.WriteLine("После обновления элемента по индексу 1 до 0 nums = " + string.Join(",", nums)); /* Очистить список */ nums.Clear(); Console.WriteLine("После очистки списка nums = " + string.Join(",", nums)); /* Добавление элемента в конец */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); Console.WriteLine("После добавления элементов nums = " + string.Join(",", nums)); /* Вставка элемента в середину */ nums.Insert(3, 6); Console.WriteLine("После вставки числа 6 по индексу 3 nums = " + string.Join(",", nums)); /* Удаление элемента */ nums.RemoveAt(3); Console.WriteLine("После удаления элемента по индексу 3 nums = " + string.Join(",", nums)); /* Обходить список по индексам */ int count = 0; for (int i = 0; i < nums.Count; i++) { count += nums[i]; } /* Непосредственно обходить элементы списка */ count = 0; foreach (int x in nums) { count += x; } /* Объединить два списка */ List nums1 = [6, 8, 7, 10, 9]; nums.AddRange(nums1); Console.WriteLine("После конкатенации списка nums1 к nums nums = " + string.Join(",", nums)); /* Отсортировать список */ nums.Sort(); // После сортировки элементы списка располагаются по возрастанию Console.WriteLine("После сортировки списка nums = " + string.Join(",", nums)); } } ================================================ FILE: ru/codes/csharp/chapter_array_and_linkedlist/my_list.cs ================================================ /** * File: my_list.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_array_and_linkedlist; /* Класс списка */ class MyList { private int[] arr; // Массив (для хранения элементов списка) private int arrCapacity = 10; // Вместимость списка private int arrSize = 0; // Длина списка (текущее число элементов) private readonly int extendRatio = 2; // Коэффициент увеличения списка при каждом расширении /* Конструктор */ public MyList() { arr = new int[arrCapacity]; } /* Получить длину списка (текущее число элементов) */ public int Size() { return arrSize; } /* Получить вместимость списка */ public int Capacity() { return arrCapacity; } /* Доступ к элементу */ public int Get(int index) { // Если индекс выходит за границы, выбрасывается исключение; далее аналогично if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("индекс выходит за границы"); return arr[index]; } /* Обновление элемента */ public void Set(int index, int num) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("индекс выходит за границы"); arr[index] = num; } /* Добавление элемента в конец */ public void Add(int num) { // При превышении вместимости по числу элементов запускается расширение if (arrSize == arrCapacity) ExtendCapacity(); arr[arrSize] = num; // Обновить число элементов arrSize++; } /* Вставка элемента в середину */ public void Insert(int index, int num) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("индекс выходит за границы"); // При превышении вместимости по числу элементов запускается расширение if (arrSize == arrCapacity) ExtendCapacity(); // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад for (int j = arrSize - 1; j >= index; j--) { arr[j + 1] = arr[j]; } arr[index] = num; // Обновить число элементов arrSize++; } /* Удаление элемента */ public int Remove(int index) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("индекс выходит за границы"); int num = arr[index]; // Сдвинуть все элементы после индекса index на одну позицию вперед for (int j = index; j < arrSize - 1; j++) { arr[j] = arr[j + 1]; } // Обновить число элементов arrSize--; // Вернуть удаленный элемент return num; } /* Расширение списка */ public void ExtendCapacity() { // Создать новый массив длиной arrCapacity * extendRatio и скопировать в него исходный массив Array.Resize(ref arr, arrCapacity * extendRatio); // Обновить вместимость списка arrCapacity = arr.Length; } /* Преобразовать список в массив */ public int[] ToArray() { // Преобразовывать только элементы списка в пределах фактической длины int[] arr = new int[arrSize]; for (int i = 0; i < arrSize; i++) { arr[i] = Get(i); } return arr; } } public class my_list { [Test] public void Test() { /* Инициализация списка */ MyList nums = new(); /* Добавление элемента в конец */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); Console.WriteLine("Список nums = " + string.Join(",", nums.ToArray()) + ", вместимость = " + nums.Capacity() + " , длина = " + nums.Size()); /* Вставка элемента в середину */ nums.Insert(3, 6); Console.WriteLine("После вставки числа 6 по индексу 3 nums = " + string.Join(",", nums.ToArray())); /* Удаление элемента */ nums.Remove(3); Console.WriteLine("После удаления элемента по индексу 3 nums = " + string.Join(",", nums.ToArray())); /* Доступ к элементу */ int num = nums.Get(1); Console.WriteLine("Элемент по индексу 1: num = " + num); /* Обновление элемента */ nums.Set(1, 0); Console.WriteLine("После обновления элемента по индексу 1 до 0 nums = " + string.Join(",", nums.ToArray())); /* Проверка механизма расширения */ for (int i = 0; i < 10; i++) { // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения nums.Add(i); } Console.WriteLine("Список nums после увеличения вместимости = " + string.Join(",", nums.ToArray()) + ", вместимость = " + nums.Capacity() + " , длина = " + nums.Size()); } } ================================================ FILE: ru/codes/csharp/chapter_backtracking/n_queens.cs ================================================ /** * File: n_queens.cs * Created Time: 2023-05-04 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class n_queens { /* Алгоритм бэктрекинга: n ферзей */ void Backtrack(int row, int n, List> state, List>> res, bool[] cols, bool[] diags1, bool[] diags2) { // Когда все строки уже обработаны, записать решение if (row == n) { List> copyState = []; foreach (List sRow in state) { copyState.Add(new List(sRow)); } res.Add(copyState); return; } // Обойти все столбцы for (int col = 0; col < n; col++) { // Вычислить главную и побочную диагонали, соответствующие этой клетке int diag1 = row - col + n - 1; int diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Попытка: поставить ферзя в эту клетку state[row][col] = "Q"; cols[col] = diags1[diag1] = diags2[diag2] = true; // Перейти к размещению следующей строки Backtrack(row + 1, n, state, res, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state[row][col] = "#"; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Решить задачу о n ферзях */ List>> NQueens(int n) { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку List> state = []; for (int i = 0; i < n; i++) { List row = []; for (int j = 0; j < n; j++) { row.Add("#"); } state.Add(row); } bool[] cols = new bool[n]; // Отмечать, есть ли ферзь в столбце bool[] diags1 = new bool[2 * n - 1]; // Отмечать наличие ферзя на главной диагонали bool[] diags2 = new bool[2 * n - 1]; // Отмечать наличие ферзя на побочной диагонали List>> res = []; Backtrack(0, n, state, res, cols, diags1, diags2); return res; } [Test] public void Test() { int n = 4; List>> res = NQueens(n); Console.WriteLine("Размер входной доски = " + n); Console.WriteLine("Количество способов расстановки ферзей: " + res.Count); foreach (List> state in res) { Console.WriteLine("--------------------"); foreach (List row in state) { PrintUtil.PrintList(row); } } } } ================================================ FILE: ru/codes/csharp/chapter_backtracking/permutations_i.cs ================================================ /** * File: permutations_i.cs * Created Time: 2023-04-24 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class permutations_i { /* Алгоритм бэктрекинга: все перестановки I */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // Когда длина состояния равна числу элементов, записать решение if (state.Count == choices.Length) { res.Add(new List(state)); return; } // Перебор всех вариантов выбора for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true; state.Add(choice); // Перейти к следующему выбору Backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* Все перестановки I */ List> PermutationsI(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } [Test] public void Test() { int[] nums = [1, 2, 3]; List> res = PermutationsI(nums); Console.WriteLine("Входной массив nums = " + string.Join(", ", nums)); Console.WriteLine("Все перестановки res = "); foreach (List permutation in res) { PrintUtil.PrintList(permutation); } } } ================================================ FILE: ru/codes/csharp/chapter_backtracking/permutations_ii.cs ================================================ /** * File: permutations_ii.cs * Created Time: 2023-04-24 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class permutations_ii { /* Алгоритм бэктрекинга: все перестановки II */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // Когда длина состояния равна числу элементов, записать решение if (state.Count == choices.Length) { res.Add(new List(state)); return; } // Перебор всех вариантов выбора HashSet duplicated = []; for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && !duplicated.Contains(choice)) { // Попытка: сделать выбор и обновить состояние duplicated.Add(choice); // Записать значения уже выбранных элементов selected[i] = true; state.Add(choice); // Перейти к следующему выбору Backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* Все перестановки II */ List> PermutationsII(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } [Test] public void Test() { int[] nums = [1, 2, 2]; List> res = PermutationsII(nums); Console.WriteLine("Входной массив nums = " + string.Join(", ", nums)); Console.WriteLine("Все перестановки res = "); foreach (List permutation in res) { PrintUtil.PrintList(permutation); } } } ================================================ FILE: ru/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs ================================================ /** * File: preorder_traversal_i_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_i_compact { List res = []; /* Предварительный обход: пример 1 */ void PreOrder(TreeNode? root) { if (root == null) { return; } if (root.val == 7) { // Записать решение res.Add(root); } PreOrder(root.left); PreOrder(root.right); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\nИнициализация двоичного дерева"); PrintUtil.PrintTree(root); // Предварительный обход PreOrder(root); Console.WriteLine("\nВсе узлы со значением 7"); PrintUtil.PrintList(res.Select(p => p.val).ToList()); } } ================================================ FILE: ru/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs ================================================ /** * File: preorder_traversal_ii_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_ii_compact { List path = []; List> res = []; /* Предварительный обход: пример 2 */ void PreOrder(TreeNode? root) { if (root == null) { return; } // Попытка path.Add(root); if (root.val == 7) { // Записать решение res.Add(new List(path)); } PreOrder(root.left); PreOrder(root.right); // Откат path.RemoveAt(path.Count - 1); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\nИнициализация двоичного дерева"); PrintUtil.PrintTree(root); // Предварительный обход PreOrder(root); Console.WriteLine("\nВсе пути от корня к узлу 7"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: ru/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs ================================================ /** * File: preorder_traversal_iii_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_iii_compact { List path = []; List> res = []; /* Предварительный обход: пример 3 */ void PreOrder(TreeNode? root) { // Отсечение if (root == null || root.val == 3) { return; } // Попытка path.Add(root); if (root.val == 7) { // Записать решение res.Add(new List(path)); } PreOrder(root.left); PreOrder(root.right); // Откат path.RemoveAt(path.Count - 1); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\nИнициализация двоичного дерева"); PrintUtil.PrintTree(root); // Предварительный обход PreOrder(root); Console.WriteLine("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: ru/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs ================================================ /** * File: preorder_traversal_iii_template.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_iii_template { /* Проверить, является ли текущее состояние решением */ bool IsSolution(List state) { return state.Count != 0 && state[^1].val == 7; } /* Записать решение */ void RecordSolution(List state, List> res) { res.Add(new List(state)); } /* Проверить, допустим ли этот выбор в текущем состоянии */ bool IsValid(List state, TreeNode choice) { return choice != null && choice.val != 3; } /* Обновить состояние */ void MakeChoice(List state, TreeNode choice) { state.Add(choice); } /* Восстановить состояние */ void UndoChoice(List state, TreeNode choice) { state.RemoveAt(state.Count - 1); } /* Алгоритм бэктрекинга: пример 3 */ void Backtrack(List state, List choices, List> res) { // Проверить, является ли текущее состояние решением if (IsSolution(state)) { // Записать решение RecordSolution(state, res); } // Перебор всех вариантов выбора foreach (TreeNode choice in choices) { // Отсечение: проверить допустимость выбора if (IsValid(state, choice)) { // Попытка: сделать выбор и обновить состояние MakeChoice(state, choice); // Перейти к следующему выбору Backtrack(state, [choice.left!, choice.right!], res); // Откат: отменить выбор и восстановить предыдущее состояние UndoChoice(state, choice); } } } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\nИнициализация двоичного дерева"); PrintUtil.PrintTree(root); // Алгоритм бэктрекинга List> res = []; List choices = [root!]; Backtrack([], choices, res); Console.WriteLine("\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: ru/codes/csharp/chapter_backtracking/subset_sum_i.cs ================================================ /** * File: subset_sum_i.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_i { /* Алгоритм бэктрекинга: сумма подмножеств I */ void Backtrack(List state, int target, int[] choices, int start, List> res) { // Если сумма подмножества равна target, записать решение if (target == 0) { res.Add(new List(state)); return; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств for (int i = start; i < choices.Length; i++) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if (target - choices[i] < 0) { break; } // Попытка: сделать выбор и обновить target и start state.Add(choices[i]); // Перейти к следующему выбору Backtrack(state, target - choices[i], choices, i, res); // Откат: отменить выбор и восстановить предыдущее состояние state.RemoveAt(state.Count - 1); } } /* Решить задачу суммы подмножеств I */ List> SubsetSumI(int[] nums, int target) { List state = []; // Состояние (подмножество) Array.Sort(nums); // Отсортировать nums int start = 0; // Стартовая вершина обхода List> res = []; // Список результатов (список подмножеств) Backtrack(state, target, nums, start, res); return res; } [Test] public void Test() { int[] nums = [3, 4, 5]; int target = 9; List> res = SubsetSumI(nums, target); Console.WriteLine("Входной массив nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("Все подмножества с суммой " + target + ": res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } } } ================================================ FILE: ru/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs ================================================ /** * File: subset_sum_i_naive.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_i_naive { /* Алгоритм бэктрекинга: сумма подмножеств I */ void Backtrack(List state, int target, int total, int[] choices, List> res) { // Если сумма подмножества равна target, записать решение if (total == target) { res.Add(new List(state)); return; } // Перебор всех вариантов выбора for (int i = 0; i < choices.Length; i++) { // Отсечение: если сумма подмножества превышает target, пропустить этот выбор if (total + choices[i] > target) { continue; } // Попытка: сделать выбор и обновить элемент и total state.Add(choices[i]); // Перейти к следующему выбору Backtrack(state, target, total + choices[i], choices, res); // Откат: отменить выбор и восстановить предыдущее состояние state.RemoveAt(state.Count - 1); } } /* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ List> SubsetSumINaive(int[] nums, int target) { List state = []; // Состояние (подмножество) int total = 0; // Сумма подмножеств List> res = []; // Список результатов (список подмножеств) Backtrack(state, target, total, nums, res); return res; } [Test] public void Test() { int[] nums = [3, 4, 5]; int target = 9; List> res = SubsetSumINaive(nums, target); Console.WriteLine("Входной массив nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("Все подмножества с суммой " + target + ": res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } Console.WriteLine("Обратите внимание: результат этого метода содержит повторяющиеся множества"); } } ================================================ FILE: ru/codes/csharp/chapter_backtracking/subset_sum_ii.cs ================================================ /** * File: subset_sum_ii.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_ii { /* Алгоритм бэктрекинга: сумма подмножеств II */ void Backtrack(List state, int target, int[] choices, int start, List> res) { // Если сумма подмножества равна target, записать решение if (target == 0) { res.Add(new List(state)); return; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента for (int i = start; i < choices.Length; i++) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if (target - choices[i] < 0) { break; } // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить if (i > start && choices[i] == choices[i - 1]) { continue; } // Попытка: сделать выбор и обновить target и start state.Add(choices[i]); // Перейти к следующему выбору Backtrack(state, target - choices[i], choices, i + 1, res); // Откат: отменить выбор и восстановить предыдущее состояние state.RemoveAt(state.Count - 1); } } /* Решить задачу суммы подмножеств II */ List> SubsetSumII(int[] nums, int target) { List state = []; // Состояние (подмножество) Array.Sort(nums); // Отсортировать nums int start = 0; // Стартовая вершина обхода List> res = []; // Список результатов (список подмножеств) Backtrack(state, target, nums, start, res); return res; } [Test] public void Test() { int[] nums = [4, 4, 5]; int target = 9; List> res = SubsetSumII(nums, target); Console.WriteLine("Входной массив nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("Все подмножества с суммой " + target + ": res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } } } ================================================ FILE: ru/codes/csharp/chapter_computational_complexity/iteration.cs ================================================ /** * File: iteration.cs * Created Time: 2023-08-28 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_computational_complexity; public class iteration { /* Цикл for */ int ForLoop(int n) { int res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } /* Цикл while */ int WhileLoop(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i; i += 1; // Обновить условную переменную } return res; } /* Цикл while (двойное обновление) */ int WhileLoopII(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i += 1; i *= 2; } return res; } /* Двойной цикл for */ string NestedForLoop(int n) { StringBuilder res = new(); // Цикл по i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // Цикл по j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { res.Append($"({i}, {j}), "); } } return res.ToString(); } /* Driver Code */ [Test] public void Test() { int n = 5; int res; res = ForLoop(n); Console.WriteLine("\nРезультат суммирования в цикле for res = " + res); res = WhileLoop(n); Console.WriteLine("\nРезультат суммирования в цикле while res = " + res); res = WhileLoopII(n); Console.WriteLine("\nРезультат суммирования в цикле while (двойное обновление) res = " + res); string resStr = NestedForLoop(n); Console.WriteLine("\nРезультат обхода в двойном цикле for " + resStr); } } ================================================ FILE: ru/codes/csharp/chapter_computational_complexity/recursion.cs ================================================ /** * File: recursion.cs * Created Time: 2023-08-28 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_computational_complexity; public class recursion { /* Рекурсия */ int Recur(int n) { // Условие завершения if (n == 1) return 1; // Рекурсия: рекурсивный вызов int res = Recur(n - 1); // Возврат: вернуть результат return n + res; } /* Имитация рекурсии итерацией */ int ForLoopRecur(int n) { // Использовать явный стек для имитации системного стека вызовов Stack stack = new(); int res = 0; // Рекурсия: рекурсивный вызов for (int i = n; i > 0; i--) { // Имитировать «рекурсию» с помощью операции помещения в стек stack.Push(i); } // Возврат: вернуть результат while (stack.Count > 0) { // Имитировать «возврат» с помощью операции извлечения из стека res += stack.Pop(); } // res = 1+2+3+...+n return res; } /* Хвостовая рекурсия */ int TailRecur(int n, int res) { // Условие завершения if (n == 0) return res; // Хвостовой рекурсивный вызов return TailRecur(n - 1, res + n); } /* Последовательность Фибоначчи: рекурсия */ int Fib(int n) { // Условие завершения: f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // Рекурсивный вызов f(n) = f(n-1) + f(n-2) int res = Fib(n - 1) + Fib(n - 2); // Вернуть результат f(n) return res; } /* Driver Code */ [Test] public void Test() { int n = 5; int res; res = Recur(n); Console.WriteLine("\nРезультат суммирования в рекурсивной функции res = " + res); res = ForLoopRecur(n); Console.WriteLine("\nРезультат суммирования при имитации рекурсии итерацией res = " + res); res = TailRecur(n, 0); Console.WriteLine("\nРезультат суммирования в хвостовой рекурсии res = " + res); res = Fib(n); Console.WriteLine("\nЧлен последовательности Фибоначчи с номером " + n + " = " + res); } } ================================================ FILE: ru/codes/csharp/chapter_computational_complexity/space_complexity.cs ================================================ /** * File: space_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class space_complexity { /* Функция */ int Function() { // Выполнить некоторые операции return 0; } /* Постоянная сложность */ void Constant(int n) { // Константы, переменные и объекты занимают O(1) памяти int a = 0; int b = 0; int[] nums = new int[10000]; ListNode node = new(0); // Переменные в цикле занимают O(1) памяти for (int i = 0; i < n; i++) { int c = 0; } // Функции в цикле занимают O(1) памяти for (int i = 0; i < n; i++) { Function(); } } /* Линейная сложность */ void Linear(int n) { // Массив длины n занимает O(n) памяти int[] nums = new int[n]; // Список длины n занимает O(n) памяти List nodes = []; for (int i = 0; i < n; i++) { nodes.Add(new ListNode(i)); } // Хеш-таблица длины n занимает O(n) памяти Dictionary map = []; for (int i = 0; i < n; i++) { map.Add(i, i.ToString()); } } /* Линейная сложность (рекурсивная реализация) */ void LinearRecur(int n) { Console.WriteLine("Рекурсия n = " + n); if (n == 1) return; LinearRecur(n - 1); } /* Квадратичная сложность */ void Quadratic(int n) { // Матрица занимает O(n^2) памяти int[,] numMatrix = new int[n, n]; // Двумерный список занимает O(n^2) памяти List> numList = []; for (int i = 0; i < n; i++) { List tmp = []; for (int j = 0; j < n; j++) { tmp.Add(0); } numList.Add(tmp); } } /* Квадратичная сложность (рекурсивная реализация) */ int QuadraticRecur(int n) { if (n <= 0) return 0; int[] nums = new int[n]; Console.WriteLine("В рекурсии n = " + n + ", длина nums = " + nums.Length); return QuadraticRecur(n - 1); } /* Экспоненциальная сложность (построение полного двоичного дерева) */ TreeNode? BuildTree(int n) { if (n == 0) return null; TreeNode root = new(0) { left = BuildTree(n - 1), right = BuildTree(n - 1) }; return root; } [Test] public void Test() { int n = 5; // Постоянная сложность Constant(n); // Линейная сложность Linear(n); LinearRecur(n); // Квадратичная сложность Quadratic(n); QuadraticRecur(n); // Экспоненциальная сложность TreeNode? root = BuildTree(n); PrintUtil.PrintTree(root); } } ================================================ FILE: ru/codes/csharp/chapter_computational_complexity/time_complexity.cs ================================================ /** * File: time_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class time_complexity { void Algorithm(int n) { int a = 1; // +0 (прием 1) a += n; // +0 (прием 1) // +n (прием 2) for (int i = 0; i < 5 * n + 1; i++) { Console.WriteLine(0); } // +n*n (прием 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { Console.WriteLine(0); } } } // Временная сложность алгоритма A: константная void AlgorithmA(int n) { Console.WriteLine(0); } // Временная сложность алгоритма B: линейная void AlgorithmB(int n) { for (int i = 0; i < n; i++) { Console.WriteLine(0); } } // Временная сложность алгоритма C: константная void AlgorithmC(int n) { for (int i = 0; i < 1000000; i++) { Console.WriteLine(0); } } /* Постоянная сложность */ int Constant(int n) { int count = 0; int size = 100000; for (int i = 0; i < size; i++) count++; return count; } /* Линейная сложность */ int Linear(int n) { int count = 0; for (int i = 0; i < n; i++) count++; return count; } /* Линейная сложность (обход массива) */ int ArrayTraversal(int[] nums) { int count = 0; // Число итераций пропорционально длине массива foreach (int num in nums) { count++; } return count; } /* Квадратичная сложность */ int Quadratic(int n) { int count = 0; // Число итераций квадратично зависит от размера данных n for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* Квадратичная сложность (пузырьковая сортировка) */ int BubbleSort(int[] nums) { int count = 0; // Счетчик // Внешний цикл: неотсортированный диапазон [0, i] for (int i = nums.Length - 1; i > 0; i--) { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); count += 3; // Обмен элементов включает 3 элементарные операции } } } return count; } /* Экспоненциальная сложность (итеративная реализация) */ int Exponential(int n) { int count = 0, bas = 1; // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) for (int i = 0; i < n; i++) { for (int j = 0; j < bas; j++) { count++; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* Экспоненциальная сложность (рекурсивная реализация) */ int ExpRecur(int n) { if (n == 1) return 1; return ExpRecur(n - 1) + ExpRecur(n - 1) + 1; } /* Логарифмическая сложность (итеративная реализация) */ int Logarithmic(int n) { int count = 0; while (n > 1) { n /= 2; count++; } return count; } /* Логарифмическая сложность (рекурсивная реализация) */ int LogRecur(int n) { if (n <= 1) return 0; return LogRecur(n / 2) + 1; } /* Линейно-логарифмическая сложность */ int LinearLogRecur(int n) { if (n <= 1) return 1; int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2); for (int i = 0; i < n; i++) { count++; } return count; } /* Факториальная сложность (рекурсивная реализация) */ int FactorialRecur(int n) { if (n == 0) return 1; int count = 0; // Из одного получается n for (int i = 0; i < n; i++) { count += FactorialRecur(n - 1); } return count; } [Test] public void Test() { // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях int n = 8; Console.WriteLine("Размер входных данных n = " + n); int count = Constant(n); Console.WriteLine("Число операций константной сложности = " + count); count = Linear(n); Console.WriteLine("Число операций линейной сложности = " + count); count = ArrayTraversal(new int[n]); Console.WriteLine("Число операций линейной сложности (обход массива) = " + count); count = Quadratic(n); Console.WriteLine("Число операций квадратичной сложности = " + count); int[] nums = new int[n]; for (int i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = BubbleSort(nums); Console.WriteLine("Число операций квадратичной сложности (пузырьковая сортировка) = " + count); count = Exponential(n); Console.WriteLine("Число операций экспоненциальной сложности (итеративная реализация) = " + count); count = ExpRecur(n); Console.WriteLine("Число операций экспоненциальной сложности (рекурсивная реализация) = " + count); count = Logarithmic(n); Console.WriteLine("Число операций логарифмической сложности (итеративная реализация) = " + count); count = LogRecur(n); Console.WriteLine("Число операций логарифмической сложности (рекурсивная реализация) = " + count); count = LinearLogRecur(n); Console.WriteLine("Число операций линейно-логарифмической сложности (рекурсивная реализация) = " + count); count = FactorialRecur(n); Console.WriteLine("Число операций факториальной сложности (рекурсивная реализация) = " + count); } } ================================================ FILE: ru/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs ================================================ /** * File: worst_best_time_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class worst_best_time_complexity { /* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ int[] RandomNumbers(int n) { int[] nums = new int[n]; // Создать массив nums = { 1, 2, 3, ..., n } for (int i = 0; i < n; i++) { nums[i] = i + 1; } // Случайно перемешать элементы массива for (int i = 0; i < nums.Length; i++) { int index = new Random().Next(i, nums.Length); (nums[i], nums[index]) = (nums[index], nums[i]); } return nums; } /* Найти индекс числа 1 в массиве nums */ int FindOne(int[] nums) { for (int i = 0; i < nums.Length; i++) { // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) if (nums[i] == 1) return i; } return -1; } /* Driver Code */ [Test] public void Test() { for (int i = 0; i < 10; i++) { int n = 100; int[] nums = RandomNumbers(n); int index = FindOne(nums); Console.WriteLine("\nМассив [1, 2, ..., n] после перемешивания = " + string.Join(",", nums)); Console.WriteLine("Индекс числа 1 = " + index); } } } ================================================ FILE: ru/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs ================================================ /** * File: binary_search_recur.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class binary_search_recur { /* Бинарный поиск: задача f(i, j) */ int DFS(int[] nums, int target, int i, int j) { // Если интервал пуст, целевой элемент отсутствует, вернуть -1 if (i > j) { return -1; } // Вычислить индекс середины m int m = (i + j) / 2; if (nums[m] < target) { // Рекурсивная подзадача f(m+1, j) return DFS(nums, target, m + 1, j); } else if (nums[m] > target) { // Рекурсивная подзадача f(i, m-1) return DFS(nums, target, i, m - 1); } else { // Целевой элемент найден, вернуть его индекс return m; } } /* Бинарный поиск */ int BinarySearch(int[] nums, int target) { int n = nums.Length; // Решить задачу f(0, n-1) return DFS(nums, target, 0, n - 1); } [Test] public void Test() { int target = 6; int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // Бинарный поиск (двусторонне замкнутый интервал) int index = BinarySearch(nums, target); Console.WriteLine("Индекс целевого элемента 6 = " + index); } } ================================================ FILE: ru/codes/csharp/chapter_divide_and_conquer/build_tree.cs ================================================ /** * File: build_tree.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class build_tree { /* Построить двоичное дерево: разделяй и властвуй */ TreeNode? DFS(int[] preorder, Dictionary inorderMap, int i, int l, int r) { // Завершить при пустом диапазоне поддерева if (r - l < 0) return null; // Инициализировать корневой узел TreeNode root = new(preorder[i]); // Найти m, чтобы разделить левое и правое поддеревья int m = inorderMap[preorder[i]]; // Подзадача: построить левое поддерево root.left = DFS(preorder, inorderMap, i + 1, l, m - 1); // Подзадача: построить правое поддерево root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r); // Вернуть корневой узел return root; } /* Построить двоичное дерево */ TreeNode? BuildTree(int[] preorder, int[] inorder) { // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам Dictionary inorderMap = []; for (int i = 0; i < inorder.Length; i++) { inorderMap.TryAdd(inorder[i], i); } TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1); return root; } [Test] public void Test() { int[] preorder = [3, 9, 2, 1, 7]; int[] inorder = [9, 3, 1, 2, 7]; Console.WriteLine("Предварительный обход = " + string.Join(", ", preorder)); Console.WriteLine("Симметричный обход = " + string.Join(", ", inorder)); TreeNode? root = BuildTree(preorder, inorder); Console.WriteLine("Построенное двоичное дерево:"); PrintUtil.PrintTree(root); } } ================================================ FILE: ru/codes/csharp/chapter_divide_and_conquer/hanota.cs ================================================ /** * File: hanota.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class hanota { /* Переместить один диск */ void Move(List src, List tar) { // Снять диск с вершины src int pan = src[^1]; src.RemoveAt(src.Count - 1); // Положить диск на вершину tar tar.Add(pan); } /* Решить задачу Ханойской башни f(i) */ void DFS(int i, List src, List buf, List tar) { // Если в src остался только один диск, сразу переместить его в tar if (i == 1) { Move(src, tar); return; } // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar DFS(i - 1, src, tar, buf); // Подзадача f(1): переместить оставшийся один диск из src в tar Move(src, tar); // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src DFS(i - 1, buf, src, tar); } /* Решить задачу Ханойской башни */ void SolveHanota(List A, List B, List C) { int n = A.Count; // Переместить верхние n дисков из A в C с помощью B DFS(n, A, B, C); } [Test] public void Test() { // Хвост списка соответствует вершине столбца List A = [5, 4, 3, 2, 1]; List B = []; List C = []; Console.WriteLine("Исходное состояние:"); Console.WriteLine("A = " + string.Join(", ", A)); Console.WriteLine("B = " + string.Join(", ", B)); Console.WriteLine("C = " + string.Join(", ", C)); SolveHanota(A, B, C); Console.WriteLine("После завершения перемещения дисков:"); Console.WriteLine("A = " + string.Join(", ", A)); Console.WriteLine("B = " + string.Join(", ", B)); Console.WriteLine("C = " + string.Join(", ", C)); } } ================================================ FILE: ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs ================================================ /** * File: climbing_stairs_backtrack.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_backtrack { /* Бэктрекинг */ void Backtrack(List choices, int state, int n, List res) { // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 if (state == n) res[0]++; // Перебор всех вариантов выбора foreach (int choice in choices) { // Отсечение: нельзя выходить за n-ю ступень if (state + choice > n) continue; // Попытка: сделать выбор и обновить состояние Backtrack(choices, state + choice, n, res); // Откат } } /* Подъем по лестнице: бэктрекинг */ int ClimbingStairsBacktrack(int n) { List choices = [1, 2]; // Можно подняться на 1 или 2 ступени int state = 0; // Начать подъем с 0-й ступени List res = [0]; // Использовать res[0] для хранения числа решений Backtrack(choices, state, n, res); return res[0]; } [Test] public void Test() { int n = 9; int res = ClimbingStairsBacktrack(n); Console.WriteLine($"Количество способов подняться по лестнице из {n} ступеней = {res}"); } } ================================================ FILE: ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs ================================================ /** * File: climbing_stairs_constraint_dp.cs * Created Time: 2023-07-03 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_constraint_dp { /* Подъем по лестнице с ограничениями: динамическое программирование */ int ClimbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // Инициализация таблицы dp для хранения решений подзадач int[,] dp = new int[n + 1, 3]; // Начальное состояние: заранее задать решения наименьших подзадач dp[1, 1] = 1; dp[1, 2] = 0; dp[2, 1] = 0; dp[2, 2] = 1; // Переход состояний: постепенное решение больших подзадач через меньшие for (int i = 3; i <= n; i++) { dp[i, 1] = dp[i - 1, 2]; dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2]; } return dp[n, 1] + dp[n, 2]; } [Test] public void Test() { int n = 9; int res = ClimbingStairsConstraintDP(n); Console.WriteLine($"Количество способов подняться по лестнице из {n} ступеней = {res}"); } } ================================================ FILE: ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs ================================================ /** * File: climbing_stairs_dfs.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dfs { /* Поиск */ int DFS(int i) { // dp[1] и dp[2] уже известны, вернуть их if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] int count = DFS(i - 1) + DFS(i - 2); return count; } /* Подъем по лестнице: поиск */ int ClimbingStairsDFS(int n) { return DFS(n); } [Test] public void Test() { int n = 9; int res = ClimbingStairsDFS(n); Console.WriteLine($"Количество способов подняться по лестнице из {n} ступеней = {res}"); } } ================================================ FILE: ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs ================================================ /** * File: climbing_stairs_dfs_mem.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dfs_mem { /* Поиск с мемоизацией */ int DFS(int i, int[] mem) { // dp[1] и dp[2] уже известны, вернуть их if (i == 1 || i == 2) return i; // Если запись dp[i] существует, сразу вернуть ее if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = DFS(i - 1, mem) + DFS(i - 2, mem); // Сохранить dp[i] mem[i] = count; return count; } /* Подъем по лестнице: поиск с мемоизацией */ int ClimbingStairsDFSMem(int n) { // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи int[] mem = new int[n + 1]; Array.Fill(mem, -1); return DFS(n, mem); } [Test] public void Test() { int n = 9; int res = ClimbingStairsDFSMem(n); Console.WriteLine($"Количество способов подняться по лестнице из {n} ступеней = {res}"); } } ================================================ FILE: ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs ================================================ /** * File: climbing_stairs_dp.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dp { /* Подъем по лестнице: динамическое программирование */ int ClimbingStairsDP(int n) { if (n == 1 || n == 2) return n; // Инициализация таблицы dp для хранения решений подзадач int[] dp = new int[n + 1]; // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = 1; dp[2] = 2; // Переход состояний: постепенное решение больших подзадач через меньшие for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ int ClimbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } [Test] public void Test() { int n = 9; int res = ClimbingStairsDP(n); Console.WriteLine($"Количество способов подняться по лестнице из {n} ступеней = {res}"); res = ClimbingStairsDPComp(n); Console.WriteLine($"Количество способов подняться по лестнице из {n} ступеней = {res}"); } } ================================================ FILE: ru/codes/csharp/chapter_dynamic_programming/coin_change.cs ================================================ /** * File: coin_change.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class coin_change { /* Размен монет: динамическое программирование */ int CoinChangeDP(int[] coins, int amt) { int n = coins.Length; int MAX = amt + 1; // Инициализация таблицы dp int[,] dp = new int[n + 1, amt + 1]; // Переход состояний: первая строка и первый столбец for (int a = 1; a <= amt; a++) { dp[0, a] = MAX; } // Переход состояний: остальные строки и столбцы for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[i, a] = dp[i - 1, a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1); } } } return dp[n, amt] != MAX ? dp[n, amt] : -1; } /* Размен монет: динамическое программирование с оптимизацией памяти */ int CoinChangeDPComp(int[] coins, int amt) { int n = coins.Length; int MAX = amt + 1; // Инициализация таблицы dp int[] dp = new int[amt + 1]; Array.Fill(dp, MAX); dp[0] = 0; // Переход состояний for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } [Test] public void Test() { int[] coins = [1, 2, 5]; int amt = 4; // Динамическое программирование int res = CoinChangeDP(coins, amt); Console.WriteLine("Минимальное число монет для набора целевой суммы = " + res); // Динамическое программирование с оптимизацией памяти res = CoinChangeDPComp(coins, amt); Console.WriteLine("Минимальное число монет для набора целевой суммы = " + res); } } ================================================ FILE: ru/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs ================================================ /** * File: coin_change_ii.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class coin_change_ii { /* Размен монет II: динамическое программирование */ int CoinChangeIIDP(int[] coins, int amt) { int n = coins.Length; // Инициализация таблицы dp int[,] dp = new int[n + 1, amt + 1]; // Инициализация первого столбца for (int i = 0; i <= n; i++) { dp[i, 0] = 1; } // Переход состояний for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[i, a] = dp[i - 1, a]; } else { // Сумма двух решений: не брать или взять монету i dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]]; } } } return dp[n, amt]; } /* Размен монет II: динамическое программирование с оптимизацией памяти */ int CoinChangeIIDPComp(int[] coins, int amt) { int n = coins.Length; // Инициализация таблицы dp int[] dp = new int[amt + 1]; dp[0] = 1; // Переход состояний for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Сумма двух решений: не брать или взять монету i dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } [Test] public void Test() { int[] coins = [1, 2, 5]; int amt = 5; // Динамическое программирование int res = CoinChangeIIDP(coins, amt); Console.WriteLine("Количество комбинаций монет для набора целевой суммы = " + res); // Динамическое программирование с оптимизацией памяти res = CoinChangeIIDPComp(coins, amt); Console.WriteLine("Количество комбинаций монет для набора целевой суммы = " + res); } } ================================================ FILE: ru/codes/csharp/chapter_dynamic_programming/edit_distance.cs ================================================ /** * File: edit_distance.cs * Created Time: 2023-07-14 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class edit_distance { /* Редакционное расстояние: полный перебор */ int EditDistanceDFS(string s, string t, int i, int j) { // Если s и t пусты, вернуть 0 if (i == 0 && j == 0) return 0; // Если s пусто, вернуть длину t if (i == 0) return j; // Если t пусто, вернуть длину s if (j == 0) return i; // Если два символа равны, сразу пропустить их if (s[i - 1] == t[j - 1]) return EditDistanceDFS(s, t, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 int insert = EditDistanceDFS(s, t, i, j - 1); int delete = EditDistanceDFS(s, t, i - 1, j); int replace = EditDistanceDFS(s, t, i - 1, j - 1); // Вернуть минимальное число шагов редактирования return Math.Min(Math.Min(insert, delete), replace) + 1; } /* Редакционное расстояние: поиск с мемоизацией */ int EditDistanceDFSMem(string s, string t, int[][] mem, int i, int j) { // Если s и t пусты, вернуть 0 if (i == 0 && j == 0) return 0; // Если s пусто, вернуть длину t if (i == 0) return j; // Если t пусто, вернуть длину s if (j == 0) return i; // Если запись уже есть, сразу вернуть ее if (mem[i][j] != -1) return mem[i][j]; // Если два символа равны, сразу пропустить их if (s[i - 1] == t[j - 1]) return EditDistanceDFSMem(s, t, mem, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 int insert = EditDistanceDFSMem(s, t, mem, i, j - 1); int delete = EditDistanceDFSMem(s, t, mem, i - 1, j); int replace = EditDistanceDFSMem(s, t, mem, i - 1, j - 1); // Сохранить и вернуть минимальное число шагов редактирования mem[i][j] = Math.Min(Math.Min(insert, delete), replace) + 1; return mem[i][j]; } /* Редакционное расстояние: динамическое программирование */ int EditDistanceDP(string s, string t) { int n = s.Length, m = t.Length; int[,] dp = new int[n + 1, m + 1]; // Переход состояний: первая строка и первый столбец for (int i = 1; i <= n; i++) { dp[i, 0] = i; } for (int j = 1; j <= m; j++) { dp[0, j] = j; } // Переход состояний: остальные строки и столбцы for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // Если два символа равны, сразу пропустить их dp[i, j] = dp[i - 1, j - 1]; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1; } } } return dp[n, m]; } /* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ int EditDistanceDPComp(string s, string t) { int n = s.Length, m = t.Length; int[] dp = new int[m + 1]; // Переход состояний: первая строка for (int j = 1; j <= m; j++) { dp[j] = j; } // Переход состояний: остальные строки for (int i = 1; i <= n; i++) { // Переход состояний: первый столбец int leftup = dp[0]; // Временно сохранить dp[i-1, j-1] dp[0] = i; // Переход состояний: остальные столбцы for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // Если два символа равны, сразу пропустить их dp[j] = leftup; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации } } return dp[m]; } [Test] public void Test() { string s = "bag"; string t = "pack"; int n = s.Length, m = t.Length; // Полный перебор int res = EditDistanceDFS(s, t, n, m); Console.WriteLine("Чтобы преобразовать " + s + " в " + t + ", нужно минимум " + res + " шагов редактирования"); // Поиск с мемоизацией int[][] mem = new int[n + 1][]; for (int i = 0; i <= n; i++) { mem[i] = new int[m + 1]; Array.Fill(mem[i], -1); } res = EditDistanceDFSMem(s, t, mem, n, m); Console.WriteLine("Чтобы преобразовать " + s + " в " + t + ", нужно минимум " + res + " шагов редактирования"); // Динамическое программирование res = EditDistanceDP(s, t); Console.WriteLine("Чтобы преобразовать " + s + " в " + t + ", нужно минимум " + res + " шагов редактирования"); // Динамическое программирование с оптимизацией памяти res = EditDistanceDPComp(s, t); Console.WriteLine("Чтобы преобразовать " + s + " в " + t + ", нужно минимум " + res + " шагов редактирования"); } } ================================================ FILE: ru/codes/csharp/chapter_dynamic_programming/knapsack.cs ================================================ /** * File: knapsack.cs * Created Time: 2023-07-07 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class knapsack { /* Рюкзак 0-1: полный перебор */ int KnapsackDFS(int[] weight, int[] val, int i, int c) { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i == 0 || c == 0) { return 0; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (weight[i - 1] > c) { return KnapsackDFS(weight, val, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут int no = KnapsackDFS(weight, val, i - 1, c); int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1]; // Вернуть вариант с большей стоимостью из двух возможных return Math.Max(no, yes); } /* Рюкзак 0-1: поиск с мемоизацией */ int KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i == 0 || c == 0) { return 0; } // Если запись уже есть, вернуть сразу if (mem[i][c] != -1) { return mem[i][c]; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (weight[i - 1] > c) { return KnapsackDFSMem(weight, val, mem, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут int no = KnapsackDFSMem(weight, val, mem, i - 1, c); int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1]; // Сохранить и вернуть вариант с большей стоимостью из двух решений mem[i][c] = Math.Max(no, yes); return mem[i][c]; } /* Рюкзак 0-1: динамическое программирование */ int KnapsackDP(int[] weight, int[] val, int cap) { int n = weight.Length; // Инициализация таблицы dp int[,] dp = new int[n + 1, cap + 1]; // Переход состояний for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (weight[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i, c] = dp[i - 1, c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]); } } } return dp[n, cap]; } /* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ int KnapsackDPComp(int[] weight, int[] val, int cap) { int n = weight.Length; // Инициализация таблицы dp int[] dp = new int[cap + 1]; // Переход состояний for (int i = 1; i <= n; i++) { // Обход в обратном порядке for (int c = cap; c > 0; c--) { if (weight[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[c] = dp[c]; } else { // Большее из двух решений: не брать или взять предмет i dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]); } } } return dp[cap]; } [Test] public void Test() { int[] weight = [10, 20, 30, 40, 50]; int[] val = [50, 120, 150, 210, 240]; int cap = 50; int n = weight.Length; // Полный перебор int res = KnapsackDFS(weight, val, n, cap); Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); // Поиск с мемоизацией int[][] mem = new int[n + 1][]; for (int i = 0; i <= n; i++) { mem[i] = new int[cap + 1]; Array.Fill(mem[i], -1); } res = KnapsackDFSMem(weight, val, mem, n, cap); Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); // Динамическое программирование res = KnapsackDP(weight, val, cap); Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); // Динамическое программирование с оптимизацией памяти res = KnapsackDPComp(weight, val, cap); Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); } } ================================================ FILE: ru/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs ================================================ /** * File: min_cost_climbing_stairs_dp.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class min_cost_climbing_stairs_dp { /* Минимальная стоимость подъема по лестнице: динамическое программирование */ int MinCostClimbingStairsDP(int[] cost) { int n = cost.Length - 1; if (n == 1 || n == 2) return cost[n]; // Инициализация таблицы dp для хранения решений подзадач int[] dp = new int[n + 1]; // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = cost[1]; dp[2] = cost[2]; // Переход состояний: постепенное решение больших подзадач через меньшие for (int i = 3; i <= n; i++) { dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ int MinCostClimbingStairsDPComp(int[] cost) { int n = cost.Length - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = Math.Min(a, tmp) + cost[i]; a = tmp; } return b; } [Test] public void Test() { int[] cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; Console.WriteLine("Список стоимостей ступеней:"); PrintUtil.PrintList(cost); int res = MinCostClimbingStairsDP(cost); Console.WriteLine($"Минимальная стоимость подъема по лестнице = {res}"); res = MinCostClimbingStairsDPComp(cost); Console.WriteLine($"Минимальная стоимость подъема по лестнице = {res}"); } } ================================================ FILE: ru/codes/csharp/chapter_dynamic_programming/min_path_sum.cs ================================================ /** * File: min_path_sum.cs * Created Time: 2023-07-10 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class min_path_sum { /* Минимальная сумма пути: полный перебор */ int MinPathSumDFS(int[][] grid, int i, int j) { // Если это верхняя левая ячейка, завершить поиск if (i == 0 && j == 0) { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 || j < 0) { return int.MaxValue; } // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) int up = MinPathSumDFS(grid, i - 1, j); int left = MinPathSumDFS(grid, i, j - 1); // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) return Math.Min(left, up) + grid[i][j]; } /* Минимальная сумма пути: поиск с мемоизацией */ int MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { // Если это верхняя левая ячейка, завершить поиск if (i == 0 && j == 0) { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 || j < 0) { return int.MaxValue; } // Если запись уже есть, вернуть сразу if (mem[i][j] != -1) { return mem[i][j]; } // Минимальная стоимость пути для левой и верхней ячеек int up = MinPathSumDFSMem(grid, mem, i - 1, j); int left = MinPathSumDFSMem(grid, mem, i, j - 1); // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) mem[i][j] = Math.Min(left, up) + grid[i][j]; return mem[i][j]; } /* Минимальная сумма пути: динамическое программирование */ int MinPathSumDP(int[][] grid) { int n = grid.Length, m = grid[0].Length; // Инициализация таблицы dp int[,] dp = new int[n, m]; dp[0, 0] = grid[0][0]; // Переход состояний: первая строка for (int j = 1; j < m; j++) { dp[0, j] = dp[0, j - 1] + grid[0][j]; } // Переход состояний: первый столбец for (int i = 1; i < n; i++) { dp[i, 0] = dp[i - 1, 0] + grid[i][0]; } // Переход состояний: остальные строки и столбцы for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j]; } } return dp[n - 1, m - 1]; } /* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ int MinPathSumDPComp(int[][] grid) { int n = grid.Length, m = grid[0].Length; // Инициализация таблицы dp int[] dp = new int[m]; dp[0] = grid[0][0]; // Переход состояний: первая строка for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // Переход состояний: остальные строки for (int i = 1; i < n; i++) { // Переход состояний: первый столбец dp[0] = dp[0] + grid[i][0]; // Переход состояний: остальные столбцы for (int j = 1; j < m; j++) { dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } [Test] public void Test() { int[][] grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2] ]; int n = grid.Length, m = grid[0].Length; // Полный перебор int res = MinPathSumDFS(grid, n - 1, m - 1); Console.WriteLine("Минимальная сумма пути из левого верхнего угла в правый нижний = " + res); // Поиск с мемоизацией int[][] mem = new int[n][]; for (int i = 0; i < n; i++) { mem[i] = new int[m]; Array.Fill(mem[i], -1); } res = MinPathSumDFSMem(grid, mem, n - 1, m - 1); Console.WriteLine("Минимальная сумма пути из левого верхнего угла в правый нижний = " + res); // Динамическое программирование res = MinPathSumDP(grid); Console.WriteLine("Минимальная сумма пути из левого верхнего угла в правый нижний = " + res); // Динамическое программирование с оптимизацией памяти res = MinPathSumDPComp(grid); Console.WriteLine("Минимальная сумма пути из левого верхнего угла в правый нижний = " + res); } } ================================================ FILE: ru/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs ================================================ /** * File: unbounded_knapsack.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class unbounded_knapsack { /* Полный рюкзак: динамическое программирование */ int UnboundedKnapsackDP(int[] wgt, int[] val, int cap) { int n = wgt.Length; // Инициализация таблицы dp int[,] dp = new int[n + 1, cap + 1]; // Переход состояний for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i, c] = dp[i - 1, c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]); } } } return dp[n, cap]; } /* Полный рюкзак: динамическое программирование с оптимизацией памяти */ int UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { int n = wgt.Length; // Инициализация таблицы dp int[] dp = new int[cap + 1]; // Переход состояний for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[c] = dp[c]; } else { // Большее из двух решений: не брать или взять предмет i dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } [Test] public void Test() { int[] wgt = [1, 2, 3]; int[] val = [5, 11, 15]; int cap = 4; // Динамическое программирование int res = UnboundedKnapsackDP(wgt, val, cap); Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); // Динамическое программирование с оптимизацией памяти res = UnboundedKnapsackDPComp(wgt, val, cap); Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); } } ================================================ FILE: ru/codes/csharp/chapter_graph/graph_adjacency_list.cs ================================================ /** * File: graph_adjacency_list.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_graph; /* Класс неориентированного графа на основе списка смежности */ public class GraphAdjList { // Список смежности, где key — вершина, а value — все смежные ей вершины public Dictionary> adjList; /* Конструктор */ public GraphAdjList(Vertex[][] edges) { adjList = []; // Добавить все вершины и ребра foreach (Vertex[] edge in edges) { AddVertex(edge[0]); AddVertex(edge[1]); AddEdge(edge[0], edge[1]); } } /* Получить число вершин */ int Size() { return adjList.Count; } /* Добавление ребра */ public void AddEdge(Vertex vet1, Vertex vet2) { if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) throw new InvalidOperationException(); // Добавить ребро vet1 - vet2 adjList[vet1].Add(vet2); adjList[vet2].Add(vet1); } /* Удаление ребра */ public void RemoveEdge(Vertex vet1, Vertex vet2) { if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) throw new InvalidOperationException(); // Удалить ребро vet1 - vet2 adjList[vet1].Remove(vet2); adjList[vet2].Remove(vet1); } /* Добавление вершины */ public void AddVertex(Vertex vet) { if (adjList.ContainsKey(vet)) return; // Добавить новый список в список смежности adjList.Add(vet, []); } /* Удаление вершины */ public void RemoveVertex(Vertex vet) { if (!adjList.ContainsKey(vet)) throw new InvalidOperationException(); // Удалить из списка смежности список, соответствующий вершине vet adjList.Remove(vet); // Обойти списки других вершин и удалить все ребра, содержащие vet foreach (List list in adjList.Values) { list.Remove(vet); } } /* Вывести список смежности */ public void Print() { Console.WriteLine("Список смежности ="); foreach (KeyValuePair> pair in adjList) { List tmp = []; foreach (Vertex vertex in pair.Value) tmp.Add(vertex.val); Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); } } } public class graph_adjacency_list { [Test] public void Test() { /* Инициализация неориентированного графа */ Vertex[] v = Vertex.ValsToVets([1, 3, 2, 5, 4]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]] ]; GraphAdjList graph = new(edges); Console.WriteLine("\nГраф после инициализации"); graph.Print(); /* Добавление ребра */ // Вершины 1 и 2 соответствуют v[0] и v[2] graph.AddEdge(v[0], v[2]); Console.WriteLine("\nГраф после добавления ребра 1-2"); graph.Print(); /* Удаление ребра */ // Вершины 1 и 3 соответствуют v[0] и v[1] graph.RemoveEdge(v[0], v[1]); Console.WriteLine("\nГраф после удаления ребра 1-3"); graph.Print(); /* Добавление вершины */ Vertex v5 = new(6); graph.AddVertex(v5); Console.WriteLine("\nГраф после добавления вершины 6"); graph.Print(); /* Удаление вершины */ // Вершина 3 соответствует v[1] graph.RemoveVertex(v[1]); Console.WriteLine("\nГраф после удаления вершины 3"); graph.Print(); } } ================================================ FILE: ru/codes/csharp/chapter_graph/graph_adjacency_matrix.cs ================================================ /** * File: graph_adjacency_matrix.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_graph; /* Класс неориентированного графа на основе матрицы смежности */ class GraphAdjMat { List vertices; // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» List> adjMat; // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» /* Конструктор */ public GraphAdjMat(int[] vertices, int[][] edges) { this.vertices = []; this.adjMat = []; // Добавление вершины foreach (int val in vertices) { AddVertex(val); } // Добавить ребра // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices foreach (int[] e in edges) { AddEdge(e[0], e[1]); } } /* Получить число вершин */ int Size() { return vertices.Count; } /* Добавление вершины */ public void AddVertex(int val) { int n = Size(); // Добавить значение новой вершины в список вершин vertices.Add(val); // Добавить строку в матрицу смежности List newRow = new(n); for (int j = 0; j < n; j++) { newRow.Add(0); } adjMat.Add(newRow); // Добавить столбец в матрицу смежности foreach (List row in adjMat) { row.Add(0); } } /* Удаление вершины */ public void RemoveVertex(int index) { if (index >= Size()) throw new IndexOutOfRangeException(); // Удалить вершину с индексом index из списка вершин vertices.RemoveAt(index); // Удалить строку с индексом index из матрицы смежности adjMat.RemoveAt(index); // Удалить столбец с индексом index из матрицы смежности foreach (List row in adjMat) { row.RemoveAt(index); } } /* Добавление ребра */ // Параметры i и j соответствуют индексам элементов vertices public void AddEdge(int i, int j) { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) throw new IndexOutOfRangeException(); // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) adjMat[i][j] = 1; adjMat[j][i] = 1; } /* Удаление ребра */ // Параметры i и j соответствуют индексам элементов vertices public void RemoveEdge(int i, int j) { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) throw new IndexOutOfRangeException(); adjMat[i][j] = 0; adjMat[j][i] = 0; } /* Вывести матрицу смежности */ public void Print() { Console.Write("Список вершин = "); PrintUtil.PrintList(vertices); Console.WriteLine("Матрица смежности ="); PrintUtil.PrintMatrix(adjMat); } } public class graph_adjacency_matrix { [Test] public void Test() { /* Инициализация неориентированного графа */ // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices int[] vertices = [1, 3, 2, 5, 4]; int[][] edges = [ [0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4] ]; GraphAdjMat graph = new(vertices, edges); Console.WriteLine("\nГраф после инициализации"); graph.Print(); /* Добавление ребра */ // Индексы вершин 1 и 2 равны 0 и 2 соответственно graph.AddEdge(0, 2); Console.WriteLine("\nГраф после добавления ребра 1-2"); graph.Print(); /* Удаление ребра */ // Индексы вершин 1 и 3 равны 0 и 1 соответственно graph.RemoveEdge(0, 1); Console.WriteLine("\nГраф после удаления ребра 1-3"); graph.Print(); /* Добавление вершины */ graph.AddVertex(6); Console.WriteLine("\nГраф после добавления вершины 6"); graph.Print(); /* Удаление вершины */ // Индекс вершины 3 равен 1 graph.RemoveVertex(1); Console.WriteLine("\nГраф после удаления вершины 3"); graph.Print(); } } ================================================ FILE: ru/codes/csharp/chapter_graph/graph_bfs.cs ================================================ /** * File: graph_bfs.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_graph; public class graph_bfs { /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины List GraphBFS(GraphAdjList graph, Vertex startVet) { // Последовательность обхода вершин List res = []; // Хеш-множество для хранения уже посещенных вершин HashSet visited = [startVet]; // Очередь используется для реализации BFS Queue que = new(); que.Enqueue(startVet); // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (que.Count > 0) { Vertex vet = que.Dequeue(); // Извлечь головную вершину из очереди res.Add(vet); // Отметить посещенную вершину foreach (Vertex adjVet in graph.adjList[vet]) { if (visited.Contains(adjVet)) { continue; // Пропустить уже посещенную вершину } que.Enqueue(adjVet); // Помещать в очередь только непосещенные вершины visited.Add(adjVet); // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res; } [Test] public void Test() { /* Инициализация неориентированного графа */ Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]] ]; GraphAdjList graph = new(edges); Console.WriteLine("\nГраф после инициализации"); graph.Print(); /* Обход в ширину */ List res = GraphBFS(graph, v[0]); Console.WriteLine("\nПоследовательность вершин при обходе в ширину (BFS)"); Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); } } ================================================ FILE: ru/codes/csharp/chapter_graph/graph_dfs.cs ================================================ /** * File: graph_dfs.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_graph; public class graph_dfs { /* Вспомогательная функция обхода в глубину */ void DFS(GraphAdjList graph, HashSet visited, List res, Vertex vet) { res.Add(vet); // Отметить посещенную вершину visited.Add(vet); // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины foreach (Vertex adjVet in graph.adjList[vet]) { if (visited.Contains(adjVet)) { continue; // Пропустить уже посещенную вершину } // Рекурсивно обходить смежные вершины DFS(graph, visited, res, adjVet); } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины List GraphDFS(GraphAdjList graph, Vertex startVet) { // Последовательность обхода вершин List res = []; // Хеш-множество для хранения уже посещенных вершин HashSet visited = []; DFS(graph, visited, res, startVet); return res; } [Test] public void Test() { /* Инициализация неориентированного графа */ Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; GraphAdjList graph = new(edges); Console.WriteLine("\nГраф после инициализации"); graph.Print(); /* Обход в глубину */ List res = GraphDFS(graph, v[0]); Console.WriteLine("\nПоследовательность вершин при обходе в глубину (DFS)"); Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); } } ================================================ FILE: ru/codes/csharp/chapter_greedy/coin_change_greedy.cs ================================================ /** * File: coin_change_greedy.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class coin_change_greedy { /* Размен монет: жадный алгоритм */ int CoinChangeGreedy(int[] coins, int amt) { // Предположить, что список coins упорядочен int i = coins.Length - 1; int count = 0; // Циклически выполнять жадный выбор, пока не останется суммы while (amt > 0) { // Найти монету, которая меньше остатка суммы и наиболее к нему близка while (i > 0 && coins[i] > amt) { i--; } // Выбрать coins[i] amt -= coins[i]; count++; } // Если допустимое решение не найдено, вернуть -1 return amt == 0 ? count : -1; } [Test] public void Test() { // Жадный подход: гарантирует нахождение глобально оптимального решения int[] coins = [1, 5, 10, 20, 50, 100]; int amt = 186; int res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("Минимальное число монет для набора суммы " + amt + " = " + res); // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = [1, 20, 50]; amt = 60; res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("Минимальное число монет для набора суммы " + amt + " = " + res); Console.WriteLine("На самом деле минимум равен 3: 20 + 20 + 20"); // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = [1, 49, 50]; amt = 98; res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("Минимальное число монет для набора суммы " + amt + " = " + res); Console.WriteLine("На самом деле минимум равен 2: 49 + 49"); } } ================================================ FILE: ru/codes/csharp/chapter_greedy/fractional_knapsack.cs ================================================ /** * File: fractional_knapsack.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; /* Предмет */ class Item(int w, int v) { public int w = w; // Вес предмета public int v = v; // Стоимость предмета } public class fractional_knapsack { /* Дробный рюкзак: жадный алгоритм */ double FractionalKnapsack(int[] wgt, int[] val, int cap) { // Создать список предметов с двумя свойствами: вес и стоимость Item[] items = new Item[wgt.Length]; for (int i = 0; i < wgt.Length; i++) { items[i] = new Item(wgt[i], val[i]); } // Отсортировать по удельной стоимости item.v / item.w в порядке убывания Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w)); // Циклический жадный выбор double res = 0; foreach (Item item in items) { if (item.w <= cap) { // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком res += item.v; cap -= item.w; } else { // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета res += (double)item.v / item.w * cap; // Свободной вместимости больше не осталось, поэтому выйти из цикла break; } } return res; } [Test] public void Test() { int[] wgt = [10, 20, 30, 40, 50]; int[] val = [50, 120, 150, 210, 240]; int cap = 50; // Жадный алгоритм double res = FractionalKnapsack(wgt, val, cap); Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); } } ================================================ FILE: ru/codes/csharp/chapter_greedy/max_capacity.cs ================================================ /** * File: max_capacity.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class max_capacity { /* Максимальная вместимость: жадный алгоритм */ int MaxCapacity(int[] ht) { // Инициализировать i и j так, чтобы они располагались по двум концам массива int i = 0, j = ht.Length - 1; // Начальная максимальная вместимость равна 0 int res = 0; // Выполнять жадный выбор в цикле, пока две доски не встретятся while (i < j) { // Обновить максимальную вместимость int cap = Math.Min(ht[i], ht[j]) * (j - i); res = Math.Max(res, cap); // Сдвигать внутрь более короткую сторону if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } [Test] public void Test() { int[] ht = [3, 8, 5, 2, 7, 7, 3, 4]; // Жадный алгоритм int res = MaxCapacity(ht); Console.WriteLine("Максимальная вместимость = " + res); } } ================================================ FILE: ru/codes/csharp/chapter_greedy/max_product_cutting.cs ================================================ /** * File: max_product_cutting.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class max_product_cutting { /* Максимальное произведение разрезания: жадный алгоритм */ int MaxProductCutting(int n) { // Когда n <= 3, обязательно нужно выделить одну 1 if (n <= 3) { return 1 * (n - 1); } // Жадно выделить множители 3, где a — число троек, а b — остаток int a = n / 3; int b = n % 3; if (b == 1) { // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 return (int)Math.Pow(3, a - 1) * 2 * 2; } if (b == 2) { // Если остаток равен 2, ничего не делать return (int)Math.Pow(3, a) * 2; } // Если остаток равен 0, ничего не делать return (int)Math.Pow(3, a); } [Test] public void Test() { int n = 58; // Жадный алгоритм int res = MaxProductCutting(n); Console.WriteLine("Максимальное произведение после разрезания = " + res); } } ================================================ FILE: ru/codes/csharp/chapter_hashing/array_hash_map.cs ================================================ /** * File: array_hash_map.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_hashing; /* Пара ключ-значение int->string */ class Pair(int key, string val) { public int key = key; public string val = val; } /* Хеш-таблица на основе массива */ class ArrayHashMap { List buckets; public ArrayHashMap() { // Инициализировать массив, содержащий 100 корзин buckets = []; for (int i = 0; i < 100; i++) { buckets.Add(null); } } /* Хеш-функция */ int HashFunc(int key) { int index = key % 100; return index; } /* Операция поиска */ public string? Get(int key) { int index = HashFunc(key); Pair? pair = buckets[index]; if (pair == null) return null; return pair.val; } /* Операция добавления */ public void Put(int key, string val) { Pair pair = new(key, val); int index = HashFunc(key); buckets[index] = pair; } /* Операция удаления */ public void Remove(int key) { int index = HashFunc(key); // Присвоить null, что означает удаление buckets[index] = null; } /* Получить все пары ключ-значение */ public List PairSet() { List pairSet = []; foreach (Pair? pair in buckets) { if (pair != null) pairSet.Add(pair); } return pairSet; } /* Получить все ключи */ public List KeySet() { List keySet = []; foreach (Pair? pair in buckets) { if (pair != null) keySet.Add(pair.key); } return keySet; } /* Получить все значения */ public List ValueSet() { List valueSet = []; foreach (Pair? pair in buckets) { if (pair != null) valueSet.Add(pair.val); } return valueSet; } /* Вывести хеш-таблицу */ public void Print() { foreach (Pair kv in PairSet()) { Console.WriteLine(kv.key + " -> " + kv.val); } } } public class array_hash_map { [Test] public void Test() { /* Инициализация хеш-таблицы */ ArrayHashMap map = new(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.Put(12836, "Сяо Ха"); map.Put(15937, "Сяо Ло"); map.Put(16750, "Сяо Суань"); map.Put(13276, "Сяо Фа"); map.Put(10583, "Сяо Я"); Console.WriteLine("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); map.Print(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value string? name = map.Get(15937); Console.WriteLine("\nДля номера 15937 найдено имя " + name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.Remove(10583); Console.WriteLine("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); map.Print(); /* Обход хеш-таблицы */ Console.WriteLine("\nОтдельный обход пар ключ-значение"); foreach (Pair kv in map.PairSet()) { Console.WriteLine(kv.key + " -> " + kv.val); } Console.WriteLine("\nОтдельный обход ключей"); foreach (int key in map.KeySet()) { Console.WriteLine(key); } Console.WriteLine("\nОтдельный обход значений"); foreach (string val in map.ValueSet()) { Console.WriteLine(val); } } } ================================================ FILE: ru/codes/csharp/chapter_hashing/built_in_hash.cs ================================================ /** * File: built_in_hash.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; public class built_in_hash { [Test] public void Test() { int num = 3; int hashNum = num.GetHashCode(); Console.WriteLine("Хеш-значение целого числа " + num + " = " + hashNum); bool bol = true; int hashBol = bol.GetHashCode(); Console.WriteLine("Хеш-значение булева значения " + bol + " = " + hashBol); double dec = 3.14159; int hashDec = dec.GetHashCode(); Console.WriteLine("Хеш-значение десятичного числа " + dec + " = " + hashDec); string str = "Hello Algo"; int hashStr = str.GetHashCode(); Console.WriteLine("Хеш-значение строки " + str + " = " + hashStr); object[] arr = [12836, "Сяо Ха"]; int hashTup = arr.GetHashCode(); Console.WriteLine("Хеш-значение массива [" + string.Join(", ", arr) + "] = " + hashTup); ListNode obj = new(0); int hashObj = obj.GetHashCode(); Console.WriteLine("Хеш-значение объекта узла " + obj + " = " + hashObj); } } ================================================ FILE: ru/codes/csharp/chapter_hashing/hash_map.cs ================================================ /** * File: hash_map.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_hashing; public class hash_map { [Test] public void Test() { /* Инициализация хеш-таблицы */ Dictionary map = new() { /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу { 12836, "Сяо Ха" }, { 15937, "Сяо Ло" }, { 16750, "Сяо Суань" }, { 13276, "Сяо Фа" }, { 10583, "Сяо Я" } }; Console.WriteLine("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); PrintUtil.PrintHashMap(map); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value string name = map[15937]; Console.WriteLine("\nДля номера 15937 найдено имя " + name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.Remove(10583); Console.WriteLine("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); PrintUtil.PrintHashMap(map); /* Обход хеш-таблицы */ Console.WriteLine("\nОтдельный обход пар ключ-значение"); foreach (var kv in map) { Console.WriteLine(kv.Key + " -> " + kv.Value); } Console.WriteLine("\nОтдельный обход ключей"); foreach (int key in map.Keys) { Console.WriteLine(key); } Console.WriteLine("\nОтдельный обход значений"); foreach (string val in map.Values) { Console.WriteLine(val); } } } ================================================ FILE: ru/codes/csharp/chapter_hashing/hash_map_chaining.cs ================================================ /** * File: hash_map_chaining.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; /* Хеш-таблица с цепочками */ class HashMapChaining { int size; // Число пар ключ-значение int capacity; // Вместимость хеш-таблицы double loadThres; // Порог коэффициента загрузки для запуска расширения int extendRatio; // Коэффициент расширения List> buckets; // Массив корзин /* Конструктор */ public HashMapChaining() { size = 0; capacity = 4; loadThres = 2.0 / 3.0; extendRatio = 2; buckets = new List>(capacity); for (int i = 0; i < capacity; i++) { buckets.Add([]); } } /* Хеш-функция */ int HashFunc(int key) { return key % capacity; } /* Коэффициент загрузки */ double LoadFactor() { return (double)size / capacity; } /* Операция поиска */ public string? Get(int key) { int index = HashFunc(key); // Обойти корзину; если найден key, вернуть соответствующее val foreach (Pair pair in buckets[index]) { if (pair.key == key) { return pair.val; } } // Если key не найден, вернуть null return null; } /* Операция добавления */ public void Put(int key, string val) { // Когда коэффициент загрузки превышает порог, выполнить расширение if (LoadFactor() > loadThres) { Extend(); } int index = HashFunc(key); // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть foreach (Pair pair in buckets[index]) { if (pair.key == key) { pair.val = val; return; } } // Если такого key нет, добавить пару ключ-значение в конец buckets[index].Add(new Pair(key, val)); size++; } /* Операция удаления */ public void Remove(int key) { int index = HashFunc(key); // Обойти корзину и удалить из нее пару ключ-значение foreach (Pair pair in buckets[index].ToList()) { if (pair.key == key) { buckets[index].Remove(pair); size--; break; } } } /* Расширить хеш-таблицу */ void Extend() { // Временно сохранить исходную хеш-таблицу List> bucketsTmp = buckets; // Инициализация новой хеш-таблицы после расширения capacity *= extendRatio; buckets = new List>(capacity); for (int i = 0; i < capacity; i++) { buckets.Add([]); } size = 0; // Перенести пары ключ-значение из исходной хеш-таблицы в новую foreach (List bucket in bucketsTmp) { foreach (Pair pair in bucket) { Put(pair.key, pair.val); } } } /* Вывести хеш-таблицу */ public void Print() { foreach (List bucket in buckets) { List res = []; foreach (Pair pair in bucket) { res.Add(pair.key + " -> " + pair.val); } foreach (string kv in res) { Console.WriteLine(kv); } } } } public class hash_map_chaining { [Test] public void Test() { /* Инициализация хеш-таблицы */ HashMapChaining map = new(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.Put(12836, "Сяо Ха"); map.Put(15937, "Сяо Ло"); map.Put(16750, "Сяо Суань"); map.Put(13276, "Сяо Фа"); map.Put(10583, "Сяо Я"); Console.WriteLine("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); map.Print(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value string? name = map.Get(13276); Console.WriteLine("\nДля номера 13276 найдено имя " + name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.Remove(12836); Console.WriteLine("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение"); map.Print(); } } ================================================ FILE: ru/codes/csharp/chapter_hashing/hash_map_open_addressing.cs ================================================ /** * File: hash_map_open_addressing.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; /* Хеш-таблица с открытой адресацией */ class HashMapOpenAddressing { int size; // Число пар ключ-значение int capacity = 4; // Вместимость хеш-таблицы double loadThres = 2.0 / 3.0; // Порог коэффициента загрузки для запуска расширения int extendRatio = 2; // Коэффициент расширения Pair[] buckets; // Массив корзин Pair TOMBSTONE = new(-1, "-1"); // Удалить метку /* Конструктор */ public HashMapOpenAddressing() { size = 0; buckets = new Pair[capacity]; } /* Хеш-функция */ int HashFunc(int key) { return key % capacity; } /* Коэффициент загрузки */ double LoadFactor() { return (double)size / capacity; } /* Найти индекс корзины, соответствующий key */ int FindBucket(int key) { int index = HashFunc(key); int firstTombstone = -1; // Выполнять линейное пробирование и завершить при встрече с пустой корзиной while (buckets[index] != null) { // Если встретился key, вернуть соответствующий индекс корзины if (buckets[index].key == key) { // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index]; buckets[index] = TOMBSTONE; return firstTombstone; // Вернуть индекс корзины после перемещения } return index; // Вернуть индекс корзины } // Записать первую встретившуюся метку удаления if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index; } // Вычислить индекс корзины; при выходе за конец вернуться к началу index = (index + 1) % capacity; } // Если key не существует, вернуть индекс точки добавления return firstTombstone == -1 ? index : firstTombstone; } /* Операция поиска */ public string? Get(int key) { // Найти индекс корзины, соответствующий key int index = FindBucket(key); // Если пара ключ-значение найдена, вернуть соответствующее val if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index].val; } // Если пары ключ-значение не существует, вернуть null return null; } /* Операция добавления */ public void Put(int key, string val) { // Когда коэффициент загрузки превышает порог, выполнить расширение if (LoadFactor() > loadThres) { Extend(); } // Найти индекс корзины, соответствующий key int index = FindBucket(key); // Если пара ключ-значение найдена, перезаписать val и вернуть if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index].val = val; return; } // Если пары ключ-значение нет, добавить ее buckets[index] = new Pair(key, val); size++; } /* Операция удаления */ public void Remove(int key) { // Найти индекс корзины, соответствующий key int index = FindBucket(key); // Если пара ключ-значение найдена, заменить ее меткой удаления if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE; size--; } } /* Расширить хеш-таблицу */ void Extend() { // Временно сохранить исходную хеш-таблицу Pair[] bucketsTmp = buckets; // Инициализация новой хеш-таблицы после расширения capacity *= extendRatio; buckets = new Pair[capacity]; size = 0; // Перенести пары ключ-значение из исходной хеш-таблицы в новую foreach (Pair pair in bucketsTmp) { if (pair != null && pair != TOMBSTONE) { Put(pair.key, pair.val); } } } /* Вывести хеш-таблицу */ public void Print() { foreach (Pair pair in buckets) { if (pair == null) { Console.WriteLine("null"); } else if (pair == TOMBSTONE) { Console.WriteLine("TOMBSTONE"); } else { Console.WriteLine(pair.key + " -> " + pair.val); } } } } public class hash_map_open_addressing { [Test] public void Test() { /* Инициализация хеш-таблицы */ HashMapOpenAddressing map = new(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.Put(12836, "Сяо Ха"); map.Put(15937, "Сяо Ло"); map.Put(16750, "Сяо Суань"); map.Put(13276, "Сяо Фа"); map.Put(10583, "Сяо Я"); Console.WriteLine("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); map.Print(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value string? name = map.Get(13276); Console.WriteLine("\nДля номера 13276 найдено имя " + name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.Remove(16750); Console.WriteLine("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение"); map.Print(); } } ================================================ FILE: ru/codes/csharp/chapter_hashing/simple_hash.cs ================================================ /** * File: simple_hash.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; public class simple_hash { /* Аддитивное хеширование */ int AddHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = (hash + c) % MODULUS; } return (int)hash; } /* Мультипликативное хеширование */ int MulHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = (31 * hash + c) % MODULUS; } return (int)hash; } /* XOR-хеширование */ int XorHash(string key) { int hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash ^= c; } return hash & MODULUS; } /* Хеширование с циклическим сдвигом */ int RotHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS; } return (int)hash; } [Test] public void Test() { string key = "Hello Algo"; int hash = AddHash(key); Console.WriteLine("Хеш-сумма сложением = " + hash); hash = MulHash(key); Console.WriteLine("Хеш-сумма умножением = " + hash); hash = XorHash(key); Console.WriteLine("Хеш-сумма XOR = " + hash); hash = RotHash(key); Console.WriteLine("Хеш-сумма с циклическим сдвигом = " + hash); } } ================================================ FILE: ru/codes/csharp/chapter_heap/heap.cs ================================================ /** * File: heap.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_heap; public class heap { void TestPush(PriorityQueue heap, int val) { heap.Enqueue(val, val); // Добавление элемента в кучу Console.WriteLine($"\nПосле добавления элемента {val} в кучу\n"); PrintUtil.PrintHeap(heap); } void TestPop(PriorityQueue heap) { int val = heap.Dequeue(); // Извлечение элемента с вершины кучи Console.WriteLine($"\nПосле извлечения элемента вершины кучи {val}\n"); PrintUtil.PrintHeap(heap); } [Test] public void Test() { /* Инициализация кучи */ // Инициализация минимальной кучи PriorityQueue minHeap = new(); // Инициализировать максимальную кучу (достаточно изменить Comparer с помощью lambda-выражения) PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); Console.WriteLine("Ниже приведен тестовый пример для max-heap"); /* Добавление элемента в кучу */ TestPush(maxHeap, 1); TestPush(maxHeap, 3); TestPush(maxHeap, 2); TestPush(maxHeap, 5); TestPush(maxHeap, 4); /* Получение элемента с вершины кучи */ int peek = maxHeap.Peek(); Console.WriteLine($"Элемент на вершине кучи = {peek}"); /* Извлечение элемента с вершины кучи */ // Элементы, извлеченные из кучи, образуют последовательность от большего к меньшему TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); /* Получение размера кучи */ int size = maxHeap.Count; Console.WriteLine($"Количество элементов в куче = {size}"); /* Проверка, пуста ли куча */ bool isEmpty = maxHeap.Count == 0; Console.WriteLine($"Пуста ли куча: {isEmpty}"); /* Построить кучу по входному списку */ var list = new int[] { 1, 3, 2, 5, 4 }; minHeap = new PriorityQueue(list.Select(x => (x, x))); Console.WriteLine("После построения min-heap из входного списка"); PrintUtil.PrintHeap(minHeap); } } ================================================ FILE: ru/codes/csharp/chapter_heap/my_heap.cs ================================================ /** * File: my_heap.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_heap; /* Максимальная куча */ class MaxHeap { // Использовать список вместо массива, чтобы не учитывать проблему расширения List maxHeap; /* Конструктор, создающий пустую кучу */ public MaxHeap() { maxHeap = []; } /* Конструктор: построить кучу по входному списку */ public MaxHeap(IEnumerable nums) { // Добавить элементы списка в кучу без изменений maxHeap = new List(nums); // Выполнить heapify для всех узлов, кроме листовых var size = Parent(this.Size() - 1); for (int i = size; i >= 0; i--) { SiftDown(i); } } /* Получить индекс левого дочернего узла */ int Left(int i) { return 2 * i + 1; } /* Получить индекс правого дочернего узла */ int Right(int i) { return 2 * i + 2; } /* Получить индекс родительского узла */ int Parent(int i) { return (i - 1) / 2; // Округление вниз при делении } /* Доступ к элементу на вершине кучи */ public int Peek() { return maxHeap[0]; } /* Добавление элемента в кучу */ public void Push(int val) { // Добавление узла maxHeap.Add(val); // Просеивание снизу вверх SiftUp(Size() - 1); } /* Получение размера кучи */ public int Size() { return maxHeap.Count; } /* Проверка, пуста ли куча */ public bool IsEmpty() { return Size() == 0; } /* Начиная с узла i, выполнить просеивание снизу вверх */ void SiftUp(int i) { while (true) { // Получение родительского узла для узла i int p = Parent(i); // Если «выход за пределы корневого узла» или «узел не требует исправления», завершить просеивание if (p < 0 || maxHeap[i] <= maxHeap[p]) break; // Поменять два узла местами Swap(i, p); // Циклическое просеивание вверх i = p; } } /* Извлечение элемента из кучи */ public int Pop() { // Обработка пустого случая if (IsEmpty()) throw new IndexOutOfRangeException(); // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) Swap(0, Size() - 1); // Удаление узла int val = maxHeap.Last(); maxHeap.RemoveAt(Size() - 1); // Просеивание сверху вниз SiftDown(0); // Вернуть элемент с вершины кучи return val; } /* Начиная с узла i, выполнить просеивание сверху вниз */ void SiftDown(int i) { while (true) { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma int l = Left(i), r = Right(i), ma = i; if (l < Size() && maxHeap[l] > maxHeap[ma]) ma = l; if (r < Size() && maxHeap[r] > maxHeap[ma]) ma = r; // Если «узел i максимален» или «выход за пределы листовых узлов», завершить просеивание if (ma == i) break; // Поменять два узла местами Swap(i, ma); // Циклическое просеивание вниз i = ma; } } /* Поменять элементы местами */ void Swap(int i, int p) { (maxHeap[i], maxHeap[p]) = (maxHeap[p], maxHeap[i]); } /* Вывести кучу (двоичное дерево) */ public void Print() { var queue = new Queue(maxHeap); PrintUtil.PrintHeap(queue); } } public class my_heap { [Test] public void Test() { /* Инициализация максимальной кучи */ MaxHeap maxHeap = new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); Console.WriteLine("\nПосле построения кучи из входного списка"); maxHeap.Print(); /* Получение элемента с вершины кучи */ int peek = maxHeap.Peek(); Console.WriteLine($"Элемент на вершине кучи = {peek}"); /* Добавление элемента в кучу */ int val = 7; maxHeap.Push(val); Console.WriteLine($"После добавления элемента {val} в кучу"); maxHeap.Print(); /* Извлечение элемента с вершины кучи */ peek = maxHeap.Pop(); Console.WriteLine($"После извлечения элемента вершины кучи {peek}"); maxHeap.Print(); /* Получение размера кучи */ int size = maxHeap.Size(); Console.WriteLine($"Количество элементов в куче = {size}"); /* Проверка, пуста ли куча */ bool isEmpty = maxHeap.IsEmpty(); Console.WriteLine($"Пуста ли куча: {isEmpty}"); } } ================================================ FILE: ru/codes/csharp/chapter_heap/top_k.cs ================================================ /** * File: top_k.cs * Created Time: 2023-06-14 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_heap; public class top_k { /* Найти k наибольших элементов массива с помощью кучи */ PriorityQueue TopKHeap(int[] nums, int k) { // Инициализация минимальной кучи PriorityQueue heap = new(); // Поместить первые k элементов массива в кучу for (int i = 0; i < k; i++) { heap.Enqueue(nums[i], nums[i]); } // Начиная с элемента k+1, поддерживать длину кучи равной k for (int i = k; i < nums.Length; i++) { // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу if (nums[i] > heap.Peek()) { heap.Dequeue(); heap.Enqueue(nums[i], nums[i]); } } return heap; } [Test] public void Test() { int[] nums = [1, 7, 6, 3, 2]; int k = 3; PriorityQueue res = TopKHeap(nums, k); Console.WriteLine("Наибольшие " + k + " элементов ="); PrintUtil.PrintHeap(res); } } ================================================ FILE: ru/codes/csharp/chapter_searching/binary_search.cs ================================================ /** * File: binary_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class binary_search { /* Бинарный поиск (двусторонне замкнутый интервал) */ int BinarySearch(int[] nums, int target) { // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно int i = 0, j = nums.Length - 1; // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) while (i <= j) { int m = i + (j - i) / 2; // Вычислить индекс середины m if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j] i = m + 1; else if (nums[m] > target) // Это означает, что target находится в интервале [i, m-1] j = m - 1; else // Целевой элемент найден, вернуть его индекс return m; } // Целевой элемент не найден, вернуть -1 return -1; } /* Бинарный поиск (лево замкнутый, право открытый интервал) */ int BinarySearchLCRO(int[] nums, int target) { // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно int i = 0, j = nums.Length; // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) while (i < j) { int m = i + (j - i) / 2; // Вычислить индекс середины m if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j) i = m + 1; else if (nums[m] > target) // Это означает, что target находится в интервале [i, m) j = m; else // Целевой элемент найден, вернуть его индекс return m; } // Целевой элемент не найден, вернуть -1 return -1; } [Test] public void Test() { int target = 6; int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* Бинарный поиск (двусторонне замкнутый интервал) */ int index = BinarySearch(nums, target); Console.WriteLine("Индекс целевого элемента 6 = " + index); /* Бинарный поиск (лево замкнутый, право открытый интервал) */ index = BinarySearchLCRO(nums, target); Console.WriteLine("Индекс целевого элемента 6 = " + index); } } ================================================ FILE: ru/codes/csharp/chapter_searching/binary_search_edge.cs ================================================ /** * File: binary_search_edge.cs * Created Time: 2023-08-06 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_searching; public class binary_search_edge { /* Бинарный поиск самого левого target */ int BinarySearchLeftEdge(int[] nums, int target) { // Эквивалентно поиску точки вставки target int i = binary_search_insertion.BinarySearchInsertion(nums, target); // target не найден, вернуть -1 if (i == nums.Length || nums[i] != target) { return -1; } // Найти target и вернуть индекс i return i; } /* Бинарный поиск самого правого target */ int BinarySearchRightEdge(int[] nums, int target) { // Преобразовать задачу в поиск самого левого target + 1 int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1); // j указывает на самый правый target, а i — на первый элемент больше target int j = i - 1; // target не найден, вернуть -1 if (j == -1 || nums[j] != target) { return -1; } // Найти target и вернуть индекс j return j; } [Test] public void Test() { // Массив с повторяющимися элементами int[] nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; Console.WriteLine("\nМассив nums = " + nums.PrintList()); // Бинарный поиск левой и правой границы foreach (int target in new int[] { 6, 7 }) { int index = BinarySearchLeftEdge(nums, target); Console.WriteLine("Индекс самого левого элемента " + target + " равен " + index); index = BinarySearchRightEdge(nums, target); Console.WriteLine("Индекс самого правого элемента " + target + " равен " + index); } } } ================================================ FILE: ru/codes/csharp/chapter_searching/binary_search_insertion.cs ================================================ /** * File: binary_search_insertion.cs * Created Time: 2023-08-06 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_searching; public class binary_search_insertion { /* Бинарный поиск точки вставки (без повторяющихся элементов) */ public static int BinarySearchInsertionSimple(int[] nums, int target) { int i = 0, j = nums.Length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Вычислить индекс середины m if (nums[m] < target) { i = m + 1; // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1; // target находится в интервале [i, m-1] } else { return m; // Найти target и вернуть точку вставки m } } // target не найден, вернуть точку вставки i return i; } /* Бинарный поиск точки вставки (с повторяющимися элементами) */ public static int BinarySearchInsertion(int[] nums, int target) { int i = 0, j = nums.Length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // Вычислить индекс середины m if (nums[m] < target) { i = m + 1; // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1; // target находится в интервале [i, m-1] } else { j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] } } // Вернуть точку вставки i return i; } [Test] public void Test() { // Массив без повторяющихся элементов int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; Console.WriteLine("\nМассив nums = " + nums.PrintList()); // Бинарный поиск точки вставки foreach (int target in new int[] { 6, 9 }) { int index = BinarySearchInsertionSimple(nums, target); Console.WriteLine("Индекс позиции вставки элемента " + target + " равен " + index); } // Массив с повторяющимися элементами nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; Console.WriteLine("\nМассив nums = " + nums.PrintList()); // Бинарный поиск точки вставки foreach (int target in new int[] { 2, 6, 20 }) { int index = BinarySearchInsertion(nums, target); Console.WriteLine("Индекс позиции вставки элемента " + target + " равен " + index); } } } ================================================ FILE: ru/codes/csharp/chapter_searching/hashing_search.cs ================================================ /** * File: hashing_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class hashing_search { /* Хеш-поиск (массив) */ int HashingSearchArray(Dictionary map, int target) { // key хеш-таблицы: целевой элемент, value: индекс // Если такого key нет в хеш-таблице, вернуть -1 return map.GetValueOrDefault(target, -1); } /* Хеш-поиск (связный список) */ ListNode? HashingSearchLinkedList(Dictionary map, int target) { // key хеш-таблицы: значение целевого узла, value: объект узла // Если такого key нет в хеш-таблице, вернуть null return map.GetValueOrDefault(target); } [Test] public void Test() { int target = 3; /* Хеш-поиск (массив) */ int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // Инициализация хеш-таблицы Dictionary map = []; for (int i = 0; i < nums.Length; i++) { map[nums[i]] = i; // key: элемент, value: индекс } int index = HashingSearchArray(map, target); Console.WriteLine("Индекс целевого элемента 3 = " + index); /* Хеш-поиск (связный список) */ ListNode? head = ListNode.ArrToLinkedList(nums); // Инициализация хеш-таблицы Dictionary map1 = []; while (head != null) { map1[head.val] = head; // key: значение узла, value: узел head = head.next; } ListNode? node = HashingSearchLinkedList(map1, target); Console.WriteLine("Объект узла со значением 3 = " + node); } } ================================================ FILE: ru/codes/csharp/chapter_searching/linear_search.cs ================================================ /** * File: linear_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class linear_search { /* Линейный поиск (массив) */ int LinearSearchArray(int[] nums, int target) { // Обход массива for (int i = 0; i < nums.Length; i++) { // Целевой элемент найден, вернуть его индекс if (nums[i] == target) return i; } // Целевой элемент не найден, вернуть -1 return -1; } /* Линейный поиск (связный список) */ ListNode? LinearSearchLinkedList(ListNode? head, int target) { // Обойти связный список while (head != null) { // Найти целевой узел и вернуть его if (head.val == target) return head; head = head.next; } // Целевой узел не найден, вернуть null return null; } [Test] public void Test() { int target = 3; /* Выполнить линейный поиск в массиве */ int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; int index = LinearSearchArray(nums, target); Console.WriteLine("Индекс целевого элемента 3 = " + index); /* Выполнить линейный поиск в связном списке */ ListNode? head = ListNode.ArrToLinkedList(nums); ListNode? node = LinearSearchLinkedList(head, target); Console.WriteLine("Объект узла со значением 3 = " + node); } } ================================================ FILE: ru/codes/csharp/chapter_searching/two_sum.cs ================================================ /** * File: two_sum.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class two_sum { /* Метод 1: полный перебор */ int[] TwoSumBruteForce(int[] nums, int target) { int size = nums.Length; // Два вложенных цикла, временная сложность O(n^2) for (int i = 0; i < size - 1; i++) { for (int j = i + 1; j < size; j++) { if (nums[i] + nums[j] == target) return [i, j]; } } return []; } /* Метод 2: вспомогательная хеш-таблица */ int[] TwoSumHashTable(int[] nums, int target) { int size = nums.Length; // Вспомогательная хеш-таблица, пространственная сложность O(n) Dictionary dic = []; // Один цикл, временная сложность O(n) for (int i = 0; i < size; i++) { if (dic.ContainsKey(target - nums[i])) { return [dic[target - nums[i]], i]; } dic.Add(nums[i], i); } return []; } [Test] public void Test() { // ======= Test Case ======= int[] nums = [2, 7, 11, 15]; int target = 13; // ====== Основной код ====== // Метод 1 int[] res = TwoSumBruteForce(nums, target); Console.WriteLine("Результат метода 1 res = " + string.Join(",", res)); // Метод 2 res = TwoSumHashTable(nums, target); Console.WriteLine("Результат метода 2 res = " + string.Join(",", res)); } } ================================================ FILE: ru/codes/csharp/chapter_sorting/bubble_sort.cs ================================================ /** * File: bubble_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class bubble_sort { /* Пузырьковая сортировка */ void BubbleSort(int[] nums) { // Внешний цикл: неотсортированный диапазон [0, i] for (int i = nums.Length - 1; i > 0; i--) { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); } } } } /* Пузырьковая сортировка (оптимизация флагом) */ void BubbleSortWithFlag(int[] nums) { // Внешний цикл: неотсортированный диапазон [0, i] for (int i = nums.Length - 1; i > 0; i--) { bool flag = false; // Инициализировать флаг // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); flag = true; // Записать обмен элементов } } if (!flag) break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; BubbleSort(nums); Console.WriteLine("После пузырьковой сортировки nums = " + string.Join(",", nums)); int[] nums1 = [4, 1, 3, 1, 5, 2]; BubbleSortWithFlag(nums1); Console.WriteLine("После пузырьковой сортировки nums1 = " + string.Join(",", nums1)); } } ================================================ FILE: ru/codes/csharp/chapter_sorting/bucket_sort.cs ================================================ /** * File: bucket_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class bucket_sort { /* Сортировка корзинами */ void BucketSort(float[] nums) { // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину int k = nums.Length / 2; List> buckets = []; for (int i = 0; i < k; i++) { buckets.Add([]); } // 1. Распределить элементы массива по корзинам foreach (float num in nums) { // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] int i = (int)(num * k); // Добавить num в корзину i buckets[i].Add(num); } // 2. Выполнить сортировку внутри каждой корзины foreach (List bucket in buckets) { // Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки bucket.Sort(); } // 3. Обойти корзины и объединить результаты int j = 0; foreach (List bucket in buckets) { foreach (float num in bucket) { nums[j++] = num; } } } [Test] public void Test() { // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) float[] nums = [0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f]; BucketSort(nums); Console.WriteLine("После сортировки корзинами nums = " + string.Join(" ", nums)); } } ================================================ FILE: ru/codes/csharp/chapter_sorting/counting_sort.cs ================================================ /** * File: counting_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class counting_sort { /* Сортировка подсчетом */ // Простая реализация, не подходит для сортировки объектов void CountingSortNaive(int[] nums) { // 1. Найти максимальный элемент массива m int m = 0; foreach (int num in nums) { m = Math.Max(m, num); } // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num int[] counter = new int[m + 1]; foreach (int num in nums) { counter[num]++; } // 3. Обойти counter и заполнить исходный массив nums элементами int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* Сортировка подсчетом */ // Полная реализация, позволяет сортировать объекты и является стабильной сортировкой void CountingSort(int[] nums) { // 1. Найти максимальный элемент массива m int m = 0; foreach (int num in nums) { m = Math.Max(m, num); } // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num int[] counter = new int[m + 1]; foreach (int num in nums) { counter[num]++; } // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» // То есть counter[num]-1 — это индекс последнего появления num в res for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res // Инициализировать массив res для хранения результата int n = nums.Length; int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // Поместить num по соответствующему индексу counter[num]--; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num } // Перезаписать исходный массив nums массивом результата res for (int i = 0; i < n; i++) { nums[i] = res[i]; } } [Test] public void Test() { int[] nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; CountingSortNaive(nums); Console.WriteLine("После сортировки подсчетом (объекты не поддерживаются) nums = " + string.Join(" ", nums)); int[] nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; CountingSort(nums1); Console.WriteLine("После сортировки подсчетом nums1 = " + string.Join(" ", nums)); } } ================================================ FILE: ru/codes/csharp/chapter_sorting/heap_sort.cs ================================================ /** * File: heap_sort.cs * Created Time: 2023-06-01 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class heap_sort { /* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ void SiftDown(int[] nums, int n, int i) { while (true) { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; if (l < n && nums[l] > nums[ma]) ma = l; if (r < n && nums[r] > nums[ma]) ma = r; // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if (ma == i) break; // Поменять два узла местами (nums[ma], nums[i]) = (nums[i], nums[ma]); // Циклическое просеивание вниз i = ma; } } /* Сортировка кучей */ void HeapSort(int[] nums) { // Построение кучи: выполнить heapify для всех узлов, кроме листовых for (int i = nums.Length / 2 - 1; i >= 0; i--) { SiftDown(nums, nums.Length, i); } // Извлекать максимальный элемент из кучи в течение n-1 итераций for (int i = nums.Length - 1; i > 0; i--) { // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) (nums[i], nums[0]) = (nums[0], nums[i]); // Начиная с корневого узла, выполнить просеивание сверху вниз SiftDown(nums, i, 0); } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; HeapSort(nums); Console.WriteLine("После сортировки кучей nums = " + string.Join(" ", nums)); } } ================================================ FILE: ru/codes/csharp/chapter_sorting/insertion_sort.cs ================================================ /** * File: insertion_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class insertion_sort { /* Сортировка вставками */ void InsertionSort(int[] nums) { // Внешний цикл: отсортированный диапазон [0, i-1] for (int i = 1; i < nums.Length; i++) { int bas = nums[i], j = i - 1; // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] while (j >= 0 && nums[j] > bas) { nums[j + 1] = nums[j]; // Сдвинуть nums[j] на одну позицию вправо j--; } nums[j + 1] = bas; // Поместить base в правильную позицию } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; InsertionSort(nums); Console.WriteLine("После сортировки вставками nums = " + string.Join(",", nums)); } } ================================================ FILE: ru/codes/csharp/chapter_sorting/merge_sort.cs ================================================ /** * File: merge_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class merge_sort { /* Объединить левый и правый подмассивы */ void Merge(int[] nums, int left, int mid, int right) { // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] // Создать временный массив tmp для хранения результата слияния int[] tmp = new int[right - left + 1]; // Инициализировать начальные индексы левого и правого подмассивов int i = left, j = mid + 1, k = 0; // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums for (k = 0; k < tmp.Length; ++k) { nums[left + k] = tmp[k]; } } /* Сортировка слиянием */ void MergeSort(int[] nums, int left, int right) { // Условие завершения if (left >= right) return; // Завершить рекурсию, когда длина подмассива равна 1 // Этап разбиения int mid = left + (right - left) / 2; // Вычислить середину MergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив MergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив // Этап слияния Merge(nums, left, mid, right); } [Test] public void Test() { /* Сортировка слиянием */ int[] nums = [7, 3, 2, 6, 0, 1, 5, 4]; MergeSort(nums, 0, nums.Length - 1); Console.WriteLine("После сортировки слиянием nums = " + string.Join(",", nums)); } } ================================================ FILE: ru/codes/csharp/chapter_sorting/quick_sort.cs ================================================ /** * File: quick_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; class quickSort { /* Обмен элементов */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* Разбиение с опорными указателями */ static int Partition(int[] nums, int left, int right) { // Взять nums[left] в качестве опорного элемента int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Идти справа налево в поисках первого элемента меньше опорного while (i < j && nums[i] <= nums[left]) i++; // Идти слева направо в поисках первого элемента больше опорного Swap(nums, i, j); // Поменять эти два элемента местами } Swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } /* Быстрая сортировка */ public static void QuickSort(int[] nums, int left, int right) { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) return; // Разбиение с опорными указателями int pivot = Partition(nums, left, right); // Рекурсивно обработать левый и правый подмассивы QuickSort(nums, left, pivot - 1); QuickSort(nums, pivot + 1, right); } } /* Класс быстрой сортировки (оптимизация медианным опорным элементом) */ class QuickSortMedian { /* Обмен элементов */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* Выбрать медиану из трех кандидатов */ 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; } /* Разбиение с опорными указателями (медиана трех) */ static int Partition(int[] 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(int[] nums, int left, int right) { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) return; // Разбиение с опорными указателями int pivot = Partition(nums, left, right); // Рекурсивно обработать левый и правый подмассивы QuickSort(nums, left, pivot - 1); QuickSort(nums, pivot + 1, right); } } /* Класс быстрой сортировки (оптимизация глубины рекурсии) */ class QuickSortTailCall { /* Обмен элементов */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* Разбиение с опорными указателями */ static int Partition(int[] nums, int left, int right) { // Взять nums[left] в качестве опорного элемента int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Идти справа налево в поисках первого элемента меньше опорного while (i < j && nums[i] <= nums[left]) i++; // Идти слева направо в поисках первого элемента больше опорного Swap(nums, i, j); // Поменять эти два элемента местами } Swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } /* Быстрая сортировка (оптимизация глубины рекурсии) */ public static void QuickSort(int[] 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] } } } } public class quick_sort { [Test] public void Test() { /* Быстрая сортировка */ int[] nums = [2, 4, 1, 0, 3, 5]; quickSort.QuickSort(nums, 0, nums.Length - 1); Console.WriteLine("После быстрой сортировки nums = " + string.Join(",", nums)); /* Быстрая сортировка (оптимизация медианным опорным элементом) */ int[] nums1 = [2, 4, 1, 0, 3, 5]; QuickSortMedian.QuickSort(nums1, 0, nums1.Length - 1); Console.WriteLine("После быстрой сортировки (оптимизация медианным опорным элементом) nums1 = " + string.Join(",", nums1)); /* Быстрая сортировка (оптимизация глубины рекурсии) */ int[] nums2 = [2, 4, 1, 0, 3, 5]; QuickSortTailCall.QuickSort(nums2, 0, nums2.Length - 1); Console.WriteLine("После быстрой сортировки (оптимизация глубины рекурсии) nums2 = " + string.Join(",", nums2)); } } ================================================ FILE: ru/codes/csharp/chapter_sorting/radix_sort.cs ================================================ /** * File: radix_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class radix_sort { /* Получить k-й разряд элемента num, где exp = 10^(k-1) */ int Digit(int num, int exp) { // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени return (num / exp) % 10; } /* Сортировка подсчетом (сортировка по k-му разряду nums) */ void CountingSortDigit(int[] nums, int exp) { // Разряды десятичной системы лежат в диапазоне 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); // Получить k-й разряд nums[i], обозначив его как 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; // Получить индекс j цифры d в массиве res[j] = nums[i]; // Поместить текущий элемент по индексу j counter[d]--; // Уменьшить количество d на 1 } // Перезаписать исходный массив nums результатом for (int i = 0; i < n; i++) { nums[i] = res[i]; } } /* Поразрядная сортировка */ void RadixSort(int[] nums) { // Получить максимальный элемент массива, чтобы определить максимальное число разрядов int m = int.MinValue; foreach (int num in nums) { if (num > m) m = num; } // Проходить разряды от младшего к старшему for (int exp = 1; exp <= m; exp *= 10) { // Выполнить сортировку подсчетом по k-му разряду элементов массива // k = 1 -> exp = 1 // k = 2 -> exp = 10 // то есть exp = 10^(k-1) CountingSortDigit(nums, exp); } } [Test] public void Test() { // Поразрядная сортировка int[] nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ]; RadixSort(nums); Console.WriteLine("После поразрядной сортировки nums = " + string.Join(" ", nums)); } } ================================================ FILE: ru/codes/csharp/chapter_sorting/selection_sort.cs ================================================ /** * File: selection_sort.cs * Created Time: 2023-06-01 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class selection_sort { /* Сортировка выбором */ void SelectionSort(int[] nums) { int n = nums.Length; // Внешний цикл: неотсортированный диапазон [i, n-1] for (int i = 0; i < n - 1; i++) { // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // Записать индекс минимального элемента } // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона (nums[k], nums[i]) = (nums[i], nums[k]); } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; SelectionSort(nums); Console.WriteLine("После сортировки выбором nums = " + string.Join(" ", nums)); } } ================================================ FILE: ru/codes/csharp/chapter_stack_and_queue/array_deque.cs ================================================ /** * File: array_deque.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_stack_and_queue; /* Двусторонняя очередь на основе кольцевого массива */ public class ArrayDeque { int[] nums; // Массив для хранения элементов двусторонней очереди int front; // Указатель head, указывающий на первый элемент очереди int queSize; // Длина двусторонней очереди /* Конструктор */ public ArrayDeque(int capacity) { nums = new int[capacity]; front = queSize = 0; } /* Получить вместимость двусторонней очереди */ int Capacity() { return nums.Length; } /* Получение длины двусторонней очереди */ public int Size() { return queSize; } /* Проверка, пуста ли двусторонняя очередь */ public bool IsEmpty() { return queSize == 0; } /* Вычислить индекс в кольцевом массиве */ int Index(int i) { // С помощью операции взятия по модулю соединить начало и конец массива // Когда i выходит за конец массива, он возвращается в начало // Когда i выходит за начало массива, он возвращается в конец return (i + Capacity()) % Capacity(); } /* Добавление в голову очереди */ public void PushFirst(int num) { if (queSize == Capacity()) { Console.WriteLine("Двусторонняя очередь заполнена"); return; } // Указатель головы сдвигается на одну позицию влево // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост front = Index(front - 1); // Добавить num в голову очереди nums[front] = num; queSize++; } /* Добавление в хвост очереди */ public void PushLast(int num) { if (queSize == Capacity()) { Console.WriteLine("Двусторонняя очередь заполнена"); return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 int rear = Index(front + queSize); // Добавить num в хвост очереди nums[rear] = num; queSize++; } /* Извлечение из головы очереди */ public int PopFirst() { int num = PeekFirst(); // Указатель головы сдвигается на одну позицию назад front = Index(front + 1); queSize--; return num; } /* Извлечение из хвоста очереди */ public int PopLast() { int num = PeekLast(); queSize--; return num; } /* Доступ к элементу в начале очереди */ public int PeekFirst() { if (IsEmpty()) { throw new InvalidOperationException(); } return nums[front]; } /* Доступ к элементу в конце очереди */ public int PeekLast() { if (IsEmpty()) { throw new InvalidOperationException(); } // Вычислить индекс хвостового элемента int last = Index(front + queSize - 1); return nums[last]; } /* Вернуть массив для вывода */ public int[] ToArray() { // Преобразовывать только элементы списка в пределах фактической длины int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[Index(j)]; } return res; } } public class array_deque { [Test] public void Test() { /* Инициализация двусторонней очереди */ ArrayDeque deque = new(10); deque.PushLast(3); deque.PushLast(2); deque.PushLast(5); Console.WriteLine("Двусторонняя очередь deque = " + string.Join(" ", deque.ToArray())); /* Доступ к элементу */ int peekFirst = deque.PeekFirst(); Console.WriteLine("Первый элемент peekFirst = " + peekFirst); int peekLast = deque.PeekLast(); Console.WriteLine("Последний элемент peekLast = " + peekLast); /* Добавление элемента в очередь */ deque.PushLast(4); Console.WriteLine("После добавления элемента 4 в хвост deque = " + string.Join(" ", deque.ToArray())); deque.PushFirst(1); Console.WriteLine("После добавления элемента 1 в голову deque = " + string.Join(" ", deque.ToArray())); /* Извлечение элемента из очереди */ int popLast = deque.PopLast(); Console.WriteLine("Извлеченный из хвоста элемент = " + popLast + ", deque после извлечения из хвоста = " + string.Join(" ", deque.ToArray())); int popFirst = deque.PopFirst(); Console.WriteLine("Извлеченный из головы элемент = " + popFirst + ", deque после извлечения из головы = " + string.Join(" ", deque.ToArray())); /* Получение длины двусторонней очереди */ int size = deque.Size(); Console.WriteLine("Длина двусторонней очереди size = " + size); /* Проверка, пуста ли двусторонняя очередь */ bool isEmpty = deque.IsEmpty(); Console.WriteLine("Пуста ли двусторонняя очередь = " + isEmpty); } } ================================================ FILE: ru/codes/csharp/chapter_stack_and_queue/array_queue.cs ================================================ /** * File: array_queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* Очередь на основе кольцевого массива */ class ArrayQueue { int[] nums; // Массив для хранения элементов очереди int front; // Указатель head, указывающий на первый элемент очереди int queSize; // Длина очереди public ArrayQueue(int capacity) { nums = new int[capacity]; front = queSize = 0; } /* Получить вместимость очереди */ int Capacity() { return nums.Length; } /* Получение длины очереди */ public int Size() { return queSize; } /* Проверка, пуста ли очередь */ public bool IsEmpty() { return queSize == 0; } /* Поместить в очередь */ public void Push(int num) { if (queSize == Capacity()) { Console.WriteLine("Очередь заполнена"); return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива int rear = (front + queSize) % Capacity(); // Добавить num в хвост очереди nums[rear] = num; queSize++; } /* Извлечь из очереди */ public int Pop() { int num = Peek(); // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива front = (front + 1) % Capacity(); queSize--; return num; } /* Доступ к элементу в начале очереди */ public int Peek() { if (IsEmpty()) throw new Exception(); return nums[front]; } /* Вернуть массив */ public int[] ToArray() { // Преобразовывать только элементы списка в пределах фактической длины int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[j % this.Capacity()]; } return res; } } public class array_queue { [Test] public void Test() { /* Инициализация очереди */ int capacity = 10; ArrayQueue queue = new(capacity); /* Добавление элемента в очередь */ queue.Push(1); queue.Push(3); queue.Push(2); queue.Push(5); queue.Push(4); Console.WriteLine("Очередь queue = " + string.Join(",", queue.ToArray())); /* Доступ к элементу в начале очереди */ int peek = queue.Peek(); Console.WriteLine("Первый элемент peek = " + peek); /* Извлечение элемента из очереди */ int pop = queue.Pop(); Console.WriteLine("Извлеченный элемент pop = " + pop + ", queue после извлечения = " + string.Join(",", queue.ToArray())); /* Получение длины очереди */ int size = queue.Size(); Console.WriteLine("Длина очереди size = " + size); /* Проверка, пуста ли очередь */ bool isEmpty = queue.IsEmpty(); Console.WriteLine("Пуста ли очередь = " + isEmpty); /* Проверка кольцевого массива */ for (int i = 0; i < 10; i++) { queue.Push(i); queue.Pop(); Console.WriteLine("После " + i + "-го раунда операций enqueue и dequeue queue = " + string.Join(",", queue.ToArray())); } } } ================================================ FILE: ru/codes/csharp/chapter_stack_and_queue/array_stack.cs ================================================ /** * File: array_stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* Стек на основе массива */ class ArrayStack { List stack; public ArrayStack() { // Инициализация списка (динамического массива) stack = []; } /* Получение длины стека */ public int Size() { return stack.Count; } /* Проверка, пуст ли стек */ public bool IsEmpty() { return Size() == 0; } /* Поместить в стек */ public void Push(int num) { stack.Add(num); } /* Извлечь из стека */ public int Pop() { if (IsEmpty()) throw new Exception(); var val = Peek(); stack.RemoveAt(Size() - 1); return val; } /* Доступ к верхнему элементу стека */ public int Peek() { if (IsEmpty()) throw new Exception(); return stack[Size() - 1]; } /* Преобразовать List в Array и вернуть */ public int[] ToArray() { return [.. stack]; } } public class array_stack { [Test] public void Test() { /* Инициализация стека */ ArrayStack stack = new(); /* Помещение элемента в стек */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); Console.WriteLine("Стек stack = " + string.Join(",", stack.ToArray())); /* Доступ к верхнему элементу стека */ int peek = stack.Peek(); Console.WriteLine("Верхний элемент peek = " + peek); /* Извлечение элемента из стека */ int pop = stack.Pop(); Console.WriteLine("Извлеченный элемент pop = " + pop + ", stack после извлечения = " + string.Join(",", stack.ToArray())); /* Получение длины стека */ int size = stack.Size(); Console.WriteLine("Длина стека size = " + size); /* Проверка на пустоту */ bool isEmpty = stack.IsEmpty(); Console.WriteLine("Пуст ли стек = " + isEmpty); } } ================================================ FILE: ru/codes/csharp/chapter_stack_and_queue/deque.cs ================================================ /** * File: deque.cs * Created Time: 2022-12-30 * Author: moonache (microin1301@outlook.com) */ namespace hello_algo.chapter_stack_and_queue; public class deque { [Test] public void Test() { /* Инициализация двусторонней очереди */ // В C# связный список LinkedList рассматривается как двусторонняя очередь LinkedList deque = new(); /* Добавление элемента в очередь */ deque.AddLast(2); // Добавить в хвост очереди deque.AddLast(5); deque.AddLast(4); deque.AddFirst(3); // Добавить в голову очереди deque.AddFirst(1); Console.WriteLine("Двусторонняя очередь deque = " + string.Join(",", deque)); /* Доступ к элементу */ int? peekFirst = deque.First?.Value; // Элемент в голове очереди Console.WriteLine("Первый элемент peekFirst = " + peekFirst); int? peekLast = deque.Last?.Value; // Элемент в хвосте очереди Console.WriteLine("Последний элемент peekLast = " + peekLast); /* Извлечение элемента из очереди */ deque.RemoveFirst(); // Извлечь элемент из головы очереди Console.WriteLine("После извлечения элемента из головы deque = " + string.Join(",", deque)); deque.RemoveLast(); // Извлечь элемент из хвоста очереди Console.WriteLine("После извлечения элемента из хвоста deque = " + string.Join(",", deque)); /* Получение длины двусторонней очереди */ int size = deque.Count; Console.WriteLine("Длина двусторонней очереди size = " + size); /* Проверка, пуста ли двусторонняя очередь */ bool isEmpty = deque.Count == 0; Console.WriteLine("Пуста ли двусторонняя очередь = " + isEmpty); } } ================================================ FILE: ru/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs ================================================ /** * File: linkedlist_deque.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_stack_and_queue; /* Узел двусвязного списка */ public class ListNode(int val) { public int val = val; // Значение узла public ListNode? next = null; // Ссылка на узел-преемник public ListNode? prev = null; // Ссылка на узел-предшественник } /* Двусторонняя очередь на основе двусвязного списка */ public class LinkedListDeque { ListNode? front, rear; // Головной узел front, хвостовой узел rear int queSize = 0; // Длина двусторонней очереди public LinkedListDeque() { front = null; rear = null; } /* Получение длины двусторонней очереди */ public int Size() { return queSize; } /* Проверка, пуста ли двусторонняя очередь */ public bool IsEmpty() { return Size() == 0; } /* Операция добавления в очередь */ void Push(int num, bool isFront) { ListNode node = new(num); // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node if (IsEmpty()) { front = node; rear = node; } // Операция добавления в голову очереди else if (isFront) { // Добавить node в голову списка front!.prev = node; node.next = front; front = node; // Обновить головной узел } // Операция добавления в хвост очереди else { // Добавить node в хвост списка rear!.next = node; node.prev = rear; rear = node; // Обновить хвостовой узел } queSize++; // Обновить длину очереди } /* Добавление в голову очереди */ public void PushFirst(int num) { Push(num, true); } /* Добавление в хвост очереди */ public void PushLast(int num) { Push(num, false); } /* Операция извлечения из очереди */ int? Pop(bool isFront) { if (IsEmpty()) throw new Exception(); int? val; // Операция извлечения из головы очереди if (isFront) { val = front?.val; // Временно сохранить значение головного узла // Удалить головной узел ListNode? fNext = front?.next; if (fNext != null) { fNext.prev = null; front!.next = null; } front = fNext; // Обновить головной узел } // Операция извлечения из хвоста очереди else { val = rear?.val; // Временно сохранить значение хвостового узла // Удалить хвостовой узел ListNode? rPrev = rear?.prev; if (rPrev != null) { rPrev.next = null; rear!.prev = null; } rear = rPrev; // Обновить хвостовой узел } queSize--; // Обновить длину очереди return val; } /* Извлечение из головы очереди */ public int? PopFirst() { return Pop(true); } /* Извлечение из хвоста очереди */ public int? PopLast() { return Pop(false); } /* Доступ к элементу в начале очереди */ public int? PeekFirst() { if (IsEmpty()) throw new Exception(); return front?.val; } /* Доступ к элементу в конце очереди */ public int? PeekLast() { if (IsEmpty()) throw new Exception(); return rear?.val; } /* Вернуть массив для вывода */ public int?[] ToArray() { ListNode? node = front; int?[] res = new int?[Size()]; for (int i = 0; i < res.Length; i++) { res[i] = node?.val; node = node?.next; } return res; } } public class linkedlist_deque { [Test] public void Test() { /* Инициализация двусторонней очереди */ LinkedListDeque deque = new(); deque.PushLast(3); deque.PushLast(2); deque.PushLast(5); Console.WriteLine("Двусторонняя очередь deque = " + string.Join(" ", deque.ToArray())); /* Доступ к элементу */ int? peekFirst = deque.PeekFirst(); Console.WriteLine("Первый элемент peekFirst = " + peekFirst); int? peekLast = deque.PeekLast(); Console.WriteLine("Последний элемент peekLast = " + peekLast); /* Добавление элемента в очередь */ deque.PushLast(4); Console.WriteLine("После добавления элемента 4 в хвост deque = " + string.Join(" ", deque.ToArray())); deque.PushFirst(1); Console.WriteLine("После добавления элемента 1 в голову deque = " + string.Join(" ", deque.ToArray())); /* Извлечение элемента из очереди */ int? popLast = deque.PopLast(); Console.WriteLine("Извлеченный из хвоста элемент = " + popLast + ", deque после извлечения из хвоста = " + string.Join(" ", deque.ToArray())); int? popFirst = deque.PopFirst(); Console.WriteLine("Извлеченный из головы элемент = " + popFirst + ", deque после извлечения из головы = " + string.Join(" ", deque.ToArray())); /* Получение длины двусторонней очереди */ int size = deque.Size(); Console.WriteLine("Длина двусторонней очереди size = " + size); /* Проверка, пуста ли двусторонняя очередь */ bool isEmpty = deque.IsEmpty(); Console.WriteLine("Пуста ли двусторонняя очередь = " + isEmpty); } } ================================================ FILE: ru/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs ================================================ /** * File: linkedlist_queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* Очередь на основе связного списка */ class LinkedListQueue { ListNode? front, rear; // Головной узел front, хвостовой узел rear int queSize = 0; public LinkedListQueue() { front = null; rear = null; } /* Получение длины очереди */ public int Size() { return queSize; } /* Проверка, пуста ли очередь */ public bool IsEmpty() { return Size() == 0; } /* Поместить в очередь */ public void Push(int num) { // Добавить num после хвостового узла ListNode node = new(num); // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел if (front == null) { front = node; rear = node; // Если очередь не пуста, добавить этот узел после хвостового узла } else if (rear != null) { rear.next = node; rear = node; } queSize++; } /* Извлечь из очереди */ public int Pop() { int num = Peek(); // Удалить головной узел front = front?.next; queSize--; return num; } /* Доступ к элементу в начале очереди */ public int Peek() { if (IsEmpty()) throw new Exception(); return front!.val; } /* Преобразовать связный список в Array и вернуть */ public int[] ToArray() { if (front == null) return []; ListNode? node = front; int[] res = new int[Size()]; for (int i = 0; i < res.Length; i++) { res[i] = node!.val; node = node.next; } return res; } } public class linkedlist_queue { [Test] public void Test() { /* Инициализация очереди */ LinkedListQueue queue = new(); /* Добавление элемента в очередь */ queue.Push(1); queue.Push(3); queue.Push(2); queue.Push(5); queue.Push(4); Console.WriteLine("Очередь queue = " + string.Join(",", queue.ToArray())); /* Доступ к элементу в начале очереди */ int peek = queue.Peek(); Console.WriteLine("Первый элемент peek = " + peek); /* Извлечение элемента из очереди */ int pop = queue.Pop(); Console.WriteLine("Извлеченный элемент pop = " + pop + ", queue после извлечения = " + string.Join(",", queue.ToArray())); /* Получение длины очереди */ int size = queue.Size(); Console.WriteLine("Длина очереди size = " + size); /* Проверка, пуста ли очередь */ bool isEmpty = queue.IsEmpty(); Console.WriteLine("Пуста ли очередь = " + isEmpty); } } ================================================ FILE: ru/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs ================================================ /** * File: linkedlist_stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* Стек на основе связного списка */ class LinkedListStack { ListNode? stackPeek; // Использовать головной узел как вершину стека int stkSize = 0; // Длина стека public LinkedListStack() { stackPeek = null; } /* Получение длины стека */ public int Size() { return stkSize; } /* Проверка, пуст ли стек */ public bool IsEmpty() { return Size() == 0; } /* Поместить в стек */ public void Push(int num) { ListNode node = new(num) { next = stackPeek }; stackPeek = node; stkSize++; } /* Извлечь из стека */ public int Pop() { int num = Peek(); stackPeek = stackPeek!.next; stkSize--; return num; } /* Доступ к верхнему элементу стека */ public int Peek() { if (IsEmpty()) throw new Exception(); return stackPeek!.val; } /* Преобразовать List в Array и вернуть */ public int[] ToArray() { if (stackPeek == null) return []; ListNode? node = stackPeek; int[] res = new int[Size()]; for (int i = res.Length - 1; i >= 0; i--) { res[i] = node!.val; node = node.next; } return res; } } public class linkedlist_stack { [Test] public void Test() { /* Инициализация стека */ LinkedListStack stack = new(); /* Помещение элемента в стек */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); Console.WriteLine("Стек stack = " + string.Join(",", stack.ToArray())); /* Доступ к верхнему элементу стека */ int peek = stack.Peek(); Console.WriteLine("Верхний элемент peek = " + peek); /* Извлечение элемента из стека */ int pop = stack.Pop(); Console.WriteLine("Извлеченный элемент pop = " + pop + ", stack после извлечения = " + string.Join(",", stack.ToArray())); /* Получение длины стека */ int size = stack.Size(); Console.WriteLine("Длина стека size = " + size); /* Проверка на пустоту */ bool isEmpty = stack.IsEmpty(); Console.WriteLine("Пуст ли стек = " + isEmpty); } } ================================================ FILE: ru/codes/csharp/chapter_stack_and_queue/queue.cs ================================================ /** * File: queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; public class queue { [Test] public void Test() { /* Инициализация очереди */ Queue queue = new(); /* Добавление элемента в очередь */ queue.Enqueue(1); queue.Enqueue(3); queue.Enqueue(2); queue.Enqueue(5); queue.Enqueue(4); Console.WriteLine("Очередь queue = " + string.Join(",", queue)); /* Доступ к элементу в начале очереди */ int peek = queue.Peek(); Console.WriteLine("Первый элемент peek = " + peek); /* Извлечение элемента из очереди */ int pop = queue.Dequeue(); Console.WriteLine("Извлеченный элемент pop = " + pop + ", queue после извлечения = " + string.Join(",", queue)); /* Получение длины очереди */ int size = queue.Count; Console.WriteLine("Длина очереди size = " + size); /* Проверка, пуста ли очередь */ bool isEmpty = queue.Count == 0; Console.WriteLine("Пуста ли очередь = " + isEmpty); } } ================================================ FILE: ru/codes/csharp/chapter_stack_and_queue/stack.cs ================================================ /** * File: stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; public class stack { [Test] public void Test() { /* Инициализация стека */ Stack stack = new(); /* Помещение элемента в стек */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); // Обратите внимание: stack.ToArray() возвращает последовательность в обратном порядке, то есть индекс 0 соответствует вершине стека Console.WriteLine("Стек stack = " + string.Join(",", stack)); /* Доступ к верхнему элементу стека */ int peek = stack.Peek(); Console.WriteLine("Верхний элемент peek = " + peek); /* Извлечение элемента из стека */ int pop = stack.Pop(); Console.WriteLine("Извлеченный элемент pop = " + pop + ", stack после извлечения = " + string.Join(",", stack)); /* Получение длины стека */ int size = stack.Count; Console.WriteLine("Длина стека size = " + size); /* Проверка на пустоту */ bool isEmpty = stack.Count == 0; Console.WriteLine("Пуст ли стек = " + isEmpty); } } ================================================ FILE: ru/codes/csharp/chapter_tree/array_binary_tree.cs ================================================ /** * File: array_binary_tree.cs * Created Time: 2023-07-20 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_tree; /* Класс двоичного дерева в массивном представлении */ public class ArrayBinaryTree(List arr) { List tree = new(arr); /* Вместимость списка */ public int Size() { return tree.Count; } /* Получить значение узла с индексом i */ public int? Val(int i) { // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию if (i < 0 || i >= Size()) return null; return tree[i]; } /* Получить индекс левого дочернего узла узла с индексом i */ public int Left(int i) { return 2 * i + 1; } /* Получить индекс правого дочернего узла узла с индексом i */ public int Right(int i) { return 2 * i + 2; } /* Получить индекс родительского узла узла с индексом i */ public int Parent(int i) { return (i - 1) / 2; } /* Обход в ширину */ public List LevelOrder() { List res = []; // Непосредственно обходить массив for (int i = 0; i < Size(); i++) { if (Val(i).HasValue) res.Add(Val(i)!.Value); } return res; } /* Обход в глубину */ void DFS(int i, string order, List res) { // Если это пустая позиция, вернуть if (!Val(i).HasValue) return; // Предварительный обход if (order == "pre") res.Add(Val(i)!.Value); DFS(Left(i), order, res); // Симметричный обход if (order == "in") res.Add(Val(i)!.Value); DFS(Right(i), order, res); // Обратный обход if (order == "post") res.Add(Val(i)!.Value); } /* Предварительный обход */ public List PreOrder() { List res = []; DFS(0, "pre", res); return res; } /* Симметричный обход */ public List InOrder() { List res = []; DFS(0, "in", res); return res; } /* Обратный обход */ public List PostOrder() { List res = []; DFS(0, "post", res); return res; } } public class array_binary_tree { [Test] public void Test() { // Инициализировать двоичное дерево // Здесь используется функция, напрямую строящая двоичное дерево из массива List arr = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; TreeNode? root = TreeNode.ListToTree(arr); Console.WriteLine("\nИнициализация двоичного дерева\n"); Console.WriteLine("Массивное представление двоичного дерева:"); Console.WriteLine(arr.PrintList()); Console.WriteLine("Связное представление двоичного дерева:"); PrintUtil.PrintTree(root); // Класс двоичного дерева в массивном представлении ArrayBinaryTree abt = new(arr); // Доступ к узлу int i = 1; int l = abt.Left(i); int r = abt.Right(i); int p = abt.Parent(i); Console.WriteLine("\nТекущий узел: индекс = " + i + " , значение = " + abt.Val(i)); Console.WriteLine("Индекс левого дочернего узла = " + l + " , значение = " + (abt.Val(l).HasValue ? abt.Val(l) : "null")); Console.WriteLine("Индекс правого дочернего узла = " + r + " , значение = " + (abt.Val(r).HasValue ? abt.Val(r) : "null")); Console.WriteLine("Индекс родительского узла = " + p + " , значение = " + (abt.Val(p).HasValue ? abt.Val(p) : "null")); // Обходить дерево List res = abt.LevelOrder(); Console.WriteLine("\nОбход в ширину = " + res.PrintList()); res = abt.PreOrder(); Console.WriteLine("Предварительный обход = " + res.PrintList()); res = abt.InOrder(); Console.WriteLine("Симметричный обход = " + res.PrintList()); res = abt.PostOrder(); Console.WriteLine("Обратный обход = " + res.PrintList()); } } ================================================ FILE: ru/codes/csharp/chapter_tree/avl_tree.cs ================================================ /** * File: avl_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; /* AVL-дерево */ class AVLTree { public TreeNode? root; // Корневой узел /* Получить высоту узла */ int Height(TreeNode? node) { // Высота пустого узла равна -1, высота листового узла равна 0 return node == null ? -1 : node.height; } /* Обновить высоту узла */ void UpdateHeight(TreeNode node) { // Высота узла равна высоте более высокого поддерева + 1 node.height = Math.Max(Height(node.left), Height(node.right)) + 1; } /* Получить коэффициент баланса */ public int BalanceFactor(TreeNode? node) { // Коэффициент баланса пустого узла равен 0 if (node == null) return 0; // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева return Height(node.left) - Height(node.right); } /* Операция правого вращения */ TreeNode? RightRotate(TreeNode? node) { TreeNode? child = node?.left; TreeNode? grandChild = child?.right; // Выполнить правое вращение узла node вокруг child child.right = node; node.left = grandChild; // Обновить высоту узла UpdateHeight(node); UpdateHeight(child); // Вернуть корневой узел поддерева после вращения return child; } /* Операция левого вращения */ TreeNode? LeftRotate(TreeNode? node) { TreeNode? child = node?.right; TreeNode? grandChild = child?.left; // Выполнить левое вращение узла node вокруг child child.left = node; node.right = grandChild; // Обновить высоту узла UpdateHeight(node); UpdateHeight(child); // Вернуть корневой узел поддерева после вращения return child; } /* Выполнить вращение, чтобы снова сбалансировать поддерево */ TreeNode? Rotate(TreeNode? node) { // Получить коэффициент баланса узла node int balanceFactorInt = BalanceFactor(node); // Левосторонне перекошенное дерево if (balanceFactorInt > 1) { if (BalanceFactor(node?.left) >= 0) { // Правое вращение return RightRotate(node); } else { // Сначала левое вращение, затем правое node!.left = LeftRotate(node!.left); return RightRotate(node); } } // Правосторонне перекошенное дерево if (balanceFactorInt < -1) { if (BalanceFactor(node?.right) <= 0) { // Левое вращение return LeftRotate(node); } else { // Сначала правое вращение, затем левое node!.right = RightRotate(node!.right); return LeftRotate(node); } } // Дерево сбалансировано, вращение не требуется, вернуть сразу return node; } /* Вставка узла */ public void Insert(int val) { root = InsertHelper(root, val); } /* Рекурсивная вставка узла (вспомогательный метод) */ TreeNode? InsertHelper(TreeNode? node, int val) { if (node == null) return new TreeNode(val); /* 1. Найти позицию вставки и вставить узел */ if (val < node.val) node.left = InsertHelper(node.left, val); else if (val > node.val) node.right = InsertHelper(node.right, val); else return node; // Повторяющийся узел не вставлять, сразу вернуть UpdateHeight(node); // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = Rotate(node); // Вернуть корневой узел поддерева return node; } /* Удаление узла */ public void Remove(int val) { root = RemoveHelper(root, val); } /* Рекурсивное удаление узла (вспомогательный метод) */ TreeNode? RemoveHelper(TreeNode? node, int val) { if (node == null) return null; /* 1. Найти узел и удалить его */ if (val < node.val) node.left = RemoveHelper(node.left, val); else if (val > node.val) node.right = RemoveHelper(node.right, val); else { if (node.left == null || node.right == null) { TreeNode? child = node.left ?? node.right; // Число дочерних узлов = 0, удалить node и сразу вернуть if (child == null) return null; // Число дочерних узлов = 1, удалить node напрямую else node = child; } else { // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел TreeNode? temp = node.right; while (temp.left != null) { temp = temp.left; } node.right = RemoveHelper(node.right, temp.val!.Value); node.val = temp.val; } } UpdateHeight(node); // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = Rotate(node); // Вернуть корневой узел поддерева return node; } /* Поиск узла */ public TreeNode? Search(int val) { TreeNode? cur = root; // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Целевой узел находится в правом поддереве cur if (cur.val < val) cur = cur.right; // Целевой узел находится в левом поддереве cur else if (cur.val > val) cur = cur.left; // Найти целевой узел и выйти из цикла else break; } // Вернуть целевой узел return cur; } } public class avl_tree { static void TestInsert(AVLTree tree, int val) { tree.Insert(val); Console.WriteLine("\nПосле вставки узла " + val + " AVL-дерево имеет вид"); PrintUtil.PrintTree(tree.root); } static void TestRemove(AVLTree tree, int val) { tree.Remove(val); Console.WriteLine("\nПосле удаления узла " + val + " AVL-дерево имеет вид"); PrintUtil.PrintTree(tree.root); } [Test] public void Test() { /* Инициализация пустого AVL-дерева */ AVLTree avlTree = new(); /* Вставка узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла TestInsert(avlTree, 1); TestInsert(avlTree, 2); TestInsert(avlTree, 3); TestInsert(avlTree, 4); TestInsert(avlTree, 5); TestInsert(avlTree, 8); TestInsert(avlTree, 7); TestInsert(avlTree, 9); TestInsert(avlTree, 10); TestInsert(avlTree, 6); /* Вставка повторяющегося узла */ TestInsert(avlTree, 7); /* Удаление узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла TestRemove(avlTree, 8); // Удаление узла степени 0 TestRemove(avlTree, 5); // Удаление узла степени 1 TestRemove(avlTree, 4); // Удаление узла степени 2 /* Поиск узла */ TreeNode? node = avlTree.Search(7); Console.WriteLine("\nНайденный объект узла = " + node + ", значение узла = " + node?.val); } } ================================================ FILE: ru/codes/csharp/chapter_tree/binary_search_tree.cs ================================================ /** * File: binary_search_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; class BinarySearchTree { TreeNode? root; public BinarySearchTree() { // Инициализировать пустое дерево root = null; } /* Получить корневой узел двоичного дерева */ public TreeNode? GetRoot() { return root; } /* Поиск узла */ public TreeNode? Search(int num) { TreeNode? cur = root; // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Целевой узел находится в правом поддереве cur if (cur.val < num) cur = cur.right; // Целевой узел находится в левом поддереве cur else if (cur.val > num) cur = cur.left; // Найти целевой узел и выйти из цикла else break; } // Вернуть целевой узел return cur; } /* Вставка узла */ public void Insert(int num) { // Если дерево пусто, инициализировать корневой узел if (root == null) { root = new TreeNode(num); return; } TreeNode? cur = root, pre = null; // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Найти повторяющийся узел и сразу вернуть if (cur.val == num) return; pre = cur; // Позиция вставки находится в правом поддереве cur if (cur.val < num) cur = cur.right; // Позиция вставки находится в левом поддереве cur else cur = cur.left; } // Вставка узла TreeNode node = new(num); if (pre != null) { if (pre.val < num) pre.right = node; else pre.left = node; } } /* Удаление узла */ public void Remove(int num) { // Если дерево пусто, сразу вернуть if (root == null) return; TreeNode? cur = root, pre = null; // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Найти узел для удаления и выйти из цикла if (cur.val == num) break; pre = cur; // Узел для удаления находится в правом поддереве cur if (cur.val < num) cur = cur.right; // Узел для удаления находится в левом поддереве cur else cur = cur.left; } // Если узел для удаления отсутствует, сразу вернуть if (cur == null) return; // Число дочерних узлов = 0 или 1 if (cur.left == null || cur.right == null) { // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел TreeNode? child = cur.left ?? cur.right; // Удалить узел cur if (cur != root) { if (pre!.left == cur) pre.left = child; else pre.right = child; } else { // Если удаляемый узел является корнем, заново назначить корневой узел root = child; } } // Число дочерних узлов = 2 else { // Получить следующий узел после cur в симметричном обходе TreeNode? tmp = cur.right; while (tmp.left != null) { tmp = tmp.left; } // Рекурсивно удалить узел tmp Remove(tmp.val!.Value); // Перезаписать cur значением tmp cur.val = tmp.val; } } } public class binary_search_tree { [Test] public void Test() { /* Инициализация двоичного дерева поиска */ BinarySearchTree bst = new(); // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево int[] nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; foreach (int num in nums) { bst.Insert(num); } Console.WriteLine("\nИсходное двоичное дерево\n"); PrintUtil.PrintTree(bst.GetRoot()); /* Поиск узла */ TreeNode? node = bst.Search(7); Console.WriteLine("\nНайденный объект узла = " + node + ", значение узла = " + node?.val); /* Вставка узла */ bst.Insert(16); Console.WriteLine("\nПосле вставки узла 16 двоичное дерево имеет вид\n"); PrintUtil.PrintTree(bst.GetRoot()); /* Удаление узла */ bst.Remove(1); Console.WriteLine("\nПосле удаления узла 1 двоичное дерево имеет вид\n"); PrintUtil.PrintTree(bst.GetRoot()); bst.Remove(2); Console.WriteLine("\nПосле удаления узла 2 двоичное дерево имеет вид\n"); PrintUtil.PrintTree(bst.GetRoot()); bst.Remove(4); Console.WriteLine("\nПосле удаления узла 4 двоичное дерево имеет вид\n"); PrintUtil.PrintTree(bst.GetRoot()); } } ================================================ FILE: ru/codes/csharp/chapter_tree/binary_tree.cs ================================================ /** * File: binary_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree { [Test] public void Test() { /* Инициализация двоичного дерева */ // Инициализация узла TreeNode n1 = new(1); TreeNode n2 = new(2); TreeNode n3 = new(3); TreeNode n4 = new(4); TreeNode n5 = new(5); // Построить связи между узлами (указатели) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; Console.WriteLine("\nИнициализация двоичного дерева\n"); PrintUtil.PrintTree(n1); /* Вставка и удаление узлов */ TreeNode P = new(0); // Вставить узел P между n1 -> n2 n1.left = P; P.left = n2; Console.WriteLine("\nПосле вставки узла P\n"); PrintUtil.PrintTree(n1); // Удалить узел P n1.left = n2; Console.WriteLine("\nПосле удаления узла P\n"); PrintUtil.PrintTree(n1); } } ================================================ FILE: ru/codes/csharp/chapter_tree/binary_tree_bfs.cs ================================================ /** * File: binary_tree_bfs.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree_bfs { /* Обход в ширину */ List LevelOrder(TreeNode root) { // Инициализировать очередь и добавить корневой узел Queue queue = new(); queue.Enqueue(root); // Инициализировать список для хранения последовательности обхода List list = []; while (queue.Count != 0) { TreeNode node = queue.Dequeue(); // Извлечение из очереди list.Add(node.val!.Value); // Сохранить значение узла if (node.left != null) queue.Enqueue(node.left); // Поместить левый дочерний узел в очередь if (node.right != null) queue.Enqueue(node.right); // Поместить правый дочерний узел в очередь } return list; } [Test] public void Test() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); Console.WriteLine("\nИнициализация двоичного дерева\n"); PrintUtil.PrintTree(root); List list = LevelOrder(root!); Console.WriteLine("\nПоследовательность печати узлов при обходе в ширину = " + string.Join(",", list)); } } ================================================ FILE: ru/codes/csharp/chapter_tree/binary_tree_dfs.cs ================================================ /** * File: binary_tree_dfs.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree_dfs { List list = []; /* Предварительный обход */ void PreOrder(TreeNode? root) { if (root == null) return; // Порядок обхода: корень -> левое поддерево -> правое поддерево list.Add(root.val!.Value); PreOrder(root.left); PreOrder(root.right); } /* Симметричный обход */ void InOrder(TreeNode? root) { if (root == null) return; // Порядок обхода: левое поддерево -> корень -> правое поддерево InOrder(root.left); list.Add(root.val!.Value); InOrder(root.right); } /* Обратный обход */ void PostOrder(TreeNode? root) { if (root == null) return; // Порядок обхода: левое поддерево -> правое поддерево -> корень PostOrder(root.left); PostOrder(root.right); list.Add(root.val!.Value); } [Test] public void Test() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); Console.WriteLine("\nИнициализация двоичного дерева\n"); PrintUtil.PrintTree(root); list.Clear(); PreOrder(root); Console.WriteLine("\nПоследовательность печати узлов при предварительном обходе = " + string.Join(",", list)); list.Clear(); InOrder(root); Console.WriteLine("\nПоследовательность печати узлов при симметричном обходе = " + string.Join(",", list)); list.Clear(); PostOrder(root); Console.WriteLine("\nПоследовательность печати узлов при обратном обходе = " + string.Join(",", list)); } } ================================================ FILE: ru/codes/csharp/csharp.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.002.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hello-algo", "hello-algo.csproj", "{48B60439-EFDC-4C8F-AE8D-41979958C8AC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.Build.0 = Debug|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1E773F8A-FF66-4974-820B-FCE9032D19AE} EndGlobalSection EndGlobal ================================================ FILE: ru/codes/csharp/hello-algo.csproj ================================================  Exe net8.0 hello_algo enable enable all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: ru/codes/csharp/utils/ListNode.cs ================================================ // File: ListNode.cs // Created Time: 2022-12-16 // Author: mingXta (1195669834@qq.com) namespace hello_algo.utils; /* Узел связного списка */ public class ListNode(int x) { public int val = x; public ListNode? next; /* Десериализовать массив в связный список */ public static ListNode? ArrToLinkedList(int[] arr) { ListNode dum = new(0); ListNode head = dum; foreach (int val in arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } public override string? ToString() { List list = []; var head = this; while (head != null) { list.Add(head.val.ToString()); head = head.next; } return string.Join("->", list); } } ================================================ FILE: ru/codes/csharp/utils/PrintUtil.cs ================================================ /** * File: PrintUtil.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com), krahets (krahets@163.com) */ namespace hello_algo.utils; public class Trunk(Trunk? prev, string str) { public Trunk? prev = prev; public string str = str; }; public static class PrintUtil { /* Вывести список */ public static void PrintList(IList list) { Console.WriteLine("[" + string.Join(", ", list) + "]"); } public static string PrintList(this IEnumerable list) { return $"[ {string.Join(", ", list.Select(x => x?.ToString() ?? "null"))} ]"; } /* Вывести матрицу (Array) */ public static void PrintMatrix(T[][] matrix) { Console.WriteLine("["); foreach (T[] row in matrix) { Console.WriteLine(" " + string.Join(", ", row) + ","); } Console.WriteLine("]"); } /* Вывести матрицу (List) */ public static void PrintMatrix(List> matrix) { Console.WriteLine("["); foreach (List row in matrix) { Console.WriteLine(" " + string.Join(", ", row) + ","); } Console.WriteLine("]"); } /* Вывести связный список */ public static void PrintLinkedList(ListNode? head) { List list = []; while (head != null) { list.Add(head.val.ToString()); head = head.next; } Console.Write(string.Join(" -> ", list)); } /** * Вывести двоичное дерево * Этот вывод дерева заимствован из TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ public static void PrintTree(TreeNode? root) { PrintTree(root, null, false); } /* Вывести двоичное дерево */ public static void PrintTree(TreeNode? root, Trunk? prev, bool isRight) { if (root == null) { return; } string prev_str = " "; Trunk trunk = new(prev, prev_str); PrintTree(root.right, trunk, true); if (prev == null) { trunk.str = "———"; } else if (isRight) { trunk.str = "/———"; prev_str = " |"; } else { trunk.str = "\\———"; prev.str = prev_str; } ShowTrunks(trunk); Console.WriteLine(" " + root.val); if (prev != null) { prev.str = prev_str; } trunk.str = " |"; PrintTree(root.left, trunk, false); } public static void ShowTrunks(Trunk? p) { if (p == null) { return; } ShowTrunks(p.prev); Console.Write(p.str); } /* Вывести хеш-таблицу */ public static void PrintHashMap(Dictionary map) where K : notnull { foreach (var kv in map.Keys) { Console.WriteLine(kv.ToString() + " -> " + map[kv]?.ToString()); } } /* Вывести кучу */ public static void PrintHeap(Queue queue) { Console.Write("Массивное представление кучи:"); List list = [.. queue]; Console.WriteLine(string.Join(',', list)); Console.WriteLine("Древовидное представление кучи:"); TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); PrintTree(tree); } /* Вывести приоритетную очередь */ public static void PrintHeap(PriorityQueue queue) { var newQueue = new PriorityQueue(queue.UnorderedItems, queue.Comparer); Console.Write("Массивное представление кучи:"); List list = []; while (newQueue.TryDequeue(out int element, out _)) { list.Add(element); } Console.WriteLine("Древовидное представление кучи:"); Console.WriteLine(string.Join(',', list.ToList())); TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); PrintTree(tree); } } ================================================ FILE: ru/codes/csharp/utils/TreeNode.cs ================================================ /** * File: TreeNode.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.utils; /* Класс узла двоичного дерева */ public class TreeNode(int? x) { public int? val = x; // Значение узла public int height; // Высота узла public TreeNode? left; // Ссылка на левый дочерний узел public TreeNode? right; // Ссылка на правый дочерний узел // Правила кодирования сериализации см.: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Массивное представление двоичного дерева: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // Связное представление двоичного дерева: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* Десериализовать список в двоичное дерево: рекурсия */ static TreeNode? ListToTreeDFS(List arr, int i) { if (i < 0 || i >= arr.Count || !arr[i].HasValue) { return null; } TreeNode root = new(arr[i]) { left = ListToTreeDFS(arr, 2 * i + 1), right = ListToTreeDFS(arr, 2 * i + 2) }; return root; } /* Десериализовать список в двоичное дерево */ public static TreeNode? ListToTree(List arr) { return ListToTreeDFS(arr, 0); } /* Сериализовать двоичное дерево в список: рекурсия */ static void TreeToListDFS(TreeNode? root, int i, List res) { if (root == null) return; while (i >= res.Count) { res.Add(null); } res[i] = root.val; TreeToListDFS(root.left, 2 * i + 1, res); TreeToListDFS(root.right, 2 * i + 2, res); } /* Сериализовать двоичное дерево в список */ public static List TreeToList(TreeNode root) { List res = []; TreeToListDFS(root, 0, res); return res; } } ================================================ FILE: ru/codes/csharp/utils/Vertex.cs ================================================ /** * File: Vertex.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com), krahets (krahets@163.com) */ namespace hello_algo.utils; /* Класс вершины */ public class Vertex(int val) { public int val = val; /* На вход подается список значений vals, на выходе возвращается список вершин vets */ public static Vertex[] ValsToVets(int[] vals) { Vertex[] vets = new Vertex[vals.Length]; for (int i = 0; i < vals.Length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* На вход подается список вершин vets, на выходе возвращается список значений vals */ public static List VetsToVals(List vets) { List vals = []; foreach (Vertex vet in vets) { vals.Add(vet.val); } return vals; } } ================================================ FILE: ru/codes/dart/build.dart ================================================ import 'dart:io'; void main() { Directory foldPath = Directory('codes/dart/'); List files = foldPath.listSync(); int totalCount = 0; int errorCount = 0; for (var file in files) { if (file.path.endsWith('build.dart')) continue; if (file is File && file.path.endsWith('.dart')) { totalCount++; try { Process.runSync('dart', [file.path]); } catch (e) { errorCount++; print('Error: $e'); print('File: ${file.path}'); } } else if (file is Directory) { List subFiles = file.listSync(); for (var subFile in subFiles) { if (subFile is File && subFile.path.endsWith('.dart')) { totalCount++; try { Process.runSync('dart', [subFile.path]); } catch (e) { errorCount++; print('Error: $e'); print('File: ${file.path}'); } } } } } print('===== Build Complete ====='); print('Total: $totalCount'); print('Error: $errorCount'); } ================================================ FILE: ru/codes/dart/chapter_array_and_linkedlist/array.dart ================================================ /** * File: array.dart * Created Time: 2023-01-20 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable import 'dart:math'; /* Случайный доступ к элементу */ int randomAccess(List nums) { // Случайным образом выбрать число из интервала [0, nums.length) int randomIndex = Random().nextInt(nums.length); // Получить и вернуть случайный элемент int randomNum = nums[randomIndex]; return randomNum; } /* Увеличить длину массива */ List extend(List nums, int enlarge) { // Инициализировать массив увеличенной длины List res = List.filled(nums.length + enlarge, 0); // Скопировать все элементы исходного массива в новый массив for (var i = 0; i < nums.length; i++) { res[i] = nums[i]; } // Вернуть новый массив после расширения return res; } /* Вставить элемент _num по индексу index в массив */ void insert(List nums, int _num, int index) { // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад for (var i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Присвоить _num элементу по индексу index nums[index] = _num; } /* Удалить элемент по индексу index */ void remove(List nums, int index) { // Сдвинуть все элементы после индекса index на одну позицию вперед for (var i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* Перебрать элементы массива */ void traverse(List nums) { int count = 0; // Обход массива по индексам for (var i = 0; i < nums.length; i++) { count += nums[i]; } // Непосредственно обходить элементы массива for (int _num in nums) { count += _num; } // Перебрать массив методом forEach nums.forEach((_num) { count += _num; }); } /* Найти заданный элемент в массиве */ int find(List nums, int target) { for (var i = 0; i < nums.length; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ void main() { /* Инициализация массива */ var arr = List.filled(5, 0); print('Массив arr = $arr'); List nums = [1, 3, 2, 5, 4]; print('Массив nums = $nums'); /* Случайный доступ */ int randomNum = randomAccess(nums); print('Случайный элемент из nums = $randomNum'); /* Расширение длины */ nums = extend(nums, 3); print('После увеличения длины массива до 8 nums = $nums'); /* Вставка элемента */ insert(nums, 6, 3); print('После вставки числа 6 по индексу 3 nums = $nums'); /* Удаление элемента */ remove(nums, 2); print('После удаления элемента по индексу 2 nums = $nums'); /* Обход массива */ traverse(nums); /* Поиск элемента */ int index = find(nums, 3); print('Поиск элемента 3 в nums: индекс = $index'); } ================================================ FILE: ru/codes/dart/chapter_array_and_linkedlist/linked_list.dart ================================================ /** * File: linked_list.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import '../utils/list_node.dart'; import '../utils/print_util.dart'; /* Вставить узел P после узла n0 в связном списке */ void insert(ListNode n0, ListNode P) { ListNode? n1 = n0.next; P.next = n1; n0.next = P; } /* Удалить первый узел после узла n0 в связном списке */ void remove(ListNode n0) { if (n0.next == null) return; // n0 -> P -> n1 ListNode P = n0.next!; ListNode? n1 = P.next; n0.next = n1; } /* Доступ к узлу связного списка по индексу index */ ListNode? access(ListNode? head, int index) { for (var i = 0; i < index; i++) { if (head == null) return null; head = head.next; } return head; } /* Найти в связном списке первый узел со значением target */ int find(ListNode? head, int target) { int index = 0; while (head != null) { if (head.val == target) { return index; } head = head.next; index++; } return -1; } /* Driver Code */ void main() { // Инициализация связного списка // Инициализация всех узлов ListNode n0 = ListNode(1); ListNode n1 = ListNode(3); ListNode n2 = ListNode(2); ListNode n3 = ListNode(5); ListNode n4 = ListNode(4); // Построить ссылки между узлами n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; print('Исходный связный список'); printLinkedList(n0); /* Вставка узла */ insert(n0, ListNode(0)); print('Связный список после вставки узла'); printLinkedList(n0); /* Удаление узла */ remove(n0); print('Связный список после удаления узла'); printLinkedList(n0); /* Доступ к узлу */ ListNode? node = access(n0, 3); print('Значение узла по индексу 3 в связном списке = ${node!.val}'); /* Поиск узла */ int index = find(n0, 2); print('Индекс узла со значением 2 в связном списке = $index'); } ================================================ FILE: ru/codes/dart/chapter_array_and_linkedlist/list.dart ================================================ /** * File: list.dart * Created Time: 2023-01-24 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable /* Driver Code */ void main() { /* Инициализация списка */ List nums = [1, 3, 2, 5, 4]; print('Список nums = $nums'); /* Доступ к элементу */ int _num = nums[1]; print('Элемент по индексу 1: _num = $_num'); /* Обновление элемента */ nums[1] = 0; print('После обновления элемента по индексу 1 до 0 nums = $nums'); /* Очистить список */ nums.clear(); print('После очистки списка nums = $nums'); /* Добавление элемента в конец */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print('После добавления элементов nums = $nums'); /* Вставка элемента в середину */ nums.insert(3, 6); print('После вставки числа 6 по индексу 3 nums = $nums'); /* Удаление элемента */ nums.removeAt(3); print('После удаления элемента по индексу 3 nums = $nums'); /* Обходить список по индексам */ int count = 0; for (var i = 0; i < nums.length; i++) { count += nums[i]; } /* Непосредственно обходить элементы списка */ count = 0; for (var x in nums) { count += x; } /* Объединить два списка */ List nums1 = [6, 8, 7, 10, 9]; nums.addAll(nums1); print('После конкатенации списка nums1 к nums nums = $nums'); /* Отсортировать список */ nums.sort(); print('После сортировки списка nums = $nums'); } ================================================ FILE: ru/codes/dart/chapter_array_and_linkedlist/my_list.dart ================================================ /** * File: my_list.dart * Created Time: 2023-02-05 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* Класс списка */ class MyList { late List _arr; // Массив (для хранения элементов списка) int _capacity = 10; // Вместимость списка int _size = 0; // Длина списка (текущее число элементов) int _extendRatio = 2; // Коэффициент увеличения списка при каждом расширении /* Конструктор */ MyList() { _arr = List.filled(_capacity, 0); } /* Получить длину списка (текущее число элементов) */ int size() => _size; /* Получить вместимость списка */ int capacity() => _capacity; /* Доступ к элементу */ int get(int index) { if (index >= _size) throw RangeError('индекс выходит за границы'); return _arr[index]; } /* Обновление элемента */ void set(int index, int _num) { if (index >= _size) throw RangeError('индекс выходит за границы'); _arr[index] = _num; } /* Добавление элемента в конец */ void add(int _num) { // При превышении вместимости по числу элементов запускается расширение if (_size == _capacity) extendCapacity(); _arr[_size] = _num; // Обновить число элементов _size++; } /* Вставка элемента в середину */ void insert(int index, int _num) { if (index >= _size) throw RangeError('индекс выходит за границы'); // При превышении вместимости по числу элементов запускается расширение if (_size == _capacity) extendCapacity(); // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад for (var j = _size - 1; j >= index; j--) { _arr[j + 1] = _arr[j]; } _arr[index] = _num; // Обновить число элементов _size++; } /* Удаление элемента */ int remove(int index) { if (index >= _size) throw RangeError('индекс выходит за границы'); int _num = _arr[index]; // Сдвинуть все элементы после индекса index на одну позицию вперед for (var j = index; j < _size - 1; j++) { _arr[j] = _arr[j + 1]; } // Обновить число элементов _size--; // Вернуть удаленный элемент return _num; } /* Расширение списка */ void extendCapacity() { // Создать новый массив длиной в _extendRatio раз больше исходного массива final _newNums = List.filled(_capacity * _extendRatio, 0); // Скопировать исходный массив в новый массив List.copyRange(_newNums, 0, _arr); // Обновить ссылку на _arr _arr = _newNums; // Обновить вместимость списка _capacity = _arr.length; } /* Преобразовать список в массив */ List toArray() { List arr = []; for (var i = 0; i < _size; i++) { arr.add(get(i)); } return arr; } } /* Driver Code */ void main() { /* Инициализация списка */ MyList nums = MyList(); /* Добавление элемента в конец */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print( 'Список nums = ${nums.toArray()}, вместимость = ${nums.capacity()}, длина = ${nums.size()}'); /* Вставка элемента в середину */ nums.insert(3, 6); print('После вставки числа 6 по индексу 3 nums = ${nums.toArray()}'); /* Удаление элемента */ nums.remove(3); print('После удаления элемента по индексу 3 nums = ${nums.toArray()}'); /* Доступ к элементу */ int _num = nums.get(1); print('Элемент по индексу 1: _num = $_num'); /* Обновление элемента */ nums.set(1, 0); print('После обновления элемента по индексу 1 до 0 nums = ${nums.toArray()}'); /* Проверка механизма расширения */ for (var i = 0; i < 10; i++) { // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения nums.add(i); } print( 'Список nums после увеличения вместимости = ${nums.toArray()}, вместимость = ${nums.capacity()}, длина = ${nums.size()}'); } ================================================ FILE: ru/codes/dart/chapter_backtracking/n_queens.dart ================================================ /** * File: n_queens.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Алгоритм бэктрекинга: n ферзей */ void backtrack( int row, int n, List> state, List>> res, List cols, List diags1, List diags2, ) { // Когда все строки уже обработаны, записать решение if (row == n) { List> copyState = []; for (List sRow in state) { copyState.add(List.from(sRow)); } res.add(copyState); return; } // Обойти все столбцы for (int col = 0; col < n; col++) { // Вычислить главную и побочную диагонали, соответствующие этой клетке int diag1 = row - col + n - 1; int diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Попытка: поставить ферзя в эту клетку state[row][col] = "Q"; cols[col] = true; diags1[diag1] = true; diags2[diag2] = true; // Перейти к размещению следующей строки backtrack(row + 1, n, state, res, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state[row][col] = "#"; cols[col] = false; diags1[diag1] = false; diags2[diag2] = false; } } } /* Решить задачу о n ферзях */ List>> nQueens(int n) { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку List> state = List.generate(n, (index) => List.filled(n, "#")); List cols = List.filled(n, false); // Отмечать, есть ли ферзь в столбце List diags1 = List.filled(2 * n - 1, false); // Отмечать наличие ферзя на главной диагонали List diags2 = List.filled(2 * n - 1, false); // Отмечать наличие ферзя на побочной диагонали List>> res = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } /* Driver Code */ void main() { int n = 4; List>> res = nQueens(n); print("Размер входной доски = $n"); print("Количество способов расстановки ферзей: ${res.length}"); for (List> state in res) { print("--------------------"); for (List row in state) { print(row); } } } ================================================ FILE: ru/codes/dart/chapter_backtracking/permutations_i.dart ================================================ /** * File: permutations_i.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Алгоритм бэктрекинга: все перестановки I */ void backtrack( List state, List choices, List selected, List> res, ) { // Когда длина состояния равна числу элементов, записать решение if (state.length == choices.length) { res.add(List.from(state)); return; } // Перебор всех вариантов выбора for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true; state.add(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.removeLast(); } } } /* Все перестановки I */ List> permutationsI(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } /* Driver Code */ void main() { List nums = [1, 2, 3]; List> res = permutationsI(nums); print("Входной массив nums = $nums"); print("Все перестановки res = $res"); } ================================================ FILE: ru/codes/dart/chapter_backtracking/permutations_ii.dart ================================================ /** * File: permutations_ii.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Алгоритм бэктрекинга: все перестановки II */ void backtrack( List state, List choices, List selected, List> res, ) { // Когда длина состояния равна числу элементов, записать решение if (state.length == choices.length) { res.add(List.from(state)); return; } // Перебор всех вариантов выбора Set duplicated = {}; for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && !duplicated.contains(choice)) { // Попытка: сделать выбор и обновить состояние duplicated.add(choice); // Записать значения уже выбранных элементов selected[i] = true; state.add(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.removeLast(); } } } /* Все перестановки II */ List> permutationsII(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } /* Driver Code */ void main() { List nums = [1, 2, 2]; List> res = permutationsII(nums); print("Входной массив nums = $nums"); print("Все перестановки res = $res"); } ================================================ FILE: ru/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart ================================================ /** * File: preorder_traversal_i_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Предварительный обход: пример 1 */ void preOrder(TreeNode? root, List res) { if (root == null) { return; } if (root.val == 7) { // Записать решение res.add(root); } preOrder(root.left, res); preOrder(root.right, res); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\nИнициализация двоичного дерева"); printTree(root); // Предварительный обход List res = []; preOrder(root, res); print("\nВсе узлы со значением 7"); print(List.generate(res.length, (i) => res[i].val)); } ================================================ FILE: ru/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart ================================================ /** * File: preorder_traversal_ii_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Предварительный обход: пример 2 */ void preOrder( TreeNode? root, List path, List> res, ) { if (root == null) { return; } // Попытка path.add(root); if (root.val == 7) { // Записать решение res.add(List.from(path)); } preOrder(root.left, path, res); preOrder(root.right, path, res); // Откат path.removeLast(); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\nИнициализация двоичного дерева"); printTree(root); // Предварительный обход List path = []; List> res = []; preOrder(root, path, res); print("\nВсе пути от корня к узлу 7"); for (List vals in res) { print(List.generate(vals.length, (i) => vals[i].val)); } } ================================================ FILE: ru/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart ================================================ /** * File: preorder_traversal_iii_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Предварительный обход: пример 3 */ void preOrder( TreeNode? root, List path, List> res, ) { if (root == null || root.val == 3) { return; } // Попытка path.add(root); if (root.val == 7) { // Записать решение res.add(List.from(path)); } preOrder(root.left, path, res); preOrder(root.right, path, res); // Откат path.removeLast(); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\nИнициализация двоичного дерева"); printTree(root); // Предварительный обход List path = []; List> res = []; preOrder(root, path, res); print("\nВсе пути от корня к узлу 7"); for (List vals in res) { print(List.generate(vals.length, (i) => vals[i].val)); } } ================================================ FILE: ru/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart ================================================ /** * File: preorder_traversal_iii_template.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Проверить, является ли текущее состояние решением */ bool isSolution(List state) { return state.isNotEmpty && state.last.val == 7; } /* Записать решение */ void recordSolution(List state, List> res) { res.add(List.from(state)); } /* Проверить, допустим ли этот выбор в текущем состоянии */ bool isValid(List state, TreeNode? choice) { return choice != null && choice.val != 3; } /* Обновить состояние */ void makeChoice(List state, TreeNode? choice) { state.add(choice!); } /* Восстановить состояние */ void undoChoice(List state, TreeNode? choice) { state.removeLast(); } /* Алгоритм бэктрекинга: пример 3 */ void backtrack( List state, List choices, List> res, ) { // Проверить, является ли текущее состояние решением if (isSolution(state)) { // Записать решение recordSolution(state, res); } // Перебор всех вариантов выбора for (TreeNode? choice in choices) { // Отсечение: проверить допустимость выбора if (isValid(state, choice)) { // Попытка: сделать выбор и обновить состояние makeChoice(state, choice); // Перейти к следующему выбору backtrack(state, [choice!.left, choice.right], res); // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state, choice); } } } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\nИнициализация двоичного дерева"); printTree(root); // Алгоритм бэктрекинга List> res = []; backtrack([], [root!], res); print("\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3"); for (List path in res) { print(List.from(path.map((e) => e.val))); } } ================================================ FILE: ru/codes/dart/chapter_backtracking/subset_sum_i.dart ================================================ /** * File: subset_sum_i.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Алгоритм бэктрекинга: сумма подмножеств I */ void backtrack( List state, int target, List choices, int start, List> res, ) { // Если сумма подмножества равна target, записать решение if (target == 0) { res.add(List.from(state)); return; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств for (int i = start; i < choices.length; i++) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if (target - choices[i] < 0) { break; } // Попытка: сделать выбор и обновить target и start state.add(choices[i]); // Перейти к следующему выбору backtrack(state, target - choices[i], choices, i, res); // Откат: отменить выбор и восстановить предыдущее состояние state.removeLast(); } } /* Решить задачу суммы подмножеств I */ List> subsetSumI(List nums, int target) { List state = []; // Состояние (подмножество) nums.sort(); // Отсортировать nums int start = 0; // Стартовая вершина обхода List> res = []; // Список результатов (список подмножеств) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ void main() { List nums = [3, 4, 5]; int target = 9; List> res = subsetSumI(nums, target); print("Входной массив nums = $nums, target = $target"); print("Все подмножества с суммой $target: res = $res"); } ================================================ FILE: ru/codes/dart/chapter_backtracking/subset_sum_i_naive.dart ================================================ /** * File: subset_sum_i_naive.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Алгоритм бэктрекинга: сумма подмножеств I */ void backtrack( List state, int target, int total, List choices, List> res, ) { // Если сумма подмножества равна target, записать решение if (total == target) { res.add(List.from(state)); return; } // Перебор всех вариантов выбора for (int i = 0; i < choices.length; i++) { // Отсечение: если сумма подмножества превышает target, пропустить этот выбор if (total + choices[i] > target) { continue; } // Попытка: сделать выбор и обновить элемент и total state.add(choices[i]); // Перейти к следующему выбору backtrack(state, target, total + choices[i], choices, res); // Откат: отменить выбор и восстановить предыдущее состояние state.removeLast(); } } /* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ List> subsetSumINaive(List nums, int target) { List state = []; // Состояние (подмножество) int total = 0; // Сумма элементов List> res = []; // Список результатов (список подмножеств) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ void main() { List nums = [3, 4, 5]; int target = 9; List> res = subsetSumINaive(nums, target); print("Входной массив nums = $nums, target = $target"); print("Все подмножества с суммой $target: res = $res"); print("Обратите внимание: результат этого метода содержит повторяющиеся множества"); } ================================================ FILE: ru/codes/dart/chapter_backtracking/subset_sum_ii.dart ================================================ /** * File: subset_sum_ii.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Алгоритм бэктрекинга: сумма подмножеств II */ void backtrack( List state, int target, List choices, int start, List> res, ) { // Если сумма подмножества равна target, записать решение if (target == 0) { res.add(List.from(state)); return; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента for (int i = start; i < choices.length; i++) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if (target - choices[i] < 0) { break; } // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить if (i > start && choices[i] == choices[i - 1]) { continue; } // Попытка: сделать выбор и обновить target и start state.add(choices[i]); // Перейти к следующему выбору backtrack(state, target - choices[i], choices, i + 1, res); // Откат: отменить выбор и восстановить предыдущее состояние state.removeLast(); } } /* Решить задачу суммы подмножеств II */ List> subsetSumII(List nums, int target) { List state = []; // Состояние (подмножество) nums.sort(); // Отсортировать nums int start = 0; // Стартовая вершина обхода List> res = []; // Список результатов (список подмножеств) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ void main() { List nums = [4, 4, 5]; int target = 9; List> res = subsetSumII(nums, target); print("Входной массив nums = $nums, target = $target"); print("Все подмножества с суммой $target: res = $res"); } ================================================ FILE: ru/codes/dart/chapter_computational_complexity/iteration.dart ================================================ /** * File: iteration.dart * Created Time: 2023-08-27 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Цикл for */ int forLoop(int n) { int res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } /* Цикл while */ int whileLoop(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Обновить условную переменную } return res; } /* Цикл while (двойное обновление) */ int whileLoopII(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i++; i *= 2; } return res; } /* Двойной цикл for */ String nestedForLoop(int n) { String res = ""; // Цикл по i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // Цикл по j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { res += "($i, $j), "; } } return res; } /* Driver Code */ void main() { int n = 5; int res; res = forLoop(n); print("\nРезультат суммирования в цикле for res = $res"); res = whileLoop(n); print("\nРезультат суммирования в цикле while res = $res"); res = whileLoopII(n); print("\nРезультат суммирования в цикле while (двойное обновление) res = $res"); String resStr = nestedForLoop(n); print("\nРезультат двойного цикла for $resStr"); } ================================================ FILE: ru/codes/dart/chapter_computational_complexity/recursion.dart ================================================ /** * File: recursion.dart * Created Time: 2023-08-27 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Рекурсия */ int recur(int n) { // Условие завершения if (n == 1) return 1; // Рекурсия: рекурсивный вызов int res = recur(n - 1); // Возврат: вернуть результат return n + res; } /* Имитация рекурсии итерацией */ int forLoopRecur(int n) { // Использовать явный стек для имитации системного стека вызовов List stack = []; int res = 0; // Рекурсия: рекурсивный вызов for (int i = n; i > 0; i--) { // Имитировать «рекурсию» с помощью операции помещения в стек stack.add(i); } // Возврат: вернуть результат while (!stack.isEmpty) { // Имитировать «возврат» с помощью операции извлечения из стека res += stack.removeLast(); } // res = 1+2+3+...+n return res; } /* Хвостовая рекурсия */ int tailRecur(int n, int res) { // Условие завершения if (n == 0) return res; // Хвостовой рекурсивный вызов return tailRecur(n - 1, res + n); } /* Последовательность Фибоначчи: рекурсия */ int fib(int n) { // Условие завершения: f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // Рекурсивный вызов f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // Вернуть результат f(n) return res; } /* Driver Code */ void main() { int n = 5; int res; res = recur(n); print("\nРезультат суммирования в рекурсивной функции res = $res"); res = tailRecur(n, 0); print("\nРезультат суммирования в хвостовой рекурсии res = $res"); res = forLoopRecur(n); print("\nРезультат суммирования при имитации рекурсии итерацией res = $res"); res = fib(n); print("\nЧлен последовательности Фибоначчи с номером $n = $res"); } ================================================ FILE: ru/codes/dart/chapter_computational_complexity/space_complexity.dart ================================================ /** * File: space_complexity.dart * Created Time: 2023-2-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable import 'dart:collection'; import '../utils/list_node.dart'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Функция */ int function() { // Выполнить некоторые операции return 0; } /* Постоянная сложность */ void constant(int n) { // Константы, переменные и объекты занимают O(1) памяти final int a = 0; int b = 0; List nums = List.filled(10000, 0); ListNode node = ListNode(0); // Переменные в цикле занимают O(1) памяти for (var i = 0; i < n; i++) { int c = 0; } // Функции в цикле занимают O(1) памяти for (var i = 0; i < n; i++) { function(); } } /* Линейная сложность */ void linear(int n) { // Массив длины n занимает O(n) памяти List nums = List.filled(n, 0); // Список длины n занимает O(n) памяти List nodes = []; for (var i = 0; i < n; i++) { nodes.add(ListNode(i)); } // Хеш-таблица длины n занимает O(n) памяти Map map = HashMap(); for (var i = 0; i < n; i++) { map.putIfAbsent(i, () => i.toString()); } } /* Линейная сложность (рекурсивная реализация) */ void linearRecur(int n) { print('Рекурсия n = $n'); if (n == 1) return; linearRecur(n - 1); } /* Квадратичная сложность */ void quadratic(int n) { // Матрица занимает O(n^2) памяти List> numMatrix = List.generate(n, (_) => List.filled(n, 0)); // Двумерный список занимает O(n^2) памяти List> numList = []; for (var i = 0; i < n; i++) { List tmp = []; for (int j = 0; j < n; j++) { tmp.add(0); } numList.add(tmp); } } /* Квадратичная сложность (рекурсивная реализация) */ int quadraticRecur(int n) { if (n <= 0) return 0; List nums = List.filled(n, 0); print('В рекурсии n = $n длина nums = ${nums.length}'); return quadraticRecur(n - 1); } /* Экспоненциальная сложность (построение полного двоичного дерева) */ TreeNode? buildTree(int n) { if (n == 0) return null; TreeNode root = TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ void main() { int n = 5; // Постоянная сложность constant(n); // Линейная сложность linear(n); linearRecur(n); // Квадратичная сложность quadratic(n); quadraticRecur(n); // Экспоненциальная сложность TreeNode? root = buildTree(n); printTree(root); } ================================================ FILE: ru/codes/dart/chapter_computational_complexity/time_complexity.dart ================================================ /** * File: time_complexity.dart * Created Time: 2023-02-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable /* Постоянная сложность */ int constant(int n) { int count = 0; int size = 100000; for (var i = 0; i < size; i++) { count++; } return count; } /* Линейная сложность */ int linear(int n) { int count = 0; for (var i = 0; i < n; i++) { count++; } return count; } /* Линейная сложность (обход массива) */ int arrayTraversal(List nums) { int count = 0; // Число итераций пропорционально длине массива for (var _num in nums) { count++; } return count; } /* Квадратичная сложность */ int quadratic(int n) { int count = 0; // Число итераций квадратично зависит от размера данных n for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* Квадратичная сложность (пузырьковая сортировка) */ int bubbleSort(List nums) { int count = 0; // Счетчик // Внешний цикл: неотсортированный диапазон [0, i] for (var i = nums.length - 1; i > 0; i--) { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (var j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // Обмен элементов включает 3 элементарные операции } } } return count; } /* Экспоненциальная сложность (итеративная реализация) */ int exponential(int n) { int count = 0, base = 1; // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) for (var i = 0; i < n; i++) { for (var j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* Экспоненциальная сложность (рекурсивная реализация) */ int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* Логарифмическая сложность (итеративная реализация) */ int logarithmic(int n) { int count = 0; while (n > 1) { n = n ~/ 2; count++; } return count; } /* Логарифмическая сложность (рекурсивная реализация) */ int logRecur(int n) { if (n <= 1) return 0; return logRecur(n ~/ 2) + 1; } /* Линейно-логарифмическая сложность */ int linearLogRecur(int n) { if (n <= 1) return 1; int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2); for (var i = 0; i < n; i++) { count++; } return count; } /* Факториальная сложность (рекурсивная реализация) */ int factorialRecur(int n) { if (n == 0) return 1; int count = 0; // Из одного получается n for (var i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ void main() { // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях int n = 8; print('Размер входных данных n = $n'); int count = constant(n); print('Число операций константной сложности = $count'); count = linear(n); print('Число операций линейной сложности = $count'); count = arrayTraversal(List.filled(n, 0)); print('Число операций линейной сложности (обход массива) = $count'); count = quadratic(n); print('Число операций квадратичной сложности = $count'); final nums = List.filled(n, 0); for (int i = 0; i < n; i++) { nums[i] = n - i; // [n,n-1,...,2,1] } count = bubbleSort(nums); print('Число операций квадратичной сложности (пузырьковая сортировка) = $count'); count = exponential(n); print('Число операций экспоненциальной сложности (итеративная реализация) = $count'); count = expRecur(n); print('Число операций экспоненциальной сложности (рекурсивная реализация) = $count'); count = logarithmic(n); print('Число операций логарифмической сложности (итеративная реализация) = $count'); count = logRecur(n); print('Число операций логарифмической сложности (рекурсивная реализация) = $count'); count = linearLogRecur(n); print('Число операций линейно-логарифмической сложности (рекурсивная реализация) = $count'); count = factorialRecur(n); print('Число операций факториальной сложности (рекурсивная реализация) = $count'); } ================================================ FILE: ru/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart ================================================ /** * File: worst_best_time_complexity.dart * Created Time: 2023-02-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ List randomNumbers(int n) { final nums = List.filled(n, 0); // Создать массив nums = { 1, 2, 3, ..., n } for (var i = 0; i < n; i++) { nums[i] = i + 1; } // Случайно перемешать элементы массива nums.shuffle(); return nums; } /* Найти индекс числа 1 в массиве nums */ int findOne(List nums) { for (var i = 0; i < nums.length; i++) { // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) if (nums[i] == 1) return i; } return -1; } /* Driver Code */ void main() { for (var i = 0; i < 10; i++) { int n = 100; final nums = randomNumbers(n); int index = findOne(nums); print('\nМассив [1, 2, ..., n] после перемешивания = $nums'); print('Индекс числа 1 = $index'); } } ================================================ FILE: ru/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart ================================================ /** * File: binary_search_recur.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Бинарный поиск: задача f(i, j) */ int dfs(List nums, int target, int i, int j) { // Если интервал пуст, целевой элемент отсутствует, вернуть -1 if (i > j) { return -1; } // Вычислить индекс середины m int m = (i + j) ~/ 2; if (nums[m] < target) { // Рекурсивная подзадача f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // Рекурсивная подзадача f(i, m-1) return dfs(nums, target, i, m - 1); } else { // Целевой элемент найден, вернуть его индекс return m; } } /* Бинарный поиск */ int binarySearch(List nums, int target) { int n = nums.length; // Решить задачу f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ void main() { int target = 6; List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // Бинарный поиск (двусторонне замкнутый интервал) int index = binarySearch(nums, target); print("Индекс целевого элемента 6 = $index"); } ================================================ FILE: ru/codes/dart/chapter_divide_and_conquer/build_tree.dart ================================================ /** * File: build_tree.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Построить двоичное дерево: разделяй и властвуй */ TreeNode? dfs( List preorder, Map inorderMap, int i, int l, int r, ) { // Завершить при пустом диапазоне поддерева if (r - l < 0) { return null; } // Инициализировать корневой узел TreeNode? root = TreeNode(preorder[i]); // Найти m, чтобы разделить левое и правое поддеревья int m = inorderMap[preorder[i]]!; // Подзадача: построить левое поддерево root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // Подзадача: построить правое поддерево root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // Вернуть корневой узел return root; } /* Построить двоичное дерево */ TreeNode? buildTree(List preorder, List inorder) { // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам Map inorderMap = {}; for (int i = 0; i < inorder.length; i++) { inorderMap[inorder[i]] = i; } TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ void main() { List preorder = [3, 9, 2, 1, 7]; List inorder = [9, 3, 1, 2, 7]; print("Предварительный обход = $preorder"); print("Симметричный обход = $inorder"); TreeNode? root = buildTree(preorder, inorder); print("Построенное двоичное дерево:"); printTree(root!); } ================================================ FILE: ru/codes/dart/chapter_divide_and_conquer/hanota.dart ================================================ /** * File: hanota.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Переместить один диск */ void move(List src, List tar) { // Снять диск с вершины src int pan = src.removeLast(); // Положить диск на вершину tar tar.add(pan); } /* Решить задачу Ханойской башни f(i) */ void dfs(int i, List src, List buf, List tar) { // Если в src остался только один диск, сразу переместить его в tar if (i == 1) { move(src, tar); return; } // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar dfs(i - 1, src, tar, buf); // Подзадача f(1): переместить оставшийся один диск из src в tar move(src, tar); // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src dfs(i - 1, buf, src, tar); } /* Решить задачу Ханойской башни */ void solveHanota(List A, List B, List C) { int n = A.length; // Переместить верхние n дисков из A в C с помощью B dfs(n, A, B, C); } /* Driver Code */ void main() { // Хвост списка соответствует вершине столбца List A = [5, 4, 3, 2, 1]; List B = []; List C = []; print("Исходное состояние:"); print("A = $A"); print("B = $B"); print("C = $C"); solveHanota(A, B, C); print("После завершения перемещения дисков:"); print("A = $A"); print("B = $B"); print("C = $C"); } ================================================ FILE: ru/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart ================================================ /** * File: climbing_stairs_backtrack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Бэктрекинг */ void backtrack(List choices, int state, int n, List res) { // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 if (state == n) { res[0]++; } // Перебор всех вариантов выбора for (int choice in choices) { // Отсечение: нельзя выходить за n-ю ступень if (state + choice > n) continue; // Попытка: сделать выбор и обновить состояние backtrack(choices, state + choice, n, res); // Откат } } /* Подъем по лестнице: бэктрекинг */ int climbingStairsBacktrack(int n) { List choices = [1, 2]; // Можно подняться на 1 или 2 ступени int state = 0; // Начать подъем с 0-й ступени List res = []; res.add(0); // Использовать res[0] для хранения числа решений backtrack(choices, state, n, res); return res[0]; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsBacktrack(n); print("Количество способов подняться по лестнице из $n ступеней = $res"); } ================================================ FILE: ru/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart ================================================ /** * File: climbing_stairs_constraint_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Подъем по лестнице с ограничениями: динамическое программирование */ int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // Инициализация таблицы dp для хранения решений подзадач List> dp = List.generate(n + 1, (index) => List.filled(3, 0)); // Начальное состояние: заранее задать решения наименьших подзадач dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // Переход состояний: постепенное решение больших подзадач через меньшие for (int i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsConstraintDP(n); print("Количество способов подняться по лестнице из $n ступеней = $res"); } ================================================ FILE: ru/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart ================================================ /** * File: climbing_stairs_dfs.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Поиск */ int dfs(int i) { // dp[1] и dp[2] уже известны, вернуть их if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1) + dfs(i - 2); return count; } /* Подъем по лестнице: поиск */ int climbingStairsDFS(int n) { return dfs(n); } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDFS(n); print("Количество способов подняться по лестнице из $n ступеней = $res"); } ================================================ FILE: ru/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart ================================================ /** * File: climbing_stairs_dfs_mem.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Поиск с мемоизацией */ int dfs(int i, List mem) { // dp[1] и dp[2] уже известны, вернуть их if (i == 1 || i == 2) return i; // Если запись dp[i] существует, сразу вернуть ее if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1, mem) + dfs(i - 2, mem); // Сохранить dp[i] mem[i] = count; return count; } /* Подъем по лестнице: поиск с мемоизацией */ int climbingStairsDFSMem(int n) { // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи List mem = List.filled(n + 1, -1); return dfs(n, mem); } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDFSMem(n); print("Количество способов подняться по лестнице из $n ступеней = $res"); } ================================================ FILE: ru/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart ================================================ /** * File: climbing_stairs_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Подъем по лестнице: динамическое программирование */ int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; // Инициализация таблицы dp для хранения решений подзадач List dp = List.filled(n + 1, 0); // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = 1; dp[2] = 2; // Переход состояний: постепенное решение больших подзадач через меньшие for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ int climbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDP(n); print("Количество способов подняться по лестнице из $n ступеней = $res"); res = climbingStairsDPComp(n); print("Количество способов подняться по лестнице из $n ступеней = $res"); } ================================================ FILE: ru/codes/dart/chapter_dynamic_programming/coin_change.dart ================================================ /** * File: coin_change.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Размен монет: динамическое программирование */ int coinChangeDP(List coins, int amt) { int n = coins.length; int MAX = amt + 1; // Инициализация таблицы dp List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); // Переход состояний: первая строка и первый столбец for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // Переход состояний: остальные строки и столбцы for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] != MAX ? dp[n][amt] : -1; } /* Размен монет: динамическое программирование с оптимизацией памяти */ int coinChangeDPComp(List coins, int amt) { int n = coins.length; int MAX = amt + 1; // Инициализация таблицы dp List dp = List.filled(amt + 1, MAX); dp[0] = 0; // Переход состояний for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } /* Driver Code */ void main() { List coins = [1, 2, 5]; int amt = 4; // Динамическое программирование int res = coinChangeDP(coins, amt); print("Минимальное число монет для набора целевой суммы = $res"); // Динамическое программирование с оптимизацией памяти res = coinChangeDPComp(coins, amt); print("Минимальное число монет для набора целевой суммы = $res"); } ================================================ FILE: ru/codes/dart/chapter_dynamic_programming/coin_change_ii.dart ================================================ /** * File: coin_change_ii.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Размен монет II: динамическое программирование */ int coinChangeIIDP(List coins, int amt) { int n = coins.length; // Инициализация таблицы dp List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); // Инициализация первого столбца for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // Переход состояний for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a]; } else { // Сумма двух решений: не брать или взять монету i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* Размен монет II: динамическое программирование с оптимизацией памяти */ int coinChangeIIDPComp(List coins, int amt) { int n = coins.length; // Инициализация таблицы dp List dp = List.filled(amt + 1, 0); dp[0] = 1; // Переход состояний for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Сумма двух решений: не брать или взять монету i dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ void main() { List coins = [1, 2, 5]; int amt = 5; // Динамическое программирование int res = coinChangeIIDP(coins, amt); print("Количество комбинаций монет для набора целевой суммы = $res"); // Динамическое программирование с оптимизацией памяти res = coinChangeIIDPComp(coins, amt); print("Количество комбинаций монет для набора целевой суммы = $res"); } ================================================ FILE: ru/codes/dart/chapter_dynamic_programming/edit_distance.dart ================================================ /** * File: edit_distance.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Редакционное расстояние: полный перебор */ int editDistanceDFS(String s, String t, int i, int j) { // Если s и t пусты, вернуть 0 if (i == 0 && j == 0) return 0; // Если s пусто, вернуть длину t if (i == 0) return j; // Если t пусто, вернуть длину s if (j == 0) return i; // Если два символа равны, сразу пропустить их if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 int insert = editDistanceDFS(s, t, i, j - 1); int delete = editDistanceDFS(s, t, i - 1, j); int replace = editDistanceDFS(s, t, i - 1, j - 1); // Вернуть минимальное число шагов редактирования return min(min(insert, delete), replace) + 1; } /* Редакционное расстояние: поиск с мемоизацией */ int editDistanceDFSMem(String s, String t, List> mem, int i, int j) { // Если s и t пусты, вернуть 0 if (i == 0 && j == 0) return 0; // Если s пусто, вернуть длину t if (i == 0) return j; // Если t пусто, вернуть длину s if (j == 0) return i; // Если запись уже есть, сразу вернуть ее if (mem[i][j] != -1) return mem[i][j]; // Если два символа равны, сразу пропустить их if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 int insert = editDistanceDFSMem(s, t, mem, i, j - 1); int delete = editDistanceDFSMem(s, t, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Сохранить и вернуть минимальное число шагов редактирования mem[i][j] = min(min(insert, delete), replace) + 1; return mem[i][j]; } /* Редакционное расстояние: динамическое программирование */ int editDistanceDP(String s, String t) { int n = s.length, m = t.length; List> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0)); // Переход состояний: первая строка и первый столбец for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // Переход состояний: остальные строки и столбцы for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // Если два символа равны, сразу пропустить их dp[i][j] = dp[i - 1][j - 1]; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ int editDistanceDPComp(String s, String t) { int n = s.length, m = t.length; List dp = List.filled(m + 1, 0); // Переход состояний: первая строка for (int j = 1; j <= m; j++) { dp[j] = j; } // Переход состояний: остальные строки for (int i = 1; i <= n; i++) { // Переход состояний: первый столбец int leftup = dp[0]; // Временно сохранить dp[i-1, j-1] dp[0] = i; // Переход состояний: остальные столбцы for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // Если два символа равны, сразу пропустить их dp[j] = leftup; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации } } return dp[m]; } /* Driver Code */ void main() { String s = "bag"; String t = "pack"; int n = s.length, m = t.length; // Полный перебор int res = editDistanceDFS(s, t, n, m); print("Чтобы преобразовать " + s + " в " + t + ", нужно минимум $res шагов"); // Поиск с мемоизацией List> mem = List.generate(n + 1, (_) => List.filled(m + 1, -1)); res = editDistanceDFSMem(s, t, mem, n, m); print("Чтобы преобразовать " + s + " в " + t + ", нужно минимум $res шагов"); // Динамическое программирование res = editDistanceDP(s, t); print("Чтобы преобразовать " + s + " в " + t + ", нужно минимум $res шагов"); // Динамическое программирование с оптимизацией памяти res = editDistanceDPComp(s, t); print("Чтобы преобразовать " + s + " в " + t + ", нужно минимум $res шагов"); } ================================================ FILE: ru/codes/dart/chapter_dynamic_programming/knapsack.dart ================================================ /** * File: knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Рюкзак 0-1: полный перебор */ int knapsackDFS(List wgt, List val, int i, int c) { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i == 0 || c == 0) { return 0; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // Вернуть вариант с большей стоимостью из двух возможных return max(no, yes); } /* Рюкзак 0-1: поиск с мемоизацией */ int knapsackDFSMem( List wgt, List val, List> mem, int i, int c, ) { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i == 0 || c == 0) { return 0; } // Если запись уже есть, вернуть сразу if (mem[i][c] != -1) { return mem[i][c]; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут int no = knapsackDFSMem(wgt, val, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // Сохранить и вернуть вариант с большей стоимостью из двух решений mem[i][c] = max(no, yes); return mem[i][c]; } /* Рюкзак 0-1: динамическое программирование */ int knapsackDP(List wgt, List val, int cap) { int n = wgt.length; // Инициализация таблицы dp List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); // Переход состояний for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } /* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ int knapsackDPComp(List wgt, List val, int cap) { int n = wgt.length; // Инициализация таблицы dp List dp = List.filled(cap + 1, 0); // Переход состояний for (int i = 1; i <= n; i++) { // Обход в обратном порядке for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // Большее из двух решений: не брать или взять предмет i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ void main() { List wgt = [10, 20, 30, 40, 50]; List val = [50, 120, 150, 210, 240]; int cap = 50; int n = wgt.length; // Полный перебор int res = knapsackDFS(wgt, val, n, cap); print("Максимальная стоимость предметов без превышения вместимости рюкзака = $res"); // Поиск с мемоизацией List> mem = List.generate(n + 1, (index) => List.filled(cap + 1, -1)); res = knapsackDFSMem(wgt, val, mem, n, cap); print("Максимальная стоимость предметов без превышения вместимости рюкзака = $res"); // Динамическое программирование res = knapsackDP(wgt, val, cap); print("Максимальная стоимость предметов без превышения вместимости рюкзака = $res"); // Динамическое программирование с оптимизацией памяти res = knapsackDPComp(wgt, val, cap); print("Максимальная стоимость предметов без превышения вместимости рюкзака = $res"); } ================================================ FILE: ru/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart ================================================ /** * File: min_cost_climbing_stairs_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Минимальная стоимость подъема по лестнице: динамическое программирование */ int minCostClimbingStairsDP(List cost) { int n = cost.length - 1; if (n == 1 || n == 2) return cost[n]; // Инициализация таблицы dp для хранения решений подзадач List dp = List.filled(n + 1, 0); // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = cost[1]; dp[2] = cost[2]; // Переход состояний: постепенное решение больших подзадач через меньшие for (int i = 3; i <= n; i++) { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ int minCostClimbingStairsDPComp(List cost) { int n = cost.length - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ void main() { List cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; print("Список стоимостей ступеней = $cost"); int res = minCostClimbingStairsDP(cost); print("Минимальная стоимость подъема по лестнице = $res"); res = minCostClimbingStairsDPComp(cost); print("Минимальная стоимость подъема по лестнице = $res"); } ================================================ FILE: ru/codes/dart/chapter_dynamic_programming/min_path_sum.dart ================================================ /** * File: min_path_sum.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Минимальная сумма пути: полный перебор */ int minPathSumDFS(List> grid, int i, int j) { // Если это верхняя левая ячейка, завершить поиск if (i == 0 && j == 0) { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 || j < 0) { // В Dart тип int — целое число фиксированного диапазона; значения, представляющего «бесконечность», не существует return BigInt.from(2).pow(31).toInt(); } // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) return min(left, up) + grid[i][j]; } /* Минимальная сумма пути: поиск с мемоизацией */ int minPathSumDFSMem(List> grid, List> mem, int i, int j) { // Если это верхняя левая ячейка, завершить поиск if (i == 0 && j == 0) { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 || j < 0) { // В Dart тип int — целое число фиксированного диапазона; значения, представляющего «бесконечность», не существует return BigInt.from(2).pow(31).toInt(); } // Если запись уже есть, вернуть сразу if (mem[i][j] != -1) { return mem[i][j]; } // Минимальная стоимость пути для левой и верхней ячеек int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) mem[i][j] = min(left, up) + grid[i][j]; return mem[i][j]; } /* Минимальная сумма пути: динамическое программирование */ int minPathSumDP(List> grid) { int n = grid.length, m = grid[0].length; // Инициализация таблицы dp List> dp = List.generate(n, (i) => List.filled(m, 0)); dp[0][0] = grid[0][0]; // Переход состояний: первая строка for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // Переход состояний: первый столбец for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // Переход состояний: остальные строки и столбцы for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ int minPathSumDPComp(List> grid) { int n = grid.length, m = grid[0].length; // Инициализация таблицы dp List dp = List.filled(m, 0); dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // Переход состояний: остальные строки for (int i = 1; i < n; i++) { // Переход состояний: первый столбец dp[0] = dp[0] + grid[i][0]; // Переход состояний: остальные столбцы for (int j = 1; j < m; j++) { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ void main() { List> grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; int n = grid.length, m = grid[0].length; // Полный перебор int res = minPathSumDFS(grid, n - 1, m - 1); print("Минимальная сумма пути из левого верхнего угла в правый нижний = $res"); // Поиск с мемоизацией List> mem = List.generate(n, (i) => List.filled(m, -1)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); print("Минимальная сумма пути из левого верхнего угла в правый нижний = $res"); // Динамическое программирование res = minPathSumDP(grid); print("Минимальная сумма пути из левого верхнего угла в правый нижний = $res"); // Динамическое программирование с оптимизацией памяти res = minPathSumDPComp(grid); print("Минимальная сумма пути из левого верхнего угла в правый нижний = $res"); } ================================================ FILE: ru/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart ================================================ /** * File: unbounded_knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Полный рюкзак: динамическое программирование */ int unboundedKnapsackDP(List wgt, List val, int cap) { int n = wgt.length; // Инициализация таблицы dp List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); // Переход состояний for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } /* Полный рюкзак: динамическое программирование с оптимизацией памяти */ int unboundedKnapsackDPComp(List wgt, List val, int cap) { int n = wgt.length; // Инициализация таблицы dp List dp = List.filled(cap + 1, 0); // Переход состояний for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[c] = dp[c]; } else { // Большее из двух решений: не брать или взять предмет i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ void main() { List wgt = [1, 2, 3]; List val = [5, 11, 15]; int cap = 4; // Динамическое программирование int res = unboundedKnapsackDP(wgt, val, cap); print("Максимальная стоимость предметов без превышения вместимости рюкзака = $res"); // Динамическое программирование с оптимизацией памяти int resComp = unboundedKnapsackDPComp(wgt, val, cap); print("Максимальная стоимость предметов без превышения вместимости рюкзака = $resComp"); } ================================================ FILE: ru/codes/dart/chapter_graph/graph_adjacency_list.dart ================================================ /** * File: graph_adjacency_list.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/vertex.dart'; /* Класс неориентированного графа на основе списка смежности */ class GraphAdjList { // Список смежности, где key — вершина, а value — все смежные ей вершины Map> adjList = {}; /* Конструктор */ GraphAdjList(List> edges) { for (List edge in edges) { addVertex(edge[0]); addVertex(edge[1]); addEdge(edge[0], edge[1]); } } /* Получить число вершин */ int size() { return adjList.length; } /* Добавление ребра */ void addEdge(Vertex vet1, Vertex vet2) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) { throw ArgumentError; } // Добавить ребро vet1 - vet2 adjList[vet1]!.add(vet2); adjList[vet2]!.add(vet1); } /* Удаление ребра */ void removeEdge(Vertex vet1, Vertex vet2) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) { throw ArgumentError; } // Удалить ребро vet1 - vet2 adjList[vet1]!.remove(vet2); adjList[vet2]!.remove(vet1); } /* Добавление вершины */ void addVertex(Vertex vet) { if (adjList.containsKey(vet)) return; // Добавить новый список в список смежности adjList[vet] = []; } /* Удаление вершины */ void removeVertex(Vertex vet) { if (!adjList.containsKey(vet)) { throw ArgumentError; } // Удалить из списка смежности список, соответствующий вершине vet adjList.remove(vet); // Обойти списки других вершин и удалить все ребра, содержащие vet adjList.forEach((key, value) { value.remove(vet); }); } /* Вывести список смежности */ void printAdjList() { print("Список смежности ="); adjList.forEach((key, value) { List tmp = []; for (Vertex vertex in value) { tmp.add(vertex.val); } print("${key.val}: $tmp,"); }); } } /* Driver Code */ void main() { /* Инициализация неориентированного графа */ List v = Vertex.valsToVets([1, 3, 2, 5, 4]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ]; GraphAdjList graph = GraphAdjList(edges); print("\nГраф после инициализации"); graph.printAdjList(); /* Добавление ребра */ // Вершины 1 и 2 соответствуют v[0] и v[2] graph.addEdge(v[0], v[2]); print("\nГраф после добавления ребра 1-2"); graph.printAdjList(); /* Удаление ребра */ // Вершины 1 и 3 соответствуют v[0] и v[1] graph.removeEdge(v[0], v[1]); print("\nГраф после удаления ребра 1-3"); graph.printAdjList(); /* Добавление вершины */ Vertex v5 = Vertex(6); graph.addVertex(v5); print("\nГраф после добавления вершины 6"); graph.printAdjList(); /* Удаление вершины */ // Вершина 3 соответствует v[1] graph.removeVertex(v[1]); print("\nГраф после удаления вершины 3"); graph.printAdjList(); } ================================================ FILE: ru/codes/dart/chapter_graph/graph_adjacency_matrix.dart ================================================ /** * File: graph_adjacency_matrix.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* Класс неориентированного графа на основе матрицы смежности */ class GraphAdjMat { List vertices = []; // Элемент вершины: элемент представляет «значение вершины», индекс представляет «индекс вершины» List> adjMat = []; // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» /* Конструктор */ GraphAdjMat(List vertices, List> edges) { this.vertices = []; this.adjMat = []; // Добавление вершины for (int val in vertices) { addVertex(val); } // Добавить ребра // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices for (List e in edges) { addEdge(e[0], e[1]); } } /* Получить число вершин */ int size() { return vertices.length; } /* Добавление вершины */ void addVertex(int val) { int n = size(); // Добавить значение новой вершины в список вершин vertices.add(val); // Добавить строку в матрицу смежности List newRow = List.filled(n, 0, growable: true); adjMat.add(newRow); // Добавить столбец в матрицу смежности for (List row in adjMat) { row.add(0); } } /* Удаление вершины */ void removeVertex(int index) { if (index >= size()) { throw IndexError; } // Удалить вершину с индексом index из списка вершин vertices.removeAt(index); // Удалить строку с индексом index из матрицы смежности adjMat.removeAt(index); // Удалить столбец с индексом index из матрицы смежности for (List row in adjMat) { row.removeAt(index); } } /* Добавление ребра */ // Параметры i и j соответствуют индексам элементов vertices void addEdge(int i, int j) { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw IndexError; } // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) adjMat[i][j] = 1; adjMat[j][i] = 1; } /* Удаление ребра */ // Параметры i и j соответствуют индексам элементов vertices void removeEdge(int i, int j) { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw IndexError; } adjMat[i][j] = 0; adjMat[j][i] = 0; } /* Вывести матрицу смежности */ void printAdjMat() { print("Список вершин = $vertices"); print("Матрица смежности = "); printMatrix(adjMat); } } /* Driver Code */ void main() { /* Инициализация неориентированного графа */ // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices List vertices = [1, 3, 2, 5, 4]; List> edges = [ [0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4], ]; GraphAdjMat graph = GraphAdjMat(vertices, edges); print("\nГраф после инициализации"); graph.printAdjMat(); /* Добавление ребра */ // Индексы вершин 1 и 2 равны 0 и 2 соответственно graph.addEdge(0, 2); print("\nГраф после добавления ребра 1-2"); graph.printAdjMat(); /* Удаление ребра */ // Индексы вершин 1 и 3 равны 0 и 1 соответственно graph.removeEdge(0, 1); print("\nГраф после удаления ребра 1-3"); graph.printAdjMat(); /* Добавление вершины */ graph.addVertex(6); print("\nГраф после добавления вершины 6"); graph.printAdjMat(); /* Удаление вершины */ // Индекс вершины 3 равен 1 graph.removeVertex(1); print("\nГраф после удаления вершины 3"); graph.printAdjMat(); } ================================================ FILE: ru/codes/dart/chapter_graph/graph_bfs.dart ================================================ /** * File: graph_bfs.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; import '../utils/vertex.dart'; import 'graph_adjacency_list.dart'; /* Обход в ширину */ List graphBFS(GraphAdjList graph, Vertex startVet) { // Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины // Последовательность обхода вершин List res = []; // Хеш-множество для хранения уже посещенных вершин Set visited = {}; visited.add(startVet); // Очередь используется для реализации BFS Queue que = Queue(); que.add(startVet); // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (que.isNotEmpty) { Vertex vet = que.removeFirst(); // Извлечь головную вершину из очереди res.add(vet); // Отметить посещенную вершину // Обойти все смежные вершины данной вершины for (Vertex adjVet in graph.adjList[vet]!) { if (visited.contains(adjVet)) { continue; // Пропустить уже посещенную вершину } que.add(adjVet); // Помещать в очередь только непосещенные вершины visited.add(adjVet); // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res; } /* Dirver Code */ void main() { /* Инициализация неориентированного графа */ List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; GraphAdjList graph = GraphAdjList(edges); print("\nГраф после инициализации"); graph.printAdjList(); /* Обход в ширину */ List res = graphBFS(graph, v[0]); print("\nПоследовательность вершин при обходе в ширину (BFS)"); print(Vertex.vetsToVals(res)); } ================================================ FILE: ru/codes/dart/chapter_graph/graph_dfs.dart ================================================ /** * File: graph_dfs.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/vertex.dart'; import 'graph_adjacency_list.dart'; /* Вспомогательная функция обхода в глубину */ void dfs( GraphAdjList graph, Set visited, List res, Vertex vet, ) { res.add(vet); // Отметить посещенную вершину visited.add(vet); // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины for (Vertex adjVet in graph.adjList[vet]!) { if (visited.contains(adjVet)) { continue; // Пропустить уже посещенную вершину } // Рекурсивно обходить смежные вершины dfs(graph, visited, res, adjVet); } } /* Обход в глубину */ List graphDFS(GraphAdjList graph, Vertex startVet) { // Последовательность обхода вершин List res = []; // Хеш-множество для хранения уже посещенных вершин Set visited = {}; dfs(graph, visited, res, startVet); return res; } /* Driver Code */ void main() { /* Инициализация неориентированного графа */ List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; GraphAdjList graph = GraphAdjList(edges); print("\nГраф после инициализации"); graph.printAdjList(); /* Обход в глубину */ List res = graphDFS(graph, v[0]); print("\nПоследовательность вершин при обходе в глубину (DFS)"); print(Vertex.vetsToVals(res)); } ================================================ FILE: ru/codes/dart/chapter_greedy/coin_change_greedy.dart ================================================ /** * File: coin_change_greedy.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Размен монет: жадный алгоритм */ int coinChangeGreedy(List coins, int amt) { // Предположить, что список coins упорядочен int i = coins.length - 1; int count = 0; // Циклически выполнять жадный выбор, пока не останется суммы while (amt > 0) { // Найти монету, которая меньше остатка суммы и наиболее к нему близка while (i > 0 && coins[i] > amt) { i--; } // Выбрать coins[i] amt -= coins[i]; count++; } // Если допустимое решение не найдено, вернуть -1 return amt == 0 ? count : -1; } /* Driver Code */ void main() { // Жадный подход: гарантирует нахождение глобально оптимального решения List coins = [1, 5, 10, 20, 50, 100]; int amt = 186; int res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("Минимальное число монет для набора суммы $amt = $res"); // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("Минимальное число монет для набора суммы $amt = $res"); print("На самом деле минимум равен 3: 20 + 20 + 20"); // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("Минимальное число монет для набора суммы $amt = $res"); print("На самом деле минимум равен 2: 49 + 49"); } ================================================ FILE: ru/codes/dart/chapter_greedy/fractional_knapsack.dart ================================================ /** * File: fractional_knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Предмет */ class Item { int w; // Вес предмета int v; // Стоимость предмета Item(this.w, this.v); } /* Дробный рюкзак: жадный алгоритм */ double fractionalKnapsack(List wgt, List val, int cap) { // Создать список предметов с двумя свойствами: вес и стоимость List items = List.generate(wgt.length, (i) => Item(wgt[i], val[i])); // Отсортировать по удельной стоимости item.v / item.w в порядке убывания items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w)); // Циклический жадный выбор double res = 0; for (Item item in items) { if (item.w <= cap) { // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком res += item.v; cap -= item.w; } else { // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета res += item.v / item.w * cap; // Свободной вместимости больше не осталось, поэтому выйти из цикла break; } } return res; } /* Driver Code */ void main() { List wgt = [10, 20, 30, 40, 50]; List val = [50, 120, 150, 210, 240]; int cap = 50; // Жадный алгоритм double res = fractionalKnapsack(wgt, val, cap); print("Максимальная стоимость предметов без превышения вместимости рюкзака = $res"); } ================================================ FILE: ru/codes/dart/chapter_greedy/max_capacity.dart ================================================ /** * File: max_capacity.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Максимальная вместимость: жадный алгоритм */ int maxCapacity(List ht) { // Инициализировать i и j так, чтобы они располагались по двум концам массива int i = 0, j = ht.length - 1; // Начальная максимальная вместимость равна 0 int res = 0; // Выполнять жадный выбор в цикле, пока две доски не встретятся while (i < j) { // Обновить максимальную вместимость int cap = min(ht[i], ht[j]) * (j - i); res = max(res, cap); // Сдвигать внутрь более короткую сторону if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ void main() { List ht = [3, 8, 5, 2, 7, 7, 3, 4]; // Жадный алгоритм int res = maxCapacity(ht); print("Максимальная вместимость = $res"); } ================================================ FILE: ru/codes/dart/chapter_greedy/max_product_cutting.dart ================================================ /** * File: max_product_cutting.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* Максимальное произведение разрезания: жадный алгоритм */ int maxProductCutting(int n) { // Когда n <= 3, обязательно нужно выделить одну 1 if (n <= 3) { return 1 * (n - 1); } // Жадно выделить множители 3, где a — число троек, а b — остаток int a = n ~/ 3; int b = n % 3; if (b == 1) { // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 return (pow(3, a - 1) * 2 * 2).toInt(); } if (b == 2) { // Если остаток равен 2, ничего не делать return (pow(3, a) * 2).toInt(); } // Если остаток равен 0, ничего не делать return pow(3, a).toInt(); } /* Driver Code */ void main() { int n = 58; // Жадный алгоритм int res = maxProductCutting(n); print("Максимальное произведение после разрезания = $res"); } ================================================ FILE: ru/codes/dart/chapter_hashing/array_hash_map.dart ================================================ /** * File: array_hash_map.dart * Created Time: 2023-03-29 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Пара ключ-значение */ class Pair { int key; String val; Pair(this.key, this.val); } /* Хеш-таблица на основе массива */ class ArrayHashMap { late List _buckets; ArrayHashMap() { // Инициализировать массив, содержащий 100 корзин _buckets = List.filled(100, null); } /* Хеш-функция */ int _hashFunc(int key) { final int index = key % 100; return index; } /* Операция поиска */ String? get(int key) { final int index = _hashFunc(key); final Pair? pair = _buckets[index]; if (pair == null) { return null; } return pair.val; } /* Операция добавления */ void put(int key, String val) { final Pair pair = Pair(key, val); final int index = _hashFunc(key); _buckets[index] = pair; } /* Операция удаления */ void remove(int key) { final int index = _hashFunc(key); _buckets[index] = null; } /* Получить все пары ключ-значение */ List pairSet() { List pairSet = []; for (final Pair? pair in _buckets) { if (pair != null) { pairSet.add(pair); } } return pairSet; } /* Получить все ключи */ List keySet() { List keySet = []; for (final Pair? pair in _buckets) { if (pair != null) { keySet.add(pair.key); } } return keySet; } /* Получить все значения */ List values() { List valueSet = []; for (final Pair? pair in _buckets) { if (pair != null) { valueSet.add(pair.val); } } return valueSet; } /* Вывести хеш-таблицу */ void printHashMap() { for (final Pair kv in pairSet()) { print("${kv.key} -> ${kv.val}"); } } } /* Driver Code */ void main() { /* Инициализация хеш-таблицы */ final ArrayHashMap map = ArrayHashMap(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(12836, "Сяо Ха"); map.put(15937, "Сяо Ло"); map.put(16750, "Сяо Суань"); map.put(13276, "Сяо Фа"); map.put(10583, "Сяо Я"); print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); map.printHashMap(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value String? name = map.get(15937); print("\nДля номера 15937 найдено имя $name"); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(10583); print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); map.printHashMap(); /* Обход хеш-таблицы */ print("\nОтдельный обход пар ключ-значение"); map.pairSet().forEach((kv) => print("${kv.key} -> ${kv.val}")); print("\nОтдельный обход ключей"); map.keySet().forEach((key) => print("$key")); print("\nОтдельный обход значений"); map.values().forEach((val) => print("$val")); } ================================================ FILE: ru/codes/dart/chapter_hashing/built_in_hash.dart ================================================ /** * File: built_in_hash.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../chapter_stack_and_queue/linkedlist_deque.dart'; /* Driver Code */ void main() { int _num = 3; int hashNum = _num.hashCode; print("Хеш-значение целого числа $_num = $hashNum"); bool bol = true; int hashBol = bol.hashCode; print("Хеш-значение булева значения $bol = $hashBol"); double dec = 3.14159; int hashDec = dec.hashCode; print("Хеш-значение десятичного числа $dec = $hashDec"); String str = "Hello Algo"; int hashStr = str.hashCode; print("Хеш-значение строки $str = $hashStr"); List arr = [12836, "Сяо Ха"]; int hashArr = arr.hashCode; print("Хеш-значение массива $arr = $hashArr"); ListNode obj = new ListNode(0); int hashObj = obj.hashCode; print("Хеш-значение объекта узла $obj = $hashObj"); } ================================================ FILE: ru/codes/dart/chapter_hashing/hash_map.dart ================================================ /** * File: hash_map.dart * Created Time: 2023-03-29 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Driver Code */ void main() { /* Инициализация хеш-таблицы */ final Map map = {}; /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map[12836] = "Сяо Ха"; map[15937] = "Сяо Ло"; map[16750] = "Сяо Суань"; map[13276] = "Сяо Фа"; map[10583] = "Сяо Я"; print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); map.forEach((key, value) => print("$key -> $value")); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value final String? name = map[15937]; print("\nДля номера 15937 найдено имя $name"); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(10583); print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); map.forEach((key, value) => print("$key -> $value")); /* Обход хеш-таблицы */ print("\nОтдельный обход пар ключ-значение"); map.forEach((key, value) => print("$key -> $value")); print("\nОтдельный обход ключей"); map.keys.forEach((key) => print(key)); print("\nОтдельный обход значений"); map.forEach((key, value) => print("$value")); map.values.forEach((value) => print(value)); } ================================================ FILE: ru/codes/dart/chapter_hashing/hash_map_chaining.dart ================================================ /** * File: hash_map_chaining.dart * Created Time: 2023-06-24 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'array_hash_map.dart'; /* Хеш-таблица с цепочками */ class HashMapChaining { late int size; // Число пар ключ-значение late int capacity; // Вместимость хеш-таблицы late double loadThres; // Порог коэффициента загрузки для запуска расширения late int extendRatio; // Коэффициент расширения late List> buckets; // Массив корзин /* Конструктор */ HashMapChaining() { size = 0; capacity = 4; loadThres = 2.0 / 3.0; extendRatio = 2; buckets = List.generate(capacity, (_) => []); } /* Хеш-функция */ int hashFunc(int key) { return key % capacity; } /* Коэффициент загрузки */ double loadFactor() { return size / capacity; } /* Операция поиска */ String? get(int key) { int index = hashFunc(key); List bucket = buckets[index]; // Обойти корзину; если найден key, вернуть соответствующее val for (Pair pair in bucket) { if (pair.key == key) { return pair.val; } } // Если key не найден, вернуть null return null; } /* Операция добавления */ void put(int key, String val) { // Когда коэффициент загрузки превышает порог, выполнить расширение if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); List bucket = buckets[index]; // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть for (Pair pair in bucket) { if (pair.key == key) { pair.val = val; return; } } // Если такого key нет, добавить пару ключ-значение в конец Pair pair = Pair(key, val); bucket.add(pair); size++; } /* Операция удаления */ void remove(int key) { int index = hashFunc(key); List bucket = buckets[index]; // Обойти корзину и удалить из нее пару ключ-значение for (Pair pair in bucket) { if (pair.key == key) { bucket.remove(pair); size--; break; } } } /* Расширить хеш-таблицу */ void extend() { // Временно сохранить исходную хеш-таблицу List> bucketsTmp = buckets; // Инициализация новой хеш-таблицы после расширения capacity *= extendRatio; buckets = List.generate(capacity, (_) => []); size = 0; // Перенести пары ключ-значение из исходной хеш-таблицы в новую for (List bucket in bucketsTmp) { for (Pair pair in bucket) { put(pair.key, pair.val); } } } /* Вывести хеш-таблицу */ void printHashMap() { for (List bucket in buckets) { List res = []; for (Pair pair in bucket) { res.add("${pair.key} -> ${pair.val}"); } print(res); } } } /* Driver Code */ void main() { /* Инициализация хеш-таблицы */ HashMapChaining map = HashMapChaining(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(12836, "Сяо Ха"); map.put(15937, "Сяо Ло"); map.put(16750, "Сяо Суань"); map.put(13276, "Сяо Фа"); map.put(10583, "Сяо Я"); print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); map.printHashMap(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value String? name = map.get(13276); print("\nДля номера 13276 найдено имя ${name}"); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(12836); print("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение"); map.printHashMap(); } ================================================ FILE: ru/codes/dart/chapter_hashing/hash_map_open_addressing.dart ================================================ /** * File: hash_map_open_addressing.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'array_hash_map.dart'; /* Хеш-таблица с открытой адресацией */ class HashMapOpenAddressing { late int _size; // Число пар ключ-значение int _capacity = 4; // Вместимость хеш-таблицы double _loadThres = 2.0 / 3.0; // Порог коэффициента загрузки для запуска расширения int _extendRatio = 2; // Коэффициент расширения late List _buckets; // Массив корзин Pair _TOMBSTONE = Pair(-1, "-1"); // Удалить метку /* Конструктор */ HashMapOpenAddressing() { _size = 0; _buckets = List.generate(_capacity, (index) => null); } /* Хеш-функция */ int hashFunc(int key) { return key % _capacity; } /* Коэффициент загрузки */ double loadFactor() { return _size / _capacity; } /* Найти индекс корзины, соответствующий key */ int findBucket(int key) { int index = hashFunc(key); int firstTombstone = -1; // Выполнять линейное пробирование и завершить при встрече с пустой корзиной while (_buckets[index] != null) { // Если встретился key, вернуть соответствующий индекс корзины if (_buckets[index]!.key == key) { // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс if (firstTombstone != -1) { _buckets[firstTombstone] = _buckets[index]; _buckets[index] = _TOMBSTONE; return firstTombstone; // Вернуть индекс корзины после перемещения } return index; // Вернуть индекс корзины } // Записать первую встретившуюся метку удаления if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) { firstTombstone = index; } // Вычислить индекс корзины; при выходе за конец вернуться к началу index = (index + 1) % _capacity; } // Если key не существует, вернуть индекс точки добавления return firstTombstone == -1 ? index : firstTombstone; } /* Операция поиска */ String? get(int key) { // Найти индекс корзины, соответствующий key int index = findBucket(key); // Если пара ключ-значение найдена, вернуть соответствующее val if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { return _buckets[index]!.val; } // Если пары ключ-значение не существует, вернуть null return null; } /* Операция добавления */ void put(int key, String val) { // Когда коэффициент загрузки превышает порог, выполнить расширение if (loadFactor() > _loadThres) { extend(); } // Найти индекс корзины, соответствующий key int index = findBucket(key); // Если пара ключ-значение найдена, перезаписать val и вернуть if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { _buckets[index]!.val = val; return; } // Если пары ключ-значение нет, добавить ее _buckets[index] = new Pair(key, val); _size++; } /* Операция удаления */ void remove(int key) { // Найти индекс корзины, соответствующий key int index = findBucket(key); // Если пара ключ-значение найдена, заменить ее меткой удаления if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { _buckets[index] = _TOMBSTONE; _size--; } } /* Расширить хеш-таблицу */ void extend() { // Временно сохранить исходную хеш-таблицу List bucketsTmp = _buckets; // Инициализация новой хеш-таблицы после расширения _capacity *= _extendRatio; _buckets = List.generate(_capacity, (index) => null); _size = 0; // Перенести пары ключ-значение из исходной хеш-таблицы в новую for (Pair? pair in bucketsTmp) { if (pair != null && pair != _TOMBSTONE) { put(pair.key, pair.val); } } } /* Вывести хеш-таблицу */ void printHashMap() { for (Pair? pair in _buckets) { if (pair == null) { print("null"); } else if (pair == _TOMBSTONE) { print("TOMBSTONE"); } else { print("${pair.key} -> ${pair.val}"); } } } } /* Driver Code */ void main() { /* Инициализация хеш-таблицы */ HashMapOpenAddressing map = HashMapOpenAddressing(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(12836, "Сяо Ха"); map.put(15937, "Сяо Ло"); map.put(16750, "Сяо Суань"); map.put(13276, "Сяо Фа"); map.put(10583, "Сяо Я"); print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); map.printHashMap(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value String? name = map.get(13276); print("\nДля номера 13276 найдено имя $name"); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(16750); print("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение"); map.printHashMap(); } ================================================ FILE: ru/codes/dart/chapter_hashing/simple_hash.dart ================================================ /** * File: simple_hash.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Аддитивное хеширование */ int addHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = (hash + key.codeUnitAt(i)) % MODULUS; } return hash; } /* Мультипликативное хеширование */ int mulHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = (31 * hash + key.codeUnitAt(i)) % MODULUS; } return hash; } /* XOR-хеширование */ int xorHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash ^= key.codeUnitAt(i); } return hash & MODULUS; } /* Хеширование с циклическим сдвигом */ int rotHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS; } return hash; } /* Dirver Code */ void main() { String key = "Hello Algo"; int hash = addHash(key); print("Хеш-сумма сложением = $hash"); hash = mulHash(key); print("Хеш-сумма умножением = $hash"); hash = xorHash(key); print("Хеш-сумма XOR = $hash"); hash = rotHash(key); print("Хеш-сумма с циклическим сдвигом = $hash"); } ================================================ FILE: ru/codes/dart/chapter_heap/my_heap.dart ================================================ /** * File: my_heap.dart * Created Time: 2023-04-09 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* Максимальная куча */ class MaxHeap { late List _maxHeap; /* Конструктор, строящий кучу по входному списку */ MaxHeap(List nums) { // Добавить элементы списка в кучу без изменений _maxHeap = nums; // Выполнить heapify для всех узлов, кроме листовых for (int i = _parent(size() - 1); i >= 0; i--) { siftDown(i); } } /* Получить индекс левого дочернего узла */ int _left(int i) { return 2 * i + 1; } /* Получить индекс правого дочернего узла */ int _right(int i) { return 2 * i + 2; } /* Получить индекс родительского узла */ int _parent(int i) { return (i - 1) ~/ 2; // Округление вниз при делении } /* Поменять элементы местами */ void _swap(int i, int j) { int tmp = _maxHeap[i]; _maxHeap[i] = _maxHeap[j]; _maxHeap[j] = tmp; } /* Получение размера кучи */ int size() { return _maxHeap.length; } /* Проверка, пуста ли куча */ bool isEmpty() { return size() == 0; } /* Доступ к элементу на вершине кучи */ int peek() { return _maxHeap[0]; } /* Добавление элемента в кучу */ void push(int val) { // Добавление узла _maxHeap.add(val); // Просеивание снизу вверх siftUp(size() - 1); } /* Начиная с узла i, выполнить просеивание снизу вверх */ void siftUp(int i) { while (true) { // Получение родительского узла для узла i int p = _parent(i); // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» if (p < 0 || _maxHeap[i] <= _maxHeap[p]) { break; } // Поменять два узла местами _swap(i, p); // Циклическое просеивание вверх i = p; } } /* Извлечение элемента из кучи */ int pop() { // Обработка пустого случая if (isEmpty()) throw Exception('куча пуста'); // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) _swap(0, size() - 1); // Удаление узла int val = _maxHeap.removeLast(); // Просеивание сверху вниз siftDown(0); // Вернуть элемент с вершины кучи return val; } /* Начиная с узла i, выполнить просеивание сверху вниз */ void siftDown(int i) { while (true) { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma int l = _left(i); int r = _right(i); int ma = i; if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l; if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r; // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if (ma == i) break; // Поменять два узла местами _swap(i, ma); // Циклическое просеивание вниз i = ma; } } /* Вывести кучу (двоичное дерево) */ void print() { printHeap(_maxHeap); } } /* Driver Code */ void main() { /* Инициализация максимальной кучи */ MaxHeap maxHeap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); print("\nПосле построения кучи из входного списка"); maxHeap.print(); /* Получение элемента с вершины кучи */ int peek = maxHeap.peek(); print("\nЭлемент на вершине кучи = $peek"); /* Добавление элемента в кучу */ int val = 7; maxHeap.push(val); print("\nПосле добавления элемента $val в кучу"); maxHeap.print(); /* Извлечение элемента с вершины кучи */ peek = maxHeap.pop(); print("\nПосле извлечения элемента вершины кучи $peek"); maxHeap.print(); /* Получение размера кучи */ int size = maxHeap.size(); print("\nКоличество элементов в куче = $size"); /* Проверка, пуста ли куча */ bool isEmpty = maxHeap.isEmpty(); print("\nПуста ли куча: $isEmpty"); } ================================================ FILE: ru/codes/dart/chapter_heap/top_k.dart ================================================ /** * File: top_k.dart * Created Time: 2023-08-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* Найти k наибольших элементов массива с помощью кучи */ MinHeap topKHeap(List nums, int k) { // Инициализировать минимальную кучу, поместив в нее первые k элементов массива MinHeap heap = MinHeap(nums.sublist(0, k)); // Начиная с элемента k+1, поддерживать длину кучи равной k for (int i = k; i < nums.length; i++) { // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу if (nums[i] > heap.peek()) { heap.pop(); heap.push(nums[i]); } } return heap; } /* Driver Code */ void main() { List nums = [1, 7, 6, 3, 2]; int k = 3; MinHeap res = topKHeap(nums, k); print("Наибольшие $k элементов"); res.print(); } /* Минимальная куча */ class MinHeap { late List _minHeap; /* Конструктор, строящий кучу по входному списку */ MinHeap(List nums) { // Добавить элементы списка в кучу без изменений _minHeap = nums; // Выполнить heapify для всех узлов, кроме листовых for (int i = _parent(size() - 1); i >= 0; i--) { siftDown(i); } } /* Вернуть элементы в куче */ List getHeap() { return _minHeap; } /* Получить индекс левого дочернего узла */ int _left(int i) { return 2 * i + 1; } /* Получить индекс правого дочернего узла */ int _right(int i) { return 2 * i + 2; } /* Получить индекс родительского узла */ int _parent(int i) { return (i - 1) ~/ 2; // Округление вниз при делении } /* Поменять элементы местами */ void _swap(int i, int j) { int tmp = _minHeap[i]; _minHeap[i] = _minHeap[j]; _minHeap[j] = tmp; } /* Получение размера кучи */ int size() { return _minHeap.length; } /* Проверка, пуста ли куча */ bool isEmpty() { return size() == 0; } /* Доступ к элементу на вершине кучи */ int peek() { return _minHeap[0]; } /* Добавление элемента в кучу */ void push(int val) { // Добавление узла _minHeap.add(val); // Просеивание снизу вверх siftUp(size() - 1); } /* Начиная с узла i, выполнить просеивание снизу вверх */ void siftUp(int i) { while (true) { // Получение родительского узла для узла i int p = _parent(i); // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» if (p < 0 || _minHeap[i] >= _minHeap[p]) { break; } // Поменять два узла местами _swap(i, p); // Циклическое просеивание вверх i = p; } } /* Извлечение элемента из кучи */ int pop() { // Обработка пустого случая if (isEmpty()) throw Exception('куча пуста'); // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) _swap(0, size() - 1); // Удаление узла int val = _minHeap.removeLast(); // Просеивание сверху вниз siftDown(0); // Вернуть элемент с вершины кучи return val; } /* Начиная с узла i, выполнить просеивание сверху вниз */ void siftDown(int i) { while (true) { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma int l = _left(i); int r = _right(i); int mi = i; if (l < size() && _minHeap[l] < _minHeap[mi]) mi = l; if (r < size() && _minHeap[r] < _minHeap[mi]) mi = r; // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if (mi == i) break; // Поменять два узла местами _swap(i, mi); // Циклическое просеивание вниз i = mi; } } /* Вывести кучу (двоичное дерево) */ void print() { printHeap(_minHeap); } } ================================================ FILE: ru/codes/dart/chapter_searching/binary_search.dart ================================================ /** * File: binary_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* Бинарный поиск (двусторонне замкнутый интервал) */ int binarySearch(List nums, int target) { // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно int i = 0, j = nums.length - 1; // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) while (i <= j) { int m = i + (j - i) ~/ 2; // Вычислить индекс середины m if (nums[m] < target) { // Это означает, что target находится в интервале [m+1, j] i = m + 1; } else if (nums[m] > target) { // Это означает, что target находится в интервале [i, m-1] j = m - 1; } else { // Целевой элемент найден, вернуть его индекс return m; } } // Целевой элемент не найден, вернуть -1 return -1; } /* Бинарный поиск (лево замкнутый, право открытый интервал) */ int binarySearchLCRO(List nums, int target) { // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно int i = 0, j = nums.length; // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) while (i < j) { int m = i + (j - i) ~/ 2; // Вычислить индекс середины m if (nums[m] < target) { // Это означает, что target находится в интервале [m+1, j) i = m + 1; } else if (nums[m] > target) { // Это означает, что target находится в интервале [i, m) j = m; } else { // Целевой элемент найден, вернуть его индекс return m; } } // Целевой элемент не найден, вернуть -1 return -1; } /* Driver Code*/ void main() { int target = 6; final nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* Бинарный поиск (двусторонне замкнутый интервал) */ int index = binarySearch(nums, target); print('Индекс целевого элемента 6 = $index'); /* Бинарный поиск (лево замкнутый, право открытый интервал) */ index = binarySearchLCRO(nums, target); print('Индекс целевого элемента 6 = $index'); } ================================================ FILE: ru/codes/dart/chapter_searching/binary_search_edge.dart ================================================ /** * File: binary_search_edge.dart * Created Time: 2023-08-14 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'binary_search_insertion.dart'; /* Бинарный поиск самого левого target */ int binarySearchLeftEdge(List nums, int target) { // Эквивалентно поиску точки вставки target int i = binarySearchInsertion(nums, target); // target не найден, вернуть -1 if (i == nums.length || nums[i] != target) { return -1; } // Найти target и вернуть индекс i return i; } /* Бинарный поиск самого правого target */ int binarySearchRightEdge(List nums, int target) { // Преобразовать задачу в поиск самого левого target + 1 int i = binarySearchInsertion(nums, target + 1); // j указывает на самый правый target, а i — на первый элемент больше target int j = i - 1; // target не найден, вернуть -1 if (j == -1 || nums[j] != target) { return -1; } // Найти target и вернуть индекс j return j; } /* Driver Code */ void main() { // Массив с повторяющимися элементами List nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; print("\nМассив nums = $nums"); // Бинарный поиск левой и правой границы for (int target in [6, 7]) { int index = binarySearchLeftEdge(nums, target); print("Индекс самого левого элемента $target равен $index"); index = binarySearchRightEdge(nums, target); print("Индекс самого правого элемента $target равен $index"); } } ================================================ FILE: ru/codes/dart/chapter_searching/binary_search_insertion.dart ================================================ /** * File: binary_search_insertion.dart * Created Time: 2023-08-14 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Бинарный поиск точки вставки (без повторяющихся элементов) */ int binarySearchInsertionSimple(List nums, int target) { int i = 0, j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] while (i <= j) { int m = i + (j - i) ~/ 2; // Вычислить индекс середины m if (nums[m] < target) { i = m + 1; // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1; // target находится в интервале [i, m-1] } else { return m; // Найти target и вернуть точку вставки m } } // target не найден, вернуть точку вставки i return i; } /* Бинарный поиск точки вставки (с повторяющимися элементами) */ int binarySearchInsertion(List nums, int target) { int i = 0, j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] while (i <= j) { int m = i + (j - i) ~/ 2; // Вычислить индекс середины m if (nums[m] < target) { i = m + 1; // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1; // target находится в интервале [i, m-1] } else { j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] } } // Вернуть точку вставки i return i; } /* Driver Code */ void main() { // Массив без повторяющихся элементов List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; print("\nМассив nums = $nums"); // Бинарный поиск точки вставки for (int target in [6, 9]) { int index = binarySearchInsertionSimple(nums, target); print("Индекс позиции вставки элемента $target равен $index"); } // Массив с повторяющимися элементами nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; print("\nМассив nums = $nums"); // Бинарный поиск точки вставки for (int target in [2, 6, 20]) { int index = binarySearchInsertion(nums, target); print("Индекс позиции вставки элемента $target равен $index"); } } ================================================ FILE: ru/codes/dart/chapter_searching/hashing_search.dart ================================================ /** * File: hashing_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:collection'; import '../utils/list_node.dart'; /* Хеш-поиск (массив) */ int hashingSearchArray(Map map, int target) { // key хеш-таблицы: целевой элемент, value: индекс // Если такого key нет в хеш-таблице, вернуть -1 if (!map.containsKey(target)) { return -1; } return map[target]!; } /* Хеш-поиск (связный список) */ ListNode? hashingSearchLinkedList(Map map, int target) { // key хеш-таблицы: значение целевого узла, value: объект узла // Если такого key нет в хеш-таблице, вернуть null if (!map.containsKey(target)) { return null; } return map[target]!; } /* Driver Code */ void main() { int target = 3; /* Хеш-поиск (массив) */ List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // Инициализация хеш-таблицы Map map = HashMap(); for (int i = 0; i < nums.length; i++) { map.putIfAbsent(nums[i], () => i); // key: элемент, value: индекс } int index = hashingSearchArray(map, target); print('Индекс целевого элемента 3 = $index'); /* Хеш-поиск (связный список) */ ListNode? head = listToLinkedList(nums); // Инициализация хеш-таблицы Map map1 = HashMap(); while (head != null) { map1.putIfAbsent(head.val, () => head!); // key: значение узла, value: узел head = head.next; } ListNode? node = hashingSearchLinkedList(map1, target); print('Объект узла со значением 3 = $node'); } ================================================ FILE: ru/codes/dart/chapter_searching/linear_search.dart ================================================ /** * File: linear_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import '../utils/list_node.dart'; /* Линейный поиск (массив) */ int linearSearchArray(List nums, int target) { // Обход массива for (int i = 0; i < nums.length; i++) { // Целевой элемент найден, вернуть его индекс if (nums[i] == target) { return i; } } // Целевой элемент не найден, вернуть -1 return -1; } /* Линейный поиск (связный список) */ ListNode? linearSearchList(ListNode? head, int target) { // Обойти связный список while (head != null) { // Найти целевой узел и вернуть его if (head.val == target) return head; head = head.next; } // Целевой элемент не найден, вернуть null return null; } /* Driver Code */ void main() { int target = 3; /* Выполнить линейный поиск в массиве */ List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; int index = linearSearchArray(nums, target); print('Индекс целевого элемента 3 = $index'); /* Выполнить линейный поиск в связном списке */ ListNode? head = listToLinkedList(nums); ListNode? node = linearSearchList(head, target); print('Объект узла со значением 3 = $node'); } ================================================ FILE: ru/codes/dart/chapter_searching/two_sum.dart ================================================ /** * File: two_sum.dart * Created Time: 2023-2-11 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:collection'; /* Способ 1: полный перебор */ List twoSumBruteForce(List nums, int target) { int size = nums.length; // Два вложенных цикла, временная сложность O(n^2) for (var i = 0; i < size - 1; i++) { for (var j = i + 1; j < size; j++) { if (nums[i] + nums[j] == target) return [i, j]; } } return [0]; } /* Способ 2: вспомогательная хеш-таблица */ List twoSumHashTable(List nums, int target) { int size = nums.length; // Вспомогательная хеш-таблица, пространственная сложность O(n) Map dic = HashMap(); // Один цикл, временная сложность O(n) for (var i = 0; i < size; i++) { if (dic.containsKey(target - nums[i])) { return [dic[target - nums[i]]!, i]; } dic.putIfAbsent(nums[i], () => i); } return [0]; } /* Driver Code */ void main() { // ======= Test Case ======= List nums = [2, 7, 11, 15]; int target = 13; // ====== Основной код ====== // Метод 1 List res = twoSumBruteForce(nums, target); print('Результат метода 1 res = $res'); // Метод 2 res = twoSumHashTable(nums, target); print('Результат метода 2 res = $res'); } ================================================ FILE: ru/codes/dart/chapter_sorting/bubble_sort.dart ================================================ /** * File: bubble_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* Пузырьковая сортировка */ void bubbleSort(List nums) { // Внешний цикл: неотсортированный диапазон [0, i] for (int i = nums.length - 1; i > 0; i--) { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* Пузырьковая сортировка (оптимизация флагом) */ void bubbleSortWithFlag(List nums) { // Внешний цикл: неотсортированный диапазон [0, i] for (int i = nums.length - 1; i > 0; i--) { bool flag = false; // Инициализировать флаг // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // Записать обмен элементов } } if (!flag) break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); print("После пузырьковой сортировки nums = $nums"); List nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); print("После пузырьковой сортировки nums1 = $nums1"); } ================================================ FILE: ru/codes/dart/chapter_sorting/bucket_sort.dart ================================================ /** * File: bucket_sort.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* Сортировка корзинами */ void bucketSort(List nums) { // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину int k = nums.length ~/ 2; List> buckets = List.generate(k, (index) => []); // 1. Распределить элементы массива по корзинам for (double _num in nums) { // Входные данные находятся в диапазоне [0, 1), используем _num * k для отображения в диапазон индексов [0, k-1] int i = (_num * k).toInt(); // Добавить _num в корзину bucket_idx buckets[i].add(_num); } // 2. Выполнить сортировку внутри каждой корзины for (List bucket in buckets) { bucket.sort(); } // 3. Обойти корзины и объединить результаты int i = 0; for (List bucket in buckets) { for (double _num in bucket) { nums[i++] = _num; } } } /* Driver Code*/ void main() { // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) final nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); print('После сортировки корзинами nums = $nums'); } ================================================ FILE: ru/codes/dart/chapter_sorting/counting_sort.dart ================================================ /** * File: counting_sort.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:math'; /* Сортировка подсчетом */ // Простая реализация, не подходит для сортировки объектов void countingSortNaive(List nums) { // 1. Найти максимальный элемент массива m int m = 0; for (int _num in nums) { m = max(m, _num); } // 2. Подсчитать число появлений каждой цифры // counter[_num] обозначает число появлений _num List counter = List.filled(m + 1, 0); for (int _num in nums) { counter[_num]++; } // 3. Обойти counter и заполнить исходный массив nums элементами int i = 0; for (int _num = 0; _num < m + 1; _num++) { for (int j = 0; j < counter[_num]; j++, i++) { nums[i] = _num; } } } /* Сортировка подсчетом */ // Полная реализация, позволяет сортировать объекты и является стабильной сортировкой void countingSort(List nums) { // 1. Найти максимальный элемент массива m int m = 0; for (int _num in nums) { m = max(m, _num); } // 2. Подсчитать число появлений каждой цифры // counter[_num] обозначает число появлений _num List counter = List.filled(m + 1, 0); for (int _num in nums) { counter[_num]++; } // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» // То есть counter[_num]-1 — это индекс последнего появления _num в res for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res // Инициализировать массив res для хранения результата int n = nums.length; List res = List.filled(n, 0); for (int i = n - 1; i >= 0; i--) { int _num = nums[i]; res[counter[_num] - 1] = _num; // Поместить _num по соответствующему индексу counter[_num]--; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения _num } // Перезаписать исходный массив nums массивом результата res nums.setAll(0, res); } /* Driver Code*/ void main() { final nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); print('После сортировки подсчетом (объекты не поддерживаются) nums = $nums'); final nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); print('После сортировки подсчетом nums1 = $nums1'); } ================================================ FILE: ru/codes/dart/chapter_sorting/heap_sort.dart ================================================ /** * File: heap_sort.dart * Created Time: 2023-06-01 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ void siftDown(List nums, int n, int i) { while (true) { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; if (l < n && nums[l] > nums[ma]) ma = l; if (r < n && nums[r] > nums[ma]) ma = r; // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if (ma == i) break; // Поменять два узла местами int temp = nums[i]; nums[i] = nums[ma]; nums[ma] = temp; // Циклическое просеивание вниз i = ma; } } /* Сортировка кучей */ void heapSort(List nums) { // Построение кучи: выполнить heapify для всех узлов, кроме листовых for (int i = nums.length ~/ 2 - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // Извлекать максимальный элемент из кучи в течение n-1 итераций for (int i = nums.length - 1; i > 0; i--) { // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) int tmp = nums[0]; nums[0] = nums[i]; nums[i] = tmp; // Начиная с корневого узла, выполнить просеивание сверху вниз siftDown(nums, i, 0); } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; heapSort(nums); print("После сортировки кучей nums = $nums"); } ================================================ FILE: ru/codes/dart/chapter_sorting/insertion_sort.dart ================================================ /** * File: insertion_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* Сортировка вставками */ void insertionSort(List nums) { // Внешний цикл: отсортированный диапазон [0, i-1] for (int i = 1; i < nums.length; i++) { int base = nums[i], j = i - 1; // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // Сдвинуть nums[j] на одну позицию вправо j--; } nums[j + 1] = base; // Поместить base в правильную позицию } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); print("После сортировки вставками nums = $nums"); } ================================================ FILE: ru/codes/dart/chapter_sorting/merge_sort.dart ================================================ /** * File: merge_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* Объединить левый и правый подмассивы */ void merge(List nums, int left, int mid, int right) { // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] // Создать временный массив tmp для хранения результата слияния List tmp = List.filled(right - left + 1, 0); // Инициализировать начальные индексы левого и правого подмассивов int i = left, j = mid + 1, k = 0; // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* Сортировка слиянием */ void mergeSort(List nums, int left, int right) { // Условие завершения if (left >= right) return; // Завершить рекурсию, когда длина подмассива равна 1 // Этап разбиения int mid = left + (right - left) ~/ 2; // Вычислить середину mergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив mergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив // Этап слияния merge(nums, left, mid, right); } /* Driver Code */ void main() { /* Сортировка слиянием */ List nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); print("После сортировки слиянием nums = $nums"); } ================================================ FILE: ru/codes/dart/chapter_sorting/quick_sort.dart ================================================ /** * File: quick_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* Класс быстрой сортировки */ class QuickSort { /* Обмен элементов */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Разбиение с опорными указателями */ static int _partition(List nums, int left, int right) { // Взять nums[left] в качестве опорного элемента int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Идти справа налево в поисках первого элемента меньше опорного while (i < j && nums[i] <= nums[left]) i++; // Идти слева направо в поисках первого элемента больше опорного _swap(nums, i, j); // Поменять эти два элемента местами } _swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } /* Быстрая сортировка */ static void quickSort(List nums, int left, int right) { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) return; // Разбиение с опорными указателями int pivot = _partition(nums, left, right); // Рекурсивно обработать левый и правый подмассивы quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } /* Класс быстрой сортировки (оптимизация медианным опорным элементом) */ class QuickSortMedian { /* Обмен элементов */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Выбрать медиану из трех кандидатов */ static int _medianThree(List nums, int left, int mid, int right) { int l = nums[left], m = nums[mid], r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // m находится между l и r if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l находится между m и r return right; } /* Разбиение с опорными указателями (медиана трех) */ static int _partition(List 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; // Вернуть индекс опорного элемента } /* Быстрая сортировка */ static void quickSort(List nums, int left, int right) { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) return; // Разбиение с опорными указателями int pivot = _partition(nums, left, right); // Рекурсивно обработать левый и правый подмассивы quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } /* Класс быстрой сортировки (оптимизация глубины рекурсии) */ class QuickSortTailCall { /* Обмен элементов */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Разбиение с опорными указателями */ static int _partition(List nums, int left, int right) { // Взять nums[left] в качестве опорного элемента int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Идти справа налево в поисках первого элемента меньше опорного while (i < j && nums[i] <= nums[left]) i++; // Идти слева направо в поисках первого элемента больше опорного _swap(nums, i, j); // Поменять эти два элемента местами } _swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } /* Быстрая сортировка (оптимизация глубины рекурсии) */ static void quickSort(List 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] } } } } /* Driver Code */ void main() { /* Быстрая сортировка */ List nums = [2, 4, 1, 0, 3, 5]; QuickSort.quickSort(nums, 0, nums.length - 1); print("После быстрой сортировки nums = $nums"); /* Быстрая сортировка (оптимизация медианным опорным элементом) */ List nums1 = [2, 4, 1, 0, 3, 5]; QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); print("После быстрой сортировки (оптимизация медианным опорным элементом) nums1 = $nums1"); /* Быстрая сортировка (оптимизация глубины рекурсии) */ List nums2 = [2, 4, 1, 0, 3, 5]; QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); print("После быстрой сортировки (оптимизация глубины рекурсии) nums2 = $nums2"); } ================================================ FILE: ru/codes/dart/chapter_sorting/radix_sort.dart ================================================ /** * File: radix_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* Получить k-й разряд элемента _num, где exp = 10^(k-1) */ int digit(int _num, int exp) { // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени return (_num ~/ exp) % 10; } /* Сортировка подсчетом (сортировка по k-му разряду nums) */ void countingSortDigit(List nums, int exp) { // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 List counter = List.filled(10, 0); int n = nums.length; // Подсчитать число появлений каждой цифры от 0 до 9 for (int i = 0; i < n; i++) { int d = digit(nums[i], exp); // Получить k-й разряд nums[i], обозначив его как d counter[d]++; // Подсчитать число появлений цифры d } // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // Выполняя обратный проход, заполнить res элементами по статистике в корзинах List res = List.filled(n, 0); for (int i = n - 1; i >= 0; i--) { int d = digit(nums[i], exp); int j = counter[d] - 1; // Получить индекс j цифры d в массиве res[j] = nums[i]; // Поместить текущий элемент по индексу j counter[d]--; // Уменьшить количество d на 1 } // Перезаписать исходный массив nums результатом for (int i = 0; i < n; i++) nums[i] = res[i]; } /* Поразрядная сортировка */ void radixSort(List nums) { // Получить максимальный элемент массива, чтобы определить максимальное число разрядов // В dart длина int составляет 64 бита int m = -1 << 63; for (int _num in nums) if (_num > m) m = _num; // Проходить разряды от младшего к старшему for (int exp = 1; exp <= m; exp *= 10) // Выполнить сортировку подсчетом по k-му разряду элементов массива // k = 1 -> exp = 1 // k = 2 -> exp = 10 // то есть exp = 10^(k-1) countingSortDigit(nums, exp); } /* Driver Code */ void main() { // Поразрядная сортировка List nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ]; radixSort(nums); print("После поразрядной сортировки nums = $nums"); } ================================================ FILE: ru/codes/dart/chapter_sorting/selection_sort.dart ================================================ /** * File: selection_sort.dart * Created Time: 2023-06-01 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Сортировка выбором */ void selectionSort(List nums) { int n = nums.length; // Внешний цикл: неотсортированный диапазон [i, n-1] for (int i = 0; i < n - 1; i++) { // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // Записать индекс минимального элемента } // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; selectionSort(nums); print("После сортировки выбором nums = $nums"); } ================================================ FILE: ru/codes/dart/chapter_stack_and_queue/array_deque.dart ================================================ /** * File: array_deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Двусторонняя очередь на основе кольцевого массива */ class ArrayDeque { late List _nums; // Массив для хранения элементов двусторонней очереди late int _front; // Указатель head, указывающий на первый элемент очереди late int _queSize; // Длина двусторонней очереди /* Конструктор */ ArrayDeque(int capacity) { this._nums = List.filled(capacity, 0); this._front = this._queSize = 0; } /* Получить вместимость двусторонней очереди */ int capacity() { return _nums.length; } /* Получение длины двусторонней очереди */ int size() { return _queSize; } /* Проверка, пуста ли двусторонняя очередь */ bool isEmpty() { return _queSize == 0; } /* Вычислить индекс в кольцевом массиве */ int index(int i) { // С помощью операции взятия по модулю соединить начало и конец массива // Когда i выходит за конец массива, он возвращается в начало // Когда i выходит за начало массива, он возвращается в конец return (i + capacity()) % capacity(); } /* Добавление в голову очереди */ void pushFirst(int _num) { if (_queSize == capacity()) { throw Exception("Двусторонняя очередь заполнена"); } // Указатель головы сместить влево на одну позицию // С помощью операции взятия остатка реализовать возврат _front к хвосту после выхода за начало массива _front = index(_front - 1); // Добавить _num в голову очереди _nums[_front] = _num; _queSize++; } /* Добавление в хвост очереди */ void pushLast(int _num) { if (_queSize == capacity()) { throw Exception("Двусторонняя очередь заполнена"); } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 int rear = index(_front + _queSize); // Добавить _num в хвост очереди _nums[rear] = _num; _queSize++; } /* Извлечение из головы очереди */ int popFirst() { int _num = peekFirst(); // Указатель головы сместить вправо на одну позицию _front = index(_front + 1); _queSize--; return _num; } /* Извлечение из хвоста очереди */ int popLast() { int _num = peekLast(); _queSize--; return _num; } /* Доступ к элементу в начале очереди */ int peekFirst() { if (isEmpty()) { throw Exception("двусторонняя очередь пуста"); } return _nums[_front]; } /* Доступ к элементу в конце очереди */ int peekLast() { if (isEmpty()) { throw Exception("двусторонняя очередь пуста"); } // Вычислить индекс хвостового элемента int last = index(_front + _queSize - 1); return _nums[last]; } /* Вернуть массив для вывода */ List toArray() { // Преобразовывать только элементы списка в пределах фактической длины List res = List.filled(_queSize, 0); for (int i = 0, j = _front; i < _queSize; i++, j++) { res[i] = _nums[index(j)]; } return res; } } /* Driver Code */ void main() { /* Инициализация двусторонней очереди */ final ArrayDeque deque = ArrayDeque(10); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); print("Двусторонняя очередь deque = ${deque.toArray()}"); /* Доступ к элементу */ final int peekFirst = deque.peekFirst(); print("Первый элемент peekFirst = $peekFirst"); final int peekLast = deque.peekLast(); print("Последний элемент peekLast = $peekLast"); /* Добавление элемента в очередь */ deque.pushLast(4); print("После добавления элемента 4 в хвост deque = ${deque.toArray()}"); deque.pushFirst(1); print("После добавления элемента 1 в голову deque = ${deque.toArray()}"); /* Извлечение элемента из очереди */ final int popLast = deque.popLast(); print("Извлеченный из хвоста элемент = $popLast, deque после извлечения из хвоста = ${deque.toArray()}"); final int popFirst = deque.popFirst(); print("Извлеченный из головы элемент = $popFirst, deque после извлечения из головы = ${deque.toArray()}"); /* Получение длины двусторонней очереди */ final int size = deque.size(); print("Длина двусторонней очереди size = $size"); /* Проверка, пуста ли двусторонняя очередь */ final bool isEmpty = deque.isEmpty(); print("Пуста ли двусторонняя очередь = $isEmpty"); } ================================================ FILE: ru/codes/dart/chapter_stack_and_queue/array_queue.dart ================================================ /** * File: array_queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Очередь на основе кольцевого массива */ class ArrayQueue { late List _nums; // Массив для хранения элементов очереди late int _front; // Указатель head, указывающий на первый элемент очереди late int _queSize; // Длина очереди ArrayQueue(int capacity) { _nums = List.filled(capacity, 0); _front = _queSize = 0; } /* Получить вместимость очереди */ int capaCity() { return _nums.length; } /* Получение длины очереди */ int size() { return _queSize; } /* Проверка, пуста ли очередь */ bool isEmpty() { return _queSize == 0; } /* Поместить в очередь */ void push(int _num) { if (_queSize == capaCity()) { throw Exception("Очередь заполнена"); } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива int rear = (_front + _queSize) % capaCity(); // Добавить _num в хвост очереди _nums[rear] = _num; _queSize++; } /* Извлечь из очереди */ int pop() { int _num = peek(); // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива _front = (_front + 1) % capaCity(); _queSize--; return _num; } /* Доступ к элементу в начале очереди */ int peek() { if (isEmpty()) { throw Exception("очередь пуста"); } return _nums[_front]; } /* Вернуть Array */ List toArray() { // Преобразовывать только элементы списка в пределах фактической длины final List res = List.filled(_queSize, 0); for (int i = 0, j = _front; i < _queSize; i++, j++) { res[i] = _nums[j % capaCity()]; } return res; } } /* Driver Code */ void main() { /* Инициализация очереди */ final int capacity = 10; final ArrayQueue queue = ArrayQueue(capacity); /* Добавление элемента в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print("Очередь queue = ${queue.toArray()}"); /* Доступ к элементу в начале очереди */ final int peek = queue.peek(); print("Первый элемент peek = $peek"); /* Извлечение элемента из очереди */ final int pop = queue.pop(); print("Извлеченный элемент pop = $pop, queue после извлечения = ${queue.toArray()}"); /* Получить длину очереди */ final int size = queue.size(); print("Длина очереди size = $size"); /* Проверка, пуста ли очередь */ final bool isEmpty = queue.isEmpty(); print("Пуста ли очередь = $isEmpty"); /* Проверка кольцевого массива */ for (int i = 0; i < 10; i++) { queue.push(i); queue.pop(); print("После $i-го раунда операций enqueue и dequeue queue = ${queue.toArray()}"); } } ================================================ FILE: ru/codes/dart/chapter_stack_and_queue/array_stack.dart ================================================ /** * File: array_stack.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Стек на основе массива */ class ArrayStack { late List _stack; ArrayStack() { _stack = []; } /* Получение длины стека */ int size() { return _stack.length; } /* Проверка, пуст ли стек */ bool isEmpty() { return _stack.isEmpty; } /* Поместить в стек */ void push(int _num) { _stack.add(_num); } /* Извлечь из стека */ int pop() { if (isEmpty()) { throw Exception("стек пуст"); } return _stack.removeLast(); } /* Доступ к верхнему элементу стека */ int peek() { if (isEmpty()) { throw Exception("стек пуст"); } return _stack.last; } /* Преобразовать стек в Array и вернуть */ List toArray() => _stack; } /* Driver Code */ void main() { /* Инициализация стека */ final ArrayStack stack = ArrayStack(); /* Помещение элемента в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print("Стек stack = ${stack.toArray()}"); /* Доступ к верхнему элементу стека */ final int peek = stack.peek(); print("Верхний элемент peek = $peek"); /* Извлечение элемента из стека */ final int pop = stack.pop(); print("Извлеченный элемент pop = $pop, stack после извлечения = ${stack.toArray()}"); /* Получение длины стека */ final int size = stack.size(); print("Длина стека size = $size"); /* Проверка на пустоту */ final bool isEmpty = stack.isEmpty(); print("Пуст ли стек = $isEmpty"); } ================================================ FILE: ru/codes/dart/chapter_stack_and_queue/deque.dart ================================================ /** * File: deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; void main() { /* Инициализация двусторонней очереди */ final Queue deque = Queue(); deque.addFirst(3); deque.addLast(2); deque.addLast(5); print("Двусторонняя очередь deque = $deque"); /* Доступ к элементу */ final int peekFirst = deque.first; print("Первый элемент peekFirst = $peekFirst"); final int peekLast = deque.last; print("Последний элемент peekLast = $peekLast"); /* Добавление элемента в очередь */ deque.addLast(4); print("После добавления элемента 4 в хвост deque = $deque"); deque.addFirst(1); print("После добавления элемента 1 в голову deque = $deque"); /* Извлечение элемента из очереди */ final int popLast = deque.removeLast(); print("Извлеченный из хвоста элемент = $popLast, deque после извлечения из хвоста = $deque"); final int popFirst = deque.removeFirst(); print("Извлеченный из головы элемент = $popFirst, deque после извлечения из головы = $deque"); /* Получение длины двусторонней очереди */ final int size = deque.length; print("Длина двусторонней очереди size = $size"); /* Проверка, пуста ли двусторонняя очередь */ final bool isEmpty = deque.isEmpty; print("Пуста ли двусторонняя очередь = $isEmpty"); } ================================================ FILE: ru/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart ================================================ /** * File: linkedlist_deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Узел двусвязного списка */ class ListNode { int val; // Значение узла ListNode? next; // Ссылка на узел-преемник ListNode? prev; // Ссылка на узел-предшественник ListNode(this.val, {this.next, this.prev}); } /* Двусторонняя очередь на основе двусвязного списка */ class LinkedListDeque { late ListNode? _front; // Головной узел _front late ListNode? _rear; // Хвостовой узел _rear int _queSize = 0; // Длина двусторонней очереди LinkedListDeque() { this._front = null; this._rear = null; } /* Получить длину двусторонней очереди */ int size() { return this._queSize; } /* Проверка, пуста ли двусторонняя очередь */ bool isEmpty() { return size() == 0; } /* Операция добавления в очередь */ void push(int _num, bool isFront) { final ListNode node = ListNode(_num); if (isEmpty()) { // Если связный список пуст, пусть _front и _rear оба указывают на node _front = _rear = node; } else if (isFront) { // Операция добавления в голову очереди // Добавить node в начало связного списка _front!.prev = node; node.next = _front; _front = node; // Обновить головной узел } else { // Операция добавления в хвост очереди // Добавить node в конец связного списка _rear!.next = node; node.prev = _rear; _rear = node; // Обновить хвостовой узел } _queSize++; // Обновить длину очереди } /* Добавление в голову очереди */ void pushFirst(int _num) { push(_num, true); } /* Добавление в хвост очереди */ void pushLast(int _num) { push(_num, false); } /* Операция извлечения из очереди */ int? pop(bool isFront) { // Если очередь пуста, сразу вернуть null if (isEmpty()) { return null; } final int val; if (isFront) { // Операция извлечения из головы очереди val = _front!.val; // Временно сохранить значение головного узла // Удалить головной узел ListNode? fNext = _front!.next; if (fNext != null) { fNext.prev = null; _front!.next = null; } _front = fNext; // Обновить головной узел } else { // Операция извлечения из хвоста очереди val = _rear!.val; // Временно сохранить значение хвостового узла // Удалить хвостовой узел ListNode? rPrev = _rear!.prev; if (rPrev != null) { rPrev.next = null; _rear!.prev = null; } _rear = rPrev; // Обновить хвостовой узел } _queSize--; // Обновить длину очереди return val; } /* Извлечение из головы очереди */ int? popFirst() { return pop(true); } /* Извлечение из хвоста очереди */ int? popLast() { return pop(false); } /* Доступ к элементу в начале очереди */ int? peekFirst() { return _front?.val; } /* Доступ к элементу в конце очереди */ int? peekLast() { return _rear?.val; } /* Вернуть массив для вывода */ List toArray() { ListNode? node = _front; final List res = []; for (int i = 0; i < _queSize; i++) { res.add(node!.val); node = node.next; } return res; } } /* Driver Code */ void main() { /* Инициализация двусторонней очереди */ final LinkedListDeque deque = LinkedListDeque(); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); print("Двусторонняя очередь deque = ${deque.toArray()}"); /* Доступ к элементу */ int? peekFirst = deque.peekFirst(); print("Первый элемент peekFirst = $peekFirst"); int? peekLast = deque.peekLast(); print("Последний элемент peekLast = $peekLast"); /* Добавление элемента в очередь */ deque.pushLast(4); print("После добавления элемента 4 в хвост deque = ${deque.toArray()}"); deque.pushFirst(1); print("После добавления элемента 1 в голову deque = ${deque.toArray()}"); /* Извлечение элемента из очереди */ int? popLast = deque.popLast(); print("Извлеченный из хвоста элемент = $popLast, deque после извлечения из хвоста = ${deque.toArray()}"); int? popFirst = deque.popFirst(); print("Извлеченный из головы элемент = $popFirst, deque после извлечения из головы = ${deque.toArray()}"); /* Получение длины двусторонней очереди */ int size = deque.size(); print("Длина двусторонней очереди size = $size"); /* Проверка, пуста ли двусторонняя очередь */ bool isEmpty = deque.isEmpty(); print("Пуста ли двусторонняя очередь = $isEmpty"); } ================================================ FILE: ru/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart ================================================ /** * File: linkedlist_queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/list_node.dart'; /* Очередь на основе связного списка */ class LinkedListQueue { ListNode? _front; // Головной узел _front ListNode? _rear; // Хвостовой узел _rear int _queSize = 0; // Длина очереди LinkedListQueue() { _front = null; _rear = null; } /* Получение длины очереди */ int size() { return _queSize; } /* Проверка, пуста ли очередь */ bool isEmpty() { return _queSize == 0; } /* Поместить в очередь */ void push(int _num) { // Добавить _num после хвостового узла final node = ListNode(_num); // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел if (_front == null) { _front = node; _rear = node; } else { // Если очередь не пуста, добавить этот узел после хвостового узла _rear!.next = node; _rear = node; } _queSize++; } /* Извлечь из очереди */ int pop() { final int _num = peek(); // Удалить головной узел _front = _front!.next; _queSize--; return _num; } /* Доступ к элементу в начале очереди */ int peek() { if (_queSize == 0) { throw Exception('очередь пуста'); } return _front!.val; } /* Преобразовать связный список в Array и вернуть */ List toArray() { ListNode? node = _front; final List queue = []; while (node != null) { queue.add(node.val); node = node.next; } return queue; } } /* Driver Code */ void main() { /* Инициализация очереди */ final queue = LinkedListQueue(); /* Добавление элемента в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print("Очередь queue = ${queue.toArray()}"); /* Доступ к элементу в начале очереди */ final int peek = queue.peek(); print("Первый элемент peek = $peek"); /* Извлечение элемента из очереди */ final int pop = queue.pop(); print("Извлеченный элемент pop = $pop, queue после извлечения = ${queue.toArray()}"); /* Получение длины очереди */ final int size = queue.size(); print("Длина очереди size = $size"); /* Проверка, пуста ли очередь */ final bool isEmpty = queue.isEmpty(); print("Пуста ли очередь = $isEmpty"); } ================================================ FILE: ru/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart ================================================ /** * File: linkedlist_stack.dart * Created Time: 2023-03-27 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/list_node.dart'; /* Стек на основе класса связного списка */ class LinkedListStack { ListNode? _stackPeek; // Использовать головной узел как вершину стека int _stkSize = 0; // Длина стека LinkedListStack() { _stackPeek = null; } /* Получение длины стека */ int size() { return _stkSize; } /* Проверка, пуст ли стек */ bool isEmpty() { return _stkSize == 0; } /* Поместить в стек */ void push(int _num) { final ListNode node = ListNode(_num); node.next = _stackPeek; _stackPeek = node; _stkSize++; } /* Извлечь из стека */ int pop() { final int _num = peek(); _stackPeek = _stackPeek!.next; _stkSize--; return _num; } /* Доступ к верхнему элементу стека */ int peek() { if (_stackPeek == null) { throw Exception("стек пуст"); } return _stackPeek!.val; } /* Преобразовать связный список в List и вернуть */ List toList() { ListNode? node = _stackPeek; List list = []; while (node != null) { list.add(node.val); node = node.next; } list = list.reversed.toList(); return list; } } /* Driver Code */ void main() { /* Инициализация стека */ final LinkedListStack stack = LinkedListStack(); /* Помещение элемента в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print("Стек stack = ${stack.toList()}"); /* Доступ к верхнему элементу стека */ final int peek = stack.peek(); print("Верхний элемент peek = $peek"); /* Извлечение элемента из стека */ final int pop = stack.pop(); print("Извлеченный элемент pop = $pop, stack после извлечения = ${stack.toList()}"); /* Получение длины стека */ final int size = stack.size(); print("Длина стека size = $size"); /* Проверка на пустоту */ final bool isEmpty = stack.isEmpty(); print("Пуст ли стек = $isEmpty"); } ================================================ FILE: ru/codes/dart/chapter_stack_and_queue/queue.dart ================================================ /** * File: queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; void main() { /* Инициализация очереди */ // В Dart двусторонняя очередь Queue обычно рассматривается как обычная очередь final Queue queue = Queue(); /* Добавление элемента в очередь */ queue.add(1); queue.add(3); queue.add(2); queue.add(5); queue.add(4); print("Очередь queue = $queue"); /* Доступ к элементу в начале очереди */ final int peek = queue.first; print("Первый элемент peek = $peek"); /* Извлечение элемента из очереди */ final int pop = queue.removeFirst(); print("Извлеченный элемент pop = $pop, queue после извлечения = $queue"); /* Получить длину очереди */ final int size = queue.length; print("Длина очереди size = $size"); /* Проверка, пуста ли очередь */ final bool isEmpty = queue.isEmpty; print("Пуста ли очередь = $isEmpty"); } ================================================ FILE: ru/codes/dart/chapter_stack_and_queue/stack.dart ================================================ /** * File: stack.dart * Created Time: 2023-03-27 * Author: liuyuxin (gvenusleo@gmail.com) */ void main() { /* Инициализация стека */ // В Dart нет встроенного класса стека, поэтому List можно использовать как стек final List stack = []; /* Помещение элемента в стек */ stack.add(1); stack.add(3); stack.add(2); stack.add(5); stack.add(4); print("Стек stack = $stack"); /* Доступ к верхнему элементу стека */ final int peek = stack.last; print("Верхний элемент peek = $peek"); /* Извлечение элемента из стека */ final int pop = stack.removeLast(); print("Извлеченный элемент pop = $pop, stack после извлечения = $stack"); /* Получение длины стека */ final int size = stack.length; print("Длина стека size = $size"); /* Проверка на пустоту */ final bool isEmpty = stack.isEmpty; print("Пуст ли стек = $isEmpty"); } ================================================ FILE: ru/codes/dart/chapter_tree/array_binary_tree.dart ================================================ /** * File: array_binary_tree.dart * Created Time: 2023-08-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Класс двоичного дерева в массивном представлении */ class ArrayBinaryTree { late List _tree; /* Конструктор */ ArrayBinaryTree(this._tree); /* Вместимость списка */ int size() { return _tree.length; } /* Получить значение узла с индексом i */ int? val(int i) { // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию if (i < 0 || i >= size()) { return null; } return _tree[i]; } /* Получить индекс левого дочернего узла узла с индексом i */ int? left(int i) { return 2 * i + 1; } /* Получить индекс правого дочернего узла узла с индексом i */ int? right(int i) { return 2 * i + 2; } /* Получить индекс родительского узла узла с индексом i */ int? parent(int i) { return (i - 1) ~/ 2; } /* Обход в ширину */ List levelOrder() { List res = []; for (int i = 0; i < size(); i++) { if (val(i) != null) { res.add(val(i)!); } } return res; } /* Обход в глубину */ void dfs(int i, String order, List res) { // Если это пустая позиция, вернуть if (val(i) == null) { return; } // Предварительный обход if (order == 'pre') { res.add(val(i)); } dfs(left(i)!, order, res); // Симметричный обход if (order == 'in') { res.add(val(i)); } dfs(right(i)!, order, res); // Обратный обход if (order == 'post') { res.add(val(i)); } } /* Предварительный обход */ List preOrder() { List res = []; dfs(0, 'pre', res); return res; } /* Симметричный обход */ List inOrder() { List res = []; dfs(0, 'in', res); return res; } /* Обратный обход */ List postOrder() { List res = []; dfs(0, 'post', res); return res; } } /* Driver Code */ void main() { // Инициализировать двоичное дерево // Здесь используется функция, напрямую строящая двоичное дерево из массива List arr = [ 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ]; TreeNode? root = listToTree(arr); print("\nИнициализация двоичного дерева\n"); print("Массивное представление двоичного дерева:"); print(arr); print("Связное представление двоичного дерева:"); printTree(root); // Класс двоичного дерева в массивном представлении ArrayBinaryTree abt = ArrayBinaryTree(arr); // Доступ к узлу int i = 1; int? l = abt.left(i); int? r = abt.right(i); int? p = abt.parent(i); print("\nТекущий узел: индекс = $i, значение = ${abt.val(i)}"); print("Индекс левого дочернего узла = $l, значение = ${(l == null ? "null" : abt.val(l))}"); print("Индекс правого дочернего узла = $r, значение = ${(r == null ? "null" : abt.val(r))}"); print("Индекс родительского узла = $p, значение = ${(p == null ? "null" : abt.val(p))}"); // Обходить дерево List res = abt.levelOrder(); print("\nОбход в ширину = $res"); res = abt.preOrder(); print("Предварительный обход = $res"); res = abt.inOrder(); print("Симметричный обход = $res"); res = abt.postOrder(); print("Обратный обход = $res"); } ================================================ FILE: ru/codes/dart/chapter_tree/avl_tree.dart ================================================ /** * File: avl_tree.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; class AVLTree { TreeNode? root; /* Конструктор */ AVLTree() { root = null; } /* Получить высоту узла */ int height(TreeNode? node) { // Высота пустого узла равна -1, высота листового узла равна 0 return node == null ? -1 : node.height; } /* Обновить высоту узла */ void updateHeight(TreeNode? node) { // Высота узла равна высоте более высокого поддерева + 1 node!.height = max(height(node.left), height(node.right)) + 1; } /* Получить коэффициент баланса */ int balanceFactor(TreeNode? node) { // Коэффициент баланса пустого узла равен 0 if (node == null) return 0; // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева return height(node.left) - height(node.right); } /* Операция правого вращения */ TreeNode? rightRotate(TreeNode? node) { TreeNode? child = node!.left; TreeNode? grandChild = child!.right; // Выполнить правое вращение узла node вокруг child child.right = node; node.left = grandChild; // Обновить высоту узла updateHeight(node); updateHeight(child); // Вернуть корневой узел поддерева после вращения return child; } /* Операция левого вращения */ TreeNode? leftRotate(TreeNode? node) { TreeNode? child = node!.right; TreeNode? grandChild = child!.left; // Выполнить левое вращение узла node вокруг child child.left = node; node.right = grandChild; // Обновить высоту узла updateHeight(node); updateHeight(child); // Вернуть корневой узел поддерева после вращения return child; } /* Выполнить вращение, чтобы снова сбалансировать поддерево */ TreeNode? rotate(TreeNode? node) { // Получить коэффициент баланса узла node int factor = balanceFactor(node); // Левосторонне перекошенное дерево if (factor > 1) { if (balanceFactor(node!.left) >= 0) { // Правое вращение return rightRotate(node); } else { // Сначала левое вращение, затем правое node.left = leftRotate(node.left); return rightRotate(node); } } // Правосторонне перекошенное дерево if (factor < -1) { if (balanceFactor(node!.right) <= 0) { // Левое вращение return leftRotate(node); } else { // Сначала правое вращение, затем левое node.right = rightRotate(node.right); return leftRotate(node); } } // Дерево сбалансировано, вращение не требуется, вернуть сразу return node; } /* Вставка узла */ void insert(int val) { root = insertHelper(root, val); } /* Рекурсивная вставка узла (вспомогательный метод) */ TreeNode? insertHelper(TreeNode? node, int val) { if (node == null) return TreeNode(val); /* 1. Найти позицию вставки и вставить узел */ if (val < node.val) node.left = insertHelper(node.left, val); else if (val > node.val) node.right = insertHelper(node.right, val); else return node; // Повторяющийся узел не вставлять, сразу вернуть updateHeight(node); // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = rotate(node); // Вернуть корневой узел поддерева return node; } /* Удаление узла */ void remove(int val) { root = removeHelper(root, val); } /* Рекурсивное удаление узла (вспомогательный метод) */ TreeNode? removeHelper(TreeNode? node, int val) { if (node == null) return null; /* 1. Найти узел и удалить его */ if (val < node.val) node.left = removeHelper(node.left, val); else if (val > node.val) node.right = removeHelper(node.right, val); else { if (node.left == null || node.right == null) { TreeNode? child = node.left ?? node.right; // Число дочерних узлов = 0, удалить node и сразу вернуть if (child == null) return null; // Число дочерних узлов = 1, удалить node напрямую else node = child; } else { // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел TreeNode? temp = node.right; while (temp!.left != null) { temp = temp.left; } node.right = removeHelper(node.right, temp.val); node.val = temp.val; } } updateHeight(node); // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = rotate(node); // Вернуть корневой узел поддерева return node; } /* Поиск узла */ TreeNode? search(int val) { TreeNode? cur = root; // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Целевой узел находится в правом поддереве cur if (val < cur.val) cur = cur.left; // Целевой узел находится в левом поддереве cur else if (val > cur.val) cur = cur.right; // Целевой узел равен текущему узлу else break; } return cur; } } void testInsert(AVLTree tree, int val) { tree.insert(val); print("\nПосле вставки узла $val AVL-дерево имеет вид"); printTree(tree.root); } void testRemove(AVLTree tree, int val) { tree.remove(val); print("\nПосле удаления узла $val AVL-дерево имеет вид"); printTree(tree.root); } /* Driver Code */ void main() { /* Инициализация пустого AVL-дерева */ AVLTree avlTree = AVLTree(); /* Вставка узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); testInsert(avlTree, 4); testInsert(avlTree, 5); testInsert(avlTree, 8); testInsert(avlTree, 7); testInsert(avlTree, 9); testInsert(avlTree, 10); testInsert(avlTree, 6); /* Вставка повторяющегося узла */ testInsert(avlTree, 7); /* Удаление узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла testRemove(avlTree, 8); // Удаление узла степени 0 testRemove(avlTree, 5); // Удаление узла степени 1 testRemove(avlTree, 4); // Удаление узла степени 2 /* Поиск узла */ TreeNode? node = avlTree.search(7); print("\nНайденный объект узла = $node, значение узла = ${node!.val}"); } ================================================ FILE: ru/codes/dart/chapter_tree/binary_search_tree.dart ================================================ /** * File: binary_search_tree.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Двоичное дерево поиска */ class BinarySearchTree { late TreeNode? _root; /* Конструктор */ BinarySearchTree() { // Инициализировать пустое дерево _root = null; } /* Получить корневой узел двоичного дерева */ TreeNode? getRoot() { return _root; } /* Поиск узла */ TreeNode? search(int _num) { TreeNode? cur = _root; // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Целевой узел находится в правом поддереве cur if (cur.val < _num) cur = cur.right; // Целевой узел находится в левом поддереве cur else if (cur.val > _num) cur = cur.left; // Найти целевой узел и выйти из цикла else break; } // Вернуть целевой узел return cur; } /* Вставка узла */ void insert(int _num) { // Если дерево пусто, инициализировать корневой узел if (_root == null) { _root = TreeNode(_num); return; } TreeNode? cur = _root; TreeNode? pre = null; // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Найти повторяющийся узел и сразу вернуть if (cur.val == _num) return; pre = cur; // Позиция вставки находится в правом поддереве cur if (cur.val < _num) cur = cur.right; // Позиция вставки находится в левом поддереве cur else cur = cur.left; } // Вставка узла TreeNode? node = TreeNode(_num); if (pre!.val < _num) pre.right = node; else pre.left = node; } /* Удаление узла */ void remove(int _num) { // Если дерево пусто, сразу вернуть if (_root == null) return; TreeNode? cur = _root; TreeNode? pre = null; // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Найти узел для удаления и выйти из цикла if (cur.val == _num) break; pre = cur; // Узел для удаления находится в правом поддереве cur if (cur.val < _num) cur = cur.right; // Узел для удаления находится в левом поддереве cur else cur = cur.left; } // Если удаляемого узла нет, сразу вернуть if (cur == null) return; // Число дочерних узлов = 0 или 1 if (cur.left == null || cur.right == null) { // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел TreeNode? child = cur.left ?? cur.right; // Удалить узел cur if (cur != _root) { if (pre!.left == cur) pre.left = child; else pre.right = child; } else { // Если удаляемый узел является корнем, заново назначить корневой узел _root = child; } } else { // Число дочерних узлов = 2 // Получить следующий узел после cur в симметричном обходе TreeNode? tmp = cur.right; while (tmp!.left != null) { tmp = tmp.left; } // Рекурсивно удалить узел tmp remove(tmp.val); // Перезаписать cur значением tmp cur.val = tmp.val; } } } /* Driver Code */ void main() { /* Инициализация двоичного дерева поиска */ BinarySearchTree bst = BinarySearchTree(); // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево List nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (int _num in nums) { bst.insert(_num); } print("\nИсходное двоичное дерево\n"); printTree(bst.getRoot()); /* Поиск узла */ TreeNode? node = bst.search(7); print("\nНайденный объект узла = $node, значение узла = ${node?.val}"); /* Вставка узла */ bst.insert(16); print("\nПосле вставки узла 16 двоичное дерево имеет вид\n"); printTree(bst.getRoot()); /* Удаление узла */ bst.remove(1); print("\nПосле удаления узла 1 двоичное дерево имеет вид\n"); printTree(bst.getRoot()); bst.remove(2); print("\nПосле удаления узла 2 двоичное дерево имеет вид\n"); printTree(bst.getRoot()); bst.remove(4); print("\nПосле удаления узла 4 двоичное дерево имеет вид\n"); printTree(bst.getRoot()); } ================================================ FILE: ru/codes/dart/chapter_tree/binary_tree.dart ================================================ /** * File: binary_tree.dart * Created Time: 2023-04-03 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; void main() { /* Инициализация двоичного дерева */ // Инициализировать узлы дерева TreeNode n1 = TreeNode(1); TreeNode n2 = TreeNode(2); TreeNode n3 = TreeNode(3); TreeNode n4 = TreeNode(4); TreeNode n5 = TreeNode(5); // Построить связи между узлами (указатели) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; print("\nИнициализация двоичного дерева\n"); printTree(n1); /* Вставка и удаление узлов */ TreeNode p = TreeNode(0); // Вставить узел p между n1 -> n2 n1.left = p; p.left = n2; print("\nПосле вставки узла P\n"); printTree(n1); // Удалить узел P n1.left = n2; print("\nПосле удаления узла P\n"); printTree(n1); } ================================================ FILE: ru/codes/dart/chapter_tree/binary_tree_bfs.dart ================================================ /** * File: binary_tree_bfs.dart * Created Time: 2023-04-03 * Author: liuyuxin (gvenusleo@gmai.com) */ import 'dart:collection'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* Обход в ширину */ List levelOrder(TreeNode? root) { // Инициализировать очередь и добавить корневой узел Queue queue = Queue(); queue.add(root); // Инициализировать список для хранения последовательности обхода List res = []; while (queue.isNotEmpty) { TreeNode? node = queue.removeFirst(); // Извлечение из очереди res.add(node!.val); // Сохранить значение узла if (node.left != null) queue.add(node.left); // Поместить левый дочерний узел в очередь if (node.right != null) queue.add(node.right); // Поместить правый дочерний узел в очередь } return res; } /* Driver Code */ void main() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); print("\nИнициализация двоичного дерева\n"); printTree(root); // Обход в ширину List res = levelOrder(root); print("\nПоследовательность печати узлов при обходе в ширину = $res"); } ================================================ FILE: ru/codes/dart/chapter_tree/binary_tree_dfs.dart ================================================ /** * File: binary_tree_dfs.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; // Инициализировать список для хранения последовательности обхода List list = []; /* Предварительный обход */ void preOrder(TreeNode? node) { if (node == null) return; // Порядок обхода: корень -> левое поддерево -> правое поддерево list.add(node.val); preOrder(node.left); preOrder(node.right); } /* Симметричный обход */ void inOrder(TreeNode? node) { if (node == null) return; // Порядок обхода: левое поддерево -> корень -> правое поддерево inOrder(node.left); list.add(node.val); inOrder(node.right); } /* Обратный обход */ void postOrder(TreeNode? node) { if (node == null) return; // Порядок обхода: левое поддерево -> правое поддерево -> корень postOrder(node.left); postOrder(node.right); list.add(node.val); } /* Driver Code */ void main() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); print("\nИнициализация двоичного дерева\n"); printTree(root); /* Предварительный обход */ list.clear(); preOrder(root); print("\nПоследовательность печати узлов при предварительном обходе = $list"); /* Симметричный обход */ list.clear(); inOrder(root); print("\nПоследовательность печати узлов при симметричном обходе = $list"); /* Обратный обход */ list.clear(); postOrder(root); print("\nПоследовательность печати узлов при обратном обходе = $list"); } ================================================ FILE: ru/codes/dart/utils/list_node.dart ================================================ /** * File: list_node.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* Узел связного списка */ class ListNode { int val; ListNode? next; ListNode(this.val, [this.next]); } /* Десериализовать список в связный список */ ListNode? listToLinkedList(List list) { ListNode dum = ListNode(0); ListNode? head = dum; for (int val in list) { head?.next = ListNode(val); head = head?.next; } return dum.next; } ================================================ FILE: ru/codes/dart/utils/print_util.dart ================================================ /** * File: print_util.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:io'; import 'list_node.dart'; import 'tree_node.dart'; class Trunk { Trunk? prev; String str; Trunk(this.prev, this.str); } /* Вывести матрицу (Array) */ void printMatrix(List> matrix) { print("["); for (List row in matrix) { print(" $row,"); } print("]"); } /* Вывести связный список */ void printLinkedList(ListNode? head) { List list = []; while (head != null) { list.add('${head.val}'); head = head.next; } print(list.join(' -> ')); } /** * Вывести двоичное дерево * Этот вывод дерева заимствован из TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTree(TreeNode? root, [Trunk? prev = null, bool isRight = false]) { if (root == null) { return; } String prev_str = ' '; Trunk trunk = Trunk(prev, prev_str); printTree(root.right, trunk, true); if (prev == null) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); print(' ${root.val}'); if (prev != null) { prev.str = prev_str; } trunk.str = ' |'; printTree(root.left, trunk, false); } void showTrunks(Trunk? p) { if (p == null) { return; } showTrunks(p.prev); stdout.write(p.str); } /* Вывести кучу */ void printHeap(List heap) { print("Массивное представление кучи: $heap"); print("Древовидное представление кучи:"); TreeNode? root = listToTree(heap); printTree(root); } ================================================ FILE: ru/codes/dart/utils/tree_node.dart ================================================ /** * File: tree_node.dart * Created Time: 2023-2-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* Класс узла двоичного дерева */ class TreeNode { int val; // Значение узла int height; // Высота узла TreeNode? left; // Ссылка на левый дочерний узел TreeNode? right; // Ссылка на правый дочерний узел /* Конструктор */ TreeNode(this.val, [this.height = 0, this.left, this.right]); } /* Десериализовать список в двоичное дерево: рекурсия */ TreeNode? listToTreeDFS(List arr, int i) { if (i < 0 || i >= arr.length || arr[i] == null) { return null; } TreeNode? root = TreeNode(arr[i]!); root.left = listToTreeDFS(arr, 2 * i + 1); root.right = listToTreeDFS(arr, 2 * i + 2); return root; } /* Десериализовать список в двоичное дерево */ TreeNode? listToTree(List arr) { return listToTreeDFS(arr, 0); } /* Сериализовать двоичное дерево в список: рекурсия */ void treeToListDFS(TreeNode? root, int i, List res) { if (root == null) return; while (i >= res.length) { res.add(null); } res[i] = root.val; treeToListDFS(root.left, 2 * i + 1, res); treeToListDFS(root.right, 2 * i + 2, res); } /* Сериализовать двоичное дерево в список */ List treeToList(TreeNode? root) { List res = []; treeToListDFS(root, 0, res); return res; } ================================================ FILE: ru/codes/dart/utils/vertex.dart ================================================ /** * File: Vertex.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Класс вершины */ class Vertex { int val; Vertex(this.val); /* На вход подается список значений vals, на выходе возвращается список вершин vets */ static List valsToVets(List vals) { List vets = []; for (int i in vals) { vets.add(Vertex(i)); } return vets; } /* На вход подается список вершин vets, на выходе возвращается список значений vals */ static List vetsToVals(List vets) { List vals = []; for (Vertex vet in vets) { vals.add(vet.val); } return vals; } } ================================================ FILE: ru/codes/docker-compose.yml ================================================ version: '3.8' services: hello-algo-code: build: context: . args: # Set the languages to be installed, separated by spaces LANGS: "python cpp java csharp" image: hello-algo-code container_name: hello-algo-code stdin_open: true tty: true ================================================ FILE: ru/codes/go/chapter_array_and_linkedlist/array.go ================================================ // File: array.go // Created Time: 2022-12-29 // Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( "math/rand" ) /* Случайный доступ к элементу */ func randomAccess(nums []int) (randomNum int) { // Случайным образом выбрать число из интервала [0, nums.length) randomIndex := rand.Intn(len(nums)) // Получить и вернуть случайный элемент randomNum = nums[randomIndex] return } /* Увеличить длину массива */ func extend(nums []int, enlarge int) []int { // Инициализировать массив увеличенной длины res := make([]int, len(nums)+enlarge) // Скопировать все элементы исходного массива в новый массив for i, num := range nums { res[i] = num } // Вернуть новый массив после расширения return res } /* Вставить элемент num по индексу index в массив */ func insert(nums []int, num int, index int) { // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад for i := len(nums) - 1; i > index; i-- { nums[i] = nums[i-1] } // Присвоить num элементу по индексу index nums[index] = num } /* Удалить элемент по индексу index */ func remove(nums []int, index int) { // Сдвинуть все элементы после индекса index на одну позицию вперед for i := index; i < len(nums)-1; i++ { nums[i] = nums[i+1] } } /* Обход массива */ func traverse(nums []int) { count := 0 // Обход массива по индексам for i := 0; i < len(nums); i++ { count += nums[i] } count = 0 // Непосредственно обходить элементы массива for _, num := range nums { count += num } // Одновременно обходить индексы и элементы данных for i, num := range nums { count += nums[i] count += num } } /* Найти заданный элемент в массиве */ func find(nums []int, target int) (index int) { index = -1 for i := 0; i < len(nums); i++ { if nums[i] == target { index = i break } } return } ================================================ FILE: ru/codes/go/chapter_array_and_linkedlist/array_test.go ================================================ // File: array_test.go // Created Time: 2022-12-29 // Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist /** * Мы рассматриваем срез Slice в Go как массив Array. Это позволяет * снизить порог понимания и сосредоточиться на структурах данных и алгоритмах. */ import ( "fmt" "testing" ) /* Driver Code */ func TestArray(t *testing.T) { /* Инициализация массива */ var arr [5]int fmt.Println("Массив arr =", arr) // В Go при указании длины ([5]int) получается массив, а без указания длины ([]int) — срез // Так как массивы в Go имеют длину, определяемую на этапе компиляции, для задания длины можно использовать только константы // Для удобства реализации функции расширения extend() ниже срез (Slice) рассматривается как массив (Array) nums := []int{1, 3, 2, 5, 4} fmt.Println("Массив nums =", nums) /* Случайный доступ */ randomNum := randomAccess(nums) fmt.Println("Случайный элемент из nums =", randomNum) /* Расширение длины */ nums = extend(nums, 3) fmt.Println("После увеличения длины массива до 8 nums =", nums) /* Вставка элемента */ insert(nums, 6, 3) fmt.Println("После вставки числа 6 по индексу 3 nums =", nums) /* Удаление элемента */ remove(nums, 2) fmt.Println("После удаления элемента по индексу 2 nums =", nums) /* Обход массива */ traverse(nums) /* Поиск элемента */ index := find(nums, 3) fmt.Println("Поиск элемента 3 в nums: индекс =", index) } ================================================ FILE: ru/codes/go/chapter_array_and_linkedlist/linked_list.go ================================================ // File: linked_list.go // Created Time: 2022-12-29 // Author: cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( . "github.com/krahets/hello-algo/pkg" ) /* Вставить узел P после узла n0 в связном списке */ func insertNode(n0 *ListNode, P *ListNode) { n1 := n0.Next P.Next = n1 n0.Next = P } /* Удалить первый узел после узла n0 в связном списке */ func removeItem(n0 *ListNode) { if n0.Next == nil { return } // n0 -> P -> n1 P := n0.Next n1 := P.Next n0.Next = n1 } /* Доступ к узлу связного списка по индексу index */ func access(head *ListNode, index int) *ListNode { for i := 0; i < index; i++ { if head == nil { return nil } head = head.Next } return head } /* Найти в связном списке первый узел со значением target */ func findNode(head *ListNode, target int) int { index := 0 for head != nil { if head.Val == target { return index } head = head.Next index++ } return -1 } ================================================ FILE: ru/codes/go/chapter_array_and_linkedlist/linked_list_test.go ================================================ // File: linked_list_test.go // Created Time: 2022-12-29 // Author: cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLinkedList(t *testing.T) { /* Инициализировать связный список 1 -> 3 -> 2 -> 5 -> 4 */ // Инициализация всех узлов n0 := NewListNode(1) n1 := NewListNode(3) n2 := NewListNode(2) n3 := NewListNode(5) n4 := NewListNode(4) // Построить ссылки между узлами n0.Next = n1 n1.Next = n2 n2.Next = n3 n3.Next = n4 fmt.Println("Исходный связный список") PrintLinkedList(n0) /* Вставка узла */ insertNode(n0, NewListNode(0)) fmt.Println("Связный список после вставки узла") PrintLinkedList(n0) /* Удаление узла */ removeItem(n0) fmt.Println("Связный список после удаления узла") PrintLinkedList(n0) /* Доступ к узлу */ node := access(n0, 3) fmt.Println("Значение узла по индексу 3 в связном списке =", node) /* Поиск узла */ index := findNode(n0, 2) fmt.Println("Индекс узла со значением 2 в связном списке =", index) } ================================================ FILE: ru/codes/go/chapter_array_and_linkedlist/list_test.go ================================================ // File: list_test.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "sort" "testing" ) /* Driver Code */ func TestList(t *testing.T) { /* Инициализация списка */ nums := []int{1, 3, 2, 5, 4} fmt.Println("Список nums =", nums) /* Доступ к элементу */ num := nums[1] // Обратиться к элементу по индексу 1 fmt.Println("Элемент по индексу 1: num =", num) /* Обновление элемента */ nums[1] = 0 // Обновить элемент по индексу 1 до 0 fmt.Println("После обновления элемента по индексу 1 до 0 nums =", nums) /* Очистить список */ nums = nil fmt.Println("После очистки списка nums =", nums) /* Добавление элемента в конец */ nums = append(nums, 1) nums = append(nums, 3) nums = append(nums, 2) nums = append(nums, 5) nums = append(nums, 4) fmt.Println("После добавления элементов nums =", nums) /* Вставка элемента в середину */ nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // Вставить число 6 по индексу 3 fmt.Println("После вставки числа 6 по индексу 3 nums =", nums) /* Удаление элемента */ nums = append(nums[:3], nums[4:]...) // Удалить элемент по индексу 3 fmt.Println("После удаления элемента по индексу 3 nums =", nums) /* Обходить список по индексам */ count := 0 for i := 0; i < len(nums); i++ { count += nums[i] } /* Непосредственно обходить элементы списка */ count = 0 for _, x := range nums { count += x } /* Объединить два списка */ nums1 := []int{6, 8, 7, 10, 9} nums = append(nums, nums1...) // Присоединить список nums1 после nums fmt.Println("После конкатенации списка nums1 к nums nums =", nums) /* Отсортировать список */ sort.Ints(nums) // После сортировки элементы списка располагаются по возрастанию fmt.Println("После сортировки списка nums =", nums) } ================================================ FILE: ru/codes/go/chapter_array_and_linkedlist/my_list.go ================================================ // File: my_list.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist /* Класс списка */ type myList struct { arrCapacity int arr []int arrSize int extendRatio int } /* Конструктор */ func newMyList() *myList { return &myList{ arrCapacity: 10, // Вместимость списка arr: make([]int, 10), // Массив (для хранения элементов списка) arrSize: 0, // Длина списка (текущее число элементов) extendRatio: 2, // Коэффициент увеличения списка при каждом расширении } } /* Получить длину списка (текущее число элементов) */ func (l *myList) size() int { return l.arrSize } /* Получить вместимость списка */ func (l *myList) capacity() int { return l.arrCapacity } /* Доступ к элементу */ func (l *myList) get(index int) int { // Если индекс выходит за границы, выбрасывается исключение; далее аналогично if index < 0 || index >= l.arrSize { panic("индекс выходит за границы") } return l.arr[index] } /* Обновление элемента */ func (l *myList) set(num, index int) { if index < 0 || index >= l.arrSize { panic("индекс выходит за границы") } l.arr[index] = num } /* Добавление элемента в конец */ func (l *myList) add(num int) { // При превышении вместимости по числу элементов запускается расширение if l.arrSize == l.arrCapacity { l.extendCapacity() } l.arr[l.arrSize] = num // Обновить число элементов l.arrSize++ } /* Вставка элемента в середину */ func (l *myList) insert(num, index int) { if index < 0 || index >= l.arrSize { panic("индекс выходит за границы") } // При превышении вместимости по числу элементов запускается расширение if l.arrSize == l.arrCapacity { l.extendCapacity() } // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад for j := l.arrSize - 1; j >= index; j-- { l.arr[j+1] = l.arr[j] } l.arr[index] = num // Обновить число элементов l.arrSize++ } /* Удаление элемента */ func (l *myList) remove(index int) int { if index < 0 || index >= l.arrSize { panic("индекс выходит за границы") } num := l.arr[index] // Сдвинуть все элементы после индекса index на одну позицию вперед for j := index; j < l.arrSize-1; j++ { l.arr[j] = l.arr[j+1] } // Обновить число элементов l.arrSize-- // Вернуть удаленный элемент return num } /* Расширение списка */ func (l *myList) extendCapacity() { // Создать новый массив длиной в extendRatio раз больше исходного и скопировать в него исходный массив l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...) // Обновить вместимость списка l.arrCapacity = len(l.arr) } /* Вернуть список фактической длины */ func (l *myList) toArray() []int { // Преобразовывать только элементы списка в пределах фактической длины return l.arr[:l.arrSize] } ================================================ FILE: ru/codes/go/chapter_array_and_linkedlist/my_list_test.go ================================================ // File: my_list_test.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "testing" ) /* Driver Code */ func TestMyList(t *testing.T) { /* Инициализация списка */ nums := newMyList() /* Добавление элемента в конец */ nums.add(1) nums.add(3) nums.add(2) nums.add(5) nums.add(4) fmt.Printf("Список nums = %v, вместимость = %v, длина = %v\n", nums.toArray(), nums.capacity(), nums.size()) /* Вставка элемента в середину */ nums.insert(6, 3) fmt.Printf("После вставки числа 6 по индексу 3 nums = %v\n", nums.toArray()) /* Удаление элемента */ nums.remove(3) fmt.Printf("После удаления элемента по индексу 3 nums = %v\n", nums.toArray()) /* Доступ к элементу */ num := nums.get(1) fmt.Printf("Элемент по индексу 1: num = %v\n", num) /* Обновление элемента */ nums.set(0, 1) fmt.Printf("После обновления элемента по индексу 1 до 0 nums = %v\n", nums.toArray()) /* Проверка механизма расширения */ for i := 0; i < 10; i++ { // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения nums.add(i) } fmt.Printf("Список nums после увеличения вместимости = %v, вместимость = %v, длина = %v\n", nums.toArray(), nums.capacity(), nums.size()) } ================================================ FILE: ru/codes/go/chapter_backtracking/n_queens.go ================================================ // File: n_queens.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* Алгоритм бэктрекинга: n ферзей */ func backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) { // Когда все строки уже обработаны, записать решение if row == n { newState := make([][]string, len(*state)) for i, _ := range newState { newState[i] = make([]string, len((*state)[0])) copy(newState[i], (*state)[i]) } *res = append(*res, newState) return } // Обойти все столбцы for col := 0; col < n; col++ { // Вычислить главную и побочную диагонали, соответствующие этой клетке diag1 := row - col + n - 1 diag2 := row + col // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] { // Попытка: поставить ферзя в эту клетку (*state)[row][col] = "Q" (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true // Перейти к размещению следующей строки backtrack(row+1, n, state, res, cols, diags1, diags2) // Откат: восстановить эту клетку как пустую (*state)[row][col] = "#" (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false } } } /* Решить задачу о n ферзях */ func nQueens(n int) [][][]string { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку state := make([][]string, n) for i := 0; i < n; i++ { row := make([]string, n) for i := 0; i < n; i++ { row[i] = "#" } state[i] = row } // Отмечать, есть ли ферзь в столбце cols := make([]bool, n) diags1 := make([]bool, 2*n-1) diags2 := make([]bool, 2*n-1) res := make([][][]string, 0) backtrack(0, n, &state, &res, &cols, &diags1, &diags2) return res } ================================================ FILE: ru/codes/go/chapter_backtracking/n_queens_test.go ================================================ // File: n_queens_test.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" ) func TestNQueens(t *testing.T) { n := 4 res := nQueens(n) fmt.Println("Размер входной доски =", n) fmt.Println("Количество способов расстановки ферзей:", len(res)) for _, state := range res { fmt.Println("--------------------") for _, row := range state { fmt.Println(row) } } } ================================================ FILE: ru/codes/go/chapter_backtracking/permutation_test.go ================================================ // File: permutation_test.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPermutationI(t *testing.T) { /* Все перестановки I */ nums := []int{1, 2, 3} fmt.Printf("Входной массив nums = ") PrintSlice(nums) res := permutationsI(nums) fmt.Printf("Все перестановки res = ") fmt.Println(res) } func TestPermutationII(t *testing.T) { nums := []int{1, 2, 2} fmt.Printf("Входной массив nums = ") PrintSlice(nums) res := permutationsII(nums) fmt.Printf("Все перестановки res = ") fmt.Println(res) } ================================================ FILE: ru/codes/go/chapter_backtracking/permutations_i.go ================================================ // File: permutations_i.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* Алгоритм бэктрекинга: все перестановки I */ func backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // Когда длина состояния равна числу элементов, записать решение if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // Перебор всех вариантов выбора for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // Отсечение: нельзя выбирать один и тот же элемент повторно if !(*selected)[i] { // Попытка: сделать выбор и обновить состояние (*selected)[i] = true *state = append(*state, choice) // Перейти к следующему выбору backtrackI(state, choices, selected, res) // Откат: отменить выбор и восстановить предыдущее состояние (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* Все перестановки I */ func permutationsI(nums []int) [][]int { res := make([][]int, 0) state := make([]int, 0) selected := make([]bool, len(nums)) backtrackI(&state, &nums, &selected, &res) return res } ================================================ FILE: ru/codes/go/chapter_backtracking/permutations_ii.go ================================================ // File: permutations_ii.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* Алгоритм бэктрекинга: все перестановки II */ func backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // Когда длина состояния равна числу элементов, записать решение if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // Перебор всех вариантов выбора duplicated := make(map[int]struct{}, 0) for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if _, ok := duplicated[choice]; !ok && !(*selected)[i] { // Попробовать: сделать выбор, обновить состояние // Записать значение уже выбранного элемента duplicated[choice] = struct{}{} (*selected)[i] = true *state = append(*state, choice) // Перейти к следующему выбору backtrackII(state, choices, selected, res) // Откат: отменить выбор и восстановить предыдущее состояние (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* Все перестановки II */ func permutationsII(nums []int) [][]int { res := make([][]int, 0) state := make([]int, 0) selected := make([]bool, len(nums)) backtrackII(&state, &nums, &selected, &res) return res } ================================================ FILE: ru/codes/go/chapter_backtracking/preorder_traversal_i_compact.go ================================================ // File: preorder_traversal_i_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* Предварительный обход: пример 1 */ func preOrderI(root *TreeNode, res *[]*TreeNode) { if root == nil { return } if (root.Val).(int) == 7 { // Записать решение *res = append(*res, root) } preOrderI(root.Left, res) preOrderI(root.Right, res) } ================================================ FILE: ru/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go ================================================ // File: preorder_traversal_ii_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* Предварительный обход: пример 2 */ func preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { if root == nil { return } // Попытка *path = append(*path, root) if root.Val.(int) == 7 { // Записать решение *res = append(*res, append([]*TreeNode{}, *path...)) } preOrderII(root.Left, res, path) preOrderII(root.Right, res, path) // Откат *path = (*path)[:len(*path)-1] } ================================================ FILE: ru/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go ================================================ // File: preorder_traversal_iii_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* Предварительный обход: пример 3 */ func preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { // Отсечение if root == nil || root.Val == 3 { return } // Попытка *path = append(*path, root) if root.Val.(int) == 7 { // Записать решение *res = append(*res, append([]*TreeNode{}, *path...)) } preOrderIII(root.Left, res, path) preOrderIII(root.Right, res, path) // Откат *path = (*path)[:len(*path)-1] } ================================================ FILE: ru/codes/go/chapter_backtracking/preorder_traversal_iii_template.go ================================================ // File: preorder_traversal_iii_template.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* Проверить, является ли текущее состояние решением */ func isSolution(state *[]*TreeNode) bool { return len(*state) != 0 && (*state)[len(*state)-1].Val == 7 } /* Записать решение */ func recordSolution(state *[]*TreeNode, res *[][]*TreeNode) { *res = append(*res, append([]*TreeNode{}, *state...)) } /* Проверить, допустим ли этот выбор в текущем состоянии */ func isValid(state *[]*TreeNode, choice *TreeNode) bool { return choice != nil && choice.Val != 3 } /* Обновить состояние */ func makeChoice(state *[]*TreeNode, choice *TreeNode) { *state = append(*state, choice) } /* Восстановить состояние */ func undoChoice(state *[]*TreeNode, choice *TreeNode) { *state = (*state)[:len(*state)-1] } /* Алгоритм бэктрекинга: пример 3 */ func backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) { // Проверить, является ли текущее состояние решением if isSolution(state) { // Записать решение recordSolution(state, res) } // Перебор всех вариантов выбора for _, choice := range *choices { // Отсечение: проверить допустимость выбора if isValid(state, choice) { // Попытка: сделать выбор и обновить состояние makeChoice(state, choice) // Перейти к следующему выбору temp := make([]*TreeNode, 0) temp = append(temp, choice.Left, choice.Right) backtrackIII(state, &temp, res) // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state, choice) } } } ================================================ FILE: ru/codes/go/chapter_backtracking/preorder_traversal_test.go ================================================ // File: preorder_traversal_i_compact_test.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPreorderTraversalICompact(t *testing.T) { /* Инициализация двоичного дерева */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\nИнициализация двоичного дерева") PrintTree(root) // Предварительный обход res := make([]*TreeNode, 0) preOrderI(root, &res) fmt.Println("\nВсе узлы со значением 7") for _, node := range res { fmt.Printf("%v ", node.Val) } fmt.Println() } func TestPreorderTraversalIICompact(t *testing.T) { /* Инициализация двоичного дерева */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\nИнициализация двоичного дерева") PrintTree(root) // Предварительный обход path := make([]*TreeNode, 0) res := make([][]*TreeNode, 0) preOrderII(root, &res, &path) fmt.Println("\nВсе пути от корня к узлу 7") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } func TestPreorderTraversalIIICompact(t *testing.T) { /* Инициализация двоичного дерева */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\nИнициализация двоичного дерева") PrintTree(root) // Предварительный обход path := make([]*TreeNode, 0) res := make([][]*TreeNode, 0) preOrderIII(root, &res, &path) fmt.Println("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } func TestPreorderTraversalIIITemplate(t *testing.T) { /* Инициализация двоичного дерева */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\nИнициализация двоичного дерева") PrintTree(root) // Алгоритм бэктрекинга res := make([][]*TreeNode, 0) state := make([]*TreeNode, 0) choices := make([]*TreeNode, 0) choices = append(choices, root) backtrackIII(&state, &choices, &res) fmt.Println("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } ================================================ FILE: ru/codes/go/chapter_backtracking/subset_sum_i.go ================================================ // File: subset_sum_i.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import "sort" /* Алгоритм бэктрекинга: сумма подмножеств I */ func backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) { // Если сумма подмножества равна target, записать решение if target == 0 { newState := append([]int{}, *state...) *res = append(*res, newState) return } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств for i := start; i < len(*choices); i++ { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if target-(*choices)[i] < 0 { break } // Попытка: сделать выбор и обновить target и start *state = append(*state, (*choices)[i]) // Перейти к следующему выбору backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res) // Откат: отменить выбор и восстановить предыдущее состояние *state = (*state)[:len(*state)-1] } } /* Решить задачу суммы подмножеств I */ func subsetSumI(nums []int, target int) [][]int { state := make([]int, 0) // Состояние (подмножество) sort.Ints(nums) // Отсортировать nums start := 0 // Стартовая вершина обхода res := make([][]int, 0) // Список результатов (список подмножеств) backtrackSubsetSumI(start, target, &state, &nums, &res) return res } ================================================ FILE: ru/codes/go/chapter_backtracking/subset_sum_i_naive.go ================================================ // File: subset_sum_i_naive.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* Алгоритм бэктрекинга: сумма подмножеств I */ func backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) { // Если сумма подмножества равна target, записать решение if target == total { newState := append([]int{}, *state...) *res = append(*res, newState) return } // Перебор всех вариантов выбора for i := 0; i < len(*choices); i++ { // Отсечение: если сумма подмножества превышает target, пропустить этот выбор if total+(*choices)[i] > target { continue } // Попытка: сделать выбор и обновить элемент и total *state = append(*state, (*choices)[i]) // Перейти к следующему выбору backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res) // Откат: отменить выбор и восстановить предыдущее состояние *state = (*state)[:len(*state)-1] } } /* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ func subsetSumINaive(nums []int, target int) [][]int { state := make([]int, 0) // Состояние (подмножество) total := 0 // Сумма подмножеств res := make([][]int, 0) // Список результатов (список подмножеств) backtrackSubsetSumINaive(total, target, &state, &nums, &res) return res } ================================================ FILE: ru/codes/go/chapter_backtracking/subset_sum_ii.go ================================================ // File: subset_sum_ii.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import "sort" /* Алгоритм бэктрекинга: сумма подмножеств II */ func backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) { // Если сумма подмножества равна target, записать решение if target == 0 { newState := append([]int{}, *state...) *res = append(*res, newState) return } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента for i := start; i < len(*choices); i++ { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if target-(*choices)[i] < 0 { break } // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить if i > start && (*choices)[i] == (*choices)[i-1] { continue } // Попытка: сделать выбор и обновить target и start *state = append(*state, (*choices)[i]) // Перейти к следующему выбору backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res) // Откат: отменить выбор и восстановить предыдущее состояние *state = (*state)[:len(*state)-1] } } /* Решить задачу суммы подмножеств II */ func subsetSumII(nums []int, target int) [][]int { state := make([]int, 0) // Состояние (подмножество) sort.Ints(nums) // Отсортировать nums start := 0 // Стартовая вершина обхода res := make([][]int, 0) // Список результатов (список подмножеств) backtrackSubsetSumII(start, target, &state, &nums, &res) return res } ================================================ FILE: ru/codes/go/chapter_backtracking/subset_sum_test.go ================================================ // File: subset_sum_test.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestSubsetSumINaive(t *testing.T) { nums := []int{3, 4, 5} target := 9 res := subsetSumINaive(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", входной массив nums = ") PrintSlice(nums) fmt.Println("Все подмножества с суммой " + strconv.Itoa(target) + ": res = ") for i := range res { PrintSlice(res[i]) } fmt.Println("Обратите внимание: результат этого метода содержит повторяющиеся множества") } func TestSubsetSumI(t *testing.T) { nums := []int{3, 4, 5} target := 9 res := subsetSumI(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", входной массив nums = ") PrintSlice(nums) fmt.Println("Все подмножества с суммой " + strconv.Itoa(target) + ": res = ") for i := range res { PrintSlice(res[i]) } } func TestSubsetSumII(t *testing.T) { nums := []int{4, 4, 5} target := 9 res := subsetSumII(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", входной массив nums = ") PrintSlice(nums) fmt.Println("Все подмножества с суммой " + strconv.Itoa(target) + ": res = ") for i := range res { PrintSlice(res[i]) } } ================================================ FILE: ru/codes/go/chapter_computational_complexity/iteration.go ================================================ // File: iteration.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import "fmt" /* Цикл for */ func forLoop(n int) int { res := 0 // Циклическое суммирование 1, 2, ..., n-1, n for i := 1; i <= n; i++ { res += i } return res } /* Цикл while */ func whileLoop(n int) int { res := 0 // Инициализация условной переменной i := 1 // Циклическое суммирование 1, 2, ..., n-1, n for i <= n { res += i // Обновить условную переменную i++ } return res } /* Цикл while (двойное обновление) */ func whileLoopII(n int) int { res := 0 // Инициализация условной переменной i := 1 // Циклическое суммирование 1, 4, 10, ... for i <= n { res += i // Обновить условную переменную i++ i *= 2 } return res } /* Двойной цикл for */ func nestedForLoop(n int) string { res := "" // Цикл по i = 1, 2, ..., n-1, n for i := 1; i <= n; i++ { for j := 1; j <= n; j++ { // Цикл по j = 1, 2, ..., n-1, n res += fmt.Sprintf("(%d, %d), ", i, j) } } return res } ================================================ FILE: ru/codes/go/chapter_computational_complexity/iteration_test.go ================================================ // File: iteration_test.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import ( "fmt" "testing" ) /* Driver Code */ func TestIteration(t *testing.T) { n := 5 res := forLoop(n) fmt.Println("\nРезультат суммирования в цикле for res = ", res) res = whileLoop(n) fmt.Println("\nРезультат суммирования в цикле while res = ", res) res = whileLoopII(n) fmt.Println("\nРезультат суммирования в цикле while (двойное обновление) res = ", res) resStr := nestedForLoop(n) fmt.Println("\nРезультат обхода в двойном цикле for ", resStr) } ================================================ FILE: ru/codes/go/chapter_computational_complexity/recursion.go ================================================ // File: recursion.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import "container/list" /* Рекурсия */ func recur(n int) int { // Условие завершения if n == 1 { return 1 } // Рекурсия: рекурсивный вызов res := recur(n - 1) // Возврат: вернуть результат return n + res } /* Имитация рекурсии итерацией */ func forLoopRecur(n int) int { // Использовать явный стек для имитации системного стека вызовов stack := list.New() res := 0 // Рекурсия: рекурсивный вызов for i := n; i > 0; i-- { // Имитировать «рекурсию» с помощью операции помещения в стек stack.PushBack(i) } // Возврат: вернуть результат for stack.Len() != 0 { // Имитировать «возврат» с помощью операции извлечения из стека res += stack.Back().Value.(int) stack.Remove(stack.Back()) } // res = 1+2+3+...+n return res } /* Хвостовая рекурсия */ func tailRecur(n int, res int) int { // Условие завершения if n == 0 { return res } // Хвостовой рекурсивный вызов return tailRecur(n-1, res+n) } /* Последовательность Фибоначчи: рекурсия */ func fib(n int) int { // Условие завершения: f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1 } // Рекурсивный вызов f(n) = f(n-1) + f(n-2) res := fib(n-1) + fib(n-2) // Вернуть результат f(n) return res } ================================================ FILE: ru/codes/go/chapter_computational_complexity/recursion_test.go ================================================ // File: recursion_test.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import ( "fmt" "testing" ) /* Driver Code */ func TestRecursion(t *testing.T) { n := 5 res := recur(n) fmt.Println("\nРезультат суммирования в рекурсивной функции res = ", res) res = forLoopRecur(n) fmt.Println("\nРезультат суммирования при имитации рекурсии итерацией res = ", res) res = tailRecur(n, 0) fmt.Println("\nРезультат суммирования в хвостовой рекурсии res = ", res) res = fib(n) fmt.Println("\nЧлен последовательности Фибоначчи с номером", n, "=", res) } ================================================ FILE: ru/codes/go/chapter_computational_complexity/space_complexity.go ================================================ // File: space_complexity.go // Created Time: 2022-12-15 // Author: cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "fmt" "strconv" . "github.com/krahets/hello-algo/pkg" ) /* Структура */ type node struct { val int next *node } /* Создать структуру node */ func newNode(val int) *node { return &node{val: val} } /* Функция */ func function() int { // Выполнить некоторые операции... return 0 } /* Постоянная сложность */ func spaceConstant(n int) { // Константы, переменные и объекты занимают O(1) памяти const a = 0 b := 0 nums := make([]int, 10000) node := newNode(0) // Переменные в цикле занимают O(1) памяти var c int for i := 0; i < n; i++ { c = 0 } // Функции в цикле занимают O(1) памяти for i := 0; i < n; i++ { function() } b += 0 c += 0 nums[0] = 0 node.val = 0 } /* Линейная сложность */ func spaceLinear(n int) { // Массив длины n занимает O(n) памяти _ = make([]int, n) // Список длины n занимает O(n) памяти var nodes []*node for i := 0; i < n; i++ { nodes = append(nodes, newNode(i)) } // Хеш-таблица длины n занимает O(n) памяти m := make(map[int]string, n) for i := 0; i < n; i++ { m[i] = strconv.Itoa(i) } } /* Линейная сложность (рекурсивная реализация) */ func spaceLinearRecur(n int) { fmt.Println("Рекурсия n =", n) if n == 1 { return } spaceLinearRecur(n - 1) } /* Квадратичная сложность */ func spaceQuadratic(n int) { // Матрица занимает O(n^2) памяти numMatrix := make([][]int, n) for i := 0; i < n; i++ { numMatrix[i] = make([]int, n) } } /* Квадратичная сложность (рекурсивная реализация) */ func spaceQuadraticRecur(n int) int { if n <= 0 { return 0 } nums := make([]int, n) fmt.Printf("В рекурсии n = %d, длина nums = %d\n", n, len(nums)) return spaceQuadraticRecur(n - 1) } /* Экспоненциальная сложность (построение полного двоичного дерева) */ func buildTree(n int) *TreeNode { if n == 0 { return nil } root := NewTreeNode(0) root.Left = buildTree(n - 1) root.Right = buildTree(n - 1) return root } ================================================ FILE: ru/codes/go/chapter_computational_complexity/space_complexity_test.go ================================================ // File: space_complexity_test.go // Created Time: 2022-12-15 // Author: cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "testing" . "github.com/krahets/hello-algo/pkg" ) func TestSpaceComplexity(t *testing.T) { n := 5 // Постоянная сложность spaceConstant(n) // Линейная сложность spaceLinear(n) spaceLinearRecur(n) // Квадратичная сложность spaceQuadratic(n) spaceQuadraticRecur(n) // Экспоненциальная сложность root := buildTree(n) PrintTree(root) } ================================================ FILE: ru/codes/go/chapter_computational_complexity/time_complexity.go ================================================ // File: time_complexity.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_computational_complexity /* Постоянная сложность */ func constant(n int) int { count := 0 size := 100000 for i := 0; i < size; i++ { count++ } return count } /* Линейная сложность */ func linear(n int) int { count := 0 for i := 0; i < n; i++ { count++ } return count } /* Линейная сложность (обход массива) */ func arrayTraversal(nums []int) int { count := 0 // Число итераций пропорционально длине массива for range nums { count++ } return count } /* Квадратичная сложность */ func quadratic(n int) int { count := 0 // Число итераций квадратично зависит от размера данных n for i := 0; i < n; i++ { for j := 0; j < n; j++ { count++ } } return count } /* Квадратичная сложность (пузырьковая сортировка) */ func bubbleSort(nums []int) int { count := 0 // Счетчик // Внешний цикл: неотсортированный диапазон [0, i] for i := len(nums) - 1; i > 0; i-- { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // Поменять местами nums[j] и nums[j + 1] tmp := nums[j] nums[j] = nums[j+1] nums[j+1] = tmp count += 3 // Обмен элементов включает 3 элементарные операции } } } return count } /* Экспоненциальная сложность (итеративная реализация) */ func exponential(n int) int { count, base := 0, 1 // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) for i := 0; i < n; i++ { for j := 0; j < base; j++ { count++ } base *= 2 } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count } /* Экспоненциальная сложность (рекурсивная реализация) */ func expRecur(n int) int { if n == 1 { return 1 } return expRecur(n-1) + expRecur(n-1) + 1 } /* Логарифмическая сложность (итеративная реализация) */ func logarithmic(n int) int { count := 0 for n > 1 { n = n / 2 count++ } return count } /* Логарифмическая сложность (рекурсивная реализация) */ func logRecur(n int) int { if n <= 1 { return 0 } return logRecur(n/2) + 1 } /* Линейно-логарифмическая сложность */ func linearLogRecur(n int) int { if n <= 1 { return 1 } count := linearLogRecur(n/2) + linearLogRecur(n/2) for i := 0; i < n; i++ { count++ } return count } /* Факториальная сложность (рекурсивная реализация) */ func factorialRecur(n int) int { if n == 0 { return 1 } count := 0 // Из одного получается n for i := 0; i < n; i++ { count += factorialRecur(n - 1) } return count } ================================================ FILE: ru/codes/go/chapter_computational_complexity/time_complexity_test.go ================================================ // File: time_complexity_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_computational_complexity import ( "fmt" "testing" ) func TestTimeComplexity(t *testing.T) { n := 8 fmt.Println("Размер входных данных n =", n) count := constant(n) fmt.Println("Число операций константной сложности =", count) count = linear(n) fmt.Println("Число операций линейной сложности =", count) count = arrayTraversal(make([]int, n)) fmt.Println("Число операций линейной сложности (обход массива) =", count) count = quadratic(n) fmt.Println("Число операций квадратичной сложности =", count) nums := make([]int, n) for i := 0; i < n; i++ { nums[i] = n - i } count = bubbleSort(nums) fmt.Println("Число операций квадратичной сложности (пузырьковая сортировка) =", count) count = exponential(n) fmt.Println("Число операций экспоненциальной сложности (итеративная реализация) =", count) count = expRecur(n) fmt.Println("Число операций экспоненциальной сложности (рекурсивная реализация) =", count) count = logarithmic(n) fmt.Println("Число операций логарифмической сложности (итеративная реализация) =", count) count = logRecur(n) fmt.Println("Число операций логарифмической сложности (рекурсивная реализация) =", count) count = linearLogRecur(n) fmt.Println("Число операций линейно-логарифмической сложности (рекурсивная реализация) =", count) count = factorialRecur(n) fmt.Println("Число операций факториальной сложности (рекурсивная реализация) =", count) } ================================================ FILE: ru/codes/go/chapter_computational_complexity/worst_best_time_complexity.go ================================================ // File: worst_best_time_complexity.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "math/rand" ) /* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ func randomNumbers(n int) []int { nums := make([]int, n) // Создать массив nums = { 1, 2, 3, ..., n } for i := 0; i < n; i++ { nums[i] = i + 1 } // Случайно перемешать элементы массива rand.Shuffle(len(nums), func(i, j int) { nums[i], nums[j] = nums[j], nums[i] }) return nums } /* Найти индекс числа 1 в массиве nums */ func findOne(nums []int) int { for i := 0; i < len(nums); i++ { // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) if nums[i] == 1 { return i } } return -1 } ================================================ FILE: ru/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go ================================================ // File: worst_best_time_complexity_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "fmt" "testing" ) func TestWorstBestTimeComplexity(t *testing.T) { for i := 0; i < 10; i++ { n := 100 nums := randomNumbers(n) index := findOne(nums) fmt.Println("\nМассив [1, 2, ..., n] после перемешивания =", nums) fmt.Println("Индекс числа 1 =", index) } } ================================================ FILE: ru/codes/go/chapter_divide_and_conquer/binary_search_recur.go ================================================ // File: binary_search_recur.go // Created Time: 2023-07-19 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer /* Бинарный поиск: задача f(i, j) */ func dfs(nums []int, target, i, j int) int { // Если интервал пуст, это означает отсутствие целевого элемента, вернуть -1 if i > j { return -1 } // Вычислить средний индекс m := i + ((j - i) >> 1) // Сравнить середину и целевой элемент if nums[m] < target { // Если меньше, рекурсивно обрабатывать правую половину массива // Рекурсивная подзадача f(m+1, j) return dfs(nums, target, m+1, j) } else if nums[m] > target { // Если больше, рекурсивно обработать левую половину массива // Рекурсивная подзадача f(i, m-1) return dfs(nums, target, i, m-1) } else { // Целевой элемент найден, вернуть его индекс return m } } /* Бинарный поиск */ func binarySearch(nums []int, target int) int { n := len(nums) return dfs(nums, target, 0, n-1) } ================================================ FILE: ru/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go ================================================ // File: binary_search_recur_test.go // Created Time: 2023-07-19 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "fmt" "testing" ) func TestBinarySearch(t *testing.T) { nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} target := 6 noTarget := 99 targetIndex := binarySearch(nums, target) fmt.Println("Индекс целевого элемента 6 = ", targetIndex) noTargetIndex := binarySearch(nums, noTarget) fmt.Println("Индекс отсутствующего целевого элемента =", noTargetIndex) } ================================================ FILE: ru/codes/go/chapter_divide_and_conquer/build_tree.go ================================================ // File: build_tree.go // Created Time: 2023-07-20 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import . "github.com/krahets/hello-algo/pkg" /* Построить двоичное дерево: разделяй и властвуй */ func dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode { // Завершить при пустом диапазоне поддерева if r-l < 0 { return nil } // Инициализировать корневой узел root := NewTreeNode(preorder[i]) // Найти m, чтобы разделить левое и правое поддеревья m := inorderMap[preorder[i]] // Подзадача: построить левое поддерево root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1) // Подзадача: построить правое поддерево root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r) // Вернуть корневой узел return root } /* Построить двоичное дерево */ func buildTree(preorder, inorder []int) *TreeNode { // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам inorderMap := make(map[int]int, len(inorder)) for i := 0; i < len(inorder); i++ { inorderMap[inorder[i]] = i } root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1) return root } ================================================ FILE: ru/codes/go/chapter_divide_and_conquer/build_tree_test.go ================================================ // File: build_tree_test.go // Created Time: 2023-07-20 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestBuildTree(t *testing.T) { preorder := []int{3, 9, 2, 1, 7} inorder := []int{9, 3, 1, 2, 7} fmt.Print("Предварительный обход = ") PrintSlice(preorder) fmt.Print("Симметричный обход = ") PrintSlice(inorder) root := buildTree(preorder, inorder) fmt.Println("Построенное двоичное дерево:") PrintTree(root) } ================================================ FILE: ru/codes/go/chapter_divide_and_conquer/hanota.go ================================================ // File: hanota.go // Created Time: 2023-07-21 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import "container/list" /* Переместить один диск */ func move(src, tar *list.List) { // Снять диск с вершины src pan := src.Back() // Положить диск на вершину tar tar.PushBack(pan.Value) // Убрать верхний диск из src src.Remove(pan) } /* Решить задачу Ханойской башни f(i) */ func dfsHanota(i int, src, buf, tar *list.List) { // Если в src остался только один диск, сразу переместить его в tar if i == 1 { move(src, tar) return } // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar dfsHanota(i-1, src, tar, buf) // Подзадача f(1): переместить оставшийся один диск из src в tar move(src, tar) // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src dfsHanota(i-1, buf, src, tar) } /* Решить задачу Ханойской башни */ func solveHanota(A, B, C *list.List) { n := A.Len() // Переместить верхние n дисков из A в C с помощью B dfsHanota(n, A, B, C) } ================================================ FILE: ru/codes/go/chapter_divide_and_conquer/hanota_test.go ================================================ // File: hanota_test.go // Created Time: 2023-07-21 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHanota(t *testing.T) { // Хвост списка соответствует вершине столбца A := list.New() for i := 5; i > 0; i-- { A.PushBack(i) } B := list.New() C := list.New() fmt.Println("Исходное состояние:") fmt.Print("A = ") PrintList(A) fmt.Print("B = ") PrintList(B) fmt.Print("C = ") PrintList(C) solveHanota(A, B, C) fmt.Println("После завершения перемещения дисков:") fmt.Print("A = ") PrintList(A) fmt.Print("B = ") PrintList(B) fmt.Print("C = ") PrintList(C) } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go ================================================ // File: climbing_stairs_backtrack.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Бэктрекинг */ func backtrack(choices []int, state, n int, res []int) { // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 if state == n { res[0] = res[0] + 1 } // Перебор всех вариантов выбора for _, choice := range choices { // Отсечение: нельзя выходить за n-ю ступень if state+choice > n { continue } // Попытка: сделать выбор и обновить состояние backtrack(choices, state+choice, n, res) // Откат } } /* Подъем по лестнице: бэктрекинг */ func climbingStairsBacktrack(n int) int { // Можно подняться на 1 или 2 ступени choices := []int{1, 2} // Начать подъем с 0-й ступени state := 0 res := make([]int, 1) // Использовать res[0] для хранения числа решений res[0] = 0 backtrack(choices, state, n, res) return res[0] } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go ================================================ // File: climbing_stairs_constraint_dp.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Подъем по лестнице с ограничениями: динамическое программирование */ func climbingStairsConstraintDP(n int) int { if n == 1 || n == 2 { return 1 } // Инициализация таблицы dp для хранения решений подзадач dp := make([][3]int, n+1) // Начальное состояние: заранее задать решения наименьших подзадач dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // Переход состояний: постепенное решение больших подзадач через меньшие for i := 3; i <= n; i++ { dp[i][1] = dp[i-1][2] dp[i][2] = dp[i-2][1] + dp[i-2][2] } return dp[n][1] + dp[n][2] } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go ================================================ // File: climbing_stairs_dfs.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Поиск */ func dfs(i int) int { // dp[1] и dp[2] уже известны, вернуть их if i == 1 || i == 2 { return i } // dp[i] = dp[i-1] + dp[i-2] count := dfs(i-1) + dfs(i-2) return count } /* Подъем по лестнице: поиск */ func climbingStairsDFS(n int) int { return dfs(n) } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go ================================================ // File: climbing_stairs_dfs_mem.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Поиск с мемоизацией */ func dfsMem(i int, mem []int) int { // dp[1] и dp[2] уже известны, вернуть их if i == 1 || i == 2 { return i } // Если запись dp[i] существует, сразу вернуть ее if mem[i] != -1 { return mem[i] } // dp[i] = dp[i-1] + dp[i-2] count := dfsMem(i-1, mem) + dfsMem(i-2, mem) // Сохранить dp[i] mem[i] = count return count } /* Подъем по лестнице: поиск с мемоизацией */ func climbingStairsDFSMem(n int) int { // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи mem := make([]int, n+1) for i := range mem { mem[i] = -1 } return dfsMem(n, mem) } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go ================================================ // File: climbing_stairs_dp.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Подъем по лестнице: динамическое программирование */ func climbingStairsDP(n int) int { if n == 1 || n == 2 { return n } // Инициализация таблицы dp для хранения решений подзадач dp := make([]int, n+1) // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = 1 dp[2] = 2 // Переход состояний: постепенное решение больших подзадач через меньшие for i := 3; i <= n; i++ { dp[i] = dp[i-1] + dp[i-2] } return dp[n] } /* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ func climbingStairsDPComp(n int) int { if n == 1 || n == 2 { return n } a, b := 1, 2 // Переход состояний: постепенное решение больших подзадач через меньшие for i := 3; i <= n; i++ { a, b = b, a+b } return b } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/climbing_stairs_test.go ================================================ // File: climbing_stairs_test.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestClimbingStairsBacktrack(t *testing.T) { n := 9 res := climbingStairsBacktrack(n) fmt.Printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res) } func TestClimbingStairsDFS(t *testing.T) { n := 9 res := climbingStairsDFS(n) fmt.Printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res) } func TestClimbingStairsDFSMem(t *testing.T) { n := 9 res := climbingStairsDFSMem(n) fmt.Printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res) } func TestClimbingStairsDP(t *testing.T) { n := 9 res := climbingStairsDP(n) fmt.Printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res) } func TestClimbingStairsDPComp(t *testing.T) { n := 9 res := climbingStairsDPComp(n) fmt.Printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res) } func TestClimbingStairsConstraintDP(t *testing.T) { n := 9 res := climbingStairsConstraintDP(n) fmt.Printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res) } func TestMinCostClimbingStairsDPComp(t *testing.T) { cost := []int{0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1} fmt.Printf("Список стоимостей ступеней = %v\n", cost) res := minCostClimbingStairsDP(cost) fmt.Printf("Минимальная стоимость подъема по лестнице = %d\n", res) res = minCostClimbingStairsDPComp(cost) fmt.Printf("Минимальная стоимость подъема по лестнице = %d\n", res) } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/coin_change.go ================================================ // File: coin_change.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* Размен монет: динамическое программирование */ func coinChangeDP(coins []int, amt int) int { n := len(coins) max := amt + 1 // Инициализация таблицы dp dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, amt+1) } // Переход состояний: первая строка и первый столбец for a := 1; a <= amt; a++ { dp[0][a] = max } // Переход состояний: остальные строки и столбцы for i := 1; i <= n; i++ { for a := 1; a <= amt; a++ { if coins[i-1] > a { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i-1][a] } else { // Меньшее из двух решений: не брать или взять монету i dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1))) } } } if dp[n][amt] != max { return dp[n][amt] } return -1 } /* Размен монет: динамическое программирование */ func coinChangeDPComp(coins []int, amt int) int { n := len(coins) max := amt + 1 // Инициализация таблицы dp dp := make([]int, amt+1) for i := 1; i <= amt; i++ { dp[i] = max } // Переход состояний for i := 1; i <= n; i++ { // Прямой обход for a := 1; a <= amt; a++ { if coins[i-1] > a { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a] } else { // Меньшее из двух решений: не брать или взять монету i dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1))) } } } if dp[amt] != max { return dp[amt] } return -1 } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/coin_change_ii.go ================================================ // File: coin_change_ii.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Размен монет II: динамическое программирование */ func coinChangeIIDP(coins []int, amt int) int { n := len(coins) // Инициализация таблицы dp dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, amt+1) } // Инициализация первого столбца for i := 0; i <= n; i++ { dp[i][0] = 1 } // Переход состояний: остальные строки и столбцы for i := 1; i <= n; i++ { for a := 1; a <= amt; a++ { if coins[i-1] > a { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i-1][a] } else { // Сумма двух решений: не брать или взять монету i dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]] } } } return dp[n][amt] } /* Размен монет II: динамическое программирование с оптимизацией памяти */ func coinChangeIIDPComp(coins []int, amt int) int { n := len(coins) // Инициализация таблицы dp dp := make([]int, amt+1) dp[0] = 1 // Переход состояний for i := 1; i <= n; i++ { // Прямой обход for a := 1; a <= amt; a++ { if coins[i-1] > a { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a] } else { // Сумма двух решений: не брать или взять монету i dp[a] = dp[a] + dp[a-coins[i-1]] } } } return dp[amt] } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/coin_change_test.go ================================================ // File: coin_change_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestCoinChange(t *testing.T) { coins := []int{1, 2, 5} amt := 4 // Динамическое программирование res := coinChangeDP(coins, amt) fmt.Printf("Минимальное число монет для набора целевой суммы = %d\n", res) // Динамическое программирование с оптимизацией памяти res = coinChangeDPComp(coins, amt) fmt.Printf("Минимальное число монет для набора целевой суммы = %d\n", res) } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/edit_distance.go ================================================ // File: edit_distance.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Редакционное расстояние: полный перебор */ func editDistanceDFS(s string, t string, i int, j int) int { // Если s и t пусты, вернуть 0 if i == 0 && j == 0 { return 0 } // Если s пусто, вернуть длину t if i == 0 { return j } // Если t пусто, вернуть длину s if j == 0 { return i } // Если два символа равны, сразу пропустить их if s[i-1] == t[j-1] { return editDistanceDFS(s, t, i-1, j-1) } // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 insert := editDistanceDFS(s, t, i, j-1) deleted := editDistanceDFS(s, t, i-1, j) replace := editDistanceDFS(s, t, i-1, j-1) // Вернуть минимальное число шагов редактирования return MinInt(MinInt(insert, deleted), replace) + 1 } /* Редакционное расстояние: поиск с мемоизацией */ func editDistanceDFSMem(s string, t string, mem [][]int, i int, j int) int { // Если s и t пусты, вернуть 0 if i == 0 && j == 0 { return 0 } // Если s пусто, вернуть длину t if i == 0 { return j } // Если t пусто, вернуть длину s if j == 0 { return i } // Если запись уже есть, сразу вернуть ее if mem[i][j] != -1 { return mem[i][j] } // Если два символа равны, сразу пропустить их if s[i-1] == t[j-1] { return editDistanceDFSMem(s, t, mem, i-1, j-1) } // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 insert := editDistanceDFSMem(s, t, mem, i, j-1) deleted := editDistanceDFSMem(s, t, mem, i-1, j) replace := editDistanceDFSMem(s, t, mem, i-1, j-1) // Сохранить и вернуть минимальное число шагов редактирования mem[i][j] = MinInt(MinInt(insert, deleted), replace) + 1 return mem[i][j] } /* Редакционное расстояние: динамическое программирование */ func editDistanceDP(s string, t string) int { n := len(s) m := len(t) dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, m+1) } // Переход состояний: первая строка и первый столбец for i := 1; i <= n; i++ { dp[i][0] = i } for j := 1; j <= m; j++ { dp[0][j] = j } // Переход состояний: остальные строки и столбцы for i := 1; i <= n; i++ { for j := 1; j <= m; j++ { if s[i-1] == t[j-1] { // Если два символа равны, сразу пропустить их dp[i][j] = dp[i-1][j-1] } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1 } } } return dp[n][m] } /* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ func editDistanceDPComp(s string, t string) int { n := len(s) m := len(t) dp := make([]int, m+1) // Переход состояний: первая строка for j := 1; j <= m; j++ { dp[j] = j } // Переход состояний: остальные строки for i := 1; i <= n; i++ { // Переход состояний: первый столбец leftUp := dp[0] // Временно сохранить dp[i-1, j-1] dp[0] = i // Переход состояний: остальные столбцы for j := 1; j <= m; j++ { temp := dp[j] if s[i-1] == t[j-1] { // Если два символа равны, сразу пропустить их dp[j] = leftUp } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1 } leftUp = temp // Обновить до значения dp[i-1, j-1] для следующей итерации } } return dp[m] } func MinInt(a, b int) int { if a < b { return a } return b } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/edit_distance_test.go ================================================ // File: edit_distance_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestEditDistanceDFS(test *testing.T) { s := "bag" t := "pack" n := len(s) m := len(t) // Полный перебор res := editDistanceDFS(s, t, n, m) fmt.Printf("Чтобы преобразовать %s в %s, нужно минимум %d шагов\n", s, t, res) // Поиск с мемоизацией mem := make([][]int, n+1) for i := 0; i <= n; i++ { mem[i] = make([]int, m+1) for j := 0; j <= m; j++ { mem[i][j] = -1 } } res = editDistanceDFSMem(s, t, mem, n, m) fmt.Printf("Чтобы преобразовать %s в %s, нужно минимум %d шагов\n", s, t, res) // Динамическое программирование res = editDistanceDP(s, t) fmt.Printf("Чтобы преобразовать %s в %s, нужно минимум %d шагов\n", s, t, res) // Динамическое программирование с оптимизацией памяти res = editDistanceDPComp(s, t) fmt.Printf("Чтобы преобразовать %s в %s, нужно минимум %d шагов\n", s, t, res) } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/knapsack.go ================================================ // File: knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* Рюкзак 0-1: полный перебор */ func knapsackDFS(wgt, val []int, i, c int) int { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if i == 0 || c == 0 { return 0 } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if wgt[i-1] > c { return knapsackDFS(wgt, val, i-1, c) } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут no := knapsackDFS(wgt, val, i-1, c) yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1] // Вернуть вариант с большей стоимостью из двух возможных return int(math.Max(float64(no), float64(yes))) } /* Рюкзак 0-1: поиск с мемоизацией */ func knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if i == 0 || c == 0 { return 0 } // Если запись уже есть, вернуть сразу if mem[i][c] != -1 { return mem[i][c] } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if wgt[i-1] > c { return knapsackDFSMem(wgt, val, mem, i-1, c) } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут no := knapsackDFSMem(wgt, val, mem, i-1, c) yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1] // Вернуть вариант с большей стоимостью из двух возможных mem[i][c] = int(math.Max(float64(no), float64(yes))) return mem[i][c] } /* Рюкзак 0-1: динамическое программирование */ func knapsackDP(wgt, val []int, cap int) int { n := len(wgt) // Инициализация таблицы dp dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, cap+1) } // Переход состояний for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i-1][c] } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1]))) } } } return dp[n][cap] } /* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ func knapsackDPComp(wgt, val []int, cap int) int { n := len(wgt) // Инициализация таблицы dp dp := make([]int, cap+1) // Переход состояний for i := 1; i <= n; i++ { // Обход в обратном порядке for c := cap; c >= 1; c-- { if wgt[i-1] <= c { // Большее из двух решений: не брать или взять предмет i dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) } } } return dp[cap] } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/knapsack_test.go ================================================ // File: knapsack_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestKnapsack(t *testing.T) { wgt := []int{10, 20, 30, 40, 50} val := []int{50, 120, 150, 210, 240} c := 50 n := len(wgt) // Полный перебор res := knapsackDFS(wgt, val, n, c) fmt.Printf("Максимальная стоимость предметов без превышения вместимости рюкзака = %d\n", res) // Поиск с мемоизацией mem := make([][]int, n+1) for i := 0; i <= n; i++ { mem[i] = make([]int, c+1) for j := 0; j <= c; j++ { mem[i][j] = -1 } } res = knapsackDFSMem(wgt, val, mem, n, c) fmt.Printf("Максимальная стоимость предметов без превышения вместимости рюкзака = %d\n", res) // Динамическое программирование res = knapsackDP(wgt, val, c) fmt.Printf("Максимальная стоимость предметов без превышения вместимости рюкзака = %d\n", res) // Динамическое программирование с оптимизацией памяти res = knapsackDPComp(wgt, val, c) fmt.Printf("Максимальная стоимость предметов без превышения вместимости рюкзака = %d\n", res) } func TestUnboundedKnapsack(t *testing.T) { wgt := []int{1, 2, 3} val := []int{5, 11, 15} c := 4 // Динамическое программирование res := unboundedKnapsackDP(wgt, val, c) fmt.Printf("Максимальная стоимость предметов без превышения вместимости рюкзака = %d\n", res) // Динамическое программирование с оптимизацией памяти res = unboundedKnapsackDPComp(wgt, val, c) fmt.Printf("Максимальная стоимость предметов без превышения вместимости рюкзака = %d\n", res) } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go ================================================ // File: min_cost_climbing_stairs_dp.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* Минимальная стоимость подъема по лестнице: динамическое программирование */ func minCostClimbingStairsDP(cost []int) int { n := len(cost) - 1 if n == 1 || n == 2 { return cost[n] } min := func(a, b int) int { if a < b { return a } return b } // Инициализация таблицы dp для хранения решений подзадач dp := make([]int, n+1) // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = cost[1] dp[2] = cost[2] // Переход состояний: постепенное решение больших подзадач через меньшие for i := 3; i <= n; i++ { dp[i] = min(dp[i-1], dp[i-2]) + cost[i] } return dp[n] } /* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ func minCostClimbingStairsDPComp(cost []int) int { n := len(cost) - 1 if n == 1 || n == 2 { return cost[n] } min := func(a, b int) int { if a < b { return a } return b } // Начальное состояние: заранее задать решения наименьших подзадач a, b := cost[1], cost[2] // Переход состояний: постепенное решение больших подзадач через меньшие for i := 3; i <= n; i++ { tmp := b b = min(a, tmp) + cost[i] a = tmp } return b } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/min_path_sum.go ================================================ // File: min_path_sum.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* Минимальная сумма пути: полный перебор */ func minPathSumDFS(grid [][]int, i, j int) int { // Если это верхняя левая ячейка, завершить поиск if i == 0 && j == 0 { return grid[0][0] } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if i < 0 || j < 0 { return math.MaxInt } // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) up := minPathSumDFS(grid, i-1, j) left := minPathSumDFS(grid, i, j-1) // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) return int(math.Min(float64(left), float64(up))) + grid[i][j] } /* Минимальная сумма пути: поиск с мемоизацией */ func minPathSumDFSMem(grid, mem [][]int, i, j int) int { // Если это верхняя левая ячейка, завершить поиск if i == 0 && j == 0 { return grid[0][0] } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if i < 0 || j < 0 { return math.MaxInt } // Если запись уже есть, вернуть сразу if mem[i][j] != -1 { return mem[i][j] } // Минимальная стоимость пути для левой и верхней ячеек up := minPathSumDFSMem(grid, mem, i-1, j) left := minPathSumDFSMem(grid, mem, i, j-1) // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j] return mem[i][j] } /* Минимальная сумма пути: динамическое программирование */ func minPathSumDP(grid [][]int) int { n, m := len(grid), len(grid[0]) // Инициализация таблицы dp dp := make([][]int, n) for i := 0; i < n; i++ { dp[i] = make([]int, m) } dp[0][0] = grid[0][0] // Переход состояний: первая строка for j := 1; j < m; j++ { dp[0][j] = dp[0][j-1] + grid[0][j] } // Переход состояний: первый столбец for i := 1; i < n; i++ { dp[i][0] = dp[i-1][0] + grid[i][0] } // Переход состояний: остальные строки и столбцы for i := 1; i < n; i++ { for j := 1; j < m; j++ { dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j] } } return dp[n-1][m-1] } /* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ func minPathSumDPComp(grid [][]int) int { n, m := len(grid), len(grid[0]) // Инициализация таблицы dp dp := make([]int, m) // Переход состояний: первая строка dp[0] = grid[0][0] for j := 1; j < m; j++ { dp[j] = dp[j-1] + grid[0][j] } // Переход состояний: остальные строки и столбцы for i := 1; i < n; i++ { // Переход состояний: первый столбец dp[0] = dp[0] + grid[i][0] // Переход состояний: остальные столбцы for j := 1; j < m; j++ { dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j] } } return dp[m-1] } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/min_path_sum_test.go ================================================ // File: min_path_sum_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestMinPathSum(t *testing.T) { grid := [][]int{ {1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}, } n, m := len(grid), len(grid[0]) // Полный перебор res := minPathSumDFS(grid, n-1, m-1) fmt.Printf("Минимальная сумма пути из левого верхнего угла в правый нижний = %d\n", res) // Поиск с мемоизацией mem := make([][]int, n) for i := 0; i < n; i++ { mem[i] = make([]int, m) for j := 0; j < m; j++ { mem[i][j] = -1 } } res = minPathSumDFSMem(grid, mem, n-1, m-1) fmt.Printf("Минимальная сумма пути из левого верхнего угла в правый нижний = %d\n", res) // Динамическое программирование res = minPathSumDP(grid) fmt.Printf("Минимальная сумма пути из левого верхнего угла в правый нижний = %d\n", res) // Динамическое программирование с оптимизацией памяти res = minPathSumDPComp(grid) fmt.Printf("Минимальная сумма пути из левого верхнего угла в правый нижний = %d\n", res) } ================================================ FILE: ru/codes/go/chapter_dynamic_programming/unbounded_knapsack.go ================================================ // File: unbounded_knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* Полный рюкзак: динамическое программирование */ func unboundedKnapsackDP(wgt, val []int, cap int) int { n := len(wgt) // Инициализация таблицы dp dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, cap+1) } // Переход состояний for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i-1][c] } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1]))) } } } return dp[n][cap] } /* Полный рюкзак: динамическое программирование с оптимизацией памяти */ func unboundedKnapsackDPComp(wgt, val []int, cap int) int { n := len(wgt) // Инициализация таблицы dp dp := make([]int, cap+1) // Переход состояний for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // Если вместимость рюкзака превышена, предмет i не выбирать dp[c] = dp[c] } else { // Большее из двух решений: не брать или взять предмет i dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) } } } return dp[cap] } ================================================ FILE: ru/codes/go/chapter_graph/graph_adjacency_list.go ================================================ // File: graph_adjacency_list.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "strconv" "strings" . "github.com/krahets/hello-algo/pkg" ) /* Класс неориентированного графа на основе списка смежности */ type graphAdjList struct { // Список смежности, где key — вершина, а value — все смежные ей вершины adjList map[Vertex][]Vertex } /* Конструктор */ func newGraphAdjList(edges [][]Vertex) *graphAdjList { g := &graphAdjList{ adjList: make(map[Vertex][]Vertex), } // Добавить все вершины и ребра for _, edge := range edges { g.addVertex(edge[0]) g.addVertex(edge[1]) g.addEdge(edge[0], edge[1]) } return g } /* Получить число вершин */ func (g *graphAdjList) size() int { return len(g.adjList) } /* Добавление ребра */ func (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) { _, ok1 := g.adjList[vet1] _, ok2 := g.adjList[vet2] if !ok1 || !ok2 || vet1 == vet2 { panic("error") } // Добавить ребро vet1 - vet2, добавив анонимную struct{} g.adjList[vet1] = append(g.adjList[vet1], vet2) g.adjList[vet2] = append(g.adjList[vet2], vet1) } /* Удаление ребра */ func (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) { _, ok1 := g.adjList[vet1] _, ok2 := g.adjList[vet2] if !ok1 || !ok2 || vet1 == vet2 { panic("error") } // Удалить ребро vet1 - vet2 g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2) g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1) } /* Добавление вершины */ func (g *graphAdjList) addVertex(vet Vertex) { _, ok := g.adjList[vet] if ok { return } // Добавить новый список в список смежности g.adjList[vet] = make([]Vertex, 0) } /* Удаление вершины */ func (g *graphAdjList) removeVertex(vet Vertex) { _, ok := g.adjList[vet] if !ok { panic("error") } // Удалить из списка смежности список, соответствующий вершине vet delete(g.adjList, vet) // Обойти списки других вершин и удалить все ребра, содержащие vet for v, list := range g.adjList { g.adjList[v] = DeleteSliceElms(list, vet) } } /* Вывести список смежности */ func (g *graphAdjList) print() { var builder strings.Builder fmt.Printf("Список смежности = \n") for k, v := range g.adjList { builder.WriteString("\t\t" + strconv.Itoa(k.Val) + ": ") for _, vet := range v { builder.WriteString(strconv.Itoa(vet.Val) + " ") } fmt.Println(builder.String()) builder.Reset() } } ================================================ FILE: ru/codes/go/chapter_graph/graph_adjacency_list_test.go ================================================ // File: graph_adjacency_list_test.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphAdjList(t *testing.T) { /* Инициализация неориентированного графа */ v := ValsToVets([]int{1, 3, 2, 5, 4}) edges := [][]Vertex{{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}} graph := newGraphAdjList(edges) fmt.Println("Граф после инициализации:") graph.print() /* Добавление ребра */ // Вершины 1 и 2 соответствуют v[0] и v[2] graph.addEdge(v[0], v[2]) fmt.Println("\nГраф после добавления ребра 1-2") graph.print() /* Удаление ребра */ // Вершины 1 и 3 соответствуют v[0] и v[1] graph.removeEdge(v[0], v[1]) fmt.Println("\nГраф после удаления ребра 1-3") graph.print() /* Добавление вершины */ v5 := NewVertex(6) graph.addVertex(v5) fmt.Println("\nГраф после добавления вершины 6") graph.print() /* Удаление вершины */ // Вершина 3 соответствует v[1] graph.removeVertex(v[1]) fmt.Println("\nГраф после удаления вершины 3") graph.print() } ================================================ FILE: ru/codes/go/chapter_graph/graph_adjacency_matrix.go ================================================ // File: graph_adjacency_matrix.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import "fmt" /* Класс неориентированного графа на основе матрицы смежности */ type graphAdjMat struct { // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» vertices []int // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» adjMat [][]int } /* Конструктор */ func newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat { // Добавление вершины n := len(vertices) adjMat := make([][]int, n) for i := range adjMat { adjMat[i] = make([]int, n) } // Инициализировать граф g := &graphAdjMat{ vertices: vertices, adjMat: adjMat, } // Добавить ребра // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices for i := range edges { g.addEdge(edges[i][0], edges[i][1]) } return g } /* Получить число вершин */ func (g *graphAdjMat) size() int { return len(g.vertices) } /* Добавление вершины */ func (g *graphAdjMat) addVertex(val int) { n := g.size() // Добавить значение новой вершины в список вершин g.vertices = append(g.vertices, val) // Добавить строку в матрицу смежности newRow := make([]int, n) g.adjMat = append(g.adjMat, newRow) // Добавить столбец в матрицу смежности for i := range g.adjMat { g.adjMat[i] = append(g.adjMat[i], 0) } } /* Удаление вершины */ func (g *graphAdjMat) removeVertex(index int) { if index >= g.size() { return } // Удалить вершину с индексом index из списка вершин g.vertices = append(g.vertices[:index], g.vertices[index+1:]...) // Удалить строку с индексом index из матрицы смежности g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...) // Удалить столбец с индексом index из матрицы смежности for i := range g.adjMat { g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...) } } /* Добавление ребра */ // Параметры i и j соответствуют индексам элементов vertices func (g *graphAdjMat) addEdge(i, j int) { // Обработка выхода индекса за границы и случая равенства if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { fmt.Errorf("%s", "Index Out Of Bounds Exception") } // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) g.adjMat[i][j] = 1 g.adjMat[j][i] = 1 } /* Удаление ребра */ // Параметры i и j соответствуют индексам элементов vertices func (g *graphAdjMat) removeEdge(i, j int) { // Обработка выхода индекса за границы и случая равенства if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { fmt.Errorf("%s", "Index Out Of Bounds Exception") } g.adjMat[i][j] = 0 g.adjMat[j][i] = 0 } /* Вывести матрицу смежности */ func (g *graphAdjMat) print() { fmt.Printf("\tСписок вершин = %v\n", g.vertices) fmt.Printf("\tМатрица смежности = \n") for i := range g.adjMat { fmt.Printf("\t\t\t%v\n", g.adjMat[i]) } } ================================================ FILE: ru/codes/go/chapter_graph/graph_adjacency_matrix_test.go ================================================ // File: graph_adjacency_matrix_test.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" ) func TestGraphAdjMat(t *testing.T) { /* Инициализация неориентированного графа */ // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices vertices := []int{1, 3, 2, 5, 4} edges := [][]int{{0, 1}, {1, 2}, {2, 3}, {0, 3}, {2, 4}, {3, 4}} graph := newGraphAdjMat(vertices, edges) fmt.Println("Граф после инициализации:") graph.print() /* Добавление ребра */ // Индексы вершин 1 и 2 равны 0 и 2 соответственно graph.addEdge(0, 2) fmt.Println("Граф после добавления ребра 1-2") graph.print() /* Удаление ребра */ // Индексы вершин 1 и 3 равны 0 и 1 соответственно graph.removeEdge(0, 1) fmt.Println("Граф после удаления ребра 1-3") graph.print() /* Добавление вершины */ graph.addVertex(6) fmt.Println("Граф после добавления вершины 6") graph.print() /* Удаление вершины */ // Индекс вершины 3 равен 1 graph.removeVertex(1) fmt.Println("Граф после удаления вершины 3") graph.print() } ================================================ FILE: ru/codes/go/chapter_graph/graph_bfs.go ================================================ // File: graph_bfs.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( . "github.com/krahets/hello-algo/pkg" ) /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины func graphBFS(g *graphAdjList, startVet Vertex) []Vertex { // Последовательность обхода вершин res := make([]Vertex, 0) // Хеш-множество для хранения уже посещенных вершин visited := make(map[Vertex]struct{}) visited[startVet] = struct{}{} // Очередь используется для реализации BFS, срез используется для имитации очереди queue := make([]Vertex, 0) queue = append(queue, startVet) // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины for len(queue) > 0 { // Извлечь головную вершину из очереди vet := queue[0] queue = queue[1:] // Отметить посещенную вершину res = append(res, vet) // Обойти все смежные вершины данной вершины for _, adjVet := range g.adjList[vet] { _, isExist := visited[adjVet] // Помещать в очередь только непосещенные вершины if !isExist { queue = append(queue, adjVet) visited[adjVet] = struct{}{} } } } // Вернуть последовательность обхода вершин return res } ================================================ FILE: ru/codes/go/chapter_graph/graph_bfs_test.go ================================================ // File: graph_bfs_test.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphBFS(t *testing.T) { /* Инициализация неориентированного графа */ vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) edges := [][]Vertex{ {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[1], vets[4]}, {vets[2], vets[5]}, {vets[3], vets[4]}, {vets[3], vets[6]}, {vets[4], vets[5]}, {vets[4], vets[7]}, {vets[5], vets[8]}, {vets[6], vets[7]}, {vets[7], vets[8]}} graph := newGraphAdjList(edges) fmt.Println("Граф после инициализации:") graph.print() /* Обход в ширину */ res := graphBFS(graph, vets[0]) fmt.Println("Последовательность вершин при обходе в ширину (BFS):") PrintSlice(VetsToVals(res)) } ================================================ FILE: ru/codes/go/chapter_graph/graph_dfs.go ================================================ // File: graph_dfs.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( . "github.com/krahets/hello-algo/pkg" ) /* Вспомогательная функция обхода в глубину */ func dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) { // Операция append возвращает новую ссылку, поэтому исходную ссылку нужно заново присвоить новому срезу *res = append(*res, vet) visited[vet] = struct{}{} // Обойти все смежные вершины данной вершины for _, adjVet := range g.adjList[vet] { _, isExist := visited[adjVet] // Рекурсивно обходить смежные вершины if !isExist { dfs(g, visited, res, adjVet) } } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины func graphDFS(g *graphAdjList, startVet Vertex) []Vertex { // Последовательность обхода вершин res := make([]Vertex, 0) // Хеш-множество для хранения уже посещенных вершин visited := make(map[Vertex]struct{}) dfs(g, visited, &res, startVet) // Вернуть последовательность обхода вершин return res } ================================================ FILE: ru/codes/go/chapter_graph/graph_dfs_test.go ================================================ // File: graph_dfs_test.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphDFS(t *testing.T) { /* Инициализация неориентированного графа */ vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6}) edges := [][]Vertex{ {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[2], vets[5]}, {vets[4], vets[5]}, {vets[5], vets[6]}} graph := newGraphAdjList(edges) fmt.Println("Граф после инициализации:") graph.print() /* Обход в глубину */ res := graphDFS(graph, vets[0]) fmt.Println("Последовательность вершин при обходе в глубину (DFS):") PrintSlice(VetsToVals(res)) } ================================================ FILE: ru/codes/go/chapter_greedy/coin_change_greedy.go ================================================ // File: coin_change_greedy.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy /* Размен монет: жадный алгоритм */ func coinChangeGreedy(coins []int, amt int) int { // Предположить, что список coins упорядочен i := len(coins) - 1 count := 0 // Циклически выполнять жадный выбор, пока не останется суммы for amt > 0 { // Найти монету, которая меньше остатка суммы и наиболее к нему близка for i > 0 && coins[i] > amt { i-- } // Выбрать coins[i] amt -= coins[i] count++ } // Если допустимое решение не найдено, вернуть -1 if amt != 0 { return -1 } return count } ================================================ FILE: ru/codes/go/chapter_greedy/coin_change_greedy_test.go ================================================ // File: coin_change_greedy_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestCoinChangeGreedy(t *testing.T) { // Жадный подход: гарантирует нахождение глобально оптимального решения coins := []int{1, 5, 10, 20, 50, 100} amt := 186 res := coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("Минимальное число монет для набора суммы %d = %d\n", amt, res) // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = []int{1, 20, 50} amt = 60 res = coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("Минимальное число монет для набора суммы %d = %d\n", amt, res) fmt.Println("На самом деле минимум равен 3: 20 + 20 + 20") // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = []int{1, 49, 50} amt = 98 res = coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("Минимальное число монет для набора суммы %d = %d\n", amt, res) fmt.Println("На самом деле минимум равен 2: 49 + 49") } ================================================ FILE: ru/codes/go/chapter_greedy/fractional_knapsack.go ================================================ // File: fractional_knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "sort" /* Предмет */ type Item struct { w int // Вес предмета v int // Стоимость предмета } /* Дробный рюкзак: жадный алгоритм */ func fractionalKnapsack(wgt []int, val []int, cap int) float64 { // Создать список предметов с двумя свойствами: вес и стоимость items := make([]Item, len(wgt)) for i := 0; i < len(wgt); i++ { items[i] = Item{wgt[i], val[i]} } // Отсортировать по удельной стоимости item.v / item.w в порядке убывания sort.Slice(items, func(i, j int) bool { return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w) }) // Циклический жадный выбор res := 0.0 for _, item := range items { if item.w <= cap { // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком res += float64(item.v) cap -= item.w } else { // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета res += float64(item.v) / float64(item.w) * float64(cap) // Свободной вместимости больше не осталось, поэтому выйти из цикла break } } return res } ================================================ FILE: ru/codes/go/chapter_greedy/fractional_knapsack_test.go ================================================ // File: fractional_knapsack_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestFractionalKnapsack(t *testing.T) { wgt := []int{10, 20, 30, 40, 50} val := []int{50, 120, 150, 210, 240} capacity := 50 // Жадный алгоритм res := fractionalKnapsack(wgt, val, capacity) fmt.Println("Максимальная стоимость предметов без превышения вместимости рюкзака =", res) } ================================================ FILE: ru/codes/go/chapter_greedy/max_capacity.go ================================================ // File: max_capacity.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "math" /* Максимальная вместимость: жадный алгоритм */ func maxCapacity(ht []int) int { // Инициализировать i и j так, чтобы они располагались по двум концам массива i, j := 0, len(ht)-1 // Начальная максимальная вместимость равна 0 res := 0 // Выполнять жадный выбор в цикле, пока две доски не встретятся for i < j { // Обновить максимальную вместимость capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i) res = int(math.Max(float64(res), float64(capacity))) // Сдвигать внутрь более короткую сторону if ht[i] < ht[j] { i++ } else { j-- } } return res } ================================================ FILE: ru/codes/go/chapter_greedy/max_capacity_test.go ================================================ // File: max_capacity_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestMaxCapacity(t *testing.T) { ht := []int{3, 8, 5, 2, 7, 7, 3, 4} // Жадный алгоритм res := maxCapacity(ht) fmt.Println("Максимальная вместимость =", res) } ================================================ FILE: ru/codes/go/chapter_greedy/max_product_cutting.go ================================================ // File: max_product_cutting.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "math" /* Максимальное произведение разрезания: жадный алгоритм */ func maxProductCutting(n int) int { // Когда n <= 3, обязательно нужно выделить одну 1 if n <= 3 { return 1 * (n - 1) } // Жадно выделить множители 3, где a — число троек, а b — остаток a := n / 3 b := n % 3 if b == 1 { // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 return int(math.Pow(3, float64(a-1))) * 2 * 2 } if b == 2 { // Если остаток равен 2, ничего не делать return int(math.Pow(3, float64(a))) * 2 } // Если остаток равен 0, ничего не делать return int(math.Pow(3, float64(a))) } ================================================ FILE: ru/codes/go/chapter_greedy/max_product_cutting_test.go ================================================ // File: max_product_cutting_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestMaxProductCutting(t *testing.T) { n := 58 // Жадный алгоритм res := maxProductCutting(n) fmt.Println("Максимальное произведение после разрезания =", res) } ================================================ FILE: ru/codes/go/chapter_hashing/array_hash_map.go ================================================ // File: array_hash_map.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import "fmt" /* Пара ключ-значение */ type pair struct { key int val string } /* Хеш-таблица на основе массива */ type arrayHashMap struct { buckets []*pair } /* Инициализация хеш-таблицы */ func newArrayHashMap() *arrayHashMap { // Инициализировать массив, содержащий 100 корзин buckets := make([]*pair, 100) return &arrayHashMap{buckets: buckets} } /* Хеш-функция */ func (a *arrayHashMap) hashFunc(key int) int { index := key % 100 return index } /* Операция поиска */ func (a *arrayHashMap) get(key int) string { index := a.hashFunc(key) pair := a.buckets[index] if pair == nil { return "Not Found" } return pair.val } /* Операция добавления */ func (a *arrayHashMap) put(key int, val string) { pair := &pair{key: key, val: val} index := a.hashFunc(key) a.buckets[index] = pair } /* Операция удаления */ func (a *arrayHashMap) remove(key int) { index := a.hashFunc(key) // Присвоить nil, что означает удаление a.buckets[index] = nil } /* Получить все ключи */ func (a *arrayHashMap) pairSet() []*pair { var pairs []*pair for _, pair := range a.buckets { if pair != nil { pairs = append(pairs, pair) } } return pairs } /* Получить все ключи */ func (a *arrayHashMap) keySet() []int { var keys []int for _, pair := range a.buckets { if pair != nil { keys = append(keys, pair.key) } } return keys } /* Получить все значения */ func (a *arrayHashMap) valueSet() []string { var values []string for _, pair := range a.buckets { if pair != nil { values = append(values, pair.val) } } return values } /* Вывести хеш-таблицу */ func (a *arrayHashMap) print() { for _, pair := range a.buckets { if pair != nil { fmt.Println(pair.key, "->", pair.val) } } } ================================================ FILE: ru/codes/go/chapter_hashing/array_hash_map_test.go ================================================ // File: array_hash_map_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "testing" ) func TestArrayHashMap(t *testing.T) { /* Инициализация хеш-таблицы */ hmap := newArrayHashMap() /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу hmap.put(12836, "Сяо Ха") hmap.put(15937, "Сяо Ло") hmap.put(16750, "Сяо Суань") hmap.put(13276, "Сяо Фа") hmap.put(10583, "Сяо Я") fmt.Println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") hmap.print() /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value name := hmap.get(15937) fmt.Println("\nДля номера 15937 найдено имя " + name) /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы hmap.remove(10583) fmt.Println("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") hmap.print() /* Обход хеш-таблицы */ fmt.Println("\nОтдельный обход пар ключ-значение") for _, kv := range hmap.pairSet() { fmt.Println(kv.key, " -> ", kv.val) } fmt.Println("\nОтдельный обход ключей") for _, key := range hmap.keySet() { fmt.Println(key) } fmt.Println("\nОтдельный обход значений") for _, val := range hmap.valueSet() { fmt.Println(val) } } ================================================ FILE: ru/codes/go/chapter_hashing/hash_collision_test.go ================================================ // File: hash_collision_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "testing" ) func TestHashMapChaining(t *testing.T) { /* Инициализация хеш-таблицы */ hmap := newHashMapChaining() /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу hmap.put(12836, "Сяо Ха") hmap.put(15937, "Сяо Ло") hmap.put(16750, "Сяо Суань") hmap.put(13276, "Сяо Фа") hmap.put(10583, "Сяо Я") fmt.Println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") hmap.print() /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value name := hmap.get(15937) fmt.Println("\nДля номера 15937 найдено имя", name) /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы hmap.remove(12836) fmt.Println("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение") hmap.print() } func TestHashMapOpenAddressing(t *testing.T) { /* Инициализация хеш-таблицы */ hmap := newHashMapOpenAddressing() /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу hmap.put(12836, "Сяо Ха") hmap.put(15937, "Сяо Ло") hmap.put(16750, "Сяо Суань") hmap.put(13276, "Сяо Фа") hmap.put(10583, "Сяо Я") fmt.Println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") hmap.print() /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value name := hmap.get(13276) fmt.Println("\nДля номера 13276 найдено имя", name) /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы hmap.remove(16750) fmt.Println("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение") hmap.print() } ================================================ FILE: ru/codes/go/chapter_hashing/hash_map_chaining.go ================================================ // File: hash_map_chaining.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import ( "fmt" "strconv" "strings" ) /* Хеш-таблица с цепочками */ type hashMapChaining struct { size int // Число пар ключ-значение capacity int // Вместимость хеш-таблицы loadThres float64 // Порог коэффициента загрузки для запуска расширения extendRatio int // Коэффициент расширения buckets [][]pair // Массив корзин } /* Конструктор */ func newHashMapChaining() *hashMapChaining { buckets := make([][]pair, 4) for i := 0; i < 4; i++ { buckets[i] = make([]pair, 0) } return &hashMapChaining{ size: 0, capacity: 4, loadThres: 2.0 / 3.0, extendRatio: 2, buckets: buckets, } } /* Хеш-функция */ func (m *hashMapChaining) hashFunc(key int) int { return key % m.capacity } /* Коэффициент загрузки */ func (m *hashMapChaining) loadFactor() float64 { return float64(m.size) / float64(m.capacity) } /* Операция поиска */ func (m *hashMapChaining) get(key int) string { idx := m.hashFunc(key) bucket := m.buckets[idx] // Обойти корзину; если найден key, вернуть соответствующее val for _, p := range bucket { if p.key == key { return p.val } } // Если key не найден, вернуть пустую строку return "" } /* Операция добавления */ func (m *hashMapChaining) put(key int, val string) { // Когда коэффициент загрузки превышает порог, выполнить расширение if m.loadFactor() > m.loadThres { m.extend() } idx := m.hashFunc(key) // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть for i := range m.buckets[idx] { if m.buckets[idx][i].key == key { m.buckets[idx][i].val = val return } } // Если такого key нет, добавить пару ключ-значение в конец p := pair{ key: key, val: val, } m.buckets[idx] = append(m.buckets[idx], p) m.size += 1 } /* Операция удаления */ func (m *hashMapChaining) remove(key int) { idx := m.hashFunc(key) // Обойти корзину и удалить из нее пару ключ-значение for i, p := range m.buckets[idx] { if p.key == key { // Удаление из среза m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...) m.size -= 1 break } } } /* Расширить хеш-таблицу */ func (m *hashMapChaining) extend() { // Временно сохранить исходную хеш-таблицу tmpBuckets := make([][]pair, len(m.buckets)) for i := 0; i < len(m.buckets); i++ { tmpBuckets[i] = make([]pair, len(m.buckets[i])) copy(tmpBuckets[i], m.buckets[i]) } // Инициализация новой хеш-таблицы после расширения m.capacity *= m.extendRatio m.buckets = make([][]pair, m.capacity) for i := 0; i < m.capacity; i++ { m.buckets[i] = make([]pair, 0) } m.size = 0 // Перенести пары ключ-значение из исходной хеш-таблицы в новую for _, bucket := range tmpBuckets { for _, p := range bucket { m.put(p.key, p.val) } } } /* Вывести хеш-таблицу */ func (m *hashMapChaining) print() { var builder strings.Builder for _, bucket := range m.buckets { builder.WriteString("[") for _, p := range bucket { builder.WriteString(strconv.Itoa(p.key) + " -> " + p.val + " ") } builder.WriteString("]") fmt.Println(builder.String()) builder.Reset() } } ================================================ FILE: ru/codes/go/chapter_hashing/hash_map_open_addressing.go ================================================ // File: hash_map_open_addressing.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import ( "fmt" ) /* Хеш-таблица с открытой адресацией */ type hashMapOpenAddressing struct { size int // Число пар ключ-значение capacity int // Вместимость хеш-таблицы loadThres float64 // Порог коэффициента загрузки для запуска расширения extendRatio int // Коэффициент расширения buckets []*pair // Массив корзин TOMBSTONE *pair // Удалить метку } /* Конструктор */ func newHashMapOpenAddressing() *hashMapOpenAddressing { return &hashMapOpenAddressing{ size: 0, capacity: 4, loadThres: 2.0 / 3.0, extendRatio: 2, buckets: make([]*pair, 4), TOMBSTONE: &pair{-1, "-1"}, } } /* Хеш-функция */ func (h *hashMapOpenAddressing) hashFunc(key int) int { return key % h.capacity // Вычислить хеш-значение по ключу } /* Коэффициент загрузки */ func (h *hashMapOpenAddressing) loadFactor() float64 { return float64(h.size) / float64(h.capacity) // Вычислить текущий коэффициент загрузки } /* Найти индекс корзины, соответствующий key */ func (h *hashMapOpenAddressing) findBucket(key int) int { index := h.hashFunc(key) // Получить начальный индекс firstTombstone := -1 // Запомнить положение первого TOMBSTONE for h.buckets[index] != nil { if h.buckets[index].key == key { if firstTombstone != -1 { // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс h.buckets[firstTombstone] = h.buckets[index] h.buckets[index] = h.TOMBSTONE return firstTombstone // Вернуть индекс корзины после перемещения } return index // Вернуть найденный индекс } if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE { firstTombstone = index // Запомнить положение первой метки удаления } index = (index + 1) % h.capacity // Линейное пробирование: при выходе за хвост вернуться к началу } // Если key не существует, вернуть индекс точки добавления if firstTombstone != -1 { return firstTombstone } return index } /* Операция поиска */ func (h *hashMapOpenAddressing) get(key int) string { index := h.findBucket(key) // Найти индекс корзины, соответствующий key if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { return h.buckets[index].val // Если пара ключ-значение найдена, вернуть соответствующее val } return "" // Если пара ключ-значение не существует, вернуть "" } /* Операция добавления */ func (h *hashMapOpenAddressing) put(key int, val string) { if h.loadFactor() > h.loadThres { h.extend() // Когда коэффициент загрузки превышает порог, выполнить расширение } index := h.findBucket(key) // Найти индекс корзины, соответствующий key if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE { h.buckets[index] = &pair{key, val} // Если пары ключ-значение нет, добавить ее h.size++ } else { h.buckets[index].val = val // Если пара ключ-значение найдена, перезаписать val } } /* Операция удаления */ func (h *hashMapOpenAddressing) remove(key int) { index := h.findBucket(key) // Найти индекс корзины, соответствующий key if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { h.buckets[index] = h.TOMBSTONE // Если пара ключ-значение найдена, заменить ее меткой удаления h.size-- } } /* Расширить хеш-таблицу */ func (h *hashMapOpenAddressing) extend() { oldBuckets := h.buckets // Временно сохранить исходную хеш-таблицу h.capacity *= h.extendRatio // Обновить емкость h.buckets = make([]*pair, h.capacity) // Инициализация новой хеш-таблицы после расширения h.size = 0 // Сбросить размер // Перенести пары ключ-значение из исходной хеш-таблицы в новую for _, pair := range oldBuckets { if pair != nil && pair != h.TOMBSTONE { h.put(pair.key, pair.val) } } } /* Вывести хеш-таблицу */ func (h *hashMapOpenAddressing) print() { for _, pair := range h.buckets { if pair == nil { fmt.Println("nil") } else if pair == h.TOMBSTONE { fmt.Println("TOMBSTONE") } else { fmt.Printf("%d -> %s\n", pair.key, pair.val) } } } ================================================ FILE: ru/codes/go/chapter_hashing/hash_map_test.go ================================================ // File: hash_map_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHashMap(t *testing.T) { /* Инициализация хеш-таблицы */ hmap := make(map[int]string) /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу hmap[12836] = "Сяо Ха" hmap[15937] = "Сяо Ло" hmap[16750] = "Сяо Суань" hmap[13276] = "Сяо Фа" hmap[10583] = "Сяо Я" fmt.Println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") PrintMap(hmap) /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value name := hmap[15937] fmt.Println("\nДля номера 15937 найдено имя", name) /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы delete(hmap, 10583) fmt.Println("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") PrintMap(hmap) /* Обход хеш-таблицы */ // Перебрать пары ключ-значение key->value fmt.Println("\nОтдельный обход пар ключ-значение") for key, value := range hmap { fmt.Println(key, "->", value) } // Отдельно обходить ключи key fmt.Println("\nОтдельный обход ключей") for key := range hmap { fmt.Println(key) } // Отдельно обходить значения value fmt.Println("\nОтдельный обход значений") for _, value := range hmap { fmt.Println(value) } } func TestSimpleHash(t *testing.T) { var hash int key := "Hello Algo" hash = addHash(key) fmt.Println("Хеш-сумма сложением = " + strconv.Itoa(hash)) hash = mulHash(key) fmt.Println("Хеш-сумма умножением = " + strconv.Itoa(hash)) hash = xorHash(key) fmt.Println("Хеш-сумма XOR = " + strconv.Itoa(hash)) hash = rotHash(key) fmt.Println("Хеш-сумма с циклическим сдвигом = " + strconv.Itoa(hash)) } ================================================ FILE: ru/codes/go/chapter_hashing/simple_hash.go ================================================ // File: simple_hash.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import "fmt" /* Аддитивное хеширование */ func addHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = (hash + int64(b)) % modulus } return int(hash) } /* Мультипликативное хеширование */ func mulHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = (31*hash + int64(b)) % modulus } return int(hash) } /* XOR-хеширование */ func xorHash(key string) int { hash := 0 modulus := 1000000007 for _, b := range []byte(key) { fmt.Println(int(b)) hash ^= int(b) hash = (31*hash + int(b)) % modulus } return hash & modulus } /* Хеширование с циклическим сдвигом */ func rotHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus } return int(hash) } ================================================ FILE: ru/codes/go/chapter_heap/heap.go ================================================ // File: heap.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap // В Go можно построить максимальную кучу для целых чисел, реализовав heap.Interface // Для реализации heap.Interface одновременно требуется реализовать sort.Interface type intHeap []any // Функция Push интерфейса heap.Interface, реализующая добавление элемента в кучу func (h *intHeap) Push(x any) { // Push и Pop используют pointer receiver в качестве параметра // Потому что они не только изменяют содержимое среза, но и меняют его длину. *h = append(*h, x.(int)) } // Функция Pop интерфейса heap.Interface, реализующая извлечение элемента с вершины кучи func (h *intHeap) Pop() any { // Элемент, который нужно удалить из кучи, хранится в конце last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Функция Len интерфейса sort.Interface func (h *intHeap) Len() int { return len(*h) } // Функция Less интерфейса sort.Interface func (h *intHeap) Less(i, j int) bool { // При реализации минимальной кучи нужно изменить знак сравнения на «меньше» return (*h)[i].(int) > (*h)[j].(int) } // Функция Swap интерфейса sort.Interface func (h *intHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Top: получить элемент на вершине кучи func (h *intHeap) Top() any { return (*h)[0] } ================================================ FILE: ru/codes/go/chapter_heap/heap_test.go ================================================ // File: heap_test.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap import ( "container/heap" "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func testPush(h *intHeap, val int) { // Вызвать функции heap.Interface для добавления элемента heap.Push(h, val) fmt.Printf("\nПосле добавления элемента %d в кучу\n", val) PrintHeap(*h) } func testPop(h *intHeap) { // Вызвать функции heap.Interface для удаления элемента val := heap.Pop(h) fmt.Printf("\nПосле извлечения элемента вершины кучи %d\n", val) PrintHeap(*h) } func TestHeap(t *testing.T) { /* Инициализация кучи */ // Инициализация максимальной кучи maxHeap := &intHeap{} heap.Init(maxHeap) /* Добавление элемента в кучу */ testPush(maxHeap, 1) testPush(maxHeap, 3) testPush(maxHeap, 2) testPush(maxHeap, 5) testPush(maxHeap, 4) /* Получение элемента с вершины кучи */ top := maxHeap.Top() fmt.Printf("Элемент на вершине кучи = %d\n", top) /* Извлечение элемента с вершины кучи */ testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) /* Получение размера кучи */ size := len(*maxHeap) fmt.Printf("Количество элементов в куче = %d\n", size) /* Проверка, пуста ли куча */ isEmpty := len(*maxHeap) == 0 fmt.Printf("Пуста ли куча: %t\n", isEmpty) } func TestMyHeap(t *testing.T) { /* Инициализация кучи */ // Инициализация максимальной кучи maxHeap := newMaxHeap([]any{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}) fmt.Printf("После построения кучи из входного массива\n") maxHeap.print() /* Получение элемента с вершины кучи */ peek := maxHeap.peek() fmt.Printf("\nЭлемент на вершине кучи = %d\n", peek) /* Добавление элемента в кучу */ val := 7 maxHeap.push(val) fmt.Printf("\nПосле добавления элемента %d в кучу\n", val) maxHeap.print() /* Извлечение элемента с вершины кучи */ peek = maxHeap.pop() fmt.Printf("\nПосле извлечения элемента вершины кучи %d\n", peek) maxHeap.print() /* Получение размера кучи */ size := maxHeap.size() fmt.Printf("\nКоличество элементов в куче = %d\n", size) /* Проверка, пуста ли куча */ isEmpty := maxHeap.isEmpty() fmt.Printf("\nПуста ли куча: %t\n", isEmpty) } func TestTopKHeap(t *testing.T) { /* Инициализация кучи */ // Инициализация максимальной кучи nums := []int{1, 7, 6, 3, 2} k := 3 res := topKHeap(nums, k) fmt.Printf("Наибольшие " + strconv.Itoa(k) + " элементов") PrintHeap(*res) } ================================================ FILE: ru/codes/go/chapter_heap/my_heap.go ================================================ // File: my_heap.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap import ( "fmt" . "github.com/krahets/hello-algo/pkg" ) type maxHeap struct { // Использовать срез вместо массива, чтобы не учитывать проблему расширения data []any } /* Конструктор, создающий пустую кучу */ func newHeap() *maxHeap { return &maxHeap{ data: make([]any, 0), } } /* Конструктор, строящий кучу по срезу */ func newMaxHeap(nums []any) *maxHeap { // Добавить элементы списка в кучу без изменений h := &maxHeap{data: nums} for i := h.parent(len(h.data) - 1); i >= 0; i-- { // Выполнить heapify для всех узлов, кроме листовых h.siftDown(i) } return h } /* Получить индекс левого дочернего узла */ func (h *maxHeap) left(i int) int { return 2*i + 1 } /* Получить индекс правого дочернего узла */ func (h *maxHeap) right(i int) int { return 2*i + 2 } /* Получить индекс родительского узла */ func (h *maxHeap) parent(i int) int { // Округление вниз при делении return (i - 1) / 2 } /* Поменять элементы местами */ func (h *maxHeap) swap(i, j int) { h.data[i], h.data[j] = h.data[j], h.data[i] } /* Получение размера кучи */ func (h *maxHeap) size() int { return len(h.data) } /* Проверка, пуста ли куча */ func (h *maxHeap) isEmpty() bool { return len(h.data) == 0 } /* Доступ к элементу на вершине кучи */ func (h *maxHeap) peek() any { return h.data[0] } /* Добавление элемента в кучу */ func (h *maxHeap) push(val any) { // Добавление узла h.data = append(h.data, val) // Просеивание снизу вверх h.siftUp(len(h.data) - 1) } /* Начиная с узла i, выполнить просеивание снизу вверх */ func (h *maxHeap) siftUp(i int) { for true { // Получение родительского узла для узла i p := h.parent(i) // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» if p < 0 || h.data[i].(int) <= h.data[p].(int) { break } // Поменять два узла местами h.swap(i, p) // Циклическое просеивание вверх i = p } } /* Извлечение элемента из кучи */ func (h *maxHeap) pop() any { // Обработка пустого случая if h.isEmpty() { fmt.Println("error") return nil } // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) h.swap(0, h.size()-1) // Удаление узла val := h.data[len(h.data)-1] h.data = h.data[:len(h.data)-1] // Просеивание сверху вниз h.siftDown(0) // Вернуть элемент с вершины кучи return val } /* Начиная с узла i, выполнить просеивание сверху вниз */ func (h *maxHeap) siftDown(i int) { for true { // Определить узел с максимальным значением среди i, l и r и обозначить его как max l, r, max := h.left(i), h.right(i), i if l < h.size() && h.data[l].(int) > h.data[max].(int) { max = l } if r < h.size() && h.data[r].(int) > h.data[max].(int) { max = r } // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if max == i { break } // Поменять два узла местами h.swap(i, max) // Циклическое просеивание вниз i = max } } /* Вывести кучу (двоичное дерево) */ func (h *maxHeap) print() { PrintHeap(h.data) } ================================================ FILE: ru/codes/go/chapter_heap/top_k.go ================================================ // File: top_k.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_heap import "container/heap" type minHeap []any func (h *minHeap) Len() int { return len(*h) } func (h *minHeap) Less(i, j int) bool { return (*h)[i].(int) < (*h)[j].(int) } func (h *minHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Метод Push интерфейса heap.Interface, реализующий добавление элемента в кучу func (h *minHeap) Push(x any) { *h = append(*h, x.(int)) } // Метод Pop интерфейса heap.Interface, реализующий извлечение элемента с вершины кучи func (h *minHeap) Pop() any { // Элемент, который нужно удалить из кучи, хранится в конце last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Top: получить элемент на вершине кучи func (h *minHeap) Top() any { return (*h)[0] } /* Найти k наибольших элементов массива с помощью кучи */ func topKHeap(nums []int, k int) *minHeap { // Инициализация минимальной кучи h := &minHeap{} heap.Init(h) // Поместить первые k элементов массива в кучу for i := 0; i < k; i++ { heap.Push(h, nums[i]) } // Начиная с элемента k+1, поддерживать длину кучи равной k for i := k; i < len(nums); i++ { // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу if nums[i] > h.Top().(int) { heap.Pop(h) heap.Push(h, nums[i]) } } return h } ================================================ FILE: ru/codes/go/chapter_searching/binary_search.go ================================================ // File: binary_search.go // Created Time: 2022-12-05 // Author: Slone123c (274325721@qq.com) package chapter_searching /* Бинарный поиск (двусторонне замкнутый интервал) */ func binarySearch(nums []int, target int) int { // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно i, j := 0, len(nums)-1 // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) for i <= j { m := i + (j-i)/2 // Вычислить индекс середины m if nums[m] < target { // Это означает, что target находится в интервале [m+1, j] i = m + 1 } else if nums[m] > target { // Это означает, что target находится в интервале [i, m-1] j = m - 1 } else { // Целевой элемент найден, вернуть его индекс return m } } // Целевой элемент не найден, вернуть -1 return -1 } /* Бинарный поиск (лево замкнутый, право открытый интервал) */ func binarySearchLCRO(nums []int, target int) int { // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно i, j := 0, len(nums) // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) for i < j { m := i + (j-i)/2 // Вычислить индекс середины m if nums[m] < target { // Это означает, что target находится в интервале [m+1, j) i = m + 1 } else if nums[m] > target { // Это означает, что target находится в интервале [i, m) j = m } else { // Целевой элемент найден, вернуть его индекс return m } } // Целевой элемент не найден, вернуть -1 return -1 } ================================================ FILE: ru/codes/go/chapter_searching/binary_search_edge.go ================================================ // File: binary_search_edge.go // Created Time: 2023-08-23 // Author: Reanon (793584285@qq.com) package chapter_searching /* Бинарный поиск самого левого target */ func binarySearchLeftEdge(nums []int, target int) int { // Эквивалентно поиску точки вставки target i := binarySearchInsertion(nums, target) // target не найден, вернуть -1 if i == len(nums) || nums[i] != target { return -1 } // Найти target и вернуть индекс i return i } /* Бинарный поиск самого правого target */ func binarySearchRightEdge(nums []int, target int) int { // Преобразовать задачу в поиск самого левого target + 1 i := binarySearchInsertion(nums, target+1) // j указывает на самый правый target, а i — на первый элемент больше target j := i - 1 // target не найден, вернуть -1 if j == -1 || nums[j] != target { return -1 } // Найти target и вернуть индекс j return j } ================================================ FILE: ru/codes/go/chapter_searching/binary_search_insertion.go ================================================ // File: binary_search_insertion.go // Created Time: 2023-08-23 // Author: Reanon (793584285@qq.com) package chapter_searching /* Бинарный поиск точки вставки (без повторяющихся элементов) */ func binarySearchInsertionSimple(nums []int, target int) int { // Инициализировать двусторонне замкнутый интервал [0, n-1] i, j := 0, len(nums)-1 for i <= j { // Вычислить индекс середины m m := i + (j-i)/2 if nums[m] < target { // target находится в интервале [m+1, j] i = m + 1 } else if nums[m] > target { // target находится в интервале [i, m-1] j = m - 1 } else { // Найти target и вернуть точку вставки m return m } } // target не найден, вернуть точку вставки i return i } /* Бинарный поиск точки вставки (с повторяющимися элементами) */ func binarySearchInsertion(nums []int, target int) int { // Инициализировать двусторонне замкнутый интервал [0, n-1] i, j := 0, len(nums)-1 for i <= j { // Вычислить индекс середины m m := i + (j-i)/2 if nums[m] < target { // target находится в интервале [m+1, j] i = m + 1 } else if nums[m] > target { // target находится в интервале [i, m-1] j = m - 1 } else { // Первый элемент меньше target находится в интервале [i, m-1] j = m - 1 } } // Вернуть точку вставки i return i } ================================================ FILE: ru/codes/go/chapter_searching/binary_search_test.go ================================================ // File: binary_search_test.go // Created Time: 2022-12-05 // Author: Slone123c (274325721@qq.com) package chapter_searching import ( "fmt" "testing" ) func TestBinarySearch(t *testing.T) { var ( target = 6 nums = []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} expected = 2 ) // Выполнить бинарный поиск в массиве actual := binarySearch(nums, target) fmt.Println("Индекс целевого элемента 6 =", actual) if actual != expected { t.Errorf("Индекс целевого элемента 6 = %d, должно быть %d", actual, expected) } } func TestBinarySearchEdge(t *testing.T) { // Массив с повторяющимися элементами nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} fmt.Println("\nМассив nums =", nums) // Бинарный поиск левой и правой границы for _, target := range []int{6, 7} { index := binarySearchLeftEdge(nums, target) fmt.Println("Индекс самого левого элемента", target, "равен", index) index = binarySearchRightEdge(nums, target) fmt.Println("Индекс самого правого элемента", target, "равен", index) } } func TestBinarySearchInsertion(t *testing.T) { // Массив без повторяющихся элементов nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} fmt.Println("Массив nums =", nums) // Бинарный поиск точки вставки for _, target := range []int{6, 9} { index := binarySearchInsertionSimple(nums, target) fmt.Println("Индекс позиции вставки элемента", target, "равен", index) } // Массив с повторяющимися элементами nums = []int{1, 3, 6, 6, 6, 6, 6, 10, 12, 15} fmt.Println("\nМассив nums =", nums) // Бинарный поиск точки вставки for _, target := range []int{2, 6, 20} { index := binarySearchInsertion(nums, target) fmt.Println("Индекс позиции вставки элемента", target, "равен", index) } } ================================================ FILE: ru/codes/go/chapter_searching/hashing_search.go ================================================ // File: hashing_search.go // Created Time: 2022-12-12 // Author: Slone123c (274325721@qq.com) package chapter_searching import . "github.com/krahets/hello-algo/pkg" /* Хеш-поиск (массив) */ func hashingSearchArray(m map[int]int, target int) int { // key хеш-таблицы: целевой элемент, value: индекс // Если такого key нет в хеш-таблице, вернуть -1 if index, ok := m[target]; ok { return index } else { return -1 } } /* Хеш-поиск (связный список) */ func hashingSearchLinkedList(m map[int]*ListNode, target int) *ListNode { // key хеш-таблицы: значение целевого узла, value: объект узла // Если такого key нет в хеш-таблице, вернуть nil if node, ok := m[target]; ok { return node } else { return nil } } ================================================ FILE: ru/codes/go/chapter_searching/hashing_search_test.go ================================================ // File: hashing_search_test.go // Created Time: 2022-12-12 // Author: Slone123c (274325721@qq.com) package chapter_searching import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHashingSearch(t *testing.T) { target := 3 /* Хеш-поиск (массив) */ nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} // Инициализация хеш-таблицы m := make(map[int]int) for i := 0; i < len(nums); i++ { m[nums[i]] = i } index := hashingSearchArray(m, target) fmt.Println("Индекс целевого элемента 3 = ", index) /* Хеш-поиск (связный список) */ head := ArrayToLinkedList(nums) // Инициализация хеш-таблицы m1 := make(map[int]*ListNode) for head != nil { m1[head.Val] = head head = head.Next } node := hashingSearchLinkedList(m1, target) fmt.Println("Объект узла со значением 3 =", node) } ================================================ FILE: ru/codes/go/chapter_searching/linear_search.go ================================================ // File: linear_search.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_searching import ( . "github.com/krahets/hello-algo/pkg" ) /* Линейный поиск (массив) */ func linearSearchArray(nums []int, target int) int { // Обход массива for i := 0; i < len(nums); i++ { // Целевой элемент найден, вернуть его индекс if nums[i] == target { return i } } // Целевой элемент не найден, вернуть -1 return -1 } /* Линейный поиск (связный список) */ func linearSearchLinkedList(node *ListNode, target int) *ListNode { // Обойти связный список for node != nil { // Найти целевой узел и вернуть его if node.Val == target { return node } node = node.Next } // Целевой элемент не найден, вернуть nil return nil } ================================================ FILE: ru/codes/go/chapter_searching/linear_search_test.go ================================================ // File: linear_search_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_searching import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLinearSearch(t *testing.T) { target := 3 nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} // Выполнить линейный поиск в массиве index := linearSearchArray(nums, target) fmt.Println("Индекс целевого элемента 3 =", index) // Выполнить линейный поиск в связном списке head := ArrayToLinkedList(nums) node := linearSearchLinkedList(head, target) fmt.Println("Объект узла со значением 3 =", node) } ================================================ FILE: ru/codes/go/chapter_searching/two_sum.go ================================================ // File: two_sum.go // Created Time: 2022-11-25 // Author: reanon (793584285@qq.com) package chapter_searching /* Метод 1: полный перебор */ func twoSumBruteForce(nums []int, target int) []int { size := len(nums) // Два вложенных цикла, временная сложность O(n^2) for i := 0; i < size-1; i++ { for j := i + 1; j < size; j++ { if nums[i]+nums[j] == target { return []int{i, j} } } } return nil } /* Метод 2: вспомогательная хеш-таблица */ func twoSumHashTable(nums []int, target int) []int { // Вспомогательная хеш-таблица, пространственная сложность O(n) hashTable := map[int]int{} // Один цикл, временная сложность O(n) for idx, val := range nums { if preIdx, ok := hashTable[target-val]; ok { return []int{preIdx, idx} } hashTable[val] = idx } return nil } ================================================ FILE: ru/codes/go/chapter_searching/two_sum_test.go ================================================ // File: two_sum_test.go // Created Time: 2022-11-25 // Author: reanon (793584285@qq.com) package chapter_searching import ( "fmt" "testing" ) func TestTwoSum(t *testing.T) { // ======= Test Case ======= nums := []int{2, 7, 11, 15} target := 13 // ====== Основной код ====== // Метод 1: решение полным перебором res := twoSumBruteForce(nums, target) fmt.Println("Результат метода 1 res =", res) // Способ 2: хеш-таблица res = twoSumHashTable(nums, target) fmt.Println("Результат метода 2 res =", res) } ================================================ FILE: ru/codes/go/chapter_sorting/bubble_sort.go ================================================ // File: bubble_sort.go // Created Time: 2022-12-06 // Author: Slone123c (274325721@qq.com) package chapter_sorting /* Пузырьковая сортировка */ func bubbleSort(nums []int) { // Внешний цикл: неотсортированный диапазон [0, i] for i := len(nums) - 1; i > 0; i-- { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // Поменять местами nums[j] и nums[j + 1] nums[j], nums[j+1] = nums[j+1], nums[j] } } } } /* Пузырьковая сортировка (оптимизация флагом) */ func bubbleSortWithFlag(nums []int) { // Внешний цикл: неотсортированный диапазон [0, i] for i := len(nums) - 1; i > 0; i-- { flag := false // Инициализировать флаг // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // Поменять местами nums[j] и nums[j + 1] nums[j], nums[j+1] = nums[j+1], nums[j] flag = true // Записать обмен элементов } } if flag == false { // На этой итерации «всплытия» не было ни одного обмена, сразу выйти break } } } ================================================ FILE: ru/codes/go/chapter_sorting/bubble_sort_test.go ================================================ // File: bubble_sort_test.go // Created Time: 2022-12-06 // Author: Slone123c (274325721@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestBubbleSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} bubbleSort(nums) fmt.Println("После пузырьковой сортировки nums =", nums) nums1 := []int{4, 1, 3, 1, 5, 2} bubbleSortWithFlag(nums1) fmt.Println("После пузырьковой сортировки nums1 =", nums1) } ================================================ FILE: ru/codes/go/chapter_sorting/bucket_sort.go ================================================ // File: bucket_sort.go // Created Time: 2023-03-27 // Author: Reanon (793584285@qq.com) package chapter_sorting import "sort" /* Сортировка корзинами */ func bucketSort(nums []float64) { // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину k := len(nums) / 2 buckets := make([][]float64, k) for i := 0; i < k; i++ { buckets[i] = make([]float64, 0) } // 1. Распределить элементы массива по корзинам for _, num := range nums { // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] i := int(num * float64(k)) // Добавить num в корзину i buckets[i] = append(buckets[i], num) } // 2. Выполнить сортировку внутри каждой корзины for i := 0; i < k; i++ { // Использовать встроенную функцию сортировки среза; ее также можно заменить другим алгоритмом сортировки sort.Float64s(buckets[i]) } // 3. Обойти корзины и объединить результаты i := 0 for _, bucket := range buckets { for _, num := range bucket { nums[i] = num i++ } } } ================================================ FILE: ru/codes/go/chapter_sorting/bucket_sort_test.go ================================================ // File: bucket_sort_test.go // Created Time: 2023-03-27 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestBucketSort(t *testing.T) { // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) nums := []float64{0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37} bucketSort(nums) fmt.Println("После сортировки корзинами nums =", nums) } ================================================ FILE: ru/codes/go/chapter_sorting/counting_sort.go ================================================ // File: counting_sort.go // Created Time: 2023-03-20 // Author: Reanon (793584285@qq.com) package chapter_sorting type CountingSort struct{} /* Сортировка подсчетом */ // Простая реализация, не подходит для сортировки объектов func countingSortNaive(nums []int) { // 1. Найти максимальный элемент массива m m := 0 for _, num := range nums { if num > m { m = num } } // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num counter := make([]int, m+1) for _, num := range nums { counter[num]++ } // 3. Обойти counter и заполнить исходный массив nums элементами for i, num := 0, 0; num < m+1; num++ { for j := 0; j < counter[num]; j++ { nums[i] = num i++ } } } /* Сортировка подсчетом */ // Полная реализация, позволяет сортировать объекты и является стабильной сортировкой func countingSort(nums []int) { // 1. Найти максимальный элемент массива m m := 0 for _, num := range nums { if num > m { m = num } } // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num counter := make([]int, m+1) for _, num := range nums { counter[num]++ } // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» // То есть counter[num]-1 — это индекс последнего появления num в res for i := 0; i < m; i++ { counter[i+1] += counter[i] } // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res // Инициализировать массив res для хранения результата n := len(nums) res := make([]int, n) for i := n - 1; i >= 0; i-- { num := nums[i] // Поместить num по соответствующему индексу res[counter[num]-1] = num // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num counter[num]-- } // Перезаписать исходный массив nums массивом результата res copy(nums, res) } ================================================ FILE: ru/codes/go/chapter_sorting/counting_sort_test.go ================================================ // File: counting_sort_test.go // Created Time: 2023-03-20 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestCountingSort(t *testing.T) { nums := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} countingSortNaive(nums) fmt.Println("После сортировки подсчетом (объекты не поддерживаются) nums =", nums) nums1 := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} countingSort(nums1) fmt.Println("После сортировки подсчетом nums1 =", nums1) } ================================================ FILE: ru/codes/go/chapter_sorting/heap_sort.go ================================================ // File: heap_sort.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting /* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ func siftDown(nums *[]int, n, i int) { for true { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma l := 2*i + 1 r := 2*i + 2 ma := i if l < n && (*nums)[l] > (*nums)[ma] { ma = l } if r < n && (*nums)[r] > (*nums)[ma] { ma = r } // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if ma == i { break } // Поменять два узла местами (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i] // Циклическое просеивание вниз i = ma } } /* Сортировка кучей */ func heapSort(nums *[]int) { // Построение кучи: выполнить heapify для всех узлов, кроме листовых for i := len(*nums)/2 - 1; i >= 0; i-- { siftDown(nums, len(*nums), i) } // Извлекать максимальный элемент из кучи в течение n-1 итераций for i := len(*nums) - 1; i > 0; i-- { // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0] // Начиная с корневого узла, выполнить просеивание сверху вниз siftDown(nums, i, 0) } } ================================================ FILE: ru/codes/go/chapter_sorting/heap_sort_test.go ================================================ // File: heap_sort_test.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestHeapSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} heapSort(&nums) fmt.Println("После сортировки кучей nums =", nums) } ================================================ FILE: ru/codes/go/chapter_sorting/insertion_sort.go ================================================ // File: insertion_sort.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting /* Сортировка вставками */ func insertionSort(nums []int) { // Внешний цикл: отсортированный диапазон [0, i-1] for i := 1; i < len(nums); i++ { base := nums[i] j := i - 1 // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] for j >= 0 && nums[j] > base { nums[j+1] = nums[j] // Сдвинуть nums[j] на одну позицию вправо j-- } nums[j+1] = base // Поместить base в правильную позицию } } ================================================ FILE: ru/codes/go/chapter_sorting/insertion_sort_test.go ================================================ // File: insertion_sort_test.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) func TestInsertionSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} insertionSort(nums) fmt.Println("После сортировки вставками nums =", nums) } ================================================ FILE: ru/codes/go/chapter_sorting/merge_sort.go ================================================ // File: merge_sort.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting /* Объединить левый и правый подмассивы */ func merge(nums []int, left, mid, right int) { // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] // Создать временный массив tmp для хранения результата слияния tmp := make([]int, right-left+1) // Инициализировать начальные индексы левого и правого подмассивов i, j, k := left, mid+1, 0 // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив for i <= mid && j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i] i++ } else { tmp[k] = nums[j] j++ } k++ } // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив for i <= mid { tmp[k] = nums[i] i++ k++ } for j <= right { tmp[k] = nums[j] j++ k++ } // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums for k := 0; k < len(tmp); k++ { nums[left+k] = tmp[k] } } /* Сортировка слиянием */ func mergeSort(nums []int, left, right int) { // Условие завершения if left >= right { return } // Этап разбиения mid := left + (right - left) / 2 mergeSort(nums, left, mid) mergeSort(nums, mid+1, right) // Этап слияния merge(nums, left, mid, right) } ================================================ FILE: ru/codes/go/chapter_sorting/merge_sort_test.go ================================================ // File: merge_sort_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) func TestMergeSort(t *testing.T) { nums := []int{7, 3, 2, 6, 0, 1, 5, 4} mergeSort(nums, 0, len(nums)-1) fmt.Println("После сортировки слиянием nums =", nums) } ================================================ FILE: ru/codes/go/chapter_sorting/quick_sort.go ================================================ // File: quick_sort.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting // Быстрая сортировка type quickSort struct{} // Быстрая сортировка (оптимизация медианным опорным элементом) type quickSortMedian struct{} // Быстрая сортировка (оптимизация глубины рекурсии) type quickSortTailCall struct{} /* Разбиение с опорными указателями */ func (q *quickSort) partition(nums []int, left, right int) int { // Взять nums[left] в качестве опорного элемента i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- // Идти справа налево в поисках первого элемента меньше опорного } for i < j && nums[i] <= nums[left] { i++ // Идти слева направо в поисках первого элемента больше опорного } // Обмен элементов nums[i], nums[j] = nums[j], nums[i] } // Переместить опорный элемент на границу двух подмассивов nums[i], nums[left] = nums[left], nums[i] return i // Вернуть индекс опорного элемента } /* Быстрая сортировка */ func (q *quickSort) quickSort(nums []int, left, right int) { // Завершить рекурсию, когда длина подмассива равна 1 if left >= right { return } // Разбиение с опорными указателями pivot := q.partition(nums, left, right) // Рекурсивно обработать левый и правый подмассивы q.quickSort(nums, left, pivot-1) q.quickSort(nums, pivot+1, right) } /* Выбрать медиану из трех кандидатов */ func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int { l, m, r := nums[left], nums[mid], nums[right] if (l <= m && m <= r) || (r <= m && m <= l) { return mid // m находится между l и r } if (m <= l && l <= r) || (r <= l && l <= m) { return left // l находится между m и r } return right } /* Разбиение с опорными указателями (медиана трех) */ func (q *quickSortMedian) partition(nums []int, left, right int) int { // Взять nums[left] в качестве опорного элемента med := q.medianThree(nums, left, (left+right)/2, right) // Переместить медиану в крайний левый элемент массива nums[left], nums[med] = nums[med], nums[left] // Взять nums[left] в качестве опорного элемента i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- // Идти справа налево в поисках первого элемента меньше опорного } for i < j && nums[i] <= nums[left] { i++ // Идти слева направо в поисках первого элемента больше опорного } // Обмен элементов nums[i], nums[j] = nums[j], nums[i] } // Переместить опорный элемент на границу двух подмассивов nums[i], nums[left] = nums[left], nums[i] return i // Вернуть индекс опорного элемента } /* Быстрая сортировка */ func (q *quickSortMedian) quickSort(nums []int, left, right int) { // Завершить рекурсию, когда длина подмассива равна 1 if left >= right { return } // Разбиение с опорными указателями pivot := q.partition(nums, left, right) // Рекурсивно обработать левый и правый подмассивы q.quickSort(nums, left, pivot-1) q.quickSort(nums, pivot+1, right) } /* Разбиение с опорными указателями */ func (q *quickSortTailCall) partition(nums []int, left, right int) int { // Взять nums[left] в качестве опорного элемента i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- // Идти справа налево в поисках первого элемента меньше опорного } for i < j && nums[i] <= nums[left] { i++ // Идти слева направо в поисках первого элемента больше опорного } // Обмен элементов nums[i], nums[j] = nums[j], nums[i] } // Переместить опорный элемент на границу двух подмассивов nums[i], nums[left] = nums[left], nums[i] return i // Вернуть индекс опорного элемента } /* Быстрая сортировка (оптимизация глубины рекурсии) */ func (q *quickSortTailCall) quickSort(nums []int, left, right int) { // Завершить, когда длина подмассива равна 1 for left < right { // Операция разбиения с опорными указателями pivot := q.partition(nums, left, right) // Выполнить быструю сортировку для более короткого из двух подмассивов if pivot-left < right-pivot { q.quickSort(nums, left, pivot-1) // Рекурсивно отсортировать левый подмассив left = pivot + 1 // Оставшийся неотсортированный диапазон: [pivot + 1, right] } else { q.quickSort(nums, pivot+1, right) // Рекурсивно отсортировать правый подмассив right = pivot - 1 // Оставшийся неотсортированный диапазон: [left, pivot - 1] } } } ================================================ FILE: ru/codes/go/chapter_sorting/quick_sort_test.go ================================================ // File: quick_sort_test.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) // Быстрая сортировка func TestQuickSort(t *testing.T) { q := quickSort{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("После быстрой сортировки nums =", nums) } // Быстрая сортировка (оптимизация медианным опорным элементом) func TestQuickSortMedian(t *testing.T) { q := quickSortMedian{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("После быстрой сортировки (оптимизация медианным опорным элементом) nums =", nums) } // Быстрая сортировка (оптимизация глубины рекурсии) func TestQuickSortTailCall(t *testing.T) { q := quickSortTailCall{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("После быстрой сортировки (оптимизация глубины рекурсии) nums =", nums) } ================================================ FILE: ru/codes/go/chapter_sorting/radix_sort.go ================================================ // File: radix_sort.go // Created Time: 2023-01-18 // Author: Reanon (793584285@qq.com) package chapter_sorting import "math" /* Получить k-й разряд элемента num, где exp = 10^(k-1) */ func digit(num, exp int) int { // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени return (num / exp) % 10 } /* Сортировка подсчетом (сортировка по k-му разряду nums) */ func countingSortDigit(nums []int, exp int) { // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 counter := make([]int, 10) n := len(nums) // Подсчитать число появлений каждой цифры от 0 до 9 for i := 0; i < n; i++ { d := digit(nums[i], exp) // Получить k-й разряд nums[i], обозначив его как d counter[d]++ // Подсчитать число появлений цифры d } // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» for i := 1; i < 10; i++ { counter[i] += counter[i-1] } // Выполняя обратный проход, заполнить res элементами по статистике в корзинах res := make([]int, n) for i := n - 1; i >= 0; i-- { d := digit(nums[i], exp) j := counter[d] - 1 // Получить индекс j цифры d в массиве res[j] = nums[i] // Поместить текущий элемент по индексу j counter[d]-- // Уменьшить количество d на 1 } // Перезаписать исходный массив nums результатом for i := 0; i < n; i++ { nums[i] = res[i] } } /* Поразрядная сортировка */ func radixSort(nums []int) { // Получить максимальный элемент массива, чтобы определить максимальное число разрядов max := math.MinInt for _, num := range nums { if num > max { max = num } } // Проходить разряды от младшего к старшему for exp := 1; max >= exp; exp *= 10 { // Выполнить сортировку подсчетом по k-му разряду элементов массива // k = 1 -> exp = 1 // k = 2 -> exp = 10 // то есть exp = 10^(k-1) countingSortDigit(nums, exp) } } ================================================ FILE: ru/codes/go/chapter_sorting/radix_sort_test.go ================================================ // File: radix_sort_test.go // Created Time: 2023-01-18 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestRadixSort(t *testing.T) { /* Поразрядная сортировка */ nums := []int{10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996} radixSort(nums) fmt.Println("После поразрядной сортировки nums =", nums) } ================================================ FILE: ru/codes/go/chapter_sorting/selection_sort.go ================================================ // File: selection_sort.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting /* Сортировка выбором */ func selectionSort(nums []int) { n := len(nums) // Внешний цикл: неотсортированный диапазон [i, n-1] for i := 0; i < n-1; i++ { // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне k := i for j := i + 1; j < n; j++ { if nums[j] < nums[k] { // Записать индекс минимального элемента k = j } } // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона nums[i], nums[k] = nums[k], nums[i] } } ================================================ FILE: ru/codes/go/chapter_sorting/selection_sort_test.go ================================================ // File: selection_sort_test.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestSelectionSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} selectionSort(nums) fmt.Println("После сортировки выбором nums =", nums) } ================================================ FILE: ru/codes/go/chapter_stack_and_queue/array_deque.go ================================================ // File: array_deque.go // Created Time: 2023-03-13 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import "fmt" /* Двусторонняя очередь на основе кольцевого массива */ type arrayDeque struct { nums []int // Массив для хранения элементов двусторонней очереди front int // Указатель head, указывающий на первый элемент очереди queSize int // Длина двусторонней очереди queCapacity int // Вместимость очереди (то есть максимальное число элементов) } /* Инициализация очереди */ func newArrayDeque(queCapacity int) *arrayDeque { return &arrayDeque{ nums: make([]int, queCapacity), queCapacity: queCapacity, front: 0, queSize: 0, } } /* Получение длины двусторонней очереди */ func (q *arrayDeque) size() int { return q.queSize } /* Проверка, пуста ли двусторонняя очередь */ func (q *arrayDeque) isEmpty() bool { return q.queSize == 0 } /* Вычислить индекс в кольцевом массиве */ func (q *arrayDeque) index(i int) int { // С помощью операции взятия по модулю соединить начало и конец массива // Когда i выходит за конец массива, он возвращается в начало // Когда i выходит за начало массива, он возвращается в конец return (i + q.queCapacity) % q.queCapacity } /* Добавление в голову очереди */ func (q *arrayDeque) pushFirst(num int) { if q.queSize == q.queCapacity { fmt.Println("Двусторонняя очередь заполнена") return } // Указатель головы сдвигается на одну позицию влево // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост q.front = q.index(q.front - 1) // Добавить num в голову очереди q.nums[q.front] = num q.queSize++ } /* Добавление в хвост очереди */ func (q *arrayDeque) pushLast(num int) { if q.queSize == q.queCapacity { fmt.Println("Двусторонняя очередь заполнена") return } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 rear := q.index(q.front + q.queSize) // Добавить num в хвост очереди q.nums[rear] = num q.queSize++ } /* Извлечение из головы очереди */ func (q *arrayDeque) popFirst() any { num := q.peekFirst() if num == nil { return nil } // Указатель головы сдвигается на одну позицию назад q.front = q.index(q.front + 1) q.queSize-- return num } /* Извлечение из хвоста очереди */ func (q *arrayDeque) popLast() any { num := q.peekLast() if num == nil { return nil } q.queSize-- return num } /* Доступ к элементу в начале очереди */ func (q *arrayDeque) peekFirst() any { if q.isEmpty() { return nil } return q.nums[q.front] } /* Доступ к элементу в конце очереди */ func (q *arrayDeque) peekLast() any { if q.isEmpty() { return nil } // Вычислить индекс хвостового элемента last := q.index(q.front + q.queSize - 1) return q.nums[last] } /* Получить Slice для вывода */ func (q *arrayDeque) toSlice() []int { // Преобразовывать только элементы списка в пределах фактической длины res := make([]int, q.queSize) for i, j := 0, q.front; i < q.queSize; i++ { res[i] = q.nums[q.index(j)] j++ } return res } ================================================ FILE: ru/codes/go/chapter_stack_and_queue/array_queue.go ================================================ // File: array_queue.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue /* Очередь на основе кольцевого массива */ type arrayQueue struct { nums []int // Массив для хранения элементов очереди front int // Указатель head, указывающий на первый элемент очереди queSize int // Длина очереди queCapacity int // Вместимость очереди (то есть максимальное число элементов) } /* Инициализация очереди */ func newArrayQueue(queCapacity int) *arrayQueue { return &arrayQueue{ nums: make([]int, queCapacity), queCapacity: queCapacity, front: 0, queSize: 0, } } /* Получение длины очереди */ func (q *arrayQueue) size() int { return q.queSize } /* Проверка, пуста ли очередь */ func (q *arrayQueue) isEmpty() bool { return q.queSize == 0 } /* Поместить в очередь */ func (q *arrayQueue) push(num int) { // Когда rear == queCapacity, очередь заполнена if q.queSize == q.queCapacity { return } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива rear := (q.front + q.queSize) % q.queCapacity // Добавить num в хвост очереди q.nums[rear] = num q.queSize++ } /* Извлечь из очереди */ func (q *arrayQueue) pop() any { num := q.peek() if num == nil { return nil } // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива q.front = (q.front + 1) % q.queCapacity q.queSize-- return num } /* Доступ к элементу в начале очереди */ func (q *arrayQueue) peek() any { if q.isEmpty() { return nil } return q.nums[q.front] } /* Получить Slice для вывода */ func (q *arrayQueue) toSlice() []int { rear := (q.front + q.queSize) if rear >= q.queCapacity { rear %= q.queCapacity return append(q.nums[q.front:], q.nums[:rear]...) } return q.nums[q.front:rear] } ================================================ FILE: ru/codes/go/chapter_stack_and_queue/array_stack.go ================================================ // File: array_stack.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue /* Стек на основе массива */ type arrayStack struct { data []int // Данные } /* Инициализация стека */ func newArrayStack() *arrayStack { return &arrayStack{ // Установить длину стека равной 0, а емкость равной 16 data: make([]int, 0, 16), } } /* Длина стека */ func (s *arrayStack) size() int { return len(s.data) } /* Пуст ли стек */ func (s *arrayStack) isEmpty() bool { return s.size() == 0 } /* Поместить в стек */ func (s *arrayStack) push(v int) { // Срез автоматически расширяется s.data = append(s.data, v) } /* Извлечь из стека */ func (s *arrayStack) pop() any { val := s.peek() s.data = s.data[:len(s.data)-1] return val } /* Получить элемент на вершине стека */ func (s *arrayStack) peek() any { if s.isEmpty() { return nil } val := s.data[len(s.data)-1] return val } /* Получить Slice для вывода */ func (s *arrayStack) toSlice() []int { return s.data } ================================================ FILE: ru/codes/go/chapter_stack_and_queue/deque_test.go ================================================ // File: deque_test.go // Created Time: 2022-11-29 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestDeque(t *testing.T) { /* Инициализация двусторонней очереди */ // В Go list используется как двусторонняя очередь deque := list.New() /* Добавление элемента в очередь */ deque.PushBack(2) deque.PushBack(5) deque.PushBack(4) deque.PushFront(3) deque.PushFront(1) fmt.Print("Двусторонняя очередь deque = ") PrintList(deque) /* Доступ к элементу */ front := deque.Front() fmt.Println("Первый элемент front =", front.Value) rear := deque.Back() fmt.Println("Последний элемент rear =", rear.Value) /* Извлечение элемента из очереди */ deque.Remove(front) fmt.Print("Извлеченный из головы элемент front = ", front.Value, ", deque после извлечения из головы = ") PrintList(deque) deque.Remove(rear) fmt.Print("Извлеченный из хвоста элемент rear = ", rear.Value, ", deque после извлечения из хвоста = ") PrintList(deque) /* Получение длины двусторонней очереди */ size := deque.Len() fmt.Println("Длина двусторонней очереди size =", size) /* Проверка, пуста ли двусторонняя очередь */ isEmpty := deque.Len() == 0 fmt.Println("Пуста ли двусторонняя очередь =", isEmpty) } func TestArrayDeque(t *testing.T) { /* Инициализация двусторонней очереди */ // В Go list используется как двусторонняя очередь deque := newArrayDeque(16) /* Добавление элемента в очередь */ deque.pushLast(3) deque.pushLast(2) deque.pushLast(5) fmt.Print("Двусторонняя очередь deque = ") PrintSlice(deque.toSlice()) /* Доступ к элементу */ peekFirst := deque.peekFirst() fmt.Println("Первый элемент peekFirst =", peekFirst) peekLast := deque.peekLast() fmt.Println("Последний элемент peekLast =", peekLast) /* Добавление элемента в очередь */ deque.pushLast(4) fmt.Print("После добавления элемента 4 в хвост deque = ") PrintSlice(deque.toSlice()) deque.pushFirst(1) fmt.Print("После добавления элемента 1 в голову deque = ") PrintSlice(deque.toSlice()) /* Извлечение элемента из очереди */ popFirst := deque.popFirst() fmt.Print("Извлеченный из головы элемент popFirst = ", popFirst, ", deque после извлечения из головы = ") PrintSlice(deque.toSlice()) popLast := deque.popLast() fmt.Print("Извлеченный из хвоста элемент popLast = ", popLast, ", deque после извлечения из хвоста = ") PrintSlice(deque.toSlice()) /* Получение длины двусторонней очереди */ size := deque.size() fmt.Println("Длина двусторонней очереди size =", size) /* Проверка, пуста ли двусторонняя очередь */ isEmpty := deque.isEmpty() fmt.Println("Пуста ли двусторонняя очередь =", isEmpty) } func TestLinkedListDeque(t *testing.T) { // Инициализация очереди deque := newLinkedListDeque() // Добавление элемента в очередь deque.pushLast(2) deque.pushLast(5) deque.pushLast(4) deque.pushFirst(3) deque.pushFirst(1) fmt.Print("Очередь deque = ") PrintList(deque.toList()) // Доступ к элементу в начале очереди front := deque.peekFirst() fmt.Println("Первый элемент front =", front) rear := deque.peekLast() fmt.Println("Последний элемент rear =", rear) // Извлечение элемента из очереди popFirst := deque.popFirst() fmt.Print("Извлеченный из головы элемент popFirst = ", popFirst, ", deque после извлечения из головы = ") PrintList(deque.toList()) popLast := deque.popLast() fmt.Print("Извлеченный из хвоста элемент popLast = ", popLast, ", deque после извлечения из хвоста = ") PrintList(deque.toList()) // Получить длину очереди size := deque.size() fmt.Println("Длина очереди size =", size) // Проверка на пустоту isEmpty := deque.isEmpty() fmt.Println("Пуста ли очередь =", isEmpty) } // BenchmarkLinkedListDeque 67.92 ns/op in Mac M1 Pro func BenchmarkLinkedListDeque(b *testing.B) { deque := newLinkedListDeque() // use b.N for looping for i := 0; i < b.N; i++ { deque.pushLast(777) } for i := 0; i < b.N; i++ { deque.popFirst() } } ================================================ FILE: ru/codes/go/chapter_stack_and_queue/linkedlist_deque.go ================================================ // File: linkedlist_deque.go // Created Time: 2022-11-29 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* Двусторонняя очередь на основе двусвязного списка */ type linkedListDeque struct { // Использовать встроенный пакет list data *list.List } /* Инициализировать двустороннюю очередь */ func newLinkedListDeque() *linkedListDeque { return &linkedListDeque{ data: list.New(), } } /* Поместить элемент в голову очереди */ func (s *linkedListDeque) pushFirst(value any) { s.data.PushFront(value) } /* Поместить элемент в хвост очереди */ func (s *linkedListDeque) pushLast(value any) { s.data.PushBack(value) } /* Извлечь элемент из головы очереди */ func (s *linkedListDeque) popFirst() any { if s.isEmpty() { return nil } e := s.data.Front() s.data.Remove(e) return e.Value } /* Извлечь элемент из хвоста очереди */ func (s *linkedListDeque) popLast() any { if s.isEmpty() { return nil } e := s.data.Back() s.data.Remove(e) return e.Value } /* Доступ к элементу в начале очереди */ func (s *linkedListDeque) peekFirst() any { if s.isEmpty() { return nil } e := s.data.Front() return e.Value } /* Доступ к элементу в конце очереди */ func (s *linkedListDeque) peekLast() any { if s.isEmpty() { return nil } e := s.data.Back() return e.Value } /* Получение длины очереди */ func (s *linkedListDeque) size() int { return s.data.Len() } /* Проверка, пуста ли очередь */ func (s *linkedListDeque) isEmpty() bool { return s.data.Len() == 0 } /* Получить List для вывода */ func (s *linkedListDeque) toList() *list.List { return s.data } ================================================ FILE: ru/codes/go/chapter_stack_and_queue/linkedlist_queue.go ================================================ // File: linkedlist_queue.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* Очередь на основе связного списка */ type linkedListQueue struct { // Использовать встроенный пакет list для реализации очереди data *list.List } /* Инициализация очереди */ func newLinkedListQueue() *linkedListQueue { return &linkedListQueue{ data: list.New(), } } /* Поместить в очередь */ func (s *linkedListQueue) push(value any) { s.data.PushBack(value) } /* Извлечь из очереди */ func (s *linkedListQueue) pop() any { if s.isEmpty() { return nil } e := s.data.Front() s.data.Remove(e) return e.Value } /* Доступ к элементу в начале очереди */ func (s *linkedListQueue) peek() any { if s.isEmpty() { return nil } e := s.data.Front() return e.Value } /* Получение длины очереди */ func (s *linkedListQueue) size() int { return s.data.Len() } /* Проверка, пуста ли очередь */ func (s *linkedListQueue) isEmpty() bool { return s.data.Len() == 0 } /* Получить List для вывода */ func (s *linkedListQueue) toList() *list.List { return s.data } ================================================ FILE: ru/codes/go/chapter_stack_and_queue/linkedlist_stack.go ================================================ // File: linkedlist_stack.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* Стек на основе связного списка */ type linkedListStack struct { // Использовать встроенный пакет list для реализации стека data *list.List } /* Инициализация стека */ func newLinkedListStack() *linkedListStack { return &linkedListStack{ data: list.New(), } } /* Поместить в стек */ func (s *linkedListStack) push(value int) { s.data.PushBack(value) } /* Извлечь из стека */ func (s *linkedListStack) pop() any { if s.isEmpty() { return nil } e := s.data.Back() s.data.Remove(e) return e.Value } /* Доступ к верхнему элементу стека */ func (s *linkedListStack) peek() any { if s.isEmpty() { return nil } e := s.data.Back() return e.Value } /* Получение длины стека */ func (s *linkedListStack) size() int { return s.data.Len() } /* Проверка, пуст ли стек */ func (s *linkedListStack) isEmpty() bool { return s.data.Len() == 0 } /* Получить List для вывода */ func (s *linkedListStack) toList() *list.List { return s.data } ================================================ FILE: ru/codes/go/chapter_stack_and_queue/queue_test.go ================================================ // File: queue_test.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestQueue(t *testing.T) { /* Инициализация очереди */ // В Go list используется как очередь queue := list.New() /* Добавление элемента в очередь */ queue.PushBack(1) queue.PushBack(3) queue.PushBack(2) queue.PushBack(5) queue.PushBack(4) fmt.Print("Очередь queue = ") PrintList(queue) /* Доступ к элементу в начале очереди */ peek := queue.Front() fmt.Println("Первый элемент peek =", peek.Value) /* Извлечение элемента из очереди */ pop := queue.Front() queue.Remove(pop) fmt.Print("Извлеченный элемент pop = ", pop.Value, ", queue после извлечения = ") PrintList(queue) /* Получение длины очереди */ size := queue.Len() fmt.Println("Длина очереди size =", size) /* Проверка, пуста ли очередь */ isEmpty := queue.Len() == 0 fmt.Println("Пуста ли очередь =", isEmpty) } func TestArrayQueue(t *testing.T) { // Инициализировать очередь, используя общий интерфейс очереди capacity := 10 queue := newArrayQueue(capacity) if queue.pop() != nil { t.Errorf("want:%v,got:%v", nil, queue.pop()) } // Добавление элемента в очередь queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) fmt.Print("Очередь queue = ") PrintSlice(queue.toSlice()) // Доступ к элементу в начале очереди peek := queue.peek() fmt.Println("Первый элемент peek =", peek) // Извлечение элемента из очереди pop := queue.pop() fmt.Print("Извлеченный элемент pop = ", pop, ", queue после извлечения = ") PrintSlice(queue.toSlice()) // Получить длину очереди size := queue.size() fmt.Println("Длина очереди size =", size) // Проверка на пустоту isEmpty := queue.isEmpty() fmt.Println("Пуста ли очередь =", isEmpty) /* Проверка кольцевого массива */ for i := 0; i < 10; i++ { queue.push(i) queue.pop() fmt.Print("После ", i, "-го раунда операций enqueue и dequeue queue =") PrintSlice(queue.toSlice()) } } func TestLinkedListQueue(t *testing.T) { // Инициализировать очередь queue := newLinkedListQueue() // Добавление элемента в очередь queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) fmt.Print("Очередь queue = ") PrintList(queue.toList()) // Доступ к элементу в начале очереди peek := queue.peek() fmt.Println("Первый элемент peek =", peek) // Извлечение элемента из очереди pop := queue.pop() fmt.Print("Извлеченный элемент pop = ", pop, ", queue после извлечения = ") PrintList(queue.toList()) // Получить длину очереди size := queue.size() fmt.Println("Длина очереди size =", size) // Проверка на пустоту isEmpty := queue.isEmpty() fmt.Println("Пуста ли очередь =", isEmpty) } // BenchmarkArrayQueue 8 ns/op in Mac M1 Pro func BenchmarkArrayQueue(b *testing.B) { capacity := 1000 queue := newArrayQueue(capacity) // use b.N for looping for i := 0; i < b.N; i++ { queue.push(777) } for i := 0; i < b.N; i++ { queue.pop() } } // BenchmarkLinkedQueue 62.66 ns/op in Mac M1 Pro func BenchmarkLinkedQueue(b *testing.B) { queue := newLinkedListQueue() // use b.N for looping for i := 0; i < b.N; i++ { queue.push(777) } for i := 0; i < b.N; i++ { queue.pop() } } ================================================ FILE: ru/codes/go/chapter_stack_and_queue/stack_test.go ================================================ // File: stack_test.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestStack(t *testing.T) { /* Инициализация стека */ // В Go рекомендуется использовать Slice как стек var stack []int /* Помещение элемента в стек */ stack = append(stack, 1) stack = append(stack, 3) stack = append(stack, 2) stack = append(stack, 5) stack = append(stack, 4) fmt.Print("Стек stack = ") PrintSlice(stack) /* Доступ к верхнему элементу стека */ peek := stack[len(stack)-1] fmt.Println("Верхний элемент peek =", peek) /* Извлечение элемента из стека */ pop := stack[len(stack)-1] stack = stack[:len(stack)-1] fmt.Print("Извлеченный элемент pop = ", pop, ", stack после извлечения = ") PrintSlice(stack) /* Получение длины стека */ size := len(stack) fmt.Println("Длина стека size =", size) /* Проверка на пустоту */ isEmpty := len(stack) == 0 fmt.Println("Пуст ли стек =", isEmpty) } func TestArrayStack(t *testing.T) { // Инициализировать стек, используя интерфейс stack := newArrayStack() // Помещение элемента в стек stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) fmt.Print("Стек stack = ") PrintSlice(stack.toSlice()) // Доступ к верхнему элементу стека peek := stack.peek() fmt.Println("Верхний элемент peek =", peek) // Извлечение элемента из стека pop := stack.pop() fmt.Print("Извлеченный элемент pop = ", pop, ", stack после извлечения = ") PrintSlice(stack.toSlice()) // Получение длины стека size := stack.size() fmt.Println("Длина стека size =", size) // Проверка на пустоту isEmpty := stack.isEmpty() fmt.Println("Пуст ли стек =", isEmpty) } func TestLinkedListStack(t *testing.T) { // Инициализация стека stack := newLinkedListStack() // Помещение элемента в стек stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) fmt.Print("Стек stack = ") PrintList(stack.toList()) // Доступ к верхнему элементу стека peek := stack.peek() fmt.Println("Верхний элемент peek =", peek) // Извлечение элемента из стека pop := stack.pop() fmt.Print("Извлеченный элемент pop = ", pop, ", stack после извлечения = ") PrintList(stack.toList()) // Получение длины стека size := stack.size() fmt.Println("Длина стека size =", size) // Проверка на пустоту isEmpty := stack.isEmpty() fmt.Println("Пуст ли стек =", isEmpty) } // BenchmarkArrayStack 8 ns/op in Mac M1 Pro func BenchmarkArrayStack(b *testing.B) { stack := newArrayStack() // use b.N for looping for i := 0; i < b.N; i++ { stack.push(777) } for i := 0; i < b.N; i++ { stack.pop() } } // BenchmarkLinkedListStack 65.02 ns/op in Mac M1 Pro func BenchmarkLinkedListStack(b *testing.B) { stack := newLinkedListStack() // use b.N for looping for i := 0; i < b.N; i++ { stack.push(777) } for i := 0; i < b.N; i++ { stack.pop() } } ================================================ FILE: ru/codes/go/chapter_tree/array_binary_tree.go ================================================ // File: array_binary_tree.go // Created Time: 2023-07-24 // Author: Reanon (793584285@qq.com) package chapter_tree /* Класс двоичного дерева в массивном представлении */ type arrayBinaryTree struct { tree []any } /* Конструктор */ func newArrayBinaryTree(arr []any) *arrayBinaryTree { return &arrayBinaryTree{ tree: arr, } } /* Вместимость списка */ func (abt *arrayBinaryTree) size() int { return len(abt.tree) } /* Получить значение узла с индексом i */ func (abt *arrayBinaryTree) val(i int) any { // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию if i < 0 || i >= abt.size() { return nil } return abt.tree[i] } /* Получить индекс левого дочернего узла узла с индексом i */ func (abt *arrayBinaryTree) left(i int) int { return 2*i + 1 } /* Получить индекс правого дочернего узла узла с индексом i */ func (abt *arrayBinaryTree) right(i int) int { return 2*i + 2 } /* Получить индекс родительского узла узла с индексом i */ func (abt *arrayBinaryTree) parent(i int) int { return (i - 1) / 2 } /* Обход в ширину */ func (abt *arrayBinaryTree) levelOrder() []any { var res []any // Непосредственно обходить массив for i := 0; i < abt.size(); i++ { if abt.val(i) != nil { res = append(res, abt.val(i)) } } return res } /* Обход в глубину */ func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) { // Если это пустая позиция, вернуть if abt.val(i) == nil { return } // Предварительный обход if order == "pre" { *res = append(*res, abt.val(i)) } abt.dfs(abt.left(i), order, res) // Симметричный обход if order == "in" { *res = append(*res, abt.val(i)) } abt.dfs(abt.right(i), order, res) // Обратный обход if order == "post" { *res = append(*res, abt.val(i)) } } /* Предварительный обход */ func (abt *arrayBinaryTree) preOrder() []any { var res []any abt.dfs(0, "pre", &res) return res } /* Симметричный обход */ func (abt *arrayBinaryTree) inOrder() []any { var res []any abt.dfs(0, "in", &res) return res } /* Обратный обход */ func (abt *arrayBinaryTree) postOrder() []any { var res []any abt.dfs(0, "post", &res) return res } ================================================ FILE: ru/codes/go/chapter_tree/array_binary_tree_test.go ================================================ // File: array_binary_tree_test.go // Created Time: 2023-07-24 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestArrayBinaryTree(t *testing.T) { // Инициализировать двоичное дерево // Здесь используется функция, напрямую строящая двоичное дерево из массива arr := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} root := SliceToTree(arr) fmt.Println("\nИнициализация двоичного дерева") fmt.Println("Массивное представление двоичного дерева:") fmt.Println(arr) fmt.Println("Связное представление двоичного дерева:") PrintTree(root) // Класс двоичного дерева в массивном представлении abt := newArrayBinaryTree(arr) // Доступ к узлу i := 1 l := abt.left(i) r := abt.right(i) p := abt.parent(i) fmt.Println("\nТекущий узел: индекс =", i, ", значение =", abt.val(i)) fmt.Println("Индекс левого дочернего узла =", l, ", значение =", abt.val(l)) fmt.Println("Индекс правого дочернего узла =", r, ", значение =", abt.val(r)) fmt.Println("Индекс родительского узла =", p, ", значение =", abt.val(p)) // Обходить дерево res := abt.levelOrder() fmt.Println("\nОбход в ширину =", res) res = abt.preOrder() fmt.Println("Предварительный обход =", res) res = abt.inOrder() fmt.Println("Симметричный обход =", res) res = abt.postOrder() fmt.Println("Обратный обход =", res) } ================================================ FILE: ru/codes/go/chapter_tree/avl_tree.go ================================================ // File: avl_tree.go // Created Time: 2023-01-08 // Author: Reanon (793584285@qq.com) package chapter_tree import . "github.com/krahets/hello-algo/pkg" /* AVL-дерево */ type aVLTree struct { // Корневой узел root *TreeNode } func newAVLTree() *aVLTree { return &aVLTree{root: nil} } /* Получить высоту узла */ func (t *aVLTree) height(node *TreeNode) int { // Высота пустого узла равна -1, высота листового узла равна 0 if node != nil { return node.Height } return -1 } /* Обновить высоту узла */ func (t *aVLTree) updateHeight(node *TreeNode) { lh := t.height(node.Left) rh := t.height(node.Right) // Высота узла равна высоте более высокого поддерева + 1 if lh > rh { node.Height = lh + 1 } else { node.Height = rh + 1 } } /* Получить коэффициент баланса */ func (t *aVLTree) balanceFactor(node *TreeNode) int { // Коэффициент баланса пустого узла равен 0 if node == nil { return 0 } // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева return t.height(node.Left) - t.height(node.Right) } /* Операция правого вращения */ func (t *aVLTree) rightRotate(node *TreeNode) *TreeNode { child := node.Left grandChild := child.Right // Выполнить правое вращение узла node вокруг child child.Right = node node.Left = grandChild // Обновить высоту узла t.updateHeight(node) t.updateHeight(child) // Вернуть корневой узел поддерева после вращения return child } /* Операция левого вращения */ func (t *aVLTree) leftRotate(node *TreeNode) *TreeNode { child := node.Right grandChild := child.Left // Выполнить левое вращение узла node вокруг child child.Left = node node.Right = grandChild // Обновить высоту узла t.updateHeight(node) t.updateHeight(child) // Вернуть корневой узел поддерева после вращения return child } /* Выполнить вращение, чтобы снова сбалансировать поддерево */ func (t *aVLTree) rotate(node *TreeNode) *TreeNode { // Получить коэффициент баланса узла node // В Go рекомендуется использовать короткие имена переменных, здесь bf обозначает t.balanceFactor bf := t.balanceFactor(node) // Левосторонне перекошенное дерево if bf > 1 { if t.balanceFactor(node.Left) >= 0 { // Правое вращение return t.rightRotate(node) } else { // Сначала левое вращение, затем правое node.Left = t.leftRotate(node.Left) return t.rightRotate(node) } } // Правосторонне перекошенное дерево if bf < -1 { if t.balanceFactor(node.Right) <= 0 { // Левое вращение return t.leftRotate(node) } else { // Сначала правое вращение, затем левое node.Right = t.rightRotate(node.Right) return t.leftRotate(node) } } // Дерево сбалансировано, вращение не требуется, вернуть сразу return node } /* Вставка узла */ func (t *aVLTree) insert(val int) { t.root = t.insertHelper(t.root, val) } /* Рекурсивная вставка узла (вспомогательная функция) */ func (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode { if node == nil { return NewTreeNode(val) } /* 1. Найти позицию вставки и вставить узел */ if val < node.Val.(int) { node.Left = t.insertHelper(node.Left, val) } else if val > node.Val.(int) { node.Right = t.insertHelper(node.Right, val) } else { // Повторяющийся узел не вставлять, сразу вернуть return node } // Обновить высоту узла t.updateHeight(node) /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = t.rotate(node) // Вернуть корневой узел поддерева return node } /* Удаление узла */ func (t *aVLTree) remove(val int) { t.root = t.removeHelper(t.root, val) } /* Рекурсивное удаление узла (вспомогательная функция) */ func (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode { if node == nil { return nil } /* 1. Найти узел и удалить его */ if val < node.Val.(int) { node.Left = t.removeHelper(node.Left, val) } else if val > node.Val.(int) { node.Right = t.removeHelper(node.Right, val) } else { if node.Left == nil || node.Right == nil { child := node.Left if node.Right != nil { child = node.Right } if child == nil { // Число дочерних узлов = 0, удалить node и сразу вернуть return nil } else { // Число дочерних узлов = 1, удалить node напрямую node = child } } else { // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел temp := node.Right for temp.Left != nil { temp = temp.Left } node.Right = t.removeHelper(node.Right, temp.Val.(int)) node.Val = temp.Val } } // Обновить высоту узла t.updateHeight(node) /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = t.rotate(node) // Вернуть корневой узел поддерева return node } /* Поиск узла */ func (t *aVLTree) search(val int) *TreeNode { cur := t.root // Искать в цикле и выйти после прохода за листовой узел for cur != nil { if cur.Val.(int) < val { // Целевой узел находится в правом поддереве cur cur = cur.Right } else if cur.Val.(int) > val { // Целевой узел находится в левом поддереве cur cur = cur.Left } else { // Найти целевой узел и выйти из цикла break } } // Вернуть целевой узел return cur } ================================================ FILE: ru/codes/go/chapter_tree/avl_tree_test.go ================================================ // File: avl_tree_test.go // Created Time: 2023-01-08 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestAVLTree(t *testing.T) { /* Инициализация пустого AVL-дерева */ tree := newAVLTree() /* Вставка узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла testInsert(tree, 1) testInsert(tree, 2) testInsert(tree, 3) testInsert(tree, 4) testInsert(tree, 5) testInsert(tree, 8) testInsert(tree, 7) testInsert(tree, 9) testInsert(tree, 10) testInsert(tree, 6) /* Вставка повторяющегося узла */ testInsert(tree, 7) /* Удаление узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла testRemove(tree, 8) // Удаление узла степени 0 testRemove(tree, 5) // Удаление узла степени 1 testRemove(tree, 4) // Удаление узла степени 2 /* Поиск узла */ node := tree.search(7) fmt.Printf("\nНайденный объект узла = %#v, значение узла = %d\n", node, node.Val) } func testInsert(tree *aVLTree, val int) { tree.insert(val) fmt.Printf("\nПосле вставки узла %d AVL-дерево имеет вид\n", val) PrintTree(tree.root) } func testRemove(tree *aVLTree, val int) { tree.remove(val) fmt.Printf("\nПосле удаления узла %d AVL-дерево имеет вид\n", val) PrintTree(tree.root) } ================================================ FILE: ru/codes/go/chapter_tree/binary_search_tree.go ================================================ // File: binary_search_tree.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( . "github.com/krahets/hello-algo/pkg" ) type binarySearchTree struct { root *TreeNode } func newBinarySearchTree() *binarySearchTree { bst := &binarySearchTree{} // Инициализировать пустое дерево bst.root = nil return bst } /* Получить корневой узел */ func (bst *binarySearchTree) getRoot() *TreeNode { return bst.root } /* Поиск узла */ func (bst *binarySearchTree) search(num int) *TreeNode { node := bst.root // Искать в цикле и выйти после прохода за листовой узел for node != nil { if node.Val.(int) < num { // Целевой узел находится в правом поддереве cur node = node.Right } else if node.Val.(int) > num { // Целевой узел находится в левом поддереве cur node = node.Left } else { // Найти целевой узел и выйти из цикла break } } // Вернуть целевой узел return node } /* Вставка узла */ func (bst *binarySearchTree) insert(num int) { cur := bst.root // Если дерево пусто, инициализировать корневой узел if cur == nil { bst.root = NewTreeNode(num) return } // Позиция узла, предшествующего вставляемому var pre *TreeNode = nil // Искать в цикле и выйти после прохода за листовой узел for cur != nil { if cur.Val == num { return } pre = cur if cur.Val.(int) < num { cur = cur.Right } else { cur = cur.Left } } // Вставка узла node := NewTreeNode(num) if pre.Val.(int) < num { pre.Right = node } else { pre.Left = node } } /* Удаление узла */ func (bst *binarySearchTree) remove(num int) { cur := bst.root // Если дерево пусто, сразу вернуть if cur == nil { return } // Позиция узла, предшествующего удаляемому var pre *TreeNode = nil // Искать в цикле и выйти после прохода за листовой узел for cur != nil { if cur.Val == num { break } pre = cur if cur.Val.(int) < num { // Удаляемый узел находится в правом поддереве cur = cur.Right } else { // Удаляемый узел находится в левом поддереве cur = cur.Left } } // Если узел для удаления отсутствует, сразу вернуть if cur == nil { return } // Число дочерних узлов равно 0 или 1 if cur.Left == nil || cur.Right == nil { var child *TreeNode = nil // Извлечь дочерний узел удаляемого узла if cur.Left != nil { child = cur.Left } else { child = cur.Right } // Удалить узел cur if cur != bst.root { if pre.Left == cur { pre.Left = child } else { pre.Right = child } } else { // Если удаляемый узел является корнем, заново назначить корневой узел bst.root = child } // Число дочерних узлов равно 2 } else { // Получить следующий после cur узел в симметричном обходе для удаляемого узла tmp := cur.Right for tmp.Left != nil { tmp = tmp.Left } // Рекурсивно удалить узел tmp bst.remove(tmp.Val.(int)) // Перезаписать cur значением tmp cur.Val = tmp.Val } } /* Вывести двоичное дерево поиска */ func (bst *binarySearchTree) print() { PrintTree(bst.root) } ================================================ FILE: ru/codes/go/chapter_tree/binary_search_tree_test.go ================================================ // File: binary_search_tree_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" ) func TestBinarySearchTree(t *testing.T) { bst := newBinarySearchTree() nums := []int{8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15} // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево for _, num := range nums { bst.insert(num) } fmt.Println("\nИсходное двоичное дерево:") bst.print() // Получить корневой узел node := bst.getRoot() fmt.Println("\nКорневой узел двоичного дерева:", node.Val) // Поиск узла node = bst.search(7) fmt.Println("Найденный объект узла =", node, ", значение узла =", node.Val) // Вставка узла bst.insert(16) fmt.Println("\nПосле вставки узла 16 двоичное дерево имеет вид:") bst.print() // Удаление узла bst.remove(1) fmt.Println("\nПосле удаления узла 1 двоичное дерево имеет вид:") bst.print() bst.remove(2) fmt.Println("\nПосле удаления узла 2 двоичное дерево имеет вид:") bst.print() bst.remove(4) fmt.Println("\nПосле удаления узла 4 двоичное дерево имеет вид:") bst.print() } ================================================ FILE: ru/codes/go/chapter_tree/binary_tree_bfs.go ================================================ // File: binary_tree_bfs.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "container/list" . "github.com/krahets/hello-algo/pkg" ) /* Обход в ширину */ func levelOrder(root *TreeNode) []any { // Инициализировать очередь и добавить корневой узел queue := list.New() queue.PushBack(root) // Инициализировать срез для хранения последовательности обхода nums := make([]any, 0) for queue.Len() > 0 { // Извлечение из очереди node := queue.Remove(queue.Front()).(*TreeNode) // Сохранить значение узла nums = append(nums, node.Val) if node.Left != nil { // Поместить левый дочерний узел в очередь queue.PushBack(node.Left) } if node.Right != nil { // Поместить правый дочерний узел в очередь queue.PushBack(node.Right) } } return nums } ================================================ FILE: ru/codes/go/chapter_tree/binary_tree_bfs_test.go ================================================ // File: binary_tree_bfs_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLevelOrder(t *testing.T) { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\nИнициализация двоичного дерева:") PrintTree(root) // Обход в ширину nums := levelOrder(root) fmt.Println("\nПоследовательность печати узлов при обходе в ширину =", nums) } ================================================ FILE: ru/codes/go/chapter_tree/binary_tree_dfs.go ================================================ // File: binary_tree_dfs.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( . "github.com/krahets/hello-algo/pkg" ) var nums []any /* Предварительный обход */ func preOrder(node *TreeNode) { if node == nil { return } // Порядок обхода: корень -> левое поддерево -> правое поддерево nums = append(nums, node.Val) preOrder(node.Left) preOrder(node.Right) } /* Симметричный обход */ func inOrder(node *TreeNode) { if node == nil { return } // Порядок обхода: левое поддерево -> корень -> правое поддерево inOrder(node.Left) nums = append(nums, node.Val) inOrder(node.Right) } /* Обратный обход */ func postOrder(node *TreeNode) { if node == nil { return } // Порядок обхода: левое поддерево -> правое поддерево -> корень postOrder(node.Left) postOrder(node.Right) nums = append(nums, node.Val) } ================================================ FILE: ru/codes/go/chapter_tree/binary_tree_dfs_test.go ================================================ // File: binary_tree_dfs_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPreInPostOrderTraversal(t *testing.T) { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\nИнициализация двоичного дерева:") PrintTree(root) // Предварительный обход nums = nil preOrder(root) fmt.Println("\nПоследовательность печати узлов при предварительном обходе =", nums) // Симметричный обход nums = nil inOrder(root) fmt.Println("\nПоследовательность печати узлов при симметричном обходе =", nums) // Обратный обход nums = nil postOrder(root) fmt.Println("\nПоследовательность печати узлов при обратном обходе =", nums) } ================================================ FILE: ru/codes/go/chapter_tree/binary_tree_test.go ================================================ // File: binary_tree_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestBinaryTree(t *testing.T) { /* Инициализация двоичного дерева */ // Инициализация узла n1 := NewTreeNode(1) n2 := NewTreeNode(2) n3 := NewTreeNode(3) n4 := NewTreeNode(4) n5 := NewTreeNode(5) // Построить связи между узлами (указатели) n1.Left = n2 n1.Right = n3 n2.Left = n4 n2.Right = n5 fmt.Println("Инициализация двоичного дерева") PrintTree(n1) /* Вставка и удаление узлов */ // Вставка узла p := NewTreeNode(0) n1.Left = p p.Left = n2 fmt.Println("После вставки узла P") PrintTree(n1) // Удаление узла n1.Left = n2 fmt.Println("После удаления узла P") PrintTree(n1) } ================================================ FILE: ru/codes/go/go.mod ================================================ module github.com/krahets/hello-algo go 1.19 ================================================ FILE: ru/codes/go/pkg/list_node.go ================================================ // File: list_node.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg // ListNode: узел связного списка type ListNode struct { Next *ListNode Val int } // NewListNode: конструктор узла связного списка func NewListNode(v int) *ListNode { return &ListNode{ Next: nil, Val: v, } } // ArrayToLinkedList десериализует массив в связный список func ArrayToLinkedList(arr []int) *ListNode { // dummy header of linked list dummy := NewListNode(0) node := dummy for _, val := range arr { node.Next = NewListNode(val) node = node.Next } return dummy.Next } ================================================ FILE: ru/codes/go/pkg/list_node_test.go ================================================ // File: list_node_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg import ( "testing" ) func TestListNode(t *testing.T) { arr := []int{2, 3, 5, 6, 7} head := ArrayToLinkedList(arr) PrintLinkedList(head) } ================================================ FILE: ru/codes/go/pkg/print_utils.go ================================================ // File: print_utils.go // Created Time: 2022-12-03 // Author: Reanon (793584285@qq.com), krahets (krahets@163.com), msk397 (machangxinq@gmail.com) package pkg import ( "container/list" "fmt" "strconv" "strings" ) // PrintSlice: вывести срез func PrintSlice[T any](nums []T) { fmt.Printf("%v", nums) fmt.Println() } // PrintList: вывести список func PrintList(list *list.List) { if list.Len() == 0 { fmt.Print("[]\n") return } e := list.Front() // Преобразование к string повлияет на эффективность fmt.Print("[") for e.Next() != nil { fmt.Print(e.Value, " ") e = e.Next() } fmt.Print(e.Value, "]\n") } // PrintMap: вывести хеш-таблицу func PrintMap[K comparable, V any](m map[K]V) { for key, value := range m { fmt.Println(key, "->", value) } } // PrintHeap: вывести кучу func PrintHeap(h []any) { fmt.Printf("Массивное представление кучи:") fmt.Printf("%v", h) fmt.Printf("\nДревовидное представление кучи:\n") root := SliceToTree(h) PrintTree(root) } // PrintLinkedList: вывести связный список func PrintLinkedList(node *ListNode) { if node == nil { return } var builder strings.Builder for node.Next != nil { builder.WriteString(strconv.Itoa(node.Val) + " -> ") node = node.Next } builder.WriteString(strconv.Itoa(node.Val)) fmt.Println(builder.String()) } // PrintTree: вывести двоичное дерево func PrintTree(root *TreeNode) { printTreeHelper(root, nil, false) } // printTreeHelper: вывести двоичное дерево // Этот вывод дерева заимствован из TECHIE DELIGHT // https://www.techiedelight.com/c-program-print-binary-tree/ func printTreeHelper(root *TreeNode, prev *trunk, isRight bool) { if root == nil { return } prevStr := " " trunk := newTrunk(prev, prevStr) printTreeHelper(root.Right, trunk, true) if prev == nil { trunk.str = "———" } else if isRight { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev.str = prevStr } showTrunk(trunk) fmt.Println(root.Val) if prev != nil { prev.str = prevStr } trunk.str = " |" printTreeHelper(root.Left, trunk, false) } type trunk struct { prev *trunk str string } func newTrunk(prev *trunk, str string) *trunk { return &trunk{ prev: prev, str: str, } } func showTrunk(t *trunk) { if t == nil { return } showTrunk(t.prev) fmt.Print(t.str) } ================================================ FILE: ru/codes/go/pkg/tree_node.go ================================================ // File: tree_node.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg // TreeNode: узел двоичного дерева type TreeNode struct { Val any // Значение узла Height int // Высота узла Left *TreeNode // Ссылка на левый дочерний узел Right *TreeNode // Ссылка на правый дочерний узел } // NewTreeNode: конструктор узла двоичного дерева func NewTreeNode(v any) *TreeNode { return &TreeNode{ Val: v, Height: 0, Left: nil, Right: nil, } } // Правила кодирования сериализации см.: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Представление двоичного дерева массивом: // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] // Представление двоичного дерева связным списком: // // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // // ——— 1 // // \——— 2 // | /——— 9 // \——— 4 // \——— 8 // SliceToTreeDFS десериализует список в двоичное дерево: рекурсия func SliceToTreeDFS(arr []any, i int) *TreeNode { if i < 0 || i >= len(arr) || arr[i] == nil { return nil } root := NewTreeNode(arr[i]) root.Left = SliceToTreeDFS(arr, 2*i+1) root.Right = SliceToTreeDFS(arr, 2*i+2) return root } // SliceToTree десериализует срез в двоичное дерево func SliceToTree(arr []any) *TreeNode { return SliceToTreeDFS(arr, 0) } // TreeToSliceDFS сериализует двоичное дерево в срез: рекурсия func TreeToSliceDFS(root *TreeNode, i int, res *[]any) { if root == nil { return } for i >= len(*res) { *res = append(*res, nil) } (*res)[i] = root.Val TreeToSliceDFS(root.Left, 2*i+1, res) TreeToSliceDFS(root.Right, 2*i+2, res) } // TreeToSlice сериализует двоичное дерево в срез func TreeToSlice(root *TreeNode) []any { var res []any TreeToSliceDFS(root, 0, &res) return res } ================================================ FILE: ru/codes/go/pkg/tree_node_test.go ================================================ // File: tree_node_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg import ( "fmt" "testing" ) func TestTreeNode(t *testing.T) { arr := []any{1, 2, 3, nil, 5, 6, nil} node := SliceToTree(arr) // print tree PrintTree(node) // tree to arr fmt.Println(TreeToSlice(node)) } ================================================ FILE: ru/codes/go/pkg/vertex.go ================================================ // File: vertex.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package pkg // Vertex: класс вершины type Vertex struct { Val int } // NewVertex: конструктор вершины func NewVertex(val int) Vertex { return Vertex{ Val: val, } } // ValsToVets десериализует список значений в список вершин func ValsToVets(vals []int) []Vertex { vets := make([]Vertex, len(vals)) for i := 0; i < len(vals); i++ { vets[i] = NewVertex(vals[i]) } return vets } // VetsToVals сериализует список вершин в список значений func VetsToVals(vets []Vertex) []int { vals := make([]int, len(vets)) for i := range vets { vals[i] = vets[i].Val } return vals } // DeleteSliceElms удаляет указанный элемент из среза func DeleteSliceElms[T any](a []T, elms ...T) []T { if len(a) == 0 || len(elms) == 0 { return a } // Сначала преобразовать элементы в set m := make(map[any]struct{}) for _, v := range elms { m[v] = struct{}{} } // Отфильтровать указанный элемент res := make([]T, 0, len(a)) for _, v := range a { if _, ok := m[v]; !ok { res = append(res, v) } } return res } ================================================ FILE: ru/codes/java/.gitignore ================================================ build ================================================ FILE: ru/codes/java/chapter_array_and_linkedlist/array.java ================================================ /** * 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; } /* Вставить элемент num по индексу index в массив */ static void insert(int[] nums, int num, int index) { // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад for (int i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Присвоить num элементу по индексу index nums[index] = num; } /* Удалить элемент по индексу index */ static void remove(int[] nums, int index) { // Сдвинуть все элементы после индекса index на одну позицию вперед 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; } /* Driver Code */ 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("После вставки числа 6 по индексу 3 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("Поиск элемента 3 в nums: индекс = " + index); } } ================================================ FILE: ru/codes/java/chapter_array_and_linkedlist/linked_list.java ================================================ /** * 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 { /* Вставить узел P после узла n0 в связном списке */ 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; } /* Driver Code */ public static void main(String[] args) { /* Инициализация связного списка */ // Инициализация всех узлов ListNode n0 = new ListNode(1); ListNode n1 = new ListNode(3); ListNode n2 = new ListNode(2); ListNode n3 = new ListNode(5); ListNode n4 = new ListNode(4); // Построить ссылки между узлами n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; System.out.println("Исходный связный список"); 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); } } ================================================ FILE: ru/codes/java/chapter_array_and_linkedlist/list.java ================================================ /** * 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) { /* Инициализация списка */ // Обратите внимание: тип элементов массива int[] — это обертка Integer[] Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; List nums = new ArrayList<>(Arrays.asList(numbers)); System.out.println("Список nums = " + nums); /* Доступ к элементу */ int num = nums.get(1); System.out.println("Элемент по индексу 1: num = " + num); /* Обновление элемента */ 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("После вставки числа 6 по индексу 3 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; } /* Объединить два списка */ 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); } } ================================================ FILE: ru/codes/java/chapter_array_and_linkedlist/my_list.java ================================================ /** * 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 и все следующие элементы на одну позицию назад 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 на одну позицию вперед 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 { /* Driver Code */ 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("После вставки числа 6 по индексу 3 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()); } } ================================================ FILE: ru/codes/java/chapter_backtracking/n_queens.java ================================================ /** * 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); } } } } ================================================ FILE: ru/codes/java/chapter_backtracking/permutations_i.java ================================================ /** * 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); } } ================================================ FILE: ru/codes/java/chapter_backtracking/permutations_ii.java ================================================ /** * 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); } } ================================================ FILE: ru/codes/java/chapter_backtracking/preorder_traversal_i_compact.java ================================================ /** * 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); } } ================================================ FILE: ru/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java ================================================ /** * 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); } } } ================================================ FILE: ru/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java ================================================ /** * 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); } } } ================================================ FILE: ru/codes/java/chapter_backtracking/preorder_traversal_iii_template.java ================================================ /** * 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); } } } ================================================ FILE: ru/codes/java/chapter_backtracking/subset_sum_i.java ================================================ /** * 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; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств for (int i = start; i < choices.length; i++) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if (target - choices[i] < 0) { break; } // Попытка: сделать выбор и обновить target и start state.add(choices[i]); // Перейти к следующему выбору backtrack(state, target - choices[i], choices, i, res); // Откат: отменить выбор и восстановить предыдущее состояние state.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); } } ================================================ FILE: ru/codes/java/chapter_backtracking/subset_sum_i_naive.java ================================================ /** * 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("Обратите внимание: результат этого метода содержит повторяющиеся множества"); } } ================================================ FILE: ru/codes/java/chapter_backtracking/subset_sum_ii.java ================================================ /** * 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; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента for (int i = start; i < choices.length; i++) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if (target - choices[i] < 0) { break; } // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить if (i > start && choices[i] == choices[i - 1]) { continue; } // Попытка: сделать выбор и обновить target и start state.add(choices[i]); // Перейти к следующему выбору backtrack(state, target - choices[i], choices, i + 1, res); // Откат: отменить выбор и восстановить предыдущее состояние state.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); } } ================================================ FILE: ru/codes/java/chapter_computational_complexity/iteration.java ================================================ /** * 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 (двойное обновление) */ static int whileLoopII(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i++; i *= 2; } return res; } /* Двойной цикл 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(); } /* Driver Code */ public static void main(String[] args) { int n = 5; int res; res = forLoop(n); System.out.println("\nРезультат суммирования в цикле for res = " + res); res = whileLoop(n); System.out.println("\nРезультат суммирования в цикле while res = " + res); res = whileLoopII(n); System.out.println("\nРезультат суммирования в цикле while (двойное обновление) res = " + res); String resStr = nestedForLoop(n); System.out.println("\nРезультат обхода в двойном цикле for " + resStr); } } ================================================ FILE: ru/codes/java/chapter_computational_complexity/recursion.java ================================================ /** * 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; } /* Driver Code */ 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); } } ================================================ FILE: ru/codes/java/chapter_computational_complexity/space_complexity.java ================================================ /** * 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; } /* Driver Code */ public static void main(String[] args) { int n = 5; // Постоянная сложность constant(n); // Линейная сложность linear(n); linearRecur(n); // Квадратичная сложность quadratic(n); quadraticRecur(n); // Экспоненциальная сложность TreeNode root = buildTree(n); PrintUtil.printTree(root); } } ================================================ FILE: ru/codes/java/chapter_computational_complexity/time_complexity.java ================================================ /** * 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; // На каждом шаге клетка делится надвое, образуя последовательность 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; // Из одного получается n for (int i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ 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); } } ================================================ FILE: ru/codes/java/chapter_computational_complexity/worst_best_time_complexity.java ================================================ /** * 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; } /* Найти индекс числа 1 в массиве nums */ 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; } /* Driver Code */ public static void main(String[] args) { for (int i = 0; i < 10; i++) { int n = 100; int[] nums = randomNumbers(n); int index = findOne(nums); System.out.println("\nМассив [1, 2, ..., n] после перемешивания = " + Arrays.toString(nums)); System.out.println("Индекс числа 1 = " + index); } } } ================================================ FILE: ru/codes/java/chapter_divide_and_conquer/binary_search_recur.java ================================================ /** * 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) / 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); } } ================================================ FILE: ru/codes/java/chapter_divide_and_conquer/build_tree.java ================================================ /** * 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) { // Инициализировать хеш-таблицу для хранения соответствия элементов 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); } } ================================================ FILE: ru/codes/java/chapter_divide_and_conquer/hanota.java ================================================ /** * 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 остался только один диск, сразу переместить его в tar if (i == 1) { move(src, tar); return; } // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar dfs(i - 1, src, tar, buf); // Подзадача f(1): переместить оставшийся один диск из src в tar move(src, tar); // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src dfs(i - 1, buf, src, tar); } /* Решить задачу Ханойской башни */ static void solveHanota(List A, List B, List C) { int n = A.size(); // Переместить верхние n дисков из A в C с помощью B 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); } } ================================================ FILE: ru/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java ================================================ /** * 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)); } } ================================================ FILE: ru/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java ================================================ /** * 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)); } } ================================================ FILE: ru/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java ================================================ /** * 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)); } } ================================================ FILE: ru/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java ================================================ /** * 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)); } } ================================================ FILE: ru/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java ================================================ /** * 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)); } } ================================================ FILE: ru/codes/java/chapter_dynamic_programming/coin_change.java ================================================ /** * 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); } } ================================================ FILE: ru/codes/java/chapter_dynamic_programming/coin_change_ii.java ================================================ /** * 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 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 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); } } ================================================ FILE: ru/codes/java/chapter_dynamic_programming/edit_distance.java ================================================ /** * 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; // Если два символа равны, сразу пропустить их if (s.charAt(i - 1) == t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 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]; // Если два символа равны, сразу пропустить их if (s.charAt(i - 1) == t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 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)) { // Если два символа равны, сразу пропустить их dp[i][j] = dp[i - 1][j - 1]; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 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)) { // Если два символа равны, сразу пропустить их dp[j] = leftup; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 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 + " шагов"); } } ================================================ FILE: ru/codes/java/chapter_dynamic_programming/knapsack.java ================================================ /** * 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]; // Вернуть вариант с большей стоимостью из двух возможных 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]; // Сохранить и вернуть вариант с большей стоимостью из двух решений 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); } } ================================================ FILE: ru/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java ================================================ /** * 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)); } } ================================================ FILE: ru/codes/java/chapter_dynamic_programming/min_path_sum.java ================================================ /** * 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); } } ================================================ FILE: ru/codes/java/chapter_dynamic_programming/unbounded_knapsack.java ================================================ /** * 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); } } ================================================ FILE: ru/codes/java/chapter_graph/graph_adjacency_list.java ================================================ /** * 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 { // Список смежности, где key — вершина, а value — все смежные ей вершины 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(); } } ================================================ FILE: ru/codes/java/chapter_graph/graph_adjacency_matrix.java ================================================ /** * 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); } // Добавить ребра // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices 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 соответствуют индексам элементов vertices public void addEdge(int i, int j) { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw new IndexOutOfBoundsException(); // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) adjMat.get(i).set(j, 1); adjMat.get(j).set(i, 1); } /* Удаление ребра */ // Параметры i и j соответствуют индексам элементов vertices public void removeEdge(int i, int j) { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw new 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) { /* Инициализация неориентированного графа */ // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices int[] vertices = { 1, 3, 2, 5, 4 }; int[][] edges = { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; GraphAdjMat graph = new GraphAdjMat(vertices, edges); System.out.println("\nГраф после инициализации"); 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(); } } ================================================ FILE: ru/codes/java/chapter_graph/graph_bfs.java ================================================ /** * 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)); } } ================================================ FILE: ru/codes/java/chapter_graph/graph_dfs.java ================================================ /** * 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)); } } ================================================ FILE: ru/codes/java/chapter_greedy/coin_change_greedy.java ================================================ /** * 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) { // Предположить, что список coins упорядочен int i = coins.length - 1; int count = 0; // Циклически выполнять жадный выбор, пока не останется суммы while (amt > 0) { // Найти монету, которая меньше остатка суммы и наиболее к нему близка while (i > 0 && coins[i] > amt) { i--; } // Выбрать coins[i] amt -= coins[i]; count++; } // Если допустимое решение не найдено, вернуть -1 return amt == 0 ? count : -1; } 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"); } } ================================================ FILE: ru/codes/java/chapter_greedy/fractional_knapsack.java ================================================ /** * 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) { // Создать список предметов с двумя свойствами: вес и стоимость 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); } } ================================================ FILE: ru/codes/java/chapter_greedy/max_capacity.java ================================================ /** * 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; // Выполнять жадный выбор в цикле, пока две доски не встретятся 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); } } ================================================ FILE: ru/codes/java/chapter_greedy/max_product_cutting.java ================================================ /** * 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 — число троек, а 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); } } ================================================ FILE: ru/codes/java/chapter_hashing/array_hash_map.java ================================================ /** * 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, "Сяо Ха"); map.put(15937, "Сяо Ло"); map.put(16750, "Сяо Суань"); map.put(13276, "Сяо Фа"); map.put(10583, "Сяо Я"); System.out.println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); map.print(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value String name = map.get(15937); System.out.println("\nДля номера 15937 найдено имя " + name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(10583); System.out.println("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); map.print(); /* Обход хеш-таблицы */ System.out.println("\nОтдельный обход пар ключ-значение"); for (Pair kv : map.pairSet()) { System.out.println(kv.key + " -> " + kv.val); } System.out.println("\nОтдельный обход ключей"); for (int key : map.keySet()) { System.out.println(key); } System.out.println("\nОтдельный обход значений"); for (String val : map.valueSet()) { System.out.println(val); } } } ================================================ FILE: ru/codes/java/chapter_hashing/built_in_hash.java ================================================ /** * 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 Algo"; int hashStr = str.hashCode(); System.out.println("Хеш-значение строки " + str + " = " + hashStr); Object[] arr = { 12836, "Сяо Ха" }; 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); } } ================================================ FILE: ru/codes/java/chapter_hashing/hash_map.java ================================================ /** * 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, "Сяо Ха"); map.put(15937, "Сяо Ло"); map.put(16750, "Сяо Суань"); map.put(13276, "Сяо Фа"); map.put(10583, "Сяо Я"); System.out.println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); PrintUtil.printHashMap(map); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value String name = map.get(15937); System.out.println("\nДля номера 15937 найдено имя " + name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(10583); System.out.println("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); PrintUtil.printHashMap(map); /* Обход хеш-таблицы */ System.out.println("\nОтдельный обход пар ключ-значение"); for (Map.Entry kv : map.entrySet()) { System.out.println(kv.getKey() + " -> " + kv.getValue()); } System.out.println("\nОтдельный обход ключей"); for (int key : map.keySet()) { System.out.println(key); } System.out.println("\nОтдельный обход значений"); for (String val : map.values()) { System.out.println(val); } } } ================================================ FILE: ru/codes/java/chapter_hashing/hash_map_chaining.java ================================================ /** * 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); // Обойти корзину; если найден key, вернуть соответствующее val for (Pair pair : bucket) { if (pair.key == key) { return pair.val; } } // Если key не найден, вернуть null return null; } /* Операция добавления */ void put(int key, String val) { // Когда коэффициент загрузки превышает порог, выполнить расширение if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); List bucket = buckets.get(index); // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть for (Pair pair : bucket) { if (pair.key == key) { pair.val = val; return; } } // Если такого key нет, добавить пару ключ-значение в конец Pair pair = new Pair(key, val); bucket.add(pair); size++; } /* Операция удаления */ 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, "Сяо Ха"); map.put(15937, "Сяо Ло"); map.put(16750, "Сяо Суань"); map.put(13276, "Сяо Фа"); map.put(10583, "Сяо Я"); System.out.println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); map.print(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value String name = map.get(13276); System.out.println("\nДля номера 13276 найдено имя " + name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(12836); System.out.println("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение"); map.print(); } } ================================================ FILE: ru/codes/java/chapter_hashing/hash_map_open_addressing.java ================================================ /** * 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, "Сяо Ха"); hashmap.put(15937, "Сяо Ло"); hashmap.put(16750, "Сяо Суань"); hashmap.put(13276, "Сяо Фа"); hashmap.put(10583, "Сяо Я"); System.out.println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); hashmap.print(); // Операция поиска // Передать ключ key в хеш-таблицу и получить значение val String name = hashmap.get(13276); System.out.println("\nДля номера 13276 найдено имя " + name); // Операция удаления // Удалить пару (key, val) из хеш-таблицы hashmap.remove(16750); System.out.println("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение"); hashmap.print(); } } ================================================ FILE: ru/codes/java/chapter_hashing/simple_hash.java ================================================ /** * 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 Algo"; 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); } } ================================================ FILE: ru/codes/java/chapter_heap/heap.java ================================================ /** * 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 с помощью lambda-выражения) Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); System.out.println("\nНиже приведен тестовый пример для max-heap"); /* Добавление элемента в кучу */ 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После построения min-heap из входного списка"); PrintUtil.printHeap(minHeap); } } ================================================ FILE: ru/codes/java/chapter_heap/my_heap.java ================================================ /** * 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); // Выполнить heapify для всех узлов, кроме листовых 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); // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) break; // Поменять два узла местами 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; // Поменять два узла местами 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); } } ================================================ FILE: ru/codes/java/chapter_heap/top_k.java ================================================ /** * 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); } } ================================================ FILE: ru/codes/java/chapter_searching/binary_search.java ================================================ /** * 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 указывают на первый элемент массива и позицию сразу за последним элементом соответственно 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); } } ================================================ FILE: ru/codes/java/chapter_searching/binary_search_edge.java ================================================ /** * 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); } } } ================================================ FILE: ru/codes/java/chapter_searching/binary_search_insertion.java ================================================ /** * 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); } } } ================================================ FILE: ru/codes/java/chapter_searching/hashing_search.java ================================================ /** * 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) { // key хеш-таблицы: целевой элемент, value: индекс // Если такого key нет в хеш-таблице, вернуть -1 return map.getOrDefault(target, -1); } /* Хеш-поиск (связный список) */ static ListNode hashingSearchLinkedList(Map map, int target) { // key хеш-таблицы: значение целевого узла, value: объект узла // Если такого key нет в хеш-таблице, вернуть 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); // key: элемент, value: индекс } 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); // key: значение узла, value: узел head = head.next; } ListNode node = hashingSearchLinkedList(map1, target); System.out.println("Объект узла со значением 3 = " + node); } } ================================================ FILE: ru/codes/java/chapter_searching/linear_search.java ================================================ /** * 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); } } ================================================ FILE: ru/codes/java/chapter_searching/two_sum.java ================================================ /** * File: two_sum.java * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ package chapter_searching; import java.util.*; public class two_sum { /* Метод 1: полный перебор */ static int[] twoSumBruteForce(int[] nums, int target) { int size = nums.length; // Два вложенных цикла, временная сложность O(n^2) 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]; } /* Метод 2: вспомогательная хеш-таблица */ 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) { // ======= Test Case ======= int[] nums = { 2, 7, 11, 15 }; int target = 13; // ====== Основной код ====== // Метод 1 int[] res = twoSumBruteForce(nums, target); System.out.println("Результат метода 1 res = " + Arrays.toString(res)); // Метод 2 res = twoSumHashTable(nums, target); System.out.println("Результат метода 2 res = " + Arrays.toString(res)); } } ================================================ FILE: ru/codes/java/chapter_sorting/bubble_sort.java ================================================ /** * 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)); } } ================================================ FILE: ru/codes/java/chapter_sorting/bucket_sort.java ================================================ /** * 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)); } } ================================================ FILE: ru/codes/java/chapter_sorting/counting_sort.java ================================================ /** * 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 — это индекс последнего появления num в res for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res // Инициализировать массив res для хранения результата int n = nums.length; int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // Поместить num по соответствующему индексу counter[num]--; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num } // Перезаписать исходный массив nums массивом результата res 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)); } } ================================================ FILE: ru/codes/java/chapter_sorting/heap_sort.java ================================================ /** * 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; // Поменять два узла местами int temp = nums[i]; nums[i] = nums[ma]; nums[ma] = temp; // Циклическое просеивание вниз i = ma; } } /* Сортировка кучей */ public static void heapSort(int[] nums) { // Построение кучи: выполнить heapify для всех узлов, кроме листовых 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)); } } ================================================ FILE: ru/codes/java/chapter_sorting/insertion_sort.java ================================================ /** * 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] на одну позицию вправо 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)); } } ================================================ FILE: ru/codes/java/chapter_sorting/merge_sort.java ================================================ /** * 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)); } } ================================================ FILE: ru/codes/java/chapter_sorting/quick_sort.java ================================================ /** * 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); // Поменять эти два элемента местами } swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов 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; } /* Выбрать медиану из трех кандидатов */ 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; } /* Разбиение с опорными указателями (медиана трех) */ static int partition(int[] 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(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); // Поменять эти два элемента местами } swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } /* Быстрая сортировка (оптимизация глубины рекурсии) */ public static void quickSort(int[] 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] } } } } 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)); } } ================================================ FILE: ru/codes/java/chapter_sorting/radix_sort.java ================================================ /** * File: radix_sort.java * Created Time: 2023-01-17 * Author: krahets (krahets@163.com) */ package chapter_sorting; import java.util.*; public class radix_sort { /* Получить k-й разряд элемента num, где exp = 10^(k-1) */ static int digit(int num, int exp) { // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени return (num / exp) % 10; } /* Сортировка подсчетом (сортировка по k-му разряду nums) */ static void countingSortDigit(int[] nums, int exp) { // Разряды десятичной системы лежат в диапазоне 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); // Получить k-й разряд nums[i], обозначив его как 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; // Получить индекс j цифры d в массиве 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)); } } ================================================ FILE: ru/codes/java/chapter_sorting/selection_sort.java ================================================ /** * 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)); } } ================================================ FILE: ru/codes/java/chapter_stack_and_queue/array_deque.java ================================================ /** * 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; // Указатель head, указывающий на первый элемент очереди 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 после выхода за начало массива возвращается в хвост front = index(front - 1); // Добавить num в голову очереди nums[front] = num; queSize++; } /* Добавление в хвост очереди */ public void pushLast(int num) { if (queSize == capacity()) { System.out.println("Двусторонняя очередь заполнена"); return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 int rear = index(front + queSize); // Добавить num в хвост очереди 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() { // Преобразовывать только элементы списка в пределах фактической длины 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) { /* Инициализация двусторонней очереди */ ArrayDeque deque = new ArrayDeque(10); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); 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); /* Добавление элемента в очередь */ deque.pushLast(4); System.out.println("После добавления элемента 4 в хвост deque = " + Arrays.toString(deque.toArray())); deque.pushFirst(1); System.out.println("После добавления элемента 1 в голову deque = " + Arrays.toString(deque.toArray())); /* Извлечение элемента из очереди */ int popLast = deque.popLast(); System.out.println("Извлеченный из хвоста элемент = " + popLast + ", deque после извлечения из хвоста = " + Arrays.toString(deque.toArray())); int popFirst = deque.popFirst(); System.out.println("Извлеченный из головы элемент = " + popFirst + ", deque после извлечения из головы = " + Arrays.toString(deque.toArray())); /* Получение длины двусторонней очереди */ int size = deque.size(); System.out.println("Длина двусторонней очереди size = " + size); /* Проверка, пуста ли двусторонняя очередь */ boolean isEmpty = deque.isEmpty(); System.out.println("Пуста ли двусторонняя очередь = " + isEmpty); } } ================================================ FILE: ru/codes/java/chapter_stack_and_queue/array_queue.java ================================================ /** * 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; // Указатель head, указывающий на первый элемент очереди 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; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива int rear = (front + queSize) % capacity(); // Добавить num в хвост очереди nums[rear] = num; queSize++; } /* Извлечь из очереди */ public int pop() { int num = peek(); // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива front = (front + 1) % capacity(); queSize--; return num; } /* Доступ к элементу в начале очереди */ public int peek() { if (isEmpty()) throw new IndexOutOfBoundsException(); return nums[front]; } /* Вернуть массив */ public int[] toArray() { // Преобразовывать только элементы списка в пределах фактической длины int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[j % 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 = " + pop + ", queue после извлечения = " + 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); queue.pop(); System.out.println("После " + i + "-го раунда операций enqueue и dequeue queue = " + Arrays.toString(queue.toArray())); } } } ================================================ FILE: ru/codes/java/chapter_stack_and_queue/array_stack.java ================================================ /** * 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 = " + pop + ", stack после извлечения = " + Arrays.toString(stack.toArray())); /* Получение длины стека */ int size = stack.size(); System.out.println("Длина стека size = " + size); /* Проверка на пустоту */ boolean isEmpty = stack.isEmpty(); System.out.println("Пуст ли стек = " + isEmpty); } } ================================================ FILE: ru/codes/java/chapter_stack_and_queue/deque.java ================================================ /** * 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 после извлечения из хвоста = " + deque); int popFirst = deque.pollFirst(); System.out.println("Извлеченный из головы элемент = " + popFirst + ", deque после извлечения из головы = " + deque); /* Получение длины двусторонней очереди */ int size = deque.size(); System.out.println("Длина двусторонней очереди size = " + size); /* Проверка, пуста ли двусторонняя очередь */ boolean isEmpty = deque.isEmpty(); System.out.println("Пуста ли двусторонняя очередь = " + isEmpty); } } ================================================ FILE: ru/codes/java/chapter_stack_and_queue/linkedlist_deque.java ================================================ /** * 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; // Обновить головной узел // Операция добавления в хвост очереди } else { // Добавить node в хвост списка rear.next = node; node.prev = rear; rear = node; // Обновить хвостовой узел } queSize++; // Обновить длину очереди } /* Добавление в голову очереди */ public void pushFirst(int num) { push(num, true); } /* Добавление в хвост очереди */ public void pushLast(int num) { push(num, false); } /* Операция извлечения из очереди */ 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; // Обновить головной узел // Операция извлечения из хвоста очереди } else { val = rear.val; // Временно сохранить значение хвостового узла // Удалить хвостовой узел ListNode rPrev = rear.prev; if (rPrev != null) { rPrev.next = null; rear.prev = null; } rear = rPrev; // Обновить хвостовой узел } queSize--; // Обновить длину очереди return val; } /* Извлечение из головы очереди */ public int popFirst() { return pop(true); } /* Извлечение из хвоста очереди */ public int popLast() { return pop(false); } /* Доступ к элементу в начале очереди */ public int peekFirst() { if (isEmpty()) throw new 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())); /* Доступ к элементу */ int peekFirst = deque.peekFirst(); System.out.println("Первый элемент peekFirst = " + peekFirst); int peekLast = deque.peekLast(); System.out.println("Последний элемент peekLast = " + peekLast); /* Добавление элемента в очередь */ deque.pushLast(4); System.out.println("После добавления элемента 4 в хвост deque = " + Arrays.toString(deque.toArray())); deque.pushFirst(1); System.out.println("После добавления элемента 1 в голову deque = " + Arrays.toString(deque.toArray())); /* Извлечение элемента из очереди */ int popLast = deque.popLast(); System.out.println("Извлеченный из хвоста элемент = " + popLast + ", deque после извлечения из хвоста = " + Arrays.toString(deque.toArray())); int popFirst = deque.popFirst(); System.out.println("Извлеченный из головы элемент = " + popFirst + ", deque после извлечения из головы = " + Arrays.toString(deque.toArray())); /* Получение длины двусторонней очереди */ int size = deque.size(); System.out.println("Длина двусторонней очереди size = " + size); /* Проверка, пуста ли двусторонняя очередь */ boolean isEmpty = deque.isEmpty(); System.out.println("Пуста ли двусторонняя очередь = " + isEmpty); } } ================================================ FILE: ru/codes/java/chapter_stack_and_queue/linkedlist_queue.java ================================================ /** * 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); // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел 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; } /* Преобразовать связный список в Array и вернуть */ 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 = " + pop + ", queue после извлечения = " + Arrays.toString(queue.toArray())); /* Получение длины очереди */ int size = queue.size(); System.out.println("Длина очереди size = " + size); /* Проверка, пуста ли очередь */ boolean isEmpty = queue.isEmpty(); System.out.println("Пуста ли очередь = " + isEmpty); } } ================================================ FILE: ru/codes/java/chapter_stack_and_queue/linkedlist_stack.java ================================================ /** * 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 = " + pop + ", stack после извлечения = " + Arrays.toString(stack.toArray())); /* Получение длины стека */ int size = stack.size(); System.out.println("Длина стека size = " + size); /* Проверка на пустоту */ boolean isEmpty = stack.isEmpty(); System.out.println("Пуст ли стек = " + isEmpty); } } ================================================ FILE: ru/codes/java/chapter_stack_and_queue/queue.java ================================================ /** * 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 = " + pop + ", queue после извлечения = " + queue); /* Получение длины очереди */ int size = queue.size(); System.out.println("Длина очереди size = " + size); /* Проверка, пуста ли очередь */ boolean isEmpty = queue.isEmpty(); System.out.println("Пуста ли очередь = " + isEmpty); } } ================================================ FILE: ru/codes/java/chapter_stack_and_queue/stack.java ================================================ /** * 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 = " + pop + ", stack после извлечения = " + stack); /* Получение длины стека */ int size = stack.size(); System.out.println("Длина стека size = " + size); /* Проверка на пустоту */ boolean isEmpty = stack.isEmpty(); System.out.println("Пуст ли стек = " + isEmpty); } } ================================================ FILE: ru/codes/java/chapter_tree/array_binary_tree.java ================================================ /** * 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); } } ================================================ FILE: ru/codes/java/chapter_tree/avl_tree.java ================================================ /** * 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; // Выполнить правое вращение узла node вокруг child 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; // Выполнить левое вращение узла node вокруг child 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, удалить node и сразу вернуть if (child == null) return null; // Число дочерних узлов = 1, удалить node напрямую else node = child; } else { // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел TreeNode temp = node.right; while (temp.left != null) { temp = temp.left; } node.right = removeHelper(node.right, temp.val); node.val = temp.val; } } updateHeight(node); // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = rotate(node); // Вернуть корневой узел поддерева return node; } /* Поиск узла */ 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); } } ================================================ FILE: ru/codes/java/chapter_tree/binary_search_tree.java ================================================ /** * 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()); } } ================================================ FILE: ru/codes/java/chapter_tree/binary_tree.java ================================================ /** * 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); } } ================================================ FILE: ru/codes/java/chapter_tree/binary_tree_bfs.java ================================================ /** * 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); } } ================================================ FILE: ru/codes/java/chapter_tree/binary_tree_dfs.java ================================================ /** * 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); } } ================================================ FILE: ru/codes/java/utils/ListNode.java ================================================ /** * 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; } } ================================================ FILE: ru/codes/java/utils/PrintUtil.java ================================================ /** * 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 { /* Вывести матрицу (Array) */ public static void printMatrix(T[][] matrix) { System.out.println("["); for (T[] row : matrix) { System.out.println(" " + row + ","); } System.out.println("]"); } /* Вывести матрицу (List) */ 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); } } ================================================ FILE: ru/codes/java/utils/TreeNode.java ================================================ /** * 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; } } ================================================ FILE: ru/codes/java/utils/Vertex.java ================================================ /** * 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; } } ================================================ FILE: ru/codes/javascript/.prettierrc ================================================ { "tabWidth": 4, "useTabs": false, "semi": true, "singleQuote": true } ================================================ FILE: ru/codes/javascript/chapter_array_and_linkedlist/array.js ================================================ /** * File: array.js * Created Time: 2022-11-27 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Случайный доступ к элементу */ function randomAccess(nums) { // Случайным образом выбрать число из интервала [0, nums.length) const random_index = Math.floor(Math.random() * nums.length); // Получить и вернуть случайный элемент const random_num = nums[random_index]; return random_num; } /* Увеличить длину массива */ // Обратите внимание: Array в JavaScript — это динамический массив, его можно расширять напрямую // Для удобства обучения в этой функции Array рассматривается как массив неизменяемой длины function extend(nums, enlarge) { // Инициализировать массив увеличенной длины const res = new Array(nums.length + enlarge).fill(0); // Скопировать все элементы исходного массива в новый массив for (let i = 0; i < nums.length; i++) { res[i] = nums[i]; } // Вернуть новый массив после расширения return res; } /* Вставить элемент num по индексу index в массив */ function insert(nums, num, index) { // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад for (let i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Присвоить num элементу по индексу index nums[index] = num; } /* Удалить элемент по индексу index */ function remove(nums, index) { // Сдвинуть все элементы после индекса index на одну позицию вперед for (let i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* Обход массива */ function traverse(nums) { let count = 0; // Обход массива по индексам for (let i = 0; i < nums.length; i++) { count += nums[i]; } // Непосредственно обходить элементы массива for (const num of nums) { count += num; } } /* Найти заданный элемент в массиве */ function find(nums, target) { for (let i = 0; i < nums.length; i++) { if (nums[i] === target) return i; } return -1; } /* Driver Code */ /* Инициализация массива */ const arr = new Array(5).fill(0); console.log('Массив arr =', arr); let nums = [1, 3, 2, 5, 4]; console.log('Массив nums =', nums); /* Случайный доступ */ let random_num = randomAccess(nums); console.log('Случайный элемент из nums =', random_num); /* Расширение длины */ nums = extend(nums, 3); console.log('После увеличения длины массива до 8 nums =', nums); /* Вставка элемента */ insert(nums, 6, 3); console.log('После вставки числа 6 по индексу 3 nums =', nums); /* Удаление элемента */ remove(nums, 2); console.log('После удаления элемента по индексу 2 nums =', nums); /* Обход массива */ traverse(nums); /* Поиск элемента */ let index = find(nums, 3); console.log('Поиск элемента 3 в nums: индекс =', index); ================================================ FILE: ru/codes/javascript/chapter_array_and_linkedlist/linked_list.js ================================================ /** * File: linked_list.js * Created Time: 2022-12-12 * Author: IsChristina (christinaxia77@foxmail.com), Justin (xiefahit@gmail.com) */ const { printLinkedList } = require('../modules/PrintUtil'); const { ListNode } = require('../modules/ListNode'); /* Вставить узел P после узла n0 в связном списке */ function insert(n0, P) { const n1 = n0.next; P.next = n1; n0.next = P; } /* Удалить первый узел после узла n0 в связном списке */ function remove(n0) { if (!n0.next) return; // n0 -> P -> n1 const P = n0.next; const n1 = P.next; n0.next = n1; } /* Доступ к узлу связного списка по индексу index */ function access(head, index) { for (let i = 0; i < index; i++) { if (!head) { return null; } head = head.next; } return head; } /* Найти в связном списке первый узел со значением target */ function find(head, target) { let index = 0; while (head !== null) { if (head.val === target) { return index; } head = head.next; index += 1; } return -1; } /* Driver Code */ /* Инициализация связного списка */ // Инициализация всех узлов const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // Построить ссылки между узлами n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; console.log('Исходный связный список'); printLinkedList(n0); /* Вставка узла */ insert(n0, new ListNode(0)); console.log('Связный список после вставки узла'); printLinkedList(n0); /* Удаление узла */ remove(n0); console.log('Связный список после удаления узла'); printLinkedList(n0); /* Доступ к узлу */ const node = access(n0, 3); console.log('Значение узла по индексу 3 в связном списке = ' + node.val); /* Поиск узла */ const index = find(n0, 2); console.log('Индекс узла со значением 2 в связном списке = ' + index); ================================================ FILE: ru/codes/javascript/chapter_array_and_linkedlist/list.js ================================================ /** * File: list.js * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* Инициализация списка */ const nums = [1, 3, 2, 5, 4]; console.log(`Список nums = ${nums}`); /* Доступ к элементу */ const num = nums[1]; console.log(`Элемент по индексу 1: num = ${num}`); /* Обновление элемента */ nums[1] = 0; console.log(`После обновления элемента по индексу 1 до 0 nums = ${nums}`); /* Очистить список */ nums.length = 0; console.log(`После очистки списка nums = ${nums}`); /* Добавление элемента в конец */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); console.log(`После добавления элементов nums = ${nums}`); /* Вставка элемента в середину */ nums.splice(3, 0, 6); console.log(`После вставки числа 6 по индексу 3 nums = ${nums}`); /* Удаление элемента */ nums.splice(3, 1); console.log(`После удаления элемента по индексу 3 nums = ${nums}`); /* Обходить список по индексам */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* Непосредственно обходить элементы списка */ count = 0; for (const x of nums) { count += x; } /* Объединить два списка */ const nums1 = [6, 8, 7, 10, 9]; nums.push(...nums1); console.log(`После конкатенации списка nums1 к nums nums = ${nums}`); /* Отсортировать список */ nums.sort((a, b) => a - b); console.log(`После сортировки списка nums = ${nums}`); ================================================ FILE: ru/codes/javascript/chapter_array_and_linkedlist/my_list.js ================================================ /** * File: my_list.js * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* Класс списка */ class MyList { #arr = new Array(); // Массив (для хранения элементов списка) #capacity = 10; // Вместимость списка #size = 0; // Длина списка (текущее число элементов) #extendRatio = 2; // Коэффициент увеличения списка при каждом расширении /* Конструктор */ constructor() { this.#arr = new Array(this.#capacity); } /* Получить длину списка (текущее число элементов) */ size() { return this.#size; } /* Получить вместимость списка */ capacity() { return this.#capacity; } /* Доступ к элементу */ get(index) { // Если индекс выходит за границы, выбрасывается исключение; далее аналогично if (index < 0 || index >= this.#size) throw new Error('индекс выходит за границы'); return this.#arr[index]; } /* Обновление элемента */ set(index, num) { if (index < 0 || index >= this.#size) throw new Error('индекс выходит за границы'); this.#arr[index] = num; } /* Добавление элемента в конец */ add(num) { // Если длина равна вместимости, требуется расширение if (this.#size === this.#capacity) { this.extendCapacity(); } // Добавить новый элемент в конец списка this.#arr[this.#size] = num; this.#size++; } /* Вставка элемента в середину */ insert(index, num) { if (index < 0 || index >= this.#size) throw new Error('индекс выходит за границы'); // При превышении вместимости по числу элементов запускается расширение if (this.#size === this.#capacity) { this.extendCapacity(); } // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад for (let j = this.#size - 1; j >= index; j--) { this.#arr[j + 1] = this.#arr[j]; } // Обновить число элементов this.#arr[index] = num; this.#size++; } /* Удаление элемента */ remove(index) { if (index < 0 || index >= this.#size) throw new Error('индекс выходит за границы'); let num = this.#arr[index]; // Сдвинуть все элементы после индекса index на одну позицию вперед for (let j = index; j < this.#size - 1; j++) { this.#arr[j] = this.#arr[j + 1]; } // Обновить число элементов this.#size--; // Вернуть удаленный элемент return num; } /* Расширение списка */ extendCapacity() { // Создать новый массив длиной в extendRatio раз больше исходного и скопировать в него исходный массив this.#arr = this.#arr.concat( new Array(this.capacity() * (this.#extendRatio - 1)) ); // Обновить вместимость списка this.#capacity = this.#arr.length; } /* Преобразовать список в массив */ toArray() { let size = this.size(); // Преобразовывать только элементы списка в пределах фактической длины const arr = new Array(size); for (let i = 0; i < size; i++) { arr[i] = this.get(i); } return arr; } } /* Driver Code */ /* Инициализация списка */ const nums = new MyList(); /* Добавление элемента в конец */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); console.log( `Список nums = ${nums.toArray()}, вместимость = ${nums.capacity()}, длина = ${nums.size()}` ); /* Вставка элемента в середину */ nums.insert(3, 6); console.log(`После вставки числа 6 по индексу 3 nums = ${nums.toArray()}`); /* Удаление элемента */ nums.remove(3); console.log(`После удаления элемента по индексу 3 nums = ${nums.toArray()}`); /* Доступ к элементу */ const num = nums.get(1); console.log(`Элемент по индексу 1: num = ${num}`); /* Обновление элемента */ nums.set(1, 0); console.log(`После обновления элемента по индексу 1 до 0 nums = ${nums.toArray()}`); /* Проверка механизма расширения */ for (let i = 0; i < 10; i++) { // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения nums.add(i); } console.log( `Список nums после увеличения вместимости = ${nums.toArray()}, вместимость = ${nums.capacity()}, длина = ${nums.size()}` ); ================================================ FILE: ru/codes/javascript/chapter_backtracking/n_queens.js ================================================ /** * File: n_queens.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* Алгоритм бэктрекинга: n ферзей */ function backtrack(row, n, state, res, cols, diags1, diags2) { // Когда все строки уже обработаны, записать решение if (row === n) { res.push(state.map((row) => row.slice())); return; } // Обойти все столбцы for (let col = 0; col < n; col++) { // Вычислить главную и побочную диагонали, соответствующие этой клетке const diag1 = row - col + n - 1; const diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Попытка: поставить ферзя в эту клетку state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // Перейти к размещению следующей строки backtrack(row + 1, n, state, res, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Решить задачу о n ферзях */ function nQueens(n) { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку const state = Array.from({ length: n }, () => Array(n).fill('#')); const cols = Array(n).fill(false); // Отмечать, есть ли ферзь в столбце const diags1 = Array(2 * n - 1).fill(false); // Отмечать наличие ферзя на главной диагонали const diags2 = Array(2 * n - 1).fill(false); // Отмечать наличие ферзя на побочной диагонали const res = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } // Driver Code const n = 4; const res = nQueens(n); console.log(`Размер входной доски = ${n}`); console.log(`Количество способов расстановки ферзей: ${res.length}`); res.forEach((state) => { console.log('--------------------'); state.forEach((row) => console.log(row)); }); ================================================ FILE: ru/codes/javascript/chapter_backtracking/permutations_i.js ================================================ /** * File: permutations_i.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* Алгоритм бэктрекинга: все перестановки I */ function backtrack(state, choices, selected, res) { // Когда длина состояния равна числу элементов, записать решение if (state.length === choices.length) { res.push([...state]); return; } // Перебор всех вариантов выбора choices.forEach((choice, i) => { // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true; state.push(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop(); } }); } /* Все перестановки I */ function permutationsI(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums = [1, 2, 3]; const res = permutationsI(nums); console.log(`Входной массив nums = ${JSON.stringify(nums)}`); console.log(`Все перестановки res = ${JSON.stringify(res)}`); ================================================ FILE: ru/codes/javascript/chapter_backtracking/permutations_ii.js ================================================ /** * File: permutations_ii.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* Алгоритм бэктрекинга: все перестановки II */ function backtrack(state, choices, selected, res) { // Когда длина состояния равна числу элементов, записать решение if (state.length === choices.length) { res.push([...state]); return; } // Перебор всех вариантов выбора const duplicated = new Set(); choices.forEach((choice, i) => { // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && !duplicated.has(choice)) { // Попытка: сделать выбор и обновить состояние duplicated.add(choice); // Записать значения уже выбранных элементов selected[i] = true; state.push(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop(); } }); } /* Все перестановки II */ function permutationsII(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums = [1, 2, 2]; const res = permutationsII(nums); console.log(`Входной массив nums = ${JSON.stringify(nums)}`); console.log(`Все перестановки res = ${JSON.stringify(res)}`); ================================================ FILE: ru/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js ================================================ /** * File: preorder_traversal_i_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Предварительный обход: пример 1 */ function preOrder(root, res) { if (root === null) { return; } if (root.val === 7) { // Записать решение res.push(root); } preOrder(root.left, res); preOrder(root.right, res); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nИнициализация двоичного дерева'); printTree(root); // Предварительный обход const res = []; preOrder(root, res); console.log('\nВсе узлы со значением 7'); console.log(res.map((node) => node.val)); ================================================ FILE: ru/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js ================================================ /** * File: preorder_traversal_ii_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Предварительный обход: пример 2 */ function preOrder(root, path, res) { if (root === null) { return; } // Попытка path.push(root); if (root.val === 7) { // Записать решение res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // Откат path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nИнициализация двоичного дерева'); printTree(root); // Предварительный обход const path = []; const res = []; preOrder(root, path, res); console.log('\nВсе пути от корня к узлу 7'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: ru/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js ================================================ /** * File: preorder_traversal_iii_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Предварительный обход: пример 3 */ function preOrder(root, path, res) { // Отсечение if (root === null || root.val === 3) { return; } // Попытка path.push(root); if (root.val === 7) { // Записать решение res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // Откат path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nИнициализация двоичного дерева'); printTree(root); // Предварительный обход const path = []; const res = []; preOrder(root, path, res); console.log('\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: ru/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js ================================================ /** * File: preorder_traversal_iii_template.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Проверить, является ли текущее состояние решением */ function isSolution(state) { return state && state[state.length - 1]?.val === 7; } /* Записать решение */ function recordSolution(state, res) { res.push([...state]); } /* Проверить, допустим ли этот выбор в текущем состоянии */ function isValid(state, choice) { return choice !== null && choice.val !== 3; } /* Обновить состояние */ function makeChoice(state, choice) { state.push(choice); } /* Восстановить состояние */ function undoChoice(state) { state.pop(); } /* Алгоритм бэктрекинга: пример 3 */ function backtrack(state, choices, res) { // Проверить, является ли текущее состояние решением if (isSolution(state)) { // Записать решение recordSolution(state, res); } // Перебор всех вариантов выбора for (const choice of choices) { // Отсечение: проверить допустимость выбора if (isValid(state, choice)) { // Попытка: сделать выбор и обновить состояние makeChoice(state, choice); // Перейти к следующему выбору backtrack(state, [choice.left, choice.right], res); // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state); } } } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nИнициализация двоичного дерева'); printTree(root); // Алгоритм бэктрекинга const res = []; backtrack([], [root], res); console.log('\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: ru/codes/javascript/chapter_backtracking/subset_sum_i.js ================================================ /** * File: subset_sum_i.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Алгоритм бэктрекинга: сумма подмножеств I */ function backtrack(state, target, choices, start, res) { // Если сумма подмножества равна target, записать решение if (target === 0) { res.push([...state]); return; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств for (let i = start; i < choices.length; i++) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if (target - choices[i] < 0) { break; } // Попытка: сделать выбор и обновить target и start state.push(choices[i]); // Перейти к следующему выбору backtrack(state, target - choices[i], choices, i, res); // Откат: отменить выбор и восстановить предыдущее состояние state.pop(); } } /* Решить задачу суммы подмножеств I */ function subsetSumI(nums, target) { const state = []; // Состояние (подмножество) nums.sort((a, b) => a - b); // Отсортировать nums const start = 0; // Стартовая вершина обхода const res = []; // Список результатов (список подмножеств) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumI(nums, target); console.log(`Входной массив nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`Все подмножества с суммой ${target}: res = ${JSON.stringify(res)}`); ================================================ FILE: ru/codes/javascript/chapter_backtracking/subset_sum_i_naive.js ================================================ /** * File: subset_sum_i_naive.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Алгоритм бэктрекинга: сумма подмножеств I */ function backtrack(state, target, total, choices, res) { // Если сумма подмножества равна target, записать решение if (total === target) { res.push([...state]); return; } // Перебор всех вариантов выбора for (let i = 0; i < choices.length; i++) { // Отсечение: если сумма подмножества превышает target, пропустить этот выбор if (total + choices[i] > target) { continue; } // Попытка: сделать выбор и обновить элемент и total state.push(choices[i]); // Перейти к следующему выбору backtrack(state, target, total + choices[i], choices, res); // Откат: отменить выбор и восстановить предыдущее состояние state.pop(); } } /* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ function subsetSumINaive(nums, target) { const state = []; // Состояние (подмножество) const total = 0; // Сумма подмножеств const res = []; // Список результатов (список подмножеств) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumINaive(nums, target); console.log(`Входной массив nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`Все подмножества с суммой ${target}: res = ${JSON.stringify(res)}`); console.log('Обратите внимание: результат этого метода содержит повторяющиеся множества'); ================================================ FILE: ru/codes/javascript/chapter_backtracking/subset_sum_ii.js ================================================ /** * File: subset_sum_ii.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Алгоритм бэктрекинга: сумма подмножеств II */ function backtrack(state, target, choices, start, res) { // Если сумма подмножества равна target, записать решение if (target === 0) { res.push([...state]); return; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента for (let i = start; i < choices.length; i++) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if (target - choices[i] < 0) { break; } // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить if (i > start && choices[i] === choices[i - 1]) { continue; } // Попытка: сделать выбор и обновить target и start state.push(choices[i]); // Перейти к следующему выбору backtrack(state, target - choices[i], choices, i + 1, res); // Откат: отменить выбор и восстановить предыдущее состояние state.pop(); } } /* Решить задачу суммы подмножеств II */ function subsetSumII(nums, target) { const state = []; // Состояние (подмножество) nums.sort((a, b) => a - b); // Отсортировать nums const start = 0; // Стартовая вершина обхода const res = []; // Список результатов (список подмножеств) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [4, 4, 5]; const target = 9; const res = subsetSumII(nums, target); console.log(`Входной массив nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`Все подмножества с суммой ${target}: res = ${JSON.stringify(res)}`); ================================================ FILE: ru/codes/javascript/chapter_computational_complexity/iteration.js ================================================ /** * File: iteration.js * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Цикл for */ function forLoop(n) { let res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { res += i; } return res; } /* Цикл while */ function whileLoop(n) { let res = 0; let i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Обновить условную переменную } return res; } /* Цикл while (двойное обновление) */ function whileLoopII(n) { let res = 0; let i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i++; i *= 2; } return res; } /* Двойной цикл for */ function nestedForLoop(n) { let res = ''; // Цикл по i = 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { // Цикл по j = 1, 2, ..., n-1, n for (let j = 1; j <= n; j++) { res += `(${i}, ${j}), `; } } return res; } /* Driver Code */ const n = 5; let res; res = forLoop(n); console.log(`Результат суммирования в цикле for res = ${res}`); res = whileLoop(n); console.log(`Результат суммирования в цикле while res = ${res}`); res = whileLoopII(n); console.log(`Результат суммирования в цикле while (двойное обновление) res = ${res}`); const resStr = nestedForLoop(n); console.log(`Результат обхода в двойном цикле for ${resStr}`); ================================================ FILE: ru/codes/javascript/chapter_computational_complexity/recursion.js ================================================ /** * File: recursion.js * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Рекурсия */ function recur(n) { // Условие завершения if (n === 1) return 1; // Рекурсия: рекурсивный вызов const res = recur(n - 1); // Возврат: вернуть результат return n + res; } /* Имитация рекурсии итерацией */ function forLoopRecur(n) { // Использовать явный стек для имитации системного стека вызовов const stack = []; let res = 0; // Рекурсия: рекурсивный вызов for (let i = n; i > 0; i--) { // Имитировать «рекурсию» с помощью операции помещения в стек stack.push(i); } // Возврат: вернуть результат while (stack.length) { // Имитировать «возврат» с помощью операции извлечения из стека res += stack.pop(); } // res = 1+2+3+...+n return res; } /* Хвостовая рекурсия */ function tailRecur(n, res) { // Условие завершения if (n === 0) return res; // Хвостовой рекурсивный вызов return tailRecur(n - 1, res + n); } /* Последовательность Фибоначчи: рекурсия */ function fib(n) { // Условие завершения: f(1) = 0, f(2) = 1 if (n === 1 || n === 2) return n - 1; // Рекурсивный вызов f(n) = f(n-1) + f(n-2) const res = fib(n - 1) + fib(n - 2); // Вернуть результат f(n) return res; } /* Driver Code */ const n = 5; let res; res = recur(n); console.log(`Результат суммирования в рекурсивной функции res = ${res}`); res = forLoopRecur(n); console.log(`Результат суммирования при имитации рекурсии итерацией res = ${res}`); res = tailRecur(n, 0); console.log(`Результат суммирования в хвостовой рекурсии res = ${res}`); res = fib(n); console.log(`Член последовательности Фибоначчи с номером ${n} = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_computational_complexity/space_complexity.js ================================================ /** * File: space_complexity.js * Created Time: 2023-02-05 * Author: Justin (xiefahit@gmail.com) */ const { ListNode } = require('../modules/ListNode'); const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Функция */ function constFunc() { // Выполнить некоторые операции return 0; } /* Постоянная сложность */ function constant(n) { // Константы, переменные и объекты занимают O(1) памяти const a = 0; const b = 0; const nums = new Array(10000); const node = new ListNode(0); // Переменные в цикле занимают O(1) памяти for (let i = 0; i < n; i++) { const c = 0; } // Функции в цикле занимают O(1) памяти for (let i = 0; i < n; i++) { constFunc(); } } /* Линейная сложность */ function linear(n) { // Массив длины n занимает O(n) памяти const nums = new Array(n); // Список длины n занимает O(n) памяти const nodes = []; for (let i = 0; i < n; i++) { nodes.push(new ListNode(i)); } // Хеш-таблица длины n занимает O(n) памяти const map = new Map(); for (let i = 0; i < n; i++) { map.set(i, i.toString()); } } /* Линейная сложность (рекурсивная реализация) */ function linearRecur(n) { console.log(`Рекурсия n = ${n}`); if (n === 1) return; linearRecur(n - 1); } /* Квадратичная сложность */ function quadratic(n) { // Матрица занимает O(n^2) памяти const numMatrix = Array(n) .fill(null) .map(() => Array(n).fill(null)); // Двумерный список занимает O(n^2) памяти const numList = []; for (let i = 0; i < n; i++) { const tmp = []; for (let j = 0; j < n; j++) { tmp.push(0); } numList.push(tmp); } } /* Квадратичная сложность (рекурсивная реализация) */ function quadraticRecur(n) { if (n <= 0) return 0; const nums = new Array(n); console.log(`В рекурсии n = ${n} длина nums = ${nums.length}`); return quadraticRecur(n - 1); } /* Экспоненциальная сложность (построение полного двоичного дерева) */ function buildTree(n) { if (n === 0) return null; const root = new TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ const n = 5; // Постоянная сложность constant(n); // Линейная сложность linear(n); linearRecur(n); // Квадратичная сложность quadratic(n); quadraticRecur(n); // Экспоненциальная сложность const root = buildTree(n); printTree(root); ================================================ FILE: ru/codes/javascript/chapter_computational_complexity/time_complexity.js ================================================ /** * File: time_complexity.js * Created Time: 2023-01-02 * Author: RiverTwilight (contact@rene.wang) */ /* Постоянная сложность */ function constant(n) { let count = 0; const size = 100000; for (let i = 0; i < size; i++) count++; return count; } /* Линейная сложность */ function linear(n) { let count = 0; for (let i = 0; i < n; i++) count++; return count; } /* Линейная сложность (обход массива) */ function arrayTraversal(nums) { let count = 0; // Число итераций пропорционально длине массива for (let i = 0; i < nums.length; i++) { count++; } return count; } /* Квадратичная сложность */ function quadratic(n) { let count = 0; // Число итераций квадратично зависит от размера данных n for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { count++; } } return count; } /* Квадратичная сложность (пузырьковая сортировка) */ function bubbleSort(nums) { let count = 0; // Счетчик // Внешний цикл: неотсортированный диапазон [0, i] for (let i = nums.length - 1; i > 0; i--) { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // Обмен элементов включает 3 элементарные операции } } } return count; } /* Экспоненциальная сложность (итеративная реализация) */ function exponential(n) { let count = 0, base = 1; // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) for (let i = 0; i < n; i++) { for (let j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* Экспоненциальная сложность (рекурсивная реализация) */ function expRecur(n) { if (n === 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* Логарифмическая сложность (итеративная реализация) */ function logarithmic(n) { let count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* Логарифмическая сложность (рекурсивная реализация) */ function logRecur(n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* Линейно-логарифмическая сложность */ function linearLogRecur(n) { if (n <= 1) return 1; let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (let i = 0; i < n; i++) { count++; } return count; } /* Факториальная сложность (рекурсивная реализация) */ function factorialRecur(n) { if (n === 0) return 1; let count = 0; // Из одного получается n for (let i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях const n = 8; console.log('Размер входных данных n = ' + n); let count = constant(n); console.log('Число операций константной сложности = ' + count); count = linear(n); console.log('Число операций линейной сложности = ' + count); count = arrayTraversal(new Array(n)); console.log('Число операций линейной сложности (обход массива) = ' + count); count = quadratic(n); console.log('Число операций квадратичной сложности = ' + count); let nums = new Array(n); for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); console.log('Число операций квадратичной сложности (пузырьковая сортировка) = ' + count); count = exponential(n); console.log('Число операций экспоненциальной сложности (итеративная реализация) = ' + count); count = expRecur(n); console.log('Число операций экспоненциальной сложности (рекурсивная реализация) = ' + count); count = logarithmic(n); console.log('Число операций логарифмической сложности (итеративная реализация) = ' + count); count = logRecur(n); console.log('Число операций логарифмической сложности (рекурсивная реализация) = ' + count); count = linearLogRecur(n); console.log('Число операций линейно-логарифмической сложности (рекурсивная реализация) = ' + count); count = factorialRecur(n); console.log('Число операций факториальной сложности (рекурсивная реализация) = ' + count); ================================================ FILE: ru/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js ================================================ /** * File: worst_best_time_complexity.js * Created Time: 2023-01-05 * Author: RiverTwilight (contact@rene.wang) */ /* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ function randomNumbers(n) { const nums = Array(n); // Создать массив nums = { 1, 2, 3, ..., n } for (let i = 0; i < n; i++) { nums[i] = i + 1; } // Случайно перемешать элементы массива for (let i = 0; i < n; i++) { const r = Math.floor(Math.random() * (i + 1)); const temp = nums[i]; nums[i] = nums[r]; nums[r] = temp; } return nums; } /* Найти индекс числа 1 в массиве nums */ function findOne(nums) { for (let i = 0; i < nums.length; i++) { // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) if (nums[i] === 1) { return i; } } return -1; } /* Driver Code */ for (let i = 0; i < 10; i++) { const n = 100; const nums = randomNumbers(n); const index = findOne(nums); console.log('\nМассив [1, 2, ..., n] после перемешивания = [' + nums.join(', ') + ']'); console.log('Индекс числа 1 = ' + index); } ================================================ FILE: ru/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js ================================================ /** * File: binary_search_recur.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Бинарный поиск: задача f(i, j) */ function dfs(nums, target, i, j) { // Если интервал пуст, целевой элемент отсутствует, вернуть -1 if (i > j) { return -1; } // Вычислить индекс середины m const m = i + ((j - i) >> 1); if (nums[m] < target) { // Рекурсивная подзадача f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // Рекурсивная подзадача f(i, m-1) return dfs(nums, target, i, m - 1); } else { // Целевой элемент найден, вернуть его индекс return m; } } /* Бинарный поиск */ function binarySearch(nums, target) { const n = nums.length; // Решить задачу f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // Бинарный поиск (двусторонне замкнутый интервал) const index = binarySearch(nums, target); console.log(`Индекс целевого элемента 6 = ${index}`); ================================================ FILE: ru/codes/javascript/chapter_divide_and_conquer/build_tree.js ================================================ /** * File: build_tree.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ const { printTree } = require('../modules/PrintUtil'); const { TreeNode } = require('../modules/TreeNode'); /* Построить двоичное дерево: разделяй и властвуй */ function dfs(preorder, inorderMap, i, l, r) { // Завершить при пустом диапазоне поддерева if (r - l < 0) return null; // Инициализировать корневой узел const root = new TreeNode(preorder[i]); // Найти m, чтобы разделить левое и правое поддеревья const m = inorderMap.get(preorder[i]); // Подзадача: построить левое поддерево root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // Подзадача: построить правое поддерево root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // Вернуть корневой узел return root; } /* Построить двоичное дерево */ function buildTree(preorder, inorder) { // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам let inorderMap = new Map(); for (let i = 0; i < inorder.length; i++) { inorderMap.set(inorder[i], i); } const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ const preorder = [3, 9, 2, 1, 7]; const inorder = [9, 3, 1, 2, 7]; console.log('Предварительный обход = ' + JSON.stringify(preorder)); console.log('Симметричный обход = ' + JSON.stringify(inorder)); const root = buildTree(preorder, inorder); console.log('Построенное двоичное дерево:'); printTree(root); ================================================ FILE: ru/codes/javascript/chapter_divide_and_conquer/hanota.js ================================================ /** * File: hanota.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Переместить один диск */ function move(src, tar) { // Снять диск с вершины src const pan = src.pop(); // Положить диск на вершину tar tar.push(pan); } /* Решить задачу Ханойской башни f(i) */ function dfs(i, src, buf, tar) { // Если в src остался только один диск, сразу переместить его в tar if (i === 1) { move(src, tar); return; } // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar dfs(i - 1, src, tar, buf); // Подзадача f(1): переместить оставшийся один диск из src в tar move(src, tar); // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src dfs(i - 1, buf, src, tar); } /* Решить задачу Ханойской башни */ function solveHanota(A, B, C) { const n = A.length; // Переместить верхние n дисков из A в C с помощью B dfs(n, A, B, C); } /* Driver Code */ // Хвост списка соответствует вершине столбца const A = [5, 4, 3, 2, 1]; const B = []; const C = []; console.log('Исходное состояние:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); solveHanota(A, B, C); console.log('После завершения перемещения дисков:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); ================================================ FILE: ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js ================================================ /** * File: climbing_stairs_backtrack.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Бэктрекинг */ function backtrack(choices, state, n, res) { // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 if (state === n) res.set(0, res.get(0) + 1); // Перебор всех вариантов выбора for (const choice of choices) { // Отсечение: нельзя выходить за n-ю ступень if (state + choice > n) continue; // Попытка: сделать выбор и обновить состояние backtrack(choices, state + choice, n, res); // Откат } } /* Подъем по лестнице: бэктрекинг */ function climbingStairsBacktrack(n) { const choices = [1, 2]; // Можно подняться на 1 или 2 ступени const state = 0; // Начать подъем с 0-й ступени const res = new Map(); res.set(0, 0); // Использовать res[0] для хранения числа решений backtrack(choices, state, n, res); return res.get(0); } /* Driver Code */ const n = 9; const res = climbingStairsBacktrack(n); console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js ================================================ /** * File: climbing_stairs_constraint_dp.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Подъем по лестнице с ограничениями: динамическое программирование */ function climbingStairsConstraintDP(n) { if (n === 1 || n === 2) { return 1; } // Инициализация таблицы dp для хранения решений подзадач const dp = Array.from(new Array(n + 1), () => new Array(3)); // Начальное состояние: заранее задать решения наименьших подзадач dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // Переход состояний: постепенное решение больших подзадач через меньшие for (let i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ const n = 9; const res = climbingStairsConstraintDP(n); console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js ================================================ /** * File: climbing_stairs_dfs.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Поиск */ function dfs(i) { // dp[1] и dp[2] уже известны, вернуть их if (i === 1 || i === 2) return i; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1) + dfs(i - 2); return count; } /* Подъем по лестнице: поиск */ function climbingStairsDFS(n) { return dfs(n); } /* Driver Code */ const n = 9; const res = climbingStairsDFS(n); console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js ================================================ /** * File: climbing_stairs_dfs_mem.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Поиск с мемоизацией */ function dfs(i, mem) { // dp[1] и dp[2] уже известны, вернуть их if (i === 1 || i === 2) return i; // Если запись dp[i] существует, сразу вернуть ее if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1, mem) + dfs(i - 2, mem); // Сохранить dp[i] mem[i] = count; return count; } /* Подъем по лестнице: поиск с мемоизацией */ function climbingStairsDFSMem(n) { // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи const mem = new Array(n + 1).fill(-1); return dfs(n, mem); } /* Driver Code */ const n = 9; const res = climbingStairsDFSMem(n); console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js ================================================ /** * File: climbing_stairs_dp.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Подъем по лестнице: динамическое программирование */ function climbingStairsDP(n) { if (n === 1 || n === 2) return n; // Инициализация таблицы dp для хранения решений подзадач const dp = new Array(n + 1).fill(-1); // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = 1; dp[2] = 2; // Переход состояний: постепенное решение больших подзадач через меньшие for (let i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ function climbingStairsDPComp(n) { if (n === 1 || n === 2) return n; let a = 1, b = 2; for (let i = 3; i <= n; i++) { const tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ const n = 9; let res = climbingStairsDP(n); console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res}`); res = climbingStairsDPComp(n); console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_dynamic_programming/coin_change.js ================================================ /** * File: coin_change.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Размен монет: динамическое программирование */ function coinChangeDP(coins, amt) { const n = coins.length; const MAX = amt + 1; // Инициализация таблицы dp const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // Переход состояний: первая строка и первый столбец for (let a = 1; a <= amt; a++) { dp[0][a] = MAX; } // Переход состояний: остальные строки и столбцы for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] !== MAX ? dp[n][amt] : -1; } /* Размен монет: динамическое программирование с оптимизацией памяти */ function coinChangeDPComp(coins, amt) { const n = coins.length; const MAX = amt + 1; // Инициализация таблицы dp const dp = Array.from({ length: amt + 1 }, () => MAX); dp[0] = 0; // Переход состояний for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] !== MAX ? dp[amt] : -1; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 4; // Динамическое программирование let res = coinChangeDP(coins, amt); console.log(`Минимальное число монет для набора целевой суммы = ${res}`); // Динамическое программирование с оптимизацией памяти res = coinChangeDPComp(coins, amt); console.log(`Минимальное число монет для набора целевой суммы = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_dynamic_programming/coin_change_ii.js ================================================ /** * File: coin_change_ii.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Размен монет II: динамическое программирование */ function coinChangeIIDP(coins, amt) { const n = coins.length; // Инициализация таблицы dp const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // Инициализация первого столбца for (let i = 0; i <= n; i++) { dp[i][0] = 1; } // Переход состояний for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a]; } else { // Сумма двух решений: не брать или взять монету i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* Размен монет II: динамическое программирование с оптимизацией памяти */ function coinChangeIIDPComp(coins, amt) { const n = coins.length; // Инициализация таблицы dp const dp = Array.from({ length: amt + 1 }, () => 0); dp[0] = 1; // Переход состояний for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Сумма двух решений: не брать или взять монету i dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 5; // Динамическое программирование let res = coinChangeIIDP(coins, amt); console.log(`Количество комбинаций монет для набора целевой суммы = ${res}`); // Динамическое программирование с оптимизацией памяти res = coinChangeIIDPComp(coins, amt); console.log(`Количество комбинаций монет для набора целевой суммы = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_dynamic_programming/edit_distance.js ================================================ /** * File: edit_distance.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Редакционное расстояние: полный перебор */ function editDistanceDFS(s, t, i, j) { // Если s и t пусты, вернуть 0 if (i === 0 && j === 0) return 0; // Если s пусто, вернуть длину t if (i === 0) return j; // Если t пусто, вернуть длину s if (j === 0) return i; // Если два символа равны, сразу пропустить их if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 const insert = editDistanceDFS(s, t, i, j - 1); const del = editDistanceDFS(s, t, i - 1, j); const replace = editDistanceDFS(s, t, i - 1, j - 1); // Вернуть минимальное число шагов редактирования return Math.min(insert, del, replace) + 1; } /* Редакционное расстояние: поиск с мемоизацией */ function editDistanceDFSMem(s, t, mem, i, j) { // Если s и t пусты, вернуть 0 if (i === 0 && j === 0) return 0; // Если s пусто, вернуть длину t if (i === 0) return j; // Если t пусто, вернуть длину s if (j === 0) return i; // Если запись уже есть, сразу вернуть ее if (mem[i][j] !== -1) return mem[i][j]; // Если два символа равны, сразу пропустить их if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 const insert = editDistanceDFSMem(s, t, mem, i, j - 1); const del = editDistanceDFSMem(s, t, mem, i - 1, j); const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Сохранить и вернуть минимальное число шагов редактирования mem[i][j] = Math.min(insert, del, replace) + 1; return mem[i][j]; } /* Редакционное расстояние: динамическое программирование */ function editDistanceDP(s, t) { const n = s.length, m = t.length; const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); // Переход состояний: первая строка и первый столбец for (let i = 1; i <= n; i++) { dp[i][0] = i; } for (let j = 1; j <= m; j++) { dp[0][j] = j; } // Переход состояний: остальные строки и столбцы for (let i = 1; i <= n; i++) { for (let j = 1; j <= m; j++) { if (s.charAt(i - 1) === t.charAt(j - 1)) { // Если два символа равны, сразу пропустить их dp[i][j] = dp[i - 1][j - 1]; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ function editDistanceDPComp(s, t) { const n = s.length, m = t.length; const dp = new Array(m + 1).fill(0); // Переход состояний: первая строка for (let j = 1; j <= m; j++) { dp[j] = j; } // Переход состояний: остальные строки for (let i = 1; i <= n; i++) { // Переход состояний: первый столбец let leftup = dp[0]; // Временно сохранить dp[i-1, j-1] dp[0] = i; // Переход состояний: остальные столбцы for (let j = 1; j <= m; j++) { const temp = dp[j]; if (s.charAt(i - 1) === t.charAt(j - 1)) { // Если два символа равны, сразу пропустить их dp[j] = leftup; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; } leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации } } return dp[m]; } const s = 'bag'; const t = 'pack'; const n = s.length, m = t.length; // Полный перебор let res = editDistanceDFS(s, t, n, m); console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); // Поиск с мемоизацией const mem = Array.from(new Array(n + 1), () => new Array(m + 1).fill(-1)); res = editDistanceDFSMem(s, t, mem, n, m); console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); // Динамическое программирование res = editDistanceDP(s, t); console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); // Динамическое программирование с оптимизацией памяти res = editDistanceDPComp(s, t); console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); ================================================ FILE: ru/codes/javascript/chapter_dynamic_programming/knapsack.js ================================================ /** * File: knapsack.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Рюкзак 0-1: полный перебор */ function knapsackDFS(wgt, val, i, c) { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i === 0 || c === 0) { return 0; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут const no = knapsackDFS(wgt, val, i - 1, c); const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // Вернуть вариант с большей стоимостью из двух возможных return Math.max(no, yes); } /* Рюкзак 0-1: поиск с мемоизацией */ function knapsackDFSMem(wgt, val, mem, i, c) { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i === 0 || c === 0) { return 0; } // Если запись уже есть, вернуть сразу if (mem[i][c] !== -1) { return mem[i][c]; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут const no = knapsackDFSMem(wgt, val, mem, i - 1, c); const yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // Сохранить и вернуть вариант с большей стоимостью из двух решений mem[i][c] = Math.max(no, yes); return mem[i][c]; } /* Рюкзак 0-1: динамическое программирование */ function knapsackDP(wgt, val, cap) { const n = wgt.length; // Инициализация таблицы dp const dp = Array(n + 1) .fill(0) .map(() => Array(cap + 1).fill(0)); // Переход состояний for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = Math.max( dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ function knapsackDPComp(wgt, val, cap) { const n = wgt.length; // Инициализация таблицы dp const dp = Array(cap + 1).fill(0); // Переход состояний for (let i = 1; i <= n; i++) { // Обход в обратном порядке for (let c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // Большее из двух решений: не брать или взять предмет i dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // Полный перебор let res = knapsackDFS(wgt, val, n, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); // Поиск с мемоизацией const mem = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => -1) ); res = knapsackDFSMem(wgt, val, mem, n, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); // Динамическое программирование res = knapsackDP(wgt, val, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); // Динамическое программирование с оптимизацией памяти res = knapsackDPComp(wgt, val, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js ================================================ /** * File: min_cost_climbing_stairs_dp.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Минимальная стоимость подъема по лестнице: динамическое программирование */ function minCostClimbingStairsDP(cost) { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } // Инициализация таблицы dp для хранения решений подзадач const dp = new Array(n + 1); // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = cost[1]; dp[2] = cost[2]; // Переход состояний: постепенное решение больших подзадач через меньшие for (let i = 3; i <= n; i++) { dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ function minCostClimbingStairsDPComp(cost) { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } let a = cost[1], b = cost[2]; for (let i = 3; i <= n; i++) { const tmp = b; b = Math.min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; console.log('Список стоимостей ступеней =', cost); let res = minCostClimbingStairsDP(cost); console.log(`Минимальная стоимость подъема по лестнице = ${res}`); res = minCostClimbingStairsDPComp(cost); console.log(`Минимальная стоимость подъема по лестнице = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_dynamic_programming/min_path_sum.js ================================================ /** * File: min_path_sum.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Минимальная сумма пути: полный перебор */ function minPathSumDFS(grid, i, j) { // Если это верхняя левая ячейка, завершить поиск if (i === 0 && j === 0) { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 || j < 0) { return Infinity; } // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) const up = minPathSumDFS(grid, i - 1, j); const left = minPathSumDFS(grid, i, j - 1); // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) return Math.min(left, up) + grid[i][j]; } /* Минимальная сумма пути: поиск с мемоизацией */ function minPathSumDFSMem(grid, mem, i, j) { // Если это верхняя левая ячейка, завершить поиск if (i === 0 && j === 0) { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 || j < 0) { return Infinity; } // Если запись уже есть, вернуть сразу if (mem[i][j] !== -1) { return mem[i][j]; } // Минимальная стоимость пути для левой и верхней ячеек const up = minPathSumDFSMem(grid, mem, i - 1, j); const left = minPathSumDFSMem(grid, mem, i, j - 1); // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) mem[i][j] = Math.min(left, up) + grid[i][j]; return mem[i][j]; } /* Минимальная сумма пути: динамическое программирование */ function minPathSumDP(grid) { const n = grid.length, m = grid[0].length; // Инициализация таблицы dp const dp = Array.from({ length: n }, () => Array.from({ length: m }, () => 0) ); dp[0][0] = grid[0][0]; // Переход состояний: первая строка for (let j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // Переход состояний: первый столбец for (let i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // Переход состояний: остальные строки и столбцы for (let i = 1; i < n; i++) { for (let j = 1; j < m; j++) { dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ function minPathSumDPComp(grid) { const n = grid.length, m = grid[0].length; // Инициализация таблицы dp const dp = new Array(m); // Переход состояний: первая строка dp[0] = grid[0][0]; for (let j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // Переход состояний: остальные строки for (let i = 1; i < n; i++) { // Переход состояний: первый столбец dp[0] = dp[0] + grid[i][0]; // Переход состояний: остальные столбцы for (let j = 1; j < m; j++) { dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ const grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; const n = grid.length, m = grid[0].length; // Полный перебор let res = minPathSumDFS(grid, n - 1, m - 1); console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); // Поиск с мемоизацией const mem = Array.from({ length: n }, () => Array.from({ length: m }, () => -1) ); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); // Динамическое программирование res = minPathSumDP(grid); console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); // Динамическое программирование с оптимизацией памяти res = minPathSumDPComp(grid); console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js ================================================ /** * File: unbounded_knapsack.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Полный рюкзак: динамическое программирование */ function unboundedKnapsackDP(wgt, val, cap) { const n = wgt.length; // Инициализация таблицы dp const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // Переход состояний for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = Math.max( dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* Полный рюкзак: динамическое программирование с оптимизацией памяти */ function unboundedKnapsackDPComp(wgt, val, cap) { const n = wgt.length; // Инициализация таблицы dp const dp = Array.from({ length: cap + 1 }, () => 0); // Переход состояний for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[c] = dp[c]; } else { // Большее из двух решений: не брать или взять предмет i dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [1, 2, 3]; const val = [5, 11, 15]; const cap = 4; // Динамическое программирование let res = unboundedKnapsackDP(wgt, val, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); // Динамическое программирование с оптимизацией памяти res = unboundedKnapsackDPComp(wgt, val, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_graph/graph_adjacency_list.js ================================================ /** * File: graph_adjacency_list.js * Created Time: 2023-02-09 * Author: Justin (xiefahit@gmail.com) */ const { Vertex } = require('../modules/Vertex'); /* Класс неориентированного графа на основе списка смежности */ class GraphAdjList { // Список смежности, где key — вершина, а value — все смежные ей вершины adjList; /* Конструктор */ constructor(edges) { this.adjList = new Map(); // Добавить все вершины и ребра for (const edge of edges) { this.addVertex(edge[0]); this.addVertex(edge[1]); this.addEdge(edge[0], edge[1]); } } /* Получить число вершин */ size() { return this.adjList.size; } /* Добавление ребра */ addEdge(vet1, vet2) { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 ) { throw new Error('Illegal Argument Exception'); } // Добавить ребро vet1 - vet2 this.adjList.get(vet1).push(vet2); this.adjList.get(vet2).push(vet1); } /* Удаление ребра */ removeEdge(vet1, vet2) { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 || this.adjList.get(vet1).indexOf(vet2) === -1 ) { throw new Error('Illegal Argument Exception'); } // Удалить ребро vet1 - vet2 this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); } /* Добавление вершины */ addVertex(vet) { if (this.adjList.has(vet)) return; // Добавить новый список в список смежности this.adjList.set(vet, []); } /* Удаление вершины */ removeVertex(vet) { if (!this.adjList.has(vet)) { throw new Error('Illegal Argument Exception'); } // Удалить из списка смежности список, соответствующий вершине vet this.adjList.delete(vet); // Обойти списки других вершин и удалить все ребра, содержащие vet for (const set of this.adjList.values()) { const index = set.indexOf(vet); if (index > -1) { set.splice(index, 1); } } } /* Вывести список смежности */ print() { console.log('Список смежности ='); for (const [key, value] of this.adjList) { const tmp = []; for (const vertex of value) { tmp.push(vertex.val); } console.log(key.val + ': ' + tmp.join()); } } } if (require.main === module) { /* Driver Code */ /* Инициализация неориентированного графа */ const v0 = new Vertex(1), v1 = new Vertex(3), v2 = new Vertex(2), v3 = new Vertex(5), v4 = new Vertex(4); const edges = [ [v0, v1], [v1, v2], [v2, v3], [v0, v3], [v2, v4], [v3, v4], ]; const graph = new GraphAdjList(edges); console.log('\nГраф после инициализации'); graph.print(); /* Добавление ребра */ // Вершины 1 и 2 соответствуют v0 и v2 graph.addEdge(v0, v2); console.log('\nГраф после добавления ребра 1-2'); graph.print(); /* Удаление ребра */ // Вершины 1 и 3 соответствуют v0 и v1 graph.removeEdge(v0, v1); console.log('\nГраф после удаления ребра 1-3'); graph.print(); /* Добавление вершины */ const v5 = new Vertex(6); graph.addVertex(v5); console.log('\nГраф после добавления вершины 6'); graph.print(); /* Удаление вершины */ // Вершина 3 соответствует v1 graph.removeVertex(v1); console.log('\nГраф после удаления вершины 3'); graph.print(); } module.exports = { GraphAdjList, }; ================================================ FILE: ru/codes/javascript/chapter_graph/graph_adjacency_matrix.js ================================================ /** * File: graph_adjacency_matrix.js * Created Time: 2023-02-09 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Класс неориентированного графа на основе матрицы смежности */ class GraphAdjMat { vertices; // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» adjMat; // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» /* Конструктор */ constructor(vertices, edges) { this.vertices = []; this.adjMat = []; // Добавление вершины for (const val of vertices) { this.addVertex(val); } // Добавить ребра // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices for (const e of edges) { this.addEdge(e[0], e[1]); } } /* Получить число вершин */ size() { return this.vertices.length; } /* Добавление вершины */ addVertex(val) { const n = this.size(); // Добавить значение новой вершины в список вершин this.vertices.push(val); // Добавить строку в матрицу смежности const newRow = []; for (let j = 0; j < n; j++) { newRow.push(0); } this.adjMat.push(newRow); // Добавить столбец в матрицу смежности for (const row of this.adjMat) { row.push(0); } } /* Удаление вершины */ removeVertex(index) { if (index >= this.size()) { throw new RangeError('Index Out Of Bounds Exception'); } // Удалить вершину с индексом index из списка вершин this.vertices.splice(index, 1); // Удалить строку с индексом index из матрицы смежности this.adjMat.splice(index, 1); // Удалить столбец с индексом index из матрицы смежности for (const row of this.adjMat) { row.splice(index, 1); } } /* Добавление ребра */ // Параметры i и j соответствуют индексам элементов vertices addEdge(i, j) { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) === (j, i) this.adjMat[i][j] = 1; this.adjMat[j][i] = 1; } /* Удаление ребра */ // Параметры i и j соответствуют индексам элементов vertices removeEdge(i, j) { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } this.adjMat[i][j] = 0; this.adjMat[j][i] = 0; } /* Вывести матрицу смежности */ print() { console.log('Список вершин = ', this.vertices); console.log('Матрица смежности =', this.adjMat); } } /* Driver Code */ /* Инициализация неориентированного графа */ // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices const vertices = [1, 3, 2, 5, 4]; const edges = [ [0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4], ]; const graph = new GraphAdjMat(vertices, edges); console.log('\nГраф после инициализации'); graph.print(); /* Добавление ребра */ // Индексы вершин 1 и 2 равны 0 и 2 соответственно graph.addEdge(0, 2); console.log('\nГраф после добавления ребра 1-2'); graph.print(); /* Удаление ребра */ // Индексы вершин 1 и 3 равны 0 и 1 соответственно graph.removeEdge(0, 1); console.log('\nГраф после удаления ребра 1-3'); graph.print(); /* Добавление вершины */ graph.addVertex(6); console.log('\nГраф после добавления вершины 6'); graph.print(); /* Удаление вершины */ // Индекс вершины 3 равен 1 graph.removeVertex(1); console.log('\nГраф после удаления вершины 3'); graph.print(); ================================================ FILE: ru/codes/javascript/chapter_graph/graph_bfs.js ================================================ /** * File: graph_bfs.js * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { GraphAdjList } = require('./graph_adjacency_list'); const { Vertex } = require('../modules/Vertex'); /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины function graphBFS(graph, startVet) { // Последовательность обхода вершин const res = []; // Хеш-множество для хранения уже посещенных вершин const visited = new Set(); visited.add(startVet); // Очередь используется для реализации BFS const que = [startVet]; // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (que.length) { const vet = que.shift(); // Извлечь головную вершину из очереди res.push(vet); // Отметить посещенную вершину // Обойти все смежные вершины данной вершины for (const adjVet of graph.adjList.get(vet) ?? []) { if (visited.has(adjVet)) { continue; // Пропустить уже посещенную вершину } que.push(adjVet); // Помещать в очередь только непосещенные вершины visited.add(adjVet); // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res; } /* Driver Code */ /* Инициализация неориентированного графа */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; const graph = new GraphAdjList(edges); console.log('\nГраф после инициализации'); graph.print(); /* Обход в ширину */ const res = graphBFS(graph, v[0]); console.log('\nПоследовательность вершин при обходе в ширину (BFS)'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: ru/codes/javascript/chapter_graph/graph_dfs.js ================================================ /** * File: graph_dfs.js * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { Vertex } = require('../modules/Vertex'); const { GraphAdjList } = require('./graph_adjacency_list'); /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины function dfs(graph, visited, res, vet) { res.push(vet); // Отметить посещенную вершину visited.add(vet); // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины for (const adjVet of graph.adjList.get(vet)) { if (visited.has(adjVet)) { continue; // Пропустить уже посещенную вершину } // Рекурсивно обходить смежные вершины dfs(graph, visited, res, adjVet); } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины function graphDFS(graph, startVet) { // Последовательность обхода вершин const res = []; // Хеш-множество для хранения уже посещенных вершин const visited = new Set(); dfs(graph, visited, res, startVet); return res; } /* Driver Code */ /* Инициализация неориентированного графа */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; const graph = new GraphAdjList(edges); console.log('\nГраф после инициализации'); graph.print(); /* Обход в глубину */ const res = graphDFS(graph, v[0]); console.log('\nПоследовательность вершин при обходе в глубину (DFS)'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: ru/codes/javascript/chapter_greedy/coin_change_greedy.js ================================================ /** * File: coin_change_greedy.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Размен монет: жадный алгоритм */ function coinChangeGreedy(coins, amt) { // Предположить, что массив coins упорядочен let i = coins.length - 1; let count = 0; // Циклически выполнять жадный выбор, пока не останется суммы while (amt > 0) { // Найти монету, которая меньше остатка суммы и наиболее к нему близка while (i > 0 && coins[i] > amt) { i--; } // Выбрать coins[i] amt -= coins[i]; count++; } // Если допустимое решение не найдено, вернуть -1 return amt === 0 ? count : -1; } /* Driver Code */ // Жадный подход: гарантирует нахождение глобально оптимального решения let coins = [1, 5, 10, 20, 50, 100]; let amt = 186; let res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`Минимальное число монет для набора суммы ${amt} = ${res}`); // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`Минимальное число монет для набора суммы ${amt} = ${res}`); console.log('На самом деле минимум равен 3: 20 + 20 + 20'); // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`Минимальное число монет для набора суммы ${amt} = ${res}`); console.log('На самом деле минимум равен 2: 49 + 49'); ================================================ FILE: ru/codes/javascript/chapter_greedy/fractional_knapsack.js ================================================ /** * File: fractional_knapsack.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Предмет */ class Item { constructor(w, v) { this.w = w; // Вес предмета this.v = v; // Стоимость предмета } } /* Дробный рюкзак: жадный алгоритм */ function fractionalKnapsack(wgt, val, cap) { // Создать список предметов с двумя свойствами: вес и стоимость const items = wgt.map((w, i) => new Item(w, val[i])); // Отсортировать по удельной стоимости item.v / item.w в порядке убывания items.sort((a, b) => b.v / b.w - a.v / a.w); // Циклический жадный выбор let res = 0; for (const item of items) { if (item.w <= cap) { // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком res += item.v; cap -= item.w; } else { // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета res += (item.v / item.w) * cap; // Свободной вместимости больше не осталось, поэтому выйти из цикла break; } } return res; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // Жадный алгоритм const res = fractionalKnapsack(wgt, val, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_greedy/max_capacity.js ================================================ /** * File: max_capacity.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Максимальная вместимость: жадный алгоритм */ function maxCapacity(ht) { // Инициализировать i и j так, чтобы они располагались по двум концам массива let i = 0, j = ht.length - 1; // Начальная максимальная вместимость равна 0 let res = 0; // Выполнять жадный выбор в цикле, пока две доски не встретятся while (i < j) { // Обновить максимальную вместимость const cap = Math.min(ht[i], ht[j]) * (j - i); res = Math.max(res, cap); // Сдвигать внутрь более короткую сторону if (ht[i] < ht[j]) { i += 1; } else { j -= 1; } } return res; } /* Driver Code */ const ht = [3, 8, 5, 2, 7, 7, 3, 4]; // Жадный алгоритм const res = maxCapacity(ht); console.log(`Максимальная вместимость = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_greedy/max_product_cutting.js ================================================ /** * File: max_product_cutting.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Максимальное произведение разрезания: жадный алгоритм */ function maxProductCutting(n) { // Когда n <= 3, обязательно нужно выделить одну 1 if (n <= 3) { return 1 * (n - 1); } // Жадно выделить множители 3, где a — число троек, а b — остаток let a = Math.floor(n / 3); let b = n % 3; if (b === 1) { // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 return Math.pow(3, a - 1) * 2 * 2; } if (b === 2) { // Если остаток равен 2, ничего не делать return Math.pow(3, a) * 2; } // Если остаток равен 0, ничего не делать return Math.pow(3, a); } /* Driver Code */ let n = 58; // Жадный алгоритм let res = maxProductCutting(n); console.log(`Максимальное произведение после разрезания = ${res}`); ================================================ FILE: ru/codes/javascript/chapter_hashing/array_hash_map.js ================================================ /** * File: array_hash_map.js * Created Time: 2022-12-26 * Author: Justin (xiefahit@gmail.com) */ /* Пара ключ-значение Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* Хеш-таблица на основе массива */ class ArrayHashMap { #buckets; constructor() { // Инициализировать массив, содержащий 100 корзин this.#buckets = new Array(100).fill(null); } /* Хеш-функция */ #hashFunc(key) { return key % 100; } /* Операция поиска */ get(key) { let index = this.#hashFunc(key); let pair = this.#buckets[index]; if (pair === null) return null; return pair.val; } /* Операция добавления */ set(key, val) { let index = this.#hashFunc(key); this.#buckets[index] = new Pair(key, val); } /* Операция удаления */ delete(key) { let index = this.#hashFunc(key); // Присвоить null, что означает удаление this.#buckets[index] = null; } /* Получить все пары ключ-значение */ entries() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i]); } } return arr; } /* Получить все ключи */ keys() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i].key); } } return arr; } /* Получить все значения */ values() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i].val); } } return arr; } /* Вывести хеш-таблицу */ print() { let pairSet = this.entries(); for (const pair of pairSet) { console.info(`${pair.key} -> ${pair.val}`); } } } /* Driver Code */ /* Инициализация хеш-таблицы */ const map = new ArrayHashMap(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.set(12836, 'Сяо Ха'); map.set(15937, 'Сяо Ло'); map.set(16750, 'Сяо Суань'); map.set(13276, 'Сяо Фа'); map.set(10583, 'Сяо Я'); console.info('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); map.print(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value let name = map.get(15937); console.info('\nПо номеру 15937 найдено имя ' + name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.delete(10583); console.info('\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение'); map.print(); /* Обход хеш-таблицы */ console.info('\nОтдельный обход пар ключ-значение'); for (const pair of map.entries()) { if (!pair) continue; console.info(pair.key + ' -> ' + pair.val); } console.info('\nОтдельный обход ключей'); for (const key of map.keys()) { console.info(key); } console.info('\nОтдельный обход значений'); for (const val of map.values()) { console.info(val); } ================================================ FILE: ru/codes/javascript/chapter_hashing/hash_map.js ================================================ /** * File: hash_map.js * Created Time: 2022-12-26 * Author: Justin (xiefahit@gmail.com) */ /* Driver Code */ /* Инициализация хеш-таблицы */ const map = new Map(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.set(12836, 'Сяо Ха'); map.set(15937, 'Сяо Ло'); map.set(16750, 'Сяо Суань'); map.set(13276, 'Сяо Фа'); map.set(10583, 'Сяо Я'); console.info('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); console.info(map); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value let name = map.get(15937); console.info('\nПо номеру 15937 найдено имя ' + name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.delete(10583); console.info('\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение'); console.info(map); /* Обход хеш-таблицы */ console.info('\nОтдельный обход пар ключ-значение'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\nОтдельный обход ключей'); for (const k of map.keys()) { console.info(k); } console.info('\nОтдельный обход значений'); for (const v of map.values()) { console.info(v); } ================================================ FILE: ru/codes/javascript/chapter_hashing/hash_map_chaining.js ================================================ /** * File: hash_map_chaining.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Пара ключ-значение Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* Хеш-таблица с цепочками */ class HashMapChaining { #size; // Число пар ключ-значение #capacity; // Вместимость хеш-таблицы #loadThres; // Порог коэффициента загрузки для запуска расширения #extendRatio; // Коэффициент расширения #buckets; // Массив корзин /* Конструктор */ constructor() { this.#size = 0; this.#capacity = 4; this.#loadThres = 2.0 / 3.0; this.#extendRatio = 2; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); } /* Хеш-функция */ #hashFunc(key) { return key % this.#capacity; } /* Коэффициент загрузки */ #loadFactor() { return this.#size / this.#capacity; } /* Операция поиска */ get(key) { const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // Обойти корзину; если найден key, вернуть соответствующее val for (const pair of bucket) { if (pair.key === key) { return pair.val; } } // Если key не найден, вернуть null return null; } /* Операция добавления */ put(key, val) { // Когда коэффициент загрузки превышает порог, выполнить расширение if (this.#loadFactor() > this.#loadThres) { this.#extend(); } const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть for (const pair of bucket) { if (pair.key === key) { pair.val = val; return; } } // Если такого key нет, добавить пару ключ-значение в конец const pair = new Pair(key, val); bucket.push(pair); this.#size++; } /* Операция удаления */ remove(key) { const index = this.#hashFunc(key); let bucket = this.#buckets[index]; // Обойти корзину и удалить из нее пару ключ-значение for (let i = 0; i < bucket.length; i++) { if (bucket[i].key === key) { bucket.splice(i, 1); this.#size--; break; } } } /* Расширить хеш-таблицу */ #extend() { // Временно сохранить исходную хеш-таблицу const bucketsTmp = this.#buckets; // Инициализация новой хеш-таблицы после расширения this.#capacity *= this.#extendRatio; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); this.#size = 0; // Перенести пары ключ-значение из исходной хеш-таблицы в новую for (const bucket of bucketsTmp) { for (const pair of bucket) { this.put(pair.key, pair.val); } } } /* Вывести хеш-таблицу */ print() { for (const bucket of this.#buckets) { let res = []; for (const pair of bucket) { res.push(pair.key + ' -> ' + pair.val); } console.log(res); } } } /* Driver Code */ /* Инициализация хеш-таблицы */ const map = new HashMapChaining(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(12836, 'Сяо Ха'); map.put(15937, 'Сяо Ло'); map.put(16750, 'Сяо Суань'); map.put(13276, 'Сяо Фа'); map.put(10583, 'Сяо Я'); console.log('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); map.print(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value const name = map.get(13276); console.log('\nДля номера 13276 найдено имя ' + name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(12836); console.log('\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение'); map.print(); ================================================ FILE: ru/codes/javascript/chapter_hashing/hash_map_open_addressing.js ================================================ /** * File: hashMapOpenAddressing.js * Created Time: 2023-06-13 * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) */ /* Пара ключ-значение Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* Хеш-таблица с открытой адресацией */ class HashMapOpenAddressing { #size; // Число пар ключ-значение #capacity; // Вместимость хеш-таблицы #loadThres; // Порог коэффициента загрузки для запуска расширения #extendRatio; // Коэффициент расширения #buckets; // Массив корзин #TOMBSTONE; // Удалить метку /* Конструктор */ constructor() { this.#size = 0; // Число пар ключ-значение this.#capacity = 4; // Вместимость хеш-таблицы this.#loadThres = 2.0 / 3.0; // Порог коэффициента загрузки для запуска расширения this.#extendRatio = 2; // Коэффициент расширения this.#buckets = Array(this.#capacity).fill(null); // Массив корзин this.#TOMBSTONE = new Pair(-1, '-1'); // Удалить метку } /* Хеш-функция */ #hashFunc(key) { return key % this.#capacity; } /* Коэффициент загрузки */ #loadFactor() { return this.#size / this.#capacity; } /* Найти индекс корзины, соответствующий key */ #findBucket(key) { let index = this.#hashFunc(key); let firstTombstone = -1; // Выполнять линейное пробирование и завершить при встрече с пустой корзиной while (this.#buckets[index] !== null) { // Если встретился key, вернуть соответствующий индекс корзины if (this.#buckets[index].key === key) { // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс if (firstTombstone !== -1) { this.#buckets[firstTombstone] = this.#buckets[index]; this.#buckets[index] = this.#TOMBSTONE; return firstTombstone; // Вернуть индекс корзины после перемещения } return index; // Вернуть индекс корзины } // Записать первую встретившуюся метку удаления if ( firstTombstone === -1 && this.#buckets[index] === this.#TOMBSTONE ) { firstTombstone = index; } // Вычислить индекс корзины; при выходе за конец вернуться к началу index = (index + 1) % this.#capacity; } // Если key не существует, вернуть индекс точки добавления return firstTombstone === -1 ? index : firstTombstone; } /* Операция поиска */ get(key) { // Найти индекс корзины, соответствующий key const index = this.#findBucket(key); // Если пара ключ-значение найдена, вернуть соответствующее val if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { return this.#buckets[index].val; } // Если пары ключ-значение не существует, вернуть null return null; } /* Операция добавления */ put(key, val) { // Когда коэффициент загрузки превышает порог, выполнить расширение if (this.#loadFactor() > this.#loadThres) { this.#extend(); } // Найти индекс корзины, соответствующий key const index = this.#findBucket(key); // Если пара ключ-значение найдена, перезаписать val и вернуть if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { this.#buckets[index].val = val; return; } // Если пары ключ-значение нет, добавить ее this.#buckets[index] = new Pair(key, val); this.#size++; } /* Операция удаления */ remove(key) { // Найти индекс корзины, соответствующий key const index = this.#findBucket(key); // Если пара ключ-значение найдена, заменить ее меткой удаления if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { this.#buckets[index] = this.#TOMBSTONE; this.#size--; } } /* Расширить хеш-таблицу */ #extend() { // Временно сохранить исходную хеш-таблицу const bucketsTmp = this.#buckets; // Инициализация новой хеш-таблицы после расширения this.#capacity *= this.#extendRatio; this.#buckets = Array(this.#capacity).fill(null); this.#size = 0; // Перенести пары ключ-значение из исходной хеш-таблицы в новую for (const pair of bucketsTmp) { if (pair !== null && pair !== this.#TOMBSTONE) { this.put(pair.key, pair.val); } } } /* Вывести хеш-таблицу */ print() { for (const pair of this.#buckets) { if (pair === null) { console.log('null'); } else if (pair === this.#TOMBSTONE) { console.log('TOMBSTONE'); } else { console.log(pair.key + ' -> ' + pair.val); } } } } /* Driver Code */ // Инициализация хеш-таблицы const hashmap = new HashMapOpenAddressing(); // Операция добавления // Добавить пару (key, val) в хеш-таблицу hashmap.put(12836, 'Сяо Ха'); hashmap.put(15937, 'Сяо Ло'); hashmap.put(16750, 'Сяо Суань'); hashmap.put(13276, 'Сяо Фа'); hashmap.put(10583, 'Сяо Я'); console.log('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); hashmap.print(); // Операция поиска // Передать ключ key в хеш-таблицу и получить значение val const name = hashmap.get(13276); console.log('\nДля номера 13276 найдено имя ' + name); // Операция удаления // Удалить пару (key, val) из хеш-таблицы hashmap.remove(16750); console.log('\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение'); hashmap.print(); ================================================ FILE: ru/codes/javascript/chapter_hashing/simple_hash.js ================================================ /** * File: simple_hash.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Аддитивное хеширование */ function addHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* Мультипликативное хеширование */ function mulHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (31 * hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* XOR-хеширование */ function xorHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash ^= c.charCodeAt(0); } return hash % MODULUS; } /* Хеширование с циклическим сдвигом */ function rotHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; } return hash; } /* Driver Code */ const key = 'Hello Algo'; let hash = addHash(key); console.log('Хеш-сумма сложением = ' + hash); hash = mulHash(key); console.log('Хеш-сумма умножением = ' + hash); hash = xorHash(key); console.log('Хеш-сумма XOR = ' + hash); hash = rotHash(key); console.log('Хеш-сумма с циклическим сдвигом = ' + hash); ================================================ FILE: ru/codes/javascript/chapter_heap/my_heap.js ================================================ /** * File: my_heap.js * Created Time: 2023-02-06 * Author: what-is-me (whatisme@outlook.jp) */ const { printHeap } = require('../modules/PrintUtil'); /* Класс максимальной кучи */ class MaxHeap { #maxHeap; /* Конструктор, создающий пустую кучу или строящий кучу по входному списку */ constructor(nums) { // Добавить элементы списка в кучу без изменений this.#maxHeap = nums === undefined ? [] : [...nums]; // Выполнить heapify для всех узлов, кроме листовых for (let i = this.#parent(this.size() - 1); i >= 0; i--) { this.#siftDown(i); } } /* Получить индекс левого дочернего узла */ #left(i) { return 2 * i + 1; } /* Получить индекс правого дочернего узла */ #right(i) { return 2 * i + 2; } /* Получить индекс родительского узла */ #parent(i) { return Math.floor((i - 1) / 2); // Округление вниз при делении } /* Поменять элементы местами */ #swap(i, j) { const tmp = this.#maxHeap[i]; this.#maxHeap[i] = this.#maxHeap[j]; this.#maxHeap[j] = tmp; } /* Получение размера кучи */ size() { return this.#maxHeap.length; } /* Проверка, пуста ли куча */ isEmpty() { return this.size() === 0; } /* Доступ к элементу на вершине кучи */ peek() { return this.#maxHeap[0]; } /* Добавление элемента в кучу */ push(val) { // Добавление узла this.#maxHeap.push(val); // Просеивание снизу вверх this.#siftUp(this.size() - 1); } /* Начиная с узла i, выполнить просеивание снизу вверх */ #siftUp(i) { while (true) { // Получение родительского узла для узла i const p = this.#parent(i); // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break; // Поменять два узла местами this.#swap(i, p); // Циклическое просеивание вверх i = p; } } /* Извлечение элемента из кучи */ pop() { // Обработка пустого случая if (this.isEmpty()) throw new Error('куча пуста'); // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) this.#swap(0, this.size() - 1); // Удаление узла const val = this.#maxHeap.pop(); // Просеивание сверху вниз this.#siftDown(0); // Вернуть элемент с вершины кучи return val; } /* Начиная с узла i, выполнить просеивание сверху вниз */ #siftDown(i) { while (true) { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma const l = this.#left(i), r = this.#right(i); let ma = i; if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l; if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r; // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if (ma === i) break; // Поменять два узла местами this.#swap(i, ma); // Циклическое просеивание вниз i = ma; } } /* Вывести кучу (двоичное дерево) */ print() { printHeap(this.#maxHeap); } /* Извлечь элементы из кучи */ getMaxHeap() { return this.#maxHeap; } } /* Driver Code */ if (require.main === module) { /* Инициализация максимальной кучи */ const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); console.log('\nПосле построения кучи из входного списка'); maxHeap.print(); /* Получение элемента с вершины кучи */ let peek = maxHeap.peek(); console.log(`\nЭлемент на вершине кучи = ${peek}`); /* Добавление элемента в кучу */ let val = 7; maxHeap.push(val); console.log(`\nПосле добавления элемента ${val} в кучу`); maxHeap.print(); /* Извлечение элемента с вершины кучи */ peek = maxHeap.pop(); console.log(`\nПосле извлечения элемента вершины кучи ${peek}`); maxHeap.print(); /* Получение размера кучи */ let size = maxHeap.size(); console.log(`\nКоличество элементов в куче = ${size}`); /* Проверка, пуста ли куча */ let isEmpty = maxHeap.isEmpty(); console.log(`\nПуста ли куча: ${isEmpty}`); } module.exports = { MaxHeap, }; ================================================ FILE: ru/codes/javascript/chapter_heap/top_k.js ================================================ /** * File: top_k.js * Created Time: 2023-08-13 * Author: Justin (xiefahit@gmail.com) */ const { MaxHeap } = require('./my_heap'); /* Добавление элемента в кучу */ function pushMinHeap(maxHeap, val) { // Инвертировать знак элемента maxHeap.push(-val); } /* Извлечение элемента из кучи */ function popMinHeap(maxHeap) { // Инвертировать знак элемента return -maxHeap.pop(); } /* Доступ к элементу на вершине кучи */ function peekMinHeap(maxHeap) { // Инвертировать знак элемента return -maxHeap.peek(); } /* Извлечь элементы из кучи */ function getMinHeap(maxHeap) { // Инвертировать знак элемента return maxHeap.getMaxHeap().map((num) => -num); } /* Найти k наибольших элементов массива с помощью кучи */ function topKHeap(nums, k) { // Инициализация минимальной кучи // Обратите внимание: мы инвертируем все элементы кучи, чтобы с помощью максимальной кучи имитировать минимальную const maxHeap = new MaxHeap([]); // Поместить первые k элементов массива в кучу for (let i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // Начиная с элемента k+1, поддерживать длину кучи равной k for (let i = k; i < nums.length; i++) { // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } // Вернуть элементы кучи return getMinHeap(maxHeap); } /* Driver Code */ const nums = [1, 7, 6, 3, 2]; const k = 3; const res = topKHeap(nums, k); console.log(`Наибольшие ${k} элементов`, res); ================================================ FILE: ru/codes/javascript/chapter_searching/binary_search.js ================================================ /** * File: binary_search.js * Created Time: 2022-12-22 * Author: JoseHung (szhong@link.cuhk.edu.hk) */ /* Бинарный поиск (двусторонне замкнутый интервал) */ function binarySearch(nums, target) { // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно let i = 0, j = nums.length - 1; // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) while (i <= j) { // Вычислить индекс середины m, используя parseInt() для округления вниз const m = parseInt(i + (j - i) / 2); if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j] i = m + 1; else if (nums[m] > target) // Это означает, что target находится в интервале [i, m-1] j = m - 1; else return m; // Целевой элемент найден, вернуть его индекс } // Целевой элемент не найден, вернуть -1 return -1; } /* Бинарный поиск (лево замкнутый, право открытый интервал) */ function binarySearchLCRO(nums, target) { // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно let i = 0, j = nums.length; // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) while (i < j) { // Вычислить индекс середины m, используя parseInt() для округления вниз const m = parseInt(i + (j - i) / 2); if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j) i = m + 1; else if (nums[m] > target) // Это означает, что target находится в интервале [i, m) j = m; // Целевой элемент найден, вернуть его индекс else return m; } // Целевой элемент не найден, вернуть -1 return -1; } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* Бинарный поиск (двусторонне замкнутый интервал) */ let index = binarySearch(nums, target); console.log('Индекс целевого элемента 6 = ' + index); /* Бинарный поиск (лево замкнутый, право открытый интервал) */ index = binarySearchLCRO(nums, target); console.log('Индекс целевого элемента 6 = ' + index); ================================================ FILE: ru/codes/javascript/chapter_searching/binary_search_edge.js ================================================ /** * File: binary_search_edge.js * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ const { binarySearchInsertion } = require('./binary_search_insertion.js'); /* Бинарный поиск самого левого target */ function binarySearchLeftEdge(nums, target) { // Эквивалентно поиску точки вставки target const i = binarySearchInsertion(nums, target); // target не найден, вернуть -1 if (i === nums.length || nums[i] !== target) { return -1; } // Найти target и вернуть индекс i return i; } /* Бинарный поиск самого правого target */ function binarySearchRightEdge(nums, target) { // Преобразовать задачу в поиск самого левого target + 1 const i = binarySearchInsertion(nums, target + 1); // j указывает на самый правый target, а i — на первый элемент больше target const j = i - 1; // target не найден, вернуть -1 if (j === -1 || nums[j] !== target) { return -1; } // Найти target и вернуть индекс j return j; } /* Driver Code */ // Массив с повторяющимися элементами const nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\nМассив nums = ' + nums); // Бинарный поиск левой и правой границы for (const target of [6, 7]) { let index = binarySearchLeftEdge(nums, target); console.log('Индекс самого левого элемента ' + target + ' равен ' + index); index = binarySearchRightEdge(nums, target); console.log('Индекс самого правого элемента ' + target + ' равен ' + index); } ================================================ FILE: ru/codes/javascript/chapter_searching/binary_search_insertion.js ================================================ /** * File: binary_search_insertion.js * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Бинарный поиск точки вставки (без повторяющихся элементов) */ function binarySearchInsertionSimple(nums, target) { let i = 0, j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // Вычислить индекс середины m, используя Math.floor() для округления вниз if (nums[m] < target) { i = m + 1; // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1; // target находится в интервале [i, m-1] } else { return m; // Найти target и вернуть точку вставки m } } // target не найден, вернуть точку вставки i return i; } /* Бинарный поиск точки вставки (с повторяющимися элементами) */ function binarySearchInsertion(nums, target) { let i = 0, j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // Вычислить индекс середины m, используя Math.floor() для округления вниз if (nums[m] < target) { i = m + 1; // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1; // target находится в интервале [i, m-1] } else { j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] } } // Вернуть точку вставки i return i; } /* Driver Code */ // Массив без повторяющихся элементов let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; console.log('\nМассив nums = ' + nums); // Бинарный поиск точки вставки for (const target of [6, 9]) { const index = binarySearchInsertionSimple(nums, target); console.log('Индекс позиции вставки элемента ' + target + ' равен ' + index); } // Массив с повторяющимися элементами nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\nМассив nums = ' + nums); // Бинарный поиск точки вставки for (const target of [2, 6, 20]) { const index = binarySearchInsertion(nums, target); console.log('Индекс позиции вставки элемента ' + target + ' равен ' + index); } module.exports = { binarySearchInsertion, }; ================================================ FILE: ru/codes/javascript/chapter_searching/hashing_search.js ================================================ /** * File: hashing_search.js * Created Time: 2022-12-29 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { arrToLinkedList } = require('../modules/ListNode'); /* Хеш-поиск (массив) */ function hashingSearchArray(map, target) { // key хеш-таблицы: целевой элемент, value: индекс // Если такого key нет в хеш-таблице, вернуть -1 return map.has(target) ? map.get(target) : -1; } /* Хеш-поиск (связный список) */ function hashingSearchLinkedList(map, target) { // key хеш-таблицы: значение целевого узла, value: объект узла // Если такого key нет в хеш-таблице, вернуть null return map.has(target) ? map.get(target) : null; } /* Driver Code */ const target = 3; /* Хеш-поиск (массив) */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // Инициализация хеш-таблицы const map = new Map(); for (let i = 0; i < nums.length; i++) { map.set(nums[i], i); // key: элемент, value: индекс } const index = hashingSearchArray(map, target); console.log('Индекс целевого элемента 3 = ' + index); /* Хеш-поиск (связный список) */ let head = arrToLinkedList(nums); // Инициализация хеш-таблицы const map1 = new Map(); while (head != null) { map1.set(head.val, head); // key: значение узла, value: узел head = head.next; } const node = hashingSearchLinkedList(map1, target); console.log('Объект узла со значением 3 =', node); ================================================ FILE: ru/codes/javascript/chapter_searching/linear_search.js ================================================ /** * File: linear_search.js * Created Time: 2022-12-22 * Author: JoseHung (szhong@link.cuhk.edu.hk) */ const { ListNode, arrToLinkedList } = require('../modules/ListNode'); /* Линейный поиск (массив) */ function linearSearchArray(nums, target) { // Обход массива for (let i = 0; i < nums.length; i++) { // Целевой элемент найден, вернуть его индекс if (nums[i] === target) { return i; } } // Целевой элемент не найден, вернуть -1 return -1; } /* Линейный поиск (связный список) */ function linearSearchLinkedList(head, target) { // Обойти связный список while (head) { // Найти целевой узел и вернуть его if (head.val === target) { return head; } head = head.next; } // Целевой узел не найден, вернуть null return null; } /* Driver Code */ const target = 3; /* Выполнить линейный поиск в массиве */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; const index = linearSearchArray(nums, target); console.log('Индекс целевого элемента 3 = ' + index); /* Выполнить линейный поиск в связном списке */ const head = arrToLinkedList(nums); const node = linearSearchLinkedList(head, target); console.log('Объект узла со значением 3 = ', node); ================================================ FILE: ru/codes/javascript/chapter_searching/two_sum.js ================================================ /** * File: two_sum.js * Created Time: 2022-12-15 * Author: gyt95 (gytkwan@gmail.com) */ /* Метод 1: полный перебор */ function twoSumBruteForce(nums, target) { const n = nums.length; // Два вложенных цикла, временная сложность O(n^2) for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { if (nums[i] + nums[j] === target) { return [i, j]; } } } return []; } /* Метод 2: вспомогательная хеш-таблица */ function twoSumHashTable(nums, target) { // Вспомогательная хеш-таблица, пространственная сложность O(n) let m = {}; // Один цикл, временная сложность O(n) for (let i = 0; i < nums.length; i++) { if (m[target - nums[i]] !== undefined) { return [m[target - nums[i]], i]; } else { m[nums[i]] = i; } } return []; } /* Driver Code */ // Метод 1 const nums = [2, 7, 11, 15], target = 13; let res = twoSumBruteForce(nums, target); console.log('Результат метода 1 res = ', res); // Метод 2 res = twoSumHashTable(nums, target); console.log('Результат метода 2 res = ', res); ================================================ FILE: ru/codes/javascript/chapter_sorting/bubble_sort.js ================================================ /** * File: bubble_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Пузырьковая сортировка */ function bubbleSort(nums) { // Внешний цикл: неотсортированный диапазон [0, i] for (let i = nums.length - 1; i > 0; i--) { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* Пузырьковая сортировка (оптимизация флагом) */ function bubbleSortWithFlag(nums) { // Внешний цикл: неотсортированный диапазон [0, i] for (let i = nums.length - 1; i > 0; i--) { let flag = false; // Инициализировать флаг // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // Записать обмен элементов } } if (!flag) break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); console.log('После пузырьковой сортировки nums =', nums); const nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); console.log('После пузырьковой сортировки nums =', nums1); ================================================ FILE: ru/codes/javascript/chapter_sorting/bucket_sort.js ================================================ /** * File: bucket_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* Сортировка корзинами */ function bucketSort(nums) { // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину const k = nums.length / 2; const buckets = []; for (let i = 0; i < k; i++) { buckets.push([]); } // 1. Распределить элементы массива по корзинам for (const num of nums) { // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] const i = Math.floor(num * k); // Добавить num в корзину i buckets[i].push(num); } // 2. Выполнить сортировку внутри каждой корзины for (const bucket of buckets) { // Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки bucket.sort((a, b) => a - b); } // 3. Обойти корзины и объединить результаты let i = 0; for (const bucket of buckets) { for (const num of bucket) { nums[i++] = num; } } } /* Driver Code */ const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); console.log('После сортировки корзинами nums =', nums); ================================================ FILE: ru/codes/javascript/chapter_sorting/counting_sort.js ================================================ /** * File: counting_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* Сортировка подсчетом */ // Простая реализация, не подходит для сортировки объектов function countingSortNaive(nums) { // 1. Найти максимальный элемент массива m let m = Math.max(...nums); // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num const counter = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. Обойти counter и заполнить исходный массив nums элементами let i = 0; for (let num = 0; num < m + 1; num++) { for (let j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* Сортировка подсчетом */ // Полная реализация, позволяет сортировать объекты и является стабильной сортировкой function countingSort(nums) { // 1. Найти максимальный элемент массива m let m = Math.max(...nums); // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num const counter = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» // То есть counter[num]-1 — это индекс последнего появления num в res for (let i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res // Инициализировать массив res для хранения результата const n = nums.length; const res = new Array(n); for (let i = n - 1; i >= 0; i--) { const num = nums[i]; res[counter[num] - 1] = num; // Поместить num по соответствующему индексу counter[num]--; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num } // Перезаписать исходный массив nums массивом результата res for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Driver Code */ const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); console.log('После сортировки подсчетом (объекты не поддерживаются) nums =', nums); const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); console.log('После сортировки подсчетом nums1 =', nums1); ================================================ FILE: ru/codes/javascript/chapter_sorting/heap_sort.js ================================================ /** * File: heap_sort.js * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ function siftDown(nums, n, i) { while (true) { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma let l = 2 * i + 1; let r = 2 * i + 2; let ma = i; if (l < n && nums[l] > nums[ma]) { ma = l; } if (r < n && nums[r] > nums[ma]) { ma = r; } // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if (ma === i) { break; } // Поменять два узла местами [nums[i], nums[ma]] = [nums[ma], nums[i]]; // Циклическое просеивание вниз i = ma; } } /* Сортировка кучей */ function heapSort(nums) { // Построение кучи: выполнить heapify для всех узлов, кроме листовых for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // Извлекать максимальный элемент из кучи в течение n-1 итераций for (let i = nums.length - 1; i > 0; i--) { // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) [nums[0], nums[i]] = [nums[i], nums[0]]; // Начиная с корневого узла, выполнить просеивание сверху вниз siftDown(nums, i, 0); } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; heapSort(nums); console.log('После сортировки кучей nums =', nums); ================================================ FILE: ru/codes/javascript/chapter_sorting/insertion_sort.js ================================================ /** * File: insertion_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Сортировка вставками */ function insertionSort(nums) { // Внешний цикл: отсортированный диапазон [0, i-1] for (let i = 1; i < nums.length; i++) { let base = nums[i], j = i - 1; // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // Сдвинуть nums[j] на одну позицию вправо j--; } nums[j + 1] = base; // Поместить base в правильную позицию } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); console.log('После сортировки вставками nums =', nums); ================================================ FILE: ru/codes/javascript/chapter_sorting/merge_sort.js ================================================ /** * File: merge_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Объединить левый и правый подмассивы */ function merge(nums, left, mid, right) { // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] // Создать временный массив tmp для хранения результата слияния const tmp = new Array(right - left + 1); // Инициализировать начальные индексы левого и правого подмассивов let i = left, j = mid + 1, k = 0; // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* Сортировка слиянием */ function mergeSort(nums, left, right) { // Условие завершения if (left >= right) return; // Завершить рекурсию, когда длина подмассива равна 1 // Этап разбиения let mid = Math.floor(left + (right - left) / 2); // Вычислить середину mergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив mergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив // Этап слияния merge(nums, left, mid, right); } /* Driver Code */ const nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); console.log('После сортировки слиянием nums =', nums); ================================================ FILE: ru/codes/javascript/chapter_sorting/quick_sort.js ================================================ /** * File: quick_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Класс быстрой сортировки */ class QuickSort { /* Обмен элементов */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Разбиение с опорными указателями */ partition(nums, left, right) { // Взять nums[left] в качестве опорного элемента let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j -= 1; // Идти справа налево в поисках первого элемента меньше опорного } while (i < j && nums[i] <= nums[left]) { i += 1; // Идти слева направо в поисках первого элемента больше опорного } // Обмен элементов this.swap(nums, i, j); // Поменять эти два элемента местами } this.swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } /* Быстрая сортировка */ quickSort(nums, left, right) { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) return; // Разбиение с опорными указателями const pivot = this.partition(nums, left, right); // Рекурсивно обработать левый и правый подмассивы this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* Класс быстрой сортировки (оптимизация медианным опорным элементом) */ class QuickSortMedian { /* Обмен элементов */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Выбрать медиану из трех кандидатов */ medianThree(nums, left, mid, right) { let l = nums[left], m = nums[mid], r = nums[right]; // m находится между l и r if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // l находится между m и r if ((m <= l && l <= r) || (r <= l && l <= m)) return left; return right; } /* Разбиение с опорными указателями (медиана трех) */ partition(nums, left, right) { // Выбрать медиану из трех кандидатов let med = this.medianThree( nums, left, Math.floor((left + right) / 2), right ); // Переместить медиану в крайний левый элемент массива this.swap(nums, left, med); // Взять nums[left] в качестве опорного элемента let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Идти справа налево в поисках первого элемента меньше опорного while (i < j && nums[i] <= nums[left]) i++; // Идти слева направо в поисках первого элемента больше опорного this.swap(nums, i, j); // Поменять эти два элемента местами } this.swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } /* Быстрая сортировка */ quickSort(nums, left, right) { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) return; // Разбиение с опорными указателями const pivot = this.partition(nums, left, right); // Рекурсивно обработать левый и правый подмассивы this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* Класс быстрой сортировки (оптимизация глубины рекурсии) */ class QuickSortTailCall { /* Обмен элементов */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Разбиение с опорными указателями */ partition(nums, left, right) { // Взять nums[left] в качестве опорного элемента let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // Идти справа налево в поисках первого элемента меньше опорного while (i < j && nums[i] <= nums[left]) i++; // Идти слева направо в поисках первого элемента больше опорного this.swap(nums, i, j); // Поменять эти два элемента местами } this.swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } /* Быстрая сортировка (оптимизация глубины рекурсии) */ quickSort(nums, left, right) { // Завершить, когда длина подмассива равна 1 while (left < right) { // Операция разбиения с опорными указателями let pivot = this.partition(nums, left, right); // Выполнить быструю сортировку для более короткого из двух подмассивов if (pivot - left < right - pivot) { this.quickSort(nums, left, pivot - 1); // Рекурсивно отсортировать левый подмассив left = pivot + 1; // Оставшийся неотсортированный диапазон: [pivot + 1, right] } else { this.quickSort(nums, pivot + 1, right); // Рекурсивно отсортировать правый подмассив right = pivot - 1; // Оставшийся неотсортированный диапазон: [left, pivot - 1] } } } } /* Driver Code */ /* Быстрая сортировка */ const nums = [2, 4, 1, 0, 3, 5]; const quickSort = new QuickSort(); quickSort.quickSort(nums, 0, nums.length - 1); console.log('После быстрой сортировки nums =', nums); /* Быстрая сортировка (оптимизация медианным опорным элементом) */ const nums1 = [2, 4, 1, 0, 3, 5]; const quickSortMedian = new QuickSortMedian(); quickSortMedian.quickSort(nums1, 0, nums1.length - 1); console.log('После быстрой сортировки (оптимизация медианным опорным элементом) nums =', nums1); /* Быстрая сортировка (оптимизация глубины рекурсии) */ const nums2 = [2, 4, 1, 0, 3, 5]; const quickSortTailCall = new QuickSortTailCall(); quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); console.log('После быстрой сортировки (оптимизация глубины рекурсии) nums =', nums2); ================================================ FILE: ru/codes/javascript/chapter_sorting/radix_sort.js ================================================ /** * File: radix_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* Получить k-й разряд элемента num, где exp = 10^(k-1) */ function digit(num, exp) { // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени return Math.floor(num / exp) % 10; } /* Сортировка подсчетом (сортировка по k-му разряду nums) */ function countingSortDigit(nums, exp) { // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 const counter = new Array(10).fill(0); const n = nums.length; // Подсчитать число появлений каждой цифры от 0 до 9 for (let i = 0; i < n; i++) { const d = digit(nums[i], exp); // Получить k-й разряд nums[i], обозначив его как d counter[d]++; // Подсчитать число появлений цифры d } // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» for (let i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // Выполняя обратный проход, заполнить res элементами по статистике в корзинах const res = new Array(n).fill(0); for (let i = n - 1; i >= 0; i--) { const d = digit(nums[i], exp); const j = counter[d] - 1; // Получить индекс j цифры d в массиве res[j] = nums[i]; // Поместить текущий элемент по индексу j counter[d]--; // Уменьшить количество d на 1 } // Перезаписать исходный массив nums результатом for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Поразрядная сортировка */ function radixSort(nums) { // Получить максимальный элемент массива, чтобы определить максимальное число разрядов let m = Math.max(... nums); // Проходить разряды от младшего к старшему for (let exp = 1; exp <= m; exp *= 10) { // Выполнить сортировку подсчетом по k-му разряду элементов массива // k = 1 -> exp = 1 // k = 2 -> exp = 10 // то есть exp = 10^(k-1) countingSortDigit(nums, exp); } } /* Driver Code */ const nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radixSort(nums); console.log('После поразрядной сортировки nums =', nums); ================================================ FILE: ru/codes/javascript/chapter_sorting/selection_sort.js ================================================ /** * File: selection_sort.js * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* Сортировка выбором */ function selectionSort(nums) { let n = nums.length; // Внешний цикл: неотсортированный диапазон [i, n-1] for (let i = 0; i < n - 1; i++) { // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне let k = i; for (let j = i + 1; j < n; j++) { if (nums[j] < nums[k]) { k = j; // Записать индекс минимального элемента } } // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона [nums[i], nums[k]] = [nums[k], nums[i]]; } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; selectionSort(nums); console.log('После сортировки выбором nums =', nums); ================================================ FILE: ru/codes/javascript/chapter_stack_and_queue/array_deque.js ================================================ /** * File: array_deque.js * Created Time: 2023-02-28 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Двусторонняя очередь на основе кольцевого массива */ class ArrayDeque { #nums; // Массив для хранения элементов двусторонней очереди #front; // Указатель head, указывающий на первый элемент очереди #queSize; // Длина двусторонней очереди /* Конструктор */ constructor(capacity) { this.#nums = new Array(capacity); this.#front = 0; this.#queSize = 0; } /* Получить вместимость двусторонней очереди */ capacity() { return this.#nums.length; } /* Получение длины двусторонней очереди */ size() { return this.#queSize; } /* Проверка, пуста ли двусторонняя очередь */ isEmpty() { return this.#queSize === 0; } /* Вычислить индекс в кольцевом массиве */ index(i) { // С помощью операции взятия по модулю соединить начало и конец массива // Когда i выходит за конец массива, он возвращается в начало // Когда i выходит за начало массива, он возвращается в конец return (i + this.capacity()) % this.capacity(); } /* Добавление в голову очереди */ pushFirst(num) { if (this.#queSize === this.capacity()) { console.log('Двусторонняя очередь заполнена'); return; } // Указатель головы сдвигается на одну позицию влево // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост this.#front = this.index(this.#front - 1); // Добавить num в голову очереди this.#nums[this.#front] = num; this.#queSize++; } /* Добавление в хвост очереди */ pushLast(num) { if (this.#queSize === this.capacity()) { console.log('Двусторонняя очередь заполнена'); return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 const rear = this.index(this.#front + this.#queSize); // Добавить num в хвост очереди this.#nums[rear] = num; this.#queSize++; } /* Извлечение из головы очереди */ popFirst() { const num = this.peekFirst(); // Указатель головы сдвигается на одну позицию назад this.#front = this.index(this.#front + 1); this.#queSize--; return num; } /* Извлечение из хвоста очереди */ popLast() { const num = this.peekLast(); this.#queSize--; return num; } /* Доступ к элементу в начале очереди */ peekFirst() { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); return this.#nums[this.#front]; } /* Доступ к элементу в конце очереди */ peekLast() { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); // Вычислить индекс хвостового элемента const last = this.index(this.#front + this.#queSize - 1); return this.#nums[last]; } /* Вернуть массив для вывода */ toArray() { // Преобразовывать только элементы списка в пределах фактической длины const res = []; for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) { res[i] = this.#nums[this.index(j)]; } return res; } } /* Driver Code */ /* Инициализация двусторонней очереди */ const capacity = 5; const deque = new ArrayDeque(capacity); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); console.log('Двусторонняя очередь deque = [' + deque.toArray() + ']'); /* Доступ к элементу */ const peekFirst = deque.peekFirst(); console.log('Первый элемент peekFirst = ' + peekFirst); const peekLast = deque.peekLast(); console.log('Последний элемент peekLast = ' + peekLast); /* Добавление элемента в очередь */ deque.pushLast(4); console.log('После добавления элемента 4 в хвост deque = [' + deque.toArray() + ']'); deque.pushFirst(1); console.log('После добавления элемента 1 в голову deque = [' + deque.toArray() + ']'); /* Извлечение элемента из очереди */ const popLast = deque.popLast(); console.log( 'Извлеченный из хвоста элемент = ' + popLast + ', deque после извлечения из хвоста = [' + deque.toArray() + ']' ); const popFirst = deque.popFirst(); console.log( 'Извлеченный из головы элемент = ' + popFirst + ', deque после извлечения из головы = [' + deque.toArray() + ']' ); /* Получение длины двусторонней очереди */ const size = deque.size(); console.log('Длина двусторонней очереди size = ' + size); /* Проверка, пуста ли двусторонняя очередь */ const isEmpty = deque.isEmpty(); console.log('Пуста ли двусторонняя очередь = ' + isEmpty); ================================================ FILE: ru/codes/javascript/chapter_stack_and_queue/array_queue.js ================================================ /** * File: array_queue.js * Created Time: 2022-12-13 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Очередь на основе кольцевого массива */ class ArrayQueue { #nums; // Массив для хранения элементов очереди #front = 0; // Указатель head, указывающий на первый элемент очереди #queSize = 0; // Длина очереди constructor(capacity) { this.#nums = new Array(capacity); } /* Получить вместимость очереди */ get capacity() { return this.#nums.length; } /* Получение длины очереди */ get size() { return this.#queSize; } /* Проверка, пуста ли очередь */ isEmpty() { return this.#queSize === 0; } /* Поместить в очередь */ push(num) { if (this.size === this.capacity) { console.log('Очередь заполнена'); return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива const rear = (this.#front + this.size) % this.capacity; // Добавить num в хвост очереди this.#nums[rear] = num; this.#queSize++; } /* Извлечь из очереди */ pop() { const num = this.peek(); // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива this.#front = (this.#front + 1) % this.capacity; this.#queSize--; return num; } /* Доступ к элементу в начале очереди */ peek() { if (this.isEmpty()) throw new Error('очередь пуста'); return this.#nums[this.#front]; } /* Вернуть Array */ toArray() { // Преобразовывать только элементы списка в пределах фактической длины const arr = new Array(this.size); for (let i = 0, j = this.#front; i < this.size; i++, j++) { arr[i] = this.#nums[j % this.capacity]; } return arr; } } /* Driver Code */ /* Инициализация очереди */ const capacity = 10; const queue = new ArrayQueue(capacity); /* Добавление элемента в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('Очередь queue =', queue.toArray()); /* Доступ к элементу в начале очереди */ const peek = queue.peek(); console.log('Первый элемент peek = ' + peek); /* Извлечение элемента из очереди */ const pop = queue.pop(); console.log('Извлеченный элемент pop = ' + pop + ', queue после извлечения =', queue.toArray()); /* Получение длины очереди */ const size = queue.size; console.log('Длина очереди size = ' + size); /* Проверка, пуста ли очередь */ const isEmpty = queue.isEmpty(); console.log('Пуста ли очередь = ' + isEmpty); /* Проверка кольцевого массива */ for (let i = 0; i < 10; i++) { queue.push(i); queue.pop(); console.log('После ' + i + '-го раунда операций enqueue и dequeue queue =', queue.toArray()); } ================================================ FILE: ru/codes/javascript/chapter_stack_and_queue/array_stack.js ================================================ /** * File: array_stack.js * Created Time: 2022-12-09 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Стек на основе массива */ class ArrayStack { #stack; constructor() { this.#stack = []; } /* Получение длины стека */ get size() { return this.#stack.length; } /* Проверка, пуст ли стек */ isEmpty() { return this.#stack.length === 0; } /* Поместить в стек */ push(num) { this.#stack.push(num); } /* Извлечь из стека */ pop() { if (this.isEmpty()) throw new Error('стек пуст'); return this.#stack.pop(); } /* Доступ к верхнему элементу стека */ top() { if (this.isEmpty()) throw new Error('стек пуст'); return this.#stack[this.#stack.length - 1]; } /* Вернуть Array */ toArray() { return this.#stack; } } /* Driver Code */ /* Инициализация стека */ const stack = new ArrayStack(); /* Помещение элемента в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('Стек stack = '); console.log(stack.toArray()); /* Доступ к верхнему элементу стека */ const top = stack.top(); console.log('Верхний элемент top = ' + top); /* Извлечение элемента из стека */ const pop = stack.pop(); console.log('Извлеченный элемент pop = ' + pop + ', stack после извлечения = '); console.log(stack.toArray()); /* Получение длины стека */ const size = stack.size; console.log('Длина стека size = ' + size); /* Проверка на пустоту */ const isEmpty = stack.isEmpty(); console.log('Пуст ли стек = ' + isEmpty); ================================================ FILE: ru/codes/javascript/chapter_stack_and_queue/deque.js ================================================ /** * File: deque.js * Created Time: 2023-01-17 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Driver Code */ /* Инициализация двусторонней очереди */ // В JavaScript нет встроенной двусторонней очереди, поэтому Array можно использовать как двустороннюю очередь const deque = []; /* Добавление элемента в очередь */ deque.push(2); deque.push(5); deque.push(4); // Обратите внимание: поскольку используется массив, временная сложность метода unshift() равна O(n) deque.unshift(3); deque.unshift(1); console.log('Двусторонняя очередь deque = ', deque); /* Доступ к элементу */ const peekFirst = deque[0]; console.log('Первый элемент peekFirst = ' + peekFirst); const peekLast = deque[deque.length - 1]; console.log('Последний элемент peekLast = ' + peekLast); /* Извлечение элемента из очереди */ // Обратите внимание: поскольку используется массив, временная сложность метода shift() равна O(n) const popFront = deque.shift(); console.log( 'Извлеченный из головы элемент popFront = ' + popFront + ', deque после извлечения из головы = ' + deque ); const popBack = deque.pop(); console.log( 'Извлеченный из хвоста элемент popBack = ' + popBack + ', deque после извлечения из хвоста = ' + deque ); /* Получение длины двусторонней очереди */ const size = deque.length; console.log('Длина двусторонней очереди size = ' + size); /* Проверка, пуста ли двусторонняя очередь */ const isEmpty = size === 0; console.log('Пуста ли двусторонняя очередь = ' + isEmpty); ================================================ FILE: ru/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js ================================================ /** * File: linkedlist_deque.js * Created Time: 2023-02-04 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Узел двусвязного списка */ class ListNode { prev; // Ссылка на узел-предшественник (указатель) next; // Ссылка на узел-преемник (указатель) val; // Значение узла constructor(val) { this.val = val; this.next = null; this.prev = null; } } /* Двусторонняя очередь на основе двусвязного списка */ class LinkedListDeque { #front; // Головной узел front #rear; // Хвостовой узел rear #queSize; // Длина двусторонней очереди constructor() { this.#front = null; this.#rear = null; this.#queSize = 0; } /* Операция добавления в хвост очереди */ pushLast(val) { const node = new ListNode(val); // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node if (this.#queSize === 0) { this.#front = node; this.#rear = node; } else { // Добавить node в хвост списка this.#rear.next = node; node.prev = this.#rear; this.#rear = node; // Обновить хвостовой узел } this.#queSize++; } /* Операция добавления в голову очереди */ pushFirst(val) { const node = new ListNode(val); // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node if (this.#queSize === 0) { this.#front = node; this.#rear = node; } else { // Добавить node в голову списка this.#front.prev = node; node.next = this.#front; this.#front = node; // Обновить головной узел } this.#queSize++; } /* Операция извлечения из хвоста очереди */ popLast() { if (this.#queSize === 0) { return null; } const value = this.#rear.val; // Сохранить значение хвостового узла // Удалить хвостовой узел let temp = this.#rear.prev; if (temp !== null) { temp.next = null; this.#rear.prev = null; } this.#rear = temp; // Обновить хвостовой узел this.#queSize--; return value; } /* Операция извлечения из головы очереди */ popFirst() { if (this.#queSize === 0) { return null; } const value = this.#front.val; // Сохранить значение хвостового узла // Удалить головной узел let temp = this.#front.next; if (temp !== null) { temp.prev = null; this.#front.next = null; } this.#front = temp; // Обновить головной узел this.#queSize--; return value; } /* Доступ к элементу в конце очереди */ peekLast() { return this.#queSize === 0 ? null : this.#rear.val; } /* Доступ к элементу в начале очереди */ peekFirst() { return this.#queSize === 0 ? null : this.#front.val; } /* Получение длины двусторонней очереди */ size() { return this.#queSize; } /* Проверка, пуста ли двусторонняя очередь */ isEmpty() { return this.#queSize === 0; } /* Вывести двустороннюю очередь */ print() { const arr = []; let temp = this.#front; while (temp !== null) { arr.push(temp.val); temp = temp.next; } console.log('[' + arr.join(', ') + ']'); } } /* Driver Code */ /* Инициализация двусторонней очереди */ const linkedListDeque = new LinkedListDeque(); linkedListDeque.pushLast(3); linkedListDeque.pushLast(2); linkedListDeque.pushLast(5); console.log('Двусторонняя очередь linkedListDeque = '); linkedListDeque.print(); /* Доступ к элементу */ const peekFirst = linkedListDeque.peekFirst(); console.log('Первый элемент peekFirst = ' + peekFirst); const peekLast = linkedListDeque.peekLast(); console.log('Последний элемент peekLast = ' + peekLast); /* Добавление элемента в очередь */ linkedListDeque.pushLast(4); console.log('После добавления элемента 4 в хвост linkedListDeque = '); linkedListDeque.print(); linkedListDeque.pushFirst(1); console.log('После добавления элемента 1 в голову linkedListDeque = '); linkedListDeque.print(); /* Извлечение элемента из очереди */ const popLast = linkedListDeque.popLast(); console.log('Извлеченный из хвоста элемент = ' + popLast + ', linkedListDeque после извлечения из хвоста = '); linkedListDeque.print(); const popFirst = linkedListDeque.popFirst(); console.log('Извлеченный из головы элемент = ' + popFirst + ', linkedListDeque после извлечения из головы = '); linkedListDeque.print(); /* Получение длины двусторонней очереди */ const size = linkedListDeque.size(); console.log('Длина двусторонней очереди size = ' + size); /* Проверка, пуста ли двусторонняя очередь */ const isEmpty = linkedListDeque.isEmpty(); console.log('Пуста ли двусторонняя очередь = ' + isEmpty); ================================================ FILE: ru/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js ================================================ /** * File: linkedlist_queue.js * Created Time: 2022-12-20 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ const { ListNode } = require('../modules/ListNode'); /* Очередь на основе связного списка */ class LinkedListQueue { #front; // Головной узел #front #rear; // Хвостовой узел #rear #queSize = 0; constructor() { this.#front = null; this.#rear = null; } /* Получение длины очереди */ get size() { return this.#queSize; } /* Проверка, пуста ли очередь */ isEmpty() { return this.size === 0; } /* Поместить в очередь */ push(num) { // Добавить num после хвостового узла const node = new ListNode(num); // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел if (!this.#front) { this.#front = node; this.#rear = node; // Если очередь не пуста, добавить этот узел после хвостового узла } else { this.#rear.next = node; this.#rear = node; } this.#queSize++; } /* Извлечь из очереди */ pop() { const num = this.peek(); // Удалить головной узел this.#front = this.#front.next; this.#queSize--; return num; } /* Доступ к элементу в начале очереди */ peek() { if (this.size === 0) throw new Error('очередь пуста'); return this.#front.val; } /* Преобразовать связный список в Array и вернуть */ toArray() { let node = this.#front; const res = new Array(this.size); for (let i = 0; i < res.length; i++) { res[i] = node.val; node = node.next; } return res; } } /* Driver Code */ /* Инициализация очереди */ const queue = new LinkedListQueue(); /* Добавление элемента в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('Очередь queue = ' + queue.toArray()); /* Доступ к элементу в начале очереди */ const peek = queue.peek(); console.log('Первый элемент peek = ' + peek); /* Извлечение элемента из очереди */ const pop = queue.pop(); console.log('Извлеченный элемент pop = ' + pop + ', queue после извлечения = ' + queue.toArray()); /* Получение длины очереди */ const size = queue.size; console.log('Длина очереди size = ' + size); /* Проверка, пуста ли очередь */ const isEmpty = queue.isEmpty(); console.log('Пуста ли очередь = ' + isEmpty); ================================================ FILE: ru/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js ================================================ /** * File: linkedlist_stack.js * Created Time: 2022-12-22 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ const { ListNode } = require('../modules/ListNode'); /* Стек на основе связного списка */ class LinkedListStack { #stackPeek; // Использовать головной узел как вершину стека #stkSize = 0; // Длина стека constructor() { this.#stackPeek = null; } /* Получение длины стека */ get size() { return this.#stkSize; } /* Проверка, пуст ли стек */ isEmpty() { return this.size === 0; } /* Поместить в стек */ push(num) { const node = new ListNode(num); node.next = this.#stackPeek; this.#stackPeek = node; this.#stkSize++; } /* Извлечь из стека */ pop() { const num = this.peek(); this.#stackPeek = this.#stackPeek.next; this.#stkSize--; return num; } /* Доступ к верхнему элементу стека */ peek() { if (!this.#stackPeek) throw new Error('стек пуст'); return this.#stackPeek.val; } /* Преобразовать связный список в Array и вернуть */ toArray() { let node = this.#stackPeek; const res = new Array(this.size); for (let i = res.length - 1; i >= 0; i--) { res[i] = node.val; node = node.next; } return res; } } /* Driver Code */ /* Инициализация стека */ const stack = new LinkedListStack(); /* Помещение элемента в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('Стек stack = ' + stack.toArray()); /* Доступ к верхнему элементу стека */ const peek = stack.peek(); console.log('Верхний элемент peek = ' + peek); /* Извлечение элемента из стека */ const pop = stack.pop(); console.log('Извлеченный элемент pop = ' + pop + ', stack после извлечения = ' + stack.toArray()); /* Получение длины стека */ const size = stack.size; console.log('Длина стека size = ' + size); /* Проверка на пустоту */ const isEmpty = stack.isEmpty(); console.log('Пуст ли стек = ' + isEmpty); ================================================ FILE: ru/codes/javascript/chapter_stack_and_queue/queue.js ================================================ /** * File: queue.js * Created Time: 2022-12-05 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* Инициализация очереди */ // В JavaScript нет встроенной очереди, поэтому Array можно использовать как очередь const queue = []; /* Добавление элемента в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('Очередь queue =', queue); /* Доступ к элементу в начале очереди */ const peek = queue[0]; console.log('Первый элемент peek =', peek); /* Извлечение элемента из очереди */ // В основе лежит массив, поэтому временная сложность метода shift() равна O(n) const pop = queue.shift(); console.log('Извлеченный элемент pop =', pop, ', queue после извлечения = ', queue); /* Получение длины очереди */ const size = queue.length; console.log('Длина очереди size =', size); /* Проверка, пуста ли очередь */ const isEmpty = queue.length === 0; console.log('Пуста ли очередь = ', isEmpty); ================================================ FILE: ru/codes/javascript/chapter_stack_and_queue/stack.js ================================================ /** * File: stack.js * Created Time: 2022-12-04 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* Инициализация стека */ // В JavaScript нет встроенного класса стека, поэтому Array можно использовать как стек const stack = []; /* Помещение элемента в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('Стек stack =', stack); /* Доступ к верхнему элементу стека */ const peek = stack[stack.length - 1]; console.log('Верхний элемент peek =', peek); /* Извлечение элемента из стека */ const pop = stack.pop(); console.log('Извлеченный элемент pop =', pop); console.log('stack после извлечения =', stack); /* Получение длины стека */ const size = stack.length; console.log('Длина стека size =', size); /* Проверка на пустоту */ const isEmpty = stack.length === 0; console.log('Пуст ли стек =', isEmpty); ================================================ FILE: ru/codes/javascript/chapter_tree/array_binary_tree.js ================================================ /** * File: array_binary_tree.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Класс двоичного дерева в массивном представлении */ class ArrayBinaryTree { #tree; /* Конструктор */ constructor(arr) { this.#tree = arr; } /* Вместимость списка */ size() { return this.#tree.length; } /* Получить значение узла с индексом i */ val(i) { // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию if (i < 0 || i >= this.size()) return null; return this.#tree[i]; } /* Получить индекс левого дочернего узла узла с индексом i */ left(i) { return 2 * i + 1; } /* Получить индекс правого дочернего узла узла с индексом i */ right(i) { return 2 * i + 2; } /* Получить индекс родительского узла узла с индексом i */ parent(i) { return Math.floor((i - 1) / 2); // Округление вниз при делении } /* Обход в ширину */ levelOrder() { let res = []; // Непосредственно обходить массив for (let i = 0; i < this.size(); i++) { if (this.val(i) !== null) res.push(this.val(i)); } return res; } /* Обход в глубину */ #dfs(i, order, res) { // Если это пустая позиция, вернуть if (this.val(i) === null) return; // Предварительный обход if (order === 'pre') res.push(this.val(i)); this.#dfs(this.left(i), order, res); // Симметричный обход if (order === 'in') res.push(this.val(i)); this.#dfs(this.right(i), order, res); // Обратный обход if (order === 'post') res.push(this.val(i)); } /* Предварительный обход */ preOrder() { const res = []; this.#dfs(0, 'pre', res); return res; } /* Симметричный обход */ inOrder() { const res = []; this.#dfs(0, 'in', res); return res; } /* Обратный обход */ postOrder() { const res = []; this.#dfs(0, 'post', res); return res; } } /* Driver Code */ // Инициализировать двоичное дерево // Здесь используется функция, напрямую строящая двоичное дерево из массива const arr = Array.of( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ); const root = arrToTree(arr); console.log('\nИнициализация двоичного дерева\n'); console.log('Массивное представление двоичного дерева:'); console.log(arr); console.log('Связное представление двоичного дерева:'); printTree(root); // Класс двоичного дерева в массивном представлении const abt = new ArrayBinaryTree(arr); // Доступ к узлу const i = 1; const l = abt.left(i); const r = abt.right(i); const p = abt.parent(i); console.log('\nТекущий узел: индекс = ' + i + ', значение = ' + abt.val(i)); console.log( 'Индекс левого дочернего узла = ' + l + ', значение = ' + (l === null ? 'null' : abt.val(l)) ); console.log( 'Индекс правого дочернего узла = ' + r + ', значение = ' + (r === null ? 'null' : abt.val(r)) ); console.log( 'Индекс родительского узла = ' + p + ', значение = ' + (p === null ? 'null' : abt.val(p)) ); // Обходить дерево let res = abt.levelOrder(); console.log('\nОбход в ширину: ' + res); res = abt.preOrder(); console.log('Предварительный обход: ' + res); res = abt.inOrder(); console.log('Симметричный обход: ' + res); res = abt.postOrder(); console.log('Обратный обход: ' + res); ================================================ FILE: ru/codes/javascript/chapter_tree/avl_tree.js ================================================ /** * File: avl_tree.js * Created Time: 2023-02-05 * Author: what-is-me (whatisme@outlook.jp) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* AVL-дерево */ class AVLTree { /* Конструктор */ constructor() { this.root = null; // Корневой узел } /* Получить высоту узла */ height(node) { // Высота пустого узла равна -1, высота листового узла равна 0 return node === null ? -1 : node.height; } /* Обновить высоту узла */ #updateHeight(node) { // Высота узла равна высоте более высокого поддерева + 1 node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; } /* Получить коэффициент баланса */ balanceFactor(node) { // Коэффициент баланса пустого узла равен 0 if (node === null) return 0; // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева return this.height(node.left) - this.height(node.right); } /* Операция правого вращения */ #rightRotate(node) { const child = node.left; const grandChild = child.right; // Выполнить правое вращение узла node вокруг child child.right = node; node.left = grandChild; // Обновить высоту узла this.#updateHeight(node); this.#updateHeight(child); // Вернуть корневой узел поддерева после вращения return child; } /* Операция левого вращения */ #leftRotate(node) { const child = node.right; const grandChild = child.left; // Выполнить левое вращение узла node вокруг child child.left = node; node.right = grandChild; // Обновить высоту узла this.#updateHeight(node); this.#updateHeight(child); // Вернуть корневой узел поддерева после вращения return child; } /* Выполнить вращение, чтобы снова сбалансировать поддерево */ #rotate(node) { // Получить коэффициент баланса узла node const balanceFactor = this.balanceFactor(node); // Левосторонне перекошенное дерево if (balanceFactor > 1) { if (this.balanceFactor(node.left) >= 0) { // Правое вращение return this.#rightRotate(node); } else { // Сначала левое вращение, затем правое node.left = this.#leftRotate(node.left); return this.#rightRotate(node); } } // Правосторонне перекошенное дерево if (balanceFactor < -1) { if (this.balanceFactor(node.right) <= 0) { // Левое вращение return this.#leftRotate(node); } else { // Сначала правое вращение, затем левое node.right = this.#rightRotate(node.right); return this.#leftRotate(node); } } // Дерево сбалансировано, вращение не требуется, вернуть сразу return node; } /* Вставка узла */ insert(val) { this.root = this.#insertHelper(this.root, val); } /* Рекурсивная вставка узла (вспомогательный метод) */ #insertHelper(node, val) { if (node === null) return new TreeNode(val); /* 1. Найти позицию вставки и вставить узел */ if (val < node.val) node.left = this.#insertHelper(node.left, val); else if (val > node.val) node.right = this.#insertHelper(node.right, val); else return node; // Повторяющийся узел не вставлять, сразу вернуть this.#updateHeight(node); // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = this.#rotate(node); // Вернуть корневой узел поддерева return node; } /* Удаление узла */ remove(val) { this.root = this.#removeHelper(this.root, val); } /* Рекурсивное удаление узла (вспомогательный метод) */ #removeHelper(node, val) { if (node === null) return null; /* 1. Найти узел и удалить его */ if (val < node.val) node.left = this.#removeHelper(node.left, val); else if (val > node.val) node.right = this.#removeHelper(node.right, val); else { if (node.left === null || node.right === null) { const child = node.left !== null ? node.left : node.right; // Число дочерних узлов = 0, удалить node и сразу вернуть if (child === null) return null; // Число дочерних узлов = 1, удалить node напрямую else node = child; } else { // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел let temp = node.right; while (temp.left !== null) { temp = temp.left; } node.right = this.#removeHelper(node.right, temp.val); node.val = temp.val; } } this.#updateHeight(node); // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = this.#rotate(node); // Вернуть корневой узел поддерева return node; } /* Поиск узла */ search(val) { let cur = this.root; // Искать в цикле и выйти после прохода за листовой узел while (cur !== null) { // Целевой узел находится в правом поддереве cur if (cur.val < val) cur = cur.right; // Целевой узел находится в левом поддереве cur else if (cur.val > val) cur = cur.left; // Найти целевой узел и выйти из цикла else break; } // Вернуть целевой узел return cur; } } function testInsert(tree, val) { tree.insert(val); console.log('\nПосле вставки узла ' + val + ' AVL-дерево имеет вид'); printTree(tree.root); } function testRemove(tree, val) { tree.remove(val); console.log('\nПосле удаления узла ' + val + ' AVL-дерево имеет вид'); printTree(tree.root); } /* Driver Code */ /* Инициализация пустого AVL-дерева */ const avlTree = new AVLTree(); /* Вставка узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); testInsert(avlTree, 4); testInsert(avlTree, 5); testInsert(avlTree, 8); testInsert(avlTree, 7); testInsert(avlTree, 9); testInsert(avlTree, 10); testInsert(avlTree, 6); /* Вставка повторяющегося узла */ testInsert(avlTree, 7); /* Удаление узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла testRemove(avlTree, 8); // Удаление узла степени 0 testRemove(avlTree, 5); // Удаление узла степени 1 testRemove(avlTree, 4); // Удаление узла степени 2 /* Поиск узла */ const node = avlTree.search(7); console.log('\nНайденный объект узла =', node, ', значение узла = ' + node.val); ================================================ FILE: ru/codes/javascript/chapter_tree/binary_search_tree.js ================================================ /** * File: binary_search_tree.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Двоичное дерево поиска */ class BinarySearchTree { /* Конструктор */ constructor() { // Инициализировать пустое дерево this.root = null; } /* Получить корневой узел двоичного дерева */ getRoot() { return this.root; } /* Поиск узла */ search(num) { let cur = this.root; // Искать в цикле и выйти после прохода за листовой узел while (cur !== null) { // Целевой узел находится в правом поддереве cur if (cur.val < num) cur = cur.right; // Целевой узел находится в левом поддереве cur else if (cur.val > num) cur = cur.left; // Найти целевой узел и выйти из цикла else break; } // Вернуть целевой узел return cur; } /* Вставка узла */ insert(num) { // Если дерево пусто, инициализировать корневой узел if (this.root === null) { this.root = new TreeNode(num); return; } let cur = this.root, pre = null; // Искать в цикле и выйти после прохода за листовой узел while (cur !== null) { // Найти повторяющийся узел и сразу вернуть if (cur.val === num) return; pre = cur; // Позиция вставки находится в правом поддереве cur if (cur.val < num) cur = cur.right; // Позиция вставки находится в левом поддереве cur else cur = cur.left; } // Вставка узла const node = new TreeNode(num); if (pre.val < num) pre.right = node; else pre.left = node; } /* Удаление узла */ remove(num) { // Если дерево пусто, сразу вернуть if (this.root === null) return; let cur = this.root, pre = null; // Искать в цикле и выйти после прохода за листовой узел while (cur !== null) { // Найти узел для удаления и выйти из цикла if (cur.val === num) break; pre = cur; // Узел для удаления находится в правом поддереве cur if (cur.val < num) cur = cur.right; // Узел для удаления находится в левом поддереве cur else cur = cur.left; } // Если узел для удаления отсутствует, сразу вернуть if (cur === null) return; // Число дочерних узлов = 0 или 1 if (cur.left === null || cur.right === null) { // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел const child = cur.left !== null ? cur.left : cur.right; // Удалить узел cur if (cur !== this.root) { if (pre.left === cur) pre.left = child; else pre.right = child; } else { // Если удаляемый узел является корнем, заново назначить корневой узел this.root = child; } } // Число дочерних узлов = 2 else { // Получить следующий узел после cur в симметричном обходе let tmp = cur.right; while (tmp.left !== null) { tmp = tmp.left; } // Рекурсивно удалить узел tmp this.remove(tmp.val); // Перезаписать cur значением tmp cur.val = tmp.val; } } } /* Driver Code */ /* Инициализация двоичного дерева поиска */ const bst = new BinarySearchTree(); // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (const num of nums) { bst.insert(num); } console.log('\nИсходное двоичное дерево\n'); printTree(bst.getRoot()); /* Поиск узла */ const node = bst.search(7); console.log('\nНайденный объект узла = ' + node + ', значение узла = ' + node.val); /* Вставка узла */ bst.insert(16); console.log('\nПосле вставки узла 16 двоичное дерево имеет вид\n'); printTree(bst.getRoot()); /* Удаление узла */ bst.remove(1); console.log('\nПосле удаления узла 1 двоичное дерево имеет вид\n'); printTree(bst.getRoot()); bst.remove(2); console.log('\nПосле удаления узла 2 двоичное дерево имеет вид\n'); printTree(bst.getRoot()); bst.remove(4); console.log('\nПосле удаления узла 4 двоичное дерево имеет вид\n'); printTree(bst.getRoot()); ================================================ FILE: ru/codes/javascript/chapter_tree/binary_tree.js ================================================ /** * File: binary_tree.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Инициализация двоичного дерева */ // Инициализация узла let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // Построить связи между узлами (указатели) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; console.log('\nИнициализация двоичного дерева\n'); printTree(n1); /* Вставка и удаление узлов */ const P = new TreeNode(0); // Вставить узел P между n1 -> n2 n1.left = P; P.left = n2; console.log('\nПосле вставки узла P\n'); printTree(n1); // Удалить узел P n1.left = n2; console.log('\nПосле удаления узла P\n'); printTree(n1); ================================================ FILE: ru/codes/javascript/chapter_tree/binary_tree_bfs.js ================================================ /** * File: binary_tree_bfs.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* Обход в ширину */ function levelOrder(root) { // Инициализировать очередь и добавить корневой узел const queue = [root]; // Инициализировать список для хранения последовательности обхода const list = []; while (queue.length) { let node = queue.shift(); // Извлечение из очереди list.push(node.val); // Сохранить значение узла if (node.left) queue.push(node.left); // Поместить левый дочерний узел в очередь if (node.right) queue.push(node.right); // Поместить правый дочерний узел в очередь } return list; } /* Driver Code */ /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\nИнициализация двоичного дерева\n'); printTree(root); /* Обход в ширину */ const list = levelOrder(root); console.log('\nПоследовательность печати узлов при обходе в ширину = ' + list); ================================================ FILE: ru/codes/javascript/chapter_tree/binary_tree_dfs.js ================================================ /** * File: binary_tree_dfs.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); // Инициализировать список для хранения последовательности обхода const list = []; /* Предварительный обход */ function preOrder(root) { if (root === null) return; // Порядок обхода: корень -> левое поддерево -> правое поддерево list.push(root.val); preOrder(root.left); preOrder(root.right); } /* Симметричный обход */ function inOrder(root) { if (root === null) return; // Порядок обхода: левое поддерево -> корень -> правое поддерево inOrder(root.left); list.push(root.val); inOrder(root.right); } /* Обратный обход */ function postOrder(root) { if (root === null) return; // Порядок обхода: левое поддерево -> правое поддерево -> корень postOrder(root.left); postOrder(root.right); list.push(root.val); } /* Driver Code */ /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\nИнициализация двоичного дерева\n'); printTree(root); /* Предварительный обход */ list.length = 0; preOrder(root); console.log('\nПоследовательность печати узлов при предварительном обходе = ' + list); /* Симметричный обход */ list.length = 0; inOrder(root); console.log('\nПоследовательность печати узлов при симметричном обходе = ' + list); /* Обратный обход */ list.length = 0; postOrder(root); console.log('\nПоследовательность печати узлов при обратном обходе = ' + list); ================================================ FILE: ru/codes/javascript/modules/ListNode.js ================================================ /** * File: ListNode.js * Created Time: 2022-12-12 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Узел связного списка */ class ListNode { val; // Значение узла next; // Ссылка (указатель) на следующий узел constructor(val, next) { this.val = val === undefined ? 0 : val; this.next = next === undefined ? null : next; } } /* Десериализовать список в связный список */ function arrToLinkedList(arr) { const dum = new ListNode(0); let head = dum; for (const val of arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } module.exports = { ListNode, arrToLinkedList, }; ================================================ FILE: ru/codes/javascript/modules/PrintUtil.js ================================================ /** * File: PrintUtil.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('./TreeNode'); /* Вывести связный список */ function printLinkedList(head) { let list = []; while (head !== null) { list.push(head.val.toString()); head = head.next; } console.log(list.join(' -> ')); } function Trunk(prev, str) { this.prev = prev; this.str = str; } /** * Вывести двоичное дерево * Этот вывод дерева заимствован из TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ function printTree(root) { printTree(root, null, false); } /* Вывести двоичное дерево */ function printTree(root, prev, isRight) { if (root === null) { return; } let prev_str = ' '; let trunk = new Trunk(prev, prev_str); printTree(root.right, trunk, true); if (!prev) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); console.log(' ' + root.val); if (prev) { prev.str = prev_str; } trunk.str = ' |'; printTree(root.left, trunk, false); } function showTrunks(p) { if (!p) { return; } showTrunks(p.prev); process.stdout.write(p.str); } /* Вывести кучу */ function printHeap(arr) { console.log('Массивное представление кучи:'); console.log(arr); console.log('Древовидное представление кучи:'); printTree(arrToTree(arr)); } module.exports = { printLinkedList, printTree, printHeap, }; ================================================ FILE: ru/codes/javascript/modules/TreeNode.js ================================================ /** * File: TreeNode.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ /* Узел двоичного дерева */ class TreeNode { val; // Значение узла left; // Указатель на левый дочерний узел right; // Указатель на правый дочерний узел height; // Высота узла constructor(val, left, right, height) { this.val = val === undefined ? 0 : val; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; this.height = height === undefined ? 0 : height; } } /* Десериализовать массив в двоичное дерево */ function arrToTree(arr, i = 0) { if (i < 0 || i >= arr.length || arr[i] === null) { return null; } let root = new TreeNode(arr[i]); root.left = arrToTree(arr, 2 * i + 1); root.right = arrToTree(arr, 2 * i + 2); return root; } module.exports = { TreeNode, arrToTree, }; ================================================ FILE: ru/codes/javascript/modules/Vertex.js ================================================ /** * File: Vertex.js * Created Time: 2023-02-15 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Класс вершины */ class Vertex { val; constructor(val) { this.val = val; } /* На вход подается список значений vals, на выходе возвращается список вершин vets */ static valsToVets(vals) { const vets = []; for (let i = 0; i < vals.length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* На вход подается список вершин vets, на выходе возвращается список значений vals */ static vetsToVals(vets) { const vals = []; for (const vet of vets) { vals.push(vet.val); } return vals; } } module.exports = { Vertex, }; ================================================ FILE: ru/codes/javascript/test_all.js ================================================ import { bold, brightRed } from 'jsr:@std/fmt/colors'; import { expandGlob } from 'jsr:@std/fs'; import { relative, resolve } from 'jsr:@std/path'; /** * @typedef {import('jsr:@std/fs').WalkEntry} WalkEntry * @type {WalkEntry[]} */ const entries = []; for await (const entry of expandGlob( resolve(import.meta.dirname, './chapter_*/*.js') )) { entries.push(entry); } /** @type {{ status: Promise; stderr: ReadableStream; }[]} */ const processes = []; for (const file of entries) { const execute = new Deno.Command('node', { args: [relative(import.meta.dirname, file.path)], cwd: import.meta.dirname, stdin: 'piped', stdout: 'piped', stderr: 'piped', }); const process = execute.spawn(); processes.push({ status: process.status, stderr: process.stderr }); } const results = await Promise.all( processes.map(async (item) => { const status = await item.status; return { status, stderr: item.stderr }; }) ); /** @type {ReadableStream[]} */ const errors = []; for (const result of results) { if (!result.status.success) { errors.push(result.stderr); } } console.log(`Tested ${entries.length} files`); console.log(`Found exception in ${errors.length} files`); if (errors.length) { console.log(); for (const error of errors) { const reader = error.getReader(); const { value } = await reader.read(); const decoder = new TextDecoder(); console.log(`${bold(brightRed('error'))}: ${decoder.decode(value)}`); } throw new Error('Test failed'); } ================================================ FILE: ru/codes/kotlin/chapter_array_and_linkedlist/array.kt ================================================ /** * File: array.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_array_and_linkedlist import java.util.concurrent.ThreadLocalRandom /* Случайный доступ к элементу */ fun randomAccess(nums: IntArray): Int { // Случайным образом выбрать число из интервала [0, nums.size) val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size) // Получить и вернуть случайный элемент val randomNum = nums[randomIndex] return randomNum } /* Увеличить длину массива */ fun extend(nums: IntArray, enlarge: Int): IntArray { // Инициализировать массив увеличенной длины val res = IntArray(nums.size + enlarge) // Скопировать все элементы исходного массива в новый массив for (i in nums.indices) { res[i] = nums[i] } // Вернуть новый массив после расширения return res } /* Вставить элемент num по индексу index в массив */ fun insert(nums: IntArray, num: Int, index: Int) { // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад for (i in nums.size - 1 downTo index + 1) { nums[i] = nums[i - 1] } // Присвоить num элементу по индексу index nums[index] = num } /* Удалить элемент по индексу index */ fun remove(nums: IntArray, index: Int) { // Сдвинуть все элементы после индекса index на одну позицию вперед for (i in index.. P -> n1 val p = n0.next val n1 = p?.next n0.next = n1 } /* Доступ к узлу связного списка по индексу index */ fun access(head: ListNode?, index: Int): ListNode? { var h = head for (i in 0..= size) throw IndexOutOfBoundsException("индекс выходит за границы") return arr[index] } /* Обновление элемента */ fun set(index: Int, num: Int) { if (index < 0 || index >= size) throw IndexOutOfBoundsException("индекс выходит за границы") arr[index] = num } /* Добавление элемента в конец */ fun add(num: Int) { // При превышении вместимости по числу элементов запускается расширение if (size == capacity()) extendCapacity() arr[size] = num // Обновить число элементов size++ } /* Вставка элемента в середину */ fun insert(index: Int, num: Int) { if (index < 0 || index >= size) throw IndexOutOfBoundsException("индекс выходит за границы") // При превышении вместимости по числу элементов запускается расширение if (size == capacity()) extendCapacity() // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад for (j in size - 1 downTo index) arr[j + 1] = arr[j] arr[index] = num // Обновить число элементов size++ } /* Удаление элемента */ fun remove(index: Int): Int { if (index < 0 || index >= size) throw IndexOutOfBoundsException("индекс выходит за границы") val num = arr[index] // Сдвинуть все элементы после индекса index на одну позицию вперед for (j in index..>, res: MutableList>?>, cols: BooleanArray, diags1: BooleanArray, diags2: BooleanArray ) { // Когда все строки уже обработаны, записать решение if (row == n) { val copyState = mutableListOf>() for (sRow in state) { copyState.add(sRow.toMutableList()) } res.add(copyState) return } // Обойти все столбцы for (col in 0..>?> { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку val state = mutableListOf>() for (i in 0..() for (j in 0..>?>() backtrack(0, n, state, res, cols, diags1, diags2) return res } /* Driver Code */ fun main() { val n = 4 val res = nQueens(n) println("Размер входной доски = $n") println("Количество способов расстановки ферзей: ${res.size}") for (state in res) { println("--------------------") for (row in state!!) { println(row) } } } ================================================ FILE: ru/codes/kotlin/chapter_backtracking/permutations_i.kt ================================================ /** * File: permutations_i.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.permutations_i /* Алгоритм бэктрекинга: все перестановки I */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // Когда длина состояния равна числу элементов, записать решение if (state.size == choices.size) { res.add(state.toMutableList()) return } // Перебор всех вариантов выбора for (i in choices.indices) { val choice = choices[i] // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true state.add(choice) // Перейти к следующему выбору backtrack(state, choices, selected, res) // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false state.removeAt(state.size - 1) } } } /* Все перестановки I */ fun permutationsI(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(1, 2, 3) val res = permutationsI(nums) println("Входной массив nums = ${nums.contentToString()}") println("Все перестановки res = $res") } ================================================ FILE: ru/codes/kotlin/chapter_backtracking/permutations_ii.kt ================================================ /** * File: permutations_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.permutations_ii /* Алгоритм бэктрекинга: все перестановки II */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // Когда длина состояния равна числу элементов, записать решение if (state.size == choices.size) { res.add(state.toMutableList()) return } // Перебор всех вариантов выбора val duplicated = HashSet() for (i in choices.indices) { val choice = choices[i] // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && !duplicated.contains(choice)) { // Попытка: сделать выбор и обновить состояние duplicated.add(choice) // Записать значения уже выбранных элементов selected[i] = true state.add(choice) // Перейти к следующему выбору backtrack(state, choices, selected, res) // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false state.removeAt(state.size - 1) } } } /* Все перестановки II */ fun permutationsII(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(1, 2, 2) val res = permutationsII(nums) println("Входной массив nums = ${nums.contentToString()}") println("Все перестановки res = $res") } ================================================ FILE: ru/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt ================================================ /** * File: preorder_traversal_i_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_i_compact import utils.TreeNode import utils.printTree var res: MutableList? = null /* Предварительный обход: пример 1 */ fun preOrder(root: TreeNode?) { if (root == null) { return } if (root._val == 7) { // Записать решение res!!.add(root) } preOrder(root.left) preOrder(root.right) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\nИнициализация двоичного дерева") printTree(root) // Предварительный обход res = mutableListOf() preOrder(root) println("\nВсе узлы со значением 7") val vals = mutableListOf() for (node in res!!) { vals.add(node._val) } println(vals) } ================================================ FILE: ru/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt ================================================ /** * File: preorder_traversal_ii_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_ii_compact import utils.TreeNode import utils.printTree var path: MutableList? = null var res: MutableList>? = null /* Предварительный обход: пример 2 */ fun preOrder(root: TreeNode?) { if (root == null) { return } // Попытка path!!.add(root) if (root._val == 7) { // Записать решение res!!.add(path!!.toMutableList()) } preOrder(root.left) preOrder(root.right) // Откат path!!.removeAt(path!!.size - 1) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\nИнициализация двоичного дерева") printTree(root) // Предварительный обход path = mutableListOf() res = mutableListOf() preOrder(root) println("\nВсе пути от корня к узлу 7") for (path in res!!) { val _vals = mutableListOf() for (node in path) { _vals.add(node._val) } println(_vals) } } ================================================ FILE: ru/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt ================================================ /** * File: preorder_traversal_iii_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_iii_compact import utils.TreeNode import utils.printTree var path: MutableList? = null var res: MutableList>? = null /* Предварительный обход: пример 3 */ fun preOrder(root: TreeNode?) { // Отсечение if (root == null || root._val == 3) { return } // Попытка path!!.add(root) if (root._val == 7) { // Записать решение res!!.add(path!!.toMutableList()) } preOrder(root.left) preOrder(root.right) // Откат path!!.removeAt(path!!.size - 1) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\nИнициализация двоичного дерева") printTree(root) // Предварительный обход path = mutableListOf() res = mutableListOf() preOrder(root) println("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3") for (path in res!!) { val _vals = mutableListOf() for (node in path) { _vals.add(node._val) } println(_vals) } } ================================================ FILE: ru/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt ================================================ /** * File: preorder_traversal_iii_template.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_iii_template import utils.TreeNode import utils.printTree /* Проверить, является ли текущее состояние решением */ fun isSolution(state: MutableList): Boolean { return state.isNotEmpty() && state[state.size - 1]?._val == 7 } /* Записать решение */ fun recordSolution(state: MutableList?, res: MutableList?>) { res.add(state!!.toMutableList()) } /* Проверить, допустим ли этот выбор в текущем состоянии */ fun isValid(state: MutableList?, choice: TreeNode?): Boolean { return choice != null && choice._val != 3 } /* Обновить состояние */ fun makeChoice(state: MutableList, choice: TreeNode?) { state.add(choice) } /* Восстановить состояние */ fun undoChoice(state: MutableList, choice: TreeNode?) { state.removeLast() } /* Алгоритм бэктрекинга: пример 3 */ fun backtrack( state: MutableList, choices: MutableList, res: MutableList?> ) { // Проверить, является ли текущее состояние решением if (isSolution(state)) { // Записать решение recordSolution(state, res) } // Перебор всех вариантов выбора for (choice in choices) { // Отсечение: проверить допустимость выбора if (isValid(state, choice)) { // Попытка: сделать выбор и обновить состояние makeChoice(state, choice) // Перейти к следующему выбору backtrack(state, mutableListOf(choice!!.left, choice.right), res) // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state, choice) } } } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\nИнициализация двоичного дерева") printTree(root) // Алгоритм бэктрекинга val res = mutableListOf?>() backtrack(mutableListOf(), mutableListOf(root), res) println("\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3") for (path in res) { val vals = mutableListOf() for (node in path!!) { if (node != null) { vals.add(node._val) } } println(vals) } } ================================================ FILE: ru/codes/kotlin/chapter_backtracking/subset_sum_i.kt ================================================ /** * File: subset_sum_i.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_i /* Алгоритм бэктрекинга: сумма подмножеств I */ fun backtrack( state: MutableList, target: Int, choices: IntArray, start: Int, res: MutableList?> ) { // Если сумма подмножества равна target, записать решение if (target == 0) { res.add(state.toMutableList()) return } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств for (i in start..?> { val state = mutableListOf() // Состояние (подмножество) nums.sort() // Отсортировать nums val start = 0 // Стартовая вершина обхода val res = mutableListOf?>() // Список результатов (список подмножеств) backtrack(state, target, nums, start, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(3, 4, 5) val target = 9 val res = subsetSumI(nums, target) println("Входной массив nums = ${nums.contentToString()}, target = $target") println("Все подмножества с суммой $target: res = $res") } ================================================ FILE: ru/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt ================================================ /** * File: subset_sum_i_native.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_i_naive /* Алгоритм бэктрекинга: сумма подмножеств I */ fun backtrack( state: MutableList, target: Int, total: Int, choices: IntArray, res: MutableList?> ) { // Если сумма подмножества равна target, записать решение if (total == target) { res.add(state.toMutableList()) return } // Перебор всех вариантов выбора for (i in choices.indices) { // Отсечение: если сумма подмножества превышает target, пропустить этот выбор if (total + choices[i] > target) { continue } // Попытка: сделать выбор и обновить элемент и total state.add(choices[i]) // Перейти к следующему выбору backtrack(state, target, total + choices[i], choices, res) // Откат: отменить выбор и восстановить предыдущее состояние state.removeAt(state.size - 1) } } /* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ fun subsetSumINaive(nums: IntArray, target: Int): MutableList?> { val state = mutableListOf() // Состояние (подмножество) val total = 0 // Сумма подмножеств val res = mutableListOf?>() // Список результатов (список подмножеств) backtrack(state, target, total, nums, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(3, 4, 5) val target = 9 val res = subsetSumINaive(nums, target) println("Входной массив nums = ${nums.contentToString()}, target = $target") println("Все подмножества с суммой $target: res = $res") println("Обратите внимание: результат этого метода содержит повторяющиеся множества") } ================================================ FILE: ru/codes/kotlin/chapter_backtracking/subset_sum_ii.kt ================================================ /** * File: subset_sum_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_ii /* Алгоритм бэктрекинга: сумма подмножеств II */ fun backtrack( state: MutableList, target: Int, choices: IntArray, start: Int, res: MutableList?> ) { // Если сумма подмножества равна target, записать решение if (target == 0) { res.add(state.toMutableList()) return } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента for (i in start.. start && choices[i] == choices[i - 1]) { continue } // Попытка: сделать выбор и обновить target и start state.add(choices[i]) // Перейти к следующему выбору backtrack(state, target - choices[i], choices, i + 1, res) // Откат: отменить выбор и восстановить предыдущее состояние state.removeAt(state.size - 1) } } /* Решить задачу суммы подмножеств II */ fun subsetSumII(nums: IntArray, target: Int): MutableList?> { val state = mutableListOf() // Состояние (подмножество) nums.sort() // Отсортировать nums val start = 0 // Стартовая вершина обхода val res = mutableListOf?>() // Список результатов (список подмножеств) backtrack(state, target, nums, start, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(4, 4, 5) val target = 9 val res = subsetSumII(nums, target) println("Входной массив nums = ${nums.contentToString()}, target = $target") println("Все подмножества с суммой $target: res = $res") } ================================================ FILE: ru/codes/kotlin/chapter_computational_complexity/iteration.kt ================================================ /** * File: iteration.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.iteration /* Цикл for */ fun forLoop(n: Int): Int { var res = 0 // Циклическое суммирование 1, 2, ..., n-1, n for (i in 1..n) { res += i } return res } /* Цикл while */ fun whileLoop(n: Int): Int { var res = 0 var i = 1 // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i i++ // Обновить условную переменную } return res } /* Цикл while (двойное обновление) */ fun whileLoopII(n: Int): Int { var res = 0 var i = 1 // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i // Обновить условную переменную i++ i *= 2 } return res } /* Двойной цикл for */ fun nestedForLoop(n: Int): String { val res = StringBuilder() // Цикл по i = 1, 2, ..., n-1, n for (i in 1..n) { // Цикл по j = 1, 2, ..., n-1, n for (j in 1..n) { res.append(" ($i, $j), ") } } return res.toString() } /* Driver Code */ fun main() { val n = 5 var res: Int res = forLoop(n) println("\nРезультат суммирования в цикле for res = $res") res = whileLoop(n) println("\nРезультат суммирования в цикле while res = $res") res = whileLoopII(n) println("\nРезультат суммирования в цикле while (двойное обновление) res = $res") val resStr = nestedForLoop(n) println("\nРезультат обхода в двойном цикле for $resStr") } ================================================ FILE: ru/codes/kotlin/chapter_computational_complexity/recursion.kt ================================================ /** * File: recursion.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.recursion import java.util.* /* Рекурсия */ fun recur(n: Int): Int { // Условие завершения if (n == 1) return 1 // Рекурсивный шаг: рекурсивный вызов val res = recur(n - 1) // Возврат: вернуть результат return n + res } /* Имитация рекурсии итерацией */ fun forLoopRecur(n: Int): Int { // Использовать явный стек для имитации системного стека вызовов val stack = Stack() var res = 0 // Рекурсивный шаг: рекурсивный вызов for (i in n downTo 0) { // Имитировать «рекурсию» с помощью операции помещения в стек stack.push(i) } // Возврат: вернуть результат while (stack.isNotEmpty()) { // Имитировать «возврат» с помощью операции извлечения из стека res += stack.pop() } // res = 1+2+3+...+n return res } /* Хвостовая рекурсия */ tailrec fun tailRecur(n: Int, res: Int): Int { // Добавить ключевое слово tailrec, чтобы включить оптимизацию хвостовой рекурсии // Условие завершения if (n == 0) return res // Хвостовой рекурсивный вызов return tailRecur(n - 1, res + n) } /* Последовательность Фибоначчи: рекурсия */ fun fib(n: Int): Int { // Условие завершения: f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1 // Рекурсивный вызов f(n) = f(n-1) + f(n-2) val res = fib(n - 1) + fib(n - 2) // Вернуть результат f(n) return res } /* Driver Code */ fun main() { val n = 5 var res: Int res = recur(n) println("\nРезультат суммирования в рекурсивной функции res = $res") res = forLoopRecur(n) println("\nРезультат суммирования при имитации рекурсии итерацией res = $res") res = tailRecur(n, 0) println("\nРезультат суммирования в хвостовой рекурсии res = $res") res = fib(n) println("\nЧлен последовательности Фибоначчи с номером $n = $res") } ================================================ FILE: ru/codes/kotlin/chapter_computational_complexity/space_complexity.kt ================================================ /** * File: space_complexity.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.space_complexity import utils.ListNode import utils.TreeNode import utils.printTree /* Функция */ fun function(): Int { // Выполнить некоторые операции return 0 } /* Постоянная сложность */ fun constant(n: Int) { // Константы, переменные и объекты занимают O(1) памяти val a = 0 var b = 0 val nums = Array(10000) { 0 } val node = ListNode(0) // Переменные в цикле занимают O(1) памяти for (i in 0..() for (i in 0..() for (i in 0..?>(n) // Двумерный список занимает O(n^2) памяти val numList = mutableListOf>() for (i in 0..() for (j in 0.. nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp count += 3 // Обмен элементов включает 3 элементарные операции } } } return count } /* Экспоненциальная сложность (итеративная реализация) */ fun exponential(n: Int): Int { var count = 0 var base = 1 // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) for (i in 0.. 1) { n1 /= 2 count++ } return count } /* Логарифмическая сложность (рекурсивная реализация) */ fun logRecur(n: Int): Int { if (n <= 1) return 0 return logRecur(n / 2) + 1 } /* Линейно-логарифмическая сложность */ fun linearLogRecur(n: Int): Int { if (n <= 1) return 1 var count = linearLogRecur(n / 2) + linearLogRecur(n / 2) for (i in 0.. { val nums = IntArray(n) // Создать массив nums = { 1, 2, 3, ..., n } for (i in 0..(n) for (i in 0..): Int { for (i in nums.indices) { // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) if (nums[i] == 1) return i } return -1 } /* Driver Code */ fun main() { for (i in 0..9) { val n = 100 val nums = randomNumbers(n) val index = findOne(nums) println("\nМассив [1, 2, ..., n] после перемешивания = ${nums.contentToString()}") println("Индекс числа 1 = $index") } } ================================================ FILE: ru/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt ================================================ /** * File: binary_search_recur.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.binary_search_recur /* Бинарный поиск: задача f(i, j) */ fun dfs( nums: IntArray, target: Int, i: Int, j: Int ): Int { // Если интервал пуст, целевой элемент отсутствует, вернуть -1 if (i > j) { return -1 } // Вычислить индекс середины m val m = (i + j) / 2 return if (nums[m] < target) { // Рекурсивная подзадача f(m+1, j) dfs(nums, target, m + 1, j) } else if (nums[m] > target) { // Рекурсивная подзадача f(i, m-1) dfs(nums, target, i, m - 1) } else { // Целевой элемент найден, вернуть его индекс m } } /* Бинарный поиск */ fun binarySearch(nums: IntArray, target: Int): Int { val n = nums.size // Решить задачу f(0, n-1) return dfs(nums, target, 0, n - 1) } /* Driver Code */ fun main() { val target = 6 val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) // Бинарный поиск (двусторонне замкнутый интервал) val index = binarySearch(nums, target) println("Индекс целевого элемента 6 = $index") } ================================================ FILE: ru/codes/kotlin/chapter_divide_and_conquer/build_tree.kt ================================================ /** * File: build_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.build_tree import utils.TreeNode import utils.printTree /* Построить двоичное дерево: разделяй и властвуй */ fun dfs( preorder: IntArray, inorderMap: Map, i: Int, l: Int, r: Int ): TreeNode? { // Завершить при пустом диапазоне поддерева if (r - l < 0) return null // Инициализировать корневой узел val root = TreeNode(preorder[i]) // Найти m, чтобы разделить левое и правое поддеревья val m = inorderMap[preorder[i]]!! // Подзадача: построить левое поддерево root.left = dfs(preorder, inorderMap, i + 1, l, m - 1) // Подзадача: построить правое поддерево root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r) // Вернуть корневой узел return root } /* Построить двоичное дерево */ fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? { // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам val inorderMap = HashMap() for (i in inorder.indices) { inorderMap[inorder[i]] = i } val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1) return root } /* Driver Code */ fun main() { val preorder = intArrayOf(3, 9, 2, 1, 7) val inorder = intArrayOf(9, 3, 1, 2, 7) println("Предварительный обход = ${preorder.contentToString()}") println("Симметричный обход = ${inorder.contentToString()}") val root = buildTree(preorder, inorder) println("Построенное двоичное дерево:") printTree(root) } ================================================ FILE: ru/codes/kotlin/chapter_divide_and_conquer/hanota.kt ================================================ /** * File: hanota.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.hanota /* Переместить один диск */ fun move(src: MutableList, tar: MutableList) { // Снять диск с вершины src val pan = src.removeAt(src.size - 1) // Положить диск на вершину tar tar.add(pan) } /* Решить задачу Ханойской башни f(i) */ fun dfs(i: Int, src: MutableList, buf: MutableList, tar: MutableList) { // Если в src остался только один диск, сразу переместить его в tar if (i == 1) { move(src, tar) return } // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar dfs(i - 1, src, tar, buf) // Подзадача f(1): переместить оставшийся один диск из src в tar move(src, tar) // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src dfs(i - 1, buf, src, tar) } /* Решить задачу Ханойской башни */ fun solveHanota(A: MutableList, B: MutableList, C: MutableList) { val n = A.size // Переместить верхние n дисков из A в C с помощью B dfs(n, A, B, C) } /* Driver Code */ fun main() { // Хвост списка соответствует вершине столбца val A = mutableListOf(5, 4, 3, 2, 1) val B = mutableListOf() val C = mutableListOf() println("Исходное состояние:") println("A = $A") println("B = $B") println("C = $C") solveHanota(A, B, C) println("После завершения перемещения дисков:") println("A = $A") println("B = $B") println("C = $C") } ================================================ FILE: ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt ================================================ /** * File: climbing_stairs_backtrack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* Бэктрекинг */ fun backtrack( choices: MutableList, state: Int, n: Int, res: MutableList ) { // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 if (state == n) res[0] = res[0] + 1 // Перебор всех вариантов выбора for (choice in choices) { // Отсечение: нельзя выходить за n-ю ступень if (state + choice > n) continue // Попытка: сделать выбор и обновить состояние backtrack(choices, state + choice, n, res) // Откат } } /* Подъем по лестнице: бэктрекинг */ fun climbingStairsBacktrack(n: Int): Int { val choices = mutableListOf(1, 2) // Можно подняться на 1 или 2 ступени val state = 0 // Начать подъем с 0-й ступени val res = mutableListOf() res.add(0) // Использовать res[0] для хранения числа решений backtrack(choices, state, n, res) return res[0] } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsBacktrack(n) println("Количество способов подняться по лестнице из $n ступеней = $res") } ================================================ FILE: ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt ================================================ /** * File: climbing_stairs_constraint_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* Подъем по лестнице с ограничениями: динамическое программирование */ fun climbingStairsConstraintDP(n: Int): Int { if (n == 1 || n == 2) { return 1 } // Инициализация таблицы dp для хранения решений подзадач val dp = Array(n + 1) { IntArray(3) } // Начальное состояние: заранее задать решения наименьших подзадач dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // Переход состояний: постепенное решение больших подзадач через меньшие for (i in 3..n) { dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] } return dp[n][1] + dp[n][2] } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsConstraintDP(n) println("Количество способов подняться по лестнице из $n ступеней = $res") } ================================================ FILE: ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt ================================================ /** * File: climbing_stairs_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* Поиск */ fun dfs(i: Int): Int { // dp[1] и dp[2] уже известны, вернуть их if (i == 1 || i == 2) return i // dp[i] = dp[i-1] + dp[i-2] val count = dfs(i - 1) + dfs(i - 2) return count } /* Подъем по лестнице: поиск */ fun climbingStairsDFS(n: Int): Int { return dfs(n) } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsDFS(n) println("Количество способов подняться по лестнице из $n ступеней = $res") } ================================================ FILE: ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt ================================================ /** * File: climbing_stairs_dfs_mem.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* Поиск с мемоизацией */ fun dfs(i: Int, mem: IntArray): Int { // dp[1] и dp[2] уже известны, вернуть их if (i == 1 || i == 2) return i // Если запись dp[i] существует, сразу вернуть ее if (mem[i] != -1) return mem[i] // dp[i] = dp[i-1] + dp[i-2] val count = dfs(i - 1, mem) + dfs(i - 2, mem) // Сохранить dp[i] mem[i] = count return count } /* Подъем по лестнице: поиск с мемоизацией */ fun climbingStairsDFSMem(n: Int): Int { // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи val mem = IntArray(n + 1) mem.fill(-1) return dfs(n, mem) } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsDFSMem(n) println("Количество способов подняться по лестнице из $n ступеней = $res") } ================================================ FILE: ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt ================================================ /** * File: climbing_stairs_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* Подъем по лестнице: динамическое программирование */ fun climbingStairsDP(n: Int): Int { if (n == 1 || n == 2) return n // Инициализация таблицы dp для хранения решений подзадач val dp = IntArray(n + 1) // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = 1 dp[2] = 2 // Переход состояний: постепенное решение больших подзадач через меньшие for (i in 3..n) { dp[i] = dp[i - 1] + dp[i - 2] } return dp[n] } /* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ fun climbingStairsDPComp(n: Int): Int { if (n == 1 || n == 2) return n var a = 1 var b = 2 for (i in 3..n) { val temp = b b += a a = temp } return b } /* Driver Code */ fun main() { val n = 9 var res = climbingStairsDP(n) println("Количество способов подняться по лестнице из $n ступеней = $res") res = climbingStairsDPComp(n) println("Количество способов подняться по лестнице из $n ступеней = $res") } ================================================ FILE: ru/codes/kotlin/chapter_dynamic_programming/coin_change.kt ================================================ /** * File: coin_change.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* Размен монет: динамическое программирование */ fun coinChangeDP(coins: IntArray, amt: Int): Int { val n = coins.size val MAX = amt + 1 // Инициализация таблицы dp val dp = Array(n + 1) { IntArray(amt + 1) } // Переход состояний: первая строка и первый столбец for (a in 1..amt) { dp[0][a] = MAX } // Переход состояний: остальные строки и столбцы for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a] } else { // Меньшее из двух решений: не брать или взять монету i dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) } } } return if (dp[n][amt] != MAX) dp[n][amt] else -1 } /* Размен монет: динамическое программирование с оптимизацией памяти */ fun coinChangeDPComp(coins: IntArray, amt: Int): Int { val n = coins.size val MAX = amt + 1 // Инициализация таблицы dp val dp = IntArray(amt + 1) dp.fill(MAX) dp[0] = 0 // Переход состояний for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a] } else { // Меньшее из двух решений: не брать или взять монету i dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) } } } return if (dp[amt] != MAX) dp[amt] else -1 } /* Driver Code */ fun main() { val coins = intArrayOf(1, 2, 5) val amt = 4 // Динамическое программирование var res = coinChangeDP(coins, amt) println("Минимальное число монет для набора целевой суммы = $res") // Динамическое программирование с оптимизацией памяти res = coinChangeDPComp(coins, amt) println("Минимальное число монет для набора целевой суммы = $res") } ================================================ FILE: ru/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt ================================================ /** * File: coin_change_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* Размен монет II: динамическое программирование */ fun coinChangeIIDP(coins: IntArray, amt: Int): Int { val n = coins.size // Инициализация таблицы dp val dp = Array(n + 1) { IntArray(amt + 1) } // Инициализация первого столбца for (i in 0..n) { dp[i][0] = 1 } // Переход состояний for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a] } else { // Сумма двух решений: не брать или взять монету i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] } } } return dp[n][amt] } /* Размен монет II: динамическое программирование с оптимизацией памяти */ fun coinChangeIIDPComp(coins: IntArray, amt: Int): Int { val n = coins.size // Инициализация таблицы dp val dp = IntArray(amt + 1) dp[0] = 1 // Переход состояний for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a] } else { // Сумма двух решений: не брать или взять монету i dp[a] = dp[a] + dp[a - coins[i - 1]] } } } return dp[amt] } /* Driver Code */ fun main() { val coins = intArrayOf(1, 2, 5) val amt = 5 // Динамическое программирование var res = coinChangeIIDP(coins, amt) println("Количество комбинаций монет для набора целевой суммы = $res") // Динамическое программирование с оптимизацией памяти res = coinChangeIIDPComp(coins, amt) println("Количество комбинаций монет для набора целевой суммы = $res") } ================================================ FILE: ru/codes/kotlin/chapter_dynamic_programming/edit_distance.kt ================================================ /** * File: edit_distance.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* Редакционное расстояние: полный перебор */ fun editDistanceDFS( s: String, t: String, i: Int, j: Int ): Int { // Если s и t пусты, вернуть 0 if (i == 0 && j == 0) return 0 // Если s пусто, вернуть длину t if (i == 0) return j // Если t пусто, вернуть длину s if (j == 0) return i // Если два символа равны, сразу пропустить их if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1) // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 val insert = editDistanceDFS(s, t, i, j - 1) val delete = editDistanceDFS(s, t, i - 1, j) val replace = editDistanceDFS(s, t, i - 1, j - 1) // Вернуть минимальное число шагов редактирования return min(min(insert, delete), replace) + 1 } /* Редакционное расстояние: поиск с мемоизацией */ fun editDistanceDFSMem( s: String, t: String, mem: Array, i: Int, j: Int ): Int { // Если s и t пусты, вернуть 0 if (i == 0 && j == 0) return 0 // Если s пусто, вернуть длину t if (i == 0) return j // Если t пусто, вернуть длину s if (j == 0) return i // Если запись уже есть, сразу вернуть ее if (mem[i][j] != -1) return mem[i][j] // Если два символа равны, сразу пропустить их if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1) // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 val insert = editDistanceDFSMem(s, t, mem, i, j - 1) val delete = editDistanceDFSMem(s, t, mem, i - 1, j) val replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1) // Сохранить и вернуть минимальное число шагов редактирования mem[i][j] = min(min(insert, delete), replace) + 1 return mem[i][j] } /* Редакционное расстояние: динамическое программирование */ fun editDistanceDP(s: String, t: String): Int { val n = s.length val m = t.length val dp = Array(n + 1) { IntArray(m + 1) } // Переход состояний: первая строка и первый столбец for (i in 1..n) { dp[i][0] = i } for (j in 1..m) { dp[0][j] = j } // Переход состояний: остальные строки и столбцы for (i in 1..n) { for (j in 1..m) { if (s[i - 1] == t[j - 1]) { // Если два символа равны, сразу пропустить их dp[i][j] = dp[i - 1][j - 1] } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 } } } return dp[n][m] } /* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ fun editDistanceDPComp(s: String, t: String): Int { val n = s.length val m = t.length val dp = IntArray(m + 1) // Переход состояний: первая строка for (j in 1..m) { dp[j] = j } // Переход состояний: остальные строки for (i in 1..n) { // Переход состояний: первый столбец var leftup = dp[0] // Временно сохранить dp[i-1, j-1] dp[0] = i // Переход состояний: остальные столбцы for (j in 1..m) { val temp = dp[j] if (s[i - 1] == t[j - 1]) { // Если два символа равны, сразу пропустить их dp[j] = leftup } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 } leftup = temp // Обновить до значения dp[i-1, j-1] для следующей итерации } } return dp[m] } /* Driver Code */ fun main() { val s = "bag" val t = "pack" val n = s.length val m = t.length // Полный перебор var res = editDistanceDFS(s, t, n, m) println("Чтобы преобразовать $s в $t, нужно минимум $res шагов") // Поиск с мемоизацией val mem = Array(n + 1) { IntArray(m + 1) } for (row in mem) row.fill(-1) res = editDistanceDFSMem(s, t, mem, n, m) println("Чтобы преобразовать $s в $t, нужно минимум $res шагов") // Динамическое программирование res = editDistanceDP(s, t) println("Чтобы преобразовать $s в $t, нужно минимум $res шагов") // Динамическое программирование с оптимизацией памяти res = editDistanceDPComp(s, t) println("Чтобы преобразовать $s в $t, нужно минимум $res шагов") } ================================================ FILE: ru/codes/kotlin/chapter_dynamic_programming/knapsack.kt ================================================ /** * File: knapsack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.max /* Рюкзак 0-1: полный перебор */ fun knapsackDFS( wgt: IntArray, _val: IntArray, i: Int, c: Int ): Int { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i == 0 || c == 0) { return 0 } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (wgt[i - 1] > c) { return knapsackDFS(wgt, _val, i - 1, c) } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут val no = knapsackDFS(wgt, _val, i - 1, c) val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1] // Вернуть вариант с большей стоимостью из двух возможных return max(no, yes) } /* Рюкзак 0-1: поиск с мемоизацией */ fun knapsackDFSMem( wgt: IntArray, _val: IntArray, mem: Array, i: Int, c: Int ): Int { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i == 0 || c == 0) { return 0 } // Если запись уже есть, вернуть сразу if (mem[i][c] != -1) { return mem[i][c] } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, _val, mem, i - 1, c) } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут val no = knapsackDFSMem(wgt, _val, mem, i - 1, c) val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1] // Сохранить и вернуть вариант с большей стоимостью из двух решений mem[i][c] = max(no, yes) return mem[i][c] } /* Рюкзак 0-1: динамическое программирование */ fun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int { val n = wgt.size // Инициализация таблицы dp val dp = Array(n + 1) { IntArray(cap + 1) } // Переход состояний for (i in 1..n) { for (c in 1..cap) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c] } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1]) } } } return dp[n][cap] } /* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ fun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int { val n = wgt.size // Инициализация таблицы dp val dp = IntArray(cap + 1) // Переход состояний for (i in 1..n) { // Обход в обратном порядке for (c in cap downTo 1) { if (wgt[i - 1] <= c) { // Большее из двух решений: не брать или взять предмет i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) } } } return dp[cap] } /* Driver Code */ fun main() { val wgt = intArrayOf(10, 20, 30, 40, 50) val _val = intArrayOf(50, 120, 150, 210, 240) val cap = 50 val n = wgt.size // Полный перебор var res = knapsackDFS(wgt, _val, n, cap) println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") // Поиск с мемоизацией val mem = Array(n + 1) { IntArray(cap + 1) } for (row in mem) { row.fill(-1) } res = knapsackDFSMem(wgt, _val, mem, n, cap) println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") // Динамическое программирование res = knapsackDP(wgt, _val, cap) println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") // Динамическое программирование с оптимизацией памяти res = knapsackDPComp(wgt, _val, cap) println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") } ================================================ FILE: ru/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt ================================================ /** * File: min_cost_climbing_stairs_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* Минимальная стоимость подъема по лестнице: динамическое программирование */ fun minCostClimbingStairsDP(cost: IntArray): Int { val n = cost.size - 1 if (n == 1 || n == 2) return cost[n] // Инициализация таблицы dp для хранения решений подзадач val dp = IntArray(n + 1) // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = cost[1] dp[2] = cost[2] // Переход состояний: постепенное решение больших подзадач через меньшие for (i in 3..n) { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] } return dp[n] } /* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ fun minCostClimbingStairsDPComp(cost: IntArray): Int { val n = cost.size - 1 if (n == 1 || n == 2) return cost[n] var a = cost[1] var b = cost[2] for (i in 3..n) { val tmp = b b = min(a, tmp) + cost[i] a = tmp } return b } /* Driver Code */ fun main() { val cost = intArrayOf(0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1) println("Список стоимостей ступеней = ${cost.contentToString()}") var res = minCostClimbingStairsDP(cost) println("Минимальная стоимость подъема по лестнице = $res") res = minCostClimbingStairsDPComp(cost) println("Минимальная стоимость подъема по лестнице = $res") } ================================================ FILE: ru/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt ================================================ /** * File: min_path_sum.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* Минимальная сумма пути: полный перебор */ fun minPathSumDFS(grid: Array, i: Int, j: Int): Int { // Если это верхняя левая ячейка, завершить поиск if (i == 0 && j == 0) { return grid[0][0] } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 || j < 0) { return Int.MAX_VALUE } // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) val up = minPathSumDFS(grid, i - 1, j) val left = minPathSumDFS(grid, i, j - 1) // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) return min(left, up) + grid[i][j] } /* Минимальная сумма пути: поиск с мемоизацией */ fun minPathSumDFSMem( grid: Array, mem: Array, i: Int, j: Int ): Int { // Если это верхняя левая ячейка, завершить поиск if (i == 0 && j == 0) { return grid[0][0] } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 || j < 0) { return Int.MAX_VALUE } // Если запись уже есть, вернуть сразу if (mem[i][j] != -1) { return mem[i][j] } // Минимальная стоимость пути для левой и верхней ячеек val up = minPathSumDFSMem(grid, mem, i - 1, j) val left = minPathSumDFSMem(grid, mem, i, j - 1) // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) mem[i][j] = min(left, up) + grid[i][j] return mem[i][j] } /* Минимальная сумма пути: динамическое программирование */ fun minPathSumDP(grid: Array): Int { val n = grid.size val m = grid[0].size // Инициализация таблицы dp val dp = Array(n) { IntArray(m) } dp[0][0] = grid[0][0] // Переход состояний: первая строка for (j in 1..): Int { val n = grid.size val m = grid[0].size // Инициализация таблицы dp val dp = IntArray(m) // Переход состояний: первая строка dp[0] = grid[0][0] for (j in 1.. c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c] } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1]) } } } return dp[n][cap] } /* Полный рюкзак: динамическое программирование с оптимизацией памяти */ fun unboundedKnapsackDPComp( wgt: IntArray, _val: IntArray, cap: Int ): Int { val n = wgt.size // Инициализация таблицы dp val dp = IntArray(cap + 1) // Переход состояний for (i in 1..n) { for (c in 1..cap) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[c] = dp[c] } else { // Большее из двух решений: не брать или взять предмет i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) } } } return dp[cap] } /* Driver Code */ fun main() { val wgt = intArrayOf(1, 2, 3) val _val = intArrayOf(5, 11, 15) val cap = 4 // Динамическое программирование var res = unboundedKnapsackDP(wgt, _val, cap) println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") // Динамическое программирование с оптимизацией памяти res = unboundedKnapsackDPComp(wgt, _val, cap) println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") } ================================================ FILE: ru/codes/kotlin/chapter_graph/graph_adjacency_list.kt ================================================ /** * File: graph_adjacency_list.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex /* Класс неориентированного графа на основе списка смежности */ class GraphAdjList(edges: Array>) { // Список смежности, где key — вершина, а value — все смежные ей вершины val adjList = HashMap>() /* Конструктор */ init { // Добавить все вершины и ребра for (edge in edges) { addVertex(edge[0]!!) addVertex(edge[1]!!) addEdge(edge[0]!!, edge[1]!!) } } /* Получить число вершин */ fun size(): Int { return adjList.size } /* Добавление ребра */ fun addEdge(vet1: Vertex, vet2: Vertex) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) throw IllegalArgumentException() // Добавить ребро vet1 - vet2 adjList[vet1]?.add(vet2) adjList[vet2]?.add(vet1) } /* Удаление ребра */ fun removeEdge(vet1: Vertex, vet2: Vertex) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) throw IllegalArgumentException() // Удалить ребро vet1 - vet2 adjList[vet1]?.remove(vet2) adjList[vet2]?.remove(vet1) } /* Добавление вершины */ fun addVertex(vet: Vertex) { if (adjList.containsKey(vet)) return // Добавить новый список в список смежности adjList[vet] = mutableListOf() } /* Удаление вершины */ fun removeVertex(vet: Vertex) { if (!adjList.containsKey(vet)) throw IllegalArgumentException() // Удалить из списка смежности список, соответствующий вершине vet adjList.remove(vet) // Обойти списки других вершин и удалить все ребра, содержащие vet for (list in adjList.values) { list.remove(vet) } } /* Вывести список смежности */ fun print() { println("Список смежности =") for (pair in adjList.entries) { val tmp = mutableListOf() for (vertex in pair.value) { tmp.add(vertex._val) } println("${pair.key._val}: $tmp,") } } } /* Driver Code */ fun main() { /* Инициализация неориентированного графа */ val v = Vertex.valsToVets(intArrayOf(1, 3, 2, 5, 4)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[2], v[3]), arrayOf(v[2], v[4]), arrayOf(v[3], v[4]) ) val graph = GraphAdjList(edges) println("\nГраф после инициализации") graph.print() /* Добавление ребра */ // Вершины 1 и 2 соответствуют v[0] и v[2] graph.addEdge(v[0]!!, v[2]!!) println("\nГраф после добавления ребра 1-2") graph.print() /* Удаление ребра */ // Вершины 1 и 3 соответствуют v[0] и v[1] graph.removeEdge(v[0]!!, v[1]!!) println("\nГраф после удаления ребра 1-3") graph.print() /* Добавление вершины */ val v5 = Vertex(6) graph.addVertex(v5) println("\nГраф после добавления вершины 6") graph.print() /* Удаление вершины */ // Вершина 3 соответствует v[1] graph.removeVertex(v[1]!!) println("\nГраф после удаления вершины 3") graph.print() } ================================================ FILE: ru/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt ================================================ /** * File: graph_adjacency_matrix.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.printMatrix /* Класс неориентированного графа на основе матрицы смежности */ class GraphAdjMat(vertices: IntArray, edges: Array) { val vertices = mutableListOf() // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» val adjMat = mutableListOf>() // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» /* Конструктор */ init { // Добавление вершины for (vertex in vertices) { addVertex(vertex) } // Добавить ребра // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices for (edge in edges) { addEdge(edge[0], edge[1]) } } /* Получить число вершин */ fun size(): Int { return vertices.size } /* Добавление вершины */ fun addVertex(_val: Int) { val n = size() // Добавить значение новой вершины в список вершин vertices.add(_val) // Добавить строку в матрицу смежности val newRow = mutableListOf() for (j in 0..= size()) throw IndexOutOfBoundsException() // Удалить вершину с индексом index из списка вершин vertices.removeAt(index) // Удалить строку с индексом index из матрицы смежности adjMat.removeAt(index) // Удалить столбец с индексом index из матрицы смежности for (row in adjMat) { row.removeAt(index) } } /* Добавление ребра */ // Параметры i и j соответствуют индексам элементов vertices fun addEdge(i: Int, j: Int) { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw IndexOutOfBoundsException() // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) adjMat[i][j] = 1 adjMat[j][i] = 1 } /* Удаление ребра */ // Параметры i и j соответствуют индексам элементов vertices fun removeEdge(i: Int, j: Int) { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw IndexOutOfBoundsException() adjMat[i][j] = 0 adjMat[j][i] = 0 } /* Вывести матрицу смежности */ fun print() { print("Список вершин = ") println(vertices) println("Матрица смежности =") printMatrix(adjMat) } } /* Driver Code */ fun main() { /* Инициализация неориентированного графа */ // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices val vertices = intArrayOf(1, 3, 2, 5, 4) val edges = arrayOf( intArrayOf(0, 1), intArrayOf(0, 3), intArrayOf(1, 2), intArrayOf(2, 3), intArrayOf(2, 4), intArrayOf(3, 4) ) val graph = GraphAdjMat(vertices, edges) println("\nГраф после инициализации") graph.print() /* Добавление ребра */ // Индексы вершин 1 и 2 равны 0 и 2 соответственно graph.addEdge(0, 2) println("\nГраф после добавления ребра 1-2") graph.print() /* Удаление ребра */ // Индексы вершин 1 и 3 равны 0 и 1 соответственно graph.removeEdge(0, 1) println("\nГраф после удаления ребра 1-3") graph.print() /* Добавление вершины */ graph.addVertex(6) println("\nГраф после добавления вершины 6") graph.print() /* Удаление вершины */ // Индекс вершины 3 равен 1 graph.removeVertex(1) println("\nГраф после удаления вершины 3") graph.print() } ================================================ FILE: ru/codes/kotlin/chapter_graph/graph_bfs.kt ================================================ /** * File: graph_bfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex import java.util.* /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList { // Последовательность обхода вершин val res = mutableListOf() // Хеш-множество для хранения уже посещенных вершин val visited = HashSet() visited.add(startVet) // Очередь используется для реализации BFS val que = LinkedList() que.offer(startVet) // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (!que.isEmpty()) { val vet = que.poll() // Извлечь головную вершину из очереди res.add(vet) // Отметить посещенную вершину // Обойти все смежные вершины данной вершины for (adjVet in graph.adjList[vet]!!) { if (visited.contains(adjVet)) continue // Пропустить уже посещенную вершину que.offer(adjVet) // Помещать в очередь только непосещенные вершины visited.add(adjVet) // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res } /* Driver Code */ fun main() { /* Инициализация неориентированного графа */ val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[1], v[4]), arrayOf(v[2], v[5]), arrayOf(v[3], v[4]), arrayOf(v[3], v[6]), arrayOf(v[4], v[5]), arrayOf(v[4], v[7]), arrayOf(v[5], v[8]), arrayOf(v[6], v[7]), arrayOf(v[7], v[8]) ) val graph = GraphAdjList(edges) println("\nГраф после инициализации") graph.print() /* Обход в ширину */ val res = graphBFS(graph, v[0]!!) println("\nПоследовательность вершин при обходе в ширину (BFS)") println(Vertex.vetsToVals(res)) } ================================================ FILE: ru/codes/kotlin/chapter_graph/graph_dfs.kt ================================================ /** * File: graph_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex /* Вспомогательная функция обхода в глубину */ fun dfs( graph: GraphAdjList, visited: MutableSet, res: MutableList, vet: Vertex? ) { res.add(vet) // Отметить посещенную вершину visited.add(vet) // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины for (adjVet in graph.adjList[vet]!!) { if (visited.contains(adjVet)) continue // Пропустить уже посещенную вершину // Рекурсивно обходить смежные вершины dfs(graph, visited, res, adjVet) } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины fun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList { // Последовательность обхода вершин val res = mutableListOf() // Хеш-множество для хранения уже посещенных вершин val visited = HashSet() dfs(graph, visited, res, startVet) return res } /* Driver Code */ fun main() { /* Инициализация неориентированного графа */ val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[2], v[5]), arrayOf(v[4], v[5]), arrayOf(v[5], v[6]) ) val graph = GraphAdjList(edges) println("\nГраф после инициализации") graph.print() /* Обход в глубину */ val res = graphDFS(graph, v[0]) println("\nПоследовательность вершин при обходе в глубину (DFS)") println(Vertex.vetsToVals(res)) } ================================================ FILE: ru/codes/kotlin/chapter_greedy/coin_change_greedy.kt ================================================ /** * File: coin_change_greedy.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy /* Размен монет: жадный алгоритм */ fun coinChangeGreedy(coins: IntArray, amt: Int): Int { // Предположить, что список coins упорядочен var am = amt var i = coins.size - 1 var count = 0 // Циклически выполнять жадный выбор, пока не останется суммы while (am > 0) { // Найти монету, которая меньше остатка суммы и наиболее к нему близка while (i > 0 && coins[i] > am) { i-- } // Выбрать coins[i] am -= coins[i] count++ } // Если допустимое решение не найдено, вернуть -1 return if (am == 0) count else -1 } /* Driver Code */ fun main() { // Жадный подход: гарантирует нахождение глобально оптимального решения var coins = intArrayOf(1, 5, 10, 20, 50, 100) var amt = 186 var res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("Минимальное число монет для набора суммы $amt = $res") // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = intArrayOf(1, 20, 50) amt = 60 res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("Минимальное число монет для набора суммы $amt = $res") println("На самом деле минимум равен 3: 20 + 20 + 20") // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = intArrayOf(1, 49, 50) amt = 98 res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("Минимальное число монет для набора суммы $amt = $res") println("На самом деле минимум равен 2: 49 + 49") } ================================================ FILE: ru/codes/kotlin/chapter_greedy/fractional_knapsack.kt ================================================ /** * File: fractional_knapsack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy /* Предмет */ class Item( val w: Int, // Предмет val v: Int // Стоимость предмета ) /* Дробный рюкзак: жадный алгоритм */ fun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double { // Создать список предметов с двумя свойствами: вес и стоимость var cap = c val items = arrayOfNulls(wgt.size) for (i in wgt.indices) { items[i] = Item(wgt[i], _val[i]) } // Отсортировать по удельной стоимости item.v / item.w в порядке убывания items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) } // Циклический жадный выбор var res = 0.0 for (item in items) { if (item!!.w <= cap) { // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком res += item.v cap -= item.w } else { // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета res += item.v.toDouble() / item.w * cap // Свободной вместимости больше не осталось, поэтому выйти из цикла break } } return res } /* Driver Code */ fun main() { val wgt = intArrayOf(10, 20, 30, 40, 50) val _val = intArrayOf(50, 120, 150, 210, 240) val cap = 50 // Жадный алгоритм val res = fractionalKnapsack(wgt, _val, cap) println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") } ================================================ FILE: ru/codes/kotlin/chapter_greedy/max_capacity.kt ================================================ /** * File: max_capacity.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy import kotlin.math.max import kotlin.math.min /* Максимальная вместимость: жадный алгоритм */ fun maxCapacity(ht: IntArray): Int { // Инициализировать i и j так, чтобы они располагались по двум концам массива var i = 0 var j = ht.size - 1 // Начальная максимальная вместимость равна 0 var res = 0 // Выполнять жадный выбор в цикле, пока две доски не встретятся while (i < j) { // Обновить максимальную вместимость val cap = min(ht[i], ht[j]) * (j - i) res = max(res, cap) // Сдвигать внутрь более короткую сторону if (ht[i] < ht[j]) { i++ } else { j-- } } return res } /* Driver Code */ fun main() { val ht = intArrayOf(3, 8, 5, 2, 7, 7, 3, 4) // Жадный алгоритм val res = maxCapacity(ht) println("Максимальная вместимость = $res") } ================================================ FILE: ru/codes/kotlin/chapter_greedy/max_product_cutting.kt ================================================ /** * File: max_product_cutting.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy import kotlin.math.pow /* Максимальное произведение разрезания: жадный алгоритм */ fun maxProductCutting(n: Int): Int { // Когда n <= 3, обязательно нужно выделить одну 1 if (n <= 3) { return 1 * (n - 1) } // Жадно выделить множители 3, где a — число троек, а b — остаток val a = n / 3 val b = n % 3 if (b == 1) { // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 return 3.0.pow((a - 1)).toInt() * 2 * 2 } if (b == 2) { // Если остаток равен 2, ничего не делать return 3.0.pow(a).toInt() * 2 * 2 } // Если остаток равен 0, ничего не делать return 3.0.pow(a).toInt() } /* Driver Code */ fun main() { val n = 58 // Жадный алгоритм val res = maxProductCutting(n) println("Максимальное произведение после разрезания = $res") } ================================================ FILE: ru/codes/kotlin/chapter_hashing/array_hash_map.kt ================================================ /** * File: array_hash_map.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* Пара ключ-значение */ class Pair( var key: Int, var _val: String ) /* Хеш-таблица на основе массива */ class ArrayHashMap { // Инициализировать массив, содержащий 100 корзин private val buckets = arrayOfNulls(100) /* Хеш-функция */ fun hashFunc(key: Int): Int { val index = key % 100 return index } /* Операция поиска */ fun get(key: Int): String? { val index = hashFunc(key) val pair = buckets[index] ?: return null return pair._val } /* Операция добавления */ fun put(key: Int, _val: String) { val pair = Pair(key, _val) val index = hashFunc(key) buckets[index] = pair } /* Операция удаления */ fun remove(key: Int) { val index = hashFunc(key) // Присвоить null, что означает удаление buckets[index] = null } /* Получить все пары ключ-значение */ fun pairSet(): MutableList { val pairSet = mutableListOf() for (pair in buckets) { if (pair != null) pairSet.add(pair) } return pairSet } /* Получить все ключи */ fun keySet(): MutableList { val keySet = mutableListOf() for (pair in buckets) { if (pair != null) keySet.add(pair.key) } return keySet } /* Получить все значения */ fun valueSet(): MutableList { val valueSet = mutableListOf() for (pair in buckets) { if (pair != null) valueSet.add(pair._val) } return valueSet } /* Вывести хеш-таблицу */ fun print() { for (kv in pairSet()) { val key = kv.key val _val = kv._val println("$key -> $_val") } } } /* Driver Code */ fun main() { /* Инициализация хеш-таблицы */ val map = ArrayHashMap() /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(12836, "Сяо Ха") map.put(15937, "Сяо Ло") map.put(16750, "Сяо Суань") map.put(13276, "Сяо Фа") map.put(10583, "Сяо Я") println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") map.print() /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value val name = map.get(15937) println("\nДля номера 15937 найдено имя $name") /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(10583) println("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") map.print() /* Обход хеш-таблицы */ println("\nОтдельный обход пар ключ-значение") for (kv in map.pairSet()) { println("${kv.key} -> ${kv._val}") } println("\nОтдельный обход ключей") for (key in map.keySet()) { println(key) } println("\nОтдельный обход значений") for (_val in map.valueSet()) { println(_val) } } ================================================ FILE: ru/codes/kotlin/chapter_hashing/built_in_hash.kt ================================================ /** * File: built_in_hash.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing import utils.ListNode /* Driver Code */ fun main() { val num = 3 val hashNum = num.hashCode() println("Хеш-значение целого числа $num = $hashNum") val bol = true val hashBol = bol.hashCode() println("Хеш-значение булева значения $bol = $hashBol") val dec = 3.14159 val hashDec = dec.hashCode() println("Хеш-значение десятичного числа $dec = $hashDec") val str = "Hello Algo" val hashStr = str.hashCode() println("Хеш-значение строки $str = $hashStr") val arr = arrayOf(12836, "Сяо Ха") val hashTup = arr.contentHashCode() println("Хеш-значение массива ${arr.contentToString()} = $hashTup") val obj = ListNode(0) val hashObj = obj.hashCode() println("Хеш-значение объекта узла $obj = $hashObj") } ================================================ FILE: ru/codes/kotlin/chapter_hashing/hash_map.kt ================================================ /** * File: hash_map.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing import utils.printHashMap /* Driver Code */ fun main() { /* Инициализация хеш-таблицы */ val map = HashMap() /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map[12836] = "Сяо Ха" map[15937] = "Сяо Ло" map[16750] = "Сяо Суань" map[13276] = "Сяо Фа" map[10583] = "Сяо Я" println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") printHashMap(map) /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value val name = map[15937] println("\nДля номера 15937 найдено имя $name") /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(10583) println("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") printHashMap(map) /* Обход хеш-таблицы */ println("\nОтдельный обход пар ключ-значение") for ((key, value) in map) { println("$key -> $value") } println("\nОтдельный обход ключей") for (key in map.keys) { println(key) } println("\nОтдельный обход значений") for (_val in map.values) { println(_val) } } ================================================ FILE: ru/codes/kotlin/chapter_hashing/hash_map_chaining.kt ================================================ /** * File: hash_map_chaining.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* Хеш-таблица с цепочками */ class HashMapChaining { var size: Int // Число пар ключ-значение var capacity: Int // Вместимость хеш-таблицы val loadThres: Double // Порог коэффициента загрузки для запуска расширения val extendRatio: Int // Коэффициент расширения var buckets: MutableList> // Массив корзин /* Конструктор */ init { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = mutableListOf() for (i in 0.. loadThres) { extend() } val index = hashFunc(key) val bucket = buckets[index] // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть for (pair in bucket) { if (pair.key == key) { pair._val = _val return } } // Если такого key нет, добавить пару ключ-значение в конец val pair = Pair(key, _val) bucket.add(pair) size++ } /* Операция удаления */ fun remove(key: Int) { val index = hashFunc(key) val bucket = buckets[index] // Обойти корзину и удалить из нее пару ключ-значение for (pair in bucket) { if (pair.key == key) { bucket.remove(pair) size-- break } } } /* Расширить хеш-таблицу */ fun extend() { // Временно сохранить исходную хеш-таблицу val bucketsTmp = buckets // Инициализация новой хеш-таблицы после расширения capacity *= extendRatio // mutablelist не имеет фиксированного размера buckets = mutableListOf() for (i in 0..() for (pair in bucket) { val k = pair.key val v = pair._val res.add("$k -> $v") } println(res) } } } /* Driver Code */ fun main() { /* Инициализация хеш-таблицы */ val map = HashMapChaining() /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(12836, "Сяо Ха") map.put(15937, "Сяо Ло") map.put(16750, "Сяо Суань") map.put(13276, "Сяо Фа") map.put(10583, "Сяо Я") println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") map.print() /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value val name = map.get(13276) println("\nДля номера 13276 найдено имя $name") /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(12836) println("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение") map.print() } ================================================ FILE: ru/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt ================================================ /** * File: hash_map_open_addressing.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* Хеш-таблица с открытой адресацией */ class HashMapOpenAddressing { private var size: Int // Число пар ключ-значение private var capacity: Int // Вместимость хеш-таблицы private val loadThres: Double // Порог коэффициента загрузки для запуска расширения private val extendRatio: Int // Коэффициент расширения private var buckets: Array // Массив корзин private val TOMBSTONE: Pair // Удалить метку /* Конструктор */ init { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = arrayOfNulls(capacity) TOMBSTONE = Pair(-1, "-1") } /* Хеш-функция */ fun hashFunc(key: Int): Int { return key % capacity } /* Коэффициент загрузки */ fun loadFactor(): Double { return (size / capacity).toDouble() } /* Найти индекс корзины, соответствующий key */ fun findBucket(key: Int): Int { var index = hashFunc(key) var firstTombstone = -1 // Выполнять линейное пробирование и завершить при встрече с пустой корзиной while (buckets[index] != null) { // Если встретился key, вернуть соответствующий индекс корзины if (buckets[index]?.key == key) { // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index] buckets[index] = TOMBSTONE return firstTombstone // Вернуть индекс корзины после перемещения } return index // Вернуть индекс корзины } // Записать первую встретившуюся метку удаления if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index } // Вычислить индекс корзины; при выходе за конец вернуться к началу index = (index + 1) % capacity } // Если key не существует, вернуть индекс точки добавления return if (firstTombstone == -1) index else firstTombstone } /* Операция поиска */ fun get(key: Int): String? { // Найти индекс корзины, соответствующий key val index = findBucket(key) // Если пара ключ-значение найдена, вернуть соответствующее val if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index]?._val } // Если пары ключ-значение не существует, вернуть null return null } /* Операция добавления */ fun put(key: Int, _val: String) { // Когда коэффициент загрузки превышает порог, выполнить расширение if (loadFactor() > loadThres) { extend() } // Найти индекс корзины, соответствующий key val index = findBucket(key) // Если пара ключ-значение найдена, перезаписать val и вернуть if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index]!!._val = _val return } // Если пары ключ-значение нет, добавить ее buckets[index] = Pair(key, _val) size++ } /* Операция удаления */ fun remove(key: Int) { // Найти индекс корзины, соответствующий key val index = findBucket(key) // Если пара ключ-значение найдена, заменить ее меткой удаления if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE size-- } } /* Расширить хеш-таблицу */ fun extend() { // Временно сохранить исходную хеш-таблицу val bucketsTmp = buckets // Инициализация новой хеш-таблицы после расширения capacity *= extendRatio buckets = arrayOfNulls(capacity) size = 0 // Перенести пары ключ-значение из исходной хеш-таблицы в новую for (pair in bucketsTmp) { if (pair != null && pair != TOMBSTONE) { put(pair.key, pair._val) } } } /* Вывести хеш-таблицу */ fun print() { for (pair in buckets) { if (pair == null) { println("null") } else if (pair == TOMBSTONE) { println("TOMESTOME") } else { println("${pair.key} -> ${pair._val}") } } } } /* Driver Code */ fun main() { // Инициализация хеш-таблицы val hashmap = HashMapOpenAddressing() // Операция добавления // Добавить пару (key, val) в хеш-таблицу hashmap.put(12836, "Сяо Ха") hashmap.put(15937, "Сяо Ло") hashmap.put(16750, "Сяо Суань") hashmap.put(13276, "Сяо Фа") hashmap.put(10583, "Сяо Я") println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") hashmap.print() // Операция поиска // Передать ключ key в хеш-таблицу и получить значение val val name = hashmap.get(13276) println("\nДля номера 13276 найдено имя $name") // Операция удаления // Удалить пару (key, val) из хеш-таблицы hashmap.remove(16750) println("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение") hashmap.print() } ================================================ FILE: ru/codes/kotlin/chapter_hashing/simple_hash.kt ================================================ /** * File: simple_hash.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* Аддитивное хеширование */ fun addHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = (hash + c.code) % MODULUS } return hash.toInt() } /* Мультипликативное хеширование */ fun mulHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = (31 * hash + c.code) % MODULUS } return hash.toInt() } /* XOR-хеширование */ fun xorHash(key: String): Int { var hash = 0 val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = hash xor c.code } return hash and MODULUS } /* Хеширование с циклическим сдвигом */ fun rotHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS } return hash.toInt() } /* Driver Code */ fun main() { val key = "Hello Algo" var hash = addHash(key) println("Хеш-сумма сложением = $hash") hash = mulHash(key) println("Хеш-сумма умножением = $hash") hash = xorHash(key) println("Хеш-сумма XOR = $hash") hash = rotHash(key) println("Хеш-сумма с циклическим сдвигом = $hash") } ================================================ FILE: ru/codes/kotlin/chapter_heap/heap.kt ================================================ /** * File: heap.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* fun testPush(heap: Queue, _val: Int) { heap.offer(_val) // Добавление элемента в кучу print("\nПосле добавления элемента $_val в кучу\n") printHeap(heap) } fun testPop(heap: Queue) { val _val = heap.poll() // Извлечение элемента с вершины кучи print("\nПосле извлечения элемента вершины кучи $_val\n") printHeap(heap) } /* Driver Code */ fun main() { /* Инициализация кучи */ // Инициализация минимальной кучи var minHeap = PriorityQueue() // Инициализация максимальной кучи (достаточно изменить Comparator с помощью lambda-выражения) val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } println("\nНиже приведен тестовый пример для max-heap") /* Добавление элемента в кучу */ testPush(maxHeap, 1) testPush(maxHeap, 3) testPush(maxHeap, 2) testPush(maxHeap, 5) testPush(maxHeap, 4) /* Получение элемента с вершины кучи */ val peek = maxHeap.peek() print("\nЭлемент на вершине кучи = $peek\n") /* Извлечение элемента с вершины кучи */ testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) /* Получение размера кучи */ val size = maxHeap.size print("\nКоличество элементов в куче = $size\n") /* Проверка, пуста ли куча */ val isEmpty = maxHeap.isEmpty() print("\nПуста ли куча: $isEmpty\n") /* Построить кучу по входному списку */ // Временная сложность равна O(n), а не O(nlogn) minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) println("\nПосле построения min-heap из входного списка") printHeap(minHeap) } ================================================ FILE: ru/codes/kotlin/chapter_heap/my_heap.kt ================================================ /** * File: my_heap.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* /* Максимальная куча */ class MaxHeap(nums: MutableList?) { // Использовать список вместо массива, чтобы не учитывать проблему расширения private val maxHeap = mutableListOf() /* Конструктор, строящий кучу по входному списку */ init { // Добавить элементы списка в кучу без изменений maxHeap.addAll(nums!!) // Выполнить heapify для всех узлов, кроме листовых for (i in parent(size() - 1) downTo 0) { siftDown(i) } } /* Получить индекс левого дочернего узла */ private fun left(i: Int): Int { return 2 * i + 1 } /* Получить индекс правого дочернего узла */ private fun right(i: Int): Int { return 2 * i + 2 } /* Получить индекс родительского узла */ private fun parent(i: Int): Int { return (i - 1) / 2 // Округление вниз при делении } /* Поменять элементы местами */ private fun swap(i: Int, j: Int) { val temp = maxHeap[i] maxHeap[i] = maxHeap[j] maxHeap[j] = temp } /* Получение размера кучи */ fun size(): Int { return maxHeap.size } /* Проверка, пуста ли куча */ fun isEmpty(): Boolean { /* Проверка, пуста ли куча */ return size() == 0 } /* Доступ к элементу на вершине кучи */ fun peek(): Int { return maxHeap[0] } /* Добавление элемента в кучу */ fun push(_val: Int) { // Добавление узла maxHeap.add(_val) // Просеивание снизу вверх siftUp(size() - 1) } /* Начиная с узла i, выполнить просеивание снизу вверх */ private fun siftUp(it: Int) { // Параметры функций в Kotlin неизменяемы, поэтому создается временная переменная var i = it while (true) { // Получение родительского узла для узла i val p = parent(i) // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» if (p < 0 || maxHeap[i] <= maxHeap[p]) break // Поменять два узла местами swap(i, p) // Циклическое просеивание вверх i = p } } /* Извлечение элемента из кучи */ fun pop(): Int { // Обработка пустого случая if (isEmpty()) throw IndexOutOfBoundsException() // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) swap(0, size() - 1) // Удаление узла val _val = maxHeap.removeAt(size() - 1) // Просеивание сверху вниз siftDown(0) // Вернуть элемент с вершины кучи return _val } /* Начиная с узла i, выполнить просеивание сверху вниз */ private fun siftDown(it: Int) { // Параметры функций в Kotlin неизменяемы, поэтому создается временная переменная var i = it while (true) { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma val l = left(i) val r = right(i) var ma = i if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if (ma == i) break // Поменять два узла местами swap(i, ma) // Циклическое просеивание вниз i = ma } } /* Вывести кучу (двоичное дерево) */ fun print() { val queue = PriorityQueue { a: Int, b: Int -> b - a } queue.addAll(maxHeap) printHeap(queue) } } /* Driver Code */ fun main() { /* Инициализация максимальной кучи */ val maxHeap = MaxHeap(mutableListOf(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)) println("\nПосле построения кучи из входного списка") maxHeap.print() /* Получение элемента с вершины кучи */ var peek = maxHeap.peek() print("\nЭлемент на вершине кучи = $peek\n") /* Добавление элемента в кучу */ val _val = 7 maxHeap.push(_val) print("\nПосле добавления элемента $_val в кучу\n") maxHeap.print() /* Извлечение элемента с вершины кучи */ peek = maxHeap.pop() print("\nПосле извлечения элемента вершины кучи $peek\n") maxHeap.print() /* Получение размера кучи */ val size = maxHeap.size() print("\nКоличество элементов в куче = $size\n") /* Проверка, пуста ли куча */ val isEmpty = maxHeap.isEmpty() print("\nПуста ли куча: $isEmpty\n") } ================================================ FILE: ru/codes/kotlin/chapter_heap/top_k.kt ================================================ /** * File: top_k.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* /* Найти k наибольших элементов массива с помощью кучи */ fun topKHeap(nums: IntArray, k: Int): Queue { // Инициализация минимальной кучи val heap = PriorityQueue() // Поместить первые k элементов массива в кучу for (i in 0.. heap.peek()) { heap.poll() heap.offer(nums[i]) } } return heap } /* Driver Code */ fun main() { val nums = intArrayOf(1, 7, 6, 3, 2) val k = 3 val res = topKHeap(nums, k) println("Наибольшие $k элементов") printHeap(res) } ================================================ FILE: ru/codes/kotlin/chapter_searching/binary_search.kt ================================================ /** * File: binary_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* Бинарный поиск (двусторонне замкнутый интервал) */ fun binarySearch(nums: IntArray, target: Int): Int { // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно var i = 0 var j = nums.size - 1 // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) while (i <= j) { val m = i + (j - i) / 2 // Вычислить индекс середины m if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j] i = m + 1 else if (nums[m] > target) // Это означает, что target находится в интервале [i, m-1] j = m - 1 else // Целевой элемент найден, вернуть его индекс return m } // Целевой элемент не найден, вернуть -1 return -1 } /* Бинарный поиск (лево замкнутый, право открытый интервал) */ fun binarySearchLCRO(nums: IntArray, target: Int): Int { // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно var i = 0 var j = nums.size // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) while (i < j) { val m = i + (j - i) / 2 // Вычислить индекс середины m if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j) i = m + 1 else if (nums[m] > target) // Это означает, что target находится в интервале [i, m) j = m else // Целевой элемент найден, вернуть его индекс return m } // Целевой элемент не найден, вернуть -1 return -1 } /* Driver Code */ fun main() { val target = 6 val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) /* Бинарный поиск (двусторонне замкнутый интервал) */ var index = binarySearch(nums, target) println("Индекс целевого элемента 6 = $index") /* Бинарный поиск (лево замкнутый, право открытый интервал) */ index = binarySearchLCRO(nums, target) println("Индекс целевого элемента 6 = $index") } ================================================ FILE: ru/codes/kotlin/chapter_searching/binary_search_edge.kt ================================================ /** * File: binary_search_edge.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* Бинарный поиск самого левого target */ fun binarySearchLeftEdge(nums: IntArray, target: Int): Int { // Эквивалентно поиску точки вставки target val i = binarySearchInsertion(nums, target) // target не найден, вернуть -1 if (i == nums.size || nums[i] != target) { return -1 } // Найти target и вернуть индекс i return i } /* Бинарный поиск самого правого target */ fun binarySearchRightEdge(nums: IntArray, target: Int): Int { // Преобразовать задачу в поиск самого левого target + 1 val i = binarySearchInsertion(nums, target + 1) // j указывает на самый правый target, а i — на первый элемент больше target val j = i - 1 // target не найден, вернуть -1 if (j == -1 || nums[j] != target) { return -1 } // Найти target и вернуть индекс j return j } /* Driver Code */ fun main() { // Массив с повторяющимися элементами val nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) println("\nМассив nums = ${nums.contentToString()}") // Бинарный поиск левой и правой границы for (target in intArrayOf(6, 7)) { var index = binarySearchLeftEdge(nums, target) println("Индекс самого левого элемента $target равен $index") index = binarySearchRightEdge(nums, target) println("Индекс самого правого элемента $target равен $index") } } ================================================ FILE: ru/codes/kotlin/chapter_searching/binary_search_insertion.kt ================================================ /** * File: binary_search_insertion.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* Бинарный поиск точки вставки (без повторяющихся элементов) */ fun binarySearchInsertionSimple(nums: IntArray, target: Int): Int { var i = 0 var j = nums.size - 1 // Инициализировать двусторонне замкнутый интервал [0, n-1] while (i <= j) { val m = i + (j - i) / 2 // Вычислить индекс середины m if (nums[m] < target) { i = m + 1 // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1 // target находится в интервале [i, m-1] } else { return m // Найти target и вернуть точку вставки m } } // target не найден, вернуть точку вставки i return i } /* Бинарный поиск точки вставки (с повторяющимися элементами) */ fun binarySearchInsertion(nums: IntArray, target: Int): Int { var i = 0 var j = nums.size - 1 // Инициализировать двусторонне замкнутый интервал [0, n-1] while (i <= j) { val m = i + (j - i) / 2 // Вычислить индекс середины m if (nums[m] < target) { i = m + 1 // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1 // target находится в интервале [i, m-1] } else { j = m - 1 // Первый элемент меньше target находится в интервале [i, m-1] } } // Вернуть точку вставки i return i } /* Driver Code */ fun main() { // Массив без повторяющихся элементов var nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) println("\nМассив nums = ${nums.contentToString()}") // Бинарный поиск точки вставки for (target in intArrayOf(6, 9)) { val index = binarySearchInsertionSimple(nums, target) println("Индекс позиции вставки элемента $target равен $index") } // Массив с повторяющимися элементами nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) println("\nМассив nums = ${nums.contentToString()}") // Бинарный поиск точки вставки for (target in intArrayOf(2, 6, 20)) { val index = binarySearchInsertion(nums, target) println("Индекс позиции вставки элемента $target равен $index") } } ================================================ FILE: ru/codes/kotlin/chapter_searching/hashing_search.kt ================================================ /** * File: hashing_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching import utils.ListNode /* Хеш-поиск (массив) */ fun hashingSearchArray(map: Map, target: Int): Int { // key хеш-таблицы: целевой элемент, _val: индекс // Если такого key нет в хеш-таблице, вернуть -1 return map.getOrDefault(target, -1) } /* Хеш-поиск (связный список) */ fun hashingSearchLinkedList(map: Map, target: Int): ListNode? { // key хеш-таблицы: значение целевого узла, _val: объект узла // Если такого key нет в хеш-таблице, вернуть null return map.getOrDefault(target, null) } /* Driver Code */ fun main() { val target = 3 /* Хеш-поиск (массив) */ val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) // Инициализация хеш-таблицы val map = HashMap() for (i in nums.indices) { map[nums[i]] = i // key: элемент, _val: индекс } val index = hashingSearchArray(map, target) println("Индекс целевого элемента 3 = $index") /* Хеш-поиск (связный список) */ var head = ListNode.arrToLinkedList(nums) // Инициализация хеш-таблицы val map1 = HashMap() while (head != null) { map1[head._val] = head // key: значение узла, _val: узел head = head.next } val node = hashingSearchLinkedList(map1, target) println("Объект узла со значением 3 = $node") } ================================================ FILE: ru/codes/kotlin/chapter_searching/linear_search.kt ================================================ /** * File: linear_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching import utils.ListNode /* Линейный поиск (массив) */ fun linearSearchArray(nums: IntArray, target: Int): Int { // Обход массива for (i in nums.indices) { // Целевой элемент найден, вернуть его индекс if (nums[i] == target) return i } // Целевой элемент не найден, вернуть -1 return -1 } /* Линейный поиск (связный список) */ fun linearSearchLinkedList(h: ListNode?, target: Int): ListNode? { // Обойти связный список var head = h while (head != null) { // Найти целевой узел и вернуть его if (head._val == target) return head head = head.next } // Целевой узел не найден, вернуть null return null } /* Driver Code */ fun main() { val target = 3 /* Выполнить линейный поиск в массиве */ val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) val index = linearSearchArray(nums, target) println("Индекс целевого элемента 3 = $index") /* Выполнить линейный поиск в связном списке */ val head = ListNode.arrToLinkedList(nums) val node = linearSearchLinkedList(head, target) println("Объект узла со значением 3 = $node") } ================================================ FILE: ru/codes/kotlin/chapter_searching/two_sum.kt ================================================ /** * File: two_sum.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* Метод 1: полный перебор */ fun twoSumBruteForce(nums: IntArray, target: Int): IntArray { val size = nums.size // Два вложенных цикла, временная сложность O(n^2) for (i in 0..() // Один цикл, временная сложность O(n) for (i in 0.. nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp } } } } /* Пузырьковая сортировка (оптимизация флагом) */ fun bubbleSortWithFlag(nums: IntArray) { // Внешний цикл: неотсортированный диапазон [0, i] for (i in nums.size - 1 downTo 1) { var flag = false // Инициализировать флаг // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (j in 0.. nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp flag = true // Записать обмен элементов } } if (!flag) break // На этой итерации «всплытия» не было ни одного обмена, сразу выйти } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) bubbleSort(nums) println("После пузырьковой сортировки nums = ${nums.contentToString()}") val nums1 = intArrayOf(4, 1, 3, 1, 5, 2) bubbleSortWithFlag(nums1) println("После пузырьковой сортировки nums1 = ${nums1.contentToString()}") } ================================================ FILE: ru/codes/kotlin/chapter_sorting/bucket_sort.kt ================================================ /** * File: bucket_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* Сортировка корзинами */ fun bucketSort(nums: FloatArray) { // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину val k = nums.size / 2 val buckets = mutableListOf>() for (i in 0.. nums[ma]) ma = l if (r < n && nums[r] > nums[ma]) ma = r // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if (ma == i) break // Поменять два узла местами val temp = nums[i] nums[i] = nums[ma] nums[ma] = temp // Циклическое просеивание вниз i = ma } } /* Сортировка кучей */ fun heapSort(nums: IntArray) { // Построение кучи: выполнить heapify для всех узлов, кроме листовых for (i in nums.size / 2 - 1 downTo 0) { siftDown(nums, nums.size, i) } // Извлекать максимальный элемент из кучи в течение n-1 итераций for (i in nums.size - 1 downTo 1) { // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) val temp = nums[0] nums[0] = nums[i] nums[i] = temp // Начиная с корневого узла, выполнить просеивание сверху вниз siftDown(nums, i, 0) } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) heapSort(nums) println("После сортировки кучей nums = ${nums.contentToString()}") } ================================================ FILE: ru/codes/kotlin/chapter_sorting/insertion_sort.kt ================================================ /** * File: insertion_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* Сортировка вставками */ fun insertionSort(nums: IntArray) { // Внешний цикл: отсортированные элементы равны 1, 2, ..., n for (i in nums.indices) { val base = nums[i] var j = i - 1 // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j] // Сдвинуть nums[j] на одну позицию вправо j-- } nums[j + 1] = base // Поместить base в правильную позицию } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) insertionSort(nums) println("После сортировки вставками nums = ${nums.contentToString()}") } ================================================ FILE: ru/codes/kotlin/chapter_sorting/merge_sort.kt ================================================ /** * File: merge_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* Объединить левый и правый подмассивы */ fun merge(nums: IntArray, left: Int, mid: Int, right: Int) { // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] // Создать временный массив tmp для хранения результата слияния val tmp = IntArray(right - left + 1) // Инициализировать начальные индексы левого и правого подмассивов var i = left var j = mid + 1 var k = 0 // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++] else tmp[k++] = nums[j++] } // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив while (i <= mid) { tmp[k++] = nums[i++] } while (j <= right) { tmp[k++] = nums[j++] } // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums for (l in tmp.indices) { nums[left + l] = tmp[l] } } /* Сортировка слиянием */ fun mergeSort(nums: IntArray, left: Int, right: Int) { // Условие завершения if (left >= right) return // Завершить рекурсию, когда длина подмассива равна 1 // Этап разбиения val mid = left + (right - left) / 2 // Вычислить середину mergeSort(nums, left, mid) // Рекурсивно обработать левый подмассив mergeSort(nums, mid + 1, right) // Рекурсивно обработать правый подмассив // Этап слияния merge(nums, left, mid, right) } /* Driver Code */ fun main() { /* Сортировка слиянием */ val nums = intArrayOf(7, 3, 2, 6, 0, 1, 5, 4) mergeSort(nums, 0, nums.size - 1) println("После сортировки слиянием nums = ${nums.contentToString()}") } ================================================ FILE: ru/codes/kotlin/chapter_sorting/quick_sort.kt ================================================ /** * File: quick_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* Обмен элементов */ fun swap(nums: IntArray, i: Int, j: Int) { val temp = nums[i] nums[i] = nums[j] nums[j] = temp } /* Разбиение с опорными указателями */ fun partition(nums: IntArray, left: Int, right: Int): Int { // Взять nums[left] в качестве опорного элемента var i = left var j = right while (i < j) { while (i < j && nums[j] >= nums[left]) j-- // Идти справа налево в поисках первого элемента меньше опорного while (i < j && nums[i] <= nums[left]) i++ // Идти слева направо в поисках первого элемента больше опорного swap(nums, i, j) // Поменять эти два элемента местами } swap(nums, i, left) // Переместить опорный элемент на границу двух подмассивов return i // Вернуть индекс опорного элемента } /* Быстрая сортировка */ fun quickSort(nums: IntArray, left: Int, right: Int) { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) return // Разбиение с опорными указателями val pivot = partition(nums, left, right) // Рекурсивно обработать левый и правый подмассивы quickSort(nums, left, pivot - 1) quickSort(nums, pivot + 1, right) } /* Выбрать медиану из трех кандидатов */ fun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int { val l = nums[left] val m = nums[mid] val r = nums[right] if ((m in l..r) || (m in r..l)) return mid // m находится между l и r if ((l in m..r) || (l in r..m)) return left // l находится между m и r return right } /* Разбиение с опорными указателями (медиана трех) */ fun partitionMedian(nums: IntArray, left: Int, right: Int): Int { // Выбрать медиану из трех кандидатов val med = medianThree(nums, left, (left + right) / 2, right) // Переместить медиану в крайний левый элемент массива swap(nums, left, med) // Взять nums[left] в качестве опорного элемента var i = left var j = right while (i < j) { while (i < j && nums[j] >= nums[left]) j-- // Идти справа налево в поисках первого элемента меньше опорного while (i < j && nums[i] <= nums[left]) i++ // Идти слева направо в поисках первого элемента больше опорного swap(nums, i, j) // Поменять эти два элемента местами } swap(nums, i, left) // Переместить опорный элемент на границу двух подмассивов return i // Вернуть индекс опорного элемента } /* Быстрая сортировка */ fun quickSortMedian(nums: IntArray, left: Int, right: Int) { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) return // Разбиение с опорными указателями val pivot = partitionMedian(nums, left, right) // Рекурсивно обработать левый и правый подмассивы quickSort(nums, left, pivot - 1) quickSort(nums, pivot + 1, right) } /* Быстрая сортировка (оптимизация глубины рекурсии) */ fun quickSortTailCall(nums: IntArray, left: Int, right: Int) { // Завершить, когда длина подмассива равна 1 var l = left var r = right while (l < r) { // Операция разбиения с опорными указателями val pivot = partition(nums, l, r) // Выполнить быструю сортировку для более короткого из двух подмассивов if (pivot - l < r - pivot) { quickSort(nums, l, pivot - 1) // Рекурсивно отсортировать левый подмассив l = pivot + 1 // Оставшийся неотсортированный диапазон: [pivot + 1, right] } else { quickSort(nums, pivot + 1, r) // Рекурсивно отсортировать правый подмассив r = pivot - 1 // Оставшийся неотсортированный диапазон: [left, pivot - 1] } } } /* Driver Code */ fun main() { /* Быстрая сортировка */ val nums = intArrayOf(2, 4, 1, 0, 3, 5) quickSort(nums, 0, nums.size - 1) println("После быстрой сортировки nums = ${nums.contentToString()}") /* Быстрая сортировка (оптимизация медианным опорным элементом) */ val nums1 = intArrayOf(2, 4, 1, 0, 3, 5) quickSortMedian(nums1, 0, nums1.size - 1) println("После быстрой сортировки (оптимизация медианным опорным элементом) nums1 = ${nums1.contentToString()}") /* Быстрая сортировка (оптимизация глубины рекурсии) */ val nums2 = intArrayOf(2, 4, 1, 0, 3, 5) quickSortTailCall(nums2, 0, nums2.size - 1) println("После быстрой сортировки (оптимизация глубины рекурсии) nums2 = ${nums2.contentToString()}") } ================================================ FILE: ru/codes/kotlin/chapter_sorting/radix_sort.kt ================================================ /** * File: radix_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* Получить k-й разряд элемента num, где exp = 10^(k-1) */ fun digit(num: Int, exp: Int): Int { // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени return (num / exp) % 10 } /* Сортировка подсчетом (сортировка по k-му разряду nums) */ fun countingSortDigit(nums: IntArray, exp: Int) { // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 val counter = IntArray(10) val n = nums.size // Подсчитать число появлений каждой цифры от 0 до 9 for (i in 0.. m) m = num var exp = 1 // Проходить разряды от младшего к старшему while (exp <= m) { // Выполнить сортировку подсчетом по k-му разряду элементов массива // k = 1 -> exp = 1 // k = 2 -> exp = 10 // то есть exp = 10^(k-1) countingSortDigit(nums, exp) exp *= 10 } } /* Driver Code */ fun main() { // Поразрядная сортировка val nums = intArrayOf( 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ) radixSort(nums) println("После поразрядной сортировки nums = ${nums.contentToString()}") } ================================================ FILE: ru/codes/kotlin/chapter_sorting/selection_sort.kt ================================================ /** * File: selection_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* Сортировка выбором */ fun selectionSort(nums: IntArray) { val n = nums.size // Внешний цикл: неотсортированный диапазон [i, n-1] for (i in 0..() /* Получение длины стека */ fun size(): Int { return stack.size } /* Проверка, пуст ли стек */ fun isEmpty(): Boolean { return size() == 0 } /* Поместить в стек */ fun push(num: Int) { stack.add(num) } /* Извлечь из стека */ fun pop(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return stack.removeAt(size() - 1) } /* Доступ к верхнему элементу стека */ fun peek(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return stack[size() - 1] } /* Преобразовать List в Array и вернуть */ fun toArray(): Array { return stack.toTypedArray() } } /* Driver Code */ fun main() { /* Инициализация стека */ val stack = ArrayStack() /* Помещение элемента в стек */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("Стек stack = ${stack.toArray().contentToString()}") /* Доступ к верхнему элементу стека */ val peek = stack.peek() println("Верхний элемент peek = $peek") /* Извлечение элемента из стека */ val pop = stack.pop() println("Извлеченный элемент pop = $pop, stack после извлечения = ${stack.toArray().contentToString()}") /* Получение длины стека */ val size = stack.size() println("Длина стека size = $size") /* Проверка на пустоту */ val isEmpty = stack.isEmpty() println("Пуст ли стек = $isEmpty") } ================================================ FILE: ru/codes/kotlin/chapter_stack_and_queue/deque.kt ================================================ /** * File: deque.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* Инициализация двусторонней очереди */ val deque = LinkedList() deque.offerLast(3) deque.offerLast(2) deque.offerLast(5) println("Двусторонняя очередь deque = $deque") /* Доступ к элементу */ val peekFirst = deque.peekFirst() println("Первый элемент peekFirst = $peekFirst") val peekLast = deque.peekLast() println("Последний элемент peekLast = $peekLast") /* Добавление элемента в очередь */ deque.offerLast(4) println("После добавления элемента 4 в хвост deque = $deque") deque.offerFirst(1) println("После добавления элемента 1 в голову deque = $deque") /* Извлечение элемента из очереди */ val popLast = deque.pollLast() println("Извлеченный из хвоста элемент = $popLast, deque после извлечения из хвоста = $deque") val popFirst = deque.pollFirst() println("Извлеченный из головы элемент = $popFirst, deque после извлечения из головы = $deque") /* Получение длины двусторонней очереди */ val size = deque.size println("Длина двусторонней очереди size = $size") /* Проверка, пуста ли двусторонняя очередь */ val isEmpty = deque.isEmpty() println("Пуста ли двусторонняя очередь = $isEmpty") } ================================================ FILE: ru/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt ================================================ /** * File: linkedlist_deque.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* Узел двусвязного списка */ class ListNode(var _val: Int) { // Значение узла var next: ListNode? = null // Ссылка на узел-преемник var prev: ListNode? = null // Ссылка на узел-предшественник } /* Двусторонняя очередь на основе двусвязного списка */ class LinkedListDeque { private var front: ListNode? = null // Головной узел front private var rear: ListNode? = null // Хвостовой узел rear private var queSize: Int = 0 // Длина двусторонней очереди /* Получение длины двусторонней очереди */ fun size(): Int { return queSize } /* Проверка, пуста ли двусторонняя очередь */ fun isEmpty(): Boolean { return size() == 0 } /* Операция добавления в очередь */ fun push(num: Int, isFront: Boolean) { val node = ListNode(num) // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node if (isEmpty()) { rear = node front = rear // Операция добавления в голову очереди } else if (isFront) { // Добавить node в голову списка front?.prev = node node.next = front front = node // Обновить головной узел // Операция добавления в хвост очереди } else { // Добавить node в хвост списка rear?.next = node node.prev = rear rear = node // Обновить хвостовой узел } queSize++ // Обновить длину очереди } /* Добавление в голову очереди */ fun pushFirst(num: Int) { push(num, true) } /* Добавление в хвост очереди */ fun pushLast(num: Int) { push(num, false) } /* Операция извлечения из очереди */ fun pop(isFront: Boolean): Int { if (isEmpty()) throw IndexOutOfBoundsException() val _val: Int // Операция извлечения из головы очереди if (isFront) { _val = front!!._val // Временно сохранить значение головного узла // Удалить головной узел val fNext = front!!.next if (fNext != null) { fNext.prev = null front!!.next = null } front = fNext // Обновить головной узел // Операция извлечения из хвоста очереди } else { _val = rear!!._val // Временно сохранить значение хвостового узла // Удалить хвостовой узел val rPrev = rear!!.prev if (rPrev != null) { rPrev.next = null rear!!.prev = null } rear = rPrev // Обновить хвостовой узел } queSize-- // Обновить длину очереди return _val } /* Извлечение из головы очереди */ fun popFirst(): Int { return pop(true) } /* Извлечение из хвоста очереди */ fun popLast(): Int { return pop(false) } /* Доступ к элементу в начале очереди */ fun peekFirst(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return front!!._val } /* Доступ к элементу в конце очереди */ fun peekLast(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return rear!!._val } /* Вернуть массив для вывода */ fun toArray(): IntArray { var node = front val res = IntArray(size()) for (i in res.indices) { res[i] = node!!._val node = node.next } return res } } /* Driver Code */ fun main() { /* Инициализация двусторонней очереди */ val deque = LinkedListDeque() deque.pushLast(3) deque.pushLast(2) deque.pushLast(5) println("Двусторонняя очередь deque = ${deque.toArray().contentToString()}") /* Доступ к элементу */ val peekFirst = deque.peekFirst() println("Первый элемент peekFirst = $peekFirst") val peekLast = deque.peekLast() println("Последний элемент peekLast = $peekLast") /* Добавление элемента в очередь */ deque.pushLast(4) println("После добавления элемента 4 в хвост deque = ${deque.toArray().contentToString()}") deque.pushFirst(1) println("После добавления элемента 1 в голову deque = ${deque.toArray().contentToString()}") /* Извлечение элемента из очереди */ val popLast = deque.popLast() println("Извлеченный из хвоста элемент = ${popLast}, deque после извлечения из хвоста = ${deque.toArray().contentToString()}") val popFirst = deque.popFirst() println("Извлеченный из головы элемент = ${popFirst}, deque после извлечения из головы = ${deque.toArray().contentToString()}") /* Получение длины двусторонней очереди */ val size = deque.size() println("Длина двусторонней очереди size = $size") /* Проверка, пуста ли двусторонняя очередь */ val isEmpty = deque.isEmpty() println("Пуста ли двусторонняя очередь = $isEmpty") } ================================================ FILE: ru/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt ================================================ /** * File: linkedlist_queue.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* Очередь на основе связного списка */ class LinkedListQueue( // Головной узел front, хвостовой узел rear private var front: ListNode? = null, private var rear: ListNode? = null, private var queSize: Int = 0 ) { /* Получение длины очереди */ fun size(): Int { return queSize } /* Проверка, пуста ли очередь */ fun isEmpty(): Boolean { return size() == 0 } /* Поместить в очередь */ fun push(num: Int) { // Добавить num после хвостового узла val node = ListNode(num) // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел if (front == null) { front = node rear = node // Если очередь не пуста, добавить этот узел после хвостового узла } else { rear?.next = node rear = node } queSize++ } /* Извлечь из очереди */ fun pop(): Int { val num = peek() // Удалить головной узел front = front?.next queSize-- return num } /* Доступ к элементу в начале очереди */ fun peek(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return front!!._val } /* Преобразовать связный список в Array и вернуть */ fun toArray(): IntArray { var node = front val res = IntArray(size()) for (i in res.indices) { res[i] = node!!._val node = node.next } return res } } /* Driver Code */ fun main() { /* Инициализация очереди */ val queue = LinkedListQueue() /* Добавление элемента в очередь */ queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) println("Очередь queue = ${queue.toArray().contentToString()}") /* Доступ к элементу в начале очереди */ val peek = queue.peek() println("Первый элемент peek = $peek") /* Извлечение элемента из очереди */ val pop = queue.pop() println("Извлеченный элемент pop = $pop, queue после извлечения = ${queue.toArray().contentToString()}") /* Получение длины очереди */ val size = queue.size() println("Длина очереди size = $size") /* Проверка, пуста ли очередь */ val isEmpty = queue.isEmpty() println("Пуста ли очередь = $isEmpty") } ================================================ FILE: ru/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt ================================================ /** * File: linkedlist_stack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* Стек на основе связного списка */ class LinkedListStack( private var stackPeek: ListNode? = null, // Использовать головной узел как вершину стека private var stkSize: Int = 0 // Длина стека ) { /* Получение длины стека */ fun size(): Int { return stkSize } /* Проверка, пуст ли стек */ fun isEmpty(): Boolean { return size() == 0 } /* Поместить в стек */ fun push(num: Int) { val node = ListNode(num) node.next = stackPeek stackPeek = node stkSize++ } /* Извлечь из стека */ fun pop(): Int? { val num = peek() stackPeek = stackPeek?.next stkSize-- return num } /* Доступ к верхнему элементу стека */ fun peek(): Int? { if (isEmpty()) throw IndexOutOfBoundsException() return stackPeek?._val } /* Преобразовать List в Array и вернуть */ fun toArray(): IntArray { var node = stackPeek val res = IntArray(size()) for (i in res.size - 1 downTo 0) { res[i] = node?._val!! node = node.next } return res } } /* Driver Code */ fun main() { /* Инициализация стека */ val stack = LinkedListStack() /* Помещение элемента в стек */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("Стек stack = ${stack.toArray().contentToString()}") /* Доступ к верхнему элементу стека */ val peek = stack.peek()!! println("Верхний элемент peek = $peek") /* Извлечение элемента из стека */ val pop = stack.pop()!! println("Извлеченный элемент pop = $pop, stack после извлечения = ${stack.toArray().contentToString()}") /* Получение длины стека */ val size = stack.size() println("Длина стека size = $size") /* Проверка на пустоту */ val isEmpty = stack.isEmpty() println("Пуст ли стек = $isEmpty") } ================================================ FILE: ru/codes/kotlin/chapter_stack_and_queue/queue.kt ================================================ /** * File: queue.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* Инициализация очереди */ val queue = LinkedList() /* Добавление элемента в очередь */ queue.offer(1) queue.offer(3) queue.offer(2) queue.offer(5) queue.offer(4) println("Очередь queue = $queue") /* Доступ к элементу в начале очереди */ val peek = queue.peek() println("Первый элемент peek = $peek") /* Извлечение элемента из очереди */ val pop = queue.poll() println("Извлеченный элемент pop = $pop, queue после извлечения = $queue") /* Получение длины очереди */ val size = queue.size println("Длина очереди size = $size") /* Проверка, пуста ли очередь */ val isEmpty = queue.isEmpty() println("Пуста ли очередь = $isEmpty") } ================================================ FILE: ru/codes/kotlin/chapter_stack_and_queue/stack.kt ================================================ /** * File: stack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* Инициализация стека */ val stack = Stack() /* Помещение элемента в стек */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("Стек stack = $stack") /* Доступ к верхнему элементу стека */ val peek = stack.peek() println("Верхний элемент peek = $peek") /* Извлечение элемента из стека */ val pop = stack.pop() println("Извлеченный элемент pop = $pop, stack после извлечения = $stack") /* Получение длины стека */ val size = stack.size println("Длина стека size = $size") /* Проверка на пустоту */ val isEmpty = stack.isEmpty() println("Пуст ли стек = $isEmpty") } ================================================ FILE: ru/codes/kotlin/chapter_tree/array_binary_tree.kt ================================================ /** * File: array_binary_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* Класс двоичного дерева в массивном представлении */ class ArrayBinaryTree(private val tree: MutableList) { /* Вместимость списка */ fun size(): Int { return tree.size } /* Получить значение узла с индексом i */ fun _val(i: Int): Int? { // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию if (i < 0 || i >= size()) return null return tree[i] } /* Получить индекс левого дочернего узла узла с индексом i */ fun left(i: Int): Int { return 2 * i + 1 } /* Получить индекс правого дочернего узла узла с индексом i */ fun right(i: Int): Int { return 2 * i + 2 } /* Получить индекс родительского узла узла с индексом i */ fun parent(i: Int): Int { return (i - 1) / 2 } /* Обход в ширину */ fun levelOrder(): MutableList { val res = mutableListOf() // Непосредственно обходить массив for (i in 0..) { // Если это пустая позиция, вернуть if (_val(i) == null) return // Предварительный обход if ("pre" == order) res.add(_val(i)) dfs(left(i), order, res) // Симметричный обход if ("in" == order) res.add(_val(i)) dfs(right(i), order, res) // Обратный обход if ("post" == order) res.add(_val(i)) } /* Предварительный обход */ fun preOrder(): MutableList { val res = mutableListOf() dfs(0, "pre", res) return res } /* Симметричный обход */ fun inOrder(): MutableList { val res = mutableListOf() dfs(0, "in", res) return res } /* Обратный обход */ fun postOrder(): MutableList { val res = mutableListOf() dfs(0, "post", res) return res } } /* Driver Code */ fun main() { // Инициализировать двоичное дерево // Здесь используется функция, напрямую строящая двоичное дерево из списка val arr = mutableListOf(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15) val root = TreeNode.listToTree(arr) println("\nИнициализация двоичного дерева\n") println("Массивное представление двоичного дерева:") println(arr) println("Связное представление двоичного дерева:") printTree(root) // Класс двоичного дерева в массивном представлении val abt = ArrayBinaryTree(arr) // Доступ к узлу val i = 1 val l = abt.left(i) val r = abt.right(i) val p = abt.parent(i) println("Текущий узел: индекс = $i, значение = ${abt._val(i)}") println("Индекс левого дочернего узла = $l, значение = ${abt._val(l)}") println("Индекс правого дочернего узла = $r, значение = ${abt._val(r)}") println("Индекс родительского узла = $p, значение = ${abt._val(p)}") // Обходить дерево var res = abt.levelOrder() println("\nОбход в ширину = $res") res = abt.preOrder() println("Предварительный обход = $res") res = abt.inOrder() println("Симметричный обход = $res") res = abt.postOrder() println("Обратный обход = $res") } ================================================ FILE: ru/codes/kotlin/chapter_tree/avl_tree.kt ================================================ /** * File: avl_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree import kotlin.math.max /* AVL-дерево */ class AVLTree { var root: TreeNode? = null // Корневой узел /* Получить высоту узла */ fun height(node: TreeNode?): Int { // Высота пустого узла равна -1, высота листового узла равна 0 return node?.height ?: -1 } /* Обновить высоту узла */ private fun updateHeight(node: TreeNode?) { // Высота узла равна высоте более высокого поддерева + 1 node?.height = max(height(node?.left), height(node?.right)) + 1 } /* Получить коэффициент баланса */ fun balanceFactor(node: TreeNode?): Int { // Коэффициент баланса пустого узла равен 0 if (node == null) return 0 // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева return height(node.left) - height(node.right) } /* Операция правого вращения */ private fun rightRotate(node: TreeNode?): TreeNode { val child = node!!.left val grandChild = child!!.right // Выполнить правое вращение узла node вокруг child child.right = node node.left = grandChild // Обновить высоту узла updateHeight(node) updateHeight(child) // Вернуть корневой узел поддерева после вращения return child } /* Операция левого вращения */ private fun leftRotate(node: TreeNode?): TreeNode { val child = node!!.right val grandChild = child!!.left // Выполнить левое вращение узла node вокруг child child.left = node node.right = grandChild // Обновить высоту узла updateHeight(node) updateHeight(child) // Вернуть корневой узел поддерева после вращения return child } /* Выполнить вращение, чтобы снова сбалансировать поддерево */ private fun rotate(node: TreeNode): TreeNode { // Получить коэффициент баланса узла node val balanceFactor = balanceFactor(node) // Левосторонне перекошенное дерево if (balanceFactor > 1) { if (balanceFactor(node.left) >= 0) { // Правое вращение return rightRotate(node) } else { // Сначала левое вращение, затем правое node.left = leftRotate(node.left) return rightRotate(node) } } // Правосторонне перекошенное дерево if (balanceFactor < -1) { if (balanceFactor(node.right) <= 0) { // Левое вращение return leftRotate(node) } else { // Сначала правое вращение, затем левое node.right = rightRotate(node.right) return leftRotate(node) } } // Дерево сбалансировано, вращение не требуется, вернуть сразу return node } /* Вставка узла */ fun insert(_val: Int) { root = insertHelper(root, _val) } /* Рекурсивная вставка узла (вспомогательный метод) */ private fun insertHelper(n: TreeNode?, _val: Int): TreeNode { if (n == null) return TreeNode(_val) var node = n /* 1. Найти позицию вставки и вставить узел */ if (_val < node._val) node.left = insertHelper(node.left, _val) else if (_val > node._val) node.right = insertHelper(node.right, _val) else return node // Повторяющийся узел не вставлять, сразу вернуть updateHeight(node) // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = rotate(node) // Вернуть корневой узел поддерева return node } /* Удаление узла */ fun remove(_val: Int) { root = removeHelper(root, _val) } /* Рекурсивное удаление узла (вспомогательный метод) */ private fun removeHelper(n: TreeNode?, _val: Int): TreeNode? { var node = n ?: return null /* 1. Найти узел и удалить его */ if (_val < node._val) node.left = removeHelper(node.left, _val) else if (_val > node._val) node.right = removeHelper(node.right, _val) else { if (node.left == null || node.right == null) { val child = if (node.left != null) node.left else node.right // Число дочерних узлов = 0, удалить node и сразу вернуть if (child == null) return null // Число дочерних узлов = 1, удалить node напрямую else node = child } else { // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел var temp = node.right while (temp!!.left != null) { temp = temp.left } node.right = removeHelper(node.right, temp._val) node._val = temp._val } } updateHeight(node) // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = rotate(node) // Вернуть корневой узел поддерева return node } /* Поиск узла */ fun search(_val: Int): TreeNode? { var cur = root // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Целевой узел находится в правом поддереве cur cur = if (cur._val < _val) cur.right!! // Целевой узел находится в левом поддереве cur else if (cur._val > _val) cur.left // Найти целевой узел и выйти из цикла else break } // Вернуть целевой узел return cur } } fun testInsert(tree: AVLTree, _val: Int) { tree.insert(_val) println("\nПосле вставки узла $_val AVL-дерево имеет вид") printTree(tree.root) } fun testRemove(tree: AVLTree, _val: Int) { tree.remove(_val) println("\nПосле удаления узла $_val AVL-дерево имеет вид") printTree(tree.root) } /* Driver Code */ fun main() { /* Инициализация пустого AVL-дерева */ val avlTree = AVLTree() /* Вставка узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла testInsert(avlTree, 1) testInsert(avlTree, 2) testInsert(avlTree, 3) testInsert(avlTree, 4) testInsert(avlTree, 5) testInsert(avlTree, 8) testInsert(avlTree, 7) testInsert(avlTree, 9) testInsert(avlTree, 10) testInsert(avlTree, 6) /* Вставка повторяющегося узла */ testInsert(avlTree, 7) /* Удаление узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла testRemove(avlTree, 8) // Удаление узла степени 0 testRemove(avlTree, 5) // Удаление узла степени 1 testRemove(avlTree, 4) // Удаление узла степени 2 /* Поиск узла */ val node = avlTree.search(7) println("\nНайденный объект узла = $node, значение узла = ${node?._val}") } ================================================ FILE: ru/codes/kotlin/chapter_tree/binary_search_tree.kt ================================================ /** * File: binary_search_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* Двоичное дерево поиска */ class BinarySearchTree { // Инициализировать пустое дерево private var root: TreeNode? = null /* Получить корневой узел двоичного дерева */ fun getRoot(): TreeNode? { return root } /* Поиск узла */ fun search(num: Int): TreeNode? { var cur = root // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Целевой узел находится в правом поддереве cur cur = if (cur._val < num) cur.right // Целевой узел находится в левом поддереве cur else if (cur._val > num) cur.left // Найти целевой узел и выйти из цикла else break } // Вернуть целевой узел return cur } /* Вставка узла */ fun insert(num: Int) { // Если дерево пусто, инициализировать корневой узел if (root == null) { root = TreeNode(num) return } var cur = root var pre: TreeNode? = null // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Найти повторяющийся узел и сразу вернуть if (cur._val == num) return pre = cur // Позиция вставки находится в правом поддереве cur cur = if (cur._val < num) cur.right // Позиция вставки находится в левом поддереве cur else cur.left } // Вставка узла val node = TreeNode(num) if (pre?._val!! < num) pre.right = node else pre.left = node } /* Удаление узла */ fun remove(num: Int) { // Если дерево пусто, сразу вернуть if (root == null) return var cur = root var pre: TreeNode? = null // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Найти узел для удаления и выйти из цикла if (cur._val == num) break pre = cur // Узел для удаления находится в правом поддереве cur cur = if (cur._val < num) cur.right // Узел для удаления находится в левом поддереве cur else cur.left } // Если узел для удаления отсутствует, сразу вернуть if (cur == null) return // Число дочерних узлов = 0 или 1 if (cur.left == null || cur.right == null) { // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел val child = if (cur.left != null) cur.left else cur.right // Удалить узел cur if (cur != root) { if (pre!!.left == cur) pre.left = child else pre.right = child } else { // Если удаляемый узел является корнем, заново назначить корневой узел root = child } // Число дочерних узлов = 2 } else { // Получить следующий узел после cur в симметричном обходе var tmp = cur.right while (tmp!!.left != null) { tmp = tmp.left } // Рекурсивно удалить узел tmp remove(tmp._val) // Перезаписать cur значением tmp cur._val = tmp._val } } } /* Driver Code */ fun main() { /* Инициализация двоичного дерева поиска */ val bst = BinarySearchTree() // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево val nums = intArrayOf(8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15) for (num in nums) { bst.insert(num) } println("\nИсходное двоичное дерево\n") printTree(bst.getRoot()) /* Поиск узла */ val node = bst.search(7) println("Найденный объект узла = $node, значение узла = ${node?._val}") /* Вставка узла */ bst.insert(16) println("\nПосле вставки узла 16 двоичное дерево имеет вид\n") printTree(bst.getRoot()) /* Удаление узла */ bst.remove(1) println("\nПосле удаления узла 1 двоичное дерево имеет вид\n") printTree(bst.getRoot()) bst.remove(2) println("\nПосле удаления узла 2 двоичное дерево имеет вид\n") printTree(bst.getRoot()) bst.remove(4) println("\nПосле удаления узла 4 двоичное дерево имеет вид\n") printTree(bst.getRoot()) } ================================================ FILE: ru/codes/kotlin/chapter_tree/binary_tree.kt ================================================ /** * File: binary_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* Driver Code */ fun main() { /* Инициализация двоичного дерева */ // Инициализация узла val n1 = TreeNode(1) val n2 = TreeNode(2) val n3 = TreeNode(3) val n4 = TreeNode(4) val n5 = TreeNode(5) // Построить связи между узлами (указатели) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 println("\nИнициализация двоичного дерева\n") printTree(n1) /* Вставка и удаление узлов */ val P = TreeNode(0) // Вставить узел P между n1 -> n2 n1.left = P P.left = n2 println("\nПосле вставки узла P\n") printTree(n1) // Удалить узел P n1.left = n2 println("\nПосле удаления узла P\n") printTree(n1) } ================================================ FILE: ru/codes/kotlin/chapter_tree/binary_tree_bfs.kt ================================================ /** * File: binary_tree_bfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree import java.util.* /* Обход в ширину */ fun levelOrder(root: TreeNode?): MutableList { // Инициализировать очередь и добавить корневой узел val queue = LinkedList() queue.add(root) // Инициализировать список для хранения последовательности обхода val list = mutableListOf() while (queue.isNotEmpty()) { val node = queue.poll() // Извлечение из очереди list.add(node?._val!!) // Сохранить значение узла if (node.left != null) queue.offer(node.left) // Поместить левый дочерний узел в очередь if (node.right != null) queue.offer(node.right) // Поместить правый дочерний узел в очередь } return list } /* Driver Code */ fun main() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из списка val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) println("\nИнициализация двоичного дерева\n") printTree(root) /* Обход в ширину */ val list = levelOrder(root) println("\nПоследовательность печати узлов при обходе в ширину = $list") } ================================================ FILE: ru/codes/kotlin/chapter_tree/binary_tree_dfs.kt ================================================ /** * File: binary_tree_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree // Инициализировать список для хранения последовательности обхода var list = mutableListOf() /* Предварительный обход */ fun preOrder(root: TreeNode?) { if (root == null) return // Порядок обхода: корень -> левое поддерево -> правое поддерево list.add(root._val) preOrder(root.left) preOrder(root.right) } /* Симметричный обход */ fun inOrder(root: TreeNode?) { if (root == null) return // Порядок обхода: левое поддерево -> корень -> правое поддерево inOrder(root.left) list.add(root._val) inOrder(root.right) } /* Обратный обход */ fun postOrder(root: TreeNode?) { if (root == null) return // Порядок обхода: левое поддерево -> правое поддерево -> корень postOrder(root.left) postOrder(root.right) list.add(root._val) } /* Driver Code */ fun main() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из списка val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) println("\nИнициализация двоичного дерева\n") printTree(root) /* Предварительный обход */ list.clear() preOrder(root) println("\nПоследовательность печати узлов при предварительном обходе = $list") /* Симметричный обход */ list.clear() inOrder(root) println("\nПоследовательность печати узлов при симметричном обходе = $list") /* Обратный обход */ list.clear() postOrder(root) println("\nПоследовательность печати узлов при обратном обходе = $list") } ================================================ FILE: ru/codes/kotlin/utils/ListNode.kt ================================================ /** * File: ListNode.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* Узел связного списка */ class ListNode(var _val: Int) { var next: ListNode? = null companion object { /* Десериализовать список в связный список */ fun arrToLinkedList(arr: IntArray): ListNode? { val dum = ListNode(0) var head = dum for (_val in arr) { head.next = ListNode(_val) head = head.next!! } return dum.next } } } ================================================ FILE: ru/codes/kotlin/utils/PrintUtil.kt ================================================ /** * File: PrintUtil.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils import java.util.* class Trunk(var prev: Trunk?, var str: String) /* Вывести матрицу (Array) */ fun printMatrix(matrix: Array>) { println("[") for (row in matrix) { println(" $row,") } println("]") } /* Вывести матрицу (List) */ fun printMatrix(matrix: MutableList>) { println("[") for (row in matrix) { println(" $row,") } println("]") } /* Вывести связный список */ fun printLinkedList(h: ListNode?) { var head = h val list = mutableListOf() while (head != null) { list.add(head._val.toString()) head = head.next } println(list.joinToString(separator = " -> ")) } /* Вывести двоичное дерево */ fun printTree(root: TreeNode?) { printTree(root, null, false) } /** * Вывести двоичное дерево * Этот вывод дерева заимствован из TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ fun printTree(root: TreeNode?, prev: Trunk?, isRight: Boolean) { if (root == null) { return } var prevStr = " " val trunk = Trunk(prev, prevStr) printTree(root.right, trunk, true) if (prev == null) { trunk.str = "———" } else if (isRight) { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev.str = prevStr } showTrunks(trunk) println(" ${root._val}") if (prev != null) { prev.str = prevStr } trunk.str = " |" printTree(root.left, trunk, false) } fun showTrunks(p: Trunk?) { if (p == null) { return } showTrunks(p.prev) print(p.str) } /* Вывести хеш-таблицу */ fun printHashMap(map: Map) { for ((key, value) in map) { println("${key.toString()} -> $value") } } /* Вывести кучу */ fun printHeap(queue: Queue?) { val list = mutableListOf() queue?.let { list.addAll(it) } print("Массивное представление кучи:") println(list) println("Древовидное представление кучи:") val root = TreeNode.listToTree(list) printTree(root) } ================================================ FILE: ru/codes/kotlin/utils/TreeNode.kt ================================================ /** * File: TreeNode.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* Класс узла двоичного дерева */ /* Конструктор */ class TreeNode( var _val: Int // Значение узла ) { var height: Int = 0 // Высота узла var left: TreeNode? = null // Ссылка на левый дочерний узел var right: TreeNode? = null // Ссылка на правый дочерний узел // Правила кодирования сериализации см.: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Массивное представление двоичного дерева: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // Связное представление двоичного дерева: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* Десериализовать список в двоичное дерево: рекурсия */ companion object { private fun listToTreeDFS(arr: MutableList, i: Int): TreeNode? { if (i < 0 || i >= arr.size || arr[i] == null) { return null } val root = TreeNode(arr[i]!!) root.left = listToTreeDFS(arr, 2 * i + 1) root.right = listToTreeDFS(arr, 2 * i + 2) return root } /* Десериализовать список в двоичное дерево */ fun listToTree(arr: MutableList): TreeNode? { return listToTreeDFS(arr, 0) } /* Сериализовать двоичное дерево в список: рекурсия */ private fun treeToListDFS(root: TreeNode?, i: Int, res: MutableList) { if (root == null) return while (i >= res.size) { res.add(null) } res[i] = root._val treeToListDFS(root.left, 2 * i + 1, res) treeToListDFS(root.right, 2 * i + 2, res) } /* Сериализовать двоичное дерево в список */ fun treeToList(root: TreeNode?): MutableList { val res = mutableListOf() treeToListDFS(root, 0, res) return res } } } ================================================ FILE: ru/codes/kotlin/utils/Vertex.kt ================================================ /** * File: Vertex.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* Класс вершины */ class Vertex(val _val: Int) { companion object { /* На вход подается список значений vals, на выходе возвращается список вершин vets */ fun valsToVets(vals: IntArray): Array { val vets = arrayOfNulls(vals.size) for (i in vals.indices) { vets[i] = Vertex(vals[i]) } return vets } /* На вход подается список вершин vets, на выходе возвращается список значений vals */ fun vetsToVals(vets: MutableList): MutableList { val vals = mutableListOf() for (vet in vets) { vals.add(vet!!._val) } return vals } } } ================================================ FILE: ru/codes/python/.gitignore ================================================ __pycache__ ================================================ FILE: ru/codes/python/chapter_array_and_linkedlist/array.py ================================================ """ 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 # Обратите внимание: list в Python — это динамический массив, его можно расширять напрямую # Для удобства обучения в этой функции list рассматривается как массив неизменяемой длины def extend(nums: list[int], enlarge: int) -> list[int]: """Увеличить длину массива""" # Инициализировать массив увеличенной длины res = [0] * (len(nums) + enlarge) # Скопировать все элементы исходного массива в новый массив for i in range(len(nums)): res[i] = nums[i] # Вернуть новый массив после расширения return res def insert(nums: list[int], num: int, index: int): """Вставить элемент num по индексу index в массив""" # Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад 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 на одну позицию вперед 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("После вставки числа 6 по индексу 3 nums =", nums) # Удаление элемента remove(nums, 2) print("После удаления элемента по индексу 2 nums =", nums) # Обход массива traverse(nums) # Поиск элемента index: int = find(nums, 3) print("Поиск элемента 3 в nums: индекс =", index) ================================================ FILE: ru/codes/python/chapter_array_and_linkedlist/linked_list.py ================================================ """ 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): """Вставить узел P после узла n0 в связном списке""" 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)) ================================================ FILE: ru/codes/python/chapter_array_and_linkedlist/list.py ================================================ """ 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После вставки числа 6 по индексу 3 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 # Объединить два списка nums1 = [6, 8, 7, 10, 9] nums += nums1 print("\nПосле конкатенации списка nums1 к nums nums =", nums) # Отсортировать список nums.sort() print("\nПосле сортировки списка nums =", nums) ================================================ FILE: ru/codes/python/chapter_array_and_linkedlist/my_list.py ================================================ """ 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("индекс выходит за границы") return self._arr[index] def set(self, num: int, index: int): """Обновление элемента""" if index < 0 or index >= self._size: raise IndexError("индекс выходит за границы") 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("индекс выходит за границы") # При превышении вместимости по числу элементов запускается расширение if self._size == self.capacity(): self.extend_capacity() # Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад 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("индекс выходит за границы") num = self._arr[index] # Сдвинуть все элементы после индекса index на одну позицию вперед 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("После вставки числа 6 по индексу 3 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 после увеличения вместимости = {nums.to_array()}, вместимость = {nums.capacity()}, длина = {nums.size()}") ================================================ FILE: ru/codes/python/chapter_backtracking/n_queens.py ================================================ """ 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 """Driver Code""" 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) ================================================ FILE: ru/codes/python/chapter_backtracking/permutations_i.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [1, 2, 3] res = permutations_i(nums) print(f"Входной массив nums = {nums}") print(f"Все перестановки res = {res}") ================================================ FILE: ru/codes/python/chapter_backtracking/permutations_ii.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [1, 2, 2] res = permutations_ii(nums) print(f"Входной массив nums = {nums}") print(f"Все перестановки res = {res}") ================================================ FILE: ru/codes/python/chapter_backtracking/preorder_traversal_i_compact.py ================================================ """ 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): """Предварительный обход: пример 1""" if root is None: return if root.val == 7: # Записать решение res.append(root) pre_order(root.left) pre_order(root.right) """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\nИнициализация двоичного дерева") print_tree(root) # Предварительный обход res = list[TreeNode]() pre_order(root) print("\nВсе узлы со значением 7") print([node.val for node in res]) ================================================ FILE: ru/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py ================================================ """ 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): """Предварительный обход: пример 2""" if root is None: return # Попытка path.append(root) if root.val == 7: # Записать решение res.append(list(path)) pre_order(root.left) pre_order(root.right) # Откат path.pop() """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\nИнициализация двоичного дерева") print_tree(root) # Предварительный обход path = list[TreeNode]() res = list[list[TreeNode]]() pre_order(root) print("\nВсе пути от корня к узлу 7") for path in res: print([node.val for node in path]) ================================================ FILE: ru/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py ================================================ """ 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): """Предварительный обход: пример 3""" # Отсечение if root is None or root.val == 3: return # Попытка path.append(root) if root.val == 7: # Записать решение res.append(list(path)) pre_order(root.left) pre_order(root.right) # Откат path.pop() """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\nИнициализация двоичного дерева") print_tree(root) # Предварительный обход path = list[TreeNode]() res = list[list[TreeNode]]() pre_order(root) print("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3") for path in res: print([node.val for node in path]) ================================================ FILE: ru/codes/python/chapter_backtracking/preorder_traversal_iii_template.py ================================================ """ 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]] ): """Алгоритм бэктрекинга: пример 3""" # Проверить, является ли текущее состояние решением if is_solution(state): # Записать решение record_solution(state, res) # Перебор всех вариантов выбора for choice in choices: # Отсечение: проверить допустимость выбора if is_valid(state, choice): # Попытка: сделать выбор и обновить состояние make_choice(state, choice) # Перейти к следующему выбору backtrack(state, [choice.left, choice.right], res) # Откат: отменить выбор и восстановить предыдущее состояние undo_choice(state, choice) """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\nИнициализация двоичного дерева") print_tree(root) # Алгоритм бэктрекинга res = [] backtrack(state=[], choices=[root], res=res) print("\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3") for path in res: print([node.val for node in path]) ================================================ FILE: ru/codes/python/chapter_backtracking/subset_sum_i.py ================================================ """ 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 # Обойти все варианты выбора # Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств for i in range(start, len(choices)): # Отсечение 1: если сумма подмножества превышает 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 """Driver Code""" if __name__ == "__main__": nums = [3, 4, 5] target = 9 res = subset_sum_i(nums, target) print(f"Входной массив nums = {nums}, target = {target}") print(f"Все подмножества с суммой {target}: res = {res}") ================================================ FILE: ru/codes/python/chapter_backtracking/subset_sum_i_naive.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [3, 4, 5] target = 9 res = subset_sum_i_naive(nums, target) print(f"Входной массив nums = {nums}, target = {target}") print(f"Все подмножества с суммой {target}: res = {res}") print(f"Обратите внимание: результат этого метода содержит повторяющиеся множества") ================================================ FILE: ru/codes/python/chapter_backtracking/subset_sum_ii.py ================================================ """ 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 # Обойти все варианты выбора # Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств # Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента for i in range(start, len(choices)): # Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл # Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if target - choices[i] < 0: break # Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить 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 """Driver Code""" if __name__ == "__main__": nums = [4, 4, 5] target = 9 res = subset_sum_ii(nums, target) print(f"Входной массив nums = {nums}, target = {target}") print(f"Все подмножества с суммой {target}: res = {res}") ================================================ FILE: ru/codes/python/chapter_computational_complexity/iteration.py ================================================ """ 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 (двойное обновление)""" 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"\nРезультат суммирования в цикле for res = {res}") res = while_loop(n) print(f"\nРезультат суммирования в цикле while res = {res}") res = while_loop_ii(n) print(f"\nРезультат суммирования в цикле while (двойное обновление) res = {res}") res = nested_for_loop(n) print(f"\nРезультат обхода в двойном цикле for {res}") ================================================ FILE: ru/codes/python/chapter_computational_complexity/recursion.py ================================================ """ 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}") ================================================ FILE: ru/codes/python/chapter_computational_complexity/space_complexity.py ================================================ """ 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 равна n, n-1, ..., 2, 1 nums = [0] * n 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) ================================================ FILE: ru/codes/python/chapter_computational_complexity/time_complexity.py ================================================ """ 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 # На каждом шаге клетка делится надвое, образуя последовательность 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 = linear_log_recur(n // 2) + linear_log_recur(n // 2) # Текущая подзадача содержит n операций for _ in range(n): count += 1 return count def factorial_recur(n: int) -> int: """Факториальная сложность (рекурсивная реализация)""" if n == 0: return 1 count = 0 # Из одного получается n for _ in range(n): count += factorial_recur(n - 1) return count """Driver Code""" if __name__ == "__main__": # Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях n = 8 print("Размер входных данных n =", n) count = constant(n) print("Число операций константной сложности =", count) count = linear(n) print("Число операций линейной сложности =", count) count = array_traversal([0] * n) print("Число операций линейной сложности (обход массива) =", count) count = quadratic(n) print("Число операций квадратичной сложности =", count) nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1] count = bubble_sort(nums) print("Число операций квадратичной сложности (пузырьковая сортировка) =", count) count = exponential(n) print("Число операций экспоненциальной сложности (итеративная реализация) =", count) count = exp_recur(n) print("Число операций экспоненциальной сложности (рекурсивная реализация) =", count) count = logarithmic(n) print("Число операций логарифмической сложности (итеративная реализация) =", count) count = log_recur(n) print("Число операций логарифмической сложности (рекурсивная реализация) =", count) count = linear_log_recur(n) print("Число операций линейно-логарифмической сложности (рекурсивная реализация) =", count) count = factorial_recur(n) print("Число операций факториальной сложности (рекурсивная реализация) =", count) ================================================ FILE: ru/codes/python/chapter_computational_complexity/worst_best_time_complexity.py ================================================ """ 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: """Найти индекс числа 1 в массиве nums""" 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) ================================================ FILE: ru/codes/python/chapter_divide_and_conquer/binary_search_recur.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # Бинарный поиск (двусторонне замкнутый интервал) index = binary_search(nums, target) print("Индекс целевого элемента 6 = ", index) ================================================ FILE: ru/codes/python/chapter_divide_and_conquer/build_tree.py ================================================ """ 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 их индексам inorder_map = {val: i for i, val in enumerate(inorder)} root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) return root """Driver Code""" if __name__ == "__main__": preorder = [3, 9, 2, 1, 7] inorder = [9, 3, 1, 2, 7] print(f"Предварительный обход = {preorder}") print(f"Симметричный обход = {inorder}") root = build_tree(preorder, inorder) print("Построенное двоичное дерево:") print_tree(root) ================================================ FILE: ru/codes/python/chapter_divide_and_conquer/hanota.py ================================================ """ 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 остался только один диск, сразу переместить его в tar if i == 1: move(src, tar) return # Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar dfs(i - 1, src, tar, buf) # Подзадача f(1): переместить оставшийся один диск из src в tar move(src, tar) # Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src dfs(i - 1, buf, src, tar) def solve_hanota(A: list[int], B: list[int], C: list[int]): """Решить задачу Ханойской башни""" n = len(A) # Переместить верхние n дисков из A в C с помощью B dfs(n, A, B, C) """Driver Code""" 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}") ================================================ FILE: ru/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_backtrack(n) print(f"Количество способов подняться по лестнице из {n} ступеней = {res}") ================================================ FILE: ru/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_constraint_dp(n) print(f"Количество способов подняться по лестнице из {n} ступеней = {res}") ================================================ FILE: ru/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dfs(n) print(f"Количество способов подняться по лестнице из {n} ступеней = {res}") ================================================ FILE: ru/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dfs_mem(n) print(f"Количество способов подняться по лестнице из {n} ступеней = {res}") ================================================ FILE: ru/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dp(n) print(f"Количество способов подняться по лестнице из {n} ступеней = {res}") res = climbing_stairs_dp_comp(n) print(f"Количество способов подняться по лестнице из {n} ступеней = {res}") ================================================ FILE: ru/codes/python/chapter_dynamic_programming/coin_change.py ================================================ """ 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 """Driver Code""" 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}") ================================================ FILE: ru/codes/python/chapter_dynamic_programming/coin_change_ii.py ================================================ """ 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] """Driver Code""" 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}") ================================================ FILE: ru/codes/python/chapter_dynamic_programming/edit_distance.py ================================================ """ File: edit_distancde.py Created Time: 2023-07-04 Author: krahets (krahets@163.com) """ def edit_distance_dfs(s: str, t: str, i: int, j: int) -> int: """Редакционное расстояние: полный перебор""" # Если s и t пусты, вернуть 0 if i == 0 and j == 0: return 0 # Если s пусто, вернуть длину t if i == 0: return j # Если t пусто, вернуть длину s if j == 0: return i # Если два символа равны, сразу пропустить их if s[i - 1] == t[j - 1]: return edit_distance_dfs(s, t, i - 1, j - 1) # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 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] # Если два символа равны, сразу пропустить их if s[i - 1] == t[j - 1]: return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 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]: # Если два символа равны, сразу пропустить их dp[i][j] = dp[i - 1][j - 1] else: # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 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]: # Если два символа равны, сразу пропустить их dp[j] = leftup else: # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[j] = min(dp[j - 1], dp[j], leftup) + 1 leftup = temp # Обновить до значения dp[i-1, j-1] для следующей итерации return dp[m] """Driver Code""" if __name__ == "__main__": s = "bag" t = "pack" n, m = len(s), len(t) # Полный перебор res = edit_distance_dfs(s, t, n, m) print(f"Чтобы преобразовать {s} в {t}, нужно минимум {res} шагов") # Поиск с мемоизацией 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} шагов") ================================================ FILE: ru/codes/python/chapter_dynamic_programming/knapsack.py ================================================ """ 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] # Вернуть вариант с большей стоимостью из двух возможных 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] # Сохранить и вернуть вариант с большей стоимостью из двух решений 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] """Driver Code""" if __name__ == "__main__": wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = len(wgt) # Полный перебор res = knapsack_dfs(wgt, val, n, cap) print(f"Максимальная стоимость предметов без превышения вместимости рюкзака = {res}") # Поиск с мемоизацией 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}") ================================================ FILE: ru/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py ================================================ """ 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 """Driver Code""" 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}") ================================================ FILE: ru/codes/python/chapter_dynamic_programming/min_path_sum.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] n, m = len(grid), len(grid[0]) # Полный перебор res = min_path_sum_dfs(grid, n - 1, m - 1) print(f"Минимальная сумма пути из левого верхнего угла в правый нижний = {res}") # Поиск с мемоизацией 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}") ================================================ FILE: ru/codes/python/chapter_dynamic_programming/unbounded_knapsack.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": wgt = [1, 2, 3] val = [5, 11, 15] cap = 4 # Динамическое программирование res = unbounded_knapsack_dp(wgt, val, cap) print(f"Максимальная стоимость предметов без превышения вместимости рюкзака = {res}") # Динамическое программирование с оптимизацией памяти res = unbounded_knapsack_dp_comp(wgt, val, cap) print(f"Максимальная стоимость предметов без превышения вместимости рюкзака = {res}") ================================================ FILE: ru/codes/python/chapter_graph/graph_adjacency_list.py ================================================ """ 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]]): """Конструктор""" # Список смежности, где key — вершина, а value — все смежные ей вершины 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},") """Driver Code""" 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() ================================================ FILE: ru/codes/python/chapter_graph/graph_adjacency_matrix.py ================================================ """ 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 представляют собой индексы вершин, то есть соответствуют индексам элементов vertices 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 соответствуют индексам элементов vertices # Обработка выхода индекса за границы и случая равенства 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 соответствуют индексам элементов vertices # Обработка выхода индекса за границы и случая равенства if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: raise IndexError() self.adj_mat[i][j] = 0 self.adj_mat[j][i] = 0 def print(self): """Вывести матрицу смежности""" print("Список вершин =", self.vertices) print("Матрица смежности =") print_matrix(self.adj_mat) """Driver Code""" if __name__ == "__main__": # Инициализация неориентированного графа # Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices vertices = [1, 3, 2, 5, 4] edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] graph = GraphAdjMat(vertices, edges) print("\nГраф после инициализации") graph.print() # Добавление ребра # Индексы вершин 1 и 2 равны 0 и 2 соответственно 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() ================================================ FILE: ru/codes/python/chapter_graph/graph_bfs.py ================================================ """ 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 """Driver Code""" 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)) ================================================ FILE: ru/codes/python/chapter_graph/graph_dfs.py ================================================ """ 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 """Driver Code""" 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)) ================================================ FILE: ru/codes/python/chapter_greedy/coin_change_greedy.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # Жадный подход: гарантирует нахождение глобально оптимального решения coins = [1, 5, 10, 20, 50, 100] amt = 186 res = coin_change_greedy(coins, amt) print(f"\ncoins = {coins}, amt = {amt}") print(f"Минимальное число монет для набора суммы {amt} = {res}") # Жадный подход: не гарантирует нахождение глобально оптимального решения 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") ================================================ FILE: ru/codes/python/chapter_greedy/fractional_knapsack.py ================================================ """ 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: """Дробный рюкзак: жадный алгоритм""" # Создать список предметов с двумя свойствами: вес и стоимость 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 """Driver Code""" if __name__ == "__main__": wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = len(wgt) # Жадный алгоритм res = fractional_knapsack(wgt, val, cap) print(f"Максимальная стоимость предметов без превышения вместимости рюкзака = {res}") ================================================ FILE: ru/codes/python/chapter_greedy/max_capacity.py ================================================ """ 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 # Выполнять жадный выбор в цикле, пока две доски не встретятся 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 """Driver Code""" if __name__ == "__main__": ht = [3, 8, 5, 2, 7, 7, 3, 4] # Жадный алгоритм res = max_capacity(ht) print(f"Максимальная вместимость = {res}") ================================================ FILE: ru/codes/python/chapter_greedy/max_product_cutting.py ================================================ """ 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 — число троек, а 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)) """Driver Code""" if __name__ == "__main__": n = 58 # Жадный алгоритм res = max_product_cutting(n) print(f"Максимальное произведение после разрезания = {res}") ================================================ FILE: ru/codes/python/chapter_hashing/array_hash_map.py ================================================ """ 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 | None: """Операция поиска""" 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, "Сяо Ха") hmap.put(15937, "Сяо Ло") hmap.put(16750, "Сяо Суань") hmap.put(13276, "Сяо Фа") hmap.put(10583, "Сяо Я") print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") hmap.print() # Операция поиска # Передать ключ key в хеш-таблицу и получить значение value name = hmap.get(15937) print("\nДля номера 15937 найдено имя " + name) # Операция удаления # Удалить пару (key, value) из хеш-таблицы hmap.remove(10583) print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") hmap.print() # Обход хеш-таблицы print("\nОтдельный обход пар ключ-значение") for pair in hmap.entry_set(): print(pair.key, "->", pair.val) print("\nОтдельный обход ключей") for key in hmap.key_set(): print(key) print("\nОтдельный обход значений") for val in hmap.value_set(): print(val) ================================================ FILE: ru/codes/python/chapter_hashing/built_in_hash.py ================================================ """ 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 Algo" hash_str = hash(str) print(f"Хеш-значение строки {str} = {hash_str}") tup = (12836, "Сяо Ха") hash_tup = hash(tup) print(f"Хеш-значение кортежа {tup} = {hash(hash_tup)}") obj = ListNode(0) hash_obj = hash(obj) print(f"Хеш-значение объекта узла {obj} = {hash_obj}") ================================================ FILE: ru/codes/python/chapter_hashing/hash_map.py ================================================ """ 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] = "Сяо Ха" hmap[15937] = "Сяо Ло" hmap[16750] = "Сяо Суань" hmap[13276] = "Сяо Фа" hmap[10583] = "Сяо Я" print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") print_dict(hmap) # Операция поиска # Передать ключ key в хеш-таблицу и получить значение value name: str = hmap[15937] print("\nДля номера 15937 найдено имя " + name) # Операция удаления # Удалить пару (key, value) из хеш-таблицы hmap.pop(10583) print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") print_dict(hmap) # Обход хеш-таблицы print("\nОтдельный обход пар ключ-значение") for key, value in hmap.items(): print(key, "->", value) print("\nОтдельный обход ключей") for key in hmap.keys(): print(key) print("\nОтдельный обход значений") for val in hmap.values(): print(val) ================================================ FILE: ru/codes/python/chapter_hashing/hash_map_chaining.py ================================================ """ 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] # Обойти корзину; если найден key, вернуть соответствующее val for pair in bucket: if pair.key == key: return pair.val # Если key не найден, вернуть None return None def put(self, key: int, val: str): """Операция добавления""" # Когда коэффициент загрузки превышает порог, выполнить расширение if self.load_factor() > self.load_thres: self.extend() index = self.hash_func(key) bucket = self.buckets[index] # Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть for pair in bucket: if pair.key == key: pair.val = val return # Если такого key нет, добавить пару ключ-значение в конец pair = Pair(key, val) bucket.append(pair) self.size += 1 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, "Сяо Ха") hashmap.put(15937, "Сяо Ло") hashmap.put(16750, "Сяо Суань") hashmap.put(13276, "Сяо Фа") hashmap.put(10583, "Сяо Я") print("\nПосле завершения добавления хеш-таблица имеет вид\n[Key1 -> Value1, Key2 -> Value2, ...]") hashmap.print() # Операция поиска # Передать ключ key в хеш-таблицу и получить значение value name = hashmap.get(13276) print("\nДля номера 13276 найдено имя " + name) # Операция удаления # Удалить пару (key, value) из хеш-таблицы hashmap.remove(12836) print("\nПосле удаления 12836 хеш-таблица имеет вид\n[Key1 -> Value1, Key2 -> Value2, ...]") hashmap.print() ================================================ FILE: ru/codes/python/chapter_hashing/hash_map_open_addressing.py ================================================ """ 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: # Если встретился key, вернуть соответствующий индекс корзины if self.buckets[index].key == key: # Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс if first_tombstone != -1: self.buckets[first_tombstone] = self.buckets[index] self.buckets[index] = self.TOMBSTONE return first_tombstone # Вернуть индекс корзины после перемещения return index # Вернуть индекс корзины # Записать первую встретившуюся метку удаления if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE: first_tombstone = index # Вычислить индекс корзины; при выходе за конец вернуться к началу index = (index + 1) % self.capacity # Если key не существует, вернуть индекс точки добавления return index if first_tombstone == -1 else first_tombstone def get(self, key: int) -> str: """Операция поиска""" # Найти индекс корзины, соответствующий key 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, "Сяо Ха") hashmap.put(15937, "Сяо Ло") hashmap.put(16750, "Сяо Суань") hashmap.put(13276, "Сяо Фа") hashmap.put(10583, "Сяо Я") print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") hashmap.print() # Операция поиска # Передать ключ key в хеш-таблицу и получить значение val name = hashmap.get(13276) print("\nДля номера 13276 найдено имя " + name) # Операция удаления # Удалить пару (key, val) из хеш-таблицы hashmap.remove(16750) print("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение") hashmap.print() ================================================ FILE: ru/codes/python/chapter_hashing/simple_hash.py ================================================ """ 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 Algo" 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}") ================================================ FILE: ru/codes/python/chapter_heap/heap.py ================================================ """ 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]) """Driver Code""" if __name__ == "__main__": # Инициализация минимальной кучи min_heap, flag = [], 1 # Инициализация максимальной кучи max_heap, flag = [], -1 print("\nНиже приведен тестовый пример для max-heap") # Модуль heapq в Python по умолчанию реализует минимальную кучу # Можно помещать в кучу отрицательные элементы, чтобы инвертировать отношение порядка и таким образом реализовать максимальную кучу # В этом примере 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После построения min-heap из входного списка") print_heap(min_heap) ================================================ FILE: ru/codes/python/chapter_heap/my_heap.py ================================================ """ 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 # Выполнить heapify для всех узлов, кроме листовых 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) # Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» if p < 0 or self.max_heap[i] <= self.max_heap[p]: break # Поменять два узла местами self.swap(i, p) # Циклическое просеивание вверх i = p def pop(self) -> int: """Извлечение элемента из кучи""" # Обработка пустого случая if self.is_empty(): raise IndexError("куча пуста") # Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) self.swap(0, self.size() - 1) # Удаление узла val = self.max_heap.pop() # Просеивание сверху вниз self.sift_down(0) # Вернуть элемент с вершины кучи return val def sift_down(self, i: int): """Начиная с узла i, выполнить просеивание сверху вниз""" 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 # Поменять два узла местами self.swap(i, ma) # Циклическое просеивание вниз i = ma def print(self): """Вывести кучу (двоичное дерево)""" print_heap(self.max_heap) """Driver Code""" if __name__ == "__main__": # Инициализация максимальной кучи max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) print("\nПосле построения кучи из входного списка") 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}") ================================================ FILE: ru/codes/python/chapter_heap/top_k.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [1, 7, 6, 3, 2] k = 3 res = top_k_heap(nums, k) print(f"Наибольшие {k} элементов") print_heap(res) ================================================ FILE: ru/codes/python/chapter_searching/binary_search.py ================================================ """ 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) // 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 указывают на первый элемент массива и позицию сразу за последним элементом соответственно i, j = 0, len(nums) # Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) while i < j: m = (i + j) // 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 """Driver Code""" if __name__ == "__main__": target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # Бинарный поиск (двусторонне замкнутый интервал) index = binary_search(nums, target) print("Индекс целевого элемента 6 = ", index) # Бинарный поиск (лево замкнутый, право открытый интервал) index = binary_search_lcro(nums, target) print("Индекс целевого элемента 6 = ", index) ================================================ FILE: ru/codes/python/chapter_searching/binary_search_edge.py ================================================ """ 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: """Бинарный поиск самого левого target""" # Эквивалентно поиску точки вставки target i = binary_search_insertion(nums, target) # target не найден, вернуть -1 if i == len(nums) or nums[i] != target: return -1 # Найти target и вернуть индекс i return i def binary_search_right_edge(nums: list[int], target: int) -> int: """Бинарный поиск самого правого target""" # Преобразовать задачу в поиск самого левого target + 1 i = binary_search_insertion(nums, target + 1) # j указывает на самый правый target, а i — на первый элемент больше target j = i - 1 # target не найден, вернуть -1 if j == -1 or nums[j] != target: return -1 # Найти target и вернуть индекс j return j """Driver Code""" if __name__ == "__main__": # Массив с повторяющимися элементами nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print(f"\nМассив nums = {nums}") # Бинарный поиск левой и правой границы for target in [6, 7]: index = binary_search_left_edge(nums, target) print(f"Индекс самого левого элемента {target} равен {index}") index = binary_search_right_edge(nums, target) print(f"Индекс самого правого элемента {target} равен {index}") ================================================ FILE: ru/codes/python/chapter_searching/binary_search_insertion.py ================================================ """ 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) // 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 # Найти target и вернуть точку вставки m # target не найден, вернуть точку вставки i return i def binary_search_insertion(nums: list[int], target: int) -> int: """Бинарный поиск точки вставки (с повторяющимися элементами)""" i, j = 0, len(nums) - 1 # Инициализировать двусторонне замкнутый интервал [0, n-1] while i <= j: m = (i + j) // 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: j = m - 1 # Первый элемент меньше target находится в интервале [i, m-1] # Вернуть точку вставки i return i """Driver Code""" if __name__ == "__main__": # Массив без повторяющихся элементов nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print(f"\nМассив nums = {nums}") # Бинарный поиск точки вставки for target in [6, 9]: index = binary_search_insertion_simple(nums, target) print(f"Индекс позиции вставки элемента {target} равен {index}") # Массив с повторяющимися элементами 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}") ================================================ FILE: ru/codes/python/chapter_searching/hashing_search.py ================================================ """ 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: """Хеш-поиск (массив)""" # key хеш-таблицы: целевой элемент, value: индекс # Если такого key нет в хеш-таблице, вернуть -1 return hmap.get(target, -1) def hashing_search_linkedlist( hmap: dict[int, ListNode], target: int ) -> ListNode | None: """Хеш-поиск (связный список)""" # key хеш-таблицы: целевой элемент, value: объект узла # Если такого key нет в хеш-таблице, вернуть None return hmap.get(target, None) """Driver Code""" 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 # key: элемент, value: индекс 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 # key: значение узла, value: узел head = head.next node: ListNode = hashing_search_linkedlist(map1, target) print("Объект узла со значением 3 =", node) ================================================ FILE: ru/codes/python/chapter_searching/linear_search.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": target = 3 # Выполнить линейный поиск в массиве nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] index: int = linear_search_array(nums, target) print("Индекс целевого элемента 3 =", index) # Выполнить линейный поиск в связном списке head: ListNode = list_to_linked_list(nums) node: ListNode | None = linear_search_linkedlist(head, target) print("Объект узла со значением 3 =", node) ================================================ FILE: ru/codes/python/chapter_searching/two_sum.py ================================================ """ 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]: """Метод 1: полный перебор""" # Два вложенных цикла, временная сложность 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]: """Метод 2: вспомогательная хеш-таблица""" # Вспомогательная хеш-таблица, пространственная сложность 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 [] """Driver Code""" if __name__ == "__main__": # ======= Test Case ======= nums = [2, 7, 11, 15] target = 13 # ====== Основной код ====== # Метод 1 res: list[int] = two_sum_brute_force(nums, target) print("Результат метода 1 res =", res) # Метод 2 res: list[int] = two_sum_hash_table(nums, target) print("Результат метода 2 res =", res) ================================================ FILE: ru/codes/python/chapter_sorting/bubble_sort.py ================================================ """ 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 # На этой итерации «всплытия» не было ни одного обмена, сразу выйти """Driver Code""" 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) ================================================ FILE: ru/codes/python/chapter_sorting/bucket_sort.py ================================================ """ 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) ================================================ FILE: ru/codes/python/chapter_sorting/counting_sort.py ================================================ """ File: counting_sort.py Created Time: 2023-03-21 Author: krahets (krahets@163.com) """ def counting_sort_naive(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 и заполнить исходный массив 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 — это индекс последнего появления num в res 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 # Перезаписать исходный массив nums массивом результата res for i in range(n): nums[i] = res[i] """Driver Code""" if __name__ == "__main__": nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort_naive(nums) print(f"После сортировки подсчетом (объекты не поддерживаются) nums = {nums}") nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort(nums1) print(f"После сортировки подсчетом nums1 = {nums1}") ================================================ FILE: ru/codes/python/chapter_sorting/heap_sort.py ================================================ """ 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 # Поменять два узла местами nums[i], nums[ma] = nums[ma], nums[i] # Циклическое просеивание вниз i = ma def heap_sort(nums: list[int]): """Сортировка кучей""" # Построение кучи: выполнить heapify для всех узлов, кроме листовых 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) """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] heap_sort(nums) print("После сортировки кучей nums =", nums) ================================================ FILE: ru/codes/python/chapter_sorting/insertion_sort.py ================================================ """ 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] на одну позицию вправо j -= 1 nums[j + 1] = base # Поместить base в правильную позицию """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] insertion_sort(nums) print("После сортировки вставками nums =", nums) ================================================ FILE: ru/codes/python/chapter_sorting/merge_sort.py ================================================ """ 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) // 2 # Вычислить середину merge_sort(nums, left, mid) # Рекурсивно обработать левый подмассив merge_sort(nums, mid + 1, right) # Рекурсивно обработать правый подмассив # Этап слияния merge(nums, left, mid, right) """Driver Code""" if __name__ == "__main__": nums = [7, 3, 2, 6, 0, 1, 5, 4] merge_sort(nums, 0, len(nums) - 1) print("После сортировки слиянием nums =", nums) ================================================ FILE: ru/codes/python/chapter_sorting/quick_sort.py ================================================ """ 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] # Переместить опорный элемент на границу двух подмассивов 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: """Выбрать медиану из трех кандидатов""" 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] # Переместить опорный элемент на границу двух подмассивов 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] # Переместить опорный элемент на границу двух подмассивов 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) # Выполнить быструю сортировку для более короткого из двух подмассивов 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] """Driver Code""" 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) ================================================ FILE: ru/codes/python/chapter_sorting/radix_sort.py ================================================ """ File: radix_sort.py Created Time: 2023-03-26 Author: krahets (krahets@163.com) """ def digit(num: int, exp: int) -> int: """Получить k-й разряд элемента num, где exp = 10^(k-1)""" # Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени return (num // exp) % 10 def counting_sort_digit(nums: list[int], exp: int): """Сортировка подсчетом (сортировка по k-му разряду nums)""" # Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 counter = [0] * 10 n = len(nums) # Подсчитать число появлений каждой цифры от 0 до 9 for i in range(n): d = digit(nums[i], exp) # Получить k-й разряд nums[i], обозначив его как 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 # Получить индекс j цифры d в массиве 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 """Driver Code""" if __name__ == "__main__": # Поразрядная сортировка nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ] radix_sort(nums) print("После поразрядной сортировки nums =", nums) ================================================ FILE: ru/codes/python/chapter_sorting/selection_sort.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] selection_sort(nums) print("После сортировки выбором nums =", nums) ================================================ FILE: ru/codes/python/chapter_stack_and_queue/array_deque.py ================================================ """ 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 # Указатель головы сдвигается на одну позицию влево # С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост 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() # Указатель головы сдвигается на одну позицию назад 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("двусторонняя очередь пуста") return self._nums[self._front] def peek_last(self) -> int: """Доступ к элементу в конце очереди""" if self.is_empty(): raise IndexError("двусторонняя очередь пуста") # Вычислить индекс хвостового элемента last = self.index(self._front + self._size - 1) return self._nums[last] def to_array(self) -> list[int]: """Вернуть массив для вывода""" # Преобразовывать только элементы списка в пределах фактической длины res = [] for i in range(self._size): res.append(self._nums[self.index(self._front + i)]) 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) ================================================ FILE: ru/codes/python/chapter_stack_and_queue/array_queue.py ================================================ """ 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 # Указатель head, указывающий на первый элемент очереди 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("очередь заполнена") # Вычислить указатель хвоста, указывающий на индекс хвоста + 1 # С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива rear: int = (self._front + self._size) % self.capacity() # Добавить num в хвост очереди self._nums[rear] = num self._size += 1 def pop(self) -> int: """Извлечь из очереди""" num: int = self.peek() # Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива self._front = (self._front + 1) % self.capacity() self._size -= 1 return num def peek(self) -> int: """Доступ к элементу в начале очереди""" if self.is_empty(): raise IndexError("очередь пуста") return self._nums[self._front] def to_list(self) -> list[int]: """Вернуть список для вывода""" res = [0] * self.size() j: int = self._front for i in range(self.size()): 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 после извлечения =", 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, "-го раунда операций enqueue и dequeue queue =", queue.to_list()) ================================================ FILE: ru/codes/python/chapter_stack_and_queue/array_stack.py ================================================ """ 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("стек пуст") return self._stack.pop() def peek(self) -> int: """Доступ к верхнему элементу стека""" if self.is_empty(): raise IndexError("стек пуст") 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 после извлечения =", stack.to_list()) # Получение длины стека size: int = stack.size() print("Длина стека size =", size) # Проверка на пустоту is_empty: bool = stack.is_empty() print("Пуст ли стек =", is_empty) ================================================ FILE: ru/codes/python/chapter_stack_and_queue/deque.py ================================================ """ 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("deque после извлечения из головы =", deq) pop_rear: int = deq.pop() # Извлечь элемент из хвоста очереди print("Извлеченный из хвоста элемент pop_rear =", pop_rear) print("deque после извлечения из хвоста =", deq) # Получение длины двусторонней очереди size: int = len(deq) print("Длина двусторонней очереди size =", size) # Проверка, пуста ли двусторонняя очередь is_empty: bool = len(deq) == 0 print("Пуста ли двусторонняя очередь =", is_empty) ================================================ FILE: ru/codes/python/chapter_stack_and_queue/linkedlist_deque.py ================================================ """ 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: # Добавить node в голову списка self._front.prev = node node.next = self._front self._front = node # Обновить головной узел # Операция добавления в хвост очереди else: # Добавить node в хвост списка self._rear.next = node node.prev = self._rear self._rear = node # Обновить хвостовой узел self._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("двусторонняя очередь пуста") # Операция извлечения из головы очереди 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("двусторонняя очередь пуста") return self._front.val def peek_last(self) -> int: """Доступ к элементу в конце очереди""" if self.is_empty(): raise IndexError("двусторонняя очередь пуста") 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) ================================================ FILE: ru/codes/python/chapter_stack_and_queue/linkedlist_queue.py ================================================ """ 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) # Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел 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("очередь пуста") 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 после извлечения =", queue.to_list()) # Получение длины очереди size: int = queue.size() print("Длина очереди size =", size) # Проверка, пуста ли очередь is_empty: bool = queue.is_empty() print("Пуста ли очередь =", is_empty) ================================================ FILE: ru/codes/python/chapter_stack_and_queue/linkedlist_stack.py ================================================ """ 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("стек пуст") 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 после извлечения =", stack.to_list()) # Получение длины стека size: int = stack.size() print("Длина стека size =", size) # Проверка на пустоту is_empty: bool = stack.is_empty() print("Пуст ли стек =", is_empty) ================================================ FILE: ru/codes/python/chapter_stack_and_queue/queue.py ================================================ """ 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 после извлечения =", que) # Получение длины очереди size: int = len(que) print("Длина очереди size =", size) # Проверка, пуста ли очередь is_empty: bool = len(que) == 0 print("Пуста ли очередь =", is_empty) ================================================ FILE: ru/codes/python/chapter_stack_and_queue/stack.py ================================================ """ File: stack.py Created Time: 2022-11-29 Author: Peng Chen (pengchzn@gmail.com) """ """Driver Code""" if __name__ == "__main__": # Инициализировать стек # В Python нет встроенного класса стека, поэтому list можно использовать как стек 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 после извлечения =", stack) # Получение длины стека size: int = len(stack) print("Длина стека size =", size) # Проверка на пустоту is_empty: bool = len(stack) == 0 print("Пуст ли стек =", is_empty) ================================================ FILE: ru/codes/python/chapter_tree/array_binary_tree.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # Инициализировать двоичное дерево # Здесь используется функция, напрямую строящая двоичное дерево из массива arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] root = list_to_tree(arr) print("\nИнициализация двоичного дерева\n") print("Массивное представление двоичного дерева:") print(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) ================================================ FILE: ru/codes/python/chapter_tree/avl_tree.py ================================================ """ 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 # Выполнить правое вращение узла node вокруг child 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 # Выполнить левое вращение узла node вокруг child 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, удалить node и сразу вернуть if child is None: return None # Число дочерних узлов = 1, удалить node напрямую 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 """Driver Code""" 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)) ================================================ FILE: ru/codes/python/chapter_tree/binary_search_tree.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # Инициализация двоичного дерева поиска bst = BinarySearchTree() nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] # Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево for num in nums: bst.insert(num) print("\nИсходное двоичное дерево\n") print_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()) ================================================ FILE: ru/codes/python/chapter_tree/binary_tree.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # Инициализация двоичного дерева # Инициализация узлов n1 = TreeNode(val=1) n2 = TreeNode(val=2) n3 = TreeNode(val=3) n4 = TreeNode(val=4) n5 = TreeNode(val=5) # Построить связи между узлами (указатели) n1.left = n2 n1.right = n3 n2.left = n4 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) ================================================ FILE: ru/codes/python/chapter_tree/binary_tree_bfs.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # Инициализировать двоичное дерево # Здесь используется функция, напрямую строящая двоичное дерево из массива root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\nИнициализация двоичного дерева\n") print_tree(root) # Обход в ширину res: list[int] = level_order(root) print("\nПоследовательность печати узлов при обходе в ширину = ", res) ================================================ FILE: ru/codes/python/chapter_tree/binary_tree_dfs.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": # Инициализировать двоичное дерево # Здесь используется функция, напрямую строящая двоичное дерево из массива root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\nИнициализация двоичного дерева\n") print_tree(root) # Предварительный обход res = [] pre_order(root) print("\nПоследовательность печати узлов при предварительном обходе = ", res) # Симметричный обход res.clear() in_order(root) print("\nПоследовательность печати узлов при симметричном обходе = ", res) # Обратный обход res.clear() post_order(root) print("\nПоследовательность печати узлов при обратном обходе = ", res) ================================================ FILE: ru/codes/python/modules/__init__.py ================================================ # Follow the PEP 585 - Type Hinting Generics In Standard Collections # https://peps.python.org/pep-0585/ from __future__ import annotations # Import common libs here to simplify the code by `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, ) ================================================ FILE: ru/codes/python/modules/list_node.py ================================================ """ 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 ================================================ FILE: ru/codes/python/modules/print_util.py ================================================ """ 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) ================================================ FILE: ru/codes/python/modules/tree_node.py ================================================ """ 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 ================================================ FILE: ru/codes/python/modules/vertex.py ================================================ # 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] ================================================ FILE: ru/codes/python/test_all.py ================================================ import os import glob import subprocess env = os.environ.copy() env["PYTHONIOENCODING"] = "utf-8" if __name__ == "__main__": # find source code files src_paths = sorted(glob.glob("chapter_*/*.py")) errors = [] # run python code 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' ) # Wait for the process to complete, and get the output and error messages stdout, stderr = process.communicate() # Check the exit status exit_status = process.returncode if exit_status != 0: errors.append(stderr) print(f"Tested {len(src_paths)} files") print(f"Found exception in {len(errors)} files") if len(errors) > 0: raise RuntimeError("\n\n".join(errors)) ================================================ FILE: ru/codes/pythontutor/chapter_array_and_linkedlist/array.md ================================================ https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_access%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%BC%20%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%BC%20%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%B8%D0%B7%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B0%20%5B0%2C%20len%28nums%29-1%5D%0A%20%20%20%20random_index%20%3D%20random.randint%280%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B8%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%0A%20%20%20%20random_num%20%3D%20nums%5Brandom_index%5D%0A%20%20%20%20return%20random_num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%D0%A1%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%0A%20%20%20%20random_num%3A%20int%20%3D%20random_access%28nums%29%0A%20%20%20%20print%28%22%D0%A1%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20nums%20%3D%22%2C%20random_num%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20insert%28nums%3A%20list%5Bint%5D%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20num%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%20index%20%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%B4%D0%B2%D0%B8%D0%BD%D1%83%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%81%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D0%BE%D0%BC%20index%20%D0%B8%20%D0%B2%D1%81%D0%B5%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BD%D0%B0%20%D0%BE%D0%B4%D0%BD%D1%83%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%20%D0%BD%D0%B0%D0%B7%D0%B0%D0%B4%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%20index%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20-%201%5D%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B8%D1%81%D0%B2%D0%BE%D0%B8%D1%82%D1%8C%20num%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%20index%0A%20%20%20%20nums%5Bindex%5D%20%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20insert%28nums%2C%206%2C%203%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%206%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%203%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20remove%28nums%3A%20list%5Bint%5D%2C%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%20index%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%B4%D0%B2%D0%B8%D0%BD%D1%83%D1%82%D1%8C%20%D0%B2%D1%81%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D0%B0%20index%20%D0%BD%D0%B0%20%D0%BE%D0%B4%D0%BD%D1%83%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%20%D0%B2%D0%BF%D0%B5%D1%80%D0%B5%D0%B4%0A%20%20%20%20for%20i%20in%20range%28index%2C%20len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20%2B%201%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20remove%28nums%2C%202%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%202%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20traverse%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%9E%D0%B1%D1%85%D0%BE%D0%B4%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%D0%9E%D0%B1%D1%85%D0%BE%D0%B4%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D0%B0%D0%BC%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%D0%9D%D0%B5%D0%BF%D0%BE%D1%81%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%20%20%20%20%23%20%D0%9E%D0%B4%D0%BD%D0%BE%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%8B%20%D0%B8%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%0A%20%20%20%20for%20i%2C%20num%20in%20enumerate%28nums%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%D0%9E%D0%B1%D1%85%D0%BE%D0%B4%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20traverse%28nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20find%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B5%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20index%3A%20int%20%3D%20find%28nums%2C%203%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%203%20%D0%B2%20nums%3A%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%3D%22%2C%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=%23%20%D0%9E%D0%B1%D1%80%D0%B0%D1%82%D0%B8%D1%82%D0%B5%20%D0%B2%D0%BD%D0%B8%D0%BC%D0%B0%D0%BD%D0%B8%D0%B5%3A%20list%20%D0%B2%20Python%20%E2%80%94%20%D1%8D%D1%82%D0%BE%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%2C%20%D0%B5%D0%B3%D0%BE%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D1%80%D0%B0%D1%81%D1%88%D0%B8%D1%80%D1%8F%D1%82%D1%8C%20%D0%BD%D0%B0%D0%BF%D1%80%D1%8F%D0%BC%D1%83%D1%8E%0A%23%20%D0%94%D0%BB%D1%8F%20%D1%83%D0%B4%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B0%20%D0%BE%D0%B1%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%8D%D1%82%D0%BE%D0%B9%20%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8%20list%20%D1%80%D0%B0%D1%81%D1%81%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D0%B2%D0%B0%D0%B5%D1%82%D1%81%D1%8F%20%D0%BA%D0%B0%D0%BA%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D0%BD%D0%B5%D0%B8%D0%B7%D0%BC%D0%B5%D0%BD%D1%8F%D0%B5%D0%BC%D0%BE%D0%B9%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%0Adef%20extend%28nums%3A%20list%5Bint%5D%2C%20enlarge%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%D0%A3%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%83%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20%28len%28nums%29%20%2B%20enlarge%29%0A%20%20%20%20%23%20%D0%A1%D0%BA%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B2%D1%81%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%B8%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B2%20%D0%BD%D0%BE%D0%B2%D1%8B%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%BD%D0%BE%D0%B2%D1%8B%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D1%80%D0%B0%D1%81%D1%88%D0%B8%D1%80%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%D0%A0%D0%B0%D1%81%D1%88%D0%B8%D1%80%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%0A%20%20%20%20nums%20%3D%20extend%28nums%2C%203%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%83%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B4%D0%BE%208%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%83%D0%B7%D0%B5%D0%BB-%D0%BF%D1%80%D0%B5%D0%B5%D0%BC%D0%BD%D0%B8%D0%BA%0A%0Adef%20insert%28n0%3A%20ListNode%2C%20P%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%20P%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%20n0%20%D0%B2%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%BC%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B5%22%22%22%0A%20%20%20%20n1%20%3D%20n0.next%0A%20%20%20%20P.next%20%3D%20n1%0A%20%20%20%20n0.next%20%3D%20P%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B2%D1%81%D0%B5%D1%85%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20p%20%3D%20ListNode%280%29%0A%20%20%20%20insert%28n0%2C%20p%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%83%D0%B7%D0%B5%D0%BB-%D0%BF%D1%80%D0%B5%D0%B5%D0%BC%D0%BD%D0%B8%D0%BA%0A%0Adef%20remove%28n0%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%20n0%20%D0%B2%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%BC%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B5%22%22%22%0A%20%20%20%20if%20not%20n0.next%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20n0%20-%3E%20P%20-%3E%20n1%0A%20%20%20%20P%20%3D%20n0.next%0A%20%20%20%20n1%20%3D%20P.next%0A%20%20%20%20n0.next%20%3D%20n1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B2%D1%81%D0%B5%D1%85%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20remove%28n0%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%83%D0%B7%D0%B5%D0%BB-%D0%BF%D1%80%D0%B5%D0%B5%D0%BC%D0%BD%D0%B8%D0%BA%0A%0Adef%20access%28head%3A%20ListNode%2C%20index%3A%20int%29%20-%3E%20ListNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%83%D0%B7%D0%BB%D1%83%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%20index%22%22%22%0A%20%20%20%20for%20_%20in%20range%28index%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20head%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20return%20head%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B2%D1%81%D0%B5%D1%85%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%83%D0%B7%D0%BB%D1%83%0A%20%20%20%20node%20%3D%20access%28n0%2C%203%29%0A%20%20%20%20print%28%22%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%203%20%D0%B2%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%BC%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B5%20%3D%20%7B%7D%22.format%28node.val%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%83%D0%B7%D0%B5%D0%BB-%D0%BF%D1%80%D0%B5%D0%B5%D0%BC%D0%BD%D0%B8%D0%BA%0A%0Adef%20find%28head%3A%20ListNode%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%B2%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%BC%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%20%D1%81%D0%BE%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%20target%22%22%22%0A%20%20%20%20index%20%3D%200%0A%20%20%20%20while%20head%3A%0A%20%20%20%20%20%20%20%20if%20head.val%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20index%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20%20%20%20%20index%20%2B%3D%201%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B2%D1%81%D0%B5%D1%85%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20index%20%3D%20find%28n0%2C%202%29%0A%20%20%20%20print%28%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D1%81%D0%BE%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%202%20%D0%B2%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%BC%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B5%20%3D%20%7B%7D%22.format%28index%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_array_and_linkedlist/my_list.md ================================================ https://pythontutor.com/render.html#code=class%20MyList%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._capacity%3A%20int%20%3D%2010%0A%20%20%20%20%20%20%20%20self._arr%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20%2A%20self._capacity%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%20%20%20%20%20%20%20%20self._extend_ratio%3A%20int%20%3D%202%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._capacity%0A%0A%20%20%20%20def%20get%28self%2C%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%27%29%0A%20%20%20%20%20%20%20%20return%20self._arr%5Bindex%5D%0A%0A%20%20%20%20def%20set%28self%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%27%29%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%0A%20%20%20%20def%20add%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self.size%28%29%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20self._arr%5Bself._size%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%27%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28self._size%20-%201%2C%20index%20-%201%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%20%2B%201%5D%20%3D%20self._arr%5Bj%5D%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self%2C%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%27%29%0A%20%20%20%20%20%20%20%20num%20%3D%20self._arr%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28index%2C%20self._size%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%5D%20%3D%20self._arr%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20extend_capacity%28self%29%3A%0A%20%20%20%20%20%20%20%20self._arr%20%3D%20self._arr%20%2B%20%5B0%5D%20%2A%20self.capacity%28%29%20%2A%20%28self._extend_ratio%20-%201%29%0A%20%20%20%20%20%20%20%20self._capacity%20%3D%20len%28self._arr%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20nums%20%3D%20MyList%28%29%0A%20%20%20%20nums.add%281%29%0A%20%20%20%20nums.add%283%29%0A%20%20%20%20nums.add%282%29%0A%20%20%20%20nums.add%285%29%0A%20%20%20%20nums.add%284%29%0A%20%20%20%20nums.insert%286%2C%20index%3D3%29%0A%20%20%20%20nums.remove%283%29%0A%20%20%20%20num%20%3D%20nums.get%281%29%0A%20%20%20%20nums.set%280%2C%201%29%0A%20%20%20%20for%20i%20in%20range%2810%29%3A%0A%20%20%20%20%20%20%20%20nums.add%28i%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_backtracking/n_queens.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28row%3A%20int%2C%20n%3A%20int%2C%20state%3A%20list%5Blist%5Bstr%5D%5D%2C%20res%3A%20list%5Blist%5Blist%5Bstr%5D%5D%5D%2C%20cols%3A%20list%5Bbool%5D%2C%20diags1%3A%20list%5Bbool%5D%2C%20diags2%3A%20list%5Bbool%5D%29%3A%0A%20%20%20%20if%20row%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res.append%28%5Blist%28row%29%20for%20row%20in%20state%5D%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20for%20col%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20diag1%20%3D%20row%20-%20col%20%2B%20n%20-%201%0A%20%20%20%20%20%20%20%20diag2%20%3D%20row%20%2B%20col%0A%20%20%20%20%20%20%20%20if%20not%20cols%5Bcol%5D%20and%20%28not%20diags1%5Bdiag1%5D%29%20and%20%28not%20diags2%5Bdiag2%5D%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%27Q%27%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28row%20%2B%201%2C%20n%2C%20state%2C%20res%2C%20cols%2C%20diags1%2C%20diags2%29%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%27%23%27%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20False%0A%0Adef%20n_queens%28n%3A%20int%29%20-%3E%20list%5Blist%5Blist%5Bstr%5D%5D%5D%3A%0A%20%20%20%20state%20%3D%20%5B%5B%27%23%27%20for%20_%20in%20range%28n%29%5D%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20cols%20%3D%20%5BFalse%5D%20%2A%20n%0A%20%20%20%20diags1%20%3D%20%5BFalse%5D%20%2A%20%282%20%2A%20n%20-%201%29%0A%20%20%20%20diags2%20%3D%20%5BFalse%5D%20%2A%20%282%20%2A%20n%20-%201%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%280%2C%20n%2C%20state%2C%20res%2C%20cols%2C%20diags1%2C%20diags2%29%0A%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20res%20%3D%20n_queens%28n%29%0A%20%20%20%20print%28f%27%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%B4%D0%BE%D1%81%D0%BA%D0%B8%20%3D%20%7Bn%7D%27%29%0A%20%20%20%20print%28f%27%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20%D1%84%D0%B5%D1%80%D0%B7%D0%B5%D0%B9%3A%20%7Blen%28res%29%7D%27%29%0A%20%20%20%20for%20state%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%27--------------------%27%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20state%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28row%29&cumulative=false&curInstr=61&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_backtracking/permutations_i.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%20%D0%B1%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%D0%B0%3A%20%D0%B2%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20I%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D0%B0%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20%D1%87%D0%B8%D1%81%D0%BB%D1%83%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%2C%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%20%D0%B2%D1%81%D0%B5%D1%85%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%B0%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%3A%20%D0%BD%D0%B5%D0%BB%D1%8C%D0%B7%D1%8F%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B4%D0%B8%D0%BD%20%D0%B8%20%D1%82%D0%BE%D1%82%20%D0%B6%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%BE%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%3A%20%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B9%D1%82%D0%B8%20%D0%BA%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D1%83%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%3A%20%D0%BE%D1%82%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%B2%D0%BE%D1%81%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D1%8B%D0%B4%D1%83%D1%89%D0%B5%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_i%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20I%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%202%2C%203%5D%0A%0A%20%20%20%20res%20%3D%20permutations_i%28nums%29%0A%0A%20%20%20%20print%28f%22%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_backtracking/permutations_ii.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%20%D0%B1%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%D0%B0%3A%20%D0%B2%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20II%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D0%B0%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20%D1%87%D0%B8%D1%81%D0%BB%D1%83%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%2C%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%20%D0%B2%D1%81%D0%B5%D1%85%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%B0%0A%20%20%20%20duplicated%20%3D%20set%5Bint%5D%28%29%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%3A%20%D0%BD%D0%B5%D0%BB%D1%8C%D0%B7%D1%8F%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B4%D0%B8%D0%BD%20%D0%B8%20%D1%82%D0%BE%D1%82%20%D0%B6%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%BE%20%D0%B8%20%D0%BD%D0%B5%D0%BB%D1%8C%D0%B7%D1%8F%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%BE%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%20%D1%80%D0%B0%D0%B2%D0%BD%D1%8B%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%20and%20choice%20not%20in%20duplicated%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%3A%20%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20%20%20%20%20duplicated.add%28choice%29%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%83%D0%B6%D0%B5%20%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B9%D1%82%D0%B8%20%D0%BA%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D1%83%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%3A%20%D0%BE%D1%82%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%B2%D0%BE%D1%81%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D1%8B%D0%B4%D1%83%D1%89%D0%B5%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_ii%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20II%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%202%2C%202%5D%0A%0A%20%20%20%20res%20%3D%20permutations_ii%28nums%29%0A%0A%20%20%20%20print%28f%22%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B5%D1%81%D0%B5%D1%80%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B8%D0%BB%D0%B8%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20None%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%BE%20%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B5%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D1%8C%D1%8F%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B5%D1%81%D0%B5%D1%80%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%3A%20%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%201%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20res.append%28root%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%0A%20%20%20%20res%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%D0%92%D1%81%D0%B5%20%D1%83%D0%B7%D0%BB%D1%8B%20%D1%81%D0%BE%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%207%22%29%0A%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20res%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B5%D1%81%D0%B5%D1%80%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B8%D0%BB%D0%B8%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20None%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%BE%20%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B5%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D1%8C%D1%8F%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B5%D1%81%D0%B5%D1%80%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%3A%20%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%202%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%D0%92%D1%81%D0%B5%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%BE%D1%82%20%D0%BA%D0%BE%D1%80%D0%BD%D1%8F%20%D0%BA%20%D1%83%D0%B7%D0%BB%D1%83%207%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B5%D1%81%D0%B5%D1%80%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B8%D0%BB%D0%B8%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20None%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%BE%20%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B5%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D1%8C%D1%8F%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B5%D1%81%D0%B5%D1%80%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%3A%20%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%203%22%22%22%0A%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20if%20root%20is%20None%20or%20root.val%20%3D%3D%203%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%D0%92%D1%81%D0%B5%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%BE%D1%82%20%D0%BA%D0%BE%D1%80%D0%BD%D1%8F%20%D0%BA%20%D1%83%D0%B7%D0%BB%D1%83%207%2C%20%D0%BD%D0%B5%20%D1%81%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D1%89%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D1%81%D0%BE%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%203%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%3D0%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0Adef%20is_solution%28state%3A%20list%5BTreeNode%5D%29%20-%3E%20bool%3A%0A%20%20%20%20return%20state%20and%20state%5B-1%5D.val%20%3D%3D%207%0A%0Adef%20record_solution%28state%3A%20list%5BTreeNode%5D%2C%20res%3A%20list%5Blist%5BTreeNode%5D%5D%29%3A%0A%20%20%20%20res.append%28list%28state%29%29%0A%0Adef%20is_valid%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%20-%3E%20bool%3A%0A%20%20%20%20return%20choice%20is%20not%20None%20and%20choice.val%20%21%3D%203%0A%0Adef%20make_choice%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20state.append%28choice%29%0A%0Adef%20undo_choice%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20state.pop%28%29%0A%0Adef%20backtrack%28state%3A%20list%5BTreeNode%5D%2C%20choices%3A%20list%5BTreeNode%5D%2C%20res%3A%20list%5Blist%5BTreeNode%5D%5D%29%3A%0A%20%20%20%20if%20is_solution%28state%29%3A%0A%20%20%20%20%20%20%20%20record_solution%28state%2C%20res%29%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20if%20is_valid%28state%2C%20choice%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20make_choice%28state%2C%20choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20%5Bchoice.left%2C%20choice.right%5D%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20undo_choice%28state%2C%20choice%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3D%5Broot%5D%2C%20res%3Dres%29%0A%20%20%20%20print%28%27%5Cn%D0%92%D1%8B%D0%B2%D0%B5%D1%81%D1%82%D0%B8%20%D0%B2%D1%81%D0%B5%20%D0%BF%D1%83%D1%82%D0%B8%27%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=138&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_backtracking/subset_sum_i.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20choices%3A%20list%5Bint%5D%2C%20start%3A%20int%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%20%D0%B1%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%D0%B0%3A%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%20I%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20target%2C%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%20%D0%B2%D1%81%D0%B5%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D1%8B%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%B0%0A%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%202%3A%20%D0%BD%D0%B0%D1%87%D0%B8%D0%BD%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%20%D1%81%20start%2C%20%D1%87%D1%82%D0%BE%D0%B1%D1%8B%20%D0%B8%D0%B7%D0%B1%D0%B5%D0%B6%D0%B0%D1%82%D1%8C%20%D0%B3%D0%B5%D0%BD%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D1%85%D1%81%D1%8F%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%0A%20%20%20%20for%20i%20in%20range%28start%2C%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%201%3A%20%D0%B5%D1%81%D0%BB%D0%B8%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B0%D0%B5%D1%82%20target%2C%20%D0%BD%D0%B5%D0%BC%D0%B5%D0%B4%D0%BB%D0%B5%D0%BD%D0%BD%D0%BE%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D1%82%D1%8C%20%D1%86%D0%B8%D0%BA%D0%BB%0A%20%20%20%20%20%20%20%20%23%20%D0%AD%D1%82%D0%BE%20%D1%81%D0%B2%D1%8F%D0%B7%D0%B0%D0%BD%D0%BE%20%D1%81%20%D1%82%D0%B5%D0%BC%2C%20%D1%87%D1%82%D0%BE%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%83%D0%B6%D0%B5%20%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%2C%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%2C%20%D0%B8%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D1%82%D0%BE%D1%87%D0%BD%D0%BE%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%81%D0%B8%D1%82%20target%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%3A%20%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20target%20%D0%B8%20start%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B9%D1%82%D0%B8%20%D0%BA%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D1%83%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%20-%20choices%5Bi%5D%2C%20choices%2C%20i%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%3A%20%D0%BE%D1%82%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%B2%D0%BE%D1%81%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D1%8B%D0%B4%D1%83%D1%89%D0%B5%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D1%88%D0%B8%D1%82%D1%8C%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D1%83%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%20I%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%D0%A1%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%20%28%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%BE%29%0A%20%20%20%20nums.sort%28%29%20%20%23%20%D0%9E%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20nums%0A%20%20%20%20start%20%3D%200%20%20%23%20%D0%A1%D1%82%D0%B0%D1%80%D1%82%D0%BE%D0%B2%D0%B0%D1%8F%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B0%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B0%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%BE%D0%B2%20%28%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%29%0A%20%20%20%20backtrack%28state%2C%20target%2C%20nums%2C%20start%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D1%81%20%D1%81%D1%83%D0%BC%D0%BC%D0%BE%D0%B9%20%7Btarget%7D%3A%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%0A%20%20%20%20target%3A%20int%2C%0A%20%20%20%20total%3A%20int%2C%0A%20%20%20%20choices%3A%20list%5Bint%5D%2C%0A%20%20%20%20res%3A%20list%5Blist%5Bint%5D%5D%2C%0A%29%3A%0A%20%20%20%20%22%22%22%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%20%D0%B1%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%D0%B0%3A%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%20I%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20target%2C%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20if%20total%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%20%D0%B2%D1%81%D0%B5%D1%85%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%B0%0A%20%20%20%20for%20i%20in%20range%28len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%3A%20%D0%B5%D1%81%D0%BB%D0%B8%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B0%D0%B5%D1%82%20target%2C%20%D0%BF%D1%80%D0%BE%D0%BF%D1%83%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%8D%D1%82%D0%BE%D1%82%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%0A%20%20%20%20%20%20%20%20if%20total%20%2B%20choices%5Bi%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%3A%20%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%20total%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B9%D1%82%D0%B8%20%D0%BA%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D1%83%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%2C%20total%20%2B%20choices%5Bi%5D%2C%20choices%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%3A%20%D0%BE%D1%82%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%B2%D0%BE%D1%81%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D1%8B%D0%B4%D1%83%D1%89%D0%B5%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i_naive%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D1%88%D0%B8%D1%82%D1%8C%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D1%83%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%20I%20%28%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%D0%BC%D0%B8%29%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%D0%A1%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%20%28%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%BE%29%0A%20%20%20%20total%20%3D%200%20%20%23%20%D0%A1%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%BE%D0%B2%20%28%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%29%0A%20%20%20%20backtrack%28state%2C%20target%2C%20total%2C%20nums%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i_naive%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D1%81%20%D1%81%D1%83%D0%BC%D0%BC%D0%BE%D0%B9%20%7Btarget%7D%3A%20res%20%3D%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%D0%9E%D0%B1%D1%80%D0%B0%D1%82%D0%B8%D1%82%D0%B5%20%D0%B2%D0%BD%D0%B8%D0%BC%D0%B0%D0%BD%D0%B8%D0%B5%3A%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%8D%D1%82%D0%BE%D0%B3%D0%BE%20%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%B0%20%D1%81%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D1%82%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%B5%D1%81%D1%8F%20%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_backtracking/subset_sum_ii.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28state%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20choices%3A%20list%5Bint%5D%2C%20start%3A%20int%2C%20res%3A%20list%5Blist%5Bint%5D%5D%29%3A%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20for%20i%20in%20range%28start%2C%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20if%20i%20%3E%20start%20and%20choices%5Bi%5D%20%3D%3D%20choices%5Bi%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%20-%20choices%5Bi%5D%2C%20choices%2C%20i%20%2B%201%2C%20res%29%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0Adef%20subset_sum_ii%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20state%20%3D%20%5B%5D%0A%20%20%20%20nums.sort%28%29%0A%20%20%20%20start%20%3D%200%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%2C%20target%2C%20nums%2C%20start%2C%20res%29%0A%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_ii%28nums%2C%20target%29%0A%20%20%20%20print%28f%27%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%27%29%0A%20%20%20%20print%28f%27%D0%92%D1%81%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D1%81%20%D1%81%D1%83%D0%BC%D0%BC%D0%BE%D0%B9%20%7Btarget%7D%3A%20res%20%3D%20%7Bres%7D%27%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_computational_complexity/iteration.md ================================================ https://pythontutor.com/render.html#code=def%20for_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A6%D0%B8%D0%BA%D0%BB%20for%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20for%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D& https://pythontutor.com/render.html#code=def%20while_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A6%D0%B8%D0%BA%D0%BB%20while%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BD%D0%BE%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BD%D1%83%D1%8E%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%83%D1%8E%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20while%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20while_loop_ii%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A6%D0%B8%D0%BA%D0%BB%20while%20%28%D0%B4%D0%B2%D0%BE%D0%B9%D0%BD%D0%BE%D0%B5%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%29%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BD%D0%BE%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%201%2C%204%2C%2010%2C%20...%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BD%D1%83%D1%8E%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%83%D1%8E%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20i%20%2A%3D%202%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop_ii%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20while%20%28%D0%B4%D0%B2%D0%BE%D0%B9%D0%BD%D0%BE%D0%B5%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%29%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20nested_for_loop%28n%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B2%D0%BE%D0%B9%D0%BD%D0%BE%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%20for%22%22%22%0A%20%20%20%20res%20%3D%20%22%22%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%20%D0%BF%D0%BE%20i%20%3D%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%20%D0%BF%D0%BE%20j%20%3D%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20f%22%28%7Bi%7D%2C%20%7Bj%7D%29%2C%20%22%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20nested_for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B0%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B9%D0%BD%D0%BE%D0%BC%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20for%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%D0%92%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%82%3A%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%BE%D0%B9%20%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20tail_recur%28n%2C%20res%29%3A%0A%20%20%20%20%22%22%22%D0%A5%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%B0%D1%8F%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%D0%A5%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%0A%20%20%20%20return%20tail_recur%28n%20-%201%2C%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n%2C%200%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%3A%20f%281%29%20%3D%200%2C%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A7%D0%BB%D0%B5%D0%BD%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8%20%D1%81%20%D0%BD%D0%BE%D0%BC%D0%B5%D1%80%D0%BE%D0%BC%20%7Bn%7D%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%98%D0%BC%D0%B8%D1%82%D0%B0%D1%86%D0%B8%D1%8F%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B8%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20%D0%98%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%8F%D0%B2%D0%BD%D1%8B%D0%B9%20%D1%81%D1%82%D0%B5%D0%BA%20%D0%B4%D0%BB%D1%8F%20%D0%B8%D0%BC%D0%B8%D1%82%D0%B0%D1%86%D0%B8%D0%B8%20%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%D0%BE%D0%B2%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%0A%20%20%20%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D0%BC%D0%B8%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%C2%AB%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8E%C2%BB%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%20%D0%BF%D0%BE%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%D0%92%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%82%3A%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D0%BC%D0%B8%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%C2%AB%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%82%C2%BB%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%BF%D1%80%D0%B8%20%D0%B8%D0%BC%D0%B8%D1%82%D0%B0%D1%86%D0%B8%D0%B8%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_computational_complexity/recursion.md ================================================ https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%D0%92%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%82%3A%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%BE%D0%B9%20%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20tail_recur%28n%2C%20res%29%3A%0A%20%20%20%20%22%22%22%D0%A5%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%B0%D1%8F%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%D0%A5%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%0A%20%20%20%20return%20tail_recur%28n%20-%201%2C%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n%2C%200%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%3A%20f%281%29%20%3D%200%2C%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A7%D0%BB%D0%B5%D0%BD%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8%20%D1%81%20%D0%BD%D0%BE%D0%BC%D0%B5%D1%80%D0%BE%D0%BC%20%7Bn%7D%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%98%D0%BC%D0%B8%D1%82%D0%B0%D1%86%D0%B8%D1%8F%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B8%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20%D0%98%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%8F%D0%B2%D0%BD%D1%8B%D0%B9%20%D1%81%D1%82%D0%B5%D0%BA%20%D0%B4%D0%BB%D1%8F%20%D0%B8%D0%BC%D0%B8%D1%82%D0%B0%D1%86%D0%B8%D0%B8%20%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%D0%BE%D0%B2%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%0A%20%20%20%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D0%BC%D0%B8%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%C2%AB%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8E%C2%BB%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%20%D0%BF%D0%BE%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%D0%92%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%82%3A%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D0%BC%D0%B8%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%C2%AB%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%82%C2%BB%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%BF%D1%80%D0%B8%20%D0%B8%D0%BC%D0%B8%D1%82%D0%B0%D1%86%D0%B8%D0%B8%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_computational_complexity/space_complexity.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%83%D0%B7%D0%B5%D0%BB-%D0%BF%D1%80%D0%B5%D0%B5%D0%BC%D0%BD%D0%B8%D0%BA%0A%0Adef%20function%28%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%92%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D1%8C%20%D0%BD%D0%B5%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D1%8B%D0%B5%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%0A%20%20%20%20return%200%0A%0Adef%20constant%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%BD%D1%81%D1%82%D0%B0%D0%BD%D1%82%D1%8B%2C%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5%20%D0%B8%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D1%8B%20%D0%B7%D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D1%8E%D1%82%20O%281%29%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20a%20%3D%200%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%2010%0A%20%20%20%20node%20%3D%20ListNode%280%29%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20%D0%B7%D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D1%8E%D1%82%20O%281%29%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20c%20%3D%200%0A%20%20%20%20%23%20%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20%D0%B7%D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D1%8E%D1%82%20O%281%29%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20function%28%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20constant%28n%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%20n%20%D0%B7%D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D0%B5%D1%82%20O%28n%29%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%20n%20%D0%B7%D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D0%B5%D1%82%20O%28n%29%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20hmap%20%3D%20dict%5Bint%2C%20str%5D%28%29%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20hmap%5Bi%5D%20%3D%20str%28i%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20linear%28n%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear_recur%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20print%28%22%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%20n%20%3D%22%2C%20n%29%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20linear_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20linear_recur%28n%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20quadratic%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20%23%20%D0%94%D0%B2%D1%83%D0%BC%D0%B5%D1%80%D0%BD%D1%8B%D0%B9%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B7%D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D0%B5%D1%82%20O%28n%5E2%29%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20num_matrix%20%3D%20%5B%5B0%5D%20%2A%20n%20for%20_%20in%20range%28n%29%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%D0%9A%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20quadratic%28n%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20quadratic_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20nums%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20n%2C%20n-1%2C%20...%2C%202%2C%201%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20return%20quadratic_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%D0%9A%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20quadratic_recur%28n%29&cumulative=false&curInstr=28&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0Adef%20build_tree%28n%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%AD%D0%BA%D1%81%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%BE%D0%BB%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%29%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%280%29%0A%20%20%20%20root.left%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20root.right%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20return%20root%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%D0%AD%D0%BA%D1%81%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20root%20%3D%20build_tree%28n%29&cumulative=false&curInstr=507&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_computational_complexity/time_complexity.md ================================================ https://pythontutor.com/render.html#code=def%20constant%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20size%20%3D%2010%0A%20%20%20%20for%20_%20in%20range%28size%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20constant%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BA%D0%BE%D0%BD%D1%81%D1%82%D0%B0%D0%BD%D1%82%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20linear%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20array_traversal%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%29%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BF%D1%80%D0%BE%D0%BF%D0%BE%D1%80%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%B4%D0%BB%D0%B8%D0%BD%D0%B5%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20array_traversal%28%5B0%5D%20%2A%20n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20quadratic%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%BE%20%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D1%82%20%D0%BE%D1%82%20%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%D0%B0%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20quadratic%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D0%BF%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%B0%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%29%22%22%22%0A%20%20%20%20count%20%3D%200%20%20%23%20%D0%A1%D1%87%D0%B5%D1%82%D1%87%D0%B8%D0%BA%0A%20%20%20%20%23%20%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D0%BD%D1%83%D1%82%D1%80%D0%B5%D0%BD%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B0%20%5B0%2C%20i%5D%20%D0%B2%20%D0%B5%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BA%D0%BE%D0%BD%D0%B5%D1%86%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%20nums%5Bj%5D%20%D0%B8%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%20%3D%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%203%20%20%23%20%D0%9E%D0%B1%D0%BC%D0%B5%D0%BD%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20%D0%B2%D0%BA%D0%BB%D1%8E%D1%87%D0%B0%D0%B5%D1%82%203%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%BD%D1%8B%D0%B5%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%5D%20%20%23%20%5Bn%2C%20n-1%2C%20...%2C%202%2C%201%5D%0A%20%20%20%20count%20%3D%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D0%BF%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%B0%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20exponential%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%AD%D0%BA%D1%81%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20base%20%3D%201%0A%20%20%20%20%23%20%D0%9D%D0%B0%20%D0%BA%D0%B0%D0%B6%D0%B4%D0%BE%D0%BC%20%D1%88%D0%B0%D0%B3%D0%B5%20%D0%BA%D0%BB%D0%B5%D1%82%D0%BA%D0%B0%20%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%81%D1%8F%20%D0%BD%D0%B0%D0%B4%D0%B2%D0%BE%D0%B5%2C%20%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D1%83%D1%8F%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%201%2C%202%2C%204%2C%208%2C%20...%2C%202%5E%28n-1%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28base%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%20%20%20%20base%20%2A%3D%202%0A%20%20%20%20%23%20count%20%3D%201%20%2B%202%20%2B%204%20%2B%208%20%2B%20..%20%2B%202%5E%28n-1%29%20%3D%202%5En%20-%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20exponential%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D1%8D%D0%BA%D1%81%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20exp_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%AD%D0%BA%D1%81%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20return%20exp_recur%28n%20-%201%29%20%2B%20exp_recur%28n%20-%201%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%207%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20exp_recur%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D1%8D%D0%BA%D1%81%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20logarithmic%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20while%20n%20%3E%201%3A%0A%20%20%20%20%20%20%20%20n%20%3D%20n%20%2F%202%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20logarithmic%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BB%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20return%20log_recur%28n%20%2F%202%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20log_recur%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BB%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear_log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%BE-%D0%BB%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%20%2F%2F%202%29%20%2B%20linear_log_recur%28n%20%2F%2F%202%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%BE-%D0%BB%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20factorial_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A4%D0%B0%D0%BA%D1%82%D0%BE%D1%80%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%D0%98%D0%B7%20%D0%BE%D0%B4%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B0%D0%B5%D1%82%D1%81%D1%8F%20n%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20factorial_recur%28n%20-%201%29%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20factorial_recur%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D1%84%D0%B0%D0%BA%D1%82%D0%BE%D1%80%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md ================================================ https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_numbers%28n%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%B3%D0%B5%D0%BD%D0%B5%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%81%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%201%2C%202%2C%20...%2C%20n%20%D0%B2%20%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D0%BE%D0%BC%20%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B5%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%BE%D0%B7%D0%B4%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%3A%201%2C%202%2C%203%2C%20...%2C%20n%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%A1%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D0%BE%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%88%D0%B0%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20random.shuffle%28nums%29%0A%20%20%20%20return%20nums%0A%0Adef%20find_one%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%201%20%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B5%20nums%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%201%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D0%B5%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%2C%20%D0%B4%D0%BE%D1%81%D1%82%D0%B8%D0%B3%D0%B0%D0%B5%D1%82%D1%81%D1%8F%20%D0%BB%D1%83%D1%87%D1%88%D0%B0%D1%8F%20%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20O%281%29%0A%20%20%20%20%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%201%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%BA%D0%BE%D0%BD%D1%86%D0%B5%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%2C%20%D0%B4%D0%BE%D1%81%D1%82%D0%B8%D0%B3%D0%B0%D0%B5%D1%82%D1%81%D1%8F%20%D1%85%D1%83%D0%B4%D1%88%D0%B0%D1%8F%20%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20O%28n%29%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2010%0A%20%20%20%20nums%20%3D%20random_numbers%28n%29%0A%20%20%20%20index%20%3D%20find_one%28nums%29%0A%20%20%20%20print%28%22%5Cn%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%5B1%2C%202%2C%20...%2C%20n%5D%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%88%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%3D%22%2C%20nums%29%0A%20%20%20%20print%28%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%201%20%3D%22%2C%20index%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md ================================================ https://pythontutor.com/render.html#code=def%20dfs%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20i%3A%20int%2C%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%3A%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%20f%28i%2C%20j%29%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%D0%BF%D1%83%D1%81%D1%82%2C%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BE%D1%82%D1%81%D1%83%D1%82%D1%81%D1%82%D0%B2%D1%83%D0%B5%D1%82%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%20%20%20%20if%20i%20%3E%20j%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%0A%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%20f%28m%2B1%2C%20j%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums%2C%20target%2C%20m%20%2B%201%2C%20j%29%0A%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%20f%28i%2C%20m-1%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums%2C%20target%2C%20i%2C%20m%20-%201%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%A6%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B5%D0%B3%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%0A%20%20%20%20%20%20%20%20return%20m%0A%0Adef%20binary_search%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%D0%A0%D0%B5%D1%88%D0%B8%D1%82%D1%8C%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D1%83%20f%280%2C%20n-1%29%0A%20%20%20%20return%20dfs%28nums%2C%20target%2C%200%2C%20n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%28%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%29%0A%20%20%20%20index%20%3D%20binary_search%28nums%2C%20target%29%0A%20%20%20%20print%28%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%206%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_divide_and_conquer/build_tree.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0Adef%20dfs%28%0A%20%20%20%20preorder%3A%20list%5Bint%5D%2C%0A%20%20%20%20inorder_map%3A%20dict%5Bint%2C%20int%5D%2C%0A%20%20%20%20i%3A%20int%2C%0A%20%20%20%20l%3A%20int%2C%0A%20%20%20%20r%3A%20int%2C%0A%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%3A%20%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9%20%D0%B8%20%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9%22%22%22%0A%20%20%20%20%23%20%D0%97%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B8%20%D0%BF%D1%83%D1%81%D1%82%D0%BE%D0%BC%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%0A%20%20%20%20if%20r%20-%20l%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BA%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20root%20%3D%20TreeNode%28preorder%5Bi%5D%29%0A%20%20%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20m%2C%20%D1%87%D1%82%D0%BE%D0%B1%D1%8B%20%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B5%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D1%8C%D1%8F%0A%20%20%20%20m%20%3D%20inorder_map%5Bpreorder%5Bi%5D%5D%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%3A%20%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%0A%20%20%20%20root.left%20%3D%20dfs%28preorder%2C%20inorder_map%2C%20i%20%2B%201%2C%20l%2C%20m%20-%201%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%3A%20%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%0A%20%20%20%20root.right%20%3D%20dfs%28preorder%2C%20inorder_map%2C%20i%20%2B%201%20%2B%20m%20-%20l%2C%20m%20%2B%201%2C%20r%29%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%BA%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20return%20root%0A%0A%0Adef%20build_tree%28preorder%3A%20list%5Bint%5D%2C%20inorder%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%20%D0%B4%D0%BB%D1%8F%20%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B8%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20inorder%20%D0%B8%D1%85%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D0%B0%D0%BC%0A%20%20%20%20inorder_map%20%3D%20%7Bval%3A%20i%20for%20i%2C%20val%20in%20enumerate%28inorder%29%7D%0A%20%20%20%20root%20%3D%20dfs%28preorder%2C%20inorder_map%2C%200%2C%200%2C%20len%28inorder%29%20-%201%29%0A%20%20%20%20return%20root%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20preorder%20%3D%20%5B3%2C%209%2C%202%2C%201%2C%207%5D%0A%20%20%20%20inorder%20%3D%20%5B9%2C%203%2C%201%2C%202%2C%207%5D%0A%20%20%20%20print%28f%22%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%20%3D%20%7Bpreorder%7D%22%29%0A%20%20%20%20print%28f%22%D0%A1%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%20%3D%20%7Binorder%7D%22%29%0A%20%20%20%20root%20%3D%20build_tree%28preorder%2C%20inorder%29&cumulative=false&curInstr=21&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_divide_and_conquer/hanota.md ================================================ https://pythontutor.com/render.html#code=def%20move%28src%3A%20list%5Bint%5D%2C%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BE%D0%B4%D0%B8%D0%BD%20%D0%B4%D0%B8%D1%81%D0%BA%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%BD%D1%8F%D1%82%D1%8C%20%D0%B4%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D1%8B%20src%0A%20%20%20%20pan%20%3D%20src.pop%28%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%20%D0%B4%D0%B8%D1%81%D0%BA%20%D0%BD%D0%B0%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D1%83%20tar%0A%20%20%20%20tar.append%28pan%29%0A%0A%0Adef%20dfs%28i%3A%20int%2C%20src%3A%20list%5Bint%5D%2C%20buf%3A%20list%5Bint%5D%2C%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D1%88%D0%B8%D1%82%D1%8C%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D1%83%20%D0%A5%D0%B0%D0%BD%D0%BE%D0%B9%D1%81%D0%BA%D0%BE%D0%B9%20%D0%B1%D0%B0%D1%88%D0%BD%D0%B8%20f%28i%29%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%20src%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%81%D1%8F%20%D1%82%D0%BE%D0%BB%D1%8C%D0%BA%D0%BE%20%D0%BE%D0%B4%D0%B8%D0%BD%20%D0%B4%D0%B8%D1%81%D0%BA%2C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B5%D0%B3%D0%BE%20%D0%B2%20tar%0A%20%20%20%20if%20i%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20move%28src%2C%20tar%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%20f%28i-1%29%3A%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B5%20i-1%20%D0%B4%D0%B8%D1%81%D0%BA%D0%BE%D0%B2%20%D0%B8%D0%B7%20src%20%D0%B2%20buf%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20tar%0A%20%20%20%20dfs%28i%20-%201%2C%20src%2C%20tar%2C%20buf%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%20f%281%29%3A%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BE%D1%81%D1%82%D0%B0%D0%B2%D1%88%D0%B8%D0%B9%D1%81%D1%8F%20%D0%BE%D0%B4%D0%B8%D0%BD%20%D0%B4%D0%B8%D1%81%D0%BA%20%D0%B8%D0%B7%20src%20%D0%B2%20tar%0A%20%20%20%20move%28src%2C%20tar%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%20f%28i-1%29%3A%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B5%20i-1%20%D0%B4%D0%B8%D1%81%D0%BA%D0%BE%D0%B2%20%D0%B8%D0%B7%20buf%20%D0%B2%20tar%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20src%0A%20%20%20%20dfs%28i%20-%201%2C%20buf%2C%20src%2C%20tar%29%0A%0A%0Adef%20solve_hanota%28A%3A%20list%5Bint%5D%2C%20B%3A%20list%5Bint%5D%2C%20C%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D1%88%D0%B8%D1%82%D1%8C%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D1%83%20%D0%A5%D0%B0%D0%BD%D0%BE%D0%B9%D1%81%D0%BA%D0%BE%D0%B9%20%D0%B1%D0%B0%D1%88%D0%BD%D0%B8%22%22%22%0A%20%20%20%20n%20%3D%20len%28A%29%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B5%20n%20%D0%B4%D0%B8%D1%81%D0%BA%D0%BE%D0%B2%20%D0%B8%D0%B7%20A%20%D0%B2%20C%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20B%0A%20%20%20%20dfs%28n%2C%20A%2C%20B%2C%20C%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%A5%D0%B2%D0%BE%D1%81%D1%82%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D0%B5%D1%82%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B5%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D0%B0%0A%20%20%20%20A%20%3D%20%5B5%2C%204%2C%203%2C%202%2C%201%5D%0A%20%20%20%20B%20%3D%20%5B%5D%0A%20%20%20%20C%20%3D%20%5B%5D%0A%20%20%20%20print%28%22%D0%98%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29%0A%0A%20%20%20%20solve_hanota%28A%2C%20B%2C%20C%29%0A%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B4%D0%B8%D1%81%D0%BA%D0%BE%D0%B2%3A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28choices%3A%20list%5Bint%5D%2C%20state%3A%20int%2C%20n%3A%20int%2C%20res%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%B4%D0%BE%D1%81%D1%82%D0%B8%D0%B3%D0%B0%D0%B5%D1%82%20n-%D0%B9%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B8%2C%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%20%D1%83%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B8%D0%B2%D0%B0%D0%B5%D1%82%D1%81%D1%8F%20%D0%BD%D0%B0%201%0A%20%20%20%20if%20state%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%5B0%5D%20%2B%3D%201%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%20%D0%B2%D1%81%D0%B5%D1%85%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%B0%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%3A%20%D0%BD%D0%B5%D0%BB%D1%8C%D0%B7%D1%8F%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%8C%20%D0%B7%D0%B0%20n-%D1%8E%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D1%8C%0A%20%20%20%20%20%20%20%20if%20state%20%2B%20choice%20%3E%20n%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%3A%20%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20backtrack%28choices%2C%20state%20%2B%20choice%2C%20n%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%0A%0A%0Adef%20climbing_stairs_backtrack%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%B1%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%22%22%22%0A%20%20%20%20choices%20%3D%20%5B1%2C%202%5D%20%20%23%20%D0%9C%D0%BE%D0%B6%D0%BD%D0%BE%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BD%D0%B0%201%20%D0%B8%D0%BB%D0%B8%202%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B8%0A%20%20%20%20state%20%3D%200%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B0%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D1%81%200-%D0%B9%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B8%0A%20%20%20%20res%20%3D%20%5B0%5D%20%20%23%20%D0%98%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20res%5B0%5D%20%D0%B4%D0%BB%D1%8F%20%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%0A%20%20%20%20backtrack%28choices%2C%20state%2C%20n%2C%20res%29%0A%20%20%20%20return%20res%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_backtrack%28n%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D0%B8%D0%B7%20%7Bn%7D%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md ================================================ https://pythontutor.com/render.html#code=def%20climbing_stairs_constraint_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D1%81%20%D0%BE%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%D0%BC%D0%B8%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%20%D0%B4%D0%BB%D1%8F%20%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%203%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%20%D0%B7%D0%B0%D1%80%D0%B0%D0%BD%D0%B5%D0%B5%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%BD%D0%B0%D0%B8%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D1%85%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%0A%20%20%20%20dp%5B1%5D%5B1%5D%2C%20dp%5B1%5D%5B2%5D%20%3D%201%2C%200%0A%20%20%20%20dp%5B2%5D%5B1%5D%2C%20dp%5B2%5D%5B2%5D%20%3D%200%2C%201%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%BE%D1%81%D1%82%D0%B5%D0%BF%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D1%85%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%20%D1%87%D0%B5%D1%80%D0%B5%D0%B7%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D0%B5%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B1%5D%20%3D%20dp%5Bi%20-%201%5D%5B2%5D%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B2%5D%20%3D%20dp%5Bi%20-%202%5D%5B1%5D%20%2B%20dp%5Bi%20-%202%5D%5B2%5D%0A%20%20%20%20return%20dp%5Bn%5D%5B1%5D%20%2B%20dp%5Bn%5D%5B2%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_constraint_dp%28n%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D0%B8%D0%B7%20%7Bn%7D%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md ================================================ https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%22%22%22%0A%20%20%20%20%23%20dp%5B1%5D%20%D0%B8%20dp%5B2%5D%20%D1%83%D0%B6%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%B5%D1%81%D1%82%D0%BD%D1%8B%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D1%85%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%29%20%2B%20dfs%28i%20-%202%29%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%22%22%22%0A%20%20%20%20return%20dfs%28n%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs%28n%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D0%B8%D0%B7%20%7Bn%7D%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md ================================================ https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%2C%20mem%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%BC%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20dp%5B1%5D%20%D0%B8%20dp%5B2%5D%20%D1%83%D0%B6%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%B5%D1%81%D1%82%D0%BD%D1%8B%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D1%85%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D1%8C%20dp%5Bi%5D%20%D1%81%D1%83%D1%89%D0%B5%D1%81%D1%82%D0%B2%D1%83%D0%B5%D1%82%2C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B5%D0%B5%0A%20%20%20%20if%20mem%5Bi%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%2C%20mem%29%20%2B%20dfs%28i%20-%202%2C%20mem%29%0A%20%20%20%20%23%20%D0%A1%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C%20dp%5Bi%5D%0A%20%20%20%20mem%5Bi%5D%20%3D%20count%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs_mem%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%BC%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20mem%5Bi%5D%20%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BD%D0%B0%20i-%D1%8E%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D1%8C%2C%20-1%20%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%20%D0%BE%D1%82%D1%81%D1%83%D1%82%D1%81%D1%82%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8%0A%20%20%20%20mem%20%3D%20%5B-1%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20return%20dfs%28n%2C%20mem%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs_mem%28n%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D0%B8%D0%B7%20%7Bn%7D%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md ================================================ https://pythontutor.com/render.html#code=def%20climbing_stairs_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%20%D0%B4%D0%BB%D1%8F%20%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%20%D0%B7%D0%B0%D1%80%D0%B0%D0%BD%D0%B5%D0%B5%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%BD%D0%B0%D0%B8%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D1%85%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%0A%20%20%20%20dp%5B1%5D%2C%20dp%5B2%5D%20%3D%201%2C%202%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%BE%D1%81%D1%82%D0%B5%D0%BF%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D1%85%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%20%D1%87%D0%B5%D1%80%D0%B5%D0%B7%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D0%B5%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20dp%5Bi%20-%201%5D%20%2B%20dp%5Bi%20-%202%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp%28n%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D0%B8%D0%B7%20%7Bn%7D%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20climbing_stairs_dp_comp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20a%2C%20b%20%3D%201%2C%202%0A%20%20%20%20for%20_%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a%2C%20b%20%3D%20b%2C%20a%20%2B%20b%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp_comp%28n%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D0%B8%D0%B7%20%7Bn%7D%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_dynamic_programming/coin_change.md ================================================ https://pythontutor.com/render.html#code=def%20coin_change_dp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D0%BD%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D0%B0%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%20%D0%B8%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D0%B5%D1%86%0A%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Ba%5D%20%3D%20MAX%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%D0%B8%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D1%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9C%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%D0%B5%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20min%28dp%5Bi%20-%201%5D%5Ba%5D%2C%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%20if%20dp%5Bn%5D%5Bamt%5D%20%21%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20coin_change_dp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20coin_change_dp_comp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D0%BD%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5BMAX%5D%20%2A%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%200%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D1%80%D1%8F%D0%BC%D0%BE%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9C%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%D0%B5%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20min%28dp%5Ba%5D%2C%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bamt%5D%20if%20dp%5Bamt%5D%20%21%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20res%20%3D%20coin_change_dp_comp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md ================================================ https://pythontutor.com/render.html#code=def%20coin_change_ii_dp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D0%BD%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20II%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D0%B0%0A%20%20%20%20for%20i%20in%20range%28n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%A1%D1%83%D0%BC%D0%BC%D0%B0%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%20%2B%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20coin_change_ii_dp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D0%BA%D0%BE%D0%BC%D0%B1%D0%B8%D0%BD%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20coin_change_ii_dp_comp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D0%BD%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20II%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D1%80%D1%8F%D0%BC%D0%BE%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%A1%D1%83%D0%BC%D0%BC%D0%B0%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%20%2B%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20res%20%3D%20coin_change_ii_dp_comp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D0%BA%D0%BE%D0%BC%D0%B1%D0%B8%D0%BD%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_dynamic_programming/edit_distance.md ================================================ https://pythontutor.com/render.html#code=def%20edit_distance_dp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D0%B4%D0%B0%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B5%20%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28m%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D0%B0%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%20%D0%B8%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D0%B5%D1%86%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20i%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%D0%B8%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D1%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B4%D0%B2%D0%B0%20%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D0%B0%20%D1%80%D0%B0%D0%B2%D0%BD%D1%8B%2C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%BF%D1%80%D0%BE%D0%BF%D1%83%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B8%D1%85%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%20%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%3D%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%20%D0%B4%D0%BB%D1%8F%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%2C%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%20%D0%B7%D0%B0%D0%BC%D0%B5%D0%BD%D1%8B%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%2C%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%29%20%2B%201%0A%20%20%20%20return%20dp%5Bn%5D%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20edit_distance_dp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%D0%A7%D1%82%D0%BE%D0%B1%D1%8B%20%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%7Bs%7D%20%D0%B2%20%7Bt%7D%2C%20%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D1%83%D0%BC%20%7Bres%7D%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20edit_distance_dp_comp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D0%B4%D0%B0%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B5%20%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D0%B0%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D0%B5%D1%86%0A%20%20%20%20%20%20%20%20leftup%20%3D%20dp%5B0%5D%20%20%23%20%D0%92%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%20%D1%81%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C%20dp%5Bi-1%2C%20j-1%5D%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D1%8B%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20dp%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B4%D0%B2%D0%B0%20%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D0%B0%20%D1%80%D0%B0%D0%B2%D0%BD%D1%8B%2C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%BF%D1%80%D0%BE%D0%BF%D1%83%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B8%D1%85%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20leftup%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%20%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%3D%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%20%D0%B4%D0%BB%D1%8F%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%2C%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%20%D0%B7%D0%B0%D0%BC%D0%B5%D0%BD%D1%8B%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D%2C%20dp%5Bj%5D%2C%20leftup%29%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20leftup%20%3D%20temp%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BE%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20dp%5Bi-1%2C%20j-1%5D%20%D0%B4%D0%BB%D1%8F%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%B9%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%0A%20%20%20%20return%20dp%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20res%20%3D%20edit_distance_dp_comp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%D0%A7%D1%82%D0%BE%D0%B1%D1%8B%20%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%7Bs%7D%20%D0%B2%20%7Bt%7D%2C%20%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D1%83%D0%BC%20%7Bres%7D%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_dynamic_programming/knapsack.md ================================================ https://pythontutor.com/render.html#code=def%20knapsack_dfs%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20i%3A%20int%2C%20c%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%200-1%3A%20%D0%BF%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D1%81%D0%B5%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D1%8B%20%D1%83%D0%B6%D0%B5%20%D1%80%D0%B0%D1%81%D1%81%D0%BC%D0%BE%D1%82%D1%80%D0%B5%D0%BD%D1%8B%20%D0%B8%D0%BB%D0%B8%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B5%20%D0%BD%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D0%BE%D1%81%D1%8C%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D1%82%D0%BE%D0%BB%D1%8C%D0%BA%D0%BE%20%D0%BD%D0%B5%20%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%B4%D0%BB%D1%8F%20%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B5%D0%B2%2C%20%D0%BA%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%20%D0%BD%D0%B5%20%D0%BA%D0%BB%D0%B0%D0%B4%D1%83%D1%82%20%D0%B8%20%D0%BA%D0%BB%D0%B0%D0%B4%D1%83%D1%82%0A%20%20%20%20no%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%20%D1%81%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B9%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%D1%8E%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D0%B2%D0%BE%D0%B7%D0%BC%D0%BE%D0%B6%D0%BD%D1%8B%D1%85%0A%20%20%20%20return%20max%28no%2C%20yes%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%0A%20%20%20%20res%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20n%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20knapsack_dfs_mem%28%0A%20%20%20%20wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20mem%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20c%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%200-1%3A%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%BC%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D1%81%D0%B5%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D1%8B%20%D1%83%D0%B6%D0%B5%20%D1%80%D0%B0%D1%81%D1%81%D0%BC%D0%BE%D1%82%D1%80%D0%B5%D0%BD%D1%8B%20%D0%B8%D0%BB%D0%B8%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B5%20%D0%BD%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D0%BE%D1%81%D1%8C%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D1%8C%20%D1%83%D0%B6%D0%B5%20%D0%B5%D1%81%D1%82%D1%8C%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%0A%20%20%20%20if%20mem%5Bi%5D%5Bc%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D1%82%D0%BE%D0%BB%D1%8C%D0%BA%D0%BE%20%D0%BD%D0%B5%20%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%B4%D0%BB%D1%8F%20%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B5%D0%B2%2C%20%D0%BA%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%20%D0%BD%D0%B5%20%D0%BA%D0%BB%D0%B0%D0%B4%D1%83%D1%82%20%D0%B8%20%D0%BA%D0%BB%D0%B0%D0%B4%D1%83%D1%82%0A%20%20%20%20no%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%D0%A1%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B8%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%20%D1%81%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B9%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%D1%8E%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%0A%20%20%20%20mem%5Bi%5D%5Bc%5D%20%3D%20max%28no%2C%20yes%29%0A%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%BC%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20res%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20n%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20knapsack_dp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%200-1%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%91%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B5%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D%2C%20dp%5Bi%20-%201%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20knapsack_dp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20knapsack_dp_comp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%200-1%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D1%85%D0%BE%D0%B4%20%D0%B2%20%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%BC%20%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B5%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%28cap%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%91%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B5%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D%2C%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20res%20%3D%20knapsack_dp_comp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md ================================================ https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%D0%B0%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%20%D0%B4%D0%BB%D1%8F%20%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%20%D0%B7%D0%B0%D1%80%D0%B0%D0%BD%D0%B5%D0%B5%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%BD%D0%B0%D0%B8%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D1%85%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%0A%20%20%20%20dp%5B1%5D%2C%20dp%5B2%5D%20%3D%20cost%5B1%5D%2C%20cost%5B2%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%BE%D1%81%D1%82%D0%B5%D0%BF%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D1%85%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%20%D1%87%D0%B5%D1%80%D0%B5%D0%B7%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D0%B5%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20min%28dp%5Bi%20-%201%5D%2C%20dp%5Bi%20-%202%5D%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0%2C%201%2C%2010%2C%201%2C%201%2C%201%2C%2010%2C%201%2C%201%2C%2010%2C%201%5D%0A%20%20%20%20print%28f%22%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B5%D0%B9%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp%28cost%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%D0%B0%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp_comp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%D0%B0%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20a%2C%20b%20%3D%20cost%5B1%5D%2C%20cost%5B2%5D%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a%2C%20b%20%3D%20b%2C%20min%28a%2C%20b%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0%2C%201%2C%2010%2C%201%2C%201%2C%201%2C%2010%2C%201%2C%201%2C%2010%2C%201%5D%0A%20%20%20%20print%28f%22%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B5%D0%B9%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp_comp%28cost%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%D0%B0%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md ================================================ https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs%28grid%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%3A%20%D0%BF%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%8D%D1%82%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D1%8F%D1%8F%20%D0%BB%D0%B5%D0%B2%D0%B0%D1%8F%20%D1%8F%D1%87%D0%B5%D0%B9%D0%BA%D0%B0%2C%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%8B%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%D0%B8%D0%BB%D0%B8%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D0%B0%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D1%8F%D1%82%20%D0%B7%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%2B%E2%88%9E%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B4%D0%BE%20%28i-1%2C%20j%29%20%D0%B8%20%28i%2C%20j-1%29%0A%20%20%20%20up%20%3D%20min_path_sum_dfs%28grid%2C%20i%20-%201%2C%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs%28grid%2C%20i%2C%20j%20-%201%29%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B4%D0%BE%20%28i%2C%20j%29%0A%20%20%20%20return%20min%28left%2C%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%0A%20%20%20%20res%20%3D%20min_path_sum_dfs%28grid%2C%20n%20-%201%2C%20m%20-%201%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BD%D0%B8%D0%B6%D0%BD%D0%B8%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs_mem%28%0A%20%20%20%20grid%3A%20list%5Blist%5Bint%5D%5D%2C%20mem%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20j%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%3A%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%BC%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%8D%D1%82%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D1%8F%D1%8F%20%D0%BB%D0%B5%D0%B2%D0%B0%D1%8F%20%D1%8F%D1%87%D0%B5%D0%B9%D0%BA%D0%B0%2C%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%8B%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%D0%B8%D0%BB%D0%B8%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D0%B0%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D1%8F%D1%82%20%D0%B7%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%2B%E2%88%9E%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D1%8C%20%D1%83%D0%B6%D0%B5%20%D0%B5%D1%81%D1%82%D1%8C%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%0A%20%20%20%20if%20mem%5Bi%5D%5Bj%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%20%20%20%20%23%20%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B4%D0%BB%D1%8F%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D0%B8%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B9%20%D1%8F%D1%87%D0%B5%D0%B5%D0%BA%0A%20%20%20%20up%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20i%20-%201%2C%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20i%2C%20j%20-%201%29%0A%20%20%20%20%23%20%D0%A1%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B8%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B4%D0%BE%20%28i%2C%20j%29%0A%20%20%20%20mem%5Bi%5D%5Bj%5D%20%3D%20min%28left%2C%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%23%20%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%BC%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20%2A%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20res%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20n%20-%201%2C%20m%20-%201%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BD%D0%B8%D0%B6%D0%BD%D0%B8%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20dp%5B0%5D%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D0%B0%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20dp%5B0%5D%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D0%B5%D1%86%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20dp%5Bi%20-%201%5D%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%D0%B8%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D1%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bn%20-%201%5D%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20min_path_sum_dp%28grid%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BD%D0%B8%D0%B6%D0%BD%D0%B8%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp_comp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20m%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D0%B0%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%0A%20%20%20%20dp%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20dp%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D0%B5%D1%86%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%3D%20dp%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D1%8B%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D%2C%20dp%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20res%20%3D%20min_path_sum_dp_comp%28grid%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BD%D0%B8%D0%B6%D0%BD%D0%B8%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md ================================================ https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%91%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B5%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D%2C%20dp%5Bi%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20val%20%3D%20%5B5%2C%2011%2C%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp_comp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D1%80%D1%8F%D0%BC%D0%BE%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%91%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B5%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D%2C%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20val%20%3D%20%5B5%2C%2011%2C%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp_comp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_graph/graph_adjacency_list.md ================================================ https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%27Vertex%27%5D%3A%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.adj_list%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20remove_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.remove%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.remove%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20remove_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet%20not%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list.pop%28vet%29%0A%20%20%20%20%20%20%20%20for%20vertex%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%5Bvertex%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.adj_list%5Bvertex%5D.remove%28vet%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B1%2C%203%2C%202%2C%205%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%20%5Bv%5B2%5D%2C%20v%5B3%5D%5D%2C%20%5Bv%5B2%5D%2C%20v%5B4%5D%5D%2C%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%20%20%20%20graph.add_edge%28v%5B0%5D%2C%20v%5B2%5D%29%0A%20%20%20%20graph.remove_edge%28v%5B0%5D%2C%20v%5B1%5D%29%0A%20%20%20%20v5%20%3D%20Vertex%286%29%0A%20%20%20%20graph.add_vertex%28v5%29%0A%20%20%20%20graph.remove_vertex%28v%5B1%5D%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md ================================================ https://pythontutor.com/render.html#code=class%20GraphAdjMat%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20vertices%3A%20list%5Bint%5D%2C%20edges%3A%20list%5Blist%5Bint%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20self.vertices%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.adj_mat%3A%20list%5Blist%5Bint%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20val%20in%20vertices%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28val%29%0A%20%20%20%20%20%20%20%20for%20e%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28e%5B0%5D%2C%20e%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.vertices%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20n%20%3D%20self.size%28%29%0A%20%20%20%20%20%20%20%20self.vertices.append%28val%29%0A%20%20%20%20%20%20%20%20new_row%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20%20%20%20%20self.adj_mat.append%28new_row%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.append%280%29%0A%0A%20%20%20%20def%20remove_vertex%28self%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.vertices.pop%28index%29%0A%20%20%20%20%20%20%20%20self.adj_mat.pop%28index%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.pop%28index%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20%28j%20%3E%3D%20self.size%28%29%29%20or%20%28i%20%3D%3D%20j%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%201%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%201%0A%0A%20%20%20%20def%20remove_edge%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20%28j%20%3E%3D%20self.size%28%29%29%20or%20%28i%20%3D%3D%20j%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%200%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%200%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20vertices%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20edges%20%3D%20%5B%5B0%2C%201%5D%2C%20%5B0%2C%203%5D%2C%20%5B1%2C%202%5D%2C%20%5B2%2C%203%5D%2C%20%5B2%2C%204%5D%2C%20%5B3%2C%204%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjMat%28vertices%2C%20edges%29%0A%20%20%20%20graph.add_edge%280%2C%202%29%0A%20%20%20%20graph.remove_edge%280%2C%201%29%0A%20%20%20%20graph.add_vertex%286%29%0A%20%20%20%20graph.remove_vertex%281%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_graph/graph_bfs.md ================================================ https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20Vertex%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%27Vertex%27%5D%3A%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0Adef%20graph_bfs%28graph%3A%20GraphAdjList%2C%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20que%20%3D%20deque%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20while%20len%28que%29%20%3E%200%3A%0A%20%20%20%20%20%20%20%20vet%20%3D%20que.popleft%28%29%0A%20%20%20%20%20%20%20%20res.append%28vet%29%0A%20%20%20%20%20%20%20%20for%20adj_vet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20adj_vet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20que.append%28adj_vet%29%0A%20%20%20%20%20%20%20%20%20%20%20%20visited.add%28adj_vet%29%0A%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0%2C%201%2C%202%2C%203%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%20%5Bv%5B1%5D%2C%20v%5B4%5D%5D%2C%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%20%20%20%20res%20%3D%20graph_bfs%28graph%2C%20v%5B0%5D%29&cumulative=false&curInstr=131&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_graph/graph_dfs.md ================================================ https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%27Vertex%27%5D%3A%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0Adef%20dfs%28graph%3A%20GraphAdjList%2C%20visited%3A%20set%5BVertex%5D%2C%20res%3A%20list%5BVertex%5D%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20res.append%28vet%29%0A%20%20%20%20visited.add%28vet%29%0A%20%20%20%20for%20adjVet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20if%20adjVet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20dfs%28graph%2C%20visited%2C%20res%2C%20adjVet%29%0A%0Adef%20graph_dfs%28graph%3A%20GraphAdjList%2C%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%29%0A%20%20%20%20dfs%28graph%2C%20visited%2C%20res%2C%20start_vet%29%0A%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0%2C%201%2C%202%2C%203%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%20%5Bv%5B1%5D%2C%20v%5B4%5D%5D%2C%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20res%20%3D%20graph_dfs%28graph%2C%20v%5B0%5D%29&cumulative=false&curInstr=130&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_greedy/coin_change_greedy.md ================================================ https://pythontutor.com/render.html#code=def%20coin_change_greedy%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D0%BD%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B5%D0%B4%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%2C%20%D1%87%D1%82%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20coins%20%D1%83%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BE%D1%87%D0%B5%D0%BD%0A%20%20%20%20i%20%3D%20len%28coins%29%20-%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%20%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%82%D1%8C%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%2C%20%D0%BF%D0%BE%D0%BA%D0%B0%20%D0%BD%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D0%BD%D0%B5%D1%82%D1%81%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%0A%20%20%20%20while%20amt%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%2C%20%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D0%B0%D1%8F%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BA%D0%B0%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%D0%B8%20%D0%BD%D0%B0%D0%B8%D0%B1%D0%BE%D0%BB%D0%B5%D0%B5%20%D0%BA%20%D0%BD%D0%B5%D0%BC%D1%83%20%D0%B1%D0%BB%D0%B8%D0%B7%D0%BA%D0%B0%0A%20%20%20%20%20%20%20%20while%20i%20%3E%200%20and%20coins%5Bi%5D%20%3E%20amt%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20-%3D%201%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D1%8B%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20amt%20-%3D%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B4%D0%BE%D0%BF%D1%83%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D0%B5%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%D0%BE%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%20%20%20%20return%20count%20if%20amt%20%3D%3D%200%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B4%D1%85%D0%BE%D0%B4%3A%20%D0%B3%D0%B0%D1%80%D0%B0%D0%BD%D1%82%D0%B8%D1%80%D1%83%D0%B5%D1%82%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B3%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20coins%20%3D%20%5B1%2C%205%2C%2010%2C%2020%2C%2050%2C%20100%5D%0A%20%20%20%20amt%20%3D%20186%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%7Bamt%7D%20%3D%20%7Bres%7D%22%29%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B4%D1%85%D0%BE%D0%B4%3A%20%D0%BD%D0%B5%20%D0%B3%D0%B0%D1%80%D0%B0%D0%BD%D1%82%D0%B8%D1%80%D1%83%D0%B5%D1%82%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B3%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20coins%20%3D%20%5B1%2C%2020%2C%2050%5D%0A%20%20%20%20amt%20%3D%2060%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%7Bamt%7D%20%3D%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%D0%9D%D0%B0%20%D1%81%D0%B0%D0%BC%D0%BE%D0%BC%20%D0%B4%D0%B5%D0%BB%D0%B5%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D1%83%D0%BC%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%203%3A%2020%20%2B%2020%20%2B%2020%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_greedy/fractional_knapsack.md ================================================ https://pythontutor.com/render.html#code=class%20Item%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20w%3A%20int%2C%20v%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.w%20%3D%20w%20%20%23%20%D0%92%D0%B5%D1%81%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%B0%0A%20%20%20%20%20%20%20%20self.v%20%3D%20v%20%20%23%20%D0%A1%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%B0%0A%0Adef%20fractional_knapsack%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%94%D1%80%D0%BE%D0%B1%D0%BD%D1%8B%D0%B9%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%BE%D0%B7%D0%B4%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D1%81%20%D0%B4%D0%B2%D1%83%D0%BC%D1%8F%20%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%B0%D0%BC%D0%B8%3A%20%D0%B2%D0%B5%D1%81%20%D0%B8%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20items%20%3D%20%5BItem%28w%2C%20v%29%20for%20w%2C%20v%20in%20zip%28wgt%2C%20val%29%5D%0A%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BF%D0%BE%20%D1%83%D0%B4%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20item.v%20%2F%20item.w%20%D0%B2%20%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B5%20%D1%83%D0%B1%D1%8B%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%0A%20%20%20%20items.sort%28key%3Dlambda%20item%3A%20item.v%20%2F%20item.w%2C%20reverse%3DTrue%29%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20for%20item%20in%20items%3A%0A%20%20%20%20%20%20%20%20if%20item.w%20%3C%3D%20cap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D0%B2%D1%88%D0%B5%D0%B9%D1%81%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D0%B4%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D1%87%D0%BD%D0%BE%2C%20%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20%D1%86%D0%B5%D0%BB%D0%B8%D0%BA%D0%BE%D0%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20item.v%0A%20%20%20%20%20%20%20%20%20%20%20%20cap%20-%3D%20item.w%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D0%B2%D1%88%D0%B5%D0%B9%D1%81%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D0%BD%D0%B5%D0%B4%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D1%87%D0%BD%D0%BE%2C%20%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%20%D1%87%D0%B0%D1%81%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B5%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20%28item.v%20%2F%20item.w%29%20%2A%20cap%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%A1%D0%B2%D0%BE%D0%B1%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D0%BD%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D0%BE%D1%81%D1%8C%2C%20%D0%BF%D0%BE%D1%8D%D1%82%D0%BE%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B9%D1%82%D0%B8%20%D0%B8%D0%B7%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%0A%20%20%20%20res%20%3D%20fractional_knapsack%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_greedy/max_capacity.md ================================================ https://pythontutor.com/render.html#code=def%20max_capacity%28ht%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20i%20%D0%B8%20j%20%D1%82%D0%B0%D0%BA%2C%20%D1%87%D1%82%D0%BE%D0%B1%D1%8B%20%D0%BE%D0%BD%D0%B8%20%D1%80%D0%B0%D1%81%D0%BF%D0%BE%D0%BB%D0%B0%D0%B3%D0%B0%D0%BB%D0%B8%D1%81%D1%8C%20%D0%BF%D0%BE%20%D0%B4%D0%B2%D1%83%D0%BC%20%D0%BA%D0%BE%D0%BD%D1%86%D0%B0%D0%BC%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28ht%29%20-%201%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%200%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%D0%92%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%82%D1%8C%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%2C%20%D0%BF%D0%BE%D0%BA%D0%B0%20%D0%B4%D0%B2%D0%B5%20%D0%B4%D0%BE%D1%81%D0%BA%D0%B8%20%D0%BD%D0%B5%20%D0%B2%D1%81%D1%82%D1%80%D0%B5%D1%82%D1%8F%D1%82%D1%81%D1%8F%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20cap%20%3D%20min%28ht%5Bi%5D%2C%20ht%5Bj%5D%29%20%2A%20%28j%20-%20i%29%0A%20%20%20%20%20%20%20%20res%20%3D%20max%28res%2C%20cap%29%0A%20%20%20%20%20%20%20%20%23%20%D0%A1%D0%B4%D0%B2%D0%B8%D0%B3%D0%B0%D1%82%D1%8C%20%D0%B2%D0%BD%D1%83%D1%82%D1%80%D1%8C%20%D0%B1%D0%BE%D0%BB%D0%B5%D0%B5%20%D0%BA%D0%BE%D1%80%D0%BE%D1%82%D0%BA%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D1%83%0A%20%20%20%20%20%20%20%20if%20ht%5Bi%5D%20%3C%20ht%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20ht%20%3D%20%5B3%2C%208%2C%205%2C%202%2C%207%2C%207%2C%203%2C%204%5D%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%0A%20%20%20%20res%20%3D%20max_capacity%28ht%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_greedy/max_product_cutting.md ================================================ https://pythontutor.com/render.html#code=import%20math%0A%0Adef%20max_product_cutting%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D0%B7%D0%B0%D0%BD%D0%B8%D1%8F%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20n%20%3C%3D%203%2C%20%D0%BE%D0%B1%D1%8F%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%20%D0%B2%D1%8B%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BE%D0%B4%D0%BD%D1%83%201%0A%20%20%20%20if%20n%20%3C%3D%203%3A%0A%20%20%20%20%20%20%20%20return%201%20%2A%20%28n%20-%201%29%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D0%BE%20%D0%B2%D1%8B%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BC%D0%BD%D0%BE%D0%B6%D0%B8%D1%82%D0%B5%D0%BB%D0%B8%203%2C%20%D0%B3%D0%B4%D0%B5%20a%20%E2%80%94%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%82%D1%80%D0%BE%D0%B5%D0%BA%2C%20%D0%B0%20b%20%E2%80%94%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%0A%20%20%20%20a%2C%20b%20%3D%20n%20%2F%2F%203%2C%20n%20%25%203%0A%20%20%20%20if%20b%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%201%2C%20%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B4%D0%BD%D1%83%20%D0%BF%D0%B0%D1%80%D1%83%201%20%2A%203%20%D0%B2%202%20%2A%202%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%20-%201%29%29%20%2A%202%20%2A%202%0A%20%20%20%20if%20b%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%202%2C%20%D0%BD%D0%B8%D1%87%D0%B5%D0%B3%D0%BE%20%D0%BD%D0%B5%20%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%20%2A%202%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%200%2C%20%D0%BD%D0%B8%D1%87%D0%B5%D0%B3%D0%BE%20%D0%BD%D0%B5%20%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%0A%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2058%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%0A%20%20%20%20res%20%3D%20max_product_cutting%28n%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D0%B7%D0%B0%D0%BD%D0%B8%D1%8F%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_hashing/array_hash_map.md ================================================ https://pythontutor.com/render.html#code=class%20Pair%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Aclass%20ArrayHashMap%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self.buckets%3A%20list%5BPair%20%7C%20None%5D%20%3D%20%5BNone%5D%20%2A%2020%0A%0A%20%20%20%20def%20hash_func%28self%2C%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20index%20%3D%20key%20%25%2020%0A%20%20%20%20%20%20%20%20return%20index%0A%0A%20%20%20%20def%20get%28self%2C%20key%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20pair%3A%20Pair%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20if%20pair%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20pair.val%0A%0A%20%20%20%20def%20put%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key%2C%20val%29%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20pair%0A%0A%20%20%20%20def%20remove%28self%2C%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20None%0A%0A%20%20%20%20def%20entry_set%28self%29%20-%3E%20list%5BPair%5D%3A%0A%20%20%20%20%20%20%20%20result%3A%20list%5BPair%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20key_set%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.key%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20value_set%28self%29%20-%3E%20list%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.val%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print%28pair.key%2C%20%27-%3E%27%2C%20pair.val%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20hmap%20%3D%20ArrayHashMap%28%29%0A%20%20%20%20hmap.put%2812836%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A5%D0%B0%27%29%0A%20%20%20%20hmap.put%2815937%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%9B%D0%BE%27%29%0A%20%20%20%20hmap.put%2816750%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A1%D1%83%D0%B0%D0%BD%D1%8C%27%29%0A%20%20%20%20hmap.put%2813276%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A4%D0%B0%27%29%0A%20%20%20%20hmap.put%2810583%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%AF%27%29%0A%20%20%20%20name%20%3D%20hmap.get%2815937%29%0A%20%20%20%20hmap.remove%2810583%29%0A%20%20%20%20print%28%27%5Cn%D0%9E%D1%82%D0%B4%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%20%D0%BF%D0%B0%D1%80%20%D0%BA%D0%BB%D1%8E%D1%87-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%27%29%0A%20%20%20%20for%20pair%20in%20hmap.entry_set%28%29%3A%0A%20%20%20%20%20%20%20%20print%28pair.key%2C%20%27-%3E%27%2C%20pair.val%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_hashing/hash_map_chaining.md ================================================ https://pythontutor.com/render.html#code=class%20Pair%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Aclass%20HashMapChaining%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20self.capacity%20%3D%204%0A%20%20%20%20%20%20%20%20self.load_thres%20%3D%202.0%20%2F%203.0%0A%20%20%20%20%20%20%20%20self.extend_ratio%20%3D%202%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%0A%20%20%20%20def%20hash_func%28self%2C%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20key%20%25%20self.capacity%0A%0A%20%20%20%20def%20load_factor%28self%29%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20return%20self.size%20%2F%20self.capacity%0A%0A%20%20%20%20def%20get%28self%2C%20key%3A%20int%29%20-%3E%20str%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20pair.val%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20def%20put%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20if%20self.load_factor%28%29%20%3E%20self.load_thres%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend%28%29%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pair.val%20%3D%20val%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key%2C%20val%29%0A%20%20%20%20%20%20%20%20bucket.append%28pair%29%0A%20%20%20%20%20%20%20%20self.size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self%2C%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bucket.remove%28pair%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.size%20-%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20def%20extend%28self%29%3A%0A%20%20%20%20%20%20%20%20buckets%20%3D%20self.buckets%0A%20%20%20%20%20%20%20%20self.capacity%20%2A%3D%20self.extend_ratio%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.put%28pair.key%2C%20pair.val%29%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20res.append%28str%28pair.key%29%20%2B%20%27%20-%3E%20%27%20%2B%20pair.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28res%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20hashmap%20%3D%20HashMapChaining%28%29%0A%20%20%20%20hashmap.put%2812836%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A5%D0%B0%27%29%0A%20%20%20%20hashmap.put%2815937%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%9B%D0%BE%27%29%0A%20%20%20%20hashmap.put%2816750%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A1%D1%83%D0%B0%D0%BD%D1%8C%27%29%0A%20%20%20%20hashmap.put%2813276%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A4%D0%B0%27%29%0A%20%20%20%20hashmap.put%2810583%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%AF%27%29%0A%20%20%20%20name%20%3D%20hashmap.get%2813276%29%0A%20%20%20%20hashmap.remove%2812836%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_hashing/simple_hash.md ================================================ https://pythontutor.com/render.html#code=def%20add_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%90%D0%B4%D0%B4%D0%B8%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5%20%D1%85%D0%B5%D1%88%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%2B%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20mul_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D1%83%D0%BB%D1%8C%D1%82%D0%B8%D0%BF%D0%BB%D0%B8%D0%BA%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5%20%D1%85%D0%B5%D1%88%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%2031%20%2A%20hash%20%2B%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20xor_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22XOR-%D1%85%D0%B5%D1%88%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%5E%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20rot_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A5%D0%B5%D1%88%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%BC%20%D1%81%D0%B4%D0%B2%D0%B8%D0%B3%D0%BE%D0%BC%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%20%28hash%20%3C%3C%204%29%20%5E%20%28hash%20%3E%3E%2028%29%20%5E%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20key%20%3D%20%22Hello%20Algo%22%0A%0A%20%20%20%20hash%20%3D%20add_hash%28key%29%0A%20%20%20%20print%28f%22%D0%A5%D0%B5%D1%88-%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D1%81%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%20%3D%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20mul_hash%28key%29%0A%20%20%20%20print%28f%22%D0%A5%D0%B5%D1%88-%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D1%83%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%20%3D%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20xor_hash%28key%29%0A%20%20%20%20print%28f%22%D0%A5%D0%B5%D1%88-%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20XOR%20%3D%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20rot_hash%28key%29%0A%20%20%20%20print%28f%22%D0%A5%D0%B5%D1%88-%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D1%81%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%BC%20%D1%81%D0%B4%D0%B2%D0%B8%D0%B3%D0%BE%D0%BC%20%3D%20%7Bhash%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_heap/my_heap.md ================================================ https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.parent%28self.size%28%29%20-%201%29%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.sift_down%28i%29%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%28self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%29%20%3D%20%28self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20sift_down%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%28l%2C%20r%2C%20ma%29%20%3D%20%28self.left%28i%29%2C%20self.right%28i%29%2C%20i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B1%2C%202%2C%203%2C%204%2C%205%5D%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%BA%D1%83%D1%87%D0%B0%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9A%D0%BE%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D0%BE%D1%80%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%20%D0%B1%D0%B5%D0%B7%20%D0%B8%D0%B7%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B7%D0%BB%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B7%D0%BB%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%80%D0%BE%D0%B4%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE%20%D1%83%D0%B7%D0%BB%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%20%20%23%20%D0%9E%D0%BA%D1%80%D1%83%D0%B3%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B2%D0%BD%D0%B8%D0%B7%20%D0%BF%D1%80%D0%B8%20%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B8%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%D0%B0%20%D0%BA%D1%83%D1%87%D0%B8%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0%2C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BA%D1%83%D1%87%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%20%D0%BD%D0%B0%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.max_heap%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20%23%20%D0%9E%D0%B1%D1%80%D0%B0%D1%82%D0%B8%D1%82%D0%B5%20%D0%B2%D0%BD%D0%B8%D0%BC%D0%B0%D0%BD%D0%B8%D0%B5%3A%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%83%D0%B6%D0%B5%20%D1%8F%D0%B2%D0%BB%D1%8F%D0%B5%D1%82%D1%81%D1%8F%20%D0%BA%D0%BE%D1%80%D1%80%D0%B5%D0%BA%D1%82%D0%BD%D0%BE%D0%B9%20%D0%BA%D1%83%D1%87%D0%B5%D0%B9%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%206%2C%206%2C%207%2C%205%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%5D%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D1%81%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D1%8B%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20peek%20%3D%20max_heap.peek%28%29%0A%20%20%20%20print%28f%22%5Cn%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%20%3D%20%7Bpeek%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%28self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%29%20%3D%20%28self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.max_heap.append%28val%29%0A%20%20%20%20%20%20%20%20self.sift_up%28self.size%28%29%20-%201%29%0A%0A%20%20%20%20def%20sift_up%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20p%20%3D%20self.parent%28i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20p%20%3C%200%20or%20self.max_heap%5Bi%5D%20%3C%3D%20self.max_heap%5Bp%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20p%29%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20p%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%206%2C%206%2C%207%2C%205%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%5D%29%0A%20%20%20%20val%20%3D%207%0A%20%20%20%20max_heap.push%28val%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%28self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%29%20%3D%20%28self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%BA%D1%83%D1%87%D0%B0%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%27%29%0A%20%20%20%20%20%20%20%20self.swap%280%2C%20self.size%28%29%20-%201%29%0A%20%20%20%20%20%20%20%20val%20%3D%20self.max_heap.pop%28%29%0A%20%20%20%20%20%20%20%20self.sift_down%280%29%0A%20%20%20%20%20%20%20%20return%20val%0A%0A%20%20%20%20def%20sift_down%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%28l%2C%20r%2C%20ma%29%20%3D%20%28self.left%28i%29%2C%20self.right%28i%29%2C%20i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%207%2C%206%2C%207%2C%206%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%2C%205%5D%29%0A%20%20%20%20peek%20%3D%20max_heap.pop%28%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_heap/top_k.md ================================================ https://pythontutor.com/render.html#code=import%20heapq%0A%0Adef%20top_k_heap%28nums%3A%20list%5Bint%5D%2C%20k%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20k%20%D0%BD%D0%B0%D0%B8%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D1%85%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20%D0%BA%D1%83%D1%87%D0%B8%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20heap%20%3D%20%5B%5D%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B5%20k%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20for%20i%20in%20range%28k%29%3A%0A%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B8%D0%BD%D0%B0%D1%8F%20%D1%81%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20k%2B1%2C%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BA%D1%83%D1%87%D0%B8%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%D0%B9%20k%0A%20%20%20%20for%20i%20in%20range%28k%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%BD%D0%B0%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%2C%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D1%83%20%D0%BA%D1%83%D1%87%D0%B8%20%D0%B8%20%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3E%20heap%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappop%28heap%29%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20return%20heap%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%207%2C%206%2C%203%2C%202%5D%0A%20%20%20%20k%20%3D%203%0A%0A%20%20%20%20res%20%3D%20top_k_heap%28nums%2C%20k%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_searching/binary_search.md ================================================ https://pythontutor.com/render.html#code=def%20binary_search%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%28%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%29%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%5B0%2C%20n-1%5D%2C%20%D1%82%D0%BE%20%D0%B5%D1%81%D1%82%D1%8C%20i%20%D0%B8%20j%20%D1%83%D0%BA%D0%B0%D0%B7%D1%8B%D0%B2%D0%B0%D1%8E%D1%82%20%D0%BD%D0%B0%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D0%B8%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B0%D0%B5%D1%82%D1%81%D1%8F%2C%20%D0%BA%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%20%D0%BF%D1%83%D1%81%D1%82%20%28%D0%BF%D1%80%D0%B8%20i%20%3E%20j%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%D0%BF%D1%83%D1%81%D1%82%29%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%20%D0%B2%20Python%20%D0%BC%D0%BE%D0%B3%D1%83%D1%82%20%D0%B1%D1%8B%D1%82%D1%8C%20%D1%81%D0%BA%D0%BE%D0%BB%D1%8C%20%D1%83%D0%B3%D0%BE%D0%B4%D0%BD%D0%BE%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D0%BC%D0%B8%20%28%D0%BE%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%87%D0%B5%D0%BD%D1%8B%20%D1%82%D0%BE%D0%BB%D1%8C%D0%BA%D0%BE%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BC%D0%BE%D0%BC%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%29%2C%20%D0%BF%D0%BE%D1%8D%D1%82%D0%BE%D0%BC%D1%83%20%D0%BD%D0%B5%20%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%20%D1%83%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BF%D0%BE%D0%BB%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D1%85%20%D1%87%D0%B8%D1%81%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%D0%AD%D1%82%D0%BE%20%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%2C%20%D1%87%D1%82%D0%BE%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bm%2B1%2C%20j%5D%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%D0%AD%D1%82%D0%BE%20%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%2C%20%D1%87%D1%82%D0%BE%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%D0%A6%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B5%D0%B3%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%0A%20%20%20%20return%20-1%20%20%23%20%D0%A6%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%28%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%29%0A%20%20%20%20index%20%3D%20binary_search%28nums%2C%20target%29%0A%20%20%20%20print%28%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%206%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20binary_search_lcro%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%28%D0%BB%D0%B5%D0%B2%D0%BE%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%2C%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%20%D0%BE%D1%82%D0%BA%D1%80%D1%8B%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%29%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D0%BE%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%2C%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%20%D0%BE%D1%82%D0%BA%D1%80%D1%8B%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%5B0%2C%20n%29%2C%20%D1%82%D0%BE%20%D0%B5%D1%81%D1%82%D1%8C%20i%20%D0%B8%20j%20%D1%83%D0%BA%D0%B0%D0%B7%D1%8B%D0%B2%D0%B0%D1%8E%D1%82%20%D0%BD%D0%B0%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B8%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%B7%D0%B0%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BD%D0%B8%D0%BC%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%BC%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B0%D0%B5%D1%82%D1%81%D1%8F%2C%20%D0%BA%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%20%D0%BF%D1%83%D1%81%D1%82%20%28%D0%BF%D1%80%D0%B8%20i%20%3D%20j%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%D0%BF%D1%83%D1%81%D1%82%29%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%D0%AD%D1%82%D0%BE%20%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%2C%20%D1%87%D1%82%D0%BE%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bm%2B1%2C%20j%29%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20%20%23%20%D0%AD%D1%82%D0%BE%20%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%2C%20%D1%87%D1%82%D0%BE%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m%29%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%D0%A6%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B5%D0%B3%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%0A%20%20%20%20return%20-1%20%20%23%20%D0%A6%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%28%D0%BB%D0%B5%D0%B2%D0%BE%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%2C%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%20%D0%BE%D1%82%D0%BA%D1%80%D1%8B%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%29%0A%20%20%20%20index%20%3D%20binary_search_lcro%28nums%2C%20target%29%0A%20%20%20%20print%28%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%206%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_searching/binary_search_edge.md ================================================ https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%28%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%29%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bm%2B1%2C%20j%5D%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%82%D0%BE%D1%87%D0%BA%D1%83%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_left_edge%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%D0%B0%D0%BC%D0%BE%D0%B3%D0%BE%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20target%22%22%22%0A%20%20%20%20%23%20%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D1%83%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20target%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums%2C%20target%29%0A%20%20%20%20%23%20target%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%20%20%20%20if%20i%20%3D%3D%20len%28nums%29%20or%20nums%5Bi%5D%20%21%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20target%20%D0%B8%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20i%0A%20%20%20%20return%20i%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B9%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_left_edge%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B0%D0%BC%D0%BE%D0%B3%D0%BE%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%7Btarget%7D%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%28%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%29%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bm%2B1%2C%20j%5D%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%82%D0%BE%D1%87%D0%BA%D1%83%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_right_edge%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%D0%B0%D0%BC%D0%BE%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B3%D0%BE%20target%22%22%22%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D1%83%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%D0%B0%D0%BC%D0%BE%D0%B3%D0%BE%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20target%20%2B%201%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums%2C%20target%20%2B%201%29%0A%20%20%20%20%23%20j%20%D1%83%D0%BA%D0%B0%D0%B7%D1%8B%D0%B2%D0%B0%D0%B5%D1%82%20%D0%BD%D0%B0%20%D1%81%D0%B0%D0%BC%D1%8B%D0%B9%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20target%2C%20%D0%B0%20i%20%E2%80%94%20%D0%BD%D0%B0%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20target%0A%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%23%20target%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%20%20%20%20if%20j%20%3D%3D%20-1%20or%20nums%5Bj%5D%20%21%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20target%20%D0%B8%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20j%0A%20%20%20%20return%20j%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B9%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_right_edge%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B0%D0%BC%D0%BE%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%7Btarget%7D%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_searching/binary_search_insertion.md ================================================ https://pythontutor.com/render.html#code=def%20binary_search_insertion_simple%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%28%D0%B1%D0%B5%D0%B7%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D1%85%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%29%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bm%2B1%2C%20j%5D%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20target%20%D0%B8%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%82%D0%BE%D1%87%D0%BA%D1%83%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20m%0A%20%20%20%20%23%20target%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%82%D0%BE%D1%87%D0%BA%D1%83%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D1%85%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion_simple%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%7Btarget%7D%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%28%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%29%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bm%2B1%2C%20j%5D%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%82%D0%BE%D1%87%D0%BA%D1%83%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%7Btarget%7D%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_searching/two_sum.md ================================================ https://pythontutor.com/render.html#code=def%20two_sum_brute_force%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B5%D1%82%D0%BE%D0%B4%201%3A%20%D0%BF%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%22%22%22%0A%20%20%20%20%23%20%D0%94%D0%B2%D0%B0%20%D0%B2%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%BD%D1%8B%D1%85%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B0%2C%20%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20O%28n%5E2%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%2B%20nums%5Bj%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bi%2C%20j%5D%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%207%2C%2011%2C%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_brute_force%28nums%2C%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20two_sum_hash_table%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B5%D1%82%D0%BE%D0%B4%202%3A%20%D0%B2%D1%81%D0%BF%D0%BE%D0%BC%D0%BE%D0%B3%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0%22%22%22%0A%20%20%20%20%23%20%D0%92%D1%81%D0%BF%D0%BE%D0%BC%D0%BE%D0%B3%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0%2C%20%D0%BF%D1%80%D0%BE%D1%81%D1%82%D1%80%D0%B0%D0%BD%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20O%28n%29%0A%20%20%20%20dic%20%3D%20%7B%7D%0A%20%20%20%20%23%20%D0%9E%D0%B4%D0%B8%D0%BD%20%D1%86%D0%B8%D0%BA%D0%BB%2C%20%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20O%28n%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20target%20-%20nums%5Bi%5D%20in%20dic%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bdic%5Btarget%20-%20nums%5Bi%5D%5D%2C%20i%5D%0A%20%20%20%20%20%20%20%20dic%5Bnums%5Bi%5D%5D%20%3D%20i%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%207%2C%2011%2C%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_hash_table%28nums%2C%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_sorting/bubble_sort.md ================================================ https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%B0%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D0%BD%D1%83%D1%82%D1%80%D0%B5%D0%BD%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B0%20%5B0%2C%20i%5D%20%D0%B2%20%D0%B5%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BA%D0%BE%D0%BD%D0%B5%D1%86%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%20nums%5Bj%5D%20%D0%B8%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%BF%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20bubble_sort_with_flag%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%B0%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%28%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%84%D0%BB%D0%B0%D0%B3%D0%BE%D0%BC%29%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20flag%20%3D%20False%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%84%D0%BB%D0%B0%D0%B3%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D0%BD%D1%83%D1%82%D1%80%D0%B5%D0%BD%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B0%20%5B0%2C%20i%5D%20%D0%B2%20%D0%B5%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BA%D0%BE%D0%BD%D0%B5%D1%86%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%20nums%5Bj%5D%20%D0%B8%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20flag%20%3D%20True%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B1%D0%BC%D0%B5%D0%BD%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20%20%20%20%20if%20not%20flag%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%20%20%23%20%D0%9D%D0%B0%20%D1%8D%D1%82%D0%BE%D0%B9%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%20%C2%AB%D0%B2%D1%81%D0%BF%D0%BB%D1%8B%D1%82%D0%B8%D1%8F%C2%BB%20%D0%BD%D0%B5%20%D0%B1%D1%8B%D0%BB%D0%BE%20%D0%BD%D0%B8%20%D0%BE%D0%B4%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%BE%D0%B1%D0%BC%D0%B5%D0%BD%D0%B0%2C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%B2%D1%8B%D0%B9%D1%82%D0%B8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort_with_flag%28nums%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%BF%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_sorting/bucket_sort.md ================================================ https://pythontutor.com/render.html#code=def%20bucket_sort%28nums%3A%20list%5Bfloat%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20k%20%3D%20n%2F2%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%2C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BF%D0%BE%D0%BB%D0%B0%D0%B3%D0%B0%D1%8F%20%D1%80%D0%B0%D1%81%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%202%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20%D0%B2%20%D0%BA%D0%B0%D0%B6%D0%B4%D1%83%D1%8E%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D1%83%0A%20%20%20%20k%20%3D%20len%28nums%29%20%2F%2F%202%0A%20%20%20%20buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28k%29%5D%0A%20%20%20%20%23%201.%20%D0%A0%D0%B0%D1%81%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%BF%D0%BE%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D0%B0%D0%BC%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D0%B5%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D0%B5%20%D0%BB%D0%B5%D0%B6%D0%B0%D1%82%20%D0%B2%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B5%20%5B0%2C%201%29%3B%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20num%20%2A%20k%20%D0%B4%D0%BB%D1%8F%20%D0%BE%D1%82%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D0%BE%D0%B2%20%5B0%2C%20k-1%5D%0A%20%20%20%20%20%20%20%20i%20%3D%20int%28num%20%2A%20k%29%0A%20%20%20%20%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20num%20%D0%B2%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D1%83%20i%0A%20%20%20%20%20%20%20%20buckets%5Bi%5D.append%28num%29%0A%20%20%20%20%23%202.%20%D0%92%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D1%8C%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D1%83%20%D0%B2%D0%BD%D1%83%D1%82%D1%80%D0%B8%20%D0%BA%D0%B0%D0%B6%D0%B4%D0%BE%D0%B9%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D1%8B%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B2%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%BD%D1%83%D1%8E%20%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8E%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%3B%20%D0%B5%D0%B5%20%D1%82%D0%B0%D0%BA%D0%B6%D0%B5%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D0%B7%D0%B0%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B4%D1%80%D1%83%D0%B3%D0%B8%D0%BC%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%BE%D0%BC%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%0A%20%20%20%20%20%20%20%20bucket.sort%28%29%0A%20%20%20%20%23%203.%20%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D1%8B%20%D0%B8%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%B4%D0%B8%D0%BD%D0%B8%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D1%8B%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20for%20num%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%9F%D1%83%D1%81%D1%82%D1%8C%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D0%B5%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D0%B5%20%E2%80%94%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%20%D1%81%20%D0%BF%D0%BB%D0%B0%D0%B2%D0%B0%D1%8E%D1%89%D0%B5%D0%B9%20%D1%82%D0%BE%D1%87%D0%BA%D0%BE%D0%B9%20%D0%B8%D0%B7%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B0%20%5B0%2C%201%29%0A%20%20%20%20nums%20%3D%20%5B0.49%2C%200.96%2C%200.82%2C%200.09%2C%200.57%2C%200.43%2C%200.91%2C%200.75%2C%200.15%2C%200.37%5D%0A%20%20%20%20bucket_sort%28nums%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_sorting/counting_sort.md ================================================ https://pythontutor.com/render.html#code=def%20counting_sort_naive%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%D0%BF%D0%BE%D0%B4%D1%81%D1%87%D0%B5%D1%82%D0%BE%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D1%81%D1%82%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%2C%20%D0%BD%D0%B5%20%D0%BF%D0%BE%D0%B4%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B4%D0%BB%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BE%D0%B2%0A%20%20%20%20%23%201.%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20m%0A%20%20%20%20m%20%3D%200%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20max%28m%2C%20num%29%0A%20%20%20%20%23%202.%20%D0%9F%D0%BE%D0%B4%D1%81%D1%87%D0%B8%D1%82%D0%B0%D1%82%D1%8C%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BF%D0%BE%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B9%20%D0%BA%D0%B0%D0%B6%D0%B4%D0%BE%D0%B9%20%D1%86%D0%B8%D1%84%D1%80%D1%8B%0A%20%20%20%20%23%20counter%5Bnum%5D%20%D0%BE%D0%B1%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BF%D0%BE%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B9%20num%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%20counter%20%D0%B8%20%D0%B7%D0%B0%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B8%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20num%20in%20range%28m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28counter%5Bnum%5D%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort_naive%28nums%29%0A%20%20%20%20print%28f%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%BF%D0%BE%D0%B4%D1%81%D1%87%D0%B5%D1%82%D0%BE%D0%BC%20%28%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D1%8B%20%D0%BD%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D0%B2%D0%B0%D1%8E%D1%82%D1%81%D1%8F%29%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20counting_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%D0%BF%D0%BE%D0%B4%D1%81%D1%87%D0%B5%D1%82%D0%BE%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%2C%20%D0%BF%D0%BE%D0%B7%D0%B2%D0%BE%D0%BB%D1%8F%D0%B5%D1%82%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D1%8B%20%D0%B8%20%D1%8F%D0%B2%D0%BB%D1%8F%D0%B5%D1%82%D1%81%D1%8F%20%D1%81%D1%82%D0%B0%D0%B1%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%BE%D0%B9%0A%20%20%20%20%23%201.%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20m%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%202.%20%D0%9F%D0%BE%D0%B4%D1%81%D1%87%D0%B8%D1%82%D0%B0%D1%82%D1%8C%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BF%D0%BE%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B9%20%D0%BA%D0%B0%D0%B6%D0%B4%D0%BE%D0%B9%20%D1%86%D0%B8%D1%84%D1%80%D1%8B%0A%20%20%20%20%23%20counter%5Bnum%5D%20%D0%BE%D0%B1%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BF%D0%BE%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B9%20num%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D1%84%D0%B8%D0%BA%D1%81%D0%BD%D1%8B%D0%B5%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20counter%20%D0%B8%20%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%C2%AB%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BF%D0%BE%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B9%C2%BB%20%D0%B2%20%C2%AB%D0%BA%D0%BE%D0%BD%D0%B5%D1%87%D0%BD%D1%8B%D0%B9%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%C2%BB%0A%20%20%20%20%23%20%D0%A2%D0%BE%20%D0%B5%D1%81%D1%82%D1%8C%20counter%5Bnum%5D-1%20%E2%80%94%20%D1%8D%D1%82%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BD%D0%B5%D0%B3%D0%BE%20%D0%BF%D0%BE%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20num%20%D0%B2%20res%0A%20%20%20%20for%20i%20in%20range%28m%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%20%2B%201%5D%20%2B%3D%20counter%5Bi%5D%0A%20%20%20%20%23%204.%20%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%20nums%20%D0%B2%20%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%BC%20%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B5%20%D0%B8%20%D0%BF%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%B2%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20res%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20res%20%D0%B4%D0%BB%D1%8F%20%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%B0%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20res%5Bcounter%5Bnum%5D%20-%201%5D%20%3D%20num%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20num%20%D0%BF%D0%BE%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D1%8E%D1%89%D0%B5%D0%BC%D1%83%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20-%3D%201%20%20%23%20%D0%A3%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D1%84%D0%B8%D0%BA%D1%81%D0%BD%D1%83%D1%8E%20%D1%81%D1%83%D0%BC%D0%BC%D1%83%20%D0%BD%D0%B0%201%2C%20%D1%87%D1%82%D0%BE%D0%B1%D1%8B%20%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%B3%D0%BE%20%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D1%8F%20num%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D0%B8%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%BE%D0%BC%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%B0%20res%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort%28nums%29%0A%20%20%20%20print%28f%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%BF%D0%BE%D0%B4%D1%81%D1%87%D0%B5%D1%82%D0%BE%D0%BC%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_sorting/heap_sort.md ================================================ https://pythontutor.com/render.html#code=def%20sift_down%28nums%3A%20list%5Bint%5D%2C%20n%3A%20int%2C%20i%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%BA%D1%83%D1%87%D0%B8%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20n%3B%20%D0%BD%D0%B0%D1%87%D0%B8%D0%BD%D0%B0%D1%8F%20%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20i%2C%20%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%BE%D1%81%D0%B5%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%D0%B2%D0%B5%D1%80%D1%85%D1%83%20%D0%B2%D0%BD%D0%B8%D0%B7%22%22%22%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%20%D1%81%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%BC%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%20%D1%81%D1%80%D0%B5%D0%B4%D0%B8%20i%2C%20l%20%D0%B8%20r%20%D0%B8%20%D0%BE%D0%B1%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B8%D1%82%D1%8C%20%D0%B5%D0%B3%D0%BE%20%D0%BA%D0%B0%D0%BA%20ma%0A%20%20%20%20%20%20%20%20l%20%3D%202%20%2A%20i%20%2B%201%0A%20%20%20%20%20%20%20%20r%20%3D%202%20%2A%20i%20%2B%202%0A%20%20%20%20%20%20%20%20ma%20%3D%20i%0A%20%20%20%20%20%20%20%20if%20l%20%3C%20n%20and%20nums%5Bl%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20if%20r%20%3C%20n%20and%20nums%5Br%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%83%D0%B7%D0%B5%D0%BB%20i%20%D1%83%D0%B6%D0%B5%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D0%B5%D0%BD%20%D0%B8%D0%BB%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%8B%20l%20%D0%B8%20r%20%D0%B2%D0%BD%D0%B5%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%2C%20%D0%B4%D0%B0%D0%BB%D1%8C%D0%BD%D0%B5%D0%B9%D1%88%D0%B5%D0%B5%20%D0%BF%D1%80%D0%BE%D1%81%D0%B5%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%BD%D0%B5%20%D1%82%D1%80%D0%B5%D0%B1%D1%83%D0%B5%D1%82%D1%81%D1%8F%2C%20%D0%B2%D1%8B%D0%B9%D1%82%D0%B8%0A%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%B4%D0%B2%D0%B0%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bma%5D%20%3D%20nums%5Bma%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D1%81%D0%B5%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%B2%D0%BD%D0%B8%D0%B7%0A%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0Adef%20heap_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%D0%BA%D1%83%D1%87%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%3A%20%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D1%8C%20heapify%20%D0%B4%D0%BB%D1%8F%20%D0%B2%D1%81%D0%B5%D1%85%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%2C%20%D0%BA%D1%80%D0%BE%D0%BC%D0%B5%20%D0%BB%D0%B8%D1%81%D1%82%D0%BE%D0%B2%D1%8B%D1%85%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20%2F%2F%202%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20len%28nums%29%2C%20i%29%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D0%BA%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D0%BA%D1%83%D1%87%D0%B8%20%D0%B2%20%D1%82%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20n-1%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%BA%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%20%D1%81%20%D1%81%D0%B0%D0%BC%D1%8B%D0%BC%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%BC%20%D0%BB%D0%B8%D1%81%D1%82%D0%BE%D0%BC%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%20%28%D0%BF%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D0%B8%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%29%0A%20%20%20%20%20%20%20%20nums%5B0%5D%2C%20nums%5Bi%5D%20%3D%20nums%5Bi%5D%2C%20nums%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B8%D0%BD%D0%B0%D1%8F%20%D1%81%20%D0%BA%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%83%D0%B7%D0%BB%D0%B0%2C%20%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%BE%D1%81%D0%B5%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%D0%B2%D0%B5%D1%80%D1%85%D1%83%20%D0%B2%D0%BD%D0%B8%D0%B7%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20i%2C%200%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20heap_sort%28nums%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%BA%D1%83%D1%87%D0%B5%D0%B9%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_sorting/insertion_sort.md ================================================ https://pythontutor.com/render.html#code=def%20insertion_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%D0%BC%D0%B8%22%22%22%0A%20%20%20%20%23%20%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%5B0%2C%20i-1%5D%0A%20%20%20%20for%20i%20in%20range%281%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20base%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D0%BD%D1%83%D1%82%D1%80%D0%B5%D0%BD%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20base%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%20%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B0%20%5B0%2C%20i-1%5D%0A%20%20%20%20%20%20%20%20while%20j%20%3E%3D%200%20and%20nums%5Bj%5D%20%3E%20base%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%5D%20%20%23%20%D0%A1%D0%B4%D0%B2%D0%B8%D0%BD%D1%83%D1%82%D1%8C%20nums%5Bj%5D%20%D0%BD%D0%B0%20%D0%BE%D0%B4%D0%BD%D1%83%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%20%D0%B2%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20base%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20base%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20insertion_sort%28nums%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%D0%BC%D0%B8%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_sorting/merge_sort.md ================================================ https://pythontutor.com/render.html#code=def%20merge%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20tmp%20%3D%20%5B0%5D%20%2A%20%28right%20-%20left%20%2B%201%29%0A%20%20%20%20%28i%2C%20j%2C%20k%29%20%3D%20%28left%2C%20mid%20%2B%201%2C%200%29%0A%20%20%20%20while%20i%20%3C%3D%20mid%20and%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3C%3D%20nums%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20while%20i%20%3C%3D%20mid%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20while%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20for%20k%20in%20range%280%2C%20len%28tmp%29%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bleft%20%2B%20k%5D%20%3D%20tmp%5Bk%5D%0A%0Adef%20merge_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20mid%20%3D%20%28left%20%2B%20right%29%20%2F%2F%202%0A%20%20%20%20merge_sort%28nums%2C%20left%2C%20mid%29%0A%20%20%20%20merge_sort%28nums%2C%20mid%20%2B%201%2C%20right%29%0A%20%20%20%20merge%28nums%2C%20left%2C%20mid%2C%20right%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20nums%20%3D%20%5B7%2C%203%2C%202%2C%206%2C%200%2C%201%2C%205%2C%204%5D%0A%20%20%20%20merge_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%27%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D1%81%D0%BB%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5%D0%BC%20nums%20%3D%27%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_sorting/quick_sort.md ================================================ https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%B1%D0%B8%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%D0%B8%20%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8F%D0%BC%D0%B8%22%22%22%0A%20%20%20%20%23%20%D0%92%D0%B7%D1%8F%D1%82%D1%8C%20nums%5Bleft%5D%20%D0%B2%20%D0%BA%D0%B0%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%D0%98%D0%B4%D1%82%D0%B8%20%D1%81%D0%BF%D1%80%D0%B0%D0%B2%D0%B0%20%D0%BD%D0%B0%D0%BB%D0%B5%D0%B2%D0%BE%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%D1%85%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%D0%98%D0%B4%D1%82%D0%B8%20%D1%81%D0%BB%D0%B5%D0%B2%D0%B0%20%D0%BD%D0%B0%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%D1%85%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D0%BC%D0%B5%D0%BD%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%83%20%D0%B4%D0%B2%D1%83%D1%85%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%BE%D0%B2%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20partition%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%BC%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%B1%D0%B8%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%D0%B8%20%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8F%D0%BC%D0%B8%22%22%22%0A%20%20%20%20%23%20%D0%92%D0%B7%D1%8F%D1%82%D1%8C%20nums%5Bleft%5D%20%D0%B2%20%D0%BA%D0%B0%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%D0%98%D0%B4%D1%82%D0%B8%20%D1%81%D0%BF%D1%80%D0%B0%D0%B2%D0%B0%20%D0%BD%D0%B0%D0%BB%D0%B5%D0%B2%D0%BE%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%D1%85%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%D0%98%D0%B4%D1%82%D0%B8%20%D1%81%D0%BB%D0%B5%D0%B2%D0%B0%20%D0%BD%D0%B0%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%D1%85%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D0%BC%D0%B5%D0%BD%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%83%20%D0%B4%D0%B2%D1%83%D1%85%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%BE%D0%B2%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%B0%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%22%22%22%0A%20%20%20%20%23%20%D0%97%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D1%82%D1%8C%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8E%2C%20%D0%BA%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%201%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%A0%D0%B0%D0%B7%D0%B1%D0%B8%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%D0%B8%20%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8F%D0%BC%D0%B8%0A%20%20%20%20pivot%20%3D%20partition%28nums%2C%20left%2C%20right%29%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%BE%20%D0%BE%D0%B1%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%B0%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D1%8B%0A%20%20%20%20quick_sort%28nums%2C%20left%2C%20pivot%20-%201%29%0A%20%20%20%20quick_sort%28nums%2C%20pivot%20%2B%201%2C%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%B0%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20quick_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B9%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20median_three%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%92%D1%8B%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B5%D0%B4%D0%B8%D0%B0%D0%BD%D1%83%20%D0%B8%D0%B7%20%D1%82%D1%80%D0%B5%D1%85%20%D0%BA%D0%B0%D0%BD%D0%B4%D0%B8%D0%B4%D0%B0%D1%82%D0%BE%D0%B2%22%22%22%0A%20%20%20%20l%2C%20m%2C%20r%20%3D%20nums%5Bleft%5D%2C%20nums%5Bmid%5D%2C%20nums%5Bright%5D%0A%20%20%20%20if%20%28l%20%3C%3D%20m%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20m%20%3C%3D%20l%29%3A%0A%20%20%20%20%20%20%20%20return%20mid%20%20%23%20m%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20l%20%D0%B8%20r%0A%20%20%20%20if%20%28m%20%3C%3D%20l%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20l%20%3C%3D%20m%29%3A%0A%20%20%20%20%20%20%20%20return%20left%20%20%23%20l%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20m%20%D0%B8%20r%0A%20%20%20%20return%20right%0A%0Adef%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%B1%D0%B8%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%D0%B8%20%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8F%D0%BC%D0%B8%20%28%D0%BC%D0%B5%D0%B4%D0%B8%D0%B0%D0%BD%D0%B0%20%D1%82%D1%80%D0%B5%D1%85%29%22%22%22%0A%20%20%20%20%23%20%D0%92%D0%B7%D1%8F%D1%82%D1%8C%20nums%5Bleft%5D%20%D0%B2%20%D0%BA%D0%B0%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20med%20%3D%20median_three%28nums%2C%20left%2C%20%28left%20%2B%20right%29%20%2F%2F%202%2C%20right%29%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B5%D0%B4%D0%B8%D0%B0%D0%BD%D1%83%20%D0%B2%20%D0%BA%D1%80%D0%B0%D0%B9%D0%BD%D0%B8%D0%B9%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%5Bleft%5D%2C%20nums%5Bmed%5D%20%3D%20nums%5Bmed%5D%2C%20nums%5Bleft%5D%0A%20%20%20%20%23%20%D0%92%D0%B7%D1%8F%D1%82%D1%8C%20nums%5Bleft%5D%20%D0%B2%20%D0%BA%D0%B0%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%D0%98%D0%B4%D1%82%D0%B8%20%D1%81%D0%BF%D1%80%D0%B0%D0%B2%D0%B0%20%D0%BD%D0%B0%D0%BB%D0%B5%D0%B2%D0%BE%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%D1%85%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%D0%98%D0%B4%D1%82%D0%B8%20%D1%81%D0%BB%D0%B5%D0%B2%D0%B0%20%D0%BD%D0%B0%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%D1%85%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D0%BC%D0%B5%D0%BD%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%83%20%D0%B4%D0%B2%D1%83%D1%85%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%BE%D0%B2%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%9E%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B5%D0%B4%D0%B8%D0%B0%D0%BD%D0%BD%D1%8B%D0%BC%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%BC%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20partition%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%BC%20%28%D0%BC%D0%B5%D0%B4%D0%B8%D0%B0%D0%BD%D0%BD%D0%B0%D1%8F%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%28i%2C%20j%29%20%3D%20%28left%2C%20right%29%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%28nums%5Bi%5D%2C%20nums%5Bj%5D%29%20%3D%20%28nums%5Bj%5D%2C%20nums%5Bi%5D%29%0A%20%20%20%20%28nums%5Bi%5D%2C%20nums%5Bleft%5D%29%20%3D%20%28nums%5Bleft%5D%2C%20nums%5Bi%5D%29%0A%20%20%20%20return%20i%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20while%20left%20%3C%20right%3A%0A%20%20%20%20%20%20%20%20pivot%20%3D%20partition%28nums%2C%20left%2C%20right%29%0A%20%20%20%20%20%20%20%20if%20pivot%20-%20left%20%3C%20right%20-%20pivot%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums%2C%20left%2C%20pivot%20-%201%29%0A%20%20%20%20%20%20%20%20%20%20%20%20left%20%3D%20pivot%20%2B%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums%2C%20pivot%20%2B%201%2C%20right%29%0A%20%20%20%20%20%20%20%20%20%20%20%20right%20%3D%20pivot%20-%201%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20quick_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%27%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B9%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%28%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B5%D0%B9%29%20nums%20%3D%27%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_sorting/radix_sort.md ================================================ https://pythontutor.com/render.html#code=def%20digit%28num%3A%20int%2C%20exp%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20return%20num%20%2F%2F%20exp%20%25%2010%0A%0Adef%20counting_sort_digit%28nums%3A%20list%5Bint%5D%2C%20exp%3A%20int%29%3A%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%2010%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D%2C%20exp%29%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20%2B%3D%201%0A%20%20%20%20for%20i%20in%20range%281%2C%2010%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%5D%20%2B%3D%20counter%5Bi%20-%201%5D%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D%2C%20exp%29%0A%20%20%20%20%20%20%20%20j%20%3D%20counter%5Bd%5D%20-%201%0A%20%20%20%20%20%20%20%20res%5Bj%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20-%3D%201%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0Adef%20radix_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20exp%20%3D%201%0A%20%20%20%20while%20exp%20%3C%3D%20m%3A%0A%20%20%20%20%20%20%20%20counting_sort_digit%28nums%2C%20exp%29%0A%20%20%20%20%20%20%20%20exp%20%2A%3D%2010%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20nums%20%3D%20%5B105%2C%20356%2C%20428%2C%20348%2C%20818%5D%0A%20%20%20%20radix_sort%28nums%29%0A%20%20%20%20print%28%27%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%BF%D0%BE%D1%80%D0%B0%D0%B7%D1%80%D1%8F%D0%B4%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20nums%20%3D%27%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_sorting/selection_sort.md ================================================ https://pythontutor.com/render.html#code=def%20selection_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%BE%D0%BC%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%5Bi%2C%20n-1%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D0%BD%D1%83%D1%82%D1%80%D0%B5%D0%BD%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BD%D0%B0%D0%B9%D1%82%D0%B8%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%BC%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B5%0A%20%20%20%20%20%20%20%20k%20%3D%20i%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3C%20nums%5Bk%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20k%20%3D%20j%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D1%8D%D1%82%D0%BE%D1%82%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%20%D1%81%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%BC%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%BC%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B0%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bk%5D%20%3D%20nums%5Bk%5D%2C%20nums%5Bi%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20selection_sort%28nums%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%BE%D0%BC%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_stack_and_queue/array_queue.md ================================================ https://pythontutor.com/render.html#code=class%20ArrayQueue%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20size%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self._nums%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20%2A%20size%0A%20%20%20%20%20%20%20%20self._front%3A%20int%20%3D%200%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self._nums%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20self._size%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self._size%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%D0%B7%D0%B0%D0%BF%D0%BE%D0%BB%D0%BD%D0%B5%D0%BD%D0%B0%27%29%0A%20%20%20%20%20%20%20%20rear%3A%20int%20%3D%20%28self._front%20%2B%20self._size%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20self._nums%5Brear%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20num%3A%20int%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._front%20%3D%20%28self._front%20%2B%201%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%27%29%0A%20%20%20%20%20%20%20%20return%20self._nums%5Bself._front%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20self.size%28%29%0A%20%20%20%20%20%20%20%20j%3A%20int%20%3D%20self._front%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20self._nums%5Bj%20%25%20self.capacity%28%29%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20queue%20%3D%20ArrayQueue%2810%29%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%27%D0%9F%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20peek%20%3D%27%2C%20peek%29%0A%20%20%20%20pop%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%27%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%27%2C%20pop%29%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%27%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20size%20%3D%27%2C%20size%29%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%27%D0%9F%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%3D%27%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_stack_and_queue/array_stack.md ================================================ https://pythontutor.com/render.html#code=class%20ArrayStack%3A%0A%20%20%20%20%22%22%22%D0%A1%D1%82%D0%B5%D0%BA%20%D0%BD%D0%B0%20%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B5%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9A%D0%BE%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D0%BE%D1%80%22%22%22%0A%20%20%20%20%20%20%20%20self._stack%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._stack%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0%2C%20%D0%BF%D1%83%D1%81%D1%82%20%D0%BB%D0%B8%20%D1%81%D1%82%D0%B5%D0%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%20%3D%3D%20%5B%5D%0A%0A%20%20%20%20def%20push%28self%2C%20item%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%81%D1%82%D0%B5%D0%BA%22%22%22%0A%20%20%20%20%20%20%20%20self._stack.append%28item%29%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%D1%81%D1%82%D0%B5%D0%BA%20%D0%BF%D1%83%D1%81%D1%82%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack.pop%28%29%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%BC%D1%83%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%D1%81%D1%82%D0%B5%D0%BA%20%D0%BF%D1%83%D1%81%D1%82%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack%5B-1%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B4%D0%BB%D1%8F%20%D0%B2%D1%8B%D0%B2%D0%BE%D0%B4%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20stack%20%3D%20ArrayStack%28%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%B2%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%D0%A1%D1%82%D0%B5%D0%BA%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%BC%D1%83%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%D0%92%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22stack%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%83%D1%81%D1%82%D0%BE%D1%82%D1%83%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%D0%9F%D1%83%D1%81%D1%82%20%D0%BB%D0%B8%20%D1%81%D1%82%D0%B5%D0%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%0A%0Aclass%20LinkedListQueue%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._front%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._rear%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20not%20self._front%0A%0A%20%20%20%20def%20push%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28num%29%0A%20%20%20%20%20%20%20%20if%20self._front%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._front%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear.next%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._front%20%3D%20self._front.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%27%29%0A%20%20%20%20%20%20%20%20return%20self._front.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20queue%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20temp%20%3D%20self._front%0A%20%20%20%20%20%20%20%20while%20temp%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28temp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20temp.next%0A%20%20%20%20%20%20%20%20return%20queue%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20queue%20%3D%20LinkedListQueue%28%29%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%20%20%20%20print%28%27%D0%9E%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20queue%20%3D%27%2C%20queue.to_list%28%29%29%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%27%D0%9F%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20front%20%3D%27%2C%20peek%29%0A%20%20%20%20pop_front%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%27%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%27%2C%20pop_front%29%0A%20%20%20%20print%28%27queue%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%3D%27%2C%20queue.to_list%28%29%29%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%27%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20size%20%3D%27%2C%20size%29%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%27%D0%9F%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%3D%27%2C%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%0A%0Aclass%20LinkedListStack%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._peek%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20not%20self._peek%0A%0A%20%20%20%20def%20push%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28val%29%0A%20%20%20%20%20%20%20%20node.next%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20self._peek.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D1%81%D1%82%D0%B5%D0%BA%20%D0%BF%D1%83%D1%81%D1%82%27%29%0A%20%20%20%20%20%20%20%20return%20self._peek.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20arr%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20node%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20while%20node%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20arr.append%28node.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20node%20%3D%20node.next%0A%20%20%20%20%20%20%20%20arr.reverse%28%29%0A%20%20%20%20%20%20%20%20return%20arr%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20stack%20%3D%20LinkedListStack%28%29%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%27%D0%A1%D1%82%D0%B5%D0%BA%20stack%20%3D%27%2C%20stack.to_list%28%29%29%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%27%D0%92%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20peek%20%3D%27%2C%20peek%29%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%27%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%27%2C%20pop%29%0A%20%20%20%20print%28%27stack%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%3D%27%2C%20stack.to_list%28%29%29%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%27%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20size%20%3D%27%2C%20size%29%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%27%D0%9F%D1%83%D1%81%D1%82%20%D0%BB%D0%B8%20%D1%81%D1%82%D0%B5%D0%BA%20%3D%27%2C%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_tree/array_binary_tree.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20ArrayBinaryTree%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20arr%3A%20list%5Bint%20%7C%20None%5D%29%3A%0A%20%20%20%20%20%20%20%20self._tree%20%3D%20list%28arr%29%0A%0A%20%20%20%20def%20size%28self%29%3A%0A%20%20%20%20%20%20%20%20return%20len%28self._tree%29%0A%0A%20%20%20%20def%20val%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20self._tree%5Bi%5D%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%0A%0A%20%20%20%20def%20level_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20dfs%28self%2C%20i%3A%20int%2C%20order%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%27pre%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.left%28i%29%2C%20order%29%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%27in%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.right%28i%29%2C%20order%29%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%27post%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%0A%20%20%20%20def%20pre_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%27pre%27%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20in_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%27in%27%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20post_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%27post%27%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20arr%20%3D%20%5B1%2C%202%2C%203%2C%204%2C%20None%2C%206%2C%20None%5D%0A%20%20%20%20abt%20%3D%20ArrayBinaryTree%28arr%29%0A%20%20%20%20i%20%3D%201%0A%20%20%20%20%28l%2C%20r%2C%20p%29%20%3D%20%28abt.left%28i%29%2C%20abt.right%28i%29%2C%20abt.parent%28i%29%29%0A%20%20%20%20res%20%3D%20abt.level_order%28%29%0A%20%20%20%20res%20%3D%20abt.pre_order%28%29%0A%20%20%20%20res%20%3D%20abt.in_order%28%29%0A%20%20%20%20res%20%3D%20abt.post_order%28%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_tree/binary_search_tree.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20BinarySearchTree%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20search%28self%2C%20num%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20cur%20%3D%20self._root%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20cur.val%20%3E%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20return%20cur%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%28cur%2C%20pre%29%20%3D%20%28self._root%2C%20None%29%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%20%20%20%20node%20%3D%20bst.search%287%29%0A%20%20%20%20print%28%27%5Cn%D0%9D%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%20%D1%83%D0%B7%D0%BB%D0%B0%20%3D%20%7B%7D%2C%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%20%3D%20%7B%7D%27.format%28node%2C%20node.val%29%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9A%D0%BE%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D0%BE%D1%80%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BF%D1%83%D1%81%D1%82%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D1%83%D0%B7%D0%BB%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%20%D0%BF%D1%83%D1%81%D1%82%D0%BE%2C%20%D0%B8%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BA%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D1%81%D0%BA%D0%B0%D1%82%D1%8C%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20%D0%B8%20%D0%B2%D1%8B%D0%B9%D1%82%D0%B8%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%BF%D1%80%D0%BE%D1%85%D0%BE%D0%B4%D0%B0%20%D0%B7%D0%B0%20%D0%BB%D0%B8%D1%81%D1%82%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%B9%D1%81%D1%8F%20%D1%83%D0%B7%D0%B5%D0%BB%20%D0%B8%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8F%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%BC%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8F%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%BC%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20bst.insert%2816%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20BinarySearchTree%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%28cur%2C%20pre%29%20%3D%20%28self._root%2C%20None%29%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%20%20%20%20def%20remove%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%28cur%2C%20pre%29%20%3D%20%28self._root%2C%20None%29%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20if%20cur%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20if%20cur.left%20is%20None%20or%20cur.right%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20child%20%3D%20cur.left%20or%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur%20%21%3D%20self._root%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20pre.left%20%3D%3D%20cur%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20child%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%3A%20TreeNode%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20tmp.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20tmp.left%0A%20%20%20%20%20%20%20%20%20%20%20%20self.remove%28tmp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20cur.val%20%3D%20tmp.val%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%20%20%20%20bst.remove%281%29%0A%20%20%20%20bst.remove%282%29%0A%20%20%20%20bst.remove%284%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_tree/binary_tree_bfs.md ================================================ https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0Adef%20level_order%28root%3A%20TreeNode%20%7C%20None%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20queue%3A%20deque%5BTreeNode%5D%20%3D%20deque%28%29%0A%20%20%20%20queue.append%28root%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20while%20queue%3A%0A%20%20%20%20%20%20%20%20node%3A%20TreeNode%20%3D%20queue.popleft%28%29%0A%20%20%20%20%20%20%20%20res.append%28node.val%29%0A%20%20%20%20%20%20%20%20if%20node.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.left%29%0A%20%20%20%20%20%20%20%20if%20node.right%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.right%29%0A%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%20%20%20%20res%20%3D%20level_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%D0%B2%20%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83%20%3D%20%27%2C%20res%29&cumulative=false&curInstr=127&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/pythontutor/chapter_tree/binary_tree_dfs.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0Adef%20pre_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20pre_order%28root%3Droot.left%29%0A%20%20%20%20pre_order%28root%3Droot.right%29%0A%0Adef%20in_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20in_order%28root%3Droot.left%29%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20in_order%28root%3Droot.right%29%0A%0Adef%20post_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20post_order%28root%3Droot.left%29%0A%20%20%20%20post_order%28root%3Droot.right%29%0A%20%20%20%20res.append%28root.val%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20pre_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D0%BF%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%3D%20%27%2C%20res%29%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20in_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D0%BE%D0%BC%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%3D%20%27%2C%20res%29%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20post_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%BC%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%3D%20%27%2C%20res%29&cumulative=false&curInstr=129&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/codes/ruby/chapter_array_and_linkedlist/array.rb ================================================ =begin File: array.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Случайный доступ к элементу ### def random_access(nums) # Случайным образом выбрать число из интервала [0, nums.length) random_index = Random.rand(0...nums.length) # Получить и вернуть случайный элемент nums[random_index] end ### Увеличить длину массива ### # Обратите внимание: Array в Ruby является динамическим массивом и может расширяться напрямую # Для удобства обучения в этой функции Array рассматривается как массив неизменяемой длины def extend(nums, enlarge) # Инициализировать массив увеличенной длины res = Array.new(nums.length + enlarge, 0) # Скопировать все элементы исходного массива в новый массив for i in 0...nums.length res[i] = nums[i] end # Вернуть новый массив после расширения res end ### Вставка элемента num по индексу index в массив ### def insert(nums, num, index) # Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад for i in (nums.length - 1).downto(index + 1) nums[i] = nums[i - 1] end # Присвоить num элементу по индексу index nums[index] = num end ### Удаление элемента по индексу index ### def remove(nums, index) # Сдвинуть все элементы после индекса index на одну позицию вперед for i in index...(nums.length - 1) nums[i] = nums[i + 1] end end ### Обход массива ### def traverse(nums) count = 0 # Обход массива по индексам for i in 0...nums.length count += nums[i] end # Непосредственно обходить элементы массива for num in nums count += num end end ### Поиск заданного элемента в массиве ### def find(nums, target) for i in 0...nums.length return i if nums[i] == target end -1 end ### Driver Code ### if __FILE__ == $0 # Инициализация массива arr = Array.new(5, 0) puts "Массив arr = #{arr}" nums = [1, 3, 2, 5, 4] puts "Массив nums = #{nums}" # Случайный доступ random_num = random_access(nums) puts "Случайный элемент из nums = #{random_num}" # Расширение длины nums = extend(nums, 3) puts "После увеличения длины массива до 8 nums = #{nums}" # Вставка элемента insert(nums, 6, 3) puts "После вставки числа 6 по индексу 3 nums = #{nums}" # Удаление элемента remove(nums, 2) puts "После удаления элемента по индексу 2 nums = #{nums}" # Обход массива traverse(nums) # Поиск элемента index = find(nums, 3) puts "Поиск элемента 3 в nums: индекс = #{index}" end ================================================ FILE: ru/codes/ruby/chapter_array_and_linkedlist/linked_list.rb ================================================ =begin File: linked_list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' require_relative '../utils/print_util' ### Вставка узла _p после узла n0 в связном списке ### # В Ruby `p` является встроенной функцией, а `P` — константой, поэтому вместо них можно использовать `_p` def insert(n0, _p) n1 = n0.next _p.next = n1 n0.next = _p end ### Удаление первого узла после узла n0 в связном списке ### def remove(n0) return if n0.next.nil? # n0 -> remove_node -> n1 remove_node = n0.next n1 = remove_node.next n0.next = n1 end ### Доступ к узлу связного списка по индексу index ### def access(head, index) for i in 0...index return nil if head.nil? head = head.next end head end ### Поиск первого узла со значением target в связном списке ### def find(head, target) index = 0 while head return index if head.val == target head = head.next index += 1 end -1 end ### Driver Code ### if __FILE__ == $0 # Инициализация связного списка # Инициализация всех узлов n0 = ListNode.new(1) n1 = ListNode.new(3) n2 = ListNode.new(2) n3 = ListNode.new(5) n4 = ListNode.new(4) # Построить ссылки между узлами n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 puts "Исходный связный список" print_linked_list(n0) # Вставка узла insert(n0, ListNode.new(0)) print_linked_list n0 # Удаление узла remove(n0) puts "Связный список после удаления узла" print_linked_list(n0) # Доступ к узлу node = access(n0, 3) puts "Значение узла по индексу 3 в связном списке = #{node.val}" # Поиск узла index = find(n0, 2) puts "Индекс узла со значением 2 в связном списке = #{index}" end ================================================ FILE: ru/codes/ruby/chapter_array_and_linkedlist/list.rb ================================================ =begin File: list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # Инициализация списка nums = [1, 3, 2, 5, 4] puts "Список nums = #{nums}" # Доступ к элементу num = nums[1] puts "Элемент по индексу 1: num = #{num}" # Обновление элемента nums[1] = 0 puts "После обновления элемента по индексу 1 до 0 nums = #{nums}" # Очистить список nums.clear puts "После очистки списка nums = #{nums}" # Добавление элемента в конец nums << 1 nums << 3 nums << 2 nums << 5 nums << 4 puts "После добавления элементов nums = #{nums}" # Вставка элемента в середину nums.insert(3, 6) puts "После вставки элемента 6 по индексу 3 nums = #{nums}" # Удаление элемента nums.delete_at(3) puts "После удаления элемента по индексу 3 nums = #{nums}" # Обходить список по индексам count = 0 for i in 0...nums.length count += nums[i] end # Непосредственно обходить элементы списка count = 0 nums.each do |x| count += x end # Объединить два списка nums1 = [6, 8, 7, 10, 9] nums += nums1 puts "После конкатенации списка nums1 к nums nums = #{nums}" nums = nums.sort { |a, b| a <=> b } puts "После сортировки списка nums = #{nums}" end ================================================ FILE: ru/codes/ruby/chapter_array_and_linkedlist/my_list.rb ================================================ =begin File: my_list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Класс списка ### class MyList attr_reader :size # Получить длину списка (текущее число элементов) attr_reader :capacity # Получить вместимость списка ### Конструктор ### def initialize @capacity = 10 @size = 0 @extend_ratio = 2 @arr = Array.new(capacity) end ### Доступ к элементу ### def get(index) # Если индекс выходит за границы, выбрасывается исключение; далее аналогично raise IndexError, "индекс выходит за границы" if index < 0 || index >= size @arr[index] end ### Доступ к элементу ### def set(index, num) raise IndexError, "индекс выходит за границы" if index < 0 || index >= size @arr[index] = num end ### Добавление элемента в конец ### def add(num) # При превышении вместимости по числу элементов запускается расширение extend_capacity if size == capacity @arr[size] = num # Обновить число элементов @size += 1 end ### Вставка элемента в середину ### def insert(index, num) raise IndexError, "индекс выходит за границы" if index < 0 || index >= size # При превышении вместимости по числу элементов запускается расширение extend_capacity if size == capacity # Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад for j in (size - 1).downto(index) @arr[j + 1] = @arr[j] end @arr[index] = num # Обновить число элементов @size += 1 end ### Удаление элемента ### def remove(index) raise IndexError, "индекс выходит за границы" if index < 0 || index >= size num = @arr[index] # Сдвинуть все элементы после индекса index на одну позицию вперед for j in index...size @arr[j] = @arr[j + 1] end # Обновить число элементов @size -= 1 # Вернуть удаленный элемент num end ### Расширение списка ### def extend_capacity # Создать новый массив длиной в extend_ratio раз больше исходного и скопировать в него исходный массив arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1)) # Обновить вместимость списка @capacity = arr.length end ### Преобразование списка в массив ### def to_array sz = size # Преобразовывать только элементы списка в пределах фактической длины arr = Array.new(sz) for i in 0...sz arr[i] = get(i) end arr end end ### Driver Code ### if __FILE__ == $0 # Инициализация списка nums = MyList.new # Добавление элемента в конец nums.add(1) nums.add(3) nums.add(2) nums.add(5) nums.add(4) puts "Список nums = #{nums.to_array}, емкость = #{nums.capacity}, длина = #{nums.size}" # Вставка элемента в середину nums.insert(3, 6) puts "После вставки числа 6 по индексу 3 nums = #{nums.to_array}" # Удаление элемента nums.remove(3) puts "После удаления элемента по индексу 3 nums = #{nums.to_array}" # Доступ к элементу num = nums.get(1) puts "Элемент по индексу 1: num = #{num}" # Обновление элемента nums.set(1, 0) puts "После обновления элемента по индексу 1 до 0 nums = #{nums.to_array}" # Проверка механизма расширения for i in 0...10 # При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения nums.add(i) end puts "После расширения список nums = #{nums.to_array}, емкость = #{nums.capacity}, длина = #{nums.size}" end ================================================ FILE: ru/codes/ruby/chapter_backtracking/n_queens.rb ================================================ =begin File: n_queens.rb Created Time: 2024-05-21 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Алгоритм бэктрекинга: n ферзей ### def backtrack(row, n, state, res, cols, diags1, diags2) # Когда все строки уже обработаны, записать решение if row == n res << state.map { |row| row.dup } return end # Обойти все столбцы for col in 0...n # Вычислить главную и побочную диагонали, соответствующие этой клетке diag1 = row - col + n - 1 diag2 = row + col # Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if !cols[col] && !diags1[diag1] && !diags2[diag2] # Попытка: поставить ферзя в эту клетку state[row][col] = "Q" cols[col] = diags1[diag1] = diags2[diag2] = true # Перейти к размещению следующей строки backtrack(row + 1, n, state, res, cols, diags1, diags2) # Откат: восстановить эту клетку как пустую state[row][col] = "#" cols[col] = diags1[diag1] = diags2[diag2] = false end end end ### Решить задачу о n ферзях ### def n_queens(n) # Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку state = Array.new(n) { Array.new(n, "#") } cols = Array.new(n, false) # Отмечать, есть ли ферзь в столбце diags1 = Array.new(2 * n - 1, false) # Отмечать наличие ферзя на главной диагонали diags2 = Array.new(2 * n - 1, false) # Отмечать наличие ферзя на побочной диагонали res = [] backtrack(0, n, state, res, cols, diags1, diags2) res end ### Driver Code ### if __FILE__ == $0 n = 4 res = n_queens(n) puts "Размер входной доски = #{n}" puts "Количество способов расстановки ферзей: #{res.length}" for state in res puts "--------------------" for row in state p row end end end ================================================ FILE: ru/codes/ruby/chapter_backtracking/permutations_i.rb ================================================ =begin File: permutations_i.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Алгоритм бэктрекинга: все перестановки I ### def backtrack(state, choices, selected, res) # Когда длина состояния равна числу элементов, записать решение if state.length == choices.length res << state.dup return end # Перебор всех вариантов выбора choices.each_with_index do |choice, i| # Отсечение: нельзя выбирать один и тот же элемент повторно unless selected[i] # Попытка: сделать выбор и обновить состояние selected[i] = true state << choice # Перейти к следующему выбору backtrack(state, choices, selected, res) # Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false state.pop end end end ### Все перестановки I ### def permutations_i(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ### Driver Code ### if __FILE__ == $0 nums = [1, 2, 3] res = permutations_i(nums) puts "Входной массив nums = #{nums}" puts "Все перестановки res = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_backtracking/permutations_ii.rb ================================================ =begin File: permutations_ii.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Алгоритм бэктрекинга: все перестановки II ### def backtrack(state, choices, selected, res) # Когда длина состояния равна числу элементов, записать решение if state.length == choices.length res << state.dup return end # Перебор всех вариантов выбора duplicated = Set.new choices.each_with_index do |choice, i| # Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if !selected[i] && !duplicated.include?(choice) # Попытка: сделать выбор и обновить состояние duplicated.add(choice) selected[i] = true state << choice # Перейти к следующему выбору backtrack(state, choices, selected, res) # Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false state.pop end end end ### Все перестановки II ### def permutations_ii(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ### Driver Code ### if __FILE__ == $0 nums = [1, 2, 2] res = permutations_ii(nums) puts "Входной массив nums = #{nums}" puts "Все перестановки res = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb ================================================ =begin File: preorder_traversal_i_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Предварительный обход: пример 1 ### def pre_order(root) return unless root # Записать решение $res << root if root.val == 7 pre_order(root.left) pre_order(root.right) end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\nИнициализация двоичного дерева" print_tree(root) # Предварительный обход $res = [] pre_order(root) puts "\nВсе узлы со значением 7" p $res.map { |node| node.val } end ================================================ FILE: ru/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb ================================================ =begin File: preorder_traversal_ii_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Предварительный обход: пример 2 ### def pre_order(root) return unless root # Попытка $path << root # Записать решение $res << $path.dup if root.val == 7 pre_order(root.left) pre_order(root.right) # Откат $path.pop end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\nИнициализация двоичного дерева" print_tree(root) # Предварительный обход $path, $res = [], [] pre_order(root) puts "\nВсе пути от корня к узлу 7" for path in $res p path.map { |node| node.val } end end ================================================ FILE: ru/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb ================================================ =begin File: preorder_traversal_iii_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Предварительный обход: пример 3 ### def pre_order(root) # Отсечение return if !root || root.val == 3 # Попытка $path.append(root) # Записать решение $res << $path.dup if root.val == 7 pre_order(root.left) pre_order(root.right) # Откат $path.pop end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\nИнициализация двоичного дерева" print_tree(root) # Предварительный обход $path, $res = [], [] pre_order(root) puts "\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3" for path in $res p path.map { |node| node.val } end end ================================================ FILE: ru/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb ================================================ =begin File: preorder_traversal_iii_template.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Проверка, является ли текущее состояние решением ### def is_solution?(state) !state.empty? && state.last.val == 7 end ### Записать решение ### def record_solution(state, res) res << state.dup end ### Проверка допустимости этого выбора в текущем состоянии ### def is_valid?(state, choice) choice && choice.val != 3 end ### Обновить состояние ### def make_choice(state, choice) state << choice end ### Восстановить состояние ### def undo_choice(state, choice) state.pop end ### Алгоритм бэктрекинга: пример 3 ### def backtrack(state, choices, res) # Проверить, является ли текущее состояние решением record_solution(state, res) if is_solution?(state) # Перебор всех вариантов выбора for choice in choices # Отсечение: проверить допустимость выбора if is_valid?(state, choice) # Попытка: сделать выбор и обновить состояние make_choice(state, choice) # Перейти к следующему выбору backtrack(state, [choice.left, choice.right], res) # Откат: отменить выбор и восстановить предыдущее состояние undo_choice(state, choice) end end end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\nИнициализация двоичного дерева" print_tree(root) # Алгоритм бэктрекинга res = [] backtrack([], [root], res) puts "\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3" for path in res p path.map { |node| node.val } end end ================================================ FILE: ru/codes/ruby/chapter_backtracking/subset_sum_i.rb ================================================ =begin File: subset_sum_i.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Алгоритм бэктрекинга: сумма подмножеств I ### def backtrack(state, target, choices, start, res) # Если сумма подмножества равна target, записать решение if target.zero? res << state.dup return end # Обойти все варианты выбора # Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств for i in start...choices.length # Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл # Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target break if target - choices[i] < 0 # Попытка: сделать выбор и обновить target и start state << choices[i] # Перейти к следующему выбору backtrack(state, target - choices[i], choices, i, res) # Откат: отменить выбор и восстановить предыдущее состояние state.pop end end ### Решить задачу суммы подмножеств I ### def subset_sum_i(nums, target) state = [] # Состояние (подмножество) nums.sort! # Отсортировать nums start = 0 # Стартовая вершина обхода res = [] # Список результатов (список подмножеств) backtrack(state, target, nums, start, res) res end ### Driver Code ### if __FILE__ == $0 nums = [3, 4, 5] target = 9 res = subset_sum_i(nums, target) puts "Входной массив nums = #{nums}, target = #{target}" puts "Все подмножества с суммой #{target}: res = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb ================================================ =begin File: subset_sum_i_naive.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Алгоритм бэктрекинга: сумма подмножеств I ### def backtrack(state, target, total, choices, res) # Если сумма подмножества равна target, записать решение if total == target res << state.dup return end # Перебор всех вариантов выбора for i in 0...choices.length # Отсечение: если сумма подмножества превышает target, пропустить этот выбор next if total + choices[i] > target # Попытка: сделать выбор и обновить элемент и total state << choices[i] # Перейти к следующему выбору backtrack(state, target, total + choices[i], choices, res) # Откат: отменить выбор и восстановить предыдущее состояние state.pop end end # ## Решить задачу суммы подмножеств I (с повторяющимися подмножествами) ### def subset_sum_i_naive(nums, target) state = [] # Состояние (подмножество) total = 0 # Сумма подмножеств res = [] # Список результатов (список подмножеств) backtrack(state, target, total, nums, res) res end ### Driver Code ### if __FILE__ == $0 nums = [3, 4, 5] target = 9 res = subset_sum_i_naive(nums, target) puts "Входной массив nums = #{nums}, target = #{target}" puts "Все подмножества с суммой #{target}: res = #{res}" puts "Обратите внимание: результат этого метода содержит повторяющиеся множества" end ================================================ FILE: ru/codes/ruby/chapter_backtracking/subset_sum_ii.rb ================================================ =begin File: subset_sum_ii.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Алгоритм бэктрекинга: сумма подмножеств II ### def backtrack(state, target, choices, start, res) # Если сумма подмножества равна target, записать решение if target.zero? res << state.dup return end # Обойти все варианты выбора # Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств # Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента for i in start...choices.length # Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл # Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target break if target - choices[i] < 0 # Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить next if i > start && choices[i] == choices[i - 1] # Попытка: сделать выбор и обновить target и start state << choices[i] # Перейти к следующему выбору backtrack(state, target - choices[i], choices, i + 1, res) # Откат: отменить выбор и восстановить предыдущее состояние state.pop end end ### Решить задачу суммы подмножеств II ### def subset_sum_ii(nums, target) state = [] # Состояние (подмножество) nums.sort! # Отсортировать nums start = 0 # Стартовая вершина обхода res = [] # Список результатов (список подмножеств) backtrack(state, target, nums, start, res) res end ### Driver Code ### if __FILE__ == $0 nums = [4, 4, 5] target = 9 res = subset_sum_ii(nums, target) puts "Входной массив nums = #{nums}, target = #{target}" puts "Все подмножества с суммой #{target}: res = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_computational_complexity/iteration.rb ================================================ =begin File: iteration.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com), Cy (9738314@gmail.com) =end ### Цикл for ### def for_loop(n) res = 0 # Циклическое суммирование 1, 2, ..., n-1, n for i in 1..n res += i end res end ### Цикл while ### def while_loop(n) res = 0 i = 1 # Инициализация условной переменной # Циклическое суммирование 1, 2, ..., n-1, n while i <= n res += i i += 1 # Обновить условную переменную end res end # ## Цикл while (двойное обновление) ### def while_loop_ii(n) res = 0 i = 1 # Инициализация условной переменной # Циклическое суммирование 1, 4, 10, ... while i <= n res += i # Обновить условную переменную i += 1 i *= 2 end res end ### Двойной цикл for ### def nested_for_loop(n) res = "" # Цикл по i = 1, 2, ..., n-1, n for i in 1..n # Цикл по j = 1, 2, ..., n-1, n for j in 1..n res += "(#{i}, #{j}), " end end res end ### Driver Code ### if __FILE__ == $0 n = 5 res = for_loop(n) puts "\nРезультат суммирования в цикле for res = #{res}" res = while_loop(n) puts "\nРезультат суммирования в цикле while res = #{res}" res = while_loop_ii(n) puts "\nРезультат суммирования в цикле while (двойное обновление) res = #{res}" res = nested_for_loop(n) puts "\nРезультат обхода в двойном цикле for #{res}" end ================================================ FILE: ru/codes/ruby/chapter_computational_complexity/recursion.rb ================================================ =begin File: recursion.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Рекурсия ### def recur(n) # Условие завершения return 1 if n == 1 # Рекурсия: рекурсивный вызов res = recur(n - 1) # Возврат: вернуть результат n + res end ### Имитация рекурсии итерацией ### def for_loop_recur(n) # Использовать явный стек для имитации системного стека вызовов stack = [] res = 0 # Рекурсия: рекурсивный вызов for i in n.downto(0) # Имитировать «рекурсию» с помощью операции помещения в стек stack << i end # Возврат: вернуть результат while !stack.empty? res += stack.pop end # res = 1+2+3+...+n res end ### Хвостовая рекурсия ### def tail_recur(n, res) # Условие завершения return res if n == 0 # Хвостовой рекурсивный вызов tail_recur(n - 1, res + n) end ### Последовательность Фибоначчи: рекурсия ### def fib(n) # Условие завершения: f(1) = 0, f(2) = 1 return n - 1 if n == 1 || n == 2 # Рекурсивный вызов f(n) = f(n-1) + f(n-2) res = fib(n - 1) + fib(n - 2) # Вернуть результат f(n) res end ### Driver Code ### if __FILE__ == $0 n = 5 res = recur(n) puts "\nРезультат суммирования в рекурсивной функции res = #{res}" res = for_loop_recur(n) puts "\nРезультат суммирования при имитации рекурсии res = #{res}" res = tail_recur(n, 0) puts "\nРезультат суммирования в хвостовой рекурсии res = #{res}" res = fib(n) puts "\n#{n}-й элемент последовательности Фибоначчи: #{res}" end ================================================ FILE: ru/codes/ruby/chapter_computational_complexity/space_complexity.rb ================================================ =begin File: space_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Функция ### def function # Выполнить некоторые операции 0 end ### Постоянная сложность ### def constant(n) # Константы, переменные и объекты занимают O(1) памяти a = 0 nums = [0] * 10000 node = ListNode.new # Переменные в цикле занимают O(1) памяти (0...n).each { c = 0 } # Функции в цикле занимают O(1) памяти (0...n).each { function } end ### Линейная сложность ### def linear(n) # Список длины n занимает O(n) памяти nums = Array.new(n, 0) # Хеш-таблица длины n занимает O(n) памяти hmap = {} for i in 0...n hmap[i] = i.to_s end end # ## Линейная сложность (рекурсивная реализация) ### def linear_recur(n) puts "Рекурсия n = #{n}" return if n == 1 linear_recur(n - 1) end ### Квадратичная сложность ### def quadratic(n) # Двумерный список занимает O(n^2) памяти Array.new(n) { Array.new(n, 0) } end # ## Квадратичная сложность (рекурсивная реализация) ### def quadratic_recur(n) return 0 unless n > 0 # Длина массива nums равна n, n-1, ..., 2, 1 nums = Array.new(n, 0) quadratic_recur(n - 1) end # ## Экспоненциальная сложность (построение полного двоичного дерева) ### def build_tree(n) return if n == 0 TreeNode.new.tap do |root| root.left = build_tree(n - 1) root.right = build_tree(n - 1) end end ### Driver Code ### if __FILE__ == $0 n = 5 # Постоянная сложность constant(n) # Линейная сложность linear(n) linear_recur(n) # Квадратичная сложность quadratic(n) quadratic_recur(n) # Экспоненциальная сложность root = build_tree(n) print_tree(root) end ================================================ FILE: ru/codes/ruby/chapter_computational_complexity/time_complexity.rb ================================================ =begin File: time_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Постоянная сложность ### def constant(n) count = 0 size = 100000 (0...size).each { count += 1 } count end ### Линейная сложность ### def linear(n) count = 0 (0...n).each { count += 1 } count end # ## Линейная сложность (обход массива) ### def array_traversal(nums) count = 0 # Число итераций пропорционально длине массива for num in nums count += 1 end count end ### Квадратичная сложность ### def quadratic(n) count = 0 # Число итераций квадратично зависит от размера данных n for i in 0...n for j in 0...n count += 1 end end count end # ## Квадратичная сложность (пузырьковая сортировка) ### def bubble_sort(nums) count = 0 # Счетчик # Внешний цикл: неотсортированный диапазон [0, i] for i in (nums.length - 1).downto(0) # Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for j in 0...i if nums[j] > nums[j + 1] # Поменять местами nums[j] и nums[j + 1] tmp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = tmp count += 3 # Обмен элементов включает 3 элементарные операции end end end count end # ## Экспоненциальная сложность (итеративная реализация) ### def exponential(n) count, base = 0, 1 # На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) (0...n).each do (0...base).each { count += 1 } base *= 2 end # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 count end # ## Экспоненциальная сложность (рекурсивная реализация) ### def exp_recur(n) return 1 if n == 1 exp_recur(n - 1) + exp_recur(n - 1) + 1 end # ## Логарифмическая сложность (итеративная реализация) ### def logarithmic(n) count = 0 while n > 1 n /= 2 count += 1 end count end # ## Логарифмическая сложность (рекурсивная реализация) ### def log_recur(n) return 0 unless n > 1 log_recur(n / 2) + 1 end ### Линейно-логарифмическая сложность ### def linear_log_recur(n) return 1 unless n > 1 count = linear_log_recur(n / 2) + linear_log_recur(n / 2) (0...n).each { count += 1 } count end # ## Факториальная сложность (рекурсивная реализация) ### def factorial_recur(n) return 1 if n == 0 count = 0 # Из одного получается n (0...n).each { count += factorial_recur(n - 1) } count end ### Driver Code ### if __FILE__ == $0 # Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях n = 8 puts "Размер входных данных n = #{n}" count = constant(n) puts "Число операций константной сложности = #{count}" count = linear(n) puts "Число операций линейной сложности = #{count}" count = array_traversal(Array.new(n, 0)) puts "Число операций линейной сложности (обход массива) = #{count}" count = quadratic(n) puts "Число операций квадратичной сложности = #{count}" nums = Array.new(n) { |i| n - i } # [n, n-1, ..., 2, 1] count = bubble_sort(nums) puts "Число операций квадратичной сложности (пузырьковая сортировка) = #{count}" count = exponential(n) puts "Число операций экспоненциальной сложности (итеративная реализация) = #{count}" count = exp_recur(n) puts "Число операций экспоненциальной сложности (рекурсивная реализация) = #{count}" count = logarithmic(n) puts "Число операций логарифмической сложности (итеративная реализация) = #{count}" count = log_recur(n) puts "Число операций логарифмической сложности (рекурсивная реализация) = #{count}" count = linear_log_recur(n) puts "Число операций линейно-логарифмической сложности (рекурсивная реализация) = #{count}" count = factorial_recur(n) puts "Число операций факториальной сложности (рекурсивная реализация) = #{count}" end ================================================ FILE: ru/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb ================================================ =begin File: worst_best_time_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Создать массив с элементами: 1, 2, ..., n в случайном порядке ### def random_numbers(n) # Создать массив nums =: 1, 2, 3, ..., n nums = Array.new(n) { |i| i + 1 } # Случайно перемешать элементы массива nums.shuffle! end ### Найти индекс числа 1 в массиве nums ### def find_one(nums) for i in 0...nums.length # Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) # Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) return i if nums[i] == 1 end -1 end ### Driver Code ### if __FILE__ == $0 for i in 0...10 n = 100 nums = random_numbers(n) index = find_one(nums) puts "\nМассив [1, 2, ..., n] после перемешивания = #{nums}" puts "Индекс числа 1 = #{index}" end end ================================================ FILE: ru/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb ================================================ =begin File: binary_search_recur.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Бинарный поиск: задача f(i, j) ### def dfs(nums, target, i, j) # Если интервал пуст, целевой элемент отсутствует, вернуть -1 return -1 if i > j # Вычислить индекс середины m m = (i + j) / 2 if nums[m] < target # Рекурсивная подзадача f(m+1, j) return dfs(nums, target, m + 1, j) elsif nums[m] > target # Рекурсивная подзадача f(i, m-1) return dfs(nums, target, i, m - 1) else # Целевой элемент найден, вернуть его индекс return m end end ### Бинарный поиск ### def binary_search(nums, target) n = nums.length # Решить задачу f(0, n-1) dfs(nums, target, 0, n - 1) end ### Driver Code ### if __FILE__ == $0 target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # Бинарный поиск (двусторонне замкнутый интервал) index = binary_search(nums, target) puts "Индекс целевого элемента 6 = #{index}" end ================================================ FILE: ru/codes/ruby/chapter_divide_and_conquer/build_tree.rb ================================================ =begin File: build_tree.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Построить двоичное дерево: разделяй и властвуй ### def dfs(preorder, inorder_map, i, l, r) # Завершить при пустом диапазоне поддерева return if r - l < 0 # Инициализировать корневой узел root = TreeNode.new(preorder[i]) # Найти m, чтобы разделить левое и правое поддеревья m = inorder_map[preorder[i]] # Подзадача: построить левое поддерево root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) # Подзадача: построить правое поддерево root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) # Вернуть корневой узел root end ### Построить двоичное дерево ### def build_tree(preorder, inorder) # Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам inorder_map = {} inorder.each_with_index { |val, i| inorder_map[val] = i } dfs(preorder, inorder_map, 0, 0, inorder.length - 1) end ### Driver Code ### if __FILE__ == $0 preorder = [3, 9, 2, 1, 7] inorder = [9, 3, 1, 2, 7] puts "Предварительный обход = #{preorder}" puts "Симметричный обход = #{inorder}" root = build_tree(preorder, inorder) puts "Построенное двоичное дерево:" print_tree(root) end ================================================ FILE: ru/codes/ruby/chapter_divide_and_conquer/hanota.rb ================================================ =begin File: hanota.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Переместить один диск ### def move(src, tar) # Снять диск с вершины src pan = src.pop # Положить диск на вершину tar tar << pan end ### Решить задачу Ханойской башни f(i) ### def dfs(i, src, buf, tar) # Если в src остался только один диск, сразу переместить его в tar if i == 1 move(src, tar) return end # Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar dfs(i - 1, src, tar, buf) # Подзадача f(1): переместить оставшийся один диск из src в tar move(src, tar) # Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src dfs(i - 1, buf, src, tar) end ### Решить задачу Ханойской башни ### def solve_hanota(_A, _B, _C) n = _A.length # Переместить верхние n дисков из A в C с помощью B dfs(n, _A, _B, _C) end ### Driver Code ### if __FILE__ == $0 # Хвост списка соответствует вершине столбца A = [5, 4, 3, 2, 1] B = [] C = [] puts "Исходное состояние:" puts "A = #{A}" puts "B = #{B}" puts "C = #{C}" solve_hanota(A, B, C) puts "После завершения перемещения дисков:" puts "A = #{A}" puts "B = #{B}" puts "C = #{C}" end ================================================ FILE: ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb ================================================ =begin File: climbing_stairs_backtrack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Бэктрекинг ### def backtrack(choices, state, n, res) # Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 res[0] += 1 if state == n # Перебор всех вариантов выбора for choice in choices # Отсечение: нельзя выходить за n-ю ступень next if state + choice > n # Попытка: сделать выбор и обновить состояние backtrack(choices, state + choice, n, res) end # Откат end ### Подъем по лестнице: бэктрекинг ### def climbing_stairs_backtrack(n) choices = [1, 2] # Можно подняться на 1 или 2 ступени state = 0 # Начать подъем с 0-й ступени res = [0] # Использовать res[0] для хранения числа решений backtrack(choices, state, n, res) res.first end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_backtrack(n) puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}" end ================================================ FILE: ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb ================================================ =begin File: climbing_stairs_constraint_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Подъем по лестнице с ограничениями: динамическое программирование ### def climbing_stairs_constraint_dp(n) return 1 if n == 1 || n == 2 # Инициализация таблицы dp для хранения решений подзадач dp = Array.new(n + 1) { Array.new(3, 0) } # Начальное состояние: заранее задать решения наименьших подзадач dp[1][1], dp[1][2] = 1, 0 dp[2][1], dp[2][2] = 0, 1 # Переход состояний: постепенное решение больших подзадач через меньшие for i in 3...(n + 1) dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] end dp[n][1] + dp[n][2] end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_constraint_dp(n) puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}" end ================================================ FILE: ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb ================================================ =begin File: climbing_stairs_dfs.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Поиск ### def dfs(i) # dp[1] и dp[2] уже известны, вернуть их return i if i == 1 || i == 2 # dp[i] = dp[i-1] + dp[i-2] dfs(i - 1) + dfs(i - 2) end ### Подъем по лестнице: поиск ### def climbing_stairs_dfs(n) dfs(n) end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dfs(n) puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}" end ================================================ FILE: ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb ================================================ =begin File: climbing_stairs_dfs_mem.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Поиск с мемоизацией ### def dfs(i, mem) # dp[1] и dp[2] уже известны, вернуть их return i if i == 1 || i == 2 # Если запись dp[i] существует, сразу вернуть ее return mem[i] if mem[i] != -1 # dp[i] = dp[i-1] + dp[i-2] count = dfs(i - 1, mem) + dfs(i - 2, mem) # Сохранить dp[i] mem[i] = count end ### Подъем по лестнице: поиск с мемоизацией ### def climbing_stairs_dfs_mem(n) # mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи mem = Array.new(n + 1, -1) dfs(n, mem) end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dfs_mem(n) puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}" end ================================================ FILE: ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb ================================================ =begin File: climbing_stairs_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Подъем по лестнице: динамическое программирование ### def climbing_stairs_dp(n) return n if n == 1 || n == 2 # Инициализация таблицы dp для хранения решений подзадач dp = Array.new(n + 1, 0) # Начальное состояние: заранее задать решения наименьших подзадач dp[1], dp[2] = 1, 2 # Переход состояний: постепенное решение больших подзадач через меньшие (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] } dp[n] end ### Подъем по лестнице: динамическое программирование с оптимизацией памяти ### def climbing_stairs_dp_comp(n) return n if n == 1 || n == 2 a, b = 1, 2 (3...(n + 1)).each { a, b = b, a + b } b end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dp(n) puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}" res = climbing_stairs_dp_comp(n) puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}" end ================================================ FILE: ru/codes/ruby/chapter_dynamic_programming/coin_change.rb ================================================ =begin File: coin_change.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Размен монет: динамическое программирование ### def coin_change_dp(coins, amt) n = coins.length _MAX = amt + 1 # Инициализация таблицы dp dp = Array.new(n + 1) { Array.new(amt + 1, 0) } # Переход состояний: первая строка и первый столбец (1...(amt + 1)).each { |a| dp[0][a] = _MAX } # Переход состояний: остальные строки и столбцы for i in 1...(n + 1) for a in 1...(amt + 1) if coins[i - 1] > a # Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a] else # Меньшее из двух решений: не брать или взять монету i dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min end end end dp[n][amt] != _MAX ? dp[n][amt] : -1 end ### Размен монет: динамическое программирование с оптимизацией памяти ### def coin_change_dp_comp(coins, amt) n = coins.length _MAX = amt + 1 # Инициализация таблицы dp dp = Array.new(amt + 1, _MAX) dp[0] = 0 # Переход состояний for i in 1...(n + 1) # Прямой обход for a in 1...(amt + 1) if coins[i - 1] > a # Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a] else # Меньшее из двух решений: не брать или взять монету i dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min end end end dp[amt] != _MAX ? dp[amt] : -1 end ### Driver Code ### if __FILE__ == $0 coins = [1, 2, 5] amt = 4 # Динамическое программирование res = coin_change_dp(coins, amt) puts "Минимальное число монет для набора целевой суммы = #{res}" # Динамическое программирование с оптимизацией памяти res = coin_change_dp_comp(coins, amt) puts "Минимальное число монет для набора целевой суммы = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb ================================================ =begin File: coin_change_ii.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Размен монет II: динамическое программирование ### def coin_change_ii_dp(coins, amt) n = coins.length # Инициализация таблицы dp dp = Array.new(n + 1) { Array.new(amt + 1, 0) } # Инициализация первого столбца (0...(n + 1)).each { |i| dp[i][0] = 1 } # Переход состояний for i in 1...(n + 1) for a in 1...(amt + 1) if coins[i - 1] > a # Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a] else # Сумма двух решений: не брать или взять монету i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] end end end dp[n][amt] end ### Размен монет II: динамическое программирование с оптимизацией памяти ### def coin_change_ii_dp_comp(coins, amt) n = coins.length # Инициализация таблицы dp dp = Array.new(amt + 1, 0) dp[0] = 1 # Переход состояний for i in 1...(n + 1) # Прямой обход for a in 1...(amt + 1) if coins[i - 1] > a # Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a] else # Сумма двух решений: не брать или взять монету i dp[a] = dp[a] + dp[a - coins[i - 1]] end end end dp[amt] end ### Driver Code ### if __FILE__ == $0 coins = [1, 2, 5] amt = 5 # Динамическое программирование res = coin_change_ii_dp(coins, amt) puts "Количество комбинаций монет для набора целевой суммы = #{res}" # Динамическое программирование с оптимизацией памяти res = coin_change_ii_dp_comp(coins, amt) puts "Количество комбинаций монет для набора целевой суммы = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_dynamic_programming/edit_distance.rb ================================================ =begin File: edit_distance.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Редакционное расстояние: полный перебор ### def edit_distance_dfs(s, t, i, j) # Если s и t пусты, вернуть 0 return 0 if i == 0 && j == 0 # Если s пусто, вернуть длину t return j if i == 0 # Если t пусто, вернуть длину s return i if j == 0 # Если два символа равны, сразу пропустить их return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1] # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 insert = edit_distance_dfs(s, t, i, j - 1) delete = edit_distance_dfs(s, t, i - 1, j) replace = edit_distance_dfs(s, t, i - 1, j - 1) # Вернуть минимальное число шагов редактирования [insert, delete, replace].min + 1 end def edit_distance_dfs_mem(s, t, mem, i, j) # Если s и t пусты, вернуть 0 return 0 if i == 0 && j == 0 # Если s пусто, вернуть длину t return j if i == 0 # Если t пусто, вернуть длину s return i if j == 0 # Если запись уже есть, сразу вернуть ее return mem[i][j] if mem[i][j] != -1 # Если два символа равны, сразу пропустить их return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1] # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) # Сохранить и вернуть минимальное число шагов редактирования mem[i][j] = [insert, delete, replace].min + 1 end ### Редакционное расстояние: динамическое программирование ### def edit_distance_dp(s, t) n, m = s.length, t.length dp = Array.new(n + 1) { Array.new(m + 1, 0) } # Переход состояний: первая строка и первый столбец (1...(n + 1)).each { |i| dp[i][0] = i } (1...(m + 1)).each { |j| dp[0][j] = j } # Переход состояний: остальные строки и столбцы for i in 1...(n + 1) for j in 1...(m +1) if s[i - 1] == t[j - 1] # Если два символа равны, сразу пропустить их dp[i][j] = dp[i - 1][j - 1] else # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1 end end end dp[n][m] end ### Редакционное расстояние: динамическое программирование с оптимизацией памяти ### def edit_distance_dp_comp(s, t) n, m = s.length, t.length dp = Array.new(m + 1, 0) # Переход состояний: первая строка (1...(m + 1)).each { |j| dp[j] = j } # Переход состояний: остальные строки for i in 1...(n + 1) # Переход состояний: первый столбец leftup = dp.first # Временно сохранить dp[i-1, j-1] dp[0] += 1 # Переход состояний: остальные столбцы for j in 1...(m + 1) temp = dp[j] if s[i - 1] == t[j - 1] # Если два символа равны, сразу пропустить их dp[j] = leftup else # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[j] = [dp[j - 1], dp[j], leftup].min + 1 end leftup = temp # Обновить до значения dp[i-1, j-1] для следующей итерации end end dp[m] end ### Driver Code ### if __FILE__ == $0 s = 'bag' t = 'pack' n, m = s.length, t.length # Полный перебор res = edit_distance_dfs(s, t, n, m) puts "Чтобы преобразовать #{s} в #{t}, требуется минимум #{res} правок" # Поиск с мемоизацией mem = Array.new(n + 1) { Array.new(m + 1, -1) } res = edit_distance_dfs_mem(s, t, mem, n, m) puts "Чтобы преобразовать #{s} в #{t}, требуется минимум #{res} правок" # Динамическое программирование res = edit_distance_dp(s, t) puts "Чтобы преобразовать #{s} в #{t}, требуется минимум #{res} правок" # Динамическое программирование с оптимизацией памяти res = edit_distance_dp_comp(s, t) puts "Чтобы преобразовать #{s} в #{t}, требуется минимум #{res} правок" end ================================================ FILE: ru/codes/ruby/chapter_dynamic_programming/knapsack.rb ================================================ =begin File: knapsack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Рюкзак 0-1: полный перебор ### def knapsack_dfs(wgt, val, i, c) # Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 return 0 if i == 0 || c == 0 # Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c # Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут no = knapsack_dfs(wgt, val, i - 1, c) yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] # Вернуть вариант с большей стоимостью из двух возможных [no, yes].max end ### Рюкзак 0-1: поиск с мемоизацией ### def knapsack_dfs_mem(wgt, val, mem, i, c) # Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 return 0 if i == 0 || c == 0 # Если запись уже есть, вернуть сразу return mem[i][c] if mem[i][c] != -1 # Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c # Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] # Сохранить и вернуть вариант с большей стоимостью из двух решений mem[i][c] = [no, yes].max end ### Рюкзак 0-1: динамическое программирование ### def knapsack_dp(wgt, val, cap) n = wgt.length # Инициализация таблицы dp dp = Array.new(n + 1) { Array.new(cap + 1, 0) } # Переход состояний for i in 1...(n + 1) for c in 1...(cap + 1) if wgt[i - 1] > c # Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c] else # Большее из двух решений: не брать или взять предмет i dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max end end end dp[n][cap] end ### Рюкзак 0-1: динамическое программирование с оптимизацией памяти ### def knapsack_dp_comp(wgt, val, cap) n = wgt.length # Инициализация таблицы dp dp = Array.new(cap + 1, 0) # Переход состояний for i in 1...(n + 1) # Обход в обратном порядке for c in cap.downto(1) if wgt[i - 1] > c # Если вместимость рюкзака превышена, предмет i не выбирать dp[c] = dp[c] else # Большее из двух решений: не брать или взять предмет i dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max end end end dp[cap] end ### Driver Code ### if __FILE__ == $0 wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = wgt.length # Полный перебор res = knapsack_dfs(wgt, val, n, cap) puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" # Поиск с мемоизацией mem = Array.new(n + 1) { Array.new(cap + 1, -1) } res = knapsack_dfs_mem(wgt, val, mem, n, cap) puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" # Динамическое программирование res = knapsack_dp(wgt, val, cap) puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" # Динамическое программирование с оптимизацией памяти res = knapsack_dp_comp(wgt, val, cap) puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb ================================================ =begin File: min_cost_climbing_stairs_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Минимальная стоимость подъема по лестнице: динамическое программирование ### def min_cost_climbing_stairs_dp(cost) n = cost.length - 1 return cost[n] if n == 1 || n == 2 # Инициализация таблицы dp для хранения решений подзадач dp = Array.new(n + 1, 0) # Начальное состояние: заранее задать решения наименьших подзадач dp[1], dp[2] = cost[1], cost[2] # Переход состояний: постепенное решение больших подзадач через меньшие (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] } dp[n] end # Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти def min_cost_climbing_stairs_dp_comp(cost) n = cost.length - 1 return cost[n] if n == 1 || n == 2 a, b = cost[1], cost[2] (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] } b end ### Driver Code ### if __FILE__ == $0 cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] puts "Список стоимостей ступеней = #{cost}" res = min_cost_climbing_stairs_dp(cost) puts "Минимальная стоимость подъема по лестнице = #{res}" res = min_cost_climbing_stairs_dp_comp(cost) puts "Минимальная стоимость подъема по лестнице = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_dynamic_programming/min_path_sum.rb ================================================ =begin File: min_path_sum.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Минимальная сумма пути: полный перебор ### def min_path_sum_dfs(grid, i, j) # Если это верхняя левая ячейка, завершить поиск return grid[i][j] if i == 0 && j == 0 # Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ return Float::INFINITY if i < 0 || j < 0 # Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) up = min_path_sum_dfs(grid, i - 1, j) left = min_path_sum_dfs(grid, i, j - 1) # Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) [left, up].min + grid[i][j] end ### Минимальная сумма пути: поиск с мемоизацией ### def min_path_sum_dfs_mem(grid, mem, i, j) # Если это верхняя левая ячейка, завершить поиск return grid[0][0] if i == 0 && j == 0 # Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ return Float::INFINITY if i < 0 || j < 0 # Если запись уже есть, вернуть сразу return mem[i][j] if mem[i][j] != -1 # Минимальная стоимость пути для левой и верхней ячеек up = min_path_sum_dfs_mem(grid, mem, i - 1, j) left = min_path_sum_dfs_mem(grid, mem, i, j - 1) # Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) mem[i][j] = [left, up].min + grid[i][j] end ### Минимальная сумма пути: динамическое программирование ### def min_path_sum_dp(grid) n, m = grid.length, grid.first.length # Инициализация таблицы dp dp = Array.new(n) { Array.new(m, 0) } dp[0][0] = grid[0][0] # Переход состояний: первая строка (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] } # Переход состояний: первый столбец (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] } # Переход состояний: остальные строки и столбцы for i in 1...n for j in 1...m dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j] end end dp[n -1][m -1] end ### Минимальная сумма пути: динамическое программирование с оптимизацией памяти ### def min_path_sum_dp_comp(grid) n, m = grid.length, grid.first.length # Инициализация таблицы dp dp = Array.new(m, 0) # Переход состояний: первая строка dp[0] = grid[0][0] (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] } # Переход состояний: остальные строки for i in 1...n # Переход состояний: первый столбец dp[0] = dp[0] + grid[i][0] # Переход состояний: остальные столбцы (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] } end dp[m - 1] end ### Driver Code ### if __FILE__ == $0 grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] n, m = grid.length, grid.first.length # Полный перебор res = min_path_sum_dfs(grid, n - 1, m - 1) puts "Минимальная сумма пути из левого верхнего угла в правый нижний = #{res}" # Поиск с мемоизацией mem = Array.new(n) { Array.new(m, - 1) } res = min_path_sum_dfs_mem(grid, mem, n - 1, m -1) puts "Минимальная сумма пути из левого верхнего угла в правый нижний = #{res}" # Динамическое программирование res = min_path_sum_dp(grid) puts "Минимальная сумма пути из левого верхнего угла в правый нижний = #{res}" # Динамическое программирование с оптимизацией памяти res = min_path_sum_dp_comp(grid) puts "Минимальная сумма пути из левого верхнего угла в правый нижний = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb ================================================ =begin File: unbounded_knapsack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Полный рюкзак: динамическое программирование ### def unbounded_knapsack_dp(wgt, val, cap) n = wgt.length # Инициализация таблицы dp dp = Array.new(n + 1) { Array.new(cap + 1, 0) } # Переход состояний for i in 1...(n + 1) for c in 1...(cap + 1) if wgt[i - 1] > c # Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c] else # Большее из двух решений: не брать или взять предмет i dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max end end end dp[n][cap] end # ## Полный рюкзак: динамическое программирование с оптимизацией памяти ##3 def unbounded_knapsack_dp_comp(wgt, val, cap) n = wgt.length # Инициализация таблицы dp dp = Array.new(cap + 1, 0) # Переход состояний for i in 1...(n + 1) # Прямой обход for c in 1...(cap + 1) if wgt[i -1] > c # Если вместимость рюкзака превышена, предмет i не выбирать dp[c] = dp[c] else # Большее из двух решений: не брать или взять предмет i dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max end end end dp[cap] end ### Driver Code ### if __FILE__ == $0 wgt = [1, 2, 3] val = [5, 11, 15] cap = 4 # Динамическое программирование res = unbounded_knapsack_dp(wgt, val, cap) puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" # Динамическое программирование с оптимизацией памяти res = unbounded_knapsack_dp_comp(wgt, val, cap) puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_graph/graph_adjacency_list.rb ================================================ =begin File: graph_adjacency_list.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/vertex' ### Класс неориентированного графа на основе списка смежности ### class GraphAdjList attr_reader :adj_list ### Конструктор ### def initialize(edges) # Список смежности, где key — вершина, а value — все смежные ей вершины @adj_list = {} # Добавить все вершины и ребра for edge in edges add_vertex(edge[0]) add_vertex(edge[1]) add_edge(edge[0], edge[1]) end end ### Получение числа вершин ### def size @adj_list.length end ### Добавление ребра ### def add_edge(vet1, vet2) raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) @adj_list[vet1] << vet2 @adj_list[vet2] << vet1 end ### Удаление ребра ### def remove_edge(vet1, vet2) raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) # Удалить ребро vet1 - vet2 @adj_list[vet1].delete(vet2) @adj_list[vet2].delete(vet1) end ### Добавление вершины ### def add_vertex(vet) return if @adj_list.include?(vet) # Добавить новый список в список смежности @adj_list[vet] = [] end ### Удаление вершины ### def remove_vertex(vet) raise ArgumentError unless @adj_list.include?(vet) # Удалить из списка смежности список, соответствующий вершине vet @adj_list.delete(vet) # Обойти списки других вершин и удалить все ребра, содержащие vet for vertex in @adj_list @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet) end end ### Вывести список смежности ### def __print__ puts 'Список смежности =' for vertex in @adj_list tmp = @adj_list[vertex.first].map { |v| v.val } puts "#{vertex.first.val}: #{tmp}," end end end ### Driver Code ### if __FILE__ == $0 # Инициализация неориентированного графа v = vals_to_vets([1, 3, 2, 5, 4]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ] graph = GraphAdjList.new(edges) puts "\nГраф после инициализации" graph.__print__ # Добавить ребро # Вершины 1 и 2, то есть v[0] и v[2] graph.add_edge(v[0], v[2]) puts "\nГраф после добавления ребра 1-2" graph.__print__ # Удалить ребро # Вершины 1 и 3 соответствуют v[0], v[1] graph.remove_edge(v[0], v[1]) puts "\nГраф после удаления ребра 1-3" graph.__print__ # Добавление вершины v5 = Vertex.new(6) graph.add_vertex(v5) puts "\nГраф после добавления вершины 6" graph.__print__ # Удаление вершины # Вершина 3 соответствует v[1] graph.remove_vertex(v[1]) puts "\nГраф после удаления вершины 3" graph.__print__ end ================================================ FILE: ru/codes/ruby/chapter_graph/graph_adjacency_matrix.rb ================================================ =begin File: graph_adjacency_matrix.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/print_util' ### Класс неориентированного графа на основе матрицы смежности ### class GraphAdjMat def initialize(vertices, edges) ### Конструктор ### # Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» @vertices = [] # Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» @adj_mat = [] # Добавление вершины vertices.each { |val| add_vertex(val) } # Добавить ребра # Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices edges.each { |e| add_edge(e[0], e[1]) } end ### Получение числа вершин ### def size @vertices.length end ### Добавление вершины ### def add_vertex(val) n = size # Добавить значение новой вершины в список вершин @vertices << val # Добавить строку в матрицу смежности new_row = Array.new(n, 0) @adj_mat << new_row # Добавить столбец в матрицу смежности @adj_mat.each { |row| row << 0 } end ### Удаление вершины ### def remove_vertex(index) raise IndexError if index >= size # Удалить вершину с индексом index из списка вершин @vertices.delete_at(index) # Удалить строку с индексом index из матрицы смежности @adj_mat.delete_at(index) # Удалить столбец с индексом index из матрицы смежности @adj_mat.each { |row| row.delete_at(index) } end ### Добавление ребра ### def add_edge(i, j) # Параметры i и j соответствуют индексам элементов vertices # Обработка выхода индекса за границы и случая равенства if i < 0 || j < 0 || i >= size || j >= size || i == j raise IndexError end # В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) @adj_mat[i][j] = 1 @adj_mat[j][i] = 1 end ### Удаление ребра ### def remove_edge(i, j) # Параметры i и j соответствуют индексам элементов vertices # Обработка выхода индекса за границы и случая равенства if i < 0 || j < 0 || i >= size || j >= size || i == j raise IndexError end @adj_mat[i][j] = 0 @adj_mat[j][i] = 0 end ### Вывести матрицу смежности ### def __print__ puts "Список вершин = #{@vertices}" puts 'Матрица смежности =' print_matrix(@adj_mat) end end ### Driver Code ### if __FILE__ == $0 # Инициализация неориентированного графа # Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices vertices = [1, 3, 2, 5, 4] edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] graph = GraphAdjMat.new(vertices, edges) puts "\nГраф после инициализации" graph.__print__ # Добавление ребра # Индексы вершин 1 и 2 равны 0 и 2 соответственно graph.add_edge(0, 2) puts "\nГраф после добавления ребра 1-2" graph.__print__ # Удалить ребро # Индексы вершин 1 и 3 равны 0 и 1 graph.remove_edge(0, 1) puts "\nГраф после удаления ребра 1-3" graph.__print__ # Добавление вершины graph.add_vertex(6) puts "\nГраф после добавления вершины 6" graph.__print__ # Удаление вершины # Индекс вершины 3 равен 1 graph.remove_vertex(1) puts "\nГраф после удаления вершины 3" graph.__print__ end ================================================ FILE: ru/codes/ruby/chapter_graph/graph_bfs.rb ================================================ =begin File: graph_bfs.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require 'set' require_relative './graph_adjacency_list' require_relative '../utils/vertex' ### Обход в ширину ### def graph_bfs(graph, start_vet) # Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины # Последовательность обхода вершин res = [] # Хеш-множество для хранения уже посещенных вершин visited = Set.new([start_vet]) # Очередь используется для реализации BFS que = [start_vet] # Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while que.length > 0 vet = que.shift # Извлечь головную вершину из очереди res << vet # Отметить посещенную вершину # Обойти все смежные вершины данной вершины for adj_vet in graph.adj_list[vet] next if visited.include?(adj_vet) # Пропустить уже посещенную вершину que << adj_vet # Помещать в очередь только непосещенные вершины visited.add(adj_vet) # Отметить эту вершину как посещенную end end # Вернуть последовательность обхода вершин res end ### Driver Code ### if __FILE__ == $0 # Инициализация неориентированного графа v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ] graph = GraphAdjList.new(edges) puts "\nГраф после инициализации" graph.__print__ # Обход в ширину res = graph_bfs(graph, v.first) puts "\nПоследовательность вершин при обходе в ширину (BFS)" p vets_to_vals(res) end ================================================ FILE: ru/codes/ruby/chapter_graph/graph_dfs.rb ================================================ =begin File: graph_dfs.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require 'set' require_relative './graph_adjacency_list' require_relative '../utils/vertex' ### Вспомогательная функция обхода в глубину ### def dfs(graph, visited, res, vet) res << vet # Отметить посещенную вершину visited.add(vet) # Отметить эту вершину как посещенную # Обойти все смежные вершины данной вершины for adj_vet in graph.adj_list[vet] next if visited.include?(adj_vet) # Пропустить уже посещенную вершину # Рекурсивно обходить смежные вершины dfs(graph, visited, res, adj_vet) end end ### Обход в глубину ### def graph_dfs(graph, start_vet) # Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины # Последовательность обхода вершин res = [] # Хеш-множество для хранения уже посещенных вершин visited = Set.new dfs(graph, visited, res, start_vet) res end ### Driver Code ### if __FILE__ == $0 # Инициализация неориентированного графа v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ] graph = GraphAdjList.new(edges) puts "\nГраф после инициализации" graph.__print__ # Обход в глубину res = graph_dfs(graph, v[0]) puts "\nПоследовательность вершин при обходе в глубину (DFS)" p vets_to_vals(res) end ================================================ FILE: ru/codes/ruby/chapter_greedy/coin_change_greedy.rb ================================================ =begin File: coin_change_greedy.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Размен монет: жадный алгоритм ### def coin_change_greedy(coins, amt) # Предположить, что список coins упорядочен i = coins.length - 1 count = 0 # Циклически выполнять жадный выбор, пока не останется суммы while amt > 0 # Найти монету, которая меньше остатка суммы и наиболее к нему близка while i > 0 && coins[i] > amt i -= 1 end # Выбрать coins[i] amt -= coins[i] count += 1 end # Если допустимое решение не найдено, вернуть -1 amt == 0 ? count : -1 end ### Driver Code ### if __FILE__ == $0 # Жадный подход: гарантирует нахождение глобально оптимального решения coins = [1, 5, 10, 20, 50, 100] amt = 186 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "Минимальное количество монет для суммы #{amt}: #{res}" # Жадный подход: не гарантирует нахождение глобально оптимального решения coins = [1, 20, 50] amt = 60 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "Минимальное количество монет для суммы #{amt}: #{res}" puts "На самом деле минимум равен 3: 20 + 20 + 20" # Жадный подход: не гарантирует нахождение глобально оптимального решения coins = [1, 49, 50] amt = 98 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "Минимальное количество монет для суммы #{amt}: #{res}" puts "На самом деле минимум равен 2: 49 + 49" end ================================================ FILE: ru/codes/ruby/chapter_greedy/fractional_knapsack.rb ================================================ =begin File: fractional_knapsack.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Предмет ### class Item attr_accessor :w # Вес предмета attr_accessor :v # Стоимость предмета def initialize(w, v) @w = w @v = v end end ### Дробный рюкзак: жадный алгоритм ### def fractional_knapsack(wgt, val, cap) # Создать список предметов с двумя свойствами: вес и стоимость items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) } # Отсортировать по удельной стоимости item.v / item.w в порядке убывания items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) } # Циклический жадный выбор res = 0 for item in items if item.w <= cap # Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком res += item.v cap -= item.w else # Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета res += (item.v.to_f / item.w) * cap # Свободной вместимости больше не осталось, поэтому выйти из цикла break end end res end ### Driver Code ### if __FILE__ == $0 wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = wgt.length # Жадный алгоритм res = fractional_knapsack(wgt, val, cap) puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_greedy/max_capacity.rb ================================================ =begin File: max_capacity.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Максимальная вместимость: жадный алгоритм ### def max_capacity(ht) # Инициализировать i и j так, чтобы они располагались по двум концам массива i, j = 0, ht.length - 1 # Начальная максимальная вместимость равна 0 res = 0 # Выполнять жадный выбор в цикле, пока две доски не встретятся while i < j # Обновить максимальную вместимость cap = [ht[i], ht[j]].min * (j - i) res = [res, cap].max # Сдвигать внутрь более короткую сторону if ht[i] < ht[j] i += 1 else j -= 1 end end res end ### Driver Code ### if __FILE__ == $0 ht = [3, 8, 5, 2, 7, 7, 3, 4] # Жадный алгоритм res = max_capacity(ht) puts "Максимальная вместимость = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_greedy/max_product_cutting.rb ================================================ =begin File: max_product_cutting.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Максимальное произведение разрезания: жадный алгоритм ### def max_product_cutting(n) # Когда n <= 3, обязательно нужно выделить одну 1 return 1 * (n - 1) if n <= 3 # Жадно выделить множители 3, где a — число троек, а b — остаток a, b = n / 3, n % 3 # Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 return (3.pow(a - 1) * 2 * 2).to_i if b == 1 # Если остаток равен 2, ничего не делать return (3.pow(a) * 2).to_i if b == 2 # Если остаток равен 0, ничего не делать 3.pow(a).to_i end ### Driver Code ### if __FILE__ == $0 n = 58 # Жадный алгоритм res = max_product_cutting(n) puts "Максимальное произведение после разрезания = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_hashing/array_hash_map.rb ================================================ =begin File: array_hash_map.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Пара ключ-значение ### class Pair attr_accessor :key, :val def initialize(key, val) @key = key @val = val end end ### Хеш-таблица на основе массива ### class ArrayHashMap ### Конструктор ### def initialize # Инициализировать массив, содержащий 100 корзин @buckets = Array.new(100) end ### Хеш-функция ### def hash_func(key) index = key % 100 end ### Операция поиска ### def get(key) index = hash_func(key) pair = @buckets[index] return if pair.nil? pair.val end ### Операция добавления ### def put(key, val) pair = Pair.new(key, val) index = hash_func(key) @buckets[index] = pair end ### Операция удаления ### def remove(key) index = hash_func(key) # Присвоить nil, что означает удаление @buckets[index] = nil end ### Получить все пары ключ-значение ### def entry_set result = [] @buckets.each { |pair| result << pair unless pair.nil? } result end ### Получить все ключи ### def key_set result = [] @buckets.each { |pair| result << pair.key unless pair.nil? } result end ### Получить все значения ### def value_set result = [] @buckets.each { |pair| result << pair.val unless pair.nil? } result end ### Вывести хеш-таблицу ### def print @buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? } end end ### Driver Code ### if __FILE__ == $0 # Инициализация хеш-таблицы hmap = ArrayHashMap.new # Операция добавления # Добавить пару (key, value) в хеш-таблицу hmap.put(12836, "Сяо Ха") hmap.put(15937, "Сяо Ло") hmap.put(16750, "Сяо Суань") hmap.put(13276, "Сяо Фа") hmap.put(10583, "Сяо Я") puts "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" hmap.print # Операция поиска # По ключу key получить из хеш-таблицы значение value name = hmap.get(15937) puts "\nДля номера 15937 найдено имя #{name}" # Операция удаления # Удалить пару значений (key, value) из хеш-таблицы hmap.remove(10583) puts "\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение" hmap.print # Обход хеш-таблицы puts "\nОтдельный обход пар ключ-значение" for pair in hmap.entry_set puts "#{pair.key} -> #{pair.val}" end puts "\nОтдельный обход ключей" for key in hmap.key_set puts key end puts "\nОтдельный обход значений" for val in hmap.value_set puts val end end ================================================ FILE: ru/codes/ruby/chapter_hashing/built_in_hash.rb ================================================ =begin File: built_in_hash.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### Driver Code ### if __FILE__ == $0 num = 3 hash_num = num.hash puts "Хеш-значение целого числа #{num}: #{hash_num}" bol = true hash_bol = bol.hash puts "Хеш-значение булева значения #{bol}: #{hash_bol}" dec = 3.14159 hash_dec = dec.hash puts "Хеш-значение десятичного числа #{dec}: #{hash_dec}" str = "Hello Algo" hash_str = str.hash puts "Хеш-значение строки #{str}: #{hash_str}" tup = [12836, 'Сяо Ха'] hash_tup = tup.hash puts "Хеш-значение кортежа #{tup}: #{hash_tup}" obj = ListNode.new(0) hash_obj = obj.hash puts "Хеш-значение объекта узла #{obj}: #{hash_obj}" end ================================================ FILE: ru/codes/ruby/chapter_hashing/hash_map.rb ================================================ =begin File: hash_map.rb Created Time: 2024-04-14 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/print_util' ### Driver Code ### if __FILE__ == $0 # Инициализация хеш-таблицы hmap = {} # Операция добавления # Добавить пару (key, value) в хеш-таблицу hmap[12836] = "Сяо Ха" hmap[15937] = "Сяо Ло" hmap[16750] = "Сяо Суань" hmap[13276] = "Сяо Фа" hmap[10583] = "Сяо Я" puts "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" print_hash_map(hmap) # Операция поиска # Передать ключ key в хеш-таблицу и получить значение value name = hmap[15937] puts "\nДля номера 15937 найдено имя #{name}" # Операция удаления # Удалить пару (key, value) из хеш-таблицы hmap.delete(10583) puts "\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение" print_hash_map(hmap) # Обход хеш-таблицы puts "\nОтдельный обход пар ключ-значение" hmap.entries.each { |key, value| puts "#{key} -> #{value}" } puts "\nОтдельный обход ключей" hmap.keys.each { |key| puts key } puts "\nОтдельный обход значений" hmap.values.each { |val| puts val } end ================================================ FILE: ru/codes/ruby/chapter_hashing/hash_map_chaining.rb ================================================ =begin File: hash_map_chaining.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative './array_hash_map' ### Хеш-таблица с цепочками ### class HashMapChaining ### Конструктор ### def initialize @size = 0 # Число пар ключ-значение @capacity = 4 # Вместимость хеш-таблицы @load_thres = 2.0 / 3.0 # Порог коэффициента загрузки для запуска расширения @extend_ratio = 2 # Коэффициент расширения @buckets = Array.new(@capacity) { [] } # Массив корзин end ### Хеш-функция ### def hash_func(key) key % @capacity end ### Коэффициент загрузки ### def load_factor @size / @capacity end ### Операция поиска ### def get(key) index = hash_func(key) bucket = @buckets[index] # Обойти корзину; если найден key, вернуть соответствующее val for pair in bucket return pair.val if pair.key == key end # Если key не найден, вернуть nil nil end ### Операция добавления ### def put(key, val) # Когда коэффициент загрузки превышает порог, выполнить расширение extend if load_factor > @load_thres index = hash_func(key) bucket = @buckets[index] # Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть for pair in bucket if pair.key == key pair.val = val return end end # Если такого key нет, добавить пару ключ-значение в конец pair = Pair.new(key, val) bucket << pair @size += 1 end ### Операция удаления ### def remove(key) index = hash_func(key) bucket = @buckets[index] # Обойти корзину и удалить из нее пару ключ-значение for pair in bucket if pair.key == key bucket.delete(pair) @size -= 1 break end end end ### Расширение хеш-таблицы ### def extend # Временно сохранить исходную хеш-таблицу buckets = @buckets # Инициализация новой хеш-таблицы после расширения @capacity *= @extend_ratio @buckets = Array.new(@capacity) { [] } @size = 0 # Перенести пары ключ-значение из исходной хеш-таблицы в новую for bucket in buckets for pair in bucket put(pair.key, pair.val) end end end ### Вывести хеш-таблицу ### def print for bucket in @buckets res = [] for pair in bucket res << "#{pair.key} -> #{pair.val}" end pp res end end end ### Driver Code ### if __FILE__ == $0 # ## Инициализация хеш-таблицы hashmap = HashMapChaining.new # Операция добавления # Добавить пару (key, value) в хеш-таблицу hashmap.put(12836, "Сяо Ха") hashmap.put(15937, "Сяо Ло") hashmap.put(16750, "Сяо Суань") hashmap.put(13276, "Сяо Фа") hashmap.put(10583, "Сяо Я") puts "\nПосле завершения добавления хеш-таблица имеет вид\n[Key1 -> Value1, Key2 -> Value2, ...]" hashmap.print # Операция поиска # Передать ключ key в хеш-таблицу и получить значение value name = hashmap.get(13276) puts "\nДля номера 13276 найдено имя #{name}" # Операция удаления # Удалить пару (key, value) из хеш-таблицы hashmap.remove(12836) puts "\nПосле удаления 12836 хеш-таблица имеет вид\n[Key1 -> Value1, Key2 -> Value2, ...]" hashmap.print end ================================================ FILE: ru/codes/ruby/chapter_hashing/hash_map_open_addressing.rb ================================================ =begin File: hash_map_open_addressing.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative './array_hash_map' ### Хеш-таблица с открытой адресацией ### class HashMapOpenAddressing TOMBSTONE = Pair.new(-1, '-1') # Удалить метку ### Конструктор ### def initialize @size = 0 # Число пар ключ-значение @capacity = 4 # Вместимость хеш-таблицы @load_thres = 2.0 / 3.0 # Порог коэффициента загрузки для запуска расширения @extend_ratio = 2 # Коэффициент расширения @buckets = Array.new(@capacity) # Массив корзин end ### Хеш-функция ### def hash_func(key) key % @capacity end ### Коэффициент загрузки ### def load_factor @size / @capacity end ### Найти индекс корзины, соответствующий key ### def find_bucket(key) index = hash_func(key) first_tombstone = -1 # Выполнять линейное пробирование и завершить при встрече с пустой корзиной while !@buckets[index].nil? # Если встретился key, вернуть соответствующий индекс корзины if @buckets[index].key == key # Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс if first_tombstone != -1 @buckets[first_tombstone] = @buckets[index] @buckets[index] = TOMBSTONE return first_tombstone # Вернуть индекс корзины после перемещения end return index # Вернуть индекс корзины end # Записать первую встретившуюся метку удаления first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE # Вычислить индекс корзины; при выходе за конец вернуться к началу index = (index + 1) % @capacity end # Если key не существует, вернуть индекс точки добавления first_tombstone == -1 ? index : first_tombstone end ### Операция поиска ### def get(key) # Найти индекс корзины, соответствующий key index = find_bucket(key) # Если пара ключ-значение найдена, вернуть соответствующее val return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index]) # Если пара ключ-значение не существует, вернуть nil nil end ### Операция добавления ### def put(key, val) # Когда коэффициент загрузки превышает порог, выполнить расширение extend if load_factor > @load_thres # Найти индекс корзины, соответствующий key index = find_bucket(key) # Если пара ключ-значение найдена, перезаписать val и вернуть unless [nil, TOMBSTONE].include?(@buckets[index]) @buckets[index].val = val return end # Если пары ключ-значение нет, добавить ее @buckets[index] = Pair.new(key, val) @size += 1 end ### Операция удаления ### def remove(key) # Найти индекс корзины, соответствующий key index = find_bucket(key) # Если пара ключ-значение найдена, заменить ее меткой удаления unless [nil, TOMBSTONE].include?(@buckets[index]) @buckets[index] = TOMBSTONE @size -= 1 end end ### Расширение хеш-таблицы ### def extend # Временно сохранить исходную хеш-таблицу buckets_tmp = @buckets # Инициализация новой хеш-таблицы после расширения @capacity *= @extend_ratio @buckets = Array.new(@capacity) @size = 0 # Перенести пары ключ-значение из исходной хеш-таблицы в новую for pair in buckets_tmp put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair) end end ### Вывести хеш-таблицу ### def print for pair in @buckets if pair.nil? puts "Nil" elsif pair == TOMBSTONE puts "TOMBSTONE" else puts "#{pair.key} -> #{pair.val}" end end end end ### Driver Code ### if __FILE__ == $0 # Инициализация хеш-таблицы hashmap = HashMapOpenAddressing.new # Операция добавления # Добавить пару (key, val) в хеш-таблицу hashmap.put(12836, "Сяо Ха") hashmap.put(15937, "Сяо Ло") hashmap.put(16750, "Сяо Суань") hashmap.put(13276, "Сяо Фа") hashmap.put(10583, "Сяо Я") puts "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" hashmap.print # Операция поиска # Передать ключ key в хеш-таблицу и получить значение val name = hashmap.get(13276) puts "\nДля номера 13276 найдено имя #{name}" # Операция удаления # Удалить пару (key, val) из хеш-таблицы hashmap.remove(16750) puts "\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение" hashmap.print end ================================================ FILE: ru/codes/ruby/chapter_hashing/simple_hash.rb ================================================ =begin File: simple_hash.rb Created Time: 2024-04-14 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Аддитивное хеширование ### def add_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash += c.ord } hash % modulus end ### Мультипликативное хеширование ### def mul_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash = 31 * hash + c.ord } hash % modulus end ### XOR-хеширование ### def xor_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash ^= c.ord } hash % modulus end ### Хеширование с циклическим сдвигом ### def rot_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord } hash % modulus end ### Driver Code ### if __FILE__ == $0 key = "Hello Algo" hash = add_hash(key) puts "Хеш-сумма сложением = #{hash}" hash = mul_hash(key) puts "Хеш-сумма умножением = #{hash}" hash = xor_hash(key) puts "Хеш-сумма XOR = #{hash}" hash = rot_hash(key) puts "Хеш-сумма с циклическим сдвигом = #{hash}" end ================================================ FILE: ru/codes/ruby/chapter_heap/my_heap.rb ================================================ =begin File: my_heap.rb Created Time: 2024-04-19 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/print_util' ### Максимальная куча ### class MaxHeap attr_reader :max_heap ### Конструктор, строящий кучу по входному списку ### def initialize(nums) # Добавить элементы списка в кучу без изменений @max_heap = nums # Выполнить heapify для всех узлов, кроме листовых parent(size - 1).downto(0) do |i| sift_down(i) end end ### Получить индекс левого дочернего узла ### def left(i) 2 * i + 1 end ### Получить индекс правого дочернего узла ### def right(i) 2 * i + 2 end ### Получить индекс родительского узла ### def parent(i) (i - 1) / 2 # Округление вниз при делении end ### Обмен элементов ### def swap(i, j) @max_heap[i], @max_heap[j] = @max_heap[j], @max_heap[i] end ### Получить размер кучи ### def size @max_heap.length end ### Проверка, пуста ли куча ### def is_empty? size == 0 end ### Доступ к элементу на вершине кучи ### def peek @max_heap[0] end ### Добавление элемента в кучу ### def push(val) # Добавление узла @max_heap << val # Просеивание снизу вверх sift_up(size - 1) end ### Начиная с узла i, выполнить просеивание снизу вверх ### def sift_up(i) loop do # Получение родительского узла для узла i p = parent(i) # Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» break if p < 0 || @max_heap[i] <= @max_heap[p] # Поменять два узла местами swap(i, p) # Циклическое просеивание вверх i = p end end ### Извлечение элемента из кучи ### def pop # Обработка пустого случая raise IndexError, "куча пуста" if is_empty? # Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) swap(0, size - 1) # Удаление узла val = @max_heap.pop # Просеивание сверху вниз sift_down(0) # Вернуть элемент с вершины кучи val end ### Начиная с узла i, выполнить просеивание сверху вниз ### def sift_down(i) loop do # Определить узел с максимальным значением среди i, l и r и обозначить его как ma l, r, ma = left(i), right(i), i ma = l if l < size && @max_heap[l] > @max_heap[ma] ma = r if r < size && @max_heap[r] > @max_heap[ma] # Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти break if ma == i # Поменять два узла местами swap(i, ma) # Циклическое просеивание вниз i = ma end end # ## Вывести кучу (двоичное дерево) ### def __print__ print_heap(@max_heap) end end ### Driver Code ### if __FILE__ == $0 # Инициализация максимальной кучи max_heap = MaxHeap.new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) puts "\nПосле построения кучи из входного списка" max_heap.__print__ # Получение элемента с вершины кучи peek = max_heap.peek puts "\nЭлемент на вершине кучи = #{peek}" # Добавление элемента в кучу val = 7 max_heap.push(val) puts "\nПосле добавления элемента #{val} в кучу" max_heap.__print__ # Извлечение элемента с вершины кучи peek = max_heap.pop puts "\nПосле извлечения вершины кучи #{peek}" max_heap.__print__ # Получение размера кучи size = max_heap.size puts "\nКоличество элементов в куче = #{size}" # Проверка, пуста ли куча is_empty = max_heap.is_empty? puts "\nПуста ли куча: #{is_empty}" end ================================================ FILE: ru/codes/ruby/chapter_heap/top_k.rb ================================================ =begin File: top_k.rb Created Time: 2024-04-19 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative "./my_heap" ### Добавление элемента в кучу ### def push_min_heap(heap, val) # Инвертировать знак элемента heap.push(-val) end ### Извлечение элемента из кучи ### def pop_min_heap(heap) # Инвертировать знак элемента -heap.pop end ### Доступ к элементу на вершине кучи ### def peek_min_heap(heap) # Инвертировать знак элемента -heap.peek end ### Извлечение элементов из кучи ### def get_min_heap(heap) # Инвертировать все элементы кучи heap.max_heap.map { |x| -x } end ### Поиск k наибольших элементов массива с помощью кучи ### def top_k_heap(nums, k) # Инициализация минимальной кучи # Обратите внимание: мы инвертируем все элементы кучи, чтобы с помощью максимальной кучи имитировать минимальную max_heap = MaxHeap.new([]) # Поместить первые k элементов массива в кучу for i in 0...k push_min_heap(max_heap, nums[i]) end # Начиная с элемента k+1, поддерживать длину кучи равной k for i in k...nums.length # Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу if nums[i] > peek_min_heap(max_heap) pop_min_heap(max_heap) push_min_heap(max_heap, nums[i]) end end get_min_heap(max_heap) end ### Driver Code ### if __FILE__ == $0 nums = [1, 7, 6, 3, 2] k = 3 res = top_k_heap(nums, k) puts "#{k} наибольших элементов:" print_heap(res) end ================================================ FILE: ru/codes/ruby/chapter_searching/binary_search.rb ================================================ =begin File: binary_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### Бинарный поиск (двусторонне замкнутый интервал) ### def binary_search(nums, target) # Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно i, j = 0, nums.length - 1 # Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) while i <= j # Теоретически числа в Ruby могут быть сколь угодно большими (ограничены только объемом памяти), поэтому не нужно учитывать переполнение больших чисел m = (i + j) / 2 # Вычислить индекс середины m if nums[m] < target i = m + 1 # Это означает, что target находится в интервале [m+1, j] elsif nums[m] > target j = m - 1 # Это означает, что target находится в интервале [i, m-1] else return m # Целевой элемент найден, вернуть его индекс end end -1 # Целевой элемент не найден, вернуть -1 end ### Бинарный поиск (лево замкнутый, право открытый интервал) ### def binary_search_lcro(nums, target) # Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно i, j = 0, nums.length # Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) while i < j # Вычислить индекс середины m m = (i + j) / 2 if nums[m] < target i = m + 1 # Это означает, что target находится в интервале [m+1, j) elsif nums[m] > target j = m - 1 # Это означает, что target находится в интервале [i, m) else return m # Целевой элемент найден, вернуть его индекс end end -1 # Целевой элемент не найден, вернуть -1 end ### Driver Code ### if __FILE__ == $0 target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # Бинарный поиск (двусторонне замкнутый интервал) index = binary_search(nums, target) puts "Индекс целевого элемента 6 = #{index}" # Бинарный поиск (лево замкнутый, право открытый интервал) index = binary_search_lcro(nums, target) puts "Индекс целевого элемента 6 = #{index}" end ================================================ FILE: ru/codes/ruby/chapter_searching/binary_search_edge.rb ================================================ =begin File: binary_search_edge.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative './binary_search_insertion' ### Бинарный поиск самого левого target ### def binary_search_left_edge(nums, target) # Эквивалентно поиску точки вставки target i = binary_search_insertion(nums, target) # target не найден, вернуть -1 return -1 if i == nums.length || nums[i] != target i # Найти target и вернуть индекс i end ### Бинарный поиск самого правого target ### def binary_search_right_edge(nums, target) # Преобразовать задачу в поиск самого левого target + 1 i = binary_search_insertion(nums, target + 1) # j указывает на самый правый target, а i — на первый элемент больше target j = i - 1 # target не найден, вернуть -1 return -1 if j == -1 || nums[j] != target j # Найти target и вернуть индекс j end ### Driver Code ### if __FILE__ == $0 # Массив с повторяющимися элементами nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] puts "\nМассив nums = #{nums}" # Бинарный поиск левой и правой границы for target in [6, 7] index = binary_search_left_edge(nums, target) puts "Индекс крайнего левого элемента #{target}: #{index}" index = binary_search_right_edge(nums, target) puts "Индекс крайнего правого элемента #{target}: #{index}" end end ================================================ FILE: ru/codes/ruby/chapter_searching/binary_search_insertion.rb ================================================ =begin File: binary_search_insertion.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### Бинарный поиск точки вставки (без повторяющихся элементов) ### def binary_search_insertion_simple(nums, target) # Инициализировать двусторонне замкнутый интервал [0, n-1] i, j = 0, nums.length - 1 while i <= j # Вычислить индекс середины m m = (i + j) / 2 if nums[m] < target i = m + 1 # target находится в интервале [m+1, j] elsif nums[m] > target j = m - 1 # target находится в интервале [i, m-1] else return m # Найти target и вернуть точку вставки m end end i # target не найден, вернуть точку вставки i end ### Бинарный поиск точки вставки (с повторяющимися элементами) ### def binary_search_insertion(nums, target) # Инициализировать двусторонне замкнутый интервал [0, n-1] i, j = 0, nums.length - 1 while i <= j # Вычислить индекс середины m m = (i + j) / 2 if nums[m] < target i = m + 1 # target находится в интервале [m+1, j] elsif nums[m] > target j = m - 1 # target находится в интервале [i, m-1] else j = m - 1 # Первый элемент меньше target находится в интервале [i, m-1] end end i # Вернуть точку вставки i end ### Driver Code ### if __FILE__ == $0 # Массив без повторяющихся элементов nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] puts "\nМассив nums = #{nums}" # Бинарный поиск точки вставки for target in [6, 9] index = binary_search_insertion_simple(nums, target) puts "Индекс позиции вставки элемента #{target}: #{index}" end # Массив с повторяющимися элементами nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] puts "\nМассив nums = #{nums}" # Бинарный поиск точки вставки for target in [2, 6, 20] index = binary_search_insertion(nums, target) puts "Индекс позиции вставки элемента #{target}: #{index}" end end ================================================ FILE: ru/codes/ruby/chapter_searching/hashing_search.rb ================================================ =begin File: hashing_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/list_node' ### Хеш-поиск (массив) ### def hashing_search_array(hmap, target) # key хеш-таблицы: целевой элемент, value: индекс # Если такого key нет в хеш-таблице, вернуть -1 hmap[target] || -1 end ### Хеш-поиск (связный список) ### def hashing_search_linkedlist(hmap, target) # key хеш-таблицы: целевой элемент, value: объект узла # Если такого key нет в хеш-таблице, вернуть None hmap[target] || nil end ### Driver Code ### if __FILE__ == $0 target = 3 # Хеш-поиск (массив) nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] # Инициализация хеш-таблицы map0 = {} for i in 0...nums.length map0[nums[i]] = i # key: элемент, value: индекс end index = hashing_search_array(map0, target) puts "Индекс целевого элемента 3 = #{index}" # Хеш-поиск (связный список) head = arr_to_linked_list(nums) # Инициализация хеш-таблицы map1 = {} while head map1[head.val] = head head = head.next end node = hashing_search_linkedlist(map1, target) puts "Объект узла со значением 3 = #{node}" end ================================================ FILE: ru/codes/ruby/chapter_searching/linear_search.rb ================================================ =begin File: linear_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/list_node' ### Линейный поиск (массив) ### def linear_search_array(nums, target) # Обход массива for i in 0...nums.length return i if nums[i] == target # Целевой элемент найден, вернуть его индекс end -1 # Целевой элемент не найден, вернуть -1 end ### Линейный поиск (связный список) ### def linear_search_linkedlist(head, target) # Обойти связный список while head return head if head.val == target # Найти целевой узел и вернуть его head = head.next end nil # Целевой узел не найден, вернуть None end ### Driver Code ### if __FILE__ == $0 target = 3 # Выполнить линейный поиск в массиве nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] index = linear_search_array(nums, target) puts "Индекс целевого элемента 3 = #{index}" # Выполнить линейный поиск в связном списке head = arr_to_linked_list(nums) node = linear_search_linkedlist(head, target) puts "Объект узла со значением 3 = #{node}" end ================================================ FILE: ru/codes/ruby/chapter_searching/two_sum.rb ================================================ =begin File: two_sum.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### Метод 1: полный перебор ### def two_sum_brute_force(nums, target) # Два вложенных цикла, временная сложность O(n^2) for i in 0...(nums.length - 1) for j in (i + 1)...nums.length return [i, j] if nums[i] + nums[j] == target end end [] end ### Метод 2: вспомогательная хеш-таблица ### def two_sum_hash_table(nums, target) # Вспомогательная хеш-таблица, пространственная сложность O(n) dic = {} # Один цикл, временная сложность O(n) for i in 0...nums.length return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i]) dic[nums[i]] = i end [] end ### Driver Code ### if __FILE__ == $0 # ======= Test Case ======= nums = [2, 7, 11, 15] target = 13 # ====== Основной код ====== # Метод 1 res = two_sum_brute_force(nums, target) puts "Результат метода 1 res = #{res}" # Метод 2 res = two_sum_hash_table(nums, target) puts "Результат метода 2 res = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_sorting/bubble_sort.rb ================================================ =begin File: bubble_sort.rb Created Time: 2024-05-02 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Пузырьковая сортировка ### def bubble_sort(nums) n = nums.length # Внешний цикл: неотсортированный диапазон [0, i] for i in (n - 1).downto(1) # Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for j in 0...i if nums[j] > nums[j + 1] # Поменять местами nums[j] и nums[j + 1] nums[j], nums[j + 1] = nums[j + 1], nums[j] end end end end # ## Пузырьковая сортировка (оптимизация флагом) ### def bubble_sort_with_flag(nums) n = nums.length # Внешний цикл: неотсортированный диапазон [0, i] for i in (n - 1).downto(1) flag = false # Инициализировать флаг # Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for j in 0...i if nums[j] > nums[j + 1] # Поменять местами nums[j] и nums[j + 1] nums[j], nums[j + 1] = nums[j + 1], nums[j] flag = true # Записать обмен элементов end end break unless flag # На этой итерации «всплытия» не было ни одного обмена, сразу выйти end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] bubble_sort(nums) puts "После пузырьковой сортировки nums = #{nums}" nums1 = [4, 1, 3, 1, 5, 2] bubble_sort_with_flag(nums1) puts "После пузырьковой сортировки nums = #{nums1}" end ================================================ FILE: ru/codes/ruby/chapter_sorting/bucket_sort.rb ================================================ =begin File: bucket_sort.rb Created Time: 2024-04-17 Author: Martin Xu (martin.xus@gmail.com) =end ### Сортировка корзинами ### def bucket_sort(nums) # Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину k = nums.length / 2 buckets = Array.new(k) { [] } # 1. Распределить элементы массива по корзинам nums.each do |num| # Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] i = (num * k).to_i # Добавить num в корзину i buckets[i] << num end # 2. Выполнить сортировку внутри каждой корзины buckets.each do |bucket| # Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки bucket.sort! end # 3. Обойти корзины и объединить результаты i = 0 buckets.each do |bucket| bucket.each do |num| nums[i] = num i += 1 end end end ### Driver Code ### if __FILE__ == $0 # Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] bucket_sort(nums) puts "После сортировки корзинами nums = #{nums}" end ================================================ FILE: ru/codes/ruby/chapter_sorting/counting_sort.rb ================================================ =begin File: counting_sort.rb Created Time: 2024-05-02 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Сортировка подсчетом ### def counting_sort_naive(nums) # Простая реализация, не подходит для сортировки объектов # 1. Найти максимальный элемент массива m m = 0 nums.each { |num| m = [m, num].max } # 2. Подсчитать число появлений каждой цифры # counter[num] обозначает число появлений num counter = Array.new(m + 1, 0) nums.each { |num| counter[num] += 1 } # 3. Обойти counter и заполнить исходный массив nums элементами i = 0 for num in 0...(m + 1) (0...counter[num]).each do nums[i] = num i += 1 end end end ### Сортировка подсчетом ### def counting_sort(nums) # Полная реализация, позволяет сортировать объекты и является стабильной сортировкой # 1. Найти максимальный элемент массива m m = nums.max # 2. Подсчитать число появлений каждой цифры # counter[num] обозначает число появлений num counter = Array.new(m + 1, 0) nums.each { |num| counter[num] += 1 } # 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» # То есть counter[num]-1 — это индекс последнего появления num в res (0...m).each { |i| counter[i + 1] += counter[i] } # 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res # Инициализировать массив res для хранения результата n = nums.length res = Array.new(n, 0) (n - 1).downto(0).each do |i| num = nums[i] res[counter[num] - 1] = num # Поместить num по соответствующему индексу counter[num] -= 1 # Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num end # Перезаписать исходный массив nums массивом результата res (0...n).each { |i| nums[i] = res[i] } end ### Driver Code ### if __FILE__ == $0 nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort_naive(nums) puts "После сортировки подсчетом (объекты не поддерживаются) nums = #{nums}" nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort(nums1) puts "После сортировки подсчетом nums1 = #{nums1}" end ================================================ FILE: ru/codes/ruby/chapter_sorting/heap_sort.rb ================================================ =begin File: heap_sort.rb Created Time: 2024-04-10 Author: junminhong (junminhong1110@gmail.com) =end ### Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз ### def sift_down(nums, n, i) while true # Определить узел с максимальным значением среди i, l и r и обозначить его как ma l = 2 * i + 1 r = 2 * i + 2 ma = i ma = l if l < n && nums[l] > nums[ma] ma = r if r < n && nums[r] > nums[ma] # Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти break if ma == i # Поменять два узла местами nums[i], nums[ma] = nums[ma], nums[i] # Циклическое просеивание вниз i = ma end end ### Сортировка кучей ### def heap_sort(nums) # Построение кучи: выполнить heapify для всех узлов, кроме листовых (nums.length / 2 - 1).downto(0) do |i| sift_down(nums, nums.length, i) end # Извлекать максимальный элемент из кучи в течение n-1 итераций (nums.length - 1).downto(1) do |i| # Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) nums[0], nums[i] = nums[i], nums[0] # Начиная с корневого узла, выполнить просеивание сверху вниз sift_down(nums, i, 0) end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] heap_sort(nums) puts "После сортировки кучей nums = #{nums.inspect}" end ================================================ FILE: ru/codes/ruby/chapter_sorting/insertion_sort.rb ================================================ =begin File: insertion_sort.rb Created Time: 2024-04-02 Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Сортировка вставками ### def insertion_sort(nums) n = nums.length # Внешний цикл: отсортированный диапазон [0, i-1] for i in 1...n base = nums[i] j = i - 1 # Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] while j >= 0 && nums[j] > base nums[j + 1] = nums[j] # Сдвинуть nums[j] на одну позицию вправо j -= 1 end nums[j + 1] = base # Поместить base в правильную позицию end end ### Driver Code ### nums = [4, 1, 3, 1, 5, 2] insertion_sort(nums) puts "После сортировки вставками nums = #{nums}" ================================================ FILE: ru/codes/ruby/chapter_sorting/merge_sort.rb ================================================ =begin File: merge_sort.rb Created Time: 2024-04-10 Author: junminhong (junminhong1110@gmail.com) =end ### Слияние левого и правого подмассивов ### def merge(nums, left, mid, right) # Интервал левого подмассива: [left, mid], правого подмассива: [mid+1, right] # Создать временный массив tmp для хранения результата слияния tmp = Array.new(right - left + 1, 0) # Инициализировать начальные индексы левого и правого подмассивов i, j, k = left, mid + 1, 0 # Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив while i <= mid && j <= right if nums[i] <= nums[j] tmp[k] = nums[i] i += 1 else tmp[k] = nums[j] j += 1 end k += 1 end # Скопировать оставшиеся элементы левого и правого подмассивов во временный массив while i <= mid tmp[k] = nums[i] i += 1 k += 1 end while j <= right tmp[k] = nums[j] j += 1 k += 1 end # Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums (0...tmp.length).each do |k| nums[left + k] = tmp[k] end end ### Сортировка слиянием ### def merge_sort(nums, left, right) # Условие завершения # Когда длина подмассива равна 1, рекурсия завершается return if left >= right # Этап разбиения mid = left + (right - left) / 2 # Вычислить середину merge_sort(nums, left, mid) # Рекурсивно обработать левый подмассив merge_sort(nums, mid + 1, right) # Рекурсивно обработать правый подмассив # Этап слияния merge(nums, left, mid, right) end ### Driver Code ### if __FILE__ == $0 nums = [7, 3, 2, 6, 0, 1, 5, 4] merge_sort(nums, 0, nums.length - 1) puts "После сортировки слиянием nums = #{nums.inspect}" end ================================================ FILE: ru/codes/ruby/chapter_sorting/quick_sort.rb ================================================ =begin File: quick_sort.rb Created Time: 2024-04-01 Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Класс быстрой сортировки ### class QuickSort class << self ### Разбиение с опорными указателями ### def partition(nums, left, right) # Взять nums[left] в качестве опорного элемента i, j = left, right while i < j while i < j && nums[j] >= nums[left] j -= 1 # Идти справа налево в поисках первого элемента меньше опорного end while i < j && nums[i] <= nums[left] i += 1 # Идти слева направо в поисках первого элемента больше опорного end # Обмен элементов nums[i], nums[j] = nums[j], nums[i] end # Переместить опорный элемент на границу двух подмассивов nums[i], nums[left] = nums[left], nums[i] i # Вернуть индекс опорного элемента end ### Класс быстрой сортировки ### def quick_sort(nums, left, right) # Рекурсивно обрабатывать, пока длина подмассива не станет равной 1 if left < right # Разбиение с опорными указателями pivot = partition(nums, left, right) # Рекурсивно обработать левый и правый подмассивы quick_sort(nums, left, pivot - 1) quick_sort(nums, pivot + 1, right) end nums end end end # ## Класс быстрой сортировки (оптимизация медианой) ### class QuickSortMedian class << self ### Выбрать медиану из трех кандидатов ### def median_three(nums, left, mid, right) # Выбрать медиану из трех кандидатов _l, _m, _r = nums[left], nums[mid], nums[right] # m находится между l и r return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l) # l находится между m и r return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m) return right end # ## Разбиение с опорными указателями (медиана трех) ### def partition(nums, left, right) # ## Использовать nums[left] как опорный элемент med = median_three(nums, left, (left + right) / 2, right) # Переместить медиану в крайний левый элемент массива nums[left], nums[med] = nums[med], nums[left] i, j = left, right while i < j while i < j && nums[j] >= nums[left] j -= 1 # Идти справа налево в поисках первого элемента меньше опорного end while i < j && nums[i] <= nums[left] i += 1 # Идти слева направо в поисках первого элемента больше опорного end # Обмен элементов nums[i], nums[j] = nums[j], nums[i] end # Переместить опорный элемент на границу двух подмассивов nums[i], nums[left] = nums[left], nums[i] i # Вернуть индекс опорного элемента end ### Быстрая сортировка ### def quick_sort(nums, left, right) # Рекурсивно обрабатывать, пока длина подмассива не станет равной 1 if left < right # Разбиение с опорными указателями pivot = partition(nums, left, right) # Рекурсивно обработать левый и правый подмассивы quick_sort(nums, left, pivot - 1) quick_sort(nums, pivot + 1, right) end nums end end end # ## Класс быстрой сортировки (оптимизация глубины рекурсии) ### class QuickSortTailCall class << self ### Разбиение с опорными указателями ### def partition(nums, left, right) # Использовать nums[left] как опорный элемент i = left j = right while i < j while i < j && nums[j] >= nums[left] j -= 1 # Идти справа налево в поисках первого элемента меньше опорного end while i < j && nums[i] <= nums[left] i += 1 # Идти слева направо в поисках первого элемента больше опорного end # Обмен элементов nums[i], nums[j] = nums[j], nums[i] end # Переместить опорный элемент на границу двух подмассивов nums[i], nums[left] = nums[left], nums[i] i # Вернуть индекс опорного элемента end # ## Быстрая сортировка (оптимизация глубины рекурсии) ### def quick_sort(nums, left, right) # Рекурсивно обрабатывать, пока длина подмассива не станет равной 1 while left < right # Разбиение с опорными указателями pivot = partition(nums, left, right) # Выполнить быструю сортировку для более короткого из двух подмассивов if pivot - left < right - pivot quick_sort(nums, left, pivot - 1) left = pivot + 1 # Оставшийся неотсортированный диапазон: [pivot + 1, right] else quick_sort(nums, pivot + 1, right) right = pivot - 1 # Оставшийся неотсортированный диапазон: [left, pivot - 1] end end end end end ### Driver Code ### if __FILE__ == $0 # Быстрая сортировка nums = [2, 4, 1, 0, 3, 5] QuickSort.quick_sort(nums, 0, nums.length - 1) puts "После быстрой сортировки nums = #{nums}" # Быстрая сортировка (оптимизация медианным опорным элементом) nums1 = [2, 4, 1, 0, 3, 5] QuickSortMedian.quick_sort(nums1, 0, nums1.length - 1) puts "После быстрой сортировки (оптимизация медианным опорным элементом) nums1 = #{nums1}" # Быстрая сортировка (оптимизация глубины рекурсии) nums2 = [2, 4, 1, 0, 3, 5] QuickSortTailCall.quick_sort(nums2, 0, nums2.length - 1) puts "После быстрой сортировки (оптимизация глубины рекурсии) nums2 = #{nums2}" end ================================================ FILE: ru/codes/ruby/chapter_sorting/radix_sort.rb ================================================ =begin File: radix_sort.rb Created Time: 2024-05-03 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Получить k-й разряд элемента num, где exp = 10^(k-1) ### def digit(num, exp) # Передача exp вместо k позволяет избежать повторного выполнения дорогостоящих вычислений степени (num / exp) % 10 end # ## Сортировка подсчетом (сортировка по k-му разряду nums) ### def counting_sort_digit(nums, exp) # Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 counter = Array.new(10, 0) n = nums.length # Подсчитать число появлений каждой цифры от 0 до 9 for i in 0...n d = digit(nums[i], exp) # Получить k-й разряд nums[i], обозначив его как d counter[d] += 1 # Подсчитать число появлений цифры d end # Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» (1...10).each { |i| counter[i] += counter[i - 1] } # Выполняя обратный проход, заполнить res элементами по статистике в корзинах res = Array.new(n, 0) for i in (n - 1).downto(0) d = digit(nums[i], exp) j = counter[d] - 1 # Получить индекс j цифры d в массиве res[j] = nums[i] # Поместить текущий элемент по индексу j counter[d] -= 1 # Уменьшить количество d на 1 end # Перезаписать исходный массив nums результатом (0...n).each { |i| nums[i] = res[i] } end ### Поразрядная сортировка ### def radix_sort(nums) # Получить максимальный элемент массива, чтобы определить максимальное число разрядов m = nums.max # Проходить разряды от младшего к старшему exp = 1 while exp <= m # Выполнить сортировку подсчетом по k-му разряду элементов массива # k = 1 -> exp = 1 # k = 2 -> exp = 10 # то есть exp = 10^(k-1) counting_sort_digit(nums, exp) exp *= 10 end end ### Driver Code ### if __FILE__ == $0 # Поразрядная сортировка nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ] radix_sort(nums) puts "После поразрядной сортировки nums = #{nums}" end ================================================ FILE: ru/codes/ruby/chapter_sorting/selection_sort.rb ================================================ =begin File: selection_sort.rb Created Time: 2024-05-03 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Сортировка выбором ### def selection_sort(nums) n = nums.length # Внешний цикл: неотсортированный диапазон [i, n-1] for i in 0...(n - 1) # Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне k = i for j in (i + 1)...n if nums[j] < nums[k] k = j # Записать индекс минимального элемента end end # Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона nums[i], nums[k] = nums[k], nums[i] end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] selection_sort(nums) puts "После сортировки выбором nums = #{nums}" end ================================================ FILE: ru/codes/ruby/chapter_stack_and_queue/array_deque.rb ================================================ =begin File: array_deque.rb Created Time: 2024-04-05 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Двусторонняя очередь на основе кольцевого массива ### class ArrayDeque ### Получение длины двусторонней очереди ### attr_reader :size ### Конструктор ### def initialize(capacity) @nums = Array.new(capacity, 0) @front = 0 @size = 0 end ### Получить вместимость двусторонней очереди ### def capacity @nums.length end ### Проверка, пуста ли двусторонняя очередь ### def is_empty? size.zero? end ### Добавление в голову очереди ### def push_first(num) if size == capacity puts 'Двусторонняя очередь заполнена' return end # Указатель головы сдвигается на одну позицию влево # С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост @front = index(@front - 1) # Добавить num в голову очереди @nums[@front] = num @size += 1 end ### Добавление в хвост очереди ### def push_last(num) if size == capacity puts 'Двусторонняя очередь заполнена' return end # Вычислить указатель хвоста, указывающий на индекс хвоста + 1 rear = index(@front + size) # Добавить num в хвост очереди @nums[rear] = num @size += 1 end ### Извлечение из головы очереди ### def pop_first num = peek_first # Указатель головы сдвигается на одну позицию назад @front = index(@front + 1) @size -= 1 num end ### Извлечение из хвоста очереди ### def pop_last num = peek_last @size -= 1 num end ### Доступ к элементу в начале очереди ### def peek_first raise IndexError, 'двусторонняя очередь пуста' if is_empty? @nums[@front] end ### Доступ к элементу в хвосте очереди ### def peek_last raise IndexError, 'двусторонняя очередь пуста' if is_empty? # Вычислить индекс хвостового элемента last = index(@front + size - 1) @nums[last] end ### Вернуть массив для вывода ### def to_array # Преобразовывать только элементы списка в пределах фактической длины res = [] for i in 0...size res << @nums[index(@front + i)] end res end private ### Вычислить индекс в кольцевом массиве ### def index(i) # С помощью операции взятия по модулю соединить начало и конец массива # Когда i выходит за конец массива, он возвращается в начало # Когда i выходит за начало массива, он возвращается в конец (i + capacity) % capacity end end ### Driver Code ### if __FILE__ == $0 # Инициализация двусторонней очереди deque = ArrayDeque.new(10) deque.push_last(3) deque.push_last(2) deque.push_last(5) puts "Двусторонняя очередь deque = #{deque.to_array}" # Доступ к элементу peek_first = deque.peek_first puts "Первый элемент peek_first = #{peek_first}" peek_last = deque.peek_last puts "Последний элемент peek_last = #{peek_last}" # Добавление элемента в очередь deque.push_last(4) puts "После добавления элемента 4 в хвост deque = #{deque.to_array}" deque.push_first(1) puts "После добавления элемента 1 в хвост deque = #{deque.to_array}" # Извлечение элемента из очереди pop_last = deque.pop_last puts "Извлечен элемент из хвоста = #{pop_last}, deque после извлечения из хвоста = #{deque.to_array}" pop_first = deque.pop_first puts "Извлечен элемент из головы = #{pop_first}, deque после извлечения из головы = #{deque.to_array}" # Получение длины двусторонней очереди size = deque.size puts "Длина двусторонней очереди size = #{size}" # Проверка, пуста ли двусторонняя очередь is_empty = deque.is_empty? puts "Пуста ли двусторонняя очередь = #{is_empty}" end ================================================ FILE: ru/codes/ruby/chapter_stack_and_queue/array_queue.rb ================================================ =begin File: array_queue.rb Created Time: 2024-04-05 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Очередь на основе кольцевого массива ### class ArrayQueue ### Получение длины очереди ### attr_reader :size ### Конструктор ### def initialize(size) @nums = Array.new(size, 0) # Массив для хранения элементов очереди @front = 0 # Указатель head, указывающий на первый элемент очереди @size = 0 # Длина очереди end ### Получить вместимость очереди ### def capacity @nums.length end ### Проверка, пуста ли очередь ### def is_empty? size.zero? end ### Добавление в очередь ### def push(num) raise IndexError, 'очередь заполнена' if size == capacity # Вычислить указатель хвоста, указывающий на индекс хвоста + 1 # С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива rear = (@front + size) % capacity # Добавить num в хвост очереди @nums[rear] = num @size += 1 end ### Извлечение из очереди ### def pop num = peek # Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива @front = (@front + 1) % capacity @size -= 1 num end ### Доступ к элементу в начале очереди ### def peek raise IndexError, 'очередь пуста' if is_empty? @nums[@front] end ### Вернуть список для вывода ### def to_array res = Array.new(size, 0) j = @front for i in 0...size res[i] = @nums[j % capacity] j += 1 end res end end ### Driver Code ### if __FILE__ == $0 # Инициализация очереди queue = ArrayQueue.new(10) # Добавление элемента в очередь queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "Очередь queue = #{queue.to_array}" # Доступ к элементу в начале очереди peek = queue.peek puts "Первый элемент peek = #{peek}" # Извлечение элемента из очереди pop = queue.pop puts "Извлеченный элемент pop = #{pop}" puts "queue после извлечения = #{queue.to_array}" # Получение длины очереди size = queue.size puts "Длина очереди size = #{size}" # Проверка, пуста ли очередь is_empty = queue.is_empty? puts "Пуста ли очередь = #{is_empty}" # Проверка кольцевого массива for i in 0...10 queue.push(i) queue.pop puts "После #{i}-го цикла enqueue + dequeue queue = #{queue.to_array}" end end ================================================ FILE: ru/codes/ruby/chapter_stack_and_queue/array_stack.rb ================================================ =begin File: array_stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Стек на основе массива ### class ArrayStack ### Конструктор ### def initialize @stack = [] end ### Получить длину стека ### def size @stack.length end ### Проверка, пуст ли стек ### def is_empty? @stack.empty? end ### Помещение в стек ### def push(item) @stack << item end ### Извлечение из стека ### def pop raise IndexError, 'стек пуст' if is_empty? @stack.pop end ### Доступ к верхнему элементу стека ### def peek raise IndexError, 'стек пуст' if is_empty? @stack.last end ### Вернуть список для вывода ### def to_array @stack end end ### Driver Code ### if __FILE__ == $0 # Инициализация стека stack = ArrayStack.new # Помещение элемента в стек stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) puts "Стек stack = #{stack.to_array}" # Доступ к верхнему элементу стека peek = stack.peek puts "Верхний элемент peek = #{peek}" # Извлечение элемента из стека pop = stack.pop puts "Извлеченный элемент pop = #{pop}" puts "stack после извлечения = #{stack.to_array}" # Получение длины стека size = stack.size puts "Длина стека size = #{size}" # Проверка на пустоту is_empty = stack.is_empty? puts "Пуст ли стек = #{is_empty}" end ================================================ FILE: ru/codes/ruby/chapter_stack_and_queue/deque.rb ================================================ =begin File: deque.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # Инициализировать двустороннюю очередь # В Ruby нет встроенной двусторонней очереди, поэтому Array можно использовать как двустороннюю очередь deque = [] # Элемент помещается в очередь deque << 2 deque << 5 deque << 4 # Обратите внимание: поскольку используется массив, временная сложность метода Array#unshift равна O(n) deque.unshift(3) deque.unshift(1) puts "Двусторонняя очередь deque = #{deque}" # Доступ к элементу peek_first = deque.first puts "Первый элемент peek_first = #{peek_first}" peek_last = deque.last puts "Последний элемент peek_last = #{peek_last}" # Элемент извлекается из очереди # Обратите внимание: поскольку используется массив, временная сложность метода Array#shift равна O(n) pop_front = deque.shift puts "Извлечен элемент из головы pop_front = #{pop_front}, deque после извлечения из головы = #{deque}" pop_back = deque.pop puts "Извлечен элемент из хвоста pop_back = #{pop_back}, deque после извлечения из хвоста = #{deque}" # Получение длины двусторонней очереди size = deque.length puts "Длина двусторонней очереди size = #{size}" # Проверка, пуста ли двусторонняя очередь is_empty = size.zero? puts "Пуста ли двусторонняя очередь = #{is_empty}" end ================================================ FILE: ru/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb ================================================ =begin File: linkedlist_deque.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end # ## Узел двусвязного списка class ListNode attr_accessor :val attr_accessor :next # Ссылка на узел-преемник attr_accessor :prev # Ссылка на узел-предшественник ### Конструктор ### def initialize(val) @val = val end end ### Двусторонняя очередь на основе двусвязного списка ### class LinkedListDeque ### Получение длины двусторонней очереди ### attr_reader :size ### Конструктор ### def initialize @front = nil # Головной узел front @rear = nil # Хвостовой узел rear @size = 0 # Длина двусторонней очереди end ### Проверка, пуста ли двусторонняя очередь ### def is_empty? size.zero? end ### Операция добавления в очередь ### def push(num, is_front) node = ListNode.new(num) # Если связный список пуст, пусть front и rear оба указывают на node if is_empty? @front = @rear = node # Операция добавления в голову очереди elsif is_front # Добавить node в голову списка @front.prev = node node.next = @front @front = node # Обновить головной узел # Операция добавления в хвост очереди else # Добавить node в хвост списка @rear.next = node node.prev = @rear @rear = node # Обновить хвостовой узел end @size += 1 # Обновить длину очереди end ### Добавление в голову очереди ### def push_first(num) push(num, true) end ### Добавление в хвост очереди ### def push_last(num) push(num, false) end ### Операция извлечения из очереди ### def pop(is_front) raise IndexError, 'двусторонняя очередь пуста' if is_empty? # Операция извлечения из головы очереди if is_front val = @front.val # Временно сохранить значение головного узла # Удалить головной узел fnext = @front.next unless fnext.nil? fnext.prev = nil @front.next = nil end @front = fnext # Обновить головной узел # Операция извлечения из хвоста очереди else val = @rear.val # Временно сохранить значение хвостового узла # Удалить хвостовой узел rprev = @rear.prev unless rprev.nil? rprev.next = nil @rear.prev = nil end @rear = rprev # Обновить хвостовой узел end @size -= 1 # Обновить длину очереди val end ### Извлечение из головы очереди ### def pop_first pop(true) end ### Извлечение из головы очереди ### def pop_last pop(false) end ### Доступ к элементу в начале очереди ### def peek_first raise IndexError, 'двусторонняя очередь пуста' if is_empty? @front.val end ### Доступ к элементу в хвосте очереди ### def peek_last raise IndexError, 'двусторонняя очередь пуста' if is_empty? @rear.val end ### Вернуть массив для вывода ### def to_array node = @front res = Array.new(size, 0) for i in 0...size res[i] = node.val node = node.next end res end end ### Driver Code ### if __FILE__ == $0 # Инициализация двусторонней очереди deque = LinkedListDeque.new deque.push_last(3) deque.push_last(2) deque.push_last(5) puts "Двусторонняя очередь deque = #{deque.to_array}" # Доступ к элементу peek_first = deque.peek_first puts "Первый элемент peek_first = #{peek_first}" peek_last = deque.peek_last puts "Последний элемент peek_last = #{peek_last}" # Добавление элемента в очередь deque.push_last(4) puts "После добавления элемента 4 в хвост deque = #{deque.to_array}" deque.push_first(1) puts "После добавления элемента 1 в голову deque = #{deque.to_array}" # Извлечение элемента из очереди pop_last = deque.pop_last puts "Извлечен элемент из хвоста = #{pop_last}, deque после извлечения из хвоста = #{deque.to_array}" pop_first = deque.pop_first puts "Извлечен элемент из головы = #{pop_first}, deque после извлечения из головы = #{deque.to_array}" # Получение длины двусторонней очереди size = deque.size puts "Длина двусторонней очереди size = #{size}" # Проверка, пуста ли двусторонняя очередь is_empty = deque.is_empty? puts "Пуста ли двусторонняя очередь = #{is_empty}" end ================================================ FILE: ru/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb ================================================ =begin File: linkedlist_queue.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### Очередь на основе связного списка ### class LinkedListQueue ### Получение длины очереди ### attr_reader :size ### Конструктор ### def initialize @front = nil # Головной узел front @rear = nil # Хвостовой узел rear @size = 0 end ### Проверка, пуста ли очередь ### def is_empty? @front.nil? end ### Добавление в очередь ### def push(num) # Добавить num после хвостового узла node = ListNode.new(num) # Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел if @front.nil? @front = node @rear = node # Если очередь не пуста, добавить этот узел после хвостового узла else @rear.next = node @rear = node end @size += 1 end ### Извлечение из очереди ### def pop num = peek # Удалить головной узел @front = @front.next @size -= 1 num end ### Доступ к элементу в начале очереди ### def peek raise IndexError, 'очередь пуста' if is_empty? @front.val end ### Преобразовать связный список в Array и вернуть ### def to_array queue = [] temp = @front while temp queue << temp.val temp = temp.next end queue end end ### Driver Code ### if __FILE__ == $0 # Инициализация очереди queue = LinkedListQueue.new # Элемент помещается в очередь queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "Очередь queue = #{queue.to_array}" # Доступ к элементу в начале очереди peek = queue.peek puts "Первый элемент front = #{peek}" # Извлечение элемента из очереди pop_front = queue.pop puts "Извлеченный элемент pop = #{pop_front}" puts "queue после извлечения = #{queue.to_array}" # Получение длины очереди size = queue.size puts "Длина очереди size = #{size}" # Проверка, пуста ли очередь is_empty = queue.is_empty? puts "Пуста ли очередь = #{is_empty}" end ================================================ FILE: ru/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb ================================================ =begin File: linkedlist_stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### Стек на основе связного списка ### class LinkedListStack attr_reader :size ### Конструктор ### def initialize @size = 0 end ### Проверка, пуст ли стек ### def is_empty? @peek.nil? end ### Помещение в стек ### def push(val) node = ListNode.new(val) node.next = @peek @peek = node @size += 1 end ### Извлечение из стека ### def pop num = peek @peek = @peek.next @size -= 1 num end ### Доступ к верхнему элементу стека ### def peek raise IndexError, 'стек пуст' if is_empty? @peek.val end ### Преобразовать связный список в Array и вернуть ### def to_array arr = [] node = @peek while node arr << node.val node = node.next end arr.reverse end end ### Driver Code ### if __FILE__ == $0 # Инициализация стека stack = LinkedListStack.new # Помещение элемента в стек stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) puts "Стек stack = #{stack.to_array}" # Доступ к верхнему элементу стека peek = stack.peek puts "Верхний элемент peek = #{peek}" # Извлечение элемента из стека pop = stack.pop puts "Извлеченный элемент pop = #{pop}" puts "stack после извлечения = #{stack.to_array}" # Получение длины стека size = stack.size puts "Длина стека size = #{size}" # Проверка на пустоту is_empty = stack.is_empty? puts "Пуст ли стек = #{is_empty}" end ================================================ FILE: ru/codes/ruby/chapter_stack_and_queue/queue.rb ================================================ =begin File: queue.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # Инициализировать очередь # Во встроенной очереди Ruby (Thread::Queue) нет методов peek и обхода, поэтому Array можно использовать как очередь queue = [] # Добавление элемента в очередь queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "Очередь queue = #{queue}" # Обратиться к элементу очереди peek = queue.first puts "Первый элемент peek = #{peek}" # Элемент извлекается из очереди # Обратите внимание: поскольку используется массив, временная сложность метода Array#shift равна O(n) pop = queue.shift puts "Извлеченный элемент pop = #{pop}" puts "queue после извлечения = #{queue}" # Получение длины очереди size = queue.length puts "Длина очереди size = #{size}" # Проверка, пуста ли очередь is_empty = queue.empty? puts "Пуста ли очередь = #{is_empty}" end ================================================ FILE: ru/codes/ruby/chapter_stack_and_queue/stack.rb ================================================ =begin File: stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # Инициализировать стек # В Ruby нет встроенного класса стека, поэтому Array можно использовать как стек stack = [] # Помещение элемента в стек stack << 1 stack << 3 stack << 2 stack << 5 stack << 4 puts "Стек stack = #{stack}" # Доступ к верхнему элементу стека peek = stack.last puts "Верхний элемент peek = #{peek}" # Извлечение элемента из стека pop = stack.pop puts "Извлеченный элемент pop = #{pop}" puts "stack после извлечения = #{stack}" # Получение длины стека size = stack.length puts "Длина стека size = #{size}" # Проверка на пустоту is_empty = stack.empty? puts "Пуст ли стек = #{is_empty}" end ================================================ FILE: ru/codes/ruby/chapter_tree/array_binary_tree.rb ================================================ =begin File: array_binary_tree.rb Created Time: 2024-04-17 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Класс двоичного дерева в массивном представлении ### class ArrayBinaryTree ### Конструктор ### def initialize(arr) @tree = arr.to_a end ### Вместимость списка ### def size @tree.length end ### Получить значение узла с индексом i ### def val(i) # Если индекс выходит за границы, вернуть nil, обозначающий пустую ячейку return if i < 0 || i >= size @tree[i] end ### Получить индекс левого дочернего узла узла с индексом i ### def left(i) 2 * i + 1 end ### Получить индекс правого дочернего узла узла с индексом i ### def right(i) 2 * i + 2 end ### Получить индекс родительского узла узла с индексом i ### def parent(i) (i - 1) / 2 end ### Обход в ширину ### def level_order @res = [] # Непосредственно обходить массив for i in 0...size @res << val(i) unless val(i).nil? end @res end ### Обход в глубину ### def dfs(i, order) return if val(i).nil? # Предварительный обход @res << val(i) if order == :pre dfs(left(i), order) # Симметричный обход @res << val(i) if order == :in dfs(right(i), order) # Обратный обход @res << val(i) if order == :post end ### Предварительный обход ### def pre_order @res = [] dfs(0, :pre) @res end ### Симметричный обход ### def in_order @res = [] dfs(0, :in) @res end ### Обратный обход ### def post_order @res = [] dfs(0, :post) @res end end ### Driver Code ### if __FILE__ == $0 # Инициализировать двоичное дерево # Здесь используется функция, напрямую строящая двоичное дерево из массива arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] root = arr_to_tree(arr) puts "\nИнициализация двоичного дерева\n\n" puts 'Массивное представление двоичного дерева:' pp arr puts 'Связное представление двоичного дерева:' print_tree(root) # Класс двоичного дерева в массивном представлении abt = ArrayBinaryTree.new(arr) # Доступ к узлу i = 1 l, r, _p = abt.left(i), abt.right(i), abt.parent(i) puts "\nИндекс текущего узла = #{i}, значение = #{abt.val(i).inspect}" puts "Индекс его левого дочернего узла = #{l}, значение = #{abt.val(l).inspect}" puts "Индекс его правого дочернего узла = #{r}, значение = #{abt.val(r).inspect}" puts "Индекс его родительского узла = #{_p}, значение = #{abt.val(_p).inspect}" # Обходить дерево res = abt.level_order puts "\nОбход в ширину: #{res}" res = abt.pre_order puts "Предварительный обход: #{res}" res = abt.in_order puts "Симметричный обход: #{res}" res = abt.post_order puts "Обратный обход: #{res}" end ================================================ FILE: ru/codes/ruby/chapter_tree/avl_tree.rb ================================================ =begin File: avl_tree.rb Created Time: 2024-04-17 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### AVL-дерево ### class AVLTree ### Конструктор ### def initialize @root = nil end ### Получение корневого узла двоичного дерева ### def get_root @root end ### Получить высоту узла ### def height(node) # Высота пустого узла равна -1, высота листового узла равна 0 return node.height unless node.nil? -1 end ### Обновить высоту узла ### def update_height(node) # Высота узла равна высоте более высокого поддерева + 1 node.height = [height(node.left), height(node.right)].max + 1 end ### Получить коэффициент баланса ### def balance_factor(node) # Коэффициент баланса пустого узла равен 0 return 0 if node.nil? # Коэффициент баланса узла = высота левого поддерева - высота правого поддерева height(node.left) - height(node.right) end ### Операция правого вращения ### def right_rotate(node) child = node.left grand_child = child.right # Выполнить правое вращение узла node вокруг child child.right = node node.left = grand_child # Обновить высоту узла update_height(node) update_height(child) # Вернуть корневой узел поддерева после вращения child end ### Операция левого вращения ### def left_rotate(node) child = node.right grand_child = child.left # Выполнить левое вращение узла node вокруг child child.left = node node.right = grand_child # Обновить высоту узла update_height(node) update_height(child) # Вернуть корневой узел поддерева после вращения child end ### Выполнить вращение, чтобы снова сбалансировать поддерево ### def rotate(node) # Получить коэффициент баланса узла node balance_factor = balance_factor(node) # Обойти левое поддерево if balance_factor > 1 if balance_factor(node.left) >= 0 # Правое вращение return right_rotate(node) else # Сначала левое вращение, затем правое node.left = left_rotate(node.left) return right_rotate(node) end # Правостороннее дерево обхода elsif balance_factor < -1 if balance_factor(node.right) <= 0 # Левое вращение return left_rotate(node) else # Сначала правое вращение, затем левое node.right = right_rotate(node.right) return left_rotate(node) end end # Дерево сбалансировано, вращение не требуется, вернуть сразу node end ### Вставка узла ### def insert(val) @root = insert_helper(@root, val) end # ## Рекурсивная вставка узла (вспомогательный метод) ### def insert_helper(node, val) return TreeNode.new(val) if node.nil? # 1. Найти позицию вставки и вставить узел if val < node.val node.left = insert_helper(node.left, val) elsif val > node.val node.right = insert_helper(node.right, val) else # Повторяющийся узел не вставлять, сразу вернуть return node end # Обновить высоту узла update_height(node) # 2. Выполнить вращение, чтобы снова сбалансировать поддерево rotate(node) end ### Удаление узла ### def remove(val) @root = remove_helper(@root, val) end # ## Рекурсивное удаление узла (вспомогательный метод) ### def remove_helper(node, val) return if node.nil? # 1. Найти узел и удалить его if val < node.val node.left = remove_helper(node.left, val) elsif val > node.val node.right = remove_helper(node.right, val) else if node.left.nil? || node.right.nil? child = node.left || node.right # Число дочерних узлов = 0, удалить node и сразу вернуть return if child.nil? # Число дочерних узлов = 1, удалить node напрямую node = child else # Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел temp = node.right while !temp.left.nil? temp = temp.left end node.right = remove_helper(node.right, temp.val) node.val = temp.val end end # Обновить высоту узла update_height(node) # 2. Выполнить вращение, чтобы снова сбалансировать поддерево rotate(node) end ### Поиск узла ### def search(val) cur = @root # Искать в цикле и выйти после прохода за листовой узел while !cur.nil? # Целевой узел находится в правом поддереве cur if cur.val < val cur = cur.right # Целевой узел находится в левом поддереве cur elsif cur.val > val cur = cur.left # Найти целевой узел и выйти из цикла else break end end # Вернуть целевой узел cur end end ### Driver Code ### if __FILE__ == $0 def test_insert(tree, val) tree.insert(val) puts "\nAVL-дерево после вставки узла #{val}:" print_tree(tree.get_root) end def test_remove(tree, val) tree.remove(val) puts "\nAVL-дерево после удаления узла #{val}:" print_tree(tree.get_root) end # Инициализация пустого AVL-дерева avl_tree = AVLTree.new # Вставка узла # Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6] test_insert(avl_tree, val) end # Вставка повторяющегося узла test_insert(avl_tree, 7) # Удаление узла # Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла test_remove(avl_tree, 8) # Удаление узла степени 0 test_remove(avl_tree, 5) # Удаление узла степени 1 test_remove(avl_tree, 4) # Удаление узла степени 2 result_node = avl_tree.search(7) puts "\nНайденный объект узла = #{result_node}, значение узла = #{result_node.val}" end ================================================ FILE: ru/codes/ruby/chapter_tree/binary_search_tree.rb ================================================ =begin File: binary_search_tree.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Двоичное дерево поиска ### class BinarySearchTree ### Конструктор ### def initialize # Инициализировать пустое дерево @root = nil end ### Получение корневого узла двоичного дерева ### def get_root @root end ### Поиск узла ### def search(num) cur = @root # Искать в цикле и выйти после прохода за листовой узел while !cur.nil? # Целевой узел находится в правом поддереве cur if cur.val < num cur = cur.right # Целевой узел находится в левом поддереве cur elsif cur.val > num cur = cur.left # Найти целевой узел и выйти из цикла else break end end cur end ### Вставка узла ### def insert(num) # Если дерево пусто, инициализировать корневой узел if @root.nil? @root = TreeNode.new(num) return end # Искать в цикле и выйти после прохода за листовой узел cur, pre = @root, nil while !cur.nil? # Найти повторяющийся узел и сразу вернуть return if cur.val == num pre = cur # Позиция вставки находится в правом поддереве cur if cur.val < num cur = cur.right # Позиция вставки находится в левом поддереве cur else cur = cur.left end end # Вставка узла node = TreeNode.new(num) if pre.val < num pre.right = node else pre.left = node end end ### Удаление узла ### def remove(num) # Если дерево пусто, сразу вернуть return if @root.nil? # Искать в цикле и выйти после прохода за листовой узел cur, pre = @root, nil while !cur.nil? # Найти узел для удаления и выйти из цикла break if cur.val == num pre = cur # Узел для удаления находится в правом поддереве cur if cur.val < num cur = cur.right # Узел для удаления находится в левом поддереве cur else cur = cur.left end end # Если узел для удаления отсутствует, сразу вернуть return if cur.nil? # Число дочерних узлов = 0 или 1 if cur.left.nil? || cur.right.nil? # Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел child = cur.left || cur.right # Удалить узел cur if cur != @root if pre.left == cur pre.left = child else pre.right = child end else # Если удаляемый узел является корнем, заново назначить корневой узел @root = child end # Число дочерних узлов = 2 else # Получить следующий узел после cur в симметричном обходе tmp = cur.right while !tmp.left.nil? tmp = tmp.left end # Рекурсивно удалить узел tmp remove(tmp.val) # Перезаписать cur значением tmp cur.val = tmp.val end end end ### Driver Code ### if __FILE__ == $0 # Инициализация двоичного дерева поиска bst = BinarySearchTree.new nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] # Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево nums.each { |num| bst.insert(num) } puts "\nИсходное двоичное дерево\n" print_tree(bst.get_root) # Поиск узла node = bst.search(7) puts "\nНайденный объект узла: #{node}, значение узла = #{node.val}" # Вставка узла bst.insert(16) puts "\nПосле вставки узла 16 двоичное дерево имеет вид\n" print_tree(bst.get_root) # Удаление узла bst.remove(1) puts "\nПосле удаления узла 1 двоичное дерево имеет вид\n" print_tree(bst.get_root) bst.remove(2) puts "\nПосле удаления узла 2 двоичное дерево имеет вид\n" print_tree(bst.get_root) bst.remove(4) puts "\nПосле удаления узла 4 двоичное дерево имеет вид\n" print_tree(bst.get_root) end ================================================ FILE: ru/codes/ruby/chapter_tree/binary_tree.rb ================================================ =begin File: binary_tree.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Driver Code ### if __FILE__ == $0 # Инициализация двоичного дерева # Инициализация узлов n1 = TreeNode.new(1) n2 = TreeNode.new(2) n3 = TreeNode.new(3) n4 = TreeNode.new(4) n5 = TreeNode.new(5) # Построить связи между узлами (указатели) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 puts "\nИнициализация двоичного дерева\n\n" print_tree(n1) # Вставка и удаление узлов _p = TreeNode.new(0) # Вставить узел _p между n1 -> n2 n1.left = _p _p.left = n2 puts "\nПосле вставки узла _p\n\n" print_tree(n1) # Удаление узла n1.left = n2 puts "\nПосле удаления узла _p\n\n" print_tree(n1) end ================================================ FILE: ru/codes/ruby/chapter_tree/binary_tree_bfs.rb ================================================ =begin File: binary_tree_bfs.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Обход в ширину ### def level_order(root) # Инициализировать очередь и добавить корневой узел queue = [root] # Инициализировать список для хранения последовательности обхода res = [] while !queue.empty? node = queue.shift # Извлечение из очереди res << node.val # Сохранить значение узла queue << node.left unless node.left.nil? # Поместить левый дочерний узел в очередь queue << node.right unless node.right.nil? # Поместить правый дочерний узел в очередь end res end ### Driver Code ### if __FILE__ == $0 # Инициализировать двоичное дерево # Здесь используется функция, напрямую строящая двоичное дерево из массива root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) puts "\nИнициализация двоичного дерева\n\n" print_tree(root) # Обход в ширину res = level_order(root) puts "\nПоследовательность печати узлов при обходе в ширину = #{res}" end ================================================ FILE: ru/codes/ruby/chapter_tree/binary_tree_dfs.rb ================================================ =begin File: binary_tree_dfs.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Предварительный обход ### def pre_order(root) return if root.nil? # Порядок обхода: корень -> левое поддерево -> правое поддерево $res << root.val pre_order(root.left) pre_order(root.right) end ### Симметричный обход ### def in_order(root) return if root.nil? # Порядок обхода: левое поддерево -> корень -> правое поддерево in_order(root.left) $res << root.val in_order(root.right) end ### Обратный обход ### def post_order(root) return if root.nil? # Порядок обхода: левое поддерево -> правое поддерево -> корень post_order(root.left) post_order(root.right) $res << root.val end ### Driver Code ### if __FILE__ == $0 # Инициализировать двоичное дерево # Здесь используется функция, напрямую строящая двоичное дерево из массива root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) puts "\nИнициализация двоичного дерева\n\n" print_tree(root) # Предварительный обход $res = [] pre_order(root) puts "\nПоследовательность печати узлов при предварительном обходе = #{$res}" # Симметричный обход $res.clear in_order(root) puts "\nПоследовательность печати узлов при симметричном обходе = #{$res}" # Обратный обход $res.clear post_order(root) puts "\nПоследовательность печати узлов при обратном обходе = #{$res}" end ================================================ FILE: ru/codes/ruby/test_all.rb ================================================ require 'open3' start_time = Time.now ruby_code_dir = File.dirname(__FILE__) files = Dir.glob("#{ruby_code_dir}/chapter_*/*.rb") errors = [] files.each do |file| stdout, stderr, status = Open3.capture3("ruby #{file}") errors << stderr unless status.success? end puts "\x1b[34mTested #{files.count} files\x1b[m" unless errors.empty? puts "\x1b[33mFound exception in #{errors.length} files\x1b[m" raise errors.join("\n\n") else puts "\x1b[32mPASS\x1b[m" end puts "Testing finishes after #{((Time.now - start_time) * 1000).round} ms" ================================================ FILE: ru/codes/ruby/utils/list_node.rb ================================================ =begin File: list_node.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Класс узла связного списка ### class ListNode attr_accessor :val # Значение узла attr_accessor :next # Ссылка на следующий узел def initialize(val=0, next_node=nil) @val = val @next = next_node end end ### Десериализация списка в связный список ### def arr_to_linked_list(arr) head = current = ListNode.new(arr[0]) for i in 1...arr.length current.next = ListNode.new(arr[i]) current = current.next end head end ### Сериализация связного списка в список ### def linked_list_to_arr(head) arr = [] while head arr << head.val head = head.next end end ================================================ FILE: ru/codes/ruby/utils/print_util.rb ================================================ =begin File: print_util.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative "./tree_node" ### Вывести матрицу ### def print_matrix(mat) s = [] mat.each { |arr| s << " #{arr.to_s}" } puts "[\n#{s.join(",\n")}\n]" end ### Вывести связный список ### def print_linked_list(head) list = [] while head list << head.val head = head.next end puts "#{list.join(" -> ")}" end class Trunk attr_accessor :prev, :str def initialize(prev, str) @prev = prev @str = str end end def show_trunk(p) return if p.nil? show_trunk(p.prev) print p.str end ### Вывести двоичное дерево ### # This tree printer is borrowed from TECHIE DELIGHT # https://www.techiedelight.com/c-program-print-binary-tree/ def print_tree(root, prev=nil, is_right=false) return if root.nil? prev_str = " " trunk = Trunk.new(prev, prev_str) print_tree(root.right, trunk, true) if prev.nil? trunk.str = "———" elsif is_right trunk.str = "/———" prev_str = " |" else trunk.str = "\\———" prev.str = prev_str end show_trunk(trunk) puts " #{root.val}" prev.str = prev_str if prev trunk.str = " |" print_tree(root.left, trunk, false) end ### Вывести хеш-таблицу ### def print_hash_map(hmap) hmap.entries.each { |key, value| puts "#{key} -> #{value}" } end ### Вывести кучу ### def print_heap(heap) puts "Массивное представление кучи:#{heap}" puts "Древовидное представление кучи:" root = arr_to_tree(heap) print_tree(root) end ================================================ FILE: ru/codes/ruby/utils/tree_node.rb ================================================ =begin File: tree_node.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Класс узла двоичного дерева ### class TreeNode attr_accessor :val # Значение узла attr_accessor :height # Высота узла attr_accessor :left # Ссылка на левый дочерний узел attr_accessor :right # Ссылка на правый дочерний узел def initialize(val=0) @val = val @height = 0 end end ### Десериализация списка в двоичное дерево: рекурсия ### def arr_to_tree_dfs(arr, i) # Если индекс выходит за длину массива или соответствующий элемент равен nil, вернуть nil return if i < 0 || i >= arr.length || arr[i].nil? # Построить текущий узел root = TreeNode.new(arr[i]) # Рекурсивно построить левое и правое поддеревья root.left = arr_to_tree_dfs(arr, 2 * i + 1) root.right = arr_to_tree_dfs(arr, 2 * i + 2) root end ### Десериализация списка в двоичное дерево ### def arr_to_tree(arr) arr_to_tree_dfs(arr, 0) end ### Сериализация двоичного дерева в список: рекурсия ### def tree_to_arr_dfs(root, i, res) return if root.nil? res += Array.new(i - res.length + 1) if i >= res.length res[i] = root.val tree_to_arr_dfs(root.left, 2 * i + 1, res) tree_to_arr_dfs(root.right, 2 * i + 2, res) end ### Сериализация двоичного дерева в список ### def tree_to_arr(root) res = [] tree_to_arr_dfs(root, 0, res) res end ================================================ FILE: ru/codes/ruby/utils/vertex.rb ================================================ =begin File: vertex.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Класс вершины ### class Vertex attr_accessor :val def initialize(val) @val = val end end ### На вход подается список значений vals, на выходе возвращается список вершин vets ### def vals_to_vets(vals) Array.new(vals.length) { |i| Vertex.new(vals[i]) } end ### На вход подается список вершин vets, на выходе возвращается список значений vals ### def vets_to_vals(vets) Array.new(vets.length) { |i| vets[i].val } end ================================================ FILE: ru/codes/rust/.gitignore ================================================ target/ Cargo.lock ================================================ FILE: ru/codes/rust/Cargo.toml ================================================ [package] name = "hello-algo-rust" version = "0.1.0" edition = "2021" publish = false # Run Command: cargo run --bin time_complexity [[bin]] name = "time_complexity" path = "chapter_computational_complexity/time_complexity.rs" # Run Command: cargo run --bin worst_best_time_complexity [[bin]] name = "worst_best_time_complexity" path = "chapter_computational_complexity/worst_best_time_complexity.rs" # Run Command: cargo run --bin space_complexity [[bin]] name = "space_complexity" path = "chapter_computational_complexity/space_complexity.rs" # Run Command: cargo run --bin iteration [[bin]] name = "iteration" path = "chapter_computational_complexity/iteration.rs" # Run Command: cargo run --bin recursion [[bin]] name = "recursion" path = "chapter_computational_complexity/recursion.rs" # Run Command: cargo run --bin two_sum [[bin]] name = "two_sum" path = "chapter_searching/two_sum.rs" # Run Command: cargo run --bin array [[bin]] name = "array" path = "chapter_array_and_linkedlist/array.rs" # Run Command: cargo run --bin linked_list [[bin]] name = "linked_list" path = "chapter_array_and_linkedlist/linked_list.rs" # Run Command: cargo run --bin list [[bin]] name = "list" path = "chapter_array_and_linkedlist/list.rs" # Run Command: cargo run --bin my_list [[bin]] name = "my_list" path = "chapter_array_and_linkedlist/my_list.rs" # Run Command: cargo run --bin stack [[bin]] name = "stack" path = "chapter_stack_and_queue/stack.rs" # Run Command: cargo run --bin linkedlist_stack [[bin]] name = "linkedlist_stack" path = "chapter_stack_and_queue/linkedlist_stack.rs" # Run Command: cargo run --bin queue [[bin]] name = "queue" path = "chapter_stack_and_queue/queue.rs" # Run Command: cargo run --bin linkedlist_queue [[bin]] name = "linkedlist_queue" path = "chapter_stack_and_queue/linkedlist_queue.rs" # Run Command: cargo run --bin deque [[bin]] name = "deque" path = "chapter_stack_and_queue/deque.rs" # Run Command: cargo run --bin array_deque [[bin]] name = "array_deque" path = "chapter_stack_and_queue/array_deque.rs" # Run Command: cargo run --bin linkedlist_deque [[bin]] name = "linkedlist_deque" path = "chapter_stack_and_queue/linkedlist_deque.rs" # Run Command: cargo run --bin simple_hash [[bin]] name = "simple_hash" path = "chapter_hashing/simple_hash.rs" # Run Command: cargo run --bin hash_map [[bin]] name = "hash_map" path = "chapter_hashing/hash_map.rs" # Run Command: cargo run --bin array_hash_map [[bin]] name = "array_hash_map" path = "chapter_hashing/array_hash_map.rs" # Run Command: cargo run --bin build_in_hash [[bin]] name = "build_in_hash" path = "chapter_hashing/build_in_hash.rs" # Run Command: cargo run --bin hash_map_chaining [[bin]] name = "hash_map_chaining" path = "chapter_hashing/hash_map_chaining.rs" # Run Command: cargo run --bin hash_map_open_addressing [[bin]] name = "hash_map_open_addressing" path = "chapter_hashing/hash_map_open_addressing.rs" # Run Command: cargo run --bin binary_search [[bin]] name = "binary_search" path = "chapter_searching/binary_search.rs" # Run Command: cargo run --bin binary_search_edge [[bin]] name = "binary_search_edge" path = "chapter_searching/binary_search_edge.rs" # Run Command: cargo run --bin binary_search_insertion [[bin]] name = "binary_search_insertion" path = "chapter_searching/binary_search_insertion.rs" # Run Command: cargo run --bin bubble_sort [[bin]] name = "bubble_sort" path = "chapter_sorting/bubble_sort.rs" # Run Command: cargo run --bin insertion_sort [[bin]] name = "insertion_sort" path = "chapter_sorting/insertion_sort.rs" # Run Command: cargo run --bin quick_sort [[bin]] name = "quick_sort" path = "chapter_sorting/quick_sort.rs" # Run Command: cargo run --bin merge_sort [[bin]] name = "merge_sort" path = "chapter_sorting/merge_sort.rs" # Run Command: cargo run --bin selection_sort [[bin]] name = "selection_sort" path = "chapter_sorting/selection_sort.rs" # Run Command: cargo run --bin bucket_sort [[bin]] name = "bucket_sort" path = "chapter_sorting/bucket_sort.rs" # Run Command: cargo run --bin heap_sort [[bin]] name = "heap_sort" path = "chapter_sorting/heap_sort.rs" # Run Command: cargo run --bin counting_sort [[bin]] name = "counting_sort" path = "chapter_sorting/counting_sort.rs" # Run Command: cargo run --bin radix_sort [[bin]] name = "radix_sort" path = "chapter_sorting/radix_sort.rs" # Run Command: cargo run --bin array_stack [[bin]] name = "array_stack" path = "chapter_stack_and_queue/array_stack.rs" # Run Command: cargo run --bin array_queue [[bin]] name = "array_queue" path = "chapter_stack_and_queue/array_queue.rs" # Run Command: cargo run --bin array_binary_tree [[bin]] name = "array_binary_tree" path = "chapter_tree/array_binary_tree.rs" # Run Command: cargo run --bin avl_tree [[bin]] name = "avl_tree" path = "chapter_tree/avl_tree.rs" # Run Command: cargo run --bin binary_search_tree [[bin]] name = "binary_search_tree" path = "chapter_tree/binary_search_tree.rs" # Run Command: cargo run --bin binary_tree_bfs [[bin]] name = "binary_tree_bfs" path = "chapter_tree/binary_tree_bfs.rs" # Run Command: cargo run --bin binary_tree_dfs [[bin]] name = "binary_tree_dfs" path = "chapter_tree/binary_tree_dfs.rs" # Run Command: cargo run --bin binary_tree [[bin]] name = "binary_tree" path = "chapter_tree/binary_tree.rs" # Run Command: cargo run --bin heap [[bin]] name = "heap" path = "chapter_heap/heap.rs" # Run Command: cargo run --bin my_heap [[bin]] name = "my_heap" path = "chapter_heap/my_heap.rs" # Run Command: cargo run --bin top_k [[bin]] name = "top_k" path = "chapter_heap/top_k.rs" # Run Command: cargo run --bin graph_adjacency_list [[bin]] name = "graph_adjacency_list" path = "chapter_graph/graph_adjacency_list.rs" # Run Command: cargo run --bin graph_adjacency_matrix [[bin]] name = "graph_adjacency_matrix" path = "chapter_graph/graph_adjacency_matrix.rs" # Run Command: cargo run --bin graph_bfs [[bin]] name = "graph_bfs" path = "chapter_graph/graph_bfs.rs" # Run Command: cargo run --bin graph_dfs [[bin]] name = "graph_dfs" path = "chapter_graph/graph_dfs.rs" # Run Command: cargo run --bin linear_search [[bin]] name = "linear_search" path = "chapter_searching/linear_search.rs" # Run Command: cargo run --bin hashing_search [[bin]] name = "hashing_search" path = "chapter_searching/hashing_search.rs" # Run Command: cargo run --bin climbing_stairs_dfs [[bin]] name = "climbing_stairs_dfs" path = "chapter_dynamic_programming/climbing_stairs_dfs.rs" # Run Command: cargo run --bin climbing_stairs_dfs_mem [[bin]] name = "climbing_stairs_dfs_mem" path = "chapter_dynamic_programming/climbing_stairs_dfs_mem.rs" # Run Command: cargo run --bin climbing_stairs_dp [[bin]] name = "climbing_stairs_dp" path = "chapter_dynamic_programming/climbing_stairs_dp.rs" # Run Command: cargo run --bin min_cost_climbing_stairs_dp [[bin]] name = "min_cost_climbing_stairs_dp" path = "chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs" # Run Command: cargo run --bin climbing_stairs_constraint_dp [[bin]] name = "climbing_stairs_constraint_dp" path = "chapter_dynamic_programming/climbing_stairs_constraint_dp.rs" # Run Command: cargo run --bin climbing_stairs_backtrack [[bin]] name = "climbing_stairs_backtrack" path = "chapter_dynamic_programming/climbing_stairs_backtrack.rs" # Run Command: cargo run --bin subset_sum_i_naive [[bin]] name = "subset_sum_i_naive" path = "chapter_backtracking/subset_sum_i_naive.rs" # Run Command: cargo run --bin subset_sum_i [[bin]] name = "subset_sum_i" path = "chapter_backtracking/subset_sum_i.rs" # Run Command: cargo run --bin subset_sum_ii [[bin]] name = "subset_sum_ii" path = "chapter_backtracking/subset_sum_ii.rs" # Run Command: cargo run --bin coin_change [[bin]] name = "coin_change" path = "chapter_dynamic_programming/coin_change.rs" # Run Command: cargo run --bin coin_change_ii [[bin]] name = "coin_change_ii" path = "chapter_dynamic_programming/coin_change_ii.rs" # Run Command: cargo run --bin unbounded_knapsack [[bin]] name = "unbounded_knapsack" path = "chapter_dynamic_programming/unbounded_knapsack.rs" # Run Command: cargo run --bin knapsack [[bin]] name = "knapsack" path = "chapter_dynamic_programming/knapsack.rs" # Run Command: cargo run --bin min_path_sum [[bin]] name = "min_path_sum" path = "chapter_dynamic_programming/min_path_sum.rs" # Run Command: cargo run --bin edit_distance [[bin]] name = "edit_distance" path = "chapter_dynamic_programming/edit_distance.rs" # Run Command: cargo run --bin n_queens [[bin]] name = "n_queens" path = "chapter_backtracking/n_queens.rs" # Run Command: cargo run --bin permutations_i [[bin]] name = "permutations_i" path = "chapter_backtracking/permutations_i.rs" # Run Command: cargo run --bin permutations_ii [[bin]] name = "permutations_ii" path = "chapter_backtracking/permutations_ii.rs" # Run Command: cargo run --bin preorder_traversal_i_compact [[bin]] name = "preorder_traversal_i_compact" path = "chapter_backtracking/preorder_traversal_i_compact.rs" # Run Command: cargo run --bin preorder_traversal_ii_compact [[bin]] name = "preorder_traversal_ii_compact" path = "chapter_backtracking/preorder_traversal_ii_compact.rs" # Run Command: cargo run --bin preorder_traversal_iii_compact [[bin]] name = "preorder_traversal_iii_compact" path = "chapter_backtracking/preorder_traversal_iii_compact.rs" # Run Command: cargo run --bin preorder_traversal_iii_template [[bin]] name = "preorder_traversal_iii_template" path = "chapter_backtracking/preorder_traversal_iii_template.rs" # Run Command: cargo run --bin binary_search_recur [[bin]] name = "binary_search_recur" path = "chapter_divide_and_conquer/binary_search_recur.rs" # Run Command: cargo run --bin hanota [[bin]] name = "hanota" path = "chapter_divide_and_conquer/hanota.rs" # Run Command: cargo run --bin build_tree [[bin]] name = "build_tree" path = "chapter_divide_and_conquer/build_tree.rs" # Run Command: cargo run --bin coin_change_greedy [[bin]] name = "coin_change_greedy" path = "chapter_greedy/coin_change_greedy.rs" # Run Command: cargo run --bin fractional_knapsack [[bin]] name = "fractional_knapsack" path = "chapter_greedy/fractional_knapsack.rs" # Run Command: cargo run --bin max_capacity [[bin]] name = "max_capacity" path = "chapter_greedy/max_capacity.rs" # Run Command: cargo run --bin max_product_cutting [[bin]] name = "max_product_cutting" path = "chapter_greedy/max_product_cutting.rs" [dependencies] rand = "0.8.5" ================================================ FILE: ru/codes/rust/chapter_array_and_linkedlist/array.rs ================================================ /* * File: array.rs * Created Time: 2023-01-15 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use rand::Rng; /* Случайный доступ к элементу */ fn random_access(nums: &[i32]) -> i32 { // Случайным образом выбрать число из интервала [0, nums.len()) let random_index = rand::thread_rng().gen_range(0..nums.len()); // Получить и вернуть случайный элемент let random_num = nums[random_index]; random_num } /* Увеличить длину массива */ fn extend(nums: &[i32], enlarge: usize) -> Vec { // Инициализировать массив увеличенной длины let mut res: Vec = vec![0; nums.len() + enlarge]; // Скопировать все элементы исходного массива в новый res[0..nums.len()].copy_from_slice(nums); // Вернуть новый массив после расширения res } /* Вставить элемент num по индексу index в массив */ fn insert(nums: &mut [i32], num: i32, index: usize) { // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад for i in (index + 1..nums.len()).rev() { nums[i] = nums[i - 1]; } // Присвоить num элементу по индексу index nums[index] = num; } /* Удалить элемент по индексу index */ fn remove(nums: &mut [i32], index: usize) { // Сдвинуть все элементы после индекса index на одну позицию вперед for i in index..nums.len() - 1 { nums[i] = nums[i + 1]; } } /* Обход массива */ fn traverse(nums: &[i32]) { let mut _count = 0; // Обход массива по индексам for i in 0..nums.len() { _count += nums[i]; } // Непосредственно обходить элементы массива _count = 0; for &num in nums { _count += num; } } /* Найти заданный элемент в массиве */ fn find(nums: &[i32], target: i32) -> Option { for i in 0..nums.len() { if nums[i] == target { return Some(i); } } None } /* Driver Code */ fn main() { /* Инициализация массива */ let arr: [i32; 5] = [0; 5]; print!("Массив arr = "); print_util::print_array(&arr); // В Rust при указании длины ([i32; 5]) получается массив, а без указания длины (&[i32]) — срез // Так как массивы в Rust имеют длину, определяемую на этапе компиляции, для задания длины можно использовать только константы // Vector обычно используется в Rust как тип динамического массива // Для удобства реализации метода расширения extend() ниже vector рассматривается как массив (array) let nums: Vec = vec![1, 3, 2, 5, 4]; print!("\nМассив nums = "); print_util::print_array(&nums); // Случайный доступ let random_num = random_access(&nums); println!("\nСлучайный элемент из nums = {}", random_num); // Расширение длины let mut nums: Vec = extend(&nums, 3); print!("После увеличения длины массива до 8 nums = "); print_util::print_array(&nums); // Вставка элемента insert(&mut nums, 6, 3); print!("\nПосле вставки числа 6 по индексу 3 nums = "); print_util::print_array(&nums); // Удаление элемента remove(&mut nums, 2); print!("\nПосле удаления элемента по индексу 2 nums = "); print_util::print_array(&nums); // Обход массива traverse(&nums); // Поиск элемента let index = find(&nums, 3).unwrap(); println!("\nПоиск элемента 3 в nums: индекс = {}", index); } ================================================ FILE: ru/codes/rust/chapter_array_and_linkedlist/linked_list.rs ================================================ /* * File: linked_list.rs * Created Time: 2023-03-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* Вставить узел P после узла n0 в связном списке */ #[allow(non_snake_case)] pub fn insert(n0: &Rc>>, P: Rc>>) { let n1 = n0.borrow_mut().next.take(); P.borrow_mut().next = n1; n0.borrow_mut().next = Some(P); } /* Удалить первый узел после узла n0 в связном списке */ #[allow(non_snake_case)] pub fn remove(n0: &Rc>>) { // n0 -> P -> n1 let P = n0.borrow_mut().next.take(); if let Some(node) = P { let n1 = node.borrow_mut().next.take(); n0.borrow_mut().next = n1; } } /* Доступ к узлу связного списка по индексу index */ pub fn access(head: Rc>>, index: i32) -> Option>>> { fn dfs( head: Option<&Rc>>>, index: i32, ) -> Option>>> { if index <= 0 { return head.cloned(); } if let Some(node) = head { dfs(node.borrow().next.as_ref(), index - 1) } else { None } } dfs(Some(head).as_ref(), index) } /* Найти в связном списке первый узел со значением target */ pub fn find(head: Rc>>, target: T) -> i32 { fn find(head: Option<&Rc>>>, target: T, idx: i32) -> i32 { if let Some(node) = head { if node.borrow().val == target { return idx; } return find(node.borrow().next.as_ref(), target, idx + 1); } else { -1 } } find(Some(head).as_ref(), target, 0) } /* Driver Code */ fn main() { /* Инициализация связного списка */ // Инициализация всех узлов let n0 = ListNode::new(1); let n1 = ListNode::new(3); let n2 = ListNode::new(2); let n3 = ListNode::new(5); let n4 = ListNode::new(4); // Построить ссылки между узлами n0.borrow_mut().next = Some(n1.clone()); n1.borrow_mut().next = Some(n2.clone()); n2.borrow_mut().next = Some(n3.clone()); n3.borrow_mut().next = Some(n4.clone()); print!("Исходный связный список "); print_util::print_linked_list(&n0); /* Вставка узла */ insert(&n0, ListNode::new(0)); print!("Связный список после вставки узла "); print_util::print_linked_list(&n0); /* Удаление узла */ remove(&n0); print!("Связный список после удаления узла "); print_util::print_linked_list(&n0); /* Доступ к узлу */ let node = access(n0.clone(), 3); println!("Значение узла по индексу 3 в связном списке = {}", node.unwrap().borrow().val); /* Поиск узла */ let index = find(n0.clone(), 2); println!("Индекс узла со значением 2 в связном списке = {}", index); } ================================================ FILE: ru/codes/rust/chapter_array_and_linkedlist/list.rs ================================================ /* * File: list.rs * Created Time: 2023-01-18 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Driver Code */ fn main() { // Инициализация списка let mut nums: Vec = vec![1, 3, 2, 5, 4]; print!("Список nums = "); print_util::print_array(&nums); // Доступ к элементу let num = nums[1]; println!("\nЭлемент по индексу 1: num = {num}"); // Обновление элемента nums[1] = 0; print!("После обновления элемента по индексу 1 до 0 nums = "); print_util::print_array(&nums); // Очистить список nums.clear(); print!("\nПосле очистки списка nums = "); print_util::print_array(&nums); // Добавление элемента в конец nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); print!("\nПосле добавления элементов nums = "); print_util::print_array(&nums); // Вставка элемента в середину nums.insert(3, 6); print!("\nПосле вставки числа 6 по индексу 3 nums = "); print_util::print_array(&nums); // Удаление элемента nums.remove(3); print!("\nПосле удаления элемента по индексу 3 nums = "); print_util::print_array(&nums); // Обходить список по индексам let mut _count = 0; for i in 0..nums.len() { _count += nums[i]; } // Непосредственно обходить элементы списка _count = 0; for x in &nums { _count += x; } // Объединить два списка let mut nums1 = vec![6, 8, 7, 10, 9]; nums.append(&mut nums1); // После append (перемещение) nums1 становится пустым! // nums.extend(&nums1); // extend (заимствование), nums1 можно продолжать использовать print!("\nПосле конкатенации списка nums1 к nums nums = "); print_util::print_array(&nums); // Отсортировать список nums.sort(); print!("\nПосле сортировки списка nums = "); print_util::print_array(&nums); } ================================================ FILE: ru/codes/rust/chapter_array_and_linkedlist/my_list.rs ================================================ /* * File: my_list.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Класс списка */ #[allow(dead_code)] struct MyList { arr: Vec, // Массив (для хранения элементов списка) capacity: usize, // Вместимость списка size: usize, // Длина списка (текущее число элементов) extend_ratio: usize, // Коэффициент увеличения списка при каждом расширении } #[allow(unused, unused_comparisons)] impl MyList { /* Конструктор */ pub fn new(capacity: usize) -> Self { let mut vec = vec![0; capacity]; Self { arr: vec, capacity, size: 0, extend_ratio: 2, } } /* Получить длину списка (текущее число элементов) */ pub fn size(&self) -> usize { return self.size; } /* Получить вместимость списка */ pub fn capacity(&self) -> usize { return self.capacity; } /* Доступ к элементу */ pub fn get(&self, index: usize) -> i32 { // Если индекс выходит за границы, выбрасывается исключение; далее аналогично if index >= self.size { panic!("индекс выходит за границы") }; return self.arr[index]; } /* Обновление элемента */ pub fn set(&mut self, index: usize, num: i32) { if index >= self.size { panic!("индекс выходит за границы") }; self.arr[index] = num; } /* Добавление элемента в конец */ pub fn add(&mut self, num: i32) { // При превышении вместимости по числу элементов запускается расширение if self.size == self.capacity() { self.extend_capacity(); } self.arr[self.size] = num; // Обновить число элементов self.size += 1; } /* Вставка элемента в середину */ pub fn insert(&mut self, index: usize, num: i32) { if index >= self.size() { panic!("индекс выходит за границы") }; // При превышении вместимости по числу элементов запускается расширение if self.size == self.capacity() { self.extend_capacity(); } // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад for j in (index..self.size).rev() { self.arr[j + 1] = self.arr[j]; } self.arr[index] = num; // Обновить число элементов self.size += 1; } /* Удаление элемента */ pub fn remove(&mut self, index: usize) -> i32 { if index >= self.size() { panic!("индекс выходит за границы") }; let num = self.arr[index]; // Сдвинуть все элементы после индекса index на одну позицию вперед for j in index..self.size - 1 { self.arr[j] = self.arr[j + 1]; } // Обновить число элементов self.size -= 1; // Вернуть удаленный элемент return num; } /* Расширение списка */ pub fn extend_capacity(&mut self) { // Создать новый массив длиной в extend_ratio раз больше исходного и скопировать в него исходный массив let new_capacity = self.capacity * self.extend_ratio; self.arr.resize(new_capacity, 0); // Обновить вместимость списка self.capacity = new_capacity; } /* Преобразовать список в массив */ pub fn to_array(&self) -> Vec { // Преобразовывать только элементы списка в пределах фактической длины let mut arr = Vec::new(); for i in 0..self.size { arr.push(self.get(i)); } arr } } /* Driver Code */ fn main() { /* Инициализация списка */ let mut nums = MyList::new(10); /* Добавление элемента в конец */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print!("Список nums = "); print_util::print_array(&nums.to_array()); print!(" , вместимость = {} , длина = {}", nums.capacity(), nums.size()); /* Вставка элемента в середину */ nums.insert(3, 6); print!("\nПосле вставки числа 6 по индексу 3 nums = "); print_util::print_array(&nums.to_array()); /* Удаление элемента */ nums.remove(3); print!("\nПосле удаления элемента по индексу 3 nums = "); print_util::print_array(&nums.to_array()); /* Доступ к элементу */ let num = nums.get(1); println!("\nЭлемент по индексу 1: num = {num}"); /* Обновление элемента */ nums.set(1, 0); print!("После обновления элемента по индексу 1 до 0 nums = "); print_util::print_array(&nums.to_array()); /* Проверка механизма расширения */ for i in 0..10 { // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения nums.add(i); } print!("\nСписок nums после увеличения вместимости = "); print_util::print_array(&nums.to_array()); print!(" , вместимость = {} , длина = {}", nums.capacity(), nums.size()); } ================================================ FILE: ru/codes/rust/chapter_backtracking/n_queens.rs ================================================ /* * File: n_queens.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* Алгоритм бэктрекинга: n ферзей */ fn backtrack( row: usize, n: usize, state: &mut Vec>, res: &mut Vec>>, cols: &mut [bool], diags1: &mut [bool], diags2: &mut [bool], ) { // Когда все строки уже обработаны, записать решение if row == n { res.push(state.clone()); return; } // Обойти все столбцы for col in 0..n { // Вычислить главную и побочную диагонали, соответствующие этой клетке let diag1 = row + n - 1 - col; let diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if !cols[col] && !diags1[diag1] && !diags2[diag2] { // Попытка: поставить ферзя в эту клетку state[row][col] = "Q".into(); (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true); // Перейти к размещению следующей строки backtrack(row + 1, n, state, res, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state[row][col] = "#".into(); (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false); } } } /* Решить задачу о n ферзях */ fn n_queens(n: usize) -> Vec>> { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку let mut state: Vec> = vec![vec!["#".to_string(); n]; n]; let mut cols = vec![false; n]; // Отмечать, есть ли ферзь в столбце let mut diags1 = vec![false; 2 * n - 1]; // Отмечать наличие ферзя на главной диагонали let mut diags2 = vec![false; 2 * n - 1]; // Отмечать наличие ферзя на побочной диагонали let mut res: Vec>> = Vec::new(); backtrack( 0, n, &mut state, &mut res, &mut cols, &mut diags1, &mut diags2, ); res } /* Driver Code */ pub fn main() { let n: usize = 4; let res = n_queens(n); println!("Размер входной доски = {n}"); println!("Количество способов расстановки ферзей: {}", res.len()); for state in res.iter() { println!("--------------------"); for row in state.iter() { println!("{:?}", row); } } } ================================================ FILE: ru/codes/rust/chapter_backtracking/permutations_i.rs ================================================ /* * File: permutations_i.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* Алгоритм бэктрекинга: все перестановки I */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // Когда длина состояния равна числу элементов, записать решение if state.len() == choices.len() { res.push(state); return; } // Перебор всех вариантов выбора for i in 0..choices.len() { let choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно if !selected[i] { // Попытка: сделать выбор и обновить состояние selected[i] = true; state.push(choice); // Перейти к следующему выбору backtrack(state.clone(), choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop(); } } } /* Все перестановки I */ fn permutations_i(nums: &mut [i32]) -> Vec> { let mut res = Vec::new(); // Состояние (подмножество) backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [1, 2, 3]; let res = permutations_i(&mut nums); println!("Входной массив nums = {:?}", &nums); println!("Все перестановки res = {:?}", &res); } ================================================ FILE: ru/codes/rust/chapter_backtracking/permutations_ii.rs ================================================ /* * File: permutations_ii.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use std::collections::HashSet; /* Алгоритм бэктрекинга: все перестановки II */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // Когда длина состояния равна числу элементов, записать решение if state.len() == choices.len() { res.push(state); return; } // Перебор всех вариантов выбора let mut duplicated = HashSet::::new(); for i in 0..choices.len() { let choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if !selected[i] && !duplicated.contains(&choice) { // Попытка: сделать выбор и обновить состояние duplicated.insert(choice); // Записать значения уже выбранных элементов selected[i] = true; state.push(choice); // Перейти к следующему выбору backtrack(state.clone(), choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop(); } } } /* Все перестановки II */ fn permutations_ii(nums: &mut [i32]) -> Vec> { let mut res = Vec::new(); backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [1, 2, 2]; let res = permutations_ii(&mut nums); println!("Входной массив nums = {:?}", &nums); println!("Все перестановки res = {:?}", &res); } ================================================ FILE: ru/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs ================================================ /* * File: preorder_traversal_i_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* Предварительный обход: пример 1 */ fn pre_order(res: &mut Vec>>, root: Option<&Rc>>) { if root.is_none() { return; } if let Some(node) = root { if node.borrow().val == 7 { // Записать решение res.push(node.clone()); } pre_order(res, node.borrow().left.as_ref()); pre_order(res, node.borrow().right.as_ref()); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("Инициализация двоичного дерева"); print_util::print_tree(root.as_ref().unwrap()); // Предварительный обход let mut res = Vec::new(); pre_order(&mut res, root.as_ref()); println!("\nВсе узлы со значением 7"); let mut vals = Vec::new(); for node in res { vals.push(node.borrow().val) } println!("{:?}", vals); } ================================================ FILE: ru/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs ================================================ /* * File: preorder_traversal_ii_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* Предварительный обход: пример 2 */ fn pre_order( res: &mut Vec>>>, path: &mut Vec>>, root: Option<&Rc>>, ) { if root.is_none() { return; } if let Some(node) = root { // Попытка path.push(node.clone()); if node.borrow().val == 7 { // Записать решение res.push(path.clone()); } pre_order(res, path, node.borrow().left.as_ref()); pre_order(res, path, node.borrow().right.as_ref()); // Откат path.pop(); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("Инициализация двоичного дерева"); print_util::print_tree(root.as_ref().unwrap()); // Предварительный обход let mut path = Vec::new(); let mut res = Vec::new(); pre_order(&mut res, &mut path, root.as_ref()); println!("\nВсе пути от корня к узлу 7"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: ru/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs ================================================ /* * File: preorder_traversal_iii_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* Предварительный обход: пример 3 */ fn pre_order( res: &mut Vec>>>, path: &mut Vec>>, root: Option<&Rc>>, ) { // Отсечение if root.is_none() || root.as_ref().unwrap().borrow().val == 3 { return; } if let Some(node) = root { // Попытка path.push(node.clone()); if node.borrow().val == 7 { // Записать решение res.push(path.clone()); } pre_order(res, path, node.borrow().left.as_ref()); pre_order(res, path, node.borrow().right.as_ref()); // Откат path.pop(); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("Инициализация двоичного дерева"); print_util::print_tree(root.as_ref().unwrap()); // Предварительный обход let mut path = Vec::new(); let mut res = Vec::new(); pre_order(&mut res, &mut path, root.as_ref()); println!("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: ru/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs ================================================ /* * File: preorder_traversal_iii_template.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* Проверить, является ли текущее состояние решением */ fn is_solution(state: &mut Vec>>) -> bool { return !state.is_empty() && state.last().unwrap().borrow().val == 7; } /* Записать решение */ fn record_solution( state: &mut Vec>>, res: &mut Vec>>>, ) { res.push(state.clone()); } /* Проверить, допустим ли этот выбор в текущем состоянии */ fn is_valid(_: &mut Vec>>, choice: Option<&Rc>>) -> bool { return choice.is_some() && choice.unwrap().borrow().val != 3; } /* Обновить состояние */ fn make_choice(state: &mut Vec>>, choice: Rc>) { state.push(choice); } /* Восстановить состояние */ fn undo_choice(state: &mut Vec>>, _: Rc>) { state.pop(); } /* Алгоритм бэктрекинга: пример 3 */ fn backtrack( state: &mut Vec>>, choices: &Vec>>>, res: &mut Vec>>>, ) { // Проверить, является ли текущее состояние решением if is_solution(state) { // Записать решение record_solution(state, res); } // Перебор всех вариантов выбора for &choice in choices.iter() { // Отсечение: проверить допустимость выбора if is_valid(state, choice) { // Попытка: сделать выбор и обновить состояние make_choice(state, choice.unwrap().clone()); // Перейти к следующему выбору backtrack( state, &vec![ choice.unwrap().borrow().left.as_ref(), choice.unwrap().borrow().right.as_ref(), ], res, ); // Откат: отменить выбор и восстановить предыдущее состояние undo_choice(state, choice.unwrap().clone()); } } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("Инициализация двоичного дерева"); print_util::print_tree(root.as_ref().unwrap()); // Алгоритм бэктрекинга let mut res = Vec::new(); backtrack(&mut Vec::new(), &mut vec![root.as_ref()], &mut res); println!("\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: ru/codes/rust/chapter_backtracking/subset_sum_i.rs ================================================ /* * File: subset_sum_i.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Алгоритм бэктрекинга: сумма подмножеств I */ fn backtrack( state: &mut Vec, target: i32, choices: &[i32], start: usize, res: &mut Vec>, ) { // Если сумма подмножества равна target, записать решение if target == 0 { res.push(state.clone()); return; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств for i in start..choices.len() { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if target - choices[i] < 0 { break; } // Попытка: сделать выбор и обновить target и start state.push(choices[i]); // Перейти к следующему выбору backtrack(state, target - choices[i], choices, i, res); // Откат: отменить выбор и восстановить предыдущее состояние state.pop(); } } /* Решить задачу суммы подмножеств I */ fn subset_sum_i(nums: &mut [i32], target: i32) -> Vec> { let mut state = Vec::new(); // Состояние (подмножество) nums.sort(); // Отсортировать nums let start = 0; // Стартовая вершина обхода let mut res = Vec::new(); // Список результатов (список подмножеств) backtrack(&mut state, target, nums, start, &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [3, 4, 5]; let target = 9; let res = subset_sum_i(&mut nums, target); println!("Входной массив nums = {:?}, target = {}", &nums, target); println!("Все подмножества с суммой {}: res = {:?}", target, &res); } ================================================ FILE: ru/codes/rust/chapter_backtracking/subset_sum_i_naive.rs ================================================ /* * File: subset_sum_i_naive.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Алгоритм бэктрекинга: сумма подмножеств I */ fn backtrack( state: &mut Vec, target: i32, total: i32, choices: &[i32], res: &mut Vec>, ) { // Если сумма подмножества равна target, записать решение if total == target { res.push(state.clone()); return; } // Перебор всех вариантов выбора for i in 0..choices.len() { // Отсечение: если сумма подмножества превышает target, пропустить этот выбор if total + choices[i] > target { continue; } // Попытка: сделать выбор и обновить элемент и total state.push(choices[i]); // Перейти к следующему выбору backtrack(state, target, total + choices[i], choices, res); // Откат: отменить выбор и восстановить предыдущее состояние state.pop(); } } /* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ fn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec> { let mut state = Vec::new(); // Состояние (подмножество) let total = 0; // Сумма подмножеств let mut res = Vec::new(); // Список результатов (список подмножеств) backtrack(&mut state, target, total, nums, &mut res); res } /* Driver Code */ pub fn main() { let nums = [3, 4, 5]; let target = 9; let res = subset_sum_i_naive(&nums, target); println!("Входной массив nums = {:?}, target = {}", &nums, target); println!("Все подмножества с суммой {}: res = {:?}", target, &res); println!("Обратите внимание: результат этого метода содержит повторяющиеся множества"); } ================================================ FILE: ru/codes/rust/chapter_backtracking/subset_sum_ii.rs ================================================ /* * File: subset_sum_ii.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Алгоритм бэктрекинга: сумма подмножеств II */ fn backtrack( state: &mut Vec, target: i32, choices: &[i32], start: usize, res: &mut Vec>, ) { // Если сумма подмножества равна target, записать решение if target == 0 { res.push(state.clone()); return; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента for i in start..choices.len() { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if target - choices[i] < 0 { break; } // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить if i > start && choices[i] == choices[i - 1] { continue; } // Попытка: сделать выбор и обновить target и start state.push(choices[i]); // Перейти к следующему выбору backtrack(state, target - choices[i], choices, i + 1, res); // Откат: отменить выбор и восстановить предыдущее состояние state.pop(); } } /* Решить задачу суммы подмножеств II */ fn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec> { let mut state = Vec::new(); // Состояние (подмножество) nums.sort(); // Отсортировать nums let start = 0; // Стартовая вершина обхода let mut res = Vec::new(); // Список результатов (список подмножеств) backtrack(&mut state, target, nums, start, &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [4, 4, 5]; let target = 9; let res = subset_sum_ii(&mut nums, target); println!("Входной массив nums = {:?}, target = {}", &nums, target); println!("Все подмножества с суммой {}: res = {:?}", target, &res); } ================================================ FILE: ru/codes/rust/chapter_computational_complexity/iteration.rs ================================================ /* * File: iteration.rs * Created Time: 2023-09-02 * Author: night-cruise (2586447362@qq.com) */ /* Цикл for */ fn for_loop(n: i32) -> i32 { let mut res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for i in 1..=n { res += i; } res } /* Цикл while */ fn while_loop(n: i32) -> i32 { let mut res = 0; let mut i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while i <= n { res += i; i += 1; // Обновить условную переменную } res } /* Цикл while (двойное обновление) */ fn while_loop_ii(n: i32) -> i32 { let mut res = 0; let mut i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while i <= n { res += i; // Обновить условную переменную i += 1; i *= 2; } res } /* Двойной цикл for */ fn nested_for_loop(n: i32) -> String { let mut res = vec![]; // Цикл по i = 1, 2, ..., n-1, n for i in 1..=n { // Цикл по j = 1, 2, ..., n-1, n for j in 1..=n { res.push(format!("({}, {}), ", i, j)); } } res.join("") } /* Driver Code */ fn main() { let n = 5; let mut res; res = for_loop(n); println!("\nРезультат суммирования в цикле for res = {res}"); res = while_loop(n); println!("\nРезультат суммирования в цикле while res = {res}"); res = while_loop_ii(n); println!("\nРезультат суммирования в цикле while (двойное обновление) res = {}", res); let res = nested_for_loop(n); println!("\nРезультат обхода в двойном цикле for {res}"); } ================================================ FILE: ru/codes/rust/chapter_computational_complexity/recursion.rs ================================================ /* * File: recursion.rs * Created Time: 2023-09-02 * Author: night-cruise (2586447362@qq.com) */ /* Рекурсия */ fn recur(n: i32) -> i32 { // Условие завершения if n == 1 { return 1; } // Рекурсия: рекурсивный вызов let res = recur(n - 1); // Возврат: вернуть результат n + res } /* Имитация рекурсии итерацией */ fn for_loop_recur(n: i32) -> i32 { // Использовать явный стек для имитации системного стека вызовов let mut stack = Vec::new(); let mut res = 0; // Рекурсия: рекурсивный вызов for i in (1..=n).rev() { // Имитировать «рекурсию» с помощью операции помещения в стек stack.push(i); } // Возврат: вернуть результат while !stack.is_empty() { // Имитировать «возврат» с помощью операции извлечения из стека res += stack.pop().unwrap(); } // res = 1+2+3+...+n res } /* Хвостовая рекурсия */ fn tail_recur(n: i32, res: i32) -> i32 { // Условие завершения if n == 0 { return res; } // Хвостовой рекурсивный вызов tail_recur(n - 1, res + n) } /* Последовательность Фибоначчи: рекурсия */ fn fib(n: i32) -> i32 { // Условие завершения: f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1; } // Рекурсивный вызов f(n) = f(n-1) + f(n-2) let res = fib(n - 1) + fib(n - 2); // Вернуть результат res } /* Driver Code */ fn main() { let n = 5; let mut res; res = recur(n); println!("\nРезультат суммирования в рекурсивной функции res = {res}"); res = for_loop_recur(n); println!("\nРезультат суммирования при имитации рекурсии res = {res}"); res = tail_recur(n, 0); println!("\nРезультат суммирования в хвостовой рекурсии res = {res}"); res = fib(n); println!("\nЧлен последовательности Фибоначчи с номером {n} = {res}"); } ================================================ FILE: ru/codes/rust/chapter_computational_complexity/space_complexity.rs ================================================ /* * File: space_complexity.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode, TreeNode}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; /* Функция */ fn function() -> i32 { // Выполнить некоторые операции return 0; } /* Постоянная сложность */ #[allow(unused)] fn constant(n: i32) { // Константы, переменные и объекты занимают O(1) памяти const A: i32 = 0; let b = 0; let nums = vec![0; 10000]; let node = ListNode::new(0); // Переменные в цикле занимают O(1) памяти for i in 0..n { let c = 0; } // Функции в цикле занимают O(1) памяти for i in 0..n { function(); } } /* Линейная сложность */ #[allow(unused)] fn linear(n: i32) { // Массив длины n занимает O(n) памяти let mut nums = vec![0; n as usize]; // Список длины n занимает O(n) памяти let mut nodes = Vec::new(); for i in 0..n { nodes.push(ListNode::new(i)) } // Хеш-таблица длины n занимает O(n) памяти let mut map = HashMap::new(); for i in 0..n { map.insert(i, i.to_string()); } } /* Линейная сложность (рекурсивная реализация) */ fn linear_recur(n: i32) { println!("Рекурсия n = {}", n); if n == 1 { return; }; linear_recur(n - 1); } /* Квадратичная сложность */ #[allow(unused)] fn quadratic(n: i32) { // Матрица занимает O(n^2) памяти let num_matrix = vec![vec![0; n as usize]; n as usize]; // Двумерный список занимает O(n^2) памяти let mut num_list = Vec::new(); for i in 0..n { let mut tmp = Vec::new(); for j in 0..n { tmp.push(0); } num_list.push(tmp); } } /* Квадратичная сложность (рекурсивная реализация) */ fn quadratic_recur(n: i32) -> i32 { if n <= 0 { return 0; }; // Длина массива nums равна n, n-1, ..., 2, 1 let nums = vec![0; n as usize]; println!("В рекурсии n = {} , длина nums = {}", n, nums.len()); return quadratic_recur(n - 1); } /* Экспоненциальная сложность (построение полного двоичного дерева) */ fn build_tree(n: i32) -> Option>> { if n == 0 { return None; }; let root = TreeNode::new(0); root.borrow_mut().left = build_tree(n - 1); root.borrow_mut().right = build_tree(n - 1); return Some(root); } /* Driver Code */ fn main() { let n = 5; // Постоянная сложность constant(n); // Линейная сложность linear(n); linear_recur(n); // Квадратичная сложность quadratic(n); quadratic_recur(n); // Экспоненциальная сложность let root = build_tree(n); print_util::print_tree(&root.unwrap()); } ================================================ FILE: ru/codes/rust/chapter_computational_complexity/time_complexity.rs ================================================ /* * File: time_complexity.rs * Created Time: 2023-01-10 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ /* Постоянная сложность */ fn constant(n: i32) -> i32 { _ = n; let mut count = 0; let size = 100_000; for _ in 0..size { count += 1; } count } /* Линейная сложность */ fn linear(n: i32) -> i32 { let mut count = 0; for _ in 0..n { count += 1; } count } /* Линейная сложность (обход массива) */ fn array_traversal(nums: &[i32]) -> i32 { let mut count = 0; // Число итераций пропорционально длине массива for _ in nums { count += 1; } count } /* Квадратичная сложность */ fn quadratic(n: i32) -> i32 { let mut count = 0; // Число итераций квадратично зависит от размера данных n for _ in 0..n { for _ in 0..n { count += 1; } } count } /* Квадратичная сложность (пузырьковая сортировка) */ fn bubble_sort(nums: &mut [i32]) -> i32 { let mut count = 0; // Счетчик // Внешний цикл: неотсортированный диапазон [0, i] for i in (1..nums.len()).rev() { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for j in 0..i { if nums[j] > nums[j + 1] { // Поменять местами nums[j] и nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // Обмен элементов включает 3 элементарные операции } } } count } /* Экспоненциальная сложность (итеративная реализация) */ fn exponential(n: i32) -> i32 { let mut count = 0; let mut base = 1; // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) for _ in 0..n { for _ in 0..base { count += 1 } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 count } /* Экспоненциальная сложность (рекурсивная реализация) */ fn exp_recur(n: i32) -> i32 { if n == 1 { return 1; } exp_recur(n - 1) + exp_recur(n - 1) + 1 } /* Логарифмическая сложность (итеративная реализация) */ fn logarithmic(mut n: i32) -> i32 { let mut count = 0; while n > 1 { n = n / 2; count += 1; } count } /* Логарифмическая сложность (рекурсивная реализация) */ fn log_recur(n: i32) -> i32 { if n <= 1 { return 0; } log_recur(n / 2) + 1 } /* Линейно-логарифмическая сложность */ fn linear_log_recur(n: i32) -> i32 { if n <= 1 { return 1; } let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2); for _ in 0..n { count += 1; } return count; } /* Факториальная сложность (рекурсивная реализация) */ fn factorial_recur(n: i32) -> i32 { if n == 0 { return 1; } let mut count = 0; // Из одного получается n for _ in 0..n { count += factorial_recur(n - 1); } count } /* Driver Code */ fn main() { // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях let n: i32 = 8; println!("Размер входных данных n = {}", n); let mut count = constant(n); println!("Число операций константной сложности = {}", count); count = linear(n); println!("Число операций линейной сложности = {}", count); count = array_traversal(&vec![0; n as usize]); println!("Число операций линейной сложности (обход массива) = {}", count); count = quadratic(n); println!("Число операций квадратичной сложности = {}", count); let mut nums = (1..=n).rev().collect::>(); // [n,n-1,...,2,1] count = bubble_sort(&mut nums); println!("Число операций квадратичной сложности (пузырьковая сортировка) = {}", count); count = exponential(n); println!("Число операций экспоненциальной сложности (итеративная реализация) = {}", count); count = exp_recur(n); println!("Число операций экспоненциальной сложности (рекурсивная реализация) = {}", count); count = logarithmic(n); println!("Число операций логарифмической сложности (итеративная реализация) = {}", count); count = log_recur(n); println!("Число операций логарифмической сложности (рекурсивная реализация) = {}", count); count = linear_log_recur(n); println!("Число операций линейно-логарифмической сложности (рекурсивная реализация) = {}", count); count = factorial_recur(n); println!("Число операций факториальной сложности (рекурсивная реализация) = {}", count); } ================================================ FILE: ru/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs ================================================ /* * File: worst_best_time_complexity.rs * Created Time: 2023-01-13 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use rand::seq::SliceRandom; use rand::thread_rng; /* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ fn random_numbers(n: i32) -> Vec { // Создать массив nums = { 1, 2, 3, ..., n } let mut nums = (1..=n).collect::>(); // Случайно перемешать элементы массива nums.shuffle(&mut thread_rng()); nums } /* Найти индекс числа 1 в массиве nums */ fn find_one(nums: &[i32]) -> Option { for i in 0..nums.len() { // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) if nums[i] == 1 { return Some(i); } } None } /* Driver Code */ fn main() { for _ in 0..10 { let n = 100; let nums = random_numbers(n); let index = find_one(&nums).unwrap(); print!("\nМассив [1, 2, ..., n] после перемешивания = "); print_util::print_array(&nums); println!("\nИндекс числа 1 = {}", index); } } ================================================ FILE: ru/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs ================================================ /* * File: binary_search_recur.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* Бинарный поиск: задача f(i, j) */ fn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 { // Если интервал пуст, целевой элемент отсутствует, вернуть -1 if i > j { return -1; } let m: i32 = i + (j - i) / 2; if nums[m as usize] < target { // Рекурсивная подзадача f(m+1, j) return dfs(nums, target, m + 1, j); } else if nums[m as usize] > target { // Рекурсивная подзадача f(i, m-1) return dfs(nums, target, i, m - 1); } else { // Целевой элемент найден, вернуть его индекс return m; } } /* Бинарный поиск */ fn binary_search(nums: &[i32], target: i32) -> i32 { let n = nums.len() as i32; // Решить задачу f(0, n-1) dfs(nums, target, 0, n - 1) } /* Driver Code */ pub fn main() { let target = 6; let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // Бинарный поиск (двусторонне замкнутый интервал) let index = binary_search(&nums, target); println!("Индекс целевого элемента 6 = {index}"); } ================================================ FILE: ru/codes/rust/chapter_divide_and_conquer/build_tree.rs ================================================ /* * File: build_tree.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, TreeNode}; use std::collections::HashMap; use std::{cell::RefCell, rc::Rc}; /* Построить двоичное дерево: разделяй и властвуй */ fn dfs( preorder: &[i32], inorder_map: &HashMap, i: i32, l: i32, r: i32, ) -> Option>> { // Завершить при пустом диапазоне поддерева if r - l < 0 { return None; } // Инициализировать корневой узел let root = TreeNode::new(preorder[i as usize]); // Найти m, чтобы разделить левое и правое поддеревья let m = inorder_map.get(&preorder[i as usize]).unwrap(); // Подзадача: построить левое поддерево root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1); // Подзадача: построить правое поддерево root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r); // Вернуть корневой узел Some(root) } /* Построить двоичное дерево */ fn build_tree(preorder: &[i32], inorder: &[i32]) -> Option>> { // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам let mut inorder_map: HashMap = HashMap::new(); for i in 0..inorder.len() { inorder_map.insert(inorder[i], i as i32); } let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1); root } /* Driver Code */ fn main() { let preorder = [3, 9, 2, 1, 7]; let inorder = [9, 3, 1, 2, 7]; println!("Предварительный обход = {:?}", preorder); println!("Симметричный обход = {:?}", inorder); let root = build_tree(&preorder, &inorder); println!("Построенное двоичное дерево:"); print_util::print_tree(root.as_ref().unwrap()); } ================================================ FILE: ru/codes/rust/chapter_divide_and_conquer/hanota.rs ================================================ /* * File: hanota.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ #![allow(non_snake_case)] /* Переместить один диск */ fn move_pan(src: &mut Vec, tar: &mut Vec) { // Снять диск с вершины src let pan = src.pop().unwrap(); // Положить диск на вершину tar tar.push(pan); } /* Решить задачу Ханойской башни f(i) */ fn dfs(i: i32, src: &mut Vec, buf: &mut Vec, tar: &mut Vec) { // Если в src остался только один диск, сразу переместить его в tar if i == 1 { move_pan(src, tar); return; } // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar dfs(i - 1, src, tar, buf); // Подзадача f(1): переместить оставшийся один диск из src в tar move_pan(src, tar); // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src dfs(i - 1, buf, src, tar); } /* Решить задачу Ханойской башни */ fn solve_hanota(A: &mut Vec, B: &mut Vec, C: &mut Vec) { let n = A.len() as i32; // Переместить верхние n дисков из A в C с помощью B dfs(n, A, B, C); } /* Driver Code */ pub fn main() { let mut A = vec![5, 4, 3, 2, 1]; let mut B = Vec::new(); let mut C = Vec::new(); println!("Исходное состояние:"); println!("A = {:?}", A); println!("B = {:?}", B); println!("C = {:?}", C); solve_hanota(&mut A, &mut B, &mut C); println!("После завершения перемещения дисков:"); println!("A = {:?}", A); println!("B = {:?}", B); println!("C = {:?}", C); } ================================================ FILE: ru/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs ================================================ /* * File: climbing_stairs_backtrack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Бэктрекинг */ fn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) { // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 if state == n { res[0] = res[0] + 1; } // Перебор всех вариантов выбора for &choice in choices { // Отсечение: нельзя выходить за n-ю ступень if state + choice > n { continue; } // Попытка: сделать выбор и обновить состояние backtrack(choices, state + choice, n, res); // Откат } } /* Подъем по лестнице: бэктрекинг */ fn climbing_stairs_backtrack(n: usize) -> i32 { let choices = vec![1, 2]; // Можно подняться на 1 или 2 ступени let state = 0; // Начать подъем с 0-й ступени let mut res = Vec::new(); res.push(0); // Использовать res[0] для хранения числа решений backtrack(&choices, state, n as i32, &mut res); res[0] } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_backtrack(n); println!("Количество способов подняться по лестнице из {n} ступеней = {res}"); } ================================================ FILE: ru/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs ================================================ /* * File: climbing_stairs_constraint_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Подъем по лестнице с ограничениями: динамическое программирование */ fn climbing_stairs_constraint_dp(n: usize) -> i32 { if n == 1 || n == 2 { return 1; }; // Инициализация таблицы dp для хранения решений подзадач let mut dp = vec![vec![-1; 3]; n + 1]; // Начальное состояние: заранее задать решения наименьших подзадач dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // Переход состояний: постепенное решение больших подзадач через меньшие for i in 3..=n { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } dp[n][1] + dp[n][2] } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_constraint_dp(n); println!("Количество способов подняться по лестнице из {n} ступеней = {res}"); } ================================================ FILE: ru/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs ================================================ /* * File: climbing_stairs_dfs.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Поиск */ fn dfs(i: usize) -> i32 { // dp[1] и dp[2] уже известны, вернуть их if i == 1 || i == 2 { return i as i32; } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i - 1) + dfs(i - 2); count } /* Подъем по лестнице: поиск */ fn climbing_stairs_dfs(n: usize) -> i32 { dfs(n) } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dfs(n); println!("Количество способов подняться по лестнице из {n} ступеней = {res}"); } ================================================ FILE: ru/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs ================================================ /* * File: climbing_stairs_dfs_mem.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Поиск с мемоизацией */ fn dfs(i: usize, mem: &mut [i32]) -> i32 { // dp[1] и dp[2] уже известны, вернуть их if i == 1 || i == 2 { return i as i32; } // Если запись dp[i] существует, сразу вернуть ее if mem[i] != -1 { return mem[i]; } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i - 1, mem) + dfs(i - 2, mem); // Сохранить dp[i] mem[i] = count; count } /* Подъем по лестнице: поиск с мемоизацией */ fn climbing_stairs_dfs_mem(n: usize) -> i32 { // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи let mut mem = vec![-1; n + 1]; dfs(n, &mut mem) } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dfs_mem(n); println!("Количество способов подняться по лестнице из {n} ступеней = {res}"); } ================================================ FILE: ru/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs ================================================ /* * File: climbing_stairs_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Подъем по лестнице: динамическое программирование */ fn climbing_stairs_dp(n: usize) -> i32 { // dp[1] и dp[2] уже известны, вернуть их if n == 1 || n == 2 { return n as i32; } // Инициализация таблицы dp для хранения решений подзадач let mut dp = vec![-1; n + 1]; // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = 1; dp[2] = 2; // Переход состояний: постепенное решение больших подзадач через меньшие for i in 3..=n { dp[i] = dp[i - 1] + dp[i - 2]; } dp[n] } /* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ fn climbing_stairs_dp_comp(n: usize) -> i32 { if n == 1 || n == 2 { return n as i32; } let (mut a, mut b) = (1, 2); for _ in 3..=n { let tmp = b; b = a + b; a = tmp; } b } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dp(n); println!("Количество способов подняться по лестнице из {n} ступеней = {res}"); let res = climbing_stairs_dp_comp(n); println!("Количество способов подняться по лестнице из {n} ступеней = {res}"); } ================================================ FILE: ru/codes/rust/chapter_dynamic_programming/coin_change.rs ================================================ /* * File: coin_change.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Размен монет: динамическое программирование */ fn coin_change_dp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); let max = amt + 1; // Инициализация таблицы dp let mut dp = vec![vec![0; amt + 1]; n + 1]; // Переход состояний: первая строка и первый столбец for a in 1..=amt { dp[0][a] = max; } // Переход состояний: остальные строки и столбцы for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1); } } } if dp[n][amt] != max { return dp[n][amt] as i32; } else { -1 } } /* Размен монет: динамическое программирование с оптимизацией памяти */ fn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); let max = amt + 1; // Инициализация таблицы dp let mut dp = vec![0; amt + 1]; dp.fill(max); dp[0] = 0; // Переход состояний for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1); } } } if dp[amt] != max { return dp[amt] as i32; } else { -1 } } /* Driver Code */ pub fn main() { let coins = [1, 2, 5]; let amt: usize = 4; // Динамическое программирование let res = coin_change_dp(&coins, amt); println!("Минимальное число монет для набора целевой суммы = {res}"); // Динамическое программирование с оптимизацией памяти let res = coin_change_dp_comp(&coins, amt); println!("Минимальное число монет для набора целевой суммы = {res}"); } ================================================ FILE: ru/codes/rust/chapter_dynamic_programming/coin_change_ii.rs ================================================ /* * File: coin_change_ii.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Размен монет II: динамическое программирование */ fn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); // Инициализация таблицы dp let mut dp = vec![vec![0; amt + 1]; n + 1]; // Инициализация первого столбца for i in 0..=n { dp[i][0] = 1; } // Переход состояний for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a]; } else { // Сумма двух решений: не брать или взять монету i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize]; } } } dp[n][amt] } /* Размен монет II: динамическое программирование с оптимизацией памяти */ fn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); // Инициализация таблицы dp let mut dp = vec![0; amt + 1]; dp[0] = 1; // Переход состояний for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Сумма двух решений: не брать или взять монету i dp[a] = dp[a] + dp[a - coins[i - 1] as usize]; } } } dp[amt] } /* Driver Code */ pub fn main() { let coins = [1, 2, 5]; let amt: usize = 5; // Динамическое программирование let res = coin_change_ii_dp(&coins, amt); println!("Количество комбинаций монет для набора целевой суммы = {res}"); // Динамическое программирование с оптимизацией памяти let res = coin_change_ii_dp_comp(&coins, amt); println!("Количество комбинаций монет для набора целевой суммы = {res}"); } ================================================ FILE: ru/codes/rust/chapter_dynamic_programming/edit_distance.rs ================================================ /* * File: edit_distance.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Редакционное расстояние: полный перебор */ fn edit_distance_dfs(s: &str, t: &str, i: usize, j: usize) -> i32 { // Если s и t пусты, вернуть 0 if i == 0 && j == 0 { return 0; } // Если s пусто, вернуть длину t if i == 0 { return j as i32; } // Если t пусто, вернуть длину s if j == 0 { return i as i32; } // Если два символа равны, сразу пропустить их if s.chars().nth(i - 1) == t.chars().nth(j - 1) { return edit_distance_dfs(s, t, i - 1, j - 1); } // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 let insert = edit_distance_dfs(s, t, i, j - 1); let delete = edit_distance_dfs(s, t, i - 1, j); let replace = edit_distance_dfs(s, t, i - 1, j - 1); // Вернуть минимальное число шагов редактирования std::cmp::min(std::cmp::min(insert, delete), replace) + 1 } /* Редакционное расстояние: поиск с мемоизацией */ fn edit_distance_dfs_mem(s: &str, t: &str, mem: &mut Vec>, i: usize, j: usize) -> i32 { // Если s и t пусты, вернуть 0 if i == 0 && j == 0 { return 0; } // Если s пусто, вернуть длину t if i == 0 { return j as i32; } // Если t пусто, вернуть длину s if j == 0 { return i as i32; } // Если запись уже есть, сразу вернуть ее if mem[i][j] != -1 { return mem[i][j]; } // Если два символа равны, сразу пропустить их if s.chars().nth(i - 1) == t.chars().nth(j - 1) { return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); } // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 let insert = edit_distance_dfs_mem(s, t, mem, i, j - 1); let delete = edit_distance_dfs_mem(s, t, mem, i - 1, j); let replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); // Сохранить и вернуть минимальное число шагов редактирования mem[i][j] = std::cmp::min(std::cmp::min(insert, delete), replace) + 1; mem[i][j] } /* Редакционное расстояние: динамическое программирование */ fn edit_distance_dp(s: &str, t: &str) -> i32 { let (n, m) = (s.len(), t.len()); let mut dp = vec![vec![0; m + 1]; n + 1]; // Переход состояний: первая строка и первый столбец for i in 1..=n { dp[i][0] = i as i32; } for j in 1..m { dp[0][j] = j as i32; } // Переход состояний: остальные строки и столбцы for i in 1..=n { for j in 1..=m { if s.chars().nth(i - 1) == t.chars().nth(j - 1) { // Если два символа равны, сразу пропустить их dp[i][j] = dp[i - 1][j - 1]; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[i][j] = std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } dp[n][m] } /* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ fn edit_distance_dp_comp(s: &str, t: &str) -> i32 { let (n, m) = (s.len(), t.len()); let mut dp = vec![0; m + 1]; // Переход состояний: первая строка for j in 1..m { dp[j] = j as i32; } // Переход состояний: остальные строки for i in 1..=n { // Переход состояний: первый столбец let mut leftup = dp[0]; // Временно сохранить dp[i-1, j-1] dp[0] = i as i32; // Переход состояний: остальные столбцы for j in 1..=m { let temp = dp[j]; if s.chars().nth(i - 1) == t.chars().nth(j - 1) { // Если два символа равны, сразу пропустить их dp[j] = leftup; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации } } dp[m] } /* Driver Code */ pub fn main() { let s = "bag"; let t = "pack"; let (n, m) = (s.len(), t.len()); // Полный перебор let res = edit_distance_dfs(s, t, n, m); println!("Чтобы преобразовать {s} в {t}, нужно минимум {res} шагов"); // Поиск с запоминанием let mut mem = vec![vec![0; m + 1]; n + 1]; for row in mem.iter_mut() { row.fill(-1); } let res = edit_distance_dfs_mem(s, t, &mut mem, n, m); println!("Чтобы преобразовать {s} в {t}, нужно минимум {res} шагов"); // Динамическое программирование let res = edit_distance_dp(s, t); println!("Чтобы преобразовать {s} в {t}, нужно минимум {res} шагов"); // Динамическое программирование с оптимизацией памяти let res = edit_distance_dp_comp(s, t); println!("Чтобы преобразовать {s} в {t}, нужно минимум {res} шагов"); } ================================================ FILE: ru/codes/rust/chapter_dynamic_programming/knapsack.rs ================================================ /* * File: knapsack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Рюкзак 0-1: полный перебор */ fn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if i == 0 || c == 0 { return 0; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if wgt[i - 1] > c as i32 { return knapsack_dfs(wgt, val, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут let no = knapsack_dfs(wgt, val, i - 1, c); let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; // Вернуть вариант с большей стоимостью из двух возможных std::cmp::max(no, yes) } /* Рюкзак 0-1: поиск с мемоизацией */ fn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec>, i: usize, c: usize) -> i32 { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if i == 0 || c == 0 { return 0; } // Если запись уже есть, вернуть сразу if mem[i][c] != -1 { return mem[i][c]; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if wgt[i - 1] > c as i32 { return knapsack_dfs_mem(wgt, val, mem, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c); let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; // Сохранить и вернуть вариант с большей стоимостью из двух решений mem[i][c] = std::cmp::max(no, yes); mem[i][c] } /* Рюкзак 0-1: динамическое программирование */ fn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // Инициализация таблицы dp let mut dp = vec![vec![0; cap + 1]; n + 1]; // Переход состояний for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = std::cmp::max( dp[i - 1][c], dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1], ); } } } dp[n][cap] } /* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ fn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // Инициализация таблицы dp let mut dp = vec![0; cap + 1]; // Переход состояний for i in 1..=n { // Обход в обратном порядке for c in (1..=cap).rev() { if wgt[i - 1] <= c as i32 { // Большее из двух решений: не брать или взять предмет i dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); } } } dp[cap] } /* Driver Code */ pub fn main() { let wgt = [10, 20, 30, 40, 50]; let val = [50, 120, 150, 210, 240]; let cap: usize = 50; let n = wgt.len(); // Полный перебор let res = knapsack_dfs(&wgt, &val, n, cap); println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {res}"); // Поиск с запоминанием let mut mem = vec![vec![0; cap + 1]; n + 1]; for row in mem.iter_mut() { row.fill(-1); } let res = knapsack_dfs_mem(&wgt, &val, &mut mem, n, cap); println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {res}"); // Динамическое программирование let res = knapsack_dp(&wgt, &val, cap); println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {res}"); // Динамическое программирование с оптимизацией памяти let res = knapsack_dp_comp(&wgt, &val, cap); println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {res}"); } ================================================ FILE: ru/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs ================================================ /* * File: min_cost_climbing_stairs_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use std::cmp; /* Минимальная стоимость подъема по лестнице: динамическое программирование */ fn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 { let n = cost.len() - 1; if n == 1 || n == 2 { return cost[n]; } // Инициализация таблицы dp для хранения решений подзадач let mut dp = vec![-1; n + 1]; // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = cost[1]; dp[2] = cost[2]; // Переход состояний: постепенное решение больших подзадач через меньшие for i in 3..=n { dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i]; } dp[n] } /* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ fn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 { let n = cost.len() - 1; if n == 1 || n == 2 { return cost[n]; }; let (mut a, mut b) = (cost[1], cost[2]); for i in 3..=n { let tmp = b; b = cmp::min(a, tmp) + cost[i]; a = tmp; } b } /* Driver Code */ pub fn main() { let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; println!("Список стоимостей ступеней = {:?}", &cost); let res = min_cost_climbing_stairs_dp(&cost); println!("Минимальная стоимость подъема по лестнице = {res}"); let res = min_cost_climbing_stairs_dp_comp(&cost); println!("Минимальная стоимость подъема по лестнице = {res}"); } ================================================ FILE: ru/codes/rust/chapter_dynamic_programming/min_path_sum.rs ================================================ /* * File: min_path_sum.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Минимальная сумма пути: полный перебор */ fn min_path_sum_dfs(grid: &Vec>, i: i32, j: i32) -> i32 { // Если это верхняя левая ячейка, завершить поиск if i == 0 && j == 0 { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if i < 0 || j < 0 { return i32::MAX; } // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) let up = min_path_sum_dfs(grid, i - 1, j); let left = min_path_sum_dfs(grid, i, j - 1); // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) std::cmp::min(left, up) + grid[i as usize][j as usize] } /* Минимальная сумма пути: поиск с мемоизацией */ fn min_path_sum_dfs_mem(grid: &Vec>, mem: &mut Vec>, i: i32, j: i32) -> i32 { // Если это верхняя левая ячейка, завершить поиск if i == 0 && j == 0 { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if i < 0 || j < 0 { return i32::MAX; } // Если запись уже есть, вернуть сразу if mem[i as usize][j as usize] != -1 { return mem[i as usize][j as usize]; } // Минимальная стоимость пути для левой и верхней ячеек let up = min_path_sum_dfs_mem(grid, mem, i - 1, j); let left = min_path_sum_dfs_mem(grid, mem, i, j - 1); // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize]; mem[i as usize][j as usize] } /* Минимальная сумма пути: динамическое программирование */ fn min_path_sum_dp(grid: &Vec>) -> i32 { let (n, m) = (grid.len(), grid[0].len()); // Инициализация таблицы dp let mut dp = vec![vec![0; m]; n]; dp[0][0] = grid[0][0]; // Переход состояний: первая строка for j in 1..m { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // Переход состояний: первый столбец for i in 1..n { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // Переход состояний: остальные строки и столбцы for i in 1..n { for j in 1..m { dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } dp[n - 1][m - 1] } /* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ fn min_path_sum_dp_comp(grid: &Vec>) -> i32 { let (n, m) = (grid.len(), grid[0].len()); // Инициализация таблицы dp let mut dp = vec![0; m]; // Переход состояний: первая строка dp[0] = grid[0][0]; for j in 1..m { dp[j] = dp[j - 1] + grid[0][j]; } // Переход состояний: остальные строки for i in 1..n { // Переход состояний: первый столбец dp[0] = dp[0] + grid[i][0]; // Переход состояний: остальные столбцы for j in 1..m { dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j]; } } dp[m - 1] } /* Driver Code */ pub fn main() { let grid = vec![ vec![1, 3, 1, 5], vec![2, 2, 4, 2], vec![5, 3, 2, 1], vec![4, 3, 5, 2], ]; let (n, m) = (grid.len(), grid[0].len()); // Полный перебор let res = min_path_sum_dfs(&grid, n as i32 - 1, m as i32 - 1); println!("Минимальная сумма пути из левого верхнего угла в правый нижний = {res}"); // Поиск с мемоизацией let mut mem = vec![vec![0; m]; n]; for row in mem.iter_mut() { row.fill(-1); } let res = min_path_sum_dfs_mem(&grid, &mut mem, n as i32 - 1, m as i32 - 1); println!("Минимальная сумма пути из левого верхнего угла в правый нижний = {res}"); // Динамическое программирование let res = min_path_sum_dp(&grid); println!("Минимальная сумма пути из левого верхнего угла в правый нижний = {res}"); // Динамическое программирование с оптимизацией памяти let res = min_path_sum_dp_comp(&grid); println!("Минимальная сумма пути из левого верхнего угла в правый нижний = {res}"); } ================================================ FILE: ru/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs ================================================ /* * File: unbounded_knapsack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* Полный рюкзак: динамическое программирование */ fn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // Инициализация таблицы dp let mut dp = vec![vec![0; cap + 1]; n + 1]; // Переход состояний for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]); } } } return dp[n][cap]; } /* Полный рюкзак: динамическое программирование с оптимизацией памяти */ fn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // Инициализация таблицы dp let mut dp = vec![0; cap + 1]; // Переход состояний for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // Если вместимость рюкзака превышена, предмет i не выбирать dp[c] = dp[c]; } else { // Большее из двух решений: не брать или взять предмет i dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); } } } dp[cap] } /* Driver Code */ pub fn main() { let wgt = [1, 2, 3]; let val = [5, 11, 15]; let cap: usize = 4; // Динамическое программирование let res = unbounded_knapsack_dp(&wgt, &val, cap); println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {res}"); // Динамическое программирование с оптимизацией памяти let res = unbounded_knapsack_dp_comp(&wgt, &val, cap); println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {res}"); } ================================================ FILE: ru/codes/rust/chapter_graph/graph_adjacency_list.rs ================================================ /* * File: graph_adjacency_list.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ pub use hello_algo_rust::include::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::HashMap; /* Тип неориентированного графа на основе списка смежности */ pub struct GraphAdjList { // Список смежности, где key — вершина, а value — все смежные ей вершины pub adj_list: HashMap>, // maybe HashSet for value part is better? } impl GraphAdjList { /* Конструктор */ pub fn new(edges: Vec<[Vertex; 2]>) -> Self { let mut graph = GraphAdjList { adj_list: HashMap::new(), }; // Добавить все вершины и ребра for edge in edges { graph.add_vertex(edge[0]); graph.add_vertex(edge[1]); graph.add_edge(edge[0], edge[1]); } graph } /* Получить число вершин */ #[allow(unused)] pub fn size(&self) -> usize { self.adj_list.len() } /* Добавление ребра */ pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) { if vet1 == vet2 { panic!("value error"); } // Добавить ребро vet1 - vet2 self.adj_list.entry(vet1).or_default().push(vet2); self.adj_list.entry(vet2).or_default().push(vet1); } /* Удаление ребра */ #[allow(unused)] pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) { if vet1 == vet2 { panic!("value error"); } // Удалить ребро vet1 - vet2 self.adj_list .entry(vet1) .and_modify(|v| v.retain(|&e| e != vet2)); self.adj_list .entry(vet2) .and_modify(|v| v.retain(|&e| e != vet1)); } /* Добавление вершины */ pub fn add_vertex(&mut self, vet: Vertex) { if self.adj_list.contains_key(&vet) { return; } // Добавить новый список в список смежности self.adj_list.insert(vet, vec![]); } /* Удаление вершины */ #[allow(unused)] pub fn remove_vertex(&mut self, vet: Vertex) { // Удалить из списка смежности список, соответствующий вершине vet self.adj_list.remove(&vet); // Обойти списки других вершин и удалить все ребра, содержащие vet for list in self.adj_list.values_mut() { list.retain(|&v| v != vet); } } /* Вывести список смежности */ pub fn print(&self) { println!("Список смежности ="); for (vertex, list) in &self.adj_list { let list = list.iter().map(|vertex| vertex.val).collect::>(); println!("{}: {:?},", vertex.val, list); } } } /* Driver Code */ #[allow(unused)] fn main() { /* Инициализация неориентированного графа */ let v = vals_to_vets(vec![1, 3, 2, 5, 4]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ]; let mut graph = GraphAdjList::new(edges); println!("\nГраф после инициализации"); graph.print(); /* Добавление ребра */ // Вершины 1 и 2 соответствуют v[0] и v[2] graph.add_edge(v[0], v[2]); println!("\nГраф после добавления ребра 1-2"); graph.print(); /* Удаление ребра */ // Вершины 1 и 3 соответствуют v[0] и v[1] graph.remove_edge(v[0], v[1]); println!("\nГраф после удаления ребра 1-3"); graph.print(); /* Добавление вершины */ let v5 = Vertex { val: 6 }; graph.add_vertex(v5); println!("\nГраф после добавления вершины 6"); graph.print(); /* Удаление вершины */ // Вершина 3 соответствует v[1] graph.remove_vertex(v[1]); println!("\nГраф после удаления вершины 3"); graph.print(); } ================================================ FILE: ru/codes/rust/chapter_graph/graph_adjacency_matrix.rs ================================================ /* * File: graph_adjacency_matrix.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ /* Тип неориентированного графа на основе матрицы смежности */ pub struct GraphAdjMat { // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» pub vertices: Vec, // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» pub adj_mat: Vec>, } impl GraphAdjMat { /* Конструктор */ pub fn new(vertices: Vec, edges: Vec<[usize; 2]>) -> Self { let mut graph = GraphAdjMat { vertices: vec![], adj_mat: vec![], }; // Добавление вершины for val in vertices { graph.add_vertex(val); } // Добавить ребра // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices for edge in edges { graph.add_edge(edge[0], edge[1]) } graph } /* Получить число вершин */ pub fn size(&self) -> usize { self.vertices.len() } /* Добавление вершины */ pub fn add_vertex(&mut self, val: i32) { let n = self.size(); // Добавить значение новой вершины в список вершин self.vertices.push(val); // Добавить строку в матрицу смежности self.adj_mat.push(vec![0; n]); // Добавить столбец в матрицу смежности for row in self.adj_mat.iter_mut() { row.push(0); } } /* Удаление вершины */ pub fn remove_vertex(&mut self, index: usize) { if index >= self.size() { panic!("index error") } // Удалить вершину с индексом index из списка вершин self.vertices.remove(index); // Удалить строку с индексом index из матрицы смежности self.adj_mat.remove(index); // Удалить столбец с индексом index из матрицы смежности for row in self.adj_mat.iter_mut() { row.remove(index); } } /* Добавление ребра */ pub fn add_edge(&mut self, i: usize, j: usize) { // Параметры i и j соответствуют индексам элементов vertices // Обработка выхода индекса за границы и случая равенства if i >= self.size() || j >= self.size() || i == j { panic!("index error") } // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) self.adj_mat[i][j] = 1; self.adj_mat[j][i] = 1; } /* Удаление ребра */ // Параметры i и j соответствуют индексам элементов vertices pub fn remove_edge(&mut self, i: usize, j: usize) { // Параметры i и j соответствуют индексам элементов vertices // Обработка выхода индекса за границы и случая равенства if i >= self.size() || j >= self.size() || i == j { panic!("index error") } self.adj_mat[i][j] = 0; self.adj_mat[j][i] = 0; } /* Вывести матрицу смежности */ pub fn print(&self) { println!("Список вершин = {:?}", self.vertices); println!("Матрица смежности ="); println!("["); for row in &self.adj_mat { println!(" {:?},", row); } println!("]") } } /* Driver Code */ fn main() { /* Инициализация неориентированного графа */ // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices let vertices = vec![1, 3, 2, 5, 4]; let edges = vec![[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]]; let mut graph = GraphAdjMat::new(vertices, edges); println!("\nГраф после инициализации"); graph.print(); /* Добавление ребра */ // Индексы вершин 1 и 2 равны 0 и 2 соответственно graph.add_edge(0, 2); println!("\nГраф после добавления ребра 1-2"); graph.print(); /* Удаление ребра */ // Индексы вершин 1 и 3 равны 0 и 1 соответственно graph.remove_edge(0, 1); println!("\nГраф после удаления ребра 1-3"); graph.print(); /* Добавление вершины */ graph.add_vertex(6); println!("\nГраф после добавления вершины 6"); graph.print(); /* Удаление вершины */ // Индекс вершины 3 равен 1 graph.remove_vertex(1); println!("\nГраф после удаления вершины 3"); graph.print(); } ================================================ FILE: ru/codes/rust/chapter_graph/graph_bfs.rs ================================================ /* * File: graph_bfs.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ mod graph_adjacency_list; use graph_adjacency_list::GraphAdjList; use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::{HashSet, VecDeque}; /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { // Последовательность обхода вершин let mut res = vec![]; // Хеш-множество для хранения уже посещенных вершин let mut visited = HashSet::new(); visited.insert(start_vet); // Очередь используется для реализации BFS let mut que = VecDeque::new(); que.push_back(start_vet); // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while let Some(vet) = que.pop_front() { res.push(vet); // Отметить посещенную вершину // Обойти все смежные вершины данной вершины if let Some(adj_vets) = graph.adj_list.get(&vet) { for &adj_vet in adj_vets { if visited.contains(&adj_vet) { continue; // Пропустить уже посещенную вершину } que.push_back(adj_vet); // Помещать в очередь только непосещенные вершины visited.insert(adj_vet); // Отметить эту вершину как посещенную } } } // Вернуть последовательность обхода вершин res } /* Driver Code */ fn main() { /* Инициализация неориентированного графа */ let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; let graph = GraphAdjList::new(edges); println!("\nГраф после инициализации"); graph.print(); /* Обход в ширину */ let res = graph_bfs(graph, v[0]); println!("\nПоследовательность вершин при обходе в ширину (BFS)"); println!("{:?}", vets_to_vals(res)); } ================================================ FILE: ru/codes/rust/chapter_graph/graph_dfs.rs ================================================ /* * File: graph_dfs.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ mod graph_adjacency_list; use graph_adjacency_list::GraphAdjList; use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::HashSet; /* Вспомогательная функция обхода в глубину */ fn dfs(graph: &GraphAdjList, visited: &mut HashSet, res: &mut Vec, vet: Vertex) { res.push(vet); // Отметить посещенную вершину visited.insert(vet); // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины if let Some(adj_vets) = graph.adj_list.get(&vet) { for &adj_vet in adj_vets { if visited.contains(&adj_vet) { continue; // Пропустить уже посещенную вершину } // Рекурсивно обходить смежные вершины dfs(graph, visited, res, adj_vet); } } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { // Последовательность обхода вершин let mut res = vec![]; // Хеш-множество для хранения уже посещенных вершин let mut visited = HashSet::new(); dfs(&graph, &mut visited, &mut res, start_vet); res } /* Driver Code */ fn main() { /* Инициализация неориентированного графа */ let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; let graph = GraphAdjList::new(edges); println!("\nГраф после инициализации"); graph.print(); /* Обход в глубину */ let res = graph_dfs(graph, v[0]); println!("\nПоследовательность вершин при обходе в глубину (DFS)"); println!("{:?}", vets_to_vals(res)); } ================================================ FILE: ru/codes/rust/chapter_greedy/coin_change_greedy.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* Размен монет: жадный алгоритм */ fn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 { // Предположить, что список coins упорядочен let mut i = coins.len() - 1; let mut count = 0; // Циклически выполнять жадный выбор, пока не останется суммы while amt > 0 { // Найти монету, которая меньше остатка суммы и наиболее к нему близка while i > 0 && coins[i] > amt { i -= 1; } // Выбрать coins[i] amt -= coins[i]; count += 1; } // Если допустимое решение не найдено, вернуть -1 if amt == 0 { count } else { -1 } } /* Driver Code */ fn main() { // Жадный подход: гарантирует нахождение глобально оптимального решения let coins = [1, 5, 10, 20, 50, 100]; let amt = 186; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("Минимальное число монет для набора суммы {} = {}", amt, res); // Жадный подход: не гарантирует нахождение глобально оптимального решения let coins = [1, 20, 50]; let amt = 60; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("Минимальное число монет для набора суммы {} = {}", amt, res); println!("На самом деле минимум равен 3: 20 + 20 + 20"); // Жадный подход: не гарантирует нахождение глобально оптимального решения let coins = [1, 49, 50]; let amt = 98; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("Минимальное число монет для набора суммы {} = {}", amt, res); println!("На самом деле минимум равен 2: 49 + 49"); } ================================================ FILE: ru/codes/rust/chapter_greedy/fractional_knapsack.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* Предмет */ struct Item { w: i32, // Вес предмета v: i32, // Стоимость предмета } impl Item { fn new(w: i32, v: i32) -> Self { Self { w, v } } } /* Дробный рюкзак: жадный алгоритм */ fn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 { // Создать список предметов с двумя свойствами: вес и стоимость let mut items = wgt .iter() .zip(val.iter()) .map(|(&w, &v)| Item::new(w, v)) .collect::>(); // Отсортировать по удельной стоимости item.v / item.w в порядке убывания items.sort_by(|a, b| { (b.v as f64 / b.w as f64) .partial_cmp(&(a.v as f64 / a.w as f64)) .unwrap() }); // Циклический жадный выбор let mut res = 0.0; for item in &items { if item.w <= cap { // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком res += item.v as f64; cap -= item.w; } else { // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета res += item.v as f64 / item.w as f64 * cap as f64; // Свободной вместимости больше не осталось, поэтому выйти из цикла break; } } res } /* Driver Code */ fn main() { let wgt = [10, 20, 30, 40, 50]; let val = [50, 120, 150, 210, 240]; let cap = 50; // Жадный алгоритм let res = fractional_knapsack(&wgt, &val, cap); println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {}", res); } ================================================ FILE: ru/codes/rust/chapter_greedy/max_capacity.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* Максимальная вместимость: жадный алгоритм */ fn max_capacity(ht: &[i32]) -> i32 { // Инициализировать i и j так, чтобы они располагались по двум концам массива let mut i = 0; let mut j = ht.len() - 1; // Начальная максимальная вместимость равна 0 let mut res = 0; // Выполнять жадный выбор в цикле, пока две доски не встретятся while i < j { // Обновить максимальную вместимость let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32; res = std::cmp::max(res, cap); // Сдвигать внутрь более короткую сторону if ht[i] < ht[j] { i += 1; } else { j -= 1; } } res } /* Driver Code */ fn main() { let ht = [3, 8, 5, 2, 7, 7, 3, 4]; // Жадный алгоритм let res = max_capacity(&ht); println!("Максимальная вместимость = {}", res); } ================================================ FILE: ru/codes/rust/chapter_greedy/max_product_cutting.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* Максимальное произведение разрезания: жадный алгоритм */ fn max_product_cutting(n: i32) -> i32 { // Когда n <= 3, обязательно нужно выделить одну 1 if n <= 3 { return 1 * (n - 1); } // Жадно выделить множители 3, где a — число троек, а b — остаток let a = n / 3; let b = n % 3; if b == 1 { // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 3_i32.pow(a as u32 - 1) * 2 * 2 } else if b == 2 { // Если остаток равен 2, ничего не делать 3_i32.pow(a as u32) * 2 } else { // Если остаток равен 0, ничего не делать 3_i32.pow(a as u32) } } /* Driver Code */ fn main() { let n = 58; // Жадный алгоритм let res = max_product_cutting(n); println!("Максимальное произведение после разрезания = {}", res); } ================================================ FILE: ru/codes/rust/chapter_hashing/array_hash_map.rs ================================================ /** * File: array_hash_map.rs * Created Time: 2023-2-18 * Author: xBLACICEx (xBLACKICEx@outlook.com) */ /* Пара ключ-значение */ #[derive(Debug, Clone, PartialEq)] pub struct Pair { pub key: i32, pub val: String, } /* Хеш-таблица на основе массива */ pub struct ArrayHashMap { buckets: Vec>, } impl ArrayHashMap { pub fn new() -> ArrayHashMap { // Инициализировать массив, содержащий 100 корзин Self { buckets: vec![None; 100], } } /* Хеш-функция */ fn hash_func(&self, key: i32) -> usize { key as usize % 100 } /* Операция поиска */ pub fn get(&self, key: i32) -> Option<&String> { let index = self.hash_func(key); self.buckets[index].as_ref().map(|pair| &pair.val) } /* Операция добавления */ pub fn put(&mut self, key: i32, val: &str) { let index = self.hash_func(key); self.buckets[index] = Some(Pair { key, val: val.to_string(), }); } /* Операция удаления */ pub fn remove(&mut self, key: i32) { let index = self.hash_func(key); // Присвоить None, что означает удаление self.buckets[index] = None; } /* Получить все пары ключ-значение */ pub fn entry_set(&self) -> Vec<&Pair> { self.buckets .iter() .filter_map(|pair| pair.as_ref()) .collect() } /* Получить все ключи */ pub fn key_set(&self) -> Vec<&i32> { self.buckets .iter() .filter_map(|pair| pair.as_ref().map(|pair| &pair.key)) .collect() } /* Получить все значения */ pub fn value_set(&self) -> Vec<&String> { self.buckets .iter() .filter_map(|pair| pair.as_ref().map(|pair| &pair.val)) .collect() } /* Вывести хеш-таблицу */ pub fn print(&self) { for pair in self.entry_set() { println!("{} -> {}", pair.key, pair.val); } } } fn main() { /* Инициализация хеш-таблицы */ let mut map = ArrayHashMap::new(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(12836, "Сяо Ха"); map.put(15937, "Сяо Ло"); map.put(16750, "Сяо Суань"); map.put(13276, "Сяо Фа"); map.put(10583, "Сяо Я"); println!("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); map.print(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value let name = map.get(15937).unwrap(); println!("\nДля номера 15937 найдено имя {}", name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(10583); println!("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); map.print(); /* Обход хеш-таблицы */ println!("\nОтдельный обход пар ключ-значение"); for pair in map.entry_set() { println!("{} -> {}", pair.key, pair.val); } println!("\nОтдельный обход ключей"); for key in map.key_set() { println!("{}", key); } println!("\nОтдельный обход значений"); for val in map.value_set() { println!("{}", val); } } ================================================ FILE: ru/codes/rust/chapter_hashing/build_in_hash.rs ================================================ /* * File: build_in_hash.rs * Created Time: 2023-7-6 * Author: WSL0809 (wslzzy@outlook.com) */ use hello_algo_rust::include::ListNode; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; /* Driver Code */ fn main() { let num = 3; let mut num_hasher = DefaultHasher::new(); num.hash(&mut num_hasher); let hash_num = num_hasher.finish(); println!("Хеш-значение целого числа {} = {}", num, hash_num); let bol = true; let mut bol_hasher = DefaultHasher::new(); bol.hash(&mut bol_hasher); let hash_bol = bol_hasher.finish(); println!("Хеш-значение булева значения {} = {}", bol, hash_bol); let dec: f32 = 3.14159; let mut dec_hasher = DefaultHasher::new(); dec.to_bits().hash(&mut dec_hasher); let hash_dec = dec_hasher.finish(); println!("Хеш-значение десятичного числа {} = {}", dec, hash_dec); let str = "Hello Algo"; let mut str_hasher = DefaultHasher::new(); str.hash(&mut str_hasher); let hash_str = str_hasher.finish(); println!("Хеш-значение строки {} = {}", str, hash_str); let arr = (&12836, &"Сяо Ха"); let mut tup_hasher = DefaultHasher::new(); arr.hash(&mut tup_hasher); let hash_tup = tup_hasher.finish(); println!("Хеш-значение кортежа {:?} = {}", arr, hash_tup); let node = ListNode::new(42); let mut hasher = DefaultHasher::new(); node.borrow().val.hash(&mut hasher); let hash = hasher.finish(); println!("Хеш-значение объекта узла {:?} = {}", node, hash); } ================================================ FILE: ru/codes/rust/chapter_hashing/hash_map.rs ================================================ /* * File: hash_map.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::collections::HashMap; /* Driver Code */ pub fn main() { // Инициализация хеш-таблицы let mut map = HashMap::new(); // Операция добавления // Добавить пару (key, value) в хеш-таблицу map.insert(12836, "Сяо Ха"); map.insert(15937, "Сяо Ло"); map.insert(16750, "Сяо Суань"); map.insert(13276, "Сяо Фа"); map.insert(10583, "Сяо Я"); println!("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); print_util::print_hash_map(&map); // Операция поиска // Передать ключ key в хеш-таблицу и получить значение value let name = map.get(&15937).copied().unwrap(); println!("\nДля номера 15937 найдено имя {name}"); // Операция удаления // Удалить пару (key, value) из хеш-таблицы _ = map.remove(&10583); println!("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); print_util::print_hash_map(&map); // Обход хеш-таблицы println!("\nОтдельный обход пар ключ-значение"); print_util::print_hash_map(&map); println!("\nОтдельный обход ключей"); for key in map.keys() { println!("{key}"); } println!("\nОтдельный обход значений"); for value in map.values() { println!("{value}"); } } ================================================ FILE: ru/codes/rust/chapter_hashing/hash_map_chaining.rs ================================================ /* * File: hash_map_chaining.rs * Created Time: 2023-07-07 * Author: WSL0809 (wslzzy@outlook.com) */ #[derive(Clone)] /* Пара ключ-значение */ struct Pair { key: i32, val: String, } /* Хеш-таблица с цепочками */ struct HashMapChaining { size: usize, capacity: usize, load_thres: f32, extend_ratio: usize, buckets: Vec>, } impl HashMapChaining { /* Конструктор */ fn new() -> Self { Self { size: 0, capacity: 4, load_thres: 2.0 / 3.0, extend_ratio: 2, buckets: vec![vec![]; 4], } } /* Хеш-функция */ fn hash_func(&self, key: i32) -> usize { key as usize % self.capacity } /* Коэффициент загрузки */ fn load_factor(&self) -> f32 { self.size as f32 / self.capacity as f32 } /* Операция удаления */ fn remove(&mut self, key: i32) -> Option { let index = self.hash_func(key); // Обойти корзину и удалить из нее пару ключ-значение for (i, p) in self.buckets[index].iter_mut().enumerate() { if p.key == key { let pair = self.buckets[index].remove(i); self.size -= 1; return Some(pair.val); } } // Если key не найден, вернуть None None } /* Расширить хеш-таблицу */ fn extend(&mut self) { // Временно сохранить исходную хеш-таблицу let buckets_tmp = std::mem::take(&mut self.buckets); // Инициализация новой хеш-таблицы после расширения self.capacity *= self.extend_ratio; self.buckets = vec![Vec::new(); self.capacity as usize]; self.size = 0; // Перенести пары ключ-значение из исходной хеш-таблицы в новую for bucket in buckets_tmp { for pair in bucket { self.put(pair.key, pair.val); } } } /* Вывести хеш-таблицу */ fn print(&self) { for bucket in &self.buckets { let mut res = Vec::new(); for pair in bucket { res.push(format!("{} -> {}", pair.key, pair.val)); } println!("{:?}", res); } } /* Операция добавления */ fn put(&mut self, key: i32, val: String) { // Когда коэффициент загрузки превышает порог, выполнить расширение if self.load_factor() > self.load_thres { self.extend(); } let index = self.hash_func(key); // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть for pair in self.buckets[index].iter_mut() { if pair.key == key { pair.val = val; return; } } // Если такого key нет, добавить пару ключ-значение в конец let pair = Pair { key, val }; self.buckets[index].push(pair); self.size += 1; } /* Операция поиска */ fn get(&self, key: i32) -> Option<&str> { let index = self.hash_func(key); // Обойти корзину; если найден key, вернуть соответствующее val for pair in self.buckets[index].iter() { if pair.key == key { return Some(&pair.val); } } // Если key не найден, вернуть None None } } /* Driver Code */ pub fn main() { /* Инициализация хеш-таблицы */ let mut map = HashMapChaining::new(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(12836, "Сяо Ха".to_string()); map.put(15937, "Сяо Ло".to_string()); map.put(16750, "Сяо Суань".to_string()); map.put(13276, "Сяо Фа".to_string()); map.put(10583, "Сяо Я".to_string()); println!("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); map.print(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value println!( "\nПо номеру 13276 найдено имя {}",\nmatch map.get(13276) {\n Some(value) => value,\n None => "Not a valid Key",\n} ); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(12836); println!("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение"); map.print(); } ================================================ FILE: ru/codes/rust/chapter_hashing/hash_map_open_addressing.rs ================================================ /* * File: hash_map_open_addressing.rs * Created Time: 2023-07-16 * Author: WSL0809 (wslzzy@outlook.com), night-cruise (2586447362@qq.com) */ #![allow(non_snake_case)] #![allow(unused)] mod array_hash_map; use array_hash_map::Pair; /* Хеш-таблица с открытой адресацией */ struct HashMapOpenAddressing { size: usize, // Число пар ключ-значение capacity: usize, // Вместимость хеш-таблицы load_thres: f64, // Порог коэффициента загрузки для запуска расширения extend_ratio: usize, // Коэффициент расширения buckets: Vec>, // Массив корзин TOMBSTONE: Option, // Удалить метку } impl HashMapOpenAddressing { /* Конструктор */ fn new() -> Self { Self { size: 0, capacity: 4, load_thres: 2.0 / 3.0, extend_ratio: 2, buckets: vec![None; 4], TOMBSTONE: Some(Pair { key: -1, val: "-1".to_string(), }), } } /* Хеш-функция */ fn hash_func(&self, key: i32) -> usize { (key % self.capacity as i32) as usize } /* Коэффициент загрузки */ fn load_factor(&self) -> f64 { self.size as f64 / self.capacity as f64 } /* Найти индекс корзины, соответствующий key */ fn find_bucket(&mut self, key: i32) -> usize { let mut index = self.hash_func(key); let mut first_tombstone = -1; // Выполнять линейное пробирование и завершить при встрече с пустой корзиной while self.buckets[index].is_some() { // Если встретился key, вернуть соответствующий индекс корзины if self.buckets[index].as_ref().unwrap().key == key { // Если ранее встретилась метка удаления, переместить пару ключ-значение в этот индекс if first_tombstone != -1 { self.buckets[first_tombstone as usize] = self.buckets[index].take(); self.buckets[index] = self.TOMBSTONE.clone(); return first_tombstone as usize; // Вернуть индекс корзины после перемещения } return index; // Вернуть индекс корзины } // Записать первую встретившуюся метку удаления if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE { first_tombstone = index as i32; } // Вычислить индекс корзины; при выходе за конец вернуться к началу index = (index + 1) % self.capacity; } // Если key не существует, вернуть индекс точки добавления if first_tombstone == -1 { index } else { first_tombstone as usize } } /* Операция поиска */ fn get(&mut self, key: i32) -> Option<&str> { // Найти индекс корзины, соответствующий key let index = self.find_bucket(key); // Если пара ключ-значение найдена, вернуть соответствующее val if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { return self.buckets[index].as_ref().map(|pair| &pair.val as &str); } // Если пары ключ-значение не существует, вернуть null None } /* Операция добавления */ fn put(&mut self, key: i32, val: String) { // Когда коэффициент загрузки превышает порог, выполнить расширение if self.load_factor() > self.load_thres { self.extend(); } // Найти индекс корзины, соответствующий key let index = self.find_bucket(key); // Если пара ключ-значение найдена, перезаписать val и вернуть if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { self.buckets[index].as_mut().unwrap().val = val; return; } // Если пары ключ-значение нет, добавить ее self.buckets[index] = Some(Pair { key, val }); self.size += 1; } /* Операция удаления */ fn remove(&mut self, key: i32) { // Найти индекс корзины, соответствующий key let index = self.find_bucket(key); // Если пара ключ-значение найдена, заменить ее меткой удаления if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { self.buckets[index] = self.TOMBSTONE.clone(); self.size -= 1; } } /* Расширить хеш-таблицу */ fn extend(&mut self) { // Временно сохранить исходную хеш-таблицу let buckets_tmp = self.buckets.clone(); // Инициализация новой хеш-таблицы после расширения self.capacity *= self.extend_ratio; self.buckets = vec![None; self.capacity]; self.size = 0; // Перенести пары ключ-значение из исходной хеш-таблицы в новую for pair in buckets_tmp { if pair.is_none() || pair == self.TOMBSTONE { continue; } let pair = pair.unwrap(); self.put(pair.key, pair.val); } } /* Вывести хеш-таблицу */ fn print(&self) { for pair in &self.buckets { if pair.is_none() { println!("null"); } else if pair == &self.TOMBSTONE { println!("TOMBSTONE"); } else { let pair = pair.as_ref().unwrap(); println!("{} -> {}", pair.key, pair.val); } } } } /* Driver Code */ fn main() { /* Инициализация хеш-таблицы */ let mut hashmap = HashMapOpenAddressing::new(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу hashmap.put(12836, "Сяо Ха".to_string()); hashmap.put(15937, "Сяо Ло".to_string()); hashmap.put(16750, "Сяо Суань".to_string()); hashmap.put(13276, "Сяо Фа".to_string()); hashmap.put(10583, "Сяо Я".to_string()); println!("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); hashmap.print(); /* Операция поиска */ // Передать ключ key в хеш-таблицу и получить значение val let name = hashmap.get(13276).unwrap(); println!("\nДля номера 13276 найдено имя {}", name); /* Операция удаления */ // Удалить пару (key, val) из хеш-таблицы hashmap.remove(16750); println!("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение"); hashmap.print(); } ================================================ FILE: ru/codes/rust/chapter_hashing/simple_hash.rs ================================================ /* * File: simple_hash.rs * Created Time: 2023-09-07 * Author: night-cruise (2586447362@qq.com) */ /* Аддитивное хеширование */ fn add_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = (hash + c as i64) % MODULUS; } hash as i32 } /* Мультипликативное хеширование */ fn mul_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = (31 * hash + c as i64) % MODULUS; } hash as i32 } /* XOR-хеширование */ fn xor_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash ^= c as i64; } (hash & MODULUS) as i32 } /* Хеширование с циклическим сдвигом */ fn rot_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS; } hash as i32 } /* Driver Code */ fn main() { let key = "Hello Algo"; let hash = add_hash(key); println!("Хеш-сумма сложением = {hash}"); let hash = mul_hash(key); println!("Хеш-сумма умножением = {hash}"); let hash = xor_hash(key); println!("Хеш-сумма XOR = {hash}"); let hash = rot_hash(key); println!("Хеш-сумма с циклическим сдвигом = {hash}"); } ================================================ FILE: ru/codes/rust/chapter_heap/heap.rs ================================================ /* * File: heap.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::{cmp::Reverse, collections::BinaryHeap}; fn test_push_max(heap: &mut BinaryHeap, val: i32) { heap.push(val); // Добавление элемента в кучу println!("\nПосле добавления элемента {} в кучу", val); print_util::print_heap(heap.iter().map(|&val| val).collect()); } fn test_pop_max(heap: &mut BinaryHeap) { let val = heap.pop().unwrap(); println!("\nПосле извлечения элемента вершины кучи {}", val); print_util::print_heap(heap.iter().map(|&val| val).collect()); } /* Driver Code */ fn main() { /* Инициализация кучи */ // Инициализация минимальной кучи #[allow(unused_assignments)] let mut min_heap = BinaryHeap::new(); // BinaryHeap в Rust является максимальной кучей; для минимальной кучи обычно используют оболочку Reverse // Инициализировать максимальную кучу let mut max_heap = BinaryHeap::new(); println!("\nНиже приведен тестовый пример для max-heap"); /* Добавление элемента в кучу */ test_push_max(&mut max_heap, 1); test_push_max(&mut max_heap, 3); test_push_max(&mut max_heap, 2); test_push_max(&mut max_heap, 5); test_push_max(&mut max_heap, 4); /* Получение элемента с вершины кучи */ let peek = max_heap.peek().unwrap(); println!("\nЭлемент на вершине кучи = {}", peek); /* Извлечение элемента с вершины кучи */ test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); /* Получение размера кучи */ let size = max_heap.len(); println!("\nКоличество элементов в куче = {}", size); /* Проверка, пуста ли куча */ let is_empty = max_heap.is_empty(); println!("\nПуста ли куча: {}", is_empty); /* Построить кучу по входному списку */ // Временная сложность равна O(n), а не O(nlogn) min_heap = BinaryHeap::from( vec![1, 3, 2, 5, 4] .into_iter() .map(|val| Reverse(val)) .collect::>>(), ); println!("\nПосле построения min-heap из входного списка"); print_util::print_heap(min_heap.iter().map(|&val| val.0).collect()); } ================================================ FILE: ru/codes/rust/chapter_heap/my_heap.rs ================================================ /* * File: my_heap.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* Максимальная куча */ struct MaxHeap { // Использовать vector вместо массива, чтобы не учитывать проблему расширения max_heap: Vec, } impl MaxHeap { /* Конструктор, строящий кучу по входному списку */ fn new(nums: Vec) -> Self { // Добавить элементы списка в кучу без изменений let mut heap = MaxHeap { max_heap: nums }; // Выполнить heapify для всех узлов, кроме листовых for i in (0..=Self::parent(heap.size() - 1)).rev() { heap.sift_down(i); } heap } /* Получить индекс левого дочернего узла */ fn left(i: usize) -> usize { 2 * i + 1 } /* Получить индекс правого дочернего узла */ fn right(i: usize) -> usize { 2 * i + 2 } /* Получить индекс родительского узла */ fn parent(i: usize) -> usize { (i - 1) / 2 // Округление вниз при делении } /* Поменять элементы местами */ fn swap(&mut self, i: usize, j: usize) { self.max_heap.swap(i, j); } /* Получение размера кучи */ fn size(&self) -> usize { self.max_heap.len() } /* Проверка, пуста ли куча */ fn is_empty(&self) -> bool { self.max_heap.is_empty() } /* Доступ к элементу на вершине кучи */ fn peek(&self) -> Option { self.max_heap.first().copied() } /* Добавление элемента в кучу */ fn push(&mut self, val: i32) { // Добавление узла self.max_heap.push(val); // Просеивание снизу вверх self.sift_up(self.size() - 1); } /* Начиная с узла i, выполнить просеивание снизу вверх */ fn sift_up(&mut self, mut i: usize) { loop { // Если узел i уже является вершиной кучи, завершить просеивание if i == 0 { break; } // Получение родительского узла для узла i let p = Self::parent(i); // Когда «узел не требует исправления», завершить просеивание if self.max_heap[i] <= self.max_heap[p] { break; } // Поменять два узла местами self.swap(i, p); // Циклическое просеивание вверх i = p; } } /* Извлечение элемента из кучи */ fn pop(&mut self) -> i32 { // Обработка пустого случая if self.is_empty() { panic!("index out of bounds"); } // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) self.swap(0, self.size() - 1); // Удаление узла let val = self.max_heap.pop().unwrap(); // Просеивание сверху вниз self.sift_down(0); // Вернуть элемент с вершины кучи val } /* Начиная с узла i, выполнить просеивание сверху вниз */ fn sift_down(&mut self, mut i: usize) { loop { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma let (l, r, mut ma) = (Self::left(i), Self::right(i), i); if l < self.size() && self.max_heap[l] > self.max_heap[ma] { ma = l; } if r < self.size() && self.max_heap[r] > self.max_heap[ma] { ma = r; } // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if ma == i { break; } // Поменять два узла местами self.swap(i, ma); // Циклическое просеивание вниз i = ma; } } /* Вывести кучу (двоичное дерево) */ fn print(&self) { print_util::print_heap(self.max_heap.clone()); } } /* Driver Code */ fn main() { /* Инициализация максимальной кучи */ let mut max_heap = MaxHeap::new(vec![9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); println!("\nПосле построения кучи из входного списка"); max_heap.print(); /* Получение элемента с вершины кучи */ let peek = max_heap.peek(); if let Some(peek) = peek { println!("\nЭлемент на вершине кучи = {}", peek); } /* Добавление элемента в кучу */ let val = 7; max_heap.push(val); println!("\nПосле добавления элемента {} в кучу", val); max_heap.print(); /* Извлечение элемента с вершины кучи */ let peek = max_heap.pop(); println!("\nПосле извлечения элемента вершины кучи {}", peek); max_heap.print(); /* Получение размера кучи */ let size = max_heap.size(); println!("\nКоличество элементов в куче = {}", size); /* Проверка, пуста ли куча */ let is_empty = max_heap.is_empty(); println!("\nПуста ли куча: {}", is_empty); } ================================================ FILE: ru/codes/rust/chapter_heap/top_k.rs ================================================ /* * File: top_k.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::cmp::Reverse; use std::collections::BinaryHeap; /* Найти k наибольших элементов массива с помощью кучи */ fn top_k_heap(nums: Vec, k: usize) -> BinaryHeap> { // BinaryHeap — это максимальная куча; с помощью Reverse элементы инвертируются, чтобы реализовать минимальную кучу let mut heap = BinaryHeap::>::new(); // Поместить первые k элементов массива в кучу for &num in nums.iter().take(k) { heap.push(Reverse(num)); } // Начиная с элемента k+1, поддерживать длину кучи равной k for &num in nums.iter().skip(k) { // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу if num > heap.peek().unwrap().0 { heap.pop(); heap.push(Reverse(num)); } } heap } /* Driver Code */ fn main() { let nums = vec![1, 7, 6, 3, 2]; let k = 3; let res = top_k_heap(nums, k); println!("Наибольшие {} элементов", k); print_util::print_heap(res.into_iter().map(|item| item.0).collect()); } ================================================ FILE: ru/codes/rust/chapter_searching/binary_search.rs ================================================ /* * File: binary_search.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ /* Бинарный поиск (двусторонне замкнутый интервал) */ fn binary_search(nums: &[i32], target: i32) -> i32 { // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно let mut i = 0; let mut j = nums.len() as i32 - 1; // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) while i <= j { let m = i + (j - i) / 2; // Вычислить индекс середины m if nums[m as usize] < target { // Это означает, что target находится в интервале [m+1, j] i = m + 1; } else if nums[m as usize] > target { // Это означает, что target находится в интервале [i, m-1] j = m - 1; } else { // Целевой элемент найден, вернуть его индекс return m; } } // Целевой элемент не найден, вернуть -1 return -1; } /* Бинарный поиск (лево замкнутый, право открытый интервал) */ fn binary_search_lcro(nums: &[i32], target: i32) -> i32 { // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно let mut i = 0; let mut j = nums.len() as i32; // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) while i < j { let m = i + (j - i) / 2; // Вычислить индекс середины m if nums[m as usize] < target { // Это означает, что target находится в интервале [m+1, j) i = m + 1; } else if nums[m as usize] > target { // Это означает, что target находится в интервале [i, m) j = m; } else { // Целевой элемент найден, вернуть его индекс return m; } } // Целевой элемент не найден, вернуть -1 return -1; } /* Driver Code */ pub fn main() { let target = 6; let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // Бинарный поиск (двусторонне замкнутый интервал) let mut index = binary_search(&nums, target); println!("Индекс целевого элемента 6 = {index}"); // Бинарный поиск (лево замкнутый, право открытый интервал) index = binary_search_lcro(&nums, target); println!("Индекс целевого элемента 6 = {index}"); } ================================================ FILE: ru/codes/rust/chapter_searching/binary_search_edge.rs ================================================ /* * File: binary_search_edge.rs * Created Time: 2023-08-30 * Author: night-cruise (2586447362@qq.com) */ mod binary_search_insertion; use binary_search_insertion::binary_search_insertion; /* Бинарный поиск самого левого target */ fn binary_search_left_edge(nums: &[i32], target: i32) -> i32 { // Эквивалентно поиску точки вставки target let i = binary_search_insertion(nums, target); // target не найден, вернуть -1 if i == nums.len() as i32 || nums[i as usize] != target { return -1; } // Найти target и вернуть индекс i i } /* Бинарный поиск самого правого target */ fn binary_search_right_edge(nums: &[i32], target: i32) -> i32 { // Преобразовать задачу в поиск самого левого target + 1 let i = binary_search_insertion(nums, target + 1); // j указывает на самый правый target, а i — на первый элемент больше target let j = i - 1; // target не найден, вернуть -1 if j == -1 || nums[j as usize] != target { return -1; } // Найти target и вернуть индекс j j } /* Driver Code */ fn main() { // Массив с повторяющимися элементами let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; println!("\nМассив nums = {:?}", nums); // Бинарный поиск левой и правой границы for target in [6, 7] { let index = binary_search_left_edge(&nums, target); println!("Индекс самого левого элемента {} равен {}", target, index); let index = binary_search_right_edge(&nums, target); println!("Индекс самого правого элемента {} равен {}", target, index); } } ================================================ FILE: ru/codes/rust/chapter_searching/binary_search_insertion.rs ================================================ /* * File: binary_search_insertion.rs * Created Time: 2023-08-30 * Author: night-cruise (2586447362@qq.com) */ #![allow(unused)] /* Бинарный поиск точки вставки (без повторяющихся элементов) */ fn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 { let (mut i, mut j) = (0, nums.len() as i32 - 1); // Инициализировать двусторонне замкнутый интервал [0, n-1] while i <= j { let m = i + (j - i) / 2; // Вычислить индекс середины m if nums[m as usize] < target { i = m + 1; // target находится в интервале [m+1, j] } else if nums[m as usize] > target { j = m - 1; // target находится в интервале [i, m-1] } else { return m; } } // target не найден, вернуть точку вставки i i } /* Бинарный поиск точки вставки (с повторяющимися элементами) */ pub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 { let (mut i, mut j) = (0, nums.len() as i32 - 1); // Инициализировать двусторонне замкнутый интервал [0, n-1] while i <= j { let m = i + (j - i) / 2; // Вычислить индекс середины m if nums[m as usize] < target { i = m + 1; // target находится в интервале [m+1, j] } else if nums[m as usize] > target { j = m - 1; // target находится в интервале [i, m-1] } else { j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] } } // Вернуть точку вставки i i } /* Driver Code */ fn main() { // Массив без повторяющихся элементов let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; println!("\nМассив nums = {:?}", nums); // Бинарный поиск точки вставки for target in [6, 9] { let index = binary_search_insertion_simple(&nums, target); println!("Индекс позиции вставки элемента {} равен {}", target, index); } // Массив с повторяющимися элементами let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; println!("\nМассив nums = {:?}", nums); // Бинарный поиск точки вставки for target in [2, 6, 20] { let index = binary_search_insertion(&nums, target); println!("Индекс позиции вставки элемента {} равен {}", target, index); } } ================================================ FILE: ru/codes/rust/chapter_searching/hashing_search.rs ================================================ /* * File: hashing_search.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::ListNode; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; /* Хеш-поиск (массив) */ fn hashing_search_array<'a>(map: &'a HashMap, target: i32) -> Option<&'a usize> { // key хеш-таблицы: целевой элемент, value: индекс // Если такого key нет в хеш-таблице, вернуть None map.get(&target) } /* Хеш-поиск (связный список) */ fn hashing_search_linked_list( map: &HashMap>>>, target: i32, ) -> Option<&Rc>>> { // key хеш-таблицы: значение целевого узла, value: объект узла // Если такого key нет в хеш-таблице, вернуть None map.get(&target) } /* Driver Code */ pub fn main() { let target = 3; /* Хеш-поиск (массив) */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // Инициализация хеш-таблицы let mut map = HashMap::new(); for (i, num) in nums.iter().enumerate() { map.insert(*num, i); // key: элемент, value: индекс } let index = hashing_search_array(&map, target); println!("Индекс целевого элемента 3 = {}", index.unwrap()); /* Хеш-поиск (связный список) */ let head = ListNode::arr_to_linked_list(&nums); // Инициализировать хеш-таблицу // let mut map1 = HashMap::new(); let map1 = ListNode::linked_list_to_hashmap(head); let node = hashing_search_linked_list(&map1, target); println!("Объект узла со значением 3 = {:?}", node); } ================================================ FILE: ru/codes/rust/chapter_searching/linear_search.rs ================================================ /* * File: linear_search.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::ListNode; use std::cell::RefCell; use std::rc::Rc; /* Линейный поиск (массив) */ fn linear_search_array(nums: &[i32], target: i32) -> i32 { // Обход массива for (i, num) in nums.iter().enumerate() { // Целевой элемент найден, вернуть его индекс if num == &target { return i as i32; } } // Целевой элемент не найден, вернуть -1 return -1; } /* Линейный поиск (связный список) */ fn linear_search_linked_list( head: Rc>>, target: i32, ) -> Option>>> { // Найти целевой узел и вернуть его if head.borrow().val == target { return Some(head); }; // Найти целевой узел и вернуть его if let Some(node) = &head.borrow_mut().next { return linear_search_linked_list(node.clone(), target); } // Целевой узел не найден, вернуть None return None; } /* Driver Code */ pub fn main() { let target = 3; /* Выполнить линейный поиск в массиве */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; let index = linear_search_array(&nums, target); println!("Индекс целевого элемента 3 = {}", index); /* Выполнить линейный поиск в связном списке */ let head = ListNode::arr_to_linked_list(&nums); let node = linear_search_linked_list(head.unwrap(), target); println!("Объект узла со значением 3 = {:?}", node); } ================================================ FILE: ru/codes/rust/chapter_searching/two_sum.rs ================================================ /* * File: two_sum.rs * Created Time: 2023-01-14 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::collections::HashMap; /* Метод 1: полный перебор */ pub fn two_sum_brute_force(nums: &Vec, target: i32) -> Option> { let size = nums.len(); // Два вложенных цикла, временная сложность O(n^2) for i in 0..size - 1 { for j in i + 1..size { if nums[i] + nums[j] == target { return Some(vec![i as i32, j as i32]); } } } None } /* Метод 2: вспомогательная хеш-таблица */ pub fn two_sum_hash_table(nums: &Vec, target: i32) -> Option> { // Вспомогательная хеш-таблица, пространственная сложность O(n) let mut dic = HashMap::new(); // Один цикл, временная сложность O(n) for (i, num) in nums.iter().enumerate() { match dic.get(&(target - num)) { Some(v) => return Some(vec![*v as i32, i as i32]), None => dic.insert(num, i as i32), }; } None } fn main() { // ======= Test Case ======= let nums = vec![2, 7, 11, 15]; let target = 13; // ====== Основной код ====== // Метод 1 let res = two_sum_brute_force(&nums, target).unwrap(); print!("Результат метода 1 res = "); print_util::print_array(&res); // Метод 2 let res = two_sum_hash_table(&nums, target).unwrap(); print!("\nРезультат метода 2 res = "); print_util::print_array(&res); } ================================================ FILE: ru/codes/rust/chapter_sorting/bubble_sort.rs ================================================ /* * File: bubble_sort.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Пузырьковая сортировка */ fn bubble_sort(nums: &mut [i32]) { // Внешний цикл: неотсортированный диапазон [0, i] for i in (1..nums.len()).rev() { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for j in 0..i { if nums[j] > nums[j + 1] { // Поменять местами nums[j] и nums[j + 1] nums.swap(j, j + 1); } } } } /* Пузырьковая сортировка (оптимизация флагом) */ fn bubble_sort_with_flag(nums: &mut [i32]) { // Внешний цикл: неотсортированный диапазон [0, i] for i in (1..nums.len()).rev() { let mut flag = false; // Инициализировать флаг // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for j in 0..i { if nums[j] > nums[j + 1] { // Поменять местами nums[j] и nums[j + 1] nums.swap(j, j + 1); flag = true; // Записать обмен элементов } } if !flag { break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти }; } } /* Driver Code */ pub fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; bubble_sort(&mut nums); print!("После пузырьковой сортировки nums = "); print_util::print_array(&nums); let mut nums1 = [4, 1, 3, 1, 5, 2]; bubble_sort_with_flag(&mut nums1); print!("\nПосле пузырьковой сортировки nums1 = "); print_util::print_array(&nums1); } ================================================ FILE: ru/codes/rust/chapter_sorting/bucket_sort.rs ================================================ /* * File: bucket_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* Сортировка корзинами */ fn bucket_sort(nums: &mut [f64]) { // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину let k = nums.len() / 2; let mut buckets = vec![vec![]; k]; // 1. Распределить элементы массива по корзинам for &num in nums.iter() { // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] let i = (num * k as f64) as usize; // Добавить num в корзину i buckets[i].push(num); } // 2. Выполнить сортировку внутри каждой корзины for bucket in &mut buckets { // Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки bucket.sort_by(|a, b| a.partial_cmp(b).unwrap()); } // 3. Обойти корзины и объединить результаты let mut i = 0; for bucket in buckets.iter() { for &num in bucket.iter() { nums[i] = num; i += 1; } } } /* Driver Code */ fn main() { // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) let mut nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucket_sort(&mut nums); print!("После сортировки корзинами nums = "); print_util::print_array(&nums); } ================================================ FILE: ru/codes/rust/chapter_sorting/counting_sort.rs ================================================ /* * File: counting_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* Сортировка подсчетом */ // Простая реализация, не подходит для сортировки объектов fn counting_sort_naive(nums: &mut [i32]) { // 1. Найти максимальный элемент массива m let m = *nums.iter().max().unwrap(); // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num let mut counter = vec![0; m as usize + 1]; for &num in nums.iter() { counter[num as usize] += 1; } // 3. Обойти counter и заполнить исходный массив nums элементами let mut i = 0; for num in 0..m + 1 { for _ in 0..counter[num as usize] { nums[i] = num; i += 1; } } } /* Сортировка подсчетом */ // Полная реализация, позволяет сортировать объекты и является стабильной сортировкой fn counting_sort(nums: &mut [i32]) { // 1. Найти максимальный элемент массива m let m = *nums.iter().max().unwrap() as usize; // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num let mut counter = vec![0; m + 1]; for &num in nums.iter() { counter[num as usize] += 1; } // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» // То есть counter[num]-1 — это индекс последнего появления num в res for i in 0..m { counter[i + 1] += counter[i]; } // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res // Инициализировать массив res для хранения результата let n = nums.len(); let mut res = vec![0; n]; for i in (0..n).rev() { let num = nums[i]; res[counter[num as usize] - 1] = num; // Поместить num по соответствующему индексу counter[num as usize] -= 1; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num } // Перезаписать исходный массив nums массивом результата res nums.copy_from_slice(&res) } /* Driver Code */ fn main() { let mut nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; counting_sort_naive(&mut nums); print!("После сортировки подсчетом (объекты не поддерживаются) nums = "); print_util::print_array(&nums); let mut nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; counting_sort(&mut nums1); print!("\nПосле сортировки подсчетом nums1 = "); print_util::print_array(&nums1); } ================================================ FILE: ru/codes/rust/chapter_sorting/heap_sort.rs ================================================ /* * File: heap_sort.rs * Created Time: 2023-07-04 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ fn sift_down(nums: &mut [i32], n: usize, mut i: usize) { loop { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma let l = 2 * i + 1; let r = 2 * i + 2; let mut ma = i; if l < n && nums[l] > nums[ma] { ma = l; } if r < n && nums[r] > nums[ma] { ma = r; } // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if ma == i { break; } // Поменять два узла местами nums.swap(i, ma); // Циклическое просеивание вниз i = ma; } } /* Сортировка кучей */ fn heap_sort(nums: &mut [i32]) { // Построение кучи: выполнить heapify для всех узлов, кроме листовых for i in (0..nums.len() / 2).rev() { sift_down(nums, nums.len(), i); } // Извлекать максимальный элемент из кучи в течение n-1 итераций for i in (1..nums.len()).rev() { // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) nums.swap(0, i); // Начиная с корневого узла, выполнить просеивание сверху вниз sift_down(nums, i, 0); } } /* Driver Code */ fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; heap_sort(&mut nums); print!("После сортировки кучей nums = "); print_util::print_array(&nums); } ================================================ FILE: ru/codes/rust/chapter_sorting/insertion_sort.rs ================================================ /* * File: insertion_sort.rs * Created Time: 2023-02-13 * Author: xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; /* Сортировка вставками */ fn insertion_sort(nums: &mut [i32]) { // Внешний цикл: отсортированный диапазон [0, i-1] for i in 1..nums.len() { let (base, mut j) = (nums[i], (i - 1) as i32); // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] while j >= 0 && nums[j as usize] > base { nums[(j + 1) as usize] = nums[j as usize]; // Сдвинуть nums[j] на одну позицию вправо j -= 1; } nums[(j + 1) as usize] = base; // Поместить base в правильную позицию } } /* Driver Code */ fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; insertion_sort(&mut nums); print!("После сортировки вставками nums = "); print_util::print_array(&nums); } ================================================ FILE: ru/codes/rust/chapter_sorting/merge_sort.rs ================================================ /** * File: merge_sort.rs * Created Time: 2023-02-14 * Author: xBLACKICEx (xBLACKICEx@outlook.com) */ /* Объединить левый и правый подмассивы */ fn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) { // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] // Создать временный массив tmp для хранения результата слияния let tmp_size = right - left + 1; let mut tmp = vec![0; tmp_size]; // Инициализировать начальные индексы левого и правого подмассивов let (mut i, mut j, mut k) = (left, mid + 1, 0); // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив while i <= mid && j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i]; i += 1; } else { tmp[k] = nums[j]; j += 1; } k += 1; } // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив while i <= mid { tmp[k] = nums[i]; k += 1; i += 1; } while j <= right { tmp[k] = nums[j]; k += 1; j += 1; } // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums for k in 0..tmp_size { nums[left + k] = tmp[k]; } } /* Сортировка слиянием */ fn merge_sort(nums: &mut [i32], left: usize, right: usize) { // Условие завершения if left >= right { return; // Завершить рекурсию, когда длина подмассива равна 1 } // Этап разбиения let mid = left + (right - left) / 2; // Вычислить середину merge_sort(nums, left, mid); // Рекурсивно обработать левый подмассив merge_sort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив // Этап слияния merge(nums, left, mid, right); } /* Driver Code */ fn main() { /* Сортировка слиянием */ let mut nums = [7, 3, 2, 6, 0, 1, 5, 4]; let right = nums.len() - 1; merge_sort(&mut nums, 0, right); println!("После сортировки слиянием nums = {:?}", nums); } ================================================ FILE: ru/codes/rust/chapter_sorting/quick_sort.rs ================================================ /** * File: quick_sort.rs * Created Time: 2023-02-16 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ /* Быстрая сортировка */ struct QuickSort; impl QuickSort { /* Разбиение с опорными указателями */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // Взять nums[left] в качестве опорного элемента let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // Идти справа налево в поисках первого элемента меньше опорного } while i < j && nums[i] <= nums[left] { i += 1; // Идти слева направо в поисках первого элемента больше опорного } nums.swap(i, j); // Поменять эти два элемента местами } nums.swap(i, left); // Переместить опорный элемент на границу двух подмассивов i // Вернуть индекс опорного элемента } /* Быстрая сортировка */ pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { // Завершить рекурсию, когда длина подмассива равна 1 if left >= right { return; } // Разбиение с опорными указателями let pivot = Self::partition(nums, left as usize, right as usize) as i32; // Рекурсивно обработать левый и правый подмассивы Self::quick_sort(left, pivot - 1, nums); Self::quick_sort(pivot + 1, right, nums); } } /* Быстрая сортировка (оптимизация медианным опорным элементом) */ struct QuickSortMedian; impl QuickSortMedian { /* Выбрать медиану из трех кандидатов */ fn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize { let (l, m, r) = (nums[left], nums[mid], nums[right]); if (l <= m && m <= r) || (r <= m && m <= l) { return mid; // m находится между l и r } if (m <= l && l <= r) || (r <= l && l <= m) { return left; // l находится между m и r } right } /* Разбиение с опорными указателями (медиана трех) */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // Выбрать медиану из трех кандидатов let med = Self::median_three(nums, left, (left + right) / 2, right); // Переместить медиану в крайний левый элемент массива nums.swap(left, med); // Взять nums[left] в качестве опорного элемента let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // Идти справа налево в поисках первого элемента меньше опорного } while i < j && nums[i] <= nums[left] { i += 1; // Идти слева направо в поисках первого элемента больше опорного } nums.swap(i, j); // Поменять эти два элемента местами } nums.swap(i, left); // Переместить опорный элемент на границу двух подмассивов i // Вернуть индекс опорного элемента } /* Быстрая сортировка */ pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { // Завершить рекурсию, когда длина подмассива равна 1 if left >= right { return; } // Разбиение с опорными указателями let pivot = Self::partition(nums, left as usize, right as usize) as i32; // Рекурсивно обработать левый и правый подмассивы Self::quick_sort(left, pivot - 1, nums); Self::quick_sort(pivot + 1, right, nums); } } /* Быстрая сортировка (оптимизация глубины рекурсии) */ struct QuickSortTailCall; impl QuickSortTailCall { /* Разбиение с опорными указателями */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // Взять nums[left] в качестве опорного элемента let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // Идти справа налево в поисках первого элемента меньше опорного } while i < j && nums[i] <= nums[left] { i += 1; // Идти слева направо в поисках первого элемента больше опорного } nums.swap(i, j); // Поменять эти два элемента местами } nums.swap(i, left); // Переместить опорный элемент на границу двух подмассивов i // Вернуть индекс опорного элемента } /* Быстрая сортировка (оптимизация глубины рекурсии) */ pub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) { // Завершить, когда длина подмассива равна 1 while left < right { // Операция разбиения с опорными указателями let pivot = Self::partition(nums, left as usize, right as usize) as i32; // Выполнить быструю сортировку для более короткого из двух подмассивов if pivot - left < right - pivot { Self::quick_sort(left, pivot - 1, nums); // Рекурсивно отсортировать левый подмассив left = pivot + 1; // Оставшийся неотсортированный диапазон: [pivot + 1, right] } else { Self::quick_sort(pivot + 1, right, nums); // Рекурсивно отсортировать правый подмассив right = pivot - 1; // Оставшийся неотсортированный диапазон: [left, pivot - 1] } } } } /* Driver Code */ fn main() { /* Быстрая сортировка */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSort::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("После быстрой сортировки nums = {:?}", nums); /* Быстрая сортировка (оптимизация медианным опорным элементом) */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSortMedian::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("После быстрой сортировки (оптимизация медианным опорным элементом) nums = {:?}", nums); /* Быстрая сортировка (оптимизация глубины рекурсии) */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSortTailCall::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("После быстрой сортировки (оптимизация глубины рекурсии) nums = {:?}", nums); } ================================================ FILE: ru/codes/rust/chapter_sorting/radix_sort.rs ================================================ /* * File: radix_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* Получить k-й разряд элемента num, где exp = 10^(k-1) */ fn digit(num: i32, exp: i32) -> usize { // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени return ((num / exp) % 10) as usize; } /* Сортировка подсчетом (сортировка по k-му разряду nums) */ fn counting_sort_digit(nums: &mut [i32], exp: i32) { // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 let mut counter = [0; 10]; let n = nums.len(); // Подсчитать число появлений каждой цифры от 0 до 9 for i in 0..n { let d = digit(nums[i], exp); // Получить k-й разряд nums[i], обозначив его как d counter[d] += 1; // Подсчитать число появлений цифры d } // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» for i in 1..10 { counter[i] += counter[i - 1]; } // Выполняя обратный проход, заполнить res элементами по статистике в корзинах let mut res = vec![0; n]; for i in (0..n).rev() { let d = digit(nums[i], exp); let j = counter[d] - 1; // Получить индекс j цифры d в массиве res[j] = nums[i]; // Поместить текущий элемент по индексу j counter[d] -= 1; // Уменьшить количество d на 1 } // Перезаписать исходный массив nums результатом nums.copy_from_slice(&res); } /* Поразрядная сортировка */ fn radix_sort(nums: &mut [i32]) { // Получить максимальный элемент массива, чтобы определить максимальное число разрядов let m = *nums.into_iter().max().unwrap(); // Проходить разряды от младшего к старшему let mut exp = 1; while exp <= m { counting_sort_digit(nums, exp); exp *= 10; } } /* Driver Code */ fn main() { // Поразрядная сортировка let mut nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radix_sort(&mut nums); print!("После поразрядной сортировки nums = "); print_util::print_array(&nums); } ================================================ FILE: ru/codes/rust/chapter_sorting/selection_sort.rs ================================================ /* * File: selection_sort.rs * Created Time: 2023-05-30 * Author: WSL0809 (wslzzy@outlook.com) */ use hello_algo_rust::include::print_util; /* Сортировка выбором */ fn selection_sort(nums: &mut [i32]) { if nums.is_empty() { return; } let n = nums.len(); // Внешний цикл: неотсортированный диапазон [i, n-1] for i in 0..n - 1 { // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне let mut k = i; for j in i + 1..n { if nums[j] < nums[k] { k = j; // Записать индекс минимального элемента } } // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона nums.swap(i, k); } } /* Driver Code */ pub fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; selection_sort(&mut nums); print!("\nПосле сортировки выбором nums = "); print_util::print_array(&nums); } ================================================ FILE: ru/codes/rust/chapter_stack_and_queue/array_deque.rs ================================================ /* * File: array_deque.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Двусторонняя очередь на основе кольцевого массива */ struct ArrayDeque { nums: Vec, // Массив для хранения элементов двусторонней очереди front: usize, // Указатель head, указывающий на первый элемент очереди que_size: usize, // Длина двусторонней очереди } impl ArrayDeque { /* Конструктор */ pub fn new(capacity: usize) -> Self { Self { nums: vec![T::default(); capacity], front: 0, que_size: 0, } } /* Получить вместимость двусторонней очереди */ pub fn capacity(&self) -> usize { self.nums.len() } /* Получение длины двусторонней очереди */ pub fn size(&self) -> usize { self.que_size } /* Проверка, пуста ли двусторонняя очередь */ pub fn is_empty(&self) -> bool { self.que_size == 0 } /* Вычислить индекс в кольцевом массиве */ fn index(&self, i: i32) -> usize { // С помощью операции взятия по модулю соединить начало и конец массива // Когда i выходит за конец массива, он возвращается в начало // Когда i выходит за начало массива, он возвращается в конец ((i + self.capacity() as i32) % self.capacity() as i32) as usize } /* Добавление в голову очереди */ pub fn push_first(&mut self, num: T) { if self.que_size == self.capacity() { println!("Двусторонняя очередь заполнена"); return; } // Указатель головы сдвигается на одну позицию влево // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост self.front = self.index(self.front as i32 - 1); // Добавить num в голову очереди self.nums[self.front] = num; self.que_size += 1; } /* Добавление в хвост очереди */ pub fn push_last(&mut self, num: T) { if self.que_size == self.capacity() { println!("Двусторонняя очередь заполнена"); return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 let rear = self.index(self.front as i32 + self.que_size as i32); // Добавить num в хвост очереди self.nums[rear] = num; self.que_size += 1; } /* Извлечение из головы очереди */ fn pop_first(&mut self) -> T { let num = self.peek_first(); // Указатель головы сдвигается на одну позицию назад self.front = self.index(self.front as i32 + 1); self.que_size -= 1; num } /* Извлечение из хвоста очереди */ fn pop_last(&mut self) -> T { let num = self.peek_last(); self.que_size -= 1; num } /* Доступ к элементу в начале очереди */ fn peek_first(&self) -> T { if self.is_empty() { panic!("двусторонняя очередь пуста") }; self.nums[self.front] } /* Доступ к элементу в конце очереди */ fn peek_last(&self) -> T { if self.is_empty() { panic!("двусторонняя очередь пуста") }; // Вычислить индекс хвостового элемента let last = self.index(self.front as i32 + self.que_size as i32 - 1); self.nums[last] } /* Вернуть массив для вывода */ fn to_array(&self) -> Vec { // Преобразовывать только элементы списка в пределах фактической длины let mut res = vec![T::default(); self.que_size]; let mut j = self.front; for i in 0..self.que_size { res[i] = self.nums[self.index(j as i32)]; j += 1; } res } } /* Driver Code */ fn main() { /* Инициализация двусторонней очереди */ let mut deque = ArrayDeque::new(10); deque.push_last(3); deque.push_last(2); deque.push_last(5); print!("Двусторонняя очередь deque = "); print_util::print_array(&deque.to_array()); /* Доступ к элементу */ let peek_first = deque.peek_first(); print!("\nПервый элемент peek_first = {}", peek_first); let peek_last = deque.peek_last(); print!("\nПоследний элемент peek_last = {}", peek_last); /* Добавление элемента в очередь */ deque.push_last(4); print!("\nПосле добавления элемента 4 в хвост deque = "); print_util::print_array(&deque.to_array()); deque.push_first(1); print!("\nПосле добавления элемента 1 в голову deque = "); print_util::print_array(&deque.to_array()); /* Извлечение элемента из очереди */ let pop_last = deque.pop_last(); print!("\nИзвлеченный из хвоста элемент = {}, deque после извлечения из хвоста = ", pop_last); print_util::print_array(&deque.to_array()); let pop_first = deque.pop_first(); print!("\nИзвлеченный из головы элемент = {}, deque после извлечения из головы = ", pop_first); print_util::print_array(&deque.to_array()); /* Получение длины двусторонней очереди */ let size = deque.size(); print!("\nДлина двусторонней очереди size = {}", size); /* Проверка, пуста ли двусторонняя очередь */ let is_empty = deque.is_empty(); print!("\nПуста ли двусторонняя очередь = {}", is_empty); } ================================================ FILE: ru/codes/rust/chapter_stack_and_queue/array_queue.rs ================================================ /* * File: array_queue.rs * Created Time: 2023-02-06 * Author: WSL0809 (wslzzy@outlook.com) */ /* Очередь на основе кольцевого массива */ struct ArrayQueue { nums: Vec, // Массив для хранения элементов очереди front: i32, // Указатель head, указывающий на первый элемент очереди que_size: i32, // Длина очереди que_capacity: i32, // Вместимость очереди } impl ArrayQueue { /* Конструктор */ fn new(capacity: i32) -> ArrayQueue { ArrayQueue { nums: vec![T::default(); capacity as usize], front: 0, que_size: 0, que_capacity: capacity, } } /* Получить вместимость очереди */ fn capacity(&self) -> i32 { self.que_capacity } /* Получение длины очереди */ fn size(&self) -> i32 { self.que_size } /* Проверка, пуста ли очередь */ fn is_empty(&self) -> bool { self.que_size == 0 } /* Поместить в очередь */ fn push(&mut self, num: T) { if self.que_size == self.capacity() { println!("Очередь заполнена"); return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива let rear = (self.front + self.que_size) % self.que_capacity; // Добавить num в хвост очереди self.nums[rear as usize] = num; self.que_size += 1; } /* Извлечь из очереди */ fn pop(&mut self) -> T { let num = self.peek(); // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива self.front = (self.front + 1) % self.que_capacity; self.que_size -= 1; num } /* Доступ к элементу в начале очереди */ fn peek(&self) -> T { if self.is_empty() { panic!("index out of bounds"); } self.nums[self.front as usize] } /* Вернуть массив */ fn to_vector(&self) -> Vec { let cap = self.que_capacity; let mut j = self.front; let mut arr = vec![T::default(); cap as usize]; for i in 0..self.que_size { arr[i as usize] = self.nums[(j % cap) as usize]; j += 1; } arr } } /* Driver Code */ fn main() { /* Инициализация очереди */ let capacity = 10; let mut queue = ArrayQueue::new(capacity); /* Добавление элемента в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); println!("Очередь queue = {:?}", queue.to_vector()); /* Доступ к элементу в начале очереди */ let peek = queue.peek(); println!("Первый элемент peek = {}", peek); /* Извлечение элемента из очереди */ let pop = queue.pop(); println!( "Извлеченный элемент pop = {:?}, queue после извлечения = {:?}", pop, queue.to_vector() ); /* Получение длины очереди */ let size = queue.size(); println!("Длина очереди size = {}", size); /* Проверка, пуста ли очередь */ let is_empty = queue.is_empty(); println!("Пуста ли очередь = {}", is_empty); /* Проверка кольцевого массива */ for i in 0..10 { queue.push(i); queue.pop(); println!("После {:?}-го раунда операций enqueue и dequeue queue = {:?}", i, queue.to_vector()); } } ================================================ FILE: ru/codes/rust/chapter_stack_and_queue/array_stack.rs ================================================ /* * File: array_stack.rs * Created Time: 2023-02-05 * Author: WSL0809 (wslzzy@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Стек на основе массива */ struct ArrayStack { stack: Vec, } impl ArrayStack { /* Инициализация стека */ fn new() -> ArrayStack { ArrayStack:: { stack: Vec::::new(), } } /* Получение длины стека */ fn size(&self) -> usize { self.stack.len() } /* Проверка, пуст ли стек */ fn is_empty(&self) -> bool { self.size() == 0 } /* Поместить в стек */ fn push(&mut self, num: T) { self.stack.push(num); } /* Извлечь из стека */ fn pop(&mut self) -> Option { self.stack.pop() } /* Доступ к верхнему элементу стека */ fn peek(&self) -> Option<&T> { if self.is_empty() { panic!("стек пуст") }; self.stack.last() } /* Вернуть &Vec */ fn to_array(&self) -> &Vec { &self.stack } } /* Driver Code */ fn main() { // Инициализация стека let mut stack = ArrayStack::::new(); // Помещение элемента в стек stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("Стек stack = "); print_util::print_array(stack.to_array()); // Доступ к верхнему элементу стека let peek = stack.peek().unwrap(); print!("\nВерхний элемент peek = {}", peek); // Извлечение элемента из стека let pop = stack.pop().unwrap(); print!("\nИзвлеченный элемент pop = {pop}, stack после извлечения = "); print_util::print_array(stack.to_array()); // Получение длины стека let size = stack.size(); print!("\nДлина стека size = {size}"); // Проверка на пустоту let is_empty = stack.is_empty(); print!("\nПуст ли стек = {is_empty}"); } ================================================ FILE: ru/codes/rust/chapter_stack_and_queue/deque.rs ================================================ /* * File: deque.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; use std::collections::VecDeque; /* Driver Code */ pub fn main() { // Инициализация двусторонней очереди let mut deque: VecDeque = VecDeque::new(); deque.push_back(3); deque.push_back(2); deque.push_back(5); print!("Двусторонняя очередь deque = "); print_util::print_queue(&deque); // Доступ к элементу let peek_first = deque.front().unwrap(); print!("\nПервый элемент peekFirst = {peek_first}"); let peek_last = deque.back().unwrap(); print!("\nПоследний элемент peekLast = {peek_last}"); /* Добавление элемента в очередь */ deque.push_back(4); print!("\nПосле добавления элемента 4 в хвост deque = "); print_util::print_queue(&deque); deque.push_front(1); print!("\nПосле добавления элемента 1 в голову deque = "); print_util::print_queue(&deque); // Извлечение элемента из очереди let pop_last = deque.pop_back().unwrap(); print!("\nИзвлеченный из хвоста элемент = {pop_last}, deque после извлечения из хвоста = "); print_util::print_queue(&deque); let pop_first = deque.pop_front().unwrap(); print!("\nИзвлеченный из головы элемент = {pop_first}, deque после извлечения из головы = "); print_util::print_queue(&deque); // Получение длины двусторонней очереди let size = deque.len(); print!("\nДлина двусторонней очереди size = {size}"); // Проверка, пуста ли двусторонняя очередь let is_empty = deque.is_empty(); print!("\nПуста ли двусторонняя очередь = {is_empty}"); } ================================================ FILE: ru/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs ================================================ /* * File: linkedlist_deque.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::cell::RefCell; use std::rc::Rc; /* Узел двусвязного списка */ pub struct ListNode { pub val: T, // Значение узла pub next: Option>>>, // Указатель на узел-преемник pub prev: Option>>>, // Указатель на узел-предшественник } impl ListNode { pub fn new(val: T) -> Rc>> { Rc::new(RefCell::new(ListNode { val, next: None, prev: None, })) } } /* Двусторонняя очередь на основе двусвязного списка */ #[allow(dead_code)] pub struct LinkedListDeque { front: Option>>>, // Головной узел front rear: Option>>>, // Хвостовой узел rear que_size: usize, // Длина двусторонней очереди } impl LinkedListDeque { pub fn new() -> Self { Self { front: None, rear: None, que_size: 0, } } /* Получение длины двусторонней очереди */ pub fn size(&self) -> usize { return self.que_size; } /* Проверка, пуста ли двусторонняя очередь */ pub fn is_empty(&self) -> bool { return self.que_size == 0; } /* Операция добавления в очередь */ fn push(&mut self, num: T, is_front: bool) { let node = ListNode::new(num); // Операция добавления в голову очереди if is_front { match self.front.take() { // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node None => { self.rear = Some(node.clone()); self.front = Some(node); } // Добавить node в голову списка Some(old_front) => { old_front.borrow_mut().prev = Some(node.clone()); node.borrow_mut().next = Some(old_front); self.front = Some(node); // Обновить головной узел } } } // Операция добавления в хвост очереди else { match self.rear.take() { // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node None => { self.front = Some(node.clone()); self.rear = Some(node); } // Добавить node в хвост списка Some(old_rear) => { old_rear.borrow_mut().next = Some(node.clone()); node.borrow_mut().prev = Some(old_rear); self.rear = Some(node); // Обновить хвостовой узел } } } self.que_size += 1; // Обновить длину очереди } /* Добавление в голову очереди */ pub fn push_first(&mut self, num: T) { self.push(num, true); } /* Добавление в хвост очереди */ pub fn push_last(&mut self, num: T) { self.push(num, false); } /* Операция извлечения из очереди */ fn pop(&mut self, is_front: bool) -> Option { // Если очередь пуста, сразу вернуть None if self.is_empty() { return None; }; // Операция извлечения из головы очереди if is_front { self.front.take().map(|old_front| { match old_front.borrow_mut().next.take() { Some(new_front) => { new_front.borrow_mut().prev.take(); self.front = Some(new_front); // Обновить головной узел } None => { self.rear.take(); } } self.que_size -= 1; // Обновить длину очереди old_front.borrow().val }) } // Операция извлечения из хвоста очереди else { self.rear.take().map(|old_rear| { match old_rear.borrow_mut().prev.take() { Some(new_rear) => { new_rear.borrow_mut().next.take(); self.rear = Some(new_rear); // Обновить хвостовой узел } None => { self.front.take(); } } self.que_size -= 1; // Обновить длину очереди old_rear.borrow().val }) } } /* Извлечение из головы очереди */ pub fn pop_first(&mut self) -> Option { return self.pop(true); } /* Извлечение из хвоста очереди */ pub fn pop_last(&mut self) -> Option { return self.pop(false); } /* Доступ к элементу в начале очереди */ pub fn peek_first(&self) -> Option<&Rc>>> { self.front.as_ref() } /* Доступ к элементу в конце очереди */ pub fn peek_last(&self) -> Option<&Rc>>> { self.rear.as_ref() } /* Вернуть массив для вывода */ pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { let mut res: Vec = Vec::new(); fn recur(cur: Option<&Rc>>>, res: &mut Vec) { if let Some(cur) = cur { res.push(cur.borrow().val); recur(cur.borrow().next.as_ref(), res); } } recur(head, &mut res); res } } /* Driver Code */ fn main() { /* Инициализация двусторонней очереди */ let mut deque = LinkedListDeque::new(); deque.push_last(3); deque.push_last(2); deque.push_last(5); print!("Двусторонняя очередь deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); /* Доступ к элементу */ let peek_first = deque.peek_first().unwrap().borrow().val; print!("\nПервый элемент peek_first = {}", peek_first); let peek_last = deque.peek_last().unwrap().borrow().val; print!("\nПоследний элемент peek_last = {}", peek_last); /* Добавление элемента в очередь */ deque.push_last(4); print!("\nПосле добавления элемента 4 в хвост deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); deque.push_first(1); print!("\nПосле добавления элемента 1 в голову deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); /* Извлечение элемента из очереди */ let pop_last = deque.pop_last().unwrap(); print!("\nИзвлеченный из хвоста элемент = {}, deque после извлечения из хвоста = ", pop_last); print_util::print_array(&deque.to_array(deque.peek_first())); let pop_first = deque.pop_first().unwrap(); print!("\nИзвлеченный из головы элемент = {}, deque после извлечения из головы = ", pop_first); print_util::print_array(&deque.to_array(deque.peek_first())); /* Получение длины двусторонней очереди */ let size = deque.size(); print!("\nДлина двусторонней очереди size = {}", size); /* Проверка, пуста ли двусторонняя очередь */ let is_empty = deque.is_empty(); print!("\nПуста ли двусторонняя очередь = {}", is_empty); } ================================================ FILE: ru/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs ================================================ /* * File: linkedlist_queue.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* Очередь на основе связного списка */ #[allow(dead_code)] pub struct LinkedListQueue { front: Option>>>, // Головной узел front rear: Option>>>, // Хвостовой узел rear que_size: usize, // Длина очереди } impl LinkedListQueue { pub fn new() -> Self { Self { front: None, rear: None, que_size: 0, } } /* Получение длины очереди */ pub fn size(&self) -> usize { return self.que_size; } /* Проверка, пуста ли очередь */ pub fn is_empty(&self) -> bool { return self.que_size == 0; } /* Поместить в очередь */ pub fn push(&mut self, num: T) { // Добавить num после хвостового узла let new_rear = ListNode::new(num); match self.rear.take() { // Если очередь не пуста, добавить этот узел после хвостового узла Some(old_rear) => { old_rear.borrow_mut().next = Some(new_rear.clone()); self.rear = Some(new_rear); } // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел None => { self.front = Some(new_rear.clone()); self.rear = Some(new_rear); } } self.que_size += 1; } /* Извлечь из очереди */ pub fn pop(&mut self) -> Option { self.front.take().map(|old_front| { match old_front.borrow_mut().next.take() { Some(new_front) => { self.front = Some(new_front); } None => { self.rear.take(); } } self.que_size -= 1; old_front.borrow().val }) } /* Доступ к элементу в начале очереди */ pub fn peek(&self) -> Option<&Rc>>> { self.front.as_ref() } /* Преобразовать связный список в Array и вернуть */ pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { let mut res: Vec = Vec::new(); fn recur(cur: Option<&Rc>>>, res: &mut Vec) { if let Some(cur) = cur { res.push(cur.borrow().val); recur(cur.borrow().next.as_ref(), res); } } recur(head, &mut res); res } } /* Driver Code */ fn main() { /* Инициализация очереди */ let mut queue = LinkedListQueue::new(); /* Добавление элемента в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print!("Очередь queue = "); print_util::print_array(&queue.to_array(queue.peek())); /* Доступ к элементу в начале очереди */ let peek = queue.peek().unwrap().borrow().val; print!("\nПервый элемент peek = {}", peek); /* Извлечение элемента из очереди */ let pop = queue.pop().unwrap(); print!("\nИзвлеченный элемент pop = {}, queue после извлечения = ", pop); print_util::print_array(&queue.to_array(queue.peek())); /* Получение длины очереди */ let size = queue.size(); print!("\nДлина очереди size = {}", size); /* Проверка, пуста ли очередь */ let is_empty = queue.is_empty(); print!("\nПуста ли очередь = {}", is_empty); } ================================================ FILE: ru/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs ================================================ /* * File: linkedlist_stack.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* Стек на основе связного списка */ #[allow(dead_code)] pub struct LinkedListStack { stack_peek: Option>>>, // Использовать головной узел как вершину стека stk_size: usize, // Длина стека } impl LinkedListStack { pub fn new() -> Self { Self { stack_peek: None, stk_size: 0, } } /* Получение длины стека */ pub fn size(&self) -> usize { return self.stk_size; } /* Проверка, пуст ли стек */ pub fn is_empty(&self) -> bool { return self.size() == 0; } /* Поместить в стек */ pub fn push(&mut self, num: T) { let node = ListNode::new(num); node.borrow_mut().next = self.stack_peek.take(); self.stack_peek = Some(node); self.stk_size += 1; } /* Извлечь из стека */ pub fn pop(&mut self) -> Option { self.stack_peek.take().map(|old_head| { self.stack_peek = old_head.borrow_mut().next.take(); self.stk_size -= 1; old_head.borrow().val }) } /* Доступ к верхнему элементу стека */ pub fn peek(&self) -> Option<&Rc>>> { self.stack_peek.as_ref() } /* Преобразовать List в Array и вернуть */ pub fn to_array(&self) -> Vec { fn _to_array(head: Option<&Rc>>>) -> Vec { if let Some(node) = head { let mut nums = _to_array(node.borrow().next.as_ref()); nums.push(node.borrow().val); return nums; } return Vec::new(); } _to_array(self.peek()) } } /* Driver Code */ fn main() { /* Инициализация стека */ let mut stack = LinkedListStack::new(); /* Помещение элемента в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("Стек stack = "); print_util::print_array(&stack.to_array()); /* Доступ к верхнему элементу стека */ let peek = stack.peek().unwrap().borrow().val; print!("\nВерхний элемент peek = {}", peek); /* Извлечение элемента из стека */ let pop = stack.pop().unwrap(); print!("\nИзвлеченный элемент pop = {}, stack после извлечения = ", pop); print_util::print_array(&stack.to_array()); /* Получение длины стека */ let size = stack.size(); print!("\nДлина стека size = {}", size); /* Проверка на пустоту */ let is_empty = stack.is_empty(); print!("\nПуст ли стек = {}", is_empty); } ================================================ FILE: ru/codes/rust/chapter_stack_and_queue/queue.rs ================================================ /* * File: queue.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; use std::collections::VecDeque; /* Driver Code */ pub fn main() { // Инициализация очереди let mut queue: VecDeque = VecDeque::new(); // Добавление элемента в очередь queue.push_back(1); queue.push_back(3); queue.push_back(2); queue.push_back(5); queue.push_back(4); print!("Очередь queue = "); print_util::print_queue(&queue); // Доступ к элементу в начале очереди let peek = queue.front().unwrap(); println!("\nПервый элемент peek = {peek}"); // Извлечение элемента из очереди let pop = queue.pop_front().unwrap(); print!("Извлеченный элемент pop = {pop}, queue после извлечения = "); print_util::print_queue(&queue); // Получение длины очереди let size = queue.len(); print!("\nДлина очереди size = {size}"); // Проверка, пуста ли очередь let is_empty = queue.is_empty(); print!("\nПуста ли очередь = {is_empty}"); } ================================================ FILE: ru/codes/rust/chapter_stack_and_queue/stack.rs ================================================ /* * File: stack.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Driver Code */ pub fn main() { // Инициализировать стек // В Rust рекомендуется использовать Vec как стек let mut stack: Vec = Vec::new(); // Помещение элемента в стек stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("Стек stack = "); print_util::print_array(&stack); // Доступ к верхнему элементу стека let peek = stack.last().unwrap(); print!("\nВерхний элемент peek = {peek}"); // Извлечение элемента из стека let pop = stack.pop().unwrap(); print!("\nИзвлеченный элемент pop = {pop}, stack после извлечения = "); print_util::print_array(&stack); // Получение длины стека let size = stack.len(); print!("\nДлина стека size = {size}"); // Проверка, пуст ли стек let is_empty = stack.is_empty(); print!("\nПуст ли стек = {is_empty}"); } ================================================ FILE: ru/codes/rust/chapter_tree/array_binary_tree.rs ================================================ /* * File: array_binary_tree.rs * Created Time: 2023-07-25 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::{print_util, tree_node}; /* Класс двоичного дерева в массивном представлении */ struct ArrayBinaryTree { tree: Vec>, } impl ArrayBinaryTree { /* Конструктор */ fn new(arr: Vec>) -> Self { Self { tree: arr } } /* Вместимость списка */ fn size(&self) -> i32 { self.tree.len() as i32 } /* Получить значение узла с индексом i */ fn val(&self, i: i32) -> Option { // Если индекс выходит за границы, вернуть None, обозначающий пустую позицию if i < 0 || i >= self.size() { None } else { self.tree[i as usize] } } /* Получить индекс левого дочернего узла узла с индексом i */ fn left(&self, i: i32) -> i32 { 2 * i + 1 } /* Получить индекс правого дочернего узла узла с индексом i */ fn right(&self, i: i32) -> i32 { 2 * i + 2 } /* Получить индекс родительского узла узла с индексом i */ fn parent(&self, i: i32) -> i32 { (i - 1) / 2 } /* Обход в ширину */ fn level_order(&self) -> Vec { self.tree.iter().filter_map(|&x| x).collect() } /* Обход в глубину */ fn dfs(&self, i: i32, order: &'static str, res: &mut Vec) { if self.val(i).is_none() { return; } let val = self.val(i).unwrap(); // Предварительный обход if order == "pre" { res.push(val); } self.dfs(self.left(i), order, res); // Симметричный обход if order == "in" { res.push(val); } self.dfs(self.right(i), order, res); // Обратный обход if order == "post" { res.push(val); } } /* Предварительный обход */ fn pre_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "pre", &mut res); res } /* Симметричный обход */ fn in_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "in", &mut res); res } /* Обратный обход */ fn post_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "post", &mut res); res } } /* Driver Code */ fn main() { // Инициализировать двоичное дерево // Здесь используется функция, напрямую строящая двоичное дерево из массива let arr = vec![ Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15), ]; let root = tree_node::vec_to_tree(arr.clone()).unwrap(); println!("\nИнициализация двоичного дерева\n"); println!("Массивное представление двоичного дерева:"); println!( "[{}]", arr.iter() .map(|&val| if let Some(val) = val { format!("{val}") } else { "null".to_string() }) .collect::>() .join(", ") ); println!("Связное представление двоичного дерева:"); print_util::print_tree(&root); // Класс двоичного дерева в массивном представлении let abt = ArrayBinaryTree::new(arr); // Доступ к узлу let i = 1; let l = abt.left(i); let r = abt.right(i); let p = abt.parent(i); println!( "\nТекущий индекс узла = {}, значение = {}",\ni,\nif let Some(val) = abt.val(i) {\n format!("{val}")\n} else {\n "null".to_string()\n} ); println!( "Индекс левого дочернего узла = {}, значение = {}", l, if let Some(val) = abt.val(l) { format!("{val}") } else { "null".to_string() } ); println!( "Индекс правого дочернего узла = {}, значение = {}", r, if let Some(val) = abt.val(r) { format!("{val}") } else { "null".to_string() } ); println!( "Индекс родительского узла = {}, значение = {}", p, if let Some(val) = abt.val(p) { format!("{val}") } else { "null".to_string() } ); // Обходить дерево let mut res = abt.level_order(); println!("\nОбход в ширину: {:?}", res); res = abt.pre_order(); println!("Предварительный обход: {:?}", res); res = abt.in_order(); println!("Симметричный обход: {:?}", res); res = abt.post_order(); println!("Обратный обход: {:?}", res); } ================================================ FILE: ru/codes/rust/chapter_tree/avl_tree.rs ================================================ /* * File: avl_tree.rs * Created Time: 2023-07-14 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::{print_util, TreeNode}; use std::cell::RefCell; use std::cmp::Ordering; use std::rc::Rc; type OptionTreeNodeRc = Option>>; /* AVL-дерево */ struct AVLTree { root: OptionTreeNodeRc, // Корневой узел } impl AVLTree { /* Конструктор */ fn new() -> Self { Self { root: None } } /* Получить высоту узла */ fn height(node: OptionTreeNodeRc) -> i32 { // Высота пустого узла равна -1, высота листового узла равна 0 match node { Some(node) => node.borrow().height, None => -1, } } /* Обновить высоту узла */ fn update_height(node: OptionTreeNodeRc) { if let Some(node) = node { let left = node.borrow().left.clone(); let right = node.borrow().right.clone(); // Высота узла равна высоте более высокого поддерева + 1 node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1; } } /* Получить коэффициент баланса */ fn balance_factor(node: OptionTreeNodeRc) -> i32 { match node { // Коэффициент баланса пустого узла равен 0 None => 0, // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева Some(node) => { Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone()) } } } /* Операция правого вращения */ fn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { match node { Some(node) => { let child = node.borrow().left.clone().unwrap(); let grand_child = child.borrow().right.clone(); // Выполнить правое вращение узла node вокруг child child.borrow_mut().right = Some(node.clone()); node.borrow_mut().left = grand_child; // Обновить высоту узла Self::update_height(Some(node)); Self::update_height(Some(child.clone())); // Вернуть корневой узел поддерева после вращения Some(child) } None => None, } } /* Операция левого вращения */ fn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { match node { Some(node) => { let child = node.borrow().right.clone().unwrap(); let grand_child = child.borrow().left.clone(); // Выполнить левое вращение узла node вокруг child child.borrow_mut().left = Some(node.clone()); node.borrow_mut().right = grand_child; // Обновить высоту узла Self::update_height(Some(node)); Self::update_height(Some(child.clone())); // Вернуть корневой узел поддерева после вращения Some(child) } None => None, } } /* Выполнить вращение, чтобы снова сбалансировать поддерево */ fn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { // Получить коэффициент баланса узла node let balance_factor = Self::balance_factor(node.clone()); // Левосторонне перекошенное дерево if balance_factor > 1 { let node = node.unwrap(); if Self::balance_factor(node.borrow().left.clone()) >= 0 { // Правое вращение Self::right_rotate(Some(node)) } else { // Сначала левое вращение, затем правое let left = node.borrow().left.clone(); node.borrow_mut().left = Self::left_rotate(left); Self::right_rotate(Some(node)) } } // Правосторонне перекошенное дерево else if balance_factor < -1 { let node = node.unwrap(); if Self::balance_factor(node.borrow().right.clone()) <= 0 { // Левое вращение Self::left_rotate(Some(node)) } else { // Сначала правое вращение, затем левое let right = node.borrow().right.clone(); node.borrow_mut().right = Self::right_rotate(right); Self::left_rotate(Some(node)) } } else { // Дерево сбалансировано, вращение не требуется, вернуть сразу node } } /* Вставка узла */ fn insert(&mut self, val: i32) { self.root = Self::insert_helper(self.root.clone(), val); } /* Рекурсивная вставка узла (вспомогательный метод) */ fn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { match node { Some(mut node) => { /* 1. Найти позицию вставки и вставить узел */ match { let node_val = node.borrow().val; node_val } .cmp(&val) { Ordering::Greater => { let left = node.borrow().left.clone(); node.borrow_mut().left = Self::insert_helper(left, val); } Ordering::Less => { let right = node.borrow().right.clone(); node.borrow_mut().right = Self::insert_helper(right, val); } Ordering::Equal => { return Some(node); // Повторяющийся узел не вставлять, сразу вернуть } } Self::update_height(Some(node.clone())); // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = Self::rotate(Some(node)).unwrap(); // Вернуть корневой узел поддерева Some(node) } None => Some(TreeNode::new(val)), } } /* Удаление узла */ fn remove(&self, val: i32) { Self::remove_helper(self.root.clone(), val); } /* Рекурсивное удаление узла (вспомогательный метод) */ fn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { match node { Some(mut node) => { /* 1. Найти узел и удалить его */ if val < node.borrow().val { let left = node.borrow().left.clone(); node.borrow_mut().left = Self::remove_helper(left, val); } else if val > node.borrow().val { let right = node.borrow().right.clone(); node.borrow_mut().right = Self::remove_helper(right, val); } else if node.borrow().left.is_none() || node.borrow().right.is_none() { let child = if node.borrow().left.is_some() { node.borrow().left.clone() } else { node.borrow().right.clone() }; match child { // Число дочерних узлов = 0, удалить node и сразу вернуть None => { return None; } // Число дочерних узлов = 1, удалить node напрямую Some(child) => node = child, } } else { // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел let mut temp = node.borrow().right.clone().unwrap(); loop { let temp_left = temp.borrow().left.clone(); if temp_left.is_none() { break; } temp = temp_left.unwrap(); } let right = node.borrow().right.clone(); node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val); node.borrow_mut().val = temp.borrow().val; } Self::update_height(Some(node.clone())); // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = Self::rotate(Some(node)).unwrap(); // Вернуть корневой узел поддерева Some(node) } None => None, } } /* Поиск узла */ fn search(&self, val: i32) -> OptionTreeNodeRc { let mut cur = self.root.clone(); // Искать в цикле и выйти после прохода за листовой узел while let Some(current) = cur.clone() { match current.borrow().val.cmp(&val) { // Целевой узел находится в правом поддереве cur Ordering::Less => { cur = current.borrow().right.clone(); } // Целевой узел находится в левом поддереве cur Ordering::Greater => { cur = current.borrow().left.clone(); } // Найти целевой узел и выйти из цикла Ordering::Equal => { break; } } } // Вернуть целевой узел cur } } /* Driver Code */ fn main() { fn test_insert(tree: &mut AVLTree, val: i32) { tree.insert(val); println!("\nПосле вставки узла {} AVL-дерево имеет вид", val); print_util::print_tree(&tree.root.clone().unwrap()); } fn test_remove(tree: &mut AVLTree, val: i32) { tree.remove(val); println!("\nПосле удаления узла {} AVL-дерево имеет вид", val); print_util::print_tree(&tree.root.clone().unwrap()); } /* Инициализация пустого AVL-дерева */ let mut avl_tree = AVLTree::new(); /* Вставка узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла test_insert(&mut avl_tree, 1); test_insert(&mut avl_tree, 2); test_insert(&mut avl_tree, 3); test_insert(&mut avl_tree, 4); test_insert(&mut avl_tree, 5); test_insert(&mut avl_tree, 8); test_insert(&mut avl_tree, 7); test_insert(&mut avl_tree, 9); test_insert(&mut avl_tree, 10); test_insert(&mut avl_tree, 6); /* Вставка повторяющегося узла */ test_insert(&mut avl_tree, 7); /* Удаление узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла test_remove(&mut avl_tree, 8); // Удаление узла степени 0 test_remove(&mut avl_tree, 5); // Удаление узла степени 1 test_remove(&mut avl_tree, 4); // Удаление узла степени 2 /* Поиск узла */ let node = avl_tree.search(7); if let Some(node) = node { println!( "\nНайденный объект узла = {:?}, значение узла = {}",\n&*node.borrow(),\nnode.borrow().val ); } } ================================================ FILE: ru/codes/rust/chapter_tree/binary_search_tree.rs ================================================ /* * File: binary_search_tree.rs * Created Time: 2023-04-20 * Author: xBLACKICEx (xBLACKICE@outlook.com)、night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::cell::RefCell; use std::cmp::Ordering; use std::rc::Rc; use hello_algo_rust::include::TreeNode; type OptionTreeNodeRc = Option>>; /* Двоичное дерево поиска */ pub struct BinarySearchTree { root: OptionTreeNodeRc, } impl BinarySearchTree { /* Конструктор */ pub fn new() -> Self { // Инициализировать пустое дерево Self { root: None } } /* Получить корневой узел двоичного дерева */ pub fn get_root(&self) -> OptionTreeNodeRc { self.root.clone() } /* Поиск узла */ pub fn search(&self, num: i32) -> OptionTreeNodeRc { let mut cur = self.root.clone(); // Искать в цикле и выйти после прохода за листовой узел while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // Целевой узел находится в правом поддереве cur Ordering::Greater => cur = node.borrow().right.clone(), // Целевой узел находится в левом поддереве cur Ordering::Less => cur = node.borrow().left.clone(), // Найти целевой узел и выйти из цикла Ordering::Equal => break, } } // Вернуть целевой узел cur } /* Вставка узла */ pub fn insert(&mut self, num: i32) { // Если дерево пусто, инициализировать корневой узел if self.root.is_none() { self.root = Some(TreeNode::new(num)); return; } let mut cur = self.root.clone(); let mut pre = None; // Искать в цикле и выйти после прохода за листовой узел while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // Найти повторяющийся узел и сразу вернуть Ordering::Equal => return, // Позиция вставки находится в правом поддереве cur Ordering::Greater => { pre = cur.clone(); cur = node.borrow().right.clone(); } // Позиция вставки находится в левом поддереве cur Ordering::Less => { pre = cur.clone(); cur = node.borrow().left.clone(); } } } // Вставка узла let pre = pre.unwrap(); let node = Some(TreeNode::new(num)); if num > pre.borrow().val { pre.borrow_mut().right = node; } else { pre.borrow_mut().left = node; } } /* Удаление узла */ pub fn remove(&mut self, num: i32) { // Если дерево пусто, сразу вернуть if self.root.is_none() { return; } let mut cur = self.root.clone(); let mut pre = None; // Искать в цикле и выйти после прохода за листовой узел while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // Найти узел для удаления и выйти из цикла Ordering::Equal => break, // Узел для удаления находится в правом поддереве cur Ordering::Greater => { pre = cur.clone(); cur = node.borrow().right.clone(); } // Узел для удаления находится в левом поддереве cur Ordering::Less => { pre = cur.clone(); cur = node.borrow().left.clone(); } } } // Если узел для удаления отсутствует, сразу вернуть if cur.is_none() { return; } let cur = cur.unwrap(); let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone()); match (left_child.clone(), right_child.clone()) { // Число дочерних узлов = 0 или 1 (None, None) | (Some(_), None) | (None, Some(_)) => { // Когда число дочерних узлов = 0 / 1, child = nullptr / этот дочерний узел let child = left_child.or(right_child); let pre = pre.unwrap(); // Удалить узел cur if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) { let left = pre.borrow().left.clone(); if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) { pre.borrow_mut().left = child; } else { pre.borrow_mut().right = child; } } else { // Если удаляемый узел является корнем, заново назначить корневой узел self.root = child; } } // Число дочерних узлов = 2 (Some(_), Some(_)) => { // Получить следующий узел после cur в симметричном обходе let mut tmp = cur.borrow().right.clone(); while let Some(node) = tmp.clone() { if node.borrow().left.is_some() { tmp = node.borrow().left.clone(); } else { break; } } let tmp_val = tmp.unwrap().borrow().val; // Рекурсивно удалить узел tmp self.remove(tmp_val); // Перезаписать cur значением tmp cur.borrow_mut().val = tmp_val; } } } } /* Driver Code */ fn main() { /* Инициализация двоичного дерева поиска */ let mut bst = BinarySearchTree::new(); // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for &num in &nums { bst.insert(num); } println!("\nИсходное двоичное дерево\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); /* Найти узел */ let node = bst.search(7); println!( "\nНайденный объект узла = {:?}, значение узла = {}",\nnode.clone().unwrap(),\nnode.clone().unwrap().borrow().val ); /* Вставка узла */ bst.insert(16); println!("\nПосле вставки узла 16 двоичное дерево имеет вид\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); /* Удаление узла */ bst.remove(1); println!("\nПосле удаления узла 1 двоичное дерево имеет вид\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); bst.remove(2); println!("\nПосле удаления узла 2 двоичное дерево имеет вид\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); bst.remove(4); println!("\nПосле удаления узла 4 двоичное дерево имеет вид\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); } ================================================ FILE: ru/codes/rust/chapter_tree/binary_tree.rs ================================================ /** * File: binary_tree.rs * Created Time: 2023-02-27 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use std::rc::Rc; use hello_algo_rust::include::{print_util, TreeNode}; /* Driver Code */ fn main() { /* Инициализация двоичного дерева */ // Инициализация узла let n1 = TreeNode::new(1); let n2 = TreeNode::new(2); let n3 = TreeNode::new(3); let n4 = TreeNode::new(4); let n5 = TreeNode::new(5); // Построить связи между узлами (указатели) n1.borrow_mut().left = Some(Rc::clone(&n2)); n1.borrow_mut().right = Some(Rc::clone(&n3)); n2.borrow_mut().left = Some(Rc::clone(&n4)); n2.borrow_mut().right = Some(Rc::clone(&n5)); println!("\nИнициализация двоичного дерева\n"); print_util::print_tree(&n1); // Вставка и удаление узлов let p = TreeNode::new(0); // Вставить узел P между n1 -> n2 p.borrow_mut().left = Some(Rc::clone(&n2)); n1.borrow_mut().left = Some(Rc::clone(&p)); println!("\nПосле вставки узла P\n"); print_util::print_tree(&n1); // Удалить узел P drop(p); n1.borrow_mut().left = Some(Rc::clone(&n2)); println!("\nПосле удаления узла P\n"); print_util::print_tree(&n1); } ================================================ FILE: ru/codes/rust/chapter_tree/binary_tree_bfs.rs ================================================ /* * File: binary_tree_bfs.rs * Created Time: 2023-04-07 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use hello_algo_rust::op_vec; use std::collections::VecDeque; use std::{cell::RefCell, rc::Rc}; /* Обход в ширину */ fn level_order(root: &Rc>) -> Vec { // Инициализировать очередь и добавить корневой узел let mut que = VecDeque::new(); que.push_back(root.clone()); // Инициализировать список для хранения последовательности обхода let mut vec = Vec::new(); while let Some(node) = que.pop_front() { // Извлечение из очереди vec.push(node.borrow().val); // Сохранить значение узла if let Some(left) = node.borrow().left.as_ref() { que.push_back(left.clone()); // Поместить левый дочерний узел в очередь } if let Some(right) = node.borrow().right.as_ref() { que.push_back(right.clone()); // Поместить правый дочерний узел в очередь }; } vec } /* Driver Code */ fn main() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]).unwrap(); println!("Инициализация двоичного дерева\n"); print_util::print_tree(&root); /* Обход в ширину */ let vec = level_order(&root); print!("\nПоследовательность печати узлов при обходе в ширину = {:?}", vec); } ================================================ FILE: ru/codes/rust/chapter_tree/binary_tree_dfs.rs ================================================ /* * File: binary_tree_dfs.rs * Created Time: 2023-04-06 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use hello_algo_rust::op_vec; use std::cell::RefCell; use std::rc::Rc; /* Предварительный обход */ fn pre_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // Порядок обхода: корень -> левое поддерево -> правое поддерево let node = node.borrow(); res.push(node.val); dfs(node.left.as_ref(), res); dfs(node.right.as_ref(), res); } } dfs(root, &mut result); result } /* Симметричный обход */ fn in_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // Порядок обхода: левое поддерево -> корень -> правое поддерево let node = node.borrow(); dfs(node.left.as_ref(), res); res.push(node.val); dfs(node.right.as_ref(), res); } } dfs(root, &mut result); result } /* Обратный обход */ fn post_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // Порядок обхода: левое поддерево -> правое поддерево -> корень let node = node.borrow(); dfs(node.left.as_ref(), res); dfs(node.right.as_ref(), res); res.push(node.val); } } dfs(root, &mut result); result } /* Driver Code */ fn main() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]); println!("Инициализация двоичного дерева\n"); print_util::print_tree(root.as_ref().unwrap()); /* Предварительный обход */ let vec = pre_order(root.as_ref()); println!("\nПоследовательность печати узлов при предварительном обходе = {:?}", vec); /* Симметричный обход */ let vec = in_order(root.as_ref()); println!("\nПоследовательность печати узлов при симметричном обходе = {:?}", vec); /* Обратный обход */ let vec = post_order(root.as_ref()); print!("\nПоследовательность печати узлов при обратном обходе = {:?}", vec); } ================================================ FILE: ru/codes/rust/src/include/list_node.rs ================================================ /* * File: list_node.rs * Created Time: 2023-03-05 * Author: codingonion (coderonion@gmail.com), rongyi (hiarongyi@gmail.com) */ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; #[derive(Debug)] pub struct ListNode { pub val: T, pub next: Option>>>, } impl ListNode { pub fn new(val: T) -> Rc>> { Rc::new(RefCell::new(ListNode { val, next: None })) } /* Десериализовать массив в связный список */ pub fn arr_to_linked_list(array: &[T]) -> Option>>> where T: Copy + Clone, { let mut head = None; // insert in reverse order for item in array.iter().rev() { let node = Rc::new(RefCell::new(ListNode { val: *item, next: head.take(), })); head = Some(node); } head } /* Преобразовать связный список в хеш-таблицу */ pub fn linked_list_to_hashmap( linked_list: Option>>>, ) -> HashMap>>> where T: std::hash::Hash + Eq + Copy + Clone, { let mut hashmap = HashMap::new(); let mut node = linked_list; while let Some(cur) = node { let borrow = cur.borrow(); hashmap.insert(borrow.val.clone(), cur.clone()); node = borrow.next.clone(); } hashmap } } ================================================ FILE: ru/codes/rust/src/include/mod.rs ================================================ /* * File: include.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICE@outlook.com) */ pub mod list_node; pub mod print_util; pub mod tree_node; pub mod vertex; // rexport to include pub use list_node::*; pub use print_util::*; pub use tree_node::*; pub use vertex::*; ================================================ FILE: ru/codes/rust/src/include/print_util.rs ================================================ /* * File: print_util.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use std::cell::{Cell, RefCell}; use std::fmt::Display; use std::collections::{HashMap, VecDeque}; use std::rc::Rc; use super::list_node::ListNode; use super::tree_node::{TreeNode, vec_to_tree}; struct Trunk<'a, 'b> { prev: Option<&'a Trunk<'a, 'b>>, str: Cell<&'b str>, } /* Вывести массив */ pub fn print_array(nums: &[T]) { print!("["); if nums.len() > 0 { for (i, num) in nums.iter().enumerate() { print!("{}{}", num, if i == nums.len() - 1 {"]"} else {", "} ); } } else { print!("]"); } } /* Вывести хеш-таблицу */ pub fn print_hash_map(map: &HashMap) { for (key, value) in map { println!("{key} -> {value}"); } } /* Вывести очередь (двустороннюю очередь) */ pub fn print_queue(queue: &VecDeque) { print!("["); let iter = queue.iter(); for (i, data) in iter.enumerate() { print!("{}{}", data, if i == queue.len() - 1 {"]"} else {", "} ); } } /* Вывести связный список */ pub fn print_linked_list(head: &Rc>>) { print!("{}{}", head.borrow().val, if head.borrow().next.is_none() {"\n"} else {" -> "}); if let Some(node) = &head.borrow().next { return print_linked_list(node); } } /* Вывести двоичное дерево */ pub fn print_tree(root: &Rc>) { _print_tree(Some(root), None, false); } /* Вывести двоичное дерево */ fn _print_tree(root: Option<&Rc>>, prev: Option<&Trunk>, is_right: bool) { if let Some(node) = root { let mut prev_str = " "; let trunk = Trunk { prev, str: Cell::new(prev_str) }; _print_tree(node.borrow().right.as_ref(), Some(&trunk), true); if prev.is_none() { trunk.str.set("———"); } else if is_right { trunk.str.set("/———"); prev_str = " |"; } else { trunk.str.set("\\———"); prev.as_ref().unwrap().str.set(prev_str); } show_trunks(Some(&trunk)); println!(" {}", node.borrow().val); if let Some(prev) = prev { prev.str.set(prev_str); } trunk.str.set(" |"); _print_tree(node.borrow().left.as_ref(), Some(&trunk), false); } } fn show_trunks(trunk: Option<&Trunk>) { if let Some(trunk) = trunk { show_trunks(trunk.prev); print!("{}", trunk.str.get()); } } /* Вывести кучу */ pub fn print_heap(heap: Vec) { println!("Массивное представление кучи: {:?}", heap); println!("Древовидное представление кучи:"); if let Some(root) = vec_to_tree(heap.into_iter().map(|val| Some(val)).collect()) { print_tree(&root); } } ================================================ FILE: ru/codes/rust/src/include/tree_node.rs ================================================ /* * File: tree_node.rs * Created Time: 2023-02-27 * Author: xBLACKICEx (xBLACKICE@outlook.com), night-cruise (2586447362@qq.com) */ use std::cell::RefCell; use std::rc::Rc; /* Тип узла двоичного дерева */ #[derive(Debug)] pub struct TreeNode { pub val: i32, pub height: i32, pub parent: Option>>, pub left: Option>>, pub right: Option>>, } impl TreeNode { /* Конструктор */ pub fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, height: 0, parent: None, left: None, right: None, })) } } #[macro_export] macro_rules! op_vec { ( $( $x:expr ),* ) => { vec![ $(Option::from($x)),* ] }; } // Правила кодирования сериализации см.: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Массивное представление двоичного дерева: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // Связное представление двоичного дерева: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* Десериализовать список в двоичное дерево: рекурсия */ fn vec_to_tree_dfs(arr: &[Option], i: usize) -> Option>> { if i >= arr.len() || arr[i].is_none() { return None; } let root = TreeNode::new(arr[i].unwrap()); root.borrow_mut().left = vec_to_tree_dfs(arr, 2 * i + 1); root.borrow_mut().right = vec_to_tree_dfs(arr, 2 * i + 2); Some(root) } /* Десериализовать список в двоичное дерево */ pub fn vec_to_tree(arr: Vec>) -> Option>> { vec_to_tree_dfs(&arr, 0) } /* Сериализовать двоичное дерево в список: рекурсия */ fn tree_to_vec_dfs(root: Option<&Rc>>, i: usize, res: &mut Vec>) { if let Some(root) = root { // i + 1 is the minimum valid size to access index i while res.len() < i + 1 { res.push(None); } res[i] = Some(root.borrow().val); tree_to_vec_dfs(root.borrow().left.as_ref(), 2 * i + 1, res); tree_to_vec_dfs(root.borrow().right.as_ref(), 2 * i + 2, res); } } /* Сериализовать двоичное дерево в список */ pub fn tree_to_vec(root: Option>>) -> Vec> { let mut res = vec![]; tree_to_vec_dfs(root.as_ref(), 0, &mut res); res } ================================================ FILE: ru/codes/rust/src/include/vertex.rs ================================================ /* * File: vertex.rs * Created Time: 2023-07-13 * Author: night-cruise (2586447362@qq.com) */ /* Тип вершины */ #[derive(Copy, Clone, Hash, PartialEq, Eq)] pub struct Vertex { pub val: i32, } impl From for Vertex { fn from(value: i32) -> Self { Self { val: value } } } /* На вход подается список значений vals, на выходе возвращается список вершин vets */ pub fn vals_to_vets(vals: Vec) -> Vec { vals.into_iter().map(|val| val.into()).collect() } /* На вход подается список вершин vets, на выходе возвращается список значений vals */ pub fn vets_to_vals(vets: Vec) -> Vec { vets.into_iter().map(|vet| vet.val).collect() } ================================================ FILE: ru/codes/rust/src/lib.rs ================================================ pub mod include; ================================================ FILE: ru/codes/swift/.gitignore ================================================ # Created by https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager # Edit at https://www.toptal.com/developers/gitignore?templates=objective-c,swift,swiftpackagemanager ### Objective-C ### # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## User settings xcuserdata/ ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint *.xccheckout ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ DerivedData/ *.moved-aside *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 ## Obj-C/Swift specific *.hmap ## App packaging *.ipa *.dSYM.zip *.dSYM # CocoaPods # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build/ # fastlane # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output # Code Injection # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ ### Objective-C Patch ### ### Swift ### # Xcode # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins # Package.resolved # *.xcodeproj # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project # .swiftpm .build/ # CocoaPods # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts # Accio dependency management Dependencies/ .accio/ # fastlane # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control # Code Injection # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode ### SwiftPackageManager ### Packages xcuserdata *.xcodeproj # End of https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager ================================================ FILE: ru/codes/swift/Package.resolved ================================================ { "pins" : [ { "identity" : "swift-collections", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { "branch" : "release/1.1", "revision" : "4a1d92ba85027010d2c528c05576cde9a362254b" } } ], "version" : 2 } ================================================ FILE: ru/codes/swift/Package.swift ================================================ // swift-tools-version: 5.7 import PackageDescription let package = Package( name: "HelloAlgo", products: [ // chapter_computational_complexity .executable(name: "iteration", targets: ["iteration"]), .executable(name: "recursion", targets: ["recursion"]), .executable(name: "time_complexity", targets: ["time_complexity"]), .executable(name: "worst_best_time_complexity", targets: ["worst_best_time_complexity"]), .executable(name: "space_complexity", targets: ["space_complexity"]), // chapter_array_and_linkedlist .executable(name: "array", targets: ["array"]), .executable(name: "linked_list", targets: ["linked_list"]), .executable(name: "list", targets: ["list"]), .executable(name: "my_list", targets: ["my_list"]), // chapter_stack_and_queue .executable(name: "stack", targets: ["stack"]), .executable(name: "linkedlist_stack", targets: ["linkedlist_stack"]), .executable(name: "array_stack", targets: ["array_stack"]), .executable(name: "queue", targets: ["queue"]), .executable(name: "linkedlist_queue", targets: ["linkedlist_queue"]), .executable(name: "array_queue", targets: ["array_queue"]), .executable(name: "deque", targets: ["deque"]), .executable(name: "linkedlist_deque", targets: ["linkedlist_deque"]), .executable(name: "array_deque", targets: ["array_deque"]), // chapter_hashing .executable(name: "hash_map", targets: ["hash_map"]), .executable(name: "array_hash_map", targets: ["array_hash_map"]), .executable(name: "hash_map_chaining", targets: ["hash_map_chaining"]), .executable(name: "hash_map_open_addressing", targets: ["hash_map_open_addressing"]), .executable(name: "simple_hash", targets: ["simple_hash"]), .executable(name: "built_in_hash", targets: ["built_in_hash"]), // chapter_tree .executable(name: "binary_tree", targets: ["binary_tree"]), .executable(name: "binary_tree_bfs", targets: ["binary_tree_bfs"]), .executable(name: "binary_tree_dfs", targets: ["binary_tree_dfs"]), .executable(name: "array_binary_tree", targets: ["array_binary_tree"]), .executable(name: "binary_search_tree", targets: ["binary_search_tree"]), .executable(name: "avl_tree", targets: ["avl_tree"]), // chapter_heap .executable(name: "heap", targets: ["heap"]), .executable(name: "my_heap", targets: ["my_heap"]), .executable(name: "top_k", targets: ["top_k"]), // chapter_graph .executable(name: "graph_adjacency_matrix", targets: ["graph_adjacency_matrix"]), .executable(name: "graph_adjacency_list", targets: ["graph_adjacency_list"]), .executable(name: "graph_bfs", targets: ["graph_bfs"]), .executable(name: "graph_dfs", targets: ["graph_dfs"]), // chapter_searching .executable(name: "binary_search", targets: ["binary_search"]), .executable(name: "binary_search_insertion", targets: ["binary_search_insertion"]), .executable(name: "binary_search_edge", targets: ["binary_search_edge"]), .executable(name: "two_sum", targets: ["two_sum"]), .executable(name: "linear_search", targets: ["linear_search"]), .executable(name: "hashing_search", targets: ["hashing_search"]), // chapter_sorting .executable(name: "selection_sort", targets: ["selection_sort"]), .executable(name: "bubble_sort", targets: ["bubble_sort"]), .executable(name: "insertion_sort", targets: ["insertion_sort"]), .executable(name: "quick_sort", targets: ["quick_sort"]), .executable(name: "merge_sort", targets: ["merge_sort"]), .executable(name: "heap_sort", targets: ["heap_sort"]), .executable(name: "bucket_sort", targets: ["bucket_sort"]), .executable(name: "counting_sort", targets: ["counting_sort"]), .executable(name: "radix_sort", targets: ["radix_sort"]), // chapter_divide_and_conquer .executable(name: "binary_search_recur", targets: ["binary_search_recur"]), .executable(name: "build_tree", targets: ["build_tree"]), .executable(name: "hanota", targets: ["hanota"]), // chapter_backtracking .executable(name: "preorder_traversal_i_compact", targets: ["preorder_traversal_i_compact"]), .executable(name: "preorder_traversal_ii_compact", targets: ["preorder_traversal_ii_compact"]), .executable(name: "preorder_traversal_iii_compact", targets: ["preorder_traversal_iii_compact"]), .executable(name: "preorder_traversal_iii_template", targets: ["preorder_traversal_iii_template"]), .executable(name: "permutations_i", targets: ["permutations_i"]), .executable(name: "permutations_ii", targets: ["permutations_ii"]), .executable(name: "subset_sum_i_naive", targets: ["subset_sum_i_naive"]), .executable(name: "subset_sum_i", targets: ["subset_sum_i"]), .executable(name: "subset_sum_ii", targets: ["subset_sum_ii"]), .executable(name: "n_queens", targets: ["n_queens"]), // chapter_dynamic_programming .executable(name: "climbing_stairs_backtrack", targets: ["climbing_stairs_backtrack"]), .executable(name: "climbing_stairs_dfs", targets: ["climbing_stairs_dfs"]), .executable(name: "climbing_stairs_dfs_mem", targets: ["climbing_stairs_dfs_mem"]), .executable(name: "climbing_stairs_dp", targets: ["climbing_stairs_dp"]), .executable(name: "min_cost_climbing_stairs_dp", targets: ["min_cost_climbing_stairs_dp"]), .executable(name: "climbing_stairs_constraint_dp", targets: ["climbing_stairs_constraint_dp"]), .executable(name: "min_path_sum", targets: ["min_path_sum"]), .executable(name: "knapsack", targets: ["knapsack"]), .executable(name: "unbounded_knapsack", targets: ["unbounded_knapsack"]), .executable(name: "coin_change", targets: ["coin_change"]), .executable(name: "coin_change_ii", targets: ["coin_change_ii"]), .executable(name: "edit_distance", targets: ["edit_distance"]), // chapter_greedy .executable(name: "coin_change_greedy", targets: ["coin_change_greedy"]), .executable(name: "fractional_knapsack", targets: ["fractional_knapsack"]), .executable(name: "max_capacity", targets: ["max_capacity"]), .executable(name: "max_product_cutting", targets: ["max_product_cutting"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-collections", branch: "release/1.1"), ], targets: [ // helper .target(name: "utils", path: "utils"), .target(name: "graph_adjacency_list_target", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list_target.swift"], swiftSettings: [.define("TARGET")]), .target(name: "binary_search_insertion_target", path: "chapter_searching", sources: ["binary_search_insertion_target.swift"], swiftSettings: [.define("TARGET")]), // chapter_computational_complexity .executableTarget(name: "iteration", path: "chapter_computational_complexity", sources: ["iteration.swift"]), .executableTarget(name: "recursion", path: "chapter_computational_complexity", sources: ["recursion.swift"]), .executableTarget(name: "time_complexity", path: "chapter_computational_complexity", sources: ["time_complexity.swift"]), .executableTarget(name: "worst_best_time_complexity", path: "chapter_computational_complexity", sources: ["worst_best_time_complexity.swift"]), .executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]), // chapter_array_and_linkedlist .executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]), .executableTarget(name: "linked_list", dependencies: ["utils"], path: "chapter_array_and_linkedlist", sources: ["linked_list.swift"]), .executableTarget(name: "list", path: "chapter_array_and_linkedlist", sources: ["list.swift"]), .executableTarget(name: "my_list", path: "chapter_array_and_linkedlist", sources: ["my_list.swift"]), // chapter_stack_and_queue .executableTarget(name: "stack", path: "chapter_stack_and_queue", sources: ["stack.swift"]), .executableTarget(name: "linkedlist_stack", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_stack.swift"]), .executableTarget(name: "array_stack", path: "chapter_stack_and_queue", sources: ["array_stack.swift"]), .executableTarget(name: "queue", path: "chapter_stack_and_queue", sources: ["queue.swift"]), .executableTarget(name: "linkedlist_queue", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_queue.swift"]), .executableTarget(name: "array_queue", path: "chapter_stack_and_queue", sources: ["array_queue.swift"]), .executableTarget(name: "deque", path: "chapter_stack_and_queue", sources: ["deque.swift"]), .executableTarget(name: "linkedlist_deque", path: "chapter_stack_and_queue", sources: ["linkedlist_deque.swift"]), .executableTarget(name: "array_deque", path: "chapter_stack_and_queue", sources: ["array_deque.swift"]), // chapter_hashing .executableTarget(name: "hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map.swift"]), .executableTarget(name: "array_hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["array_hash_map.swift"]), .executableTarget(name: "hash_map_chaining", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_chaining.swift"]), .executableTarget(name: "hash_map_open_addressing", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_open_addressing.swift"]), .executableTarget(name: "simple_hash", path: "chapter_hashing", sources: ["simple_hash.swift"]), .executableTarget(name: "built_in_hash", dependencies: ["utils"], path: "chapter_hashing", sources: ["built_in_hash.swift"]), // chapter_tree .executableTarget(name: "binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree.swift"]), .executableTarget(name: "binary_tree_bfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_bfs.swift"]), .executableTarget(name: "binary_tree_dfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_dfs.swift"]), .executableTarget(name: "array_binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["array_binary_tree.swift"]), .executableTarget(name: "binary_search_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_search_tree.swift"]), .executableTarget(name: "avl_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["avl_tree.swift"]), // chapter_heap .executableTarget(name: "heap", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["heap.swift"]), .executableTarget(name: "my_heap", dependencies: ["utils"], path: "chapter_heap", sources: ["my_heap.swift"]), .executableTarget(name: "top_k", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["top_k.swift"]), // chapter_graph .executableTarget(name: "graph_adjacency_matrix", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_matrix.swift"]), .executableTarget(name: "graph_adjacency_list", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list.swift"]), .executableTarget(name: "graph_bfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_bfs.swift"]), .executableTarget(name: "graph_dfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_dfs.swift"]), // chapter_searching .executableTarget(name: "binary_search", path: "chapter_searching", sources: ["binary_search.swift"]), .executableTarget(name: "binary_search_insertion", path: "chapter_searching", sources: ["binary_search_insertion.swift"]), .executableTarget(name: "binary_search_edge", dependencies: ["binary_search_insertion_target"], path: "chapter_searching", sources: ["binary_search_edge.swift"]), .executableTarget(name: "two_sum", path: "chapter_searching", sources: ["two_sum.swift"]), .executableTarget(name: "linear_search", dependencies: ["utils"], path: "chapter_searching", sources: ["linear_search.swift"]), .executableTarget(name: "hashing_search", dependencies: ["utils"], path: "chapter_searching", sources: ["hashing_search.swift"]), // chapter_sorting .executableTarget(name: "selection_sort", path: "chapter_sorting", sources: ["selection_sort.swift"]), .executableTarget(name: "bubble_sort", path: "chapter_sorting", sources: ["bubble_sort.swift"]), .executableTarget(name: "insertion_sort", path: "chapter_sorting", sources: ["insertion_sort.swift"]), .executableTarget(name: "quick_sort", path: "chapter_sorting", sources: ["quick_sort.swift"]), .executableTarget(name: "merge_sort", path: "chapter_sorting", sources: ["merge_sort.swift"]), .executableTarget(name: "heap_sort", path: "chapter_sorting", sources: ["heap_sort.swift"]), .executableTarget(name: "bucket_sort", path: "chapter_sorting", sources: ["bucket_sort.swift"]), .executableTarget(name: "counting_sort", path: "chapter_sorting", sources: ["counting_sort.swift"]), .executableTarget(name: "radix_sort", path: "chapter_sorting", sources: ["radix_sort.swift"]), // chapter_divide_and_conquer .executableTarget(name: "binary_search_recur", path: "chapter_divide_and_conquer", sources: ["binary_search_recur.swift"]), .executableTarget(name: "build_tree", dependencies: ["utils"], path: "chapter_divide_and_conquer", sources: ["build_tree.swift"]), .executableTarget(name: "hanota", path: "chapter_divide_and_conquer", sources: ["hanota.swift"]), // chapter_backtracking .executableTarget(name: "preorder_traversal_i_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_i_compact.swift"]), .executableTarget(name: "preorder_traversal_ii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_ii_compact.swift"]), .executableTarget(name: "preorder_traversal_iii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_compact.swift"]), .executableTarget(name: "preorder_traversal_iii_template", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_template.swift"]), .executableTarget(name: "permutations_i", path: "chapter_backtracking", sources: ["permutations_i.swift"]), .executableTarget(name: "permutations_ii", path: "chapter_backtracking", sources: ["permutations_ii.swift"]), .executableTarget(name: "subset_sum_i_naive", path: "chapter_backtracking", sources: ["subset_sum_i_naive.swift"]), .executableTarget(name: "subset_sum_i", path: "chapter_backtracking", sources: ["subset_sum_i.swift"]), .executableTarget(name: "subset_sum_ii", path: "chapter_backtracking", sources: ["subset_sum_ii.swift"]), .executableTarget(name: "n_queens", path: "chapter_backtracking", sources: ["n_queens.swift"]), // chapter_dynamic_programming .executableTarget(name: "climbing_stairs_backtrack", path: "chapter_dynamic_programming", sources: ["climbing_stairs_backtrack.swift"]), .executableTarget(name: "climbing_stairs_dfs", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs.swift"]), .executableTarget(name: "climbing_stairs_dfs_mem", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs_mem.swift"]), .executableTarget(name: "climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dp.swift"]), .executableTarget(name: "min_cost_climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["min_cost_climbing_stairs_dp.swift"]), .executableTarget(name: "climbing_stairs_constraint_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_constraint_dp.swift"]), .executableTarget(name: "min_path_sum", path: "chapter_dynamic_programming", sources: ["min_path_sum.swift"]), .executableTarget(name: "knapsack", path: "chapter_dynamic_programming", sources: ["knapsack.swift"]), .executableTarget(name: "unbounded_knapsack", path: "chapter_dynamic_programming", sources: ["unbounded_knapsack.swift"]), .executableTarget(name: "coin_change", path: "chapter_dynamic_programming", sources: ["coin_change.swift"]), .executableTarget(name: "coin_change_ii", path: "chapter_dynamic_programming", sources: ["coin_change_ii.swift"]), .executableTarget(name: "edit_distance", path: "chapter_dynamic_programming", sources: ["edit_distance.swift"]), // chapter_greedy .executableTarget(name: "coin_change_greedy", path: "chapter_greedy", sources: ["coin_change_greedy.swift"]), .executableTarget(name: "fractional_knapsack", path: "chapter_greedy", sources: ["fractional_knapsack.swift"]), .executableTarget(name: "max_capacity", path: "chapter_greedy", sources: ["max_capacity.swift"]), .executableTarget(name: "max_product_cutting", path: "chapter_greedy", sources: ["max_product_cutting.swift"]), ] ) ================================================ FILE: ru/codes/swift/chapter_array_and_linkedlist/array.swift ================================================ /** * File: array.swift * Created Time: 2023-01-05 * Author: nuomi1 (nuomi1@qq.com) */ /* Случайный доступ к элементу */ func randomAccess(nums: [Int]) -> Int { // Случайным образом выбрать число из интервала [0, nums.count) let randomIndex = nums.indices.randomElement()! // Получить и вернуть случайный элемент let randomNum = nums[randomIndex] return randomNum } /* Увеличить длину массива */ func extend(nums: [Int], enlarge: Int) -> [Int] { // Инициализировать массив увеличенной длины var res = Array(repeating: 0, count: nums.count + enlarge) // Скопировать все элементы исходного массива в новый массив for i in nums.indices { res[i] = nums[i] } // Вернуть новый массив после расширения return res } /* Вставить элемент num по индексу index в массив */ func insert(nums: inout [Int], num: Int, index: Int) { // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад for i in nums.indices.dropFirst(index).reversed() { nums[i] = nums[i - 1] } // Присвоить num элементу по индексу index nums[index] = num } /* Удалить элемент по индексу index */ func remove(nums: inout [Int], index: Int) { // Сдвинуть все элементы после индекса index на одну позицию вперед for i in nums.indices.dropFirst(index).dropLast() { nums[i] = nums[i + 1] } } /* Обход массива */ func traverse(nums: [Int]) { var count = 0 // Обход массива по индексам for i in nums.indices { count += nums[i] } // Непосредственно обходить элементы массива for num in nums { count += num } // Одновременно обходить индексы и элементы данных for (i, num) in nums.enumerated() { count += nums[i] count += num } } /* Найти заданный элемент в массиве */ func find(nums: [Int], target: Int) -> Int { for i in nums.indices { if nums[i] == target { return i } } return -1 } @main enum _Array { /* Driver Code */ static func main() { /* Инициализация массива */ let arr = Array(repeating: 0, count: 5) print("Массив arr = \(arr)") var nums = [1, 3, 2, 5, 4] print("Массив nums = \(nums)") /* Случайный доступ */ let randomNum = randomAccess(nums: nums) print("Случайный элемент из nums = \(randomNum)") /* Расширение длины */ nums = extend(nums: nums, enlarge: 3) print("После увеличения длины массива до 8 nums = \(nums)") /* Вставка элемента */ insert(nums: &nums, num: 6, index: 3) print("После вставки числа 6 по индексу 3 nums = \(nums)") /* Удаление элемента */ remove(nums: &nums, index: 2) print("После удаления элемента по индексу 2 nums = \(nums)") /* Обход массива */ traverse(nums: nums) /* Поиск элемента */ let index = find(nums: nums, target: 3) print("Поиск элемента 3 в nums: индекс = \(index)") } } ================================================ FILE: ru/codes/swift/chapter_array_and_linkedlist/linked_list.swift ================================================ /** * File: linked_list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Вставить узел P после узла n0 в связном списке */ func insert(n0: ListNode, P: ListNode) { let n1 = n0.next P.next = n1 n0.next = P } /* Удалить первый узел после узла n0 в связном списке */ func remove(n0: ListNode) { if n0.next == nil { return } // n0 -> P -> n1 let P = n0.next let n1 = P?.next n0.next = n1 } /* Доступ к узлу связного списка по индексу index */ func access(head: ListNode, index: Int) -> ListNode? { var head: ListNode? = head for _ in 0 ..< index { if head == nil { return nil } head = head?.next } return head } /* Найти в связном списке первый узел со значением target */ func find(head: ListNode, target: Int) -> Int { var head: ListNode? = head var index = 0 while head != nil { if head?.val == target { return index } head = head?.next index += 1 } return -1 } @main enum LinkedList { /* Driver Code */ static func main() { /* Инициализация связного списка */ // Инициализация всех узлов let n0 = ListNode(x: 1) let n1 = ListNode(x: 3) let n2 = ListNode(x: 2) let n3 = ListNode(x: 5) let n4 = ListNode(x: 4) // Построить ссылки между узлами n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 print("Исходный связный список") PrintUtil.printLinkedList(head: n0) /* Вставка узла */ insert(n0: n0, P: ListNode(x: 0)) print("Связный список после вставки узла") PrintUtil.printLinkedList(head: n0) /* Удаление узла */ remove(n0: n0) print("Связный список после удаления узла") PrintUtil.printLinkedList(head: n0) /* Доступ к узлу */ let node = access(head: n0, index: 3) print("Значение узла по индексу 3 в связном списке = \(node!.val)") /* Поиск узла */ let index = find(head: n0, target: 2) print("Индекс узла со значением 2 в связном списке = \(index)") } } ================================================ FILE: ru/codes/swift/chapter_array_and_linkedlist/list.swift ================================================ /** * File: list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ @main enum List { /* Driver Code */ static func main() { /* Инициализация списка */ var nums = [1, 3, 2, 5, 4] print("Список nums = \(nums)") /* Доступ к элементу */ let num = nums[1] print("Элемент по индексу 1: num = \(num)") /* Обновление элемента */ nums[1] = 0 print("После обновления элемента по индексу 1 до 0 nums = \(nums)") /* Очистить список */ nums.removeAll() print("После очистки списка nums = \(nums)") /* Добавление элемента в конец */ nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) print("После добавления элементов nums = \(nums)") /* Вставка элемента в середину */ nums.insert(6, at: 3) print("После вставки числа 6 по индексу 3 nums = \(nums)") /* Удаление элемента */ nums.remove(at: 3) print("После удаления элемента по индексу 3 nums = \(nums)") /* Обходить список по индексам */ var count = 0 for i in nums.indices { count += nums[i] } /* Непосредственно обходить элементы списка */ count = 0 for x in nums { count += x } /* Объединить два списка */ let nums1 = [6, 8, 7, 10, 9] nums.append(contentsOf: nums1) print("После конкатенации списка nums1 к nums nums = \(nums)") /* Отсортировать список */ nums.sort() print("После сортировки списка nums = \(nums)") } } ================================================ FILE: ru/codes/swift/chapter_array_and_linkedlist/my_list.swift ================================================ /** * File: my_list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ /* Класс списка */ class MyList { private var arr: [Int] // Массив (для хранения элементов списка) private var _capacity: Int // Вместимость списка private var _size: Int // Длина списка (текущее число элементов) private let extendRatio: Int // Коэффициент увеличения списка при каждом расширении /* Конструктор */ init() { _capacity = 10 _size = 0 extendRatio = 2 arr = Array(repeating: 0, count: _capacity) } /* Получить длину списка (текущее число элементов) */ func size() -> Int { _size } /* Получить вместимость списка */ func capacity() -> Int { _capacity } /* Доступ к элементу */ func get(index: Int) -> Int { // Если индекс выходит за границы, выбросить ошибку; далее аналогично if index < 0 || index >= size() { fatalError("индекс выходит за границы") } return arr[index] } /* Обновление элемента */ func set(index: Int, num: Int) { if index < 0 || index >= size() { fatalError("индекс выходит за границы") } arr[index] = num } /* Добавление элемента в конец */ func add(num: Int) { // При превышении вместимости по числу элементов запускается расширение if size() == capacity() { extendCapacity() } arr[size()] = num // Обновить число элементов _size += 1 } /* Вставка элемента в середину */ func insert(index: Int, num: Int) { if index < 0 || index >= size() { fatalError("индекс выходит за границы") } // При превышении вместимости по числу элементов запускается расширение if size() == capacity() { extendCapacity() } // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад for j in (index ..< size()).reversed() { arr[j + 1] = arr[j] } arr[index] = num // Обновить число элементов _size += 1 } /* Удаление элемента */ @discardableResult func remove(index: Int) -> Int { if index < 0 || index >= size() { fatalError("индекс выходит за границы") } let num = arr[index] // Сдвинуть все элементы после индекса index на одну позицию вперед for j in index ..< (size() - 1) { arr[j] = arr[j + 1] } // Обновить число элементов _size -= 1 // Вернуть удаленный элемент return num } /* Расширение списка */ func extendCapacity() { // Создать новый массив длиной в extendRatio раз больше исходного и скопировать в него исходный массив arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1)) // Обновить вместимость списка _capacity = arr.count } /* Преобразовать список в массив */ func toArray() -> [Int] { Array(arr.prefix(size())) } } @main enum _MyList { /* Driver Code */ static func main() { /* Инициализация списка */ let nums = MyList() /* Добавление элемента в конец */ nums.add(num: 1) nums.add(num: 3) nums.add(num: 2) nums.add(num: 5) nums.add(num: 4) print("Список nums = \(nums.toArray()) , вместимость = \(nums.capacity()) , длина = \(nums.size())") /* Вставка элемента в середину */ nums.insert(index: 3, num: 6) print("После вставки числа 6 по индексу 3 nums = \(nums.toArray())") /* Удаление элемента */ nums.remove(index: 3) print("После удаления элемента по индексу 3 nums = \(nums.toArray())") /* Доступ к элементу */ let num = nums.get(index: 1) print("Элемент по индексу 1: num = \(num)") /* Обновление элемента */ nums.set(index: 1, num: 0) print("После обновления элемента по индексу 1 до 0 nums = \(nums.toArray())") /* Проверка механизма расширения */ for i in 0 ..< 10 { // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения nums.add(num: i) } print("Список nums после увеличения вместимости = \(nums.toArray()) , вместимость = \(nums.capacity()) , длина = \(nums.size())") } } ================================================ FILE: ru/codes/swift/chapter_backtracking/n_queens.swift ================================================ /** * File: n_queens.swift * Created Time: 2023-05-14 * Author: nuomi1 (nuomi1@qq.com) */ /* Алгоритм бэктрекинга: n ферзей */ func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) { // Когда все строки уже обработаны, записать решение if row == n { res.append(state) return } // Обойти все столбцы for col in 0 ..< n { // Вычислить главную и побочную диагонали, соответствующие этой клетке let diag1 = row - col + n - 1 let diag2 = row + col // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if !cols[col] && !diags1[diag1] && !diags2[diag2] { // Попытка: поставить ферзя в эту клетку state[row][col] = "Q" cols[col] = true diags1[diag1] = true diags2[diag2] = true // Перейти к размещению следующей строки backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) // Откат: восстановить эту клетку как пустую state[row][col] = "#" cols[col] = false diags1[diag1] = false diags2[diag2] = false } } } /* Решить задачу о n ферзях */ func nQueens(n: Int) -> [[[String]]] { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку var state = Array(repeating: Array(repeating: "#", count: n), count: n) var cols = Array(repeating: false, count: n) // Отмечать, есть ли ферзь в столбце var diags1 = Array(repeating: false, count: 2 * n - 1) // Отмечать наличие ферзя на главной диагонали var diags2 = Array(repeating: false, count: 2 * n - 1) // Отмечать наличие ферзя на побочной диагонали var res: [[[String]]] = [] backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) return res } @main enum NQueens { /* Driver Code */ static func main() { let n = 4 let res = nQueens(n: n) print("Размер входной доски = \(n)") print("Количество способов расстановки ферзей: \(res.count)") for state in res { print("--------------------") for row in state { print(row) } } } } ================================================ FILE: ru/codes/swift/chapter_backtracking/permutations_i.swift ================================================ /** * File: permutations_i.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ /* Алгоритм бэктрекинга: все перестановки I */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // Когда длина состояния равна числу элементов, записать решение if state.count == choices.count { res.append(state) return } // Перебор всех вариантов выбора for (i, choice) in choices.enumerated() { // Отсечение: нельзя выбирать один и тот же элемент повторно if !selected[i] { // Попытка: сделать выбор и обновить состояние selected[i] = true state.append(choice) // Перейти к следующему выбору backtrack(state: &state, choices: choices, selected: &selected, res: &res) // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false state.removeLast() } } } /* Все перестановки I */ func permutationsI(nums: [Int]) -> [[Int]] { var state: [Int] = [] var selected = Array(repeating: false, count: nums.count) var res: [[Int]] = [] backtrack(state: &state, choices: nums, selected: &selected, res: &res) return res } @main enum PermutationsI { /* Driver Code */ static func main() { let nums = [1, 2, 3] let res = permutationsI(nums: nums) print("Входной массив nums = \(nums)") print("Все перестановки res = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_backtracking/permutations_ii.swift ================================================ /** * File: permutations_ii.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ /* Алгоритм бэктрекинга: все перестановки II */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // Когда длина состояния равна числу элементов, записать решение if state.count == choices.count { res.append(state) return } // Перебор всех вариантов выбора var duplicated: Set = [] for (i, choice) in choices.enumerated() { // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if !selected[i], !duplicated.contains(choice) { // Попытка: сделать выбор и обновить состояние duplicated.insert(choice) // Записать значения уже выбранных элементов selected[i] = true state.append(choice) // Перейти к следующему выбору backtrack(state: &state, choices: choices, selected: &selected, res: &res) // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false state.removeLast() } } } /* Все перестановки II */ func permutationsII(nums: [Int]) -> [[Int]] { var state: [Int] = [] var selected = Array(repeating: false, count: nums.count) var res: [[Int]] = [] backtrack(state: &state, choices: nums, selected: &selected, res: &res) return res } @main enum PermutationsII { /* Driver Code */ static func main() { let nums = [1, 2, 3] let res = permutationsII(nums: nums) print("Входной массив nums = \(nums)") print("Все перестановки res = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift ================================================ /** * File: preorder_traversal_i_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var res: [TreeNode] = [] /* Предварительный обход: пример 1 */ func preOrder(root: TreeNode?) { guard let root = root else { return } if root.val == 7 { // Записать решение res.append(root) } preOrder(root: root.left) preOrder(root: root.right) } @main enum PreorderTraversalICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\nИнициализация двоичного дерева") PrintUtil.printTree(root: root) // Предварительный обход res = [] preOrder(root: root) print("\nВсе узлы со значением 7") var vals: [Int] = [] for node in res { vals.append(node.val) } print(vals) } } ================================================ FILE: ru/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift ================================================ /** * File: preorder_traversal_ii_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var path: [TreeNode] = [] var res: [[TreeNode]] = [] /* Предварительный обход: пример 2 */ func preOrder(root: TreeNode?) { guard let root = root else { return } // Попытка path.append(root) if root.val == 7 { // Записать решение res.append(path) } preOrder(root: root.left) preOrder(root: root.right) // Откат path.removeLast() } @main enum PreorderTraversalIICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\nИнициализация двоичного дерева") PrintUtil.printTree(root: root) // Предварительный обход path = [] res = [] preOrder(root: root) print("\nВсе пути от корня к узлу 7") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: ru/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift ================================================ /** * File: preorder_traversal_iii_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var path: [TreeNode] = [] var res: [[TreeNode]] = [] /* Предварительный обход: пример 3 */ func preOrder(root: TreeNode?) { // Отсечение guard let root = root, root.val != 3 else { return } // Попытка path.append(root) if root.val == 7 { // Записать решение res.append(path) } preOrder(root: root.left) preOrder(root: root.right) // Откат path.removeLast() } @main enum PreorderTraversalIIICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\nИнициализация двоичного дерева") PrintUtil.printTree(root: root) // Предварительный обход path = [] res = [] preOrder(root: root) print("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: ru/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift ================================================ /** * File: preorder_traversal_iii_template.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Проверить, является ли текущее состояние решением */ func isSolution(state: [TreeNode]) -> Bool { !state.isEmpty && state.last!.val == 7 } /* Записать решение */ func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) { res.append(state) } /* Проверить, допустим ли этот выбор в текущем состоянии */ func isValid(state: [TreeNode], choice: TreeNode?) -> Bool { choice != nil && choice!.val != 3 } /* Обновить состояние */ func makeChoice(state: inout [TreeNode], choice: TreeNode) { state.append(choice) } /* Восстановить состояние */ func undoChoice(state: inout [TreeNode], choice: TreeNode) { state.removeLast() } /* Алгоритм бэктрекинга: пример 3 */ func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) { // Проверить, является ли текущее состояние решением if isSolution(state: state) { recordSolution(state: state, res: &res) } // Перебор всех вариантов выбора for choice in choices { // Отсечение: проверить допустимость выбора if isValid(state: state, choice: choice) { // Попытка: сделать выбор и обновить состояние makeChoice(state: &state, choice: choice) // Перейти к следующему выбору backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res) // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state: &state, choice: choice) } } } @main enum PreorderTraversalIIITemplate { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\nИнициализация двоичного дерева") PrintUtil.printTree(root: root) // Алгоритм бэктрекинга var state: [TreeNode] = [] var res: [[TreeNode]] = [] backtrack(state: &state, choices: [root].compactMap { $0 }, res: &res) print("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: ru/codes/swift/chapter_backtracking/subset_sum_i.swift ================================================ /** * File: subset_sum_i.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Алгоритм бэктрекинга: сумма подмножеств I */ func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { // Если сумма подмножества равна target, записать решение if target == 0 { res.append(state) return } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств for i in choices.indices.dropFirst(start) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if target - choices[i] < 0 { break } // Попытка: сделать выбор и обновить target и start state.append(choices[i]) // Перейти к следующему выбору backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res) // Откат: отменить выбор и восстановить предыдущее состояние state.removeLast() } } /* Решить задачу суммы подмножеств I */ func subsetSumI(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // Состояние (подмножество) let nums = nums.sorted() // Отсортировать nums let start = 0 // Стартовая вершина обхода var res: [[Int]] = [] // Список результатов (список подмножеств) backtrack(state: &state, target: target, choices: nums, start: start, res: &res) return res } @main enum SubsetSumI { /* Driver Code */ static func main() { let nums = [3, 4, 5] let target = 9 let res = subsetSumI(nums: nums, target: target) print("Входной массив nums = \(nums), target = \(target)") print("Все подмножества с суммой \(target): res = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_backtracking/subset_sum_i_naive.swift ================================================ /** * File: subset_sum_i_naive.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Алгоритм бэктрекинга: сумма подмножеств I */ func backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) { // Если сумма подмножества равна target, записать решение if total == target { res.append(state) return } // Перебор всех вариантов выбора for i in choices.indices { // Отсечение: если сумма подмножества превышает target, пропустить этот выбор if total + choices[i] > target { continue } // Попытка: сделать выбор и обновить элемент и total state.append(choices[i]) // Перейти к следующему выбору backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res) // Откат: отменить выбор и восстановить предыдущее состояние state.removeLast() } } /* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ func subsetSumINaive(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // Состояние (подмножество) let total = 0 // Сумма подмножеств var res: [[Int]] = [] // Список результатов (список подмножеств) backtrack(state: &state, target: target, total: total, choices: nums, res: &res) return res } @main enum SubsetSumINaive { /* Driver Code */ static func main() { let nums = [3, 4, 5] let target = 9 let res = subsetSumINaive(nums: nums, target: target) print("Входной массив nums = \(nums), target = \(target)") print("Все подмножества с суммой \(target): res = \(res)") print("Обратите внимание: результат этого метода содержит повторяющиеся множества") } } ================================================ FILE: ru/codes/swift/chapter_backtracking/subset_sum_ii.swift ================================================ /** * File: subset_sum_ii.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Алгоритм бэктрекинга: сумма подмножеств II */ func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { // Если сумма подмножества равна target, записать решение if target == 0 { res.append(state) return } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента for i in choices.indices.dropFirst(start) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if target - choices[i] < 0 { break } // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить if i > start, choices[i] == choices[i - 1] { continue } // Попытка: сделать выбор и обновить target и start state.append(choices[i]) // Перейти к следующему выбору backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res) // Откат: отменить выбор и восстановить предыдущее состояние state.removeLast() } } /* Решить задачу суммы подмножеств II */ func subsetSumII(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // Состояние (подмножество) let nums = nums.sorted() // Отсортировать nums let start = 0 // Стартовая вершина обхода var res: [[Int]] = [] // Список результатов (список подмножеств) backtrack(state: &state, target: target, choices: nums, start: start, res: &res) return res } @main enum SubsetSumII { /* Driver Code */ static func main() { let nums = [4, 4, 5] let target = 9 let res = subsetSumII(nums: nums, target: target) print("Входной массив nums = \(nums), target = \(target)") print("Все подмножества с суммой \(target): res = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_computational_complexity/iteration.swift ================================================ /** * File: iteration.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Цикл for */ func forLoop(n: Int) -> Int { var res = 0 // Циклическое суммирование 1, 2, ..., n-1, n for i in 1 ... n { res += i } return res } /* Цикл while */ func whileLoop(n: Int) -> Int { var res = 0 var i = 1 // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while i <= n { res += i i += 1 // Обновить условную переменную } return res } /* Цикл while (двойное обновление) */ func whileLoopII(n: Int) -> Int { var res = 0 var i = 1 // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while i <= n { res += i // Обновить условную переменную i += 1 i *= 2 } return res } /* Двойной цикл for */ func nestedForLoop(n: Int) -> String { var res = "" // Цикл по i = 1, 2, ..., n-1, n for i in 1 ... n { // Цикл по j = 1, 2, ..., n-1, n for j in 1 ... n { res.append("(\(i), \(j)), ") } } return res } @main enum Iteration { /* Driver Code */ static func main() { let n = 5 var res = 0 res = forLoop(n: n) print("\nРезультат суммирования в цикле for res = \(res)") res = whileLoop(n: n) print("\nРезультат суммирования в цикле while res = \(res)") res = whileLoopII(n: n) print("\nРезультат суммирования в цикле while (двойное обновление) res = \(res)") let resStr = nestedForLoop(n: n) print("\nРезультат обхода в двойном цикле for \(resStr)") } } ================================================ FILE: ru/codes/swift/chapter_computational_complexity/recursion.swift ================================================ /** * File: recursion.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Рекурсия */ func recur(n: Int) -> Int { // Условие завершения if n == 1 { return 1 } // Рекурсия: рекурсивный вызов let res = recur(n: n - 1) // Возврат: вернуть результат return n + res } /* Имитация рекурсии итерацией */ func forLoopRecur(n: Int) -> Int { // Использовать явный стек для имитации системного стека вызовов var stack: [Int] = [] var res = 0 // Рекурсия: рекурсивный вызов for i in (1 ... n).reversed() { // Имитировать «рекурсию» с помощью операции помещения в стек stack.append(i) } // Возврат: вернуть результат while !stack.isEmpty { // Имитировать «возврат» с помощью операции извлечения из стека res += stack.removeLast() } // res = 1+2+3+...+n return res } /* Хвостовая рекурсия */ func tailRecur(n: Int, res: Int) -> Int { // Условие завершения if n == 0 { return res } // Хвостовой рекурсивный вызов return tailRecur(n: n - 1, res: res + n) } /* Последовательность Фибоначчи: рекурсия */ func fib(n: Int) -> Int { // Условие завершения: f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1 } // Рекурсивный вызов f(n) = f(n-1) + f(n-2) let res = fib(n: n - 1) + fib(n: n - 2) // Вернуть результат f(n) return res } @main enum Recursion { /* Driver Code */ static func main() { let n = 5 var res = 0 res = recursion.recur(n: n) print("\nРезультат суммирования в рекурсивной функции res = \(res)") res = recursion.forLoopRecur(n: n) print("\nРезультат суммирования при имитации рекурсии res = \(res)") res = recursion.tailRecur(n: n, res: 0) print("\nРезультат суммирования в хвостовой рекурсии res = \(res)") res = recursion.fib(n: n) print("\nЧлен последовательности Фибоначчи с номером \(n) = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_computational_complexity/space_complexity.swift ================================================ /** * File: space_complexity.swift * Created Time: 2023-01-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Функция */ @discardableResult func function() -> Int { // Выполнить некоторые операции return 0 } /* Постоянная сложность */ func constant(n: Int) { // Константы, переменные и объекты занимают O(1) памяти let a = 0 var b = 0 let nums = Array(repeating: 0, count: 10000) let node = ListNode(x: 0) // Переменные в цикле занимают O(1) памяти for _ in 0 ..< n { let c = 0 } // Функции в цикле занимают O(1) памяти for _ in 0 ..< n { function() } } /* Линейная сложность */ func linear(n: Int) { // Массив длины n занимает O(n) памяти let nums = Array(repeating: 0, count: n) // Список длины n занимает O(n) памяти let nodes = (0 ..< n).map { ListNode(x: $0) } // Хеш-таблица длины n занимает O(n) памяти let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") }) } /* Линейная сложность (рекурсивная реализация) */ func linearRecur(n: Int) { print("Рекурсия n = \(n)") if n == 1 { return } linearRecur(n: n - 1) } /* Квадратичная сложность */ func quadratic(n: Int) { // Двумерный список занимает O(n^2) памяти let numList = Array(repeating: Array(repeating: 0, count: n), count: n) } /* Квадратичная сложность (рекурсивная реализация) */ @discardableResult func quadraticRecur(n: Int) -> Int { if n <= 0 { return 0 } // Длина массива nums равна n, n-1, ..., 2, 1 let nums = Array(repeating: 0, count: n) print("В рекурсии n = \(n), длина nums = \(nums.count)") return quadraticRecur(n: n - 1) } /* Экспоненциальная сложность (построение полного двоичного дерева) */ func buildTree(n: Int) -> TreeNode? { if n == 0 { return nil } let root = TreeNode(x: 0) root.left = buildTree(n: n - 1) root.right = buildTree(n: n - 1) return root } @main enum SpaceComplexity { /* Driver Code */ static func main() { let n = 5 // Постоянная сложность constant(n: n) // Линейная сложность linear(n: n) linearRecur(n: n) // Квадратичная сложность quadratic(n: n) quadraticRecur(n: n) // Экспоненциальная сложность let root = buildTree(n: n) PrintUtil.printTree(root: root) } } ================================================ FILE: ru/codes/swift/chapter_computational_complexity/time_complexity.swift ================================================ /** * File: time_complexity.swift * Created Time: 2022-12-26 * Author: nuomi1 (nuomi1@qq.com) */ /* Постоянная сложность */ func constant(n: Int) -> Int { var count = 0 let size = 100_000 for _ in 0 ..< size { count += 1 } return count } /* Линейная сложность */ func linear(n: Int) -> Int { var count = 0 for _ in 0 ..< n { count += 1 } return count } /* Линейная сложность (обход массива) */ func arrayTraversal(nums: [Int]) -> Int { var count = 0 // Число итераций пропорционально длине массива for _ in nums { count += 1 } return count } /* Квадратичная сложность */ func quadratic(n: Int) -> Int { var count = 0 // Число итераций квадратично зависит от размера данных n for _ in 0 ..< n { for _ in 0 ..< n { count += 1 } } return count } /* Квадратичная сложность (пузырьковая сортировка) */ func bubbleSort(nums: inout [Int]) -> Int { var count = 0 // Счетчик // Внешний цикл: неотсортированный диапазон [0, i] for i in nums.indices.dropFirst().reversed() { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for j in 0 ..< i { if nums[j] > nums[j + 1] { // Поменять местами nums[j] и nums[j + 1] let tmp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = tmp count += 3 // Обмен элементов включает 3 элементарные операции } } } return count } /* Экспоненциальная сложность (итеративная реализация) */ func exponential(n: Int) -> Int { var count = 0 var base = 1 // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) for _ in 0 ..< n { for _ in 0 ..< base { count += 1 } base *= 2 } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count } /* Экспоненциальная сложность (рекурсивная реализация) */ func expRecur(n: Int) -> Int { if n == 1 { return 1 } return expRecur(n: n - 1) + expRecur(n: n - 1) + 1 } /* Логарифмическая сложность (итеративная реализация) */ func logarithmic(n: Int) -> Int { var count = 0 var n = n while n > 1 { n = n / 2 count += 1 } return count } /* Логарифмическая сложность (рекурсивная реализация) */ func logRecur(n: Int) -> Int { if n <= 1 { return 0 } return logRecur(n: n / 2) + 1 } /* Линейно-логарифмическая сложность */ func linearLogRecur(n: Int) -> Int { if n <= 1 { return 1 } var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2) for _ in stride(from: 0, to: n, by: 1) { count += 1 } return count } /* Факториальная сложность (рекурсивная реализация) */ func factorialRecur(n: Int) -> Int { if n == 0 { return 1 } var count = 0 // Из одного получается n for _ in 0 ..< n { count += factorialRecur(n: n - 1) } return count } @main enum TimeComplexity { /* Driver Code */ static func main() { // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях let n = 8 print("Размер входных данных n = \(n)") var count = constant(n: n) print("Число операций константной сложности = \(count)") count = linear(n: n) print("Число операций линейной сложности = \(count)") count = arrayTraversal(nums: Array(repeating: 0, count: n)) print("Число операций линейной сложности (обход массива) = \(count)") count = quadratic(n: n) print("Число операций квадратичной сложности = \(count)") var nums = Array(stride(from: n, to: 0, by: -1)) // [n,n-1,...,2,1] count = bubbleSort(nums: &nums) print("Число операций квадратичной сложности (пузырьковая сортировка) = \(count)") count = exponential(n: n) print("Число операций экспоненциальной сложности (итеративная реализация) = \(count)") count = expRecur(n: n) print("Число операций экспоненциальной сложности (рекурсивная реализация) = \(count)") count = logarithmic(n: n) print("Число операций логарифмической сложности (итеративная реализация) = \(count)") count = logRecur(n: n) print("Число операций логарифмической сложности (рекурсивная реализация) = \(count)") count = linearLogRecur(n: n) print("Число операций линейно-логарифмической сложности (рекурсивная реализация) = \(count)") count = factorialRecur(n: n) print("Число операций факториальной сложности (рекурсивная реализация) = \(count)") } } ================================================ FILE: ru/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift ================================================ /** * File: worst_best_time_complexity.swift * Created Time: 2022-12-26 * Author: nuomi1 (nuomi1@qq.com) */ /* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ func randomNumbers(n: Int) -> [Int] { // Создать массив nums = { 1, 2, 3, ..., n } var nums = Array(1 ... n) // Случайно перемешать элементы массива nums.shuffle() return nums } /* Найти индекс числа 1 в массиве nums */ func findOne(nums: [Int]) -> Int { for i in nums.indices { // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) if nums[i] == 1 { return i } } return -1 } @main enum WorstBestTimeComplexity { /* Driver Code */ static func main() { for _ in 0 ..< 10 { let n = 100 let nums = randomNumbers(n: n) let index = findOne(nums: nums) print("Массив [1, 2, ..., n] после перемешивания = \(nums)") print("Индекс числа 1 = \(index)") } } } ================================================ FILE: ru/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift ================================================ /** * File: binary_search_recur.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Бинарный поиск: задача f(i, j) */ func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int { // Если интервал пуст, целевой элемент отсутствует, вернуть -1 if i > j { return -1 } // Вычислить индекс середины m let m = (i + j) / 2 if nums[m] < target { // Рекурсивная подзадача f(m+1, j) return dfs(nums: nums, target: target, i: m + 1, j: j) } else if nums[m] > target { // Рекурсивная подзадача f(i, m-1) return dfs(nums: nums, target: target, i: i, j: m - 1) } else { // Целевой элемент найден, вернуть его индекс return m } } /* Бинарный поиск */ func binarySearch(nums: [Int], target: Int) -> Int { // Решить задачу f(0, n-1) dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1) } @main enum BinarySearchRecur { /* Driver Code */ static func main() { let target = 6 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] // Бинарный поиск (двусторонне замкнутый интервал) let index = binarySearch(nums: nums, target: target) print("Индекс целевого элемента 6 = \(index)") } } ================================================ FILE: ru/codes/swift/chapter_divide_and_conquer/build_tree.swift ================================================ /** * File: build_tree.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Построить двоичное дерево: разделяй и властвуй */ func dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? { // Завершить при пустом диапазоне поддерева if r - l < 0 { return nil } // Инициализировать корневой узел let root = TreeNode(x: preorder[i]) // Найти m, чтобы разделить левое и правое поддеревья let m = inorderMap[preorder[i]]! // Подзадача: построить левое поддерево root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1) // Подзадача: построить правое поддерево root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r) // Вернуть корневой узел return root } /* Построить двоичное дерево */ func buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? { // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset } return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1) } @main enum BuildTree { /* Driver Code */ static func main() { let preorder = [3, 9, 2, 1, 7] let inorder = [9, 3, 1, 2, 7] print("Предварительный обход = \(preorder)") print("Симметричный обход = \(inorder)") let root = buildTree(preorder: preorder, inorder: inorder) print("Построенное двоичное дерево:") PrintUtil.printTree(root: root) } } ================================================ FILE: ru/codes/swift/chapter_divide_and_conquer/hanota.swift ================================================ /** * File: hanota.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Переместить один диск */ func move(src: inout [Int], tar: inout [Int]) { // Снять диск с вершины src let pan = src.popLast()! // Положить диск на вершину tar tar.append(pan) } /* Решить задачу Ханойской башни f(i) */ func dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) { // Если в src остался только один диск, сразу переместить его в tar if i == 1 { move(src: &src, tar: &tar) return } // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar dfs(i: i - 1, src: &src, buf: &tar, tar: &buf) // Подзадача f(1): переместить оставшийся один диск из src в tar move(src: &src, tar: &tar) // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src dfs(i: i - 1, src: &buf, buf: &src, tar: &tar) } /* Решить задачу Ханойской башни */ func solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) { let n = A.count // Хвост списка соответствует вершине столбца // Переместить верхние n дисков из src в C с помощью B dfs(i: n, src: &A, buf: &B, tar: &C) } @main enum Hanota { /* Driver Code */ static func main() { // Хвост списка соответствует вершине столбца var A = [5, 4, 3, 2, 1] var B: [Int] = [] var C: [Int] = [] print("Исходное состояние:") print("A = \(A)") print("B = \(B)") print("C = \(C)") solveHanota(A: &A, B: &B, C: &C) print("После завершения перемещения дисков:") print("A = \(A)") print("B = \(B)") print("C = \(C)") } } ================================================ FILE: ru/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift ================================================ /** * File: climbing_stairs_backtrack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Бэктрекинг */ func backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) { // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 if state == n { res[0] += 1 } // Перебор всех вариантов выбора for choice in choices { // Отсечение: нельзя выходить за n-ю ступень if state + choice > n { continue } // Попытка: сделать выбор и обновить состояние backtrack(choices: choices, state: state + choice, n: n, res: &res) // Откат } } /* Подъем по лестнице: бэктрекинг */ func climbingStairsBacktrack(n: Int) -> Int { let choices = [1, 2] // Можно подняться на 1 или 2 ступени let state = 0 // Начать подъем с 0-й ступени var res: [Int] = [] res.append(0) // Использовать res[0] для хранения числа решений backtrack(choices: choices, state: state, n: n, res: &res) return res[0] } @main enum ClimbingStairsBacktrack { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsBacktrack(n: n) print("Количество способов подняться по лестнице из \(n) ступеней = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift ================================================ /** * File: climbing_stairs_constraint_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Подъем по лестнице с ограничениями: динамическое программирование */ func climbingStairsConstraintDP(n: Int) -> Int { if n == 1 || n == 2 { return 1 } // Инициализация таблицы dp для хранения решений подзадач var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1) // Начальное состояние: заранее задать решения наименьших подзадач dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // Переход состояний: постепенное решение больших подзадач через меньшие for i in 3 ... n { dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] } return dp[n][1] + dp[n][2] } @main enum ClimbingStairsConstraintDP { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsConstraintDP(n: n) print("Количество способов подняться по лестнице из \(n) ступеней = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift ================================================ /** * File: climbing_stairs_dfs.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Поиск */ func dfs(i: Int) -> Int { // dp[1] и dp[2] уже известны, вернуть их if i == 1 || i == 2 { return i } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i: i - 1) + dfs(i: i - 2) return count } /* Подъем по лестнице: поиск */ func climbingStairsDFS(n: Int) -> Int { dfs(i: n) } @main enum ClimbingStairsDFS { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsDFS(n: n) print("Количество способов подняться по лестнице из \(n) ступеней = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift ================================================ /** * File: climbing_stairs_dfs_mem.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Поиск с мемоизацией */ func dfs(i: Int, mem: inout [Int]) -> Int { // dp[1] и dp[2] уже известны, вернуть их if i == 1 || i == 2 { return i } // Если запись dp[i] существует, сразу вернуть ее if mem[i] != -1 { return mem[i] } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem) // Сохранить dp[i] mem[i] = count return count } /* Подъем по лестнице: поиск с мемоизацией */ func climbingStairsDFSMem(n: Int) -> Int { // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи var mem = Array(repeating: -1, count: n + 1) return dfs(i: n, mem: &mem) } @main enum ClimbingStairsDFSMem { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsDFSMem(n: n) print("Количество способов подняться по лестнице из \(n) ступеней = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift ================================================ /** * File: climbing_stairs_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Подъем по лестнице: динамическое программирование */ func climbingStairsDP(n: Int) -> Int { if n == 1 || n == 2 { return n } // Инициализация таблицы dp для хранения решений подзадач var dp = Array(repeating: 0, count: n + 1) // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = 1 dp[2] = 2 // Переход состояний: постепенное решение больших подзадач через меньшие for i in 3 ... n { dp[i] = dp[i - 1] + dp[i - 2] } return dp[n] } /* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ func climbingStairsDPComp(n: Int) -> Int { if n == 1 || n == 2 { return n } var a = 1 var b = 2 for _ in 3 ... n { (a, b) = (b, a + b) } return b } @main enum ClimbingStairsDP { /* Driver Code */ static func main() { let n = 9 var res = climbingStairsDP(n: n) print("Количество способов подняться по лестнице из \(n) ступеней = \(res)") res = climbingStairsDPComp(n: n) print("Количество способов подняться по лестнице из \(n) ступеней = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_dynamic_programming/coin_change.swift ================================================ /** * File: coin_change.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Размен монет: динамическое программирование */ func coinChangeDP(coins: [Int], amt: Int) -> Int { let n = coins.count let MAX = amt + 1 // Инициализация таблицы dp var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) // Переход состояний: первая строка и первый столбец for a in 1 ... amt { dp[0][a] = MAX } // Переход состояний: остальные строки и столбцы for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a] } else { // Меньшее из двух решений: не брать или взять монету i dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) } } } return dp[n][amt] != MAX ? dp[n][amt] : -1 } /* Размен монет: динамическое программирование с оптимизацией памяти */ func coinChangeDPComp(coins: [Int], amt: Int) -> Int { let n = coins.count let MAX = amt + 1 // Инициализация таблицы dp var dp = Array(repeating: MAX, count: amt + 1) dp[0] = 0 // Переход состояний for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a] } else { // Меньшее из двух решений: не брать или взять монету i dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) } } } return dp[amt] != MAX ? dp[amt] : -1 } @main enum CoinChange { /* Driver Code */ static func main() { let coins = [1, 2, 5] let amt = 4 // Динамическое программирование var res = coinChangeDP(coins: coins, amt: amt) print("Минимальное число монет для набора целевой суммы = \(res)") // Динамическое программирование с оптимизацией памяти res = coinChangeDPComp(coins: coins, amt: amt) print("Минимальное число монет для набора целевой суммы = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_dynamic_programming/coin_change_ii.swift ================================================ /** * File: coin_change_ii.swift * Created Time: 2023-07-16 * Author: nuomi1 (nuomi1@qq.com) */ /* Размен монет II: динамическое программирование */ func coinChangeIIDP(coins: [Int], amt: Int) -> Int { let n = coins.count // Инициализация таблицы dp var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) // Инициализация первого столбца for i in 0 ... n { dp[i][0] = 1 } // Переход состояний for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a] } else { // Сумма двух решений: не брать или взять монету i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] } } } return dp[n][amt] } /* Размен монет II: динамическое программирование с оптимизацией памяти */ func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { let n = coins.count // Инициализация таблицы dp var dp = Array(repeating: 0, count: amt + 1) dp[0] = 1 // Переход состояний for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a] } else { // Сумма двух решений: не брать или взять монету i dp[a] = dp[a] + dp[a - coins[i - 1]] } } } return dp[amt] } @main enum CoinChangeII { /* Driver Code */ static func main() { let coins = [1, 2, 5] let amt = 5 // Динамическое программирование var res = coinChangeIIDP(coins: coins, amt: amt) print("Количество комбинаций монет для набора целевой суммы = \(res)") // Динамическое программирование с оптимизацией памяти res = coinChangeIIDPComp(coins: coins, amt: amt) print("Количество комбинаций монет для набора целевой суммы = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_dynamic_programming/edit_distance.swift ================================================ /** * File: edit_distance.swift * Created Time: 2023-07-16 * Author: nuomi1 (nuomi1@qq.com) */ /* Редакционное расстояние: полный перебор */ func editDistanceDFS(s: String, t: String, i: Int, j: Int) -> Int { // Если s и t пусты, вернуть 0 if i == 0, j == 0 { return 0 } // Если s пусто, вернуть длину t if i == 0 { return j } // Если t пусто, вернуть длину s if j == 0 { return i } // Если два символа равны, сразу пропустить их if s.utf8CString[i - 1] == t.utf8CString[j - 1] { return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) } // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) // Вернуть минимальное число шагов редактирования return min(min(insert, delete), replace) + 1 } /* Редакционное расстояние: поиск с мемоизацией */ func editDistanceDFSMem(s: String, t: String, mem: inout [[Int]], i: Int, j: Int) -> Int { // Если s и t пусты, вернуть 0 if i == 0, j == 0 { return 0 } // Если s пусто, вернуть длину t if i == 0 { return j } // Если t пусто, вернуть длину s if j == 0 { return i } // Если запись уже есть, сразу вернуть ее if mem[i][j] != -1 { return mem[i][j] } // Если два символа равны, сразу пропустить их if s.utf8CString[i - 1] == t.utf8CString[j - 1] { return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) } // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) // Сохранить и вернуть минимальное число шагов редактирования mem[i][j] = min(min(insert, delete), replace) + 1 return mem[i][j] } /* Редакционное расстояние: динамическое программирование */ func editDistanceDP(s: String, t: String) -> Int { let n = s.utf8CString.count let m = t.utf8CString.count var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1) // Переход состояний: первая строка и первый столбец for i in 1 ... n { dp[i][0] = i } for j in 1 ... m { dp[0][j] = j } // Переход состояний: остальные строки и столбцы for i in 1 ... n { for j in 1 ... m { if s.utf8CString[i - 1] == t.utf8CString[j - 1] { // Если два символа равны, сразу пропустить их dp[i][j] = dp[i - 1][j - 1] } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 } } } return dp[n][m] } /* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ func editDistanceDPComp(s: String, t: String) -> Int { let n = s.utf8CString.count let m = t.utf8CString.count var dp = Array(repeating: 0, count: m + 1) // Переход состояний: первая строка for j in 1 ... m { dp[j] = j } // Переход состояний: остальные строки for i in 1 ... n { // Переход состояний: первый столбец var leftup = dp[0] // Временно сохранить dp[i-1, j-1] dp[0] = i // Переход состояний: остальные столбцы for j in 1 ... m { let temp = dp[j] if s.utf8CString[i - 1] == t.utf8CString[j - 1] { // Если два символа равны, сразу пропустить их dp[j] = leftup } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 } leftup = temp // Обновить до значения dp[i-1, j-1] для следующей итерации } } return dp[m] } @main enum EditDistance { /* Driver Code */ static func main() { let s = "bag" let t = "pack" let n = s.utf8CString.count let m = t.utf8CString.count // Полный перебор var res = editDistanceDFS(s: s, t: t, i: n, j: m) print("Чтобы преобразовать \(s) в \(t), нужно минимум \(res) шагов") // Поиск с мемоизацией var mem = Array(repeating: Array(repeating: -1, count: m + 1), count: n + 1) res = editDistanceDFSMem(s: s, t: t, mem: &mem, i: n, j: m) print("Чтобы преобразовать \(s) в \(t), нужно минимум \(res) шагов") // Динамическое программирование res = editDistanceDP(s: s, t: t) print("Чтобы преобразовать \(s) в \(t), нужно минимум \(res) шагов") // Динамическое программирование с оптимизацией памяти res = editDistanceDPComp(s: s, t: t) print("Чтобы преобразовать \(s) в \(t), нужно минимум \(res) шагов") } } ================================================ FILE: ru/codes/swift/chapter_dynamic_programming/knapsack.swift ================================================ /** * File: knapsack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Рюкзак 0-1: полный перебор */ func knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if i == 0 || c == 0 { return 0 } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if wgt[i - 1] > c { return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] // Вернуть вариант с большей стоимостью из двух возможных return max(no, yes) } /* Рюкзак 0-1: поиск с мемоизацией */ func knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if i == 0 || c == 0 { return 0 } // Если запись уже есть, вернуть сразу if mem[i][c] != -1 { return mem[i][c] } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if wgt[i - 1] > c { return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] // Сохранить и вернуть вариант с большей стоимостью из двух решений mem[i][c] = max(no, yes) return mem[i][c] } /* Рюкзак 0-1: динамическое программирование */ func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // Инициализация таблицы dp var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) // Переход состояний for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c] } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) } } } return dp[n][cap] } /* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // Инициализация таблицы dp var dp = Array(repeating: 0, count: cap + 1) // Переход состояний for i in 1 ... n { // Обход в обратном порядке for c in (1 ... cap).reversed() { if wgt[i - 1] <= c { // Большее из двух решений: не брать или взять предмет i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) } } } return dp[cap] } @main enum Knapsack { /* Driver Code */ static func main() { let wgt = [10, 20, 30, 40, 50] let val = [50, 120, 150, 210, 240] let cap = 50 let n = wgt.count // Полный перебор var res = knapsackDFS(wgt: wgt, val: val, i: n, c: cap) print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") // Поиск с мемоизацией var mem = Array(repeating: Array(repeating: -1, count: cap + 1), count: n + 1) res = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: n, c: cap) print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") // Динамическое программирование res = knapsackDP(wgt: wgt, val: val, cap: cap) print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") // Динамическое программирование с оптимизацией памяти res = knapsackDPComp(wgt: wgt, val: val, cap: cap) print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift ================================================ /** * File: min_cost_climbing_stairs_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Минимальная стоимость подъема по лестнице: динамическое программирование */ func minCostClimbingStairsDP(cost: [Int]) -> Int { let n = cost.count - 1 if n == 1 || n == 2 { return cost[n] } // Инициализация таблицы dp для хранения решений подзадач var dp = Array(repeating: 0, count: n + 1) // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = cost[1] dp[2] = cost[2] // Переход состояний: постепенное решение больших подзадач через меньшие for i in 3 ... n { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] } return dp[n] } /* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ func minCostClimbingStairsDPComp(cost: [Int]) -> Int { let n = cost.count - 1 if n == 1 || n == 2 { return cost[n] } var (a, b) = (cost[1], cost[2]) for i in 3 ... n { (a, b) = (b, min(a, b) + cost[i]) } return b } @main enum MinCostClimbingStairsDP { /* Driver Code */ static func main() { let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] print("Список стоимостей ступеней = \(cost)") var res = minCostClimbingStairsDP(cost: cost) print("Минимальная стоимость подъема по лестнице = \(res)") res = minCostClimbingStairsDPComp(cost: cost) print("Минимальная стоимость подъема по лестнице = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_dynamic_programming/min_path_sum.swift ================================================ /** * File: min_path_sum.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Минимальная сумма пути: полный перебор */ func minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int { // Если это верхняя левая ячейка, завершить поиск if i == 0, j == 0 { return grid[0][0] } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if i < 0 || j < 0 { return .max } // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) let up = minPathSumDFS(grid: grid, i: i - 1, j: j) let left = minPathSumDFS(grid: grid, i: i, j: j - 1) // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) return min(left, up) + grid[i][j] } /* Минимальная сумма пути: поиск с мемоизацией */ func minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int { // Если это верхняя левая ячейка, завершить поиск if i == 0, j == 0 { return grid[0][0] } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if i < 0 || j < 0 { return .max } // Если запись уже есть, вернуть сразу if mem[i][j] != -1 { return mem[i][j] } // Минимальная стоимость пути для левой и верхней ячеек let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j) let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1) // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) mem[i][j] = min(left, up) + grid[i][j] return mem[i][j] } /* Минимальная сумма пути: динамическое программирование */ func minPathSumDP(grid: [[Int]]) -> Int { let n = grid.count let m = grid[0].count // Инициализация таблицы dp var dp = Array(repeating: Array(repeating: 0, count: m), count: n) dp[0][0] = grid[0][0] // Переход состояний: первая строка for j in 1 ..< m { dp[0][j] = dp[0][j - 1] + grid[0][j] } // Переход состояний: первый столбец for i in 1 ..< n { dp[i][0] = dp[i - 1][0] + grid[i][0] } // Переход состояний: остальные строки и столбцы for i in 1 ..< n { for j in 1 ..< m { dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] } } return dp[n - 1][m - 1] } /* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ func minPathSumDPComp(grid: [[Int]]) -> Int { let n = grid.count let m = grid[0].count // Инициализация таблицы dp var dp = Array(repeating: 0, count: m) // Переход состояний: первая строка dp[0] = grid[0][0] for j in 1 ..< m { dp[j] = dp[j - 1] + grid[0][j] } // Переход состояний: остальные строки for i in 1 ..< n { // Переход состояний: первый столбец dp[0] = dp[0] + grid[i][0] // Переход состояний: остальные столбцы for j in 1 ..< m { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] } } return dp[m - 1] } @main enum MinPathSum { /* Driver Code */ static func main() { let grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ] let n = grid.count let m = grid[0].count // Полный перебор var res = minPathSumDFS(grid: grid, i: n - 1, j: m - 1) print("Минимальная сумма пути из левого верхнего угла в правый нижний = \(res)") // Поиск с мемоизацией var mem = Array(repeating: Array(repeating: -1, count: m), count: n) res = minPathSumDFSMem(grid: grid, mem: &mem, i: n - 1, j: m - 1) print("Минимальная сумма пути из левого верхнего угла в правый нижний = \(res)") // Динамическое программирование res = minPathSumDP(grid: grid) print("Минимальная сумма пути из левого верхнего угла в правый нижний = \(res)") // Динамическое программирование с оптимизацией памяти res = minPathSumDPComp(grid: grid) print("Минимальная сумма пути из левого верхнего угла в правый нижний = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift ================================================ /** * File: unbounded_knapsack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* Полный рюкзак: динамическое программирование */ func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // Инициализация таблицы dp var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) // Переход состояний for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c] } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) } } } return dp[n][cap] } /* Полный рюкзак: динамическое программирование с оптимизацией памяти */ func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // Инициализация таблицы dp var dp = Array(repeating: 0, count: cap + 1) // Переход состояний for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // Если вместимость рюкзака превышена, предмет i не выбирать dp[c] = dp[c] } else { // Большее из двух решений: не брать или взять предмет i dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) } } } return dp[cap] } @main enum UnboundedKnapsack { /* Driver Code */ static func main() { let wgt = [1, 2, 3] let val = [5, 11, 15] let cap = 4 // Динамическое программирование var res = unboundedKnapsackDP(wgt: wgt, val: val, cap: cap) print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") // Динамическое программирование с оптимизацией памяти res = unboundedKnapsackDPComp(wgt: wgt, val: val, cap: cap) print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_graph/graph_adjacency_list.swift ================================================ /** * File: graph_adjacency_list.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Класс неориентированного графа на основе списка смежности */ public class GraphAdjList { // Список смежности, где key — вершина, а value — все смежные ей вершины public private(set) var adjList: [Vertex: [Vertex]] /* Конструктор */ public init(edges: [[Vertex]]) { adjList = [:] // Добавить все вершины и ребра for edge in edges { addVertex(vet: edge[0]) addVertex(vet: edge[1]) addEdge(vet1: edge[0], vet2: edge[1]) } } /* Получить число вершин */ public func size() -> Int { adjList.count } /* Добавление ребра */ public func addEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("Неверный аргумент") } // Добавить ребро vet1 - vet2 adjList[vet1]?.append(vet2) adjList[vet2]?.append(vet1) } /* Удаление ребра */ public func removeEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("Неверный аргумент") } // Удалить ребро vet1 - vet2 adjList[vet1]?.removeAll { $0 == vet2 } adjList[vet2]?.removeAll { $0 == vet1 } } /* Добавление вершины */ public func addVertex(vet: Vertex) { if adjList[vet] != nil { return } // Добавить новый список в список смежности adjList[vet] = [] } /* Удаление вершины */ public func removeVertex(vet: Vertex) { if adjList[vet] == nil { fatalError("Неверный аргумент") } // Удалить из списка смежности список, соответствующий вершине vet adjList.removeValue(forKey: vet) // Обойти списки других вершин и удалить все ребра, содержащие vet for key in adjList.keys { adjList[key]?.removeAll { $0 == vet } } } /* Вывести список смежности */ public func print() { Swift.print("Список смежности =") for (vertex, list) in adjList { let list = list.map { $0.val } Swift.print("\(vertex.val): \(list),") } } } #if !TARGET @main enum GraphAdjacencyList { /* Driver Code */ static func main() { /* Инициализация неориентированного графа */ let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] let graph = GraphAdjList(edges: edges) print("\nГраф после инициализации") graph.print() /* Добавление ребра */ // Вершины 1 и 2 соответствуют v[0] и v[2] graph.addEdge(vet1: v[0], vet2: v[2]) print("\nГраф после добавления ребра 1-2") graph.print() /* Удаление ребра */ // Вершины 1 и 3 соответствуют v[0] и v[1] graph.removeEdge(vet1: v[0], vet2: v[1]) print("\nГраф после удаления ребра 1-3") graph.print() /* Добавление вершины */ let v5 = Vertex(val: 6) graph.addVertex(vet: v5) print("\nГраф после добавления вершины 6") graph.print() /* Удаление вершины */ // Вершина 3 соответствует v[1] graph.removeVertex(vet: v[1]) print("\nГраф после удаления вершины 3") graph.print() } } #endif ================================================ FILE: ru/codes/swift/chapter_graph/graph_adjacency_list_target.swift ================================================ /** * File: graph_adjacency_list.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Класс неориентированного графа на основе списка смежности */ public class GraphAdjList { // Список смежности, где key — вершина, а value — все смежные ей вершины public private(set) var adjList: [Vertex: [Vertex]] /* Конструктор */ public init(edges: [[Vertex]]) { adjList = [:] // Добавить все вершины и ребра for edge in edges { addVertex(vet: edge[0]) addVertex(vet: edge[1]) addEdge(vet1: edge[0], vet2: edge[1]) } } /* Получить число вершин */ public func size() -> Int { adjList.count } /* Добавление ребра */ public func addEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("Неверный аргумент") } // Добавить ребро vet1 - vet2 adjList[vet1]?.append(vet2) adjList[vet2]?.append(vet1) } /* Удаление ребра */ public func removeEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("Неверный аргумент") } // Удалить ребро vet1 - vet2 adjList[vet1]?.removeAll { $0 == vet2 } adjList[vet2]?.removeAll { $0 == vet1 } } /* Добавление вершины */ public func addVertex(vet: Vertex) { if adjList[vet] != nil { return } // Добавить новый список в список смежности adjList[vet] = [] } /* Удаление вершины */ public func removeVertex(vet: Vertex) { if adjList[vet] == nil { fatalError("Неверный аргумент") } // Удалить из списка смежности список, соответствующий вершине vet adjList.removeValue(forKey: vet) // Обойти списки других вершин и удалить все ребра, содержащие vet for key in adjList.keys { adjList[key]?.removeAll { $0 == vet } } } /* Вывести список смежности */ public func print() { Swift.print("Список смежности =") for (vertex, list) in adjList { let list = list.map { $0.val } Swift.print("\(vertex.val): \(list),") } } } #if !TARGET @main enum GraphAdjacencyList { /* Driver Code */ static func main() { /* Инициализация неориентированного графа */ let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] let graph = GraphAdjList(edges: edges) print("\nГраф после инициализации") graph.print() /* Добавление ребра */ // Вершины 1 и 2 соответствуют v[0] и v[2] graph.addEdge(vet1: v[0], vet2: v[2]) print("\nГраф после добавления ребра 1-2") graph.print() /* Удаление ребра */ // Вершины 1 и 3 соответствуют v[0] и v[1] graph.removeEdge(vet1: v[0], vet2: v[1]) print("\nГраф после удаления ребра 1-3") graph.print() /* Добавление вершины */ let v5 = Vertex(val: 6) graph.addVertex(vet: v5) print("\nГраф после добавления вершины 6") graph.print() /* Удаление вершины */ // Вершина 3 соответствует v[1] graph.removeVertex(vet: v[1]) print("\nГраф после удаления вершины 3") graph.print() } } #endif ================================================ FILE: ru/codes/swift/chapter_graph/graph_adjacency_matrix.swift ================================================ /** * File: graph_adjacency_matrix.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Класс неориентированного графа на основе матрицы смежности */ class GraphAdjMat { private var vertices: [Int] // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» private var adjMat: [[Int]] // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» /* Конструктор */ init(vertices: [Int], edges: [[Int]]) { self.vertices = [] adjMat = [] // Добавление вершины for val in vertices { addVertex(val: val) } // Добавить ребра // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices for e in edges { addEdge(i: e[0], j: e[1]) } } /* Получить число вершин */ func size() -> Int { vertices.count } /* Добавление вершины */ func addVertex(val: Int) { let n = size() // Добавить значение новой вершины в список вершин vertices.append(val) // Добавить строку в матрицу смежности let newRow = Array(repeating: 0, count: n) adjMat.append(newRow) // Добавить столбец в матрицу смежности for i in adjMat.indices { adjMat[i].append(0) } } /* Удаление вершины */ func removeVertex(index: Int) { if index >= size() { fatalError("Выход за границы диапазона") } // Удалить вершину с индексом index из списка вершин vertices.remove(at: index) // Удалить строку с индексом index из матрицы смежности adjMat.remove(at: index) // Удалить столбец с индексом index из матрицы смежности for i in adjMat.indices { adjMat[i].remove(at: index) } } /* Добавление ребра */ // Параметры i и j соответствуют индексам элементов vertices func addEdge(i: Int, j: Int) { // Обработка выхода индекса за границы и случая равенства if i < 0 || j < 0 || i >= size() || j >= size() || i == j { fatalError("Выход за границы диапазона") } // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) adjMat[i][j] = 1 adjMat[j][i] = 1 } /* Удаление ребра */ // Параметры i и j соответствуют индексам элементов vertices func removeEdge(i: Int, j: Int) { // Обработка выхода индекса за границы и случая равенства if i < 0 || j < 0 || i >= size() || j >= size() || i == j { fatalError("Выход за границы диапазона") } adjMat[i][j] = 0 adjMat[j][i] = 0 } /* Вывести матрицу смежности */ func print() { Swift.print("Список вершин = ", terminator: "") Swift.print(vertices) Swift.print("Матрица смежности =") PrintUtil.printMatrix(matrix: adjMat) } } @main enum GraphAdjacencyMatrix { /* Driver Code */ static func main() { /* Инициализация неориентированного графа */ // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices let vertices = [1, 3, 2, 5, 4] let edges = [[0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4]] let graph = GraphAdjMat(vertices: vertices, edges: edges) print("\nГраф после инициализации") graph.print() /* Добавление ребра */ // Индексы вершин 1 и 2 равны 0 и 2 соответственно graph.addEdge(i: 0, j: 2) print("\nГраф после добавления ребра 1-2") graph.print() /* Удаление ребра */ // Индексы вершин 1 и 3 равны 0 и 1 соответственно graph.removeEdge(i: 0, j: 1) print("\nГраф после удаления ребра 1-3") graph.print() /* Добавление вершины */ graph.addVertex(val: 6) print("\nГраф после добавления вершины 6") graph.print() /* Удаление вершины */ // Индекс вершины 3 равен 1 graph.removeVertex(index: 1) print("\nГраф после удаления вершины 3") graph.print() } } ================================================ FILE: ru/codes/swift/chapter_graph/graph_bfs.swift ================================================ /** * File: graph_bfs.swift * Created Time: 2023-02-21 * Author: nuomi1 (nuomi1@qq.com) */ import graph_adjacency_list_target import utils /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { // Последовательность обхода вершин var res: [Vertex] = [] // Хеш-множество для хранения уже посещенных вершин var visited: Set = [startVet] // Очередь используется для реализации BFS var que: [Vertex] = [startVet] // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while !que.isEmpty { let vet = que.removeFirst() // Извлечь головную вершину из очереди res.append(vet) // Отметить посещенную вершину // Обойти все смежные вершины данной вершины for adjVet in graph.adjList[vet] ?? [] { if visited.contains(adjVet) { continue // Пропустить уже посещенную вершину } que.append(adjVet) // Помещать в очередь только непосещенные вершины visited.insert(adjVet) // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res } @main enum GraphBFS { /* Driver Code */ static func main() { /* Инициализация неориентированного графа */ let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) let edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ] let graph = GraphAdjList(edges: edges) print("\nГраф после инициализации") graph.print() /* Обход в ширину */ let res = graphBFS(graph: graph, startVet: v[0]) print("\nПоследовательность вершин при обходе в ширину (BFS)") print(Vertex.vetsToVals(vets: res)) } } ================================================ FILE: ru/codes/swift/chapter_graph/graph_dfs.swift ================================================ /** * File: graph_dfs.swift * Created Time: 2023-02-21 * Author: nuomi1 (nuomi1@qq.com) */ import graph_adjacency_list_target import utils /* Вспомогательная функция обхода в глубину */ func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { res.append(vet) // Отметить посещенную вершину visited.insert(vet) // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины for adjVet in graph.adjList[vet] ?? [] { if visited.contains(adjVet) { continue // Пропустить уже посещенную вершину } // Рекурсивно обходить смежные вершины dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { // Последовательность обхода вершин var res: [Vertex] = [] // Хеш-множество для хранения уже посещенных вершин var visited: Set = [] dfs(graph: graph, visited: &visited, res: &res, vet: startVet) return res } @main enum GraphDFS { /* Driver Code */ static func main() { /* Инициализация неориентированного графа */ let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6]) let edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ] let graph = GraphAdjList(edges: edges) print("\nГраф после инициализации") graph.print() /* Обход в глубину */ let res = graphDFS(graph: graph, startVet: v[0]) print("\nПоследовательность вершин при обходе в глубину (DFS)") print(Vertex.vetsToVals(vets: res)) } } ================================================ FILE: ru/codes/swift/chapter_greedy/coin_change_greedy.swift ================================================ /** * File: coin_change_greedy.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* Размен монет: жадный алгоритм */ func coinChangeGreedy(coins: [Int], amt: Int) -> Int { // Предположить, что список coins упорядочен var i = coins.count - 1 var count = 0 var amt = amt // Циклически выполнять жадный выбор, пока не останется суммы while amt > 0 { // Найти монету, которая меньше остатка суммы и наиболее к нему близка while i > 0 && coins[i] > amt { i -= 1 } // Выбрать coins[i] amt -= coins[i] count += 1 } // Если допустимое решение не найдено, вернуть -1 return amt == 0 ? count : -1 } @main enum CoinChangeGreedy { /* Driver Code */ static func main() { // Жадный подход: гарантирует нахождение глобально оптимального решения var coins = [1, 5, 10, 20, 50, 100] var amt = 186 var res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("Минимальное число монет для набора суммы \(amt) = \(res)") // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = [1, 20, 50] amt = 60 res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("Минимальное число монет для набора суммы \(amt) = \(res)") print("На самом деле минимум равен 3: 20 + 20 + 20") // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = [1, 49, 50] amt = 98 res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("Минимальное число монет для набора суммы \(amt) = \(res)") print("На самом деле минимум равен 2: 49 + 49") } } ================================================ FILE: ru/codes/swift/chapter_greedy/fractional_knapsack.swift ================================================ /** * File: fractional_knapsack.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* Предмет */ class Item { var w: Int // Вес предмета var v: Int // Стоимость предмета init(w: Int, v: Int) { self.w = w self.v = v } } /* Дробный рюкзак: жадный алгоритм */ func fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double { // Создать список предметов с двумя свойствами: вес и стоимость var items = zip(wgt, val).map { Item(w: $0, v: $1) } // Отсортировать по удельной стоимости item.v / item.w в порядке убывания items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) } // Циклический жадный выбор var res = 0.0 var cap = cap for item in items { if item.w <= cap { // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком res += Double(item.v) cap -= item.w } else { // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета res += Double(item.v) / Double(item.w) * Double(cap) // Свободной вместимости больше не осталось, поэтому выйти из цикла break } } return res } @main enum FractionalKnapsack { /* Driver Code */ static func main() { // Вес предмета let wgt = [10, 20, 30, 40, 50] // Стоимость предмета let val = [50, 120, 150, 210, 240] // Вместимость рюкзака let cap = 50 // Жадный алгоритм let res = fractionalKnapsack(wgt: wgt, val: val, cap: cap) print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_greedy/max_capacity.swift ================================================ /** * File: max_capacity.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* Максимальная вместимость: жадный алгоритм */ func maxCapacity(ht: [Int]) -> Int { // Инициализировать i и j так, чтобы они располагались по двум концам массива var i = ht.startIndex, j = ht.endIndex - 1 // Начальная максимальная вместимость равна 0 var res = 0 // Выполнять жадный выбор в цикле, пока две доски не встретятся while i < j { // Обновить максимальную вместимость let cap = min(ht[i], ht[j]) * (j - i) res = max(res, cap) // Сдвигать внутрь более короткую сторону if ht[i] < ht[j] { i += 1 } else { j -= 1 } } return res } @main enum MaxCapacity { /* Driver Code */ static func main() { let ht = [3, 8, 5, 2, 7, 7, 3, 4] // Жадный алгоритм let res = maxCapacity(ht: ht) print("Максимальная вместимость = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_greedy/max_product_cutting.swift ================================================ /** * File: max_product_cutting.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ import Foundation func pow(_ x: Int, _ y: Int) -> Int { Int(Double(truncating: pow(Decimal(x), y) as NSDecimalNumber)) } /* Максимальное произведение разрезания: жадный алгоритм */ func maxProductCutting(n: Int) -> Int { // Когда n <= 3, обязательно нужно выделить одну 1 if n <= 3 { return 1 * (n - 1) } // Жадно выделить множители 3, где a — число троек, а b — остаток let a = n / 3 let b = n % 3 if b == 1 { // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 return pow(3, a - 1) * 2 * 2 } if b == 2 { // Если остаток равен 2, ничего не делать return pow(3, a) * 2 } // Если остаток равен 0, ничего не делать return pow(3, a) } @main enum MaxProductCutting { static func main() { let n = 58 // Жадный алгоритм let res = maxProductCutting(n: n) print("Максимальное произведение после разрезания = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_hashing/array_hash_map.swift ================================================ /** * File: array_hash_map.swift * Created Time: 2023-01-16 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Хеш-таблица на основе массива */ class ArrayHashMap { private var buckets: [Pair?] init() { // Инициализировать массив, содержащий 100 корзин buckets = Array(repeating: nil, count: 100) } /* Хеш-функция */ private func hashFunc(key: Int) -> Int { let index = key % 100 return index } /* Операция поиска */ func get(key: Int) -> String? { let index = hashFunc(key: key) let pair = buckets[index] return pair?.val } /* Операция добавления */ func put(key: Int, val: String) { let pair = Pair(key: key, val: val) let index = hashFunc(key: key) buckets[index] = pair } /* Операция удаления */ func remove(key: Int) { let index = hashFunc(key: key) // Присвоить nil, что означает удаление buckets[index] = nil } /* Получить все пары ключ-значение */ func pairSet() -> [Pair] { buckets.compactMap { $0 } } /* Получить все ключи */ func keySet() -> [Int] { buckets.compactMap { $0?.key } } /* Получить все значения */ func valueSet() -> [String] { buckets.compactMap { $0?.val } } /* Вывести хеш-таблицу */ func print() { for pair in pairSet() { Swift.print("\(pair.key) -> \(pair.val)") } } } @main enum _ArrayHashMap { /* Driver Code */ static func main() { /* Инициализация хеш-таблицы */ let map = ArrayHashMap() /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(key: 12836, val: "Сяо Ха") map.put(key: 15937, val: "Сяо Ло") map.put(key: 16750, val: "Сяо Суань") map.put(key: 13276, val: "Сяо Фа") map.put(key: 10583, val: "Сяо Я") print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") map.print() /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value let name = map.get(key: 15937)! print("\nДля номера 15937 найдено имя \(name)") /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(key: 10583) print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") map.print() /* Обход хеш-таблицы */ print("\nОтдельный обход пар ключ-значение") for pair in map.pairSet() { print("\(pair.key) -> \(pair.val)") } print("\nОтдельный обход ключей") for key in map.keySet() { print(key) } print("\nОтдельный обход значений") for val in map.valueSet() { print(val) } } } ================================================ FILE: ru/codes/swift/chapter_hashing/built_in_hash.swift ================================================ /** * File: built_in_hash.swift * Created Time: 2023-07-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum BuiltInHash { /* Driver Code */ static func main() { let num = 3 let hashNum = num.hashValue print("Хеш-значение целого числа \(num) = \(hashNum)") let bol = true let hashBol = bol.hashValue print("Хеш-значение булева значения \(bol) = \(hashBol)") let dec = 3.14159 let hashDec = dec.hashValue print("Хеш-значение десятичного числа \(dec) = \(hashDec)") let str = "Hello Algo" let hashStr = str.hashValue print("Хеш-значение строки \(str) = \(hashStr)") let arr = [AnyHashable(12836), AnyHashable("Сяо Ха")] let hashTup = arr.hashValue print("Хеш-значение массива \(arr) = \(hashTup)") let obj = ListNode(x: 0) let hashObj = obj.hashValue print("Хеш-значение объекта узла \(obj) = \(hashObj)") } } ================================================ FILE: ru/codes/swift/chapter_hashing/hash_map.swift ================================================ /** * File: hash_map.swift * Created Time: 2023-01-16 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum HashMap { /* Driver Code */ static func main() { /* Инициализация хеш-таблицы */ var map: [Int: String] = [:] /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map[12836] = "Сяо Ха" map[15937] = "Сяо Ло" map[16750] = "Сяо Суань" map[13276] = "Сяо Фа" map[10583] = "Сяо Я" print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") PrintUtil.printHashMap(map: map) /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value let name = map[15937]! print("\nДля номера 15937 найдено имя \(name)") /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.removeValue(forKey: 10583) print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") PrintUtil.printHashMap(map: map) /* Обход хеш-таблицы */ print("\nОтдельный обход пар ключ-значение") for (key, value) in map { print("\(key) -> \(value)") } print("\nОтдельный обход ключей") for key in map.keys { print(key) } print("\nОтдельный обход значений") for value in map.values { print(value) } } } ================================================ FILE: ru/codes/swift/chapter_hashing/hash_map_chaining.swift ================================================ /** * File: hash_map_chaining.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Хеш-таблица с цепочками */ class HashMapChaining { var size: Int // Число пар ключ-значение var capacity: Int // Вместимость хеш-таблицы var loadThres: Double // Порог коэффициента загрузки для запуска расширения var extendRatio: Int // Коэффициент расширения var buckets: [[Pair]] // Массив корзин /* Конструктор */ init() { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = Array(repeating: [], count: capacity) } /* Хеш-функция */ func hashFunc(key: Int) -> Int { key % capacity } /* Коэффициент загрузки */ func loadFactor() -> Double { Double(size) / Double(capacity) } /* Операция поиска */ func get(key: Int) -> String? { let index = hashFunc(key: key) let bucket = buckets[index] // Обойти корзину; если найден key, вернуть соответствующее val for pair in bucket { if pair.key == key { return pair.val } } // Если key не найден, вернуть nil return nil } /* Операция добавления */ func put(key: Int, val: String) { // Когда коэффициент загрузки превышает порог, выполнить расширение if loadFactor() > loadThres { extend() } let index = hashFunc(key: key) let bucket = buckets[index] // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть for pair in bucket { if pair.key == key { pair.val = val return } } // Если такого key нет, добавить пару ключ-значение в конец let pair = Pair(key: key, val: val) buckets[index].append(pair) size += 1 } /* Операция удаления */ func remove(key: Int) { let index = hashFunc(key: key) let bucket = buckets[index] // Обойти корзину и удалить из нее пару ключ-значение for (pairIndex, pair) in bucket.enumerated() { if pair.key == key { buckets[index].remove(at: pairIndex) size -= 1 break } } } /* Расширить хеш-таблицу */ func extend() { // Временно сохранить исходную хеш-таблицу let bucketsTmp = buckets // Инициализация новой хеш-таблицы после расширения capacity *= extendRatio buckets = Array(repeating: [], count: capacity) size = 0 // Перенести пары ключ-значение из исходной хеш-таблицы в новую for bucket in bucketsTmp { for pair in bucket { put(key: pair.key, val: pair.val) } } } /* Вывести хеш-таблицу */ func print() { for bucket in buckets { let res = bucket.map { "\($0.key) -> \($0.val)" } Swift.print(res) } } } @main enum _HashMapChaining { /* Driver Code */ static func main() { /* Инициализация хеш-таблицы */ let map = HashMapChaining() /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(key: 12836, val: "Сяо Ха") map.put(key: 15937, val: "Сяо Ло") map.put(key: 16750, val: "Сяо Суань") map.put(key: 13276, val: "Сяо Фа") map.put(key: 10583, val: "Сяо Я") print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") map.print() /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value let name = map.get(key: 13276) print("\nДля номера 13276 найдено имя \(name!)") /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(key: 12836) print("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение") map.print() } } ================================================ FILE: ru/codes/swift/chapter_hashing/hash_map_open_addressing.swift ================================================ /** * File: hash_map_open_addressing.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Хеш-таблица с открытой адресацией */ class HashMapOpenAddressing { var size: Int // Число пар ключ-значение var capacity: Int // Вместимость хеш-таблицы var loadThres: Double // Порог коэффициента загрузки для запуска расширения var extendRatio: Int // Коэффициент расширения var buckets: [Pair?] // Массив корзин var TOMBSTONE: Pair // Удалить метку /* Конструктор */ init() { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = Array(repeating: nil, count: capacity) TOMBSTONE = Pair(key: -1, val: "-1") } /* Хеш-функция */ func hashFunc(key: Int) -> Int { key % capacity } /* Коэффициент загрузки */ func loadFactor() -> Double { Double(size) / Double(capacity) } /* Найти индекс корзины, соответствующий key */ func findBucket(key: Int) -> Int { var index = hashFunc(key: key) var firstTombstone = -1 // Выполнять линейное пробирование и завершить при встрече с пустой корзиной while buckets[index] != nil { // Если встретился key, вернуть соответствующий индекс корзины if buckets[index]!.key == key { // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс if firstTombstone != -1 { buckets[firstTombstone] = buckets[index] buckets[index] = TOMBSTONE return firstTombstone // Вернуть индекс корзины после перемещения } return index // Вернуть индекс корзины } // Записать первую встретившуюся метку удаления if firstTombstone == -1 && buckets[index] == TOMBSTONE { firstTombstone = index } // Вычислить индекс корзины; при выходе за конец вернуться к началу index = (index + 1) % capacity } // Если key не существует, вернуть индекс точки добавления return firstTombstone == -1 ? index : firstTombstone } /* Операция поиска */ func get(key: Int) -> String? { // Найти индекс корзины, соответствующий key let index = findBucket(key: key) // Если пара ключ-значение найдена, вернуть соответствующее val if buckets[index] != nil, buckets[index] != TOMBSTONE { return buckets[index]!.val } // Если пары ключ-значение не существует, вернуть null return nil } /* Операция добавления */ func put(key: Int, val: String) { // Когда коэффициент загрузки превышает порог, выполнить расширение if loadFactor() > loadThres { extend() } // Найти индекс корзины, соответствующий key let index = findBucket(key: key) // Если пара ключ-значение найдена, перезаписать val и вернуть if buckets[index] != nil, buckets[index] != TOMBSTONE { buckets[index]!.val = val return } // Если пары ключ-значение нет, добавить ее buckets[index] = Pair(key: key, val: val) size += 1 } /* Операция удаления */ func remove(key: Int) { // Найти индекс корзины, соответствующий key let index = findBucket(key: key) // Если пара ключ-значение найдена, заменить ее меткой удаления if buckets[index] != nil, buckets[index] != TOMBSTONE { buckets[index] = TOMBSTONE size -= 1 } } /* Расширить хеш-таблицу */ func extend() { // Временно сохранить исходную хеш-таблицу let bucketsTmp = buckets // Инициализация новой хеш-таблицы после расширения capacity *= extendRatio buckets = Array(repeating: nil, count: capacity) size = 0 // Перенести пары ключ-значение из исходной хеш-таблицы в новую for pair in bucketsTmp { if let pair, pair != TOMBSTONE { put(key: pair.key, val: pair.val) } } } /* Вывести хеш-таблицу */ func print() { for pair in buckets { if pair == nil { Swift.print("null") } else if pair == TOMBSTONE { Swift.print("TOMBSTONE") } else { Swift.print("\(pair!.key) -> \(pair!.val)") } } } } @main enum _HashMapOpenAddressing { /* Driver Code */ static func main() { /* Инициализация хеш-таблицы */ let map = HashMapOpenAddressing() /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(key: 12836, val: "Сяо Ха") map.put(key: 15937, val: "Сяо Ло") map.put(key: 16750, val: "Сяо Суань") map.put(key: 13276, val: "Сяо Фа") map.put(key: 10583, val: "Сяо Я") print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") map.print() /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value let name = map.get(key: 13276) print("\nДля номера 13276 найдено имя \(name!)") /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(key: 16750) print("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение") map.print() } } ================================================ FILE: ru/codes/swift/chapter_hashing/simple_hash.swift ================================================ /** * File: simple_hash.swift * Created Time: 2023-07-01 * Author: nuomi1 (nuomi1@qq.com) */ /* Аддитивное хеширование */ func addHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = (hash + Int(scalar.value)) % MODULUS } } return hash } /* Мультипликативное хеширование */ func mulHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = (31 * hash + Int(scalar.value)) % MODULUS } } return hash } /* XOR-хеширование */ func xorHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash ^= Int(scalar.value) } } return hash & MODULUS } /* Хеширование с циклическим сдвигом */ func rotHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS } } return hash } @main enum SimpleHash { /* Driver Code */ static func main() { let key = "Hello Algo" var hash = addHash(key: key) print("Хеш-сумма сложением = \(hash)") hash = mulHash(key: key) print("Хеш-сумма умножением = \(hash)") hash = xorHash(key: key) print("Хеш-сумма XOR = \(hash)") hash = rotHash(key: key) print("Хеш-сумма с циклическим сдвигом = \(hash)") } } ================================================ FILE: ru/codes/swift/chapter_heap/heap.swift ================================================ /** * File: heap.swift * Created Time: 2024-03-17 * Author: nuomi1 (nuomi1@qq.com) */ import HeapModule import utils func testPush(heap: inout Heap, val: Int) { heap.insert(val) print("\nПосле добавления элемента \(val) в кучу\n") PrintUtil.printHeap(queue: heap.unordered) } func testPop(heap: inout Heap) { let val = heap.removeMax() print("\nПосле извлечения элемента вершины кучи \(val)\n") PrintUtil.printHeap(queue: heap.unordered) } @main enum _Heap { /* Driver Code */ static func main() { /* Инициализация кучи */ // Тип Heap в Swift одновременно поддерживает максимальную и минимальную кучу var heap = Heap() /* Добавление элемента в кучу */ testPush(heap: &heap, val: 1) testPush(heap: &heap, val: 3) testPush(heap: &heap, val: 2) testPush(heap: &heap, val: 5) testPush(heap: &heap, val: 4) /* Получение элемента с вершины кучи */ let peek = heap.max() print("\nЭлемент на вершине кучи = \(peek!)\n") /* Извлечение элемента с вершины кучи */ testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) /* Получение размера кучи */ let size = heap.count print("\nКоличество элементов в куче = \(size)\n") /* Проверка, пуста ли куча */ let isEmpty = heap.isEmpty print("\nПуста ли куча: \(isEmpty)\n") /* Построить кучу по входному списку */ // Временная сложность равна O(n), а не O(nlogn) let heap2 = Heap([1, 3, 2, 5, 4]) print("\nПосле построения кучи из входного списка") PrintUtil.printHeap(queue: heap2.unordered) } } ================================================ FILE: ru/codes/swift/chapter_heap/my_heap.swift ================================================ /** * File: my_heap.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Максимальная куча */ class MaxHeap { private var maxHeap: [Int] /* Конструктор, строящий кучу по входному списку */ init(nums: [Int]) { // Добавить элементы списка в кучу без изменений maxHeap = nums // Выполнить heapify для всех узлов, кроме листовых for i in (0 ... parent(i: size() - 1)).reversed() { siftDown(i: i) } } /* Получить индекс левого дочернего узла */ private func left(i: Int) -> Int { 2 * i + 1 } /* Получить индекс правого дочернего узла */ private func right(i: Int) -> Int { 2 * i + 2 } /* Получить индекс родительского узла */ private func parent(i: Int) -> Int { (i - 1) / 2 // Округление вниз при делении } /* Поменять элементы местами */ private func swap(i: Int, j: Int) { maxHeap.swapAt(i, j) } /* Получение размера кучи */ func size() -> Int { maxHeap.count } /* Проверка, пуста ли куча */ func isEmpty() -> Bool { size() == 0 } /* Доступ к элементу на вершине кучи */ func peek() -> Int { maxHeap[0] } /* Добавление элемента в кучу */ func push(val: Int) { // Добавление узла maxHeap.append(val) // Просеивание снизу вверх siftUp(i: size() - 1) } /* Начиная с узла i, выполнить просеивание снизу вверх */ private func siftUp(i: Int) { var i = i while true { // Получение родительского узла для узла i let p = parent(i: i) // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» if p < 0 || maxHeap[i] <= maxHeap[p] { break } // Поменять два узла местами swap(i: i, j: p) // Циклическое просеивание вверх i = p } } /* Извлечение элемента из кучи */ func pop() -> Int { // Обработка пустого случая if isEmpty() { fatalError("куча пуста") } // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) swap(i: 0, j: size() - 1) // Удаление узла let val = maxHeap.remove(at: size() - 1) // Просеивание сверху вниз siftDown(i: 0) // Вернуть элемент с вершины кучи return val } /* Начиная с узла i, выполнить просеивание сверху вниз */ private func siftDown(i: Int) { var i = i while true { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma let l = left(i: i) let r = right(i: i) var ma = i if l < size(), maxHeap[l] > maxHeap[ma] { ma = l } if r < size(), maxHeap[r] > maxHeap[ma] { ma = r } // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if ma == i { break } // Поменять два узла местами swap(i: i, j: ma) // Циклическое просеивание вниз i = ma } } /* Вывести кучу (двоичное дерево) */ func print() { let queue = maxHeap PrintUtil.printHeap(queue: queue) } } @main enum MyHeap { /* Driver Code */ static func main() { /* Инициализация максимальной кучи */ let maxHeap = MaxHeap(nums: [9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) print("\nПосле построения кучи из входного списка") maxHeap.print() /* Получение элемента с вершины кучи */ var peek = maxHeap.peek() print("\nЭлемент на вершине кучи = \(peek)") /* Добавление элемента в кучу */ let val = 7 maxHeap.push(val: val) print("\nПосле добавления элемента \(val) в кучу") maxHeap.print() /* Извлечение элемента с вершины кучи */ peek = maxHeap.pop() print("\nПосле извлечения элемента вершины кучи \(peek)") maxHeap.print() /* Получение размера кучи */ let size = maxHeap.size() print("\nКоличество элементов в куче = \(size)") /* Проверка, пуста ли куча */ let isEmpty = maxHeap.isEmpty() print("\nПуста ли куча: \(isEmpty)") } } ================================================ FILE: ru/codes/swift/chapter_heap/top_k.swift ================================================ /** * File: top_k.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ import HeapModule import utils /* Найти k наибольших элементов массива с помощью кучи */ func topKHeap(nums: [Int], k: Int) -> [Int] { // Инициализировать минимальную кучу и построить ее по первым k элементам var heap = Heap(nums.prefix(k)) // Начиная с элемента k+1, поддерживать длину кучи равной k for i in nums.indices.dropFirst(k) { // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу if nums[i] > heap.min()! { _ = heap.removeMin() heap.insert(nums[i]) } } return heap.unordered } @main enum TopK { /* Driver Code */ static func main() { let nums = [1, 7, 6, 3, 2] let k = 3 let res = topKHeap(nums: nums, k: k) print("Наибольшие \(k) элементов") PrintUtil.printHeap(queue: res) } } ================================================ FILE: ru/codes/swift/chapter_searching/binary_search.swift ================================================ /** * File: binary_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ /* Бинарный поиск (двусторонне замкнутый интервал) */ func binarySearch(nums: [Int], target: Int) -> Int { // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно var i = nums.startIndex var j = nums.endIndex - 1 // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) while i <= j { let m = i + (j - i) / 2 // Вычислить индекс середины m if nums[m] < target { // Это означает, что target находится в интервале [m+1, j] i = m + 1 } else if nums[m] > target { // Это означает, что target находится в интервале [i, m-1] j = m - 1 } else { // Целевой элемент найден, вернуть его индекс return m } } // Целевой элемент не найден, вернуть -1 return -1 } /* Бинарный поиск (лево замкнутый, право открытый интервал) */ func binarySearchLCRO(nums: [Int], target: Int) -> Int { // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно var i = nums.startIndex var j = nums.endIndex // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) while i < j { let m = i + (j - i) / 2 // Вычислить индекс середины m if nums[m] < target { // Это означает, что target находится в интервале [m+1, j) i = m + 1 } else if nums[m] > target { // Это означает, что target находится в интервале [i, m) j = m } else { // Целевой элемент найден, вернуть его индекс return m } } // Целевой элемент не найден, вернуть -1 return -1 } @main enum BinarySearch { /* Driver Code */ static func main() { let target = 6 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] /* Бинарный поиск (двусторонне замкнутый интервал) */ var index = binarySearch(nums: nums, target: target) print("Индекс целевого элемента 6 = \(index)") /* Бинарный поиск (лево замкнутый, право открытый интервал) */ index = binarySearchLCRO(nums: nums, target: target) print("Индекс целевого элемента 6 = \(index)") } } ================================================ FILE: ru/codes/swift/chapter_searching/binary_search_edge.swift ================================================ /** * File: binary_search_edge.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ import binary_search_insertion_target /* Бинарный поиск самого левого target */ func binarySearchLeftEdge(nums: [Int], target: Int) -> Int { // Эквивалентно поиску точки вставки target let i = binarySearchInsertion(nums: nums, target: target) // target не найден, вернуть -1 if i == nums.endIndex || nums[i] != target { return -1 } // Найти target и вернуть индекс i return i } /* Бинарный поиск самого правого target */ func binarySearchRightEdge(nums: [Int], target: Int) -> Int { // Преобразовать задачу в поиск самого левого target + 1 let i = binarySearchInsertion(nums: nums, target: target + 1) // j указывает на самый правый target, а i — на первый элемент больше target let j = i - 1 // target не найден, вернуть -1 if j == -1 || nums[j] != target { return -1 } // Найти target и вернуть индекс j return j } @main enum BinarySearchEdge { /* Driver Code */ static func main() { // Массив с повторяющимися элементами let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\nМассив nums = \(nums)") // Бинарный поиск левой и правой границы for target in [6, 7] { var index = binarySearchLeftEdge(nums: nums, target: target) print("Индекс самого левого элемента \(target) равен \(index)") index = binarySearchRightEdge(nums: nums, target: target) print("Индекс самого правого элемента \(target) равен \(index)") } } } ================================================ FILE: ru/codes/swift/chapter_searching/binary_search_insertion.swift ================================================ /** * File: binary_search_insertion.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ /* Бинарный поиск точки вставки (без повторяющихся элементов) */ func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { // Инициализировать двусторонне замкнутый интервал [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // Вычислить индекс середины m if nums[m] < target { i = m + 1 // target находится в интервале [m+1, j] } else if nums[m] > target { j = m - 1 // target находится в интервале [i, m-1] } else { return m // Найти target и вернуть точку вставки m } } // target не найден, вернуть точку вставки i return i } /* Бинарный поиск точки вставки (с повторяющимися элементами) */ public func binarySearchInsertion(nums: [Int], target: Int) -> Int { // Инициализировать двусторонне замкнутый интервал [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // Вычислить индекс середины m if nums[m] < target { i = m + 1 // target находится в интервале [m+1, j] } else if nums[m] > target { j = m - 1 // target находится в интервале [i, m-1] } else { j = m - 1 // Первый элемент меньше target находится в интервале [i, m-1] } } // Вернуть точку вставки i return i } #if !TARGET @main enum BinarySearchInsertion { /* Driver Code */ static func main() { // Массив без повторяющихся элементов var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print("\nМассив nums = \(nums)") // Бинарный поиск точки вставки for target in [6, 9] { let index = binarySearchInsertionSimple(nums: nums, target: target) print("Индекс позиции вставки элемента \(target) равен \(index)") } // Массив с повторяющимися элементами nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\nМассив nums = \(nums)") // Бинарный поиск точки вставки for target in [2, 6, 20] { let index = binarySearchInsertion(nums: nums, target: target) print("Индекс позиции вставки элемента \(target) равен \(index)") } } } #endif ================================================ FILE: ru/codes/swift/chapter_searching/binary_search_insertion_target.swift ================================================ /** * File: binary_search_insertion.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ /* Бинарный поиск точки вставки (без повторяющихся элементов) */ func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { // Инициализировать двусторонне замкнутый интервал [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // Вычислить индекс середины m if nums[m] < target { i = m + 1 // target находится в интервале [m+1, j] } else if nums[m] > target { j = m - 1 // target находится в интервале [i, m-1] } else { return m // Найти target и вернуть точку вставки m } } // target не найден, вернуть точку вставки i return i } /* Бинарный поиск точки вставки (с повторяющимися элементами) */ public func binarySearchInsertion(nums: [Int], target: Int) -> Int { // Инициализировать двусторонне замкнутый интервал [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // Вычислить индекс середины m if nums[m] < target { i = m + 1 // target находится в интервале [m+1, j] } else if nums[m] > target { j = m - 1 // target находится в интервале [i, m-1] } else { j = m - 1 // Первый элемент меньше target находится в интервале [i, m-1] } } // Вернуть точку вставки i return i } #if !TARGET @main enum BinarySearchInsertion { /* Driver Code */ static func main() { // Массив без повторяющихся элементов var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print("\nМассив nums = \(nums)") // Бинарный поиск точки вставки for target in [6, 9] { let index = binarySearchInsertionSimple(nums: nums, target: target) print("Индекс позиции вставки элемента \(target) равен \(index)") } // Массив с повторяющимися элементами nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\nМассив nums = \(nums)") // Бинарный поиск точки вставки for target in [2, 6, 20] { let index = binarySearchInsertion(nums: nums, target: target) print("Индекс позиции вставки элемента \(target) равен \(index)") } } } #endif ================================================ FILE: ru/codes/swift/chapter_searching/hashing_search.swift ================================================ /** * File: hashing_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Хеш-поиск (массив) */ func hashingSearchArray(map: [Int: Int], target: Int) -> Int { // key хеш-таблицы: целевой элемент, value: индекс // Если такого key нет в хеш-таблице, вернуть -1 return map[target, default: -1] } /* Хеш-поиск (связный список) */ func hashingSearchLinkedList(map: [Int: ListNode], target: Int) -> ListNode? { // key хеш-таблицы: значение целевого узла, value: объект узла // Если такого key нет в хеш-таблице, вернуть null return map[target] } @main enum HashingSearch { /* Driver Code */ static func main() { let target = 3 /* Хеш-поиск (массив) */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] // Инициализация хеш-таблицы var map: [Int: Int] = [:] for i in nums.indices { map[nums[i]] = i // key: элемент, value: индекс } let index = hashingSearchArray(map: map, target: target) print("Индекс целевого элемента 3 = \(index)") /* Хеш-поиск (связный список) */ var head = ListNode.arrToLinkedList(arr: nums) // Инициализация хеш-таблицы var map1: [Int: ListNode] = [:] while head != nil { map1[head!.val] = head! // key: значение узла, value: узел head = head?.next } let node = hashingSearchLinkedList(map: map1, target: target) print("Объект узла со значением 3 = \(node!)") } } ================================================ FILE: ru/codes/swift/chapter_searching/linear_search.swift ================================================ /** * File: linear_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Линейный поиск (массив) */ func linearSearchArray(nums: [Int], target: Int) -> Int { // Обход массива for i in nums.indices { // Целевой элемент найден, вернуть его индекс if nums[i] == target { return i } } // Целевой элемент не найден, вернуть -1 return -1 } /* Линейный поиск (связный список) */ func linearSearchLinkedList(head: ListNode?, target: Int) -> ListNode? { var head = head // Обойти связный список while head != nil { // Найти целевой узел и вернуть его if head?.val == target { return head } head = head?.next } // Целевой узел не найден, вернуть null return nil } @main enum LinearSearch { /* Driver Code */ static func main() { let target = 3 /* Выполнить линейный поиск в массиве */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] let index = linearSearchArray(nums: nums, target: target) print("Индекс целевого элемента 3 = \(index)") /* Выполнить линейный поиск в связном списке */ let head = ListNode.arrToLinkedList(arr: nums) let node = linearSearchLinkedList(head: head, target: target) print("Объект узла со значением 3 = \(node!)") } } ================================================ FILE: ru/codes/swift/chapter_searching/two_sum.swift ================================================ /** * File: two_sum.swift * Created Time: 2023-01-03 * Author: nuomi1 (nuomi1@qq.com) */ /* Метод 1: полный перебор */ func twoSumBruteForce(nums: [Int], target: Int) -> [Int] { // Два вложенных цикла, временная сложность O(n^2) for i in nums.indices.dropLast() { for j in nums.indices.dropFirst(i + 1) { if nums[i] + nums[j] == target { return [i, j] } } } return [0] } /* Метод 2: вспомогательная хеш-таблица */ func twoSumHashTable(nums: [Int], target: Int) -> [Int] { // Вспомогательная хеш-таблица, пространственная сложность O(n) var dic: [Int: Int] = [:] // Один цикл, временная сложность O(n) for i in nums.indices { if let j = dic[target - nums[i]] { return [j, i] } dic[nums[i]] = i } return [0] } @main enum LeetcodeTwoSum { /* Driver Code */ static func main() { // ======= Test Case ======= let nums = [2, 7, 11, 15] let target = 13 // ====== Основной код ====== // Метод 1 var res = twoSumBruteForce(nums: nums, target: target) print("Результат метода 1 res = \(res)") // Метод 2 res = twoSumHashTable(nums: nums, target: target) print("Результат метода 2 res = \(res)") } } ================================================ FILE: ru/codes/swift/chapter_sorting/bubble_sort.swift ================================================ /** * File: bubble_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* Пузырьковая сортировка */ func bubbleSort(nums: inout [Int]) { // Внешний цикл: неотсортированный диапазон [0, i] for i in nums.indices.dropFirst().reversed() { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for j in 0 ..< i { if nums[j] > nums[j + 1] { // Поменять местами nums[j] и nums[j + 1] nums.swapAt(j, j + 1) } } } } /* Пузырьковая сортировка (оптимизация флагом) */ func bubbleSortWithFlag(nums: inout [Int]) { // Внешний цикл: неотсортированный диапазон [0, i] for i in nums.indices.dropFirst().reversed() { var flag = false // Инициализировать флаг for j in 0 ..< i { if nums[j] > nums[j + 1] { // Поменять местами nums[j] и nums[j + 1] nums.swapAt(j, j + 1) flag = true // Записать обмен элементов } } if !flag { // На этой итерации «всплытия» не было ни одного обмена, сразу выйти break } } } @main enum BubbleSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] bubbleSort(nums: &nums) print("После пузырьковой сортировки nums = \(nums)") var nums1 = [4, 1, 3, 1, 5, 2] bubbleSortWithFlag(nums: &nums1) print("После пузырьковой сортировки nums1 = \(nums1)") } } ================================================ FILE: ru/codes/swift/chapter_sorting/bucket_sort.swift ================================================ /** * File: bucket_sort.swift * Created Time: 2023-03-27 * Author: nuomi1 (nuomi1@qq.com) */ /* Сортировка корзинами */ func bucketSort(nums: inout [Double]) { // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину let k = nums.count / 2 var buckets = (0 ..< k).map { _ in [Double]() } // 1. Распределить элементы массива по корзинам for num in nums { // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] let i = Int(num * Double(k)) // Добавить num в корзину i buckets[i].append(num) } // 2. Выполнить сортировку внутри каждой корзины for i in buckets.indices { // Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки buckets[i].sort() } // 3. Обойти корзины и объединить результаты var i = nums.startIndex for bucket in buckets { for num in bucket { nums[i] = num i += 1 } } } @main enum BucketSort { /* Driver Code */ static func main() { // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) var nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] bucketSort(nums: &nums) print("После сортировки корзинами nums = \(nums)") } } ================================================ FILE: ru/codes/swift/chapter_sorting/counting_sort.swift ================================================ /** * File: counting_sort.swift * Created Time: 2023-03-22 * Author: nuomi1 (nuomi1@qq.com) */ /* Сортировка подсчетом */ // Простая реализация, не подходит для сортировки объектов func countingSortNaive(nums: inout [Int]) { // 1. Найти максимальный элемент массива m let m = nums.max()! // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num var counter = Array(repeating: 0, count: m + 1) for num in nums { counter[num] += 1 } // 3. Обойти counter и заполнить исходный массив nums элементами var i = 0 for num in 0 ..< m + 1 { for _ in 0 ..< counter[num] { nums[i] = num i += 1 } } } /* Сортировка подсчетом */ // Полная реализация, позволяет сортировать объекты и является стабильной сортировкой func countingSort(nums: inout [Int]) { // 1. Найти максимальный элемент массива m let m = nums.max()! // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num var counter = Array(repeating: 0, count: m + 1) for num in nums { counter[num] += 1 } // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» // То есть counter[num]-1 — это индекс последнего появления num в res for i in 0 ..< m { counter[i + 1] += counter[i] } // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res // Инициализировать массив res для хранения результата var res = Array(repeating: 0, count: nums.count) for i in nums.indices.reversed() { let num = nums[i] res[counter[num] - 1] = num // Поместить num по соответствующему индексу counter[num] -= 1 // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num } // Перезаписать исходный массив nums массивом результата res for i in nums.indices { nums[i] = res[i] } } @main enum CountingSort { /* Driver Code */ static func main() { var nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] countingSortNaive(nums: &nums) print("После сортировки подсчетом (объекты не поддерживаются) nums = \(nums)") var nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] countingSort(nums: &nums1) print("После сортировки подсчетом nums1 = \(nums1)") } } ================================================ FILE: ru/codes/swift/chapter_sorting/heap_sort.swift ================================================ /** * File: heap_sort.swift * Created Time: 2023-05-28 * Author: nuomi1 (nuomi1@qq.com) */ /* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ func siftDown(nums: inout [Int], n: Int, i: Int) { var i = i while true { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma let l = 2 * i + 1 let r = 2 * i + 2 var ma = i if l < n, nums[l] > nums[ma] { ma = l } if r < n, nums[r] > nums[ma] { ma = r } // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if ma == i { break } // Поменять два узла местами nums.swapAt(i, ma) // Циклическое просеивание вниз i = ma } } /* Сортировка кучей */ func heapSort(nums: inout [Int]) { // Построение кучи: выполнить heapify для всех узлов, кроме листовых for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) { siftDown(nums: &nums, n: nums.count, i: i) } // Извлекать максимальный элемент из кучи в течение n-1 итераций for i in nums.indices.dropFirst().reversed() { // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) nums.swapAt(0, i) // Начиная с корневого узла, выполнить просеивание сверху вниз siftDown(nums: &nums, n: i, i: 0) } } @main enum HeapSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] heapSort(nums: &nums) print("После сортировки кучей nums = \(nums)") } } ================================================ FILE: ru/codes/swift/chapter_sorting/insertion_sort.swift ================================================ /** * File: insertion_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* Сортировка вставками */ func insertionSort(nums: inout [Int]) { // Внешний цикл: отсортированный диапазон [0, i-1] for i in nums.indices.dropFirst() { let base = nums[i] var j = i - 1 // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] while j >= 0, nums[j] > base { nums[j + 1] = nums[j] // Сдвинуть nums[j] на одну позицию вправо j -= 1 } nums[j + 1] = base // Поместить base в правильную позицию } } @main enum InsertionSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] insertionSort(nums: &nums) print("После сортировки вставками nums = \(nums)") } } ================================================ FILE: ru/codes/swift/chapter_sorting/merge_sort.swift ================================================ /** * File: merge_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* Объединить левый и правый подмассивы */ func merge(nums: inout [Int], left: Int, mid: Int, right: Int) { // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] // Создать временный массив tmp для хранения результата слияния var tmp = Array(repeating: 0, count: right - left + 1) // Инициализировать начальные индексы левого и правого подмассивов var i = left, j = mid + 1, k = 0 // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив while i <= mid, j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i] i += 1 } else { tmp[k] = nums[j] j += 1 } k += 1 } // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив while i <= mid { tmp[k] = nums[i] i += 1 k += 1 } while j <= right { tmp[k] = nums[j] j += 1 k += 1 } // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums for k in tmp.indices { nums[left + k] = tmp[k] } } /* Сортировка слиянием */ func mergeSort(nums: inout [Int], left: Int, right: Int) { // Условие завершения if left >= right { // Завершить рекурсию, когда длина подмассива равна 1 return } // Этап разбиения let mid = left + (right - left) / 2 // Вычислить середину mergeSort(nums: &nums, left: left, right: mid) // Рекурсивно обработать левый подмассив mergeSort(nums: &nums, left: mid + 1, right: right) // Рекурсивно обработать правый подмассив // Этап слияния merge(nums: &nums, left: left, mid: mid, right: right) } @main enum MergeSort { /* Driver Code */ static func main() { /* Сортировка слиянием */ var nums = [7, 3, 2, 6, 0, 1, 5, 4] mergeSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) print("После сортировки слиянием nums = \(nums)") } } ================================================ FILE: ru/codes/swift/chapter_sorting/quick_sort.swift ================================================ /** * File: quick_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* Класс быстрой сортировки */ /* Разбиение с опорными указателями */ func partition(nums: inout [Int], left: Int, right: Int) -> Int { // Взять nums[left] в качестве опорного элемента var i = left var j = right while i < j { while i < j, nums[j] >= nums[left] { j -= 1 // Идти справа налево в поисках первого элемента меньше опорного } while i < j, nums[i] <= nums[left] { i += 1 // Идти слева направо в поисках первого элемента больше опорного } nums.swapAt(i, j) // Поменять эти два элемента местами } nums.swapAt(i, left) // Переместить опорный элемент на границу двух подмассивов return i // Вернуть индекс опорного элемента } /* Быстрая сортировка */ func quickSort(nums: inout [Int], left: Int, right: Int) { // Завершить рекурсию, когда длина подмассива равна 1 if left >= right { return } // Разбиение с опорными указателями let pivot = partition(nums: &nums, left: left, right: right) // Рекурсивно обработать левый и правый подмассивы quickSort(nums: &nums, left: left, right: pivot - 1) quickSort(nums: &nums, left: pivot + 1, right: right) } /* Класс быстрой сортировки (оптимизация медианным опорным элементом) */ /* Выбрать медиану из трех кандидатов */ func medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int { let l = nums[left] let m = nums[mid] let r = nums[right] if (l <= m && m <= r) || (r <= m && m <= l) { return mid // m находится между l и r } if (m <= l && l <= r) || (r <= l && l <= m) { return left // l находится между m и r } return right } /* Разбиение с опорными указателями (медиана трех) */ func partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int { // Выбрать медиану из трех кандидатов let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right) // Переместить медиану в крайний левый элемент массива nums.swapAt(left, med) return partition(nums: &nums, left: left, right: right) } /* Быстрая сортировка (оптимизация медианным опорным элементом) */ func quickSortMedian(nums: inout [Int], left: Int, right: Int) { // Завершить рекурсию, когда длина подмассива равна 1 if left >= right { return } // Разбиение с опорными указателями let pivot = partitionMedian(nums: &nums, left: left, right: right) // Рекурсивно обработать левый и правый подмассивы quickSortMedian(nums: &nums, left: left, right: pivot - 1) quickSortMedian(nums: &nums, left: pivot + 1, right: right) } /* Быстрая сортировка (оптимизация глубины рекурсии) */ func quickSortTailCall(nums: inout [Int], left: Int, right: Int) { var left = left var right = right // Завершить, когда длина подмассива равна 1 while left < right { // Операция разбиения с опорными указателями let pivot = partition(nums: &nums, left: left, right: right) // Выполнить быструю сортировку для более короткого из двух подмассивов if (pivot - left) < (right - pivot) { quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // Рекурсивно отсортировать левый подмассив left = pivot + 1 // Оставшийся неотсортированный диапазон: [pivot + 1, right] } else { quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // Рекурсивно отсортировать правый подмассив right = pivot - 1 // Оставшийся неотсортированный диапазон: [left, pivot - 1] } } } @main enum QuickSort { /* Driver Code */ static func main() { /* Быстрая сортировка */ var nums = [2, 4, 1, 0, 3, 5] quickSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) print("После быстрой сортировки nums = \(nums)") /* Быстрая сортировка (оптимизация медианным опорным элементом) */ var nums1 = [2, 4, 1, 0, 3, 5] quickSortMedian(nums: &nums1, left: nums1.startIndex, right: nums1.endIndex - 1) print("После быстрой сортировки (оптимизация медианным опорным элементом) nums1 = \(nums1)") /* Быстрая сортировка (оптимизация глубины рекурсии) */ var nums2 = [2, 4, 1, 0, 3, 5] quickSortTailCall(nums: &nums2, left: nums2.startIndex, right: nums2.endIndex - 1) print("После быстрой сортировки (оптимизация глубины рекурсии) nums2 = \(nums2)") } } ================================================ FILE: ru/codes/swift/chapter_sorting/radix_sort.swift ================================================ /** * File: radix_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* Получить k-й разряд элемента num, где exp = 10^(k-1) */ func digit(num: Int, exp: Int) -> Int { // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени (num / exp) % 10 } /* Сортировка подсчетом (сортировка по k-му разряду nums) */ func countingSortDigit(nums: inout [Int], exp: Int) { // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 var counter = Array(repeating: 0, count: 10) // Подсчитать число появлений каждой цифры от 0 до 9 for i in nums.indices { let d = digit(num: nums[i], exp: exp) // Получить k-й разряд nums[i], обозначив его как d counter[d] += 1 // Подсчитать число появлений цифры d } // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» for i in 1 ..< 10 { counter[i] += counter[i - 1] } // Выполняя обратный проход, заполнить res элементами по статистике в корзинах var res = Array(repeating: 0, count: nums.count) for i in nums.indices.reversed() { let d = digit(num: nums[i], exp: exp) let j = counter[d] - 1 // Получить индекс j цифры d в массиве res[j] = nums[i] // Поместить текущий элемент по индексу j counter[d] -= 1 // Уменьшить количество d на 1 } // Перезаписать исходный массив nums результатом for i in nums.indices { nums[i] = res[i] } } /* Поразрядная сортировка */ func radixSort(nums: inout [Int]) { // Получить максимальный элемент массива, чтобы определить максимальное число разрядов var m = Int.min for num in nums { if num > m { m = num } } // Проходить разряды от младшего к старшему for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) { // Выполнить сортировку подсчетом по k-му разряду элементов массива // k = 1 -> exp = 1 // k = 2 -> exp = 10 // то есть exp = 10^(k-1) countingSortDigit(nums: &nums, exp: exp) } } @main enum RadixSort { /* Driver Code */ static func main() { // Поразрядная сортировка var nums = [ 10_546_151, 35_663_510, 42_865_989, 34_862_445, 81_883_077, 88_906_420, 72_429_244, 30_524_779, 82_060_337, 63_832_996, ] radixSort(nums: &nums) print("После поразрядной сортировки nums = \(nums)") } } ================================================ FILE: ru/codes/swift/chapter_sorting/selection_sort.swift ================================================ /** * File: selection_sort.swift * Created Time: 2023-05-28 * Author: nuomi1 (nuomi1@qq.com) */ /* Сортировка выбором */ func selectionSort(nums: inout [Int]) { // Внешний цикл: неотсортированный диапазон [i, n-1] for i in nums.indices.dropLast() { // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне var k = i for j in nums.indices.dropFirst(i + 1) { if nums[j] < nums[k] { k = j // Записать индекс минимального элемента } } // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона nums.swapAt(i, k) } } @main enum SelectionSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] selectionSort(nums: &nums) print("После сортировки выбором nums = \(nums)") } } ================================================ FILE: ru/codes/swift/chapter_stack_and_queue/array_deque.swift ================================================ /** * File: array_deque.swift * Created Time: 2023-02-22 * Author: nuomi1 (nuomi1@qq.com) */ /* Двусторонняя очередь на основе кольцевого массива */ class ArrayDeque { private var nums: [Int] // Массив для хранения элементов двусторонней очереди private var front: Int // Указатель head, указывающий на первый элемент очереди private var _size: Int // Длина двусторонней очереди /* Конструктор */ init(capacity: Int) { nums = Array(repeating: 0, count: capacity) front = 0 _size = 0 } /* Получить вместимость двусторонней очереди */ func capacity() -> Int { nums.count } /* Получение длины двусторонней очереди */ func size() -> Int { _size } /* Проверка, пуста ли двусторонняя очередь */ func isEmpty() -> Bool { size() == 0 } /* Вычислить индекс в кольцевом массиве */ private func index(i: Int) -> Int { // С помощью операции взятия по модулю соединить начало и конец массива // Когда i выходит за конец массива, он возвращается в начало // Когда i выходит за начало массива, он возвращается в конец (i + capacity()) % capacity() } /* Добавление в голову очереди */ func pushFirst(num: Int) { if size() == capacity() { print("Двусторонняя очередь заполнена") return } // Указатель головы сдвигается на одну позицию влево // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост front = index(i: front - 1) // Добавить num в голову очереди nums[front] = num _size += 1 } /* Добавление в хвост очереди */ func pushLast(num: Int) { if size() == capacity() { print("Двусторонняя очередь заполнена") return } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 let rear = index(i: front + size()) // Добавить num в хвост очереди nums[rear] = num _size += 1 } /* Извлечение из головы очереди */ func popFirst() -> Int { let num = peekFirst() // Указатель головы сдвигается на одну позицию назад front = index(i: front + 1) _size -= 1 return num } /* Извлечение из хвоста очереди */ func popLast() -> Int { let num = peekLast() _size -= 1 return num } /* Доступ к элементу в начале очереди */ func peekFirst() -> Int { if isEmpty() { fatalError("двусторонняя очередь пуста") } return nums[front] } /* Доступ к элементу в конце очереди */ func peekLast() -> Int { if isEmpty() { fatalError("двусторонняя очередь пуста") } // Вычислить индекс хвостового элемента let last = index(i: front + size() - 1) return nums[last] } /* Вернуть массив для вывода */ func toArray() -> [Int] { // Преобразовывать только элементы списка в пределах фактической длины (front ..< front + size()).map { nums[index(i: $0)] } } } @main enum _ArrayDeque { /* Driver Code */ static func main() { /* Инициализация двусторонней очереди */ let deque = ArrayDeque(capacity: 10) deque.pushLast(num: 3) deque.pushLast(num: 2) deque.pushLast(num: 5) print("Двусторонняя очередь deque = \(deque.toArray())") /* Доступ к элементу */ let peekFirst = deque.peekFirst() print("Первый элемент peekFirst = \(peekFirst)") let peekLast = deque.peekLast() print("Последний элемент peekLast = \(peekLast)") /* Добавление элемента в очередь */ deque.pushLast(num: 4) print("После добавления элемента 4 в хвост deque = \(deque.toArray())") deque.pushFirst(num: 1) print("После добавления элемента 1 в голову deque = \(deque.toArray())") /* Извлечение элемента из очереди */ let popLast = deque.popLast() print("Извлеченный из хвоста элемент = \(popLast), deque после извлечения из хвоста = \(deque.toArray())") let popFirst = deque.popFirst() print("Извлеченный из головы элемент = \(popFirst), deque после извлечения из головы = \(deque.toArray())") /* Получение длины двусторонней очереди */ let size = deque.size() print("Длина двусторонней очереди size = \(size)") /* Проверка, пуста ли двусторонняя очередь */ let isEmpty = deque.isEmpty() print("Пуста ли двусторонняя очередь = \(isEmpty)") } } ================================================ FILE: ru/codes/swift/chapter_stack_and_queue/array_queue.swift ================================================ /** * File: array_queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ /* Очередь на основе кольцевого массива */ class ArrayQueue { private var nums: [Int] // Массив для хранения элементов очереди private var front: Int // Указатель head, указывающий на первый элемент очереди private var _size: Int // Длина очереди init(capacity: Int) { // Инициализация массива nums = Array(repeating: 0, count: capacity) front = 0 _size = 0 } /* Получить вместимость очереди */ func capacity() -> Int { nums.count } /* Получение длины очереди */ func size() -> Int { _size } /* Проверка, пуста ли очередь */ func isEmpty() -> Bool { size() == 0 } /* Поместить в очередь */ func push(num: Int) { if size() == capacity() { print("Очередь заполнена") return } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива let rear = (front + size()) % capacity() // Добавить num в хвост очереди nums[rear] = num _size += 1 } /* Извлечь из очереди */ @discardableResult func pop() -> Int { let num = peek() // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива front = (front + 1) % capacity() _size -= 1 return num } /* Доступ к элементу в начале очереди */ func peek() -> Int { if isEmpty() { fatalError("очередь пуста") } return nums[front] } /* Вернуть массив */ func toArray() -> [Int] { // Преобразовывать только элементы списка в пределах фактической длины (front ..< front + size()).map { nums[$0 % capacity()] } } } @main enum _ArrayQueue { /* Driver Code */ static func main() { /* Инициализация очереди */ let capacity = 10 let queue = ArrayQueue(capacity: capacity) /* Добавление элемента в очередь */ queue.push(num: 1) queue.push(num: 3) queue.push(num: 2) queue.push(num: 5) queue.push(num: 4) print("Очередь queue = \(queue.toArray())") /* Доступ к элементу в начале очереди */ let peek = queue.peek() print("Первый элемент peek = \(peek)") /* Извлечение элемента из очереди */ let pop = queue.pop() print("Извлеченный элемент pop = \(pop), queue после извлечения = \(queue.toArray())") /* Получение длины очереди */ let size = queue.size() print("Длина очереди size = \(size)") /* Проверка, пуста ли очередь */ let isEmpty = queue.isEmpty() print("Пуста ли очередь = \(isEmpty)") /* Проверка кольцевого массива */ for i in 0 ..< 10 { queue.push(num: i) queue.pop() print("После \(i)-го раунда операций enqueue и dequeue queue = \(queue.toArray())") } } } ================================================ FILE: ru/codes/swift/chapter_stack_and_queue/array_stack.swift ================================================ /** * File: array_stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ /* Стек на основе массива */ class ArrayStack { private var stack: [Int] init() { // Инициализация списка (динамического массива) stack = [] } /* Получение длины стека */ func size() -> Int { stack.count } /* Проверка, пуст ли стек */ func isEmpty() -> Bool { stack.isEmpty } /* Поместить в стек */ func push(num: Int) { stack.append(num) } /* Извлечь из стека */ @discardableResult func pop() -> Int { if isEmpty() { fatalError("стек пуст") } return stack.removeLast() } /* Доступ к верхнему элементу стека */ func peek() -> Int { if isEmpty() { fatalError("стек пуст") } return stack.last! } /* Преобразовать List в Array и вернуть */ func toArray() -> [Int] { stack } } @main enum _ArrayStack { /* Driver Code */ static func main() { /* Инициализация стека */ let stack = ArrayStack() /* Помещение элемента в стек */ stack.push(num: 1) stack.push(num: 3) stack.push(num: 2) stack.push(num: 5) stack.push(num: 4) print("Стек stack = \(stack.toArray())") /* Доступ к верхнему элементу стека */ let peek = stack.peek() print("Верхний элемент peek = \(peek)") /* Извлечение элемента из стека */ let pop = stack.pop() print("Извлеченный элемент pop = \(pop), stack после извлечения = \(stack.toArray())") /* Получение длины стека */ let size = stack.size() print("Длина стека size = \(size)") /* Проверка на пустоту */ let isEmpty = stack.isEmpty() print("Пуст ли стек = \(isEmpty)") } } ================================================ FILE: ru/codes/swift/chapter_stack_and_queue/deque.swift ================================================ /** * File: deque.swift * Created Time: 2023-01-14 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Deque { /* Driver Code */ static func main() { /* Инициализация двусторонней очереди */ // В Swift нет встроенного класса двусторонней очереди, поэтому Array можно использовать как двустороннюю очередь var deque: [Int] = [] /* Добавление элемента в очередь */ deque.append(2) deque.append(5) deque.append(4) deque.insert(3, at: 0) deque.insert(1, at: 0) print("Двусторонняя очередь deque = \(deque)") /* Доступ к элементу */ let peekFirst = deque.first! print("Первый элемент peekFirst = \(peekFirst)") let peekLast = deque.last! print("Последний элемент peekLast = \(peekLast)") /* Извлечение элемента из очереди */ // При использовании Array для имитации popFirst имеет сложность O(n) let popFirst = deque.removeFirst() print("Извлеченный из головы элемент popFirst = \(popFirst), deque после извлечения из головы = \(deque)") let popLast = deque.removeLast() print("Извлеченный из хвоста элемент popLast = \(popLast), deque после извлечения из хвоста = \(deque)") /* Получение длины двусторонней очереди */ let size = deque.count print("Длина двусторонней очереди size = \(size)") /* Проверка, пуста ли двусторонняя очередь */ let isEmpty = deque.isEmpty print("Пуста ли двусторонняя очередь = \(isEmpty)") } } ================================================ FILE: ru/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift ================================================ /** * File: linkedlist_deque.swift * Created Time: 2023-02-22 * Author: nuomi1 (nuomi1@qq.com) */ /* Узел двусвязного списка */ class ListNode { var val: Int // Значение узла var next: ListNode? // Ссылка на узел-преемник weak var prev: ListNode? // Ссылка на узел-предшественник init(val: Int) { self.val = val } } /* Двусторонняя очередь на основе двусвязного списка */ class LinkedListDeque { private var front: ListNode? // Головной узел front private var rear: ListNode? // Хвостовой узел rear private var _size: Int // Длина двусторонней очереди init() { _size = 0 } /* Получение длины двусторонней очереди */ func size() -> Int { _size } /* Проверка, пуста ли двусторонняя очередь */ func isEmpty() -> Bool { size() == 0 } /* Операция добавления в очередь */ private func push(num: Int, isFront: Bool) { let node = ListNode(val: num) // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node if isEmpty() { front = node rear = node } // Операция добавления в голову очереди else if isFront { // Добавить node в голову списка front?.prev = node node.next = front front = node // Обновить головной узел } // Операция добавления в хвост очереди else { // Добавить node в хвост списка rear?.next = node node.prev = rear rear = node // Обновить хвостовой узел } _size += 1 // Обновить длину очереди } /* Добавление в голову очереди */ func pushFirst(num: Int) { push(num: num, isFront: true) } /* Добавление в хвост очереди */ func pushLast(num: Int) { push(num: num, isFront: false) } /* Операция извлечения из очереди */ private func pop(isFront: Bool) -> Int { if isEmpty() { fatalError("двусторонняя очередь пуста") } let val: Int // Операция извлечения из головы очереди if isFront { val = front!.val // Временно сохранить значение головного узла // Удалить головной узел let fNext = front?.next if fNext != nil { fNext?.prev = nil front?.next = nil } front = fNext // Обновить головной узел } // Операция извлечения из хвоста очереди else { val = rear!.val // Временно сохранить значение хвостового узла // Удалить хвостовой узел let rPrev = rear?.prev if rPrev != nil { rPrev?.next = nil rear?.prev = nil } rear = rPrev // Обновить хвостовой узел } _size -= 1 // Обновить длину очереди return val } /* Извлечение из головы очереди */ func popFirst() -> Int { pop(isFront: true) } /* Извлечение из хвоста очереди */ func popLast() -> Int { pop(isFront: false) } /* Доступ к элементу в начале очереди */ func peekFirst() -> Int { if isEmpty() { fatalError("двусторонняя очередь пуста") } return front!.val } /* Доступ к элементу в конце очереди */ func peekLast() -> Int { if isEmpty() { fatalError("двусторонняя очередь пуста") } return rear!.val } /* Вернуть массив для вывода */ func toArray() -> [Int] { var node = front var res = Array(repeating: 0, count: size()) for i in res.indices { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListDeque { /* Driver Code */ static func main() { /* Инициализация двусторонней очереди */ let deque = LinkedListDeque() deque.pushLast(num: 3) deque.pushLast(num: 2) deque.pushLast(num: 5) print("Двусторонняя очередь deque = \(deque.toArray())") /* Доступ к элементу */ let peekFirst = deque.peekFirst() print("Первый элемент peekFirst = \(peekFirst)") let peekLast = deque.peekLast() print("Последний элемент peekLast = \(peekLast)") /* Добавление элемента в очередь */ deque.pushLast(num: 4) print("После добавления элемента 4 в хвост deque = \(deque.toArray())") deque.pushFirst(num: 1) print("После добавления элемента 1 в голову deque = \(deque.toArray())") /* Извлечение элемента из очереди */ let popLast = deque.popLast() print("Извлеченный из хвоста элемент = \(popLast), deque после извлечения из хвоста = \(deque.toArray())") let popFirst = deque.popFirst() print("Извлеченный из головы элемент = \(popFirst), deque после извлечения из головы = \(deque.toArray())") /* Получение длины двусторонней очереди */ let size = deque.size() print("Длина двусторонней очереди size = \(size)") /* Проверка, пуста ли двусторонняя очередь */ let isEmpty = deque.isEmpty() print("Пуста ли двусторонняя очередь = \(isEmpty)") } } ================================================ FILE: ru/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift ================================================ /** * File: linkedlist_queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Очередь на основе связного списка */ class LinkedListQueue { private var front: ListNode? // Головной узел private var rear: ListNode? // Хвостовой узел private var _size: Int init() { _size = 0 } /* Получение длины очереди */ func size() -> Int { _size } /* Проверка, пуста ли очередь */ func isEmpty() -> Bool { size() == 0 } /* Поместить в очередь */ func push(num: Int) { // Добавить num после хвостового узла let node = ListNode(x: num) // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел if front == nil { front = node rear = node } // Если очередь не пуста, добавить этот узел после хвостового узла else { rear?.next = node rear = node } _size += 1 } /* Извлечь из очереди */ @discardableResult func pop() -> Int { let num = peek() // Удалить головной узел front = front?.next _size -= 1 return num } /* Доступ к элементу в начале очереди */ func peek() -> Int { if isEmpty() { fatalError("очередь пуста") } return front!.val } /* Преобразовать связный список в Array и вернуть */ func toArray() -> [Int] { var node = front var res = Array(repeating: 0, count: size()) for i in res.indices { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListQueue { /* Driver Code */ static func main() { /* Инициализация очереди */ let queue = LinkedListQueue() /* Добавление элемента в очередь */ queue.push(num: 1) queue.push(num: 3) queue.push(num: 2) queue.push(num: 5) queue.push(num: 4) print("Очередь queue = \(queue.toArray())") /* Доступ к элементу в начале очереди */ let peek = queue.peek() print("Первый элемент peek = \(peek)") /* Извлечение элемента из очереди */ let pop = queue.pop() print("Извлеченный элемент pop = \(pop), queue после извлечения = \(queue.toArray())") /* Получение длины очереди */ let size = queue.size() print("Длина очереди size = \(size)") /* Проверка, пуста ли очередь */ let isEmpty = queue.isEmpty() print("Пуста ли очередь = \(isEmpty)") } } ================================================ FILE: ru/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift ================================================ /** * File: linkedlist_stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Стек на основе связного списка */ class LinkedListStack { private var _peek: ListNode? // Использовать головной узел как вершину стека private var _size: Int // Длина стека init() { _size = 0 } /* Получение длины стека */ func size() -> Int { _size } /* Проверка, пуст ли стек */ func isEmpty() -> Bool { size() == 0 } /* Поместить в стек */ func push(num: Int) { let node = ListNode(x: num) node.next = _peek _peek = node _size += 1 } /* Извлечь из стека */ @discardableResult func pop() -> Int { let num = peek() _peek = _peek?.next _size -= 1 return num } /* Доступ к верхнему элементу стека */ func peek() -> Int { if isEmpty() { fatalError("стек пуст") } return _peek!.val } /* Преобразовать List в Array и вернуть */ func toArray() -> [Int] { var node = _peek var res = Array(repeating: 0, count: size()) for i in res.indices.reversed() { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListStack { /* Driver Code */ static func main() { /* Инициализация стека */ let stack = LinkedListStack() /* Помещение элемента в стек */ stack.push(num: 1) stack.push(num: 3) stack.push(num: 2) stack.push(num: 5) stack.push(num: 4) print("Стек stack = \(stack.toArray())") /* Доступ к верхнему элементу стека */ let peek = stack.peek() print("Верхний элемент peek = \(peek)") /* Извлечение элемента из стека */ let pop = stack.pop() print("Извлеченный элемент pop = \(pop), stack после извлечения = \(stack.toArray())") /* Получение длины стека */ let size = stack.size() print("Длина стека size = \(size)") /* Проверка на пустоту */ let isEmpty = stack.isEmpty() print("Пуст ли стек = \(isEmpty)") } } ================================================ FILE: ru/codes/swift/chapter_stack_and_queue/queue.swift ================================================ /** * File: queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Queue { /* Driver Code */ static func main() { /* Инициализация очереди */ // В Swift нет встроенного класса очереди, поэтому Array можно использовать как очередь var queue: [Int] = [] /* Добавление элемента в очередь */ queue.append(1) queue.append(3) queue.append(2) queue.append(5) queue.append(4) print("Очередь queue = \(queue)") /* Доступ к элементу в начале очереди */ let peek = queue.first! print("Первый элемент peek = \(peek)") /* Извлечение элемента из очереди */ // При использовании Array для имитации pop имеет сложность O(n) let pool = queue.removeFirst() print("Извлеченный элемент pop = \(pool), queue после извлечения = \(queue)") /* Получение длины очереди */ let size = queue.count print("Длина очереди size = \(size)") /* Проверка, пуста ли очередь */ let isEmpty = queue.isEmpty print("Пуста ли очередь = \(isEmpty)") } } ================================================ FILE: ru/codes/swift/chapter_stack_and_queue/stack.swift ================================================ /** * File: stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Stack { /* Driver Code */ static func main() { /* Инициализация стека */ // В Swift нет встроенного класса стека, поэтому Array можно использовать как стек var stack: [Int] = [] /* Помещение элемента в стек */ stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) print("Стек stack = \(stack)") /* Доступ к верхнему элементу стека */ let peek = stack.last! print("Верхний элемент peek = \(peek)") /* Извлечение элемента из стека */ let pop = stack.removeLast() print("Извлеченный элемент pop = \(pop), stack после извлечения = \(stack)") /* Получение длины стека */ let size = stack.count print("Длина стека size = \(size)") /* Проверка на пустоту */ let isEmpty = stack.isEmpty print("Пуст ли стек = \(isEmpty)") } } ================================================ FILE: ru/codes/swift/chapter_tree/array_binary_tree.swift ================================================ /** * File: array_binary_tree.swift * Created Time: 2023-07-23 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Класс двоичного дерева в массивном представлении */ class ArrayBinaryTree { private var tree: [Int?] /* Конструктор */ init(arr: [Int?]) { tree = arr } /* Вместимость списка */ func size() -> Int { tree.count } /* Получить значение узла с индексом i */ func val(i: Int) -> Int? { // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию if i < 0 || i >= size() { return nil } return tree[i] } /* Получить индекс левого дочернего узла узла с индексом i */ func left(i: Int) -> Int { 2 * i + 1 } /* Получить индекс правого дочернего узла узла с индексом i */ func right(i: Int) -> Int { 2 * i + 2 } /* Получить индекс родительского узла узла с индексом i */ func parent(i: Int) -> Int { (i - 1) / 2 } /* Обход в ширину */ func levelOrder() -> [Int] { var res: [Int] = [] // Непосредственно обходить массив for i in 0 ..< size() { if let val = val(i: i) { res.append(val) } } return res } /* Обход в глубину */ private func dfs(i: Int, order: String, res: inout [Int]) { // Если это пустая позиция, вернуть guard let val = val(i: i) else { return } // Предварительный обход if order == "pre" { res.append(val) } dfs(i: left(i: i), order: order, res: &res) // Симметричный обход if order == "in" { res.append(val) } dfs(i: right(i: i), order: order, res: &res) // Обратный обход if order == "post" { res.append(val) } } /* Предварительный обход */ func preOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "pre", res: &res) return res } /* Симметричный обход */ func inOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "in", res: &res) return res } /* Обратный обход */ func postOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "post", res: &res) return res } } @main enum _ArrayBinaryTree { /* Driver Code */ static func main() { // Инициализировать двоичное дерево // Здесь используется функция, напрямую строящая двоичное дерево из массива let arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] let root = TreeNode.listToTree(arr: arr) print("\nИнициализация двоичного дерева\n") print("Массивное представление двоичного дерева:") print(arr) print("Связное представление двоичного дерева:") PrintUtil.printTree(root: root) // Класс двоичного дерева в массивном представлении let abt = ArrayBinaryTree(arr: arr) // Доступ к узлу let i = 1 let l = abt.left(i: i) let r = abt.right(i: i) let p = abt.parent(i: i) print("\nТекущий узел: индекс = \(i), значение = \(abt.val(i: i) as Any)") print("Индекс левого дочернего узла = \(l), значение = \(abt.val(i: l) as Any)") print("Индекс правого дочернего узла = \(r), значение = \(abt.val(i: r) as Any)") print("Индекс родительского узла = \(p), значение = \(abt.val(i: p) as Any)") // Обходить дерево var res = abt.levelOrder() print("\nОбход в ширину: \(res)") res = abt.preOrder() print("Предварительный обход: \(res)") res = abt.inOrder() print("Симметричный обход: \(res)") res = abt.postOrder() print("Обратный обход: \(res)") } } ================================================ FILE: ru/codes/swift/chapter_tree/avl_tree.swift ================================================ /** * File: avl_tree.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* AVL-дерево */ class AVLTree { fileprivate var root: TreeNode? // Корневой узел init() {} /* Получить высоту узла */ func height(node: TreeNode?) -> Int { // Высота пустого узла равна -1, высота листового узла равна 0 node?.height ?? -1 } /* Обновить высоту узла */ private func updateHeight(node: TreeNode?) { // Высота узла равна высоте более высокого поддерева + 1 node?.height = max(height(node: node?.left), height(node: node?.right)) + 1 } /* Получить коэффициент баланса */ func balanceFactor(node: TreeNode?) -> Int { // Коэффициент баланса пустого узла равен 0 guard let node = node else { return 0 } // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева return height(node: node.left) - height(node: node.right) } /* Операция правого вращения */ private func rightRotate(node: TreeNode?) -> TreeNode? { let child = node?.left let grandChild = child?.right // Выполнить правое вращение узла node вокруг child child?.right = node node?.left = grandChild // Обновить высоту узла updateHeight(node: node) updateHeight(node: child) // Вернуть корневой узел поддерева после вращения return child } /* Операция левого вращения */ private func leftRotate(node: TreeNode?) -> TreeNode? { let child = node?.right let grandChild = child?.left // Выполнить левое вращение узла node вокруг child child?.left = node node?.right = grandChild // Обновить высоту узла updateHeight(node: node) updateHeight(node: child) // Вернуть корневой узел поддерева после вращения return child } /* Выполнить вращение, чтобы снова сбалансировать поддерево */ private func rotate(node: TreeNode?) -> TreeNode? { // Получить коэффициент баланса узла node let balanceFactor = balanceFactor(node: node) // Левосторонне перекошенное дерево if balanceFactor > 1 { if self.balanceFactor(node: node?.left) >= 0 { // Правое вращение return rightRotate(node: node) } else { // Сначала левое вращение, затем правое node?.left = leftRotate(node: node?.left) return rightRotate(node: node) } } // Правосторонне перекошенное дерево if balanceFactor < -1 { if self.balanceFactor(node: node?.right) <= 0 { // Левое вращение return leftRotate(node: node) } else { // Сначала правое вращение, затем левое node?.right = rightRotate(node: node?.right) return leftRotate(node: node) } } // Дерево сбалансировано, вращение не требуется, вернуть сразу return node } /* Вставка узла */ func insert(val: Int) { root = insertHelper(node: root, val: val) } /* Рекурсивная вставка узла (вспомогательный метод) */ private func insertHelper(node: TreeNode?, val: Int) -> TreeNode? { var node = node if node == nil { return TreeNode(x: val) } /* 1. Найти позицию вставки и вставить узел */ if val < node!.val { node?.left = insertHelper(node: node?.left, val: val) } else if val > node!.val { node?.right = insertHelper(node: node?.right, val: val) } else { return node // Повторяющийся узел не вставлять, сразу вернуть } updateHeight(node: node) // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = rotate(node: node) // Вернуть корневой узел поддерева return node } /* Удаление узла */ func remove(val: Int) { root = removeHelper(node: root, val: val) } /* Рекурсивное удаление узла (вспомогательный метод) */ private func removeHelper(node: TreeNode?, val: Int) -> TreeNode? { var node = node if node == nil { return nil } /* 1. Найти узел и удалить его */ if val < node!.val { node?.left = removeHelper(node: node?.left, val: val) } else if val > node!.val { node?.right = removeHelper(node: node?.right, val: val) } else { if node?.left == nil || node?.right == nil { let child = node?.left ?? node?.right // Число дочерних узлов = 0, удалить node и сразу вернуть if child == nil { return nil } // Число дочерних узлов = 1, удалить node напрямую else { node = child } } else { // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел var temp = node?.right while temp?.left != nil { temp = temp?.left } node?.right = removeHelper(node: node?.right, val: temp!.val) node?.val = temp!.val } } updateHeight(node: node) // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = rotate(node: node) // Вернуть корневой узел поддерева return node } /* Поиск узла */ func search(val: Int) -> TreeNode? { var cur = root while cur != nil { // Целевой узел находится в правом поддереве cur if cur!.val < val { cur = cur?.right } // Целевой узел находится в левом поддереве cur else if cur!.val > val { cur = cur?.left } // Найти целевой узел и выйти из цикла else { break } } // Вернуть целевой узел return cur } } @main enum _AVLTree { static func testInsert(tree: AVLTree, val: Int) { tree.insert(val: val) print("\nПосле вставки узла \(val) AVL-дерево имеет вид") PrintUtil.printTree(root: tree.root) } static func testRemove(tree: AVLTree, val: Int) { tree.remove(val: val) print("\nПосле удаления узла \(val) AVL-дерево имеет вид") PrintUtil.printTree(root: tree.root) } /* Driver Code */ static func main() { /* Инициализация пустого AVL-дерева */ let avlTree = AVLTree() /* Вставка узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла testInsert(tree: avlTree, val: 1) testInsert(tree: avlTree, val: 2) testInsert(tree: avlTree, val: 3) testInsert(tree: avlTree, val: 4) testInsert(tree: avlTree, val: 5) testInsert(tree: avlTree, val: 8) testInsert(tree: avlTree, val: 7) testInsert(tree: avlTree, val: 9) testInsert(tree: avlTree, val: 10) testInsert(tree: avlTree, val: 6) /* Вставка повторяющегося узла */ testInsert(tree: avlTree, val: 7) /* Удаление узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла testRemove(tree: avlTree, val: 8) // Удаление узла степени 0 testRemove(tree: avlTree, val: 5) // Удаление узла степени 1 testRemove(tree: avlTree, val: 4) // Удаление узла степени 2 /* Поиск узла */ let node = avlTree.search(val: 7) print("\nНайденный объект узла = \(node!), значение узла = \(node!.val)") } } ================================================ FILE: ru/codes/swift/chapter_tree/binary_search_tree.swift ================================================ /** * File: binary_search_tree.swift * Created Time: 2023-01-26 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Двоичное дерево поиска */ class BinarySearchTree { private var root: TreeNode? /* Конструктор */ init() { // Инициализировать пустое дерево root = nil } /* Получить корневой узел двоичного дерева */ func getRoot() -> TreeNode? { root } /* Поиск узла */ func search(num: Int) -> TreeNode? { var cur = root // Искать в цикле и выйти после прохода за листовой узел while cur != nil { // Целевой узел находится в правом поддереве cur if cur!.val < num { cur = cur?.right } // Целевой узел находится в левом поддереве cur else if cur!.val > num { cur = cur?.left } // Найти целевой узел и выйти из цикла else { break } } // Вернуть целевой узел return cur } /* Вставка узла */ func insert(num: Int) { // Если дерево пусто, инициализировать корневой узел if root == nil { root = TreeNode(x: num) return } var cur = root var pre: TreeNode? // Искать в цикле и выйти после прохода за листовой узел while cur != nil { // Найти повторяющийся узел и сразу вернуть if cur!.val == num { return } pre = cur // Позиция вставки находится в правом поддереве cur if cur!.val < num { cur = cur?.right } // Позиция вставки находится в левом поддереве cur else { cur = cur?.left } } // Вставка узла let node = TreeNode(x: num) if pre!.val < num { pre?.right = node } else { pre?.left = node } } /* Удаление узла */ func remove(num: Int) { // Если дерево пусто, сразу вернуть if root == nil { return } var cur = root var pre: TreeNode? // Искать в цикле и выйти после прохода за листовой узел while cur != nil { // Найти узел для удаления и выйти из цикла if cur!.val == num { break } pre = cur // Узел для удаления находится в правом поддереве cur if cur!.val < num { cur = cur?.right } // Узел для удаления находится в левом поддереве cur else { cur = cur?.left } } // Если узел для удаления отсутствует, сразу вернуть if cur == nil { return } // Число дочерних узлов = 0 или 1 if cur?.left == nil || cur?.right == nil { // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел let child = cur?.left ?? cur?.right // Удалить узел cur if cur !== root { if pre?.left === cur { pre?.left = child } else { pre?.right = child } } else { // Если удаляемый узел является корнем, заново назначить корневой узел root = child } } // Число дочерних узлов = 2 else { // Получить следующий узел после cur в симметричном обходе var tmp = cur?.right while tmp?.left != nil { tmp = tmp?.left } // Рекурсивно удалить узел tmp remove(num: tmp!.val) // Перезаписать cur значением tmp cur?.val = tmp!.val } } } @main enum _BinarySearchTree { /* Driver Code */ static func main() { /* Инициализация двоичного дерева поиска */ let bst = BinarySearchTree() // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] for num in nums { bst.insert(num: num) } print("\nИсходное двоичное дерево\n") PrintUtil.printTree(root: bst.getRoot()) /* Поиск узла */ let node = bst.search(num: 7) print("\nНайденный объект узла = \(node!), значение узла = \(node!.val)") /* Вставка узла */ bst.insert(num: 16) print("\nПосле вставки узла 16 двоичное дерево имеет вид\n") PrintUtil.printTree(root: bst.getRoot()) /* Удаление узла */ bst.remove(num: 1) print("\nПосле удаления узла 1 двоичное дерево имеет вид\n") PrintUtil.printTree(root: bst.getRoot()) bst.remove(num: 2) print("\nПосле удаления узла 2 двоичное дерево имеет вид\n") PrintUtil.printTree(root: bst.getRoot()) bst.remove(num: 4) print("\nПосле удаления узла 4 двоичное дерево имеет вид\n") PrintUtil.printTree(root: bst.getRoot()) } } ================================================ FILE: ru/codes/swift/chapter_tree/binary_tree.swift ================================================ /** * File: binary_tree.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum BinaryTree { /* Driver Code */ static func main() { /* Инициализация двоичного дерева */ // Инициализация узла let n1 = TreeNode(x: 1) let n2 = TreeNode(x: 2) let n3 = TreeNode(x: 3) let n4 = TreeNode(x: 4) let n5 = TreeNode(x: 5) // Построить связи между узлами (указатели) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 print("\nИнициализация двоичного дерева\n") PrintUtil.printTree(root: n1) /* Вставка и удаление узлов */ let P = TreeNode(x: 0) // Вставить узел P между n1 -> n2 n1.left = P P.left = n2 print("\nПосле вставки узла P\n") PrintUtil.printTree(root: n1) // Удалить узел P n1.left = n2 print("\nПосле удаления узла P\n") PrintUtil.printTree(root: n1) } } ================================================ FILE: ru/codes/swift/chapter_tree/binary_tree_bfs.swift ================================================ /** * File: binary_tree_bfs.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* Обход в ширину */ func levelOrder(root: TreeNode) -> [Int] { // Инициализировать очередь и добавить корневой узел var queue: [TreeNode] = [root] // Инициализировать список для хранения последовательности обхода var list: [Int] = [] while !queue.isEmpty { let node = queue.removeFirst() // Извлечение из очереди list.append(node.val) // Сохранить значение узла if let left = node.left { queue.append(left) // Поместить левый дочерний узел в очередь } if let right = node.right { queue.append(right) // Поместить правый дочерний узел в очередь } } return list } @main enum BinaryTreeBFS { /* Driver Code */ static func main() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива let node = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! print("\nИнициализация двоичного дерева\n") PrintUtil.printTree(root: node) /* Обход в ширину */ let list = levelOrder(root: node) print("\nПоследовательность печати узлов при обходе в ширину = \(list)") } } ================================================ FILE: ru/codes/swift/chapter_tree/binary_tree_dfs.swift ================================================ /** * File: binary_tree_dfs.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils // Инициализировать список для хранения последовательности обхода var list: [Int] = [] /* Предварительный обход */ func preOrder(root: TreeNode?) { guard let root = root else { return } // Порядок обхода: корень -> левое поддерево -> правое поддерево list.append(root.val) preOrder(root: root.left) preOrder(root: root.right) } /* Симметричный обход */ func inOrder(root: TreeNode?) { guard let root = root else { return } // Порядок обхода: левое поддерево -> корень -> правое поддерево inOrder(root: root.left) list.append(root.val) inOrder(root: root.right) } /* Обратный обход */ func postOrder(root: TreeNode?) { guard let root = root else { return } // Порядок обхода: левое поддерево -> правое поддерево -> корень postOrder(root: root.left) postOrder(root: root.right) list.append(root.val) } @main enum BinaryTreeDFS { /* Driver Code */ static func main() { /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива let root = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! print("\nИнициализация двоичного дерева\n") PrintUtil.printTree(root: root) /* Предварительный обход */ list.removeAll() preOrder(root: root) print("\nПоследовательность печати узлов при предварительном обходе = \(list)") /* Симметричный обход */ list.removeAll() inOrder(root: root) print("\nПоследовательность печати узлов при симметричном обходе = \(list)") /* Обратный обход */ list.removeAll() postOrder(root: root) print("\nПоследовательность печати узлов при обратном обходе = \(list)") } } ================================================ FILE: ru/codes/swift/utils/ListNode.swift ================================================ /** * File: ListNode.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ public class ListNode: Hashable { public var val: Int // Значение узла public var next: ListNode? // Ссылка на узел-преемник public init(x: Int) { val = x } public static func == (lhs: ListNode, rhs: ListNode) -> Bool { lhs.val == rhs.val && lhs.next.map { ObjectIdentifier($0) } == rhs.next.map { ObjectIdentifier($0) } } public func hash(into hasher: inout Hasher) { hasher.combine(val) hasher.combine(next.map { ObjectIdentifier($0) }) } public static func arrToLinkedList(arr: [Int]) -> ListNode? { let dum = ListNode(x: 0) var head: ListNode? = dum for val in arr { head?.next = ListNode(x: val) head = head?.next } return dum.next } } ================================================ FILE: ru/codes/swift/utils/Pair.swift ================================================ /** * File: Pair.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ /* Пара ключ-значение */ public class Pair: Equatable { public var key: Int public var val: String public init(key: Int, val: String) { self.key = key self.val = val } public static func == (lhs: Pair, rhs: Pair) -> Bool { lhs.key == rhs.key && lhs.val == rhs.val } } ================================================ FILE: ru/codes/swift/utils/PrintUtil.swift ================================================ /** * File: PrintUtil.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ public enum PrintUtil { private class Trunk { var prev: Trunk? var str: String init(prev: Trunk?, str: String) { self.prev = prev self.str = str } } public static func printLinkedList(head: ListNode) { var head: ListNode? = head var list: [String] = [] while head != nil { list.append("\(head!.val)") head = head?.next } print(list.joined(separator: " -> ")) } public static func printTree(root: TreeNode?) { printTree(root: root, prev: nil, isRight: false) } private static func printTree(root: TreeNode?, prev: Trunk?, isRight: Bool) { if root == nil { return } var prevStr = " " let trunk = Trunk(prev: prev, str: prevStr) printTree(root: root?.right, prev: trunk, isRight: true) if prev == nil { trunk.str = "———" } else if isRight { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev?.str = prevStr } showTrunks(p: trunk) print(" \(root!.val)") if prev != nil { prev?.str = prevStr } trunk.str = " |" printTree(root: root?.left, prev: trunk, isRight: false) } private static func showTrunks(p: Trunk?) { if p == nil { return } showTrunks(p: p?.prev) print(p!.str, terminator: "") } public static func printHashMap(map: [K: V]) { for (key, value) in map { print("\(key) -> \(value)") } } public static func printHeap(queue: [Int]) { print("Массивное представление кучи:", terminator: "") print(queue) print("Древовидное представление кучи:") let root = TreeNode.listToTree(arr: queue) printTree(root: root) } public static func printMatrix(matrix: [[T]]) { print("[") for row in matrix { print(" \(row),") } print("]") } } ================================================ FILE: ru/codes/swift/utils/TreeNode.swift ================================================ /** * File: TreeNode.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ /* Класс узла двоичного дерева */ public class TreeNode { public var val: Int // Значение узла public var height: Int // Высота узла public var left: TreeNode? // Ссылка на левый дочерний узел public var right: TreeNode? // Ссылка на правый дочерний узел /* Конструктор */ public init(x: Int) { val = x height = 0 } // Правила кодирования сериализации см.: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // Представление двоичного дерева массивом: // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] // Представление двоичного дерева связным списком: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* Десериализовать список в двоичное дерево: рекурсия */ private static func listToTreeDFS(arr: [Int?], i: Int) -> TreeNode? { if i < 0 || i >= arr.count || arr[i] == nil { return nil } let root = TreeNode(x: arr[i]!) root.left = listToTreeDFS(arr: arr, i: 2 * i + 1) root.right = listToTreeDFS(arr: arr, i: 2 * i + 2) return root } /* Десериализовать список в двоичное дерево */ public static func listToTree(arr: [Int?]) -> TreeNode? { listToTreeDFS(arr: arr, i: 0) } /* Сериализовать двоичное дерево в список: рекурсия */ private static func treeToListDFS(root: TreeNode?, i: Int, res: inout [Int?]) { if root == nil { return } while i >= res.count { res.append(nil) } res[i] = root?.val treeToListDFS(root: root?.left, i: 2 * i + 1, res: &res) treeToListDFS(root: root?.right, i: 2 * i + 2, res: &res) } /* Сериализовать двоичное дерево в список */ public static func treeToList(root: TreeNode?) -> [Int?] { var res: [Int?] = [] treeToListDFS(root: root, i: 0, res: &res) return res } } ================================================ FILE: ru/codes/swift/utils/Vertex.swift ================================================ /** * File: Vertex.swift * Created Time: 2023-02-19 * Author: nuomi1 (nuomi1@qq.com) */ /* Класс вершины */ public class Vertex: Hashable { public var val: Int public init(val: Int) { self.val = val } public static func == (lhs: Vertex, rhs: Vertex) -> Bool { lhs.val == rhs.val } public func hash(into hasher: inout Hasher) { hasher.combine(val) } /* На вход подается список значений vals, на выходе возвращается список вершин vets */ public static func valsToVets(vals: [Int]) -> [Vertex] { vals.map { Vertex(val: $0) } } /* На вход подается список вершин vets, на выходе возвращается список значений vals */ public static func vetsToVals(vets: [Vertex]) -> [Int] { vets.map { $0.val } } } ================================================ FILE: ru/codes/typescript/.gitignore ================================================ node_modules package-lock.json ================================================ FILE: ru/codes/typescript/.prettierrc ================================================ { "tabWidth": 4, "useTabs": false, "semi": true, "singleQuote": true } ================================================ FILE: ru/codes/typescript/chapter_array_and_linkedlist/array.ts ================================================ /** * File: array.ts * Created Time: 2022-12-04 * Author: Justin (xiefahit@gmail.com) */ /* Случайный доступ к элементу */ function randomAccess(nums: number[]): number { // Случайным образом выбрать число из интервала [0, nums.length) const random_index = Math.floor(Math.random() * nums.length); // Получить и вернуть случайный элемент const random_num = nums[random_index]; return random_num; } /* Увеличить длину массива */ // Обратите внимание: Array в TypeScript — это динамический массив, его можно расширять напрямую // Для удобства обучения в этой функции Array рассматривается как массив неизменяемой длины function extend(nums: number[], enlarge: number): number[] { // Инициализировать массив увеличенной длины const res = new Array(nums.length + enlarge).fill(0); // Скопировать все элементы исходного массива в новый массив for (let i = 0; i < nums.length; i++) { res[i] = nums[i]; } // Вернуть новый массив после расширения return res; } /* Вставить элемент num по индексу index в массив */ function insert(nums: number[], num: number, index: number): void { // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад for (let i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // Присвоить num элементу по индексу index nums[index] = num; } /* Удалить элемент по индексу index */ function remove(nums: number[], index: number): void { // Сдвинуть все элементы после индекса index на одну позицию вперед for (let i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* Обход массива */ function traverse(nums: number[]): void { let count = 0; // Обход массива по индексам for (let i = 0; i < nums.length; i++) { count += nums[i]; } // Непосредственно обходить элементы массива for (const num of nums) { count += num; } } /* Найти заданный элемент в массиве */ function find(nums: number[], target: number): number { for (let i = 0; i < nums.length; i++) { if (nums[i] === target) { return i; } } return -1; } /* Driver Code */ /* Инициализация массива */ const arr: number[] = new Array(5).fill(0); console.log('Массив arr =', arr); let nums: number[] = [1, 3, 2, 5, 4]; console.log('Массив nums =', nums); /* Случайный доступ */ let random_num = randomAccess(nums); console.log('Случайный элемент из nums =', random_num); /* Расширение длины */ nums = extend(nums, 3); console.log('После расширения длины массива до 8 nums =', nums); /* Вставка элемента */ insert(nums, 6, 3); console.log('После вставки числа 6 по индексу 3 nums =', nums); /* Удаление элемента */ remove(nums, 2); console.log('После удаления элемента по индексу 2 nums =', nums); /* Обход массива */ traverse(nums); /* Поиск элемента */ let index = find(nums, 3); console.log('Поиск элемента 3 в nums: индекс =', index); export {}; ================================================ FILE: ru/codes/typescript/chapter_array_and_linkedlist/linked_list.ts ================================================ /** * File: linked_list.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from '../modules/ListNode'; import { printLinkedList } from '../modules/PrintUtil'; /* Вставить узел P после узла n0 в связном списке */ function insert(n0: ListNode, P: ListNode): void { const n1 = n0.next; P.next = n1; n0.next = P; } /* Удалить первый узел после узла n0 в связном списке */ function remove(n0: ListNode): void { if (!n0.next) { return; } // n0 -> P -> n1 const P = n0.next; const n1 = P.next; n0.next = n1; } /* Доступ к узлу связного списка по индексу index */ function access(head: ListNode | null, index: number): ListNode | null { for (let i = 0; i < index; i++) { if (!head) { return null; } head = head.next; } return head; } /* Найти в связном списке первый узел со значением target */ function find(head: ListNode | null, target: number): number { let index = 0; while (head !== null) { if (head.val === target) { return index; } head = head.next; index += 1; } return -1; } /* Driver Code */ /* Инициализация связного списка */ // Инициализация всех узлов const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // Построить ссылки между узлами n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; console.log('Инициализированный связный список'); printLinkedList(n0); /* Вставка узла */ insert(n0, new ListNode(0)); console.log('Связный список после вставки узла'); printLinkedList(n0); /* Удаление узла */ remove(n0); console.log('Связный список после удаления узла'); printLinkedList(n0); /* Доступ к узлу */ const node = access(n0, 3); console.log(`Значение узла по индексу 3 в связном списке = ${node?.val}`); /* Поиск узла */ const index = find(n0, 2); console.log(`Индекс узла со значением 2 в связном списке = ${index}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_array_and_linkedlist/list.ts ================================================ /** * File: list.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ /* Инициализация списка */ const nums: number[] = [1, 3, 2, 5, 4]; console.log(`Список nums = ${nums}`); /* Доступ к элементу */ const num: number = nums[1]; console.log(`Элемент по индексу 1: num = ${num}`); /* Обновление элемента */ nums[1] = 0; console.log(`После обновления элемента по индексу 1 до 0 nums = ${nums}`); /* Очистить список */ nums.length = 0; console.log(`После очистки списка nums = ${nums}`); /* Добавление элемента в конец */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); console.log(`После добавления элементов nums = ${nums}`); /* Вставка элемента в середину */ nums.splice(3, 0, 6); console.log(`После вставки числа 6 по индексу 3 nums = ${nums}`); /* Удаление элемента */ nums.splice(3, 1); console.log(`После удаления элемента по индексу 3 nums = ${nums}`); /* Обходить список по индексам */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* Непосредственно обходить элементы списка */ count = 0; for (const x of nums) { count += x; } /* Объединить два списка */ const nums1: number[] = [6, 8, 7, 10, 9]; nums.push(...nums1); console.log(`После конкатенации списка nums1 к nums nums = ${nums}`); /* Отсортировать список */ nums.sort((a, b) => a - b); console.log(`После сортировки списка nums = ${nums}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_array_and_linkedlist/my_list.ts ================================================ /** * File: my_list.ts * Created Time: 2022-12-11 * Author: Justin (xiefahit@gmail.com) */ /* Класс списка */ class MyList { private arr: Array; // Массив (для хранения элементов списка) private _capacity: number = 10; // Вместимость списка private _size: number = 0; // Длина списка (текущее число элементов) private extendRatio: number = 2; // Коэффициент увеличения списка при каждом расширении /* Конструктор */ constructor() { this.arr = new Array(this._capacity); } /* Получить длину списка (текущее число элементов) */ public size(): number { return this._size; } /* Получить вместимость списка */ public capacity(): number { return this._capacity; } /* Доступ к элементу */ public get(index: number): number { // Если индекс выходит за границы, выбрасывается исключение; далее аналогично if (index < 0 || index >= this._size) throw new Error('индекс выходит за границы'); return this.arr[index]; } /* Обновление элемента */ public set(index: number, num: number): void { if (index < 0 || index >= this._size) throw new Error('индекс выходит за границы'); this.arr[index] = num; } /* Добавление элемента в конец */ public add(num: number): void { // Если длина равна вместимости, требуется расширение if (this._size === this._capacity) this.extendCapacity(); // Добавить новый элемент в конец списка this.arr[this._size] = num; this._size++; } /* Вставка элемента в середину */ public insert(index: number, num: number): void { if (index < 0 || index >= this._size) throw new Error('индекс выходит за границы'); // При превышении вместимости по числу элементов запускается расширение if (this._size === this._capacity) { this.extendCapacity(); } // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад for (let j = this._size - 1; j >= index; j--) { this.arr[j + 1] = this.arr[j]; } // Обновить число элементов this.arr[index] = num; this._size++; } /* Удаление элемента */ public remove(index: number): number { if (index < 0 || index >= this._size) throw new Error('индекс выходит за границы'); let num = this.arr[index]; // Сдвинуть все элементы после индекса index на одну позицию вперед for (let j = index; j < this._size - 1; j++) { this.arr[j] = this.arr[j + 1]; } // Обновить число элементов this._size--; // Вернуть удаленный элемент return num; } /* Расширение списка */ public extendCapacity(): void { // Создать новый массив длиной size и скопировать в него исходный массив this.arr = this.arr.concat( new Array(this.capacity() * (this.extendRatio - 1)) ); // Обновить вместимость списка this._capacity = this.arr.length; } /* Преобразовать список в массив */ public toArray(): number[] { let size = this.size(); // Преобразовывать только элементы списка в пределах фактической длины const arr = new Array(size); for (let i = 0; i < size; i++) { arr[i] = this.get(i); } return arr; } } /* Driver Code */ /* Инициализация списка */ const nums = new MyList(); /* Добавление элемента в конец */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); console.log( `Список nums = ${nums.toArray()}, вместимость = ${nums.capacity()}, длина = ${nums.size()}` ); /* Вставка элемента в середину */ nums.insert(3, 6); console.log(`После вставки числа 6 по индексу 3 nums = ${nums.toArray()}`); /* Удаление элемента */ nums.remove(3); console.log(`После удаления элемента по индексу 3 nums = ${nums.toArray()}`); /* Доступ к элементу */ const num = nums.get(1); console.log(`Элемент по индексу 1: num = ${num}`); /* Обновление элемента */ nums.set(1, 0); console.log(`После обновления элемента по индексу 1 до 0 nums = ${nums.toArray()}`); /* Проверка механизма расширения */ for (let i = 0; i < 10; i++) { // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения nums.add(i); } console.log( `Список nums после увеличения вместимости = ${nums.toArray()}, вместимость = ${nums.capacity()}, длина = ${nums.size()}` ); export {}; ================================================ FILE: ru/codes/typescript/chapter_backtracking/n_queens.ts ================================================ /** * File: n_queens.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* Алгоритм бэктрекинга: n ферзей */ function backtrack( row: number, n: number, state: string[][], res: string[][][], cols: boolean[], diags1: boolean[], diags2: boolean[] ): void { // Когда все строки уже обработаны, записать решение if (row === n) { res.push(state.map((row) => row.slice())); return; } // Обойти все столбцы for (let col = 0; col < n; col++) { // Вычислить главную и побочную диагонали, соответствующие этой клетке const diag1 = row - col + n - 1; const diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Попытка: поставить ферзя в эту клетку state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // Перейти к размещению следующей строки backtrack(row + 1, n, state, res, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Решить задачу о n ферзях */ function nQueens(n: number): string[][][] { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку const state = Array.from({ length: n }, () => Array(n).fill('#')); const cols = Array(n).fill(false); // Отмечать, есть ли ферзь в столбце const diags1 = Array(2 * n - 1).fill(false); // Отмечать наличие ферзя на главной диагонали const diags2 = Array(2 * n - 1).fill(false); // Отмечать наличие ферзя на побочной диагонали const res: string[][][] = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } // Driver Code const n = 4; const res = nQueens(n); console.log(`Размер входной доски = ${n}`); console.log(`Количество способов расстановки ферзей: ${res.length}`); res.forEach((state) => { console.log('--------------------'); state.forEach((row) => console.log(row)); }); export {}; ================================================ FILE: ru/codes/typescript/chapter_backtracking/permutations_i.ts ================================================ /** * File: permutations_i.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* Алгоритм бэктрекинга: все перестановки I */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // Когда длина состояния равна числу элементов, записать решение if (state.length === choices.length) { res.push([...state]); return; } // Перебор всех вариантов выбора choices.forEach((choice, i) => { // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true; state.push(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop(); } }); } /* Все перестановки I */ function permutationsI(nums: number[]): number[][] { const res: number[][] = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums: number[] = [1, 2, 3]; const res: number[][] = permutationsI(nums); console.log(`Входной массив nums = ${JSON.stringify(nums)}`); console.log(`Все перестановки res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_backtracking/permutations_ii.ts ================================================ /** * File: permutations_ii.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* Алгоритм бэктрекинга: все перестановки II */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // Когда длина состояния равна числу элементов, записать решение if (state.length === choices.length) { res.push([...state]); return; } // Перебор всех вариантов выбора const duplicated = new Set(); choices.forEach((choice, i) => { // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && !duplicated.has(choice)) { // Попытка: сделать выбор и обновить состояние duplicated.add(choice); // Записать значения уже выбранных элементов selected[i] = true; state.push(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop(); } }); } /* Все перестановки II */ function permutationsII(nums: number[]): number[][] { const res: number[][] = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums: number[] = [1, 2, 2]; const res: number[][] = permutationsII(nums); console.log(`Входной массив nums = ${JSON.stringify(nums)}`); console.log(`Все перестановки res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts ================================================ /** * File: preorder_traversal_i_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Предварительный обход: пример 1 */ function preOrder(root: TreeNode | null, res: TreeNode[]): void { if (root === null) { return; } if (root.val === 7) { // Записать решение res.push(root); } preOrder(root.left, res); preOrder(root.right, res); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nИнициализация двоичного дерева'); printTree(root); // Предварительный обход const res: TreeNode[] = []; preOrder(root, res); console.log('\nВывести все узлы со значением 7'); console.log(res.map((node) => node.val)); export {}; ================================================ FILE: ru/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts ================================================ /** * File: preorder_traversal_ii_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Предварительный обход: пример 2 */ function preOrder( root: TreeNode | null, path: TreeNode[], res: TreeNode[][] ): void { if (root === null) { return; } // Попытка path.push(root); if (root.val === 7) { // Записать решение res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // Откат path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nИнициализация двоичного дерева'); printTree(root); // Предварительный обход const path: TreeNode[] = []; const res: TreeNode[][] = []; preOrder(root, path, res); console.log('\nВывести все пути от корня к узлу 7'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: ru/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts ================================================ /** * File: preorder_traversal_iii_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Предварительный обход: пример 3 */ function preOrder( root: TreeNode | null, path: TreeNode[], res: TreeNode[][] ): void { // Отсечение if (root === null || root.val === 3) { return; } // Попытка path.push(root); if (root.val === 7) { // Записать решение res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // Откат path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nИнициализация двоичного дерева'); printTree(root); // Предварительный обход const path: TreeNode[] = []; const res: TreeNode[][] = []; preOrder(root, path, res); console.log('\nВывести все пути от корня к узлу 7, не содержащие узлов со значением 3'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: ru/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts ================================================ /** * File: preorder_traversal_iii_template.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Проверить, является ли текущее состояние решением */ function isSolution(state: TreeNode[]): boolean { return state && state[state.length - 1]?.val === 7; } /* Записать решение */ function recordSolution(state: TreeNode[], res: TreeNode[][]): void { res.push([...state]); } /* Проверить, допустим ли этот выбор в текущем состоянии */ function isValid(state: TreeNode[], choice: TreeNode): boolean { return choice !== null && choice.val !== 3; } /* Обновить состояние */ function makeChoice(state: TreeNode[], choice: TreeNode): void { state.push(choice); } /* Восстановить состояние */ function undoChoice(state: TreeNode[]): void { state.pop(); } /* Алгоритм бэктрекинга: пример 3 */ function backtrack( state: TreeNode[], choices: TreeNode[], res: TreeNode[][] ): void { // Проверить, является ли текущее состояние решением if (isSolution(state)) { // Записать решение recordSolution(state, res); } // Перебор всех вариантов выбора for (const choice of choices) { // Отсечение: проверить допустимость выбора if (isValid(state, choice)) { // Попытка: сделать выбор и обновить состояние makeChoice(state, choice); // Перейти к следующему выбору backtrack(state, [choice.left, choice.right], res); // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state); } } } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\nИнициализация двоичного дерева'); printTree(root); // Алгоритм бэктрекинга const res: TreeNode[][] = []; backtrack([], [root], res); console.log('\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: ru/codes/typescript/chapter_backtracking/subset_sum_i.ts ================================================ /** * File: subset_sum_i.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Алгоритм бэктрекинга: сумма подмножеств I */ function backtrack( state: number[], target: number, choices: number[], start: number, res: number[][] ): void { // Если сумма подмножества равна target, записать решение if (target === 0) { res.push([...state]); return; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств for (let i = start; i < choices.length; i++) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if (target - choices[i] < 0) { break; } // Попытка: сделать выбор и обновить target и start state.push(choices[i]); // Перейти к следующему выбору backtrack(state, target - choices[i], choices, i, res); // Откат: отменить выбор и восстановить предыдущее состояние state.pop(); } } /* Решить задачу суммы подмножеств I */ function subsetSumI(nums: number[], target: number): number[][] { const state = []; // Состояние (подмножество) nums.sort((a, b) => a - b); // Отсортировать nums const start = 0; // Стартовая вершина обхода const res = []; // Список результатов (список подмножеств) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumI(nums, target); console.log(`Входной массив nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`Все подмножества с суммой ${target}: res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts ================================================ /** * File: subset_sum_i_naive.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Алгоритм бэктрекинга: сумма подмножеств I */ function backtrack( state: number[], target: number, total: number, choices: number[], res: number[][] ): void { // Если сумма подмножества равна target, записать решение if (total === target) { res.push([...state]); return; } // Перебор всех вариантов выбора for (let i = 0; i < choices.length; i++) { // Отсечение: если сумма подмножества превышает target, пропустить этот выбор if (total + choices[i] > target) { continue; } // Попытка: сделать выбор и обновить элемент и total state.push(choices[i]); // Перейти к следующему выбору backtrack(state, target, total + choices[i], choices, res); // Откат: отменить выбор и восстановить предыдущее состояние state.pop(); } } /* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ function subsetSumINaive(nums: number[], target: number): number[][] { const state = []; // Состояние (подмножество) const total = 0; // Сумма подмножеств const res = []; // Список результатов (список подмножеств) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumINaive(nums, target); console.log(`Входной массив nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`Все подмножества с суммой ${target}: res = ${JSON.stringify(res)}`); console.log('Обратите внимание: результат этого метода содержит повторяющиеся множества'); export {}; ================================================ FILE: ru/codes/typescript/chapter_backtracking/subset_sum_ii.ts ================================================ /** * File: subset_sum_ii.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Алгоритм бэктрекинга: сумма подмножеств II */ function backtrack( state: number[], target: number, choices: number[], start: number, res: number[][] ): void { // Если сумма подмножества равна target, записать решение if (target === 0) { res.push([...state]); return; } // Обойти все варианты выбора // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента for (let i = start; i < choices.length; i++) { // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target if (target - choices[i] < 0) { break; } // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить if (i > start && choices[i] === choices[i - 1]) { continue; } // Попытка: сделать выбор и обновить target и start state.push(choices[i]); // Перейти к следующему выбору backtrack(state, target - choices[i], choices, i + 1, res); // Откат: отменить выбор и восстановить предыдущее состояние state.pop(); } } /* Решить задачу суммы подмножеств II */ function subsetSumII(nums: number[], target: number): number[][] { const state = []; // Состояние (подмножество) nums.sort((a, b) => a - b); // Отсортировать nums const start = 0; // Стартовая вершина обхода const res = []; // Список результатов (список подмножеств) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [4, 4, 5]; const target = 9; const res = subsetSumII(nums, target); console.log(`Входной массив nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`Все подмножества с суммой ${target}: res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_computational_complexity/iteration.ts ================================================ /** * File: iteration.ts * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Цикл for */ function forLoop(n: number): number { let res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { res += i; } return res; } /* Цикл while */ function whileLoop(n: number): number { let res = 0; let i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Обновить условную переменную } return res; } /* Цикл while (двойное обновление) */ function whileLoopII(n: number): number { let res = 0; let i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i++; i *= 2; } return res; } /* Двойной цикл for */ function nestedForLoop(n: number): string { let res = ''; // Цикл по i = 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { // Цикл по j = 1, 2, ..., n-1, n for (let j = 1; j <= n; j++) { res += `(${i}, ${j}), `; } } return res; } /* Driver Code */ const n = 5; let res: number; res = forLoop(n); console.log(`Результат суммирования в цикле for res = ${res}`); res = whileLoop(n); console.log(`Результат суммирования в цикле while res = ${res}`); res = whileLoopII(n); console.log(`Результат суммирования в цикле while (двойное обновление) res = ${res}`); const resStr = nestedForLoop(n); console.log(`Результат обхода в двойном цикле for ${resStr}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_computational_complexity/recursion.ts ================================================ /** * File: recursion.ts * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Рекурсия */ function recur(n: number): number { // Условие завершения if (n === 1) return 1; // Рекурсия: рекурсивный вызов const res = recur(n - 1); // Возврат: вернуть результат return n + res; } /* Имитация рекурсии итерацией */ function forLoopRecur(n: number): number { // Использовать явный стек для имитации системного стека вызовов const stack: number[] = []; let res: number = 0; // Рекурсия: рекурсивный вызов for (let i = n; i > 0; i--) { // Имитировать «рекурсию» с помощью операции помещения в стек stack.push(i); } // Возврат: вернуть результат while (stack.length) { // Имитировать «возврат» с помощью операции извлечения из стека res += stack.pop(); } // res = 1+2+3+...+n return res; } /* Хвостовая рекурсия */ function tailRecur(n: number, res: number): number { // Условие завершения if (n === 0) return res; // Хвостовой рекурсивный вызов return tailRecur(n - 1, res + n); } /* Последовательность Фибоначчи: рекурсия */ function fib(n: number): number { // Условие завершения: f(1) = 0, f(2) = 1 if (n === 1 || n === 2) return n - 1; // Рекурсивный вызов f(n) = f(n-1) + f(n-2) const res = fib(n - 1) + fib(n - 2); // Вернуть результат f(n) return res; } /* Driver Code */ const n = 5; let res: number; res = recur(n); console.log(`Результат суммирования в рекурсивной функции res = ${res}`); res = forLoopRecur(n); console.log(`Результат суммирования при имитации рекурсии итерацией res = ${res}`); res = tailRecur(n, 0); console.log(`Результат суммирования в хвостовой рекурсии res = ${res}`); res = fib(n); console.log(`Член последовательности Фибоначчи с номером ${n} = ${res}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_computational_complexity/space_complexity.ts ================================================ /** * File: space_complexity.ts * Created Time: 2023-02-05 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from '../modules/ListNode'; import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Функция */ function constFunc(): number { // Выполнить некоторые операции return 0; } /* Постоянная сложность */ function constant(n: number): void { // Константы, переменные и объекты занимают O(1) памяти const a = 0; const b = 0; const nums = new Array(10000); const node = new ListNode(0); // Переменные в цикле занимают O(1) памяти for (let i = 0; i < n; i++) { const c = 0; } // Функции в цикле занимают O(1) памяти for (let i = 0; i < n; i++) { constFunc(); } } /* Линейная сложность */ function linear(n: number): void { // Массив длины n занимает O(n) памяти const nums = new Array(n); // Список длины n занимает O(n) памяти const nodes: ListNode[] = []; for (let i = 0; i < n; i++) { nodes.push(new ListNode(i)); } // Хеш-таблица длины n занимает O(n) памяти const map = new Map(); for (let i = 0; i < n; i++) { map.set(i, i.toString()); } } /* Линейная сложность (рекурсивная реализация) */ function linearRecur(n: number): void { console.log(`Рекурсия n = ${n}`); if (n === 1) return; linearRecur(n - 1); } /* Квадратичная сложность */ function quadratic(n: number): void { // Матрица занимает O(n^2) памяти const numMatrix = Array(n) .fill(null) .map(() => Array(n).fill(null)); // Двумерный список занимает O(n^2) памяти const numList = []; for (let i = 0; i < n; i++) { const tmp = []; for (let j = 0; j < n; j++) { tmp.push(0); } numList.push(tmp); } } /* Квадратичная сложность (рекурсивная реализация) */ function quadraticRecur(n: number): number { if (n <= 0) return 0; const nums = new Array(n); console.log(`В рекурсии n = ${n} длина nums = ${nums.length}`); return quadraticRecur(n - 1); } /* Экспоненциальная сложность (построение полного двоичного дерева) */ function buildTree(n: number): TreeNode | null { if (n === 0) return null; const root = new TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ const n = 5; // Постоянная сложность constant(n); // Линейная сложность linear(n); linearRecur(n); // Квадратичная сложность quadratic(n); quadraticRecur(n); // Экспоненциальная сложность const root = buildTree(n); printTree(root); ================================================ FILE: ru/codes/typescript/chapter_computational_complexity/time_complexity.ts ================================================ /** * File: time_complexity.ts * Created Time: 2023-01-02 * Author: RiverTwilight (contact@rene.wang) */ /* Постоянная сложность */ function constant(n: number): number { let count = 0; const size = 100000; for (let i = 0; i < size; i++) count++; return count; } /* Линейная сложность */ function linear(n: number): number { let count = 0; for (let i = 0; i < n; i++) count++; return count; } /* Линейная сложность (обход массива) */ function arrayTraversal(nums: number[]): number { let count = 0; // Число итераций пропорционально длине массива for (let i = 0; i < nums.length; i++) { count++; } return count; } /* Квадратичная сложность */ function quadratic(n: number): number { let count = 0; // Число итераций квадратично зависит от размера данных n for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { count++; } } return count; } /* Квадратичная сложность (пузырьковая сортировка) */ function bubbleSort(nums: number[]): number { let count = 0; // Счетчик // Внешний цикл: неотсортированный диапазон [0, i] for (let i = nums.length - 1; i > 0; i--) { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // Обмен элементов включает 3 элементарные операции } } } return count; } /* Экспоненциальная сложность (итеративная реализация) */ function exponential(n: number): number { let count = 0, base = 1; // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) for (let i = 0; i < n; i++) { for (let j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* Экспоненциальная сложность (рекурсивная реализация) */ function expRecur(n: number): number { if (n === 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* Логарифмическая сложность (итеративная реализация) */ function logarithmic(n: number): number { let count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* Логарифмическая сложность (рекурсивная реализация) */ function logRecur(n: number): number { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* Линейно-логарифмическая сложность */ function linearLogRecur(n: number): number { if (n <= 1) return 1; let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (let i = 0; i < n; i++) { count++; } return count; } /* Факториальная сложность (рекурсивная реализация) */ function factorialRecur(n: number): number { if (n === 0) return 1; let count = 0; // Из одного получается n for (let i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях const n = 8; console.log('Размер входных данных n = ' + n); let count = constant(n); console.log('Число операций постоянной сложности = ' + count); count = linear(n); console.log('Число операций линейной сложности = ' + count); count = arrayTraversal(new Array(n)); console.log('Число операций линейной сложности (обход массива) = ' + count); count = quadratic(n); console.log('Число операций квадратичной сложности = ' + count); var nums = new Array(n); for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); console.log('Число операций квадратичной сложности (пузырьковая сортировка) = ' + count); count = exponential(n); console.log('Число операций экспоненциальной сложности (итеративная реализация) = ' + count); count = expRecur(n); console.log('Число операций экспоненциальной сложности (рекурсивная реализация) = ' + count); count = logarithmic(n); console.log('Число операций логарифмической сложности (итеративная реализация) = ' + count); count = logRecur(n); console.log('Число операций логарифмической сложности (рекурсивная реализация) = ' + count); count = linearLogRecur(n); console.log('Число операций линейно-логарифмической сложности (рекурсивная реализация) = ' + count); count = factorialRecur(n); console.log('Число операций факториальной сложности (рекурсивная реализация) = ' + count); export {}; ================================================ FILE: ru/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts ================================================ /** * File: worst_best_time_complexity.ts * Created Time: 2023-01-05 * Author: RiverTwilight (contact@rene.wang) */ /* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ function randomNumbers(n: number): number[] { const nums = Array(n); // Создать массив nums = { 1, 2, 3, ..., n } for (let i = 0; i < n; i++) { nums[i] = i + 1; } // Случайно перемешать элементы массива for (let i = 0; i < n; i++) { const r = Math.floor(Math.random() * (i + 1)); const temp = nums[i]; nums[i] = nums[r]; nums[r] = temp; } return nums; } /* Найти индекс числа 1 в массиве nums */ function findOne(nums: number[]): number { for (let i = 0; i < nums.length; i++) { // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) if (nums[i] === 1) { return i; } } return -1; } /* Driver Code */ for (let i = 0; i < 10; i++) { const n = 100; const nums = randomNumbers(n); const index = findOne(nums); console.log('\nПосле перемешивания массива [ 1, 2, ..., n ] = [' + nums.join(', ') + ']'); console.log('Индекс числа 1 = ' + index); } export {}; ================================================ FILE: ru/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts ================================================ /** * File: binary_search_recur.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Бинарный поиск: задача f(i, j) */ function dfs(nums: number[], target: number, i: number, j: number): number { // Если интервал пуст, целевой элемент отсутствует, вернуть -1 if (i > j) { return -1; } // Вычислить индекс середины m const m = i + ((j - i) >> 1); if (nums[m] < target) { // Рекурсивная подзадача f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // Рекурсивная подзадача f(i, m-1) return dfs(nums, target, i, m - 1); } else { // Целевой элемент найден, вернуть его индекс return m; } } /* Бинарный поиск */ function binarySearch(nums: number[], target: number): number { const n = nums.length; // Решить задачу f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // Бинарный поиск (двусторонне замкнутый интервал) const index = binarySearch(nums, target); console.log(`Индекс целевого элемента 6 = ${index}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_divide_and_conquer/build_tree.ts ================================================ /** * File: build_tree.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ import { printTree } from '../modules/PrintUtil'; import { TreeNode } from '../modules/TreeNode'; /* Построить двоичное дерево: разделяй и властвуй */ function dfs( preorder: number[], inorderMap: Map, i: number, l: number, r: number ): TreeNode | null { // Завершить при пустом диапазоне поддерева if (r - l < 0) return null; // Инициализировать корневой узел const root: TreeNode = new TreeNode(preorder[i]); // Найти m, чтобы разделить левое и правое поддеревья const m = inorderMap.get(preorder[i]); // Подзадача: построить левое поддерево root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // Подзадача: построить правое поддерево root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // Вернуть корневой узел return root; } /* Построить двоичное дерево */ function buildTree(preorder: number[], inorder: number[]): TreeNode | null { // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам let inorderMap = new Map(); for (let i = 0; i < inorder.length; i++) { inorderMap.set(inorder[i], i); } const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ const preorder = [3, 9, 2, 1, 7]; const inorder = [9, 3, 1, 2, 7]; console.log('Предварительный обход = ' + JSON.stringify(preorder)); console.log('Симметричный обход = ' + JSON.stringify(inorder)); const root = buildTree(preorder, inorder); console.log('Построенное двоичное дерево:'); printTree(root); ================================================ FILE: ru/codes/typescript/chapter_divide_and_conquer/hanota.ts ================================================ /** * File: hanota.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Переместить один диск */ function move(src: number[], tar: number[]): void { // Снять диск с вершины src const pan = src.pop(); // Положить диск на вершину tar tar.push(pan); } /* Решить задачу Ханойской башни f(i) */ function dfs(i: number, src: number[], buf: number[], tar: number[]): void { // Если в src остался только один диск, сразу переместить его в tar if (i === 1) { move(src, tar); return; } // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar dfs(i - 1, src, tar, buf); // Подзадача f(1): переместить оставшийся один диск из src в tar move(src, tar); // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src dfs(i - 1, buf, src, tar); } /* Решить задачу Ханойской башни */ function solveHanota(A: number[], B: number[], C: number[]): void { const n = A.length; // Переместить верхние n дисков из A в C с помощью B dfs(n, A, B, C); } /* Driver Code */ // Хвост списка соответствует вершине столбца const A = [5, 4, 3, 2, 1]; const B = []; const C = []; console.log('Начальное состояние:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); solveHanota(A, B, C); console.log('После завершения перемещения дисков:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); ================================================ FILE: ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts ================================================ /** * File: climbing_stairs_backtrack.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Бэктрекинг */ function backtrack( choices: number[], state: number, n: number, res: Map<0, any> ): void { // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 if (state === n) res.set(0, res.get(0) + 1); // Перебор всех вариантов выбора for (const choice of choices) { // Отсечение: нельзя выходить за n-ю ступень if (state + choice > n) continue; // Попытка: сделать выбор и обновить состояние backtrack(choices, state + choice, n, res); // Откат } } /* Подъем по лестнице: бэктрекинг */ function climbingStairsBacktrack(n: number): number { const choices = [1, 2]; // Можно подняться на 1 или 2 ступени const state = 0; // Начать подъем с 0-й ступени const res = new Map(); res.set(0, 0); // Использовать res[0] для хранения числа решений backtrack(choices, state, n, res); return res.get(0); } /* Driver Code */ const n = 9; const res = climbingStairsBacktrack(n); console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res} вариантов`); export {}; ================================================ FILE: ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts ================================================ /** * File: climbing_stairs_constraint_dp.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Подъем по лестнице с ограничениями: динамическое программирование */ function climbingStairsConstraintDP(n: number): number { if (n === 1 || n === 2) { return 1; } // Инициализация таблицы dp для хранения решений подзадач const dp = Array.from({ length: n + 1 }, () => new Array(3)); // Начальное состояние: заранее задать решения наименьших подзадач dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // Переход состояний: постепенное решение больших подзадач через меньшие for (let i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ const n = 9; const res = climbingStairsConstraintDP(n); console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res} вариантов`); export {}; ================================================ FILE: ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts ================================================ /** * File: climbing_stairs_dfs.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Поиск */ function dfs(i: number): number { // dp[1] и dp[2] уже известны, вернуть их if (i === 1 || i === 2) return i; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1) + dfs(i - 2); return count; } /* Подъем по лестнице: поиск */ function climbingStairsDFS(n: number): number { return dfs(n); } /* Driver Code */ const n = 9; const res = climbingStairsDFS(n); console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res} вариантов`); export {}; ================================================ FILE: ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts ================================================ /** * File: climbing_stairs_dfs_mem.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Поиск с мемоизацией */ function dfs(i: number, mem: number[]): number { // dp[1] и dp[2] уже известны, вернуть их if (i === 1 || i === 2) return i; // Если запись dp[i] существует, сразу вернуть ее if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1, mem) + dfs(i - 2, mem); // Сохранить dp[i] mem[i] = count; return count; } /* Подъем по лестнице: поиск с мемоизацией */ function climbingStairsDFSMem(n: number): number { // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи const mem = new Array(n + 1).fill(-1); return dfs(n, mem); } /* Driver Code */ const n = 9; const res = climbingStairsDFSMem(n); console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res} вариантов`); export {}; ================================================ FILE: ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts ================================================ /** * File: climbing_stairs_dp.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Подъем по лестнице: динамическое программирование */ function climbingStairsDP(n: number): number { if (n === 1 || n === 2) return n; // Инициализация таблицы dp для хранения решений подзадач const dp = new Array(n + 1).fill(-1); // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = 1; dp[2] = 2; // Переход состояний: постепенное решение больших подзадач через меньшие for (let i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ function climbingStairsDPComp(n: number): number { if (n === 1 || n === 2) return n; let a = 1, b = 2; for (let i = 3; i <= n; i++) { const tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ const n = 9; let res = climbingStairsDP(n); console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res} вариантов`); res = climbingStairsDPComp(n); console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res} вариантов`); export {}; ================================================ FILE: ru/codes/typescript/chapter_dynamic_programming/coin_change.ts ================================================ /** * File: coin_change.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Размен монет: динамическое программирование */ function coinChangeDP(coins: Array, amt: number): number { const n = coins.length; const MAX = amt + 1; // Инициализация таблицы dp const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // Переход состояний: первая строка и первый столбец for (let a = 1; a <= amt; a++) { dp[0][a] = MAX; } // Переход состояний: остальные строки и столбцы for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] !== MAX ? dp[n][amt] : -1; } /* Размен монет: динамическое программирование с оптимизацией памяти */ function coinChangeDPComp(coins: Array, amt: number): number { const n = coins.length; const MAX = amt + 1; // Инициализация таблицы dp const dp = Array.from({ length: amt + 1 }, () => MAX); dp[0] = 0; // Переход состояний for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] !== MAX ? dp[amt] : -1; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 4; // Динамическое программирование let res = coinChangeDP(coins, amt); console.log(`Минимальное число монет для набора целевой суммы = ${res}`); // Динамическое программирование с оптимизацией памяти res = coinChangeDPComp(coins, amt); console.log(`Минимальное число монет для набора целевой суммы = ${res}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts ================================================ /** * File: coin_change_ii.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Размен монет II: динамическое программирование */ function coinChangeIIDP(coins: Array, amt: number): number { const n = coins.length; // Инициализация таблицы dp const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // Инициализация первого столбца for (let i = 0; i <= n; i++) { dp[i][0] = 1; } // Переход состояний for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a]; } else { // Сумма двух решений: не брать или взять монету i dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* Размен монет II: динамическое программирование с оптимизацией памяти */ function coinChangeIIDPComp(coins: Array, amt: number): number { const n = coins.length; // Инициализация таблицы dp const dp = Array.from({ length: amt + 1 }, () => 0); dp[0] = 1; // Переход состояний for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Сумма двух решений: не брать или взять монету i dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 5; // Динамическое программирование let res = coinChangeIIDP(coins, amt); console.log(`Количество комбинаций монет для набора целевой суммы = ${res}`); // Динамическое программирование с оптимизацией памяти res = coinChangeIIDPComp(coins, amt); console.log(`Количество комбинаций монет для набора целевой суммы = ${res}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_dynamic_programming/edit_distance.ts ================================================ /** * File: edit_distance.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Редакционное расстояние: полный перебор */ function editDistanceDFS(s: string, t: string, i: number, j: number): number { // Если s и t пусты, вернуть 0 if (i === 0 && j === 0) return 0; // Если s пусто, вернуть длину t if (i === 0) return j; // Если t пусто, вернуть длину s if (j === 0) return i; // Если два символа равны, сразу пропустить их if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 const insert = editDistanceDFS(s, t, i, j - 1); const del = editDistanceDFS(s, t, i - 1, j); const replace = editDistanceDFS(s, t, i - 1, j - 1); // Вернуть минимальное число шагов редактирования return Math.min(insert, del, replace) + 1; } /* Редакционное расстояние: поиск с мемоизацией */ function editDistanceDFSMem( s: string, t: string, mem: Array>, i: number, j: number ): number { // Если s и t пусты, вернуть 0 if (i === 0 && j === 0) return 0; // Если s пусто, вернуть длину t if (i === 0) return j; // Если t пусто, вернуть длину s if (j === 0) return i; // Если запись уже есть, сразу вернуть ее if (mem[i][j] !== -1) return mem[i][j]; // Если два символа равны, сразу пропустить их if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 const insert = editDistanceDFSMem(s, t, mem, i, j - 1); const del = editDistanceDFSMem(s, t, mem, i - 1, j); const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Сохранить и вернуть минимальное число шагов редактирования mem[i][j] = Math.min(insert, del, replace) + 1; return mem[i][j]; } /* Редакционное расстояние: динамическое программирование */ function editDistanceDP(s: string, t: string): number { const n = s.length, m = t.length; const dp = Array.from({ length: n + 1 }, () => Array.from({ length: m + 1 }, () => 0) ); // Переход состояний: первая строка и первый столбец for (let i = 1; i <= n; i++) { dp[i][0] = i; } for (let j = 1; j <= m; j++) { dp[0][j] = j; } // Переход состояний: остальные строки и столбцы for (let i = 1; i <= n; i++) { for (let j = 1; j <= m; j++) { if (s.charAt(i - 1) === t.charAt(j - 1)) { // Если два символа равны, сразу пропустить их dp[i][j] = dp[i - 1][j - 1]; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ function editDistanceDPComp(s: string, t: string): number { const n = s.length, m = t.length; const dp = new Array(m + 1).fill(0); // Переход состояний: первая строка for (let j = 1; j <= m; j++) { dp[j] = j; } // Переход состояний: остальные строки for (let i = 1; i <= n; i++) { // Переход состояний: первый столбец let leftup = dp[0]; // Временно сохранить dp[i-1, j-1] dp[0] = i; // Переход состояний: остальные столбцы for (let j = 1; j <= m; j++) { const temp = dp[j]; if (s.charAt(i - 1) === t.charAt(j - 1)) { // Если два символа равны, сразу пропустить их dp[j] = leftup; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; } leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации } } return dp[m]; } /* Driver Code */ const s = 'bag'; const t = 'pack'; const n = s.length, m = t.length; // Полный перебор let res = editDistanceDFS(s, t, n, m); console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); // Поиск с мемоизацией const mem = Array.from({ length: n + 1 }, () => Array.from({ length: m + 1 }, () => -1) ); res = editDistanceDFSMem(s, t, mem, n, m); console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); // Динамическое программирование res = editDistanceDP(s, t); console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); // Динамическое программирование с оптимизацией памяти res = editDistanceDPComp(s, t); console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); export {}; ================================================ FILE: ru/codes/typescript/chapter_dynamic_programming/knapsack.ts ================================================ /** * File: knapsack.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Рюкзак 0-1: полный перебор */ function knapsackDFS( wgt: Array, val: Array, i: number, c: number ): number { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i === 0 || c === 0) { return 0; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут const no = knapsackDFS(wgt, val, i - 1, c); const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // Вернуть вариант с большей стоимостью из двух возможных return Math.max(no, yes); } /* Рюкзак 0-1: поиск с мемоизацией */ function knapsackDFSMem( wgt: Array, val: Array, mem: Array>, i: number, c: number ): number { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i === 0 || c === 0) { return 0; } // Если запись уже есть, вернуть сразу if (mem[i][c] !== -1) { return mem[i][c]; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут const no = knapsackDFSMem(wgt, val, mem, i - 1, c); const yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // Сохранить и вернуть вариант с большей стоимостью из двух решений mem[i][c] = Math.max(no, yes); return mem[i][c]; } /* Рюкзак 0-1: динамическое программирование */ function knapsackDP( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // Инициализация таблицы dp const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // Переход состояний for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = Math.max( dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ function knapsackDPComp( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // Инициализация таблицы dp const dp = Array(cap + 1).fill(0); // Переход состояний for (let i = 1; i <= n; i++) { // Обход в обратном порядке for (let c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // Большее из двух решений: не брать или взять предмет i dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // Полный перебор let res = knapsackDFS(wgt, val, n, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); // Поиск с мемоизацией const mem = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => -1) ); res = knapsackDFSMem(wgt, val, mem, n, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); // Динамическое программирование res = knapsackDP(wgt, val, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); // Динамическое программирование с оптимизацией памяти res = knapsackDPComp(wgt, val, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts ================================================ /** * File: min_cost_climbing_stairs_dp.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Минимальная стоимость подъема по лестнице: динамическое программирование */ function minCostClimbingStairsDP(cost: Array): number { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } // Инициализация таблицы dp для хранения решений подзадач const dp = new Array(n + 1); // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = cost[1]; dp[2] = cost[2]; // Переход состояний: постепенное решение больших подзадач через меньшие for (let i = 3; i <= n; i++) { dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ function minCostClimbingStairsDPComp(cost: Array): number { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } let a = cost[1], b = cost[2]; for (let i = 3; i <= n; i++) { const tmp = b; b = Math.min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; console.log(`Стоимость подъема по ступеням: ${cost}`); let res = minCostClimbingStairsDP(cost); console.log(`Минимальная стоимость подъема по лестнице = ${res}`); res = minCostClimbingStairsDPComp(cost); console.log(`Минимальная стоимость подъема по лестнице = ${res}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_dynamic_programming/min_path_sum.ts ================================================ /** * File: min_path_sum.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Минимальная сумма пути: полный перебор */ function minPathSumDFS( grid: Array>, i: number, j: number ): number { // Если это верхняя левая ячейка, завершить поиск if (i === 0 && j == 0) { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 || j < 0) { return Infinity; } // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) const up = minPathSumDFS(grid, i - 1, j); const left = minPathSumDFS(grid, i, j - 1); // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) return Math.min(left, up) + grid[i][j]; } /* Минимальная сумма пути: поиск с мемоизацией */ function minPathSumDFSMem( grid: Array>, mem: Array>, i: number, j: number ): number { // Если это верхняя левая ячейка, завершить поиск if (i === 0 && j === 0) { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 || j < 0) { return Infinity; } // Если запись уже есть, вернуть сразу if (mem[i][j] != -1) { return mem[i][j]; } // Минимальная стоимость пути для левой и верхней ячеек const up = minPathSumDFSMem(grid, mem, i - 1, j); const left = minPathSumDFSMem(grid, mem, i, j - 1); // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) mem[i][j] = Math.min(left, up) + grid[i][j]; return mem[i][j]; } /* Минимальная сумма пути: динамическое программирование */ function minPathSumDP(grid: Array>): number { const n = grid.length, m = grid[0].length; // Инициализация таблицы dp const dp = Array.from({ length: n }, () => Array.from({ length: m }, () => 0) ); dp[0][0] = grid[0][0]; // Переход состояний: первая строка for (let j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // Переход состояний: первый столбец for (let i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // Переход состояний: остальные строки и столбцы for (let i = 1; i < n; i++) { for (let j: number = 1; j < m; j++) { dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ function minPathSumDPComp(grid: Array>): number { const n = grid.length, m = grid[0].length; // Инициализация таблицы dp const dp = new Array(m); // Переход состояний: первая строка dp[0] = grid[0][0]; for (let j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // Переход состояний: остальные строки for (let i = 1; i < n; i++) { // Переход состояний: первый столбец dp[0] = dp[0] + grid[i][0]; // Переход состояний: остальные столбцы for (let j = 1; j < m; j++) { dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ const grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; const n = grid.length, m = grid[0].length; // Полный перебор let res = minPathSumDFS(grid, n - 1, m - 1); console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); // Поиск с мемоизацией const mem = Array.from({ length: n }, () => Array.from({ length: m }, () => -1) ); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); // Динамическое программирование res = minPathSumDP(grid); console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); // Динамическое программирование с оптимизацией памяти res = minPathSumDPComp(grid); console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts ================================================ /** * File: unbounded_knapsack.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Полный рюкзак: динамическое программирование */ function unboundedKnapsackDP( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // Инициализация таблицы dp const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // Переход состояний for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = Math.max( dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* Полный рюкзак: динамическое программирование с оптимизацией памяти */ function unboundedKnapsackDPComp( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // Инициализация таблицы dp const dp = Array.from({ length: cap + 1 }, () => 0); // Переход состояний for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[c] = dp[c]; } else { // Большее из двух решений: не брать или взять предмет i dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [1, 2, 3]; const val = [5, 11, 15]; const cap = 4; // Динамическое программирование let res = unboundedKnapsackDP(wgt, val, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); // Динамическое программирование с оптимизацией памяти res = unboundedKnapsackDPComp(wgt, val, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_graph/graph_adjacency_list.ts ================================================ /** * File: graph_adjacency_list.ts * Created Time: 2023-02-09 * Author: Justin (xiefahit@gmail.com) */ import { Vertex } from '../modules/Vertex'; /* Класс неориентированного графа на основе списка смежности */ class GraphAdjList { // Список смежности, где key — вершина, а value — все смежные ей вершины adjList: Map; /* Конструктор */ constructor(edges: Vertex[][]) { this.adjList = new Map(); // Добавить все вершины и ребра for (const edge of edges) { this.addVertex(edge[0]); this.addVertex(edge[1]); this.addEdge(edge[0], edge[1]); } } /* Получить число вершин */ size(): number { return this.adjList.size; } /* Добавление ребра */ addEdge(vet1: Vertex, vet2: Vertex): void { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 ) { throw new Error('Illegal Argument Exception'); } // Добавить ребро vet1 - vet2 this.adjList.get(vet1).push(vet2); this.adjList.get(vet2).push(vet1); } /* Удаление ребра */ removeEdge(vet1: Vertex, vet2: Vertex): void { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 || this.adjList.get(vet1).indexOf(vet2) === -1 ) { throw new Error('Illegal Argument Exception'); } // Удалить ребро vet1 - vet2 this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); } /* Добавление вершины */ addVertex(vet: Vertex): void { if (this.adjList.has(vet)) return; // Добавить новый список в список смежности this.adjList.set(vet, []); } /* Удаление вершины */ removeVertex(vet: Vertex): void { if (!this.adjList.has(vet)) { throw new Error('Illegal Argument Exception'); } // Удалить из списка смежности список, соответствующий вершине vet this.adjList.delete(vet); // Обойти списки других вершин и удалить все ребра, содержащие vet for (const set of this.adjList.values()) { const index: number = set.indexOf(vet); if (index > -1) { set.splice(index, 1); } } } /* Вывести список смежности */ print(): void { console.log('Список смежности ='); for (const [key, value] of this.adjList.entries()) { const tmp = []; for (const vertex of value) { tmp.push(vertex.val); } console.log(key.val + ': ' + tmp.join()); } } } /* Driver Code */ if (import.meta.url.endsWith(process.argv[1])) { /* Инициализация неориентированного графа */ const v0 = new Vertex(1), v1 = new Vertex(3), v2 = new Vertex(2), v3 = new Vertex(5), v4 = new Vertex(4); const edges = [ [v0, v1], [v1, v2], [v2, v3], [v0, v3], [v2, v4], [v3, v4], ]; const graph = new GraphAdjList(edges); console.log('\nПосле инициализации граф имеет вид'); graph.print(); /* Добавление ребра */ // Вершины 1 и 2 соответствуют v0 и v2 graph.addEdge(v0, v2); console.log('\nПосле добавления ребра 1-2 граф имеет вид'); graph.print(); /* Удаление ребра */ // Вершины 1 и 3 соответствуют v0 и v1 graph.removeEdge(v0, v1); console.log('\nПосле удаления ребра 1-3 граф имеет вид'); graph.print(); /* Добавление вершины */ const v5 = new Vertex(6); graph.addVertex(v5); console.log('\nПосле добавления вершины 6 граф имеет вид'); graph.print(); /* Удаление вершины */ // Вершина 3 соответствует v1 graph.removeVertex(v1); console.log('\nПосле удаления вершины 3 граф имеет вид'); graph.print(); } export { GraphAdjList }; ================================================ FILE: ru/codes/typescript/chapter_graph/graph_adjacency_matrix.ts ================================================ /** * File: graph_adjacency_matrix.ts * Created Time: 2023-02-09 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Класс неориентированного графа на основе матрицы смежности */ class GraphAdjMat { vertices: number[]; // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» adjMat: number[][]; // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» /* Конструктор */ constructor(vertices: number[], edges: number[][]) { this.vertices = []; this.adjMat = []; // Добавление вершины for (const val of vertices) { this.addVertex(val); } // Добавить ребра // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices for (const e of edges) { this.addEdge(e[0], e[1]); } } /* Получить число вершин */ size(): number { return this.vertices.length; } /* Добавление вершины */ addVertex(val: number): void { const n: number = this.size(); // Добавить значение новой вершины в список вершин this.vertices.push(val); // Добавить строку в матрицу смежности const newRow: number[] = []; for (let j: number = 0; j < n; j++) { newRow.push(0); } this.adjMat.push(newRow); // Добавить столбец в матрицу смежности for (const row of this.adjMat) { row.push(0); } } /* Удаление вершины */ removeVertex(index: number): void { if (index >= this.size()) { throw new RangeError('Index Out Of Bounds Exception'); } // Удалить вершину с индексом index из списка вершин this.vertices.splice(index, 1); // Удалить строку с индексом index из матрицы смежности this.adjMat.splice(index, 1); // Удалить столбец с индексом index из матрицы смежности for (const row of this.adjMat) { row.splice(index, 1); } } /* Добавление ребра */ // Параметры i и j соответствуют индексам элементов vertices addEdge(i: number, j: number): void { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) === (j, i) this.adjMat[i][j] = 1; this.adjMat[j][i] = 1; } /* Удаление ребра */ // Параметры i и j соответствуют индексам элементов vertices removeEdge(i: number, j: number): void { // Обработка выхода индекса за границы и случая равенства if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } this.adjMat[i][j] = 0; this.adjMat[j][i] = 0; } /* Вывести матрицу смежности */ print(): void { console.log('Список вершин = ', this.vertices); console.log('Матрица смежности =', this.adjMat); } } /* Driver Code */ /* Инициализация неориентированного графа */ // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices const vertices: number[] = [1, 3, 2, 5, 4]; const edges: number[][] = [ [0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4], ]; const graph: GraphAdjMat = new GraphAdjMat(vertices, edges); console.log('\nПосле инициализации граф имеет вид'); graph.print(); /* Добавление ребра */ // Индексы вершин 1 и 2 равны 0 и 2 соответственно graph.addEdge(0, 2); console.log('\nПосле добавления ребра 1-2 граф имеет вид'); graph.print(); /* Удаление ребра */ // Индексы вершин 1 и 3 равны 0 и 1 соответственно graph.removeEdge(0, 1); console.log('\nПосле удаления ребра 1-3 граф имеет вид'); graph.print(); /* Добавление вершины */ graph.addVertex(6); console.log('\nПосле добавления вершины 6 граф имеет вид'); graph.print(); /* Удаление вершины */ // Индекс вершины 3 равен 1 graph.removeVertex(1); console.log('\nПосле удаления вершины 3 граф имеет вид'); graph.print(); export {}; ================================================ FILE: ru/codes/typescript/chapter_graph/graph_bfs.ts ================================================ /** * File: graph_bfs.ts * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { GraphAdjList } from './graph_adjacency_list'; import { Vertex } from '../modules/Vertex'; /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { // Последовательность обхода вершин const res: Vertex[] = []; // Хеш-множество для хранения уже посещенных вершин const visited: Set = new Set(); visited.add(startVet); // Очередь используется для реализации BFS const que = [startVet]; // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (que.length) { const vet = que.shift(); // Извлечь головную вершину из очереди res.push(vet); // Отметить посещенную вершину // Обойти все смежные вершины данной вершины for (const adjVet of graph.adjList.get(vet) ?? []) { if (visited.has(adjVet)) { continue; // Пропустить уже посещенную вершину } que.push(adjVet); // Помещать в очередь только непосещенные вершины visited.add(adjVet); // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res; } /* Driver Code */ /* Инициализация неориентированного графа */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; const graph = new GraphAdjList(edges); console.log('\nПосле инициализации граф имеет вид'); graph.print(); /* Обход в ширину */ const res = graphBFS(graph, v[0]); console.log('\nПоследовательность вершин при обходе в ширину (BFS)'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: ru/codes/typescript/chapter_graph/graph_dfs.ts ================================================ /** * File: graph_dfs.ts * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { Vertex } from '../modules/Vertex'; import { GraphAdjList } from './graph_adjacency_list'; /* Вспомогательная функция обхода в глубину */ function dfs( graph: GraphAdjList, visited: Set, res: Vertex[], vet: Vertex ): void { res.push(vet); // Отметить посещенную вершину visited.add(vet); // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины for (const adjVet of graph.adjList.get(vet)) { if (visited.has(adjVet)) { continue; // Пропустить уже посещенную вершину } // Рекурсивно обходить смежные вершины dfs(graph, visited, res, adjVet); } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { // Последовательность обхода вершин const res: Vertex[] = []; // Хеш-множество для хранения уже посещенных вершин const visited: Set = new Set(); dfs(graph, visited, res, startVet); return res; } /* Driver Code */ /* Инициализация неориентированного графа */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; const graph = new GraphAdjList(edges); console.log('\nПосле инициализации граф имеет вид'); graph.print(); /* Обход в глубину */ const res = graphDFS(graph, v[0]); console.log('\nПоследовательность вершин при обходе в глубину (DFS)'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: ru/codes/typescript/chapter_greedy/coin_change_greedy.ts ================================================ /** * File: coin_change_greedy.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Размен монет: жадный алгоритм */ function coinChangeGreedy(coins: number[], amt: number): number { // Предположить, что массив coins упорядочен let i = coins.length - 1; let count = 0; // Циклически выполнять жадный выбор, пока не останется суммы while (amt > 0) { // Найти монету, которая меньше остатка суммы и наиболее к нему близка while (i > 0 && coins[i] > amt) { i--; } // Выбрать coins[i] amt -= coins[i]; count++; } // Если допустимое решение не найдено, вернуть -1 return amt === 0 ? count : -1; } /* Driver Code */ // Жадный подход: гарантирует нахождение глобально оптимального решения let coins: number[] = [1, 5, 10, 20, 50, 100]; let amt: number = 186; let res: number = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`Минимальное число монет для набора суммы ${amt} = ${res}`); // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`Минимальное число монет для набора суммы ${amt} = ${res}`); console.log('На самом деле минимум равен 3: 20 + 20 + 20'); // Жадный подход: не гарантирует нахождение глобально оптимального решения coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`Минимальное число монет для набора суммы ${amt} = ${res}`); console.log('На самом деле минимум равен 2: 49 + 49'); export {}; ================================================ FILE: ru/codes/typescript/chapter_greedy/fractional_knapsack.ts ================================================ /** * File: fractional_knapsack.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Предмет */ class Item { w: number; // Вес предмета v: number; // Стоимость предмета constructor(w: number, v: number) { this.w = w; this.v = v; } } /* Дробный рюкзак: жадный алгоритм */ function fractionalKnapsack(wgt: number[], val: number[], cap: number): number { // Создать список предметов с двумя свойствами: вес и стоимость const items: Item[] = wgt.map((w, i) => new Item(w, val[i])); // Отсортировать по удельной стоимости item.v / item.w в порядке убывания items.sort((a, b) => b.v / b.w - a.v / a.w); // Циклический жадный выбор let res = 0; for (const item of items) { if (item.w <= cap) { // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком res += item.v; cap -= item.w; } else { // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета res += (item.v / item.w) * cap; // Свободной вместимости больше не осталось, поэтому выйти из цикла break; } } return res; } /* Driver Code */ const wgt: number[] = [10, 20, 30, 40, 50]; const val: number[] = [50, 120, 150, 210, 240]; const cap: number = 50; // Жадный алгоритм const res: number = fractionalKnapsack(wgt, val, cap); console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_greedy/max_capacity.ts ================================================ /** * File: max_capacity.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Максимальная вместимость: жадный алгоритм */ function maxCapacity(ht: number[]): number { // Инициализировать i и j так, чтобы они располагались по двум концам массива let i = 0, j = ht.length - 1; // Начальная максимальная вместимость равна 0 let res = 0; // Выполнять жадный выбор в цикле, пока две доски не встретятся while (i < j) { // Обновить максимальную вместимость const cap: number = Math.min(ht[i], ht[j]) * (j - i); res = Math.max(res, cap); // Сдвигать внутрь более короткую сторону if (ht[i] < ht[j]) { i += 1; } else { j -= 1; } } return res; } /* Driver Code */ const ht: number[] = [3, 8, 5, 2, 7, 7, 3, 4]; // Жадный алгоритм const res: number = maxCapacity(ht); console.log(`Максимальная вместимость = ${res}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_greedy/max_product_cutting.ts ================================================ /** * File: max_product_cutting.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* Максимальное произведение разрезания: жадный алгоритм */ function maxProductCutting(n: number): number { // Когда n <= 3, обязательно нужно выделить одну 1 if (n <= 3) { return 1 * (n - 1); } // Жадно выделить множители 3, где a — число троек, а b — остаток let a: number = Math.floor(n / 3); let b: number = n % 3; if (b === 1) { // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 return Math.pow(3, a - 1) * 2 * 2; } if (b === 2) { // Если остаток равен 2, ничего не делать return Math.pow(3, a) * 2; } // Если остаток равен 0, ничего не делать return Math.pow(3, a); } /* Driver Code */ let n: number = 58; // Жадный алгоритм let res: number = maxProductCutting(n); console.log(`Максимальное произведение после разрезания = ${res}`); export {}; ================================================ FILE: ru/codes/typescript/chapter_hashing/array_hash_map.ts ================================================ /** * File: array_hash_map.ts * Created Time: 2022-12-20 * Author: Daniel (better.sunjian@gmail.com) */ /* Пара ключ-значение Number -> String */ class Pair { public key: number; public val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* Хеш-таблица на основе массива */ class ArrayHashMap { private readonly buckets: (Pair | null)[]; constructor() { // Инициализировать массив, содержащий 100 корзин this.buckets = new Array(100).fill(null); } /* Хеш-функция */ private hashFunc(key: number): number { return key % 100; } /* Операция поиска */ public get(key: number): string | null { let index = this.hashFunc(key); let pair = this.buckets[index]; if (pair === null) return null; return pair.val; } /* Операция добавления */ public set(key: number, val: string) { let index = this.hashFunc(key); this.buckets[index] = new Pair(key, val); } /* Операция удаления */ public delete(key: number) { let index = this.hashFunc(key); // Присвоить null, что означает удаление this.buckets[index] = null; } /* Получить все пары ключ-значение */ public entries(): (Pair | null)[] { let arr: (Pair | null)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i]); } } return arr; } /* Получить все ключи */ public keys(): (number | undefined)[] { let arr: (number | undefined)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i].key); } } return arr; } /* Получить все значения */ public values(): (string | undefined)[] { let arr: (string | undefined)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i].val); } } return arr; } /* Вывести хеш-таблицу */ public print() { let pairSet = this.entries(); for (const pair of pairSet) { console.info(`${pair.key} -> ${pair.val}`); } } } /* Driver Code */ /* Инициализация хеш-таблицы */ const map = new ArrayHashMap(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.set(12836, 'Сяо Ха'); map.set(15937, 'Сяо Ло'); map.set(16750, 'Сяо Суань'); map.set(13276, 'Сяо Фа'); map.set(10583, 'Сяо Я'); console.info('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); map.print(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value let name = map.get(15937); console.info('\nПо номеру 15937 найдено имя ' + name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.delete(10583); console.info('\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение'); map.print(); /* Обход хеш-таблицы */ console.info('\nОтдельный обход пар ключ-значение'); for (const pair of map.entries()) { if (!pair) continue; console.info(pair.key + ' -> ' + pair.val); } console.info('\nОтдельный обход ключей'); for (const key of map.keys()) { console.info(key); } console.info('\nОтдельный обход значений'); for (const val of map.values()) { console.info(val); } export {}; ================================================ FILE: ru/codes/typescript/chapter_hashing/hash_map.ts ================================================ /** * File: hash_map.ts * Created Time: 2022-12-20 * Author: Daniel (better.sunjian@gmail.com) */ /* Driver Code */ /* Инициализация хеш-таблицы */ const map = new Map(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.set(12836, 'Сяо Ха'); map.set(15937, 'Сяо Ло'); map.set(16750, 'Сяо Суань'); map.set(13276, 'Сяо Фа'); map.set(10583, 'Сяо Я'); console.info('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); console.info(map); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value let name = map.get(15937); console.info('\nПо номеру 15937 найдено имя ' + name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.delete(10583); console.info('\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение'); console.info(map); /* Обход хеш-таблицы */ console.info('\nОтдельный обход пар ключ-значение'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\nОтдельный обход ключей'); for (const k of map.keys()) { console.info(k); } console.info('\nОтдельный обход значений'); for (const v of map.values()) { console.info(v); } export {}; ================================================ FILE: ru/codes/typescript/chapter_hashing/hash_map_chaining.ts ================================================ /** * File: hash_map_chaining.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Пара ключ-значение Number -> String */ class Pair { key: number; val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* Хеш-таблица с цепочками */ class HashMapChaining { #size: number; // Число пар ключ-значение #capacity: number; // Вместимость хеш-таблицы #loadThres: number; // Порог коэффициента загрузки для запуска расширения #extendRatio: number; // Коэффициент расширения #buckets: Pair[][]; // Массив корзин /* Конструктор */ constructor() { this.#size = 0; this.#capacity = 4; this.#loadThres = 2.0 / 3.0; this.#extendRatio = 2; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); } /* Хеш-функция */ #hashFunc(key: number): number { return key % this.#capacity; } /* Коэффициент загрузки */ #loadFactor(): number { return this.#size / this.#capacity; } /* Операция поиска */ get(key: number): string | null { const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // Обойти корзину; если найден key, вернуть соответствующее val for (const pair of bucket) { if (pair.key === key) { return pair.val; } } // Если key не найден, вернуть null return null; } /* Операция добавления */ put(key: number, val: string): void { // Когда коэффициент загрузки превышает порог, выполнить расширение if (this.#loadFactor() > this.#loadThres) { this.#extend(); } const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть for (const pair of bucket) { if (pair.key === key) { pair.val = val; return; } } // Если такого key нет, добавить пару ключ-значение в конец const pair = new Pair(key, val); bucket.push(pair); this.#size++; } /* Операция удаления */ remove(key: number): void { const index = this.#hashFunc(key); let bucket = this.#buckets[index]; // Обойти корзину и удалить из нее пару ключ-значение for (let i = 0; i < bucket.length; i++) { if (bucket[i].key === key) { bucket.splice(i, 1); this.#size--; break; } } } /* Расширить хеш-таблицу */ #extend(): void { // Временно сохранить исходную хеш-таблицу const bucketsTmp = this.#buckets; // Инициализация новой хеш-таблицы после расширения this.#capacity *= this.#extendRatio; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); this.#size = 0; // Перенести пары ключ-значение из исходной хеш-таблицы в новую for (const bucket of bucketsTmp) { for (const pair of bucket) { this.put(pair.key, pair.val); } } } /* Вывести хеш-таблицу */ print(): void { for (const bucket of this.#buckets) { let res = []; for (const pair of bucket) { res.push(pair.key + ' -> ' + pair.val); } console.log(res); } } } /* Driver Code */ /* Инициализация хеш-таблицы */ const map = new HashMapChaining(); /* Операция добавления */ // Добавить пару (key, value) в хеш-таблицу map.put(12836, 'Сяо Ха'); map.put(15937, 'Сяо Ло'); map.put(16750, 'Сяо Суань'); map.put(13276, 'Сяо Фа'); map.put(10583, 'Сяо Я'); console.log('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); map.print(); /* Операция поиска */ // Ввести в хеш-таблицу ключ key и получить значение value const name = map.get(13276); console.log('\nПо номеру 13276 найдено имя ' + name); /* Операция удаления */ // Удалить пару (key, value) из хеш-таблицы map.remove(12836); console.log('\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение'); map.print(); export {}; ================================================ FILE: ru/codes/typescript/chapter_hashing/hash_map_open_addressing.ts ================================================ /** * File: hash_map_open_addressing.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) */ /* Пара ключ-значение Number -> String */ class Pair { key: number; val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* Хеш-таблица с открытой адресацией */ class HashMapOpenAddressing { private size: number; // Число пар ключ-значение private capacity: number; // Вместимость хеш-таблицы private loadThres: number; // Порог коэффициента загрузки для запуска расширения private extendRatio: number; // Коэффициент расширения private buckets: Array; // Массив корзин private TOMBSTONE: Pair; // Удалить метку /* Конструктор */ constructor() { this.size = 0; // Число пар ключ-значение this.capacity = 4; // Вместимость хеш-таблицы this.loadThres = 2.0 / 3.0; // Порог коэффициента загрузки для запуска расширения this.extendRatio = 2; // Коэффициент расширения this.buckets = Array(this.capacity).fill(null); // Массив корзин this.TOMBSTONE = new Pair(-1, '-1'); // Удалить метку } /* Хеш-функция */ private hashFunc(key: number): number { return key % this.capacity; } /* Коэффициент загрузки */ private loadFactor(): number { return this.size / this.capacity; } /* Найти индекс корзины, соответствующий key */ private findBucket(key: number): number { let index = this.hashFunc(key); let firstTombstone = -1; // Выполнять линейное пробирование и завершить при встрече с пустой корзиной while (this.buckets[index] !== null) { // Если встретился key, вернуть соответствующий индекс корзины if (this.buckets[index]!.key === key) { // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс if (firstTombstone !== -1) { this.buckets[firstTombstone] = this.buckets[index]; this.buckets[index] = this.TOMBSTONE; return firstTombstone; // Вернуть индекс корзины после перемещения } return index; // Вернуть индекс корзины } // Записать первую встретившуюся метку удаления if ( firstTombstone === -1 && this.buckets[index] === this.TOMBSTONE ) { firstTombstone = index; } // Вычислить индекс корзины; при выходе за конец вернуться к началу index = (index + 1) % this.capacity; } // Если key не существует, вернуть индекс точки добавления return firstTombstone === -1 ? index : firstTombstone; } /* Операция поиска */ get(key: number): string | null { // Найти индекс корзины, соответствующий key const index = this.findBucket(key); // Если пара ключ-значение найдена, вернуть соответствующее val if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { return this.buckets[index]!.val; } // Если пары ключ-значение не существует, вернуть null return null; } /* Операция добавления */ put(key: number, val: string): void { // Когда коэффициент загрузки превышает порог, выполнить расширение if (this.loadFactor() > this.loadThres) { this.extend(); } // Найти индекс корзины, соответствующий key const index = this.findBucket(key); // Если пара ключ-значение найдена, перезаписать val и вернуть if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { this.buckets[index]!.val = val; return; } // Если пары ключ-значение нет, добавить ее this.buckets[index] = new Pair(key, val); this.size++; } /* Операция удаления */ remove(key: number): void { // Найти индекс корзины, соответствующий key const index = this.findBucket(key); // Если пара ключ-значение найдена, заменить ее меткой удаления if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { this.buckets[index] = this.TOMBSTONE; this.size--; } } /* Расширить хеш-таблицу */ private extend(): void { // Временно сохранить исходную хеш-таблицу const bucketsTmp = this.buckets; // Инициализация новой хеш-таблицы после расширения this.capacity *= this.extendRatio; this.buckets = Array(this.capacity).fill(null); this.size = 0; // Перенести пары ключ-значение из исходной хеш-таблицы в новую for (const pair of bucketsTmp) { if (pair !== null && pair !== this.TOMBSTONE) { this.put(pair.key, pair.val); } } } /* Вывести хеш-таблицу */ print(): void { for (const pair of this.buckets) { if (pair === null) { console.log('null'); } else if (pair === this.TOMBSTONE) { console.log('TOMBSTONE'); } else { console.log(pair.key + ' -> ' + pair.val); } } } } /* Driver Code */ // Инициализация хеш-таблицы const hashmap = new HashMapOpenAddressing(); // Операция добавления // Добавить пару (key, val) в хеш-таблицу hashmap.put(12836, 'Сяо Ха'); hashmap.put(15937, 'Сяо Ло'); hashmap.put(16750, 'Сяо Суань'); hashmap.put(13276, 'Сяо Фа'); hashmap.put(10583, 'Сяо Я'); console.log('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); hashmap.print(); // Операция поиска // Передать ключ key в хеш-таблицу и получить значение val const name = hashmap.get(13276); console.log('\nПо номеру 13276 найдено имя ' + name); // Операция удаления // Удалить пару (key, val) из хеш-таблицы hashmap.remove(16750); console.log('\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение'); hashmap.print(); export {}; ================================================ FILE: ru/codes/typescript/chapter_hashing/simple_hash.ts ================================================ /** * File: simple_hash.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* Аддитивное хеширование */ function addHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* Мультипликативное хеширование */ function mulHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (31 * hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* XOR-хеширование */ function xorHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash ^= c.charCodeAt(0); } return hash % MODULUS; } /* Хеширование с циклическим сдвигом */ function rotHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; } return hash; } /* Driver Code */ const key = 'Hello Algo'; let hash = addHash(key); console.log('Хеш-значение по сложению = ' + hash); hash = mulHash(key); console.log('Хеш-значение по умножению = ' + hash); hash = xorHash(key); console.log('Хеш-значение по XOR = ' + hash); hash = rotHash(key); console.log('Хеш-значение по циклическому сдвигу = ' + hash); ================================================ FILE: ru/codes/typescript/chapter_heap/my_heap.ts ================================================ /** * File: my_heap.ts * Created Time: 2023-02-07 * Author: Justin (xiefahit@gmail.com) */ import { printHeap } from '../modules/PrintUtil'; /* Класс максимальной кучи */ class MaxHeap { private maxHeap: number[]; /* Конструктор, создающий пустую кучу или строящий кучу по входному списку */ constructor(nums?: number[]) { // Добавить элементы списка в кучу без изменений this.maxHeap = nums === undefined ? [] : [...nums]; // Выполнить heapify для всех узлов, кроме листовых for (let i = this.parent(this.size() - 1); i >= 0; i--) { this.siftDown(i); } } /* Получить индекс левого дочернего узла */ private left(i: number): number { return 2 * i + 1; } /* Получить индекс правого дочернего узла */ private right(i: number): number { return 2 * i + 2; } /* Получить индекс родительского узла */ private parent(i: number): number { return Math.floor((i - 1) / 2); // Округление вниз при делении } /* Поменять элементы местами */ private swap(i: number, j: number): void { const tmp = this.maxHeap[i]; this.maxHeap[i] = this.maxHeap[j]; this.maxHeap[j] = tmp; } /* Получение размера кучи */ public size(): number { return this.maxHeap.length; } /* Проверка, пуста ли куча */ public isEmpty(): boolean { return this.size() === 0; } /* Доступ к элементу на вершине кучи */ public peek(): number { return this.maxHeap[0]; } /* Добавление элемента в кучу */ public push(val: number): void { // Добавление узла this.maxHeap.push(val); // Просеивание снизу вверх this.siftUp(this.size() - 1); } /* Начиная с узла i, выполнить просеивание снизу вверх */ private siftUp(i: number): void { while (true) { // Получение родительского узла для узла i const p = this.parent(i); // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break; // Поменять два узла местами this.swap(i, p); // Циклическое просеивание вверх i = p; } } /* Извлечение элемента из кучи */ public pop(): number { // Обработка пустого случая if (this.isEmpty()) throw new RangeError('Heap is empty.'); // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) this.swap(0, this.size() - 1); // Удаление узла const val = this.maxHeap.pop(); // Просеивание сверху вниз this.siftDown(0); // Вернуть элемент с вершины кучи return val; } /* Начиная с узла i, выполнить просеивание сверху вниз */ private siftDown(i: number): void { while (true) { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma const l = this.left(i), r = this.right(i); let ma = i; if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l; if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r; // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if (ma === i) break; // Поменять два узла местами this.swap(i, ma); // Циклическое просеивание вниз i = ma; } } /* Вывести кучу (двоичное дерево) */ public print(): void { printHeap(this.maxHeap); } /* Извлечь элементы из кучи */ public getMaxHeap(): number[] { return this.maxHeap; } } /* Driver Code */ if (import.meta.url.endsWith(process.argv[1])) { /* Инициализация максимальной кучи */ const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); console.log('\nПосле построения кучи из входного списка'); maxHeap.print(); /* Получение элемента с вершины кучи */ let peek = maxHeap.peek(); console.log(`\nЭлемент на вершине кучи = ${peek}`); /* Добавление элемента в кучу */ const val = 7; maxHeap.push(val); console.log(`\nПосле добавления элемента ${val} в кучу`); maxHeap.print(); /* Извлечение элемента с вершины кучи */ peek = maxHeap.pop(); console.log(`\nПосле извлечения элемента вершины кучи ${peek}`); maxHeap.print(); /* Получение размера кучи */ const size = maxHeap.size(); console.log(`\nКоличество элементов в куче = ${size}`); /* Проверка, пуста ли куча */ const isEmpty = maxHeap.isEmpty(); console.log(`\nПуста ли куча: ${isEmpty}`); } export { MaxHeap }; ================================================ FILE: ru/codes/typescript/chapter_heap/top_k.ts ================================================ /** * File: top_k.ts * Created Time: 2023-08-13 * Author: Justin (xiefahit@gmail.com) */ import { MaxHeap } from './my_heap'; /* Добавление элемента в кучу */ function pushMinHeap(maxHeap: MaxHeap, val: number): void { // Инвертировать знак элемента maxHeap.push(-val); } /* Извлечение элемента из кучи */ function popMinHeap(maxHeap: MaxHeap): number { // Инвертировать знак элемента return -maxHeap.pop(); } /* Доступ к элементу на вершине кучи */ function peekMinHeap(maxHeap: MaxHeap): number { // Инвертировать знак элемента return -maxHeap.peek(); } /* Извлечь элементы из кучи */ function getMinHeap(maxHeap: MaxHeap): number[] { // Инвертировать знак элемента return maxHeap.getMaxHeap().map((num: number) => -num); } /* Найти k наибольших элементов массива с помощью кучи */ function topKHeap(nums: number[], k: number): number[] { // Инициализация минимальной кучи // Обратите внимание: мы инвертируем все элементы кучи, чтобы с помощью максимальной кучи имитировать минимальную const maxHeap = new MaxHeap([]); // Поместить первые k элементов массива в кучу for (let i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // Начиная с элемента k+1, поддерживать длину кучи равной k for (let i = k; i < nums.length; i++) { // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } // Вернуть элементы кучи return getMinHeap(maxHeap); } /* Driver Code */ const nums = [1, 7, 6, 3, 2]; const k = 3; const res = topKHeap(nums, k); console.log(`Наибольшие ${k} элементов`, res); ================================================ FILE: ru/codes/typescript/chapter_searching/binary_search.ts ================================================ /** * File: binary_search.ts * Created Time: 2022-12-27 * Author: Daniel (better.sunjian@gmail.com) */ /* Бинарный поиск (двусторонне замкнутый интервал) */ function binarySearch(nums: number[], target: number): number { // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно let i = 0, j = nums.length - 1; // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) while (i <= j) { // Вычислить индекс середины m const m = Math.floor(i + (j - i) / 2); if (nums[m] < target) { // Это означает, что target находится в интервале [m+1, j] i = m + 1; } else if (nums[m] > target) { // Это означает, что target находится в интервале [i, m-1] j = m - 1; } else { // Целевой элемент найден, вернуть его индекс return m; } } return -1; // Целевой элемент не найден, вернуть -1 } /* Бинарный поиск (лево замкнутый, право открытый интервал) */ function binarySearchLCRO(nums: number[], target: number): number { // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно let i = 0, j = nums.length; // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) while (i < j) { // Вычислить индекс середины m const m = Math.floor(i + (j - i) / 2); if (nums[m] < target) { // Это означает, что target находится в интервале [m+1, j) i = m + 1; } else if (nums[m] > target) { // Это означает, что target находится в интервале [i, m) j = m; } else { // Целевой элемент найден, вернуть его индекс return m; } } return -1; // Целевой элемент не найден, вернуть -1 } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* Бинарный поиск (двусторонне замкнутый интервал) */ let index = binarySearch(nums, target); console.info('Индекс целевого элемента 6 = %d', index); /* Бинарный поиск (лево замкнутый, право открытый интервал) */ index = binarySearchLCRO(nums, target); console.info('Индекс целевого элемента 6 = %d', index); export {}; ================================================ FILE: ru/codes/typescript/chapter_searching/binary_search_edge.ts ================================================ /** * File: binary_search_edge.ts * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ import { binarySearchInsertion } from './binary_search_insertion'; /* Бинарный поиск самого левого target */ function binarySearchLeftEdge(nums: Array, target: number): number { // Эквивалентно поиску точки вставки target const i = binarySearchInsertion(nums, target); // target не найден, вернуть -1 if (i === nums.length || nums[i] !== target) { return -1; } // Найти target и вернуть индекс i return i; } /* Бинарный поиск самого правого target */ function binarySearchRightEdge(nums: Array, target: number): number { // Преобразовать задачу в поиск самого левого target + 1 const i = binarySearchInsertion(nums, target + 1); // j указывает на самый правый target, а i — на первый элемент больше target const j = i - 1; // target не найден, вернуть -1 if (j === -1 || nums[j] !== target) { return -1; } // Найти target и вернуть индекс j return j; } /* Driver Code */ // Массив с повторяющимися элементами let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\nМассив nums = ' + nums); // Бинарный поиск левой и правой границы for (const target of [6, 7]) { let index = binarySearchLeftEdge(nums, target); console.log('Индекс самого левого элемента ' + target + ' = ' + index); index = binarySearchRightEdge(nums, target); console.log('Индекс самого правого элемента ' + target + ' = ' + index); } export {}; ================================================ FILE: ru/codes/typescript/chapter_searching/binary_search_insertion.ts ================================================ /** * File: binary_search_insertion.ts * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* Бинарный поиск точки вставки (без повторяющихся элементов) */ function binarySearchInsertionSimple( nums: Array, target: number ): number { let i = 0, j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // Вычислить индекс середины m, используя Math.floor() для округления вниз if (nums[m] < target) { i = m + 1; // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1; // target находится в интервале [i, m-1] } else { return m; // Найти target и вернуть точку вставки m } } // target не найден, вернуть точку вставки i return i; } /* Бинарный поиск точки вставки (с повторяющимися элементами) */ function binarySearchInsertion(nums: Array, target: number): number { let i = 0, j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // Вычислить индекс середины m, используя Math.floor() для округления вниз if (nums[m] < target) { i = m + 1; // target находится в интервале [m+1, j] } else if (nums[m] > target) { j = m - 1; // target находится в интервале [i, m-1] } else { j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] } } // Вернуть точку вставки i return i; } /* Driver Code */ // Массив без повторяющихся элементов let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; console.log('\nМассив nums = ' + nums); // Бинарный поиск точки вставки for (const target of [6, 9]) { const index = binarySearchInsertionSimple(nums, target); console.log('Индекс точки вставки элемента ' + target + ' = ' + index); } // Массив с повторяющимися элементами nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\nМассив nums = ' + nums); // Бинарный поиск точки вставки for (const target of [2, 6, 20]) { const index = binarySearchInsertion(nums, target); console.log('Индекс точки вставки элемента ' + target + ' = ' + index); } export { binarySearchInsertion }; ================================================ FILE: ru/codes/typescript/chapter_searching/hashing_search.ts ================================================ /** * File: hashing_search.ts * Created Time: 2022-12-29 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { ListNode, arrToLinkedList } from '../modules/ListNode'; /* Хеш-поиск (массив) */ function hashingSearchArray(map: Map, target: number): number { // key хеш-таблицы: целевой элемент, value: индекс // Если такого key нет в хеш-таблице, вернуть -1 return map.has(target) ? (map.get(target) as number) : -1; } /* Хеш-поиск (связный список) */ function hashingSearchLinkedList( map: Map, target: number ): ListNode | null { // key хеш-таблицы: значение целевого узла, value: объект узла // Если такого key нет в хеш-таблице, вернуть null return map.has(target) ? (map.get(target) as ListNode) : null; } /* Driver Code */ const target = 3; /* Хеш-поиск (массив) */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // Инициализация хеш-таблицы const map = new Map(); for (let i = 0; i < nums.length; i++) { map.set(nums[i], i); // key: элемент, value: индекс } const index = hashingSearchArray(map, target); console.log('Индекс целевого элемента 3 = ' + index); /* Хеш-поиск (связный список) */ let head = arrToLinkedList(nums); // Инициализация хеш-таблицы const map1 = new Map(); while (head != null) { map1.set(head.val, head); // key: значение узла, value: узел head = head.next; } const node = hashingSearchLinkedList(map1, target); console.log('Объект узла со значением 3 =', node); export {}; ================================================ FILE: ru/codes/typescript/chapter_searching/linear_search.ts ================================================ /** * File: linear_search.ts * Created Time: 2023-01-07 * Author: Daniel (better.sunjian@gmail.com) */ import { ListNode, arrToLinkedList } from '../modules/ListNode'; /* Линейный поиск (массив) */ function linearSearchArray(nums: number[], target: number): number { // Обход массива for (let i = 0; i < nums.length; i++) { // Целевой элемент найден, вернуть его индекс if (nums[i] === target) { return i; } } // Целевой элемент не найден, вернуть -1 return -1; } /* Линейный поиск (связный список) */ function linearSearchLinkedList( head: ListNode | null, target: number ): ListNode | null { // Обойти связный список while (head) { // Найти целевой узел и вернуть его if (head.val === target) { return head; } head = head.next; } // Целевой узел не найден, вернуть null return null; } /* Driver Code */ const target = 3; /* Выполнить линейный поиск в массиве */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; const index = linearSearchArray(nums, target); console.log('Индекс целевого элемента 3 =', index); /* Выполнить линейный поиск в связном списке */ const head = arrToLinkedList(nums); const node = linearSearchLinkedList(head, target); console.log('Объект узла со значением 3 =', node); export {}; ================================================ FILE: ru/codes/typescript/chapter_searching/two_sum.ts ================================================ /** * File: two_sum.ts * Created Time: 2022-12-15 * Author: gyt95 (gytkwan@gmail.com) */ /* Метод 1: полный перебор */ function twoSumBruteForce(nums: number[], target: number): number[] { const n = nums.length; // Два вложенных цикла, временная сложность O(n^2) for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { if (nums[i] + nums[j] === target) { return [i, j]; } } } return []; } /* Метод 2: вспомогательная хеш-таблица */ function twoSumHashTable(nums: number[], target: number): number[] { // Вспомогательная хеш-таблица, пространственная сложность O(n) let m: Map = new Map(); // Один цикл, временная сложность O(n) for (let i = 0; i < nums.length; i++) { let index = m.get(target - nums[i]); if (index !== undefined) { return [index, i]; } else { m.set(nums[i], i); } } return []; } /* Driver Code */ // Метод 1 const nums = [2, 7, 11, 15], target = 13; let res = twoSumBruteForce(nums, target); console.log('Метод 1: res = ', res); // Метод 2 res = twoSumHashTable(nums, target); console.log('Метод 2: res = ', res); export {}; ================================================ FILE: ru/codes/typescript/chapter_sorting/bubble_sort.ts ================================================ /** * File: bubble_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* Пузырьковая сортировка */ function bubbleSort(nums: number[]): void { // Внешний цикл: неотсортированный диапазон [0, i] for (let i = nums.length - 1; i > 0; i--) { // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* Пузырьковая сортировка (оптимизация флагом) */ function bubbleSortWithFlag(nums: number[]): void { // Внешний цикл: неотсортированный диапазон [0, i] for (let i = nums.length - 1; i > 0; i--) { let flag = false; // Инициализировать флаг // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // Записать обмен элементов } } if (!flag) break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); console.log('После завершения пузырьковой сортировки nums =', nums); const nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); console.log('После завершения пузырьковой сортировки nums =', nums1); export {}; ================================================ FILE: ru/codes/typescript/chapter_sorting/bucket_sort.ts ================================================ /** * File: bucket_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* Сортировка корзинами */ function bucketSort(nums: number[]): void { // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину const k = nums.length / 2; const buckets: number[][] = []; for (let i = 0; i < k; i++) { buckets.push([]); } // 1. Распределить элементы массива по корзинам for (const num of nums) { // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] const i = Math.floor(num * k); // Добавить num в корзину i buckets[i].push(num); } // 2. Выполнить сортировку внутри каждой корзины for (const bucket of buckets) { // Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки bucket.sort((a, b) => a - b); } // 3. Обойти корзины и объединить результаты let i = 0; for (const bucket of buckets) { for (const num of bucket) { nums[i++] = num; } } } /* Driver Code */ const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); console.log('После завершения сортировки корзинами nums =', nums); export {}; ================================================ FILE: ru/codes/typescript/chapter_sorting/counting_sort.ts ================================================ /** * File: counting_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* Сортировка подсчетом */ // Простая реализация, не подходит для сортировки объектов function countingSortNaive(nums: number[]): void { // 1. Найти максимальный элемент массива m let m: number = Math.max(...nums); // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num const counter: number[] = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. Обойти counter и заполнить исходный массив nums элементами let i = 0; for (let num = 0; num < m + 1; num++) { for (let j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* Сортировка подсчетом */ // Полная реализация, позволяет сортировать объекты и является стабильной сортировкой function countingSort(nums: number[]): void { // 1. Найти максимальный элемент массива m let m: number = Math.max(...nums); // 2. Подсчитать число появлений каждой цифры // counter[num] обозначает число появлений num const counter: number[] = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» // То есть counter[num]-1 — это индекс последнего появления num в res for (let i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res // Инициализировать массив res для хранения результата const n = nums.length; const res: number[] = new Array(n); for (let i = n - 1; i >= 0; i--) { const num = nums[i]; res[counter[num] - 1] = num; // Поместить num по соответствующему индексу counter[num]--; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num } // Перезаписать исходный массив nums массивом результата res for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Driver Code */ const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); console.log('После завершения сортировки подсчетом (объекты не сортируются) nums =', nums); const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); console.log('После завершения сортировки подсчетом nums1 =', nums1); export {}; ================================================ FILE: ru/codes/typescript/chapter_sorting/heap_sort.ts ================================================ /** * File: heap_sort.ts * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ function siftDown(nums: number[], n: number, i: number): void { while (true) { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma let l = 2 * i + 1; let r = 2 * i + 2; let ma = i; if (l < n && nums[l] > nums[ma]) { ma = l; } if (r < n && nums[r] > nums[ma]) { ma = r; } // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if (ma === i) { break; } // Поменять два узла местами [nums[i], nums[ma]] = [nums[ma], nums[i]]; // Циклическое просеивание вниз i = ma; } } /* Сортировка кучей */ function heapSort(nums: number[]): void { // Построение кучи: выполнить heapify для всех узлов, кроме листовых for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // Извлекать максимальный элемент из кучи в течение n-1 итераций for (let i = nums.length - 1; i > 0; i--) { // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) [nums[0], nums[i]] = [nums[i], nums[0]]; // Начиная с корневого узла, выполнить просеивание сверху вниз siftDown(nums, i, 0); } } /* Driver Code */ const nums: number[] = [4, 1, 3, 1, 5, 2]; heapSort(nums); console.log('После завершения сортировки кучей nums =', nums); export {}; ================================================ FILE: ru/codes/typescript/chapter_sorting/insertion_sort.ts ================================================ /** * File: insertion_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* Сортировка вставками */ function insertionSort(nums: number[]): void { // Внешний цикл: отсортированный диапазон [0, i-1] for (let i = 1; i < nums.length; i++) { const base = nums[i]; let j = i - 1; // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // Сдвинуть nums[j] на одну позицию вправо j--; } nums[j + 1] = base; // Поместить base в правильную позицию } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); console.log('После завершения сортировки вставками nums =', nums); export {}; ================================================ FILE: ru/codes/typescript/chapter_sorting/merge_sort.ts ================================================ /** * File: merge_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* Объединить левый и правый подмассивы */ function merge(nums: number[], left: number, mid: number, right: number): void { // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] // Создать временный массив tmp для хранения результата слияния const tmp = new Array(right - left + 1); // Инициализировать начальные индексы левого и правого подмассивов let i = left, j = mid + 1, k = 0; // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* Сортировка слиянием */ function mergeSort(nums: number[], left: number, right: number): void { // Условие завершения if (left >= right) return; // Завершить рекурсию, когда длина подмассива равна 1 // Этап разбиения let mid = Math.floor(left + (right - left) / 2); // Вычислить середину mergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив mergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив // Этап слияния merge(nums, left, mid, right); } /* Driver Code */ const nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); console.log('После завершения сортировки слиянием nums =', nums); export {}; ================================================ FILE: ru/codes/typescript/chapter_sorting/quick_sort.ts ================================================ /** * File: quick_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* Класс быстрой сортировки */ class QuickSort { /* Обмен элементов */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Разбиение с опорными указателями */ partition(nums: number[], left: number, right: number): number { // Взять nums[left] в качестве опорного элемента let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j -= 1; // Идти справа налево в поисках первого элемента меньше опорного } while (i < j && nums[i] <= nums[left]) { i += 1; // Идти слева направо в поисках первого элемента больше опорного } // Обмен элементов this.swap(nums, i, j); // Поменять эти два элемента местами } this.swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } /* Быстрая сортировка */ quickSort(nums: number[], left: number, right: number): void { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) { return; } // Разбиение с опорными указателями const pivot = this.partition(nums, left, right); // Рекурсивно обработать левый и правый подмассивы this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* Класс быстрой сортировки (оптимизация медианным опорным элементом) */ class QuickSortMedian { /* Обмен элементов */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Выбрать медиану из трех кандидатов */ medianThree( nums: number[], left: number, mid: number, right: number ): number { let l = nums[left], m = nums[mid], r = nums[right]; // m находится между l и r if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // l находится между m и r if ((m <= l && l <= r) || (r <= l && l <= m)) return left; return right; } /* Разбиение с опорными указателями (медиана трех) */ partition(nums: number[], left: number, right: number): number { // Выбрать медиану из трех кандидатов let med = this.medianThree( nums, left, Math.floor((left + right) / 2), right ); // Переместить медиану в крайний левый элемент массива this.swap(nums, left, med); // Взять nums[left] в качестве опорного элемента let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // Идти справа налево в поисках первого элемента меньше опорного } while (i < j && nums[i] <= nums[left]) { i++; // Идти слева направо в поисках первого элемента больше опорного } this.swap(nums, i, j); // Поменять эти два элемента местами } this.swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } /* Быстрая сортировка */ quickSort(nums: number[], left: number, right: number): void { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) { return; } // Разбиение с опорными указателями const pivot = this.partition(nums, left, right); // Рекурсивно обработать левый и правый подмассивы this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* Класс быстрой сортировки (оптимизация глубины рекурсии) */ class QuickSortTailCall { /* Обмен элементов */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* Разбиение с опорными указателями */ partition(nums: number[], left: number, right: number): number { // Взять nums[left] в качестве опорного элемента let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // Идти справа налево в поисках первого элемента меньше опорного } while (i < j && nums[i] <= nums[left]) { i++; // Идти слева направо в поисках первого элемента больше опорного } this.swap(nums, i, j); // Поменять эти два элемента местами } this.swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } /* Быстрая сортировка (оптимизация глубины рекурсии) */ quickSort(nums: number[], left: number, right: number): void { // Завершить, когда длина подмассива равна 1 while (left < right) { // Операция разбиения с опорными указателями let pivot = this.partition(nums, left, right); // Выполнить быструю сортировку для более короткого из двух подмассивов if (pivot - left < right - pivot) { this.quickSort(nums, left, pivot - 1); // Рекурсивно отсортировать левый подмассив left = pivot + 1; // Оставшийся неотсортированный диапазон: [pivot + 1, right] } else { this.quickSort(nums, pivot + 1, right); // Рекурсивно отсортировать правый подмассив right = pivot - 1; // Оставшийся неотсортированный диапазон: [left, pivot - 1] } } } } /* Driver Code */ /* Быстрая сортировка */ const nums = [2, 4, 1, 0, 3, 5]; const quickSort = new QuickSort(); quickSort.quickSort(nums, 0, nums.length - 1); console.log('После завершения быстрой сортировки nums =', nums); /* Быстрая сортировка (оптимизация медианным опорным элементом) */ const nums1 = [2, 4, 1, 0, 3, 5]; const quickSortMedian = new QuickSortMedian(); quickSortMedian.quickSort(nums1, 0, nums1.length - 1); console.log('После завершения быстрой сортировки (оптимизация медианным опорным элементом) nums1 =', nums1); /* Быстрая сортировка (оптимизация глубины рекурсии) */ const nums2 = [2, 4, 1, 0, 3, 5]; const quickSortTailCall = new QuickSortTailCall(); quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); console.log('После завершения быстрой сортировки (оптимизация глубины рекурсии) nums2 =', nums2); export {}; ================================================ FILE: ru/codes/typescript/chapter_sorting/radix_sort.ts ================================================ /** * File: radix_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* Получить k-й разряд элемента num, где exp = 10^(k-1) */ function digit(num: number, exp: number): number { // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени return Math.floor(num / exp) % 10; } /* Сортировка подсчетом (сортировка по k-му разряду nums) */ function countingSortDigit(nums: number[], exp: number): void { // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 const counter = new Array(10).fill(0); const n = nums.length; // Подсчитать число появлений каждой цифры от 0 до 9 for (let i = 0; i < n; i++) { const d = digit(nums[i], exp); // Получить k-й разряд nums[i], обозначив его как d counter[d]++; // Подсчитать число появлений цифры d } // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» for (let i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // Выполняя обратный проход, заполнить res элементами по статистике в корзинах const res = new Array(n).fill(0); for (let i = n - 1; i >= 0; i--) { const d = digit(nums[i], exp); const j = counter[d] - 1; // Получить индекс j цифры d в массиве res[j] = nums[i]; // Поместить текущий элемент по индексу j counter[d]--; // Уменьшить количество d на 1 } // Перезаписать исходный массив nums результатом for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Поразрядная сортировка */ function radixSort(nums: number[]): void { // Получить максимальный элемент массива, чтобы определить максимальное число разрядов let m: number = Math.max(... nums); // Проходить разряды от младшего к старшему for (let exp = 1; exp <= m; exp *= 10) { // Выполнить сортировку подсчетом по k-му разряду элементов массива // k = 1 -> exp = 1 // k = 2 -> exp = 10 // то есть exp = 10^(k-1) countingSortDigit(nums, exp); } } /* Driver Code */ const nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radixSort(nums); console.log('После завершения поразрядной сортировки nums =', nums); export {}; ================================================ FILE: ru/codes/typescript/chapter_sorting/selection_sort.ts ================================================ /** * File: selection_sort.ts * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* Сортировка выбором */ function selectionSort(nums: number[]): void { let n = nums.length; // Внешний цикл: неотсортированный диапазон [i, n-1] for (let i = 0; i < n - 1; i++) { // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне let k = i; for (let j = i + 1; j < n; j++) { if (nums[j] < nums[k]) { k = j; // Записать индекс минимального элемента } } // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона [nums[i], nums[k]] = [nums[k], nums[i]]; } } /* Driver Code */ const nums: number[] = [4, 1, 3, 1, 5, 2]; selectionSort(nums); console.log('После завершения сортировки выбором nums =', nums); export {}; ================================================ FILE: ru/codes/typescript/chapter_stack_and_queue/array_deque.ts ================================================ /** * File: array_deque.ts * Created Time: 2023-02-28 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Двусторонняя очередь на основе кольцевого массива */ class ArrayDeque { private nums: number[]; // Массив для хранения элементов двусторонней очереди private front: number; // Указатель head, указывающий на первый элемент очереди private queSize: number; // Длина двусторонней очереди /* Конструктор */ constructor(capacity: number) { this.nums = new Array(capacity); this.front = 0; this.queSize = 0; } /* Получить вместимость двусторонней очереди */ capacity(): number { return this.nums.length; } /* Получение длины двусторонней очереди */ size(): number { return this.queSize; } /* Проверка, пуста ли двусторонняя очередь */ isEmpty(): boolean { return this.queSize === 0; } /* Вычислить индекс в кольцевом массиве */ index(i: number): number { // С помощью операции взятия по модулю соединить начало и конец массива // Когда i выходит за конец массива, он возвращается в начало // Когда i выходит за начало массива, он возвращается в конец return (i + this.capacity()) % this.capacity(); } /* Добавление в голову очереди */ pushFirst(num: number): void { if (this.queSize === this.capacity()) { console.log('Двусторонняя очередь заполнена'); return; } // Указатель головы сдвигается на одну позицию влево // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост this.front = this.index(this.front - 1); // Добавить num в голову очереди this.nums[this.front] = num; this.queSize++; } /* Добавление в хвост очереди */ pushLast(num: number): void { if (this.queSize === this.capacity()) { console.log('Двусторонняя очередь заполнена'); return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 const rear: number = this.index(this.front + this.queSize); // Добавить num в хвост очереди this.nums[rear] = num; this.queSize++; } /* Извлечение из головы очереди */ popFirst(): number { const num: number = this.peekFirst(); // Указатель головы сдвигается на одну позицию назад this.front = this.index(this.front + 1); this.queSize--; return num; } /* Извлечение из хвоста очереди */ popLast(): number { const num: number = this.peekLast(); this.queSize--; return num; } /* Доступ к элементу в начале очереди */ peekFirst(): number { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); return this.nums[this.front]; } /* Доступ к элементу в конце очереди */ peekLast(): number { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); // Вычислить индекс хвостового элемента const last = this.index(this.front + this.queSize - 1); return this.nums[last]; } /* Вернуть массив для вывода */ toArray(): number[] { // Преобразовывать только элементы списка в пределах фактической длины const res: number[] = []; for (let i = 0, j = this.front; i < this.queSize; i++, j++) { res[i] = this.nums[this.index(j)]; } return res; } } /* Driver Code */ /* Инициализация двусторонней очереди */ const capacity = 5; const deque: ArrayDeque = new ArrayDeque(capacity); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); console.log('Двусторонняя очередь deque = [' + deque.toArray() + ']'); /* Доступ к элементу */ const peekFirst = deque.peekFirst(); console.log('Элемент в начале очереди peekFirst = ' + peekFirst); const peekLast = deque.peekLast(); console.log('Элемент в конце очереди peekLast = ' + peekLast); /* Добавление элемента в очередь */ deque.pushLast(4); console.log('После добавления элемента 4 в хвост deque = [' + deque.toArray() + ']'); deque.pushFirst(1); console.log('После добавления элемента 1 в голову deque = [' + deque.toArray() + ']'); /* Извлечение элемента из очереди */ const popLast = deque.popLast(); console.log( 'Извлеченный из хвоста элемент = ' + popLast + ', deque после извлечения из хвоста = [' + deque.toArray() + ']' ); const popFirst = deque.popFirst(); console.log( 'Извлеченный из головы элемент = ' + popFirst + ', deque после извлечения из головы = [' + deque.toArray() + ']' ); /* Получение длины двусторонней очереди */ const size = deque.size(); console.log('Длина двусторонней очереди size = ' + size); /* Проверка, пуста ли двусторонняя очередь */ const isEmpty = deque.isEmpty(); console.log('Пуста ли двусторонняя очередь = ' + isEmpty); export {}; ================================================ FILE: ru/codes/typescript/chapter_stack_and_queue/array_queue.ts ================================================ /** * File: array_queue.ts * Created Time: 2022-12-11 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Очередь на основе кольцевого массива */ class ArrayQueue { private nums: number[]; // Массив для хранения элементов очереди private front: number; // Указатель head, указывающий на первый элемент очереди private queSize: number; // Длина очереди constructor(capacity: number) { this.nums = new Array(capacity); this.front = this.queSize = 0; } /* Получить вместимость очереди */ get capacity(): number { return this.nums.length; } /* Получение длины очереди */ get size(): number { return this.queSize; } /* Проверка, пуста ли очередь */ isEmpty(): boolean { return this.queSize === 0; } /* Поместить в очередь */ push(num: number): void { if (this.size === this.capacity) { console.log('Очередь заполнена'); return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива const rear = (this.front + this.queSize) % this.capacity; // Добавить num в хвост очереди this.nums[rear] = num; this.queSize++; } /* Извлечь из очереди */ pop(): number { const num = this.peek(); // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива this.front = (this.front + 1) % this.capacity; this.queSize--; return num; } /* Доступ к элементу в начале очереди */ peek(): number { if (this.isEmpty()) throw new Error('очередь пуста'); return this.nums[this.front]; } /* Вернуть Array */ toArray(): number[] { // Преобразовывать только элементы списка в пределах фактической длины const arr = new Array(this.size); for (let i = 0, j = this.front; i < this.size; i++, j++) { arr[i] = this.nums[j % this.capacity]; } return arr; } } /* Driver Code */ /* Инициализация очереди */ const capacity = 10; const queue = new ArrayQueue(capacity); /* Добавление элемента в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('Очередь queue =', queue.toArray()); /* Доступ к элементу в начале очереди */ const peek = queue.peek(); console.log('Элемент в начале очереди peek = ' + peek); /* Извлечение элемента из очереди */ const pop = queue.pop(); console.log('Извлечен элемент pop = ' + pop + ', очередь после извлечения queue =', queue.toArray()); /* Получение длины очереди */ const size = queue.size; console.log('Длина очереди size = ' + size); /* Проверка, пуста ли очередь */ const isEmpty = queue.isEmpty(); console.log('Пуста ли очередь = ' + isEmpty); /* Проверка кольцевого массива */ for (let i = 0; i < 10; i++) { queue.push(i); queue.pop(); console.log('После ' + i + '-го добавления и извлечения queue =', queue.toArray()); } export {}; ================================================ FILE: ru/codes/typescript/chapter_stack_and_queue/array_stack.ts ================================================ /** * File: array_stack.ts * Created Time: 2022-12-08 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Стек на основе массива */ class ArrayStack { private stack: number[]; constructor() { this.stack = []; } /* Получение длины стека */ get size(): number { return this.stack.length; } /* Проверка, пуст ли стек */ isEmpty(): boolean { return this.stack.length === 0; } /* Поместить в стек */ push(num: number): void { this.stack.push(num); } /* Извлечь из стека */ pop(): number | undefined { if (this.isEmpty()) throw new Error('стек пуст'); return this.stack.pop(); } /* Доступ к верхнему элементу стека */ top(): number | undefined { if (this.isEmpty()) throw new Error('стек пуст'); return this.stack[this.stack.length - 1]; } /* Вернуть Array */ toArray() { return this.stack; } } /* Driver Code */ /* Инициализация стека */ const stack = new ArrayStack(); /* Помещение элемента в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('Стек stack = '); console.log(stack.toArray()); /* Доступ к верхнему элементу стека */ const top = stack.top(); console.log('Верхний элемент стека top = ' + top); /* Извлечение элемента из стека */ const pop = stack.pop(); console.log('Извлечен элемент pop = ' + pop + ', стек после извлечения stack = '); console.log(stack.toArray()); /* Получение длины стека */ const size = stack.size; console.log('Длина стека size = ' + size); /* Проверка на пустоту */ const isEmpty = stack.isEmpty(); console.log('Пуст ли стек = ' + isEmpty); export {}; ================================================ FILE: ru/codes/typescript/chapter_stack_and_queue/deque.ts ================================================ /** * File: deque.ts * Created Time: 2023-01-17 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Driver Code */ /* Инициализация двусторонней очереди */ // В TypeScript нет встроенной двусторонней очереди, поэтому Array можно использовать как двустороннюю очередь const deque: number[] = []; /* Добавление элемента в очередь */ deque.push(2); deque.push(5); deque.push(4); // Обратите внимание: поскольку используется массив, временная сложность метода unshift() равна O(n) deque.unshift(3); deque.unshift(1); console.log('Двусторонняя очередь deque = ', deque); /* Доступ к элементу */ const peekFirst: number = deque[0]; console.log('Элемент в начале очереди peekFirst = ' + peekFirst); const peekLast: number = deque[deque.length - 1]; console.log('Элемент в конце очереди peekLast = ' + peekLast); /* Извлечение элемента из очереди */ // Обратите внимание: поскольку используется массив, временная сложность метода shift() равна O(n) const popFront: number = deque.shift() as number; console.log( 'Извлеченный из головы элемент popFront = ' + popFront + ', deque после извлечения из головы = ' + deque ); const popBack: number = deque.pop() as number; console.log( 'Извлеченный из хвоста элемент popBack = ' + popBack + ', deque после извлечения из хвоста = ' + deque ); /* Получение длины двусторонней очереди */ const size: number = deque.length; console.log('Длина двусторонней очереди size = ' + size); /* Проверка, пуста ли двусторонняя очередь */ const isEmpty: boolean = size === 0; console.log('Пуста ли двусторонняя очередь = ' + isEmpty); export {}; ================================================ FILE: ru/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts ================================================ /** * File: linkedlist_deque.ts * Created Time: 2023-02-04 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Узел двусвязного списка */ class ListNode { prev: ListNode; // Ссылка на узел-предшественник (указатель) next: ListNode; // Ссылка на узел-преемник (указатель) val: number; // Значение узла constructor(val: number) { this.val = val; this.next = null; this.prev = null; } } /* Двусторонняя очередь на основе двусвязного списка */ class LinkedListDeque { private front: ListNode; // Головной узел front private rear: ListNode; // Хвостовой узел rear private queSize: number; // Длина двусторонней очереди constructor() { this.front = null; this.rear = null; this.queSize = 0; } /* Операция добавления в хвост очереди */ pushLast(val: number): void { const node: ListNode = new ListNode(val); // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node if (this.queSize === 0) { this.front = node; this.rear = node; } else { // Добавить node в хвост списка this.rear.next = node; node.prev = this.rear; this.rear = node; // Обновить хвостовой узел } this.queSize++; } /* Операция добавления в голову очереди */ pushFirst(val: number): void { const node: ListNode = new ListNode(val); // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node if (this.queSize === 0) { this.front = node; this.rear = node; } else { // Добавить node в голову списка this.front.prev = node; node.next = this.front; this.front = node; // Обновить головной узел } this.queSize++; } /* Операция извлечения из хвоста очереди */ popLast(): number { if (this.queSize === 0) { return null; } const value: number = this.rear.val; // Сохранить значение хвостового узла // Удалить хвостовой узел let temp: ListNode = this.rear.prev; if (temp !== null) { temp.next = null; this.rear.prev = null; } this.rear = temp; // Обновить хвостовой узел this.queSize--; return value; } /* Операция извлечения из головы очереди */ popFirst(): number { if (this.queSize === 0) { return null; } const value: number = this.front.val; // Сохранить значение хвостового узла // Удалить головной узел let temp: ListNode = this.front.next; if (temp !== null) { temp.prev = null; this.front.next = null; } this.front = temp; // Обновить головной узел this.queSize--; return value; } /* Доступ к элементу в конце очереди */ peekLast(): number { return this.queSize === 0 ? null : this.rear.val; } /* Доступ к элементу в начале очереди */ peekFirst(): number { return this.queSize === 0 ? null : this.front.val; } /* Получение длины двусторонней очереди */ size(): number { return this.queSize; } /* Проверка, пуста ли двусторонняя очередь */ isEmpty(): boolean { return this.queSize === 0; } /* Вывести двустороннюю очередь */ print(): void { const arr: number[] = []; let temp: ListNode = this.front; while (temp !== null) { arr.push(temp.val); temp = temp.next; } console.log('[' + arr.join(', ') + ']'); } } /* Driver Code */ /* Инициализация двусторонней очереди */ const linkedListDeque: LinkedListDeque = new LinkedListDeque(); linkedListDeque.pushLast(3); linkedListDeque.pushLast(2); linkedListDeque.pushLast(5); console.log('Двусторонняя очередь linkedListDeque = '); linkedListDeque.print(); /* Доступ к элементу */ const peekFirst: number = linkedListDeque.peekFirst(); console.log('Элемент в начале очереди peekFirst = ' + peekFirst); const peekLast: number = linkedListDeque.peekLast(); console.log('Элемент в конце очереди peekLast = ' + peekLast); /* Добавление элемента в очередь */ linkedListDeque.pushLast(4); console.log('После добавления элемента 4 в хвост linkedListDeque = '); linkedListDeque.print(); linkedListDeque.pushFirst(1); console.log('После добавления элемента 1 в голову linkedListDeque = '); linkedListDeque.print(); /* Извлечение элемента из очереди */ const popLast: number = linkedListDeque.popLast(); console.log('Извлечен элемент из хвоста = ' + popLast + ', linkedListDeque после извлечения из хвоста = '); linkedListDeque.print(); const popFirst: number = linkedListDeque.popFirst(); console.log('Извлечен элемент из головы = ' + popFirst + ', linkedListDeque после извлечения из головы = '); linkedListDeque.print(); /* Получение длины двусторонней очереди */ const size: number = linkedListDeque.size(); console.log('Длина двусторонней очереди size = ' + size); /* Проверка, пуста ли двусторонняя очередь */ const isEmpty: boolean = linkedListDeque.isEmpty(); console.log('Пуста ли двусторонняя очередь = ' + isEmpty); ================================================ FILE: ru/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts ================================================ /** * File: linkedlist_queue.ts * Created Time: 2022-12-19 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ import { ListNode } from '../modules/ListNode'; /* Очередь на основе связного списка */ class LinkedListQueue { private front: ListNode | null; // Головной узел front private rear: ListNode | null; // Хвостовой узел rear private queSize: number = 0; constructor() { this.front = null; this.rear = null; } /* Получение длины очереди */ get size(): number { return this.queSize; } /* Проверка, пуста ли очередь */ isEmpty(): boolean { return this.size === 0; } /* Поместить в очередь */ push(num: number): void { // Добавить num после хвостового узла const node = new ListNode(num); // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел if (!this.front) { this.front = node; this.rear = node; // Если очередь не пуста, добавить этот узел после хвостового узла } else { this.rear!.next = node; this.rear = node; } this.queSize++; } /* Извлечь из очереди */ pop(): number { const num = this.peek(); if (!this.front) throw new Error('очередь пуста'); // Удалить головной узел this.front = this.front.next; this.queSize--; return num; } /* Доступ к элементу в начале очереди */ peek(): number { if (this.size === 0) throw new Error('очередь пуста'); return this.front!.val; } /* Преобразовать связный список в Array и вернуть */ toArray(): number[] { let node = this.front; const res = new Array(this.size); for (let i = 0; i < res.length; i++) { res[i] = node!.val; node = node!.next; } return res; } } /* Driver Code */ /* Инициализация очереди */ const queue = new LinkedListQueue(); /* Добавление элемента в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('Очередь queue = ' + queue.toArray()); /* Доступ к элементу в начале очереди */ const peek = queue.peek(); console.log('Элемент в начале очереди peek = ' + peek); /* Извлечение элемента из очереди */ const pop = queue.pop(); console.log('Извлечен элемент pop = ' + pop + ', очередь после извлечения queue = ' + queue.toArray()); /* Получение длины очереди */ const size = queue.size; console.log('Длина очереди size = ' + size); /* Проверка, пуста ли очередь */ const isEmpty = queue.isEmpty(); console.log('Пуста ли очередь = ' + isEmpty); export {}; ================================================ FILE: ru/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts ================================================ /** * File: linkedlist_stack.ts * Created Time: 2022-12-21 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ import { ListNode } from '../modules/ListNode'; /* Стек на основе связного списка */ class LinkedListStack { private stackPeek: ListNode | null; // Использовать головной узел как вершину стека private stkSize: number = 0; // Длина стека constructor() { this.stackPeek = null; } /* Получение длины стека */ get size(): number { return this.stkSize; } /* Проверка, пуст ли стек */ isEmpty(): boolean { return this.size === 0; } /* Поместить в стек */ push(num: number): void { const node = new ListNode(num); node.next = this.stackPeek; this.stackPeek = node; this.stkSize++; } /* Извлечь из стека */ pop(): number { const num = this.peek(); if (!this.stackPeek) throw new Error('стек пуст'); this.stackPeek = this.stackPeek.next; this.stkSize--; return num; } /* Доступ к верхнему элементу стека */ peek(): number { if (!this.stackPeek) throw new Error('стек пуст'); return this.stackPeek.val; } /* Преобразовать связный список в Array и вернуть */ toArray(): number[] { let node = this.stackPeek; const res = new Array(this.size); for (let i = res.length - 1; i >= 0; i--) { res[i] = node!.val; node = node!.next; } return res; } } /* Driver Code */ /* Инициализация стека */ const stack = new LinkedListStack(); /* Помещение элемента в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('Стек stack = ' + stack.toArray()); /* Доступ к верхнему элементу стека */ const peek = stack.peek(); console.log('Верхний элемент стека peek = ' + peek); /* Извлечение элемента из стека */ const pop = stack.pop(); console.log('Извлечен элемент pop = ' + pop + ', стек после извлечения stack = ' + stack.toArray()); /* Получение длины стека */ const size = stack.size; console.log('Длина стека size = ' + size); /* Проверка на пустоту */ const isEmpty = stack.isEmpty(); console.log('Пуст ли стек = ' + isEmpty); export {}; ================================================ FILE: ru/codes/typescript/chapter_stack_and_queue/queue.ts ================================================ /** * File: queue.ts * Created Time: 2022-12-05 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* Инициализация очереди */ // В TypeScript нет встроенной очереди, поэтому Array можно использовать как очередь const queue: number[] = []; /* Добавление элемента в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('Очередь queue =', queue); /* Доступ к элементу в начале очереди */ const peek = queue[0]; console.log('Элемент в начале очереди peek =', peek); /* Извлечение элемента из очереди */ // В основе лежит массив, поэтому временная сложность метода shift() равна O(n) const pop = queue.shift(); console.log('Извлечен элемент pop =', pop, ', очередь после извлечения queue = ', queue); /* Получение длины очереди */ const size = queue.length; console.log('Длина очереди size =', size); /* Проверка, пуста ли очередь */ const isEmpty = queue.length === 0; console.log('Пуста ли очередь = ', isEmpty); export {}; ================================================ FILE: ru/codes/typescript/chapter_stack_and_queue/stack.ts ================================================ /** * File: stack.ts * Created Time: 2022-12-04 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* Инициализация стека */ // В TypeScript нет встроенного класса стека, поэтому Array можно использовать как стек const stack: number[] = []; /* Помещение элемента в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('Стек stack =', stack); /* Доступ к верхнему элементу стека */ const peek = stack[stack.length - 1]; console.log('Верхний элемент стека peek =', peek); /* Извлечение элемента из стека */ const pop = stack.pop(); console.log('Извлечен элемент pop =', pop); console.log('Стек после извлечения =', stack); /* Получение длины стека */ const size = stack.length; console.log('Длина стека size =', size); /* Проверка на пустоту */ const isEmpty = stack.length === 0; console.log('Пуст ли стек =', isEmpty); export {}; ================================================ FILE: ru/codes/typescript/chapter_tree/array_binary_tree.ts ================================================ /** * File: array_binary_tree.js * Created Time: 2023-08-09 * Author: yuan0221 (yl1452491917@gmail.com) */ import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; type Order = 'pre' | 'in' | 'post'; /* Класс двоичного дерева в массивном представлении */ class ArrayBinaryTree { #tree: (number | null)[]; /* Конструктор */ constructor(arr: (number | null)[]) { this.#tree = arr; } /* Вместимость списка */ size(): number { return this.#tree.length; } /* Получить значение узла с индексом i */ val(i: number): number | null { // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию if (i < 0 || i >= this.size()) return null; return this.#tree[i]; } /* Получить индекс левого дочернего узла узла с индексом i */ left(i: number): number { return 2 * i + 1; } /* Получить индекс правого дочернего узла узла с индексом i */ right(i: number): number { return 2 * i + 2; } /* Получить индекс родительского узла узла с индексом i */ parent(i: number): number { return Math.floor((i - 1) / 2); // Округление вниз при делении } /* Обход в ширину */ levelOrder(): number[] { let res = []; // Непосредственно обходить массив for (let i = 0; i < this.size(); i++) { if (this.val(i) !== null) res.push(this.val(i)); } return res; } /* Обход в глубину */ #dfs(i: number, order: Order, res: (number | null)[]): void { // Если это пустая позиция, вернуть if (this.val(i) === null) return; // Предварительный обход if (order === 'pre') res.push(this.val(i)); this.#dfs(this.left(i), order, res); // Симметричный обход if (order === 'in') res.push(this.val(i)); this.#dfs(this.right(i), order, res); // Обратный обход if (order === 'post') res.push(this.val(i)); } /* Предварительный обход */ preOrder(): (number | null)[] { const res = []; this.#dfs(0, 'pre', res); return res; } /* Симметричный обход */ inOrder(): (number | null)[] { const res = []; this.#dfs(0, 'in', res); return res; } /* Обратный обход */ postOrder(): (number | null)[] { const res = []; this.#dfs(0, 'post', res); return res; } } /* Driver Code */ // Инициализировать двоичное дерево // Здесь используется функция, напрямую строящая двоичное дерево из массива const arr = Array.of( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ); const root = arrToTree(arr); console.log('\nИнициализация двоичного дерева\n'); console.log('Массивное представление двоичного дерева:'); console.log(arr); console.log('Связное представление двоичного дерева:'); printTree(root); // Класс двоичного дерева в массивном представлении const abt = new ArrayBinaryTree(arr); // Доступ к узлу const i = 1; const l = abt.left(i); const r = abt.right(i); const p = abt.parent(i); console.log('\nТекущий индекс узла = ' + i + ', значение = ' + abt.val(i)); console.log( 'Индекс левого дочернего узла = ' + l + ', значение = ' + (l === null ? 'null' : abt.val(l)) ); console.log( 'Индекс правого дочернего узла = ' + r + ', значение = ' + (r === null ? 'null' : abt.val(r)) ); console.log( 'Индекс родительского узла = ' + p + ', значение = ' + (p === null ? 'null' : abt.val(p)) ); // Обходить дерево let res = abt.levelOrder(); console.log('\nОбход в ширину: ' + res); res = abt.preOrder(); console.log('Предварительный обход: ' + res); res = abt.inOrder(); console.log('Симметричный обход: ' + res); res = abt.postOrder(); console.log('Обратный обход: ' + res); export {}; ================================================ FILE: ru/codes/typescript/chapter_tree/avl_tree.ts ================================================ /** * File: avl_tree.ts * Created Time: 2023-02-06 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* AVL-дерево */ class AVLTree { root: TreeNode; /* Конструктор */ constructor() { this.root = null; // Корневой узел } /* Получить высоту узла */ height(node: TreeNode): number { // Высота пустого узла равна -1, высота листового узла равна 0 return node === null ? -1 : node.height; } /* Обновить высоту узла */ private updateHeight(node: TreeNode): void { // Высота узла равна высоте более высокого поддерева + 1 node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; } /* Получить коэффициент баланса */ balanceFactor(node: TreeNode): number { // Коэффициент баланса пустого узла равен 0 if (node === null) return 0; // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева return this.height(node.left) - this.height(node.right); } /* Операция правого вращения */ private rightRotate(node: TreeNode): TreeNode { const child = node.left; const grandChild = child.right; // Выполнить правое вращение узла node вокруг child child.right = node; node.left = grandChild; // Обновить высоту узла this.updateHeight(node); this.updateHeight(child); // Вернуть корневой узел поддерева после вращения return child; } /* Операция левого вращения */ private leftRotate(node: TreeNode): TreeNode { const child = node.right; const grandChild = child.left; // Выполнить левое вращение узла node вокруг child child.left = node; node.right = grandChild; // Обновить высоту узла this.updateHeight(node); this.updateHeight(child); // Вернуть корневой узел поддерева после вращения return child; } /* Выполнить вращение, чтобы снова сбалансировать поддерево */ private rotate(node: TreeNode): TreeNode { // Получить коэффициент баланса узла node const balanceFactor = this.balanceFactor(node); // Левосторонне перекошенное дерево if (balanceFactor > 1) { if (this.balanceFactor(node.left) >= 0) { // Правое вращение return this.rightRotate(node); } else { // Сначала левое вращение, затем правое node.left = this.leftRotate(node.left); return this.rightRotate(node); } } // Правосторонне перекошенное дерево if (balanceFactor < -1) { if (this.balanceFactor(node.right) <= 0) { // Левое вращение return this.leftRotate(node); } else { // Сначала правое вращение, затем левое node.right = this.rightRotate(node.right); return this.leftRotate(node); } } // Дерево сбалансировано, вращение не требуется, вернуть сразу return node; } /* Вставка узла */ insert(val: number): void { this.root = this.insertHelper(this.root, val); } /* Рекурсивная вставка узла (вспомогательный метод) */ private insertHelper(node: TreeNode, val: number): TreeNode { if (node === null) return new TreeNode(val); /* 1. Найти позицию вставки и вставить узел */ if (val < node.val) { node.left = this.insertHelper(node.left, val); } else if (val > node.val) { node.right = this.insertHelper(node.right, val); } else { return node; // Повторяющийся узел не вставлять, сразу вернуть } this.updateHeight(node); // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = this.rotate(node); // Вернуть корневой узел поддерева return node; } /* Удаление узла */ remove(val: number): void { this.root = this.removeHelper(this.root, val); } /* Рекурсивное удаление узла (вспомогательный метод) */ private removeHelper(node: TreeNode, val: number): TreeNode { if (node === null) return null; /* 1. Найти узел и удалить его */ if (val < node.val) { node.left = this.removeHelper(node.left, val); } else if (val > node.val) { node.right = this.removeHelper(node.right, val); } else { if (node.left === null || node.right === null) { const child = node.left !== null ? node.left : node.right; // Число дочерних узлов = 0, удалить node и сразу вернуть if (child === null) { return null; } else { // Число дочерних узлов = 1, удалить node напрямую node = child; } } else { // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел let temp = node.right; while (temp.left !== null) { temp = temp.left; } node.right = this.removeHelper(node.right, temp.val); node.val = temp.val; } } this.updateHeight(node); // Обновить высоту узла /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ node = this.rotate(node); // Вернуть корневой узел поддерева return node; } /* Поиск узла */ search(val: number): TreeNode { let cur = this.root; // Искать в цикле и выйти после прохода за листовой узел while (cur !== null) { if (cur.val < val) { // Целевой узел находится в правом поддереве cur cur = cur.right; } else if (cur.val > val) { // Целевой узел находится в левом поддереве cur cur = cur.left; } else { // Найти целевой узел и выйти из цикла break; } } // Вернуть целевой узел return cur; } } function testInsert(tree: AVLTree, val: number): void { tree.insert(val); console.log('\nПосле вставки узла ' + val + ' AVL-дерево имеет вид'); printTree(tree.root); } function testRemove(tree: AVLTree, val: number): void { tree.remove(val); console.log('\nПосле удаления узла ' + val + ' AVL-дерево имеет вид'); printTree(tree.root); } /* Driver Code */ /* Инициализация пустого AVL-дерева */ const avlTree = new AVLTree(); /* Вставка узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); testInsert(avlTree, 4); testInsert(avlTree, 5); testInsert(avlTree, 8); testInsert(avlTree, 7); testInsert(avlTree, 9); testInsert(avlTree, 10); testInsert(avlTree, 6); /* Вставка повторяющегося узла */ testInsert(avlTree, 7); /* Удаление узла */ // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла testRemove(avlTree, 8); // Удаление узла степени 0 testRemove(avlTree, 5); // Удаление узла степени 1 testRemove(avlTree, 4); // Удаление узла степени 2 /* Поиск узла */ const node = avlTree.search(7); console.log('\nНайденный объект узла =', node, ', значение узла = ' + node.val); export {}; ================================================ FILE: ru/codes/typescript/chapter_tree/binary_search_tree.ts ================================================ /** * File: binary_search_tree.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Двоичное дерево поиска */ class BinarySearchTree { private root: TreeNode | null; /* Конструктор */ constructor() { // Инициализировать пустое дерево this.root = null; } /* Получить корневой узел двоичного дерева */ getRoot(): TreeNode | null { return this.root; } /* Поиск узла */ search(num: number): TreeNode | null { let cur = this.root; // Искать в цикле и выйти после прохода за листовой узел while (cur !== null) { // Целевой узел находится в правом поддереве cur if (cur.val < num) cur = cur.right; // Целевой узел находится в левом поддереве cur else if (cur.val > num) cur = cur.left; // Найти целевой узел и выйти из цикла else break; } // Вернуть целевой узел return cur; } /* Вставка узла */ insert(num: number): void { // Если дерево пусто, инициализировать корневой узел if (this.root === null) { this.root = new TreeNode(num); return; } let cur: TreeNode | null = this.root, pre: TreeNode | null = null; // Искать в цикле и выйти после прохода за листовой узел while (cur !== null) { // Найти повторяющийся узел и сразу вернуть if (cur.val === num) return; pre = cur; // Позиция вставки находится в правом поддереве cur if (cur.val < num) cur = cur.right; // Позиция вставки находится в левом поддереве cur else cur = cur.left; } // Вставка узла const node = new TreeNode(num); if (pre!.val < num) pre!.right = node; else pre!.left = node; } /* Удаление узла */ remove(num: number): void { // Если дерево пусто, сразу вернуть if (this.root === null) return; let cur: TreeNode | null = this.root, pre: TreeNode | null = null; // Искать в цикле и выйти после прохода за листовой узел while (cur !== null) { // Найти узел для удаления и выйти из цикла if (cur.val === num) break; pre = cur; // Узел для удаления находится в правом поддереве cur if (cur.val < num) cur = cur.right; // Узел для удаления находится в левом поддереве cur else cur = cur.left; } // Если узел для удаления отсутствует, сразу вернуть if (cur === null) return; // Число дочерних узлов = 0 или 1 if (cur.left === null || cur.right === null) { // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел const child: TreeNode | null = cur.left !== null ? cur.left : cur.right; // Удалить узел cur if (cur !== this.root) { if (pre!.left === cur) pre!.left = child; else pre!.right = child; } else { // Если удаляемый узел является корнем, заново назначить корневой узел this.root = child; } } // Число дочерних узлов = 2 else { // Получить следующий узел после cur в симметричном обходе let tmp: TreeNode | null = cur.right; while (tmp!.left !== null) { tmp = tmp!.left; } // Рекурсивно удалить узел tmp this.remove(tmp!.val); // Перезаписать cur значением tmp cur.val = tmp!.val; } } } /* Driver Code */ /* Инициализация двоичного дерева поиска */ const bst = new BinarySearchTree(); // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (const num of nums) { bst.insert(num); } console.log('\nИнициализированное двоичное дерево\n'); printTree(bst.getRoot()); /* Поиск узла */ const node = bst.search(7); console.log( '\nНайденный объект узла = ' + node + ', значение узла = ' + (node ? node.val : 'null') ); /* Вставка узла */ bst.insert(16); console.log('\nПосле вставки узла 16 двоичное дерево имеет вид\n'); printTree(bst.getRoot()); /* Удаление узла */ bst.remove(1); console.log('\nПосле удаления узла 1 двоичное дерево имеет вид\n'); printTree(bst.getRoot()); bst.remove(2); console.log('\nПосле удаления узла 2 двоичное дерево имеет вид\n'); printTree(bst.getRoot()); bst.remove(4); console.log('\nПосле удаления узла 4 двоичное дерево имеет вид\n'); printTree(bst.getRoot()); export {}; ================================================ FILE: ru/codes/typescript/chapter_tree/binary_tree.ts ================================================ /** * File: binary_tree.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Инициализация двоичного дерева */ // Инициализация узла let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // Построить связи между узлами (указатели) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; console.log('\nИнициализация двоичного дерева\n'); printTree(n1); /* Вставка и удаление узлов */ const P = new TreeNode(0); // Вставить узел P между n1 -> n2 n1.left = P; P.left = n2; console.log('\nПосле вставки узла P\n'); printTree(n1); // Удалить узел P n1.left = n2; console.log('\nПосле удаления узла P\n'); printTree(n1); export {}; ================================================ FILE: ru/codes/typescript/chapter_tree/binary_tree_bfs.ts ================================================ /** * File: binary_tree_bfs.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* Обход в ширину */ function levelOrder(root: TreeNode | null): number[] { // Инициализировать очередь и добавить корневой узел const queue = [root]; // Инициализировать список для хранения последовательности обхода const list: number[] = []; while (queue.length) { let node = queue.shift() as TreeNode; // Извлечение из очереди list.push(node.val); // Сохранить значение узла if (node.left) { queue.push(node.left); // Поместить левый дочерний узел в очередь } if (node.right) { queue.push(node.right); // Поместить правый дочерний узел в очередь } } return list; } /* Driver Code */ /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\nИнициализация двоичного дерева\n'); printTree(root); /* Обход в ширину */ const list = levelOrder(root); console.log('\nПоследовательность печати узлов при обходе в ширину = ' + list); export {}; ================================================ FILE: ru/codes/typescript/chapter_tree/binary_tree_dfs.ts ================================================ /** * File: binary_tree_dfs.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; // Инициализировать список для хранения последовательности обхода const list: number[] = []; /* Предварительный обход */ function preOrder(root: TreeNode | null): void { if (root === null) { return; } // Порядок обхода: корень -> левое поддерево -> правое поддерево list.push(root.val); preOrder(root.left); preOrder(root.right); } /* Симметричный обход */ function inOrder(root: TreeNode | null): void { if (root === null) { return; } // Порядок обхода: левое поддерево -> корень -> правое поддерево inOrder(root.left); list.push(root.val); inOrder(root.right); } /* Обратный обход */ function postOrder(root: TreeNode | null): void { if (root === null) { return; } // Порядок обхода: левое поддерево -> правое поддерево -> корень postOrder(root.left); postOrder(root.right); list.push(root.val); } /* Driver Code */ /* Инициализация двоичного дерева */ // Здесь используется функция, напрямую строящая двоичное дерево из массива const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\nИнициализация двоичного дерева\n'); printTree(root); /* Предварительный обход */ list.length = 0; preOrder(root); console.log('\nПоследовательность печати узлов при предварительном обходе = ' + list); /* Симметричный обход */ list.length = 0; inOrder(root); console.log('\nПоследовательность печати узлов при симметричном обходе = ' + list); /* Обратный обход */ list.length = 0; postOrder(root); console.log('\nПоследовательность печати узлов при обратном обходе = ' + list); export {}; ================================================ FILE: ru/codes/typescript/modules/ListNode.ts ================================================ /** * File: ListNode.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ /* Узел связного списка */ class ListNode { val: number; next: ListNode | null; constructor(val?: number, next?: ListNode | null) { this.val = val === undefined ? 0 : val; this.next = next === undefined ? null : next; } } /* Десериализовать массив в связный список */ function arrToLinkedList(arr: number[]): ListNode | null { const dum: ListNode = new ListNode(0); let head = dum; for (const val of arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } export { ListNode, arrToLinkedList }; ================================================ FILE: ru/codes/typescript/modules/PrintUtil.ts ================================================ /** * File: PrintUtil.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from './ListNode'; import { TreeNode, arrToTree } from './TreeNode'; /* Вывести связный список */ function printLinkedList(head: ListNode | null): void { const list: string[] = []; while (head !== null) { list.push(head.val.toString()); head = head.next; } console.log(list.join(' -> ')); } class Trunk { prev: Trunk | null; str: string; constructor(prev: Trunk | null, str: string) { this.prev = prev; this.str = str; } } /** * Вывести двоичное дерево * Этот вывод дерева заимствован из TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ function printTree(root: TreeNode | null) { printTreeHelper(root, null, false); } /* Вывести двоичное дерево */ function printTreeHelper( root: TreeNode | null, prev: Trunk | null, isRight: boolean ) { if (root === null) { return; } let prev_str = ' '; const trunk = new Trunk(prev, prev_str); printTreeHelper(root.right, trunk, true); if (prev === null) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); console.log(' ' + root.val); if (prev) { prev.str = prev_str; } trunk.str = ' |'; printTreeHelper(root.left, trunk, false); } function showTrunks(p: Trunk | null) { if (p === null) { return; } showTrunks(p.prev); process.stdout.write(p.str); } /* Вывести кучу */ function printHeap(arr: number[]): void { console.log('Массивное представление кучи:'); console.log(arr); console.log('Древовидное представление кучи:'); const root = arrToTree(arr); printTree(root); } export { printLinkedList, printTree, printHeap }; ================================================ FILE: ru/codes/typescript/modules/TreeNode.ts ================================================ /** * File: TreeNode.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ /* Узел двоичного дерева */ class TreeNode { val: number; // Значение узла height: number; // Высота узла left: TreeNode | null; // Указатель на левый дочерний узел right: TreeNode | null; // Указатель на правый дочерний узел constructor( val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null ) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } /* Десериализовать массив в двоичное дерево */ function arrToTree(arr: (number | null)[], i: number = 0): TreeNode | null { if (i < 0 || i >= arr.length || arr[i] === null) { return null; } let root = new TreeNode(arr[i]); root.left = arrToTree(arr, 2 * i + 1); root.right = arrToTree(arr, 2 * i + 2); return root; } export { TreeNode, arrToTree }; ================================================ FILE: ru/codes/typescript/modules/Vertex.ts ================================================ /** * File: Vertex.ts * Created Time: 2023-02-15 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Класс вершины */ class Vertex { val: number; constructor(val: number) { this.val = val; } /* На вход подается список значений vals, на выходе возвращается список вершин vets */ public static valsToVets(vals: number[]): Vertex[] { const vets: Vertex[] = []; for (let i = 0; i < vals.length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* На вход подается список вершин vets, на выходе возвращается список значений vals */ public static vetsToVals(vets: Vertex[]): number[] { const vals: number[] = []; for (const vet of vets) { vals.push(vet.val); } return vals; } } export { Vertex }; ================================================ FILE: ru/codes/typescript/package.json ================================================ { "private": true, "type": "module", "scripts": { "check": "tsc" }, "devDependencies": { "@types/node": "^24.9.2", "typescript": "^5.9.3" } } ================================================ FILE: ru/codes/typescript/tsconfig.json ================================================ { "compilerOptions": { "baseUrl": ".", "module": "esnext", "moduleResolution": "node", "types": ["@types/node"], "noEmit": true, "target": "esnext", }, "include": ["chapter_*/*.ts"], "exclude": ["node_modules"] } ================================================ FILE: ru/codes/zig/.gitignore ================================================ zig-out zig-cache .zig-cache !/.vscode/ ================================================ FILE: ru/codes/zig/build.zig ================================================ // File: build.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) //! Zig Version: 0.14.1 //! Build Command: zig build //! Run Command: zig build run | zig build run_* //! Test Command: zig build test | zig build test -Dtest-filter=* const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const chapters = [_][]const u8{ "chapter_computational_complexity", "chapter_array_and_linkedlist", "chapter_stack_and_queue", "chapter_hashing", "chapter_tree", "chapter_heap", "chapter_searching", "chapter_sorting", "chapter_dynamic_programming", }; const test_step = b.step("test", "Run unit tests"); const test_filters = b.option([]const []const u8, "test-filter", "Skip tests that do not match any filter") orelse &[0][]const u8{}; buildChapterExeModules(b, target, optimize, &chapters, test_step, test_filters); buildMainExeModule(b, target, optimize); } fn buildChapterExeModules( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, chapter_dirs: []const []const u8, test_step: *std.Build.Step, test_filters: []const []const u8, ) void { for (chapter_dirs) |chapter_dir_name| { const chapter_dir_path = std.fs.path.join(b.allocator, &[_][]const u8{chapter_dir_name}) catch continue; var chapter_dir = std.fs.cwd().openDir(chapter_dir_path, .{ .iterate = true }) catch continue; defer chapter_dir.close(); var it = chapter_dir.iterate(); while (it.next() catch continue) |chapter_dir_entry| { if (chapter_dir_entry.kind != .file or !std.mem.endsWith(u8, chapter_dir_entry.name, ".zig")) continue; const exe_mod = buildExeModuleFromChapterDirEntry(b, target, optimize, chapter_dir_name, chapter_dir_entry) catch continue; addTestStepToExeModule(b, test_step, exe_mod, test_filters); } } } fn buildExeModuleFromChapterDirEntry( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, chapter_dir_name: []const u8, chapter_dir_entry: std.fs.Dir.Entry, ) !*std.Build.Module { const zig_file_path = try std.fs.path.join(b.allocator, &[_][]const u8{ chapter_dir_name, chapter_dir_entry.name }); const zig_file_name = chapter_dir_entry.name[0 .. chapter_dir_entry.name.len - 4]; // abstract zig file name from xxx.zig // Здесь временно добавлены только главы про массивы и связные списки; после доработки будут открыты все const new_algo_names = [_][]const u8{ "array", "linked_list", "list", "my_list", "iteration", "recursion", "space_complexity", "time_complexity", "worst_best_time_complexity", }; var can_run = false; for (new_algo_names) |name| { if (std.mem.eql(u8, zig_file_name, name)) { can_run = true; } } if (!can_run) { return error.CanNotRunUseOldZigCodes; } // std.debug.print("now run zig file name = {s}\n", .{zig_file_name}); const exe_mod = b.createModule(.{ .root_source_file = b.path(zig_file_path), .target = target, .optimize = optimize, }); const exe = b.addExecutable(.{ .name = zig_file_name, .root_module = exe_mod, }); const utils_mod = createUtilsModule(b, target, optimize); exe_mod.addImport("utils", utils_mod); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const step_name = try std.fmt.allocPrint(b.allocator, "run_{s}", .{zig_file_name}); const step_desc = try std.fmt.allocPrint(b.allocator, "Run {s}/{s}.zig", .{ chapter_dir_name, zig_file_name }); const run_step = b.step(step_name, step_desc); run_step.dependOn(&run_cmd.step); return exe_mod; } fn buildMainExeModule( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, ) void { const exe_mod = b.createModule(.{ .root_source_file = b.path("main.zig"), .target = target, .optimize = optimize, }); const utils_mod = createUtilsModule(b, target, optimize); exe_mod.addImport("utils", utils_mod); const exe = b.addExecutable(.{ .name = "main", .root_module = exe_mod, }); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run all hello algo zig"); run_step.dependOn(&run_cmd.step); } fn createUtilsModule(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Module { const utils_mod = b.createModule(.{ .root_source_file = b.path("utils/utils.zig"), .target = target, .optimize = optimize, }); return utils_mod; } fn addTestStepToExeModule(b: *std.Build, test_step: *std.Build.Step, exe_mod: *std.Build.Module, test_filters: []const []const u8) void { const exe_unit_tests = b.addTest(.{ .root_module = exe_mod, .filters = test_filters, }); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); test_step.dependOn(&run_exe_unit_tests.step); } ================================================ FILE: ru/codes/zig/chapter_array_and_linkedlist/array.zig ================================================ // File: array.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // Случайный доступ к элементу pub fn randomAccess(nums: []const i32) i32 { // Случайным образом выбрать целое число из интервала [0, nums.len) const random_index = std.crypto.random.intRangeLessThan(usize, 0, nums.len); // Получить и вернуть случайный элемент const randomNum = nums[random_index]; return randomNum; } // Увеличить длину массива pub fn extend(allocator: std.mem.Allocator, nums: []const i32, enlarge: usize) ![]i32 { // Инициализировать массив увеличенной длины const res = try allocator.alloc(i32, nums.len + enlarge); @memset(res, 0); // Скопировать все элементы исходного массива в новый массив std.mem.copyForwards(i32, res, nums); // Вернуть новый массив после расширения return res; } // Вставить элемент num по индексу index в массив pub fn insert(nums: []i32, num: i32, index: usize) void { // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад var i = nums.len - 1; while (i > index) : (i -= 1) { nums[i] = nums[i - 1]; } // Присвоить num элементу по индексу index nums[index] = num; } // Удалить элемент по индексу index pub fn remove(nums: []i32, index: usize) void { // Сдвинуть все элементы после индекса index на одну позицию вперед var i = index; while (i < nums.len - 1) : (i += 1) { nums[i] = nums[i + 1]; } } // Обход массива pub fn traverse(nums: []const i32) void { var count: i32 = 0; // Обход массива по индексам var i: usize = 0; while (i < nums.len) : (i += 1) { count += nums[i]; } // Непосредственно обходить элементы массива count = 0; for (nums) |num| { count += num; } // Одновременно обходить индексы и элементы данных for (nums, 0..) |num, index| { count += nums[index]; count += num; } } // Найти заданный элемент в массиве pub fn find(nums: []i32, target: i32) i32 { for (nums, 0..) |num, i| { if (num == target) return @intCast(i); } return -1; } // Driver Code pub fn run() !void { // Инициализация массива const arr = [_]i32{0} ** 5; std.debug.print("Массив arr = {}\n", .{utils.fmt.slice(&arr)}); // Срез массива var array = [_]i32{ 1, 3, 2, 5, 4 }; var known_at_runtime_zero: usize = 0; _ = &known_at_runtime_zero; var nums = array[known_at_runtime_zero..array.len]; // Превратить указатель в срез через известную во время выполнения переменную known_at_runtime_zero std.debug.print("Массив nums = {}\n", .{utils.fmt.slice(nums)}); // Случайный доступ const randomNum = randomAccess(nums); std.debug.print("Случайный элемент из nums = {}\n", .{randomNum}); // Инициализация аллокатора памяти var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); // Расширение длины nums = try extend(allocator, nums, 3); std.debug.print("После расширения длины массива до 8 nums = {}\n", .{utils.fmt.slice(nums)}); // Вставка элемента insert(nums, 6, 3); std.debug.print("После вставки числа 6 по индексу 3 nums = {}\n", .{utils.fmt.slice(nums)}); // Удаление элемента remove(nums, 2); std.debug.print("После удаления элемента по индексу 2 nums = {}\n", .{utils.fmt.slice(nums)}); // Обход массива traverse(nums); // Поиск элемента const index = find(nums, 3); std.debug.print("Поиск элемента 3 в nums: индекс = {}\n", .{index}); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "array" { try run(); } ================================================ FILE: ru/codes/zig/chapter_array_and_linkedlist/linked_list.zig ================================================ // File: linked_list.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); const ListNode = utils.ListNode; // Вставить узел P после узла n0 в связном списке pub fn insert(comptime T: type, n0: *ListNode(T), P: *ListNode(T)) void { const n1 = n0.next; P.next = n1; n0.next = P; } // Удалить первый узел после узла n0 в связном списке pub fn remove(comptime T: type, n0: *ListNode(T)) void { // n0 -> P -> n1 => n0 -> n1 const P = n0.next; const n1 = P.?.next; n0.next = n1; } // Доступ к узлу связного списка по индексу index pub fn access(comptime T: type, node: *ListNode(T), index: i32) ?*ListNode(T) { var head: ?*ListNode(T) = node; var i: i32 = 0; while (i < index) : (i += 1) { if (head) |cur| { head = cur.next; } else { return null; } } return head; } // Найти в связном списке первый узел со значением target pub fn find(comptime T: type, node: *ListNode(T), target: T) i32 { var head: ?*ListNode(T) = node; var index: i32 = 0; while (head) |cur| { if (cur.val == target) return index; head = cur.next; index += 1; } return -1; } // Driver Code pub fn run() void { // Инициализация всех узлов var n0 = ListNode(i32){ .val = 1 }; var n1 = ListNode(i32){ .val = 3 }; var n2 = ListNode(i32){ .val = 2 }; var n3 = ListNode(i32){ .val = 5 }; var n4 = ListNode(i32){ .val = 4 }; // Построить ссылки между узлами n0.next = &n1; n1.next = &n2; n2.next = &n3; n3.next = &n4; std.debug.print( "Инициализированный связный список = {}\n",\n .{utils.fmt.linkedList(i32, &n0)},\n); // Вставка узла var tmp = ListNode(i32){ .val = 0 }; insert(i32, &n0, &tmp); std.debug.print( "Связный список после вставки узла = {}\n",\n .{utils.fmt.linkedList(i32, &n0)},\n); // Удаление узла remove(i32, &n0); std.debug.print( "Связный список после удаления узла = {}\n",\n .{utils.fmt.linkedList(i32, &n0)},\n); // Доступ к узлу const node = access(i32, &n0, 3); std.debug.print( "Значение узла по индексу 3 в связном списке = {}\n",\n .{node.?.val},\n); // Поиск узла const index = find(i32, &n0, 2); std.debug.print( "Индекс узла со значением 2 в связном списке = {}\n",\n .{index},\n); std.debug.print("\n", .{}); } pub fn main() void { run(); } test "linked_list" { run(); } ================================================ FILE: ru/codes/zig/chapter_array_and_linkedlist/list.zig ================================================ // File: list.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // Driver Code pub fn run() !void { // Инициализация списка var nums = std.ArrayList(i32).init(std.heap.page_allocator); defer nums.deinit(); // Отложенное освобождение памяти try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); std.debug.print("Список nums = {}\n", .{utils.fmt.slice(nums.items)}); // Доступ к элементу const num = nums.items[1]; std.debug.print("Элемент по индексу 1: num = {}\n", .{num}); // Обновление элемента nums.items[1] = 0; std.debug.print("После обновления элемента по индексу 1 до 0 nums = {}\n", .{utils.fmt.slice(nums.items)}); // Очистить список nums.clearRetainingCapacity(); std.debug.print("После очистки списка nums = {}\n", .{utils.fmt.slice(nums.items)}); // Добавление элемента в конец try nums.append(1); try nums.append(3); try nums.append(2); try nums.append(5); try nums.append(4); std.debug.print("После добавления элементов nums = {}\n", .{utils.fmt.slice(nums.items)}); // Вставка элемента в середину try nums.insert(3, 6); std.debug.print("После вставки числа 6 по индексу 3 nums = {}\n", .{utils.fmt.slice(nums.items)}); // Удаление элемента _ = nums.orderedRemove(3); std.debug.print("После удаления элемента по индексу 3 nums = {}\n", .{utils.fmt.slice(nums.items)}); // Обходить список по индексам var count: i32 = 0; var i: usize = 0; while (i < nums.items.len) : (i += 1) { count += nums.items[i]; } // Непосредственно обходить элементы списка count = 0; for (nums.items) |x| { count += x; } // Объединить два списка var nums1 = std.ArrayList(i32).init(std.heap.page_allocator); defer nums1.deinit(); try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); try nums.insertSlice(nums.items.len, nums1.items); std.debug.print("После конкатенации списка nums1 к nums nums = {}\n", .{utils.fmt.slice(nums.items)}); // Отсортировать список std.mem.sort(i32, nums.items, {}, comptime std.sort.asc(i32)); std.debug.print("После сортировки списка nums = {}\n", .{utils.fmt.slice(nums.items)}); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "list" { try run(); } ================================================ FILE: ru/codes/zig/chapter_array_and_linkedlist/my_list.zig ================================================ // File: my_list.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // Класс списка const MyList = struct { const Self = @This(); items: []i32, // Массив (для хранения элементов списка) capacity: usize, // Вместимость списка allocator: std.mem.Allocator, // Аллокатор памяти extend_ratio: usize = 2, // Коэффициент увеличения списка при каждом расширении // Конструктор (выделение памяти + инициализация списка) pub fn init(allocator: std.mem.Allocator) Self { return Self{ .items = &[_]i32{}, .capacity = 0, .allocator = allocator, }; } // Деструктор (освобождение памяти) pub fn deinit(self: Self) void { self.allocator.free(self.allocatedSlice()); } // Добавление элемента в конец pub fn add(self: *Self, item: i32) !void { // При превышении вместимости по числу элементов запускается расширение const newlen = self.items.len + 1; try self.ensureTotalCapacity(newlen); // Обновление элемента self.items.len += 1; const new_item_ptr = &self.items[self.items.len - 1]; new_item_ptr.* = item; } // Получить длину списка (текущее число элементов) pub fn getSize(self: *Self) usize { return self.items.len; } // Получить вместимость списка pub fn getCapacity(self: *Self) usize { return self.capacity; } // Доступ к элементу pub fn get(self: *Self, index: usize) i32 { // Если индекс выходит за границы, выбрасывается исключение; далее аналогично if (index < 0 or index >= self.items.len) { @panic("индекс выходит за границы"); } return self.items[index]; } // Обновление элемента pub fn set(self: *Self, index: usize, num: i32) void { // Если индекс выходит за границы, выбрасывается исключение; далее аналогично if (index < 0 or index >= self.items.len) { @panic("индекс выходит за границы"); } self.items[index] = num; } // Вставка элемента в середину pub fn insert(self: *Self, index: usize, item: i32) !void { if (index < 0 or index >= self.items.len) { @panic("индекс выходит за границы"); } // При превышении вместимости по числу элементов запускается расширение const newlen = self.items.len + 1; try self.ensureTotalCapacity(newlen); // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад self.items.len += 1; var i = self.items.len - 1; while (i >= index) : (i -= 1) { self.items[i] = self.items[i - 1]; } self.items[index] = item; } // Удаление элемента pub fn remove(self: *Self, index: usize) i32 { if (index < 0 or index >= self.getSize()) { @panic("индекс выходит за границы"); } // Сдвинуть все элементы после индекса index на одну позицию вперед const item = self.items[index]; var i = index; while (i < self.items.len - 1) : (i += 1) { self.items[i] = self.items[i + 1]; } self.items.len -= 1; // Вернуть удаленный элемент return item; } // Преобразовать список в массив pub fn toArraySlice(self: *Self) ![]i32 { return self.toOwnedSlice(false); } // Вернуть новый срез и указать, нужно ли сбросить или очистить контейнер списка pub fn toOwnedSlice(self: *Self, clear: bool) ![]i32 { const allocator = self.allocator; const old_memory = self.allocatedSlice(); if (allocator.remap(old_memory, self.items.len)) |new_items| { if (clear) { self.* = init(allocator); } return new_items; } const new_memory = try allocator.alloc(i32, self.items.len); @memcpy(new_memory, self.items); if (clear) { self.clearAndFree(); } return new_memory; } // Расширение списка fn ensureTotalCapacity(self: *Self, new_capacity: usize) !void { if (self.capacity >= new_capacity) return; const capcacity = if (self.capacity == 0) 10 else self.capacity; const better_capacity = capcacity * self.extend_ratio; const old_memory = self.allocatedSlice(); if (self.allocator.remap(old_memory, better_capacity)) |new_memory| { self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } else { const new_memory = try self.allocator.alloc(i32, better_capacity); @memcpy(new_memory[0..self.items.len], self.items); self.allocator.free(old_memory); self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } } fn clearAndFree(self: *Self, allocator: std.mem.Allocator) void { allocator.free(self.allocatedSlice()); self.items.len = 0; self.capacity = 0; } fn allocatedSlice(self: Self) []i32 { return self.items.ptr[0..self.capacity]; } }; // Driver Code pub fn run() !void { var gpa = std.heap.DebugAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Инициализация списка var nums = MyList.init(allocator); // Отложенное освобождение памяти defer nums.deinit(); // Добавление элемента в конец try nums.add(1); try nums.add(3); try nums.add(2); try nums.add(5); try nums.add(4); std.debug.print("Список nums = {}, вместимость = {}, длина = {}\n", .{\n utils.fmt.slice(nums.items),\n nums.getCapacity(),\n nums.getSize(),\n}); // Вставка элемента в середину try nums.insert(3, 6); std.debug.print( "После вставки числа 6 по индексу 3 получаем nums = {}\n",\n .{utils.fmt.slice(nums.items)},\n); // Удаление элемента _ = nums.remove(3); std.debug.print( "После удаления элемента по индексу 3 получаем nums = {}\n",\n .{utils.fmt.slice(nums.items)},\n); // Доступ к элементу const num = nums.get(1); std.debug.print("Элемент по индексу 1: num = {}\n", .{num}); // Обновление элемента nums.set(1, 0); std.debug.print( "После обновления элемента по индексу 1 значением 0 получаем nums = {}\n",\n .{utils.fmt.slice(nums.items)},\n); // Проверка механизма расширения var i: i32 = 0; while (i < 10) : (i += 1) { // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения try nums.add(i); } std.debug.print( "Список nums после увеличения вместимости = {}, вместимость = {}, длина = {}\n",\n .{\n utils.fmt.slice(nums.items),\n nums.getCapacity(),\n nums.getSize(),\n },\n); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "my_list" { try run(); } ================================================ FILE: ru/codes/zig/chapter_computational_complexity/iteration.zig ================================================ // File: iteration.zig // Created Time: 2023-09-27 // Author: QiLOL (pikaqqpika@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const Allocator = std.mem.Allocator; // Цикл for fn forLoop(n: usize) i32 { var res: i32 = 0; // Циклическое суммирование 1, 2, ..., n-1, n for (1..n + 1) |i| { res += @intCast(i); } return res; } // Цикл while fn whileLoop(n: i32) i32 { var res: i32 = 0; var i: i32 = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) : (i += 1) { res += @intCast(i); } return res; } // Цикл while (двойное обновление) fn whileLoopII(n: i32) i32 { var res: i32 = 0; var i: i32 = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) : ({ // Обновить условную переменную i += 1; i *= 2; }) { res += @intCast(i); } return res; } // Двойной цикл for fn nestedForLoop(allocator: Allocator, n: usize) ![]const u8 { var res = std.ArrayList(u8).init(allocator); defer res.deinit(); var buffer: [20]u8 = undefined; // Цикл по i = 1, 2, ..., n-1, n for (1..n + 1) |i| { // Цикл по j = 1, 2, ..., n-1, n for (1..n + 1) |j| { const str = try std.fmt.bufPrint(&buffer, "({d}, {d}), ", .{ i, j }); try res.appendSlice(str); } } return res.toOwnedSlice(); } // Driver Code pub fn run() !void { var gpa = std.heap.DebugAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const n: i32 = 5; var res: i32 = 0; res = forLoop(n); std.debug.print("Результат суммирования в цикле for res = {}\n", .{res}); res = whileLoop(n); std.debug.print("Результат суммирования в цикле while res = {}\n", .{res}); res = whileLoopII(n); std.debug.print("Результат суммирования в цикле while (двойное обновление) res = {}\n", .{res}); const resStr = try nestedForLoop(allocator, n); std.debug.print("Результат обхода в двойном цикле for {s}\n", .{resStr}); allocator.free(resStr); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "interation" { try run(); } ================================================ FILE: ru/codes/zig/chapter_computational_complexity/recursion.zig ================================================ // File: recursion.zig // Created Time: 2023-09-27 // Author: QiLOL (pikaqqpika@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // Рекурсивная функция fn recur(n: i32) i32 { // Условие завершения if (n == 1) { return 1; } // Рекурсия: рекурсивный вызов const res = recur(n - 1); // Возврат: вернуть результат return n + res; } // Имитация рекурсии итерацией fn forLoopRecur(comptime n: i32) i32 { // Использовать явный стек для имитации системного стека вызовов var stack: [n]i32 = undefined; var res: i32 = 0; // Рекурсия: рекурсивный вызов var i: usize = n; while (i > 0) { stack[i - 1] = @intCast(i); i -= 1; } // Возврат: вернуть результат var index: usize = n; while (index > 0) { index -= 1; res += stack[index]; } // res = 1+2+3+...+n return res; } // Хвосторекурсивная функция fn tailRecur(n: i32, res: i32) i32 { // Условие завершения if (n == 0) { return res; } // Хвостовой рекурсивный вызов return tailRecur(n - 1, res + n); } // Числа Фибоначчи fn fib(n: i32) i32 { // Условие завершения: f(1) = 0, f(2) = 1 if (n == 1 or n == 2) { return n - 1; } // Рекурсивный вызов f(n) = f(n-1) + f(n-2) const res: i32 = fib(n - 1) + fib(n - 2); // Вернуть результат f(n) return res; } // Driver Code pub fn run() void { const n: i32 = 5; var res: i32 = 0; res = recur(n); std.debug.print("Результат суммирования в рекурсивной функции res = {}\n", .{recur(n)}); res = forLoopRecur(n); std.debug.print("Результат суммирования при имитации рекурсии итерацией res = {}\n", .{forLoopRecur(n)}); res = tailRecur(n, 0); std.debug.print("Результат суммирования в хвостовой рекурсии res = {}\n", .{tailRecur(n, 0)}); res = fib(n); std.debug.print("Член последовательности Фибоначчи с номером {} = {}\n", .{ n, fib(n) }); std.debug.print("\n", .{}); } pub fn main() void { run(); } test "recursion" { run(); } ================================================ FILE: ru/codes/zig/chapter_computational_complexity/space_complexity.zig ================================================ // File: space_complexity.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); const ListNode = utils.ListNode; const TreeNode = utils.TreeNode; // Функция fn function() i32 { // Выполнить некоторые операции return 0; } // Постоянная сложность fn constant(n: i32) void { // Константы, переменные и объекты занимают O(1) памяти const a: i32 = 0; const b: i32 = 0; const nums = [_]i32{0} ** 10000; const node = ListNode(i32){ .val = 0 }; var i: i32 = 0; // Переменные в цикле занимают O(1) памяти while (i < n) : (i += 1) { const c: i32 = 0; _ = c; } // Функции в цикле занимают O(1) памяти i = 0; while (i < n) : (i += 1) { _ = function(); } _ = a; _ = b; _ = nums; _ = node; } // Линейная сложность fn linear(comptime n: i32) !void { // Массив длины n занимает O(n) памяти const nums = [_]i32{0} ** n; // Список длины n занимает O(n) памяти var nodes = std.ArrayList(i32).init(std.heap.page_allocator); defer nodes.deinit(); var i: i32 = 0; while (i < n) : (i += 1) { try nodes.append(i); } // Хеш-таблица длины n занимает O(n) памяти var map = std.AutoArrayHashMap(i32, []const u8).init(std.heap.page_allocator); defer map.deinit(); var j: i32 = 0; while (j < n) : (j += 1) { const string = try std.fmt.allocPrint(std.heap.page_allocator, "{d}", .{j}); defer std.heap.page_allocator.free(string); try map.put(i, string); } _ = nums; } // Линейная сложность (рекурсивная реализация) fn linearRecur(comptime n: i32) void { std.debug.print("Рекурсия n = {}\n", .{n}); if (n == 1) return; linearRecur(n - 1); } // Квадратичная сложность fn quadratic(n: i32) !void { // Двумерный список занимает O(n^2) памяти var nodes = std.ArrayList(std.ArrayList(i32)).init(std.heap.page_allocator); defer nodes.deinit(); var i: i32 = 0; while (i < n) : (i += 1) { var tmp = std.ArrayList(i32).init(std.heap.page_allocator); defer tmp.deinit(); var j: i32 = 0; while (j < n) : (j += 1) { try tmp.append(0); } try nodes.append(tmp); } } // Квадратичная сложность (рекурсивная реализация) fn quadraticRecur(comptime n: i32) i32 { if (n <= 0) return 0; const nums = [_]i32{0} ** n; std.debug.print("В рекурсии n = {} длина nums = {}\n", .{ n, nums.len }); return quadraticRecur(n - 1); } // Экспоненциальная сложность (построение полного двоичного дерева) fn buildTree(allocator: std.mem.Allocator, n: i32) !?*TreeNode(i32) { if (n == 0) return null; const root = try allocator.create(TreeNode(i32)); root.init(0); root.left = try buildTree(allocator, n - 1); root.right = try buildTree(allocator, n - 1); return root; } // Освободить память дерева fn freeTree(allocator: std.mem.Allocator, root: ?*const TreeNode(i32)) void { if (root == null) return; freeTree(allocator, root.?.left); freeTree(allocator, root.?.right); allocator.destroy(root.?); } // Driver Code pub fn run() !void { var gpa = std.heap.DebugAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const n: i32 = 5; // Постоянная сложность constant(n); // Линейная сложность try linear(n); linearRecur(n); // Квадратичная сложность try quadratic(n); _ = quadraticRecur(n); // Экспоненциальная сложность const root = try buildTree(allocator, n); defer freeTree(allocator, root); std.debug.print("{}\n", .{utils.fmt.tree(i32, root)}); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "space_complexity" { try run(); } ================================================ FILE: ru/codes/zig/chapter_computational_complexity/time_complexity.zig ================================================ // File: time_complexity.zig // Created Time: 2022-12-28 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // Постоянная сложность fn constant(n: i32) i32 { _ = n; var count: i32 = 0; const size: i32 = 100_000; var i: i32 = 0; while (i < size) : (i += 1) { count += 1; } return count; } // Линейная сложность fn linear(n: i32) i32 { var count: i32 = 0; var i: i32 = 0; while (i < n) : (i += 1) { count += 1; } return count; } // Линейная сложность (обход массива) fn arrayTraversal(nums: []i32) i32 { var count: i32 = 0; // Число итераций пропорционально длине массива for (nums) |_| { count += 1; } return count; } // Квадратичная сложность fn quadratic(n: i32) i32 { var count: i32 = 0; var i: i32 = 0; // Число итераций квадратично зависит от размера данных n while (i < n) : (i += 1) { var j: i32 = 0; while (j < n) : (j += 1) { count += 1; } } return count; } // Квадратичная сложность (пузырьковая сортировка) fn bubbleSort(nums: []i32) i32 { var count: i32 = 0; // Счетчик // Внешний цикл: неотсортированный диапазон [0, i] var i: i32 = @as(i32, @intCast(nums.len)) - 1; while (i > 0) : (i -= 1) { var j: usize = 0; // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] const tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // Обмен элементов включает 3 элементарные операции } } } return count; } // Экспоненциальная сложность (итеративная реализация) fn exponential(n: i32) i32 { var count: i32 = 0; var bas: i32 = 1; var i: i32 = 0; // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) while (i < n) : (i += 1) { var j: i32 = 0; while (j < bas) : (j += 1) { count += 1; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } // Экспоненциальная сложность (рекурсивная реализация) fn expRecur(n: i32) i32 { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } // Логарифмическая сложность (итеративная реализация) fn logarithmic(n: i32) i32 { var count: i32 = 0; var n_var: i32 = n; while (n_var > 1) : (n_var = @divTrunc(n_var, 2)) { count += 1; } return count; } // Логарифмическая сложность (рекурсивная реализация) fn logRecur(n: i32) i32 { if (n <= 1) return 0; return logRecur(@divTrunc(n, 2)) + 1; } // Линейно-логарифмическая сложность fn linearLogRecur(n: i32) i32 { if (n <= 1) return 1; var count: i32 = linearLogRecur(@divTrunc(n, 2)) + linearLogRecur(@divTrunc(n, 2)); var i: i32 = 0; while (i < n) : (i += 1) { count += 1; } return count; } // Факториальная сложность (рекурсивная реализация) fn factorialRecur(n: i32) i32 { if (n == 0) return 1; var count: i32 = 0; var i: i32 = 0; // Из одного получается n while (i < n) : (i += 1) { count += factorialRecur(n - 1); } return count; } // Driver Code pub fn run() void { // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях const n: i32 = 8; std.debug.print("Размер входных данных n = {}\n", .{n}); var count = constant(n); std.debug.print("Число операций постоянной сложности = {}\n", .{count}); count = linear(n); std.debug.print("Число операций линейной сложности = {}\n", .{count}); var nums = [_]i32{0} ** n; count = arrayTraversal(&nums); std.debug.print("Число операций линейной сложности (обход массива) = {}\n", .{count}); count = quadratic(n); std.debug.print("Число операций квадратичной сложности = {}\n", .{count}); for (&nums, 0..) |*num, i| { num.* = n - @as(i32, @intCast(i)); // [n,n-1,...,2,1] } count = bubbleSort(&nums); std.debug.print("Число операций квадратичной сложности (пузырьковая сортировка) = {}\n", .{count}); count = exponential(n); std.debug.print("Число операций экспоненциальной сложности (итеративная реализация) = {}\n", .{count}); count = expRecur(n); std.debug.print("Число операций экспоненциальной сложности (рекурсивная реализация) = {}\n", .{count}); count = logarithmic(n); std.debug.print("Число операций логарифмической сложности (итеративная реализация) = {}\n", .{count}); count = logRecur(n); std.debug.print("Число операций логарифмической сложности (рекурсивная реализация) = {}\n", .{count}); count = linearLogRecur(n); std.debug.print("Число операций линейно-логарифмической сложности (рекурсивная реализация) = {}\n", .{count}); count = factorialRecur(n); std.debug.print("Число операций факториальной сложности (рекурсивная реализация) = {}\n", .{count}); std.debug.print("\n", .{}); } pub fn main() !void { run(); } test "time_complexity" { run(); } ================================================ FILE: ru/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig ================================================ // File: worst_best_time_complexity.zig // Created Time: 2022-12-28 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // Создать массив с элементами { 1, 2, ..., n } в случайном порядке pub fn randomNumbers(comptime n: usize) [n]i32 { var nums: [n]i32 = undefined; // Создать массив nums = { 1, 2, 3, ..., n } for (&nums, 0..) |*num, i| { num.* = @as(i32, @intCast(i)) + 1; } // Случайно перемешать элементы массива const rand = std.crypto.random; rand.shuffle(i32, &nums); return nums; } // Найти индекс числа 1 в массиве nums pub fn findOne(nums: []i32) i32 { for (nums, 0..) |num, i| { // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) if (num == 1) return @intCast(i); } return -1; } // Driver Code pub fn run() void { var i: i32 = 0; while (i < 10) : (i += 1) { const n: usize = 100; var nums = randomNumbers(n); const index = findOne(&nums); std.debug.print("После перемешивания массива [ 1, 2, ..., n ] = ", .{}); std.debug.print("{}\n", .{utils.fmt.slice(nums)}); std.debug.print("Индекс числа 1 = {}\n", .{index}); } std.debug.print("\n", .{}); } pub fn main() !void { run(); } test "worst_best_time_complexity" { run(); } ================================================ FILE: ru/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig ================================================ // File: climbing_stairs_backtrack.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // Бэктрекинг fn backtrack(choices: []i32, state: i32, n: i32, res: std.ArrayList(i32)) void { // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 if (state == n) { res.items[0] = res.items[0] + 1; } // Перебор всех вариантов выбора for (choices) |choice| { // Отсечение: нельзя выходить за n-ю ступень if (state + choice > n) { continue; } // Попытка: сделать выбор и обновить состояние backtrack(choices, state + choice, n, res); // Откат } } // Подъем по лестнице: бэктрекинг fn climbingStairsBacktrack(n: usize) !i32 { var choices = [_]i32{ 1, 2 }; // Можно подняться на 1 или 2 ступени var state: i32 = 0; // Начать подъем с 0-й ступени var res = std.ArrayList(i32).init(std.heap.page_allocator); defer res.deinit(); try res.append(0); // Использовать res[0] для хранения числа решений backtrack(&choices, state, @intCast(n), res); return res.items[0]; } // Driver Code pub fn main() !void { var n: usize = 9; var res = try climbingStairsBacktrack(n); std.debug.print("Количество способов подняться по лестнице из {} ступеней: {} вариантов\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig ================================================ // File: climbing_stairs_constraint_dp.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // Подъем по лестнице с ограничениями: динамическое программирование fn climbingStairsConstraintDP(comptime n: usize) i32 { if (n == 1 or n == 2) { return 1; } // Инициализация таблицы dp для хранения решений подзадач var dp = [_][3]i32{ [_]i32{ -1, -1, -1 } } ** (n + 1); // Начальное состояние: заранее задать решения наименьших подзадач dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // Переход состояний: постепенное решение больших подзадач через меньшие for (3..n + 1) |i| { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsConstraintDP(n); std.debug.print("Количество способов подняться по лестнице из {} ступеней: {} вариантов\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig ================================================ // File: climbing_stairs_dfs.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // Поиск fn dfs(i: usize) i32 { // dp[1] и dp[2] уже известны, вернуть их if (i == 1 or i == 2) { return @intCast(i); } // dp[i] = dp[i-1] + dp[i-2] var count = dfs(i - 1) + dfs(i - 2); return count; } // Подъем по лестнице: поиск fn climbingStairsDFS(comptime n: usize) i32 { return dfs(n); } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsDFS(n); std.debug.print("Количество способов подняться по лестнице из {} ступеней: {} вариантов\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig ================================================ // File: climbing_stairs_dfs_mem.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // Поиск с мемоизацией fn dfs(i: usize, mem: []i32) i32 { // dp[1] и dp[2] уже известны, вернуть их if (i == 1 or i == 2) { return @intCast(i); } // Если запись dp[i] существует, сразу вернуть ее if (mem[i] != -1) { return mem[i]; } // dp[i] = dp[i-1] + dp[i-2] var count = dfs(i - 1, mem) + dfs(i - 2, mem); // Сохранить dp[i] mem[i] = count; return count; } // Подъем по лестнице: поиск с мемоизацией fn climbingStairsDFSMem(comptime n: usize) i32 { // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи var mem = [_]i32{ -1 } ** (n + 1); return dfs(n, &mem); } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsDFSMem(n); std.debug.print("Количество способов подняться по лестнице из {} ступеней: {} вариантов\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig ================================================ // File: climbing_stairs_dp.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // Подъем по лестнице: динамическое программирование fn climbingStairsDP(comptime n: usize) i32 { // dp[1] и dp[2] уже известны, вернуть их if (n == 1 or n == 2) { return @intCast(n); } // Инициализация таблицы dp для хранения решений подзадач var dp = [_]i32{-1} ** (n + 1); // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = 1; dp[2] = 2; // Переход состояний: постепенное решение больших подзадач через меньшие for (3..n + 1) |i| { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } // Подъем по лестнице: динамическое программирование с оптимизацией памяти fn climbingStairsDPComp(comptime n: usize) i32 { if (n == 1 or n == 2) { return @intCast(n); } var a: i32 = 1; var b: i32 = 2; for (3..n + 1) |_| { var tmp = b; b = a + b; a = tmp; } return b; } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsDP(n); std.debug.print("Количество способов подняться по лестнице из {} ступеней: {} вариантов\n", .{ n, res }); res = climbingStairsDPComp(n); std.debug.print("Количество способов подняться по лестнице из {} ступеней: {} вариантов\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_dynamic_programming/coin_change.zig ================================================ // File: coin_change.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // Размен монет: динамическое программирование fn coinChangeDP(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; comptime var max = amt + 1; // Инициализация таблицы dp var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); // Переход состояний: первая строка и первый столбец for (1..amt + 1) |a| { dp[0][a] = max; } // Переход состояний: остальные строки и столбцы for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[i][a] = @min(dp[i - 1][a], dp[i][a - @as(usize, @intCast(coins[i - 1]))] + 1); } } } if (dp[n][amt] != max) { return @intCast(dp[n][amt]); } else { return -1; } } // Размен монет: динамическое программирование с оптимизацией памяти fn coinChangeDPComp(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; comptime var max = amt + 1; // Инициализация таблицы dp var dp = [_]i32{0} ** (amt + 1); @memset(&dp, max); dp[0] = 0; // Переход состояний for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[a] = @min(dp[a], dp[a - @as(usize, @intCast(coins[i - 1]))] + 1); } } } if (dp[amt] != max) { return @intCast(dp[amt]); } else { return -1; } } // Driver Code pub fn main() !void { comptime var coins = [_]i32{ 1, 2, 5 }; comptime var amt: usize = 4; // Динамическое программирование var res = coinChangeDP(&coins, amt); std.debug.print("Минимальное число монет для набора целевой суммы = {}\n", .{res}); // Динамическое программирование с оптимизацией памяти res = coinChangeDPComp(&coins, amt); std.debug.print("Минимальное число монет для набора целевой суммы = {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_dynamic_programming/coin_change_ii.zig ================================================ // File: coin_change_ii.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // Размен монет II: динамическое программирование fn coinChangeIIDP(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; // Инициализация таблицы dp var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); // Инициализация первого столбца for (0..n + 1) |i| { dp[i][0] = 1; } // Переход состояний for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // Если целевая сумма превышена, монету i не выбирать dp[i][a] = dp[i - 1][a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[i][a] = dp[i - 1][a] + dp[i][a - @as(usize, @intCast(coins[i - 1]))]; } } } return dp[n][amt]; } // Размен монет II: динамическое программирование с оптимизацией памяти fn coinChangeIIDPComp(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; // Инициализация таблицы dp var dp = [_]i32{0} ** (amt + 1); dp[0] = 1; // Переход состояний for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // Если целевая сумма превышена, монету i не выбирать dp[a] = dp[a]; } else { // Меньшее из двух решений: не брать или взять монету i dp[a] = dp[a] + dp[a - @as(usize, @intCast(coins[i - 1]))]; } } } return dp[amt]; } // Driver Code pub fn main() !void { comptime var coins = [_]i32{ 1, 2, 5 }; comptime var amt: usize = 5; // Динамическое программирование var res = coinChangeIIDP(&coins, amt); std.debug.print("Количество комбинаций монет для набора целевой суммы = {}\n", .{res}); // Динамическое программирование с оптимизацией памяти res = coinChangeIIDPComp(&coins, amt); std.debug.print("Количество комбинаций монет для набора целевой суммы = {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_dynamic_programming/edit_distance.zig ================================================ // File: edit_distance.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // Редакционное расстояние: полный перебор fn editDistanceDFS(comptime s: []const u8, comptime t: []const u8, i: usize, j: usize) i32 { // Если s и t пусты, вернуть 0 if (i == 0 and j == 0) { return 0; } // Если s пусто, вернуть длину t if (i == 0) { return @intCast(j); } // Если t пусто, вернуть длину s if (j == 0) { return @intCast(i); } // Если два символа равны, сразу пропустить их if (s[i - 1] == t[j - 1]) { return editDistanceDFS(s, t, i - 1, j - 1); } // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 var insert = editDistanceDFS(s, t, i, j - 1); var delete = editDistanceDFS(s, t, i - 1, j); var replace = editDistanceDFS(s, t, i - 1, j - 1); // Вернуть минимальное число шагов редактирования return @min(@min(insert, delete), replace) + 1; } // Редакционное расстояние: поиск с мемоизацией fn editDistanceDFSMem(comptime s: []const u8, comptime t: []const u8, mem: anytype, i: usize, j: usize) i32 { // Если s и t пусты, вернуть 0 if (i == 0 and j == 0) { return 0; } // Если s пусто, вернуть длину t if (i == 0) { return @intCast(j); } // Если t пусто, вернуть длину s if (j == 0) { return @intCast(i); } // Если запись уже есть, сразу вернуть ее if (mem[i][j] != -1) { return mem[i][j]; } // Если два символа равны, сразу пропустить их if (s[i - 1] == t[j - 1]) { return editDistanceDFSMem(s, t, mem, i - 1, j - 1); } // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 var insert = editDistanceDFSMem(s, t, mem, i, j - 1); var delete = editDistanceDFSMem(s, t, mem, i - 1, j); var replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // Сохранить и вернуть минимальное число шагов редактирования mem[i][j] = @min(@min(insert, delete), replace) + 1; return mem[i][j]; } // Редакционное расстояние: динамическое программирование fn editDistanceDP(comptime s: []const u8, comptime t: []const u8) i32 { comptime var n = s.len; comptime var m = t.len; var dp = [_][m + 1]i32{[_]i32{0} ** (m + 1)} ** (n + 1); // Переход состояний: первая строка и первый столбец for (1..n + 1) |i| { dp[i][0] = @intCast(i); } for (1..m + 1) |j| { dp[0][j] = @intCast(j); } // Переход состояний: остальные строки и столбцы for (1..n + 1) |i| { for (1..m + 1) |j| { if (s[i - 1] == t[j - 1]) { // Если два символа равны, сразу пропустить их dp[i][j] = dp[i - 1][j - 1]; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[i][j] = @min(@min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } // Редакционное расстояние: динамическое программирование с оптимизацией памяти fn editDistanceDPComp(comptime s: []const u8, comptime t: []const u8) i32 { comptime var n = s.len; comptime var m = t.len; var dp = [_]i32{0} ** (m + 1); // Переход состояний: первая строка for (1..m + 1) |j| { dp[j] = @intCast(j); } // Переход состояний: остальные строки for (1..n + 1) |i| { // Переход состояний: первый столбец var leftup = dp[0]; // Временно сохранить dp[i-1, j-1] dp[0] = @intCast(i); // Переход состояний: остальные столбцы for (1..m + 1) |j| { var temp = dp[j]; if (s[i - 1] == t[j - 1]) { // Если два символа равны, сразу пропустить их dp[j] = leftup; } else { // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 dp[j] = @min(@min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации } } return dp[m]; } // Driver Code pub fn main() !void { const s = "bag"; const t = "pack"; comptime var n = s.len; comptime var m = t.len; // Полный перебор var res = editDistanceDFS(s, t, n, m); std.debug.print("Чтобы преобразовать {s} в {s}, нужно минимум {} шагов\n", .{ s, t, res }); // Поиск с запоминанием var mem = [_][m + 1]i32{[_]i32{-1} ** (m + 1)} ** (n + 1); res = editDistanceDFSMem(s, t, @constCast(&mem), n, m); std.debug.print("Чтобы преобразовать {s} в {s}, нужно минимум {} шагов\n", .{ s, t, res }); // Динамическое программирование res = editDistanceDP(s, t); std.debug.print("Чтобы преобразовать {s} в {s}, нужно минимум {} шагов\n", .{ s, t, res }); // Динамическое программирование с оптимизацией памяти res = editDistanceDPComp(s, t); std.debug.print("Чтобы преобразовать {s} в {s}, нужно минимум {} шагов\n", .{ s, t, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_dynamic_programming/knapsack.zig ================================================ // File: knapsack.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // Рюкзак 0-1: полный перебор fn knapsackDFS(wgt: []i32, val: []i32, i: usize, c: usize) i32 { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i == 0 or c == 0) { return 0; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут var no = knapsackDFS(wgt, val, i - 1, c); var yes = knapsackDFS(wgt, val, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; // Вернуть вариант с большей стоимостью из двух возможных return @max(no, yes); } // Рюкзак 0-1: поиск с мемоизацией fn knapsackDFSMem(wgt: []i32, val: []i32, mem: anytype, i: usize, c: usize) i32 { // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 if (i == 0 or c == 0) { return 0; } // Если запись уже есть, вернуть сразу if (mem[i][c] != -1) { return mem[i][c]; } // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут var no = knapsackDFSMem(wgt, val, mem, i - 1, c); var yes = knapsackDFSMem(wgt, val, mem, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; // Сохранить и вернуть вариант с большей стоимостью из двух решений mem[i][c] = @max(no, yes); return mem[i][c]; } // Рюкзак 0-1: динамическое программирование fn knapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // Инициализация таблицы dp var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); // Переход состояний for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = @max(dp[i - 1][c], dp[i - 1][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[n][cap]; } // Рюкзак 0-1: динамическое программирование с оптимизацией памяти fn knapsackDPComp(wgt: []i32, val: []i32, comptime cap: usize) i32 { var n = wgt.len; // Инициализация таблицы dp var dp = [_]i32{0} ** (cap + 1); // Переход состояний for (1..n + 1) |i| { // Обход в обратном порядке var c = cap; while (c > 0) : (c -= 1) { if (wgt[i - 1] < c) { // Большее из двух решений: не брать или взять предмет i dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[cap]; } // Driver Code pub fn main() !void { comptime var wgt = [_]i32{ 10, 20, 30, 40, 50 }; comptime var val = [_]i32{ 50, 120, 150, 210, 240 }; comptime var cap = 50; comptime var n = wgt.len; // Полный перебор var res = knapsackDFS(&wgt, &val, n, cap); std.debug.print("Максимальная стоимость предметов без превышения вместимости рюкзака = {}\n", .{res}); // Поиск с запоминанием var mem = [_][cap + 1]i32{[_]i32{-1} ** (cap + 1)} ** (n + 1); res = knapsackDFSMem(&wgt, &val, @constCast(&mem), n, cap); std.debug.print("Максимальная стоимость предметов без превышения вместимости рюкзака = {}\n", .{res}); // Динамическое программирование res = knapsackDP(&wgt, &val, cap); std.debug.print("Максимальная стоимость предметов без превышения вместимости рюкзака = {}\n", .{res}); // Динамическое программирование с оптимизацией памяти res = knapsackDPComp(&wgt, &val, cap); std.debug.print("Максимальная стоимость предметов без превышения вместимости рюкзака = {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig ================================================ // File: min_cost_climbing_stairs_dp.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // Минимальная стоимость подъема по лестнице: динамическое программирование fn minCostClimbingStairsDP(comptime cost: []i32) i32 { comptime var n = cost.len - 1; if (n == 1 or n == 2) { return cost[n]; } // Инициализация таблицы dp для хранения решений подзадач var dp = [_]i32{-1} ** (n + 1); // Начальное состояние: заранее задать решения наименьших подзадач dp[1] = cost[1]; dp[2] = cost[2]; // Переход состояний: постепенное решение больших подзадач через меньшие for (3..n + 1) |i| { dp[i] = @min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } // Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти fn minCostClimbingStairsDPComp(cost: []i32) i32 { var n = cost.len - 1; if (n == 1 or n == 2) { return cost[n]; } var a = cost[1]; var b = cost[2]; // Переход состояний: постепенное решение больших подзадач через меньшие for (3..n + 1) |i| { var tmp = b; b = @min(a, tmp) + cost[i]; a = tmp; } return b; } // Driver Code pub fn main() !void { comptime var cost = [_]i32{ 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; std.debug.print("Стоимость подъема по ступеням = {any}\n", .{cost}); var res = minCostClimbingStairsDP(&cost); std.debug.print("Стоимость подъема по ступеням = {}\n", .{res}); res = minCostClimbingStairsDPComp(&cost); std.debug.print("Стоимость подъема по ступеням = {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_dynamic_programming/min_path_sum.zig ================================================ // File: min_path_sum.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // Минимальная сумма пути: полный перебор fn minPathSumDFS(grid: anytype, i: i32, j: i32) i32 { // Если это верхняя левая ячейка, завершить поиск if (i == 0 and j == 0) { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 or j < 0) { return std.math.maxInt(i32); } // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) var up = minPathSumDFS(grid, i - 1, j); var left = minPathSumDFS(grid, i, j - 1); // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) return @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; } // Минимальная сумма пути: поиск с мемоизацией fn minPathSumDFSMem(grid: anytype, mem: anytype, i: i32, j: i32) i32 { // Если это верхняя левая ячейка, завершить поиск if (i == 0 and j == 0) { return grid[0][0]; } // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ if (i < 0 or j < 0) { return std.math.maxInt(i32); } // Если запись уже есть, вернуть сразу if (mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] != -1) { return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; } // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) var up = minPathSumDFSMem(grid, mem, i - 1, j); var left = minPathSumDFSMem(grid, mem, i, j - 1); // Вернуть минимальную стоимость пути из левого верхнего угла в (i, j) // Записать и вернуть минимальную стоимость пути из левого верхнего угла в (i, j) mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] = @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; } // Минимальная сумма пути: динамическое программирование fn minPathSumDP(comptime grid: anytype) i32 { comptime var n = grid.len; comptime var m = grid[0].len; // Инициализация таблицы dp var dp = [_][m]i32{[_]i32{0} ** m} ** n; dp[0][0] = grid[0][0]; // Переход состояний: первая строка for (1..m) |j| { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // Переход состояний: первый столбец for (1..n) |i| { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // Переход состояний: остальные строки и столбцы for (1..n) |i| { for (1..m) |j| { dp[i][j] = @min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } // Минимальная сумма пути: динамическое программирование с оптимизацией памяти fn minPathSumDPComp(comptime grid: anytype) i32 { comptime var n = grid.len; comptime var m = grid[0].len; // Инициализация таблицы dp var dp = [_]i32{0} ** m; // Переход состояний: первая строка dp[0] = grid[0][0]; for (1..m) |j| { dp[j] = dp[j - 1] + grid[0][j]; } // Переход состояний: остальные строки for (1..n) |i| { // Переход состояний: первый столбец dp[0] = dp[0] + grid[i][0]; for (1..m) |j| { dp[j] = @min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } // Driver Code pub fn main() !void { comptime var grid = [_][4]i32{ [_]i32{ 1, 3, 1, 5 }, [_]i32{ 2, 2, 4, 2 }, [_]i32{ 5, 3, 2, 1 }, [_]i32{ 4, 3, 5, 2 }, }; comptime var n = grid.len; comptime var m = grid[0].len; // Полный перебор var res = minPathSumDFS(&grid, n - 1, m - 1); std.debug.print("Минимальная сумма пути из левого верхнего угла в правый нижний = {}\n", .{res}); // Поиск с мемоизацией var mem = [_][m]i32{[_]i32{-1} ** m} ** n; res = minPathSumDFSMem(&grid, &mem, n - 1, m - 1); std.debug.print("Минимальная сумма пути из левого верхнего угла в правый нижний = {}\n", .{res}); // Динамическое программирование res = minPathSumDP(&grid); std.debug.print("Минимальная сумма пути из левого верхнего угла в правый нижний = {}\n", .{res}); // Динамическое программирование с оптимизацией памяти res = minPathSumDPComp(&grid); std.debug.print("Минимальная сумма пути из левого верхнего угла в правый нижний = {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig ================================================ // File: unbounded_knapsack.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // Полный рюкзак: динамическое программирование fn unboundedKnapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // Инициализация таблицы dp var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); // Переход состояний for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[i][c] = dp[i - 1][c]; } else { // Большее из двух решений: не брать или взять предмет i dp[i][c] = @max(dp[i - 1][c], dp[i][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[n][cap]; } // Полный рюкзак: динамическое программирование с оптимизацией памяти fn unboundedKnapsackDPComp(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // Инициализация таблицы dp var dp = [_]i32{0} ** (cap + 1); // Переход состояний for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // Если вместимость рюкзака превышена, предмет i не выбирать dp[c] = dp[c]; } else { // Большее из двух решений: не брать или взять предмет i dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[cap]; } // Driver Code pub fn main() !void { comptime var wgt = [_]i32{ 1, 2, 3 }; comptime var val = [_]i32{ 5, 11, 15 }; comptime var cap = 4; // Динамическое программирование var res = unboundedKnapsackDP(&wgt, &val, cap); std.debug.print("Максимальная стоимость предметов без превышения вместимости рюкзака = {}\n", .{res}); // Динамическое программирование с оптимизацией памяти res = unboundedKnapsackDPComp(&wgt, &val, cap); std.debug.print("Максимальная стоимость предметов без превышения вместимости рюкзака = {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_hashing/array_hash_map.zig ================================================ // File: array_hash_map.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Пара ключ-значение const Pair = struct { key: usize = undefined, val: []const u8 = undefined, pub fn init(key: usize, val: []const u8) Pair { return Pair { .key = key, .val = val, }; } }; // Хеш-таблица на основе массива pub fn ArrayHashMap(comptime T: type) type { return struct { bucket: ?std.ArrayList(?T) = null, mem_allocator: std.mem.Allocator = undefined, const Self = @This(); // Конструктор pub fn init(self: *Self, allocator: std.mem.Allocator) !void { self.mem_allocator = allocator; // Инициализировать корзину (массив) длиной 100 self.bucket = std.ArrayList(?T).init(self.mem_allocator); var i: i32 = 0; while (i < 100) : (i += 1) { try self.bucket.?.append(null); } } // Деструктор pub fn deinit(self: *Self) void { if (self.bucket != null) self.bucket.?.deinit(); } // Хеш-функция fn hashFunc(key: usize) usize { var index = key % 100; return index; } // Операция поиска pub fn get(self: *Self, key: usize) []const u8 { var index = hashFunc(key); var pair = self.bucket.?.items[index]; return pair.?.val; } // Операция добавления pub fn put(self: *Self, key: usize, val: []const u8) !void { var pair = Pair.init(key, val); var index = hashFunc(key); self.bucket.?.items[index] = pair; } // Операция удаления pub fn remove(self: *Self, key: usize) !void { var index = hashFunc(key); // Присвоить null, что означает удаление self.bucket.?.items[index] = null; } // Получить все пары ключ-значение pub fn pairSet(self: *Self) !std.ArrayList(T) { var entry_set = std.ArrayList(T).init(self.mem_allocator); for (self.bucket.?.items) |item| { if (item == null) continue; try entry_set.append(item.?); } return entry_set; } // Получить все ключи pub fn keySet(self: *Self) !std.ArrayList(usize) { var key_set = std.ArrayList(usize).init(self.mem_allocator); for (self.bucket.?.items) |item| { if (item == null) continue; try key_set.append(item.?.key); } return key_set; } // Получить все значения pub fn valueSet(self: *Self) !std.ArrayList([]const u8) { var value_set = std.ArrayList([]const u8).init(self.mem_allocator); for (self.bucket.?.items) |item| { if (item == null) continue; try value_set.append(item.?.val); } return value_set; } // Вывести хеш-таблицу pub fn print(self: *Self) !void { var entry_set = try self.pairSet(); defer entry_set.deinit(); for (entry_set.items) |item| { std.debug.print("{} -> {s}\n", .{item.key, item.val}); } } }; } // Driver Code pub fn main() !void { // Инициализация хеш-таблицы var map = ArrayHashMap(Pair){}; try map.init(std.heap.page_allocator); defer map.deinit(); // Операция добавления // Добавить пару (key, value) в хеш-таблицу try map.put(12836, "Сяо Ха"); try map.put(15937, "Сяо Ло"); try map.put(16750, "Сяо Суань"); try map.put(13276, "Сяо Фа"); try map.put(10583, "Сяо Я"); std.debug.print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение\n", .{}); try map.print(); // Операция поиска // Передать ключ key в хеш-таблицу и получить значение value var name = map.get(15937); std.debug.print("\nПо номеру 15937 найдено имя {s}\n", .{name}); // Операция удаления // Удалить пару (key, value) из хеш-таблицы try map.remove(10583); std.debug.print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение\n", .{}); try map.print(); // Обход хеш-таблицы std.debug.print("\nОтдельный обход пар ключ-значение\n", .{}); var entry_set = try map.pairSet(); for (entry_set.items) |kv| { std.debug.print("{} -> {s}\n", .{kv.key, kv.val}); } defer entry_set.deinit(); std.debug.print("\nОтдельный обход ключей\n", .{}); var key_set = try map.keySet(); for (key_set.items) |key| { std.debug.print("{}\n", .{key}); } defer key_set.deinit(); std.debug.print("\nОтдельный обход значений\n", .{}); var value_set = try map.valueSet(); for (value_set.items) |val| { std.debug.print("{s}\n", .{val}); } defer value_set.deinit(); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_hashing/hash_map.zig ================================================ // File: hash_map.zig // Created Time: 2023-01-13 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // Инициализация хеш-таблицы var map = std.AutoHashMap(i32, []const u8).init(std.heap.page_allocator); // Отложенное освобождение памяти defer map.deinit(); // Операция добавления // Добавить пару (key, value) в хеш-таблицу try map.put(12836, "Сяо Ха"); try map.put(15937, "Сяо Ло"); try map.put(16750, "Сяо Суань"); try map.put(13276, "Сяо Фа"); try map.put(10583, "Сяо Я"); std.debug.print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение\n", .{}); inc.PrintUtil.printHashMap(i32, []const u8, map); // Операция поиска // Передать ключ key в хеш-таблицу и получить значение value var name = map.get(15937).?; std.debug.print("\nПо номеру 15937 найдено имя {s}\n", .{name}); // Операция удаления // Удалить пару (key, value) из хеш-таблицы _ = map.remove(10583); std.debug.print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение\n", .{}); inc.PrintUtil.printHashMap(i32, []const u8, map); // Обход хеш-таблицы std.debug.print("\nОтдельный обход пар ключ-значение\n", .{}); inc.PrintUtil.printHashMap(i32, []const u8, map); std.debug.print("\nОтдельный обход ключей\n", .{}); var it = map.iterator(); while (it.next()) |kv| { std.debug.print("{}\n", .{kv.key_ptr.*}); } std.debug.print("\nОтдельный обход значений\n", .{}); it = map.iterator(); while (it.next()) |kv| { std.debug.print("{s}\n", .{kv.value_ptr.*}); } _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_heap/heap.zig ================================================ // File: heap.zig // Created Time: 2023-01-14 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); fn lessThan(context: void, a: i32, b: i32) std.math.Order { _ = context; return std.math.order(a, b); } fn greaterThan(context: void, a: i32, b: i32) std.math.Order { return lessThan(context, a, b).invert(); } fn testPush(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype, val: T) !void { try heap.add(val); // Добавление элемента в кучу std.debug.print("\nПосле добавления элемента {} в кучу\n", .{val}); try inc.PrintUtil.printHeap(T, mem_allocator, heap); } fn testPop(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype) !void { var val = heap.remove(); // Извлечение элемента с вершины кучи std.debug.print("\nПосле извлечения элемента вершины кучи {}\n", .{val}); try inc.PrintUtil.printHeap(T, mem_allocator, heap); } // Driver Code pub fn main() !void { // Инициализация аллокатора памяти var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // Инициализировать кучу // Инициализировать минимальную кучу const PQlt = std.PriorityQueue(i32, void, lessThan); var min_heap = PQlt.init(std.heap.page_allocator, {}); defer min_heap.deinit(); // Инициализация максимальной кучи const PQgt = std.PriorityQueue(i32, void, greaterThan); var max_heap = PQgt.init(std.heap.page_allocator, {}); defer max_heap.deinit(); std.debug.print("\nНиже приведен тестовый пример для большой кучи", .{}); // Добавление элемента в кучу try testPush(i32, mem_allocator, &max_heap, 1); try testPush(i32, mem_allocator, &max_heap, 3); try testPush(i32, mem_allocator, &max_heap, 2); try testPush(i32, mem_allocator, &max_heap, 5); try testPush(i32, mem_allocator, &max_heap, 4); // Получение элемента с вершины кучи var peek = max_heap.peek().?; std.debug.print("\nЭлемент на вершине кучи = {}\n", .{peek}); // Извлечение элемента с вершины кучи try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); // Получить размер кучи var size = max_heap.len; std.debug.print("\nКоличество элементов в куче = {}\n", .{size}); // Проверка, пуста ли куча var is_empty = if (max_heap.len == 0) true else false; std.debug.print("\nПуста ли куча: {}\n", .{is_empty}); // Построить кучу по входному списку try min_heap.addSlice(&[_]i32{ 1, 3, 2, 5, 4 }); std.debug.print("\nПосле построения мин-кучи из входного списка\n", .{}); try inc.PrintUtil.printHeap(i32, mem_allocator, min_heap); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_heap/my_heap.zig ================================================ // File: my_heap.zig // Created Time: 2023-01-14 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Упрощенная реализация класса кучи pub fn MaxHeap(comptime T: type) type { return struct { const Self = @This(); max_heap: ?std.ArrayList(T) = null, // Использовать список вместо массива, чтобы не учитывать проблему расширения // Конструктор, строящий кучу по входному списку pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []const T) !void { if (self.max_heap != null) return; self.max_heap = std.ArrayList(T).init(allocator); // Добавить элементы списка в кучу без изменений try self.max_heap.?.appendSlice(nums); // Выполнить heapify для всех узлов, кроме листовых var i: usize = parent(self.size() - 1) + 1; while (i > 0) : (i -= 1) { try self.siftDown(i - 1); } } // Деструктор, освободить память pub fn deinit(self: *Self) void { if (self.max_heap != null) self.max_heap.?.deinit(); } // Получить индекс левого дочернего узла fn left(i: usize) usize { return 2 * i + 1; } // Получить индекс правого дочернего узла fn right(i: usize) usize { return 2 * i + 2; } // Получить индекс родительского узла fn parent(i: usize) usize { // return (i - 1) / 2; // округление вниз при делении return @divFloor(i - 1, 2); } // Поменять элементы местами fn swap(self: *Self, i: usize, j: usize) !void { var tmp = self.max_heap.?.items[i]; try self.max_heap.?.replaceRange(i, 1, &[_]T{self.max_heap.?.items[j]}); try self.max_heap.?.replaceRange(j, 1, &[_]T{tmp}); } // Получение размера кучи pub fn size(self: *Self) usize { return self.max_heap.?.items.len; } // Проверка, пуста ли куча pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // Доступ к элементу на вершине кучи pub fn peek(self: *Self) T { return self.max_heap.?.items[0]; } // Добавление элемента в кучу pub fn push(self: *Self, val: T) !void { // Добавление узла try self.max_heap.?.append(val); // Просеивание снизу вверх try self.siftUp(self.size() - 1); } // Начиная с узла i, выполнить просеивание снизу вверх fn siftUp(self: *Self, i_: usize) !void { var i = i_; while (true) { // Получение родительского узла для узла i var p = parent(i); // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» if (p < 0 or self.max_heap.?.items[i] <= self.max_heap.?.items[p]) break; // Поменять два узла местами try self.swap(i, p); // Циклическое просеивание вверх i = p; } } // Извлечение элемента из кучи pub fn pop(self: *Self) !T { // Обработка проверки if (self.isEmpty()) unreachable; // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) try self.swap(0, self.size() - 1); // Удаление узла var val = self.max_heap.?.pop(); // Просеивание сверху вниз try self.siftDown(0); // Вернуть элемент с вершины кучи return val; } // Начиная с узла i, выполнить просеивание сверху вниз fn siftDown(self: *Self, i_: usize) !void { var i = i_; while (true) { // Определить узел с максимальным значением среди i, l и r и обозначить его как ma var l = left(i); var r = right(i); var ma = i; if (l < self.size() and self.max_heap.?.items[l] > self.max_heap.?.items[ma]) ma = l; if (r < self.size() and self.max_heap.?.items[r] > self.max_heap.?.items[ma]) ma = r; // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти if (ma == i) break; // Поменять два узла местами try self.swap(i, ma); // Циклическое просеивание вниз i = ma; } } fn lessThan(context: void, a: T, b: T) std.math.Order { _ = context; return std.math.order(a, b); } fn greaterThan(context: void, a: T, b: T) std.math.Order { return lessThan(context, a, b).invert(); } // Вывести кучу (двоичное дерево) pub fn print(self: *Self, mem_allocator: std.mem.Allocator) !void { const PQgt = std.PriorityQueue(T, void, greaterThan); var queue = PQgt.init(std.heap.page_allocator, {}); defer queue.deinit(); try queue.addSlice(self.max_heap.?.items); try inc.PrintUtil.printHeap(T, mem_allocator, queue); } }; } // Driver Code pub fn main() !void { // Инициализация аллокатора памяти var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // Инициализация максимальной кучи var max_heap = MaxHeap(i32){}; try max_heap.init(std.heap.page_allocator, &[_]i32{ 9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2 }); defer max_heap.deinit(); std.debug.print("\nПосле построения кучи из входного списка\n", .{}); try max_heap.print(mem_allocator); // Получение элемента с вершины кучи var peek = max_heap.peek(); std.debug.print("\nЭлемент на вершине кучи = {}\n", .{peek}); // Добавление элемента в кучу const val = 7; try max_heap.push(val); std.debug.print("\nПосле добавления элемента {} в кучу\n", .{val}); try max_heap.print(mem_allocator); // Извлечение элемента с вершины кучи peek = try max_heap.pop(); std.debug.print("\nПосле извлечения элемента вершины кучи {}\n", .{peek}); try max_heap.print(mem_allocator); // Получить размер кучи var size = max_heap.size(); std.debug.print("\nКоличество элементов в куче = {}", .{size}); // Проверка, пуста ли куча var is_empty = max_heap.isEmpty(); std.debug.print("\nПуста ли куча: {}\n", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_searching/binary_search.zig ================================================ // File: binary_search.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Бинарный поиск (двусторонне замкнутый интервал) fn binarySearch(comptime T: type, nums: std.ArrayList(T), target: T) T { // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно var i: usize = 0; var j: usize = nums.items.len - 1; // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) while (i <= j) { var m = i + (j - i) / 2; // Вычислить индекс середины m if (nums.items[m] < target) { // Это означает, что target находится в интервале [m+1, j] i = m + 1; } else if (nums.items[m] > target) { // Это означает, что target находится в интервале [i, m-1] j = m - 1; } else { // Целевой элемент найден, вернуть его индекс return @intCast(m); } } // Целевой элемент не найден, вернуть -1 return -1; } // Бинарный поиск (лево замкнутый, право открытый интервал) fn binarySearchLCRO(comptime T: type, nums: std.ArrayList(T), target: T) T { // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно var i: usize = 0; var j: usize = nums.items.len; // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) while (i <= j) { var m = i + (j - i) / 2; // Вычислить индекс середины m if (nums.items[m] < target) { // Это означает, что target находится в интервале [m+1, j) i = m + 1; } else if (nums.items[m] > target) { // Это означает, что target находится в интервале [i, m) j = m; } else { // Целевой элемент найден, вернуть его индекс return @intCast(m); } } // Целевой элемент не найден, вернуть -1 return -1; } // Driver Code pub fn main() !void { var target: i32 = 6; var nums = std.ArrayList(i32).init(std.heap.page_allocator); defer nums.deinit(); try nums.appendSlice(&[_]i32{ 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }); // Бинарный поиск (двусторонне замкнутый интервал) var index = binarySearch(i32, nums, target); std.debug.print("Индекс целевого элемента 6 = {}\n", .{index}); // Бинарный поиск (лево замкнутый, право открытый интервал) index = binarySearchLCRO(i32, nums, target); std.debug.print("Индекс целевого элемента 6 = {}\n", .{index}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_searching/hashing_search.zig ================================================ // File: hashing_search.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Хеш-поиск (массив) fn hashingSearchArray(comptime T: type, map: std.AutoHashMap(T, T), target: T) T { // key хеш-таблицы: целевой элемент, value: индекс // Если такого key нет в хеш-таблице, вернуть -1 if (map.getKey(target) == null) return -1; return map.get(target).?; } // Хеш-поиск (связный список) fn hashingSearchLinkedList(comptime T: type, map: std.AutoHashMap(T, *inc.ListNode(T)), target: T) ?*inc.ListNode(T) { // key хеш-таблицы: значение целевого узла, value: объект узла // Если такого key нет в хеш-таблице, вернуть null if (map.getKey(target) == null) return null; return map.get(target); } // Driver Code pub fn main() !void { var target: i32 = 3; // Хеш-поиск (массив) var nums = [_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; // Инициализация хеш-таблицы var map = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); defer map.deinit(); for (nums, 0..) |num, i| { try map.put(num, @as(i32, @intCast(i))); // key: элемент, value: индекс } var index = hashingSearchArray(i32, map, target); std.debug.print("Индекс целевого элемента 3 = {}\n", .{index}); // Хеш-поиск (связный список) var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var head = try inc.ListUtil.arrToLinkedList(i32, mem_allocator, &nums); // Инициализация хеш-таблицы var map1 = std.AutoHashMap(i32, *inc.ListNode(i32)).init(std.heap.page_allocator); defer map1.deinit(); while (head != null) { try map1.put(head.?.val, head.?); head = head.?.next; } var node = hashingSearchLinkedList(i32, map1, target); std.debug.print("Объект узла со значением 3 = ", .{}); try inc.PrintUtil.printLinkedList(i32, node); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_searching/linear_search.zig ================================================ // File: linear_search.zig // Created Time: 2023-01-13 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Линейный поиск (массив) fn linearSearchArray(comptime T: type, nums: std.ArrayList(T), target: T) T { // Обход массива for (nums.items, 0..) |num, i| { // Найти целевой элемент и вернуть его индекс if (num == target) { return @intCast(i); } } // Целевой элемент не найден, вернуть -1 return -1; } // Линейный поиск (связный список) pub fn linearSearchLinkedList(comptime T: type, node: ?*inc.ListNode(T), target: T) ?*inc.ListNode(T) { var head = node; // Обойти связный список while (head != null) { // Найти целевой узел и вернуть его if (head.?.val == target) return head; head = head.?.next; } return null; } // Driver Code pub fn main() !void { var target: i32 = 3; // Выполнить линейный поиск в массиве var nums = std.ArrayList(i32).init(std.heap.page_allocator); defer nums.deinit(); try nums.appendSlice(&[_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }); var index = linearSearchArray(i32, nums, target); std.debug.print("Индекс целевого элемента 3 = {}\n", .{index}); // Выполнить линейный поиск в связном списке var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var head = try inc.ListUtil.listToLinkedList(i32, mem_allocator, nums); var node = linearSearchLinkedList(i32, head, target); std.debug.print("Объект узла со значением 3 = ", .{}); try inc.PrintUtil.printLinkedList(i32, node); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_searching/two_sum.zig ================================================ // File: two_sum.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Метод 1: полный перебор pub fn twoSumBruteForce(nums: []i32, target: i32) ?[2]i32 { var size: usize = nums.len; var i: usize = 0; // Два вложенных цикла, временная сложность O(n^2) while (i < size - 1) : (i += 1) { var j = i + 1; while (j < size) : (j += 1) { if (nums[i] + nums[j] == target) { return [_]i32{@intCast(i), @intCast(j)}; } } } return null; } // Метод 2: вспомогательная хеш-таблица pub fn twoSumHashTable(nums: []i32, target: i32) !?[2]i32 { var size: usize = nums.len; // Вспомогательная хеш-таблица, пространственная сложность O(n) var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); defer dic.deinit(); var i: usize = 0; // Один цикл, временная сложность O(n) while (i < size) : (i += 1) { if (dic.contains(target - nums[i])) { return [_]i32{dic.get(target - nums[i]).?, @intCast(i)}; } try dic.put(nums[i], @intCast(i)); } return null; } pub fn main() !void { // ======= Test Case ======= var nums = [_]i32{ 2, 7, 11, 15 }; var target: i32 = 9; // ====== Основной код ====== // Метод 1 var res = twoSumBruteForce(&nums, target).?; std.debug.print("Метод 1: res = ", .{}); inc.PrintUtil.printArray(i32, &res); // Метод 2 res = (try twoSumHashTable(&nums, target)).?; std.debug.print("\nМетод 2: res = ", .{}); inc.PrintUtil.printArray(i32, &res); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_sorting/bubble_sort.zig ================================================ // File: bubble_sort.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Пузырьковая сортировка fn bubbleSort(nums: []i32) void { // Внешний цикл: неотсортированный диапазон [0, i] var i: usize = nums.len - 1; while (i > 0) : (i -= 1) { var j: usize = 0; // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] var tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } // Пузырьковая сортировка (оптимизация флагом) fn bubbleSortWithFlag(nums: []i32) void { // Внешний цикл: неотсортированный диапазон [0, i] var i: usize = nums.len - 1; while (i > 0) : (i -= 1) { var flag = false; // Инициализировать флаг var j: usize = 0; // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // Поменять местами nums[j] и nums[j + 1] var tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; } } if (!flag) break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти } } // Driver Code pub fn main() !void { var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; bubbleSort(&nums); std.debug.print("После завершения пузырьковой сортировки nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); var nums1 = [_]i32{ 4, 1, 3, 1, 5, 2 }; bubbleSortWithFlag(&nums1); std.debug.print("\nПосле завершения пузырьковой сортировки nums1 = ", .{}); inc.PrintUtil.printArray(i32, &nums1); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_sorting/insertion_sort.zig ================================================ // File: insertion_sort.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Сортировка вставками fn insertionSort(nums: []i32) void { // Внешний цикл: отсортированный диапазон [0, i-1] var i: usize = 1; while (i < nums.len) : (i += 1) { var base = nums[i]; var j: usize = i; // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] while (j >= 1 and nums[j - 1] > base) : (j -= 1) { nums[j] = nums[j - 1]; // Сдвинуть nums[j] на одну позицию вправо } nums[j] = base; // Поместить base в правильную позицию } } // Driver Code pub fn main() !void { var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; insertionSort(&nums); std.debug.print("После завершения сортировки вставками nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_sorting/merge_sort.zig ================================================ // File: merge_sort.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Объединить левый и правый подмассивы // Диапазон левого подмассива [left, mid] // Диапазон правого подмассива [mid + 1, right] fn merge(nums: []i32, left: usize, mid: usize, right: usize) !void { // Инициализация вспомогательного массива var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var tmp = try mem_allocator.alloc(i32, right + 1 - left); std.mem.copy(i32, tmp, nums[left..right+1]); // Начальный и конечный индексы левого подмассива var leftStart = left - left; var leftEnd = mid - left; // Начальный и конечный индексы правого подмассива var rightStart = mid + 1 - left; var rightEnd = right - left; // i и j указывают соответственно на первые элементы левого и правого подмассивов var i = leftStart; var j = rightStart; // Объединить левый и правый подмассивы, перезаписывая исходный массив nums var k = left; while (k <= right) : (k += 1) { // Если «левый подмассив уже полностью слит», выбрать элемент правого подмассива и выполнить j++ if (i > leftEnd) { nums[k] = tmp[j]; j += 1; // Иначе, если «правый подмассив уже полностью слит» или «элемент левого подмассива <= элементу правого подмассива», выбрать элемент левого подмассива и выполнить i++ } else if (j > rightEnd or tmp[i] <= tmp[j]) { nums[k] = tmp[i]; i += 1; // Иначе, если «оба подмассива еще не полностью слиты» и «элемент левого подмассива > элемента правого подмассива», выбрать элемент правого подмассива и выполнить j++ } else { nums[k] = tmp[j]; j += 1; } } } // Сортировка слиянием fn mergeSort(nums: []i32, left: usize, right: usize) !void { // Условие завершения if (left >= right) return; // Завершить рекурсию, когда длина подмассива равна 1 // Этап разбиения var mid = left + (right - left) / 2; // Вычислить середину try mergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив try mergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив // Этап слияния try merge(nums, left, mid, right); } // Driver Code pub fn main() !void { // Сортировка слиянием var nums = [_]i32{ 7, 3, 2, 6, 0, 1, 5, 4 }; try mergeSort(&nums, 0, nums.len - 1); std.debug.print("После завершения сортировки слиянием nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_sorting/quick_sort.zig ================================================ // File: quick_sort.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Класс быстрой сортировки const QuickSort = struct { // Обмен элементов pub fn swap(nums: []i32, i: usize, j: usize) void { var tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } // Разбиение с опорными указателями pub fn partition(nums: []i32, left: usize, right: usize) usize { // Взять nums[left] в качестве опорного элемента var i = left; var j = right; while (i < j) { while (i < j and nums[j] >= nums[left]) j -= 1; // Идти справа налево в поисках первого элемента меньше опорного while (i < j and nums[i] <= nums[left]) i += 1; // Идти слева направо в поисках первого элемента больше опорного swap(nums, i, j); // Поменять эти два элемента местами } swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } // Быстрая сортировка pub fn quickSort(nums: []i32, left: usize, right: usize) void { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) return; // Разбиение с опорными указателями var pivot = partition(nums, left, right); // Рекурсивно обработать левый и правый подмассивы quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } }; // Класс быстрой сортировки (оптимизация медианным опорным элементом) const QuickSortMedian = struct { // Обмен элементов pub fn swap(nums: []i32, i: usize, j: usize) void { var tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } // Выбрать медиану из трех кандидатов pub fn medianThree(nums: []i32, left: usize, mid: usize, right: usize) usize { var l = nums[left]; var m = nums[mid]; var r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // m находится между l и r if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l находится между m и r return right; } // Разбиение с опорными указателями (медиана трех) pub fn partition(nums: []i32, left: usize, right: usize) usize { // Выбрать медиану из трех кандидатов var med = medianThree(nums, left, (left + right) / 2, right); // Переместить медиану в крайний левый элемент массива swap(nums, left, med); // Взять nums[left] в качестве опорного элемента var i = left; var j = right; while (i < j) { while (i < j and nums[j] >= nums[left]) j -= 1; // Идти справа налево в поисках первого элемента меньше опорного while (i < j and nums[i] <= nums[left]) i += 1; // Идти слева направо в поисках первого элемента больше опорного swap(nums, i, j); // Поменять эти два элемента местами } swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } // Быстрая сортировка pub fn quickSort(nums: []i32, left: usize, right: usize) void { // Завершить рекурсию, когда длина подмассива равна 1 if (left >= right) return; // Разбиение с опорными указателями var pivot = partition(nums, left, right); if (pivot == 0) return; // Рекурсивно обработать левый и правый подмассивы quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } }; // Класс быстрой сортировки (оптимизация глубины рекурсии) const QuickSortTailCall = struct { // Обмен элементов pub fn swap(nums: []i32, i: usize, j: usize) void { var tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } // Разбиение с опорными указателями pub fn partition(nums: []i32, left: usize, right: usize) usize { // Взять nums[left] в качестве опорного элемента var i = left; var j = right; while (i < j) { while (i < j and nums[j] >= nums[left]) j -= 1; // Идти справа налево в поисках первого элемента меньше опорного while (i < j and nums[i] <= nums[left]) i += 1; // Идти слева направо в поисках первого элемента больше опорного swap(nums, i, j); // Поменять эти два элемента местами } swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов return i; // Вернуть индекс опорного элемента } // Быстрая сортировка (оптимизация глубины рекурсии) pub fn quickSort(nums: []i32, left_: usize, right_: usize) void { var left = left_; var right = right_; // Завершить рекурсию, когда длина подмассива равна 1 while (left < right) { // Операция разбиения с опорными указателями var pivot = partition(nums, left, right); // Выполнить быструю сортировку для более короткого из двух подмассивов if (pivot - left < right - pivot) { quickSort(nums, left, pivot - 1); // Рекурсивно отсортировать левый подмассив left = pivot + 1; // Оставшийся неотсортированный диапазон: [pivot + 1, right] } else { quickSort(nums, pivot + 1, right); // Рекурсивно отсортировать правый подмассив right = pivot - 1; // Оставшийся неотсортированный диапазон: [left, pivot - 1] } } } }; // Driver Code pub fn main() !void { // Быстрая сортировка var nums = [_]i32{ 2, 4, 1, 0, 3, 5 }; QuickSort.quickSort(&nums, 0, nums.len - 1); std.debug.print("После завершения быстрой сортировки nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); // Быстрая сортировка (оптимизация медианным опорным элементом) var nums1 = [_]i32{ 2, 4, 1, 0, 3, 5 }; QuickSortMedian.quickSort(&nums1, 0, nums1.len - 1); std.debug.print("\nПосле завершения быстрой сортировки (оптимизация медианным опорным элементом) nums = ", .{}); inc.PrintUtil.printArray(i32, &nums1); // Быстрая сортировка (оптимизация глубины рекурсии) var nums2 = [_]i32{ 2, 4, 1, 0, 3, 5 }; QuickSortTailCall.quickSort(&nums2, 0, nums2.len - 1); std.debug.print("\nПосле завершения быстрой сортировки (оптимизация глубины рекурсии) nums = ", .{}); inc.PrintUtil.printArray(i32, &nums2); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_sorting/radix_sort.zig ================================================ // File: radix_sort.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Получить k-й разряд элемента num, где exp = 10^(k-1) fn digit(num: i32, exp: i32) i32 { // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени return @mod(@divFloor(num, exp), 10); } // Сортировка подсчетом (сортировка по k-му разряду nums) fn countingSortDigit(nums: []i32, exp: i32) !void { // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); // defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var counter = try mem_allocator.alloc(usize, 10); @memset(counter, 0); var n = nums.len; // Подсчитать число появлений каждой цифры от 0 до 9 for (nums) |num| { var d: u32 = @bitCast(digit(num, exp)); // Получить k-й разряд nums[i], обозначив его как d counter[d] += 1; // Подсчитать число появлений цифры d } // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» var i: usize = 1; while (i < 10) : (i += 1) { counter[i] += counter[i - 1]; } // Выполняя обратный проход, заполнить res элементами по статистике в корзинах var res = try mem_allocator.alloc(i32, n); i = n - 1; while (i >= 0) : (i -= 1) { var d: u32 = @bitCast(digit(nums[i], exp)); var j = counter[d] - 1; // Получить индекс j цифры d в массиве res[j] = nums[i]; // Поместить текущий элемент по индексу j counter[d] -= 1; // Уменьшить количество d на 1 if (i == 0) break; } // Перезаписать исходный массив nums результатом i = 0; while (i < n) : (i += 1) { nums[i] = res[i]; } } // Поразрядная сортировка fn radixSort(nums: []i32) !void { // Получить максимальный элемент массива, чтобы определить максимальное число разрядов var m: i32 = std.math.minInt(i32); for (nums) |num| { if (num > m) m = num; } // Проходить разряды от младшего к старшему var exp: i32 = 1; while (exp <= m) : (exp *= 10) { // Выполнить сортировку подсчетом по k-му разряду элементов массива // k = 1 -> exp = 1 // k = 2 -> exp = 10 // то есть exp = 10^(k-1) try countingSortDigit(nums, exp); } } // Driver Code pub fn main() !void { // Поразрядная сортировка var nums = [_]i32{ 23, 12, 3, 4, 788, 192 }; try radixSort(&nums); std.debug.print("После завершения поразрядной сортировки nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_stack_and_queue/array_queue.zig ================================================ // File: array_queue.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Очередь на основе кольцевого массива pub fn ArrayQueue(comptime T: type) type { return struct { const Self = @This(); nums: []T = undefined, // Массив для хранения элементов очереди cap: usize = 0, // Вместимость очереди front: usize = 0, // Указатель head, указывающий на первый элемент очереди queSize: usize = 0, // Указатель хвоста, указывающий на позицию после хвоста mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // Аллокатор памяти // Конструктор (выделение памяти + инициализация массива) pub fn init(self: *Self, allocator: std.mem.Allocator, cap: usize) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.cap = cap; self.nums = try self.mem_allocator.alloc(T, self.cap); @memset(self.nums, @as(T, 0)); } // Деструктор (освобождение памяти) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // Получить вместимость очереди pub fn capacity(self: *Self) usize { return self.cap; } // Получение длины очереди pub fn size(self: *Self) usize { return self.queSize; } // Проверка, пуста ли очередь pub fn isEmpty(self: *Self) bool { return self.queSize == 0; } // Поместить в очередь pub fn push(self: *Self, num: T) !void { if (self.size() == self.capacity()) { std.debug.print("Очередь заполнена\n", .{}); return; } // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива var rear = (self.front + self.queSize) % self.capacity(); // Добавить num после хвостового узла self.nums[rear] = num; self.queSize += 1; } // Извлечь из очереди pub fn pop(self: *Self) T { var num = self.peek(); // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива self.front = (self.front + 1) % self.capacity(); self.queSize -= 1; return num; } // Доступ к элементу в начале очереди pub fn peek(self: *Self) T { if (self.isEmpty()) @panic("очередь пуста"); return self.nums[self.front]; } // Вернуть массив pub fn toArray(self: *Self) ![]T { // Преобразовывать только элементы списка в пределах фактической длины var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; var j: usize = self.front; while (i < self.size()) : ({ i += 1; j += 1; }) { res[i] = self.nums[j % self.capacity()]; } return res; } }; } // Driver Code pub fn main() !void { // Инициализация очереди var capacity: usize = 10; var queue = ArrayQueue(i32){}; try queue.init(std.heap.page_allocator, capacity); defer queue.deinit(); // Добавление элемента в очередь try queue.push(1); try queue.push(3); try queue.push(2); try queue.push(5); try queue.push(4); std.debug.print("Очередь queue = ", .{}); inc.PrintUtil.printArray(i32, try queue.toArray()); // Доступ к элементу в начале очереди var peek = queue.peek(); std.debug.print("\nЭлемент в начале очереди peek = {}", .{peek}); // Извлечение элемента из очереди var pop = queue.pop(); std.debug.print("\nИзвлечен элемент pop = {}, очередь после извлечения queue = ", .{pop}); inc.PrintUtil.printArray(i32, try queue.toArray()); // Получение длины очереди var size = queue.size(); std.debug.print("\nДлина очереди size = {}", .{size}); // Проверка, пуста ли очередь var is_empty = queue.isEmpty(); std.debug.print("\nПуста ли очередь = {}", .{is_empty}); // Проверка кольцевого массива var i: i32 = 0; while (i < 10) : (i += 1) { try queue.push(i); _ = queue.pop(); std.debug.print("\nПосле {}-го добавления и извлечения queue = ", .{i}); inc.PrintUtil.printArray(i32, try queue.toArray()); } _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_stack_and_queue/array_stack.zig ================================================ // File: array_stack.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Стек на основе массива pub fn ArrayStack(comptime T: type) type { return struct { const Self = @This(); stack: ?std.ArrayList(T) = null, // Конструктор (выделение памяти + инициализация стека) pub fn init(self: *Self, allocator: std.mem.Allocator) void { if (self.stack == null) { self.stack = std.ArrayList(T).init(allocator); } } // Деструктор (освобождение памяти) pub fn deinit(self: *Self) void { if (self.stack == null) return; self.stack.?.deinit(); } // Получение длины стека pub fn size(self: *Self) usize { return self.stack.?.items.len; } // Проверка, пуст ли стек pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // Доступ к верхнему элементу стека pub fn peek(self: *Self) T { if (self.isEmpty()) @panic("стек пуст"); return self.stack.?.items[self.size() - 1]; } // Поместить в стек pub fn push(self: *Self, num: T) !void { try self.stack.?.append(num); } // Извлечь из стека pub fn pop(self: *Self) T { var num = self.stack.?.pop(); return num; } // Вернуть ArrayList pub fn toList(self: *Self) std.ArrayList(T) { return self.stack.?; } }; } // Driver Code pub fn main() !void { // Инициализация стека var stack = ArrayStack(i32){}; stack.init(std.heap.page_allocator); // Отложенное освобождение памяти defer stack.deinit(); // Помещение элемента в стек try stack.push(1); try stack.push(3); try stack.push(2); try stack.push(5); try stack.push(4); std.debug.print("Стек stack = ", .{}); inc.PrintUtil.printList(i32, stack.toList()); // Доступ к верхнему элементу стека var peek = stack.peek(); std.debug.print("\nВерхний элемент стека peek = {}", .{peek}); // Извлечение элемента из стека var top = stack.pop(); std.debug.print("\nИзвлечен элемент pop = {}, стек после извлечения stack = ", .{top}); inc.PrintUtil.printList(i32, stack.toList()); // Получение длины стека var size = stack.size(); std.debug.print("\nДлина стека size = {}", .{size}); // Проверка, пуст ли стек var is_empty = stack.isEmpty(); std.debug.print("\nПуст ли стек = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_stack_and_queue/deque.zig ================================================ // File: deque.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // Инициализация двусторонней очереди const L = std.TailQueue(i32); var deque = L{}; // Добавление элемента в очередь var node1 = L.Node{ .data = 2 }; var node2 = L.Node{ .data = 5 }; var node3 = L.Node{ .data = 4 }; var node4 = L.Node{ .data = 3 }; var node5 = L.Node{ .data = 1 }; deque.append(&node1); // Добавить в хвост очереди deque.append(&node2); deque.append(&node3); deque.prepend(&node4); // Добавить в голову очереди deque.prepend(&node5); std.debug.print("Двусторонняя очередь deque = ", .{}); inc.PrintUtil.printQueue(i32, deque); // Доступ к элементу var peek_first = deque.first.?.data; // Элемент в голове очереди std.debug.print("\nЭлемент в начале очереди peek_first = {}", .{peek_first}); var peek_last = deque.last.?.data; // Элемент в хвосте очереди std.debug.print("\nЭлемент в конце очереди peek_last = {}", .{peek_last}); // Извлечение элемента из очереди var pop_first = deque.popFirst().?.data; // Извлечь элемент из головы очереди std.debug.print("\nИзвлечен элемент из головы pop_first = {}, deque после извлечения из головы = ", .{pop_first}); inc.PrintUtil.printQueue(i32, deque); var pop_last = deque.pop().?.data; // Извлечь элемент из хвоста очереди std.debug.print("\nИзвлечен элемент из хвоста pop_last = {}, deque после извлечения из хвоста = ", .{pop_last}); inc.PrintUtil.printQueue(i32, deque); // Получение длины двусторонней очереди var size = deque.len; std.debug.print("\nДлина двусторонней очереди size = {}", .{size}); // Проверка, пуста ли двусторонняя очередь var is_empty = if (deque.len == 0) true else false; std.debug.print("\nПуста ли двусторонняя очередь = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig ================================================ // File: linkedlist_deque.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Узел двусвязного списка pub fn ListNode(comptime T: type) type { return struct { const Self = @This(); val: T = undefined, // Значение узла next: ?*Self = null, // Указатель на узел-преемник prev: ?*Self = null, // Указатель на узел-предшественник // Initialize a list node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.next = null; self.prev = null; } }; } // Двусторонняя очередь на основе двусвязного списка pub fn LinkedListDeque(comptime T: type) type { return struct { const Self = @This(); front: ?*ListNode(T) = null, // Головной узел front rear: ?*ListNode(T) = null, // Хвостовой узел rear que_size: usize = 0, // Длина двусторонней очереди mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // Аллокатор памяти // Конструктор (выделение памяти + инициализация очереди) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.front = null; self.rear = null; self.que_size = 0; } // Деструктор (освобождение памяти) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // Получение длины двусторонней очереди pub fn size(self: *Self) usize { return self.que_size; } // Проверка, пуста ли двусторонняя очередь pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // Операция добавления в очередь pub fn push(self: *Self, num: T, is_front: bool) !void { var node = try self.mem_allocator.create(ListNode(T)); node.init(num); // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node if (self.isEmpty()) { self.front = node; self.rear = node; // Операция добавления в голову очереди } else if (is_front) { // Добавить node в голову списка self.front.?.prev = node; node.next = self.front; self.front = node; // Обновить головной узел // Операция добавления в хвост очереди } else { // Добавить node в хвост списка self.rear.?.next = node; node.prev = self.rear; self.rear = node; // Обновить хвостовой узел } self.que_size += 1; // Обновить длину очереди } // Добавление в голову очереди pub fn pushFirst(self: *Self, num: T) !void { try self.push(num, true); } // Добавление в хвост очереди pub fn pushLast(self: *Self, num: T) !void { try self.push(num, false); } // Операция извлечения из очереди pub fn pop(self: *Self, is_front: bool) T { if (self.isEmpty()) @panic("двусторонняя очередь пуста"); var val: T = undefined; // Операция извлечения из головы очереди if (is_front) { val = self.front.?.val; // Временно сохранить значение головного узла // Удалить головной узел var fNext = self.front.?.next; if (fNext != null) { fNext.?.prev = null; self.front.?.next = null; } self.front = fNext; // Обновить головной узел // Операция извлечения из хвоста очереди } else { val = self.rear.?.val; // Временно сохранить значение хвостового узла // Удалить хвостовой узел var rPrev = self.rear.?.prev; if (rPrev != null) { rPrev.?.next = null; self.rear.?.prev = null; } self.rear = rPrev; // Обновить хвостовой узел } self.que_size -= 1; // Обновить длину очереди return val; } // Извлечение из головы очереди pub fn popFirst(self: *Self) T { return self.pop(true); } // Извлечение из хвоста очереди pub fn popLast(self: *Self) T { return self.pop(false); } // Доступ к элементу в начале очереди pub fn peekFirst(self: *Self) T { if (self.isEmpty()) @panic("двусторонняя очередь пуста"); return self.front.?.val; } // Доступ к элементу в конце очереди pub fn peekLast(self: *Self) T { if (self.isEmpty()) @panic("двусторонняя очередь пуста"); return self.rear.?.val; } // Вернуть массив для вывода pub fn toArray(self: *Self) ![]T { var node = self.front; var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; while (i < res.len) : (i += 1) { res[i] = node.?.val; node = node.?.next; } return res; } }; } // Driver Code pub fn main() !void { // Инициализация двусторонней очереди var deque = LinkedListDeque(i32){}; try deque.init(std.heap.page_allocator); defer deque.deinit(); try deque.pushLast(3); try deque.pushLast(2); try deque.pushLast(5); std.debug.print("Двусторонняя очередь deque = ", .{}); inc.PrintUtil.printArray(i32, try deque.toArray()); // Доступ к элементу var peek_first = deque.peekFirst(); std.debug.print("\nЭлемент в начале очереди peek_first = {}", .{peek_first}); var peek_last = deque.peekLast(); std.debug.print("\nЭлемент в конце очереди peek_last = {}", .{peek_last}); // Добавление элемента в очередь try deque.pushLast(4); std.debug.print("\nПосле добавления элемента 4 в хвост deque = ", .{}); inc.PrintUtil.printArray(i32, try deque.toArray()); try deque.pushFirst(1); std.debug.print("\nПосле добавления элемента 1 в голову deque = ", .{}); inc.PrintUtil.printArray(i32, try deque.toArray()); // Извлечение элемента из очереди var pop_last = deque.popLast(); std.debug.print("\nИзвлечен элемент из хвоста = {}, deque после извлечения из хвоста = ", .{pop_last}); inc.PrintUtil.printArray(i32, try deque.toArray()); var pop_first = deque.popFirst(); std.debug.print("\nИзвлечен элемент из головы = {}, deque после извлечения из головы = ", .{pop_first}); inc.PrintUtil.printArray(i32, try deque.toArray()); // Получение длины двусторонней очереди var size = deque.size(); std.debug.print("\nДлина двусторонней очереди size = {}", .{size}); // Проверка, пуста ли двусторонняя очередь var is_empty = deque.isEmpty(); std.debug.print("\nПуста ли двусторонняя очередь = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig ================================================ // File: linkedlist_queue.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Очередь на основе связного списка pub fn LinkedListQueue(comptime T: type) type { return struct { const Self = @This(); front: ?*inc.ListNode(T) = null, // Головной узел front rear: ?*inc.ListNode(T) = null, // Хвостовой узел rear que_size: usize = 0, // Длина очереди mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // Аллокатор памяти // Конструктор (выделение памяти + инициализация очереди) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.front = null; self.rear = null; self.que_size = 0; } // Деструктор (освобождение памяти) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // Получение длины очереди pub fn size(self: *Self) usize { return self.que_size; } // Проверка, пуста ли очередь pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // Доступ к элементу в начале очереди pub fn peek(self: *Self) T { if (self.size() == 0) @panic("очередь пуста"); return self.front.?.val; } // Поместить в очередь pub fn push(self: *Self, num: T) !void { // Добавить num после хвостового узла var node = try self.mem_allocator.create(inc.ListNode(T)); node.init(num); // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел if (self.front == null) { self.front = node; self.rear = node; // Если очередь не пуста, добавить этот узел после хвостового узла } else { self.rear.?.next = node; self.rear = node; } self.que_size += 1; } // Извлечь из очереди pub fn pop(self: *Self) T { var num = self.peek(); // Удалить головной узел self.front = self.front.?.next; self.que_size -= 1; return num; } // Преобразовать связный список в массив pub fn toArray(self: *Self) ![]T { var node = self.front; var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; while (i < res.len) : (i += 1) { res[i] = node.?.val; node = node.?.next; } return res; } }; } // Driver Code pub fn main() !void { // Инициализация очереди var queue = LinkedListQueue(i32){}; try queue.init(std.heap.page_allocator); defer queue.deinit(); // Добавление элемента в очередь try queue.push(1); try queue.push(3); try queue.push(2); try queue.push(5); try queue.push(4); std.debug.print("Очередь queue = ", .{}); inc.PrintUtil.printArray(i32, try queue.toArray()); // Доступ к элементу в начале очереди var peek = queue.peek(); std.debug.print("\nЭлемент в начале очереди peek = {}", .{peek}); // Извлечение элемента из очереди var pop = queue.pop(); std.debug.print("\nИзвлечен элемент pop = {}, очередь после извлечения queue = ", .{pop}); inc.PrintUtil.printArray(i32, try queue.toArray()); // Получение длины очереди var size = queue.size(); std.debug.print("\nДлина очереди size = {}", .{size}); // Проверка, пуста ли очередь var is_empty = queue.isEmpty(); std.debug.print("\nПуста ли очередь = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig ================================================ // File: linkedlist_stack.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Стек на основе связного списка pub fn LinkedListStack(comptime T: type) type { return struct { const Self = @This(); stack_top: ?*inc.ListNode(T) = null, // Использовать головной узел как вершину стека stk_size: usize = 0, // Длина стека mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // Аллокатор памяти // Конструктор (выделение памяти + инициализация стека) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.stack_top = null; self.stk_size = 0; } // Деструктор (освобождение памяти) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // Получение длины стека pub fn size(self: *Self) usize { return self.stk_size; } // Проверка, пуст ли стек pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // Доступ к верхнему элементу стека pub fn peek(self: *Self) T { if (self.size() == 0) @panic("стек пуст"); return self.stack_top.?.val; } // Поместить в стек pub fn push(self: *Self, num: T) !void { var node = try self.mem_allocator.create(inc.ListNode(T)); node.init(num); node.next = self.stack_top; self.stack_top = node; self.stk_size += 1; } // Извлечь из стека pub fn pop(self: *Self) T { var num = self.peek(); self.stack_top = self.stack_top.?.next; self.stk_size -= 1; return num; } // Преобразовать стек в массив pub fn toArray(self: *Self) ![]T { var node = self.stack_top; var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; while (i < res.len) : (i += 1) { res[res.len - i - 1] = node.?.val; node = node.?.next; } return res; } }; } // Driver Code pub fn main() !void { // Инициализация стека var stack = LinkedListStack(i32){}; try stack.init(std.heap.page_allocator); // Отложенное освобождение памяти defer stack.deinit(); // Помещение элемента в стек try stack.push(1); try stack.push(3); try stack.push(2); try stack.push(5); try stack.push(4); std.debug.print("Стек stack = ", .{}); inc.PrintUtil.printArray(i32, try stack.toArray()); // Доступ к верхнему элементу стека var peek = stack.peek(); std.debug.print("\nВерхний элемент стека top = {}", .{peek}); // Извлечение элемента из стека var pop = stack.pop(); std.debug.print("\nИзвлечен элемент pop = {}, стек после извлечения stack = ", .{pop}); inc.PrintUtil.printArray(i32, try stack.toArray()); // Получение длины стека var size = stack.size(); std.debug.print("\nДлина стека size = {}", .{size}); // Проверка, пуст ли стек var is_empty = stack.isEmpty(); std.debug.print("\nПуст ли стек = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_stack_and_queue/queue.zig ================================================ // File: queue.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // Инициализация очереди const L = std.TailQueue(i32); var queue = L{}; // Добавление элемента в очередь var node1 = L.Node{ .data = 1 }; var node2 = L.Node{ .data = 3 }; var node3 = L.Node{ .data = 2 }; var node4 = L.Node{ .data = 5 }; var node5 = L.Node{ .data = 4 }; queue.append(&node1); queue.append(&node2); queue.append(&node3); queue.append(&node4); queue.append(&node5); std.debug.print("Очередь queue = ", .{}); inc.PrintUtil.printQueue(i32, queue); // Доступ к элементу в начале очереди var peek = queue.first.?.data; std.debug.print("\nЭлемент в начале очереди peek = {}", .{peek}); // Извлечение элемента из очереди var pop = queue.popFirst().?.data; std.debug.print("\nИзвлечен элемент pop = {}, очередь после извлечения queue = ", .{pop}); inc.PrintUtil.printQueue(i32, queue); // Получение длины очереди var size = queue.len; std.debug.print("\nДлина очереди size = {}", .{size}); // Проверка, пуста ли очередь var is_empty = if (queue.len == 0) true else false; std.debug.print("\nПуста ли очередь = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_stack_and_queue/stack.zig ================================================ // File: stack.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // Инициализировать стек // В Zig рекомендуется использовать ArrayList как стек var stack = std.ArrayList(i32).init(std.heap.page_allocator); // Отложенное освобождение памяти defer stack.deinit(); // Помещение элемента в стек try stack.append(1); try stack.append(3); try stack.append(2); try stack.append(5); try stack.append(4); std.debug.print("Стек stack = ", .{}); inc.PrintUtil.printList(i32, stack); // Доступ к верхнему элементу стека var peek = stack.items[stack.items.len - 1]; std.debug.print("\nВерхний элемент стека peek = {}", .{peek}); // Извлечение элемента из стека var pop = stack.pop(); std.debug.print("\nИзвлечен элемент pop = {}, стек после извлечения stack = ", .{pop}); inc.PrintUtil.printList(i32, stack); // Получение длины стека var size = stack.items.len; std.debug.print("\nДлина стека size = {}", .{size}); // Проверка, пуст ли стек var is_empty = if (stack.items.len == 0) true else false; std.debug.print("\nПуст ли стек = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_tree/avl_tree.zig ================================================ // File: avl_tree.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // AVL-дерево pub fn AVLTree(comptime T: type) type { return struct { const Self = @This(); root: ?*inc.TreeNode(T) = null, // Корневой узел mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // Аллокатор памяти // Конструктор pub fn init(self: *Self, allocator: std.mem.Allocator) void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } } // Метод-деструктор pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // Получить высоту узла fn height(self: *Self, node: ?*inc.TreeNode(T)) i32 { _ = self; // Высота пустого узла равна -1, высота листового узла равна 0 return if (node == null) -1 else node.?.height; } // Обновить высоту узла fn updateHeight(self: *Self, node: ?*inc.TreeNode(T)) void { // Высота узла равна высоте более высокого поддерева + 1 node.?.height = @max(self.height(node.?.left), self.height(node.?.right)) + 1; } // Получить коэффициент баланса fn balanceFactor(self: *Self, node: ?*inc.TreeNode(T)) i32 { // Коэффициент баланса пустого узла равен 0 if (node == null) return 0; // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева return self.height(node.?.left) - self.height(node.?.right); } // Операция правого вращения fn rightRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { var child = node.?.left; var grandChild = child.?.right; // Выполнить правое вращение узла node вокруг child child.?.right = node; node.?.left = grandChild; // Обновить высоту узла self.updateHeight(node); self.updateHeight(child); // Вернуть корневой узел поддерева после вращения return child; } // Операция левого вращения fn leftRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { var child = node.?.right; var grandChild = child.?.left; // Выполнить левое вращение узла node вокруг child child.?.left = node; node.?.right = grandChild; // Обновить высоту узла self.updateHeight(node); self.updateHeight(child); // Вернуть корневой узел поддерева после вращения return child; } // Выполнить вращение, чтобы снова сбалансировать поддерево fn rotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { // Получить коэффициент баланса узла node var balance_factor = self.balanceFactor(node); // Левосторонне перекошенное дерево if (balance_factor > 1) { if (self.balanceFactor(node.?.left) >= 0) { // Правое вращение return self.rightRotate(node); } else { // Сначала левое вращение, затем правое node.?.left = self.leftRotate(node.?.left); return self.rightRotate(node); } } // Правосторонне перекошенное дерево if (balance_factor < -1) { if (self.balanceFactor(node.?.right) <= 0) { // Левое вращение return self.leftRotate(node); } else { // Сначала правое вращение, затем левое node.?.right = self.rightRotate(node.?.right); return self.leftRotate(node); } } // Дерево сбалансировано, вращение не требуется, вернуть сразу return node; } // Вставка узла fn insert(self: *Self, val: T) !void { self.root = (try self.insertHelper(self.root, val)).?; } // Рекурсивная вставка узла (вспомогательный метод) fn insertHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) !?*inc.TreeNode(T) { var node = node_; if (node == null) { var tmp_node = try self.mem_allocator.create(inc.TreeNode(T)); tmp_node.init(val); return tmp_node; } // 1. Найти позицию вставки и вставить узел if (val < node.?.val) { node.?.left = try self.insertHelper(node.?.left, val); } else if (val > node.?.val) { node.?.right = try self.insertHelper(node.?.right, val); } else { return node; // Повторяющийся узел не вставлять, сразу вернуть } self.updateHeight(node); // Обновить высоту узла // 2. Выполнить вращение, чтобы снова сбалансировать поддерево node = self.rotate(node); // Вернуть корневой узел поддерева return node; } // Удаление узла fn remove(self: *Self, val: T) void { self.root = self.removeHelper(self.root, val).?; } // Рекурсивное удаление узла (вспомогательный метод) fn removeHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) ?*inc.TreeNode(T) { var node = node_; if (node == null) return null; // 1. Найти узел и удалить его if (val < node.?.val) { node.?.left = self.removeHelper(node.?.left, val); } else if (val > node.?.val) { node.?.right = self.removeHelper(node.?.right, val); } else { if (node.?.left == null or node.?.right == null) { var child = if (node.?.left != null) node.?.left else node.?.right; // Число дочерних узлов = 0, удалить node и сразу вернуть if (child == null) { return null; // Число дочерних узлов = 1, удалить node напрямую } else { node = child; } } else { // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел var temp = node.?.right; while (temp.?.left != null) { temp = temp.?.left; } node.?.right = self.removeHelper(node.?.right, temp.?.val); node.?.val = temp.?.val; } } self.updateHeight(node); // Обновить высоту узла // 2. Выполнить вращение, чтобы снова сбалансировать поддерево node = self.rotate(node); // Вернуть корневой узел поддерева return node; } // Поиск узла fn search(self: *Self, val: T) ?*inc.TreeNode(T) { var cur = self.root; // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Целевой узел находится в правом поддереве cur if (cur.?.val < val) { cur = cur.?.right; // Целевой узел находится в левом поддереве cur } else if (cur.?.val > val) { cur = cur.?.left; // Найти целевой узел и выйти из цикла } else { break; } } // Вернуть целевой узел return cur; } }; } pub fn testInsert(comptime T: type, tree_: *AVLTree(T), val: T) !void { var tree = tree_; try tree.insert(val); std.debug.print("\nПосле вставки узла {} AVL-дерево имеет вид\n", .{val}); try inc.PrintUtil.printTree(tree.root, null, false); } pub fn testRemove(comptime T: type, tree_: *AVLTree(T), val: T) void { var tree = tree_; tree.remove(val); std.debug.print("\nПосле удаления узла {} AVL-дерево имеет вид\n", .{val}); try inc.PrintUtil.printTree(tree.root, null, false); } // Driver Code pub fn main() !void { // Инициализация пустого AVL-дерева var avl_tree = AVLTree(i32){}; avl_tree.init(std.heap.page_allocator); defer avl_tree.deinit(); // Вставка узла // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла try testInsert(i32, &avl_tree, 1); try testInsert(i32, &avl_tree, 2); try testInsert(i32, &avl_tree, 3); try testInsert(i32, &avl_tree, 4); try testInsert(i32, &avl_tree, 5); try testInsert(i32, &avl_tree, 8); try testInsert(i32, &avl_tree, 7); try testInsert(i32, &avl_tree, 9); try testInsert(i32, &avl_tree, 10); try testInsert(i32, &avl_tree, 6); // Вставка повторяющегося узла try testInsert(i32, &avl_tree, 7); // Удаление узла // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла testRemove(i32, &avl_tree, 8); // Удаление узла степени 0 testRemove(i32, &avl_tree, 5); // Удаление узла степени 1 testRemove(i32, &avl_tree, 4); // Удаление узла степени 2 // Поиск узла var node = avl_tree.search(7).?; std.debug.print("\nНайденный объект узла = {any}, значение узла = {}\n", .{node, node.val}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_tree/binary_search_tree.zig ================================================ // File: binary_search_tree.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Двоичное дерево поиска pub fn BinarySearchTree(comptime T: type) type { return struct { const Self = @This(); root: ?*inc.TreeNode(T) = null, mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // Аллокатор памяти // Конструктор pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []T) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } std.mem.sort(T, nums, {}, comptime std.sort.asc(T)); // Отсортировать массив self.root = try self.buildTree(nums, 0, nums.len - 1); // Построить двоичное дерево поиска } // Метод-деструктор pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // Построить двоичное дерево поиска fn buildTree(self: *Self, nums: []T, i: usize, j: usize) !?*inc.TreeNode(T) { if (i > j) return null; // Использовать средний узел массива как корневой узел var mid = i + (j - i) / 2; var node = try self.mem_allocator.create(inc.TreeNode(T)); node.init(nums[mid]); // Рекурсивно построить левое и правое поддеревья if (mid >= 1) node.left = try self.buildTree(nums, i, mid - 1); node.right = try self.buildTree(nums, mid + 1, j); return node; } // Получить корневой узел двоичного дерева fn getRoot(self: *Self) ?*inc.TreeNode(T) { return self.root; } // Поиск узла fn search(self: *Self, num: T) ?*inc.TreeNode(T) { var cur = self.root; // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Целевой узел находится в правом поддереве cur if (cur.?.val < num) { cur = cur.?.right; // Целевой узел находится в левом поддереве cur } else if (cur.?.val > num) { cur = cur.?.left; // Найти целевой узел и выйти из цикла } else { break; } } // Вернуть целевой узел return cur; } // Вставка узла fn insert(self: *Self, num: T) !void { // Если дерево пусто, инициализировать корневой узел if (self.root == null) { self.root = try self.mem_allocator.create(inc.TreeNode(T)); return; } var cur = self.root; var pre: ?*inc.TreeNode(T) = null; // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Найти повторяющийся узел и сразу вернуть if (cur.?.val == num) return; pre = cur; // Позиция вставки находится в правом поддереве cur if (cur.?.val < num) { cur = cur.?.right; // Позиция вставки находится в левом поддереве cur } else { cur = cur.?.left; } } // Вставка узла var node = try self.mem_allocator.create(inc.TreeNode(T)); node.init(num); if (pre.?.val < num) { pre.?.right = node; } else { pre.?.left = node; } } // Удаление узла fn remove(self: *Self, num: T) void { // Если дерево пусто, сразу вернуть if (self.root == null) return; var cur = self.root; var pre: ?*inc.TreeNode(T) = null; // Искать в цикле и выйти после прохода за листовой узел while (cur != null) { // Найти узел для удаления и выйти из цикла if (cur.?.val == num) break; pre = cur; // Узел для удаления находится в правом поддереве cur if (cur.?.val < num) { cur = cur.?.right; // Узел для удаления находится в левом поддереве cur } else { cur = cur.?.left; } } // Если узел для удаления отсутствует, сразу вернуть if (cur == null) return; // Число дочерних узлов = 0 или 1 if (cur.?.left == null or cur.?.right == null) { // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел var child = if (cur.?.left != null) cur.?.left else cur.?.right; // Удалить узел cur if (pre.?.left == cur) { pre.?.left = child; } else { pre.?.right = child; } // Число дочерних узлов = 2 } else { // Получить следующий узел после cur в симметричном обходе var tmp = cur.?.right; while (tmp.?.left != null) { tmp = tmp.?.left; } var tmp_val = tmp.?.val; // Рекурсивно удалить узел tmp self.remove(tmp.?.val); // Перезаписать cur значением tmp cur.?.val = tmp_val; } } }; } // Driver Code pub fn main() !void { // Инициализация двоичного дерева var nums = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; var bst = BinarySearchTree(i32){}; try bst.init(std.heap.page_allocator, &nums); defer bst.deinit(); std.debug.print("Инициализированное двоичное дерево\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); // Поиск узла var node = bst.search(7); std.debug.print("\nНайденный объект узла = {any}, значение узла = {}\n", .{node, node.?.val}); // Вставка узла try bst.insert(16); std.debug.print("\nПосле вставки узла 16 двоичное дерево имеет вид\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); // Удаление узла bst.remove(1); std.debug.print("\nПосле удаления узла 1 двоичное дерево имеет вид\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); bst.remove(2); std.debug.print("\nПосле удаления узла 2 двоичное дерево имеет вид\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); bst.remove(4); std.debug.print("\nПосле удаления узла 4 двоичное дерево имеет вид\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_tree/binary_tree.zig ================================================ // File: binary_tree.zig // Created Time: 2023-01-14 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // Инициализация двоичного дерева // Инициализация узлов var n1 = inc.TreeNode(i32){ .val = 1 }; var n2 = inc.TreeNode(i32){ .val = 2 }; var n3 = inc.TreeNode(i32){ .val = 3 }; var n4 = inc.TreeNode(i32){ .val = 4 }; var n5 = inc.TreeNode(i32){ .val = 5 }; // Построить связи между узлами (указатели) n1.left = &n2; n1.right = &n3; n2.left = &n4; n2.right = &n5; std.debug.print("Инициализация двоичного дерева\n", .{}); try inc.PrintUtil.printTree(&n1, null, false); // Вставка и удаление узлов var p = inc.TreeNode(i32){ .val = 0 }; // Вставить узел P между n1 -> n2 n1.left = &p; p.left = &n2; std.debug.print("После вставки узла P\n", .{}); try inc.PrintUtil.printTree(&n1, null, false); // Удаление узла n1.left = &n2; std.debug.print("После удаления узла P\n", .{}); try inc.PrintUtil.printTree(&n1, null, false); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_tree/binary_tree_bfs.zig ================================================ // File: binary_tree_bfs.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Обход в ширину fn levelOrder(comptime T: type, mem_allocator: std.mem.Allocator, root: *inc.TreeNode(T)) !std.ArrayList(T) { // Инициализировать очередь и добавить корневой узел const L = std.TailQueue(*inc.TreeNode(T)); var queue = L{}; var root_node = try mem_allocator.create(L.Node); root_node.data = root; queue.append(root_node); // Инициализировать список для хранения последовательности обхода var list = std.ArrayList(T).init(std.heap.page_allocator); while (queue.len > 0) { var queue_node = queue.popFirst().?; // Извлечение из очереди var node = queue_node.data; try list.append(node.val); // Сохранить значение узла if (node.left != null) { var tmp_node = try mem_allocator.create(L.Node); tmp_node.data = node.left.?; queue.append(tmp_node); // Поместить левый дочерний узел в очередь } if (node.right != null) { var tmp_node = try mem_allocator.create(L.Node); tmp_node.data = node.right.?; queue.append(tmp_node); // Поместить правый дочерний узел в очередь } } return list; } // Driver Code pub fn main() !void { // Инициализация аллокатора памяти var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // Инициализировать двоичное дерево // Здесь используется функция, напрямую строящая двоичное дерево из массива var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); std.debug.print("Инициализация двоичного дерева\n", .{}); try inc.PrintUtil.printTree(root, null, false); // Обход в ширину var list = try levelOrder(i32, mem_allocator, root.?); defer list.deinit(); std.debug.print("\nПоследовательность печати узлов при обходе в ширину = ", .{}); inc.PrintUtil.printList(i32, list); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/chapter_tree/binary_tree_dfs.zig ================================================ // File: binary_tree_dfs.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); var list = std.ArrayList(i32).init(std.heap.page_allocator); // Предварительный обход fn preOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { if (root == null) return; // Порядок обхода: корень -> левое поддерево -> правое поддерево try list.append(root.?.val); try preOrder(T, root.?.left); try preOrder(T, root.?.right); } // Симметричный обход fn inOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { if (root == null) return; // Порядок обхода: левое поддерево -> корень -> правое поддерево try inOrder(T, root.?.left); try list.append(root.?.val); try inOrder(T, root.?.right); } // Обратный обход fn postOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { if (root == null) return; // Порядок обхода: левое поддерево -> правое поддерево -> корень try postOrder(T, root.?.left); try postOrder(T, root.?.right); try list.append(root.?.val); } // Driver Code pub fn main() !void { // Инициализация аллокатора памяти var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // Инициализировать двоичное дерево // Здесь используется функция, напрямую строящая двоичное дерево из массива var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); std.debug.print("Инициализация двоичного дерева\n", .{}); try inc.PrintUtil.printTree(root, null, false); // Предварительный обход list.clearRetainingCapacity(); try preOrder(i32, root); std.debug.print("\nПоследовательность печати узлов при предварительном обходе = ", .{}); inc.PrintUtil.printList(i32, list); // Симметричный обход list.clearRetainingCapacity(); try inOrder(i32, root); std.debug.print("\nПоследовательность печати узлов при симметричном обходе = ", .{}); inc.PrintUtil.printList(i32, list); // Обратный обход list.clearRetainingCapacity(); try postOrder(i32, root); std.debug.print("\nПоследовательность печати узлов при обратном обходе = ", .{}); inc.PrintUtil.printList(i32, list); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: ru/codes/zig/include/PrintUtil.zig ================================================ // File: PrintUtil.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); pub const ListUtil = @import("ListNode.zig"); pub const ListNode = ListUtil.ListNode; pub const TreeUtil = @import("TreeNode.zig"); pub const TreeNode = TreeUtil.TreeNode; // Вывести очередь pub fn printQueue(comptime T: type, queue: std.TailQueue(T)) void { var node = queue.first; std.debug.print("[", .{}); var i: i32 = 0; while (node != null) : (i += 1) { var data = node.?.data; std.debug.print("{}{s}", .{ data, if (i == queue.len - 1) "]" else ", " }); node = node.?.next; } } // Вывести хеш-таблицу pub fn printHashMap(comptime TKey: type, comptime TValue: type, map: std.AutoHashMap(TKey, TValue)) void { var it = map.iterator(); while (it.next()) |kv| { var key = kv.key_ptr.*; var value = kv.value_ptr.*; std.debug.print("{} -> {s}\n", .{ key, value }); } } // Вывести кучу pub fn printHeap(comptime T: type, mem_allocator: std.mem.Allocator, queue: anytype) !void { var arr = queue.items; var len = queue.len; std.debug.print("Массивное представление кучи:", .{}); printArray(T, arr[0..len]); std.debug.print("\nДревовидное представление кучи:\n", .{}); var root = try TreeUtil.arrToTree(T, mem_allocator, arr[0..len]); try printTree(root, null, false); } ================================================ FILE: ru/codes/zig/include/include.zig ================================================ // File: include.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) pub const PrintUtil = @import("PrintUtil.zig"); pub const TreeUtil = @import("TreeNode.zig"); pub const TreeNode = TreeUtil.TreeNode; ================================================ FILE: ru/codes/zig/main.zig ================================================ const std = @import("std"); const iteration = @import("chapter_computational_complexity/iteration.zig"); const recursion = @import("chapter_computational_complexity/recursion.zig"); const time_complexity = @import("chapter_computational_complexity/time_complexity.zig"); const space_complexity = @import("chapter_computational_complexity/space_complexity.zig"); const worst_best_time_complexity = @import("chapter_computational_complexity/worst_best_time_complexity.zig"); const array = @import("chapter_array_and_linkedlist/array.zig"); const linked_list = @import("chapter_array_and_linkedlist/linked_list.zig"); const list = @import("chapter_array_and_linkedlist/list.zig"); const my_list = @import("chapter_array_and_linkedlist/my_list.zig"); pub fn main() !void { try iteration.run(); recursion.run(); time_complexity.run(); try space_complexity.run(); worst_best_time_complexity.run(); try array.run(); linked_list.run(); try list.run(); try my_list.run(); } ================================================ FILE: ru/codes/zig/utils/ListNode.zig ================================================ // File: ListNode.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // Узел связного списка pub fn ListNode(comptime T: type) type { return struct { const Self = @This(); val: T = 0, next: ?*Self = null, // Initialize a list node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.next = null; } }; } // Десериализовать список в связный список pub fn listToLinkedList(comptime T: type, allocator: std.mem.Allocator, list: std.ArrayList(T)) !?*ListNode(T) { var dum = try allocator.create(ListNode(T)); dum.init(0); var head = dum; for (list.items) |val| { var tmp = try allocator.create(ListNode(T)); tmp.init(val); head.next = tmp; head = head.next.?; } return dum.next; } // Десериализовать массив в связный список pub fn arrToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*ListNode(T) { var dum = try mem_allocator.create(ListNode(T)); dum.init(0); var head = dum; for (arr) |val| { var tmp = try mem_allocator.create(ListNode(T)); tmp.init(val); head.next = tmp; head = head.next.?; } return dum.next; } ================================================ FILE: ru/codes/zig/utils/TreeNode.zig ================================================ // File: TreeNode.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // Узел двоичного дерева pub fn TreeNode(comptime T: type) type { return struct { const Self = @This(); val: T = undefined, // Значение узла height: i32 = undefined, // Высота узла left: ?*Self = null, // Указатель на левый дочерний узел right: ?*Self = null, // Указатель на правый дочерний узел // Initialize a tree node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.height = 0; self.left = null; self.right = null; } }; } // Десериализовать массив в двоичное дерево pub fn arrToTree(comptime T: type, allocator: std.mem.Allocator, arr: []T) !?*TreeNode(T) { if (arr.len == 0) return null; var root = try allocator.create(TreeNode(T)); root.init(arr[0]); const L = std.TailQueue(*TreeNode(T)); var que = L{}; var root_node = try allocator.create(L.Node); root_node.data = root; que.append(root_node); var index: usize = 0; while (que.len > 0) { const que_node = que.popFirst().?; var node = que_node.data; index += 1; if (index >= arr.len) break; if (index < arr.len) { var tmp = try allocator.create(TreeNode(T)); tmp.init(arr[index]); node.left = tmp; var tmp_node = try allocator.create(L.Node); tmp_node.data = node.left.?; que.append(tmp_node); } index += 1; if (index >= arr.len) break; if (index < arr.len) { var tmp = try allocator.create(TreeNode(T)); tmp.init(arr[index]); node.right = tmp; var tmp_node = try allocator.create(L.Node); tmp_node.data = node.right.?; que.append(tmp_node); } } return root; } ================================================ FILE: ru/codes/zig/utils/format.zig ================================================ // File: format.zig // Created Time: 2025-07-19 // Author: CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const ListNode = @import("ListNode.zig").ListNode; const TreeNode = @import("TreeNode.zig").TreeNode; pub fn slice(items: anytype) SliceFormatter(@TypeOf(items)) { return .{ .items = items }; } pub fn SliceFormatter(comptime SliceType: type) type { return struct { const Self = @This(); items: SliceType, pub fn format( self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) !void { try writer.writeAll("["); if (self.items.len > 0) { for (self.items, 0..) |item, i| { try std.fmt.format(writer, "{}", .{item}); if (i != self.items.len - 1) { try writer.writeAll(", "); } } } try writer.writeAll("]"); } }; } pub fn linkedList(comptime T: type, head: *const ListNode(T)) LinkedListFormatter(T) { return .{ .head = head }; } pub fn LinkedListFormatter(comptime T: type) type { return struct { const Self = @This(); head: *const ListNode(T), pub fn format( self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) !void { try printLinkedList(self.head, writer); } pub fn printLinkedList(head: *const ListNode(T), writer: anytype) !void { try std.fmt.format(writer, "{}", .{head.val}); if (head.next) |next_node| { try writer.writeAll("->"); try printLinkedList(next_node, writer); } } }; } pub fn tree(comptime T: type, root: ?*const TreeNode(T)) TreeFormatter(T) { return .{ .root = root }; } pub fn TreeFormatter(comptime T: type) type { return struct { const Self = @This(); root: ?*const TreeNode(T), pub fn format( self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) !void { try printTree(self.root, null, false, writer); } // Вывести двоичное дерево fn printTree(root: ?*const TreeNode(T), prev: ?*Trunk, isRight: bool, writer: anytype) !void { if (root == null) { return; } var prev_str = " "; var trunk = Trunk{ .prev = prev, .str = prev_str }; try printTree(root.?.right, &trunk, true, writer); if (prev == null) { trunk.str = "———"; } else if (isRight) { trunk.str = "/———"; prev_str = " |"; } else { trunk.str = "\\———"; prev.?.str = prev_str; } try showTrunks(&trunk, writer); try std.fmt.format(writer, "{d}\n", .{root.?.val}); if (prev) |_| { prev.?.str = prev_str; } trunk.str = " |"; try printTree(root.?.left, &trunk, false, writer); } // Вывести двоичное дерево // Этот вывод дерева заимствован из TECHIE DELIGHT // https://www.techiedelight.com/c-program-print-binary-tree/ const Trunk = struct { prev: ?*Trunk = null, str: []const u8 = undefined, pub fn init(self: *Trunk, prev: ?*Trunk, str: []const u8) void { self.prev = prev; self.str = str; } }; pub fn showTrunks(p: ?*Trunk, writer: anytype) !void { if (p == null) return; try showTrunks(p.?.prev, writer); try std.fmt.format(writer, "{s}", .{p.?.str}); } }; } ================================================ FILE: ru/codes/zig/utils/utils.zig ================================================ // File: format.zig // Created Time: 2025-07-15 // Author: CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); pub const fmt = @import("format.zig"); pub const ListNode = @import("ListNode.zig").ListNode; pub const TreeNode = @import("TreeNode.zig").TreeNode; ================================================ FILE: ru/docs/chapter_appendix/contribution.md ================================================ # Присоединяйтесь к созданию книги Возможности автора ограничены, поэтому в книге неизбежно могут встречаться упущения и ошибки. Просим отнестись к этому с пониманием. Если вы заметите опечатки, неработающие ссылки, пропуски в содержании, двусмысленные формулировки, неясные объяснения или неудачную структуру изложения, пожалуйста, помогите нам это исправить, чтобы читатели получили более качественный учебный ресурс. Все GitHub ID [авторов](https://github.com/krahets/hello-algo/graphs/contributors) будут указаны на главных страницах репозитория книги, веб-версии и PDF-версии в знак благодарности за их бескорыстный вклад в сообщество открытого исходного кода. !!! success "Сила открытого исходного кода" Интервал между двумя тиражами бумажной книги обычно довольно велик, поэтому обновлять содержание очень неудобно. В этой же открытой книге цикл обновления содержания сокращается до нескольких дней, а иногда даже до нескольких часов. ### Небольшие правки содержания Как показано на рисунке ниже, в правом верхнем углу каждой страницы есть "значок редактирования". Текст или код можно изменить следующим образом. 1. Нажмите на "значок редактирования". Если появится сообщение "You need to fork this repository", согласитесь с этим действием. 2. Измените содержимое исходного Markdown-файла, проверьте корректность правок и постарайтесь сохранить единый стиль оформления. 3. Внизу страницы заполните описание изменений, затем нажмите кнопку "Propose file change". После перехода на следующую страницу нажмите кнопку "Create pull request", чтобы отправить pull request. ![Кнопка редактирования страницы](contribution.assets/edit_markdown.png) Изображения нельзя изменить напрямую, поэтому проблему с ними нужно описывать через новый [Issue](https://github.com/krahets/hello-algo/issues) или комментарий. Мы постараемся как можно быстрее исправить и обновить изображение. ### Создание содержания Если вам интересно участвовать в этом проекте с открытым исходным кодом, например переводить код на другие языки программирования или расширять содержание статей, то следует придерживаться следующего процесса Pull Request. 1. Войдите в GitHub и сделайте Fork [репозитория книги](https://github.com/krahets/hello-algo) в свой личный аккаунт. 2. Перейдите на страницу своего Fork-репозитория и с помощью команды `git clone` клонируйте репозиторий локально. 3. Создавайте и редактируйте содержание локально, затем проведите полное тестирование и проверьте корректность кода. 4. Зафиксируйте локальные изменения, после чего выполните Push в удаленный репозиторий. 5. Обновите страницу репозитория и нажмите кнопку "Create pull request", чтобы инициировать pull request. ### Развертывание Docker В корневом каталоге `hello-algo` выполните следующий Docker-скрипт, после чего проект станет доступен по адресу `http://localhost:8000`: ```shell docker-compose up -d ``` Удалить развертывание можно следующей командой: ```shell docker-compose down ``` ================================================ FILE: ru/docs/chapter_appendix/index.md ================================================ # Приложение ![Приложение](../assets/covers/chapter_appendix.jpg) ================================================ FILE: ru/docs/chapter_appendix/installation.md ================================================ # Установка среды программирования ## Установка IDE В качестве локальной интегрированной среды разработки (IDE) рекомендуется использовать открытую и быструю VS Code. Перейдите на [официальный сайт VS Code](https://code.visualstudio.com/), выберите версию для своей операционной системы и установите ее. ![Загрузка VS Code с официального сайта](installation.assets/vscode_installation.png) VS Code обладает мощной экосистемой расширений и поддерживает выполнение и отладку большинства языков программирования. Например, после установки расширения "Python Extension Pack" можно отлаживать код на Python. Процесс установки показан на рисунке ниже. ![Установка расширений VS Code](installation.assets/vscode_extension_installation.png) ## Установка языковой среды ### Среда Python 1. Загрузите и установите [Miniconda3](https://docs.conda.io/en/latest/miniconda.html), требуется Python 3.10 или более поздняя версия. 2. В магазине расширений VS Code найдите `python` и установите Python Extension Pack. 3. (Необязательно) Введите в командной строке `pip install black`, чтобы установить инструмент форматирования кода. ### Среда C/C++ 1. В Windows требуется установить [MinGW](https://sourceforge.net/projects/mingw-w64/files/) ([руководство по настройке](https://blog.csdn.net/qq_33698226/article/details/129031241)); в macOS компилятор Clang уже установлен по умолчанию. 2. В магазине расширений VS Code найдите `c++` и установите C/C++ Extension Pack. 3. (Необязательно) Откройте страницу Settings, найдите параметр форматирования `Clang_format_fallback Style` и задайте значение `{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }`. ### Среда Java 1. Загрузите и установите [OpenJDK](https://jdk.java.net/18/) (требуемая версия: > JDK 9). 2. В магазине расширений VS Code найдите `java` и установите Extension Pack for Java. ### Среда C# 1. Загрузите и установите [.Net 8.0](https://dotnet.microsoft.com/en-us/download). 2. В магазине расширений VS Code найдите `C# Dev Kit` и установите C# Dev Kit ([руководство по настройке](https://code.visualstudio.com/docs/csharp/get-started)). 3. Также можно использовать Visual Studio ([руководство по установке](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022)). ### Среда Go 1. Загрузите и установите [go](https://go.dev/dl/). 2. В магазине расширений VS Code найдите `go` и установите Go. 3. Нажмите `Ctrl + Shift + P`, чтобы открыть командную палитру, введите `go`, выберите `Go: Install/Update Tools`, отметьте все инструменты и установите их. ### Среда Swift 1. Загрузите и установите [Swift](https://www.swift.org/download/). 2. В магазине расширений VS Code найдите `swift` и установите [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang). ### Среда JavaScript 1. Загрузите и установите [Node.js](https://nodejs.org/en/). 2. (Необязательно) В магазине расширений VS Code найдите `Prettier` и установите инструмент форматирования кода. ### Среда TypeScript 1. Выполните те же шаги, что и для среды JavaScript. 2. Установите [TypeScript Execute (tsx)](https://github.com/privatenumber/tsx?tab=readme-ov-file#global-installation). 3. В магазине расширений VS Code найдите `typescript` и установите [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors). ### Среда Dart 1. Загрузите и установите [Dart](https://dart.dev/get-dart). 2. В магазине расширений VS Code найдите `dart` и установите [Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code). ### Среда Rust 1. Загрузите и установите [Rust](https://www.rust-lang.org/tools/install). 2. В магазине расширений VS Code найдите `rust` и установите [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). ================================================ FILE: ru/docs/chapter_appendix/terminology.md ================================================ # Глоссарий В таблице ниже приведены важные термины, встречающиеся в книге. Обратите внимание на следующие моменты. - Рекомендуем запомнить английские названия терминов, чтобы легче читать англоязычные материалы. - В русской версии для каждого термина приводится единый рекомендуемый перевод.

Таблица   Важные термины по структурам данных и алгоритмам

| English | Русский | | ------------------------------ | ------------------------------ | | algorithm | алгоритм | | data structure | структура данных | | code | код | | file | файл | | function | функция | | method | метод | | variable | переменная | | asymptotic complexity analysis | асимптотический анализ сложности | | time complexity | временная сложность | | space complexity | пространственная сложность | | loop | цикл | | iteration | итерация | | recursion | рекурсия | | tail recursion | хвостовая рекурсия | | recursion tree | дерево рекурсии | | big-$O$ notation | нотация big-$O$ | | asymptotic upper bound | асимптотическая верхняя граница | | sign-magnitude | прямой код | | 1’s complement | обратный код | | 2’s complement | дополнительный код | | array | массив | | index | индекс | | linked list | связный список | | linked list node, list node | узел связного списка | | head node | головной узел | | tail node | хвостовой узел | | list | список | | dynamic array | динамический массив | | hard disk | жесткий диск | | random-access memory (RAM) | оперативная память | | cache memory | кеш-память | | cache miss | промах кеша | | cache hit rate | коэффициент попадания в кеш | | stack | стек | | top of the stack | вершина стека | | bottom of the stack | основание стека | | queue | очередь | | double-ended queue | двусторонняя очередь | | front of the queue | голова очереди | | rear of the queue | хвост очереди | | hash table | хеш-таблица | | hash set | хеш-набор | | bucket | бакет | | hash function | хеш-функция | | hash collision | хеш-коллизия | | load factor | коэффициент заполнения | | separate chaining | цепная адресация | | open addressing | открытая адресация | | linear probing | линейное зондирование | | lazy deletion | ленивое удаление | | binary tree | двоичное дерево | | tree node | узел дерева | | left-child node | левый дочерний узел | | right-child node | правый дочерний узел | | parent node | родительский узел | | left subtree | левое поддерево | | right subtree | правое поддерево | | root node | корневой узел | | leaf node | листовой узел | | edge | ребро | | level | уровень | | degree | степень | | height | высота | | depth | глубина | | perfect binary tree | идеальное двоичное дерево | | complete binary tree | полное двоичное дерево | | full binary tree | строгое двоичное дерево | | balanced binary tree | сбалансированное двоичное дерево | | binary search tree | двоичное дерево поиска | | AVL tree | АВЛ-дерево | | red-black tree | красно-черное дерево | | level-order traversal | обход по уровням | | breadth-first traversal | обход в ширину | | depth-first traversal | обход в глубину | | pre-order traversal | прямой обход | | in-order traversal | симметричный обход | | post-order traversal | обратный обход | | balanced binary search tree | сбалансированное двоичное дерево поиска | | balance factor | фактор баланса | | heap | куча | | max heap | максимальная куча | | min heap | минимальная куча | | priority queue | приоритетная очередь | | heapify | упорядочивание кучи | | top-$k$ problem | поиск $k$ наибольших элементов | | graph | граф | | vertex | вершина | | undirected graph | неориентированный граф | | directed graph | ориентированный граф | | connected graph | связный граф | | disconnected graph | несвязный граф | | weighted graph | взвешенный граф | | adjacency | смежность | | path | путь | | in-degree | входящая степень | | out-degree | исходящая степень | | adjacency matrix | матрица смежности | | adjacency list | список смежности | | breadth-first search | поиск в ширину | | depth-first search | поиск в глубину | | binary search | двоичный поиск | | searching algorithm | алгоритм поиска | | sorting algorithm | алгоритм сортировки | | selection sort | сортировка выбором | | bubble sort | сортировка пузырьком | | insertion sort | сортировка вставкой | | quick sort | быстрая сортировка | | merge sort | сортировка слиянием | | heap sort | пирамидальная сортировка | | bucket sort | блочная сортировка | | counting sort | сортировка подсчетом | | radix sort | поразрядная сортировка | | divide and conquer | разделяй и властвуй | | hanota problem | задача о Ханойской башне | | backtracking algorithm | алгоритм поиска с возвратом | | constraint | ограничение | | solution | решение | | state | состояние | | pruning | отсечение | | permutations problem | задача о перестановках | | subset-sum problem | задача о сумме подмножеств | | $n$-queens problem | задача о $n$ ферзях | | dynamic programming | динамическое программирование | | initial state | начальное состояние | | state-transition equation | уравнение перехода состояния | | knapsack problem | задача о рюкзаке | | edit distance problem | задача о расстоянии редактирования | | greedy algorithm | жадный алгоритм | ================================================ FILE: ru/docs/chapter_array_and_linkedlist/array.md ================================================ # Массив Массив (array) - это линейная структура данных, в которой элементы одного типа хранятся в непрерывной области памяти. Положение элемента в массиве называется его индексом (index). На рисунке ниже показаны основные понятия, связанные с массивом, и способ его хранения. ![Определение массива и способ хранения](array.assets/array_definition.png) ## Основные операции с массивом ### Инициализация массива Существует два способа инициализации массива: без начальных значений и с заданными начальными значениями. Если начальные значения не указаны, большинство языков программирования инициализируют элементы массива нулями: === "Python" ```python title="array.py" # Инициализация массива arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] nums: list[int] = [1, 3, 2, 5, 4] ``` === "C++" ```cpp title="array.cpp" /* Инициализация массива */ // Хранится в стеке int arr[5]; int nums[5] = { 1, 3, 2, 5, 4 }; // Хранится в куче (требуется ручное освобождение памяти) int* arr1 = new int[5]; int* nums1 = new int[5] { 1, 3, 2, 5, 4 }; ``` === "Java" ```java title="array.java" /* Инициализация массива */ int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } int[] nums = { 1, 3, 2, 5, 4 }; ``` === "C#" ```csharp title="array.cs" /* Инициализация массива */ int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ] int[] nums = [1, 3, 2, 5, 4]; ``` === "Go" ```go title="array.go" /* Инициализация массива */ var arr [5]int // В Go указание длины ([5]int) создает массив, а отсутствие длины ([]int) - срез // Поскольку длина массива в Go определяется на этапе компиляции, для задания длины можно использовать только константы // Чтобы упростить реализацию метода extend(), ниже будем рассматривать срезы (Slice) как массивы (Array) nums := []int{1, 3, 2, 5, 4} ``` === "Swift" ```swift title="array.swift" /* Инициализация массива */ let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] let nums = [1, 3, 2, 5, 4] ``` === "JS" ```javascript title="array.js" /* Инициализация массива */ var arr = new Array(5).fill(0); var nums = [1, 3, 2, 5, 4]; ``` === "TS" ```typescript title="array.ts" /* Инициализация массива */ let arr: number[] = new Array(5).fill(0); let nums: number[] = [1, 3, 2, 5, 4]; ``` === "Dart" ```dart title="array.dart" /* Инициализация массива */ List arr = List.filled(5, 0); // [0, 0, 0, 0, 0] List nums = [1, 3, 2, 5, 4]; ``` === "Rust" ```rust title="array.rs" /* Инициализация массива */ let arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0] let slice: &[i32] = &[0; 5]; // В Rust указание длины ([i32; 5]) создает массив, а отсутствие длины (&[i32]) - срез // Поскольку длина массива в Rust определяется на этапе компиляции, для задания длины можно использовать только константы // Vector в Rust обычно используется как динамический массив // Чтобы упростить реализацию метода extend(), ниже будем рассматривать vector как массив (array) let nums: Vec = vec![1, 3, 2, 5, 4]; ``` === "C" ```c title="array.c" /* Инициализация массива */ int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 } int nums[5] = { 1, 3, 2, 5, 4 }; ``` === "Kotlin" ```kotlin title="array.kt" /* Инициализация массива */ var arr = IntArray(5) // { 0, 0, 0, 0, 0 } var nums = intArrayOf(1, 3, 2, 5, 4) ``` === "Ruby" ```ruby title="array.rb" # Инициализация массива arr = Array.new(5, 0) nums = [1, 3, 2, 5, 4] ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%0Aarr%20%3D%20%5B0%5D%20%2A%205%20%20%23%20%5B%200%2C%200%2C%200%2C%200%2C%200%20%5D%0Anums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Доступ к элементам Элементы массива хранятся в непрерывной области памяти, что упрощает вычисление их адресов. Зная адрес массива в памяти (то есть адрес первого элемента) и индекс некоторого элемента, мы можем по формуле с рисунка ниже вычислить адрес этого элемента и напрямую обратиться к нему. ![Вычисление адреса элемента массива](array.assets/array_memory_location_calculation.png) Если посмотреть на рисунок выше, можно заметить, что индекс первого элемента массива равен $0$ , и это кажется не слишком интуитивным, ведь естественнее было бы начинать счет с $1$ . Однако с точки зрения формулы адресации **индекс по сути является смещением относительно адреса памяти**. Смещение первого элемента равно $0$ , поэтому индекс $0$ полностью логичен. Доступ к элементам массива очень эффективен: любой элемент массива можно получить за $O(1)$ времени. ```src [file]{array}-[class]{}-[func]{random_access} ``` ### Вставка элемента Элементы массива в памяти расположены вплотную друг к другу, и между ними нет места для размещения новых данных. Как показано на рисунке ниже, если мы хотим вставить элемент в середину массива, то все элементы после этой позиции нужно сдвинуть на одну позицию вправо, а затем записать новое значение в освободившийся индекс. ![Пример вставки элемента в массив](array.assets/array_insert_element.png) Стоит отметить, что длина массива фиксирована, поэтому вставка нового элемента неизбежно приведет к потере элемента на конце массива. Решение этой проблемы мы оставим для обсуждения в разделе о "списках". ```src [file]{array}-[class]{}-[func]{insert} ``` ### Удаление элемента Аналогично, как показано на рисунке ниже, если нужно удалить элемент по индексу $i$ , то все элементы после индекса $i$ необходимо сдвинуть на одну позицию влево. ![Пример удаления элемента из массива](array.assets/array_remove_element.png) Обрати внимание: после удаления исходный последний элемент становится бессмысленным, поэтому специально изменять его не требуется. ```src [file]{array}-[class]{}-[func]{remove} ``` В целом операции вставки и удаления в массиве имеют следующие недостатки. - **Высокая временная сложность**: средняя временная сложность и вставки, и удаления равна $O(n)$ , где $n$ - длина массива. - **Потеря элементов**: поскольку длина массива неизменяема, после вставки элементы, выходящие за пределы длины массива, будут потеряны. - **Потери памяти**: можно заранее инициализировать более длинный массив и использовать только его переднюю часть; тогда теряемые при вставке элементы на конце не будут нести смысла, но такой подход приводит к лишнему расходу памяти. ### Обход массива В большинстве языков программирования массив можно обходить как по индексу, так и напрямую перебирая каждый элемент: ```src [file]{array}-[class]{}-[func]{traverse} ``` ### Поиск элемента Чтобы найти заданный элемент в массиве, нужно пройти по массиву и на каждой итерации проверять, совпадает ли значение; если совпадает, вернуть соответствующий индекс. Поскольку массив - это линейная структура данных, такая операция поиска называется линейным поиском. ```src [file]{array}-[class]{}-[func]{find} ``` ### Расширение массива В сложной системной среде программа не может гарантировать, что память сразу после массива доступна, поэтому безопасно расширить емкость массива невозможно. Поэтому в большинстве языков программирования **длина массива неизменяема**. Если мы хотим расширить массив, нужно заново создать больший массив и затем по одному скопировать в него элементы исходного массива. Это операция с временной сложностью $O(n)$ , и при больших массивах она очень затратна. Соответствующий код показан ниже: ```src [file]{array}-[class]{}-[func]{extend} ``` ## Преимущества и ограничения массива Массив хранится в непрерывной области памяти, и все его элементы имеют один и тот же тип. Такой подход содержит богатую априорную информацию, которую система может использовать для оптимизации эффективности операций с этой структурой данных. - **Высокая пространственная эффективность**: массив выделяет для данных непрерывный блок памяти без дополнительного структурного накладного расхода. - **Поддержка произвольного доступа**: массив позволяет обращаться к любому элементу за $O(1)$ времени. - **Локальность кэша**: при обращении к элементу массива компьютер загружает не только сам элемент, но и соседние данные, что позволяет использовать кэш для ускорения последующих операций. Непрерывное хранение данных - это палка о двух концах, и у него есть следующие ограничения. - **Низкая эффективность вставки и удаления**: когда элементов в массиве много, вставка и удаление требуют сдвига большого количества элементов. - **Неизменяемая длина**: после инициализации длина массива фиксирована; расширение массива требует копирования всех данных в новый массив, что стоит дорого. - **Потери памяти**: если выделенный массив больше, чем реально необходимо, лишнее пространство пропадает впустую. ## Типичные применения массива Массив - это базовая и очень распространенная структура данных. Он часто используется как в различных алгоритмах, так и при реализации более сложных структур данных. - **Произвольный доступ**: если мы хотим случайным образом выбирать некоторые образцы, можно сохранить их в массиве и сгенерировать случайную последовательность индексов для выборки. - **Сортировка и поиск**: массив - самая распространенная структура данных для алгоритмов сортировки и поиска. Быстрая сортировка, сортировка слиянием, двоичный поиск и многие другие алгоритмы в основном работают именно с массивами. - **Таблица поиска**: когда нужно быстро находить элемент или его соответствие, массив можно использовать как таблицу поиска. Например, если мы хотим реализовать отображение символов в коды ASCII, можно использовать значение ASCII как индекс, а соответствующий элемент хранить по этой позиции массива. - **Машинное обучение**: в нейронных сетях широко используются операции линейной алгебры над векторами, матрицами и тензорами, и все эти данные строятся в форме массивов. Массив - самая часто используемая структура данных в программировании нейросетей. - **Реализация структур данных**: массивы можно использовать для реализации стеков, очередей, хеш-таблиц, куч, графов и других структур данных. Например, матрица смежности графа по сути является двумерным массивом. ================================================ FILE: ru/docs/chapter_array_and_linkedlist/index.md ================================================ # Массивы и списки ![Массивы и списки](../assets/covers/chapter_array_and_linkedlist.jpg) !!! abstract Мир структур данных напоминает прочную кирпичную стену. Кирпичи массива уложены ровно и плотно прилегают друг к другу. Узлы связного списка, напротив, разбросаны в разных местах, а соединяющие их связи свободно тянутся между промежутками. ================================================ FILE: ru/docs/chapter_array_and_linkedlist/linked_list.md ================================================ # Связный список Память - общий ресурс для всех программ, и в сложной среде выполнения свободные участки памяти могут быть разбросаны по всему адресному пространству. Мы знаем, что память для хранения массива должна быть непрерывной, а если массив очень велик, в памяти может не оказаться столь большого непрерывного блока. Именно здесь и проявляется преимущество гибкости связного списка. Связный список (linked list) - это линейная структура данных, в которой каждый элемент представляет собой объект-узел, а сами узлы соединены между собой с помощью ссылок. Ссылка хранит адрес памяти следующего узла, благодаря чему из текущего узла можно перейти к следующему. Конструкция связного списка позволяет хранить отдельные узлы в разных местах памяти, и их адреса вовсе не обязаны быть последовательными. ![Определение связного списка и способ хранения](linked_list.assets/linkedlist_definition.png) Как видно на рисунке выше, базовой единицей связного списка является объект узел (node). Каждый узел содержит две части данных: значение узла и ссылку на следующий узел. - Первый узел связного списка называется головным узлом, а последний - хвостовым узлом. - Хвостовой узел указывает на пустое значение, что в Java, C++ и Python обозначается как `null` , `nullptr` и `None` соответственно. - В языках, поддерживающих указатели, таких как C, C++, Go и Rust, упомянутую выше ссылку следует заменить на указатель. Как показано в коде ниже, узел связного списка `ListNode` хранит не только значение, но и дополнительную ссылку (указатель). Поэтому **при одинаковом объеме данных связный список занимает больше памяти, чем массив**. === "Python" ```python title="" class ListNode: """Класс узла связного списка""" def __init__(self, val: int): self.val: int = val # Значение узла self.next: ListNode | None = None # Ссылка на следующий узел ``` === "C++" ```cpp title="" /* Структура узла связного списка */ struct ListNode { int val; // Значение узла ListNode *next; // Указатель на следующий узел ListNode(int x) : val(x), next(nullptr) {} // Конструктор }; ``` === "Java" ```java title="" /* Класс узла связного списка */ class ListNode { int val; // Значение узла ListNode next; // Ссылка на следующий узел ListNode(int x) { val = x; } // Конструктор } ``` === "C#" ```csharp title="" /* Класс узла связного списка */ class ListNode(int x) { // Конструктор int val = x; // Значение узла ListNode? next; // Ссылка на следующий узел } ``` === "Go" ```go title="" /* Структура узла связного списка */ type ListNode struct { Val int // Значение узла Next *ListNode // Указатель на следующий узел } // NewListNode Конструктор, создает новый узел func NewListNode(val int) *ListNode { return &ListNode{ Val: val, Next: nil, } } ``` === "Swift" ```swift title="" /* Класс узла связного списка */ class ListNode { var val: Int // Значение узла var next: ListNode? // Ссылка на следующий узел init(x: Int) { // Конструктор val = x } } ``` === "JS" ```javascript title="" /* Класс узла связного списка */ class ListNode { constructor(val, next) { this.val = (val === undefined ? 0 : val); // Значение узла this.next = (next === undefined ? null : next); // Ссылка на следующий узел } } ``` === "TS" ```typescript title="" /* Класс узла связного списка */ class ListNode { val: number; next: ListNode | null; constructor(val?: number, next?: ListNode | null) { this.val = val === undefined ? 0 : val; // Значение узла this.next = next === undefined ? null : next; // Ссылка на следующий узел } } ``` === "Dart" ```dart title="" /* Класс узла связного списка */ class ListNode { int val; // Значение узла ListNode? next; // Ссылка на следующий узел ListNode(this.val, [this.next]); // Конструктор } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* Класс узла связного списка */ #[derive(Debug)] struct ListNode { val: i32, // Значение узла next: Option>>, // Указатель на следующий узел } ``` === "C" ```c title="" /* Структура узла связного списка */ typedef struct ListNode { int val; // Значение узла struct ListNode *next; // Указатель на следующий узел } ListNode; /* Конструктор */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *) malloc(sizeof(ListNode)); node->val = val; node->next = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* Класс узла связного списка */ // Конструктор class ListNode(x: Int) { val _val: Int = x // Значение узла val next: ListNode? = null // Ссылка на следующий узел } ``` === "Ruby" ```ruby title="" # Класс узла связного списка class ListNode attr_accessor :val # Значение узла attr_accessor :next # Ссылка на следующий узел def initialize(val=0, next_node=nil) @val = val @next = next_node end end ``` ## Основные операции со связным списком ### Инициализация связного списка Построение связного списка состоит из двух шагов: сначала нужно инициализировать объекты всех узлов, затем установить ссылочные связи между ними. После завершения инициализации мы можем, начиная с головы списка, последовательно проходить все узлы по ссылке `next`. === "Python" ```python title="linked_list.py" # Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 # Инициализация отдельных узлов 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 ``` === "C++" ```cpp title="linked_list.cpp" /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ // Инициализация отдельных узлов 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; ``` === "Java" ```java title="linked_list.java" /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ // Инициализация отдельных узлов 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; ``` === "C#" ```csharp title="linked_list.cs" /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ // Инициализация отдельных узлов ListNode n0 = new(1); ListNode n1 = new(3); ListNode n2 = new(2); ListNode n3 = new(5); ListNode n4 = new(4); // Построение ссылок между узлами n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Go" ```go title="linked_list.go" /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ // Инициализация отдельных узлов n0 := NewListNode(1) n1 := NewListNode(3) n2 := NewListNode(2) n3 := NewListNode(5) n4 := NewListNode(4) // Построение ссылок между узлами n0.Next = n1 n1.Next = n2 n2.Next = n3 n3.Next = n4 ``` === "Swift" ```swift title="linked_list.swift" /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ // Инициализация отдельных узлов let n0 = ListNode(x: 1) let n1 = ListNode(x: 3) let n2 = ListNode(x: 2) let n3 = ListNode(x: 5) let n4 = ListNode(x: 4) // Построение ссылок между узлами n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 ``` === "JS" ```javascript title="linked_list.js" /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ // Инициализация отдельных узлов const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // Построение ссылок между узлами n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "TS" ```typescript title="linked_list.ts" /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ // Инициализация отдельных узлов const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // Построение ссылок между узлами n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Dart" ```dart title="linked_list.dart" /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */\ // Инициализация отдельных узлов ListNode n0 = ListNode(1); ListNode n1 = ListNode(3); ListNode n2 = ListNode(2); ListNode n3 = ListNode(5); ListNode n4 = ListNode(4); // Построение ссылок между узлами n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Rust" ```rust title="linked_list.rs" /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ // Инициализация отдельных узлов let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None })); let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None })); let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None })); let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None })); let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None })); // Построение ссылок между узлами n0.borrow_mut().next = Some(n1.clone()); n1.borrow_mut().next = Some(n2.clone()); n2.borrow_mut().next = Some(n3.clone()); n3.borrow_mut().next = Some(n4.clone()); ``` === "C" ```c title="linked_list.c" /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ // Инициализация отдельных узлов ListNode* n0 = newListNode(1); ListNode* n1 = newListNode(3); ListNode* n2 = newListNode(2); ListNode* n3 = newListNode(5); ListNode* n4 = newListNode(4); // Построение ссылок между узлами n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; ``` === "Kotlin" ```kotlin title="linked_list.kt" /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ // Инициализация отдельных узлов val n0 = ListNode(1) val n1 = ListNode(3) val n2 = ListNode(2) val n3 = ListNode(5) val n4 = ListNode(4) // Построение ссылок между узлами n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Ruby" ```ruby title="linked_list.rb" # Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 # Инициализация отдельных узлов n0 = ListNode.new(1) n1 = ListNode.new(3) n2 = ListNode.new(2) n3 = ListNode.new(5) n4 = ListNode.new(4) # Построение ссылок между узлами n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D1%8B%D0%B9%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%D1%83%D0%B7%D0%B5%D0%BB%D0%BA%D0%BB%D0%B0%D1%81%D1%81%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D1%8B%D0%B9%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%201%20-%3E%203%20-%3E%202%20-%3E%205%20-%3E%204%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BA%D0%B0%D0%B6%D0%B4%D1%8B%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false Массив в целом - это одна переменная: например, массив `nums` содержит элементы `nums[0]` , `nums[1]` и т.д. Связный список же состоит из множества независимых объектов-узлов. **Обычно в качестве обозначения всего связного списка используют головной узел**; например, в приведенном выше коде связный список можно обозначить как `n0` . ### Вставка узла Вставить узел в связный список очень легко. Как показано на рисунке ниже, предположим, что мы хотим вставить новый узел `P` между двумя соседними узлами `n0` и `n1` ; **для этого нужно изменить всего две ссылки (указателя)**, а временная сложность будет равна $O(1)$ . Для сравнения: временная сложность вставки элемента в массив составляет $O(n)$ , и при большом объеме данных это менее эффективно. ![Пример вставки узла в связный список](linked_list.assets/linkedlist_insert_node.png) ```src [file]{linked_list}-[class]{}-[func]{insert} ``` ### Удаление узла Как показано на рисунке ниже, удалить узел из связного списка тоже очень просто: **нужно изменить всего одну ссылку (указатель)**. Стоит отметить, что хотя после завершения операции удаления узел `P` все еще указывает на `n1` , при обходе связного списка до `P` уже нельзя добраться. Это означает, что `P` фактически больше не принадлежит данному списку. ![Удаление узла из связного списка](linked_list.assets/linkedlist_remove_node.png) ```src [file]{linked_list}-[class]{}-[func]{remove} ``` ### Доступ к узлу **Доступ к узлам в связном списке менее эффективен**. Как уже обсуждалось в предыдущем разделе, к любому элементу массива можно обратиться за $O(1)$ времени. Со связным списком это не так: программе нужно начать с головного узла и последовательно двигаться дальше, пока не будет найден целевой узел. То есть для доступа к $i$ -му узлу списка нужно выполнить $i - 1$ итераций, а временная сложность составляет $O(n)$ . ```src [file]{linked_list}-[class]{}-[func]{access} ``` ### Поиск узла Поиск узла заключается в обходе связного списка, нахождении узла со значением `target` и возврате его индекса в списке. Этот процесс тоже относится к линейному поиску. Код выглядит следующим образом: ```src [file]{linked_list}-[class]{}-[func]{find} ``` ## Сравнение массива и связного списка В таблице ниже обобщаются свойства массива и связного списка, а также сравнивается эффективность соответствующих операций. Поскольку они используют противоположные стратегии хранения, их свойства и эффективность операций тоже во многом противоположны.

Таблица   Сравнение эффективности массива и связного списка

| | Массив | Связный список | | ------------- | ---------------------------------------------- | ----------------------- | | Способ хранения | Непрерывная область памяти | Разрозненная область памяти | | Расширение емкости | Длина неизменяема | Гибкое расширение | | Эффективность памяти | Элементы занимают меньше памяти, но возможны потери пространства | Элементы занимают больше памяти | | Доступ к элементу | $O(1)$ | $O(n)$ | | Добавление элемента | $O(n)$ | $O(1)$ | | Удаление элемента | $O(n)$ | $O(1)$ | ## Основные типы связных списков Как показано на рисунке ниже, существует три распространенных типа связных списков. - **Односвязный список**: это обычный связный список, рассмотренный выше. Узел односвязного списка содержит значение и ссылку на следующий узел. Первый узел называется головным, последний - хвостовым, и хвост указывает на `None` . - **Циклический список**: если заставить хвостовой узел односвязного списка указывать на головной, то есть соединить хвост с головой, получится циклический список. В циклическом списке любой узел можно рассматривать как головной. - **Двусвязный список**: по сравнению с односвязным списком двусвязный хранит ссылки в двух направлениях. Определение узла двусвязного списка включает как ссылку на следующий узел, так и ссылку на предыдущий узел. По сравнению с односвязным списком двусвязный более гибок и позволяет обходить список в обе стороны, но за это приходится платить дополнительной памятью. === "Python" ```python title="" class ListNode: """Класс узла двусвязного списка""" def __init__(self, val: int): self.val: int = val # Значение узла self.next: ListNode | None = None # Ссылка на следующий узел self.prev: ListNode | None = None # Ссылка на предыдущий узел ``` === "C++" ```cpp title="" /* Структура узла двусвязного списка */ struct ListNode { int val; // Значение узла ListNode *next; // Указатель на следующий узел ListNode *prev; // Указатель на предыдущий узел ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // Конструктор }; ``` === "Java" ```java title="" /* Класс узла двусвязного списка */ class ListNode { int val; // Значение узла ListNode next; // Ссылка на следующий узел ListNode prev; // Ссылка на предыдущий узел ListNode(int x) { val = x; } // Конструктор } ``` === "C#" ```csharp title="" /* Класс узла двусвязного списка */ class ListNode(int x) { // Конструктор int val = x; // Значение узла ListNode next; // Ссылка на следующий узел ListNode prev; // Ссылка на предыдущий узел } ``` === "Go" ```go title="" /* Структура узла двусвязного списка */ type DoublyListNode struct { Val int // Значение узла Next *DoublyListNode // Указатель на следующий узел Prev *DoublyListNode // Указатель на предыдущий узел } // NewDoublyListNode Инициализация func NewDoublyListNode(val int) *DoublyListNode { return &DoublyListNode{ Val: val, Next: nil, Prev: nil, } } ``` === "Swift" ```swift title="" /* Класс узла двусвязного списка */ class ListNode { var val: Int // Значение узла var next: ListNode? // Ссылка на следующий узел var prev: ListNode? // Ссылка на предыдущий узел init(x: Int) { // Конструктор val = x } } ``` === "JS" ```javascript title="" /* Класс узла двусвязного списка */ class ListNode { constructor(val, next, prev) { this.val = val === undefined ? 0 : val; // Значение узла this.next = next === undefined ? null : next; // Ссылка на следующий узел this.prev = prev === undefined ? null : prev; // Ссылка на предыдущий узел } } ``` === "TS" ```typescript title="" /* Класс узла двусвязного списка */ class ListNode { val: number; next: ListNode | null; prev: ListNode | null; constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) { this.val = val === undefined ? 0 : val; // Значение узла this.next = next === undefined ? null : next; // Ссылка на следующий узел this.prev = prev === undefined ? null : prev; // Ссылка на предыдущий узел } } ``` === "Dart" ```dart title="" /* Класс узла двусвязного списка */ class ListNode { int val; // Значение узла ListNode? next; // Ссылка на следующий узел ListNode? prev; // Ссылка на предыдущий узел ListNode(this.val, [this.next, this.prev]); // Конструктор } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* Тип узла двусвязного списка */ #[derive(Debug)] struct ListNode { val: i32, // Значение узла next: Option>>, // Указатель на следующий узел prev: Option>>, // Указатель на предыдущий узел } /* Конструктор */ impl ListNode { fn new(val: i32) -> Self { ListNode { val, next: None, prev: None, } } } ``` === "C" ```c title="" /* Структура узла двусвязного списка */ typedef struct ListNode { int val; // Значение узла struct ListNode *next; // Указатель на следующий узел struct ListNode *prev; // Указатель на предыдущий узел } ListNode; /* Конструктор */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *) malloc(sizeof(ListNode)); node->val = val; node->next = NULL; node->prev = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* Класс узла двусвязного списка */ // Конструктор class ListNode(x: Int) { val _val: Int = x // Значение узла val next: ListNode? = null // Ссылка на следующий узел val prev: ListNode? = null // Ссылка на предыдущий узел } ``` === "Ruby" ```ruby title="" # Класс узла двусвязного списка class ListNode attr_accessor :val # Значение узла attr_accessor :next # Ссылка на следующий узел attr_accessor :prev # Ссылка на предыдущий узел def initialize(val=0, next_node=nil, prev_node=nil) @val = val @next = next_node @prev = prev_node end end ``` ![Распространенные типы связных списков](linked_list.assets/linkedlist_common_types.png) ## Типичные применения связных списков Односвязные списки обычно используются для реализации стеков, очередей, хеш-таблиц и графов. - **Стеки и очереди**: если операции вставки и удаления выполняются на одном конце связного списка, он проявляет свойства LIFO, соответствующие стеку; если вставка происходит на одном конце, а удаление на другом, он проявляет свойства FIFO, соответствующие очереди. - **Хеш-таблицы**: метод цепочек - один из основных способов разрешения коллизий в хеш-таблицах. В этом подходе все конфликтующие элементы помещаются в связный список. - **Графы**: список смежности - это распространенный способ представления графа, при котором каждой вершине графа соответствует связный список, а каждый элемент этого списка представляет другую вершину, соединенную с данной. Двусвязные списки обычно используются там, где нужен быстрый доступ как к предыдущему, так и к следующему элементу. - **Продвинутые структуры данных**: например, в красно-черных деревьях и B-деревьях нам нужен доступ к родительскому узлу; этого можно добиться, сохранив в узле ссылку на родителя, по аналогии с двусвязным списком. - **История браузера**: когда пользователь в браузере нажимает кнопки "вперед" или "назад", браузеру нужно знать предыдущую и следующую посещенные страницы. Свойства двусвязного списка делают такую операцию простой. - **Алгоритм LRU**: в алгоритмах вытеснения из кэша (LRU) нужно быстро находить наименее недавно использованные данные, а также быстро добавлять и удалять узлы. Для этого двусвязный список подходит очень хорошо. Циклические списки часто применяются в сценариях, требующих циклических операций, например при планировании ресурсов в операционной системе. - **Алгоритм циклического распределения кванта времени**: в операционных системах round-robin scheduling - это распространенный алгоритм планирования CPU, который циклически обходит набор процессов. Каждому процессу выделяется квант времени, и когда он исчерпан, CPU переключается на следующий процесс. Такую циклическую операцию удобно реализовать с помощью кольцевого списка. - **Буферы данных**: в некоторых реализациях буферов данных также могут использоваться циклические списки. Например, в аудио- и видеоплеерах поток данных может делиться на несколько буферных блоков и помещаться в кольцевой список для обеспечения непрерывного воспроизведения. ================================================ FILE: ru/docs/chapter_array_and_linkedlist/list.md ================================================ # Список Список (list) - это абстрактное понятие структуры данных, обозначающее упорядоченную коллекцию элементов, которая поддерживает доступ к элементам, их изменение, добавление, удаление и обход, не требуя от пользователя учитывать ограничения по емкости. Список может быть реализован как на основе связного списка, так и на основе массива. - Связный список естественным образом можно рассматривать как список: он поддерживает операции добавления, удаления, поиска и изменения элементов и может гибко расширяться динамически. - Массив тоже поддерживает операции добавления, удаления, поиска и изменения элементов, но из-за неизменяемости длины его можно считать лишь списком с ограниченной длиной. Когда список реализуется с помощью массива, **неизменяемость длины снижает его практическую полезность**. Причина в том, что мы обычно не можем заранее точно знать, сколько данных нужно хранить, а значит, трудно выбрать подходящую длину списка. Если длина слишком мала, она может не покрыть реальные потребности; если слишком велика, будет зря расходоваться память. Чтобы решить эту проблему, можно использовать динамический массив (dynamic array) для реализации списка. Он сохраняет все преимущества массива и при этом может динамически расширяться во время выполнения программы. На практике **списки из стандартных библиотек многих языков программирования реализованы именно на основе динамических массивов**, например `list` в Python, `ArrayList` в Java, `vector` в C++ и `List` в C#. В дальнейшем обсуждении мы будем считать понятия "список" и "динамический массив" эквивалентными. ## Основные операции со списком ### Инициализация списка Обычно используются два способа инициализации: без начальных значений и с начальными значениями: === "Python" ```python title="list.py" # Инициализация списка # Без начальных значений nums1: list[int] = [] # С начальными значениями nums: list[int] = [1, 3, 2, 5, 4] ``` === "C++" ```cpp title="list.cpp" /* Инициализация списка */ // Обрати внимание: в C++ vector соответствует описываемому здесь nums // Без начальных значений vector nums1; // С начальными значениями vector nums = { 1, 3, 2, 5, 4 }; ``` === "Java" ```java title="list.java" /* Инициализация списка */ // Без начальных значений List nums1 = new ArrayList<>(); // С начальными значениями (обрати внимание: элементы массива должны использовать обертку Integer[] вместо int[]) Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; List nums = new ArrayList<>(Arrays.asList(numbers)); ``` === "C#" ```csharp title="list.cs" /* Инициализация списка */ // Без начальных значений List nums1 = []; // С начальными значениями int[] numbers = [1, 3, 2, 5, 4]; List nums = [.. numbers]; ``` === "Go" ```go title="list_test.go" /* Инициализация списка */ // Без начальных значений nums1 := []int{} // С начальными значениями nums := []int{1, 3, 2, 5, 4} ``` === "Swift" ```swift title="list.swift" /* Инициализация списка */ // Без начальных значений let nums1: [Int] = [] // С начальными значениями var nums = [1, 3, 2, 5, 4] ``` === "JS" ```javascript title="list.js" /* Инициализация списка */ // Без начальных значений const nums1 = []; // С начальными значениями const nums = [1, 3, 2, 5, 4]; ``` === "TS" ```typescript title="list.ts" /* Инициализация списка */ // Без начальных значений const nums1: number[] = []; // С начальными значениями const nums: number[] = [1, 3, 2, 5, 4]; ``` === "Dart" ```dart title="list.dart" /* Инициализация списка */ // Без начальных значений List nums1 = []; // С начальными значениями List nums = [1, 3, 2, 5, 4]; ``` === "Rust" ```rust title="list.rs" /* Инициализация списка */ // Без начальных значений let nums1: Vec = Vec::new(); // С начальными значениями let nums: Vec = vec![1, 3, 2, 5, 4]; ``` === "C" ```c title="list.c" // В C нет встроенного динамического массива ``` === "Kotlin" ```kotlin title="list.kt" /* Инициализация списка */ // Без начальных значений var nums1 = listOf() // С начальными значениями var numbers = arrayOf(1, 3, 2, 5, 4) var nums = numbers.toMutableList() ``` === "Ruby" ```ruby title="list.rb" # Инициализация списка # Без начальных значений nums1 = [] # С начальными значениями nums = [1, 3, 2, 5, 4] ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20%23%20%D0%91%D0%B5%D0%B7%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D1%85%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B9%0A%20%20%20%20nums1%20%3D%20%5B%5D%0A%20%20%20%20%23%20%D0%A1%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%BC%D0%B8%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%D0%BC%D0%B8%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Доступ к элементам Поскольку в этом разделе список рассматривается как структура на основе динамического массива, доступ к элементам и их обновление можно выполнять за $O(1)$ времени, что очень эффективно. === "Python" ```python title="list.py" # Доступ к элементу num: int = nums[1] # Доступ к элементу по индексу 1 # Обновление элемента nums[1] = 0 # Обновить элемент по индексу 1 значением 0 ``` === "C++" ```cpp title="list.cpp" /* Доступ к элементу */ int num = nums[1]; // Доступ к элементу по индексу 1 /* Обновление элемента */ nums[1] = 0; // Обновить элемент по индексу 1 значением 0 ``` === "Java" ```java title="list.java" /* Доступ к элементу */ int num = nums.get(1); // Доступ к элементу по индексу 1 /* Обновление элемента */ nums.set(1, 0); // Обновить элемент по индексу 1 значением 0 ``` === "C#" ```csharp title="list.cs" /* Доступ к элементу */ int num = nums[1]; // Доступ к элементу по индексу 1 /* Обновление элемента */ nums[1] = 0; // Обновить элемент по индексу 1 значением 0 ``` === "Go" ```go title="list_test.go" /* Доступ к элементу */ num := nums[1] // Доступ к элементу по индексу 1 /* Обновление элемента */ nums[1] = 0 // Обновить элемент по индексу 1 значением 0 ``` === "Swift" ```swift title="list.swift" /* Доступ к элементу */ let num = nums[1] // Доступ к элементу по индексу 1 /* Обновление элемента */ nums[1] = 0 // Обновить элемент по индексу 1 значением 0 ``` === "JS" ```javascript title="list.js" /* Доступ к элементу */ const num = nums[1]; // Доступ к элементу по индексу 1 /* Обновление элемента */ nums[1] = 0; // Обновить элемент по индексу 1 значением 0 ``` === "TS" ```typescript title="list.ts" /* Доступ к элементу */ const num: number = nums[1]; // Доступ к элементу по индексу 1 /* Обновление элемента */ nums[1] = 0; // Обновить элемент по индексу 1 значением 0 ``` === "Dart" ```dart title="list.dart" /* Доступ к элементу */ int num = nums[1]; // Доступ к элементу по индексу 1 /* Обновление элемента */ nums[1] = 0; // Обновить элемент по индексу 1 значением 0 ``` === "Rust" ```rust title="list.rs" /* Доступ к элементу */ let num: i32 = nums[1]; // Доступ к элементу по индексу 1 /* Обновление элемента */ nums[1] = 0; // Обновить элемент по индексу 1 значением 0 ``` === "C" ```c title="list.c" // В C нет встроенного динамического массива ``` === "Kotlin" ```kotlin title="list.kt" /* Доступ к элементу */ val num = nums[1] // Доступ к элементу по индексу 1 /* Обновление элемента */ nums[1] = 0 // Обновить элемент по индексу 1 значением 0 ``` === "Ruby" ```ruby title="list.rb" # Доступ к элементу num = nums[1] # Доступ к элементу по индексу 1 # Обновление элемента nums[1] = 0 # Обновить элемент по индексу 1 значением 0 ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%0A%20%20%20%20num%20%3D%20nums%5B1%5D%20%20%23%20%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%B8%D1%82%D1%8C%D1%81%D1%8F%20%D0%BA%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%201%20%D0%BF%D0%BE%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%0A%0A%20%20%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%0A%20%20%20%20nums%5B1%5D%20%3D%200%20%20%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%201%20%D0%B4%D0%BE%200&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Вставка и удаление элементов В отличие от массива список позволяет свободно добавлять и удалять элементы. Добавление элемента в конец списка имеет временную сложность $O(1)$ , но операции вставки и удаления по-прежнему имеют ту же эффективность, что и у массива, то есть $O(n)$ . === "Python" ```python title="list.py" # Очистить список nums.clear() # Добавить элементы в конец nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) # Вставить элемент в середину nums.insert(3, 6) # Вставить число 6 по индексу 3 # Удалить элемент nums.pop(3) # Удалить элемент по индексу 3 ``` === "C++" ```cpp title="list.cpp" /* Очистить список */ nums.clear(); /* Добавить элементы в конец */ nums.push_back(1); nums.push_back(3); nums.push_back(2); nums.push_back(5); nums.push_back(4); /* Вставить элемент в середину */ nums.insert(nums.begin() + 3, 6); // Вставить число 6 по индексу 3 /* Удалить элемент */ nums.erase(nums.begin() + 3); // Удалить элемент по индексу 3 ``` === "Java" ```java title="list.java" /* Очистить список */ nums.clear(); /* Добавить элементы в конец */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); /* Вставить элемент в середину */ nums.add(3, 6); // Вставить число 6 по индексу 3 /* Удалить элемент */ nums.remove(3); // Удалить элемент по индексу 3 ``` === "C#" ```csharp title="list.cs" /* Очистить список */ nums.Clear(); /* Добавить элементы в конец */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); /* Вставить элемент в середину */ nums.Insert(3, 6); // Вставить число 6 по индексу 3 /* Удалить элемент */ nums.RemoveAt(3); // Удалить элемент по индексу 3 ``` === "Go" ```go title="list_test.go" /* Очистить список */ nums = nil /* Добавить элементы в конец */ nums = append(nums, 1) nums = append(nums, 3) nums = append(nums, 2) nums = append(nums, 5) nums = append(nums, 4) /* Вставить элемент в середину */ nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // Вставить число 6 по индексу 3 /* Удалить элемент */ nums = append(nums[:3], nums[4:]...) // Удалить элемент по индексу 3 ``` === "Swift" ```swift title="list.swift" /* Очистить список */ nums.removeAll() /* Добавить элементы в конец */ nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) /* Вставить элемент в середину */ nums.insert(6, at: 3) // Вставить число 6 по индексу 3 /* Удалить элемент */ nums.remove(at: 3) // Удалить элемент по индексу 3 ``` === "JS" ```javascript title="list.js" /* Очистить список */ nums.length = 0; /* Добавить элементы в конец */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); /* Вставить элемент в середину */ nums.splice(3, 0, 6); // Вставить число 6 по индексу 3 /* Удалить элемент */ nums.splice(3, 1); // Удалить элемент по индексу 3 ``` === "TS" ```typescript title="list.ts" /* Очистить список */ nums.length = 0; /* Добавить элементы в конец */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); /* Вставить элемент в середину */ nums.splice(3, 0, 6); // Вставить число 6 по индексу 3 /* Удалить элемент */ nums.splice(3, 1); // Удалить элемент по индексу 3 ``` === "Dart" ```dart title="list.dart" /* Очистить список */ nums.clear(); /* Добавить элементы в конец */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); /* Вставить элемент в середину */ nums.insert(3, 6); // Вставить число 6 по индексу 3 /* Удалить элемент */ nums.removeAt(3); // Удалить элемент по индексу 3 ``` === "Rust" ```rust title="list.rs" /* Очистить список */ nums.clear(); /* Добавить элементы в конец */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); /* Вставить элемент в середину */ nums.insert(3, 6); // Вставить число 6 по индексу 3 /* Удалить элемент */ nums.remove(3); // Удалить элемент по индексу 3 ``` === "C" ```c title="list.c" // В C нет встроенного динамического массива ``` === "Kotlin" ```kotlin title="list.kt" /* Очистить список */ nums.clear(); /* Добавить элементы в конец */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); /* Вставить элемент в середину */ nums.add(3, 6); // Вставить число 6 по индексу 3 /* Удалить элемент */ nums.remove(3); // Удалить элемент по индексу 3 ``` === "Ruby" ```ruby title="list.rb" # Очистить список nums.clear # Добавить элементы в конец nums << 1 nums << 3 nums << 2 nums << 5 nums << 4 # Вставить элемент в середину nums.insert(3, 6) # Вставить число 6 по индексу 3 # Удалить элемент nums.delete_at(3) # Удалить элемент по индексу 3 ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%A1%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%BC%D0%B8%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%D0%BC%D0%B8%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D1%87%D0%B8%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20nums.clear%28%29%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BA%D0%BE%D0%BD%D0%B5%D1%86%0A%20%20%20%20nums.append%281%29%0A%20%20%20%20nums.append%283%29%0A%20%20%20%20nums.append%282%29%0A%20%20%20%20nums.append%285%29%0A%20%20%20%20nums.append%284%29%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%83%0A%20%20%20%20nums.insert%283%2C%206%29%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%206%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%203%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%0A%20%20%20%20nums.pop%283%29%20%20%20%20%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%203&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Обход списка Как и массив, список можно обходить как по индексам, так и напрямую по элементам. === "Python" ```python title="list.py" # Обход списка по индексам count = 0 for i in range(len(nums)): count += nums[i] # Прямой обход элементов списка for num in nums: count += num ``` === "C++" ```cpp title="list.cpp" /* Обход списка по индексам */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums[i]; } /* Прямой обход элементов списка */ count = 0; for (int num : nums) { count += num; } ``` === "Java" ```java title="list.java" /* Обход списка по индексам */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums.get(i); } /* Прямой обход элементов списка */ for (int num : nums) { count += num; } ``` === "C#" ```csharp title="list.cs" /* Обход списка по индексам */ int count = 0; for (int i = 0; i < nums.Count; i++) { count += nums[i]; } /* Прямой обход элементов списка */ count = 0; foreach (int num in nums) { count += num; } ``` === "Go" ```go title="list_test.go" /* Обход списка по индексам */ count := 0 for i := 0; i < len(nums); i++ { count += nums[i] } /* Прямой обход элементов списка */ count = 0 for _, num := range nums { count += num } ``` === "Swift" ```swift title="list.swift" /* Обход списка по индексам */ var count = 0 for i in nums.indices { count += nums[i] } /* Прямой обход элементов списка */ count = 0 for num in nums { count += num } ``` === "JS" ```javascript title="list.js" /* Обход списка по индексам */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* Прямой обход элементов списка */ count = 0; for (const num of nums) { count += num; } ``` === "TS" ```typescript title="list.ts" /* Обход списка по индексам */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* Прямой обход элементов списка */ count = 0; for (const num of nums) { count += num; } ``` === "Dart" ```dart title="list.dart" /* Обход списка по индексам */ int count = 0; for (var i = 0; i < nums.length; i++) { count += nums[i]; } /* Прямой обход элементов списка */ count = 0; for (var num in nums) { count += num; } ``` === "Rust" ```rust title="list.rs" // Обход списка по индексам let mut _count = 0; for i in 0..nums.len() { _count += nums[i]; } // Прямой обход элементов списка _count = 0; for num in &nums { _count += num; } ``` === "C" ```c title="list.c" // В C нет встроенного динамического массива ``` === "Kotlin" ```kotlin title="list.kt" /* Обход списка по индексам */ var count = 0 for (i in nums.indices) { count += nums[i] } /* Прямой обход элементов списка */ for (num in nums) { count += num } ``` === "Ruby" ```ruby title="list.rb" # Обход списка по индексам count = 0 for i in 0...nums.length count += nums[i] end # Прямой обход элементов списка count = 0 for num in nums count += num end ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%B1%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D0%B0%D0%BC%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%0A%20%20%20%20%23%20%D0%9D%D0%B5%D0%BF%D0%BE%D1%81%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Конкатенация списков Создав новый список `nums1` , мы можем присоединить его к хвосту исходного списка. === "Python" ```python title="list.py" # Конкатенация двух списков nums1: list[int] = [6, 8, 7, 10, 9] nums += nums1 # Присоединить список nums1 к концу nums ``` === "C++" ```cpp title="list.cpp" /* Конкатенация двух списков */ vector nums1 = { 6, 8, 7, 10, 9 }; // Присоединить список nums1 к концу nums nums.insert(nums.end(), nums1.begin(), nums1.end()); ``` === "Java" ```java title="list.java" /* Конкатенация двух списков */ List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); nums.addAll(nums1); // Присоединить список nums1 к концу nums ``` === "C#" ```csharp title="list.cs" /* Конкатенация двух списков */ List nums1 = [6, 8, 7, 10, 9]; nums.AddRange(nums1); // Присоединить список nums1 к концу nums ``` === "Go" ```go title="list_test.go" /* Конкатенация двух списков */ nums1 := []int{6, 8, 7, 10, 9} nums = append(nums, nums1...) // Присоединить список nums1 к концу nums ``` === "Swift" ```swift title="list.swift" /* Конкатенация двух списков */ let nums1 = [6, 8, 7, 10, 9] nums.append(contentsOf: nums1) // Присоединить список nums1 к концу nums ``` === "JS" ```javascript title="list.js" /* Конкатенация двух списков */ const nums1 = [6, 8, 7, 10, 9]; nums.push(...nums1); // Присоединить список nums1 к концу nums ``` === "TS" ```typescript title="list.ts" /* Конкатенация двух списков */ const nums1: number[] = [6, 8, 7, 10, 9]; nums.push(...nums1); // Присоединить список nums1 к концу nums ``` === "Dart" ```dart title="list.dart" /* Конкатенация двух списков */ List nums1 = [6, 8, 7, 10, 9]; nums.addAll(nums1); // Присоединить список nums1 к концу nums ``` === "Rust" ```rust title="list.rs" /* Конкатенация двух списков */ let nums1: Vec = vec![6, 8, 7, 10, 9]; nums.extend(nums1); ``` === "C" ```c title="list.c" // В C нет встроенного динамического массива ``` === "Kotlin" ```kotlin title="list.kt" /* Конкатенация двух списков */ val nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList() nums.addAll(nums1) // Присоединить список nums1 к концу nums ``` === "Ruby" ```ruby title="list.rb" # Конкатенация двух списков nums1 = [6, 8, 7, 10, 9] nums += nums1 ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%B1%D1%8A%D0%B5%D0%B4%D0%B8%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B4%D0%B2%D0%B0%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20nums1%20%3D%20%5B6%2C%208%2C%207%2C%2010%2C%209%5D%0A%20%20%20%20nums%20%2B%3D%20nums1%20%20%23%20%D0%9F%D1%80%D0%B8%D1%81%D0%BE%D0%B5%D0%B4%D0%B8%D0%BD%D0%B8%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20nums1%20%D0%BA%20nums&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Сортировка списка После сортировки списка мы сможем применять алгоритмы "двоичный поиск" и "два указателя", которые очень часто встречаются в задачах по массивам. === "Python" ```python title="list.py" # Отсортировать список nums.sort() # После сортировки элементы списка идут по возрастанию ``` === "C++" ```cpp title="list.cpp" /* Отсортировать список */ sort(nums.begin(), nums.end()); // После сортировки элементы списка идут по возрастанию ``` === "Java" ```java title="list.java" /* Отсортировать список */ Collections.sort(nums); // После сортировки элементы списка идут по возрастанию ``` === "C#" ```csharp title="list.cs" /* Отсортировать список */ nums.Sort(); // После сортировки элементы списка идут по возрастанию ``` === "Go" ```go title="list_test.go" /* Отсортировать список */ sort.Ints(nums) // После сортировки элементы списка идут по возрастанию ``` === "Swift" ```swift title="list.swift" /* Отсортировать список */ nums.sort() // После сортировки элементы списка идут по возрастанию ``` === "JS" ```javascript title="list.js" /* Отсортировать список */ nums.sort((a, b) => a - b); // После сортировки элементы списка идут по возрастанию ``` === "TS" ```typescript title="list.ts" /* Отсортировать список */ nums.sort((a, b) => a - b); // После сортировки элементы списка идут по возрастанию ``` === "Dart" ```dart title="list.dart" /* Отсортировать список */ nums.sort(); // После сортировки элементы списка идут по возрастанию ``` === "Rust" ```rust title="list.rs" /* Отсортировать список */ nums.sort(); // После сортировки элементы списка идут по возрастанию ``` === "C" ```c title="list.c" // В C нет встроенного динамического массива ``` === "Kotlin" ```kotlin title="list.kt" /* Отсортировать список */ nums.sort() // После сортировки элементы списка идут по возрастанию ``` === "Ruby" ```ruby title="list.rb" # Отсортировать список nums = nums.sort { |a, b| a <=> b } # После сортировки элементы списка идут по возрастанию ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20nums.sort%28%29%20%20%23%20%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%2C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%80%D0%B0%D1%81%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D1%8B%20%D0%BF%D0%BE%20%D0%B2%D0%BE%D0%B7%D1%80%D0%B0%D1%81%D1%82%D0%B0%D0%BD%D0%B8%D1%8E&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## Реализация списка Во многих языках программирования списки встроены в стандартную библиотеку, например в Java, C++, Python и других языках. Их реализация довольно сложна, а настройки параметров тщательно продуманы: начальная емкость, коэффициент расширения и так далее. Если это интересно, стоит заглянуть в исходный код. Чтобы лучше понять принцип работы списка, попробуем реализовать его упрощенную версию, в которой есть три ключевых аспекта проектирования. - **Начальная емкость**: выбрать разумную начальную емкость внутреннего массива. В этом примере мы берем 10. - **Учет количества элементов**: объявить переменную `size` , которая будет хранить текущее число элементов в списке и обновляться в реальном времени при вставке и удалении элементов. С помощью этой переменной можно определять конец списка и понимать, требуется ли расширение. - **Механизм расширения**: если при вставке элементов емкость списка исчерпана, нужно выполнить расширение. Для этого сначала создается больший массив с учетом коэффициента расширения, а затем все элементы текущего массива по порядку переносятся в новый. В этом примере мы считаем, что каждый раз массив расширяется в 2 раза. ```src [file]{my_list}-[class]{my_list}-[func]{} ``` ================================================ FILE: ru/docs/chapter_array_and_linkedlist/ram_and_cache.md ================================================ # Оперативная память и кэш * В первых двух разделах этой главы мы разобрали массивы и связные списки - две базовые и важные структуры данных, которые представляют соответственно непрерывное хранение и разрозненное хранение. На практике **физическая структура во многом определяет, насколько эффективно программа использует память и кэш**, а это, в свою очередь, влияет на общую производительность алгоритма. ## Устройства хранения данных в компьютере В компьютере есть три типа устройств хранения данных: жесткий диск (hard disk) , оперативная память (random-access memory, RAM) и кэш-память (cache memory) . В таблице ниже показаны их различные роли и характеристики в компьютерной системе.

Таблица   Устройства хранения данных в компьютере

| | Жесткий диск | Оперативная память | Кэш | | -------------- | --------------------------------------- | ----------------------------------------- | ------------------------------------------------------- | | Назначение | Долговременное хранение данных, включая ОС, программы, файлы и т.д. | Временное хранение выполняемых программ и обрабатываемых данных | Хранение часто используемых данных и инструкций, уменьшающее число обращений CPU к памяти | | Энергозависимость | Данные не теряются после отключения питания | Данные теряются после отключения питания | Данные теряются после отключения питания | | Емкость | Большая, уровень TB | Меньшая, уровень GB | Очень малая, уровень MB | | Скорость | Низкая, от сотен до тысяч MB/s | Высокая, десятки GB/s | Очень высокая, десятки и сотни GB/s | | Цена | Низкая, единицы валюты за GB | Высокая, десятки и сотни валютных единиц за GB | Очень высокая, входит в стоимость CPU | Компьютерную систему хранения можно представить в виде пирамиды, показанной на рисунке ниже. Чем ближе устройство хранения к вершине пирамиды, тем оно быстрее, тем меньше его емкость и тем выше его стоимость. Такая многоуровневая конструкция возникла не случайно, а стала результатом тщательных инженерных компромиссов. - **Жесткий диск трудно заменить оперативной памятью**. Во-первых, данные в оперативной памяти исчезают после отключения питания, поэтому она не подходит для долговременного хранения. Во-вторых, память стоит в разы дороже жесткого диска, что мешает ее широкому применению. - **Кэш не может одновременно быть и очень большим, и очень быстрым**. По мере роста емкости кэшей L1, L2 и L3 их физический размер увеличивается, расстояние до ядра CPU становится больше, время передачи данных растет, а задержка доступа к элементам увеличивается. При текущем уровне технологий многоуровневая структура кэша является лучшим балансом между емкостью, скоростью и стоимостью. ![Система хранения данных компьютера](ram_and_cache.assets/storage_pyramid.png) !!! tip Иерархия памяти компьютера отражает тонкий баланс между скоростью, емкостью и стоимостью. Подобные компромиссы встречаются почти во всех областях инженерии: приходится искать оптимальный баланс между преимуществами и ограничениями. В итоге **жесткий диск используется для долговременного хранения больших объемов данных, оперативная память - для временного хранения данных, с которыми программа работает прямо сейчас, а кэш - для хранения часто используемых данных и инструкций**, чтобы ускорять выполнение программ. Все три уровня работают совместно и обеспечивают эффективную работу компьютерной системы. Как показано на рисунке ниже, во время выполнения программы данные читаются с жесткого диска в оперативную память, а затем используются CPU в вычислениях. Кэш можно рассматривать как часть CPU: **он подгружает данные из оперативной памяти**, обеспечивая CPU высокоскоростной доступ и тем самым значительно ускоряя выполнение программы и уменьшая зависимость от более медленной RAM. ![Поток данных между жестким диском, RAM и кэшем](ram_and_cache.assets/computer_storage_devices.png) ## Эффективность использования памяти структурами данных С точки зрения использования пространства памяти массивы и связные списки имеют свои преимущества и ограничения. С одной стороны, **память ограничена, и один и тот же участок памяти не может совместно использоваться несколькими программами**, поэтому нам хочется, чтобы структуры данных использовали пространство как можно эффективнее. Элементы массива расположены плотно и не требуют дополнительного места для хранения ссылок (указателей) между узлами списка, поэтому массивы эффективнее по памяти. Однако массиву нужно сразу выделить достаточно большой непрерывный участок памяти, что может приводить к потерям пространства, а его расширение требует дополнительных затрат времени и памяти. Напротив, связные списки выделяют и освобождают память на уровне узлов, что дает большую гибкость. С другой стороны, во время выполнения программы **при многократном выделении и освобождении памяти фрагментация свободной памяти становится все более серьезной**, что снижает эффективность ее использования. Массивы из-за непрерывного хранения относительно менее подвержены фрагментации. Напротив, элементы связного списка распределены по памяти, и частые операции вставки и удаления легче приводят к фрагментации. ## Эффективность использования кэша структурами данных Хотя по объему кэш намного меньше оперативной памяти, он значительно быстрее и играет критически важную роль в скорости выполнения программ. Поскольку объем кэша ограничен и в нем можно хранить только небольшую долю часто используемых данных, когда CPU пытается обратиться к данным, которых в кэше нет, происходит промах кэша (cache miss) , и CPU вынужден загружать нужные данные из более медленной памяти. Очевидно, что **чем меньше промахов кэша, тем выше эффективность чтения и записи данных CPU**, а значит, тем лучше производительность программы. Долю обращений, при которых CPU успешно получает данные из кэша, называют коэффициентом попадания в кэш (cache hit rate) ; этот показатель обычно используют для оценки эффективности кэша. Чтобы добиться как можно большей эффективности, кэш использует следующие механизмы загрузки данных. - **Строки кэша**: кэш хранит и загружает данные не по одному байту, а строками кэша. По сравнению с передачей по байтам это гораздо эффективнее. - **Механизм предвыборки**: процессор старается предсказать шаблон доступа к данным (например последовательный доступ, доступ с фиксированным шагом и т.д.) и на основе этого шаблона заранее загружает данные в кэш, повышая вероятность попадания. - **Пространственная локальность**: если к некоторым данным уже обратились, то велика вероятность, что в ближайшее время понадобятся и соседние данные. Поэтому, загружая некоторые данные, кэш часто подгружает и окружающие их данные. - **Временная локальность**: если к данным уже обратились, то высока вероятность, что к ним снова обратятся в ближайшем будущем. Кэш использует это свойство, сохраняя недавно использованные данные. На практике **массивы и связные списки по-разному используют кэш**, и это проявляется в нескольких аспектах. - **Занимаемое пространство**: элементы связного списка занимают больше места, чем элементы массива, поэтому в кэше помещается меньше полезных данных. - **Строки кэша**: данные списка разбросаны по памяти, а кэш загружает данные строками, поэтому доля бесполезно загружаемых данных оказывается выше. - **Механизм предвыборки**: шаблон доступа к данным у массивов более предсказуем, чем у списков, то есть системе легче угадать, какие данные понадобятся следующими. - **Пространственная локальность**: массив хранится в компактной области памяти, поэтому данные рядом с уже загруженными с большей вероятностью скоро будут использованы. В целом **массивы имеют более высокий коэффициент попадания в кэш, поэтому по эффективности операций они обычно превосходят связные списки**. Именно поэтому при решении алгоритмических задач структуры данных на основе массивов часто оказываются предпочтительнее. Важно понимать, что **высокая эффективность кэша не означает, что массивы во всех случаях лучше связных списков**. В реальных приложениях выбор структуры данных должен определяться конкретными требованиями. Например, и массивы, и списки могут использоваться для реализации "стека" (подробнее об этом будет рассказано в следующей главе), но подходят они для разных сценариев. - При решении алгоритмических задач мы обычно предпочитаем стек на основе массива, потому что он дает более высокую эффективность операций и поддерживает произвольный доступ, а цена за это - необходимость заранее выделить некоторый объем памяти под массив. - Если объем данных очень велик, структура сильно динамична, а ожидаемый размер стека трудно оценить заранее, то более уместен стек на основе связного списка. Список позволяет распределить большой объем данных по разным участкам памяти и избегает накладных расходов, связанных с расширением массива. ================================================ FILE: ru/docs/chapter_array_and_linkedlist/summary.md ================================================ # Резюме ### Ключевые выводы - Массивы и связные списки - это две базовые структуры данных, представляющие два способа хранения данных в памяти компьютера: хранение в непрерывном пространстве и хранение в разрозненном пространстве. Их свойства во многом взаимно дополняют друг друга. - Массив поддерживает произвольный доступ и занимает меньше памяти; однако вставка и удаление элементов в нем неэффективны, а длина после инициализации фиксирована. - Связный список позволяет эффективно вставлять и удалять узлы путем изменения ссылок (указателей), а также гибко менять длину; однако доступ к узлам менее эффективен, а памяти он занимает больше. Распространенные типы списков включают односвязные, циклические и двусвязные списки. - Список - это упорядоченная коллекция элементов, поддерживающая добавление, удаление, поиск и изменение, и обычно реализуемая на основе динамического массива. Он сохраняет преимущества массива и при этом может гибко менять длину. - Появление списка значительно повысило практическую ценность массива, хотя это и может приводить к потере части памяти. - Во время работы программы данные в основном хранятся в оперативной памяти. Массив обеспечивает более высокую эффективность использования пространства памяти, а связный список дает большую гибкость в использовании памяти. - Кэш, используя строки кэша, механизм предвыборки, а также пространственную и временную локальность, предоставляет CPU быстрый доступ к данным и заметно повышает эффективность выполнения программ. - Поскольку массивы обычно имеют более высокий коэффициент попадания в кэш, они в большинстве случаев работают эффективнее списков. При выборе структуры данных нужно исходить из конкретных требований и сценариев. ### Q & A **Q**: Влияет ли хранение массива в стеке или в куче на временную и пространственную эффективность? Массивы, расположенные и в стеке, и в куче, все равно хранятся в непрерывной области памяти, поэтому эффективность операций с данными у них в целом одинакова. Однако у стека и кучи есть собственные особенности, из-за которых возникают следующие различия. 1. Эффективность выделения и освобождения: стек представляет собой относительно небольшой участок памяти, а выделение в нем обычно выполняется автоматически компилятором; куча же обычно больше, может выделяться динамически из кода и легче фрагментируется. Поэтому выделение и освобождение памяти в куче обычно медленнее, чем в стеке. 2. Ограничение размера: объем стека относительно невелик, а размер кучи обычно ограничивается доступной памятью. Поэтому куча лучше подходит для хранения больших массивов. 3. Гибкость: размер массива в стеке должен быть известен во время компиляции, а размер массива в куче может определяться динамически во время выполнения. **Q**: Почему для массива требуется, чтобы все элементы были одного типа, а для связного списка это не подчеркивается? Связный список состоит из узлов, а узлы соединяются между собой через ссылки (указатели), поэтому каждый узел в принципе может хранить данные разного типа, например `int` , `double` , `string` , `object` и т.д. Напротив, элементы массива должны быть одного типа, иначе нельзя будет вычислять адрес элемента через смещение. Например, если массив одновременно содержит `int` и `long` , один элемент занимает 4 байта, а другой - 8 байт ; в этом случае формула ниже уже не позволит вычислить смещение, потому что в массиве будут присутствовать элементы разной длины. ```shell # Адрес элемента в памяти = адрес массива в памяти (адрес первого элемента) + длина элемента * индекс элемента ``` **Q**: После удаления узла `P` нужно ли присваивать `P.next = None` ? Можно и не изменять `P.next` . С точки зрения данного списка, при обходе от головы к хвосту узел `P` уже больше не встретится. Это означает, что узел `P` уже удален из списка, и то, куда он указывает после этого, на сам список больше не влияет. С точки зрения задач по структурам данных и алгоритмам, отсутствие такого разрыва обычно не критично, если логика программы остается корректной. Но с точки зрения стандартной библиотеки разорвать связь безопаснее и логичнее. Если этого не сделать и удаленный узел не будет нормально собран, он может мешать освобождению памяти последующих узлов. **Q**: Временная сложность вставки и удаления в связном списке равна $O(1)$ . Но до вставки или удаления обычно еще нужно потратить $O(n)$ на поиск элемента. Почему тогда общая сложность не $O(n)$ ? Если сначала искать элемент, а потом удалять его, то временная сложность действительно будет $O(n)$ . Однако преимущество связного списка с $O(1)$ вставкой и удалением проявляется в других сценариях. Например, двустороннюю очередь удобно реализовывать именно на связном списке: мы поддерживаем указатели на голову и хвост, и тогда каждая операция вставки или удаления остается $O(1)$ . **Q**: На рисунке "Определение связного списка и способ хранения" светло-голубой блок с указателем узла - это отдельный адрес памяти? Или он делит память пополам со значением узла? Этот рисунок дает только качественное представление; количественно все зависит от конкретных условий. - Значения узлов разных типов занимают разный объем памяти, например `int` , `long` , `double` и объекты-экземпляры. - Размер памяти, занимаемой переменной-указателем, зависит от операционной системы и среды компиляции и обычно составляет 8 байт или 4 байта. **Q**: Всегда ли добавление элемента в конец списка имеет сложность $O(1)$ ? Если при добавлении элемента длина списка превышается, то сначала приходится расширять список, а уже затем добавлять новый элемент. Система выделяет новый участок памяти и переносит туда все элементы исходного списка, и в этот момент временная сложность становится $O(n)$ . **Q**: В утверждении "появление списка сильно повысило практическую полезность массива, но может приводить к потере части памяти" под потерями памяти имеется в виду дополнительная память под такие переменные, как емкость, длина и коэффициент расширения? Потери памяти здесь в основном имеют два значения: во-первых, список обычно имеет некоторую начальную емкость, которая может быть нам не нужна целиком; во-вторых, чтобы избежать слишком частых расширений, емкость при расширении обычно умножается на некоторый коэффициент, например $\times 1.5$ . Из-за этого появляется много пустых слотов, которые обычно нельзя полностью заполнить. **Q**: В Python после инициализации `n = [1, 2, 3]` адреса этих трех элементов выглядят непрерывными, но после `m = [2, 1, 3]` можно заметить, что `id` элементов не идут подряд, а совпадают с одинаковыми числами из `n` . Если адреса элементов не непрерывны, остается ли `m` массивом? Предположим, что элементами списка являются узлы `n = [n1, n2, n3, n4, n5]` . Обычно эти 5 объектов-узлов тоже будут храниться в разных местах памяти. Однако, имея индекс списка, мы по-прежнему можем за $O(1)$ получить адрес памяти соответствующего узла и обратиться к нему. Это связано с тем, что в массиве хранятся ссылки на узлы, а не сами узлы. В отличие от многих других языков, в Python даже числа обернуты в объекты, и в списке хранятся не сами числа, а ссылки на них. Поэтому мы и наблюдаем, что одинаковые числа в двух массивах имеют один и тот же `id` , а адреса этих чисел не обязаны быть непрерывными. **Q**: В C++ STL уже есть двусвязный список `std::list` , но в некоторых учебниках по алгоритмам им пользуются не так часто. Это связано с какими-то ограничениями? С одной стороны, при разработке алгоритмов мы обычно предпочитаем структуры на основе массива, а к связным спискам прибегаем только при необходимости, по двум главным причинам. - Накладные расходы по памяти: поскольку каждому элементу нужны два дополнительных указателя (на предыдущий и следующий элементы), `std::list` обычно занимает больше памяти, чем `std::vector` . - Низкая дружелюбность к кэшу: поскольку данные не лежат непрерывно, `std::list` хуже использует кэш. В большинстве случаев `std::vector` показывает лучшую производительность. С другой стороны, случаи, когда связный список действительно необходим, в основном возникают в деревьях и графах. Для стеков и очередей чаще используют предоставляемые языком `stack` и `queue` , а не связный список напрямую. **Q**: Операция `res = [[0]] * n` создает двумерный список. Каждый `[0]` в нем независим? Нет, они не независимы. В таком двумерном списке все `[0]` на самом деле являются ссылками на один и тот же объект. Если изменить один из них, окажется, что меняются и все остальные соответствующие элементы. Если нужно, чтобы каждый `[0]` был независимым, можно использовать `res = [[0] for _ in range(n)]` . В этом варианте создаются $n$ независимых объектов-списков `[0]` . **Q**: Операция `res = [0] * n` создает список. Каждый целочисленный `0` в нем независим? В этом списке все целые числа `0` являются ссылками на один и тот же объект. Это связано с тем, что Python использует механизм кэш-пула для маленьких целых чисел (обычно от -5 до 256), чтобы максимально переиспользовать объекты и повысить производительность. Хотя все элементы указывают на один и тот же объект, мы все равно можем независимо изменять элементы списка, потому что целые числа в Python - это "неизменяемые объекты". Когда мы изменяем некоторый элемент, на самом деле происходит переключение ссылки на другой объект, а не изменение исходного объекта. Однако если элементами списка являются "изменяемые объекты" (например списки, словари или экземпляры классов), то изменение одного элемента прямо меняет сам объект, и все элементы, ссылающиеся на него, увидят одно и то же изменение. ================================================ FILE: ru/docs/chapter_backtracking/backtracking_algorithm.md ================================================ # Алгоритм поиска с возвратом Алгоритм поиска с возвратом (backtracking algorithm) - это метод решения задач путем полного перебора. Его основная идея состоит в том, чтобы, начиная с некоторого исходного состояния, грубо перебрать все возможные решения, записывать корректные решения и продолжать поиск до тех пор, пока решение не будет найдено или пока не будут исчерпаны все возможные варианты. Обычно алгоритмы поиска с возвратом используют обход в глубину для обхода пространства решений. В главе "Бинарные деревья" мы уже упоминали, что прямой, симметричный и обратный обходы относятся к обходу в глубину. Теперь мы на основе прямого обхода построим задачу поиска с возвратом и постепенно разберем принцип работы этого алгоритма. !!! question "Пример 1" Дано двоичное дерево. Найдите и запишите все узлы со значением $7$ ; верните список этих узлов. Для этой задачи мы выполняем прямой обход дерева и проверяем, равно ли значение текущего узла $7$ ; если да, то добавляем значение этого узла в список результатов `res` . Соответствующий процесс показан на рисунке ниже и в коде: ```src [file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order} ``` ![Поиск узлов при прямом обходе](backtracking_algorithm.assets/preorder_find_nodes.png) ## Попытка и откат **Алгоритм называется поиском с возвратом, потому что при поиске в пространстве решений он использует стратегию "попытка" и "откат"**. Когда в процессе поиска алгоритм приходит в состояние, из которого нельзя двигаться дальше или нельзя получить удовлетворяющее условиям решение, он отменяет предыдущий выбор, возвращается к более раннему состоянию и пробует другие возможные варианты. Для примера 1 посещение каждого узла представляет собой "попытку", а прохождение листового узла или возврат к родителю через `return` означает "откат". Важно понимать, что **откат не сводится только к возврату из функции**. Чтобы показать это, слегка расширим пример 1. !!! question "Пример 2" Найдите в двоичном дереве все узлы со значением $7$ и **верните пути от корня до этих узлов**. Взяв за основу код примера 1, добавим список `path` для записи пути посещенных узлов. Когда встречается узел со значением $7$ , мы копируем `path` и добавляем его в список результатов `res` . После завершения обхода именно `res` будет содержать все решения. Код приведен ниже: ```src [file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order} ``` В каждой "попытке" мы добавляем текущий узел в `path` , чтобы записать путь; а перед "откатом" нам нужно удалить этот узел из `path` , **чтобы восстановить состояние, существовавшее до текущей попытки**. Если посмотреть на процесс, изображенный на рисунке ниже, **то попытку и откат можно понимать как "движение вперед" и "отмену"**: это два взаимно противоположных действия. === "<1>" ![Попытка и откат](backtracking_algorithm.assets/preorder_find_paths_step1.png) === "<2>" ![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png) === "<3>" ![preorder_find_paths_step3](backtracking_algorithm.assets/preorder_find_paths_step3.png) === "<4>" ![preorder_find_paths_step4](backtracking_algorithm.assets/preorder_find_paths_step4.png) === "<5>" ![preorder_find_paths_step5](backtracking_algorithm.assets/preorder_find_paths_step5.png) === "<6>" ![preorder_find_paths_step6](backtracking_algorithm.assets/preorder_find_paths_step6.png) === "<7>" ![preorder_find_paths_step7](backtracking_algorithm.assets/preorder_find_paths_step7.png) === "<8>" ![preorder_find_paths_step8](backtracking_algorithm.assets/preorder_find_paths_step8.png) === "<9>" ![preorder_find_paths_step9](backtracking_algorithm.assets/preorder_find_paths_step9.png) === "<10>" ![preorder_find_paths_step10](backtracking_algorithm.assets/preorder_find_paths_step10.png) === "<11>" ![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png) ## Обрезка Сложные задачи поиска с возвратом обычно содержат одно или несколько ограничений, **которые часто можно использовать для "обрезки"**. !!! question "Пример 3" Найдите в двоичном дереве все узлы со значением $7$ , верните пути от корня до этих узлов, **причем путь не должен содержать узлы со значением $3$**. Чтобы выполнить это ограничение, **нам нужно добавить операцию обрезки**: во время поиска, если встречается узел со значением $3$ , мы сразу возвращаемся и не продолжаем дальнейший поиск. Код выглядит так: ```src [file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order} ``` Термин "обрезка" очень нагляден. Как показано на рисунке ниже, во время поиска **мы отсекаем ветви, не удовлетворяющие ограничениям** , тем самым избегая множества бессмысленных попыток и повышая эффективность поиска. ![Обрезка по условиям задачи](backtracking_algorithm.assets/preorder_find_constrained_paths.png) ## Каркас кода Теперь попробуем извлечь общий каркас из действий "попытка", "откат" и "обрезка", чтобы сделать код более универсальным. В следующем каркасе кода `state` обозначает текущее состояние задачи, а `choices` - список выборов, доступных в текущем состоянии: === "Python" ```python title="" def backtrack(state: State, choices: list[choice], res: list[state]): """Каркас алгоритма поиска с возвратом""" # Проверка, является ли текущее состояние решением if is_solution(state): # Запись решения record_solution(state, res) # Дальше не продолжаем поиск return # Перебор всех возможных выборов for choice in choices: # Обрезка: проверка допустимости выбора if is_valid(state, choice): # Попытка: сделать выбор и обновить состояние make_choice(state, choice) backtrack(state, choices, res) # Откат: отменить выбор и восстановить предыдущее состояние undo_choice(state, choice) ``` === "C++" ```cpp title="" /* Каркас алгоритма поиска с возвратом */ void backtrack(State *state, vector &choices, vector &res) { // Проверка, является ли текущее состояние решением if (isSolution(state)) { // Запись решения recordSolution(state, res); // Дальше не продолжаем поиск return; } // Перебор всех возможных выборов for (Choice choice : choices) { // Обрезка: проверка допустимости выбора if (isValid(state, choice)) { // Попытка: сделать выбор и обновить состояние makeChoice(state, choice); backtrack(state, choices, res); // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state, choice); } } } ``` === "Java" ```java title="" /* Каркас алгоритма поиска с возвратом */ void backtrack(State state, List choices, List res) { // Проверка, является ли текущее состояние решением if (isSolution(state)) { // Запись решения recordSolution(state, res); // Дальше не продолжаем поиск return; } // Перебор всех возможных выборов for (Choice choice : choices) { // Обрезка: проверка допустимости выбора if (isValid(state, choice)) { // Попытка: сделать выбор и обновить состояние makeChoice(state, choice); backtrack(state, choices, res); // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state, choice); } } } ``` === "C#" ```csharp title="" /* Каркас алгоритма поиска с возвратом */ void Backtrack(State state, List choices, List res) { // Проверка, является ли текущее состояние решением if (IsSolution(state)) { // Запись решения RecordSolution(state, res); // Дальше не продолжаем поиск return; } // Перебор всех возможных выборов foreach (Choice choice in choices) { // Обрезка: проверка допустимости выбора if (IsValid(state, choice)) { // Попытка: сделать выбор и обновить состояние MakeChoice(state, choice); Backtrack(state, choices, res); // Откат: отменить выбор и восстановить предыдущее состояние UndoChoice(state, choice); } } } ``` === "Go" ```go title="" /* Каркас алгоритма поиска с возвратом */ func backtrack(state *State, choices []Choice, res *[]State) { // Проверка, является ли текущее состояние решением if isSolution(state) { // Запись решения recordSolution(state, res) // Дальше не продолжаем поиск return } // Перебор всех возможных выборов for _, choice := range choices { // Обрезка: проверка допустимости выбора if isValid(state, choice) { // Попытка: сделать выбор и обновить состояние makeChoice(state, choice) backtrack(state, choices, res) // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state, choice) } } } ``` === "Swift" ```swift title="" /* Каркас алгоритма поиска с возвратом */ func backtrack(state: inout State, choices: [Choice], res: inout [State]) { // Проверка, является ли текущее состояние решением if isSolution(state: state) { // Запись решения recordSolution(state: state, res: &res) // Дальше не продолжаем поиск return } // Перебор всех возможных выборов for choice in choices { // Обрезка: проверка допустимости выбора if isValid(state: state, choice: choice) { // Попытка: сделать выбор и обновить состояние makeChoice(state: &state, choice: choice) backtrack(state: &state, choices: choices, res: &res) // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state: &state, choice: choice) } } } ``` === "JS" ```javascript title="" /* Каркас алгоритма поиска с возвратом */ function backtrack(state, choices, res) { // Проверка, является ли текущее состояние решением if (isSolution(state)) { // Запись решения recordSolution(state, res); // Дальше не продолжаем поиск return; } // Перебор всех возможных выборов for (let choice of choices) { // Обрезка: проверка допустимости выбора if (isValid(state, choice)) { // Попытка: сделать выбор и обновить состояние makeChoice(state, choice); backtrack(state, choices, res); // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state, choice); } } } ``` === "TS" ```typescript title="" /* Каркас алгоритма поиска с возвратом */ function backtrack(state: State, choices: Choice[], res: State[]): void { // Проверка, является ли текущее состояние решением if (isSolution(state)) { // Запись решения recordSolution(state, res); // Дальше не продолжаем поиск return; } // Перебор всех возможных выборов for (let choice of choices) { // Обрезка: проверка допустимости выбора if (isValid(state, choice)) { // Попытка: сделать выбор и обновить состояние makeChoice(state, choice); backtrack(state, choices, res); // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state, choice); } } } ``` === "Dart" ```dart title="" /* Каркас алгоритма поиска с возвратом */ void backtrack(State state, List, List res) { // Проверка, является ли текущее состояние решением if (isSolution(state)) { // Запись решения recordSolution(state, res); // Дальше не продолжаем поиск return; } // Перебор всех возможных выборов for (Choice choice in choices) { // Обрезка: проверка допустимости выбора if (isValid(state, choice)) { // Попытка: сделать выбор и обновить состояние makeChoice(state, choice); backtrack(state, choices, res); // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state, choice); } } } ``` === "Rust" ```rust title="" /* Каркас алгоритма поиска с возвратом */ fn backtrack(state: &mut State, choices: &Vec, res: &mut Vec) { // Проверка, является ли текущее состояние решением if is_solution(state) { // Запись решения record_solution(state, res); // Дальше не продолжаем поиск return; } // Перебор всех возможных выборов for choice in choices { // Обрезка: проверка допустимости выбора if is_valid(state, choice) { // Попытка: сделать выбор и обновить состояние make_choice(state, choice); backtrack(state, choices, res); // Откат: отменить выбор и восстановить предыдущее состояние undo_choice(state, choice); } } } ``` === "C" ```c title="" /* Каркас алгоритма поиска с возвратом */ void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) { // Проверка, является ли текущее состояние решением if (isSolution(state)) { // Запись решения recordSolution(state, res, numRes); // Дальше не продолжаем поиск return; } // Перебор всех возможных выборов for (int i = 0; i < numChoices; i++) { // Обрезка: проверка допустимости выбора if (isValid(state, &choices[i])) { // Попытка: сделать выбор и обновить состояние makeChoice(state, &choices[i]); backtrack(state, choices, numChoices, res, numRes); // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state, &choices[i]); } } } ``` === "Kotlin" ```kotlin title="" /* Каркас алгоритма поиска с возвратом */ fun backtrack(state: State?, choices: List, res: List?) { // Проверка, является ли текущее состояние решением if (isSolution(state)) { // Запись решения recordSolution(state, res) // Дальше не продолжаем поиск return } // Перебор всех возможных выборов for (choice in choices) { // Обрезка: проверка допустимости выбора if (isValid(state, choice)) { // Попытка: сделать выбор и обновить состояние makeChoice(state, choice) backtrack(state, choices, res) // Откат: отменить выбор и восстановить предыдущее состояние undoChoice(state, choice) } } } ``` === "Ruby" ```ruby title="" ### Каркас алгоритма поиска с возвратом ### def backtrack(state, choices, res) # Проверка, является ли текущее состояние решением if is_solution?(state) # Запись решения record_solution(state, res) return end # Перебор всех возможных выборов for choice in choices # Обрезка: проверка допустимости выбора if is_valid?(state, choice) # Попытка: сделать выбор и обновить состояние make_choice(state, choice) backtrack(state, choices, res) # Откат: отменить выбор и восстановить предыдущее состояние undo_choice(state, choice) end end end ``` Теперь, опираясь на этот каркас, решим пример 3. Состояние `state` здесь - это путь обхода узлов, выбор `choices` - левый и правый потомки текущего узла, а результат `res` - список путей: ```src [file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack} ``` Согласно условию задачи, после нахождения узла со значением $7$ мы должны продолжать поиск, **поэтому оператор `return` после записи решения нужно удалить**. На рисунке ниже сравниваются процессы поиска в случаях, когда `return` сохраняется и когда он удаляется. ![Сравнение поиска при сохранении и удалении return](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) По сравнению с реализацией на основе прямого обхода, версия на основе общего каркаса поиска с возвратом выглядит более громоздкой, но при этом обладает лучшей универсальностью. На практике **многие задачи поиска с возвратом можно решать в рамках этого каркаса**. Для этого нужно лишь определить `state` и `choices` под конкретную задачу и реализовать соответствующие методы каркаса. ## Часто используемые термины Чтобы яснее анализировать алгоритмические задачи, подытожим значения часто используемых терминов поиска с возвратом и сопоставим их с примером 3, как показано в таблице ниже.

Таблица   Часто используемые термины алгоритма поиска с возвратом

| Термин | Определение | Пример 3 | | ------------------------ | -------------------------------------------------------------------------- | --------------------------------------------------------------------- | | Решение (solution) | Решение - это ответ, удовлетворяющий условиям задачи; решений может быть одно или несколько | Все пути от корня до узла $7$ , удовлетворяющие ограничениям | | Ограничение (constraint) | Ограничение определяет допустимость решения и обычно используется для обрезки | Путь не содержит узлы со значением $3$ | | Состояние (state) | Состояние описывает ситуацию задачи в некоторый момент времени, включая уже сделанные выборы | Текущий путь посещенных узлов, то есть список узлов `path` | | Попытка (attempt) | Попытка - это исследование пространства решений на основе доступных выборов, включая выбор, обновление состояния и проверку, является ли состояние решением | Рекурсивный переход к левому или правому потомку, добавление узла в `path` и проверка, равно ли значение узла $7$ | | Откат (backtracking) | Откат означает отмену предыдущих выборов и возврат к более раннему состоянию при встрече состояния, не удовлетворяющего ограничениям | Завершение поиска при проходе через лист, окончании посещения узла или встрече узла со значением $3$ , то есть возврат из функции | | Обрезка (pruning) | Обрезка - это способ избегать бессмысленных путей поиска на основе свойств задачи и ее ограничений, повышающий эффективность | При встрече узла со значением $3$ поиск по этой ветви прекращается | !!! tip Такие понятия, как задача, решение и состояние, являются общими и встречаются не только в поиске с возвратом, но и в "разделяй и властвуй", динамическом программировании, жадных алгоритмах и других темах. ## Преимущества и ограничения Алгоритм поиска с возвратом по своей сути представляет собой алгоритм обхода в глубину, который перебирает все возможные решения, пока не найдет удовлетворяющее условиям. Преимущество этого подхода в том, что он позволяет находить все возможные решения и при разумной обрезке может работать весьма эффективно. Однако при работе с большими или сложными задачами **эффективность поиска с возвратом может оказаться неприемлемой**. - **Время**: поиск с возвратом обычно требует обхода всех возможных состояний пространства состояний, и его временная сложность может достигать экспоненциального или факториального порядка. - **Память**: при рекурсивных вызовах нужно хранить текущее состояние (например, путь, вспомогательные переменные для обрезки и т.д.), поэтому при большой глубине рекурсии потребность в памяти может стать значительной. Тем не менее **поиск с возвратом по-прежнему остается лучшим решением для некоторых поисковых задач и задач удовлетворения ограничений**. В таких задачах заранее невозможно предсказать, какие выборы приведут к эффективному решению, поэтому приходится перебирать все возможные варианты. В этой ситуации **ключевым становится вопрос оптимизации эффективности** , и для этого обычно используют две стратегии. - **Обрезка**: избегать поиска по тем путям, которые заведомо не приведут к решению, тем самым экономя время и память. - **Эвристический поиск**: вводить во время поиска дополнительные стратегии или оценки, чтобы в первую очередь исследовать пути, наиболее вероятно ведущие к эффективному решению. ## Типичные задачи поиска с возвратом Алгоритм поиска с возвратом можно использовать для решения множества поисковых задач, задач удовлетворения ограничений и задач комбинаторной оптимизации. **Поисковые задачи**: целью таких задач является поиск решений, удовлетворяющих определенным условиям. - Задача о перестановках: дано множество, требуется найти все возможные перестановки его элементов. - Задача о сумме подмножеств: даны множество и целевая сумма; нужно найти все подмножества, сумма элементов которых равна целевой. - Задача о Ханойской башне: даны три стержня и набор дисков разного размера; требуется перенести все диски с одного стержня на другой, перемещая за раз только один диск и не помещая больший диск на меньший. **Задачи удовлетворения ограничений**: целью таких задач является поиск решений, удовлетворяющих всем ограничениям. - Задача о $n$ ферзях: разместить $n$ ферзей на шахматной доске размера $n \times n$ так, чтобы они не атаковали друг друга. - Судоку: заполнить сетку $9 \times 9$ числами от $1$ до $9$ так, чтобы в каждой строке, каждом столбце и каждом блоке $3 \times 3$ числа не повторялись. - Задача раскраски графа: дан неориентированный граф; требуется раскрасить его вершины минимальным числом цветов так, чтобы соседние вершины имели разные цвета. **Задачи комбинаторной оптимизации**: целью таких задач является поиск оптимального решения в некотором комбинаторном пространстве при заданных ограничениях. - Задача о рюкзаке 0-1: даны набор предметов и рюкзак; у каждого предмета есть ценность и вес, и нужно выбрать предметы так, чтобы при ограниченной вместимости рюкзака суммарная ценность была максимальной. - Задача коммивояжера: начиная из некоторой вершины графа, требуется посетить все остальные вершины ровно по одному разу и вернуться в исходную вершину, найдя при этом кратчайший путь. - Задача о максимальной клике: дан неориентированный граф; требуется найти в нем максимальный полный подграф, то есть подграф, в котором любая пара вершин соединена ребром. Стоит отметить: для многих задач комбинаторной оптимизации поиск с возвратом не является оптимальным способом решения. - Задача о рюкзаке 0-1 обычно решается с помощью динамического программирования, что дает более высокую временную эффективность. - Задача коммивояжера является известной NP-Hard задачей; для ее решения часто используют генетические алгоритмы, муравьиные алгоритмы и другие методы. - Задача о максимальной клике является классической задачей теории графов и может решаться жадными и другими эвристическими алгоритмами. ================================================ FILE: ru/docs/chapter_backtracking/index.md ================================================ # Поиск с возвратом ![Поиск с возвратом](../assets/covers/chapter_backtracking.jpg) !!! abstract Мы словно исследователи в лабиринте: на пути вперед могут встречаться тупики и трудности. Сила возврата позволяет нам начать заново, пробовать снова и снова и в конце концов найти выход к свету. ================================================ FILE: ru/docs/chapter_backtracking/n_queens_problem.md ================================================ # Задача о n ферзях !!! question Согласно правилам шахмат ферзь может атаковать фигуры, находящиеся с ним на одной строке, в одном столбце или на одной диагонали. Даны $n$ ферзей и шахматная доска размера $n \times n$ ; требуется найти такие расстановки, при которых ни одна пара ферзей не может атаковать друг друга. Как показано на рисунке ниже, при $n = 4$ существует два решения. С точки зрения поиска с возвратом доска размера $n \times n$ содержит $n^2$ клеток, которые образуют все возможные выборы `choices` . По мере поочередного размещения ферзей состояние доски непрерывно меняется, и текущее содержимое доски образует состояние `state` . ![Решения задачи о 4 ферзях](n_queens_problem.assets/solution_4_queens.png) На рисунке ниже показаны три ограничения этой задачи: **несколько ферзей не могут находиться на одной строке, в одном столбце или на одной диагонали**. При этом нужно помнить, что диагонали бывают двух типов: главная `\` и побочная `/` . ![Ограничения задачи о n ферзях](n_queens_problem.assets/n_queens_constraints.png) ### Построчная стратегия размещения Число ферзей и число строк доски одинаково и равно $n$ , поэтому легко получить следующий вывод: **в каждой строке доски разрешено и нужно разместить ровно одного ферзя**. Иначе говоря, можно использовать построчную стратегию: начиная с первой строки, размещать по одному ферзю в каждой строке, пока не будет достигнута последняя. На рисунке ниже показан процесс построчного размещения для задачи о 4 ферзях. Из-за ограничений размера изображения на нем раскрыта только одна ветвь поиска для первой строки, а все варианты, не удовлетворяющие ограничениям по столбцам и диагоналям, были отсечены. ![Построчная стратегия размещения](n_queens_problem.assets/n_queens_placing.png) По своей сути **построчная стратегия сама по себе выполняет роль обрезки** , потому что заранее исключает все ветви поиска, в которых в одной строке оказалось бы несколько ферзей. ### Обрезка по столбцам и диагоналям Чтобы удовлетворить ограничению по столбцам, можно использовать булев массив `cols` длины $n$ , который записывает, есть ли ферзь в каждом столбце. Перед каждым размещением мы используем `cols` для отсечения столбцов, уже занятых ферзями, а затем динамически обновляем состояние `cols` во время отката. !!! tip Обратите внимание: начало координат матрицы находится в левом верхнем углу, при этом индексы строк растут сверху вниз, а индексы столбцов - слева направо. Как теперь обработать ограничения по диагоналям? Пусть клетка на доске имеет координаты $(row, col)$ . Выбрав некоторую главную диагональ в матрице, можно заметить, что разность индексов строки и столбца одинакова для всех клеток этой диагонали, **то есть для всех клеток главной диагонали значение $row - col$ постоянно**. Это означает, что если для двух клеток выполняется равенство $row_1 - col_1 = row_2 - col_2$ , то они обязательно лежат на одной и той же главной диагонали. Используя это правило, можно с помощью массива `diags1` , показанного на рисунке ниже, отмечать наличие ферзя на каждой главной диагонали. Аналогично **для всех клеток побочной диагонали значение $row + col$ является постоянным**. Поэтому для обработки ограничений по побочным диагоналям можно использовать еще один массив `diags2` . ![Обработка ограничений по столбцам и диагоналям](n_queens_problem.assets/n_queens_cols_diagonals.png) ### Реализация кода Заметим, что в квадратной матрице размера $n$ диапазон значений $row - col$ равен $[-n + 1, n - 1]$ , а диапазон значений $row + col$ равен $[0, 2n - 2]$ . Следовательно, число главных и побочных диагоналей равно $2n - 1$ , а значит, длины массивов `diags1` и `diags2` тоже равны $2n - 1$ . ```src [file]{n_queens}-[class]{}-[func]{n_queens} ``` Если размещать ферзей построчно $n$ раз, учитывая ограничение по столбцам, то начиная с первой строки и заканчивая последней мы получаем соответственно $n$, $n-1$, $\dots$, $2$, $1$ вариантов выбора, что дает $O(n!)$ времени. При записи решения нужно скопировать матрицу `state` и добавить ее в `res` , а копирование требует $O(n^2)$ времени. Следовательно, **общая временная сложность равна $O(n! \cdot n^2)$** . На практике обрезка по диагональным ограничениям дополнительно сильно уменьшает пространство поиска, поэтому фактическая эффективность часто лучше этой оценки. Массив `state` использует $O(n^2)$ пространства, а массивы `cols` , `diags1` и `diags2` используют по $O(n)$ пространства. Максимальная глубина рекурсии равна $n$ , что требует $O(n)$ памяти стека. Следовательно, **пространственная сложность равна $O(n^2)$** . ================================================ FILE: ru/docs/chapter_backtracking/permutations_problem.md ================================================ # Задача о перестановках Задача о перестановках является типичным применением алгоритма поиска с возвратом. Ее определение состоит в том, чтобы для данного множества элементов (например, массива или строки) найти все возможные перестановки этих элементов. В таблице ниже приведено несколько примеров входных массивов и соответствующих им перестановок.

Таблица   Примеры перестановок

| Входной массив | Все перестановки | | :------------- | :----------------------------------------------------------------- | | $[1]$ | $[1]$ | | $[1, 2]$ | $[1, 2], [2, 1]$ | | $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ | ## Случай без равных элементов !!! question Дан массив целых чисел, в котором нет повторяющихся элементов. Верните все возможные перестановки. С точки зрения поиска с возвратом **процесс построения перестановок можно представить как результат последовательности выборов**. Пусть входной массив равен $[1, 2, 3]$ ; если мы сначала выберем $1$ , затем $3$ , а потом $2$ , то получим перестановку $[1, 3, 2]$ . Откат здесь означает отмену одного из выборов с последующей попыткой других вариантов. С точки зрения кода поиска с возвратом множество кандидатов `choices` состоит из всех элементов входного массива, а состояние `state` - из элементов, уже выбранных к текущему моменту. Поскольку каждый элемент разрешено выбирать только один раз, **все элементы в `state` должны быть уникальны**. Как показано на рисунке ниже, процесс поиска можно развернуть в дерево рекурсии, где каждый узел представляет текущее состояние `state` . Начиная от корня, после трех раундов выбора мы попадаем в листья, и каждый лист соответствует одной перестановке. ![Дерево рекурсии для перестановок](permutations_problem.assets/permutations_i.png) ### Обрезка повторного выбора Чтобы гарантировать, что каждый элемент выбирается только один раз, введем булев массив `selected` , где `selected[i]` обозначает, был ли уже выбран `choices[i]` , и на его основе выполним следующую обрезку. - После того как сделан выбор `choice[i]` , мы присваиваем `selected[i]` значение $\text{True}$ , тем самым отмечая, что этот элемент уже выбран. - При обходе списка вариантов `choices` пропускаем все уже выбранные элементы, то есть выполняем обрезку. Как показано на рисунке ниже, если в первом раунде мы выберем 1 , во втором - 3 , а в третьем - 2 , то во втором раунде нужно отсечь ветвь элемента 1 , а в третьем - ветви элементов 1 и 3 . ![Пример обрезки в задаче о перестановках](permutations_problem.assets/permutations_i_pruning.png) Из рисунка видно, что такая обрезка уменьшает размер пространства поиска с $O(n^n)$ до $O(n!)$ . ### Реализация кода После прояснения всей логики можно просто "заполнить пропуски" в шаблоне поиска с возвратом. Чтобы сократить общий объем кода, мы не будем отдельно реализовывать каждую функцию из каркаса, а раскроем их прямо внутри `backtrack()` : ```src [file]{permutations_i}-[class]{}-[func]{permutations_i} ``` ## Учет равных элементов !!! question Дан массив целых чисел, **который может содержать повторяющиеся элементы**. Верните все неповторяющиеся перестановки. Пусть входной массив равен $[1, 1, 2]$ . Чтобы различать два одинаковых элемента $1$ , будем обозначать второй из них как $\hat{1}$ . Как показано на рисунке ниже, описанный выше метод создаст результат, половина которого окажется дублирующейся. ![Повторяющиеся перестановки](permutations_problem.assets/permutations_ii.png) Как же убрать повторяющиеся перестановки? Самый прямолинейный способ - воспользоваться хеш-множеством и удалить дубликаты уже после генерации результата. Но это не слишком изящно, **потому что ветви поиска, порождающие дубликаты, вообще не нужно посещать: их следует распознавать заранее и отсекать**, что дополнительно повышает эффективность алгоритма. ### Обрезка равных элементов Посмотрите на рисунок ниже: в первом раунде выбрать $1$ или выбрать $\hat{1}$ - это одно и то же, а значит, все перестановки, полученные из этих двух выборов, будут дублироваться. Поэтому ветвь $\hat{1}$ нужно отсечь. Точно так же, если в первом раунде выбрать $2$ , то во втором раунде выборы $1$ и $\hat{1}$ снова создадут дублирующиеся ветви, поэтому и в этом случае ветвь $\hat{1}$ нужно отсечь. Иначе говоря, **наша цель заключается в том, чтобы на каждом раунде выбора каждый из нескольких равных элементов выбирался только один раз**. ![Обрезка повторяющихся перестановок](permutations_problem.assets/permutations_ii_pruning.png) ### Реализация кода На основе решения из предыдущей задачи можно на каждом раунде выбора заводить хеш-множество `duplicated` , которое будет записывать элементы, уже встречавшиеся в этом раунде, и отсекать повторы: ```src [file]{permutations_ii}-[class]{}-[func]{permutations_ii} ``` Если предположить, что все элементы попарно различны, то из $n$ элементов можно получить $n!$ перестановок; при записи результата требуется копировать список длины $n$ , что занимает $O(n)$ времени. **Следовательно, временная сложность равна $O(n!n)$** . Максимальная глубина рекурсии равна $n$ , что требует $O(n)$ стековой памяти. Массив `selected` занимает $O(n)$ пространства. Одновременно может существовать до $n$ хеш-множеств `duplicated` , что дает $O(n^2)$ памяти. **Следовательно, пространственная сложность равна $O(n^2)$** . ### Сравнение двух видов обрезки Обратите внимание: хотя и `selected` , и `duplicated` используются для обрезки, их цели различаются. - **Обрезка повторного выбора**: во всем процессе поиска существует только один `selected` . Он записывает, какие элементы уже входят в текущее состояние, и нужен для того, чтобы один и тот же элемент не появлялся в `state` дважды. - **Обрезка равных элементов**: каждый раунд выбора (каждый вызов `backtrack`) содержит собственный `duplicated` . Он записывает, какие элементы уже выбирались в текущем раунде (`for` цикле), и нужен для того, чтобы равные элементы выбирались только один раз. На рисунке ниже показана область действия двух условий обрезки. Помните, что каждый узел дерева соответствует одному выбору, а путь от корня до листа образует одну перестановку. ![Область действия двух условий обрезки](permutations_problem.assets/permutations_ii_pruning_summary.png) ================================================ FILE: ru/docs/chapter_backtracking/subset_sum_problem.md ================================================ # Задача о сумме подмножеств ## Случай без повторяющихся элементов !!! question Дан массив положительных целых чисел `nums` и целое положительное значение `target` . Найдите все возможные комбинации, сумма элементов которых равна `target` . Во входном массиве нет повторяющихся элементов, и каждый элемент можно выбирать неограниченное число раз. Верните эти комбинации в виде списка; в результате не должно быть повторяющихся комбинаций. Например, для входного множества $\{3, 4, 5\}$ и целевого значения $9$ решениями будут $\{3, 3, 3\}$ и $\{4, 5\}$ . При этом важно учитывать два обстоятельства. - Элементы входного множества можно выбирать повторно неограниченное число раз. - Подмножество не различает порядок элементов, поэтому $\{4, 5\}$ и $\{5, 4\}$ считаются одним и тем же подмножеством. ### Отталкиваемся от решения задачи о перестановках Как и в задаче о перестановках, можно представлять построение подмножеств как результат последовательности выборов и во время выбора динамически обновлять "сумму элементов"; когда эта сумма становится равной `target` , соответствующее подмножество записывается в список результатов. Однако в отличие от задачи о перестановках **в этой задаче элементы множества можно выбирать неограниченное число раз**, поэтому нам не нужен булев список `selected` для записи того, был ли выбран элемент. Можно слегка изменить код для перестановок и получить первоначальную версию решения: ```src [file]{subset_sum_i_naive}-[class]{}-[func]{subset_sum_i_naive} ``` Если подать на этот код массив $[3, 4, 5]$ и целевое значение $9$ , то на выходе мы получим $[3, 3, 3], [4, 5], [5, 4]$ . **Хотя все подмножества с суммой $9$ успешно найдены, среди них все же присутствуют дубликаты: $[4, 5]$ и $[5, 4]$** . Причина в том, что процесс поиска различает порядок выбора, тогда как для подмножеств порядок не важен. Как показано на рисунке ниже, сначала выбрать $4$ , а затем $5$ , и сначала выбрать $5$ , а затем $4$ - это разные ветви поиска, но им соответствует одно и то же подмножество. ![Поиск подмножеств и обрезка по выходу за границу](subset_sum_problem.assets/subset_sum_i_naive.png) Чтобы убрать повторяющиеся подмножества, **одна из прямых идей - удалить дубликаты уже из итогового списка результатов**. Но это решение малоэффективно по двум причинам. - Когда массив содержит много элементов, а особенно когда `target` велик, процесс поиска порождает огромное число повторяющихся подмножеств. - Сравнение подмножеств (то есть массивов) само по себе довольно затратно: сначала приходится сортировать массивы, а затем поэлементно сравнивать их. ### Обрезка повторяющихся подмножеств **Поэтому стоит выполнять устранение дубликатов прямо во время поиска, с помощью обрезки**. Посмотрите на рисунок ниже: повторяющиеся подмножества возникают тогда, когда элементы массива выбираются в разном порядке, например так. 1. Если в первом и втором раундах выбрать соответственно $3$ и $4$ , то будут сгенерированы все подмножества, содержащие эти два элемента, и их можно обозначить как $[3, 4, \dots]$ . 2. После этого, если в первом раунде выбрать $4$ , **то во втором раунде нужно пропустить $3$** , потому что подмножества $[4, 3, \dots]$ полностью дублируют подмножества, уже построенные на шаге `1.` . Во время поиска варианты на каждом уровне пробуются по одному слева направо, поэтому чем правее ветвь, тем больше ветвей оказывается отсечено. 1. В первых двух раундах выбираются $3$ и $5$ , что дает подмножества $[3, 5, \dots]$ . 2. В первых двух раундах выбираются $4$ и $5$ , что дает подмножества $[4, 5, \dots]$ . 3. Если же в первом раунде выбрать $5$ , **то во втором раунде нужно пропустить $3$ и $4$** , потому что подмножества $[5, 3, \dots]$ и $[5, 4, \dots]$ полностью дублируют случаи, описанные в шагах `1.` и `2.` . ![Повторяющиеся подмножества из-за разного порядка выбора](subset_sum_problem.assets/subset_sum_i_pruning.png) В общем виде, если входной массив имеет вид $[x_1, x_2, \dots, x_n]$ , а последовательность выборов в ходе поиска равна $[x_{i_1}, x_{i_2}, \dots, x_{i_m}]$ , то она должна удовлетворять условию $i_1 \leq i_2 \leq \dots \leq i_m$ ; **все последовательности выборов, не удовлетворяющие этому условию, приводят к дубликатам и должны отсекаться**. ### Реализация кода Чтобы реализовать такую обрезку, инициализируем переменную `start` , которая будет указывать начальную точку обхода. **После выбора элемента $x_i$ следующий раунд начинается с индекса $i$**. Благодаря этому последовательность выборов всегда удовлетворяет условию $i_1 \leq i_2 \leq \dots \leq i_m$ , а значит, каждое подмножество создается только один раз. Помимо этого, мы внесем в код еще два улучшения. - Перед началом поиска отсортируем массив `nums` . Тогда при обходе всех вариантов **можно сразу прервать цикл, как только сумма подмножества превысит `target`** , потому что все последующие элементы будут еще больше и их сумма тоже превысит `target` . - Откажемся от отдельной переменной суммы `total` и **будем учитывать сумму через вычитание из `target`** ; когда `target` станет равным $0$ , решение фиксируется. ```src [file]{subset_sum_i}-[class]{}-[func]{subset_sum_i} ``` На рисунке ниже показан полный процесс поиска с возвратом для массива $[3, 4, 5]$ и целевого значения $9$ . ![Процесс поиска с возвратом для задачи о сумме подмножеств I](subset_sum_problem.assets/subset_sum_i.png) ## Учет повторяющихся элементов !!! question Дан массив положительных целых чисел `nums` и целое положительное значение `target` . Найдите все возможные комбинации, сумма элементов которых равна `target` . **Во входном массиве могут присутствовать повторяющиеся элементы, и каждый элемент разрешено выбирать только один раз**. Верните эти комбинации в виде списка; в результате не должно быть повторяющихся комбинаций. По сравнению с предыдущей задачей **во входном массиве теперь могут присутствовать повторяющиеся элементы**, и это создает новую проблему. Например, если дан массив $[4, \hat{4}, 5]$ и целевое значение $9$ , то существующий код вернет результат $[4, 5], [\hat{4}, 5]$ , то есть с повторяющимся подмножеством. **Причина появления дублей в том, что равные элементы выбираются несколько раз в одном и том же раунде**. На рисунке ниже в первом раунде существует три варианта выбора, и два из них равны $4$ ; из-за этого появляются две дублирующиеся ветви поиска и, соответственно, повторяющиеся подмножества. Точно так же два элемента $4$ во втором раунде тоже порождают дубликаты. ![Повторяющиеся подмножества из-за равных элементов](subset_sum_problem.assets/subset_sum_ii_repeat.png) ### Обрезка равных элементов Чтобы решить эту проблему, **нужно ограничить выбор равных элементов так, чтобы в каждом раунде каждый из них выбирался только один раз**. Реализуется это довольно естественно: поскольку массив отсортирован, равные элементы стоят рядом. Значит, если в текущем раунде текущий элемент равен соседнему слева, то этот вариант уже был рассмотрен, и текущий элемент нужно пропустить. Одновременно **по условию этой задачи каждый элемент массива можно выбрать только один раз**. К счастью, это ограничение тоже можно реализовать через переменную `start` : после выбора элемента $x_i$ следующий раунд начинается с индекса $i + 1$ . Так мы одновременно убираем повторяющиеся подмножества и исключаем повторный выбор одного и того же элемента. ### Реализация кода ```src [file]{subset_sum_ii}-[class]{}-[func]{subset_sum_ii} ``` На рисунке ниже показан процесс поиска с возвратом для массива $[4, 4, 5]$ и целевого значения $9$ . В нем используются четыре вида обрезки. Попробуйте сопоставить рисунок с комментариями в коде, чтобы понять полный процесс поиска и то, как работает каждый тип обрезки. ![Процесс поиска с возвратом для задачи о сумме подмножеств II](subset_sum_problem.assets/subset_sum_ii.png) ================================================ FILE: ru/docs/chapter_backtracking/summary.md ================================================ # Резюме ### Ключевые выводы - Алгоритм поиска с возвратом по своей сути является методом полного перебора: он ищет решения путем обхода пространства решений в глубину. Во время поиска он фиксирует решения, удовлетворяющие условиям, пока не найдет все такие решения или пока обход не завершится. - Процесс поиска с возвратом состоит из двух частей: попытки и отката. Он с помощью поиска в глубину пробует разные варианты выбора; когда встречается состояние, не удовлетворяющее ограничениям, алгоритм отменяет предыдущий выбор, возвращается к прошлому состоянию и продолжает пробовать другие варианты. Попытка и откат являются двумя противоположными по направлению действиями. - Задачи поиска с возвратом обычно содержат несколько ограничений, которые можно использовать для обрезки. Обрезка позволяет заранее завершать ненужные ветви поиска и тем самым значительно повышать эффективность. - Алгоритм поиска с возвратом в первую очередь применяется для решения поисковых задач и задач с ограничениями. Задачи комбинаторной оптимизации тоже можно решать с его помощью, но для них часто существуют более эффективные или более подходящие методы. - Задача о перестановках нацелена на поиск всех возможных перестановок элементов данного множества. Мы используем массив для записи того, был ли выбран каждый элемент, и отсекаем ветви, где один и тот же элемент выбирается повторно, чтобы гарантировать однократный выбор каждого элемента. - В задаче о перестановках, если во множестве присутствуют повторяющиеся элементы, в итоговом результате возникнут повторяющиеся перестановки. Поэтому нужно ограничить выбор равных элементов так, чтобы в каждом раунде каждый из них выбирался только один раз; обычно это реализуется с помощью хеш-множества. - Цель задачи о сумме подмножеств - найти все подмножества данного множества, сумма которых равна целевому значению. В множестве порядок элементов не важен, однако процесс поиска порождает результаты во всех возможных порядках, из-за чего появляются повторяющиеся подмножества. Поэтому перед запуском поиска с возвратом мы сортируем данные и вводим переменную, указывающую начальную точку обхода в каждом раунде, чтобы отсечь ветви, создающие дубликаты. - В задаче о сумме подмножеств равные элементы массива также порождают повторяющиеся множества. При наличии предварительной сортировки их можно отсекать, проверяя равенство соседних элементов, и тем самым гарантировать, что в каждом раунде равные элементы будут выбираться только один раз. - Задача о $n$ ферзях состоит в поиске способов разместить $n$ ферзей на доске размера $n \times n$ так, чтобы никакие два ферзя не атаковали друг друга. Ограничения этой задачи включают строки, столбцы, главные диагонали и побочные диагонали. Чтобы выполнить ограничение по строкам, используется построчная стратегия размещения, гарантирующая по одному ферзю в каждой строке. - Обработка ограничений по столбцам и диагоналям устроена похожим образом. Для ограничения по столбцам используется массив, фиксирующий наличие ферзя в каждом столбце. Для диагоналей используются два массива, записывающие наличие ферзей на главных и побочных диагоналях. Основная сложность здесь состоит в том, чтобы найти закономерность индексов строк и столбцов клеток, лежащих на одной и той же главной или побочной диагонали. ### Вопросы и ответы **Q**: Как понять связь между поиском с возвратом и рекурсией? В целом поиск с возвратом - это скорее "алгоритмическая стратегия", а рекурсия больше похожа на "инструмент". - Алгоритмы поиска с возвратом обычно реализуются на основе рекурсии. Однако поиск с возвратом - это лишь один из вариантов применения рекурсии, а именно ее использование в поисковых задачах. - Структура рекурсии отражает парадигму разбиения на подзадачи и часто применяется для решения задач "разделяй и властвуй", поиска с возвратом, динамического программирования (мемоизированной рекурсии) и других подобных задач. ================================================ FILE: ru/docs/chapter_computational_complexity/index.md ================================================ # Анализ сложности ![Анализ сложности](../assets/covers/chapter_complexity_analysis.jpg) !!! abstract Анализ сложности подобен пространственно-временному проводнику в необъятной вселенной алгоритмов. Он ведет нас в глубину двух измерений - времени и пространства, помогая искать более изящные решения. ================================================ FILE: ru/docs/chapter_computational_complexity/iteration_and_recursion.md ================================================ # Итерация и рекурсия В алгоритмах часто требуется повторное выполнение определенной задачи, что тесно связано с анализом сложности. Поэтому, прежде чем перейти к обсуждению временной и пространственной сложности, рассмотрим, как реализовать повторное выполнение задач в программе, а именно две основные структуры управления программой: итерацию и рекурсию. ## Итерация Итерация (iteration) - это структура управления, которая позволяет повторно выполнять определенную задачу. В итерации программа повторяет выполнение определенного участка кода, пока выполняется определенное условие. ### Цикл for Цикл `for` - одна из наиболее распространенных форм итерации, **которая подходит для использования, когда количество итераций известно заранее**. Следующая функция реализует суммирование $1 + 2 + \dots + n$ с использованием цикла `for` , а результат суммирования сохраняется в переменной `res` . Следует отметить, что в Python диапазон `range(a, b)` соответствует левому закрытому, правому открытому интервалу, то есть перебираются значения $a, a + 1, \dots, b-1$ : ```src [file]{iteration}-[class]{}-[func]{for_loop} ``` Ниже представлена блок-схема этой функции суммирования. ![Блок-схема функции суммирования](iteration_and_recursion.assets/iteration.png) Количество операций этой функции суммирования пропорционально размеру входных данных $n$ , или, другими словами, линейно зависит от него. **На самом деле временная сложность описывает именно эту линейную зависимость**. Соответствующий материал будет подробно рассмотрен в следующем разделе. ### Цикл while Подобно циклу `for` , цикл `while` также представляет собой метод реализации итерации. В цикле `while` программа перед каждой итерацией проверяет условие: если условие истинно, то выполнение продолжается, иначе цикл завершается. Ниже приведен пример реализации суммирования $1 + 2 + \dots + n$ с использованием цикла `while` : ```src [file]{iteration}-[class]{}-[func]{while_loop} ``` **Цикл `while` обладает большей степенью свободы по сравнению с циклом `for` **. В цикле `while` можно свободно управлять инициализацией и обновлением условной переменной. Например, в следующем коде условная переменная $i$ обновляется дважды на каждой итерации, что затруднительно сделать с использованием цикла `for` : ```src [file]{iteration}-[class]{}-[func]{while_loop_ii} ``` В целом **код с использованием цикла `for` более компактный, а цикл `while` более гибкий**. Но они оба могут реализовать итерационную структуру. Выбор между ними определяется требованиями конкретной задачи. ### Вложенные циклы Внутрь одной циклической структуры можно вложить другую, например используя два цикла `for` : ```src [file]{iteration}-[class]{}-[func]{nested_for_loop} ``` Ниже приведена блок-схема такого вложенного цикла. ![Блок-схема вложенного цикла](iteration_and_recursion.assets/nested_iteration.png) В этом случае количество выполненных действий пропорционально $n^2$ , или, другими словами, время выполнения алгоритма и размер входных данных $n$ находятся в квадратичной зависимости. Можно и дальше добавлять вложенные циклы, тогда каждое вложение будет повышать размерность, увеличивая временную сложность до кубической зависимости, зависимости четвертой степени и так далее. ## Рекурсия Рекурсия (recursion) - это стратегия алгоритма, при которой функция вызывает саму себя для решения задачи. Она включает два основных этапа. 1. **Вызов**: программа постоянно вызывает саму себя, обычно передавая меньшие или более упрощенные параметры, пока не будет достигнуто условие завершения. 2. **Возврат**: после срабатывания условия завершения программа начинает возвращаться из самой глубокой рекурсивной функции, объединяя результаты каждого уровня. С точки зрения реализации рекурсивный код включает три основных элемента. 1. **Условие завершения**: используется для определения момента перехода от вызова к возврату. 2. **Рекурсивный вызов**: соответствует вызову, функция вызывает саму себя, обычно с меньшими или упрощенными параметрами. 3. **Возврат результата**: соответствует возврату, возвращает результат текущего уровня рекурсии на предыдущий уровень. Рассмотрим следующий код: вызов функции `recur(n)` позволяет вычислить сумму $1 + 2 + \dots + n$ : ```src [file]{recursion}-[class]{}-[func]{recur} ``` Ниже представлен рекурсивный процесс этой функции. ![Рекурсивный процесс функции суммирования](iteration_and_recursion.assets/recursion_sum.png) Хотя с точки зрения вычислений итерация и рекурсия могут давать одинаковый результат, **они представляют собой совершенно разные парадигмы мышления и решения задач**. - **Итерация**: решение задачи снизу вверх. Начинаем с самых базовых шагов, которые затем повторяются или накапливаются до завершения задачи. - **Рекурсия**: решение задачи сверху вниз. Исходная задача разбивается на более мелкие подзадачи, которые имеют ту же форму, что и исходная задача. Далее подзадачи продолжают делиться на еще более мелкие, пока не достигается базовый случай, решение которого известно. Рассмотрим в качестве примера вышеупомянутую функцию суммирования, где решается задача $f(n) = 1 + 2 + \dots + n$ . - **Итерация**: моделирование процесса суммирования в цикле проходит от $1$ до $n$ , выполняя операцию суммирования на каждом шаге, чтобы получить итоговое значение $f(n)$ . - **Рекурсия**: последовательное разбиение задачи на подзадачи вида $f(n) = n + f(n - 1)$ до достижения базового случая $f(1) = 1$ . ### Стек вызовов Каждый раз, когда рекурсивная функция вызывает саму себя, система выделяет память для нового вызова функции, чтобы хранить локальные переменные, адрес вызова и другую информацию. Это поведение имеет два последствия. - Контекстные данные функции хранятся в области памяти, называемой пространством стекового кадра, и освобождаются только после возврата функции. **Поэтому рекурсия обычно требует больше памяти, чем итерация**. - Рекурсивный вызов функции создает дополнительные накладные расходы. **Поэтому рекурсия обычно менее эффективна по времени, чем цикл**. Как показано на рисунке ниже, до срабатывания условия завершения одновременно существует $n$ невозвращенных рекурсивных функций, а **глубина рекурсии равна $n$** . ![Глубина рекурсивного вызова](iteration_and_recursion.assets/recursion_sum_depth.png) На практике глубина рекурсии, разрешенная языком программирования, обычно ограничена, и слишком глубокая рекурсия может привести к ошибке переполнения стека. ### Хвостовая рекурсия Интересно, что **если рекурсивный вызов происходит на последнем шаге перед возвратом функции** , то компилятор или интерпретатор может оптимизировать этот вызов, сделав его по эффективности использования памяти сопоставимым с итерацией. Это называется хвостовой рекурсией (tail recursion). - **Обычная рекурсия**: когда функция возвращается на предыдущий уровень, необходимо продолжить выполнение кода, поэтому системе нужно сохранить контекст предыдущего вызова. - **Хвостовая рекурсия**: рекурсивный вызов является последней операцией перед возвратом функции, что означает, что после возврата на предыдущий уровень не требуется выполнять другие операции, поэтому системе не нужно сохранять контекст предыдущей функции. В качестве примера вычисления суммы $1 + 2 + \dots + n$ можно установить переменную результата `res` в качестве параметра функции, чтобы реализовать хвостовую рекурсию: ```src [file]{recursion}-[class]{}-[func]{tail_recur} ``` Процесс выполнения хвостовой рекурсии показан на рисунке ниже. Сравнивая обычную и хвостовую рекурсии, можно заметить, что точка выполнения операции суммирования у них различается. - **Обычная рекурсия**: операция суммирования выполняется в процессе возврата, после каждого возврата необходимо снова выполнить операцию суммирования. - **Хвостовая рекурсия**: операция суммирования выполняется в процессе вызова, а процесс возврата требует только последовательного возврата. ![Процесс хвостовой рекурсии](iteration_and_recursion.assets/tail_recursion_sum.png) !!! tip Обратите внимание: многие компиляторы и интерпретаторы не поддерживают оптимизацию хвостовой рекурсии. Например, Python по умолчанию такую оптимизацию не выполняет, поэтому даже функция в хвостово-рекурсивной форме все равно может привести к переполнению стека. ### Дерево рекурсии При решении задач, связанных с алгоритмами типа "разделяй и властвуй", рекурсия часто оказывается более интуитивной и читабельной, чем итерация. Рассмотрим в качестве примера последовательность Фибоначчи. !!! question Дана последовательность Фибоначчи $0, 1, 1, 2, 3, 5, 8, 13, \dots$ ; найди $n$-й элемент этой последовательности. Обозначив $n$-й член последовательности Фибоначчи как $f(n)$ , можно сформулировать два утверждения. - Первые два числа последовательности: $f(1) = 0$ и $f(2) = 1$ . - Каждое число последовательности является суммой двух предыдущих чисел, то есть $f(n) = f(n - 1) + f(n - 2)$ . Используя рекурсивные вызовы в соответствии с рекуррентным соотношением и принимая первые два числа за условия остановки, можно написать рекурсивный код. Вызов `fib(n)` позволит получить $n$-й член последовательности Фибоначчи: ```src [file]{recursion}-[class]{}-[func]{fib} ``` Проанализировав приведенный код, можно заметить, что внутри функции осуществляется рекурсивный вызов двух функций, **то есть из одного вызова образуются два ветвления**. Как показано на рисунке ниже, при последующем выполнении рекурсивных вызовов в итоге образуется дерево рекурсии (recursion tree) глубиной $n$ . ![Дерево рекурсии последовательности Фибоначчи](iteration_and_recursion.assets/recursion_tree.png) По своей сути рекурсия отражает парадигму мышления "разбиение задачи на более мелкие подзадачи", что делает стратегию "разделяй и властвуй" крайне важной. - С точки зрения **алгоритмов** многие важные алгоритмические стратегии, такие как поиск, сортировка, возврат, "разделяй и властвуй" и динамическое программирование, прямо или косвенно используют этот подход. - С точки зрения **структур данных** рекурсия естественно подходит для решения задач, связанных со списками, деревьями и графами, поскольку они очень хорошо поддаются анализу с использованием идеи "разделяй и властвуй". ## Сравнение Подводя итог, можно сказать, что итерация и рекурсия различаются по реализации, производительности и применимости, как показано в таблице ниже.

Таблица   Сравнение итерации и рекурсии

| | Итерация | Рекурсия | | -------- | -------------------------------------- | ------------------------------------------------------------ | | Способ реализации | Циклическая структура | Функция вызывает саму себя | | Временная эффективность | Обычно высокая эффективность, нет затрат на вызов функции | Каждый вызов функции создает затраты | | Использование памяти | Обычно используется фиксированный объем памяти | Накопление вызовов функции может использовать значительное количество пространства стека | | Сфера использования | Подходит для простых циклических задач, код интуитивно понятен и хорошо читаем | Подходит для разбиения на подзадачи, для структур деревья и графы, алгоритмов "разделяй и властвуй", возврата и т. д.; структура кода проста и ясна | !!! tip Если дальнейшее содержание кажется сложным, можно вернуться к нему после чтения главы о "стеке". Какова же внутренняя связь между итерацией и рекурсией? В рассмотренном примере рекурсивной функции операция сложения выполняется на этапе возврата рекурсии. Это означает, что функция, вызванная первой, фактически завершает операцию сложения последней, **что соответствует принципу стека "первым пришел - последним вышел"**. На самом деле такие термины рекурсии, как "стек вызовов" и "пространство стекового кадра", уже намекают на тесную связь между рекурсией и стеком. 1. **Вызов**: когда вызывается функция, система выделяет для нее новый стековый кадр в "стеке вызовов" для хранения локальных переменных функции, параметров, адреса возврата и других данных. 2. **Возврат**: когда функция завершает выполнение и возвращает результат, соответствующий стековый кадр удаляется из "стека вызовов", восстанавливая среду выполнения предыдущей функции. Таким образом, **можно использовать явный стек для моделирования поведения стека вызовов**, чтобы преобразовать рекурсию в итеративную форму: ```src [file]{recursion}-[class]{}-[func]{for_loop_recur} ``` Наблюдая за приведенным выше кодом, можно заметить, что после преобразования рекурсии в итерацию код становится более сложным. Хотя во многих случаях итерация и рекурсия действительно могут быть преобразованы друг в друга, это не всегда стоит делать по двум причинам. - Преобразованный код может стать труднее для понимания и менее читаемым. - Для некоторых сложных задач моделирование поведения системного стека вызовов может оказаться очень трудным. Итак, **выбор между итерацией и рекурсией зависит от природы конкретной задачи**. В практическом программировании крайне важно взвешивать преимущества и недостатки обоих подходов и выбирать подходящий метод с учетом контекста. ================================================ FILE: ru/docs/chapter_computational_complexity/performance_evaluation.md ================================================ # Оценка эффективности алгоритмов В процессе разработки алгоритмов мы стремимся к достижению следующих целей. 1. **Найти решение задачи**: алгоритм должен надежно находить правильное решение задачи в заданных пределах входных данных. 2. **Найти оптимальное решение**: для одной и той же задачи может существовать несколько решений, и мы стремимся найти максимально эффективный алгоритм. Таким образом, при условии возможности решения задачи эффективность алгоритма становится основным критерием его оценки, который включает два аспекта. - **Временная эффективность**: продолжительность выполнения алгоритма. - **Пространственная эффективность**: объем памяти, занимаемой алгоритмом. В двух словах, **наша цель - разработка быстрых и экономных структур данных и алгоритмов**. Эффективная оценка алгоритмов крайне важна, так как только так можно сравнивать различные алгоритмы и управлять процессом их разработки и оптимизации. Методы оценки эффективности делятся на два типа: практическое тестирование и теоретическую оценку. ## Практическое тестирование Предположим, у нас есть алгоритмы `A` и `B`, которые решают одну и ту же задачу, и необходимо сравнить их эффективность. Самый прямой метод - это запустить оба алгоритма на компьютере и зафиксировать время их выполнения и объем используемой памяти. Этот метод отражает реальную ситуацию, но имеет значительные ограничения. С одной стороны, **сложно исключить влияние факторов тестовой среды**. Аппаратная конфигурация влияет на производительность алгоритма. Например, если алгоритм обладает высокой степенью параллелизма, он будет лучше работать на многоядерных CPU; если алгоритм интенсивно использует память, его производительность будет выше на высокопроизводительной памяти. Это означает, что результаты тестирования на разных машинах могут значительно отличаться, а для получения средней эффективности пришлось бы тестировать на различных платформах, что крайне затруднительно. С другой стороны, **проведение полного тестирования требует значительных ресурсов**. С изменением объема входных данных алгоритмы демонстрируют разную эффективность. Например, при небольшом объеме данных алгоритм `A` может работать быстрее, чем алгоритм `B`, но при большом объеме данных результат может быть противоположным. Следовательно, для получения убедительных выводов необходимо тестировать различные масштабы входных данных, что требует значительных вычислительных ресурсов. ## Теоретическая оценка Из-за значительных ограничений практического тестирования можно рассмотреть возможность оценки эффективности алгоритмов только с помощью вычислений. Такой метод называется анализом асимптотической сложности (asymptotic complexity analysis), или сокращенно анализом сложности. Анализ сложности позволяет отразить зависимость между ресурсами времени и пространства, необходимыми для выполнения алгоритма, и размером входных данных. **Он описывает тенденцию роста времени и пространства, необходимых для выполнения алгоритма, по мере увеличения размера входных данных**. Это определение может показаться сложным, но его можно разбить на три ключевых момента. - "Ресурсы времени и пространства" соответствуют временной сложности (time complexity) и пространственной сложности (space complexity). - "По мере увеличения размера входных данных" означает, что сложность отражает зависимость эффективности алгоритма от объема входных данных. - "Тенденция роста времени и пространства" указывает, что анализ сложности фокусируется не на конкретных значениях времени выполнения или объема занимаемой памяти, а на скорости их роста. **Анализ сложности преодолевает недостатки метода практического тестирования**, что выражается в следующих аспектах. - Он не требует фактического выполнения кода, что делает его более экологичным и энергосберегающим. - Он независим от тестовой среды, а результаты анализа применимы ко всем платформам выполнения. - Он может продемонстрировать эффективность алгоритма при различных объемах данных, особенно при больших объемах. !!! tip Если понятие сложности пока все еще кажется вам запутанным, не переживайте: мы подробно разберем его в следующих разделах. Анализ сложности предоставляет нам мерило оценки эффективности алгоритмов, позволяя измерять время и ресурсы, необходимые для выполнения конкретного алгоритма, а также сравнивать эффективность различных алгоритмов. Сложность - это математическое понятие, которое новичкам может показаться абстрактным и сложным для изучения. С этой точки зрения анализ сложности не то, с чего стоит начинать изучение алгоритмов. Однако, обсуждая особенности той или иной структуры данных или алгоритма, невозможно избежать анализа их скорости выполнения и использования памяти. Таким образом, перед погружением в изучение структур данных и алгоритмов рекомендуется получить базовое представление об анализе сложности, чтобы иметь возможность выполнять хотя бы базовую оценку их эффективности. ================================================ FILE: ru/docs/chapter_computational_complexity/space_complexity.md ================================================ # Пространственная сложность Пространственная сложность (space complexity) служит для оценки того, как меняется объем памяти, требуемой алгоритму, по мере роста объема данных. Это понятие очень похоже на временную сложность, только вместо времени выполнения рассматривается объем используемой памяти. ## Пространство, связанное с алгоритмом Память, которую использует алгоритм во время работы, в основном делится на следующие части. - **Входное пространство**: используется для хранения входных данных алгоритма. - **Временное пространство**: используется для хранения переменных, объектов, контекста функций и других данных, возникающих во время выполнения алгоритма. - **Выходное пространство**: используется для хранения выходных данных алгоритма. Как правило, при анализе пространственной сложности в расчет включают временное пространство и выходное пространство. Временное пространство можно дополнительно разделить на три части. - **Временные данные**: используются для хранения различных констант, переменных, объектов и т.д., возникающих во время выполнения алгоритма. - **Пространство кадров стека**: используется для хранения контекстных данных вызываемых функций. При каждом вызове функции система создает на вершине стека новый кадр; после возврата функции пространство этого кадра освобождается. - **Пространство инструкций**: используется для хранения скомпилированных инструкций программы и в реальном подсчете обычно не учитывается. При анализе пространственной сложности программы **обычно учитываются временные данные, пространство стека и выходные данные**, как показано на рисунке ниже. ![Пространство, используемое алгоритмом](space_complexity.assets/space_types.png) Ниже приведен соответствующий код: === "Python" ```python title="" class Node: """Класс""" def __init__(self, x: int): self.val: int = x # Значение узла self.next: Node | None = None # Ссылка на следующий узел def function() -> int: """Функция""" # Выполнить некоторые операции... return 0 def algorithm(n) -> int: # Входные данные A = 0 # Временные данные (константа, обычно обозначается заглавной буквой) b = 0 # Временные данные (переменная) node = Node(0) # Временные данные (объект) c = function() # Пространство кадра стека (вызов функции) return A + b + c # Выходные данные ``` === "C++" ```cpp title="" /* Структура */ struct Node { int val; Node *next; Node(int x) : val(x), next(nullptr) {} }; /* Функция */ int func() { // Выполнить некоторые операции... return 0; } int algorithm(int n) { // Входные данные const int a = 0; // Временные данные (константа) int b = 0; // Временные данные (переменная) Node* node = new Node(0); // Временные данные (объект) int c = func(); // Пространство кадра стека (вызов функции) return a + b + c; // Выходные данные } ``` === "Java" ```java title="" /* Класс */ class Node { int val; Node next; Node(int x) { val = x; } } /* Функция */ int function() { // Выполнить некоторые операции... return 0; } int algorithm(int n) { // Входные данные final int a = 0; // Временные данные (константа) int b = 0; // Временные данные (переменная) Node node = new Node(0); // Временные данные (объект) int c = function(); // Пространство кадра стека (вызов функции) return a + b + c; // Выходные данные } ``` === "C#" ```csharp title="" /* Класс */ class Node(int x) { int val = x; Node next; } /* Функция */ int Function() { // Выполнить некоторые операции... return 0; } int Algorithm(int n) { // Входные данные const int a = 0; // Временные данные (константа) int b = 0; // Временные данные (переменная) Node node = new(0); // Временные данные (объект) int c = Function(); // Пространство кадра стека (вызов функции) return a + b + c; // Выходные данные } ``` === "Go" ```go title="" /* Структура */ type node struct { val int next *node } /* Создать структуру node */ func newNode(val int) *node { return &node{val: val} } /* Функция */ func function() int { // Выполнить некоторые операции... return 0 } func algorithm(n int) int { // Входные данные const a = 0 // Временные данные (константа) b := 0 // Временные данные (переменная) newNode(0) // Временные данные (объект) c := function() // Пространство кадра стека (вызов функции) return a + b + c // Выходные данные } ``` === "Swift" ```swift title="" /* Класс */ class Node { var val: Int var next: Node? init(x: Int) { val = x } } /* Функция */ func function() -> Int { // Выполнить некоторые операции... return 0 } func algorithm(n: Int) -> Int { // Входные данные let a = 0 // Временные данные (константа) var b = 0 // Временные данные (переменная) let node = Node(x: 0) // Временные данные (объект) let c = function() // Пространство кадра стека (вызов функции) return a + b + c // Выходные данные } ``` === "JS" ```javascript title="" /* Класс */ class Node { val; next; constructor(val) { this.val = val === undefined ? 0 : val; // Значение узла this.next = null; // Ссылка на следующий узел } } /* Функция */ function constFunc() { // Выполнить некоторые операции return 0; } function algorithm(n) { // Входные данные const a = 0; // Временные данные (константа) let b = 0; // Временные данные (переменная) const node = new Node(0); // Временные данные (объект) const c = constFunc(); // Пространство кадра стека (вызов функции) return a + b + c; // Выходные данные } ``` === "TS" ```typescript title="" /* Класс */ class Node { val: number; next: Node | null; constructor(val?: number) { this.val = val === undefined ? 0 : val; // Значение узла this.next = null; // Ссылка на следующий узел } } /* Функция */ function constFunc(): number { // Выполнить некоторые операции return 0; } function algorithm(n: number): number { // Входные данные const a = 0; // Временные данные (константа) let b = 0; // Временные данные (переменная) const node = new Node(0); // Временные данные (объект) const c = constFunc(); // Пространство кадра стека (вызов функции) return a + b + c; // Выходные данные } ``` === "Dart" ```dart title="" /* Класс */ class Node { int val; Node next; Node(this.val, [this.next]); } /* Функция */ int function() { // Выполнить некоторые операции... return 0; } int algorithm(int n) { // Входные данные const int a = 0; // Временные данные (константа) int b = 0; // Временные данные (переменная) Node node = Node(0); // Временные данные (объект) int c = function(); // Пространство кадра стека (вызов функции) return a + b + c; // Выходные данные } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* Структура */ struct Node { val: i32, next: Option>>, } /* Создать структуру Node */ impl Node { fn new(val: i32) -> Self { Self { val: val, next: None } } } /* Функция */ fn function() -> i32 { // Выполнить некоторые операции... return 0; } fn algorithm(n: i32) -> i32 { // Входные данные const a: i32 = 0; // Временные данные (константа) let mut b = 0; // Временные данные (переменная) let node = Node::new(0); // Временные данные (объект) let c = function(); // Пространство кадра стека (вызов функции) return a + b + c; // Выходные данные } ``` === "C" ```c title="" /* Функция */ int func() { // Выполнить некоторые операции... return 0; } int algorithm(int n) { // Входные данные const int a = 0; // Временные данные (константа) int b = 0; // Временные данные (переменная) int c = func(); // Пространство кадра стека (вызов функции) return a + b + c; // Выходные данные } ``` === "Kotlin" ```kotlin title="" /* Класс */ class Node(var _val: Int) { var next: Node? = null } /* Функция */ fun function(): Int { // Выполнить некоторые операции... return 0 } fun algorithm(n: Int): Int { // Входные данные val a = 0 // Временные данные (константа) var b = 0 // Временные данные (переменная) val node = Node(0) // Временные данные (объект) val c = function() // Пространство кадра стека (вызов функции) return a + b + c // Выходные данные } ``` === "Ruby" ```ruby title="" ### Класс ### class Node attr_accessor :val # Значение узла attr_accessor :next # Ссылка на следующий узел def initialize(x) @val = x end end ### Функция ### def function # Выполнить некоторые операции... 0 end ### Алгоритм ### def algorithm(n) # Входные данные a = 0 # Временные данные (константа) b = 0 # Временные данные (переменная) node = Node.new(0) # Временные данные (объект) c = function # Пространство кадра стека (вызов функции) a + b + c # Выходные данные end ``` ## Метод вывода Метод вывода пространственной сложности в целом аналогичен выводу временной сложности: меняется только объект подсчета, с количества операций на размер используемого пространства. В отличие от временной сложности, **обычно рассматривается только худшая пространственная сложность**. Это связано с тем, что память является жестким ограничением: необходимо гарантировать, что для любых входных данных у программы будет достаточно памяти. Рассмотрим следующий код. Понятие худшей пространственной сложности здесь имеет два значения. 1. **Ориентир на худшие входные данные**: когда $n < 10$ , пространственная сложность равна $O(1)$ ; но когда $n > 10$ , инициализированный массив `nums` занимает $O(n)$ пространства, поэтому худшая пространственная сложность равна $O(n)$ . 2. **Ориентир на пиковое использование памяти во время выполнения**: например, до выполнения последней строки программа занимает $O(1)$ пространства; при инициализации массива `nums` она занимает $O(n)$ пространства, поэтому худшая пространственная сложность также равна $O(n)$ . === "Python" ```python title="" def algorithm(n: int): a = 0 # O(1) b = [0] * 10000 # O(1) if n > 10: nums = [0] * n # O(n) ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 0; // O(1) vector b(10000); // O(1) if (n > 10) vector nums(n); // O(n) } ``` === "Java" ```java title="" void algorithm(int n) { int a = 0; // O(1) int[] b = new int[10000]; // O(1) if (n > 10) int[] nums = new int[n]; // O(n) } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 0; // O(1) int[] b = new int[10000]; // O(1) if (n > 10) { int[] nums = new int[n]; // O(n) } } ``` === "Go" ```go title="" func algorithm(n int) { a := 0 // O(1) b := make([]int, 10000) // O(1) var nums []int if n > 10 { nums := make([]int, n) // O(n) } fmt.Println(a, b, nums) } ``` === "Swift" ```swift title="" func algorithm(n: Int) { let a = 0 // O(1) let b = Array(repeating: 0, count: 10000) // O(1) if n > 10 { let nums = Array(repeating: 0, count: n) // O(n) } } ``` === "JS" ```javascript title="" function algorithm(n) { const a = 0; // O(1) const b = new Array(10000); // O(1) if (n > 10) { const nums = new Array(n); // O(n) } } ``` === "TS" ```typescript title="" function algorithm(n: number): void { const a = 0; // O(1) const b = new Array(10000); // O(1) if (n > 10) { const nums = new Array(n); // O(n) } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 0; // O(1) List b = List.filled(10000, 0); // O(1) if (n > 10) { List nums = List.filled(n, 0); // O(n) } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let a = 0; // O(1) let b = [0; 10000]; // O(1) if n > 10 { let nums = vec![0; n as usize]; // O(n) } } ``` === "C" ```c title="" void algorithm(int n) { int a = 0; // O(1) int b[10000]; // O(1) if (n > 10) int nums[n] = {0}; // O(n) } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { val a = 0 // O(1) val b = IntArray(10000) // O(1) if (n > 10) { val nums = IntArray(n) // O(n) } } ``` === "Ruby" ```ruby title="" def algorithm(n) a = 0 # O(1) b = Array.new(10000) # O(1) nums = Array.new(n) if n > 10 # O(n) end ``` **В рекурсивных функциях необходимо учитывать пространство кадров стека**. Рассмотрим следующий код: === "Python" ```python title="" def function() -> int: # Выполнить некоторые операции return 0 def loop(n: int): """Пространственная сложность цикла равна O(1)""" for _ in range(n): function() def recur(n: int): """Пространственная сложность рекурсии равна O(n)""" if n == 1: return return recur(n - 1) ``` === "C++" ```cpp title="" int func() { // Выполнить некоторые операции return 0; } /* Пространственная сложность цикла равна O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } /* Пространственная сложность рекурсии равна O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Java" ```java title="" int function() { // Выполнить некоторые операции return 0; } /* Пространственная сложность цикла равна O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } /* Пространственная сложность рекурсии равна O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "C#" ```csharp title="" int Function() { // Выполнить некоторые операции return 0; } /* Пространственная сложность цикла равна O(1) */ void Loop(int n) { for (int i = 0; i < n; i++) { Function(); } } /* Пространственная сложность рекурсии равна O(n) */ int Recur(int n) { if (n == 1) return 1; return Recur(n - 1); } ``` === "Go" ```go title="" func function() int { // Выполнить некоторые операции return 0 } /* Пространственная сложность цикла равна O(1) */ func loop(n int) { for i := 0; i < n; i++ { function() } } /* Пространственная сложность рекурсии равна O(n) */ func recur(n int) { if n == 1 { return } recur(n - 1) } ``` === "Swift" ```swift title="" @discardableResult func function() -> Int { // Выполнить некоторые операции return 0 } /* Пространственная сложность цикла равна O(1) */ func loop(n: Int) { for _ in 0 ..< n { function() } } /* Пространственная сложность рекурсии равна O(n) */ func recur(n: Int) { if n == 1 { return } recur(n: n - 1) } ``` === "JS" ```javascript title="" function constFunc() { // Выполнить некоторые операции return 0; } /* Пространственная сложность цикла равна O(1) */ function loop(n) { for (let i = 0; i < n; i++) { constFunc(); } } /* Пространственная сложность рекурсии равна O(n) */ function recur(n) { if (n === 1) return; return recur(n - 1); } ``` === "TS" ```typescript title="" function constFunc(): number { // Выполнить некоторые операции return 0; } /* Пространственная сложность цикла равна O(1) */ function loop(n: number): void { for (let i = 0; i < n; i++) { constFunc(); } } /* Пространственная сложность рекурсии равна O(n) */ function recur(n: number): void { if (n === 1) return; return recur(n - 1); } ``` === "Dart" ```dart title="" int function() { // Выполнить некоторые операции return 0; } /* Пространственная сложность цикла равна O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } /* Пространственная сложность рекурсии равна O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Rust" ```rust title="" fn function() -> i32 { // Выполнить некоторые операции return 0; } /* Пространственная сложность цикла равна O(1) */ fn loop(n: i32) { for i in 0..n { function(); } } /* Пространственная сложность рекурсии равна O(n) */ fn recur(n: i32) { if n == 1 { return; } recur(n - 1); } ``` === "C" ```c title="" int func() { // Выполнить некоторые операции return 0; } /* Пространственная сложность цикла равна O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } /* Пространственная сложность рекурсии равна O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Kotlin" ```kotlin title="" fun function(): Int { // Выполнить некоторые операции return 0 } /* Пространственная сложность цикла равна O(1) */ fun loop(n: Int) { for (i in 0..Функция (function) может выполняться независимо, и все ее параметры передаются явно. Метод (method) связан с объектом, неявно получает объект, который его вызывает, и может работать с данными, содержащимися в экземпляре класса. Ниже это проиллюстрировано на примере нескольких распространенных языков программирования. - C - процедурный язык программирования без объектно-ориентированной модели, поэтому в нем есть только функции. Однако мы можем имитировать объектно-ориентированное программирование через структуры (`struct`), и функции, связанные со структурами, эквивалентны методам в других языках. - Java и C# - объектно-ориентированные языки программирования, в которых блоки кода (методы) обычно являются частью класса. Статические методы по поведению похожи на функции, потому что они привязаны к классу и не могут обращаться к конкретным переменным экземпляра. - C++ и Python поддерживают как процедурное программирование (функции), так и объектно-ориентированное программирование (методы). **Q**: Отражает ли диаграмма "распространенных типов пространственной сложности" абсолютный размер занятой памяти? Нет, эта диаграмма показывает пространственную сложность, а значит отражает именно тенденцию роста, а не абсолютный объем занятого пространства. Если взять $n = 8$ , можно заметить, что значения на кривых не совпадают напрямую с соответствующими функциями. Это связано с тем, что каждая кривая содержит константный член, который сжимает диапазон значений до визуально удобного масштаба. На практике, поскольку мы обычно не знаем, какова "константная" сложность каждого метода, только по сложности мы, как правило, не можем выбрать оптимальное решение для случая $n = 8$ . Но для $n = 8^5$ выбор уже очевиден: в этой области доминирует именно тенденция роста. **Q**: Бывают ли случаи, когда в реальных сценариях алгоритм специально проектируют так, чтобы жертвовать временем ради пространства или пространством ради времени? На практике в большинстве случаев выбирают обмен пространства на время. Например, для индексов в базах данных обычно строят B+ деревья или хеш-индексы, расходуя значительный объем памяти ради эффективных запросов уровня $O(\log n)$ или даже $O(1)$. В сценариях, где память особенно дорога, наоборот, могут жертвовать временем ради пространства. Например, в embedded-разработке память устройства очень ограничена, поэтому инженеры могут отказаться от хеш-таблиц и выбрать последовательный поиск по массиву, экономя память ценой более медленного поиска. ================================================ FILE: ru/docs/chapter_computational_complexity/time_complexity.md ================================================ # Временная сложность Время выполнения действительно может наглядно и точно отражать эффективность алгоритма. Но если мы захотим точно оценить время работы некоторого фрагмента кода, то столкнемся со следующими шагами. 1. **Определить платформу выполнения**, включая конфигурацию оборудования, язык программирования, системную среду и т.д., поскольку все эти факторы влияют на эффективность выполнения кода. 2. **Оценить время выполнения различных вычислительных операций**, например операция сложения `+` требует 1 нс , операция умножения `*` требует 10 нс , операция вывода `print()` требует 5 нс и т.д. 3. **Подсчитать все вычислительные операции в коде** и суммировать время выполнения всех операций, чтобы получить общее время работы. Например, в следующем коде размер входных данных равен $n$ : === "Python" ```python title="" # На некоторой платформе выполнения def algorithm(n: int): a = 2 # 1 нс a = a + 1 # 1 нс a = a * 2 # 10 нс # Цикл выполняется n раз for _ in range(n): # 1 нс print(0) # 5 нс ``` === "C++" ```cpp title="" // На некоторой платформе выполнения void algorithm(int n) { int a = 2; // 1 нс a = a + 1; // 1 нс a = a * 2; // 10 нс // Цикл выполняется n раз for (int i = 0; i < n; i++) { // 1 нс cout << 0 << endl; // 5 нс } } ``` === "Java" ```java title="" // На некоторой платформе выполнения void algorithm(int n) { int a = 2; // 1 нс a = a + 1; // 1 нс a = a * 2; // 10 нс // Цикл выполняется n раз for (int i = 0; i < n; i++) { // 1 нс System.out.println(0); // 5 нс } } ``` === "C#" ```csharp title="" // На некоторой платформе выполнения void Algorithm(int n) { int a = 2; // 1 нс a = a + 1; // 1 нс a = a * 2; // 10 нс // Цикл выполняется n раз for (int i = 0; i < n; i++) { // 1 нс Console.WriteLine(0); // 5 нс } } ``` === "Go" ```go title="" // На некоторой платформе выполнения func algorithm(n int) { a := 2 // 1 нс a = a + 1 // 1 нс a = a * 2 // 10 нс // Цикл выполняется n раз for i := 0; i < n; i++ { // 1 нс fmt.Println(a) // 5 нс } } ``` === "Swift" ```swift title="" // На некоторой платформе выполнения func algorithm(n: Int) { var a = 2 // 1 нс a = a + 1 // 1 нс a = a * 2 // 10 нс // Цикл выполняется n раз for _ in 0 ..< n { // 1 нс print(0) // 5 нс } } ``` === "JS" ```javascript title="" // На некоторой платформе выполнения function algorithm(n) { var a = 2; // 1 нс a = a + 1; // 1 нс a = a * 2; // 10 нс // Цикл выполняется n раз for(let i = 0; i < n; i++) { // 1 нс console.log(0); // 5 нс } } ``` === "TS" ```typescript title="" // На некоторой платформе выполнения function algorithm(n: number): void { var a: number = 2; // 1 нс a = a + 1; // 1 нс a = a * 2; // 10 нс // Цикл выполняется n раз for(let i = 0; i < n; i++) { // 1 нс console.log(0); // 5 нс } } ``` === "Dart" ```dart title="" // На некоторой платформе выполнения void algorithm(int n) { int a = 2; // 1 нс a = a + 1; // 1 нс a = a * 2; // 10 нс // Цикл выполняется n раз for (int i = 0; i < n; i++) { // 1 нс print(0); // 5 нс } } ``` === "Rust" ```rust title="" // На некоторой платформе выполнения fn algorithm(n: i32) { let mut a = 2; // 1 нс a = a + 1; // 1 нс a = a * 2; // 10 нс // Цикл выполняется n раз for _ in 0..n { // 1 нс println!("{}", 0); // 5 нс } } ``` === "C" ```c title="" // На некоторой платформе выполнения void algorithm(int n) { int a = 2; // 1 нс a = a + 1; // 1 нс a = a * 2; // 10 нс // Цикл выполняется n раз for (int i = 0; i < n; i++) { // 1 нс printf("%d", 0); // 5 нс } } ``` === "Kotlin" ```kotlin title="" // На некоторой платформе выполнения fun algorithm(n: Int) { var a = 2 // 1 нс a = a + 1 // 1 нс a = a * 2 // 10 нс // Цикл выполняется n раз for (i in 0.. 1$ он медленнее алгоритма `A` , а при $n > 1000000$ медленнее алгоритма `C` . Если размер входных данных достаточно велик, алгоритм с постоянной сложностью обязательно лучше алгоритма с линейной сложностью. В этом и состоит смысл тенденции роста времени. - **Метод вывода временной сложности проще**. Платформа выполнения и тип вычислительных операций не влияют на тенденцию роста времени работы алгоритма. Поэтому в анализе временной сложности можно считать время выполнения всех вычислительных операций одинаковым единичным временем и тем самым упростить подсчет времени выполнения до подсчета количества операций. - **У временной сложности есть и определенные ограничения**. Например, хотя временная сложность алгоритмов `A` и `C` одинакова, их реальное время выполнения сильно различается. Точно так же, хотя временная сложность `B` выше, чем у `C` , при малых $n$ алгоритм `B` очевидно лучше `C` . Несмотря на эти ограничения, анализ сложности все равно остается самым эффективным и самым распространенным способом оценки алгоритмов. ## Асимптотическая верхняя граница функции Для функции с входным размером $n$ : === "Python" ```python title="" def algorithm(n: int): a = 1 # +1 a = a + 1 # +1 a = a * 2 # +1 # Цикл выполняется n раз for i in range(n): # +1 print(0) # +1 ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // Цикл выполняется n раз for (int i = 0; i < n; i++) { // +1 (каждый раз выполняется i ++) cout << 0 << endl; // +1 } } ``` === "Java" ```java title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // Цикл выполняется n раз for (int i = 0; i < n; i++) { // +1 (каждый раз выполняется i ++) System.out.println(0); // +1 } } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // Цикл выполняется n раз for (int i = 0; i < n; i++) { // +1 (каждый раз выполняется i ++) Console.WriteLine(0); // +1 } } ``` === "Go" ```go title="" func algorithm(n int) { a := 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // Цикл выполняется n раз for i := 0; i < n; i++ { // +1 fmt.Println(a) // +1 } } ``` === "Swift" ```swift title="" func algorithm(n: Int) { var a = 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // Цикл выполняется n раз for _ in 0 ..< n { // +1 print(0) // +1 } } ``` === "JS" ```javascript title="" function algorithm(n) { var a = 1; // +1 a += 1; // +1 a *= 2; // +1 // Цикл выполняется n раз for(let i = 0; i < n; i++){ // +1 (каждый раз выполняется i ++) console.log(0); // +1 } } ``` === "TS" ```typescript title="" function algorithm(n: number): void{ var a: number = 1; // +1 a += 1; // +1 a *= 2; // +1 // Цикл выполняется n раз for(let i = 0; i < n; i++){ // +1 (каждый раз выполняется i ++) console.log(0); // +1 } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // Цикл выполняется n раз for (int i = 0; i < n; i++) { // +1 (каждый раз выполняется i ++) print(0); // +1 } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let mut a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // Цикл выполняется n раз for _ in 0..n { // +1 (каждый раз выполняется i ++) println!("{}", 0); // +1 } } ``` === "C" ```c title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // Цикл выполняется n раз for (int i = 0; i < n; i++) { // +1 (каждый раз выполняется i ++) printf("%d", 0); // +1 } } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { var a = 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // Цикл выполняется n раз for (i in 0..нотацией Big $O$ (big-$O$ notation) и обозначает асимптотическую верхнюю границу (asymptotic upper bound) функции $T(n)$ . Иными словами, анализ временной сложности сводится к определению асимптотической верхней границы числа операций $T(n)$, и у этого понятия есть строгое математическое определение. !!! note "Асимптотическая верхняя граница функции" Если существуют положительное действительное число $c$ и действительное число $n_0$ , такие что для всех $n > n_0$ выполняется $T(n) \leq c \cdot f(n)$ , то можно считать, что $f(n)$ задает асимптотическую верхнюю границу для $T(n)$ ; это записывается как $T(n) = O(f(n))$ . Как показано на рисунке ниже, вычислить асимптотическую верхнюю границу - значит найти такую функцию $f(n)$ , что при стремлении $n$ к бесконечности функции $T(n)$ и $f(n)$ имеют один и тот же порядок роста и отличаются только постоянным коэффициентом $c$. ![Асимптотическая верхняя граница функции](time_complexity.assets/asymptotic_upper_bound.png) ## Метод вывода Математическое определение асимптотической верхней границы выглядит довольно формально, и если оно пока не до конца понятно, переживать не стоит. Сначала можно освоить сам метод вывода, а в процессе дальнейшей практики постепенно почувствовать его математический смысл. Согласно определению, после того как мы определили $f(n)$ , можно получить временную сложность $O(f(n))$ . Но как определить саму асимптотическую верхнюю границу $f(n)$ ? В целом процесс состоит из двух шагов: сначала подсчитать количество операций, затем определить асимптотическую верхнюю границу. ### Шаг 1: подсчет количества операций Для кода это можно делать построчно сверху вниз. Однако, поскольку в выражении $c \cdot f(n)$ постоянный коэффициент $c$ может быть сколь угодно большим, **различные коэффициенты и постоянные члены в числе операций $T(n)$ можно игнорировать**. Исходя из этого принципа, можно сформулировать следующие упрощающие приемы подсчета. 1. **Игнорировать константы в $T(n)$**. Они не зависят от $n$ , а значит не влияют на временную сложность. 2. **Опускать все коэффициенты**. Например, циклы на $2n$ раз или $5n + 1$ раз можно упростить до $n$ раз, потому что коэффициент перед $n$ не влияет на временную сложность. 3. **При вложенных циклах использовать умножение**. Общее число операций равно произведению числа операций внешнего и внутреннего циклов; при этом для каждого уровня цикла по-прежнему можно применять приемы из пунктов `1.` и `2.` . Для заданной функции мы можем использовать перечисленные выше приемы и подсчитать число операций: === "Python" ```python title="" def algorithm(n: int): a = 1 # +0 (прием 1) a = a + n # +0 (прием 1) # +n (прием 2) for i in range(5 * n + 1): print(0) # +n*n (прием 3) for i in range(2 * n): for j in range(n + 1): print(0) ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 1; // +0 (прием 1) a = a + n; // +0 (прием 1) // +n (прием 2) for (int i = 0; i < 5 * n + 1; i++) { cout << 0 << endl; } // +n*n (прием 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { cout << 0 << endl; } } } ``` === "Java" ```java title="" void algorithm(int n) { int a = 1; // +0 (прием 1) a = a + n; // +0 (прием 1) // +n (прием 2) for (int i = 0; i < 5 * n + 1; i++) { System.out.println(0); } // +n*n (прием 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { System.out.println(0); } } } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 1; // +0 (прием 1) a = a + n; // +0 (прием 1) // +n (прием 2) for (int i = 0; i < 5 * n + 1; i++) { Console.WriteLine(0); } // +n*n (прием 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { Console.WriteLine(0); } } } ``` === "Go" ```go title="" func algorithm(n int) { a := 1 // +0 (прием 1) a = a + n // +0 (прием 1) // +n (прием 2) for i := 0; i < 5 * n + 1; i++ { fmt.Println(0) } // +n*n (прием 3) for i := 0; i < 2 * n; i++ { for j := 0; j < n + 1; j++ { fmt.Println(0) } } } ``` === "Swift" ```swift title="" func algorithm(n: Int) { var a = 1 // +0 (прием 1) a = a + n // +0 (прием 1) // +n (прием 2) for _ in 0 ..< (5 * n + 1) { print(0) } // +n*n (прием 3) for _ in 0 ..< (2 * n) { for _ in 0 ..< (n + 1) { print(0) } } } ``` === "JS" ```javascript title="" function algorithm(n) { let a = 1; // +0 (прием 1) a = a + n; // +0 (прием 1) // +n (прием 2) for (let i = 0; i < 5 * n + 1; i++) { console.log(0); } // +n*n (прием 3) for (let i = 0; i < 2 * n; i++) { for (let j = 0; j < n + 1; j++) { console.log(0); } } } ``` === "TS" ```typescript title="" function algorithm(n: number): void { let a = 1; // +0 (прием 1) a = a + n; // +0 (прием 1) // +n (прием 2) for (let i = 0; i < 5 * n + 1; i++) { console.log(0); } // +n*n (прием 3) for (let i = 0; i < 2 * n; i++) { for (let j = 0; j < n + 1; j++) { console.log(0); } } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 1; // +0 (прием 1) a = a + n; // +0 (прием 1) // +n (прием 2) for (int i = 0; i < 5 * n + 1; i++) { print(0); } // +n*n (прием 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { print(0); } } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let mut a = 1; // +0 (прием 1) a = a + n; // +0 (прием 1) // +n (прием 2) for i in 0..(5 * n + 1) { println!("{}", 0); } // +n*n (прием 3) for i in 0..(2 * n) { for j in 0..(n + 1) { println!("{}", 0); } } } ``` === "C" ```c title="" void algorithm(int n) { int a = 1; // +0 (прием 1) a = a + n; // +0 (прием 1) // +n (прием 2) for (int i = 0; i < 5 * n + 1; i++) { printf("%d", 0); } // +n*n (прием 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { printf("%d", 0); } } } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { var a = 1 // +0 (прием 1) a = a + n // +0 (прием 1) // +n (прием 2) for (i in 0..<5 * n + 1) { println(0) } // +n*n (прием 3) for (i in 0..<2 * n) { for (j in 0.. Таблица   Временная сложность, соответствующая разному количеству операций

| Число операций $T(n)$ | Временная сложность $O(f(n))$ | | ---------------------- | -------------------- | | $100000$ | $O(1)$ | | $3n + 2$ | $O(n)$ | | $2n^2 + 3n + 2$ | $O(n^2)$ | | $n^3 + 10000n^2$ | $O(n^3)$ | | $2^n + 10000n^{10000}$ | $O(2^n)$ | ## Распространенные типы Пусть размер входных данных равен $n$ ; распространенные типы временной сложности показаны на рисунке ниже в порядке от меньшей к большей. $$ \begin{aligned} O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline \text{Постоянная} < \text{Логарифмическая} < \text{Линейная} < \text{Линейно-логарифмическая} < \text{Квадратичная} < \text{Экспоненциальная} < \text{Факториальная} \end{aligned} $$ ![Распространенные типы временной сложности](time_complexity.assets/time_complexity_common_types.png) ### Постоянная сложность $O(1)$ Число операций при постоянной сложности не зависит от размера входных данных $n$ , то есть не изменяется вместе с изменением $n$ . В следующей функции, хотя число операций `size` может быть большим, оно не зависит от размера входных данных $n$ , поэтому временная сложность по-прежнему равна $O(1)$ : ```src [file]{time_complexity}-[class]{}-[func]{constant} ``` ### Линейная сложность $O(n)$ Линейная сложность характеризуется тем, что число операций растет линейно относительно размера входных данных $n$ . Линейная сложность обычно встречается в одноуровневых циклах: ```src [file]{time_complexity}-[class]{}-[func]{linear} ``` Операции обхода массива и обхода связного списка имеют временную сложность $O(n)$ , где $n$ - длина массива или списка: ```src [file]{time_complexity}-[class]{}-[func]{array_traversal} ``` Стоит отметить, что **размер входных данных $n$ нужно определять конкретно в зависимости от типа входа**. Например, в первом примере переменная $n$ сама является размером входных данных; во втором примере размером данных служит длина массива. ### Квадратичная сложность $O(n^2)$ Квадратичная сложность характеризуется тем, что число операций растет квадратично относительно размера входных данных $n$ . Квадратичная сложность обычно встречается во вложенных циклах: временная сложность внешнего и внутреннего циклов равна $O(n)$ , поэтому общая временная сложность составляет $O(n^2)$ : ```src [file]{time_complexity}-[class]{}-[func]{quadratic} ``` На рисунке ниже сравниваются три временные сложности: постоянная, линейная и квадратичная. ![Постоянная, линейная и квадратичная временная сложность](time_complexity.assets/time_complexity_constant_linear_quadratic.png) Возьмем в качестве примера пузырьковую сортировку: внешний цикл выполняется $n - 1$ раз, внутренний цикл выполняется $n-1$ , $n-2$ , $\dots$ , $2$ , $1$ раз, в среднем это $n / 2$ раз, поэтому временная сложность равна $O((n - 1)n / 2) = O(n^2)$ : ```src [file]{time_complexity}-[class]{}-[func]{bubble_sort} ``` ### Экспоненциальная сложность $O(2^n)$ Типичный пример экспоненциального роста в биологии - деление клеток: в начальном состоянии есть одна клетка, после одного деления их становится 2, после двух делений - 4 и так далее; после $n$ раундов деления клеток становится $2^n$ . На рисунке ниже и в следующем коде моделируется процесс деления клеток; временная сложность равна $O(2^n)$ . Здесь входное значение $n$ обозначает число раундов деления, а возвращаемое значение `count` обозначает общее число делений. ```src [file]{time_complexity}-[class]{}-[func]{exponential} ``` ![Экспоненциальная временная сложность](time_complexity.assets/time_complexity_exponential.png) В реальных алгоритмах экспоненциальная сложность также часто встречается в рекурсивных функциях. Например, в следующем коде процесс рекурсивно делится надвое и останавливается после $n$ разбиений: ```src [file]{time_complexity}-[class]{}-[func]{exp_recur} ``` Экспоненциальный рост происходит очень быстро и часто встречается в переборных методах, грубой силе, поиске с возвратом и тому подобных подходах. Для задач большого масштаба экспоненциальная сложность неприемлема, и обычно приходится применять динамическое программирование, жадные алгоритмы и другие стратегии. ### Логарифмическая сложность $O(\log n)$ В противоположность экспоненциальной, логарифмическая сложность описывает ситуацию, когда **в каждом раунде размер задачи уменьшается вдвое**. Пусть размер входных данных равен $n$ ; так как на каждом шаге размер уменьшается вдвое, число итераций равно $\log_2 n$ , то есть является обратной функцией к $2^n$ . На рисунке ниже и в следующем коде моделируется процесс, в котором **в каждом раунде размер задачи уменьшается вдвое**; временная сложность равна $O(\log_2 n)$ и кратко записывается как $O(\log n)$ : ```src [file]{time_complexity}-[class]{}-[func]{logarithmic} ``` ![Логарифмическая временная сложность](time_complexity.assets/time_complexity_logarithmic.png) Подобно экспоненциальной сложности, логарифмическая также часто встречается в рекурсивных функциях. Следующий код формирует рекурсивное дерево высотой $\log_2 n$ : ```src [file]{time_complexity}-[class]{}-[func]{log_recur} ``` Логарифмическая сложность часто встречается в алгоритмах, основанных на стратегии "разделяй и властвуй", и отражает идеи разбиения на части и упрощения сложной задачи. Она растет медленно и считается одной из самых желательных временных сложностей после константной. !!! tip "Каково основание у $O(\log n)$ ?" Точнее говоря, "разделение на $m$ частей" соответствует временной сложности $O(\log_m n)$ . А по формуле перехода к другому основанию логарифма мы получаем равные по сложности выражения с разными основаниями: $$ O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n) $$ Иными словами, основание $m$ можно менять без влияния на сложность. Поэтому мы обычно опускаем основание $m$ и напрямую записываем логарифмическую сложность как $O(\log n)$ . ### Линейно-логарифмическая сложность $O(n \log n)$ Линейно-логарифмическая сложность часто встречается в рекурсивных разбиениях, где временная сложность одного измерения равна $O(\log n)$ , а другого - $O(n)$ . Соответствующий код выглядит следующим образом: ```src [file]{time_complexity}-[class]{}-[func]{linear_log_recur} ``` На рисунке ниже показано, как возникает линейно-логарифмическая сложность. Общее число операций на каждом уровне бинарного дерева равно $n$ , а дерево имеет $\log_2 n + 1$ уровней, поэтому временная сложность равна $O(n \log n)$ . ![Линейно-логарифмическая временная сложность](time_complexity.assets/time_complexity_logarithmic_linear.png) Временная сложность основных алгоритмов сортировки обычно равна $O(n \log n)$ , например у быстрой сортировки, сортировки слиянием, пирамидальной сортировки и т.д. ### Факториальная сложность $O(n!)$ Факториальная сложность соответствует математической задаче полной перестановки. Если даны $n$ попарно различных элементов, то число всех возможных перестановок равно: $$ n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1 $$ Факториал обычно реализуют через рекурсию. Как показано на рисунке ниже и в следующем коде, на первом уровне происходит ветвление на $n$ подзадач, на втором - на $n - 1$ и так далее, пока на $n$-м уровне ветвление не прекращается: ```src [file]{time_complexity}-[class]{}-[func]{factorial_recur} ``` ![Факториальная временная сложность](time_complexity.assets/time_complexity_factorial.png) Следует отметить, что поскольку при $n \geq 4$ всегда выполняется $n! > 2^n$ , факториальная сложность растет еще быстрее, чем экспоненциальная, и при больших $n$ становится неприемлемой. ## Худшая, лучшая и средняя временная сложность **Временная эффективность алгоритма часто не фиксирована, а зависит от распределения входных данных**. Предположим, на вход подается массив `nums` длины $n$ , состоящий из чисел от $1$ до $n$ , каждое из которых встречается ровно один раз; при этом порядок элементов случайно перемешан. Задача состоит в том, чтобы вернуть индекс элемента $1$ . Тогда можно сделать следующие выводы. - Когда `nums = [?, ?, ..., 1]` , то есть когда последний элемент равен $1$ , нужно полностью пройти по массиву, **что дает худшую временную сложность $O(n)$** . - Когда `nums = [1, ?, ?, ...]` , то есть когда первый элемент равен $1$ , независимо от длины массива продолжать обход не нужно, **что дает лучшую временную сложность $\Omega(1)$** . Худшая временная сложность соответствует асимптотической верхней границе функции и обозначается нотацией Big $O$ . Соответственно, лучшая временная сложность соответствует асимптотической нижней границе функции и обозначается символом $\Omega$ : ```src [file]{worst_best_time_complexity}-[class]{}-[func]{find_one} ``` Стоит отметить, что на практике лучшая временная сложность используется редко, поскольку обычно она достигается лишь с очень малой вероятностью и может вводить в заблуждение. **Худшая временная сложность гораздо практичнее, потому что задает безопасную оценку эффективности** и позволяет уверенно использовать алгоритм. Из приведенного выше примера видно, что худшая и лучшая временные сложности возникают только при особых распределениях данных; вероятность таких случаев может быть низкой, и они не всегда реально отражают эффективность алгоритма. Напротив, **средняя временная сложность способна показать эффективность алгоритма на случайных входных данных** и обозначается символом $\Theta$ . Для некоторых алгоритмов можно относительно просто вывести средний случай при случайном распределении данных. Например, в приведенном выше примере входной массив перемешан, а вероятность появления элемента $1$ на любом индексе одинакова; следовательно, среднее число итераций алгоритма равно половине длины массива, то есть $n / 2$ , а средняя временная сложность равна $\Theta(n / 2) = \Theta(n)$ . Однако для более сложных алгоритмов вычислить среднюю временную сложность часто непросто, потому что трудно проанализировать полное математическое ожидание на заданном распределении данных. В таких случаях обычно используют худшую временную сложность как критерий оценки эффективности алгоритма. !!! question "Почему символ $\Theta$ встречается так редко?" Возможно, потому что символ $O$ звучит слишком привычно, и мы часто используем его для обозначения средней временной сложности. Но строго говоря, это некорректно. В этой книге и в других материалах, если встретится выражение вроде "средняя временная сложность $O(n)$", просто понимай его как $\Theta(n)$ . ================================================ FILE: ru/docs/chapter_data_structure/basic_data_types.md ================================================ # Базовые типы данных Когда речь заходит о данных в компьютере, мы в первую очередь вспоминаем текст, изображения, видео, звук, 3D-модели и многие другие формы представления информации. Хотя способы организации этих данных различаются, все они строятся из базовых типов данных. **Базовые типы данных - это типы, которые процессор может обрабатывать непосредственно**. В алгоритмах они используются напрямую и в основном включают следующее. - Целочисленные типы `byte` , `short` , `int` , `long` . - Типы с плавающей точкой `float` , `double` , используемые для представления дробных чисел. - Символьный тип `char` , используемый для представления букв, знаков препинания и даже эмодзи в разных языках. - Логический тип `bool` , используемый для представления суждений "да" и "нет". **Базовые типы данных хранятся в компьютере в двоичной форме**. Один двоичный разряд равен $1$ биту. В большинстве современных операционных систем $1$ байт (byte) состоит из $8$ битов (bit). Диапазон значений базовых типов данных зависит от объема занимаемого ими пространства. Ниже в качестве примера используется Java. - Целочисленный тип `byte` занимает $1$ байт = $8$ бит и может представлять $2^{8}$ чисел. - Целочисленный тип `int` занимает $4$ байта = $32$ бита и может представлять $2^{32}$ чисел. В таблице ниже перечислены объем памяти, диапазон значений и значения по умолчанию для различных базовых типов данных в Java. Эту таблицу не нужно заучивать наизусть; достаточно иметь общее представление и при необходимости обращаться к ней.

Таблица   Объем памяти и диапазоны значений базовых типов данных

| Тип | Обозначение | Объем памяти | Минимальное значение | Максимальное значение | Значение по умолчанию | | -------- | ----------- | ------------ | ------------------------- | ----------------------- | --------------------- | | Целые | `byte` | 1 байт | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | $0$ | | | `short` | 2 байта | $-2^{15}$ | $2^{15} - 1$ | $0$ | | | `int` | 4 байта | $-2^{31}$ | $2^{31} - 1$ | $0$ | | | `long` | 8 байт | $-2^{63}$ | $2^{63} - 1$ | $0$ | | Вещественные | `float` | 4 байта | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ | | | `double` | 8 байт | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | $0.0$ | | Символы | `char` | 2 байта | $0$ | $2^{16} - 1$ | $0$ | | Логические | `bool` | 1 байт | $\text{false}$ | $\text{true}$ | $\text{false}$ | Обрати внимание: приведенная выше таблица относится именно к базовым типам данных Java. В каждом языке программирования свои определения типов, поэтому объем памяти, диапазон значений и значения по умолчанию могут различаться. - В Python целочисленный тип `int` может иметь произвольный размер, ограниченный только доступной памятью; тип `float` является 64-битным числом двойной точности; типа `char` нет, а отдельный символ на деле является строкой `str` длины 1. - В C и C++ размер базовых типов данных явно не зафиксирован и зависит от реализации и платформы. Таблица выше соответствует модели данных LP64 [data model](https://en.cppreference.com/w/cpp/language/types#Properties), используемой в 64-битных Unix-системах, включая Linux и macOS. - Размер символа `char` в C и C++ составляет 1 байт, а в большинстве других языков программирования зависит от конкретного способа кодирования символов; подробнее это рассматривается в разделе "Кодирование символов". - Хотя для представления логического значения достаточно 1 бита ( $0$ или $1$ ), в памяти оно обычно хранится как 1 байт. Это связано с тем, что современные CPU обычно используют 1 байт как минимальную адресуемую единицу памяти. Какова же связь между базовыми типами данных и структурами данных? Мы знаем, что структура данных - это способ организации и хранения данных в компьютере. Подлежащее в этой фразе - "структура", а не "данные". Если мы хотим представить "ряд чисел", то естественно подумаем об использовании массива. Это связано с тем, что линейная структура массива может выразить отношения соседства и порядка между числами, а то, что именно хранится внутри - целые `int` , вещественные `float` или символы `char` , - к "структуре данных" отношения не имеет. Иными словами, **базовые типы данных задают "тип содержимого" данных, а структуры данных задают "способ организации" данных**. Например, в следующем коде мы используем одну и ту же структуру данных (массив) для хранения и представления различных базовых типов данных, включая `int` , `float` , `char` , `bool` и т.д. === "Python" ```python title="" # Инициализируем массивы с использованием различных базовых типов данных numbers: list[int] = [0] * 5 decimals: list[float] = [0.0] * 5 # В Python символы фактически являются строками длины 1 characters: list[str] = ['0'] * 5 bools: list[bool] = [False] * 5 # Списки Python могут свободно хранить различные базовые типы данных и ссылки на объекты data = [0, 0.0, 'a', False, ListNode(0)] ``` === "C++" ```cpp title="" // Инициализируем массивы с использованием различных базовых типов данных int numbers[5]; float decimals[5]; char characters[5]; bool bools[5]; ``` === "Java" ```java title="" // Инициализируем массивы с использованием различных базовых типов данных int[] numbers = new int[5]; float[] decimals = new float[5]; char[] characters = new char[5]; boolean[] bools = new boolean[5]; ``` === "C#" ```csharp title="" // Инициализируем массивы с использованием различных базовых типов данных int[] numbers = new int[5]; float[] decimals = new float[5]; char[] characters = new char[5]; bool[] bools = new bool[5]; ``` === "Go" ```go title="" // Инициализируем массивы с использованием различных базовых типов данных var numbers = [5]int{} var decimals = [5]float64{} var characters = [5]byte{} var bools = [5]bool{} ``` === "Swift" ```swift title="" // Инициализируем массивы с использованием различных базовых типов данных let numbers = Array(repeating: 0, count: 5) let decimals = Array(repeating: 0.0, count: 5) let characters: [Character] = Array(repeating: "a", count: 5) let bools = Array(repeating: false, count: 5) ``` === "JS" ```javascript title="" // Массивы JavaScript могут свободно хранить различные базовые типы данных и объекты const array = [0, 0.0, 'a', false]; ``` === "TS" ```typescript title="" // Инициализируем массивы с использованием различных базовых типов данных const numbers: number[] = []; const characters: string[] = []; const bools: boolean[] = []; ``` === "Dart" ```dart title="" // Инициализируем массивы с использованием различных базовых типов данных List numbers = List.filled(5, 0); List decimals = List.filled(5, 0.0); List characters = List.filled(5, 'a'); List bools = List.filled(5, false); ``` === "Rust" ```rust title="" // Инициализируем массивы с использованием различных базовых типов данных let numbers: Vec = vec![0; 5]; let decimals: Vec = vec![0.0; 5]; let characters: Vec = vec!['0'; 5]; let bools: Vec = vec![false; 5]; ``` === "C" ```c title="" // Инициализируем массивы с использованием различных базовых типов данных int numbers[10]; float decimals[10]; char characters[10]; bool bools[10]; ``` === "Kotlin" ```kotlin title="" // Инициализируем массивы с использованием различных базовых типов данных val numbers = IntArray(5) val decinals = FloatArray(5) val characters = CharArray(5) val bools = BooleanArray(5) ``` === "Ruby" ```ruby title="" # Списки Ruby могут свободно хранить различные базовые типы данных и ссылки на объекты data = [0, 0.0, 'a', false, ListNode(0)] ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D1%8B%D0%B9%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%D1%83%D0%B7%D0%B5%D0%BB%D0%BA%D0%BB%D0%B0%D1%81%D1%81%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%81%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%D0%BC%20%D0%BD%D0%B5%D1%81%D0%BA%D0%BE%D0%BB%D1%8C%D0%BA%D0%B8%D1%85%20%D0%B1%D0%B0%D0%B7%D0%BE%D0%B2%D1%8B%D1%85%20%D1%82%D0%B8%D0%BF%D0%BE%D0%B2%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20%2A%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20%2A%205%0A%20%20%20%20%23%20%D0%92%20Python%20%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D1%8B%20%D0%BD%D0%B0%20%D1%81%D0%B0%D0%BC%D0%BE%D0%BC%20%D0%B4%D0%B5%D0%BB%D0%B5%20%D1%8F%D0%B2%D0%BB%D1%8F%D1%8E%D1%82%D1%81%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%D0%BC%D0%B8%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%201%0A%20%20%20%20characters%20%3D%20%5B%270%27%5D%20%2A%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20%2A%205%0A%20%20%20%20%23%20%D0%A1%D0%BF%D0%B8%D1%81%D0%BA%D0%B8%20%D0%B2%20Python%20%D0%BC%D0%BE%D0%B3%D1%83%D1%82%20%D1%81%D0%B2%D0%BE%D0%B1%D0%BE%D0%B4%D0%BD%D0%BE%20%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C%20%D1%80%D0%B0%D0%B7%D0%BB%D0%B8%D1%87%D0%BD%D1%8B%D0%B5%20%D0%B1%D0%B0%D0%B7%D0%BE%D0%B2%D1%8B%D0%B5%20%D1%82%D0%B8%D0%BF%D1%8B%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20%D0%B8%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BD%D0%B0%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D1%8B%0A%20%20%20%20data%20%3D%20%5B0%2C%200.0%2C%20%27a%27%2C%20False%2C%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: ru/docs/chapter_data_structure/character_encoding.md ================================================ # Кодирование символов * В компьютере все данные хранятся в виде двоичных чисел, и символьный тип данных `char` не является исключением. Для представления символов необходимо задать "таблицу символов", которая устанавливает взаимно-однозначное соответствие между каждым символом и двоичным числом. С помощью этой таблицы компьютер может преобразовывать двоичные числа в символы. ## Таблица символов ASCII Код ASCII - это самая ранняя таблица символов; ее полное название - American Standard Code for Information Interchange (американский стандартный код обмена информацией). Для представления символов в ней используются 7 двоичных битов (нижние 7 битов одного байта), что позволяет закодировать до 128 различных символов. Как показано на рисунке ниже, ASCII включает заглавные и строчные буквы английского алфавита, цифры 0 ~ 9, некоторые знаки препинания, а также некоторые управляющие символы (например перевод строки и табуляцию). ![Таблица ASCII](character_encoding.assets/ascii_table.png) Однако **код ASCII может представлять только английский язык**. С развитием компьютерных технологий появилась таблица символов EASCII, способная охватывать больше языков. Она расширяет 7-битную основу ASCII до 8 битов и может представлять 256 различных символов. Во всем мире постепенно появились разные таблицы EASCII, подходящие для разных регионов. Первые 128 символов в этих таблицах одинаковы и соответствуют ASCII, а последние 128 символов определяются по-разному, чтобы удовлетворять потребностям разных языков. ## Таблица символов GBK Позже люди обнаружили, что **кодов EASCII все равно недостаточно для количества символов во многих языках**. Например, китайских иероглифов существует почти сто тысяч, а в повседневном употреблении нужны тысячи. В 1980 году Государственное управление стандартов Китая выпустило таблицу символов GB2312, включающую 6763 иероглифа, что в основном удовлетворило потребности компьютерной обработки китайского текста. Однако GB2312 не умеет работать с некоторыми редкими иероглифами и традиционными формами письма. Таблица символов GBK представляет собой расширение GB2312 и в общей сложности содержит 21886 иероглифов. В схеме кодирования GBK символы ASCII представляются одним байтом, а китайские иероглифы - двумя байтами. ## Таблица символов Unicode С бурным развитием компьютерной техники таблицы символов и стандарты кодирования начали стремительно множиться, и это породило множество проблем. С одной стороны, такие таблицы обычно определяли символы только для конкретных языков и не могли нормально работать в многоязычной среде. С другой стороны, для одного и того же языка существовало несколько стандартов кодирования; если две машины использовали разные стандарты, при обмене информацией возникали искажения текста. Исследователи той эпохи задумались: **если создать достаточно полную таблицу символов, которая включит все языки и знаки мира, разве это не решит проблемы многоязычной среды и искаженного текста**? Под влиянием этой идеи и появилась большая и всеобъемлющая таблица символов Unicode. Unicode по-китайски называется "единый код" и теоретически способен вместить более миллиона символов. Его цель - собрать символы со всего мира в единую таблицу символов, предоставить универсальный стандарт для обработки и отображения текстов на разных языках и уменьшить количество проблем с искажением текста, вызванных различиями стандартов кодирования. С момента публикации в 1991 году Unicode непрерывно расширялся, добавляя новые языки и символы. По состоянию на сентябрь 2022 года Unicode уже включал 149186 символов, в том числе буквы разных языков, знаки, а также эмодзи. В огромной таблице символов Unicode часто используемые символы занимают 2 байта, а некоторые редкие символы - 3 байта и даже 4 байта. Unicode - это универсальный набор символов, который по сути просто присваивает каждому символу номер (так называемую "кодовую точку"), **но не определяет, как именно хранить эти кодовые точки в компьютере**. Тут неизбежно возникает вопрос: если в одном тексте одновременно встречаются кодовые точки Unicode разной длины, как система должна разбирать символы? Например, если дан код длиной 2 байта, как понять, является ли это одним 2-байтовым символом или двумя 1-байтовыми? Для этой проблемы **прямолинейное решение состоит в том, чтобы хранить все символы в кодировке одинаковой длины**. Как показано на рисунке ниже, каждый символ в "Hello" занимает 1 байт, а каждый символ в "алгоритм" занимает 2 байта. Мы можем дополнить старшие биты нулями и закодировать все символы в "Hello алгоритм" в виде 2-байтовых единиц. Тогда система сможет считывать по одному символу каждые 2 байта и восстановить эту фразу. ![Пример кодирования Unicode](character_encoding.assets/unicode_hello_algo.png) Однако ASCII уже показал нам, что для кодирования английского текста достаточно 1 байта. Если использовать описанную выше схему, английский текст будет занимать вдвое больше памяти, чем при ASCII, а это очень неэффективно. Поэтому нам нужен более эффективный способ кодирования Unicode. ## Кодировка UTF-8 Сегодня UTF-8 стала самым широко используемым способом кодирования Unicode в мире. **Это кодировка переменной длины**, использующая от 1 до 4 байт на символ в зависимости от его сложности. Символам ASCII нужен только 1 байт, латинским и греческим буквам - 2 байта, часто используемым китайским символам - 3 байта, а некоторым редким символам - 4 байта. Правила кодирования UTF-8 не слишком сложны и делятся на два случая. - Для символов длиной 1 байт старший бит устанавливается в $0$ , а оставшиеся 7 битов содержат кодовую точку Unicode. Стоит отметить, что символы ASCII занимают первые 128 кодовых точек в наборе Unicode. Иными словами, **кодировка UTF-8 обратно совместима с ASCII**. Это означает, что мы можем использовать UTF-8 для разбора очень старых ASCII-текстов. - Для символов длиной $n$ байт (где $n > 1$) старшие $n$ битов первого байта устанавливаются в $1$ , а $(n + 1)$-й бит устанавливается в $0$ ; начиная со второго байта, старшие 2 бита каждого байта устанавливаются в $10$ ; все остальные биты используются для заполнения кодовой точки Unicode соответствующего символа. На рисунке ниже показана UTF-8-кодировка для строки "Hello алгоритм". Можно заметить, что поскольку старшие $n$ битов установлены в $1$ , система может определить длину символа как $n$ , подсчитав число ведущих единиц. Но почему старшие 2 бита всех остальных байтов устанавливаются в $10$ ? На самом деле это $10$ играет роль контрольного маркера. Если система начнет разбирать текст с неверного байта, префикс $10$ поможет быстро обнаружить аномалию. Причина выбора $10$ в качестве контрольного маркера в том, что по правилам UTF-8 символ не может иметь старшие два бита, равные $10$ . Это можно доказать от противного: если предположить, что у некоторого символа старшие два бита равны $10$ , то длина такого символа должна быть 1 байт, то есть это ASCII. Но у ASCII старший бит обязан быть $0$ , что противоречит предположению. ![Пример кодировки UTF-8](character_encoding.assets/utf-8_hello_algo.png) Помимо UTF-8, распространены еще два следующих способа кодирования. - **Кодировка UTF-16**: использует 2 или 4 байта для представления символа. Все символы ASCII и часто используемые неанглийские символы представляются 2 байтами; небольшая часть символов требует 4 байта. Для 2-байтовых символов кодировка UTF-16 совпадает с кодовой точкой Unicode. - **Кодировка UTF-32**: каждый символ занимает 4 байта. Это означает, что UTF-32 требует больше места, чем UTF-8 и UTF-16, особенно в текстах с большой долей ASCII-символов. С точки зрения занимаемого места UTF-8 очень эффективна для английских символов, потому что им нужен всего 1 байт; а для некоторых неанглийских символов (например китайских) UTF-16 может быть эффективнее, потому что ей требуется только 2 байта, тогда как UTF-8 может потребовать 3 байта. С точки зрения совместимости у UTF-8 наилучшая универсальность, и многие инструменты и библиотеки в первую очередь поддерживают именно UTF-8. ## Кодирование символов в языках программирования Для большинства языков программирования прошлого строки во время выполнения программы использовали фиксированные по длине кодировки, такие как UTF-16 или UTF-32. При кодировке фиксированной длины строку можно обрабатывать как массив, и такой подход дает следующие преимущества. - **Произвольный доступ**: к строкам в UTF-16 легко осуществлять произвольный доступ. UTF-8 же является кодировкой переменной длины, поэтому, чтобы найти $i$ -й символ, нужно пройти от начала строки до этого символа, а это требует $O(n)$ времени. - **Подсчет длины строки**: аналогично произвольному доступу, вычисление длины строки в UTF-16 - это операция $O(1)$ . А вот вычисление длины строки в UTF-8 требует обхода всей строки. - **Строковые операции**: многие операции со строками (разделение, конкатенация, вставка, удаление и т.д.) над строками в UTF-16 реализуются проще. При работе с UTF-8 обычно требуются дополнительные вычисления, чтобы не породить некорректную UTF-8-последовательность. Вообще говоря, проектирование схем кодирования символов в языках программирования - очень интересная тема, в которой учитывается множество факторов. - Тип `String` в Java использует кодировку UTF-16, и каждый символ занимает 2 байта. Это связано с тем, что на раннем этапе проектирования Java считалось, что 16 битов достаточно для представления всех возможных символов. Но это оказалось неверным предположением. Позднее Unicode вышел за пределы 16 битов, поэтому символы в Java теперь могут представляться парой 16-битных значений (так называемой "суррогатной парой"). - Строки в JavaScript и TypeScript используют UTF-16 по причинам, похожим на Java. Когда Netscape впервые выпустила JavaScript в 1995 году, Unicode еще находился на ранней стадии развития, и 16-битного кодирования тогда было достаточно для представления всех символов Unicode. - C# использует UTF-16 главным образом потому, что платформа .NET была разработана Microsoft, а многие технологии Microsoft (включая Windows) широко используют именно UTF-16. Из-за недооценки общего числа символов перечисленным выше языкам пришлось использовать "суррогатные пары" для представления Unicode-символов длиной больше 16 бит. Это вынужденный компромисс. С одной стороны, в строках с суррогатными парами один символ может занимать 2 байта или 4 байта, из-за чего теряется преимущество кодировки фиксированной длины. С другой стороны, обработка суррогатных пар требует дополнительного кода, что повышает сложность разработки и отладки. По этим причинам некоторые языки программирования предложили иные схемы кодирования. - `str` в Python использует Unicode и гибкое строковое представление, где длина хранимого символа зависит от наибольшей кодовой точки Unicode в строке. Если все символы строки принадлежат ASCII, каждый символ занимает 1 байт; если есть символы за пределами ASCII, но все они лежат в базовой многоязычной плоскости (BMP), каждый символ занимает 2 байта; если встречаются символы за пределами BMP, каждый символ занимает 4 байта. - Тип `string` в Go внутри использует кодировку UTF-8. Язык Go также предоставляет тип `rune`, предназначенный для представления одной кодовой точки Unicode. - Типы `str` и `String` в Rust внутри используют UTF-8. В Rust также есть тип `char`, представляющий одну кодовую точку Unicode. Следует помнить, что выше обсуждался способ хранения строк внутри языков программирования, **а это не то же самое, что хранение строк в файлах или передача их по сети**. При файловом хранении и сетевой передаче мы обычно кодируем строки в формате UTF-8, чтобы получить наилучшую совместимость и эффективность по занимаемому месту. ================================================ FILE: ru/docs/chapter_data_structure/classification_of_data_structure.md ================================================ # Классификация структур данных К распространенным структурам данных относятся массивы, связные списки, стеки, очереди, хеш-таблицы, деревья, кучи и графы. Их можно классифицировать по двум измерениям: логической структуре и физической структуре. ## Логическая структура: линейная и нелинейная **Логическая структура раскрывает логические отношения между элементами данных**. В массивах и связных списках данные расположены в определенном порядке, что отражает линейные отношения между элементами. В деревьях данные расположены по уровням сверху вниз, что демонстрирует отношения "предок" и "потомок". Графы состоят из вершин и ребер, отражая сложные сетевые отношения. Как показано на рисунке ниже, логические структуры делятся на две большие категории: линейные и нелинейные. Линейные структуры более интуитивны, поскольку в них данные расположены линейно и логически связаны. Нелинейные структуры, напротив, представляют собой нелинейное расположение элементов данных. - **Линейные структуры данных**: массивы, связные списки, стеки, очереди, хеш-таблицы, в которых элементы связаны отношением "один к одному". - **Нелинейные структуры данных**: деревья, кучи, графы, хеш-таблицы. Нелинейные структуры данных можно дополнительно разделить на древовидные и сетевые. - **Древовидные структуры**: деревья, кучи, хеш-таблицы, в которых элементы связаны отношением "один ко многим". - **Сетевые структуры**: графы, в которых элементы связаны отношением "многие ко многим". ![Линейные и нелинейные структуры данных](classification_of_data_structure.assets/classification_logic_structure.png) ## Физическая структура: непрерывная и разрозненная **Во время выполнения программы обрабатываемые данные в основном хранятся в памяти**. На рисунке ниже показан модуль оперативной памяти компьютера, где каждый черный блок содержит определенный участок памяти. Память можно представить как огромную таблицу Excel, в которой каждая ячейка способна хранить данные определенного размера. **Система обращается к данным по адресам памяти соответствующих позиций**. Как показано на рисунке ниже, компьютер по определенным правилам присваивает каждой ячейке в этой таблице номер, чтобы каждый участок памяти имел уникальный адрес. Благодаря этим адресам программа получает доступ к данным, находящимся в памяти. ![Планка памяти, участок памяти и адрес памяти](classification_of_data_structure.assets/computer_memory_location.png) !!! tip Стоит отметить, что сравнение памяти с таблицей Excel - это упрощенная аналогия; реальный механизм работы памяти гораздо сложнее и включает такие понятия, как адресное пространство, управление памятью, кэш-механизмы, виртуальная и физическая память. Память - общий ресурс для всех программ. Когда некоторый участок памяти занят одной программой, другие программы обычно не могут использовать его одновременно. **Поэтому при проектировании структур данных и алгоритмов память занимает важное место**. Например, пиковое потребление памяти алгоритмом не должно превышать объем доступной свободной памяти системы; если не хватает непрерывных крупных участков памяти, выбранная структура данных должна уметь размещаться в разрозненных областях памяти. Как показано на рисунке ниже, **физическая структура отражает способ хранения данных в памяти компьютера**. Ее можно разделить на хранение в непрерывном пространстве (массивы) и хранение в разрозненном пространстве (связные списки). Физическая структура на низком уровне определяет способы доступа к данным, их обновления, вставки и удаления. Эти два типа физических структур взаимно дополняют друг друга по временной и пространственной эффективности. ![Хранение в непрерывном и разрозненном пространстве](classification_of_data_structure.assets/classification_phisical_structure.png) Стоит отметить, что **все структуры данных реализуются на основе массивов, связных списков или их комбинации**. Например, стек и очередь можно реализовать как с помощью массивов, так и с помощью связных списков; реализация хеш-таблицы также может одновременно включать массивы и связные списки. - **Можно реализовать на основе массивов**: стеки, очереди, хеш-таблицы, деревья, кучи, графы, матрицы, тензоры (массивы размерности $\geq 3$ ) и т.д. - **Можно реализовать на основе связных списков**: стеки, очереди, хеш-таблицы, деревья, кучи, графы и т.д. После инициализации длину связного списка все еще можно изменять во время выполнения программы, поэтому его также называют "динамической структурой данных". Длина массива после инициализации неизменна, поэтому его также называют "статической структурой данных". Стоит отметить, что массив может изменять длину за счет повторного выделения памяти, тем самым приобретая определенную "динамичность". !!! tip Если тебе пока трудно понять физическую структуру, рекомендуется сначала прочитать следующую главу, а затем вернуться к этому разделу. ================================================ FILE: ru/docs/chapter_data_structure/index.md ================================================ # Структуры данных ![Структуры данных](../assets/covers/chapter_data_structure.jpg) !!! abstract Структуры данных подобны прочному и многообразному каркасу. Они задают схему упорядоченной организации данных, на основе которой оживают алгоритмы. ================================================ FILE: ru/docs/chapter_data_structure/number_encoding.md ================================================ # Кодирование чисел * !!! tip В этой книге разделы, помеченные символом `*`, относятся к дополнительному чтению. Если у тебя мало времени или материал кажется трудным, можно сначала пропустить их и вернуться после изучения обязательных разделов. ## Прямой, обратный и дополнительный коды В таблице из предыдущего раздела можно заметить, что все целочисленные типы могут представлять на одно отрицательное число больше, чем положительных. Например, диапазон `byte` равен $[-128, 127]$ . Это выглядит не слишком интуитивно, и внутренняя причина связана с прямым, обратным и дополнительным кодами. Прежде всего нужно отметить, что **числа хранятся в компьютере в виде "дополнительного кода"**. Прежде чем разбирать причины такого решения, сначала дадим определения всем трем способам представления. - **Прямой код**: старший бит двоичного представления числа рассматривается как знаковый, где $0$ означает положительное число, а $1$ - отрицательное; остальные биты представляют значение числа. - **Обратный код**: для положительного числа обратный код совпадает с прямым; для отрицательного числа он получается инверсией всех битов прямого кода, кроме знакового бита. - **Дополнительный код**: для положительного числа дополнительный код совпадает с прямым; для отрицательного числа он получается добавлением $1$ к его обратному коду. На рисунке ниже показаны способы преобразования между прямым, обратным и дополнительным кодами. ![Преобразования между прямым, обратным и дополнительным кодами](number_encoding.assets/1s_2s_complement.png) Прямой код (sign-magnitude), хотя и является самым наглядным, имеет определенные ограничения. С одной стороны, **прямой код отрицательных чисел нельзя напрямую использовать в вычислениях**. Например, при вычислении $1 + (-2)$ в прямом коде результатом будет $-3$ , что, очевидно, неверно. $$ \begin{aligned} & 1 + (-2) \newline & \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline & = 1000 \; 0011 \newline & \rightarrow -3 \end{aligned} $$ Чтобы решить эту проблему, компьютеры ввели обратный код (1's complement). Если сначала преобразовать прямой код в обратный и выполнить вычисление $1 + (-2)$ в обратном коде, а затем перевести результат обратно в прямой код, то получится правильный результат $-1$ . $$ \begin{aligned} & 1 + (-2) \newline & \rightarrow 0000 \; 0001 \; \text{(прямой код)} + 1000 \; 0010 \; \text{(прямой код)} \newline & = 0000 \; 0001 \; \text{(обратный код)} + 1111 \; 1101 \; \text{(обратный код)} \newline & = 1111 \; 1110 \; \text{(обратный код)} \newline & = 1000 \; 0001 \; \text{(прямой код)} \newline & \rightarrow -1 \end{aligned} $$ С другой стороны, **в прямом коде у нуля есть два представления: $+0$ и $-0$ **. Это означает, что числу ноль соответствуют два разных двоичных кода, что может приводить к неоднозначности. Например, если в условном выражении не различать положительный и отрицательный ноль, можно получить ошибочный результат. А если специально обрабатывать такую неоднозначность, придется вводить дополнительные проверки, что может снизить вычислительную эффективность компьютера. $$ \begin{aligned} +0 & \rightarrow 0000 \; 0000 \newline -0 & \rightarrow 1000 \; 0000 \end{aligned} $$ Как и прямой код, обратный код тоже страдает от неоднозначности положительного и отрицательного нуля, поэтому компьютеры ввели дополнительный код (2's complement). Сначала посмотрим на процесс преобразования отрицательного нуля из прямого кода в обратный, а затем в дополнительный: $$ \begin{aligned} -0 \rightarrow \; & 1000 \; 0000 \; \text{(прямой код)} \newline = \; & 1111 \; 1111 \; \text{(обратный код)} \newline = 1 \; & 0000 \; 0000 \; \text{(дополнительный код)} \newline \end{aligned} $$ При добавлении $1$ к обратному коду отрицательного нуля возникает перенос, но длина типа `byte` составляет всего 8 бит, поэтому переполнившаяся в 9-й бит единица отбрасывается. Иными словами, **дополнительный код отрицательного нуля равен $0000 \; 0000$ и совпадает с дополнительным кодом положительного нуля**. Значит, в представлении дополнительного кода существует только один ноль, и проблема неоднозначности положительного и отрицательного нуля тем самым устраняется. Остается последний вопрос: диапазон типа `byte` равен $[-128, 127]$ , откуда берется лишнее отрицательное число $-128$ ? Мы замечаем, что у всех целых чисел из интервала $[-127, +127]$ есть соответствующие прямой, обратный и дополнительный коды, а прямой и дополнительный коды можно преобразовывать друг в друга. Однако **дополнительный код $1000 \; 0000$ является исключением: у него нет соответствующего прямого кода**. Согласно правилу преобразования, прямой код для этого дополнительного кода должен быть равен $0000 \; 0000$ . Это очевидное противоречие, потому что такой прямой код обозначает число $0$ , а его дополнительный код должен совпадать с ним самим. Компьютер просто определяет, что этот особый дополнительный код $1000 \; 0000$ представляет число $-128$ . На самом деле результат вычисления $(-1) + (-127)$ в дополнительном коде как раз и равен $-128$ . $$ \begin{aligned} & (-127) + (-1) \newline & \rightarrow 1111 \; 1111 \; \text{(прямой код)} + 1000 \; 0001 \; \text{(прямой код)} \newline & = 1000 \; 0000 \; \text{(обратный код)} + 1111 \; 1110 \; \text{(обратный код)} \newline & = 1000 \; 0001 \; \text{(дополнительный код)} + 1111 \; 1111 \; \text{(дополнительный код)} \newline & = 1000 \; 0000 \; \text{(дополнительный код)} \newline & \rightarrow -128 \end{aligned} $$ Ты, вероятно, уже заметил, что все приведенные выше вычисления были операциями сложения. Это указывает на важный факт: **аппаратные схемы внутри компьютера в основном проектируются на основе операций сложения**. Причина в том, что сложение по сравнению с другими операциями (например умножением, делением и вычитанием) проще реализуется на аппаратном уровне, легче распараллеливается и выполняется быстрее. Обрати внимание: это не означает, что компьютер умеет только складывать. **Комбинируя сложение с некоторыми базовыми логическими операциями, компьютер может реализовать и другие математические операции**. Например, вычитание $a - b$ можно преобразовать в сложение $a + (-b)$ ; умножение и деление можно свести к многократному сложению или вычитанию. Теперь можно подвести итог, почему компьютеры используют дополнительный код: с представлением в дополнительном коде компьютер может использовать одни и те же схемы и операции для сложения положительных и отрицательных чисел, без необходимости проектировать специальные аппаратные схемы для вычитания и без особой обработки неоднозначности положительного и отрицательного нуля. Это значительно упрощает аппаратную архитектуру и повышает эффективность вычислений. Идея дополнительного кода очень изящна; из-за ограничений по объему мы на этом остановимся. Если тебе интересно, стоит изучить эту тему глубже. ## Кодирование чисел с плавающей точкой Внимательный читатель может заметить: `int` и `float` имеют одинаковую длину, по 4 байта , но почему диапазон значений у `float` намного больше, чем у `int` ? Это выглядит парадоксально, ведь `float` должен еще представлять дробные числа, а значит диапазон вроде бы должен быть меньше. На самом деле **это связано с тем, что число с плавающей точкой `float` использует другой способ представления**. Обозначим двоичное число длиной 32 бита как: $$ b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0 $$ Согласно стандарту IEEE 754, 32-битный `float` состоит из следующих трех частей. - Бит знака $\mathrm{S}$ : занимает 1 бит и соответствует $b_{31}$ . - Биты экспоненты $\mathrm{E}$ : занимают 8 бит и соответствуют $b_{30} b_{29} \ldots b_{23}$ . - Биты мантиссы $\mathrm{N}$ : занимают 23 бита и соответствуют $b_{22} b_{21} \ldots b_0$ . Формула вычисления значения, соответствующего двоичному числу `float`, имеет вид: $$ \text {val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2-127} \times\left(1 . b_{22} b_{21} \ldots b_0\right)_2 $$ Если перейти к десятичной записи, формула вычисления будет такой: $$ \text {val}=(-1)^{\mathrm{S}} \times 2^{\mathrm{E} -127} \times (1 + \mathrm{N}) $$ Диапазоны значений соответствующих частей таковы: $$ \begin{aligned} \mathrm{S} \in & \{ 0, 1\}, \quad \mathrm{E} \in \{ 1, 2, \dots, 254 \} \newline (1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} 2^{-i}) \subset [1, 2 - 2^{-23}] \end{aligned} $$ ![Пример вычисления float по стандарту IEEE 754](number_encoding.assets/ieee_754_float.png) Посмотрим на рисунок выше: если взять пример $\mathrm{S} = 0$ , $\mathrm{E} = 124$ , $\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ , то получим: $$ \text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875 $$ Теперь мы можем ответить на исходный вопрос: **в представлении `float` присутствуют биты экспоненты, поэтому его диапазон значений намного больше, чем у `int`**. Согласно приведенным выше вычислениям, максимально возможное положительное число для `float` равно $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ ; если изменить бит знака, получим минимальное отрицательное число. **Хотя число с плавающей точкой `float` расширяет диапазон значений, побочным эффектом становится потеря точности**. Целочисленный тип `int` использует все 32 бита для представления числа, и числа распределены равномерно; а из-за существования битов экспоненты у `float` чем больше число, тем больше обычно становится разница между двумя соседними представимыми значениями. Как показано в таблице ниже, значения экспоненты $\mathrm{E} = 0$ и $\mathrm{E} = 255$ имеют специальный смысл и **используются для представления нуля, бесконечности, $\mathrm{NaN}$ и т.д.**

Таблица   Значение поля экспоненты

| Поле экспоненты E | Поле мантиссы $\mathrm{N} = 0$ | Поле мантиссы $\mathrm{N} \ne 0$ | Формула вычисления | | ------------------- | ------------------------------ | -------------------------------- | ----------------------------------------------------------------------- | | $0$ | $\pm 0$ | Денормализованное число | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ | | $1, 2, \dots, 254$ | Нормализованное число | Нормализованное число | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ | | $255$ | $\pm \infty$ | $\mathrm{NaN}$ | | Стоит отметить, что денормализованные числа заметно повышают точность чисел с плавающей точкой. Наименьшее положительное нормализованное число равно $2^{-126}$ , а наименьшее положительное денормализованное число равно $2^{-126} \times 2^{-23}$ . Двойная точность `double` использует способ представления, аналогичный `float` , поэтому здесь мы не будем подробно останавливаться на нем. ================================================ FILE: ru/docs/chapter_data_structure/summary.md ================================================ # Резюме ### Ключевые выводы - Структуры данных можно классифицировать с точки зрения логической и физической структуры. Логическая структура описывает логические отношения между элементами данных, а физическая структура описывает способ хранения данных в памяти компьютера. - К распространенным логическим структурам относятся линейные, древовидные и сетевые. Обычно структуры данных делятся на линейные (массивы, связные списки, стеки, очереди) и нелинейные (деревья, графы, кучи). Реализация хеш-таблицы может включать как линейные, так и нелинейные структуры данных. - При выполнении программы данные хранятся в памяти компьютера. Каждый участок памяти имеет соответствующий адрес, с помощью которого программа получает доступ к данным. - Физическая структура делится на хранение в непрерывном пространстве (массивы) и хранение в разрозненном пространстве (связные списки). Все структуры данных реализуются на основе массивов, связных списков или их комбинации. - Базовые типы данных в компьютере включают целые `byte` , `short` , `int` , `long` , числа с плавающей точкой `float` , `double` , символы `char` и логический тип `bool` . Их диапазон значений зависит от объема занимаемого пространства и способа представления. - Прямой код, обратный код и дополнительный код - это три способа кодирования чисел в компьютере, между которыми можно выполнять взаимные преобразования. В прямом коде старший бит целого числа является знаковым, а остальные биты представляют значение числа. - Целые числа в компьютере хранятся в виде дополнительного кода. В таком представлении компьютер может одинаково обрабатывать сложение положительных и отрицательных чисел без специальной аппаратной схемы для вычитания, и при этом исчезает неоднозначность положительного и отрицательного нуля. - Кодирование числа с плавающей точкой состоит из 1 бита знака, 8 битов экспоненты и 23 битов мантиссы. Благодаря наличию экспоненты диапазон значений у чисел с плавающей точкой намного больше, чем у целых, но это достигается ценой потери точности. - ASCII - это самый ранний набор английских символов длиной 1 байт, включающий в общей сложности 127 символов. Набор GBK - распространенный китайский набор символов, включающий более двадцати тысяч иероглифов. Unicode стремится предоставить единый полный стандарт набора символов, включающий символы всех языков мира, чтобы решить проблемы искаженного текста, вызванные несовместимыми способами кодирования. - UTF-8 - самый популярный способ кодирования Unicode, обладающий очень хорошей универсальностью. Это кодировка переменной длины, хорошо расширяемая и эффективно использующая память. UTF-16 и UTF-32 относятся к кодировкам фиксированной длины. При кодировании китайского текста UTF-16 занимает меньше места, чем UTF-8. Такие языки программирования, как Java и C#, по умолчанию используют UTF-16. ### Q & A **Q**: Почему хеш-таблица одновременно включает линейные и нелинейные структуры данных? В основе хеш-таблицы лежит массив, а для разрешения коллизий мы можем использовать "цепочки адресации" (об этом будет рассказано в последующем разделе "Хеш-коллизии"): каждый бакет массива указывает на связный список, а если длина списка превышает некоторый порог, он может быть преобразован в дерево (обычно в красно-черное дерево). С точки зрения хранения данных в основе хеш-таблицы находится массив, где каждый слот бакета может содержать либо отдельное значение, либо связный список, либо дерево. Поэтому хеш-таблица действительно может одновременно включать линейные структуры данных (массивы, списки) и нелинейные структуры данных (деревья). **Q**: Длина типа `char` равна 1 байту? Длина типа `char` определяется используемым в языке программирования способом кодирования. Например, Java, JavaScript, TypeScript и C# используют кодировку UTF-16 (для хранения кодовых точек Unicode), поэтому длина `char` у них равна 2 байтам. **Q**: Не является ли двусмысленным утверждение, что структуры данных, реализованные на основе массива, также называются "статическими структурами данных"? Ведь стек тоже поддерживает операции push и pop, а они явно "динамические". Стек действительно может поддерживать динамические операции над данными, но сама структура данных при этом остается "статической" (ее длина неизменна). Хотя структуры на основе массива могут динамически добавлять и удалять элементы, их емкость фиксирована. Если количество данных превышает заранее выделенный размер, приходится создавать новый, более крупный массив и копировать в него содержимое старого. **Q**: При построении стека (очереди) его размер не задается явно, почему же его относят к "статическим структурам данных"? В языках высокого уровня нам не нужно вручную задавать начальную емкость стека (очереди): это автоматически делает сама реализация класса. Например, начальная емкость `ArrayList` в Java обычно равна 10. Кроме того, автоматом реализуется и расширение емкости. Подробнее это рассматривается в последующем разделе о "списках". **Q**: Если метод преобразования из прямого кода в дополнительный - это "сначала инвертировать, затем прибавить 1", то обратное преобразование из дополнительного кода в прямой, по идее, должно быть обратной операцией "сначала вычесть 1, затем инвертировать". Почему же дополнительный код также можно перевести в прямой тем же способом "сначала инвертировать, затем прибавить 1"? Это связано с тем, что взаимное преобразование прямого и дополнительного кодов по сути является вычислением "дополнения". Сначала дадим определение дополнения: если $a + b = c$ , то говорят, что $a$ является дополнением числа $b$ до $c$ ; аналогично, $b$ является дополнением числа $a$ до $c$ . Для двоичного числа длины $n = 4$ со значением $0010$ , если рассматривать его как прямой код (не учитывая знаковый бит), то его дополнительный код получается правилом "сначала инвертировать, затем прибавить 1": $$ 0010 \rightarrow 1101 \rightarrow 1110 $$ Мы видим, что сумма прямого и дополнительного кодов равна $0010 + 1110 = 10000$ , то есть дополнительный код $1110$ является "дополнением" прямого кода $0010$ до $10000$ . **Это означает, что описанная выше операция "сначала инвертировать, затем прибавить 1" на самом деле вычисляет дополнение до $10000$ **. Тогда чему равно "дополнение" дополнительного кода $1110$ до $10000$ ? Мы снова можем получить его правилом "сначала инвертировать, затем прибавить 1": $$ 1110 \rightarrow 0001 \rightarrow 0010 $$ Иначе говоря, прямой и дополнительный коды являются взаимными "дополнениями" друг друга до $10000$ , поэтому и "прямой код -> дополнительный код", и "дополнительный код -> прямой код" можно реализовать одной и той же операцией (сначала инвертировать, затем прибавить 1). Разумеется, можно получить прямой код из дополнительного кода $1110$ и обратной операцией, то есть "сначала вычесть 1, затем инвертировать": $$ 1110 \rightarrow 1101 \rightarrow 0010 $$ В итоге и "сначала инвертировать, затем прибавить 1", и "сначала вычесть 1, затем инвертировать" - это два эквивалентных способа вычисления дополнения до $10000$ . По сути операция "инвертировать" сама по себе вычисляет дополнение до $1111$ (потому что всегда выполняется `прямой код + обратный код = 1111` ); а дополнительный код, получающийся после добавления 1 к обратному коду, и есть дополнение до $10000$ . Приведенный выше пример использовал $n = 4$ , но его можно обобщить на двоичные числа любой длины. ================================================ FILE: ru/docs/chapter_divide_and_conquer/binary_search_recur.md ================================================ # Поисковая стратегия разделяй и властвуй Мы уже знаем, что алгоритмы поиска делятся на две большие категории. - **Полный перебор**: реализуется через обход структуры данных, временная сложность равна $O(n)$ . - **Адаптивный поиск**: использует особую организацию данных или априорную информацию, временная сложность может достигать $O(\log n)$ и даже $O(1)$ . На практике **алгоритмы поиска с временной сложностью $O(\log n)$ обычно реализуются на основе стратегии "разделяй и властвуй"**, например двоичный поиск и деревья. - На каждом шаге двоичный поиск раскладывает задачу (поиск целевого элемента в массиве) на более мелкую задачу (поиск целевого элемента в одной половине массива), и этот процесс продолжается, пока массив не станет пустым или пока не будет найден целевой элемент. - Деревья являются типичными представителями идей "разделяй и властвуй"; в таких структурах данных, как двоичное дерево поиска, AVL-дерево и куча, временная сложность различных операций равна $O(\log n)$ . Стратегия "разделяй и властвуй" для двоичного поиска выглядит следующим образом. - **Задача раскладывается на части**: двоичный поиск рекурсивно разбивает исходную задачу (поиск в массиве) на подзадачу (поиск в одной половине массива), и это достигается сравнением среднего элемента с целевым значением. - **Подзадачи независимы**: в двоичном поиске на каждом шаге обрабатывается только одна подзадача, и она не зависит от других подзадач. - **Решения подзадач не нужно объединять**: двоичный поиск нацелен на поиск конкретного элемента, поэтому объединять решения подзадач не требуется. Как только подзадача решена, одновременно считается решенной и исходная задача. Иными словами, стратегия "разделяй и властвуй" повышает эффективность поиска потому, что при полном переборе за один шаг удается исключить только один вариант, **тогда как при поиске на основе "разделяй и властвуй" за один шаг можно исключить половину вариантов**. ### Реализация двоичного поиска на основе "разделяй и властвуй" В предыдущих главах двоичный поиск реализовывался через итерацию. Теперь реализуем его с помощью стратегии "разделяй и властвуй", то есть через рекурсию. !!! question Дан отсортированный массив `nums` длины $n$ , в котором все элементы уникальны. Найдите элемент `target` . С точки зрения стратегии "разделяй и властвуй" обозначим подзадачу, соответствующую интервалу поиска $[i, j]$ , через $f(i, j)$ . Начиная с исходной задачи $f(0, n-1)$ , выполняем двоичный поиск по следующим шагам. 1. Вычислить середину $m$ интервала поиска $[i, j]$ и с ее помощью исключить половину интервала. 2. Рекурсивно решить подзадачу вдвое меньшего размера; это может быть либо $f(i, m-1)$ , либо $f(m+1, j)$ . 3. Повторять шаг `1.` и шаг `2.` , пока не будет найден `target` или пока интервал не станет пустым. На рисунке ниже показан процесс применения стратегии "разделяй и властвуй" для поиска элемента $6$ в массиве. ![Процесс двоичного поиска в стиле разделяй и властвуй](binary_search_recur.assets/binary_search_recur.png) В реализации кода мы объявляем рекурсивную функцию `dfs()` для решения задачи $f(i, j)$ : ```src [file]{binary_search_recur}-[class]{}-[func]{binary_search} ``` ================================================ FILE: ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.md ================================================ # Задача построения двоичного дерева !!! question Даны прямой обход `preorder` и симметричный обход `inorder` некоторого двоичного дерева. Постройте по ним двоичное дерево и верните его корневой узел. Предполагается, что в дереве нет узлов с одинаковыми значениями (как показано на рисунке ниже). ![Пример данных для построения двоичного дерева](build_binary_tree_problem.assets/build_tree_example.png) ### Проверка, является ли это задачей "разделяй и властвуй" Исходная задача - построить двоичное дерево по `preorder` и `inorder` - является типичной задачей для стратегии "разделяй и властвуй". - **Задача раскладывается на части**: если смотреть с точки зрения стратегии "разделяй и властвуй", исходную задачу можно разбить на две подзадачи: построение левого поддерева и построение правого поддерева, плюс одно действие: инициализация корневого узла. Для каждого поддерева (подзадачи) можно использовать тот же способ разбиения, пока не будет достигнута наименьшая подзадача (пустое поддерево). - **Подзадачи независимы**: левое и правое поддеревья независимы друг от друга и не пересекаются. При построении левого поддерева нам нужно смотреть только на ту часть прямого и симметричного обходов, которая соответствует левому поддереву. Для правого поддерева рассуждение аналогично. - **Решения подзадач можно объединить**: когда левое и правое поддеревья (решения подзадач) уже построены, их можно присоединить к корневому узлу и тем самым получить решение исходной задачи. ### Как разделить поддеревья Из анализа выше видно, что эта задача действительно решается через "разделяй и властвуй", **но как именно, имея прямой обход `preorder` и симметричный обход `inorder`, отделить левое и правое поддеревья**? По определению и `preorder` , и `inorder` можно разбить на три части. - Прямой обход: `[ корневой узел | левое поддерево | правое поддерево ]` , например для дерева на рисунке выше это `[ 3 | 9 | 2 1 7 ]` . - Симметричный обход: `[ левое поддерево | корневой узел | правое поддерево ]` , например для дерева на рисунке выше это `[ 9 | 3 | 1 2 7 ]` . На примере данных с рисунка можно получить результат разбиения по следующим шагам. 1. Первый элемент прямого обхода, равный 3, является значением корневого узла. 2. Найти индекс корневого узла 3 в `inorder` ; используя этот индекс, можно разбить `inorder` на `[ 9 | 3 | 1 2 7 ]` . 3. По результату разбиения `inorder` нетрудно определить, что число узлов в левом и правом поддеревьях равно 1 и 3 соответственно, а значит, `preorder` можно разбить как `[ 3 | 9 | 2 1 7 ]` . ![Разбиение поддеревьев в прямом и симметричном обходах](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png) ### Описание интервалов поддеревьев через переменные Согласно описанному выше способу разбиения, **мы уже получили интервалы индексов корневого узла, левого и правого поддеревьев в `preorder` и `inorder`**. Чтобы описывать эти интервалы, нам понадобится несколько указателей-переменных. - Обозначим индекс корневого узла текущего дерева в `preorder` через $i$ . - Обозначим индекс корневого узла текущего дерева в `inorder` через $m$ . - Обозначим интервал индексов текущего дерева в `inorder` через $[l, r]$ . Как показано в таблице ниже, этих переменных достаточно для описания индекса корневого узла в `preorder` и интервалов поддеревьев в `inorder` .

Таблица   Индексы корневого узла и поддеревьев в прямом и симметричном обходах

| | Индекс корневого узла в `preorder` | Интервал индексов поддерева в `inorder` | | ---------------- | ---------------------------------- | ---------------------------------------- | | Текущее дерево | $i$ | $[l, r]$ | | Левое поддерево | $i + 1$ | $[l, m-1]$ | | Правое поддерево | $i + 1 + (m - l)$ | $[m+1, r]$ | Стоит отметить, что $(m-l)$ в индексе корневого узла правого поддерева означает число узлов в левом поддереве; лучше всего понимать это выражение вместе с рисунком ниже. ![Представление индексных интервалов корня и поддеревьев](build_binary_tree_problem.assets/build_tree_division_pointers.png) ### Реализация кода Чтобы ускорить поиск $m$ , мы используем хеш-таблицу `hmap` для хранения отображения значений массива `inorder` в индексы: ```src [file]{build_tree}-[class]{}-[func]{build_tree} ``` На рисунке ниже показан рекурсивный процесс построения двоичного дерева: каждый узел создается в фазе "спуска", а каждое ребро (ссылка) формируется в фазе "подъема". === "<1>" ![Рекурсивный процесс построения двоичного дерева](build_binary_tree_problem.assets/built_tree_step1.png) === "<2>" ![built_tree_step2](build_binary_tree_problem.assets/built_tree_step2.png) === "<3>" ![built_tree_step3](build_binary_tree_problem.assets/built_tree_step3.png) === "<4>" ![built_tree_step4](build_binary_tree_problem.assets/built_tree_step4.png) === "<5>" ![built_tree_step5](build_binary_tree_problem.assets/built_tree_step5.png) === "<6>" ![built_tree_step6](build_binary_tree_problem.assets/built_tree_step6.png) === "<7>" ![built_tree_step7](build_binary_tree_problem.assets/built_tree_step7.png) === "<8>" ![built_tree_step8](build_binary_tree_problem.assets/built_tree_step8.png) === "<9>" ![built_tree_step9](build_binary_tree_problem.assets/built_tree_step9.png) Результаты разбиения `preorder` и `inorder` внутри каждого рекурсивного вызова показаны на рисунке ниже. ![Результаты разбиения в каждом рекурсивном вызове](build_binary_tree_problem.assets/built_tree_overall.png) Пусть число узлов дерева равно $n$ ; инициализация каждого узла (то есть выполнение одного рекурсивного вызова `dfs()` ) занимает $O(1)$ времени. **Следовательно, общая временная сложность равна $O(n)$** . Хеш-таблица хранит отображение значений `inorder` в индексы, поэтому ее пространственная сложность равна $O(n)$ . В худшем случае, когда двоичное дерево вырождается в связный список, глубина рекурсии достигает $n$ и требует $O(n)$ памяти стека. **Следовательно, общая пространственная сложность также равна $O(n)$** . ================================================ FILE: ru/docs/chapter_divide_and_conquer/divide_and_conquer.md ================================================ # Стратегия разделяй и властвуй Разделяй и властвуй (divide and conquer) - это очень важная и широко используемая стратегия построения алгоритмов. Обычно она реализуется через рекурсию и включает два этапа: "разделение" и "объединение". 1. **Разделение (этап декомпозиции)**: рекурсивно разбить исходную задачу на две или более подзадачи, пока не будет достигнута наименьшая подзадача. 2. **Объединение (этап синтеза)**: начиная с уже известных решений наименьших подзадач, снизу вверх объединять решения подзадач и тем самым получать решение исходной задачи. Как показано на рисунке ниже, "сортировка слиянием" является одним из типичных примеров применения стратегии "разделяй и властвуй". 1. **Разделение**: рекурсивно разделить исходный массив (исходную задачу) на два подмассива (подзадачи), пока в подмассиве не останется только один элемент (наименьшая подзадача). 2. **Объединение**: снизу вверх объединять упорядоченные подмассивы (решения подзадач), чтобы получить упорядоченный исходный массив (решение исходной задачи). ![Стратегия разделяй и властвуй в сортировке слиянием](divide_and_conquer.assets/divide_and_conquer_merge_sort.png) ## Как определить задачу "разделяй и властвуй" Чтобы понять, подходит ли задача для решения методом "разделяй и властвуй", обычно можно ориентироваться на следующие критерии. 1. **Задача раскладывается на части**: исходную задачу можно разбить на более мелкие и похожие подзадачи, причем такое разбиение можно применять рекурсивно. 2. **Подзадачи независимы**: подзадачи не пересекаются, не зависят друг от друга и могут решаться независимо. 3. **Решения подзадач можно объединить**: решение исходной задачи получается объединением решений подзадач. Очевидно, что сортировка слиянием удовлетворяет всем трем критериям. 1. **Задача раскладывается на части**: массив (исходная задача) рекурсивно делится на два подмассива (подзадачи). 2. **Подзадачи независимы**: каждый подмассив можно сортировать отдельно (то есть каждую подзадачу можно решать независимо). 3. **Решения подзадач можно объединить**: два упорядоченных подмассива (решения подзадач) можно объединить в один упорядоченный массив (решение исходной задачи). ## Повышение эффективности с помощью "разделяй и властвуй" **Стратегия "разделяй и властвуй" не только позволяет эффективно решать алгоритмические задачи, но и часто повышает эффективность самих алгоритмов**. Именно поэтому быстрая сортировка, сортировка слиянием и пирамидальная сортировка обычно работают быстрее, чем сортировка выбором, пузырьком и вставками. Тогда возникает естественный вопрос: **почему стратегия "разделяй и властвуй" повышает эффективность алгоритма и какова внутренняя логика этого подхода**? Иными словами, почему разбиение большой задачи на несколько подзадач, решение этих подзадач и последующее объединение их решений оказывается эффективнее, чем прямое решение исходной задачи? Этот вопрос можно рассмотреть с двух сторон: через число операций и через параллельные вычисления. ### Оптимизация числа операций Рассмотрим "сортировку пузырьком": для массива длины $n$ ей требуется $O(n^2)$ времени. Предположим, что мы разделим массив на два подмассива в середине, как показано на рисунке ниже. Тогда само разбиение потребует $O(n)$ времени, сортировка каждого подмассива займет $O((n / 2)^2)$ времени, а объединение двух подмассивов потребует еще $O(n)$ времени. Общая временная сложность будет равна: $$ O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n) $$ ![Сортировка пузырьком до и после разбиения массива](divide_and_conquer.assets/divide_and_conquer_bubble_sort.png) Теперь рассмотрим следующее неравенство, в котором левая и правая части обозначают общее число операций до разбиения и после него: $$ \begin{aligned} n^2 & > \frac{n^2}{2} + 2n \newline n^2 - \frac{n^2}{2} - 2n & > 0 \newline n(n - 4) & > 0 \end{aligned} $$ **Это означает, что при $n > 4$ число операций после разбиения становится меньше, а значит, сортировка должна работать быстрее**. При этом важно заметить, что временная сложность после разбиения все еще остается квадратичной, то есть $O(n^2)$ ; уменьшается лишь константный множитель. Если пойти дальше и **продолжать делить каждый подмассив пополам**, пока в нем не останется только один элемент, то мы фактически получим "сортировку слиянием", чья временная сложность равна $O(n \log n)$ . Можно пойти еще дальше и спросить: **что если задать несколько точек разделения** и равномерно разбить исходный массив на $k$ подмассивов? Такая ситуация очень похожа на блочную сортировку, которая особенно хорошо подходит для сортировки очень больших объемов данных и теоретически может достигать временной сложности $O(n + k)$ . ### Оптимизация параллельных вычислений Мы знаем, что подзадачи, порождаемые стратегией "разделяй и властвуй", являются независимыми, **а значит, их обычно можно решать параллельно**. Иначе говоря, "разделяй и властвуй" не только может уменьшить временную сложность алгоритма, **но и хорошо сочетается с параллельной оптимизацией на уровне системы**. Параллельная оптимизация особенно эффективна в среде с несколькими ядрами или несколькими процессорами, потому что система может одновременно обрабатывать разные подзадачи, лучше загружая вычислительные ресурсы и тем самым заметно сокращая общее время работы. Например, в показанной ниже "блочной сортировке" большой объем данных равномерно распределяется по блокам. Тогда сортировку каждого блока можно поручить отдельным вычислительным единицам, а после завершения просто объединить результаты. ![Параллельные вычисления в блочной сортировке](divide_and_conquer.assets/divide_and_conquer_parallel_computing.png) ## Типичные применения стратегии "разделяй и властвуй" С одной стороны, стратегию "разделяй и властвуй" можно использовать для решения многих классических алгоритмических задач. - **Поиск ближайшей пары точек**: сначала множество точек делится на две части, затем ищется ближайшая пара в каждой части, а затем ближайшая пара, пересекающая границу между двумя частями. - **Умножение больших чисел**: например, алгоритм Карацубы, который раскладывает умножение больших чисел на несколько умножений и сложений меньших чисел. - **Умножение матриц**: например, алгоритм Штрассена, который раскладывает умножение больших матриц на несколько умножений и сложений матриц меньшего размера. - **Задача о Ханойской башне**: задача о Ханойской башне решается рекурсивно и является типичным примером применения стратегии "разделяй и властвуй". - **Подсчет инверсий**: если в последовательности предыдущее число больше следующего, то такая пара образует инверсию. Эту задачу можно решить с помощью идей "разделяй и властвуй", опираясь на сортировку слиянием. С другой стороны, стратегия "разделяй и властвуй" очень широко применяется при проектировании алгоритмов и структур данных. - **Двоичный поиск**: двоичный поиск делит отсортированный массив на две части по индексу середины, а затем, в зависимости от результата сравнения целевого значения со средним элементом, исключает одну из половин и повторяет ту же операцию на оставшемся интервале. - **Сортировка слиянием**: она уже была рассмотрена в начале этого раздела, поэтому не будем повторяться. - **Быстрая сортировка**: в ней выбирается опорное значение, после чего массив делится на два подмассива: один содержит элементы меньше опорного, а другой - больше. Затем такая же операция повторяется для обеих частей, пока в подмассиве не останется один элемент. - **Блочная сортировка**: ее основная идея заключается в распределении данных по нескольким блокам, сортировке элементов внутри каждого блока и последующем последовательном извлечении элементов из блоков для построения отсортированного массива. - **Деревья**: например, двоичные деревья поиска, AVL-деревья, красно-черные деревья, B-деревья, B+ деревья и т.д. Их операции поиска, вставки и удаления можно рассматривать как применение стратегии "разделяй и властвуй". - **Кучи**: куча является особым видом полного двоичного дерева, а такие операции, как вставка, удаление и упорядочивание, по сути содержат идеи "разделяй и властвуй". - **Хеш-таблицы**: хотя хеш-таблицы напрямую не используют стратегию "разделяй и властвуй", некоторые способы разрешения коллизий косвенно опираются на эту идею. Например, длинные цепочки в методе цепочек могут преобразовываться в красно-черные деревья для повышения эффективности поиска. Нетрудно заметить, что **"разделяй и властвуй" - это "тихая" алгоритмическая идея**, скрыто присутствующая внутри самых разных алгоритмов и структур данных. ================================================ FILE: ru/docs/chapter_divide_and_conquer/hanota_problem.md ================================================ # Задача о Ханойской башне В задачах сортировки слиянием и построения двоичного дерева мы делили исходную задачу на две подзадачи, каждая из которых имела размер, равный примерно половине исходной задачи. Однако для задачи о Ханойской башне используется другая стратегия разбиения. !!! question Даны три стержня, обозначенные как `A` , `B` и `C` . В начальном состоянии на стержне `A` находятся $n$ дисков, расположенных сверху вниз в порядке от меньшего к большему. Нужно переместить эти $n$ дисков на стержень `C` , сохранив их исходный порядок (как показано на рисунке ниже). Во время перемещения дисков необходимо соблюдать следующие правила. 1. Диск можно снять только с вершины одного стержня и положить только на вершину другого стержня. 2. За один раз можно перемещать только один диск. 3. Меньший диск всегда должен лежать на большем. ![Пример задачи о Ханойской башне](hanota_problem.assets/hanota_example.png) **Обозначим задачу о Ханойской башне размера $i$ как $f(i)$** . Например, $f(3)$ означает задачу перемещения 3 дисков со стержня `A` на стержень `C` . ### Рассмотрим базовые случаи Как показано на рисунке ниже, для задачи $f(1)$ , то есть когда имеется только один диск, достаточно просто переместить его напрямую со стержня `A` на стержень `C` . === "<1>" ![Решение задачи размера 1](hanota_problem.assets/hanota_f1_step1.png) === "<2>" ![hanota_f1_step2](hanota_problem.assets/hanota_f1_step2.png) Как показано на рисунке ниже, для задачи $f(2)$ , то есть когда есть два диска, **поскольку меньший диск все время должен лежать на большем, приходится использовать `B` как вспомогательный стержень**. 1. Сначала переместить верхний маленький диск с `A` на `B` . 2. Затем переместить большой диск с `A` на `C` . 3. Наконец, переместить маленький диск с `B` на `C` . === "<1>" ![Решение задачи размера 2](hanota_problem.assets/hanota_f2_step1.png) === "<2>" ![hanota_f2_step2](hanota_problem.assets/hanota_f2_step2.png) === "<3>" ![hanota_f2_step3](hanota_problem.assets/hanota_f2_step3.png) === "<4>" ![hanota_f2_step4](hanota_problem.assets/hanota_f2_step4.png) Процесс решения задачи $f(2)$ можно кратко описать так: **переместить два диска с `A` на `C` с помощью `B`** . Здесь `C` называется целевым стержнем, а `B` - буферным стержнем. ### Разбиение на подзадачи Для задачи $f(3)$ , то есть когда имеется три диска, ситуация становится сложнее. Поскольку решения $f(1)$ и $f(2)$ уже известны, можно подойти к задаче с точки зрения стратегии "разделяй и властвуй" и **рассматривать два верхних диска на `A` как единое целое**, выполняя шаги, показанные на рисунке ниже. Так три диска успешно перемещаются с `A` на `C` . 1. Сделать `B` целевым стержнем, а `C` буферным, и переместить два диска с `A` на `B` . 2. Переместить оставшийся один диск с `A` напрямую на `C` . 3. Сделать `C` целевым стержнем, а `A` буферным, и переместить два диска с `B` на `C` . === "<1>" ![Решение задачи размера 3](hanota_problem.assets/hanota_f3_step1.png) === "<2>" ![hanota_f3_step2](hanota_problem.assets/hanota_f3_step2.png) === "<3>" ![hanota_f3_step3](hanota_problem.assets/hanota_f3_step3.png) === "<4>" ![hanota_f3_step4](hanota_problem.assets/hanota_f3_step4.png) Иначе говоря, **мы разбиваем задачу $f(3)$ на две подзадачи $f(2)$ и одну подзадачу $f(1)$** . Если последовательно решить эти три подзадачи, исходная задача тоже будет решена. Это показывает, что подзадачи независимы и что их решения можно объединить. Таким образом, можно сформулировать показанную на рисунке ниже стратегию "разделяй и властвуй" для задачи о Ханойской башне: исходная задача $f(n)$ разбивается на две подзадачи $f(n-1)$ и одну подзадачу $f(1)$ , которые затем решаются в следующем порядке. 1. Переместить $n-1$ дисков с `A` на `B` с помощью `C` . 2. Переместить оставшийся $1$ диск напрямую с `A` на `C` . 3. Переместить $n-1$ дисков с `B` на `C` с помощью `A` . Для двух подзадач $f(n-1)$ **можно применять тот же способ рекурсивного разбиения**, пока не будет достигнута наименьшая подзадача $f(1)$ . А решение для $f(1)$ уже известно и требует всего одного перемещения. ![Стратегия разделяй и властвуй для решения задачи о Ханойской башне](hanota_problem.assets/hanota_divide_and_conquer.png) ### Реализация кода В коде мы объявляем рекурсивную функцию `dfs(i, src, buf, tar)` , которая перемещает $i$ верхних дисков со стержня `src` на целевой стержень `tar` с помощью буферного стержня `buf` : ```src [file]{hanota}-[class]{}-[func]{solve_hanota} ``` Как показано на рисунке ниже, задача о Ханойской башне формирует дерево рекурсии высоты $n$ , в котором каждый узел представляет подзадачу и соответствует одному открытому вызову `dfs()` ; **поэтому временная сложность равна $O(2^n)$ , а пространственная сложность равна $O(n)$** . ![Дерево рекурсии задачи о Ханойской башне](hanota_problem.assets/hanota_recursive_tree.png) !!! quote Задача о Ханойской башне происходит из древней легенды. В одном из храмов древней Индии монахи имели три высоких алмазных стержня и $64$ золотых диска разного размера. Монахи непрерывно перекладывали диски и верили, что в тот момент, когда последний диск будет правильно перенесен, мир подойдет к концу. Однако даже если бы монахи перемещали по одному диску в секунду, им понадобилось бы примерно $2^{64} \approx 1.84×10^{19}$ секунд, то есть около $585$ миллиардов лет, что намного превышает текущую оценку возраста Вселенной. Поэтому, если легенда и верна, нам, вероятно, пока не о чем беспокоиться. ================================================ FILE: ru/docs/chapter_divide_and_conquer/index.md ================================================ # Разделяй и властвуй ![Разделяй и властвуй](../assets/covers/chapter_divide_and_conquer.jpg) !!! abstract Сложная задача раскладывается слой за слоем, и каждое новое разбиение делает ее проще. Принцип "разделяй и властвуй" показывает важный факт: если начать с простого, многое перестает быть сложным. ================================================ FILE: ru/docs/chapter_divide_and_conquer/summary.md ================================================ # Резюме ### Ключевые выводы - "Разделяй и властвуй" - это распространенная стратегия проектирования алгоритмов, которая включает два этапа: разделение (декомпозицию) и объединение (синтез), и обычно реализуется с помощью рекурсии. - Критерии применимости этой стратегии к задаче включают: возможность разложения задачи, независимость подзадач и возможность объединения их решений. - Сортировка слиянием является типичным применением стратегии "разделяй и властвуй": она рекурсивно делит массив на два равных по длине подмассива, пока не останется массив из одного элемента, после чего начинает поэтапное объединение. - Использование стратегии "разделяй и властвуй" часто позволяет повысить эффективность алгоритма. С одной стороны, она уменьшает число операций; с другой - после разбиения способствует параллельной оптимизации на уровне системы. - "Разделяй и властвуй" не только помогает решать многие алгоритмические задачи, но и широко используется при проектировании структур данных и алгоритмов, поэтому его можно встретить буквально повсюду. - По сравнению с полным перебором адаптивный поиск работает эффективнее. Алгоритмы поиска со сложностью $O(\log n)$ обычно реализуются на основе стратегии "разделяй и властвуй". - Двоичный поиск - еще одно типичное применение стратегии "разделяй и властвуй", в котором отсутствует шаг объединения решений подзадач. Его можно реализовать рекурсивно, опираясь на эту стратегию. - В задаче построения двоичного дерева исходная задача построения дерева может быть разбита на две подзадачи: построение левого и правого поддеревьев, а реализуется это через разбиение индексных интервалов прямого и симметричного обходов. - В задаче о Ханойской башне задача размера $n$ разбивается на две подзадачи размера $n-1$ и одну подзадачу размера $1$ . После последовательного решения этих трех подзадач исходная задача также оказывается решенной. ================================================ FILE: ru/docs/chapter_dynamic_programming/dp_problem_features.md ================================================ # Свойства задач динамического программирования В предыдущем разделе мы увидели, как динамическое программирование решает исходную задачу через разложение на подзадачи. На самом деле разложение на подзадачи - это общий алгоритмический подход, но в методе "разделяй и властвуй", динамическом программировании и поиске с возвратом акценты расставлены по-разному. - Алгоритмы "разделяй и властвуй" рекурсивно раскладывают исходную задачу на несколько независимых подзадач, пока не будет достигнута наименьшая подзадача, а затем в процессе возврата объединяют решения подзадач в решение исходной задачи. - Динамическое программирование тоже раскладывает задачу рекурсивно, но его главное отличие от метода "разделяй и властвуй" в том, что подзадачи здесь зависят друг от друга и в процессе разложения возникает много перекрывающихся подзадач. - Алгоритм поиска с возвратом перебирает все возможные решения через попытки и откат и с помощью обрезки избегает ненужных ветвей поиска. Решение исходной задачи состоит из последовательности решений, и подзадачей можно считать префикс этой последовательности решений. На практике динамическое программирование часто применяется для задач оптимизации. Такие задачи не только содержат перекрывающиеся подзадачи, но и обладают еще двумя важными свойствами: оптимальной подструктурой и отсутствием последствий. ## Оптимальная подструктура Немного изменим задачу о подъеме по лестнице, чтобы нагляднее показать понятие оптимальной подструктуры. !!! question "Минимальная стоимость подъема по лестнице" Дана лестница, по которой можно подниматься на $1$ или на $2$ ступени за раз. На каждой ступени указано неотрицательное целое число, обозначающее цену попадания на эту ступень. Дан массив неотрицательных целых чисел $cost$ , где $cost[i]$ - это цена для ступени $i$ , а $cost[0]$ соответствует земле (начальной позиции). Найдите минимальную суммарную стоимость, необходимую для достижения вершины. Как показано на рисунке ниже, если цены для ступеней $1$ , $2$ и $3$ равны соответственно $1$ , $10$ и $1$ , то минимальная стоимость подъема с земли на третью ступень равна $2$ . ![Минимальная стоимость подъема на 3-ю ступень](dp_problem_features.assets/min_cost_cs_example.png) Пусть $dp[i]$ обозначает накопленную стоимость подъема на ступень $i$ . Поскольку на ступень $i$ можно прийти только со ступени $i - 1$ или со ступени $i - 2$ , значение $dp[i]$ может быть либо $dp[i - 1] + cost[i]$ , либо $dp[i - 2] + cost[i]$ . Чтобы минимизировать стоимость, нужно выбрать меньший из этих двух вариантов: $$ dp[i] = \min(dp[i-1], dp[i-2]) + cost[i] $$ Отсюда и возникает смысл оптимальной подструктуры: **оптимальное решение исходной задачи строится из оптимальных решений подзадач**. Очевидно, что эта задача обладает оптимальной подструктурой: мы берем лучшее из двух оптимальных решений подзадач $dp[i-1]$ и $dp[i-2]$ и на его основе строим оптимальное решение исходной задачи $dp[i]$ . А обладает ли оптимальной подструктурой исходная задача о числе способов подъема по лестнице из прошлого раздела? Формально она не про оптимум, а про подсчет количества. Но если переформулировать ее как "найдите максимальное количество способов", мы неожиданно увидим, что **хотя исходная задача осталась по сути той же, оптимальная подструктура стала явной**: максимальное число способов добраться до ступени $n$ равно сумме максимальных чисел способов добраться до ступеней $n-1$ и $n-2$ . То есть объяснение оптимальной подструктуры в разных задачах может быть довольно гибким. Зная уравнение перехода состояния, а также начальные состояния $dp[1] = cost[1]$ и $dp[2] = cost[2]$ , мы можем сразу написать код динамического программирования: ```src [file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp} ``` На рисунке ниже показан процесс динамического программирования для этой задачи. ![Процесс динамического программирования для минимальной стоимости подъема](dp_problem_features.assets/min_cost_cs_dp.png) В этой задаче тоже можно оптимизировать пространство, сжав одномерное состояние в нулевое измерение и тем самым уменьшив пространственную сложность с $O(n)$ до $O(1)$ : ```src [file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp_comp} ``` ## Отсутствие последствий Отсутствие последствий - одно из ключевых свойств, благодаря которому динамическое программирование вообще может эффективно работать. Его определение таково: **если текущее состояние задано однозначно, то его дальнейшее развитие зависит только от него самого и не зависит от всей истории предыдущих состояний**. Для примера снова рассмотрим задачу о лестнице. Если дано состояние $i$ , то из него можно перейти в состояния $i+1$ и $i+2$ , соответствующие прыжкам на $1$ и на $2$ ступени. Чтобы сделать один из этих выборов, не нужно знать, какими были состояния до $i$ ; на будущее влияет только текущее состояние $i$ . Однако если добавить в задачу дополнительное ограничение, ситуация изменится. !!! question "Подъем по лестнице с ограничением" Дана лестница из $n$ ступеней. За один шаг можно подняться на $1$ или на $2$ ступени, **но нельзя два раунда подряд прыгать на $1$ ступень**. Сколькими способами можно добраться до вершины? Как показано на рисунке ниже, на третью ступень теперь существует только $2$ допустимых способа добраться: вариант с тремя последовательными прыжками на $1$ не удовлетворяет ограничению и потому отбрасывается. ![Число способов подняться на 3-ю ступень при наличии ограничения](dp_problem_features.assets/climbing_stairs_constraint_example.png) В этой задаче, если в предыдущем раунде был сделан прыжок на $1$ ступень, то в следующем раунде уже обязательно нужно прыгнуть на $2$ ступени. Иными словами, **следующий выбор уже нельзя определить только по текущему состоянию (текущему номеру ступени) - он зависит еще и от предыдущего состояния (с какой ступени мы пришли в прошлый раз)**. Нетрудно заметить, что в таком виде задача больше не удовлетворяет свойству отсутствия последствий, а уравнение перехода состояния $dp[i] = dp[i-1] + dp[i-2]$ перестает работать, потому что $dp[i-1]$ соответствует прыжку на $1$ ступень, но при этом включает множество вариантов, где предыдущий раунд тоже был прыжком на $1$ ступень. Такие варианты уже нельзя напрямую учитывать в $dp[i]$ , если мы хотим соблюдать ограничение. Поэтому нам нужно расширить определение состояния: **состояние $[i, j]$ означает, что мы находимся на ступени $i$ и в предыдущем раунде прыгнули на $j$ ступеней**, где $j \in \{1, 2\}$ . Такое определение состояния эффективно различает, был ли в прошлом раунде прыжок на $1$ или на $2$ ступени, и позволяет корректно определить, откуда произошло текущее состояние. - Если в предыдущем раунде был прыжок на $1$ ступень, то в раунде перед ним мог быть только прыжок на $2$ ступени, то есть $dp[i, 1]$ может перейти только из $dp[i-1, 2]$ . - Если в предыдущем раунде был прыжок на $2$ ступени, то еще шагом раньше можно было прыгнуть либо на $1$ , либо на $2$ ступени, то есть $dp[i, 2]$ может переходить из $dp[i-2, 1]$ или из $dp[i-2, 2]$ . Как показано на рисунке ниже, при таком определении $dp[i, j]$ обозначает число способов для состояния $[i, j]$ . Тогда уравнение перехода состояния имеет вид: $$ \begin{cases} dp[i, 1] = dp[i-1, 2] \\ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] \end{cases} $$ ![Рекуррентная связь с учетом ограничения](dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png) В конце достаточно вернуть $dp[n, 1] + dp[n, 2]$ ; эта сумма и представляет общее число способов добраться до ступени $n$ : ```src [file]{climbing_stairs_constraint_dp}-[class]{}-[func]{climbing_stairs_constraint_dp} ``` В этом примере достаточно дополнительно учитывать только одно предыдущее состояние, поэтому после расширения определения состояния задача снова начинает удовлетворять свойству отсутствия последствий. Однако в некоторых задачах "зависимость от прошлого" бывает гораздо серьезнее. !!! question "Подъем по лестнице с порождением препятствий" Дана лестница из $n$ ступеней. За один шаг можно подняться на $1$ или на $2$ ступени. **При этом, если вы попали на ступень $i$ , система автоматически создает препятствие на ступени $2i$ , и на всех последующих шагах становиться на ступень $2i$ уже нельзя**. Например, если в первых двух раундах вы попали на ступени $2$ и $3$ , то после этого нельзя будет попадать на ступени $4$ и $6$ . Сколько существует способов добраться до вершины? В этой задаче следующий прыжок зависит от всех предыдущих состояний, потому что каждый прыжок порождает новое препятствие на более высокой ступени и тем самым влияет на все будущие прыжки. Для задач такого типа динамическое программирование обычно оказывается непригодным. Вообще, многие сложные задачи комбинаторной оптимизации (например, задача коммивояжера) не обладают свойством отсутствия последствий. Для таких задач обычно выбирают другие методы - например, эвристический поиск, генетические алгоритмы, обучение с подкреплением и т.д., - чтобы за ограниченное время получить пригодное локально оптимальное решение. ================================================ FILE: ru/docs/chapter_dynamic_programming/dp_solution_pipeline.md ================================================ # Подход к решению задач динамического программирования В двух предыдущих разделах были рассмотрены основные свойства задач динамического программирования. Теперь исследуем два более практических вопроса. 1. Как определить, является ли некоторая задача задачей динамического программирования? 2. С чего начинать решение такой задачи и как выглядит полный процесс решения? ## Определение задачи В целом, если задача содержит перекрывающиеся подзадачи, оптимальную подструктуру и удовлетворяет свойству отсутствия последствий, то она обычно подходит для решения с помощью динамического программирования. Однако извлечь все эти свойства напрямую из формулировки задачи бывает трудно. Поэтому на практике мы обычно ослабляем требования и **сначала смотрим, подходит ли задача для решения методом поиска с возвратом (полного перебора)**. **Задачи, подходящие для поиска с возвратом, обычно удовлетворяют "модели дерева решений"**. Такие задачи можно описать деревом, где каждый узел представляет одно решение, а каждый путь представляет последовательность решений. Иначе говоря, если в задаче есть четко выраженные решения и ответ порождается последовательностью таких решений, то она удовлетворяет модели дерева решений и обычно допускает решение через поиск с возвратом. Поверх этого у задач динамического программирования есть и некоторые дополнительные "плюсы". - В условии задачи фигурируют слова "максимальный", "минимальный", "наибольший", "наименьший" и другие формулировки оптимизации. - Состояния задачи можно описать списком, многомерной матрицей или деревом, и между состоянием и соседними состояниями существует рекуррентная зависимость. Соответственно, существуют и некоторые "минусы". - Цель задачи состоит в поиске всех возможных решений, а не одного оптимального решения. - В формулировке явно присутствуют признаки комбинаторного перечисления, и требуется вернуть сразу много конкретных вариантов. Если задача удовлетворяет модели дерева решений и имеет достаточно явные "плюсы", мы можем предположить, что это задача динамического программирования, а затем проверить это предположение уже в процессе решения. ## Этапы решения задачи Конкретный процесс решения задач динамического программирования зависит от природы и сложности задачи, но обычно включает следующие шаги: описание решений, определение состояний, построение таблицы $dp$ , вывод уравнения перехода состояния, определение граничных условий и порядка переходов. Чтобы нагляднее показать этот процесс, рассмотрим классическую задачу "минимальная сумма пути". !!! question Дана двумерная сетка `grid` размера $n \times m$ , в каждой клетке которой записано неотрицательное целое число, означающее стоимость прохождения через эту клетку. Робот стартует из левой верхней клетки и за один шаг может двигаться только вправо или вниз, пока не достигнет правой нижней клетки. Верните минимальную сумму пути от левой верхней клетки до правой нижней. На рисунке ниже показан пример, в котором минимальная сумма пути равна $13$ . ![Пример данных для задачи о минимальной сумме пути](dp_solution_pipeline.assets/min_path_sum_example.png) **Шаг 1: понять решения на каждом раунде, определить состояние и тем самым получить таблицу $dp$** В этой задаче на каждом раунде решение состоит в том, чтобы из текущей клетки сделать один шаг вниз или вправо. Пусть индексы строки и столбца текущей клетки равны $[i, j]$ ; тогда после шага вниз или вправо индексы становятся равными $[i+1, j]$ или $[i, j+1]$ . Значит, состояние должно включать два переменных индекса: строки и столбца, то есть состояние обозначается как $[i, j]$ . Подзадача, соответствующая состоянию $[i, j]$ , такова: минимальная сумма пути от стартовой клетки $[0, 0]$ до клетки $[i, j]$ . Ее решение обозначается через $dp[i, j]$ . На этом этапе мы получаем двумерную матрицу $dp$ , показанную на рисунке ниже, размер которой совпадает с размером входной сетки `grid` . ![Определение состояния и таблицы dp](dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png) !!! note Как в динамическом программировании, так и в поиске с возвратом, решение задачи можно описать как последовательность решений, а состояние образуется всеми переменными решений. Оно должно содержать всю информацию, достаточную для вывода следующего состояния. Каждому состоянию соответствует некоторая подзадача, и для хранения решений всех подзадач мы определяем таблицу $dp$ ; каждая независимая переменная состояния становится одним измерением таблицы $dp$ . По сути таблица $dp$ - это отображение от состояния к решению соответствующей подзадачи. **Шаг 2: найти оптимальную подструктуру и на ее основе вывести уравнение перехода состояния** Для состояния $[i, j]$ возможны только два источника: клетка сверху $[i-1, j]$ и клетка слева $[i, j-1]$ . Следовательно, оптимальная подструктура выглядит так: минимальная сумма пути до $[i, j]$ определяется меньшим из двух значений - минимальной суммы пути до $[i-1, j]$ и минимальной суммы пути до $[i, j-1]$ . По этому рассуждению получается уравнение перехода состояния, показанное на рисунке ниже: $$ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j] $$ ![Оптимальная подструктура и уравнение перехода состояния](dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png) !!! note Опираясь на уже определенную таблицу $dp$ , нужно продумать отношение между исходной задачей и подзадачами и найти способ построить оптимальное решение исходной задачи из оптимальных решений подзадач, то есть найти оптимальную подструктуру. Как только оптимальная подструктура найдена, на ее основе можно построить уравнение перехода состояния. **Шаг 3: определить граничные условия и порядок переходов** В этой задаче состояния в первой строке могут переходить только из клетки слева, а состояния в первом столбце - только из клетки сверху, поэтому первая строка $i = 0$ и первый столбец $j = 0$ образуют граничные условия. Как показано на рисунке ниже, поскольку каждая клетка получается из клетки слева и клетки сверху, мы можем проходить матрицу циклами: внешний цикл по строкам, внутренний - по столбцам. ![Граничные условия и порядок перехода состояний](dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png) !!! note В динамическом программировании граничные условия используются для инициализации таблицы $dp$ , а в поиске - для обрезки. Смысл порядка перехода состояния в том, чтобы к моменту вычисления текущей подзадачи все более мелкие подзадачи, от которых она зависит, уже были вычислены корректно. После этого анализа мы уже можем напрямую написать код динамического программирования. Однако разложение на подзадачи - это мышление "сверху вниз", поэтому с точки зрения мышления более естественно реализовывать задачу в порядке "полный перебор $\rightarrow$ поиск с мемоизацией $\rightarrow$ динамическое программирование". ### Метод 1: полный перебор Начав со состояния $[i, j]$ , мы непрерывно раскладываем его на меньшие состояния $[i-1, j]$ и $[i, j-1]$ . Рекурсивная функция при этом имеет следующие элементы. - **Параметры рекурсии**: состояние $[i, j]$ . - **Возвращаемое значение**: минимальная сумма пути до $[i, j]$ , то есть $dp[i, j]$ . - **Условие завершения**: когда $i = 0$ и $j = 0$ , возвращается стоимость $grid[0, 0]$ . - **Обрезка**: если $i < 0$ или $j < 0$ , индекс выходит за границы, и в этом случае возвращается стоимость $+\infty$ , обозначающая невозможность. Код реализации: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs} ``` На рисунке ниже показано дерево рекурсии с корнем в $dp[2, 1]$ ; в нем содержатся перекрывающиеся подзадачи, и их число будет резко расти вместе с размером сетки `grid` . По своей сути причина появления перекрывающихся подзадач такова: **существует много разных путей от левого верхнего угла до одной и той же клетки**. ![Дерево рекурсии полного перебора](dp_solution_pipeline.assets/min_path_sum_dfs.png) У каждого состояния есть два выбора - вниз и вправо, а от левого верхнего угла до правого нижнего нужно сделать всего $m + n - 2$ шагов, поэтому худшая временная сложность равна $O(2^{m + n})$ , где $n$ и $m$ - число строк и столбцов сетки соответственно. Заметим, что в этой оценке не учитывается близость к границам сетки: у граничных клеток остается только один выбор, так что фактическое число путей будет несколько меньше. ### Метод 2: поиск с мемоизацией Введем список памяти `mem` того же размера, что и сетка `grid` , для хранения решений всех подзадач и отсечения перекрывающихся подзадач: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs_mem} ``` Как показано на рисунке ниже, после добавления мемоизации решение каждой подзадачи вычисляется только один раз, поэтому временная сложность определяется общим числом состояний, то есть равна $O(nm)$ . ![Дерево рекурсии для поиска с мемоизацией](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png) ### Метод 3: динамическое программирование Итеративная реализация динамического программирования выглядит так: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp} ``` На рисунке ниже показан процесс переходов состояний в задаче о минимальной сумме пути. Он проходит по всей сетке, **поэтому временная сложность равна $O(nm)$** . Размер массива `dp` равен $n \times m$ , **поэтому пространственная сложность равна $O(nm)$** . === "<1>" ![Процесс динамического программирования для минимальной суммы пути](dp_solution_pipeline.assets/min_path_sum_dp_step1.png) === "<2>" ![min_path_sum_dp_step2](dp_solution_pipeline.assets/min_path_sum_dp_step2.png) === "<3>" ![min_path_sum_dp_step3](dp_solution_pipeline.assets/min_path_sum_dp_step3.png) === "<4>" ![min_path_sum_dp_step4](dp_solution_pipeline.assets/min_path_sum_dp_step4.png) === "<5>" ![min_path_sum_dp_step5](dp_solution_pipeline.assets/min_path_sum_dp_step5.png) === "<6>" ![min_path_sum_dp_step6](dp_solution_pipeline.assets/min_path_sum_dp_step6.png) === "<7>" ![min_path_sum_dp_step7](dp_solution_pipeline.assets/min_path_sum_dp_step7.png) === "<8>" ![min_path_sum_dp_step8](dp_solution_pipeline.assets/min_path_sum_dp_step8.png) === "<9>" ![min_path_sum_dp_step9](dp_solution_pipeline.assets/min_path_sum_dp_step9.png) === "<10>" ![min_path_sum_dp_step10](dp_solution_pipeline.assets/min_path_sum_dp_step10.png) === "<11>" ![min_path_sum_dp_step11](dp_solution_pipeline.assets/min_path_sum_dp_step11.png) === "<12>" ![min_path_sum_dp_step12](dp_solution_pipeline.assets/min_path_sum_dp_step12.png) ### Оптимизация пространства Поскольку каждая клетка зависит только от клетки слева и клетки сверху, таблицу $dp$ можно реализовать с помощью одномерного массива, соответствующего одной строке. Обратите внимание: поскольку массив `dp` теперь может представлять только одну строку состояний, мы не можем заранее инициализировать состояния первого столбца, а должны обновлять их по мере обхода каждой строки: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp_comp} ``` ================================================ FILE: ru/docs/chapter_dynamic_programming/edit_distance_problem.md ================================================ # Задача о расстоянии редактирования Расстояние редактирования, также называемое расстоянием Левенштейна, - это минимальное количество изменений, необходимых для преобразования одной строки в другую. Обычно оно используется для измерения сходства двух последовательностей в информационном поиске и обработке естественного языка. !!! question Даны две строки $s$ и $t$ . Верните минимальное число шагов редактирования, необходимое для преобразования $s$ в $t$ . Для строки допускаются три операции редактирования: вставка одного символа, удаление одного символа и замена одного символа на произвольный другой символ. Как показано на рисунке ниже, для преобразования `kitten` в `sitting` требуется 3 шага редактирования: 2 операции замены и 1 операция вставки; для преобразования `hello` в `algo` также требуется 3 шага: 2 замены и 1 удаление. ![Пример данных для задачи о расстоянии редактирования](edit_distance_problem.assets/edit_distance_example.png) **Задачу о расстоянии редактирования можно естественным образом объяснить с помощью модели дерева решений**. Строки соответствуют узлам дерева, а один шаг решения, то есть одна операция редактирования, соответствует одному ребру дерева. Как показано на рисунке ниже, если не ограничивать число операций, то каждый узел может порождать множество ребер, и каждое из них соответствует одному из вариантов преобразования. Это означает, что преобразовать `hello` в `algo` можно множеством разных путей. С точки зрения дерева решений цель этой задачи - найти кратчайший путь между узлом `hello` и узлом `algo` . ![Представление задачи о расстоянии редактирования через дерево решений](edit_distance_problem.assets/edit_distance_decision_tree.png) ### Идея динамического программирования **Шаг 1: продумать решения на каждом раунде, определить состояние и тем самым получить таблицу $dp$** На каждом раунде решение состоит в выполнении одной операции редактирования над строкой $s$ . Нам нужно, чтобы в ходе выполнения операций размер задачи постепенно уменьшался; только тогда можно строить подзадачи. Пусть длины строк $s$ и $t$ равны соответственно $n$ и $m$ ; сначала рассмотрим последние символы этих строк, то есть $s[n-1]$ и $t[m-1]$ . - Если $s[n-1]$ и $t[m-1]$ совпадают, их можно просто пропустить и сразу перейти к сравнению $s[n-2]$ и $t[m-2]$ . - Если $s[n-1]$ и $t[m-1]$ различны, нужно выполнить над $s$ одну операцию редактирования (вставку, удаление или замену), чтобы последние символы стали одинаковыми, после чего можно перейти к задаче меньшего размера. Иначе говоря, каждый шаг решения, то есть операция редактирования над строкой $s$ , меняет те символы, которые еще необходимо сопоставить в строках $s$ и $t$ . Поэтому состояние определяется текущими позициями рассматриваемых символов в $s$ и $t$ , то есть состоянием $[i, j]$ . Подзадача, соответствующая состоянию $[i, j]$ , такова: **минимальное число операций редактирования, необходимое для преобразования первых $i$ символов строки $s$ в первые $j$ символов строки $t$**. Отсюда получается двумерная таблица $dp$ размера $(i+1) \times (j+1)$ . **Шаг 2: найти оптимальную подструктуру и на ее основе вывести уравнение перехода состояния** Рассмотрим подзадачу $dp[i, j]$ . Ее последние символы - это $s[i-1]$ и $t[j-1]$ . В зависимости от операции редактирования возможны три случая, показанные на рисунке ниже. 1. Вставить после $s[i-1]$ символ $t[j-1]$ ; тогда остается подзадача $dp[i, j-1]$ . 2. Удалить $s[i-1]$ ; тогда остается подзадача $dp[i-1, j]$ . 3. Заменить $s[i-1]$ на $t[j-1]$ ; тогда остается подзадача $dp[i-1, j-1]$ . ![Переходы состояния в задаче о расстоянии редактирования](edit_distance_problem.assets/edit_distance_state_transfer.png) Согласно этому анализу оптимальная подструктура такова: минимальное число шагов редактирования для $dp[i, j]$ равно минимуму из трех значений - $dp[i, j-1]$ , $dp[i-1, j]$ и $dp[i-1, j-1]$ - плюс $1$ шаг за текущее редактирование. Значит, уравнение перехода состояния имеет вид: $$ dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 $$ Заметим, что **если символы $s[i-1]$ и $t[j-1]$ совпадают, то редактировать текущий символ не нужно**. В этом случае уравнение перехода состояния имеет вид: $$ dp[i, j] = dp[i-1, j-1] $$ **Шаг 3: определить граничные условия и порядок переходов** Когда обе строки пусты, число операций редактирования равно $0$ , то есть $dp[0, 0] = 0$ . Когда строка $s$ пуста, а строка $t$ непуста, минимальное число операций равно длине строки $t$ , то есть вся первая строка инициализируется как $dp[0, j] = j$ . Когда строка $s$ непуста, а строка $t$ пуста, минимальное число операций равно длине строки $s$ , то есть весь первый столбец инициализируется как $dp[i, 0] = i$ . Из уравнения перехода видно, что решение $dp[i, j]$ зависит от значений слева, сверху и слева сверху, поэтому всю таблицу $dp$ можно обходить двумя вложенными циклами в прямом порядке. ### Реализация кода ```src [file]{edit_distance}-[class]{}-[func]{edit_distance_dp} ``` Как показано на рисунке ниже, процесс переходов состояния в задаче о расстоянии редактирования очень похож на задачи о рюкзаке: и там и здесь его можно рассматривать как заполнение двумерной сетки. === "<1>" ![Процесс динамического программирования для расстояния редактирования](edit_distance_problem.assets/edit_distance_dp_step1.png) === "<2>" ![edit_distance_dp_step2](edit_distance_problem.assets/edit_distance_dp_step2.png) === "<3>" ![edit_distance_dp_step3](edit_distance_problem.assets/edit_distance_dp_step3.png) === "<4>" ![edit_distance_dp_step4](edit_distance_problem.assets/edit_distance_dp_step4.png) === "<5>" ![edit_distance_dp_step5](edit_distance_problem.assets/edit_distance_dp_step5.png) === "<6>" ![edit_distance_dp_step6](edit_distance_problem.assets/edit_distance_dp_step6.png) === "<7>" ![edit_distance_dp_step7](edit_distance_problem.assets/edit_distance_dp_step7.png) === "<8>" ![edit_distance_dp_step8](edit_distance_problem.assets/edit_distance_dp_step8.png) === "<9>" ![edit_distance_dp_step9](edit_distance_problem.assets/edit_distance_dp_step9.png) === "<10>" ![edit_distance_dp_step10](edit_distance_problem.assets/edit_distance_dp_step10.png) === "<11>" ![edit_distance_dp_step11](edit_distance_problem.assets/edit_distance_dp_step11.png) === "<12>" ![edit_distance_dp_step12](edit_distance_problem.assets/edit_distance_dp_step12.png) === "<13>" ![edit_distance_dp_step13](edit_distance_problem.assets/edit_distance_dp_step13.png) === "<14>" ![edit_distance_dp_step14](edit_distance_problem.assets/edit_distance_dp_step14.png) === "<15>" ![edit_distance_dp_step15](edit_distance_problem.assets/edit_distance_dp_step15.png) ### Оптимизация пространства Поскольку $dp[i,j]$ зависит от значения сверху $dp[i-1, j]$ , слева $dp[i, j-1]$ и слева сверху $dp[i-1, j-1]$ , прямой обход после оптимизации памяти теряет значение слева сверху, а обратный обход не позволяет заранее построить значение слева $dp[i, j-1]$ . Значит, оба наивных варианта обхода здесь непригодны. Чтобы решить эту проблему, можно использовать переменную `leftup` для временного сохранения значения слева сверху $dp[i-1, j-1]$ ; после этого остается учитывать только верхнее и левое значения. Тогда ситуация становится аналогичной задаче о полном рюкзаке, и можно выполнять прямой обход. Код приведен ниже: ```src [file]{edit_distance}-[class]{}-[func]{edit_distance_dp_comp} ``` ================================================ FILE: ru/docs/chapter_dynamic_programming/index.md ================================================ # Динамическое программирование ![Динамическое программирование](../assets/covers/chapter_dynamic_programming.jpg) !!! abstract Ручьи впадают в реки, а реки вливаются в море. Динамическое программирование собирает решения малых задач в ответ на большую задачу и шаг за шагом ведет нас к ее решению. ================================================ FILE: ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md ================================================ # Первое знакомство с динамическим программированием Динамическое программирование (dynamic programming) - это важная алгоритмическая парадигма, которая разбивает задачу на последовательность более мелких подзадач и за счет хранения их решений избегает повторных вычислений, тем самым резко повышая эффективность по времени. В этом разделе мы начнем с классического примера: сначала представим его грубое решение методом поиска с возвратом, увидим в нем перекрывающиеся подзадачи, а затем постепенно выведем более эффективное решение на основе динамического программирования. !!! question "Подъем по лестнице" Дана лестница из $n$ ступеней. За один шаг можно подняться на $1$ или на $2$ ступени. Сколькими способами можно добраться до вершины? Как показано на рисунке ниже, для лестницы из $3$ ступеней существует $3$ способа добраться до вершины. ![Число способов подняться на 3-ю ступень](intro_to_dynamic_programming.assets/climbing_stairs_example.png) Цель этой задачи - вычислить количество способов. **Поэтому можно попробовать использовать для ее решения метод поиска с возвратом**. Если представить подъем по лестнице как последовательность решений, то мы начинаем от земли и на каждом раунде выбираем прыжок на $1$ или на $2$ ступени; всякий раз, когда достигаем вершины, увеличиваем число способов на $1$ , а если перескакиваем вершину, обрезаем эту ветвь. Код выглядит так: ```src [file]{climbing_stairs_backtrack}-[class]{}-[func]{climbing_stairs_backtrack} ``` ## Метод 1: полный перебор Алгоритм поиска с возвратом обычно не раскладывает задачу явно на подзадачи; вместо этого он рассматривает решение как последовательность решений, используя попытки и обрезку для поиска всех возможных ответов. Попробуем посмотреть на задачу именно как на разложение подзадач. Пусть число способов добраться до ступени $i$ равно $dp[i]$ ; тогда $dp[i]$ - это исходная задача, а ее подзадачи включают: $$ dp[i-1], dp[i-2], \dots, dp[2], dp[1] $$ Поскольку за один раунд можно подняться только на $1$ или на $2$ ступени, стоя на ступени $i$ , в предыдущий раунд мы могли находиться только на ступени $i - 1$ или на ступени $i - 2$ . Иначе говоря, на ступень $i$ можно попасть только со ступени $i -1$ или со ступени $i - 2$ . Отсюда получается важный вывод: **число способов добраться до ступени $i - 1$ плюс число способов добраться до ступени $i - 2$ равно числу способов добраться до ступени $i$**. Формула имеет вид: $$ dp[i] = dp[i-1] + dp[i-2] $$ Это означает, что в задаче о подъеме по лестнице между подзадачами существует рекуррентная зависимость, и **решение исходной задачи может быть построено на основе решений подзадач**. Эта связь показана на рисунке ниже. ![Рекуррентная связь числа способов](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png) По рекуррентной формуле можно получить решение полного перебора. Начиная с $dp[n]$ , **мы рекурсивно разлагаем большую задачу в сумму двух меньших задач** , пока не дойдем до наименьших подзадач $dp[1]$ и $dp[2]$ . Их решения уже известны: $dp[1] = 1$ и $dp[2] = 2$ , что означает $1$ и $2$ способа подняться соответственно на $1$-ю и $2$-ю ступени. Посмотрите на следующий код: как и стандартный поиск с возвратом, он относится к поиску в глубину, но выглядит более компактно: ```src [file]{climbing_stairs_dfs}-[class]{}-[func]{climbing_stairs_dfs} ``` На рисунке ниже показано дерево рекурсии, возникающее при полном переборе. Для задачи $dp[n]$ глубина дерева рекурсии равна $n$ , а временная сложность равна $O(2^n)$ . Экспоненциальный рост взрывообразен: если подать на вход достаточно большое значение $n$ , ожидание станет очень долгим. ![Дерево рекурсии для подъема по лестнице](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png) Если посмотреть на рисунок выше, то видно, что **экспоненциальная временная сложность порождается "перекрывающимися подзадачами"**. Например, $dp[9]$ раскладывается в $dp[8]$ и $dp[7]$ , а $dp[8]$ - в $dp[7]$ и $dp[6]$ ; обе ветви содержат подзадачу $dp[7]$ . Продолжая это рассуждение, мы видим, что подзадачи порождают все более мелкие перекрывающиеся подзадачи без конца. Подавляющая часть вычислительных ресурсов уходит именно на них. ## Метод 2: поиск с мемоизацией Чтобы ускорить алгоритм, **мы хотим, чтобы каждая перекрывающаяся подзадача вычислялась только один раз**. Для этого объявим массив `mem` для хранения решения каждой подзадачи и будем обрезать повторные вычисления в процессе поиска. 1. Когда $dp[i]$ вычисляется впервые, мы сохраняем его в `mem[i]` для последующего использования. 2. Когда значение $dp[i]$ требуется снова, мы просто берем его напрямую из `mem[i]` и тем самым избегаем повторного вычисления подзадачи. Код приведен ниже: ```src [file]{climbing_stairs_dfs_mem}-[class]{}-[func]{climbing_stairs_dfs_mem} ``` Как показано на рисунке ниже, **после введения мемоизации каждая перекрывающаяся подзадача вычисляется только один раз, и временная сложность оптимизируется до $O(n)$** . Это огромный скачок в эффективности. ![Дерево рекурсии для поиска с мемоизацией](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png) ## Метод 3: динамическое программирование **Поиск с мемоизацией - это метод "сверху вниз"** : мы начинаем с исходной задачи (корня), рекурсивно раскладываем более крупные подзадачи на меньшие, пока не достигнем наименьших подзадач с уже известным ответом (листьев). Затем в процессе возврата постепенно собираем решения подзадач и тем самым получаем решение исходной задачи. Напротив, **динамическое программирование - это метод "снизу вверх"** : начиная с решений наименьших подзадач, мы итеративно строим решения для более крупных подзадач, пока не получим ответ на исходную задачу. Поскольку в динамическом программировании нет этапа возврата, для его реализации достаточно обычных циклов, без рекурсии. В приведенном ниже коде мы инициализируем массив `dp` для хранения решений подзадач; он выполняет ту же роль, что и массив `mem` в мемоизированном поиске: ```src [file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp} ``` На рисунке ниже смоделирован процесс выполнения этого кода. ![Процесс динамического программирования для подъема по лестнице](intro_to_dynamic_programming.assets/climbing_stairs_dp.png) Как и в поиске с возвратом, в динамическом программировании используется понятие "состояние" для обозначения некоторого этапа решения задачи; каждое состояние соответствует одной подзадаче и ее локально оптимальному решению. Например, в задаче о лестнице состояние определяется текущим номером ступени $i$ . На основе сказанного можно подвести несколько часто используемых терминов динамического программирования. - Массив `dp` называют таблицей dp, а $dp[i]$ обозначает решение подзадачи, соответствующей состоянию $i$ . - Состояния, соответствующие наименьшим подзадачам (первая и вторая ступени), называют начальными состояниями. - Рекуррентную формулу $dp[i] = dp[i-1] + dp[i-2]$ называют уравнением перехода состояния. ## Оптимизация пространства Внимательный читатель мог заметить, что **поскольку $dp[i]$ зависит только от $dp[i-1]$ и $dp[i-2]$ , нам не нужен весь массив `dp` для хранения ответов всех подзадач** ; достаточно двух переменных, которые будут "перекатываться" вперед. Код имеет вид: ```src [file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp_comp} ``` Из кода видно, что после отказа от массива `dp` пространственная сложность уменьшается с $O(n)$ до $O(1)$ . Во многих задачах динамического программирования текущее состояние зависит лишь от ограниченного числа предыдущих состояний. Тогда можно сохранять только действительно нужные состояния и за счет "уменьшения размерности" экономить память. **Этот прием оптимизации памяти называют "скользящими переменными" или "скользящим массивом"**. ================================================ FILE: ru/docs/chapter_dynamic_programming/knapsack_problem.md ================================================ # Задача о рюкзаке 0-1 Задача о рюкзаке является отличным примером для начала изучения динамического программирования и представляет собой одну из наиболее распространенных форм этой задачи. У нее существует множество вариантов, например задача о рюкзаке 0-1, задача о полном рюкзаке, задача о многократном рюкзаке и т.д. В этом разделе сначала разберем самый распространенный вариант - задачу о рюкзаке 0-1. !!! question Даны $n$ предметов. Вес $i$-го предмета равен $wgt[i-1]$ , стоимость равна $val[i-1]$ . Также дан рюкзак вместимости $cap$ . Каждый предмет можно выбрать только один раз. Найдите максимальную суммарную стоимость, которую можно поместить в рюкзак при заданной вместимости. Как видно на рисунке ниже, поскольку номер предмета $i$ начинается с $1$ , а индексы массива начинаются с $0$ , предмету $i$ соответствуют вес $wgt[i-1]$ и стоимость $val[i-1]$ . ![Пример данных для задачи о рюкзаке 0-1](knapsack_problem.assets/knapsack_example.png) Задачу о рюкзаке 0-1 можно рассматривать как процесс из $n$ раундов принятия решений: для каждого предмета есть два решения - не класть его в рюкзак или положить в рюкзак. Поэтому задача удовлетворяет модели дерева решений. Цель задачи - найти "максимальную суммарную стоимость при ограниченной вместимости рюкзака", а это с большой вероятностью указывает на задачу динамического программирования. **Шаг 1: продумать решения на каждом раунде, определить состояние и тем самым получить таблицу $dp$** Для каждого предмета возможны два случая: не класть его в рюкзак, тогда вместимость не меняется; или положить его в рюкзак, тогда оставшаяся вместимость уменьшается. Отсюда получается определение состояния: текущий номер предмета $i$ и текущая вместимость рюкзака $c$ , то есть состояние обозначается как $[i, c]$ . Подзадача, соответствующая состоянию $[i, c]$ , такова: **максимальная стоимость, которую можно получить, используя первые $i$ предметов и рюкзак вместимости $c$**. Ее решение обозначается через $dp[i, c]$ . Искомым значением является $dp[n, cap]$ , значит, нам нужна двумерная таблица $dp$ размера $(n+1) \times (cap+1)$ . **Шаг 2: найти оптимальную подструктуру и на ее основе вывести уравнение перехода состояния** После того как мы принимаем решение по предмету $i$ , остается подзадача, связанная с первыми $i-1$ предметами. Здесь возможны два случая. - **Не класть предмет $i$** : вместимость рюкзака не меняется, и состояние переходит в $[i-1, c]$ . - **Положить предмет $i$** : вместимость рюкзака уменьшается на $wgt[i-1]$ , а стоимость увеличивается на $val[i-1]$ , и состояние переходит в $[i-1, c-wgt[i-1]]$ . Этот анализ и раскрывает оптимальную подструктуру задачи: **максимальная стоимость $dp[i, c]$ равна лучшему из двух вариантов - не брать предмет $i$ или взять предмет $i$**. Отсюда получается уравнение перехода состояния: $$ dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) $$ Нужно учитывать, что если вес текущего предмета $wgt[i - 1]$ превышает оставшуюся вместимость $c$ , то предмет можно только не брать. **Шаг 3: определить граничные условия и порядок переходов** Когда предметов нет или вместимость рюкзака равна $0$ , максимальная стоимость равна $0$ ; то есть весь первый столбец $dp[i, 0]$ и вся первая строка $dp[0, c]$ заполняются нулями. Текущее состояние $[i, c]$ зависит от состояния сверху $[i-1, c]$ и состояния слева сверху $[i-1, c-wgt[i-1]]$ , поэтому достаточно двумя вложенными циклами пройти по всей таблице $dp$ в прямом порядке. После этого анализа реализуем по порядку: полный перебор, поиск с мемоизацией и динамическое программирование. ### Метод 1: полный перебор Код поиска содержит следующие элементы. - **Параметры рекурсии**: состояние $[i, c]$ . - **Возвращаемое значение**: решение подзадачи $dp[i, c]$ . - **Условие завершения**: когда номер предмета выходит за границу, то есть $i = 0$ , или оставшаяся вместимость равна $0$ , рекурсия завершается и возвращается стоимость $0$ . - **Обрезка**: если вес текущего предмета превышает оставшуюся вместимость рюкзака, то можно только не класть этот предмет. ```src [file]{knapsack}-[class]{}-[func]{knapsack_dfs} ``` Как показано на рисунке ниже, поскольку каждый предмет создает две ветви поиска - "не брать" и "брать", временная сложность равна $O(2^n)$ . Посмотрев на дерево рекурсии, легко заметить наличие перекрывающихся подзадач, например $dp[1, 10]$ и подобных. Когда число предметов растет, вместимость рюкзака велика, а особенно когда много предметов с одинаковым весом, количество перекрывающихся подзадач быстро увеличивается. ![Дерево полного перебора для задачи о рюкзаке 0-1](knapsack_problem.assets/knapsack_dfs.png) ### Метод 2: мемоизация Чтобы каждая перекрывающаяся подзадача вычислялась только один раз, используем таблицу памяти `mem` для хранения решений подзадач, где `mem[i][c]` соответствует $dp[i, c]$ . После введения мемоизации **временная сложность определяется числом подзадач** , то есть равна $O(n \times cap)$ . Код выглядит так: ```src [file]{knapsack}-[class]{}-[func]{knapsack_dfs_mem} ``` На рисунке ниже показаны ветви поиска, которые были отсечены благодаря мемоизации. ![Дерево поиска с мемоизацией для задачи о рюкзаке 0-1](knapsack_problem.assets/knapsack_dfs_mem.png) ### Метод 3: динамическое программирование По своей сути динамическое программирование здесь - это процесс последовательного заполнения таблицы $dp$ в соответствии с переходами состояний. Код приведен ниже: ```src [file]{knapsack}-[class]{}-[func]{knapsack_dp} ``` Как показано на рисунке ниже, и временная сложность, и пространственная сложность определяются размером массива `dp` , то есть равны $O(n \times cap)$ . === "<1>" ![Процесс динамического программирования для задачи о рюкзаке 0-1](knapsack_problem.assets/knapsack_dp_step1.png) === "<2>" ![knapsack_dp_step2](knapsack_problem.assets/knapsack_dp_step2.png) === "<3>" ![knapsack_dp_step3](knapsack_problem.assets/knapsack_dp_step3.png) === "<4>" ![knapsack_dp_step4](knapsack_problem.assets/knapsack_dp_step4.png) === "<5>" ![knapsack_dp_step5](knapsack_problem.assets/knapsack_dp_step5.png) === "<6>" ![knapsack_dp_step6](knapsack_problem.assets/knapsack_dp_step6.png) === "<7>" ![knapsack_dp_step7](knapsack_problem.assets/knapsack_dp_step7.png) === "<8>" ![knapsack_dp_step8](knapsack_problem.assets/knapsack_dp_step8.png) === "<9>" ![knapsack_dp_step9](knapsack_problem.assets/knapsack_dp_step9.png) === "<10>" ![knapsack_dp_step10](knapsack_problem.assets/knapsack_dp_step10.png) === "<11>" ![knapsack_dp_step11](knapsack_problem.assets/knapsack_dp_step11.png) === "<12>" ![knapsack_dp_step12](knapsack_problem.assets/knapsack_dp_step12.png) === "<13>" ![knapsack_dp_step13](knapsack_problem.assets/knapsack_dp_step13.png) === "<14>" ![knapsack_dp_step14](knapsack_problem.assets/knapsack_dp_step14.png) ### Оптимизация пространства Поскольку каждое состояние зависит только от состояния в предыдущей строке, можно использовать два массива, которые будут продвигаться вперед по очереди, и тем самым уменьшить пространственную сложность с $O(n^2)$ до $O(n)$ . Если пойти дальше, можно спросить: можно ли оптимизировать память так, чтобы использовать только один массив? Наблюдение показывает, что каждое состояние зависит от клетки прямо сверху и клетки слева сверху. Предположим, что у нас есть только один массив, и в момент начала обхода строки $i$ он еще хранит состояния строки $i-1$ . - Если обходить массив слева направо, то к моменту вычисления $dp[i, j]$ значения слева сверху $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ могут уже быть перезаписаны, и правильный результат перехода состояния получить не удастся. - Если же обходить массив справа налево, проблема перезаписи не возникает, и переход состояния вычисляется корректно. На рисунке ниже показан процесс перехода от строки $i = 1$ к строке $i = 2$ при использовании одного массива. С его помощью удобно понять различие между прямым и обратным обходом. === "<1>" ![Процесс динамического программирования после оптимизации памяти для рюкзака 0-1](knapsack_problem.assets/knapsack_dp_comp_step1.png) === "<2>" ![knapsack_dp_comp_step2](knapsack_problem.assets/knapsack_dp_comp_step2.png) === "<3>" ![knapsack_dp_comp_step3](knapsack_problem.assets/knapsack_dp_comp_step3.png) === "<4>" ![knapsack_dp_comp_step4](knapsack_problem.assets/knapsack_dp_comp_step4.png) === "<5>" ![knapsack_dp_comp_step5](knapsack_problem.assets/knapsack_dp_comp_step5.png) === "<6>" ![knapsack_dp_comp_step6](knapsack_problem.assets/knapsack_dp_comp_step6.png) В коде для этого достаточно удалить первое измерение массива `dp` , а внутренний цикл заменить на обратный обход: ```src [file]{knapsack}-[class]{}-[func]{knapsack_dp_comp} ``` ================================================ FILE: ru/docs/chapter_dynamic_programming/summary.md ================================================ # Резюме ### Ключевые выводы - Динамическое программирование раскладывает задачу на подзадачи и повышает вычислительную эффективность за счет хранения решений этих подзадач и устранения повторных вычислений. - Если не учитывать затраты времени, то любую задачу динамического программирования можно решить с помощью поиска с возвратом (полного перебора), однако в дереве рекурсии возникает множество перекрывающихся подзадач, из-за чего эффективность крайне низка. После введения таблицы памяти можно хранить решения всех уже вычисленных подзадач и гарантировать, что каждая перекрывающаяся подзадача будет вычисляться только один раз. - Поиск с мемоизацией - это рекурсивный метод "сверху вниз", а соответствующее ему динамическое программирование - это итеративный метод "снизу вверх", похожий на заполнение таблицы. Поскольку текущее состояние обычно зависит только от части локальных состояний, можно убрать одно измерение таблицы $dp$ и тем самым снизить пространственную сложность. - Разложение на подзадачи - это общий алгоритмический подход, но в методе "разделяй и властвуй", динамическом программировании и поиске с возвратом он имеет разные свойства. - Для задач динамического программирования характерны три главных свойства: перекрывающиеся подзадачи, оптимальная подструктура и отсутствие последствий. - Если оптимальное решение исходной задачи можно построить из оптимальных решений подзадач, то задача обладает оптимальной подструктурой. - Отсутствие последствий означает, что для данного состояния его дальнейшее развитие определяется только этим состоянием и не зависит от всех прошлых состояний. Многие задачи комбинаторной оптимизации этим свойством не обладают и потому не могут эффективно решаться с помощью динамического программирования. **Задачи о рюкзаке** - Задача о рюкзаке - один из самых типичных классов задач динамического программирования; она включает варианты 0-1 рюкзака, полного рюкзака, многократного рюкзака и другие. - В задаче о рюкзаке 0-1 состояние определяется как максимальная стоимость первых $i$ предметов в рюкзаке вместимости $c$ . Рассматривая два решения - не брать предмет и брать предмет, - можно получить оптимальную подструктуру и вывести уравнение перехода состояния. При оптимизации памяти, поскольку каждое состояние зависит от значения сверху и слева сверху, внутренний цикл нужно выполнять в обратном порядке, чтобы не перезаписать нужное значение. - В задаче о полном рюкзаке число экземпляров каждого предмета не ограничено, поэтому при выборе предмета переход состояния отличается от варианта 0-1. Поскольку состояние зависит от значения сверху и слева, после оптимизации памяти внутренний цикл следует выполнять в прямом порядке. - Задача о размене монет - это вариант задачи о полном рюкзаке. Здесь вместо "максимальной стоимости" ищется "минимальное число монет", поэтому в уравнении перехода $\max()$ заменяется на $\min()$ . Кроме того, вместо условия "не превышать вместимость рюкзака" нужно **ровно** набрать целевую сумму, поэтому значение $amt + 1$ используется как обозначение недопустимого решения "сумму набрать нельзя". - В задаче о размене монет II вместо "минимального числа монет" требуется найти "число комбинаций монет", поэтому в уравнении перехода оператор $\min()$ заменяется на суммирование. **Задача о расстоянии редактирования** - Расстояние редактирования (расстояние Левенштейна) используется для измерения сходства двух строк и определяется как минимальное число операций редактирования, необходимых для преобразования одной строки в другую; допустимые операции - вставка, удаление и замена. - В задаче о расстоянии редактирования состояние определяется как минимальное число шагов редактирования, необходимых для преобразования первых $i$ символов строки $s$ в первые $j$ символов строки $t$ . Если $s[i] \ne t[j]$ , то существуют три решения: вставка, удаление и замена, и каждому из них соответствует своя остаточная подзадача. На этой основе выводятся оптимальная подструктура и уравнение перехода состояния. Если же $s[i] = t[j]$ , то редактировать текущий символ не нужно. - В задаче о расстоянии редактирования состояние зависит от значений сверху, слева и слева сверху. Поэтому после оптимизации памяти ни прямой, ни обратный обход сам по себе не дает корректного перехода состояния. Для решения этой проблемы значение слева сверху временно сохраняется в отдельной переменной, что делает ситуацию эквивалентной задаче о полном рюкзаке и позволяет использовать прямой обход. ================================================ FILE: ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md ================================================ # Задача о полном рюкзаке В этом разделе сначала решим еще одну распространенную задачу о рюкзаке - задачу о полном рюкзаке, а затем рассмотрим один из ее типичных частных случаев: задачу о размене монет. ## Задача о полном рюкзаке !!! question Даны $n$ предметов. Вес $i$-го предмета равен $wgt[i-1]$ , стоимость равна $val[i-1]$ . Также дан рюкзак вместимости $cap$ . **Каждый предмет можно выбирать многократно**. Найдите максимальную суммарную стоимость, которую можно поместить в рюкзак при заданной вместимости. Пример показан на рисунке ниже. ![Пример данных для задачи о полном рюкзаке](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png) ### Идея динамического программирования Задача о полном рюкзаке очень похожа на задачу о рюкзаке 0-1; **разница состоит только в том, что количество выборов каждого предмета не ограничено**. - В задаче о рюкзаке 0-1 каждого предмета существует только один экземпляр, поэтому после того как предмет $i$ помещен в рюкзак, выбирать можно только из первых $i-1$ предметов. - В задаче о полном рюкзаке количество предметов не ограничено, поэтому после того как предмет $i$ помещен в рюкзак, **можно продолжать выбирать из первых $i$ предметов**. При этом состояние $[i, c]$ в задаче о полном рюкзаке может изменяться двумя способами. - **Не брать предмет $i$** : как и в задаче о рюкзаке 0-1, переход осуществляется в $[i-1, c]$ . - **Взять предмет $i$** : в отличие от рюкзака 0-1 переход происходит в $[i, c-wgt[i-1]]$ . Следовательно, уравнение перехода состояния принимает вид: $$ dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) $$ ### Реализация кода Если сравнить код этой задачи с кодом задачи о рюкзаке 0-1, то окажется, что в переходе состояний меняется только одна деталь: вместо $i-1$ появляется $i$ ; все остальное остается таким же: ```src [file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp} ``` ### Оптимизация пространства Поскольку текущее состояние переходит из состояния слева и состояния сверху, **после оптимизации памяти каждую строку таблицы $dp$ нужно обходить слева направо**. Этот порядок обхода как раз противоположен задаче о рюкзаке 0-1. Разницу удобно понять по рисунку ниже. === "<1>" ![Процесс динамического программирования после оптимизации памяти для полного рюкзака](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png) === "<2>" ![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png) === "<3>" ![unbounded_knapsack_dp_comp_step3](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png) === "<4>" ![unbounded_knapsack_dp_comp_step4](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png) === "<5>" ![unbounded_knapsack_dp_comp_step5](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png) === "<6>" ![unbounded_knapsack_dp_comp_step6](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png) Код реализации здесь довольно прост: достаточно просто убрать первое измерение массива `dp` : ```src [file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp_comp} ``` ## Задача о размене монет Задача о рюкзаке представляет собой целый класс задач динамического программирования, у которого есть множество вариантов, и одной из таких вариаций является задача о размене монет. !!! question Даны $n$ видов монет, номинал монеты $i$ равен $coins[i - 1]$ , а целевая сумма равна $amt$ . **Монеты каждого вида можно брать многократно**. Требуется найти минимальное число монет, которыми можно набрать целевую сумму. Если набрать сумму невозможно, верните $-1$ . Пример показан на рисунке ниже. ![Пример данных для задачи о размене монет](unbounded_knapsack_problem.assets/coin_change_example.png) ### Идея динамического программирования **Задачу о размене монет можно рассматривать как частный случай задачи о полном рюкзаке** ; между ними существуют следующие соответствия и различия. - Эти две задачи можно взаимно преобразовать: "предмет" соответствует "монете", "вес предмета" соответствует "номиналу монеты", а "вместимость рюкзака" соответствует "целевой сумме". - Цель оптимизации противоположна: в задаче о полном рюкзаке нужно максимизировать стоимость предметов, а в задаче о размене монет - минимизировать число монет. - В задаче о полном рюкзаке ищется решение, не превышающее вместимость, а в задаче о размене монет требуется **ровно** набрать целевую сумму. **Шаг 1: продумать решения на каждом раунде, определить состояние и тем самым получить таблицу $dp$** Подзадача, соответствующая состоянию $[i, a]$ , выглядит так: **минимальное число монет из первых $i$ видов, которыми можно набрать сумму $a$**. Решение этой подзадачи обозначается как $dp[i, a]$ . Размер двумерной таблицы $dp$ равен $(n+1) \times (amt+1)$ . **Шаг 2: найти оптимальную подструктуру и на ее основе вывести уравнение перехода состояния** По сравнению с задачей о полном рюкзаке здесь есть два отличия в уравнении перехода состояния. - Нужно искать минимум, а не максимум, поэтому оператор $\max()$ заменяется на $\min()$ . - Оптимизируемое значение - это число монет, а не суммарная стоимость, поэтому при выборе монеты нужно просто прибавить $1$ . $$ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) $$ **Шаг 3: определить граничные условия и порядок переходов** Когда целевая сумма равна $0$ , минимальное число монет для ее набора равно $0$ , то есть весь первый столбец $dp[i, 0]$ заполняется нулями. Когда монет нет, **невозможно набрать никакую целевую сумму $> 0$** ; это и есть недопустимое решение. Чтобы функция $\min()$ в уравнении перехода состояния могла распознавать и отбрасывать такие недопустимые решения, удобно использовать значение $+ \infty$ ; то есть всю первую строку $dp[0, a]$ нужно инициализировать значением $+ \infty$ . ### Реализация кода Большинство языков программирования не предоставляет представление для $+ \infty$ в целочисленном виде, поэтому обычно приходится заменять его на максимальное значение типа `int` . Но тогда возникает риск переполнения: операция $+ 1$ в уравнении перехода может переполнить большое число. Поэтому здесь мы используем число $amt + 1$ как обозначение недопустимого решения, потому что для набора суммы $amt$ максимум нужно не больше чем $amt$ монет. Перед возвратом результата проверяем, равно ли $dp[n, amt]$ значению $amt + 1$ ; если да, то возвращаем $-1$ , что означает невозможность набрать целевую сумму. Код приведен ниже: ```src [file]{coin_change}-[class]{}-[func]{coin_change_dp} ``` Как показано на рисунке ниже, процесс динамического программирования для задачи о размене монет очень похож на задачу о полном рюкзаке. === "<1>" ![Процесс динамического программирования для задачи о размене монет](unbounded_knapsack_problem.assets/coin_change_dp_step1.png) === "<2>" ![coin_change_dp_step2](unbounded_knapsack_problem.assets/coin_change_dp_step2.png) === "<3>" ![coin_change_dp_step3](unbounded_knapsack_problem.assets/coin_change_dp_step3.png) === "<4>" ![coin_change_dp_step4](unbounded_knapsack_problem.assets/coin_change_dp_step4.png) === "<5>" ![coin_change_dp_step5](unbounded_knapsack_problem.assets/coin_change_dp_step5.png) === "<6>" ![coin_change_dp_step6](unbounded_knapsack_problem.assets/coin_change_dp_step6.png) === "<7>" ![coin_change_dp_step7](unbounded_knapsack_problem.assets/coin_change_dp_step7.png) === "<8>" ![coin_change_dp_step8](unbounded_knapsack_problem.assets/coin_change_dp_step8.png) === "<9>" ![coin_change_dp_step9](unbounded_knapsack_problem.assets/coin_change_dp_step9.png) === "<10>" ![coin_change_dp_step10](unbounded_knapsack_problem.assets/coin_change_dp_step10.png) === "<11>" ![coin_change_dp_step11](unbounded_knapsack_problem.assets/coin_change_dp_step11.png) === "<12>" ![coin_change_dp_step12](unbounded_knapsack_problem.assets/coin_change_dp_step12.png) === "<13>" ![coin_change_dp_step13](unbounded_knapsack_problem.assets/coin_change_dp_step13.png) === "<14>" ![coin_change_dp_step14](unbounded_knapsack_problem.assets/coin_change_dp_step14.png) === "<15>" ![coin_change_dp_step15](unbounded_knapsack_problem.assets/coin_change_dp_step15.png) ### Оптимизация пространства Оптимизация пространства для задачи о размене монет выполняется так же, как и для полного рюкзака: ```src [file]{coin_change}-[class]{}-[func]{coin_change_dp_comp} ``` ## Задача о размене монет II !!! question Даны $n$ видов монет, номинал монеты $i$ равен $coins[i - 1]$ , а целевая сумма равна $amt$ . Монеты каждого вида можно брать многократно. **Найдите число различных комбинаций монет, которыми можно набрать целевую сумму**. Пример показан на рисунке ниже. ![Пример данных для задачи о размене монет II](unbounded_knapsack_problem.assets/coin_change_ii_example.png) ### Идея динамического программирования По сравнению с предыдущей задачей здесь целью является число комбинаций. Поэтому подзадача меняется на следующую: **число комбинаций из первых $i$ видов монет, которыми можно набрать сумму $a$**. При этом таблица $dp$ по-прежнему остается двумерной матрицей размера $(n+1) \times (amt + 1)$ . Число комбинаций для текущего состояния равно сумме числа комбинаций для двух решений: не брать текущую монету и брать текущую монету. Поэтому уравнение перехода состояния принимает вид: $$ dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] $$ Когда целевая сумма равна $0$ , ее можно набрать, не выбирая ни одной монеты, поэтому весь первый столбец $dp[i, 0]$ нужно инициализировать единицами. Когда монет нет, невозможно набрать никакую сумму $>0$ , поэтому вся первая строка $dp[0, a]$ должна быть заполнена нулями. ### Реализация кода ```src [file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp} ``` ### Оптимизация пространства При оптимизации памяти способ остается тем же самым: достаточно убрать измерение, отвечающее за виды монет: ```src [file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp_comp} ``` ================================================ FILE: ru/docs/chapter_graph/graph.md ================================================ # Граф Граф (graph) - это нелинейная структура данных, состоящая из вершин (vertex) и ребер (edge). Граф $G$ можно абстрактно представить как множество вершин $V$ и множество ребер $E$ . Ниже приведен пример графа, содержащего 5 вершин и 7 ребер. $$ \begin{aligned} V & = \{ 1, 2, 3, 4, 5 \} \newline E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline G & = \{ V, E \} \newline \end{aligned} $$ Если рассматривать вершины как узлы, а ребра как ссылки, соединяющие узлы, граф можно считать структурой данных, расширяющей связный список. Как показано на рисунке ниже, **по сравнению с линейными отношениями (связный список) и отношениями разделения (дерево), сетевые отношения (граф) обладают большей свободой** и потому являются более сложными. ![Связь между связным списком, деревом и графом](graph.assets/linkedlist_tree_graph.png) ## Распространенные типы и термины графов В зависимости от наличия направления у ребер графы делятся на неориентированные графы (undirected graph) и ориентированные графы (directed graph) , как показано на рисунке ниже. - В неориентированном графе ребро представляет двустороннюю связь между двумя вершинами, например дружеские отношения в социальных сетях. - В ориентированном графе ребро имеет направление, то есть ребра $A \rightarrow B$ и $A \leftarrow B$ независимы друг от друга, например отношения подписки и подписчиков. ![Ориентированный и неориентированный графы](graph.assets/directed_graph.png) В зависимости от того, связаны ли все вершины между собой, граф делится на связный граф (connected graph) и несвязный граф (disconnected graph) , как показано на рисунке ниже. - В связном графе из любой вершины можно достичь любой другой вершины. - В несвязном графе существует по крайней мере одна вершина, недостижимая из текущей. ![Связный и несвязный графы](graph.assets/connected_graph.png) Мы также можем добавить к ребрам переменную "вес" и получить показанный ниже взвешенный граф (weighted graph). Например, в мобильных играх вроде Honor of Kings система рассчитывает "близость" между игроками по времени совместной игры, и такую сеть близости можно представить взвешенным графом. ![Взвешенный и невзвешенный графы](graph.assets/weighted_graph.png) Со структурой данных "граф" связаны следующие основные термины. - Смежность (adjacency): если между двумя вершинами существует ребро, то такие вершины называются смежными. На рисунке выше с вершиной 1 смежны вершины 2, 3 и 5. - Путь (path): последовательность ребер от вершины A до вершины B называется путем из A в B. На рисунке выше последовательность ребер 1-5-2-4 является одним из путей от вершины 1 к вершине 4. - Степень (degree): количество ребер, принадлежащих вершине. Для ориентированного графа входящая степень (in-degree) показывает, сколько ребер входит в вершину, а исходящая степень (out-degree) показывает, сколько ребер из нее выходит. ## Представление графа Распространенные способы представления графа включают "матрицу смежности" и "список смежности". Ниже для примера рассматривается неориентированный граф. ### Матрица смежности Пусть число вершин графа равно $n$ ; тогда матрица смежности (adjacency matrix) использует матрицу размера $n \times n$ для представления графа, где каждая строка и каждый столбец соответствуют вершине, а элементы матрицы показывают наличие или отсутствие ребра. Как показано на рисунке ниже, обозначим матрицу смежности через $M$ , а список вершин через $V$ ; тогда элемент матрицы $M[i, j] = 1$ означает наличие ребра между вершинами $V[i]$ и $V[j]$ , а элемент $M[i, j] = 0$ означает отсутствие ребра. ![Представление графа матрицей смежности](graph.assets/adjacency_matrix.png) Матрица смежности обладает следующими особенностями. - В простом графе вершина не может соединяться сама с собой, поэтому элементы на главной диагонали матрицы смежности не имеют значения. - Для неориентированного графа ребра в обоих направлениях эквивалентны, поэтому матрица смежности симметрична относительно главной диагонали. - Если заменить в матрице смежности значения $1$ и $0$ на веса, то можно представить взвешенный граф. При представлении графа матрицей смежности можно напрямую обращаться к элементам матрицы и получать сведения о ребрах, поэтому операции добавления, удаления, поиска и изменения обладают высокой эффективностью и выполняются за $O(1)$ . Однако пространственная сложность матрицы составляет $O(n^2)$ , поэтому она требует значительных затрат памяти. ### Список смежности Список смежности (adjacency list) использует $n$ списков для представления графа, где узлы списка обозначают вершины. $i$-й список соответствует вершине $i$ и хранит все смежные с ней вершины, то есть все вершины, соединенные с данной вершиной. На рисунке ниже показан пример графа, представленного списком смежности. ![Представление графа списком смежности](graph.assets/adjacency_list.png) Список смежности хранит только реально существующие ребра, а общее число ребер обычно значительно меньше $n^2$ , поэтому он лучше экономит память. Однако для поиска ребра в списке смежности требуется обходить список, поэтому по времени он уступает матрице смежности. Если посмотреть на рисунок выше, можно заметить, что **структура списка смежности очень похожа на цепную адресацию в хеш-таблицах, поэтому здесь можно использовать похожие методы оптимизации эффективности**. Например, если список слишком длинный, его можно преобразовать в AVL-дерево или красно-черное дерево, чтобы снизить временную сложность с $O(n)$ до $O(\log n)$ ; также список можно преобразовать в хеш-таблицу, чтобы довести временную сложность до $O(1)$ . ## Типичные применения графов Как показано в таблице ниже, многие реальные системы можно моделировать с помощью графов, а соответствующие задачи затем сводить к задачам вычислений на графах.

Таблица   Распространенные графы в реальной жизни

| | Вершина | Ребро | Задача вычислений на графе | | -------- | ------- | -------------------- | -------------------------- | | Социальные сети | Пользователь | Дружеская связь | Рекомендация потенциальных друзей | | Линии метро | Станция | Связь между станциями | Рекомендация кратчайшего маршрута | | Солнечная система | Небесное тело | Гравитационное взаимодействие между телами | Вычисление орбит планет | ================================================ FILE: ru/docs/chapter_graph/graph_operations.md ================================================ # Базовые операции графа Базовые операции графа можно разделить на операции над "ребрами" и операции над "вершинами". При двух способах представления, "матрице смежности" и "списке смежности", реализация этих операций различается. ## Реализация на основе матрицы смежности Пусть дан неориентированный граф с числом вершин $n$ . Тогда способы реализации различных операций показаны на рисунках ниже. - **Добавление или удаление ребра**: достаточно изменить соответствующее ребро в матрице смежности, что требует $O(1)$ времени. Поскольку граф неориентированный, необходимо одновременно обновить ребра в обоих направлениях. - **Добавление вершины**: в конец матрицы смежности добавляется строка и столбец, полностью заполненные нулями; это требует $O(n)$ времени. - **Удаление вершины**: из матрицы смежности удаляется строка и столбец. При удалении первой строки и первого столбца достигается худший случай, когда требуется "сдвинуть влево вверх" $(n-1)^2$ элементов, поэтому используется $O(n^2)$ времени. - **Инициализация**: передаются $n$ вершин, затем инициализируется список вершин `vertices` длины $n$ , что требует $O(n)$ времени; после этого инициализируется матрица смежности `adjMat` размера $n \times n$ , что требует $O(n^2)$ времени. === "<1>" ![Инициализация матрицы смежности, добавление и удаление ребер и вершин](graph_operations.assets/adjacency_matrix_step1_initialization.png) === "<2>" ![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png) === "<3>" ![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png) === "<4>" ![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png) === "<5>" ![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png) Ниже приведен код реализации графа на основе матрицы смежности: ```src [file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{} ``` ## Реализация на основе списка смежности Пусть неориентированный граф содержит в сумме $n$ вершин и $m$ ребер. Тогда различные операции можно реализовать способом, показанным на рисунках ниже. - **Добавление ребра**: достаточно добавить ребро в конец списка, соответствующего вершине; это требует $O(1)$ времени. Поскольку граф неориентированный, необходимо одновременно добавить ребра в обоих направлениях. - **Удаление ребра**: нужно найти и удалить указанное ребро в списке, соответствующем вершине; это требует $O(m)$ времени. В неориентированном графе необходимо удалить ребра в обоих направлениях. - **Добавление вершины**: в список смежности добавляется еще один список, а новая вершина становится его головным узлом; это требует $O(1)$ времени. - **Удаление вершины**: требуется пройти по всему списку смежности и удалить все ребра, содержащие указанную вершину; это требует $O(n + m)$ времени. - **Инициализация**: в списке смежности создаются $n$ вершин и $2m$ ребер; это требует $O(n + m)$ времени. === "<1>" ![Инициализация списка смежности, добавление и удаление ребер и вершин](graph_operations.assets/adjacency_list_step1_initialization.png) === "<2>" ![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png) === "<3>" ![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png) === "<4>" ![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png) === "<5>" ![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png) Ниже приведен код списка смежности. По сравнению с рисунками выше, реальная реализация имеет следующие отличия. - Чтобы упростить добавление и удаление вершин, а также сделать код проще, мы используем список, то есть динамический массив, вместо связного списка. - Для хранения списка смежности используется хеш-таблица, где `key` - это экземпляр вершины, а `value` - список смежных вершин данной вершины. Кроме того, в списке смежности используется класс `Vertex` для представления вершины. Причина в том, что если, как и в матрице смежности, различать вершины по индексам списка, то при удалении вершины с индексом $i$ пришлось бы обходить весь список смежности и уменьшать на $1$ все индексы, большие $i$ , что крайне неэффективно. Если же каждая вершина является уникальным экземпляром `Vertex` , то после удаления одной вершины остальные вершины менять уже не требуется. ```src [file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{} ``` ## Сравнение эффективности Пусть в графе имеется $n$ вершин и $m$ ребер. В таблице ниже сравниваются временная и пространственная эффективность матрицы смежности и списка смежности. Обратите внимание: список смежности (связный список) соответствует реализации из этой статьи, а список смежности (хеш-таблица) означает вариант, в котором все списки заменены хеш-таблицами.

Таблица   Сравнение матрицы смежности и списка смежности

| | Матрица смежности | Список смежности (связный список) | Список смежности (хеш-таблица) | | ------------ | ----------------- | --------------------------------- | ------------------------------ | | Проверка смежности | $O(1)$ | $O(n)$ | $O(1)$ | | Добавление ребра | $O(1)$ | $O(1)$ | $O(1)$ | | Удаление ребра | $O(1)$ | $O(n)$ | $O(1)$ | | Добавление вершины | $O(n)$ | $O(1)$ | $O(1)$ | | Удаление вершины | $O(n^2)$ | $O(n + m)$ | $O(n)$ | | Занимаемая память | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | Если смотреть только на таблицу, может показаться, что список смежности на основе хеш-таблицы является лучшим и по времени, и по памяти. Но на практике операции над ребрами в матрице смежности обычно выполняются быстрее, потому что там нужен лишь один доступ к массиву или одно присваивание. В целом матрица смежности воплощает принцип "обмена пространства на время", а список смежности - принцип "обмена времени на пространство". ================================================ FILE: ru/docs/chapter_graph/graph_traversal.md ================================================ # Обход графа Дерево представляет отношение "один ко многим", тогда как граф обладает большей свободой и может выражать произвольные отношения "многие ко многим". Поэтому дерево можно рассматривать как частный случай графа. Очевидно, что **операции обхода дерева также являются частным случаем операций обхода графа**. И графы, и деревья требуют применения алгоритмов обхода. Способы обхода графа также делятся на два типа: обход в ширину и обход в глубину. ## Обход в ширину **Обход в ширину - это способ обхода от ближнего к дальнему, при котором начиная с некоторого узла сначала посещают ближайшие вершины, а затем слой за слоем расширяются наружу**. Как показано на рисунке ниже, начиная с вершины в левом верхнем углу, мы сначала обходим все смежные вершины этой вершины, затем все смежные вершины следующей вершины и так далее, пока не будут посещены все вершины. ![Обход графа в ширину](graph_traversal.assets/graph_bfs.png) ### Реализация алгоритма BFS обычно реализуется с помощью очереди, код приведен ниже. Очередь обладает свойством "первым пришел - первым вышел", что хорошо соответствует идее BFS "от ближнего к дальнему". 1. Поместить стартовую вершину обхода `startVet` в очередь и запустить цикл. 2. На каждой итерации цикла извлекать вершину из головы очереди и записывать ее посещение, после чего добавлять все смежные вершины этой вершины в хвост очереди. 3. Повторять шаг `2.` до тех пор, пока не будут посещены все вершины. Чтобы предотвратить повторный обход вершин, нам нужно хеш-множество `visited` , в котором записывается, какие вершины уже посещены. !!! tip Хеш-множество можно рассматривать как хеш-таблицу, которая хранит только `key` и не хранит `value` . Оно позволяет выполнять добавление, удаление и проверку наличия `key` за $O(1)$ времени. Благодаря уникальности `key` хеш-множество обычно используется, например, для устранения повторов. ```src [file]{graph_bfs}-[class]{}-[func]{graph_bfs} ``` Код сравнительно абстрактен, поэтому рекомендуется сверяться с рисунками ниже для лучшего понимания. === "<1>" ![Шаги обхода графа в ширину](graph_traversal.assets/graph_bfs_step1.png) === "<2>" ![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png) === "<3>" ![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png) === "<4>" ![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png) === "<5>" ![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png) === "<6>" ![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png) === "<7>" ![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png) === "<8>" ![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png) === "<9>" ![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png) === "<10>" ![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png) === "<11>" ![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png) !!! question "Является ли последовательность обхода в ширину единственной?" Нет. Обход в ширину требует только соблюдения порядка "от ближнего к дальнему", **а порядок обхода нескольких вершин на одинаковом расстоянии может произвольно меняться**. Например, на рисунке выше можно поменять местами порядок посещения вершин $1$ и $3$ , а вершины $2$, $4$, $6$ также можно переставлять произвольно. ### Анализ сложности **Временная сложность**: все вершины по одному разу помещаются в очередь и извлекаются из нее, что требует $O(|V|)$ времени; при обходе смежных вершин, поскольку граф неориентированный, все ребра будут посещены по $2$ раза, что требует $O(2|E|)$ времени; в сумме получается $O(|V| + |E|)$ . **Пространственная сложность**: список `res` , хеш-множество `visited` и очередь `que` в худшем случае могут содержать до $|V|$ вершин, поэтому требуется $O(|V|)$ памяти. ## Обход в глубину **Обход в глубину - это способ обхода, при котором сначала идут до самого конца, а когда дальше идти нельзя, возвращаются назад**. Как показано на рисунке ниже, начиная с вершины в левом верхнем углу, мы выбираем некоторую смежную вершину текущей вершины, идем до упора, затем возвращаемся назад, снова идем до упора и так далее, пока не будут посещены все вершины. ![Обход графа в глубину](graph_traversal.assets/graph_dfs.png) ### Реализация алгоритма Такой алгоритмический шаблон "дойти до конца и вернуться" обычно реализуется через рекурсию. Подобно обходу в ширину, в обходе в глубину мы также используем хеш-множество `visited` для записи уже посещенных вершин и тем самым избегаем повторного посещения. ```src [file]{graph_dfs}-[class]{}-[func]{graph_dfs} ``` Алгоритмический процесс обхода в глубину показан на рисунках ниже. - **Прямая пунктирная линия обозначает нисходящую рекурсию** , то есть запуск нового рекурсивного метода для посещения новой вершины. - **Изогнутая пунктирная линия обозначает восходящую рекурсию** , то есть данный рекурсивный метод завершился и управление вернулось туда, откуда он был вызван. Чтобы лучше понять алгоритм, рекомендуется совместить рисунки ниже с кодом и мысленно проследить весь процесс DFS, включая моменты запуска и возврата каждого рекурсивного вызова. === "<1>" ![Шаги обхода графа в глубину](graph_traversal.assets/graph_dfs_step1.png) === "<2>" ![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png) === "<3>" ![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png) === "<4>" ![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png) === "<5>" ![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png) === "<6>" ![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png) === "<7>" ![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png) === "<8>" ![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png) === "<9>" ![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png) === "<10>" ![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png) === "<11>" ![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png) !!! question "Является ли последовательность обхода в глубину единственной?" Как и в случае обхода в ширину, последовательность DFS тоже не является единственной. Для заданной вершины допустимо сначала углубиться в любое направление, то есть порядок смежных вершин может быть произвольным, и все такие варианты будут корректными обходами в глубину. Если взять в качестве примера обход дерева, то варианты "корень $\rightarrow$ лево $\rightarrow$ право", "лево $\rightarrow$ корень $\rightarrow$ право" и "лево $\rightarrow$ право $\rightarrow$ корень" соответствуют прямому, симметричному и обратному обходам соответственно. Они показывают три разных приоритета обхода, но все они относятся к обходу в глубину. ### Анализ сложности **Временная сложность**: все вершины будут посещены по $1$ разу, что требует $O(|V|)$ времени; все ребра будут посещены по $2$ раза, что требует $O(2|E|)$ времени; суммарно получается $O(|V| + |E|)$ . **Пространственная сложность**: число вершин в списке `res` и хеш-множестве `visited` в худшем случае достигает $|V|$ , максимальная глубина рекурсии тоже равна $|V|$ , поэтому требуется $O(|V|)$ памяти. ================================================ FILE: ru/docs/chapter_graph/index.md ================================================ # Графы ![Графы](../assets/covers/chapter_graph.jpg) !!! abstract В жизни мы похожи на вершины, соединенные множеством невидимых ребер. Каждая встреча и каждое расставание оставляют в этой огромной сети свой след. ================================================ FILE: ru/docs/chapter_graph/summary.md ================================================ # Краткие итоги ### Основные моменты - Граф состоит из вершин и ребер и может быть задан как множество вершин и множество ребер. - По сравнению с линейными отношениями (связный список) и отношениями разделения (дерево), сетевые отношения (граф) обладают большей свободой и потому более сложны. - Ребра ориентированного графа имеют направление, в связном графе любые вершины достижимы, а во взвешенном графе каждое ребро содержит переменную веса. - Матрица смежности использует матрицу для представления графа: каждая строка и каждый столбец соответствуют вершине, а элементы матрицы показывают, есть между двумя вершинами ребро или нет. Матрица смежности эффективна в операциях добавления, удаления, поиска и изменения, но расходует больше памяти. - Список смежности использует несколько списков для представления графа; $i$-й список соответствует вершине $i$ и хранит все ее смежные вершины. По сравнению с матрицей смежности список смежности экономит пространство, но для поиска ребра в нем приходится обходить список, поэтому по времени он уступает. - Когда списки в списке смежности становятся слишком длинными, их можно преобразовать в красно-черное дерево или хеш-таблицу, чтобы повысить эффективность поиска. - С точки зрения алгоритмической идеи матрица смежности отражает принцип "обмена пространства на время", а список смежности - принцип "обмена времени на пространство". - Графы можно использовать для моделирования различных реальных систем, таких как социальные сети, линии метро и так далее. - Дерево является частным случаем графа, а обход дерева - частным случаем обхода графа. - Обход графа в ширину представляет собой способ поиска, который расширяется от ближнего к дальнему и обычно реализуется с помощью очереди. - Обход графа в глубину представляет собой способ поиска, который сначала идет до самого конца, а затем возвращается назад, когда путь исчерпан; обычно он реализуется на основе рекурсии. ### Q & A **Q**: Что считается путем: последовательность вершин или последовательность ребер? Определение в разных языковых версиях Википедии различается: в английской версии путь определяется как "последовательность ребер", а в русской версии - как "последовательность вершин". В английской версии исходная формулировка выглядит так: In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices. В этой книге путь рассматривается как последовательность ребер, а не как последовательность вершин. Причина в том, что между двумя вершинами может существовать несколько ребер, и в таком случае каждому ребру соответствует свой путь. **Q**: Есть ли в несвязном графе вершины, до которых нельзя дойти? В несвязном графе, начиная из некоторой вершины, по крайней мере одна вершина оказывается недостижимой. Чтобы обойти весь несвязный граф, нужно задать несколько стартовых точек и обойти все связные компоненты графа. **Q**: Есть ли требования к порядку вершин в списке "всех вершин, соединенных с данной вершиной" в списке смежности? Порядок может быть произвольным. Но на практике может понадобиться сортировка по определенному правилу, например по порядку добавления вершин или по возрастанию значений вершин; это помогает быстро находить вершины с некоторым экстремальным свойством. ================================================ FILE: ru/docs/chapter_greedy/fractional_knapsack_problem.md ================================================ # Задача о дробном рюкзаке !!! question Дано $n$ предметов. Вес предмета $i$ равен $wgt[i-1]$, ценность равна $val[i-1]$, также дан рюкзак вместимостью $cap$. Каждый предмет можно выбрать только один раз, **но разрешается взять лишь часть предмета, а ценность вычисляется пропорционально взятому весу**. Требуется найти максимальную ценность предметов в рюкзаке при ограниченной вместимости. Пример показан на рисунке ниже. ![Пример данных для задачи о дробном рюкзаке](fractional_knapsack_problem.assets/fractional_knapsack_example.png) Задача о дробном рюкзаке в целом очень похожа на задачу о рюкзаке 0-1: состояние включает текущий предмет $i$ и вместимость $c$, а цель состоит в нахождении максимальной ценности при заданной вместимости рюкзака. Отличие в том, что здесь разрешено брать только часть предмета. Как показано на рисунке ниже, **мы можем произвольно делить предмет и вычислять соответствующую ценность пропорционально весу**. 1. Для предмета $i$ его ценность на единицу веса равна $val[i-1] / wgt[i-1]$, сокращенно - удельная ценность. 2. Если взять часть предмета $i$ весом $w$, то ценность рюкзака увеличится на $w \times val[i-1] / wgt[i-1]$. ![Ценность предмета на единицу веса](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png) ### Определение жадной стратегии Максимизация общей ценности предметов в рюкзаке **по сути равносильна максимизации ценности на единицу веса**. Отсюда естественно выводится следующая жадная стратегия. 1. Отсортировать предметы по убыванию удельной ценности. 2. Перебирать все предметы и **на каждом шаге жадно выбирать предмет с наибольшей удельной ценностью**. 3. Если оставшейся вместимости рюкзака недостаточно, взять часть текущего предмета, чтобы заполнить рюкзак. ![Жадная стратегия для задачи о дробном рюкзаке](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png) ### Код реализации Мы вводим класс `Item`, чтобы можно было сортировать предметы по удельной ценности. Далее циклически выполняем жадный выбор и, когда рюкзак заполнен, выходим и возвращаем ответ: ```src [file]{fractional_knapsack}-[class]{}-[func]{fractional_knapsack} ``` Встроенный алгоритм сортировки обычно имеет временную сложность $O(n \log n)$, а пространственная сложность обычно равна $O(\log n)$ или $O(n)$, в зависимости от конкретной реализации в языке программирования. Помимо сортировки, в худшем случае потребуется пройти весь список предметов, но это не меняет асимптотику, **поэтому итоговая временная сложность равна $O(n \log n)$**, где $n$ - число предметов. Поскольку инициализируется список объектов `Item`, **пространственная сложность равна $O(n)$**. ### Доказательство корректности Используем доказательство от противного. Предположим, что предмет $x$ имеет наибольшую удельную ценность, некоторый алгоритм получил максимальную ценность `res`, но в найденном решении предмет $x$ отсутствует. Теперь вынем из рюкзака произвольный предмет единичного веса и заменим его на предмет $x$ того же веса. Поскольку предмет $x$ имеет наибольшую удельную ценность, общая ценность после замены обязательно станет больше `res`. **Это противоречит тому, что `res` является оптимальным решением, а значит оптимальное решение обязательно содержит предмет $x$**. Для других предметов в этом решении можно построить аналогичное противоречие. Иными словами, **предметы с большей удельной ценностью всегда являются более выгодным выбором**, а значит жадная стратегия корректна. Как показано на рисунке ниже, если рассматривать вес предметов и их удельную ценность как горизонтальную и вертикальную оси двумерной диаграммы, то задачу о дробном рюкзаке можно интерпретировать как «поиск максимальной площади, ограниченной конечным отрезком по горизонтали». Эта аналогия помогает понять корректность жадной стратегии с геометрической точки зрения. ![Геометрическая интерпретация задачи о дробном рюкзаке](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png) ================================================ FILE: ru/docs/chapter_greedy/greedy_algorithm.md ================================================ # Жадный алгоритм Жадный алгоритм (greedy algorithm) - это распространенный метод решения задач оптимизации. Его основная идея состоит в том, чтобы на каждом этапе принятия решения выбирать вариант, который выглядит наилучшим прямо сейчас, то есть жадно принимать локально оптимальные решения в надежде получить глобально оптимальный результат. Жадные алгоритмы просты и эффективны, поэтому широко применяются во многих практических задачах. Жадные алгоритмы и динамическое программирование часто используются для решения задач оптимизации. У них есть некоторое сходство, например оба метода опираются на свойство оптимальной подструктуры, но принципы работы различаются. - Динамическое программирование при получении текущего решения учитывает все предыдущие решения и использует ответы для прошлых подзадач, чтобы построить ответ для текущей подзадачи. - Жадный алгоритм не учитывает предыдущие решения, а просто движется вперед, каждый раз делая жадный выбор и постепенно сужая область задачи, пока она не будет решена. Чтобы лучше понять принцип работы жадного алгоритма, разберем его на примере задачи «размен монет». Эта задача уже встречалась в разделе «задача о полном рюкзаке», поэтому она наверняка вам знакома. !!! question Дано $n$ видов монет. Номинал монеты $i$ равен $coins[i - 1]$, целевая сумма равна $amt$, причем каждую монету можно брать неограниченное число раз. Требуется найти минимальное число монет, которыми можно набрать целевую сумму. Если набрать сумму невозможно, верните $-1$. Жадная стратегия для этой задачи показана на рисунке ниже. Для заданной целевой суммы **мы жадно выбираем монету, которая не превышает ее и находится к ней ближе всего**, и повторяем этот шаг, пока не получим нужную сумму. ![Жадная стратегия для задачи о размене монет](greedy_algorithm.assets/coin_change_greedy_strategy.png) Ниже приведен код реализации. ```src [file]{coin_change_greedy}-[class]{}-[func]{coin_change_greedy} ``` У вас может невольно вырваться: «Эврика!» Жадный алгоритм решает задачу размена монет всего примерно десятью строками кода. ## Преимущества и ограничения жадного алгоритма **Жадный алгоритм не только прост в реализации, но и обычно очень эффективен**. В приведенном выше коде обозначим минимальный номинал монеты через $\min(coins)$, тогда жадный выбор выполняется не более чем $amt / \min(coins)$ раз, а временная сложность равна $O(amt / \min(coins))$. Это на порядок меньше, чем временная сложность решения через динамическое программирование $O(n \times amt)$. Однако **для некоторых наборов номиналов монет жадный алгоритм не может найти оптимальный ответ**. Ниже показаны два примера. - **Положительный пример $coins = [1, 5, 10, 20, 50, 100]$**: для такого набора монет при любом $amt$ жадный алгоритм находит оптимальное решение. - **Отрицательный пример $coins = [1, 20, 50]$**: пусть $amt = 60$. Жадный алгоритм найдет только комбинацию $50 + 1 \times 10$, то есть всего $11$ монет, тогда как динамическое программирование находит оптимум $20 + 20 + 20$, где требуется лишь $3$ монеты. - **Отрицательный пример $coins = [1, 49, 50]$**: пусть $amt = 98$. Жадный алгоритм найдет только комбинацию $50 + 1 \times 48$, то есть всего $49$ монет, тогда как динамическое программирование находит оптимум $49 + 49$, где требуется лишь $2$ монеты. ![Примеры, где жадный алгоритм не находит оптимального решения](greedy_algorithm.assets/coin_change_greedy_vs_dp.png) Иными словами, в задаче о размене монет жадный алгоритм не гарантирует нахождение глобально оптимального решения и иногда может приводить к очень плохому ответу. Для этой задачи больше подходит динамическое программирование. В общем случае жадный алгоритм применим в двух следующих ситуациях. 1. **Можно гарантировать нахождение оптимального решения**: в таком случае жадный алгоритм часто является лучшим выбором, поскольку обычно он эффективнее, чем поиск с возвратом и динамическое программирование. 2. **Можно найти приближенно оптимальное решение**: в таком случае жадный алгоритм тоже полезен. Для многих сложных задач поиск глобального оптимума очень труден, и возможность быстро найти субоптимальный ответ уже весьма ценна. ## Свойства жадного алгоритма Тогда возникает вопрос: какие задачи подходят для решения жадным алгоритмом? Или, другими словами, в каких случаях жадный алгоритм может гарантировать оптимальный ответ? По сравнению с динамическим программированием условия применения жадного алгоритма более строгие. В основном нас интересуют два свойства задачи. - **Свойство жадного выбора**: только когда локально оптимальный выбор всегда может привести к глобально оптимальному решению, жадный алгоритм способен гарантировать оптимум. - **Оптимальная подструктура**: оптимальное решение исходной задачи содержит оптимальные решения подзадач. Оптимальная подструктура уже обсуждалась в главе «Динамическое программирование», поэтому здесь не будем повторяться. Стоит отметить, что у некоторых задач оптимальная подструктура не столь очевидна, но их все равно можно решать жадным алгоритмом. Основное внимание мы уделяем тому, как определить свойство жадного выбора. Хотя формулировка выглядит довольно простой, **на практике для многих задач доказать свойство жадного выбора совсем не легко**. Например, в задаче о размене монет легко привести контрпример и опровергнуть свойство жадного выбора, но вот доказать его истинность намного сложнее. Если спросить: **для каких наборов монет можно использовать жадный алгоритм**? - обычно удается дать лишь интуитивный или примерный ответ, а не строгое математическое доказательство. !!! quote Существует статья, в которой приводится алгоритм со временной сложностью $O(n^3)$ для определения того, можно ли с помощью жадного алгоритма находить оптимальный размен для любой суммы в заданной системе монет. Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234. ## Этапы решения задач жадным алгоритмом Процесс решения жадной задачи в общем виде можно разбить на три шага. 1. **Анализ задачи**: разобраться в свойствах задачи, включая определение состояний, целевой функции и ограничений. Этот этап присутствует и в поиске с возвратом, и в динамическом программировании. 2. **Определение жадной стратегии**: определить, какой жадный выбор следует делать на каждом шаге. Эта стратегия должна уменьшать размер задачи на каждом этапе и в итоге привести к решению всей задачи. 3. **Доказательство корректности**: обычно требуется доказать, что задача обладает свойством жадного выбора и оптимальной подструктурой. На этом этапе может понадобиться математическое доказательство, например индукция или доказательство от противного. Определение жадной стратегии - это ключевой этап решения, но на практике он часто оказывается непростым по следующим причинам. - **Жадные стратегии для разных задач сильно различаются**. Для многих задач стратегия довольно очевидна, и до нее можно дойти за счет общих рассуждений и нескольких проб. Но в более сложных задачах жадная стратегия может быть очень скрытой, и тут уже многое зависит от опыта решения задач и алгоритмической подготовки. - **Некоторые жадные стратегии выглядят убедительно, но оказываются обманчивыми**. Бывает, что мы с уверенностью придумали жадную стратегию, написали код и отправили его на проверку, а часть тестов не проходит. Причина в том, что спроектированная стратегия лишь «частично верна», и описанная выше задача о размене монет - типичный пример. Чтобы гарантировать корректность, нужно дать строгое математическое доказательство жадной стратегии, **обычно с использованием доказательства от противного или математической индукции**. Однако и доказательство корректности может оказаться непростой задачей. Если идей нет, мы обычно начинаем отлаживать код на тестовых примерах, постепенно меняя и проверяя жадную стратегию. ## Типичные задачи для жадного алгоритма Жадные алгоритмы часто применяются в задачах оптимизации, обладающих свойством жадного выбора и оптимальной подструктурой. Ниже приведены некоторые типичные задачи, решаемые жадным подходом. - **Задача о размене монет**: при некоторых системах монет жадный алгоритм всегда дает оптимальный ответ. - **Задача о расписании интервалов**: пусть есть несколько задач, каждая выполняется в некотором временном интервале, и требуется завершить как можно больше задач. Если каждый раз выбирать задачу с самым ранним временем окончания, то жадный алгоритм дает оптимальный ответ. - **Задача о дробном рюкзаке**: дана группа предметов и грузоподъемность. Требуется выбрать предметы так, чтобы их общий вес не превышал ограничение, а общая ценность была максимальной. Если каждый раз выбирать предмет с наилучшим отношением стоимости к весу, то в некоторых случаях жадный алгоритм дает оптимальный ответ. - **Задача о покупке и продаже акций**: дана история цен акции. Можно совершать несколько сделок, но если акция уже куплена, то до продажи покупать снова нельзя. Цель - получить максимальную прибыль. - **Код Хаффмана**: это жадный алгоритм для сжатия данных без потерь. Построив дерево Хаффмана и каждый раз объединяя два узла с наименьшей частотой, мы получаем дерево с минимальной взвешенной длиной пути, то есть минимальной длиной кодирования. - **Алгоритм Дейкстры**: это жадный алгоритм решения задачи о кратчайших путях от заданной исходной вершины до всех остальных вершин. ================================================ FILE: ru/docs/chapter_greedy/index.md ================================================ # Жадность ![Жадность](../assets/covers/chapter_greedy.jpg) !!! abstract Подсолнух поворачивается к солнцу, постоянно стремясь к наилучшим условиям для роста. Жадная стратегия через цепочку простых выборов постепенно приводит к наилучшему ответу. ================================================ FILE: ru/docs/chapter_greedy/max_capacity_problem.md ================================================ # Задача о максимальной вместимости !!! question Дан массив $ht$, где каждый элемент обозначает высоту вертикальной перегородки. Любые две перегородки в массиве вместе с пространством между ними образуют контейнер. Вместимость контейнера равна произведению высоты и ширины (площади), где высота определяется более короткой перегородкой, а ширина - разностью индексов двух перегородок в массиве. Требуется выбрать две перегородки так, чтобы образованный ими контейнер имел максимальную вместимость. Пример показан на рисунке ниже. ![Пример данных для задачи о максимальной вместимости](max_capacity_problem.assets/max_capacity_example.png) Контейнер образуется произвольными двумя перегородками, **поэтому состоянием задачи служит пара индексов этих перегородок, обозначим ее как $[i, j]$**. Согласно условию, вместимость равна произведению высоты на ширину, где высота определяется короткой перегородкой, а ширина - разностью индексов двух перегородок. Обозначим вместимость через $cap[i, j]$, тогда формула принимает вид: $$ cap[i, j] = \min(ht[i], ht[j]) \times (j - i) $$ Пусть длина массива равна $n$. Тогда число пар перегородок, то есть общее число состояний, равно $C_n^2 = \frac{n(n - 1)}{2}$. Самый прямолинейный подход - **перебрать все состояния**, после чего найти максимальную вместимость. Его временная сложность равна $O(n^2)$. ### Определение жадной стратегии У этой задачи есть и более эффективное решение. Как показано на рисунке ниже, рассмотрим состояние $[i, j]$, где индексы удовлетворяют $i < j$, а высоты - условию $ht[i] < ht[j]$, то есть $i$ - короткая перегородка, а $j$ - длинная. ![Начальное состояние](max_capacity_problem.assets/max_capacity_initial_state.png) Как показано на рисунке ниже, **если в этот момент сдвинуть длинную перегородку $j$ ближе к короткой перегородке $i$, то вместимость обязательно уменьшится**. Причина в том, что после смещения длинной перегородки $j$ ширина $j-i$ обязательно станет меньше, а высота определяется короткой перегородкой, поэтому высота либо останется прежней (если $i$ останется короткой перегородкой), либо уменьшится (если сдвинутая $j$ станет короткой перегородкой). ![Состояние после перемещения длинной перегородки внутрь](max_capacity_problem.assets/max_capacity_moving_long_board.png) Рассуждая в обратную сторону, **только сдвигая короткую перегородку $i$ внутрь, мы можем получить шанс увеличить вместимость**. Хотя ширина при этом обязательно уменьшится, **высота может возрасти** (если после перемещения короткая перегородка $i$ станет выше). Например, на рисунке ниже после перемещения короткой перегородки площадь увеличивается. ![Состояние после перемещения короткой перегородки внутрь](max_capacity_problem.assets/max_capacity_moving_short_board.png) Отсюда и выводится жадная стратегия для этой задачи: инициализировать два указателя по краям контейнера и на каждом шаге сдвигать внутрь указатель, соответствующий короткой перегородке, пока указатели не встретятся. На рисунках ниже показан процесс выполнения этой жадной стратегии. 1. В начальном состоянии указатели $i$ и $j$ стоят на двух концах массива. 2. Вычислить вместимость текущего состояния $cap[i, j]$ и обновить максимальную вместимость. 3. Сравнить высоты перегородок $i$ и $j$, после чего сдвинуть короткую перегородку на одну позицию внутрь. 4. Повторять шаги `2.` и `3.` до тех пор, пока $i$ и $j$ не встретятся. === "<1>" ![Жадный процесс решения задачи о максимальной вместимости](max_capacity_problem.assets/max_capacity_greedy_step1.png) === "<2>" ![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png) === "<3>" ![max_capacity_greedy_step3](max_capacity_problem.assets/max_capacity_greedy_step3.png) === "<4>" ![max_capacity_greedy_step4](max_capacity_problem.assets/max_capacity_greedy_step4.png) === "<5>" ![max_capacity_greedy_step5](max_capacity_problem.assets/max_capacity_greedy_step5.png) === "<6>" ![max_capacity_greedy_step6](max_capacity_problem.assets/max_capacity_greedy_step6.png) === "<7>" ![max_capacity_greedy_step7](max_capacity_problem.assets/max_capacity_greedy_step7.png) === "<8>" ![max_capacity_greedy_step8](max_capacity_problem.assets/max_capacity_greedy_step8.png) === "<9>" ![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png) ### Код реализации Цикл в коде выполняется не более $n$ раз, **поэтому временная сложность равна $O(n)$**. Переменные $i$, $j$, $res$ используют дополнительную память постоянного размера, **поэтому пространственная сложность равна $O(1)$**. ```src [file]{max_capacity}-[class]{}-[func]{max_capacity} ``` ### Доказательство корректности Жадный алгоритм быстрее полного перебора именно потому, что каждый жадный шаг «пропускает» часть состояний. Например, в состоянии $cap[i, j]$ перегородка $i$ является короткой, а $j$ - длинной. Если жадно сдвинуть короткую перегородку $i$ на одну позицию внутрь, то состояния, показанные на рисунке ниже, будут «пропущены». **Это означает, что позже мы уже не сможем проверить вместимость этих состояний**. $$ cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1] $$ ![Состояния, пропущенные из-за смещения короткой перегородки](max_capacity_problem.assets/max_capacity_skipped_states.png) Нетрудно заметить, что **эти пропущенные состояния на самом деле и есть все состояния, в которых длинная перегородка $j$ сдвигается внутрь**. Ранее мы уже доказали, что перемещение длинной перегородки внутрь обязательно уменьшает вместимость. Иными словами, пропущенные состояния не могут быть оптимальным решением, **поэтому их пропуск не приводит к потере оптимума**. Приведенный анализ показывает, что операция перемещения короткой перегородки является «безопасной», а жадная стратегия действительно корректна. ================================================ FILE: ru/docs/chapter_greedy/max_product_cutting_problem.md ================================================ # Задача о максимальном произведении разбиения !!! question Дан положительный целый $n$. Требуется разложить его в сумму как минимум двух положительных целых чисел и найти максимально возможное произведение всех полученных чисел, как показано на рисунке ниже. ![Определение задачи о максимальном произведении разбиения](max_product_cutting_problem.assets/max_product_cutting_definition.png) Предположим, что мы разбили $n$ на $m$ целочисленных множителей, где $i$-й множитель обозначим через $n_i$, то есть $$ n = \sum_{i=1}^{m}n_i $$ Цель задачи - найти максимальное произведение всех целочисленных множителей, то есть $$ \max(\prod_{i=1}^{m}n_i) $$ Нужно понять: каким должно быть число частей $m$ и какими должны быть значения каждого $n_i$? ### Определение жадной стратегии Из опыта известно, что произведение двух целых чисел часто больше их суммы. Предположим, что мы выделяем из $n$ множитель $2$, тогда произведение равно $2(n-2)$. Сравним это выражение с $n$: $$ \begin{aligned} 2(n-2) & \geq n \newline 2n - n - 4 & \geq 0 \newline n & \geq 4 \end{aligned} $$ Как показано на рисунке ниже, когда $n \geq 4$, выделение множителя $2$ увеличивает произведение. **Это означает, что все целые числа, большие либо равные $4$, следует продолжать разбивать**. **Жадная стратегия 1**: если в схеме разбиения присутствует множитель $\geq 4$, то его нужно дальше разбивать. В конечной схеме разбиения должны остаться только множители $1$, $2$, $3$. ![Разбиение увеличивает произведение](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png) Теперь подумаем, какой множитель является наилучшим. Среди $1$, $2$, $3$ очевидно худшим является $1$, потому что всегда выполняется $1 \times (n-1) < n$, то есть выделение $1$ уменьшает произведение. Как показано на рисунке ниже, при $n = 6$ имеем $3 \times 3 > 2 \times 2 \times 2$. **Это означает, что выделять $3$ выгоднее, чем выделять $2$**. **Жадная стратегия 2**: в схеме разбиения должно быть не более двух множителей $2$. Потому что три двойки всегда можно заменить двумя тройками и получить большее произведение. ![Оптимальные множители разбиения](max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png) Итак, получаем следующую жадную стратегию. 1. Для заданного целого $n$ непрерывно выделять из него множитель $3$, пока остаток не станет равным $0$, $1$ или $2$. 2. Если остаток равен $0$, это означает, что $n$ кратно $3$, и больше ничего делать не нужно. 3. Если остаток равен $2$, дальнейшее разбиение не требуется, его нужно сохранить. 4. Если остаток равен $1$, то поскольку $2 \times 2 > 1 \times 3$, последний множитель $3$ следует заменить на $2$. ### Код реализации Как показано на рисунке ниже, нам не нужен цикл, чтобы выполнять разбиение числа. Можно использовать целочисленное деление, чтобы получить число троек $a$, и операцию взятия остатка, чтобы получить остаток $b$. Тогда имеем: $$ n = 3 a + b $$ Обратите внимание, что для граничного случая $n \leq 3$ необходимо выделить множитель $1$, и тогда произведение равно $1 \times (n - 1)$. ```src [file]{max_product_cutting}-[class]{}-[func]{max_product_cutting} ``` ![Метод вычисления максимального произведения разбиения](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png) **Временная сложность зависит от того, как в языке программирования реализовано возведение в степень**. Если взять Python, то обычно используются три распространенные функции для вычисления степени. - Оператор `**` и функция `pow()` имеют временную сложность $O(\log⁡ a)$. - Функция `math.pow()` внутри вызывает функцию `pow()` из библиотеки C, выполняющую возведение в степень с плавающей точкой, и ее временная сложность равна $O(1)$. Переменные $a$ и $b$ занимают дополнительную память постоянного размера, **поэтому пространственная сложность равна $O(1)$**. ### Доказательство корректности Используем доказательство от противного и рассмотрим только случай $n \geq 4$. 1. **Все множители $\leq 3$**: предположим, что в оптимальной схеме разбиения существует множитель $x \geq 4$. Тогда его можно дальше разложить в $2(x-2)$ и получить большее или равное произведение. Это противоречит предположению. 2. **Схема разбиения не содержит $1$**: предположим, что в оптимальной схеме присутствует множитель $1$. Тогда его можно объединить с другим множителем и получить большее произведение. Это противоречит предположению. 3. **Схема разбиения содержит не более двух $2$**: предположим, что в оптимальной схеме присутствуют три двойки. Тогда их можно заменить двумя тройками и получить большее произведение. Это противоречит предположению. ================================================ FILE: ru/docs/chapter_greedy/summary.md ================================================ # Резюме ### Ключевые моменты - Жадный алгоритм обычно используется для решения задач оптимизации. Его принцип состоит в том, чтобы на каждом этапе принятия решения делать локально оптимальный выбор в надежде получить глобально оптимальный ответ. - Жадный алгоритм итеративно делает один жадный выбор за другим, на каждом шаге превращая задачу в подзадачу меньшего размера, пока задача не будет полностью решена. - Жадный алгоритм не только прост в реализации, но и часто обладает высокой эффективностью. По сравнению с динамическим программированием его временная сложность обычно ниже. - В задаче о размене монет для некоторых наборов монет жадный алгоритм способен гарантировать оптимальный ответ, а для других наборов - нет: он может дать очень плохое решение. - Задачи, подходящие для жадного алгоритма, обладают двумя ключевыми свойствами: свойством жадного выбора и оптимальной подструктурой. Свойство жадного выбора отражает корректность жадной стратегии. - Для некоторых сложных задач доказать свойство жадного выбора непросто. Относительно легче найти контрпример и опровергнуть его, как это видно на примере задачи о размене монет. - Решение жадной задачи обычно состоит из трех шагов: анализ задачи, определение жадной стратегии и доказательство корректности. Из них ключевым является выбор жадной стратегии, а доказательство корректности часто оказывается самым трудным. - В задаче о дробном рюкзаке, в отличие от задачи о рюкзаке 0-1, разрешено брать часть предмета, поэтому ее можно решать жадным алгоритмом. Корректность жадной стратегии доказывается методом от противного. - Задачу о максимальной вместимости можно решать полным перебором со временной сложностью $O(n^2)$. Разработав жадную стратегию со сдвигом короткой перегородки внутрь на каждом шаге, временную сложность можно оптимизировать до $O(n)$. - В задаче о максимальном произведении разбиения мы последовательно выводим две жадные стратегии: все целые числа $\geq 4$ следует дальше разбивать, а оптимальным множителем разбиения является $3$. В коде присутствуют операции возведения в степень, поэтому временная сложность зависит от способа их реализации и обычно равна $O(1)$ или $O(\log n)$. ================================================ FILE: ru/docs/chapter_hashing/hash_algorithm.md ================================================ # Алгоритмы хеширования В двух предыдущих разделах мы рассмотрели принципы работы хеш-таблицы и способы обработки хеш-коллизий. Однако и открытая адресация, и метод цепочек **лишь позволяют хеш-таблице корректно работать при возникновении коллизий, но не уменьшают вероятность появления самих коллизий**. Если хеш-коллизии происходят слишком часто, производительность хеш-таблицы резко деградирует. Как показано на рисунке ниже, для хеш-таблицы с методом цепочек в идеальном случае пары ключ-значение равномерно распределены по всем бакетам, и это дает наилучшую эффективность поиска; в худшем же случае все пары ключ-значение оказываются в одном бакете, и временная сложность вырождается до $O(n)$ . ![Лучший и худший случаи хеш-коллизий](hash_algorithm.assets/hash_collision_best_worst_condition.png) **Распределение пар ключ-значение определяется хеш-функцией**. Вспомним этапы вычисления хеш-функции: сначала вычисляется хеш-значение, затем оно берется по модулю длины массива: ```shell index = hash(key) % capacity ``` Из этой формулы видно: при фиксированной емкости хеш-таблицы `capacity` **выходное значение определяет именно хеш-алгоритм `hash()` **, а значит, именно он определяет распределение пар ключ-значение в хеш-таблице. Это означает, что для уменьшения вероятности хеш-коллизий нам следует сосредоточиться на проектировании хеш-алгоритма `hash()` . ## Цели хеш-алгоритма Чтобы получить структуру данных хеш-таблицы, которая будет одновременно быстрой и надежной, хеш-алгоритм должен обладать следующими свойствами. - **Детерминированность**: для одинакового входа хеш-алгоритм всегда должен выдавать одинаковый результат. Только так хеш-таблица остается надежной. - **Высокая эффективность**: вычисление хеш-значения должно быть достаточно быстрым. Чем меньше вычислительные затраты, тем выше практическая ценность хеш-таблицы. - **Равномерное распределение**: хеш-алгоритм должен стараться распределять пары ключ-значение в хеш-таблице равномерно. Чем равномернее распределение, тем ниже вероятность хеш-коллизий. На практике хеш-алгоритмы используются не только для реализации хеш-таблиц, но и во многих других областях. - **Хранение паролей**: чтобы защищать пароли пользователей, система обычно хранит не сами пароли в открытом виде, а их хеш-значения. Когда пользователь вводит пароль, система вычисляет хеш-значение введенного пароля и сравнивает его с сохраненным значением. Если они совпадают, пароль считается правильным. - **Проверка целостности данных**: отправитель может вычислить хеш-значение данных и отправить его вместе с самими данными; получатель затем вычисляет хеш-значение повторно и сравнивает его с полученным. Если они совпадают, данные считаются целостными. Для приложений, связанных с криптографией, чтобы не допустить восстановления исходного пароля по хеш-значению и иных форм обратного анализа, хеш-алгоритм должен обладать более строгими свойствами безопасности. - **Односторонность**: по хеш-значению нельзя восстановить какую-либо информацию о входных данных. - **Устойчивость к коллизиям**: должно быть крайне трудно найти два разных входа, имеющих одинаковое хеш-значение. - **Эффект лавины**: даже небольшое изменение во входных данных должно приводить к заметному и непредсказуемому изменению результата. Обрати внимание: **"равномерное распределение" и "устойчивость к коллизиям" - это два независимых понятия** , и выполнение первого не означает автоматического выполнения второго. Например, при случайном распределении входных `key` хеш-функция `key % 100` может выдавать достаточно равномерное распределение. Однако этот хеш-алгоритм слишком прост: все `key` с одинаковыми двумя последними цифрами будут иметь одинаковый результат, а значит, по хеш-значению можно легко подобрать подходящие `key` и, например, взломать пароль. ## Проектирование хеш-алгоритма Разработка хеш-алгоритма - это сложная задача, в которой нужно учитывать множество факторов. Однако для некоторых нетребовательных сценариев мы можем спроектировать и несколько простых хеш-алгоритмов. - **Аддитивный хеш**: складываем ASCII-коды всех символов входной строки и используем полученную сумму как хеш-значение. - **Мультипликативный хеш**: используем "некоррелированность" умножения; на каждом шаге умножаем текущее значение на константу и добавляем ASCII-код очередного символа. - **XOR-хеш**: последовательно накапливаем элементы входных данных в одном хеш-значении через операцию XOR. - **Ротационный хеш**: последовательно накапливаем ASCII-коды символов, причем перед каждым накоплением выполняем циклический сдвиг хеш-значения. ```src [file]{simple_hash}-[class]{}-[func]{rot_hash} ``` Нетрудно заметить, что последний шаг каждого из этих хеш-алгоритмов - взятие по модулю большого простого числа $1000000007$ , чтобы гарантировать, что хеш-значение остается в разумных границах. Стоит задуматься: почему подчеркивается именно взятие по модулю простого числа, и какие недостатки возникают при использовании составного модуля? Это интересный вопрос. Сначала дадим вывод: **использование большого простого числа в качестве модуля позволяет в максимальной степени обеспечивать равномерное распределение хеш-значений**. Поскольку простое число не имеет общих делителей с другими числами, это помогает уменьшить периодические закономерности, возникающие из-за операции взятия остатка, и тем самым снизить число хеш-коллизий. Рассмотрим пример. Предположим, мы выбрали составное число $9$ в качестве модуля. Оно делится на $3$ , поэтому все `key` , которые делятся на $3$ , будут отображаться только в три хеш-значения: $0$ , $3$ , $6$ . $$ \begin{aligned} \text{modulus} & = 9 \newline \text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline \text{hash} & = \{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\dots \} \end{aligned} $$ Если входные `key` как раз удовлетворяют такому распределению в виде арифметической прогрессии, то хеш-значения начнут скучиваться, а это усугубит хеш-коллизии. Теперь предположим, что мы заменили `modulus` на простое число $13$ ; поскольку между `key` и `modulus` нет общих делителей, равномерность распределения хеш-значений заметно улучшится. $$ \begin{aligned} \text{modulus} & = 13 \newline \text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline \text{hash} & = \{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \dots \} \end{aligned} $$ Следует отметить: если можно гарантировать, что `key` распределены случайно и равномерно, то выбор простого или составного числа в качестве модуля не так важен - оба варианта способны дать равномерное распределение хеш-значений. Но если в распределении `key` присутствует периодичность, то взятие по модулю составного числа гораздо легче приводит к кластеризации. Итак, на практике мы обычно выбираем простое число в качестве модуля, причем это простое число желательно брать достаточно большим, чтобы по возможности убрать периодические закономерности и повысить устойчивость хеш-алгоритма. ## Распространенные хеш-алгоритмы Нетрудно заметить, что описанные выше простые хеш-алгоритмы довольно хрупкие и далеки от поставленных целей. Например, сложение и XOR подчиняются коммутативному закону, поэтому аддитивный хеш и XOR-хеш не различают строки, состоящие из одних и тех же символов, но в разном порядке. Это может усиливать хеш-коллизии и даже создавать некоторые проблемы безопасности. На практике мы обычно используем стандартные хеш-алгоритмы, такие как MD5, SHA-1, SHA-2 и SHA-3. Они могут отображать входные данные произвольной длины в хеш-значения фиксированной длины. На протяжении почти ста лет хеш-алгоритмы непрерывно развивались и оптимизировались. Одни исследователи старались повысить их производительность, а другие исследователи и хакеры сосредоточивались на поиске уязвимостей в их безопасности. В таблице ниже приведены распространенные хеш-алгоритмы, которые часто встречаются в реальных приложениях. - MD5 и SHA-1 уже многократно были успешно атакованы, поэтому они выведены из большинства сценариев, где требуется безопасность. - SHA-256 из семейства SHA-2 является одним из самых надежных хеш-алгоритмов; на сегодняшний день не известно успешных практических атак, поэтому он широко используется в самых разных протоколах и системах безопасности. - SHA-3 по сравнению с SHA-2 требует меньших затрат на реализацию и обеспечивает более высокую вычислительную эффективность, но на данный момент распространен слабее, чем семейство SHA-2.

Таблица   Распространенные хеш-алгоритмы

| | MD5 | SHA-1 | SHA-2 | SHA-3 | | -------- | ------------------------------ | ---------------- | ---------------------------- | ------------------- | | Год появления | 1992 | 1995 | 2002 | 2008 | | Длина вывода | 128 bit | 160 bit | 256/512 bit | 224/256/384/512 bit | | Хеш-коллизии | Частые | Частые | Редкие | Редкие | | Уровень безопасности | Низкий, успешно атакован | Низкий, успешно атакован | Высокий | Высокий | | Применение | Устарел, но еще используется для проверки целостности данных | Устарел | Проверка криптовалютных транзакций, цифровые подписи и т. д. | Может использоваться как замена SHA-2 | ## Хеш-значения структур данных Мы знаем, что `key` в хеш-таблице могут быть целыми числами, вещественными числами, строками и другими типами данных. Языки программирования обычно предоставляют встроенные хеш-алгоритмы для этих типов, чтобы вычислять индексы бакетов в хеш-таблице. Возьмем Python: в нем можно вызвать функцию `hash()` , чтобы вычислить хеш-значения для различных типов данных. - Хеш-значение целого числа и булева значения совпадает с самим значением. - Вычисление хеш-значений для вещественных чисел и строк устроено сложнее; интересующиеся читатели могут изучить это самостоятельно. - Хеш-значение кортежа получается путем хеширования каждого элемента, а затем объединения этих хеш-значений в одно итоговое значение. - Хеш-значение объекта обычно строится на основе его адреса в памяти. Если переопределить метод хеширования объекта, можно реализовать вычисление хеша по содержимому. !!! tip Обрати внимание: определения и способы вычисления встроенных хеш-значений в разных языках программирования отличаются. === "Python" ```python title="built_in_hash.py" num = 3 hash_num = hash(num) # Хеш-значение целого числа 3 равно 3 bol = True hash_bol = hash(bol) # Хеш-значение булевого значения True равно 1 dec = 3.14159 hash_dec = hash(dec) # Хеш-значение числа 3.14159 равно 326484311674566659 str = "Hello Algo" hash_str = hash(str) # Хеш-значение строки "Hello Algo" равно 4617003410720528961 tup = (12836, "Сяо Ха") hash_tup = hash(tup) # Хеш-значение кортежа (12836, "Сяо Ха") равно 1029005403108185979 obj = ListNode(0) hash_obj = hash(obj) # Хеш-значение объекта узла равно 274267521 ``` === "C++" ```cpp title="built_in_hash.cpp" int num = 3; size_t hashNum = hash()(num); // Хеш-значение целого числа 3 равно 3 bool bol = true; size_t hashBol = hash()(bol); // Хеш-значение булевого значения 1 равно 1 double dec = 3.14159; size_t hashDec = hash()(dec); // Хеш-значение числа 3.14159 равно 4614256650576692846 string str = "Hello Algo"; size_t hashStr = hash()(str); // Хеш-значение строки "Hello Algo" равно 15466937326284535026 // В C++ встроенный std::hash() предоставляет вычисление хеша только для базовых типов данных // Для массивов и объектов хеш-значение обычно приходится реализовывать самостоятельно ``` === "Java" ```java title="built_in_hash.java" int num = 3; int hashNum = Integer.hashCode(num); // Хеш-значение целого числа 3 равно 3 boolean bol = true; int hashBol = Boolean.hashCode(bol); // Хеш-значение булевого значения true равно 1231 double dec = 3.14159; int hashDec = Double.hashCode(dec); // Хеш-значение числа 3.14159 равно -1340954729 String str = "Hello Algo"; int hashStr = str.hashCode(); // Хеш-значение строки "Hello Algo" равно -727081396 Object[] arr = { 12836, "Сяо Ха" }; int hashTup = Arrays.hashCode(arr); // Хеш-значение массива [12836, Сяо Ха] равно 1151158 ListNode obj = new ListNode(0); int hashObj = obj.hashCode(); // Хеш-значение объекта узла utils.ListNode@7dc5e7b4 равно 2110121908 ``` === "C#" ```csharp title="built_in_hash.cs" int num = 3; int hashNum = num.GetHashCode(); // Хеш-значение целого числа 3 равно 3; bool bol = true; int hashBol = bol.GetHashCode(); // Хеш-значение булевого значения true равно 1; double dec = 3.14159; int hashDec = dec.GetHashCode(); // Хеш-значение числа 3.14159 равно -1340954729; string str = "Hello Algo"; int hashStr = str.GetHashCode(); // Хеш-значение строки "Hello Algo" равно -586107568; object[] arr = [12836, "Сяо Ха"]; int hashTup = arr.GetHashCode(); // Хеш-значение массива [12836, Сяо Ха] равно 42931033; ListNode obj = new(0); int hashObj = obj.GetHashCode(); // Хеш-значение объекта узла 0 равно 39053774; ``` === "Go" ```go title="built_in_hash.go" // В Go нет встроенной функции hash code ``` === "Swift" ```swift title="built_in_hash.swift" let num = 3 let hashNum = num.hashValue // Хеш-значение целого числа 3 равно 9047044699613009734 let bol = true let hashBol = bol.hashValue // Хеш-значение булевого значения true равно -4431640247352757451 let dec = 3.14159 let hashDec = dec.hashValue // Хеш-значение числа 3.14159 равно -2465384235396674631 let str = "Hello Algo" let hashStr = str.hashValue // Хеш-значение строки "Hello Algo" равно -7850626797806988787 let arr = [AnyHashable(12836), AnyHashable("Сяо Ха")] let hashTup = arr.hashValue // Хеш-значение массива [AnyHashable(12836), AnyHashable("Сяо Ха")] равно -2308633508154532996 let obj = ListNode(x: 0) let hashObj = obj.hashValue // Хеш-значение объекта узла utils.ListNode равно -2434780518035996159 ``` === "JS" ```javascript title="built_in_hash.js" // В JavaScript нет встроенной функции hash code ``` === "TS" ```typescript title="built_in_hash.ts" // В TypeScript нет встроенной функции hash code ``` === "Dart" ```dart title="built_in_hash.dart" int num = 3; int hashNum = num.hashCode; // Хеш-значение целого числа 3 равно 34803 bool bol = true; int hashBol = bol.hashCode; // Хеш-значение булевого значения true равно 1231 double dec = 3.14159; int hashDec = dec.hashCode; // Хеш-значение числа 3.14159 равно 2570631074981783 String str = "Hello Algo"; int hashStr = str.hashCode; // Хеш-значение строки "Hello Algo" равно 468167534 List arr = [12836, "Сяо Ха"]; int hashArr = arr.hashCode; // Хеш-значение массива [12836, Сяо Ха] равно 976512528 ListNode obj = new ListNode(0); int hashObj = obj.hashCode; // Хеш-значение объекта Instance of 'ListNode' равно 1033450432 ``` === "Rust" ```rust title="built_in_hash.rs" use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; let num = 3; let mut num_hasher = DefaultHasher::new(); num.hash(&mut num_hasher); let hash_num = num_hasher.finish(); // Хеш-значение целого числа 3 равно 568126464209439262 let bol = true; let mut bol_hasher = DefaultHasher::new(); bol.hash(&mut bol_hasher); let hash_bol = bol_hasher.finish(); // Хеш-значение булевого значения true равно 4952851536318644461 let dec: f32 = 3.14159; let mut dec_hasher = DefaultHasher::new(); dec.to_bits().hash(&mut dec_hasher); let hash_dec = dec_hasher.finish(); // Хеш-значение числа 3.14159 равно 2566941990314602357 let str = "Hello Algo"; let mut str_hasher = DefaultHasher::new(); str.hash(&mut str_hasher); let hash_str = str_hasher.finish(); // Хеш-значение строки "Hello Algo" равно 16092673739211250988 let arr = (&12836, &"Сяо Ха"); let mut tup_hasher = DefaultHasher::new(); arr.hash(&mut tup_hasher); let hash_tup = tup_hasher.finish(); // Хеш-значение кортежа (12836, "Сяо Ха") равно 1885128010422702749 let node = ListNode::new(42); let mut hasher = DefaultHasher::new(); node.borrow().val.hash(&mut hasher); let hash = hasher.finish(); // Хеш-значение объекта RefCell { value: ListNode { val: 42, next: None } } равно 15387811073369036852 ``` === "C" ```c title="built_in_hash.c" // В C нет встроенной функции hash code ``` === "Kotlin" ```kotlin title="built_in_hash.kt" val num = 3 val hashNum = num.hashCode() // Хеш-значение целого числа 3 равно 3 val bol = true val hashBol = bol.hashCode() // Хеш-значение булевого значения true равно 1231 val dec = 3.14159 val hashDec = dec.hashCode() // Хеш-значение числа 3.14159 равно -1340954729 val str = "Hello Algo" val hashStr = str.hashCode() // Хеш-значение строки "Hello Algo" равно -727081396 val arr = arrayOf(12836, "Сяо Ха") val hashTup = arr.hashCode() // Хеш-значение массива [12836, Сяо Ха] равно 189568618 val obj = ListNode(0) val hashObj = obj.hashCode() // Хеш-значение объекта узла utils.ListNode@1d81eb93 равно 495053715 ``` === "Ruby" ```ruby title="built_in_hash.rb" num = 3 hash_num = num.hash # Хеш-значение целого числа 3 равно -4385856518450339636 bol = true hash_bol = bol.hash # Хеш-значение булевого значения true равно -1617938112149317027 dec = 3.14159 hash_dec = dec.hash # Хеш-значение числа 3.14159 равно -1479186995943067893 str = "Hello Algo" hash_str = str.hash # Хеш-значение строки "Hello Algo" равно -4075943250025831763 tup = [12836, 'Сяо Ха'] hash_tup = tup.hash # Хеш-значение кортежа (12836, 'Сяо Ха') равно 1999544809202288822 obj = ListNode.new(0) hash_obj = obj.hash # Хеш-значение объекта # равно 4302940560806366381 ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D1%8B%D0%B9%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%D1%83%D0%B7%D0%B5%D0%BB%D0%BA%D0%BB%D0%B0%D1%81%D1%81%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%86%D0%B5%D0%BB%D0%BE%D0%B3%D0%BE%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%203%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B1%D1%83%D0%BB%D0%B5%D0%B2%D0%B0%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20True%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%203.14159%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20Algo%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%22Hello%20Algo%22%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836%2C%20%22%D0%A1%D1%8F%D0%BE%20%D0%A5%D0%B0%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BA%D0%BE%D1%80%D1%82%D0%B5%D0%B6%D0%B0%20%2812836%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A5%D0%B0%27%29%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%B0%20%D1%83%D0%B7%D0%BB%D0%B0%20%3CListNode%20object%20at%200x1058fd810%3E%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false Во многих языках программирования **в качестве `key` хеш-таблицы можно использовать только неизменяемые объекты** . Если, например, использовать список (динамический массив) как `key` , то после изменения содержимого списка изменится и его хеш-значение, из-за чего мы уже не сможем найти прежнее `value` в хеш-таблице. Хотя у пользовательских объектов (например, у узла связного списка) поля являются изменяемыми, сам объект все же может быть хешируемым. **Причина в том, что хеш-значение объекта обычно строится на основе адреса в памяти** : даже если содержимое объекта меняется, его адрес памяти остается прежним, а значит, и хеш-значение не меняется. Внимательный читатель мог заметить, что при запуске программы в разных консолях выводимые хеш-значения отличаются. **Это связано с тем, что интерпретатор Python при каждом запуске добавляет в хеш-функцию строк случайную соль (salt)**. Такой подход эффективно защищает от атак типа HashDoS и повышает безопасность хеш-алгоритма. ================================================ FILE: ru/docs/chapter_hashing/hash_collision.md ================================================ # Хеш-коллизии Как уже говорилось в предыдущем разделе, **в обычных условиях входное пространство хеш-функции намного больше выходного пространства** , поэтому теоретически хеш-коллизии неизбежны. Например, если входное пространство состоит из всех целых чисел, а выходное пространство ограничено размером массива, то неизбежно несколько целых чисел будут отображаться в один и тот же индекс бакета. Хеш-коллизии могут приводить к ошибочным результатам поиска и серьезно влиять на работоспособность хеш-таблицы. Чтобы решить эту проблему, можно при каждом конфликте выполнять расширение хеш-таблицы, пока конфликт не исчезнет. Этот метод понятен и прост, но слишком неэффективен, потому что расширение хеш-таблицы требует большого объема переноса данных и вычислений хеш-значений. Чтобы повысить эффективность, можно использовать следующие стратегии. 1. Улучшить структуру данных хеш-таблицы, **чтобы она могла корректно работать даже при возникновении хеш-коллизий**. 2. Выполнять расширение только тогда, когда это действительно необходимо, то есть когда хеш-коллизии становятся достаточно серьезными. Основные способы улучшения структуры хеш-таблицы включают метод цепочек и открытую адресацию. ## Метод цепочек В исходной хеш-таблице каждый бакет может хранить только одну пару ключ-значение. Метод цепочек (separate chaining) превращает отдельный элемент в связный список: пары ключ-значение становятся узлами списка, и все конфликтующие пары ключ-значение хранятся в одном и том же списке. На рисунке ниже показан пример хеш-таблицы, реализованной методом цепочек. ![Хеш-таблица с методом цепочек](hash_collision.assets/hash_table_chaining.png) Методы работы с хеш-таблицей, построенной на основе метода цепочек, меняются следующим образом. - **Поиск элемента**: передаем `key` , по хеш-функции получаем индекс бакета, после чего обращаемся к голове списка и обходим список, сравнивая `key` , пока не найдем целевую пару ключ-значение. - **Добавление элемента**: сначала через хеш-функцию получаем голову списка, затем добавляем узел (пару ключ-значение) в этот список. - **Удаление элемента**: по результату хеш-функции обращаемся к голове списка, затем обходим список, находим целевой узел и удаляем его. Метод цепочек имеет следующие ограничения. - **Рост потребления памяти**: связный список содержит указатели на узлы, поэтому по сравнению с массивом он требует больше памяти. - **Снижение эффективности поиска**: для нахождения нужного элемента нужно линейно обходить связный список. Ниже приведена простая реализация хеш-таблицы методом цепочек. Следует обратить внимание на два момента. - Для упрощения кода вместо связного списка используется список (динамический массив). В этой реализации хеш-таблица (массив) содержит несколько бакетов, и каждый бакет представляет собой список. - Ниже включен метод расширения хеш-таблицы. Когда коэффициент загрузки превышает $\frac{2}{3}$ , мы расширяем хеш-таблицу до $2$ раз от прежней емкости. ```src [file]{hash_map_chaining}-[class]{hash_map_chaining}-[func]{} ``` Следует отметить, что когда связный список становится очень длинным, эффективность поиска $O(n)$ оказывается низкой. **В этом случае список можно преобразовать в AVL-дерево или красно-черное дерево** , чтобы оптимизировать временную сложность поиска до $O(\log n)$ . ## Открытая адресация Открытая адресация (open addressing) не вводит дополнительных структур данных, а обрабатывает хеш-коллизии с помощью многократного пробирования; основные варианты пробирования включают линейное пробирование, квадратичное пробирование и повторное хеширование. Ниже на примере линейного пробирования рассмотрим механизм работы хеш-таблицы с открытой адресацией. ### Линейное пробирование Линейное пробирование использует линейный поиск с фиксированным шагом. Его методы работы отличаются от обычной хеш-таблицы. - **Вставка элемента**: по хеш-функции вычисляется индекс бакета; если бакет уже занят, то от места конфликта выполняется линейный обход вперед (шаг обычно равен $1$ ), пока не будет найден пустой бакет, после чего элемент вставляется туда. - **Поиск элемента**: если возник конфликт, то с тем же шагом продолжается линейный обход вперед, пока не будет найден целевой элемент и возвращено `value` ; если встречается пустой бакет, это означает, что искомого элемента в хеш-таблице нет, и возвращается `None` . На рисунке ниже показано распределение пар ключ-значение в хеш-таблице с открытой адресацией (линейное пробирование). Для этой хеш-функции все `key` с одинаковыми двумя последними цифрами отображаются в один и тот же бакет. Благодаря линейному пробированию они по очереди сохраняются в этом бакете и в следующих за ним бакетах. ![Распределение пар ключ-значение в хеш-таблице с открытой адресацией (линейное пробирование)](hash_collision.assets/hash_table_linear_probing.png) Однако **линейное пробирование легко приводит к кластеризации**. Иначе говоря, чем длиннее непрерывная занятая область в массиве, тем выше вероятность новых коллизий в этой области, что еще сильнее способствует росту этой группы и в итоге ухудшает эффективность операций добавления, удаления, поиска и обновления. Стоит заметить, что **мы не можем напрямую удалять элементы из хеш-таблицы с открытой адресацией**. Причина в том, что удаление создаст внутри массива пустой бакет `None` , а при поиске элемента линейное пробирование остановится на этом пустом бакете и вернет результат, из-за чего элементы ниже этого бакета уже не смогут быть найдены, и программа может ошибочно посчитать, что их не существует, как показано на рисунке ниже. ![Проблема поиска после удаления элемента в открытой адресации](hash_collision.assets/hash_table_open_addressing_deletion.png) Чтобы решить эту проблему, можно использовать механизм ленивого удаления (lazy deletion): он не удаляет элемент из хеш-таблицы напрямую, **а помечает этот бакет специальной константой `TOMBSTONE` **. В этом механизме и `None` , и `TOMBSTONE` означают пустой бакет, и оба могут быть использованы для размещения пары ключ-значение. Но есть важное различие: при линейном пробировании, встретив `TOMBSTONE` , нужно продолжать обход, потому что ниже него все еще могут существовать пары ключ-значение. Однако **ленивое удаление может ускорять деградацию производительности хеш-таблицы**. Это связано с тем, что каждая операция удаления создает новую метку удаления; по мере роста числа `TOMBSTONE` время поиска тоже увеличивается, потому что линейное пробирование может быть вынуждено перескакивать через множество `TOMBSTONE` , прежде чем найдет целевой элемент. Поэтому имеет смысл при линейном пробировании запоминать индекс первого встреченного `TOMBSTONE` и затем менять найденный целевой элемент местами с этим `TOMBSTONE` . Преимущество такого подхода в том, что при каждом поиске или добавлении элемент будет перемещаться в бакет, расположенный ближе к его идеальной позиции (начальной точке пробирования), а значит, эффективность поиска улучшится. Ниже приведена реализация хеш-таблицы с открытой адресацией, то есть с линейным пробированием, включающая ленивое удаление. Чтобы пространство хеш-таблицы использовалось более полно, мы рассматриваем ее как кольцевой массив: когда обход выходит за конец массива, он возвращается к началу и продолжается. ```src [file]{hash_map_open_addressing}-[class]{hash_map_open_addressing}-[func]{} ``` ### Квадратичное пробирование Квадратичное пробирование похоже на линейное пробирование и тоже является одной из распространенных стратегий открытой адресации. При возникновении конфликта оно не пропускает фиксированное число шагов, а переходит на расстояние, равное "квадрату числа попыток", то есть на $1, 4, 9, \dots$ шагов. Квадратичное пробирование имеет следующие основные преимущества. - Квадратичное пробирование пытается смягчить эффект кластеризации линейного пробирования, так как пропускает расстояния, равные квадрату номера попытки. - Квадратичное пробирование перепрыгивает на более дальние позиции в поисках свободного места, что помогает распределять данные более равномерно. Однако квадратичное пробирование не является идеальным. - Кластеризация все равно существует: некоторые позиции по-прежнему занимают чаще других. - Из-за быстрого роста квадрата квадратичное пробирование может не охватить всю хеш-таблицу, а это означает, что даже при наличии пустых бакетов оно может так до них и не добраться. ### Повторное хеширование Как видно из названия, метод повторного хеширования использует для пробирования несколько хеш-функций $f_1(x)$, $f_2(x)$, $f_3(x)$, $\dots$ . - **Вставка элемента**: если хеш-функция $f_1(x)$ вызывает конфликт, то пробуем $f_2(x)$ , и так далее, пока не будет найдено пустое место для вставки элемента. - **Поиск элемента**: поиск идет в том же порядке хеш-функций, пока не будет найден целевой элемент; если встречается пустая позиция или уже были опробованы все хеш-функции, это означает, что элемента в хеш-таблице нет, и возвращается `None` . По сравнению с линейным пробированием метод повторного хеширования меньше подвержен кластеризации, но несколько хеш-функций приносят дополнительные вычислительные затраты. !!! tip Обрати внимание: у хеш-таблиц с открытой адресацией (линейное пробирование, квадратичное пробирование и повторное хеширование) есть общая проблема: в них нельзя напрямую удалять элементы. ## Выбор в языках программирования Разные языки программирования используют разные стратегии реализации хеш-таблиц. Ниже приведено несколько примеров. - Python использует открытую адресацию. В словаре `dict` для пробирования применяются псевдослучайные числа. - Java использует метод цепочек. Начиная с JDK 1.8, когда длина массива внутри `HashMap` достигает 64, а длина списка достигает 8, этот список преобразуется в красно-черное дерево для повышения производительности поиска. - Go использует метод цепочек. В Go установлено, что каждый бакет может хранить не более 8 пар ключ-значение; при переполнении подключается overflow-бакет, а когда таких бакетов становится слишком много, выполняется специальное расширение того же масштаба, чтобы сохранить производительность. ================================================ FILE: ru/docs/chapter_hashing/hash_map.md ================================================ # Хеш-таблица Хеш-таблица (hash table), также называемая таблицей рассеяния, реализует эффективный поиск элементов за счет установления соответствия между ключом `key` и значением `value` . Иначе говоря, если передать в хеш-таблицу ключ `key` , то можно за $O(1)$ времени получить соответствующее значение `value` . Как показано на рисунке ниже, пусть есть $n$ студентов, и у каждого из них есть два поля данных: имя и номер студенческого билета. Если мы хотим реализовать запрос вида "ввести номер студенческого билета и вернуть соответствующее имя", то для этого можно использовать показанную ниже хеш-таблицу. ![Абстрактное представление хеш-таблицы](hash_map.assets/hash_table_lookup.png) Помимо хеш-таблицы, функцией поиска также обладают массив и связный список. Сравнение их эффективности приведено в таблице ниже. - **Добавление элемента**: нужно лишь добавить элемент в конец массива (или списка), что занимает $O(1)$ времени. - **Поиск элемента**: так как массив (или список) неупорядочен, приходится обходить все элементы, что занимает $O(n)$ времени. - **Удаление элемента**: сначала нужно найти элемент, затем удалить его из массива (или списка), что занимает $O(n)$ времени.

Таблица   Сравнение эффективности поиска элементов

| | Массив | Связный список | Хеш-таблица | | -------- | ------ | -------------- | ----------- | | Поиск элемента | $O(n)$ | $O(n)$ | $O(1)$ | | Добавление элемента | $O(1)$ | $O(1)$ | $O(1)$ | | Удаление элемента | $O(n)$ | $O(n)$ | $O(1)$ | Нетрудно заметить, что **операции поиска, добавления и удаления в хеш-таблице имеют временную сложность $O(1)$** , то есть выполняются очень эффективно. ## Основные операции с хеш-таблицей К базовым операциям хеш-таблицы относятся инициализация, поиск, добавление пар ключ-значение и удаление пар ключ-значение. Пример кода приведен ниже: === "Python" ```python title="hash_map.py" # Инициализация хеш-таблицы hmap: dict = {} # Операция добавления # Добавить пару ключ-значение (key, value) в хеш-таблицу hmap[12836] = "Сяо Ха" hmap[15937] = "Сяо Ло" hmap[16750] = "Сяо Суань" hmap[13276] = "Сяо Фа" hmap[10583] = "Сяо Я" # Операция поиска # Передать в хеш-таблицу ключ key и получить значение value name: str = hmap[15937] # Операция удаления # Удалить пару ключ-значение (key, value) из хеш-таблицы hmap.pop(10583) ``` === "C++" ```cpp title="hash_map.cpp" /* Инициализация хеш-таблицы */ unordered_map map; /* Операция добавления */ // Добавить пару ключ-значение (key, value) в хеш-таблицу map[12836] = "Сяо Ха"; map[15937] = "Сяо Ло"; map[16750] = "Сяо Суань"; map[13276] = "Сяо Фа"; map[10583] = "Сяо Я"; /* Операция поиска */ // Передать в хеш-таблицу ключ key и получить значение value string name = map[15937]; /* Операция удаления */ // Удалить пару ключ-значение (key, value) из хеш-таблицы map.erase(10583); ``` === "Java" ```java title="hash_map.java" /* Инициализация хеш-таблицы */ Map map = new HashMap<>(); /* Операция добавления */ // Добавить пару ключ-значение (key, value) в хеш-таблицу map.put(12836, "Сяо Ха"); map.put(15937, "Сяо Ло"); map.put(16750, "Сяо Суань"); map.put(13276, "Сяо Фа"); map.put(10583, "Сяо Я"); /* Операция поиска */ // Передать в хеш-таблицу ключ key и получить значение value String name = map.get(15937); /* Операция удаления */ // Удалить пару ключ-значение (key, value) из хеш-таблицы map.remove(10583); ``` === "C#" ```csharp title="hash_map.cs" /* Инициализация хеш-таблицы */ Dictionary map = new() { /* Операция добавления */ // Добавить пару ключ-значение (key, value) в хеш-таблицу { 12836, "Сяо Ха" }, { 15937, "Сяо Ло" }, { 16750, "Сяо Суань" }, { 13276, "Сяо Фа" }, { 10583, "Сяо Я" } }; /* Операция поиска */ // Передать в хеш-таблицу ключ key и получить значение value string name = map[15937]; /* Операция удаления */ // Удалить пару ключ-значение (key, value) из хеш-таблицы map.Remove(10583); ``` === "Go" ```go title="hash_map_test.go" /* Инициализация хеш-таблицы */ hmap := make(map[int]string) /* Операция добавления */ // Добавить пару ключ-значение (key, value) в хеш-таблицу hmap[12836] = "Сяо Ха" hmap[15937] = "Сяо Ло" hmap[16750] = "Сяо Суань" hmap[13276] = "Сяо Фа" hmap[10583] = "Сяо Я" /* Операция поиска */ // Передать в хеш-таблицу ключ key и получить значение value name := hmap[15937] /* Операция удаления */ // Удалить пару ключ-значение (key, value) из хеш-таблицы delete(hmap, 10583) ``` === "Swift" ```swift title="hash_map.swift" /* Инициализация хеш-таблицы */ var map: [Int: String] = [:] /* Операция добавления */ // Добавить пару ключ-значение (key, value) в хеш-таблицу map[12836] = "Сяо Ха" map[15937] = "Сяо Ло" map[16750] = "Сяо Суань" map[13276] = "Сяо Фа" map[10583] = "Сяо Я" /* Операция поиска */ // Передать в хеш-таблицу ключ key и получить значение value let name = map[15937]! /* Операция удаления */ // Удалить пару ключ-значение (key, value) из хеш-таблицы map.removeValue(forKey: 10583) ``` === "JS" ```javascript title="hash_map.js" /* Инициализация хеш-таблицы */ const map = new Map(); /* Операция добавления */ // Добавить пару ключ-значение (key, value) в хеш-таблицу map.set(12836, 'Сяо Ха'); map.set(15937, 'Сяо Ло'); map.set(16750, 'Сяо Суань'); map.set(13276, 'Сяо Фа'); map.set(10583, 'Сяо Я'); /* Операция поиска */ // Передать в хеш-таблицу ключ key и получить значение value let name = map.get(15937); /* Операция удаления */ // Удалить пару ключ-значение (key, value) из хеш-таблицы map.delete(10583); ``` === "TS" ```typescript title="hash_map.ts" /* Инициализация хеш-таблицы */ const map = new Map(); /* Операция добавления */ // Добавить пару ключ-значение (key, value) в хеш-таблицу map.set(12836, 'Сяо Ха'); map.set(15937, 'Сяо Ло'); map.set(16750, 'Сяо Суань'); map.set(13276, 'Сяо Фа'); map.set(10583, 'Сяо Я'); console.info('\nПосле добавления хеш-таблица имеет вид\nKey -> Value'); console.info(map); /* Операция поиска */ // Передать в хеш-таблицу ключ key и получить значение value let name = map.get(15937); console.info('\nПо номеру 15937 найдено имя ' + name); /* Операция удаления */ // Удалить пару ключ-значение (key, value) из хеш-таблицы map.delete(10583); console.info('\nПосле удаления 10583 хеш-таблица имеет вид\nKey -> Value'); console.info(map); ``` === "Dart" ```dart title="hash_map.dart" /* Инициализация хеш-таблицы */ Map map = {}; /* Операция добавления */ // Добавить пару ключ-значение (key, value) в хеш-таблицу map[12836] = "Сяо Ха"; map[15937] = "Сяо Ло"; map[16750] = "Сяо Суань"; map[13276] = "Сяо Фа"; map[10583] = "Сяо Я"; /* Операция поиска */ // Передать в хеш-таблицу ключ key и получить значение value String name = map[15937]; /* Операция удаления */ // Удалить пару ключ-значение (key, value) из хеш-таблицы map.remove(10583); ``` === "Rust" ```rust title="hash_map.rs" use std::collections::HashMap; /* Инициализация хеш-таблицы */ let mut map: HashMap = HashMap::new(); /* Операция добавления */ // Добавить пару ключ-значение (key, value) в хеш-таблицу map.insert(12836, "Сяо Ха".to_string()); map.insert(15937, "Сяо Ло".to_string()); map.insert(16750, "Сяо Суань".to_string()); map.insert(13279, "Сяо Фа".to_string()); map.insert(10583, "Сяо Я".to_string()); /* Операция поиска */ // Передать в хеш-таблицу ключ key и получить значение value let _name: Option<&String> = map.get(&15937); /* Операция удаления */ // Удалить пару ключ-значение (key, value) из хеш-таблицы let _removed_value: Option = map.remove(&10583); ``` === "C" ```c title="hash_map.c" // В C нет встроенной хеш-таблицы ``` === "Kotlin" ```kotlin title="hash_map.kt" /* Инициализация хеш-таблицы */ val map = HashMap() /* Операция добавления */ // Добавить пару ключ-значение (key, value) в хеш-таблицу map[12836] = "Сяо Ха" map[15937] = "Сяо Ло" map[16750] = "Сяо Суань" map[13276] = "Сяо Фа" map[10583] = "Сяо Я" /* Операция поиска */ // Передать в хеш-таблицу ключ key и получить значение value val name = map[15937] /* Операция удаления */ // Удалить пару ключ-значение (key, value) из хеш-таблицы map.remove(10583) ``` === "Ruby" ```ruby title="hash_map.rb" # Инициализация хеш-таблицы hmap = {} # Операция добавления # Добавить пару ключ-значение (key, value) в хеш-таблицу hmap[12836] = "Сяо Ха" hmap[15937] = "Сяо Ло" hmap[16750] = "Сяо Суань" hmap[13276] = "Сяо Фа" hmap[10583] = "Сяо Я" # Операция поиска # Передать в хеш-таблицу ключ key и получить значение value name = hmap[15937] # Операция удаления # Удалить пару ключ-значение (key, value) из хеш-таблицы hmap.delete(10583) ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%20%D0%BF%D0%B0%D1%80%D1%83%20%D0%BA%D0%BB%D1%8E%D1%87-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%28key%2C%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A5%D0%B0%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%9B%D0%BE%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A1%D1%83%D0%B0%D0%BD%D1%8C%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A4%D0%B0%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%D0%A3%D1%82%D0%B5%D0%BD%D0%BE%D0%BA%22%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B4%D0%B0%D1%82%D1%8C%20%D0%BA%D0%BB%D1%8E%D1%87%20key%20%D0%B2%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%20%D0%B8%20%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%B7%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20%D0%BF%D0%B0%D1%80%D1%83%20%D0%BA%D0%BB%D1%8E%D1%87-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%28key%2C%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false Существует три распространенных способа обхода хеш-таблицы: обход пар ключ-значение, обход ключей и обход значений. Примеры кода приведены ниже: === "Python" ```python title="hash_map.py" # Обход хеш-таблицы # Обход пар ключ-значение key->value for key, value in hmap.items(): print(key, "->", value) # Обход только ключей key for key in hmap.keys(): print(key) # Обход только значений value for value in hmap.values(): print(value) ``` === "C++" ```cpp title="hash_map.cpp" /* Обход хеш-таблицы */ // Обход пар ключ-значение key->value for (auto kv: map) { cout << kv.first << " -> " << kv.second << endl; } // Обход key->value с помощью итератора for (auto iter = map.begin(); iter != map.end(); iter++) { cout << iter->first << "->" << iter->second << endl; } ``` === "Java" ```java title="hash_map.java" /* Обход хеш-таблицы */ // Обход пар ключ-значение key->value for (Map.Entry kv: map.entrySet()) { System.out.println(kv.getKey() + " -> " + kv.getValue()); } // Обход только ключей key for (int key: map.keySet()) { System.out.println(key); } // Обход только значений value for (String val: map.values()) { System.out.println(val); } ``` === "C#" ```csharp title="hash_map.cs" /* Обход хеш-таблицы */ // Обход пар ключ-значение Key->Value foreach (var kv in map) { Console.WriteLine(kv.Key + " -> " + kv.Value); } // Обход только ключей key foreach (int key in map.Keys) { Console.WriteLine(key); } // Обход только значений value foreach (string val in map.Values) { Console.WriteLine(val); } ``` === "Go" ```go title="hash_map_test.go" /* Обход хеш-таблицы */ // Обход пар ключ-значение key->value for key, value := range hmap { fmt.Println(key, "->", value) } // Обход только ключей key for key := range hmap { fmt.Println(key) } // Обход только значений value for _, value := range hmap { fmt.Println(value) } ``` === "Swift" ```swift title="hash_map.swift" /* Обход хеш-таблицы */ // Обход пар ключ-значение Key->Value for (key, value) in map { print("\(key) -> \(value)") } // Обход только ключей Key for key in map.keys { print(key) } // Обход только значений Value for value in map.values { print(value) } ``` === "JS" ```javascript title="hash_map.js" /* Обход хеш-таблицы */ console.info('\nОбход пар ключ-значение Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\nОбход только ключей Key'); for (const k of map.keys()) { console.info(k); } console.info('\nОбход только значений Value'); for (const v of map.values()) { console.info(v); } ``` === "TS" ```typescript title="hash_map.ts" /* Обход хеш-таблицы */ console.info('\nОбход пар ключ-значение Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\nОбход только ключей Key'); for (const k of map.keys()) { console.info(k); } console.info('\nОбход только значений Value'); for (const v of map.values()) { console.info(v); } ``` === "Dart" ```dart title="hash_map.dart" /* Обход хеш-таблицы */ // Обход пар ключ-значение Key->Value map.forEach((key, value) { print('$key -> $value'); }); // Обход только ключей Key map.keys.forEach((key) { print(key); }); // Обход только значений Value map.values.forEach((value) { print(value); }); ``` === "Rust" ```rust title="hash_map.rs" /* Обход хеш-таблицы */ // Обход пар ключ-значение Key->Value for (key, value) in &map { println!("{key} -> {value}"); } // Обход только ключей Key for key in map.keys() { println!("{key}"); } // Обход только значений Value for value in map.values() { println!("{value}"); } ``` === "C" ```c title="hash_map.c" // В C нет встроенной хеш-таблицы ``` === "Kotlin" ```kotlin title="hash_map.kt" /* Обход хеш-таблицы */ // Обход пар ключ-значение key->value for ((key, value) in map) { println("$key -> $value") } // Обход только ключей key for (key in map.keys) { println(key) } // Обход только значений value for (_val in map.values) { println(_val) } ``` === "Ruby" ```ruby title="hash_map.rb" # Обход хеш-таблицы # Обход пар ключ-значение key->value hmap.entries.each { |key, value| puts "#{key} -> #{value}" } # Обход только ключей key hmap.keys.each { |key| puts key } # Обход только значений value hmap.values.each { |val| puts val } ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%20%D0%BF%D0%B0%D1%80%D1%83%20%D0%BA%D0%BB%D1%8E%D1%87-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%28key%2C%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A5%D0%B0%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%9B%D0%BE%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A1%D1%83%D0%B0%D0%BD%D1%8C%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A4%D0%B0%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%D0%A3%D1%82%D0%B5%D0%BD%D0%BE%D0%BA%22%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%0A%20%20%20%20%23%20%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%D0%BF%D0%B0%D1%80%D0%B0%20%D0%BA%D0%BB%D1%8E%D1%87-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20key-%3Evalue%0A%20%20%20%20for%20key%2C%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%2C%20%22-%3E%22%2C%20value%29%0A%20%20%20%20%23%20%D0%BE%D1%82%D0%B4%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%D0%BA%D0%BB%D1%8E%D1%87%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%D0%BE%D1%82%D0%B4%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## Простая реализация хеш-таблицы Сначала рассмотрим самый простой случай: **реализуем хеш-таблицу только с помощью одного массива**. В хеш-таблице каждую пустую ячейку массива мы называем бакетом (bucket), и каждый бакет может хранить одну пару ключ-значение. Следовательно, операция поиска сводится к тому, чтобы найти бакет, соответствующий `key` , и получить из него `value` . Но как определить бакет, соответствующий заданному `key` ? Это делается с помощью хеш-функции (hash function). Назначение хеш-функции - отображать большое входное пространство в меньшее выходное пространство. В хеш-таблице входным пространством являются все `key` , а выходным - все бакеты, то есть индексы массива. Иначе говоря, передав `key` на вход, **мы можем с помощью хеш-функции получить позицию хранения соответствующей пары ключ-значение в массиве**. Процесс вычисления хеш-функции для одного `key` включает два шага. 1. Сначала с помощью некоторого хеш-алгоритма `hash()` вычисляется хеш-значение. 2. Затем хеш-значение берется по модулю числа бакетов (длины массива) `capacity` , чтобы получить бакет (индекс массива) `index` , соответствующий этому `key` . ```shell index = hash(key) % capacity ``` После этого можно использовать `index` для доступа к соответствующему бакету в хеш-таблице и получения `value` . Пусть длина массива `capacity = 100` , а хеш-алгоритм `hash(key) = key` . Тогда легко получить хеш-функцию `key % 100` . На рисунке ниже на примере `key` "номер студенческого билета" и `value` "имя" показан принцип работы хеш-функции. ![Принцип работы хеш-функции](hash_map.assets/hash_function.png) Ниже приведен код простой реализации хеш-таблицы. В нем мы инкапсулируем `key` и `value` в класс `Pair` , чтобы представить пару ключ-значение. ```src [file]{array_hash_map}-[class]{array_hash_map}-[func]{} ``` ## Хеш-коллизии и расширение По сути, хеш-функция отображает входное пространство, состоящее из всех `key` , в выходное пространство, состоящее из всех индексов массива, а входное пространство обычно значительно больше выходного. Поэтому **теоретически неизбежно существование ситуации "несколько входов соответствуют одному выходу"**. Для хеш-функции из приведенного выше примера, если последние две цифры `key` совпадают, то совпадает и результат хеш-функции. Например, если искать студентов с номерами 12836 и 20336, то получим: ```shell 12836 % 100 = 36 20336 % 100 = 36 ``` Как показано на рисунке ниже, два номера указывают на одно и то же имя, что, очевидно, неверно. Такую ситуацию, когда нескольким входам соответствует один и тот же выход, называют хеш-коллизией (hash collision). ![Пример хеш-коллизии](hash_map.assets/hash_collision.png) Легко понять, что чем больше емкость хеш-таблицы $n$ , тем ниже вероятность того, что несколько `key` попадут в один и тот же бакет, а значит, тем меньше коллизий. Поэтому **мы можем уменьшать число хеш-коллизий путем расширения хеш-таблицы**. Как показано на рисунке ниже, до расширения пары ключ-значение `(136, A)` и `(236, D)` конфликтовали, а после расширения коллизия исчезла. ![Расширение хеш-таблицы](hash_map.assets/hash_table_reshash.png) Подобно расширению массива, расширение хеш-таблицы требует перенести все пары ключ-значение из старой таблицы в новую, а это очень затратно по времени; кроме того, поскольку емкость хеш-таблицы `capacity` изменилась, нам приходится с помощью хеш-функции заново вычислять позиции хранения всех пар ключ-значение, что дополнительно увеличивает вычислительные расходы процесса расширения. Поэтому языки программирования обычно заранее резервируют достаточно большую емкость хеш-таблицы, чтобы избежать частых расширений. Коэффициент загрузки (load factor) - важное понятие хеш-таблицы. Он определяется как отношение числа элементов в хеш-таблице к числу бакетов и используется для оценки степени серьезности хеш-коллизий, **а также часто служит условием срабатывания расширения хеш-таблицы**. Например, в Java, когда коэффициент загрузки превышает $0.75$ , система расширяет хеш-таблицу до $2$ раз от исходной емкости. ================================================ FILE: ru/docs/chapter_hashing/index.md ================================================ # Хеш-таблицы ![Хеш-таблицы](../assets/covers/chapter_hashing.jpg) !!! abstract Хеш-таблица устанавливает соответствие между ключом и значением. Благодаря этому она позволяет получать нужное значение по ключу за очень короткое время. ================================================ FILE: ru/docs/chapter_hashing/summary.md ================================================ # Резюме ### Ключевые выводы - Передав `key` , мы можем получить `value` из хеш-таблицы за $O(1)$ времени, поэтому она очень эффективна. - К типичным операциям хеш-таблицы относятся поиск, добавление пары ключ-значение, удаление пары ключ-значение и обход хеш-таблицы. - Хеш-функция отображает `key` в индекс массива, после чего можно обратиться к соответствующему бакету и получить `value` . - Два разных `key` после хеш-функции могут дать один и тот же индекс массива, что приводит к ошибочному результату поиска; это явление называется хеш-коллизией. - Чем больше емкость хеш-таблицы, тем ниже вероятность хеш-коллизий. Поэтому хеш-коллизии можно смягчать путем расширения хеш-таблицы. Как и у массива, операция расширения у хеш-таблицы очень затратна. - Коэффициент загрузки определяется как отношение числа элементов в хеш-таблице к числу бакетов, отражает степень серьезности хеш-коллизий и часто используется как условие запуска расширения хеш-таблицы. - Метод цепочек превращает одиночный элемент в связный список и хранит все конфликтующие элементы в одном списке. Однако слишком длинный список снижает эффективность поиска, поэтому его можно дополнительно преобразовать в красно-черное дерево. - Открытая адресация обрабатывает хеш-коллизии за счет многократного пробирования. Линейное пробирование использует фиксированный шаг, его недостатки - невозможность прямого удаления элементов и склонность к кластеризации. Повторное хеширование использует несколько хеш-функций и по сравнению с линейным пробированием меньше подвержено кластеризации, но требует больше вычислений. - Разные языки программирования выбирают разные стратегии реализации хеш-таблиц. Например, `HashMap` в Java использует метод цепочек, а `Dict` в Python - открытую адресацию. - Для хеш-таблицы желательно, чтобы хеш-алгоритм был детерминированным, быстрым и обеспечивал равномерное распределение. В криптографии от него дополнительно требуют устойчивости к коллизиям и эффекта лавины. - В качестве модуля хеш-алгоритмы обычно используют большое простое число, чтобы максимально обеспечить равномерность распределения хеш-значений и снизить число хеш-коллизий. - К распространенным хеш-алгоритмам относятся MD5, SHA-1, SHA-2 и SHA-3. MD5 часто применяли для проверки целостности файлов, а SHA-2 широко используется в протоколах и приложениях, связанных с безопасностью. - Языки программирования обычно предоставляют для типов данных встроенные хеш-алгоритмы, чтобы вычислять индексы бакетов в хеш-таблице. Как правило, хешируемыми могут быть только неизменяемые объекты. ### Q & A **Q**: В каких случаях временная сложность хеш-таблицы становится $O(n)$ ? Когда хеш-коллизии становятся достаточно серьезными, временная сложность хеш-таблицы деградирует до $O(n)$ . Если хеш-функция спроектирована хорошо, емкость выбрана разумно, а конфликты распределены достаточно равномерно, то временная сложность обычно считается $O(1)$ . При использовании встроенной хеш-таблицы языка программирования мы, как правило, и принимаем ее за $O(1)$ . **Q**: Почему бы не использовать хеш-функцию $f(x) = x$ ? Тогда ведь коллизий не будет. При хеш-функции $f(x) = x$ каждому элементу соответствует уникальный индекс бакета, и такая структура становится эквивалентна массиву. Однако входное пространство обычно намного больше выходного пространства (длины массива), поэтому последним шагом хеш-функции обычно выступает взятие по модулю длины массива. Иначе говоря, цель хеш-таблицы состоит в том, чтобы отобразить большее пространство состояний в меньшее пространство и при этом обеспечить $O(1)$ поиска. **Q**: В основе хеш-таблицы лежат массив, связный список и двоичное дерево. Почему же она может быть быстрее них? Во-первых, у хеш-таблицы повышается временная эффективность, но снижается пространственная эффективность. Значительная часть ее памяти остается неиспользованной. Во-вторых, она быстрее только в определенных сценариях. Если одну и ту же задачу можно реализовать на массиве или связном списке с той же асимптотикой, то часто такая реализация окажется быстрее, чем хеш-таблица. Причина в том, что вычисление хеш-функции само по себе стоит времени, то есть константа в сложности получается выше. Наконец, временная сложность хеш-таблицы тоже может деградировать. Например, при методе цепочек мы все равно выполняем поиск в связном списке или красно-черном дереве, поэтому риск деградации до $O(n)$ сохраняется. **Q**: Есть ли у повторного хеширования недостаток "нельзя напрямую удалять элементы"? Можно ли повторно использовать место, помеченное как удаленное? Повторное хеширование - это разновидность открытой адресации, а у всех методов открытой адресации есть недостаток: элементы нельзя удалять напрямую, поэтому приходится использовать метку удаления. Пространство, помеченное как удаленное, можно использовать повторно. Когда новый элемент вставляется в хеш-таблицу и в процессе пробирования попадает на такую отмеченную позицию, эта позиция может быть занята новым элементом. Такой подход сохраняет последовательность пробирования и одновременно поддерживает приемлемую эффективность использования памяти. **Q**: Почему при линейном пробировании во время поиска элемента вообще возникает хеш-коллизия? Во время поиска мы через хеш-функцию находим соответствующий бакет и соответствующую пару ключ-значение, но видим, что `key` не совпадает, а это и означает наличие хеш-коллизии. Поэтому метод линейного пробирования в соответствии с заранее заданным шагом последовательно движется дальше, пока не найдет правильную пару ключ-значение или не убедится, что поиск завершился неудачей. **Q**: Почему расширение хеш-таблицы помогает смягчать хеш-коллизии? Последний шаг хеш-функции обычно состоит во взятии по модулю длины массива $n$ , чтобы результат попадал в диапазон индексов массива; после расширения длина массива $n$ меняется, а значит, может измениться и индекс, соответствующий данному `key` . Несколько `key` , которые раньше попадали в один бакет, после расширения могут распределиться по нескольким бакетам, и тем самым хеш-коллизии будут ослаблены. **Q**: Если нам нужен быстрый доступ, почему бы просто не использовать массив? Когда `key` данных - это непрерывные целые числа из маленького диапазона, действительно можно напрямую использовать массив: это просто и эффективно. Но если `key` имеют другой тип данных (например, строки), тогда нужен хеш-алгоритм, который отобразит `key` в индекс массива, а хранение элементов будет выполняться через массив бакетов. Такая структура и называется хеш-таблицей. ================================================ FILE: ru/docs/chapter_heap/build_heap.md ================================================ # Построение кучи В некоторых случаях требуется построить кучу, используя сразу все элементы списка. Этот процесс называется построением кучи. ## Реализация через операцию добавления в кучу Сначала мы создаем пустую кучу, затем обходим список и для каждого элемента по очереди выполняем операцию добавления в кучу: сначала помещаем элемент в хвост кучи, а затем выполняем для него упорядочивание снизу вверх. Каждый раз, когда элемент добавляется в кучу, ее длина увеличивается на единицу. Поскольку узлы последовательно добавляются в двоичное дерево сверху вниз, куча строится сверху вниз. Пусть число элементов равно $n$ ; так как каждая операция добавления требует $O(\log{n})$ времени, временная сложность такого построения кучи составляет $O(n \log n)$ . ## Реализация через обход и упорядочивание На самом деле можно реализовать и более эффективный способ построения кучи, который состоит из двух шагов. 1. Без изменений добавить все элементы списка в кучу; в этот момент свойства кучи еще не выполняются. 2. Обойти кучу в обратном порядке, то есть в порядке, обратном обходу по уровням, и по очереди выполнить упорядочивание сверху вниз для каждого нелистового узла. **После того как некоторый узел был упорядочен, поддерево с этим узлом в качестве корня становится корректной подкучей**. А поскольку обход выполняется в обратном порядке, куча строится снизу вверх. Причина выбора обратного обхода в том, что он гарантирует: поддеревья ниже текущего узла уже являются корректными подкучами, а значит, упорядочивание текущего узла действительно будет эффективным. Стоит отметить, что **листовые узлы не имеют дочерних узлов, поэтому они естественным образом являются корректными подкучами и не требуют упорядочивания**. Как показано в коде ниже, последний нелистовой узел является родителем последнего узла, и именно с него мы начинаем обратный обход и упорядочивание: ```src [file]{my_heap}-[class]{max_heap}-[func]{__init__} ``` ## Анализ сложности Теперь попробуем оценить временную сложность второго способа построения кучи. - Пусть число узлов полного двоичного дерева равно $n$ , тогда число листовых узлов равно $(n + 1) / 2$ , где $/$ означает целочисленное деление вниз. Следовательно, число узлов, которые нужно упорядочивать, равно $(n - 1) / 2$ . - В процессе упорядочивания сверху вниз каждый узел в худшем случае может просеяться до листа, поэтому максимальное число итераций равно высоте двоичного дерева $\log n$ . Перемножив эти два значения, можно получить временную сложность построения кучи $O(n \log n)$ . **Но эта оценка неточна, потому что мы не учли свойство двоичного дерева: на нижних уровнях узлов гораздо больше, чем на верхних**. Далее выполним более точный расчет. Чтобы упростить вычисления, предположим, что дано "идеальное двоичное дерево" высоты $h$ с числом узлов $n$ ; это предположение не повлияет на корректность результата. ![Число узлов на каждом уровне идеального двоичного дерева](build_heap.assets/heapify_operations_count.png) Как показано на рисунке выше, максимальное число итераций упорядочивания сверху вниз для некоторого узла равно расстоянию от этого узла до листового узла, а это расстояние как раз и есть высота узла. Поэтому мы можем просуммировать для каждого уровня выражение "число узлов $\times$ высота узла" и **получить суммарное число итераций упорядочивания для всех узлов**. $$ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1 $$ Чтобы упростить это выражение, воспользуемся школьными знаниями о последовательностях и сначала умножим $T(h)$ на $2$ : $$ \begin{aligned} T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline 2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^{h}\times1 \newline \end{aligned} $$ Используя метод вычитания со сдвигом, вычтем из нижней строки $2 T(h)$ верхнюю строку $T(h)$ , тогда получим: $$ 2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h $$ Из этого выражения видно, что $T(h)$ представляет собой геометрическую прогрессию, поэтому можно напрямую применить формулу суммы и получить временную сложность: $$ \begin{aligned} T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline & = 2^{h+1} - h - 2 \newline & = O(2^h) \end{aligned} $$ Далее, число узлов идеального двоичного дерева высоты $h$ равно $n = 2^{h+1} - 1$ , поэтому несложно получить сложность $O(2^h) = O(n)$ . Из этого вывода следует, что **построение кучи из входного списка имеет временную сложность $O(n)$ , то есть выполняется очень эффективно**. ================================================ FILE: ru/docs/chapter_heap/heap.md ================================================ # Куча Куча (heap) - это полное двоичное дерево, удовлетворяющее определенным условиям. Основных типов кучи два, как показано на рисунке ниже. - Минимальная куча (min heap): значение любого узла $\leq$ значения его дочерних узлов. - Максимальная куча (max heap): значение любого узла $\geq$ значения его дочерних узлов. ![Минимальная куча и максимальная куча](heap.assets/min_heap_and_max_heap.png) Куча, являясь частным случаем полного двоичного дерева, обладает следующими свойствами. - Узлы самого нижнего уровня заполняются слева, а все остальные уровни заполнены полностью. - Корневой узел двоичного дерева мы называем вершиной кучи, а самый правый узел нижнего уровня - основанием кучи. - Для максимальной (минимальной) кучи значение элемента на вершине, то есть у корневого узла, является максимальным (минимальным). ## Распространенные операции с кучей Нужно отметить, что многие языки программирования предоставляют не саму кучу, а очередь с приоритетом (priority queue) - абстрактную структуру данных, определяемую как очередь, в которой элементы извлекаются в соответствии с приоритетом. На практике **куча обычно используется для реализации очереди с приоритетом, а максимальная куча эквивалентна очереди с приоритетом, в которой элементы извлекаются по убыванию**. С точки зрения использования очередь с приоритетом и куча могут считаться эквивалентными структурами данных. Поэтому в этой книге мы не будем специально различать их и в дальнейшем будем единообразно называть кучей. Распространенные операции с кучей приведены в таблице ниже. Конкретные имена методов зависят от языка программирования.

Таблица   Эффективность операций с кучей

| Имя метода | Описание | Временная сложность | | ----------- | ------------------------------------------------ | ------------------- | | `push()` | Поместить элемент в кучу | $O(\log n)$ | | `pop()` | Извлечь элемент с вершины кучи | $O(\log n)$ | | `peek()` | Получить доступ к вершине кучи (для max / min кучи это соответственно максимум / минимум) | $O(1)$ | | `size()` | Получить число элементов в куче | $O(1)$ | | `isEmpty()` | Проверить, пуста ли куча | $O(1)$ | В реальных приложениях мы можем напрямую использовать классы кучи, предоставляемые языком программирования, или классы очереди с приоритетом. Подобно сортировкам "по возрастанию" и "по убыванию", мы можем переключаться между "минимальной кучей" и "максимальной кучей", изменяя `flag` или модифицируя `Comparator` . Код приведен ниже: === "Python" ```python title="heap.py" # Инициализация минимальной кучи min_heap, flag = [], 1 # Инициализация максимальной кучи max_heap, flag = [], -1 # Модуль heapq в Python по умолчанию реализует минимальную кучу # Если инвертировать знак элемента перед добавлением, то отношение порядка перевернется и так реализуется максимальная куча # В этом примере flag = 1 соответствует минимальной куче, а flag = -1 - максимальной # Добавление элементов в кучу heapq.heappush(max_heap, flag * 1) heapq.heappush(max_heap, flag * 3) heapq.heappush(max_heap, flag * 2) heapq.heappush(max_heap, flag * 5) heapq.heappush(max_heap, flag * 4) # Получение элемента на вершине кучи peek: int = flag * max_heap[0] # 5 # Извлечение элемента с вершины кучи # Извлеченные элементы образуют последовательность по убыванию val = flag * heapq.heappop(max_heap) # 5 val = flag * heapq.heappop(max_heap) # 4 val = flag * heapq.heappop(max_heap) # 3 val = flag * heapq.heappop(max_heap) # 2 val = flag * heapq.heappop(max_heap) # 1 # Получение размера кучи size: int = len(max_heap) # Проверка, пуста ли куча is_empty: bool = not max_heap # Построение кучи из входного списка min_heap: list[int] = [1, 3, 2, 5, 4] heapq.heapify(min_heap) ``` === "C++" ```cpp title="heap.cpp" /* Инициализация кучи */ // Инициализация минимальной кучи priority_queue, greater> minHeap; // Инициализация максимальной кучи priority_queue, less> maxHeap; /* Добавление элементов в кучу */ maxHeap.push(1); maxHeap.push(3); maxHeap.push(2); maxHeap.push(5); maxHeap.push(4); /* Получение элемента на вершине кучи */ int peek = maxHeap.top(); // 5 /* Извлечение элемента с вершины кучи */ // Извлеченные элементы образуют последовательность по убыванию maxHeap.pop(); // 5 maxHeap.pop(); // 4 maxHeap.pop(); // 3 maxHeap.pop(); // 2 maxHeap.pop(); // 1 /* Получение размера кучи */ int size = maxHeap.size(); /* Проверка, пуста ли куча */ bool isEmpty = maxHeap.empty(); /* Построение кучи из входного списка */ vector input{1, 3, 2, 5, 4}; priority_queue, greater> minHeap(input.begin(), input.end()); ``` === "Java" ```java title="heap.java" /* Инициализация кучи */ // Инициализация минимальной кучи Queue minHeap = new PriorityQueue<>(); // Инициализация максимальной кучи (достаточно изменить Comparator через lambda) Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); /* Добавление элементов в кучу */ maxHeap.offer(1); maxHeap.offer(3); maxHeap.offer(2); maxHeap.offer(5); maxHeap.offer(4); /* Получение элемента на вершине кучи */ int peek = maxHeap.peek(); // 5 /* Извлечение элемента с вершины кучи */ // Извлеченные элементы образуют последовательность по убыванию peek = maxHeap.poll(); // 5 peek = maxHeap.poll(); // 4 peek = maxHeap.poll(); // 3 peek = maxHeap.poll(); // 2 peek = maxHeap.poll(); // 1 /* Получение размера кучи */ int size = maxHeap.size(); /* Проверка, пуста ли куча */ boolean isEmpty = maxHeap.isEmpty(); /* Построение кучи из входного списка */ minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); ``` === "C#" ```csharp title="heap.cs" /* Инициализация кучи */ // Инициализация минимальной кучи PriorityQueue minHeap = new(); // Инициализация максимальной кучи (достаточно изменить Comparer через lambda) PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); /* Добавление элементов в кучу */ maxHeap.Enqueue(1, 1); maxHeap.Enqueue(3, 3); maxHeap.Enqueue(2, 2); maxHeap.Enqueue(5, 5); maxHeap.Enqueue(4, 4); /* Получение элемента на вершине кучи */ int peek = maxHeap.Peek();//5 /* Извлечение элемента с вершины кучи */ // Извлеченные элементы образуют последовательность по убыванию peek = maxHeap.Dequeue(); // 5 peek = maxHeap.Dequeue(); // 4 peek = maxHeap.Dequeue(); // 3 peek = maxHeap.Dequeue(); // 2 peek = maxHeap.Dequeue(); // 1 /* Получение размера кучи */ int size = maxHeap.Count; /* Проверка, пуста ли куча */ bool isEmpty = maxHeap.Count == 0; /* Построение кучи из входного списка */ minHeap = new PriorityQueue([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]); ``` === "Go" ```go title="heap.go" // В Go можно построить целочисленную максимальную кучу, реализовав heap.Interface // Для реализации heap.Interface также нужно реализовать sort.Interface type intHeap []any // Метод Push из heap.Interface, реализует добавление элемента в кучу func (h *intHeap) Push(x any) { // Push и Pop используют pointer receiver // Потому что они не только изменяют содержимое среза, но и его длину *h = append(*h, x.(int)) } // Метод Pop из heap.Interface, реализует извлечение элемента с вершины кучи func (h *intHeap) Pop() any { // Извлекаемый элемент хранится в конце last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Метод Len из sort.Interface func (h *intHeap) Len() int { return len(*h) } // Метод Less из sort.Interface func (h *intHeap) Less(i, j int) bool { // Для минимальной кучи здесь нужно заменить сравнение на < return (*h)[i].(int) > (*h)[j].(int) } // Метод Swap из sort.Interface func (h *intHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Top получает элемент на вершине кучи func (h *intHeap) Top() any { return (*h)[0] } /* Driver Code */ func TestHeap(t *testing.T) { /* Инициализация кучи */ // Инициализация максимальной кучи maxHeap := &intHeap{} heap.Init(maxHeap) /* Добавление элементов в кучу */ // Вызываем методы heap.Interface для добавления элементов heap.Push(maxHeap, 1) heap.Push(maxHeap, 3) heap.Push(maxHeap, 2) heap.Push(maxHeap, 4) heap.Push(maxHeap, 5) /* Получение элемента на вершине кучи */ top := maxHeap.Top() fmt.Printf("Элемент на вершине кучи: %d\n", top) /* Извлечение элемента с вершины кучи */ // Вызываем методы heap.Interface для удаления элементов heap.Pop(maxHeap) // 5 heap.Pop(maxHeap) // 4 heap.Pop(maxHeap) // 3 heap.Pop(maxHeap) // 2 heap.Pop(maxHeap) // 1 /* Получение размера кучи */ size := len(*maxHeap) fmt.Printf("Число элементов в куче: %d\n", size) /* Проверка, пуста ли куча */ isEmpty := len(*maxHeap) == 0 fmt.Printf("Пуста ли куча: %t\n", isEmpty) } ``` === "Swift" ```swift title="heap.swift" /* Инициализация кучи */ // Тип Heap в Swift поддерживает и max-heap, и min-heap, но требует swift-collections var heap = Heap() /* Добавление элементов в кучу */ heap.insert(1) heap.insert(3) heap.insert(2) heap.insert(5) heap.insert(4) /* Получение элемента на вершине кучи */ var peek = heap.max()! /* Извлечение элемента с вершины кучи */ peek = heap.removeMax() // 5 peek = heap.removeMax() // 4 peek = heap.removeMax() // 3 peek = heap.removeMax() // 2 peek = heap.removeMax() // 1 /* Получение размера кучи */ let size = heap.count /* Проверка, пуста ли куча */ let isEmpty = heap.isEmpty /* Построение кучи из входного списка */ let heap2 = Heap([1, 3, 2, 5, 4]) ``` === "JS" ```javascript title="heap.js" // В JavaScript нет встроенного класса Heap ``` === "TS" ```typescript title="heap.ts" // В TypeScript нет встроенного класса Heap ``` === "Dart" ```dart title="heap.dart" // В Dart нет встроенного класса Heap ``` === "Rust" ```rust title="heap.rs" use std::collections::BinaryHeap; use std::cmp::Reverse; /* Инициализация кучи */ // Инициализация минимальной кучи let mut min_heap = BinaryHeap::>::new(); // Инициализация максимальной кучи let mut max_heap = BinaryHeap::new(); /* Добавление элементов в кучу */ max_heap.push(1); max_heap.push(3); max_heap.push(2); max_heap.push(5); max_heap.push(4); /* Получение элемента на вершине кучи */ let peek = max_heap.peek().unwrap(); // 5 /* Извлечение элемента с вершины кучи */ // Извлеченные элементы образуют последовательность по убыванию let peek = max_heap.pop().unwrap(); // 5 let peek = max_heap.pop().unwrap(); // 4 let peek = max_heap.pop().unwrap(); // 3 let peek = max_heap.pop().unwrap(); // 2 let peek = max_heap.pop().unwrap(); // 1 /* Получение размера кучи */ let size = max_heap.len(); /* Проверка, пуста ли куча */ let is_empty = max_heap.is_empty(); /* Построение кучи из входного списка */ let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]); ``` === "C" ```c title="heap.c" // В C нет встроенного класса Heap ``` === "Kotlin" ```kotlin title="heap.kt" /* Инициализация кучи */ // Инициализация минимальной кучи var minHeap = PriorityQueue() // Инициализация максимальной кучи (достаточно изменить Comparator через lambda) val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } /* Добавление элементов в кучу */ maxHeap.offer(1) maxHeap.offer(3) maxHeap.offer(2) maxHeap.offer(5) maxHeap.offer(4) /* Получение элемента на вершине кучи */ var peek = maxHeap.peek() // 5 /* Извлечение элемента с вершины кучи */ // Извлеченные элементы образуют последовательность по убыванию peek = maxHeap.poll() // 5 peek = maxHeap.poll() // 4 peek = maxHeap.poll() // 3 peek = maxHeap.poll() // 2 peek = maxHeap.poll() // 1 /* Получение размера кучи */ val size = maxHeap.size /* Проверка, пуста ли куча */ val isEmpty = maxHeap.isEmpty() /* Построение кучи из входного списка */ minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) ``` === "Ruby" ```ruby title="heap.rb" # В Ruby нет встроенного класса Heap ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20min-%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20min_heap%2C%20flag%20%3D%20%5B%5D%2C%201%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20max-%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20max_heap%2C%20flag%20%3D%20%5B%5D%2C%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C%20heapq%20%D0%B2%20Python%20%D0%BF%D0%BE%20%D1%83%D0%BC%D0%BE%D0%BB%D1%87%D0%B0%D0%BD%D0%B8%D1%8E%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D1%83%D0%B5%D1%82%20min-%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B4%20%D0%BF%D0%BE%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%BE%D1%82%D1%80%D0%B8%D1%86%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%2C%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%B8%D1%82%D1%8C%20%D0%BE%D1%82%D0%BD%D0%BE%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B0%20%D0%B8%20%D1%82%D0%B5%D0%BC%20%D1%81%D0%B0%D0%BC%D1%8B%D0%BC%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20max-%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20%23%20%D0%92%20%D1%8D%D1%82%D0%BE%D0%BC%20%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D0%B5%20flag%20%3D%201%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D0%B5%D1%82%20min-%D0%BA%D1%83%D1%87%D0%B5%2C%20%D0%B0%20flag%20%3D%20-1%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D0%B5%D1%82%20max-%D0%BA%D1%83%D1%87%D0%B5%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%201%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%203%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%202%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%205%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20peek%20%3D%20flag%20%2A%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5%20%D0%B8%D0%B7%20%D0%BA%D1%83%D1%87%D0%B8%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D1%83%D1%8E%D1%82%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BE%D1%82%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B3%D0%BE%20%D0%BA%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%D0%BC%D1%83%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%B8%D1%82%D1%8C%2C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BA%D1%83%D1%87%D0%B0%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%92%D1%85%D0%BE%D0%B4%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%D0%B8%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20min_heap%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## Реализация кучи Ниже реализуется максимальная куча. Чтобы преобразовать ее в минимальную кучу, достаточно инвертировать всю логику сравнений по величине, например заменить $\geq$ на $\leq$ . Заинтересованные читатели могут попробовать реализовать это самостоятельно. ### Хранение и представление кучи В разделе "Двоичные деревья" мы уже говорили, что полное двоичное дерево очень удобно представлять массивом. Поскольку куча как раз и является полным двоичным деревом, **для хранения кучи мы также будем использовать массив**. Когда двоичное дерево представляется массивом, элементы массива соответствуют значениям узлов, а индексы - положениям этих узлов в двоичном дереве. **Указатели на узлы реализуются через формулы отображения индексов**. Как показано на рисунке ниже, для заданного индекса $i$ индекс левого дочернего узла равен $2i + 1$ , правого дочернего узла - $2i + 2$ , а родительского узла - $(i - 1) / 2$ с округлением вниз. Если индекс выходит за допустимые границы, это означает пустой узел или отсутствие узла. ![Представление и хранение кучи](heap.assets/representation_of_heap.png) Мы можем инкапсулировать формулы отображения индексов в функции, чтобы потом было удобнее ими пользоваться: ```src [file]{my_heap}-[class]{max_heap}-[func]{parent} ``` ### Доступ к элементу на вершине кучи Элемент на вершине кучи - это корневой узел двоичного дерева, то есть первый элемент списка: ```src [file]{my_heap}-[class]{max_heap}-[func]{peek} ``` ### Добавление элемента в кучу Пусть дан элемент `val` . Сначала мы помещаем его в основание кучи. После добавления свойства кучи могут нарушиться, потому что `val` может оказаться больше, чем другие элементы в куче. **Поэтому необходимо восстановить порядок на пути от вставленного узла к корню** ; эта операция называется упорядочиванием кучи. Рассмотрим ситуацию, когда упорядочивание выполняется **снизу вверх**, начиная от только что вставленного узла. Как показано на рисунках ниже, мы сравниваем значение вставленного узла со значением его родителя; если вставленный узел больше, то меняем их местами. Затем продолжаем выполнять ту же операцию и последовательно восстанавливать корректность всех узлов по пути снизу вверх, пока не выйдем за корень или не встретим узел, для которого обмен не требуется. === "<1>" ![Шаги добавления элемента в кучу](heap.assets/heap_push_step1.png) === "<2>" ![heap_push_step2](heap.assets/heap_push_step2.png) === "<3>" ![heap_push_step3](heap.assets/heap_push_step3.png) === "<4>" ![heap_push_step4](heap.assets/heap_push_step4.png) === "<5>" ![heap_push_step5](heap.assets/heap_push_step5.png) === "<6>" ![heap_push_step6](heap.assets/heap_push_step6.png) === "<7>" ![heap_push_step7](heap.assets/heap_push_step7.png) === "<8>" ![heap_push_step8](heap.assets/heap_push_step8.png) === "<9>" ![heap_push_step9](heap.assets/heap_push_step9.png) Пусть общее число узлов равно $n$ , тогда высота дерева равна $O(\log n)$ . Следовательно, максимальное число итераций операции упорядочивания кучи тоже не превышает $O(\log n)$ . Отсюда **временная сложность добавления элемента в кучу равна $O(\log n)$** . Код приведен ниже: ```src [file]{my_heap}-[class]{max_heap}-[func]{sift_up} ``` ### Извлечение элемента с вершины кучи Элемент на вершине кучи - это корневой узел двоичного дерева, то есть первый элемент списка. Если просто удалить первый элемент списка, то индексы всех узлов двоичного дерева изменятся, и это сильно затруднит последующее восстановление структуры при помощи упорядочивания кучи. Чтобы по возможности минимизировать изменение индексов элементов, мы используем следующий порядок действий. 1. Поменять местами элемент на вершине кучи и элемент у основания кучи, то есть поменять корневой узел с самым правым листовым узлом. 2. После обмена удалить основание кучи из списка. Стоит отметить, что, поскольку обмен уже выполнен, фактически удаляется исходный элемент вершины кучи. 3. Начиная от корневого узла, **выполнить упорядочивание кучи сверху вниз**. Как показано на рисунках ниже, **направление операции упорядочивания кучи сверху вниз противоположно операции упорядочивания кучи снизу вверх**. Мы сравниваем значение корневого узла со значениями двух дочерних узлов, выбираем больший дочерний узел и меняем его местами с корневым узлом. Затем циклически повторяем ту же операцию, пока не выйдем за листовой узел или не встретим узел, который уже не требует обмена. === "<1>" ![Шаги извлечения элемента с вершины кучи](heap.assets/heap_pop_step1.png) === "<2>" ![heap_pop_step2](heap.assets/heap_pop_step2.png) === "<3>" ![heap_pop_step3](heap.assets/heap_pop_step3.png) === "<4>" ![heap_pop_step4](heap.assets/heap_pop_step4.png) === "<5>" ![heap_pop_step5](heap.assets/heap_pop_step5.png) === "<6>" ![heap_pop_step6](heap.assets/heap_pop_step6.png) === "<7>" ![heap_pop_step7](heap.assets/heap_pop_step7.png) === "<8>" ![heap_pop_step8](heap.assets/heap_pop_step8.png) === "<9>" ![heap_pop_step9](heap.assets/heap_pop_step9.png) === "<10>" ![heap_pop_step10](heap.assets/heap_pop_step10.png) Как и операция добавления в кучу, операция извлечения элемента с вершины кучи также имеет временную сложность $O(\log n)$ . Код приведен ниже: ```src [file]{my_heap}-[class]{max_heap}-[func]{sift_down} ``` ## Типичные применения кучи - **Очередь с приоритетом**: куча обычно является предпочтительной структурой данных для реализации очереди с приоритетом; добавление и извлечение элементов имеют временную сложность $O(\log n)$ , а построение кучи - $O(n)$ , и все эти операции выполняются очень эффективно. - **Пирамидальная сортировка**: для заданного набора данных можно построить кучу, а затем непрерывно извлекать из нее элементы, получая отсортированные данные. Однако на практике мы обычно используем более изящную реализацию пирамидальной сортировки; подробности см. в разделе "Пирамидальная сортировка". - **Получение наибольших $k$ элементов**: это классическая алгоритмическая задача и одновременно типичное применение кучи. Например, выбор 10 самых горячих новостей для списка популярных тем или выбор 10 самых продаваемых товаров. ================================================ FILE: ru/docs/chapter_heap/index.md ================================================ # Куча ![Куча](../assets/covers/chapter_heap.jpg) !!! abstract Куча - это полное двоичное дерево, удовлетворяющее определенным условиям. В максимальной и минимальной куче элемент на вершине всегда обладает самым выраженным приоритетом. ================================================ FILE: ru/docs/chapter_heap/summary.md ================================================ # Резюме ### Ключевые выводы - Куча представляет собой полное двоичное дерево и делится на максимальную кучу и минимальную кучу. Элемент на вершине максимальной (минимальной) кучи является наибольшим (наименьшим). - Очередь с приоритетом определяется как очередь, элементы которой извлекаются в соответствии с приоритетом; обычно ее реализуют с помощью кучи. - К основным операциям кучи и их временным сложностям относятся: добавление элемента в кучу $O(\log n)$ , извлечение элемента с вершины кучи $O(\log n)$ и доступ к вершине кучи $O(1)$ . - Полное двоичное дерево очень удобно представлять массивом, поэтому кучу обычно тоже хранят в массиве. - Операция упорядочивания кучи используется для поддержания свойств кучи и применяется как при добавлении элемента, так и при извлечении элемента. - Временную сложность построения кучи из $n$ элементов можно оптимизировать до $O(n)$ , что очень эффективно. - Top-k - это классическая алгоритмическая задача, которую можно эффективно решать с помощью кучи за $O(n \log k)$ . ### Q & A **Q**: Является ли "куча" как структура данных тем же самым понятием, что и "куча" в управлении памятью? Это не одно и то же, просто у них случайно совпало название. Куча в памяти компьютерной системы является частью динамического распределения памяти: во время выполнения программы она используется для хранения данных. Программа может запросить определенный объем памяти в куче для хранения сложных структур, таких как объекты и массивы. Когда эти данные больше не нужны, память нужно освободить, чтобы не допустить утечек. По сравнению со стековой памятью управление памятью в куче требует большей осторожности, а неправильное использование может привести к утечкам памяти и проблемам с указателями. ================================================ FILE: ru/docs/chapter_heap/top_k.md ================================================ # Задача Top-k !!! question Дан неупорядоченный массив `nums` длины $n$ . Требуется вернуть наибольшие $k$ элементов массива. Для этой задачи мы сначала покажем два относительно прямолинейных способа решения, а затем более эффективный способ на основе кучи. ## Метод 1: выбор через обход Как показано на рисунке ниже, можно выполнить $k$ проходов по массиву и на каждом проходе извлекать соответственно $1$-й, $2$-й, $\dots$ , $k$-й по величине элемент; временная сложность такого подхода равна $O(nk)$ . Этот метод подходит только для случая $k \ll n$ , потому что когда $k$ приближается к $n$ , его временная сложность стремится к $O(n^2)$ , а это уже очень затратно. ![Поиск наибольших k элементов через обход](top_k.assets/top_k_traversal.png) !!! tip Когда $k = n$ , мы получаем полную упорядоченную последовательность, и в этот момент задача становится эквивалентной алгоритму "сортировка выбором". ## Метод 2: сортировка Как показано на рисунке ниже, можно сначала отсортировать массив `nums` , а затем вернуть его крайние правые $k$ элементов; временная сложность такого метода равна $O(n \log n)$ . Очевидно, что этот способ делает слишком много, потому что нам нужно только найти наибольшие $k$ элементов, а сортировать остальные элементы совсем не обязательно. ![Поиск наибольших k элементов через сортировку](top_k.assets/top_k_sorting.png) ## Метод 3: куча Задачу Top-k можно решить гораздо эффективнее с помощью кучи, как показано на рисунках ниже. 1. Инициализировать минимальную кучу, у которой вершина содержит наименьший элемент. 2. Сначала по очереди поместить в кучу первые $k$ элементов массива. 3. Начиная с элемента номер $k + 1$ , если текущий элемент больше элемента на вершине кучи, то извлечь вершину кучи и поместить в кучу текущий элемент. 4. После завершения обхода в куче будут храниться как раз наибольшие $k$ элементов. === "<1>" ![Поиск наибольших k элементов с помощью кучи](top_k.assets/top_k_heap_step1.png) === "<2>" ![top_k_heap_step2](top_k.assets/top_k_heap_step2.png) === "<3>" ![top_k_heap_step3](top_k.assets/top_k_heap_step3.png) === "<4>" ![top_k_heap_step4](top_k.assets/top_k_heap_step4.png) === "<5>" ![top_k_heap_step5](top_k.assets/top_k_heap_step5.png) === "<6>" ![top_k_heap_step6](top_k.assets/top_k_heap_step6.png) === "<7>" ![top_k_heap_step7](top_k.assets/top_k_heap_step7.png) === "<8>" ![top_k_heap_step8](top_k.assets/top_k_heap_step8.png) === "<9>" ![top_k_heap_step9](top_k.assets/top_k_heap_step9.png) Пример кода приведен ниже: ```src [file]{top_k}-[class]{}-[func]{top_k_heap} ``` Всего выполняется $n$ операций добавления и извлечения из кучи, а максимальная длина кучи равна $k$ , поэтому временная сложность равна $O(n \log k)$ . Этот метод очень эффективен: когда $k$ мало, временная сложность стремится к $O(n)$ ; когда $k$ велико, она все равно не превышает $O(n \log n)$ . Кроме того, этот метод подходит и для сценариев с динамическим потоком данных. При непрерывном поступлении новых данных мы можем продолжать поддерживать содержимое кучи, тем самым динамически обновляя наибольшие $k$ элементов. ================================================ FILE: ru/docs/chapter_hello_algo/index.md ================================================ --- comments: true icon: material/rocket-launch-outline --- # Перед началом Несколько лет назад я публиковал на LeetCode разборы серии задач "Sword for Offer" и получил поддержку и ободрение от многих читателей. Во время общения с ними мне чаще всего задавали один и тот же вопрос: "как начать изучать алгоритмы?" Постепенно этот вопрос начал меня по-настоящему занимать. Слепо бросаться в решение задач кажется самым популярным способом: он прост, прямолинеен и действительно работает. Но решение задач похоже на игру в "Сапера": люди с сильными навыками самообучения способны обезвредить мины одну за другой, а тем, у кого не хватает базы, легко набить себе шишки и шаг за шагом отступить под давлением неудач. Полностью проходить учебники тоже принято часто, но для тех, кто готовится к поиску работы, диплом, резюме, письменные тесты и собеседования уже отнимают большую часть сил, и потому толстые книги нередко превращаются в тяжелое испытание. Если ты тоже сталкиваешься с такими трудностями, то можно сказать, что эта книга сама "нашла" тебя. Она стала моим ответом на этот вопрос: пусть и не идеальным, но как минимум честной и активной попыткой. Эта книга сама по себе не гарантирует предложения о работе, но поможет тебе увидеть "карту знаний" по структурам данных и алгоритмам, понять форму, размер и расположение разных "мин" и освоить разные "способы разминирования". Освоив это, ты сможешь увереннее решать задачи и читать технические материалы, шаг за шагом выстраивая целостную систему знаний. Я глубоко согласен со словами профессора Фейнмана: "Knowledge isn't free. You have to pay attention." В этом смысле книга не совсем "бесплатна". Чтобы не подвести то драгоценное "внимание", которое ты ей уделишь, я постараюсь вложить в ее создание максимум собственного "внимания". Я хорошо понимаю пределы собственных знаний. Хотя материал этой книги уже довольно долго шлифовался, в нем наверняка все еще осталось немало ошибок, поэтому я искренне прошу преподавателей и читателей указывать на неточности и недоработки. ![Hello Algo](../assets/covers/chapter_hello_algo.jpg){ class="cover-image" }

Hello, алгоритмы!

Появление компьютеров радикально изменило мир. Благодаря высокой скорости вычислений и отличной программируемости они стали идеальной средой для исполнения алгоритмов и обработки данных. Реалистичная графика в играх, интеллектуальные решения в автономном вождении, впечатляющие партии AlphaGo и естественное взаимодействие ChatGPT: все это изящные проявления алгоритмов на компьютере. На самом деле еще до появления компьютеров алгоритмы и структуры данных уже существовали во всех уголках мира. Ранние алгоритмы были сравнительно простыми: например, древние способы счета или последовательности действий при изготовлении инструментов. По мере развития цивилизации алгоритмы становились тоньше и сложнее. За мастерством ремесленников, промышленными продуктами, освобождающими производительные силы, и даже за научными законами движения Вселенной почти всегда стоит изобретательная алгоритмическая мысль. Точно так же структуры данных встречаются повсюду: от социальных сетей до схем метро многие системы можно моделировать как "граф"; от государства до семьи основные формы общественной организации обладают свойствами "дерева"; зимняя одежда похожа на "стек", где то, что надевают первым, снимают последним; тубус для бадминтонных воланов похож на "очередь", где элементы добавляются с одного конца и извлекаются с другого; словарь похож на "хеш-таблицу", позволяющую быстро находить нужную статью. Эта книга стремится с помощью понятных анимированных иллюстраций и исполняемых примеров кода помочь читателю понять ключевые идеи алгоритмов и структур данных и научиться реализовывать их программно. На этой основе книга также пытается показать живые проявления алгоритмов в сложном мире и раскрыть их красоту. Надеюсь, она окажется для тебя полезной. ================================================ FILE: ru/docs/chapter_introduction/algorithms_are_everywhere.md ================================================ # Алгоритмы повсюду Говоря об алгоритмах, естественно вспомнить о математике. Однако на самом деле многие алгоритмы не связаны со сложной математикой, а больше полагаются на базовую логику, которая повсеместно встречается в нашей повседневной жизни. Прежде чем углубиться в обсуждение алгоритмов, стоит упомянуть интересный факт: **вы уже точно освоили множество алгоритмов и привыкли применять их в повседневной жизни**. Далее приведем несколько конкретных примеров, чтобы подтвердить этот факт. **Пример 1: поиск в словаре**. В словаре все слова упорядочены по алфавиту. Предположим, нам нужно найти слово, начинающееся на букву $r$; обычно для этого нужно выполнить следующие действия. 1. Откройте словарь примерно на половине страниц и посмотрите, какая буква является первой на этой странице; предположим, это буква $m$. 2. Поскольку в алфавите буква $r$ идет после $m$, исключаем первую половину словаря, и область поиска сужается до второй половины. 3. Продолжайте повторять шаги `1.` и `2.` , пока не найдете страницу, где первой буквой слов будет $r$. === "<1>" ![Этапы поиска в словаре. Шаг 1](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png) === "<2>" ![Этапы поиска в словаре. Шаг 2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png) === "<3>" ![Этапы поиска в словаре. Шаг 3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png) === "<4>" ![Этапы поиска в словаре. Шаг 4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png) === "<5>" ![Этапы поиска в словаре. Шаг 5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png) Навык поиска в словаре, которым владеет каждый школьник, на самом деле является известным алгоритмом двоичного поиска. С точки зрения структуры данных словарь можно рассматривать как отсортированный массив; с точки зрения алгоритма последовательность операций по поиску в словаре можно считать двоичным поиском. **Пример 2: упорядочивание карт**. Во время игры в карты необходимо каждый раз упорядочивать карты в руке от меньшего к большему. Для этого нужно выполнить следующие действия. 1. Разделите карты на упорядоченную и неупорядоченную части, предполагая, что изначально самая левая карта уже упорядочена. 2. Из неупорядоченной части извлеките одну карту и вставьте ее в правильное место в упорядоченной части; после этого две самые левые карты станут упорядоченными. 3. Повторяйте шаг `2.` , каждый раз перемещая одну карту из неупорядоченной части в упорядоченную, пока все карты не станут упорядоченными. ![Этапы упорядочивания карт](algorithms_are_everywhere.assets/playing_cards_sorting.png) Метод упорядочивания карт по своей сути является алгоритмом сортировки вставками, который весьма эффективен при обработке небольших наборов данных. Многие функции сортировки в библиотеках программирования используют именно этот алгоритм. **Пример 3: сдача**. Предположим, что в супермаркете мы купили товар стоимостью $69$ руб. и дали кассиру купюру в $100$ руб. Кассир должен вернуть нам $31$ руб. Для этого ему нужно выполнить следующие действия. 1. Варианты выбора - это купюры номиналом меньше $31$ руб. Пусть у нас имеются номиналы $1$ , $5$ , $10$ и $20$ руб. 2. Возьмем самую крупную доступную купюру в $20$ руб. Остаток сдачи составит $31 - 20 = 11$ руб. 3. Возьмем самую крупную из оставшихся купюр в $10$ руб. Остаток составит $11 - 10 = 1$ руб. 4. Возьмем самую крупную из оставшихся купюр в $1$ руб. Остаток составит $1 - 1 = 0$ руб. 5. Завершим выдачу сдачи, схема: $20 + 10 + 1 = 31$ руб. ![Этапы выдачи сдачи](algorithms_are_everywhere.assets/greedy_change.png) В этих шагах мы на каждом этапе выбираем наилучший вариант, используя купюры наибольшего номинала, и в итоге получаем рабочую схему сдачи. С точки зрения структуры данных и алгоритмов этот метод по своей сути является жадным алгоритмом. От приготовления блюда до межзвездных путешествий решение практически любой задачи неразрывно связано с алгоритмами. Появление компьютеров позволило нам с помощью программирования хранить структуры данных в памяти, а также писать код для вызовов к CPU и GPU для выполнения алгоритмов. Таким образом, мы можем переносить задачи из реальной жизни в компьютер и решать различные сложные проблемы более эффективно. !!! tip Если представление о структурах данных, алгоритмах, массивах и двоичном поиске пока остается расплывчатым, просто продолжайте читать. Эта книга постепенно введет вас в мир структур данных и алгоритмов. ================================================ FILE: ru/docs/chapter_introduction/index.md ================================================ # Введение в алгоритмы ![Введение в алгоритмы](../assets/covers/chapter_introduction.jpg) !!! abstract Юная девушка кружится в танце, переплетаясь с данными, а по подолу ее платья струится мелодия алгоритмов. Она приглашает вас присоединиться к танцу: следуйте за ее шагами и войдите в мир алгоритмов, полный логики и красоты. ================================================ FILE: ru/docs/chapter_introduction/summary.md ================================================ # Резюме ### Ключевые выводы - Алгоритмы повсеместно присутствуют в нашей повседневной жизни и не являются недосягаемыми сложными знаниями. На самом деле мы уже освоили множество алгоритмов, которые помогают решать различные жизненные задачи. - Принцип поиска в словаре соответствует алгоритму двоичного поиска. Двоичный поиск иллюстрирует важную идею алгоритмов "разделяй и властвуй". - Процесс сортировки карт в колоде очень похож на алгоритм сортировки вставками, который хорошо подходит для сортировки небольших наборов данных. - Процесс размена по своей сути является жадным алгоритмом, в котором на каждом этапе принимается наилучшее на данный момент решение. - Алгоритм представляет собой набор инструкций или шагов, предназначенных для решения конкретной задачи в ограниченное время, а структура данных - это способ организации и хранения данных в компьютере. - Структуры данных и алгоритмы тесно связаны. Структуры данных являются основой для алгоритмов, а алгоритмы оживляют структуры данных. - Структуры данных и алгоритмы можно сравнить с конструктором: детали конструктора представляют данные, их форма и способы соединения - структуры данных, а этапы сборки конструктора соответствуют алгоритмам. ### Q & A **Q**: Я программист и в повседневной работе никогда не использовал алгоритмы для решения задач, поскольку часто используемые алгоритмы уже встроены в языки программирования и ими можно пользоваться напрямую. Значит ли это, что рабочие задачи еще не требуют применения алгоритмов? Если сравнить конкретные профессиональные навыки с приемами в боевых искусствах, то базовые дисциплины скорее напоминают "внутреннюю силу". Я считаю, что изучение алгоритмов и других базовых дисциплин важно не для того, чтобы реализовывать их с нуля в работе, а для того, чтобы на основе полученных знаний принимать профессиональные решения и оценки при решении задач, тем самым повышая общее качество работы. Простой пример: каждый язык программирования имеет встроенные функции сортировки. - Если бы мы не изучали структуры данных и алгоритмы, то, получив любые данные, возможно, просто передали бы их этой функции сортировки. Все работает гладко, производительность хорошая, и на первый взгляд проблем нет. - Однако если мы изучили алгоритмы, то знаем, что временная сложность встроенной функции сортировки составляет $O(n \log n)$ ; если же данные представлены целыми числами фиксированной разрядности, например номерами студентов, то можно использовать более эффективный метод поразрядной сортировки, снизив временную сложность до $O(nk)$ , где $k$ - это количество разрядов, а при больших объемах данных выиграть во времени, затратах и пользовательском опыте. В инженерной практике множество задач трудно решить оптимальным образом, и многие из них решаются "как-то". Сложность задачи зависит как от ее природы, так и от уровня знаний и опыта человека, который ее анализирует. Чем более полными знаниями и большим опытом обладает человек, тем глубже он может проанализировать проблему и тем изящнее может быть ее решение. ================================================ FILE: ru/docs/chapter_introduction/what_is_dsa.md ================================================ # Что такое алгоритм ## Определение алгоритма Алгоритм (algorithm) - это набор инструкций или шагов, предназначенных для решения конкретной задачи за ограниченное время. Он обладает следующими свойствами. - Задача четко определена и включает ясные определения входных и выходных данных. - Обладает осуществимостью и может быть выполнен за ограниченное количество шагов, времени и памяти. - Каждый шаг имеет определенное значение, и при одинаковых входных данных и условиях выполнения результат всегда будет одинаковым. ## Определение структуры данных Структура данных (data structure) - это способ организации и хранения данных, включающий содержимое данных, их взаимосвязи и методы операций с ними. Структура данных преследует следующие цели. - Минимизировать занимаемое пространство для экономии памяти компьютера. - Обеспечивать максимально быструю обработку данных, включая доступ, добавление, удаление и обновление данных. - Обеспечивать простое представление данных и логическую информацию для эффективного выполнения алгоритмов. **Проектирование структуры данных - это процесс, полный компромиссов**. Если вы хотите улучшить один аспект, часто приходится идти на уступки в другом. Приведем два примера. - Связный список, по сравнению с массивом, более удобен для добавления и удаления данных, но имеет проблемы со скоростью доступа к данным. - Граф, по сравнению со связным списком, предоставляет более богатую логическую информацию, но требует большего объема памяти. ## Связь между структурами данных и алгоритмами Как показано на рисунке ниже, структуры данных и алгоритмы тесно взаимосвязаны, что проявляется в следующих трех аспектах. - Структуры данных являются основой алгоритмов. Они обеспечивают структурированное хранение данных и методы их обработки. - Алгоритмы оживляют структуры данных. Сами по себе структуры данных лишь хранят информацию, но в сочетании с алгоритмами они позволяют решать конкретные задачи. - Алгоритмы можно реализовать на основе различных структур данных, однако эффективность их выполнения может значительно различаться, поэтому выбор подходящей структуры данных является ключевым фактором. ![Связь между структурами данных и алгоритмами](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png) Структуры данных и алгоритмы подобны конструктору, как показано на рисунке ниже. Комплект конструктора, помимо множества деталей, содержит также подробную инструкцию по сборке. Следуя этой инструкции шаг за шагом, можно собрать красивую модель. ![Сборка конструктора](what_is_dsa.assets/assembling_blocks.png) Подробное описание аналогии с конструктором представлено в таблице ниже.

Таблица   Сравнение структур данных и алгоритмов с конструктором

| Структуры данных и алгоритмы | Конструктор | | ---------------------------- | ----------- | | Входные данные | Несобранные детали конструктора | | Структура данных | Организация деталей конструктора, включая форму, размер, способы соединения и т. д. | | Алгоритм | Последовательность действий по сборке деталей в целевую модель | | Выходные данные | Собранная модель конструктора | Стоит отметить, что структуры данных и алгоритмы не зависят от языка программирования. Именно поэтому данная книга предлагает их реализации на различных языках. !!! tip "Принятое сокращение" В реальных обсуждениях выражение "структуры данных и алгоритмы" обычно сокращают до просто "алгоритмы". Например, хорошо известные задачи LeetCode на деле одновременно проверяют знания и по структурам данных, и по алгоритмам. ================================================ FILE: ru/docs/chapter_preface/about_the_book.md ================================================ # Об этой книге Этот проект задуман как открытое, бесплатное и дружелюбное к новичкам введение в структуры данных и алгоритмы. - В книге используются анимированные иллюстрации: материал изложен ясно и последовательно, что облегчает освоение и помогает начинающим выстроить карту знаний по структурам данных и алгоритмам. - Исходный код можно запустить одним нажатием, что позволяет тренироваться, развивать навыки программирования и понимать принципы работы алгоритмов и реализации структур данных на фундаментальном уровне. - Мы призываем читателей к взаимопомощи: задавайте вопросы и делитесь идеями в комментариях. Обсуждения помогают двигаться вперед всем вместе. ## Целевая аудитория Если вы новичок в алгоритмах, никогда с ними не сталкивались или уже имеете некоторый опыт решения задач, но еще не обладаете четким пониманием структур данных и алгоритмов, эта книга создана специально для вас! Если у вас уже есть определенный опыт решения задач и вы знакомы с большинством типов задач, эта книга поможет вам освежить и систематизировать знания об алгоритмах, а исходный код может служить набором инструментов для решения задач или алгоритмическим словарем. Если вы владеете алгоритмами на экспертном уровне, мы будем рады вашим ценным советам или [совместному участию в создании книги](https://www.hello-algo.com/chapter_appendix/contribution/). !!! success "Предварительные требования" Необходимо иметь хотя бы базовую подготовку в одном из языков программирования, чтобы читать и писать простой код. ## Структура содержания Основное содержание книги представлено на рисунке ниже. - **Анализ сложности**: критерии и методы оценки структур данных и алгоритмов. Методы расчета временной и пространственной сложности, распространенные типы, примеры и т. д. - **Структуры данных**: классификация основных типов данных и структур данных. Определение, преимущества и недостатки, основные операции, распространенные типы, типичные приложения и методы реализации массивов, списков, стеков, очередей, хеш-таблиц, деревьев, куч и графов. - **Алгоритмы**: определение, преимущества и недостатки, эффективность, области применения, этапы решения и примеры задач для поиска, сортировки, алгоритма "разделяй и властвуй", поиска с возвратом, динамического программирования и жадных алгоритмов. ![Основное содержание книги](about_the_book.assets/hello_algo_mindmap.png) ## Благодарности Эта книга постоянно совершенствуется благодаря совместным усилиям множества участников открытого сообщества. Благодарим каждого автора, вложившего свое время и силы; их имена перечислены в порядке, автоматически сгенерированном GitHub: krahets, coderonion, Gonglja, nuomi1, Reanon, justin-tse, hpstory, danielsss, curtishd, night-cruise, S-N-O-R-L-A-X, rongyi, msk397, gvenusleo, khoaxuantu, rivertwilight, K3v123, gyt95, zhuoqinyue, yuelinxin, Zuoxun, mingXta, Phoenix0415, FangYuan33, GN-Yu, longsizhuo, IsChristina, xBLACKICEx, guowei-gong, Cathay-Chen, pengchzn, QiLOL, magentaqin, hello-ikun, JoseHung, qualifier1024, thomasq0, sunshinesDL, L-Super, Guanngxu, Transmigration-zhou, WSL0809, Slone123c, lhxsm, yuan0221, what-is-me, Shyam-Chen, theNefelibatas, longranger2, codeberg-user, xiongsp, JeffersonHuang, prinpal, seven1240, Wonderdch, malone6, xiaomiusa87, gaofer, bluebean-cloud, a16su, SamJin98, hongyun-robot, nanlei, XiaChuerwu, yd-j, iron-irax, mgisr, steventimes, junminhong, heshuyue, danny900714, MolDuM, Nigh, Dr-XYZ, XC-Zero, reeswell, PXG-XPG, NI-SW, Horbin-Magician, Enlightenus, YangXuanyi, beatrix-chan, DullSword, xjr7670, jiaxianhua, qq909244296, iStig, boloboloda, hts0000, gledfish, wenjianmin, keshida, kilikilikid, lclc6, lwbaptx, linyejoe2, liuxjerry, llql1211, fbigm, echo1937, szu17dmy, dshlstarr, Yucao-cy, coderlef, czruby, bongbongbakudan, beintentional, ZongYangL, ZhongYuuu, ZhongGuanbin, hezhizhen, linzeyan, ZJKung, luluxia, xb534, ztkuaikuai, yw-1021, ElaBosak233, baagod, zhouLion, yishangzhang, yi427, yanedie, yabo083, weibk, wangwang105, th1nk3r-ing, tao363, 4yDX3906, syd168, sslmj2020, smilelsb, siqyka, selear, sdshaoda, Xi-Row, popozhu, nuquist19, noobcodemaker, XiaoK29, chadyi, lyl625760, lucaswangdev, 0130w, shanghai-Jerry, EJackYang, Javesun99, eltociear, lipusheng, KNChiu, BlindTerran, ShiMaRing, lovelock, FreddieLi, FloranceYeh, fanchenggang, gltianwen, goerll, nedchu, curly210102, CuB3y0nd, KraHsu, CarrotDLaw, youshaoXG, bubble9um, Asashishi, Asa0oo0o0o, fanenr, eagleanurag, akshiterate, 52coder, foursevenlove, KorsChen, GaochaoZhu, hopkings2008, yang-le, realwujing, Evilrabbit520, Umer-Jahangir, Turing-1024-Lee, Suremotoo, paoxiaomooo, Chieko-Seren, Allen-Scai, ymmmas, Risuntsy, Richard-Zhang1019, RafaelCaso, qingpeng9802, primexiao, Urbaner3, zhongfq, nidhoggfgg, MwumLi, CreatorMetaSky, martinx, ZnYang2018, hugtyftg, logan-qiu, psychelzh, Keynman, KeiichiKasai и KawaiiAsh. Рецензирование кода книги выполнили coderonion, curtishd, Gonglja, gvenusleo, hpstory, justin-tse, khoaxuantu, krahets, night-cruise, nuomi1, Reanon и rongyi (в алфавитном порядке). Благодарим их за потраченное время и силы, которые обеспечили стандартизацию и единообразие кода на различных языках. Традиционную китайскую версию книги вычитали Shyam-Chen и Dr-XYZ, английскую версию - yuelinxin, K3v123, QiLOL, Phoenix0415, SamJin98, yanedie, RafaelCaso, pengchzn, thomasq0 и magentaqin, а японскую версию - eltociear. Именно благодаря их постоянному вкладу эта книга может служить более широкому кругу читателей, и мы искренне благодарим их. Инструмент генерации ePub-версии этой книги разработал zhongfq. Благодарим его за вклад, который дал читателям более свободный способ чтения. В процессе создания этой книги мне помогало много людей. - Благодарю моего наставника в компании, доктора Ли Си: в одной из бесед вы вдохновили меня быстрее начать, что укрепило мою решимость написать эту книгу; - Благодарю мою девушку Bubble, первого читателя этой книги: с позиции новичка в алгоритмах она дала много ценных советов, благодаря которым книга стала более понятной и доступной; - Благодарю Tengbao, Qibao и Feibao за креативное название книги, которое навевает приятные воспоминания о первой строке кода "Hello World!"; - Благодарю Xiaoquan за профессиональную помощь в вопросах интеллектуальной собственности, что сыграло важную роль в совершенствовании этой открытой книги; - Благодарю Sutong за дизайн обложки и логотипа книги, а также за терпение при многочисленных исправлениях по моим просьбам; - Благодарю @squidfunk за советы по оформлению и за разработку открытой темы документации [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master). В процессе написания книги я ознакомился с множеством учебников и статей по структурам данных и алгоритмам. Эти работы послужили отличным образцом для этой книги, обеспечив ее точность и качество. Я искренне благодарю всех преподавателей и предшественников за их выдающийся вклад! Эта книга пропагандирует метод обучения, сочетающий умственную и практическую деятельность; в этом отношении на меня сильно повлияла [Dive into Deep Learning](https://github.com/d2l-ai/d2l-zh). Я настоятельно рекомендую эту замечательную работу всем читателям. **Сердечно благодарю моих родителей: именно ваша постоянная поддержка и ободрение дали мне возможность заняться этим увлекательным делом**. ================================================ FILE: ru/docs/chapter_preface/index.md ================================================ # Предисловие ![Предисловие](../assets/covers/chapter_preface.jpg) !!! abstract Алгоритмы подобны прекрасной симфонии, а каждая строка кода льется подобно мелодии. Пусть эта книга тихо зазвучит в вашем сознании и оставит после себя особую и глубокую мелодию. ================================================ FILE: ru/docs/chapter_preface/suggestions.md ================================================ # Как пользоваться этой книгой !!! tip Для получения наилучшего опыта чтения рекомендуется полностью прочитать этот раздел. ## Соглашения о стиле изложения - Главы, помеченные `*` в заголовке, являются дополнительными и содержат более сложный материал. Если времени мало, их можно пропустить. - Профессиональные термины выделяются полужирным шрифтом в печатной и PDF-версии или подчеркиванием в веб-версии, например массив (array). Рекомендуется запоминать их для удобства чтения литературы. - Важные моменты и обобщающие фразы будут **выделяться полужирным шрифтом**, и на такие тексты следует обращать особое внимание. - Слова и выражения со специальным смыслом будут отмечаться "кавычками", чтобы избежать неоднозначности. - Когда термины различаются между языками программирования, в качестве стандарта используется Python; например, `None` применяется для обозначения "пустого" значения. - В некоторых местах книга отходит от стандартов комментирования программного кода ради более компактного оформления. Комментарии в основном делятся на три типа: заголовочные, содержательные и многострочные. === "Python" ```python title="" """Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п.""" # Содержательный комментарий: подробно поясняет код """ Многострочный комментарий """ ``` === "C++" ```cpp title="" /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ // Содержательный комментарий: подробно поясняет код /** * Многострочный * комментарий */ ``` === "Java" ```java title="" /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ // Содержательный комментарий: подробно поясняет код /** * Многострочный * комментарий */ ``` === "C#" ```csharp title="" /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ // Содержательный комментарий: подробно поясняет код /** * Многострочный * комментарий */ ``` === "Go" ```go title="" /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ // Содержательный комментарий: подробно поясняет код /** * Многострочный * комментарий */ ``` === "Swift" ```swift title="" /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ // Содержательный комментарий: подробно поясняет код /** * Многострочный * комментарий */ ``` === "JS" ```javascript title="" /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ // Содержательный комментарий: подробно поясняет код /** * Многострочный * комментарий */ ``` === "TS" ```typescript title="" /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ // Содержательный комментарий: подробно поясняет код /** * Многострочный * комментарий */ ``` === "Dart" ```dart title="" /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ // Содержательный комментарий: подробно поясняет код /** * Многострочный * комментарий */ ``` === "Rust" ```rust title="" /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ // Содержательный комментарий: подробно поясняет код // Многострочный // комментарий ``` === "C" ```c title="" /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ // Содержательный комментарий: подробно поясняет код /** * Многострочный * комментарий */ ``` === "Kotlin" ```kotlin title="" /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ // Содержательный комментарий: подробно поясняет код /** * Многострочный * комментарий */ ``` === "Ruby" ```ruby title="" ### Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. ### # Содержательный комментарий: подробно поясняет код # Многострочный # комментарий ``` ## Эффективное обучение с помощью анимированных иллюстраций По сравнению с текстом видео и изображения обладают более высокой плотностью информации и более четкой структурой, поэтому их легче воспринимать. В этой книге **ключевые и сложные моменты в основном представлены в виде анимированных иллюстраций**, а текст служит пояснением и дополнением. Если во время чтения вы встречаете фрагмент с анимированной иллюстрацией, как на рисунке ниже, **используйте иллюстрацию в качестве основного источника информации, а текст - в качестве вспомогательного**, объединяя оба источника для понимания материала. ![Пример анимированной иллюстрации](../index.assets/animation.gif) ## Углубление понимания через практику кода Сопроводительный код этой книги размещен в [репозитории GitHub](https://github.com/krahets/hello-algo). Как показано ниже, **исходный код содержит тестовые примеры и может быть запущен одним нажатием кнопки**. Если позволяет время, **рекомендуется самостоятельно набирать код**. Если времени на обучение мало, по крайней мере **просмотрите и выполните весь код**. Процесс написания кода приносит больше пользы, чем его чтение. **Настоящее обучение - это обучение на практике**. ![Пример запуска кода](../index.assets/running_code.gif) Подготовка к запуску кода в основном состоит из трех этапов. **Шаг 1: установка локальной среды программирования**. Воспользуйтесь [руководством](https://www.hello-algo.com/chapter_appendix/installation/) из приложения. Если среда уже установлена, этот шаг можно пропустить. **Шаг 2: клонирование или загрузка репозитория кода**. Перейдите в [репозиторий GitHub](https://github.com/krahets/hello-algo). Если у вас уже установлен [Git](https://git-scm.com/downloads), репозиторий можно клонировать следующей командой: ```shell git clone https://github.com/krahets/hello-algo.git ``` Также можно нажать кнопку "Download ZIP" в месте, показанном на рисунке ниже, напрямую скачать архив с кодом и затем распаковать его локально. ![Клонирование репозитория и загрузка кода](suggestions.assets/download_code.png) **Шаг 3: запуск исходного кода**. Как показано на рисунке ниже, для блоков кода, у которых сверху указано имя файла, соответствующий исходный файл можно найти в папке `codes` репозитория. Исходные файлы запускаются одним нажатием, что помогает не тратить лишнее время на отладку и сосредоточиться на изучении материала. ![Блоки кода и соответствующие исходные файлы](suggestions.assets/code_md_to_repo.png) Помимо локального запуска, **веб-версия также поддерживает визуальное выполнение Python-кода** (на базе [pythontutor](https://pythontutor.com/)). Как показано ниже, можно нажать "Визуализировать выполнение" под блоком кода, чтобы раскрыть окно и наблюдать за выполнением алгоритма; также можно нажать "Полноэкранный режим" для более удобного просмотра. ![Визуальный запуск Python-кода](suggestions.assets/pythontutor_example.png) ## Совместный рост через вопросы и обсуждения Во время чтения книги не стоит пропускать те места, которые остались непонятными. **Мы призываем вас задавать вопросы в разделе комментариев**: я и мои коллеги постараемся ответить вам как можно тщательнее, обычно в течение двух дней. Как показано на рисунке ниже, в веб-версии у каждой главы внизу есть раздел комментариев. Рекомендуется уделять внимание его содержанию. С одной стороны, это поможет увидеть, с какими трудностями сталкиваются другие читатели, восполнить пробелы и подтолкнуть себя к более глубокому пониманию. С другой стороны, мы надеемся, что вы будете отвечать на вопросы других участников и делиться своими мнениями. ![Пример раздела комментариев](../index.assets/comment.gif) ## Дорожная карта изучения алгоритмов В целом процесс изучения структур данных и алгоритмов можно разделить на три этапа. 1. **Этап 1: введение в алгоритмы**. Необходимо познакомиться с особенностями и применением различных структур данных, изучить принципы, процессы, назначение и эффективность различных алгоритмов. 2. **Этап 2: решение алгоритмических задач**. Рекомендуется начинать с популярных задач и решить не менее 100 из них, чтобы познакомиться с основными алгоритмическими проблемами. При первых попытках "забывание знаний" может стать испытанием, но это нормально. Следуйте при повторении задач "кривой забывания Эббингауза", и обычно после 3-5 циклов повторения материал хорошо запоминается. Рекомендуемые списки задач и планы практики см. в этом [репозитории GitHub](https://github.com/krahets/LeetCode-Book). 3. **Этап 3: построение системы знаний**. В процессе обучения можно читать статьи по алгоритмам, изучать каркасы решений и учебники, чтобы постоянно обогащать свою систему знаний. В решении задач можно применять продвинутые стратегии, например классификацию по темам, несколько решений одной задачи или одно решение для нескольких задач; соответствующий опыт можно найти в различных сообществах. Как показано на рисунке ниже, содержание этой книги в основном охватывает "этап 1" и призвано помочь вам более эффективно перейти к обучению на этапах 2 и 3. ![Дорожная карта изучения алгоритмов](suggestions.assets/learning_route.png) ================================================ FILE: ru/docs/chapter_preface/summary.md ================================================ # Резюме ### Ключевые выводы - Основная аудитория этой книги - новички в изучении алгоритмов. Если у вас уже есть определенная база, книга поможет систематизировать знания, а исходный код послужит инструментальной библиотекой для решения задач. - Содержание книги включает три основные части - анализ сложности, структуры данных и алгоритмы - и охватывает большинство тем в этой области. - Для новичков в алгоритмах крайне важно изучить начальные разделы книги, чтобы избежать множества ошибок в будущем. - Анимированные иллюстрации в книге обычно используются для представления ключевых и сложных аспектов. При чтении книги следует уделять этим материалам больше внимания. - Практика - лучший способ изучения программирования. Настоятельно рекомендуется запускать исходный код и самостоятельно писать программы. - В веб-версии книги каждая глава имеет область комментариев, где можно задавать вопросы и делиться своими мыслями. ================================================ FILE: ru/docs/chapter_reference/index.md ================================================ --- icon: material/bookshelf --- # Список литературы [1] Thomas H. Cormen и др. Introduction to Algorithms (3rd Edition). [2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition). [3] Robert Sedgewick и др. Algorithms (4th Edition). [4] Yan Weimin. Data Structures (C Language Edition). [5] Deng Junhui. Data Structures (C++ Language Edition, 3rd Edition). [6] Mark Allen Weiss; пер. Chen Yue. Data Structures and Algorithm Analysis: Java Description (3rd Edition). [7] Cheng Jie. A Plainspoken Guide to Data Structures. [8] Wang Zheng. The Beauty of Data Structures and Algorithms. [9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition). [10] Aston Zhang и др. Dive into Deep Learning. ================================================ FILE: ru/docs/chapter_searching/binary_search.md ================================================ # Двоичный поиск Двоичный поиск (binary search) - это эффективный алгоритм поиска, основанный на стратегии "разделяй и властвуй". Он использует упорядоченность данных, сокращая на каждом шаге область поиска вдвое, пока не будет найден целевой элемент или пока интервал поиска не опустеет. !!! question Дан массив `nums` длины $n$, элементы которого расположены в порядке возрастания и не повторяются. Найдите и верните индекс элемента `target` в этом массиве. Если массив не содержит этого элемента, верните $-1$ . Пример показан на рисунке ниже. ![Пример данных для двоичного поиска](binary_search.assets/binary_search_example.png) Как показано на рисунке ниже, сначала инициализируем указатели $i = 0$ и $j = n - 1$ , которые указывают на первый и последний элементы массива и задают интервал поиска $[0, n - 1]$ . Обратите внимание: квадратные скобки обозначают замкнутый интервал и включают граничные значения. Далее в цикле выполняются следующие два шага. 1. Вычислить индекс середины $m = \lfloor {(i + j) / 2} \rfloor$ , где $\lfloor \: \rfloor$ означает операцию округления вниз. 2. Сравнить `nums[m]` и `target` , после чего возможны три случая. 1. Если `nums[m] < target` , это означает, что `target` находится в интервале $[m + 1, j]$ , поэтому выполняется $i = m + 1$ . 2. Если `nums[m] > target` , это означает, что `target` находится в интервале $[i, m - 1]$ , поэтому выполняется $j = m - 1$ . 3. Если `nums[m] = target` , значит, элемент `target` найден, поэтому возвращается индекс $m$ . Если массив не содержит целевой элемент, область поиска в итоге сузится до пустого интервала. В этом случае возвращается $-1$ . === "<1>" ![Процесс двоичного поиска](binary_search.assets/binary_search_step1.png) === "<2>" ![binary_search_step2](binary_search.assets/binary_search_step2.png) === "<3>" ![binary_search_step3](binary_search.assets/binary_search_step3.png) === "<4>" ![binary_search_step4](binary_search.assets/binary_search_step4.png) === "<5>" ![binary_search_step5](binary_search.assets/binary_search_step5.png) === "<6>" ![binary_search_step6](binary_search.assets/binary_search_step6.png) === "<7>" ![binary_search_step7](binary_search.assets/binary_search_step7.png) Стоит отметить, что поскольку и $i$ , и $j$ имеют тип `int` , **то сумма $i + j$ может выйти за пределы диапазона типа `int`**. Чтобы избежать переполнения, обычно используют формулу $m = \lfloor {i + (j - i) / 2} \rfloor$ для вычисления середины. Код приведен ниже: ```src [file]{binary_search}-[class]{}-[func]{binary_search} ``` **Временная сложность равна $O(\log n)$** : в цикле двоичного поиска интервал каждый раз сокращается вдвое, поэтому число итераций равно $\log_2 n$ . **Пространственная сложность равна $O(1)$** : указатели $i$ и $j$ занимают константный объем памяти. ## Методы представления интервалов Помимо описанного выше двойного замкнутого интервала, часто используется и левозамкнутый правооткрытый интервал, который задается как $[0, n)$ , то есть левая граница включается, а правая - нет. В этом представлении интервал $[i, j)$ пуст, когда $i = j$ . На основе этого представления можно реализовать двоичный поиск с той же функциональностью: ```src [file]{binary_search}-[class]{}-[func]{binary_search_lcro} ``` Как показано на рисунке ниже, в этих двух вариантах представления интервала различаются инициализация, условие цикла и операция сужения интервала в алгоритме двоичного поиска. Поскольку в записи "двойной замкнутый интервал" обе границы являются закрытыми, операции сужения интервала при помощи указателей $i$ и $j$ тоже получаются симметричными. Из-за этого в таком варианте сложнее допустить ошибку, **поэтому обычно рекомендуется использовать именно запись "двойной замкнутый интервал"**. ![Два определения интервалов](binary_search.assets/binary_search_ranges.png) ## Преимущества и ограничения Двоичный поиск показывает хорошие результаты и по времени, и по памяти. - Двоичный поиск очень эффективен по времени. На больших объемах данных логарифмическая временная сложность дает заметное преимущество. Например, когда размер данных $n = 2^{20}$ , линейный поиск потребует $2^{20} = 1048576$ итераций, тогда как двоичный поиск выполнится всего за $\log_2 2^{20} = 20$ итераций. - Двоичный поиск не требует дополнительной памяти. По сравнению с алгоритмами поиска, которым нужно внешнее пространство (например, с хеш-поиском), двоичный поиск заметно экономнее по памяти. Однако двоичный поиск подходит не для всех ситуаций, и основные причины таковы. - Двоичный поиск применим только к упорядоченным данным. Если входные данные неупорядочены, специально сортировать их ради двоичного поиска невыгодно. Это связано с тем, что временная сложность алгоритмов сортировки обычно составляет $O(n \log n)$ , что выше, чем у линейного и двоичного поиска. Если элементы приходится часто вставлять, то для сохранения порядка в массиве их нужно помещать в конкретные позиции, а это требует $O(n)$ времени и тоже обходится дорого. - Двоичный поиск применим только к массивам. Для него нужен скачкообразный доступ к элементам, а в связном списке такой доступ малоэффективен, поэтому двоичный поиск не подходит для списков и структур данных, построенных на их основе. - При малом объеме данных линейный поиск работает лучше. В линейном поиске на каждом шаге нужна всего одна операция сравнения; в двоичном поиске требуется 1 сложение, 1 деление, от 1 до 3 сравнений и еще 1 сложение или вычитание, то есть всего от 4 до 6 элементарных операций. Поэтому при небольшом $n$ линейный поиск может оказаться быстрее двоичного. ================================================ FILE: ru/docs/chapter_searching/binary_search_edge.md ================================================ # Двоичный поиск границ ## Поиск левой границы !!! question Дан упорядоченный массив `nums` длины $n$, который может содержать повторяющиеся элементы. Верните индекс самого левого элемента `target` в массиве. Если массив не содержит этот элемент, верните $-1$ . Вспомним метод поиска точки вставки при двоичном поиске: после завершения поиска указатель $i$ указывает на самый левый `target` , **поэтому поиск точки вставки по сути является поиском индекса самого левого `target`**. Рассмотрим реализацию поиска левой границы через функцию поиска точки вставки. Обратите внимание: массив может не содержать `target` , и тогда возможны две ситуации. - Индекс точки вставки $i$ выходит за границы массива. - Элемент `nums[i]` не равен `target` . Если возникает любая из этих ситуаций, достаточно сразу вернуть $-1$ . Код приведен ниже: ```src [file]{binary_search_edge}-[class]{}-[func]{binary_search_left_edge} ``` ## Поиск правой границы Как тогда найти самый правый `target` ? Самый прямой способ - изменить код, заменив операцию сужения указателя в случае `nums[m] == target` . Мы не будем приводить этот код, заинтересованные читатели могут реализовать его самостоятельно. Ниже представлены два более изящных способа. ### Повторное использование поиска левой границы На самом деле функцию поиска самого левого элемента можно использовать и для поиска самого правого элемента. Конкретная идея такова: **преобразовать поиск самого правого `target` в поиск самого левого `target + 1`**. Как показано на рисунке ниже, после завершения поиска указатель $i$ указывает на самый левый `target + 1` (если он существует), а указатель $j$ указывает на самый правый `target` , **поэтому достаточно вернуть $j$**. ![Преобразование поиска правой границы в поиск левой](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png) Обратите внимание: функция возвращает точку вставки $i$ , поэтому из нее нужно вычесть $1$ , чтобы получить $j$ : ```src [file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge} ``` ### Преобразование в поиск элемента Мы знаем, что если массив не содержит `target` , то в конце поиска указатели $i$ и $j$ будут указывать соответственно на первый элемент, больший `target` , и на первый элемент, меньший `target` . Следовательно, как показано на рисунке ниже, для поиска левой и правой границы можно сконструировать элемент, которого нет в массиве. - Поиск самого левого `target` : можно преобразовать в поиск `target - 0.5` и вернуть указатель $i$ . - Поиск самого правого `target` : можно преобразовать в поиск `target + 0.5` и вернуть указатель $j$ . ![Преобразование поиска границ в поиск элемента](binary_search_edge.assets/binary_search_edge_by_element.png) Код здесь опущен, но стоит обратить внимание на два момента. - По условию массив не содержит дробных чисел, поэтому нам не нужно беспокоиться о том, как обрабатывать случай равенства другим элементам массива. - Поскольку этот метод вводит дробные числа, переменную `target` в функции нужно изменить на тип с плавающей запятой (в Python менять ничего не требуется). ================================================ FILE: ru/docs/chapter_searching/binary_search_insertion.md ================================================ # Двоичный поиск точки вставки Двоичный поиск можно использовать не только для поиска целевого элемента, но и для решения многих вариаций задачи, например для поиска позиции вставки целевого элемента. ## Случай без повторяющихся элементов !!! question Дан упорядоченный массив `nums` длины $n$ и элемент `target` , причем в массиве нет повторяющихся элементов. Нужно вставить `target` в массив `nums` , сохранив порядок. Если элемент `target` уже присутствует в массиве, вставьте его слева от него. Верните индекс, который будет иметь `target` после вставки. Пример показан на рисунке ниже. ![Пример данных для точки вставки](binary_search_insertion.assets/binary_search_insertion_example.png) Если мы хотим переиспользовать код двоичного поиска из предыдущего раздела, нужно ответить на два вопроса. **Вопрос 1**: если массив содержит `target` , будет ли индекс вставки совпадать с индексом этого элемента? По условию `target` нужно вставить слева от равного элемента, а это означает, что новый `target` занимает место старого `target` . Иначе говоря, **если массив содержит `target` , то индекс вставки совпадает с индексом этого `target`**. **Вопрос 2**: если массив не содержит `target` , индекс какого элемента будет точкой вставки? Рассмотрим процесс двоичного поиска подробнее: когда `nums[m] < target` , указатель $i$ сдвигается вправо и тем самым приближается к элементу, который больше либо равен `target` . Аналогично указатель $j$ постепенно приближается к элементу, который меньше либо равен `target` . Следовательно, после завершения двоичного поиска обязательно выполняется следующее: указатель $i$ указывает на первый элемент, больший `target` , а указатель $j$ указывает на первый элемент, меньший `target` . **Нетрудно сделать вывод, что если массив не содержит `target` , то индекс вставки равен $i$** . Код приведен ниже: ```src [file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple} ``` ## Случай с повторяющимися элементами !!! question В предыдущей задаче теперь допускается, что массив может содержать повторяющиеся элементы, а все остальные условия остаются без изменений. Если в массиве есть несколько элементов `target` , то обычный двоичный поиск сможет вернуть индекс только одного из них, **но не позволит определить, сколько элементов `target` находится слева и справа от него**. По условию целевой элемент нужно вставить в самую левую позицию, **поэтому нам нужно найти индекс самого левого `target` в массиве**. На первом этапе можно рассмотреть решение, показанное на рисунке ниже. 1. Выполнить двоичный поиск и получить индекс любого элемента `target` , обозначив его как $k$ . 2. Начиная с индекса $k$ , линейно двигаться влево и вернуть результат, когда будет найден самый левый `target` . ![Линейный поиск точки вставки среди повторяющихся элементов](binary_search_insertion.assets/binary_search_insertion_naive.png) Этот метод применим на практике, однако в нем есть линейный поиск, поэтому его временная сложность равна $O(n)$ . Когда в массиве имеется много повторяющихся `target` , такой подход работает неэффективно. Теперь рассмотрим расширение кода двоичного поиска. Как показано на рисунке ниже, общий процесс остается прежним: на каждом шаге мы сначала вычисляем индекс середины $m$ , а затем сравниваем `target` и `nums[m]` , после чего возможны следующие случаи. - Когда `nums[m] < target` или `nums[m] > target` , это означает, что `target` еще не найден, поэтому используется стандартная операция сужения интервала в двоичном поиске, **благодаря чему указатели $i$ и $j$ приближаются к `target`**. - Когда `nums[m] == target` , это означает, что элементы меньше `target` находятся в интервале $[i, m - 1]$ , поэтому мы используем $j = m - 1$ для сужения интервала, **тем самым приближая указатель $j$ к элементам, меньшим `target`**. После завершения цикла указатель $i$ будет указывать на самый левый `target` , а указатель $j$ - на первый элемент, меньший `target` , **поэтому индекс $i$ и является точкой вставки**. === "<1>" ![Шаги поиска точки вставки для повторяющихся элементов](binary_search_insertion.assets/binary_search_insertion_step1.png) === "<2>" ![binary_search_insertion_step2](binary_search_insertion.assets/binary_search_insertion_step2.png) === "<3>" ![binary_search_insertion_step3](binary_search_insertion.assets/binary_search_insertion_step3.png) === "<4>" ![binary_search_insertion_step4](binary_search_insertion.assets/binary_search_insertion_step4.png) === "<5>" ![binary_search_insertion_step5](binary_search_insertion.assets/binary_search_insertion_step5.png) === "<6>" ![binary_search_insertion_step6](binary_search_insertion.assets/binary_search_insertion_step6.png) === "<7>" ![binary_search_insertion_step7](binary_search_insertion.assets/binary_search_insertion_step7.png) === "<8>" ![binary_search_insertion_step8](binary_search_insertion.assets/binary_search_insertion_step8.png) Если посмотреть на следующий код, то видно, что действия в ветвях `nums[m] > target` и `nums[m] == target` совпадают, поэтому эти две ветви можно объединить. Даже в этом случае можно оставить условия развернутыми, потому что так логика выглядит более ясной и код легче читать. ```src [file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion} ``` !!! tip Код в этом разделе записан в стиле "двойного замкнутого интервала". При желании можно самостоятельно реализовать вариант "слева закрыт, справа открыт". Если смотреть в целом, суть двоичного поиска сводится к тому, что для указателей $i$ и $j$ заранее задаются ориентиры поиска; целью может быть конкретный элемент, например `target` , а может быть и диапазон элементов, например все элементы, меньшие `target` . В ходе непрерывного двоичного деления указатели $i$ и $j$ постепенно приближаются к заранее заданной цели. В конце они либо успешно находят ответ, либо останавливаются после выхода за границы. ================================================ FILE: ru/docs/chapter_searching/index.md ================================================ # Поиск ![Поиск](../assets/covers/chapter_searching.jpg) !!! abstract Поиск - это движение в неизвестность: иногда приходится пройти каждый уголок пространства, а иногда удается быстро найти цель. В этом пути каждый новый шаг может привести к ответу, которого мы не ожидали. ================================================ FILE: ru/docs/chapter_searching/replace_linear_by_hashing.md ================================================ # Стратегии оптимизации хеширования В алгоритмических задачах **мы часто заменяем линейный поиск на хеш-поиск, чтобы уменьшить временную сложность алгоритма**. Разберем одну задачу, чтобы лучше понять этот прием. !!! question Дан массив целых чисел `nums` и целевой элемент `target` . Найдите в массиве два элемента, сумма которых равна `target` , и верните их индексы. Подойдет любой корректный ответ. ## Линейный поиск: обмен времени на пространство Рассмотрим прямой перебор всех возможных комбинаций. Как показано на рисунке ниже, мы запускаем два вложенных цикла и на каждом шаге проверяем, равна ли сумма двух целых чисел `target` ; если да, то возвращаем их индексы. ![Линейный поиск для задачи о двух суммах](replace_linear_by_hashing.assets/two_sum_brute_force.png) Код приведен ниже: ```src [file]{two_sum}-[class]{}-[func]{two_sum_brute_force} ``` Временная сложность этого метода равна $O(n^2)$ , а пространственная сложность равна $O(1)$ , поэтому на больших объемах данных он очень медленный. ## Хеш-поиск: обмен пространства на время Рассмотрим вариант с использованием хеш-таблицы, где ключами и значениями будут элементы массива и их индексы. При циклическом обходе массива на каждом шаге выполняются действия, показанные на рисунке ниже. 1. Проверить, находится ли число `target - nums[i]` в хеш-таблице; если да, то сразу вернуть индексы этих двух элементов. 2. Добавить в хеш-таблицу пару из ключа `nums[i]` и индекса `i` . === "<1>" ![Вспомогательная хеш-таблица для задачи о двух суммах](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) === "<2>" ![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png) === "<3>" ![two_sum_hashtable_step3](replace_linear_by_hashing.assets/two_sum_hashtable_step3.png) Код реализации показан ниже, и для него достаточно одного цикла: ```src [file]{two_sum}-[class]{}-[func]{two_sum_hash_table} ``` Благодаря хеш-поиску этот метод снижает временную сложность с $O(n^2)$ до $O(n)$ , существенно повышая эффективность работы. Поскольку требуется поддерживать дополнительную хеш-таблицу, пространственная сложность составляет $O(n)$ . **Несмотря на это, в целом данный метод лучше сбалансирован по времени и памяти, поэтому именно он является оптимальным решением этой задачи**. ================================================ FILE: ru/docs/chapter_searching/searching_algorithm_revisited.md ================================================ # Переосмысление алгоритмов поиска Алгоритмы поиска (searching algorithm) используются для того, чтобы находить один или несколько элементов, удовлетворяющих определенным условиям, в структурах данных, таких как массивы, списки, деревья или графы. Алгоритмы поиска можно разделить на две категории по способу реализации. - **Поиск целевого элемента путем обхода структуры данных**, например обход массива, списка, дерева или графа. - **Эффективный поиск элементов с использованием структуры организации данных или априорной информации**, например двоичный поиск, хеш-поиск и поиск в двоичном дереве поиска. Нетрудно заметить, что эти темы уже рассматривались в предыдущих главах, поэтому алгоритмы поиска нам уже знакомы. В этом разделе мы систематизируем полученные ранее знания и еще раз посмотрим на них как на единую группу методов. ## Полный перебор Полный перебор заключается в том, что мы обходим каждый элемент структуры данных, чтобы найти целевой элемент. - "Линейный поиск" применяется к линейным структурам данных, таким как массивы и списки. Он начинается с одного конца структуры данных и последовательно проверяет элементы, пока не найдет целевой элемент или пока не достигнет другого конца структуры данных. - "Обход в ширину" и "обход в глубину" - это две стратегии обхода графов и деревьев. Обход в ширину стартует из начального узла и исследует все узлы текущего уровня, прежде чем переходить к следующему. Обход в глубину стартует из начального узла, проходит один путь до конца, затем возвращается назад и пробует другие пути, пока не будет полностью пройдена вся структура данных. Преимущество полного перебора состоит в его простоте и универсальности, **поскольку он не требует предварительной обработки данных и использования дополнительных структур данных**. Однако **временная сложность таких алгоритмов равна $O(n)$** , где $n$ - число элементов, поэтому при больших объемах данных их производительность невысока. ## Адаптивный поиск Адаптивный поиск использует специфические свойства данных (например, упорядоченность), чтобы оптимизировать процесс поиска и тем самым эффективнее находить целевой элемент. - "Двоичный поиск" использует упорядоченность данных для эффективного поиска и применим только к массивам. - "Хеш-поиск" использует хеш-таблицу для построения отображения между поисковыми данными и целевыми данными, благодаря чему запросы выполняются эффективно. - "Поиск в дереве" ведется в конкретной древовидной структуре (например, в двоичном дереве поиска) и позволяет быстро отсекать узлы на основе сравнения значений, чтобы найти цель. Преимущество этих алгоритмов заключается в высокой эффективности: **их временная сложность может достигать $O(\log n)$ и даже $O(1)$** . Однако **для использования таких алгоритмов обычно требуется предварительная обработка данных**. Например, для двоичного поиска нужно заранее отсортировать массив, а хеш-поиск и поиск в дереве требуют дополнительных структур данных, поддержание которых тоже отнимает время и память. !!! tip Адаптивные алгоритмы поиска часто называют алгоритмами поиска в узком смысле, **поскольку они в основном предназначены для быстрого нахождения целевого элемента в конкретной структуре данных**. ## Выбор метода поиска Для поиска целевого элемента в наборе данных размера $n$ можно использовать линейный поиск, двоичный поиск, поиск в дереве, хеш-поиск и другие методы. Принципы работы этих методов показаны на рисунке ниже. ![Различные стратегии поиска](searching_algorithm_revisited.assets/searching_algorithms.png) Эффективность и особенности перечисленных методов приведены в таблице ниже.

Таблица   Сравнение эффективности алгоритмов поиска

| | Линейный поиск | Двоичный поиск | Поиск в дереве | Хеш-поиск | | ---------------------------- | -------------- | ------------------- | ------------------- | ------------------- | | Поиск элемента | $O(n)$ | $O(\log n)$ | $O(\log n)$ | $O(1)$ | | Вставка элемента | $O(1)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | | Удаление элемента | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | | Дополнительное пространство | $O(1)$ | $O(1)$ | $O(n)$ | $O(n)$ | | Предварительная обработка | / | Сортировка $O(n \log n)$ | Построение дерева $O(n \log n)$ | Построение хеш-таблицы $O(n)$ | | Упорядоченность данных | Не требуется | Требуется | Требуется | Не требуется | Выбор алгоритма поиска также зависит от масштаба данных, требований к производительности поиска, а также частоты запросов и обновлений данных. **Линейный поиск** - Обладает хорошей универсальностью и не требует никакой предварительной обработки данных. Если нужно выполнить только один запрос, то время предварительной обработки для остальных трех методов окажется больше, чем время линейного поиска. - Подходит для небольших объемов данных, потому что в этом случае влияние временной сложности на эффективность невелико. - Подходит для сценариев с высокой частотой обновления данных, поскольку этот метод не требует никакого дополнительного обслуживания данных. **Двоичный поиск** - Подходит для больших наборов данных и демонстрирует стабильную эффективность; его худшая временная сложность равна $O(\log n)$ . - Объем данных не должен быть слишком большим, потому что массив требует непрерывного участка памяти. - Не подходит для сценариев с частыми вставками и удалениями данных, так как поддержание массива в отсортированном виде требует больших затрат. **Хеш-поиск** - Подходит для сценариев, в которых требования к скорости запросов очень высоки; средняя временная сложность равна $O(1)$ . - Не подходит для сценариев, где требуется упорядоченность данных или поиск по диапазону, потому что хеш-таблица не умеет поддерживать порядок данных. - Сильно зависит от хеш-функции и стратегии обработки коллизий, поэтому риск деградации производительности сравнительно велик. - Не подходит для слишком больших объемов данных, так как хеш-таблице требуется дополнительное пространство, чтобы максимально снизить число коллизий и обеспечить хорошую производительность поиска. **Поиск в дереве** - Подходит для очень больших объемов данных, потому что узлы дерева распределены в памяти и не требуют непрерывного хранения. - Подходит для сценариев, где нужно поддерживать упорядоченные данные или выполнять поиск по диапазону. - В процессе постоянных вставок и удалений узлов двоичное дерево поиска может перекоситься, и тогда временная сложность деградирует до $O(n)$ . - Если использовать AVL-дерево или красно-черное дерево, то все операции могут стабильно выполняться за $O(\log n)$ , но поддержание баланса дерева увеличивает дополнительные накладные расходы. ================================================ FILE: ru/docs/chapter_searching/summary.md ================================================ # Резюме ### Ключевые выводы - Двоичный поиск опирается на упорядоченность данных и выполняет поиск путем циклического сокращения интервала вдвое. Он требует упорядоченных входных данных и подходит только для массивов или структур данных, реализованных на их основе. - Полный перебор находит данные путем обхода структуры данных. Линейный поиск подходит для массивов и списков, а обход в ширину и обход в глубину подходят для графов и деревьев. Эти алгоритмы универсальны и не требуют предварительной обработки данных, но их временная сложность $O(n)$ сравнительно велика. - Хеш-поиск, поиск в дереве и двоичный поиск относятся к эффективным методам поиска и позволяют быстро находить целевой элемент в конкретных структурах данных. Такие алгоритмы обладают высокой эффективностью, их временная сложность может достигать $O(\log n)$ и даже $O(1)$ , но обычно им нужны дополнительные структуры данных. - На практике нужно анализировать размер данных, требования к производительности поиска, а также частоту запросов и обновлений данных, чтобы выбрать подходящий метод поиска. - Линейный поиск подходит для небольших или часто обновляемых наборов данных; двоичный поиск - для больших отсортированных данных; хеш-поиск - для сценариев с высокими требованиями к скорости запросов и без необходимости поиска по диапазону; поиск в дереве - для больших динамических данных, где нужно поддерживать порядок и выполнять диапазонные запросы. - Замена линейного поиска на хеш-поиск - это распространенная стратегия ускорения, которая позволяет снизить временную сложность с $O(n)$ до $O(1)$ . ================================================ FILE: ru/docs/chapter_sorting/bubble_sort.md ================================================ # Сортировка пузырьком Сортировка пузырьком (bubble sort) реализует сортировку путем последовательного сравнения и обмена соседних элементов. Этот процесс напоминает всплытие пузырьков снизу вверх, откуда и произошло название алгоритма. Как показано на рисунке ниже, процесс "всплытия" можно смоделировать через операцию обмена элементов: начиная от левого края массива и двигаясь вправо, мы последовательно сравниваем соседние элементы и, если "левый элемент > правый элемент", меняем их местами. После завершения прохода максимальный элемент будет перемещен в самый правый конец массива. === "<1>" ![Моделирование пузырька через обмен элементов](bubble_sort.assets/bubble_operation_step1.png) === "<2>" ![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png) === "<3>" ![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png) === "<4>" ![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png) === "<5>" ![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png) === "<6>" ![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png) === "<7>" ![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png) ## Алгоритм Пусть длина массива равна $n$ ; тогда шаги сортировки пузырьком показаны на рисунке ниже. 1. Сначала выполнить один проход "всплытия" по $n$ элементам, **переместив максимальный элемент массива на правильную позицию**. 2. Затем выполнить "всплытие" по оставшимся $n - 1$ элементам, **переместив второй по величине элемент на правильную позицию**. 3. Продолжать по аналогии; после $n - 1$ раундов "всплытия" **первые $n - 1$ по величине элементы окажутся на правильных позициях**. 4. Оставшийся единственный элемент обязательно является минимальным, сортировать его уже не нужно, поэтому сортировка завершена. ![Процесс сортировки пузырьком](bubble_sort.assets/bubble_sort_overview.png) Пример кода: ```src [file]{bubble_sort}-[class]{}-[func]{bubble_sort} ``` ## Оптимизация эффективности Если в каком-либо раунде "всплытия" не произошло ни одного обмена, значит, массив уже отсортирован и можно сразу вернуть результат. Поэтому можно добавить флаг `flag` для отслеживания этой ситуации и немедленного выхода. После такой оптимизации худшая и средняя временные сложности сортировки пузырьком по-прежнему равны $O(n^2)$ ; однако если входной массив уже полностью упорядочен, достигается лучшая временная сложность $O(n)$ . ```src [file]{bubble_sort}-[class]{}-[func]{bubble_sort_with_flag} ``` ## Характеристики алгоритма - **Временная сложность равна $O(n^2)$, алгоритм адаптивен**: длины диапазонов, проходящих "всплытие" в разных раундах, последовательно равны $n - 1$, $n - 2$, $\dots$, $2$, $1$ , а их сумма равна $(n - 1) n / 2$ . После добавления оптимизации с `flag` лучшая временная сложность может достигать $O(n)$ . - **Пространственная сложность равна $O(1)$, сортировка выполняется на месте**: указатели $i$ и $j$ используют константный объем дополнительной памяти. - **Стабильная сортировка**: поскольку при "всплытии" равные элементы не обмениваются местами. ================================================ FILE: ru/docs/chapter_sorting/bucket_sort.md ================================================ # Блочная сортировка Рассмотренные выше алгоритмы сортировки относятся к "сортировкам на основе сравнений": они упорядочивают данные, сравнивая элементы друг с другом. Временная сложность таких алгоритмов не может быть лучше $O(n \log n)$ . Далее мы рассмотрим несколько "сортировок без сравнений", чья временная сложность может достигать линейного порядка. Блочная сортировка (bucket sort) является типичным применением стратегии "разделяй и властвуй". Она создает набор упорядоченных по величине блоков, где каждый блок соответствует определенному диапазону данных; затем элементы равномерно распределяются по этим блокам, внутри каждого блока отдельно выполняется сортировка, а в конце результаты объединяются в порядке блоков. ## Алгоритм Рассмотрим массив длины $n$, элементы которого являются числами с плавающей запятой из диапазона $[0, 1)$ . Процесс блочной сортировки показан на рисунке ниже. 1. Инициализировать $k$ блоков и распределить $n$ элементов по этим $k$ блокам. 2. Отсортировать каждый блок по отдельности (здесь используется встроенная функция сортировки языка программирования). 3. Объединить результаты в порядке следования блоков от меньшего к большему. ![Процесс блочной сортировки](bucket_sort.assets/bucket_sort_overview.png) Код приведен ниже: ```src [file]{bucket_sort}-[class]{}-[func]{bucket_sort} ``` ## Характеристики алгоритма Блочная сортировка подходит для обработки очень больших объемов данных. Например, если вход содержит 1 миллион элементов и из-за ограничений памяти система не может загрузить их все сразу, можно разбить данные на 1000 блоков, отсортировать каждый блок отдельно, а затем объединить результаты. - **Временная сложность равна $O(n + k)$** : если элементы распределены по блокам равномерно, то в каждом блоке будет $\frac{n}{k}$ элементов. Если сортировка одного блока требует $O(\frac{n}{k} \log\frac{n}{k})$ времени, то сортировка всех блоков потребует $O(n \log\frac{n}{k})$ времени. **Когда число блоков $k$ достаточно велико, временная сложность приближается к $O(n)$** . На объединение результатов требуется $O(n + k)$ времени, потому что нужно пройти по всем блокам и элементам. В худшем случае все данные попадут в один блок, и если сортировка этого блока использует $O(n^2)$ времени, общая сложность также станет $O(n^2)$ . - **Пространственная сложность равна $O(n + k)$, сортировка не выполняется на месте**: требуются дополнительные блоки в количестве $k$ и место для всех $n$ элементов внутри них. - Является ли блочная сортировка стабильной, зависит от того, стабилен ли алгоритм сортировки внутри каждого блока. ## Как добиться равномерного распределения Теоретически временная сложность блочной сортировки может достигать $O(n)$ ; **ключ к этому - как можно более равномерно распределить элементы по блокам**. На практике данные часто распределены неравномерно. Например, если нужно распределить все товары на маркетплейсе по 10 ценовым блокам, количество товаров дешевле 100 рублей может быть очень большим, а товаров дороже 1000 рублей - очень маленьким. Если просто разбить диапазон цен на 10 равных частей, число товаров в каждом блоке будет сильно различаться. Чтобы добиться более равномерного распределения, можно сначала задать грубую линию раздела и приблизительно распределить данные по 3 блокам. **После этого блоки с большим числом товаров можно снова делить на 3 блока и продолжать процесс до тех пор, пока число элементов в каждом блоке не станет примерно одинаковым**. Как показано на рисунке ниже, по сути этот метод строит рекурсивное дерево, цель которого - сделать значения в листьях как можно более равномерными. Конечно, совсем не обязательно каждый раз делить данные именно на 3 блока; конкретную схему разбиения можно выбирать в зависимости от свойств данных. ![Рекурсивное разбиение по блокам](bucket_sort.assets/scatter_in_buckets_recursively.png) Если нам заранее известна вероятностная модель распределения цен товаров, **то границы цен для каждого блока можно задавать в соответствии с этим распределением**. Важно отметить, что фактическое распределение данных не обязательно специально измерять - его можно приблизить некоторой вероятностной моделью исходя из свойств данных. Как показано на рисунке ниже, если предположить, что цены товаров подчиняются нормальному распределению, то можно разумно задать интервалы цен и тем самым распределить товары по блокам достаточно равномерно. ![Разбиение блоков по вероятностному распределению](bucket_sort.assets/scatter_in_buckets_distribution.png) ================================================ FILE: ru/docs/chapter_sorting/counting_sort.md ================================================ # Сортировка подсчетом Сортировка подсчетом (counting sort) реализует сортировку за счет подсчета количества вхождений элементов и обычно используется для массивов целых чисел. ## Простая реализация Сначала рассмотрим простой пример. Дан массив `nums` длины $n$ , элементы которого являются "неотрицательными целыми числами". Общий процесс сортировки подсчетом показан на рисунке ниже. 1. Пройти по массиву, найти в нем максимальное число, обозначить его как $m$ , а затем создать вспомогательный массив `counter` длины $m + 1$ . 2. **С помощью `counter` подсчитать, сколько раз каждое число встречается в `nums`**; при этом `counter[num]` хранит число вхождений значения `num` . Делается это просто: достаточно пройти по `nums` (пусть текущее число равно `num` ) и на каждом шаге увеличить `counter[num]` на $1$ . 3. **Поскольку индексы массива `counter` изначально упорядочены, можно считать, что все числа уже отсортированы**. Далее остается пройти по `counter` и в соответствии с числом вхождений записать значения обратно в `nums` в порядке возрастания. ![Процесс сортировки подсчетом](counting_sort.assets/counting_sort_overview.png) Код приведен ниже: ```src [file]{counting_sort}-[class]{}-[func]{counting_sort_naive} ``` !!! note "Связь между сортировкой подсчетом и блочной сортировкой" Если посмотреть на сортировку подсчетом с точки зрения блочной сортировки, то каждый индекс массива `counter` можно рассматривать как отдельный блок, а процесс подсчета - как распределение элементов по соответствующим блокам. Иными словами, сортировка подсчетом является частным случаем блочной сортировки для целочисленных данных. ## Полная реализация Внимательный читатель мог заметить, что **если входные данные представлены объектами, то описанный выше шаг `3.` перестает работать**. Например, если входными данными являются объекты товаров и мы хотим отсортировать их по цене (полю класса), то описанный алгоритм сможет выдать только отсортированный ряд цен, но не исходные объекты в нужном порядке. Как же получить корректный порядок исходных данных? Сначала вычислим "префиксную сумму" массива `counter` . Как следует из названия, префиксная сумма в индексе `i` , обозначаемая как `prefix[i]` , равна сумме первых `i` элементов массива: $$ \text{prefix}[i] = \sum_{j=0}^i \text{counter[j]} $$ **Префиксная сумма имеет четкий смысл: `prefix[num] - 1` обозначает индекс последнего вхождения элемента `num` в результирующем массиве `res`**. Это очень важная информация, потому что она указывает, в какую позицию результирующего массива должен попасть каждый элемент. Далее мы проходим исходный массив `nums` в обратном порядке и на каждой итерации для очередного элемента `num` выполняем два действия. 1. Записать `num` в массив `res` по индексу `prefix[num] - 1` . 2. Уменьшить префиксную сумму `prefix[num]` на $1$ , чтобы получить индекс следующего размещения элемента `num` . После завершения прохода массив `res` будет содержать отсортированный результат; остается только переписать `res` обратно в `nums` . Полный процесс сортировки подсчетом показан на рисунке ниже. === "<1>" ![Шаги сортировки подсчетом](counting_sort.assets/counting_sort_step1.png) === "<2>" ![counting_sort_step2](counting_sort.assets/counting_sort_step2.png) === "<3>" ![counting_sort_step3](counting_sort.assets/counting_sort_step3.png) === "<4>" ![counting_sort_step4](counting_sort.assets/counting_sort_step4.png) === "<5>" ![counting_sort_step5](counting_sort.assets/counting_sort_step5.png) === "<6>" ![counting_sort_step6](counting_sort.assets/counting_sort_step6.png) === "<7>" ![counting_sort_step7](counting_sort.assets/counting_sort_step7.png) === "<8>" ![counting_sort_step8](counting_sort.assets/counting_sort_step8.png) Код реализации сортировки подсчетом приведен ниже: ```src [file]{counting_sort}-[class]{}-[func]{counting_sort} ``` ## Характеристики алгоритма - **Временная сложность равна $O(n + m)$, алгоритм не является адаптивным** : необходимо пройти по `nums` и по `counter` , а оба этих прохода занимают линейное время. Обычно выполняется $n \gg m$ , поэтому временная сложность стремится к $O(n)$ . - **Пространственная сложность равна $O(n + m)$, сортировка не выполняется на месте**: используются массивы `res` и `counter` длины $n$ и $m$ соответственно. - **Стабильная сортировка**: порядок заполнения `res` идет "справа налево", поэтому обратный проход по `nums` позволяет сохранить относительный порядок равных элементов и тем самым реализовать стабильную сортировку. Вообще говоря, прямой проход по `nums` тоже даст правильный результат сортировки, но он будет нестабильным. ## Ограничения На этом этапе сортировка подсчетом может показаться очень изящной: она позволяет эффективно сортировать данные, опираясь только на подсчет числа вхождений. Однако условия ее применения довольно строгие. **Сортировка подсчетом применима только к неотрицательным целым числам**. Чтобы использовать ее для других типов данных, нужно убедиться, что эти данные можно преобразовать в неотрицательные целые числа и что при преобразовании относительный порядок элементов не изменится. Например, для массива целых чисел с отрицательными значениями можно сначала прибавить ко всем числам константу, превратив их в положительные, затем выполнить сортировку и после этого преобразовать значения обратно. **Сортировка подсчетом подходит для случаев, когда объем данных велик, но диапазон значений невелик**. Например, в приведенном выше примере $m$ не должно быть слишком большим, иначе будет занято слишком много памяти. А когда $n \ll m$ , сортировка подсчетом использует $O(m)$ времени и может оказаться медленнее, чем алгоритмы сортировки с $O(n \log n)$ . ================================================ FILE: ru/docs/chapter_sorting/heap_sort.md ================================================ # Пирамидальная сортировка !!! tip Перед чтением этого раздела убедитесь, что вы уже изучили главу "Куча". Пирамидальная сортировка (heap sort) - это эффективный алгоритм сортировки, основанный на структуре данных "куча". Для его реализации можно использовать уже изученные нами "построение кучи" и "извлечение элементов из кучи". 1. Подать на вход массив и построить из него мин-кучу; в этот момент минимальный элемент будет находиться в вершине кучи. 2. Непрерывно выполнять извлечение из кучи и по порядку записывать извлеченные элементы - так получится последовательность, отсортированная по возрастанию. Хотя этот метод и работоспособен, он требует дополнительного массива для хранения извлеченных элементов и потому расходует лишнюю память. На практике обычно используют более изящную реализацию. ## Алгоритм Пусть длина массива равна $n$ ; тогда процесс пирамидальной сортировки показан на рисунке ниже. 1. Подать на вход массив и построить из него макс-кучу. После этого максимальный элемент окажется в вершине кучи. 2. Обменять элемент в вершине кучи (первый элемент) с элементом внизу кучи (последний элемент). После обмена длина кучи уменьшается на $1$ , а число уже отсортированных элементов увеличивается на $1$ . 3. Начиная с вершины, выполнить операцию просеивания сверху вниз. После этого свойство кучи будет восстановлено. 4. Циклически повторять шаг `2.` и шаг `3.` . После $n - 1$ раундов массив будет полностью отсортирован. !!! tip На самом деле операция извлечения из кучи тоже включает шаг `2.` и шаг `3.` , только дополнительно содержит действие по удалению элемента. === "<1>" ![Шаги пирамидальной сортировки](heap_sort.assets/heap_sort_step1.png) === "<2>" ![heap_sort_step2](heap_sort.assets/heap_sort_step2.png) === "<3>" ![heap_sort_step3](heap_sort.assets/heap_sort_step3.png) === "<4>" ![heap_sort_step4](heap_sort.assets/heap_sort_step4.png) === "<5>" ![heap_sort_step5](heap_sort.assets/heap_sort_step5.png) === "<6>" ![heap_sort_step6](heap_sort.assets/heap_sort_step6.png) === "<7>" ![heap_sort_step7](heap_sort.assets/heap_sort_step7.png) === "<8>" ![heap_sort_step8](heap_sort.assets/heap_sort_step8.png) === "<9>" ![heap_sort_step9](heap_sort.assets/heap_sort_step9.png) === "<10>" ![heap_sort_step10](heap_sort.assets/heap_sort_step10.png) === "<11>" ![heap_sort_step11](heap_sort.assets/heap_sort_step11.png) === "<12>" ![heap_sort_step12](heap_sort.assets/heap_sort_step12.png) В коде используется та же функция просеивания сверху вниз `sift_down()`, что и в главе "Куча". Важно помнить, что длина кучи уменьшается по мере извлечения максимального элемента, поэтому функции `sift_down()` нужно передавать параметр длины $n$ , чтобы указать текущую действительную длину кучи. Код приведен ниже: ```src [file]{heap_sort}-[class]{}-[func]{heap_sort} ``` ## Характеристики алгоритма - **Временная сложность равна $O(n \log n)$, алгоритм не является адаптивным**: построение кучи занимает $O(n)$ времени. Извлечение максимального элемента из кучи имеет временную сложность $O(\log n)$ и выполняется $n - 1$ раз. - **Пространственная сложность равна $O(1)$, сортировка выполняется на месте**: несколько переменных-указателей используют $O(1)$ памяти. Обмен элементов и операции просеивания выполняются прямо в исходном массиве. - **Нестабильная сортировка**: при обмене вершины кучи и нижнего элемента относительный порядок равных элементов может измениться. ================================================ FILE: ru/docs/chapter_sorting/index.md ================================================ # Сортировка ![Сортировка](../assets/covers/chapter_sorting.jpg) !!! abstract Сортировка упорядочивает хаотичные данные и позволяет быстрее находить закономерности. За кажущейся простотой скрывается целая группа алгоритмов с разными достоинствами и ограничениями. ================================================ FILE: ru/docs/chapter_sorting/insertion_sort.md ================================================ # Сортировка вставками Сортировка вставками (insertion sort) - это простой алгоритм сортировки, принцип которого очень похож на ручную сортировку карт в колоде. Точнее говоря, в неотсортированном диапазоне выбирается опорный элемент, после чего он сравнивается с элементами слева в уже отсортированном диапазоне и вставляется в правильную позицию. На рисунке ниже показан процесс вставки элемента в массив. Пусть опорный элемент обозначен как `base` ; нам нужно сдвинуть все элементы от целевого индекса до `base` на одну позицию вправо, а затем записать `base` в целевой индекс. ![Одна операция вставки](insertion_sort.assets/insertion_operation.png) ## Алгоритм Общий процесс сортировки вставками показан на рисунке ниже. 1. В начальном состоянии отсортирован только первый элемент массива. 2. Выбрать второй элемент массива как `base` ; после вставки в правильную позицию **первые два элемента массива окажутся отсортированными**. 3. Выбрать третий элемент как `base` ; после вставки в правильную позицию **первые три элемента массива окажутся отсортированными**. 4. Продолжать по аналогии; в последнем раунде в качестве `base` берется последний элемент, и после его вставки **все элементы массива будут отсортированы**. ![Процесс сортировки вставками](insertion_sort.assets/insertion_sort_overview.png) Пример кода: ```src [file]{insertion_sort}-[class]{}-[func]{insertion_sort} ``` ## Характеристики алгоритма - **Временная сложность равна $O(n^2)$, алгоритм адаптивен**: в худшем случае каждой операции вставки требуется соответственно $n - 1$, $n-2$, $\dots$, $2$, $1$ итераций, а их сумма равна $(n - 1) n / 2$ , поэтому временная сложность равна $O(n^2)$ . Если входные данные уже упорядочены, операция вставки завершается раньше. Когда входной массив полностью отсортирован, сортировка вставками достигает лучшей временной сложности $O(n)$ . - **Пространственная сложность равна $O(1)$, сортировка выполняется на месте**: указатели $i$ и $j$ используют константный объем дополнительной памяти. - **Стабильная сортировка**: в процессе вставки элементы помещаются справа от равных им элементов, поэтому их относительный порядок не меняется. ## Преимущества сортировки вставками Временная сложность сортировки вставками равна $O(n^2)$ , а у быстрой сортировки, которую мы скоро изучим, временная сложность равна $O(n \log n)$ . Несмотря на более высокую асимптотическую сложность, **на малых объемах данных сортировка вставками обычно работает быстрее**. Этот вывод похож на сравнение линейного и двоичного поиска. Алгоритмы уровня $O(n \log n)$ , такие как быстрая сортировка, относятся к алгоритмам на основе стратегии "разделяй и властвуй" и обычно включают больше элементарных вычислений. Когда объем данных мал, значения $n^2$ и $n \log n$ близки друг к другу, поэтому асимптотика не доминирует, а решающим становится число элементарных операций в каждом раунде. На практике встроенные функции сортировки во многих языках программирования (например, в Java) используют сортировку вставками. Общая идея такова: для длинных массивов применять алгоритмы сортировки на основе стратегии "разделяй и властвуй", например быструю сортировку; для коротких массивов сразу использовать сортировку вставками. Хотя сортировка пузырьком, выбором и вставками имеют одинаковую временную сложность $O(n^2)$ , в реальных задачах **сортировка вставками используется заметно чаще, чем сортировка пузырьком и сортировка выбором**. Основные причины таковы. - Сортировка пузырьком основана на обмене элементов, для чего нужна временная переменная и суммарно выполняются 3 элементарные операции; сортировка вставками основана на присваивании элементов и требует всего 1 элементарной операции. Поэтому **вычислительные затраты сортировки пузырьком обычно выше, чем у сортировки вставками**. - Временная сложность сортировки выбором в любом случае равна $O(n^2)$ . **Если входные данные уже частично упорядочены, сортировка вставками обычно эффективнее сортировки выбором**. - Сортировка выбором нестабильна, поэтому ее нельзя использовать для многоуровневой сортировки. ================================================ FILE: ru/docs/chapter_sorting/merge_sort.md ================================================ # Сортировка слиянием Сортировка слиянием (merge sort) - это алгоритм сортировки, основанный на стратегии "разделяй и властвуй", который включает этапы "разделения" и "слияния", показанные на рисунке ниже. 1. **Этап разделения**: массив рекурсивно делится пополам, и задача сортировки длинного массива превращается в задачи сортировки более коротких массивов. 2. **Этап слияния**: когда длина подмассива становится равной 1, разделение завершается и начинается слияние; два коротких упорядоченных массива непрерывно объединяются в один более длинный упорядоченный массив, пока процесс не завершится. ![Этапы разделения и слияния в сортировке слиянием](merge_sort.assets/merge_sort_overview.png) ## Алгоритм Как показано на рисунке ниже, на этапе "разделения" массив рекурсивно разбивается сверху вниз по середине на два подмассива. 1. Вычислить середину массива `mid` и рекурсивно разделить левый подмассив (интервал `[left, mid]` ) и правый подмассив (интервал `[mid + 1, right]` ). 2. Рекурсивно повторять шаг `1.` , пока длина подмассива не станет равной 1. Этап "слияния" снизу вверх объединяет левый и правый подмассивы в один упорядоченный массив. Следует заметить, что начиная с подмассивов длины 1, каждый подмассив в фазе слияния уже является упорядоченным. === "<1>" ![Шаги сортировки слиянием](merge_sort.assets/merge_sort_step1.png) === "<2>" ![merge_sort_step2](merge_sort.assets/merge_sort_step2.png) === "<3>" ![merge_sort_step3](merge_sort.assets/merge_sort_step3.png) === "<4>" ![merge_sort_step4](merge_sort.assets/merge_sort_step4.png) === "<5>" ![merge_sort_step5](merge_sort.assets/merge_sort_step5.png) === "<6>" ![merge_sort_step6](merge_sort.assets/merge_sort_step6.png) === "<7>" ![merge_sort_step7](merge_sort.assets/merge_sort_step7.png) === "<8>" ![merge_sort_step8](merge_sort.assets/merge_sort_step8.png) === "<9>" ![merge_sort_step9](merge_sort.assets/merge_sort_step9.png) === "<10>" ![merge_sort_step10](merge_sort.assets/merge_sort_step10.png) Нетрудно заметить, что порядок рекурсии в сортировке слиянием совпадает с порядком обхода в глубину двоичного дерева. - **Обход в глубину**: сначала рекурсивно обходится левое поддерево, затем правое поддерево, а в конце обрабатывается корневой узел. - **Сортировка слиянием**: сначала рекурсивно разделяется левый подмассив, затем правый подмассив, а в конце выполняется слияние. Реализация сортировки слиянием показана в коде ниже. Обратите внимание: в `nums` объединяемый интервал равен `[left, right]` , а соответствующий интервал в `tmp` равен `[0, right - left]` . ```src [file]{merge_sort}-[class]{}-[func]{merge_sort} ``` ## Характеристики алгоритма - **Временная сложность равна $O(n \log n)$, алгоритм не является адаптивным**: этап разделения создает дерево рекурсии высоты $\log n$ , а суммарное число операций слияния на каждом уровне равно $n$ , поэтому общая временная сложность составляет $O(n \log n)$ . - **Пространственная сложность равна $O(n)$, сортировка не выполняется на месте**: глубина рекурсии равна $\log n$ , из-за чего требуется $O(\log n)$ памяти под стек вызовов. Для этапа слияния нужен вспомогательный массив, поэтому дополнительно используется $O(n)$ памяти. - **Стабильная сортировка**: в процессе слияния относительный порядок равных элементов не меняется. ## Сортировка связного списка Для связных списков сортировка слиянием имеет заметное преимущество перед другими алгоритмами сортировки: **пространственную сложность задачи сортировки списка можно оптимизировать до $O(1)$**. - **Этап разделения**: работу по разбиению списка можно реализовать с помощью "итерации" вместо "рекурсии", тем самым устранив расход памяти на стек вызовов. - **Этап слияния**: в связном списке добавление и удаление узлов требует только изменения ссылок (указателей), поэтому при слиянии двух коротких упорядоченных списков в один длинный упорядоченный список не нужно создавать дополнительный список. Детали реализации достаточно сложны; заинтересованные читатели могут обратиться к соответствующим материалам самостоятельно. ================================================ FILE: ru/docs/chapter_sorting/quick_sort.md ================================================ # Быстрая сортировка Быстрая сортировка (quick sort) - это алгоритм сортировки, основанный на стратегии "разделяй и властвуй"; он работает эффективно и применяется очень широко. Ключевая операция быстрой сортировки - это "разделение с опорным элементом". Ее цель такова: выбрать некоторый элемент массива в качестве "опорного" и переместить все элементы меньше опорного влево от него, а все элементы больше опорного - вправо. Конкретный процесс показан на рисунке ниже. 1. Выбрать самый левый элемент массива как опорный и инициализировать два указателя `i` и `j` , направленные на левую и правую границы массива. 2. Запустить цикл, в котором `i` и `j` ищут соответственно первый элемент, больший опорного, и первый элемент, меньший опорного, после чего эти два элемента меняются местами. 3. Повторять шаг `2.` , пока указатели `i` и `j` не встретятся, а затем обменять опорный элемент с элементом на границе двух подмассивов. === "<1>" ![Шаги разделения с опорным элементом](quick_sort.assets/pivot_division_step1.png) === "<2>" ![pivot_division_step2](quick_sort.assets/pivot_division_step2.png) === "<3>" ![pivot_division_step3](quick_sort.assets/pivot_division_step3.png) === "<4>" ![pivot_division_step4](quick_sort.assets/pivot_division_step4.png) === "<5>" ![pivot_division_step5](quick_sort.assets/pivot_division_step5.png) === "<6>" ![pivot_division_step6](quick_sort.assets/pivot_division_step6.png) === "<7>" ![pivot_division_step7](quick_sort.assets/pivot_division_step7.png) === "<8>" ![pivot_division_step8](quick_sort.assets/pivot_division_step8.png) === "<9>" ![pivot_division_step9](quick_sort.assets/pivot_division_step9.png) После завершения разделения исходный массив разбивается на три части: левый подмассив, опорный элемент и правый подмассив; при этом выполняется условие "любой элемент левого подмассива $\leq$ опорный элемент $\leq$ любой элемент правого подмассива". Следовательно, далее нам нужно лишь отсортировать эти два подмассива. !!! note "Стратегия разделяй и властвуй в быстрой сортировке" Иными словами, разделение с опорным элементом сводит задачу сортировки длинного массива к двум задачам сортировки более коротких массивов. ```src [file]{quick_sort}-[class]{quick_sort}-[func]{partition} ``` ## Алгоритм Общий процесс быстрой сортировки показан на рисунке ниже. 1. Сначала выполнить "разделение с опорным элементом" для исходного массива и получить неотсортированные левый и правый подмассивы. 2. Затем рекурсивно выполнить "разделение с опорным элементом" для левого и правого подмассивов. 3. Продолжать рекурсию до тех пор, пока длина подмассива не станет равной 1; после этого сортировка всего массива будет завершена. ![Процесс быстрой сортировки](quick_sort.assets/quick_sort_overview.png) ```src [file]{quick_sort}-[class]{quick_sort}-[func]{quick_sort} ``` ## Характеристики алгоритма - **Временная сложность равна $O(n \log n)$, алгоритм не является адаптивным**: в среднем глубина рекурсии при разделении равна $\log n$ , а суммарное число циклов на каждом уровне равно $n$ , поэтому общая сложность составляет $O(n \log n)$ . В худшем случае каждое разделение делит массив длины $n$ на подмассивы длины $0$ и $n - 1$ ; тогда глубина рекурсии достигает $n$ , на каждом уровне выполняется $n$ операций, и общая временная сложность вырождается в $O(n^2)$ . - **Пространственная сложность равна $O(n)$, сортировка выполняется на месте**: если входной массив полностью отсортирован в обратном порядке, глубина рекурсии достигает худшего случая $n$ , что требует $O(n)$ памяти под стек вызовов. При этом сама сортировка выполняется в исходном массиве без дополнительного массива. - **Нестабильная сортировка**: на последнем шаге разделения опорный элемент может быть обменян вправо от равного ему элемента. ## Почему быстрая сортировка быстрая Уже по названию понятно, что быстрая сортировка должна иметь преимущества по эффективности. Хотя ее средняя временная сложность совпадает со сложностью "сортировки слиянием" и "пирамидальной сортировки", на практике быстрая сортировка обычно работает быстрее. Основные причины таковы. - **Вероятность худшего случая очень мала**: хотя худшая временная сложность быстрой сортировки равна $O(n^2)$ и она не так стабильна, как сортировка слиянием, в подавляющем большинстве случаев она работает за $O(n \log n)$ . - **Высокая эффективность использования кэша**: при выполнении разделения система может загрузить весь подмассив в кэш, поэтому доступ к элементам оказывается быстрым. Алгоритмы вроде "пирамидальной сортировки" требуют скачкообразного доступа к элементам и таким свойством не обладают. - **Небольшой константный множитель в сложности**: среди трех перечисленных алгоритмов у быстрой сортировки обычно меньше всего сравнений, присваиваний и обменов. Это похоже на причину, по которой "сортировка вставками" часто быстрее "сортировки пузырьком". ## Оптимизация выбора опорного элемента **На некоторых входных данных временная эффективность быстрой сортировки может ухудшаться**. Рассмотрим крайний случай: входной массив полностью отсортирован в обратном порядке. Поскольку в качестве опорного мы выбираем самый левый элемент, после разделения он будет обменян в самый правый конец массива, из-за чего длина левого подмассива станет $n - 1$ , а длина правого - $0$ . Если рекурсия будет продолжаться таким образом, то после каждого разделения один из подмассивов будет иметь длину $0$ , стратегия "разделяй и властвуй" потеряет смысл, а быстрая сортировка выродится в нечто близкое к "сортировке пузырьком". Чтобы по возможности избежать такого сценария, **можно улучшить стратегию выбора опорного элемента в процедуре разделения**. Например, можно выбирать случайный элемент массива как опорный. Однако если не повезет и каждый раз будет выбираться неудачный опорный элемент, производительность все равно останется неудовлетворительной. Стоит учитывать, что языки программирования обычно генерируют псевдослучайные числа. Если специально построить тестовый пример под такую последовательность, эффективность быстрой сортировки все равно может деградировать. Чтобы улучшить ситуацию, можно взять три кандидата (обычно первый, последний и средний элементы массива) и **использовать медиану этих трех значений как опорный элемент**. Благодаря этому вероятность того, что опорный элемент окажется "не слишком маленьким и не слишком большим", заметно возрастает. Конечно, можно брать и большее число кандидатов, чтобы еще сильнее повысить устойчивость алгоритма. После этого вероятность деградации временной сложности до $O(n^2)$ существенно уменьшается. Пример кода: ```src [file]{quick_sort}-[class]{quick_sort_median}-[func]{partition} ``` ## Оптимизация глубины рекурсии **На некоторых входных данных быстрая сортировка может занимать слишком много памяти**. Рассмотрим полностью отсортированный входной массив. Пусть длина текущего подмассива в рекурсии равна $m$ ; тогда после каждого разделения будут получаться левый подмассив длины $0$ и правый подмассив длины $m - 1$ . Это означает, что на каждом уровне размер задачи уменьшается совсем немного (лишь на один элемент), а высота дерева рекурсии достигает $n - 1$ , поэтому требуется $O(n)$ памяти под стек вызовов. Чтобы избежать накопления стековых кадров, после каждого разделения можно сравнивать длины двух подмассивов и **рекурсивно обрабатывать только более короткий из них**. Поскольку длина короткого подмассива не превысит $n / 2$ , такой подход гарантирует, что глубина рекурсии не превысит $\log n$ , а худшая пространственная сложность будет оптимизирована до $O(\log n)$ . Код приведен ниже: ```src [file]{quick_sort}-[class]{quick_sort_tail_call}-[func]{quick_sort} ``` ================================================ FILE: ru/docs/chapter_sorting/radix_sort.md ================================================ # Поразрядная сортировка В предыдущем разделе была рассмотрена сортировка подсчетом: она хорошо подходит для случаев, когда объем данных $n$ велик, а диапазон значений $m$ сравнительно мал. Предположим теперь, что нужно отсортировать $n = 10^6$ номеров студентов, причем каждый номер представляет собой $8$-значное число. Тогда диапазон данных $m = 10^8$ оказывается очень большим; сортировка подсчетом потребует огромного объема памяти, а поразрядная сортировка позволяет этого избежать. Поразрядная сортировка (radix sort) по своей основной идее совпадает с сортировкой подсчетом и тоже реализует сортировку через подсчет количества. При этом поразрядная сортировка использует соотношение между разрядами числа и последовательно сортирует данные по каждому разряду, получая итоговый упорядоченный результат. ## Алгоритм Рассмотрим пример со студенческими номерами: будем считать, что младший разряд имеет номер $1$ , а старший - номер $8$ . Тогда процесс поразрядной сортировки показан на рисунке ниже. 1. Инициализировать номер разряда $k = 1$ . 2. Выполнить "сортировку подсчетом" по $k$-му разряду студенческого номера. После этого данные будут упорядочены по $k$-му разряду по возрастанию. 3. Увеличить $k$ на $1$ и вернуться к шагу `2.` , продолжая процесс, пока сортировка не будет выполнена для всех разрядов. ![Процесс поразрядной сортировки](radix_sort.assets/radix_sort_overview.png) Ниже разберем реализацию кода. Для числа $x$ в системе счисления с основанием $d$ получить его $k$-й разряд $x_k$ можно по формуле: $$ x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d $$ где $\lfloor a \rfloor$ обозначает округление числа $a$ вниз, а $\bmod \: d$ означает взятие остатка по модулю $d$ . Для студенческих номеров выполняется $d = 10$ и $k \in [1, 8]$ . Кроме того, нам нужно слегка изменить код сортировки подсчетом, чтобы он мог сортировать числа по их $k$-му разряду: ```src [file]{radix_sort}-[class]{}-[func]{radix_sort} ``` !!! question "Почему сортировка выполняется от младшего разряда к старшему?" В последовательных раундах сортировки результаты более позднего раунда перекрывают результаты предыдущего. Например, если после первого раунда получилось $a < b$ , а после второго - $a > b$ , то именно результат второго раунда станет окончательным. Поскольку старшие разряды имеют более высокий приоритет, сначала нужно сортировать по младшим разрядам, а затем по старшим. ## Характеристики алгоритма По сравнению с сортировкой подсчетом поразрядная сортировка подходит для случаев с большим диапазоном чисел, **но только при условии, что данные можно представить в виде чисел фиксированной длины и число разрядов не слишком велико**. Например, числа с плавающей запятой плохо подходят для поразрядной сортировки, потому что число разрядов $k$ слишком велико и может привести к ситуации $O(nk) \gg O(n^2)$ . - **Временная сложность равна $O(nk)$, алгоритм не является адаптивным**: пусть объем данных равен $n$ , числа записаны в системе счисления с основанием $d$ , а максимальное число разрядов равно $k$ . Тогда выполнение сортировки подсчетом для одного разряда требует $O(n + d)$ времени, а сортировка по всем $k$ разрядам требует $O((n + d)k)$ времени. Обычно $d$ и $k$ сравнительно малы, поэтому временная сложность стремится к $O(n)$ . - **Пространственная сложность равна $O(n + d)$, сортировка не выполняется на месте**: как и в сортировке подсчетом, здесь требуются массивы `res` и `counter` длины $n$ и $d$ . - **Стабильная сортировка**: если сортировка подсчетом стабильна, то и поразрядная сортировка стабильна; если же сортировка подсчетом нестабильна, поразрядная сортировка не может гарантировать корректный результат. ================================================ FILE: ru/docs/chapter_sorting/selection_sort.md ================================================ # Сортировка выбором Сортировка выбором (selection sort) работает очень просто: запускается цикл, и на каждом шаге из неотсортированного диапазона выбирается минимальный элемент, после чего он переносится в конец уже отсортированного диапазона. Пусть длина массива равна $n$ ; тогда процесс сортировки выбором выглядит так, как показано на рисунке ниже. 1. В начальном состоянии все элементы не отсортированы, то есть неотсортированный диапазон индексов равен $[0, n-1]$ . 2. Выбрать минимальный элемент из диапазона $[0, n-1]$ и поменять его местами с элементом в позиции $0$ . После этого первый элемент массива отсортирован. 3. Выбрать минимальный элемент из диапазона $[1, n-1]$ и поменять его местами с элементом в позиции $1$ . После этого первые два элемента массива отсортированы. 4. Продолжать по аналогии. После $n - 1$ раундов выбора и обмена первые $n - 1$ элементов массива будут отсортированы. 5. Оставшийся элемент обязательно является максимальным, сортировать его не нужно, поэтому массив считается отсортированным. === "<1>" ![Шаги сортировки выбором](selection_sort.assets/selection_sort_step1.png) === "<2>" ![selection_sort_step2](selection_sort.assets/selection_sort_step2.png) === "<3>" ![selection_sort_step3](selection_sort.assets/selection_sort_step3.png) === "<4>" ![selection_sort_step4](selection_sort.assets/selection_sort_step4.png) === "<5>" ![selection_sort_step5](selection_sort.assets/selection_sort_step5.png) === "<6>" ![selection_sort_step6](selection_sort.assets/selection_sort_step6.png) === "<7>" ![selection_sort_step7](selection_sort.assets/selection_sort_step7.png) === "<8>" ![selection_sort_step8](selection_sort.assets/selection_sort_step8.png) === "<9>" ![selection_sort_step9](selection_sort.assets/selection_sort_step9.png) === "<10>" ![selection_sort_step10](selection_sort.assets/selection_sort_step10.png) === "<11>" ![selection_sort_step11](selection_sort.assets/selection_sort_step11.png) В коде мы используем $k$ для записи минимального элемента в пределах неотсортированного диапазона: ```src [file]{selection_sort}-[class]{}-[func]{selection_sort} ``` ## Характеристики алгоритма - **Временная сложность равна $O(n^2)$, сортировка не является адаптивной**: внешний цикл выполняется $n - 1$ раз; в первом раунде длина неотсортированного диапазона равна $n$ , а в последнем - $2$ , то есть отдельные раунды содержат $n$, $n - 1$, $\dots$, $3$, $2$ проходов внутреннего цикла, их сумма равна $\frac{(n - 1)(n + 2)}{2}$ . - **Пространственная сложность равна $O(1)$, сортировка выполняется на месте**: указатели $i$ и $j$ используют константный объем дополнительной памяти. - **Нестабильная сортировка**: как показано на рисунке ниже, элемент `nums[i]` может быть переставлен вправо от другого равного ему элемента, из-за чего их относительный порядок изменится. ![Пример нестабильности сортировки выбором](selection_sort.assets/selection_sort_instability.png) ================================================ FILE: ru/docs/chapter_sorting/sorting_algorithm.md ================================================ # Алгоритмы сортировки Алгоритмы сортировки (sorting algorithm) используются для упорядочивания набора данных по определенному правилу. Они применяются очень широко, потому что упорядоченные данные обычно проще анализировать, обрабатывать и искать в них нужные элементы. Как показано на рисунке ниже, данными в алгоритмах сортировки могут быть целые числа, числа с плавающей запятой, символы, строки и другие типы. Критерий сравнения тоже можно задать по-разному, например по величине чисел, по порядку ASCII-кодов символов или по пользовательскому правилу. ![Примеры типов данных и правил сравнения](sorting_algorithm.assets/sorting_examples.png) ## Критерии оценки **Скорость выполнения**: мы ожидаем, что временная сложность алгоритма сортировки будет как можно ниже, а общее число операций будет как можно меньше (то есть константа во временной сложности будет небольшой). Для больших объемов данных этот критерий особенно важен. **Сортировка на месте**: как следует из названия, сортировка на месте выполняется прямо в исходном массиве и не требует дополнительного вспомогательного массива, что позволяет экономить память. Обычно при сортировке на месте переносов данных меньше, а скорость работы выше. **Стабильность**: стабильная сортировка после завершения не меняет относительный порядок одинаковых элементов в массиве. Стабильность является необходимым условием для многоуровневой сортировки. Предположим, у нас есть таблица со сведениями о студентах, где в первом и втором столбцах записаны имя и возраст. В этом случае нестабильная сортировка может разрушить уже существующий порядок входных данных: ```shell # Входные данные уже отсортированы по имени # (name, age) ('A', 19) ('B', 18) ('C', 21) ('D', 19) ('E', 23) # Если затем нестабильным алгоритмом отсортировать список по возрасту, # относительный порядок ('D', 19) и ('A', 19) изменится, # и свойство упорядоченности по имени будет потеряно ('B', 18) ('D', 19) ('A', 19) ('C', 21) ('E', 23) ``` **Адаптивность**: адаптивная сортировка умеет использовать уже существующий порядок входных данных, чтобы сократить вычисления и добиться лучшей эффективности. Лучшая временная сложность адаптивных алгоритмов обычно лучше их средней временной сложности. **Основанность на сравнении**: сортировка на основе сравнений использует операторы сравнения ($<$, $=$, $>$), чтобы определить относительный порядок элементов и отсортировать массив; ее теоретически лучшая временная сложность равна $O(n \log n)$ . А вот сортировка без сравнений не опирается на операторы сравнения, поэтому может достигать $O(n)$ , но универсальность у нее ниже. ## Идеальный алгоритм сортировки **Быстрый, выполняющийся на месте, стабильный, адаптивный и универсальный**. Очевидно, что на сегодняшний день не существует алгоритма сортировки, который одновременно обладал бы всеми этими свойствами. Поэтому при выборе алгоритма сортировки нужно исходить из конкретных особенностей данных и требований задачи. Далее мы последовательно изучим разные алгоритмы сортировки и на основании приведенных выше критериев разберем их преимущества и недостатки. ================================================ FILE: ru/docs/chapter_sorting/summary.md ================================================ # Резюме ### Ключевые выводы - Сортировка пузырьком выполняет сортировку за счет обмена соседних элементов. Если добавить флаг для досрочного выхода, лучшую временную сложность пузырьковой сортировки можно оптимизировать до $O(n)$ . - Сортировка вставками на каждом раунде вставляет элемент из неотсортированного диапазона в правильную позицию внутри отсортированного диапазона. Хотя ее временная сложность равна $O(n^2)$ , она очень популярна для задач сортировки небольших массивов, поскольку число элементарных операций у нее сравнительно невелико. - Быстрая сортировка основана на операции разделения с опорным элементом. При неудачном выборе опорного элемента на каждом раунде ее временная сложность может деградировать до $O(n^2)$ . Использование медианы трех элементов или случайного опорного элемента уменьшает вероятность этой деградации. Если всегда рекурсивно обрабатывать более короткий поддиапазон первым, можно эффективно уменьшить глубину рекурсии и оптимизировать пространственную сложность до $O(\log n)$ . - Сортировка слиянием включает этапы разделения и слияния и служит типичным проявлением стратегии "разделяй и властвуй". Для сортировки массива ей требуется вспомогательный массив, поэтому пространственная сложность равна $O(n)$ ; однако при сортировке связного списка пространственную сложность можно оптимизировать до $O(1)$ . - Блочная сортировка включает три этапа: распределение данных по блокам, сортировку внутри блоков и объединение результатов. Она тоже отражает стратегию "разделяй и властвуй" и подходит для очень больших объемов данных. Ключ к эффективности блочной сортировки - равномерное распределение данных. - Сортировка подсчетом является частным случаем блочной сортировки; она реализует сортировку через подсчет числа вхождений данных. Сортировка подсчетом подходит для случаев, когда объем данных велик, но диапазон значений ограничен, и при этом данные можно преобразовать в положительные целые числа. - Поразрядная сортировка выполняет сортировку данных путем последовательной сортировки по каждому разряду и требует, чтобы данные можно было представить в виде чисел фиксированной разрядности. - В общем случае нам хотелось бы найти алгоритм сортировки, который одновременно обладал бы высокой эффективностью, стабильностью, выполнением на месте и адаптивностью. Но, как и в других разделах алгоритмов и структур данных, не существует одного алгоритма сортировки, способного удовлетворить всем этим требованиям одновременно. На практике приходится выбирать подходящий алгоритм в зависимости от свойств данных. - На рисунке ниже сравниваются эффективность, стабильность, выполнение на месте и адаптивность основных алгоритмов сортировки. ![Сравнение алгоритмов сортировки](summary.assets/sorting_algorithms_comparison.png) ### Вопросы и ответы **В**: В каких случаях стабильность алгоритма сортировки является обязательной? В реальных задачах нам может понадобиться сортировать объекты по некоторому атрибуту. Например, у студентов есть два атрибута: имя и рост. Мы хотим выполнить многоуровневую сортировку: сначала отсортировать по имени и получить `(A, 180) (B, 185) (C, 170) (D, 170)` , а затем отсортировать по росту. Если используемый алгоритм сортировки нестабилен, то мы можем получить `(D, 170) (C, 170) (A, 180) (B, 185)` . Нетрудно увидеть, что в этом случае студенты D и C поменялись местами, порядок по имени разрушился, а именно этого мы и не хотим. **В**: Можно ли поменять местами порядок "поиска справа налево" и "поиска слева направо" в разделении с опорным элементом? Нет. Если в качестве опорного элемента выбирается самый левый элемент, необходимо сначала выполнять "поиск справа налево", а уже затем - "поиск слева направо". Этот вывод кажется немного неочевидным, поэтому разберем его подробнее. Последний шаг `partition()` - это обмен `nums[left]` и `nums[i]` . После обмена все элементы слева от опорного должны быть `<=` опорного, **а значит, перед этим обменом должно выполняться условие `nums[left] >= nums[i]`**. Если сначала выполнять "поиск слева направо", то в случае, когда не удается найти элемент больше опорного, **цикл завершится в состоянии `i == j` , и при этом может оказаться, что `nums[j] == nums[i] > nums[left]`**. Иными словами, на последнем шаге обмена элемент, больший опорного, будет помещен в начало массива, из-за чего разделение завершится неверно. Например, для массива `[0, 0, 0, 0, 1]` , если сначала выполнять "поиск слева направо", после разделения получится `[1, 0, 0, 0, 0]` , а это неправильный результат. Если же выбрать `nums[right]` в качестве опорного элемента, то ситуация станет противоположной, и тогда сначала нужно выполнять "поиск слева направо". **В**: Почему при оптимизации глубины рекурсии в быстрой сортировке выбор короткого массива гарантирует, что глубина рекурсии не превысит $\log n$ ? Глубина рекурсии - это число текущих рекурсивных вызовов, которые еще не завершились. На каждом раунде разделения исходный массив разбивается на два подмассива. После оптимизации глубины рекурсии длина подмассива, в который мы продолжаем рекурсивный спуск, не превышает половины длины исходного массива. Если рассматривать худший случай, когда длина каждый раз становится ровно вдвое меньше, итоговая глубина рекурсии и будет равна $\log n$ . В исходной версии быстрой сортировки может происходить последовательный рекурсивный вызов для более длинных массивов; в худшем случае это будут длины $n$ , $n - 1$ , $\dots$ , $2$ , $1$ , а глубина рекурсии окажется равной $n$ . Оптимизация глубины рекурсии как раз и позволяет избежать такого сценария. **В**: Если все элементы массива равны, будет ли временная сложность быстрой сортировки равна $O(n^2)$ ? Как справиться с таким вырождением? Да. Для этого случая можно рассмотреть разделение массива на три части: элементы меньше опорного, равные опорному и большие опорного. Рекурсию нужно продолжать только для частей меньше и больше опорного. При таком подходе массив, целиком состоящий из одинаковых элементов, будет отсортирован всего за один раунд разделения. **В**: Почему худшая временная сложность блочной сортировки равна $O(n^2)$ ? В худшем случае все элементы попадут в один и тот же блок. Если затем сортировать этот блок алгоритмом со сложностью $O(n^2)$ , то общая временная сложность тоже станет $O(n^2)$ . ================================================ FILE: ru/docs/chapter_stack_and_queue/deque.md ================================================ # Двусторонняя очередь В обычной очереди мы можем удалять элементы только из головы и добавлять их только в хвост. Как показано на рисунке ниже, двусторонняя очередь (double-ended queue) обеспечивает большую гибкость и позволяет выполнять добавление и удаление элементов как с головы, так и с хвоста. ![Операции двусторонней очереди](deque.assets/deque_operations.png) ## Основные операции с двусторонней очередью Распространенные операции двусторонней очереди приведены в таблице ниже. Конкретные названия методов зависят от используемого языка программирования.

Таблица   Эффективность операций двусторонней очереди

| Имя метода | Описание | Временная сложность | | ------------ | -------------------------------- | ------------------- | | `push_first()` | Добавить элемент в голову очереди | $O(1)$ | | `push_last()` | Добавить элемент в хвост очереди | $O(1)$ | | `pop_first()` | Удалить элемент из головы очереди | $O(1)$ | | `pop_last()` | Удалить элемент из хвоста очереди | $O(1)$ | | `peek_first()` | Просмотреть элемент в голове очереди | $O(1)$ | | `peek_last()` | Просмотреть элемент в хвосте очереди | $O(1)$ | Точно так же мы можем напрямую использовать уже реализованные в языках программирования классы двусторонней очереди: === "Python" ```python title="deque.py" from collections import deque # Инициализация двусторонней очереди deq: deque[int] = deque() # Поместить элементы в очередь deq.append(2) # Добавить в хвост deq.append(5) deq.append(4) deq.appendleft(3) # Добавить в голову deq.appendleft(1) # Просмотреть элементы front: int = deq[0] # Элемент в голове rear: int = deq[-1] # Элемент в хвосте # Извлечь элементы из очереди pop_front: int = deq.popleft() # Извлечь элемент из головы pop_rear: int = deq.pop() # Извлечь элемент из хвоста # Получить длину двусторонней очереди size: int = len(deq) # Проверить, пуста ли двусторонняя очередь is_empty: bool = len(deq) == 0 ``` === "C++" ```cpp title="deque.cpp" /* Инициализация двусторонней очереди */ deque deque; /* Поместить элементы в очередь */ deque.push_back(2); // Добавить в хвост deque.push_back(5); deque.push_back(4); deque.push_front(3); // Добавить в голову deque.push_front(1); /* Просмотреть элементы */ int front = deque.front(); // Элемент в голове int back = deque.back(); // Элемент в хвосте /* Извлечь элементы из очереди */ deque.pop_front(); // Извлечь элемент из головы deque.pop_back(); // Извлечь элемент из хвоста /* Получить длину двусторонней очереди */ int size = deque.size(); /* Проверить, пуста ли двусторонняя очередь */ bool empty = deque.empty(); ``` === "Java" ```java title="deque.java" /* Инициализация двусторонней очереди */ Deque deque = new LinkedList<>(); /* Поместить элементы в очередь */ deque.offerLast(2); // Добавить в хвост deque.offerLast(5); deque.offerLast(4); deque.offerFirst(3); // Добавить в голову deque.offerFirst(1); /* Просмотреть элементы */ int peekFirst = deque.peekFirst(); // Элемент в голове int peekLast = deque.peekLast(); // Элемент в хвосте /* Извлечь элементы из очереди */ int popFirst = deque.pollFirst(); // Извлечь элемент из головы int popLast = deque.pollLast(); // Извлечь элемент из хвоста /* Получить длину двусторонней очереди */ int size = deque.size(); /* Проверить, пуста ли двусторонняя очередь */ boolean isEmpty = deque.isEmpty(); ``` === "C#" ```csharp title="deque.cs" /* Инициализация двусторонней очереди */ // В C# двустороннюю очередь обычно моделируют через связный список LinkedList LinkedList deque = new(); /* Поместить элементы в очередь */ deque.AddLast(2); // Добавить в хвост deque.AddLast(5); deque.AddLast(4); deque.AddFirst(3); // Добавить в голову deque.AddFirst(1); /* Просмотреть элементы */ int peekFirst = deque.First.Value; // Элемент в голове int peekLast = deque.Last.Value; // Элемент в хвосте /* Извлечь элементы из очереди */ deque.RemoveFirst(); // Извлечь элемент из головы deque.RemoveLast(); // Извлечь элемент из хвоста /* Получить длину двусторонней очереди */ int size = deque.Count; /* Проверить, пуста ли двусторонняя очередь */ bool isEmpty = deque.Count == 0; ``` === "Go" ```go title="deque_test.go" /* Инициализация двусторонней очереди */ // В Go list обычно используется как двусторонняя очередь deque := list.New() /* Поместить элементы в очередь */ deque.PushBack(2) // Добавить в хвост deque.PushBack(5) deque.PushBack(4) deque.PushFront(3) // Добавить в голову deque.PushFront(1) /* Просмотреть элементы */ front := deque.Front() // Элемент в голове rear := deque.Back() // Элемент в хвосте /* Извлечь элементы из очереди */ deque.Remove(front) // Извлечь элемент из головы deque.Remove(rear) // Извлечь элемент из хвоста /* Получить длину двусторонней очереди */ size := deque.Len() /* Проверить, пуста ли двусторонняя очередь */ isEmpty := deque.Len() == 0 ``` === "Swift" ```swift title="deque.swift" /* Инициализация двусторонней очереди */ // В Swift нет встроенного класса двусторонней очереди, поэтому можно использовать Array как двустороннюю очередь var deque: [Int] = [] /* Поместить элементы в очередь */ deque.append(2) // Добавить в хвост deque.append(5) deque.append(4) deque.insert(3, at: 0) // Добавить в голову deque.insert(1, at: 0) /* Просмотреть элементы */ let peekFirst = deque.first! // Элемент в голове let peekLast = deque.last! // Элемент в хвосте /* Извлечь элементы из очереди */ // При моделировании через Array сложность popFirst равна O(n) let popFirst = deque.removeFirst() // Извлечь элемент из головы let popLast = deque.removeLast() // Извлечь элемент из хвоста /* Получить длину двусторонней очереди */ let size = deque.count /* Проверить, пуста ли двусторонняя очередь */ let isEmpty = deque.isEmpty ``` === "JS" ```javascript title="deque.js" /* Инициализация двусторонней очереди */ // В JavaScript нет встроенной двусторонней очереди, поэтому можно использовать Array как двустороннюю очередь const deque = []; /* Поместить элементы в очередь */ deque.push(2); deque.push(5); deque.push(4); // Обрати внимание: поскольку это массив, метод unshift() имеет сложность O(n) deque.unshift(3); deque.unshift(1); /* Просмотреть элементы */ const peekFirst = deque[0]; const peekLast = deque[deque.length - 1]; /* Извлечь элементы из очереди */ // Обрати внимание: поскольку это массив, метод shift() имеет сложность O(n) const popFront = deque.shift(); const popBack = deque.pop(); /* Получить длину двусторонней очереди */ const size = deque.length; /* Проверить, пуста ли двусторонняя очередь */ const isEmpty = size === 0; ``` === "TS" ```typescript title="deque.ts" /* Инициализация двусторонней очереди */ // В TypeScript нет встроенной двусторонней очереди, поэтому можно использовать Array как двустороннюю очередь const deque: number[] = []; /* Поместить элементы в очередь */ deque.push(2); deque.push(5); deque.push(4); // Обрати внимание: поскольку это массив, метод unshift() имеет сложность O(n) deque.unshift(3); deque.unshift(1); /* Просмотреть элементы */ const peekFirst: number = deque[0]; const peekLast: number = deque[deque.length - 1]; /* Извлечь элементы из очереди */ // Обрати внимание: поскольку это массив, метод shift() имеет сложность O(n) const popFront: number = deque.shift() as number; const popBack: number = deque.pop() as number; /* Получить длину двусторонней очереди */ const size: number = deque.length; /* Проверить, пуста ли двусторонняя очередь */ const isEmpty: boolean = size === 0; ``` === "Dart" ```dart title="deque.dart" /* Инициализация двусторонней очереди */ // В Dart Queue определена как двусторонняя очередь Queue deque = Queue(); /* Поместить элементы в очередь */ deque.addLast(2); // Добавить в хвост deque.addLast(5); deque.addLast(4); deque.addFirst(3); // Добавить в голову deque.addFirst(1); /* Просмотреть элементы */ int peekFirst = deque.first; // Элемент в голове int peekLast = deque.last; // Элемент в хвосте /* Извлечь элементы из очереди */ int popFirst = deque.removeFirst(); // Извлечь элемент из головы int popLast = deque.removeLast(); // Извлечь элемент из хвоста /* Получить длину двусторонней очереди */ int size = deque.length; /* Проверить, пуста ли двусторонняя очередь */ bool isEmpty = deque.isEmpty; ``` === "Rust" ```rust title="deque.rs" /* Инициализация двусторонней очереди */ let mut deque: VecDeque = VecDeque::new(); /* Поместить элементы в очередь */ deque.push_back(2); // Добавить в хвост deque.push_back(5); deque.push_back(4); deque.push_front(3); // Добавить в голову deque.push_front(1); /* Просмотреть элементы */ if let Some(front) = deque.front() { // Элемент в голове } if let Some(rear) = deque.back() { // Элемент в хвосте } /* Извлечь элементы из очереди */ if let Some(pop_front) = deque.pop_front() { // Извлечь элемент из головы } if let Some(pop_rear) = deque.pop_back() { // Извлечь элемент из хвоста } /* Получить длину двусторонней очереди */ let size = deque.len(); /* Проверить, пуста ли двусторонняя очередь */ let is_empty = deque.is_empty(); ``` === "C" ```c title="deque.c" // В C нет встроенной двусторонней очереди ``` === "Kotlin" ```kotlin title="deque.kt" /* Инициализация двусторонней очереди */ val deque = LinkedList() /* Поместить элементы в очередь */ deque.offerLast(2) // Добавить в хвост deque.offerLast(5) deque.offerLast(4) deque.offerFirst(3) // Добавить в голову deque.offerFirst(1) /* Просмотреть элементы */ val peekFirst = deque.peekFirst() // Элемент в голове val peekLast = deque.peekLast() // Элемент в хвосте /* Извлечь элементы из очереди */ val popFirst = deque.pollFirst() // Извлечь элемент из головы val popLast = deque.pollLast() // Извлечь элемент из хвоста /* Получить длину двусторонней очереди */ val size = deque.size /* Проверить, пуста ли двусторонняя очередь */ val isEmpty = deque.isEmpty() ``` === "Ruby" ```ruby title="deque.rb" # Инициализация двусторонней очереди # В Ruby нет встроенной двусторонней очереди, поэтому можно использовать Array как двустороннюю очередь deque = [] # Поместить элементы в очередь deque << 2 deque << 5 deque << 4 # Обрати внимание: поскольку это массив, метод Array#unshift имеет сложность O(n) deque.unshift(3) deque.unshift(1) # Просмотреть элементы peek_first = deque.first peek_last = deque.last # Извлечь элементы из очереди # Обрати внимание: поскольку это массив, метод Array#shift имеет сложность O(n) pop_front = deque.shift pop_back = deque.pop # Получить длину двусторонней очереди size = deque.length # Проверить, пуста ли двусторонняя очередь is_empty = size.zero? ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8E%D1%8E%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20deq.append%282%29%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D0%B2%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%83%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8F%D1%8F%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20deque%20%3D%22%2C%20deq%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20print%28%22%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20front%20%3D%22%2C%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20print%28%22%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20rear%20%3D%22%2C%20rear%29%0A%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D0%B0%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20print%28%22%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%2C%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B8%D0%B7%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%8B%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%2C%20pop_front%20%3D%22%2C%20pop_front%29%0A%20%20%20%20print%28%22deque%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%D0%B7%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%8B%20%3D%22%2C%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20print%28%22%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%2C%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B8%D0%B7%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B0%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%2C%20pop_rear%20%3D%22%2C%20pop_rear%29%0A%20%20%20%20print%28%22deque%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%D0%B7%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B0%20%3D%22%2C%20deq%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%D0%B9%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%D0%B9%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%B8%D1%82%D1%8C%2C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8F%D1%8F%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%D0%9F%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8F%D1%8F%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## Реализация двусторонней очереди * Реализация двусторонней очереди похожа на реализацию обычной очереди: в качестве базовой структуры данных можно выбрать связный список или массив. ### Реализация на основе двусвязного списка Вспомним предыдущий раздел: там мы использовали обычный односвязный список для реализации очереди, потому что он позволяет удобно удалять головной узел, что соответствует операции `dequeue` , и добавлять новый узел после хвостового узла, что соответствует операции `enqueue` . Для двусторонней очереди и голова, и хвост допускают операции добавления и удаления элементов. Иначе говоря, двусторонняя очередь требует реализации еще одного симметричного направления операций. Поэтому в качестве базовой структуры данных двусторонней очереди удобно использовать двусвязный список. Как показано на рисунках ниже, мы рассматриваем головной и хвостовой узлы двусвязного списка как голову и хвост двусторонней очереди и одновременно реализуем функции добавления и удаления узлов с обеих сторон. === "<1>" ![Операции enqueue и dequeue для двусторонней очереди на связном списке](deque.assets/linkedlist_deque_step1.png) === "<2>" ![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png) === "<3>" ![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png) === "<4>" ![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png) === "<5>" ![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png) Код реализации приведен ниже: ```src [file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{} ``` ### Реализация на основе массива Как показано на рисунках ниже, аналогично реализации обычной очереди на массиве мы также можем использовать кольцевой массив для реализации двусторонней очереди. === "<1>" ![Операции enqueue и dequeue для двусторонней очереди на массиве](deque.assets/array_deque_step1.png) === "<2>" ![array_deque_push_last](deque.assets/array_deque_step2_push_last.png) === "<3>" ![array_deque_push_first](deque.assets/array_deque_step3_push_first.png) === "<4>" ![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png) === "<5>" ![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png) На основе реализации обычной очереди нужно лишь добавить методы добавления в голову очереди и удаления из хвоста: ```src [file]{array_deque}-[class]{array_deque}-[func]{} ``` ## Применение двусторонней очереди Двусторонняя очередь сочетает в себе логику стека и очереди, **поэтому она может покрыть все сценарии применения обеих структур и при этом предоставляет более высокую степень свободы**. Мы знаем, что функция "undo" в программном обеспечении обычно реализуется с помощью стека: система помещает каждое изменение в стек с помощью `push` , а затем использует `pop` для отмены. Однако, учитывая ограниченность системных ресурсов, программы обычно ограничивают число шагов отмены, например разрешают хранить только $50$ шагов. Когда длина стека превышает этот предел, программе нужно удалить элемент с дна стека, то есть с головы очереди. **Но стек не может реализовать такую операцию, и в этом случае его приходится заменять двусторонней очередью**. Обрати внимание: основная логика "undo" по-прежнему следует стековому правилу LIFO, просто двусторонняя очередь позволяет более гибко реализовать некоторые дополнительные механизмы. ================================================ FILE: ru/docs/chapter_stack_and_queue/index.md ================================================ # Стек и очередь ![Стек и очередь](../assets/covers/chapter_stack_and_queue.jpg) !!! abstract Стек и очередь - две базовые линейные структуры данных. Они соответственно воплощают принципы "последним пришел - первым вышел" и "первым пришел - первым вышел". ================================================ FILE: ru/docs/chapter_stack_and_queue/queue.md ================================================ # Очередь Очередь (queue) - это линейная структура данных, подчиняющаяся правилу "первым пришел - первым вышел". Как видно из названия, очередь моделирует обычную ситуацию ожидания: новые люди непрерывно присоединяются к хвосту очереди, а стоящие в начале по одному уходят. Как показано на рисунке ниже, начало очереди называется головой очереди, а конец - хвостом очереди; операцию добавления элемента в хвост называют `enqueue`, а операцию удаления элемента из головы - `dequeue`. ![Правило FIFO для очереди](queue.assets/queue_operations.png) ## Основные операции с очередью Распространенные операции с очередью показаны в таблице ниже. Следует учитывать, что названия методов в разных языках могут различаться. Здесь мы используем те же названия, что и для стека.

Таблица   Эффективность операций с очередью

| Имя метода | Описание | Временная сложность | | ---------- | ----------------------------------------- | ------------------- | | `push()` | Поместить элемент в очередь, то есть добавить его в хвост | $O(1)$ | | `pop()` | Извлечь элемент из головы очереди | $O(1)$ | | `peek()` | Просмотреть элемент в голове очереди | $O(1)$ | Обычно достаточно использовать готовые классы очереди, предоставляемые языками программирования: === "Python" ```python title="queue.py" from collections import deque # Инициализация очереди # В Python обычно используют двустороннюю очередь deque как обычную очередь # Хотя queue.Queue() является "чистой" очередью, она не слишком удобна, поэтому ее не рекомендуют que: deque[int] = deque() # Поместить элементы в очередь que.append(1) que.append(3) que.append(2) que.append(5) que.append(4) # Просмотреть элемент в голове очереди front: int = que[0] # Извлечь элемент из очереди pop: int = que.popleft() # Получить длину очереди size: int = len(que) # Проверить, пуста ли очередь is_empty: bool = len(que) == 0 ``` === "C++" ```cpp title="queue.cpp" /* Инициализация очереди */ queue queue; /* Поместить элементы в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); /* Просмотреть элемент в голове очереди */ int front = queue.front(); /* Извлечь элемент из очереди */ queue.pop(); /* Получить длину очереди */ int size = queue.size(); /* Проверить, пуста ли очередь */ bool empty = queue.empty(); ``` === "Java" ```java title="queue.java" /* Инициализация очереди */ Queue queue = new LinkedList<>(); /* Поместить элементы в очередь */ queue.offer(1); queue.offer(3); queue.offer(2); queue.offer(5); queue.offer(4); /* Просмотреть элемент в голове очереди */ int peek = queue.peek(); /* Извлечь элемент из очереди */ int pop = queue.poll(); /* Получить длину очереди */ int size = queue.size(); /* Проверить, пуста ли очередь */ boolean isEmpty = queue.isEmpty(); ``` === "C#" ```csharp title="queue.cs" /* Инициализация очереди */ Queue queue = new(); /* Поместить элементы в очередь */ queue.Enqueue(1); queue.Enqueue(3); queue.Enqueue(2); queue.Enqueue(5); queue.Enqueue(4); /* Просмотреть элемент в голове очереди */ int peek = queue.Peek(); /* Извлечь элемент из очереди */ int pop = queue.Dequeue(); /* Получить длину очереди */ int size = queue.Count; /* Проверить, пуста ли очередь */ bool isEmpty = queue.Count == 0; ``` === "Go" ```go title="queue_test.go" /* Инициализация очереди */ // В Go очередь обычно реализуют через list queue := list.New() /* Поместить элементы в очередь */ queue.PushBack(1) queue.PushBack(3) queue.PushBack(2) queue.PushBack(5) queue.PushBack(4) /* Просмотреть элемент в голове очереди */ peek := queue.Front() /* Извлечь элемент из очереди */ pop := queue.Front() queue.Remove(pop) /* Получить длину очереди */ size := queue.Len() /* Проверить, пуста ли очередь */ isEmpty := queue.Len() == 0 ``` === "Swift" ```swift title="queue.swift" /* Инициализация очереди */ // В Swift нет встроенного класса очереди, поэтому можно использовать Array как очередь var queue: [Int] = [] /* Поместить элементы в очередь */ queue.append(1) queue.append(3) queue.append(2) queue.append(5) queue.append(4) /* Просмотреть элемент в голове очереди */ let peek = queue.first! /* Извлечь элемент из очереди */ // Поскольку в основе лежит массив, removeFirst имеет сложность O(n) let pool = queue.removeFirst() /* Получить длину очереди */ let size = queue.count /* Проверить, пуста ли очередь */ let isEmpty = queue.isEmpty ``` === "JS" ```javascript title="queue.js" /* Инициализация очереди */ // В JavaScript нет встроенной очереди, поэтому можно использовать Array как очередь const queue = []; /* Поместить элементы в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); /* Просмотреть элемент в голове очереди */ const peek = queue[0]; /* Извлечь элемент из очереди */ // В основе лежит массив, поэтому shift() имеет сложность O(n) const pop = queue.shift(); /* Получить длину очереди */ const size = queue.length; /* Проверить, пуста ли очередь */ const empty = queue.length === 0; ``` === "TS" ```typescript title="queue.ts" /* Инициализация очереди */ // В TypeScript нет встроенной очереди, поэтому можно использовать Array как очередь const queue: number[] = []; /* Поместить элементы в очередь */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); /* Просмотреть элемент в голове очереди */ const peek = queue[0]; /* Извлечь элемент из очереди */ // В основе лежит массив, поэтому shift() имеет сложность O(n) const pop = queue.shift(); /* Получить длину очереди */ const size = queue.length; /* Проверить, пуста ли очередь */ const empty = queue.length === 0; ``` === "Dart" ```dart title="queue.dart" /* Инициализация очереди */ // В Dart класс Queue является двусторонней очередью и может использоваться как обычная очередь Queue queue = Queue(); /* Поместить элементы в очередь */ queue.add(1); queue.add(3); queue.add(2); queue.add(5); queue.add(4); /* Просмотреть элемент в голове очереди */ int peek = queue.first; /* Извлечь элемент из очереди */ int pop = queue.removeFirst(); /* Получить длину очереди */ int size = queue.length; /* Проверить, пуста ли очередь */ bool isEmpty = queue.isEmpty; ``` === "Rust" ```rust title="queue.rs" /* Инициализация двусторонней очереди */ // В Rust двусторонняя очередь может использоваться как обычная очередь let mut deque: VecDeque = VecDeque::new(); /* Поместить элементы в очередь */ deque.push_back(1); deque.push_back(3); deque.push_back(2); deque.push_back(5); deque.push_back(4); /* Просмотреть элемент в голове очереди */ if let Some(front) = deque.front() { } /* Извлечь элемент из очереди */ if let Some(pop) = deque.pop_front() { } /* Получить длину очереди */ let size = deque.len(); /* Проверить, пуста ли очередь */ let is_empty = deque.is_empty(); ``` === "C" ```c title="queue.c" // В C нет встроенной очереди ``` === "Kotlin" ```kotlin title="queue.kt" /* Инициализация очереди */ val queue = LinkedList() /* Поместить элементы в очередь */ queue.offer(1) queue.offer(3) queue.offer(2) queue.offer(5) queue.offer(4) /* Просмотреть элемент в голове очереди */ val peek = queue.peek() /* Извлечь элемент из очереди */ val pop = queue.poll() /* Получить длину очереди */ val size = queue.size /* Проверить, пуста ли очередь */ val isEmpty = queue.isEmpty() ``` === "Ruby" ```ruby title="queue.rb" # Инициализация очереди # Встроенная очередь в Ruby (Thread::Queue) не имеет методов peek и traverse, поэтому можно использовать Array как очередь queue = [] # Поместить элементы в очередь queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) # Просмотреть элемент очереди peek = queue.first # Извлечь элемент из очереди # Обрати внимание: поскольку это массив, метод Array#shift имеет сложность O(n) pop = queue.shift # Получить длину очереди size = queue.length # Проверить, пуста ли очередь is_empty = queue.empty? ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20%23%20%D0%92%20Python%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8E%D1%8E%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20deque%20%D0%BE%D0%B1%D1%8B%D1%87%D0%BD%D0%BE%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D1%83%D1%8E%D1%82%20%D0%BA%D0%B0%D0%BA%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20%23%20%D0%A5%D0%BE%D1%82%D1%8F%20queue.Queue%28%29%20%D1%8F%D0%B2%D0%BB%D1%8F%D0%B5%D1%82%D1%81%D1%8F%20%D0%BD%D0%B0%D1%81%D1%82%D0%BE%D1%8F%D1%89%D0%B8%D0%BC%20%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%BE%D0%BC%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%2C%20%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%D1%81%D1%8F%20%D0%B8%D0%BC%20%D0%BD%D0%B5%20%D1%81%D0%BB%D0%B8%D1%88%D0%BA%D0%BE%D0%BC%20%D1%83%D0%B4%D0%BE%D0%B1%D0%BD%D0%BE%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20que%20%3D%22%2C%20que%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20front%20%3D%22%2C%20front%29%0A%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B8%D0%B7%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22que%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%3D%22%2C%20que%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%B8%D1%82%D1%8C%2C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%D0%9F%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## Реализация очереди Чтобы реализовать очередь, нам нужна такая структура данных, которая позволяет добавлять элементы с одного конца и удалять их с другого; и связный список, и массив этим требованиям удовлетворяют. ### Реализация на основе связного списка Как показано на рисунке ниже, мы можем рассматривать головной узел и хвостовой узел связного списка как голову очереди и хвост очереди соответственно, договорившись, что добавлять узлы можно только в хвост, а удалять - только из головы. === "<1>" ![Операции enqueue и dequeue в реализации очереди на связном списке](queue.assets/linkedlist_queue_step1.png) === "<2>" ![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png) === "<3>" ![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png) Ниже приведен код реализации очереди на связном списке: ```src [file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{} ``` ### Реализация на основе массива Удаление первого элемента из массива имеет временную сложность $O(n)$ , из-за чего операция `dequeue` оказывается неэффективной. Однако этого можно избежать с помощью следующего приема. Мы можем использовать переменную `front` , указывающую на индекс элемента в голове очереди, и поддерживать переменную `size` , которая хранит длину очереди. Определим `rear = front + size` ; эта формула дает позицию `rear`, указывающую на ячейку сразу после хвоста очереди. Исходя из этого, **эффективный диапазон элементов массива равен `[front, rear - 1]`**, а различные операции реализуются, как показано на рисунке ниже. - Операция `enqueue`: записать входной элемент по индексу `rear` и увеличить `size` на 1. - Операция `dequeue`: просто увеличить `front` на 1 и уменьшить `size` на 1. Можно увидеть, что и `enqueue` , и `dequeue` требуют всего одной операции, а значит обе имеют временную сложность $O(1)$ . === "<1>" ![Операции enqueue и dequeue в реализации очереди на массиве](queue.assets/array_queue_step1.png) === "<2>" ![array_queue_push](queue.assets/array_queue_step2_push.png) === "<3>" ![array_queue_pop](queue.assets/array_queue_step3_pop.png) Ты можешь заметить еще одну проблему: при непрерывных операциях `enqueue` и `dequeue` значения `front` и `rear` оба движутся вправо, и **когда они доходят до конца массива, дальше сдвигаться уже нельзя**. Чтобы решить эту проблему, можно рассматривать массив как кольцевой массив, у которого начало и конец соединены. Для кольцевого массива нужно сделать так, чтобы `front` или `rear`, перешагнув конец массива, сразу возвращались к его началу и продолжали движение. Такую периодичность удобно реализовать с помощью операции взятия остатка, как показано в коде ниже: ```src [file]{array_queue}-[class]{array_queue}-[func]{} ``` Даже такая реализация очереди остается ограниченной: ее длина неизменяема. Однако это несложно исправить, заменив массив на динамический массив и тем самым введя механизм расширения. Заинтересованные читатели могут попробовать реализовать это самостоятельно. Выводы сравнения двух реализаций в целом такие же, как и для стека, поэтому здесь мы не будем повторяться. ## Типичные применения очереди - **Очереди заказов**. После оформления заказа покупателем заказ попадает в очередь, а затем система обрабатывает заказы по порядку. Во время крупных распродаж за короткое время возникает огромный поток заказов, и высокая конкурентная нагрузка становится ключевой инженерной проблемой. - **Различные отложенные задачи**. Любой сценарий, где нужно реализовать принцип "кто раньше пришел, тот раньше обслуживается", например очередь заданий принтера или очередь блюд на кухне ресторана, хорошо моделируется очередью, которая эффективно поддерживает нужный порядок обработки. ================================================ FILE: ru/docs/chapter_stack_and_queue/stack.md ================================================ # Стек Стек (stack) - это линейная структура данных, подчиняющаяся логике "последним пришел - первым вышел". Стек можно сравнить со стопкой тарелок на столе. Если разрешено перемещать только одну тарелку за раз, то, чтобы достать тарелку снизу, сначала придется по одной убрать все тарелки сверху. Если заменить тарелки различными элементами, например целыми числами, символами, объектами и т.д., получится структура данных "стек". Как показано на рисунке ниже, верхнюю часть стопки элементов мы называем вершиной стека, а нижнюю - основанием стека. Операция добавления элемента на вершину называется `push`, а операция удаления верхнего элемента - `pop`. ![Правило LIFO для стека](stack.assets/stack_operations.png) ## Основные операции со стеком Основные операции со стеком показаны в таблице ниже. Конкретные имена методов зависят от используемого языка программирования. Здесь в качестве примера используются распространенные названия `push()` , `pop()` и `peek()` .

Таблица   Эффективность операций со стеком

| Метод | Описание | Временная сложность | | -------- | --------------------------------- | ------------------- | | `push()` | Поместить элемент в стек (на вершину) | $O(1)$ | | `pop()` | Извлечь верхний элемент стека | $O(1)$ | | `peek()` | Просмотреть верхний элемент | $O(1)$ | Обычно достаточно использовать встроенный стек, предоставляемый языком программирования. Однако в некоторых языках специальный класс стека может отсутствовать. В таком случае можно использовать массив или связный список как стек и в логике программы игнорировать операции, не относящиеся к стеку. === "Python" ```python title="stack.py" # Инициализация стека # В Python нет встроенного класса стека, поэтому можно использовать list как стек stack: list[int] = [] # Поместить элементы в стек stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) # Просмотреть верхний элемент peek: int = stack[-1] # Извлечь элемент pop: int = stack.pop() # Получить длину стека size: int = len(stack) # Проверить, пуст ли стек is_empty: bool = len(stack) == 0 ``` === "C++" ```cpp title="stack.cpp" /* Инициализация стека */ stack stack; /* Поместить элементы в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* Просмотреть верхний элемент */ int top = stack.top(); /* Извлечь элемент */ stack.pop(); // Без возвращаемого значения /* Получить длину стека */ int size = stack.size(); /* Проверить, пуст ли стек */ bool empty = stack.empty(); ``` === "Java" ```java title="stack.java" /* Инициализация стека */ Stack stack = new Stack<>(); /* Поместить элементы в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* Просмотреть верхний элемент */ int peek = stack.peek(); /* Извлечь элемент */ int pop = stack.pop(); /* Получить длину стека */ int size = stack.size(); /* Проверить, пуст ли стек */ boolean isEmpty = stack.isEmpty(); ``` === "C#" ```csharp title="stack.cs" /* Инициализация стека */ Stack stack = new(); /* Поместить элементы в стек */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); /* Просмотреть верхний элемент */ int peek = stack.Peek(); /* Извлечь элемент */ int pop = stack.Pop(); /* Получить длину стека */ int size = stack.Count; /* Проверить, пуст ли стек */ bool isEmpty = stack.Count == 0; ``` === "Go" ```go title="stack_test.go" /* Инициализация стека */ // В Go рекомендуется использовать Slice как стек var stack []int /* Поместить элементы в стек */ stack = append(stack, 1) stack = append(stack, 3) stack = append(stack, 2) stack = append(stack, 5) stack = append(stack, 4) /* Просмотреть верхний элемент */ peek := stack[len(stack)-1] /* Извлечь элемент */ pop := stack[len(stack)-1] stack = stack[:len(stack)-1] /* Получить длину стека */ size := len(stack) /* Проверить, пуст ли стек */ isEmpty := len(stack) == 0 ``` === "Swift" ```swift title="stack.swift" /* Инициализация стека */ // В Swift нет встроенного класса стека, поэтому можно использовать Array как стек var stack: [Int] = [] /* Поместить элементы в стек */ stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) /* Просмотреть верхний элемент */ let peek = stack.last! /* Извлечь элемент */ let pop = stack.removeLast() /* Получить длину стека */ let size = stack.count /* Проверить, пуст ли стек */ let isEmpty = stack.isEmpty ``` === "JS" ```javascript title="stack.js" /* Инициализация стека */ // В JavaScript нет встроенного класса стека, поэтому можно использовать Array как стек const stack = []; /* Поместить элементы в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* Просмотреть верхний элемент */ const peek = stack[stack.length-1]; /* Извлечь элемент */ const pop = stack.pop(); /* Получить длину стека */ const size = stack.length; /* Проверить, пуст ли стек */ const is_empty = stack.length === 0; ``` === "TS" ```typescript title="stack.ts" /* Инициализация стека */ // В TypeScript нет встроенного класса стека, поэтому можно использовать Array как стек const stack: number[] = []; /* Поместить элементы в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* Просмотреть верхний элемент */ const peek = stack[stack.length - 1]; /* Извлечь элемент */ const pop = stack.pop(); /* Получить длину стека */ const size = stack.length; /* Проверить, пуст ли стек */ const is_empty = stack.length === 0; ``` === "Dart" ```dart title="stack.dart" /* Инициализация стека */ // В Dart нет встроенного класса стека, поэтому можно использовать List как стек List stack = []; /* Поместить элементы в стек */ stack.add(1); stack.add(3); stack.add(2); stack.add(5); stack.add(4); /* Просмотреть верхний элемент */ int peek = stack.last; /* Извлечь элемент */ int pop = stack.removeLast(); /* Получить длину стека */ int size = stack.length; /* Проверить, пуст ли стек */ bool isEmpty = stack.isEmpty; ``` === "Rust" ```rust title="stack.rs" /* Инициализация стека */ // Используем Vec как стек let mut stack: Vec = Vec::new(); /* Поместить элементы в стек */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* Просмотреть верхний элемент */ let top = stack.last().unwrap(); /* Извлечь элемент */ let pop = stack.pop().unwrap(); /* Получить длину стека */ let size = stack.len(); /* Проверить, пуст ли стек */ let is_empty = stack.is_empty(); ``` === "C" ```c title="stack.c" // В C нет встроенного стека ``` === "Kotlin" ```kotlin title="stack.kt" /* Инициализация стека */ val stack = Stack() /* Поместить элементы в стек */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) /* Просмотреть верхний элемент */ val peek = stack.peek() /* Извлечь элемент */ val pop = stack.pop() /* Получить длину стека */ val size = stack.size /* Проверить, пуст ли стек */ val isEmpty = stack.isEmpty() ``` === "Ruby" ```ruby title="stack.rb" # Инициализация стека # В Ruby нет встроенного класса стека, поэтому можно использовать Array как стек stack = [] # Поместить элементы в стек stack << 1 stack << 3 stack << 2 stack << 5 stack << 4 # Просмотреть верхний элемент peek = stack.last # Извлечь элемент pop = stack.pop # Получить длину стека size = stack.length # Проверить, пуст ли стек is_empty = stack.empty? ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20%23%20%D0%92%20Python%20%D0%BD%D0%B5%D1%82%20%D0%B2%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B0%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%2C%20%D0%BF%D0%BE%D1%8D%D1%82%D0%BE%D0%BC%D1%83%20list%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BA%D0%B0%D0%BA%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%D1%81%D1%82%D0%B5%D0%BA%20stack%20%3D%22%2C%20stack%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%D0%92%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20stack%20%3D%22%2C%20stack%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%B8%D1%82%D1%8C%2C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%D0%9F%D1%83%D1%81%D1%82%20%D0%BB%D0%B8%20%D1%81%D1%82%D0%B5%D0%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## Реализация стека Чтобы глубже понять механизм работы стека, попробуем самостоятельно реализовать класс стека. Стек подчиняется принципу LIFO, поэтому мы можем добавлять и удалять элементы только на вершине. Однако и массив, и связный список позволяют добавлять и удалять элементы в произвольном месте. **Следовательно, стек можно рассматривать как ограниченный массив или связный список**. Иными словами, мы можем скрыть часть нерелевантных операций массива или списка, так чтобы внешняя логика соответствовала свойствам стека. ### Реализация на основе связного списка Если реализовывать стек на основе связного списка, то головной узел списка можно рассматривать как вершину стека, а хвостовой - как основание. Как показано на рисунке ниже, для операции `push` достаточно вставить элемент в голову связного списка. Такой способ вставки называется вставкой в голову. Для операции `pop` достаточно удалить головной узел из списка. === "<1>" ![Операции push и pop в реализации стека на связном списке](stack.assets/linkedlist_stack_step1.png) === "<2>" ![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png) === "<3>" ![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png) Ниже приведен пример кода реализации стека на основе связного списка: ```src [file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{} ``` ### Реализация на основе массива Если реализовывать стек на основе массива, то хвост массива можно рассматривать как вершину стека. Как показано на рисунке ниже, операции `push` и `pop` соответствуют добавлению элемента в конец массива и удалению элемента из конца, обе имеют временную сложность $O(1)$ . === "<1>" ![Операции push и pop в реализации стека на массиве](stack.assets/array_stack_step1.png) === "<2>" ![array_stack_push](stack.assets/array_stack_step2_push.png) === "<3>" ![array_stack_pop](stack.assets/array_stack_step3_pop.png) Поскольку количество элементов, помещаемых в стек, может непрерывно расти, мы можем использовать динамический массив и тем самым не заниматься расширением массива вручную. Ниже приведен пример кода: ```src [file]{array_stack}-[class]{array_stack}-[func]{} ``` ## Сравнение двух реализаций **Поддерживаемые операции** Обе реализации поддерживают все операции, определенные для стека. Реализация на массиве дополнительно позволяет выполнять произвольный доступ, но это уже выходит за рамки определения стека и обычно не используется. **Временная эффективность** В реализации на массиве и `push` , и `pop` выполняются в заранее выделенной непрерывной памяти, которая хорошо использует локальность кэша, поэтому такие операции обычно эффективнее. Однако если при `push` емкость массива оказывается превышена, включается механизм расширения, и временная сложность именно этой операции становится $O(n)$ . В реализации на связном списке расширение выполняется очень гибко, и проблемы падения эффективности из-за расширения массива здесь нет. Но сама операция `push` требует инициализации объекта-узла и изменения указателей, поэтому в среднем она немного менее эффективна. Впрочем, если помещаемые в стек элементы уже сами являются объектами-узлами, шаг инициализации можно пропустить и тем самым повысить эффективность. Итак, когда элементами, помещаемыми и извлекаемыми из стека, являются базовые типы данных, например `int` или `double` , можно сделать следующие выводы. - Стек на основе массива теряет в эффективности в моменты расширения, но поскольку расширение происходит редко, его средняя эффективность выше. - Стек на основе связного списка может обеспечивать более стабильную производительность. **Пространственная эффективность** При инициализации массива система выделяет начальную емкость, которая может превышать реальную потребность. Кроме того, механизм расширения обычно увеличивает емкость по некоторому коэффициенту, например в 2 раза, и расширенная емкость тоже может оказаться больше фактически необходимой. Поэтому **реализация стека на основе массива может приводить к некоторым потерям памяти**. Однако, поскольку узлы связного списка должны дополнительно хранить указатели, **узлы списка сами по себе занимают больше пространства**. В итоге нельзя просто сказать, какая из реализаций более экономна по памяти; это нужно анализировать в контексте конкретной задачи. ## Типичные применения стека - **Кнопки "назад" и "вперед" в браузере, undo и redo в программах**. Каждый раз, когда мы открываем новую страницу, браузер помещает предыдущую страницу в стек, чтобы по операции "назад" можно было вернуться к ней. Операция "назад" по сути является `pop` . Если нужно одновременно поддерживать и "назад", и "вперед", то обычно используются два стека. - **Управление памятью программы**. Каждый раз при вызове функции система помещает на вершину стека стековый кадр, в котором хранится контекст функции. В рекурсивной функции на этапе углубления рекурсии непрерывно выполняются операции `push` , а на этапе возврата - операции `pop` . ================================================ FILE: ru/docs/chapter_stack_and_queue/summary.md ================================================ # Резюме ### Основные выводы - Стек - это структура данных, следующая правилу "последним пришел - первым вышел", и его можно реализовать с помощью массива или связного списка. - С точки зрения временной эффективности реализация стека на массиве обычно работает быстрее в среднем, но во время расширения емкости временная сложность отдельной операции `push` может ухудшаться до $O(n)$ . Напротив, реализация стека на связном списке дает более стабильные характеристики. - С точки зрения использования памяти реализация стека на массиве может приводить к некоторой потере пространства. Однако следует учитывать, что узлы связного списка занимают больше памяти, чем элементы массива. - Очередь - это структура данных, следующая правилу "первым пришел - первым вышел", и ее также можно реализовать с помощью массива или связного списка. Сравнение временной и пространственной эффективности для очереди в целом приводит к тем же выводам, что и для стека. - Двусторонняя очередь - это очередь с более высокой степенью свободы, которая позволяет добавлять и удалять элементы с обоих концов. ### Q & A **Q**: Реализованы ли кнопки "вперед" и "назад" в браузере с помощью двусвязного списка? По сути, функция переходов "вперед/назад" в браузере отражает логику стека. Когда пользователь открывает новую страницу, она помещается на вершину стека; когда пользователь нажимает кнопку "назад", эта страница снимается с вершины стека. Двусторонняя очередь позволяет удобно реализовать некоторые дополнительные операции, об этом уже упоминалось в разделе "Двусторонняя очередь". **Q**: Нужно ли освобождать память узла после извлечения его из стека? Если извлеченный узел еще понадобится, память освобождать не нужно. Если он больше не нужен, то в языках `Java` и `Python` есть автоматический сборщик мусора, поэтому ручное освобождение памяти не требуется; в `C` и `C++` память нужно освобождать вручную. **Q**: Двусторонняя очередь выглядит как два соединенных стека. Для чего она нужна? Двусторонняя очередь похожа на комбинацию стека и очереди или на два соединенных стека. Она объединяет логику обеих структур, поэтому может покрыть все их применения и при этом остается более гибкой. **Q**: Как именно реализуются отмена (undo) и повтор (redo)? Используются два стека: стек `A` для отмены и стек `B` для повтора. 1. Каждый раз, когда пользователь выполняет действие, это действие помещается в стек `A` , а стек `B` очищается. 2. Когда пользователь выполняет "undo", последнее действие извлекается из стека `A` и помещается в стек `B` . 3. Когда пользователь выполняет "redo", последнее действие извлекается из стека `B` и помещается обратно в стек `A` . ================================================ FILE: ru/docs/chapter_tree/array_representation_of_tree.md ================================================ # Представление двоичного дерева массивом В представлении через связную структуру единицей хранения двоичного дерева является узел `TreeNode` , а между узлами существуют связи через указатели. В предыдущем разделе были рассмотрены основные операции двоичного дерева в таком представлении. Возникает вопрос: можно ли представить двоичное дерево с помощью массива? Ответ: да. ## Представление идеального двоичного дерева Сначала разберем простой случай. Если дана идеальная двоичная структура и все ее узлы хранятся в массиве в порядке обхода по уровням, то каждому узлу будет соответствовать единственный индекс массива. Из свойств обхода по уровням можно вывести формулу соответствия между индексом родителя и индексами дочерних узлов: **если индекс некоторого узла равен $i$ , то индекс его левого дочернего узла равен $2i + 1$ , а правого - $2i + 2$** . На рисунке ниже показано соответствие между индексами разных узлов. ![Представление идеального двоичного дерева массивом](array_representation_of_tree.assets/array_representation_binary_tree.png) **Эта формула соответствия играет ту же роль, что и ссылки на узлы в связной структуре** . Имея любой узел в массиве, мы можем с ее помощью получить доступ к его левому и правому дочерним узлам. ## Представление произвольного двоичного дерева Идеальное двоичное дерево - лишь частный случай; в обычной двоичной структуре на промежуточных уровнях часто существует множество `None` . Поскольку последовательность обхода по уровням не содержит этих `None` , мы не можем по одной лишь этой последовательности определить их количество и расположение. **Это означает, что одному и тому же обходу по уровням может соответствовать сразу несколько различных структур двоичного дерева**. Как показано на рисунке ниже, для неполной двоичной структуры описанный выше способ представления массивом уже перестает работать. ![Одной последовательности обхода по уровням соответствуют разные двоичные структуры](array_representation_of_tree.assets/array_representation_without_empty.png) Чтобы решить эту проблему, **мы можем явно записывать все `None` в последовательности обхода по уровням** . Как показано на рисунке ниже, после такой обработки последовательность обхода по уровням уже сможет однозначно задавать двоичное дерево. Пример кода приведен ниже: === "Python" ```python title="" # Представление двоичного дерева массивом # Используем None для обозначения пустых позиций tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] ``` === "C++" ```cpp title="" /* Представление двоичного дерева массивом */ // Используем максимальное значение int, INT_MAX, для обозначения пустых позиций vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` === "Java" ```java title="" /* Представление двоичного дерева массивом */ // Используя обертку Integer для int, можно применять null для обозначения пустых позиций Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; ``` === "C#" ```csharp title="" /* Представление двоичного дерева массивом */ // Используя nullable-тип int? , можно применять null для обозначения пустых позиций int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Go" ```go title="" /* Представление двоичного дерева массивом */ // Используем срез типа any, чтобы можно было применять nil для обозначения пустых позиций tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} ``` === "Swift" ```swift title="" /* Представление двоичного дерева массивом */ // Используя nullable-тип Int? , можно применять nil для обозначения пустых позиций let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` === "JS" ```javascript title="" /* Представление двоичного дерева массивом */ // Используем null для обозначения пустых позиций let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "TS" ```typescript title="" /* Представление двоичного дерева массивом */ // Используем null для обозначения пустых позиций let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Dart" ```dart title="" /* Представление двоичного дерева массивом */ // Используя nullable-тип int? , можно применять null для обозначения пустых позиций List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Rust" ```rust title="" /* Представление двоичного дерева массивом */ // Используем None для обозначения пустых позиций let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)]; ``` === "C" ```c title="" /* Представление двоичного дерева массивом */ // Используем максимальное значение int для обозначения пустых позиций, поэтому узлы не должны принимать значение INT_MAX int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` === "Kotlin" ```kotlin title="" /* Представление двоичного дерева массивом */ // Используем null для обозначения пустых позиций val tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) ``` === "Ruby" ```ruby title="" ### Представление двоичного дерева массивом ### # Используем nil для обозначения пустых позиций tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` ![Представление произвольного двоичного дерева массивом](array_representation_of_tree.assets/array_representation_with_empty.png) Стоит отметить, что **полное двоичное дерево очень удобно представлять массивом** . Если вспомнить определение полного двоичного дерева, то `None` появляются только на самом нижнем уровне и справа, **а значит, все `None` обязательно находятся в конце последовательности обхода по уровням**. Это означает, что при представлении полного двоичного дерева массивом можно не хранить все `None` , что очень удобно. На рисунке ниже приведен пример. ![Представление полного двоичного дерева массивом](array_representation_of_tree.assets/array_representation_complete_binary_tree.png) Ниже приведен код реализации двоичного дерева, представленного массивом. Он включает следующие операции. - Для заданного узла получить его значение, левого дочернего узла, правого дочернего узла и родительский узел. - Получить последовательности прямого, симметричного, обратного обходов и обхода по уровням. ```src [file]{array_binary_tree}-[class]{array_binary_tree}-[func]{} ``` ## Преимущества и ограничения Представление двоичного дерева массивом имеет в основном следующие преимущества. - Массив хранится в непрерывной области памяти, хорошо работает с кешем и обеспечивает высокую скорость доступа и обхода. - Не нужно хранить указатели, поэтому память расходуется экономнее. - Разрешается произвольный доступ к узлам. Однако у представления массивом есть и некоторые ограничения. - Для хранения массива требуется непрерывная область памяти, поэтому такой способ не подходит для деревьев с очень большим объемом данных. - Добавление и удаление узлов приходится реализовывать через вставку и удаление элементов массива, а это не слишком эффективно. - Когда в двоичном дереве имеется большое число `None` , доля действительно полезных данных в массиве оказывается низкой, и эффективность использования пространства падает. ================================================ FILE: ru/docs/chapter_tree/avl_tree.md ================================================ # AVL-дерево * В разделе "Двоичное дерево поиска" мы упоминали, что после многократных операций вставки и удаления узлов двоичное дерево поиска может выродиться в связный список. В таком случае временная сложность всех операций ухудшается с $O(\log n)$ до $O(n)$ . Как показано на рисунке ниже, после двух операций удаления узлов это двоичное дерево поиска вырождается в связный список. ![Деградация AVL-дерева после удаления узлов](avl_tree.assets/avltree_degradation_from_removing_node.png) Другой пример: если в идеальное двоичное дерево, показанное на рисунке ниже, вставить два узла, то дерево сильно наклонится влево, а временная сложность поиска тоже ухудшится. ![Деградация AVL-дерева после вставки узлов](avl_tree.assets/avltree_degradation_from_inserting_node.png) В 1962 году Г. М. Adelson-Velsky и Е. М. Landis в статье "An algorithm for the organization of information" предложили AVL-дерево. В статье подробно описан набор операций, гарантирующий, что при непрерывном добавлении и удалении узлов AVL-дерево не вырождается, благодаря чему временная сложность различных операций сохраняется на уровне $O(\log n)$ . Иначе говоря, в сценариях, где часто выполняются вставка, удаление, поиск и изменение, AVL-дерево всегда поддерживает эффективную работу с данными и потому имеет высокую практическую ценность. ## Распространенные термины AVL-дерева AVL-дерево одновременно является и двоичным деревом поиска, и сбалансированным двоичным деревом, то есть одновременно удовлетворяет всем свойствам обеих этих структур. Поэтому AVL-дерево является разновидностью сбалансированного двоичного дерева поиска (balanced binary search tree). ### Высота узла Поскольку операции AVL-дерева требуют получать высоту узла, нам нужно добавить в класс узла переменную `height` : === "Python" ```python title="" class TreeNode: """Класс узла AVL-дерева""" def __init__(self, val: int): self.val: int = val # Значение узла self.height: int = 0 # Высота узла self.left: TreeNode | None = None # Ссылка на левого дочернего узла self.right: TreeNode | None = None # Ссылка на правого дочернего узла ``` === "C++" ```cpp title="" /* Класс узла AVL-дерева */ struct TreeNode { int val{}; // Значение узла int height = 0; // Высота узла TreeNode *left{}; // Левый дочерний узел TreeNode *right{}; // Правый дочерний узел TreeNode() = default; explicit TreeNode(int x) : val(x){} }; ``` === "Java" ```java title="" /* Класс узла AVL-дерева */ class TreeNode { public int val; // Значение узла public int height; // Высота узла public TreeNode left; // Левый дочерний узел public TreeNode right; // Правый дочерний узел public TreeNode(int x) { val = x; } } ``` === "C#" ```csharp title="" /* Класс узла AVL-дерева */ class TreeNode(int? x) { public int? val = x; // Значение узла public int height; // Высота узла public TreeNode? left; // Ссылка на левого дочернего узла public TreeNode? right; // Ссылка на правого дочернего узла } ``` === "Go" ```go title="" /* Структура узла AVL-дерева */ type TreeNode struct { Val int // Значение узла Height int // Высота узла Left *TreeNode // Ссылка на левого дочернего узла Right *TreeNode // Ссылка на правого дочернего узла } ``` === "Swift" ```swift title="" /* Класс узла AVL-дерева */ class TreeNode { var val: Int // Значение узла var height: Int // Высота узла var left: TreeNode? // Левый дочерний узел var right: TreeNode? // Правый дочерний узел init(x: Int) { val = x height = 0 } } ``` === "JS" ```javascript title="" /* Класс узла AVL-дерева */ class TreeNode { val; // Значение узла height; // Высота узла left; // Указатель на левого дочернего узла right; // Указатель на правого дочернего узла constructor(val, left, right, height) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } ``` === "TS" ```typescript title="" /* Класс узла AVL-дерева */ class TreeNode { val: number; // Значение узла height: number; // Высота узла left: TreeNode | null; // Указатель на левого дочернего узла right: TreeNode | null; // Указатель на правого дочернего узла constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } ``` === "Dart" ```dart title="" /* Класс узла AVL-дерева */ class TreeNode { int val; // Значение узла int height; // Высота узла TreeNode? left; // Левый дочерний узел TreeNode? right; // Правый дочерний узел TreeNode(this.val, [this.height = 0, this.left, this.right]); } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* Структура узла AVL-дерева */ struct TreeNode { val: i32, // Значение узла height: i32, // Высота узла left: Option>>, // Левый дочерний узел right: Option>>, // Правый дочерний узел } impl TreeNode { /* Конструктор */ fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, height: 0, left: None, right: None })) } } ``` === "C" ```c title="" /* Структура узла AVL-дерева */ typedef struct TreeNode { int val; int height; struct TreeNode *left; struct TreeNode *right; } TreeNode; /* Конструктор */ TreeNode *newTreeNode(int val) { TreeNode *node; node = (TreeNode *)malloc(sizeof(TreeNode)); node->val = val; node->height = 0; node->left = NULL; node->right = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* Класс узла AVL-дерева */ class TreeNode(val _val: Int) { // Значение узла val height: Int = 0 // Высота узла val left: TreeNode? = null // Левый дочерний узел val right: TreeNode? = null // Правый дочерний узел } ``` === "Ruby" ```ruby title="" ### Класс узла AVL-дерева ### class TreeNode attr_accessor :val # Значение узла attr_accessor :height # Высота узла attr_accessor :left # Ссылка на левого дочернего узла attr_accessor :right # Ссылка на правого дочернего узла def initialize(val) @val = val @height = 0 end end ``` "Высота узла" означает расстояние от этого узла до самого удаленного листового узла, то есть число пройденных "ребер". Особенно важно помнить, что высота листового узла равна $0$ , а высота пустого узла равна $-1$ . Мы создадим две вспомогательные функции: одну для получения высоты узла, другую для ее обновления: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{update_height} ``` ### Баланс-фактор узла Баланс-фактор (balance factor) узла определяется как высота левого поддерева минус высота правого поддерева; при этом баланс-фактор пустого узла считается равным $0$ . Мы также инкапсулируем получение баланс-фактора в отдельную функцию, чтобы потом было удобнее ее использовать: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{balance_factor} ``` !!! tip Пусть баланс-фактор равен $f$ ; тогда для любого узла AVL-дерева выполняется $-1 \le f \le 1$ . ## Вращения AVL-дерева Особенность AVL-дерева заключается в операции "вращения", которая позволяет заново сбалансировать разбалансированный узел, не нарушая последовательность симметричного обхода двоичного дерева. Иначе говоря, **операция вращения одновременно сохраняет свойство "двоичного дерева поиска" и возвращает дерево в состояние "сбалансированного двоичного дерева"**. Узлы, для которых абсолютное значение баланс-фактора больше $1$ , мы называем "разбалансированными узлами". В зависимости от вида разбаланса вращения делятся на четыре типа: правое вращение, левое вращение, сначала левое затем правое, и сначала правое затем левое. Ниже разберем их подробно. ### Правое вращение Как показано на рисунках ниже, под узлом указан его баланс-фактор. Если двигаться снизу вверх, то первым разбалансированным узлом в двоичном дереве будет "узел 3". Рассмотрим поддерево с этим узлом в качестве корня, обозначим данный узел как `node` , его левого дочернего узла как `child` и выполним "правое вращение". После завершения правого вращения поддерево снова станет сбалансированным и при этом сохранит свойство двоичного дерева поиска. === "<1>" ![Шаги правого вращения](avl_tree.assets/avltree_right_rotate_step1.png) === "<2>" ![avltree_right_rotate_step2](avl_tree.assets/avltree_right_rotate_step2.png) === "<3>" ![avltree_right_rotate_step3](avl_tree.assets/avltree_right_rotate_step3.png) === "<4>" ![avltree_right_rotate_step4](avl_tree.assets/avltree_right_rotate_step4.png) Как показано на рисунке ниже, когда у узла `child` есть правый дочерний узел, который мы обозначим как `grand_child` , в правое вращение нужно добавить еще один шаг: сделать `grand_child` левым дочерним узлом `node` . ![Правое вращение при наличии grand_child](avl_tree.assets/avltree_right_rotate_with_grandchild.png) "Поворот вправо" - это лишь образное описание; в реальности он реализуется через изменение указателей узлов. Код приведен ниже: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{right_rotate} ``` ### Левое вращение Соответственно, если рассмотреть "зеркальную" версию приведенного выше разбалансированного двоичного дерева, то понадобится выполнить "левое вращение", показанное на рисунке ниже. ![Левое вращение](avl_tree.assets/avltree_left_rotate.png) По той же причине, когда у узла `child` есть левый дочерний узел, который обозначим как `grand_child` , в левое вращение также требуется добавить шаг: сделать `grand_child` правым дочерним узлом `node` . ![Левое вращение при наличии grand_child](avl_tree.assets/avltree_left_rotate_with_grandchild.png) Можно заметить, что **операции правого и левого вращения логически зеркально симметричны, и два вида разбаланса, которые они исправляют, тоже симметричны**. Поэтому, опираясь на эту симметрию, достаточно заменить в коде правого вращения все `left` на `right` , а все `right` на `left` , чтобы получить реализацию левого вращения: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{left_rotate} ``` ### Сначала левое, затем правое вращение Для разбалансированного узла 3 на рисунке ниже ни одно лишь левое вращение, ни одно лишь правое вращение не способны вернуть поддерево в баланс. В этом случае нужно сначала выполнить "левое вращение" для `child` , а затем выполнить "правое вращение" для `node` . ![Сначала левое, затем правое вращение](avl_tree.assets/avltree_left_right_rotate.png) ### Сначала правое, затем левое вращение Как показано на рисунке ниже, для зеркальной ситуации предыдущего разбалансированного двоичного дерева нужно сначала выполнить "правое вращение" для `child` , а затем "левое вращение" для `node` . ![Сначала правое, затем левое вращение](avl_tree.assets/avltree_right_left_rotate.png) ### Выбор вращения Четыре вида разбаланса, показанные на рисунке ниже, по одному соответствуют рассмотренным выше случаям; для них соответственно требуются правое вращение, сначала левое затем правое, сначала правое затем левое и левое вращение. ![Четыре случая вращений AVL-дерева](avl_tree.assets/avltree_rotation_cases.png) Как показано в таблице ниже, мы определяем, какому из этих четырех случаев соответствует разбалансированный узел, по знаку баланс-фактора самого разбалансированного узла и по знаку баланс-фактора дочернего узла на более высокой стороне.

Таблица   Условия выбора для четырех случаев вращений

| Баланс-фактор разбалансированного узла | Баланс-фактор дочернего узла | Какое вращение использовать | | -------------------------------------- | ---------------------------- | --------------------------- | | $> 1$ (левостороннее дерево) | $\geq 0$ | Правое вращение | | $> 1$ (левостороннее дерево) | $<0$ | Сначала левое, затем правое | | $< -1$ (правостороннее дерево) | $\leq 0$ | Левое вращение | | $< -1$ (правостороннее дерево) | $>0$ | Сначала правое, затем левое | Для удобства мы инкапсулируем операцию вращения в отдельную функцию. **С помощью этой функции можно выполнить корректное вращение для любой ситуации разбаланса и снова привести узел в сбалансированное состояние**. Код приведен ниже: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{rotate} ``` ## Распространенные операции AVL-дерева ### Вставка узла Операция вставки узла в AVL-дерево по основному процессу похожа на вставку в двоичное дерево поиска. Единственная разница состоит в том, что после вставки в AVL-дерево на пути от вставленного узла к корню может появиться цепочка разбалансированных узлов. Поэтому **начиная от этого узла, мы должны выполнять вращения снизу вверх, чтобы вернуть в баланс все разбалансированные узлы**. Код приведен ниже: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{insert_helper} ``` ### Удаление узла Аналогично, на основе метода удаления узла из двоичного дерева поиска нужно добавить вращения снизу вверх, чтобы восстановить баланс всех разбалансированных узлов. Код приведен ниже: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{remove_helper} ``` ### Поиск узла Операция поиска узла в AVL-дереве совпадает с поиском в двоичном дереве поиска, поэтому здесь она повторно не рассматривается. ## Типичные применения AVL-дерева - Организация и хранение больших массивов данных, особенно в сценариях с частым поиском и относительно редкими вставками и удалениями. - Использование при построении индексных систем в базах данных. - Красно-черное дерево тоже является распространенным видом сбалансированного двоичного дерева поиска. По сравнению с AVL-деревом условия баланса у красно-черного дерева мягче, поэтому при вставке и удалении требуется меньше вращений, а средняя эффективность операций добавления и удаления выше. ================================================ FILE: ru/docs/chapter_tree/binary_search_tree.md ================================================ # Двоичное дерево поиска Как показано на рисунке ниже, двоичное дерево поиска (binary search tree) удовлетворяет следующим условиям. 1. Для корневого узла все значения в левом поддереве меньше значения корневого узла, а все значения в правом поддереве больше значения корневого узла. 2. Левое и правое поддеревья любого узла также являются двоичными деревьями поиска, то есть тоже удовлетворяют условию `1.` . ![Двоичное дерево поиска](binary_search_tree.assets/binary_search_tree.png) ## Операции с двоичным деревом поиска Мы инкапсулируем двоичное дерево поиска в класс `BinarySearchTree` и объявляем переменную-член `root` , которая указывает на корневой узел дерева. ### Поиск узла Для заданного целевого значения узла `num` можно выполнить поиск, опираясь на свойства двоичного дерева поиска. Как показано на рисунках ниже, мы объявляем узел `cur` , стартуем от корня дерева `root` и циклически сравниваем значения `cur.val` и `num` . - Если `cur.val < num` , это означает, что целевой узел находится в правом поддереве `cur` , поэтому выполняем `cur = cur.right` . - Если `cur.val > num` , это означает, что целевой узел находится в левом поддереве `cur` , поэтому выполняем `cur = cur.left` . - Если `cur.val = num` , это означает, что целевой узел найден, и мы выходим из цикла, возвращая этот узел. === "<1>" ![Пример поиска узла в двоичном дереве поиска](binary_search_tree.assets/bst_search_step1.png) === "<2>" ![bst_search_step2](binary_search_tree.assets/bst_search_step2.png) === "<3>" ![bst_search_step3](binary_search_tree.assets/bst_search_step3.png) === "<4>" ![bst_search_step4](binary_search_tree.assets/bst_search_step4.png) Операция поиска в двоичном дереве поиска работает по тому же принципу, что и двоичный поиск: на каждом шаге она отбрасывает половину вариантов. Число итераций не превосходит высоты двоичного дерева, а когда дерево сбалансировано, требуется $O(\log n)$ времени. Пример кода приведен ниже: ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{search} ``` ### Вставка узла Пусть дан элемент `num` , который нужно вставить. Чтобы сохранить свойство двоичного дерева поиска "левое поддерево < корень < правое поддерево", процесс вставки выглядит следующим образом. 1. **Найти позицию для вставки**: как и в операции поиска, начиная от корня, мы циклически спускаемся вниз в зависимости от соотношения между текущим значением узла и `num` , пока не выйдем за листовой узел (то есть не дойдем до `None` ). 2. **Вставить узел в найденную позицию**: инициализировать узел `num` и поставить его на место этого `None` . ![Вставка узла в двоичное дерево поиска](binary_search_tree.assets/bst_insert.png) В реализации кода нужно обратить внимание на следующие два момента. - Двоичное дерево поиска не допускает дублирующихся узлов, иначе его определение будет нарушено. Поэтому если вставляемый узел уже существует в дереве, вставка не выполняется и функция сразу возвращается. - Чтобы реализовать вставку, нам нужно использовать узел `pre` для сохранения узла предыдущей итерации цикла. Тогда, когда обход дойдет до `None` , мы сможем получить его родителя и завершить вставку. ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{insert} ``` Как и поиск узла, вставка узла требует $O(\log n)$ времени. ### Удаление узла Сначала нужно найти в двоичном дереве целевой узел, а затем удалить его. Как и при вставке, после удаления необходимо сохранить свойство двоичного дерева поиска: "левое поддерево < корень < правое поддерево". Поэтому в зависимости от числа дочерних узлов у удаляемого узла, то есть для случаев со степенью 0, 1 и 2, выполняются разные операции удаления. Как показано на рисунке ниже, когда степень удаляемого узла равна $0$ , это значит, что узел является листом и может быть удален напрямую. ![Удаление узла в двоичном дереве поиска (степень 0)](binary_search_tree.assets/bst_remove_case1.png) Как показано на рисунке ниже, когда степень удаляемого узла равна $1$ , достаточно заменить удаляемый узел его дочерним узлом. ![Удаление узла в двоичном дереве поиска (степень 1)](binary_search_tree.assets/bst_remove_case2.png) Когда степень удаляемого узла равна $2$ , мы уже не можем удалить его напрямую и должны использовать для замены другой узел. Чтобы сохранить свойство двоичного дерева поиска "левое поддерево $<$ корень $<$ правое поддерево", **этим узлом может быть минимальный узел правого поддерева или максимальный узел левого поддерева**. Предположим, мы выбираем минимальный узел правого поддерева, то есть следующий узел в симметричном обходе. Тогда процесс удаления выглядит так. 1. Найти следующий узел в "последовательности симметричного обхода" для удаляемого узла и обозначить его как `tmp` . 2. Значением `tmp` перезаписать значение удаляемого узла, а затем рекурсивно удалить узел `tmp` из дерева. === "<1>" ![Удаление узла в двоичном дереве поиска (степень 2)](binary_search_tree.assets/bst_remove_case3_step1.png) === "<2>" ![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png) === "<3>" ![bst_remove_case3_step3](binary_search_tree.assets/bst_remove_case3_step3.png) === "<4>" ![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png) Операция удаления узла также требует $O(\log n)$ времени, где поиск удаляемого узла стоит $O(\log n)$ , а получение следующего узла симметричного обхода также требует $O(\log n)$ . Пример кода приведен ниже: ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{remove} ``` ### Упорядоченность симметричного обхода Как показано на рисунке ниже, симметричный обход двоичного дерева следует порядку "лево $\rightarrow$ корень $\rightarrow$ право", а двоичное дерево поиска удовлетворяет соотношению "левый дочерний узел $<$ корень $<$ правый дочерний узел". Это означает, что при симметричном обходе двоичного дерева поиска мы всегда сначала будем посещать следующий минимальный узел, и отсюда получается важное свойство: **последовательность симметричного обхода двоичного дерева поиска является возрастающей**. Используя это свойство возрастающей последовательности симметричного обхода, мы можем получить отсортированные данные из двоичного дерева поиска всего за $O(n)$ времени, без дополнительной сортировки, что очень эффективно. ![Последовательность симметричного обхода двоичного дерева поиска](binary_search_tree.assets/bst_inorder_traversal.png) ## Эффективность двоичного дерева поиска Для заданного набора данных можно рассмотреть хранение либо в массиве, либо в двоичном дереве поиска. Из таблицы ниже видно, что временная сложность операций двоичного дерева поиска имеет логарифмический порядок и обеспечивает стабильную высокую производительность. Только в сценариях с очень частыми вставками и редкими поисками и удалениями массив может быть эффективнее, чем двоичное дерево поиска.

Таблица   Сравнение эффективности массива и дерева поиска

| | Неупорядоченный массив | Двоичное дерево поиска | | -------- | ---------------------- | ---------------------- | | Поиск элемента | $O(n)$ | $O(\log n)$ | | Вставка элемента | $O(1)$ | $O(\log n)$ | | Удаление элемента | $O(n)$ | $O(\log n)$ | В идеальном случае двоичное дерево поиска является "сбалансированным", и тогда любой узел можно найти за $\log n$ итераций. Однако если в двоичное дерево поиска непрерывно вставлять и удалять узлы, оно может выродиться в связный список, как показано на рисунке ниже. Тогда временная сложность различных операций тоже вырождается до $O(n)$ . ![Деградация двоичного дерева поиска](binary_search_tree.assets/bst_degradation.png) ## Типичные применения двоичного дерева поиска - Используется как многоуровневый индекс в системах, обеспечивая эффективный поиск, вставку и удаление. - Служит базовой структурой данных для некоторых поисковых алгоритмов. - Применяется для хранения потока данных в отсортированном состоянии. ================================================ FILE: ru/docs/chapter_tree/binary_tree.md ================================================ # Двоичное дерево Двоичное дерево (binary tree) - это нелинейная структура данных, представляющая отношения между "предками" и "потомками" и отражающая логику "разделяй и властвуй". Подобно связному списку, базовой единицей двоичного дерева является узел; каждый узел содержит значение, ссылку на левого дочернего узла и ссылку на правого дочернего узла. === "Python" ```python title="" class TreeNode: """Класс узла двоичного дерева""" def __init__(self, val: int): self.val: int = val # Значение узла self.left: TreeNode | None = None # Ссылка на левого дочернего узла self.right: TreeNode | None = None # Ссылка на правого дочернего узла ``` === "C++" ```cpp title="" /* Структура узла двоичного дерева */ struct TreeNode { int val; // Значение узла TreeNode *left; // Указатель на левого дочернего узла TreeNode *right; // Указатель на правого дочернего узла TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; ``` === "Java" ```java title="" /* Класс узла двоичного дерева */ class TreeNode { int val; // Значение узла TreeNode left; // Ссылка на левого дочернего узла TreeNode right; // Ссылка на правого дочернего узла TreeNode(int x) { val = x; } } ``` === "C#" ```csharp title="" /* Класс узла двоичного дерева */ class TreeNode(int? x) { public int? val = x; // Значение узла public TreeNode? left; // Ссылка на левого дочернего узла public TreeNode? right; // Ссылка на правого дочернего узла } ``` === "Go" ```go title="" /* Структура узла двоичного дерева */ type TreeNode struct { Val int Left *TreeNode Right *TreeNode } /* Конструктор */ func NewTreeNode(v int) *TreeNode { return &TreeNode{ Left: nil, // Указатель на левого дочернего узла Right: nil, // Указатель на правого дочернего узла Val: v, // Значение узла } } ``` === "Swift" ```swift title="" /* Класс узла двоичного дерева */ class TreeNode { var val: Int // Значение узла var left: TreeNode? // Ссылка на левого дочернего узла var right: TreeNode? // Ссылка на правого дочернего узла init(x: Int) { val = x } } ``` === "JS" ```javascript title="" /* Класс узла двоичного дерева */ class TreeNode { val; // Значение узла left; // Указатель на левого дочернего узла right; // Указатель на правого дочернего узла constructor(val, left, right) { this.val = val === undefined ? 0 : val; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } ``` === "TS" ```typescript title="" /* Класс узла двоичного дерева */ class TreeNode { val: number; left: TreeNode | null; right: TreeNode | null; constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { this.val = val === undefined ? 0 : val; // Значение узла this.left = left === undefined ? null : left; // Ссылка на левого дочернего узла this.right = right === undefined ? null : right; // Ссылка на правого дочернего узла } } ``` === "Dart" ```dart title="" /* Класс узла двоичного дерева */ class TreeNode { int val; // Значение узла TreeNode? left; // Ссылка на левого дочернего узла TreeNode? right; // Ссылка на правого дочернего узла TreeNode(this.val, [this.left, this.right]); } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* Структура узла двоичного дерева */ struct TreeNode { val: i32, // Значение узла left: Option>>, // Ссылка на левого дочернего узла right: Option>>, // Ссылка на правого дочернего узла } impl TreeNode { /* Конструктор */ fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, left: None, right: None })) } } ``` === "C" ```c title="" /* Структура узла двоичного дерева */ typedef struct TreeNode { int val; // Значение узла int height; // Высота узла struct TreeNode *left; // Указатель на левого дочернего узла struct TreeNode *right; // Указатель на правого дочернего узла } TreeNode; /* Конструктор */ TreeNode *newTreeNode(int val) { TreeNode *node; node = (TreeNode *)malloc(sizeof(TreeNode)); node->val = val; node->height = 0; node->left = NULL; node->right = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* Класс узла двоичного дерева */ class TreeNode(val _val: Int) { // Значение узла val left: TreeNode? = null // Ссылка на левого дочернего узла val right: TreeNode? = null // Ссылка на правого дочернего узла } ``` === "Ruby" ```ruby title="" ### Класс узла двоичного дерева ### class TreeNode attr_accessor :val # Значение узла attr_accessor :left # Ссылка на левого дочернего узла attr_accessor :right # Ссылка на правого дочернего узла def initialize(val) @val = val end end ``` Каждый узел имеет две ссылки (указателя), которые соответственно указывают на левого дочернего узла (left-child node) и правого дочернего узла (right-child node); данный узел называется родительским узлом (parent node) для этих двух дочерних узлов. Если задан некоторый узел двоичного дерева, то дерево, образованное его левым дочерним узлом и всеми узлами ниже него, называется левым поддеревом (left subtree) этого узла; аналогично определяется правое поддерево (right subtree). **Узлы, не имеющие дочерних узлов, называют листьями, а все остальные узлы содержат дочерние узлы и непустые поддеревья**. Как показано на рисунке ниже, если рассматривать "узел 2" как родительский, то его левым и правым дочерними узлами будут "узел 4" и "узел 5"; левое поддерево - это "узел 4 и дерево ниже него", а правое поддерево - это "узел 5 и дерево ниже него". ![Родительский узел, дочерние узлы и поддеревья](binary_tree.assets/binary_tree_definition.png) ## Распространенные термины двоичного дерева Распространенные термины двоичного дерева показаны на рисунке ниже. - Корневой узел (root node): узел, расположенный на верхнем уровне двоичного дерева и не имеющий родительского узла. - Листовой узел (leaf node): узел без дочерних узлов; оба его указателя направлены на `None` . - Ребро (edge): отрезок, соединяющий два узла, то есть ссылка (указатель) между узлами. - Уровень (level) узла: увеличивается сверху вниз; уровень корневого узла равен 1 . - Степень (degree) узла: число дочерних узлов данного узла. В двоичном дереве возможны степени 0, 1, 2 . - Высота (height) двоичного дерева: число ребер от корневого узла до самого удаленного листового узла. - Глубина (depth) узла: число ребер от корневого узла до данного узла. - Высота (height) узла: число ребер от самого удаленного листового узла до данного узла. ![Распространенные термины двоичного дерева](binary_tree.assets/binary_tree_terminology.png) !!! tip Обычно под "высотой" и "глубиной" понимают "число пройденных ребер", но в некоторых задачах или учебниках их могут определять как "число пройденных узлов". В таком случае и высоту, и глубину нужно увеличить на 1 . ## Базовые операции двоичного дерева ### Инициализация двоичного дерева Как и в связном списке, сначала инициализируются узлы, а затем между ними строятся ссылки (указатели). === "Python" ```python title="binary_tree.py" # Инициализация двоичного дерева # Инициализация узлов n1 = TreeNode(val=1) n2 = TreeNode(val=2) n3 = TreeNode(val=3) n4 = TreeNode(val=4) n5 = TreeNode(val=5) # Построение ссылок (указателей) между узлами n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` === "C++" ```cpp title="binary_tree.cpp" /* Инициализация двоичного дерева */ // Инициализация узлов TreeNode* n1 = new TreeNode(1); TreeNode* n2 = new TreeNode(2); TreeNode* n3 = new TreeNode(3); TreeNode* n4 = new TreeNode(4); TreeNode* n5 = new TreeNode(5); // Построение ссылок (указателей) между узлами n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; ``` === "Java" ```java title="binary_tree.java" // Инициализация узлов TreeNode n1 = new TreeNode(1); TreeNode n2 = new TreeNode(2); TreeNode n3 = new TreeNode(3); TreeNode n4 = new TreeNode(4); TreeNode n5 = new TreeNode(5); // Построение ссылок (указателей) между узлами n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "C#" ```csharp title="binary_tree.cs" /* Инициализация двоичного дерева */ // Инициализация узлов TreeNode n1 = new(1); TreeNode n2 = new(2); TreeNode n3 = new(3); TreeNode n4 = new(4); TreeNode n5 = new(5); // Построение ссылок (указателей) между узлами n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "Go" ```go title="binary_tree.go" /* Инициализация двоичного дерева */ // Инициализация узлов n1 := NewTreeNode(1) n2 := NewTreeNode(2) n3 := NewTreeNode(3) n4 := NewTreeNode(4) n5 := NewTreeNode(5) // Построение ссылок (указателей) между узлами n1.Left = n2 n1.Right = n3 n2.Left = n4 n2.Right = n5 ``` === "Swift" ```swift title="binary_tree.swift" // Инициализация узлов let n1 = TreeNode(x: 1) let n2 = TreeNode(x: 2) let n3 = TreeNode(x: 3) let n4 = TreeNode(x: 4) let n5 = TreeNode(x: 5) // Построение ссылок (указателей) между узлами n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` === "JS" ```javascript title="binary_tree.js" /* Инициализация двоичного дерева */ // Инициализация узлов let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // Построение ссылок (указателей) между узлами n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "TS" ```typescript title="binary_tree.ts" /* Инициализация двоичного дерева */ // Инициализация узлов let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // Построение ссылок (указателей) между узлами n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "Dart" ```dart title="binary_tree.dart" /* Инициализация двоичного дерева */ // Инициализация узлов TreeNode n1 = new TreeNode(1); TreeNode n2 = new TreeNode(2); TreeNode n3 = new TreeNode(3); TreeNode n4 = new TreeNode(4); TreeNode n5 = new TreeNode(5); // Построение ссылок (указателей) между узлами n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "Rust" ```rust title="binary_tree.rs" // Инициализация узлов let n1 = TreeNode::new(1); let n2 = TreeNode::new(2); let n3 = TreeNode::new(3); let n4 = TreeNode::new(4); let n5 = TreeNode::new(5); // Построение ссылок (указателей) между узлами n1.borrow_mut().left = Some(n2.clone()); n1.borrow_mut().right = Some(n3); n2.borrow_mut().left = Some(n4); n2.borrow_mut().right = Some(n5); ``` === "C" ```c title="binary_tree.c" /* Инициализация двоичного дерева */ // Инициализация узлов TreeNode *n1 = newTreeNode(1); TreeNode *n2 = newTreeNode(2); TreeNode *n3 = newTreeNode(3); TreeNode *n4 = newTreeNode(4); TreeNode *n5 = newTreeNode(5); // Построение ссылок (указателей) между узлами n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; ``` === "Kotlin" ```kotlin title="binary_tree.kt" // Инициализация узлов val n1 = TreeNode(1) val n2 = TreeNode(2) val n3 = TreeNode(3) val n4 = TreeNode(4) val n5 = TreeNode(5) // Построение ссылок (указателей) между узлами n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` === "Ruby" ```ruby title="binary_tree.rb" # Инициализация двоичного дерева # Инициализация узлов n1 = TreeNode.new(1) n2 = TreeNode.new(2) n3 = TreeNode.new(3) n4 = TreeNode.new(4) n5 = TreeNode.new(5) # Построение ссылок (указателей) между узлами n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%20%28%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D0%B8%29%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### Вставка и удаление узлов Как и в связном списке, вставка и удаление узлов в двоичном дереве могут выполняться через изменение указателей. На рисунке ниже приведен пример. ![Вставка и удаление узлов в двоичном дереве](binary_tree.assets/binary_tree_add_remove.png) === "Python" ```python title="binary_tree.py" # Вставка и удаление узлов p = TreeNode(0) # Вставить узел P между n1 -> n2 n1.left = p p.left = n2 # Удалить узел P n1.left = n2 ``` === "C++" ```cpp title="binary_tree.cpp" /* Вставка и удаление узлов */ TreeNode* P = new TreeNode(0); // Вставить узел P между n1 -> n2 n1->left = P; P->left = n2; // Удалить узел P n1->left = n2; // Освободить память delete P; ``` === "Java" ```java title="binary_tree.java" TreeNode P = new TreeNode(0); // Вставить узел P между n1 -> n2 n1.left = P; P.left = n2; // Удалить узел P n1.left = n2; ``` === "C#" ```csharp title="binary_tree.cs" /* Вставка и удаление узлов */ TreeNode P = new(0); // Вставить узел P между n1 -> n2 n1.left = P; P.left = n2; // Удалить узел P n1.left = n2; ``` === "Go" ```go title="binary_tree.go" /* Вставка и удаление узлов */ // Вставить узел P между n1 -> n2 p := NewTreeNode(0) n1.Left = p p.Left = n2 // Удалить узел P n1.Left = n2 ``` === "Swift" ```swift title="binary_tree.swift" let P = TreeNode(x: 0) // Вставить узел P между n1 -> n2 n1.left = P P.left = n2 // Удалить узел P n1.left = n2 ``` === "JS" ```javascript title="binary_tree.js" /* Вставка и удаление узлов */ let P = new TreeNode(0); // Вставить узел P между n1 -> n2 n1.left = P; P.left = n2; // Удалить узел P n1.left = n2; ``` === "TS" ```typescript title="binary_tree.ts" /* Вставка и удаление узлов */ const P = new TreeNode(0); // Вставить узел P между n1 -> n2 n1.left = P; P.left = n2; // Удалить узел P n1.left = n2; ``` === "Dart" ```dart title="binary_tree.dart" /* Вставка и удаление узлов */ TreeNode P = new TreeNode(0); // Вставить узел P между n1 -> n2 n1.left = P; P.left = n2; // Удалить узел P n1.left = n2; ``` === "Rust" ```rust title="binary_tree.rs" let p = TreeNode::new(0); // Вставить узел P между n1 -> n2 n1.borrow_mut().left = Some(p.clone()); p.borrow_mut().left = Some(n2.clone()); // Удалить узел p n1.borrow_mut().left = Some(n2); ``` === "C" ```c title="binary_tree.c" /* Вставка и удаление узлов */ TreeNode *P = newTreeNode(0); // Вставить узел P между n1 -> n2 n1->left = P; P->left = n2; // Удалить узел P n1->left = n2; // Освободить память free(P); ``` === "Kotlin" ```kotlin title="binary_tree.kt" val P = TreeNode(0) // Вставить узел P между n1 -> n2 n1.left = P P.left = n2 // Удалить узел P n1.left = n2 ``` === "Ruby" ```ruby title="binary_tree.rb" # Вставка и удаление узлов _p = TreeNode.new(0) # Вставить узел _p между n1 -> n2 n1.left = _p _p.left = n2 # Удалить узел n1.left = n2 ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%20%28%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D0%B8%29%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D0%B8%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%20P%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20n1%20-%3E%20n2%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%20P%0A%20%20%20%20n1.left%20%3D%20n2&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false !!! tip Стоит помнить, что вставка узла может изменить исходную логическую структуру двоичного дерева, а удаление узла обычно означает удаление этого узла вместе со всеми его поддеревьями. Поэтому в двоичном дереве операции вставки и удаления обычно являются частью более крупного набора операций, который и реализует осмысленное действие. ## Распространенные типы двоичных деревьев ### Идеальное двоичное дерево Как показано на рисунке ниже, идеальное двоичное дерево (perfect binary tree) полностью заполнено на всех уровнях. В идеальном двоичном дереве степень листовых узлов равна $0$ , а у всех остальных узлов степень равна $2$ ; если высота дерева равна $h$ , то общее число узлов равно $2^{h+1} - 1$ , что образует стандартную экспоненциальную зависимость и отражает часто встречающееся в природе явление клеточного деления. !!! tip В китайскоязычном сообществе идеальное двоичное дерево часто называют полностью заполненным двоичным деревом. ![Идеальное двоичное дерево](binary_tree.assets/perfect_binary_tree.png) ### Полное двоичное дерево Как показано на рисунке ниже, полное двоичное дерево (complete binary tree) допускает неполное заполнение только на самом нижнем уровне, причем узлы этого уровня должны непрерывно заполняться слева направо. Стоит отметить, что идеальное двоичное дерево тоже является полным двоичным деревом. ![Полное двоичное дерево](binary_tree.assets/complete_binary_tree.png) ### Строгое двоичное дерево Как показано на рисунке ниже, строгое двоичное дерево (full binary tree) имеет у всех нелистовых узлов ровно двух дочерних узлов. ![Строгое двоичное дерево](binary_tree.assets/full_binary_tree.png) ### Сбалансированное двоичное дерево Как показано на рисунке ниже, в сбалансированном двоичном дереве (balanced binary tree) для любого узла абсолютное значение разности высот левого и правого поддеревьев не превышает 1 . ![Сбалансированное двоичное дерево](binary_tree.assets/balanced_binary_tree.png) ## Вырождение двоичного дерева На рисунке ниже показаны идеальная структура двоичного дерева и вырожденная структура. Когда каждый уровень двоичного дерева полностью заполнен узлами, мы получаем "идеальное двоичное дерево"; когда же все узлы смещаются к одной стороне, двоичное дерево вырождается в "связный список". - Идеальное двоичное дерево соответствует лучшему случаю и позволяет в полной мере раскрыть преимущества подхода "разделяй и властвуй". - Связный список представляет противоположную крайность: все операции становятся линейными, а временная сложность деградирует до $O(n)$ . ![Лучший и худший случаи структуры двоичного дерева](binary_tree.assets/binary_tree_best_worst_cases.png) Как показано в таблице ниже, в лучшем и худшем случаях число листовых узлов, общее число узлов, высота и другие характеристики двоичного дерева достигают максимума или минимума.

Таблица   Лучший и худший случаи структуры двоичного дерева

| | Идеальное двоичное дерево | Связный список | | --------------------------- | ------------------------- | -------------- | | Число узлов на уровне $i$ | $2^{i-1}$ | $1$ | | Число листьев у дерева высоты $h$ | $2^h$ | $1$ | | Общее число узлов у дерева высоты $h$ | $2^{h+1} - 1$ | $h + 1$ | | Высота дерева с $n$ узлами | $\log_2 (n+1) - 1$ | $n - 1$ | ================================================ FILE: ru/docs/chapter_tree/binary_tree_traversal.md ================================================ # Обход двоичного дерева С точки зрения физической структуры дерево представляет собой разновидность структуры данных на основе связей, поэтому его обход выполняется через последовательный доступ к узлам по указателям. Однако дерево является нелинейной структурой данных, а значит, его обход сложнее, чем обход связного списка, и для него требуется использовать поисковые алгоритмы. К распространенным способам обхода двоичного дерева относятся обход по уровням, прямой обход, симметричный обход и обратный обход. ## Обход по уровням Как показано на рисунке ниже, обход по уровням (level-order traversal) проходит двоичное дерево сверху вниз по уровням и на каждом уровне посещает узлы слева направо. По своей сути обход по уровням относится к обходу в ширину (breadth-first traversal), также называемому поиском в ширину (breadth-first search, BFS); он отражает идею "расширяться от центра к периферии слой за слоем". ![Обход двоичного дерева по уровням](binary_tree_traversal.assets/binary_tree_bfs.png) ### Код реализации Обход в ширину обычно реализуется с помощью "очереди". Очередь подчиняется правилу "первым пришел - первым вышел", а обход в ширину подчиняется правилу "продвигаться по уровням", поэтому стоящая за ними идея согласована. Код реализации приведен ниже: ```src [file]{binary_tree_bfs}-[class]{}-[func]{level_order} ``` ### Анализ сложности - **Временная сложность равна $O(n)$** : все узлы посещаются по одному разу, поэтому требуется $O(n)$ времени, где $n$ - число узлов. - **Пространственная сложность равна $O(n)$** : в худшем случае, то есть для полной двоичной деревообразной структуры, до достижения самого нижнего уровня в очереди одновременно может находиться до $(n + 1) / 2$ узлов, что требует $O(n)$ памяти. ## Прямой, симметричный и обратный обходы Соответственно, прямой, симметричный и обратный обходы относятся к обходу в глубину (depth-first traversal), также называемому поиском в глубину (depth-first search, DFS); он отражает идею "сначала идти до конца, затем возвращаться и продолжать". На рисунке ниже показан принцип работы обхода двоичного дерева в глубину. **Обход в глубину можно представить как обход всей двоичной структуры по внешнему контуру** , и у каждого узла встречаются три позиции, соответствующие прямому, симметричному и обратному обходам. ![Прямой, симметричный и обратный обходы двоичного дерева поиска](binary_tree_traversal.assets/binary_tree_dfs.png) ### Код реализации Поиск в глубину обычно реализуется через рекурсию: ```src [file]{binary_tree_dfs}-[class]{}-[func]{post_order} ``` !!! tip Поиск в глубину можно реализовать и итеративно; заинтересованные читатели могут изучить это самостоятельно. На рисунках ниже показан рекурсивный процесс прямого обхода двоичного дерева. Его можно разделить на две противоположные части: "вход в рекурсию" и "возврат". 1. "Вход в рекурсию" означает запуск нового вызова функции; в этом процессе программа переходит к следующему узлу. 2. "Возврат" означает завершение вызова функции и возврат назад, то есть текущий узел уже полностью обработан. === "<1>" ![Рекурсивный процесс прямого обхода](binary_tree_traversal.assets/preorder_step1.png) === "<2>" ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png) === "<3>" ![preorder_step3](binary_tree_traversal.assets/preorder_step3.png) === "<4>" ![preorder_step4](binary_tree_traversal.assets/preorder_step4.png) === "<5>" ![preorder_step5](binary_tree_traversal.assets/preorder_step5.png) === "<6>" ![preorder_step6](binary_tree_traversal.assets/preorder_step6.png) === "<7>" ![preorder_step7](binary_tree_traversal.assets/preorder_step7.png) === "<8>" ![preorder_step8](binary_tree_traversal.assets/preorder_step8.png) === "<9>" ![preorder_step9](binary_tree_traversal.assets/preorder_step9.png) === "<10>" ![preorder_step10](binary_tree_traversal.assets/preorder_step10.png) === "<11>" ![preorder_step11](binary_tree_traversal.assets/preorder_step11.png) ### Анализ сложности - **Временная сложность равна $O(n)$** : все узлы посещаются по одному разу, поэтому требуется $O(n)$ времени. - **Пространственная сложность равна $O(n)$** : в худшем случае, когда дерево вырождается в связный список, глубина рекурсии достигает $n$ , и система тратит $O(n)$ памяти на стек вызовов. ================================================ FILE: ru/docs/chapter_tree/index.md ================================================ # Деревья ![Деревья](../assets/covers/chapter_tree.jpg) !!! abstract Высокое дерево полно жизни: мощные корни, густая листва и раскидистые ветви. Оно наглядно показывает нам живую форму данных, построенную на принципе "разделяй и властвуй". ================================================ FILE: ru/docs/chapter_tree/summary.md ================================================ # Краткие итоги ### Основные моменты - Двоичное дерево - это нелинейная структура данных, отражающая логику "разделяй и властвуй". Каждый узел двоичного дерева содержит значение и два указателя, которые соответственно ведут к левому и правому дочерним узлам. - Для любого узла двоичного дерева дерево, образованное его левым (правым) дочерним узлом и всеми нижележащими узлами, называется левым (правым) поддеревом этого узла. - К связанным с двоичным деревом терминам относятся корневой узел, листовой узел, уровень, степень, ребро, высота, глубина и так далее. - Инициализация двоичного дерева, вставка узлов и удаление узлов аналогичны операциям со связным списком. - К распространенным видам двоичного дерева относятся идеальное двоичное дерево, полное двоичное дерево, строгое двоичное дерево и сбалансированное двоичное дерево. Идеальное двоичное дерево - наиболее желательное состояние, а связный список - худший случай после вырождения. - Двоичное дерево можно представить массивом: значения узлов и пустые позиции располагаются в порядке обхода по уровням, а связи между родителем и детьми реализуются через индексацию. - Обход двоичного дерева по уровням является методом поиска в ширину; он отражает идею "расширяться от центра к периферии слой за слоем" и обычно реализуется через очередь. - Прямой, симметричный и обратный обходы относятся к поиску в глубину; они отражают идею "сначала дойти до конца, затем вернуться и продолжить" и обычно реализуются рекурсивно. - Двоичное дерево поиска - это эффективная структура данных для поиска элементов; его поиск, вставка и удаление имеют временную сложность $O(\log n)$ . Когда двоичное дерево поиска вырождается в связный список, все эти сложности деградируют до $O(n)$ . - AVL-дерево, также называемое сбалансированным двоичным деревом поиска, с помощью вращений гарантирует, что после постоянных вставок и удалений узлов дерево остается сбалансированным. - Вращения AVL-дерева включают правое вращение, левое вращение, сначала правое затем левое и сначала левое затем правое. После вставки или удаления узла AVL-дерево выполняет вращения снизу вверх, чтобы снова восстановить баланс. ### Q & A **Q**: Для двоичного дерева, состоящего из одного узла, высота дерева и глубина корня обе равны $0$ ? Да, потому что высота и глубина обычно определяются как "число пройденных ребер". **Q**: Вставка и удаление в двоичном дереве обычно выполняются в составе набора операций. Что именно означает этот "набор операций"? Можно ли понимать это как освобождение ресурсов у дочерних узлов ресурса? Возьмем в качестве примера двоичное дерево поиска: операция удаления узла делится на три случая, и каждый из этих случаев требует нескольких последовательных шагов работы с узлами. **Q**: Почему у DFS для двоичного дерева есть три порядка: прямой, симметричный и обратный? Для чего они нужны? Подобно прямому и обратному обходу массива, прямой, симметричный и обратный обходы - это три способа обхода двоичного дерева, с помощью которых можно получить результаты в определенном порядке. Например, в двоичном дереве поиска, где соблюдается отношение `значение левого дочернего узла < значение корня < значение правого дочернего узла` , если обходить дерево с приоритетом "лево $\rightarrow$ корень $\rightarrow$ право", то получится упорядоченная последовательность узлов. **Q**: Правое вращение работает с отношениями между `node` , `child` и `grand_child` . А связь между `node` и его исходным родителем разве не нужно поддерживать? После правого вращения она ведь не оборвется? На это нужно смотреть с точки зрения рекурсии. В правое вращение `right_rotate(root)` передается корень поддерева, а затем через `return child` возвращается корень этого поддерева уже после вращения. Соединение между новым корнем поддерева и его родителем восстанавливается после возврата функции и не входит в обязанности самой операции правого вращения. **Q**: В C++ функции делятся на `private` и `public` . Какая логика стоит за этим? Почему `height()` и `updateHeight()` помещают в разные области видимости? Главный критерий - область использования метода. Если метод нужен только внутри класса, его следует проектировать как `private` . Например, самостоятельный вызов `updateHeight()` пользователем не имеет смысла: это лишь один из шагов внутри вставки или удаления. А `height()` используется для чтения высоты узла, подобно `vector.size()` , поэтому его разумно делать `public` . **Q**: Как построить двоичное дерево поиска из набора входных данных? Важен ли выбор корневого узла? Да, важен. Способ построения дерева уже показан в методе `build_tree()` в коде двоичного дерева поиска. Что касается выбора корня, обычно входные данные сортируют, берут средний элемент как корень, а затем рекурсивно строят левое и правое поддеревья. Это позволяет в наибольшей степени сохранить баланс дерева. **Q**: Нужно ли в Java всегда использовать `equals()` для сравнения строк? В Java для базовых типов `==` используется, чтобы сравнивать, равны ли значения двух переменных. Для ссылочных типов логика у этих двух способов уже разная. - `==` : сравнивает, ссылаются ли две переменные на один и тот же объект, то есть совпадает ли их адрес в памяти. - `equals()`: сравнивает, равны ли значения двух объектов. Поэтому если нужно сравнить значения, то следует использовать `equals()` . Но строки, инициализированные как `String a = "hi"; String b = "hi";` , хранятся в строковом пуле констант и указывают на один и тот же объект, поэтому в таком случае `a == b` тоже может дать истинный результат при сравнении содержимого. **Q**: До достижения самого нижнего уровня при обходе в ширину число узлов в очереди равно $2^h$ ? Да. Например, для полного двоичного дерева высоты $h = 2$ общее число узлов равно $n = 7$ , а число узлов на нижнем уровне равно $4 = 2^h = (n + 1) / 2$ . ================================================ FILE: ru/docs/index.html ================================================

Учебник по структурам данных и алгоритмам с анимированными схемами и кодом, готовым к запуску в один клик

Начать чтение GitHub

500 анимированных схем, код на 14 языках программирования и 3000 ответов сообщества помогут вам быстро войти в мир структур данных и алгоритмов.

Рекомендации

«Понятная вводная книга по структурам данных и алгоритмам, которая ведет читателя через практическое обучение шаг за шагом. Очень рекомендую всем начинающим.»

—— Junhui Deng, профессор факультета компьютерных наук Университета Цинхуа

«Если бы у меня была “Hello Algo”, когда я изучал структуры данных и алгоритмы, учиться было бы в десять раз проще!»

—— Mu Li, Senior Principal Scientist, Amazon

Анимированные схемы

Материал изложен ясно и последовательно, поэтому вход в тему получается плавным.

"A picture is worth a thousand words."
«Одна схема стоит тысячи слов»

Запуск в один клик

Код на более чем десяти языках можно запускать и визуализировать прямо в книге.

"Talk is cheap. Show me the code."
«Меньше слов, больше кода»

Учимся вместе

Добро пожаловать к обсуждениям и вопросам: продвигаться вместе проще.

"Learning by teaching."
«Обучая других, учишься сам»

Переводчики

Русская печатная версия была переведена И. А. Шевкун, а русская онлайн-версия была вычитана Yuyan Huang. Благодарим их за вклад!

Translator: shevkun
И. А. Шевкун

Переводчики кода

Многоязычные версии кода в этой книге были подготовлены при участии следующих разработчиков. Благодарим их за время и вклад!

Участники проекта

Книга постоянно совершенствуется благодаря совместным усилиям более чем 200 участников сообщества. Спасибо им за время и вклад!

Contributors
================================================ FILE: ru/docs/index.md ================================================ # Hello Алго Учебник по структурам данных и алгоритмам с анимированными иллюстрациями и готовыми к запуску примерами кода. [Начать чтение](chapter_hello_algo/) ================================================ FILE: ru/mkdocs.yml ================================================ # Config inheritance INHERIT: ../mkdocs.yml # Project information site_name: Hello Algo site_url: https://www.hello-algo.com/ru/ site_description: "Учебник по структурам данных и алгоритмам с анимированными иллюстрациями и готовым к запуску кодом" docs_dir: ../build/ru/docs site_dir: ../site/ru # Repository edit_uri: tree/main/ru/docs version: 1.3.0 # Configuration theme: custom_dir: ../build/overrides language: ru font: text: PT Sans palette: - scheme: default primary: white accent: teal toggle: icon: material/theme-light-dark name: Темная тема - scheme: slate primary: black accent: teal toggle: icon: material/theme-light-dark name: Светлая тема extra: status: new: Недавно добавлено # Page tree nav: - Перед началом: - chapter_hello_algo/index.md - Глава 0. Предисловие: # [icon: material/book-open-outline] - chapter_preface/index.md - 0.1 Об этой книге: chapter_preface/about_the_book.md - 0.2 Как пользоваться этой книгой: chapter_preface/suggestions.md - 0.3 Резюме: chapter_preface/summary.md - Глава 1. Введение в алгоритмы: # [icon: material/calculator-variant-outline] - chapter_introduction/index.md - 1.1 Алгоритмы повсюду: chapter_introduction/algorithms_are_everywhere.md - 1.2 Что такое алгоритм: chapter_introduction/what_is_dsa.md - 1.3 Резюме: chapter_introduction/summary.md - Глава 2. Анализ сложности: # [icon: material/timer-sand] - chapter_computational_complexity/index.md - 2.1 Оценка эффективности алгоритмов: chapter_computational_complexity/performance_evaluation.md - 2.2 Итерация и рекурсия: chapter_computational_complexity/iteration_and_recursion.md - 2.3 Временная сложность: chapter_computational_complexity/time_complexity.md - 2.4 Пространственная сложность: chapter_computational_complexity/space_complexity.md - 2.5 Резюме: chapter_computational_complexity/summary.md - Глава 3. Структуры данных: # [icon: material/shape-outline] - chapter_data_structure/index.md - 3.1 Классификация структур данных: chapter_data_structure/classification_of_data_structure.md - 3.2 Базовые типы данных: chapter_data_structure/basic_data_types.md - 3.3 Кодирование чисел *: chapter_data_structure/number_encoding.md - 3.4 Кодирование символов *: chapter_data_structure/character_encoding.md - 3.5 Резюме: chapter_data_structure/summary.md - Глава 4. Массивы и списки: # [icon: material/view-list-outline] - chapter_array_and_linkedlist/index.md - 4.1 Массив: chapter_array_and_linkedlist/array.md - 4.2 Связный список: chapter_array_and_linkedlist/linked_list.md - 4.3 Список: chapter_array_and_linkedlist/list.md - 4.4 Оперативная память и кэш *: chapter_array_and_linkedlist/ram_and_cache.md - 4.5 Резюме: chapter_array_and_linkedlist/summary.md - Глава 5. Стек и очередь: # [icon: material/stack-overflow] - chapter_stack_and_queue/index.md - 5.1 Стек: chapter_stack_and_queue/stack.md - 5.2 Очередь: chapter_stack_and_queue/queue.md - 5.3 Двусторонняя очередь: chapter_stack_and_queue/deque.md - 5.4 Резюме: chapter_stack_and_queue/summary.md - Глава 6. Хеш-таблицы: # [icon: material/table-search] - chapter_hashing/index.md - 6.1 Хеш-таблица: chapter_hashing/hash_map.md - 6.2 Хеш-коллизии: chapter_hashing/hash_collision.md - 6.3 Алгоритмы хеширования: chapter_hashing/hash_algorithm.md - 6.4 Резюме: chapter_hashing/summary.md - Глава 7. Деревья: # [icon: material/graph-outline] - chapter_tree/index.md - 7.1 Двоичное дерево: chapter_tree/binary_tree.md - 7.2 Обход двоичного дерева: chapter_tree/binary_tree_traversal.md - 7.3 Представление двоичного дерева массивом: chapter_tree/array_representation_of_tree.md - 7.4 Двоичное дерево поиска: chapter_tree/binary_search_tree.md - 7.5 AVL-дерево *: chapter_tree/avl_tree.md - 7.6 Краткие итоги: chapter_tree/summary.md - Глава 8. Куча: # [icon: material/family-tree] - chapter_heap/index.md - 8.1 Куча: chapter_heap/heap.md - 8.2 Построение кучи: chapter_heap/build_heap.md - 8.3 Задача Top-k: chapter_heap/top_k.md - 8.4 Резюме: chapter_heap/summary.md - Глава 9. Графы: # [icon: material/graphql] - chapter_graph/index.md - 9.1 Граф: chapter_graph/graph.md - 9.2 Базовые операции графа: chapter_graph/graph_operations.md - 9.3 Обход графа: chapter_graph/graph_traversal.md - 9.4 Краткие итоги: chapter_graph/summary.md - Глава 10. Поиск: # [icon: material/text-search] - chapter_searching/index.md - 10.1 Двоичный поиск: chapter_searching/binary_search.md - 10.2 Двоичный поиск точки вставки: chapter_searching/binary_search_insertion.md - 10.3 Двоичный поиск границ: chapter_searching/binary_search_edge.md - 10.4 Стратегии оптимизации хеширования: chapter_searching/replace_linear_by_hashing.md - 10.5 Переосмысление алгоритмов поиска: chapter_searching/searching_algorithm_revisited.md - 10.6 Резюме: chapter_searching/summary.md - Глава 11. Сортировка: # [icon: material/sort-ascending] - chapter_sorting/index.md - 11.1 Алгоритмы сортировки: chapter_sorting/sorting_algorithm.md - 11.2 Сортировка выбором: chapter_sorting/selection_sort.md - 11.3 Сортировка пузырьком: chapter_sorting/bubble_sort.md - 11.4 Сортировка вставками: chapter_sorting/insertion_sort.md - 11.5 Быстрая сортировка: chapter_sorting/quick_sort.md - 11.6 Сортировка слиянием: chapter_sorting/merge_sort.md - 11.7 Пирамидальная сортировка: chapter_sorting/heap_sort.md - 11.8 Блочная сортировка: chapter_sorting/bucket_sort.md - 11.9 Сортировка подсчетом: chapter_sorting/counting_sort.md - 11.10 Поразрядная сортировка: chapter_sorting/radix_sort.md - 11.11 Резюме: chapter_sorting/summary.md - Глава 12. Разделяй и властвуй: # [icon: material/set-split] - chapter_divide_and_conquer/index.md - 12.1 Стратегия разделяй и властвуй: chapter_divide_and_conquer/divide_and_conquer.md - 12.2 Поисковая стратегия разделяй и властвуй: chapter_divide_and_conquer/binary_search_recur.md - 12.3 Задача построения двоичного дерева: chapter_divide_and_conquer/build_binary_tree_problem.md - 12.4 Задача о Ханойской башне: chapter_divide_and_conquer/hanota_problem.md - 12.5 Резюме: chapter_divide_and_conquer/summary.md - Глава 13. Поиск с возвратом: # [icon: material/map-marker-path] - chapter_backtracking/index.md - 13.1 Алгоритм поиска с возвратом: chapter_backtracking/backtracking_algorithm.md - 13.2 Задача о перестановках: chapter_backtracking/permutations_problem.md - 13.3 Задача о сумме подмножеств: chapter_backtracking/subset_sum_problem.md - 13.4 Задача о n ферзях: chapter_backtracking/n_queens_problem.md - 13.5 Резюме: chapter_backtracking/summary.md - Глава 14. Динамическое программирование: # [icon: material/table-pivot] - chapter_dynamic_programming/index.md - 14.1 Первое знакомство с динамическим программированием: chapter_dynamic_programming/intro_to_dynamic_programming.md - 14.2 Свойства задач динамического программирования: chapter_dynamic_programming/dp_problem_features.md - 14.3 Подход к решению задач динамического программирования: chapter_dynamic_programming/dp_solution_pipeline.md - 14.4 Задача о рюкзаке 0-1: chapter_dynamic_programming/knapsack_problem.md - 14.5 Задача о полном рюкзаке: chapter_dynamic_programming/unbounded_knapsack_problem.md - 14.6 Задача о расстоянии редактирования: chapter_dynamic_programming/edit_distance_problem.md - 14.7 Резюме: chapter_dynamic_programming/summary.md - Глава 15. Жадность: # [icon: material/head-heart-outline] - chapter_greedy/index.md - 15.1 Жадный алгоритм: chapter_greedy/greedy_algorithm.md - 15.2 Задача о дробном рюкзаке: chapter_greedy/fractional_knapsack_problem.md - 15.3 Задача о максимальной вместимости: chapter_greedy/max_capacity_problem.md - 15.4 Задача о максимальном произведении разбиения: chapter_greedy/max_product_cutting_problem.md - 15.5 Резюме: chapter_greedy/summary.md - Глава 16. Приложение: # [icon: material/help-circle-outline] - chapter_appendix/index.md - 16.1 Установка среды программирования: chapter_appendix/installation.md - 16.2 Присоединяйтесь к созданию книги: chapter_appendix/contribution.md - 16.3 Глоссарий: chapter_appendix/terminology.md - Список литературы: - chapter_reference/index.md ================================================ FILE: zh-hant/README.md ================================================

hello-algo-typing-svg
動畫圖解、一鍵執行的資料結構與演算法教程

简体中文 | 繁體中文 | English日本語Русский

## 關於本書 本專案旨在打造一本開源免費、新手友好的資料結構與演算法入門教程。 - 全書採用動畫圖解,內容清晰易懂、學習曲線平滑,引導初學者探索資料結構與演算法的知識地圖。 - 源程式碼可一鍵執行,幫助讀者在練習中提升程式設計技能,瞭解演算法工作原理和資料結構底層實現。 - 提倡讀者互助學習,歡迎大家在評論區提出問題與分享見解,在交流討論中共同進步。 若本書對您有所幫助,請在頁面右上角點個 Star :star: 支持一下,謝謝! ## 推薦語 > “一本通俗易懂的資料結構與演算法入門書,引導讀者手腦並用地學習,強烈推薦演算法初學者閱讀。” > > **—— 鄧俊輝,清華大學計算機系教授** > “如果我當年學資料結構與演算法的時候有《Hello 演算法》,學起來應該會簡單 10 倍!” > > **—— 李沐,亞馬遜資深首席科學家** ## 鳴謝

Warp-Github-LG-02

[Warp is built for coding with multiple AI agents.](https://go.warp.dev/hello-algo) 強烈推薦 Warp 終端,高顏值 + 好用的 AI,體驗非常棒! ## 貢獻 > [!Important] > > 歡迎您於 [#1171](https://github.com/krahets/hello-algo/issues/1171) 為繁體中文版勘誤。 本開源書仍在持續更新之中,歡迎您參與本專案,一同為讀者提供更優質的學習內容。 - [內容修正](https://www.hello-algo.com/chapter_appendix/contribution/):請您協助修正或在評論區指出語法錯誤、內容缺失、文字歧義、無效連結或程式碼 bug 等問題。 - [程式碼轉譯](https://github.com/krahets/hello-algo/issues/15):期待您貢獻各種語言程式碼,已支持 Python、Java、C++、Go、JavaScript 等 12 門程式語言。 - [中譯英](https://github.com/krahets/hello-algo/issues/914):誠邀您加入我們的翻譯小組,成員主要來自計算機相關專業、英語專業和英文母語者。 歡迎您提出寶貴意見和建議,如有任何問題請提交 Issues 或微信聯繫 `krahets-jyd` 。 感謝本開源書的每一位撰稿人,是他們的無私奉獻讓這本書變得更好,他們是:

## License The texts, code, images, photos, and videos in this repository are licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). ================================================ FILE: zh-hant/codes/Dockerfile ================================================ FROM ubuntu:latest # Use Ubuntu image from Aliyun RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \ sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \ sed -i 's/ports.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list RUN apt-get update && apt-get install -y wget # Install languages environment ARG LANGS RUN for LANG in $LANGS; do \ case $LANG in \ python) \ apt-get install -y python3.10 && \ update-alternatives --install /usr/bin/python python /usr/bin/python3.10 1 ;; \ cpp) \ apt-get install -y g++ gdb ;; \ java) \ apt-get install -y openjdk-17-jdk ;; \ csharp) \ wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \ dpkg -i packages-microsoft-prod.deb && \ apt-get update && \ apt-get install -y dotnet-sdk-8.0 ;; \ # More languages... *) \ echo "Warning: No installation workflow for $LANG" ;; \ esac \ done WORKDIR /codes COPY ./ ./ CMD ["/bin/bash"] ================================================ FILE: zh-hant/codes/c/.gitignore ================================================ # Ignore all * # Unignore all with extensions !*.* # Unignore all dirs !*/ *.dSYM/ build/ ================================================ FILE: zh-hant/codes/c/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(hello_algo C) set(CMAKE_C_STANDARD 11) include_directories(./include) add_subdirectory(chapter_computational_complexity) add_subdirectory(chapter_array_and_linkedlist) add_subdirectory(chapter_stack_and_queue) add_subdirectory(chapter_hashing) add_subdirectory(chapter_tree) add_subdirectory(chapter_heap) add_subdirectory(chapter_graph) add_subdirectory(chapter_searching) add_subdirectory(chapter_sorting) add_subdirectory(chapter_divide_and_conquer) add_subdirectory(chapter_backtracking) add_subdirectory(chapter_dynamic_programming) add_subdirectory(chapter_greedy) ================================================ FILE: zh-hant/codes/c/chapter_array_and_linkedlist/CMakeLists.txt ================================================ add_executable(array array.c) add_executable(linked_list linked_list.c) add_executable(my_list my_list.c) ================================================ FILE: zh-hant/codes/c/chapter_array_and_linkedlist/array.c ================================================ /** * File: array.c * Created Time: 2022-12-20 * Author: MolDuM (moldum@163.com) */ #include "../utils/common.h" /* 隨機訪問元素 */ int randomAccess(int *nums, int size) { // 在區間 [0, size) 中隨機抽取一個數字 int randomIndex = rand() % size; // 獲取並返回隨機元素 int randomNum = nums[randomIndex]; return randomNum; } /* 擴展陣列長度 */ int *extend(int *nums, int size, int enlarge) { // 初始化一個擴展長度後的陣列 int *res = (int *)malloc(sizeof(int) * (size + enlarge)); // 將原陣列中的所有元素複製到新陣列 for (int i = 0; i < size; i++) { res[i] = nums[i]; } // 初始化擴展後的空間 for (int i = size; i < size + enlarge; i++) { res[i] = 0; } // 返回擴展後的新陣列 return res; } /* 在陣列的索引 index 處插入元素 num */ void insert(int *nums, int size, int num, int index) { // 把索引 index 以及之後的所有元素向後移動一位 for (int i = size - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 將 num 賦給 index 處的元素 nums[index] = num; } /* 刪除索引 index 處的元素 */ // 注意:stdio.h 佔用了 remove 關鍵詞 void removeItem(int *nums, int size, int index) { // 把索引 index 之後的所有元素向前移動一位 for (int i = index; i < size - 1; i++) { nums[i] = nums[i + 1]; } } /* 走訪陣列 */ void traverse(int *nums, int size) { int count = 0; // 透過索引走訪陣列 for (int i = 0; i < size; i++) { count += nums[i]; } } /* 在陣列中查詢指定元素 */ int find(int *nums, int size, int target) { for (int i = 0; i < size; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ int main() { /* 初始化陣列 */ int size = 5; int arr[5]; printf("陣列 arr = "); printArray(arr, size); int nums[] = {1, 3, 2, 5, 4}; printf("陣列 nums = "); printArray(nums, size); /* 隨機訪問 */ int randomNum = randomAccess(nums, size); printf("在 nums 中獲取隨機元素 %d", randomNum); /* 長度擴展 */ int enlarge = 3; int *res = extend(nums, size, enlarge); size += enlarge; printf("將陣列長度擴展至 8 ,得到 nums = "); printArray(res, size); /* 插入元素 */ insert(res, size, 6, 3); printf("在索引 3 處插入數字 6 ,得到 nums = "); printArray(res, size); /* 刪除元素 */ removeItem(res, size, 2); printf("刪除索引 2 處的元素,得到 nums = "); printArray(res, size); /* 走訪陣列 */ traverse(res, size); /* 查詢元素 */ int index = find(res, size, 3); printf("在 res 中查詢元素 3 ,得到索引 = %d\n", index); /* 釋放記憶體 */ free(res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_array_and_linkedlist/linked_list.c ================================================ /** * File: linked_list.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* 在鏈結串列的節點 n0 之後插入節點 P */ void insert(ListNode *n0, ListNode *P) { ListNode *n1 = n0->next; P->next = n1; n0->next = P; } /* 刪除鏈結串列的節點 n0 之後的首個節點 */ // 注意:stdio.h 佔用了 remove 關鍵詞 void removeItem(ListNode *n0) { if (!n0->next) return; // n0 -> P -> n1 ListNode *P = n0->next; ListNode *n1 = P->next; n0->next = n1; // 釋放記憶體 free(P); } /* 訪問鏈結串列中索引為 index 的節點 */ ListNode *access(ListNode *head, int index) { for (int i = 0; i < index; i++) { if (head == NULL) return NULL; head = head->next; } return head; } /* 在鏈結串列中查詢值為 target 的首個節點 */ int find(ListNode *head, int target) { int index = 0; while (head) { if (head->val == target) return index; head = head->next; index++; } return -1; } /* Driver Code */ int main() { /* 初始化鏈結串列 */ // 初始化各個節點 ListNode *n0 = newListNode(1); ListNode *n1 = newListNode(3); ListNode *n2 = newListNode(2); ListNode *n3 = newListNode(5); ListNode *n4 = newListNode(4); // 構建節點之間的引用 n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; printf("初始化的鏈結串列為\r\n"); printLinkedList(n0); /* 插入節點 */ insert(n0, newListNode(0)); printf("插入節點後的鏈結串列為\r\n"); printLinkedList(n0); /* 刪除節點 */ removeItem(n0); printf("刪除節點後的鏈結串列為\r\n"); printLinkedList(n0); /* 訪問節點 */ ListNode *node = access(n0, 3); printf("鏈結串列中索引 3 處的節點的值 = %d\r\n", node->val); /* 查詢節點 */ int index = find(n0, 2); printf("鏈結串列中值為 2 的節點的索引 = %d\r\n", index); // 釋放記憶體 freeMemoryLinkedList(n0); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_array_and_linkedlist/my_list.c ================================================ /** * File: my_list.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* 串列類別 */ typedef struct { int *arr; // 陣列(儲存串列元素) int capacity; // 串列容量 int size; // 串列大小 int extendRatio; // 串列每次擴容的倍數 } MyList; void extendCapacity(MyList *nums); /* 建構子 */ MyList *newMyList() { MyList *nums = malloc(sizeof(MyList)); nums->capacity = 10; nums->arr = malloc(sizeof(int) * nums->capacity); nums->size = 0; nums->extendRatio = 2; return nums; } /* 析構函式 */ void delMyList(MyList *nums) { free(nums->arr); free(nums); } /* 獲取串列長度 */ int size(MyList *nums) { return nums->size; } /* 獲取串列容量 */ int capacity(MyList *nums) { return nums->capacity; } /* 訪問元素 */ int get(MyList *nums, int index) { assert(index >= 0 && index < nums->size); return nums->arr[index]; } /* 更新元素 */ void set(MyList *nums, int index, int num) { assert(index >= 0 && index < nums->size); nums->arr[index] = num; } /* 在尾部新增元素 */ void add(MyList *nums, int num) { if (size(nums) == capacity(nums)) { extendCapacity(nums); // 擴容 } nums->arr[size(nums)] = num; nums->size++; } /* 在中間插入元素 */ void insert(MyList *nums, int index, int num) { assert(index >= 0 && index < size(nums)); // 元素數量超出容量時,觸發擴容機制 if (size(nums) == capacity(nums)) { extendCapacity(nums); // 擴容 } for (int i = size(nums); i > index; --i) { nums->arr[i] = nums->arr[i - 1]; } nums->arr[index] = num; nums->size++; } /* 刪除元素 */ // 注意:stdio.h 佔用了 remove 關鍵詞 int removeItem(MyList *nums, int index) { assert(index >= 0 && index < size(nums)); int num = nums->arr[index]; for (int i = index; i < size(nums) - 1; i++) { nums->arr[i] = nums->arr[i + 1]; } nums->size--; return num; } /* 串列擴容 */ void extendCapacity(MyList *nums) { // 先分配空間 int newCapacity = capacity(nums) * nums->extendRatio; int *extend = (int *)malloc(sizeof(int) * newCapacity); int *temp = nums->arr; // 複製舊資料到新資料 for (int i = 0; i < size(nums); i++) extend[i] = nums->arr[i]; // 釋放舊資料 free(temp); // 更新新資料 nums->arr = extend; nums->capacity = newCapacity; } /* 將串列轉換為 Array 用於列印 */ int *toArray(MyList *nums) { return nums->arr; } /* Driver Code */ int main() { /* 初始化串列 */ MyList *nums = newMyList(); /* 在尾部新增元素 */ add(nums, 1); add(nums, 3); add(nums, 2); add(nums, 5); add(nums, 4); printf("串列 nums = "); printArray(toArray(nums), size(nums)); printf("容量 = %d ,長度 = %d\n", capacity(nums), size(nums)); /* 在中間插入元素 */ insert(nums, 3, 6); printf("在索引 3 處插入數字 6 ,得到 nums = "); printArray(toArray(nums), size(nums)); /* 刪除元素 */ removeItem(nums, 3); printf("刪除索引 3 處的元素,得到 nums = "); printArray(toArray(nums), size(nums)); /* 訪問元素 */ int num = get(nums, 1); printf("訪問索引 1 處的元素,得到 num = %d\n", num); /* 更新元素 */ set(nums, 1, 0); printf("將索引 1 處的元素更新為 0 ,得到 nums = "); printArray(toArray(nums), size(nums)); /* 測試擴容機制 */ for (int i = 0; i < 10; i++) { // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 add(nums, i); } printf("擴容後的串列 nums = "); printArray(toArray(nums), size(nums)); printf("容量 = %d ,長度 = %d\n", capacity(nums), size(nums)); /* 釋放分配記憶體 */ delMyList(nums); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_backtracking/CMakeLists.txt ================================================ add_executable(permutations_i permutations_i.c) add_executable(permutations_ii permutations_ii.c) add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.c) add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.c) add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.c) add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.c) add_executable(subset_sum_i_naive subset_sum_i_naive.c) add_executable(subset_sum_i subset_sum_i.c) add_executable(subset_sum_ii subset_sum_ii.c) add_executable(n_queens n_queens.c) ================================================ FILE: zh-hant/codes/c/chapter_backtracking/n_queens.c ================================================ /** * File : n_queens.c * Created Time: 2023-09-25 * Author : lucas (superrat6@gmail.com) */ #include "../utils/common.h" #define MAX_SIZE 100 /* 回溯演算法:n 皇后 */ void backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE], bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) { // 當放置完所有行時,記錄解 if (row == n) { res[*resSize] = (char **)malloc(sizeof(char *) * n); for (int i = 0; i < n; ++i) { res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1)); strcpy(res[*resSize][i], state[i]); } (*resSize)++; return; } // 走訪所有列 for (int col = 0; col < n; col++) { // 計算該格子對應的主對角線和次對角線 int diag1 = row - col + n - 1; int diag2 = row + col; // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 嘗試:將皇后放置在該格子 state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // 放置下一行 backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2); // 回退:將該格子恢復為空位 state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* 求解 n 皇后 */ char ***nQueens(int n, int *returnSize) { char state[MAX_SIZE][MAX_SIZE]; // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { state[i][j] = '#'; } state[i][n] = '\0'; } bool cols[MAX_SIZE] = {false}; // 記錄列是否有皇后 bool diags1[2 * MAX_SIZE - 1] = {false}; // 記錄主對角線上是否有皇后 bool diags2[2 * MAX_SIZE - 1] = {false}; // 記錄次對角線上是否有皇后 char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE); *returnSize = 0; backtrack(0, n, state, res, returnSize, cols, diags1, diags2); return res; } /* Driver Code */ int main() { int n = 4; int returnSize; char ***res = nQueens(n, &returnSize); printf("輸入棋盤長寬為%d\n", n); printf("皇后放置方案共有 %d 種\n", returnSize); for (int i = 0; i < returnSize; ++i) { for (int j = 0; j < n; ++j) { printf("["); for (int k = 0; res[i][j][k] != '\0'; ++k) { printf("%c", res[i][j][k]); if (res[i][j][k + 1] != '\0') { printf(", "); } } printf("]\n"); } printf("---------------------\n"); } // 釋放記憶體 for (int i = 0; i < returnSize; ++i) { for (int j = 0; j < n; ++j) { free(res[i][j]); } free(res[i]); } free(res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_backtracking/permutations_i.c ================================================ /** * File: permutations_i.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com), krahets (krahets@163.com) */ #include "../utils/common.h" // 假設最多有 1000 個排列 #define MAX_SIZE 1000 /* 回溯演算法:全排列 I */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // 當狀態長度等於元素數量時,記錄解 if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // 走訪所有選擇 for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // 剪枝:不允許重複選擇元素 if (!selected[i]) { // 嘗試:做出選擇,更新狀態 selected[i] = true; state[stateSize] = choice; // 進行下一輪選擇 backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false; } } } /* 全排列 I */ int **permutationsI(int *nums, int numsSize, int *returnSize) { int *state = (int *)malloc(numsSize * sizeof(int)); bool *selected = (bool *)malloc(numsSize * sizeof(bool)); for (int i = 0; i < numsSize; i++) { selected[i] = false; } int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); *returnSize = 0; backtrack(state, 0, nums, numsSize, selected, res, returnSize); free(state); free(selected); return res; } /* Driver Code */ int main() { int nums[] = {1, 2, 3}; int numsSize = sizeof(nums) / sizeof(nums[0]); int returnSize; int **res = permutationsI(nums, numsSize, &returnSize); printf("輸入陣列 nums = "); printArray(nums, numsSize); printf("\n所有排列 res = \n"); for (int i = 0; i < returnSize; i++) { printArray(res[i], numsSize); } // 釋放記憶體 for (int i = 0; i < returnSize; i++) { free(res[i]); } free(res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_backtracking/permutations_ii.c ================================================ /** * File: permutations_ii.c * Created Time: 2023-10-17 * Author: krahets (krahets@163.com) */ #include "../utils/common.h" // 假設最多有 1000 個排列,元素最大為 1000 #define MAX_SIZE 1000 /* 回溯演算法:全排列 II */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // 當狀態長度等於元素數量時,記錄解 if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // 走訪所有選擇 bool duplicated[MAX_SIZE] = {false}; for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 if (!selected[i] && !duplicated[choice]) { // 嘗試:做出選擇,更新狀態 duplicated[choice] = true; // 記錄選擇過的元素值 selected[i] = true; state[stateSize] = choice; // 進行下一輪選擇 backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false; } } } /* 全排列 II */ int **permutationsII(int *nums, int numsSize, int *returnSize) { int *state = (int *)malloc(numsSize * sizeof(int)); bool *selected = (bool *)malloc(numsSize * sizeof(bool)); for (int i = 0; i < numsSize; i++) { selected[i] = false; } int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); *returnSize = 0; backtrack(state, 0, nums, numsSize, selected, res, returnSize); free(state); free(selected); return res; } /* Driver Code */ int main() { int nums[] = {1, 1, 2}; int numsSize = sizeof(nums) / sizeof(nums[0]); int returnSize; int **res = permutationsII(nums, numsSize, &returnSize); printf("輸入陣列 nums = "); printArray(nums, numsSize); printf("\n所有排列 res = \n"); for (int i = 0; i < returnSize; i++) { printArray(res[i], numsSize); } // 釋放記憶體 for (int i = 0; i < returnSize; i++) { free(res[i]); } free(res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_backtracking/preorder_traversal_i_compact.c ================================================ /** * File: preorder_traversal_i_compact.c * Created Time: 2023-05-10 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // 假設結果長度不超過 100 #define MAX_SIZE 100 TreeNode *res[MAX_SIZE]; int resSize = 0; /* 前序走訪:例題一 */ void preOrder(TreeNode *root) { if (root == NULL) { return; } if (root->val == 7) { // 記錄解 res[resSize++] = root; } preOrder(root->left); preOrder(root->right); } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\n初始化二元樹\n"); printTree(root); // 前序走訪 preOrder(root); printf("\n輸出所有值為 7 的節點\n"); int *vals = malloc(resSize * sizeof(int)); for (int i = 0; i < resSize; i++) { vals[i] = res[i]->val; } printArray(vals, resSize); // 釋放記憶體 freeMemoryTree(root); free(vals); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c ================================================ /** * File: preorder_traversal_ii_compact.c * Created Time: 2023-05-28 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // 假設路徑和結果長度不超過 100 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* 前序走訪:例題二 */ void preOrder(TreeNode *root) { if (root == NULL) { return; } // 嘗試 path[pathSize++] = root; if (root->val == 7) { // 記錄解 for (int i = 0; i < pathSize; ++i) { res[resSize][i] = path[i]; } resSize++; } preOrder(root->left); preOrder(root->right); // 回退 pathSize--; } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\n初始化二元樹\n"); printTree(root); // 前序走訪 preOrder(root); printf("\n輸出所有根節點到節點 7 的路徑\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // 釋放記憶體 freeMemoryTree(root); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c ================================================ /** * File: preorder_traversal_iii_compact.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // 假設路徑和結果長度不超過 100 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* 前序走訪:例題三 */ void preOrder(TreeNode *root) { // 剪枝 if (root == NULL || root->val == 3) { return; } // 嘗試 path[pathSize++] = root; if (root->val == 7) { // 記錄解 for (int i = 0; i < pathSize; i++) { res[resSize][i] = path[i]; } resSize++; } preOrder(root->left); preOrder(root->right); // 回退 pathSize--; } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\n初始化二元樹\n"); printTree(root); // 前序走訪 preOrder(root); printf("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // 釋放記憶體 freeMemoryTree(root); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_template.c ================================================ /** * File: preorder_traversal_iii_template.c * Created Time: 2023-06-04 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" // 假設路徑和結果長度不超過 100 #define MAX_SIZE 100 #define MAX_RES_SIZE 100 TreeNode *path[MAX_SIZE]; TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; int pathSize = 0, resSize = 0; /* 判斷當前狀態是否為解 */ bool isSolution(void) { return pathSize > 0 && path[pathSize - 1]->val == 7; } /* 記錄解 */ void recordSolution(void) { for (int i = 0; i < pathSize; i++) { res[resSize][i] = path[i]; } resSize++; } /* 判斷在當前狀態下,該選擇是否合法 */ bool isValid(TreeNode *choice) { return choice != NULL && choice->val != 3; } /* 更新狀態 */ void makeChoice(TreeNode *choice) { path[pathSize++] = choice; } /* 恢復狀態 */ void undoChoice(void) { pathSize--; } /* 回溯演算法:例題三 */ void backtrack(TreeNode *choices[2]) { // 檢查是否為解 if (isSolution()) { // 記錄解 recordSolution(); } // 走訪所有選擇 for (int i = 0; i < 2; i++) { TreeNode *choice = choices[i]; // 剪枝:檢查選擇是否合法 if (isValid(choice)) { // 嘗試:做出選擇,更新狀態 makeChoice(choice); // 進行下一輪選擇 TreeNode *nextChoices[2] = {choice->left, choice->right}; backtrack(nextChoices); // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(); } } } /* Driver Code */ int main() { int arr[] = {1, 7, 3, 4, 5, 6, 7}; TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); printf("\n初始化二元樹\n"); printTree(root); // 回溯演算法 TreeNode *choices[2] = {root, NULL}; backtrack(choices); printf("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點\n"); for (int i = 0; i < resSize; ++i) { int *vals = malloc(MAX_SIZE * sizeof(int)); int size = 0; for (int j = 0; res[i][j] != NULL; ++j) { vals[size++] = res[i][j]->val; } printArray(vals, size); free(vals); } // 釋放記憶體 freeMemoryTree(root); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_backtracking/subset_sum_i.c ================================================ /** * File: subset_sum_i.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // 狀態(子集) int state[MAX_SIZE]; int stateSize = 0; // 結果串列(子集串列) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* 回溯演算法:子集和 I */ void backtrack(int target, int *choices, int choicesSize, int start) { // 子集和等於 target 時,記錄解 if (target == 0) { for (int i = 0; i < stateSize; ++i) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 for (int i = start; i < choicesSize; i++) { // 剪枝一:若子集和超過 target ,則直接結束迴圈 // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target if (target - choices[i] < 0) { break; } // 嘗試:做出選擇,更新 target, start state[stateSize] = choices[i]; stateSize++; // 進行下一輪選擇 backtrack(target - choices[i], choices, choicesSize, i); // 回退:撤銷選擇,恢復到之前的狀態 stateSize--; } } /* 比較函式 */ int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); } /* 求解子集和 I */ void subsetSumI(int *nums, int numsSize, int target) { qsort(nums, numsSize, sizeof(int), cmp); // 對 nums 進行排序 int start = 0; // 走訪起始點 backtrack(target, nums, numsSize, start); } /* Driver Code */ int main() { int nums[] = {3, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumI(nums, numsSize, target); printf("輸入陣列 nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("所有和等於 %d 的子集 res = \n", target); for (int i = 0; i < resSize; ++i) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: zh-hant/codes/c/chapter_backtracking/subset_sum_i_naive.c ================================================ /** * File: subset_sum_i_naive.c * Created Time: 2023-07-28 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // 狀態(子集) int state[MAX_SIZE]; int stateSize = 0; // 結果串列(子集串列) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* 回溯演算法:子集和 I */ void backtrack(int target, int total, int *choices, int choicesSize) { // 子集和等於 target 時,記錄解 if (total == target) { for (int i = 0; i < stateSize; i++) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // 走訪所有選擇 for (int i = 0; i < choicesSize; i++) { // 剪枝:若子集和超過 target ,則跳過該選擇 if (total + choices[i] > target) { continue; } // 嘗試:做出選擇,更新元素和 total state[stateSize++] = choices[i]; // 進行下一輪選擇 backtrack(target, total + choices[i], choices, choicesSize); // 回退:撤銷選擇,恢復到之前的狀態 stateSize--; } } /* 求解子集和 I(包含重複子集) */ void subsetSumINaive(int *nums, int numsSize, int target) { resSize = 0; // 初始化解的數量為0 backtrack(target, 0, nums, numsSize); } /* Driver Code */ int main() { int nums[] = {3, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumINaive(nums, numsSize, target); printf("輸入陣列 nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("所有和等於 %d 的子集 res = \n", target); for (int i = 0; i < resSize; i++) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: zh-hant/codes/c/chapter_backtracking/subset_sum_ii.c ================================================ /** * File: subset_sum_ii.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 100 #define MAX_RES_SIZE 100 // 狀態(子集) int state[MAX_SIZE]; int stateSize = 0; // 結果串列(子集串列) int res[MAX_RES_SIZE][MAX_SIZE]; int resColSizes[MAX_RES_SIZE]; int resSize = 0; /* 回溯演算法:子集和 II */ void backtrack(int target, int *choices, int choicesSize, int start) { // 子集和等於 target 時,記錄解 if (target == 0) { for (int i = 0; i < stateSize; i++) { res[resSize][i] = state[i]; } resColSizes[resSize++] = stateSize; return; } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 for (int i = start; i < choicesSize; i++) { // 剪枝一:若子集和超過 target ,則直接跳過 if (target - choices[i] < 0) { continue; } // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 if (i > start && choices[i] == choices[i - 1]) { continue; } // 嘗試:做出選擇,更新 target, start state[stateSize] = choices[i]; stateSize++; // 進行下一輪選擇 backtrack(target - choices[i], choices, choicesSize, i + 1); // 回退:撤銷選擇,恢復到之前的狀態 stateSize--; } } /* 比較函式 */ int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); } /* 求解子集和 II */ void subsetSumII(int *nums, int numsSize, int target) { // 對 nums 進行排序 qsort(nums, numsSize, sizeof(int), cmp); // 開始回溯 backtrack(target, nums, numsSize, 0); } /* Driver Code */ int main() { int nums[] = {4, 4, 5}; int numsSize = sizeof(nums) / sizeof(nums[0]); int target = 9; subsetSumII(nums, numsSize, target); printf("輸入陣列 nums = "); printArray(nums, numsSize); printf("target = %d\n", target); printf("所有和等於 %d 的子集 res = \n", target); for (int i = 0; i < resSize; ++i) { printArray(res[i], resColSizes[i]); } return 0; } ================================================ FILE: zh-hant/codes/c/chapter_computational_complexity/CMakeLists.txt ================================================ add_executable(iteration iteration.c) add_executable(recursion recursion.c) add_executable(time_complexity time_complexity.c) add_executable(worst_best_time_complexity worst_best_time_complexity.c) add_executable(space_complexity space_complexity.c) ================================================ FILE: zh-hant/codes/c/chapter_computational_complexity/iteration.c ================================================ /** * File: iteration.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com), MwumLi (mwumli@hotmail.com) */ #include "../utils/common.h" /* for 迴圈 */ int forLoop(int n) { int res = 0; // 迴圈求和 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } /* while 迴圈 */ int whileLoop(int n) { int res = 0; int i = 1; // 初始化條件變數 // 迴圈求和 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // 更新條件變數 } return res; } /* while 迴圈(兩次更新) */ int whileLoopII(int n) { int res = 0; int i = 1; // 初始化條件變數 // 迴圈求和 1, 4, 10, ... while (i <= n) { res += i; // 更新條件變數 i++; i *= 2; } return res; } /* 雙層 for 迴圈 */ char *nestedForLoop(int n) { // n * n 為對應點數量,"(i, j), " 對應字串長最大為 6+10*2,加上最後一個空字元 \0 的額外空間 int size = n * n * 26 + 1; char *res = malloc(size * sizeof(char)); // 迴圈 i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // 迴圈 j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { char tmp[26]; snprintf(tmp, sizeof(tmp), "(%d, %d), ", i, j); strncat(res, tmp, size - strlen(res) - 1); } } return res; } /* Driver Code */ int main() { int n = 5; int res; res = forLoop(n); printf("\nfor 迴圈的求和結果 res = %d\n", res); res = whileLoop(n); printf("\nwhile 迴圈的求和結果 res = %d\n", res); res = whileLoopII(n); printf("\nwhile 迴圈(兩次更新)求和結果 res = %d\n", res); char *resStr = nestedForLoop(n); printf("\n雙層 for 迴圈的走訪結果 %s\r\n", resStr); free(resStr); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_computational_complexity/recursion.c ================================================ /** * File: recursion.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 遞迴 */ int recur(int n) { // 終止條件 if (n == 1) return 1; // 遞:遞迴呼叫 int res = recur(n - 1); // 迴:返回結果 return n + res; } /* 使用迭代模擬遞迴 */ int forLoopRecur(int n) { int stack[1000]; // 藉助一個大陣列來模擬堆疊 int top = -1; // 堆疊頂索引 int res = 0; // 遞:遞迴呼叫 for (int i = n; i > 0; i--) { // 透過“入堆疊操作”模擬“遞” stack[1 + top++] = i; } // 迴:返回結果 while (top >= 0) { // 透過“出堆疊操作”模擬“迴” res += stack[top--]; } // res = 1+2+3+...+n return res; } /* 尾遞迴 */ int tailRecur(int n, int res) { // 終止條件 if (n == 0) return res; // 尾遞迴呼叫 return tailRecur(n - 1, res + n); } /* 費波那契數列:遞迴 */ int fib(int n) { // 終止條件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // 遞迴呼叫 f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // 返回結果 f(n) return res; } /* Driver Code */ int main() { int n = 5; int res; res = recur(n); printf("\n遞迴函式的求和結果 res = %d\n", res); res = forLoopRecur(n); printf("\n使用迭代模擬遞迴求和結果 res = %d\n", res); res = tailRecur(n, 0); printf("\n尾遞迴函式的求和結果 res = %d\n", res); res = fib(n); printf("\n費波那契數列的第 %d 項為 %d\n", n, res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_computational_complexity/space_complexity.c ================================================ /** * File: space_complexity.c * Created Time: 2023-04-15 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 函式 */ int func() { // 執行某些操作 return 0; } /* 常數階 */ void constant(int n) { // 常數、變數、物件佔用 O(1) 空間 const int a = 0; int b = 0; int nums[1000]; ListNode *node = newListNode(0); free(node); // 迴圈中的變數佔用 O(1) 空間 for (int i = 0; i < n; i++) { int c = 0; } // 迴圈中的函式佔用 O(1) 空間 for (int i = 0; i < n; i++) { func(); } } /* 雜湊表 */ typedef struct { int key; int val; UT_hash_handle hh; // 基於 uthash.h 實現 } HashTable; /* 線性階 */ void linear(int n) { // 長度為 n 的陣列佔用 O(n) 空間 int *nums = malloc(sizeof(int) * n); free(nums); // 長度為 n 的串列佔用 O(n) 空間 ListNode **nodes = malloc(sizeof(ListNode *) * n); for (int i = 0; i < n; i++) { nodes[i] = newListNode(i); } // 記憶體釋放 for (int i = 0; i < n; i++) { free(nodes[i]); } free(nodes); // 長度為 n 的雜湊表佔用 O(n) 空間 HashTable *h = NULL; for (int i = 0; i < n; i++) { HashTable *tmp = malloc(sizeof(HashTable)); tmp->key = i; tmp->val = i; HASH_ADD_INT(h, key, tmp); } // 記憶體釋放 HashTable *curr, *tmp; HASH_ITER(hh, h, curr, tmp) { HASH_DEL(h, curr); free(curr); } } /* 線性階(遞迴實現) */ void linearRecur(int n) { printf("遞迴 n = %d\r\n", n); if (n == 1) return; linearRecur(n - 1); } /* 平方階 */ void quadratic(int n) { // 二維串列佔用 O(n^2) 空間 int **numMatrix = malloc(sizeof(int *) * n); for (int i = 0; i < n; i++) { int *tmp = malloc(sizeof(int) * n); for (int j = 0; j < n; j++) { tmp[j] = 0; } numMatrix[i] = tmp; } // 記憶體釋放 for (int i = 0; i < n; i++) { free(numMatrix[i]); } free(numMatrix); } /* 平方階(遞迴實現) */ int quadraticRecur(int n) { if (n <= 0) return 0; int *nums = malloc(sizeof(int) * n); printf("遞迴 n = %d 中的 nums 長度 = %d\r\n", n, n); int res = quadraticRecur(n - 1); free(nums); return res; } /* 指數階(建立滿二元樹) */ TreeNode *buildTree(int n) { if (n == 0) return NULL; TreeNode *root = newTreeNode(0); root->left = buildTree(n - 1); root->right = buildTree(n - 1); return root; } /* Driver Code */ int main() { int n = 5; // 常數階 constant(n); // 線性階 linear(n); linearRecur(n); // 平方階 quadratic(n); quadraticRecur(n); // 指數階 TreeNode *root = buildTree(n); printTree(root); // 釋放記憶體 freeMemoryTree(root); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_computational_complexity/time_complexity.c ================================================ /** * File: time_complexity.c * Created Time: 2023-01-03 * Author: codingonion (coderonion@gmail.com) */ #include "../utils/common.h" /* 常數階 */ int constant(int n) { int count = 0; int size = 100000; int i = 0; for (int i = 0; i < size; i++) { count++; } return count; } /* 線性階 */ int linear(int n) { int count = 0; for (int i = 0; i < n; i++) { count++; } return count; } /* 線性階(走訪陣列) */ int arrayTraversal(int *nums, int n) { int count = 0; // 迴圈次數與陣列長度成正比 for (int i = 0; i < n; i++) { count++; } return count; } /* 平方階 */ int quadratic(int n) { int count = 0; // 迴圈次數與資料大小 n 成平方關係 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* 平方階(泡沫排序) */ int bubbleSort(int *nums, int n) { int count = 0; // 計數器 // 外迴圈:未排序區間為 [0, i] for (int i = n - 1; i > 0; i--) { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 元素交換包含 3 個單元操作 } } } return count; } /* 指數階(迴圈實現) */ int exponential(int n) { int count = 0; int bas = 1; // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) for (int i = 0; i < n; i++) { for (int j = 0; j < bas; j++) { count++; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指數階(遞迴實現) */ int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 對數階(迴圈實現) */ int logarithmic(int n) { int count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* 對數階(遞迴實現) */ int logRecur(int n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* 線性對數階 */ int linearLogRecur(int n) { if (n <= 1) return 1; int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (int i = 0; i < n; i++) { count++; } return count; } /* 階乘階(遞迴實現) */ int factorialRecur(int n) { if (n == 0) return 1; int count = 0; for (int i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ int main(int argc, char *argv[]) { // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 int n = 8; printf("輸入資料大小 n = %d\n", n); int count = constant(n); printf("常數階的操作數量 = %d\n", count); count = linear(n); printf("線性階的操作數量 = %d\n", count); // 分配堆積區記憶體(建立一維可變長陣列:陣列中元素數量為 n ,元素型別為 int ) int *nums = (int *)malloc(n * sizeof(int)); count = arrayTraversal(nums, n); printf("線性階(走訪陣列)的操作數量 = %d\n", count); count = quadratic(n); printf("平方階的操作數量 = %d\n", count); for (int i = 0; i < n; i++) { nums[i] = n - i; // [n,n-1,...,2,1] } count = bubbleSort(nums, n); printf("平方階(泡沫排序)的操作數量 = %d\n", count); count = exponential(n); printf("指數階(迴圈實現)的操作數量 = %d\n", count); count = expRecur(n); printf("指數階(遞迴實現)的操作數量 = %d\n", count); count = logarithmic(n); printf("對數階(迴圈實現)的操作數量 = %d\n", count); count = logRecur(n); printf("對數階(遞迴實現)的操作數量 = %d\n", count); count = linearLogRecur(n); printf("線性對數階(遞迴實現)的操作數量 = %d\n", count); count = factorialRecur(n); printf("階乘階(遞迴實現)的操作數量 = %d\n", count); // 釋放堆積區記憶體 if (nums != NULL) { free(nums); nums = NULL; } getchar(); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_computational_complexity/worst_best_time_complexity.c ================================================ /** * File: worst_best_time_complexity.c * Created Time: 2023-01-03 * Author: codingonion (coderonion@gmail.com) */ #include "../utils/common.h" /* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ int *randomNumbers(int n) { // 分配堆積區記憶體(建立一維可變長陣列:陣列中元素數量為 n ,元素型別為 int ) int *nums = (int *)malloc(n * sizeof(int)); // 生成陣列 nums = { 1, 2, 3, ..., n } for (int i = 0; i < n; i++) { nums[i] = i + 1; } // 隨機打亂陣列元素 for (int i = n - 1; i > 0; i--) { int j = rand() % (i + 1); int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } return nums; } /* 查詢陣列 nums 中數字 1 所在索引 */ int findOne(int *nums, int n) { for (int i = 0; i < n; i++) { // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) if (nums[i] == 1) return i; } return -1; } /* Driver Code */ int main(int argc, char *argv[]) { // 初始化隨機數種子 srand((unsigned int)time(NULL)); for (int i = 0; i < 10; i++) { int n = 100; int *nums = randomNumbers(n); int index = findOne(nums, n); printf("\n陣列 [ 1, 2, ..., n ] 被打亂後 = "); printArray(nums, n); printf("數字 1 的索引為 %d\n", index); // 釋放堆積區記憶體 if (nums != NULL) { free(nums); nums = NULL; } } return 0; } ================================================ FILE: zh-hant/codes/c/chapter_divide_and_conquer/CMakeLists.txt ================================================ add_executable(binary_search_recur binary_search_recur.c) add_executable(build_tree build_tree.c) add_executable(hanota hanota.c) ================================================ FILE: zh-hant/codes/c/chapter_divide_and_conquer/binary_search_recur.c ================================================ /** * File: binary_search_recur.c * Created Time: 2023-10-01 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 二分搜尋:問題 f(i, j) */ int dfs(int nums[], int target, int i, int j) { // 若區間為空,代表無目標元素,則返回 -1 if (i > j) { return -1; } // 計算中點索引 m int m = (i + j) / 2; if (nums[m] < target) { // 遞迴子問題 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 遞迴子問題 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目標元素,返回其索引 return m; } } /* 二分搜尋 */ int binarySearch(int nums[], int target, int numsSize) { int n = numsSize; // 求解問題 f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ int main() { int target = 6; int nums[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; int numsSize = sizeof(nums) / sizeof(nums[0]); // 二分搜尋(雙閉區間) int index = binarySearch(nums, target, numsSize); printf("目標元素 6 的索引 = %d\n", index); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_divide_and_conquer/build_tree.c ================================================ /** * File : build_tree.c * Created Time: 2023-10-16 * Author : lucas (superrat6@gmail.com) */ #include "../utils/common.h" // 假設所有元素都小於 1000 #define MAX_SIZE 1000 /* 構建二元樹:分治 */ TreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) { // 子樹區間為空時終止 if (r - l < 0) return NULL; // 初始化根節點 TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); root->val = preorder[i]; root->left = NULL; root->right = NULL; // 查詢 m ,從而劃分左右子樹 int m = inorderMap[preorder[i]]; // 子問題:構建左子樹 root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size); // 子問題:構建右子樹 root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size); // 返回根節點 return root; } /* 構建二元樹 */ TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) { // 初始化雜湊表,儲存 inorder 元素到索引的對映 int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE); for (int i = 0; i < inorderSize; i++) { inorderMap[inorder[i]] = i; } TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize); free(inorderMap); return root; } /* Driver Code */ int main() { int preorder[] = {3, 9, 2, 1, 7}; int inorder[] = {9, 3, 1, 2, 7}; int preorderSize = sizeof(preorder) / sizeof(preorder[0]); int inorderSize = sizeof(inorder) / sizeof(inorder[0]); printf("前序走訪 = "); printArray(preorder, preorderSize); printf("中序走訪 = "); printArray(inorder, inorderSize); TreeNode *root = buildTree(preorder, preorderSize, inorder, inorderSize); printf("構建的二元樹為:\n"); printTree(root); freeMemoryTree(root); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_divide_and_conquer/hanota.c ================================================ /** * File: hanota.c * Created Time: 2023-10-01 * Author: Zuoxun (845242523@qq.com), lucas(superrat6@gmail.com) */ #include "../utils/common.h" // 假設最多有 1000 個排列 #define MAX_SIZE 1000 /* 移動一個圓盤 */ void move(int *src, int *srcSize, int *tar, int *tarSize) { // 從 src 頂部拿出一個圓盤 int pan = src[*srcSize - 1]; src[*srcSize - 1] = 0; (*srcSize)--; // 將圓盤放入 tar 頂部 tar[*tarSize] = pan; (*tarSize)++; } /* 求解河內塔問題 f(i) */ void dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) { // 若 src 只剩下一個圓盤,則直接將其移到 tar if (i == 1) { move(src, srcSize, tar, tarSize); return; } // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize); // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar move(src, srcSize, tar, tarSize); // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize); } /* 求解河內塔問題 */ void solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) { // 將 A 頂部 n 個圓盤藉助 B 移到 C dfs(*ASize, A, ASize, B, BSize, C, CSize); } /* Driver Code */ int main() { // 串列尾部是柱子頂部 int a[] = {5, 4, 3, 2, 1}; int b[MAX_SIZE] = {0}; int c[MAX_SIZE] = {0}; int ASize = sizeof(a) / sizeof(a[0]); int BSize = 0; int CSize = 0; printf("\n初始狀態下:"); printf("\nA = "); printArray(a, ASize); printf("B = "); printArray(b, BSize); printf("C = "); printArray(c, CSize); solveHanota(a, &ASize, b, &BSize, c, &CSize); printf("\n圓盤移動完成後:"); printf("A = "); printArray(a, ASize); printf("B = "); printArray(b, BSize); printf("C = "); printArray(c, CSize); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_dynamic_programming/CMakeLists.txt ================================================ add_executable(climbing_stairs_constraint_dp climbing_stairs_constraint_dp.c) add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.c) add_executable(min_path_sum min_path_sum.c) add_executable(knapsack knapsack.c) add_executable(unbounded_knapsack unbounded_knapsack.c) add_executable(coin_change coin_change.c) add_executable(coin_change_ii coin_change_ii.c) add_executable(edit_distance edit_distance.c) ================================================ FILE: zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c ================================================ /** * File: climbing_stairs_backtrack.c * Created Time: 2023-09-22 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* 回溯 */ void backtrack(int *choices, int state, int n, int *res, int len) { // 當爬到第 n 階時,方案數量加 1 if (state == n) res[0]++; // 走訪所有選擇 for (int i = 0; i < len; i++) { int choice = choices[i]; // 剪枝:不允許越過第 n 階 if (state + choice > n) continue; // 嘗試:做出選擇,更新狀態 backtrack(choices, state + choice, n, res, len); // 回退 } } /* 爬樓梯:回溯 */ int climbingStairsBacktrack(int n) { int choices[2] = {1, 2}; // 可選擇向上爬 1 階或 2 階 int state = 0; // 從第 0 階開始爬 int *res = (int *)malloc(sizeof(int)); *res = 0; // 使用 res[0] 記錄方案數量 int len = sizeof(choices) / sizeof(int); backtrack(choices, state, n, res, len); int result = *res; free(res); return result; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsBacktrack(n); printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c ================================================ /** * File: climbing_stairs_constraint_dp.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 帶約束爬樓梯:動態規劃 */ int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // 初始化 dp 表,用於儲存子問題的解 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(3, sizeof(int)); } // 初始狀態:預設最小子問題的解 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 狀態轉移:從較小子問題逐步求解較大子問題 for (int i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } int res = dp[n][1] + dp[n][2]; // 釋放記憶體 for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsConstraintDP(n); printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c ================================================ /** * File: climbing_stairs_dfs.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* 搜尋 */ int dfs(int i) { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1) + dfs(i - 2); return count; } /* 爬樓梯:搜尋 */ int climbingStairsDFS(int n) { return dfs(n); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFS(n); printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c ================================================ /** * File: climbing_stairs_dfs_mem.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* 記憶化搜尋 */ int dfs(int i, int *mem) { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i; // 若存在記錄 dp[i] ,則直接返回之 if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1, mem) + dfs(i - 2, mem); // 記錄 dp[i] mem[i] = count; return count; } /* 爬樓梯:記憶化搜尋 */ int climbingStairsDFSMem(int n) { // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 int *mem = (int *)malloc((n + 1) * sizeof(int)); for (int i = 0; i <= n; i++) { mem[i] = -1; } int result = dfs(n, mem); free(mem); return result; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFSMem(n); printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c ================================================ /** * File: climbing_stairs_dp.c * Created Time: 2023-09-19 * Author: huawuque404 (huawuque404@163.com) */ #include "../utils/common.h" /* 爬樓梯:動態規劃 */ int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; // 初始化 dp 表,用於儲存子問題的解 int *dp = (int *)malloc((n + 1) * sizeof(int)); // 初始狀態:預設最小子問題的解 dp[1] = 1; dp[2] = 2; // 狀態轉移:從較小子問題逐步求解較大子問題 for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } int result = dp[n]; free(dp); return result; } /* 爬樓梯:空間最佳化後的動態規劃 */ int climbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDP(n); printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); res = climbingStairsDPComp(n); printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_dynamic_programming/coin_change.c ================================================ /** * File: coin_change.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 求最小值 */ int myMin(int a, int b) { return a < b ? a : b; } /* 零錢兌換:動態規劃 */ int coinChangeDP(int coins[], int amt, int coinsSize) { int n = coinsSize; int MAX = amt + 1; // 初始化 dp 表 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(amt + 1, sizeof(int)); } // 狀態轉移:首行首列 for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 狀態轉移:其餘行和列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } int res = dp[n][amt] != MAX ? dp[n][amt] : -1; // 釋放記憶體 for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* 零錢兌換:空間最佳化後的動態規劃 */ int coinChangeDPComp(int coins[], int amt, int coinsSize) { int n = coinsSize; int MAX = amt + 1; // 初始化 dp 表 int *dp = malloc((amt + 1) * sizeof(int)); for (int j = 1; j <= amt; j++) { dp[j] = MAX; } dp[0] = 0; // 狀態轉移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1); } } } int res = dp[amt] != MAX ? dp[amt] : -1; // 釋放記憶體 free(dp); return res; } /* Driver code */ int main() { int coins[] = {1, 2, 5}; int coinsSize = sizeof(coins) / sizeof(coins[0]); int amt = 4; // 動態規劃 int res = coinChangeDP(coins, amt, coinsSize); printf("湊到目標金額所需的最少硬幣數量為 %d\n", res); // 空間最佳化後的動態規劃 res = coinChangeDPComp(coins, amt, coinsSize); printf("湊到目標金額所需的最少硬幣數量為 %d\n", res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_dynamic_programming/coin_change_ii.c ================================================ /** * File: coin_change_ii.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 零錢兌換 II:動態規劃 */ int coinChangeIIDP(int coins[], int amt, int coinsSize) { int n = coinsSize; // 初始化 dp 表 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(amt + 1, sizeof(int)); } // 初始化首列 for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // 狀態轉移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a]; } else { // 不選和選硬幣 i 這兩種方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } int res = dp[n][amt]; // 釋放記憶體 for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } /* 零錢兌換 II:空間最佳化後的動態規劃 */ int coinChangeIIDPComp(int coins[], int amt, int coinsSize) { int n = coinsSize; // 初始化 dp 表 int *dp = calloc(amt + 1, sizeof(int)); dp[0] = 1; // 狀態轉移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } int res = dp[amt]; // 釋放記憶體 free(dp); return res; } /* Driver code */ int main() { int coins[] = {1, 2, 5}; int coinsSize = sizeof(coins) / sizeof(coins[0]); int amt = 5; // 動態規劃 int res = coinChangeIIDP(coins, amt, coinsSize); printf("湊出目標金額的硬幣組合數量為 %d\n", res); // 空間最佳化後的動態規劃 res = coinChangeIIDPComp(coins, amt, coinsSize); printf("湊出目標金額的硬幣組合數量為 %d\n", res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_dynamic_programming/edit_distance.c ================================================ /** * File: edit_distance.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 求最小值 */ int myMin(int a, int b) { return a < b ? a : b; } /* 編輯距離:暴力搜尋 */ int editDistanceDFS(char *s, char *t, int i, int j) { // 若 s 和 t 都為空,則返回 0 if (i == 0 && j == 0) return 0; // 若 s 為空,則返回 t 長度 if (i == 0) return j; // 若 t 為空,則返回 s 長度 if (j == 0) return i; // 若兩字元相等,則直接跳過此兩字元 if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 int insert = editDistanceDFS(s, t, i, j - 1); int del = editDistanceDFS(s, t, i - 1, j); int replace = editDistanceDFS(s, t, i - 1, j - 1); // 返回最少編輯步數 return myMin(myMin(insert, del), replace) + 1; } /* 編輯距離:記憶化搜尋 */ int editDistanceDFSMem(char *s, char *t, int memCols, int **mem, int i, int j) { // 若 s 和 t 都為空,則返回 0 if (i == 0 && j == 0) return 0; // 若 s 為空,則返回 t 長度 if (i == 0) return j; // 若 t 為空,則返回 s 長度 if (j == 0) return i; // 若已有記錄,則直接返回之 if (mem[i][j] != -1) return mem[i][j]; // 若兩字元相等,則直接跳過此兩字元 if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 int insert = editDistanceDFSMem(s, t, memCols, mem, i, j - 1); int del = editDistanceDFSMem(s, t, memCols, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); // 記錄並返回最少編輯步數 mem[i][j] = myMin(myMin(insert, del), replace) + 1; return mem[i][j]; } /* 編輯距離:動態規劃 */ int editDistanceDP(char *s, char *t, int n, int m) { int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(m + 1, sizeof(int)); } // 狀態轉移:首行首列 for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // 狀態轉移:其餘行和列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // 若兩字元相等,則直接跳過此兩字元 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } int res = dp[n][m]; // 釋放記憶體 for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* 編輯距離:空間最佳化後的動態規劃 */ int editDistanceDPComp(char *s, char *t, int n, int m) { int *dp = calloc(m + 1, sizeof(int)); // 狀態轉移:首行 for (int j = 1; j <= m; j++) { dp[j] = j; } // 狀態轉移:其餘行 for (int i = 1; i <= n; i++) { // 狀態轉移:首列 int leftup = dp[0]; // 暫存 dp[i-1, j-1] dp[0] = i; // 狀態轉移:其餘列 for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 若兩字元相等,則直接跳過此兩字元 dp[j] = leftup; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 更新為下一輪的 dp[i-1, j-1] } } int res = dp[m]; // 釋放記憶體 free(dp); return res; } /* Driver Code */ int main() { char *s = "bag"; char *t = "pack"; int n = strlen(s), m = strlen(t); // 暴力搜尋 int res = editDistanceDFS(s, t, n, m); printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res); // 記憶化搜尋 int **mem = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { mem[i] = malloc((m + 1) * sizeof(int)); memset(mem[i], -1, (m + 1) * sizeof(int)); } res = editDistanceDFSMem(s, t, m + 1, mem, n, m); printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res); // 釋放記憶體 for (int i = 0; i <= n; i++) { free(mem[i]); } free(mem); // 動態規劃 res = editDistanceDP(s, t, n, m); printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res); // 空間最佳化後的動態規劃 res = editDistanceDPComp(s, t, n, m); printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_dynamic_programming/knapsack.c ================================================ /** * File: knapsack.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 求最大值 */ int myMax(int a, int b) { return a > b ? a : b; } /* 0-1 背包:暴力搜尋 */ int knapsackDFS(int wgt[], int val[], int i, int c) { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i == 0 || c == 0) { return 0; } // 若超過背包容量,則只能選擇不放入背包 if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // 返回兩種方案中價值更大的那一個 return myMax(no, yes); } /* 0-1 背包:記憶化搜尋 */ int knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i == 0 || c == 0) { return 0; } // 若已有記錄,則直接返回 if (mem[i][c] != -1) { return mem[i][c]; } // 若超過背包容量,則只能選擇不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 記錄並返回兩種方案中價值更大的那一個 mem[i][c] = myMax(no, yes); return mem[i][c]; } /* 0-1 背包:動態規劃 */ int knapsackDP(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // 初始化 dp 表 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(cap + 1, sizeof(int)); } // 狀態轉移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[n][cap]; // 釋放記憶體 for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* 0-1 背包:空間最佳化後的動態規劃 */ int knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // 初始化 dp 表 int *dp = calloc(cap + 1, sizeof(int)); // 狀態轉移 for (int i = 1; i <= n; i++) { // 倒序走訪 for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // 不選和選物品 i 這兩種方案的較大值 dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[cap]; // 釋放記憶體 free(dp); return res; } /* Driver Code */ int main() { int wgt[] = {10, 20, 30, 40, 50}; int val[] = {50, 120, 150, 210, 240}; int cap = 50; int n = sizeof(wgt) / sizeof(wgt[0]); int wgtSize = n; // 暴力搜尋 int res = knapsackDFS(wgt, val, n, cap); printf("不超過背包容量的最大物品價值為 %d\n", res); // 記憶化搜尋 int **mem = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { mem[i] = malloc((cap + 1) * sizeof(int)); memset(mem[i], -1, (cap + 1) * sizeof(int)); } res = knapsackDFSMem(wgt, val, cap + 1, mem, n, cap); printf("不超過背包容量的最大物品價值為 %d\n", res); // 釋放記憶體 for (int i = 0; i <= n; i++) { free(mem[i]); } free(mem); // 動態規劃 res = knapsackDP(wgt, val, cap, wgtSize); printf("不超過背包容量的最大物品價值為 %d\n", res); // 空間最佳化後的動態規劃 res = knapsackDPComp(wgt, val, cap, wgtSize); printf("不超過背包容量的最大物品價值為 %d\n", res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c ================================================ /** * File: min_cost_climbing_stairs_dp.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 求最小值 */ int myMin(int a, int b) { return a < b ? a : b; } /* 爬樓梯最小代價:動態規劃 */ int minCostClimbingStairsDP(int cost[], int costSize) { int n = costSize - 1; if (n == 1 || n == 2) return cost[n]; // 初始化 dp 表,用於儲存子問題的解 int *dp = calloc(n + 1, sizeof(int)); // 初始狀態:預設最小子問題的解 dp[1] = cost[1]; dp[2] = cost[2]; // 狀態轉移:從較小子問題逐步求解較大子問題 for (int i = 3; i <= n; i++) { dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i]; } int res = dp[n]; // 釋放記憶體 free(dp); return res; } /* 爬樓梯最小代價:空間最佳化後的動態規劃 */ int minCostClimbingStairsDPComp(int cost[], int costSize) { int n = costSize - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = myMin(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ int main() { int cost[] = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; int costSize = sizeof(cost) / sizeof(cost[0]); printf("輸入樓梯的代價串列為:"); printArray(cost, costSize); int res = minCostClimbingStairsDP(cost, costSize); printf("爬完樓梯的最低代價為 %d\n", res); res = minCostClimbingStairsDPComp(cost, costSize); printf("爬完樓梯的最低代價為 %d\n", res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_dynamic_programming/min_path_sum.c ================================================ /** * File: min_path_sum.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" // 假設矩陣最大行列數為 100 #define MAX_SIZE 100 /* 求最小值 */ int myMin(int a, int b) { return a < b ? a : b; } /* 最小路徑和:暴力搜尋 */ int minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) { // 若為左上角單元格,則終止搜尋 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 || j < 0) { return INT_MAX; } // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); // 返回從左上角到 (i, j) 的最小路徑代價 return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; } /* 最小路徑和:記憶化搜尋 */ int minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) { // 若為左上角單元格,則終止搜尋 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 || j < 0) { return INT_MAX; } // 若已有記錄,則直接返回 if (mem[i][j] != -1) { return mem[i][j]; } // 左邊和上邊單元格的最小路徑代價 int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // 記錄並返回左上角到 (i, j) 的最小路徑代價 mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; return mem[i][j]; } /* 最小路徑和:動態規劃 */ int minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { // 初始化 dp 表 int **dp = malloc(n * sizeof(int *)); for (int i = 0; i < n; i++) { dp[i] = calloc(m, sizeof(int)); } dp[0][0] = grid[0][0]; // 狀態轉移:首行 for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 狀態轉移:首列 for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 狀態轉移:其餘行和列 for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } int res = dp[n - 1][m - 1]; // 釋放記憶體 for (int i = 0; i < n; i++) { free(dp[i]); } return res; } /* 最小路徑和:空間最佳化後的動態規劃 */ int minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { // 初始化 dp 表 int *dp = calloc(m, sizeof(int)); // 狀態轉移:首行 dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 狀態轉移:其餘行 for (int i = 1; i < n; i++) { // 狀態轉移:首列 dp[0] = dp[0] + grid[i][0]; // 狀態轉移:其餘列 for (int j = 1; j < m; j++) { dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j]; } } int res = dp[m - 1]; // 釋放記憶體 free(dp); return res; } /* Driver Code */ int main() { int grid[MAX_SIZE][MAX_SIZE] = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; int n = 4, m = 4; // 矩陣容量為 MAX_SIZE * MAX_SIZE ,有效行列數為 n * m // 暴力搜尋 int res = minPathSumDFS(grid, n - 1, m - 1); printf("從左上角到右下角的最小路徑和為 %d\n", res); // 記憶化搜尋 int mem[MAX_SIZE][MAX_SIZE]; memset(mem, -1, sizeof(mem)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); printf("從左上角到右下角的最小路徑和為 %d\n", res); // 動態規劃 res = minPathSumDP(grid, n, m); printf("從左上角到右下角的最小路徑和為 %d\n", res); // 空間最佳化後的動態規劃 res = minPathSumDPComp(grid, n, m); printf("從左上角到右下角的最小路徑和為 %d\n", res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_dynamic_programming/unbounded_knapsack.c ================================================ /** * File: unbounded_knapsack.c * Created Time: 2023-10-02 * Author: Zuoxun (845242523@qq.com) */ #include "../utils/common.h" /* 求最大值 */ int myMax(int a, int b) { return a > b ? a : b; } /* 完全背包:動態規劃 */ int unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // 初始化 dp 表 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(cap + 1, sizeof(int)); } // 狀態轉移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[n][cap]; // 釋放記憶體 for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } /* 完全背包:空間最佳化後的動態規劃 */ int unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // 初始化 dp 表 int *dp = calloc(cap + 1, sizeof(int)); // 狀態轉移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[c] = dp[c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[cap]; // 釋放記憶體 free(dp); return res; } /* Driver code */ int main() { int wgt[] = {1, 2, 3}; int val[] = {5, 11, 15}; int wgtSize = sizeof(wgt) / sizeof(wgt[0]); int cap = 4; // 動態規劃 int res = unboundedKnapsackDP(wgt, val, cap, wgtSize); printf("不超過背包容量的最大物品價值為 %d\n", res); // 空間最佳化後的動態規劃 res = unboundedKnapsackDPComp(wgt, val, cap, wgtSize); printf("不超過背包容量的最大物品價值為 %d\n", res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_graph/CMakeLists.txt ================================================ add_executable(graph_adjacency_matrix graph_adjacency_matrix.c) add_executable(graph_adjacency_list_test graph_adjacency_list_test.c) add_executable(graph_bfs graph_bfs.c) add_executable(graph_dfs graph_dfs.c) ================================================ FILE: zh-hant/codes/c/chapter_graph/graph_adjacency_list.c ================================================ /** * File: graph_adjacency_list.c * Created Time: 2023-07-07 * Author: NI-SW (947743645@qq.com) */ #include "../utils/common.h" // 假設節點最大數量為 100 #define MAX_SIZE 100 /* 節點結構體 */ typedef struct AdjListNode { Vertex *vertex; // 頂點 struct AdjListNode *next; // 後繼節點 } AdjListNode; /* 基於鄰接表實現的無向圖類別 */ typedef struct { AdjListNode *heads[MAX_SIZE]; // 節點陣列 int size; // 節點數量 } GraphAdjList; /* 建構子 */ GraphAdjList *newGraphAdjList() { GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList)); if (!graph) { return NULL; } graph->size = 0; for (int i = 0; i < MAX_SIZE; i++) { graph->heads[i] = NULL; } return graph; } /* 析構函式 */ void delGraphAdjList(GraphAdjList *graph) { for (int i = 0; i < graph->size; i++) { AdjListNode *cur = graph->heads[i]; while (cur != NULL) { AdjListNode *next = cur->next; if (cur != graph->heads[i]) { free(cur); } cur = next; } free(graph->heads[i]->vertex); free(graph->heads[i]); } free(graph); } /* 查詢頂點對應的節點 */ AdjListNode *findNode(GraphAdjList *graph, Vertex *vet) { for (int i = 0; i < graph->size; i++) { if (graph->heads[i]->vertex == vet) { return graph->heads[i]; } } return NULL; } /* 新增邊輔助函式 */ void addEdgeHelper(AdjListNode *head, Vertex *vet) { AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode)); node->vertex = vet; // 頭插法 node->next = head->next; head->next = node; } /* 新增邊 */ void addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { AdjListNode *head1 = findNode(graph, vet1); AdjListNode *head2 = findNode(graph, vet2); assert(head1 != NULL && head2 != NULL && head1 != head2); // 新增邊 vet1 - vet2 addEdgeHelper(head1, vet2); addEdgeHelper(head2, vet1); } /* 刪除邊輔助函式 */ void removeEdgeHelper(AdjListNode *head, Vertex *vet) { AdjListNode *pre = head; AdjListNode *cur = head->next; // 在鏈結串列中搜索 vet 對應節點 while (cur != NULL && cur->vertex != vet) { pre = cur; cur = cur->next; } if (cur == NULL) return; // 將 vet 對應節點從鏈結串列中刪除 pre->next = cur->next; // 釋放記憶體 free(cur); } /* 刪除邊 */ void removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { AdjListNode *head1 = findNode(graph, vet1); AdjListNode *head2 = findNode(graph, vet2); assert(head1 != NULL && head2 != NULL); // 刪除邊 vet1 - vet2 removeEdgeHelper(head1, head2->vertex); removeEdgeHelper(head2, head1->vertex); } /* 新增頂點 */ void addVertex(GraphAdjList *graph, Vertex *vet) { assert(graph != NULL && graph->size < MAX_SIZE); AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode)); head->vertex = vet; head->next = NULL; // 在鄰接表中新增一個新鏈結串列 graph->heads[graph->size++] = head; } /* 刪除頂點 */ void removeVertex(GraphAdjList *graph, Vertex *vet) { AdjListNode *node = findNode(graph, vet); assert(node != NULL); // 在鄰接表中刪除頂點 vet 對應的鏈結串列 AdjListNode *cur = node, *pre = NULL; while (cur) { pre = cur; cur = cur->next; free(pre); } // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 for (int i = 0; i < graph->size; i++) { cur = graph->heads[i]; pre = NULL; while (cur) { pre = cur; cur = cur->next; if (cur && cur->vertex == vet) { pre->next = cur->next; free(cur); break; } } } // 將該頂點之後的頂點向前移動,以填補空缺 int i; for (i = 0; i < graph->size; i++) { if (graph->heads[i] == node) break; } for (int j = i; j < graph->size - 1; j++) { graph->heads[j] = graph->heads[j + 1]; } graph->size--; free(vet); } /* 列印鄰接表 */ void printGraph(const GraphAdjList *graph) { printf("鄰接表 =\n"); for (int i = 0; i < graph->size; ++i) { AdjListNode *node = graph->heads[i]; printf("%d: [", node->vertex->val); node = node->next; while (node) { printf("%d, ", node->vertex->val); node = node->next; } printf("]\n"); } } ================================================ FILE: zh-hant/codes/c/chapter_graph/graph_adjacency_list_test.c ================================================ /** * File: graph_adjacency_list_test.c * Created Time: 2023-07-11 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" /* Driver Code */ int main() { int vals[] = {1, 3, 2, 5, 4}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // 新增所有頂點和邊 for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\n初始化後,圖為\n"); printGraph(graph); /* 新增邊 */ // 頂點 1, 2 即 v[0], v[2] addEdge(graph, v[0], v[2]); printf("\n新增邊 1-2 後,圖為\n"); printGraph(graph); /* 刪除邊 */ // 頂點 1, 3 即 v[0], v[1] removeEdge(graph, v[0], v[1]); printf("\n刪除邊 1-3 後,圖為\n"); printGraph(graph); /* 新增頂點 */ Vertex *v5 = newVertex(6); addVertex(graph, v5); printf("\n新增頂點 6 後,圖為\n"); printGraph(graph); /* 刪除頂點 */ // 頂點 3 即 v[1] removeVertex(graph, v[1]); printf("\n刪除頂點 3 後,圖為:\n"); printGraph(graph); // 釋放記憶體 delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_graph/graph_adjacency_matrix.c ================================================ /** * File: graph_adjacency_matrix.c * Created Time: 2023-07-06 * Author: NI-SW (947743645@qq.com) */ #include "../utils/common.h" // 假設頂點數量最大為 100 #define MAX_SIZE 100 /* 基於鄰接矩陣實現的無向圖結構體 */ typedef struct { int vertices[MAX_SIZE]; int adjMat[MAX_SIZE][MAX_SIZE]; int size; } GraphAdjMat; /* 建構子 */ GraphAdjMat *newGraphAdjMat() { GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat)); graph->size = 0; for (int i = 0; i < MAX_SIZE; i++) { for (int j = 0; j < MAX_SIZE; j++) { graph->adjMat[i][j] = 0; } } return graph; } /* 析構函式 */ void delGraphAdjMat(GraphAdjMat *graph) { free(graph); } /* 新增頂點 */ void addVertex(GraphAdjMat *graph, int val) { if (graph->size == MAX_SIZE) { fprintf(stderr, "圖的頂點數量已達最大值\n"); return; } // 新增第 n 個頂點,並將第 n 行和列置零 int n = graph->size; graph->vertices[n] = val; for (int i = 0; i <= n; i++) { graph->adjMat[n][i] = graph->adjMat[i][n] = 0; } graph->size++; } /* 刪除頂點 */ void removeVertex(GraphAdjMat *graph, int index) { if (index < 0 || index >= graph->size) { fprintf(stderr, "頂點索引越界\n"); return; } // 在頂點串列中移除索引 index 的頂點 for (int i = index; i < graph->size - 1; i++) { graph->vertices[i] = graph->vertices[i + 1]; } // 在鄰接矩陣中刪除索引 index 的行 for (int i = index; i < graph->size - 1; i++) { for (int j = 0; j < graph->size; j++) { graph->adjMat[i][j] = graph->adjMat[i + 1][j]; } } // 在鄰接矩陣中刪除索引 index 的列 for (int i = 0; i < graph->size; i++) { for (int j = index; j < graph->size - 1; j++) { graph->adjMat[i][j] = graph->adjMat[i][j + 1]; } } graph->size--; } /* 新增邊 */ // 參數 i, j 對應 vertices 元素索引 void addEdge(GraphAdjMat *graph, int i, int j) { if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { fprintf(stderr, "邊索引越界或相等\n"); return; } graph->adjMat[i][j] = 1; graph->adjMat[j][i] = 1; } /* 刪除邊 */ // 參數 i, j 對應 vertices 元素索引 void removeEdge(GraphAdjMat *graph, int i, int j) { if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { fprintf(stderr, "邊索引越界或相等\n"); return; } graph->adjMat[i][j] = 0; graph->adjMat[j][i] = 0; } /* 列印鄰接矩陣 */ void printGraphAdjMat(GraphAdjMat *graph) { printf("頂點串列 = "); printArray(graph->vertices, graph->size); printf("鄰接矩陣 =\n"); for (int i = 0; i < graph->size; i++) { printArray(graph->adjMat[i], graph->size); } } /* Driver Code */ int main() { // 初始化無向圖 GraphAdjMat *graph = newGraphAdjMat(); int vertices[] = {1, 3, 2, 5, 4}; for (int i = 0; i < 5; i++) { addVertex(graph, vertices[i]); } int edges[][2] = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; for (int i = 0; i < 6; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\n初始化後,圖為\n"); printGraphAdjMat(graph); /* 新增邊 */ // 頂點 1, 2 的索引分別為 0, 2 addEdge(graph, 0, 2); printf("\n新增邊 1-2 後,圖為\n"); printGraphAdjMat(graph); /* 刪除邊 */ // 頂點 1, 3 的索引分別為 0, 1 removeEdge(graph, 0, 1); printf("\n刪除邊 1-3 後,圖為\n"); printGraphAdjMat(graph); /* 新增頂點 */ addVertex(graph, 6); printf("\n新增頂點 6 後,圖為\n"); printGraphAdjMat(graph); /* 刪除頂點 */ // 頂點 3 的索引為 1 removeVertex(graph, 1); printf("\n刪除頂點 3 後,圖為\n"); printGraphAdjMat(graph); // 釋放記憶體 delGraphAdjMat(graph); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_graph/graph_bfs.c ================================================ /** * File: graph_bfs.c * Created Time: 2023-07-11 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" // 假設節點最大數量為 100 #define MAX_SIZE 100 /* 節點佇列結構體 */ typedef struct { Vertex *vertices[MAX_SIZE]; int front, rear, size; } Queue; /* 建構子 */ Queue *newQueue() { Queue *q = (Queue *)malloc(sizeof(Queue)); q->front = q->rear = q->size = 0; return q; } /* 判斷佇列是否為空 */ int isEmpty(Queue *q) { return q->size == 0; } /* 入列操作 */ void enqueue(Queue *q, Vertex *vet) { q->vertices[q->rear] = vet; q->rear = (q->rear + 1) % MAX_SIZE; q->size++; } /* 出列操作 */ Vertex *dequeue(Queue *q) { Vertex *vet = q->vertices[q->front]; q->front = (q->front + 1) % MAX_SIZE; q->size--; return vet; } /* 檢查頂點是否已被訪問 */ int isVisited(Vertex **visited, int size, Vertex *vet) { // 走訪查詢節點,使用 O(n) 時間 for (int i = 0; i < size; i++) { if (visited[i] == vet) return 1; } return 0; } /* 廣度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 void graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) { // 佇列用於實現 BFS Queue *queue = newQueue(); enqueue(queue, startVet); visited[(*visitedSize)++] = startVet; // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 while (!isEmpty(queue)) { Vertex *vet = dequeue(queue); // 佇列首頂點出隊 res[(*resSize)++] = vet; // 記錄訪問頂點 // 走訪該頂點的所有鄰接頂點 AdjListNode *node = findNode(graph, vet); while (node != NULL) { // 跳過已被訪問的頂點 if (!isVisited(visited, *visitedSize, node->vertex)) { enqueue(queue, node->vertex); // 只入列未訪問的頂點 visited[(*visitedSize)++] = node->vertex; // 標記該頂點已被訪問 } node = node->next; } } // 釋放記憶體 free(queue); } /* Driver Code */ int main() { // 初始化無向圖 int vals[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[1], v[4]}, {v[2], v[5]}, {v[3], v[4]}, {v[3], v[6]}, {v[4], v[5]}, {v[4], v[7]}, {v[5], v[8]}, {v[6], v[7]}, {v[7], v[8]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // 新增所有頂點和邊 for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\n初始化後,圖為\n"); printGraph(graph); // 廣度優先走訪 // 頂點走訪序列 Vertex *res[MAX_SIZE]; int resSize = 0; // 用於記錄已被訪問過的頂點 Vertex *visited[MAX_SIZE]; int visitedSize = 0; graphBFS(graph, v[0], res, &resSize, visited, &visitedSize); printf("\n廣度優先走訪(BFS)頂點序列為\n"); printArray(vetsToVals(res, resSize), resSize); // 釋放記憶體 delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_graph/graph_dfs.c ================================================ /** * File: graph_dfs.c * Created Time: 2023-07-13 * Author: NI-SW (947743645@qq.com) */ #include "graph_adjacency_list.c" // 假設節點最大數量為 100 #define MAX_SIZE 100 /* 檢查頂點是否已被訪問 */ int isVisited(Vertex **res, int size, Vertex *vet) { // 走訪查詢節點,使用 O(n) 時間 for (int i = 0; i < size; i++) { if (res[i] == vet) { return 1; } } return 0; } /* 深度優先走訪輔助函式 */ void dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) { // 記錄訪問頂點 res[(*resSize)++] = vet; // 走訪該頂點的所有鄰接頂點 AdjListNode *node = findNode(graph, vet); while (node != NULL) { // 跳過已被訪問的頂點 if (!isVisited(res, *resSize, node->vertex)) { // 遞迴訪問鄰接頂點 dfs(graph, res, resSize, node->vertex); } node = node->next; } } /* 深度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 void graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) { dfs(graph, res, resSize, startVet); } /* Driver Code */ int main() { // 初始化無向圖 int vals[] = {0, 1, 2, 3, 4, 5, 6}; int size = sizeof(vals) / sizeof(vals[0]); Vertex **v = valsToVets(vals, size); Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; int egdeSize = sizeof(edges) / sizeof(edges[0]); GraphAdjList *graph = newGraphAdjList(); // 新增所有頂點和邊 for (int i = 0; i < size; i++) { addVertex(graph, v[i]); } for (int i = 0; i < egdeSize; i++) { addEdge(graph, edges[i][0], edges[i][1]); } printf("\n初始化後,圖為\n"); printGraph(graph); // 深度優先走訪 Vertex *res[MAX_SIZE]; int resSize = 0; graphDFS(graph, v[0], res, &resSize); printf("\n深度優先走訪(DFS)頂點序列為\n"); printArray(vetsToVals(res, resSize), resSize); // 釋放記憶體 delGraphAdjList(graph); free(v); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_greedy/CMakeLists.txt ================================================ add_executable(coin_change_greedy coin_change_greedy.c) add_executable(fractional_knapsack fractional_knapsack.c) add_executable(max_capacity max_capacity.c) add_executable(max_product_cutting max_product_cutting.c) if (NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC") target_link_libraries(max_product_cutting m) endif() ================================================ FILE: zh-hant/codes/c/chapter_greedy/coin_change_greedy.c ================================================ /** * File: coin_change_greedy.c * Created Time: 2023-09-07 * Author: lwbaptx (lwbaptx@gmail.com) */ #include "../utils/common.h" /* 零錢兌換:貪婪 */ int coinChangeGreedy(int *coins, int size, int amt) { // 假設 coins 串列有序 int i = size - 1; int count = 0; // 迴圈進行貪婪選擇,直到無剩餘金額 while (amt > 0) { // 找到小於且最接近剩餘金額的硬幣 while (i > 0 && coins[i] > amt) { i--; } // 選擇 coins[i] amt -= coins[i]; count++; } // 若未找到可行方案,則返回 -1 return amt == 0 ? count : -1; } /* Driver Code */ int main() { // 貪婪:能夠保證找到全域性最優解 int coins1[6] = {1, 5, 10, 20, 50, 100}; int amt = 186; int res = coinChangeGreedy(coins1, 6, amt); printf("\ncoins = "); printArray(coins1, 6); printf("amt = %d\n", amt); printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res); // 貪婪:無法保證找到全域性最優解 int coins2[3] = {1, 20, 50}; amt = 60; res = coinChangeGreedy(coins2, 3, amt); printf("\ncoins = "); printArray(coins2, 3); printf("amt = %d\n", amt); printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res); printf("實際上需要的最少數量為 3 ,即 20 + 20 + 20\n"); // 貪婪:無法保證找到全域性最優解 int coins3[3] = {1, 49, 50}; amt = 98; res = coinChangeGreedy(coins3, 3, amt); printf("\ncoins = "); printArray(coins3, 3); printf("amt = %d\n", amt); printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res); printf("實際上需要的最少數量為 2 ,即 49 + 49\n"); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_greedy/fractional_knapsack.c ================================================ /** * File: fractional_knapsack.c * Created Time: 2023-09-14 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* 物品 */ typedef struct { int w; // 物品重量 int v; // 物品價值 } Item; /* 按照價值密度排序 */ int sortByValueDensity(const void *a, const void *b) { Item *t1 = (Item *)a; Item *t2 = (Item *)b; return (float)(t1->v) / t1->w < (float)(t2->v) / t2->w; } /* 分數背包:貪婪 */ float fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) { // 建立物品串列,包含兩個屬性:重量、價值 Item *items = malloc(sizeof(Item) * itemCount); for (int i = 0; i < itemCount; i++) { items[i] = (Item){.w = wgt[i], .v = val[i]}; } // 按照單位價值 item.v / item.w 從高到低進行排序 qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity); // 迴圈貪婪選擇 float res = 0.0; for (int i = 0; i < itemCount; i++) { if (items[i].w <= cap) { // 若剩餘容量充足,則將當前物品整個裝進背包 res += items[i].v; cap -= items[i].w; } else { // 若剩餘容量不足,則將當前物品的一部分裝進背包 res += (float)cap / items[i].w * items[i].v; cap = 0; break; } } free(items); return res; } /* Driver Code */ int main(void) { int wgt[] = {10, 20, 30, 40, 50}; int val[] = {50, 120, 150, 210, 240}; int capacity = 50; // 貪婪演算法 float res = fractionalKnapsack(wgt, val, sizeof(wgt) / sizeof(int), capacity); printf("不超過背包容量的最大物品價值為 %0.2f\n", res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_greedy/max_capacity.c ================================================ /** * File: max_capacity.c * Created Time: 2023-09-15 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* 求最小值 */ int myMin(int a, int b) { return a < b ? a : b; } /* 求最大值 */ int myMax(int a, int b) { return a > b ? a : b; } /* 最大容量:貪婪 */ int maxCapacity(int ht[], int htLength) { // 初始化 i, j,使其分列陣列兩端 int i = 0; int j = htLength - 1; // 初始最大容量為 0 int res = 0; // 迴圈貪婪選擇,直至兩板相遇 while (i < j) { // 更新最大容量 int capacity = myMin(ht[i], ht[j]) * (j - i); res = myMax(res, capacity); // 向內移動短板 if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ int main(void) { int ht[] = {3, 8, 5, 2, 7, 7, 3, 4}; // 貪婪演算法 int res = maxCapacity(ht, sizeof(ht) / sizeof(int)); printf("最大容量為 %d\n", res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_greedy/max_product_cutting.c ================================================ /** * File: max_product_cutting.c * Created Time: 2023-09-15 * Author: xianii (xianyi.xia@outlook.com) */ #include "../utils/common.h" /* 最大切分乘積:貪婪 */ int maxProductCutting(int n) { // 當 n <= 3 時,必須切分出一個 1 if (n <= 3) { return 1 * (n - 1); } // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 int a = n / 3; int b = n % 3; if (b == 1) { // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 return pow(3, a - 1) * 2 * 2; } if (b == 2) { // 當餘數為 2 時,不做處理 return pow(3, a) * 2; } // 當餘數為 0 時,不做處理 return pow(3, a); } /* Driver Code */ int main(void) { int n = 58; // 貪婪演算法 int res = maxProductCutting(n); printf("最大切分乘積為 %d\n", res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_hashing/CMakeLists.txt ================================================ add_executable(array_hash_map array_hash_map.c) add_executable(hash_map_chaining hash_map_chaining.c) add_executable(hash_map_open_addressing hash_map_open_addressing.c) add_executable(simple_hash simple_hash.c) ================================================ FILE: zh-hant/codes/c/chapter_hashing/array_hash_map.c ================================================ /** * File: array_hash_map.c * Created Time: 2023-03-18 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* 雜湊表預設大小 */ #define MAX_SIZE 100 /* 鍵值對 int->string */ typedef struct { int key; char *val; } Pair; /* 鍵值對的集合 */ typedef struct { void *set; int len; } MapSet; /* 基於陣列實現的雜湊表 */ typedef struct { Pair *buckets[MAX_SIZE]; } ArrayHashMap; /* 建構子 */ ArrayHashMap *newArrayHashMap() { ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap)); for (int i=0; i < MAX_SIZE; i++) { hmap->buckets[i] = NULL; } return hmap; } /* 析構函式 */ void delArrayHashMap(ArrayHashMap *hmap) { for (int i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { free(hmap->buckets[i]->val); free(hmap->buckets[i]); } } free(hmap); } /* 雜湊函式 */ int hashFunc(int key) { int index = key % MAX_SIZE; return index; } /* 查詢操作 */ const char *get(const ArrayHashMap *hmap, const int key) { int index = hashFunc(key); const Pair *Pair = hmap->buckets[index]; if (Pair == NULL) return NULL; return Pair->val; } /* 新增操作 */ void put(ArrayHashMap *hmap, const int key, const char *val) { Pair *Pair = malloc(sizeof(Pair)); Pair->key = key; Pair->val = malloc(strlen(val) + 1); strcpy(Pair->val, val); int index = hashFunc(key); hmap->buckets[index] = Pair; } /* 刪除操作 */ void removeItem(ArrayHashMap *hmap, const int key) { int index = hashFunc(key); free(hmap->buckets[index]->val); free(hmap->buckets[index]); hmap->buckets[index] = NULL; } /* 獲取所有鍵值對 */ void pairSet(ArrayHashMap *hmap, MapSet *set) { Pair *entries; int i = 0, index = 0; int total = 0; /* 統計有效鍵值對數量 */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } entries = malloc(sizeof(Pair) * total); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { entries[index].key = hmap->buckets[i]->key; entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1); strcpy(entries[index].val, hmap->buckets[i]->val); index++; } } set->set = entries; set->len = total; } /* 獲取所有鍵 */ void keySet(ArrayHashMap *hmap, MapSet *set) { int *keys; int i = 0, index = 0; int total = 0; /* 統計有效鍵值對數量 */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } keys = malloc(total * sizeof(int)); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { keys[index] = hmap->buckets[i]->key; index++; } } set->set = keys; set->len = total; } /* 獲取所有值 */ void valueSet(ArrayHashMap *hmap, MapSet *set) { char **vals; int i = 0, index = 0; int total = 0; /* 統計有效鍵值對數量 */ for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { total++; } } vals = malloc(total * sizeof(char *)); for (i = 0; i < MAX_SIZE; i++) { if (hmap->buckets[i] != NULL) { vals[index] = hmap->buckets[i]->val; index++; } } set->set = vals; set->len = total; } /* 列印雜湊表 */ void print(ArrayHashMap *hmap) { int i; MapSet set; pairSet(hmap, &set); Pair *entries = (Pair *)set.set; for (i = 0; i < set.len; i++) { printf("%d -> %s\n", entries[i].key, entries[i].val); } free(set.set); } /* Driver Code */ int main() { /* 初始化雜湊表 */ ArrayHashMap *hmap = newArrayHashMap(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) put(hmap, 12836, "小哈"); put(hmap, 15937, "小囉"); put(hmap, 16750, "小算"); put(hmap, 13276, "小法"); put(hmap, 10583, "小鴨"); printf("\n新增完成後,雜湊表為\nKey -> Value\n"); print(hmap); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value const char *name = get(hmap, 15937); printf("\n輸入學號 15937 ,查詢到姓名 %s\n", name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) removeItem(hmap, 10583); printf("\n刪除 10583 後,雜湊表為\nKey -> Value\n"); print(hmap); /* 走訪雜湊表 */ int i; printf("\n走訪鍵值對 Key->Value\n"); print(hmap); MapSet set; keySet(hmap, &set); int *keys = (int *)set.set; printf("\n單獨走訪鍵 Key\n"); for (i = 0; i < set.len; i++) { printf("%d\n", keys[i]); } free(set.set); valueSet(hmap, &set); char **vals = (char **)set.set; printf("\n單獨走訪鍵 Value\n"); for (i = 0; i < set.len; i++) { printf("%s\n", vals[i]); } free(set.set); delArrayHashMap(hmap); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_hashing/hash_map_chaining.c ================================================ /** * File: hash_map_chaining.c * Created Time: 2023-10-13 * Author: SenMing (1206575349@qq.com), krahets (krahets@163.com) */ #include #include #include // 假設 val 最大長度為 100 #define MAX_SIZE 100 /* 鍵值對 */ typedef struct { int key; char val[MAX_SIZE]; } Pair; /* 鏈結串列節點 */ typedef struct Node { Pair *pair; struct Node *next; } Node; /* 鏈式位址雜湊表 */ typedef struct { int size; // 鍵值對數量 int capacity; // 雜湊表容量 double loadThres; // 觸發擴容的負載因子閾值 int extendRatio; // 擴容倍數 Node **buckets; // 桶陣列 } HashMapChaining; /* 建構子 */ HashMapChaining *newHashMapChaining() { HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining)); hashMap->size = 0; hashMap->capacity = 4; hashMap->loadThres = 2.0 / 3.0; hashMap->extendRatio = 2; hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); for (int i = 0; i < hashMap->capacity; i++) { hashMap->buckets[i] = NULL; } return hashMap; } /* 析構函式 */ void delHashMapChaining(HashMapChaining *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Node *cur = hashMap->buckets[i]; while (cur) { Node *tmp = cur; cur = cur->next; free(tmp->pair); free(tmp); } } free(hashMap->buckets); free(hashMap); } /* 雜湊函式 */ int hashFunc(HashMapChaining *hashMap, int key) { return key % hashMap->capacity; } /* 負載因子 */ double loadFactor(HashMapChaining *hashMap) { return (double)hashMap->size / (double)hashMap->capacity; } /* 查詢操作 */ char *get(HashMapChaining *hashMap, int key) { int index = hashFunc(hashMap, key); // 走訪桶,若找到 key ,則返回對應 val Node *cur = hashMap->buckets[index]; while (cur) { if (cur->pair->key == key) { return cur->pair->val; } cur = cur->next; } return ""; // 若未找到 key ,則返回空字串 } /* 新增操作 */ void put(HashMapChaining *hashMap, int key, const char *val); /* 擴容雜湊表 */ void extend(HashMapChaining *hashMap) { // 暫存原雜湊表 int oldCapacity = hashMap->capacity; Node **oldBuckets = hashMap->buckets; // 初始化擴容後的新雜湊表 hashMap->capacity *= hashMap->extendRatio; hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); for (int i = 0; i < hashMap->capacity; i++) { hashMap->buckets[i] = NULL; } hashMap->size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (int i = 0; i < oldCapacity; i++) { Node *cur = oldBuckets[i]; while (cur) { put(hashMap, cur->pair->key, cur->pair->val); Node *temp = cur; cur = cur->next; // 釋放記憶體 free(temp->pair); free(temp); } } free(oldBuckets); } /* 新增操作 */ void put(HashMapChaining *hashMap, int key, const char *val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor(hashMap) > hashMap->loadThres) { extend(hashMap); } int index = hashFunc(hashMap, key); // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 Node *cur = hashMap->buckets[index]; while (cur) { if (cur->pair->key == key) { strcpy(cur->pair->val, val); // 若遇到指定 key ,則更新對應 val 並返回 return; } cur = cur->next; } // 若無該 key ,則將鍵值對新增至鏈結串列頭部 Pair *newPair = (Pair *)malloc(sizeof(Pair)); newPair->key = key; strcpy(newPair->val, val); Node *newNode = (Node *)malloc(sizeof(Node)); newNode->pair = newPair; newNode->next = hashMap->buckets[index]; hashMap->buckets[index] = newNode; hashMap->size++; } /* 刪除操作 */ void removeItem(HashMapChaining *hashMap, int key) { int index = hashFunc(hashMap, key); Node *cur = hashMap->buckets[index]; Node *pre = NULL; while (cur) { if (cur->pair->key == key) { // 從中刪除鍵值對 if (pre) { pre->next = cur->next; } else { hashMap->buckets[index] = cur->next; } // 釋放記憶體 free(cur->pair); free(cur); hashMap->size--; return; } pre = cur; cur = cur->next; } } /* 列印雜湊表 */ void print(HashMapChaining *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Node *cur = hashMap->buckets[i]; printf("["); while (cur) { printf("%d -> %s, ", cur->pair->key, cur->pair->val); cur = cur->next; } printf("]\n"); } } /* Driver Code */ int main() { /* 初始化雜湊表 */ HashMapChaining *hashMap = newHashMapChaining(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) put(hashMap, 12836, "小哈"); put(hashMap, 15937, "小囉"); put(hashMap, 16750, "小算"); put(hashMap, 13276, "小法"); put(hashMap, 10583, "小鴨"); printf("\n新增完成後,雜湊表為\nKey -> Value\n"); print(hashMap); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value char *name = get(hashMap, 13276); printf("\n輸入學號 13276 ,查詢到姓名 %s\n", name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) removeItem(hashMap, 12836); printf("\n刪除學號 12836 後,雜湊表為\nKey -> Value\n"); print(hashMap); /* 釋放雜湊表空間 */ delHashMapChaining(hashMap); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_hashing/hash_map_open_addressing.c ================================================ /** * File: hash_map_open_addressing.c * Created Time: 2023-10-6 * Author: lclc6 (w1929522410@163.com) */ #include "../utils/common.h" /* 開放定址雜湊表 */ typedef struct { int key; char *val; } Pair; /* 開放定址雜湊表 */ typedef struct { int size; // 鍵值對數量 int capacity; // 雜湊表容量 double loadThres; // 觸發擴容的負載因子閾值 int extendRatio; // 擴容倍數 Pair **buckets; // 桶陣列 Pair *TOMBSTONE; // 刪除標記 } HashMapOpenAddressing; // 函式宣告 void extend(HashMapOpenAddressing *hashMap); /* 建構子 */ HashMapOpenAddressing *newHashMapOpenAddressing() { HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing)); hashMap->size = 0; hashMap->capacity = 4; hashMap->loadThres = 2.0 / 3.0; hashMap->extendRatio = 2; hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair)); hashMap->TOMBSTONE->key = -1; hashMap->TOMBSTONE->val = "-1"; return hashMap; } /* 析構函式 */ void delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Pair *pair = hashMap->buckets[i]; if (pair != NULL && pair != hashMap->TOMBSTONE) { free(pair->val); free(pair); } } free(hashMap->buckets); free(hashMap->TOMBSTONE); free(hashMap); } /* 雜湊函式 */ int hashFunc(HashMapOpenAddressing *hashMap, int key) { return key % hashMap->capacity; } /* 負載因子 */ double loadFactor(HashMapOpenAddressing *hashMap) { return (double)hashMap->size / (double)hashMap->capacity; } /* 搜尋 key 對應的桶索引 */ int findBucket(HashMapOpenAddressing *hashMap, int key) { int index = hashFunc(hashMap, key); int firstTombstone = -1; // 線性探查,當遇到空桶時跳出 while (hashMap->buckets[index] != NULL) { // 若遇到 key ,返回對應的桶索引 if (hashMap->buckets[index]->key == key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone != -1) { hashMap->buckets[firstTombstone] = hashMap->buckets[index]; hashMap->buckets[index] = hashMap->TOMBSTONE; return firstTombstone; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) { firstTombstone = index; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % hashMap->capacity; } // 若 key 不存在,則返回新增點的索引 return firstTombstone == -1 ? index : firstTombstone; } /* 查詢操作 */ char *get(HashMapOpenAddressing *hashMap, int key) { // 搜尋 key 對應的桶索引 int index = findBucket(hashMap, key); // 若找到鍵值對,則返回對應 val if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { return hashMap->buckets[index]->val; } // 若鍵值對不存在,則返回空字串 return ""; } /* 新增操作 */ void put(HashMapOpenAddressing *hashMap, int key, char *val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor(hashMap) > hashMap->loadThres) { extend(hashMap); } // 搜尋 key 對應的桶索引 int index = findBucket(hashMap, key); // 若找到鍵值對,則覆蓋 val 並返回 if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { free(hashMap->buckets[index]->val); hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1)); strcpy(hashMap->buckets[index]->val, val); hashMap->buckets[index]->val[strlen(val)] = '\0'; return; } // 若鍵值對不存在,則新增該鍵值對 Pair *pair = (Pair *)malloc(sizeof(Pair)); pair->key = key; pair->val = (char *)malloc(sizeof(strlen(val) + 1)); strcpy(pair->val, val); pair->val[strlen(val)] = '\0'; hashMap->buckets[index] = pair; hashMap->size++; } /* 刪除操作 */ void removeItem(HashMapOpenAddressing *hashMap, int key) { // 搜尋 key 對應的桶索引 int index = findBucket(hashMap, key); // 若找到鍵值對,則用刪除標記覆蓋它 if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { Pair *pair = hashMap->buckets[index]; free(pair->val); free(pair); hashMap->buckets[index] = hashMap->TOMBSTONE; hashMap->size--; } } /* 擴容雜湊表 */ void extend(HashMapOpenAddressing *hashMap) { // 暫存原雜湊表 Pair **bucketsTmp = hashMap->buckets; int oldCapacity = hashMap->capacity; // 初始化擴容後的新雜湊表 hashMap->capacity *= hashMap->extendRatio; hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); hashMap->size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (int i = 0; i < oldCapacity; i++) { Pair *pair = bucketsTmp[i]; if (pair != NULL && pair != hashMap->TOMBSTONE) { put(hashMap, pair->key, pair->val); free(pair->val); free(pair); } } free(bucketsTmp); } /* 列印雜湊表 */ void print(HashMapOpenAddressing *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Pair *pair = hashMap->buckets[i]; if (pair == NULL) { printf("NULL\n"); } else if (pair == hashMap->TOMBSTONE) { printf("TOMBSTONE\n"); } else { printf("%d -> %s\n", pair->key, pair->val); } } } /* Driver Code */ int main() { // 初始化雜湊表 HashMapOpenAddressing *hashmap = newHashMapOpenAddressing(); // 新增操作 // 在雜湊表中新增鍵值對 (key, val) put(hashmap, 12836, "小哈"); put(hashmap, 15937, "小囉"); put(hashmap, 16750, "小算"); put(hashmap, 13276, "小法"); put(hashmap, 10583, "小鴨"); printf("\n新增完成後,雜湊表為\nKey -> Value\n"); print(hashmap); // 查詢操作 // 向雜湊表中輸入鍵 key ,得到值 val char *name = get(hashmap, 13276); printf("\n輸入學號 13276 ,查詢到姓名 %s\n", name); // 刪除操作 // 在雜湊表中刪除鍵值對 (key, val) removeItem(hashmap, 16750); printf("\n刪除 16750 後,雜湊表為\nKey -> Value\n"); print(hashmap); // 銷燬雜湊表 delHashMapOpenAddressing(hashmap); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_hashing/simple_hash.c ================================================ /** * File: simple_hash.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 加法雜湊 */ int addHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = (hash + (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* 乘法雜湊 */ int mulHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = (31 * hash + (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* 互斥或雜湊 */ int xorHash(char *key) { int hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash ^= (unsigned char)key[i]; } return hash & MODULUS; } /* 旋轉雜湊 */ int rotHash(char *key) { long long hash = 0; const int MODULUS = 1000000007; for (int i = 0; i < strlen(key); i++) { hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS; } return (int)hash; } /* Driver Code */ int main() { char *key = "Hello 演算法"; int hash = addHash(key); printf("加法雜湊值為 %d\n", hash); hash = mulHash(key); printf("乘法雜湊值為 %d\n", hash); hash = xorHash(key); printf("互斥或雜湊值為 %d\n", hash); hash = rotHash(key); printf("旋轉雜湊值為 %d\n", hash); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_heap/CMakeLists.txt ================================================ add_executable(my_heap_test my_heap_test.c) add_executable(top_k top_k.c) ================================================ FILE: zh-hant/codes/c/chapter_heap/my_heap.c ================================================ /** * File: my_heap.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 5000 /* 大頂堆積 */ typedef struct { // size 代表的是實際元素的個數 int size; // 使用預先分配記憶體的陣列,避免擴容 int data[MAX_SIZE]; } MaxHeap; // 函式宣告 void siftDown(MaxHeap *maxHeap, int i); void siftUp(MaxHeap *maxHeap, int i); int parent(MaxHeap *maxHeap, int i); /* 建構子,根據切片建堆積 */ MaxHeap *newMaxHeap(int nums[], int size) { // 所有元素入堆積 MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap)); maxHeap->size = size; memcpy(maxHeap->data, nums, size * sizeof(int)); for (int i = parent(maxHeap, size - 1); i >= 0; i--) { // 堆積化除葉節點以外的其他所有節點 siftDown(maxHeap, i); } return maxHeap; } /* 析構函式 */ void delMaxHeap(MaxHeap *maxHeap) { // 釋放記憶體 free(maxHeap); } /* 獲取左子節點的索引 */ int left(MaxHeap *maxHeap, int i) { return 2 * i + 1; } /* 獲取右子節點的索引 */ int right(MaxHeap *maxHeap, int i) { return 2 * i + 2; } /* 獲取父節點的索引 */ int parent(MaxHeap *maxHeap, int i) { return (i - 1) / 2; // 向下取整 } /* 交換元素 */ void swap(MaxHeap *maxHeap, int i, int j) { int temp = maxHeap->data[i]; maxHeap->data[i] = maxHeap->data[j]; maxHeap->data[j] = temp; } /* 獲取堆積大小 */ int size(MaxHeap *maxHeap) { return maxHeap->size; } /* 判斷堆積是否為空 */ int isEmpty(MaxHeap *maxHeap) { return maxHeap->size == 0; } /* 訪問堆積頂元素 */ int peek(MaxHeap *maxHeap) { return maxHeap->data[0]; } /* 元素入堆積 */ void push(MaxHeap *maxHeap, int val) { // 預設情況下,不應該新增這麼多節點 if (maxHeap->size == MAX_SIZE) { printf("heap is full!"); return; } // 新增節點 maxHeap->data[maxHeap->size] = val; maxHeap->size++; // 從底至頂堆積化 siftUp(maxHeap, maxHeap->size - 1); } /* 元素出堆積 */ int pop(MaxHeap *maxHeap) { // 判空處理 if (isEmpty(maxHeap)) { printf("heap is empty!"); return INT_MAX; } // 交換根節點與最右葉節點(交換首元素與尾元素) swap(maxHeap, 0, size(maxHeap) - 1); // 刪除節點 int val = maxHeap->data[maxHeap->size - 1]; maxHeap->size--; // 從頂至底堆積化 siftDown(maxHeap, 0); // 返回堆積頂元素 return val; } /* 從節點 i 開始,從頂至底堆積化 */ void siftDown(MaxHeap *maxHeap, int i) { while (true) { // 判斷節點 i, l, r 中值最大的節點,記為 max int l = left(maxHeap, i); int r = right(maxHeap, i); int max = i; if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) { max = l; } if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) { max = r; } // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if (max == i) { break; } // 交換兩節點 swap(maxHeap, i, max); // 迴圈向下堆積化 i = max; } } /* 從節點 i 開始,從底至頂堆積化 */ void siftUp(MaxHeap *maxHeap, int i) { while (true) { // 獲取節點 i 的父節點 int p = parent(maxHeap, i); // 當“越過根節點”或“節點無須修復”時,結束堆積化 if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) { break; } // 交換兩節點 swap(maxHeap, i, p); // 迴圈向上堆積化 i = p; } } ================================================ FILE: zh-hant/codes/c/chapter_heap/my_heap_test.c ================================================ /** * File: my_heap_test.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "my_heap.c" /* Driver Code */ int main() { /* 初始化堆積 */ // 初始化大頂堆積 int nums[] = {9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; MaxHeap *maxHeap = newMaxHeap(nums, sizeof(nums) / sizeof(int)); printf("輸入陣列並建堆積後\n"); printHeap(maxHeap->data, maxHeap->size); /* 獲取堆積頂元素 */ printf("\n堆積頂元素為 %d\n", peek(maxHeap)); /* 元素入堆積 */ push(maxHeap, 7); printf("\n元素 7 入堆積後\n"); printHeap(maxHeap->data, maxHeap->size); /* 堆積頂元素出堆積 */ int top = pop(maxHeap); printf("\n堆積頂元素 %d 出堆積後\n", top); printHeap(maxHeap->data, maxHeap->size); /* 獲取堆積大小 */ printf("\n堆積元素數量為 %d\n", size(maxHeap)); /* 判斷堆積是否為空 */ printf("\n堆積是否為空 %d\n", isEmpty(maxHeap)); // 釋放記憶體 delMaxHeap(maxHeap); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_heap/top_k.c ================================================ /** * File: top_k.c * Created Time: 2023-10-26 * Author: krahets (krahets163.com) */ #include "my_heap.c" /* 元素入堆積 */ void pushMinHeap(MaxHeap *maxHeap, int val) { // 元素取反 push(maxHeap, -val); } /* 元素出堆積 */ int popMinHeap(MaxHeap *maxHeap) { // 元素取反 return -pop(maxHeap); } /* 訪問堆積頂元素 */ int peekMinHeap(MaxHeap *maxHeap) { // 元素取反 return -peek(maxHeap); } /* 取出堆積中元素 */ int *getMinHeap(MaxHeap *maxHeap) { // 將堆積中所有元素取反並存入 res 陣列 int *res = (int *)malloc(maxHeap->size * sizeof(int)); for (int i = 0; i < maxHeap->size; i++) { res[i] = -maxHeap->data[i]; } return res; } // 基於堆積查詢陣列中最大的 k 個元素的函式 int *topKHeap(int *nums, int sizeNums, int k) { // 初始化小頂堆積 // 請注意:我們將堆積中所有元素取反,從而用大頂堆積來模擬小頂堆積 int *empty = (int *)malloc(0); MaxHeap *maxHeap = newMaxHeap(empty, 0); // 將陣列的前 k 個元素入堆積 for (int i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // 從第 k+1 個元素開始,保持堆積的長度為 k for (int i = k; i < sizeNums; i++) { // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } int *res = getMinHeap(maxHeap); // 釋放記憶體 delMaxHeap(maxHeap); return res; } /* Driver Code */ int main() { int nums[] = {1, 7, 6, 3, 2}; int k = 3; int sizeNums = sizeof(nums) / sizeof(nums[0]); int *res = topKHeap(nums, sizeNums, k); printf("最大的 %d 個元素為: ", k); printArray(res, k); free(res); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_searching/CMakeLists.txt ================================================ add_executable(binary_search binary_search.c) add_executable(two_sum two_sum.c) add_executable(binary_search_edge binary_search_edge.c) add_executable(binary_search_insertion binary_search_insertion.c) ================================================ FILE: zh-hant/codes/c/chapter_searching/binary_search.c ================================================ /** * File: binary_search.c * Created Time: 2023-03-18 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* 二分搜尋(雙閉區間) */ int binarySearch(int *nums, int len, int target) { // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 int i = 0, j = len - 1; // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) while (i <= j) { int m = i + (j - i) / 2; // 計算中點索引 m if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j] 中 i = m + 1; else if (nums[m] > target) // 此情況說明 target 在區間 [i, m-1] 中 j = m - 1; else // 找到目標元素,返回其索引 return m; } // 未找到目標元素,返回 -1 return -1; } /* 二分搜尋(左閉右開區間) */ int binarySearchLCRO(int *nums, int len, int target) { // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 int i = 0, j = len; // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) while (i < j) { int m = i + (j - i) / 2; // 計算中點索引 m if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j) 中 i = m + 1; else if (nums[m] > target) // 此情況說明 target 在區間 [i, m) 中 j = m; else // 找到目標元素,返回其索引 return m; } // 未找到目標元素,返回 -1 return -1; } /* Driver Code */ int main() { int target = 6; int nums[10] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; /* 二分搜尋(雙閉區間) */ int index = binarySearch(nums, 10, target); printf("目標元素 6 的索引 = %d\n", index); /* 二分搜尋(左閉右開區間) */ index = binarySearchLCRO(nums, 10, target); printf("目標元素 6 的索引 = %d\n", index); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_searching/binary_search_edge.c ================================================ /** * File: binary_search_edge.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 二分搜尋插入點(存在重複元素) */ int binarySearchInsertion(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // 初始化雙閉區間 [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // 計算中點索引 m if (nums[m] < target) { i = m + 1; // target 在區間 [m+1, j] 中 } else { j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 } } // 返回插入點 i return i; } /* 二分搜尋最左一個 target */ int binarySearchLeftEdge(int *nums, int numSize, int target) { // 等價於查詢 target 的插入點 int i = binarySearchInsertion(nums, numSize, target); // 未找到 target ,返回 -1 if (i == numSize || nums[i] != target) { return -1; } // 找到 target ,返回索引 i return i; } /* 二分搜尋最右一個 target */ int binarySearchRightEdge(int *nums, int numSize, int target) { // 轉化為查詢最左一個 target + 1 int i = binarySearchInsertion(nums, numSize, target + 1); // j 指向最右一個 target ,i 指向首個大於 target 的元素 int j = i - 1; // 未找到 target ,返回 -1 if (j == -1 || nums[j] != target) { return -1; } // 找到 target ,返回索引 j return j; } /* Driver Code */ int main() { // 包含重複元素的陣列 int nums[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; printf("\n陣列 nums = "); printArray(nums, sizeof(nums) / sizeof(nums[0])); // 二分搜尋左邊界和右邊界 int targets[] = {6, 7}; for (int i = 0; i < sizeof(targets) / sizeof(targets[0]); i++) { int index = binarySearchLeftEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); printf("最左一個元素 %d 的索引為 %d\n", targets[i], index); index = binarySearchRightEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); printf("最右一個元素 %d 的索引為 %d\n", targets[i], index); } return 0; } ================================================ FILE: zh-hant/codes/c/chapter_searching/binary_search_insertion.c ================================================ /** * File: binary_search_insertion.c * Created Time: 2023-09-09 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 二分搜尋插入點(無重複元素) */ int binarySearchInsertionSimple(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // 初始化雙閉區間 [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // 計算中點索引 m if (nums[m] < target) { i = m + 1; // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在區間 [i, m-1] 中 } else { return m; // 找到 target ,返回插入點 m } } // 未找到 target ,返回插入點 i return i; } /* 二分搜尋插入點(存在重複元素) */ int binarySearchInsertion(int *nums, int numSize, int target) { int i = 0, j = numSize - 1; // 初始化雙閉區間 [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // 計算中點索引 m if (nums[m] < target) { i = m + 1; // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在區間 [i, m-1] 中 } else { j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 } } // 返回插入點 i return i; } /* Driver Code */ int main() { // 無重複元素的陣列 int nums1[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; printf("\n陣列 nums = "); printArray(nums1, sizeof(nums1) / sizeof(nums1[0])); // 二分搜尋插入點 int targets1[] = {6, 9}; for (int i = 0; i < sizeof(targets1) / sizeof(targets1[0]); i++) { int index = binarySearchInsertionSimple(nums1, sizeof(nums1) / sizeof(nums1[0]), targets1[i]); printf("元素 %d 的插入點的索引為 %d\n", targets1[i], index); } // 包含重複元素的陣列 int nums2[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; printf("\n陣列 nums = "); printArray(nums2, sizeof(nums2) / sizeof(nums2[0])); // 二分搜尋插入點 int targets2[] = {2, 6, 20}; for (int i = 0; i < sizeof(targets2) / sizeof(int); i++) { int index = binarySearchInsertion(nums2, sizeof(nums2) / sizeof(nums2[0]), targets2[i]); printf("元素 %d 的插入點的索引為 %d\n", targets2[i], index); } return 0; } ================================================ FILE: zh-hant/codes/c/chapter_searching/two_sum.c ================================================ /** * File: two_sum.c * Created Time: 2023-01-19 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* 方法一:暴力列舉 */ int *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) { for (int i = 0; i < numsSize; ++i) { for (int j = i + 1; j < numsSize; ++j) { if (nums[i] + nums[j] == target) { int *res = malloc(sizeof(int) * 2); res[0] = i, res[1] = j; *returnSize = 2; return res; } } } *returnSize = 0; return NULL; } /* 雜湊表 */ typedef struct { int key; int val; UT_hash_handle hh; // 基於 uthash.h 實現 } HashTable; /* 雜湊表查詢 */ HashTable *find(HashTable *h, int key) { HashTable *tmp; HASH_FIND_INT(h, &key, tmp); return tmp; } /* 雜湊表元素插入 */ void insert(HashTable **h, int key, int val) { HashTable *t = find(*h, key); if (t == NULL) { HashTable *tmp = malloc(sizeof(HashTable)); tmp->key = key, tmp->val = val; HASH_ADD_INT(*h, key, tmp); } else { t->val = val; } } /* 方法二:輔助雜湊表 */ int *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) { HashTable *hashtable = NULL; for (int i = 0; i < numsSize; i++) { HashTable *t = find(hashtable, target - nums[i]); if (t != NULL) { int *res = malloc(sizeof(int) * 2); res[0] = t->val, res[1] = i; *returnSize = 2; return res; } insert(&hashtable, nums[i], i); } *returnSize = 0; return NULL; } /* Driver Code */ int main() { // ======= Test Case ======= int nums[] = {2, 7, 11, 15}; int target = 13; // ====== Driver Code ====== int returnSize; int *res = twoSumBruteForce(nums, sizeof(nums) / sizeof(int), target, &returnSize); // 方法一 printf("方法一 res = "); printArray(res, returnSize); // 方法二 res = twoSumHashTable(nums, sizeof(nums) / sizeof(int), target, &returnSize); printf("方法二 res = "); printArray(res, returnSize); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_sorting/CMakeLists.txt ================================================ add_executable(bubble_sort bubble_sort.c) add_executable(insertion_sort insertion_sort.c) add_executable(quick_sort quick_sort.c) add_executable(counting_sort counting_sort.c) add_executable(radix_sort radix_sort.c) add_executable(merge_sort merge_sort.c) add_executable(heap_sort heap_sort.c) add_executable(bucket_sort bucket_sort.c) add_executable(selection_sort selection_sort.c) ================================================ FILE: zh-hant/codes/c/chapter_sorting/bubble_sort.c ================================================ /** * File: bubble_sort.c * Created Time: 2022-12-26 * Author: Listening (https://github.com/L-Super) */ #include "../utils/common.h" /* 泡沫排序 */ void bubbleSort(int nums[], int size) { // 外迴圈:未排序區間為 [0, i] for (int i = size - 1; i > 0; i--) { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; } } } } /* 泡沫排序(標誌最佳化)*/ void bubbleSortWithFlag(int nums[], int size) { // 外迴圈:未排序區間為 [0, i] for (int i = size - 1; i > 0; i--) { bool flag = false; // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; flag = true; } } if (!flag) break; } } /* Driver Code */ int main() { int nums[6] = {4, 1, 3, 1, 5, 2}; printf("泡沫排序後: "); bubbleSort(nums, 6); for (int i = 0; i < 6; i++) { printf("%d ", nums[i]); } int nums1[6] = {4, 1, 3, 1, 5, 2}; printf("\n最佳化版泡沫排序後: "); bubbleSortWithFlag(nums1, 6); for (int i = 0; i < 6; i++) { printf("%d ", nums1[i]); } printf("\n"); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_sorting/bucket_sort.c ================================================ /** * File: bucket_sort.c * Created Time: 2023-05-30 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" #define SIZE 10 /* 用於 qsort 的比較函式 */ int compare(const void *a, const void *b) { float fa = *(const float *)a; float fb = *(const float *)b; return (fa > fb) - (fa < fb); } /* 桶排序 */ void bucketSort(float nums[], int n) { int k = n / 2; // 初始化 k = n/2 個桶 int *sizes = malloc(k * sizeof(int)); // 記錄每個桶的大小 float **buckets = malloc(k * sizeof(float *)); // 動態陣列的陣列(桶) // 為每個桶預分配足夠的空間 for (int i = 0; i < k; ++i) { buckets[i] = (float *)malloc(n * sizeof(float)); sizes[i] = 0; } // 1. 將陣列元素分配到各個桶中 for (int i = 0; i < n; ++i) { int idx = (int)(nums[i] * k); buckets[idx][sizes[idx]++] = nums[i]; } // 2. 對各個桶執行排序 for (int i = 0; i < k; ++i) { qsort(buckets[i], sizes[i], sizeof(float), compare); } // 3. 合併排序後的桶 int idx = 0; for (int i = 0; i < k; ++i) { for (int j = 0; j < sizes[i]; ++j) { nums[idx++] = buckets[i][j]; } // 釋放記憶體 free(buckets[i]); } } /* Driver Code */ int main() { // 設輸入資料為浮點數,範圍為 [0, 1) float nums[SIZE] = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; bucketSort(nums, SIZE); printf("桶排序完成後 nums = "); printArrayFloat(nums, SIZE); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_sorting/counting_sort.c ================================================ /** * File: counting_sort.c * Created Time: 2023-03-20 * Author: Reanon (793584285@qq.com), Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* 計數排序 */ // 簡單實現,無法用於排序物件 void countingSortNaive(int nums[], int size) { // 1. 統計陣列最大元素 m int m = 0; for (int i = 0; i < size; i++) { if (nums[i] > m) { m = nums[i]; } } // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 int *counter = calloc(m + 1, sizeof(int)); for (int i = 0; i < size; i++) { counter[nums[i]]++; } // 3. 走訪 counter ,將各元素填入原陣列 nums int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } // 4. 釋放記憶體 free(counter); } /* 計數排序 */ // 完整實現,可排序物件,並且是穩定排序 void countingSort(int nums[], int size) { // 1. 統計陣列最大元素 m int m = 0; for (int i = 0; i < size; i++) { if (nums[i] > m) { m = nums[i]; } } // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 int *counter = calloc(m, sizeof(int)); for (int i = 0; i < size; i++) { counter[nums[i]]++; } // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. 倒序走訪 nums ,將各元素填入結果陣列 res // 初始化陣列 res 用於記錄結果 int *res = malloc(sizeof(int) * size); for (int i = size - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // 將 num 放置到對應索引處 counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 } // 使用結果陣列 res 覆蓋原陣列 nums memcpy(nums, res, size * sizeof(int)); // 5. 釋放記憶體 free(res); free(counter); } /* Driver Code */ int main() { int nums[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; int size = sizeof(nums) / sizeof(int); countingSortNaive(nums, size); printf("計數排序(無法排序物件)完成後 nums = "); printArray(nums, size); int nums1[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; int size1 = sizeof(nums1) / sizeof(int); countingSort(nums1, size1); printf("計數排序完成後 nums1 = "); printArray(nums1, size1); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_sorting/heap_sort.c ================================================ /** * File: heap_sort.c * Created Time: 2023-05-30 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ void siftDown(int nums[], int n, int i) { while (1) { // 判斷節點 i, l, r 中值最大的節點,記為 ma int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; if (l < n && nums[l] > nums[ma]) ma = l; if (r < n && nums[r] > nums[ma]) ma = r; // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if (ma == i) { break; } // 交換兩節點 int temp = nums[i]; nums[i] = nums[ma]; nums[ma] = temp; // 迴圈向下堆積化 i = ma; } } /* 堆積排序 */ void heapSort(int nums[], int n) { // 建堆積操作:堆積化除葉節點以外的其他所有節點 for (int i = n / 2 - 1; i >= 0; --i) { siftDown(nums, n, i); } // 從堆積中提取最大元素,迴圈 n-1 輪 for (int i = n - 1; i > 0; --i) { // 交換根節點與最右葉節點(交換首元素與尾元素) int tmp = nums[0]; nums[0] = nums[i]; nums[i] = tmp; // 以根節點為起點,從頂至底進行堆積化 siftDown(nums, i, 0); } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; int n = sizeof(nums) / sizeof(nums[0]); heapSort(nums, n); printf("堆積排序完成後 nums = "); printArray(nums, n); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_sorting/insertion_sort.c ================================================ /** * File: insertion_sort.c * Created Time: 2022-12-29 * Author: Listening (https://github.com/L-Super) */ #include "../utils/common.h" /* 插入排序 */ void insertionSort(int nums[], int size) { // 外迴圈:已排序區間為 [0, i-1] for (int i = 1; i < size; i++) { int base = nums[i], j = i - 1; // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 while (j >= 0 && nums[j] > base) { // 將 nums[j] 向右移動一位 nums[j + 1] = nums[j]; j--; } // 將 base 賦值到正確位置 nums[j + 1] = base; } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; insertionSort(nums, 6); printf("插入排序完成後 nums = "); for (int i = 0; i < 6; i++) { printf("%d ", nums[i]); } printf("\n"); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_sorting/merge_sort.c ================================================ /** * File: merge_sort.c * Created Time: 2022-03-21 * Author: Guanngxu (446678850@qq.com) */ #include "../utils/common.h" /* 合併左子陣列和右子陣列 */ void merge(int *nums, int left, int mid, int right) { // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] // 建立一個臨時陣列 tmp ,用於存放合併後的結果 int tmpSize = right - left + 1; int *tmp = (int *)malloc(tmpSize * sizeof(int)); // 初始化左子陣列和右子陣列的起始索引 int i = left, j = mid + 1, k = 0; // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 for (k = 0; k < tmpSize; ++k) { nums[left + k] = tmp[k]; } // 釋放記憶體 free(tmp); } /* 合併排序 */ void mergeSort(int *nums, int left, int right) { // 終止條件 if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 // 劃分階段 int mid = left + (right - left) / 2; // 計算中點 mergeSort(nums, left, mid); // 遞迴左子陣列 mergeSort(nums, mid + 1, right); // 遞迴右子陣列 // 合併階段 merge(nums, left, mid, right); } /* Driver Code */ int main() { /* 合併排序 */ int nums[] = {7, 3, 2, 6, 0, 1, 5, 4}; int size = sizeof(nums) / sizeof(int); mergeSort(nums, 0, size - 1); printf("合併排序完成後 nums = "); printArray(nums, size); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_sorting/quick_sort.c ================================================ /** * File: quick_sort.c * Created Time: 2023-01-18 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* 元素交換 */ void swap(int nums[], int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵劃分 */ int partition(int nums[], int left, int right) { // 以 nums[left] 為基準數 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // 從右向左找首個小於基準數的元素 } while (i < j && nums[i] <= nums[left]) { i++; // 從左向右找首個大於基準數的元素 } // 交換這兩個元素 swap(nums, i, j); } // 將基準數交換至兩子陣列的分界線 swap(nums, i, left); // 返回基準數的索引 return i; } /* 快速排序 */ void quickSort(int nums[], int left, int right) { // 子陣列長度為 1 時終止遞迴 if (left >= right) { return; } // 哨兵劃分 int pivot = partition(nums, left, right); // 遞迴左子陣列、右子陣列 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } // 以下為中位數最佳化的快速排序 /* 選取三個候選元素的中位數 */ 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; } /* 哨兵劃分(三數取中值) */ int partitionMedian(int 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; // 返回基準數的索引 } /* 快速排序(三數取中值) */ void quickSortMedian(int nums[], int left, int right) { // 子陣列長度為 1 時終止遞迴 if (left >= right) return; // 哨兵劃分 int pivot = partitionMedian(nums, left, right); // 遞迴左子陣列、右子陣列 quickSortMedian(nums, left, pivot - 1); quickSortMedian(nums, pivot + 1, right); } // 以下為遞迴深度最佳化的快速排序 /* 快速排序(遞迴深度最佳化) */ void quickSortTailCall(int nums[], int left, int right) { // 子陣列長度為 1 時終止 while (left < right) { // 哨兵劃分操作 int pivot = partition(nums, left, right); // 對兩個子陣列中較短的那個執行快速排序 if (pivot - left < right - pivot) { // 遞迴排序左子陣列 quickSortTailCall(nums, left, pivot - 1); // 剩餘未排序區間為 [pivot + 1, right] left = pivot + 1; } else { // 遞迴排序右子陣列 quickSortTailCall(nums, pivot + 1, right); // 剩餘未排序區間為 [left, pivot - 1] right = pivot - 1; } } } /* Driver Code */ int main() { /* 快速排序 */ int nums[] = {2, 4, 1, 0, 3, 5}; int size = sizeof(nums) / sizeof(int); quickSort(nums, 0, size - 1); printf("快速排序完成後 nums = "); printArray(nums, size); /* 快速排序(中位基準數最佳化) */ int nums1[] = {2, 4, 1, 0, 3, 5}; quickSortMedian(nums1, 0, size - 1); printf("快速排序(中位基準數最佳化)完成後 nums = "); printArray(nums1, size); /* 快速排序(遞迴深度最佳化) */ int nums2[] = {2, 4, 1, 0, 3, 5}; quickSortTailCall(nums2, 0, size - 1); printf("快速排序(遞迴深度最佳化)完成後 nums = "); printArray(nums1, size); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_sorting/radix_sort.c ================================================ /** * File: radix_sort.c * Created Time: 2023-01-18 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ int digit(int num, int exp) { // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 return (num / exp) % 10; } /* 計數排序(根據 nums 第 k 位排序) */ void countingSortDigit(int nums[], int size, int exp) { // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 int *counter = (int *)malloc((sizeof(int) * 10)); memset(counter, 0, sizeof(int) * 10); // 初始化為 0 以支持後續記憶體釋放 // 統計 0~9 各數字的出現次數 for (int i = 0; i < size; i++) { // 獲取 nums[i] 第 k 位,記為 d int d = digit(nums[i], exp); // 統計數字 d 的出現次數 counter[d]++; } // 求前綴和,將“出現個數”轉換為“陣列索引” for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 倒序走訪,根據桶內統計結果,將各元素填入 res int *res = (int *)malloc(sizeof(int) * size); for (int i = size - 1; i >= 0; i--) { int d = digit(nums[i], exp); int j = counter[d] - 1; // 獲取 d 在陣列中的索引 j res[j] = nums[i]; // 將當前元素填入索引 j counter[d]--; // 將 d 的數量減 1 } // 使用結果覆蓋原陣列 nums for (int i = 0; i < size; i++) { nums[i] = res[i]; } // 釋放記憶體 free(res); free(counter); } /* 基數排序 */ void radixSort(int nums[], int size) { // 獲取陣列的最大元素,用於判斷最大位數 int max = INT32_MIN; for (int i = 0; i < size; i++) { if (nums[i] > max) { max = nums[i]; } } // 按照從低位到高位的順序走訪 for (int exp = 1; max >= exp; exp *= 10) // 對陣列元素的第 k 位執行計數排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums, size, exp); } /* Driver Code */ int main() { // 基數排序 int nums[] = {10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996}; int size = sizeof(nums) / sizeof(int); radixSort(nums, size); printf("基數排序完成後 nums = "); printArray(nums, size); } ================================================ FILE: zh-hant/codes/c/chapter_sorting/selection_sort.c ================================================ /** * File: selection_sort.c * Created Time: 2023-05-31 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 選擇排序 */ void selectionSort(int nums[], int n) { // 外迴圈:未排序區間為 [i, n-1] for (int i = 0; i < n - 1; i++) { // 內迴圈:找到未排序區間內的最小元素 int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // 記錄最小元素的索引 } // 將該最小元素與未排序區間的首個元素交換 int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; } } /* Driver Code */ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; int n = sizeof(nums) / sizeof(nums[0]); selectionSort(nums, n); printf("選擇排序完成後 nums = "); printArray(nums, n); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_stack_and_queue/CMakeLists.txt ================================================ add_executable(array_stack array_stack.c) add_executable(linkedlist_stack linkedlist_stack.c) add_executable(array_queue array_queue.c) add_executable(linkedlist_queue linkedlist_queue.c) add_executable(array_deque array_deque.c) add_executable(linkedlist_deque linkedlist_deque.c) ================================================ FILE: zh-hant/codes/c/chapter_stack_and_queue/array_deque.c ================================================ /** * File: array_deque.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 基於環形陣列實現的雙向佇列 */ typedef struct { int *nums; // 用於儲存佇列元素的陣列 int front; // 佇列首指標,指向佇列首元素 int queSize; // 尾指標,指向佇列尾 + 1 int queCapacity; // 佇列容量 } ArrayDeque; /* 建構子 */ ArrayDeque *newArrayDeque(int capacity) { ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque)); // 初始化陣列 deque->queCapacity = capacity; deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity); deque->front = deque->queSize = 0; return deque; } /* 析構函式 */ void delArrayDeque(ArrayDeque *deque) { free(deque->nums); free(deque); } /* 獲取雙向佇列的容量 */ int capacity(ArrayDeque *deque) { return deque->queCapacity; } /* 獲取雙向佇列的長度 */ int size(ArrayDeque *deque) { return deque->queSize; } /* 判斷雙向佇列是否為空 */ bool empty(ArrayDeque *deque) { return deque->queSize == 0; } /* 計算環形陣列索引 */ int dequeIndex(ArrayDeque *deque, int i) { // 透過取餘操作實現陣列首尾相連 // 當 i 越過陣列尾部時,回到頭部 // 當 i 越過陣列頭部後,回到尾部 return ((i + capacity(deque)) % capacity(deque)); } /* 佇列首入列 */ void pushFirst(ArrayDeque *deque, int num) { if (deque->queSize == capacity(deque)) { printf("雙向佇列已滿\r\n"); return; } // 佇列首指標向左移動一位 // 透過取餘操作實現 front 越過陣列頭部回到尾部 deque->front = dequeIndex(deque, deque->front - 1); // 將 num 新增到佇列首 deque->nums[deque->front] = num; deque->queSize++; } /* 佇列尾入列 */ void pushLast(ArrayDeque *deque, int num) { if (deque->queSize == capacity(deque)) { printf("雙向佇列已滿\r\n"); return; } // 計算佇列尾指標,指向佇列尾索引 + 1 int rear = dequeIndex(deque, deque->front + deque->queSize); // 將 num 新增至佇列尾 deque->nums[rear] = num; deque->queSize++; } /* 訪問佇列首元素 */ int peekFirst(ArrayDeque *deque) { // 訪問異常:雙向佇列為空 assert(empty(deque) == 0); return deque->nums[deque->front]; } /* 訪問佇列尾元素 */ int peekLast(ArrayDeque *deque) { // 訪問異常:雙向佇列為空 assert(empty(deque) == 0); int last = dequeIndex(deque, deque->front + deque->queSize - 1); return deque->nums[last]; } /* 佇列首出列 */ int popFirst(ArrayDeque *deque) { int num = peekFirst(deque); // 佇列首指標向後移動一位 deque->front = dequeIndex(deque, deque->front + 1); deque->queSize--; return num; } /* 佇列尾出列 */ int popLast(ArrayDeque *deque) { int num = peekLast(deque); deque->queSize--; return num; } /* 返回陣列用於列印 */ int *toArray(ArrayDeque *deque, int *queSize) { *queSize = deque->queSize; int *res = (int *)calloc(deque->queSize, sizeof(int)); int j = deque->front; for (int i = 0; i < deque->queSize; i++) { res[i] = deque->nums[j % deque->queCapacity]; j++; } return res; } /* Driver Code */ int main() { /* 初始化佇列 */ int capacity = 10; int queSize; ArrayDeque *deque = newArrayDeque(capacity); pushLast(deque, 3); pushLast(deque, 2); pushLast(deque, 5); printf("雙向佇列 deque = "); printArray(toArray(deque, &queSize), queSize); /* 訪問元素 */ int peekFirstNum = peekFirst(deque); printf("佇列首元素 peekFirst = %d\r\n", peekFirstNum); int peekLastNum = peekLast(deque); printf("佇列尾元素 peekLast = %d\r\n", peekLastNum); /* 元素入列 */ pushLast(deque, 4); printf("元素 4 佇列尾入列後 deque = "); printArray(toArray(deque, &queSize), queSize); pushFirst(deque, 1); printf("元素 1 佇列首入列後 deque = "); printArray(toArray(deque, &queSize), queSize); /* 元素出列 */ int popLastNum = popLast(deque); printf("佇列尾出列元素 = %d ,佇列尾出列後 deque= ", popLastNum); printArray(toArray(deque, &queSize), queSize); int popFirstNum = popFirst(deque); printf("佇列首出列元素 = %d ,佇列首出列後 deque= ", popFirstNum); printArray(toArray(deque, &queSize), queSize); /* 獲取佇列的長度 */ int dequeSize = size(deque); printf("雙向佇列長度 size = %d\r\n", dequeSize); /* 判斷佇列是否為空 */ bool isEmpty = empty(deque); printf("佇列是否為空 = %s\r\n", isEmpty ? "true" : "false"); // 釋放記憶體 delArrayDeque(deque); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_stack_and_queue/array_queue.c ================================================ /** * File: array_queue.c * Created Time: 2023-01-28 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* 基於環形陣列實現的佇列 */ typedef struct { int *nums; // 用於儲存佇列元素的陣列 int front; // 佇列首指標,指向佇列首元素 int queSize; // 當前佇列的元素數量 int queCapacity; // 佇列容量 } ArrayQueue; /* 建構子 */ ArrayQueue *newArrayQueue(int capacity) { ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue)); // 初始化陣列 queue->queCapacity = capacity; queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity); queue->front = queue->queSize = 0; return queue; } /* 析構函式 */ void delArrayQueue(ArrayQueue *queue) { free(queue->nums); free(queue); } /* 獲取佇列的容量 */ int capacity(ArrayQueue *queue) { return queue->queCapacity; } /* 獲取佇列的長度 */ int size(ArrayQueue *queue) { return queue->queSize; } /* 判斷佇列是否為空 */ bool empty(ArrayQueue *queue) { return queue->queSize == 0; } /* 訪問佇列首元素 */ int peek(ArrayQueue *queue) { assert(size(queue) != 0); return queue->nums[queue->front]; } /* 入列 */ void push(ArrayQueue *queue, int num) { if (size(queue) == capacity(queue)) { printf("佇列已滿\r\n"); return; } // 計算佇列尾指標,指向佇列尾索引 + 1 // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 int rear = (queue->front + queue->queSize) % queue->queCapacity; // 將 num 新增至佇列尾 queue->nums[rear] = num; queue->queSize++; } /* 出列 */ int pop(ArrayQueue *queue) { int num = peek(queue); // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 queue->front = (queue->front + 1) % queue->queCapacity; queue->queSize--; return num; } /* 返回陣列用於列印 */ int *toArray(ArrayQueue *queue, int *queSize) { *queSize = queue->queSize; int *res = (int *)calloc(queue->queSize, sizeof(int)); int j = queue->front; for (int i = 0; i < queue->queSize; i++) { res[i] = queue->nums[j % queue->queCapacity]; j++; } return res; } /* Driver Code */ int main() { /* 初始化佇列 */ int capacity = 10; int queSize; ArrayQueue *queue = newArrayQueue(capacity); /* 元素入列 */ push(queue, 1); push(queue, 3); push(queue, 2); push(queue, 5); push(queue, 4); printf("佇列 queue = "); printArray(toArray(queue, &queSize), queSize); /* 訪問佇列首元素 */ int peekNum = peek(queue); printf("佇列首元素 peek = %d\r\n", peekNum); /* 元素出列 */ peekNum = pop(queue); printf("出列元素 pop = %d ,出列後 queue = ", peekNum); printArray(toArray(queue, &queSize), queSize); /* 獲取佇列的長度 */ int queueSize = size(queue); printf("佇列長度 size = %d\r\n", queueSize); /* 判斷佇列是否為空 */ bool isEmpty = empty(queue); printf("佇列是否為空 = %s\r\n", isEmpty ? "true" : "false"); /* 測試環形陣列 */ for (int i = 0; i < 10; i++) { push(queue, i); pop(queue); printf("第 %d 輪入列 + 出列後 queue = ", i); printArray(toArray(queue, &queSize), queSize); } // 釋放記憶體 delArrayQueue(queue); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_stack_and_queue/array_stack.c ================================================ /** * File: array_stack.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" #define MAX_SIZE 5000 /* 基於陣列實現的堆疊 */ typedef struct { int *data; int size; } ArrayStack; /* 建構子 */ ArrayStack *newArrayStack() { ArrayStack *stack = malloc(sizeof(ArrayStack)); // 初始化一個大容量,避免擴容 stack->data = malloc(sizeof(int) * MAX_SIZE); stack->size = 0; return stack; } /* 析構函式 */ void delArrayStack(ArrayStack *stack) { free(stack->data); free(stack); } /* 獲取堆疊的長度 */ int size(ArrayStack *stack) { return stack->size; } /* 判斷堆疊是否為空 */ bool isEmpty(ArrayStack *stack) { return stack->size == 0; } /* 入堆疊 */ void push(ArrayStack *stack, int num) { if (stack->size == MAX_SIZE) { printf("堆疊已滿\n"); return; } stack->data[stack->size] = num; stack->size++; } /* 訪問堆疊頂元素 */ int peek(ArrayStack *stack) { if (stack->size == 0) { printf("堆疊為空\n"); return INT_MAX; } return stack->data[stack->size - 1]; } /* 出堆疊 */ int pop(ArrayStack *stack) { int val = peek(stack); stack->size--; return val; } /* Driver Code */ int main() { /* 初始化堆疊 */ ArrayStack *stack = newArrayStack(); /* 元素入堆疊 */ push(stack, 1); push(stack, 3); push(stack, 2); push(stack, 5); push(stack, 4); printf("堆疊 stack = "); printArray(stack->data, stack->size); /* 訪問堆疊頂元素 */ int val = peek(stack); printf("堆疊頂元素 top = %d\n", val); /* 元素出堆疊 */ val = pop(stack); printf("出堆疊元素 pop = %d ,出堆疊後 stack = ", val); printArray(stack->data, stack->size); /* 獲取堆疊的長度 */ int size = stack->size; printf("堆疊的長度 size = %d\n", size); /* 判斷是否為空 */ bool empty = isEmpty(stack); printf("堆疊是否為空 = %s\n", empty ? "true" : "false"); // 釋放記憶體 delArrayStack(stack); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_stack_and_queue/linkedlist_deque.c ================================================ /** * File: linkedlist_deque.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 雙向鏈結串列節點 */ typedef struct DoublyListNode { int val; // 節點值 struct DoublyListNode *next; // 後繼節點 struct DoublyListNode *prev; // 前驅節點 } DoublyListNode; /* 建構子 */ DoublyListNode *newDoublyListNode(int num) { DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode)); new->val = num; new->next = NULL; new->prev = NULL; return new; } /* 析構函式 */ void delDoublyListNode(DoublyListNode *node) { free(node); } /* 基於雙向鏈結串列實現的雙向佇列 */ typedef struct { DoublyListNode *front, *rear; // 頭節點 front ,尾節點 rear int queSize; // 雙向佇列的長度 } LinkedListDeque; /* 建構子 */ LinkedListDeque *newLinkedListDeque() { LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque)); deque->front = NULL; deque->rear = NULL; deque->queSize = 0; return deque; } /* 析構函式 */ void delLinkedListdeque(LinkedListDeque *deque) { // 釋放所有節點 for (int i = 0; i < deque->queSize && deque->front != NULL; i++) { DoublyListNode *tmp = deque->front; deque->front = deque->front->next; free(tmp); } // 釋放 deque 結構體 free(deque); } /* 獲取佇列的長度 */ int size(LinkedListDeque *deque) { return deque->queSize; } /* 判斷佇列是否為空 */ bool empty(LinkedListDeque *deque) { return (size(deque) == 0); } /* 入列 */ void push(LinkedListDeque *deque, int num, bool isFront) { DoublyListNode *node = newDoublyListNode(num); // 若鏈結串列為空,則令 front 和 rear 都指向node if (empty(deque)) { deque->front = deque->rear = node; } // 佇列首入列操作 else if (isFront) { // 將 node 新增至鏈結串列頭部 deque->front->prev = node; node->next = deque->front; deque->front = node; // 更新頭節點 } // 佇列尾入列操作 else { // 將 node 新增至鏈結串列尾部 deque->rear->next = node; node->prev = deque->rear; deque->rear = node; } deque->queSize++; // 更新佇列長度 } /* 佇列首入列 */ void pushFirst(LinkedListDeque *deque, int num) { push(deque, num, true); } /* 佇列尾入列 */ void pushLast(LinkedListDeque *deque, int num) { push(deque, num, false); } /* 訪問佇列首元素 */ int peekFirst(LinkedListDeque *deque) { assert(size(deque) && deque->front); return deque->front->val; } /* 訪問佇列尾元素 */ int peekLast(LinkedListDeque *deque) { assert(size(deque) && deque->rear); return deque->rear->val; } /* 出列 */ int pop(LinkedListDeque *deque, bool isFront) { if (empty(deque)) return -1; int val; // 佇列首出列操作 if (isFront) { val = peekFirst(deque); // 暫存頭節點值 DoublyListNode *fNext = deque->front->next; if (fNext) { fNext->prev = NULL; deque->front->next = NULL; } delDoublyListNode(deque->front); deque->front = fNext; // 更新頭節點 } // 佇列尾出列操作 else { val = peekLast(deque); // 暫存尾節點值 DoublyListNode *rPrev = deque->rear->prev; if (rPrev) { rPrev->next = NULL; deque->rear->prev = NULL; } delDoublyListNode(deque->rear); deque->rear = rPrev; // 更新尾節點 } deque->queSize--; // 更新佇列長度 return val; } /* 佇列首出列 */ int popFirst(LinkedListDeque *deque) { return pop(deque, true); } /* 佇列尾出列 */ int popLast(LinkedListDeque *deque) { return pop(deque, false); } /* 列印佇列 */ void printLinkedListDeque(LinkedListDeque *deque) { int *arr = malloc(sizeof(int) * deque->queSize); // 複製鏈結串列中的資料到陣列 int i; DoublyListNode *node; for (i = 0, node = deque->front; i < deque->queSize; i++) { arr[i] = node->val; node = node->next; } printArray(arr, deque->queSize); free(arr); } /* Driver Code */ int main() { /* 初始化雙向佇列 */ LinkedListDeque *deque = newLinkedListDeque(); pushLast(deque, 3); pushLast(deque, 2); pushLast(deque, 5); printf("雙向佇列 deque = "); printLinkedListDeque(deque); /* 訪問元素 */ int peekFirstNum = peekFirst(deque); printf("佇列首元素 peekFirst = %d\r\n", peekFirstNum); int peekLastNum = peekLast(deque); printf("佇列首元素 peekLast = %d\r\n", peekLastNum); /* 元素入列 */ pushLast(deque, 4); printf("元素 4 佇列尾入列後 deque ="); printLinkedListDeque(deque); pushFirst(deque, 1); printf("元素 1 佇列首入列後 deque ="); printLinkedListDeque(deque); /* 元素出列 */ int popLastNum = popLast(deque); printf("佇列尾出列元素 popLast = %d ,佇列尾出列後 deque = ", popLastNum); printLinkedListDeque(deque); int popFirstNum = popFirst(deque); printf("佇列首出列元素 popFirst = %d ,佇列首出列後 deque = ", popFirstNum); printLinkedListDeque(deque); /* 獲取佇列的長度 */ int dequeSize = size(deque); printf("雙向佇列長度 size = %d\r\n", dequeSize); /* 判斷佇列是否為空 */ bool isEmpty = empty(deque); printf("雙向佇列是否為空 = %s\r\n", isEmpty ? "true" : "false"); // 釋放記憶體 delLinkedListdeque(deque); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_stack_and_queue/linkedlist_queue.c ================================================ /** * File: linkedlist_queue.c * Created Time: 2023-03-13 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 基於鏈結串列實現的佇列 */ typedef struct { ListNode *front, *rear; int queSize; } LinkedListQueue; /* 建構子 */ LinkedListQueue *newLinkedListQueue() { LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue)); queue->front = NULL; queue->rear = NULL; queue->queSize = 0; return queue; } /* 析構函式 */ void delLinkedListQueue(LinkedListQueue *queue) { // 釋放所有節點 while (queue->front != NULL) { ListNode *tmp = queue->front; queue->front = queue->front->next; free(tmp); } // 釋放 queue 結構體 free(queue); } /* 獲取佇列的長度 */ int size(LinkedListQueue *queue) { return queue->queSize; } /* 判斷佇列是否為空 */ bool empty(LinkedListQueue *queue) { return (size(queue) == 0); } /* 入列 */ void push(LinkedListQueue *queue, int num) { // 尾節點處新增 node ListNode *node = newListNode(num); // 如果佇列為空,則令頭、尾節點都指向該節點 if (queue->front == NULL) { queue->front = node; queue->rear = node; } // 如果佇列不為空,則將該節點新增到尾節點後 else { queue->rear->next = node; queue->rear = node; } queue->queSize++; } /* 訪問佇列首元素 */ int peek(LinkedListQueue *queue) { assert(size(queue) && queue->front); return queue->front->val; } /* 出列 */ int pop(LinkedListQueue *queue) { int num = peek(queue); ListNode *tmp = queue->front; queue->front = queue->front->next; free(tmp); queue->queSize--; return num; } /* 列印佇列 */ void printLinkedListQueue(LinkedListQueue *queue) { int *arr = malloc(sizeof(int) * queue->queSize); // 複製鏈結串列中的資料到陣列 int i; ListNode *node; for (i = 0, node = queue->front; i < queue->queSize; i++) { arr[i] = node->val; node = node->next; } printArray(arr, queue->queSize); free(arr); } /* Driver Code */ int main() { /* 初始化佇列 */ LinkedListQueue *queue = newLinkedListQueue(); /* 元素入列 */ push(queue, 1); push(queue, 3); push(queue, 2); push(queue, 5); push(queue, 4); printf("佇列 queue = "); printLinkedListQueue(queue); /* 訪問佇列首元素 */ int peekNum = peek(queue); printf("佇列首元素 peek = %d\r\n", peekNum); /* 元素出列 */ peekNum = pop(queue); printf("出列元素 pop = %d ,出列後 queue = ", peekNum); printLinkedListQueue(queue); /* 獲取佇列的長度 */ int queueSize = size(queue); printf("佇列長度 size = %d\r\n", queueSize); /* 判斷佇列是否為空 */ bool isEmpty = empty(queue); printf("佇列是否為空 = %s\r\n", isEmpty ? "true" : "false"); // 釋放記憶體 delLinkedListQueue(queue); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_stack_and_queue/linkedlist_stack.c ================================================ /** * File: linkedlist_stack.c * Created Time: 2023-01-12 * Author: Zero (glj0@outlook.com) */ #include "../utils/common.h" /* 基於鏈結串列實現的堆疊 */ typedef struct { ListNode *top; // 將頭節點作為堆疊頂 int size; // 堆疊的長度 } LinkedListStack; /* 建構子 */ LinkedListStack *newLinkedListStack() { LinkedListStack *s = malloc(sizeof(LinkedListStack)); s->top = NULL; s->size = 0; return s; } /* 析構函式 */ void delLinkedListStack(LinkedListStack *s) { while (s->top) { ListNode *n = s->top->next; free(s->top); s->top = n; } free(s); } /* 獲取堆疊的長度 */ int size(LinkedListStack *s) { return s->size; } /* 判斷堆疊是否為空 */ bool isEmpty(LinkedListStack *s) { return size(s) == 0; } /* 入堆疊 */ void push(LinkedListStack *s, int num) { ListNode *node = (ListNode *)malloc(sizeof(ListNode)); node->next = s->top; // 更新新加節點指標域 node->val = num; // 更新新加節點資料域 s->top = node; // 更新堆疊頂 s->size++; // 更新堆疊大小 } /* 訪問堆疊頂元素 */ int peek(LinkedListStack *s) { if (s->size == 0) { printf("堆疊為空\n"); return INT_MAX; } return s->top->val; } /* 出堆疊 */ int pop(LinkedListStack *s) { int val = peek(s); ListNode *tmp = s->top; s->top = s->top->next; // 釋放記憶體 free(tmp); s->size--; return val; } /* Driver Code */ int main() { /* 初始化堆疊 */ LinkedListStack *stack = newLinkedListStack(); /* 元素入堆疊 */ push(stack, 1); push(stack, 3); push(stack, 2); push(stack, 5); push(stack, 4); printf("堆疊 stack = "); printLinkedList(stack->top); /* 訪問堆疊頂元素 */ int val = peek(stack); printf("堆疊頂元素 top = %d\r\n", val); /* 元素出堆疊 */ val = pop(stack); printf("出堆疊元素 pop = %d, 出堆疊後 stack = ", val); printLinkedList(stack->top); /* 獲取堆疊的長度 */ printf("堆疊的長度 size = %d\n", size(stack)); /* 判斷是否為空 */ bool empty = isEmpty(stack); printf("堆疊是否為空 = %s\n", empty ? "true" : "false"); // 釋放記憶體 delLinkedListStack(stack); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_tree/CMakeLists.txt ================================================ add_executable(avl_tree avl_tree.c) add_executable(binary_tree binary_tree.c) add_executable(binary_tree_bfs binary_tree_bfs.c) add_executable(binary_tree_dfs binary_tree_dfs.c) add_executable(binary_search_tree binary_search_tree.c) add_executable(array_binary_tree array_binary_tree.c) ================================================ FILE: zh-hant/codes/c/chapter_tree/array_binary_tree.c ================================================ /** * File: array_binary_tree.c * Created Time: 2023-07-29 * Author: Gonglja (glj0@outlook.com) */ #include "../utils/common.h" /* 陣列表示下的二元樹結構體 */ typedef struct { int *tree; int size; } ArrayBinaryTree; /* 建構子 */ ArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) { ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree)); abt->tree = malloc(sizeof(int) * arrSize); memcpy(abt->tree, arr, sizeof(int) * arrSize); abt->size = arrSize; return abt; } /* 析構函式 */ void delArrayBinaryTree(ArrayBinaryTree *abt) { free(abt->tree); free(abt); } /* 串列容量 */ int size(ArrayBinaryTree *abt) { return abt->size; } /* 獲取索引為 i 節點的值 */ int val(ArrayBinaryTree *abt, int i) { // 若索引越界,則返回 INT_MAX ,代表空位 if (i < 0 || i >= size(abt)) return INT_MAX; return abt->tree[i]; } /* 獲取索引為 i 節點的左子節點的索引 */ int left(int i) { return 2 * i + 1; } /* 獲取索引為 i 節點的右子節點的索引 */ int right(int i) { return 2 * i + 2; } /* 獲取索引為 i 節點的父節點的索引 */ int parent(int i) { return (i - 1) / 2; } /* 層序走訪 */ int *levelOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; // 直接走訪陣列 for (int i = 0; i < size(abt); i++) { if (val(abt, i) != INT_MAX) res[index++] = val(abt, i); } *returnSize = index; return res; } /* 深度優先走訪 */ void dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) { // 若為空位,則返回 if (val(abt, i) == INT_MAX) return; // 前序走訪 if (strcmp(order, "pre") == 0) res[(*index)++] = val(abt, i); dfs(abt, left(i), order, res, index); // 中序走訪 if (strcmp(order, "in") == 0) res[(*index)++] = val(abt, i); dfs(abt, right(i), order, res, index); // 後序走訪 if (strcmp(order, "post") == 0) res[(*index)++] = val(abt, i); } /* 前序走訪 */ int *preOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "pre", res, &index); *returnSize = index; return res; } /* 中序走訪 */ int *inOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "in", res, &index); *returnSize = index; return res; } /* 後序走訪 */ int *postOrder(ArrayBinaryTree *abt, int *returnSize) { int *res = (int *)malloc(sizeof(int) * size(abt)); int index = 0; dfs(abt, 0, "post", res, &index); *returnSize = index; return res; } /* Driver Code */ int main() { // 初始化二元樹 // 使用 INT_MAX 代表空位 NULL int arr[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; int arrSize = sizeof(arr) / sizeof(arr[0]); TreeNode *root = arrayToTree(arr, arrSize); printf("\n初始化二元樹\n"); printf("二元樹的陣列表示:\n"); printArray(arr, arrSize); printf("二元樹的鏈結串列表示:\n"); printTree(root); ArrayBinaryTree *abt = newArrayBinaryTree(arr, arrSize); // 訪問節點 int i = 1; int l = left(i), r = right(i), p = parent(i); printf("\n當前節點的索引為 %d,值為 %d\n", i, val(abt, i)); printf("其左子節點的索引為 %d,值為 %d\n", l, l < arrSize ? val(abt, l) : INT_MAX); printf("其右子節點的索引為 %d,值為 %d\n", r, r < arrSize ? val(abt, r) : INT_MAX); printf("其父節點的索引為 %d,值為 %d\n", p, p < arrSize ? val(abt, p) : INT_MAX); // 走訪樹 int returnSize; int *res; res = levelOrder(abt, &returnSize); printf("\n層序走訪為: "); printArray(res, returnSize); free(res); res = preOrder(abt, &returnSize); printf("前序走訪為: "); printArray(res, returnSize); free(res); res = inOrder(abt, &returnSize); printf("中序走訪為: "); printArray(res, returnSize); free(res); res = postOrder(abt, &returnSize); printf("後序走訪為: "); printArray(res, returnSize); free(res); // 釋放記憶體 delArrayBinaryTree(abt); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_tree/avl_tree.c ================================================ /** * File: avl_tree.c * Created Time: 2023-01-15 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* AVL 樹結構體 */ typedef struct { TreeNode *root; } AVLTree; /* 建構子 */ AVLTree *newAVLTree() { AVLTree *tree = (AVLTree *)malloc(sizeof(AVLTree)); tree->root = NULL; return tree; } /* 析構函式 */ void delAVLTree(AVLTree *tree) { freeMemoryTree(tree->root); free(tree); } /* 獲取節點高度 */ int height(TreeNode *node) { // 空節點高度為 -1 ,葉節點高度為 0 if (node != NULL) { return node->height; } return -1; } /* 更新節點高度 */ void updateHeight(TreeNode *node) { int lh = height(node->left); int rh = height(node->right); // 節點高度等於最高子樹高度 + 1 if (lh > rh) { node->height = lh + 1; } else { node->height = rh + 1; } } /* 獲取平衡因子 */ int balanceFactor(TreeNode *node) { // 空節點平衡因子為 0 if (node == NULL) { return 0; } // 節點平衡因子 = 左子樹高度 - 右子樹高度 return height(node->left) - height(node->right); } /* 右旋操作 */ TreeNode *rightRotate(TreeNode *node) { TreeNode *child, *grandChild; child = node->left; grandChild = child->right; // 以 child 為原點,將 node 向右旋轉 child->right = node; node->left = grandChild; // 更新節點高度 updateHeight(node); updateHeight(child); // 返回旋轉後子樹的根節點 return child; } /* 左旋操作 */ TreeNode *leftRotate(TreeNode *node) { TreeNode *child, *grandChild; child = node->right; grandChild = child->left; // 以 child 為原點,將 node 向左旋轉 child->left = node; node->right = grandChild; // 更新節點高度 updateHeight(node); updateHeight(child); // 返回旋轉後子樹的根節點 return child; } /* 執行旋轉操作,使該子樹重新恢復平衡 */ TreeNode *rotate(TreeNode *node) { // 獲取節點 node 的平衡因子 int bf = balanceFactor(node); // 左偏樹 if (bf > 1) { if (balanceFactor(node->left) >= 0) { // 右旋 return rightRotate(node); } else { // 先左旋後右旋 node->left = leftRotate(node->left); return rightRotate(node); } } // 右偏樹 if (bf < -1) { if (balanceFactor(node->right) <= 0) { // 左旋 return leftRotate(node); } else { // 先右旋後左旋 node->right = rightRotate(node->right); return leftRotate(node); } } // 平衡樹,無須旋轉,直接返回 return node; } /* 遞迴插入節點(輔助函式) */ TreeNode *insertHelper(TreeNode *node, int val) { if (node == NULL) { return newTreeNode(val); } /* 1. 查詢插入位置並插入節點 */ if (val < node->val) { node->left = insertHelper(node->left, val); } else if (val > node->val) { node->right = insertHelper(node->right, val); } else { // 重複節點不插入,直接返回 return node; } // 更新節點高度 updateHeight(node); /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = rotate(node); // 返回子樹的根節點 return node; } /* 插入節點 */ void insert(AVLTree *tree, int val) { tree->root = insertHelper(tree->root, val); } /* 遞迴刪除節點(輔助函式) */ TreeNode *removeHelper(TreeNode *node, int val) { TreeNode *child, *grandChild; if (node == NULL) { return NULL; } /* 1. 查詢節點並刪除 */ if (val < node->val) { node->left = removeHelper(node->left, val); } else if (val > node->val) { node->right = removeHelper(node->right, val); } else { if (node->left == NULL || node->right == NULL) { child = node->left; if (node->right != NULL) { child = node->right; } // 子節點數量 = 0 ,直接刪除 node 並返回 if (child == NULL) { return NULL; } else { // 子節點數量 = 1 ,直接刪除 node node = child; } } else { // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 TreeNode *temp = node->right; while (temp->left != NULL) { temp = temp->left; } int tempVal = temp->val; node->right = removeHelper(node->right, temp->val); node->val = tempVal; } } // 更新節點高度 updateHeight(node); /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = rotate(node); // 返回子樹的根節點 return node; } /* 刪除節點 */ // 由於引入了 stdio.h ,此處無法使用 remove 關鍵詞 void removeItem(AVLTree *tree, int val) { TreeNode *root = removeHelper(tree->root, val); } /* 查詢節點 */ TreeNode *search(AVLTree *tree, int val) { TreeNode *cur = tree->root; // 迴圈查詢,越過葉節點後跳出 while (cur != NULL) { if (cur->val < val) { // 目標節點在 cur 的右子樹中 cur = cur->right; } else if (cur->val > val) { // 目標節點在 cur 的左子樹中 cur = cur->left; } else { // 找到目標節點,跳出迴圈 break; } } // 找到目標節點,跳出迴圈 return cur; } void testInsert(AVLTree *tree, int val) { insert(tree, val); printf("\n插入節點 %d 後,AVL 樹為 \n", val); printTree(tree->root); } void testRemove(AVLTree *tree, int val) { removeItem(tree, val); printf("\n刪除節點 %d 後,AVL 樹為 \n", val); printTree(tree->root); } /* Driver Code */ int main() { /* 初始化空 AVL 樹 */ AVLTree *tree = (AVLTree *)newAVLTree(); /* 插入節點 */ // 請關注插入節點後,AVL 樹是如何保持平衡的 testInsert(tree, 1); testInsert(tree, 2); testInsert(tree, 3); testInsert(tree, 4); testInsert(tree, 5); testInsert(tree, 8); testInsert(tree, 7); testInsert(tree, 9); testInsert(tree, 10); testInsert(tree, 6); /* 插入重複節點 */ testInsert(tree, 7); /* 刪除節點 */ // 請關注刪除節點後,AVL 樹是如何保持平衡的 testRemove(tree, 8); // 刪除度為 0 的節點 testRemove(tree, 5); // 刪除度為 1 的節點 testRemove(tree, 4); // 刪除度為 2 的節點 /* 查詢節點 */ TreeNode *node = search(tree, 7); printf("\n查詢到的節點物件節點值 = %d \n", node->val); // 釋放記憶體 delAVLTree(tree); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_tree/binary_search_tree.c ================================================ /** * File: binary_search_tree.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* 二元搜尋樹結構體 */ typedef struct { TreeNode *root; } BinarySearchTree; /* 建構子 */ BinarySearchTree *newBinarySearchTree() { // 初始化空樹 BinarySearchTree *bst = (BinarySearchTree *)malloc(sizeof(BinarySearchTree)); bst->root = NULL; return bst; } /* 析構函式 */ void delBinarySearchTree(BinarySearchTree *bst) { freeMemoryTree(bst->root); free(bst); } /* 獲取二元樹根節點 */ TreeNode *getRoot(BinarySearchTree *bst) { return bst->root; } /* 查詢節點 */ TreeNode *search(BinarySearchTree *bst, int num) { TreeNode *cur = bst->root; // 迴圈查詢,越過葉節點後跳出 while (cur != NULL) { if (cur->val < num) { // 目標節點在 cur 的右子樹中 cur = cur->right; } else if (cur->val > num) { // 目標節點在 cur 的左子樹中 cur = cur->left; } else { // 找到目標節點,跳出迴圈 break; } } // 返回目標節點 return cur; } /* 插入節點 */ void insert(BinarySearchTree *bst, int num) { // 若樹為空,則初始化根節點 if (bst->root == NULL) { bst->root = newTreeNode(num); return; } TreeNode *cur = bst->root, *pre = NULL; // 迴圈查詢,越過葉節點後跳出 while (cur != NULL) { // 找到重複節點,直接返回 if (cur->val == num) { return; } pre = cur; if (cur->val < num) { // 插入位置在 cur 的右子樹中 cur = cur->right; } else { // 插入位置在 cur 的左子樹中 cur = cur->left; } } // 插入節點 TreeNode *node = newTreeNode(num); if (pre->val < num) { pre->right = node; } else { pre->left = node; } } /* 刪除節點 */ // 由於引入了 stdio.h ,此處無法使用 remove 關鍵詞 void removeItem(BinarySearchTree *bst, int num) { // 若樹為空,直接提前返回 if (bst->root == NULL) return; TreeNode *cur = bst->root, *pre = NULL; // 迴圈查詢,越過葉節點後跳出 while (cur != NULL) { // 找到待刪除節點,跳出迴圈 if (cur->val == num) break; pre = cur; if (cur->val < num) { // 待刪除節點在 root 的右子樹中 cur = cur->right; } else { // 待刪除節點在 root 的左子樹中 cur = cur->left; } } // 若無待刪除節點,則直接返回 if (cur == NULL) return; // 判斷待刪除節點是否存在子節點 if (cur->left == NULL || cur->right == NULL) { /* 子節點數量 = 0 or 1 */ // 當子節點數量 = 0 / 1 時, child = nullptr / 該子節點 TreeNode *child = cur->left != NULL ? cur->left : cur->right; // 刪除節點 cur if (pre->left == cur) { pre->left = child; } else { pre->right = child; } // 釋放記憶體 free(cur); } else { /* 子節點數量 = 2 */ // 獲取中序走訪中 cur 的下一個節點 TreeNode *tmp = cur->right; while (tmp->left != NULL) { tmp = tmp->left; } int tmpVal = tmp->val; // 遞迴刪除節點 tmp removeItem(bst, tmp->val); // 用 tmp 覆蓋 cur cur->val = tmpVal; } } /* Driver Code */ int main() { /* 初始化二元搜尋樹 */ int nums[] = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; BinarySearchTree *bst = newBinarySearchTree(); for (int i = 0; i < sizeof(nums) / sizeof(int); i++) { insert(bst, nums[i]); } printf("初始化的二元樹為\n"); printTree(getRoot(bst)); /* 查詢節點 */ TreeNode *node = search(bst, 7); printf("查詢到的節點物件的節點值 = %d\n", node->val); /* 插入節點 */ insert(bst, 16); printf("插入節點 16 後,二元樹為\n"); printTree(getRoot(bst)); /* 刪除節點 */ removeItem(bst, 1); printf("刪除節點 1 後,二元樹為\n"); printTree(getRoot(bst)); removeItem(bst, 2); printf("刪除節點 2 後,二元樹為\n"); printTree(getRoot(bst)); removeItem(bst, 4); printf("刪除節點 4 後,二元樹為\n"); printTree(getRoot(bst)); // 釋放記憶體 delBinarySearchTree(bst); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_tree/binary_tree.c ================================================ /** * File: binary_tree.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" /* Driver Code */ int main() { /* 初始化二元樹 */ // 初始化節點 TreeNode *n1 = newTreeNode(1); TreeNode *n2 = newTreeNode(2); TreeNode *n3 = newTreeNode(3); TreeNode *n4 = newTreeNode(4); TreeNode *n5 = newTreeNode(5); // 構建節點之間的引用(指標) n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; printf("初始化二元樹\n"); printTree(n1); /* 插入與刪除節點 */ TreeNode *P = newTreeNode(0); // 在 n1 -> n2 中間插入節點 P n1->left = P; P->left = n2; printf("插入節點 P 後\n"); printTree(n1); // 刪除節點 P n1->left = n2; // 釋放記憶體 free(P); printf("刪除節點 P 後\n"); printTree(n1); freeMemoryTree(n1); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_tree/binary_tree_bfs.c ================================================ /** * File: binary_tree_bfs.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 100 /* 層序走訪 */ int *levelOrder(TreeNode *root, int *size) { /* 輔助佇列 */ int front, rear; int index, *arr; TreeNode *node; TreeNode **queue; /* 輔助佇列 */ queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE); // 佇列指標 front = 0, rear = 0; // 加入根節點 queue[rear++] = root; // 初始化一個串列,用於儲存走訪序列 /* 輔助陣列 */ arr = (int *)malloc(sizeof(int) * MAX_SIZE); // 陣列指標 index = 0; while (front < rear) { // 隊列出隊 node = queue[front++]; // 儲存節點值 arr[index++] = node->val; if (node->left != NULL) { // 左子節點入列 queue[rear++] = node->left; } if (node->right != NULL) { // 右子節點入列 queue[rear++] = node->right; } } // 更新陣列長度的值 *size = index; arr = realloc(arr, sizeof(int) * (*size)); // 釋放輔助陣列空間 free(queue); return arr; } /* Driver Code */ int main() { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 int nums[] = {1, 2, 3, 4, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); printf("初始化二元樹\n"); printTree(root); /* 層序走訪 */ // 需要傳入陣列的長度 int *arr = levelOrder(root, &size); printf("層序走訪的節點列印序列 = "); printArray(arr, size); // 釋放記憶體 freeMemoryTree(root); free(arr); return 0; } ================================================ FILE: zh-hant/codes/c/chapter_tree/binary_tree_dfs.c ================================================ /** * File: binary_tree_dfs.c * Created Time: 2023-01-11 * Author: Reanon (793584285@qq.com) */ #include "../utils/common.h" #define MAX_SIZE 100 // 輔助陣列,用於儲存走訪序列 int arr[MAX_SIZE]; /* 前序走訪 */ void preOrder(TreeNode *root, int *size) { if (root == NULL) return; // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 arr[(*size)++] = root->val; preOrder(root->left, size); preOrder(root->right, size); } /* 中序走訪 */ void inOrder(TreeNode *root, int *size) { if (root == NULL) return; // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 inOrder(root->left, size); arr[(*size)++] = root->val; inOrder(root->right, size); } /* 後序走訪 */ void postOrder(TreeNode *root, int *size) { if (root == NULL) return; // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 postOrder(root->left, size); postOrder(root->right, size); arr[(*size)++] = root->val; } /* Driver Code */ int main() { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 int nums[] = {1, 2, 3, 4, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); printf("初始化二元樹\n"); printTree(root); /* 前序走訪 */ // 初始化輔助陣列 size = 0; preOrder(root, &size); printf("前序走訪的節點列印序列 = "); printArray(arr, size); /* 中序走訪 */ size = 0; inOrder(root, &size); printf("中序走訪的節點列印序列 = "); printArray(arr, size); /* 後序走訪 */ size = 0; postOrder(root, &size); printf("後序走訪的節點列印序列 = "); printArray(arr, size); freeMemoryTree(root); return 0; } ================================================ FILE: zh-hant/codes/c/utils/CMakeLists.txt ================================================ add_executable(utils common_test.c common.h print_util.h list_node.h tree_node.h uthash.h) ================================================ FILE: zh-hant/codes/c/utils/common.h ================================================ /** * File: common.h * Created Time: 2022-12-20 * Author: MolDuM (moldum@163.com)、Reanon (793584285@qq.com) */ #ifndef COMMON_H #define COMMON_H #include #include #include #include #include #include #include #include "list_node.h" #include "print_util.h" #include "tree_node.h" #include "vertex.h" // hash table lib #include "uthash.h" #include "vector.h" #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif #endif // COMMON_H ================================================ FILE: zh-hant/codes/c/utils/common_test.c ================================================ /** * File: include_test.c * Created Time: 2023-01-10 * Author: Reanon (793584285@qq.com) */ #include "common.h" void testListNode() { int nums[] = {2, 3, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); ListNode *head = arrToLinkedList(nums, size); printLinkedList(head); } void testTreeNode() { int nums[] = {1, 2, 3, INT_MAX, 5, 6, INT_MAX}; int size = sizeof(nums) / sizeof(int); TreeNode *root = arrayToTree(nums, size); // print tree printTree(root); // tree to arr int *arr = treeToArray(root, &size); printArray(arr, size); } int main(int argc, char *argv[]) { printf("==testListNode==\n"); testListNode(); printf("==testTreeNode==\n"); testTreeNode(); return 0; } ================================================ FILE: zh-hant/codes/c/utils/list_node.h ================================================ /** * File: list_node.h * Created Time: 2023-01-09 * Author: Reanon (793584285@qq.com) */ #ifndef LIST_NODE_H #define LIST_NODE_H #ifdef __cplusplus extern "C" { #endif /* 鏈結串列節點結構體 */ typedef struct ListNode { int val; // 節點值 struct ListNode *next; // 指向下一節點的引用 } ListNode; /* 建構子,初始化一個新節點 */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *)malloc(sizeof(ListNode)); node->val = val; node->next = NULL; return node; } /* 將陣列反序列化為鏈結串列 */ ListNode *arrToLinkedList(const int *arr, size_t size) { if (size <= 0) { return NULL; } ListNode *dummy = newListNode(0); ListNode *node = dummy; for (int i = 0; i < size; i++) { node->next = newListNode(arr[i]); node = node->next; } return dummy->next; } /* 釋放分配給鏈結串列的記憶體空間 */ void freeMemoryLinkedList(ListNode *cur) { // 釋放記憶體 ListNode *pre; while (cur != NULL) { pre = cur; cur = cur->next; free(pre); } } #ifdef __cplusplus } #endif #endif // LIST_NODE_H ================================================ FILE: zh-hant/codes/c/utils/print_util.h ================================================ /** * File: print_util.h * Created Time: 2022-12-21 * Author: MolDum (moldum@163.com), Reanon (793584285@qq.com) */ #ifndef PRINT_UTIL_H #define PRINT_UTIL_H #include #include #include #include "list_node.h" #include "tree_node.h" #ifdef __cplusplus extern "C" { #endif /* 列印陣列 */ void printArray(int arr[], int size) { if (arr == NULL || size == 0) { printf("[]"); return; } printf("["); for (int i = 0; i < size - 1; i++) { printf("%d, ", arr[i]); } printf("%d]\n", arr[size - 1]); } /* 列印陣列 */ void printArrayFloat(float arr[], int size) { if (arr == NULL || size == 0) { printf("[]"); return; } printf("["); for (int i = 0; i < size - 1; i++) { printf("%.2f, ", arr[i]); } printf("%.2f]\n", arr[size - 1]); } /* 列印鏈結串列 */ void printLinkedList(ListNode *node) { if (node == NULL) { return; } while (node->next != NULL) { printf("%d -> ", node->val); node = node->next; } printf("%d\n", node->val); } typedef struct Trunk { struct Trunk *prev; char *str; } Trunk; Trunk *newTrunk(Trunk *prev, char *str) { Trunk *trunk = (Trunk *)malloc(sizeof(Trunk)); trunk->prev = prev; trunk->str = (char *)malloc(sizeof(char) * 10); strcpy(trunk->str, str); return trunk; } void showTrunks(Trunk *trunk) { if (trunk == NULL) { return; } showTrunks(trunk->prev); printf("%s", trunk->str); } /** * 列印二元樹 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTreeHelper(TreeNode *node, Trunk *prev, bool isRight) { if (node == NULL) { return; } char *prev_str = " "; Trunk *trunk = newTrunk(prev, prev_str); printTreeHelper(node->right, trunk, true); if (prev == NULL) { trunk->str = "———"; } else if (isRight) { trunk->str = "/———"; prev_str = " |"; } else { trunk->str = "\\———"; prev->str = prev_str; } showTrunks(trunk); printf("%d\n", node->val); if (prev != NULL) { prev->str = prev_str; } trunk->str = " |"; printTreeHelper(node->left, trunk, false); } /* 列印二元樹 */ void printTree(TreeNode *root) { printTreeHelper(root, NULL, false); } /* 列印堆積 */ void printHeap(int arr[], int size) { TreeNode *root; printf("堆積的陣列表示:"); printArray(arr, size); printf("堆積的樹狀表示:\n"); root = arrayToTree(arr, size); printTree(root); } #ifdef __cplusplus } #endif #endif // PRINT_UTIL_H ================================================ FILE: zh-hant/codes/c/utils/tree_node.h ================================================ /** * File: tree_node.h * Created Time: 2023-01-09 * Author: Reanon (793584285@qq.com) */ #ifndef TREE_NODE_H #define TREE_NODE_H #ifdef __cplusplus extern "C" { #endif #include #define MAX_NODE_SIZE 5000 /* 二元樹節點結構體 */ typedef struct TreeNode { int val; // 節點值 int height; // 節點高度 struct TreeNode *left; // 左子節點指標 struct TreeNode *right; // 右子節點指標 } TreeNode; /* 建構子 */ TreeNode *newTreeNode(int val) { TreeNode *node; node = (TreeNode *)malloc(sizeof(TreeNode)); node->val = val; node->height = 0; node->left = NULL; node->right = NULL; return node; } // 序列化編碼規則請參考: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二元樹的陣列表示: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // 二元樹的鏈結串列表示: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* 將串列反序列化為二元樹:遞迴 */ TreeNode *arrayToTreeDFS(int *arr, int size, int i) { if (i < 0 || i >= size || arr[i] == INT_MAX) { return NULL; } TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); root->val = arr[i]; root->left = arrayToTreeDFS(arr, size, 2 * i + 1); root->right = arrayToTreeDFS(arr, size, 2 * i + 2); return root; } /* 將串列反序列化為二元樹 */ TreeNode *arrayToTree(int *arr, int size) { return arrayToTreeDFS(arr, size, 0); } /* 將二元樹序列化為串列:遞迴 */ void treeToArrayDFS(TreeNode *root, int i, int *res, int *size) { if (root == NULL) { return; } while (i >= *size) { res = realloc(res, (*size + 1) * sizeof(int)); res[*size] = INT_MAX; (*size)++; } res[i] = root->val; treeToArrayDFS(root->left, 2 * i + 1, res, size); treeToArrayDFS(root->right, 2 * i + 2, res, size); } /* 將二元樹序列化為串列 */ int *treeToArray(TreeNode *root, int *size) { *size = 0; int *res = NULL; treeToArrayDFS(root, 0, res, size); return res; } /* 釋放二元樹記憶體 */ void freeMemoryTree(TreeNode *root) { if (root == NULL) return; freeMemoryTree(root->left); freeMemoryTree(root->right); free(root); } #ifdef __cplusplus } #endif #endif // TREE_NODE_H ================================================ FILE: zh-hant/codes/c/utils/uthash.h ================================================ /* Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTHASH_H #define UTHASH_H #define UTHASH_VERSION 2.3.0 #include /* memcmp, memset, strlen */ #include /* ptrdiff_t */ #include /* exit */ #if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT /* This codepath is provided for backward compatibility, but I plan to remove it. */ #warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" typedef unsigned int uint32_t; typedef unsigned char uint8_t; #elif defined(HASH_NO_STDINT) && HASH_NO_STDINT #else #include /* uint8_t, uint32_t */ #endif /* These macros use decltype or the earlier __typeof GNU extension. As decltype is only available in newer compilers (VS2010 or gcc 4.3+ when compiling c++ source) this code uses whatever method is needed or, for VS2008 where neither is available, uses casting workarounds. */ #if !defined(DECLTYPE) && !defined(NO_DECLTYPE) #if defined(_MSC_VER) /* MS compiler */ #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ #define DECLTYPE(x) (decltype(x)) #else /* VS2008 or older (or VS2010 in C mode) */ #define NO_DECLTYPE #endif #elif defined(__MCST__) /* Elbrus C Compiler */ #define DECLTYPE(x) (__typeof(x)) #elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) #define NO_DECLTYPE #else /* GNU, Sun and other compilers */ #define DECLTYPE(x) (__typeof(x)) #endif #endif #ifdef NO_DECLTYPE #define DECLTYPE(x) #define DECLTYPE_ASSIGN(dst,src) \ do { \ char **_da_dst = (char**)(&(dst)); \ *_da_dst = (char*)(src); \ } while (0) #else #define DECLTYPE_ASSIGN(dst,src) \ do { \ (dst) = DECLTYPE(dst)(src); \ } while (0) #endif #ifndef uthash_malloc #define uthash_malloc(sz) malloc(sz) /* malloc fcn */ #endif #ifndef uthash_free #define uthash_free(ptr,sz) free(ptr) /* free fcn */ #endif #ifndef uthash_bzero #define uthash_bzero(a,n) memset(a,'\0',n) #endif #ifndef uthash_strlen #define uthash_strlen(s) strlen(s) #endif #ifndef HASH_FUNCTION #define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) #endif #ifndef HASH_KEYCMP #define HASH_KEYCMP(a,b,n) memcmp(a,b,n) #endif #ifndef uthash_noexpand_fyi #define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ #endif #ifndef uthash_expand_fyi #define uthash_expand_fyi(tbl) /* can be defined to log expands */ #endif #ifndef HASH_NONFATAL_OOM #define HASH_NONFATAL_OOM 0 #endif #if HASH_NONFATAL_OOM /* malloc failures can be recovered from */ #ifndef uthash_nonfatal_oom #define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) #define IF_HASH_NONFATAL_OOM(x) x #else /* malloc failures result in lost memory, hash tables are unusable */ #ifndef uthash_fatal #define uthash_fatal(msg) exit(-1) /* fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") #define IF_HASH_NONFATAL_OOM(x) #endif /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ #define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ /* calculate the element whose hash handle address is hhp */ #define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) /* calculate the hash handle from element address elp */ #define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) #define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ do { \ struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ unsigned _hd_bkt; \ HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ (head)->hh.tbl->buckets[_hd_bkt].count++; \ _hd_hh_item->hh_next = NULL; \ _hd_hh_item->hh_prev = NULL; \ } while (0) #define HASH_VALUE(keyptr,keylen,hashv) \ do { \ HASH_FUNCTION(keyptr, keylen, hashv); \ } while (0) #define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_bkt; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ } \ } \ } while (0) #define HASH_FIND(hh,head,keyptr,keylen,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_hashv; \ HASH_VALUE(keyptr, keylen, _hf_hashv); \ HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ } \ } while (0) #ifdef HASH_BLOOM #define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) #define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) #define HASH_BLOOM_MAKE(tbl,oomed) \ do { \ (tbl)->bloom_nbits = HASH_BLOOM; \ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ if (!(tbl)->bloom_bv) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ } \ } while (0) #define HASH_BLOOM_FREE(tbl) \ do { \ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ } while (0) #define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) #define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) #define HASH_BLOOM_ADD(tbl,hashv) \ HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #define HASH_BLOOM_TEST(tbl,hashv) \ HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #else #define HASH_BLOOM_MAKE(tbl,oomed) #define HASH_BLOOM_FREE(tbl) #define HASH_BLOOM_ADD(tbl,hashv) #define HASH_BLOOM_TEST(tbl,hashv) (1) #define HASH_BLOOM_BYTELEN 0U #endif #define HASH_MAKE_TABLE(hh,head,oomed) \ do { \ (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ if (!(head)->hh.tbl) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ (head)->hh.tbl->tail = &((head)->hh); \ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ (head)->hh.tbl->signature = HASH_SIGNATURE; \ if (!(head)->hh.tbl->buckets) { \ HASH_RECORD_OOM(oomed); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } else { \ uthash_bzero((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ uthash_free((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } \ ) \ } \ } \ } while (0) #define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ } while (0) #define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ } while (0) #define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ } while (0) #define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ } while (0) #define HASH_APPEND_LIST(hh, head, add) \ do { \ (add)->hh.next = NULL; \ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ (head)->hh.tbl->tail->next = (add); \ (head)->hh.tbl->tail = &((add)->hh); \ } while (0) #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ do { \ if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ break; \ } \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #ifdef NO_DECLTYPE #undef HASH_AKBI_INNER_LOOP #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ char *_hs_saved_head = (char*)(head); \ do { \ DECLTYPE_ASSIGN(head, _hs_iter); \ if (cmpfcn(head, add) > 0) { \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ break; \ } \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #endif #if HASH_NONFATAL_OOM #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ if (!(oomed)) { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ if (oomed) { \ HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ HASH_DELETE_HH(hh, head, &(add)->hh); \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } else { \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } \ } else { \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } \ } while (0) #else #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } while (0) #endif #define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (char*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ void *_hs_iter = (head); \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ if (_hs_iter) { \ (add)->hh.next = _hs_iter; \ if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ } else { \ (head) = (add); \ } \ HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ } else { \ HASH_APPEND_LIST(hh, head, add); \ } \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ } while (0) #define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ do { \ unsigned _hs_hashv; \ HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ } while (0) #define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) #define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) #define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (const void*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_APPEND_LIST(hh, head, add); \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ } while (0) #define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ do { \ unsigned _ha_hashv; \ HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ } while (0) #define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) #define HASH_ADD(hh,head,fieldname,keylen_in,add) \ HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) #define HASH_TO_BKT(hashv,num_bkts,bkt) \ do { \ bkt = ((hashv) & ((num_bkts) - 1U)); \ } while (0) /* delete "delptr" from the hash table. * "the usual" patch-up process for the app-order doubly-linked-list. * The use of _hd_hh_del below deserves special explanation. * These used to be expressed using (delptr) but that led to a bug * if someone used the same symbol for the head and deletee, like * HASH_DELETE(hh,users,users); * We want that to work, but by changing the head (users) below * we were forfeiting our ability to further refer to the deletee (users) * in the patch-up process. Solution: use scratch space to * copy the deletee pointer, then the latter references are via that * scratch pointer rather than through the repointed (users) symbol. */ #define HASH_DELETE(hh,head,delptr) \ HASH_DELETE_HH(hh, head, &(delptr)->hh) #define HASH_DELETE_HH(hh,head,delptrhh) \ do { \ const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } else { \ unsigned _hd_bkt; \ if (_hd_hh_del == (head)->hh.tbl->tail) { \ (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ } \ if (_hd_hh_del->prev != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ } else { \ DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ } \ if (_hd_hh_del->next != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ } \ HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ (head)->hh.tbl->num_items--; \ } \ HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ } while (0) /* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ #define HASH_FIND_STR(head,findstr,out) \ do { \ unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ } while (0) #define HASH_ADD_STR(head,strfield,add) \ do { \ unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ } while (0) #define HASH_REPLACE_STR(head,strfield,add,replaced) \ do { \ unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ } while (0) #define HASH_FIND_INT(head,findint,out) \ HASH_FIND(hh,head,findint,sizeof(int),out) #define HASH_ADD_INT(head,intfield,add) \ HASH_ADD(hh,head,intfield,sizeof(int),add) #define HASH_REPLACE_INT(head,intfield,add,replaced) \ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) #define HASH_FIND_PTR(head,findptr,out) \ HASH_FIND(hh,head,findptr,sizeof(void *),out) #define HASH_ADD_PTR(head,ptrfield,add) \ HASH_ADD(hh,head,ptrfield,sizeof(void *),add) #define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) #define HASH_DEL(head,delptr) \ HASH_DELETE(hh,head,delptr) /* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. */ #ifdef HASH_DEBUG #include /* fprintf, stderr */ #define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) #define HASH_FSCK(hh,head,where) \ do { \ struct UT_hash_handle *_thh; \ if (head) { \ unsigned _bkt_i; \ unsigned _count = 0; \ char *_prev; \ for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ unsigned _bkt_count = 0; \ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ _prev = NULL; \ while (_thh) { \ if (_prev != (char*)(_thh->hh_prev)) { \ HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ (where), (void*)_thh->hh_prev, (void*)_prev); \ } \ _bkt_count++; \ _prev = (char*)(_thh); \ _thh = _thh->hh_next; \ } \ _count += _bkt_count; \ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ } \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ _count = 0; \ _prev = NULL; \ _thh = &(head)->hh; \ while (_thh) { \ _count++; \ if (_prev != (char*)_thh->prev) { \ HASH_OOPS("%s: invalid prev %p, actual %p\n", \ (where), (void*)_thh->prev, (void*)_prev); \ } \ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ } \ } while (0) #else #define HASH_FSCK(hh,head,where) #endif /* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to * the descriptor to which this macro is defined for tuning the hash function. * The app can #include to get the prototype for write(2). */ #ifdef HASH_EMIT_KEYS #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ do { \ unsigned _klen = fieldlen; \ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ } while (0) #else #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) #endif /* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ #define HASH_BER(key,keylen,hashv) \ do { \ unsigned _hb_keylen = (unsigned)keylen; \ const unsigned char *_hb_key = (const unsigned char*)(key); \ (hashv) = 0; \ while (_hb_keylen-- != 0U) { \ (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ } \ } while (0) /* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx * (archive link: https://archive.is/Ivcan ) */ #define HASH_SAX(key,keylen,hashv) \ do { \ unsigned _sx_i; \ const unsigned char *_hs_key = (const unsigned char*)(key); \ hashv = 0; \ for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ } \ } while (0) /* FNV-1a variation */ #define HASH_FNV(key,keylen,hashv) \ do { \ unsigned _fn_i; \ const unsigned char *_hf_key = (const unsigned char*)(key); \ (hashv) = 2166136261U; \ for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ hashv = hashv ^ _hf_key[_fn_i]; \ hashv = hashv * 16777619U; \ } \ } while (0) #define HASH_OAT(key,keylen,hashv) \ do { \ unsigned _ho_i; \ const unsigned char *_ho_key=(const unsigned char*)(key); \ hashv = 0; \ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ hashv += _ho_key[_ho_i]; \ hashv += (hashv << 10); \ hashv ^= (hashv >> 6); \ } \ hashv += (hashv << 3); \ hashv ^= (hashv >> 11); \ hashv += (hashv << 15); \ } while (0) #define HASH_JEN_MIX(a,b,c) \ do { \ a -= b; a -= c; a ^= ( c >> 13 ); \ b -= c; b -= a; b ^= ( a << 8 ); \ c -= a; c -= b; c ^= ( b >> 13 ); \ a -= b; a -= c; a ^= ( c >> 12 ); \ b -= c; b -= a; b ^= ( a << 16 ); \ c -= a; c -= b; c ^= ( b >> 5 ); \ a -= b; a -= c; a ^= ( c >> 3 ); \ b -= c; b -= a; b ^= ( a << 10 ); \ c -= a; c -= b; c ^= ( b >> 15 ); \ } while (0) #define HASH_JEN(key,keylen,hashv) \ do { \ unsigned _hj_i,_hj_j,_hj_k; \ unsigned const char *_hj_key=(unsigned const char*)(key); \ hashv = 0xfeedbeefu; \ _hj_i = _hj_j = 0x9e3779b9u; \ _hj_k = (unsigned)(keylen); \ while (_hj_k >= 12U) { \ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + ( (unsigned)_hj_key[2] << 16 ) \ + ( (unsigned)_hj_key[3] << 24 ) ); \ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + ( (unsigned)_hj_key[6] << 16 ) \ + ( (unsigned)_hj_key[7] << 24 ) ); \ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + ( (unsigned)_hj_key[10] << 16 ) \ + ( (unsigned)_hj_key[11] << 24 ) ); \ \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ \ _hj_key += 12; \ _hj_k -= 12U; \ } \ hashv += (unsigned)(keylen); \ switch ( _hj_k ) { \ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ default: ; \ } \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ } while (0) /* The Paul Hsieh hash function */ #undef get16bits #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) #define get16bits(d) (*((const uint16_t *) (d))) #endif #if !defined (get16bits) #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif #define HASH_SFH(key,keylen,hashv) \ do { \ unsigned const char *_sfh_key=(unsigned const char*)(key); \ uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ \ unsigned _sfh_rem = _sfh_len & 3U; \ _sfh_len >>= 2; \ hashv = 0xcafebabeu; \ \ /* Main loop */ \ for (;_sfh_len > 0U; _sfh_len--) { \ hashv += get16bits (_sfh_key); \ _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ hashv = (hashv << 16) ^ _sfh_tmp; \ _sfh_key += 2U*sizeof (uint16_t); \ hashv += hashv >> 11; \ } \ \ /* Handle end cases */ \ switch (_sfh_rem) { \ case 3: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 16; \ hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ hashv += hashv >> 11; \ break; \ case 2: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 11; \ hashv += hashv >> 17; \ break; \ case 1: hashv += *_sfh_key; \ hashv ^= hashv << 10; \ hashv += hashv >> 1; \ break; \ default: ; \ } \ \ /* Force "avalanching" of final 127 bits */ \ hashv ^= hashv << 3; \ hashv += hashv >> 5; \ hashv ^= hashv << 4; \ hashv += hashv >> 17; \ hashv ^= hashv << 25; \ hashv += hashv >> 6; \ } while (0) /* iterate over items in a known bucket to find desired item */ #define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ do { \ if ((head).hh_head != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ } else { \ (out) = NULL; \ } \ while ((out) != NULL) { \ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ break; \ } \ } \ if ((out)->hh.hh_next != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ } else { \ (out) = NULL; \ } \ } \ } while (0) /* add an item to a bucket */ #define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ do { \ UT_hash_bucket *_ha_head = &(head); \ _ha_head->count++; \ (addhh)->hh_next = _ha_head->hh_head; \ (addhh)->hh_prev = NULL; \ if (_ha_head->hh_head != NULL) { \ _ha_head->hh_head->hh_prev = (addhh); \ } \ _ha_head->hh_head = (addhh); \ if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ && !(addhh)->tbl->noexpand) { \ HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ HASH_DEL_IN_BKT(head,addhh); \ } \ ) \ } \ } while (0) /* remove an item from a given bucket */ #define HASH_DEL_IN_BKT(head,delhh) \ do { \ UT_hash_bucket *_hd_head = &(head); \ _hd_head->count--; \ if (_hd_head->hh_head == (delhh)) { \ _hd_head->hh_head = (delhh)->hh_next; \ } \ if ((delhh)->hh_prev) { \ (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ } \ if ((delhh)->hh_next) { \ (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ } \ } while (0) /* Bucket expansion has the effect of doubling the number of buckets * and redistributing the items into the new buckets. Ideally the * items will distribute more or less evenly into the new buckets * (the extent to which this is true is a measure of the quality of * the hash function as it applies to the key domain). * * With the items distributed into more buckets, the chain length * (item count) in each bucket is reduced. Thus by expanding buckets * the hash keeps a bound on the chain length. This bounded chain * length is the essence of how a hash provides constant time lookup. * * The calculation of tbl->ideal_chain_maxlen below deserves some * explanation. First, keep in mind that we're calculating the ideal * maximum chain length based on the *new* (doubled) bucket count. * In fractions this is just n/b (n=number of items,b=new num buckets). * Since the ideal chain length is an integer, we want to calculate * ceil(n/b). We don't depend on floating point arithmetic in this * hash, so to calculate ceil(n/b) with integers we could write * * ceil(n/b) = (n/b) + ((n%b)?1:0) * * and in fact a previous version of this hash did just that. * But now we have improved things a bit by recognizing that b is * always a power of two. We keep its base 2 log handy (call it lb), * so now we can write this with a bit shift and logical AND: * * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) * */ #define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ do { \ unsigned _he_bkt; \ unsigned _he_bkt_i; \ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ if (!_he_new_buckets) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero(_he_new_buckets, \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ (tbl)->ideal_chain_maxlen = \ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ (tbl)->nonideal_items = 0; \ for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ while (_he_thh != NULL) { \ _he_hh_nxt = _he_thh->hh_next; \ HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ _he_newbkt = &(_he_new_buckets[_he_bkt]); \ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ (tbl)->nonideal_items++; \ if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ _he_newbkt->expand_mult++; \ } \ } \ _he_thh->hh_prev = NULL; \ _he_thh->hh_next = _he_newbkt->hh_head; \ if (_he_newbkt->hh_head != NULL) { \ _he_newbkt->hh_head->hh_prev = _he_thh; \ } \ _he_newbkt->hh_head = _he_thh; \ _he_thh = _he_hh_nxt; \ } \ } \ uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ (tbl)->num_buckets *= 2U; \ (tbl)->log2_num_buckets++; \ (tbl)->buckets = _he_new_buckets; \ (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ ((tbl)->ineff_expands+1U) : 0U; \ if ((tbl)->ineff_expands > 1U) { \ (tbl)->noexpand = 1; \ uthash_noexpand_fyi(tbl); \ } \ uthash_expand_fyi(tbl); \ } \ } while (0) /* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ /* Note that HASH_SORT assumes the hash handle name to be hh. * HASH_SRT was added to allow the hash handle name to be passed in. */ #define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) #define HASH_SRT(hh,head,cmpfcn) \ do { \ unsigned _hs_i; \ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ if (head != NULL) { \ _hs_insize = 1; \ _hs_looping = 1; \ _hs_list = &((head)->hh); \ while (_hs_looping != 0U) { \ _hs_p = _hs_list; \ _hs_list = NULL; \ _hs_tail = NULL; \ _hs_nmerges = 0; \ while (_hs_p != NULL) { \ _hs_nmerges++; \ _hs_q = _hs_p; \ _hs_psize = 0; \ for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ _hs_psize++; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ if (_hs_q == NULL) { \ break; \ } \ } \ _hs_qsize = _hs_insize; \ while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ if (_hs_psize == 0U) { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else if ((cmpfcn( \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ )) <= 0) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } \ if ( _hs_tail != NULL ) { \ _hs_tail->next = ((_hs_e != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ } else { \ _hs_list = _hs_e; \ } \ if (_hs_e != NULL) { \ _hs_e->prev = ((_hs_tail != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ } \ _hs_tail = _hs_e; \ } \ _hs_p = _hs_q; \ } \ if (_hs_tail != NULL) { \ _hs_tail->next = NULL; \ } \ if (_hs_nmerges <= 1U) { \ _hs_looping = 0; \ (head)->hh.tbl->tail = _hs_tail; \ DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ } \ _hs_insize *= 2U; \ } \ HASH_FSCK(hh, head, "HASH_SRT"); \ } \ } while (0) /* This function selects items from one hash into another hash. * The end result is that the selected items have dual presence * in both hashes. There is no copy of the items made; rather * they are added into the new hash through a secondary hash * hash handle that must be present in the structure. */ #define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ do { \ unsigned _src_bkt, _dst_bkt; \ void *_last_elt = NULL, *_elt; \ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ if ((src) != NULL) { \ for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ _src_hh != NULL; \ _src_hh = _src_hh->hh_next) { \ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ if (cond(_elt)) { \ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ _dst_hh->key = _src_hh->key; \ _dst_hh->keylen = _src_hh->keylen; \ _dst_hh->hashv = _src_hh->hashv; \ _dst_hh->prev = _last_elt; \ _dst_hh->next = NULL; \ if (_last_elt_hh != NULL) { \ _last_elt_hh->next = _elt; \ } \ if ((dst) == NULL) { \ DECLTYPE_ASSIGN(dst, _elt); \ HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ uthash_nonfatal_oom(_elt); \ (dst) = NULL; \ continue; \ } \ ) \ } else { \ _dst_hh->tbl = (dst)->hh_dst.tbl; \ } \ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ (dst)->hh_dst.tbl->num_items++; \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ _dst_hh->tbl = NULL; \ uthash_nonfatal_oom(_elt); \ continue; \ } \ ) \ HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ _last_elt = _elt; \ _last_elt_hh = _dst_hh; \ } \ } \ } \ } \ HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ } while (0) #define HASH_CLEAR(hh,head) \ do { \ if ((head) != NULL) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } \ } while (0) #define HASH_OVERHEAD(hh,head) \ (((head) != NULL) ? ( \ (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ sizeof(UT_hash_table) + \ (HASH_BLOOM_BYTELEN))) : 0U) #ifdef NO_DECLTYPE #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) #else #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) #endif /* obtain a count of items in the hash */ #define HASH_COUNT(head) HASH_CNT(hh,head) #define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) typedef struct UT_hash_bucket { struct UT_hash_handle *hh_head; unsigned count; /* expand_mult is normally set to 0. In this situation, the max chain length * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If * the bucket's chain exceeds this length, bucket expansion is triggered). * However, setting expand_mult to a non-zero value delays bucket expansion * (that would be triggered by additions to this particular bucket) * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. * (The multiplier is simply expand_mult+1). The whole idea of this * multiplier is to reduce bucket expansions, since they are expensive, in * situations where we know that a particular bucket tends to be overused. * It is better to let its chain length grow to a longer yet-still-bounded * value, than to do an O(n) bucket expansion too often. */ unsigned expand_mult; } UT_hash_bucket; /* random signature used only to find hash tables in external analysis */ #define HASH_SIGNATURE 0xa0111fe1u #define HASH_BLOOM_SIGNATURE 0xb12220f2u typedef struct UT_hash_table { UT_hash_bucket *buckets; unsigned num_buckets, log2_num_buckets; unsigned num_items; struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ /* in an ideal situation (all buckets used equally), no bucket would have * more than ceil(#items/#buckets) items. that's the ideal chain length. */ unsigned ideal_chain_maxlen; /* nonideal_items is the number of items in the hash whose chain position * exceeds the ideal chain maxlen. these items pay the penalty for an uneven * hash distribution; reaching them in a chain traversal takes >ideal steps */ unsigned nonideal_items; /* ineffective expands occur when a bucket doubling was performed, but * afterward, more than half the items in the hash had nonideal chain * positions. If this happens on two consecutive expansions we inhibit any * further expansion, as it's not helping; this happens when the hash * function isn't a good fit for the key domain. When expansion is inhibited * the hash will still work, albeit no longer in constant time. */ unsigned ineff_expands, noexpand; uint32_t signature; /* used only to find hash tables in external analysis */ #ifdef HASH_BLOOM uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ uint8_t *bloom_bv; uint8_t bloom_nbits; #endif } UT_hash_table; typedef struct UT_hash_handle { struct UT_hash_table *tbl; void *prev; /* prev element in app order */ void *next; /* next element in app order */ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ struct UT_hash_handle *hh_next; /* next hh in bucket order */ const void *key; /* ptr to enclosing struct's key */ unsigned keylen; /* enclosing struct's key len */ unsigned hashv; /* result of hash-fcn(key) */ } UT_hash_handle; #endif /* UTHASH_H */ ================================================ FILE: zh-hant/codes/c/utils/vector.h ================================================ /** * File: vector.h * Created Time: 2023-07-13 * Author: Zuoxun (845242523@qq.com)、Gonglja (glj0@outlook.com) */ #ifndef VECTOR_H #define VECTOR_H #ifdef __cplusplus extern "C" { #endif /* 定義向量型別 */ typedef struct vector { int size; // 當前向量的大小 int capacity; // 當前向量的容量 int depth; // 當前向量的深度 void **data; // 指向資料的指標陣列 } vector; /* 構造向量 */ vector *newVector() { vector *v = malloc(sizeof(vector)); v->size = 0; v->capacity = 4; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); return v; } /* 構造向量,指定大小、元素預設值 */ vector *_newVector(int size, void *elem, int elemSize) { vector *v = malloc(sizeof(vector)); v->size = size; v->capacity = size; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); for (int i = 0; i < size; i++) { void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[i] = tmp; } return v; } /* 析構向量 */ void delVector(vector *v) { if (v) { if (v->depth == 0) { return; } else if (v->depth == 1) { for (int i = 0; i < v->size; i++) { free(v->data[i]); } free(v); } else { for (int i = 0; i < v->size; i++) { delVector(v->data[i]); } v->depth--; } } } /* 新增元素(複製方式)到向量尾部 */ void vectorPushback(vector *v, void *elem, int elemSize) { if (v->size == v->capacity) { v->capacity *= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[v->size++] = tmp; } /* 從向量尾部彈出元素 */ void vectorPopback(vector *v) { if (v->size != 0) { free(v->data[v->size - 1]); v->size--; } } /* 清空向量 */ void vectorClear(vector *v) { delVector(v); v->size = 0; v->capacity = 4; v->depth = 1; v->data = malloc(v->capacity * sizeof(void *)); } /* 獲取向量的大小 */ int vectorSize(vector *v) { return v->size; } /* 獲取向量的尾元素 */ void *vectorBack(vector *v) { int n = v->size; return n > 0 ? v->data[n - 1] : NULL; } /* 獲取向量的頭元素 */ void *vectorFront(vector *v) { return v->size > 0 ? v->data[0] : NULL; } /* 獲取向量下標 pos 的元素 */ void *vectorAt(vector *v, int pos) { if (pos < 0 || pos >= v->size) { printf("vectorAt: out of range\n"); return NULL; } return v->data[pos]; } /* 設定向量下標 pos 的元素 */ void vectorSet(vector *v, int pos, void *elem, int elemSize) { if (pos < 0 || pos >= v->size) { printf("vectorSet: out of range\n"); return; } free(v->data[pos]); void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[pos] = tmp; } /* 向量擴容 */ void vectorExpand(vector *v) { v->capacity *= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } /* 向量縮容 */ void vectorShrink(vector *v) { v->capacity /= 2; v->data = realloc(v->data, v->capacity * sizeof(void *)); } /* 在向量下標 pos 處插入元素 */ void vectorInsert(vector *v, int pos, void *elem, int elemSize) { if (v->size == v->capacity) { vectorExpand(v); } for (int j = v->size; j > pos; j--) { v->data[j] = v->data[j - 1]; } void *tmp = malloc(sizeof(char) * elemSize); memcpy(tmp, elem, elemSize); v->data[pos] = tmp; v->size++; } /* 刪除向量下標 pos 處的元素 */ void vectorErase(vector *v, int pos) { if (v->size != 0) { free(v->data[pos]); for (int j = pos; j < v->size - 1; j++) { v->data[j] = v->data[j + 1]; } v->size--; } } /* 向量交換元素 */ void vectorSwap(vector *v, int i, int j) { void *tmp = v->data[i]; v->data[i] = v->data[j]; v->data[j] = tmp; } /* 向量是否為空 */ bool vectorEmpty(vector *v) { return v->size == 0; } /* 向量是否已滿 */ bool vectorFull(vector *v) { return v->size == v->capacity; } /* 向量是否相等 */ bool vectorEqual(vector *v1, vector *v2) { if (v1->size != v2->size) { printf("size not equal\n"); return false; } for (int i = 0; i < v1->size; i++) { void *a = v1->data[i]; void *b = v2->data[i]; if (memcmp(a, b, sizeof(a)) != 0) { printf("data %d not equal\n", i); return false; } } return true; } /* 對向量內部進行排序 */ void vectorSort(vector *v, int (*cmp)(const void *, const void *)) { qsort(v->data, v->size, sizeof(void *), cmp); } /* 列印函式, 需傳遞一個列印變數的函式進來 */ /* 當前僅支持列印深度為 1 的 vector */ void printVector(vector *v, void (*printFunc)(vector *v, void *p)) { if (v) { if (v->depth == 0) { return; } else if (v->depth == 1) { if(v->size == 0) { printf("\n"); return; } for (int i = 0; i < v->size; i++) { if (i == 0) { printf("["); } else if (i == v->size - 1) { printFunc(v, v->data[i]); printf("]\r\n"); break; } printFunc(v, v->data[i]); printf(","); } } else { for (int i = 0; i < v->size; i++) { printVector(v->data[i], printFunc); } v->depth--; } } } /* 當前僅支持列印深度為 2 的 vector */ void printVectorMatrix(vector *vv, void (*printFunc)(vector *v, void *p)) { printf("[\n"); for (int i = 0; i < vv->size; i++) { vector *v = (vector *)vv->data[i]; printf(" ["); for (int j = 0; j < v->size; j++) { printFunc(v, v->data[j]); if (j != v->size - 1) printf(","); } printf("],"); printf("\n"); } printf("]\n"); } #ifdef __cplusplus } #endif #endif // VECTOR_H ================================================ FILE: zh-hant/codes/c/utils/vertex.h ================================================ /** * File: vertex.h * Created Time: 2023-10-28 * Author: krahets (krahets@163.com) */ #ifndef VERTEX_H #define VERTEX_H #ifdef __cplusplus extern "C" { #endif /* 頂點結構體 */ typedef struct { int val; } Vertex; /* 建構子,初始化一個新節點 */ Vertex *newVertex(int val) { Vertex *vet; vet = (Vertex *)malloc(sizeof(Vertex)); vet->val = val; return vet; } /* 將值陣列轉換為頂點陣列 */ Vertex **valsToVets(int *vals, int size) { Vertex **vertices = (Vertex **)malloc(size * sizeof(Vertex *)); for (int i = 0; i < size; ++i) { vertices[i] = newVertex(vals[i]); } return vertices; } /* 將頂點陣列轉換為值陣列 */ int *vetsToVals(Vertex **vertices, int size) { int *vals = (int *)malloc(size * sizeof(int)); for (int i = 0; i < size; ++i) { vals[i] = vertices[i]->val; } return vals; } #ifdef __cplusplus } #endif #endif // VERTEX_H ================================================ FILE: zh-hant/codes/cpp/.gitignore ================================================ # Ignore all * # Unignore all with extensions !*.* # Unignore all dirs !*/ *.dSYM/ build/ ================================================ FILE: zh-hant/codes/cpp/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(hello_algo CXX) set(CMAKE_CXX_STANDARD 11) include_directories(./include) add_subdirectory(chapter_computational_complexity) add_subdirectory(chapter_array_and_linkedlist) add_subdirectory(chapter_stack_and_queue) add_subdirectory(chapter_hashing) add_subdirectory(chapter_tree) add_subdirectory(chapter_heap) add_subdirectory(chapter_graph) add_subdirectory(chapter_searching) add_subdirectory(chapter_sorting) add_subdirectory(chapter_divide_and_conquer) add_subdirectory(chapter_backtracking) add_subdirectory(chapter_dynamic_programming) add_subdirectory(chapter_greedy) ================================================ FILE: zh-hant/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt ================================================ add_executable(array array.cpp) add_executable(linked_list linked_list.cpp) add_executable(list list.cpp) add_executable(my_list my_list.cpp) ================================================ FILE: zh-hant/codes/cpp/chapter_array_and_linkedlist/array.cpp ================================================ /** * 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 以及之後的所有元素向後移動一位 for (int i = size - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 將 num 賦給 index 處的元素 nums[index] = num; } /* 刪除索引 index 處的元素 */ void remove(int *nums, int size, int index) { // 把索引 index 之後的所有元素向前移動一位 for (int i = index; i < size - 1; i++) { nums[i] = nums[i + 1]; } } /* 走訪陣列 */ void traverse(int *nums, int size) { int count = 0; // 透過索引走訪陣列 for (int i = 0; i < size; i++) { count += nums[i]; } } /* 在陣列中查詢指定元素 */ int find(int *nums, int size, int target) { for (int i = 0; i < size; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ int main() { /* 初始化陣列 */ int size = 5; int *arr = new int[size]; cout << "陣列 arr = "; printArray(arr, size); int *nums = new int[size]{1, 3, 2, 5, 4}; cout << "陣列 nums = "; printArray(nums, size); /* 隨機訪問 */ int randomNum = randomAccess(nums, size); cout << "在 nums 中獲取隨機元素 " << randomNum << endl; /* 長度擴展 */ int enlarge = 3; nums = extend(nums, size, enlarge); size += enlarge; cout << "將陣列長度擴展至 8 ,得到 nums = "; printArray(nums, size); /* 插入元素 */ insert(nums, size, 6, 3); cout << "在索引 3 處插入數字 6 ,得到 nums = "; printArray(nums, size); /* 刪除元素 */ remove(nums, size, 2); cout << "刪除索引 2 處的元素,得到 nums = "; printArray(nums, size); /* 走訪陣列 */ traverse(nums, size); /* 查詢元素 */ int index = find(nums, size, 3); cout << "在 nums 中查詢元素 3 ,得到索引 = " << index << endl; // 釋放記憶體 delete[] arr; delete[] nums; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp ================================================ /** * 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; } /* Driver Code */ 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 << "初始化的鏈結串列為" << endl; printLinkedList(n0); /* 插入節點 */ insert(n0, new ListNode(0)); cout << "插入節點後的鏈結串列為" << endl; printLinkedList(n0); /* 刪除節點 */ remove(n0); cout << "刪除節點後的鏈結串列為" << endl; printLinkedList(n0); /* 訪問節點 */ ListNode *node = access(n0, 3); cout << "鏈結串列中索引 3 處的節點的值 = " << node->val << endl; /* 查詢節點 */ int index = find(n0, 2); cout << "鏈結串列中值為 2 的節點的索引 = " << index << endl; // 釋放記憶體 freeMemoryLinkedList(n0); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_array_and_linkedlist/list.cpp ================================================ /** * File: list.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* 初始化串列 */ vector nums = {1, 3, 2, 5, 4}; cout << "串列 nums = "; printVector(nums); /* 訪問元素 */ int num = nums[1]; cout << "訪問索引 1 處的元素,得到 num = " << num << endl; /* 更新元素 */ nums[1] = 0; cout << "將索引 1 處的元素更新為 0 ,得到 nums = "; printVector(nums); /* 清空串列 */ nums.clear(); cout << "清空串列後 nums = "; printVector(nums); /* 在尾部新增元素 */ nums.push_back(1); nums.push_back(3); nums.push_back(2); nums.push_back(5); nums.push_back(4); cout << "新增元素後 nums = "; printVector(nums); /* 在中間插入元素 */ nums.insert(nums.begin() + 3, 6); cout << "在索引 3 處插入數字 6 ,得到 nums = "; printVector(nums); /* 刪除元素 */ nums.erase(nums.begin() + 3); cout << "刪除索引 3 處的元素,得到 nums = "; printVector(nums); /* 透過索引走訪串列 */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums[i]; } /* 直接走訪串列元素 */ count = 0; for (int x : nums) { count += x; } /* 拼接兩個串列 */ vector nums1 = {6, 8, 7, 10, 9}; nums.insert(nums.end(), nums1.begin(), nums1.end()); cout << "將串列 nums1 拼接到 nums 之後,得到 nums = "; printVector(nums); /* 排序串列 */ sort(nums.begin(), nums.end()); cout << "排序串列後 nums = "; printVector(nums); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_array_and_linkedlist/my_list.cpp ================================================ /** * 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("索引越界"); return arr[index]; } /* 更新元素 */ void set(int index, int num) { if (index < 0 || index >= size()) throw out_of_range("索引越界"); 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("索引越界"); // 元素數量超出容量時,觸發擴容機制 if (size() == capacity()) extendCapacity(); // 將索引 index 以及之後的元素都向後移動一位 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("索引越界"); int num = arr[index]; // 將索引 index 之後的元素都向前移動一位 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; } }; /* Driver Code */ int main() { /* 初始化串列 */ MyList *nums = new MyList(); /* 在尾部新增元素 */ nums->add(1); nums->add(3); nums->add(2); nums->add(5); nums->add(4); cout << "串列 nums = "; vector vec = nums->toVector(); printVector(vec); cout << "容量 = " << nums->capacity() << " ,長度 = " << nums->size() << endl; /* 在中間插入元素 */ nums->insert(3, 6); cout << "在索引 3 處插入數字 6 ,得到 nums = "; vec = nums->toVector(); printVector(vec); /* 刪除元素 */ nums->remove(3); cout << "刪除索引 3 處的元素,得到 nums = "; vec = nums->toVector(); printVector(vec); /* 訪問元素 */ int num = nums->get(1); cout << "訪問索引 1 處的元素,得到 num = " << num << endl; /* 更新元素 */ nums->set(1, 0); cout << "將索引 1 處的元素更新為 0 ,得到 nums = "; vec = nums->toVector(); printVector(vec); /* 測試擴容機制 */ for (int i = 0; i < 10; i++) { // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 nums->add(i); } cout << "擴容後的串列 nums = "; vec = nums->toVector(); printVector(vec); cout << "容量 = " << nums->capacity() << " ,長度 = " << nums->size() << endl; // 釋放記憶體 delete nums; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_backtracking/CMakeLists.txt ================================================ add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.cpp) add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.cpp) add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.cpp) add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.cpp) add_executable(permutations_i permutations_i.cpp) add_executable(permutations_ii permutations_ii.cpp) add_executable(n_queens n_queens.cpp) add_executable(subset_sum_i_naive subset_sum_i_naive.cpp) add_executable(subset_sum_i subset_sum_i.cpp) add_executable(subset_sum_ii subset_sum_ii.cpp) ================================================ FILE: zh-hant/codes/cpp/chapter_backtracking/n_queens.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_backtracking/permutations_i.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector nums = {1, 2, 3}; vector> res = permutationsI(nums); cout << "輸入陣列 nums = "; printVector(nums); cout << "所有排列 res = "; printVectorMatrix(res); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_backtracking/permutations_ii.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector nums = {1, 1, 2}; vector> res = permutationsII(nums); cout << "輸入陣列 nums = "; printVector(nums); cout << "所有排列 res = "; printVectorMatrix(res); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp ================================================ /** * File: preorder_traversal_i_compact.cpp * Created Time: 2023-04-16 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" vector res; /* 前序走訪:例題一 */ void preOrder(TreeNode *root) { if (root == nullptr) { return; } if (root->val == 7) { // 記錄解 res.push_back(root); } preOrder(root->left); preOrder(root->right); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n初始化二元樹" << endl; printTree(root); // 前序走訪 preOrder(root); cout << "\n輸出所有值為 7 的節點" << endl; vector vals; for (TreeNode *node : res) { vals.push_back(node->val); } printVector(vals); } ================================================ FILE: zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp ================================================ /** * File: preorder_traversal_ii_compact.cpp * Created Time: 2023-04-16 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" vector path; vector> res; /* 前序走訪:例題二 */ 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(); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n初始化二元樹" << endl; printTree(root); // 前序走訪 preOrder(root); cout << "\n輸出所有根節點到節點 7 的路徑" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp ================================================ /** * File: preorder_traversal_iii_compact.cpp * Created Time: 2023-04-16 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" vector path; vector> res; /* 前序走訪:例題三 */ 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(); } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n初始化二元樹" << endl; printTree(root); // 前序走訪 preOrder(root); cout << "\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp ================================================ /** * 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(); } /* 回溯演算法:例題三 */ void backtrack(vector &state, vector &choices, vector> &res) { // 檢查是否為解 if (isSolution(state)) { // 記錄解 recordSolution(state, res); } // 走訪所有選擇 for (TreeNode *choice : choices) { // 剪枝:檢查選擇是否合法 if (isValid(state, choice)) { // 嘗試:做出選擇,更新狀態 makeChoice(state, choice); // 進行下一輪選擇 vector nextChoices{choice->left, choice->right}; backtrack(state, nextChoices, res); // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state, choice); } } } /* Driver Code */ int main() { TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n初始化二元樹" << endl; printTree(root); // 回溯演算法 vector state; vector choices = {root}; vector> res; backtrack(state, choices, res); cout << "\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { vals.push_back(node->val); } printVector(vals); } } ================================================ FILE: zh-hant/codes/cpp/chapter_backtracking/subset_sum_i.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp ================================================ /** * 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 (size_t 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; } /* Driver Code */ 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); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_backtracking/subset_sum_ii.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_computational_complexity/CMakeLists.txt ================================================ add_executable(iteration iteration.cpp) add_executable(recursion recursion.cpp) add_executable(space_complexity space_complexity.cpp) add_executable(time_complexity time_complexity.cpp) add_executable(worst_best_time_complexity worst_best_time_complexity.cpp) ================================================ FILE: zh-hant/codes/cpp/chapter_computational_complexity/iteration.cpp ================================================ /** * 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 迴圈(兩次更新) */ int whileLoopII(int n) { int res = 0; int i = 1; // 初始化條件變數 // 迴圈求和 1, 4, 10, ... while (i <= n) { res += i; // 更新條件變數 i++; i *= 2; } return res; } /* 雙層 for 迴圈 */ string nestedForLoop(int n) { 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(); } /* Driver Code */ 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 迴圈(兩次更新)求和結果 res = " << res << endl; string resStr = nestedForLoop(n); cout << "\n雙層 for 迴圈的走訪結果 " << resStr << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_computational_complexity/recursion.cpp ================================================ /** * 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; } /* Driver Code */ 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費波那契數列的第 " << n << " 項為 " << res << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_computational_complexity/space_complexity.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int n = 5; // 常數階 constant(n); // 線性階 linear(n); linearRecur(n); // 平方階 quadratic(n); quadraticRecur(n); // 指數階 TreeNode *root = buildTree(n); printTree(root); // 釋放記憶體 freeMemoryTree(root); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_computational_complexity/time_complexity.cpp ================================================ /** * 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; // 細胞每輪一分為二,形成數列 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; } /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp ================================================ /** * 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; } /* Driver Code */ int main() { for (int i = 0; i < 1000; i++) { int n = 100; vector nums = randomNumbers(n); int index = findOne(nums); cout << "\n陣列 [ 1, 2, ..., n ] 被打亂後 = "; printVector(nums); cout << "數字 1 的索引為 " << index << endl; } return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt ================================================ add_executable(binary_search_recur binary_search_recur.cpp) add_executable(build_tree build_tree.cpp) add_executable(hanota hanota.cpp) ================================================ FILE: zh-hant/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp ================================================ /** * 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) / 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); } /* Driver Code */ int main() { int target = 6; vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; // 二分搜尋(雙閉區間) int index = binarySearch(nums, target); cout << "目標元素 6 的索引 = " << index << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_divide_and_conquer/build_tree.cpp ================================================ /** * 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) { // 初始化雜湊表,儲存 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; } /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_divide_and_conquer/hanota.cpp ================================================ /** * 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 只剩下一個圓盤,則直接將其移到 tar if (i == 1) { move(src, tar); return; } // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf dfs(i - 1, src, tar, buf); // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar move(src, tar); // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar dfs(i - 1, buf, src, tar); } /* 求解河內塔問題 */ void solveHanota(vector &A, vector &B, vector &C) { int n = A.size(); // 將 A 頂部 n 個圓盤藉助 B 移到 C dfs(n, A, B, C); } /* Driver Code */ int main() { // 串列尾部是柱子頂部 vector A = {5, 4, 3, 2, 1}; vector B = {}; vector C = {}; cout << "初始狀態下:\n"; cout << "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; } ================================================ FILE: zh-hant/codes/cpp/chapter_dynamic_programming/CMakeLists.txt ================================================ add_executable(climbing_stairs_backtrack climbing_stairs_backtrack.cpp) add_executable(climbing_stairs_dfs climbing_stairs_dfs.cpp) add_executable(climbing_stairs_dfs_mem climbing_stairs_dfs_mem.cpp) add_executable(climbing_stairs_dp climbing_stairs_dp.cpp) add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.cpp) add_executable(min_path_sum min_path_sum.cpp) add_executable(unbounded_knapsack unbounded_knapsack.cpp) add_executable(coin_change coin_change.cpp) add_executable(coin_change_ii coin_change_ii.cpp) add_executable(edit_distance edit_distance.cpp) ================================================ FILE: zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp ================================================ /** * 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]; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsBacktrack(n); cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp ================================================ /** * 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]; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsConstraintDP(n); cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp ================================================ /** * 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); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFS(n); cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp ================================================ /** * 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); } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDFSMem(n); cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int n = 9; int res = climbingStairsDP(n); cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; res = climbingStairsDPComp(n); cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_dynamic_programming/coin_change.cpp ================================================ /** * 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; } /* Driver code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp ================================================ /** * 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 這兩種方案之和 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 這兩種方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_dynamic_programming/edit_distance.cpp ================================================ /** * 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; // 若兩字元相等,則直接跳過此兩字元 if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 int insert = editDistanceDFS(s, t, i, j - 1); int del = editDistanceDFS(s, t, i - 1, j); int replace = editDistanceDFS(s, t, i - 1, j - 1); // 返回最少編輯步數 return min(min(insert, del), replace) + 1; } /* 編輯距離:記憶化搜尋 */ int editDistanceDFSMem(string s, string t, vector> &mem, int i, int j) { // 若 s 和 t 都為空,則返回 0 if (i == 0 && j == 0) return 0; // 若 s 為空,則返回 t 長度 if (i == 0) return j; // 若 t 為空,則返回 s 長度 if (j == 0) return i; // 若已有記錄,則直接返回之 if (mem[i][j] != -1) return mem[i][j]; // 若兩字元相等,則直接跳過此兩字元 if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 int insert = editDistanceDFSMem(s, t, mem, i, j - 1); int del = editDistanceDFSMem(s, t, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 記錄並返回最少編輯步數 mem[i][j] = min(min(insert, del), replace) + 1; return mem[i][j]; } /* 編輯距離:動態規劃 */ int editDistanceDP(string s, string t) { int n = s.length(), m = t.length(); vector> dp(n + 1, vector(m + 1, 0)); // 狀態轉移:首行首列 for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // 狀態轉移:其餘行和列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // 若兩字元相等,則直接跳過此兩字元 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* 編輯距離:空間最佳化後的動態規劃 */ int editDistanceDPComp(string s, string t) { int n = s.length(), m = t.length(); vector dp(m + 1, 0); // 狀態轉移:首行 for (int j = 1; j <= m; j++) { dp[j] = j; } // 狀態轉移:其餘行 for (int i = 1; i <= n; i++) { // 狀態轉移:首列 int leftup = dp[0]; // 暫存 dp[i-1, j-1] dp[0] = i; // 狀態轉移:其餘列 for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 若兩字元相等,則直接跳過此兩字元 dp[j] = leftup; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 更新為下一輪的 dp[i-1, j-1] } } return dp[m]; } /* Driver Code */ int main() { string s = "bag"; string t = "pack"; int n = s.length(), m = t.length(); // 暴力搜尋 int res = editDistanceDFS(s, t, n, m); cout << "將 " << s << " 更改為 " << t << " 最少需要編輯 " << res << " 步\n"; // 記憶化搜尋 vector> mem(n + 1, vector(m + 1, -1)); res = editDistanceDFSMem(s, t, mem, n, m); cout << "將 " << s << " 更改為 " << t << " 最少需要編輯 " << res << " 步\n"; // 動態規劃 res = editDistanceDP(s, t); cout << "將 " << s << " 更改為 " << t << " 最少需要編輯 " << res << " 步\n"; // 空間最佳化後的動態規劃 res = editDistanceDPComp(s, t); cout << "將 " << s << " 更改為 " << t << " 最少需要編輯 " << res << " 步\n"; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_dynamic_programming/knapsack.cpp ================================================ #include #include #include using namespace std; /* 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]; // 返回兩種方案中價值更大的那一個 return max(no, yes); } /* 0-1 背包:記憶化搜尋 */ int knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i == 0 || c == 0) { return 0; } // 若已有記錄,則直接返回 if (mem[i][c] != -1) { return mem[i][c]; } // 若超過背包容量,則只能選擇不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 int no = knapsackDFSMem(wgt, val, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 記錄並返回兩種方案中價值更大的那一個 mem[i][c] = max(no, yes); return mem[i][c]; } /* 0-1 背包:動態規劃 */ int knapsackDP(vector &wgt, vector &val, int cap) { int n = wgt.size(); // 初始化 dp 表 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]; } /* Driver Code */ int main() { vector wgt = {10, 20, 30, 40, 50}; vector val = {50, 120, 150, 210, 240}; int cap = 50; int n = wgt.size(); // 暴力搜尋 int res = knapsackDFS(wgt, val, n, cap); cout << "不超過背包容量的最大物品價值為 " << res << endl; // 記憶化搜尋 vector> mem(n + 1, vector(cap + 1, -1)); res = knapsackDFSMem(wgt, val, mem, n, cap); cout << "不超過背包容量的最大物品價值為 " << res << endl; // 動態規劃 res = knapsackDP(wgt, val, cap); cout << "不超過背包容量的最大物品價值為 " << res << endl; // 空間最佳化後的動態規劃 res = knapsackDPComp(wgt, val, cap); cout << "不超過背包容量的最大物品價值為 " << res << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp ================================================ /** * 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; } /* Driver Code */ int main() { vector cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; cout << "輸入樓梯的代價串列為 "; printVector(cost); int res = minCostClimbingStairsDP(cost); cout << "爬完樓梯的最低代價為 " << res << endl; res = minCostClimbingStairsDPComp(cost); cout << "爬完樓梯的最低代價為 " << res << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp ================================================ /** * 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) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; } /* 最小路徑和:記憶化搜尋 */ int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) { // 若為左上角單元格,則終止搜尋 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 || j < 0) { return INT_MAX; } // 若已有記錄,則直接返回 if (mem[i][j] != -1) { return mem[i][j]; } // 左邊和上邊單元格的最小路徑代價 int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // 記錄並返回左上角到 (i, j) 的最小路徑代價 mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; return mem[i][j]; } /* 最小路徑和:動態規劃 */ int minPathSumDP(vector> &grid) { int n = grid.size(), m = grid[0].size(); // 初始化 dp 表 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 minPathSumDPComp(vector> &grid) { int n = grid.size(), m = grid[0].size(); // 初始化 dp 表 vector dp(m); // 狀態轉移:首行 dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 狀態轉移:其餘行 for (int i = 1; i < n; i++) { // 狀態轉移:首列 dp[0] = dp[0] + grid[i][0]; // 狀態轉移:其餘列 for (int j = 1; j < m; j++) { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ int main() { vector> grid = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; int n = grid.size(), m = grid[0].size(); // 暴力搜尋 int res = minPathSumDFS(grid, n - 1, m - 1); cout << "從左上角到右下角的最小路徑和為 " << res << endl; // 記憶化搜尋 vector> mem(n, vector(m, -1)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); cout << "從左上角到右下角的最小路徑和為 " << res << endl; // 動態規劃 res = minPathSumDP(grid); cout << "從左上角到右下角的最小路徑和為 " << res << endl; // 空間最佳化後的動態規劃 res = minPathSumDPComp(grid); cout << "從左上角到右下角的最小路徑和為 " << res << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp ================================================ /** * 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]; } /* Driver code */ int main() { vector wgt = {1, 2, 3}; vector val = {5, 11, 15}; int cap = 4; // 動態規劃 int res = unboundedKnapsackDP(wgt, val, cap); cout << "不超過背包容量的最大物品價值為 " << res << endl; // 空間最佳化後的動態規劃 res = unboundedKnapsackDPComp(wgt, val, cap); cout << "不超過背包容量的最大物品價值為 " << res << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_graph/CMakeLists.txt ================================================ add_executable(graph_bfs graph_bfs.cpp) add_executable(graph_dfs graph_dfs.cpp) # add_executable(graph_adjacency_list graph_adjacency_list.cpp) add_executable(graph_adjacency_list_test graph_adjacency_list_test.cpp) add_executable(graph_adjacency_matrix graph_adjacency_matrix.cpp) ================================================ FILE: zh-hant/codes/cpp/chapter_graph/graph_adjacency_list.cpp ================================================ /** * 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: // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 unordered_map> adjList; /* 在 vector 中刪除指定節點 */ 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("不存在頂點"); // 新增邊 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("不存在頂點"); // 刪除邊 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("不存在頂點"); // 在鄰接表中刪除頂點 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 ================================================ FILE: zh-hant/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp ================================================ /** * File: graph_adjacency_list_test.cpp * Created Time: 2023-02-09 * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) */ #include "./graph_adjacency_list.cpp" /* Driver Code */ int main() { /* 初始化無向圖 */ vector v = valsToVets(vector{1, 3, 2, 5, 4}); vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; GraphAdjList graph(edges); cout << "\n初始化後,圖為" << endl; graph.print(); /* 新增邊 */ // 頂點 1, 2 即 v[0], v[2] graph.addEdge(v[0], v[2]); cout << "\n新增邊 1-2 後,圖為" << endl; graph.print(); /* 刪除邊 */ // 頂點 1, 3 即 v[0], v[1] graph.removeEdge(v[0], v[1]); cout << "\n刪除邊 1-3 後,圖為" << endl; graph.print(); /* 新增頂點 */ Vertex *v5 = new Vertex(6); graph.addVertex(v5); cout << "\n新增頂點 6 後,圖為" << endl; graph.print(); /* 刪除頂點 */ // 頂點 3 即 v[1] graph.removeVertex(v[1]); cout << "\n刪除頂點 3 後,圖為" << endl; graph.print(); // 釋放記憶體 for (Vertex *vet : v) { delete vet; } return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp ================================================ /** * 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); } // 新增邊 // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 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("頂點不存在"); } // 在頂點串列中移除索引 index 的頂點 vertices.erase(vertices.begin() + index); // 在鄰接矩陣中刪除索引 index 的行 adjMat.erase(adjMat.begin() + index); // 在鄰接矩陣中刪除索引 index 的列 for (vector &row : adjMat) { row.erase(row.begin() + index); } } /* 新增邊 */ // 參數 i, j 對應 vertices 元素索引 void addEdge(int i, int j) { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw out_of_range("頂點不存在"); } // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) adjMat[i][j] = 1; adjMat[j][i] = 1; } /* 刪除邊 */ // 參數 i, j 對應 vertices 元素索引 void removeEdge(int i, int j) { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw out_of_range("頂點不存在"); } adjMat[i][j] = 0; adjMat[j][i] = 0; } /* 列印鄰接矩陣 */ void print() { cout << "頂點串列 = "; printVector(vertices); cout << "鄰接矩陣 =" << endl; printVectorMatrix(adjMat); } }; /* Driver Code */ int main() { /* 初始化無向圖 */ // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 vector vertices = {1, 3, 2, 5, 4}; vector> edges = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; GraphAdjMat graph(vertices, edges); 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_graph/graph_bfs.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_graph/graph_dfs.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_greedy/CMakeLists.txt ================================================ add_executable(coin_change_greedy coin_change_greedy.cpp) add_executable(fractional_knapsack fractional_knapsack.cpp) add_executable(max_capacity max_capacity.cpp) ================================================ FILE: zh-hant/codes/cpp/chapter_greedy/coin_change_greedy.cpp ================================================ /** * 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) { // 假設 coins 串列有序 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; } /* Driver Code */ int main() { // 貪婪:能夠保證找到全域性最優解 vector coins = {1, 5, 10, 20, 50, 100}; int amt = 186; int res = coinChangeGreedy(coins, amt); cout << "\ncoins = "; printVector(coins); cout << "amt = " << amt << endl; cout << "湊到 " << amt << " 所需的最少硬幣數量為 " << res << endl; // 貪婪:無法保證找到全域性最優解 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_greedy/fractional_knapsack.cpp ================================================ /** * 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) { // 建立物品串列,包含兩個屬性:重量、價值 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; } /* Driver Code */ int main() { vector wgt = {10, 20, 30, 40, 50}; vector val = {50, 120, 150, 210, 240}; int cap = 50; // 貪婪演算法 double res = fractionalKnapsack(wgt, val, cap); cout << "不超過背包容量的最大物品價值為 " << res << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_greedy/max_capacity.cpp ================================================ /** * 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; // 迴圈貪婪選擇,直至兩板相遇 while (i < j) { // 更新最大容量 int cap = min(ht[i], ht[j]) * (j - i); res = max(res, cap); // 向內移動短板 if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ int main() { vector ht = {3, 8, 5, 2, 7, 7, 3, 4}; // 貪婪演算法 int res = maxCapacity(ht); cout << "最大容量為 " << res << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_greedy/max_product_cutting.cpp ================================================ /** * 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); } /* Driver Code */ int main() { int n = 58; // 貪婪演算法 int res = maxProductCutting(n); cout << "最大切分乘積為" << res << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_hashing/CMakeLists.txt ================================================ add_executable(hash_map hash_map.cpp) add_executable(array_hash_map_test array_hash_map_test.cpp) add_executable(hash_map_chaining hash_map_chaining.cpp) add_executable(hash_map_open_addressing hash_map_open_addressing.cpp) add_executable(simple_hash simple_hash.cpp) add_executable(built_in_hash built_in_hash.cpp) ================================================ FILE: zh-hant/codes/cpp/chapter_hashing/array_hash_map.cpp ================================================ /** * 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 ================================================ FILE: zh-hant/codes/cpp/chapter_hashing/array_hash_map_test.cpp ================================================ /** * File: array_hash_map_test.cpp * Created Time: 2022-12-14 * Author: msk397 (machangxinq@gmail.com) */ #include "./array_hash_map.cpp" /* Driver Code */ int main() { /* 初始化雜湊表 */ ArrayHashMap map = ArrayHashMap(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(12836, "小哈"); map.put(15937, "小囉"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鴨"); cout << "\n新增完成後,雜湊表為\nKey -> Value" << endl; map.print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value string name = map.get(15937); cout << "\n輸入學號 15937 ,查詢到姓名 " << name << endl; /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(10583); cout << "\n刪除 10583 後,雜湊表為\nKey -> Value" << endl; map.print(); /* 走訪雜湊表 */ cout << "\n走訪鍵值對 Key->Value" << endl; for (auto kv : map.pairSet()) { cout << kv->key << " -> " << kv->val << endl; } cout << "\n單獨走訪鍵 Key" << endl; for (auto key : map.keySet()) { cout << key << endl; } cout << "\n單獨走訪值 Value" << endl; for (auto val : map.valueSet()) { cout << val << endl; } return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_hashing/built_in_hash.cpp ================================================ /** * File: built_in_hash.cpp * Created Time: 2023-06-21 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { int num = 3; size_t hashNum = hash()(num); cout << "整數 " << num << " 的雜湊值為 " << hashNum << "\n"; bool bol = true; size_t hashBol = hash()(bol); cout << "布林量 " << bol << " 的雜湊值為 " << hashBol << "\n"; double dec = 3.14159; size_t hashDec = hash()(dec); cout << "小數 " << dec << " 的雜湊值為 " << hashDec << "\n"; string str = "Hello 演算法"; size_t hashStr = hash()(str); cout << "字串 " << str << " 的雜湊值為 " << hashStr << "\n"; // 在 C++ 中,內建 std:hash() 僅提供基本資料型別的雜湊值計算 // 陣列、物件的雜湊值計算需要自行實現 } ================================================ FILE: zh-hant/codes/cpp/chapter_hashing/hash_map.cpp ================================================ /** * File: hash_map.cpp * Created Time: 2022-12-14 * Author: msk397 (machangxinq@gmail.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* 初始化雜湊表 */ unordered_map map; /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map[12836] = "小哈"; map[15937] = "小囉"; map[16750] = "小算"; map[13276] = "小法"; map[10583] = "小鴨"; cout << "\n新增完成後,雜湊表為\nKey -> Value" << endl; printHashMap(map); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value string name = map[15937]; cout << "\n輸入學號 15937 ,查詢到姓名 " << name << endl; /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.erase(10583); cout << "\n刪除 10583 後,雜湊表為\nKey -> Value" << endl; printHashMap(map); /* 走訪雜湊表 */ cout << "\n走訪鍵值對 Key->Value" << endl; for (auto kv : map) { cout << kv.first << " -> " << kv.second << endl; } cout << "\n使用迭代器走訪 Key->Value" << endl; for (auto iter = map.begin(); iter != map.end(); iter++) { cout << iter->first << "->" << iter->second << endl; } return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_hashing/hash_map_chaining.cpp ================================================ /** * 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); // 走訪桶,若找到 key ,則返回對應 val for (Pair *pair : buckets[index]) { if (pair->key == key) { return pair->val; } } // 若未找到 key ,則返回空字串 return ""; } /* 新增操作 */ void put(int key, string val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for (Pair *pair : buckets[index]) { if (pair->key == key) { pair->val = val; return; } } // 若無該 key ,則將鍵值對新增至尾部 buckets[index].push_back(new Pair(key, val)); size++; } /* 刪除操作 */ 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"; } } }; /* Driver Code */ int main() { /* 初始化雜湊表 */ HashMapChaining map = HashMapChaining(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(12836, "小哈"); map.put(15937, "小囉"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鴨"); cout << "\n新增完成後,雜湊表為\nKey -> Value" << endl; map.print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value string name = map.get(13276); cout << "\n輸入學號 13276 ,查詢到姓名 " << name << endl; /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(12836); cout << "\n刪除 12836 後,雜湊表為\nKey -> Value" << endl; map.print(); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp ================================================ /** * 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; } } } }; /* Driver Code */ int main() { // 初始化雜湊表 HashMapOpenAddressing hashmap; // 新增操作 // 在雜湊表中新增鍵值對 (key, val) hashmap.put(12836, "小哈"); hashmap.put(15937, "小囉"); hashmap.put(16750, "小算"); hashmap.put(13276, "小法"); hashmap.put(10583, "小鴨"); cout << "\n新增完成後,雜湊表為\nKey -> Value" << endl; hashmap.print(); // 查詢操作 // 向雜湊表中輸入鍵 key ,得到值 val string name = hashmap.get(13276); cout << "\n輸入學號 13276 ,查詢到姓名 " << name << endl; // 刪除操作 // 在雜湊表中刪除鍵值對 (key, val) hashmap.remove(16750); cout << "\n刪除 16750 後,雜湊表為\nKey -> Value" << endl; hashmap.print(); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_hashing/simple_hash.cpp ================================================ /** * 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; } /* 互斥或雜湊 */ 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; } /* Driver Code */ int main() { string key = "Hello 演算法"; int hash = addHash(key); cout << "加法雜湊值為 " << hash << endl; hash = mulHash(key); cout << "乘法雜湊值為 " << hash << endl; hash = xorHash(key); cout << "互斥或雜湊值為 " << hash << endl; hash = rotHash(key); cout << "旋轉雜湊值為 " << hash << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_heap/CMakeLists.txt ================================================ add_executable(heap heap.cpp) add_executable(my_heap my_heap.cpp) add_executable(top_k top_k.cpp) ================================================ FILE: zh-hant/codes/cpp/chapter_heap/heap.cpp ================================================ /** * 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); } /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_heap/my_heap.cpp ================================================ /** * 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; // 交換兩節點 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("堆積為空"); } // 交換根節點與最右葉節點(交換首元素與尾元素) 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); } }; /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_heap/top_k.cpp ================================================ /** * 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; } // Driver Code 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_searching/CMakeLists.txt ================================================ add_executable(binary_search binary_search.cpp) add_executable(binary_search_insertion binary_search_insertion.cpp) add_executable(binary_search_edge binary_search_edge.cpp) add_executable(two_sum two_sum.cpp) ================================================ FILE: zh-hant/codes/cpp/chapter_searching/binary_search.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int target = 6; vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; /* 二分搜尋(雙閉區間) */ int index = binarySearch(nums, target); cout << "目標元素 6 的索引 = " << index << endl; /* 二分搜尋(左閉右開區間) */ index = binarySearchLCRO(nums, target); cout << "目標元素 6 的索引 = " << index << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_searching/binary_search_edge.cpp ================================================ /** * 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; // target 在區間 [m+1, j] 中 } else { j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 } } // 返回插入點 i return i; } /* 二分搜尋最左一個 target */ 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; } /* 二分搜尋最右一個 target */ int binarySearchRightEdge(vector &nums, int target) { // 轉化為查詢最左一個 target + 1 int i = binarySearchInsertion(nums, target + 1); // j 指向最右一個 target ,i 指向首個大於 target 的元素 int j = i - 1; // 未找到 target ,返回 -1 if (j == -1 || nums[j] != target) { return -1; } // 找到 target ,返回索引 j return j; } /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_searching/binary_search_insertion.cpp ================================================ /** * 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; // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在區間 [i, m-1] 中 } else { return m; // 找到 target ,返回插入點 m } } // 未找到 target ,返回插入點 i return i; } /* 二分搜尋插入點(存在重複元素) */ int binarySearchInsertion(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; // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在區間 [i, m-1] 中 } else { j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 } } // 返回插入點 i return i; } /* Driver Code */ int main() { // 無重複元素的陣列 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_searching/hashing_search.cpp ================================================ /** * 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) { // 雜湊表的 key: 目標元素,value: 索引 // 若雜湊表中無此 key ,返回 -1 if (map.find(target) == map.end()) return -1; return map[target]; } /* 雜湊查詢(鏈結串列) */ ListNode *hashingSearchLinkedList(unordered_map map, int target) { // 雜湊表的 key: 目標節點值,value: 節點物件 // 若雜湊表中無此 key ,返回 nullptr if (map.find(target) == map.end()) return nullptr; return map[target]; } /* Driver Code */ 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; // key: 元素,value: 索引 } int index = hashingSearchArray(map, target); cout << "目標元素 3 的索引 = " << index << endl; /* 雜湊查詢(鏈結串列) */ ListNode *head = vecToLinkedList(nums); // 初始化雜湊表 unordered_map map1; while (head != nullptr) { map1[head->val] = head; // key: 節點值,value: 節點 head = head->next; } ListNode *node = hashingSearchLinkedList(map1, target); cout << "目標節點值 3 的對應節點物件為 " << node << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_searching/linear_search.cpp ================================================ /** * 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; } /* Driver Code */ int main() { int target = 3; /* 在陣列中執行線性查詢 */ vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; int index = linearSearchArray(nums, target); cout << "目標元素 3 的索引 = " << index << endl; /* 在鏈結串列中執行線性查詢 */ ListNode *head = vecToLinkedList(nums); ListNode *node = linearSearchLinkedList(head, target); cout << "目標節點值 3 的對應節點物件為 " << node << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_searching/two_sum.cpp ================================================ /** * 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 {}; } /* Driver Code */ int main() { // ======= Test Case ======= vector nums = {2, 7, 11, 15}; int target = 13; // ====== Driver Code ====== // 方法一 vector res = twoSumBruteForce(nums, target); cout << "方法一 res = "; printVector(res); // 方法二 res = twoSumHashTable(nums, target); cout << "方法二 res = "; printVector(res); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_sorting/CMakeLists.txt ================================================ add_executable(selection_sort selection_sort.cpp) add_executable(bubble_sort bubble_sort.cpp) add_executable(insertion_sort insertion_sort.cpp) add_executable(merge_sort merge_sort.cpp) add_executable(quick_sort quick_sort.cpp) add_executable(heap_sort heap_sort.cpp) ================================================ FILE: zh-hant/codes/cpp/chapter_sorting/bubble_sort.cpp ================================================ /** * 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; // 此輪“冒泡”未交換任何元素,直接跳出 } } /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_sorting/bucket_sort.cpp ================================================ /** * 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; // 將 num 新增進桶 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; } } } /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_sorting/counting_sort.cpp ================================================ /** * 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; } /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_sorting/heap_sort.cpp ================================================ /** * 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); } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; heapSort(nums); cout << "堆積排序完成後 nums = "; printVector(nums); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_sorting/insertion_sort.cpp ================================================ /** * 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 賦值到正確位置 } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; insertionSort(nums); cout << "插入排序完成後 nums = "; printVector(nums); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_sorting/merge_sort.cpp ================================================ /** * 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); } /* Driver Code */ int main() { /* 合併排序 */ vector nums = {7, 3, 2, 6, 0, 1, 5, 4}; mergeSort(nums, 0, nums.size() - 1); cout << "合併排序完成後 nums = "; printVector(nums); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_sorting/quick_sort.cpp ================================================ /** * File: quick_sort.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* 快速排序類別 */ class QuickSort { private: /* 哨兵劃分 */ 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], nums[j]); // 交換這兩個元素 } swap(nums[i], nums[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 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], nums[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], nums[j]); // 交換這兩個元素 } swap(nums[i], nums[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 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], nums[j]); // 交換這兩個元素 } swap(nums[i], nums[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] } } } }; /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_sorting/radix_sort.cpp ================================================ /** * 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) { // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 return (num / exp) % 10; } /* 計數排序(根據 nums 第 k 位排序) */ void countingSortDigit(vector &nums, int exp) { // 十進位制的位範圍為 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); } /* Driver Code */ int main() { // 基數排序 vector nums = {10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996}; radixSort(nums); cout << "基數排序完成後 nums = "; printVector(nums); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_sorting/selection_sort.cpp ================================================ /** * 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]); } } /* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; selectionSort(nums); cout << "選擇排序完成後 nums = "; printVector(nums); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_stack_and_queue/CMakeLists.txt ================================================ add_executable(array_deque array_deque.cpp) add_executable(array_queue array_queue.cpp) add_executable(array_stack array_stack.cpp) add_executable(deque deque.cpp) add_executable(linkedlist_deque linkedlist_deque.cpp) add_executable(linkedlist_queue linkedlist_queue.cpp) add_executable(linkedlist_stack linkedlist_stack.cpp) add_executable(queue queue.cpp) add_executable(stack stack.cpp) ================================================ FILE: zh-hant/codes/cpp/chapter_stack_and_queue/array_deque.cpp ================================================ /** * 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 << "雙向佇列已滿" << endl; return; } // 佇列首指標向左移動一位 // 透過取餘操作實現 front 越過陣列頭部後回到尾部 front = index(front - 1); // 將 num 新增至佇列首 nums[front] = num; queSize++; } /* 佇列尾入列 */ void pushLast(int num) { if (queSize == capacity()) { cout << "雙向佇列已滿" << endl; return; } // 計算佇列尾指標,指向佇列尾索引 + 1 int rear = index(front + queSize); // 將 num 新增至佇列尾 nums[rear] = num; queSize++; } /* 佇列首出列 */ int popFirst() { int num = peekFirst(); // 佇列首指標向後移動一位 front = index(front + 1); queSize--; return num; } /* 佇列尾出列 */ int popLast() { int num = peekLast(); queSize--; return num; } /* 訪問佇列首元素 */ int peekFirst() { if (isEmpty()) throw out_of_range("雙向佇列為空"); return nums[front]; } /* 訪問佇列尾元素 */ int peekLast() { if (isEmpty()) throw out_of_range("雙向佇列為空"); // 計算尾元素索引 int last = index(front + queSize - 1); return nums[last]; } /* 返回陣列用於列印 */ vector toVector() { // 僅轉換有效長度範圍內的串列元素 vector res(queSize); for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[index(j)]; } return res; } }; /* Driver Code */ int main() { /* 初始化雙向佇列 */ ArrayDeque *deque = new ArrayDeque(10); deque->pushLast(3); deque->pushLast(2); deque->pushLast(5); cout << "雙向佇列 deque = "; printVector(deque->toVector()); /* 訪問元素 */ int peekFirst = deque->peekFirst(); cout << "佇列首元素 peekFirst = " << peekFirst << endl; int peekLast = deque->peekLast(); cout << "佇列尾元素 peekLast = " << peekLast << endl; /* 元素入列 */ deque->pushLast(4); cout << "元素 4 佇列尾入列後 deque = "; printVector(deque->toVector()); deque->pushFirst(1); cout << "元素 1 佇列首入列後 deque = "; printVector(deque->toVector()); /* 元素出列 */ int popLast = deque->popLast(); cout << "佇列尾出列元素 = " << popLast << ",佇列尾出列後 deque = "; printVector(deque->toVector()); int popFirst = deque->popFirst(); cout << "佇列首出列元素 = " << popFirst << ",佇列首出列後 deque = "; printVector(deque->toVector()); /* 獲取雙向佇列的長度 */ int size = deque->size(); cout << "雙向佇列長度 size = " << size << endl; /* 判斷雙向佇列是否為空 */ bool isEmpty = deque->isEmpty(); cout << "雙向佇列是否為空 = " << boolalpha << isEmpty << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_stack_and_queue/array_queue.cpp ================================================ /** * 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 << "佇列已滿" << endl; return; } // 計算佇列尾指標,指向佇列尾索引 + 1 // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 int rear = (front + queSize) % queCapacity; // 將 num 新增至佇列尾 nums[rear] = num; queSize++; } /* 出列 */ int pop() { int num = peek(); // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 front = (front + 1) % queCapacity; queSize--; return num; } /* 訪問佇列首元素 */ int peek() { if (isEmpty()) throw out_of_range("佇列為空"); 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; } }; /* Driver Code */ int main() { /* 初始化佇列 */ int capacity = 10; ArrayQueue *queue = new ArrayQueue(capacity); /* 元素入列 */ queue->push(1); queue->push(3); queue->push(2); queue->push(5); queue->push(4); cout << "佇列 queue = "; printVector(queue->toVector()); /* 訪問佇列首元素 */ int peek = queue->peek(); cout << "佇列首元素 peek = " << peek << endl; /* 元素出列 */ peek = queue->pop(); cout << "出列元素 pop = " << peek << ",出列後 queue = "; printVector(queue->toVector()); /* 獲取佇列的長度 */ int size = queue->size(); cout << "佇列長度 size = " << size << endl; /* 判斷佇列是否為空 */ bool empty = queue->isEmpty(); cout << "佇列是否為空 = " << empty << endl; /* 測試環形陣列 */ for (int i = 0; i < 10; i++) { queue->push(i); queue->pop(); cout << "第 " << i << " 輪入列 + 出列後 queue = "; printVector(queue->toVector()); } // 釋放記憶體 delete queue; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_stack_and_queue/array_stack.cpp ================================================ /** * 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("堆疊為空"); return stack.back(); } /* 返回 Vector */ vector toVector() { return stack; } }; /* Driver Code */ int main() { /* 初始化堆疊 */ ArrayStack *stack = new ArrayStack(); /* 元素入堆疊 */ stack->push(1); stack->push(3); stack->push(2); stack->push(5); stack->push(4); cout << "堆疊 stack = "; printVector(stack->toVector()); /* 訪問堆疊頂元素 */ int top = stack->top(); cout << "堆疊頂元素 top = " << top << endl; /* 元素出堆疊 */ top = stack->pop(); cout << "出堆疊元素 pop = " << top << ",出堆疊後 stack = "; printVector(stack->toVector()); /* 獲取堆疊的長度 */ int size = stack->size(); cout << "堆疊的長度 size = " << size << endl; /* 判斷是否為空 */ bool empty = stack->isEmpty(); cout << "堆疊是否為空 = " << empty << endl; // 釋放記憶體 delete stack; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_stack_and_queue/deque.cpp ================================================ /** * File: deque.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* 初始化雙向佇列 */ deque deque; /* 元素入列 */ deque.push_back(2); deque.push_back(5); deque.push_back(4); deque.push_front(3); deque.push_front(1); cout << "雙向佇列 deque = "; printDeque(deque); /* 訪問元素 */ int front = deque.front(); cout << "佇列首元素 front = " << front << endl; int back = deque.back(); cout << "佇列尾元素 back = " << back << endl; /* 元素出列 */ deque.pop_front(); cout << "佇列首出列元素 popFront = " << front << ",佇列首出列後 deque = "; printDeque(deque); deque.pop_back(); cout << "佇列尾出列元素 popLast = " << back << ",佇列尾出列後 deque = "; printDeque(deque); /* 獲取雙向佇列的長度 */ int size = deque.size(); cout << "雙向佇列長度 size = " << size << endl; /* 判斷雙向佇列是否為空 */ bool empty = deque.empty(); cout << "雙向佇列是否為空 = " << empty << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp ================================================ /** * 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) { // 將 node 新增至鏈結串列頭部 front->prev = node; node->next = front; front = node; // 更新頭節點 // 佇列尾入列操作 } else { // 將 node 新增至鏈結串列尾部 rear->next = node; node->prev = rear; rear = node; // 更新尾節點 } queSize++; // 更新佇列長度 } /* 佇列首入列 */ void pushFirst(int num) { push(num, true); } /* 佇列尾入列 */ void pushLast(int num) { push(num, false); } /* 出列操作 */ int pop(bool isFront) { if (isEmpty()) throw out_of_range("佇列為空"); 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("雙向佇列為空"); return front->val; } /* 訪問佇列尾元素 */ int peekLast() { if (isEmpty()) throw out_of_range("雙向佇列為空"); 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; } }; /* Driver Code */ int main() { /* 初始化雙向佇列 */ LinkedListDeque *deque = new LinkedListDeque(); deque->pushLast(3); deque->pushLast(2); deque->pushLast(5); cout << "雙向佇列 deque = "; printVector(deque->toVector()); /* 訪問元素 */ int peekFirst = deque->peekFirst(); cout << "佇列首元素 peekFirst = " << peekFirst << endl; int peekLast = deque->peekLast(); cout << "佇列尾元素 peekLast = " << peekLast << endl; /* 元素入列 */ deque->pushLast(4); cout << "元素 4 佇列尾入列後 deque ="; printVector(deque->toVector()); deque->pushFirst(1); cout << "元素 1 佇列首入列後 deque = "; printVector(deque->toVector()); /* 元素出列 */ int popLast = deque->popLast(); cout << "佇列尾出列元素 = " << popLast << ",佇列尾出列後 deque = "; printVector(deque->toVector()); int popFirst = deque->popFirst(); cout << "佇列首出列元素 = " << popFirst << ",佇列首出列後 deque = "; printVector(deque->toVector()); /* 獲取雙向佇列的長度 */ int size = deque->size(); cout << "雙向佇列長度 size = " << size << endl; /* 判斷雙向佇列是否為空 */ bool isEmpty = deque->isEmpty(); cout << "雙向佇列是否為空 = " << boolalpha << isEmpty << endl; // 釋放記憶體 delete deque; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp ================================================ /** * 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("佇列為空"); 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; } }; /* Driver Code */ int main() { /* 初始化佇列 */ LinkedListQueue *queue = new LinkedListQueue(); /* 元素入列 */ queue->push(1); queue->push(3); queue->push(2); queue->push(5); queue->push(4); cout << "佇列 queue = "; printVector(queue->toVector()); /* 訪問佇列首元素 */ int peek = queue->peek(); cout << "佇列首元素 peek = " << peek << endl; /* 元素出列 */ peek = queue->pop(); cout << "出列元素 pop = " << peek << ",出列後 queue = "; printVector(queue->toVector()); /* 獲取佇列的長度 */ int size = queue->size(); cout << "佇列長度 size = " << size << endl; /* 判斷佇列是否為空 */ bool empty = queue->isEmpty(); cout << "佇列是否為空 = " << empty << endl; // 釋放記憶體 delete queue; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp ================================================ /** * 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("堆疊為空"); return stackTop->val; } /* 將 List 轉化為 Array 並返回 */ 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; } }; /* Driver Code */ int main() { /* 初始化堆疊 */ LinkedListStack *stack = new LinkedListStack(); /* 元素入堆疊 */ stack->push(1); stack->push(3); stack->push(2); stack->push(5); stack->push(4); cout << "堆疊 stack = "; printVector(stack->toVector()); /* 訪問堆疊頂元素 */ int top = stack->top(); cout << "堆疊頂元素 top = " << top << endl; /* 元素出堆疊 */ top = stack->pop(); cout << "出堆疊元素 pop = " << top << ",出堆疊後 stack = "; printVector(stack->toVector()); /* 獲取堆疊的長度 */ int size = stack->size(); cout << "堆疊的長度 size = " << size << endl; /* 判斷是否為空 */ bool empty = stack->isEmpty(); cout << "堆疊是否為空 = " << empty << endl; // 釋放記憶體 delete stack; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_stack_and_queue/queue.cpp ================================================ /** * File: queue.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* 初始化佇列 */ queue queue; /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); cout << "佇列 queue = "; printQueue(queue); /* 訪問佇列首元素 */ int front = queue.front(); cout << "佇列首元素 front = " << front << endl; /* 元素出列 */ queue.pop(); cout << "出列元素 front = " << front << ",出列後 queue = "; printQueue(queue); /* 獲取佇列的長度 */ int size = queue.size(); cout << "佇列長度 size = " << size << endl; /* 判斷佇列是否為空 */ bool empty = queue.empty(); cout << "佇列是否為空 = " << empty << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_stack_and_queue/stack.cpp ================================================ /** * File: stack.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) */ #include "../utils/common.hpp" /* Driver Code */ int main() { /* 初始化堆疊 */ stack stack; /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); cout << "堆疊 stack = "; printStack(stack); /* 訪問堆疊頂元素 */ int top = stack.top(); cout << "堆疊頂元素 top = " << top << endl; /* 元素出堆疊 */ stack.pop(); // 無返回值 cout << "出堆疊元素 pop = " << top << ",出堆疊後 stack = "; printStack(stack); /* 獲取堆疊的長度 */ int size = stack.size(); cout << "堆疊的長度 size = " << size << endl; /* 判斷是否為空 */ bool empty = stack.empty(); cout << "堆疊是否為空 = " << empty << endl; return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_tree/CMakeLists.txt ================================================ add_executable(avl_tree avl_tree.cpp) add_executable(binary_search_tree binary_search_tree.cpp) add_executable(binary_tree binary_tree.cpp) add_executable(binary_tree_bfs binary_tree_bfs.cpp) add_executable(binary_tree_dfs binary_tree_dfs.cpp) add_executable(array_binary_tree array_binary_tree.cpp) ================================================ FILE: zh-hant/codes/cpp/chapter_tree/array_binary_tree.cpp ================================================ /** * 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 ,代表空位 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)); } }; /* Driver Code */ 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 << ",值為 " << (abt.val(l) != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n"; cout << "其右子節點的索引為 " << r << ",值為 " << (abt.val(r) != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n"; cout << "其父節點的索引為 " << p << ",值為 " << (abt.val(p) != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n"; // 走訪樹 vector res = abt.levelOrder(); cout << "\n層序走訪為: "; printVector(res); res = abt.preOrder(); cout << "前序走訪為: "; printVector(res); res = abt.inOrder(); cout << "中序走訪為: "; printVector(res); res = abt.postOrder(); cout << "後序走訪為: "; printVector(res); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_tree/avl_tree.cpp ================================================ /** * 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 ,直接刪除 node 並返回 if (child == nullptr) { delete node; return nullptr; } // 子節點數量 = 1 ,直接刪除 node 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); } /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_tree/binary_search_tree.cpp ================================================ /** * 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 or 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); // 用 tmp 覆蓋 cur cur->val = tmpVal; } } }; /* Driver Code */ int main() { /* 初始化二元搜尋樹 */ BinarySearchTree *bst = new BinarySearchTree(); // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 vector nums = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; for (int num : nums) { bst->insert(num); } cout << endl << "初始化的二元樹為\n" << endl; 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_tree/binary_tree.cpp ================================================ /** * File: binary_tree.cpp * Created Time: 2022-11-25 * Author: krahets (krahets@163.com) */ #include "../utils/common.hpp" /* Driver Code */ 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; } ================================================ FILE: zh-hant/codes/cpp/chapter_tree/binary_tree_bfs.cpp ================================================ /** * 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; } /* Driver Code */ int main() { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "初始化二元樹\n" << endl; printTree(root); /* 層序走訪 */ vector vec = levelOrder(root); cout << endl << "層序走訪的節點列印序列 = "; printVector(vec); return 0; } ================================================ FILE: zh-hant/codes/cpp/chapter_tree/binary_tree_dfs.cpp ================================================ /** * 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); } /* Driver Code */ int main() { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "初始化二元樹\n" << endl; printTree(root); /* 前序走訪 */ vec.clear(); preOrder(root); cout << endl << "前序走訪的節點列印序列 = "; printVector(vec); /* 中序走訪 */ vec.clear(); inOrder(root); cout << endl << "中序走訪的節點列印序列 = "; printVector(vec); /* 後序走訪 */ vec.clear(); postOrder(root); cout << endl << "後序走訪的節點列印序列 = "; printVector(vec); return 0; } ================================================ FILE: zh-hant/codes/cpp/utils/CMakeLists.txt ================================================ add_executable(utils common.hpp print_utils.hpp list_node.hpp tree_node.hpp vertex.hpp) ================================================ FILE: zh-hant/codes/cpp/utils/common.hpp ================================================ /** * 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; ================================================ FILE: zh-hant/codes/cpp/utils/list_node.hpp ================================================ /** * 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; } } ================================================ FILE: zh-hant/codes/cpp/utils/print_utils.hpp ================================================ /** * 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 /* Find an element in a vector */ template int vecFind(const vector &vec, T ele) { int j = INT_MAX; for (int i = 0; i < vec.size(); i++) { if (vec[i] == ele) { j = i; } } return j; } /* Concatenate a vector with a delim */ 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(); } /* Repeat a string for n times */ 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; } /* Get the Vector String object */ template string getVectorString(vector &list) { return "[" + strJoin(", ", list) + "]"; } /* 列印串列 */ template void printVector(vector list) { cout << getVectorString(list) << '\n'; } /* 列印矩陣 */ template void printVectorMatrix(vector> &matrix) { cout << "[" << '\n'; for (vector &list : matrix) 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; } /** * 列印二元樹 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTree(TreeNode *root, Trunk *prev, bool isRight) { 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) { // Reverse the input stack stack tmp; while (!stk.empty()) { tmp.push(stk.top()); stk.pop(); } // Generate the string to print ostringstream s; bool flag = true; while (!tmp.empty()) { if (flag) { s << tmp.top(); flag = false; } else s << ", " << tmp.top(); tmp.pop(); } cout << "[" + s.str() + "]" << '\n'; } /* 列印佇列 */ template void printQueue(queue queue) { // Generate the string to print 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) { // Generate the string to print 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'; } } /* Expose the underlying storage of the priority_queue container */ template S &Container(priority_queue &pq) { struct HackedQueue : private priority_queue { static S &Container(priority_queue &pq) { 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); } ================================================ FILE: zh-hant/codes/cpp/utils/tree_node.hpp ================================================ /** * 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; } ================================================ FILE: zh-hant/codes/cpp/utils/vertex.hpp ================================================ /** * 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; } ================================================ FILE: zh-hant/codes/csharp/.editorconfig ================================================ # CSharp formatting rules [*.cs] csharp_new_line_before_open_brace = none csharp_new_line_before_else = false csharp_new_line_before_catch = false csharp_new_line_before_finally = false csharp_indent_labels = one_less_than_current csharp_using_directive_placement = outside_namespace:silent csharp_prefer_simple_using_statement = true:suggestion csharp_prefer_braces = true:silent csharp_style_namespace_declarations = block_scoped:silent csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent csharp_style_prefer_primary_constructors = true:suggestion csharp_style_expression_bodied_methods = false:silent csharp_style_expression_bodied_constructors = false:silent csharp_style_expression_bodied_operators = false:silent csharp_style_expression_bodied_properties = true:silent csharp_style_expression_bodied_indexers = true:silent csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_lambdas = true:silent # CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language. dotnet_diagnostic.CS8981.severity = silent # IDE1006: Naming Styles dotnet_diagnostic.IDE1006.severity = silent # CA1822: Mark members as static dotnet_diagnostic.CA1822.severity = silent [*.{cs,vb}] #### Naming styles #### # Naming rules dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion dotnet_naming_rule.types_should_be_pascal_case.symbols = types dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case # Symbol specifications dotnet_naming_symbols.interface.applicable_kinds = interface dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.interface.required_modifiers = dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.types.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.non_field_members.required_modifiers = # Naming styles dotnet_naming_style.begins_with_i.required_prefix = I dotnet_naming_style.begins_with_i.required_suffix = dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_style_operator_placement_when_wrapping = beginning_of_line tab_width = 4 indent_size = 4 end_of_line = crlf # IDE0040: Add accessibility modifiers dotnet_diagnostic.IDE0040.severity = silent # IDE0044: Add readonly modifier dotnet_diagnostic.IDE0044.severity = silent ================================================ FILE: zh-hant/codes/csharp/.gitignore ================================================ .idea/ .vs/ obj/ .Debug bin/ ================================================ FILE: zh-hant/codes/csharp/GlobalUsing.cs ================================================ global using NUnit.Framework; global using hello_algo.utils; global using System.Text; ================================================ FILE: zh-hant/codes/csharp/chapter_array_and_linkedlist/array.cs ================================================ // File: array.cs // Created Time: 2022-12-14 // Author: mingXta (1195669834@qq.com) namespace hello_algo.chapter_array_and_linkedlist; public class array { /* 隨機訪問元素 */ int RandomAccess(int[] nums) { Random random = new(); // 在區間 [0, nums.Length) 中隨機抽取一個數字 int randomIndex = random.Next(nums.Length); // 獲取並返回隨機元素 int randomNum = nums[randomIndex]; return randomNum; } /* 擴展陣列長度 */ int[] Extend(int[] nums, int enlarge) { // 初始化一個擴展長度後的陣列 int[] res = new int[nums.Length + enlarge]; // 將原陣列中的所有元素複製到新陣列 for (int i = 0; i < nums.Length; i++) { res[i] = nums[i]; } // 返回擴展後的新陣列 return res; } /* 在陣列的索引 index 處插入元素 num */ void Insert(int[] nums, int num, int index) { // 把索引 index 以及之後的所有元素向後移動一位 for (int i = nums.Length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 將 num 賦給 index 處的元素 nums[index] = num; } /* 刪除索引 index 處的元素 */ void Remove(int[] nums, int index) { // 把索引 index 之後的所有元素向前移動一位 for (int i = index; i < nums.Length - 1; i++) { nums[i] = nums[i + 1]; } } /* 走訪陣列 */ void Traverse(int[] nums) { int count = 0; // 透過索引走訪陣列 for (int i = 0; i < nums.Length; i++) { count += nums[i]; } // 直接走訪陣列元素 foreach (int num in nums) { count += num; } } /* 在陣列中查詢指定元素 */ int Find(int[] nums, int target) { for (int i = 0; i < nums.Length; i++) { if (nums[i] == target) return i; } return -1; } /* 輔助函式,陣列轉字串 */ string ToString(int[] nums) { return string.Join(",", nums); } [Test] public void Test() { // 初始化陣列 int[] arr = new int[5]; Console.WriteLine("陣列 arr = " + ToString(arr)); int[] nums = [1, 3, 2, 5, 4]; Console.WriteLine("陣列 nums = " + ToString(nums)); // 隨機訪問 int randomNum = RandomAccess(nums); Console.WriteLine("在 nums 中獲取隨機元素 " + randomNum); // 長度擴展 nums = Extend(nums, 3); Console.WriteLine("將陣列長度擴展至 8 ,得到 nums = " + ToString(nums)); // 插入元素 Insert(nums, 6, 3); Console.WriteLine("在索引 3 處插入數字 6 ,得到 nums = " + ToString(nums)); // 刪除元素 Remove(nums, 2); Console.WriteLine("刪除索引 2 處的元素,得到 nums = " + ToString(nums)); // 走訪陣列 Traverse(nums); // 查詢元素 int index = Find(nums, 3); Console.WriteLine("在 nums 中查詢元素 3 ,得到索引 = " + index); } } ================================================ FILE: zh-hant/codes/csharp/chapter_array_and_linkedlist/linked_list.cs ================================================ // File: linked_list.cs // Created Time: 2022-12-16 // Author: mingXta (1195669834@qq.com) namespace hello_algo.chapter_array_and_linkedlist; public class linked_list { /* 在鏈結串列的節點 n0 之後插入節點 P */ void Insert(ListNode n0, ListNode P) { ListNode? n1 = n0.next; P.next = n1; n0.next = P; } /* 刪除鏈結串列的節點 n0 之後的首個節點 */ void Remove(ListNode n0) { if (n0.next == null) return; // n0 -> P -> n1 ListNode P = n0.next; ListNode? n1 = P.next; n0.next = n1; } /* 訪問鏈結串列中索引為 index 的節點 */ ListNode? Access(ListNode? head, int index) { for (int i = 0; i < index; i++) { if (head == null) return null; head = head.next; } return head; } /* 在鏈結串列中查詢值為 target 的首個節點 */ int Find(ListNode? head, int target) { int index = 0; while (head != null) { if (head.val == target) return index; head = head.next; index++; } return -1; } [Test] public void Test() { // 初始化鏈結串列 // 初始化各個節點 ListNode n0 = new(1); ListNode n1 = new(3); ListNode n2 = new(2); ListNode n3 = new(5); ListNode n4 = new(4); // 構建節點之間的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; Console.WriteLine($"初始化的鏈結串列為{n0}"); // 插入節點 Insert(n0, new ListNode(0)); Console.WriteLine($"插入節點後的鏈結串列為{n0}"); // 刪除節點 Remove(n0); Console.WriteLine($"刪除節點後的鏈結串列為{n0}"); // 訪問節點 ListNode? node = Access(n0, 3); Console.WriteLine($"鏈結串列中索引 3 處的節點的值 = {node?.val}"); // 查詢節點 int index = Find(n0, 2); Console.WriteLine($"鏈結串列中值為 2 的節點的索引 = {index}"); } } ================================================ FILE: zh-hant/codes/csharp/chapter_array_and_linkedlist/list.cs ================================================ /** * File: list.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_array_and_linkedlist; public class list { [Test] public void Test() { /* 初始化串列 */ int[] numbers = [1, 3, 2, 5, 4]; List nums = [.. numbers]; Console.WriteLine("串列 nums = " + string.Join(",", nums)); /* 訪問元素 */ int num = nums[1]; Console.WriteLine("訪問索引 1 處的元素,得到 num = " + num); /* 更新元素 */ nums[1] = 0; Console.WriteLine("將索引 1 處的元素更新為 0 ,得到 nums = " + string.Join(",", nums)); /* 清空串列 */ nums.Clear(); Console.WriteLine("清空串列後 nums = " + string.Join(",", nums)); /* 在尾部新增元素 */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); Console.WriteLine("新增元素後 nums = " + string.Join(",", nums)); /* 在中間插入元素 */ nums.Insert(3, 6); Console.WriteLine("在索引 3 處插入數字 6 ,得到 nums = " + string.Join(",", nums)); /* 刪除元素 */ nums.RemoveAt(3); Console.WriteLine("刪除索引 3 處的元素,得到 nums = " + string.Join(",", nums)); /* 透過索引走訪串列 */ int count = 0; for (int i = 0; i < nums.Count; i++) { count += nums[i]; } /* 直接走訪串列元素 */ count = 0; foreach (int x in nums) { count += x; } /* 拼接兩個串列 */ List nums1 = [6, 8, 7, 10, 9]; nums.AddRange(nums1); Console.WriteLine("將串列 nums1 拼接到 nums 之後,得到 nums = " + string.Join(",", nums)); /* 排序串列 */ nums.Sort(); // 排序後,串列元素從小到大排列 Console.WriteLine("排序串列後 nums = " + string.Join(",", nums)); } } ================================================ FILE: zh-hant/codes/csharp/chapter_array_and_linkedlist/my_list.cs ================================================ /** * File: my_list.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_array_and_linkedlist; /* 串列類別 */ class MyList { private int[] arr; // 陣列(儲存串列元素) private int arrCapacity = 10; // 串列容量 private int arrSize = 0; // 串列長度(當前元素數量) private readonly int extendRatio = 2; // 每次串列擴容的倍數 /* 建構子 */ public MyList() { arr = new int[arrCapacity]; } /* 獲取串列長度(當前元素數量)*/ public int Size() { return arrSize; } /* 獲取串列容量 */ public int Capacity() { return arrCapacity; } /* 訪問元素 */ public int Get(int index) { // 索引如果越界,則丟擲異常,下同 if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("索引越界"); return arr[index]; } /* 更新元素 */ public void Set(int index, int num) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("索引越界"); arr[index] = num; } /* 在尾部新增元素 */ public void Add(int num) { // 元素數量超出容量時,觸發擴容機制 if (arrSize == arrCapacity) ExtendCapacity(); arr[arrSize] = num; // 更新元素數量 arrSize++; } /* 在中間插入元素 */ public void Insert(int index, int num) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("索引越界"); // 元素數量超出容量時,觸發擴容機制 if (arrSize == arrCapacity) ExtendCapacity(); // 將索引 index 以及之後的元素都向後移動一位 for (int j = arrSize - 1; j >= index; j--) { arr[j + 1] = arr[j]; } arr[index] = num; // 更新元素數量 arrSize++; } /* 刪除元素 */ public int Remove(int index) { if (index < 0 || index >= arrSize) throw new IndexOutOfRangeException("索引越界"); int num = arr[index]; // 將將索引 index 之後的元素都向前移動一位 for (int j = index; j < arrSize - 1; j++) { arr[j] = arr[j + 1]; } // 更新元素數量 arrSize--; // 返回被刪除的元素 return num; } /* 串列擴容 */ public void ExtendCapacity() { // 新建一個長度為 arrCapacity * extendRatio 的陣列,並將原陣列複製到新陣列 Array.Resize(ref arr, arrCapacity * extendRatio); // 更新串列容量 arrCapacity = arr.Length; } /* 將串列轉換為陣列 */ public int[] ToArray() { // 僅轉換有效長度範圍內的串列元素 int[] arr = new int[arrSize]; for (int i = 0; i < arrSize; i++) { arr[i] = Get(i); } return arr; } } public class my_list { [Test] public void Test() { /* 初始化串列 */ MyList nums = new(); /* 在尾部新增元素 */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); Console.WriteLine("串列 nums = " + string.Join(",", nums.ToArray()) + " ,容量 = " + nums.Capacity() + " ,長度 = " + nums.Size()); /* 在中間插入元素 */ nums.Insert(3, 6); Console.WriteLine("在索引 3 處插入數字 6 ,得到 nums = " + string.Join(",", nums.ToArray())); /* 刪除元素 */ nums.Remove(3); Console.WriteLine("刪除索引 3 處的元素,得到 nums = " + string.Join(",", nums.ToArray())); /* 訪問元素 */ int num = nums.Get(1); Console.WriteLine("訪問索引 1 處的元素,得到 num = " + num); /* 更新元素 */ nums.Set(1, 0); Console.WriteLine("將索引 1 處的元素更新為 0 ,得到 nums = " + string.Join(",", nums.ToArray())); /* 測試擴容機制 */ for (int i = 0; i < 10; i++) { // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 nums.Add(i); } Console.WriteLine("擴容後的串列 nums = " + string.Join(",", nums.ToArray()) + " ,容量 = " + nums.Capacity() + " ,長度 = " + nums.Size()); } } ================================================ FILE: zh-hant/codes/csharp/chapter_backtracking/n_queens.cs ================================================ /** * File: n_queens.cs * Created Time: 2023-05-04 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class n_queens { /* 回溯演算法:n 皇后 */ void Backtrack(int row, int n, List> state, List>> res, bool[] cols, bool[] diags1, bool[] diags2) { // 當放置完所有行時,記錄解 if (row == n) { List> copyState = []; foreach (List sRow in state) { copyState.Add(new List(sRow)); } res.Add(copyState); return; } // 走訪所有列 for (int col = 0; col < n; col++) { // 計算該格子對應的主對角線和次對角線 int diag1 = row - col + n - 1; int diag2 = row + col; // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 嘗試:將皇后放置在該格子 state[row][col] = "Q"; cols[col] = diags1[diag1] = diags2[diag2] = true; // 放置下一行 Backtrack(row + 1, n, state, res, cols, diags1, diags2); // 回退:將該格子恢復為空位 state[row][col] = "#"; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* 求解 n 皇后 */ List>> NQueens(int n) { // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 List> state = []; for (int i = 0; i < n; i++) { List row = []; for (int j = 0; j < n; j++) { row.Add("#"); } state.Add(row); } bool[] cols = new bool[n]; // 記錄列是否有皇后 bool[] diags1 = new bool[2 * n - 1]; // 記錄主對角線上是否有皇后 bool[] diags2 = new bool[2 * n - 1]; // 記錄次對角線上是否有皇后 List>> res = []; Backtrack(0, n, state, res, cols, diags1, diags2); return res; } [Test] public void Test() { int n = 4; List>> res = NQueens(n); Console.WriteLine("輸入棋盤長寬為 " + n); Console.WriteLine("皇后放置方案共有 " + res.Count + " 種"); foreach (List> state in res) { Console.WriteLine("--------------------"); foreach (List row in state) { PrintUtil.PrintList(row); } } } } ================================================ FILE: zh-hant/codes/csharp/chapter_backtracking/permutations_i.cs ================================================ /** * File: permutations_i.cs * Created Time: 2023-04-24 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class permutations_i { /* 回溯演算法:全排列 I */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // 當狀態長度等於元素數量時,記錄解 if (state.Count == choices.Length) { res.Add(new List(state)); return; } // 走訪所有選擇 for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // 剪枝:不允許重複選擇元素 if (!selected[i]) { // 嘗試:做出選擇,更新狀態 selected[i] = true; state.Add(choice); // 進行下一輪選擇 Backtrack(state, choices, selected, res); // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* 全排列 I */ List> PermutationsI(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } [Test] public void Test() { int[] nums = [1, 2, 3]; List> res = PermutationsI(nums); Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums)); Console.WriteLine("所有排列 res = "); foreach (List permutation in res) { PrintUtil.PrintList(permutation); } } } ================================================ FILE: zh-hant/codes/csharp/chapter_backtracking/permutations_ii.cs ================================================ /** * File: permutations_ii.cs * Created Time: 2023-04-24 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class permutations_ii { /* 回溯演算法:全排列 II */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // 當狀態長度等於元素數量時,記錄解 if (state.Count == choices.Length) { res.Add(new List(state)); return; } // 走訪所有選擇 HashSet duplicated = []; for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 if (!selected[i] && !duplicated.Contains(choice)) { // 嘗試:做出選擇,更新狀態 duplicated.Add(choice); // 記錄選擇過的元素值 selected[i] = true; state.Add(choice); // 進行下一輪選擇 Backtrack(state, choices, selected, res); // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* 全排列 II */ List> PermutationsII(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } [Test] public void Test() { int[] nums = [1, 2, 2]; List> res = PermutationsII(nums); Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums)); Console.WriteLine("所有排列 res = "); foreach (List permutation in res) { PrintUtil.PrintList(permutation); } } } ================================================ FILE: zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs ================================================ /** * File: preorder_traversal_i_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_i_compact { List res = []; /* 前序走訪:例題一 */ void PreOrder(TreeNode? root) { if (root == null) { return; } if (root.val == 7) { // 記錄解 res.Add(root); } PreOrder(root.left); PreOrder(root.right); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\n初始化二元樹"); PrintUtil.PrintTree(root); // 前序走訪 PreOrder(root); Console.WriteLine("\n輸出所有值為 7 的節點"); PrintUtil.PrintList(res.Select(p => p.val).ToList()); } } ================================================ FILE: zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs ================================================ /** * File: preorder_traversal_ii_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_ii_compact { List path = []; List> res = []; /* 前序走訪:例題二 */ void PreOrder(TreeNode? root) { if (root == null) { return; } // 嘗試 path.Add(root); if (root.val == 7) { // 記錄解 res.Add(new List(path)); } PreOrder(root.left); PreOrder(root.right); // 回退 path.RemoveAt(path.Count - 1); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\n初始化二元樹"); PrintUtil.PrintTree(root); // 前序走訪 PreOrder(root); Console.WriteLine("\n輸出所有根節點到節點 7 的路徑"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs ================================================ /** * File: preorder_traversal_iii_compact.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_iii_compact { List path = []; List> res = []; /* 前序走訪:例題三 */ void PreOrder(TreeNode? root) { // 剪枝 if (root == null || root.val == 3) { return; } // 嘗試 path.Add(root); if (root.val == 7) { // 記錄解 res.Add(new List(path)); } PreOrder(root.left); PreOrder(root.right); // 回退 path.RemoveAt(path.Count - 1); } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\n初始化二元樹"); PrintUtil.PrintTree(root); // 前序走訪 PreOrder(root); Console.WriteLine("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs ================================================ /** * File: preorder_traversal_iii_template.cs * Created Time: 2023-04-17 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class preorder_traversal_iii_template { /* 判斷當前狀態是否為解 */ bool IsSolution(List state) { return state.Count != 0 && state[^1].val == 7; } /* 記錄解 */ void RecordSolution(List state, List> res) { res.Add(new List(state)); } /* 判斷在當前狀態下,該選擇是否合法 */ bool IsValid(List state, TreeNode choice) { return choice != null && choice.val != 3; } /* 更新狀態 */ void MakeChoice(List state, TreeNode choice) { state.Add(choice); } /* 恢復狀態 */ void UndoChoice(List state, TreeNode choice) { state.RemoveAt(state.Count - 1); } /* 回溯演算法:例題三 */ void Backtrack(List state, List choices, List> res) { // 檢查是否為解 if (IsSolution(state)) { // 記錄解 RecordSolution(state, res); } // 走訪所有選擇 foreach (TreeNode choice in choices) { // 剪枝:檢查選擇是否合法 if (IsValid(state, choice)) { // 嘗試:做出選擇,更新狀態 MakeChoice(state, choice); // 進行下一輪選擇 Backtrack(state, [choice.left!, choice.right!], res); // 回退:撤銷選擇,恢復到之前的狀態 UndoChoice(state, choice); } } } [Test] public void Test() { TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); Console.WriteLine("\n初始化二元樹"); PrintUtil.PrintTree(root); // 回溯演算法 List> res = []; List choices = [root!]; Backtrack([], choices, res); Console.WriteLine("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } } } ================================================ FILE: zh-hant/codes/csharp/chapter_backtracking/subset_sum_i.cs ================================================ /** * File: subset_sum_i.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_i { /* 回溯演算法:子集和 I */ void Backtrack(List state, int target, int[] choices, int start, List> res) { // 子集和等於 target 時,記錄解 if (target == 0) { res.Add(new List(state)); return; } // 走訪所有選擇 // 剪枝二:從 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.RemoveAt(state.Count - 1); } } /* 求解子集和 I */ List> SubsetSumI(int[] nums, int target) { List state = []; // 狀態(子集) Array.Sort(nums); // 對 nums 進行排序 int start = 0; // 走訪起始點 List> res = []; // 結果串列(子集串列) Backtrack(state, target, nums, start, res); return res; } [Test] public void Test() { int[] nums = [3, 4, 5]; int target = 9; List> res = SubsetSumI(nums, target); Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("所有和等於 " + target + " 的子集 res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } } } ================================================ FILE: zh-hant/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs ================================================ /** * File: subset_sum_i_naive.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_i_naive { /* 回溯演算法:子集和 I */ void Backtrack(List state, int target, int total, int[] choices, List> res) { // 子集和等於 target 時,記錄解 if (total == target) { res.Add(new List(state)); return; } // 走訪所有選擇 for (int i = 0; i < choices.Length; i++) { // 剪枝:若子集和超過 target ,則跳過該選擇 if (total + choices[i] > target) { continue; } // 嘗試:做出選擇,更新元素和 total state.Add(choices[i]); // 進行下一輪選擇 Backtrack(state, target, total + choices[i], choices, res); // 回退:撤銷選擇,恢復到之前的狀態 state.RemoveAt(state.Count - 1); } } /* 求解子集和 I(包含重複子集) */ List> SubsetSumINaive(int[] nums, int target) { List state = []; // 狀態(子集) int total = 0; // 子集和 List> res = []; // 結果串列(子集串列) Backtrack(state, target, total, nums, res); return res; } [Test] public void Test() { int[] nums = [3, 4, 5]; int target = 9; List> res = SubsetSumINaive(nums, target); Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("所有和等於 " + target + " 的子集 res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } Console.WriteLine("請注意,該方法輸出的結果包含重複集合"); } } ================================================ FILE: zh-hant/codes/csharp/chapter_backtracking/subset_sum_ii.cs ================================================ /** * File: subset_sum_ii.cs * Created Time: 2023-06-25 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_backtracking; public class subset_sum_ii { /* 回溯演算法:子集和 II */ void Backtrack(List state, int target, int[] choices, int start, List> res) { // 子集和等於 target 時,記錄解 if (target == 0) { res.Add(new List(state)); return; } // 走訪所有選擇 // 剪枝二:從 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.RemoveAt(state.Count - 1); } } /* 求解子集和 II */ List> SubsetSumII(int[] nums, int target) { List state = []; // 狀態(子集) Array.Sort(nums); // 對 nums 進行排序 int start = 0; // 走訪起始點 List> res = []; // 結果串列(子集串列) Backtrack(state, target, nums, start, res); return res; } [Test] public void Test() { int[] nums = [4, 4, 5]; int target = 9; List> res = SubsetSumII(nums, target); Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums) + ", target = " + target); Console.WriteLine("所有和等於 " + target + " 的子集 res = "); foreach (var subset in res) { PrintUtil.PrintList(subset); } } } ================================================ FILE: zh-hant/codes/csharp/chapter_computational_complexity/iteration.cs ================================================ /** * File: iteration.cs * Created Time: 2023-08-28 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_computational_complexity; public class iteration { /* for 迴圈 */ int ForLoop(int n) { int res = 0; // 迴圈求和 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } /* while 迴圈 */ int WhileLoop(int n) { int res = 0; int i = 1; // 初始化條件變數 // 迴圈求和 1, 2, ..., n-1, n while (i <= n) { res += i; i += 1; // 更新條件變數 } return res; } /* while 迴圈(兩次更新) */ int WhileLoopII(int n) { int res = 0; int i = 1; // 初始化條件變數 // 迴圈求和 1, 4, 10, ... while (i <= n) { res += i; // 更新條件變數 i += 1; i *= 2; } return res; } /* 雙層 for 迴圈 */ string NestedForLoop(int n) { StringBuilder res = new(); // 迴圈 i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // 迴圈 j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { res.Append($"({i}, {j}), "); } } return res.ToString(); } /* Driver Code */ [Test] public void Test() { int n = 5; int res; res = ForLoop(n); Console.WriteLine("\nfor 迴圈的求和結果 res = " + res); res = WhileLoop(n); Console.WriteLine("\nwhile 迴圈的求和結果 res = " + res); res = WhileLoopII(n); Console.WriteLine("\nwhile 迴圈(兩次更新)求和結果 res = " + res); string resStr = NestedForLoop(n); Console.WriteLine("\n雙層 for 迴圈的走訪結果 " + resStr); } } ================================================ FILE: zh-hant/codes/csharp/chapter_computational_complexity/recursion.cs ================================================ /** * File: recursion.cs * Created Time: 2023-08-28 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_computational_complexity; public class recursion { /* 遞迴 */ int Recur(int n) { // 終止條件 if (n == 1) return 1; // 遞:遞迴呼叫 int res = Recur(n - 1); // 迴:返回結果 return n + res; } /* 使用迭代模擬遞迴 */ int ForLoopRecur(int n) { // 使用一個顯式的堆疊來模擬系統呼叫堆疊 Stack stack = new(); int res = 0; // 遞:遞迴呼叫 for (int i = n; i > 0; i--) { // 透過“入堆疊操作”模擬“遞” stack.Push(i); } // 迴:返回結果 while (stack.Count > 0) { // 透過“出堆疊操作”模擬“迴” res += stack.Pop(); } // res = 1+2+3+...+n return res; } /* 尾遞迴 */ int TailRecur(int n, int res) { // 終止條件 if (n == 0) return res; // 尾遞迴呼叫 return TailRecur(n - 1, res + n); } /* 費波那契數列:遞迴 */ int Fib(int n) { // 終止條件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // 遞迴呼叫 f(n) = f(n-1) + f(n-2) int res = Fib(n - 1) + Fib(n - 2); // 返回結果 f(n) return res; } /* Driver Code */ [Test] public void Test() { int n = 5; int res; res = Recur(n); Console.WriteLine("\n遞迴函式的求和結果 res = " + res); res = ForLoopRecur(n); Console.WriteLine("\n使用迭代模擬遞迴求和結果 res = " + res); res = TailRecur(n, 0); Console.WriteLine("\n尾遞迴函式的求和結果 res = " + res); res = Fib(n); Console.WriteLine("\n費波那契數列的第 " + n + " 項為 " + res); } } ================================================ FILE: zh-hant/codes/csharp/chapter_computational_complexity/space_complexity.cs ================================================ /** * File: space_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class space_complexity { /* 函式 */ int Function() { // 執行某些操作 return 0; } /* 常數階 */ void Constant(int n) { // 常數、變數、物件佔用 O(1) 空間 int a = 0; int b = 0; int[] nums = new int[10000]; ListNode node = new(0); // 迴圈中的變數佔用 O(1) 空間 for (int i = 0; i < n; i++) { int c = 0; } // 迴圈中的函式佔用 O(1) 空間 for (int i = 0; i < n; i++) { Function(); } } /* 線性階 */ void Linear(int n) { // 長度為 n 的陣列佔用 O(n) 空間 int[] nums = new int[n]; // 長度為 n 的串列佔用 O(n) 空間 List nodes = []; for (int i = 0; i < n; i++) { nodes.Add(new ListNode(i)); } // 長度為 n 的雜湊表佔用 O(n) 空間 Dictionary map = []; for (int i = 0; i < n; i++) { map.Add(i, i.ToString()); } } /* 線性階(遞迴實現) */ void LinearRecur(int n) { Console.WriteLine("遞迴 n = " + n); if (n == 1) return; LinearRecur(n - 1); } /* 平方階 */ void Quadratic(int n) { // 矩陣佔用 O(n^2) 空間 int[,] numMatrix = new int[n, n]; // 二維串列佔用 O(n^2) 空間 List> numList = []; for (int i = 0; i < n; i++) { List tmp = []; for (int j = 0; j < n; j++) { tmp.Add(0); } numList.Add(tmp); } } /* 平方階(遞迴實現) */ int QuadraticRecur(int n) { if (n <= 0) return 0; int[] nums = new int[n]; Console.WriteLine("遞迴 n = " + n + " 中的 nums 長度 = " + nums.Length); return QuadraticRecur(n - 1); } /* 指數階(建立滿二元樹) */ TreeNode? BuildTree(int n) { if (n == 0) return null; TreeNode root = new(0) { left = BuildTree(n - 1), right = BuildTree(n - 1) }; return root; } [Test] public void Test() { int n = 5; // 常數階 Constant(n); // 線性階 Linear(n); LinearRecur(n); // 平方階 Quadratic(n); QuadraticRecur(n); // 指數階 TreeNode? root = BuildTree(n); PrintUtil.PrintTree(root); } } ================================================ FILE: zh-hant/codes/csharp/chapter_computational_complexity/time_complexity.cs ================================================ /** * File: time_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class time_complexity { void Algorithm(int n) { int a = 1; // +0(技巧 1) a += n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { Console.WriteLine(0); } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { Console.WriteLine(0); } } } // 演算法 A 時間複雜度:常數階 void AlgorithmA(int n) { Console.WriteLine(0); } // 演算法 B 時間複雜度:線性階 void AlgorithmB(int n) { for (int i = 0; i < n; i++) { Console.WriteLine(0); } } // 演算法 C 時間複雜度:常數階 void AlgorithmC(int n) { for (int i = 0; i < 1000000; i++) { Console.WriteLine(0); } } /* 常數階 */ int Constant(int n) { int count = 0; int size = 100000; for (int i = 0; i < size; i++) count++; return count; } /* 線性階 */ int Linear(int n) { int count = 0; for (int i = 0; i < n; i++) count++; return count; } /* 線性階(走訪陣列) */ int ArrayTraversal(int[] nums) { int count = 0; // 迴圈次數與陣列長度成正比 foreach (int num in nums) { count++; } return count; } /* 平方階 */ int Quadratic(int n) { int count = 0; // 迴圈次數與資料大小 n 成平方關係 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* 平方階(泡沫排序) */ int BubbleSort(int[] nums) { int count = 0; // 計數器 // 外迴圈:未排序區間為 [0, i] for (int i = nums.Length - 1; i > 0; i--) { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); count += 3; // 元素交換包含 3 個單元操作 } } } return count; } /* 指數階(迴圈實現) */ int Exponential(int n) { int count = 0, bas = 1; // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) for (int i = 0; i < n; i++) { for (int j = 0; j < bas; j++) { count++; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指數階(遞迴實現) */ int ExpRecur(int n) { if (n == 1) return 1; return ExpRecur(n - 1) + ExpRecur(n - 1) + 1; } /* 對數階(迴圈實現) */ int Logarithmic(int n) { int count = 0; while (n > 1) { n /= 2; count++; } return count; } /* 對數階(遞迴實現) */ int LogRecur(int n) { if (n <= 1) return 0; return LogRecur(n / 2) + 1; } /* 線性對數階 */ int LinearLogRecur(int n) { if (n <= 1) return 1; int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2); for (int i = 0; i < n; i++) { count++; } return count; } /* 階乘階(遞迴實現) */ int FactorialRecur(int n) { if (n == 0) return 1; int count = 0; // 從 1 個分裂出 n 個 for (int i = 0; i < n; i++) { count += FactorialRecur(n - 1); } return count; } [Test] public void Test() { // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 int n = 8; Console.WriteLine("輸入資料大小 n = " + n); int count = Constant(n); Console.WriteLine("常數階的操作數量 = " + count); count = Linear(n); Console.WriteLine("線性階的操作數量 = " + count); count = ArrayTraversal(new int[n]); Console.WriteLine("線性階(走訪陣列)的操作數量 = " + count); count = Quadratic(n); Console.WriteLine("平方階的操作數量 = " + count); int[] nums = new int[n]; for (int i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = BubbleSort(nums); Console.WriteLine("平方階(泡沫排序)的操作數量 = " + count); count = Exponential(n); Console.WriteLine("指數階(迴圈實現)的操作數量 = " + count); count = ExpRecur(n); Console.WriteLine("指數階(遞迴實現)的操作數量 = " + count); count = Logarithmic(n); Console.WriteLine("對數階(迴圈實現)的操作數量 = " + count); count = LogRecur(n); Console.WriteLine("對數階(遞迴實現)的操作數量 = " + count); count = LinearLogRecur(n); Console.WriteLine("線性對數階(遞迴實現)的操作數量 = " + count); count = FactorialRecur(n); Console.WriteLine("階乘階(遞迴實現)的操作數量 = " + count); } } ================================================ FILE: zh-hant/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs ================================================ /** * File: worst_best_time_complexity.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_computational_complexity; public class worst_best_time_complexity { /* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ int[] RandomNumbers(int n) { int[] nums = new int[n]; // 生成陣列 nums = { 1, 2, 3, ..., n } for (int i = 0; i < n; i++) { nums[i] = i + 1; } // 隨機打亂陣列元素 for (int i = 0; i < nums.Length; i++) { int index = new Random().Next(i, nums.Length); (nums[i], nums[index]) = (nums[index], nums[i]); } return nums; } /* 查詢陣列 nums 中數字 1 所在索引 */ int FindOne(int[] nums) { for (int i = 0; i < nums.Length; i++) { // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) if (nums[i] == 1) return i; } return -1; } /* Driver Code */ [Test] public void Test() { for (int i = 0; i < 10; i++) { int n = 100; int[] nums = RandomNumbers(n); int index = FindOne(nums); Console.WriteLine("\n陣列 [ 1, 2, ..., n ] 被打亂後 = " + string.Join(",", nums)); Console.WriteLine("數字 1 的索引為 " + index); } } } ================================================ FILE: zh-hant/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs ================================================ /** * File: binary_search_recur.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class binary_search_recur { /* 二分搜尋:問題 f(i, j) */ int DFS(int[] nums, int target, int i, int j) { // 若區間為空,代表無目標元素,則返回 -1 if (i > j) { return -1; } // 計算中點索引 m int m = (i + j) / 2; if (nums[m] < target) { // 遞迴子問題 f(m+1, j) return DFS(nums, target, m + 1, j); } else if (nums[m] > target) { // 遞迴子問題 f(i, m-1) return DFS(nums, target, i, m - 1); } else { // 找到目標元素,返回其索引 return m; } } /* 二分搜尋 */ int BinarySearch(int[] nums, int target) { int n = nums.Length; // 求解問題 f(0, n-1) return DFS(nums, target, 0, n - 1); } [Test] public void Test() { int target = 6; int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分搜尋(雙閉區間) int index = BinarySearch(nums, target); Console.WriteLine("目標元素 6 的索引 = " + index); } } ================================================ FILE: zh-hant/codes/csharp/chapter_divide_and_conquer/build_tree.cs ================================================ /** * File: build_tree.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class build_tree { /* 構建二元樹:分治 */ TreeNode? DFS(int[] preorder, Dictionary inorderMap, int i, int l, int r) { // 子樹區間為空時終止 if (r - l < 0) return null; // 初始化根節點 TreeNode root = new(preorder[i]); // 查詢 m ,從而劃分左右子樹 int m = inorderMap[preorder[i]]; // 子問題:構建左子樹 root.left = DFS(preorder, inorderMap, i + 1, l, m - 1); // 子問題:構建右子樹 root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r); // 返回根節點 return root; } /* 構建二元樹 */ TreeNode? BuildTree(int[] preorder, int[] inorder) { // 初始化雜湊表,儲存 inorder 元素到索引的對映 Dictionary inorderMap = []; for (int i = 0; i < inorder.Length; i++) { inorderMap.TryAdd(inorder[i], i); } TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1); return root; } [Test] public void Test() { int[] preorder = [3, 9, 2, 1, 7]; int[] inorder = [9, 3, 1, 2, 7]; Console.WriteLine("前序走訪 = " + string.Join(", ", preorder)); Console.WriteLine("中序走訪 = " + string.Join(", ", inorder)); TreeNode? root = BuildTree(preorder, inorder); Console.WriteLine("構建的二元樹為:"); PrintUtil.PrintTree(root); } } ================================================ FILE: zh-hant/codes/csharp/chapter_divide_and_conquer/hanota.cs ================================================ /** * File: hanota.cs * Created Time: 2023-07-18 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_divide_and_conquer; public class hanota { /* 移動一個圓盤 */ void Move(List src, List tar) { // 從 src 頂部拿出一個圓盤 int pan = src[^1]; src.RemoveAt(src.Count - 1); // 將圓盤放入 tar 頂部 tar.Add(pan); } /* 求解河內塔問題 f(i) */ void DFS(int i, List src, List buf, List tar) { // 若 src 只剩下一個圓盤,則直接將其移到 tar if (i == 1) { Move(src, tar); return; } // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf DFS(i - 1, src, tar, buf); // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar Move(src, tar); // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar DFS(i - 1, buf, src, tar); } /* 求解河內塔問題 */ void SolveHanota(List A, List B, List C) { int n = A.Count; // 將 A 頂部 n 個圓盤藉助 B 移到 C DFS(n, A, B, C); } [Test] public void Test() { // 串列尾部是柱子頂部 List A = [5, 4, 3, 2, 1]; List B = []; List C = []; Console.WriteLine("初始狀態下:"); Console.WriteLine("A = " + string.Join(", ", A)); Console.WriteLine("B = " + string.Join(", ", B)); Console.WriteLine("C = " + string.Join(", ", C)); SolveHanota(A, B, C); Console.WriteLine("圓盤移動完成後:"); Console.WriteLine("A = " + string.Join(", ", A)); Console.WriteLine("B = " + string.Join(", ", B)); Console.WriteLine("C = " + string.Join(", ", C)); } } ================================================ FILE: zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs ================================================ /** * File: climbing_stairs_backtrack.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_backtrack { /* 回溯 */ void Backtrack(List choices, int state, int n, List res) { // 當爬到第 n 階時,方案數量加 1 if (state == n) res[0]++; // 走訪所有選擇 foreach (int choice in choices) { // 剪枝:不允許越過第 n 階 if (state + choice > n) continue; // 嘗試:做出選擇,更新狀態 Backtrack(choices, state + choice, n, res); // 回退 } } /* 爬樓梯:回溯 */ int ClimbingStairsBacktrack(int n) { List choices = [1, 2]; // 可選擇向上爬 1 階或 2 階 int state = 0; // 從第 0 階開始爬 List res = [0]; // 使用 res[0] 記錄方案數量 Backtrack(choices, state, n, res); return res[0]; } [Test] public void Test() { int n = 9; int res = ClimbingStairsBacktrack(n); Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); } } ================================================ FILE: zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs ================================================ /** * File: climbing_stairs_constraint_dp.cs * Created Time: 2023-07-03 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_constraint_dp { /* 帶約束爬樓梯:動態規劃 */ int ClimbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // 初始化 dp 表,用於儲存子問題的解 int[,] dp = new int[n + 1, 3]; // 初始狀態:預設最小子問題的解 dp[1, 1] = 1; dp[1, 2] = 0; dp[2, 1] = 0; dp[2, 2] = 1; // 狀態轉移:從較小子問題逐步求解較大子問題 for (int i = 3; i <= n; i++) { dp[i, 1] = dp[i - 1, 2]; dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2]; } return dp[n, 1] + dp[n, 2]; } [Test] public void Test() { int n = 9; int res = ClimbingStairsConstraintDP(n); Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); } } ================================================ FILE: zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs ================================================ /** * File: climbing_stairs_dfs.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dfs { /* 搜尋 */ int DFS(int i) { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] int count = DFS(i - 1) + DFS(i - 2); return count; } /* 爬樓梯:搜尋 */ int ClimbingStairsDFS(int n) { return DFS(n); } [Test] public void Test() { int n = 9; int res = ClimbingStairsDFS(n); Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); } } ================================================ FILE: zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs ================================================ /** * File: climbing_stairs_dfs_mem.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dfs_mem { /* 記憶化搜尋 */ int DFS(int i, int[] mem) { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i; // 若存在記錄 dp[i] ,則直接返回之 if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = DFS(i - 1, mem) + DFS(i - 2, mem); // 記錄 dp[i] mem[i] = count; return count; } /* 爬樓梯:記憶化搜尋 */ int ClimbingStairsDFSMem(int n) { // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 int[] mem = new int[n + 1]; Array.Fill(mem, -1); return DFS(n, mem); } [Test] public void Test() { int n = 9; int res = ClimbingStairsDFSMem(n); Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); } } ================================================ FILE: zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs ================================================ /** * File: climbing_stairs_dp.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class climbing_stairs_dp { /* 爬樓梯:動態規劃 */ int ClimbingStairsDP(int n) { if (n == 1 || n == 2) return n; // 初始化 dp 表,用於儲存子問題的解 int[] dp = new int[n + 1]; // 初始狀態:預設最小子問題的解 dp[1] = 1; dp[2] = 2; // 狀態轉移:從較小子問題逐步求解較大子問題 for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* 爬樓梯:空間最佳化後的動態規劃 */ int ClimbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } [Test] public void Test() { int n = 9; int res = ClimbingStairsDP(n); Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); res = ClimbingStairsDPComp(n); Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); } } ================================================ FILE: zh-hant/codes/csharp/chapter_dynamic_programming/coin_change.cs ================================================ /** * File: coin_change.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class coin_change { /* 零錢兌換:動態規劃 */ int CoinChangeDP(int[] coins, int amt) { int n = coins.Length; int MAX = amt + 1; // 初始化 dp 表 int[,] dp = new int[n + 1, amt + 1]; // 狀態轉移:首行首列 for (int a = 1; a <= amt; a++) { dp[0, a] = MAX; } // 狀態轉移:其餘行和列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[i, a] = dp[i - 1, a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1); } } } return dp[n, amt] != MAX ? dp[n, amt] : -1; } /* 零錢兌換:空間最佳化後的動態規劃 */ int CoinChangeDPComp(int[] coins, int amt) { int n = coins.Length; int MAX = amt + 1; // 初始化 dp 表 int[] dp = new int[amt + 1]; Array.Fill(dp, MAX); dp[0] = 0; // 狀態轉移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } [Test] public void Test() { int[] coins = [1, 2, 5]; int amt = 4; // 動態規劃 int res = CoinChangeDP(coins, amt); Console.WriteLine("湊到目標金額所需的最少硬幣數量為 " + res); // 空間最佳化後的動態規劃 res = CoinChangeDPComp(coins, amt); Console.WriteLine("湊到目標金額所需的最少硬幣數量為 " + res); } } ================================================ FILE: zh-hant/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs ================================================ /** * File: coin_change_ii.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class coin_change_ii { /* 零錢兌換 II:動態規劃 */ int CoinChangeIIDP(int[] coins, int amt) { int n = coins.Length; // 初始化 dp 表 int[,] dp = new int[n + 1, amt + 1]; // 初始化首列 for (int i = 0; i <= n; i++) { dp[i, 0] = 1; } // 狀態轉移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[i, a] = dp[i - 1, a]; } else { // 不選和選硬幣 i 這兩種方案之和 dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]]; } } } return dp[n, amt]; } /* 零錢兌換 II:空間最佳化後的動態規劃 */ int CoinChangeIIDPComp(int[] coins, int amt) { int n = coins.Length; // 初始化 dp 表 int[] dp = new int[amt + 1]; dp[0] = 1; // 狀態轉移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } [Test] public void Test() { int[] coins = [1, 2, 5]; int amt = 5; // 動態規劃 int res = CoinChangeIIDP(coins, amt); Console.WriteLine("湊出目標金額的硬幣組合數量為 " + res); // 空間最佳化後的動態規劃 res = CoinChangeIIDPComp(coins, amt); Console.WriteLine("湊出目標金額的硬幣組合數量為 " + res); } } ================================================ FILE: zh-hant/codes/csharp/chapter_dynamic_programming/edit_distance.cs ================================================ /** * File: edit_distance.cs * Created Time: 2023-07-14 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class edit_distance { /* 編輯距離:暴力搜尋 */ int EditDistanceDFS(string s, string t, int i, int j) { // 若 s 和 t 都為空,則返回 0 if (i == 0 && j == 0) return 0; // 若 s 為空,則返回 t 長度 if (i == 0) return j; // 若 t 為空,則返回 s 長度 if (j == 0) return i; // 若兩字元相等,則直接跳過此兩字元 if (s[i - 1] == t[j - 1]) return EditDistanceDFS(s, t, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 int insert = EditDistanceDFS(s, t, i, j - 1); int delete = EditDistanceDFS(s, t, i - 1, j); int replace = EditDistanceDFS(s, t, i - 1, j - 1); // 返回最少編輯步數 return Math.Min(Math.Min(insert, delete), replace) + 1; } /* 編輯距離:記憶化搜尋 */ int EditDistanceDFSMem(string s, string t, int[][] mem, int i, int j) { // 若 s 和 t 都為空,則返回 0 if (i == 0 && j == 0) return 0; // 若 s 為空,則返回 t 長度 if (i == 0) return j; // 若 t 為空,則返回 s 長度 if (j == 0) return i; // 若已有記錄,則直接返回之 if (mem[i][j] != -1) return mem[i][j]; // 若兩字元相等,則直接跳過此兩字元 if (s[i - 1] == t[j - 1]) return EditDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 int insert = EditDistanceDFSMem(s, t, mem, i, j - 1); int delete = EditDistanceDFSMem(s, t, mem, i - 1, j); int replace = EditDistanceDFSMem(s, t, mem, i - 1, j - 1); // 記錄並返回最少編輯步數 mem[i][j] = Math.Min(Math.Min(insert, delete), replace) + 1; return mem[i][j]; } /* 編輯距離:動態規劃 */ int EditDistanceDP(string s, string t) { int n = s.Length, m = t.Length; int[,] dp = new int[n + 1, m + 1]; // 狀態轉移:首行首列 for (int i = 1; i <= n; i++) { dp[i, 0] = i; } for (int j = 1; j <= m; j++) { dp[0, j] = j; } // 狀態轉移:其餘行和列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // 若兩字元相等,則直接跳過此兩字元 dp[i, j] = dp[i - 1, j - 1]; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1; } } } return dp[n, m]; } /* 編輯距離:空間最佳化後的動態規劃 */ int EditDistanceDPComp(string s, string t) { int n = s.Length, m = t.Length; int[] dp = new int[m + 1]; // 狀態轉移:首行 for (int j = 1; j <= m; j++) { dp[j] = j; } // 狀態轉移:其餘行 for (int i = 1; i <= n; i++) { // 狀態轉移:首列 int leftup = dp[0]; // 暫存 dp[i-1, j-1] dp[0] = i; // 狀態轉移:其餘列 for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 若兩字元相等,則直接跳過此兩字元 dp[j] = leftup; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 更新為下一輪的 dp[i-1, j-1] } } return dp[m]; } [Test] public void Test() { string s = "bag"; string t = "pack"; int n = s.Length, m = t.Length; // 暴力搜尋 int res = EditDistanceDFS(s, t, n, m); Console.WriteLine("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); // 記憶化搜尋 int[][] mem = new int[n + 1][]; for (int i = 0; i <= n; i++) { mem[i] = new int[m + 1]; Array.Fill(mem[i], -1); } res = EditDistanceDFSMem(s, t, mem, n, m); Console.WriteLine("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); // 動態規劃 res = EditDistanceDP(s, t); Console.WriteLine("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); // 空間最佳化後的動態規劃 res = EditDistanceDPComp(s, t); Console.WriteLine("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); } } ================================================ FILE: zh-hant/codes/csharp/chapter_dynamic_programming/knapsack.cs ================================================ /** * File: knapsack.cs * Created Time: 2023-07-07 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class knapsack { /* 0-1 背包:暴力搜尋 */ int KnapsackDFS(int[] weight, int[] val, int i, int c) { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i == 0 || c == 0) { return 0; } // 若超過背包容量,則只能選擇不放入背包 if (weight[i - 1] > c) { return KnapsackDFS(weight, val, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 int no = KnapsackDFS(weight, val, i - 1, c); int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1]; // 返回兩種方案中價值更大的那一個 return Math.Max(no, yes); } /* 0-1 背包:記憶化搜尋 */ int KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i == 0 || c == 0) { return 0; } // 若已有記錄,則直接返回 if (mem[i][c] != -1) { return mem[i][c]; } // 若超過背包容量,則只能選擇不放入背包 if (weight[i - 1] > c) { return KnapsackDFSMem(weight, val, mem, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 int no = KnapsackDFSMem(weight, val, mem, i - 1, c); int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1]; // 記錄並返回兩種方案中價值更大的那一個 mem[i][c] = Math.Max(no, yes); return mem[i][c]; } /* 0-1 背包:動態規劃 */ int KnapsackDP(int[] weight, int[] val, int cap) { int n = weight.Length; // 初始化 dp 表 int[,] dp = new int[n + 1, cap + 1]; // 狀態轉移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (weight[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[i, c] = dp[i - 1, c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]); } } } return dp[n, cap]; } /* 0-1 背包:空間最佳化後的動態規劃 */ int KnapsackDPComp(int[] weight, int[] val, int cap) { int n = weight.Length; // 初始化 dp 表 int[] dp = new int[cap + 1]; // 狀態轉移 for (int i = 1; i <= n; i++) { // 倒序走訪 for (int c = cap; c > 0; c--) { if (weight[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[c] = dp[c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]); } } } return dp[cap]; } [Test] public void Test() { int[] weight = [10, 20, 30, 40, 50]; int[] val = [50, 120, 150, 210, 240]; int cap = 50; int n = weight.Length; // 暴力搜尋 int res = KnapsackDFS(weight, val, n, cap); Console.WriteLine("不超過背包容量的最大物品價值為 " + res); // 記憶化搜尋 int[][] mem = new int[n + 1][]; for (int i = 0; i <= n; i++) { mem[i] = new int[cap + 1]; Array.Fill(mem[i], -1); } res = KnapsackDFSMem(weight, val, mem, n, cap); Console.WriteLine("不超過背包容量的最大物品價值為 " + res); // 動態規劃 res = KnapsackDP(weight, val, cap); Console.WriteLine("不超過背包容量的最大物品價值為 " + res); // 空間最佳化後的動態規劃 res = KnapsackDPComp(weight, val, cap); Console.WriteLine("不超過背包容量的最大物品價值為 " + res); } } ================================================ FILE: zh-hant/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs ================================================ /** * File: min_cost_climbing_stairs_dp.cs * Created Time: 2023-06-30 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class min_cost_climbing_stairs_dp { /* 爬樓梯最小代價:動態規劃 */ int MinCostClimbingStairsDP(int[] cost) { int n = cost.Length - 1; if (n == 1 || n == 2) return cost[n]; // 初始化 dp 表,用於儲存子問題的解 int[] dp = new int[n + 1]; // 初始狀態:預設最小子問題的解 dp[1] = cost[1]; dp[2] = cost[2]; // 狀態轉移:從較小子問題逐步求解較大子問題 for (int i = 3; i <= n; i++) { dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* 爬樓梯最小代價:空間最佳化後的動態規劃 */ int MinCostClimbingStairsDPComp(int[] cost) { int n = cost.Length - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = Math.Min(a, tmp) + cost[i]; a = tmp; } return b; } [Test] public void Test() { int[] cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; Console.WriteLine("輸入樓梯的代價串列為"); PrintUtil.PrintList(cost); int res = MinCostClimbingStairsDP(cost); Console.WriteLine($"爬完樓梯的最低代價為 {res}"); res = MinCostClimbingStairsDPComp(cost); Console.WriteLine($"爬完樓梯的最低代價為 {res}"); } } ================================================ FILE: zh-hant/codes/csharp/chapter_dynamic_programming/min_path_sum.cs ================================================ /** * File: min_path_sum.cs * Created Time: 2023-07-10 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class min_path_sum { /* 最小路徑和:暴力搜尋 */ int MinPathSumDFS(int[][] grid, int i, int j) { // 若為左上角單元格,則終止搜尋 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 || j < 0) { return int.MaxValue; } // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 int up = MinPathSumDFS(grid, i - 1, j); int left = MinPathSumDFS(grid, i, j - 1); // 返回從左上角到 (i, j) 的最小路徑代價 return Math.Min(left, up) + grid[i][j]; } /* 最小路徑和:記憶化搜尋 */ int MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { // 若為左上角單元格,則終止搜尋 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 || j < 0) { return int.MaxValue; } // 若已有記錄,則直接返回 if (mem[i][j] != -1) { return mem[i][j]; } // 左邊和上邊單元格的最小路徑代價 int up = MinPathSumDFSMem(grid, mem, i - 1, j); int left = MinPathSumDFSMem(grid, mem, i, j - 1); // 記錄並返回左上角到 (i, j) 的最小路徑代價 mem[i][j] = Math.Min(left, up) + grid[i][j]; return mem[i][j]; } /* 最小路徑和:動態規劃 */ int MinPathSumDP(int[][] grid) { int n = grid.Length, m = grid[0].Length; // 初始化 dp 表 int[,] dp = new int[n, m]; dp[0, 0] = grid[0][0]; // 狀態轉移:首行 for (int j = 1; j < m; j++) { dp[0, j] = dp[0, j - 1] + grid[0][j]; } // 狀態轉移:首列 for (int i = 1; i < n; i++) { dp[i, 0] = dp[i - 1, 0] + grid[i][0]; } // 狀態轉移:其餘行和列 for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j]; } } return dp[n - 1, m - 1]; } /* 最小路徑和:空間最佳化後的動態規劃 */ int MinPathSumDPComp(int[][] grid) { int n = grid.Length, m = grid[0].Length; // 初始化 dp 表 int[] dp = new int[m]; dp[0] = grid[0][0]; // 狀態轉移:首行 for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 狀態轉移:其餘行 for (int i = 1; i < n; i++) { // 狀態轉移:首列 dp[0] = dp[0] + grid[i][0]; // 狀態轉移:其餘列 for (int j = 1; j < m; j++) { dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } [Test] public void Test() { int[][] grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2] ]; int n = grid.Length, m = grid[0].Length; // 暴力搜尋 int res = MinPathSumDFS(grid, n - 1, m - 1); Console.WriteLine("從左上角到右下角的最小路徑和為 " + res); // 記憶化搜尋 int[][] mem = new int[n][]; for (int i = 0; i < n; i++) { mem[i] = new int[m]; Array.Fill(mem[i], -1); } res = MinPathSumDFSMem(grid, mem, n - 1, m - 1); Console.WriteLine("從左上角到右下角的最小路徑和為 " + res); // 動態規劃 res = MinPathSumDP(grid); Console.WriteLine("從左上角到右下角的最小路徑和為 " + res); // 空間最佳化後的動態規劃 res = MinPathSumDPComp(grid); Console.WriteLine("從左上角到右下角的最小路徑和為 " + res); } } ================================================ FILE: zh-hant/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs ================================================ /** * File: unbounded_knapsack.cs * Created Time: 2023-07-12 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_dynamic_programming; public class unbounded_knapsack { /* 完全背包:動態規劃 */ int UnboundedKnapsackDP(int[] wgt, int[] val, int cap) { int n = wgt.Length; // 初始化 dp 表 int[,] dp = new int[n + 1, cap + 1]; // 狀態轉移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[i, c] = dp[i - 1, c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]); } } } return dp[n, cap]; } /* 完全背包:空間最佳化後的動態規劃 */ int UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { int n = wgt.Length; // 初始化 dp 表 int[] dp = new int[cap + 1]; // 狀態轉移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[c] = dp[c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } [Test] public void Test() { int[] wgt = [1, 2, 3]; int[] val = [5, 11, 15]; int cap = 4; // 動態規劃 int res = UnboundedKnapsackDP(wgt, val, cap); Console.WriteLine("不超過背包容量的最大物品價值為 " + res); // 空間最佳化後的動態規劃 res = UnboundedKnapsackDPComp(wgt, val, cap); Console.WriteLine("不超過背包容量的最大物品價值為 " + res); } } ================================================ FILE: zh-hant/codes/csharp/chapter_graph/graph_adjacency_list.cs ================================================ /** * File: graph_adjacency_list.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_graph; /* 基於鄰接表實現的無向圖類別 */ public class GraphAdjList { // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 public Dictionary> adjList; /* 建構子 */ public GraphAdjList(Vertex[][] edges) { adjList = []; // 新增所有頂點和邊 foreach (Vertex[] edge in edges) { AddVertex(edge[0]); AddVertex(edge[1]); AddEdge(edge[0], edge[1]); } } /* 獲取頂點數量 */ int Size() { return adjList.Count; } /* 新增邊 */ public void AddEdge(Vertex vet1, Vertex vet2) { if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) throw new InvalidOperationException(); // 新增邊 vet1 - vet2 adjList[vet1].Add(vet2); adjList[vet2].Add(vet1); } /* 刪除邊 */ public void RemoveEdge(Vertex vet1, Vertex vet2) { if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) throw new InvalidOperationException(); // 刪除邊 vet1 - vet2 adjList[vet1].Remove(vet2); adjList[vet2].Remove(vet1); } /* 新增頂點 */ public void AddVertex(Vertex vet) { if (adjList.ContainsKey(vet)) return; // 在鄰接表中新增一個新鏈結串列 adjList.Add(vet, []); } /* 刪除頂點 */ public void RemoveVertex(Vertex vet) { if (!adjList.ContainsKey(vet)) throw new InvalidOperationException(); // 在鄰接表中刪除頂點 vet 對應的鏈結串列 adjList.Remove(vet); // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 foreach (List list in adjList.Values) { list.Remove(vet); } } /* 列印鄰接表 */ public void Print() { Console.WriteLine("鄰接表 ="); foreach (KeyValuePair> pair in adjList) { List tmp = []; foreach (Vertex vertex in pair.Value) tmp.Add(vertex.val); Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); } } } public class graph_adjacency_list { [Test] public void Test() { /* 初始化無向圖 */ Vertex[] v = Vertex.ValsToVets([1, 3, 2, 5, 4]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]] ]; GraphAdjList graph = new(edges); Console.WriteLine("\n初始化後,圖為"); graph.Print(); /* 新增邊 */ // 頂點 1, 2 即 v[0], v[2] graph.AddEdge(v[0], v[2]); Console.WriteLine("\n新增邊 1-2 後,圖為"); graph.Print(); /* 刪除邊 */ // 頂點 1, 3 即 v[0], v[1] graph.RemoveEdge(v[0], v[1]); Console.WriteLine("\n刪除邊 1-3 後,圖為"); graph.Print(); /* 新增頂點 */ Vertex v5 = new(6); graph.AddVertex(v5); Console.WriteLine("\n新增頂點 6 後,圖為"); graph.Print(); /* 刪除頂點 */ // 頂點 3 即 v[1] graph.RemoveVertex(v[1]); Console.WriteLine("\n刪除頂點 3 後,圖為"); graph.Print(); } } ================================================ FILE: zh-hant/codes/csharp/chapter_graph/graph_adjacency_matrix.cs ================================================ /** * File: graph_adjacency_matrix.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_graph; /* 基於鄰接矩陣實現的無向圖類別 */ class GraphAdjMat { List vertices; // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” List> adjMat; // 鄰接矩陣,行列索引對應“頂點索引” /* 建構子 */ public GraphAdjMat(int[] vertices, int[][] edges) { this.vertices = []; this.adjMat = []; // 新增頂點 foreach (int val in vertices) { AddVertex(val); } // 新增邊 // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 foreach (int[] e in edges) { AddEdge(e[0], e[1]); } } /* 獲取頂點數量 */ int Size() { return vertices.Count; } /* 新增頂點 */ public void AddVertex(int val) { int n = Size(); // 向頂點串列中新增新頂點的值 vertices.Add(val); // 在鄰接矩陣中新增一行 List newRow = new(n); for (int j = 0; j < n; j++) { newRow.Add(0); } adjMat.Add(newRow); // 在鄰接矩陣中新增一列 foreach (List row in adjMat) { row.Add(0); } } /* 刪除頂點 */ public void RemoveVertex(int index) { if (index >= Size()) throw new IndexOutOfRangeException(); // 在頂點串列中移除索引 index 的頂點 vertices.RemoveAt(index); // 在鄰接矩陣中刪除索引 index 的行 adjMat.RemoveAt(index); // 在鄰接矩陣中刪除索引 index 的列 foreach (List row in adjMat) { row.RemoveAt(index); } } /* 新增邊 */ // 參數 i, j 對應 vertices 元素索引 public void AddEdge(int i, int j) { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) throw new IndexOutOfRangeException(); // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) adjMat[i][j] = 1; adjMat[j][i] = 1; } /* 刪除邊 */ // 參數 i, j 對應 vertices 元素索引 public void RemoveEdge(int i, int j) { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) throw new IndexOutOfRangeException(); adjMat[i][j] = 0; adjMat[j][i] = 0; } /* 列印鄰接矩陣 */ public void Print() { Console.Write("頂點串列 = "); PrintUtil.PrintList(vertices); Console.WriteLine("鄰接矩陣 ="); PrintUtil.PrintMatrix(adjMat); } } public class graph_adjacency_matrix { [Test] public void Test() { /* 初始化無向圖 */ // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 int[] vertices = [1, 3, 2, 5, 4]; int[][] edges = [ [0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4] ]; GraphAdjMat graph = new(vertices, edges); Console.WriteLine("\n初始化後,圖為"); graph.Print(); /* 新增邊 */ // 頂點 1, 2 的索引分別為 0, 2 graph.AddEdge(0, 2); Console.WriteLine("\n新增邊 1-2 後,圖為"); graph.Print(); /* 刪除邊 */ // 頂點 1, 3 的索引分別為 0, 1 graph.RemoveEdge(0, 1); Console.WriteLine("\n刪除邊 1-3 後,圖為"); graph.Print(); /* 新增頂點 */ graph.AddVertex(6); Console.WriteLine("\n新增頂點 6 後,圖為"); graph.Print(); /* 刪除頂點 */ // 頂點 3 的索引為 1 graph.RemoveVertex(1); Console.WriteLine("\n刪除頂點 3 後,圖為"); graph.Print(); } } ================================================ FILE: zh-hant/codes/csharp/chapter_graph/graph_bfs.cs ================================================ /** * File: graph_bfs.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_graph; public class graph_bfs { /* 廣度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 List GraphBFS(GraphAdjList graph, Vertex startVet) { // 頂點走訪序列 List res = []; // 雜湊集合,用於記錄已被訪問過的頂點 HashSet visited = [startVet]; // 佇列用於實現 BFS Queue que = new(); que.Enqueue(startVet); // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 while (que.Count > 0) { Vertex vet = que.Dequeue(); // 佇列首頂點出隊 res.Add(vet); // 記錄訪問頂點 foreach (Vertex adjVet in graph.adjList[vet]) { if (visited.Contains(adjVet)) { continue; // 跳過已被訪問的頂點 } que.Enqueue(adjVet); // 只入列未訪問的頂點 visited.Add(adjVet); // 標記該頂點已被訪問 } } // 返回頂點走訪序列 return res; } [Test] public void Test() { /* 初始化無向圖 */ Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]] ]; GraphAdjList graph = new(edges); Console.WriteLine("\n初始化後,圖為"); graph.Print(); /* 廣度優先走訪 */ List res = GraphBFS(graph, v[0]); Console.WriteLine("\n廣度優先走訪(BFS)頂點序列為"); Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); } } ================================================ FILE: zh-hant/codes/csharp/chapter_graph/graph_dfs.cs ================================================ /** * File: graph_dfs.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_graph; public class graph_dfs { /* 深度優先走訪輔助函式 */ void DFS(GraphAdjList graph, HashSet visited, List res, Vertex vet) { res.Add(vet); // 記錄訪問頂點 visited.Add(vet); // 標記該頂點已被訪問 // 走訪該頂點的所有鄰接頂點 foreach (Vertex adjVet in graph.adjList[vet]) { if (visited.Contains(adjVet)) { continue; // 跳過已被訪問的頂點 } // 遞迴訪問鄰接頂點 DFS(graph, visited, res, adjVet); } } /* 深度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 List GraphDFS(GraphAdjList graph, Vertex startVet) { // 頂點走訪序列 List res = []; // 雜湊集合,用於記錄已被訪問過的頂點 HashSet visited = []; DFS(graph, visited, res, startVet); return res; } [Test] public void Test() { /* 初始化無向圖 */ Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6]); Vertex[][] edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; GraphAdjList graph = new(edges); Console.WriteLine("\n初始化後,圖為"); graph.Print(); /* 深度優先走訪 */ List res = GraphDFS(graph, v[0]); Console.WriteLine("\n深度優先走訪(DFS)頂點序列為"); Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); } } ================================================ FILE: zh-hant/codes/csharp/chapter_greedy/coin_change_greedy.cs ================================================ /** * File: coin_change_greedy.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class coin_change_greedy { /* 零錢兌換:貪婪 */ int CoinChangeGreedy(int[] coins, int amt) { // 假設 coins 串列有序 int i = coins.Length - 1; int count = 0; // 迴圈進行貪婪選擇,直到無剩餘金額 while (amt > 0) { // 找到小於且最接近剩餘金額的硬幣 while (i > 0 && coins[i] > amt) { i--; } // 選擇 coins[i] amt -= coins[i]; count++; } // 若未找到可行方案,則返回 -1 return amt == 0 ? count : -1; } [Test] public void Test() { // 貪婪:能夠保證找到全域性最優解 int[] coins = [1, 5, 10, 20, 50, 100]; int amt = 186; int res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("湊到 " + amt + " 所需的最少硬幣數量為 " + res); // 貪婪:無法保證找到全域性最優解 coins = [1, 20, 50]; amt = 60; res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("湊到 " + amt + " 所需的最少硬幣數量為 " + res); Console.WriteLine("實際上需要的最少數量為 3 ,即 20 + 20 + 20"); // 貪婪:無法保證找到全域性最優解 coins = [1, 49, 50]; amt = 98; res = CoinChangeGreedy(coins, amt); Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); Console.WriteLine("湊到 " + amt + " 所需的最少硬幣數量為 " + res); Console.WriteLine("實際上需要的最少數量為 2 ,即 49 + 49"); } } ================================================ FILE: zh-hant/codes/csharp/chapter_greedy/fractional_knapsack.cs ================================================ /** * File: fractional_knapsack.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; /* 物品 */ class Item(int w, int v) { public int w = w; // 物品重量 public int v = v; // 物品價值 } public class fractional_knapsack { /* 分數背包:貪婪 */ double FractionalKnapsack(int[] wgt, int[] val, int cap) { // 建立物品串列,包含兩個屬性:重量、價值 Item[] items = new Item[wgt.Length]; for (int i = 0; i < wgt.Length; i++) { items[i] = new Item(wgt[i], val[i]); } // 按照單位價值 item.v / item.w 從高到低進行排序 Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w)); // 迴圈貪婪選擇 double res = 0; foreach (Item item in items) { if (item.w <= cap) { // 若剩餘容量充足,則將當前物品整個裝進背包 res += item.v; cap -= item.w; } else { // 若剩餘容量不足,則將當前物品的一部分裝進背包 res += (double)item.v / item.w * cap; // 已無剩餘容量,因此跳出迴圈 break; } } return res; } [Test] public void Test() { int[] wgt = [10, 20, 30, 40, 50]; int[] val = [50, 120, 150, 210, 240]; int cap = 50; // 貪婪演算法 double res = FractionalKnapsack(wgt, val, cap); Console.WriteLine("不超過背包容量的最大物品價值為 " + res); } } ================================================ FILE: zh-hant/codes/csharp/chapter_greedy/max_capacity.cs ================================================ /** * File: max_capacity.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class max_capacity { /* 最大容量:貪婪 */ int MaxCapacity(int[] ht) { // 初始化 i, j,使其分列陣列兩端 int i = 0, j = ht.Length - 1; // 初始最大容量為 0 int res = 0; // 迴圈貪婪選擇,直至兩板相遇 while (i < j) { // 更新最大容量 int cap = Math.Min(ht[i], ht[j]) * (j - i); res = Math.Max(res, cap); // 向內移動短板 if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } [Test] public void Test() { int[] ht = [3, 8, 5, 2, 7, 7, 3, 4]; // 貪婪演算法 int res = MaxCapacity(ht); Console.WriteLine("最大容量為 " + res); } } ================================================ FILE: zh-hant/codes/csharp/chapter_greedy/max_product_cutting.cs ================================================ /** * File: max_product_cutting.cs * Created Time: 2023-07-21 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_greedy; public class max_product_cutting { /* 最大切分乘積:貪婪 */ int MaxProductCutting(int n) { // 當 n <= 3 時,必須切分出一個 1 if (n <= 3) { return 1 * (n - 1); } // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 int a = n / 3; int b = n % 3; if (b == 1) { // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 return (int)Math.Pow(3, a - 1) * 2 * 2; } if (b == 2) { // 當餘數為 2 時,不做處理 return (int)Math.Pow(3, a) * 2; } // 當餘數為 0 時,不做處理 return (int)Math.Pow(3, a); } [Test] public void Test() { int n = 58; // 貪婪演算法 int res = MaxProductCutting(n); Console.WriteLine("最大切分乘積為" + res); } } ================================================ FILE: zh-hant/codes/csharp/chapter_hashing/array_hash_map.cs ================================================ /** * File: array_hash_map.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_hashing; /* 鍵值對 int->string */ class Pair(int key, string val) { public int key = key; public string val = val; } /* 基於陣列實現的雜湊表 */ class ArrayHashMap { List buckets; public ArrayHashMap() { // 初始化陣列,包含 100 個桶 buckets = []; for (int i = 0; i < 100; i++) { buckets.Add(null); } } /* 雜湊函式 */ int HashFunc(int key) { int index = key % 100; return index; } /* 查詢操作 */ public string? Get(int key) { int index = HashFunc(key); Pair? pair = buckets[index]; if (pair == null) return null; return pair.val; } /* 新增操作 */ public void Put(int key, string val) { Pair pair = new(key, val); int index = HashFunc(key); buckets[index] = pair; } /* 刪除操作 */ public void Remove(int key) { int index = HashFunc(key); // 置為 null ,代表刪除 buckets[index] = null; } /* 獲取所有鍵值對 */ public List PairSet() { List pairSet = []; foreach (Pair? pair in buckets) { if (pair != null) pairSet.Add(pair); } return pairSet; } /* 獲取所有鍵 */ public List KeySet() { List keySet = []; foreach (Pair? pair in buckets) { if (pair != null) keySet.Add(pair.key); } return keySet; } /* 獲取所有值 */ public List ValueSet() { List valueSet = []; foreach (Pair? pair in buckets) { if (pair != null) valueSet.Add(pair.val); } return valueSet; } /* 列印雜湊表 */ public void Print() { foreach (Pair kv in PairSet()) { Console.WriteLine(kv.key + " -> " + kv.val); } } } public class array_hash_map { [Test] public void Test() { /* 初始化雜湊表 */ ArrayHashMap map = new(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.Put(12836, "小哈"); map.Put(15937, "小囉"); map.Put(16750, "小算"); map.Put(13276, "小法"); map.Put(10583, "小鴨"); Console.WriteLine("\n新增完成後,雜湊表為\nKey -> Value"); map.Print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value string? name = map.Get(15937); Console.WriteLine("\n輸入學號 15937 ,查詢到姓名 " + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.Remove(10583); Console.WriteLine("\n刪除 10583 後,雜湊表為\nKey -> Value"); map.Print(); /* 走訪雜湊表 */ Console.WriteLine("\n走訪鍵值對 Key->Value"); foreach (Pair kv in map.PairSet()) { Console.WriteLine(kv.key + " -> " + kv.val); } Console.WriteLine("\n單獨走訪鍵 Key"); foreach (int key in map.KeySet()) { Console.WriteLine(key); } Console.WriteLine("\n單獨走訪值 Value"); foreach (string val in map.ValueSet()) { Console.WriteLine(val); } } } ================================================ FILE: zh-hant/codes/csharp/chapter_hashing/built_in_hash.cs ================================================ /** * File: built_in_hash.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; public class built_in_hash { [Test] public void Test() { int num = 3; int hashNum = num.GetHashCode(); Console.WriteLine("整數 " + num + " 的雜湊值為 " + hashNum); bool bol = true; int hashBol = bol.GetHashCode(); Console.WriteLine("布林量 " + bol + " 的雜湊值為 " + hashBol); double dec = 3.14159; int hashDec = dec.GetHashCode(); Console.WriteLine("小數 " + dec + " 的雜湊值為 " + hashDec); string str = "Hello 演算法"; int hashStr = str.GetHashCode(); Console.WriteLine("字串 " + str + " 的雜湊值為 " + hashStr); object[] arr = [12836, "小哈"]; int hashTup = arr.GetHashCode(); Console.WriteLine("陣列 [" + string.Join(", ", arr) + "] 的雜湊值為 " + hashTup); ListNode obj = new(0); int hashObj = obj.GetHashCode(); Console.WriteLine("節點物件 " + obj + " 的雜湊值為 " + hashObj); } } ================================================ FILE: zh-hant/codes/csharp/chapter_hashing/hash_map.cs ================================================ /** * File: hash_map.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_hashing; public class hash_map { [Test] public void Test() { /* 初始化雜湊表 */ Dictionary map = new() { /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) { 12836, "小哈" }, { 15937, "小囉" }, { 16750, "小算" }, { 13276, "小法" }, { 10583, "小鴨" } }; Console.WriteLine("\n新增完成後,雜湊表為\nKey -> Value"); PrintUtil.PrintHashMap(map); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value string name = map[15937]; Console.WriteLine("\n輸入學號 15937 ,查詢到姓名 " + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.Remove(10583); Console.WriteLine("\n刪除 10583 後,雜湊表為\nKey -> Value"); PrintUtil.PrintHashMap(map); /* 走訪雜湊表 */ Console.WriteLine("\n走訪鍵值對 Key->Value"); foreach (var kv in map) { Console.WriteLine(kv.Key + " -> " + kv.Value); } Console.WriteLine("\n單獨走訪鍵 Key"); foreach (int key in map.Keys) { Console.WriteLine(key); } Console.WriteLine("\n單獨走訪值 Value"); foreach (string val in map.Values) { Console.WriteLine(val); } } } ================================================ FILE: zh-hant/codes/csharp/chapter_hashing/hash_map_chaining.cs ================================================ /** * File: hash_map_chaining.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; /* 鏈式位址雜湊表 */ class HashMapChaining { int size; // 鍵值對數量 int capacity; // 雜湊表容量 double loadThres; // 觸發擴容的負載因子閾值 int extendRatio; // 擴容倍數 List> buckets; // 桶陣列 /* 建構子 */ public HashMapChaining() { size = 0; capacity = 4; loadThres = 2.0 / 3.0; extendRatio = 2; buckets = new List>(capacity); for (int i = 0; i < capacity; i++) { buckets.Add([]); } } /* 雜湊函式 */ int HashFunc(int key) { return key % capacity; } /* 負載因子 */ double LoadFactor() { return (double)size / capacity; } /* 查詢操作 */ public string? Get(int key) { int index = HashFunc(key); // 走訪桶,若找到 key ,則返回對應 val foreach (Pair pair in buckets[index]) { if (pair.key == key) { return pair.val; } } // 若未找到 key ,則返回 null return null; } /* 新增操作 */ public void Put(int key, string val) { // 當負載因子超過閾值時,執行擴容 if (LoadFactor() > loadThres) { Extend(); } int index = HashFunc(key); // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 foreach (Pair pair in buckets[index]) { if (pair.key == key) { pair.val = val; return; } } // 若無該 key ,則將鍵值對新增至尾部 buckets[index].Add(new Pair(key, val)); size++; } /* 刪除操作 */ public void Remove(int key) { int index = HashFunc(key); // 走訪桶,從中刪除鍵值對 foreach (Pair pair in buckets[index].ToList()) { if (pair.key == key) { buckets[index].Remove(pair); size--; break; } } } /* 擴容雜湊表 */ void Extend() { // 暫存原雜湊表 List> bucketsTmp = buckets; // 初始化擴容後的新雜湊表 capacity *= extendRatio; buckets = new List>(capacity); for (int i = 0; i < capacity; i++) { buckets.Add([]); } size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 foreach (List bucket in bucketsTmp) { foreach (Pair pair in bucket) { Put(pair.key, pair.val); } } } /* 列印雜湊表 */ public void Print() { foreach (List bucket in buckets) { List res = []; foreach (Pair pair in bucket) { res.Add(pair.key + " -> " + pair.val); } foreach (string kv in res) { Console.WriteLine(kv); } } } } public class hash_map_chaining { [Test] public void Test() { /* 初始化雜湊表 */ HashMapChaining map = new(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.Put(12836, "小哈"); map.Put(15937, "小囉"); map.Put(16750, "小算"); map.Put(13276, "小法"); map.Put(10583, "小鴨"); Console.WriteLine("\n新增完成後,雜湊表為\nKey -> Value"); map.Print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value string? name = map.Get(13276); Console.WriteLine("\n輸入學號 13276 ,查詢到姓名 " + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.Remove(12836); Console.WriteLine("\n刪除 12836 後,雜湊表為\nKey -> Value"); map.Print(); } } ================================================ FILE: zh-hant/codes/csharp/chapter_hashing/hash_map_open_addressing.cs ================================================ /** * File: hash_map_open_addressing.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; /* 開放定址雜湊表 */ class HashMapOpenAddressing { int size; // 鍵值對數量 int capacity = 4; // 雜湊表容量 double loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 int extendRatio = 2; // 擴容倍數 Pair[] buckets; // 桶陣列 Pair TOMBSTONE = new(-1, "-1"); // 刪除標記 /* 建構子 */ public HashMapOpenAddressing() { size = 0; buckets = new Pair[capacity]; } /* 雜湊函式 */ int HashFunc(int key) { return key % capacity; } /* 負載因子 */ double LoadFactor() { return (double)size / capacity; } /* 搜尋 key 對應的桶索引 */ int FindBucket(int key) { int index = HashFunc(key); int firstTombstone = -1; // 線性探查,當遇到空桶時跳出 while (buckets[index] != null) { // 若遇到 key ,返回對應的桶索引 if (buckets[index].key == key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index]; buckets[index] = TOMBSTONE; return firstTombstone; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % capacity; } // 若 key 不存在,則返回新增點的索引 return firstTombstone == -1 ? index : firstTombstone; } /* 查詢操作 */ public string? Get(int key) { // 搜尋 key 對應的桶索引 int index = FindBucket(key); // 若找到鍵值對,則返回對應 val if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index].val; } // 若鍵值對不存在,則返回 null return null; } /* 新增操作 */ public void Put(int key, string val) { // 當負載因子超過閾值時,執行擴容 if (LoadFactor() > loadThres) { Extend(); } // 搜尋 key 對應的桶索引 int index = FindBucket(key); // 若找到鍵值對,則覆蓋 val 並返回 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index].val = val; return; } // 若鍵值對不存在,則新增該鍵值對 buckets[index] = new Pair(key, val); size++; } /* 刪除操作 */ public void Remove(int key) { // 搜尋 key 對應的桶索引 int index = FindBucket(key); // 若找到鍵值對,則用刪除標記覆蓋它 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE; size--; } } /* 擴容雜湊表 */ void Extend() { // 暫存原雜湊表 Pair[] bucketsTmp = buckets; // 初始化擴容後的新雜湊表 capacity *= extendRatio; buckets = new Pair[capacity]; size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 foreach (Pair pair in bucketsTmp) { if (pair != null && pair != TOMBSTONE) { Put(pair.key, pair.val); } } } /* 列印雜湊表 */ public void Print() { foreach (Pair pair in buckets) { if (pair == null) { Console.WriteLine("null"); } else if (pair == TOMBSTONE) { Console.WriteLine("TOMBSTONE"); } else { Console.WriteLine(pair.key + " -> " + pair.val); } } } } public class hash_map_open_addressing { [Test] public void Test() { /* 初始化雜湊表 */ HashMapOpenAddressing map = new(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.Put(12836, "小哈"); map.Put(15937, "小囉"); map.Put(16750, "小算"); map.Put(13276, "小法"); map.Put(10583, "小鴨"); Console.WriteLine("\n新增完成後,雜湊表為\nKey -> Value"); map.Print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value string? name = map.Get(13276); Console.WriteLine("\n輸入學號 13276 ,查詢到姓名 " + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.Remove(16750); Console.WriteLine("\n刪除 16750 後,雜湊表為\nKey -> Value"); map.Print(); } } ================================================ FILE: zh-hant/codes/csharp/chapter_hashing/simple_hash.cs ================================================ /** * File: simple_hash.cs * Created Time: 2023-06-26 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_hashing; public class simple_hash { /* 加法雜湊 */ int AddHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = (hash + c) % MODULUS; } return (int)hash; } /* 乘法雜湊 */ int MulHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = (31 * hash + c) % MODULUS; } return (int)hash; } /* 互斥或雜湊 */ int XorHash(string key) { int hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash ^= c; } return hash & MODULUS; } /* 旋轉雜湊 */ int RotHash(string key) { long hash = 0; const int MODULUS = 1000000007; foreach (char c in key) { hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS; } return (int)hash; } [Test] public void Test() { string key = "Hello 演算法"; int hash = AddHash(key); Console.WriteLine("加法雜湊值為 " + hash); hash = MulHash(key); Console.WriteLine("乘法雜湊值為 " + hash); hash = XorHash(key); Console.WriteLine("互斥或雜湊值為 " + hash); hash = RotHash(key); Console.WriteLine("旋轉雜湊值為 " + hash); } } ================================================ FILE: zh-hant/codes/csharp/chapter_heap/heap.cs ================================================ /** * File: heap.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_heap; public class heap { void TestPush(PriorityQueue heap, int val) { heap.Enqueue(val, val); // 元素入堆積 Console.WriteLine($"\n元素 {val} 入堆積後\n"); PrintUtil.PrintHeap(heap); } void TestPop(PriorityQueue heap) { int val = heap.Dequeue(); // 堆積頂元素出堆積 Console.WriteLine($"\n堆積頂元素 {val} 出堆積後\n"); PrintUtil.PrintHeap(heap); } [Test] public void Test() { /* 初始化堆積 */ // 初始化小頂堆積 PriorityQueue minHeap = new(); // 初始化大頂堆積(使用 lambda 表示式修改 Comparer 即可) PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); Console.WriteLine("以下測試樣例為大頂堆積"); /* 元素入堆積 */ TestPush(maxHeap, 1); TestPush(maxHeap, 3); TestPush(maxHeap, 2); TestPush(maxHeap, 5); TestPush(maxHeap, 4); /* 獲取堆積頂元素 */ int peek = maxHeap.Peek(); Console.WriteLine($"堆積頂元素為 {peek}"); /* 堆積頂元素出堆積 */ // 出堆積元素會形成一個從大到小的序列 TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); TestPop(maxHeap); /* 獲取堆積大小 */ int size = maxHeap.Count; Console.WriteLine($"堆積元素數量為 {size}"); /* 判斷堆積是否為空 */ bool isEmpty = maxHeap.Count == 0; Console.WriteLine($"堆積是否為空 {isEmpty}"); /* 輸入串列並建堆積 */ var list = new int[] { 1, 3, 2, 5, 4 }; minHeap = new PriorityQueue(list.Select(x => (x, x))); Console.WriteLine("輸入串列並建立小頂堆積後"); PrintUtil.PrintHeap(minHeap); } } ================================================ FILE: zh-hant/codes/csharp/chapter_heap/my_heap.cs ================================================ /** * File: my_heap.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com) */ namespace hello_algo.chapter_heap; /* 大頂堆積 */ class MaxHeap { // 使用串列而非陣列,這樣無須考慮擴容問題 List maxHeap; /* 建構子,建立空堆積 */ public MaxHeap() { maxHeap = []; } /* 建構子,根據輸入串列建堆積 */ public MaxHeap(IEnumerable nums) { // 將串列元素原封不動新增進堆積 maxHeap = new List(nums); // 堆積化除葉節點以外的其他所有節點 var size = Parent(this.Size() - 1); for (int i = size; i >= 0; i--) { SiftDown(i); } } /* 獲取左子節點的索引 */ int Left(int i) { return 2 * i + 1; } /* 獲取右子節點的索引 */ int Right(int i) { return 2 * i + 2; } /* 獲取父節點的索引 */ int Parent(int i) { return (i - 1) / 2; // 向下整除 } /* 訪問堆積頂元素 */ public int Peek() { return maxHeap[0]; } /* 元素入堆積 */ public void Push(int val) { // 新增節點 maxHeap.Add(val); // 從底至頂堆積化 SiftUp(Size() - 1); } /* 獲取堆積大小 */ public int Size() { return maxHeap.Count; } /* 判斷堆積是否為空 */ public bool IsEmpty() { return Size() == 0; } /* 從節點 i 開始,從底至頂堆積化 */ void SiftUp(int i) { while (true) { // 獲取節點 i 的父節點 int p = Parent(i); // 若“越過根節點”或“節點無須修復”,則結束堆積化 if (p < 0 || maxHeap[i] <= maxHeap[p]) break; // 交換兩節點 Swap(i, p); // 迴圈向上堆積化 i = p; } } /* 元素出堆積 */ public int Pop() { // 判空處理 if (IsEmpty()) throw new IndexOutOfRangeException(); // 交換根節點與最右葉節點(交換首元素與尾元素) Swap(0, Size() - 1); // 刪除節點 int val = maxHeap.Last(); maxHeap.RemoveAt(Size() - 1); // 從頂至底堆積化 SiftDown(0); // 返回堆積頂元素 return val; } /* 從節點 i 開始,從頂至底堆積化 */ void SiftDown(int i) { while (true) { // 判斷節點 i, l, r 中值最大的節點,記為 ma int l = Left(i), r = Right(i), ma = i; if (l < Size() && maxHeap[l] > maxHeap[ma]) ma = l; if (r < Size() && maxHeap[r] > maxHeap[ma]) ma = r; // 若“節點 i 最大”或“越過葉節點”,則結束堆積化 if (ma == i) break; // 交換兩節點 Swap(i, ma); // 迴圈向下堆積化 i = ma; } } /* 交換元素 */ void Swap(int i, int p) { (maxHeap[i], maxHeap[p]) = (maxHeap[p], maxHeap[i]); } /* 列印堆積(二元樹) */ public void Print() { var queue = new Queue(maxHeap); PrintUtil.PrintHeap(queue); } } public class my_heap { [Test] public void Test() { /* 初始化大頂堆積 */ MaxHeap maxHeap = new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); Console.WriteLine("\n輸入串列並建堆積後"); maxHeap.Print(); /* 獲取堆積頂元素 */ int peek = maxHeap.Peek(); Console.WriteLine($"堆積頂元素為 {peek}"); /* 元素入堆積 */ int val = 7; maxHeap.Push(val); Console.WriteLine($"元素 {val} 入堆積後"); maxHeap.Print(); /* 堆積頂元素出堆積 */ peek = maxHeap.Pop(); Console.WriteLine($"堆積頂元素 {peek} 出堆積後"); maxHeap.Print(); /* 獲取堆積大小 */ int size = maxHeap.Size(); Console.WriteLine($"堆積元素數量為 {size}"); /* 判斷堆積是否為空 */ bool isEmpty = maxHeap.IsEmpty(); Console.WriteLine($"堆積是否為空 {isEmpty}"); } } ================================================ FILE: zh-hant/codes/csharp/chapter_heap/top_k.cs ================================================ /** * File: top_k.cs * Created Time: 2023-06-14 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_heap; public class top_k { /* 基於堆積查詢陣列中最大的 k 個元素 */ PriorityQueue TopKHeap(int[] nums, int k) { // 初始化小頂堆積 PriorityQueue heap = new(); // 將陣列的前 k 個元素入堆積 for (int i = 0; i < k; i++) { heap.Enqueue(nums[i], nums[i]); } // 從第 k+1 個元素開始,保持堆積的長度為 k for (int i = k; i < nums.Length; i++) { // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 if (nums[i] > heap.Peek()) { heap.Dequeue(); heap.Enqueue(nums[i], nums[i]); } } return heap; } [Test] public void Test() { int[] nums = [1, 7, 6, 3, 2]; int k = 3; PriorityQueue res = TopKHeap(nums, k); Console.WriteLine("最大的 " + k + " 個元素為"); PrintUtil.PrintHeap(res); } } ================================================ FILE: zh-hant/codes/csharp/chapter_searching/binary_search.cs ================================================ /** * File: binary_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class binary_search { /* 二分搜尋(雙閉區間) */ int BinarySearch(int[] nums, int target) { // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 int i = 0, j = nums.Length - 1; // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) while (i <= j) { int m = i + (j - i) / 2; // 計算中點索引 m if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j] 中 i = m + 1; else if (nums[m] > target) // 此情況說明 target 在區間 [i, m-1] 中 j = m - 1; else // 找到目標元素,返回其索引 return m; } // 未找到目標元素,返回 -1 return -1; } /* 二分搜尋(左閉右開區間) */ int BinarySearchLCRO(int[] nums, int target) { // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 int i = 0, j = nums.Length; // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) while (i < j) { int m = i + (j - i) / 2; // 計算中點索引 m if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j) 中 i = m + 1; else if (nums[m] > target) // 此情況說明 target 在區間 [i, m) 中 j = m; else // 找到目標元素,返回其索引 return m; } // 未找到目標元素,返回 -1 return -1; } [Test] public void Test() { int target = 6; int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分搜尋(雙閉區間) */ int index = BinarySearch(nums, target); Console.WriteLine("目標元素 6 的索引 = " + index); /* 二分搜尋(左閉右開區間) */ index = BinarySearchLCRO(nums, target); Console.WriteLine("目標元素 6 的索引 = " + index); } } ================================================ FILE: zh-hant/codes/csharp/chapter_searching/binary_search_edge.cs ================================================ /** * File: binary_search_edge.cs * Created Time: 2023-08-06 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_searching; public class binary_search_edge { /* 二分搜尋最左一個 target */ int BinarySearchLeftEdge(int[] nums, int target) { // 等價於查詢 target 的插入點 int i = binary_search_insertion.BinarySearchInsertion(nums, target); // 未找到 target ,返回 -1 if (i == nums.Length || nums[i] != target) { return -1; } // 找到 target ,返回索引 i return i; } /* 二分搜尋最右一個 target */ int BinarySearchRightEdge(int[] nums, int target) { // 轉化為查詢最左一個 target + 1 int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1); // j 指向最右一個 target ,i 指向首個大於 target 的元素 int j = i - 1; // 未找到 target ,返回 -1 if (j == -1 || nums[j] != target) { return -1; } // 找到 target ,返回索引 j return j; } [Test] public void Test() { // 包含重複元素的陣列 int[] nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; Console.WriteLine("\n陣列 nums = " + nums.PrintList()); // 二分搜尋左邊界和右邊界 foreach (int target in new int[] { 6, 7 }) { int index = BinarySearchLeftEdge(nums, target); Console.WriteLine("最左一個元素 " + target + " 的索引為 " + index); index = BinarySearchRightEdge(nums, target); Console.WriteLine("最右一個元素 " + target + " 的索引為 " + index); } } } ================================================ FILE: zh-hant/codes/csharp/chapter_searching/binary_search_insertion.cs ================================================ /** * File: binary_search_insertion.cs * Created Time: 2023-08-06 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_searching; public class binary_search_insertion { /* 二分搜尋插入點(無重複元素) */ public static int BinarySearchInsertionSimple(int[] nums, int target) { int i = 0, j = nums.Length - 1; // 初始化雙閉區間 [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // 計算中點索引 m if (nums[m] < target) { i = m + 1; // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在區間 [i, m-1] 中 } else { return m; // 找到 target ,返回插入點 m } } // 未找到 target ,返回插入點 i return i; } /* 二分搜尋插入點(存在重複元素) */ public static int BinarySearchInsertion(int[] nums, int target) { int i = 0, j = nums.Length - 1; // 初始化雙閉區間 [0, n-1] while (i <= j) { int m = i + (j - i) / 2; // 計算中點索引 m if (nums[m] < target) { i = m + 1; // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在區間 [i, m-1] 中 } else { j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 } } // 返回插入點 i return i; } [Test] public void Test() { // 無重複元素的陣列 int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; Console.WriteLine("\n陣列 nums = " + nums.PrintList()); // 二分搜尋插入點 foreach (int target in new int[] { 6, 9 }) { int index = BinarySearchInsertionSimple(nums, target); Console.WriteLine("元素 " + target + " 的插入點的索引為 " + index); } // 包含重複元素的陣列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; Console.WriteLine("\n陣列 nums = " + nums.PrintList()); // 二分搜尋插入點 foreach (int target in new int[] { 2, 6, 20 }) { int index = BinarySearchInsertion(nums, target); Console.WriteLine("元素 " + target + " 的插入點的索引為 " + index); } } } ================================================ FILE: zh-hant/codes/csharp/chapter_searching/hashing_search.cs ================================================ /** * File: hashing_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class hashing_search { /* 雜湊查詢(陣列) */ int HashingSearchArray(Dictionary map, int target) { // 雜湊表的 key: 目標元素,value: 索引 // 若雜湊表中無此 key ,返回 -1 return map.GetValueOrDefault(target, -1); } /* 雜湊查詢(鏈結串列) */ ListNode? HashingSearchLinkedList(Dictionary map, int target) { // 雜湊表的 key: 目標節點值,value: 節點物件 // 若雜湊表中無此 key ,返回 null return map.GetValueOrDefault(target); } [Test] public void Test() { int target = 3; /* 雜湊查詢(陣列) */ int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // 初始化雜湊表 Dictionary map = []; for (int i = 0; i < nums.Length; i++) { map[nums[i]] = i; // key: 元素,value: 索引 } int index = HashingSearchArray(map, target); Console.WriteLine("目標元素 3 的索引 = " + index); /* 雜湊查詢(鏈結串列) */ ListNode? head = ListNode.ArrToLinkedList(nums); // 初始化雜湊表 Dictionary map1 = []; while (head != null) { map1[head.val] = head; // key: 節點值,value: 節點 head = head.next; } ListNode? node = HashingSearchLinkedList(map1, target); Console.WriteLine("目標節點值 3 的對應節點物件為 " + node); } } ================================================ FILE: zh-hant/codes/csharp/chapter_searching/linear_search.cs ================================================ /** * File: linear_search.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class linear_search { /* 線性查詢(陣列) */ int LinearSearchArray(int[] nums, int target) { // 走訪陣列 for (int i = 0; i < nums.Length; i++) { // 找到目標元素,返回其索引 if (nums[i] == target) return i; } // 未找到目標元素,返回 -1 return -1; } /* 線性查詢(鏈結串列) */ ListNode? LinearSearchLinkedList(ListNode? head, int target) { // 走訪鏈結串列 while (head != null) { // 找到目標節點,返回之 if (head.val == target) return head; head = head.next; } // 未找到目標節點,返回 null return null; } [Test] public void Test() { int target = 3; /* 在陣列中執行線性查詢 */ int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; int index = LinearSearchArray(nums, target); Console.WriteLine("目標元素 3 的索引 = " + index); /* 在鏈結串列中執行線性查詢 */ ListNode? head = ListNode.ArrToLinkedList(nums); ListNode? node = LinearSearchLinkedList(head, target); Console.WriteLine("目標節點值 3 的對應節點物件為 " + node); } } ================================================ FILE: zh-hant/codes/csharp/chapter_searching/two_sum.cs ================================================ /** * File: two_sum.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_searching; public class two_sum { /* 方法一:暴力列舉 */ 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 [i, j]; } } return []; } /* 方法二:輔助雜湊表 */ int[] TwoSumHashTable(int[] nums, int target) { int size = nums.Length; // 輔助雜湊表,空間複雜度為 O(n) Dictionary dic = []; // 單層迴圈,時間複雜度為 O(n) for (int i = 0; i < size; i++) { if (dic.ContainsKey(target - nums[i])) { return [dic[target - nums[i]], i]; } dic.Add(nums[i], i); } return []; } [Test] public void Test() { // ======= Test Case ======= int[] nums = [2, 7, 11, 15]; int target = 13; // ====== Driver Code ====== // 方法一 int[] res = TwoSumBruteForce(nums, target); Console.WriteLine("方法一 res = " + string.Join(",", res)); // 方法二 res = TwoSumHashTable(nums, target); Console.WriteLine("方法二 res = " + string.Join(",", res)); } } ================================================ FILE: zh-hant/codes/csharp/chapter_sorting/bubble_sort.cs ================================================ /** * File: bubble_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class bubble_sort { /* 泡沫排序 */ void BubbleSort(int[] nums) { // 外迴圈:未排序區間為 [0, i] for (int i = nums.Length - 1; i > 0; i--) { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); } } } } /* 泡沫排序(標誌最佳化)*/ void BubbleSortWithFlag(int[] nums) { // 外迴圈:未排序區間為 [0, i] for (int i = nums.Length - 1; i > 0; i--) { bool flag = false; // 初始化標誌位 // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); flag = true; // 記錄交換元素 } } if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; BubbleSort(nums); Console.WriteLine("泡沫排序完成後 nums = " + string.Join(",", nums)); int[] nums1 = [4, 1, 3, 1, 5, 2]; BubbleSortWithFlag(nums1); Console.WriteLine("泡沫排序完成後 nums1 = " + string.Join(",", nums1)); } } ================================================ FILE: zh-hant/codes/csharp/chapter_sorting/bucket_sort.cs ================================================ /** * File: bucket_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class bucket_sort { /* 桶排序 */ void BucketSort(float[] nums) { // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 int k = nums.Length / 2; List> buckets = []; for (int i = 0; i < k; i++) { buckets.Add([]); } // 1. 將陣列元素分配到各個桶中 foreach (float num in nums) { // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] int i = (int)(num * k); // 將 num 新增進桶 i buckets[i].Add(num); } // 2. 對各個桶執行排序 foreach (List bucket in buckets) { // 使用內建排序函式,也可以替換成其他排序演算法 bucket.Sort(); } // 3. 走訪桶合併結果 int j = 0; foreach (List bucket in buckets) { foreach (float num in bucket) { nums[j++] = num; } } } [Test] public void Test() { // 設輸入資料為浮點數,範圍為 [0, 1) float[] nums = [0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f]; BucketSort(nums); Console.WriteLine("桶排序完成後 nums = " + string.Join(" ", nums)); } } ================================================ FILE: zh-hant/codes/csharp/chapter_sorting/counting_sort.cs ================================================ /** * File: counting_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class counting_sort { /* 計數排序 */ // 簡單實現,無法用於排序物件 void CountingSortNaive(int[] nums) { // 1. 統計陣列最大元素 m int m = 0; foreach (int num in nums) { m = Math.Max(m, num); } // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 int[] counter = new int[m + 1]; foreach (int num in nums) { counter[num]++; } // 3. 走訪 counter ,將各元素填入原陣列 nums int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* 計數排序 */ // 完整實現,可排序物件,並且是穩定排序 void CountingSort(int[] nums) { // 1. 統計陣列最大元素 m int m = 0; foreach (int num in nums) { m = Math.Max(m, num); } // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 int[] counter = new int[m + 1]; foreach (int num in nums) { counter[num]++; } // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. 倒序走訪 nums ,將各元素填入結果陣列 res // 初始化陣列 res 用於記錄結果 int n = nums.Length; int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // 將 num 放置到對應索引處 counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 } // 使用結果陣列 res 覆蓋原陣列 nums for (int i = 0; i < n; i++) { nums[i] = res[i]; } } [Test] public void Test() { int[] nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; CountingSortNaive(nums); Console.WriteLine("計數排序(無法排序物件)完成後 nums = " + string.Join(" ", nums)); int[] nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; CountingSort(nums1); Console.WriteLine("計數排序完成後 nums1 = " + string.Join(" ", nums)); } } ================================================ FILE: zh-hant/codes/csharp/chapter_sorting/heap_sort.cs ================================================ /** * File: heap_sort.cs * Created Time: 2023-06-01 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class heap_sort { /* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ void SiftDown(int[] nums, int n, int i) { while (true) { // 判斷節點 i, l, r 中值最大的節點,記為 ma int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; if (l < n && nums[l] > nums[ma]) ma = l; if (r < n && nums[r] > nums[ma]) ma = r; // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if (ma == i) break; // 交換兩節點 (nums[ma], nums[i]) = (nums[i], nums[ma]); // 迴圈向下堆積化 i = ma; } } /* 堆積排序 */ void HeapSort(int[] nums) { // 建堆積操作:堆積化除葉節點以外的其他所有節點 for (int i = nums.Length / 2 - 1; i >= 0; i--) { SiftDown(nums, nums.Length, i); } // 從堆積中提取最大元素,迴圈 n-1 輪 for (int i = nums.Length - 1; i > 0; i--) { // 交換根節點與最右葉節點(交換首元素與尾元素) (nums[i], nums[0]) = (nums[0], nums[i]); // 以根節點為起點,從頂至底進行堆積化 SiftDown(nums, i, 0); } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; HeapSort(nums); Console.WriteLine("堆積排序完成後 nums = " + string.Join(" ", nums)); } } ================================================ FILE: zh-hant/codes/csharp/chapter_sorting/insertion_sort.cs ================================================ /** * File: insertion_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class insertion_sort { /* 插入排序 */ void InsertionSort(int[] nums) { // 外迴圈:已排序區間為 [0, i-1] for (int i = 1; i < nums.Length; i++) { int bas = nums[i], j = i - 1; // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 while (j >= 0 && nums[j] > bas) { nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 j--; } nums[j + 1] = bas; // 將 base 賦值到正確位置 } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; InsertionSort(nums); Console.WriteLine("插入排序完成後 nums = " + string.Join(",", nums)); } } ================================================ FILE: zh-hant/codes/csharp/chapter_sorting/merge_sort.cs ================================================ /** * File: merge_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; public class merge_sort { /* 合併左子陣列和右子陣列 */ void Merge(int[] nums, int left, int mid, int right) { // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] // 建立一個臨時陣列 tmp ,用於存放合併後的結果 int[] tmp = new int[right - left + 1]; // 初始化左子陣列和右子陣列的起始索引 int i = left, j = mid + 1, k = 0; // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 for (k = 0; k < tmp.Length; ++k) { nums[left + k] = tmp[k]; } } /* 合併排序 */ void MergeSort(int[] nums, int left, int right) { // 終止條件 if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 // 劃分階段 int mid = left + (right - left) / 2; // 計算中點 MergeSort(nums, left, mid); // 遞迴左子陣列 MergeSort(nums, mid + 1, right); // 遞迴右子陣列 // 合併階段 Merge(nums, left, mid, right); } [Test] public void Test() { /* 合併排序 */ int[] nums = [7, 3, 2, 6, 0, 1, 5, 4]; MergeSort(nums, 0, nums.Length - 1); Console.WriteLine("合併排序完成後 nums = " + string.Join(",", nums)); } } ================================================ FILE: zh-hant/codes/csharp/chapter_sorting/quick_sort.cs ================================================ /** * File: quick_sort.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_sorting; class quickSort { /* 元素交換 */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* 哨兵劃分 */ static int Partition(int[] nums, int left, int right) { // 以 nums[left] 為基準數 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 從右向左找首個小於基準數的元素 while (i < j && nums[i] <= nums[left]) i++; // 從左向右找首個大於基準數的元素 Swap(nums, i, j); // 交換這兩個元素 } Swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } /* 快速排序 */ public static void QuickSort(int[] nums, int left, int right) { // 子陣列長度為 1 時終止遞迴 if (left >= right) return; // 哨兵劃分 int pivot = Partition(nums, left, right); // 遞迴左子陣列、右子陣列 QuickSort(nums, left, pivot - 1); QuickSort(nums, pivot + 1, right); } } /* 快速排序類別(中位基準數最佳化) */ class QuickSortMedian { /* 元素交換 */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* 選取三個候選元素的中位數 */ 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; } /* 哨兵劃分(三數取中值) */ static int Partition(int[] 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(int[] nums, int left, int right) { // 子陣列長度為 1 時終止遞迴 if (left >= right) return; // 哨兵劃分 int pivot = Partition(nums, left, right); // 遞迴左子陣列、右子陣列 QuickSort(nums, left, pivot - 1); QuickSort(nums, pivot + 1, right); } } /* 快速排序類別(遞迴深度最佳化) */ class QuickSortTailCall { /* 元素交換 */ static void Swap(int[] nums, int i, int j) { (nums[j], nums[i]) = (nums[i], nums[j]); } /* 哨兵劃分 */ static int Partition(int[] nums, int left, int right) { // 以 nums[left] 為基準數 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 從右向左找首個小於基準數的元素 while (i < j && nums[i] <= nums[left]) i++; // 從左向右找首個大於基準數的元素 Swap(nums, i, j); // 交換這兩個元素 } Swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } /* 快速排序(遞迴深度最佳化) */ public static void QuickSort(int[] 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] } } } } public class quick_sort { [Test] public void Test() { /* 快速排序 */ int[] nums = [2, 4, 1, 0, 3, 5]; quickSort.QuickSort(nums, 0, nums.Length - 1); Console.WriteLine("快速排序完成後 nums = " + string.Join(",", nums)); /* 快速排序(中位基準數最佳化) */ int[] nums1 = [2, 4, 1, 0, 3, 5]; QuickSortMedian.QuickSort(nums1, 0, nums1.Length - 1); Console.WriteLine("快速排序(中位基準數最佳化)完成後 nums1 = " + string.Join(",", nums1)); /* 快速排序(遞迴深度最佳化) */ int[] nums2 = [2, 4, 1, 0, 3, 5]; QuickSortTailCall.QuickSort(nums2, 0, nums2.Length - 1); Console.WriteLine("快速排序(遞迴深度最佳化)完成後 nums2 = " + string.Join(",", nums2)); } } ================================================ FILE: zh-hant/codes/csharp/chapter_sorting/radix_sort.cs ================================================ /** * File: radix_sort.cs * Created Time: 2023-04-13 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class radix_sort { /* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ int Digit(int num, int exp) { // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 return (num / exp) % 10; } /* 計數排序(根據 nums 第 k 位排序) */ void CountingSortDigit(int[] nums, int exp) { // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 int[] counter = new int[10]; int n = nums.Length; // 統計 0~9 各數字的出現次數 for (int i = 0; i < n; i++) { int d = Digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d counter[d]++; // 統計數字 d 的出現次數 } // 求前綴和,將“出現個數”轉換為“陣列索引” for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 倒序走訪,根據桶內統計結果,將各元素填入 res int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int d = Digit(nums[i], exp); int j = counter[d] - 1; // 獲取 d 在陣列中的索引 j res[j] = nums[i]; // 將當前元素填入索引 j counter[d]--; // 將 d 的數量減 1 } // 使用結果覆蓋原陣列 nums for (int i = 0; i < n; i++) { nums[i] = res[i]; } } /* 基數排序 */ void RadixSort(int[] nums) { // 獲取陣列的最大元素,用於判斷最大位數 int m = int.MinValue; foreach (int num in nums) { if (num > m) m = num; } // 按照從低位到高位的順序走訪 for (int exp = 1; exp <= m; exp *= 10) { // 對陣列元素的第 k 位執行計數排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) CountingSortDigit(nums, exp); } } [Test] public void Test() { // 基數排序 int[] nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ]; RadixSort(nums); Console.WriteLine("基數排序完成後 nums = " + string.Join(" ", nums)); } } ================================================ FILE: zh-hant/codes/csharp/chapter_sorting/selection_sort.cs ================================================ /** * File: selection_sort.cs * Created Time: 2023-06-01 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_sorting; public class selection_sort { /* 選擇排序 */ void SelectionSort(int[] nums) { int n = nums.Length; // 外迴圈:未排序區間為 [i, n-1] for (int i = 0; i < n - 1; i++) { // 內迴圈:找到未排序區間內的最小元素 int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // 記錄最小元素的索引 } // 將該最小元素與未排序區間的首個元素交換 (nums[k], nums[i]) = (nums[i], nums[k]); } } [Test] public void Test() { int[] nums = [4, 1, 3, 1, 5, 2]; SelectionSort(nums); Console.WriteLine("選擇排序完成後 nums = " + string.Join(" ", nums)); } } ================================================ FILE: zh-hant/codes/csharp/chapter_stack_and_queue/array_deque.cs ================================================ /** * File: array_deque.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_stack_and_queue; /* 基於環形陣列實現的雙向佇列 */ public class ArrayDeque { int[] nums; // 用於儲存雙向佇列元素的陣列 int front; // 佇列首指標,指向佇列首元素 int queSize; // 雙向佇列長度 /* 建構子 */ public ArrayDeque(int capacity) { nums = new int[capacity]; front = queSize = 0; } /* 獲取雙向佇列的容量 */ int Capacity() { return nums.Length; } /* 獲取雙向佇列的長度 */ public int Size() { return queSize; } /* 判斷雙向佇列是否為空 */ public bool IsEmpty() { return queSize == 0; } /* 計算環形陣列索引 */ int Index(int i) { // 透過取餘操作實現陣列首尾相連 // 當 i 越過陣列尾部後,回到頭部 // 當 i 越過陣列頭部後,回到尾部 return (i + Capacity()) % Capacity(); } /* 佇列首入列 */ public void PushFirst(int num) { if (queSize == Capacity()) { Console.WriteLine("雙向佇列已滿"); return; } // 佇列首指標向左移動一位 // 透過取餘操作實現 front 越過陣列頭部後回到尾部 front = Index(front - 1); // 將 num 新增至佇列首 nums[front] = num; queSize++; } /* 佇列尾入列 */ public void PushLast(int num) { if (queSize == Capacity()) { Console.WriteLine("雙向佇列已滿"); return; } // 計算佇列尾指標,指向佇列尾索引 + 1 int rear = Index(front + queSize); // 將 num 新增至佇列尾 nums[rear] = num; queSize++; } /* 佇列首出列 */ public int PopFirst() { int num = PeekFirst(); // 佇列首指標向後移動一位 front = Index(front + 1); queSize--; return num; } /* 佇列尾出列 */ public int PopLast() { int num = PeekLast(); queSize--; return num; } /* 訪問佇列首元素 */ public int PeekFirst() { if (IsEmpty()) { throw new InvalidOperationException(); } return nums[front]; } /* 訪問佇列尾元素 */ public int PeekLast() { if (IsEmpty()) { throw new InvalidOperationException(); } // 計算尾元素索引 int last = Index(front + queSize - 1); return nums[last]; } /* 返回陣列用於列印 */ public int[] ToArray() { // 僅轉換有效長度範圍內的串列元素 int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[Index(j)]; } return res; } } public class array_deque { [Test] public void Test() { /* 初始化雙向佇列 */ ArrayDeque deque = new(10); deque.PushLast(3); deque.PushLast(2); deque.PushLast(5); Console.WriteLine("雙向佇列 deque = " + string.Join(" ", deque.ToArray())); /* 訪問元素 */ int peekFirst = deque.PeekFirst(); Console.WriteLine("佇列首元素 peekFirst = " + peekFirst); int peekLast = deque.PeekLast(); Console.WriteLine("佇列尾元素 peekLast = " + peekLast); /* 元素入列 */ deque.PushLast(4); Console.WriteLine("元素 4 佇列尾入列後 deque = " + string.Join(" ", deque.ToArray())); deque.PushFirst(1); Console.WriteLine("元素 1 佇列首入列後 deque = " + string.Join(" ", deque.ToArray())); /* 元素出列 */ int popLast = deque.PopLast(); Console.WriteLine("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + string.Join(" ", deque.ToArray())); int popFirst = deque.PopFirst(); Console.WriteLine("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + string.Join(" ", deque.ToArray())); /* 獲取雙向佇列的長度 */ int size = deque.Size(); Console.WriteLine("雙向佇列長度 size = " + size); /* 判斷雙向佇列是否為空 */ bool isEmpty = deque.IsEmpty(); Console.WriteLine("雙向佇列是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/csharp/chapter_stack_and_queue/array_queue.cs ================================================ /** * File: array_queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* 基於環形陣列實現的佇列 */ class ArrayQueue { int[] nums; // 用於儲存佇列元素的陣列 int front; // 佇列首指標,指向佇列首元素 int queSize; // 佇列長度 public ArrayQueue(int capacity) { nums = new int[capacity]; front = queSize = 0; } /* 獲取佇列的容量 */ int Capacity() { return nums.Length; } /* 獲取佇列的長度 */ public int Size() { return queSize; } /* 判斷佇列是否為空 */ public bool IsEmpty() { return queSize == 0; } /* 入列 */ public void Push(int num) { if (queSize == Capacity()) { Console.WriteLine("佇列已滿"); return; } // 計算佇列尾指標,指向佇列尾索引 + 1 // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 int rear = (front + queSize) % Capacity(); // 將 num 新增至佇列尾 nums[rear] = num; queSize++; } /* 出列 */ public int Pop() { int num = Peek(); // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 front = (front + 1) % Capacity(); queSize--; return num; } /* 訪問佇列首元素 */ public int Peek() { if (IsEmpty()) throw new Exception(); return nums[front]; } /* 返回陣列 */ public int[] ToArray() { // 僅轉換有效長度範圍內的串列元素 int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[j % this.Capacity()]; } return res; } } public class array_queue { [Test] public void Test() { /* 初始化佇列 */ int capacity = 10; ArrayQueue queue = new(capacity); /* 元素入列 */ queue.Push(1); queue.Push(3); queue.Push(2); queue.Push(5); queue.Push(4); Console.WriteLine("佇列 queue = " + string.Join(",", queue.ToArray())); /* 訪問佇列首元素 */ int peek = queue.Peek(); Console.WriteLine("佇列首元素 peek = " + peek); /* 元素出列 */ int pop = queue.Pop(); Console.WriteLine("出列元素 pop = " + pop + ",出列後 queue = " + string.Join(",", queue.ToArray())); /* 獲取佇列的長度 */ int size = queue.Size(); Console.WriteLine("佇列長度 size = " + size); /* 判斷佇列是否為空 */ bool isEmpty = queue.IsEmpty(); Console.WriteLine("佇列是否為空 = " + isEmpty); /* 測試環形陣列 */ for (int i = 0; i < 10; i++) { queue.Push(i); queue.Pop(); Console.WriteLine("第 " + i + " 輪入列 + 出列後 queue = " + string.Join(",", queue.ToArray())); } } } ================================================ FILE: zh-hant/codes/csharp/chapter_stack_and_queue/array_stack.cs ================================================ /** * File: array_stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* 基於陣列實現的堆疊 */ class ArrayStack { List stack; public ArrayStack() { // 初始化串列(動態陣列) stack = []; } /* 獲取堆疊的長度 */ public int Size() { return stack.Count; } /* 判斷堆疊是否為空 */ public bool IsEmpty() { return Size() == 0; } /* 入堆疊 */ public void Push(int num) { stack.Add(num); } /* 出堆疊 */ public int Pop() { if (IsEmpty()) throw new Exception(); var val = Peek(); stack.RemoveAt(Size() - 1); return val; } /* 訪問堆疊頂元素 */ public int Peek() { if (IsEmpty()) throw new Exception(); return stack[Size() - 1]; } /* 將 List 轉化為 Array 並返回 */ public int[] ToArray() { return [.. stack]; } } public class array_stack { [Test] public void Test() { /* 初始化堆疊 */ ArrayStack stack = new(); /* 元素入堆疊 */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); Console.WriteLine("堆疊 stack = " + string.Join(",", stack.ToArray())); /* 訪問堆疊頂元素 */ int peek = stack.Peek(); Console.WriteLine("堆疊頂元素 peek = " + peek); /* 元素出堆疊 */ int pop = stack.Pop(); Console.WriteLine("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + string.Join(",", stack.ToArray())); /* 獲取堆疊的長度 */ int size = stack.Size(); Console.WriteLine("堆疊的長度 size = " + size); /* 判斷是否為空 */ bool isEmpty = stack.IsEmpty(); Console.WriteLine("堆疊是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/csharp/chapter_stack_and_queue/deque.cs ================================================ /** * File: deque.cs * Created Time: 2022-12-30 * Author: moonache (microin1301@outlook.com) */ namespace hello_algo.chapter_stack_and_queue; public class deque { [Test] public void Test() { /* 初始化雙向佇列 */ // 在 C# 中,將鏈結串列 LinkedList 看作雙向佇列來使用 LinkedList deque = new(); /* 元素入列 */ deque.AddLast(2); // 新增至佇列尾 deque.AddLast(5); deque.AddLast(4); deque.AddFirst(3); // 新增至佇列首 deque.AddFirst(1); Console.WriteLine("雙向佇列 deque = " + string.Join(",", deque)); /* 訪問元素 */ int? peekFirst = deque.First?.Value; // 佇列首元素 Console.WriteLine("佇列首元素 peekFirst = " + peekFirst); int? peekLast = deque.Last?.Value; // 佇列尾元素 Console.WriteLine("佇列尾元素 peekLast = " + peekLast); /* 元素出列 */ deque.RemoveFirst(); // 佇列首元素出列 Console.WriteLine("佇列首元素出列後 deque = " + string.Join(",", deque)); deque.RemoveLast(); // 佇列尾元素出列 Console.WriteLine("佇列尾元素出列後 deque = " + string.Join(",", deque)); /* 獲取雙向佇列的長度 */ int size = deque.Count; Console.WriteLine("雙向佇列長度 size = " + size); /* 判斷雙向佇列是否為空 */ bool isEmpty = deque.Count == 0; Console.WriteLine("雙向佇列是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs ================================================ /** * File: linkedlist_deque.cs * Created Time: 2023-03-08 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_stack_and_queue; /* 雙向鏈結串列節點 */ public class ListNode(int val) { public int val = val; // 節點值 public ListNode? next = null; // 後繼節點引用 public ListNode? prev = null; // 前驅節點引用 } /* 基於雙向鏈結串列實現的雙向佇列 */ public class LinkedListDeque { ListNode? front, rear; // 頭節點 front, 尾節點 rear int queSize = 0; // 雙向佇列的長度 public LinkedListDeque() { front = null; rear = null; } /* 獲取雙向佇列的長度 */ public int Size() { return queSize; } /* 判斷雙向佇列是否為空 */ public bool IsEmpty() { return Size() == 0; } /* 入列操作 */ void Push(int num, bool isFront) { ListNode node = new(num); // 若鏈結串列為空,則令 front 和 rear 都指向 node if (IsEmpty()) { front = node; rear = node; } // 佇列首入列操作 else if (isFront) { // 將 node 新增至鏈結串列頭部 front!.prev = node; node.next = front; front = node; // 更新頭節點 } // 佇列尾入列操作 else { // 將 node 新增至鏈結串列尾部 rear!.next = node; node.prev = rear; rear = node; // 更新尾節點 } queSize++; // 更新佇列長度 } /* 佇列首入列 */ public void PushFirst(int num) { Push(num, true); } /* 佇列尾入列 */ public void PushLast(int num) { Push(num, false); } /* 出列操作 */ int? Pop(bool isFront) { if (IsEmpty()) throw new Exception(); int? val; // 佇列首出列操作 if (isFront) { val = front?.val; // 暫存頭節點值 // 刪除頭節點 ListNode? fNext = front?.next; if (fNext != null) { fNext.prev = null; front!.next = null; } front = fNext; // 更新頭節點 } // 佇列尾出列操作 else { val = rear?.val; // 暫存尾節點值 // 刪除尾節點 ListNode? rPrev = rear?.prev; if (rPrev != null) { rPrev.next = null; rear!.prev = null; } rear = rPrev; // 更新尾節點 } queSize--; // 更新佇列長度 return val; } /* 佇列首出列 */ public int? PopFirst() { return Pop(true); } /* 佇列尾出列 */ public int? PopLast() { return Pop(false); } /* 訪問佇列首元素 */ public int? PeekFirst() { if (IsEmpty()) throw new Exception(); return front?.val; } /* 訪問佇列尾元素 */ public int? PeekLast() { if (IsEmpty()) throw new Exception(); return rear?.val; } /* 返回陣列用於列印 */ public int?[] ToArray() { ListNode? node = front; int?[] res = new int?[Size()]; for (int i = 0; i < res.Length; i++) { res[i] = node?.val; node = node?.next; } return res; } } public class linkedlist_deque { [Test] public void Test() { /* 初始化雙向佇列 */ LinkedListDeque deque = new(); deque.PushLast(3); deque.PushLast(2); deque.PushLast(5); Console.WriteLine("雙向佇列 deque = " + string.Join(" ", deque.ToArray())); /* 訪問元素 */ int? peekFirst = deque.PeekFirst(); Console.WriteLine("佇列首元素 peekFirst = " + peekFirst); int? peekLast = deque.PeekLast(); Console.WriteLine("佇列尾元素 peekLast = " + peekLast); /* 元素入列 */ deque.PushLast(4); Console.WriteLine("元素 4 佇列尾入列後 deque = " + string.Join(" ", deque.ToArray())); deque.PushFirst(1); Console.WriteLine("元素 1 佇列首入列後 deque = " + string.Join(" ", deque.ToArray())); /* 元素出列 */ int? popLast = deque.PopLast(); Console.WriteLine("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + string.Join(" ", deque.ToArray())); int? popFirst = deque.PopFirst(); Console.WriteLine("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + string.Join(" ", deque.ToArray())); /* 獲取雙向佇列的長度 */ int size = deque.Size(); Console.WriteLine("雙向佇列長度 size = " + size); /* 判斷雙向佇列是否為空 */ bool isEmpty = deque.IsEmpty(); Console.WriteLine("雙向佇列是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs ================================================ /** * File: linkedlist_queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* 基於鏈結串列實現的佇列 */ class LinkedListQueue { ListNode? front, rear; // 頭節點 front ,尾節點 rear int queSize = 0; public LinkedListQueue() { front = null; rear = null; } /* 獲取佇列的長度 */ public int Size() { return queSize; } /* 判斷佇列是否為空 */ public bool IsEmpty() { return Size() == 0; } /* 入列 */ public void Push(int num) { // 在尾節點後新增 num ListNode node = new(num); // 如果佇列為空,則令頭、尾節點都指向該節點 if (front == null) { front = node; rear = node; // 如果佇列不為空,則將該節點新增到尾節點後 } else if (rear != null) { rear.next = node; rear = node; } queSize++; } /* 出列 */ public int Pop() { int num = Peek(); // 刪除頭節點 front = front?.next; queSize--; return num; } /* 訪問佇列首元素 */ public int Peek() { if (IsEmpty()) throw new Exception(); return front!.val; } /* 將鏈結串列轉化為 Array 並返回 */ public int[] ToArray() { if (front == null) return []; ListNode? node = front; int[] res = new int[Size()]; for (int i = 0; i < res.Length; i++) { res[i] = node!.val; node = node.next; } return res; } } public class linkedlist_queue { [Test] public void Test() { /* 初始化佇列 */ LinkedListQueue queue = new(); /* 元素入列 */ queue.Push(1); queue.Push(3); queue.Push(2); queue.Push(5); queue.Push(4); Console.WriteLine("佇列 queue = " + string.Join(",", queue.ToArray())); /* 訪問佇列首元素 */ int peek = queue.Peek(); Console.WriteLine("佇列首元素 peek = " + peek); /* 元素出列 */ int pop = queue.Pop(); Console.WriteLine("出列元素 pop = " + pop + ",出列後 queue = " + string.Join(",", queue.ToArray())); /* 獲取佇列的長度 */ int size = queue.Size(); Console.WriteLine("佇列長度 size = " + size); /* 判斷佇列是否為空 */ bool isEmpty = queue.IsEmpty(); Console.WriteLine("佇列是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs ================================================ /** * File: linkedlist_stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; /* 基於鏈結串列實現的堆疊 */ class LinkedListStack { ListNode? stackPeek; // 將頭節點作為堆疊頂 int stkSize = 0; // 堆疊的長度 public LinkedListStack() { stackPeek = null; } /* 獲取堆疊的長度 */ public int Size() { return stkSize; } /* 判斷堆疊是否為空 */ public bool IsEmpty() { return Size() == 0; } /* 入堆疊 */ public void Push(int num) { ListNode node = new(num) { next = stackPeek }; stackPeek = node; stkSize++; } /* 出堆疊 */ public int Pop() { int num = Peek(); stackPeek = stackPeek!.next; stkSize--; return num; } /* 訪問堆疊頂元素 */ public int Peek() { if (IsEmpty()) throw new Exception(); return stackPeek!.val; } /* 將 List 轉化為 Array 並返回 */ public int[] ToArray() { if (stackPeek == null) return []; ListNode? node = stackPeek; int[] res = new int[Size()]; for (int i = res.Length - 1; i >= 0; i--) { res[i] = node!.val; node = node.next; } return res; } } public class linkedlist_stack { [Test] public void Test() { /* 初始化堆疊 */ LinkedListStack stack = new(); /* 元素入堆疊 */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); Console.WriteLine("堆疊 stack = " + string.Join(",", stack.ToArray())); /* 訪問堆疊頂元素 */ int peek = stack.Peek(); Console.WriteLine("堆疊頂元素 peek = " + peek); /* 元素出堆疊 */ int pop = stack.Pop(); Console.WriteLine("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + string.Join(",", stack.ToArray())); /* 獲取堆疊的長度 */ int size = stack.Size(); Console.WriteLine("堆疊的長度 size = " + size); /* 判斷是否為空 */ bool isEmpty = stack.IsEmpty(); Console.WriteLine("堆疊是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/csharp/chapter_stack_and_queue/queue.cs ================================================ /** * File: queue.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; public class queue { [Test] public void Test() { /* 初始化佇列 */ Queue queue = new(); /* 元素入列 */ queue.Enqueue(1); queue.Enqueue(3); queue.Enqueue(2); queue.Enqueue(5); queue.Enqueue(4); Console.WriteLine("佇列 queue = " + string.Join(",", queue)); /* 訪問佇列首元素 */ int peek = queue.Peek(); Console.WriteLine("佇列首元素 peek = " + peek); /* 元素出列 */ int pop = queue.Dequeue(); Console.WriteLine("出列元素 pop = " + pop + ",出列後 queue = " + string.Join(",", queue)); /* 獲取佇列的長度 */ int size = queue.Count; Console.WriteLine("佇列長度 size = " + size); /* 判斷佇列是否為空 */ bool isEmpty = queue.Count == 0; Console.WriteLine("佇列是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/csharp/chapter_stack_and_queue/stack.cs ================================================ /** * File: stack.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_stack_and_queue; public class stack { [Test] public void Test() { /* 初始化堆疊 */ Stack stack = new(); /* 元素入堆疊 */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); // 請注意,stack.ToArray() 得到的是倒序序列,即索引 0 為堆疊頂 Console.WriteLine("堆疊 stack = " + string.Join(",", stack)); /* 訪問堆疊頂元素 */ int peek = stack.Peek(); Console.WriteLine("堆疊頂元素 peek = " + peek); /* 元素出堆疊 */ int pop = stack.Pop(); Console.WriteLine("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + string.Join(",", stack)); /* 獲取堆疊的長度 */ int size = stack.Count; Console.WriteLine("堆疊的長度 size = " + size); /* 判斷是否為空 */ bool isEmpty = stack.Count == 0; Console.WriteLine("堆疊是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/csharp/chapter_tree/array_binary_tree.cs ================================================ /** * File: array_binary_tree.cs * Created Time: 2023-07-20 * Author: hpstory (hpstory1024@163.com) */ namespace hello_algo.chapter_tree; /* 陣列表示下的二元樹類別 */ public class ArrayBinaryTree(List arr) { List tree = new(arr); /* 串列容量 */ public int Size() { return tree.Count; } /* 獲取索引為 i 節點的值 */ public int? Val(int i) { // 若索引越界,則返回 null ,代表空位 if (i < 0 || i >= Size()) return null; return tree[i]; } /* 獲取索引為 i 節點的左子節點的索引 */ public int Left(int i) { return 2 * i + 1; } /* 獲取索引為 i 節點的右子節點的索引 */ public int Right(int i) { return 2 * i + 2; } /* 獲取索引為 i 節點的父節點的索引 */ public int Parent(int i) { return (i - 1) / 2; } /* 層序走訪 */ public List LevelOrder() { List res = []; // 直接走訪陣列 for (int i = 0; i < Size(); i++) { if (Val(i).HasValue) res.Add(Val(i)!.Value); } return res; } /* 深度優先走訪 */ void DFS(int i, string order, List res) { // 若為空位,則返回 if (!Val(i).HasValue) return; // 前序走訪 if (order == "pre") res.Add(Val(i)!.Value); DFS(Left(i), order, res); // 中序走訪 if (order == "in") res.Add(Val(i)!.Value); DFS(Right(i), order, res); // 後序走訪 if (order == "post") res.Add(Val(i)!.Value); } /* 前序走訪 */ public List PreOrder() { List res = []; DFS(0, "pre", res); return res; } /* 中序走訪 */ public List InOrder() { List res = []; DFS(0, "in", res); return res; } /* 後序走訪 */ public List PostOrder() { List res = []; DFS(0, "post", res); return res; } } public class array_binary_tree { [Test] public void Test() { // 初始化二元樹 // 這裡藉助了一個從陣列直接生成二元樹的函式 List arr = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; TreeNode? root = TreeNode.ListToTree(arr); Console.WriteLine("\n初始化二元樹\n"); Console.WriteLine("二元樹的陣列表示:"); Console.WriteLine(arr.PrintList()); Console.WriteLine("二元樹的鏈結串列表示:"); PrintUtil.PrintTree(root); // 陣列表示下的二元樹類別 ArrayBinaryTree abt = new(arr); // 訪問節點 int i = 1; int l = abt.Left(i); int r = abt.Right(i); int p = abt.Parent(i); Console.WriteLine("\n當前節點的索引為 " + i + " ,值為 " + abt.Val(i)); Console.WriteLine("其左子節點的索引為 " + l + " ,值為 " + (abt.Val(l).HasValue ? abt.Val(l) : "null")); Console.WriteLine("其右子節點的索引為 " + r + " ,值為 " + (abt.Val(r).HasValue ? abt.Val(r) : "null")); Console.WriteLine("其父節點的索引為 " + p + " ,值為 " + (abt.Val(p).HasValue ? abt.Val(p) : "null")); // 走訪樹 List res = abt.LevelOrder(); Console.WriteLine("\n層序走訪為:" + res.PrintList()); res = abt.PreOrder(); Console.WriteLine("前序走訪為:" + res.PrintList()); res = abt.InOrder(); Console.WriteLine("中序走訪為:" + res.PrintList()); res = abt.PostOrder(); Console.WriteLine("後序走訪為:" + res.PrintList()); } } ================================================ FILE: zh-hant/codes/csharp/chapter_tree/avl_tree.cs ================================================ /** * File: avl_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; /* AVL 樹 */ class AVLTree { public TreeNode? root; // 根節點 /* 獲取節點高度 */ int Height(TreeNode? node) { // 空節點高度為 -1 ,葉節點高度為 0 return node == null ? -1 : node.height; } /* 更新節點高度 */ void UpdateHeight(TreeNode node) { // 節點高度等於最高子樹高度 + 1 node.height = Math.Max(Height(node.left), Height(node.right)) + 1; } /* 獲取平衡因子 */ public int BalanceFactor(TreeNode? node) { // 空節點平衡因子為 0 if (node == null) return 0; // 節點平衡因子 = 左子樹高度 - 右子樹高度 return Height(node.left) - Height(node.right); } /* 右旋操作 */ TreeNode? RightRotate(TreeNode? node) { TreeNode? child = node?.left; TreeNode? grandChild = child?.right; // 以 child 為原點,將 node 向右旋轉 child.right = node; node.left = grandChild; // 更新節點高度 UpdateHeight(node); UpdateHeight(child); // 返回旋轉後子樹的根節點 return child; } /* 左旋操作 */ TreeNode? LeftRotate(TreeNode? node) { TreeNode? child = node?.right; TreeNode? grandChild = child?.left; // 以 child 為原點,將 node 向左旋轉 child.left = node; node.right = grandChild; // 更新節點高度 UpdateHeight(node); UpdateHeight(child); // 返回旋轉後子樹的根節點 return child; } /* 執行旋轉操作,使該子樹重新恢復平衡 */ TreeNode? Rotate(TreeNode? node) { // 獲取節點 node 的平衡因子 int balanceFactorInt = BalanceFactor(node); // 左偏樹 if (balanceFactorInt > 1) { if (BalanceFactor(node?.left) >= 0) { // 右旋 return RightRotate(node); } else { // 先左旋後右旋 node!.left = LeftRotate(node!.left); return RightRotate(node); } } // 右偏樹 if (balanceFactorInt < -1) { if (BalanceFactor(node?.right) <= 0) { // 左旋 return LeftRotate(node); } else { // 先右旋後左旋 node!.right = RightRotate(node!.right); return LeftRotate(node); } } // 平衡樹,無須旋轉,直接返回 return node; } /* 插入節點 */ public void Insert(int val) { root = InsertHelper(root, val); } /* 遞迴插入節點(輔助方法) */ TreeNode? InsertHelper(TreeNode? node, int val) { if (node == null) return new TreeNode(val); /* 1. 查詢插入位置並插入節點 */ if (val < node.val) node.left = InsertHelper(node.left, val); else if (val > node.val) node.right = InsertHelper(node.right, val); else return node; // 重複節點不插入,直接返回 UpdateHeight(node); // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = Rotate(node); // 返回子樹的根節點 return node; } /* 刪除節點 */ public void Remove(int val) { root = RemoveHelper(root, val); } /* 遞迴刪除節點(輔助方法) */ TreeNode? RemoveHelper(TreeNode? node, int val) { if (node == null) return null; /* 1. 查詢節點並刪除 */ if (val < node.val) node.left = RemoveHelper(node.left, val); else if (val > node.val) node.right = RemoveHelper(node.right, val); else { if (node.left == null || node.right == null) { TreeNode? child = node.left ?? node.right; // 子節點數量 = 0 ,直接刪除 node 並返回 if (child == null) return null; // 子節點數量 = 1 ,直接刪除 node else node = child; } else { // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 TreeNode? temp = node.right; while (temp.left != null) { temp = temp.left; } node.right = RemoveHelper(node.right, temp.val!.Value); node.val = temp.val; } } UpdateHeight(node); // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = Rotate(node); // 返回子樹的根節點 return node; } /* 查詢節點 */ public TreeNode? Search(int val) { TreeNode? cur = root; // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 目標節點在 cur 的右子樹中 if (cur.val < val) cur = cur.right; // 目標節點在 cur 的左子樹中 else if (cur.val > val) cur = cur.left; // 找到目標節點,跳出迴圈 else break; } // 返回目標節點 return cur; } } public class avl_tree { static void TestInsert(AVLTree tree, int val) { tree.Insert(val); Console.WriteLine("\n插入節點 " + val + " 後,AVL 樹為"); PrintUtil.PrintTree(tree.root); } static void TestRemove(AVLTree tree, int val) { tree.Remove(val); Console.WriteLine("\n刪除節點 " + val + " 後,AVL 樹為"); PrintUtil.PrintTree(tree.root); } [Test] public void Test() { /* 初始化空 AVL 樹 */ AVLTree avlTree = new(); /* 插入節點 */ // 請關注插入節點後,AVL 樹是如何保持平衡的 TestInsert(avlTree, 1); TestInsert(avlTree, 2); TestInsert(avlTree, 3); TestInsert(avlTree, 4); TestInsert(avlTree, 5); TestInsert(avlTree, 8); TestInsert(avlTree, 7); TestInsert(avlTree, 9); TestInsert(avlTree, 10); TestInsert(avlTree, 6); /* 插入重複節點 */ TestInsert(avlTree, 7); /* 刪除節點 */ // 請關注刪除節點後,AVL 樹是如何保持平衡的 TestRemove(avlTree, 8); // 刪除度為 0 的節點 TestRemove(avlTree, 5); // 刪除度為 1 的節點 TestRemove(avlTree, 4); // 刪除度為 2 的節點 /* 查詢節點 */ TreeNode? node = avlTree.Search(7); Console.WriteLine("\n查詢到的節點物件為 " + node + ",節點值 = " + node?.val); } } ================================================ FILE: zh-hant/codes/csharp/chapter_tree/binary_search_tree.cs ================================================ /** * File: binary_search_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; class BinarySearchTree { TreeNode? root; public BinarySearchTree() { // 初始化空樹 root = null; } /* 獲取二元樹根節點 */ public TreeNode? GetRoot() { return root; } /* 查詢節點 */ public TreeNode? Search(int num) { TreeNode? cur = root; // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 目標節點在 cur 的右子樹中 if (cur.val < num) cur = cur.right; // 目標節點在 cur 的左子樹中 else if (cur.val > num) cur = cur.left; // 找到目標節點,跳出迴圈 else break; } // 返回目標節點 return cur; } /* 插入節點 */ public void Insert(int num) { // 若樹為空,則初始化根節點 if (root == null) { root = new TreeNode(num); return; } TreeNode? cur = root, pre = null; // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 找到重複節點,直接返回 if (cur.val == num) return; pre = cur; // 插入位置在 cur 的右子樹中 if (cur.val < num) cur = cur.right; // 插入位置在 cur 的左子樹中 else cur = cur.left; } // 插入節點 TreeNode node = new(num); if (pre != null) { if (pre.val < num) pre.right = node; else pre.left = node; } } /* 刪除節點 */ public void Remove(int num) { // 若樹為空,直接提前返回 if (root == null) return; TreeNode? cur = root, pre = null; // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 找到待刪除節點,跳出迴圈 if (cur.val == num) break; pre = cur; // 待刪除節點在 cur 的右子樹中 if (cur.val < num) cur = cur.right; // 待刪除節點在 cur 的左子樹中 else cur = cur.left; } // 若無待刪除節點,則直接返回 if (cur == null) return; // 子節點數量 = 0 or 1 if (cur.left == null || cur.right == null) { // 當子節點數量 = 0 / 1 時, child = null / 該子節點 TreeNode? child = cur.left ?? cur.right; // 刪除節點 cur if (cur != root) { if (pre!.left == cur) pre.left = child; else pre.right = child; } else { // 若刪除節點為根節點,則重新指定根節點 root = child; } } // 子節點數量 = 2 else { // 獲取中序走訪中 cur 的下一個節點 TreeNode? tmp = cur.right; while (tmp.left != null) { tmp = tmp.left; } // 遞迴刪除節點 tmp Remove(tmp.val!.Value); // 用 tmp 覆蓋 cur cur.val = tmp.val; } } } public class binary_search_tree { [Test] public void Test() { /* 初始化二元搜尋樹 */ BinarySearchTree bst = new(); // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 int[] nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; foreach (int num in nums) { bst.Insert(num); } Console.WriteLine("\n初始化的二元樹為\n"); PrintUtil.PrintTree(bst.GetRoot()); /* 查詢節點 */ TreeNode? node = bst.Search(7); Console.WriteLine("\n查詢到的節點物件為 " + node + ",節點值 = " + node?.val); /* 插入節點 */ bst.Insert(16); Console.WriteLine("\n插入節點 16 後,二元樹為\n"); PrintUtil.PrintTree(bst.GetRoot()); /* 刪除節點 */ bst.Remove(1); Console.WriteLine("\n刪除節點 1 後,二元樹為\n"); PrintUtil.PrintTree(bst.GetRoot()); bst.Remove(2); Console.WriteLine("\n刪除節點 2 後,二元樹為\n"); PrintUtil.PrintTree(bst.GetRoot()); bst.Remove(4); Console.WriteLine("\n刪除節點 4 後,二元樹為\n"); PrintUtil.PrintTree(bst.GetRoot()); } } ================================================ FILE: zh-hant/codes/csharp/chapter_tree/binary_tree.cs ================================================ /** * File: binary_tree.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree { [Test] public void Test() { /* 初始化二元樹 */ // 初始化節點 TreeNode n1 = new(1); TreeNode n2 = new(2); TreeNode n3 = new(3); TreeNode n4 = new(4); TreeNode n5 = new(5); // 構建節點之間的引用(指標) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; Console.WriteLine("\n初始化二元樹\n"); PrintUtil.PrintTree(n1); /* 插入與刪除節點 */ TreeNode P = new(0); // 在 n1 -> n2 中間插入節點 P n1.left = P; P.left = n2; Console.WriteLine("\n插入節點 P 後\n"); PrintUtil.PrintTree(n1); // 刪除節點 P n1.left = n2; Console.WriteLine("\n刪除節點 P 後\n"); PrintUtil.PrintTree(n1); } } ================================================ FILE: zh-hant/codes/csharp/chapter_tree/binary_tree_bfs.cs ================================================ /** * File: binary_tree_bfs.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree_bfs { /* 層序走訪 */ List LevelOrder(TreeNode root) { // 初始化佇列,加入根節點 Queue queue = new(); queue.Enqueue(root); // 初始化一個串列,用於儲存走訪序列 List list = []; while (queue.Count != 0) { TreeNode node = queue.Dequeue(); // 隊列出隊 list.Add(node.val!.Value); // 儲存節點值 if (node.left != null) queue.Enqueue(node.left); // 左子節點入列 if (node.right != null) queue.Enqueue(node.right); // 右子節點入列 } return list; } [Test] public void Test() { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); Console.WriteLine("\n初始化二元樹\n"); PrintUtil.PrintTree(root); List list = LevelOrder(root!); Console.WriteLine("\n層序走訪的節點列印序列 = " + string.Join(",", list)); } } ================================================ FILE: zh-hant/codes/csharp/chapter_tree/binary_tree_dfs.cs ================================================ /** * File: binary_tree_dfs.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.chapter_tree; public class binary_tree_dfs { List list = []; /* 前序走訪 */ void PreOrder(TreeNode? root) { if (root == null) return; // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 list.Add(root.val!.Value); PreOrder(root.left); PreOrder(root.right); } /* 中序走訪 */ void InOrder(TreeNode? root) { if (root == null) return; // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 InOrder(root.left); list.Add(root.val!.Value); InOrder(root.right); } /* 後序走訪 */ void PostOrder(TreeNode? root) { if (root == null) return; // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 PostOrder(root.left); PostOrder(root.right); list.Add(root.val!.Value); } [Test] public void Test() { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); Console.WriteLine("\n初始化二元樹\n"); PrintUtil.PrintTree(root); list.Clear(); PreOrder(root); Console.WriteLine("\n前序走訪的節點列印序列 = " + string.Join(",", list)); list.Clear(); InOrder(root); Console.WriteLine("\n中序走訪的節點列印序列 = " + string.Join(",", list)); list.Clear(); PostOrder(root); Console.WriteLine("\n後序走訪的節點列印序列 = " + string.Join(",", list)); } } ================================================ FILE: zh-hant/codes/csharp/csharp.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.002.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hello-algo", "hello-algo.csproj", "{48B60439-EFDC-4C8F-AE8D-41979958C8AC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.Build.0 = Debug|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1E773F8A-FF66-4974-820B-FCE9032D19AE} EndGlobalSection EndGlobal ================================================ FILE: zh-hant/codes/csharp/hello-algo.csproj ================================================  Exe net8.0 hello_algo enable enable all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: zh-hant/codes/csharp/utils/ListNode.cs ================================================ // File: ListNode.cs // Created Time: 2022-12-16 // Author: mingXta (1195669834@qq.com) namespace hello_algo.utils; /* 鏈結串列節點 */ public class ListNode(int x) { public int val = x; public ListNode? next; /* 將陣列反序列化為鏈結串列 */ public static ListNode? ArrToLinkedList(int[] arr) { ListNode dum = new(0); ListNode head = dum; foreach (int val in arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } public override string? ToString() { List list = []; var head = this; while (head != null) { list.Add(head.val.ToString()); head = head.next; } return string.Join("->", list); } } ================================================ FILE: zh-hant/codes/csharp/utils/PrintUtil.cs ================================================ /** * File: PrintUtil.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com), krahets (krahets@163.com) */ namespace hello_algo.utils; public class Trunk(Trunk? prev, string str) { public Trunk? prev = prev; public string str = str; }; public static class PrintUtil { /* 列印串列 */ public static void PrintList(IList list) { Console.WriteLine("[" + string.Join(", ", list) + "]"); } public static string PrintList(this IEnumerable list) { return $"[ {string.Join(", ", list.Select(x => x?.ToString() ?? "null"))} ]"; } /* 列印矩陣 (Array) */ public static void PrintMatrix(T[][] matrix) { Console.WriteLine("["); foreach (T[] row in matrix) { Console.WriteLine(" " + string.Join(", ", row) + ","); } Console.WriteLine("]"); } /* 列印矩陣 (List) */ public static void PrintMatrix(List> matrix) { Console.WriteLine("["); foreach (List row in matrix) { Console.WriteLine(" " + string.Join(", ", row) + ","); } Console.WriteLine("]"); } /* 列印鏈結串列 */ public static void PrintLinkedList(ListNode? head) { List list = []; while (head != null) { list.Add(head.val.ToString()); head = head.next; } Console.Write(string.Join(" -> ", list)); } /** * 列印二元樹 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ public static void PrintTree(TreeNode? root) { PrintTree(root, null, false); } /* 列印二元樹 */ public static void PrintTree(TreeNode? root, Trunk? prev, bool isRight) { if (root == null) { return; } string prev_str = " "; Trunk trunk = new(prev, prev_str); PrintTree(root.right, trunk, true); if (prev == null) { trunk.str = "———"; } else if (isRight) { trunk.str = "/———"; prev_str = " |"; } else { trunk.str = "\\———"; prev.str = prev_str; } ShowTrunks(trunk); Console.WriteLine(" " + root.val); if (prev != null) { prev.str = prev_str; } trunk.str = " |"; PrintTree(root.left, trunk, false); } public static void ShowTrunks(Trunk? p) { if (p == null) { return; } ShowTrunks(p.prev); Console.Write(p.str); } /* 列印雜湊表 */ public static void PrintHashMap(Dictionary map) where K : notnull { foreach (var kv in map.Keys) { Console.WriteLine(kv.ToString() + " -> " + map[kv]?.ToString()); } } /* 列印堆積 */ public static void PrintHeap(Queue queue) { Console.Write("堆積的陣列表示:"); List list = [.. queue]; Console.WriteLine(string.Join(',', list)); Console.WriteLine("堆積的樹狀表示:"); TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); PrintTree(tree); } /* 列印優先佇列 */ public static void PrintHeap(PriorityQueue queue) { var newQueue = new PriorityQueue(queue.UnorderedItems, queue.Comparer); Console.Write("堆積的陣列表示:"); List list = []; while (newQueue.TryDequeue(out int element, out _)) { list.Add(element); } Console.WriteLine("堆積的樹狀表示:"); Console.WriteLine(string.Join(',', list.ToList())); TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); PrintTree(tree); } } ================================================ FILE: zh-hant/codes/csharp/utils/TreeNode.cs ================================================ /** * File: TreeNode.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ namespace hello_algo.utils; /* 二元樹節點類別 */ public class TreeNode(int? x) { public int? val = x; // 節點值 public int height; // 節點高度 public TreeNode? left; // 左子節點引用 public TreeNode? right; // 右子節點引用 // 序列化編碼規則請參考: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二元樹的陣列表示: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // 二元樹的鏈結串列表示: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* 將串列反序列化為二元樹:遞迴 */ static TreeNode? ListToTreeDFS(List arr, int i) { if (i < 0 || i >= arr.Count || !arr[i].HasValue) { return null; } TreeNode root = new(arr[i]) { left = ListToTreeDFS(arr, 2 * i + 1), right = ListToTreeDFS(arr, 2 * i + 2) }; return root; } /* 將串列反序列化為二元樹 */ public static TreeNode? ListToTree(List arr) { return ListToTreeDFS(arr, 0); } /* 將二元樹序列化為串列:遞迴 */ static void TreeToListDFS(TreeNode? root, int i, List res) { if (root == null) return; while (i >= res.Count) { res.Add(null); } res[i] = root.val; TreeToListDFS(root.left, 2 * i + 1, res); TreeToListDFS(root.right, 2 * i + 2, res); } /* 將二元樹序列化為串列 */ public static List TreeToList(TreeNode root) { List res = []; TreeToListDFS(root, 0, res); return res; } } ================================================ FILE: zh-hant/codes/csharp/utils/Vertex.cs ================================================ /** * File: Vertex.cs * Created Time: 2023-02-06 * Author: zjkung1123 (zjkung1123@gmail.com), krahets (krahets@163.com) */ namespace hello_algo.utils; /* 頂點類別 */ public class Vertex(int val) { public int val = val; /* 輸入值串列 vals ,返回頂點串列 vets */ public static Vertex[] ValsToVets(int[] vals) { Vertex[] vets = new Vertex[vals.Length]; for (int i = 0; i < vals.Length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* 輸入頂點串列 vets ,返回值串列 vals */ public static List VetsToVals(List vets) { List vals = []; foreach (Vertex vet in vets) { vals.Add(vet.val); } return vals; } } ================================================ FILE: zh-hant/codes/dart/build.dart ================================================ import 'dart:io'; void main() { Directory foldPath = Directory('codes/dart/'); List files = foldPath.listSync(); int totalCount = 0; int errorCount = 0; for (var file in files) { if (file.path.endsWith('build.dart')) continue; if (file is File && file.path.endsWith('.dart')) { totalCount++; try { Process.runSync('dart', [file.path]); } catch (e) { errorCount++; print('Error: $e'); print('File: ${file.path}'); } } else if (file is Directory) { List subFiles = file.listSync(); for (var subFile in subFiles) { if (subFile is File && subFile.path.endsWith('.dart')) { totalCount++; try { Process.runSync('dart', [subFile.path]); } catch (e) { errorCount++; print('Error: $e'); print('File: ${file.path}'); } } } } } print('===== Build Complete ====='); print('Total: $totalCount'); print('Error: $errorCount'); } ================================================ FILE: zh-hant/codes/dart/chapter_array_and_linkedlist/array.dart ================================================ /** * File: array.dart * Created Time: 2023-01-20 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable import 'dart:math'; /* 隨機訪問元素 */ int randomAccess(List nums) { // 在區間 [0, nums.length) 中隨機抽取一個數字 int randomIndex = Random().nextInt(nums.length); // 獲取並返回隨機元素 int randomNum = nums[randomIndex]; return randomNum; } /* 擴展陣列長度 */ List extend(List nums, int enlarge) { // 初始化一個擴展長度後的陣列 List res = List.filled(nums.length + enlarge, 0); // 將原陣列中的所有元素複製到新陣列 for (var i = 0; i < nums.length; i++) { res[i] = nums[i]; } // 返回擴展後的新陣列 return res; } /* 在陣列的索引 index 處插入元素 _num */ void insert(List nums, int _num, int index) { // 把索引 index 以及之後的所有元素向後移動一位 for (var i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 將 _num 賦給 index 處元素 nums[index] = _num; } /* 刪除索引 index 處的元素 */ void remove(List nums, int index) { // 把索引 index 之後的所有元素向前移動一位 for (var i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* 走訪陣列元素 */ void traverse(List nums) { int count = 0; // 透過索引走訪陣列 for (var i = 0; i < nums.length; i++) { count += nums[i]; } // 直接走訪陣列元素 for (int _num in nums) { count += _num; } // 透過 forEach 方法走訪陣列 nums.forEach((_num) { count += _num; }); } /* 在陣列中查詢指定元素 */ int find(List nums, int target) { for (var i = 0; i < nums.length; i++) { if (nums[i] == target) return i; } return -1; } /* Driver Code */ void main() { /* 初始化陣列 */ var arr = List.filled(5, 0); print('陣列 arr = $arr'); List nums = [1, 3, 2, 5, 4]; print('陣列 nums = $nums'); /* 隨機訪問 */ int randomNum = randomAccess(nums); print('在 nums 中獲取隨機元素 $randomNum'); /* 長度擴展 */ nums = extend(nums, 3); print('將陣列長度擴展至 8 ,得到 nums = $nums'); /* 插入元素 */ insert(nums, 6, 3); print("在索引 3 處插入數字 6 ,得到 nums = $nums"); /* 刪除元素 */ remove(nums, 2); print("刪除索引 2 處的元素,得到 nums = $nums"); /* 走訪陣列 */ traverse(nums); /* 查詢元素 */ int index = find(nums, 3); print("在 nums 中查詢元素 3 ,得到索引 = $index"); } ================================================ FILE: zh-hant/codes/dart/chapter_array_and_linkedlist/linked_list.dart ================================================ /** * File: linked_list.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import '../utils/list_node.dart'; import '../utils/print_util.dart'; /* 在鏈結串列的節點 n0 之後插入節點 P */ void insert(ListNode n0, ListNode P) { ListNode? n1 = n0.next; P.next = n1; n0.next = P; } /* 刪除鏈結串列的節點 n0 之後的首個節點 */ void remove(ListNode n0) { if (n0.next == null) return; // n0 -> P -> n1 ListNode P = n0.next!; ListNode? n1 = P.next; n0.next = n1; } /* 訪問鏈結串列中索引為 index 的節點 */ ListNode? access(ListNode? head, int index) { for (var i = 0; i < index; i++) { if (head == null) return null; head = head.next; } return head; } /* 在鏈結串列中查詢值為 target 的首個節點 */ int find(ListNode? head, int target) { int index = 0; while (head != null) { if (head.val == target) { return index; } head = head.next; index++; } return -1; } /* Driver Code */ void main() { // 初始化鏈結串列 // 初始化各個節點 ListNode n0 = ListNode(1); ListNode n1 = ListNode(3); ListNode n2 = ListNode(2); ListNode n3 = ListNode(5); ListNode n4 = ListNode(4); // 構建節點之間的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; print('初始化的鏈結串列為'); printLinkedList(n0); /* 插入節點 */ insert(n0, ListNode(0)); print('插入節點後的鏈結串列為'); printLinkedList(n0); /* 刪除節點 */ remove(n0); print('刪除節點後的鏈結串列為'); printLinkedList(n0); /* 訪問節點 */ ListNode? node = access(n0, 3); print('鏈結串列中索引 3 處的節點的值 = ${node!.val}'); /* 查詢節點 */ int index = find(n0, 2); print('鏈結串列中值為 2 的節點的索引 = $index'); } ================================================ FILE: zh-hant/codes/dart/chapter_array_and_linkedlist/list.dart ================================================ /** * File: list.dart * Created Time: 2023-01-24 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable /* Driver Code */ void main() { /* 初始化串列 */ List nums = [1, 3, 2, 5, 4]; print('串列 nums = $nums'); /* 訪問元素 */ int _num = nums[1]; print('訪問索引 1 處的元素,得到 _num = $_num'); /* 更新元素 */ nums[1] = 0; print('將索引 1 處的元素更新為 0 ,得到 nums = $nums'); /* 清空串列 */ nums.clear(); print('清空串列後 nums = $nums'); /* 在尾部新增元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print('新增元素後 nums = $nums'); /* 在中間插入元素 */ nums.insert(3, 6); print('在索引 3 處插入數字 6 ,得到 nums = $nums'); /* 刪除元素 */ nums.removeAt(3); print('刪除索引 3 處的元素,得到 nums = $nums'); /* 透過索引走訪串列 */ int count = 0; for (var i = 0; i < nums.length; i++) { count += nums[i]; } /* 直接走訪串列元素 */ count = 0; for (var x in nums) { count += x; } /* 拼接兩個串列 */ List nums1 = [6, 8, 7, 10, 9]; nums.addAll(nums1); print('將串列 nums1 拼接到 nums 之後,得到 nums = $nums'); /* 排序串列 */ nums.sort(); print('排序串列後 nums = $nums'); } ================================================ FILE: zh-hant/codes/dart/chapter_array_and_linkedlist/my_list.dart ================================================ /** * File: my_list.dart * Created Time: 2023-02-05 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 串列類別 */ class MyList { late List _arr; // 陣列(儲存串列元素) int _capacity = 10; // 串列容量 int _size = 0; // 串列長度(當前元素數量) int _extendRatio = 2; // 每次串列擴容的倍數 /* 建構子 */ MyList() { _arr = List.filled(_capacity, 0); } /* 獲取串列長度(當前元素數量)*/ int size() => _size; /* 獲取串列容量 */ int capacity() => _capacity; /* 訪問元素 */ int get(int index) { if (index >= _size) throw RangeError('索引越界'); return _arr[index]; } /* 更新元素 */ void set(int index, int _num) { if (index >= _size) throw RangeError('索引越界'); _arr[index] = _num; } /* 在尾部新增元素 */ void add(int _num) { // 元素數量超出容量時,觸發擴容機制 if (_size == _capacity) extendCapacity(); _arr[_size] = _num; // 更新元素數量 _size++; } /* 在中間插入元素 */ void insert(int index, int _num) { if (index >= _size) throw RangeError('索引越界'); // 元素數量超出容量時,觸發擴容機制 if (_size == _capacity) extendCapacity(); // 將索引 index 以及之後的元素都向後移動一位 for (var j = _size - 1; j >= index; j--) { _arr[j + 1] = _arr[j]; } _arr[index] = _num; // 更新元素數量 _size++; } /* 刪除元素 */ int remove(int index) { if (index >= _size) throw RangeError('索引越界'); int _num = _arr[index]; // 將將索引 index 之後的元素都向前移動一位 for (var j = index; j < _size - 1; j++) { _arr[j] = _arr[j + 1]; } // 更新元素數量 _size--; // 返回被刪除的元素 return _num; } /* 串列擴容 */ void extendCapacity() { // 新建一個長度為原陣列 _extendRatio 倍的新陣列 final _newNums = List.filled(_capacity * _extendRatio, 0); // 將原陣列複製到新陣列 List.copyRange(_newNums, 0, _arr); // 更新 _arr 的引用 _arr = _newNums; // 更新串列容量 _capacity = _arr.length; } /* 將串列轉換為陣列 */ List toArray() { List arr = []; for (var i = 0; i < _size; i++) { arr.add(get(i)); } return arr; } } /* Driver Code */ void main() { /* 初始化串列 */ MyList nums = MyList(); /* 在尾部新增元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print( '串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}'); /* 在中間插入元素 */ nums.insert(3, 6); print('在索引 3 處插入數字 6 ,得到 nums = ${nums.toArray()}'); /* 刪除元素 */ nums.remove(3); print('刪除索引 3 處的元素,得到 nums = ${nums.toArray()}'); /* 訪問元素 */ int _num = nums.get(1); print('訪問索引 1 處的元素,得到 _num = $_num'); /* 更新元素 */ nums.set(1, 0); print('將索引 1 處的元素更新為 0 ,得到 nums = ${nums.toArray()}'); /* 測試擴容機制 */ for (var i = 0; i < 10; i++) { // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 nums.add(i); } print( '擴容後的串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}'); } ================================================ FILE: zh-hant/codes/dart/chapter_backtracking/n_queens.dart ================================================ /** * File: n_queens.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯演算法:n 皇后 */ void backtrack( int row, int n, List> state, List>> res, List cols, List diags1, List diags2, ) { // 當放置完所有行時,記錄解 if (row == n) { List> copyState = []; for (List sRow in state) { copyState.add(List.from(sRow)); } res.add(copyState); return; } // 走訪所有列 for (int col = 0; col < n; col++) { // 計算該格子對應的主對角線和次對角線 int diag1 = row - col + n - 1; int diag2 = row + col; // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 嘗試:將皇后放置在該格子 state[row][col] = "Q"; cols[col] = true; diags1[diag1] = true; diags2[diag2] = true; // 放置下一行 backtrack(row + 1, n, state, res, cols, diags1, diags2); // 回退:將該格子恢復為空位 state[row][col] = "#"; cols[col] = false; diags1[diag1] = false; diags2[diag2] = false; } } } /* 求解 n 皇后 */ List>> nQueens(int n) { // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 List> state = List.generate(n, (index) => List.filled(n, "#")); List cols = List.filled(n, false); // 記錄列是否有皇后 List diags1 = List.filled(2 * n - 1, false); // 記錄主對角線上是否有皇后 List diags2 = List.filled(2 * n - 1, false); // 記錄次對角線上是否有皇后 List>> res = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } /* Driver Code */ void main() { int n = 4; List>> res = nQueens(n); print("輸入棋盤長寬為 $n"); print("皇后放置方案共有 ${res.length} 種"); for (List> state in res) { print("--------------------"); for (List row in state) { print(row); } } } ================================================ FILE: zh-hant/codes/dart/chapter_backtracking/permutations_i.dart ================================================ /** * File: permutations_i.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯演算法:全排列 I */ void backtrack( List state, List choices, List selected, List> res, ) { // 當狀態長度等於元素數量時,記錄解 if (state.length == choices.length) { res.add(List.from(state)); return; } // 走訪所有選擇 for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // 剪枝:不允許重複選擇元素 if (!selected[i]) { // 嘗試:做出選擇,更新狀態 selected[i] = true; state.add(choice); // 進行下一輪選擇 backtrack(state, choices, selected, res); // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false; state.removeLast(); } } } /* 全排列 I */ List> permutationsI(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } /* Driver Code */ void main() { List nums = [1, 2, 3]; List> res = permutationsI(nums); print("輸入陣列 nums = $nums"); print("所有排列 res = $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_backtracking/permutations_ii.dart ================================================ /** * File: permutations_ii.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯演算法:全排列 II */ void backtrack( List state, List choices, List selected, List> res, ) { // 當狀態長度等於元素數量時,記錄解 if (state.length == choices.length) { res.add(List.from(state)); return; } // 走訪所有選擇 Set duplicated = {}; for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 if (!selected[i] && !duplicated.contains(choice)) { // 嘗試:做出選擇,更新狀態 duplicated.add(choice); // 記錄選擇過的元素值 selected[i] = true; state.add(choice); // 進行下一輪選擇 backtrack(state, choices, selected, res); // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false; state.removeLast(); } } } /* 全排列 II */ List> permutationsII(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } /* Driver Code */ void main() { List nums = [1, 2, 2]; List> res = permutationsII(nums); print("輸入陣列 nums = $nums"); print("所有排列 res = $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart ================================================ /** * File: preorder_traversal_i_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 前序走訪:例題一 */ void preOrder(TreeNode? root, List res) { if (root == null) { return; } if (root.val == 7) { // 記錄解 res.add(root); } preOrder(root.left, res); preOrder(root.right, res); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\n初始化二元樹"); printTree(root); // 前序走訪 List res = []; preOrder(root, res); print("\n輸出所有值為 7 的節點"); print(List.generate(res.length, (i) => res[i].val)); } ================================================ FILE: zh-hant/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart ================================================ /** * File: preorder_traversal_ii_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 前序走訪:例題二 */ void preOrder( TreeNode? root, List path, List> res, ) { if (root == null) { return; } // 嘗試 path.add(root); if (root.val == 7) { // 記錄解 res.add(List.from(path)); } preOrder(root.left, path, res); preOrder(root.right, path, res); // 回退 path.removeLast(); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\n初始化二元樹"); printTree(root); // 前序走訪 List path = []; List> res = []; preOrder(root, path, res); print("\n輸出所有根節點到節點 7 的路徑"); for (List vals in res) { print(List.generate(vals.length, (i) => vals[i].val)); } } ================================================ FILE: zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart ================================================ /** * File: preorder_traversal_iii_compact.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 前序走訪:例題三 */ void preOrder( TreeNode? root, List path, List> res, ) { if (root == null || root.val == 3) { return; } // 嘗試 path.add(root); if (root.val == 7) { // 記錄解 res.add(List.from(path)); } preOrder(root.left, path, res); preOrder(root.right, path, res); // 回退 path.removeLast(); } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\n初始化二元樹"); printTree(root); // 前序走訪 List path = []; List> res = []; preOrder(root, path, res); print("\n輸出所有根節點到節點 7 的路徑"); for (List vals in res) { print(List.generate(vals.length, (i) => vals[i].val)); } } ================================================ FILE: zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart ================================================ /** * File: preorder_traversal_iii_template.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 判斷當前狀態是否為解 */ bool isSolution(List state) { return state.isNotEmpty && state.last.val == 7; } /* 記錄解 */ void recordSolution(List state, List> res) { res.add(List.from(state)); } /* 判斷在當前狀態下,該選擇是否合法 */ bool isValid(List state, TreeNode? choice) { return choice != null && choice.val != 3; } /* 更新狀態 */ void makeChoice(List state, TreeNode? choice) { state.add(choice!); } /* 恢復狀態 */ void undoChoice(List state, TreeNode? choice) { state.removeLast(); } /* 回溯演算法:例題三 */ void backtrack( List state, List choices, List> res, ) { // 檢查是否為解 if (isSolution(state)) { // 記錄解 recordSolution(state, res); } // 走訪所有選擇 for (TreeNode? choice in choices) { // 剪枝:檢查選擇是否合法 if (isValid(state, choice)) { // 嘗試:做出選擇,更新狀態 makeChoice(state, choice); // 進行下一輪選擇 backtrack(state, [choice!.left, choice.right], res); // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state, choice); } } } /* Driver Code */ void main() { TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); print("\n初始化二元樹"); printTree(root); // 回溯演算法 List> res = []; backtrack([], [root!], res); print("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點"); for (List path in res) { print(List.from(path.map((e) => e.val))); } } ================================================ FILE: zh-hant/codes/dart/chapter_backtracking/subset_sum_i.dart ================================================ /** * File: subset_sum_i.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯演算法:子集和 I */ void backtrack( List state, int target, List choices, int start, List> res, ) { // 子集和等於 target 時,記錄解 if (target == 0) { res.add(List.from(state)); return; } // 走訪所有選擇 // 剪枝二:從 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.removeLast(); } } /* 求解子集和 I */ List> subsetSumI(List nums, int target) { List state = []; // 狀態(子集) nums.sort(); // 對 nums 進行排序 int start = 0; // 走訪起始點 List> res = []; // 結果串列(子集串列) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ void main() { List nums = [3, 4, 5]; int target = 9; List> res = subsetSumI(nums, target); print("輸入陣列 nums = $nums, target = $target"); print("所有和等於 $target 的子集 res = $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_backtracking/subset_sum_i_naive.dart ================================================ /** * File: subset_sum_i_naive.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯演算法:子集和 I */ void backtrack( List state, int target, int total, List choices, List> res, ) { // 子集和等於 target 時,記錄解 if (total == target) { res.add(List.from(state)); return; } // 走訪所有選擇 for (int i = 0; i < choices.length; i++) { // 剪枝:若子集和超過 target ,則跳過該選擇 if (total + choices[i] > target) { continue; } // 嘗試:做出選擇,更新元素和 total state.add(choices[i]); // 進行下一輪選擇 backtrack(state, target, total + choices[i], choices, res); // 回退:撤銷選擇,恢復到之前的狀態 state.removeLast(); } } /* 求解子集和 I(包含重複子集) */ List> subsetSumINaive(List nums, int target) { List state = []; // 狀態(子集) int total = 0; // 元素和 List> res = []; // 結果串列(子集串列) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ void main() { List nums = [3, 4, 5]; int target = 9; List> res = subsetSumINaive(nums, target); print("輸入陣列 nums = $nums, target = $target"); print("所有和等於 $target 的子集 res = $res"); print("請注意,該方法輸出的結果包含重複集合"); } ================================================ FILE: zh-hant/codes/dart/chapter_backtracking/subset_sum_ii.dart ================================================ /** * File: subset_sum_ii.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯演算法:子集和 II */ void backtrack( List state, int target, List choices, int start, List> res, ) { // 子集和等於 target 時,記錄解 if (target == 0) { res.add(List.from(state)); return; } // 走訪所有選擇 // 剪枝二:從 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.removeLast(); } } /* 求解子集和 II */ List> subsetSumII(List nums, int target) { List state = []; // 狀態(子集) nums.sort(); // 對 nums 進行排序 int start = 0; // 走訪起始點 List> res = []; // 結果串列(子集串列) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ void main() { List nums = [4, 4, 5]; int target = 9; List> res = subsetSumII(nums, target); print("輸入陣列 nums = $nums, target = $target"); print("所有和等於 $target 的子集 res = $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_computational_complexity/iteration.dart ================================================ /** * File: iteration.dart * Created Time: 2023-08-27 * Author: liuyuxin (gvenusleo@gmail.com) */ /* for 迴圈 */ int forLoop(int n) { int res = 0; // 迴圈求和 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } /* while 迴圈 */ int whileLoop(int n) { int res = 0; int i = 1; // 初始化條件變數 // 迴圈求和 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // 更新條件變數 } return res; } /* while 迴圈(兩次更新) */ int whileLoopII(int n) { int res = 0; int i = 1; // 初始化條件變數 // 迴圈求和 1, 4, 10, ... while (i <= n) { res += i; // 更新條件變數 i++; i *= 2; } return res; } /* 雙層 for 迴圈 */ String nestedForLoop(int n) { String res = ""; // 迴圈 i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // 迴圈 j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { res += "($i, $j), "; } } return res; } /* Driver Code */ void main() { int n = 5; int res; res = forLoop(n); print("\nfor 迴圈的求和結果 res = $res"); res = whileLoop(n); print("\nwhile 迴圈的求和結果 res = $res"); res = whileLoopII(n); print("\nwhile 迴圈(兩次更新)的求和結果 res = $res"); String resStr = nestedForLoop(n); print("\n雙層 for 迴圈的結果 $resStr"); } ================================================ FILE: zh-hant/codes/dart/chapter_computational_complexity/recursion.dart ================================================ /** * File: recursion.dart * Created Time: 2023-08-27 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 遞迴 */ int recur(int n) { // 終止條件 if (n == 1) return 1; // 遞:遞迴呼叫 int res = recur(n - 1); // 迴:返回結果 return n + res; } /* 使用迭代模擬遞迴 */ int forLoopRecur(int n) { // 使用一個顯式的堆疊來模擬系統呼叫堆疊 List stack = []; int res = 0; // 遞:遞迴呼叫 for (int i = n; i > 0; i--) { // 透過“入堆疊操作”模擬“遞” stack.add(i); } // 迴:返回結果 while (!stack.isEmpty) { // 透過“出堆疊操作”模擬“迴” res += stack.removeLast(); } // res = 1+2+3+...+n return res; } /* 尾遞迴 */ int tailRecur(int n, int res) { // 終止條件 if (n == 0) return res; // 尾遞迴呼叫 return tailRecur(n - 1, res + n); } /* 費波那契數列:遞迴 */ int fib(int n) { // 終止條件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // 遞迴呼叫 f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // 返回結果 f(n) return res; } /* Driver Code */ void main() { int n = 5; int res; res = recur(n); print("\n遞迴函式的求和結果 res = $res"); res = tailRecur(n, 0); print("\n尾遞迴函式的求和結果 res = $res"); res = forLoopRecur(n); print("\n使用迭代模擬遞迴求和結果 res = $res"); res = fib(n); print("\n費波那契數列的第 $n 項為 $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_computational_complexity/space_complexity.dart ================================================ /** * File: space_complexity.dart * Created Time: 2023-2-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable import 'dart:collection'; import '../utils/list_node.dart'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 函式 */ int function() { // 執行某些操作 return 0; } /* 常數階 */ void constant(int n) { // 常數、變數、物件佔用 O(1) 空間 final int a = 0; int b = 0; List nums = List.filled(10000, 0); ListNode node = ListNode(0); // 迴圈中的變數佔用 O(1) 空間 for (var i = 0; i < n; i++) { int c = 0; } // 迴圈中的函式佔用 O(1) 空間 for (var i = 0; i < n; i++) { function(); } } /* 線性階 */ void linear(int n) { // 長度為 n 的陣列佔用 O(n) 空間 List nums = List.filled(n, 0); // 長度為 n 的串列佔用 O(n) 空間 List nodes = []; for (var i = 0; i < n; i++) { nodes.add(ListNode(i)); } // 長度為 n 的雜湊表佔用 O(n) 空間 Map map = HashMap(); for (var i = 0; i < n; i++) { map.putIfAbsent(i, () => i.toString()); } } /* 線性階(遞迴實現) */ void linearRecur(int n) { print('遞迴 n = $n'); if (n == 1) return; linearRecur(n - 1); } /* 平方階 */ void quadratic(int n) { // 矩陣佔用 O(n^2) 空間 List> numMatrix = List.generate(n, (_) => List.filled(n, 0)); // 二維串列佔用 O(n^2) 空間 List> numList = []; for (var i = 0; i < n; i++) { List tmp = []; for (int j = 0; j < n; j++) { tmp.add(0); } numList.add(tmp); } } /* 平方階(遞迴實現) */ int quadraticRecur(int n) { if (n <= 0) return 0; List nums = List.filled(n, 0); print('遞迴 n = $n 中的 nums 長度 = ${nums.length}'); return quadraticRecur(n - 1); } /* 指數階(建立滿二元樹) */ TreeNode? buildTree(int n) { if (n == 0) return null; TreeNode root = TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ void main() { int n = 5; // 常數階 constant(n); // 線性階 linear(n); linearRecur(n); // 平方階 quadratic(n); quadraticRecur(n); // 指數階 TreeNode? root = buildTree(n); printTree(root); } ================================================ FILE: zh-hant/codes/dart/chapter_computational_complexity/time_complexity.dart ================================================ /** * File: time_complexity.dart * Created Time: 2023-02-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ // ignore_for_file: unused_local_variable /* 常數階 */ int constant(int n) { int count = 0; int size = 100000; for (var i = 0; i < size; i++) { count++; } return count; } /* 線性階 */ int linear(int n) { int count = 0; for (var i = 0; i < n; i++) { count++; } return count; } /* 線性階(走訪陣列) */ int arrayTraversal(List nums) { int count = 0; // 迴圈次數與陣列長度成正比 for (var _num in nums) { count++; } return count; } /* 平方階 */ int quadratic(int n) { int count = 0; // 迴圈次數與資料大小 n 成平方關係 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } return count; } /* 平方階(泡沫排序) */ int bubbleSort(List nums) { int count = 0; // 計數器 // 外迴圈:未排序區間為 [0, i] for (var i = nums.length - 1; i > 0; i--) { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (var j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 元素交換包含 3 個單元操作 } } } return count; } /* 指數階(迴圈實現) */ int exponential(int n) { int count = 0, base = 1; // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) for (var i = 0; i < n; i++) { for (var j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指數階(遞迴實現) */ int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 對數階(迴圈實現) */ int logarithmic(int n) { int count = 0; while (n > 1) { n = n ~/ 2; count++; } return count; } /* 對數階(遞迴實現) */ int logRecur(int n) { if (n <= 1) return 0; return logRecur(n ~/ 2) + 1; } /* 線性對數階 */ int linearLogRecur(int n) { if (n <= 1) return 1; int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2); for (var i = 0; i < n; i++) { count++; } return count; } /* 階乘階(遞迴實現) */ int factorialRecur(int n) { if (n == 0) return 1; int count = 0; // 從 1 個分裂出 n 個 for (var i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ void main() { // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 int n = 8; print('輸入資料大小 n = $n'); int count = constant(n); print('常數階的操作數量 = $count'); count = linear(n); print('線性階的操作數量 = $count'); count = arrayTraversal(List.filled(n, 0)); print('線性階(走訪陣列)的操作數量 = $count'); count = quadratic(n); print('平方階的操作數量 = $count'); final nums = List.filled(n, 0); for (int i = 0; i < n; i++) { nums[i] = n - i; // [n,n-1,...,2,1] } count = bubbleSort(nums); print('平方階(泡沫排序)的操作數量 = $count'); count = exponential(n); print('指數階(迴圈實現)的操作數量 = $count'); count = expRecur(n); print('指數階(遞迴實現)的操作數量 = $count'); count = logarithmic(n); print('對數階(迴圈實現)的操作數量 = $count'); count = logRecur(n); print('對數階(遞迴實現)的操作數量 = $count'); count = linearLogRecur(n); print('線性對數階(遞迴實現)的操作數量 = $count'); count = factorialRecur(n); print('階乘階(遞迴實現)的操作數量 = $count'); } ================================================ FILE: zh-hant/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart ================================================ /** * File: worst_best_time_complexity.dart * Created Time: 2023-02-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ List randomNumbers(int n) { final nums = List.filled(n, 0); // 生成陣列 nums = { 1, 2, 3, ..., n } for (var i = 0; i < n; i++) { nums[i] = i + 1; } // 隨機打亂陣列元素 nums.shuffle(); return nums; } /* 查詢陣列 nums 中數字 1 所在索引 */ int findOne(List nums) { for (var i = 0; i < nums.length; i++) { // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) if (nums[i] == 1) return i; } return -1; } /* Driver Code */ void main() { for (var i = 0; i < 10; i++) { int n = 100; final nums = randomNumbers(n); int index = findOne(nums); print('\n陣列 [ 1, 2, ..., n ] 被打亂後 = $nums'); print('數字 1 的索引為 + $index'); } } ================================================ FILE: zh-hant/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart ================================================ /** * File: binary_search_recur.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 二分搜尋:問題 f(i, j) */ int dfs(List nums, int target, int i, int j) { // 若區間為空,代表無目標元素,則返回 -1 if (i > j) { return -1; } // 計算中點索引 m int m = (i + j) ~/ 2; if (nums[m] < target) { // 遞迴子問題 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 遞迴子問題 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目標元素,返回其索引 return m; } } /* 二分搜尋 */ int binarySearch(List nums, int target) { int n = nums.length; // 求解問題 f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ void main() { int target = 6; List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分搜尋(雙閉區間) int index = binarySearch(nums, target); print("目標元素 6 的索引 = $index"); } ================================================ FILE: zh-hant/codes/dart/chapter_divide_and_conquer/build_tree.dart ================================================ /** * File: build_tree.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 構建二元樹:分治 */ TreeNode? dfs( List preorder, Map inorderMap, int i, int l, int r, ) { // 子樹區間為空時終止 if (r - l < 0) { return null; } // 初始化根節點 TreeNode? root = TreeNode(preorder[i]); // 查詢 m ,從而劃分左右子樹 int m = inorderMap[preorder[i]]!; // 子問題:構建左子樹 root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // 子問題:構建右子樹 root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // 返回根節點 return root; } /* 構建二元樹 */ TreeNode? buildTree(List preorder, List inorder) { // 初始化雜湊表,儲存 inorder 元素到索引的對映 Map inorderMap = {}; for (int i = 0; i < inorder.length; i++) { inorderMap[inorder[i]] = i; } TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ void main() { List preorder = [3, 9, 2, 1, 7]; List inorder = [9, 3, 1, 2, 7]; print("前序走訪 = $preorder"); print("中序走訪 = $inorder"); TreeNode? root = buildTree(preorder, inorder); print("構建的二元樹為:"); printTree(root!); } ================================================ FILE: zh-hant/codes/dart/chapter_divide_and_conquer/hanota.dart ================================================ /** * File: hanota.dart * Created Time: 2023-08-10 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 移動一個圓盤 */ void move(List src, List tar) { // 從 src 頂部拿出一個圓盤 int pan = src.removeLast(); // 將圓盤放入 tar 頂部 tar.add(pan); } /* 求解河內塔問題 f(i) */ void dfs(int i, List src, List buf, List tar) { // 若 src 只剩下一個圓盤,則直接將其移到 tar if (i == 1) { move(src, tar); return; } // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf dfs(i - 1, src, tar, buf); // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar move(src, tar); // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar dfs(i - 1, buf, src, tar); } /* 求解河內塔問題 */ void solveHanota(List A, List B, List C) { int n = A.length; // 將 A 頂部 n 個圓盤藉助 B 移到 C dfs(n, A, B, C); } /* Driver Code */ void main() { // 串列尾部是柱子頂部 List A = [5, 4, 3, 2, 1]; List B = []; List C = []; print("初始狀態下:"); print("A = $A"); print("B = $B"); print("C = $C"); solveHanota(A, B, C); print("圓盤移動完成後:"); print("A = $A"); print("B = $B"); print("C = $C"); } ================================================ FILE: zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart ================================================ /** * File: climbing_stairs_backtrack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 回溯 */ void backtrack(List choices, int state, int n, List res) { // 當爬到第 n 階時,方案數量加 1 if (state == n) { res[0]++; } // 走訪所有選擇 for (int choice in choices) { // 剪枝:不允許越過第 n 階 if (state + choice > n) continue; // 嘗試:做出選擇,更新狀態 backtrack(choices, state + choice, n, res); // 回退 } } /* 爬樓梯:回溯 */ int climbingStairsBacktrack(int n) { List choices = [1, 2]; // 可選擇向上爬 1 階或 2 階 int state = 0; // 從第 0 階開始爬 List res = []; res.add(0); // 使用 res[0] 記錄方案數量 backtrack(choices, state, n, res); return res[0]; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsBacktrack(n); print("爬 $n 階樓梯共有 $res 種方案"); } ================================================ FILE: zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart ================================================ /** * File: climbing_stairs_constraint_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 帶約束爬樓梯:動態規劃 */ int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } // 初始化 dp 表,用於儲存子問題的解 List> dp = List.generate(n + 1, (index) => List.filled(3, 0)); // 初始狀態:預設最小子問題的解 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 狀態轉移:從較小子問題逐步求解較大子問題 for (int i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsConstraintDP(n); print("爬 $n 階樓梯共有 $res 種方案"); } ================================================ FILE: zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart ================================================ /** * File: climbing_stairs_dfs.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 搜尋 */ int dfs(int i) { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1) + dfs(i - 2); return count; } /* 爬樓梯:搜尋 */ int climbingStairsDFS(int n) { return dfs(n); } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDFS(n); print("爬 $n 階樓梯共有 $res 種方案"); } ================================================ FILE: zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart ================================================ /** * File: climbing_stairs_dfs_mem.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 記憶化搜尋 */ int dfs(int i, List mem) { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i; // 若存在記錄 dp[i] ,則直接返回之 if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1, mem) + dfs(i - 2, mem); // 記錄 dp[i] mem[i] = count; return count; } /* 爬樓梯:記憶化搜尋 */ int climbingStairsDFSMem(int n) { // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 List mem = List.filled(n + 1, -1); return dfs(n, mem); } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDFSMem(n); print("爬 $n 階樓梯共有 $res 種方案"); } ================================================ FILE: zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart ================================================ /** * File: climbing_stairs_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 爬樓梯:動態規劃 */ int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; // 初始化 dp 表,用於儲存子問題的解 List dp = List.filled(n + 1, 0); // 初始狀態:預設最小子問題的解 dp[1] = 1; dp[2] = 2; // 狀態轉移:從較小子問題逐步求解較大子問題 for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* 爬樓梯:空間最佳化後的動態規劃 */ int climbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; int a = 1, b = 2; for (int i = 3; i <= n; i++) { int tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ void main() { int n = 9; int res = climbingStairsDP(n); print("爬 $n 階樓梯共有 $res 種方案"); res = climbingStairsDPComp(n); print("爬 $n 階樓梯共有 $res 種方案"); } ================================================ FILE: zh-hant/codes/dart/chapter_dynamic_programming/coin_change.dart ================================================ /** * File: coin_change.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 零錢兌換:動態規劃 */ int coinChangeDP(List coins, int amt) { int n = coins.length; int MAX = amt + 1; // 初始化 dp 表 List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); // 狀態轉移:首行首列 for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 狀態轉移:其餘行和列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] != MAX ? dp[n][amt] : -1; } /* 零錢兌換:空間最佳化後的動態規劃 */ int coinChangeDPComp(List coins, int amt) { int n = coins.length; int MAX = amt + 1; // 初始化 dp 表 List dp = List.filled(amt + 1, MAX); dp[0] = 0; // 狀態轉移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } /* Driver Code */ void main() { List coins = [1, 2, 5]; int amt = 4; // 動態規劃 int res = coinChangeDP(coins, amt); print("湊到目標金額所需的最少硬幣數量為 $res"); // 空間最佳化後的動態規劃 res = coinChangeDPComp(coins, amt); print("湊到目標金額所需的最少硬幣數量為 $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_dynamic_programming/coin_change_ii.dart ================================================ /** * File: coin_change_ii.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 零錢兌換 II:動態規劃 */ int coinChangeIIDP(List coins, int amt) { int n = coins.length; // 初始化 dp 表 List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); // 初始化首列 for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // 狀態轉移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a]; } else { // 不選和選硬幣 i 這兩種方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* 零錢兌換 II:空間最佳化後的動態規劃 */ int coinChangeIIDPComp(List coins, int amt) { int n = coins.length; // 初始化 dp 表 List dp = List.filled(amt + 1, 0); dp[0] = 1; // 狀態轉移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ void main() { List coins = [1, 2, 5]; int amt = 5; // 動態規劃 int res = coinChangeIIDP(coins, amt); print("湊出目標金額的硬幣組合數量為 $res"); // 空間最佳化後的動態規劃 res = coinChangeIIDPComp(coins, amt); print("湊出目標金額的硬幣組合數量為 $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_dynamic_programming/edit_distance.dart ================================================ /** * File: edit_distance.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 編輯距離:暴力搜尋 */ int editDistanceDFS(String s, String t, int i, int j) { // 若 s 和 t 都為空,則返回 0 if (i == 0 && j == 0) return 0; // 若 s 為空,則返回 t 長度 if (i == 0) return j; // 若 t 為空,則返回 s 長度 if (j == 0) return i; // 若兩字元相等,則直接跳過此兩字元 if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 int insert = editDistanceDFS(s, t, i, j - 1); int delete = editDistanceDFS(s, t, i - 1, j); int replace = editDistanceDFS(s, t, i - 1, j - 1); // 返回最少編輯步數 return min(min(insert, delete), replace) + 1; } /* 編輯距離:記憶化搜尋 */ int editDistanceDFSMem(String s, String t, List> mem, int i, int j) { // 若 s 和 t 都為空,則返回 0 if (i == 0 && j == 0) return 0; // 若 s 為空,則返回 t 長度 if (i == 0) return j; // 若 t 為空,則返回 s 長度 if (j == 0) return i; // 若已有記錄,則直接返回之 if (mem[i][j] != -1) return mem[i][j]; // 若兩字元相等,則直接跳過此兩字元 if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 int insert = editDistanceDFSMem(s, t, mem, i, j - 1); int delete = editDistanceDFSMem(s, t, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 記錄並返回最少編輯步數 mem[i][j] = min(min(insert, delete), replace) + 1; return mem[i][j]; } /* 編輯距離:動態規劃 */ int editDistanceDP(String s, String t) { int n = s.length, m = t.length; List> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0)); // 狀態轉移:首行首列 for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } // 狀態轉移:其餘行和列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { // 若兩字元相等,則直接跳過此兩字元 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* 編輯距離:空間最佳化後的動態規劃 */ int editDistanceDPComp(String s, String t) { int n = s.length, m = t.length; List dp = List.filled(m + 1, 0); // 狀態轉移:首行 for (int j = 1; j <= m; j++) { dp[j] = j; } // 狀態轉移:其餘行 for (int i = 1; i <= n; i++) { // 狀態轉移:首列 int leftup = dp[0]; // 暫存 dp[i-1, j-1] dp[0] = i; // 狀態轉移:其餘列 for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 若兩字元相等,則直接跳過此兩字元 dp[j] = leftup; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 更新為下一輪的 dp[i-1, j-1] } } return dp[m]; } /* Driver Code */ void main() { String s = "bag"; String t = "pack"; int n = s.length, m = t.length; // 暴力搜尋 int res = editDistanceDFS(s, t, n, m); print("將 " + s + " 更改為 " + t + " 最少需要編輯 $res 步"); // 記憶化搜尋 List> mem = List.generate(n + 1, (_) => List.filled(m + 1, -1)); res = editDistanceDFSMem(s, t, mem, n, m); print("將 " + s + " 更改為 " + t + " 最少需要編輯 $res 步"); // 動態規劃 res = editDistanceDP(s, t); print("將 " + s + " 更改為 " + t + " 最少需要編輯 $res 步"); // 空間最佳化後的動態規劃 res = editDistanceDPComp(s, t); print("將 " + s + " 更改為 " + t + " 最少需要編輯 $res 步"); } ================================================ FILE: zh-hant/codes/dart/chapter_dynamic_programming/knapsack.dart ================================================ /** * File: knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 0-1 背包:暴力搜尋 */ int knapsackDFS(List wgt, List val, int i, int c) { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i == 0 || c == 0) { return 0; } // 若超過背包容量,則只能選擇不放入背包 if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // 返回兩種方案中價值更大的那一個 return max(no, yes); } /* 0-1 背包:記憶化搜尋 */ int knapsackDFSMem( List wgt, List val, List> mem, int i, int c, ) { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i == 0 || c == 0) { return 0; } // 若已有記錄,則直接返回 if (mem[i][c] != -1) { return mem[i][c]; } // 若超過背包容量,則只能選擇不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 int no = knapsackDFSMem(wgt, val, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 記錄並返回兩種方案中價值更大的那一個 mem[i][c] = max(no, yes); return mem[i][c]; } /* 0-1 背包:動態規劃 */ int knapsackDP(List wgt, List val, int cap) { int n = wgt.length; // 初始化 dp 表 List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); // 狀態轉移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } /* 0-1 背包:空間最佳化後的動態規劃 */ int knapsackDPComp(List wgt, List val, int cap) { int n = wgt.length; // 初始化 dp 表 List dp = List.filled(cap + 1, 0); // 狀態轉移 for (int i = 1; i <= n; i++) { // 倒序走訪 for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // 不選和選物品 i 這兩種方案的較大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ void main() { List wgt = [10, 20, 30, 40, 50]; List val = [50, 120, 150, 210, 240]; int cap = 50; int n = wgt.length; // 暴力搜尋 int res = knapsackDFS(wgt, val, n, cap); print("不超過背包容量的最大物品價值為 $res"); // 記憶化搜尋 List> mem = List.generate(n + 1, (index) => List.filled(cap + 1, -1)); res = knapsackDFSMem(wgt, val, mem, n, cap); print("不超過背包容量的最大物品價值為 $res"); // 動態規劃 res = knapsackDP(wgt, val, cap); print("不超過背包容量的最大物品價值為 $res"); // 空間最佳化後的動態規劃 res = knapsackDPComp(wgt, val, cap); print("不超過背包容量的最大物品價值為 $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart ================================================ /** * File: min_cost_climbing_stairs_dp.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 爬樓梯最小代價:動態規劃 */ int minCostClimbingStairsDP(List cost) { int n = cost.length - 1; if (n == 1 || n == 2) return cost[n]; // 初始化 dp 表,用於儲存子問題的解 List dp = List.filled(n + 1, 0); // 初始狀態:預設最小子問題的解 dp[1] = cost[1]; dp[2] = cost[2]; // 狀態轉移:從較小子問題逐步求解較大子問題 for (int i = 3; i <= n; i++) { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* 爬樓梯最小代價:空間最佳化後的動態規劃 */ int minCostClimbingStairsDPComp(List cost) { int n = cost.length - 1; if (n == 1 || n == 2) return cost[n]; int a = cost[1], b = cost[2]; for (int i = 3; i <= n; i++) { int tmp = b; b = min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ void main() { List cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; print("輸入樓梯的代價串列為 $cost"); int res = minCostClimbingStairsDP(cost); print("爬完樓梯的最低代價為 $res"); res = minCostClimbingStairsDPComp(cost); print("爬完樓梯的最低代價為 $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_dynamic_programming/min_path_sum.dart ================================================ /** * File: min_path_sum.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 最小路徑和:暴力搜尋 */ int minPathSumDFS(List> grid, int i, int j) { // 若為左上角單元格,則終止搜尋 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 || j < 0) { // 在 Dart 中,int 型別是固定範圍的整數,不存在表示“無窮大”的值 return BigInt.from(2).pow(31).toInt(); } // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); // 返回從左上角到 (i, j) 的最小路徑代價 return min(left, up) + grid[i][j]; } /* 最小路徑和:記憶化搜尋 */ int minPathSumDFSMem(List> grid, List> mem, int i, int j) { // 若為左上角單元格,則終止搜尋 if (i == 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 || j < 0) { // 在 Dart 中,int 型別是固定範圍的整數,不存在表示“無窮大”的值 return BigInt.from(2).pow(31).toInt(); } // 若已有記錄,則直接返回 if (mem[i][j] != -1) { return mem[i][j]; } // 左邊和上邊單元格的最小路徑代價 int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); // 記錄並返回左上角到 (i, j) 的最小路徑代價 mem[i][j] = min(left, up) + grid[i][j]; return mem[i][j]; } /* 最小路徑和:動態規劃 */ int minPathSumDP(List> grid) { int n = grid.length, m = grid[0].length; // 初始化 dp 表 List> dp = List.generate(n, (i) => List.filled(m, 0)); dp[0][0] = grid[0][0]; // 狀態轉移:首行 for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 狀態轉移:首列 for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 狀態轉移:其餘行和列 for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* 最小路徑和:空間最佳化後的動態規劃 */ int minPathSumDPComp(List> grid) { int n = grid.length, m = grid[0].length; // 初始化 dp 表 List dp = List.filled(m, 0); dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 狀態轉移:其餘行 for (int i = 1; i < n; i++) { // 狀態轉移:首列 dp[0] = dp[0] + grid[i][0]; // 狀態轉移:其餘列 for (int j = 1; j < m; j++) { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ void main() { List> grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; int n = grid.length, m = grid[0].length; // 暴力搜尋 int res = minPathSumDFS(grid, n - 1, m - 1); print("從左上角到右下角的最小路徑和為 $res"); // 記憶化搜尋 List> mem = List.generate(n, (i) => List.filled(m, -1)); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); print("從左上角到右下角的最小路徑和為 $res"); // 動態規劃 res = minPathSumDP(grid); print("從左上角到右下角的最小路徑和為 $res"); // 空間最佳化後的動態規劃 res = minPathSumDPComp(grid); print("從左上角到右下角的最小路徑和為 $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart ================================================ /** * File: unbounded_knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 完全背包:動態規劃 */ int unboundedKnapsackDP(List wgt, List val, int cap) { int n = wgt.length; // 初始化 dp 表 List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); // 狀態轉移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } /* 完全背包:空間最佳化後的動態規劃 */ int unboundedKnapsackDPComp(List wgt, List val, int cap) { int n = wgt.length; // 初始化 dp 表 List dp = List.filled(cap + 1, 0); // 狀態轉移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[c] = dp[c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ void main() { List wgt = [1, 2, 3]; List val = [5, 11, 15]; int cap = 4; // 動態規劃 int res = unboundedKnapsackDP(wgt, val, cap); print("不超過背包容量的最大物品價值為 $res"); // 空間最佳化後的動態規劃 int resComp = unboundedKnapsackDPComp(wgt, val, cap); print("不超過背包容量的最大物品價值為 $resComp"); } ================================================ FILE: zh-hant/codes/dart/chapter_graph/graph_adjacency_list.dart ================================================ /** * File: graph_adjacency_list.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/vertex.dart'; /* 基於鄰接表實現的無向圖類別 */ class GraphAdjList { // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 Map> adjList = {}; /* 建構子 */ GraphAdjList(List> edges) { for (List edge in edges) { addVertex(edge[0]); addVertex(edge[1]); addEdge(edge[0], edge[1]); } } /* 獲取頂點數量 */ int size() { return adjList.length; } /* 新增邊 */ void addEdge(Vertex vet1, Vertex vet2) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) { throw ArgumentError; } // 新增邊 vet1 - vet2 adjList[vet1]!.add(vet2); adjList[vet2]!.add(vet1); } /* 刪除邊 */ void removeEdge(Vertex vet1, Vertex vet2) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) { throw ArgumentError; } // 刪除邊 vet1 - vet2 adjList[vet1]!.remove(vet2); adjList[vet2]!.remove(vet1); } /* 新增頂點 */ void addVertex(Vertex vet) { if (adjList.containsKey(vet)) return; // 在鄰接表中新增一個新鏈結串列 adjList[vet] = []; } /* 刪除頂點 */ void removeVertex(Vertex vet) { if (!adjList.containsKey(vet)) { throw ArgumentError; } // 在鄰接表中刪除頂點 vet 對應的鏈結串列 adjList.remove(vet); // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 adjList.forEach((key, value) { value.remove(vet); }); } /* 列印鄰接表 */ void printAdjList() { print("鄰接表 ="); adjList.forEach((key, value) { List tmp = []; for (Vertex vertex in value) { tmp.add(vertex.val); } print("${key.val}: $tmp,"); }); } } /* Driver Code */ void main() { /* 初始化無向圖 */ List v = Vertex.valsToVets([1, 3, 2, 5, 4]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ]; GraphAdjList graph = GraphAdjList(edges); print("\n初始化後,圖為"); graph.printAdjList(); /* 新增邊 */ // 頂點 1, 2 即 v[0], v[2] graph.addEdge(v[0], v[2]); print("\n新增邊 1-2 後,圖為"); graph.printAdjList(); /* 刪除邊 */ // 頂點 1, 3 即 v[0], v[1] graph.removeEdge(v[0], v[1]); print("\n刪除邊 1-3 後,圖為"); graph.printAdjList(); /* 新增頂點 */ Vertex v5 = Vertex(6); graph.addVertex(v5); print("\n新增頂點 6 後,圖為"); graph.printAdjList(); /* 刪除頂點 */ // 頂點 3 即 v[1] graph.removeVertex(v[1]); print("\n刪除頂點 3 後,圖為"); graph.printAdjList(); } ================================================ FILE: zh-hant/codes/dart/chapter_graph/graph_adjacency_matrix.dart ================================================ /** * File: graph_adjacency_matrix.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* 基於鄰接矩陣實現的無向圖類別 */ class GraphAdjMat { List vertices = []; // 頂點元素,元素代表“頂點值”,索引代表“頂點索引” List> adjMat = []; //鄰接矩陣,行列索引對應“頂點索引” /* 建構子 */ GraphAdjMat(List vertices, List> edges) { this.vertices = []; this.adjMat = []; // 新增頂點 for (int val in vertices) { addVertex(val); } // 新增邊 // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 for (List e in edges) { addEdge(e[0], e[1]); } } /* 獲取頂點數量 */ int size() { return vertices.length; } /* 新增頂點 */ void addVertex(int val) { int n = size(); // 向頂點串列中新增新頂點的值 vertices.add(val); // 在鄰接矩陣中新增一行 List newRow = List.filled(n, 0, growable: true); adjMat.add(newRow); // 在鄰接矩陣中新增一列 for (List row in adjMat) { row.add(0); } } /* 刪除頂點 */ void removeVertex(int index) { if (index >= size()) { throw IndexError; } // 在頂點串列中移除索引 index 的頂點 vertices.removeAt(index); // 在鄰接矩陣中刪除索引 index 的行 adjMat.removeAt(index); // 在鄰接矩陣中刪除索引 index 的列 for (List row in adjMat) { row.removeAt(index); } } /* 新增邊 */ // 參數 i, j 對應 vertices 元素索引 void addEdge(int i, int j) { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw IndexError; } // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) adjMat[i][j] = 1; adjMat[j][i] = 1; } /* 刪除邊 */ // 參數 i, j 對應 vertices 元素索引 void removeEdge(int i, int j) { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw IndexError; } adjMat[i][j] = 0; adjMat[j][i] = 0; } /* 列印鄰接矩陣 */ void printAdjMat() { print("頂點串列 = $vertices"); print("鄰接矩陣 = "); printMatrix(adjMat); } } /* Driver Code */ void main() { /* 初始化無向圖 */ // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 List vertices = [1, 3, 2, 5, 4]; List> edges = [ [0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4], ]; GraphAdjMat graph = GraphAdjMat(vertices, edges); print("\n初始化後,圖為"); graph.printAdjMat(); /* 新增邊 */ // 頂點 1, 2 的索引分別為 0, 2 graph.addEdge(0, 2); print("\n新增邊 1-2 後,圖為"); graph.printAdjMat(); /* 刪除邊 */ // 頂點 1, 3 的索引分別為 0, 1 graph.removeEdge(0, 1); print("\n刪除邊 1-3 後,圖為"); graph.printAdjMat(); /* 新增頂點 */ graph.addVertex(6); print("\n新增頂點 6 後,圖為"); graph.printAdjMat(); /* 刪除頂點 */ // 頂點 3 的索引為 1 graph.removeVertex(1); print("\n刪除頂點 3 後,圖為"); graph.printAdjMat(); } ================================================ FILE: zh-hant/codes/dart/chapter_graph/graph_bfs.dart ================================================ /** * File: graph_bfs.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; import '../utils/vertex.dart'; import 'graph_adjacency_list.dart'; /* 廣度優先走訪 */ List graphBFS(GraphAdjList graph, Vertex startVet) { // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 // 頂點走訪序列 List res = []; // 雜湊集合,用於記錄已被訪問過的頂點 Set visited = {}; visited.add(startVet); // 佇列用於實現 BFS Queue que = Queue(); que.add(startVet); // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 while (que.isNotEmpty) { Vertex vet = que.removeFirst(); // 佇列首頂點出隊 res.add(vet); // 記錄訪問頂點 // 走訪該頂點的所有鄰接頂點 for (Vertex adjVet in graph.adjList[vet]!) { if (visited.contains(adjVet)) { continue; // 跳過已被訪問的頂點 } que.add(adjVet); // 只入列未訪問的頂點 visited.add(adjVet); // 標記該頂點已被訪問 } } // 返回頂點走訪序列 return res; } /* Dirver Code */ void main() { /* 初始化無向圖 */ List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; GraphAdjList graph = GraphAdjList(edges); print("\n初始化後,圖為"); graph.printAdjList(); /* 廣度優先走訪 */ List res = graphBFS(graph, v[0]); print("\n廣度優先走訪(BFS)頂點序列為"); print(Vertex.vetsToVals(res)); } ================================================ FILE: zh-hant/codes/dart/chapter_graph/graph_dfs.dart ================================================ /** * File: graph_dfs.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/vertex.dart'; import 'graph_adjacency_list.dart'; /* 深度優先走訪輔助函式 */ void dfs( GraphAdjList graph, Set visited, List res, Vertex vet, ) { res.add(vet); // 記錄訪問頂點 visited.add(vet); // 標記該頂點已被訪問 // 走訪該頂點的所有鄰接頂點 for (Vertex adjVet in graph.adjList[vet]!) { if (visited.contains(adjVet)) { continue; // 跳過已被訪問的頂點 } // 遞迴訪問鄰接頂點 dfs(graph, visited, res, adjVet); } } /* 深度優先走訪 */ List graphDFS(GraphAdjList graph, Vertex startVet) { // 頂點走訪序列 List res = []; // 雜湊集合,用於記錄已被訪問過的頂點 Set visited = {}; dfs(graph, visited, res, startVet); return res; } /* Driver Code */ void main() { /* 初始化無向圖 */ List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); List> edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; GraphAdjList graph = GraphAdjList(edges); print("\n初始化後,圖為"); graph.printAdjList(); /* 深度優先走訪 */ List res = graphDFS(graph, v[0]); print("\n深度優先走訪(DFS)頂點序列為"); print(Vertex.vetsToVals(res)); } ================================================ FILE: zh-hant/codes/dart/chapter_greedy/coin_change_greedy.dart ================================================ /** * File: coin_change_greedy.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 零錢兌換:貪婪 */ int coinChangeGreedy(List coins, int amt) { // 假設 coins 串列有序 int i = coins.length - 1; int count = 0; // 迴圈進行貪婪選擇,直到無剩餘金額 while (amt > 0) { // 找到小於且最接近剩餘金額的硬幣 while (i > 0 && coins[i] > amt) { i--; } // 選擇 coins[i] amt -= coins[i]; count++; } // 若未找到可行方案,則返回 -1 return amt == 0 ? count : -1; } /* Driver Code */ void main() { // 貪婪:能夠保證找到全域性最優解 List coins = [1, 5, 10, 20, 50, 100]; int amt = 186; int res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("湊到 $amt 所需的最少硬幣數量為 $res"); // 貪婪:無法保證找到全域性最優解 coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("湊到 $amt 所需的最少硬幣數量為 $res"); print("實際上需要的最少數量為 3 ,即 20 + 20 + 20"); // 貪婪:無法保證找到全域性最優解 coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); print("\ncoins = $coins, amt = $amt"); print("湊到 $amt 所需的最少硬幣數量為 $res"); print("實際上需要的最少數量為 2 ,即 49 + 49"); } ================================================ FILE: zh-hant/codes/dart/chapter_greedy/fractional_knapsack.dart ================================================ /** * File: fractional_knapsack.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 物品 */ class Item { int w; // 物品重量 int v; // 物品價值 Item(this.w, this.v); } /* 分數背包:貪婪 */ double fractionalKnapsack(List wgt, List val, int cap) { // 建立物品串列,包含兩個屬性:重量、價值 List items = List.generate(wgt.length, (i) => Item(wgt[i], val[i])); // 按照單位價值 item.v / item.w 從高到低進行排序 items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w)); // 迴圈貪婪選擇 double res = 0; for (Item item in items) { if (item.w <= cap) { // 若剩餘容量充足,則將當前物品整個裝進背包 res += item.v; cap -= item.w; } else { // 若剩餘容量不足,則將當前物品的一部分裝進背包 res += item.v / item.w * cap; // 已無剩餘容量,因此跳出迴圈 break; } } return res; } /* Driver Code */ void main() { List wgt = [10, 20, 30, 40, 50]; List val = [50, 120, 150, 210, 240]; int cap = 50; // 貪婪演算法 double res = fractionalKnapsack(wgt, val, cap); print("不超過背包容量的最大物品價值為 $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_greedy/max_capacity.dart ================================================ /** * File: max_capacity.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 最大容量:貪婪 */ int maxCapacity(List ht) { // 初始化 i, j,使其分列陣列兩端 int i = 0, j = ht.length - 1; // 初始最大容量為 0 int res = 0; // 迴圈貪婪選擇,直至兩板相遇 while (i < j) { // 更新最大容量 int cap = min(ht[i], ht[j]) * (j - i); res = max(res, cap); // 向內移動短板 if (ht[i] < ht[j]) { i++; } else { j--; } } return res; } /* Driver Code */ void main() { List ht = [3, 8, 5, 2, 7, 7, 3, 4]; // 貪婪演算法 int res = maxCapacity(ht); print("最大容量為 $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_greedy/max_product_cutting.dart ================================================ /** * File: max_product_cutting.dart * Created Time: 2023-08-11 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; /* 最大切分乘積:貪婪 */ int maxProductCutting(int n) { // 當 n <= 3 時,必須切分出一個 1 if (n <= 3) { return 1 * (n - 1); } // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 int a = n ~/ 3; int b = n % 3; if (b == 1) { // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 return (pow(3, a - 1) * 2 * 2).toInt(); } if (b == 2) { // 當餘數為 2 時,不做處理 return (pow(3, a) * 2).toInt(); } // 當餘數為 0 時,不做處理 return pow(3, a).toInt(); } /* Driver Code */ void main() { int n = 58; // 貪婪演算法 int res = maxProductCutting(n); print("最大切分乘積為 $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_hashing/array_hash_map.dart ================================================ /** * File: array_hash_map.dart * Created Time: 2023-03-29 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 鍵值對 */ class Pair { int key; String val; Pair(this.key, this.val); } /* 基於陣列實現的雜湊表 */ class ArrayHashMap { late List _buckets; ArrayHashMap() { // 初始化陣列,包含 100 個桶 _buckets = List.filled(100, null); } /* 雜湊函式 */ int _hashFunc(int key) { final int index = key % 100; return index; } /* 查詢操作 */ String? get(int key) { final int index = _hashFunc(key); final Pair? pair = _buckets[index]; if (pair == null) { return null; } return pair.val; } /* 新增操作 */ void put(int key, String val) { final Pair pair = Pair(key, val); final int index = _hashFunc(key); _buckets[index] = pair; } /* 刪除操作 */ void remove(int key) { final int index = _hashFunc(key); _buckets[index] = null; } /* 獲取所有鍵值對 */ List pairSet() { List pairSet = []; for (final Pair? pair in _buckets) { if (pair != null) { pairSet.add(pair); } } return pairSet; } /* 獲取所有鍵 */ List keySet() { List keySet = []; for (final Pair? pair in _buckets) { if (pair != null) { keySet.add(pair.key); } } return keySet; } /* 獲取所有值 */ List values() { List valueSet = []; for (final Pair? pair in _buckets) { if (pair != null) { valueSet.add(pair.val); } } return valueSet; } /* 列印雜湊表 */ void printHashMap() { for (final Pair kv in pairSet()) { print("${kv.key} -> ${kv.val}"); } } } /* Driver Code */ void main() { /* 初始化雜湊表 */ final ArrayHashMap map = ArrayHashMap(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(12836, "小哈"); map.put(15937, "小囉"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鴨"); print("\n新增完成後,雜湊表為\nKey -> Value"); map.printHashMap(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value String? name = map.get(15937); print("\n輸入學號 15937 ,查詢到姓名 $name"); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(10583); print("\n刪除 10583 後,雜湊表為\nKey -> Value"); map.printHashMap(); /* 走訪雜湊表 */ print("\n走訪鍵值對 Key->Value"); map.pairSet().forEach((kv) => print("${kv.key} -> ${kv.val}")); print("\n單獨走訪鍵 Key"); map.keySet().forEach((key) => print("$key")); print("\n單獨走訪值 Value"); map.values().forEach((val) => print("$val")); } ================================================ FILE: zh-hant/codes/dart/chapter_hashing/built_in_hash.dart ================================================ /** * File: built_in_hash.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../chapter_stack_and_queue/linkedlist_deque.dart'; /* Driver Code */ void main() { int _num = 3; int hashNum = _num.hashCode; print("整數 $_num 的雜湊值為 $hashNum"); bool bol = true; int hashBol = bol.hashCode; print("布林值 $bol 的雜湊值為 $hashBol"); double dec = 3.14159; int hashDec = dec.hashCode; print("小數 $dec 的雜湊值為 $hashDec"); String str = "Hello 演算法"; int hashStr = str.hashCode; print("字串 $str 的雜湊值為 $hashStr"); List arr = [12836, "小哈"]; int hashArr = arr.hashCode; print("陣列 $arr 的雜湊值為 $hashArr"); ListNode obj = new ListNode(0); int hashObj = obj.hashCode; print("節點物件 $obj 的雜湊值為 $hashObj"); } ================================================ FILE: zh-hant/codes/dart/chapter_hashing/hash_map.dart ================================================ /** * File: hash_map.dart * Created Time: 2023-03-29 * Author: liuyuxin (gvenusleo@gmail.com) */ /* Driver Code */ void main() { /* 初始化雜湊表 */ final Map map = {}; /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map[12836] = "小哈"; map[15937] = "小囉"; map[16750] = "小算"; map[13276] = "小法"; map[10583] = "小鴨"; print("\n新增完成後,雜湊表為\nKey -> Value"); map.forEach((key, value) => print("$key -> $value")); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value final String? name = map[15937]; print("\n輸入學號 15937 ,查詢到姓名 $name"); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(10583); print("\n刪除 10583 後,雜湊表為\nKey -> Value"); map.forEach((key, value) => print("$key -> $value")); /* 走訪雜湊表 */ print("\n走訪鍵值對 Key->Value"); map.forEach((key, value) => print("$key -> $value")); print("\n單獨走訪鍵 Key"); map.keys.forEach((key) => print(key)); print("\n單獨走訪值 Value"); map.forEach((key, value) => print("$value")); map.values.forEach((value) => print(value)); } ================================================ FILE: zh-hant/codes/dart/chapter_hashing/hash_map_chaining.dart ================================================ /** * File: hash_map_chaining.dart * Created Time: 2023-06-24 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'array_hash_map.dart'; /* 鏈式位址雜湊表 */ class HashMapChaining { late int size; // 鍵值對數量 late int capacity; // 雜湊表容量 late double loadThres; // 觸發擴容的負載因子閾值 late int extendRatio; // 擴容倍數 late List> buckets; // 桶陣列 /* 建構子 */ HashMapChaining() { size = 0; capacity = 4; loadThres = 2.0 / 3.0; extendRatio = 2; buckets = List.generate(capacity, (_) => []); } /* 雜湊函式 */ int hashFunc(int key) { return key % capacity; } /* 負載因子 */ double loadFactor() { return size / capacity; } /* 查詢操作 */ String? get(int key) { int index = hashFunc(key); List bucket = buckets[index]; // 走訪桶,若找到 key ,則返回對應 val for (Pair pair in bucket) { if (pair.key == key) { return pair.val; } } // 若未找到 key ,則返回 null return null; } /* 新增操作 */ void put(int key, String val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); List bucket = buckets[index]; // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for (Pair pair in bucket) { if (pair.key == key) { pair.val = val; return; } } // 若無該 key ,則將鍵值對新增至尾部 Pair pair = Pair(key, val); bucket.add(pair); size++; } /* 刪除操作 */ void remove(int key) { int index = hashFunc(key); List bucket = buckets[index]; // 走訪桶,從中刪除鍵值對 for (Pair pair in bucket) { if (pair.key == key) { bucket.remove(pair); size--; break; } } } /* 擴容雜湊表 */ void extend() { // 暫存原雜湊表 List> bucketsTmp = buckets; // 初始化擴容後的新雜湊表 capacity *= extendRatio; buckets = List.generate(capacity, (_) => []); size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (List bucket in bucketsTmp) { for (Pair pair in bucket) { put(pair.key, pair.val); } } } /* 列印雜湊表 */ void printHashMap() { for (List bucket in buckets) { List res = []; for (Pair pair in bucket) { res.add("${pair.key} -> ${pair.val}"); } print(res); } } } /* Driver Code */ void main() { /* 初始化雜湊表 */ HashMapChaining map = HashMapChaining(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(12836, "小哈"); map.put(15937, "小囉"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鴨"); print("\n新增完成後,雜湊表為\nKey -> Value"); map.printHashMap(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value String? name = map.get(13276); print("\n輸入學號 13276 ,查詢到姓名 ${name}"); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(12836); print("\n刪除 12836 後,雜湊表為\nKey -> Value"); map.printHashMap(); } ================================================ FILE: zh-hant/codes/dart/chapter_hashing/hash_map_open_addressing.dart ================================================ /** * File: hash_map_open_addressing.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'array_hash_map.dart'; /* 開放定址雜湊表 */ class HashMapOpenAddressing { late int _size; // 鍵值對數量 int _capacity = 4; // 雜湊表容量 double _loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 int _extendRatio = 2; // 擴容倍數 late List _buckets; // 桶陣列 Pair _TOMBSTONE = Pair(-1, "-1"); // 刪除標記 /* 建構子 */ HashMapOpenAddressing() { _size = 0; _buckets = List.generate(_capacity, (index) => null); } /* 雜湊函式 */ int hashFunc(int key) { return key % _capacity; } /* 負載因子 */ double loadFactor() { return _size / _capacity; } /* 搜尋 key 對應的桶索引 */ int findBucket(int key) { int index = hashFunc(key); int firstTombstone = -1; // 線性探查,當遇到空桶時跳出 while (_buckets[index] != null) { // 若遇到 key ,返回對應的桶索引 if (_buckets[index]!.key == key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone != -1) { _buckets[firstTombstone] = _buckets[index]; _buckets[index] = _TOMBSTONE; return firstTombstone; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) { firstTombstone = index; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % _capacity; } // 若 key 不存在,則返回新增點的索引 return firstTombstone == -1 ? index : firstTombstone; } /* 查詢操作 */ String? get(int key) { // 搜尋 key 對應的桶索引 int index = findBucket(key); // 若找到鍵值對,則返回對應 val if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { return _buckets[index]!.val; } // 若鍵值對不存在,則返回 null return null; } /* 新增操作 */ void put(int key, String val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor() > _loadThres) { extend(); } // 搜尋 key 對應的桶索引 int index = findBucket(key); // 若找到鍵值對,則覆蓋 val 並返回 if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { _buckets[index]!.val = val; return; } // 若鍵值對不存在,則新增該鍵值對 _buckets[index] = new Pair(key, val); _size++; } /* 刪除操作 */ void remove(int key) { // 搜尋 key 對應的桶索引 int index = findBucket(key); // 若找到鍵值對,則用刪除標記覆蓋它 if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { _buckets[index] = _TOMBSTONE; _size--; } } /* 擴容雜湊表 */ void extend() { // 暫存原雜湊表 List bucketsTmp = _buckets; // 初始化擴容後的新雜湊表 _capacity *= _extendRatio; _buckets = List.generate(_capacity, (index) => null); _size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (Pair? pair in bucketsTmp) { if (pair != null && pair != _TOMBSTONE) { put(pair.key, pair.val); } } } /* 列印雜湊表 */ void printHashMap() { for (Pair? pair in _buckets) { if (pair == null) { print("null"); } else if (pair == _TOMBSTONE) { print("TOMBSTONE"); } else { print("${pair.key} -> ${pair.val}"); } } } } /* Driver Code */ void main() { /* 初始化雜湊表 */ HashMapOpenAddressing map = HashMapOpenAddressing(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(12836, "小哈"); map.put(15937, "小囉"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鴨"); print("\n新增完成後,雜湊表為\nKey -> Value"); map.printHashMap(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value String? name = map.get(13276); print("\n輸入學號 13276 ,查詢到姓名 $name"); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(16750); print("\n刪除 16750 後,雜湊表為\nKey -> Value"); map.printHashMap(); } ================================================ FILE: zh-hant/codes/dart/chapter_hashing/simple_hash.dart ================================================ /** * File: simple_hash.dart * Created Time: 2023-06-25 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 加法雜湊 */ int addHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = (hash + key.codeUnitAt(i)) % MODULUS; } return hash; } /* 乘法雜湊 */ int mulHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = (31 * hash + key.codeUnitAt(i)) % MODULUS; } return hash; } /* 互斥或雜湊 */ int xorHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash ^= key.codeUnitAt(i); } return hash & MODULUS; } /* 旋轉雜湊 */ int rotHash(String key) { int hash = 0; final int MODULUS = 1000000007; for (int i = 0; i < key.length; i++) { hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS; } return hash; } /* Dirver Code */ void main() { String key = "Hello 演算法"; int hash = addHash(key); print("加法雜湊值為 $hash"); hash = mulHash(key); print("乘法雜湊值為 $hash"); hash = xorHash(key); print("互斥或雜湊值為 $hash"); hash = rotHash(key); print("旋轉雜湊值為 $hash"); } ================================================ FILE: zh-hant/codes/dart/chapter_heap/my_heap.dart ================================================ /** * File: my_heap.dart * Created Time: 2023-04-09 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* 大頂堆積 */ class MaxHeap { late List _maxHeap; /* 建構子,根據輸入串列建堆積 */ MaxHeap(List nums) { // 將串列元素原封不動新增進堆積 _maxHeap = nums; // 堆積化除葉節點以外的其他所有節點 for (int i = _parent(size() - 1); i >= 0; i--) { siftDown(i); } } /* 獲取左子節點的索引 */ int _left(int i) { return 2 * i + 1; } /* 獲取右子節點的索引 */ int _right(int i) { return 2 * i + 2; } /* 獲取父節點的索引 */ int _parent(int i) { return (i - 1) ~/ 2; // 向下整除 } /* 交換元素 */ void _swap(int i, int j) { int tmp = _maxHeap[i]; _maxHeap[i] = _maxHeap[j]; _maxHeap[j] = tmp; } /* 獲取堆積大小 */ int size() { return _maxHeap.length; } /* 判斷堆積是否為空 */ bool isEmpty() { return size() == 0; } /* 訪問堆積頂元素 */ int peek() { return _maxHeap[0]; } /* 元素入堆積 */ void push(int val) { // 新增節點 _maxHeap.add(val); // 從底至頂堆積化 siftUp(size() - 1); } /* 從節點 i 開始,從底至頂堆積化 */ void siftUp(int i) { while (true) { // 獲取節點 i 的父節點 int p = _parent(i); // 當“越過根節點”或“節點無須修復”時,結束堆積化 if (p < 0 || _maxHeap[i] <= _maxHeap[p]) { break; } // 交換兩節點 _swap(i, p); // 迴圈向上堆積化 i = p; } } /* 元素出堆積 */ int pop() { // 判空處理 if (isEmpty()) throw Exception('堆積為空'); // 交換根節點與最右葉節點(交換首元素與尾元素) _swap(0, size() - 1); // 刪除節點 int val = _maxHeap.removeLast(); // 從頂至底堆積化 siftDown(0); // 返回堆積頂元素 return val; } /* 從節點 i 開始,從頂至底堆積化 */ void siftDown(int i) { while (true) { // 判斷節點 i, l, r 中值最大的節點,記為 ma int l = _left(i); int r = _right(i); int ma = i; if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l; if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r; // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if (ma == i) break; // 交換兩節點 _swap(i, ma); // 迴圈向下堆積化 i = ma; } } /* 列印堆積(二元樹) */ void print() { printHeap(_maxHeap); } } /* Driver Code */ void main() { /* 初始化大頂堆積 */ MaxHeap maxHeap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); print("\n輸入串列並建堆積後"); maxHeap.print(); /* 獲取堆積頂元素 */ int peek = maxHeap.peek(); print("\n堆積頂元素為 $peek"); /* 元素入堆積 */ int val = 7; maxHeap.push(val); print("\n元素 $val 入堆積後"); maxHeap.print(); /* 堆積頂元素出堆積 */ peek = maxHeap.pop(); print("\n堆積頂元素 $peek 出堆積後"); maxHeap.print(); /* 獲取堆積大小 */ int size = maxHeap.size(); print("\n堆積元素數量為 $size"); /* 判斷堆積是否為空 */ bool isEmpty = maxHeap.isEmpty(); print("\n堆積是否為空 $isEmpty"); } ================================================ FILE: zh-hant/codes/dart/chapter_heap/top_k.dart ================================================ /** * File: top_k.dart * Created Time: 2023-08-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; /* 基於堆積查詢陣列中最大的 k 個元素 */ MinHeap topKHeap(List nums, int k) { // 初始化小頂堆積,將陣列的前 k 個元素入堆積 MinHeap heap = MinHeap(nums.sublist(0, k)); // 從第 k+1 個元素開始,保持堆積的長度為 k for (int i = k; i < nums.length; i++) { // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 if (nums[i] > heap.peek()) { heap.pop(); heap.push(nums[i]); } } return heap; } /* Driver Code */ void main() { List nums = [1, 7, 6, 3, 2]; int k = 3; MinHeap res = topKHeap(nums, k); print("最大的 $k 個元素為"); res.print(); } /* 小頂堆積 */ class MinHeap { late List _minHeap; /* 建構子,根據輸入串列建堆積 */ MinHeap(List nums) { // 將串列元素原封不動新增進堆積 _minHeap = nums; // 堆積化除葉節點以外的其他所有節點 for (int i = _parent(size() - 1); i >= 0; i--) { siftDown(i); } } /* 返回堆積中的元素 */ List getHeap() { return _minHeap; } /* 獲取左子節點的索引 */ int _left(int i) { return 2 * i + 1; } /* 獲取右子節點的索引 */ int _right(int i) { return 2 * i + 2; } /* 獲取父節點的索引 */ int _parent(int i) { return (i - 1) ~/ 2; // 向下整除 } /* 交換元素 */ void _swap(int i, int j) { int tmp = _minHeap[i]; _minHeap[i] = _minHeap[j]; _minHeap[j] = tmp; } /* 獲取堆積大小 */ int size() { return _minHeap.length; } /* 判斷堆積是否為空 */ bool isEmpty() { return size() == 0; } /* 訪問堆積頂元素 */ int peek() { return _minHeap[0]; } /* 元素入堆積 */ void push(int val) { // 新增節點 _minHeap.add(val); // 從底至頂堆積化 siftUp(size() - 1); } /* 從節點 i 開始,從底至頂堆積化 */ void siftUp(int i) { while (true) { // 獲取節點 i 的父節點 int p = _parent(i); // 當“越過根節點”或“節點無須修復”時,結束堆積化 if (p < 0 || _minHeap[i] >= _minHeap[p]) { break; } // 交換兩節點 _swap(i, p); // 迴圈向上堆積化 i = p; } } /* 元素出堆積 */ int pop() { // 判空處理 if (isEmpty()) throw Exception('堆積為空'); // 交換根節點與最右葉節點(交換首元素與尾元素) _swap(0, size() - 1); // 刪除節點 int val = _minHeap.removeLast(); // 從頂至底堆積化 siftDown(0); // 返回堆積頂元素 return val; } /* 從節點 i 開始,從頂至底堆積化 */ void siftDown(int i) { while (true) { // 判斷節點 i, l, r 中值最大的節點,記為 ma int l = _left(i); int r = _right(i); int mi = i; if (l < size() && _minHeap[l] < _minHeap[mi]) mi = l; if (r < size() && _minHeap[r] < _minHeap[mi]) mi = r; // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if (mi == i) break; // 交換兩節點 _swap(i, mi); // 迴圈向下堆積化 i = mi; } } /* 列印堆積(二元樹) */ void print() { printHeap(_minHeap); } } ================================================ FILE: zh-hant/codes/dart/chapter_searching/binary_search.dart ================================================ /** * File: binary_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 二分搜尋(雙閉區間) */ int binarySearch(List nums, int target) { // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 int i = 0, j = nums.length - 1; // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) while (i <= j) { int m = i + (j - i) ~/ 2; // 計算中點索引 m if (nums[m] < target) { // 此情況說明 target 在區間 [m+1, j] 中 i = m + 1; } else if (nums[m] > target) { // 此情況說明 target 在區間 [i, m-1] 中 j = m - 1; } else { // 找到目標元素,返回其索引 return m; } } // 未找到目標元素,返回 -1 return -1; } /* 二分搜尋(左閉右開區間) */ int binarySearchLCRO(List nums, int target) { // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 int i = 0, j = nums.length; // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) while (i < j) { int m = i + (j - i) ~/ 2; // 計算中點索引 m if (nums[m] < target) { // 此情況說明 target 在區間 [m+1, j) 中 i = m + 1; } else if (nums[m] > target) { // 此情況說明 target 在區間 [i, m) 中 j = m; } else { // 找到目標元素,返回其索引 return m; } } // 未找到目標元素,返回 -1 return -1; } /* Driver Code*/ void main() { int target = 6; final nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分搜尋 (雙閉區間) */ int index = binarySearch(nums, target); print('目標元素 6 的索引 = $index'); /* 二分搜尋(左閉右開區間) */ index = binarySearchLCRO(nums, target); print('目標元素 6 的索引 = $index'); } ================================================ FILE: zh-hant/codes/dart/chapter_searching/binary_search_edge.dart ================================================ /** * File: binary_search_edge.dart * Created Time: 2023-08-14 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'binary_search_insertion.dart'; /* 二分搜尋最左一個 target */ int binarySearchLeftEdge(List nums, int target) { // 等價於查詢 target 的插入點 int i = binarySearchInsertion(nums, target); // 未找到 target ,返回 -1 if (i == nums.length || nums[i] != target) { return -1; } // 找到 target ,返回索引 i return i; } /* 二分搜尋最右一個 target */ int binarySearchRightEdge(List nums, int target) { // 轉化為查詢最左一個 target + 1 int i = binarySearchInsertion(nums, target + 1); // j 指向最右一個 target ,i 指向首個大於 target 的元素 int j = i - 1; // 未找到 target ,返回 -1 if (j == -1 || nums[j] != target) { return -1; } // 找到 target ,返回索引 j return j; } /* Driver Code */ void main() { // 包含重複元素的陣列 List nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; print("\n陣列 nums = $nums"); // 二分搜尋左邊界和右邊界 for (int target in [6, 7]) { int index = binarySearchLeftEdge(nums, target); print("最左一個元素 $target 的索引為 $index"); index = binarySearchRightEdge(nums, target); print("最右一個元素 $target 的索引為 $index"); } } ================================================ FILE: zh-hant/codes/dart/chapter_searching/binary_search_insertion.dart ================================================ /** * File: binary_search_insertion.dart * Created Time: 2023-08-14 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 二分搜尋插入點(無重複元素) */ int binarySearchInsertionSimple(List nums, int target) { int i = 0, j = nums.length - 1; // 初始化雙閉區間 [0, n-1] while (i <= j) { int m = i + (j - i) ~/ 2; // 計算中點索引 m if (nums[m] < target) { i = m + 1; // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在區間 [i, m-1] 中 } else { return m; // 找到 target ,返回插入點 m } } // 未找到 target ,返回插入點 i return i; } /* 二分搜尋插入點(存在重複元素) */ int binarySearchInsertion(List nums, int target) { int i = 0, j = nums.length - 1; // 初始化雙閉區間 [0, n-1] while (i <= j) { int m = i + (j - i) ~/ 2; // 計算中點索引 m if (nums[m] < target) { i = m + 1; // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在區間 [i, m-1] 中 } else { j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 } } // 返回插入點 i return i; } /* Driver Code */ void main() { // 無重複元素的陣列 List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; print("\n陣列 nums = $nums"); // 二分搜尋插入點 for (int target in [6, 9]) { int index = binarySearchInsertionSimple(nums, target); print("元素 $target 的插入點的索引為 $index"); } // 包含重複元素的陣列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; print("\n陣列 nums = $nums"); // 二分搜尋插入點 for (int target in [2, 6, 20]) { int index = binarySearchInsertion(nums, target); print("元素 $target 的插入點的索引為 $index"); } } ================================================ FILE: zh-hant/codes/dart/chapter_searching/hashing_search.dart ================================================ /** * File: hashing_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:collection'; import '../utils/list_node.dart'; /* 雜湊查詢(陣列) */ int hashingSearchArray(Map map, int target) { // 雜湊表的 key: 目標元素,value: 索引 // 若雜湊表中無此 key ,返回 -1 if (!map.containsKey(target)) { return -1; } return map[target]!; } /* 雜湊查詢(鏈結串列) */ ListNode? hashingSearchLinkedList(Map map, int target) { // 雜湊表的 key: 目標節點值,value: 節點物件 // 若雜湊表中無此 key ,返回 null if (!map.containsKey(target)) { return null; } return map[target]!; } /* Driver Code */ void main() { int target = 3; /* 雜湊查詢(陣列) */ List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // 初始化雜湊表 Map map = HashMap(); for (int i = 0; i < nums.length; i++) { map.putIfAbsent(nums[i], () => i); // key: 元素,value: 索引 } int index = hashingSearchArray(map, target); print('目標元素 3 的索引 = $index'); /* 雜湊查詢(鏈結串列) */ ListNode? head = listToLinkedList(nums); // 初始化雜湊表 Map map1 = HashMap(); while (head != null) { map1.putIfAbsent(head.val, () => head!); // key: 節點值,value: 節點 head = head.next; } ListNode? node = hashingSearchLinkedList(map1, target); print('目標節點值 3 的對應節點物件為 $node'); } ================================================ FILE: zh-hant/codes/dart/chapter_searching/linear_search.dart ================================================ /** * File: linear_search.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import '../utils/list_node.dart'; /* 線性查詢(陣列) */ int linearSearchArray(List nums, int target) { // 走訪陣列 for (int i = 0; i < nums.length; i++) { // 找到目標元素,返回其索引 if (nums[i] == target) { return i; } } // 未找到目標元素,返回 -1 return -1; } /* 線性查詢(鏈結串列) */ ListNode? linearSearchList(ListNode? head, int target) { // 走訪鏈結串列 while (head != null) { // 找到目標節點,返回之 if (head.val == target) return head; head = head.next; } // 未找到目標元素,返回 null return null; } /* Driver Code */ void main() { int target = 3; /* 在陣列中執行線性查詢 */ List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; int index = linearSearchArray(nums, target); print('目標元素 3 的索引 = $index'); /* 在鏈結串列中執行線性查詢 */ ListNode? head = listToLinkedList(nums); ListNode? node = linearSearchList(head, target); print('目標節點值 3 的對應節點物件為 $node'); } ================================================ FILE: zh-hant/codes/dart/chapter_searching/two_sum.dart ================================================ /** * File: two_sum.dart * Created Time: 2023-2-11 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:collection'; /* 方法一: 暴力列舉 */ List twoSumBruteForce(List nums, int target) { int size = nums.length; // 兩層迴圈,時間複雜度為 O(n^2) for (var i = 0; i < size - 1; i++) { for (var j = i + 1; j < size; j++) { if (nums[i] + nums[j] == target) return [i, j]; } } return [0]; } /* 方法二: 輔助雜湊表 */ List twoSumHashTable(List nums, int target) { int size = nums.length; // 輔助雜湊表,空間複雜度為 O(n) Map dic = HashMap(); // 單層迴圈,時間複雜度為 O(n) for (var i = 0; i < size; i++) { if (dic.containsKey(target - nums[i])) { return [dic[target - nums[i]]!, i]; } dic.putIfAbsent(nums[i], () => i); } return [0]; } /* Driver Code */ void main() { // ======= Test Case ======= List nums = [2, 7, 11, 15]; int target = 13; // ====== Driver Code ====== // 方法一 List res = twoSumBruteForce(nums, target); print('方法一 res = $res'); // 方法二 res = twoSumHashTable(nums, target); print('方法二 res = $res'); } ================================================ FILE: zh-hant/codes/dart/chapter_sorting/bubble_sort.dart ================================================ /** * File: bubble_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* 泡沫排序 */ void bubbleSort(List nums) { // 外迴圈:未排序區間為 [0, i] for (int i = nums.length - 1; i > 0; i--) { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* 泡沫排序(標誌最佳化)*/ void bubbleSortWithFlag(List nums) { // 外迴圈:未排序區間為 [0, i] for (int i = nums.length - 1; i > 0; i--) { bool flag = false; // 初始化標誌位 // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // 記錄交換元素 } } if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); print("泡沫排序完成後 nums = $nums"); List nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); print("泡沫排序完成後 nums1 = $nums1"); } ================================================ FILE: zh-hant/codes/dart/chapter_sorting/bucket_sort.dart ================================================ /** * File: bucket_sort.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 桶排序 */ void bucketSort(List nums) { // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 int k = nums.length ~/ 2; List> buckets = List.generate(k, (index) => []); // 1. 將陣列元素分配到各個桶中 for (double _num in nums) { // 輸入資料範圍為 [0, 1),使用 _num * k 對映到索引範圍 [0, k-1] int i = (_num * k).toInt(); // 將 _num 新增進桶 bucket_idx buckets[i].add(_num); } // 2. 對各個桶執行排序 for (List bucket in buckets) { bucket.sort(); } // 3. 走訪桶合併結果 int i = 0; for (List bucket in buckets) { for (double _num in bucket) { nums[i++] = _num; } } } /* Driver Code*/ void main() { // 設輸入資料為浮點數,範圍為 [0, 1) final nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); print('桶排序完成後 nums = $nums'); } ================================================ FILE: zh-hant/codes/dart/chapter_sorting/counting_sort.dart ================================================ /** * File: counting_sort.dart * Created Time: 2023-05-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:math'; /* 計數排序 */ // 簡單實現,無法用於排序物件 void countingSortNaive(List nums) { // 1. 統計陣列最大元素 m int m = 0; for (int _num in nums) { m = max(m, _num); } // 2. 統計各數字的出現次數 // counter[_num] 代表 _num 的出現次數 List counter = List.filled(m + 1, 0); for (int _num in nums) { counter[_num]++; } // 3. 走訪 counter ,將各元素填入原陣列 nums int i = 0; for (int _num = 0; _num < m + 1; _num++) { for (int j = 0; j < counter[_num]; j++, i++) { nums[i] = _num; } } } /* 計數排序 */ // 完整實現,可排序物件,並且是穩定排序 void countingSort(List nums) { // 1. 統計陣列最大元素 m int m = 0; for (int _num in nums) { m = max(m, _num); } // 2. 統計各數字的出現次數 // counter[_num] 代表 _num 的出現次數 List counter = List.filled(m + 1, 0); for (int _num in nums) { counter[_num]++; } // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” // 即 counter[_num]-1 是 _num 在 res 中最後一次出現的索引 for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. 倒序走訪 nums ,將各元素填入結果陣列 res // 初始化陣列 res 用於記錄結果 int n = nums.length; List res = List.filled(n, 0); for (int i = n - 1; i >= 0; i--) { int _num = nums[i]; res[counter[_num] - 1] = _num; // 將 _num 放置到對應索引處 counter[_num]--; // 令前綴和自減 1 ,得到下次放置 _num 的索引 } // 使用結果陣列 res 覆蓋原陣列 nums nums.setAll(0, res); } /* Driver Code*/ void main() { final nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); print('計數排序(無法排序物件)完成後 nums = $nums'); final nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); print('計數排序完成後 nums1 = $nums1'); } ================================================ FILE: zh-hant/codes/dart/chapter_sorting/heap_sort.dart ================================================ /** * File: heap_sort.dart * Created Time: 2023-06-01 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ void siftDown(List nums, int n, int i) { while (true) { // 判斷節點 i, l, r 中值最大的節點,記為 ma int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; if (l < n && nums[l] > nums[ma]) ma = l; if (r < n && nums[r] > nums[ma]) ma = r; // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if (ma == i) break; // 交換兩節點 int temp = nums[i]; nums[i] = nums[ma]; nums[ma] = temp; // 迴圈向下堆積化 i = ma; } } /* 堆積排序 */ void heapSort(List nums) { // 建堆積操作:堆積化除葉節點以外的其他所有節點 for (int i = nums.length ~/ 2 - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // 從堆積中提取最大元素,迴圈 n-1 輪 for (int i = nums.length - 1; i > 0; i--) { // 交換根節點與最右葉節點(交換首元素與尾元素) int tmp = nums[0]; nums[0] = nums[i]; nums[i] = tmp; // 以根節點為起點,從頂至底進行堆積化 siftDown(nums, i, 0); } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; heapSort(nums); print("堆積排序完成後 nums = $nums"); } ================================================ FILE: zh-hant/codes/dart/chapter_sorting/insertion_sort.dart ================================================ /** * File: insertion_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* 插入排序 */ void insertionSort(List nums) { // 外迴圈:已排序區間為 [0, i-1] for (int i = 1; i < nums.length; i++) { int base = nums[i], j = i - 1; // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 j--; } nums[j + 1] = base; // 將 base 賦值到正確位置 } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); print("插入排序完成後 nums = $nums"); } ================================================ FILE: zh-hant/codes/dart/chapter_sorting/merge_sort.dart ================================================ /** * File: merge_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* 合併左子陣列和右子陣列 */ void merge(List nums, int left, int mid, int right) { // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] // 建立一個臨時陣列 tmp ,用於存放合併後的結果 List tmp = List.filled(right - left + 1, 0); // 初始化左子陣列和右子陣列的起始索引 int i = left, j = mid + 1, k = 0; // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* 合併排序 */ void mergeSort(List nums, int left, int right) { // 終止條件 if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 // 劃分階段 int mid = left + (right - left) ~/ 2; // 計算中點 mergeSort(nums, left, mid); // 遞迴左子陣列 mergeSort(nums, mid + 1, right); // 遞迴右子陣列 // 合併階段 merge(nums, left, mid, right); } /* Driver Code */ void main() { /* 合併排序 */ List nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); print("合併排序完成後 nums = $nums"); } ================================================ FILE: zh-hant/codes/dart/chapter_sorting/quick_sort.dart ================================================ /** * File: quick_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* 快速排序類別 */ class QuickSort { /* 元素交換 */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵劃分 */ static int _partition(List nums, int left, int right) { // 以 nums[left] 為基準數 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 從右向左找首個小於基準數的元素 while (i < j && nums[i] <= nums[left]) i++; // 從左向右找首個大於基準數的元素 _swap(nums, i, j); // 交換這兩個元素 } _swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } /* 快速排序 */ static void quickSort(List nums, int left, int right) { // 子陣列長度為 1 時終止遞迴 if (left >= right) return; // 哨兵劃分 int pivot = _partition(nums, left, right); // 遞迴左子陣列、右子陣列 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } /* 快速排序類別(中位基準數最佳化) */ class QuickSortMedian { /* 元素交換 */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 選取三個候選元素的中位數 */ static int _medianThree(List nums, int left, int mid, int right) { int l = nums[left], m = nums[mid], r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // m 在 l 和 r 之間 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l 在 m 和 r 之間 return right; } /* 哨兵劃分(三數取中值) */ static int _partition(List 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; // 返回基準數的索引 } /* 快速排序 */ static void quickSort(List nums, int left, int right) { // 子陣列長度為 1 時終止遞迴 if (left >= right) return; // 哨兵劃分 int pivot = _partition(nums, left, right); // 遞迴左子陣列、右子陣列 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } /* 快速排序類別(遞迴深度最佳化) */ class QuickSortTailCall { /* 元素交換 */ static void _swap(List nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵劃分 */ static int _partition(List nums, int left, int right) { // 以 nums[left] 為基準數 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 從右向左找首個小於基準數的元素 while (i < j && nums[i] <= nums[left]) i++; // 從左向右找首個大於基準數的元素 _swap(nums, i, j); // 交換這兩個元素 } _swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } /* 快速排序(遞迴深度最佳化) */ static void quickSort(List 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] } } } } /* Driver Code */ void main() { /* 快速排序 */ List nums = [2, 4, 1, 0, 3, 5]; QuickSort.quickSort(nums, 0, nums.length - 1); print("快速排序完成後 nums = $nums"); /* 快速排序(中位基準數最佳化) */ List nums1 = [2, 4, 1, 0, 3, 5]; QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); print("快速排序(中位基準數最佳化)完成後 nums1 = $nums1"); /* 快速排序(遞迴深度最佳化) */ List nums2 = [2, 4, 1, 0, 3, 5]; QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); print("快速排序(遞迴深度最佳化)完成後 nums2 = $nums2"); } ================================================ FILE: zh-hant/codes/dart/chapter_sorting/radix_sort.dart ================================================ /** * File: radix_sort.dart * Created Time: 2023-02-14 * Author: what-is-me (whatisme@outlook.jp) */ /* 獲取元素 _num 的第 k 位,其中 exp = 10^(k-1) */ int digit(int _num, int exp) { // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 return (_num ~/ exp) % 10; } /* 計數排序(根據 nums 第 k 位排序) */ void countingSortDigit(List nums, int exp) { // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 List counter = List.filled(10, 0); int n = nums.length; // 統計 0~9 各數字的出現次數 for (int i = 0; i < n; i++) { int d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d counter[d]++; // 統計數字 d 的出現次數 } // 求前綴和,將“出現個數”轉換為“陣列索引” for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 倒序走訪,根據桶內統計結果,將各元素填入 res List res = List.filled(n, 0); for (int i = n - 1; i >= 0; i--) { int d = digit(nums[i], exp); int j = counter[d] - 1; // 獲取 d 在陣列中的索引 j res[j] = nums[i]; // 將當前元素填入索引 j counter[d]--; // 將 d 的數量減 1 } // 使用結果覆蓋原陣列 nums for (int i = 0; i < n; i++) nums[i] = res[i]; } /* 基數排序 */ void radixSort(List nums) { // 獲取陣列的最大元素,用於判斷最大位數 // dart 中 int 的長度是 64 位的 int m = -1 << 63; for (int _num in nums) if (_num > m) m = _num; // 按照從低位到高位的順序走訪 for (int exp = 1; exp <= m; exp *= 10) // 對陣列元素的第 k 位執行計數排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums, exp); } /* Driver Code */ void main() { // 基數排序 List nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ]; radixSort(nums); print("基數排序完成後 nums = $nums"); } ================================================ FILE: zh-hant/codes/dart/chapter_sorting/selection_sort.dart ================================================ /** * File: selection_sort.dart * Created Time: 2023-06-01 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 選擇排序 */ void selectionSort(List nums) { int n = nums.length; // 外迴圈:未排序區間為 [i, n-1] for (int i = 0; i < n - 1; i++) { // 內迴圈:找到未排序區間內的最小元素 int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // 記錄最小元素的索引 } // 將該最小元素與未排序區間的首個元素交換 int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; } } /* Driver Code */ void main() { List nums = [4, 1, 3, 1, 5, 2]; selectionSort(nums); print("選擇排序完成後 nums = $nums"); } ================================================ FILE: zh-hant/codes/dart/chapter_stack_and_queue/array_deque.dart ================================================ /** * File: array_deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 基於環形陣列實現的雙向佇列 */ class ArrayDeque { late List _nums; // 用於儲存雙向佇列元素的陣列 late int _front; // 佇列首指標,指向佇列首元素 late int _queSize; // 雙向佇列長度 /* 建構子 */ ArrayDeque(int capacity) { this._nums = List.filled(capacity, 0); this._front = this._queSize = 0; } /* 獲取雙向佇列的容量 */ int capacity() { return _nums.length; } /* 獲取雙向佇列的長度 */ int size() { return _queSize; } /* 判斷雙向佇列是否為空 */ bool isEmpty() { return _queSize == 0; } /* 計算環形陣列索引 */ int index(int i) { // 透過取餘操作實現陣列首尾相連 // 當 i 越過陣列尾部後,回到頭部 // 當 i 越過陣列頭部後,回到尾部 return (i + capacity()) % capacity(); } /* 佇列首入列 */ void pushFirst(int _num) { if (_queSize == capacity()) { throw Exception("雙向佇列已滿"); } // 佇列首指標向左移動一位 // 透過取餘操作實現 _front 越過陣列頭部後回到尾部 _front = index(_front - 1); // 將 _num 新增至佇列首 _nums[_front] = _num; _queSize++; } /* 佇列尾入列 */ void pushLast(int _num) { if (_queSize == capacity()) { throw Exception("雙向佇列已滿"); } // 計算佇列尾指標,指向佇列尾索引 + 1 int rear = index(_front + _queSize); // 將 _num 新增至佇列尾 _nums[rear] = _num; _queSize++; } /* 佇列首出列 */ int popFirst() { int _num = peekFirst(); // 佇列首指標向右移動一位 _front = index(_front + 1); _queSize--; return _num; } /* 佇列尾出列 */ int popLast() { int _num = peekLast(); _queSize--; return _num; } /* 訪問佇列首元素 */ int peekFirst() { if (isEmpty()) { throw Exception("雙向佇列為空"); } return _nums[_front]; } /* 訪問佇列尾元素 */ int peekLast() { if (isEmpty()) { throw Exception("雙向佇列為空"); } // 計算尾元素索引 int last = index(_front + _queSize - 1); return _nums[last]; } /* 返回陣列用於列印 */ List toArray() { // 僅轉換有效長度範圍內的串列元素 List res = List.filled(_queSize, 0); for (int i = 0, j = _front; i < _queSize; i++, j++) { res[i] = _nums[index(j)]; } return res; } } /* Driver Code */ void main() { /* 初始化雙向佇列 */ final ArrayDeque deque = ArrayDeque(10); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); print("雙向佇列 deque = ${deque.toArray()}"); /* 訪問元素 */ final int peekFirst = deque.peekFirst(); print("佇列首元素 peekFirst = $peekFirst"); final int peekLast = deque.peekLast(); print("佇列尾元素 peekLast = $peekLast"); /* 元素入列 */ deque.pushLast(4); print("元素 4 佇列尾入列後 deque = ${deque.toArray()}"); deque.pushFirst(1); print("元素 1 佇列首入列後 deque = ${deque.toArray()}"); /* 元素出列 */ final int popLast = deque.popLast(); print("佇列尾出列元素 = $popLast ,佇列尾出列後 deque = ${deque.toArray()}"); final int popFirst = deque.popFirst(); print("佇列首出列元素 = $popFirst ,佇列首出列後 deque = ${deque.toArray()}"); /* 獲取雙向佇列的長度 */ final int size = deque.size(); print("雙向佇列長度 size = $size"); /* 判斷雙向佇列是否為空 */ final bool isEmpty = deque.isEmpty(); print("雙向佇列是否為空 = $isEmpty"); } ================================================ FILE: zh-hant/codes/dart/chapter_stack_and_queue/array_queue.dart ================================================ /** * File: array_queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 基於環形陣列實現的佇列 */ class ArrayQueue { late List _nums; // 用於儲存佇列元素的陣列 late int _front; // 佇列首指標,指向佇列首元素 late int _queSize; // 佇列長度 ArrayQueue(int capacity) { _nums = List.filled(capacity, 0); _front = _queSize = 0; } /* 獲取佇列的容量 */ int capaCity() { return _nums.length; } /* 獲取佇列的長度 */ int size() { return _queSize; } /* 判斷佇列是否為空 */ bool isEmpty() { return _queSize == 0; } /* 入列 */ void push(int _num) { if (_queSize == capaCity()) { throw Exception("佇列已滿"); } // 計算佇列尾指標,指向佇列尾索引 + 1 // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 int rear = (_front + _queSize) % capaCity(); // 將 _num 新增至佇列尾 _nums[rear] = _num; _queSize++; } /* 出列 */ int pop() { int _num = peek(); // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 _front = (_front + 1) % capaCity(); _queSize--; return _num; } /* 訪問佇列首元素 */ int peek() { if (isEmpty()) { throw Exception("佇列為空"); } return _nums[_front]; } /* 返回 Array */ List toArray() { // 僅轉換有效長度範圍內的串列元素 final List res = List.filled(_queSize, 0); for (int i = 0, j = _front; i < _queSize; i++, j++) { res[i] = _nums[j % capaCity()]; } return res; } } /* Driver Code */ void main() { /* 初始化佇列 */ final int capacity = 10; final ArrayQueue queue = ArrayQueue(capacity); /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print("佇列 queue = ${queue.toArray()}"); /* 訪問佇列首元素 */ final int peek = queue.peek(); print("佇列首元素 peek = $peek"); /* 元素出列 */ final int pop = queue.pop(); print("出列元素 pop = $pop ,出列後 queue = ${queue.toArray()}"); /* 獲取佇列長度 */ final int size = queue.size(); print("佇列長度 size = $size"); /* 判斷佇列是否為空 */ final bool isEmpty = queue.isEmpty(); print("佇列是否為空 = $isEmpty"); /* 測試環形陣列 */ for (int i = 0; i < 10; i++) { queue.push(i); queue.pop(); print("第 $i 輪入列 + 出列後 queue = ${queue.toArray()}"); } } ================================================ FILE: zh-hant/codes/dart/chapter_stack_and_queue/array_stack.dart ================================================ /** * File: array_stack.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 基於陣列實現的堆疊 */ class ArrayStack { late List _stack; ArrayStack() { _stack = []; } /* 獲取堆疊的長度 */ int size() { return _stack.length; } /* 判斷堆疊是否為空 */ bool isEmpty() { return _stack.isEmpty; } /* 入堆疊 */ void push(int _num) { _stack.add(_num); } /* 出堆疊 */ int pop() { if (isEmpty()) { throw Exception("堆疊為空"); } return _stack.removeLast(); } /* 訪問堆疊頂元素 */ int peek() { if (isEmpty()) { throw Exception("堆疊為空"); } return _stack.last; } /* 將堆疊轉化為 Array 並返回 */ List toArray() => _stack; } /* Driver Code */ void main() { /* 初始化堆疊 */ final ArrayStack stack = ArrayStack(); /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print("堆疊 stack = ${stack.toArray()}"); /* 訪問堆疊頂元素 */ final int peek = stack.peek(); print("堆疊頂元素 peek = $peek"); /* 元素出堆疊 */ final int pop = stack.pop(); print("出堆疊元素 pop = $pop ,出堆疊後 stack = ${stack.toArray()}"); /* 獲取堆疊的長度 */ final int size = stack.size(); print("堆疊的長度 size = $size"); /* 判斷是否為空 */ final bool isEmpty = stack.isEmpty(); print("堆疊是否為空 = $isEmpty"); } ================================================ FILE: zh-hant/codes/dart/chapter_stack_and_queue/deque.dart ================================================ /** * File: deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; void main() { /* 初始化雙向佇列 */ final Queue deque = Queue(); deque.addFirst(3); deque.addLast(2); deque.addLast(5); print("雙向佇列 deque = $deque"); /* 訪問元素 */ final int peekFirst = deque.first; print("佇列首元素 peekFirst = $peekFirst"); final int peekLast = deque.last; print("佇列尾元素 peekLast = $peekLast"); /* 元素入列 */ deque.addLast(4); print("元素 4 佇列尾入列後 deque = $deque"); deque.addFirst(1); print("元素 1 佇列首入列後 deque = $deque"); /* 元素出列 */ final int popLast = deque.removeLast(); print("佇列尾出列元素 = $popLast ,佇列尾出列後 deque = $deque"); final int popFirst = deque.removeFirst(); print("佇列首出列元素 = $popFirst ,佇列首出列後 deque = $deque"); /* 獲取雙向佇列的長度 */ final int size = deque.length; print("雙向佇列長度 size = $size"); /* 判斷雙向佇列是否為空 */ final bool isEmpty = deque.isEmpty; print("雙向佇列是否為空 = $isEmpty"); } ================================================ FILE: zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart ================================================ /** * File: linkedlist_deque.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 雙向鏈結串列節點 */ class ListNode { int val; // 節點值 ListNode? next; // 後繼節點引用 ListNode? prev; // 前驅節點引用 ListNode(this.val, {this.next, this.prev}); } /* 基於雙向鏈結串列實現的雙向對列 */ class LinkedListDeque { late ListNode? _front; // 頭節點 _front late ListNode? _rear; // 尾節點 _rear int _queSize = 0; // 雙向佇列的長度 LinkedListDeque() { this._front = null; this._rear = null; } /* 獲取雙向佇列長度 */ int size() { return this._queSize; } /* 判斷雙向佇列是否為空 */ bool isEmpty() { return size() == 0; } /* 入列操作 */ void push(int _num, bool isFront) { final ListNode node = ListNode(_num); if (isEmpty()) { // 若鏈結串列為空,則令 _front 和 _rear 都指向 node _front = _rear = node; } else if (isFront) { // 佇列首入列操作 // 將 node 新增至鏈結串列頭部 _front!.prev = node; node.next = _front; _front = node; // 更新頭節點 } else { // 佇列尾入列操作 // 將 node 新增至鏈結串列尾部 _rear!.next = node; node.prev = _rear; _rear = node; // 更新尾節點 } _queSize++; // 更新佇列長度 } /* 佇列首入列 */ void pushFirst(int _num) { push(_num, true); } /* 佇列尾入列 */ void pushLast(int _num) { push(_num, false); } /* 出列操作 */ int? pop(bool isFront) { // 若佇列為空,直接返回 null if (isEmpty()) { return null; } final int val; if (isFront) { // 佇列首出列操作 val = _front!.val; // 暫存頭節點值 // 刪除頭節點 ListNode? fNext = _front!.next; if (fNext != null) { fNext.prev = null; _front!.next = null; } _front = fNext; // 更新頭節點 } else { // 佇列尾出列操作 val = _rear!.val; // 暫存尾節點值 // 刪除尾節點 ListNode? rPrev = _rear!.prev; if (rPrev != null) { rPrev.next = null; _rear!.prev = null; } _rear = rPrev; // 更新尾節點 } _queSize--; // 更新佇列長度 return val; } /* 佇列首出列 */ int? popFirst() { return pop(true); } /* 佇列尾出列 */ int? popLast() { return pop(false); } /* 訪問佇列首元素 */ int? peekFirst() { return _front?.val; } /* 訪問佇列尾元素 */ int? peekLast() { return _rear?.val; } /* 返回陣列用於列印 */ List toArray() { ListNode? node = _front; final List res = []; for (int i = 0; i < _queSize; i++) { res.add(node!.val); node = node.next; } return res; } } /* Driver Code */ void main() { /* 初始化雙向佇列 */ final LinkedListDeque deque = LinkedListDeque(); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); print("雙向佇列 deque = ${deque.toArray()}"); /* 訪問元素 */ int? peekFirst = deque.peekFirst(); print("佇列首元素 peekFirst = $peekFirst"); int? peekLast = deque.peekLast(); print("佇列尾元素 peekLast = $peekLast"); /* 元素入列 */ deque.pushLast(4); print("元素 4 佇列尾入列後 deque = ${deque.toArray()}"); deque.pushFirst(1); print("元素 1 佇列首入列後 deque = ${deque.toArray()}"); /* 元素出列 */ int? popLast = deque.popLast(); print("佇列尾出列元素 = $popLast ,佇列尾出列後 deque = ${deque.toArray()}"); int? popFirst = deque.popFirst(); print("佇列首出列元素 = $popFirst ,佇列首出列後 deque = ${deque.toArray()}"); /* 獲取雙向佇列的長度 */ int size = deque.size(); print("雙向佇列長度 size = $size"); /* 判斷雙向佇列是否為空 */ bool isEmpty = deque.isEmpty(); print("雙向佇列是否為空 = $isEmpty"); } ================================================ FILE: zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart ================================================ /** * File: linkedlist_queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/list_node.dart'; /* 基於鏈結串列實現的佇列 */ class LinkedListQueue { ListNode? _front; // 頭節點 _front ListNode? _rear; // 尾節點 _rear int _queSize = 0; // 佇列長度 LinkedListQueue() { _front = null; _rear = null; } /* 獲取佇列的長度 */ int size() { return _queSize; } /* 判斷佇列是否為空 */ bool isEmpty() { return _queSize == 0; } /* 入列 */ void push(int _num) { // 在尾節點後新增 _num final node = ListNode(_num); // 如果佇列為空,則令頭、尾節點都指向該節點 if (_front == null) { _front = node; _rear = node; } else { // 如果佇列不為空,則將該節點新增到尾節點後 _rear!.next = node; _rear = node; } _queSize++; } /* 出列 */ int pop() { final int _num = peek(); // 刪除頭節點 _front = _front!.next; _queSize--; return _num; } /* 訪問佇列首元素 */ int peek() { if (_queSize == 0) { throw Exception('佇列為空'); } return _front!.val; } /* 將鏈結串列轉化為 Array 並返回 */ List toArray() { ListNode? node = _front; final List queue = []; while (node != null) { queue.add(node.val); node = node.next; } return queue; } } /* Driver Code */ void main() { /* 初始化佇列 */ final queue = LinkedListQueue(); /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print("佇列 queue = ${queue.toArray()}"); /* 訪問佇列首元素 */ final int peek = queue.peek(); print("佇列首元素 peek = $peek"); /* 元素出列 */ final int pop = queue.pop(); print("出列元素 pop = $pop ,出列後 queue = ${queue.toArray()}"); /* 獲取佇列的長度 */ final int size = queue.size(); print("佇列長度 size = $size"); /* 判斷佇列是否為空 */ final bool isEmpty = queue.isEmpty(); print("佇列是否為空 = $isEmpty"); } ================================================ FILE: zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart ================================================ /** * File: linkedlist_stack.dart * Created Time: 2023-03-27 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/list_node.dart'; /* 基於鏈結串列類別實現的堆疊 */ class LinkedListStack { ListNode? _stackPeek; // 將頭節點作為堆疊頂 int _stkSize = 0; // 堆疊的長度 LinkedListStack() { _stackPeek = null; } /* 獲取堆疊的長度 */ int size() { return _stkSize; } /* 判斷堆疊是否為空 */ bool isEmpty() { return _stkSize == 0; } /* 入堆疊 */ void push(int _num) { final ListNode node = ListNode(_num); node.next = _stackPeek; _stackPeek = node; _stkSize++; } /* 出堆疊 */ int pop() { final int _num = peek(); _stackPeek = _stackPeek!.next; _stkSize--; return _num; } /* 訪問堆疊頂元素 */ int peek() { if (_stackPeek == null) { throw Exception("堆疊為空"); } return _stackPeek!.val; } /* 將鏈結串列轉化為 List 並返回 */ List toList() { ListNode? node = _stackPeek; List list = []; while (node != null) { list.add(node.val); node = node.next; } list = list.reversed.toList(); return list; } } /* Driver Code */ void main() { /* 初始化堆疊 */ final LinkedListStack stack = LinkedListStack(); /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print("堆疊 stack = ${stack.toList()}"); /* 訪問堆疊頂元素 */ final int peek = stack.peek(); print("堆疊頂元素 peek = $peek"); /* 元素出堆疊 */ final int pop = stack.pop(); print("出堆疊元素 pop = $pop ,出堆疊後 stack = ${stack.toList()}"); /* 獲取堆疊的長度 */ final int size = stack.size(); print("堆疊的長度 size = $size"); /* 判斷是否為空 */ final bool isEmpty = stack.isEmpty(); print("堆疊是否為空 = $isEmpty"); } ================================================ FILE: zh-hant/codes/dart/chapter_stack_and_queue/queue.dart ================================================ /** * File: queue.dart * Created Time: 2023-03-28 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:collection'; void main() { /* 初始化佇列 */ // 在 Dart 中,一般將雙向佇列類別 Queue 看作佇列使用 final Queue queue = Queue(); /* 元素入列 */ queue.add(1); queue.add(3); queue.add(2); queue.add(5); queue.add(4); print("佇列 queue = $queue"); /* 訪問佇列首元素 */ final int peek = queue.first; print("佇列首元素 peek = $peek"); /* 元素出列 */ final int pop = queue.removeFirst(); print("出列元素 pop = $pop ,出列後 queue = $queue"); /* 獲取佇列長度 */ final int size = queue.length; print("佇列長度 size = $size"); /* 判斷佇列是否為空 */ final bool isEmpty = queue.isEmpty; print("佇列是否為空 = $isEmpty"); } ================================================ FILE: zh-hant/codes/dart/chapter_stack_and_queue/stack.dart ================================================ /** * File: stack.dart * Created Time: 2023-03-27 * Author: liuyuxin (gvenusleo@gmail.com) */ void main() { /* 初始化堆疊 */ // Dart 沒有內建的堆疊類別,可以把 List 當作堆疊來使用 final List stack = []; /* 元素入堆疊 */ stack.add(1); stack.add(3); stack.add(2); stack.add(5); stack.add(4); print("堆疊 stack = $stack"); /* 訪問堆疊頂元素 */ final int peek = stack.last; print("堆疊頂元素 peek = $peek"); /* 元素出堆疊 */ final int pop = stack.removeLast(); print("出堆疊元素 pop = $pop ,出堆疊後 stack = $stack"); /* 獲取堆疊的長度 */ final int size = stack.length; print("堆疊的長度 size = $size"); /* 判斷是否為空 */ final bool isEmpty = stack.isEmpty; print("堆疊是否為空 = $isEmpty"); } ================================================ FILE: zh-hant/codes/dart/chapter_tree/array_binary_tree.dart ================================================ /** * File: array_binary_tree.dart * Created Time: 2023-08-15 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 陣列表示下的二元樹類別 */ class ArrayBinaryTree { late List _tree; /* 建構子 */ ArrayBinaryTree(this._tree); /* 串列容量 */ int size() { return _tree.length; } /* 獲取索引為 i 節點的值 */ int? val(int i) { // 若索引越界,則返回 null ,代表空位 if (i < 0 || i >= size()) { return null; } return _tree[i]; } /* 獲取索引為 i 節點的左子節點的索引 */ int? left(int i) { return 2 * i + 1; } /* 獲取索引為 i 節點的右子節點的索引 */ int? right(int i) { return 2 * i + 2; } /* 獲取索引為 i 節點的父節點的索引 */ int? parent(int i) { return (i - 1) ~/ 2; } /* 層序走訪 */ List levelOrder() { List res = []; for (int i = 0; i < size(); i++) { if (val(i) != null) { res.add(val(i)!); } } return res; } /* 深度優先走訪 */ void dfs(int i, String order, List res) { // 若為空位,則返回 if (val(i) == null) { return; } // 前序走訪 if (order == 'pre') { res.add(val(i)); } dfs(left(i)!, order, res); // 中序走訪 if (order == 'in') { res.add(val(i)); } dfs(right(i)!, order, res); // 後序走訪 if (order == 'post') { res.add(val(i)); } } /* 前序走訪 */ List preOrder() { List res = []; dfs(0, 'pre', res); return res; } /* 中序走訪 */ List inOrder() { List res = []; dfs(0, 'in', res); return res; } /* 後序走訪 */ List postOrder() { List res = []; dfs(0, 'post', res); return res; } } /* Driver Code */ void main() { // 初始化二元樹 // 這裡藉助了一個從陣列直接生成二元樹的函式 List arr = [ 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ]; TreeNode? root = listToTree(arr); print("\n初始化二元樹\n"); print("二元樹的陣列表示:"); print(arr); print("二元樹的鏈結串列表示:"); printTree(root); // 陣列表示下的二元樹類別 ArrayBinaryTree abt = ArrayBinaryTree(arr); // 訪問節點 int i = 1; int? l = abt.left(i); int? r = abt.right(i); int? p = abt.parent(i); print("\n當前節點的索引為 $i ,值為 ${abt.val(i)}"); print("其左子節點的索引為 $l ,值為 ${(l == null ? "null" : abt.val(l))}"); print("其右子節點的索引為 $r ,值為 ${(r == null ? "null" : abt.val(r))}"); print("其父節點的索引為 $p ,值為 ${(p == null ? "null" : abt.val(p))}"); // 走訪樹 List res = abt.levelOrder(); print("\n層序走訪為:$res"); res = abt.preOrder(); print("前序走訪為 $res"); res = abt.inOrder(); print("中序走訪為 $res"); res = abt.postOrder(); print("後序走訪為 $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_tree/avl_tree.dart ================================================ /** * File: avl_tree.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import 'dart:math'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; class AVLTree { TreeNode? root; /* 建構子 */ AVLTree() { root = null; } /* 獲取節點高度 */ int height(TreeNode? node) { // 空節點高度為 -1 ,葉節點高度為 0 return node == null ? -1 : node.height; } /* 更新節點高度 */ void updateHeight(TreeNode? node) { // 節點高度等於最高子樹高度 + 1 node!.height = max(height(node.left), height(node.right)) + 1; } /* 獲取平衡因子 */ int balanceFactor(TreeNode? node) { // 空節點平衡因子為 0 if (node == null) return 0; // 節點平衡因子 = 左子樹高度 - 右子樹高度 return height(node.left) - height(node.right); } /* 右旋操作 */ TreeNode? rightRotate(TreeNode? node) { TreeNode? child = node!.left; TreeNode? grandChild = child!.right; // 以 child 為原點,將 node 向右旋轉 child.right = node; node.left = grandChild; // 更新節點高度 updateHeight(node); updateHeight(child); // 返回旋轉後子樹的根節點 return child; } /* 左旋操作 */ TreeNode? leftRotate(TreeNode? node) { TreeNode? child = node!.right; TreeNode? grandChild = child!.left; // 以 child 為原點,將 node 向左旋轉 child.left = node; node.right = grandChild; // 更新節點高度 updateHeight(node); updateHeight(child); // 返回旋轉後子樹的根節點 return child; } /* 執行旋轉操作,使該子樹重新恢復平衡 */ TreeNode? rotate(TreeNode? node) { // 獲取節點 node 的平衡因子 int factor = balanceFactor(node); // 左偏樹 if (factor > 1) { if (balanceFactor(node!.left) >= 0) { // 右旋 return rightRotate(node); } else { // 先左旋後右旋 node.left = leftRotate(node.left); return rightRotate(node); } } // 右偏樹 if (factor < -1) { if (balanceFactor(node!.right) <= 0) { // 左旋 return leftRotate(node); } else { // 先右旋後左旋 node.right = rightRotate(node.right); return leftRotate(node); } } // 平衡樹,無須旋轉,直接返回 return node; } /* 插入節點 */ void insert(int val) { root = insertHelper(root, val); } /* 遞迴插入節點(輔助方法) */ TreeNode? insertHelper(TreeNode? node, int val) { if (node == null) return TreeNode(val); /* 1. 查詢插入位置並插入節點 */ if (val < node.val) node.left = insertHelper(node.left, val); else if (val > node.val) node.right = insertHelper(node.right, val); else return node; // 重複節點不插入,直接返回 updateHeight(node); // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = rotate(node); // 返回子樹的根節點 return node; } /* 刪除節點 */ void remove(int val) { root = removeHelper(root, val); } /* 遞迴刪除節點(輔助方法) */ TreeNode? removeHelper(TreeNode? node, int val) { if (node == null) return null; /* 1. 查詢節點並刪除 */ if (val < node.val) node.left = removeHelper(node.left, val); else if (val > node.val) node.right = removeHelper(node.right, val); else { if (node.left == null || node.right == null) { TreeNode? child = node.left ?? node.right; // 子節點數量 = 0 ,直接刪除 node 並返回 if (child == null) return null; // 子節點數量 = 1 ,直接刪除 node else node = child; } else { // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 TreeNode? temp = node.right; while (temp!.left != null) { temp = temp.left; } node.right = removeHelper(node.right, temp.val); node.val = temp.val; } } updateHeight(node); // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = rotate(node); // 返回子樹的根節點 return node; } /* 查詢節點 */ TreeNode? search(int val) { TreeNode? cur = root; // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 目標節點在 cur 的右子樹中 if (val < cur.val) cur = cur.left; // 目標節點在 cur 的左子樹中 else if (val > cur.val) cur = cur.right; // 目標節點與當前節點相等 else break; } return cur; } } void testInsert(AVLTree tree, int val) { tree.insert(val); print("\n插入節點 $val 後,AVL 樹為"); printTree(tree.root); } void testRemove(AVLTree tree, int val) { tree.remove(val); print("\n刪除節點 $val 後,AVL 樹為"); printTree(tree.root); } /* Driver Code */ void main() { /* 初始化空 AVL 樹 */ AVLTree avlTree = AVLTree(); /* 插入節點 */ // 請關注插入節點後,AVL 樹是如何保持平衡的 testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); testInsert(avlTree, 4); testInsert(avlTree, 5); testInsert(avlTree, 8); testInsert(avlTree, 7); testInsert(avlTree, 9); testInsert(avlTree, 10); testInsert(avlTree, 6); /* 插入重複節點 */ testInsert(avlTree, 7); /* 刪除節點 */ // 請關注刪除節點後,AVL 樹是如何保持平衡的 testRemove(avlTree, 8); // 刪除度為 0 的節點 testRemove(avlTree, 5); // 刪除度為 1 的節點 testRemove(avlTree, 4); // 刪除度為 2 的節點 /* 查詢節點 */ TreeNode? node = avlTree.search(7); print("\n查詢到的節點物件為 $node ,節點值 = ${node!.val}"); } ================================================ FILE: zh-hant/codes/dart/chapter_tree/binary_search_tree.dart ================================================ /** * File: binary_search_tree.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 二元搜尋樹 */ class BinarySearchTree { late TreeNode? _root; /* 建構子 */ BinarySearchTree() { // 初始化空樹 _root = null; } /* 獲取二元樹的根節點 */ TreeNode? getRoot() { return _root; } /* 查詢節點 */ TreeNode? search(int _num) { TreeNode? cur = _root; // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 目標節點在 cur 的右子樹中 if (cur.val < _num) cur = cur.right; // 目標節點在 cur 的左子樹中 else if (cur.val > _num) cur = cur.left; // 找到目標節點,跳出迴圈 else break; } // 返回目標節點 return cur; } /* 插入節點 */ void insert(int _num) { // 若樹為空,則初始化根節點 if (_root == null) { _root = TreeNode(_num); return; } TreeNode? cur = _root; TreeNode? pre = null; // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 找到重複節點,直接返回 if (cur.val == _num) return; pre = cur; // 插入位置在 cur 的右子樹中 if (cur.val < _num) cur = cur.right; // 插入位置在 cur 的左子樹中 else cur = cur.left; } // 插入節點 TreeNode? node = TreeNode(_num); if (pre!.val < _num) pre.right = node; else pre.left = node; } /* 刪除節點 */ void remove(int _num) { // 若樹為空,直接提前返回 if (_root == null) return; TreeNode? cur = _root; TreeNode? pre = null; // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 找到待刪除節點,跳出迴圈 if (cur.val == _num) break; pre = cur; // 待刪除節點在 cur 的右子樹中 if (cur.val < _num) cur = cur.right; // 待刪除節點在 cur 的左子樹中 else cur = cur.left; } // 若無待刪除節點,直接返回 if (cur == null) return; // 子節點數量 = 0 or 1 if (cur.left == null || cur.right == null) { // 當子節點數量 = 0 / 1 時, child = null / 該子節點 TreeNode? child = cur.left ?? cur.right; // 刪除節點 cur if (cur != _root) { if (pre!.left == cur) pre.left = child; else pre.right = child; } else { // 若刪除節點為根節點,則重新指定根節點 _root = child; } } else { // 子節點數量 = 2 // 獲取中序走訪中 cur 的下一個節點 TreeNode? tmp = cur.right; while (tmp!.left != null) { tmp = tmp.left; } // 遞迴刪除節點 tmp remove(tmp.val); // 用 tmp 覆蓋 cur cur.val = tmp.val; } } } /* Driver Code */ void main() { /* 初始化二元搜尋樹 */ BinarySearchTree bst = BinarySearchTree(); // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 List nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (int _num in nums) { bst.insert(_num); } print("\n初始化的二元樹為\n"); printTree(bst.getRoot()); /* 查詢節點 */ TreeNode? node = bst.search(7); print("\n查詢到的節點物件為 $node ,節點值 = ${node?.val}"); /* 插入節點 */ bst.insert(16); print("\n插入節點 16 後,二元樹為\n"); printTree(bst.getRoot()); /* 刪除節點 */ bst.remove(1); print("\n刪除節點 1 後,二元樹為\n"); printTree(bst.getRoot()); bst.remove(2); print("\n刪除節點 2 後,二元樹為\n"); printTree(bst.getRoot()); bst.remove(4); print("\n刪除節點 4 後,二元樹為\n"); printTree(bst.getRoot()); } ================================================ FILE: zh-hant/codes/dart/chapter_tree/binary_tree.dart ================================================ /** * File: binary_tree.dart * Created Time: 2023-04-03 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; void main() { /* 初始化二元樹 */ // 舒適化節點 TreeNode n1 = TreeNode(1); TreeNode n2 = TreeNode(2); TreeNode n3 = TreeNode(3); TreeNode n4 = TreeNode(4); TreeNode n5 = TreeNode(5); // 構建節點之間的引用(指標) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; print("\n初始化二元樹\n"); printTree(n1); /* 插入與刪除節點 */ TreeNode p = TreeNode(0); // 在 n1 -> n2 中間插入節點 p n1.left = p; p.left = n2; print("\n插入節點 P 後\n"); printTree(n1); // 刪除節點 P n1.left = n2; print("\n刪除節點 P 後\n"); printTree(n1); } ================================================ FILE: zh-hant/codes/dart/chapter_tree/binary_tree_bfs.dart ================================================ /** * File: binary_tree_bfs.dart * Created Time: 2023-04-03 * Author: liuyuxin (gvenusleo@gmai.com) */ import 'dart:collection'; import '../utils/print_util.dart'; import '../utils/tree_node.dart'; /* 層序走訪 */ List levelOrder(TreeNode? root) { // 初始化佇列,加入根節點 Queue queue = Queue(); queue.add(root); // 初始化一個串列,用於儲存走訪序列 List res = []; while (queue.isNotEmpty) { TreeNode? node = queue.removeFirst(); // 隊列出隊 res.add(node!.val); // 儲存節點值 if (node.left != null) queue.add(node.left); // 左子節點入列 if (node.right != null) queue.add(node.right); // 右子節點入列 } return res; } /* Driver Code */ void main() { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); print("\n初始化二元樹\n"); printTree(root); // 層序走訪 List res = levelOrder(root); print("\n層序走訪的節點列印序列 = $res"); } ================================================ FILE: zh-hant/codes/dart/chapter_tree/binary_tree_dfs.dart ================================================ /** * File: binary_tree_dfs.dart * Created Time: 2023-04-04 * Author: liuyuxin (gvenusleo@gmail.com) */ import '../utils/print_util.dart'; import '../utils/tree_node.dart'; // 初始化串列,用於儲存走訪序列 List list = []; /* 前序走訪 */ void preOrder(TreeNode? node) { if (node == null) return; // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 list.add(node.val); preOrder(node.left); preOrder(node.right); } /* 中序走訪 */ void inOrder(TreeNode? node) { if (node == null) return; // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 inOrder(node.left); list.add(node.val); inOrder(node.right); } /* 後序走訪 */ void postOrder(TreeNode? node) { if (node == null) return; // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 postOrder(node.left); postOrder(node.right); list.add(node.val); } /* Driver Code */ void main() { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); print("\n初始化二元樹\n"); printTree(root); /* 前序走訪 */ list.clear(); preOrder(root); print("\n前序走訪的節點列印序列 = $list"); /* 中序走訪 */ list.clear(); inOrder(root); print("\n中序走訪的節點列印序列 = $list"); /* 後序走訪 */ list.clear(); postOrder(root); print("\n後序走訪的節點列印序列 = $list"); } ================================================ FILE: zh-hant/codes/dart/utils/list_node.dart ================================================ /** * File: list_node.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 鏈結串列節點 */ class ListNode { int val; ListNode? next; ListNode(this.val, [this.next]); } /* 將串列反序列化為鏈結串列 */ ListNode? listToLinkedList(List list) { ListNode dum = ListNode(0); ListNode? head = dum; for (int val in list) { head?.next = ListNode(val); head = head?.next; } return dum.next; } ================================================ FILE: zh-hant/codes/dart/utils/print_util.dart ================================================ /** * File: print_util.dart * Created Time: 2023-01-23 * Author: Jefferson (JeffersonHuang77@gmail.com) */ import 'dart:io'; import 'list_node.dart'; import 'tree_node.dart'; class Trunk { Trunk? prev; String str; Trunk(this.prev, this.str); } /* 列印矩陣 (Array) */ void printMatrix(List> matrix) { print("["); for (List row in matrix) { print(" $row,"); } print("]"); } /* 列印鏈結串列 */ void printLinkedList(ListNode? head) { List list = []; while (head != null) { list.add('${head.val}'); head = head.next; } print(list.join(' -> ')); } /** * 列印二元樹 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTree(TreeNode? root, [Trunk? prev = null, bool isRight = false]) { if (root == null) { return; } String prev_str = ' '; Trunk trunk = Trunk(prev, prev_str); printTree(root.right, trunk, true); if (prev == null) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); print(' ${root.val}'); if (prev != null) { prev.str = prev_str; } trunk.str = ' |'; printTree(root.left, trunk, false); } void showTrunks(Trunk? p) { if (p == null) { return; } showTrunks(p.prev); stdout.write(p.str); } /* 列印堆積 */ void printHeap(List heap) { print("堆積的陣列表示:$heap"); print("堆積的樹狀表示:"); TreeNode? root = listToTree(heap); printTree(root); } ================================================ FILE: zh-hant/codes/dart/utils/tree_node.dart ================================================ /** * File: tree_node.dart * Created Time: 2023-2-12 * Author: Jefferson (JeffersonHuang77@gmail.com) */ /* 二元樹節點類別 */ class TreeNode { int val; // 節點值 int height; // 節點高度 TreeNode? left; // 左子節點引用 TreeNode? right; // 右子節點引用 /* 建構子 */ TreeNode(this.val, [this.height = 0, this.left, this.right]); } /* 將串列反序列化為二元樹:遞迴 */ TreeNode? listToTreeDFS(List arr, int i) { if (i < 0 || i >= arr.length || arr[i] == null) { return null; } TreeNode? root = TreeNode(arr[i]!); root.left = listToTreeDFS(arr, 2 * i + 1); root.right = listToTreeDFS(arr, 2 * i + 2); return root; } /* 將串列反序列化為二元樹 */ TreeNode? listToTree(List arr) { return listToTreeDFS(arr, 0); } /* 將二元樹序列化為串列:遞迴 */ void treeToListDFS(TreeNode? root, int i, List res) { if (root == null) return; while (i >= res.length) { res.add(null); } res[i] = root.val; treeToListDFS(root.left, 2 * i + 1, res); treeToListDFS(root.right, 2 * i + 2, res); } /* 將二元樹序列化為串列 */ List treeToList(TreeNode? root) { List res = []; treeToListDFS(root, 0, res); return res; } ================================================ FILE: zh-hant/codes/dart/utils/vertex.dart ================================================ /** * File: Vertex.dart * Created Time: 2023-05-15 * Author: liuyuxin (gvenusleo@gmail.com) */ /* 頂點類別 */ class Vertex { int val; Vertex(this.val); /* 輸入值串列 vals ,返回頂點串列 vets */ static List valsToVets(List vals) { List vets = []; for (int i in vals) { vets.add(Vertex(i)); } return vets; } /* 輸入頂點串列 vets ,返回值串列 vals */ static List vetsToVals(List vets) { List vals = []; for (Vertex vet in vets) { vals.add(vet.val); } return vals; } } ================================================ FILE: zh-hant/codes/docker-compose.yml ================================================ version: '3.8' services: hello-algo-code: build: context: . args: # 設定需要安裝的語言,使用空格隔開 # Set the languages to be installed, separated by spaces LANGS: "python cpp java csharp" image: hello-algo-code container_name: hello-algo-code stdin_open: true tty: true ================================================ FILE: zh-hant/codes/go/chapter_array_and_linkedlist/array.go ================================================ // File: array.go // Created Time: 2022-12-29 // Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( "math/rand" ) /* 隨機訪問元素 */ func randomAccess(nums []int) (randomNum int) { // 在區間 [0, nums.length) 中隨機抽取一個數字 randomIndex := rand.Intn(len(nums)) // 獲取並返回隨機元素 randomNum = nums[randomIndex] return } /* 擴展陣列長度 */ func extend(nums []int, enlarge int) []int { // 初始化一個擴展長度後的陣列 res := make([]int, len(nums)+enlarge) // 將原陣列中的所有元素複製到新陣列 for i, num := range nums { res[i] = num } // 返回擴展後的新陣列 return res } /* 在陣列的索引 index 處插入元素 num */ func insert(nums []int, num int, index int) { // 把索引 index 以及之後的所有元素向後移動一位 for i := len(nums) - 1; i > index; i-- { nums[i] = nums[i-1] } // 將 num 賦給 index 處的元素 nums[index] = num } /* 刪除索引 index 處的元素 */ func remove(nums []int, index int) { // 把索引 index 之後的所有元素向前移動一位 for i := index; i < len(nums)-1; i++ { nums[i] = nums[i+1] } } /* 走訪陣列 */ func traverse(nums []int) { count := 0 // 透過索引走訪陣列 for i := 0; i < len(nums); i++ { count += nums[i] } count = 0 // 直接走訪陣列元素 for _, num := range nums { count += num } // 同時走訪資料索引和元素 for i, num := range nums { count += nums[i] count += num } } /* 在陣列中查詢指定元素 */ func find(nums []int, target int) (index int) { index = -1 for i := 0; i < len(nums); i++ { if nums[i] == target { index = i break } } return } ================================================ FILE: zh-hant/codes/go/chapter_array_and_linkedlist/array_test.go ================================================ // File: array_test.go // Created Time: 2022-12-29 // Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist /** 我們將 Go 中的 Slice 切片看作 Array 陣列。因為這樣可以 降低理解成本,利於我們將關注點放在資料結構與演算法上。 */ import ( "fmt" "testing" ) /* Driver Code */ func TestArray(t *testing.T) { /* 初始化陣列 */ var arr [5]int fmt.Println("陣列 arr =", arr) // 在 Go 中,指定長度時([5]int)為陣列,不指定長度時([]int)為切片 // 由於 Go 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度 // 為了方便實現擴容 extend() 函式,以下將切片(Slice)看作陣列(Array) nums := []int{1, 3, 2, 5, 4} fmt.Println("陣列 nums =", nums) /* 隨機訪問 */ randomNum := randomAccess(nums) fmt.Println("在 nums 中獲取隨機元素", randomNum) /* 長度擴展 */ nums = extend(nums, 3) fmt.Println("將陣列長度擴展至 8 ,得到 nums =", nums) /* 插入元素 */ insert(nums, 6, 3) fmt.Println("在索引 3 處插入數字 6 ,得到 nums =", nums) /* 刪除元素 */ remove(nums, 2) fmt.Println("刪除索引 2 處的元素,得到 nums =", nums) /* 走訪陣列 */ traverse(nums) /* 查詢元素 */ index := find(nums, 3) fmt.Println("在 nums 中查詢元素 3 ,得到索引 =", index) } ================================================ FILE: zh-hant/codes/go/chapter_array_and_linkedlist/linked_list.go ================================================ // File: linked_list.go // Created Time: 2022-12-29 // Author: cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( . "github.com/krahets/hello-algo/pkg" ) /* 在鏈結串列的節點 n0 之後插入節點 P */ func insertNode(n0 *ListNode, P *ListNode) { n1 := n0.Next P.Next = n1 n0.Next = P } /* 刪除鏈結串列的節點 n0 之後的首個節點 */ func removeItem(n0 *ListNode) { if n0.Next == nil { return } // n0 -> P -> n1 P := n0.Next n1 := P.Next n0.Next = n1 } /* 訪問鏈結串列中索引為 index 的節點 */ func access(head *ListNode, index int) *ListNode { for i := 0; i < index; i++ { if head == nil { return nil } head = head.Next } return head } /* 在鏈結串列中查詢值為 target 的首個節點 */ func findNode(head *ListNode, target int) int { index := 0 for head != nil { if head.Val == target { return index } head = head.Next index++ } return -1 } ================================================ FILE: zh-hant/codes/go/chapter_array_and_linkedlist/linked_list_test.go ================================================ // File: linked_list_test.go // Created Time: 2022-12-29 // Author: cathay (cathaycchen@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLinkedList(t *testing.T) { /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各個節點 n0 := NewListNode(1) n1 := NewListNode(3) n2 := NewListNode(2) n3 := NewListNode(5) n4 := NewListNode(4) // 構建節點之間的引用 n0.Next = n1 n1.Next = n2 n2.Next = n3 n3.Next = n4 fmt.Println("初始化的鏈結串列為") PrintLinkedList(n0) /* 插入節點 */ insertNode(n0, NewListNode(0)) fmt.Println("插入節點後的鏈結串列為") PrintLinkedList(n0) /* 刪除節點 */ removeItem(n0) fmt.Println("刪除節點後的鏈結串列為") PrintLinkedList(n0) /* 訪問節點 */ node := access(n0, 3) fmt.Println("鏈結串列中索引 3 處的節點的值 =", node) /* 查詢節點 */ index := findNode(n0, 2) fmt.Println("鏈結串列中值為 2 的節點的索引 =", index) } ================================================ FILE: zh-hant/codes/go/chapter_array_and_linkedlist/list_test.go ================================================ // File: list_test.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "sort" "testing" ) /* Driver Code */ func TestList(t *testing.T) { /* 初始化串列 */ nums := []int{1, 3, 2, 5, 4} fmt.Println("串列 nums =", nums) /* 訪問元素 */ num := nums[1] // 訪問索引 1 處的元素 fmt.Println("訪問索引 1 處的元素,得到 num =", num) /* 更新元素 */ nums[1] = 0 // 將索引 1 處的元素更新為 0 fmt.Println("將索引 1 處的元素更新為 0 ,得到 nums =", nums) /* 清空串列 */ nums = nil fmt.Println("清空串列後 nums =", nums) /* 在尾部新增元素 */ nums = append(nums, 1) nums = append(nums, 3) nums = append(nums, 2) nums = append(nums, 5) nums = append(nums, 4) fmt.Println("新增元素後 nums =", nums) /* 在中間插入元素 */ nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // 在索引 3 處插入數字 6 fmt.Println("在索引 3 處插入數字 6 ,得到 nums =", nums) /* 刪除元素 */ nums = append(nums[:3], nums[4:]...) // 刪除索引 3 處的元素 fmt.Println("刪除索引 3 處的元素,得到 nums =", nums) /* 透過索引走訪串列 */ count := 0 for i := 0; i < len(nums); i++ { count += nums[i] } /* 直接走訪串列元素 */ count = 0 for _, x := range nums { count += x } /* 拼接兩個串列 */ nums1 := []int{6, 8, 7, 10, 9} nums = append(nums, nums1...) // 將串列 nums1 拼接到 nums 之後 fmt.Println("將串列 nums1 拼接到 nums 之後,得到 nums =", nums) /* 排序串列 */ sort.Ints(nums) // 排序後,串列元素從小到大排列 fmt.Println("排序串列後 nums =", nums) } ================================================ FILE: zh-hant/codes/go/chapter_array_and_linkedlist/my_list.go ================================================ // File: my_list.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist /* 串列類別 */ type myList struct { arrCapacity int arr []int arrSize int extendRatio int } /* 建構子 */ func newMyList() *myList { return &myList{ arrCapacity: 10, // 串列容量 arr: make([]int, 10), // 陣列(儲存串列元素) arrSize: 0, // 串列長度(當前元素數量) extendRatio: 2, // 每次串列擴容的倍數 } } /* 獲取串列長度(當前元素數量) */ func (l *myList) size() int { return l.arrSize } /* 獲取串列容量 */ func (l *myList) capacity() int { return l.arrCapacity } /* 訪問元素 */ func (l *myList) get(index int) int { // 索引如果越界,則丟擲異常,下同 if index < 0 || index >= l.arrSize { panic("索引越界") } return l.arr[index] } /* 更新元素 */ func (l *myList) set(num, index int) { if index < 0 || index >= l.arrSize { panic("索引越界") } l.arr[index] = num } /* 在尾部新增元素 */ func (l *myList) add(num int) { // 元素數量超出容量時,觸發擴容機制 if l.arrSize == l.arrCapacity { l.extendCapacity() } l.arr[l.arrSize] = num // 更新元素數量 l.arrSize++ } /* 在中間插入元素 */ func (l *myList) insert(num, index int) { if index < 0 || index >= l.arrSize { panic("索引越界") } // 元素數量超出容量時,觸發擴容機制 if l.arrSize == l.arrCapacity { l.extendCapacity() } // 將索引 index 以及之後的元素都向後移動一位 for j := l.arrSize - 1; j >= index; j-- { l.arr[j+1] = l.arr[j] } l.arr[index] = num // 更新元素數量 l.arrSize++ } /* 刪除元素 */ func (l *myList) remove(index int) int { if index < 0 || index >= l.arrSize { panic("索引越界") } num := l.arr[index] // 將索引 index 之後的元素都向前移動一位 for j := index; j < l.arrSize-1; j++ { l.arr[j] = l.arr[j+1] } // 更新元素數量 l.arrSize-- // 返回被刪除的元素 return num } /* 串列擴容 */ func (l *myList) extendCapacity() { // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列 l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...) // 更新串列容量 l.arrCapacity = len(l.arr) } /* 返回有效長度的串列 */ func (l *myList) toArray() []int { // 僅轉換有效長度範圍內的串列元素 return l.arr[:l.arrSize] } ================================================ FILE: zh-hant/codes/go/chapter_array_and_linkedlist/my_list_test.go ================================================ // File: my_list_test.go // Created Time: 2022-12-18 // Author: msk397 (machangxinq@gmail.com) package chapter_array_and_linkedlist import ( "fmt" "testing" ) /* Driver Code */ func TestMyList(t *testing.T) { /* 初始化串列 */ nums := newMyList() /* 在尾部新增元素 */ nums.add(1) nums.add(3) nums.add(2) nums.add(5) nums.add(4) fmt.Printf("串列 nums = %v ,容量 = %v ,長度 = %v\n", nums.toArray(), nums.capacity(), nums.size()) /* 在中間插入元素 */ nums.insert(6, 3) fmt.Printf("在索引 3 處插入數字 6 ,得到 nums = %v\n", nums.toArray()) /* 刪除元素 */ nums.remove(3) fmt.Printf("刪除索引 3 處的元素,得到 nums = %v\n", nums.toArray()) /* 訪問元素 */ num := nums.get(1) fmt.Printf("訪問索引 1 處的元素,得到 num = %v\n", num) /* 更新元素 */ nums.set(0, 1) fmt.Printf("將索引 1 處的元素更新為 0 ,得到 nums = %v\n", nums.toArray()) /* 測試擴容機制 */ for i := 0; i < 10; i++ { // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 nums.add(i) } fmt.Printf("擴容後的串列 nums = %v ,容量 = %v ,長度 = %v\n", nums.toArray(), nums.capacity(), nums.size()) } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/n_queens.go ================================================ // File: n_queens.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* 回溯演算法:n 皇后 */ func backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) { // 當放置完所有行時,記錄解 if row == n { newState := make([][]string, len(*state)) for i, _ := range newState { newState[i] = make([]string, len((*state)[0])) copy(newState[i], (*state)[i]) } *res = append(*res, newState) return } // 走訪所有列 for col := 0; col < n; col++ { // 計算該格子對應的主對角線和次對角線 diag1 := row - col + n - 1 diag2 := row + col // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] { // 嘗試:將皇后放置在該格子 (*state)[row][col] = "Q" (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true // 放置下一行 backtrack(row+1, n, state, res, cols, diags1, diags2) // 回退:將該格子恢復為空位 (*state)[row][col] = "#" (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false } } } /* 求解 n 皇后 */ func nQueens(n int) [][][]string { // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 state := make([][]string, n) for i := 0; i < n; i++ { row := make([]string, n) for i := 0; i < n; i++ { row[i] = "#" } state[i] = row } // 記錄列是否有皇后 cols := make([]bool, n) diags1 := make([]bool, 2*n-1) diags2 := make([]bool, 2*n-1) res := make([][][]string, 0) backtrack(0, n, &state, &res, &cols, &diags1, &diags2) return res } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/n_queens_test.go ================================================ // File: n_queens_test.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" ) func TestNQueens(t *testing.T) { n := 4 res := nQueens(n) fmt.Println("輸入棋盤長寬為 ", n) fmt.Println("皇后放置方案共有 ", len(res), " 種") for _, state := range res { fmt.Println("--------------------") for _, row := range state { fmt.Println(row) } } } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/permutation_test.go ================================================ // File: permutation_test.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPermutationI(t *testing.T) { /* 全排列 I */ nums := []int{1, 2, 3} fmt.Printf("輸入陣列 nums = ") PrintSlice(nums) res := permutationsI(nums) fmt.Printf("所有排列 res = ") fmt.Println(res) } func TestPermutationII(t *testing.T) { nums := []int{1, 2, 2} fmt.Printf("輸入陣列 nums = ") PrintSlice(nums) res := permutationsII(nums) fmt.Printf("所有排列 res = ") fmt.Println(res) } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/permutations_i.go ================================================ // File: permutations_i.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* 回溯演算法:全排列 I */ func backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // 當狀態長度等於元素數量時,記錄解 if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // 走訪所有選擇 for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // 剪枝:不允許重複選擇元素 if !(*selected)[i] { // 嘗試:做出選擇,更新狀態 (*selected)[i] = true *state = append(*state, choice) // 進行下一輪選擇 backtrackI(state, choices, selected, res) // 回退:撤銷選擇,恢復到之前的狀態 (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* 全排列 I */ func permutationsI(nums []int) [][]int { res := make([][]int, 0) state := make([]int, 0) selected := make([]bool, len(nums)) backtrackI(&state, &nums, &selected, &res) return res } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/permutations_ii.go ================================================ // File: permutations_ii.go // Created Time: 2023-05-14 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* 回溯演算法:全排列 II */ func backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // 當狀態長度等於元素數量時,記錄解 if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // 走訪所有選擇 duplicated := make(map[int]struct{}, 0) for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 if _, ok := duplicated[choice]; !ok && !(*selected)[i] { // 嘗試:做出選擇,更新狀態 // 記錄選擇過的元素值 duplicated[choice] = struct{}{} (*selected)[i] = true *state = append(*state, choice) // 進行下一輪選擇 backtrackII(state, choices, selected, res) // 回退:撤銷選擇,恢復到之前的狀態 (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* 全排列 II */ func permutationsII(nums []int) [][]int { res := make([][]int, 0) state := make([]int, 0) selected := make([]bool, len(nums)) backtrackII(&state, &nums, &selected, &res) return res } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/preorder_traversal_i_compact.go ================================================ // File: preorder_traversal_i_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* 前序走訪:例題一 */ func preOrderI(root *TreeNode, res *[]*TreeNode) { if root == nil { return } if (root.Val).(int) == 7 { // 記錄解 *res = append(*res, root) } preOrderI(root.Left, res) preOrderI(root.Right, res) } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go ================================================ // File: preorder_traversal_ii_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* 前序走訪:例題二 */ func preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { if root == nil { return } // 嘗試 *path = append(*path, root) if root.Val.(int) == 7 { // 記錄解 *res = append(*res, append([]*TreeNode{}, *path...)) } preOrderII(root.Left, res, path) preOrderII(root.Right, res, path) // 回退 *path = (*path)[:len(*path)-1] } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go ================================================ // File: preorder_traversal_iii_compact.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* 前序走訪:例題三 */ func preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { // 剪枝 if root == nil || root.Val == 3 { return } // 嘗試 *path = append(*path, root) if root.Val.(int) == 7 { // 記錄解 *res = append(*res, append([]*TreeNode{}, *path...)) } preOrderIII(root.Left, res, path) preOrderIII(root.Right, res, path) // 回退 *path = (*path)[:len(*path)-1] } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_template.go ================================================ // File: preorder_traversal_iii_template.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( . "github.com/krahets/hello-algo/pkg" ) /* 判斷當前狀態是否為解 */ func isSolution(state *[]*TreeNode) bool { return len(*state) != 0 && (*state)[len(*state)-1].Val == 7 } /* 記錄解 */ func recordSolution(state *[]*TreeNode, res *[][]*TreeNode) { *res = append(*res, append([]*TreeNode{}, *state...)) } /* 判斷在當前狀態下,該選擇是否合法 */ func isValid(state *[]*TreeNode, choice *TreeNode) bool { return choice != nil && choice.Val != 3 } /* 更新狀態 */ func makeChoice(state *[]*TreeNode, choice *TreeNode) { *state = append(*state, choice) } /* 恢復狀態 */ func undoChoice(state *[]*TreeNode, choice *TreeNode) { *state = (*state)[:len(*state)-1] } /* 回溯演算法:例題三 */ func backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) { // 檢查是否為解 if isSolution(state) { // 記錄解 recordSolution(state, res) } // 走訪所有選擇 for _, choice := range *choices { // 剪枝:檢查選擇是否合法 if isValid(state, choice) { // 嘗試:做出選擇,更新狀態 makeChoice(state, choice) // 進行下一輪選擇 temp := make([]*TreeNode, 0) temp = append(temp, choice.Left, choice.Right) backtrackIII(state, &temp, res) // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state, choice) } } } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/preorder_traversal_test.go ================================================ // File: preorder_traversal_i_compact_test.go // Created Time: 2023-05-09 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPreorderTraversalICompact(t *testing.T) { /* 初始化二元樹 */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二元樹") PrintTree(root) // 前序走訪 res := make([]*TreeNode, 0) preOrderI(root, &res) fmt.Println("\n輸出所有值為 7 的節點") for _, node := range res { fmt.Printf("%v ", node.Val) } fmt.Println() } func TestPreorderTraversalIICompact(t *testing.T) { /* 初始化二元樹 */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二元樹") PrintTree(root) // 前序走訪 path := make([]*TreeNode, 0) res := make([][]*TreeNode, 0) preOrderII(root, &res, &path) fmt.Println("\n輸出所有根節點到節點 7 的路徑") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } func TestPreorderTraversalIIICompact(t *testing.T) { /* 初始化二元樹 */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二元樹") PrintTree(root) // 前序走訪 path := make([]*TreeNode, 0) res := make([][]*TreeNode, 0) preOrderIII(root, &res, &path) fmt.Println("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } func TestPreorderTraversalIIITemplate(t *testing.T) { /* 初始化二元樹 */ root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二元樹") PrintTree(root) // 回溯演算法 res := make([][]*TreeNode, 0) state := make([]*TreeNode, 0) choices := make([]*TreeNode, 0) choices = append(choices, root) backtrackIII(&state, &choices, &res) fmt.Println("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) } fmt.Println() } } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/subset_sum_i.go ================================================ // File: subset_sum_i.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import "sort" /* 回溯演算法:子集和 I */ func backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) { // 子集和等於 target 時,記錄解 if target == 0 { newState := append([]int{}, *state...) *res = append(*res, newState) return } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 for i := start; i < len(*choices); i++ { // 剪枝一:若子集和超過 target ,則直接結束迴圈 // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target if target-(*choices)[i] < 0 { break } // 嘗試:做出選擇,更新 target, start *state = append(*state, (*choices)[i]) // 進行下一輪選擇 backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res) // 回退:撤銷選擇,恢復到之前的狀態 *state = (*state)[:len(*state)-1] } } /* 求解子集和 I */ func subsetSumI(nums []int, target int) [][]int { state := make([]int, 0) // 狀態(子集) sort.Ints(nums) // 對 nums 進行排序 start := 0 // 走訪起始點 res := make([][]int, 0) // 結果串列(子集串列) backtrackSubsetSumI(start, target, &state, &nums, &res) return res } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/subset_sum_i_naive.go ================================================ // File: subset_sum_i_naive.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking /* 回溯演算法:子集和 I */ func backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) { // 子集和等於 target 時,記錄解 if target == total { newState := append([]int{}, *state...) *res = append(*res, newState) return } // 走訪所有選擇 for i := 0; i < len(*choices); i++ { // 剪枝:若子集和超過 target ,則跳過該選擇 if total+(*choices)[i] > target { continue } // 嘗試:做出選擇,更新元素和 total *state = append(*state, (*choices)[i]) // 進行下一輪選擇 backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res) // 回退:撤銷選擇,恢復到之前的狀態 *state = (*state)[:len(*state)-1] } } /* 求解子集和 I(包含重複子集) */ func subsetSumINaive(nums []int, target int) [][]int { state := make([]int, 0) // 狀態(子集) total := 0 // 子集和 res := make([][]int, 0) // 結果串列(子集串列) backtrackSubsetSumINaive(total, target, &state, &nums, &res) return res } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/subset_sum_ii.go ================================================ // File: subset_sum_ii.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import "sort" /* 回溯演算法:子集和 II */ func backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) { // 子集和等於 target 時,記錄解 if target == 0 { newState := append([]int{}, *state...) *res = append(*res, newState) return } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 for i := start; i < len(*choices); i++ { // 剪枝一:若子集和超過 target ,則直接結束迴圈 // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target if target-(*choices)[i] < 0 { break } // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 if i > start && (*choices)[i] == (*choices)[i-1] { continue } // 嘗試:做出選擇,更新 target, start *state = append(*state, (*choices)[i]) // 進行下一輪選擇 backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res) // 回退:撤銷選擇,恢復到之前的狀態 *state = (*state)[:len(*state)-1] } } /* 求解子集和 II */ func subsetSumII(nums []int, target int) [][]int { state := make([]int, 0) // 狀態(子集) sort.Ints(nums) // 對 nums 進行排序 start := 0 // 走訪起始點 res := make([][]int, 0) // 結果串列(子集串列) backtrackSubsetSumII(start, target, &state, &nums, &res) return res } ================================================ FILE: zh-hant/codes/go/chapter_backtracking/subset_sum_test.go ================================================ // File: subset_sum_test.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_backtracking import ( "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestSubsetSumINaive(t *testing.T) { nums := []int{3, 4, 5} target := 9 res := subsetSumINaive(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", 輸入陣列 nums = ") PrintSlice(nums) fmt.Println("所有和等於 " + strconv.Itoa(target) + " 的子集 res = ") for i := range res { PrintSlice(res[i]) } fmt.Println("請注意,該方法輸出的結果包含重複集合") } func TestSubsetSumI(t *testing.T) { nums := []int{3, 4, 5} target := 9 res := subsetSumI(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", 輸入陣列 nums = ") PrintSlice(nums) fmt.Println("所有和等於 " + strconv.Itoa(target) + " 的子集 res = ") for i := range res { PrintSlice(res[i]) } } func TestSubsetSumII(t *testing.T) { nums := []int{4, 4, 5} target := 9 res := subsetSumII(nums, target) fmt.Printf("target = " + strconv.Itoa(target) + ", 輸入陣列 nums = ") PrintSlice(nums) fmt.Println("所有和等於 " + strconv.Itoa(target) + " 的子集 res = ") for i := range res { PrintSlice(res[i]) } } ================================================ FILE: zh-hant/codes/go/chapter_computational_complexity/iteration.go ================================================ // File: iteration.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import "fmt" /* for 迴圈 */ func forLoop(n int) int { res := 0 // 迴圈求和 1, 2, ..., n-1, n for i := 1; i <= n; i++ { res += i } return res } /* while 迴圈 */ func whileLoop(n int) int { res := 0 // 初始化條件變數 i := 1 // 迴圈求和 1, 2, ..., n-1, n for i <= n { res += i // 更新條件變數 i++ } return res } /* while 迴圈(兩次更新) */ func whileLoopII(n int) int { res := 0 // 初始化條件變數 i := 1 // 迴圈求和 1, 4, 10, ... for i <= n { res += i // 更新條件變數 i++ i *= 2 } return res } /* 雙層 for 迴圈 */ func nestedForLoop(n int) string { res := "" // 迴圈 i = 1, 2, ..., n-1, n for i := 1; i <= n; i++ { for j := 1; j <= n; j++ { // 迴圈 j = 1, 2, ..., n-1, n res += fmt.Sprintf("(%d, %d), ", i, j) } } return res } ================================================ FILE: zh-hant/codes/go/chapter_computational_complexity/iteration_test.go ================================================ // File: iteration_test.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import ( "fmt" "testing" ) /* Driver Code */ func TestIteration(t *testing.T) { n := 5 res := forLoop(n) fmt.Println("\nfor 迴圈的求和結果 res = ", res) res = whileLoop(n) fmt.Println("\nwhile 迴圈的求和結果 res = ", res) res = whileLoopII(n) fmt.Println("\nwhile 迴圈(兩次更新)求和結果 res = ", res) resStr := nestedForLoop(n) fmt.Println("\n雙層 for 迴圈的走訪結果 ", resStr) } ================================================ FILE: zh-hant/codes/go/chapter_computational_complexity/recursion.go ================================================ // File: recursion.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import "container/list" /* 遞迴 */ func recur(n int) int { // 終止條件 if n == 1 { return 1 } // 遞:遞迴呼叫 res := recur(n - 1) // 迴:返回結果 return n + res } /* 使用迭代模擬遞迴 */ func forLoopRecur(n int) int { // 使用一個顯式的堆疊來模擬系統呼叫堆疊 stack := list.New() res := 0 // 遞:遞迴呼叫 for i := n; i > 0; i-- { // 透過“入堆疊操作”模擬“遞” stack.PushBack(i) } // 迴:返回結果 for stack.Len() != 0 { // 透過“出堆疊操作”模擬“迴” res += stack.Back().Value.(int) stack.Remove(stack.Back()) } // res = 1+2+3+...+n return res } /* 尾遞迴 */ func tailRecur(n int, res int) int { // 終止條件 if n == 0 { return res } // 尾遞迴呼叫 return tailRecur(n-1, res+n) } /* 費波那契數列:遞迴 */ func fib(n int) int { // 終止條件 f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1 } // 遞迴呼叫 f(n) = f(n-1) + f(n-2) res := fib(n-1) + fib(n-2) // 返回結果 f(n) return res } ================================================ FILE: zh-hant/codes/go/chapter_computational_complexity/recursion_test.go ================================================ // File: recursion_test.go // Created Time: 2023-08-28 // Author: Reanon (793584285@qq.com) package chapter_computational_complexity import ( "fmt" "testing" ) /* Driver Code */ func TestRecursion(t *testing.T) { n := 5 res := recur(n) fmt.Println("\n遞迴函式的求和結果 res = ", res) res = forLoopRecur(n) fmt.Println("\n使用迭代模擬遞迴求和結果 res = ", res) res = tailRecur(n, 0) fmt.Println("\n尾遞迴函式的求和結果 res = ", res) res = fib(n) fmt.Println("\n費波那契數列的第", n, "項為", res) } ================================================ FILE: zh-hant/codes/go/chapter_computational_complexity/space_complexity.go ================================================ // File: space_complexity.go // Created Time: 2022-12-15 // Author: cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "fmt" "strconv" . "github.com/krahets/hello-algo/pkg" ) /* 結構體 */ type node struct { val int next *node } /* 建立 node 結構體 */ func newNode(val int) *node { return &node{val: val} } /* 函式 */ func function() int { // 執行某些操作... return 0 } /* 常數階 */ func spaceConstant(n int) { // 常數、變數、物件佔用 O(1) 空間 const a = 0 b := 0 nums := make([]int, 10000) node := newNode(0) // 迴圈中的變數佔用 O(1) 空間 var c int for i := 0; i < n; i++ { c = 0 } // 迴圈中的函式佔用 O(1) 空間 for i := 0; i < n; i++ { function() } b += 0 c += 0 nums[0] = 0 node.val = 0 } /* 線性階 */ func spaceLinear(n int) { // 長度為 n 的陣列佔用 O(n) 空間 _ = make([]int, n) // 長度為 n 的串列佔用 O(n) 空間 var nodes []*node for i := 0; i < n; i++ { nodes = append(nodes, newNode(i)) } // 長度為 n 的雜湊表佔用 O(n) 空間 m := make(map[int]string, n) for i := 0; i < n; i++ { m[i] = strconv.Itoa(i) } } /* 線性階(遞迴實現) */ func spaceLinearRecur(n int) { fmt.Println("遞迴 n =", n) if n == 1 { return } spaceLinearRecur(n - 1) } /* 平方階 */ func spaceQuadratic(n int) { // 矩陣佔用 O(n^2) 空間 numMatrix := make([][]int, n) for i := 0; i < n; i++ { numMatrix[i] = make([]int, n) } } /* 平方階(遞迴實現) */ func spaceQuadraticRecur(n int) int { if n <= 0 { return 0 } nums := make([]int, n) fmt.Printf("遞迴 n = %d 中的 nums 長度 = %d \n", n, len(nums)) return spaceQuadraticRecur(n - 1) } /* 指數階(建立滿二元樹) */ func buildTree(n int) *TreeNode { if n == 0 { return nil } root := NewTreeNode(0) root.Left = buildTree(n - 1) root.Right = buildTree(n - 1) return root } ================================================ FILE: zh-hant/codes/go/chapter_computational_complexity/space_complexity_test.go ================================================ // File: space_complexity_test.go // Created Time: 2022-12-15 // Author: cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "testing" . "github.com/krahets/hello-algo/pkg" ) func TestSpaceComplexity(t *testing.T) { n := 5 // 常數階 spaceConstant(n) // 線性階 spaceLinear(n) spaceLinearRecur(n) // 平方階 spaceQuadratic(n) spaceQuadraticRecur(n) // 指數階 root := buildTree(n) PrintTree(root) } ================================================ FILE: zh-hant/codes/go/chapter_computational_complexity/time_complexity.go ================================================ // File: time_complexity.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_computational_complexity /* 常數階 */ func constant(n int) int { count := 0 size := 100000 for i := 0; i < size; i++ { count++ } return count } /* 線性階 */ func linear(n int) int { count := 0 for i := 0; i < n; i++ { count++ } return count } /* 線性階(走訪陣列) */ func arrayTraversal(nums []int) int { count := 0 // 迴圈次數與陣列長度成正比 for range nums { count++ } return count } /* 平方階 */ func quadratic(n int) int { count := 0 // 迴圈次數與資料大小 n 成平方關係 for i := 0; i < n; i++ { for j := 0; j < n; j++ { count++ } } return count } /* 平方階(泡沫排序) */ func bubbleSort(nums []int) int { count := 0 // 計數器 // 外迴圈:未排序區間為 [0, i] for i := len(nums) - 1; i > 0; i-- { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // 交換 nums[j] 與 nums[j + 1] tmp := nums[j] nums[j] = nums[j+1] nums[j+1] = tmp count += 3 // 元素交換包含 3 個單元操作 } } } return count } /* 指數階(迴圈實現)*/ func exponential(n int) int { count, base := 0, 1 // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) for i := 0; i < n; i++ { for j := 0; j < base; j++ { count++ } base *= 2 } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count } /* 指數階(遞迴實現)*/ func expRecur(n int) int { if n == 1 { return 1 } return expRecur(n-1) + expRecur(n-1) + 1 } /* 對數階(迴圈實現)*/ func logarithmic(n int) int { count := 0 for n > 1 { n = n / 2 count++ } return count } /* 對數階(遞迴實現)*/ func logRecur(n int) int { if n <= 1 { return 0 } return logRecur(n/2) + 1 } /* 線性對數階 */ func linearLogRecur(n int) int { if n <= 1 { return 1 } count := linearLogRecur(n/2) + linearLogRecur(n/2) for i := 0; i < n; i++ { count++ } return count } /* 階乘階(遞迴實現) */ func factorialRecur(n int) int { if n == 0 { return 1 } count := 0 // 從 1 個分裂出 n 個 for i := 0; i < n; i++ { count += factorialRecur(n - 1) } return count } ================================================ FILE: zh-hant/codes/go/chapter_computational_complexity/time_complexity_test.go ================================================ // File: time_complexity_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_computational_complexity import ( "fmt" "testing" ) func TestTimeComplexity(t *testing.T) { n := 8 fmt.Println("輸入資料大小 n =", n) count := constant(n) fmt.Println("常數階的操作數量 =", count) count = linear(n) fmt.Println("線性階的操作數量 =", count) count = arrayTraversal(make([]int, n)) fmt.Println("線性階(走訪陣列)的操作數量 =", count) count = quadratic(n) fmt.Println("平方階的操作數量 =", count) nums := make([]int, n) for i := 0; i < n; i++ { nums[i] = n - i } count = bubbleSort(nums) fmt.Println("平方階(泡沫排序)的操作數量 =", count) count = exponential(n) fmt.Println("指數階(迴圈實現)的操作數量 =", count) count = expRecur(n) fmt.Println("指數階(遞迴實現)的操作數量 =", count) count = logarithmic(n) fmt.Println("對數階(迴圈實現)的操作數量 =", count) count = logRecur(n) fmt.Println("對數階(遞迴實現)的操作數量 =", count) count = linearLogRecur(n) fmt.Println("線性對數階(遞迴實現)的操作數量 =", count) count = factorialRecur(n) fmt.Println("階乘階(遞迴實現)的操作數量 =", count) } ================================================ FILE: zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity.go ================================================ // File: worst_best_time_complexity.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "math/rand" ) /* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ func randomNumbers(n int) []int { nums := make([]int, n) // 生成陣列 nums = { 1, 2, 3, ..., n } for i := 0; i < n; i++ { nums[i] = i + 1 } // 隨機打亂陣列元素 rand.Shuffle(len(nums), func(i, j int) { nums[i], nums[j] = nums[j], nums[i] }) return nums } /* 查詢陣列 nums 中數字 1 所在索引 */ func findOne(nums []int) int { for i := 0; i < len(nums); i++ { // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) if nums[i] == 1 { return i } } return -1 } ================================================ FILE: zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go ================================================ // File: worst_best_time_complexity_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) package chapter_computational_complexity import ( "fmt" "testing" ) func TestWorstBestTimeComplexity(t *testing.T) { for i := 0; i < 10; i++ { n := 100 nums := randomNumbers(n) index := findOne(nums) fmt.Println("\n陣列 [ 1, 2, ..., n ] 被打亂後 =", nums) fmt.Println("數字 1 的索引為", index) } } ================================================ FILE: zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur.go ================================================ // File: binary_search_recur.go // Created Time: 2023-07-19 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer /* 二分搜尋:問題 f(i, j) */ func dfs(nums []int, target, i, j int) int { // 如果區間為空,代表沒有目標元素,則返回 -1 if i > j { return -1 } // 計算索引中點 m := i + ((j - i) >> 1) //判斷中點與目標元素大小 if nums[m] < target { // 小於則遞迴右半陣列 // 遞迴子問題 f(m+1, j) return dfs(nums, target, m+1, j) } else if nums[m] > target { // 大於則遞迴左半陣列 // 遞迴子問題 f(i, m-1) return dfs(nums, target, i, m-1) } else { // 找到目標元素,返回其索引 return m } } /* 二分搜尋 */ func binarySearch(nums []int, target int) int { n := len(nums) return dfs(nums, target, 0, n-1) } ================================================ FILE: zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go ================================================ // File: binary_search_recur_test.go // Created Time: 2023-07-19 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "fmt" "testing" ) func TestBinarySearch(t *testing.T) { nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} target := 6 noTarget := 99 targetIndex := binarySearch(nums, target) fmt.Println("目標元素 6 的索引 = ", targetIndex) noTargetIndex := binarySearch(nums, noTarget) fmt.Println("不存在目標元素的索引 = ", noTargetIndex) } ================================================ FILE: zh-hant/codes/go/chapter_divide_and_conquer/build_tree.go ================================================ // File: build_tree.go // Created Time: 2023-07-20 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import . "github.com/krahets/hello-algo/pkg" /* 構建二元樹:分治 */ func dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode { // 子樹區間為空時終止 if r-l < 0 { return nil } // 初始化根節點 root := NewTreeNode(preorder[i]) // 查詢 m ,從而劃分左右子樹 m := inorderMap[preorder[i]] // 子問題:構建左子樹 root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1) // 子問題:構建右子樹 root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r) // 返回根節點 return root } /* 構建二元樹 */ func buildTree(preorder, inorder []int) *TreeNode { // 初始化雜湊表,儲存 inorder 元素到索引的對映 inorderMap := make(map[int]int, len(inorder)) for i := 0; i < len(inorder); i++ { inorderMap[inorder[i]] = i } root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1) return root } ================================================ FILE: zh-hant/codes/go/chapter_divide_and_conquer/build_tree_test.go ================================================ // File: build_tree_test.go // Created Time: 2023-07-20 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestBuildTree(t *testing.T) { preorder := []int{3, 9, 2, 1, 7} inorder := []int{9, 3, 1, 2, 7} fmt.Print("前序走訪 = ") PrintSlice(preorder) fmt.Print("中序走訪 = ") PrintSlice(inorder) root := buildTree(preorder, inorder) fmt.Println("構建的二元樹為:") PrintTree(root) } ================================================ FILE: zh-hant/codes/go/chapter_divide_and_conquer/hanota.go ================================================ // File: hanota.go // Created Time: 2023-07-21 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import "container/list" /* 移動一個圓盤 */ func move(src, tar *list.List) { // 從 src 頂部拿出一個圓盤 pan := src.Back() // 將圓盤放入 tar 頂部 tar.PushBack(pan.Value) // 移除 src 頂部圓盤 src.Remove(pan) } /* 求解河內塔問題 f(i) */ func dfsHanota(i int, src, buf, tar *list.List) { // 若 src 只剩下一個圓盤,則直接將其移到 tar if i == 1 { move(src, tar) return } // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf dfsHanota(i-1, src, tar, buf) // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar move(src, tar) // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar dfsHanota(i-1, buf, src, tar) } /* 求解河內塔問題 */ func solveHanota(A, B, C *list.List) { n := A.Len() // 將 A 頂部 n 個圓盤藉助 B 移到 C dfsHanota(n, A, B, C) } ================================================ FILE: zh-hant/codes/go/chapter_divide_and_conquer/hanota_test.go ================================================ // File: hanota_test.go // Created Time: 2023-07-21 // Author: hongyun-robot (1836017030@qq.com) package chapter_divide_and_conquer import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHanota(t *testing.T) { // 串列尾部是柱子頂部 A := list.New() for i := 5; i > 0; i-- { A.PushBack(i) } B := list.New() C := list.New() fmt.Println("初始狀態下:") fmt.Print("A = ") PrintList(A) fmt.Print("B = ") PrintList(B) fmt.Print("C = ") PrintList(C) solveHanota(A, B, C) fmt.Println("圓盤移動完成後:") fmt.Print("A = ") PrintList(A) fmt.Print("B = ") PrintList(B) fmt.Print("C = ") PrintList(C) } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go ================================================ // File: climbing_stairs_backtrack.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 回溯 */ func backtrack(choices []int, state, n int, res []int) { // 當爬到第 n 階時,方案數量加 1 if state == n { res[0] = res[0] + 1 } // 走訪所有選擇 for _, choice := range choices { // 剪枝:不允許越過第 n 階 if state+choice > n { continue } // 嘗試:做出選擇,更新狀態 backtrack(choices, state+choice, n, res) // 回退 } } /* 爬樓梯:回溯 */ func climbingStairsBacktrack(n int) int { // 可選擇向上爬 1 階或 2 階 choices := []int{1, 2} // 從第 0 階開始爬 state := 0 res := make([]int, 1) // 使用 res[0] 記錄方案數量 res[0] = 0 backtrack(choices, state, n, res) return res[0] } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go ================================================ // File: climbing_stairs_constraint_dp.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 帶約束爬樓梯:動態規劃 */ func climbingStairsConstraintDP(n int) int { if n == 1 || n == 2 { return 1 } // 初始化 dp 表,用於儲存子問題的解 dp := make([][3]int, n+1) // 初始狀態:預設最小子問題的解 dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // 狀態轉移:從較小子問題逐步求解較大子問題 for i := 3; i <= n; i++ { dp[i][1] = dp[i-1][2] dp[i][2] = dp[i-2][1] + dp[i-2][2] } return dp[n][1] + dp[n][2] } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go ================================================ // File: climbing_stairs_dfs.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 搜尋 */ func dfs(i int) int { // 已知 dp[1] 和 dp[2] ,返回之 if i == 1 || i == 2 { return i } // dp[i] = dp[i-1] + dp[i-2] count := dfs(i-1) + dfs(i-2) return count } /* 爬樓梯:搜尋 */ func climbingStairsDFS(n int) int { return dfs(n) } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go ================================================ // File: climbing_stairs_dfs_mem.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 記憶化搜尋 */ func dfsMem(i int, mem []int) int { // 已知 dp[1] 和 dp[2] ,返回之 if i == 1 || i == 2 { return i } // 若存在記錄 dp[i] ,則直接返回之 if mem[i] != -1 { return mem[i] } // dp[i] = dp[i-1] + dp[i-2] count := dfsMem(i-1, mem) + dfsMem(i-2, mem) // 記錄 dp[i] mem[i] = count return count } /* 爬樓梯:記憶化搜尋 */ func climbingStairsDFSMem(n int) int { // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 mem := make([]int, n+1) for i := range mem { mem[i] = -1 } return dfsMem(n, mem) } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go ================================================ // File: climbing_stairs_dp.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 爬樓梯:動態規劃 */ func climbingStairsDP(n int) int { if n == 1 || n == 2 { return n } // 初始化 dp 表,用於儲存子問題的解 dp := make([]int, n+1) // 初始狀態:預設最小子問題的解 dp[1] = 1 dp[2] = 2 // 狀態轉移:從較小子問題逐步求解較大子問題 for i := 3; i <= n; i++ { dp[i] = dp[i-1] + dp[i-2] } return dp[n] } /* 爬樓梯:空間最佳化後的動態規劃 */ func climbingStairsDPComp(n int) int { if n == 1 || n == 2 { return n } a, b := 1, 2 // 狀態轉移:從較小子問題逐步求解較大子問題 for i := 3; i <= n; i++ { a, b = b, a+b } return b } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_test.go ================================================ // File: climbing_stairs_test.go // Created Time: 2023-07-18 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestClimbingStairsBacktrack(t *testing.T) { n := 9 res := climbingStairsBacktrack(n) fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) } func TestClimbingStairsDFS(t *testing.T) { n := 9 res := climbingStairsDFS(n) fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) } func TestClimbingStairsDFSMem(t *testing.T) { n := 9 res := climbingStairsDFSMem(n) fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) } func TestClimbingStairsDP(t *testing.T) { n := 9 res := climbingStairsDP(n) fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) } func TestClimbingStairsDPComp(t *testing.T) { n := 9 res := climbingStairsDPComp(n) fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) } func TestClimbingStairsConstraintDP(t *testing.T) { n := 9 res := climbingStairsConstraintDP(n) fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) } func TestMinCostClimbingStairsDPComp(t *testing.T) { cost := []int{0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1} fmt.Printf("輸入樓梯的代價串列為 %v\n", cost) res := minCostClimbingStairsDP(cost) fmt.Printf("爬完樓梯的最低代價為 %d\n", res) res = minCostClimbingStairsDPComp(cost) fmt.Printf("爬完樓梯的最低代價為 %d\n", res) } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/coin_change.go ================================================ // File: coin_change.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* 零錢兌換:動態規劃 */ func coinChangeDP(coins []int, amt int) int { n := len(coins) max := amt + 1 // 初始化 dp 表 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, amt+1) } // 狀態轉移:首行首列 for a := 1; a <= amt; a++ { dp[0][a] = max } // 狀態轉移:其餘行和列 for i := 1; i <= n; i++ { for a := 1; a <= amt; a++ { if coins[i-1] > a { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i-1][a] } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1))) } } } if dp[n][amt] != max { return dp[n][amt] } return -1 } /* 零錢兌換:動態規劃 */ func coinChangeDPComp(coins []int, amt int) int { n := len(coins) max := amt + 1 // 初始化 dp 表 dp := make([]int, amt+1) for i := 1; i <= amt; i++ { dp[i] = max } // 狀態轉移 for i := 1; i <= n; i++ { // 正序走訪 for a := 1; a <= amt; a++ { if coins[i-1] > a { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a] } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1))) } } } if dp[amt] != max { return dp[amt] } return -1 } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/coin_change_ii.go ================================================ // File: coin_change_ii.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 零錢兌換 II:動態規劃 */ func coinChangeIIDP(coins []int, amt int) int { n := len(coins) // 初始化 dp 表 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, amt+1) } // 初始化首列 for i := 0; i <= n; i++ { dp[i][0] = 1 } // 狀態轉移:其餘行和列 for i := 1; i <= n; i++ { for a := 1; a <= amt; a++ { if coins[i-1] > a { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i-1][a] } else { // 不選和選硬幣 i 這兩種方案之和 dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]] } } } return dp[n][amt] } /* 零錢兌換 II:空間最佳化後的動態規劃 */ func coinChangeIIDPComp(coins []int, amt int) int { n := len(coins) // 初始化 dp 表 dp := make([]int, amt+1) dp[0] = 1 // 狀態轉移 for i := 1; i <= n; i++ { // 正序走訪 for a := 1; a <= amt; a++ { if coins[i-1] > a { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a] } else { // 不選和選硬幣 i 這兩種方案之和 dp[a] = dp[a] + dp[a-coins[i-1]] } } } return dp[amt] } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/coin_change_test.go ================================================ // File: coin_change_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestCoinChange(t *testing.T) { coins := []int{1, 2, 5} amt := 4 // 動態規劃 res := coinChangeDP(coins, amt) fmt.Printf("湊到目標金額所需的最少硬幣數量為 %d\n", res) // 空間最佳化後的動態規劃 res = coinChangeDPComp(coins, amt) fmt.Printf("湊到目標金額所需的最少硬幣數量為 %d\n", res) } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/edit_distance.go ================================================ // File: edit_distance.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 編輯距離:暴力搜尋 */ func editDistanceDFS(s string, t string, i int, j int) int { // 若 s 和 t 都為空,則返回 0 if i == 0 && j == 0 { return 0 } // 若 s 為空,則返回 t 長度 if i == 0 { return j } // 若 t 為空,則返回 s 長度 if j == 0 { return i } // 若兩字元相等,則直接跳過此兩字元 if s[i-1] == t[j-1] { return editDistanceDFS(s, t, i-1, j-1) } // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 insert := editDistanceDFS(s, t, i, j-1) deleted := editDistanceDFS(s, t, i-1, j) replace := editDistanceDFS(s, t, i-1, j-1) // 返回最少編輯步數 return MinInt(MinInt(insert, deleted), replace) + 1 } /* 編輯距離:記憶化搜尋 */ func editDistanceDFSMem(s string, t string, mem [][]int, i int, j int) int { // 若 s 和 t 都為空,則返回 0 if i == 0 && j == 0 { return 0 } // 若 s 為空,則返回 t 長度 if i == 0 { return j } // 若 t 為空,則返回 s 長度 if j == 0 { return i } // 若已有記錄,則直接返回之 if mem[i][j] != -1 { return mem[i][j] } // 若兩字元相等,則直接跳過此兩字元 if s[i-1] == t[j-1] { return editDistanceDFSMem(s, t, mem, i-1, j-1) } // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 insert := editDistanceDFSMem(s, t, mem, i, j-1) deleted := editDistanceDFSMem(s, t, mem, i-1, j) replace := editDistanceDFSMem(s, t, mem, i-1, j-1) // 記錄並返回最少編輯步數 mem[i][j] = MinInt(MinInt(insert, deleted), replace) + 1 return mem[i][j] } /* 編輯距離:動態規劃 */ func editDistanceDP(s string, t string) int { n := len(s) m := len(t) dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, m+1) } // 狀態轉移:首行首列 for i := 1; i <= n; i++ { dp[i][0] = i } for j := 1; j <= m; j++ { dp[0][j] = j } // 狀態轉移:其餘行和列 for i := 1; i <= n; i++ { for j := 1; j <= m; j++ { if s[i-1] == t[j-1] { // 若兩字元相等,則直接跳過此兩字元 dp[i][j] = dp[i-1][j-1] } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1 } } } return dp[n][m] } /* 編輯距離:空間最佳化後的動態規劃 */ func editDistanceDPComp(s string, t string) int { n := len(s) m := len(t) dp := make([]int, m+1) // 狀態轉移:首行 for j := 1; j <= m; j++ { dp[j] = j } // 狀態轉移:其餘行 for i := 1; i <= n; i++ { // 狀態轉移:首列 leftUp := dp[0] // 暫存 dp[i-1, j-1] dp[0] = i // 狀態轉移:其餘列 for j := 1; j <= m; j++ { temp := dp[j] if s[i-1] == t[j-1] { // 若兩字元相等,則直接跳過此兩字元 dp[j] = leftUp } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1 } leftUp = temp // 更新為下一輪的 dp[i-1, j-1] } } return dp[m] } func MinInt(a, b int) int { if a < b { return a } return b } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/edit_distance_test.go ================================================ // File: edit_distance_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestEditDistanceDFS(test *testing.T) { s := "bag" t := "pack" n := len(s) m := len(t) // 暴力搜尋 res := editDistanceDFS(s, t, n, m) fmt.Printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res) // 記憶化搜尋 mem := make([][]int, n+1) for i := 0; i <= n; i++ { mem[i] = make([]int, m+1) for j := 0; j <= m; j++ { mem[i][j] = -1 } } res = editDistanceDFSMem(s, t, mem, n, m) fmt.Printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res) // 動態規劃 res = editDistanceDP(s, t) fmt.Printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res) // 空間最佳化後的動態規劃 res = editDistanceDPComp(s, t) fmt.Printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res) } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/knapsack.go ================================================ // File: knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* 0-1 背包:暴力搜尋 */ func knapsackDFS(wgt, val []int, i, c int) int { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if i == 0 || c == 0 { return 0 } // 若超過背包容量,則只能選擇不放入背包 if wgt[i-1] > c { return knapsackDFS(wgt, val, i-1, c) } // 計算不放入和放入物品 i 的最大價值 no := knapsackDFS(wgt, val, i-1, c) yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1] // 返回兩種方案中價值更大的那一個 return int(math.Max(float64(no), float64(yes))) } /* 0-1 背包:記憶化搜尋 */ func knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if i == 0 || c == 0 { return 0 } // 若已有記錄,則直接返回 if mem[i][c] != -1 { return mem[i][c] } // 若超過背包容量,則只能選擇不放入背包 if wgt[i-1] > c { return knapsackDFSMem(wgt, val, mem, i-1, c) } // 計算不放入和放入物品 i 的最大價值 no := knapsackDFSMem(wgt, val, mem, i-1, c) yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1] // 返回兩種方案中價值更大的那一個 mem[i][c] = int(math.Max(float64(no), float64(yes))) return mem[i][c] } /* 0-1 背包:動態規劃 */ func knapsackDP(wgt, val []int, cap int) int { n := len(wgt) // 初始化 dp 表 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, cap+1) } // 狀態轉移 for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i-1][c] } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1]))) } } } return dp[n][cap] } /* 0-1 背包:空間最佳化後的動態規劃 */ func knapsackDPComp(wgt, val []int, cap int) int { n := len(wgt) // 初始化 dp 表 dp := make([]int, cap+1) // 狀態轉移 for i := 1; i <= n; i++ { // 倒序走訪 for c := cap; c >= 1; c-- { if wgt[i-1] <= c { // 不選和選物品 i 這兩種方案的較大值 dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) } } } return dp[cap] } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/knapsack_test.go ================================================ // File: knapsack_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestKnapsack(t *testing.T) { wgt := []int{10, 20, 30, 40, 50} val := []int{50, 120, 150, 210, 240} c := 50 n := len(wgt) // 暴力搜尋 res := knapsackDFS(wgt, val, n, c) fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) // 記憶化搜尋 mem := make([][]int, n+1) for i := 0; i <= n; i++ { mem[i] = make([]int, c+1) for j := 0; j <= c; j++ { mem[i][j] = -1 } } res = knapsackDFSMem(wgt, val, mem, n, c) fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) // 動態規劃 res = knapsackDP(wgt, val, c) fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) // 空間最佳化後的動態規劃 res = knapsackDPComp(wgt, val, c) fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) } func TestUnboundedKnapsack(t *testing.T) { wgt := []int{1, 2, 3} val := []int{5, 11, 15} c := 4 // 動態規劃 res := unboundedKnapsackDP(wgt, val, c) fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) // 空間最佳化後的動態規劃 res = unboundedKnapsackDPComp(wgt, val, c) fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go ================================================ // File: min_cost_climbing_stairs_dp.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming /* 爬樓梯最小代價:動態規劃 */ func minCostClimbingStairsDP(cost []int) int { n := len(cost) - 1 if n == 1 || n == 2 { return cost[n] } min := func(a, b int) int { if a < b { return a } return b } // 初始化 dp 表,用於儲存子問題的解 dp := make([]int, n+1) // 初始狀態:預設最小子問題的解 dp[1] = cost[1] dp[2] = cost[2] // 狀態轉移:從較小子問題逐步求解較大子問題 for i := 3; i <= n; i++ { dp[i] = min(dp[i-1], dp[i-2]) + cost[i] } return dp[n] } /* 爬樓梯最小代價:空間最佳化後的動態規劃 */ func minCostClimbingStairsDPComp(cost []int) int { n := len(cost) - 1 if n == 1 || n == 2 { return cost[n] } min := func(a, b int) int { if a < b { return a } return b } // 初始狀態:預設最小子問題的解 a, b := cost[1], cost[2] // 狀態轉移:從較小子問題逐步求解較大子問題 for i := 3; i <= n; i++ { tmp := b b = min(a, tmp) + cost[i] a = tmp } return b } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/min_path_sum.go ================================================ // File: min_path_sum.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* 最小路徑和:暴力搜尋 */ func minPathSumDFS(grid [][]int, i, j int) int { // 若為左上角單元格,則終止搜尋 if i == 0 && j == 0 { return grid[0][0] } // 若行列索引越界,則返回 +∞ 代價 if i < 0 || j < 0 { return math.MaxInt } // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 up := minPathSumDFS(grid, i-1, j) left := minPathSumDFS(grid, i, j-1) // 返回從左上角到 (i, j) 的最小路徑代價 return int(math.Min(float64(left), float64(up))) + grid[i][j] } /* 最小路徑和:記憶化搜尋 */ func minPathSumDFSMem(grid, mem [][]int, i, j int) int { // 若為左上角單元格,則終止搜尋 if i == 0 && j == 0 { return grid[0][0] } // 若行列索引越界,則返回 +∞ 代價 if i < 0 || j < 0 { return math.MaxInt } // 若已有記錄,則直接返回 if mem[i][j] != -1 { return mem[i][j] } // 左邊和上邊單元格的最小路徑代價 up := minPathSumDFSMem(grid, mem, i-1, j) left := minPathSumDFSMem(grid, mem, i, j-1) // 記錄並返回左上角到 (i, j) 的最小路徑代價 mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j] return mem[i][j] } /* 最小路徑和:動態規劃 */ func minPathSumDP(grid [][]int) int { n, m := len(grid), len(grid[0]) // 初始化 dp 表 dp := make([][]int, n) for i := 0; i < n; i++ { dp[i] = make([]int, m) } dp[0][0] = grid[0][0] // 狀態轉移:首行 for j := 1; j < m; j++ { dp[0][j] = dp[0][j-1] + grid[0][j] } // 狀態轉移:首列 for i := 1; i < n; i++ { dp[i][0] = dp[i-1][0] + grid[i][0] } // 狀態轉移:其餘行和列 for i := 1; i < n; i++ { for j := 1; j < m; j++ { dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j] } } return dp[n-1][m-1] } /* 最小路徑和:空間最佳化後的動態規劃 */ func minPathSumDPComp(grid [][]int) int { n, m := len(grid), len(grid[0]) // 初始化 dp 表 dp := make([]int, m) // 狀態轉移:首行 dp[0] = grid[0][0] for j := 1; j < m; j++ { dp[j] = dp[j-1] + grid[0][j] } // 狀態轉移:其餘行和列 for i := 1; i < n; i++ { // 狀態轉移:首列 dp[0] = dp[0] + grid[i][0] // 狀態轉移:其餘列 for j := 1; j < m; j++ { dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j] } } return dp[m-1] } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/min_path_sum_test.go ================================================ // File: min_path_sum_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import ( "fmt" "testing" ) func TestMinPathSum(t *testing.T) { grid := [][]int{ {1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}, } n, m := len(grid), len(grid[0]) // 暴力搜尋 res := minPathSumDFS(grid, n-1, m-1) fmt.Printf("從左上角到右下角的最小路徑和為 %d\n", res) // 記憶化搜尋 mem := make([][]int, n) for i := 0; i < n; i++ { mem[i] = make([]int, m) for j := 0; j < m; j++ { mem[i][j] = -1 } } res = minPathSumDFSMem(grid, mem, n-1, m-1) fmt.Printf("從左上角到右下角的最小路徑和為 %d\n", res) // 動態規劃 res = minPathSumDP(grid) fmt.Printf("從左上角到右下角的最小路徑和為 %d\n", res) // 空間最佳化後的動態規劃 res = minPathSumDPComp(grid) fmt.Printf("從左上角到右下角的最小路徑和為 %d\n", res) } ================================================ FILE: zh-hant/codes/go/chapter_dynamic_programming/unbounded_knapsack.go ================================================ // File: unbounded_knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_dynamic_programming import "math" /* 完全背包:動態規劃 */ func unboundedKnapsackDP(wgt, val []int, cap int) int { n := len(wgt) // 初始化 dp 表 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, cap+1) } // 狀態轉移 for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i-1][c] } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1]))) } } } return dp[n][cap] } /* 完全背包:空間最佳化後的動態規劃 */ func unboundedKnapsackDPComp(wgt, val []int, cap int) int { n := len(wgt) // 初始化 dp 表 dp := make([]int, cap+1) // 狀態轉移 for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // 若超過背包容量,則不選物品 i dp[c] = dp[c] } else { // 不選和選物品 i 這兩種方案的較大值 dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) } } } return dp[cap] } ================================================ FILE: zh-hant/codes/go/chapter_graph/graph_adjacency_list.go ================================================ // File: graph_adjacency_list.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "strconv" "strings" . "github.com/krahets/hello-algo/pkg" ) /* 基於鄰接表實現的無向圖類別 */ type graphAdjList struct { // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 adjList map[Vertex][]Vertex } /* 建構子 */ func newGraphAdjList(edges [][]Vertex) *graphAdjList { g := &graphAdjList{ adjList: make(map[Vertex][]Vertex), } // 新增所有頂點和邊 for _, edge := range edges { g.addVertex(edge[0]) g.addVertex(edge[1]) g.addEdge(edge[0], edge[1]) } return g } /* 獲取頂點數量 */ func (g *graphAdjList) size() int { return len(g.adjList) } /* 新增邊 */ func (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) { _, ok1 := g.adjList[vet1] _, ok2 := g.adjList[vet2] if !ok1 || !ok2 || vet1 == vet2 { panic("error") } // 新增邊 vet1 - vet2, 新增匿名 struct{}, g.adjList[vet1] = append(g.adjList[vet1], vet2) g.adjList[vet2] = append(g.adjList[vet2], vet1) } /* 刪除邊 */ func (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) { _, ok1 := g.adjList[vet1] _, ok2 := g.adjList[vet2] if !ok1 || !ok2 || vet1 == vet2 { panic("error") } // 刪除邊 vet1 - vet2 g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2) g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1) } /* 新增頂點 */ func (g *graphAdjList) addVertex(vet Vertex) { _, ok := g.adjList[vet] if ok { return } // 在鄰接表中新增一個新鏈結串列 g.adjList[vet] = make([]Vertex, 0) } /* 刪除頂點 */ func (g *graphAdjList) removeVertex(vet Vertex) { _, ok := g.adjList[vet] if !ok { panic("error") } // 在鄰接表中刪除頂點 vet 對應的鏈結串列 delete(g.adjList, vet) // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 for v, list := range g.adjList { g.adjList[v] = DeleteSliceElms(list, vet) } } /* 列印鄰接表 */ func (g *graphAdjList) print() { var builder strings.Builder fmt.Printf("鄰接表 = \n") for k, v := range g.adjList { builder.WriteString("\t\t" + strconv.Itoa(k.Val) + ": ") for _, vet := range v { builder.WriteString(strconv.Itoa(vet.Val) + " ") } fmt.Println(builder.String()) builder.Reset() } } ================================================ FILE: zh-hant/codes/go/chapter_graph/graph_adjacency_list_test.go ================================================ // File: graph_adjacency_list_test.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphAdjList(t *testing.T) { /* 初始化無向圖 */ v := ValsToVets([]int{1, 3, 2, 5, 4}) edges := [][]Vertex{{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}} graph := newGraphAdjList(edges) fmt.Println("初始化後,圖為:") graph.print() /* 新增邊 */ // 頂點 1, 2 即 v[0], v[2] graph.addEdge(v[0], v[2]) fmt.Println("\n新增邊 1-2 後,圖為") graph.print() /* 刪除邊 */ // 頂點 1, 3 即 v[0], v[1] graph.removeEdge(v[0], v[1]) fmt.Println("\n刪除邊 1-3 後,圖為") graph.print() /* 新增頂點 */ v5 := NewVertex(6) graph.addVertex(v5) fmt.Println("\n新增頂點 6 後,圖為") graph.print() /* 刪除頂點 */ // 頂點 3 即 v[1] graph.removeVertex(v[1]) fmt.Println("\n刪除頂點 3 後,圖為") graph.print() } ================================================ FILE: zh-hant/codes/go/chapter_graph/graph_adjacency_matrix.go ================================================ // File: graph_adjacency_matrix.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import "fmt" /* 基於鄰接矩陣實現的無向圖類別 */ type graphAdjMat struct { // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” vertices []int // 鄰接矩陣,行列索引對應“頂點索引” adjMat [][]int } /* 建構子 */ func newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat { // 新增頂點 n := len(vertices) adjMat := make([][]int, n) for i := range adjMat { adjMat[i] = make([]int, n) } // 初始化圖 g := &graphAdjMat{ vertices: vertices, adjMat: adjMat, } // 新增邊 // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 for i := range edges { g.addEdge(edges[i][0], edges[i][1]) } return g } /* 獲取頂點數量 */ func (g *graphAdjMat) size() int { return len(g.vertices) } /* 新增頂點 */ func (g *graphAdjMat) addVertex(val int) { n := g.size() // 向頂點串列中新增新頂點的值 g.vertices = append(g.vertices, val) // 在鄰接矩陣中新增一行 newRow := make([]int, n) g.adjMat = append(g.adjMat, newRow) // 在鄰接矩陣中新增一列 for i := range g.adjMat { g.adjMat[i] = append(g.adjMat[i], 0) } } /* 刪除頂點 */ func (g *graphAdjMat) removeVertex(index int) { if index >= g.size() { return } // 在頂點串列中移除索引 index 的頂點 g.vertices = append(g.vertices[:index], g.vertices[index+1:]...) // 在鄰接矩陣中刪除索引 index 的行 g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...) // 在鄰接矩陣中刪除索引 index 的列 for i := range g.adjMat { g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...) } } /* 新增邊 */ // 參數 i, j 對應 vertices 元素索引 func (g *graphAdjMat) addEdge(i, j int) { // 索引越界與相等處理 if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { fmt.Errorf("%s", "Index Out Of Bounds Exception") } // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) g.adjMat[i][j] = 1 g.adjMat[j][i] = 1 } /* 刪除邊 */ // 參數 i, j 對應 vertices 元素索引 func (g *graphAdjMat) removeEdge(i, j int) { // 索引越界與相等處理 if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { fmt.Errorf("%s", "Index Out Of Bounds Exception") } g.adjMat[i][j] = 0 g.adjMat[j][i] = 0 } /* 列印鄰接矩陣 */ func (g *graphAdjMat) print() { fmt.Printf("\t頂點串列 = %v\n", g.vertices) fmt.Printf("\t鄰接矩陣 = \n") for i := range g.adjMat { fmt.Printf("\t\t\t%v\n", g.adjMat[i]) } } ================================================ FILE: zh-hant/codes/go/chapter_graph/graph_adjacency_matrix_test.go ================================================ // File: graph_adjacency_matrix_test.go // Created Time: 2023-01-31 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" ) func TestGraphAdjMat(t *testing.T) { /* 初始化無向圖 */ // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 vertices := []int{1, 3, 2, 5, 4} edges := [][]int{{0, 1}, {1, 2}, {2, 3}, {0, 3}, {2, 4}, {3, 4}} graph := newGraphAdjMat(vertices, edges) fmt.Println("初始化後,圖為:") graph.print() /* 新增邊 */ // 頂點 1, 2 的索引分別為 0, 2 graph.addEdge(0, 2) fmt.Println("新增邊 1-2 後,圖為") graph.print() /* 刪除邊 */ // 頂點 1, 3 的索引分別為 0, 1 graph.removeEdge(0, 1) fmt.Println("刪除邊 1-3 後,圖為") graph.print() /* 新增頂點 */ graph.addVertex(6) fmt.Println("新增頂點 6 後,圖為") graph.print() /* 刪除頂點 */ // 頂點 3 的索引為 1 graph.removeVertex(1) fmt.Println("刪除頂點 3 後,圖為") graph.print() } ================================================ FILE: zh-hant/codes/go/chapter_graph/graph_bfs.go ================================================ // File: graph_bfs.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( . "github.com/krahets/hello-algo/pkg" ) /* 廣度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 func graphBFS(g *graphAdjList, startVet Vertex) []Vertex { // 頂點走訪序列 res := make([]Vertex, 0) // 雜湊集合,用於記錄已被訪問過的頂點 visited := make(map[Vertex]struct{}) visited[startVet] = struct{}{} // 佇列用於實現 BFS, 使用切片模擬佇列 queue := make([]Vertex, 0) queue = append(queue, startVet) // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 for len(queue) > 0 { // 佇列首頂點出隊 vet := queue[0] queue = queue[1:] // 記錄訪問頂點 res = append(res, vet) // 走訪該頂點的所有鄰接頂點 for _, adjVet := range g.adjList[vet] { _, isExist := visited[adjVet] // 只入列未訪問的頂點 if !isExist { queue = append(queue, adjVet) visited[adjVet] = struct{}{} } } } // 返回頂點走訪序列 return res } ================================================ FILE: zh-hant/codes/go/chapter_graph/graph_bfs_test.go ================================================ // File: graph_bfs_test.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphBFS(t *testing.T) { /* 初始化無向圖 */ vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) edges := [][]Vertex{ {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[1], vets[4]}, {vets[2], vets[5]}, {vets[3], vets[4]}, {vets[3], vets[6]}, {vets[4], vets[5]}, {vets[4], vets[7]}, {vets[5], vets[8]}, {vets[6], vets[7]}, {vets[7], vets[8]}} graph := newGraphAdjList(edges) fmt.Println("初始化後,圖為:") graph.print() /* 廣度優先走訪 */ res := graphBFS(graph, vets[0]) fmt.Println("廣度優先走訪(BFS)頂點序列為:") PrintSlice(VetsToVals(res)) } ================================================ FILE: zh-hant/codes/go/chapter_graph/graph_dfs.go ================================================ // File: graph_dfs.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( . "github.com/krahets/hello-algo/pkg" ) /* 深度優先走訪輔助函式 */ func dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) { // append 操作會返回新的的引用,必須讓原引用重新賦值為新slice的引用 *res = append(*res, vet) visited[vet] = struct{}{} // 走訪該頂點的所有鄰接頂點 for _, adjVet := range g.adjList[vet] { _, isExist := visited[adjVet] // 遞迴訪問鄰接頂點 if !isExist { dfs(g, visited, res, adjVet) } } } /* 深度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 func graphDFS(g *graphAdjList, startVet Vertex) []Vertex { // 頂點走訪序列 res := make([]Vertex, 0) // 雜湊集合,用於記錄已被訪問過的頂點 visited := make(map[Vertex]struct{}) dfs(g, visited, &res, startVet) // 返回頂點走訪序列 return res } ================================================ FILE: zh-hant/codes/go/chapter_graph/graph_dfs_test.go ================================================ // File: graph_dfs_test.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package chapter_graph import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestGraphDFS(t *testing.T) { /* 初始化無向圖 */ vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6}) edges := [][]Vertex{ {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[2], vets[5]}, {vets[4], vets[5]}, {vets[5], vets[6]}} graph := newGraphAdjList(edges) fmt.Println("初始化後,圖為:") graph.print() /* 深度優先走訪 */ res := graphDFS(graph, vets[0]) fmt.Println("深度優先走訪(DFS)頂點序列為:") PrintSlice(VetsToVals(res)) } ================================================ FILE: zh-hant/codes/go/chapter_greedy/coin_change_greedy.go ================================================ // File: coin_change_greedy.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy /* 零錢兌換:貪婪 */ func coinChangeGreedy(coins []int, amt int) int { // 假設 coins 串列有序 i := len(coins) - 1 count := 0 // 迴圈進行貪婪選擇,直到無剩餘金額 for amt > 0 { // 找到小於且最接近剩餘金額的硬幣 for i > 0 && coins[i] > amt { i-- } // 選擇 coins[i] amt -= coins[i] count++ } // 若未找到可行方案,則返回 -1 if amt != 0 { return -1 } return count } ================================================ FILE: zh-hant/codes/go/chapter_greedy/coin_change_greedy_test.go ================================================ // File: coin_change_greedy_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestCoinChangeGreedy(t *testing.T) { // 貪婪:能夠保證找到全域性最優解 coins := []int{1, 5, 10, 20, 50, 100} amt := 186 res := coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res) // 貪婪:無法保證找到全域性最優解 coins = []int{1, 20, 50} amt = 60 res = coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res) fmt.Println("實際上需要的最少數量為 3 ,即 20 + 20 + 20") // 貪婪:無法保證找到全域性最優解 coins = []int{1, 49, 50} amt = 98 res = coinChangeGreedy(coins, amt) fmt.Printf("coins = %v, amt = %d\n", coins, amt) fmt.Printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res) fmt.Println("實際上需要的最少數量為 2 ,即 49 + 49") } ================================================ FILE: zh-hant/codes/go/chapter_greedy/fractional_knapsack.go ================================================ // File: fractional_knapsack.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "sort" /* 物品 */ type Item struct { w int // 物品重量 v int // 物品價值 } /* 分數背包:貪婪 */ func fractionalKnapsack(wgt []int, val []int, cap int) float64 { // 建立物品串列,包含兩個屬性:重量、價值 items := make([]Item, len(wgt)) for i := 0; i < len(wgt); i++ { items[i] = Item{wgt[i], val[i]} } // 按照單位價值 item.v / item.w 從高到低進行排序 sort.Slice(items, func(i, j int) bool { return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w) }) // 迴圈貪婪選擇 res := 0.0 for _, item := range items { if item.w <= cap { // 若剩餘容量充足,則將當前物品整個裝進背包 res += float64(item.v) cap -= item.w } else { // 若剩餘容量不足,則將當前物品的一部分裝進背包 res += float64(item.v) / float64(item.w) * float64(cap) // 已無剩餘容量,因此跳出迴圈 break } } return res } ================================================ FILE: zh-hant/codes/go/chapter_greedy/fractional_knapsack_test.go ================================================ // File: fractional_knapsack_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestFractionalKnapsack(t *testing.T) { wgt := []int{10, 20, 30, 40, 50} val := []int{50, 120, 150, 210, 240} capacity := 50 // 貪婪演算法 res := fractionalKnapsack(wgt, val, capacity) fmt.Println("不超過背包容量的最大物品價值為", res) } ================================================ FILE: zh-hant/codes/go/chapter_greedy/max_capacity.go ================================================ // File: max_capacity.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "math" /* 最大容量:貪婪 */ func maxCapacity(ht []int) int { // 初始化 i, j,使其分列陣列兩端 i, j := 0, len(ht)-1 // 初始最大容量為 0 res := 0 // 迴圈貪婪選擇,直至兩板相遇 for i < j { // 更新最大容量 capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i) res = int(math.Max(float64(res), float64(capacity))) // 向內移動短板 if ht[i] < ht[j] { i++ } else { j-- } } return res } ================================================ FILE: zh-hant/codes/go/chapter_greedy/max_capacity_test.go ================================================ // File: max_capacity_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestMaxCapacity(t *testing.T) { ht := []int{3, 8, 5, 2, 7, 7, 3, 4} // 貪婪演算法 res := maxCapacity(ht) fmt.Println("最大容量為", res) } ================================================ FILE: zh-hant/codes/go/chapter_greedy/max_product_cutting.go ================================================ // File: max_product_cutting.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import "math" /* 最大切分乘積:貪婪 */ func maxProductCutting(n int) int { // 當 n <= 3 時,必須切分出一個 1 if n <= 3 { return 1 * (n - 1) } // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 a := n / 3 b := n % 3 if b == 1 { // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 return int(math.Pow(3, float64(a-1))) * 2 * 2 } if b == 2 { // 當餘數為 2 時,不做處理 return int(math.Pow(3, float64(a))) * 2 } // 當餘數為 0 時,不做處理 return int(math.Pow(3, float64(a))) } ================================================ FILE: zh-hant/codes/go/chapter_greedy/max_product_cutting_test.go ================================================ // File: max_product_cutting_test.go // Created Time: 2023-07-23 // Author: Reanon (793584285@qq.com) package chapter_greedy import ( "fmt" "testing" ) func TestMaxProductCutting(t *testing.T) { n := 58 // 貪婪演算法 res := maxProductCutting(n) fmt.Println("最大切分乘積為", res) } ================================================ FILE: zh-hant/codes/go/chapter_hashing/array_hash_map.go ================================================ // File: array_hash_map.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import "fmt" /* 鍵值對 */ type pair struct { key int val string } /* 基於陣列實現的雜湊表 */ type arrayHashMap struct { buckets []*pair } /* 初始化雜湊表 */ func newArrayHashMap() *arrayHashMap { // 初始化陣列,包含 100 個桶 buckets := make([]*pair, 100) return &arrayHashMap{buckets: buckets} } /* 雜湊函式 */ func (a *arrayHashMap) hashFunc(key int) int { index := key % 100 return index } /* 查詢操作 */ func (a *arrayHashMap) get(key int) string { index := a.hashFunc(key) pair := a.buckets[index] if pair == nil { return "Not Found" } return pair.val } /* 新增操作 */ func (a *arrayHashMap) put(key int, val string) { pair := &pair{key: key, val: val} index := a.hashFunc(key) a.buckets[index] = pair } /* 刪除操作 */ func (a *arrayHashMap) remove(key int) { index := a.hashFunc(key) // 置為 nil ,代表刪除 a.buckets[index] = nil } /* 獲取所有鍵對 */ func (a *arrayHashMap) pairSet() []*pair { var pairs []*pair for _, pair := range a.buckets { if pair != nil { pairs = append(pairs, pair) } } return pairs } /* 獲取所有鍵 */ func (a *arrayHashMap) keySet() []int { var keys []int for _, pair := range a.buckets { if pair != nil { keys = append(keys, pair.key) } } return keys } /* 獲取所有值 */ func (a *arrayHashMap) valueSet() []string { var values []string for _, pair := range a.buckets { if pair != nil { values = append(values, pair.val) } } return values } /* 列印雜湊表 */ func (a *arrayHashMap) print() { for _, pair := range a.buckets { if pair != nil { fmt.Println(pair.key, "->", pair.val) } } } ================================================ FILE: zh-hant/codes/go/chapter_hashing/array_hash_map_test.go ================================================ // File: array_hash_map_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "testing" ) func TestArrayHashMap(t *testing.T) { /* 初始化雜湊表 */ hmap := newArrayHashMap() /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) hmap.put(12836, "小哈") hmap.put(15937, "小囉") hmap.put(16750, "小算") hmap.put(13276, "小法") hmap.put(10583, "小鴨") fmt.Println("\n新增完成後,雜湊表為\nKey -> Value") hmap.print() /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value name := hmap.get(15937) fmt.Println("\n輸入學號 15937 ,查詢到姓名 " + name) /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) hmap.remove(10583) fmt.Println("\n刪除 10583 後,雜湊表為\nKey -> Value") hmap.print() /* 走訪雜湊表 */ fmt.Println("\n走訪鍵值對 Key->Value") for _, kv := range hmap.pairSet() { fmt.Println(kv.key, " -> ", kv.val) } fmt.Println("\n單獨走訪鍵 Key") for _, key := range hmap.keySet() { fmt.Println(key) } fmt.Println("\n單獨走訪值 Value") for _, val := range hmap.valueSet() { fmt.Println(val) } } ================================================ FILE: zh-hant/codes/go/chapter_hashing/hash_collision_test.go ================================================ // File: hash_collision_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "testing" ) func TestHashMapChaining(t *testing.T) { /* 初始化雜湊表 */ hmap := newHashMapChaining() /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) hmap.put(12836, "小哈") hmap.put(15937, "小囉") hmap.put(16750, "小算") hmap.put(13276, "小法") hmap.put(10583, "小鴨") fmt.Println("\n新增完成後,雜湊表為\nKey -> Value") hmap.print() /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value name := hmap.get(15937) fmt.Println("\n輸入學號 15937 ,查詢到姓名", name) /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) hmap.remove(12836) fmt.Println("\n刪除 12836 後,雜湊表為\nKey -> Value") hmap.print() } func TestHashMapOpenAddressing(t *testing.T) { /* 初始化雜湊表 */ hmap := newHashMapOpenAddressing() /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) hmap.put(12836, "小哈") hmap.put(15937, "小囉") hmap.put(16750, "小算") hmap.put(13276, "小法") hmap.put(10583, "小鴨") fmt.Println("\n新增完成後,雜湊表為\nKey -> Value") hmap.print() /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value name := hmap.get(13276) fmt.Println("\n輸入學號 13276 ,查詢到姓名 ", name) /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) hmap.remove(16750) fmt.Println("\n刪除 16750 後,雜湊表為\nKey -> Value") hmap.print() } ================================================ FILE: zh-hant/codes/go/chapter_hashing/hash_map_chaining.go ================================================ // File: hash_map_chaining.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import ( "fmt" "strconv" "strings" ) /* 鏈式位址雜湊表 */ type hashMapChaining struct { size int // 鍵值對數量 capacity int // 雜湊表容量 loadThres float64 // 觸發擴容的負載因子閾值 extendRatio int // 擴容倍數 buckets [][]pair // 桶陣列 } /* 建構子 */ func newHashMapChaining() *hashMapChaining { buckets := make([][]pair, 4) for i := 0; i < 4; i++ { buckets[i] = make([]pair, 0) } return &hashMapChaining{ size: 0, capacity: 4, loadThres: 2.0 / 3.0, extendRatio: 2, buckets: buckets, } } /* 雜湊函式 */ func (m *hashMapChaining) hashFunc(key int) int { return key % m.capacity } /* 負載因子 */ func (m *hashMapChaining) loadFactor() float64 { return float64(m.size) / float64(m.capacity) } /* 查詢操作 */ func (m *hashMapChaining) get(key int) string { idx := m.hashFunc(key) bucket := m.buckets[idx] // 走訪桶,若找到 key ,則返回對應 val for _, p := range bucket { if p.key == key { return p.val } } // 若未找到 key ,則返回空字串 return "" } /* 新增操作 */ func (m *hashMapChaining) put(key int, val string) { // 當負載因子超過閾值時,執行擴容 if m.loadFactor() > m.loadThres { m.extend() } idx := m.hashFunc(key) // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for i := range m.buckets[idx] { if m.buckets[idx][i].key == key { m.buckets[idx][i].val = val return } } // 若無該 key ,則將鍵值對新增至尾部 p := pair{ key: key, val: val, } m.buckets[idx] = append(m.buckets[idx], p) m.size += 1 } /* 刪除操作 */ func (m *hashMapChaining) remove(key int) { idx := m.hashFunc(key) // 走訪桶,從中刪除鍵值對 for i, p := range m.buckets[idx] { if p.key == key { // 切片刪除 m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...) m.size -= 1 break } } } /* 擴容雜湊表 */ func (m *hashMapChaining) extend() { // 暫存原雜湊表 tmpBuckets := make([][]pair, len(m.buckets)) for i := 0; i < len(m.buckets); i++ { tmpBuckets[i] = make([]pair, len(m.buckets[i])) copy(tmpBuckets[i], m.buckets[i]) } // 初始化擴容後的新雜湊表 m.capacity *= m.extendRatio m.buckets = make([][]pair, m.capacity) for i := 0; i < m.capacity; i++ { m.buckets[i] = make([]pair, 0) } m.size = 0 // 將鍵值對從原雜湊表搬運至新雜湊表 for _, bucket := range tmpBuckets { for _, p := range bucket { m.put(p.key, p.val) } } } /* 列印雜湊表 */ func (m *hashMapChaining) print() { var builder strings.Builder for _, bucket := range m.buckets { builder.WriteString("[") for _, p := range bucket { builder.WriteString(strconv.Itoa(p.key) + " -> " + p.val + " ") } builder.WriteString("]") fmt.Println(builder.String()) builder.Reset() } } ================================================ FILE: zh-hant/codes/go/chapter_hashing/hash_map_open_addressing.go ================================================ // File: hash_map_open_addressing.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import ( "fmt" ) /* 開放定址雜湊表 */ type hashMapOpenAddressing struct { size int // 鍵值對數量 capacity int // 雜湊表容量 loadThres float64 // 觸發擴容的負載因子閾值 extendRatio int // 擴容倍數 buckets []*pair // 桶陣列 TOMBSTONE *pair // 刪除標記 } /* 建構子 */ func newHashMapOpenAddressing() *hashMapOpenAddressing { return &hashMapOpenAddressing{ size: 0, capacity: 4, loadThres: 2.0 / 3.0, extendRatio: 2, buckets: make([]*pair, 4), TOMBSTONE: &pair{-1, "-1"}, } } /* 雜湊函式 */ func (h *hashMapOpenAddressing) hashFunc(key int) int { return key % h.capacity // 根據鍵計算雜湊值 } /* 負載因子 */ func (h *hashMapOpenAddressing) loadFactor() float64 { return float64(h.size) / float64(h.capacity) // 計算當前負載因子 } /* 搜尋 key 對應的桶索引 */ func (h *hashMapOpenAddressing) findBucket(key int) int { index := h.hashFunc(key) // 獲取初始索引 firstTombstone := -1 // 記錄遇到的第一個TOMBSTONE的位置 for h.buckets[index] != nil { if h.buckets[index].key == key { if firstTombstone != -1 { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 h.buckets[firstTombstone] = h.buckets[index] h.buckets[index] = h.TOMBSTONE return firstTombstone // 返回移動後的桶索引 } return index // 返回找到的索引 } if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE { firstTombstone = index // 記錄遇到的首個刪除標記的位置 } index = (index + 1) % h.capacity // 線性探查,越過尾部則返回頭部 } // 若 key 不存在,則返回新增點的索引 if firstTombstone != -1 { return firstTombstone } return index } /* 查詢操作 */ func (h *hashMapOpenAddressing) get(key int) string { index := h.findBucket(key) // 搜尋 key 對應的桶索引 if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { return h.buckets[index].val // 若找到鍵值對,則返回對應 val } return "" // 若鍵值對不存在,則返回 "" } /* 新增操作 */ func (h *hashMapOpenAddressing) put(key int, val string) { if h.loadFactor() > h.loadThres { h.extend() // 當負載因子超過閾值時,執行擴容 } index := h.findBucket(key) // 搜尋 key 對應的桶索引 if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE { h.buckets[index] = &pair{key, val} // 若鍵值對不存在,則新增該鍵值對 h.size++ } else { h.buckets[index].val = val // 若找到鍵值對,則覆蓋 val } } /* 刪除操作 */ func (h *hashMapOpenAddressing) remove(key int) { index := h.findBucket(key) // 搜尋 key 對應的桶索引 if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { h.buckets[index] = h.TOMBSTONE // 若找到鍵值對,則用刪除標記覆蓋它 h.size-- } } /* 擴容雜湊表 */ func (h *hashMapOpenAddressing) extend() { oldBuckets := h.buckets // 暫存原雜湊表 h.capacity *= h.extendRatio // 更新容量 h.buckets = make([]*pair, h.capacity) // 初始化擴容後的新雜湊表 h.size = 0 // 重置大小 // 將鍵值對從原雜湊表搬運至新雜湊表 for _, pair := range oldBuckets { if pair != nil && pair != h.TOMBSTONE { h.put(pair.key, pair.val) } } } /* 列印雜湊表 */ func (h *hashMapOpenAddressing) print() { for _, pair := range h.buckets { if pair == nil { fmt.Println("nil") } else if pair == h.TOMBSTONE { fmt.Println("TOMBSTONE") } else { fmt.Printf("%d -> %s\n", pair.key, pair.val) } } } ================================================ FILE: zh-hant/codes/go/chapter_hashing/hash_map_test.go ================================================ // File: hash_map_test.go // Created Time: 2022-12-14 // Author: msk397 (machangxinq@gmail.com) package chapter_hashing import ( "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHashMap(t *testing.T) { /* 初始化雜湊表 */ hmap := make(map[int]string) /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) hmap[12836] = "小哈" hmap[15937] = "小囉" hmap[16750] = "小算" hmap[13276] = "小法" hmap[10583] = "小鴨" fmt.Println("\n新增完成後,雜湊表為\nKey -> Value") PrintMap(hmap) /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value name := hmap[15937] fmt.Println("\n輸入學號 15937 ,查詢到姓名 ", name) /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) delete(hmap, 10583) fmt.Println("\n刪除 10583 後,雜湊表為\nKey -> Value") PrintMap(hmap) /* 走訪雜湊表 */ // 走訪鍵值對 key->value fmt.Println("\n走訪鍵值對 Key->Value") for key, value := range hmap { fmt.Println(key, "->", value) } // 單獨走訪鍵 key fmt.Println("\n單獨走訪鍵 Key") for key := range hmap { fmt.Println(key) } // 單獨走訪值 value fmt.Println("\n單獨走訪值 Value") for _, value := range hmap { fmt.Println(value) } } func TestSimpleHash(t *testing.T) { var hash int key := "Hello 演算法" hash = addHash(key) fmt.Println("加法雜湊值為 " + strconv.Itoa(hash)) hash = mulHash(key) fmt.Println("乘法雜湊值為 " + strconv.Itoa(hash)) hash = xorHash(key) fmt.Println("互斥或雜湊值為 " + strconv.Itoa(hash)) hash = rotHash(key) fmt.Println("旋轉雜湊值為 " + strconv.Itoa(hash)) } ================================================ FILE: zh-hant/codes/go/chapter_hashing/simple_hash.go ================================================ // File: simple_hash.go // Created Time: 2023-06-23 // Author: Reanon (793584285@qq.com) package chapter_hashing import "fmt" /* 加法雜湊 */ func addHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = (hash + int64(b)) % modulus } return int(hash) } /* 乘法雜湊 */ func mulHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = (31*hash + int64(b)) % modulus } return int(hash) } /* 互斥或雜湊 */ func xorHash(key string) int { hash := 0 modulus := 1000000007 for _, b := range []byte(key) { fmt.Println(int(b)) hash ^= int(b) hash = (31*hash + int(b)) % modulus } return hash & modulus } /* 旋轉雜湊 */ func rotHash(key string) int { var hash int64 var modulus int64 modulus = 1000000007 for _, b := range []byte(key) { hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus } return int(hash) } ================================================ FILE: zh-hant/codes/go/chapter_heap/heap.go ================================================ // File: heap.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap // Go 語言中可以透過實現 heap.Interface 來構建整數大頂堆積 // 實現 heap.Interface 需要同時實現 sort.Interface type intHeap []any // Push heap.Interface 的函式,實現推入元素到堆積 func (h *intHeap) Push(x any) { // Push 和 Pop 使用 pointer receiver 作為參數 // 因為它們不僅會對切片的內容進行調整,還會修改切片的長度。 *h = append(*h, x.(int)) } // Pop heap.Interface 的函式,實現彈出堆積頂元素 func (h *intHeap) Pop() any { // 待出堆積元素存放在最後 last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Len sort.Interface 的函式 func (h *intHeap) Len() int { return len(*h) } // Less sort.Interface 的函式 func (h *intHeap) Less(i, j int) bool { // 如果實現小頂堆積,則需要調整為小於號 return (*h)[i].(int) > (*h)[j].(int) } // Swap sort.Interface 的函式 func (h *intHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Top 獲取堆積頂元素 func (h *intHeap) Top() any { return (*h)[0] } ================================================ FILE: zh-hant/codes/go/chapter_heap/heap_test.go ================================================ // File: heap_test.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap import ( "container/heap" "fmt" "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) func testPush(h *intHeap, val int) { // 呼叫 heap.Interface 的函式,來新增元素 heap.Push(h, val) fmt.Printf("\n元素 %d 入堆積後 \n", val) PrintHeap(*h) } func testPop(h *intHeap) { // 呼叫 heap.Interface 的函式,來移除元素 val := heap.Pop(h) fmt.Printf("\n堆積頂元素 %d 出堆積後 \n", val) PrintHeap(*h) } func TestHeap(t *testing.T) { /* 初始化堆積 */ // 初始化大頂堆積 maxHeap := &intHeap{} heap.Init(maxHeap) /* 元素入堆積 */ testPush(maxHeap, 1) testPush(maxHeap, 3) testPush(maxHeap, 2) testPush(maxHeap, 5) testPush(maxHeap, 4) /* 獲取堆積頂元素 */ top := maxHeap.Top() fmt.Printf("堆積頂元素為 %d\n", top) /* 堆積頂元素出堆積 */ testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) /* 獲取堆積大小 */ size := len(*maxHeap) fmt.Printf("堆積元素數量為 %d\n", size) /* 判斷堆積是否為空 */ isEmpty := len(*maxHeap) == 0 fmt.Printf("堆積是否為空 %t\n", isEmpty) } func TestMyHeap(t *testing.T) { /* 初始化堆積 */ // 初始化大頂堆積 maxHeap := newMaxHeap([]any{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}) fmt.Printf("輸入陣列並建堆積後\n") maxHeap.print() /* 獲取堆積頂元素 */ peek := maxHeap.peek() fmt.Printf("\n堆積頂元素為 %d\n", peek) /* 元素入堆積 */ val := 7 maxHeap.push(val) fmt.Printf("\n元素 %d 入堆積後\n", val) maxHeap.print() /* 堆積頂元素出堆積 */ peek = maxHeap.pop() fmt.Printf("\n堆積頂元素 %d 出堆積後\n", peek) maxHeap.print() /* 獲取堆積大小 */ size := maxHeap.size() fmt.Printf("\n堆積元素數量為 %d\n", size) /* 判斷堆積是否為空 */ isEmpty := maxHeap.isEmpty() fmt.Printf("\n堆積是否為空 %t\n", isEmpty) } func TestTopKHeap(t *testing.T) { /* 初始化堆積 */ // 初始化大頂堆積 nums := []int{1, 7, 6, 3, 2} k := 3 res := topKHeap(nums, k) fmt.Printf("最大的 " + strconv.Itoa(k) + " 個元素為") PrintHeap(*res) } ================================================ FILE: zh-hant/codes/go/chapter_heap/my_heap.go ================================================ // File: my_heap.go // Created Time: 2023-01-12 // Author: Reanon (793584285@qq.com) package chapter_heap import ( "fmt" . "github.com/krahets/hello-algo/pkg" ) type maxHeap struct { // 使用切片而非陣列,這樣無須考慮擴容問題 data []any } /* 建構子,建立空堆積 */ func newHeap() *maxHeap { return &maxHeap{ data: make([]any, 0), } } /* 建構子,根據切片建堆積 */ func newMaxHeap(nums []any) *maxHeap { // 將串列元素原封不動新增進堆積 h := &maxHeap{data: nums} for i := h.parent(len(h.data) - 1); i >= 0; i-- { // 堆積化除葉節點以外的其他所有節點 h.siftDown(i) } return h } /* 獲取左子節點的索引 */ func (h *maxHeap) left(i int) int { return 2*i + 1 } /* 獲取右子節點的索引 */ func (h *maxHeap) right(i int) int { return 2*i + 2 } /* 獲取父節點的索引 */ func (h *maxHeap) parent(i int) int { // 向下整除 return (i - 1) / 2 } /* 交換元素 */ func (h *maxHeap) swap(i, j int) { h.data[i], h.data[j] = h.data[j], h.data[i] } /* 獲取堆積大小 */ func (h *maxHeap) size() int { return len(h.data) } /* 判斷堆積是否為空 */ func (h *maxHeap) isEmpty() bool { return len(h.data) == 0 } /* 訪問堆積頂元素 */ func (h *maxHeap) peek() any { return h.data[0] } /* 元素入堆積 */ func (h *maxHeap) push(val any) { // 新增節點 h.data = append(h.data, val) // 從底至頂堆積化 h.siftUp(len(h.data) - 1) } /* 從節點 i 開始,從底至頂堆積化 */ func (h *maxHeap) siftUp(i int) { for true { // 獲取節點 i 的父節點 p := h.parent(i) // 當“越過根節點”或“節點無須修復”時,結束堆積化 if p < 0 || h.data[i].(int) <= h.data[p].(int) { break } // 交換兩節點 h.swap(i, p) // 迴圈向上堆積化 i = p } } /* 元素出堆積 */ func (h *maxHeap) pop() any { // 判空處理 if h.isEmpty() { fmt.Println("error") return nil } // 交換根節點與最右葉節點(交換首元素與尾元素) h.swap(0, h.size()-1) // 刪除節點 val := h.data[len(h.data)-1] h.data = h.data[:len(h.data)-1] // 從頂至底堆積化 h.siftDown(0) // 返回堆積頂元素 return val } /* 從節點 i 開始,從頂至底堆積化 */ func (h *maxHeap) siftDown(i int) { for true { // 判斷節點 i, l, r 中值最大的節點,記為 max l, r, max := h.left(i), h.right(i), i if l < h.size() && h.data[l].(int) > h.data[max].(int) { max = l } if r < h.size() && h.data[r].(int) > h.data[max].(int) { max = r } // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if max == i { break } // 交換兩節點 h.swap(i, max) // 迴圈向下堆積化 i = max } } /* 列印堆積(二元樹) */ func (h *maxHeap) print() { PrintHeap(h.data) } ================================================ FILE: zh-hant/codes/go/chapter_heap/top_k.go ================================================ // File: top_k.go // Created Time: 2023-06-24 // Author: Reanon (793584285@qq.com) package chapter_heap import "container/heap" type minHeap []any func (h *minHeap) Len() int { return len(*h) } func (h *minHeap) Less(i, j int) bool { return (*h)[i].(int) < (*h)[j].(int) } func (h *minHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Push heap.Interface 的方法,實現推入元素到堆積 func (h *minHeap) Push(x any) { *h = append(*h, x.(int)) } // Pop heap.Interface 的方法,實現彈出堆積頂元素 func (h *minHeap) Pop() any { // 待出堆積元素存放在最後 last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Top 獲取堆積頂元素 func (h *minHeap) Top() any { return (*h)[0] } /* 基於堆積查詢陣列中最大的 k 個元素 */ func topKHeap(nums []int, k int) *minHeap { // 初始化小頂堆積 h := &minHeap{} heap.Init(h) // 將陣列的前 k 個元素入堆積 for i := 0; i < k; i++ { heap.Push(h, nums[i]) } // 從第 k+1 個元素開始,保持堆積的長度為 k for i := k; i < len(nums); i++ { // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 if nums[i] > h.Top().(int) { heap.Pop(h) heap.Push(h, nums[i]) } } return h } ================================================ FILE: zh-hant/codes/go/chapter_searching/binary_search.go ================================================ // File: binary_search.go // Created Time: 2022-12-05 // Author: Slone123c (274325721@qq.com) package chapter_searching /* 二分搜尋(雙閉區間) */ func binarySearch(nums []int, target int) int { // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 i, j := 0, len(nums)-1 // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) for i <= j { m := i + (j-i)/2 // 計算中點索引 m if nums[m] < target { // 此情況說明 target 在區間 [m+1, j] 中 i = m + 1 } else if nums[m] > target { // 此情況說明 target 在區間 [i, m-1] 中 j = m - 1 } else { // 找到目標元素,返回其索引 return m } } // 未找到目標元素,返回 -1 return -1 } /* 二分搜尋(左閉右開區間) */ func binarySearchLCRO(nums []int, target int) int { // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 i, j := 0, len(nums) // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) for i < j { m := i + (j-i)/2 // 計算中點索引 m if nums[m] < target { // 此情況說明 target 在區間 [m+1, j) 中 i = m + 1 } else if nums[m] > target { // 此情況說明 target 在區間 [i, m) 中 j = m } else { // 找到目標元素,返回其索引 return m } } // 未找到目標元素,返回 -1 return -1 } ================================================ FILE: zh-hant/codes/go/chapter_searching/binary_search_edge.go ================================================ // File: binary_search_edge.go // Created Time: 2023-08-23 // Author: Reanon (793584285@qq.com) package chapter_searching /* 二分搜尋最左一個 target */ func binarySearchLeftEdge(nums []int, target int) int { // 等價於查詢 target 的插入點 i := binarySearchInsertion(nums, target) // 未找到 target ,返回 -1 if i == len(nums) || nums[i] != target { return -1 } // 找到 target ,返回索引 i return i } /* 二分搜尋最右一個 target */ func binarySearchRightEdge(nums []int, target int) int { // 轉化為查詢最左一個 target + 1 i := binarySearchInsertion(nums, target+1) // j 指向最右一個 target ,i 指向首個大於 target 的元素 j := i - 1 // 未找到 target ,返回 -1 if j == -1 || nums[j] != target { return -1 } // 找到 target ,返回索引 j return j } ================================================ FILE: zh-hant/codes/go/chapter_searching/binary_search_insertion.go ================================================ // File: binary_search_insertion.go // Created Time: 2023-08-23 // Author: Reanon (793584285@qq.com) package chapter_searching /* 二分搜尋插入點(無重複元素) */ func binarySearchInsertionSimple(nums []int, target int) int { // 初始化雙閉區間 [0, n-1] i, j := 0, len(nums)-1 for i <= j { // 計算中點索引 m m := i + (j-i)/2 if nums[m] < target { // target 在區間 [m+1, j] 中 i = m + 1 } else if nums[m] > target { // target 在區間 [i, m-1] 中 j = m - 1 } else { // 找到 target ,返回插入點 m return m } } // 未找到 target ,返回插入點 i return i } /* 二分搜尋插入點(存在重複元素) */ func binarySearchInsertion(nums []int, target int) int { // 初始化雙閉區間 [0, n-1] i, j := 0, len(nums)-1 for i <= j { // 計算中點索引 m m := i + (j-i)/2 if nums[m] < target { // target 在區間 [m+1, j] 中 i = m + 1 } else if nums[m] > target { // target 在區間 [i, m-1] 中 j = m - 1 } else { // 首個小於 target 的元素在區間 [i, m-1] 中 j = m - 1 } } // 返回插入點 i return i } ================================================ FILE: zh-hant/codes/go/chapter_searching/binary_search_test.go ================================================ // File: binary_search_test.go // Created Time: 2022-12-05 // Author: Slone123c (274325721@qq.com) package chapter_searching import ( "fmt" "testing" ) func TestBinarySearch(t *testing.T) { var ( target = 6 nums = []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} expected = 2 ) // 在陣列中執行二分搜尋 actual := binarySearch(nums, target) fmt.Println("目標元素 6 的索引 =", actual) if actual != expected { t.Errorf("目標元素 6 的索引 = %d, 應該為 %d", actual, expected) } } func TestBinarySearchEdge(t *testing.T) { // 包含重複元素的陣列 nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} fmt.Println("\n陣列 nums = ", nums) // 二分搜尋左邊界和右邊界 for _, target := range []int{6, 7} { index := binarySearchLeftEdge(nums, target) fmt.Println("最左一個元素", target, "的索引為", index) index = binarySearchRightEdge(nums, target) fmt.Println("最右一個元素", target, "的索引為", index) } } func TestBinarySearchInsertion(t *testing.T) { // 無重複元素的陣列 nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} fmt.Println("陣列 nums =", nums) // 二分搜尋插入點 for _, target := range []int{6, 9} { index := binarySearchInsertionSimple(nums, target) fmt.Println("元素", target, "的插入點的索引為", index) } // 包含重複元素的陣列 nums = []int{1, 3, 6, 6, 6, 6, 6, 10, 12, 15} fmt.Println("\n陣列 nums =", nums) // 二分搜尋插入點 for _, target := range []int{2, 6, 20} { index := binarySearchInsertion(nums, target) fmt.Println("元素", target, "的插入點的索引為", index) } } ================================================ FILE: zh-hant/codes/go/chapter_searching/hashing_search.go ================================================ // File: hashing_search.go // Created Time: 2022-12-12 // Author: Slone123c (274325721@qq.com) package chapter_searching import . "github.com/krahets/hello-algo/pkg" /* 雜湊查詢(陣列) */ func hashingSearchArray(m map[int]int, target int) int { // 雜湊表的 key: 目標元素,value: 索引 // 若雜湊表中無此 key ,返回 -1 if index, ok := m[target]; ok { return index } else { return -1 } } /* 雜湊查詢(鏈結串列) */ func hashingSearchLinkedList(m map[int]*ListNode, target int) *ListNode { // 雜湊表的 key: 目標節點值,value: 節點物件 // 若雜湊表中無此 key ,返回 nil if node, ok := m[target]; ok { return node } else { return nil } } ================================================ FILE: zh-hant/codes/go/chapter_searching/hashing_search_test.go ================================================ // File: hashing_search_test.go // Created Time: 2022-12-12 // Author: Slone123c (274325721@qq.com) package chapter_searching import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestHashingSearch(t *testing.T) { target := 3 /* 雜湊查詢(陣列) */ nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} // 初始化雜湊表 m := make(map[int]int) for i := 0; i < len(nums); i++ { m[nums[i]] = i } index := hashingSearchArray(m, target) fmt.Println("目標元素 3 的索引 = ", index) /* 雜湊查詢(鏈結串列) */ head := ArrayToLinkedList(nums) // 初始化雜湊表 m1 := make(map[int]*ListNode) for head != nil { m1[head.Val] = head head = head.Next } node := hashingSearchLinkedList(m1, target) fmt.Println("目標節點值 3 的對應節點物件為 ", node) } ================================================ FILE: zh-hant/codes/go/chapter_searching/linear_search.go ================================================ // File: linear_search.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_searching import ( . "github.com/krahets/hello-algo/pkg" ) /* 線性查詢(陣列) */ func linearSearchArray(nums []int, target int) int { // 走訪陣列 for i := 0; i < len(nums); i++ { // 找到目標元素,返回其索引 if nums[i] == target { return i } } // 未找到目標元素,返回 -1 return -1 } /* 線性查詢(鏈結串列) */ func linearSearchLinkedList(node *ListNode, target int) *ListNode { // 走訪鏈結串列 for node != nil { // 找到目標節點,返回之 if node.Val == target { return node } node = node.Next } // 未找到目標元素,返回 nil return nil } ================================================ FILE: zh-hant/codes/go/chapter_searching/linear_search_test.go ================================================ // File: linear_search_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_searching import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLinearSearch(t *testing.T) { target := 3 nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} // 在陣列中執行線性查詢 index := linearSearchArray(nums, target) fmt.Println("目標元素 3 的索引 =", index) // 在鏈結串列中執行線性查詢 head := ArrayToLinkedList(nums) node := linearSearchLinkedList(head, target) fmt.Println("目標節點值 3 的對應節點物件為", node) } ================================================ FILE: zh-hant/codes/go/chapter_searching/two_sum.go ================================================ // File: two_sum.go // Created Time: 2022-11-25 // Author: reanon (793584285@qq.com) package chapter_searching /* 方法一:暴力列舉 */ func twoSumBruteForce(nums []int, target int) []int { size := len(nums) // 兩層迴圈,時間複雜度為 O(n^2) for i := 0; i < size-1; i++ { for j := i + 1; j < size; j++ { if nums[i]+nums[j] == target { return []int{i, j} } } } return nil } /* 方法二:輔助雜湊表 */ func twoSumHashTable(nums []int, target int) []int { // 輔助雜湊表,空間複雜度為 O(n) hashTable := map[int]int{} // 單層迴圈,時間複雜度為 O(n) for idx, val := range nums { if preIdx, ok := hashTable[target-val]; ok { return []int{preIdx, idx} } hashTable[val] = idx } return nil } ================================================ FILE: zh-hant/codes/go/chapter_searching/two_sum_test.go ================================================ // File: two_sum_test.go // Created Time: 2022-11-25 // Author: reanon (793584285@qq.com) package chapter_searching import ( "fmt" "testing" ) func TestTwoSum(t *testing.T) { // ======= Test Case ======= nums := []int{2, 7, 11, 15} target := 13 // ====== Driver Code ====== // 方法一:暴力解法 res := twoSumBruteForce(nums, target) fmt.Println("方法一 res =", res) // 方法二:雜湊表 res = twoSumHashTable(nums, target) fmt.Println("方法二 res =", res) } ================================================ FILE: zh-hant/codes/go/chapter_sorting/bubble_sort.go ================================================ // File: bubble_sort.go // Created Time: 2022-12-06 // Author: Slone123c (274325721@qq.com) package chapter_sorting /* 泡沫排序 */ func bubbleSort(nums []int) { // 外迴圈:未排序區間為 [0, i] for i := len(nums) - 1; i > 0; i-- { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // 交換 nums[j] 與 nums[j + 1] nums[j], nums[j+1] = nums[j+1], nums[j] } } } } /* 泡沫排序(標誌最佳化)*/ func bubbleSortWithFlag(nums []int) { // 外迴圈:未排序區間為 [0, i] for i := len(nums) - 1; i > 0; i-- { flag := false // 初始化標誌位 // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // 交換 nums[j] 與 nums[j + 1] nums[j], nums[j+1] = nums[j+1], nums[j] flag = true // 記錄交換元素 } } if flag == false { // 此輪“冒泡”未交換任何元素,直接跳出 break } } } ================================================ FILE: zh-hant/codes/go/chapter_sorting/bubble_sort_test.go ================================================ // File: bubble_sort_test.go // Created Time: 2022-12-06 // Author: Slone123c (274325721@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestBubbleSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} bubbleSort(nums) fmt.Println("泡沫排序完成後 nums = ", nums) nums1 := []int{4, 1, 3, 1, 5, 2} bubbleSortWithFlag(nums1) fmt.Println("泡沫排序完成後 nums1 = ", nums1) } ================================================ FILE: zh-hant/codes/go/chapter_sorting/bucket_sort.go ================================================ // File: bucket_sort.go // Created Time: 2023-03-27 // Author: Reanon (793584285@qq.com) package chapter_sorting import "sort" /* 桶排序 */ func bucketSort(nums []float64) { // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 k := len(nums) / 2 buckets := make([][]float64, k) for i := 0; i < k; i++ { buckets[i] = make([]float64, 0) } // 1. 將陣列元素分配到各個桶中 for _, num := range nums { // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] i := int(num * float64(k)) // 將 num 新增進桶 i buckets[i] = append(buckets[i], num) } // 2. 對各個桶執行排序 for i := 0; i < k; i++ { // 使用內建切片排序函式,也可以替換成其他排序演算法 sort.Float64s(buckets[i]) } // 3. 走訪桶合併結果 i := 0 for _, bucket := range buckets { for _, num := range bucket { nums[i] = num i++ } } } ================================================ FILE: zh-hant/codes/go/chapter_sorting/bucket_sort_test.go ================================================ // File: bucket_sort_test.go // Created Time: 2023-03-27 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestBucketSort(t *testing.T) { // 設輸入資料為浮點數,範圍為 [0, 1) nums := []float64{0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37} bucketSort(nums) fmt.Println("桶排序完成後 nums = ", nums) } ================================================ FILE: zh-hant/codes/go/chapter_sorting/counting_sort.go ================================================ // File: counting_sort.go // Created Time: 2023-03-20 // Author: Reanon (793584285@qq.com) package chapter_sorting type CountingSort struct{} /* 計數排序 */ // 簡單實現,無法用於排序物件 func countingSortNaive(nums []int) { // 1. 統計陣列最大元素 m m := 0 for _, num := range nums { if num > m { m = num } } // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 counter := make([]int, m+1) for _, num := range nums { counter[num]++ } // 3. 走訪 counter ,將各元素填入原陣列 nums for i, num := 0, 0; num < m+1; num++ { for j := 0; j < counter[num]; j++ { nums[i] = num i++ } } } /* 計數排序 */ // 完整實現,可排序物件,並且是穩定排序 func countingSort(nums []int) { // 1. 統計陣列最大元素 m m := 0 for _, num := range nums { if num > m { m = num } } // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 counter := make([]int, m+1) for _, num := range nums { counter[num]++ } // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 for i := 0; i < m; i++ { counter[i+1] += counter[i] } // 4. 倒序走訪 nums ,將各元素填入結果陣列 res // 初始化陣列 res 用於記錄結果 n := len(nums) res := make([]int, n) for i := n - 1; i >= 0; i-- { num := nums[i] // 將 num 放置到對應索引處 res[counter[num]-1] = num // 令前綴和自減 1 ,得到下次放置 num 的索引 counter[num]-- } // 使用結果陣列 res 覆蓋原陣列 nums copy(nums, res) } ================================================ FILE: zh-hant/codes/go/chapter_sorting/counting_sort_test.go ================================================ // File: counting_sort_test.go // Created Time: 2023-03-20 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestCountingSort(t *testing.T) { nums := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} countingSortNaive(nums) fmt.Println("計數排序(無法排序物件)完成後 nums = ", nums) nums1 := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} countingSort(nums1) fmt.Println("計數排序完成後 nums1 = ", nums1) } ================================================ FILE: zh-hant/codes/go/chapter_sorting/heap_sort.go ================================================ // File: heap_sort.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting /* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ func siftDown(nums *[]int, n, i int) { for true { // 判斷節點 i, l, r 中值最大的節點,記為 ma l := 2*i + 1 r := 2*i + 2 ma := i if l < n && (*nums)[l] > (*nums)[ma] { ma = l } if r < n && (*nums)[r] > (*nums)[ma] { ma = r } // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if ma == i { break } // 交換兩節點 (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i] // 迴圈向下堆積化 i = ma } } /* 堆積排序 */ func heapSort(nums *[]int) { // 建堆積操作:堆積化除葉節點以外的其他所有節點 for i := len(*nums)/2 - 1; i >= 0; i-- { siftDown(nums, len(*nums), i) } // 從堆積中提取最大元素,迴圈 n-1 輪 for i := len(*nums) - 1; i > 0; i-- { // 交換根節點與最右葉節點(交換首元素與尾元素) (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0] // 以根節點為起點,從頂至底進行堆積化 siftDown(nums, i, 0) } } ================================================ FILE: zh-hant/codes/go/chapter_sorting/heap_sort_test.go ================================================ // File: heap_sort_test.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestHeapSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} heapSort(&nums) fmt.Println("堆積排序完成後 nums = ", nums) } ================================================ FILE: zh-hant/codes/go/chapter_sorting/insertion_sort.go ================================================ // File: insertion_sort.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting /* 插入排序 */ func insertionSort(nums []int) { // 外迴圈:已排序區間為 [0, i-1] for i := 1; i < len(nums); i++ { base := nums[i] j := i - 1 // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 for j >= 0 && nums[j] > base { nums[j+1] = nums[j] // 將 nums[j] 向右移動一位 j-- } nums[j+1] = base // 將 base 賦值到正確位置 } } ================================================ FILE: zh-hant/codes/go/chapter_sorting/insertion_sort_test.go ================================================ // File: insertion_sort_test.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) func TestInsertionSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} insertionSort(nums) fmt.Println("插入排序完成後 nums =", nums) } ================================================ FILE: zh-hant/codes/go/chapter_sorting/merge_sort.go ================================================ // File: merge_sort.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting /* 合併左子陣列和右子陣列 */ func merge(nums []int, left, mid, right int) { // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] // 建立一個臨時陣列 tmp ,用於存放合併後的結果 tmp := make([]int, right-left+1) // 初始化左子陣列和右子陣列的起始索引 i, j, k := left, mid+1, 0 // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 for i <= mid && j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i] i++ } else { tmp[k] = nums[j] j++ } k++ } // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 for i <= mid { tmp[k] = nums[i] i++ k++ } for j <= right { tmp[k] = nums[j] j++ k++ } // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 for k := 0; k < len(tmp); k++ { nums[left+k] = tmp[k] } } /* 合併排序 */ func mergeSort(nums []int, left, right int) { // 終止條件 if left >= right { return } // 劃分階段 mid := left + (right - left) / 2 mergeSort(nums, left, mid) mergeSort(nums, mid+1, right) // 合併階段 merge(nums, left, mid, right) } ================================================ FILE: zh-hant/codes/go/chapter_sorting/merge_sort_test.go ================================================ // File: merge_sort_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) func TestMergeSort(t *testing.T) { nums := []int{7, 3, 2, 6, 0, 1, 5, 4} mergeSort(nums, 0, len(nums)-1) fmt.Println("合併排序完成後 nums = ", nums) } ================================================ FILE: zh-hant/codes/go/chapter_sorting/quick_sort.go ================================================ // File: quick_sort.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting // 快速排序 type quickSort struct{} // 快速排序(中位基準數最佳化) type quickSortMedian struct{} // 快速排序(遞迴深度最佳化) type quickSortTailCall struct{} /* 哨兵劃分 */ func (q *quickSort) partition(nums []int, left, right int) int { // 以 nums[left] 為基準數 i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- // 從右向左找首個小於基準數的元素 } for i < j && nums[i] <= nums[left] { i++ // 從左向右找首個大於基準數的元素 } // 元素交換 nums[i], nums[j] = nums[j], nums[i] } // 將基準數交換至兩子陣列的分界線 nums[i], nums[left] = nums[left], nums[i] return i // 返回基準數的索引 } /* 快速排序 */ func (q *quickSort) quickSort(nums []int, left, right int) { // 子陣列長度為 1 時終止遞迴 if left >= right { return } // 哨兵劃分 pivot := q.partition(nums, left, right) // 遞迴左子陣列、右子陣列 q.quickSort(nums, left, pivot-1) q.quickSort(nums, pivot+1, right) } /* 選取三個候選元素的中位數 */ func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int { l, m, r := nums[left], nums[mid], nums[right] if (l <= m && m <= r) || (r <= m && m <= l) { return mid // m 在 l 和 r 之間 } if (m <= l && l <= r) || (r <= l && l <= m) { return left // l 在 m 和 r 之間 } return right } /* 哨兵劃分(三數取中值)*/ func (q *quickSortMedian) partition(nums []int, left, right int) int { // 以 nums[left] 為基準數 med := q.medianThree(nums, left, (left+right)/2, right) // 將中位數交換至陣列最左端 nums[left], nums[med] = nums[med], nums[left] // 以 nums[left] 為基準數 i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- //從右向左找首個小於基準數的元素 } for i < j && nums[i] <= nums[left] { i++ //從左向右找首個大於基準數的元素 } //元素交換 nums[i], nums[j] = nums[j], nums[i] } //將基準數交換至兩子陣列的分界線 nums[i], nums[left] = nums[left], nums[i] return i //返回基準數的索引 } /* 快速排序 */ func (q *quickSortMedian) quickSort(nums []int, left, right int) { // 子陣列長度為 1 時終止遞迴 if left >= right { return } // 哨兵劃分 pivot := q.partition(nums, left, right) // 遞迴左子陣列、右子陣列 q.quickSort(nums, left, pivot-1) q.quickSort(nums, pivot+1, right) } /* 哨兵劃分 */ func (q *quickSortTailCall) partition(nums []int, left, right int) int { // 以 nums[left] 為基準數 i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { j-- // 從右向左找首個小於基準數的元素 } for i < j && nums[i] <= nums[left] { i++ // 從左向右找首個大於基準數的元素 } // 元素交換 nums[i], nums[j] = nums[j], nums[i] } // 將基準數交換至兩子陣列的分界線 nums[i], nums[left] = nums[left], nums[i] return i // 返回基準數的索引 } /* 快速排序(遞迴深度最佳化)*/ func (q *quickSortTailCall) quickSort(nums []int, left, right int) { // 子陣列長度為 1 時終止 for left < right { // 哨兵劃分操作 pivot := q.partition(nums, left, right) // 對兩個子陣列中較短的那個執行快速排序 if pivot-left < right-pivot { q.quickSort(nums, left, pivot-1) // 遞迴排序左子陣列 left = pivot + 1 // 剩餘未排序區間為 [pivot + 1, right] } else { q.quickSort(nums, pivot+1, right) // 遞迴排序右子陣列 right = pivot - 1 // 剩餘未排序區間為 [left, pivot - 1] } } } ================================================ FILE: zh-hant/codes/go/chapter_sorting/quick_sort_test.go ================================================ // File: quick_sort_test.go // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) package chapter_sorting import ( "fmt" "testing" ) // 快速排序 func TestQuickSort(t *testing.T) { q := quickSort{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("快速排序完成後 nums = ", nums) } // 快速排序(中位基準數最佳化) func TestQuickSortMedian(t *testing.T) { q := quickSortMedian{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("快速排序(中位基準數最佳化)完成後 nums = ", nums) } // 快速排序(遞迴深度最佳化) func TestQuickSortTailCall(t *testing.T) { q := quickSortTailCall{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("快速排序(遞迴深度最佳化)完成後 nums = ", nums) } ================================================ FILE: zh-hant/codes/go/chapter_sorting/radix_sort.go ================================================ // File: radix_sort.go // Created Time: 2023-01-18 // Author: Reanon (793584285@qq.com) package chapter_sorting import "math" /* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ func digit(num, exp int) int { // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 return (num / exp) % 10 } /* 計數排序(根據 nums 第 k 位排序) */ func countingSortDigit(nums []int, exp int) { // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 counter := make([]int, 10) n := len(nums) // 統計 0~9 各數字的出現次數 for i := 0; i < n; i++ { d := digit(nums[i], exp) // 獲取 nums[i] 第 k 位,記為 d counter[d]++ // 統計數字 d 的出現次數 } // 求前綴和,將“出現個數”轉換為“陣列索引” for i := 1; i < 10; i++ { counter[i] += counter[i-1] } // 倒序走訪,根據桶內統計結果,將各元素填入 res res := make([]int, n) for i := n - 1; i >= 0; i-- { d := digit(nums[i], exp) j := counter[d] - 1 // 獲取 d 在陣列中的索引 j res[j] = nums[i] // 將當前元素填入索引 j counter[d]-- // 將 d 的數量減 1 } // 使用結果覆蓋原陣列 nums for i := 0; i < n; i++ { nums[i] = res[i] } } /* 基數排序 */ func radixSort(nums []int) { // 獲取陣列的最大元素,用於判斷最大位數 max := math.MinInt for _, num := range nums { if num > max { max = num } } // 按照從低位到高位的順序走訪 for exp := 1; max >= exp; exp *= 10 { // 對陣列元素的第 k 位執行計數排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums, exp) } } ================================================ FILE: zh-hant/codes/go/chapter_sorting/radix_sort_test.go ================================================ // File: radix_sort_test.go // Created Time: 2023-01-18 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestRadixSort(t *testing.T) { /* 基數排序 */ nums := []int{10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996} radixSort(nums) fmt.Println("基數排序完成後 nums = ", nums) } ================================================ FILE: zh-hant/codes/go/chapter_sorting/selection_sort.go ================================================ // File: selection_sort.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting /* 選擇排序 */ func selectionSort(nums []int) { n := len(nums) // 外迴圈:未排序區間為 [i, n-1] for i := 0; i < n-1; i++ { // 內迴圈:找到未排序區間內的最小元素 k := i for j := i + 1; j < n; j++ { if nums[j] < nums[k] { // 記錄最小元素的索引 k = j } } // 將該最小元素與未排序區間的首個元素交換 nums[i], nums[k] = nums[k], nums[i] } } ================================================ FILE: zh-hant/codes/go/chapter_sorting/selection_sort_test.go ================================================ // File: selection_sort_test.go // Created Time: 2023-05-29 // Author: Reanon (793584285@qq.com) package chapter_sorting import ( "fmt" "testing" ) func TestSelectionSort(t *testing.T) { nums := []int{4, 1, 3, 1, 5, 2} selectionSort(nums) fmt.Println("選擇排序完成後 nums = ", nums) } ================================================ FILE: zh-hant/codes/go/chapter_stack_and_queue/array_deque.go ================================================ // File: array_deque.go // Created Time: 2023-03-13 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import "fmt" /* 基於環形陣列實現的雙向佇列 */ type arrayDeque struct { nums []int // 用於儲存雙向佇列元素的陣列 front int // 佇列首指標,指向佇列首元素 queSize int // 雙向佇列長度 queCapacity int // 佇列容量(即最大容納元素數量) } /* 初始化佇列 */ func newArrayDeque(queCapacity int) *arrayDeque { return &arrayDeque{ nums: make([]int, queCapacity), queCapacity: queCapacity, front: 0, queSize: 0, } } /* 獲取雙向佇列的長度 */ func (q *arrayDeque) size() int { return q.queSize } /* 判斷雙向佇列是否為空 */ func (q *arrayDeque) isEmpty() bool { return q.queSize == 0 } /* 計算環形陣列索引 */ func (q *arrayDeque) index(i int) int { // 透過取餘操作實現陣列首尾相連 // 當 i 越過陣列尾部後,回到頭部 // 當 i 越過陣列頭部後,回到尾部 return (i + q.queCapacity) % q.queCapacity } /* 佇列首入列 */ func (q *arrayDeque) pushFirst(num int) { if q.queSize == q.queCapacity { fmt.Println("雙向佇列已滿") return } // 佇列首指標向左移動一位 // 透過取餘操作實現 front 越過陣列頭部後回到尾部 q.front = q.index(q.front - 1) // 將 num 新增至佇列首 q.nums[q.front] = num q.queSize++ } /* 佇列尾入列 */ func (q *arrayDeque) pushLast(num int) { if q.queSize == q.queCapacity { fmt.Println("雙向佇列已滿") return } // 計算佇列尾指標,指向佇列尾索引 + 1 rear := q.index(q.front + q.queSize) // 將 num 新增至佇列尾 q.nums[rear] = num q.queSize++ } /* 佇列首出列 */ func (q *arrayDeque) popFirst() any { num := q.peekFirst() if num == nil { return nil } // 佇列首指標向後移動一位 q.front = q.index(q.front + 1) q.queSize-- return num } /* 佇列尾出列 */ func (q *arrayDeque) popLast() any { num := q.peekLast() if num == nil { return nil } q.queSize-- return num } /* 訪問佇列首元素 */ func (q *arrayDeque) peekFirst() any { if q.isEmpty() { return nil } return q.nums[q.front] } /* 訪問佇列尾元素 */ func (q *arrayDeque) peekLast() any { if q.isEmpty() { return nil } // 計算尾元素索引 last := q.index(q.front + q.queSize - 1) return q.nums[last] } /* 獲取 Slice 用於列印 */ func (q *arrayDeque) toSlice() []int { // 僅轉換有效長度範圍內的串列元素 res := make([]int, q.queSize) for i, j := 0, q.front; i < q.queSize; i++ { res[i] = q.nums[q.index(j)] j++ } return res } ================================================ FILE: zh-hant/codes/go/chapter_stack_and_queue/array_queue.go ================================================ // File: array_queue.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue /* 基於環形陣列實現的佇列 */ type arrayQueue struct { nums []int // 用於儲存佇列元素的陣列 front int // 佇列首指標,指向佇列首元素 queSize int // 佇列長度 queCapacity int // 佇列容量(即最大容納元素數量) } /* 初始化佇列 */ func newArrayQueue(queCapacity int) *arrayQueue { return &arrayQueue{ nums: make([]int, queCapacity), queCapacity: queCapacity, front: 0, queSize: 0, } } /* 獲取佇列的長度 */ func (q *arrayQueue) size() int { return q.queSize } /* 判斷佇列是否為空 */ func (q *arrayQueue) isEmpty() bool { return q.queSize == 0 } /* 入列 */ func (q *arrayQueue) push(num int) { // 當 rear == queCapacity 表示佇列已滿 if q.queSize == q.queCapacity { return } // 計算佇列尾指標,指向佇列尾索引 + 1 // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 rear := (q.front + q.queSize) % q.queCapacity // 將 num 新增至佇列尾 q.nums[rear] = num q.queSize++ } /* 出列 */ func (q *arrayQueue) pop() any { num := q.peek() if num == nil { return nil } // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 q.front = (q.front + 1) % q.queCapacity q.queSize-- return num } /* 訪問佇列首元素 */ func (q *arrayQueue) peek() any { if q.isEmpty() { return nil } return q.nums[q.front] } /* 獲取 Slice 用於列印 */ func (q *arrayQueue) toSlice() []int { rear := (q.front + q.queSize) if rear >= q.queCapacity { rear %= q.queCapacity return append(q.nums[q.front:], q.nums[:rear]...) } return q.nums[q.front:rear] } ================================================ FILE: zh-hant/codes/go/chapter_stack_and_queue/array_stack.go ================================================ // File: array_stack.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue /* 基於陣列實現的堆疊 */ type arrayStack struct { data []int // 資料 } /* 初始化堆疊 */ func newArrayStack() *arrayStack { return &arrayStack{ // 設定堆疊的長度為 0,容量為 16 data: make([]int, 0, 16), } } /* 堆疊的長度 */ func (s *arrayStack) size() int { return len(s.data) } /* 堆疊是否為空 */ func (s *arrayStack) isEmpty() bool { return s.size() == 0 } /* 入堆疊 */ func (s *arrayStack) push(v int) { // 切片會自動擴容 s.data = append(s.data, v) } /* 出堆疊 */ func (s *arrayStack) pop() any { val := s.peek() s.data = s.data[:len(s.data)-1] return val } /* 獲取堆疊頂元素 */ func (s *arrayStack) peek() any { if s.isEmpty() { return nil } val := s.data[len(s.data)-1] return val } /* 獲取 Slice 用於列印 */ func (s *arrayStack) toSlice() []int { return s.data } ================================================ FILE: zh-hant/codes/go/chapter_stack_and_queue/deque_test.go ================================================ // File: deque_test.go // Created Time: 2022-11-29 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestDeque(t *testing.T) { /* 初始化雙向佇列 */ // 在 Go 中,將 list 作為雙向佇列使用 deque := list.New() /* 元素入列 */ deque.PushBack(2) deque.PushBack(5) deque.PushBack(4) deque.PushFront(3) deque.PushFront(1) fmt.Print("雙向佇列 deque = ") PrintList(deque) /* 訪問元素 */ front := deque.Front() fmt.Println("佇列首元素 front =", front.Value) rear := deque.Back() fmt.Println("佇列尾元素 rear =", rear.Value) /* 元素出列 */ deque.Remove(front) fmt.Print("佇列首出列元素 front = ", front.Value, ",佇列首出列後 deque = ") PrintList(deque) deque.Remove(rear) fmt.Print("佇列尾出列元素 rear = ", rear.Value, ",佇列尾出列後 deque = ") PrintList(deque) /* 獲取雙向佇列的長度 */ size := deque.Len() fmt.Println("雙向佇列長度 size =", size) /* 判斷雙向佇列是否為空 */ isEmpty := deque.Len() == 0 fmt.Println("雙向佇列是否為空 =", isEmpty) } func TestArrayDeque(t *testing.T) { /* 初始化雙向佇列 */ // 在 Go 中,將 list 作為雙向佇列使用 deque := newArrayDeque(16) /* 元素入列 */ deque.pushLast(3) deque.pushLast(2) deque.pushLast(5) fmt.Print("雙向佇列 deque = ") PrintSlice(deque.toSlice()) /* 訪問元素 */ peekFirst := deque.peekFirst() fmt.Println("佇列首元素 peekFirst =", peekFirst) peekLast := deque.peekLast() fmt.Println("佇列尾元素 peekLast =", peekLast) /* 元素入列 */ deque.pushLast(4) fmt.Print("元素 4 佇列尾入列後 deque = ") PrintSlice(deque.toSlice()) deque.pushFirst(1) fmt.Print("元素 1 佇列首入列後 deque = ") PrintSlice(deque.toSlice()) /* 元素出列 */ popFirst := deque.popFirst() fmt.Print("佇列首出列元素 popFirst = ", popFirst, ",佇列首出列後 deque = ") PrintSlice(deque.toSlice()) popLast := deque.popLast() fmt.Print("佇列尾出列元素 popLast = ", popLast, ",佇列尾出列後 deque = ") PrintSlice(deque.toSlice()) /* 獲取雙向佇列的長度 */ size := deque.size() fmt.Println("雙向佇列長度 size =", size) /* 判斷雙向佇列是否為空 */ isEmpty := deque.isEmpty() fmt.Println("雙向佇列是否為空 =", isEmpty) } func TestLinkedListDeque(t *testing.T) { // 初始化佇列 deque := newLinkedListDeque() // 元素入列 deque.pushLast(2) deque.pushLast(5) deque.pushLast(4) deque.pushFirst(3) deque.pushFirst(1) fmt.Print("佇列 deque = ") PrintList(deque.toList()) // 訪問佇列首元素 front := deque.peekFirst() fmt.Println("佇列首元素 front =", front) rear := deque.peekLast() fmt.Println("佇列尾元素 rear =", rear) // 元素出列 popFirst := deque.popFirst() fmt.Print("佇列首出列元素 popFirst = ", popFirst, ",佇列首出列後 deque = ") PrintList(deque.toList()) popLast := deque.popLast() fmt.Print("佇列尾出列元素 popLast = ", popLast, ",佇列尾出列後 deque = ") PrintList(deque.toList()) // 獲取隊的長度 size := deque.size() fmt.Println("隊的長度 size =", size) // 判斷是否為空 isEmpty := deque.isEmpty() fmt.Println("隊是否為空 =", isEmpty) } // BenchmarkLinkedListDeque 67.92 ns/op in Mac M1 Pro func BenchmarkLinkedListDeque(b *testing.B) { deque := newLinkedListDeque() // use b.N for looping for i := 0; i < b.N; i++ { deque.pushLast(777) } for i := 0; i < b.N; i++ { deque.popFirst() } } ================================================ FILE: zh-hant/codes/go/chapter_stack_and_queue/linkedlist_deque.go ================================================ // File: linkedlist_deque.go // Created Time: 2022-11-29 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* 基於雙向鏈結串列實現的雙向佇列 */ type linkedListDeque struct { // 使用內建包 list data *list.List } /* 初始化雙端佇列 */ func newLinkedListDeque() *linkedListDeque { return &linkedListDeque{ data: list.New(), } } /* 佇列首元素入列 */ func (s *linkedListDeque) pushFirst(value any) { s.data.PushFront(value) } /* 佇列尾元素入列 */ func (s *linkedListDeque) pushLast(value any) { s.data.PushBack(value) } /* 佇列首元素出列 */ func (s *linkedListDeque) popFirst() any { if s.isEmpty() { return nil } e := s.data.Front() s.data.Remove(e) return e.Value } /* 佇列尾元素出列 */ func (s *linkedListDeque) popLast() any { if s.isEmpty() { return nil } e := s.data.Back() s.data.Remove(e) return e.Value } /* 訪問佇列首元素 */ func (s *linkedListDeque) peekFirst() any { if s.isEmpty() { return nil } e := s.data.Front() return e.Value } /* 訪問佇列尾元素 */ func (s *linkedListDeque) peekLast() any { if s.isEmpty() { return nil } e := s.data.Back() return e.Value } /* 獲取佇列的長度 */ func (s *linkedListDeque) size() int { return s.data.Len() } /* 判斷佇列是否為空 */ func (s *linkedListDeque) isEmpty() bool { return s.data.Len() == 0 } /* 獲取 List 用於列印 */ func (s *linkedListDeque) toList() *list.List { return s.data } ================================================ FILE: zh-hant/codes/go/chapter_stack_and_queue/linkedlist_queue.go ================================================ // File: linkedlist_queue.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* 基於鏈結串列實現的佇列 */ type linkedListQueue struct { // 使用內建包 list 來實現佇列 data *list.List } /* 初始化佇列 */ func newLinkedListQueue() *linkedListQueue { return &linkedListQueue{ data: list.New(), } } /* 入列 */ func (s *linkedListQueue) push(value any) { s.data.PushBack(value) } /* 出列 */ func (s *linkedListQueue) pop() any { if s.isEmpty() { return nil } e := s.data.Front() s.data.Remove(e) return e.Value } /* 訪問佇列首元素 */ func (s *linkedListQueue) peek() any { if s.isEmpty() { return nil } e := s.data.Front() return e.Value } /* 獲取佇列的長度 */ func (s *linkedListQueue) size() int { return s.data.Len() } /* 判斷佇列是否為空 */ func (s *linkedListQueue) isEmpty() bool { return s.data.Len() == 0 } /* 獲取 List 用於列印 */ func (s *linkedListQueue) toList() *list.List { return s.data } ================================================ FILE: zh-hant/codes/go/chapter_stack_and_queue/linkedlist_stack.go ================================================ // File: linkedlist_stack.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" ) /* 基於鏈結串列實現的堆疊 */ type linkedListStack struct { // 使用內建包 list 來實現堆疊 data *list.List } /* 初始化堆疊 */ func newLinkedListStack() *linkedListStack { return &linkedListStack{ data: list.New(), } } /* 入堆疊 */ func (s *linkedListStack) push(value int) { s.data.PushBack(value) } /* 出堆疊 */ func (s *linkedListStack) pop() any { if s.isEmpty() { return nil } e := s.data.Back() s.data.Remove(e) return e.Value } /* 訪問堆疊頂元素 */ func (s *linkedListStack) peek() any { if s.isEmpty() { return nil } e := s.data.Back() return e.Value } /* 獲取堆疊的長度 */ func (s *linkedListStack) size() int { return s.data.Len() } /* 判斷堆疊是否為空 */ func (s *linkedListStack) isEmpty() bool { return s.data.Len() == 0 } /* 獲取 List 用於列印 */ func (s *linkedListStack) toList() *list.List { return s.data } ================================================ FILE: zh-hant/codes/go/chapter_stack_and_queue/queue_test.go ================================================ // File: queue_test.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "container/list" "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestQueue(t *testing.T) { /* 初始化佇列 */ // 在 Go 中,將 list 作為佇列來使用 queue := list.New() /* 元素入列 */ queue.PushBack(1) queue.PushBack(3) queue.PushBack(2) queue.PushBack(5) queue.PushBack(4) fmt.Print("佇列 queue = ") PrintList(queue) /* 訪問佇列首元素 */ peek := queue.Front() fmt.Println("佇列首元素 peek =", peek.Value) /* 元素出列 */ pop := queue.Front() queue.Remove(pop) fmt.Print("出列元素 pop = ", pop.Value, ",出列後 queue = ") PrintList(queue) /* 獲取佇列的長度 */ size := queue.Len() fmt.Println("佇列長度 size =", size) /* 判斷佇列是否為空 */ isEmpty := queue.Len() == 0 fmt.Println("佇列是否為空 =", isEmpty) } func TestArrayQueue(t *testing.T) { // 初始化佇列,使用佇列的通用介面 capacity := 10 queue := newArrayQueue(capacity) if queue.pop() != nil { t.Errorf("want:%v,got:%v", nil, queue.pop()) } // 元素入列 queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) fmt.Print("佇列 queue = ") PrintSlice(queue.toSlice()) // 訪問佇列首元素 peek := queue.peek() fmt.Println("佇列首元素 peek =", peek) // 元素出列 pop := queue.pop() fmt.Print("出列元素 pop = ", pop, ", 出列後 queue = ") PrintSlice(queue.toSlice()) // 獲取隊的長度 size := queue.size() fmt.Println("隊的長度 size =", size) // 判斷是否為空 isEmpty := queue.isEmpty() fmt.Println("隊是否為空 =", isEmpty) /* 測試環形陣列 */ for i := 0; i < 10; i++ { queue.push(i) queue.pop() fmt.Print("第", i, "輪入列 + 出列後 queue =") PrintSlice(queue.toSlice()) } } func TestLinkedListQueue(t *testing.T) { // 初始化隊 queue := newLinkedListQueue() // 元素入列 queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) fmt.Print("佇列 queue = ") PrintList(queue.toList()) // 訪問佇列首元素 peek := queue.peek() fmt.Println("佇列首元素 peek =", peek) // 元素出列 pop := queue.pop() fmt.Print("出列元素 pop = ", pop, ", 出列後 queue = ") PrintList(queue.toList()) // 獲取隊的長度 size := queue.size() fmt.Println("隊的長度 size =", size) // 判斷是否為空 isEmpty := queue.isEmpty() fmt.Println("隊是否為空 =", isEmpty) } // BenchmarkArrayQueue 8 ns/op in Mac M1 Pro func BenchmarkArrayQueue(b *testing.B) { capacity := 1000 queue := newArrayQueue(capacity) // use b.N for looping for i := 0; i < b.N; i++ { queue.push(777) } for i := 0; i < b.N; i++ { queue.pop() } } // BenchmarkLinkedQueue 62.66 ns/op in Mac M1 Pro func BenchmarkLinkedQueue(b *testing.B) { queue := newLinkedListQueue() // use b.N for looping for i := 0; i < b.N; i++ { queue.push(777) } for i := 0; i < b.N; i++ { queue.pop() } } ================================================ FILE: zh-hant/codes/go/chapter_stack_and_queue/stack_test.go ================================================ // File: stack_test.go // Created Time: 2022-11-28 // Author: Reanon (793584285@qq.com) package chapter_stack_and_queue import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestStack(t *testing.T) { /* 初始化堆疊 */ // 在 Go 中,推薦將 Slice 當作堆疊來使用 var stack []int /* 元素入堆疊 */ stack = append(stack, 1) stack = append(stack, 3) stack = append(stack, 2) stack = append(stack, 5) stack = append(stack, 4) fmt.Print("堆疊 stack = ") PrintSlice(stack) /* 訪問堆疊頂元素 */ peek := stack[len(stack)-1] fmt.Println("堆疊頂元素 peek =", peek) /* 元素出堆疊 */ pop := stack[len(stack)-1] stack = stack[:len(stack)-1] fmt.Print("出堆疊元素 pop = ", pop, ",出堆疊後 stack = ") PrintSlice(stack) /* 獲取堆疊的長度 */ size := len(stack) fmt.Println("堆疊的長度 size =", size) /* 判斷是否為空 */ isEmpty := len(stack) == 0 fmt.Println("堆疊是否為空 =", isEmpty) } func TestArrayStack(t *testing.T) { // 初始化堆疊, 使用介面承接 stack := newArrayStack() // 元素入堆疊 stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) fmt.Print("堆疊 stack = ") PrintSlice(stack.toSlice()) // 訪問堆疊頂元素 peek := stack.peek() fmt.Println("堆疊頂元素 peek =", peek) // 元素出堆疊 pop := stack.pop() fmt.Print("出堆疊元素 pop = ", pop, ", 出堆疊後 stack = ") PrintSlice(stack.toSlice()) // 獲取堆疊的長度 size := stack.size() fmt.Println("堆疊的長度 size =", size) // 判斷是否為空 isEmpty := stack.isEmpty() fmt.Println("堆疊是否為空 =", isEmpty) } func TestLinkedListStack(t *testing.T) { // 初始化堆疊 stack := newLinkedListStack() // 元素入堆疊 stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) fmt.Print("堆疊 stack = ") PrintList(stack.toList()) // 訪問堆疊頂元素 peek := stack.peek() fmt.Println("堆疊頂元素 peek =", peek) // 元素出堆疊 pop := stack.pop() fmt.Print("出堆疊元素 pop = ", pop, ", 出堆疊後 stack = ") PrintList(stack.toList()) // 獲取堆疊的長度 size := stack.size() fmt.Println("堆疊的長度 size =", size) // 判斷是否為空 isEmpty := stack.isEmpty() fmt.Println("堆疊是否為空 =", isEmpty) } // BenchmarkArrayStack 8 ns/op in Mac M1 Pro func BenchmarkArrayStack(b *testing.B) { stack := newArrayStack() // use b.N for looping for i := 0; i < b.N; i++ { stack.push(777) } for i := 0; i < b.N; i++ { stack.pop() } } // BenchmarkLinkedListStack 65.02 ns/op in Mac M1 Pro func BenchmarkLinkedListStack(b *testing.B) { stack := newLinkedListStack() // use b.N for looping for i := 0; i < b.N; i++ { stack.push(777) } for i := 0; i < b.N; i++ { stack.pop() } } ================================================ FILE: zh-hant/codes/go/chapter_tree/array_binary_tree.go ================================================ // File: array_binary_tree.go // Created Time: 2023-07-24 // Author: Reanon (793584285@qq.com) package chapter_tree /* 陣列表示下的二元樹類別 */ type arrayBinaryTree struct { tree []any } /* 建構子 */ func newArrayBinaryTree(arr []any) *arrayBinaryTree { return &arrayBinaryTree{ tree: arr, } } /* 串列容量 */ func (abt *arrayBinaryTree) size() int { return len(abt.tree) } /* 獲取索引為 i 節點的值 */ func (abt *arrayBinaryTree) val(i int) any { // 若索引越界,則返回 null ,代表空位 if i < 0 || i >= abt.size() { return nil } return abt.tree[i] } /* 獲取索引為 i 節點的左子節點的索引 */ func (abt *arrayBinaryTree) left(i int) int { return 2*i + 1 } /* 獲取索引為 i 節點的右子節點的索引 */ func (abt *arrayBinaryTree) right(i int) int { return 2*i + 2 } /* 獲取索引為 i 節點的父節點的索引 */ func (abt *arrayBinaryTree) parent(i int) int { return (i - 1) / 2 } /* 層序走訪 */ func (abt *arrayBinaryTree) levelOrder() []any { var res []any // 直接走訪陣列 for i := 0; i < abt.size(); i++ { if abt.val(i) != nil { res = append(res, abt.val(i)) } } return res } /* 深度優先走訪 */ func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) { // 若為空位,則返回 if abt.val(i) == nil { return } // 前序走訪 if order == "pre" { *res = append(*res, abt.val(i)) } abt.dfs(abt.left(i), order, res) // 中序走訪 if order == "in" { *res = append(*res, abt.val(i)) } abt.dfs(abt.right(i), order, res) // 後序走訪 if order == "post" { *res = append(*res, abt.val(i)) } } /* 前序走訪 */ func (abt *arrayBinaryTree) preOrder() []any { var res []any abt.dfs(0, "pre", &res) return res } /* 中序走訪 */ func (abt *arrayBinaryTree) inOrder() []any { var res []any abt.dfs(0, "in", &res) return res } /* 後序走訪 */ func (abt *arrayBinaryTree) postOrder() []any { var res []any abt.dfs(0, "post", &res) return res } ================================================ FILE: zh-hant/codes/go/chapter_tree/array_binary_tree_test.go ================================================ // File: array_binary_tree_test.go // Created Time: 2023-07-24 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestArrayBinaryTree(t *testing.T) { // 初始化二元樹 // 這裡藉助了一個從陣列直接生成二元樹的函式 arr := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} root := SliceToTree(arr) fmt.Println("\n初始化二元樹") fmt.Println("二元樹的陣列表示:") fmt.Println(arr) fmt.Println("二元樹的鏈結串列表示:") PrintTree(root) // 陣列表示下的二元樹類別 abt := newArrayBinaryTree(arr) // 訪問節點 i := 1 l := abt.left(i) r := abt.right(i) p := abt.parent(i) fmt.Println("\n當前節點的索引為", i, ",值為", abt.val(i)) fmt.Println("其左子節點的索引為", l, ",值為", abt.val(l)) fmt.Println("其右子節點的索引為", r, ",值為", abt.val(r)) fmt.Println("其父節點的索引為", p, ",值為", abt.val(p)) // 走訪樹 res := abt.levelOrder() fmt.Println("\n層序走訪為:", res) res = abt.preOrder() fmt.Println("前序走訪為:", res) res = abt.inOrder() fmt.Println("中序走訪為:", res) res = abt.postOrder() fmt.Println("後序走訪為:", res) } ================================================ FILE: zh-hant/codes/go/chapter_tree/avl_tree.go ================================================ // File: avl_tree.go // Created Time: 2023-01-08 // Author: Reanon (793584285@qq.com) package chapter_tree import . "github.com/krahets/hello-algo/pkg" /* AVL 樹 */ type aVLTree struct { // 根節點 root *TreeNode } func newAVLTree() *aVLTree { return &aVLTree{root: nil} } /* 獲取節點高度 */ func (t *aVLTree) height(node *TreeNode) int { // 空節點高度為 -1 ,葉節點高度為 0 if node != nil { return node.Height } return -1 } /* 更新節點高度 */ func (t *aVLTree) updateHeight(node *TreeNode) { lh := t.height(node.Left) rh := t.height(node.Right) // 節點高度等於最高子樹高度 + 1 if lh > rh { node.Height = lh + 1 } else { node.Height = rh + 1 } } /* 獲取平衡因子 */ func (t *aVLTree) balanceFactor(node *TreeNode) int { // 空節點平衡因子為 0 if node == nil { return 0 } // 節點平衡因子 = 左子樹高度 - 右子樹高度 return t.height(node.Left) - t.height(node.Right) } /* 右旋操作 */ func (t *aVLTree) rightRotate(node *TreeNode) *TreeNode { child := node.Left grandChild := child.Right // 以 child 為原點,將 node 向右旋轉 child.Right = node node.Left = grandChild // 更新節點高度 t.updateHeight(node) t.updateHeight(child) // 返回旋轉後子樹的根節點 return child } /* 左旋操作 */ func (t *aVLTree) leftRotate(node *TreeNode) *TreeNode { child := node.Right grandChild := child.Left // 以 child 為原點,將 node 向左旋轉 child.Left = node node.Right = grandChild // 更新節點高度 t.updateHeight(node) t.updateHeight(child) // 返回旋轉後子樹的根節點 return child } /* 執行旋轉操作,使該子樹重新恢復平衡 */ func (t *aVLTree) rotate(node *TreeNode) *TreeNode { // 獲取節點 node 的平衡因子 // Go 推薦短變數,這裡 bf 指代 t.balanceFactor bf := t.balanceFactor(node) // 左偏樹 if bf > 1 { if t.balanceFactor(node.Left) >= 0 { // 右旋 return t.rightRotate(node) } else { // 先左旋後右旋 node.Left = t.leftRotate(node.Left) return t.rightRotate(node) } } // 右偏樹 if bf < -1 { if t.balanceFactor(node.Right) <= 0 { // 左旋 return t.leftRotate(node) } else { // 先右旋後左旋 node.Right = t.rightRotate(node.Right) return t.leftRotate(node) } } // 平衡樹,無須旋轉,直接返回 return node } /* 插入節點 */ func (t *aVLTree) insert(val int) { t.root = t.insertHelper(t.root, val) } /* 遞迴插入節點(輔助函式) */ func (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode { if node == nil { return NewTreeNode(val) } /* 1. 查詢插入位置並插入節點 */ if val < node.Val.(int) { node.Left = t.insertHelper(node.Left, val) } else if val > node.Val.(int) { node.Right = t.insertHelper(node.Right, val) } else { // 重複節點不插入,直接返回 return node } // 更新節點高度 t.updateHeight(node) /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = t.rotate(node) // 返回子樹的根節點 return node } /* 刪除節點 */ func (t *aVLTree) remove(val int) { t.root = t.removeHelper(t.root, val) } /* 遞迴刪除節點(輔助函式) */ func (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode { if node == nil { return nil } /* 1. 查詢節點並刪除 */ if val < node.Val.(int) { node.Left = t.removeHelper(node.Left, val) } else if val > node.Val.(int) { node.Right = t.removeHelper(node.Right, val) } else { if node.Left == nil || node.Right == nil { child := node.Left if node.Right != nil { child = node.Right } if child == nil { // 子節點數量 = 0 ,直接刪除 node 並返回 return nil } else { // 子節點數量 = 1 ,直接刪除 node node = child } } else { // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 temp := node.Right for temp.Left != nil { temp = temp.Left } node.Right = t.removeHelper(node.Right, temp.Val.(int)) node.Val = temp.Val } } // 更新節點高度 t.updateHeight(node) /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = t.rotate(node) // 返回子樹的根節點 return node } /* 查詢節點 */ func (t *aVLTree) search(val int) *TreeNode { cur := t.root // 迴圈查詢,越過葉節點後跳出 for cur != nil { if cur.Val.(int) < val { // 目標節點在 cur 的右子樹中 cur = cur.Right } else if cur.Val.(int) > val { // 目標節點在 cur 的左子樹中 cur = cur.Left } else { // 找到目標節點,跳出迴圈 break } } // 返回目標節點 return cur } ================================================ FILE: zh-hant/codes/go/chapter_tree/avl_tree_test.go ================================================ // File: avl_tree_test.go // Created Time: 2023-01-08 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestAVLTree(t *testing.T) { /* 初始化空 AVL 樹 */ tree := newAVLTree() /* 插入節點 */ // 請關注插入節點後,AVL 樹是如何保持平衡的 testInsert(tree, 1) testInsert(tree, 2) testInsert(tree, 3) testInsert(tree, 4) testInsert(tree, 5) testInsert(tree, 8) testInsert(tree, 7) testInsert(tree, 9) testInsert(tree, 10) testInsert(tree, 6) /* 插入重複節點 */ testInsert(tree, 7) /* 刪除節點 */ // 請關注刪除節點後,AVL 樹是如何保持平衡的 testRemove(tree, 8) // 刪除度為 0 的節點 testRemove(tree, 5) // 刪除度為 1 的節點 testRemove(tree, 4) // 刪除度為 2 的節點 /* 查詢節點 */ node := tree.search(7) fmt.Printf("\n查詢到的節點物件為 %#v ,節點值 = %d \n", node, node.Val) } func testInsert(tree *aVLTree, val int) { tree.insert(val) fmt.Printf("\n插入節點 %d 後,AVL 樹為 \n", val) PrintTree(tree.root) } func testRemove(tree *aVLTree, val int) { tree.remove(val) fmt.Printf("\n刪除節點 %d 後,AVL 樹為 \n", val) PrintTree(tree.root) } ================================================ FILE: zh-hant/codes/go/chapter_tree/binary_search_tree.go ================================================ // File: binary_search_tree.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( . "github.com/krahets/hello-algo/pkg" ) type binarySearchTree struct { root *TreeNode } func newBinarySearchTree() *binarySearchTree { bst := &binarySearchTree{} // 初始化空樹 bst.root = nil return bst } /* 獲取根節點 */ func (bst *binarySearchTree) getRoot() *TreeNode { return bst.root } /* 查詢節點 */ func (bst *binarySearchTree) search(num int) *TreeNode { node := bst.root // 迴圈查詢,越過葉節點後跳出 for node != nil { if node.Val.(int) < num { // 目標節點在 cur 的右子樹中 node = node.Right } else if node.Val.(int) > num { // 目標節點在 cur 的左子樹中 node = node.Left } else { // 找到目標節點,跳出迴圈 break } } // 返回目標節點 return node } /* 插入節點 */ func (bst *binarySearchTree) insert(num int) { cur := bst.root // 若樹為空,則初始化根節點 if cur == nil { bst.root = NewTreeNode(num) return } // 待插入節點之前的節點位置 var pre *TreeNode = nil // 迴圈查詢,越過葉節點後跳出 for cur != nil { if cur.Val == num { return } pre = cur if cur.Val.(int) < num { cur = cur.Right } else { cur = cur.Left } } // 插入節點 node := NewTreeNode(num) if pre.Val.(int) < num { pre.Right = node } else { pre.Left = node } } /* 刪除節點 */ func (bst *binarySearchTree) remove(num int) { cur := bst.root // 若樹為空,直接提前返回 if cur == nil { return } // 待刪除節點之前的節點位置 var pre *TreeNode = nil // 迴圈查詢,越過葉節點後跳出 for cur != nil { if cur.Val == num { break } pre = cur if cur.Val.(int) < num { // 待刪除節點在右子樹中 cur = cur.Right } else { // 待刪除節點在左子樹中 cur = cur.Left } } // 若無待刪除節點,則直接返回 if cur == nil { return } // 子節點數為 0 或 1 if cur.Left == nil || cur.Right == nil { var child *TreeNode = nil // 取出待刪除節點的子節點 if cur.Left != nil { child = cur.Left } else { child = cur.Right } // 刪除節點 cur if cur != bst.root { if pre.Left == cur { pre.Left = child } else { pre.Right = child } } else { // 若刪除節點為根節點,則重新指定根節點 bst.root = child } // 子節點數為 2 } else { // 獲取中序走訪中待刪除節點 cur 的下一個節點 tmp := cur.Right for tmp.Left != nil { tmp = tmp.Left } // 遞迴刪除節點 tmp bst.remove(tmp.Val.(int)) // 用 tmp 覆蓋 cur cur.Val = tmp.Val } } /* 列印二元搜尋樹 */ func (bst *binarySearchTree) print() { PrintTree(bst.root) } ================================================ FILE: zh-hant/codes/go/chapter_tree/binary_search_tree_test.go ================================================ // File: binary_search_tree_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" ) func TestBinarySearchTree(t *testing.T) { bst := newBinarySearchTree() nums := []int{8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15} // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 for _, num := range nums { bst.insert(num) } fmt.Println("\n初始化的二元樹為:") bst.print() // 獲取根節點 node := bst.getRoot() fmt.Println("\n二元樹的根節點為:", node.Val) // 查詢節點 node = bst.search(7) fmt.Println("查詢到的節點物件為", node, ",節點值 =", node.Val) // 插入節點 bst.insert(16) fmt.Println("\n插入節點後 16 的二元樹為:") bst.print() // 刪除節點 bst.remove(1) fmt.Println("\n刪除節點 1 後的二元樹為:") bst.print() bst.remove(2) fmt.Println("\n刪除節點 2 後的二元樹為:") bst.print() bst.remove(4) fmt.Println("\n刪除節點 4 後的二元樹為:") bst.print() } ================================================ FILE: zh-hant/codes/go/chapter_tree/binary_tree_bfs.go ================================================ // File: binary_tree_bfs.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "container/list" . "github.com/krahets/hello-algo/pkg" ) /* 層序走訪 */ func levelOrder(root *TreeNode) []any { // 初始化佇列,加入根節點 queue := list.New() queue.PushBack(root) // 初始化一個切片,用於儲存走訪序列 nums := make([]any, 0) for queue.Len() > 0 { // 隊列出隊 node := queue.Remove(queue.Front()).(*TreeNode) // 儲存節點值 nums = append(nums, node.Val) if node.Left != nil { // 左子節點入列 queue.PushBack(node.Left) } if node.Right != nil { // 右子節點入列 queue.PushBack(node.Right) } } return nums } ================================================ FILE: zh-hant/codes/go/chapter_tree/binary_tree_bfs_test.go ================================================ // File: binary_tree_bfs_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestLevelOrder(t *testing.T) { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二元樹: ") PrintTree(root) // 層序走訪 nums := levelOrder(root) fmt.Println("\n層序走訪的節點列印序列 =", nums) } ================================================ FILE: zh-hant/codes/go/chapter_tree/binary_tree_dfs.go ================================================ // File: binary_tree_dfs.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( . "github.com/krahets/hello-algo/pkg" ) var nums []any /* 前序走訪 */ func preOrder(node *TreeNode) { if node == nil { return } // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 nums = append(nums, node.Val) preOrder(node.Left) preOrder(node.Right) } /* 中序走訪 */ func inOrder(node *TreeNode) { if node == nil { return } // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 inOrder(node.Left) nums = append(nums, node.Val) inOrder(node.Right) } /* 後序走訪 */ func postOrder(node *TreeNode) { if node == nil { return } // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 postOrder(node.Left) postOrder(node.Right) nums = append(nums, node.Val) } ================================================ FILE: zh-hant/codes/go/chapter_tree/binary_tree_dfs_test.go ================================================ // File: binary_tree_dfs_test.go // Created Time: 2022-11-26 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestPreInPostOrderTraversal(t *testing.T) { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二元樹: ") PrintTree(root) // 前序走訪 nums = nil preOrder(root) fmt.Println("\n前序走訪的節點列印序列 =", nums) // 中序走訪 nums = nil inOrder(root) fmt.Println("\n中序走訪的節點列印序列 =", nums) // 後序走訪 nums = nil postOrder(root) fmt.Println("\n後序走訪的節點列印序列 =", nums) } ================================================ FILE: zh-hant/codes/go/chapter_tree/binary_tree_test.go ================================================ // File: binary_tree_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package chapter_tree import ( "fmt" "testing" . "github.com/krahets/hello-algo/pkg" ) func TestBinaryTree(t *testing.T) { /* 初始化二元樹 */ // 初始化節點 n1 := NewTreeNode(1) n2 := NewTreeNode(2) n3 := NewTreeNode(3) n4 := NewTreeNode(4) n5 := NewTreeNode(5) // 構建節點之間的引用(指標) n1.Left = n2 n1.Right = n3 n2.Left = n4 n2.Right = n5 fmt.Println("初始化二元樹") PrintTree(n1) /* 插入與刪除節點 */ // 插入節點 p := NewTreeNode(0) n1.Left = p p.Left = n2 fmt.Println("插入節點 P 後") PrintTree(n1) // 刪除節點 n1.Left = n2 fmt.Println("刪除節點 P 後") PrintTree(n1) } ================================================ FILE: zh-hant/codes/go/go.mod ================================================ module github.com/krahets/hello-algo go 1.19 ================================================ FILE: zh-hant/codes/go/pkg/list_node.go ================================================ // File: list_node.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg // ListNode 鏈結串列節點 type ListNode struct { Next *ListNode Val int } // NewListNode 鏈結串列節點建構子 func NewListNode(v int) *ListNode { return &ListNode{ Next: nil, Val: v, } } // ArrayToLinkedList 將陣列反序列化為鏈結串列 func ArrayToLinkedList(arr []int) *ListNode { // dummy header of linked list dummy := NewListNode(0) node := dummy for _, val := range arr { node.Next = NewListNode(val) node = node.Next } return dummy.Next } ================================================ FILE: zh-hant/codes/go/pkg/list_node_test.go ================================================ // File: list_node_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg import ( "testing" ) func TestListNode(t *testing.T) { arr := []int{2, 3, 5, 6, 7} head := ArrayToLinkedList(arr) PrintLinkedList(head) } ================================================ FILE: zh-hant/codes/go/pkg/print_utils.go ================================================ // File: print_utils.go // Created Time: 2022-12-03 // Author: Reanon (793584285@qq.com), krahets (krahets@163.com), msk397 (machangxinq@gmail.com) package pkg import ( "container/list" "fmt" "strconv" "strings" ) // PrintSlice 列印切片 func PrintSlice[T any](nums []T) { fmt.Printf("%v", nums) fmt.Println() } // PrintList 列印串列 func PrintList(list *list.List) { if list.Len() == 0 { fmt.Print("[]\n") return } e := list.Front() // 強轉為 string, 會影響效率 fmt.Print("[") for e.Next() != nil { fmt.Print(e.Value, " ") e = e.Next() } fmt.Print(e.Value, "]\n") } // PrintMap 列印雜湊表 func PrintMap[K comparable, V any](m map[K]V) { for key, value := range m { fmt.Println(key, "->", value) } } // PrintHeap 列印堆積 func PrintHeap(h []any) { fmt.Printf("堆積的陣列表示:") fmt.Printf("%v", h) fmt.Printf("\n堆積的樹狀表示:\n") root := SliceToTree(h) PrintTree(root) } // PrintLinkedList 列印鏈結串列 func PrintLinkedList(node *ListNode) { if node == nil { return } var builder strings.Builder for node.Next != nil { builder.WriteString(strconv.Itoa(node.Val) + " -> ") node = node.Next } builder.WriteString(strconv.Itoa(node.Val)) fmt.Println(builder.String()) } // PrintTree 列印二元樹 func PrintTree(root *TreeNode) { printTreeHelper(root, nil, false) } // printTreeHelper 列印二元樹 // This tree printer is borrowed from TECHIE DELIGHT // https://www.techiedelight.com/c-program-print-binary-tree/ func printTreeHelper(root *TreeNode, prev *trunk, isRight bool) { if root == nil { return } prevStr := " " trunk := newTrunk(prev, prevStr) printTreeHelper(root.Right, trunk, true) if prev == nil { trunk.str = "———" } else if isRight { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev.str = prevStr } showTrunk(trunk) fmt.Println(root.Val) if prev != nil { prev.str = prevStr } trunk.str = " |" printTreeHelper(root.Left, trunk, false) } type trunk struct { prev *trunk str string } func newTrunk(prev *trunk, str string) *trunk { return &trunk{ prev: prev, str: str, } } func showTrunk(t *trunk) { if t == nil { return } showTrunk(t.prev) fmt.Print(t.str) } ================================================ FILE: zh-hant/codes/go/pkg/tree_node.go ================================================ // File: tree_node.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg // TreeNode 二元樹節點 type TreeNode struct { Val any // 節點值 Height int // 節點高度 Left *TreeNode // 左子節點引用 Right *TreeNode // 右子節點引用 } // NewTreeNode 二元樹節點建構子 func NewTreeNode(v any) *TreeNode { return &TreeNode{ Val: v, Height: 0, Left: nil, Right: nil, } } // 序列化編碼規則請參考: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二元樹的陣列表示: // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] // 二元樹的鏈結串列表示: // // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // // ——— 1 // // \——— 2 // | /——— 9 // \——— 4 // \——— 8 // SliceToTreeDFS 將串列反序列化為二元樹:遞迴 func SliceToTreeDFS(arr []any, i int) *TreeNode { if i < 0 || i >= len(arr) || arr[i] == nil { return nil } root := NewTreeNode(arr[i]) root.Left = SliceToTreeDFS(arr, 2*i+1) root.Right = SliceToTreeDFS(arr, 2*i+2) return root } // SliceToTree 將切片反序列化為二元樹 func SliceToTree(arr []any) *TreeNode { return SliceToTreeDFS(arr, 0) } // TreeToSliceDFS 將二元樹序列化為切片:遞迴 func TreeToSliceDFS(root *TreeNode, i int, res *[]any) { if root == nil { return } for i >= len(*res) { *res = append(*res, nil) } (*res)[i] = root.Val TreeToSliceDFS(root.Left, 2*i+1, res) TreeToSliceDFS(root.Right, 2*i+2, res) } // TreeToSlice 將二元樹序列化為切片 func TreeToSlice(root *TreeNode) []any { var res []any TreeToSliceDFS(root, 0, &res) return res } ================================================ FILE: zh-hant/codes/go/pkg/tree_node_test.go ================================================ // File: tree_node_test.go // Created Time: 2022-11-25 // Author: Reanon (793584285@qq.com) package pkg import ( "fmt" "testing" ) func TestTreeNode(t *testing.T) { arr := []any{1, 2, 3, nil, 5, 6, nil} node := SliceToTree(arr) // print tree PrintTree(node) // tree to arr fmt.Println(TreeToSlice(node)) } ================================================ FILE: zh-hant/codes/go/pkg/vertex.go ================================================ // File: vertex.go // Created Time: 2023-02-18 // Author: Reanon (793584285@qq.com) package pkg // Vertex 頂點類別 type Vertex struct { Val int } // NewVertex 頂點建構子 func NewVertex(val int) Vertex { return Vertex{ Val: val, } } // ValsToVets 將值串列反序列化為頂點串列 func ValsToVets(vals []int) []Vertex { vets := make([]Vertex, len(vals)) for i := 0; i < len(vals); i++ { vets[i] = NewVertex(vals[i]) } return vets } // VetsToVals 將頂點串列序列化為值串列 func VetsToVals(vets []Vertex) []int { vals := make([]int, len(vets)) for i := range vets { vals[i] = vets[i].Val } return vals } // DeleteSliceElms 刪除切片指定元素 func DeleteSliceElms[T any](a []T, elms ...T) []T { if len(a) == 0 || len(elms) == 0 { return a } // 先將元素轉為 set m := make(map[any]struct{}) for _, v := range elms { m[v] = struct{}{} } // 過濾掉指定元素 res := make([]T, 0, len(a)) for _, v := range a { if _, ok := m[v]; !ok { res = append(res, v) } } return res } ================================================ FILE: zh-hant/codes/java/.gitignore ================================================ build ================================================ FILE: zh-hant/codes/java/chapter_array_and_linkedlist/array.java ================================================ /** * 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 以及之後的所有元素向後移動一位 for (int i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 將 num 賦給 index 處的元素 nums[index] = num; } /* 刪除索引 index 處的元素 */ static void remove(int[] nums, int index) { // 把索引 index 之後的所有元素向前移動一位 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; } /* Driver Code */ 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); } } ================================================ FILE: zh-hant/codes/java/chapter_array_and_linkedlist/linked_list.java ================================================ /** * 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; } /* Driver Code */ public static void main(String[] args) { /* 初始化鏈結串列 */ // 初始化各個節點 ListNode n0 = new ListNode(1); ListNode n1 = new ListNode(3); ListNode n2 = new ListNode(2); ListNode n3 = new ListNode(5); ListNode n4 = new ListNode(4); // 構建節點之間的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; System.out.println("初始化的鏈結串列為"); 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); } } ================================================ FILE: zh-hant/codes/java/chapter_array_and_linkedlist/list.java ================================================ /** * 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) { /* 初始化串列 */ // 注意陣列的元素型別是 int[] 的包裝類別 Integer[] Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; List nums = new ArrayList<>(Arrays.asList(numbers)); System.out.println("串列 nums = " + nums); /* 訪問元素 */ int num = nums.get(1); System.out.println("訪問索引 1 處的元素,得到 num = " + num); /* 更新元素 */ 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; } /* 拼接兩個串列 */ 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); } } ================================================ FILE: zh-hant/codes/java/chapter_array_and_linkedlist/my_list.java ================================================ /** * 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 以及之後的元素都向後移動一位 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 之後的元素都向前移動一位 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 { /* Driver Code */ 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()); } } ================================================ FILE: zh-hant/codes/java/chapter_backtracking/n_queens.java ================================================ /** * 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); } } } } ================================================ FILE: zh-hant/codes/java/chapter_backtracking/permutations_i.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/chapter_backtracking/permutations_ii.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/chapter_backtracking/preorder_traversal_i_compact.java ================================================ /** * 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; /* 前序走訪:例題一 */ 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); } } ================================================ FILE: zh-hant/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java ================================================ /** * 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; /* 前序走訪:例題二 */ 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); } } } ================================================ FILE: zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java ================================================ /** * 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; /* 前序走訪:例題三 */ 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); } } } ================================================ FILE: zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_template.java ================================================ /** * 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); } /* 回溯演算法:例題三 */ 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); } } } ================================================ FILE: zh-hant/codes/java/chapter_backtracking/subset_sum_i.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/chapter_backtracking/subset_sum_i_naive.java ================================================ /** * 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("請注意,該方法輸出的結果包含重複集合"); } } ================================================ FILE: zh-hant/codes/java/chapter_backtracking/subset_sum_ii.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/chapter_computational_complexity/iteration.java ================================================ /** * 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 迴圈(兩次更新) */ static int whileLoopII(int n) { int res = 0; int i = 1; // 初始化條件變數 // 迴圈求和 1, 4, 10, ... while (i <= n) { res += i; // 更新條件變數 i++; i *= 2; } return res; } /* 雙層 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(); } /* Driver Code */ 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 迴圈(兩次更新)求和結果 res = " + res); String resStr = nestedForLoop(n); System.out.println("\n雙層 for 迴圈的走訪結果 " + resStr); } } ================================================ FILE: zh-hant/codes/java/chapter_computational_complexity/recursion.java ================================================ /** * 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; } /* Driver Code */ 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); } } ================================================ FILE: zh-hant/codes/java/chapter_computational_complexity/space_complexity.java ================================================ /** * 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; } /* Driver Code */ public static void main(String[] args) { int n = 5; // 常數階 constant(n); // 線性階 linear(n); linearRecur(n); // 平方階 quadratic(n); quadraticRecur(n); // 指數階 TreeNode root = buildTree(n); PrintUtil.printTree(root); } } ================================================ FILE: zh-hant/codes/java/chapter_computational_complexity/time_complexity.java ================================================ /** * 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; // 細胞每輪一分為二,形成數列 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; } /* Driver Code */ 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); } } ================================================ FILE: zh-hant/codes/java/chapter_computational_complexity/worst_best_time_complexity.java ================================================ /** * 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; } /* Driver Code */ public static void main(String[] args) { for (int i = 0; i < 10; i++) { int n = 100; int[] nums = randomNumbers(n); int index = findOne(nums); System.out.println("\n陣列 [ 1, 2, ..., n ] 被打亂後 = " + Arrays.toString(nums)); System.out.println("數字 1 的索引為 " + index); } } } ================================================ FILE: zh-hant/codes/java/chapter_divide_and_conquer/binary_search_recur.java ================================================ /** * 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) / 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); } } ================================================ FILE: zh-hant/codes/java/chapter_divide_and_conquer/build_tree.java ================================================ /** * 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) { // 初始化雜湊表,儲存 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); } } ================================================ FILE: zh-hant/codes/java/chapter_divide_and_conquer/hanota.java ================================================ /** * 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 只剩下一個圓盤,則直接將其移到 tar if (i == 1) { move(src, tar); return; } // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf dfs(i - 1, src, tar, buf); // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar move(src, tar); // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar dfs(i - 1, buf, src, tar); } /* 求解河內塔問題 */ static void solveHanota(List A, List B, List C) { int n = A.size(); // 將 A 頂部 n 個圓盤藉助 B 移到 C dfs(n, A, B, C); } public static void main(String[] args) { // 串列尾部是柱子頂部 List A = new ArrayList<>(Arrays.asList(5, 4, 3, 2, 1)); List B = new ArrayList<>(); List C = new ArrayList<>(); System.out.println("初始狀態下:"); System.out.println("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); } } ================================================ FILE: zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java ================================================ /** * 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java ================================================ /** * 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java ================================================ /** * 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java ================================================ /** * 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java ================================================ /** * 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_dynamic_programming/coin_change.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/chapter_dynamic_programming/coin_change_ii.java ================================================ /** * 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 這兩種方案之和 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 這兩種方案之和 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); } } ================================================ FILE: zh-hant/codes/java/chapter_dynamic_programming/edit_distance.java ================================================ /** * 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; // 若兩字元相等,則直接跳過此兩字元 if (s.charAt(i - 1) == t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 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]; // 若兩字元相等,則直接跳過此兩字元 if (s.charAt(i - 1) == t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 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)) { // 若兩字元相等,則直接跳過此兩字元 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 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)) { // 若兩字元相等,則直接跳過此兩字元 dp[j] = leftup; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 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 + " 步"); } } ================================================ FILE: zh-hant/codes/java/chapter_dynamic_programming/knapsack.java ================================================ /** * 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]; // 返回兩種方案中價值更大的那一個 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]; // 記錄並返回兩種方案中價值更大的那一個 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); } } ================================================ FILE: zh-hant/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java ================================================ /** * 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_dynamic_programming/min_path_sum.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/chapter_dynamic_programming/unbounded_knapsack.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/chapter_graph/graph_adjacency_list.java ================================================ /** * 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 { // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 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(); } } ================================================ FILE: zh-hant/codes/java/chapter_graph/graph_adjacency_matrix.java ================================================ /** * 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); } // 新增邊 // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 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 對應 vertices 元素索引 public void addEdge(int i, int j) { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw new IndexOutOfBoundsException(); // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) adjMat.get(i).set(j, 1); adjMat.get(j).set(i, 1); } /* 刪除邊 */ // 參數 i, j 對應 vertices 元素索引 public void removeEdge(int i, int j) { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw new 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) { /* 初始化無向圖 */ // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 int[] vertices = { 1, 3, 2, 5, 4 }; int[][] edges = { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; GraphAdjMat graph = new GraphAdjMat(vertices, edges); System.out.println("\n初始化後,圖為"); 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(); } } ================================================ FILE: zh-hant/codes/java/chapter_graph/graph_bfs.java ================================================ /** * 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_graph/graph_dfs.java ================================================ /** * 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_greedy/coin_change_greedy.java ================================================ /** * 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) { // 假設 coins 串列有序 int i = coins.length - 1; int count = 0; // 迴圈進行貪婪選擇,直到無剩餘金額 while (amt > 0) { // 找到小於且最接近剩餘金額的硬幣 while (i > 0 && coins[i] > amt) { i--; } // 選擇 coins[i] amt -= coins[i]; count++; } // 若未找到可行方案,則返回 -1 return amt == 0 ? count : -1; } 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"); } } ================================================ FILE: zh-hant/codes/java/chapter_greedy/fractional_knapsack.java ================================================ /** * 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) { // 建立物品串列,包含兩個屬性:重量、價值 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); } } ================================================ FILE: zh-hant/codes/java/chapter_greedy/max_capacity.java ================================================ /** * 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; // 迴圈貪婪選擇,直至兩板相遇 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); } } ================================================ FILE: zh-hant/codes/java/chapter_greedy/max_product_cutting.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/chapter_hashing/array_hash_map.java ================================================ /** * 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, "小哈"); map.put(15937, "小囉"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鴨"); System.out.println("\n新增完成後,雜湊表為\nKey -> Value"); map.print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value String name = map.get(15937); System.out.println("\n輸入學號 15937 ,查詢到姓名 " + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(10583); System.out.println("\n刪除 10583 後,雜湊表為\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); } } } ================================================ FILE: zh-hant/codes/java/chapter_hashing/built_in_hash.java ================================================ /** * 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 演算法"; int hashStr = str.hashCode(); System.out.println("字串 " + str + " 的雜湊值為 " + hashStr); Object[] arr = { 12836, "小哈" }; 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); } } ================================================ FILE: zh-hant/codes/java/chapter_hashing/hash_map.java ================================================ /** * 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, "小哈"); map.put(15937, "小囉"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鴨"); System.out.println("\n新增完成後,雜湊表為\nKey -> Value"); PrintUtil.printHashMap(map); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value String name = map.get(15937); System.out.println("\n輸入學號 15937 ,查詢到姓名 " + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(10583); System.out.println("\n刪除 10583 後,雜湊表為\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); } } } ================================================ FILE: zh-hant/codes/java/chapter_hashing/hash_map_chaining.java ================================================ /** * 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); // 走訪桶,若找到 key ,則返回對應 val for (Pair pair : bucket) { if (pair.key == key) { return pair.val; } } // 若未找到 key ,則返回 null return null; } /* 新增操作 */ void put(int key, String val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); List bucket = buckets.get(index); // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for (Pair pair : bucket) { if (pair.key == key) { pair.val = val; return; } } // 若無該 key ,則將鍵值對新增至尾部 Pair pair = new Pair(key, val); bucket.add(pair); size++; } /* 刪除操作 */ 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, "小哈"); map.put(15937, "小囉"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鴨"); System.out.println("\n新增完成後,雜湊表為\nKey -> Value"); map.print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value String name = map.get(13276); System.out.println("\n輸入學號 13276 ,查詢到姓名 " + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(12836); System.out.println("\n刪除 12836 後,雜湊表為\nKey -> Value"); map.print(); } } ================================================ FILE: zh-hant/codes/java/chapter_hashing/hash_map_open_addressing.java ================================================ /** * 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, "小哈"); hashmap.put(15937, "小囉"); hashmap.put(16750, "小算"); hashmap.put(13276, "小法"); hashmap.put(10583, "小鴨"); System.out.println("\n新增完成後,雜湊表為\nKey -> Value"); hashmap.print(); // 查詢操作 // 向雜湊表中輸入鍵 key ,得到值 val String name = hashmap.get(13276); System.out.println("\n輸入學號 13276 ,查詢到姓名 " + name); // 刪除操作 // 在雜湊表中刪除鍵值對 (key, val) hashmap.remove(16750); System.out.println("\n刪除 16750 後,雜湊表為\nKey -> Value"); hashmap.print(); } } ================================================ FILE: zh-hant/codes/java/chapter_hashing/simple_hash.java ================================================ /** * 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; } /* 互斥或雜湊 */ 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 演算法"; int hash = addHash(key); System.out.println("加法雜湊值為 " + hash); hash = mulHash(key); System.out.println("乘法雜湊值為 " + hash); hash = xorHash(key); System.out.println("互斥或雜湊值為 " + hash); hash = rotHash(key); System.out.println("旋轉雜湊值為 " + hash); } } ================================================ FILE: zh-hant/codes/java/chapter_heap/heap.java ================================================ /** * 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<>(); // 初始化大頂堆積(使用 lambda 表示式修改 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); } } ================================================ FILE: zh-hant/codes/java/chapter_heap/my_heap.java ================================================ /** * 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; // 交換兩節點 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; // 交換兩節點 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); } } ================================================ FILE: zh-hant/codes/java/chapter_heap/top_k.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/chapter_searching/binary_search.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/chapter_searching/binary_search_edge.java ================================================ /** * 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); } } } ================================================ FILE: zh-hant/codes/java/chapter_searching/binary_search_insertion.java ================================================ /** * 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); } } } ================================================ FILE: zh-hant/codes/java/chapter_searching/hashing_search.java ================================================ /** * 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) { // 雜湊表的 key: 目標元素,value: 索引 // 若雜湊表中無此 key ,返回 -1 return map.getOrDefault(target, -1); } /* 雜湊查詢(鏈結串列) */ static ListNode hashingSearchLinkedList(Map map, int target) { // 雜湊表的 key: 目標節點值,value: 節點物件 // 若雜湊表中無此 key ,返回 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); // key: 元素,value: 索引 } 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); // key: 節點值,value: 節點 head = head.next; } ListNode node = hashingSearchLinkedList(map1, target); System.out.println("目標節點值 3 的對應節點物件為 " + node); } } ================================================ FILE: zh-hant/codes/java/chapter_searching/linear_search.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/chapter_searching/two_sum.java ================================================ /** * 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) { // ======= Test Case ======= int[] nums = { 2, 7, 11, 15 }; int target = 13; // ====== Driver Code ====== // 方法一 int[] res = twoSumBruteForce(nums, target); System.out.println("方法一 res = " + Arrays.toString(res)); // 方法二 res = twoSumHashTable(nums, target); System.out.println("方法二 res = " + Arrays.toString(res)); } } ================================================ FILE: zh-hant/codes/java/chapter_sorting/bubble_sort.java ================================================ /** * 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_sorting/bucket_sort.java ================================================ /** * 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_sorting/counting_sort.java ================================================ /** * 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 是 num 在 res 中最後一次出現的索引 for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. 倒序走訪 nums ,將各元素填入結果陣列 res // 初始化陣列 res 用於記錄結果 int n = nums.length; int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // 將 num 放置到對應索引處 counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 } // 使用結果陣列 res 覆蓋原陣列 nums for (int i = 0; i < n; i++) { nums[i] = res[i]; } } 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_sorting/heap_sort.java ================================================ /** * 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; // 交換兩節點 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_sorting/insertion_sort.java ================================================ /** * 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] 向右移動一位 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_sorting/merge_sort.java ================================================ /** * 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_sorting/quick_sort.java ================================================ /** * 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); // 交換這兩個元素 } swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 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; } /* 選取三個候選元素的中位數 */ 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; } /* 哨兵劃分(三數取中值) */ static int partition(int[] 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(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); // 交換這兩個元素 } swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } /* 快速排序(遞迴深度最佳化) */ public static void quickSort(int[] 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] } } } } 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_sorting/radix_sort.java ================================================ /** * 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) { // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 return (num / exp) % 10; } /* 計數排序(根據 nums 第 k 位排序) */ static void countingSortDigit(int[] nums, int exp) { // 十進位制的位範圍為 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_sorting/selection_sort.java ================================================ /** * 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)); } } ================================================ FILE: zh-hant/codes/java/chapter_stack_and_queue/array_deque.java ================================================ /** * 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 越過陣列頭部後回到尾部 front = index(front - 1); // 將 num 新增至佇列首 nums[front] = num; queSize++; } /* 佇列尾入列 */ public void pushLast(int num) { if (queSize == capacity()) { System.out.println("雙向佇列已滿"); return; } // 計算佇列尾指標,指向佇列尾索引 + 1 int rear = index(front + queSize); // 將 num 新增至佇列尾 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() { // 僅轉換有效長度範圍內的串列元素 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) { /* 初始化雙向佇列 */ ArrayDeque deque = new ArrayDeque(10); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); 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); /* 元素入列 */ deque.pushLast(4); System.out.println("元素 4 佇列尾入列後 deque = " + Arrays.toString(deque.toArray())); deque.pushFirst(1); System.out.println("元素 1 佇列首入列後 deque = " + Arrays.toString(deque.toArray())); /* 元素出列 */ int popLast = deque.popLast(); System.out.println("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + Arrays.toString(deque.toArray())); int popFirst = deque.popFirst(); System.out.println("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + Arrays.toString(deque.toArray())); /* 獲取雙向佇列的長度 */ int size = deque.size(); System.out.println("雙向佇列長度 size = " + size); /* 判斷雙向佇列是否為空 */ boolean isEmpty = deque.isEmpty(); System.out.println("雙向佇列是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/java/chapter_stack_and_queue/array_queue.java ================================================ /** * 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; } // 計算佇列尾指標,指向佇列尾索引 + 1 // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 int rear = (front + queSize) % capacity(); // 將 num 新增至佇列尾 nums[rear] = num; queSize++; } /* 出列 */ public int pop() { int num = peek(); // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 front = (front + 1) % capacity(); queSize--; return num; } /* 訪問佇列首元素 */ public int peek() { if (isEmpty()) throw new IndexOutOfBoundsException(); return nums[front]; } /* 返回陣列 */ public int[] toArray() { // 僅轉換有效長度範圍內的串列元素 int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[j % 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 = " + pop + ",出列後 queue = " + 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); queue.pop(); System.out.println("第 " + i + " 輪入列 + 出列後 queue = " + Arrays.toString(queue.toArray())); } } } ================================================ FILE: zh-hant/codes/java/chapter_stack_and_queue/array_stack.java ================================================ /** * 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 = " + pop + ",出堆疊後 stack = " + Arrays.toString(stack.toArray())); /* 獲取堆疊的長度 */ int size = stack.size(); System.out.println("堆疊的長度 size = " + size); /* 判斷是否為空 */ boolean isEmpty = stack.isEmpty(); System.out.println("堆疊是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/java/chapter_stack_and_queue/deque.java ================================================ /** * 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 = " + deque); int popFirst = deque.pollFirst(); System.out.println("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + deque); /* 獲取雙向佇列的長度 */ int size = deque.size(); System.out.println("雙向佇列長度 size = " + size); /* 判斷雙向佇列是否為空 */ boolean isEmpty = deque.isEmpty(); System.out.println("雙向佇列是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/java/chapter_stack_and_queue/linkedlist_deque.java ================================================ /** * 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; // 更新頭節點 // 佇列尾入列操作 } else { // 將 node 新增至鏈結串列尾部 rear.next = node; node.prev = rear; rear = node; // 更新尾節點 } queSize++; // 更新佇列長度 } /* 佇列首入列 */ public void pushFirst(int num) { push(num, true); } /* 佇列尾入列 */ public void pushLast(int num) { push(num, false); } /* 出列操作 */ 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; // 更新頭節點 // 佇列尾出列操作 } else { val = rear.val; // 暫存尾節點值 // 刪除尾節點 ListNode rPrev = rear.prev; if (rPrev != null) { rPrev.next = null; rear.prev = null; } rear = rPrev; // 更新尾節點 } queSize--; // 更新佇列長度 return val; } /* 佇列首出列 */ public int popFirst() { return pop(true); } /* 佇列尾出列 */ public int popLast() { return pop(false); } /* 訪問佇列首元素 */ public int peekFirst() { if (isEmpty()) throw new 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())); /* 訪問元素 */ int peekFirst = deque.peekFirst(); System.out.println("佇列首元素 peekFirst = " + peekFirst); int peekLast = deque.peekLast(); System.out.println("佇列尾元素 peekLast = " + peekLast); /* 元素入列 */ deque.pushLast(4); System.out.println("元素 4 佇列尾入列後 deque = " + Arrays.toString(deque.toArray())); deque.pushFirst(1); System.out.println("元素 1 佇列首入列後 deque = " + Arrays.toString(deque.toArray())); /* 元素出列 */ int popLast = deque.popLast(); System.out.println("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + Arrays.toString(deque.toArray())); int popFirst = deque.popFirst(); System.out.println("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + Arrays.toString(deque.toArray())); /* 獲取雙向佇列的長度 */ int size = deque.size(); System.out.println("雙向佇列長度 size = " + size); /* 判斷雙向佇列是否為空 */ boolean isEmpty = deque.isEmpty(); System.out.println("雙向佇列是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/java/chapter_stack_and_queue/linkedlist_queue.java ================================================ /** * 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; } /* 將鏈結串列轉化為 Array 並返回 */ 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 = " + pop + ",出列後 queue = " + Arrays.toString(queue.toArray())); /* 獲取佇列的長度 */ int size = queue.size(); System.out.println("佇列長度 size = " + size); /* 判斷佇列是否為空 */ boolean isEmpty = queue.isEmpty(); System.out.println("佇列是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/java/chapter_stack_and_queue/linkedlist_stack.java ================================================ /** * 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 = " + pop + ",出堆疊後 stack = " + Arrays.toString(stack.toArray())); /* 獲取堆疊的長度 */ int size = stack.size(); System.out.println("堆疊的長度 size = " + size); /* 判斷是否為空 */ boolean isEmpty = stack.isEmpty(); System.out.println("堆疊是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/java/chapter_stack_and_queue/queue.java ================================================ /** * 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 = " + pop + ",出列後 queue = " + queue); /* 獲取佇列的長度 */ int size = queue.size(); System.out.println("佇列長度 size = " + size); /* 判斷佇列是否為空 */ boolean isEmpty = queue.isEmpty(); System.out.println("佇列是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/java/chapter_stack_and_queue/stack.java ================================================ /** * 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 = " + pop + ",出堆疊後 stack = " + stack); /* 獲取堆疊的長度 */ int size = stack.size(); System.out.println("堆疊的長度 size = " + size); /* 判斷是否為空 */ boolean isEmpty = stack.isEmpty(); System.out.println("堆疊是否為空 = " + isEmpty); } } ================================================ FILE: zh-hant/codes/java/chapter_tree/array_binary_tree.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/chapter_tree/avl_tree.java ================================================ /** * 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 ,直接刪除 node 並返回 if (child == null) return null; // 子節點數量 = 1 ,直接刪除 node else node = child; } else { // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 TreeNode temp = node.right; while (temp.left != null) { temp = temp.left; } node.right = removeHelper(node.right, temp.val); node.val = temp.val; } } updateHeight(node); // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = rotate(node); // 返回子樹的根節點 return node; } /* 查詢節點 */ 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); } } ================================================ FILE: zh-hant/codes/java/chapter_tree/binary_search_tree.java ================================================ /** * 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 or 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); // 用 tmp 覆蓋 cur 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()); } } ================================================ FILE: zh-hant/codes/java/chapter_tree/binary_tree.java ================================================ /** * 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); // 在 n1 -> n2 中間插入節點 P 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); } } ================================================ FILE: zh-hant/codes/java/chapter_tree/binary_tree_bfs.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/chapter_tree/binary_tree_dfs.java ================================================ /** * 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); } } ================================================ FILE: zh-hant/codes/java/utils/ListNode.java ================================================ /** * 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; } } ================================================ FILE: zh-hant/codes/java/utils/PrintUtil.java ================================================ /** * 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 { /* 列印矩陣(Array) */ public static void printMatrix(T[][] matrix) { System.out.println("["); for (T[] row : matrix) { System.out.println(" " + row + ","); } System.out.println("]"); } /* 列印矩陣(List) */ 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); } /** * 列印二元樹 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ public static void printTree(TreeNode root, Trunk prev, boolean isRight) { if (root == null) { return; } 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); } } ================================================ FILE: zh-hant/codes/java/utils/TreeNode.java ================================================ /** * 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; } } ================================================ FILE: zh-hant/codes/java/utils/Vertex.java ================================================ /** * 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; } } ================================================ FILE: zh-hant/codes/javascript/.prettierrc ================================================ { "tabWidth": 4, "useTabs": false, "semi": true, "singleQuote": true } ================================================ FILE: zh-hant/codes/javascript/chapter_array_and_linkedlist/array.js ================================================ /** * File: array.js * Created Time: 2022-11-27 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 隨機訪問元素 */ function randomAccess(nums) { // 在區間 [0, nums.length) 中隨機抽取一個數字 const random_index = Math.floor(Math.random() * nums.length); // 獲取並返回隨機元素 const random_num = nums[random_index]; return random_num; } /* 擴展陣列長度 */ // 請注意,JavaScript 的 Array 是動態陣列,可以直接擴展 // 為了方便學習,本函式將 Array 看作長度不可變的陣列 function extend(nums, enlarge) { // 初始化一個擴展長度後的陣列 const res = new Array(nums.length + enlarge).fill(0); // 將原陣列中的所有元素複製到新陣列 for (let i = 0; i < nums.length; i++) { res[i] = nums[i]; } // 返回擴展後的新陣列 return res; } /* 在陣列的索引 index 處插入元素 num */ function insert(nums, num, index) { // 把索引 index 以及之後的所有元素向後移動一位 for (let i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 將 num 賦給 index 處的元素 nums[index] = num; } /* 刪除索引 index 處的元素 */ function remove(nums, index) { // 把索引 index 之後的所有元素向前移動一位 for (let i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* 走訪陣列 */ function traverse(nums) { let count = 0; // 透過索引走訪陣列 for (let i = 0; i < nums.length; i++) { count += nums[i]; } // 直接走訪陣列元素 for (const num of nums) { count += num; } } /* 在陣列中查詢指定元素 */ function find(nums, target) { for (let i = 0; i < nums.length; i++) { if (nums[i] === target) return i; } return -1; } /* Driver Code */ /* 初始化陣列 */ const arr = new Array(5).fill(0); console.log('陣列 arr =', arr); let nums = [1, 3, 2, 5, 4]; console.log('陣列 nums =', nums); /* 隨機訪問 */ let random_num = randomAccess(nums); console.log('在 nums 中獲取隨機元素', random_num); /* 長度擴展 */ nums = extend(nums, 3); console.log('將陣列長度擴展至 8 ,得到 nums =', nums); /* 插入元素 */ insert(nums, 6, 3); console.log('在索引 3 處插入數字 6 ,得到 nums =', nums); /* 刪除元素 */ remove(nums, 2); console.log('刪除索引 2 處的元素,得到 nums =', nums); /* 走訪陣列 */ traverse(nums); /* 查詢元素 */ let index = find(nums, 3); console.log('在 nums 中查詢元素 3 ,得到索引 =', index); ================================================ FILE: zh-hant/codes/javascript/chapter_array_and_linkedlist/linked_list.js ================================================ /** * File: linked_list.js * Created Time: 2022-12-12 * Author: IsChristina (christinaxia77@foxmail.com), Justin (xiefahit@gmail.com) */ const { printLinkedList } = require('../modules/PrintUtil'); const { ListNode } = require('../modules/ListNode'); /* 在鏈結串列的節點 n0 之後插入節點 P */ function insert(n0, P) { const n1 = n0.next; P.next = n1; n0.next = P; } /* 刪除鏈結串列的節點 n0 之後的首個節點 */ function remove(n0) { if (!n0.next) return; // n0 -> P -> n1 const P = n0.next; const n1 = P.next; n0.next = n1; } /* 訪問鏈結串列中索引為 index 的節點 */ function access(head, index) { for (let i = 0; i < index; i++) { if (!head) { return null; } head = head.next; } return head; } /* 在鏈結串列中查詢值為 target 的首個節點 */ function find(head, target) { let index = 0; while (head !== null) { if (head.val === target) { return index; } head = head.next; index += 1; } return -1; } /* Driver Code */ /* 初始化鏈結串列 */ // 初始化各個節點 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // 構建節點之間的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; console.log('初始化的鏈結串列為'); printLinkedList(n0); /* 插入節點 */ insert(n0, new ListNode(0)); console.log('插入節點後的鏈結串列為'); printLinkedList(n0); /* 刪除節點 */ remove(n0); console.log('刪除節點後的鏈結串列為'); printLinkedList(n0); /* 訪問節點 */ const node = access(n0, 3); console.log('鏈結串列中索引 3 處的節點的值 = ' + node.val); /* 查詢節點 */ const index = find(n0, 2); console.log('鏈結串列中值為 2 的節點的索引 = ' + index); ================================================ FILE: zh-hant/codes/javascript/chapter_array_and_linkedlist/list.js ================================================ /** * File: list.js * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 初始化串列 */ const nums = [1, 3, 2, 5, 4]; console.log(`串列 nums = ${nums}`); /* 訪問元素 */ const num = nums[1]; console.log(`訪問索引 1 處的元素,得到 num = ${num}`); /* 更新元素 */ nums[1] = 0; console.log(`將索引 1 處的元素更新為 0 ,得到 nums = ${nums}`); /* 清空串列 */ nums.length = 0; console.log(`清空串列後 nums = ${nums}`); /* 在尾部新增元素 */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); console.log(`新增元素後 nums = ${nums}`); /* 在中間插入元素 */ nums.splice(3, 0, 6); console.log(`在索引 3 處插入數字 6 ,得到 nums = ${nums}`); /* 刪除元素 */ nums.splice(3, 1); console.log(`刪除索引 3 處的元素,得到 nums = ${nums}`); /* 透過索引走訪串列 */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* 直接走訪串列元素 */ count = 0; for (const x of nums) { count += x; } /* 拼接兩個串列 */ const nums1 = [6, 8, 7, 10, 9]; nums.push(...nums1); console.log(`將串列 nums1 拼接到 nums 之後,得到 nums = ${nums}`); /* 排序串列 */ nums.sort((a, b) => a - b); console.log(`排序串列後 nums = ${nums}`); ================================================ FILE: zh-hant/codes/javascript/chapter_array_and_linkedlist/my_list.js ================================================ /** * File: my_list.js * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 串列類別 */ class MyList { #arr = new Array(); // 陣列(儲存串列元素) #capacity = 10; // 串列容量 #size = 0; // 串列長度(當前元素數量) #extendRatio = 2; // 每次串列擴容的倍數 /* 建構子 */ constructor() { this.#arr = new Array(this.#capacity); } /* 獲取串列長度(當前元素數量)*/ size() { return this.#size; } /* 獲取串列容量 */ capacity() { return this.#capacity; } /* 訪問元素 */ get(index) { // 索引如果越界,則丟擲異常,下同 if (index < 0 || index >= this.#size) throw new Error('索引越界'); return this.#arr[index]; } /* 更新元素 */ set(index, num) { if (index < 0 || index >= this.#size) throw new Error('索引越界'); this.#arr[index] = num; } /* 在尾部新增元素 */ add(num) { // 如果長度等於容量,則需要擴容 if (this.#size === this.#capacity) { this.extendCapacity(); } // 將新元素新增到串列尾部 this.#arr[this.#size] = num; this.#size++; } /* 在中間插入元素 */ insert(index, num) { if (index < 0 || index >= this.#size) throw new Error('索引越界'); // 元素數量超出容量時,觸發擴容機制 if (this.#size === this.#capacity) { this.extendCapacity(); } // 將索引 index 以及之後的元素都向後移動一位 for (let j = this.#size - 1; j >= index; j--) { this.#arr[j + 1] = this.#arr[j]; } // 更新元素數量 this.#arr[index] = num; this.#size++; } /* 刪除元素 */ remove(index) { if (index < 0 || index >= this.#size) throw new Error('索引越界'); let num = this.#arr[index]; // 將索引 index 之後的元素都向前移動一位 for (let j = index; j < this.#size - 1; j++) { this.#arr[j] = this.#arr[j + 1]; } // 更新元素數量 this.#size--; // 返回被刪除的元素 return num; } /* 串列擴容 */ extendCapacity() { // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列 this.#arr = this.#arr.concat( new Array(this.capacity() * (this.#extendRatio - 1)) ); // 更新串列容量 this.#capacity = this.#arr.length; } /* 將串列轉換為陣列 */ toArray() { let size = this.size(); // 僅轉換有效長度範圍內的串列元素 const arr = new Array(size); for (let i = 0; i < size; i++) { arr[i] = this.get(i); } return arr; } } /* Driver Code */ /* 初始化串列 */ const nums = new MyList(); /* 在尾部新增元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); console.log( `串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}` ); /* 在中間插入元素 */ nums.insert(3, 6); console.log(`在索引 3 處插入數字 6 ,得到 nums = ${nums.toArray()}`); /* 刪除元素 */ nums.remove(3); console.log(`刪除索引 3 處的元素,得到 nums = ${nums.toArray()}`); /* 訪問元素 */ const num = nums.get(1); console.log(`訪問索引 1 處的元素,得到 num = ${num}`); /* 更新元素 */ nums.set(1, 0); console.log(`將索引 1 處的元素更新為 0 ,得到 nums = ${nums.toArray()}`); /* 測試擴容機制 */ for (let i = 0; i < 10; i++) { // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 nums.add(i); } console.log( `擴容後的串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}` ); ================================================ FILE: zh-hant/codes/javascript/chapter_backtracking/n_queens.js ================================================ /** * File: n_queens.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* 回溯演算法:n 皇后 */ function backtrack(row, n, state, res, cols, diags1, diags2) { // 當放置完所有行時,記錄解 if (row === n) { res.push(state.map((row) => row.slice())); return; } // 走訪所有列 for (let col = 0; col < n; col++) { // 計算該格子對應的主對角線和次對角線 const diag1 = row - col + n - 1; const diag2 = row + col; // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 嘗試:將皇后放置在該格子 state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // 放置下一行 backtrack(row + 1, n, state, res, cols, diags1, diags2); // 回退:將該格子恢復為空位 state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* 求解 n 皇后 */ function nQueens(n) { // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 const state = Array.from({ length: n }, () => Array(n).fill('#')); const cols = Array(n).fill(false); // 記錄列是否有皇后 const diags1 = Array(2 * n - 1).fill(false); // 記錄主對角線上是否有皇后 const diags2 = Array(2 * n - 1).fill(false); // 記錄次對角線上是否有皇后 const res = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } // Driver Code const n = 4; const res = nQueens(n); console.log(`輸入棋盤長寬為 ${n}`); console.log(`皇后放置方案共有 ${res.length} 種`); res.forEach((state) => { console.log('--------------------'); state.forEach((row) => console.log(row)); }); ================================================ FILE: zh-hant/codes/javascript/chapter_backtracking/permutations_i.js ================================================ /** * File: permutations_i.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* 回溯演算法:全排列 I */ function backtrack(state, choices, selected, res) { // 當狀態長度等於元素數量時,記錄解 if (state.length === choices.length) { res.push([...state]); return; } // 走訪所有選擇 choices.forEach((choice, i) => { // 剪枝:不允許重複選擇元素 if (!selected[i]) { // 嘗試:做出選擇,更新狀態 selected[i] = true; state.push(choice); // 進行下一輪選擇 backtrack(state, choices, selected, res); // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false; state.pop(); } }); } /* 全排列 I */ function permutationsI(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums = [1, 2, 3]; const res = permutationsI(nums); console.log(`輸入陣列 nums = ${JSON.stringify(nums)}`); console.log(`所有排列 res = ${JSON.stringify(res)}`); ================================================ FILE: zh-hant/codes/javascript/chapter_backtracking/permutations_ii.js ================================================ /** * File: permutations_ii.js * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* 回溯演算法:全排列 II */ function backtrack(state, choices, selected, res) { // 當狀態長度等於元素數量時,記錄解 if (state.length === choices.length) { res.push([...state]); return; } // 走訪所有選擇 const duplicated = new Set(); choices.forEach((choice, i) => { // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 if (!selected[i] && !duplicated.has(choice)) { // 嘗試:做出選擇,更新狀態 duplicated.add(choice); // 記錄選擇過的元素值 selected[i] = true; state.push(choice); // 進行下一輪選擇 backtrack(state, choices, selected, res); // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false; state.pop(); } }); } /* 全排列 II */ function permutationsII(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums = [1, 2, 2]; const res = permutationsII(nums); console.log(`輸入陣列 nums = ${JSON.stringify(nums)}`); console.log(`所有排列 res = ${JSON.stringify(res)}`); ================================================ FILE: zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js ================================================ /** * File: preorder_traversal_i_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 前序走訪:例題一 */ function preOrder(root, res) { if (root === null) { return; } if (root.val === 7) { // 記錄解 res.push(root); } preOrder(root.left, res); preOrder(root.right, res); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二元樹'); printTree(root); // 前序走訪 const res = []; preOrder(root, res); console.log('\n輸出所有值為 7 的節點'); console.log(res.map((node) => node.val)); ================================================ FILE: zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js ================================================ /** * File: preorder_traversal_ii_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 前序走訪:例題二 */ function preOrder(root, path, res) { if (root === null) { return; } // 嘗試 path.push(root); if (root.val === 7) { // 記錄解 res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // 回退 path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二元樹'); printTree(root); // 前序走訪 const path = []; const res = []; preOrder(root, path, res); console.log('\n輸出所有根節點到節點 7 的路徑'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js ================================================ /** * File: preorder_traversal_iii_compact.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 前序走訪:例題三 */ function preOrder(root, path, res) { // 剪枝 if (root === null || root.val === 3) { return; } // 嘗試 path.push(root); if (root.val === 7) { // 記錄解 res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // 回退 path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二元樹'); printTree(root); // 前序走訪 const path = []; const res = []; preOrder(root, path, res); console.log('\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js ================================================ /** * File: preorder_traversal_iii_template.js * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 判斷當前狀態是否為解 */ function isSolution(state) { return state && state[state.length - 1]?.val === 7; } /* 記錄解 */ function recordSolution(state, res) { res.push([...state]); } /* 判斷在當前狀態下,該選擇是否合法 */ function isValid(state, choice) { return choice !== null && choice.val !== 3; } /* 更新狀態 */ function makeChoice(state, choice) { state.push(choice); } /* 恢復狀態 */ function undoChoice(state) { state.pop(); } /* 回溯演算法:例題三 */ function backtrack(state, choices, res) { // 檢查是否為解 if (isSolution(state)) { // 記錄解 recordSolution(state, res); } // 走訪所有選擇 for (const choice of choices) { // 剪枝:檢查選擇是否合法 if (isValid(state, choice)) { // 嘗試:做出選擇,更新狀態 makeChoice(state, choice); // 進行下一輪選擇 backtrack(state, [choice.left, choice.right], res); // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state); } } } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二元樹'); printTree(root); // 回溯演算法 const res = []; backtrack([], [root], res); console.log('\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); ================================================ FILE: zh-hant/codes/javascript/chapter_backtracking/subset_sum_i.js ================================================ /** * File: subset_sum_i.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯演算法:子集和 I */ function backtrack(state, target, choices, start, res) { // 子集和等於 target 時,記錄解 if (target === 0) { res.push([...state]); return; } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 for (let i = start; i < choices.length; i++) { // 剪枝一:若子集和超過 target ,則直接結束迴圈 // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target if (target - choices[i] < 0) { break; } // 嘗試:做出選擇,更新 target, start state.push(choices[i]); // 進行下一輪選擇 backtrack(state, target - choices[i], choices, i, res); // 回退:撤銷選擇,恢復到之前的狀態 state.pop(); } } /* 求解子集和 I */ function subsetSumI(nums, target) { const state = []; // 狀態(子集) nums.sort((a, b) => a - b); // 對 nums 進行排序 const start = 0; // 走訪起始點 const res = []; // 結果串列(子集串列) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumI(nums, target); console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); ================================================ FILE: zh-hant/codes/javascript/chapter_backtracking/subset_sum_i_naive.js ================================================ /** * File: subset_sum_i_naive.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯演算法:子集和 I */ function backtrack(state, target, total, choices, res) { // 子集和等於 target 時,記錄解 if (total === target) { res.push([...state]); return; } // 走訪所有選擇 for (let i = 0; i < choices.length; i++) { // 剪枝:若子集和超過 target ,則跳過該選擇 if (total + choices[i] > target) { continue; } // 嘗試:做出選擇,更新元素和 total state.push(choices[i]); // 進行下一輪選擇 backtrack(state, target, total + choices[i], choices, res); // 回退:撤銷選擇,恢復到之前的狀態 state.pop(); } } /* 求解子集和 I(包含重複子集) */ function subsetSumINaive(nums, target) { const state = []; // 狀態(子集) const total = 0; // 子集和 const res = []; // 結果串列(子集串列) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumINaive(nums, target); console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); console.log('請注意,該方法輸出的結果包含重複集合'); ================================================ FILE: zh-hant/codes/javascript/chapter_backtracking/subset_sum_ii.js ================================================ /** * File: subset_sum_ii.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯演算法:子集和 II */ function backtrack(state, target, choices, start, res) { // 子集和等於 target 時,記錄解 if (target === 0) { res.push([...state]); return; } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 for (let 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.push(choices[i]); // 進行下一輪選擇 backtrack(state, target - choices[i], choices, i + 1, res); // 回退:撤銷選擇,恢復到之前的狀態 state.pop(); } } /* 求解子集和 II */ function subsetSumII(nums, target) { const state = []; // 狀態(子集) nums.sort((a, b) => a - b); // 對 nums 進行排序 const start = 0; // 走訪起始點 const res = []; // 結果串列(子集串列) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [4, 4, 5]; const target = 9; const res = subsetSumII(nums, target); console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); ================================================ FILE: zh-hant/codes/javascript/chapter_computational_complexity/iteration.js ================================================ /** * File: iteration.js * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* for 迴圈 */ function forLoop(n) { let res = 0; // 迴圈求和 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { res += i; } return res; } /* while 迴圈 */ function whileLoop(n) { let res = 0; let i = 1; // 初始化條件變數 // 迴圈求和 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // 更新條件變數 } return res; } /* while 迴圈(兩次更新) */ function whileLoopII(n) { let res = 0; let i = 1; // 初始化條件變數 // 迴圈求和 1, 4, 10, ... while (i <= n) { res += i; // 更新條件變數 i++; i *= 2; } return res; } /* 雙層 for 迴圈 */ function nestedForLoop(n) { let res = ''; // 迴圈 i = 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { // 迴圈 j = 1, 2, ..., n-1, n for (let j = 1; j <= n; j++) { res += `(${i}, ${j}), `; } } return res; } /* Driver Code */ const n = 5; let res; res = forLoop(n); console.log(`for 迴圈的求和結果 res = ${res}`); res = whileLoop(n); console.log(`while 迴圈的求和結果 res = ${res}`); res = whileLoopII(n); console.log(`while 迴圈(兩次更新)求和結果 res = ${res}`); const resStr = nestedForLoop(n); console.log(`雙層 for 迴圈的走訪結果 ${resStr}`); ================================================ FILE: zh-hant/codes/javascript/chapter_computational_complexity/recursion.js ================================================ /** * File: recursion.js * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 遞迴 */ function recur(n) { // 終止條件 if (n === 1) return 1; // 遞:遞迴呼叫 const res = recur(n - 1); // 迴:返回結果 return n + res; } /* 使用迭代模擬遞迴 */ function forLoopRecur(n) { // 使用一個顯式的堆疊來模擬系統呼叫堆疊 const stack = []; let res = 0; // 遞:遞迴呼叫 for (let i = n; i > 0; i--) { // 透過“入堆疊操作”模擬“遞” stack.push(i); } // 迴:返回結果 while (stack.length) { // 透過“出堆疊操作”模擬“迴” res += stack.pop(); } // res = 1+2+3+...+n return res; } /* 尾遞迴 */ function tailRecur(n, res) { // 終止條件 if (n === 0) return res; // 尾遞迴呼叫 return tailRecur(n - 1, res + n); } /* 費波那契數列:遞迴 */ function fib(n) { // 終止條件 f(1) = 0, f(2) = 1 if (n === 1 || n === 2) return n - 1; // 遞迴呼叫 f(n) = f(n-1) + f(n-2) const res = fib(n - 1) + fib(n - 2); // 返回結果 f(n) return res; } /* Driver Code */ const n = 5; let res; res = recur(n); console.log(`遞迴函式的求和結果 res = ${res}`); res = forLoopRecur(n); console.log(`使用迭代模擬遞迴的求和結果 res = ${res}`); res = tailRecur(n, 0); console.log(`尾遞迴函式的求和結果 res = ${res}`); res = fib(n); console.log(`費波那契數列的第 ${n} 項為 ${res}`); ================================================ FILE: zh-hant/codes/javascript/chapter_computational_complexity/space_complexity.js ================================================ /** * File: space_complexity.js * Created Time: 2023-02-05 * Author: Justin (xiefahit@gmail.com) */ const { ListNode } = require('../modules/ListNode'); const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 函式 */ function constFunc() { // 執行某些操作 return 0; } /* 常數階 */ function constant(n) { // 常數、變數、物件佔用 O(1) 空間 const a = 0; const b = 0; const nums = new Array(10000); const node = new ListNode(0); // 迴圈中的變數佔用 O(1) 空間 for (let i = 0; i < n; i++) { const c = 0; } // 迴圈中的函式佔用 O(1) 空間 for (let i = 0; i < n; i++) { constFunc(); } } /* 線性階 */ function linear(n) { // 長度為 n 的陣列佔用 O(n) 空間 const nums = new Array(n); // 長度為 n 的串列佔用 O(n) 空間 const nodes = []; for (let i = 0; i < n; i++) { nodes.push(new ListNode(i)); } // 長度為 n 的雜湊表佔用 O(n) 空間 const map = new Map(); for (let i = 0; i < n; i++) { map.set(i, i.toString()); } } /* 線性階(遞迴實現) */ function linearRecur(n) { console.log(`遞迴 n = ${n}`); if (n === 1) return; linearRecur(n - 1); } /* 平方階 */ function quadratic(n) { // 矩陣佔用 O(n^2) 空間 const numMatrix = Array(n) .fill(null) .map(() => Array(n).fill(null)); // 二維串列佔用 O(n^2) 空間 const numList = []; for (let i = 0; i < n; i++) { const tmp = []; for (let j = 0; j < n; j++) { tmp.push(0); } numList.push(tmp); } } /* 平方階(遞迴實現) */ function quadraticRecur(n) { if (n <= 0) return 0; const nums = new Array(n); console.log(`遞迴 n = ${n} 中的 nums 長度 = ${nums.length}`); return quadraticRecur(n - 1); } /* 指數階(建立滿二元樹) */ function buildTree(n) { if (n === 0) return null; const root = new TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ const n = 5; // 常數階 constant(n); // 線性階 linear(n); linearRecur(n); // 平方階 quadratic(n); quadraticRecur(n); // 指數階 const root = buildTree(n); printTree(root); ================================================ FILE: zh-hant/codes/javascript/chapter_computational_complexity/time_complexity.js ================================================ /** * File: time_complexity.js * Created Time: 2023-01-02 * Author: RiverTwilight (contact@rene.wang) */ /* 常數階 */ function constant(n) { let count = 0; const size = 100000; for (let i = 0; i < size; i++) count++; return count; } /* 線性階 */ function linear(n) { let count = 0; for (let i = 0; i < n; i++) count++; return count; } /* 線性階(走訪陣列) */ function arrayTraversal(nums) { let count = 0; // 迴圈次數與陣列長度成正比 for (let i = 0; i < nums.length; i++) { count++; } return count; } /* 平方階 */ function quadratic(n) { let count = 0; // 迴圈次數與資料大小 n 成平方關係 for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { count++; } } return count; } /* 平方階(泡沫排序) */ function bubbleSort(nums) { let count = 0; // 計數器 // 外迴圈:未排序區間為 [0, i] for (let i = nums.length - 1; i > 0; i--) { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 元素交換包含 3 個單元操作 } } } return count; } /* 指數階(迴圈實現) */ function exponential(n) { let count = 0, base = 1; // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) for (let i = 0; i < n; i++) { for (let j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指數階(遞迴實現) */ function expRecur(n) { if (n === 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 對數階(迴圈實現) */ function logarithmic(n) { let count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* 對數階(遞迴實現) */ function logRecur(n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* 線性對數階 */ function linearLogRecur(n) { if (n <= 1) return 1; let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (let i = 0; i < n; i++) { count++; } return count; } /* 階乘階(遞迴實現) */ function factorialRecur(n) { if (n === 0) return 1; let count = 0; // 從 1 個分裂出 n 個 for (let i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 const n = 8; console.log('輸入資料大小 n = ' + n); let count = constant(n); console.log('常數階的操作數量 = ' + count); count = linear(n); console.log('線性階的操作數量 = ' + count); count = arrayTraversal(new Array(n)); console.log('線性階(走訪陣列)的操作數量 = ' + count); count = quadratic(n); console.log('平方階的操作數量 = ' + count); let nums = new Array(n); for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); console.log('平方階(泡沫排序)的操作數量 = ' + count); count = exponential(n); console.log('指數階(迴圈實現)的操作數量 = ' + count); count = expRecur(n); console.log('指數階(遞迴實現)的操作數量 = ' + count); count = logarithmic(n); console.log('對數階(迴圈實現)的操作數量 = ' + count); count = logRecur(n); console.log('對數階(遞迴實現)的操作數量 = ' + count); count = linearLogRecur(n); console.log('線性對數階(遞迴實現)的操作數量 = ' + count); count = factorialRecur(n); console.log('階乘階(遞迴實現)的操作數量 = ' + count); ================================================ FILE: zh-hant/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js ================================================ /** * File: worst_best_time_complexity.js * Created Time: 2023-01-05 * Author: RiverTwilight (contact@rene.wang) */ /* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ function randomNumbers(n) { const nums = Array(n); // 生成陣列 nums = { 1, 2, 3, ..., n } for (let i = 0; i < n; i++) { nums[i] = i + 1; } // 隨機打亂陣列元素 for (let i = 0; i < n; i++) { const r = Math.floor(Math.random() * (i + 1)); const temp = nums[i]; nums[i] = nums[r]; nums[r] = temp; } return nums; } /* 查詢陣列 nums 中數字 1 所在索引 */ function findOne(nums) { for (let i = 0; i < nums.length; i++) { // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) if (nums[i] === 1) { return i; } } return -1; } /* Driver Code */ for (let i = 0; i < 10; i++) { const n = 100; const nums = randomNumbers(n); const index = findOne(nums); console.log('\n陣列 [ 1, 2, ..., n ] 被打亂後 = [' + nums.join(', ') + ']'); console.log('數字 1 的索引為 ' + index); } ================================================ FILE: zh-hant/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js ================================================ /** * File: binary_search_recur.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 二分搜尋:問題 f(i, j) */ function dfs(nums, target, i, j) { // 若區間為空,代表無目標元素,則返回 -1 if (i > j) { return -1; } // 計算中點索引 m const m = i + ((j - i) >> 1); if (nums[m] < target) { // 遞迴子問題 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 遞迴子問題 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目標元素,返回其索引 return m; } } /* 二分搜尋 */ function binarySearch(nums, target) { const n = nums.length; // 求解問題 f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分搜尋(雙閉區間) const index = binarySearch(nums, target); console.log(`目標元素 6 的索引 = ${index}`); ================================================ FILE: zh-hant/codes/javascript/chapter_divide_and_conquer/build_tree.js ================================================ /** * File: build_tree.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ const { printTree } = require('../modules/PrintUtil'); const { TreeNode } = require('../modules/TreeNode'); /* 構建二元樹:分治 */ function dfs(preorder, inorderMap, i, l, r) { // 子樹區間為空時終止 if (r - l < 0) return null; // 初始化根節點 const root = new TreeNode(preorder[i]); // 查詢 m ,從而劃分左右子樹 const m = inorderMap.get(preorder[i]); // 子問題:構建左子樹 root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // 子問題:構建右子樹 root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // 返回根節點 return root; } /* 構建二元樹 */ function buildTree(preorder, inorder) { // 初始化雜湊表,儲存 inorder 元素到索引的對映 let inorderMap = new Map(); for (let i = 0; i < inorder.length; i++) { inorderMap.set(inorder[i], i); } const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ const preorder = [3, 9, 2, 1, 7]; const inorder = [9, 3, 1, 2, 7]; console.log('前序走訪 = ' + JSON.stringify(preorder)); console.log('中序走訪 = ' + JSON.stringify(inorder)); const root = buildTree(preorder, inorder); console.log('構建的二元樹為:'); printTree(root); ================================================ FILE: zh-hant/codes/javascript/chapter_divide_and_conquer/hanota.js ================================================ /** * File: hanota.js * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 移動一個圓盤 */ function move(src, tar) { // 從 src 頂部拿出一個圓盤 const pan = src.pop(); // 將圓盤放入 tar 頂部 tar.push(pan); } /* 求解河內塔問題 f(i) */ function dfs(i, src, buf, tar) { // 若 src 只剩下一個圓盤,則直接將其移到 tar if (i === 1) { move(src, tar); return; } // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf dfs(i - 1, src, tar, buf); // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar move(src, tar); // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar dfs(i - 1, buf, src, tar); } /* 求解河內塔問題 */ function solveHanota(A, B, C) { const n = A.length; // 將 A 頂部 n 個圓盤藉助 B 移到 C dfs(n, A, B, C); } /* Driver Code */ // 串列尾部是柱子頂部 const A = [5, 4, 3, 2, 1]; const B = []; const C = []; console.log('初始狀態下:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); solveHanota(A, B, C); console.log('圓盤移動完成後:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); ================================================ FILE: zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js ================================================ /** * File: climbing_stairs_backtrack.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯 */ function backtrack(choices, state, n, res) { // 當爬到第 n 階時,方案數量加 1 if (state === n) res.set(0, res.get(0) + 1); // 走訪所有選擇 for (const choice of choices) { // 剪枝:不允許越過第 n 階 if (state + choice > n) continue; // 嘗試:做出選擇,更新狀態 backtrack(choices, state + choice, n, res); // 回退 } } /* 爬樓梯:回溯 */ function climbingStairsBacktrack(n) { const choices = [1, 2]; // 可選擇向上爬 1 階或 2 階 const state = 0; // 從第 0 階開始爬 const res = new Map(); res.set(0, 0); // 使用 res[0] 記錄方案數量 backtrack(choices, state, n, res); return res.get(0); } /* Driver Code */ const n = 9; const res = climbingStairsBacktrack(n); console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); ================================================ FILE: zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js ================================================ /** * File: climbing_stairs_constraint_dp.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 帶約束爬樓梯:動態規劃 */ function climbingStairsConstraintDP(n) { if (n === 1 || n === 2) { return 1; } // 初始化 dp 表,用於儲存子問題的解 const dp = Array.from(new Array(n + 1), () => new Array(3)); // 初始狀態:預設最小子問題的解 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 狀態轉移:從較小子問題逐步求解較大子問題 for (let i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ const n = 9; const res = climbingStairsConstraintDP(n); console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); ================================================ FILE: zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js ================================================ /** * File: climbing_stairs_dfs.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 搜尋 */ function dfs(i) { // 已知 dp[1] 和 dp[2] ,返回之 if (i === 1 || i === 2) return i; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1) + dfs(i - 2); return count; } /* 爬樓梯:搜尋 */ function climbingStairsDFS(n) { return dfs(n); } /* Driver Code */ const n = 9; const res = climbingStairsDFS(n); console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); ================================================ FILE: zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js ================================================ /** * File: climbing_stairs_dfs_mem.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 記憶化搜尋 */ function dfs(i, mem) { // 已知 dp[1] 和 dp[2] ,返回之 if (i === 1 || i === 2) return i; // 若存在記錄 dp[i] ,則直接返回之 if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1, mem) + dfs(i - 2, mem); // 記錄 dp[i] mem[i] = count; return count; } /* 爬樓梯:記憶化搜尋 */ function climbingStairsDFSMem(n) { // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 const mem = new Array(n + 1).fill(-1); return dfs(n, mem); } /* Driver Code */ const n = 9; const res = climbingStairsDFSMem(n); console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); ================================================ FILE: zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js ================================================ /** * File: climbing_stairs_dp.js * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 爬樓梯:動態規劃 */ function climbingStairsDP(n) { if (n === 1 || n === 2) return n; // 初始化 dp 表,用於儲存子問題的解 const dp = new Array(n + 1).fill(-1); // 初始狀態:預設最小子問題的解 dp[1] = 1; dp[2] = 2; // 狀態轉移:從較小子問題逐步求解較大子問題 for (let i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* 爬樓梯:空間最佳化後的動態規劃 */ function climbingStairsDPComp(n) { if (n === 1 || n === 2) return n; let a = 1, b = 2; for (let i = 3; i <= n; i++) { const tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ const n = 9; let res = climbingStairsDP(n); console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); res = climbingStairsDPComp(n); console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); ================================================ FILE: zh-hant/codes/javascript/chapter_dynamic_programming/coin_change.js ================================================ /** * File: coin_change.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 零錢兌換:動態規劃 */ function coinChangeDP(coins, amt) { const n = coins.length; const MAX = amt + 1; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 狀態轉移:首行首列 for (let a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 狀態轉移:其餘行和列 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] !== MAX ? dp[n][amt] : -1; } /* 零錢兌換:空間最佳化後的動態規劃 */ function coinChangeDPComp(coins, amt) { const n = coins.length; const MAX = amt + 1; // 初始化 dp 表 const dp = Array.from({ length: amt + 1 }, () => MAX); dp[0] = 0; // 狀態轉移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] !== MAX ? dp[amt] : -1; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 4; // 動態規劃 let res = coinChangeDP(coins, amt); console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); // 空間最佳化後的動態規劃 res = coinChangeDPComp(coins, amt); console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); ================================================ FILE: zh-hant/codes/javascript/chapter_dynamic_programming/coin_change_ii.js ================================================ /** * File: coin_change_ii.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 零錢兌換 II:動態規劃 */ function coinChangeIIDP(coins, amt) { const n = coins.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 初始化首列 for (let i = 0; i <= n; i++) { dp[i][0] = 1; } // 狀態轉移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a]; } else { // 不選和選硬幣 i 這兩種方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* 零錢兌換 II:空間最佳化後的動態規劃 */ function coinChangeIIDPComp(coins, amt) { const n = coins.length; // 初始化 dp 表 const dp = Array.from({ length: amt + 1 }, () => 0); dp[0] = 1; // 狀態轉移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 5; // 動態規劃 let res = coinChangeIIDP(coins, amt); console.log(`湊出目標金額的硬幣組合數量為 ${res}`); // 空間最佳化後的動態規劃 res = coinChangeIIDPComp(coins, amt); console.log(`湊出目標金額的硬幣組合數量為 ${res}`); ================================================ FILE: zh-hant/codes/javascript/chapter_dynamic_programming/edit_distance.js ================================================ /** * File: edit_distance.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 編輯距離:暴力搜尋 */ function editDistanceDFS(s, t, i, j) { // 若 s 和 t 都為空,則返回 0 if (i === 0 && j === 0) return 0; // 若 s 為空,則返回 t 長度 if (i === 0) return j; // 若 t 為空,則返回 s 長度 if (j === 0) return i; // 若兩字元相等,則直接跳過此兩字元 if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 const insert = editDistanceDFS(s, t, i, j - 1); const del = editDistanceDFS(s, t, i - 1, j); const replace = editDistanceDFS(s, t, i - 1, j - 1); // 返回最少編輯步數 return Math.min(insert, del, replace) + 1; } /* 編輯距離:記憶化搜尋 */ function editDistanceDFSMem(s, t, mem, i, j) { // 若 s 和 t 都為空,則返回 0 if (i === 0 && j === 0) return 0; // 若 s 為空,則返回 t 長度 if (i === 0) return j; // 若 t 為空,則返回 s 長度 if (j === 0) return i; // 若已有記錄,則直接返回之 if (mem[i][j] !== -1) return mem[i][j]; // 若兩字元相等,則直接跳過此兩字元 if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 const insert = editDistanceDFSMem(s, t, mem, i, j - 1); const del = editDistanceDFSMem(s, t, mem, i - 1, j); const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 記錄並返回最少編輯步數 mem[i][j] = Math.min(insert, del, replace) + 1; return mem[i][j]; } /* 編輯距離:動態規劃 */ function editDistanceDP(s, t) { const n = s.length, m = t.length; const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); // 狀態轉移:首行首列 for (let i = 1; i <= n; i++) { dp[i][0] = i; } for (let j = 1; j <= m; j++) { dp[0][j] = j; } // 狀態轉移:其餘行和列 for (let i = 1; i <= n; i++) { for (let j = 1; j <= m; j++) { if (s.charAt(i - 1) === t.charAt(j - 1)) { // 若兩字元相等,則直接跳過此兩字元 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* 編輯距離:空間最佳化後的動態規劃 */ function editDistanceDPComp(s, t) { const n = s.length, m = t.length; const dp = new Array(m + 1).fill(0); // 狀態轉移:首行 for (let j = 1; j <= m; j++) { dp[j] = j; } // 狀態轉移:其餘行 for (let i = 1; i <= n; i++) { // 狀態轉移:首列 let leftup = dp[0]; // 暫存 dp[i-1, j-1] dp[0] = i; // 狀態轉移:其餘列 for (let j = 1; j <= m; j++) { const temp = dp[j]; if (s.charAt(i - 1) === t.charAt(j - 1)) { // 若兩字元相等,則直接跳過此兩字元 dp[j] = leftup; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; } leftup = temp; // 更新為下一輪的 dp[i-1, j-1] } } return dp[m]; } const s = 'bag'; const t = 'pack'; const n = s.length, m = t.length; // 暴力搜尋 let res = editDistanceDFS(s, t, n, m); console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); // 記憶化搜尋 const mem = Array.from(new Array(n + 1), () => new Array(m + 1).fill(-1)); res = editDistanceDFSMem(s, t, mem, n, m); console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); // 動態規劃 res = editDistanceDP(s, t); console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); // 空間最佳化後的動態規劃 res = editDistanceDPComp(s, t); console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); ================================================ FILE: zh-hant/codes/javascript/chapter_dynamic_programming/knapsack.js ================================================ /** * File: knapsack.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 0-1 背包:暴力搜尋 */ function knapsackDFS(wgt, val, i, c) { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i === 0 || c === 0) { return 0; } // 若超過背包容量,則只能選擇不放入背包 if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 const no = knapsackDFS(wgt, val, i - 1, c); const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // 返回兩種方案中價值更大的那一個 return Math.max(no, yes); } /* 0-1 背包:記憶化搜尋 */ function knapsackDFSMem(wgt, val, mem, i, c) { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i === 0 || c === 0) { return 0; } // 若已有記錄,則直接返回 if (mem[i][c] !== -1) { return mem[i][c]; } // 若超過背包容量,則只能選擇不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 const no = knapsackDFSMem(wgt, val, mem, i - 1, c); const yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 記錄並返回兩種方案中價值更大的那一個 mem[i][c] = Math.max(no, yes); return mem[i][c]; } /* 0-1 背包:動態規劃 */ function knapsackDP(wgt, val, cap) { const n = wgt.length; // 初始化 dp 表 const dp = Array(n + 1) .fill(0) .map(() => Array(cap + 1).fill(0)); // 狀態轉移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = Math.max( dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* 0-1 背包:空間最佳化後的動態規劃 */ function knapsackDPComp(wgt, val, cap) { const n = wgt.length; // 初始化 dp 表 const dp = Array(cap + 1).fill(0); // 狀態轉移 for (let i = 1; i <= n; i++) { // 倒序走訪 for (let c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // 不選和選物品 i 這兩種方案的較大值 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // 暴力搜尋 let res = knapsackDFS(wgt, val, n, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); // 記憶化搜尋 const mem = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => -1) ); res = knapsackDFSMem(wgt, val, mem, n, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); // 動態規劃 res = knapsackDP(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); // 空間最佳化後的動態規劃 res = knapsackDPComp(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); ================================================ FILE: zh-hant/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js ================================================ /** * File: min_cost_climbing_stairs_dp.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 爬樓梯最小代價:動態規劃 */ function minCostClimbingStairsDP(cost) { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } // 初始化 dp 表,用於儲存子問題的解 const dp = new Array(n + 1); // 初始狀態:預設最小子問題的解 dp[1] = cost[1]; dp[2] = cost[2]; // 狀態轉移:從較小子問題逐步求解較大子問題 for (let i = 3; i <= n; i++) { dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* 爬樓梯最小代價:空間最佳化後的動態規劃 */ function minCostClimbingStairsDPComp(cost) { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } let a = cost[1], b = cost[2]; for (let i = 3; i <= n; i++) { const tmp = b; b = Math.min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; console.log('輸入樓梯的代價串列為:', cost); let res = minCostClimbingStairsDP(cost); console.log(`爬完樓梯的最低代價為:${res}`); res = minCostClimbingStairsDPComp(cost); console.log(`爬完樓梯的最低代價為:${res}`); ================================================ FILE: zh-hant/codes/javascript/chapter_dynamic_programming/min_path_sum.js ================================================ /** * File: min_path_sum.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 最小路徑和:暴力搜尋 */ function minPathSumDFS(grid, i, j) { // 若為左上角單元格,則終止搜尋 if (i === 0 && j === 0) { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 || j < 0) { return Infinity; } // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 const up = minPathSumDFS(grid, i - 1, j); const left = minPathSumDFS(grid, i, j - 1); // 返回從左上角到 (i, j) 的最小路徑代價 return Math.min(left, up) + grid[i][j]; } /* 最小路徑和:記憶化搜尋 */ function minPathSumDFSMem(grid, mem, i, j) { // 若為左上角單元格,則終止搜尋 if (i === 0 && j === 0) { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 || j < 0) { return Infinity; } // 若已有記錄,則直接返回 if (mem[i][j] !== -1) { return mem[i][j]; } // 左邊和上邊單元格的最小路徑代價 const up = minPathSumDFSMem(grid, mem, i - 1, j); const left = minPathSumDFSMem(grid, mem, i, j - 1); // 記錄並返回左上角到 (i, j) 的最小路徑代價 mem[i][j] = Math.min(left, up) + grid[i][j]; return mem[i][j]; } /* 最小路徑和:動態規劃 */ function minPathSumDP(grid) { const n = grid.length, m = grid[0].length; // 初始化 dp 表 const dp = Array.from({ length: n }, () => Array.from({ length: m }, () => 0) ); dp[0][0] = grid[0][0]; // 狀態轉移:首行 for (let j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 狀態轉移:首列 for (let i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 狀態轉移:其餘行和列 for (let i = 1; i < n; i++) { for (let j = 1; j < m; j++) { dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* 最小路徑和:空間最佳化後的動態規劃 */ function minPathSumDPComp(grid) { const n = grid.length, m = grid[0].length; // 初始化 dp 表 const dp = new Array(m); // 狀態轉移:首行 dp[0] = grid[0][0]; for (let j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 狀態轉移:其餘行 for (let i = 1; i < n; i++) { // 狀態轉移:首列 dp[0] = dp[0] + grid[i][0]; // 狀態轉移:其餘列 for (let j = 1; j < m; j++) { dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ const grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; const n = grid.length, m = grid[0].length; // 暴力搜尋 let res = minPathSumDFS(grid, n - 1, m - 1); console.log(`從左上角到右下角的最小路徑和為 ${res}`); // 記憶化搜尋 const mem = Array.from({ length: n }, () => Array.from({ length: m }, () => -1) ); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); console.log(`從左上角到右下角的最小路徑和為 ${res}`); // 動態規劃 res = minPathSumDP(grid); console.log(`從左上角到右下角的最小路徑和為 ${res}`); // 空間最佳化後的動態規劃 res = minPathSumDPComp(grid); console.log(`從左上角到右下角的最小路徑和為 ${res}`); ================================================ FILE: zh-hant/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js ================================================ /** * File: unbounded_knapsack.js * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 完全背包:動態規劃 */ function unboundedKnapsackDP(wgt, val, cap) { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // 狀態轉移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = Math.max( dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* 完全背包:空間最佳化後的動態規劃 */ function unboundedKnapsackDPComp(wgt, val, cap) { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: cap + 1 }, () => 0); // 狀態轉移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[c] = dp[c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [1, 2, 3]; const val = [5, 11, 15]; const cap = 4; // 動態規劃 let res = unboundedKnapsackDP(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); // 空間最佳化後的動態規劃 res = unboundedKnapsackDPComp(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); ================================================ FILE: zh-hant/codes/javascript/chapter_graph/graph_adjacency_list.js ================================================ /** * File: graph_adjacency_list.js * Created Time: 2023-02-09 * Author: Justin (xiefahit@gmail.com) */ const { Vertex } = require('../modules/Vertex'); /* 基於鄰接表實現的無向圖類別 */ class GraphAdjList { // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 adjList; /* 建構子 */ constructor(edges) { this.adjList = new Map(); // 新增所有頂點和邊 for (const edge of edges) { this.addVertex(edge[0]); this.addVertex(edge[1]); this.addEdge(edge[0], edge[1]); } } /* 獲取頂點數量 */ size() { return this.adjList.size; } /* 新增邊 */ addEdge(vet1, vet2) { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 ) { throw new Error('Illegal Argument Exception'); } // 新增邊 vet1 - vet2 this.adjList.get(vet1).push(vet2); this.adjList.get(vet2).push(vet1); } /* 刪除邊 */ removeEdge(vet1, vet2) { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 || this.adjList.get(vet1).indexOf(vet2) === -1 ) { throw new Error('Illegal Argument Exception'); } // 刪除邊 vet1 - vet2 this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); } /* 新增頂點 */ addVertex(vet) { if (this.adjList.has(vet)) return; // 在鄰接表中新增一個新鏈結串列 this.adjList.set(vet, []); } /* 刪除頂點 */ removeVertex(vet) { if (!this.adjList.has(vet)) { throw new Error('Illegal Argument Exception'); } // 在鄰接表中刪除頂點 vet 對應的鏈結串列 this.adjList.delete(vet); // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 for (const set of this.adjList.values()) { const index = set.indexOf(vet); if (index > -1) { set.splice(index, 1); } } } /* 列印鄰接表 */ print() { console.log('鄰接表 ='); for (const [key, value] of this.adjList) { const tmp = []; for (const vertex of value) { tmp.push(vertex.val); } console.log(key.val + ': ' + tmp.join()); } } } if (require.main === module) { /* Driver Code */ /* 初始化無向圖 */ const v0 = new Vertex(1), v1 = new Vertex(3), v2 = new Vertex(2), v3 = new Vertex(5), v4 = new Vertex(4); const edges = [ [v0, v1], [v1, v2], [v2, v3], [v0, v3], [v2, v4], [v3, v4], ]; const graph = new GraphAdjList(edges); console.log('\n初始化後,圖為'); graph.print(); /* 新增邊 */ // 頂點 1, 2 即 v0, v2 graph.addEdge(v0, v2); console.log('\n新增邊 1-2 後,圖為'); graph.print(); /* 刪除邊 */ // 頂點 1, 3 即 v0, v1 graph.removeEdge(v0, v1); console.log('\n刪除邊 1-3 後,圖為'); graph.print(); /* 新增頂點 */ const v5 = new Vertex(6); graph.addVertex(v5); console.log('\n新增頂點 6 後,圖為'); graph.print(); /* 刪除頂點 */ // 頂點 3 即 v1 graph.removeVertex(v1); console.log('\n刪除頂點 3 後,圖為'); graph.print(); } module.exports = { GraphAdjList, }; ================================================ FILE: zh-hant/codes/javascript/chapter_graph/graph_adjacency_matrix.js ================================================ /** * File: graph_adjacency_matrix.js * Created Time: 2023-02-09 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 基於鄰接矩陣實現的無向圖類別 */ class GraphAdjMat { vertices; // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” adjMat; // 鄰接矩陣,行列索引對應“頂點索引” /* 建構子 */ constructor(vertices, edges) { this.vertices = []; this.adjMat = []; // 新增頂點 for (const val of vertices) { this.addVertex(val); } // 新增邊 // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 for (const e of edges) { this.addEdge(e[0], e[1]); } } /* 獲取頂點數量 */ size() { return this.vertices.length; } /* 新增頂點 */ addVertex(val) { const n = this.size(); // 向頂點串列中新增新頂點的值 this.vertices.push(val); // 在鄰接矩陣中新增一行 const newRow = []; for (let j = 0; j < n; j++) { newRow.push(0); } this.adjMat.push(newRow); // 在鄰接矩陣中新增一列 for (const row of this.adjMat) { row.push(0); } } /* 刪除頂點 */ removeVertex(index) { if (index >= this.size()) { throw new RangeError('Index Out Of Bounds Exception'); } // 在頂點串列中移除索引 index 的頂點 this.vertices.splice(index, 1); // 在鄰接矩陣中刪除索引 index 的行 this.adjMat.splice(index, 1); // 在鄰接矩陣中刪除索引 index 的列 for (const row of this.adjMat) { row.splice(index, 1); } } /* 新增邊 */ // 參數 i, j 對應 vertices 元素索引 addEdge(i, j) { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) === (j, i) this.adjMat[i][j] = 1; this.adjMat[j][i] = 1; } /* 刪除邊 */ // 參數 i, j 對應 vertices 元素索引 removeEdge(i, j) { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } this.adjMat[i][j] = 0; this.adjMat[j][i] = 0; } /* 列印鄰接矩陣 */ print() { console.log('頂點串列 = ', this.vertices); console.log('鄰接矩陣 =', this.adjMat); } } /* Driver Code */ /* 初始化無向圖 */ // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 const vertices = [1, 3, 2, 5, 4]; const edges = [ [0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4], ]; const graph = new GraphAdjMat(vertices, edges); console.log('\n初始化後,圖為'); graph.print(); /* 新增邊 */ // 頂點 1, 2 的索引分別為 0, 2 graph.addEdge(0, 2); console.log('\n新增邊 1-2 後,圖為'); graph.print(); /* 刪除邊 */ // 頂點 1, 3 的索引分別為 0, 1 graph.removeEdge(0, 1); console.log('\n刪除邊 1-3 後,圖為'); graph.print(); /* 新增頂點 */ graph.addVertex(6); console.log('\n新增頂點 6 後,圖為'); graph.print(); /* 刪除頂點 */ // 頂點 3 的索引為 1 graph.removeVertex(1); console.log('\n刪除頂點 3 後,圖為'); graph.print(); ================================================ FILE: zh-hant/codes/javascript/chapter_graph/graph_bfs.js ================================================ /** * File: graph_bfs.js * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { GraphAdjList } = require('./graph_adjacency_list'); const { Vertex } = require('../modules/Vertex'); /* 廣度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 function graphBFS(graph, startVet) { // 頂點走訪序列 const res = []; // 雜湊集合,用於記錄已被訪問過的頂點 const visited = new Set(); visited.add(startVet); // 佇列用於實現 BFS const que = [startVet]; // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 while (que.length) { const vet = que.shift(); // 佇列首頂點出隊 res.push(vet); // 記錄訪問頂點 // 走訪該頂點的所有鄰接頂點 for (const adjVet of graph.adjList.get(vet) ?? []) { if (visited.has(adjVet)) { continue; // 跳過已被訪問的頂點 } que.push(adjVet); // 只入列未訪問的頂點 visited.add(adjVet); // 標記該頂點已被訪問 } } // 返回頂點走訪序列 return res; } /* Driver Code */ /* 初始化無向圖 */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; const graph = new GraphAdjList(edges); console.log('\n初始化後,圖為'); graph.print(); /* 廣度優先走訪 */ const res = graphBFS(graph, v[0]); console.log('\n廣度優先走訪(BFS)頂點序列為'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: zh-hant/codes/javascript/chapter_graph/graph_dfs.js ================================================ /** * File: graph_dfs.js * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { Vertex } = require('../modules/Vertex'); const { GraphAdjList } = require('./graph_adjacency_list'); /* 深度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 function dfs(graph, visited, res, vet) { res.push(vet); // 記錄訪問頂點 visited.add(vet); // 標記該頂點已被訪問 // 走訪該頂點的所有鄰接頂點 for (const adjVet of graph.adjList.get(vet)) { if (visited.has(adjVet)) { continue; // 跳過已被訪問的頂點 } // 遞迴訪問鄰接頂點 dfs(graph, visited, res, adjVet); } } /* 深度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 function graphDFS(graph, startVet) { // 頂點走訪序列 const res = []; // 雜湊集合,用於記錄已被訪問過的頂點 const visited = new Set(); dfs(graph, visited, res, startVet); return res; } /* Driver Code */ /* 初始化無向圖 */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; const graph = new GraphAdjList(edges); console.log('\n初始化後,圖為'); graph.print(); /* 深度優先走訪 */ const res = graphDFS(graph, v[0]); console.log('\n深度優先走訪(DFS)頂點序列為'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: zh-hant/codes/javascript/chapter_greedy/coin_change_greedy.js ================================================ /** * File: coin_change_greedy.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 零錢兌換:貪婪 */ function coinChangeGreedy(coins, amt) { // 假設 coins 陣列有序 let i = coins.length - 1; let count = 0; // 迴圈進行貪婪選擇,直到無剩餘金額 while (amt > 0) { // 找到小於且最接近剩餘金額的硬幣 while (i > 0 && coins[i] > amt) { i--; } // 選擇 coins[i] amt -= coins[i]; count++; } // 若未找到可行方案,則返回 -1 return amt === 0 ? count : -1; } /* Driver Code */ // 貪婪:能夠保證找到全域性最優解 let coins = [1, 5, 10, 20, 50, 100]; let amt = 186; let res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); // 貪婪:無法保證找到全域性最優解 coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); console.log('實際上需要的最少數量為 3 ,即 20 + 20 + 20'); // 貪婪:無法保證找到全域性最優解 coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); console.log('實際上需要的最少數量為 2 ,即 49 + 49'); ================================================ FILE: zh-hant/codes/javascript/chapter_greedy/fractional_knapsack.js ================================================ /** * File: fractional_knapsack.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 物品 */ class Item { constructor(w, v) { this.w = w; // 物品重量 this.v = v; // 物品價值 } } /* 分數背包:貪婪 */ function fractionalKnapsack(wgt, val, cap) { // 建立物品串列,包含兩個屬性:重量、價值 const items = wgt.map((w, i) => new Item(w, val[i])); // 按照單位價值 item.v / item.w 從高到低進行排序 items.sort((a, b) => b.v / b.w - a.v / a.w); // 迴圈貪婪選擇 let res = 0; for (const item of items) { if (item.w <= cap) { // 若剩餘容量充足,則將當前物品整個裝進背包 res += item.v; cap -= item.w; } else { // 若剩餘容量不足,則將當前物品的一部分裝進背包 res += (item.v / item.w) * cap; // 已無剩餘容量,因此跳出迴圈 break; } } return res; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // 貪婪演算法 const res = fractionalKnapsack(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); ================================================ FILE: zh-hant/codes/javascript/chapter_greedy/max_capacity.js ================================================ /** * File: max_capacity.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 最大容量:貪婪 */ function maxCapacity(ht) { // 初始化 i, j,使其分列陣列兩端 let i = 0, j = ht.length - 1; // 初始最大容量為 0 let res = 0; // 迴圈貪婪選擇,直至兩板相遇 while (i < j) { // 更新最大容量 const cap = Math.min(ht[i], ht[j]) * (j - i); res = Math.max(res, cap); // 向內移動短板 if (ht[i] < ht[j]) { i += 1; } else { j -= 1; } } return res; } /* Driver Code */ const ht = [3, 8, 5, 2, 7, 7, 3, 4]; // 貪婪演算法 const res = maxCapacity(ht); console.log(`最大容量為 ${res}`); ================================================ FILE: zh-hant/codes/javascript/chapter_greedy/max_product_cutting.js ================================================ /** * File: max_product_cutting.js * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 最大切分乘積:貪婪 */ function maxProductCutting(n) { // 當 n <= 3 時,必須切分出一個 1 if (n <= 3) { return 1 * (n - 1); } // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 let a = Math.floor(n / 3); let b = n % 3; if (b === 1) { // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 return Math.pow(3, a - 1) * 2 * 2; } if (b === 2) { // 當餘數為 2 時,不做處理 return Math.pow(3, a) * 2; } // 當餘數為 0 時,不做處理 return Math.pow(3, a); } /* Driver Code */ let n = 58; // 貪婪演算法 let res = maxProductCutting(n); console.log(`最大切分乘積為 ${res}`); ================================================ FILE: zh-hant/codes/javascript/chapter_hashing/array_hash_map.js ================================================ /** * File: array_hash_map.js * Created Time: 2022-12-26 * Author: Justin (xiefahit@gmail.com) */ /* 鍵值對 Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* 基於陣列實現的雜湊表 */ class ArrayHashMap { #buckets; constructor() { // 初始化陣列,包含 100 個桶 this.#buckets = new Array(100).fill(null); } /* 雜湊函式 */ #hashFunc(key) { return key % 100; } /* 查詢操作 */ get(key) { let index = this.#hashFunc(key); let pair = this.#buckets[index]; if (pair === null) return null; return pair.val; } /* 新增操作 */ set(key, val) { let index = this.#hashFunc(key); this.#buckets[index] = new Pair(key, val); } /* 刪除操作 */ delete(key) { let index = this.#hashFunc(key); // 置為 null ,代表刪除 this.#buckets[index] = null; } /* 獲取所有鍵值對 */ entries() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i]); } } return arr; } /* 獲取所有鍵 */ keys() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i].key); } } return arr; } /* 獲取所有值 */ values() { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { arr.push(this.#buckets[i].val); } } return arr; } /* 列印雜湊表 */ print() { let pairSet = this.entries(); for (const pair of pairSet) { console.info(`${pair.key} -> ${pair.val}`); } } } /* Driver Code */ /* 初始化雜湊表 */ const map = new ArrayHashMap(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.set(12836, '小哈'); map.set(15937, '小囉'); map.set(16750, '小算'); map.set(13276, '小法'); map.set(10583, '小鴨'); console.info('\n新增完成後,雜湊表為\nKey -> Value'); map.print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value let name = map.get(15937); console.info('\n輸入學號 15937 ,查詢到姓名 ' + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.delete(10583); console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); map.print(); /* 走訪雜湊表 */ console.info('\n走訪鍵值對 Key->Value'); for (const pair of map.entries()) { if (!pair) continue; console.info(pair.key + ' -> ' + pair.val); } console.info('\n單獨走訪鍵 Key'); for (const key of map.keys()) { console.info(key); } console.info('\n單獨走訪值 Value'); for (const val of map.values()) { console.info(val); } ================================================ FILE: zh-hant/codes/javascript/chapter_hashing/hash_map.js ================================================ /** * File: hash_map.js * Created Time: 2022-12-26 * Author: Justin (xiefahit@gmail.com) */ /* Driver Code */ /* 初始化雜湊表 */ const map = new Map(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.set(12836, '小哈'); map.set(15937, '小囉'); map.set(16750, '小算'); map.set(13276, '小法'); map.set(10583, '小鴨'); console.info('\n新增完成後,雜湊表為\nKey -> Value'); console.info(map); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value let name = map.get(15937); console.info('\n輸入學號 15937 ,查詢到姓名 ' + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.delete(10583); console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); console.info(map); /* 走訪雜湊表 */ console.info('\n走訪鍵值對 Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\n單獨走訪鍵 Key'); for (const k of map.keys()) { console.info(k); } console.info('\n單獨走訪值 Value'); for (const v of map.values()) { console.info(v); } ================================================ FILE: zh-hant/codes/javascript/chapter_hashing/hash_map_chaining.js ================================================ /** * File: hash_map_chaining.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 鍵值對 Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* 鏈式位址雜湊表 */ class HashMapChaining { #size; // 鍵值對數量 #capacity; // 雜湊表容量 #loadThres; // 觸發擴容的負載因子閾值 #extendRatio; // 擴容倍數 #buckets; // 桶陣列 /* 建構子 */ constructor() { this.#size = 0; this.#capacity = 4; this.#loadThres = 2.0 / 3.0; this.#extendRatio = 2; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); } /* 雜湊函式 */ #hashFunc(key) { return key % this.#capacity; } /* 負載因子 */ #loadFactor() { return this.#size / this.#capacity; } /* 查詢操作 */ get(key) { const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // 走訪桶,若找到 key ,則返回對應 val for (const pair of bucket) { if (pair.key === key) { return pair.val; } } // 若未找到 key ,則返回 null return null; } /* 新增操作 */ put(key, val) { // 當負載因子超過閾值時,執行擴容 if (this.#loadFactor() > this.#loadThres) { this.#extend(); } const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for (const pair of bucket) { if (pair.key === key) { pair.val = val; return; } } // 若無該 key ,則將鍵值對新增至尾部 const pair = new Pair(key, val); bucket.push(pair); this.#size++; } /* 刪除操作 */ remove(key) { const index = this.#hashFunc(key); let bucket = this.#buckets[index]; // 走訪桶,從中刪除鍵值對 for (let i = 0; i < bucket.length; i++) { if (bucket[i].key === key) { bucket.splice(i, 1); this.#size--; break; } } } /* 擴容雜湊表 */ #extend() { // 暫存原雜湊表 const bucketsTmp = this.#buckets; // 初始化擴容後的新雜湊表 this.#capacity *= this.#extendRatio; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); this.#size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (const bucket of bucketsTmp) { for (const pair of bucket) { this.put(pair.key, pair.val); } } } /* 列印雜湊表 */ print() { for (const bucket of this.#buckets) { let res = []; for (const pair of bucket) { res.push(pair.key + ' -> ' + pair.val); } console.log(res); } } } /* Driver Code */ /* 初始化雜湊表 */ const map = new HashMapChaining(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(12836, '小哈'); map.put(15937, '小囉'); map.put(16750, '小算'); map.put(13276, '小法'); map.put(10583, '小鴨'); console.log('\n新增完成後,雜湊表為\nKey -> Value'); map.print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value const name = map.get(13276); console.log('\n輸入學號 13276 ,查詢到姓名 ' + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(12836); console.log('\n刪除 12836 後,雜湊表為\nKey -> Value'); map.print(); ================================================ FILE: zh-hant/codes/javascript/chapter_hashing/hash_map_open_addressing.js ================================================ /** * File: hashMapOpenAddressing.js * Created Time: 2023-06-13 * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) */ /* 鍵值對 Number -> String */ class Pair { constructor(key, val) { this.key = key; this.val = val; } } /* 開放定址雜湊表 */ class HashMapOpenAddressing { #size; // 鍵值對數量 #capacity; // 雜湊表容量 #loadThres; // 觸發擴容的負載因子閾值 #extendRatio; // 擴容倍數 #buckets; // 桶陣列 #TOMBSTONE; // 刪除標記 /* 建構子 */ constructor() { this.#size = 0; // 鍵值對數量 this.#capacity = 4; // 雜湊表容量 this.#loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 this.#extendRatio = 2; // 擴容倍數 this.#buckets = Array(this.#capacity).fill(null); // 桶陣列 this.#TOMBSTONE = new Pair(-1, '-1'); // 刪除標記 } /* 雜湊函式 */ #hashFunc(key) { return key % this.#capacity; } /* 負載因子 */ #loadFactor() { return this.#size / this.#capacity; } /* 搜尋 key 對應的桶索引 */ #findBucket(key) { let index = this.#hashFunc(key); let firstTombstone = -1; // 線性探查,當遇到空桶時跳出 while (this.#buckets[index] !== null) { // 若遇到 key ,返回對應的桶索引 if (this.#buckets[index].key === key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone !== -1) { this.#buckets[firstTombstone] = this.#buckets[index]; this.#buckets[index] = this.#TOMBSTONE; return firstTombstone; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if ( firstTombstone === -1 && this.#buckets[index] === this.#TOMBSTONE ) { firstTombstone = index; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % this.#capacity; } // 若 key 不存在,則返回新增點的索引 return firstTombstone === -1 ? index : firstTombstone; } /* 查詢操作 */ get(key) { // 搜尋 key 對應的桶索引 const index = this.#findBucket(key); // 若找到鍵值對,則返回對應 val if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { return this.#buckets[index].val; } // 若鍵值對不存在,則返回 null return null; } /* 新增操作 */ put(key, val) { // 當負載因子超過閾值時,執行擴容 if (this.#loadFactor() > this.#loadThres) { this.#extend(); } // 搜尋 key 對應的桶索引 const index = this.#findBucket(key); // 若找到鍵值對,則覆蓋 val 並返回 if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { this.#buckets[index].val = val; return; } // 若鍵值對不存在,則新增該鍵值對 this.#buckets[index] = new Pair(key, val); this.#size++; } /* 刪除操作 */ remove(key) { // 搜尋 key 對應的桶索引 const index = this.#findBucket(key); // 若找到鍵值對,則用刪除標記覆蓋它 if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { this.#buckets[index] = this.#TOMBSTONE; this.#size--; } } /* 擴容雜湊表 */ #extend() { // 暫存原雜湊表 const bucketsTmp = this.#buckets; // 初始化擴容後的新雜湊表 this.#capacity *= this.#extendRatio; this.#buckets = Array(this.#capacity).fill(null); this.#size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (const pair of bucketsTmp) { if (pair !== null && pair !== this.#TOMBSTONE) { this.put(pair.key, pair.val); } } } /* 列印雜湊表 */ print() { for (const pair of this.#buckets) { if (pair === null) { console.log('null'); } else if (pair === this.#TOMBSTONE) { console.log('TOMBSTONE'); } else { console.log(pair.key + ' -> ' + pair.val); } } } } /* Driver Code */ // 初始化雜湊表 const hashmap = new HashMapOpenAddressing(); // 新增操作 // 在雜湊表中新增鍵值對 (key, val) hashmap.put(12836, '小哈'); hashmap.put(15937, '小囉'); hashmap.put(16750, '小算'); hashmap.put(13276, '小法'); hashmap.put(10583, '小鴨'); console.log('\n新增完成後,雜湊表為\nKey -> Value'); hashmap.print(); // 查詢操作 // 向雜湊表中輸入鍵 key ,得到值 val const name = hashmap.get(13276); console.log('\n輸入學號 13276 ,查詢到姓名 ' + name); // 刪除操作 // 在雜湊表中刪除鍵值對 (key, val) hashmap.remove(16750); console.log('\n刪除 16750 後,雜湊表為\nKey -> Value'); hashmap.print(); ================================================ FILE: zh-hant/codes/javascript/chapter_hashing/simple_hash.js ================================================ /** * File: simple_hash.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 加法雜湊 */ function addHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* 乘法雜湊 */ function mulHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (31 * hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* 互斥或雜湊 */ function xorHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash ^= c.charCodeAt(0); } return hash % MODULUS; } /* 旋轉雜湊 */ function rotHash(key) { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; } return hash; } /* Driver Code */ const key = 'Hello 演算法'; let hash = addHash(key); console.log('加法雜湊值為 ' + hash); hash = mulHash(key); console.log('乘法雜湊值為 ' + hash); hash = xorHash(key); console.log('互斥或雜湊值為 ' + hash); hash = rotHash(key); console.log('旋轉雜湊值為 ' + hash); ================================================ FILE: zh-hant/codes/javascript/chapter_heap/my_heap.js ================================================ /** * File: my_heap.js * Created Time: 2023-02-06 * Author: what-is-me (whatisme@outlook.jp) */ const { printHeap } = require('../modules/PrintUtil'); /* 最大堆積類別 */ class MaxHeap { #maxHeap; /* 建構子,建立空堆積或根據輸入串列建堆積 */ constructor(nums) { // 將串列元素原封不動新增進堆積 this.#maxHeap = nums === undefined ? [] : [...nums]; // 堆積化除葉節點以外的其他所有節點 for (let i = this.#parent(this.size() - 1); i >= 0; i--) { this.#siftDown(i); } } /* 獲取左子節點的索引 */ #left(i) { return 2 * i + 1; } /* 獲取右子節點的索引 */ #right(i) { return 2 * i + 2; } /* 獲取父節點的索引 */ #parent(i) { return Math.floor((i - 1) / 2); // 向下整除 } /* 交換元素 */ #swap(i, j) { const tmp = this.#maxHeap[i]; this.#maxHeap[i] = this.#maxHeap[j]; this.#maxHeap[j] = tmp; } /* 獲取堆積大小 */ size() { return this.#maxHeap.length; } /* 判斷堆積是否為空 */ isEmpty() { return this.size() === 0; } /* 訪問堆積頂元素 */ peek() { return this.#maxHeap[0]; } /* 元素入堆積 */ push(val) { // 新增節點 this.#maxHeap.push(val); // 從底至頂堆積化 this.#siftUp(this.size() - 1); } /* 從節點 i 開始,從底至頂堆積化 */ #siftUp(i) { while (true) { // 獲取節點 i 的父節點 const p = this.#parent(i); // 當“越過根節點”或“節點無須修復”時,結束堆積化 if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break; // 交換兩節點 this.#swap(i, p); // 迴圈向上堆積化 i = p; } } /* 元素出堆積 */ pop() { // 判空處理 if (this.isEmpty()) throw new Error('堆積為空'); // 交換根節點與最右葉節點(交換首元素與尾元素) this.#swap(0, this.size() - 1); // 刪除節點 const val = this.#maxHeap.pop(); // 從頂至底堆積化 this.#siftDown(0); // 返回堆積頂元素 return val; } /* 從節點 i 開始,從頂至底堆積化 */ #siftDown(i) { while (true) { // 判斷節點 i, l, r 中值最大的節點,記為 ma const l = this.#left(i), r = this.#right(i); let ma = i; if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l; if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r; // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if (ma === i) break; // 交換兩節點 this.#swap(i, ma); // 迴圈向下堆積化 i = ma; } } /* 列印堆積(二元樹) */ print() { printHeap(this.#maxHeap); } /* 取出堆積中元素 */ getMaxHeap() { return this.#maxHeap; } } /* Driver Code */ if (require.main === module) { /* 初始化大頂堆積 */ const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); console.log('\n輸入串列並建堆積後'); maxHeap.print(); /* 獲取堆積頂元素 */ let peek = maxHeap.peek(); console.log(`\n堆積頂元素為 ${peek}`); /* 元素入堆積 */ let val = 7; maxHeap.push(val); console.log(`\n元素 ${val} 入堆積後`); maxHeap.print(); /* 堆積頂元素出堆積 */ peek = maxHeap.pop(); console.log(`\n堆積頂元素 ${peek} 出堆積後`); maxHeap.print(); /* 獲取堆積大小 */ let size = maxHeap.size(); console.log(`\n堆積元素數量為 ${size}`); /* 判斷堆積是否為空 */ let isEmpty = maxHeap.isEmpty(); console.log(`\n堆積是否為空 ${isEmpty}`); } module.exports = { MaxHeap, }; ================================================ FILE: zh-hant/codes/javascript/chapter_heap/top_k.js ================================================ /** * File: top_k.js * Created Time: 2023-08-13 * Author: Justin (xiefahit@gmail.com) */ const { MaxHeap } = require('./my_heap'); /* 元素入堆積 */ function pushMinHeap(maxHeap, val) { // 元素取反 maxHeap.push(-val); } /* 元素出堆積 */ function popMinHeap(maxHeap) { // 元素取反 return -maxHeap.pop(); } /* 訪問堆積頂元素 */ function peekMinHeap(maxHeap) { // 元素取反 return -maxHeap.peek(); } /* 取出堆積中元素 */ function getMinHeap(maxHeap) { // 元素取反 return maxHeap.getMaxHeap().map((num) => -num); } /* 基於堆積查詢陣列中最大的 k 個元素 */ function topKHeap(nums, k) { // 初始化小頂堆積 // 請注意:我們將堆積中所有元素取反,從而用大頂堆積來模擬小頂堆積 const maxHeap = new MaxHeap([]); // 將陣列的前 k 個元素入堆積 for (let i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // 從第 k+1 個元素開始,保持堆積的長度為 k for (let i = k; i < nums.length; i++) { // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } // 返回堆積中元素 return getMinHeap(maxHeap); } /* Driver Code */ const nums = [1, 7, 6, 3, 2]; const k = 3; const res = topKHeap(nums, k); console.log(`最大的 ${k} 個元素為`, res); ================================================ FILE: zh-hant/codes/javascript/chapter_searching/binary_search.js ================================================ /** * File: binary_search.js * Created Time: 2022-12-22 * Author: JoseHung (szhong@link.cuhk.edu.hk) */ /* 二分搜尋(雙閉區間) */ function binarySearch(nums, target) { // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 let i = 0, j = nums.length - 1; // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) while (i <= j) { // 計算中點索引 m ,使用 parseInt() 向下取整 const m = parseInt(i + (j - i) / 2); if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j] 中 i = m + 1; else if (nums[m] > target) // 此情況說明 target 在區間 [i, m-1] 中 j = m - 1; else return m; // 找到目標元素,返回其索引 } // 未找到目標元素,返回 -1 return -1; } /* 二分搜尋(左閉右開區間) */ function binarySearchLCRO(nums, target) { // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 let i = 0, j = nums.length; // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) while (i < j) { // 計算中點索引 m ,使用 parseInt() 向下取整 const m = parseInt(i + (j - i) / 2); if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j) 中 i = m + 1; else if (nums[m] > target) // 此情況說明 target 在區間 [i, m) 中 j = m; // 找到目標元素,返回其索引 else return m; } // 未找到目標元素,返回 -1 return -1; } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分搜尋(雙閉區間) */ let index = binarySearch(nums, target); console.log('目標元素 6 的索引 = ' + index); /* 二分搜尋(左閉右開區間) */ index = binarySearchLCRO(nums, target); console.log('目標元素 6 的索引 = ' + index); ================================================ FILE: zh-hant/codes/javascript/chapter_searching/binary_search_edge.js ================================================ /** * File: binary_search_edge.js * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ const { binarySearchInsertion } = require('./binary_search_insertion.js'); /* 二分搜尋最左一個 target */ function binarySearchLeftEdge(nums, target) { // 等價於查詢 target 的插入點 const i = binarySearchInsertion(nums, target); // 未找到 target ,返回 -1 if (i === nums.length || nums[i] !== target) { return -1; } // 找到 target ,返回索引 i return i; } /* 二分搜尋最右一個 target */ function binarySearchRightEdge(nums, target) { // 轉化為查詢最左一個 target + 1 const i = binarySearchInsertion(nums, target + 1); // j 指向最右一個 target ,i 指向首個大於 target 的元素 const j = i - 1; // 未找到 target ,返回 -1 if (j === -1 || nums[j] !== target) { return -1; } // 找到 target ,返回索引 j return j; } /* Driver Code */ // 包含重複元素的陣列 const nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\n陣列 nums = ' + nums); // 二分搜尋左邊界和右邊界 for (const target of [6, 7]) { let index = binarySearchLeftEdge(nums, target); console.log('最左一個元素 ' + target + ' 的索引為 ' + index); index = binarySearchRightEdge(nums, target); console.log('最右一個元素 ' + target + ' 的索引為 ' + index); } ================================================ FILE: zh-hant/codes/javascript/chapter_searching/binary_search_insertion.js ================================================ /** * File: binary_search_insertion.js * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 二分搜尋插入點(無重複元素) */ function binarySearchInsertionSimple(nums, target) { let i = 0, j = nums.length - 1; // 初始化雙閉區間 [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // 計算中點索引 m, 使用 Math.floor() 向下取整 if (nums[m] < target) { i = m + 1; // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在區間 [i, m-1] 中 } else { return m; // 找到 target ,返回插入點 m } } // 未找到 target ,返回插入點 i return i; } /* 二分搜尋插入點(存在重複元素) */ function binarySearchInsertion(nums, target) { let i = 0, j = nums.length - 1; // 初始化雙閉區間 [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // 計算中點索引 m, 使用 Math.floor() 向下取整 if (nums[m] < target) { i = m + 1; // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在區間 [i, m-1] 中 } else { j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 } } // 返回插入點 i return i; } /* Driver Code */ // 無重複元素的陣列 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; console.log('\n陣列 nums = ' + nums); // 二分搜尋插入點 for (const target of [6, 9]) { const index = binarySearchInsertionSimple(nums, target); console.log('元素 ' + target + ' 的插入點的索引為 ' + index); } // 包含重複元素的陣列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\n陣列 nums = ' + nums); // 二分搜尋插入點 for (const target of [2, 6, 20]) { const index = binarySearchInsertion(nums, target); console.log('元素 ' + target + ' 的插入點的索引為 ' + index); } module.exports = { binarySearchInsertion, }; ================================================ FILE: zh-hant/codes/javascript/chapter_searching/hashing_search.js ================================================ /** * File: hashing_search.js * Created Time: 2022-12-29 * Author: Zhuo Qinyue (1403450829@qq.com) */ const { arrToLinkedList } = require('../modules/ListNode'); /* 雜湊查詢(陣列) */ function hashingSearchArray(map, target) { // 雜湊表的 key: 目標元素,value: 索引 // 若雜湊表中無此 key ,返回 -1 return map.has(target) ? map.get(target) : -1; } /* 雜湊查詢(鏈結串列) */ function hashingSearchLinkedList(map, target) { // 雜湊表的 key: 目標節點值,value: 節點物件 // 若雜湊表中無此 key ,返回 null return map.has(target) ? map.get(target) : null; } /* Driver Code */ const target = 3; /* 雜湊查詢(陣列) */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // 初始化雜湊表 const map = new Map(); for (let i = 0; i < nums.length; i++) { map.set(nums[i], i); // key: 元素,value: 索引 } const index = hashingSearchArray(map, target); console.log('目標元素 3 的索引 = ' + index); /* 雜湊查詢(鏈結串列) */ let head = arrToLinkedList(nums); // 初始化雜湊表 const map1 = new Map(); while (head != null) { map1.set(head.val, head); // key: 節點值,value: 節點 head = head.next; } const node = hashingSearchLinkedList(map1, target); console.log('目標節點值 3 的對應節點物件為', node); ================================================ FILE: zh-hant/codes/javascript/chapter_searching/linear_search.js ================================================ /** * File: linear_search.js * Created Time: 2022-12-22 * Author: JoseHung (szhong@link.cuhk.edu.hk) */ const { ListNode, arrToLinkedList } = require('../modules/ListNode'); /* 線性查詢(陣列) */ function linearSearchArray(nums, target) { // 走訪陣列 for (let i = 0; i < nums.length; i++) { // 找到目標元素,返回其索引 if (nums[i] === target) { return i; } } // 未找到目標元素,返回 -1 return -1; } /* 線性查詢(鏈結串列)*/ function linearSearchLinkedList(head, target) { // 走訪鏈結串列 while (head) { // 找到目標節點,返回之 if (head.val === target) { return head; } head = head.next; } // 未找到目標節點,返回 null return null; } /* Driver Code */ const target = 3; /* 在陣列中執行線性查詢 */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; const index = linearSearchArray(nums, target); console.log('目標元素 3 的索引 = ' + index); /* 在鏈結串列中執行線性查詢 */ const head = arrToLinkedList(nums); const node = linearSearchLinkedList(head, target); console.log('目標節點值 3 的對應節點物件為 ', node); ================================================ FILE: zh-hant/codes/javascript/chapter_searching/two_sum.js ================================================ /** * File: two_sum.js * Created Time: 2022-12-15 * Author: gyt95 (gytkwan@gmail.com) */ /* 方法一:暴力列舉 */ function twoSumBruteForce(nums, target) { const n = nums.length; // 兩層迴圈,時間複雜度為 O(n^2) for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { if (nums[i] + nums[j] === target) { return [i, j]; } } } return []; } /* 方法二:輔助雜湊表 */ function twoSumHashTable(nums, target) { // 輔助雜湊表,空間複雜度為 O(n) let m = {}; // 單層迴圈,時間複雜度為 O(n) for (let i = 0; i < nums.length; i++) { if (m[target - nums[i]] !== undefined) { return [m[target - nums[i]], i]; } else { m[nums[i]] = i; } } return []; } /* Driver Code */ // 方法一 const nums = [2, 7, 11, 15], target = 13; let res = twoSumBruteForce(nums, target); console.log('方法一 res = ', res); // 方法二 res = twoSumHashTable(nums, target); console.log('方法二 res = ', res); ================================================ FILE: zh-hant/codes/javascript/chapter_sorting/bubble_sort.js ================================================ /** * File: bubble_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 泡沫排序 */ function bubbleSort(nums) { // 外迴圈:未排序區間為 [0, i] for (let i = nums.length - 1; i > 0; i--) { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* 泡沫排序(標誌最佳化)*/ function bubbleSortWithFlag(nums) { // 外迴圈:未排序區間為 [0, i] for (let i = nums.length - 1; i > 0; i--) { let flag = false; // 初始化標誌位 // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // 記錄交換元素 } } if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); console.log('泡沫排序完成後 nums =', nums); const nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); console.log('泡沫排序完成後 nums =', nums1); ================================================ FILE: zh-hant/codes/javascript/chapter_sorting/bucket_sort.js ================================================ /** * File: bucket_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 桶排序 */ function bucketSort(nums) { // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 const k = nums.length / 2; const buckets = []; for (let i = 0; i < k; i++) { buckets.push([]); } // 1. 將陣列元素分配到各個桶中 for (const num of nums) { // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] const i = Math.floor(num * k); // 將 num 新增進桶 i buckets[i].push(num); } // 2. 對各個桶執行排序 for (const bucket of buckets) { // 使用內建排序函式,也可以替換成其他排序演算法 bucket.sort((a, b) => a - b); } // 3. 走訪桶合併結果 let i = 0; for (const bucket of buckets) { for (const num of bucket) { nums[i++] = num; } } } /* Driver Code */ const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); console.log('桶排序完成後 nums =', nums); ================================================ FILE: zh-hant/codes/javascript/chapter_sorting/counting_sort.js ================================================ /** * File: counting_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 計數排序 */ // 簡單實現,無法用於排序物件 function countingSortNaive(nums) { // 1. 統計陣列最大元素 m let m = Math.max(...nums); // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 const counter = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. 走訪 counter ,將各元素填入原陣列 nums let i = 0; for (let num = 0; num < m + 1; num++) { for (let j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* 計數排序 */ // 完整實現,可排序物件,並且是穩定排序 function countingSort(nums) { // 1. 統計陣列最大元素 m let m = Math.max(...nums); // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 const counter = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 for (let i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. 倒序走訪 nums ,將各元素填入結果陣列 res // 初始化陣列 res 用於記錄結果 const n = nums.length; const res = new Array(n); for (let i = n - 1; i >= 0; i--) { const num = nums[i]; res[counter[num] - 1] = num; // 將 num 放置到對應索引處 counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 } // 使用結果陣列 res 覆蓋原陣列 nums for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Driver Code */ const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); console.log('計數排序(無法排序物件)完成後 nums =', nums); const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); console.log('計數排序完成後 nums1 =', nums1); ================================================ FILE: zh-hant/codes/javascript/chapter_sorting/heap_sort.js ================================================ /** * File: heap_sort.js * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ function siftDown(nums, n, i) { while (true) { // 判斷節點 i, l, r 中值最大的節點,記為 ma let l = 2 * i + 1; let r = 2 * i + 2; let ma = i; if (l < n && nums[l] > nums[ma]) { ma = l; } if (r < n && nums[r] > nums[ma]) { ma = r; } // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if (ma === i) { break; } // 交換兩節點 [nums[i], nums[ma]] = [nums[ma], nums[i]]; // 迴圈向下堆積化 i = ma; } } /* 堆積排序 */ function heapSort(nums) { // 建堆積操作:堆積化除葉節點以外的其他所有節點 for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // 從堆積中提取最大元素,迴圈 n-1 輪 for (let i = nums.length - 1; i > 0; i--) { // 交換根節點與最右葉節點(交換首元素與尾元素) [nums[0], nums[i]] = [nums[i], nums[0]]; // 以根節點為起點,從頂至底進行堆積化 siftDown(nums, i, 0); } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; heapSort(nums); console.log('堆積排序完成後 nums =', nums); ================================================ FILE: zh-hant/codes/javascript/chapter_sorting/insertion_sort.js ================================================ /** * File: insertion_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 插入排序 */ function insertionSort(nums) { // 外迴圈:已排序區間為 [0, i-1] for (let i = 1; i < nums.length; i++) { let base = nums[i], j = i - 1; // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 j--; } nums[j + 1] = base; // 將 base 賦值到正確位置 } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); console.log('插入排序完成後 nums =', nums); ================================================ FILE: zh-hant/codes/javascript/chapter_sorting/merge_sort.js ================================================ /** * File: merge_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 合併左子陣列和右子陣列 */ function merge(nums, left, mid, right) { // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] // 建立一個臨時陣列 tmp ,用於存放合併後的結果 const tmp = new Array(right - left + 1); // 初始化左子陣列和右子陣列的起始索引 let i = left, j = mid + 1, k = 0; // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* 合併排序 */ function mergeSort(nums, left, right) { // 終止條件 if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 // 劃分階段 let mid = Math.floor(left + (right - left) / 2); // 計算中點 mergeSort(nums, left, mid); // 遞迴左子陣列 mergeSort(nums, mid + 1, right); // 遞迴右子陣列 // 合併階段 merge(nums, left, mid, right); } /* Driver Code */ const nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); console.log('合併排序完成後 nums =', nums); ================================================ FILE: zh-hant/codes/javascript/chapter_sorting/quick_sort.js ================================================ /** * File: quick_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 快速排序類別 */ class QuickSort { /* 元素交換 */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵劃分 */ partition(nums, left, right) { // 以 nums[left] 為基準數 let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j -= 1; // 從右向左找首個小於基準數的元素 } while (i < j && nums[i] <= nums[left]) { i += 1; // 從左向右找首個大於基準數的元素 } // 元素交換 this.swap(nums, i, j); // 交換這兩個元素 } this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } /* 快速排序 */ quickSort(nums, left, right) { // 子陣列長度為 1 時終止遞迴 if (left >= right) return; // 哨兵劃分 const pivot = this.partition(nums, left, right); // 遞迴左子陣列、右子陣列 this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* 快速排序類別(中位基準數最佳化) */ class QuickSortMedian { /* 元素交換 */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 選取三個候選元素的中位數 */ medianThree(nums, left, mid, right) { let l = nums[left], m = nums[mid], r = nums[right]; // m 在 l 和 r 之間 if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // l 在 m 和 r 之間 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; return right; } /* 哨兵劃分(三數取中值) */ partition(nums, left, right) { // 選取三個候選元素的中位數 let med = this.medianThree( nums, left, Math.floor((left + right) / 2), right ); // 將中位數交換至陣列最左端 this.swap(nums, left, med); // 以 nums[left] 為基準數 let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 從右向左找首個小於基準數的元素 while (i < j && nums[i] <= nums[left]) i++; // 從左向右找首個大於基準數的元素 this.swap(nums, i, j); // 交換這兩個元素 } this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } /* 快速排序 */ quickSort(nums, left, right) { // 子陣列長度為 1 時終止遞迴 if (left >= right) return; // 哨兵劃分 const pivot = this.partition(nums, left, right); // 遞迴左子陣列、右子陣列 this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* 快速排序類別(遞迴深度最佳化) */ class QuickSortTailCall { /* 元素交換 */ swap(nums, i, j) { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵劃分 */ partition(nums, left, right) { // 以 nums[left] 為基準數 let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) j--; // 從右向左找首個小於基準數的元素 while (i < j && nums[i] <= nums[left]) i++; // 從左向右找首個大於基準數的元素 this.swap(nums, i, j); // 交換這兩個元素 } this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } /* 快速排序(遞迴深度最佳化) */ quickSort(nums, left, right) { // 子陣列長度為 1 時終止 while (left < right) { // 哨兵劃分操作 let pivot = this.partition(nums, left, right); // 對兩個子陣列中較短的那個執行快速排序 if (pivot - left < right - pivot) { this.quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] } else { this.quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] } } } } /* Driver Code */ /* 快速排序 */ const nums = [2, 4, 1, 0, 3, 5]; const quickSort = new QuickSort(); quickSort.quickSort(nums, 0, nums.length - 1); console.log('快速排序完成後 nums =', nums); /* 快速排序(中位基準數最佳化) */ const nums1 = [2, 4, 1, 0, 3, 5]; const quickSortMedian = new QuickSortMedian(); quickSortMedian.quickSort(nums1, 0, nums1.length - 1); console.log('快速排序(中位基準數最佳化)完成後 nums =', nums1); /* 快速排序(遞迴深度最佳化) */ const nums2 = [2, 4, 1, 0, 3, 5]; const quickSortTailCall = new QuickSortTailCall(); quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); console.log('快速排序(遞迴深度最佳化)完成後 nums =', nums2); ================================================ FILE: zh-hant/codes/javascript/chapter_sorting/radix_sort.js ================================================ /** * File: radix_sort.js * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ function digit(num, exp) { // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 return Math.floor(num / exp) % 10; } /* 計數排序(根據 nums 第 k 位排序) */ function countingSortDigit(nums, exp) { // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 const counter = new Array(10).fill(0); const n = nums.length; // 統計 0~9 各數字的出現次數 for (let i = 0; i < n; i++) { const d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d counter[d]++; // 統計數字 d 的出現次數 } // 求前綴和,將“出現個數”轉換為“陣列索引” for (let i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 倒序走訪,根據桶內統計結果,將各元素填入 res const res = new Array(n).fill(0); for (let i = n - 1; i >= 0; i--) { const d = digit(nums[i], exp); const j = counter[d] - 1; // 獲取 d 在陣列中的索引 j res[j] = nums[i]; // 將當前元素填入索引 j counter[d]--; // 將 d 的數量減 1 } // 使用結果覆蓋原陣列 nums for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* 基數排序 */ function radixSort(nums) { // 獲取陣列的最大元素,用於判斷最大位數 let m = Math.max(... nums); // 按照從低位到高位的順序走訪 for (let exp = 1; exp <= m; exp *= 10) { // 對陣列元素的第 k 位執行計數排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums, exp); } } /* Driver Code */ const nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radixSort(nums); console.log('基數排序完成後 nums =', nums); ================================================ FILE: zh-hant/codes/javascript/chapter_sorting/selection_sort.js ================================================ /** * File: selection_sort.js * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* 選擇排序 */ function selectionSort(nums) { let n = nums.length; // 外迴圈:未排序區間為 [i, n-1] for (let i = 0; i < n - 1; i++) { // 內迴圈:找到未排序區間內的最小元素 let k = i; for (let j = i + 1; j < n; j++) { if (nums[j] < nums[k]) { k = j; // 記錄最小元素的索引 } } // 將該最小元素與未排序區間的首個元素交換 [nums[i], nums[k]] = [nums[k], nums[i]]; } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; selectionSort(nums); console.log('選擇排序完成後 nums =', nums); ================================================ FILE: zh-hant/codes/javascript/chapter_stack_and_queue/array_deque.js ================================================ /** * File: array_deque.js * Created Time: 2023-02-28 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 基於環形陣列實現的雙向佇列 */ class ArrayDeque { #nums; // 用於儲存雙向佇列元素的陣列 #front; // 佇列首指標,指向佇列首元素 #queSize; // 雙向佇列長度 /* 建構子 */ constructor(capacity) { this.#nums = new Array(capacity); this.#front = 0; this.#queSize = 0; } /* 獲取雙向佇列的容量 */ capacity() { return this.#nums.length; } /* 獲取雙向佇列的長度 */ size() { return this.#queSize; } /* 判斷雙向佇列是否為空 */ isEmpty() { return this.#queSize === 0; } /* 計算環形陣列索引 */ index(i) { // 透過取餘操作實現陣列首尾相連 // 當 i 越過陣列尾部後,回到頭部 // 當 i 越過陣列頭部後,回到尾部 return (i + this.capacity()) % this.capacity(); } /* 佇列首入列 */ pushFirst(num) { if (this.#queSize === this.capacity()) { console.log('雙向佇列已滿'); return; } // 佇列首指標向左移動一位 // 透過取餘操作實現 front 越過陣列頭部後回到尾部 this.#front = this.index(this.#front - 1); // 將 num 新增至佇列首 this.#nums[this.#front] = num; this.#queSize++; } /* 佇列尾入列 */ pushLast(num) { if (this.#queSize === this.capacity()) { console.log('雙向佇列已滿'); return; } // 計算佇列尾指標,指向佇列尾索引 + 1 const rear = this.index(this.#front + this.#queSize); // 將 num 新增至佇列尾 this.#nums[rear] = num; this.#queSize++; } /* 佇列首出列 */ popFirst() { const num = this.peekFirst(); // 佇列首指標向後移動一位 this.#front = this.index(this.#front + 1); this.#queSize--; return num; } /* 佇列尾出列 */ popLast() { const num = this.peekLast(); this.#queSize--; return num; } /* 訪問佇列首元素 */ peekFirst() { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); return this.#nums[this.#front]; } /* 訪問佇列尾元素 */ peekLast() { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); // 計算尾元素索引 const last = this.index(this.#front + this.#queSize - 1); return this.#nums[last]; } /* 返回陣列用於列印 */ toArray() { // 僅轉換有效長度範圍內的串列元素 const res = []; for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) { res[i] = this.#nums[this.index(j)]; } return res; } } /* Driver Code */ /* 初始化雙向佇列 */ const capacity = 5; const deque = new ArrayDeque(capacity); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); console.log('雙向佇列 deque = [' + deque.toArray() + ']'); /* 訪問元素 */ const peekFirst = deque.peekFirst(); console.log('佇列首元素 peekFirst = ' + peekFirst); const peekLast = deque.peekLast(); console.log('佇列尾元素 peekLast = ' + peekLast); /* 元素入列 */ deque.pushLast(4); console.log('元素 4 佇列尾入列後 deque = [' + deque.toArray() + ']'); deque.pushFirst(1); console.log('元素 1 佇列首入列後 deque = [' + deque.toArray() + ']'); /* 元素出列 */ const popLast = deque.popLast(); console.log( '佇列尾出列元素 = ' + popLast + ',佇列尾出列後 deque = [' + deque.toArray() + ']' ); const popFirst = deque.popFirst(); console.log( '佇列首出列元素 = ' + popFirst + ',佇列首出列後 deque = [' + deque.toArray() + ']' ); /* 獲取雙向佇列的長度 */ const size = deque.size(); console.log('雙向佇列長度 size = ' + size); /* 判斷雙向佇列是否為空 */ const isEmpty = deque.isEmpty(); console.log('雙向佇列是否為空 = ' + isEmpty); ================================================ FILE: zh-hant/codes/javascript/chapter_stack_and_queue/array_queue.js ================================================ /** * File: array_queue.js * Created Time: 2022-12-13 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* 基於環形陣列實現的佇列 */ class ArrayQueue { #nums; // 用於儲存佇列元素的陣列 #front = 0; // 佇列首指標,指向佇列首元素 #queSize = 0; // 佇列長度 constructor(capacity) { this.#nums = new Array(capacity); } /* 獲取佇列的容量 */ get capacity() { return this.#nums.length; } /* 獲取佇列的長度 */ get size() { return this.#queSize; } /* 判斷佇列是否為空 */ isEmpty() { return this.#queSize === 0; } /* 入列 */ push(num) { if (this.size === this.capacity) { console.log('佇列已滿'); return; } // 計算佇列尾指標,指向佇列尾索引 + 1 // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 const rear = (this.#front + this.size) % this.capacity; // 將 num 新增至佇列尾 this.#nums[rear] = num; this.#queSize++; } /* 出列 */ pop() { const num = this.peek(); // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 this.#front = (this.#front + 1) % this.capacity; this.#queSize--; return num; } /* 訪問佇列首元素 */ peek() { if (this.isEmpty()) throw new Error('佇列為空'); return this.#nums[this.#front]; } /* 返回 Array */ toArray() { // 僅轉換有效長度範圍內的串列元素 const arr = new Array(this.size); for (let i = 0, j = this.#front; i < this.size; i++, j++) { arr[i] = this.#nums[j % this.capacity]; } return arr; } } /* Driver Code */ /* 初始化佇列 */ const capacity = 10; const queue = new ArrayQueue(capacity); /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('佇列 queue =', queue.toArray()); /* 訪問佇列首元素 */ const peek = queue.peek(); console.log('佇列首元素 peek = ' + peek); /* 元素出列 */ const pop = queue.pop(); console.log('出列元素 pop = ' + pop + ',出列後 queue =', queue.toArray()); /* 獲取佇列的長度 */ const size = queue.size; console.log('佇列長度 size = ' + size); /* 判斷佇列是否為空 */ const isEmpty = queue.isEmpty(); console.log('佇列是否為空 = ' + isEmpty); /* 測試環形陣列 */ for (let i = 0; i < 10; i++) { queue.push(i); queue.pop(); console.log('第 ' + i + ' 輪入列 + 出列後 queue =', queue.toArray()); } ================================================ FILE: zh-hant/codes/javascript/chapter_stack_and_queue/array_stack.js ================================================ /** * File: array_stack.js * Created Time: 2022-12-09 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* 基於陣列實現的堆疊 */ class ArrayStack { #stack; constructor() { this.#stack = []; } /* 獲取堆疊的長度 */ get size() { return this.#stack.length; } /* 判斷堆疊是否為空 */ isEmpty() { return this.#stack.length === 0; } /* 入堆疊 */ push(num) { this.#stack.push(num); } /* 出堆疊 */ pop() { if (this.isEmpty()) throw new Error('堆疊為空'); return this.#stack.pop(); } /* 訪問堆疊頂元素 */ top() { if (this.isEmpty()) throw new Error('堆疊為空'); return this.#stack[this.#stack.length - 1]; } /* 返回 Array */ toArray() { return this.#stack; } } /* Driver Code */ /* 初始化堆疊 */ const stack = new ArrayStack(); /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('堆疊 stack = '); console.log(stack.toArray()); /* 訪問堆疊頂元素 */ const top = stack.top(); console.log('堆疊頂元素 top = ' + top); /* 元素出堆疊 */ const pop = stack.pop(); console.log('出堆疊元素 pop = ' + pop + ',出堆疊後 stack = '); console.log(stack.toArray()); /* 獲取堆疊的長度 */ const size = stack.size; console.log('堆疊的長度 size = ' + size); /* 判斷是否為空 */ const isEmpty = stack.isEmpty(); console.log('堆疊是否為空 = ' + isEmpty); ================================================ FILE: zh-hant/codes/javascript/chapter_stack_and_queue/deque.js ================================================ /** * File: deque.js * Created Time: 2023-01-17 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Driver Code */ /* 初始化雙向佇列 */ // JavaScript 沒有內建的雙端佇列,只能把 Array 當作雙端佇列來使用 const deque = []; /* 元素入列 */ deque.push(2); deque.push(5); deque.push(4); // 請注意,由於是陣列,unshift() 方法的時間複雜度為 O(n) deque.unshift(3); deque.unshift(1); console.log('雙向佇列 deque = ', deque); /* 訪問元素 */ const peekFirst = deque[0]; console.log('佇列首元素 peekFirst = ' + peekFirst); const peekLast = deque[deque.length - 1]; console.log('佇列尾元素 peekLast = ' + peekLast); /* 元素出列 */ // 請注意,由於是陣列,shift() 方法的時間複雜度為 O(n) const popFront = deque.shift(); console.log( '佇列首出列元素 popFront = ' + popFront + ',佇列首出列後 deque = ' + deque ); const popBack = deque.pop(); console.log( '佇列尾出列元素 popBack = ' + popBack + ',佇列尾出列後 deque = ' + deque ); /* 獲取雙向佇列的長度 */ const size = deque.length; console.log('雙向佇列長度 size = ' + size); /* 判斷雙向佇列是否為空 */ const isEmpty = size === 0; console.log('雙向佇列是否為空 = ' + isEmpty); ================================================ FILE: zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js ================================================ /** * File: linkedlist_deque.js * Created Time: 2023-02-04 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 雙向鏈結串列節點 */ class ListNode { prev; // 前驅節點引用 (指標) next; // 後繼節點引用 (指標) val; // 節點值 constructor(val) { this.val = val; this.next = null; this.prev = null; } } /* 基於雙向鏈結串列實現的雙向佇列 */ class LinkedListDeque { #front; // 頭節點 front #rear; // 尾節點 rear #queSize; // 雙向佇列的長度 constructor() { this.#front = null; this.#rear = null; this.#queSize = 0; } /* 佇列尾入列操作 */ pushLast(val) { const node = new ListNode(val); // 若鏈結串列為空,則令 front 和 rear 都指向 node if (this.#queSize === 0) { this.#front = node; this.#rear = node; } else { // 將 node 新增至鏈結串列尾部 this.#rear.next = node; node.prev = this.#rear; this.#rear = node; // 更新尾節點 } this.#queSize++; } /* 佇列首入列操作 */ pushFirst(val) { const node = new ListNode(val); // 若鏈結串列為空,則令 front 和 rear 都指向 node if (this.#queSize === 0) { this.#front = node; this.#rear = node; } else { // 將 node 新增至鏈結串列頭部 this.#front.prev = node; node.next = this.#front; this.#front = node; // 更新頭節點 } this.#queSize++; } /* 佇列尾出列操作 */ popLast() { if (this.#queSize === 0) { return null; } const value = this.#rear.val; // 儲存尾節點值 // 刪除尾節點 let temp = this.#rear.prev; if (temp !== null) { temp.next = null; this.#rear.prev = null; } this.#rear = temp; // 更新尾節點 this.#queSize--; return value; } /* 佇列首出列操作 */ popFirst() { if (this.#queSize === 0) { return null; } const value = this.#front.val; // 儲存尾節點值 // 刪除頭節點 let temp = this.#front.next; if (temp !== null) { temp.prev = null; this.#front.next = null; } this.#front = temp; // 更新頭節點 this.#queSize--; return value; } /* 訪問佇列尾元素 */ peekLast() { return this.#queSize === 0 ? null : this.#rear.val; } /* 訪問佇列首元素 */ peekFirst() { return this.#queSize === 0 ? null : this.#front.val; } /* 獲取雙向佇列的長度 */ size() { return this.#queSize; } /* 判斷雙向佇列是否為空 */ isEmpty() { return this.#queSize === 0; } /* 列印雙向佇列 */ print() { const arr = []; let temp = this.#front; while (temp !== null) { arr.push(temp.val); temp = temp.next; } console.log('[' + arr.join(', ') + ']'); } } /* Driver Code */ /* 初始化雙向佇列 */ const linkedListDeque = new LinkedListDeque(); linkedListDeque.pushLast(3); linkedListDeque.pushLast(2); linkedListDeque.pushLast(5); console.log('雙向佇列 linkedListDeque = '); linkedListDeque.print(); /* 訪問元素 */ const peekFirst = linkedListDeque.peekFirst(); console.log('佇列首元素 peekFirst = ' + peekFirst); const peekLast = linkedListDeque.peekLast(); console.log('佇列尾元素 peekLast = ' + peekLast); /* 元素入列 */ linkedListDeque.pushLast(4); console.log('元素 4 佇列尾入列後 linkedListDeque = '); linkedListDeque.print(); linkedListDeque.pushFirst(1); console.log('元素 1 佇列首入列後 linkedListDeque = '); linkedListDeque.print(); /* 元素出列 */ const popLast = linkedListDeque.popLast(); console.log('佇列尾出列元素 = ' + popLast + ',佇列尾出列後 linkedListDeque = '); linkedListDeque.print(); const popFirst = linkedListDeque.popFirst(); console.log('佇列首出列元素 = ' + popFirst + ',佇列首出列後 linkedListDeque = '); linkedListDeque.print(); /* 獲取雙向佇列的長度 */ const size = linkedListDeque.size(); console.log('雙向佇列長度 size = ' + size); /* 判斷雙向佇列是否為空 */ const isEmpty = linkedListDeque.isEmpty(); console.log('雙向佇列是否為空 = ' + isEmpty); ================================================ FILE: zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js ================================================ /** * File: linkedlist_queue.js * Created Time: 2022-12-20 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ const { ListNode } = require('../modules/ListNode'); /* 基於鏈結串列實現的佇列 */ class LinkedListQueue { #front; // 頭節點 #front #rear; // 尾節點 #rear #queSize = 0; constructor() { this.#front = null; this.#rear = null; } /* 獲取佇列的長度 */ get size() { return this.#queSize; } /* 判斷佇列是否為空 */ isEmpty() { return this.size === 0; } /* 入列 */ push(num) { // 在尾節點後新增 num const node = new ListNode(num); // 如果佇列為空,則令頭、尾節點都指向該節點 if (!this.#front) { this.#front = node; this.#rear = node; // 如果佇列不為空,則將該節點新增到尾節點後 } else { this.#rear.next = node; this.#rear = node; } this.#queSize++; } /* 出列 */ pop() { const num = this.peek(); // 刪除頭節點 this.#front = this.#front.next; this.#queSize--; return num; } /* 訪問佇列首元素 */ peek() { if (this.size === 0) throw new Error('佇列為空'); return this.#front.val; } /* 將鏈結串列轉化為 Array 並返回 */ toArray() { let node = this.#front; const res = new Array(this.size); for (let i = 0; i < res.length; i++) { res[i] = node.val; node = node.next; } return res; } } /* Driver Code */ /* 初始化佇列 */ const queue = new LinkedListQueue(); /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('佇列 queue = ' + queue.toArray()); /* 訪問佇列首元素 */ const peek = queue.peek(); console.log('佇列首元素 peek = ' + peek); /* 元素出列 */ const pop = queue.pop(); console.log('出列元素 pop = ' + pop + ',出列後 queue = ' + queue.toArray()); /* 獲取佇列的長度 */ const size = queue.size; console.log('佇列長度 size = ' + size); /* 判斷佇列是否為空 */ const isEmpty = queue.isEmpty(); console.log('佇列是否為空 = ' + isEmpty); ================================================ FILE: zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js ================================================ /** * File: linkedlist_stack.js * Created Time: 2022-12-22 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ const { ListNode } = require('../modules/ListNode'); /* 基於鏈結串列實現的堆疊 */ class LinkedListStack { #stackPeek; // 將頭節點作為堆疊頂 #stkSize = 0; // 堆疊的長度 constructor() { this.#stackPeek = null; } /* 獲取堆疊的長度 */ get size() { return this.#stkSize; } /* 判斷堆疊是否為空 */ isEmpty() { return this.size === 0; } /* 入堆疊 */ push(num) { const node = new ListNode(num); node.next = this.#stackPeek; this.#stackPeek = node; this.#stkSize++; } /* 出堆疊 */ pop() { const num = this.peek(); this.#stackPeek = this.#stackPeek.next; this.#stkSize--; return num; } /* 訪問堆疊頂元素 */ peek() { if (!this.#stackPeek) throw new Error('堆疊為空'); return this.#stackPeek.val; } /* 將鏈結串列轉化為 Array 並返回 */ toArray() { let node = this.#stackPeek; const res = new Array(this.size); for (let i = res.length - 1; i >= 0; i--) { res[i] = node.val; node = node.next; } return res; } } /* Driver Code */ /* 初始化堆疊 */ const stack = new LinkedListStack(); /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('堆疊 stack = ' + stack.toArray()); /* 訪問堆疊頂元素 */ const peek = stack.peek(); console.log('堆疊頂元素 peek = ' + peek); /* 元素出堆疊 */ const pop = stack.pop(); console.log('出堆疊元素 pop = ' + pop + ',出堆疊後 stack = ' + stack.toArray()); /* 獲取堆疊的長度 */ const size = stack.size; console.log('堆疊的長度 size = ' + size); /* 判斷是否為空 */ const isEmpty = stack.isEmpty(); console.log('堆疊是否為空 = ' + isEmpty); ================================================ FILE: zh-hant/codes/javascript/chapter_stack_and_queue/queue.js ================================================ /** * File: queue.js * Created Time: 2022-12-05 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* 初始化佇列 */ // JavaScript 沒有內建的佇列,可以把 Array 當作佇列來使用 const queue = []; /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('佇列 queue =', queue); /* 訪問佇列首元素 */ const peek = queue[0]; console.log('佇列首元素 peek =', peek); /* 元素出列 */ // 底層是陣列,因此 shift() 方法的時間複雜度為 O(n) const pop = queue.shift(); console.log('出列元素 pop =', pop, ',出列後 queue = ', queue); /* 獲取佇列的長度 */ const size = queue.length; console.log('佇列長度 size =', size); /* 判斷佇列是否為空 */ const isEmpty = queue.length === 0; console.log('佇列是否為空 = ', isEmpty); ================================================ FILE: zh-hant/codes/javascript/chapter_stack_and_queue/stack.js ================================================ /** * File: stack.js * Created Time: 2022-12-04 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* 初始化堆疊 */ // JavaScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 const stack = []; /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('堆疊 stack =', stack); /* 訪問堆疊頂元素 */ const peek = stack[stack.length - 1]; console.log('堆疊頂元素 peek =', peek); /* 元素出堆疊 */ const pop = stack.pop(); console.log('出堆疊元素 pop =', pop); console.log('出堆疊後 stack =', stack); /* 獲取堆疊的長度 */ const size = stack.length; console.log('堆疊的長度 size =', size); /* 判斷是否為空 */ const isEmpty = stack.length === 0; console.log('堆疊是否為空 =', isEmpty); ================================================ FILE: zh-hant/codes/javascript/chapter_tree/array_binary_tree.js ================================================ /** * File: array_binary_tree.js * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 陣列表示下的二元樹類別 */ class ArrayBinaryTree { #tree; /* 建構子 */ constructor(arr) { this.#tree = arr; } /* 串列容量 */ size() { return this.#tree.length; } /* 獲取索引為 i 節點的值 */ val(i) { // 若索引越界,則返回 null ,代表空位 if (i < 0 || i >= this.size()) return null; return this.#tree[i]; } /* 獲取索引為 i 節點的左子節點的索引 */ left(i) { return 2 * i + 1; } /* 獲取索引為 i 節點的右子節點的索引 */ right(i) { return 2 * i + 2; } /* 獲取索引為 i 節點的父節點的索引 */ parent(i) { return Math.floor((i - 1) / 2); // 向下整除 } /* 層序走訪 */ levelOrder() { let res = []; // 直接走訪陣列 for (let i = 0; i < this.size(); i++) { if (this.val(i) !== null) res.push(this.val(i)); } return res; } /* 深度優先走訪 */ #dfs(i, order, res) { // 若為空位,則返回 if (this.val(i) === null) return; // 前序走訪 if (order === 'pre') res.push(this.val(i)); this.#dfs(this.left(i), order, res); // 中序走訪 if (order === 'in') res.push(this.val(i)); this.#dfs(this.right(i), order, res); // 後序走訪 if (order === 'post') res.push(this.val(i)); } /* 前序走訪 */ preOrder() { const res = []; this.#dfs(0, 'pre', res); return res; } /* 中序走訪 */ inOrder() { const res = []; this.#dfs(0, 'in', res); return res; } /* 後序走訪 */ postOrder() { const res = []; this.#dfs(0, 'post', res); return res; } } /* Driver Code */ // 初始化二元樹 // 這裡藉助了一個從陣列直接生成二元樹的函式 const arr = Array.of( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ); const root = arrToTree(arr); console.log('\n初始化二元樹\n'); console.log('二元樹的陣列表示:'); console.log(arr); console.log('二元樹的鏈結串列表示:'); printTree(root); // 陣列表示下的二元樹類別 const abt = new ArrayBinaryTree(arr); // 訪問節點 const i = 1; const l = abt.left(i); const r = abt.right(i); const p = abt.parent(i); console.log('\n當前節點的索引為 ' + i + ' ,值為 ' + abt.val(i)); console.log( '其左子節點的索引為 ' + l + ' ,值為 ' + (l === null ? 'null' : abt.val(l)) ); console.log( '其右子節點的索引為 ' + r + ' ,值為 ' + (r === null ? 'null' : abt.val(r)) ); console.log( '其父節點的索引為 ' + p + ' ,值為 ' + (p === null ? 'null' : abt.val(p)) ); // 走訪樹 let res = abt.levelOrder(); console.log('\n層序走訪為:' + res); res = abt.preOrder(); console.log('前序走訪為:' + res); res = abt.inOrder(); console.log('中序走訪為:' + res); res = abt.postOrder(); console.log('後序走訪為:' + res); ================================================ FILE: zh-hant/codes/javascript/chapter_tree/avl_tree.js ================================================ /** * File: avl_tree.js * Created Time: 2023-02-05 * Author: what-is-me (whatisme@outlook.jp) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* AVL 樹*/ class AVLTree { /* 建構子 */ constructor() { this.root = null; //根節點 } /* 獲取節點高度 */ height(node) { // 空節點高度為 -1 ,葉節點高度為 0 return node === null ? -1 : node.height; } /* 更新節點高度 */ #updateHeight(node) { // 節點高度等於最高子樹高度 + 1 node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; } /* 獲取平衡因子 */ balanceFactor(node) { // 空節點平衡因子為 0 if (node === null) return 0; // 節點平衡因子 = 左子樹高度 - 右子樹高度 return this.height(node.left) - this.height(node.right); } /* 右旋操作 */ #rightRotate(node) { const child = node.left; const grandChild = child.right; // 以 child 為原點,將 node 向右旋轉 child.right = node; node.left = grandChild; // 更新節點高度 this.#updateHeight(node); this.#updateHeight(child); // 返回旋轉後子樹的根節點 return child; } /* 左旋操作 */ #leftRotate(node) { const child = node.right; const grandChild = child.left; // 以 child 為原點,將 node 向左旋轉 child.left = node; node.right = grandChild; // 更新節點高度 this.#updateHeight(node); this.#updateHeight(child); // 返回旋轉後子樹的根節點 return child; } /* 執行旋轉操作,使該子樹重新恢復平衡 */ #rotate(node) { // 獲取節點 node 的平衡因子 const balanceFactor = this.balanceFactor(node); // 左偏樹 if (balanceFactor > 1) { if (this.balanceFactor(node.left) >= 0) { // 右旋 return this.#rightRotate(node); } else { // 先左旋後右旋 node.left = this.#leftRotate(node.left); return this.#rightRotate(node); } } // 右偏樹 if (balanceFactor < -1) { if (this.balanceFactor(node.right) <= 0) { // 左旋 return this.#leftRotate(node); } else { // 先右旋後左旋 node.right = this.#rightRotate(node.right); return this.#leftRotate(node); } } // 平衡樹,無須旋轉,直接返回 return node; } /* 插入節點 */ insert(val) { this.root = this.#insertHelper(this.root, val); } /* 遞迴插入節點(輔助方法) */ #insertHelper(node, val) { if (node === null) return new TreeNode(val); /* 1. 查詢插入位置並插入節點 */ if (val < node.val) node.left = this.#insertHelper(node.left, val); else if (val > node.val) node.right = this.#insertHelper(node.right, val); else return node; // 重複節點不插入,直接返回 this.#updateHeight(node); // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = this.#rotate(node); // 返回子樹的根節點 return node; } /* 刪除節點 */ remove(val) { this.root = this.#removeHelper(this.root, val); } /* 遞迴刪除節點(輔助方法) */ #removeHelper(node, val) { if (node === null) return null; /* 1. 查詢節點並刪除 */ if (val < node.val) node.left = this.#removeHelper(node.left, val); else if (val > node.val) node.right = this.#removeHelper(node.right, val); else { if (node.left === null || node.right === null) { const child = node.left !== null ? node.left : node.right; // 子節點數量 = 0 ,直接刪除 node 並返回 if (child === null) return null; // 子節點數量 = 1 ,直接刪除 node else node = child; } else { // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 let temp = node.right; while (temp.left !== null) { temp = temp.left; } node.right = this.#removeHelper(node.right, temp.val); node.val = temp.val; } } this.#updateHeight(node); // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = this.#rotate(node); // 返回子樹的根節點 return node; } /* 查詢節點 */ search(val) { let cur = this.root; // 迴圈查詢,越過葉節點後跳出 while (cur !== null) { // 目標節點在 cur 的右子樹中 if (cur.val < val) cur = cur.right; // 目標節點在 cur 的左子樹中 else if (cur.val > val) cur = cur.left; // 找到目標節點,跳出迴圈 else break; } // 返回目標節點 return cur; } } function testInsert(tree, val) { tree.insert(val); console.log('\n插入節點 ' + val + ' 後,AVL 樹為'); printTree(tree.root); } function testRemove(tree, val) { tree.remove(val); console.log('\n刪除節點 ' + val + ' 後,AVL 樹為'); printTree(tree.root); } /* Driver Code */ /* 初始化空 AVL 樹 */ const avlTree = new AVLTree(); /* 插入節點 */ // 請關注插入節點後,AVL 樹是如何保持平衡的 testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); testInsert(avlTree, 4); testInsert(avlTree, 5); testInsert(avlTree, 8); testInsert(avlTree, 7); testInsert(avlTree, 9); testInsert(avlTree, 10); testInsert(avlTree, 6); /* 插入重複節點 */ testInsert(avlTree, 7); /* 刪除節點 */ // 請關注刪除節點後,AVL 樹是如何保持平衡的 testRemove(avlTree, 8); // 刪除度為 0 的節點 testRemove(avlTree, 5); // 刪除度為 1 的節點 testRemove(avlTree, 4); // 刪除度為 2 的節點 /* 查詢節點 */ const node = avlTree.search(7); console.log('\n查詢到的節點物件為', node, ',節點值 = ' + node.val); ================================================ FILE: zh-hant/codes/javascript/chapter_tree/binary_search_tree.js ================================================ /** * File: binary_search_tree.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 二元搜尋樹 */ class BinarySearchTree { /* 建構子 */ constructor() { // 初始化空樹 this.root = null; } /* 獲取二元樹根節點 */ getRoot() { return this.root; } /* 查詢節點 */ search(num) { let cur = this.root; // 迴圈查詢,越過葉節點後跳出 while (cur !== null) { // 目標節點在 cur 的右子樹中 if (cur.val < num) cur = cur.right; // 目標節點在 cur 的左子樹中 else if (cur.val > num) cur = cur.left; // 找到目標節點,跳出迴圈 else break; } // 返回目標節點 return cur; } /* 插入節點 */ insert(num) { // 若樹為空,則初始化根節點 if (this.root === null) { this.root = new TreeNode(num); return; } let cur = this.root, pre = null; // 迴圈查詢,越過葉節點後跳出 while (cur !== null) { // 找到重複節點,直接返回 if (cur.val === num) return; pre = cur; // 插入位置在 cur 的右子樹中 if (cur.val < num) cur = cur.right; // 插入位置在 cur 的左子樹中 else cur = cur.left; } // 插入節點 const node = new TreeNode(num); if (pre.val < num) pre.right = node; else pre.left = node; } /* 刪除節點 */ remove(num) { // 若樹為空,直接提前返回 if (this.root === null) return; let cur = this.root, pre = null; // 迴圈查詢,越過葉節點後跳出 while (cur !== null) { // 找到待刪除節點,跳出迴圈 if (cur.val === num) break; pre = cur; // 待刪除節點在 cur 的右子樹中 if (cur.val < num) cur = cur.right; // 待刪除節點在 cur 的左子樹中 else cur = cur.left; } // 若無待刪除節點,則直接返回 if (cur === null) return; // 子節點數量 = 0 or 1 if (cur.left === null || cur.right === null) { // 當子節點數量 = 0 / 1 時, child = null / 該子節點 const child = cur.left !== null ? cur.left : cur.right; // 刪除節點 cur if (cur !== this.root) { if (pre.left === cur) pre.left = child; else pre.right = child; } else { // 若刪除節點為根節點,則重新指定根節點 this.root = child; } } // 子節點數量 = 2 else { // 獲取中序走訪中 cur 的下一個節點 let tmp = cur.right; while (tmp.left !== null) { tmp = tmp.left; } // 遞迴刪除節點 tmp this.remove(tmp.val); // 用 tmp 覆蓋 cur cur.val = tmp.val; } } } /* Driver Code */ /* 初始化二元搜尋樹 */ const bst = new BinarySearchTree(); // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (const num of nums) { bst.insert(num); } console.log('\n初始化的二元樹為\n'); printTree(bst.getRoot()); /* 查詢節點 */ const node = bst.search(7); console.log('\n查詢到的節點物件為 ' + node + ',節點值 = ' + node.val); /* 插入節點 */ bst.insert(16); console.log('\n插入節點 16 後,二元樹為\n'); printTree(bst.getRoot()); /* 刪除節點 */ bst.remove(1); console.log('\n刪除節點 1 後,二元樹為\n'); printTree(bst.getRoot()); bst.remove(2); console.log('\n刪除節點 2 後,二元樹為\n'); printTree(bst.getRoot()); bst.remove(4); console.log('\n刪除節點 4 後,二元樹為\n'); printTree(bst.getRoot()); ================================================ FILE: zh-hant/codes/javascript/chapter_tree/binary_tree.js ================================================ /** * File: binary_tree.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { TreeNode } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 初始化二元樹 */ // 初始化節點 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // 構建節點之間的引用(指標) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; console.log('\n初始化二元樹\n'); printTree(n1); /* 插入與刪除節點 */ const P = new TreeNode(0); // 在 n1 -> n2 中間插入節點 P n1.left = P; P.left = n2; console.log('\n插入節點 P 後\n'); printTree(n1); // 刪除節點 P n1.left = n2; console.log('\n刪除節點 P 後\n'); printTree(n1); ================================================ FILE: zh-hant/codes/javascript/chapter_tree/binary_tree_bfs.js ================================================ /** * File: binary_tree_bfs.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); /* 層序走訪 */ function levelOrder(root) { // 初始化佇列,加入根節點 const queue = [root]; // 初始化一個串列,用於儲存走訪序列 const list = []; while (queue.length) { let node = queue.shift(); // 隊列出隊 list.push(node.val); // 儲存節點值 if (node.left) queue.push(node.left); // 左子節點入列 if (node.right) queue.push(node.right); // 右子節點入列 } return list; } /* Driver Code */ /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n初始化二元樹\n'); printTree(root); /* 層序走訪 */ const list = levelOrder(root); console.log('\n層序走訪的節點列印序列 = ' + list); ================================================ FILE: zh-hant/codes/javascript/chapter_tree/binary_tree_dfs.js ================================================ /** * File: binary_tree_dfs.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('../modules/TreeNode'); const { printTree } = require('../modules/PrintUtil'); // 初始化串列,用於儲存走訪序列 const list = []; /* 前序走訪 */ function preOrder(root) { if (root === null) return; // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 list.push(root.val); preOrder(root.left); preOrder(root.right); } /* 中序走訪 */ function inOrder(root) { if (root === null) return; // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 inOrder(root.left); list.push(root.val); inOrder(root.right); } /* 後序走訪 */ function postOrder(root) { if (root === null) return; // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 postOrder(root.left); postOrder(root.right); list.push(root.val); } /* Driver Code */ /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n初始化二元樹\n'); printTree(root); /* 前序走訪 */ list.length = 0; preOrder(root); console.log('\n前序走訪的節點列印序列 = ' + list); /* 中序走訪 */ list.length = 0; inOrder(root); console.log('\n中序走訪的節點列印序列 = ' + list); /* 後序走訪 */ list.length = 0; postOrder(root); console.log('\n後序走訪的節點列印序列 = ' + list); ================================================ FILE: zh-hant/codes/javascript/modules/ListNode.js ================================================ /** * File: ListNode.js * Created Time: 2022-12-12 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 鏈結串列節點 */ class ListNode { val; // 節點值 next; // 指向下一節點的引用(指標) constructor(val, next) { this.val = val === undefined ? 0 : val; this.next = next === undefined ? null : next; } } /* 將串列反序列化為鏈結串列 */ function arrToLinkedList(arr) { const dum = new ListNode(0); let head = dum; for (const val of arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } module.exports = { ListNode, arrToLinkedList, }; ================================================ FILE: zh-hant/codes/javascript/modules/PrintUtil.js ================================================ /** * File: PrintUtil.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ const { arrToTree } = require('./TreeNode'); /* 列印鏈結串列 */ function printLinkedList(head) { let list = []; while (head !== null) { list.push(head.val.toString()); head = head.next; } console.log(list.join(' -> ')); } function Trunk(prev, str) { this.prev = prev; this.str = str; } /** * 列印二元樹 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ function printTree(root) { printTree(root, null, false); } /* 列印二元樹 */ function printTree(root, prev, isRight) { if (root === null) { return; } let prev_str = ' '; let trunk = new Trunk(prev, prev_str); printTree(root.right, trunk, true); if (!prev) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); console.log(' ' + root.val); if (prev) { prev.str = prev_str; } trunk.str = ' |'; printTree(root.left, trunk, false); } function showTrunks(p) { if (!p) { return; } showTrunks(p.prev); process.stdout.write(p.str); } /* 列印堆積 */ function printHeap(arr) { console.log('堆積的陣列表示:'); console.log(arr); console.log('堆積的樹狀表示:'); printTree(arrToTree(arr)); } module.exports = { printLinkedList, printTree, printHeap, }; ================================================ FILE: zh-hant/codes/javascript/modules/TreeNode.js ================================================ /** * File: TreeNode.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 二元樹節點 */ class TreeNode { val; // 節點值 left; // 左子節點指標 right; // 右子節點指標 height; //節點高度 constructor(val, left, right, height) { this.val = val === undefined ? 0 : val; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; this.height = height === undefined ? 0 : height; } } /* 將陣列反序列化為二元樹 */ function arrToTree(arr, i = 0) { if (i < 0 || i >= arr.length || arr[i] === null) { return null; } let root = new TreeNode(arr[i]); root.left = arrToTree(arr, 2 * i + 1); root.right = arrToTree(arr, 2 * i + 2); return root; } module.exports = { TreeNode, arrToTree, }; ================================================ FILE: zh-hant/codes/javascript/modules/Vertex.js ================================================ /** * File: Vertex.js * Created Time: 2023-02-15 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 頂點類別 */ class Vertex { val; constructor(val) { this.val = val; } /* 輸入值串列 vals ,返回頂點串列 vets */ static valsToVets(vals) { const vets = []; for (let i = 0; i < vals.length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* 輸入頂點串列 vets ,返回值串列 vals */ static vetsToVals(vets) { const vals = []; for (const vet of vets) { vals.push(vet.val); } return vals; } } module.exports = { Vertex, }; ================================================ FILE: zh-hant/codes/javascript/test_all.js ================================================ import { bold, brightRed } from 'jsr:@std/fmt/colors'; import { expandGlob } from 'jsr:@std/fs'; import { relative, resolve } from 'jsr:@std/path'; /** * @typedef {import('jsr:@std/fs').WalkEntry} WalkEntry * @type {WalkEntry[]} */ const entries = []; for await (const entry of expandGlob( resolve(import.meta.dirname, './chapter_*/*.js') )) { entries.push(entry); } /** @type {{ status: Promise; stderr: ReadableStream; }[]} */ const processes = []; for (const file of entries) { const execute = new Deno.Command('node', { args: [relative(import.meta.dirname, file.path)], cwd: import.meta.dirname, stdin: 'piped', stdout: 'piped', stderr: 'piped', }); const process = execute.spawn(); processes.push({ status: process.status, stderr: process.stderr }); } const results = await Promise.all( processes.map(async (item) => { const status = await item.status; return { status, stderr: item.stderr }; }) ); /** @type {ReadableStream[]} */ const errors = []; for (const result of results) { if (!result.status.success) { errors.push(result.stderr); } } console.log(`Tested ${entries.length} files`); console.log(`Found exception in ${errors.length} files`); if (errors.length) { console.log(); for (const error of errors) { const reader = error.getReader(); const { value } = await reader.read(); const decoder = new TextDecoder(); console.log(`${bold(brightRed('error'))}: ${decoder.decode(value)}`); } throw new Error('Test failed'); } ================================================ FILE: zh-hant/codes/kotlin/chapter_array_and_linkedlist/array.kt ================================================ /** * File: array.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_array_and_linkedlist import java.util.concurrent.ThreadLocalRandom /* 隨機訪問元素 */ fun randomAccess(nums: IntArray): Int { // 在區間 [0, nums.size) 中隨機抽取一個數字 val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size) // 獲取並返回隨機元素 val randomNum = nums[randomIndex] return randomNum } /* 擴展陣列長度 */ fun extend(nums: IntArray, enlarge: Int): IntArray { // 初始化一個擴展長度後的陣列 val res = IntArray(nums.size + enlarge) // 將原陣列中的所有元素複製到新陣列 for (i in nums.indices) { res[i] = nums[i] } // 返回擴展後的新陣列 return res } /* 在陣列的索引 index 處插入元素 num */ fun insert(nums: IntArray, num: Int, index: Int) { // 把索引 index 以及之後的所有元素向後移動一位 for (i in nums.size - 1 downTo index + 1) { nums[i] = nums[i - 1] } // 將 num 賦給 index 處的元素 nums[index] = num } /* 刪除索引 index 處的元素 */ fun remove(nums: IntArray, index: Int) { // 把索引 index 之後的所有元素向前移動一位 for (i in index.. P -> n1 val p = n0.next val n1 = p?.next n0.next = n1 } /* 訪問鏈結串列中索引為 index 的節點 */ fun access(head: ListNode?, index: Int): ListNode? { var h = head for (i in 0..= size) throw IndexOutOfBoundsException("索引越界") return arr[index] } /* 更新元素 */ fun set(index: Int, num: Int) { if (index < 0 || index >= size) throw IndexOutOfBoundsException("索引越界") arr[index] = num } /* 在尾部新增元素 */ fun add(num: Int) { // 元素數量超出容量時,觸發擴容機制 if (size == capacity()) extendCapacity() arr[size] = num // 更新元素數量 size++ } /* 在中間插入元素 */ fun insert(index: Int, num: Int) { if (index < 0 || index >= size) throw IndexOutOfBoundsException("索引越界") // 元素數量超出容量時,觸發擴容機制 if (size == capacity()) extendCapacity() // 將索引 index 以及之後的元素都向後移動一位 for (j in size - 1 downTo index) arr[j + 1] = arr[j] arr[index] = num // 更新元素數量 size++ } /* 刪除元素 */ fun remove(index: Int): Int { if (index < 0 || index >= size) throw IndexOutOfBoundsException("索引越界") val num = arr[index] // 將將索引 index 之後的元素都向前移動一位 for (j in index..>, res: MutableList>?>, cols: BooleanArray, diags1: BooleanArray, diags2: BooleanArray ) { // 當放置完所有行時,記錄解 if (row == n) { val copyState = mutableListOf>() for (sRow in state) { copyState.add(sRow.toMutableList()) } res.add(copyState) return } // 走訪所有列 for (col in 0..>?> { // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 val state = mutableListOf>() for (i in 0..() for (j in 0..>?>() backtrack(0, n, state, res, cols, diags1, diags2) return res } /* Driver Code */ fun main() { val n = 4 val res = nQueens(n) println("輸入棋盤長寬為 $n") println("皇后放置方案共有 ${res.size} 種") for (state in res) { println("--------------------") for (row in state!!) { println(row) } } } ================================================ FILE: zh-hant/codes/kotlin/chapter_backtracking/permutations_i.kt ================================================ /** * File: permutations_i.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.permutations_i /* 回溯演算法:全排列 I */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // 當狀態長度等於元素數量時,記錄解 if (state.size == choices.size) { res.add(state.toMutableList()) return } // 走訪所有選擇 for (i in choices.indices) { val choice = choices[i] // 剪枝:不允許重複選擇元素 if (!selected[i]) { // 嘗試:做出選擇,更新狀態 selected[i] = true state.add(choice) // 進行下一輪選擇 backtrack(state, choices, selected, res) // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false state.removeAt(state.size - 1) } } } /* 全排列 I */ fun permutationsI(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(1, 2, 3) val res = permutationsI(nums) println("輸入陣列 nums = ${nums.contentToString()}") println("所有排列 res = $res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_backtracking/permutations_ii.kt ================================================ /** * File: permutations_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.permutations_ii /* 回溯演算法:全排列 II */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // 當狀態長度等於元素數量時,記錄解 if (state.size == choices.size) { res.add(state.toMutableList()) return } // 走訪所有選擇 val duplicated = HashSet() for (i in choices.indices) { val choice = choices[i] // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 if (!selected[i] && !duplicated.contains(choice)) { // 嘗試:做出選擇,更新狀態 duplicated.add(choice) // 記錄選擇過的元素值 selected[i] = true state.add(choice) // 進行下一輪選擇 backtrack(state, choices, selected, res) // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false state.removeAt(state.size - 1) } } } /* 全排列 II */ fun permutationsII(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(1, 2, 2) val res = permutationsII(nums) println("輸入陣列 nums = ${nums.contentToString()}") println("所有排列 res = $res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt ================================================ /** * File: preorder_traversal_i_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_i_compact import utils.TreeNode import utils.printTree var res: MutableList? = null /* 前序走訪:例題一 */ fun preOrder(root: TreeNode?) { if (root == null) { return } if (root._val == 7) { // 記錄解 res!!.add(root) } preOrder(root.left) preOrder(root.right) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\n初始化二元樹") printTree(root) // 前序走訪 res = mutableListOf() preOrder(root) println("\n輸出所有值為 7 的節點") val vals = mutableListOf() for (node in res!!) { vals.add(node._val) } println(vals) } ================================================ FILE: zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt ================================================ /** * File: preorder_traversal_ii_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_ii_compact import utils.TreeNode import utils.printTree var path: MutableList? = null var res: MutableList>? = null /* 前序走訪:例題二 */ fun preOrder(root: TreeNode?) { if (root == null) { return } // 嘗試 path!!.add(root) if (root._val == 7) { // 記錄解 res!!.add(path!!.toMutableList()) } preOrder(root.left) preOrder(root.right) // 回退 path!!.removeAt(path!!.size - 1) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\n初始化二元樹") printTree(root) // 前序走訪 path = mutableListOf() res = mutableListOf() preOrder(root) println("\n輸出所有根節點到節點 7 的路徑") for (path in res!!) { val _vals = mutableListOf() for (node in path) { _vals.add(node._val) } println(_vals) } } ================================================ FILE: zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt ================================================ /** * File: preorder_traversal_iii_compact.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_iii_compact import utils.TreeNode import utils.printTree var path: MutableList? = null var res: MutableList>? = null /* 前序走訪:例題三 */ fun preOrder(root: TreeNode?) { // 剪枝 if (root == null || root._val == 3) { return } // 嘗試 path!!.add(root) if (root._val == 7) { // 記錄解 res!!.add(path!!.toMutableList()) } preOrder(root.left) preOrder(root.right) // 回退 path!!.removeAt(path!!.size - 1) } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\n初始化二元樹") printTree(root) // 前序走訪 path = mutableListOf() res = mutableListOf() preOrder(root) println("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") for (path in res!!) { val _vals = mutableListOf() for (node in path) { _vals.add(node._val) } println(_vals) } } ================================================ FILE: zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt ================================================ /** * File: preorder_traversal_iii_template.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.preorder_traversal_iii_template import utils.TreeNode import utils.printTree /* 判斷當前狀態是否為解 */ fun isSolution(state: MutableList): Boolean { return state.isNotEmpty() && state[state.size - 1]?._val == 7 } /* 記錄解 */ fun recordSolution(state: MutableList?, res: MutableList?>) { res.add(state!!.toMutableList()) } /* 判斷在當前狀態下,該選擇是否合法 */ fun isValid(state: MutableList?, choice: TreeNode?): Boolean { return choice != null && choice._val != 3 } /* 更新狀態 */ fun makeChoice(state: MutableList, choice: TreeNode?) { state.add(choice) } /* 恢復狀態 */ fun undoChoice(state: MutableList, choice: TreeNode?) { state.removeLast() } /* 回溯演算法:例題三 */ fun backtrack( state: MutableList, choices: MutableList, res: MutableList?> ) { // 檢查是否為解 if (isSolution(state)) { // 記錄解 recordSolution(state, res) } // 走訪所有選擇 for (choice in choices) { // 剪枝:檢查選擇是否合法 if (isValid(state, choice)) { // 嘗試:做出選擇,更新狀態 makeChoice(state, choice) // 進行下一輪選擇 backtrack(state, mutableListOf(choice!!.left, choice.right), res) // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state, choice) } } } /* Driver Code */ fun main() { val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) println("\n初始化二元樹") printTree(root) // 回溯演算法 val res = mutableListOf?>() backtrack(mutableListOf(), mutableListOf(root), res) println("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點") for (path in res) { val vals = mutableListOf() for (node in path!!) { if (node != null) { vals.add(node._val) } } println(vals) } } ================================================ FILE: zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i.kt ================================================ /** * File: subset_sum_i.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_i /* 回溯演算法:子集和 I */ fun backtrack( state: MutableList, target: Int, choices: IntArray, start: Int, res: MutableList?> ) { // 子集和等於 target 時,記錄解 if (target == 0) { res.add(state.toMutableList()) return } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 for (i in start..?> { val state = mutableListOf() // 狀態(子集) nums.sort() // 對 nums 進行排序 val start = 0 // 走訪起始點 val res = mutableListOf?>() // 結果串列(子集串列) backtrack(state, target, nums, start, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(3, 4, 5) val target = 9 val res = subsetSumI(nums, target) println("輸入陣列 nums = ${nums.contentToString()}, target = $target") println("所有和等於 $target 的子集 res = $res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt ================================================ /** * File: subset_sum_i_native.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_i_naive /* 回溯演算法:子集和 I */ fun backtrack( state: MutableList, target: Int, total: Int, choices: IntArray, res: MutableList?> ) { // 子集和等於 target 時,記錄解 if (total == target) { res.add(state.toMutableList()) return } // 走訪所有選擇 for (i in choices.indices) { // 剪枝:若子集和超過 target ,則跳過該選擇 if (total + choices[i] > target) { continue } // 嘗試:做出選擇,更新元素和 total state.add(choices[i]) // 進行下一輪選擇 backtrack(state, target, total + choices[i], choices, res) // 回退:撤銷選擇,恢復到之前的狀態 state.removeAt(state.size - 1) } } /* 求解子集和 I(包含重複子集) */ fun subsetSumINaive(nums: IntArray, target: Int): MutableList?> { val state = mutableListOf() // 狀態(子集) val total = 0 // 子集和 val res = mutableListOf?>() // 結果串列(子集串列) backtrack(state, target, total, nums, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(3, 4, 5) val target = 9 val res = subsetSumINaive(nums, target) println("輸入陣列 nums = ${nums.contentToString()}, target = $target") println("所有和等於 $target 的子集 res = $res") println("請注意,該方法輸出的結果包含重複集合") } ================================================ FILE: zh-hant/codes/kotlin/chapter_backtracking/subset_sum_ii.kt ================================================ /** * File: subset_sum_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_backtracking.subset_sum_ii /* 回溯演算法:子集和 II */ fun backtrack( state: MutableList, target: Int, choices: IntArray, start: Int, res: MutableList?> ) { // 子集和等於 target 時,記錄解 if (target == 0) { res.add(state.toMutableList()) return } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 for (i in start.. start && choices[i] == choices[i - 1]) { continue } // 嘗試:做出選擇,更新 target, start state.add(choices[i]) // 進行下一輪選擇 backtrack(state, target - choices[i], choices, i + 1, res) // 回退:撤銷選擇,恢復到之前的狀態 state.removeAt(state.size - 1) } } /* 求解子集和 II */ fun subsetSumII(nums: IntArray, target: Int): MutableList?> { val state = mutableListOf() // 狀態(子集) nums.sort() // 對 nums 進行排序 val start = 0 // 走訪起始點 val res = mutableListOf?>() // 結果串列(子集串列) backtrack(state, target, nums, start, res) return res } /* Driver Code */ fun main() { val nums = intArrayOf(4, 4, 5) val target = 9 val res = subsetSumII(nums, target) println("輸入陣列 nums = ${nums.contentToString()}, target = $target") println("所有和等於 $target 的子集 res = $res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_computational_complexity/iteration.kt ================================================ /** * File: iteration.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.iteration /* for 迴圈 */ fun forLoop(n: Int): Int { var res = 0 // 迴圈求和 1, 2, ..., n-1, n for (i in 1..n) { res += i } return res } /* while 迴圈 */ fun whileLoop(n: Int): Int { var res = 0 var i = 1 // 初始化條件變數 // 迴圈求和 1, 2, ..., n-1, n while (i <= n) { res += i i++ // 更新條件變數 } return res } /* while 迴圈(兩次更新) */ fun whileLoopII(n: Int): Int { var res = 0 var i = 1 // 初始化條件變數 // 迴圈求和 1, 4, 10, ... while (i <= n) { res += i // 更新條件變數 i++ i *= 2 } return res } /* 雙層 for 迴圈 */ fun nestedForLoop(n: Int): String { val res = StringBuilder() // 迴圈 i = 1, 2, ..., n-1, n for (i in 1..n) { // 迴圈 j = 1, 2, ..., n-1, n for (j in 1..n) { res.append(" ($i, $j), ") } } return res.toString() } /* Driver Code */ fun main() { val n = 5 var res: Int res = forLoop(n) println("\nfor 迴圈的求和結果 res = $res") res = whileLoop(n) println("\nwhile 迴圈的求和結果 res = $res") res = whileLoopII(n) println("\nwhile 迴圈 (兩次更新) 求和結果 res = $res") val resStr = nestedForLoop(n) println("\n雙層 for 迴圈的走訪結果 $resStr") } ================================================ FILE: zh-hant/codes/kotlin/chapter_computational_complexity/recursion.kt ================================================ /** * File: recursion.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.recursion import java.util.* /* 遞迴 */ fun recur(n: Int): Int { // 終止條件 if (n == 1) return 1 // 遞: 遞迴呼叫 val res = recur(n - 1) // 迴: 返回結果 return n + res } /* 使用迭代模擬遞迴 */ fun forLoopRecur(n: Int): Int { // 使用一個顯式的堆疊來模擬系統呼叫堆疊 val stack = Stack() var res = 0 // 遞: 遞迴呼叫 for (i in n downTo 0) { // 透過“入堆疊操作”模擬“遞” stack.push(i) } // 迴: 返回結果 while (stack.isNotEmpty()) { // 透過“出堆疊操作”模擬“迴” res += stack.pop() } // res = 1+2+3+...+n return res } /* 尾遞迴 */ tailrec fun tailRecur(n: Int, res: Int): Int { // 新增 tailrec 關鍵詞,以開啟尾遞迴最佳化 // 終止條件 if (n == 0) return res // 尾遞迴呼叫 return tailRecur(n - 1, res + n) } /* 費波那契數列:遞迴 */ fun fib(n: Int): Int { // 終止條件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1 // 遞迴呼叫 f(n) = f(n-1) + f(n-2) val res = fib(n - 1) + fib(n - 2) // 返回結果 f(n) return res } /* Driver Code */ fun main() { val n = 5 var res: Int res = recur(n) println("\n遞迴函式的求和結果 res = $res") res = forLoopRecur(n) println("\n使用迭代模擬遞迴求和結果 res = $res") res = tailRecur(n, 0) println("\n尾遞迴函式的求和結果 res = $res") res = fib(n) println("\n費波那契數列的第 $n 項為 $res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_computational_complexity/space_complexity.kt ================================================ /** * File: space_complexity.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_computational_complexity.space_complexity import utils.ListNode import utils.TreeNode import utils.printTree /* 函式 */ fun function(): Int { // 執行某些操作 return 0 } /* 常數階 */ fun constant(n: Int) { // 常數、變數、物件佔用 O(1) 空間 val a = 0 var b = 0 val nums = Array(10000) { 0 } val node = ListNode(0) // 迴圈中的變數佔用 O(1) 空間 for (i in 0..() for (i in 0..() for (i in 0..?>(n) // 二維串列佔用 O(n^2) 空間 val numList = mutableListOf>() for (i in 0..() for (j in 0.. nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp count += 3 // 元素交換包含 3 個單元操作 } } } return count } /* 指數階(迴圈實現) */ fun exponential(n: Int): Int { var count = 0 var base = 1 // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) for (i in 0.. 1) { n1 /= 2 count++ } return count } /* 對數階(遞迴實現) */ fun logRecur(n: Int): Int { if (n <= 1) return 0 return logRecur(n / 2) + 1 } /* 線性對數階 */ fun linearLogRecur(n: Int): Int { if (n <= 1) return 1 var count = linearLogRecur(n / 2) + linearLogRecur(n / 2) for (i in 0.. { val nums = IntArray(n) // 生成陣列 nums = { 1, 2, 3, ..., n } for (i in 0..(n) for (i in 0..): Int { for (i in nums.indices) { // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) if (nums[i] == 1) return i } return -1 } /* Driver Code */ fun main() { for (i in 0..9) { val n = 100 val nums = randomNumbers(n) val index = findOne(nums) println("\n陣列 [ 1, 2, ..., n ] 被打亂後 = ${nums.contentToString()}") println("數字 1 的索引為 $index") } } ================================================ FILE: zh-hant/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt ================================================ /** * File: binary_search_recur.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.binary_search_recur /* 二分搜尋:問題 f(i, j) */ fun dfs( nums: IntArray, target: Int, i: Int, j: Int ): Int { // 若區間為空,代表無目標元素,則返回 -1 if (i > j) { return -1 } // 計算中點索引 m val m = (i + j) / 2 return if (nums[m] < target) { // 遞迴子問題 f(m+1, j) dfs(nums, target, m + 1, j) } else if (nums[m] > target) { // 遞迴子問題 f(i, m-1) dfs(nums, target, i, m - 1) } else { // 找到目標元素,返回其索引 m } } /* 二分搜尋 */ fun binarySearch(nums: IntArray, target: Int): Int { val n = nums.size // 求解問題 f(0, n-1) return dfs(nums, target, 0, n - 1) } /* Driver Code */ fun main() { val target = 6 val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) // 二分搜尋(雙閉區間) val index = binarySearch(nums, target) println("目標元素 6 的索引 = $index") } ================================================ FILE: zh-hant/codes/kotlin/chapter_divide_and_conquer/build_tree.kt ================================================ /** * File: build_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.build_tree import utils.TreeNode import utils.printTree /* 構建二元樹:分治 */ fun dfs( preorder: IntArray, inorderMap: Map, i: Int, l: Int, r: Int ): TreeNode? { // 子樹區間為空時終止 if (r - l < 0) return null // 初始化根節點 val root = TreeNode(preorder[i]) // 查詢 m ,從而劃分左右子樹 val m = inorderMap[preorder[i]]!! // 子問題:構建左子樹 root.left = dfs(preorder, inorderMap, i + 1, l, m - 1) // 子問題:構建右子樹 root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r) // 返回根節點 return root } /* 構建二元樹 */ fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? { // 初始化雜湊表,儲存 inorder 元素到索引的對映 val inorderMap = HashMap() for (i in inorder.indices) { inorderMap[inorder[i]] = i } val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1) return root } /* Driver Code */ fun main() { val preorder = intArrayOf(3, 9, 2, 1, 7) val inorder = intArrayOf(9, 3, 1, 2, 7) println("前序走訪 = ${preorder.contentToString()}") println("中序走訪 = ${inorder.contentToString()}") val root = buildTree(preorder, inorder) println("構建的二元樹為:") printTree(root) } ================================================ FILE: zh-hant/codes/kotlin/chapter_divide_and_conquer/hanota.kt ================================================ /** * File: hanota.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_divide_and_conquer.hanota /* 移動一個圓盤 */ fun move(src: MutableList, tar: MutableList) { // 從 src 頂部拿出一個圓盤 val pan = src.removeAt(src.size - 1) // 將圓盤放入 tar 頂部 tar.add(pan) } /* 求解河內塔問題 f(i) */ fun dfs(i: Int, src: MutableList, buf: MutableList, tar: MutableList) { // 若 src 只剩下一個圓盤,則直接將其移到 tar if (i == 1) { move(src, tar) return } // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf dfs(i - 1, src, tar, buf) // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar move(src, tar) // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar dfs(i - 1, buf, src, tar) } /* 求解河內塔問題 */ fun solveHanota(A: MutableList, B: MutableList, C: MutableList) { val n = A.size // 將 A 頂部 n 個圓盤藉助 B 移到 C dfs(n, A, B, C) } /* Driver Code */ fun main() { // 串列尾部是柱子頂部 val A = mutableListOf(5, 4, 3, 2, 1) val B = mutableListOf() val C = mutableListOf() println("初始狀態下:") println("A = $A") println("B = $B") println("C = $C") solveHanota(A, B, C) println("圓盤移動完成後:") println("A = $A") println("B = $B") println("C = $C") } ================================================ FILE: zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt ================================================ /** * File: climbing_stairs_backtrack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 回溯 */ fun backtrack( choices: MutableList, state: Int, n: Int, res: MutableList ) { // 當爬到第 n 階時,方案數量加 1 if (state == n) res[0] = res[0] + 1 // 走訪所有選擇 for (choice in choices) { // 剪枝:不允許越過第 n 階 if (state + choice > n) continue // 嘗試:做出選擇,更新狀態 backtrack(choices, state + choice, n, res) // 回退 } } /* 爬樓梯:回溯 */ fun climbingStairsBacktrack(n: Int): Int { val choices = mutableListOf(1, 2) // 可選擇向上爬 1 階或 2 階 val state = 0 // 從第 0 階開始爬 val res = mutableListOf() res.add(0) // 使用 res[0] 記錄方案數量 backtrack(choices, state, n, res) return res[0] } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsBacktrack(n) println("爬 $n 階樓梯共有 $res 種方案") } ================================================ FILE: zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt ================================================ /** * File: climbing_stairs_constraint_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 帶約束爬樓梯:動態規劃 */ fun climbingStairsConstraintDP(n: Int): Int { if (n == 1 || n == 2) { return 1 } // 初始化 dp 表,用於儲存子問題的解 val dp = Array(n + 1) { IntArray(3) } // 初始狀態:預設最小子問題的解 dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // 狀態轉移:從較小子問題逐步求解較大子問題 for (i in 3..n) { dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] } return dp[n][1] + dp[n][2] } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsConstraintDP(n) println("爬 $n 階樓梯共有 $res 種方案") } ================================================ FILE: zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt ================================================ /** * File: climbing_stairs_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 搜尋 */ fun dfs(i: Int): Int { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i // dp[i] = dp[i-1] + dp[i-2] val count = dfs(i - 1) + dfs(i - 2) return count } /* 爬樓梯:搜尋 */ fun climbingStairsDFS(n: Int): Int { return dfs(n) } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsDFS(n) println("爬 $n 階樓梯共有 $res 種方案") } ================================================ FILE: zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt ================================================ /** * File: climbing_stairs_dfs_mem.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 記憶化搜尋 */ fun dfs(i: Int, mem: IntArray): Int { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 || i == 2) return i // 若存在記錄 dp[i] ,則直接返回之 if (mem[i] != -1) return mem[i] // dp[i] = dp[i-1] + dp[i-2] val count = dfs(i - 1, mem) + dfs(i - 2, mem) // 記錄 dp[i] mem[i] = count return count } /* 爬樓梯:記憶化搜尋 */ fun climbingStairsDFSMem(n: Int): Int { // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 val mem = IntArray(n + 1) mem.fill(-1) return dfs(n, mem) } /* Driver Code */ fun main() { val n = 9 val res = climbingStairsDFSMem(n) println("爬 $n 階樓梯共有 $res 種方案") } ================================================ FILE: zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt ================================================ /** * File: climbing_stairs_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 爬樓梯:動態規劃 */ fun climbingStairsDP(n: Int): Int { if (n == 1 || n == 2) return n // 初始化 dp 表,用於儲存子問題的解 val dp = IntArray(n + 1) // 初始狀態:預設最小子問題的解 dp[1] = 1 dp[2] = 2 // 狀態轉移:從較小子問題逐步求解較大子問題 for (i in 3..n) { dp[i] = dp[i - 1] + dp[i - 2] } return dp[n] } /* 爬樓梯:空間最佳化後的動態規劃 */ fun climbingStairsDPComp(n: Int): Int { if (n == 1 || n == 2) return n var a = 1 var b = 2 for (i in 3..n) { val temp = b b += a a = temp } return b } /* Driver Code */ fun main() { val n = 9 var res = climbingStairsDP(n) println("爬 $n 階樓梯共有 $res 種方案") res = climbingStairsDPComp(n) println("爬 $n 階樓梯共有 $res 種方案") } ================================================ FILE: zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change.kt ================================================ /** * File: coin_change.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* 零錢兌換:動態規劃 */ fun coinChangeDP(coins: IntArray, amt: Int): Int { val n = coins.size val MAX = amt + 1 // 初始化 dp 表 val dp = Array(n + 1) { IntArray(amt + 1) } // 狀態轉移:首行首列 for (a in 1..amt) { dp[0][a] = MAX } // 狀態轉移:其餘行和列 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a] } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) } } } return if (dp[n][amt] != MAX) dp[n][amt] else -1 } /* 零錢兌換:空間最佳化後的動態規劃 */ fun coinChangeDPComp(coins: IntArray, amt: Int): Int { val n = coins.size val MAX = amt + 1 // 初始化 dp 表 val dp = IntArray(amt + 1) dp.fill(MAX) dp[0] = 0 // 狀態轉移 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a] } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) } } } return if (dp[amt] != MAX) dp[amt] else -1 } /* Driver Code */ fun main() { val coins = intArrayOf(1, 2, 5) val amt = 4 // 動態規劃 var res = coinChangeDP(coins, amt) println("湊到目標金額所需的最少硬幣數量為 $res") // 空間最佳化後的動態規劃 res = coinChangeDPComp(coins, amt) println("湊到目標金額所需的最少硬幣數量為 $res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt ================================================ /** * File: coin_change_ii.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming /* 零錢兌換 II:動態規劃 */ fun coinChangeIIDP(coins: IntArray, amt: Int): Int { val n = coins.size // 初始化 dp 表 val dp = Array(n + 1) { IntArray(amt + 1) } // 初始化首列 for (i in 0..n) { dp[i][0] = 1 } // 狀態轉移 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a] } else { // 不選和選硬幣 i 這兩種方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] } } } return dp[n][amt] } /* 零錢兌換 II:空間最佳化後的動態規劃 */ fun coinChangeIIDPComp(coins: IntArray, amt: Int): Int { val n = coins.size // 初始化 dp 表 val dp = IntArray(amt + 1) dp[0] = 1 // 狀態轉移 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a] } else { // 不選和選硬幣 i 這兩種方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]] } } } return dp[amt] } /* Driver Code */ fun main() { val coins = intArrayOf(1, 2, 5) val amt = 5 // 動態規劃 var res = coinChangeIIDP(coins, amt) println("湊出目標金額的硬幣組合數量為 $res") // 空間最佳化後的動態規劃 res = coinChangeIIDPComp(coins, amt) println("湊出目標金額的硬幣組合數量為 $res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_dynamic_programming/edit_distance.kt ================================================ /** * File: edit_distance.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* 編輯距離:暴力搜尋 */ fun editDistanceDFS( s: String, t: String, i: Int, j: Int ): Int { // 若 s 和 t 都為空,則返回 0 if (i == 0 && j == 0) return 0 // 若 s 為空,則返回 t 長度 if (i == 0) return j // 若 t 為空,則返回 s 長度 if (j == 0) return i // 若兩字元相等,則直接跳過此兩字元 if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1) // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 val insert = editDistanceDFS(s, t, i, j - 1) val delete = editDistanceDFS(s, t, i - 1, j) val replace = editDistanceDFS(s, t, i - 1, j - 1) // 返回最少編輯步數 return min(min(insert, delete), replace) + 1 } /* 編輯距離:記憶化搜尋 */ fun editDistanceDFSMem( s: String, t: String, mem: Array, i: Int, j: Int ): Int { // 若 s 和 t 都為空,則返回 0 if (i == 0 && j == 0) return 0 // 若 s 為空,則返回 t 長度 if (i == 0) return j // 若 t 為空,則返回 s 長度 if (j == 0) return i // 若已有記錄,則直接返回之 if (mem[i][j] != -1) return mem[i][j] // 若兩字元相等,則直接跳過此兩字元 if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1) // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 val insert = editDistanceDFSMem(s, t, mem, i, j - 1) val delete = editDistanceDFSMem(s, t, mem, i - 1, j) val replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1) // 記錄並返回最少編輯步數 mem[i][j] = min(min(insert, delete), replace) + 1 return mem[i][j] } /* 編輯距離:動態規劃 */ fun editDistanceDP(s: String, t: String): Int { val n = s.length val m = t.length val dp = Array(n + 1) { IntArray(m + 1) } // 狀態轉移:首行首列 for (i in 1..n) { dp[i][0] = i } for (j in 1..m) { dp[0][j] = j } // 狀態轉移:其餘行和列 for (i in 1..n) { for (j in 1..m) { if (s[i - 1] == t[j - 1]) { // 若兩字元相等,則直接跳過此兩字元 dp[i][j] = dp[i - 1][j - 1] } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 } } } return dp[n][m] } /* 編輯距離:空間最佳化後的動態規劃 */ fun editDistanceDPComp(s: String, t: String): Int { val n = s.length val m = t.length val dp = IntArray(m + 1) // 狀態轉移:首行 for (j in 1..m) { dp[j] = j } // 狀態轉移:其餘行 for (i in 1..n) { // 狀態轉移:首列 var leftup = dp[0] // 暫存 dp[i-1, j-1] dp[0] = i // 狀態轉移:其餘列 for (j in 1..m) { val temp = dp[j] if (s[i - 1] == t[j - 1]) { // 若兩字元相等,則直接跳過此兩字元 dp[j] = leftup } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 } leftup = temp // 更新為下一輪的 dp[i-1, j-1] } } return dp[m] } /* Driver Code */ fun main() { val s = "bag" val t = "pack" val n = s.length val m = t.length // 暴力搜尋 var res = editDistanceDFS(s, t, n, m) println("將 $s 更改為 $t 最少需要編輯 $res 步") // 記憶化搜尋 val mem = Array(n + 1) { IntArray(m + 1) } for (row in mem) row.fill(-1) res = editDistanceDFSMem(s, t, mem, n, m) println("將 $s 更改為 $t 最少需要編輯 $res 步") // 動態規劃 res = editDistanceDP(s, t) println("將 $s 更改為 $t 最少需要編輯 $res 步") // 空間最佳化後的動態規劃 res = editDistanceDPComp(s, t) println("將 $s 更改為 $t 最少需要編輯 $res 步") } ================================================ FILE: zh-hant/codes/kotlin/chapter_dynamic_programming/knapsack.kt ================================================ /** * File: knapsack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.max /* 0-1 背包:暴力搜尋 */ fun knapsackDFS( wgt: IntArray, _val: IntArray, i: Int, c: Int ): Int { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i == 0 || c == 0) { return 0 } // 若超過背包容量,則只能選擇不放入背包 if (wgt[i - 1] > c) { return knapsackDFS(wgt, _val, i - 1, c) } // 計算不放入和放入物品 i 的最大價值 val no = knapsackDFS(wgt, _val, i - 1, c) val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1] // 返回兩種方案中價值更大的那一個 return max(no, yes) } /* 0-1 背包:記憶化搜尋 */ fun knapsackDFSMem( wgt: IntArray, _val: IntArray, mem: Array, i: Int, c: Int ): Int { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i == 0 || c == 0) { return 0 } // 若已有記錄,則直接返回 if (mem[i][c] != -1) { return mem[i][c] } // 若超過背包容量,則只能選擇不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, _val, mem, i - 1, c) } // 計算不放入和放入物品 i 的最大價值 val no = knapsackDFSMem(wgt, _val, mem, i - 1, c) val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1] // 記錄並返回兩種方案中價值更大的那一個 mem[i][c] = max(no, yes) return mem[i][c] } /* 0-1 背包:動態規劃 */ fun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int { val n = wgt.size // 初始化 dp 表 val dp = Array(n + 1) { IntArray(cap + 1) } // 狀態轉移 for (i in 1..n) { for (c in 1..cap) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c] } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1]) } } } return dp[n][cap] } /* 0-1 背包:空間最佳化後的動態規劃 */ fun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int { val n = wgt.size // 初始化 dp 表 val dp = IntArray(cap + 1) // 狀態轉移 for (i in 1..n) { // 倒序走訪 for (c in cap downTo 1) { if (wgt[i - 1] <= c) { // 不選和選物品 i 這兩種方案的較大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) } } } return dp[cap] } /* Driver Code */ fun main() { val wgt = intArrayOf(10, 20, 30, 40, 50) val _val = intArrayOf(50, 120, 150, 210, 240) val cap = 50 val n = wgt.size // 暴力搜尋 var res = knapsackDFS(wgt, _val, n, cap) println("不超過背包容量的最大物品價值為 $res") // 記憶化搜尋 val mem = Array(n + 1) { IntArray(cap + 1) } for (row in mem) { row.fill(-1) } res = knapsackDFSMem(wgt, _val, mem, n, cap) println("不超過背包容量的最大物品價值為 $res") // 動態規劃 res = knapsackDP(wgt, _val, cap) println("不超過背包容量的最大物品價值為 $res") // 空間最佳化後的動態規劃 res = knapsackDPComp(wgt, _val, cap) println("不超過背包容量的最大物品價值為 $res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt ================================================ /** * File: min_cost_climbing_stairs_dp.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* 爬樓梯最小代價:動態規劃 */ fun minCostClimbingStairsDP(cost: IntArray): Int { val n = cost.size - 1 if (n == 1 || n == 2) return cost[n] // 初始化 dp 表,用於儲存子問題的解 val dp = IntArray(n + 1) // 初始狀態:預設最小子問題的解 dp[1] = cost[1] dp[2] = cost[2] // 狀態轉移:從較小子問題逐步求解較大子問題 for (i in 3..n) { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] } return dp[n] } /* 爬樓梯最小代價:空間最佳化後的動態規劃 */ fun minCostClimbingStairsDPComp(cost: IntArray): Int { val n = cost.size - 1 if (n == 1 || n == 2) return cost[n] var a = cost[1] var b = cost[2] for (i in 3..n) { val tmp = b b = min(a, tmp) + cost[i] a = tmp } return b } /* Driver Code */ fun main() { val cost = intArrayOf(0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1) println("輸入樓梯的代價串列為 ${cost.contentToString()}") var res = minCostClimbingStairsDP(cost) println("爬完樓梯的最低代價為 $res") res = minCostClimbingStairsDPComp(cost) println("爬完樓梯的最低代價為 $res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt ================================================ /** * File: min_path_sum.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_dynamic_programming import kotlin.math.min /* 最小路徑和:暴力搜尋 */ fun minPathSumDFS(grid: Array, i: Int, j: Int): Int { // 若為左上角單元格,則終止搜尋 if (i == 0 && j == 0) { return grid[0][0] } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 || j < 0) { return Int.MAX_VALUE } // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 val up = minPathSumDFS(grid, i - 1, j) val left = minPathSumDFS(grid, i, j - 1) // 返回從左上角到 (i, j) 的最小路徑代價 return min(left, up) + grid[i][j] } /* 最小路徑和:記憶化搜尋 */ fun minPathSumDFSMem( grid: Array, mem: Array, i: Int, j: Int ): Int { // 若為左上角單元格,則終止搜尋 if (i == 0 && j == 0) { return grid[0][0] } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 || j < 0) { return Int.MAX_VALUE } // 若已有記錄,則直接返回 if (mem[i][j] != -1) { return mem[i][j] } // 左邊和上邊單元格的最小路徑代價 val up = minPathSumDFSMem(grid, mem, i - 1, j) val left = minPathSumDFSMem(grid, mem, i, j - 1) // 記錄並返回左上角到 (i, j) 的最小路徑代價 mem[i][j] = min(left, up) + grid[i][j] return mem[i][j] } /* 最小路徑和:動態規劃 */ fun minPathSumDP(grid: Array): Int { val n = grid.size val m = grid[0].size // 初始化 dp 表 val dp = Array(n) { IntArray(m) } dp[0][0] = grid[0][0] // 狀態轉移:首行 for (j in 1..): Int { val n = grid.size val m = grid[0].size // 初始化 dp 表 val dp = IntArray(m) // 狀態轉移:首行 dp[0] = grid[0][0] for (j in 1.. c) { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c] } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1]) } } } return dp[n][cap] } /* 完全背包:空間最佳化後的動態規劃 */ fun unboundedKnapsackDPComp( wgt: IntArray, _val: IntArray, cap: Int ): Int { val n = wgt.size // 初始化 dp 表 val dp = IntArray(cap + 1) // 狀態轉移 for (i in 1..n) { for (c in 1..cap) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[c] = dp[c] } else { // 不選和選物品 i 這兩種方案的較大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) } } } return dp[cap] } /* Driver Code */ fun main() { val wgt = intArrayOf(1, 2, 3) val _val = intArrayOf(5, 11, 15) val cap = 4 // 動態規劃 var res = unboundedKnapsackDP(wgt, _val, cap) println("不超過背包容量的最大物品價值為 $res") // 空間最佳化後的動態規劃 res = unboundedKnapsackDPComp(wgt, _val, cap) println("不超過背包容量的最大物品價值為 $res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_graph/graph_adjacency_list.kt ================================================ /** * File: graph_adjacency_list.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex /* 基於鄰接表實現的無向圖類別 */ class GraphAdjList(edges: Array>) { // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 val adjList = HashMap>() /* 建構子 */ init { // 新增所有頂點和邊 for (edge in edges) { addVertex(edge[0]!!) addVertex(edge[1]!!) addEdge(edge[0]!!, edge[1]!!) } } /* 獲取頂點數量 */ fun size(): Int { return adjList.size } /* 新增邊 */ fun addEdge(vet1: Vertex, vet2: Vertex) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) throw IllegalArgumentException() // 新增邊 vet1 - vet2 adjList[vet1]?.add(vet2) adjList[vet2]?.add(vet1) } /* 刪除邊 */ fun removeEdge(vet1: Vertex, vet2: Vertex) { if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) throw IllegalArgumentException() // 刪除邊 vet1 - vet2 adjList[vet1]?.remove(vet2) adjList[vet2]?.remove(vet1) } /* 新增頂點 */ fun addVertex(vet: Vertex) { if (adjList.containsKey(vet)) return // 在鄰接表中新增一個新鏈結串列 adjList[vet] = mutableListOf() } /* 刪除頂點 */ fun removeVertex(vet: Vertex) { if (!adjList.containsKey(vet)) throw IllegalArgumentException() // 在鄰接表中刪除頂點 vet 對應的鏈結串列 adjList.remove(vet) // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 for (list in adjList.values) { list.remove(vet) } } /* 列印鄰接表 */ fun print() { println("鄰接表 =") for (pair in adjList.entries) { val tmp = mutableListOf() for (vertex in pair.value) { tmp.add(vertex._val) } println("${pair.key._val}: $tmp,") } } } /* Driver Code */ fun main() { /* 初始化無向圖 */ val v = Vertex.valsToVets(intArrayOf(1, 3, 2, 5, 4)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[2], v[3]), arrayOf(v[2], v[4]), arrayOf(v[3], v[4]) ) val graph = GraphAdjList(edges) println("\n初始化後,圖為") graph.print() /* 新增邊 */ // 頂點 1, 2 即 v[0], v[2] graph.addEdge(v[0]!!, v[2]!!) println("\n新增邊 1-2 後,圖為") graph.print() /* 刪除邊 */ // 頂點 1, 3 即 v[0], v[1] graph.removeEdge(v[0]!!, v[1]!!) println("\n刪除邊 1-3 後,圖為") graph.print() /* 新增頂點 */ val v5 = Vertex(6) graph.addVertex(v5) println("\n新增頂點 6 後,圖為") graph.print() /* 刪除頂點 */ // 頂點 3 即 v[1] graph.removeVertex(v[1]!!) println("\n刪除頂點 3 後,圖為") graph.print() } ================================================ FILE: zh-hant/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt ================================================ /** * File: graph_adjacency_matrix.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.printMatrix /* 基於鄰接矩陣實現的無向圖類別 */ class GraphAdjMat(vertices: IntArray, edges: Array) { val vertices = mutableListOf() // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” val adjMat = mutableListOf>() // 鄰接矩陣,行列索引對應“頂點索引” /* 建構子 */ init { // 新增頂點 for (vertex in vertices) { addVertex(vertex) } // 新增邊 // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 for (edge in edges) { addEdge(edge[0], edge[1]) } } /* 獲取頂點數量 */ fun size(): Int { return vertices.size } /* 新增頂點 */ fun addVertex(_val: Int) { val n = size() // 向頂點串列中新增新頂點的值 vertices.add(_val) // 在鄰接矩陣中新增一行 val newRow = mutableListOf() for (j in 0..= size()) throw IndexOutOfBoundsException() // 在頂點串列中移除索引 index 的頂點 vertices.removeAt(index) // 在鄰接矩陣中刪除索引 index 的行 adjMat.removeAt(index) // 在鄰接矩陣中刪除索引 index 的列 for (row in adjMat) { row.removeAt(index) } } /* 新增邊 */ // 參數 i, j 對應 vertices 元素索引 fun addEdge(i: Int, j: Int) { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw IndexOutOfBoundsException() // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) adjMat[i][j] = 1 adjMat[j][i] = 1 } /* 刪除邊 */ // 參數 i, j 對應 vertices 元素索引 fun removeEdge(i: Int, j: Int) { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw IndexOutOfBoundsException() adjMat[i][j] = 0 adjMat[j][i] = 0 } /* 列印鄰接矩陣 */ fun print() { print("頂點串列 = ") println(vertices) println("鄰接矩陣 =") printMatrix(adjMat) } } /* Driver Code */ fun main() { /* 初始化無向圖 */ // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 val vertices = intArrayOf(1, 3, 2, 5, 4) val edges = arrayOf( intArrayOf(0, 1), intArrayOf(0, 3), intArrayOf(1, 2), intArrayOf(2, 3), intArrayOf(2, 4), intArrayOf(3, 4) ) val graph = GraphAdjMat(vertices, edges) println("\n初始化後,圖為") graph.print() /* 新增邊 */ // 頂點 1, 2 的索引分別為 0, 2 graph.addEdge(0, 2) println("\n新增邊 1-2 後,圖為") graph.print() /* 刪除邊 */ // 頂點 1, 3 的索引分別為 0, 1 graph.removeEdge(0, 1) println("\n刪除邊 1-3 後,圖為") graph.print() /* 新增頂點 */ graph.addVertex(6) println("\n新增頂點 6 後,圖為") graph.print() /* 刪除頂點 */ // 頂點 3 的索引為 1 graph.removeVertex(1) println("\n刪除頂點 3 後,圖為") graph.print() } ================================================ FILE: zh-hant/codes/kotlin/chapter_graph/graph_bfs.kt ================================================ /** * File: graph_bfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex import java.util.* /* 廣度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList { // 頂點走訪序列 val res = mutableListOf() // 雜湊集合,用於記錄已被訪問過的頂點 val visited = HashSet() visited.add(startVet) // 佇列用於實現 BFS val que = LinkedList() que.offer(startVet) // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 while (!que.isEmpty()) { val vet = que.poll() // 佇列首頂點出隊 res.add(vet) // 記錄訪問頂點 // 走訪該頂點的所有鄰接頂點 for (adjVet in graph.adjList[vet]!!) { if (visited.contains(adjVet)) continue // 跳過已被訪問的頂點 que.offer(adjVet) // 只入列未訪問的頂點 visited.add(adjVet) // 標記該頂點已被訪問 } } // 返回頂點走訪序列 return res } /* Driver Code */ fun main() { /* 初始化無向圖 */ val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[1], v[4]), arrayOf(v[2], v[5]), arrayOf(v[3], v[4]), arrayOf(v[3], v[6]), arrayOf(v[4], v[5]), arrayOf(v[4], v[7]), arrayOf(v[5], v[8]), arrayOf(v[6], v[7]), arrayOf(v[7], v[8]) ) val graph = GraphAdjList(edges) println("\n初始化後,圖為") graph.print() /* 廣度優先走訪 */ val res = graphBFS(graph, v[0]!!) println("\n廣度優先走訪(BFS)頂點序列為") println(Vertex.vetsToVals(res)) } ================================================ FILE: zh-hant/codes/kotlin/chapter_graph/graph_dfs.kt ================================================ /** * File: graph_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_graph import utils.Vertex /* 深度優先走訪輔助函式 */ fun dfs( graph: GraphAdjList, visited: MutableSet, res: MutableList, vet: Vertex? ) { res.add(vet) // 記錄訪問頂點 visited.add(vet) // 標記該頂點已被訪問 // 走訪該頂點的所有鄰接頂點 for (adjVet in graph.adjList[vet]!!) { if (visited.contains(adjVet)) continue // 跳過已被訪問的頂點 // 遞迴訪問鄰接頂點 dfs(graph, visited, res, adjVet) } } /* 深度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 fun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList { // 頂點走訪序列 val res = mutableListOf() // 雜湊集合,用於記錄已被訪問過的頂點 val visited = HashSet() dfs(graph, visited, res, startVet) return res } /* Driver Code */ fun main() { /* 初始化無向圖 */ val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6)) val edges = arrayOf( arrayOf(v[0], v[1]), arrayOf(v[0], v[3]), arrayOf(v[1], v[2]), arrayOf(v[2], v[5]), arrayOf(v[4], v[5]), arrayOf(v[5], v[6]) ) val graph = GraphAdjList(edges) println("\n初始化後,圖為") graph.print() /* 深度優先走訪 */ val res = graphDFS(graph, v[0]) println("\n深度優先走訪(DFS)頂點序列為") println(Vertex.vetsToVals(res)) } ================================================ FILE: zh-hant/codes/kotlin/chapter_greedy/coin_change_greedy.kt ================================================ /** * File: coin_change_greedy.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy /* 零錢兌換:貪婪 */ fun coinChangeGreedy(coins: IntArray, amt: Int): Int { // 假設 coins 串列有序 var am = amt var i = coins.size - 1 var count = 0 // 迴圈進行貪婪選擇,直到無剩餘金額 while (am > 0) { // 找到小於且最接近剩餘金額的硬幣 while (i > 0 && coins[i] > am) { i-- } // 選擇 coins[i] am -= coins[i] count++ } // 若未找到可行方案,則返回 -1 return if (am == 0) count else -1 } /* Driver Code */ fun main() { // 貪婪:能夠保證找到全域性最優解 var coins = intArrayOf(1, 5, 10, 20, 50, 100) var amt = 186 var res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("湊到 $amt 所需的最少硬幣數量為 $res") // 貪婪:無法保證找到全域性最優解 coins = intArrayOf(1, 20, 50) amt = 60 res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("湊到 $amt 所需的最少硬幣數量為 $res") println("實際上需要的最少數量為 3 ,即 20 + 20 + 20") // 貪婪:無法保證找到全域性最優解 coins = intArrayOf(1, 49, 50) amt = 98 res = coinChangeGreedy(coins, amt) println("\ncoins = ${coins.contentToString()}, amt = $amt") println("湊到 $amt 所需的最少硬幣數量為 $res") println("實際上需要的最少數量為 2 ,即 49 + 49") } ================================================ FILE: zh-hant/codes/kotlin/chapter_greedy/fractional_knapsack.kt ================================================ /** * File: fractional_knapsack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy /* 物品 */ class Item( val w: Int, // 物品 val v: Int // 物品價值 ) /* 分數背包:貪婪 */ fun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double { // 建立物品串列,包含兩個屬性:重量、價值 var cap = c val items = arrayOfNulls(wgt.size) for (i in wgt.indices) { items[i] = Item(wgt[i], _val[i]) } // 按照單位價值 item.v / item.w 從高到低進行排序 items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) } // 迴圈貪婪選擇 var res = 0.0 for (item in items) { if (item!!.w <= cap) { // 若剩餘容量充足,則將當前物品整個裝進背包 res += item.v cap -= item.w } else { // 若剩餘容量不足,則將當前物品的一部分裝進背包 res += item.v.toDouble() / item.w * cap // 已無剩餘容量,因此跳出迴圈 break } } return res } /* Driver Code */ fun main() { val wgt = intArrayOf(10, 20, 30, 40, 50) val _val = intArrayOf(50, 120, 150, 210, 240) val cap = 50 // 貪婪演算法 val res = fractionalKnapsack(wgt, _val, cap) println("不超過背包容量的最大物品價值為 $res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_greedy/max_capacity.kt ================================================ /** * File: max_capacity.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy import kotlin.math.max import kotlin.math.min /* 最大容量:貪婪 */ fun maxCapacity(ht: IntArray): Int { // 初始化 i, j,使其分列陣列兩端 var i = 0 var j = ht.size - 1 // 初始最大容量為 0 var res = 0 // 迴圈貪婪選擇,直至兩板相遇 while (i < j) { // 更新最大容量 val cap = min(ht[i], ht[j]) * (j - i) res = max(res, cap) // 向內移動短板 if (ht[i] < ht[j]) { i++ } else { j-- } } return res } /* Driver Code */ fun main() { val ht = intArrayOf(3, 8, 5, 2, 7, 7, 3, 4) // 貪婪演算法 val res = maxCapacity(ht) println("最大容量為 $res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_greedy/max_product_cutting.kt ================================================ /** * File: max_product_cutting.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_greedy import kotlin.math.pow /* 最大切分乘積:貪婪 */ fun maxProductCutting(n: Int): Int { // 當 n <= 3 時,必須切分出一個 1 if (n <= 3) { return 1 * (n - 1) } // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 val a = n / 3 val b = n % 3 if (b == 1) { // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 return 3.0.pow((a - 1)).toInt() * 2 * 2 } if (b == 2) { // 當餘數為 2 時,不做處理 return 3.0.pow(a).toInt() * 2 * 2 } // 當餘數為 0 時,不做處理 return 3.0.pow(a).toInt() } /* Driver Code */ fun main() { val n = 58 // 貪婪演算法 val res = maxProductCutting(n) println("最大切分乘積為 $res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_hashing/array_hash_map.kt ================================================ /** * File: array_hash_map.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* 鍵值對 */ class Pair( var key: Int, var _val: String ) /* 基於陣列實現的雜湊表 */ class ArrayHashMap { // 初始化陣列,包含 100 個桶 private val buckets = arrayOfNulls(100) /* 雜湊函式 */ fun hashFunc(key: Int): Int { val index = key % 100 return index } /* 查詢操作 */ fun get(key: Int): String? { val index = hashFunc(key) val pair = buckets[index] ?: return null return pair._val } /* 新增操作 */ fun put(key: Int, _val: String) { val pair = Pair(key, _val) val index = hashFunc(key) buckets[index] = pair } /* 刪除操作 */ fun remove(key: Int) { val index = hashFunc(key) // 置為 null ,代表刪除 buckets[index] = null } /* 獲取所有鍵值對 */ fun pairSet(): MutableList { val pairSet = mutableListOf() for (pair in buckets) { if (pair != null) pairSet.add(pair) } return pairSet } /* 獲取所有鍵 */ fun keySet(): MutableList { val keySet = mutableListOf() for (pair in buckets) { if (pair != null) keySet.add(pair.key) } return keySet } /* 獲取所有值 */ fun valueSet(): MutableList { val valueSet = mutableListOf() for (pair in buckets) { if (pair != null) valueSet.add(pair._val) } return valueSet } /* 列印雜湊表 */ fun print() { for (kv in pairSet()) { val key = kv.key val _val = kv._val println("$key -> $_val") } } } /* Driver Code */ fun main() { /* 初始化雜湊表 */ val map = ArrayHashMap() /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(12836, "小哈") map.put(15937, "小囉") map.put(16750, "小算") map.put(13276, "小法") map.put(10583, "小鴨") println("\n新增完成後,雜湊表為\nKey -> Value") map.print() /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value val name = map.get(15937) println("\n輸入學號 15937 ,查詢到姓名 $name") /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(10583) println("\n刪除 10583 後,雜湊表為\nKey -> Value") map.print() /* 走訪雜湊表 */ println("\n走訪鍵值對 Key -> Value") for (kv in map.pairSet()) { println("${kv.key} -> ${kv._val}") } println("\n單獨走訪鍵 Key") for (key in map.keySet()) { println(key) } println("\n單獨走訪值 Value") for (_val in map.valueSet()) { println(_val) } } ================================================ FILE: zh-hant/codes/kotlin/chapter_hashing/built_in_hash.kt ================================================ /** * File: built_in_hash.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing import utils.ListNode /* Driver Code */ fun main() { val num = 3 val hashNum = num.hashCode() println("整數 $num 的雜湊值為 $hashNum") val bol = true val hashBol = bol.hashCode() println("布林量 $bol 的雜湊值為 $hashBol") val dec = 3.14159 val hashDec = dec.hashCode() println("小數 $dec 的雜湊值為 $hashDec") val str = "Hello 演算法" val hashStr = str.hashCode() println("字串 $str 的雜湊值為 $hashStr") val arr = arrayOf(12836, "小哈") val hashTup = arr.contentHashCode() println("陣列 ${arr.contentToString()} 的雜湊值為 $hashTup") val obj = ListNode(0) val hashObj = obj.hashCode() println("節點物件 $obj 的雜湊值為 $hashObj") } ================================================ FILE: zh-hant/codes/kotlin/chapter_hashing/hash_map.kt ================================================ /** * File: hash_map.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing import utils.printHashMap /* Driver Code */ fun main() { /* 初始化雜湊表 */ val map = HashMap() /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map[12836] = "小哈" map[15937] = "小囉" map[16750] = "小算" map[13276] = "小法" map[10583] = "小鴨" println("\n新增完成後,雜湊表為\nKey -> Value") printHashMap(map) /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value val name = map[15937] println("\n輸入學號 15937 ,查詢到姓名 $name") /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(10583) println("\n刪除 10583 後,雜湊表為\nKey -> Value") printHashMap(map) /* 走訪雜湊表 */ println("\n走訪鍵值對 Key->Value") for ((key, value) in map) { println("$key -> $value") } println("\n單獨走訪鍵 Key") for (key in map.keys) { println(key) } println("\n單獨走訪值 Value") for (_val in map.values) { println(_val) } } ================================================ FILE: zh-hant/codes/kotlin/chapter_hashing/hash_map_chaining.kt ================================================ /** * File: hash_map_chaining.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* 鏈式位址雜湊表 */ class HashMapChaining { var size: Int // 鍵值對數量 var capacity: Int // 雜湊表容量 val loadThres: Double // 觸發擴容的負載因子閾值 val extendRatio: Int // 擴容倍數 var buckets: MutableList> // 桶陣列 /* 建構子 */ init { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = mutableListOf() for (i in 0.. loadThres) { extend() } val index = hashFunc(key) val bucket = buckets[index] // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for (pair in bucket) { if (pair.key == key) { pair._val = _val return } } // 若無該 key ,則將鍵值對新增至尾部 val pair = Pair(key, _val) bucket.add(pair) size++ } /* 刪除操作 */ fun remove(key: Int) { val index = hashFunc(key) val bucket = buckets[index] // 走訪桶,從中刪除鍵值對 for (pair in bucket) { if (pair.key == key) { bucket.remove(pair) size-- break } } } /* 擴容雜湊表 */ fun extend() { // 暫存原雜湊表 val bucketsTmp = buckets // 初始化擴容後的新雜湊表 capacity *= extendRatio // mutablelist 無固定大小 buckets = mutableListOf() for (i in 0..() for (pair in bucket) { val k = pair.key val v = pair._val res.add("$k -> $v") } println(res) } } } /* Driver Code */ fun main() { /* 初始化雜湊表 */ val map = HashMapChaining() /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(12836, "小哈") map.put(15937, "小囉") map.put(16750, "小算") map.put(13276, "小法") map.put(10583, "小鴨") println("\n新增完成後,雜湊表為\nKey -> Value") map.print() /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value val name = map.get(13276) println("\n輸入學號 13276 ,查詢到姓名 $name") /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(12836) println("\n刪除 12836 後,雜湊表為\nKey -> Value") map.print() } ================================================ FILE: zh-hant/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt ================================================ /** * File: hash_map_open_addressing.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* 開放定址雜湊表 */ class HashMapOpenAddressing { private var size: Int // 鍵值對數量 private var capacity: Int // 雜湊表容量 private val loadThres: Double // 觸發擴容的負載因子閾值 private val extendRatio: Int // 擴容倍數 private var buckets: Array // 桶陣列 private val TOMBSTONE: Pair // 刪除標記 /* 建構子 */ init { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = arrayOfNulls(capacity) TOMBSTONE = Pair(-1, "-1") } /* 雜湊函式 */ fun hashFunc(key: Int): Int { return key % capacity } /* 負載因子 */ fun loadFactor(): Double { return (size / capacity).toDouble() } /* 搜尋 key 對應的桶索引 */ fun findBucket(key: Int): Int { var index = hashFunc(key) var firstTombstone = -1 // 線性探查,當遇到空桶時跳出 while (buckets[index] != null) { // 若遇到 key ,返回對應的桶索引 if (buckets[index]?.key == key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index] buckets[index] = TOMBSTONE return firstTombstone // 返回移動後的桶索引 } return index // 返回桶索引 } // 記錄遇到的首個刪除標記 if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % capacity } // 若 key 不存在,則返回新增點的索引 return if (firstTombstone == -1) index else firstTombstone } /* 查詢操作 */ fun get(key: Int): String? { // 搜尋 key 對應的桶索引 val index = findBucket(key) // 若找到鍵值對,則返回對應 val if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index]?._val } // 若鍵值對不存在,則返回 null return null } /* 新增操作 */ fun put(key: Int, _val: String) { // 當負載因子超過閾值時,執行擴容 if (loadFactor() > loadThres) { extend() } // 搜尋 key 對應的桶索引 val index = findBucket(key) // 若找到鍵值對,則覆蓋 val 並返回 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index]!!._val = _val return } // 若鍵值對不存在,則新增該鍵值對 buckets[index] = Pair(key, _val) size++ } /* 刪除操作 */ fun remove(key: Int) { // 搜尋 key 對應的桶索引 val index = findBucket(key) // 若找到鍵值對,則用刪除標記覆蓋它 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE size-- } } /* 擴容雜湊表 */ fun extend() { // 暫存原雜湊表 val bucketsTmp = buckets // 初始化擴容後的新雜湊表 capacity *= extendRatio buckets = arrayOfNulls(capacity) size = 0 // 將鍵值對從原雜湊表搬運至新雜湊表 for (pair in bucketsTmp) { if (pair != null && pair != TOMBSTONE) { put(pair.key, pair._val) } } } /* 列印雜湊表 */ fun print() { for (pair in buckets) { if (pair == null) { println("null") } else if (pair == TOMBSTONE) { println("TOMESTOME") } else { println("${pair.key} -> ${pair._val}") } } } } /* Driver Code */ fun main() { // 初始化雜湊表 val hashmap = HashMapOpenAddressing() // 新增操作 // 在雜湊表中新增鍵值對 (key, val) hashmap.put(12836, "小哈") hashmap.put(15937, "小囉") hashmap.put(16750, "小算") hashmap.put(13276, "小法") hashmap.put(10583, "小鴨") println("\n新增完成後,雜湊表為\nKey -> Value") hashmap.print() // 查詢操作 // 向雜湊表中輸入鍵 key ,得到值 val val name = hashmap.get(13276) println("\n輸入學號 13276 ,查詢到姓名 $name") // 刪除操作 // 在雜湊表中刪除鍵值對 (key, val) hashmap.remove(16750) println("\n刪除 16750 後,雜湊表為\nKey -> Value") hashmap.print() } ================================================ FILE: zh-hant/codes/kotlin/chapter_hashing/simple_hash.kt ================================================ /** * File: simple_hash.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_hashing /* 加法雜湊 */ fun addHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = (hash + c.code) % MODULUS } return hash.toInt() } /* 乘法雜湊 */ fun mulHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = (31 * hash + c.code) % MODULUS } return hash.toInt() } /* 互斥或雜湊 */ fun xorHash(key: String): Int { var hash = 0 val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = hash xor c.code } return hash and MODULUS } /* 旋轉雜湊 */ fun rotHash(key: String): Int { var hash = 0L val MODULUS = 1000000007 for (c in key.toCharArray()) { hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS } return hash.toInt() } /* Driver Code */ fun main() { val key = "Hello 演算法" var hash = addHash(key) println("加法雜湊值為 $hash") hash = mulHash(key) println("乘法雜湊值為 $hash") hash = xorHash(key) println("互斥或雜湊值為 $hash") hash = rotHash(key) println("旋轉雜湊值為 $hash") } ================================================ FILE: zh-hant/codes/kotlin/chapter_heap/heap.kt ================================================ /** * File: heap.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* fun testPush(heap: Queue, _val: Int) { heap.offer(_val) // 元素入堆積 print("\n元素 $_val 入堆積後\n") printHeap(heap) } fun testPop(heap: Queue) { val _val = heap.poll() // 堆積頂元素出堆積 print("\n堆積頂元素 $_val 出堆積後\n") printHeap(heap) } /* Driver Code */ fun main() { /* 初始化堆積 */ // 初始化小頂堆積 var minHeap = PriorityQueue() // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } println("\n以下測試樣例為大頂堆積") /* 元素入堆積 */ testPush(maxHeap, 1) testPush(maxHeap, 3) testPush(maxHeap, 2) testPush(maxHeap, 5) testPush(maxHeap, 4) /* 獲取堆積頂元素 */ val peek = maxHeap.peek() print("\n堆積頂元素為 $peek\n") /* 堆積頂元素出堆積 */ testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) testPop(maxHeap) /* 獲取堆積大小 */ val size = maxHeap.size print("\n堆積元素數量為 $size\n") /* 判斷堆積是否為空 */ val isEmpty = maxHeap.isEmpty() print("\n堆積是否為空 $isEmpty\n") /* 輸入串列並建堆積 */ // 時間複雜度為 O(n) ,而非 O(nlogn) minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) println("\n輸入串列並建立小頂堆積後") printHeap(minHeap) } ================================================ FILE: zh-hant/codes/kotlin/chapter_heap/my_heap.kt ================================================ /** * File: my_heap.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* /* 大頂堆積 */ class MaxHeap(nums: MutableList?) { // 使用串列而非陣列,這樣無須考慮擴容問題 private val maxHeap = mutableListOf() /* 建構子,根據輸入串列建堆積 */ init { // 將串列元素原封不動新增進堆積 maxHeap.addAll(nums!!) // 堆積化除葉節點以外的其他所有節點 for (i in parent(size() - 1) downTo 0) { siftDown(i) } } /* 獲取左子節點的索引 */ private fun left(i: Int): Int { return 2 * i + 1 } /* 獲取右子節點的索引 */ private fun right(i: Int): Int { return 2 * i + 2 } /* 獲取父節點的索引 */ private fun parent(i: Int): Int { return (i - 1) / 2 // 向下整除 } /* 交換元素 */ private fun swap(i: Int, j: Int) { val temp = maxHeap[i] maxHeap[i] = maxHeap[j] maxHeap[j] = temp } /* 獲取堆積大小 */ fun size(): Int { return maxHeap.size } /* 判斷堆積是否為空 */ fun isEmpty(): Boolean { /* 判斷堆積是否為空 */ return size() == 0 } /* 訪問堆積頂元素 */ fun peek(): Int { return maxHeap[0] } /* 元素入堆積 */ fun push(_val: Int) { // 新增節點 maxHeap.add(_val) // 從底至頂堆積化 siftUp(size() - 1) } /* 從節點 i 開始,從底至頂堆積化 */ private fun siftUp(it: Int) { // Kotlin的函式參數不可變,因此建立臨時變數 var i = it while (true) { // 獲取節點 i 的父節點 val p = parent(i) // 當“越過根節點”或“節點無須修復”時,結束堆積化 if (p < 0 || maxHeap[i] <= maxHeap[p]) break // 交換兩節點 swap(i, p) // 迴圈向上堆積化 i = p } } /* 元素出堆積 */ fun pop(): Int { // 判空處理 if (isEmpty()) throw IndexOutOfBoundsException() // 交換根節點與最右葉節點(交換首元素與尾元素) swap(0, size() - 1) // 刪除節點 val _val = maxHeap.removeAt(size() - 1) // 從頂至底堆積化 siftDown(0) // 返回堆積頂元素 return _val } /* 從節點 i 開始,從頂至底堆積化 */ private fun siftDown(it: Int) { // Kotlin的函式參數不可變,因此建立臨時變數 var i = it while (true) { // 判斷節點 i, l, r 中值最大的節點,記為 ma val l = left(i) val r = right(i) var ma = i if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if (ma == i) break // 交換兩節點 swap(i, ma) // 迴圈向下堆積化 i = ma } } /* 列印堆積(二元樹) */ fun print() { val queue = PriorityQueue { a: Int, b: Int -> b - a } queue.addAll(maxHeap) printHeap(queue) } } /* Driver Code */ fun main() { /* 初始化大頂堆積 */ val maxHeap = MaxHeap(mutableListOf(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)) println("\n輸入串列並建堆積後") maxHeap.print() /* 獲取堆積頂元素 */ var peek = maxHeap.peek() print("\n堆積頂元素為 $peek\n") /* 元素入堆積 */ val _val = 7 maxHeap.push(_val) print("\n元素 $_val 入堆積後\n") maxHeap.print() /* 堆積頂元素出堆積 */ peek = maxHeap.pop() print("\n堆積頂元素 $peek 出堆積後\n") maxHeap.print() /* 獲取堆積大小 */ val size = maxHeap.size() print("\n堆積元素數量為 $size\n") /* 判斷堆積是否為空 */ val isEmpty = maxHeap.isEmpty() print("\n堆積是否為空 $isEmpty\n") } ================================================ FILE: zh-hant/codes/kotlin/chapter_heap/top_k.kt ================================================ /** * File: top_k.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_heap import utils.printHeap import java.util.* /* 基於堆積查詢陣列中最大的 k 個元素 */ fun topKHeap(nums: IntArray, k: Int): Queue { // 初始化小頂堆積 val heap = PriorityQueue() // 將陣列的前 k 個元素入堆積 for (i in 0.. heap.peek()) { heap.poll() heap.offer(nums[i]) } } return heap } /* Driver Code */ fun main() { val nums = intArrayOf(1, 7, 6, 3, 2) val k = 3 val res = topKHeap(nums, k) println("最大的 $k 個元素為") printHeap(res) } ================================================ FILE: zh-hant/codes/kotlin/chapter_searching/binary_search.kt ================================================ /** * File: binary_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* 二分搜尋(雙閉區間) */ fun binarySearch(nums: IntArray, target: Int): Int { // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 var i = 0 var j = nums.size - 1 // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) while (i <= j) { val m = i + (j - i) / 2 // 計算中點索引 m if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j] 中 i = m + 1 else if (nums[m] > target) // 此情況說明 target 在區間 [i, m-1] 中 j = m - 1 else // 找到目標元素,返回其索引 return m } // 未找到目標元素,返回 -1 return -1 } /* 二分搜尋(左閉右開區間) */ fun binarySearchLCRO(nums: IntArray, target: Int): Int { // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 var i = 0 var j = nums.size // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) while (i < j) { val m = i + (j - i) / 2 // 計算中點索引 m if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j) 中 i = m + 1 else if (nums[m] > target) // 此情況說明 target 在區間 [i, m) 中 j = m else // 找到目標元素,返回其索引 return m } // 未找到目標元素,返回 -1 return -1 } /* Driver Code */ fun main() { val target = 6 val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) /* 二分搜尋(雙閉區間) */ var index = binarySearch(nums, target) println("目標元素 6 的索引 = $index") /* 二分搜尋(左閉右開區間) */ index = binarySearchLCRO(nums, target) println("目標元素 6 的索引 = $index") } ================================================ FILE: zh-hant/codes/kotlin/chapter_searching/binary_search_edge.kt ================================================ /** * File: binary_search_edge.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* 二分搜尋最左一個 target */ fun binarySearchLeftEdge(nums: IntArray, target: Int): Int { // 等價於查詢 target 的插入點 val i = binarySearchInsertion(nums, target) // 未找到 target ,返回 -1 if (i == nums.size || nums[i] != target) { return -1 } // 找到 target ,返回索引 i return i } /* 二分搜尋最右一個 target */ fun binarySearchRightEdge(nums: IntArray, target: Int): Int { // 轉化為查詢最左一個 target + 1 val i = binarySearchInsertion(nums, target + 1) // j 指向最右一個 target ,i 指向首個大於 target 的元素 val j = i - 1 // 未找到 target ,返回 -1 if (j == -1 || nums[j] != target) { return -1 } // 找到 target ,返回索引 j return j } /* Driver Code */ fun main() { // 包含重複元素的陣列 val nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) println("\n陣列 nums = ${nums.contentToString()}") // 二分搜尋左邊界和右邊界 for (target in intArrayOf(6, 7)) { var index = binarySearchLeftEdge(nums, target) println("最左一個元素 $target 的索引為 $index") index = binarySearchRightEdge(nums, target) println("最右一個元素 $target 的索引為 $index") } } ================================================ FILE: zh-hant/codes/kotlin/chapter_searching/binary_search_insertion.kt ================================================ /** * File: binary_search_insertion.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* 二分搜尋插入點(無重複元素) */ fun binarySearchInsertionSimple(nums: IntArray, target: Int): Int { var i = 0 var j = nums.size - 1 // 初始化雙閉區間 [0, n-1] while (i <= j) { val m = i + (j - i) / 2 // 計算中點索引 m if (nums[m] < target) { i = m + 1 // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1 // target 在區間 [i, m-1] 中 } else { return m // 找到 target ,返回插入點 m } } // 未找到 target ,返回插入點 i return i } /* 二分搜尋插入點(存在重複元素) */ fun binarySearchInsertion(nums: IntArray, target: Int): Int { var i = 0 var j = nums.size - 1 // 初始化雙閉區間 [0, n-1] while (i <= j) { val m = i + (j - i) / 2 // 計算中點索引 m if (nums[m] < target) { i = m + 1 // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1 // target 在區間 [i, m-1] 中 } else { j = m - 1 // 首個小於 target 的元素在區間 [i, m-1] 中 } } // 返回插入點 i return i } /* Driver Code */ fun main() { // 無重複元素的陣列 var nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) println("\n陣列 nums = ${nums.contentToString()}") // 二分搜尋插入點 for (target in intArrayOf(6, 9)) { val index = binarySearchInsertionSimple(nums, target) println("元素 $target 的插入點的索引為 $index") } // 包含重複元素的陣列 nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) println("\n陣列 nums = ${nums.contentToString()}") // 二分搜尋插入點 for (target in intArrayOf(2, 6, 20)) { val index = binarySearchInsertion(nums, target) println("元素 $target 的插入點的索引為 $index") } } ================================================ FILE: zh-hant/codes/kotlin/chapter_searching/hashing_search.kt ================================================ /** * File: hashing_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching import utils.ListNode /* 雜湊查詢(陣列) */ fun hashingSearchArray(map: Map, target: Int): Int { // 雜湊表的 key: 目標元素,_val: 索引 // 若雜湊表中無此 key ,返回 -1 return map.getOrDefault(target, -1) } /* 雜湊查詢(鏈結串列) */ fun hashingSearchLinkedList(map: Map, target: Int): ListNode? { // 雜湊表的 key: 目標節點值,_val: 節點物件 // 若雜湊表中無此 key ,返回 null return map.getOrDefault(target, null) } /* Driver Code */ fun main() { val target = 3 /* 雜湊查詢(陣列) */ val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) // 初始化雜湊表 val map = HashMap() for (i in nums.indices) { map[nums[i]] = i // key: 元素,_val: 索引 } val index = hashingSearchArray(map, target) println("目標元素 3 的索引 = $index") /* 雜湊查詢(鏈結串列) */ var head = ListNode.arrToLinkedList(nums) // 初始化雜湊表 val map1 = HashMap() while (head != null) { map1[head._val] = head // key: 節點值,_val: 節點 head = head.next } val node = hashingSearchLinkedList(map1, target) println("目標節點值 3 的對應節點物件為 $node") } ================================================ FILE: zh-hant/codes/kotlin/chapter_searching/linear_search.kt ================================================ /** * File: linear_search.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching import utils.ListNode /* 線性查詢(陣列) */ fun linearSearchArray(nums: IntArray, target: Int): Int { // 走訪陣列 for (i in nums.indices) { // 找到目標元素,返回其索引 if (nums[i] == target) return i } // 未找到目標元素,返回 -1 return -1 } /* 線性查詢(鏈結串列) */ fun linearSearchLinkedList(h: ListNode?, target: Int): ListNode? { // 走訪鏈結串列 var head = h while (head != null) { // 找到目標節點,返回之 if (head._val == target) return head head = head.next } // 未找到目標節點,返回 null return null } /* Driver Code */ fun main() { val target = 3 /* 在陣列中執行線性查詢 */ val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) val index = linearSearchArray(nums, target) println("目標元素 3 的索引 = $index") /* 在鏈結串列中執行線性查詢 */ val head = ListNode.arrToLinkedList(nums) val node = linearSearchLinkedList(head, target) println("目標節點值 3 的對應節點物件為 $node") } ================================================ FILE: zh-hant/codes/kotlin/chapter_searching/two_sum.kt ================================================ /** * File: two_sum.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_searching /* 方法一:暴力列舉 */ fun twoSumBruteForce(nums: IntArray, target: Int): IntArray { val size = nums.size // 兩層迴圈,時間複雜度為 O(n^2) for (i in 0..() // 單層迴圈,時間複雜度為 O(n) for (i in 0.. nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp } } } } /* 泡沫排序(標誌最佳化) */ fun bubbleSortWithFlag(nums: IntArray) { // 外迴圈:未排序區間為 [0, i] for (i in nums.size - 1 downTo 1) { var flag = false // 初始化標誌位 // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (j in 0.. nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] val temp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = temp flag = true // 記錄交換元素 } } if (!flag) break // 此輪“冒泡”未交換任何元素,直接跳出 } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) bubbleSort(nums) println("泡沫排序完成後 nums = ${nums.contentToString()}") val nums1 = intArrayOf(4, 1, 3, 1, 5, 2) bubbleSortWithFlag(nums1) println("泡沫排序完成後 nums1 = ${nums1.contentToString()}") } ================================================ FILE: zh-hant/codes/kotlin/chapter_sorting/bucket_sort.kt ================================================ /** * File: bucket_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 桶排序 */ fun bucketSort(nums: FloatArray) { // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 val k = nums.size / 2 val buckets = mutableListOf>() for (i in 0.. nums[ma]) ma = l if (r < n && nums[r] > nums[ma]) ma = r // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if (ma == i) break // 交換兩節點 val temp = nums[i] nums[i] = nums[ma] nums[ma] = temp // 迴圈向下堆積化 i = ma } } /* 堆積排序 */ fun heapSort(nums: IntArray) { // 建堆積操作:堆積化除葉節點以外的其他所有節點 for (i in nums.size / 2 - 1 downTo 0) { siftDown(nums, nums.size, i) } // 從堆積中提取最大元素,迴圈 n-1 輪 for (i in nums.size - 1 downTo 1) { // 交換根節點與最右葉節點(交換首元素與尾元素) val temp = nums[0] nums[0] = nums[i] nums[i] = temp // 以根節點為起點,從頂至底進行堆積化 siftDown(nums, i, 0) } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) heapSort(nums) println("堆積排序完成後 nums = ${nums.contentToString()}") } ================================================ FILE: zh-hant/codes/kotlin/chapter_sorting/insertion_sort.kt ================================================ /** * File: insertion_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 插入排序 */ fun insertionSort(nums: IntArray) { //外迴圈: 已排序元素為 1, 2, ..., n for (i in nums.indices) { val base = nums[i] var j = i - 1 // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j] // 將 nums[j] 向右移動一位 j-- } nums[j + 1] = base // 將 base 賦值到正確位置 } } /* Driver Code */ fun main() { val nums = intArrayOf(4, 1, 3, 1, 5, 2) insertionSort(nums) println("插入排序完成後 nums = ${nums.contentToString()}") } ================================================ FILE: zh-hant/codes/kotlin/chapter_sorting/merge_sort.kt ================================================ /** * File: merge_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 合併左子陣列和右子陣列 */ fun merge(nums: IntArray, left: Int, mid: Int, right: Int) { // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] // 建立一個臨時陣列 tmp ,用於存放合併後的結果 val tmp = IntArray(right - left + 1) // 初始化左子陣列和右子陣列的起始索引 var i = left var j = mid + 1 var k = 0 // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++] else tmp[k++] = nums[j++] } // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 while (i <= mid) { tmp[k++] = nums[i++] } while (j <= right) { tmp[k++] = nums[j++] } // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 for (l in tmp.indices) { nums[left + l] = tmp[l] } } /* 合併排序 */ fun mergeSort(nums: IntArray, left: Int, right: Int) { // 終止條件 if (left >= right) return // 當子陣列長度為 1 時終止遞迴 // 劃分階段 val mid = left + (right - left) / 2 // 計算中點 mergeSort(nums, left, mid) // 遞迴左子陣列 mergeSort(nums, mid + 1, right) // 遞迴右子陣列 // 合併階段 merge(nums, left, mid, right) } /* Driver Code */ fun main() { /* 合併排序 */ val nums = intArrayOf(7, 3, 2, 6, 0, 1, 5, 4) mergeSort(nums, 0, nums.size - 1) println("合併排序完成後 nums = ${nums.contentToString()}") } ================================================ FILE: zh-hant/codes/kotlin/chapter_sorting/quick_sort.kt ================================================ /** * File: quick_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 元素交換 */ fun swap(nums: IntArray, i: Int, j: Int) { val temp = nums[i] nums[i] = nums[j] nums[j] = temp } /* 哨兵劃分 */ fun partition(nums: IntArray, left: Int, right: Int): Int { // 以 nums[left] 為基準數 var i = left var j = right while (i < j) { while (i < j && nums[j] >= nums[left]) j-- // 從右向左找首個小於基準數的元素 while (i < j && nums[i] <= nums[left]) i++ // 從左向右找首個大於基準數的元素 swap(nums, i, j) // 交換這兩個元素 } swap(nums, i, left) // 將基準數交換至兩子陣列的分界線 return i // 返回基準數的索引 } /* 快速排序 */ fun quickSort(nums: IntArray, left: Int, right: Int) { // 子陣列長度為 1 時終止遞迴 if (left >= right) return // 哨兵劃分 val pivot = partition(nums, left, right) // 遞迴左子陣列、右子陣列 quickSort(nums, left, pivot - 1) quickSort(nums, pivot + 1, right) } /* 選取三個候選元素的中位數 */ fun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int { val l = nums[left] val m = nums[mid] val r = nums[right] if ((m in l..r) || (m in r..l)) return mid // m 在 l 和 r 之間 if ((l in m..r) || (l in r..m)) return left // l 在 m 和 r 之間 return right } /* 哨兵劃分(三數取中值) */ fun partitionMedian(nums: IntArray, left: Int, right: Int): Int { // 選取三個候選元素的中位數 val med = medianThree(nums, left, (left + right) / 2, right) // 將中位數交換至陣列最左端 swap(nums, left, med) // 以 nums[left] 為基準數 var i = left var j = right while (i < j) { while (i < j && nums[j] >= nums[left]) j-- // 從右向左找首個小於基準數的元素 while (i < j && nums[i] <= nums[left]) i++ // 從左向右找首個大於基準數的元素 swap(nums, i, j) // 交換這兩個元素 } swap(nums, i, left) // 將基準數交換至兩子陣列的分界線 return i // 返回基準數的索引 } /* 快速排序 */ fun quickSortMedian(nums: IntArray, left: Int, right: Int) { // 子陣列長度為 1 時終止遞迴 if (left >= right) return // 哨兵劃分 val pivot = partitionMedian(nums, left, right) // 遞迴左子陣列、右子陣列 quickSort(nums, left, pivot - 1) quickSort(nums, pivot + 1, right) } /* 快速排序(遞迴深度最佳化) */ fun quickSortTailCall(nums: IntArray, left: Int, right: Int) { // 子陣列長度為 1 時終止 var l = left var r = right while (l < r) { // 哨兵劃分操作 val pivot = partition(nums, l, r) // 對兩個子陣列中較短的那個執行快速排序 if (pivot - l < r - pivot) { quickSort(nums, l, pivot - 1) // 遞迴排序左子陣列 l = pivot + 1 // 剩餘未排序區間為 [pivot + 1, right] } else { quickSort(nums, pivot + 1, r) // 遞迴排序右子陣列 r = pivot - 1 // 剩餘未排序區間為 [left, pivot - 1] } } } /* Driver Code */ fun main() { /* 快速排序 */ val nums = intArrayOf(2, 4, 1, 0, 3, 5) quickSort(nums, 0, nums.size - 1) println("快速排序完成後 nums = ${nums.contentToString()}") /* 快速排序(中位基準數最佳化) */ val nums1 = intArrayOf(2, 4, 1, 0, 3, 5) quickSortMedian(nums1, 0, nums1.size - 1) println("快速排序(中位基準數最佳化)完成後 nums1 = ${nums1.contentToString()}") /* 快速排序(遞迴深度最佳化) */ val nums2 = intArrayOf(2, 4, 1, 0, 3, 5) quickSortTailCall(nums2, 0, nums2.size - 1) println("快速排序(遞迴深度最佳化)完成後 nums2 = ${nums2.contentToString()}") } ================================================ FILE: zh-hant/codes/kotlin/chapter_sorting/radix_sort.kt ================================================ /** * File: radix_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ fun digit(num: Int, exp: Int): Int { // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 return (num / exp) % 10 } /* 計數排序(根據 nums 第 k 位排序) */ fun countingSortDigit(nums: IntArray, exp: Int) { // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 val counter = IntArray(10) val n = nums.size // 統計 0~9 各數字的出現次數 for (i in 0.. m) m = num var exp = 1 // 按照從低位到高位的順序走訪 while (exp <= m) { // 對陣列元素的第 k 位執行計數排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums, exp) exp *= 10 } } /* Driver Code */ fun main() { // 基數排序 val nums = intArrayOf( 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 ) radixSort(nums) println("基數排序完成後 nums = ${nums.contentToString()}") } ================================================ FILE: zh-hant/codes/kotlin/chapter_sorting/selection_sort.kt ================================================ /** * File: selection_sort.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_sorting /* 選擇排序 */ fun selectionSort(nums: IntArray) { val n = nums.size // 外迴圈:未排序區間為 [i, n-1] for (i in 0..() /* 獲取堆疊的長度 */ fun size(): Int { return stack.size } /* 判斷堆疊是否為空 */ fun isEmpty(): Boolean { return size() == 0 } /* 入堆疊 */ fun push(num: Int) { stack.add(num) } /* 出堆疊 */ fun pop(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return stack.removeAt(size() - 1) } /* 訪問堆疊頂元素 */ fun peek(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return stack[size() - 1] } /* 將 List 轉化為 Array 並返回 */ fun toArray(): Array { return stack.toTypedArray() } } /* Driver Code */ fun main() { /* 初始化堆疊 */ val stack = ArrayStack() /* 元素入堆疊 */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("堆疊 stack = ${stack.toArray().contentToString()}") /* 訪問堆疊頂元素 */ val peek = stack.peek() println("堆疊頂元素 peek = $peek") /* 元素出堆疊 */ val pop = stack.pop() println("出堆疊元素 pop = $pop,出堆疊後 stack = ${stack.toArray().contentToString()}") /* 獲取堆疊的長度 */ val size = stack.size() println("堆疊的長度 size = $size") /* 判斷是否為空 */ val isEmpty = stack.isEmpty() println("堆疊是否為空 = $isEmpty") } ================================================ FILE: zh-hant/codes/kotlin/chapter_stack_and_queue/deque.kt ================================================ /** * File: deque.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* 初始化雙向佇列 */ val deque = LinkedList() deque.offerLast(3) deque.offerLast(2) deque.offerLast(5) println("雙向佇列 deque = $deque") /* 訪問元素 */ val peekFirst = deque.peekFirst() println("佇列首元素 peekFirst = $peekFirst") val peekLast = deque.peekLast() println("佇列尾元素 peekLast = $peekLast") /* 元素入列 */ deque.offerLast(4) println("元素 4 佇列尾入列後 deque = $deque") deque.offerFirst(1) println("元素 1 佇列首入列後 deque = $deque") /* 元素出列 */ val popLast = deque.pollLast() println("佇列尾出列元素 = $popLast,佇列尾出列後 deque = $deque") val popFirst = deque.pollFirst() println("佇列首出列元素 = $popFirst,佇列首出列後 deque = $deque") /* 獲取雙向佇列的長度 */ val size = deque.size println("雙向佇列長度 size = $size") /* 判斷雙向佇列是否為空 */ val isEmpty = deque.isEmpty() println("雙向佇列是否為空 = $isEmpty") } ================================================ FILE: zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt ================================================ /** * File: linkedlist_deque.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* 雙向鏈結串列節點 */ class ListNode(var _val: Int) { // 節點值 var next: ListNode? = null // 後繼節點引用 var prev: ListNode? = null // 前驅節點引用 } /* 基於雙向鏈結串列實現的雙向佇列 */ class LinkedListDeque { private var front: ListNode? = null // 頭節點 front private var rear: ListNode? = null // 尾節點 rear private var queSize: Int = 0 // 雙向佇列的長度 /* 獲取雙向佇列的長度 */ fun size(): Int { return queSize } /* 判斷雙向佇列是否為空 */ fun isEmpty(): Boolean { return size() == 0 } /* 入列操作 */ fun push(num: Int, isFront: Boolean) { val node = ListNode(num) // 若鏈結串列為空,則令 front 和 rear 都指向 node if (isEmpty()) { rear = node front = rear // 佇列首入列操作 } else if (isFront) { // 將 node 新增至鏈結串列頭部 front?.prev = node node.next = front front = node // 更新頭節點 // 佇列尾入列操作 } else { // 將 node 新增至鏈結串列尾部 rear?.next = node node.prev = rear rear = node // 更新尾節點 } queSize++ // 更新佇列長度 } /* 佇列首入列 */ fun pushFirst(num: Int) { push(num, true) } /* 佇列尾入列 */ fun pushLast(num: Int) { push(num, false) } /* 出列操作 */ fun pop(isFront: Boolean): Int { if (isEmpty()) throw IndexOutOfBoundsException() val _val: Int // 佇列首出列操作 if (isFront) { _val = front!!._val // 暫存頭節點值 // 刪除頭節點 val fNext = front!!.next if (fNext != null) { fNext.prev = null front!!.next = null } front = fNext // 更新頭節點 // 佇列尾出列操作 } else { _val = rear!!._val // 暫存尾節點值 // 刪除尾節點 val rPrev = rear!!.prev if (rPrev != null) { rPrev.next = null rear!!.prev = null } rear = rPrev // 更新尾節點 } queSize-- // 更新佇列長度 return _val } /* 佇列首出列 */ fun popFirst(): Int { return pop(true) } /* 佇列尾出列 */ fun popLast(): Int { return pop(false) } /* 訪問佇列首元素 */ fun peekFirst(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return front!!._val } /* 訪問佇列尾元素 */ fun peekLast(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return rear!!._val } /* 返回陣列用於列印 */ fun toArray(): IntArray { var node = front val res = IntArray(size()) for (i in res.indices) { res[i] = node!!._val node = node.next } return res } } /* Driver Code */ fun main() { /* 初始化雙向佇列 */ val deque = LinkedListDeque() deque.pushLast(3) deque.pushLast(2) deque.pushLast(5) println("雙向佇列 deque = ${deque.toArray().contentToString()}") /* 訪問元素 */ val peekFirst = deque.peekFirst() println("佇列首元素 peekFirst = $peekFirst") val peekLast = deque.peekLast() println("佇列尾元素 peekLast = $peekLast") /* 元素入列 */ deque.pushLast(4) println("元素 4 佇列尾入列後 deque = ${deque.toArray().contentToString()}") deque.pushFirst(1) println("元素 1 佇列首入列後 deque = ${deque.toArray().contentToString()}") /* 元素出列 */ val popLast = deque.popLast() println("佇列尾出列元素 = ${popLast},佇列尾出列後 deque = ${deque.toArray().contentToString()}") val popFirst = deque.popFirst() println("佇列首出列元素 = ${popFirst},佇列首出列後 deque = ${deque.toArray().contentToString()}") /* 獲取雙向佇列的長度 */ val size = deque.size() println("雙向佇列長度 size = $size") /* 判斷雙向佇列是否為空 */ val isEmpty = deque.isEmpty() println("雙向佇列是否為空 = $isEmpty") } ================================================ FILE: zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt ================================================ /** * File: linkedlist_queue.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* 基於鏈結串列實現的佇列 */ class LinkedListQueue( // 頭節點 front ,尾節點 rear private var front: ListNode? = null, private var rear: ListNode? = null, private var queSize: Int = 0 ) { /* 獲取佇列的長度 */ fun size(): Int { return queSize } /* 判斷佇列是否為空 */ fun isEmpty(): Boolean { return size() == 0 } /* 入列 */ fun push(num: Int) { // 在尾節點後新增 num val node = ListNode(num) // 如果佇列為空,則令頭、尾節點都指向該節點 if (front == null) { front = node rear = node // 如果佇列不為空,則將該節點新增到尾節點後 } else { rear?.next = node rear = node } queSize++ } /* 出列 */ fun pop(): Int { val num = peek() // 刪除頭節點 front = front?.next queSize-- return num } /* 訪問佇列首元素 */ fun peek(): Int { if (isEmpty()) throw IndexOutOfBoundsException() return front!!._val } /* 將鏈結串列轉化為 Array 並返回 */ fun toArray(): IntArray { var node = front val res = IntArray(size()) for (i in res.indices) { res[i] = node!!._val node = node.next } return res } } /* Driver Code */ fun main() { /* 初始化佇列 */ val queue = LinkedListQueue() /* 元素入列 */ queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) println("佇列 queue = ${queue.toArray().contentToString()}") /* 訪問佇列首元素 */ val peek = queue.peek() println("佇列首元素 peek = $peek") /* 元素出列 */ val pop = queue.pop() println("出列元素 pop = $pop,出列後 queue = ${queue.toArray().contentToString()}") /* 獲取佇列的長度 */ val size = queue.size() println("佇列長度 size = $size") /* 判斷佇列是否為空 */ val isEmpty = queue.isEmpty() println("佇列是否為空 = $isEmpty") } ================================================ FILE: zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt ================================================ /** * File: linkedlist_stack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue /* 基於鏈結串列實現的堆疊 */ class LinkedListStack( private var stackPeek: ListNode? = null, // 將頭節點作為堆疊頂 private var stkSize: Int = 0 // 堆疊的長度 ) { /* 獲取堆疊的長度 */ fun size(): Int { return stkSize } /* 判斷堆疊是否為空 */ fun isEmpty(): Boolean { return size() == 0 } /* 入堆疊 */ fun push(num: Int) { val node = ListNode(num) node.next = stackPeek stackPeek = node stkSize++ } /* 出堆疊 */ fun pop(): Int? { val num = peek() stackPeek = stackPeek?.next stkSize-- return num } /* 訪問堆疊頂元素 */ fun peek(): Int? { if (isEmpty()) throw IndexOutOfBoundsException() return stackPeek?._val } /* 將 List 轉化為 Array 並返回 */ fun toArray(): IntArray { var node = stackPeek val res = IntArray(size()) for (i in res.size - 1 downTo 0) { res[i] = node?._val!! node = node.next } return res } } /* Driver Code */ fun main() { /* 初始化堆疊 */ val stack = LinkedListStack() /* 元素入堆疊 */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("堆疊 stack = ${stack.toArray().contentToString()}") /* 訪問堆疊頂元素 */ val peek = stack.peek()!! println("堆疊頂元素 peek = $peek") /* 元素出堆疊 */ val pop = stack.pop()!! println("出堆疊元素 pop = $pop,出堆疊後 stack = ${stack.toArray().contentToString()}") /* 獲取堆疊的長度 */ val size = stack.size() println("堆疊的長度 size = $size") /* 判斷是否為空 */ val isEmpty = stack.isEmpty() println("堆疊是否為空 = $isEmpty") } ================================================ FILE: zh-hant/codes/kotlin/chapter_stack_and_queue/queue.kt ================================================ /** * File: queue.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* 初始化佇列 */ val queue = LinkedList() /* 元素入列 */ queue.offer(1) queue.offer(3) queue.offer(2) queue.offer(5) queue.offer(4) println("佇列 queue = $queue") /* 訪問佇列首元素 */ val peek = queue.peek() println("佇列首元素 peek = $peek") /* 元素出列 */ val pop = queue.poll() println("出列元素 pop = $pop,出列後 queue = $queue") /* 獲取佇列的長度 */ val size = queue.size println("佇列長度 size = $size") /* 判斷佇列是否為空 */ val isEmpty = queue.isEmpty() println("佇列是否為空 = $isEmpty") } ================================================ FILE: zh-hant/codes/kotlin/chapter_stack_and_queue/stack.kt ================================================ /** * File: stack.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_stack_and_queue import java.util.* /* Driver Code */ fun main() { /* 初始化堆疊 */ val stack = Stack() /* 元素入堆疊 */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) println("堆疊 stack = $stack") /* 訪問堆疊頂元素 */ val peek = stack.peek() println("堆疊頂元素 peek = $peek") /* 元素出堆疊 */ val pop = stack.pop() println("出堆疊元素 pop = $pop,出堆疊後 stack = $stack") /* 獲取堆疊的長度 */ val size = stack.size println("堆疊的長度 size = $size") /* 判斷是否為空 */ val isEmpty = stack.isEmpty() println("堆疊是否為空 = $isEmpty") } ================================================ FILE: zh-hant/codes/kotlin/chapter_tree/array_binary_tree.kt ================================================ /** * File: array_binary_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* 陣列表示下的二元樹類別 */ class ArrayBinaryTree(private val tree: MutableList) { /* 串列容量 */ fun size(): Int { return tree.size } /* 獲取索引為 i 節點的值 */ fun _val(i: Int): Int? { // 若索引越界,則返回 null ,代表空位 if (i < 0 || i >= size()) return null return tree[i] } /* 獲取索引為 i 節點的左子節點的索引 */ fun left(i: Int): Int { return 2 * i + 1 } /* 獲取索引為 i 節點的右子節點的索引 */ fun right(i: Int): Int { return 2 * i + 2 } /* 獲取索引為 i 節點的父節點的索引 */ fun parent(i: Int): Int { return (i - 1) / 2 } /* 層序走訪 */ fun levelOrder(): MutableList { val res = mutableListOf() // 直接走訪陣列 for (i in 0..) { // 若為空位,則返回 if (_val(i) == null) return // 前序走訪 if ("pre" == order) res.add(_val(i)) dfs(left(i), order, res) // 中序走訪 if ("in" == order) res.add(_val(i)) dfs(right(i), order, res) // 後序走訪 if ("post" == order) res.add(_val(i)) } /* 前序走訪 */ fun preOrder(): MutableList { val res = mutableListOf() dfs(0, "pre", res) return res } /* 中序走訪 */ fun inOrder(): MutableList { val res = mutableListOf() dfs(0, "in", res) return res } /* 後序走訪 */ fun postOrder(): MutableList { val res = mutableListOf() dfs(0, "post", res) return res } } /* Driver Code */ fun main() { // 初始化二元樹 // 這裡藉助了一個從串列直接生成二元樹的函式 val arr = mutableListOf(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15) val root = TreeNode.listToTree(arr) println("\n初始化二元樹\n") println("二元樹的陣列表示:") println(arr) println("二元樹的鏈結串列表示:") printTree(root) // 陣列表示下的二元樹類別 val abt = ArrayBinaryTree(arr) // 訪問節點 val i = 1 val l = abt.left(i) val r = abt.right(i) val p = abt.parent(i) println("當前節點的索引為 $i ,值為 ${abt._val(i)}") println("其左子節點的索引為 $l ,值為 ${abt._val(l)}") println("其右子節點的索引為 $r ,值為 ${abt._val(r)}") println("其父節點的索引為 $p ,值為 ${abt._val(p)}") // 走訪樹 var res = abt.levelOrder() println("\n層序走訪為:$res") res = abt.preOrder() println("前序走訪為:$res") res = abt.inOrder() println("中序走訪為:$res") res = abt.postOrder() println("後序走訪為:$res") } ================================================ FILE: zh-hant/codes/kotlin/chapter_tree/avl_tree.kt ================================================ /** * File: avl_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree import kotlin.math.max /* AVL 樹 */ class AVLTree { var root: TreeNode? = null // 根節點 /* 獲取節點高度 */ fun height(node: TreeNode?): Int { // 空節點高度為 -1 ,葉節點高度為 0 return node?.height ?: -1 } /* 更新節點高度 */ private fun updateHeight(node: TreeNode?) { // 節點高度等於最高子樹高度 + 1 node?.height = max(height(node?.left), height(node?.right)) + 1 } /* 獲取平衡因子 */ fun balanceFactor(node: TreeNode?): Int { // 空節點平衡因子為 0 if (node == null) return 0 // 節點平衡因子 = 左子樹高度 - 右子樹高度 return height(node.left) - height(node.right) } /* 右旋操作 */ private fun rightRotate(node: TreeNode?): TreeNode { val child = node!!.left val grandChild = child!!.right // 以 child 為原點,將 node 向右旋轉 child.right = node node.left = grandChild // 更新節點高度 updateHeight(node) updateHeight(child) // 返回旋轉後子樹的根節點 return child } /* 左旋操作 */ private fun leftRotate(node: TreeNode?): TreeNode { val child = node!!.right val grandChild = child!!.left // 以 child 為原點,將 node 向左旋轉 child.left = node node.right = grandChild // 更新節點高度 updateHeight(node) updateHeight(child) // 返回旋轉後子樹的根節點 return child } /* 執行旋轉操作,使該子樹重新恢復平衡 */ private fun rotate(node: TreeNode): TreeNode { // 獲取節點 node 的平衡因子 val balanceFactor = balanceFactor(node) // 左偏樹 if (balanceFactor > 1) { if (balanceFactor(node.left) >= 0) { // 右旋 return rightRotate(node) } else { // 先左旋後右旋 node.left = leftRotate(node.left) return rightRotate(node) } } // 右偏樹 if (balanceFactor < -1) { if (balanceFactor(node.right) <= 0) { // 左旋 return leftRotate(node) } else { // 先右旋後左旋 node.right = rightRotate(node.right) return leftRotate(node) } } // 平衡樹,無須旋轉,直接返回 return node } /* 插入節點 */ fun insert(_val: Int) { root = insertHelper(root, _val) } /* 遞迴插入節點(輔助方法) */ private fun insertHelper(n: TreeNode?, _val: Int): TreeNode { if (n == null) return TreeNode(_val) var node = n /* 1. 查詢插入位置並插入節點 */ if (_val < node._val) node.left = insertHelper(node.left, _val) else if (_val > node._val) node.right = insertHelper(node.right, _val) else return node // 重複節點不插入,直接返回 updateHeight(node) // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = rotate(node) // 返回子樹的根節點 return node } /* 刪除節點 */ fun remove(_val: Int) { root = removeHelper(root, _val) } /* 遞迴刪除節點(輔助方法) */ private fun removeHelper(n: TreeNode?, _val: Int): TreeNode? { var node = n ?: return null /* 1. 查詢節點並刪除 */ if (_val < node._val) node.left = removeHelper(node.left, _val) else if (_val > node._val) node.right = removeHelper(node.right, _val) else { if (node.left == null || node.right == null) { val child = if (node.left != null) node.left else node.right // 子節點數量 = 0 ,直接刪除 node 並返回 if (child == null) return null // 子節點數量 = 1 ,直接刪除 node else node = child } else { // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 var temp = node.right while (temp!!.left != null) { temp = temp.left } node.right = removeHelper(node.right, temp._val) node._val = temp._val } } updateHeight(node) // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = rotate(node) // 返回子樹的根節點 return node } /* 查詢節點 */ fun search(_val: Int): TreeNode? { var cur = root // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 目標節點在 cur 的右子樹中 cur = if (cur._val < _val) cur.right!! // 目標節點在 cur 的左子樹中 else if (cur._val > _val) cur.left // 找到目標節點,跳出迴圈 else break } // 返回目標節點 return cur } } fun testInsert(tree: AVLTree, _val: Int) { tree.insert(_val) println("\n插入節點 $_val 後,AVL 樹為") printTree(tree.root) } fun testRemove(tree: AVLTree, _val: Int) { tree.remove(_val) println("\n刪除節點 $_val 後,AVL 樹為") printTree(tree.root) } /* Driver Code */ fun main() { /* 初始化空 AVL 樹 */ val avlTree = AVLTree() /* 插入節點 */ // 請關注插入節點後,AVL 樹是如何保持平衡的 testInsert(avlTree, 1) testInsert(avlTree, 2) testInsert(avlTree, 3) testInsert(avlTree, 4) testInsert(avlTree, 5) testInsert(avlTree, 8) testInsert(avlTree, 7) testInsert(avlTree, 9) testInsert(avlTree, 10) testInsert(avlTree, 6) /* 插入重複節點 */ testInsert(avlTree, 7) /* 刪除節點 */ // 請關注刪除節點後,AVL 樹是如何保持平衡的 testRemove(avlTree, 8) // 刪除度為 0 的節點 testRemove(avlTree, 5) // 刪除度為 1 的節點 testRemove(avlTree, 4) // 刪除度為 2 的節點 /* 查詢節點 */ val node = avlTree.search(7) println("\n 查詢到的節點物件為 $node,節點值 = ${node?._val}") } ================================================ FILE: zh-hant/codes/kotlin/chapter_tree/binary_search_tree.kt ================================================ /** * File: binary_search_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* 二元搜尋樹 */ class BinarySearchTree { // 初始化空樹 private var root: TreeNode? = null /* 獲取二元樹根節點 */ fun getRoot(): TreeNode? { return root } /* 查詢節點 */ fun search(num: Int): TreeNode? { var cur = root // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 目標節點在 cur 的右子樹中 cur = if (cur._val < num) cur.right // 目標節點在 cur 的左子樹中 else if (cur._val > num) cur.left // 找到目標節點,跳出迴圈 else break } // 返回目標節點 return cur } /* 插入節點 */ fun insert(num: Int) { // 若樹為空,則初始化根節點 if (root == null) { root = TreeNode(num) return } var cur = root var pre: TreeNode? = null // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 找到重複節點,直接返回 if (cur._val == num) return pre = cur // 插入位置在 cur 的右子樹中 cur = if (cur._val < num) cur.right // 插入位置在 cur 的左子樹中 else cur.left } // 插入節點 val node = TreeNode(num) if (pre?._val!! < num) pre.right = node else pre.left = node } /* 刪除節點 */ fun remove(num: Int) { // 若樹為空,直接提前返回 if (root == null) return var cur = root var pre: TreeNode? = null // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 找到待刪除節點,跳出迴圈 if (cur._val == num) break pre = cur // 待刪除節點在 cur 的右子樹中 cur = if (cur._val < num) cur.right // 待刪除節點在 cur 的左子樹中 else cur.left } // 若無待刪除節點,則直接返回 if (cur == null) return // 子節點數量 = 0 or 1 if (cur.left == null || cur.right == null) { // 當子節點數量 = 0 / 1 時, child = null / 該子節點 val child = if (cur.left != null) cur.left else cur.right // 刪除節點 cur if (cur != root) { if (pre!!.left == cur) pre.left = child else pre.right = child } else { // 若刪除節點為根節點,則重新指定根節點 root = child } // 子節點數量 = 2 } else { // 獲取中序走訪中 cur 的下一個節點 var tmp = cur.right while (tmp!!.left != null) { tmp = tmp.left } // 遞迴刪除節點 tmp remove(tmp._val) // 用 tmp 覆蓋 cur cur._val = tmp._val } } } /* Driver Code */ fun main() { /* 初始化二元搜尋樹 */ val bst = BinarySearchTree() // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 val nums = intArrayOf(8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15) for (num in nums) { bst.insert(num) } println("\n初始化的二元樹為\n") printTree(bst.getRoot()) /* 查詢節點 */ val node = bst.search(7) println("查詢到的節點物件為 $node,節點值 = ${node?._val}") /* 插入節點 */ bst.insert(16) println("\n插入節點 16 後,二元樹為\n") printTree(bst.getRoot()) /* 刪除節點 */ bst.remove(1) println("\n刪除節點 1 後,二元樹為\n") printTree(bst.getRoot()) bst.remove(2) println("\n刪除節點 2 後,二元樹為\n") printTree(bst.getRoot()) bst.remove(4) println("\n刪除節點 4 後,二元樹為\n") printTree(bst.getRoot()) } ================================================ FILE: zh-hant/codes/kotlin/chapter_tree/binary_tree.kt ================================================ /** * File: binary_tree.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree /* Driver Code */ fun main() { /* 初始化二元樹 */ // 初始化節點 val n1 = TreeNode(1) val n2 = TreeNode(2) val n3 = TreeNode(3) val n4 = TreeNode(4) val n5 = TreeNode(5) // 構建節點之間的引用(指標) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 println("\n初始化二元樹\n") printTree(n1) /* 插入與刪除節點 */ val P = TreeNode(0) // 在 n1 -> n2 中間插入節點 P n1.left = P P.left = n2 println("\n插入節點 P 後\n") printTree(n1) // 刪除節點 P n1.left = n2 println("\n刪除節點 P 後\n") printTree(n1) } ================================================ FILE: zh-hant/codes/kotlin/chapter_tree/binary_tree_bfs.kt ================================================ /** * File: binary_tree_bfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree import java.util.* /* 層序走訪 */ fun levelOrder(root: TreeNode?): MutableList { // 初始化佇列,加入根節點 val queue = LinkedList() queue.add(root) // 初始化一個串列,用於儲存走訪序列 val list = mutableListOf() while (queue.isNotEmpty()) { val node = queue.poll() // 隊列出隊 list.add(node?._val!!) // 儲存節點值 if (node.left != null) queue.offer(node.left) // 左子節點入列 if (node.right != null) queue.offer(node.right) // 右子節點入列 } return list } /* Driver Code */ fun main() { /* 初始化二元樹 */ // 這裡藉助了一個從串列直接生成二元樹的函式 val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) println("\n初始化二元樹\n") printTree(root) /* 層序走訪 */ val list = levelOrder(root) println("\n層序走訪的節點列印序列 = $list") } ================================================ FILE: zh-hant/codes/kotlin/chapter_tree/binary_tree_dfs.kt ================================================ /** * File: binary_tree_dfs.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package chapter_tree import utils.TreeNode import utils.printTree // 初始化串列,用於儲存走訪序列 var list = mutableListOf() /* 前序走訪 */ fun preOrder(root: TreeNode?) { if (root == null) return // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 list.add(root._val) preOrder(root.left) preOrder(root.right) } /* 中序走訪 */ fun inOrder(root: TreeNode?) { if (root == null) return // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 inOrder(root.left) list.add(root._val) inOrder(root.right) } /* 後序走訪 */ fun postOrder(root: TreeNode?) { if (root == null) return // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 postOrder(root.left) postOrder(root.right) list.add(root._val) } /* Driver Code */ fun main() { /* 初始化二元樹 */ // 這裡藉助了一個從串列直接生成二元樹的函式 val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) println("\n初始化二元樹\n") printTree(root) /* 前序走訪 */ list.clear() preOrder(root) println("\n前序走訪的節點列印序列 = $list") /* 中序走訪 */ list.clear() inOrder(root) println("\n中序走訪的節點列印序列 = $list") /* 後序走訪 */ list.clear() postOrder(root) println("\n後序走訪的節點列印序列 = $list") } ================================================ FILE: zh-hant/codes/kotlin/utils/ListNode.kt ================================================ /** * File: ListNode.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* 鏈結串列節點 */ class ListNode(var _val: Int) { var next: ListNode? = null companion object { /* 將串列反序列化為鏈結串列 */ fun arrToLinkedList(arr: IntArray): ListNode? { val dum = ListNode(0) var head = dum for (_val in arr) { head.next = ListNode(_val) head = head.next!! } return dum.next } } } ================================================ FILE: zh-hant/codes/kotlin/utils/PrintUtil.kt ================================================ /** * File: PrintUtil.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils import java.util.* class Trunk(var prev: Trunk?, var str: String) /* 列印矩陣(Array) */ fun printMatrix(matrix: Array>) { println("[") for (row in matrix) { println(" $row,") } println("]") } /* 列印矩陣(List) */ fun printMatrix(matrix: MutableList>) { println("[") for (row in matrix) { println(" $row,") } println("]") } /* 列印鏈結串列 */ fun printLinkedList(h: ListNode?) { var head = h val list = mutableListOf() while (head != null) { list.add(head._val.toString()) head = head.next } println(list.joinToString(separator = " -> ")) } /* 列印二元樹 */ fun printTree(root: TreeNode?) { printTree(root, null, false) } /** * 列印二元樹 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ fun printTree(root: TreeNode?, prev: Trunk?, isRight: Boolean) { if (root == null) { return } var prevStr = " " val trunk = Trunk(prev, prevStr) printTree(root.right, trunk, true) if (prev == null) { trunk.str = "———" } else if (isRight) { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev.str = prevStr } showTrunks(trunk) println(" ${root._val}") if (prev != null) { prev.str = prevStr } trunk.str = " |" printTree(root.left, trunk, false) } fun showTrunks(p: Trunk?) { if (p == null) { return } showTrunks(p.prev) print(p.str) } /* 列印雜湊表 */ fun printHashMap(map: Map) { for ((key, value) in map) { println("${key.toString()} -> $value") } } /* 列印堆積 */ fun printHeap(queue: Queue?) { val list = mutableListOf() queue?.let { list.addAll(it) } print("堆積的陣列表示:") println(list) println("堆積的樹狀表示:") val root = TreeNode.listToTree(list) printTree(root) } ================================================ FILE: zh-hant/codes/kotlin/utils/TreeNode.kt ================================================ /** * File: TreeNode.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* 二元樹節點類別 */ /* 建構子 */ class TreeNode( var _val: Int // 節點值 ) { var height: Int = 0 // 節點高度 var left: TreeNode? = null // 左子節點引用 var right: TreeNode? = null // 右子節點引用 // 序列化編碼規則請參考: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二元樹的陣列表示: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // 二元樹的鏈結串列表示: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* 將串列反序列化為二元樹:遞迴 */ companion object { private fun listToTreeDFS(arr: MutableList, i: Int): TreeNode? { if (i < 0 || i >= arr.size || arr[i] == null) { return null } val root = TreeNode(arr[i]!!) root.left = listToTreeDFS(arr, 2 * i + 1) root.right = listToTreeDFS(arr, 2 * i + 2) return root } /* 將串列反序列化為二元樹 */ fun listToTree(arr: MutableList): TreeNode? { return listToTreeDFS(arr, 0) } /* 將二元樹序列化為串列:遞迴 */ private fun treeToListDFS(root: TreeNode?, i: Int, res: MutableList) { if (root == null) return while (i >= res.size) { res.add(null) } res[i] = root._val treeToListDFS(root.left, 2 * i + 1, res) treeToListDFS(root.right, 2 * i + 2, res) } /* 將二元樹序列化為串列 */ fun treeToList(root: TreeNode?): MutableList { val res = mutableListOf() treeToListDFS(root, 0, res) return res } } } ================================================ FILE: zh-hant/codes/kotlin/utils/Vertex.kt ================================================ /** * File: Vertex.kt * Created Time: 2024-01-25 * Author: curtishd (1023632660@qq.com) */ package utils /* 頂點類別 */ class Vertex(val _val: Int) { companion object { /* 輸入值串列 vals ,返回頂點串列 vets */ fun valsToVets(vals: IntArray): Array { val vets = arrayOfNulls(vals.size) for (i in vals.indices) { vets[i] = Vertex(vals[i]) } return vets } /* 輸入頂點串列 vets ,返回值串列 vals */ fun vetsToVals(vets: MutableList): MutableList { val vals = mutableListOf() for (vet in vets) { vals.add(vet!!._val) } return vals } } } ================================================ FILE: zh-hant/codes/python/.gitignore ================================================ __pycache__ ================================================ FILE: zh-hant/codes/python/chapter_array_and_linkedlist/array.py ================================================ """ 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 是動態陣列,可以直接擴展 # 為了方便學習,本函式將 list 看作長度不可變的陣列 def extend(nums: list[int], enlarge: int) -> list[int]: """擴展陣列長度""" # 初始化一個擴展長度後的陣列 res = [0] * (len(nums) + enlarge) # 將原陣列中的所有元素複製到新陣列 for i in range(len(nums)): res[i] = nums[i] # 返回擴展後的新陣列 return res def insert(nums: list[int], num: int, index: int): """在陣列的索引 index 處插入元素 num""" # 把索引 index 以及之後的所有元素向後移動一位 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 之後的所有元素向前移動一位 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) ================================================ FILE: zh-hant/codes/python/chapter_array_and_linkedlist/linked_list.py ================================================ """ 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)) ================================================ FILE: zh-hant/codes/python/chapter_array_and_linkedlist/list.py ================================================ """ 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 # 拼接兩個串列 nums1 = [6, 8, 7, 10, 9] nums += nums1 print("\n將串列 nums1 拼接到 nums 之後,得到 nums =", nums) # 排序串列 nums.sort() print("\n排序串列後 nums =", nums) ================================================ FILE: zh-hant/codes/python/chapter_array_and_linkedlist/my_list.py ================================================ """ 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("索引越界") return self._arr[index] def set(self, num: int, index: int): """更新元素""" if index < 0 or index >= self._size: raise IndexError("索引越界") 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("索引越界") # 元素數量超出容量時,觸發擴容機制 if self._size == self.capacity(): self.extend_capacity() # 將索引 index 以及之後的元素都向後移動一位 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("索引越界") num = self._arr[index] # 將索引 index 之後的元素都向前移動一位 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()}") ================================================ FILE: zh-hant/codes/python/chapter_backtracking/n_queens.py ================================================ """ 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 """Driver Code""" 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) ================================================ FILE: zh-hant/codes/python/chapter_backtracking/permutations_i.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [1, 2, 3] res = permutations_i(nums) print(f"輸入陣列 nums = {nums}") print(f"所有排列 res = {res}") ================================================ FILE: zh-hant/codes/python/chapter_backtracking/permutations_ii.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [1, 2, 2] res = permutations_ii(nums) print(f"輸入陣列 nums = {nums}") print(f"所有排列 res = {res}") ================================================ FILE: zh-hant/codes/python/chapter_backtracking/preorder_traversal_i_compact.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n初始化二元樹") print_tree(root) # 前序走訪 res = list[TreeNode]() pre_order(root) print("\n輸出所有值為 7 的節點") print([node.val for node in res]) ================================================ FILE: zh-hant/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py ================================================ """ 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() """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n初始化二元樹") print_tree(root) # 前序走訪 path = list[TreeNode]() res = list[list[TreeNode]]() pre_order(root) print("\n輸出所有根節點到節點 7 的路徑") for path in res: print([node.val for node in path]) ================================================ FILE: zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py ================================================ """ 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() """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n初始化二元樹") print_tree(root) # 前序走訪 path = list[TreeNode]() res = list[list[TreeNode]]() pre_order(root) print("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") for path in res: print([node.val for node in path]) ================================================ FILE: zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_template.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n初始化二元樹") print_tree(root) # 回溯演算法 res = [] backtrack(state=[], choices=[root], res=res) print("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點") for path in res: print([node.val for node in path]) ================================================ FILE: zh-hant/codes/python/chapter_backtracking/subset_sum_i.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [3, 4, 5] target = 9 res = subset_sum_i(nums, target) print(f"輸入陣列 nums = {nums}, target = {target}") print(f"所有和等於 {target} 的子集 res = {res}") ================================================ FILE: zh-hant/codes/python/chapter_backtracking/subset_sum_i_naive.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [3, 4, 5] target = 9 res = subset_sum_i_naive(nums, target) print(f"輸入陣列 nums = {nums}, target = {target}") print(f"所有和等於 {target} 的子集 res = {res}") print(f"請注意,該方法輸出的結果包含重複集合") ================================================ FILE: zh-hant/codes/python/chapter_backtracking/subset_sum_ii.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [4, 4, 5] target = 9 res = subset_sum_ii(nums, target) print(f"輸入陣列 nums = {nums}, target = {target}") print(f"所有和等於 {target} 的子集 res = {res}") ================================================ FILE: zh-hant/codes/python/chapter_computational_complexity/iteration.py ================================================ """ 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 迴圈(兩次更新)""" 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 迴圈(兩次更新)求和結果 res = {res}") res = nested_for_loop(n) print(f"\n雙層 for 迴圈的走訪結果 {res}") ================================================ FILE: zh-hant/codes/python/chapter_computational_complexity/recursion.py ================================================ """ 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}") ================================================ FILE: zh-hant/codes/python/chapter_computational_complexity/space_complexity.py ================================================ """ 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 長度為 n, n-1, ..., 2, 1 nums = [0] * n 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) ================================================ FILE: zh-hant/codes/python/chapter_computational_complexity/time_complexity.py ================================================ """ 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 # 細胞每輪一分為二,形成數列 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 = linear_log_recur(n // 2) + linear_log_recur(n // 2) # 當前子問題包含 n 個操作 for _ in range(n): count += 1 return count def factorial_recur(n: int) -> int: """階乘階(遞迴實現)""" if n == 0: return 1 count = 0 # 從 1 個分裂出 n 個 for _ in range(n): count += factorial_recur(n - 1) return count """Driver Code""" if __name__ == "__main__": # 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 n = 8 print("輸入資料大小 n =", n) count = constant(n) print("常數階的操作數量 =", count) count = linear(n) print("線性階的操作數量 =", count) count = array_traversal([0] * n) print("線性階(走訪陣列)的操作數量 =", count) count = quadratic(n) print("平方階的操作數量 =", count) nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1] count = bubble_sort(nums) print("平方階(泡沫排序)的操作數量 =", count) count = exponential(n) print("指數階(迴圈實現)的操作數量 =", count) count = exp_recur(n) print("指數階(遞迴實現)的操作數量 =", count) count = logarithmic(n) print("對數階(迴圈實現)的操作數量 =", count) count = log_recur(n) print("對數階(遞迴實現)的操作數量 =", count) count = linear_log_recur(n) print("線性對數階(遞迴實現)的操作數量 =", count) count = factorial_recur(n) print("階乘階(遞迴實現)的操作數量 =", count) ================================================ FILE: zh-hant/codes/python/chapter_computational_complexity/worst_best_time_complexity.py ================================================ """ 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) ================================================ FILE: zh-hant/codes/python/chapter_divide_and_conquer/binary_search_recur.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # 二分搜尋(雙閉區間) index = binary_search(nums, target) print("目標元素 6 的索引 = ", index) ================================================ FILE: zh-hant/codes/python/chapter_divide_and_conquer/build_tree.py ================================================ """ 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 元素到索引的對映 inorder_map = {val: i for i, val in enumerate(inorder)} root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) return root """Driver Code""" if __name__ == "__main__": preorder = [3, 9, 2, 1, 7] inorder = [9, 3, 1, 2, 7] print(f"前序走訪 = {preorder}") print(f"中序走訪 = {inorder}") root = build_tree(preorder, inorder) print("構建的二元樹為:") print_tree(root) ================================================ FILE: zh-hant/codes/python/chapter_divide_and_conquer/hanota.py ================================================ """ 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 只剩下一個圓盤,則直接將其移到 tar if i == 1: move(src, tar) return # 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf dfs(i - 1, src, tar, buf) # 子問題 f(1) :將 src 剩餘一個圓盤移到 tar move(src, tar) # 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar dfs(i - 1, buf, src, tar) def solve_hanota(A: list[int], B: list[int], C: list[int]): """求解河內塔問題""" n = len(A) # 將 A 頂部 n 個圓盤藉助 B 移到 C dfs(n, A, B, C) """Driver Code""" 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}") ================================================ FILE: zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_backtrack(n) print(f"爬 {n} 階樓梯共有 {res} 種方案") ================================================ FILE: zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_constraint_dp(n) print(f"爬 {n} 階樓梯共有 {res} 種方案") ================================================ FILE: zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dfs(n) print(f"爬 {n} 階樓梯共有 {res} 種方案") ================================================ FILE: zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dfs_mem(n) print(f"爬 {n} 階樓梯共有 {res} 種方案") ================================================ FILE: zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dp(n) print(f"爬 {n} 階樓梯共有 {res} 種方案") res = climbing_stairs_dp_comp(n) print(f"爬 {n} 階樓梯共有 {res} 種方案") ================================================ FILE: zh-hant/codes/python/chapter_dynamic_programming/coin_change.py ================================================ """ 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 """Driver Code""" 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}") ================================================ FILE: zh-hant/codes/python/chapter_dynamic_programming/coin_change_ii.py ================================================ """ 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] """Driver Code""" 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}") ================================================ FILE: zh-hant/codes/python/chapter_dynamic_programming/edit_distance.py ================================================ """ File: edit_distancde.py Created Time: 2023-07-04 Author: krahets (krahets@163.com) """ def edit_distance_dfs(s: str, t: str, i: int, j: int) -> int: """編輯距離:暴力搜尋""" # 若 s 和 t 都為空,則返回 0 if i == 0 and j == 0: return 0 # 若 s 為空,則返回 t 長度 if i == 0: return j # 若 t 為空,則返回 s 長度 if j == 0: return i # 若兩字元相等,則直接跳過此兩字元 if s[i - 1] == t[j - 1]: return edit_distance_dfs(s, t, i - 1, j - 1) # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 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] # 若兩字元相等,則直接跳過此兩字元 if s[i - 1] == t[j - 1]: return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 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]: # 若兩字元相等,則直接跳過此兩字元 dp[i][j] = dp[i - 1][j - 1] else: # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 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]: # 若兩字元相等,則直接跳過此兩字元 dp[j] = leftup else: # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[j] = min(dp[j - 1], dp[j], leftup) + 1 leftup = temp # 更新為下一輪的 dp[i-1, j-1] return dp[m] """Driver Code""" if __name__ == "__main__": s = "bag" t = "pack" n, m = len(s), len(t) # 暴力搜尋 res = edit_distance_dfs(s, t, n, m) print(f"將 {s} 更改為 {t} 最少需要編輯 {res} 步") # 記憶化搜尋 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} 步") ================================================ FILE: zh-hant/codes/python/chapter_dynamic_programming/knapsack.py ================================================ """ 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] # 返回兩種方案中價值更大的那一個 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] # 記錄並返回兩種方案中價值更大的那一個 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] """Driver Code""" if __name__ == "__main__": wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = len(wgt) # 暴力搜尋 res = knapsack_dfs(wgt, val, n, cap) print(f"不超過背包容量的最大物品價值為 {res}") # 記憶化搜尋 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}") ================================================ FILE: zh-hant/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py ================================================ """ 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 """Driver Code""" 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}") ================================================ FILE: zh-hant/codes/python/chapter_dynamic_programming/min_path_sum.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] n, m = len(grid), len(grid[0]) # 暴力搜尋 res = min_path_sum_dfs(grid, n - 1, m - 1) print(f"從左上角到右下角的最小路徑和為 {res}") # 記憶化搜尋 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}") ================================================ FILE: zh-hant/codes/python/chapter_dynamic_programming/unbounded_knapsack.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": wgt = [1, 2, 3] val = [5, 11, 15] cap = 4 # 動態規劃 res = unbounded_knapsack_dp(wgt, val, cap) print(f"不超過背包容量的最大物品價值為 {res}") # 空間最佳化後的動態規劃 res = unbounded_knapsack_dp_comp(wgt, val, cap) print(f"不超過背包容量的最大物品價值為 {res}") ================================================ FILE: zh-hant/codes/python/chapter_graph/graph_adjacency_list.py ================================================ """ 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]]): """建構子""" # 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 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},") """Driver Code""" 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() ================================================ FILE: zh-hant/codes/python/chapter_graph/graph_adjacency_matrix.py ================================================ """ 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 元素代表頂點索引,即對應 vertices 元素索引 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 對應 vertices 元素索引 # 索引越界與相等處理 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 對應 vertices 元素索引 # 索引越界與相等處理 if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: raise IndexError() self.adj_mat[i][j] = 0 self.adj_mat[j][i] = 0 def print(self): """列印鄰接矩陣""" print("頂點串列 =", self.vertices) print("鄰接矩陣 =") print_matrix(self.adj_mat) """Driver Code""" if __name__ == "__main__": # 初始化無向圖 # 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 vertices = [1, 3, 2, 5, 4] edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] graph = GraphAdjMat(vertices, edges) print("\n初始化後,圖為") graph.print() # 新增邊 # 頂點 1, 2 的索引分別為 0, 2 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() ================================================ FILE: zh-hant/codes/python/chapter_graph/graph_bfs.py ================================================ """ 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 """Driver Code""" 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)) ================================================ FILE: zh-hant/codes/python/chapter_graph/graph_dfs.py ================================================ """ 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 """Driver Code""" 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)) ================================================ FILE: zh-hant/codes/python/chapter_greedy/coin_change_greedy.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # 貪婪:能夠保證找到全域性最優解 coins = [1, 5, 10, 20, 50, 100] amt = 186 res = coin_change_greedy(coins, amt) print(f"\ncoins = {coins}, amt = {amt}") print(f"湊到 {amt} 所需的最少硬幣數量為 {res}") # 貪婪:無法保證找到全域性最優解 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") ================================================ FILE: zh-hant/codes/python/chapter_greedy/fractional_knapsack.py ================================================ """ 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: """分數背包:貪婪""" # 建立物品串列,包含兩個屬性:重量、價值 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 """Driver Code""" if __name__ == "__main__": wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = len(wgt) # 貪婪演算法 res = fractional_knapsack(wgt, val, cap) print(f"不超過背包容量的最大物品價值為 {res}") ================================================ FILE: zh-hant/codes/python/chapter_greedy/max_capacity.py ================================================ """ 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 # 迴圈貪婪選擇,直至兩板相遇 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 """Driver Code""" if __name__ == "__main__": ht = [3, 8, 5, 2, 7, 7, 3, 4] # 貪婪演算法 res = max_capacity(ht) print(f"最大容量為 {res}") ================================================ FILE: zh-hant/codes/python/chapter_greedy/max_product_cutting.py ================================================ """ 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)) """Driver Code""" if __name__ == "__main__": n = 58 # 貪婪演算法 res = max_product_cutting(n) print(f"最大切分乘積為 {res}") ================================================ FILE: zh-hant/codes/python/chapter_hashing/array_hash_map.py ================================================ """ 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 | None: """查詢操作""" 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, "小哈") hmap.put(15937, "小囉") hmap.put(16750, "小算") hmap.put(13276, "小法") hmap.put(10583, "小鴨") print("\n新增完成後,雜湊表為\nKey -> Value") hmap.print() # 查詢操作 # 向雜湊表中輸入鍵 key ,得到值 value name = hmap.get(15937) print("\n輸入學號 15937 ,查詢到姓名 " + name) # 刪除操作 # 在雜湊表中刪除鍵值對 (key, value) hmap.remove(10583) print("\n刪除 10583 後,雜湊表為\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) ================================================ FILE: zh-hant/codes/python/chapter_hashing/built_in_hash.py ================================================ """ 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 演算法" hash_str = hash(str) print(f"字串 {str} 的雜湊值為 {hash_str}") tup = (12836, "小哈") hash_tup = hash(tup) print(f"元組 {tup} 的雜湊值為 {hash(hash_tup)}") obj = ListNode(0) hash_obj = hash(obj) print(f"節點物件 {obj} 的雜湊值為 {hash_obj}") ================================================ FILE: zh-hant/codes/python/chapter_hashing/hash_map.py ================================================ """ 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] = "小哈" hmap[15937] = "小囉" hmap[16750] = "小算" hmap[13276] = "小法" hmap[10583] = "小鴨" print("\n新增完成後,雜湊表為\nKey -> Value") print_dict(hmap) # 查詢操作 # 向雜湊表中輸入鍵 key ,得到值 value name: str = hmap[15937] print("\n輸入學號 15937 ,查詢到姓名 " + name) # 刪除操作 # 在雜湊表中刪除鍵值對 (key, value) hmap.pop(10583) print("\n刪除 10583 後,雜湊表為\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) ================================================ FILE: zh-hant/codes/python/chapter_hashing/hash_map_chaining.py ================================================ """ 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] # 走訪桶,若找到 key ,則返回對應 val for pair in bucket: if pair.key == key: return pair.val # 若未找到 key ,則返回 None return None def put(self, key: int, val: str): """新增操作""" # 當負載因子超過閾值時,執行擴容 if self.load_factor() > self.load_thres: self.extend() index = self.hash_func(key) bucket = self.buckets[index] # 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for pair in bucket: if pair.key == key: pair.val = val return # 若無該 key ,則將鍵值對新增至尾部 pair = Pair(key, val) bucket.append(pair) self.size += 1 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, "小哈") hashmap.put(15937, "小囉") hashmap.put(16750, "小算") hashmap.put(13276, "小法") hashmap.put(10583, "小鴨") print("\n新增完成後,雜湊表為\n[Key1 -> Value1, Key2 -> Value2, ...]") hashmap.print() # 查詢操作 # 向雜湊表中輸入鍵 key ,得到值 value name = hashmap.get(13276) print("\n輸入學號 13276 ,查詢到姓名 " + name) # 刪除操作 # 在雜湊表中刪除鍵值對 (key, value) hashmap.remove(12836) print("\n刪除 12836 後,雜湊表為\n[Key1 -> Value1, Key2 -> Value2, ...]") hashmap.print() ================================================ FILE: zh-hant/codes/python/chapter_hashing/hash_map_open_addressing.py ================================================ """ 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: # 若遇到 key ,返回對應的桶索引 if self.buckets[index].key == key: # 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if first_tombstone != -1: self.buckets[first_tombstone] = self.buckets[index] self.buckets[index] = self.TOMBSTONE return first_tombstone # 返回移動後的桶索引 return index # 返回桶索引 # 記錄遇到的首個刪除標記 if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE: first_tombstone = index # 計算桶索引,越過尾部則返回頭部 index = (index + 1) % self.capacity # 若 key 不存在,則返回新增點的索引 return index if first_tombstone == -1 else first_tombstone def get(self, key: int) -> str: """查詢操作""" # 搜尋 key 對應的桶索引 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, "小哈") hashmap.put(15937, "小囉") hashmap.put(16750, "小算") hashmap.put(13276, "小法") hashmap.put(10583, "小鴨") print("\n新增完成後,雜湊表為\nKey -> Value") hashmap.print() # 查詢操作 # 向雜湊表中輸入鍵 key ,得到值 val name = hashmap.get(13276) print("\n輸入學號 13276 ,查詢到姓名 " + name) # 刪除操作 # 在雜湊表中刪除鍵值對 (key, val) hashmap.remove(16750) print("\n刪除 16750 後,雜湊表為\nKey -> Value") hashmap.print() ================================================ FILE: zh-hant/codes/python/chapter_hashing/simple_hash.py ================================================ """ 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: """互斥或雜湊""" 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 演算法" hash = add_hash(key) print(f"加法雜湊值為 {hash}") hash = mul_hash(key) print(f"乘法雜湊值為 {hash}") hash = xor_hash(key) print(f"互斥或雜湊值為 {hash}") hash = rot_hash(key) print(f"旋轉雜湊值為 {hash}") ================================================ FILE: zh-hant/codes/python/chapter_heap/heap.py ================================================ """ 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]) """Driver Code""" if __name__ == "__main__": # 初始化小頂堆積 min_heap, flag = [], 1 # 初始化大頂堆積 max_heap, flag = [], -1 print("\n以下測試樣例為大頂堆積") # Python 的 heapq 模組預設實現小頂堆積 # 考慮將“元素取負”後再入堆積,這樣就可以將大小關係顛倒,從而實現大頂堆積 # 在本示例中,flag = 1 時對應小頂堆積,flag = -1 時對應大頂堆積 # 元素入堆積 test_push(max_heap, 1, flag) test_push(max_heap, 3, flag) test_push(max_heap, 2, flag) test_push(max_heap, 5, flag) test_push(max_heap, 4, flag) # 獲取堆積頂元素 peek: int = flag * max_heap[0] print(f"\n堆積頂元素為 {peek}") # 堆積頂元素出堆積 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) ================================================ FILE: zh-hant/codes/python/chapter_heap/my_heap.py ================================================ """ 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 # 交換兩節點 self.swap(i, p) # 迴圈向上堆積化 i = p def pop(self) -> int: """元素出堆積""" # 判空處理 if self.is_empty(): raise IndexError("堆積為空") # 交換根節點與最右葉節點(交換首元素與尾元素) self.swap(0, self.size() - 1) # 刪除節點 val = self.max_heap.pop() # 從頂至底堆積化 self.sift_down(0) # 返回堆積頂元素 return val def sift_down(self, i: int): """從節點 i 開始,從頂至底堆積化""" 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 # 交換兩節點 self.swap(i, ma) # 迴圈向下堆積化 i = ma def print(self): """列印堆積(二元樹)""" print_heap(self.max_heap) """Driver Code""" if __name__ == "__main__": # 初始化大頂堆積 max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) print("\n輸入串列並建堆積後") 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}") ================================================ FILE: zh-hant/codes/python/chapter_heap/top_k.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": nums = [1, 7, 6, 3, 2] k = 3 res = top_k_heap(nums, k) print(f"最大的 {k} 個元素為") print_heap(res) ================================================ FILE: zh-hant/codes/python/chapter_searching/binary_search.py ================================================ """ 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) // 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) // 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 """Driver Code""" if __name__ == "__main__": target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # 二分搜尋(雙閉區間) index = binary_search(nums, target) print("目標元素 6 的索引 = ", index) # 二分搜尋(左閉右開區間) index = binary_search_lcro(nums, target) print("目標元素 6 的索引 = ", index) ================================================ FILE: zh-hant/codes/python/chapter_searching/binary_search_edge.py ================================================ """ 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: """二分搜尋最左一個 target""" # 等價於查詢 target 的插入點 i = binary_search_insertion(nums, target) # 未找到 target ,返回 -1 if i == len(nums) or nums[i] != target: return -1 # 找到 target ,返回索引 i return i def binary_search_right_edge(nums: list[int], target: int) -> int: """二分搜尋最右一個 target""" # 轉化為查詢最左一個 target + 1 i = binary_search_insertion(nums, target + 1) # j 指向最右一個 target ,i 指向首個大於 target 的元素 j = i - 1 # 未找到 target ,返回 -1 if j == -1 or nums[j] != target: return -1 # 找到 target ,返回索引 j return j """Driver Code""" if __name__ == "__main__": # 包含重複元素的陣列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print(f"\n陣列 nums = {nums}") # 二分搜尋左邊界和右邊界 for target in [6, 7]: index = binary_search_left_edge(nums, target) print(f"最左一個元素 {target} 的索引為 {index}") index = binary_search_right_edge(nums, target) print(f"最右一個元素 {target} 的索引為 {index}") ================================================ FILE: zh-hant/codes/python/chapter_searching/binary_search_insertion.py ================================================ """ 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) // 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 # 找到 target ,返回插入點 m # 未找到 target ,返回插入點 i return i def binary_search_insertion(nums: list[int], target: int) -> int: """二分搜尋插入點(存在重複元素)""" i, j = 0, len(nums) - 1 # 初始化雙閉區間 [0, n-1] while i <= j: m = (i + j) // 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: j = m - 1 # 首個小於 target 的元素在區間 [i, m-1] 中 # 返回插入點 i return i """Driver Code""" if __name__ == "__main__": # 無重複元素的陣列 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print(f"\n陣列 nums = {nums}") # 二分搜尋插入點 for target in [6, 9]: index = binary_search_insertion_simple(nums, target) print(f"元素 {target} 的插入點的索引為 {index}") # 包含重複元素的陣列 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}") ================================================ FILE: zh-hant/codes/python/chapter_searching/hashing_search.py ================================================ """ 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: """雜湊查詢(陣列)""" # 雜湊表的 key: 目標元素,value: 索引 # 若雜湊表中無此 key ,返回 -1 return hmap.get(target, -1) def hashing_search_linkedlist( hmap: dict[int, ListNode], target: int ) -> ListNode | None: """雜湊查詢(鏈結串列)""" # 雜湊表的 key: 目標元素,value: 節點物件 # 若雜湊表中無此 key ,返回 None return hmap.get(target, None) """Driver Code""" 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 # key: 元素,value: 索引 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 # key: 節點值,value: 節點 head = head.next node: ListNode = hashing_search_linkedlist(map1, target) print("目標節點值 3 的對應節點物件為", node) ================================================ FILE: zh-hant/codes/python/chapter_searching/linear_search.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": target = 3 # 在陣列中執行線性查詢 nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] index: int = linear_search_array(nums, target) print("目標元素 3 的索引 =", index) # 在鏈結串列中執行線性查詢 head: ListNode = list_to_linked_list(nums) node: ListNode | None = linear_search_linkedlist(head, target) print("目標節點值 3 的對應節點物件為", node) ================================================ FILE: zh-hant/codes/python/chapter_searching/two_sum.py ================================================ """ 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 [] """Driver Code""" if __name__ == "__main__": # ======= Test Case ======= nums = [2, 7, 11, 15] target = 13 # ====== Driver Code ====== # 方法一 res: list[int] = two_sum_brute_force(nums, target) print("方法一 res =", res) # 方法二 res: list[int] = two_sum_hash_table(nums, target) print("方法二 res =", res) ================================================ FILE: zh-hant/codes/python/chapter_sorting/bubble_sort.py ================================================ """ 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 # 此輪“冒泡”未交換任何元素,直接跳出 """Driver Code""" 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) ================================================ FILE: zh-hant/codes/python/chapter_sorting/bucket_sort.py ================================================ """ 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) ================================================ FILE: zh-hant/codes/python/chapter_sorting/counting_sort.py ================================================ """ File: counting_sort.py Created Time: 2023-03-21 Author: krahets (krahets@163.com) """ def counting_sort_naive(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 ,將各元素填入原陣列 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 是 num 在 res 中最後一次出現的索引 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] """Driver Code""" 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}") ================================================ FILE: zh-hant/codes/python/chapter_sorting/heap_sort.py ================================================ """ 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 # 交換兩節點 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) """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] heap_sort(nums) print("堆積排序完成後 nums =", nums) ================================================ FILE: zh-hant/codes/python/chapter_sorting/insertion_sort.py ================================================ """ 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] 向右移動一位 j -= 1 nums[j + 1] = base # 將 base 賦值到正確位置 """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] insertion_sort(nums) print("插入排序完成後 nums =", nums) ================================================ FILE: zh-hant/codes/python/chapter_sorting/merge_sort.py ================================================ """ 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) // 2 # 計算中點 merge_sort(nums, left, mid) # 遞迴左子陣列 merge_sort(nums, mid + 1, right) # 遞迴右子陣列 # 合併階段 merge(nums, left, mid, right) """Driver Code""" if __name__ == "__main__": nums = [7, 3, 2, 6, 0, 1, 5, 4] merge_sort(nums, 0, len(nums) - 1) print("合併排序完成後 nums =", nums) ================================================ FILE: zh-hant/codes/python/chapter_sorting/quick_sort.py ================================================ """ 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] # 將基準數交換至兩子陣列的分界線 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: """選取三個候選元素的中位數""" 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] # 將基準數交換至兩子陣列的分界線 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] # 將基準數交換至兩子陣列的分界線 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) # 對兩個子陣列中較短的那個執行快速排序 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] """Driver Code""" 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) ================================================ FILE: zh-hant/codes/python/chapter_sorting/radix_sort.py ================================================ """ 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)""" # 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 return (num // exp) % 10 def counting_sort_digit(nums: list[int], exp: int): """計數排序(根據 nums 第 k 位排序)""" # 十進位制的位範圍為 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 """Driver Code""" if __name__ == "__main__": # 基數排序 nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ] radix_sort(nums) print("基數排序完成後 nums =", nums) ================================================ FILE: zh-hant/codes/python/chapter_sorting/selection_sort.py ================================================ """ 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] """Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] selection_sort(nums) print("選擇排序完成後 nums =", nums) ================================================ FILE: zh-hant/codes/python/chapter_stack_and_queue/array_deque.py ================================================ """ 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 # 佇列首指標向左移動一位 # 透過取餘操作實現 front 越過陣列頭部後回到尾部 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() # 佇列首指標向後移動一位 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("雙向佇列為空") return self._nums[self._front] def peek_last(self) -> int: """訪問佇列尾元素""" if self.is_empty(): raise IndexError("雙向佇列為空") # 計算尾元素索引 last = self.index(self._front + self._size - 1) return self._nums[last] def to_array(self) -> list[int]: """返回陣列用於列印""" # 僅轉換有效長度範圍內的串列元素 res = [] for i in range(self._size): res.append(self._nums[self.index(self._front + i)]) 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) ================================================ FILE: zh-hant/codes/python/chapter_stack_and_queue/array_queue.py ================================================ """ 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("佇列已滿") # 計算佇列尾指標,指向佇列尾索引 + 1 # 透過取餘操作實現 rear 越過陣列尾部後回到頭部 rear: int = (self._front + self._size) % self.capacity() # 將 num 新增至佇列尾 self._nums[rear] = num self._size += 1 def pop(self) -> int: """出列""" num: int = self.peek() # 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 self._front = (self._front + 1) % self.capacity() self._size -= 1 return num def peek(self) -> int: """訪問佇列首元素""" if self.is_empty(): raise IndexError("佇列為空") return self._nums[self._front] def to_list(self) -> list[int]: """返回串列用於列印""" res = [0] * self.size() j: int = self._front for i in range(self.size()): 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 =", 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()) ================================================ FILE: zh-hant/codes/python/chapter_stack_and_queue/array_stack.py ================================================ """ 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("堆疊為空") return self._stack.pop() def peek(self) -> int: """訪問堆疊頂元素""" if self.is_empty(): raise IndexError("堆疊為空") 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 =", stack.to_list()) # 獲取堆疊的長度 size: int = stack.size() print("堆疊的長度 size =", size) # 判斷是否為空 is_empty: bool = stack.is_empty() print("堆疊是否為空 =", is_empty) ================================================ FILE: zh-hant/codes/python/chapter_stack_and_queue/deque.py ================================================ """ 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("佇列首出列後 deque =", deq) pop_rear: int = deq.pop() # 佇列尾元素出列 print("佇列尾出列元素 pop_rear =", pop_rear) print("佇列尾出列後 deque =", deq) # 獲取雙向佇列的長度 size: int = len(deq) print("雙向佇列長度 size =", size) # 判斷雙向佇列是否為空 is_empty: bool = len(deq) == 0 print("雙向佇列是否為空 =", is_empty) ================================================ FILE: zh-hant/codes/python/chapter_stack_and_queue/linkedlist_deque.py ================================================ """ 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: # 將 node 新增至鏈結串列頭部 self._front.prev = node node.next = self._front self._front = node # 更新頭節點 # 佇列尾入列操作 else: # 將 node 新增至鏈結串列尾部 self._rear.next = node node.prev = self._rear self._rear = node # 更新尾節點 self._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("雙向佇列為空") # 佇列首出列操作 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("雙向佇列為空") return self._front.val def peek_last(self) -> int: """訪問佇列尾元素""" if self.is_empty(): raise IndexError("雙向佇列為空") 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) ================================================ FILE: zh-hant/codes/python/chapter_stack_and_queue/linkedlist_queue.py ================================================ """ 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("佇列為空") 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 =", queue.to_list()) # 獲取佇列的長度 size: int = queue.size() print("佇列長度 size =", size) # 判斷佇列是否為空 is_empty: bool = queue.is_empty() print("佇列是否為空 =", is_empty) ================================================ FILE: zh-hant/codes/python/chapter_stack_and_queue/linkedlist_stack.py ================================================ """ 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("堆疊為空") 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 =", stack.to_list()) # 獲取堆疊的長度 size: int = stack.size() print("堆疊的長度 size =", size) # 判斷是否為空 is_empty: bool = stack.is_empty() print("堆疊是否為空 =", is_empty) ================================================ FILE: zh-hant/codes/python/chapter_stack_and_queue/queue.py ================================================ """ 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 =", que) # 獲取佇列的長度 size: int = len(que) print("佇列長度 size =", size) # 判斷佇列是否為空 is_empty: bool = len(que) == 0 print("佇列是否為空 =", is_empty) ================================================ FILE: zh-hant/codes/python/chapter_stack_and_queue/stack.py ================================================ """ File: stack.py Created Time: 2022-11-29 Author: Peng Chen (pengchzn@gmail.com) """ """Driver Code""" if __name__ == "__main__": # 初始化堆疊 # Python 沒有內建的堆疊類別,可以把 list 當作堆疊來使用 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 =", stack) # 獲取堆疊的長度 size: int = len(stack) print("堆疊的長度 size =", size) # 判斷是否為空 is_empty: bool = len(stack) == 0 print("堆疊是否為空 =", is_empty) ================================================ FILE: zh-hant/codes/python/chapter_tree/array_binary_tree.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # 初始化二元樹 # 這裡藉助了一個從陣列直接生成二元樹的函式 arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] root = list_to_tree(arr) print("\n初始化二元樹\n") print("二元樹的陣列表示:") print(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) ================================================ FILE: zh-hant/codes/python/chapter_tree/avl_tree.py ================================================ """ 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 ,直接刪除 node 並返回 if child is None: return None # 子節點數量 = 1 ,直接刪除 node 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 """Driver Code""" 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)) ================================================ FILE: zh-hant/codes/python/chapter_tree/binary_search_tree.py ================================================ """ 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 or 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) # 用 tmp 覆蓋 cur cur.val = tmp.val """Driver Code""" if __name__ == "__main__": # 初始化二元搜尋樹 bst = BinarySearchTree() nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] # 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 for num in nums: bst.insert(num) print("\n初始化的二元樹為\n") print_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()) ================================================ FILE: zh-hant/codes/python/chapter_tree/binary_tree.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # 初始化二元樹 # 初始化節點 n1 = TreeNode(val=1) n2 = TreeNode(val=2) n3 = TreeNode(val=3) n4 = TreeNode(val=4) n5 = TreeNode(val=5) # 構建節點之間的引用(指標) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 print("\n初始化二元樹\n") print_tree(n1) # 插入與刪除節點 P = TreeNode(0) # 在 n1 -> n2 中間插入節點 P n1.left = P P.left = n2 print("\n插入節點 P 後\n") print_tree(n1) # 刪除節點 n1.left = n2 print("\n刪除節點 P 後\n") print_tree(n1) ================================================ FILE: zh-hant/codes/python/chapter_tree/binary_tree_bfs.py ================================================ """ 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 """Driver Code""" if __name__ == "__main__": # 初始化二元樹 # 這裡藉助了一個從陣列直接生成二元樹的函式 root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\n初始化二元樹\n") print_tree(root) # 層序走訪 res: list[int] = level_order(root) print("\n層序走訪的節點列印序列 = ", res) ================================================ FILE: zh-hant/codes/python/chapter_tree/binary_tree_dfs.py ================================================ """ 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) """Driver Code""" if __name__ == "__main__": # 初始化二元樹 # 這裡藉助了一個從陣列直接生成二元樹的函式 root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\n初始化二元樹\n") print_tree(root) # 前序走訪 res = [] pre_order(root) print("\n前序走訪的節點列印序列 = ", res) # 中序走訪 res.clear() in_order(root) print("\n中序走訪的節點列印序列 = ", res) # 後序走訪 res.clear() post_order(root) print("\n後序走訪的節點列印序列 = ", res) ================================================ FILE: zh-hant/codes/python/modules/__init__.py ================================================ # Follow the PEP 585 - Type Hinting Generics In Standard Collections # https://peps.python.org/pep-0585/ from __future__ import annotations # Import common libs here to simplify the code by `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, ) ================================================ FILE: zh-hant/codes/python/modules/list_node.py ================================================ """ 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 ================================================ FILE: zh-hant/codes/python/modules/print_util.py ================================================ """ 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 ): """ 列印二元樹 This tree printer is borrowed from 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) ================================================ FILE: zh-hant/codes/python/modules/tree_node.py ================================================ """ 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 ================================================ FILE: zh-hant/codes/python/modules/vertex.py ================================================ # 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] ================================================ FILE: zh-hant/codes/python/test_all.py ================================================ import os import glob import subprocess env = os.environ.copy() env["PYTHONIOENCODING"] = "utf-8" if __name__ == "__main__": # find source code files src_paths = sorted(glob.glob("chapter_*/*.py")) errors = [] # run python code 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' ) # Wait for the process to complete, and get the output and error messages stdout, stderr = process.communicate() # Check the exit status exit_status = process.returncode if exit_status != 0: errors.append(stderr) print(f"Tested {len(src_paths)} files") print(f"Found exception in {len(errors)} files") if len(errors) > 0: raise RuntimeError("\n\n".join(errors)) ================================================ FILE: zh-hant/codes/pythontutor/chapter_array_and_linkedlist/array.md ================================================ https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_access%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%A8%E6%A9%9F%E8%A8%AA%E5%95%8F%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5B0%2C%20len%28nums%29-1%5D%20%E4%B8%AD%E9%9A%A8%E6%A9%9F%E6%8A%BD%E5%8F%96%E4%B8%80%E5%80%8B%E6%95%B8%E5%AD%97%0A%20%20%20%20random_index%20%3D%20random.randint%280%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E4%B8%A6%E8%BF%94%E5%9B%9E%E9%9A%A8%E6%A9%9F%E5%85%83%E7%B4%A0%0A%20%20%20%20random_num%20%3D%20nums%5Brandom_index%5D%0A%20%20%20%20return%20random_num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%99%A3%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E9%9A%A8%E6%A9%9F%E8%A8%AA%E5%95%8F%0A%20%20%20%20random_num%3A%20int%20%3D%20random_access%28nums%29%0A%20%20%20%20print%28%22%E5%9C%A8%20nums%20%E4%B8%AD%E7%8D%B2%E5%8F%96%E9%9A%A8%E6%A9%9F%E5%85%83%E7%B4%A0%22%2C%20random_num%29%0A&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20insert%28nums%3A%20list%5Bint%5D%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%99%A3%E5%88%97%E7%9A%84%E7%B4%A2%E5%BC%95%20index%20%E8%99%95%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%20num%22%22%22%0A%20%20%20%20%23%20%E6%8A%8A%E7%B4%A2%E5%BC%95%20index%20%E4%BB%A5%E5%8F%8A%E4%B9%8B%E5%BE%8C%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%90%91%E5%BE%8C%E7%A7%BB%E5%8B%95%E4%B8%80%E4%BD%8D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%20index%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E5%B0%87%20num%20%E8%B3%A6%E7%B5%A6%20index%20%E8%99%95%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5Bindex%5D%20%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%99%A3%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20insert%28nums%2C%206%2C%203%29%0A%20%20%20%20print%28%22%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E8%99%95%E6%8F%92%E5%85%A5%E6%95%B8%E5%AD%97%206%20%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20remove%28nums%3A%20list%5Bint%5D%2C%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E8%99%95%E7%9A%84%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E6%8A%8A%E7%B4%A2%E5%BC%95%20index%20%E4%B9%8B%E5%BE%8C%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%90%91%E5%89%8D%E7%A7%BB%E5%8B%95%E4%B8%80%E4%BD%8D%0A%20%20%20%20for%20i%20in%20range%28index%2C%20len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20%2B%201%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%99%A3%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20remove%28nums%2C%202%29%0A%20%20%20%20print%28%22%E5%88%AA%E9%99%A4%E7%B4%A2%E5%BC%95%202%20%E8%99%95%E7%9A%84%E5%85%83%E7%B4%A0%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20traverse%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E7%B4%A2%E5%BC%95%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%20%20%20%20%23%20%E5%90%8C%E6%99%82%E8%B5%B0%E8%A8%AA%E8%B3%87%E6%96%99%E7%B4%A2%E5%BC%95%E5%92%8C%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20i%2C%20num%20in%20enumerate%28nums%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%99%A3%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%0A%20%20%20%20traverse%28nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20find%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%99%A3%E5%88%97%E4%B8%AD%E6%9F%A5%E8%A9%A2%E6%8C%87%E5%AE%9A%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%99%A3%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E5%85%83%E7%B4%A0%0A%20%20%20%20index%3A%20int%20%3D%20find%28nums%2C%203%29%0A%20%20%20%20print%28%22%E5%9C%A8%20nums%20%E4%B8%AD%E6%9F%A5%E8%A9%A2%E5%85%83%E7%B4%A0%203%20%EF%BC%8C%E5%BE%97%E5%88%B0%E7%B4%A2%E5%BC%95%20%3D%22%2C%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=%23%20%E8%AB%8B%E6%B3%A8%E6%84%8F%EF%BC%8CPython%20%E7%9A%84%20list%20%E6%98%AF%E5%8B%95%E6%85%8B%E9%99%A3%E5%88%97%EF%BC%8C%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E6%93%B4%E5%B1%95%0A%23%20%E7%82%BA%E4%BA%86%E6%96%B9%E4%BE%BF%E5%AD%B8%E7%BF%92%EF%BC%8C%E6%9C%AC%E5%87%BD%E5%BC%8F%E5%B0%87%20list%20%E7%9C%8B%E4%BD%9C%E9%95%B7%E5%BA%A6%E4%B8%8D%E5%8F%AF%E8%AE%8A%E7%9A%84%E9%99%A3%E5%88%97%0Adef%20extend%28nums%3A%20list%5Bint%5D%2C%20enlarge%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%93%B4%E5%B1%95%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E5%80%8B%E6%93%B4%E5%B1%95%E9%95%B7%E5%BA%A6%E5%BE%8C%E7%9A%84%E9%99%A3%E5%88%97%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20%28len%28nums%29%20%2B%20enlarge%29%0A%20%20%20%20%23%20%E5%B0%87%E5%8E%9F%E9%99%A3%E5%88%97%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E8%A4%87%E8%A3%BD%E5%88%B0%E6%96%B0%E9%99%A3%E5%88%97%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%93%B4%E5%B1%95%E5%BE%8C%E7%9A%84%E6%96%B0%E9%99%A3%E5%88%97%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%99%A3%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E9%95%B7%E5%BA%A6%E6%93%B4%E5%B1%95%0A%20%20%20%20nums%20%3D%20extend%28nums%2C%203%29%0A%20%20%20%20print%28%22%E5%B0%87%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%E6%93%B4%E5%B1%95%E8%87%B3%208%20%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20insert%28n0%3A%20ListNode%2C%20P%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%9A%84%E7%AF%80%E9%BB%9E%20n0%20%E4%B9%8B%E5%BE%8C%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%20P%22%22%22%0A%20%20%20%20n1%20%3D%20n0.next%0A%20%20%20%20P.next%20%3D%20n1%0A%20%20%20%20n0.next%20%3D%20P%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E5%80%8B%E7%AF%80%E9%BB%9E%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%0A%20%20%20%20p%20%3D%20ListNode%280%29%0A%20%20%20%20insert%28n0%2C%20p%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20remove%28n0%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%9A%84%E7%AF%80%E9%BB%9E%20n0%20%E4%B9%8B%E5%BE%8C%E7%9A%84%E9%A6%96%E5%80%8B%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20if%20not%20n0.next%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20n0%20-%3E%20P%20-%3E%20n1%0A%20%20%20%20P%20%3D%20n0.next%0A%20%20%20%20n1%20%3D%20P.next%0A%20%20%20%20n0.next%20%3D%20n1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E5%80%8B%E7%AF%80%E9%BB%9E%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%0A%20%20%20%20remove%28n0%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20access%28head%3A%20ListNode%2C%20index%3A%20int%29%20-%3E%20ListNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E4%B8%AD%E7%B4%A2%E5%BC%95%E7%82%BA%20index%20%E7%9A%84%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20for%20_%20in%20range%28index%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20head%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20return%20head%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E5%80%8B%E7%AF%80%E9%BB%9E%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E7%AF%80%E9%BB%9E%0A%20%20%20%20node%20%3D%20access%28n0%2C%203%29%0A%20%20%20%20print%28%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E4%B8%AD%E7%B4%A2%E5%BC%95%203%20%E8%99%95%E7%9A%84%E7%AF%80%E9%BB%9E%E7%9A%84%E5%80%BC%20%3D%20%7B%7D%22.format%28node.val%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20find%28head%3A%20ListNode%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E4%B8%AD%E6%9F%A5%E8%A9%A2%E5%80%BC%E7%82%BA%20target%20%E7%9A%84%E9%A6%96%E5%80%8B%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20index%20%3D%200%0A%20%20%20%20while%20head%3A%0A%20%20%20%20%20%20%20%20if%20head.val%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20index%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20%20%20%20%20index%20%2B%3D%201%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E5%80%8B%E7%AF%80%E9%BB%9E%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E7%AF%80%E9%BB%9E%0A%20%20%20%20index%20%3D%20find%28n0%2C%202%29%0A%20%20%20%20print%28%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E4%B8%AD%E5%80%BC%E7%82%BA%202%20%E7%9A%84%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%7B%7D%22.format%28index%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_array_and_linkedlist/my_list.md ================================================ https://pythontutor.com/render.html#code=class%20MyList%3A%0A%20%20%20%20%22%22%22%E4%B8%B2%E5%88%97%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._capacity%3A%20int%20%3D%2010%0A%20%20%20%20%20%20%20%20self._arr%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20%2A%20self._capacity%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%20%20%20%20%20%20%20%20self._extend_ratio%3A%20int%20%3D%202%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E4%B8%B2%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%88%E7%95%B6%E5%89%8D%E5%85%83%E7%B4%A0%E6%95%B8%E9%87%8F%EF%BC%89%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E4%B8%B2%E5%88%97%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._capacity%0A%0A%20%20%20%20def%20get%28self%2C%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20return%20self._arr%5Bindex%5D%0A%0A%20%20%20%20def%20set%28self%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%0A%20%20%20%20def%20add%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%96%B0%E5%A2%9E%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.size%28%29%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20self._arr%5Bself._size%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%9C%A8%E4%B8%AD%E9%96%93%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%E7%B4%A2%E5%BC%95%20index%20%E4%BB%A5%E5%8F%8A%E4%B9%8B%E5%BE%8C%E7%9A%84%E5%85%83%E7%B4%A0%E9%83%BD%E5%90%91%E5%BE%8C%E7%A7%BB%E5%8B%95%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28self._size%20-%201%2C%20index%20-%201%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%20%2B%201%5D%20%3D%20self._arr%5Bj%5D%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self%2C%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20num%20%3D%20self._arr%5Bindex%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%20i%20%E4%B9%8B%E5%BE%8C%E7%9A%84%E5%85%83%E7%B4%A0%E9%83%BD%E5%90%91%E5%89%8D%E7%A7%BB%E5%8B%95%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28index%2C%20self._size%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%5D%20%3D%20self._arr%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20extend_capacity%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%B8%B2%E5%88%97%E6%93%B4%E5%AE%B9%22%22%22%0A%20%20%20%20%20%20%20%20self._arr%20%3D%20self._arr%20%2B%20%5B0%5D%20%2A%20self.capacity%28%29%20%2A%20%28self._extend_ratio%20-%201%29%0A%20%20%20%20%20%20%20%20self._capacity%20%3D%20len%28self._arr%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%B2%E5%88%97%0A%20%20%20%20nums%20%3D%20MyList%28%29%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%96%B0%E5%A2%9E%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.add%281%29%0A%20%20%20%20nums.add%283%29%0A%20%20%20%20nums.add%282%29%0A%20%20%20%20nums.add%285%29%0A%20%20%20%20nums.add%284%29%0A%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%96%93%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%286%2C%20index%3D3%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.remove%283%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums.get%281%29%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.set%280%2C%201%29%0A%0A%20%20%20%20%23%20%E6%B8%AC%E8%A9%A6%E6%93%B4%E5%AE%B9%E6%A9%9F%E5%88%B6%0A%20%20%20%20for%20i%20in%20range%2810%29%3A%0A%20%20%20%20%20%20%20%20nums.add%28i%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_backtracking/n_queens.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20row%3A%20int%2C%0A%20%20%20%20n%3A%20int%2C%0A%20%20%20%20state%3A%20list%5Blist%5Bstr%5D%5D%2C%0A%20%20%20%20res%3A%20list%5Blist%5Blist%5Bstr%5D%5D%5D%2C%0A%20%20%20%20cols%3A%20list%5Bbool%5D%2C%0A%20%20%20%20diags1%3A%20list%5Bbool%5D%2C%0A%20%20%20%20diags2%3A%20list%5Bbool%5D%2C%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9AN%20%E7%9A%87%E5%90%8E%22%22%22%0A%20%20%20%20%23%20%E7%95%B6%E6%94%BE%E7%BD%AE%E5%AE%8C%E6%89%80%E6%9C%89%E8%A1%8C%E6%99%82%EF%BC%8C%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20if%20row%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res.append%28%5Blist%28row%29%20for%20row%20in%20state%5D%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E5%88%97%0A%20%20%20%20for%20col%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A8%88%E7%AE%97%E8%A9%B2%E6%A0%BC%E5%AD%90%E5%B0%8D%E6%87%89%E7%9A%84%E4%B8%BB%E5%B0%8D%E8%A7%92%E7%B7%9A%E5%92%8C%E6%AC%A1%E5%B0%8D%E8%A7%92%E7%B7%9A%0A%20%20%20%20%20%20%20%20diag1%20%3D%20row%20-%20col%20%2B%20n%20-%201%0A%20%20%20%20%20%20%20%20diag2%20%3D%20row%20%2B%20col%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%A8%B1%E8%A9%B2%E6%A0%BC%E5%AD%90%E6%89%80%E5%9C%A8%E5%88%97%E3%80%81%E4%B8%BB%E5%B0%8D%E8%A7%92%E7%B7%9A%E3%80%81%E6%AC%A1%E5%B0%8D%E8%A7%92%E7%B7%9A%E4%B8%8A%E5%AD%98%E5%9C%A8%E7%9A%87%E5%90%8E%0A%20%20%20%20%20%20%20%20if%20not%20cols%5Bcol%5D%20and%20not%20diags1%5Bdiag1%5D%20and%20not%20diags2%5Bdiag2%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%B0%87%E7%9A%87%E5%90%8E%E6%94%BE%E7%BD%AE%E5%9C%A8%E8%A9%B2%E6%A0%BC%E5%AD%90%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22Q%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%94%BE%E7%BD%AE%E4%B8%8B%E4%B8%80%E8%A1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28row%20%2B%201%2C%20n%2C%20state%2C%20res%2C%20cols%2C%20diags1%2C%20diags2%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E5%B0%87%E8%A9%B2%E6%A0%BC%E5%AD%90%E6%81%A2%E5%BE%A9%E7%82%BA%E7%A9%BA%E4%BD%8D%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22%23%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20False%0A%0A%0Adef%20n_queens%28n%3A%20int%29%20-%3E%20list%5Blist%5Blist%5Bstr%5D%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%20N%20%E7%9A%87%E5%90%8E%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20n%2An%20%E5%A4%A7%E5%B0%8F%E7%9A%84%E6%A3%8B%E7%9B%A4%EF%BC%8C%E5%85%B6%E4%B8%AD%20%27Q%27%20%E4%BB%A3%E8%A1%A8%E7%9A%87%E5%90%8E%EF%BC%8C%27%23%27%20%E4%BB%A3%E8%A1%A8%E7%A9%BA%E4%BD%8D%0A%20%20%20%20state%20%3D%20%5B%5B%22%23%22%20for%20_%20in%20range%28n%29%5D%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20cols%20%3D%20%5BFalse%5D%20%2A%20n%20%20%23%20%E8%A8%98%E9%8C%84%E5%88%97%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20diags1%20%3D%20%5BFalse%5D%20%2A%20%282%20%2A%20n%20-%201%29%20%20%23%20%E8%A8%98%E9%8C%84%E4%B8%BB%E5%B0%8D%E8%A7%92%E7%B7%9A%E4%B8%8A%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20diags2%20%3D%20%5BFalse%5D%20%2A%20%282%20%2A%20n%20-%201%29%20%20%23%20%E8%A8%98%E9%8C%84%E6%AC%A1%E5%B0%8D%E8%A7%92%E7%B7%9A%E4%B8%8A%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%280%2C%20n%2C%20state%2C%20res%2C%20cols%2C%20diags1%2C%20diags2%29%0A%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20res%20%3D%20n_queens%28n%29%0A%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E6%A3%8B%E7%9B%A4%E9%95%B7%E5%AF%AC%E7%82%BA%20%7Bn%7D%22%29%0A%20%20%20%20print%28f%22%E7%9A%87%E5%90%8E%E6%94%BE%E7%BD%AE%E6%96%B9%E6%A1%88%E5%85%B1%E6%9C%89%20%7Blen%28res%29%7D%20%E7%A8%AE%22%29%0A%20%20%20%20for%20state%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%22--------------------%22%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20state%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28row%29&cumulative=false&curInstr=61&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_backtracking/permutations_i.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9A%E5%85%A8%E6%8E%92%E5%88%97%20I%22%22%22%0A%20%20%20%20%23%20%E7%95%B6%E7%8B%80%E6%85%8B%E9%95%B7%E5%BA%A6%E7%AD%89%E6%96%BC%E5%85%83%E7%B4%A0%E6%95%B8%E9%87%8F%E6%99%82%EF%BC%8C%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%A8%B1%E9%87%8D%E8%A4%87%E9%81%B8%E6%93%87%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%80%B2%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BC%AA%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%8A%B7%E9%81%B8%E6%93%87%EF%BC%8C%E6%81%A2%E5%BE%A9%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_i%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E6%8E%92%E5%88%97%20I%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%202%2C%203%5D%0A%0A%20%20%20%20res%20%3D%20permutations_i%28nums%29%0A%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E6%8E%92%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_backtracking/permutations_ii.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9A%E5%85%A8%E6%8E%92%E5%88%97%20II%22%22%22%0A%20%20%20%20%23%20%E7%95%B6%E7%8B%80%E6%85%8B%E9%95%B7%E5%BA%A6%E7%AD%89%E6%96%BC%E5%85%83%E7%B4%A0%E6%95%B8%E9%87%8F%E6%99%82%EF%BC%8C%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20duplicated%20%3D%20set%5Bint%5D%28%29%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%A8%B1%E9%87%8D%E8%A4%87%E9%81%B8%E6%93%87%E5%85%83%E7%B4%A0%20%E4%B8%94%20%E4%B8%8D%E5%85%81%E8%A8%B1%E9%87%8D%E8%A4%87%E9%81%B8%E6%93%87%E7%9B%B8%E7%AD%89%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%20and%20choice%20not%20in%20duplicated%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20duplicated.add%28choice%29%20%20%23%20%E8%A8%98%E9%8C%84%E9%81%B8%E6%93%87%E9%81%8E%E7%9A%84%E5%85%83%E7%B4%A0%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%80%B2%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BC%AA%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%8A%B7%E9%81%B8%E6%93%87%EF%BC%8C%E6%81%A2%E5%BE%A9%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_ii%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E6%8E%92%E5%88%97%20II%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%202%2C%202%5D%0A%0A%20%20%20%20res%20%3D%20permutations_ii%28nums%29%0A%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E6%8E%92%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%B0%8D%E6%87%89%E7%9A%84%E5%85%83%E7%B4%A0%E7%82%BA%20None%20%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%95%B6%E5%89%8D%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%EF%BC%9A%E4%BE%8B%E9%A1%8C%E4%B8%80%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28root%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BC%B8%E5%87%BA%E6%89%80%E6%9C%89%E5%80%BC%E7%82%BA%207%20%E7%9A%84%E7%AF%80%E9%BB%9E%22%29%0A%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20res%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%B0%8D%E6%87%89%E7%9A%84%E5%85%83%E7%B4%A0%E7%82%BA%20None%20%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%95%B6%E5%89%8D%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%EF%BC%9A%E4%BE%8B%E9%A1%8C%E4%BA%8C%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%98%97%E8%A9%A6%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BC%B8%E5%87%BA%E6%89%80%E6%9C%89%E6%A0%B9%E7%AF%80%E9%BB%9E%E5%88%B0%E7%AF%80%E9%BB%9E%207%20%E7%9A%84%E8%B7%AF%E5%BE%91%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%B0%8D%E6%87%89%E7%9A%84%E5%85%83%E7%B4%A0%E7%82%BA%20None%20%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%95%B6%E5%89%8D%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%EF%BC%9A%E4%BE%8B%E9%A1%8C%E4%B8%89%22%22%22%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%0A%20%20%20%20if%20root%20is%20None%20or%20root.val%20%3D%3D%203%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%98%97%E8%A9%A6%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BC%B8%E5%87%BA%E6%89%80%E6%9C%89%E6%A0%B9%E7%AF%80%E9%BB%9E%E5%88%B0%E7%AF%80%E9%BB%9E%207%20%E7%9A%84%E8%B7%AF%E5%BE%91%EF%BC%8C%E8%B7%AF%E5%BE%91%E4%B8%AD%E4%B8%8D%E5%8C%85%E5%90%AB%E5%80%BC%E7%82%BA%203%20%E7%9A%84%E7%AF%80%E9%BB%9E%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%B0%8D%E6%87%89%E7%9A%84%E5%85%83%E7%B4%A0%E7%82%BA%20None%20%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%95%B6%E5%89%8D%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20is_solution%28state%3A%20list%5BTreeNode%5D%29%20-%3E%20bool%3A%0A%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E7%95%B6%E5%89%8D%E7%8B%80%E6%85%8B%E6%98%AF%E5%90%A6%E7%82%BA%E8%A7%A3%22%22%22%0A%20%20%20%20return%20state%20and%20state%5B-1%5D.val%20%3D%3D%207%0A%0Adef%20record_solution%28state%3A%20list%5BTreeNode%5D%2C%20res%3A%20list%5Blist%5BTreeNode%5D%5D%29%3A%0A%20%20%20%20%22%22%22%E8%A8%98%E9%8C%84%E8%A7%A3%22%22%22%0A%20%20%20%20res.append%28list%28state%29%29%0A%0Adef%20is_valid%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%20-%3E%20bool%3A%0A%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E5%9C%A8%E7%95%B6%E5%89%8D%E7%8B%80%E6%85%8B%E4%B8%8B%EF%BC%8C%E8%A9%B2%E9%81%B8%E6%93%87%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95%22%22%22%0A%20%20%20%20return%20choice%20is%20not%20None%20and%20choice.val%20%21%3D%203%0A%0Adef%20make_choice%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E6%9B%B4%E6%96%B0%E7%8B%80%E6%85%8B%22%22%22%0A%20%20%20%20state.append%28choice%29%0A%0Adef%20undo_choice%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E6%81%A2%E5%BE%A9%E7%8B%80%E6%85%8B%22%22%22%0A%20%20%20%20state.pop%28%29%0A%0Adef%20backtrack%28%0A%20%20%20%20state%3A%20list%5BTreeNode%5D%2C%20choices%3A%20list%5BTreeNode%5D%2C%20res%3A%20list%5Blist%5BTreeNode%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9A%E4%BE%8B%E9%A1%8C%E4%B8%89%22%22%22%0A%20%20%20%20%23%20%E6%AA%A2%E6%9F%A5%E6%98%AF%E5%90%A6%E7%82%BA%E8%A7%A3%0A%20%20%20%20if%20is_solution%28state%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20%20%20%20%20record_solution%28state%2C%20res%29%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E6%AA%A2%E6%9F%A5%E9%81%B8%E6%93%87%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95%0A%20%20%20%20%20%20%20%20if%20is_valid%28state%2C%20choice%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20make_choice%28state%2C%20choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%80%B2%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BC%AA%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20%5Bchoice.left%2C%20choice.right%5D%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%8A%B7%E9%81%B8%E6%93%87%EF%BC%8C%E6%81%A2%E5%BE%A9%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20undo_choice%28state%2C%20choice%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3D%5Broot%5D%2C%20res%3Dres%29%0A%20%20%20%20print%28%22%5Cn%E8%BC%B8%E5%87%BA%E6%89%80%E6%9C%89%E8%B7%AF%E5%BE%91%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=138&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20choices%3A%20list%5Bint%5D%2C%20start%3A%20int%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E6%96%BC%20target%20%E6%99%82%EF%BC%8C%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%BA%8C%EF%BC%9A%E5%BE%9E%20start%20%E9%96%8B%E5%A7%8B%E8%B5%B0%E8%A8%AA%EF%BC%8C%E9%81%BF%E5%85%8D%E7%94%9F%E6%88%90%E9%87%8D%E8%A4%87%E5%AD%90%E9%9B%86%0A%20%20%20%20for%20i%20in%20range%28start%2C%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%80%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E9%81%8E%20target%20%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E7%B5%90%E6%9D%9F%E8%BF%B4%E5%9C%88%0A%20%20%20%20%20%20%20%20%23%20%E9%80%99%E6%98%AF%E5%9B%A0%E7%82%BA%E9%99%A3%E5%88%97%E5%B7%B2%E6%8E%92%E5%BA%8F%EF%BC%8C%E5%BE%8C%E9%82%8A%E5%85%83%E7%B4%A0%E6%9B%B4%E5%A4%A7%EF%BC%8C%E5%AD%90%E9%9B%86%E5%92%8C%E4%B8%80%E5%AE%9A%E8%B6%85%E9%81%8E%20target%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%20target%2C%20start%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E9%80%B2%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BC%AA%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%20-%20choices%5Bi%5D%2C%20choices%2C%20i%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%8A%B7%E9%81%B8%E6%93%87%EF%BC%8C%E6%81%A2%E5%BE%A9%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8B%80%E6%85%8B%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E5%B0%8D%20nums%20%E9%80%B2%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20start%20%3D%200%20%20%23%20%E8%B5%B0%E8%A8%AA%E8%B5%B7%E5%A7%8B%E9%BB%9E%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%B5%90%E6%9E%9C%E4%B8%B2%E5%88%97%EF%BC%88%E5%AD%90%E9%9B%86%E4%B8%B2%E5%88%97%EF%BC%89%0A%20%20%20%20backtrack%28state%2C%20target%2C%20nums%2C%20start%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E6%96%BC%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%0A%20%20%20%20target%3A%20int%2C%0A%20%20%20%20total%3A%20int%2C%0A%20%20%20%20choices%3A%20list%5Bint%5D%2C%0A%20%20%20%20res%3A%20list%5Blist%5Bint%5D%5D%2C%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E6%96%BC%20target%20%E6%99%82%EF%BC%8C%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20if%20total%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20for%20i%20in%20range%28len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E9%81%8E%20target%20%EF%BC%8C%E5%89%87%E8%B7%B3%E9%81%8E%E8%A9%B2%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20if%20total%20%2B%20choices%5Bi%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%E5%92%8C%20total%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E9%80%B2%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BC%AA%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%2C%20total%20%2B%20choices%5Bi%5D%2C%20choices%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%8A%B7%E9%81%B8%E6%93%87%EF%BC%8C%E6%81%A2%E5%BE%A9%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i_naive%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20I%EF%BC%88%E5%8C%85%E5%90%AB%E9%87%8D%E8%A4%87%E5%AD%90%E9%9B%86%EF%BC%89%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8B%80%E6%85%8B%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20total%20%3D%200%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%B5%90%E6%9E%9C%E4%B8%B2%E5%88%97%EF%BC%88%E5%AD%90%E9%9B%86%E4%B8%B2%E5%88%97%EF%BC%89%0A%20%20%20%20backtrack%28state%2C%20target%2C%20total%2C%20nums%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i_naive%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E6%96%BC%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E8%AB%8B%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%A9%B2%E6%96%B9%E6%B3%95%E8%BC%B8%E5%87%BA%E7%9A%84%E7%B5%90%E6%9E%9C%E5%8C%85%E5%90%AB%E9%87%8D%E8%A4%87%E9%9B%86%E5%90%88%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_ii.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20choices%3A%20list%5Bint%5D%2C%20start%3A%20int%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20II%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E6%96%BC%20target%20%E6%99%82%EF%BC%8C%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%BA%8C%EF%BC%9A%E5%BE%9E%20start%20%E9%96%8B%E5%A7%8B%E8%B5%B0%E8%A8%AA%EF%BC%8C%E9%81%BF%E5%85%8D%E7%94%9F%E6%88%90%E9%87%8D%E8%A4%87%E5%AD%90%E9%9B%86%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%89%EF%BC%9A%E5%BE%9E%20start%20%E9%96%8B%E5%A7%8B%E8%B5%B0%E8%A8%AA%EF%BC%8C%E9%81%BF%E5%85%8D%E9%87%8D%E8%A4%87%E9%81%B8%E6%93%87%E5%90%8C%E4%B8%80%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20i%20in%20range%28start%2C%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%80%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E9%81%8E%20target%20%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E7%B5%90%E6%9D%9F%E8%BF%B4%E5%9C%88%0A%20%20%20%20%20%20%20%20%23%20%E9%80%99%E6%98%AF%E5%9B%A0%E7%82%BA%E9%99%A3%E5%88%97%E5%B7%B2%E6%8E%92%E5%BA%8F%EF%BC%8C%E5%BE%8C%E9%82%8A%E5%85%83%E7%B4%A0%E6%9B%B4%E5%A4%A7%EF%BC%8C%E5%AD%90%E9%9B%86%E5%92%8C%E4%B8%80%E5%AE%9A%E8%B6%85%E9%81%8E%20target%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E5%9B%9B%EF%BC%9A%E5%A6%82%E6%9E%9C%E8%A9%B2%E5%85%83%E7%B4%A0%E8%88%87%E5%B7%A6%E9%82%8A%E5%85%83%E7%B4%A0%E7%9B%B8%E7%AD%89%EF%BC%8C%E8%AA%AA%E6%98%8E%E8%A9%B2%E6%90%9C%E5%B0%8B%E5%88%86%E6%94%AF%E9%87%8D%E8%A4%87%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%B7%B3%E9%81%8E%0A%20%20%20%20%20%20%20%20if%20i%20%3E%20start%20and%20choices%5Bi%5D%20%3D%3D%20choices%5Bi%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%20target%2C%20start%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E9%80%B2%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BC%AA%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%20-%20choices%5Bi%5D%2C%20choices%2C%20i%20%2B%201%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%8A%B7%E9%81%B8%E6%93%87%EF%BC%8C%E6%81%A2%E5%BE%A9%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_ii%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20II%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8B%80%E6%85%8B%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E5%B0%8D%20nums%20%E9%80%B2%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20start%20%3D%200%20%20%23%20%E8%B5%B0%E8%A8%AA%E8%B5%B7%E5%A7%8B%E9%BB%9E%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%B5%90%E6%9E%9C%E4%B8%B2%E5%88%97%EF%BC%88%E5%AD%90%E9%9B%86%E4%B8%B2%E5%88%97%EF%BC%89%0A%20%20%20%20backtrack%28state%2C%20target%2C%20nums%2C%20start%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_ii%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E6%96%BC%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_computational_complexity/iteration.md ================================================ https://pythontutor.com/render.html#code=def%20for_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22for%20%E8%BF%B4%E5%9C%88%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%B1%82%E5%92%8C%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnfor%20%E8%BF%B4%E5%9C%88%E7%9A%84%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D& https://pythontutor.com/render.html#code=def%20while_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E8%BF%B4%E5%9C%88%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A2%9D%E4%BB%B6%E8%AE%8A%E6%95%B8%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%B1%82%E5%92%8C%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E6%9B%B4%E6%96%B0%E6%A2%9D%E4%BB%B6%E8%AE%8A%E6%95%B8%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E8%BF%B4%E5%9C%88%E7%9A%84%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20while_loop_ii%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E8%BF%B4%E5%9C%88%EF%BC%88%E5%85%A9%E6%AC%A1%E6%9B%B4%E6%96%B0%EF%BC%89%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A2%9D%E4%BB%B6%E8%AE%8A%E6%95%B8%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%B1%82%E5%92%8C%201%2C%204%2C%2010%2C%20...%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E6%A2%9D%E4%BB%B6%E8%AE%8A%E6%95%B8%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20i%20%2A%3D%202%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop_ii%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E8%BF%B4%E5%9C%88%EF%BC%88%E5%85%A9%E6%AC%A1%E6%9B%B4%E6%96%B0%EF%BC%89%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20nested_for_loop%28n%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%22%22%22%E9%9B%99%E5%B1%A4%20for%20%E8%BF%B4%E5%9C%88%22%22%22%0A%20%20%20%20res%20%3D%20%22%22%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%20i%20%3D%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%20j%20%3D%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20f%22%28%7Bi%7D%2C%20%7Bj%7D%29%2C%20%22%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20nested_for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cn%E9%9B%99%E5%B1%A4%20for%20%E8%BF%B4%E5%9C%88%E7%9A%84%E8%B5%B0%E8%A8%AA%E7%B5%90%E6%9E%9C%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E9%81%9E%EF%BC%9A%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E8%BF%B4%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%B5%90%E6%9E%9C%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E9%81%9E%E8%BF%B4%E5%87%BD%E5%BC%8F%E7%9A%84%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20tail_recur%28n%2C%20res%29%3A%0A%20%20%20%20%22%22%22%E5%B0%BE%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E5%B0%BE%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%0A%20%20%20%20return%20tail_recur%28n%20-%201%2C%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n%2C%200%29%0A%20%20%20%20print%28f%22%5Cn%E5%B0%BE%E9%81%9E%E8%BF%B4%E5%87%BD%E5%BC%8F%E7%9A%84%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%B2%BB%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B8%E5%88%97%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%20f%281%29%20%3D%200%2C%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E7%B5%90%E6%9E%9C%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E8%B2%BB%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B8%E5%88%97%E7%9A%84%E7%AC%AC%20%7Bn%7D%20%E9%A0%85%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%93%AC%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E4%B8%80%E5%80%8B%E9%A1%AF%E5%BC%8F%E7%9A%84%E5%A0%86%E7%96%8A%E4%BE%86%E6%A8%A1%E6%93%AC%E7%B3%BB%E7%B5%B1%E5%91%BC%E5%8F%AB%E5%A0%86%E7%96%8A%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E9%81%9E%EF%BC%9A%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%0A%20%20%20%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E2%80%9C%E5%85%A5%E5%A0%86%E7%96%8A%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%93%AC%E2%80%9C%E9%81%9E%E2%80%9D%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E8%BF%B4%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%B5%90%E6%9E%9C%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E2%80%9C%E5%87%BA%E5%A0%86%E7%96%8A%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%93%AC%E2%80%9C%E8%BF%B4%E2%80%9D%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%93%AC%E9%81%9E%E8%BF%B4%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_computational_complexity/recursion.md ================================================ https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E9%81%9E%EF%BC%9A%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E8%BF%B4%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%B5%90%E6%9E%9C%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E9%81%9E%E8%BF%B4%E5%87%BD%E5%BC%8F%E7%9A%84%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20tail_recur%28n%2C%20res%29%3A%0A%20%20%20%20%22%22%22%E5%B0%BE%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E5%B0%BE%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%0A%20%20%20%20return%20tail_recur%28n%20-%201%2C%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n%2C%200%29%0A%20%20%20%20print%28f%22%5Cn%E5%B0%BE%E9%81%9E%E8%BF%B4%E5%87%BD%E5%BC%8F%E7%9A%84%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%B2%BB%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B8%E5%88%97%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%20f%281%29%20%3D%200%2C%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E7%B5%90%E6%9E%9C%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E8%B2%BB%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B8%E5%88%97%E7%9A%84%E7%AC%AC%20%7Bn%7D%20%E9%A0%85%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%93%AC%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E4%B8%80%E5%80%8B%E9%A1%AF%E5%BC%8F%E7%9A%84%E5%A0%86%E7%96%8A%E4%BE%86%E6%A8%A1%E6%93%AC%E7%B3%BB%E7%B5%B1%E5%91%BC%E5%8F%AB%E5%A0%86%E7%96%8A%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E9%81%9E%EF%BC%9A%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%0A%20%20%20%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E2%80%9C%E5%85%A5%E5%A0%86%E7%96%8A%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%93%AC%E2%80%9C%E9%81%9E%E2%80%9D%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E8%BF%B4%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%B5%90%E6%9E%9C%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E2%80%9C%E5%87%BA%E5%A0%86%E7%96%8A%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%93%AC%E2%80%9C%E8%BF%B4%E2%80%9D%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%93%AC%E9%81%9E%E8%BF%B4%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_computational_complexity/space_complexity.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20function%28%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%87%BD%E5%BC%8F%22%22%22%0A%20%20%20%20%23%20%E5%9F%B7%E8%A1%8C%E6%9F%90%E4%BA%9B%E6%93%8D%E4%BD%9C%0A%20%20%20%20return%200%0A%0Adef%20constant%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B8%B8%E6%95%B8%E9%9A%8E%22%22%22%0A%20%20%20%20%23%20%E5%B8%B8%E6%95%B8%E3%80%81%E8%AE%8A%E6%95%B8%E3%80%81%E7%89%A9%E4%BB%B6%E4%BD%94%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%96%93%0A%20%20%20%20a%20%3D%200%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%2010%0A%20%20%20%20node%20%3D%20ListNode%280%29%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E4%B8%AD%E7%9A%84%E8%AE%8A%E6%95%B8%E4%BD%94%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%96%93%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20c%20%3D%200%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E4%B8%AD%E7%9A%84%E5%87%BD%E5%BC%8F%E4%BD%94%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%96%93%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20function%28%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E5%B8%B8%E6%95%B8%E9%9A%8E%0A%20%20%20%20constant%28n%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E6%80%A7%E9%9A%8E%22%22%22%0A%20%20%20%20%23%20%E9%95%B7%E5%BA%A6%E7%82%BA%20n%20%E7%9A%84%E4%B8%B2%E5%88%97%E4%BD%94%E7%94%A8%20O%28n%29%20%E7%A9%BA%E9%96%93%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20%23%20%E9%95%B7%E5%BA%A6%E7%82%BA%20n%20%E7%9A%84%E9%9B%9C%E6%B9%8A%E8%A1%A8%E4%BD%94%E7%94%A8%20O%28n%29%20%E7%A9%BA%E9%96%93%0A%20%20%20%20hmap%20%3D%20dict%5Bint%2C%20str%5D%28%29%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20hmap%5Bi%5D%20%3D%20str%28i%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E7%B7%9A%E6%80%A7%E9%9A%8E%0A%20%20%20%20linear%28n%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear_recur%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E6%80%A7%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20print%28%22%E9%81%9E%E8%BF%B4%20n%20%3D%22%2C%20n%29%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20linear_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E7%B7%9A%E6%80%A7%E9%9A%8E%0A%20%20%20%20linear_recur%28n%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20quadratic%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%9A%8E%22%22%22%0A%20%20%20%20%23%20%E4%BA%8C%E7%B6%AD%E4%B8%B2%E5%88%97%E4%BD%94%E7%94%A8%20O%28n%5E2%29%20%E7%A9%BA%E9%96%93%0A%20%20%20%20num_matrix%20%3D%20%5B%5B0%5D%20%2A%20n%20for%20_%20in%20range%28n%29%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E5%B9%B3%E6%96%B9%E9%9A%8E%0A%20%20%20%20quadratic%28n%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20quadratic_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E9%99%A3%E5%88%97%20nums%20%E9%95%B7%E5%BA%A6%E7%82%BA%20n%2C%20n-1%2C%20...%2C%202%2C%201%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20return%20quadratic_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E5%B9%B3%E6%96%B9%E9%9A%8E%0A%20%20%20%20quadratic_recur%28n%29&cumulative=false&curInstr=28&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20build_tree%28n%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B8%E9%9A%8E%EF%BC%88%E5%BB%BA%E7%AB%8B%E6%BB%BF%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%280%29%0A%20%20%20%20root.left%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20root.right%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20return%20root%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E6%8C%87%E6%95%B8%E9%9A%8E%0A%20%20%20%20root%20%3D%20build_tree%28n%29&cumulative=false&curInstr=507&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_computational_complexity/time_complexity.md ================================================ https://pythontutor.com/render.html#code=def%20constant%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B8%B8%E6%95%B8%E9%9A%8E%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20size%20%3D%2010%0A%20%20%20%20for%20_%20in%20range%28size%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20constant%28n%29%0A%20%20%20%20print%28%22%E5%B8%B8%E6%95%B8%E9%9A%8E%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E6%80%A7%E9%9A%8E%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20linear%28n%29%0A%20%20%20%20print%28%22%E7%B7%9A%E6%80%A7%E9%9A%8E%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20array_traversal%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E6%80%A7%E9%9A%8E%EF%BC%88%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%AC%A1%E6%95%B8%E8%88%87%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%E6%88%90%E6%AD%A3%E6%AF%94%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20array_traversal%28%5B0%5D%20%2A%20n%29%0A%20%20%20%20print%28%22%E7%B7%9A%E6%80%A7%E9%9A%8E%EF%BC%88%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20quadratic%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%9A%8E%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%AC%A1%E6%95%B8%E8%88%87%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%E6%88%90%E5%B9%B3%E6%96%B9%E9%97%9C%E4%BF%82%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20quadratic%28n%29%0A%20%20%20%20print%28%22%E5%B9%B3%E6%96%B9%E9%9A%8E%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%9A%8E%EF%BC%88%E6%B3%A1%E6%B2%AB%E6%8E%92%E5%BA%8F%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%20%20%23%20%E8%A8%88%E6%95%B8%E5%99%A8%0A%20%20%20%20%23%20%E5%A4%96%E8%BF%B4%E5%9C%88%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A7%E8%BF%B4%E5%9C%88%EF%BC%9A%E5%B0%87%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%20%5B0%2C%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%E8%87%B3%E8%A9%B2%E5%8D%80%E9%96%93%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%20nums%5Bj%5D%20%E8%88%87%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%20%3D%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%203%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%E5%8C%85%E5%90%AB%203%20%E5%80%8B%E5%96%AE%E5%85%83%E6%93%8D%E4%BD%9C%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%5D%20%20%23%20%5Bn%2C%20n-1%2C%20...%2C%202%2C%201%5D%0A%20%20%20%20count%20%3D%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E5%B9%B3%E6%96%B9%E9%9A%8E%EF%BC%88%E6%B3%A1%E6%B2%AB%E6%8E%92%E5%BA%8F%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20exponential%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B8%E9%9A%8E%EF%BC%88%E8%BF%B4%E5%9C%88%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20base%20%3D%201%0A%20%20%20%20%23%20%E7%B4%B0%E8%83%9E%E6%AF%8F%E8%BC%AA%E4%B8%80%E5%88%86%E7%82%BA%E4%BA%8C%EF%BC%8C%E5%BD%A2%E6%88%90%E6%95%B8%E5%88%97%201%2C%202%2C%204%2C%208%2C%20...%2C%202%5E%28n-1%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28base%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%20%20%20%20base%20%2A%3D%202%0A%20%20%20%20%23%20count%20%3D%201%20%2B%202%20%2B%204%20%2B%208%20%2B%20..%20%2B%202%5E%28n-1%29%20%3D%202%5En%20-%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20exponential%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B8%E9%9A%8E%EF%BC%88%E8%BF%B4%E5%9C%88%E5%AF%A6%E7%8F%BE%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20exp_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B8%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20return%20exp_recur%28n%20-%201%29%20%2B%20exp_recur%28n%20-%201%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%207%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20exp_recur%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B8%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20logarithmic%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B0%8D%E6%95%B8%E9%9A%8E%EF%BC%88%E8%BF%B4%E5%9C%88%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20while%20n%20%3E%201%3A%0A%20%20%20%20%20%20%20%20n%20%3D%20n%20/%202%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20logarithmic%28n%29%0A%20%20%20%20print%28%22%E5%B0%8D%E6%95%B8%E9%9A%8E%EF%BC%88%E8%BF%B4%E5%9C%88%E5%AF%A6%E7%8F%BE%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B0%8D%E6%95%B8%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20return%20log_recur%28n%20/%202%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20log_recur%28n%29%0A%20%20%20%20print%28%22%E5%B0%8D%E6%95%B8%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20linear_log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E6%80%A7%E5%B0%8D%E6%95%B8%E9%9A%8E%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%20//%202%29%20%2B%20linear_log_recur%28n%20//%202%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%29%0A%20%20%20%20print%28%22%E7%B7%9A%E6%80%A7%E5%B0%8D%E6%95%B8%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20factorial_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E4%B9%98%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E5%BE%9E%201%20%E5%80%8B%E5%88%86%E8%A3%82%E5%87%BA%20n%20%E5%80%8B%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20factorial_recur%28n%20-%201%29%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20factorial_recur%28n%29%0A%20%20%20%20print%28%22%E9%9A%8E%E4%B9%98%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md ================================================ https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_numbers%28n%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E7%94%9F%E6%88%90%E4%B8%80%E5%80%8B%E9%99%A3%E5%88%97%EF%BC%8C%E5%85%83%E7%B4%A0%E7%82%BA%3A%201%2C%202%2C%20...%2C%20n%20%EF%BC%8C%E9%A0%86%E5%BA%8F%E8%A2%AB%E6%89%93%E4%BA%82%22%22%22%0A%20%20%20%20%23%20%E7%94%9F%E6%88%90%E9%99%A3%E5%88%97%20nums%20%3D%3A%201%2C%202%2C%203%2C%20...%2C%20n%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E9%9A%A8%E6%A9%9F%E6%89%93%E4%BA%82%E9%99%A3%E5%88%97%E5%85%83%E7%B4%A0%0A%20%20%20%20random.shuffle%28nums%29%0A%20%20%20%20return%20nums%0A%0Adef%20find_one%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9F%A5%E8%A9%A2%E9%99%A3%E5%88%97%20nums%20%E4%B8%AD%E6%95%B8%E5%AD%97%201%20%E6%89%80%E5%9C%A8%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%95%B6%E5%85%83%E7%B4%A0%201%20%E5%9C%A8%E9%99%A3%E5%88%97%E9%A0%AD%E9%83%A8%E6%99%82%EF%BC%8C%E9%81%94%E5%88%B0%E6%9C%80%E4%BD%B3%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%20O%281%29%0A%20%20%20%20%20%20%20%20%23%20%E7%95%B6%E5%85%83%E7%B4%A0%201%20%E5%9C%A8%E9%99%A3%E5%88%97%E5%B0%BE%E9%83%A8%E6%99%82%EF%BC%8C%E9%81%94%E5%88%B0%E6%9C%80%E5%B7%AE%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%20O%28n%29%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2010%0A%20%20%20%20nums%20%3D%20random_numbers%28n%29%0A%20%20%20%20index%20%3D%20find_one%28nums%29%0A%20%20%20%20print%28%22%5Cn%E9%99%A3%E5%88%97%20%5B%201%2C%202%2C%20...%2C%20n%20%5D%20%E8%A2%AB%E6%89%93%E4%BA%82%E5%BE%8C%20%3D%22%2C%20nums%29%0A%20%20%20%20print%28%22%E6%95%B8%E5%AD%97%201%20%E7%9A%84%E7%B4%A2%E5%BC%95%E7%82%BA%22%2C%20index%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md ================================================ https://pythontutor.com/render.html#code=def%20dfs%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20i%3A%20int%2C%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%EF%BC%9A%E5%95%8F%E9%A1%8C%20f%28i%2C%20j%29%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%8D%80%E9%96%93%E7%82%BA%E7%A9%BA%EF%BC%8C%E4%BB%A3%E8%A1%A8%E7%84%A1%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20i%20%3E%20j%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%0A%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E5%AD%90%E5%95%8F%E9%A1%8C%20f%28m%2B1%2C%20j%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums%2C%20target%2C%20m%20%2B%201%2C%20j%29%0A%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E5%AD%90%E5%95%8F%E9%A1%8C%20f%28i%2C%20m-1%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums%2C%20target%2C%20i%2C%20m%20-%201%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20%20%20%20%20return%20m%0A%0Adef%20binary_search%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E6%B1%82%E8%A7%A3%E5%95%8F%E9%A1%8C%20f%280%2C%20n-1%29%0A%20%20%20%20return%20dfs%28nums%2C%20target%2C%200%2C%20n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%EF%BC%88%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums%2C%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_divide_and_conquer/build_tree.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20dfs%28%0A%20%20%20%20preorder%3A%20list%5Bint%5D%2C%0A%20%20%20%20inorder_map%3A%20dict%5Bint%2C%20int%5D%2C%0A%20%20%20%20i%3A%20int%2C%0A%20%20%20%20l%3A%20int%2C%0A%20%20%20%20r%3A%20int%2C%0A%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%A7%8B%E5%BB%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E5%88%86%E6%B2%BB%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E6%A8%B9%E5%8D%80%E9%96%93%E7%82%BA%E7%A9%BA%E6%99%82%E7%B5%82%E6%AD%A2%0A%20%20%20%20if%20r%20-%20l%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28preorder%5Bi%5D%29%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%20m%20%EF%BC%8C%E5%BE%9E%E8%80%8C%E5%8A%83%E5%88%86%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20m%20%3D%20inorder_map%5Bpreorder%5Bi%5D%5D%0A%20%20%20%20%23%20%E5%AD%90%E5%95%8F%E9%A1%8C%EF%BC%9A%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20dfs%28preorder%2C%20inorder_map%2C%20i%20%2B%201%2C%20l%2C%20m%20-%201%29%0A%20%20%20%20%23%20%E5%AD%90%E5%95%8F%E9%A1%8C%EF%BC%9A%E6%A7%8B%E5%BB%BA%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.right%20%3D%20dfs%28preorder%2C%20inorder_map%2C%20i%20%2B%201%20%2B%20m%20-%20l%2C%20m%20%2B%201%2C%20r%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%A0%B9%E7%AF%80%E9%BB%9E%0A%20%20%20%20return%20root%0A%0A%0Adef%20build_tree%28preorder%3A%20list%5Bint%5D%2C%20inorder%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%A7%8B%E5%BB%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%9C%E6%B9%8A%E8%A1%A8%EF%BC%8C%E5%84%B2%E5%AD%98%20inorder%20%E5%85%83%E7%B4%A0%E5%88%B0%E7%B4%A2%E5%BC%95%E7%9A%84%E5%B0%8D%E6%98%A0%0A%20%20%20%20inorder_map%20%3D%20%7Bval%3A%20i%20for%20i%2C%20val%20in%20enumerate%28inorder%29%7D%0A%20%20%20%20root%20%3D%20dfs%28preorder%2C%20inorder_map%2C%200%2C%200%2C%20len%28inorder%29%20-%201%29%0A%20%20%20%20return%20root%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20preorder%20%3D%20%5B3%2C%209%2C%202%2C%201%2C%207%5D%0A%20%20%20%20inorder%20%3D%20%5B9%2C%203%2C%201%2C%202%2C%207%5D%0A%20%20%20%20print%28f%22%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%20%3D%20%7Bpreorder%7D%22%29%0A%20%20%20%20print%28f%22%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%20%3D%20%7Binorder%7D%22%29%0A%20%20%20%20root%20%3D%20build_tree%28preorder%2C%20inorder%29&cumulative=false&curInstr=21&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_divide_and_conquer/hanota.md ================================================ https://pythontutor.com/render.html#code=def%20move%28src%3A%20list%5Bint%5D%2C%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E7%A7%BB%E5%8B%95%E4%B8%80%E5%80%8B%E5%9C%93%E7%9B%A4%22%22%22%0A%20%20%20%20%23%20%E5%BE%9E%20src%20%E9%A0%82%E9%83%A8%E6%8B%BF%E5%87%BA%E4%B8%80%E5%80%8B%E5%9C%93%E7%9B%A4%0A%20%20%20%20pan%20%3D%20src.pop%28%29%0A%20%20%20%20%23%20%E5%B0%87%E5%9C%93%E7%9B%A4%E6%94%BE%E5%85%A5%20tar%20%E9%A0%82%E9%83%A8%0A%20%20%20%20tar.append%28pan%29%0A%0A%0Adef%20dfs%28i%3A%20int%2C%20src%3A%20list%5Bint%5D%2C%20buf%3A%20list%5Bint%5D%2C%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E6%B2%B3%E5%85%A7%E5%A1%94%E5%95%8F%E9%A1%8C%20f%28i%29%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%20src%20%E5%8F%AA%E5%89%A9%E4%B8%8B%E4%B8%80%E5%80%8B%E5%9C%93%E7%9B%A4%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E5%B0%87%E5%85%B6%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20if%20i%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20move%28src%2C%20tar%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%AD%90%E5%95%8F%E9%A1%8C%20f%28i-1%29%20%EF%BC%9A%E5%B0%87%20src%20%E9%A0%82%E9%83%A8%20i-1%20%E5%80%8B%E5%9C%93%E7%9B%A4%E8%97%89%E5%8A%A9%20tar%20%E7%A7%BB%E5%88%B0%20buf%0A%20%20%20%20dfs%28i%20-%201%2C%20src%2C%20tar%2C%20buf%29%0A%20%20%20%20%23%20%E5%AD%90%E5%95%8F%E9%A1%8C%20f%281%29%20%EF%BC%9A%E5%B0%87%20src%20%E5%89%A9%E9%A4%98%E4%B8%80%E5%80%8B%E5%9C%93%E7%9B%A4%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20move%28src%2C%20tar%29%0A%20%20%20%20%23%20%E5%AD%90%E5%95%8F%E9%A1%8C%20f%28i-1%29%20%EF%BC%9A%E5%B0%87%20buf%20%E9%A0%82%E9%83%A8%20i-1%20%E5%80%8B%E5%9C%93%E7%9B%A4%E8%97%89%E5%8A%A9%20src%20%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20dfs%28i%20-%201%2C%20buf%2C%20src%2C%20tar%29%0A%0A%0Adef%20solve_hanota%28A%3A%20list%5Bint%5D%2C%20B%3A%20list%5Bint%5D%2C%20C%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E6%B2%B3%E5%85%A7%E5%A1%94%E5%95%8F%E9%A1%8C%22%22%22%0A%20%20%20%20n%20%3D%20len%28A%29%0A%20%20%20%20%23%20%E5%B0%87%20A%20%E9%A0%82%E9%83%A8%20n%20%E5%80%8B%E5%9C%93%E7%9B%A4%E8%97%89%E5%8A%A9%20B%20%E7%A7%BB%E5%88%B0%20C%0A%20%20%20%20dfs%28n%2C%20A%2C%20B%2C%20C%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%B8%B2%E5%88%97%E5%B0%BE%E9%83%A8%E6%98%AF%E6%9F%B1%E5%AD%90%E9%A0%82%E9%83%A8%0A%20%20%20%20A%20%3D%20%5B5%2C%204%2C%203%2C%202%2C%201%5D%0A%20%20%20%20B%20%3D%20%5B%5D%0A%20%20%20%20C%20%3D%20%5B%5D%0A%20%20%20%20print%28%22%E5%88%9D%E5%A7%8B%E7%8B%80%E6%85%8B%E4%B8%8B%EF%BC%9A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29%0A%0A%20%20%20%20solve_hanota%28A%2C%20B%2C%20C%29%0A%0A%20%20%20%20print%28%22%E5%9C%93%E7%9B%A4%E7%A7%BB%E5%8B%95%E5%AE%8C%E6%88%90%E5%BE%8C%EF%BC%9A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md ================================================ https://pythontutor.com/render.html#code=def%20backtrack%28choices%3A%20list%5Bint%5D%2C%20state%3A%20int%2C%20n%3A%20int%2C%20res%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%22%22%22%0A%20%20%20%20%23%20%E7%95%B6%E7%88%AC%E5%88%B0%E7%AC%AC%20n%20%E9%9A%8E%E6%99%82%EF%BC%8C%E6%96%B9%E6%A1%88%E6%95%B8%E9%87%8F%E5%8A%A0%201%0A%20%20%20%20if%20state%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%5B0%5D%20%2B%3D%201%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%A8%B1%E8%B6%8A%E9%81%8E%E7%AC%AC%20n%20%E9%9A%8E%0A%20%20%20%20%20%20%20%20if%20state%20%2B%20choice%20%3E%20n%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20backtrack%28choices%2C%20state%20%2B%20choice%2C%20n%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%0A%0Adef%20climbing_stairs_backtrack%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%EF%BC%9A%E5%9B%9E%E6%BA%AF%22%22%22%0A%20%20%20%20choices%20%3D%20%5B1%2C%202%5D%20%20%23%20%E5%8F%AF%E9%81%B8%E6%93%87%E5%90%91%E4%B8%8A%E7%88%AC%201%20%E9%9A%8E%E6%88%96%202%20%E9%9A%8E%0A%20%20%20%20state%20%3D%200%20%20%23%20%E5%BE%9E%E7%AC%AC%200%20%E9%9A%8E%E9%96%8B%E5%A7%8B%E7%88%AC%0A%20%20%20%20res%20%3D%20%5B0%5D%20%20%23%20%E4%BD%BF%E7%94%A8%20res%5B0%5D%20%E8%A8%98%E9%8C%84%E6%96%B9%E6%A1%88%E6%95%B8%E9%87%8F%0A%20%20%20%20backtrack%28choices%2C%20state%2C%20n%2C%20res%29%0A%20%20%20%20return%20res%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_backtrack%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%9A%8E%E6%A8%93%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A8%AE%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md ================================================ https://pythontutor.com/render.html#code=def%20climbing_stairs_constraint_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B8%B6%E7%B4%84%E6%9D%9F%E7%88%AC%E6%A8%93%E6%A2%AF%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E6%96%BC%E5%84%B2%E5%AD%98%E5%AD%90%E5%95%8F%E9%A1%8C%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%203%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8B%80%E6%85%8B%EF%BC%9A%E9%A0%90%E8%A8%AD%E6%9C%80%E5%B0%8F%E5%AD%90%E5%95%8F%E9%A1%8C%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D%5B1%5D%2C%20dp%5B1%5D%5B2%5D%20%3D%201%2C%200%0A%20%20%20%20dp%5B2%5D%5B1%5D%2C%20dp%5B2%5D%5B2%5D%20%3D%200%2C%201%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%BE%9E%E8%BC%83%E5%B0%8F%E5%AD%90%E5%95%8F%E9%A1%8C%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BC%83%E5%A4%A7%E5%AD%90%E5%95%8F%E9%A1%8C%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B1%5D%20%3D%20dp%5Bi%20-%201%5D%5B2%5D%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B2%5D%20%3D%20dp%5Bi%20-%202%5D%5B1%5D%20%2B%20dp%5Bi%20-%202%5D%5B2%5D%0A%20%20%20%20return%20dp%5Bn%5D%5B1%5D%20%2B%20dp%5Bn%5D%5B2%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_constraint_dp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%9A%8E%E6%A8%93%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A8%AE%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md ================================================ https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20%E5%B7%B2%E7%9F%A5%20dp%5B1%5D%20%E5%92%8C%20dp%5B2%5D%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%29%20%2B%20dfs%28i%20-%202%29%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%EF%BC%9A%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20return%20dfs%28n%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%9A%8E%E6%A8%93%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A8%AE%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md ================================================ https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%2C%20mem%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%A8%98%E6%86%B6%E5%8C%96%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20%E5%B7%B2%E7%9F%A5%20dp%5B1%5D%20%E5%92%8C%20dp%5B2%5D%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20%E8%8B%A5%E5%AD%98%E5%9C%A8%E8%A8%98%E9%8C%84%20dp%5Bi%5D%20%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20mem%5Bi%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%2C%20mem%29%20%2B%20dfs%28i%20-%202%2C%20mem%29%0A%20%20%20%20%23%20%E8%A8%98%E9%8C%84%20dp%5Bi%5D%0A%20%20%20%20mem%5Bi%5D%20%3D%20count%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs_mem%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%EF%BC%9A%E8%A8%98%E6%86%B6%E5%8C%96%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20mem%5Bi%5D%20%E8%A8%98%E9%8C%84%E7%88%AC%E5%88%B0%E7%AC%AC%20i%20%E9%9A%8E%E7%9A%84%E6%96%B9%E6%A1%88%E7%B8%BD%E6%95%B8%EF%BC%8C-1%20%E4%BB%A3%E8%A1%A8%E7%84%A1%E8%A8%98%E9%8C%84%0A%20%20%20%20mem%20%3D%20%5B-1%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20return%20dfs%28n%2C%20mem%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs_mem%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%9A%8E%E6%A8%93%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A8%AE%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md ================================================ https://pythontutor.com/render.html#code=def%20climbing_stairs_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E6%96%BC%E5%84%B2%E5%AD%98%E5%AD%90%E5%95%8F%E9%A1%8C%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8B%80%E6%85%8B%EF%BC%9A%E9%A0%90%E8%A8%AD%E6%9C%80%E5%B0%8F%E5%AD%90%E5%95%8F%E9%A1%8C%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D%2C%20dp%5B2%5D%20%3D%201%2C%202%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%BE%9E%E8%BC%83%E5%B0%8F%E5%AD%90%E5%95%8F%E9%A1%8C%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BC%83%E5%A4%A7%E5%AD%90%E5%95%8F%E9%A1%8C%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20dp%5Bi%20-%201%5D%20%2B%20dp%5Bi%20-%202%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%9A%8E%E6%A8%93%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A8%AE%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20climbing_stairs_dp_comp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20a%2C%20b%20%3D%201%2C%202%0A%20%20%20%20for%20_%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a%2C%20b%20%3D%20b%2C%20a%20%2B%20b%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp_comp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%9A%8E%E6%A8%93%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A8%AE%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change.md ================================================ https://pythontutor.com/render.html#code=def%20coin_change_dp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%8C%A2%E5%85%8C%E6%8F%9B%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%E9%A6%96%E5%88%97%0A%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Ba%5D%20%3D%20MAX%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E7%9A%84%E8%BC%83%E5%B0%8F%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20min%28dp%5Bi%20-%201%5D%5Ba%5D%2C%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%20if%20dp%5Bn%5D%5Bamt%5D%20%21%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20coin_change_dp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E6%B9%8A%E5%88%B0%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B9%A3%E6%95%B8%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20coin_change_dp_comp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%8C%A2%E5%85%8C%E6%8F%9B%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5BMAX%5D%20%2A%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%200%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E7%9A%84%E8%BC%83%E5%B0%8F%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20min%28dp%5Ba%5D%2C%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bamt%5D%20if%20dp%5Bamt%5D%20%21%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20coin_change_dp_comp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E6%B9%8A%E5%88%B0%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B9%A3%E6%95%B8%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md ================================================ https://pythontutor.com/render.html#code=def%20coin_change_ii_dp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%8C%A2%E5%85%8C%E6%8F%9B%20II%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%28n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E4%B9%8B%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%20%2B%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20coin_change_ii_dp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E6%B9%8A%E5%87%BA%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E7%9A%84%E7%A1%AC%E5%B9%A3%E7%B5%84%E5%90%88%E6%95%B8%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20coin_change_ii_dp_comp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%8C%A2%E5%85%8C%E6%8F%9B%20II%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E4%B9%8B%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%20%2B%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20coin_change_ii_dp_comp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E6%B9%8A%E5%87%BA%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E7%9A%84%E7%A1%AC%E5%B9%A3%E7%B5%84%E5%90%88%E6%95%B8%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_dynamic_programming/edit_distance.md ================================================ https://pythontutor.com/render.html#code=def%20edit_distance_dp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%A8%E8%BC%AF%E8%B7%9D%E9%9B%A2%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28m%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20i%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%85%A9%E5%AD%97%E5%85%83%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E8%B7%B3%E9%81%8E%E6%AD%A4%E5%85%A9%E5%AD%97%E5%85%83%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%B7%A8%E8%BC%AF%E6%AD%A5%E6%95%B8%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%AA%E9%99%A4%E3%80%81%E6%9B%BF%E6%8F%9B%E9%80%99%E4%B8%89%E7%A8%AE%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%B7%A8%E8%BC%AF%E6%AD%A5%E6%95%B8%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%2C%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%29%20%2B%201%0A%20%20%20%20return%20dp%5Bn%5D%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20edit_distance_dp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%E5%B0%87%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E7%82%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%B7%A8%E8%BC%AF%20%7Bres%7D%20%E6%AD%A5%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20edit_distance_dp_comp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%A8%E8%BC%AF%E8%B7%9D%E9%9B%A2%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20%20%20%20%20leftup%20%3D%20dp%5B0%5D%20%20%23%20%E6%9A%AB%E5%AD%98%20dp%5Bi-1%2C%20j-1%5D%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20dp%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%85%A9%E5%AD%97%E5%85%83%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E8%B7%B3%E9%81%8E%E6%AD%A4%E5%85%A9%E5%AD%97%E5%85%83%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20leftup%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%B7%A8%E8%BC%AF%E6%AD%A5%E6%95%B8%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%AA%E9%99%A4%E3%80%81%E6%9B%BF%E6%8F%9B%E9%80%99%E4%B8%89%E7%A8%AE%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%B7%A8%E8%BC%AF%E6%AD%A5%E6%95%B8%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D%2C%20dp%5Bj%5D%2C%20leftup%29%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20leftup%20%3D%20temp%20%20%23%20%E6%9B%B4%E6%96%B0%E7%82%BA%E4%B8%8B%E4%B8%80%E8%BC%AA%E7%9A%84%20dp%5Bi-1%2C%20j-1%5D%0A%20%20%20%20return%20dp%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20edit_distance_dp_comp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%E5%B0%87%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E7%82%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%B7%A8%E8%BC%AF%20%7Bres%7D%20%E6%AD%A5%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_dynamic_programming/knapsack.md ================================================ https://pythontutor.com/render.html#code=def%20knapsack_dfs%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20i%3A%20int%2C%20c%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E6%9A%B4%E5%8A%9B%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E9%81%B8%E5%AE%8C%E6%89%80%E6%9C%89%E7%89%A9%E5%93%81%E6%88%96%E8%83%8C%E5%8C%85%E7%84%A1%E5%89%A9%E9%A4%98%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%E5%83%B9%E5%80%BC%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E5%8F%AA%E8%83%BD%E9%81%B8%E6%93%87%E4%B8%8D%E6%94%BE%E5%85%A5%E8%83%8C%E5%8C%85%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%8D%E6%94%BE%E5%85%A5%E5%92%8C%E6%94%BE%E5%85%A5%E7%89%A9%E5%93%81%20i%20%E7%9A%84%E6%9C%80%E5%A4%A7%E5%83%B9%E5%80%BC%0A%20%20%20%20no%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E4%B8%AD%E5%83%B9%E5%80%BC%E6%9B%B4%E5%A4%A7%E7%9A%84%E9%82%A3%E4%B8%80%E5%80%8B%0A%20%20%20%20return%20max%28no%2C%20yes%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E6%9A%B4%E5%8A%9B%E6%90%9C%E5%B0%8B%0A%20%20%20%20res%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20n%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20knapsack_dfs_mem%28%0A%20%20%20%20wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20mem%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20c%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E8%A8%98%E6%86%B6%E5%8C%96%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E9%81%B8%E5%AE%8C%E6%89%80%E6%9C%89%E7%89%A9%E5%93%81%E6%88%96%E8%83%8C%E5%8C%85%E7%84%A1%E5%89%A9%E9%A4%98%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%E5%83%B9%E5%80%BC%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E6%9C%89%E8%A8%98%E9%8C%84%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20if%20mem%5Bi%5D%5Bc%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E5%8F%AA%E8%83%BD%E9%81%B8%E6%93%87%E4%B8%8D%E6%94%BE%E5%85%A5%E8%83%8C%E5%8C%85%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%8D%E6%94%BE%E5%85%A5%E5%92%8C%E6%94%BE%E5%85%A5%E7%89%A9%E5%93%81%20i%20%E7%9A%84%E6%9C%80%E5%A4%A7%E5%83%B9%E5%80%BC%0A%20%20%20%20no%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E8%A8%98%E9%8C%84%E4%B8%A6%E8%BF%94%E5%9B%9E%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E4%B8%AD%E5%83%B9%E5%80%BC%E6%9B%B4%E5%A4%A7%E7%9A%84%E9%82%A3%E4%B8%80%E5%80%8B%0A%20%20%20%20mem%5Bi%5D%5Bc%5D%20%3D%20max%28no%2C%20yes%29%0A%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E8%A8%98%E6%86%B6%E5%8C%96%E6%90%9C%E5%B0%8B%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20res%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20n%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20knapsack_dp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%89%A9%E5%93%81%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E7%9A%84%E8%BC%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D%2C%20dp%5Bi%20-%201%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20knapsack_dp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20knapsack_dp_comp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%80%92%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%28cap%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%89%A9%E5%93%81%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E7%9A%84%E8%BC%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D%2C%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20knapsack_dp_comp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md ================================================ https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%E6%9C%80%E5%B0%8F%E4%BB%A3%E5%83%B9%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E6%96%BC%E5%84%B2%E5%AD%98%E5%AD%90%E5%95%8F%E9%A1%8C%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8B%80%E6%85%8B%EF%BC%9A%E9%A0%90%E8%A8%AD%E6%9C%80%E5%B0%8F%E5%AD%90%E5%95%8F%E9%A1%8C%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D%2C%20dp%5B2%5D%20%3D%20cost%5B1%5D%2C%20cost%5B2%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%BE%9E%E8%BC%83%E5%B0%8F%E5%AD%90%E5%95%8F%E9%A1%8C%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BC%83%E5%A4%A7%E5%AD%90%E5%95%8F%E9%A1%8C%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20min%28dp%5Bi%20-%201%5D%2C%20dp%5Bi%20-%202%5D%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0%2C%201%2C%2010%2C%201%2C%201%2C%201%2C%2010%2C%201%2C%201%2C%2010%2C%201%5D%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E6%A8%93%E6%A2%AF%E7%9A%84%E4%BB%A3%E5%83%B9%E4%B8%B2%E5%88%97%E7%82%BA%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp%28cost%29%0A%20%20%20%20print%28f%22%E7%88%AC%E5%AE%8C%E6%A8%93%E6%A2%AF%E7%9A%84%E6%9C%80%E4%BD%8E%E4%BB%A3%E5%83%B9%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp_comp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%E6%9C%80%E5%B0%8F%E4%BB%A3%E5%83%B9%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20a%2C%20b%20%3D%20cost%5B1%5D%2C%20cost%5B2%5D%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a%2C%20b%20%3D%20b%2C%20min%28a%2C%20b%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0%2C%201%2C%2010%2C%201%2C%201%2C%201%2C%2010%2C%201%2C%201%2C%2010%2C%201%5D%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E6%A8%93%E6%A2%AF%E7%9A%84%E4%BB%A3%E5%83%B9%E4%B8%B2%E5%88%97%E7%82%BA%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp_comp%28cost%29%0A%20%20%20%20print%28f%22%E7%88%AC%E5%AE%8C%E6%A8%93%E6%A2%AF%E7%9A%84%E6%9C%80%E4%BD%8E%E4%BB%A3%E5%83%B9%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md ================================================ https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs%28grid%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%EF%BC%9A%E6%9A%B4%E5%8A%9B%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E7%82%BA%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%96%AE%E5%85%83%E6%A0%BC%EF%BC%8C%E5%89%87%E7%B5%82%E6%AD%A2%E6%90%9C%E5%B0%8B%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%A1%8C%E5%88%97%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20%2B%E2%88%9E%20%E4%BB%A3%E5%83%B9%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E8%A8%88%E7%AE%97%E5%BE%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i-1%2C%20j%29%20%E5%92%8C%20%28i%2C%20j-1%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E4%BB%A3%E5%83%B9%0A%20%20%20%20up%20%3D%20min_path_sum_dfs%28grid%2C%20i%20-%201%2C%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs%28grid%2C%20i%2C%20j%20-%201%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E5%BE%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i%2C%20j%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E4%BB%A3%E5%83%B9%0A%20%20%20%20return%20min%28left%2C%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E6%9A%B4%E5%8A%9B%E6%90%9C%E5%B0%8B%0A%20%20%20%20res%20%3D%20min_path_sum_dfs%28grid%2C%20n%20-%201%2C%20m%20-%201%29%0A%20%20%20%20print%28f%22%E5%BE%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs_mem%28%0A%20%20%20%20grid%3A%20list%5Blist%5Bint%5D%5D%2C%20mem%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20j%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%EF%BC%9A%E8%A8%98%E6%86%B6%E5%8C%96%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E7%82%BA%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%96%AE%E5%85%83%E6%A0%BC%EF%BC%8C%E5%89%87%E7%B5%82%E6%AD%A2%E6%90%9C%E5%B0%8B%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%A1%8C%E5%88%97%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20%2B%E2%88%9E%20%E4%BB%A3%E5%83%B9%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E6%9C%89%E8%A8%98%E9%8C%84%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20if%20mem%5Bi%5D%5Bj%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%20%20%20%20%23%20%E5%B7%A6%E9%82%8A%E5%92%8C%E4%B8%8A%E9%82%8A%E5%96%AE%E5%85%83%E6%A0%BC%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E4%BB%A3%E5%83%B9%0A%20%20%20%20up%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20i%20-%201%2C%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20i%2C%20j%20-%201%29%0A%20%20%20%20%23%20%E8%A8%98%E9%8C%84%E4%B8%A6%E8%BF%94%E5%9B%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i%2C%20j%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E4%BB%A3%E5%83%B9%0A%20%20%20%20mem%5Bi%5D%5Bj%5D%20%3D%20min%28left%2C%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%23%20%E8%A8%98%E6%86%B6%E5%8C%96%E6%90%9C%E5%B0%8B%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20%2A%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20res%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20n%20-%201%2C%20m%20-%201%29%0A%20%20%20%20print%28f%22%E5%BE%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20dp%5B0%5D%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20dp%5B0%5D%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20dp%5Bi%20-%201%5D%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bn%20-%201%5D%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20min_path_sum_dp%28grid%29%0A%20%20%20%20print%28f%22%E5%BE%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp_comp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20m%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20dp%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20dp%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%3D%20dp%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D%2C%20dp%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20min_path_sum_dp_comp%28grid%29%0A%20%20%20%20print%28f%22%E5%BE%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md ================================================ https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%89%A9%E5%93%81%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E7%9A%84%E8%BC%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D%2C%20dp%5Bi%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20val%20%3D%20%5B5%2C%2011%2C%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp_comp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%89%A9%E5%93%81%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E7%9A%84%E8%BC%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D%2C%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20val%20%3D%20%5B5%2C%2011%2C%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp_comp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_list.md ================================================ https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A0%82%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BC%B8%E5%85%A5%E5%80%BC%E4%B8%B2%E5%88%97%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A0%82%E9%BB%9E%E4%B8%B2%E5%88%97%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%84%B0%E6%8E%A5%E8%A1%A8%E5%AF%A6%E7%8F%BE%E7%9A%84%E7%84%A1%E5%90%91%E5%9C%96%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E6%89%80%E6%9C%89%E9%A0%82%E9%BB%9E%E5%92%8C%E9%82%8A%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E9%A0%82%E9%BB%9E%E6%95%B8%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.adj_list%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%82%8A%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%82%8A%20vet1%20-%20vet2%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20remove_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E9%82%8A%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E9%82%8A%20vet1%20-%20vet2%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.remove%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.remove%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%84%B0%E6%8E%A5%E8%A1%A8%E4%B8%AD%E6%96%B0%E5%A2%9E%E4%B8%80%E5%80%8B%E6%96%B0%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20remove_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E9%A0%82%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20not%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%84%B0%E6%8E%A5%E8%A1%A8%E4%B8%AD%E5%88%AA%E9%99%A4%E9%A0%82%E9%BB%9E%20vet%20%E5%B0%8D%E6%87%89%E7%9A%84%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%0A%20%20%20%20%20%20%20%20self.adj_list.pop%28vet%29%0A%20%20%20%20%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E5%85%B6%E4%BB%96%E9%A0%82%E9%BB%9E%E7%9A%84%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%EF%BC%8C%E5%88%AA%E9%99%A4%E6%89%80%E6%9C%89%E5%8C%85%E5%90%AB%20vet%20%E7%9A%84%E9%82%8A%0A%20%20%20%20%20%20%20%20for%20vertex%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%5Bvertex%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.adj_list%5Bvertex%5D.remove%28vet%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%84%A1%E5%90%91%E5%9C%96%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B1%2C%203%2C%202%2C%205%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B2%5D%2C%20v%5B3%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B2%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%82%8A%0A%20%20%20%20graph.add_edge%28v%5B0%5D%2C%20v%5B2%5D%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E9%82%8A%0A%20%20%20%20graph.remove_edge%28v%5B0%5D%2C%20v%5B1%5D%29%0A%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%0A%20%20%20%20v5%20%3D%20Vertex%286%29%0A%20%20%20%20graph.add_vertex%28v5%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E9%A0%82%E9%BB%9E%0A%20%20%20%20graph.remove_vertex%28v%5B1%5D%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md ================================================ https://pythontutor.com/render.html#code=class%20GraphAdjMat%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%84%B0%E6%8E%A5%E7%9F%A9%E9%99%A3%E5%AF%A6%E7%8F%BE%E7%9A%84%E7%84%A1%E5%90%91%E5%9C%96%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20vertices%3A%20list%5Bint%5D%2C%20edges%3A%20list%5Blist%5Bint%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self.vertices%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.adj_mat%3A%20list%5Blist%5Bint%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20for%20val%20in%20vertices%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%82%8A%0A%20%20%20%20%20%20%20%20for%20e%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28e%5B0%5D%2C%20e%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E9%A0%82%E9%BB%9E%E6%95%B8%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.vertices%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20n%20%3D%20self.size%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%90%91%E9%A0%82%E9%BB%9E%E4%B8%B2%E5%88%97%E4%B8%AD%E6%96%B0%E5%A2%9E%E6%96%B0%E9%A0%82%E9%BB%9E%E7%9A%84%E5%80%BC%0A%20%20%20%20%20%20%20%20self.vertices.append%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%84%B0%E6%8E%A5%E7%9F%A9%E9%99%A3%E4%B8%AD%E6%96%B0%E5%A2%9E%E4%B8%80%E8%A1%8C%0A%20%20%20%20%20%20%20%20new_row%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20%20%20%20%20self.adj_mat.append%28new_row%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%84%B0%E6%8E%A5%E7%9F%A9%E9%99%A3%E4%B8%AD%E6%96%B0%E5%A2%9E%E4%B8%80%E5%88%97%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.append%280%29%0A%0A%20%20%20%20def%20remove_vertex%28self%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E9%A0%82%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%A0%82%E9%BB%9E%E4%B8%B2%E5%88%97%E4%B8%AD%E7%A7%BB%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20self.vertices.pop%28index%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%84%B0%E6%8E%A5%E7%9F%A9%E9%99%A3%E4%B8%AD%E5%88%AA%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E8%A1%8C%0A%20%20%20%20%20%20%20%20self.adj_mat.pop%28index%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%84%B0%E6%8E%A5%E7%9F%A9%E9%99%A3%E4%B8%AD%E5%88%AA%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E5%88%97%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.pop%28index%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%82%8A%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%E8%88%87%E7%9B%B8%E7%AD%89%E8%99%95%E7%90%86%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20j%20%3E%3D%20self.size%28%29%20or%20i%20%3D%3D%20j%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%201%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%201%0A%0A%20%20%20%20def%20remove_edge%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E9%82%8A%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%E8%88%87%E7%9B%B8%E7%AD%89%E8%99%95%E7%90%86%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20j%20%3E%3D%20self.size%28%29%20or%20i%20%3D%3D%20j%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%200%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%200%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%84%A1%E5%90%91%E5%9C%96%0A%20%20%20%20vertices%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20edges%20%3D%20%5B%5B0%2C%201%5D%2C%20%5B0%2C%203%5D%2C%20%5B1%2C%202%5D%2C%20%5B2%2C%203%5D%2C%20%5B2%2C%204%5D%2C%20%5B3%2C%204%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjMat%28vertices%2C%20edges%29%0A%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%82%8A%0A%20%20%20%20%23%20%E9%A0%82%E9%BB%9E%201%2C%202%20%E7%9A%84%E7%B4%A2%E5%BC%95%E5%88%86%E5%88%A5%E7%82%BA%200%2C%202%0A%20%20%20%20graph.add_edge%280%2C%202%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E9%82%8A%0A%20%20%20%20%23%20%E9%A0%82%E9%BB%9E%201%2C%203%20%E7%9A%84%E7%B4%A2%E5%BC%95%E5%88%86%E5%88%A5%E7%82%BA%200%2C%201%0A%20%20%20%20graph.remove_edge%280%2C%201%29%0A%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%0A%20%20%20%20graph.add_vertex%286%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E9%A0%82%E9%BB%9E%0A%20%20%20%20%23%20%E9%A0%82%E9%BB%9E%203%20%E7%9A%84%E7%B4%A2%E5%BC%95%E7%82%BA%201%0A%20%20%20%20graph.remove_vertex%281%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_graph/graph_bfs.md ================================================ https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A0%82%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BC%B8%E5%85%A5%E5%80%BC%E4%B8%B2%E5%88%97%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A0%82%E9%BB%9E%E4%B8%B2%E5%88%97%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%84%B0%E6%8E%A5%E8%A1%A8%E5%AF%A6%E7%8F%BE%E7%9A%84%E7%84%A1%E5%90%91%E5%9C%96%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%82%8A%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20graph_bfs%28graph%3A%20GraphAdjList%2C%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E5%BB%A3%E5%BA%A6%E5%84%AA%E5%85%88%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%23%20%E9%A0%82%E9%BB%9E%E8%B5%B0%E8%A8%AA%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E9%9B%9C%E6%B9%8A%E8%A1%A8%EF%BC%8C%E7%94%A8%E6%96%BC%E8%A8%98%E9%8C%84%E5%B7%B2%E8%A2%AB%E8%A8%AA%E5%95%8F%E9%81%8E%E7%9A%84%E9%A0%82%E9%BB%9E%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20%E4%BD%87%E5%88%97%E7%94%A8%E6%96%BC%E5%AF%A6%E7%8F%BE%20BFS%0A%20%20%20%20que%20%3D%20deque%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20%E4%BB%A5%E9%A0%82%E9%BB%9E%20vet%20%E7%82%BA%E8%B5%B7%E9%BB%9E%EF%BC%8C%E8%BF%B4%E5%9C%88%E7%9B%B4%E8%87%B3%E8%A8%AA%E5%95%8F%E5%AE%8C%E6%89%80%E6%9C%89%E9%A0%82%E9%BB%9E%0A%20%20%20%20while%20len%28que%29%20%3E%200%3A%0A%20%20%20%20%20%20%20%20vet%20%3D%20que.popleft%28%29%20%20%23%20%E4%BD%87%E5%88%97%E9%A6%96%E9%A0%82%E9%BB%9E%E5%87%BA%E9%9A%8A%0A%20%20%20%20%20%20%20%20res.append%28vet%29%20%20%23%20%E8%A8%98%E9%8C%84%E8%A8%AA%E5%95%8F%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E8%A9%B2%E9%A0%82%E9%BB%9E%E7%9A%84%E6%89%80%E6%9C%89%E9%84%B0%E6%8E%A5%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20for%20adj_vet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20adj_vet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%B7%B3%E9%81%8E%E5%B7%B2%E8%A2%AB%E8%A8%AA%E5%95%8F%E7%9A%84%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20que.append%28adj_vet%29%20%20%23%20%E5%8F%AA%E5%85%A5%E5%88%97%E6%9C%AA%E8%A8%AA%E5%95%8F%E7%9A%84%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20visited.add%28adj_vet%29%20%20%23%20%E6%A8%99%E8%A8%98%E8%A9%B2%E9%A0%82%E9%BB%9E%E5%B7%B2%E8%A2%AB%E8%A8%AA%E5%95%8F%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E9%A0%82%E9%BB%9E%E8%B5%B0%E8%A8%AA%E5%BA%8F%E5%88%97%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%84%A1%E5%90%91%E5%9C%96%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0%2C%201%2C%202%2C%203%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%0A%20%20%20%20%23%20%E5%BB%A3%E5%BA%A6%E5%84%AA%E5%85%88%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res%20%3D%20graph_bfs%28graph%2C%20v%5B0%5D%29&cumulative=false&curInstr=131&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_graph/graph_dfs.md ================================================ https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A0%82%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BC%B8%E5%85%A5%E5%80%BC%E4%B8%B2%E5%88%97%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A0%82%E9%BB%9E%E4%B8%B2%E5%88%97%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%84%B0%E6%8E%A5%E8%A1%A8%E5%AF%A6%E7%8F%BE%E7%9A%84%E7%84%A1%E5%90%91%E5%9C%96%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%82%8A%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20dfs%28graph%3A%20GraphAdjList%2C%20visited%3A%20set%5BVertex%5D%2C%20res%3A%20list%5BVertex%5D%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E5%84%AA%E5%85%88%E8%B5%B0%E8%A8%AA%E8%BC%94%E5%8A%A9%E5%87%BD%E5%BC%8F%22%22%22%0A%20%20%20%20res.append%28vet%29%20%20%23%20%E8%A8%98%E9%8C%84%E8%A8%AA%E5%95%8F%E9%A0%82%E9%BB%9E%0A%20%20%20%20visited.add%28vet%29%20%20%23%20%E6%A8%99%E8%A8%98%E8%A9%B2%E9%A0%82%E9%BB%9E%E5%B7%B2%E8%A2%AB%E8%A8%AA%E5%95%8F%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E8%A9%B2%E9%A0%82%E9%BB%9E%E7%9A%84%E6%89%80%E6%9C%89%E9%84%B0%E6%8E%A5%E9%A0%82%E9%BB%9E%0A%20%20%20%20for%20adjVet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20if%20adjVet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%B7%B3%E9%81%8E%E5%B7%B2%E8%A2%AB%E8%A8%AA%E5%95%8F%E7%9A%84%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E8%A8%AA%E5%95%8F%E9%84%B0%E6%8E%A5%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20dfs%28graph%2C%20visited%2C%20res%2C%20adjVet%29%0A%0A%0Adef%20graph_dfs%28graph%3A%20GraphAdjList%2C%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E5%84%AA%E5%85%88%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E9%84%B0%E6%8E%A5%E8%A1%A8%E4%BE%86%E8%A1%A8%E7%A4%BA%E5%9C%96%EF%BC%8C%E4%BB%A5%E4%BE%BF%E7%8D%B2%E5%8F%96%E6%8C%87%E5%AE%9A%E9%A0%82%E9%BB%9E%E7%9A%84%E6%89%80%E6%9C%89%E9%84%B0%E6%8E%A5%E9%A0%82%E9%BB%9E%0A%20%20%20%20%23%20%E9%A0%82%E9%BB%9E%E8%B5%B0%E8%A8%AA%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E9%9B%9C%E6%B9%8A%E8%A1%A8%EF%BC%8C%E7%94%A8%E6%96%BC%E8%A8%98%E9%8C%84%E5%B7%B2%E8%A2%AB%E8%A8%AA%E5%95%8F%E9%81%8E%E7%9A%84%E9%A0%82%E9%BB%9E%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%29%0A%20%20%20%20dfs%28graph%2C%20visited%2C%20res%2C%20start_vet%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%84%A1%E5%90%91%E5%9C%96%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0%2C%201%2C%202%2C%203%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%0A%20%20%20%20%23%20%E6%B7%B1%E5%BA%A6%E5%84%AA%E5%85%88%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res%20%3D%20graph_dfs%28graph%2C%20v%5B0%5D%29&cumulative=false&curInstr=130&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_greedy/coin_change_greedy.md ================================================ https://pythontutor.com/render.html#code=def%20coin_change_greedy%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%8C%A2%E5%85%8C%E6%8F%9B%EF%BC%9A%E8%B2%AA%E5%A9%AA%22%22%22%0A%20%20%20%20%23%20%E5%81%87%E8%A8%AD%20coins%20%E4%B8%B2%E5%88%97%E6%9C%89%E5%BA%8F%0A%20%20%20%20i%20%3D%20len%28coins%29%20-%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E9%80%B2%E8%A1%8C%E8%B2%AA%E5%A9%AA%E9%81%B8%E6%93%87%EF%BC%8C%E7%9B%B4%E5%88%B0%E7%84%A1%E5%89%A9%E9%A4%98%E9%87%91%E9%A1%8D%0A%20%20%20%20while%20amt%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E5%B0%8F%E6%96%BC%E4%B8%94%E6%9C%80%E6%8E%A5%E8%BF%91%E5%89%A9%E9%A4%98%E9%87%91%E9%A1%8D%E7%9A%84%E7%A1%AC%E5%B9%A3%0A%20%20%20%20%20%20%20%20while%20i%20%3E%200%20and%20coins%5Bi%5D%20%3E%20amt%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20-%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E9%81%B8%E6%93%87%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20amt%20-%3D%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%23%20%E8%8B%A5%E6%9C%AA%E6%89%BE%E5%88%B0%E5%8F%AF%E8%A1%8C%E6%96%B9%E6%A1%88%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20return%20count%20if%20amt%20%3D%3D%200%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%B2%AA%E5%A9%AA%EF%BC%9A%E8%83%BD%E5%A4%A0%E4%BF%9D%E8%AD%89%E6%89%BE%E5%88%B0%E5%85%A8%E5%9F%9F%E6%80%A7%E6%9C%80%E5%84%AA%E8%A7%A3%0A%20%20%20%20coins%20%3D%20%5B1%2C%205%2C%2010%2C%2020%2C%2050%2C%20100%5D%0A%20%20%20%20amt%20%3D%20186%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%E6%B9%8A%E5%88%B0%20%7Bamt%7D%20%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B9%A3%E6%95%B8%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29%0A%0A%20%20%20%20%23%20%E8%B2%AA%E5%A9%AA%EF%BC%9A%E7%84%A1%E6%B3%95%E4%BF%9D%E8%AD%89%E6%89%BE%E5%88%B0%E5%85%A8%E5%9F%9F%E6%80%A7%E6%9C%80%E5%84%AA%E8%A7%A3%0A%20%20%20%20coins%20%3D%20%5B1%2C%2020%2C%2050%5D%0A%20%20%20%20amt%20%3D%2060%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%E6%B9%8A%E5%88%B0%20%7Bamt%7D%20%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B9%A3%E6%95%B8%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E5%AF%A6%E9%9A%9B%E4%B8%8A%E9%9C%80%E8%A6%81%E7%9A%84%E6%9C%80%E5%B0%91%E6%95%B8%E9%87%8F%E7%82%BA%203%20%EF%BC%8C%E5%8D%B3%2020%20%2B%2020%20%2B%2020%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_greedy/fractional_knapsack.md ================================================ https://pythontutor.com/render.html#code=class%20Item%3A%0A%20%20%20%20%22%22%22%E7%89%A9%E5%93%81%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20w%3A%20int%2C%20v%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.w%20%3D%20w%20%20%23%20%E7%89%A9%E5%93%81%E9%87%8D%E9%87%8F%0A%20%20%20%20%20%20%20%20self.v%20%3D%20v%20%20%23%20%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%0A%0Adef%20fractional_knapsack%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%88%86%E6%95%B8%E8%83%8C%E5%8C%85%EF%BC%9A%E8%B2%AA%E5%A9%AA%22%22%22%0A%20%20%20%20%23%20%E5%BB%BA%E7%AB%8B%E7%89%A9%E5%93%81%E4%B8%B2%E5%88%97%EF%BC%8C%E5%8C%85%E5%90%AB%E5%85%A9%E5%80%8B%E5%B1%AC%E6%80%A7%EF%BC%9A%E9%87%8D%E9%87%8F%E3%80%81%E5%83%B9%E5%80%BC%0A%20%20%20%20items%20%3D%20%5BItem%28w%2C%20v%29%20for%20w%2C%20v%20in%20zip%28wgt%2C%20val%29%5D%0A%20%20%20%20%23%20%E6%8C%89%E7%85%A7%E5%96%AE%E4%BD%8D%E5%83%B9%E5%80%BC%20item.v%20/%20item.w%20%E5%BE%9E%E9%AB%98%E5%88%B0%E4%BD%8E%E9%80%B2%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20items.sort%28key%3Dlambda%20item%3A%20item.v%20/%20item.w%2C%20reverse%3DTrue%29%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E8%B2%AA%E5%A9%AA%E9%81%B8%E6%93%87%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20for%20item%20in%20items%3A%0A%20%20%20%20%20%20%20%20if%20item.w%20%3C%3D%20cap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%89%A9%E9%A4%98%E5%AE%B9%E9%87%8F%E5%85%85%E8%B6%B3%EF%BC%8C%E5%89%87%E5%B0%87%E7%95%B6%E5%89%8D%E7%89%A9%E5%93%81%E6%95%B4%E5%80%8B%E8%A3%9D%E9%80%B2%E8%83%8C%E5%8C%85%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20item.v%0A%20%20%20%20%20%20%20%20%20%20%20%20cap%20-%3D%20item.w%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%89%A9%E9%A4%98%E5%AE%B9%E9%87%8F%E4%B8%8D%E8%B6%B3%EF%BC%8C%E5%89%87%E5%B0%87%E7%95%B6%E5%89%8D%E7%89%A9%E5%93%81%E7%9A%84%E4%B8%80%E9%83%A8%E5%88%86%E8%A3%9D%E9%80%B2%E8%83%8C%E5%8C%85%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20%28item.v%20/%20item.w%29%20%2A%20cap%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B7%B2%E7%84%A1%E5%89%A9%E9%A4%98%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%9B%A0%E6%AD%A4%E8%B7%B3%E5%87%BA%E8%BF%B4%E5%9C%88%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E8%B2%AA%E5%A9%AA%E6%BC%94%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20fractional_knapsack%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_greedy/max_capacity.md ================================================ https://pythontutor.com/render.html#code=def%20max_capacity%28ht%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%EF%BC%9A%E8%B2%AA%E5%A9%AA%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20i%2C%20j%EF%BC%8C%E4%BD%BF%E5%85%B6%E5%88%86%E5%88%97%E9%99%A3%E5%88%97%E5%85%A9%E7%AB%AF%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28ht%29%20-%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E7%82%BA%200%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E8%B2%AA%E5%A9%AA%E9%81%B8%E6%93%87%EF%BC%8C%E7%9B%B4%E8%87%B3%E5%85%A9%E6%9D%BF%E7%9B%B8%E9%81%87%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%0A%20%20%20%20%20%20%20%20cap%20%3D%20min%28ht%5Bi%5D%2C%20ht%5Bj%5D%29%20%2A%20%28j%20-%20i%29%0A%20%20%20%20%20%20%20%20res%20%3D%20max%28res%2C%20cap%29%0A%20%20%20%20%20%20%20%20%23%20%E5%90%91%E5%85%A7%E7%A7%BB%E5%8B%95%E7%9F%AD%E6%9D%BF%0A%20%20%20%20%20%20%20%20if%20ht%5Bi%5D%20%3C%20ht%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20ht%20%3D%20%5B3%2C%208%2C%205%2C%202%2C%207%2C%207%2C%203%2C%204%5D%0A%0A%20%20%20%20%23%20%E8%B2%AA%E5%A9%AA%E6%BC%94%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20max_capacity%28ht%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_greedy/max_product_cutting.md ================================================ https://pythontutor.com/render.html#code=import%20math%0A%0Adef%20max_product_cutting%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%88%87%E5%88%86%E4%B9%98%E7%A9%8D%EF%BC%9A%E8%B2%AA%E5%A9%AA%22%22%22%0A%20%20%20%20%23%20%E7%95%B6%20n%20%3C%3D%203%20%E6%99%82%EF%BC%8C%E5%BF%85%E9%A0%88%E5%88%87%E5%88%86%E5%87%BA%E4%B8%80%E5%80%8B%201%0A%20%20%20%20if%20n%20%3C%3D%203%3A%0A%20%20%20%20%20%20%20%20return%201%20%2A%20%28n%20-%201%29%0A%20%20%20%20%23%20%E8%B2%AA%E5%A9%AA%E5%9C%B0%E5%88%87%E5%88%86%E5%87%BA%203%20%EF%BC%8Ca%20%E7%82%BA%203%20%E7%9A%84%E5%80%8B%E6%95%B8%EF%BC%8Cb%20%E7%82%BA%E9%A4%98%E6%95%B8%0A%20%20%20%20a%2C%20b%20%3D%20n%20//%203%2C%20n%20%25%203%0A%20%20%20%20if%20b%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%95%B6%E9%A4%98%E6%95%B8%E7%82%BA%201%20%E6%99%82%EF%BC%8C%E5%B0%87%E4%B8%80%E5%B0%8D%201%20%2A%203%20%E8%BD%89%E5%8C%96%E7%82%BA%202%20%2A%202%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%20-%201%29%29%20%2A%202%20%2A%202%0A%20%20%20%20if%20b%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%95%B6%E9%A4%98%E6%95%B8%E7%82%BA%202%20%E6%99%82%EF%BC%8C%E4%B8%8D%E5%81%9A%E8%99%95%E7%90%86%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%20%2A%202%0A%20%20%20%20%23%20%E7%95%B6%E9%A4%98%E6%95%B8%E7%82%BA%200%20%E6%99%82%EF%BC%8C%E4%B8%8D%E5%81%9A%E8%99%95%E7%90%86%0A%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2058%0A%0A%20%20%20%20%23%20%E8%B2%AA%E5%A9%AA%E6%BC%94%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20max_product_cutting%28n%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%88%87%E5%88%86%E4%B9%98%E7%A9%8D%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_hashing/array_hash_map.md ================================================ https://pythontutor.com/render.html#code=class%20Pair%3A%0A%20%20%20%20%22%22%22%E9%8D%B5%E5%80%BC%E5%B0%8D%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0A%0Aclass%20ArrayHashMap%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%99%A3%E5%88%97%E5%AF%A6%E7%8F%BE%E7%9A%84%E9%9B%9C%E6%B9%8A%E8%A1%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%EF%BC%8C%E5%8C%85%E5%90%AB%2020%20%E5%80%8B%E6%A1%B6%0A%20%20%20%20%20%20%20%20self.buckets%3A%20list%5BPair%20%7C%20None%5D%20%3D%20%5BNone%5D%20%2A%2020%0A%0A%20%20%20%20def%20hash_func%28self%2C%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E9%9B%9C%E6%B9%8A%E5%87%BD%E5%BC%8F%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20key%20%25%2020%0A%20%20%20%20%20%20%20%20return%20index%0A%0A%20%20%20%20def%20get%28self%2C%20key%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E8%A9%A2%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20pair%3A%20Pair%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20if%20pair%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20pair.val%0A%0A%20%20%20%20def%20put%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key%2C%20val%29%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20pair%0A%0A%20%20%20%20def%20remove%28self%2C%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20%23%20%E7%BD%AE%E7%82%BA%20None%20%EF%BC%8C%E4%BB%A3%E8%A1%A8%E5%88%AA%E9%99%A4%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20None%0A%0A%20%20%20%20def%20entry_set%28self%29%20-%3E%20list%5BPair%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E6%89%80%E6%9C%89%E9%8D%B5%E5%80%BC%E5%B0%8D%22%22%22%0A%20%20%20%20%20%20%20%20result%3A%20list%5BPair%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20key_set%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E6%89%80%E6%9C%89%E9%8D%B5%22%22%22%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.key%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20value_set%28self%29%20-%3E%20list%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E6%89%80%E6%9C%89%E5%80%BC%22%22%22%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.val%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%97%E5%8D%B0%E9%9B%9C%E6%B9%8A%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print%28pair.key%2C%20%22-%3E%22%2C%20pair.val%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%9C%E6%B9%8A%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20ArrayHashMap%28%29%0A%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E6%93%8D%E4%BD%9C%0A%20%20%20%20hmap.put%2812836%2C%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hmap.put%2815937%2C%20%22%E5%B0%8F%E5%9B%89%22%29%0A%20%20%20%20hmap.put%2816750%2C%20%22%E5%B0%8F%E7%AE%97%22%29%0A%20%20%20%20hmap.put%2813276%2C%20%22%E5%B0%8F%E6%B3%95%22%29%0A%20%20%20%20hmap.put%2810583%2C%20%22%E5%B0%8F%E9%B4%A8%22%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20name%20%3D%20hmap.get%2815937%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20hmap.remove%2810583%29%0A%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E9%9B%9C%E6%B9%8A%E8%A1%A8%0A%20%20%20%20print%28%22%5Cn%E8%B5%B0%E8%A8%AA%E9%8D%B5%E5%80%BC%E5%B0%8D%20Key-%3EValue%22%29%0A%20%20%20%20for%20pair%20in%20hmap.entry_set%28%29%3A%0A%20%20%20%20%20%20%20%20print%28pair.key%2C%20%22-%3E%22%2C%20pair.val%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_hashing/hash_map_chaining.md ================================================ https://pythontutor.com/render.html#code=class%20Pair%3A%0A%20%20%20%20%22%22%22%E9%8D%B5%E5%80%BC%E5%B0%8D%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Aclass%20HashMapChaining%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E5%BC%8F%E4%BD%8D%E5%9D%80%E9%9B%9C%E6%B9%8A%E8%A1%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20self.capacity%20%3D%204%0A%20%20%20%20%20%20%20%20self.load_thres%20%3D%202.0%20/%203.0%0A%20%20%20%20%20%20%20%20self.extend_ratio%20%3D%202%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%0A%20%20%20%20def%20hash_func%28self%2C%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E9%9B%9C%E6%B9%8A%E5%87%BD%E5%BC%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20key%20%25%20self.capacity%0A%0A%20%20%20%20def%20load_factor%28self%29%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%B2%A0%E8%BC%89%E5%9B%A0%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%20/%20self.capacity%0A%0A%20%20%20%20def%20get%28self%2C%20key%3A%20int%29%20-%3E%20str%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E8%A9%A2%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20pair.val%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20def%20put%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.load_factor%28%29%20%3E%20self.load_thres%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend%28%29%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pair.val%20%3D%20val%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key%2C%20val%29%0A%20%20%20%20%20%20%20%20bucket.append%28pair%29%0A%20%20%20%20%20%20%20%20self.size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self%2C%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bucket.remove%28pair%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.size%20-%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20def%20extend%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%93%B4%E5%AE%B9%E9%9B%9C%E6%B9%8A%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20buckets%20%3D%20self.buckets%0A%20%20%20%20%20%20%20%20self.capacity%20%2A%3D%20self.extend_ratio%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.put%28pair.key%2C%20pair.val%29%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%97%E5%8D%B0%E9%9B%9C%E6%B9%8A%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20res.append%28str%28pair.key%29%20%2B%20%22%20-%3E%20%22%20%2B%20pair.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28res%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%9C%E6%B9%8A%E8%A1%A8%0A%20%20%20%20hashmap%20%3D%20HashMapChaining%28%29%0A%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.put%2812836%2C%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hashmap.put%2815937%2C%20%22%E5%B0%8F%E5%9B%89%22%29%0A%20%20%20%20hashmap.put%2816750%2C%20%22%E5%B0%8F%E7%AE%97%22%29%0A%20%20%20%20hashmap.put%2813276%2C%20%22%E5%B0%8F%E6%B3%95%22%29%0A%20%20%20%20hashmap.put%2810583%2C%20%22%E5%B0%8F%E9%B4%A8%22%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20name%20%3D%20hashmap.get%2813276%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.remove%2812836%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_hashing/simple_hash.md ================================================ https://pythontutor.com/render.html#code=def%20add_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%8A%A0%E6%B3%95%E9%9B%9C%E6%B9%8A%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%2B%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20mul_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%B9%98%E6%B3%95%E9%9B%9C%E6%B9%8A%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%2031%20%2A%20hash%20%2B%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20xor_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%92%E6%96%A5%E6%88%96%E9%9B%9C%E6%B9%8A%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%5E%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20rot_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%97%8B%E8%BD%89%E9%9B%9C%E6%B9%8A%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%20%28hash%20%3C%3C%204%29%20%5E%20%28hash%20%3E%3E%2028%29%20%5E%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20key%20%3D%20%22Hello%20%E6%BC%94%E7%AE%97%E6%B3%95%22%0A%0A%20%20%20%20hash%20%3D%20add_hash%28key%29%0A%20%20%20%20print%28f%22%E5%8A%A0%E6%B3%95%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20mul_hash%28key%29%0A%20%20%20%20print%28f%22%E4%B9%98%E6%B3%95%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20xor_hash%28key%29%0A%20%20%20%20print%28f%22%E4%BA%92%E6%96%A5%E6%88%96%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20rot_hash%28key%29%0A%20%20%20%20print%28f%22%E6%97%8B%E8%BD%89%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%20%7Bhash%7D%22%29%0A&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_heap/my_heap.md ================================================ https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%EF%BC%8C%E6%A0%B9%E6%93%9A%E8%BC%B8%E5%85%A5%E4%B8%B2%E5%88%97%E5%BB%BA%E5%A0%86%E7%A9%8D%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%E4%B8%B2%E5%88%97%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8B%95%E6%96%B0%E5%A2%9E%E9%80%B2%E5%A0%86%E7%A9%8D%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%20%20%20%20%20%20%20%20%23%20%E5%A0%86%E7%A9%8D%E5%8C%96%E9%99%A4%E8%91%89%E7%AF%80%E9%BB%9E%E4%BB%A5%E5%A4%96%E7%9A%84%E5%85%B6%E4%BB%96%E6%89%80%E6%9C%89%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.parent%28self.size%28%29%20-%201%29%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.sift_down%28i%29%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E7%88%B6%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8F%9B%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20sift_down%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BE%9E%E7%AF%80%E9%BB%9E%20i%20%E9%96%8B%E5%A7%8B%EF%BC%8C%E5%BE%9E%E9%A0%82%E8%87%B3%E5%BA%95%E5%A0%86%E7%A9%8D%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E7%AF%80%E9%BB%9E%20i%2C%20l%2C%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E7%AF%80%E9%BB%9E%EF%BC%8C%E8%A8%98%E7%82%BA%20ma%0A%20%20%20%20%20%20%20%20%20%20%20%20l%2C%20r%2C%20ma%20%3D%20self.left%28i%29%2C%20self.right%28i%29%2C%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E7%AF%80%E9%BB%9E%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l%2C%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%89%87%E7%84%A1%E9%A0%88%E7%B9%BC%E7%BA%8C%E5%A0%86%E7%A9%8D%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%E5%85%A9%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E5%90%91%E4%B8%8B%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B1%2C%202%2C%203%2C%204%2C%205%5D%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%E4%B8%B2%E5%88%97%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8B%95%E6%96%B0%E5%A2%9E%E9%80%B2%E5%A0%86%E7%A9%8D%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E7%88%B6%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E5%A0%86%E7%A9%8D%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.max_heap%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20%23%20%E8%AB%8B%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%E5%B7%B2%E7%B6%93%E6%98%AF%E4%B8%80%E5%80%8B%E5%B7%B2%E7%B6%93%E6%98%AF%E4%B8%80%E5%80%8B%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%E7%A9%8D%20%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%206%2C%206%2C%207%2C%205%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%5D%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20max_heap.peek%28%29%0A%20%20%20%20print%28f%22%5Cn%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%E7%82%BA%20%7Bpeek%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%E4%B8%B2%E5%88%97%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8B%95%E6%96%B0%E5%A2%9E%E9%80%B2%E5%A0%86%E7%A9%8D%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E7%88%B6%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8F%9B%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E5%A0%86%E7%A9%8D%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%A9%8D%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20self.max_heap.append%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%9E%E5%BA%95%E8%87%B3%E9%A0%82%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20self.sift_up%28self.size%28%29%20-%201%29%0A%0A%20%20%20%20def%20sift_up%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BE%9E%E7%AF%80%E9%BB%9E%20i%20%E9%96%8B%E5%A7%8B%EF%BC%8C%E5%BE%9E%E5%BA%95%E8%87%B3%E9%A0%82%E5%A0%86%E7%A9%8D%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E7%AF%80%E9%BB%9E%20i%20%E7%9A%84%E7%88%B6%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20p%20%3D%20self.parent%28i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%95%B6%E2%80%9C%E8%B6%8A%E9%81%8E%E6%A0%B9%E7%AF%80%E9%BB%9E%E2%80%9D%E6%88%96%E2%80%9C%E7%AF%80%E9%BB%9E%E7%84%A1%E9%A0%88%E4%BF%AE%E5%BE%A9%E2%80%9D%E6%99%82%EF%BC%8C%E7%B5%90%E6%9D%9F%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20p%20%3C%200%20or%20self.max_heap%5Bi%5D%20%3C%3D%20self.max_heap%5Bp%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%E5%85%A9%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20p%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E5%90%91%E4%B8%8A%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20p%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20%23%20%E8%AB%8B%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%E5%B7%B2%E7%B6%93%E6%98%AF%E4%B8%80%E5%80%8B%E5%B7%B2%E7%B6%93%E6%98%AF%E4%B8%80%E5%80%8B%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%E7%A9%8D%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%206%2C%206%2C%207%2C%205%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%5D%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%A9%8D%0A%20%20%20%20val%20%3D%207%0A%20%20%20%20max_heap.push%28val%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%E4%B8%B2%E5%88%97%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8B%95%E6%96%B0%E5%A2%9E%E9%80%B2%E5%A0%86%E7%A9%8D%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E7%88%B6%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8F%9B%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E5%A0%86%E7%A9%8D%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%A9%8D%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E7%A9%BA%E8%99%95%E7%90%86%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E5%A0%86%E7%A9%8D%E7%82%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%E6%A0%B9%E7%AF%80%E9%BB%9E%E8%88%87%E6%9C%80%E5%8F%B3%E8%91%89%E7%AF%80%E9%BB%9E%EF%BC%88%E4%BA%A4%E6%8F%9B%E9%A6%96%E5%85%83%E7%B4%A0%E8%88%87%E5%B0%BE%E5%85%83%E7%B4%A0%EF%BC%89%0A%20%20%20%20%20%20%20%20self.swap%280%2C%20self.size%28%29%20-%201%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20val%20%3D%20self.max_heap.pop%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%9E%E9%A0%82%E8%87%B3%E5%BA%95%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20self.sift_down%280%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20return%20val%0A%0A%20%20%20%20def%20sift_down%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BE%9E%E7%AF%80%E9%BB%9E%20i%20%E9%96%8B%E5%A7%8B%EF%BC%8C%E5%BE%9E%E9%A0%82%E8%87%B3%E5%BA%95%E5%A0%86%E7%A9%8D%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E7%AF%80%E9%BB%9E%20i%2C%20l%2C%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E7%AF%80%E9%BB%9E%EF%BC%8C%E8%A8%98%E7%82%BA%20ma%0A%20%20%20%20%20%20%20%20%20%20%20%20l%2C%20r%2C%20ma%20%3D%20self.left%28i%29%2C%20self.right%28i%29%2C%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E7%AF%80%E9%BB%9E%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l%2C%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%89%87%E7%84%A1%E9%A0%88%E7%B9%BC%E7%BA%8C%E5%A0%86%E7%A9%8D%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%E5%85%A9%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E5%90%91%E4%B8%8B%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20%23%20%E8%AB%8B%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%E5%B7%B2%E7%B6%93%E6%98%AF%E4%B8%80%E5%80%8B%E5%B7%B2%E7%B6%93%E6%98%AF%E4%B8%80%E5%80%8B%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%E7%A9%8D%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%207%2C%206%2C%207%2C%206%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%2C%205%5D%29%0A%0A%20%20%20%20%23%20%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%A9%8D%0A%20%20%20%20peek%20%3D%20max_heap.pop%28%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_heap/top_k.md ================================================ https://pythontutor.com/render.html#code=import%20heapq%0A%0Adef%20top_k_heap%28nums%3A%20list%5Bint%5D%2C%20k%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E5%A0%86%E7%A9%8D%E6%9F%A5%E8%A9%A2%E9%99%A3%E5%88%97%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%20k%20%E5%80%8B%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20heap%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E5%B0%87%E9%99%A3%E5%88%97%E7%9A%84%E5%89%8D%20k%20%E5%80%8B%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%A9%8D%0A%20%20%20%20for%20i%20in%20range%28k%29%3A%0A%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20%23%20%E5%BE%9E%E7%AC%AC%20k%2B1%20%E5%80%8B%E5%85%83%E7%B4%A0%E9%96%8B%E5%A7%8B%EF%BC%8C%E4%BF%9D%E6%8C%81%E5%A0%86%E7%A9%8D%E7%9A%84%E9%95%B7%E5%BA%A6%E7%82%BA%20k%0A%20%20%20%20for%20i%20in%20range%28k%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E7%95%B6%E5%89%8D%E5%85%83%E7%B4%A0%E5%A4%A7%E6%96%BC%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%EF%BC%8C%E5%89%87%E5%B0%87%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%A9%8D%E3%80%81%E7%95%B6%E5%89%8D%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%A9%8D%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3E%20heap%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappop%28heap%29%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20return%20heap%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%207%2C%206%2C%203%2C%202%5D%0A%20%20%20%20k%20%3D%203%0A%0A%20%20%20%20res%20%3D%20top_k_heap%28nums%2C%20k%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_searching/binary_search.md ================================================ https://pythontutor.com/render.html#code=def%20binary_search%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%EF%BC%88%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%20%5B0%2C%20n-1%5D%20%EF%BC%8C%E5%8D%B3%20i%2C%20j%20%E5%88%86%E5%88%A5%E6%8C%87%E5%90%91%E9%99%A3%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%E3%80%81%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%EF%BC%8C%E7%95%B6%E6%90%9C%E5%B0%8B%E5%8D%80%E9%96%93%E7%82%BA%E7%A9%BA%E6%99%82%E8%B7%B3%E5%87%BA%EF%BC%88%E7%95%B6%20i%20%3E%20j%20%E6%99%82%E7%82%BA%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%90%86%E8%AB%96%E4%B8%8A%20Python%20%E7%9A%84%E6%95%B8%E5%AD%97%E5%8F%AF%E4%BB%A5%E7%84%A1%E9%99%90%E5%A4%A7%EF%BC%88%E5%8F%96%E6%B1%BA%E6%96%BC%E8%A8%98%E6%86%B6%E9%AB%94%E5%A4%A7%E5%B0%8F%EF%BC%89%EF%BC%8C%E7%84%A1%E9%A0%88%E8%80%83%E6%85%AE%E5%A4%A7%E6%95%B8%E8%B6%8A%E7%95%8C%E5%95%8F%E9%A1%8C%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E6%AD%A4%E6%83%85%E6%B3%81%E8%AA%AA%E6%98%8E%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E6%AD%A4%E6%83%85%E6%B3%81%E8%AA%AA%E6%98%8E%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20return%20-1%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%EF%BC%88%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums%2C%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20binary_search_lcro%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%EF%BC%88%E5%B7%A6%E9%96%89%E5%8F%B3%E9%96%8B%E5%8D%80%E9%96%93%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A6%E9%96%89%E5%8F%B3%E9%96%8B%E5%8D%80%E9%96%93%20%5B0%2C%20n%29%20%EF%BC%8C%E5%8D%B3%20i%2C%20j%20%E5%88%86%E5%88%A5%E6%8C%87%E5%90%91%E9%99%A3%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%E3%80%81%E5%B0%BE%E5%85%83%E7%B4%A0%2B1%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%EF%BC%8C%E7%95%B6%E6%90%9C%E5%B0%8B%E5%8D%80%E9%96%93%E7%82%BA%E7%A9%BA%E6%99%82%E8%B7%B3%E5%87%BA%EF%BC%88%E7%95%B6%20i%20%3D%20j%20%E6%99%82%E7%82%BA%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E6%AD%A4%E6%83%85%E6%B3%81%E8%AA%AA%E6%98%8E%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bm%2B1%2C%20j%29%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20%20%23%20%E6%AD%A4%E6%83%85%E6%B3%81%E8%AA%AA%E6%98%8E%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m%29%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20return%20-1%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%EF%BC%88%E5%B7%A6%E9%96%89%E5%8F%B3%E9%96%8B%E5%8D%80%E9%96%93%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search_lcro%28nums%2C%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_searching/binary_search_edge.md ================================================ https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%8F%92%E5%85%A5%E9%BB%9E%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E9%BB%9E%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_left_edge%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%9C%80%E5%B7%A6%E4%B8%80%E5%80%8B%20target%22%22%22%0A%20%20%20%20%23%20%E7%AD%89%E5%83%B9%E6%96%BC%E6%9F%A5%E8%A9%A2%20target%20%E7%9A%84%E6%8F%92%E5%85%A5%E9%BB%9E%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums%2C%20target%29%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20i%20%3D%3D%20len%28nums%29%20or%20nums%5Bi%5D%20%21%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E7%B4%A2%E5%BC%95%20i%0A%20%20%20%20return%20i%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%E7%9A%84%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E5%B7%A6%E9%82%8A%E7%95%8C%E5%92%8C%E5%8F%B3%E9%82%8A%E7%95%8C%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_left_edge%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%B7%A6%E4%B8%80%E5%80%8B%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E7%B4%A2%E5%BC%95%E7%82%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%8F%92%E5%85%A5%E9%BB%9E%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E9%BB%9E%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_right_edge%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%9C%80%E5%8F%B3%E4%B8%80%E5%80%8B%20target%22%22%22%0A%20%20%20%20%23%20%E8%BD%89%E5%8C%96%E7%82%BA%E6%9F%A5%E8%A9%A2%E6%9C%80%E5%B7%A6%E4%B8%80%E5%80%8B%20target%20%2B%201%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums%2C%20target%20%2B%201%29%0A%20%20%20%20%23%20j%20%E6%8C%87%E5%90%91%E6%9C%80%E5%8F%B3%E4%B8%80%E5%80%8B%20target%20%EF%BC%8Ci%20%E6%8C%87%E5%90%91%E9%A6%96%E5%80%8B%E5%A4%A7%E6%96%BC%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20j%20%3D%3D%20-1%20or%20nums%5Bj%5D%20%21%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20return%20j%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%E7%9A%84%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E5%B7%A6%E9%82%8A%E7%95%8C%E5%92%8C%E5%8F%B3%E9%82%8A%E7%95%8C%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_right_edge%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%8F%B3%E4%B8%80%E5%80%8B%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E7%B4%A2%E5%BC%95%E7%82%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_searching/binary_search_insertion.md ================================================ https://pythontutor.com/render.html#code=def%20binary_search_insertion_simple%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%8F%92%E5%85%A5%E9%BB%9E%EF%BC%88%E7%84%A1%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E9%BB%9E%20m%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E9%BB%9E%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E7%84%A1%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%E7%9A%84%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%8F%92%E5%85%A5%E9%BB%9E%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion_simple%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E6%8F%92%E5%85%A5%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%E7%82%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%8F%92%E5%85%A5%E9%BB%9E%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E9%BB%9E%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%E7%9A%84%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%8F%92%E5%85%A5%E9%BB%9E%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E6%8F%92%E5%85%A5%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%E7%82%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_searching/two_sum.md ================================================ https://pythontutor.com/render.html#code=def%20two_sum_brute_force%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%E4%B8%80%EF%BC%9A%E6%9A%B4%E5%8A%9B%E5%88%97%E8%88%89%22%22%22%0A%20%20%20%20%23%20%E5%85%A9%E5%B1%A4%E8%BF%B4%E5%9C%88%EF%BC%8C%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%E7%82%BA%20O%28n%5E2%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%2B%20nums%5Bj%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bi%2C%20j%5D%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%207%2C%2011%2C%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_brute_force%28nums%2C%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20two_sum_hash_table%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%E4%BA%8C%EF%BC%9A%E8%BC%94%E5%8A%A9%E9%9B%9C%E6%B9%8A%E8%A1%A8%22%22%22%0A%20%20%20%20%23%20%E8%BC%94%E5%8A%A9%E9%9B%9C%E6%B9%8A%E8%A1%A8%EF%BC%8C%E7%A9%BA%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%E7%82%BA%20O%28n%29%0A%20%20%20%20dic%20%3D%20%7B%7D%0A%20%20%20%20%23%20%E5%96%AE%E5%B1%A4%E8%BF%B4%E5%9C%88%EF%BC%8C%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%E7%82%BA%20O%28n%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20target%20-%20nums%5Bi%5D%20in%20dic%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bdic%5Btarget%20-%20nums%5Bi%5D%5D%2C%20i%5D%0A%20%20%20%20%20%20%20%20dic%5Bnums%5Bi%5D%5D%20%3D%20i%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%207%2C%2011%2C%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_hash_table%28nums%2C%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_sorting/bubble_sort.md ================================================ https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B3%A1%E6%B2%AB%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E8%BF%B4%E5%9C%88%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A7%E8%BF%B4%E5%9C%88%EF%BC%9A%E5%B0%87%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%20%5B0%2C%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%E8%87%B3%E8%A9%B2%E5%8D%80%E9%96%93%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%20nums%5Bj%5D%20%E8%88%87%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E6%B3%A1%E6%B2%AB%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20bubble_sort_with_flag%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B3%A1%E6%B2%AB%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A8%99%E8%AA%8C%E6%9C%80%E4%BD%B3%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E8%BF%B4%E5%9C%88%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20flag%20%3D%20False%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A8%99%E8%AA%8C%E4%BD%8D%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A7%E8%BF%B4%E5%9C%88%EF%BC%9A%E5%B0%87%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%20%5B0%2C%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%E8%87%B3%E8%A9%B2%E5%8D%80%E9%96%93%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%20nums%5Bj%5D%20%E8%88%87%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20flag%20%3D%20True%20%20%23%20%E8%A8%98%E9%8C%84%E4%BA%A4%E6%8F%9B%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20flag%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%20%20%23%20%E6%AD%A4%E8%BC%AA%E2%80%9C%E5%86%92%E6%B3%A1%E2%80%9D%E6%9C%AA%E4%BA%A4%E6%8F%9B%E4%BB%BB%E4%BD%95%E5%85%83%E7%B4%A0%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%B7%B3%E5%87%BA%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort_with_flag%28nums%29%0A%20%20%20%20print%28%22%E6%B3%A1%E6%B2%AB%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_sorting/bucket_sort.md ================================================ https://pythontutor.com/render.html#code=def%20bucket_sort%28nums%3A%20list%5Bfloat%5D%29%3A%0A%20%20%20%20%22%22%22%E6%A1%B6%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20k%20%3D%20n/2%20%E5%80%8B%E6%A1%B6%EF%BC%8C%E9%A0%90%E6%9C%9F%E5%90%91%E6%AF%8F%E5%80%8B%E6%A1%B6%E5%88%86%E9%85%8D%202%20%E5%80%8B%E5%85%83%E7%B4%A0%0A%20%20%20%20k%20%3D%20len%28nums%29%20//%202%0A%20%20%20%20buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28k%29%5D%0A%20%20%20%20%23%201.%20%E5%B0%87%E9%99%A3%E5%88%97%E5%85%83%E7%B4%A0%E5%88%86%E9%85%8D%E5%88%B0%E5%90%84%E5%80%8B%E6%A1%B6%E4%B8%AD%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E7%AF%84%E5%9C%8D%E7%82%BA%20%5B0%2C%201%29%EF%BC%8C%E4%BD%BF%E7%94%A8%20num%20%2A%20k%20%E5%B0%8D%E6%98%A0%E5%88%B0%E7%B4%A2%E5%BC%95%E7%AF%84%E5%9C%8D%20%5B0%2C%20k-1%5D%0A%20%20%20%20%20%20%20%20i%20%3D%20int%28num%20%2A%20k%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%20num%20%E6%96%B0%E5%A2%9E%E9%80%B2%E6%A1%B6%20i%0A%20%20%20%20%20%20%20%20buckets%5Bi%5D.append%28num%29%0A%20%20%20%20%23%202.%20%E5%B0%8D%E5%90%84%E5%80%8B%E6%A1%B6%E5%9F%B7%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%85%A7%E5%BB%BA%E6%8E%92%E5%BA%8F%E5%87%BD%E5%BC%8F%EF%BC%8C%E4%B9%9F%E5%8F%AF%E4%BB%A5%E6%9B%BF%E6%8F%9B%E6%88%90%E5%85%B6%E4%BB%96%E6%8E%92%E5%BA%8F%E6%BC%94%E7%AE%97%E6%B3%95%0A%20%20%20%20%20%20%20%20bucket.sort%28%29%0A%20%20%20%20%23%203.%20%E8%B5%B0%E8%A8%AA%E6%A1%B6%E5%90%88%E4%BD%B5%E7%B5%90%E6%9E%9C%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20for%20num%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%A8%AD%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E7%82%BA%E6%B5%AE%E9%BB%9E%E6%95%B8%EF%BC%8C%E7%AF%84%E5%9C%8D%E7%82%BA%20%5B0%2C%201%29%0A%20%20%20%20nums%20%3D%20%5B0.49%2C%200.96%2C%200.82%2C%200.09%2C%200.57%2C%200.43%2C%200.91%2C%200.75%2C%200.15%2C%200.37%5D%0A%20%20%20%20bucket_sort%28nums%29%0A%20%20%20%20print%28%22%E6%A1%B6%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_sorting/counting_sort.md ================================================ https://pythontutor.com/render.html#code=def%20counting_sort_naive%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%A8%88%E6%95%B8%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%B0%A1%E5%96%AE%E5%AF%A6%E7%8F%BE%EF%BC%8C%E7%84%A1%E6%B3%95%E7%94%A8%E6%96%BC%E6%8E%92%E5%BA%8F%E7%89%A9%E4%BB%B6%0A%20%20%20%20%23%201.%20%E7%B5%B1%E8%A8%88%E9%99%A3%E5%88%97%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%200%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20max%28m%2C%20num%29%0A%20%20%20%20%23%202.%20%E7%B5%B1%E8%A8%88%E5%90%84%E6%95%B8%E5%AD%97%E7%9A%84%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E8%B5%B0%E8%A8%AA%20counter%20%EF%BC%8C%E5%B0%87%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E5%8E%9F%E9%99%A3%E5%88%97%20nums%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20num%20in%20range%28m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28counter%5Bnum%5D%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort_naive%28nums%29%0A%20%20%20%20print%28f%22%E8%A8%88%E6%95%B8%E6%8E%92%E5%BA%8F%EF%BC%88%E7%84%A1%E6%B3%95%E6%8E%92%E5%BA%8F%E7%89%A9%E4%BB%B6%EF%BC%89%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20counting_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%A8%88%E6%95%B8%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%AE%8C%E6%95%B4%E5%AF%A6%E7%8F%BE%EF%BC%8C%E5%8F%AF%E6%8E%92%E5%BA%8F%E7%89%A9%E4%BB%B6%EF%BC%8C%E4%B8%A6%E4%B8%94%E6%98%AF%E7%A9%A9%E5%AE%9A%E6%8E%92%E5%BA%8F%0A%20%20%20%20%23%201.%20%E7%B5%B1%E8%A8%88%E9%99%A3%E5%88%97%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%202.%20%E7%B5%B1%E8%A8%88%E5%90%84%E6%95%B8%E5%AD%97%E7%9A%84%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E6%B1%82%20counter%20%E7%9A%84%E5%89%8D%E7%B6%B4%E5%92%8C%EF%BC%8C%E5%B0%87%E2%80%9C%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%E2%80%9D%E8%BD%89%E6%8F%9B%E7%82%BA%E2%80%9C%E5%B0%BE%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20%23%20%E5%8D%B3%20counter%5Bnum%5D-1%20%E6%98%AF%20num%20%E5%9C%A8%20res%20%E4%B8%AD%E6%9C%80%E5%BE%8C%E4%B8%80%E6%AC%A1%E5%87%BA%E7%8F%BE%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20for%20i%20in%20range%28m%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%20%2B%201%5D%20%2B%3D%20counter%5Bi%5D%0A%20%20%20%20%23%204.%20%E5%80%92%E5%BA%8F%E8%B5%B0%E8%A8%AA%20nums%20%EF%BC%8C%E5%B0%87%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%B5%90%E6%9E%9C%E9%99%A3%E5%88%97%20res%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%20res%20%E7%94%A8%E6%96%BC%E8%A8%98%E9%8C%84%E7%B5%90%E6%9E%9C%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20res%5Bcounter%5Bnum%5D%20-%201%5D%20%3D%20num%20%20%23%20%E5%B0%87%20num%20%E6%94%BE%E7%BD%AE%E5%88%B0%E5%B0%8D%E6%87%89%E7%B4%A2%E5%BC%95%E8%99%95%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20-%3D%201%20%20%23%20%E4%BB%A4%E5%89%8D%E7%B6%B4%E5%92%8C%E8%87%AA%E6%B8%9B%201%20%EF%BC%8C%E5%BE%97%E5%88%B0%E4%B8%8B%E6%AC%A1%E6%94%BE%E7%BD%AE%20num%20%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%B5%90%E6%9E%9C%E9%99%A3%E5%88%97%20res%20%E8%A6%86%E8%93%8B%E5%8E%9F%E9%99%A3%E5%88%97%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort%28nums%29%0A%20%20%20%20print%28f%22%E8%A8%88%E6%95%B8%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_sorting/heap_sort.md ================================================ https://pythontutor.com/render.html#code=def%20sift_down%28nums%3A%20list%5Bint%5D%2C%20n%3A%20int%2C%20i%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E7%A9%8D%E7%9A%84%E9%95%B7%E5%BA%A6%E7%82%BA%20n%20%EF%BC%8C%E5%BE%9E%E7%AF%80%E9%BB%9E%20i%20%E9%96%8B%E5%A7%8B%EF%BC%8C%E5%BE%9E%E9%A0%82%E8%87%B3%E5%BA%95%E5%A0%86%E7%A9%8D%E5%8C%96%22%22%22%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E7%AF%80%E9%BB%9E%20i%2C%20l%2C%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E7%AF%80%E9%BB%9E%EF%BC%8C%E8%A8%98%E7%82%BA%20ma%0A%20%20%20%20%20%20%20%20l%20%3D%202%20%2A%20i%20%2B%201%0A%20%20%20%20%20%20%20%20r%20%3D%202%20%2A%20i%20%2B%202%0A%20%20%20%20%20%20%20%20ma%20%3D%20i%0A%20%20%20%20%20%20%20%20if%20l%20%3C%20n%20and%20nums%5Bl%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20if%20r%20%3C%20n%20and%20nums%5Br%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E7%AF%80%E9%BB%9E%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l%2C%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%89%87%E7%84%A1%E9%A0%88%E7%B9%BC%E7%BA%8C%E5%A0%86%E7%A9%8D%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%E5%85%A9%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bma%5D%20%3D%20nums%5Bma%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E5%90%91%E4%B8%8B%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0Adef%20heap_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E7%A9%8D%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%BB%BA%E5%A0%86%E7%A9%8D%E6%93%8D%E4%BD%9C%EF%BC%9A%E5%A0%86%E7%A9%8D%E5%8C%96%E9%99%A4%E8%91%89%E7%AF%80%E9%BB%9E%E4%BB%A5%E5%A4%96%E7%9A%84%E5%85%B6%E4%BB%96%E6%89%80%E6%9C%89%E7%AF%80%E9%BB%9E%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20//%202%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20len%28nums%29%2C%20i%29%0A%20%20%20%20%23%20%E5%BE%9E%E5%A0%86%E7%A9%8D%E4%B8%AD%E6%8F%90%E5%8F%96%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%B4%E5%9C%88%20n-1%20%E8%BC%AA%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%E6%A0%B9%E7%AF%80%E9%BB%9E%E8%88%87%E6%9C%80%E5%8F%B3%E8%91%89%E7%AF%80%E9%BB%9E%EF%BC%88%E4%BA%A4%E6%8F%9B%E9%A6%96%E5%85%83%E7%B4%A0%E8%88%87%E5%B0%BE%E5%85%83%E7%B4%A0%EF%BC%89%0A%20%20%20%20%20%20%20%20nums%5B0%5D%2C%20nums%5Bi%5D%20%3D%20nums%5Bi%5D%2C%20nums%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E4%BB%A5%E6%A0%B9%E7%AF%80%E9%BB%9E%E7%82%BA%E8%B5%B7%E9%BB%9E%EF%BC%8C%E5%BE%9E%E9%A0%82%E8%87%B3%E5%BA%95%E9%80%B2%E8%A1%8C%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20i%2C%200%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20heap_sort%28nums%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%A9%8D%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_sorting/insertion_sort.md ================================================ https://pythontutor.com/render.html#code=def%20insertion_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%A4%96%E8%BF%B4%E5%9C%88%EF%BC%9A%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5B0%2C%20i-1%5D%0A%20%20%20%20for%20i%20in%20range%281%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20base%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A7%E8%BF%B4%E5%9C%88%EF%BC%9A%E5%B0%87%20base%20%E6%8F%92%E5%85%A5%E5%88%B0%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%20%5B0%2C%20i-1%5D%20%E4%B8%AD%E7%9A%84%E6%AD%A3%E7%A2%BA%E4%BD%8D%E7%BD%AE%0A%20%20%20%20%20%20%20%20while%20j%20%3E%3D%200%20and%20nums%5Bj%5D%20%3E%20base%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%5D%20%20%23%20%E5%B0%87%20nums%5Bj%5D%20%E5%90%91%E5%8F%B3%E7%A7%BB%E5%8B%95%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20base%20%20%23%20%E5%B0%87%20base%20%E8%B3%A6%E5%80%BC%E5%88%B0%E6%AD%A3%E7%A2%BA%E4%BD%8D%E7%BD%AE%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20insertion_sort%28nums%29%0A%20%20%20%20print%28%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_sorting/merge_sort.md ================================================ https://pythontutor.com/render.html#code=def%20merge%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%90%88%E4%BD%B5%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%E5%92%8C%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%E5%8D%80%E9%96%93%E7%82%BA%20%5Bleft%2C%20mid%5D%2C%20%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%E5%8D%80%E9%96%93%E7%82%BA%20%5Bmid%2B1%2C%20right%5D%0A%20%20%20%20%23%20%E5%BB%BA%E7%AB%8B%E4%B8%80%E5%80%8B%E8%87%A8%E6%99%82%E9%99%A3%E5%88%97%20tmp%20%EF%BC%8C%E7%94%A8%E6%96%BC%E5%AD%98%E6%94%BE%E5%90%88%E4%BD%B5%E5%BE%8C%E7%9A%84%E7%B5%90%E6%9E%9C%0A%20%20%20%20tmp%20%3D%20%5B0%5D%20%2A%20%28right%20-%20left%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%E5%92%8C%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%E7%9A%84%E8%B5%B7%E5%A7%8B%E7%B4%A2%E5%BC%95%0A%20%20%20%20i%2C%20j%2C%20k%20%3D%20left%2C%20mid%20%2B%201%2C%200%0A%20%20%20%20%23%20%E7%95%B6%E5%B7%A6%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%E9%83%BD%E9%82%84%E6%9C%89%E5%85%83%E7%B4%A0%E6%99%82%EF%BC%8C%E9%80%B2%E8%A1%8C%E6%AF%94%E8%BC%83%E4%B8%A6%E5%B0%87%E8%BC%83%E5%B0%8F%E7%9A%84%E5%85%83%E7%B4%A0%E8%A4%87%E8%A3%BD%E5%88%B0%E8%87%A8%E6%99%82%E9%99%A3%E5%88%97%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%20and%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3C%3D%20nums%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%87%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%E5%92%8C%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%E7%9A%84%E5%89%A9%E9%A4%98%E5%85%83%E7%B4%A0%E8%A4%87%E8%A3%BD%E5%88%B0%E8%87%A8%E6%99%82%E9%99%A3%E5%88%97%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20while%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%87%E8%87%A8%E6%99%82%E9%99%A3%E5%88%97%20tmp%20%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0%E8%A4%87%E8%A3%BD%E5%9B%9E%E5%8E%9F%E9%99%A3%E5%88%97%20nums%20%E7%9A%84%E5%B0%8D%E6%87%89%E5%8D%80%E9%96%93%0A%20%20%20%20for%20k%20in%20range%280%2C%20len%28tmp%29%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bleft%20%2B%20k%5D%20%3D%20tmp%5Bk%5D%0A%0A%0Adef%20merge_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%90%88%E4%BD%B5%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%20%20%23%20%E7%95%B6%E5%AD%90%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%E7%82%BA%201%20%E6%99%82%E7%B5%82%E6%AD%A2%E9%81%9E%E8%BF%B4%0A%20%20%20%20%23%20%E5%8A%83%E5%88%86%E9%9A%8E%E6%AE%B5%0A%20%20%20%20mid%20%3D%20%28left%20%2B%20right%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%0A%20%20%20%20merge_sort%28nums%2C%20left%2C%20mid%29%20%20%23%20%E9%81%9E%E8%BF%B4%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%0A%20%20%20%20merge_sort%28nums%2C%20mid%20%2B%201%2C%20right%29%20%20%23%20%E9%81%9E%E8%BF%B4%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%0A%20%20%20%20%23%20%E5%90%88%E4%BD%B5%E9%9A%8E%E6%AE%B5%0A%20%20%20%20merge%28nums%2C%20left%2C%20mid%2C%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B7%2C%203%2C%202%2C%206%2C%200%2C%201%2C%205%2C%204%5D%0A%20%20%20%20merge_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%90%88%E4%BD%B5%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_sorting/quick_sort.md ================================================ https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E7%82%BA%E5%9F%BA%E6%BA%96%E6%95%B8%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%BE%9E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%BE%9E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E5%80%8B%E5%A4%A7%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%87%E5%9F%BA%E6%BA%96%E6%95%B8%E4%BA%A4%E6%8F%9B%E8%87%B3%E5%85%A9%E5%AD%90%E9%99%A3%E5%88%97%E7%9A%84%E5%88%86%E7%95%8C%E7%B7%9A%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20partition%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E7%82%BA%E5%9F%BA%E6%BA%96%E6%95%B8%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%BE%9E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%BE%9E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E5%80%8B%E5%A4%A7%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%87%E5%9F%BA%E6%BA%96%E6%95%B8%E4%BA%A4%E6%8F%9B%E8%87%B3%E5%85%A9%E5%AD%90%E9%99%A3%E5%88%97%E7%9A%84%E5%88%86%E7%95%8C%E7%B7%9A%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%E7%82%BA%201%20%E6%99%82%E7%B5%82%E6%AD%A2%E9%81%9E%E8%BF%B4%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%0A%20%20%20%20pivot%20%3D%20partition%28nums%2C%20left%2C%20right%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%E3%80%81%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%0A%20%20%20%20quick_sort%28nums%2C%20left%2C%20pivot%20-%201%29%0A%20%20%20%20quick_sort%28nums%2C%20pivot%20%2B%201%2C%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20quick_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20median_three%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%81%B8%E5%8F%96%E4%B8%89%E5%80%8B%E5%80%99%E9%81%B8%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B8%22%22%22%0A%20%20%20%20l%2C%20m%2C%20r%20%3D%20nums%5Bleft%5D%2C%20nums%5Bmid%5D%2C%20nums%5Bright%5D%0A%20%20%20%20if%20%28l%20%3C%3D%20m%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20m%20%3C%3D%20l%29%3A%0A%20%20%20%20%20%20%20%20return%20mid%20%20%23%20m%20%E5%9C%A8%20l%20%E5%92%8C%20r%20%E4%B9%8B%E9%96%93%0A%20%20%20%20if%20%28m%20%3C%3D%20l%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20l%20%3C%3D%20m%29%3A%0A%20%20%20%20%20%20%20%20return%20left%20%20%23%20l%20%E5%9C%A8%20m%20%E5%92%8C%20r%20%E4%B9%8B%E9%96%93%0A%20%20%20%20return%20right%0A%0Adef%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%EF%BC%88%E4%B8%89%E6%95%B8%E5%8F%96%E4%B8%AD%E5%80%BC%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E7%82%BA%E5%9F%BA%E6%BA%96%E6%95%B8%0A%20%20%20%20med%20%3D%20median_three%28nums%2C%20left%2C%20%28left%20%2B%20right%29%20//%202%2C%20right%29%0A%20%20%20%20%23%20%E5%B0%87%E4%B8%AD%E4%BD%8D%E6%95%B8%E4%BA%A4%E6%8F%9B%E8%87%B3%E9%99%A3%E5%88%97%E6%9C%80%E5%B7%A6%E7%AB%AF%0A%20%20%20%20nums%5Bleft%5D%2C%20nums%5Bmed%5D%20%3D%20nums%5Bmed%5D%2C%20nums%5Bleft%5D%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E7%82%BA%E5%9F%BA%E6%BA%96%E6%95%B8%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%BE%9E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%BE%9E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E5%80%8B%E5%A4%A7%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%87%E5%9F%BA%E6%BA%96%E6%95%B8%E4%BA%A4%E6%8F%9B%E8%87%B3%E5%85%A9%E5%AD%90%E9%99%A3%E5%88%97%E7%9A%84%E5%88%86%E7%95%8C%E7%B7%9A%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%B8%AD%E4%BD%8D%E5%9F%BA%E6%BA%96%E6%95%B8%E6%9C%80%E4%BD%B3%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20partition%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%EF%BC%88%E4%B8%AD%E4%BD%8D%E5%9F%BA%E6%BA%96%E6%95%B8%E6%9C%80%E4%BD%B3%E5%8C%96%EF%BC%89%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E7%82%BA%E5%9F%BA%E6%BA%96%E6%95%B8%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%BE%9E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%BE%9E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E5%80%8B%E5%A4%A7%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%87%E5%9F%BA%E6%BA%96%E6%95%B8%E4%BA%A4%E6%8F%9B%E8%87%B3%E5%85%A9%E5%AD%90%E9%99%A3%E5%88%97%E7%9A%84%E5%88%86%E7%95%8C%E7%B7%9A%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%81%9E%E8%BF%B4%E6%9C%80%E4%BD%B3%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%E7%82%BA%201%20%E6%99%82%E7%B5%82%E6%AD%A2%0A%20%20%20%20while%20left%20%3C%20right%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%E6%93%8D%E4%BD%9C%0A%20%20%20%20%20%20%20%20pivot%20%3D%20partition%28nums%2C%20left%2C%20right%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%8D%E5%85%A9%E5%80%8B%E5%AD%90%E9%99%A3%E5%88%97%E4%B8%AD%E8%BC%83%E7%9F%AD%E7%9A%84%E9%82%A3%E5%80%8B%E5%9F%B7%E8%A1%8C%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%0A%20%20%20%20%20%20%20%20if%20pivot%20-%20left%20%3C%20right%20-%20pivot%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums%2C%20left%2C%20pivot%20-%201%29%20%20%23%20%E9%81%9E%E8%BF%B4%E6%8E%92%E5%BA%8F%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%0A%20%20%20%20%20%20%20%20%20%20%20%20left%20%3D%20pivot%20%2B%201%20%20%23%20%E5%89%A9%E9%A4%98%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5Bpivot%20%2B%201%2C%20right%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums%2C%20pivot%20%2B%201%2C%20right%29%20%20%23%20%E9%81%9E%E8%BF%B4%E6%8E%92%E5%BA%8F%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%0A%20%20%20%20%20%20%20%20%20%20%20%20right%20%3D%20pivot%20-%201%20%20%23%20%E5%89%A9%E9%A4%98%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5Bleft%2C%20pivot%20-%201%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%81%9E%E8%BF%B4%E6%9C%80%E4%BD%B3%E5%8C%96%EF%BC%89%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20quick_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%81%9E%E8%BF%B4%E6%9C%80%E4%BD%B3%E5%8C%96%EF%BC%89%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_sorting/radix_sort.md ================================================ https://pythontutor.com/render.html#code=def%20digit%28num%3A%20int%2C%20exp%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%85%83%E7%B4%A0%20num%20%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E5%85%B6%E4%B8%AD%20exp%20%3D%2010%5E%28k-1%29%22%22%22%0A%20%20%20%20%23%20%E5%82%B3%E5%85%A5%20exp%20%E8%80%8C%E9%9D%9E%20k%20%E5%8F%AF%E4%BB%A5%E9%81%BF%E5%85%8D%E5%9C%A8%E6%AD%A4%E9%87%8D%E8%A4%87%E5%9F%B7%E8%A1%8C%E6%98%82%E8%B2%B4%E7%9A%84%E6%AC%A1%E6%96%B9%E8%A8%88%E7%AE%97%0A%20%20%20%20return%20%28num%20//%20exp%29%20%25%2010%0A%0Adef%20counting_sort_digit%28nums%3A%20list%5Bint%5D%2C%20exp%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E8%A8%88%E6%95%B8%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A0%B9%E6%93%9A%20nums%20%E7%AC%AC%20k%20%E4%BD%8D%E6%8E%92%E5%BA%8F%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%8D%81%E9%80%B2%E4%BD%8D%E5%88%B6%E7%9A%84%E4%BD%8D%E7%AF%84%E5%9C%8D%E7%82%BA%200~9%20%EF%BC%8C%E5%9B%A0%E6%AD%A4%E9%9C%80%E8%A6%81%E9%95%B7%E5%BA%A6%E7%82%BA%2010%20%E7%9A%84%E6%A1%B6%E9%99%A3%E5%88%97%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%2010%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E7%B5%B1%E8%A8%88%200~9%20%E5%90%84%E6%95%B8%E5%AD%97%E7%9A%84%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D%2C%20exp%29%20%20%23%20%E7%8D%B2%E5%8F%96%20nums%5Bi%5D%20%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E8%A8%98%E7%82%BA%20d%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20%2B%3D%201%20%20%23%20%E7%B5%B1%E8%A8%88%E6%95%B8%E5%AD%97%20d%20%E7%9A%84%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%0A%20%20%20%20%23%20%E6%B1%82%E5%89%8D%E7%B6%B4%E5%92%8C%EF%BC%8C%E5%B0%87%E2%80%9C%E5%87%BA%E7%8F%BE%E5%80%8B%E6%95%B8%E2%80%9D%E8%BD%89%E6%8F%9B%E7%82%BA%E2%80%9C%E9%99%A3%E5%88%97%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20for%20i%20in%20range%281%2C%2010%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%5D%20%2B%3D%20counter%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E5%80%92%E5%BA%8F%E8%B5%B0%E8%A8%AA%EF%BC%8C%E6%A0%B9%E6%93%9A%E6%A1%B6%E5%85%A7%E7%B5%B1%E8%A8%88%E7%B5%90%E6%9E%9C%EF%BC%8C%E5%B0%87%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%20res%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D%2C%20exp%29%0A%20%20%20%20%20%20%20%20j%20%3D%20counter%5Bd%5D%20-%201%20%20%23%20%E7%8D%B2%E5%8F%96%20d%20%E5%9C%A8%E9%99%A3%E5%88%97%E4%B8%AD%E7%9A%84%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20res%5Bj%5D%20%3D%20nums%5Bi%5D%20%20%23%20%E5%B0%87%E7%95%B6%E5%89%8D%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20-%3D%201%20%20%23%20%E5%B0%87%20d%20%E7%9A%84%E6%95%B8%E9%87%8F%E6%B8%9B%201%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%B5%90%E6%9E%9C%E8%A6%86%E8%93%8B%E5%8E%9F%E9%99%A3%E5%88%97%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0Adef%20radix_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%95%B8%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E9%99%A3%E5%88%97%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E7%94%A8%E6%96%BC%E5%88%A4%E6%96%B7%E6%9C%80%E5%A4%A7%E4%BD%8D%E6%95%B8%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%20%E6%8C%89%E7%85%A7%E5%BE%9E%E4%BD%8E%E4%BD%8D%E5%88%B0%E9%AB%98%E4%BD%8D%E7%9A%84%E9%A0%86%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20exp%20%3D%201%0A%20%20%20%20while%20exp%20%3C%3D%20m%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%8D%E9%99%A3%E5%88%97%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%E5%9F%B7%E8%A1%8C%E8%A8%88%E6%95%B8%E6%8E%92%E5%BA%8F%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%201%20-%3E%20exp%20%3D%201%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%202%20-%3E%20exp%20%3D%2010%0A%20%20%20%20%20%20%20%20%23%20%E5%8D%B3%20exp%20%3D%2010%5E%28k-1%29%0A%20%20%20%20%20%20%20%20counting_sort_digit%28nums%2C%20exp%29%0A%20%20%20%20%20%20%20%20exp%20%2A%3D%2010%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%9F%BA%E6%95%B8%E6%8E%92%E5%BA%8F%0A%20%20%20%20nums%20%3D%20%5B%0A%20%20%20%20%20%20%20%20105%2C%0A%20%20%20%20%20%20%20%20356%2C%0A%20%20%20%20%20%20%20%20428%2C%0A%20%20%20%20%20%20%20%20348%2C%0A%20%20%20%20%20%20%20%20818%2C%0A%20%20%20%20%5D%0A%20%20%20%20radix_sort%28nums%29%0A%20%20%20%20print%28%22%E5%9F%BA%E6%95%B8%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_sorting/selection_sort.md ================================================ https://pythontutor.com/render.html#code=def%20selection_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%81%B8%E6%93%87%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E8%BF%B4%E5%9C%88%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5Bi%2C%20n-1%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A7%E8%BF%B4%E5%9C%88%EF%BC%9A%E6%89%BE%E5%88%B0%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E5%85%A7%E7%9A%84%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20k%20%3D%20i%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3C%20nums%5Bk%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20k%20%3D%20j%20%20%23%20%E8%A8%98%E9%8C%84%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%E8%A9%B2%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E8%88%87%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%9A%84%E9%A6%96%E5%80%8B%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bk%5D%20%3D%20nums%5Bk%5D%2C%20nums%5Bi%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20selection_sort%28nums%29%0A%20%20%20%20print%28%22%E9%81%B8%E6%93%87%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_stack_and_queue/array_queue.md ================================================ https://pythontutor.com/render.html#code=class%20ArrayQueue%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E7%92%B0%E5%BD%A2%E9%99%A3%E5%88%97%E5%AF%A6%E7%8F%BE%E7%9A%84%E4%BD%87%E5%88%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20size%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._nums%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20%2A%20size%20%20%23%20%E7%94%A8%E6%96%BC%E5%84%B2%E5%AD%98%E4%BD%87%E5%88%97%E5%85%83%E7%B4%A0%E7%9A%84%E9%99%A3%E5%88%97%0A%20%20%20%20%20%20%20%20self._front%3A%20int%20%3D%200%20%20%23%20%E4%BD%87%E5%88%97%E9%A6%96%E6%8C%87%E6%A8%99%EF%BC%8C%E6%8C%87%E5%90%91%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%20%20%23%20%E4%BD%87%E5%88%97%E9%95%B7%E5%BA%A6%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E4%BD%87%E5%88%97%E7%9A%84%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._nums%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E4%BD%87%E5%88%97%E7%9A%84%E9%95%B7%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E5%88%97%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._size%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E4%BD%87%E5%88%97%E5%B7%B2%E6%BB%BF%22%29%0A%20%20%20%20%20%20%20%20%23%20%E8%A8%88%E7%AE%97%E4%BD%87%E5%88%97%E5%B0%BE%E6%8C%87%E6%A8%99%EF%BC%8C%E6%8C%87%E5%90%91%E4%BD%87%E5%88%97%E5%B0%BE%E7%B4%A2%E5%BC%95%20%2B%201%0A%20%20%20%20%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E5%8F%96%E9%A4%98%E6%93%8D%E4%BD%9C%E5%AF%A6%E7%8F%BE%20rear%20%E8%B6%8A%E9%81%8E%E9%99%A3%E5%88%97%E5%B0%BE%E9%83%A8%E5%BE%8C%E5%9B%9E%E5%88%B0%E9%A0%AD%E9%83%A8%0A%20%20%20%20%20%20%20%20rear%3A%20int%20%3D%20%28self._front%20%2B%20self._size%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%20num%20%E6%96%B0%E5%A2%9E%E8%87%B3%E4%BD%87%E5%88%97%E5%B0%BE%0A%20%20%20%20%20%20%20%20self._nums%5Brear%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E5%88%97%22%22%22%0A%20%20%20%20%20%20%20%20num%3A%20int%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20%23%20%E4%BD%87%E5%88%97%E9%A6%96%E6%8C%87%E6%A8%99%E5%90%91%E5%BE%8C%E7%A7%BB%E5%8B%95%E4%B8%80%E4%BD%8D%EF%BC%8C%E8%8B%A5%E8%B6%8A%E9%81%8E%E5%B0%BE%E9%83%A8%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%E5%88%B0%E9%99%A3%E5%88%97%E9%A0%AD%E9%83%A8%0A%20%20%20%20%20%20%20%20self._front%20%3D%20%28self._front%20%2B%201%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E4%BD%87%E5%88%97%E7%82%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._nums%5Bself._front%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BF%94%E5%9B%9E%E4%B8%B2%E5%88%97%E7%94%A8%E6%96%BC%E5%88%97%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20self.size%28%29%0A%20%20%20%20%20%20%20%20j%3A%20int%20%3D%20self._front%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20self._nums%5B%28j%20%25%20self.capacity%28%29%29%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BD%87%E5%88%97%0A%20%20%20%20queue%20%3D%20ArrayQueue%2810%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%88%97%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%88%97%0A%20%20%20%20pop%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%88%97%E5%85%83%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E4%BD%87%E5%88%97%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_stack_and_queue/array_stack.md ================================================ https://pythontutor.com/render.html#code=class%20ArrayStack%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%99%A3%E5%88%97%E5%AF%A6%E7%8F%BE%E7%9A%84%E5%A0%86%E7%96%8A%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._stack%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._stack%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E5%A0%86%E7%96%8A%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%20%3D%3D%20%5B%5D%0A%0A%20%20%20%20def%20push%28self%2C%20item%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E5%A0%86%E7%96%8A%22%22%22%0A%20%20%20%20%20%20%20%20self._stack.append%28item%29%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E5%A0%86%E7%96%8A%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E5%A0%86%E7%96%8A%E7%82%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack.pop%28%29%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E5%A0%86%E7%96%8A%E7%82%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack%5B-1%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BF%94%E5%9B%9E%E4%B8%B2%E5%88%97%E7%94%A8%E6%96%BC%E5%88%97%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A0%86%E7%96%8A%0A%20%20%20%20stack%20%3D%20ArrayStack%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%96%8A%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%96%8A%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%85%83%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%BE%8C%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%0Aclass%20LinkedListQueue%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E5%AF%A6%E7%8F%BE%E7%9A%84%E4%BD%87%E5%88%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._front%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E9%A0%AD%E7%AF%80%E9%BB%9E%20front%0A%20%20%20%20%20%20%20%20self._rear%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B0%BE%E7%AF%80%E9%BB%9E%20rear%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E4%BD%87%E5%88%97%E7%9A%84%E9%95%B7%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20not%20self._front%0A%0A%20%20%20%20def%20push%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E5%88%97%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E7%AF%80%E9%BB%9E%E5%BE%8C%E6%96%B0%E5%A2%9E%20num%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28num%29%0A%20%20%20%20%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E4%BD%87%E5%88%97%E7%82%BA%E7%A9%BA%EF%BC%8C%E5%89%87%E4%BB%A4%E9%A0%AD%E3%80%81%E5%B0%BE%E7%AF%80%E9%BB%9E%E9%83%BD%E6%8C%87%E5%90%91%E8%A9%B2%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20if%20self._front%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._front%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E4%BD%87%E5%88%97%E4%B8%8D%E7%82%BA%E7%A9%BA%EF%BC%8C%E5%89%87%E5%B0%87%E8%A9%B2%E7%AF%80%E9%BB%9E%E6%96%B0%E5%A2%9E%E5%88%B0%E5%B0%BE%E7%AF%80%E9%BB%9E%E5%BE%8C%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear.next%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E5%88%97%22%22%22%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E9%A0%AD%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20self._front%20%3D%20self._front.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E4%BD%87%E5%88%97%E7%82%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._front.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BD%89%E5%8C%96%E7%82%BA%E4%B8%B2%E5%88%97%E7%94%A8%E6%96%BC%E5%88%97%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20queue%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20temp%20%3D%20self._front%0A%20%20%20%20%20%20%20%20while%20temp%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28temp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20temp.next%0A%20%20%20%20%20%20%20%20return%20queue%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BD%87%E5%88%97%0A%20%20%20%20queue%20%3D%20LinkedListQueue%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%88%97%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%20queue%20%3D%22%2C%20queue.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%88%97%0A%20%20%20%20pop_front%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%88%97%E5%85%83%E7%B4%A0%20pop%20%3D%22%2C%20pop_front%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%88%97%E5%BE%8C%20queue%20%3D%22%2C%20queue.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E4%BD%87%E5%88%97%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md ================================================ https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%0Aclass%20LinkedListStack%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E5%AF%A6%E7%8F%BE%E7%9A%84%E5%A0%86%E7%96%8A%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._peek%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E5%A0%86%E7%96%8A%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20not%20self._peek%0A%0A%20%20%20%20def%20push%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E5%A0%86%E7%96%8A%22%22%22%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28val%29%0A%20%20%20%20%20%20%20%20node.next%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E5%A0%86%E7%96%8A%22%22%22%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20self._peek.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E5%A0%86%E7%96%8A%E7%82%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._peek.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BD%89%E5%8C%96%E7%82%BA%E4%B8%B2%E5%88%97%E7%94%A8%E6%96%BC%E5%88%97%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20arr%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20node%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20while%20node%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20arr.append%28node.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20node%20%3D%20node.next%0A%20%20%20%20%20%20%20%20arr.reverse%28%29%0A%20%20%20%20%20%20%20%20return%20arr%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A0%86%E7%96%8A%0A%20%20%20%20stack%20%3D%20LinkedListStack%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%96%8A%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%96%8A%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%85%83%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%BE%8C%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_tree/array_binary_tree.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20ArrayBinaryTree%3A%0A%20%20%20%20%22%22%22%E9%99%A3%E5%88%97%E8%A1%A8%E7%A4%BA%E4%B8%8B%E7%9A%84%E4%BA%8C%E5%85%83%E6%A8%B9%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20arr%3A%20list%5Bint%20%7C%20None%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._tree%20%3D%20list%28arr%29%0A%0A%20%20%20%20def%20size%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%B8%B2%E5%88%97%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._tree%29%0A%0A%20%20%20%20def%20val%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E7%B4%A2%E5%BC%95%E7%82%BA%20i%20%E7%AF%80%E9%BB%9E%E7%9A%84%E5%80%BC%22%22%22%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20self._tree%5Bi%5D%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E7%88%B6%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%0A%0A%20%20%20%20def%20level_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%B1%A4%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20dfs%28self%2C%20i%3A%20int%2C%20order%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E5%84%AA%E5%85%88%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22pre%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.left%28i%29%2C%20order%29%0A%20%20%20%20%20%20%20%20%23%20%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22in%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.right%28i%29%2C%20order%29%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%8C%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22post%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%0A%20%20%20%20def%20pre_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%22pre%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20in_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%22in%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20post_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BE%8C%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%22post%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%A8%B9%0A%20%20%20%20arr%20%3D%20%5B1%2C%202%2C%203%2C%204%2C%20None%2C%206%2C%20None%5D%0A%20%20%20%20abt%20%3D%20ArrayBinaryTree%28arr%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E7%AF%80%E9%BB%9E%0A%20%20%20%20i%20%3D%201%0A%20%20%20%20l%2C%20r%2C%20p%20%3D%20abt.left%28i%29%2C%20abt.right%28i%29%2C%20abt.parent%28i%29%0A%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%A8%B9%0A%20%20%20%20res%20%3D%20abt.level_order%28%29%0A%20%20%20%20res%20%3D%20abt.pre_order%28%29%0A%20%20%20%20res%20%3D%20abt.in_order%28%29%0A%20%20%20%20res%20%3D%20abt.post_order%28%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_tree/binary_search_tree.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%A9%BA%E6%A8%B9%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20search%28self%2C%20num%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E8%A9%A2%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20cur%20%3D%20self._root%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%9F%A5%E8%A9%A2%EF%BC%8C%E8%B6%8A%E9%81%8E%E8%91%89%E7%AF%80%E9%BB%9E%E5%BE%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E7%AF%80%E9%BB%9E%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A8%B9%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E7%AF%80%E9%BB%9E%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A8%B9%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20cur.val%20%3E%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A8%99%E7%AF%80%E9%BB%9E%EF%BC%8C%E8%B7%B3%E5%87%BA%E8%BF%B4%E5%9C%88%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20return%20cur%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E6%A8%B9%E7%82%BA%E7%A9%BA%EF%BC%8C%E5%89%87%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%9F%A5%E8%A9%A2%EF%BC%8C%E8%B6%8A%E9%81%8E%E8%91%89%E7%AF%80%E9%BB%9E%E5%BE%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E9%87%8D%E8%A4%87%E7%AF%80%E9%BB%9E%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A8%B9%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A8%B9%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E7%AF%80%E9%BB%9E%0A%20%20%20%20node%20%3D%20bst.search%287%29%0A%20%20%20%20print%28%22%5Cn%E6%9F%A5%E8%A9%A2%E5%88%B0%E7%9A%84%E7%AF%80%E9%BB%9E%E7%89%A9%E4%BB%B6%E7%82%BA%3A%20%7B%7D%EF%BC%8C%E7%AF%80%E9%BB%9E%E5%80%BC%20%3D%20%7B%7D%22.format%28node%2C%20node.val%29%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%A9%BA%E6%A8%B9%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E6%A8%B9%E7%82%BA%E7%A9%BA%EF%BC%8C%E5%89%87%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%9F%A5%E8%A9%A2%EF%BC%8C%E8%B6%8A%E9%81%8E%E8%91%89%E7%AF%80%E9%BB%9E%E5%BE%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E9%87%8D%E8%A4%87%E7%AF%80%E9%BB%9E%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A8%B9%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A8%B9%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%0A%20%20%20%20bst.insert%2816%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%20%20%20%20def%20remove%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20if%20cur%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E7%AF%80%E9%BB%9E%E6%95%B8%E9%87%8F%20%3D%200%20or%201%0A%20%20%20%20%20%20%20%20if%20cur.left%20is%20None%20or%20cur.right%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%95%B6%E5%AD%90%E7%AF%80%E9%BB%9E%E6%95%B8%E9%87%8F%20%3D%200%20/%201%20%E6%99%82%EF%BC%8C%20child%20%3D%20null%20/%20%E8%A9%B2%E5%AD%90%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20child%20%3D%20cur.left%20or%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur%20%21%3D%20self._root%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20pre.left%20%3D%3D%20cur%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20child%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E7%AF%80%E9%BB%9E%E6%95%B8%E9%87%8F%20%3D%202%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%E4%B8%AD%20cur%20%E7%9A%84%E4%B8%8B%E4%B8%80%E5%80%8B%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%3A%20TreeNode%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20tmp.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20tmp.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20self.remove%28tmp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%94%A8%20tmp%20%E8%A6%86%E8%93%8B%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20cur.val%20%3D%20tmp.val%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%0A%20%20%20%20bst.remove%281%29%20%23%20%E5%BA%A6%E7%82%BA%200%0A%20%20%20%20bst.remove%282%29%20%23%20%E5%BA%A6%E7%82%BA%201%0A%20%20%20%20bst.remove%284%29%20%23%20%E5%BA%A6%E7%82%BA%202&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_tree/binary_tree_bfs.md ================================================ https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%B0%8D%E6%87%89%E7%9A%84%E5%85%83%E7%B4%A0%E7%82%BA%20None%20%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%95%B6%E5%89%8D%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20level_order%28root%3A%20TreeNode%20%7C%20None%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E5%B1%A4%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BD%87%E5%88%97%EF%BC%8C%E5%8A%A0%E5%85%A5%E6%A0%B9%E7%AF%80%E9%BB%9E%0A%20%20%20%20queue%3A%20deque%5BTreeNode%5D%20%3D%20deque%28%29%0A%20%20%20%20queue.append%28root%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E5%80%8B%E4%B8%B2%E5%88%97%EF%BC%8C%E7%94%A8%E6%96%BC%E5%84%B2%E5%AD%98%E8%B5%B0%E8%A8%AA%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20while%20queue%3A%0A%20%20%20%20%20%20%20%20node%3A%20TreeNode%20%3D%20queue.popleft%28%29%20%20%23%20%E9%9A%8A%E5%88%97%E5%87%BA%E9%9A%8A%0A%20%20%20%20%20%20%20%20res.append%28node.val%29%20%20%23%20%E5%84%B2%E5%AD%98%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20if%20node.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.left%29%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%85%A5%E5%88%97%0A%20%20%20%20%20%20%20%20if%20node.right%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.right%29%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%85%A5%E5%88%97%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%A8%B9%0A%20%20%20%20%23%20%E9%80%99%E8%A3%A1%E8%97%89%E5%8A%A9%E4%BA%86%E4%B8%80%E5%80%8B%E5%BE%9E%E9%99%A3%E5%88%97%E7%9B%B4%E6%8E%A5%E7%94%9F%E6%88%90%E4%BA%8C%E5%85%83%E6%A8%B9%E7%9A%84%E5%87%BD%E5%BC%8F%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%B1%A4%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res%20%3D%20level_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%B1%A4%E5%BA%8F%E8%B5%B0%E8%A8%AA%E7%9A%84%E7%AF%80%E9%BB%9E%E5%88%97%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22%2C%20res%29&cumulative=false&curInstr=127&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/pythontutor/chapter_tree/binary_tree_dfs.md ================================================ https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%B0%8D%E6%87%89%E7%9A%84%E5%85%83%E7%B4%A0%E7%82%BA%20None%20%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%95%B6%E5%89%8D%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%84%AA%E5%85%88%E9%A0%86%E5%BA%8F%EF%BC%9A%E6%A0%B9%E7%AF%80%E9%BB%9E%20-%3E%20%E5%B7%A6%E5%AD%90%E6%A8%B9%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20pre_order%28root%3Droot.left%29%0A%20%20%20%20pre_order%28root%3Droot.right%29%0A%0Adef%20in_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%84%AA%E5%85%88%E9%A0%86%E5%BA%8F%EF%BC%9A%E5%B7%A6%E5%AD%90%E6%A8%B9%20-%3E%20%E6%A0%B9%E7%AF%80%E9%BB%9E%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20in_order%28root%3Droot.left%29%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20in_order%28root%3Droot.right%29%0A%0Adef%20post_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%BE%8C%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%84%AA%E5%85%88%E9%A0%86%E5%BA%8F%EF%BC%9A%E5%B7%A6%E5%AD%90%E6%A8%B9%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A8%B9%20-%3E%20%E6%A0%B9%E7%AF%80%E9%BB%9E%0A%20%20%20%20post_order%28root%3Droot.left%29%0A%20%20%20%20post_order%28root%3Droot.right%29%0A%20%20%20%20res.append%28root.val%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%A8%B9%0A%20%20%20%20%23%20%E9%80%99%E8%A3%A1%E8%97%89%E5%8A%A9%E4%BA%86%E4%B8%80%E5%80%8B%E5%BE%9E%E9%99%A3%E5%88%97%E7%9B%B4%E6%8E%A5%E7%94%9F%E6%88%90%E4%BA%8C%E5%85%83%E6%A8%B9%E7%9A%84%E5%87%BD%E5%BC%8F%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20pre_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%E7%9A%84%E7%AF%80%E9%BB%9E%E5%88%97%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22%2C%20res%29%0A%0A%20%20%20%20%23%20%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20in_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%E7%9A%84%E7%AF%80%E9%BB%9E%E5%88%97%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22%2C%20res%29%0A%0A%20%20%20%20%23%20%E5%BE%8C%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20post_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%BE%8C%E5%BA%8F%E8%B5%B0%E8%A8%AA%E7%9A%84%E7%AF%80%E9%BB%9E%E5%88%97%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22%2C%20res%29&cumulative=false&curInstr=129&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/codes/ruby/chapter_array_and_linkedlist/array.rb ================================================ =begin File: array.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 隨機訪問元素 ### def random_access(nums) # 在區間 [0, nums.length) 中隨機抽取一個數字 random_index = Random.rand(0...nums.length) # 獲取並返回隨機元素 nums[random_index] end ### 擴展陣列長度 ### # 請注意,Ruby 的 Array 是動態陣列,可以直接擴展 # 為了方便學習,本函式將 Array 看作長度不可變的陣列 def extend(nums, enlarge) # 初始化一個擴展長度後的陣列 res = Array.new(nums.length + enlarge, 0) # 將原陣列中的所有元素複製到新陣列 for i in 0...nums.length res[i] = nums[i] end # 返回擴展後的新陣列 res end ### 在陣列的索引 index 處插入元素 num ### def insert(nums, num, index) # 把索引 index 以及之後的所有元素向後移動一位 for i in (nums.length - 1).downto(index + 1) nums[i] = nums[i - 1] end # 將 num 賦給 index 處的元素 nums[index] = num end ### 刪除索引 index 處的元素 ### def remove(nums, index) # 把索引 index 之後的所有元素向前移動一位 for i in index...(nums.length - 1) nums[i] = nums[i + 1] end end ### 走訪陣列 ### def traverse(nums) count = 0 # 透過索引走訪陣列 for i in 0...nums.length count += nums[i] end # 直接走訪陣列元素 for num in nums count += num end end ### 在陣列中查詢指定元素 ### def find(nums, target) for i in 0...nums.length return i if nums[i] == target end -1 end ### Driver Code ### if __FILE__ == $0 # 初始化陣列 arr = Array.new(5, 0) puts "陣列 arr = #{arr}" nums = [1, 3, 2, 5, 4] puts "陣列 nums = #{nums}" # 隨機訪問 random_num = random_access(nums) puts "在 nums 中獲取隨機元素 #{random_num}" # 長度擴展 nums = extend(nums, 3) puts "將陣列長度擴展至 8 ,得到 nums = #{nums}" # 插入元素 insert(nums, 6, 3) puts "在索引 3 處插入數字 6 ,得到 nums = #{nums}" # 刪除元素 remove(nums, 2) puts "刪除索引 2 處的元素,得到 nums = #{nums}" # 走訪陣列 traverse(nums) # 查詢元素 index = find(nums, 3) puts "在 nums 中查詢元素 3 ,得到索引 = #{index}" end ================================================ FILE: zh-hant/codes/ruby/chapter_array_and_linkedlist/linked_list.rb ================================================ =begin File: linked_list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' require_relative '../utils/print_util' ### 在鏈結串列的節點 n0 之後插入節點 _p ### # Ruby 的 `p` 是一個內建函式, `P` 是一個常數,所以可以使用 `_p` 代替 def insert(n0, _p) n1 = n0.next _p.next = n1 n0.next = _p end ### 刪除鏈結串列的節點 n0 之後的首個節點 ### def remove(n0) return if n0.next.nil? # n0 -> remove_node -> n1 remove_node = n0.next n1 = remove_node.next n0.next = n1 end ### 訪問鏈結串列中索引為 index 的節點 ### def access(head, index) for i in 0...index return nil if head.nil? head = head.next end head end ### 在鏈結串列中查詢值為 target 的首個節點 ### def find(head, target) index = 0 while head return index if head.val == target head = head.next index += 1 end -1 end ### Driver Code ### if __FILE__ == $0 # 初始化鏈結串列 # 初始化各個節點 n0 = ListNode.new(1) n1 = ListNode.new(3) n2 = ListNode.new(2) n3 = ListNode.new(5) n4 = ListNode.new(4) # 構建節點之間的引用 n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 puts "初始化的鏈結串列為" print_linked_list(n0) # 插入節點 insert(n0, ListNode.new(0)) print_linked_list n0 # 刪除節點 remove(n0) puts "刪除節點後的鏈結串列為" print_linked_list(n0) # 訪問節點 node = access(n0, 3) puts "鏈結串列中索引 3 處的節點的值 = #{node.val}" # 查詢節點 index = find(n0, 2) puts "鏈結串列中值為 2 的節點的索引 = #{index}" end ================================================ FILE: zh-hant/codes/ruby/chapter_array_and_linkedlist/list.rb ================================================ =begin File: list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # 初始化串列 nums = [1, 3, 2, 5, 4] puts "串列 nums = #{nums}" # 訪問元素 num = nums[1] puts "訪問索引 1 處的元素,得到 num = #{num}" # 更新元素 nums[1] = 0 puts "將索引 1 處的元素更新為 0 ,得到 nums = #{nums}" # 清空串列 nums.clear puts "清空串列後 nums = #{nums}" # 在尾部新增元素 nums << 1 nums << 3 nums << 2 nums << 5 nums << 4 puts "新增元素後 nums = #{nums}" # 在中間插入元素 nums.insert(3, 6) puts "在索引 3 處插入元素 6 ,得到 nums = #{nums}" # 刪除元素 nums.delete_at(3) puts "刪除索引 3 處的元素,得到 nums = #{nums}" # 透過索引走訪串列 count = 0 for i in 0...nums.length count += nums[i] end # 直接走訪串列元素 count = 0 nums.each do |x| count += x end # 拼接兩個串列 nums1 = [6, 8, 7, 10, 9] nums += nums1 puts "將串列 nums1 拼接到 nums 之後,得到 nums = #{nums}" nums = nums.sort { |a, b| a <=> b } puts "排序串列後 nums = #{nums}" end ================================================ FILE: zh-hant/codes/ruby/chapter_array_and_linkedlist/my_list.rb ================================================ =begin File: my_list.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 串列類別 ### class MyList attr_reader :size # 獲取串列長度(當前元素數量) attr_reader :capacity # 獲取串列容量 ### 建構子 ### def initialize @capacity = 10 @size = 0 @extend_ratio = 2 @arr = Array.new(capacity) end ### 訪問元素 ### def get(index) # 索引如果越界,則丟擲異常,下同 raise IndexError, "索引越界" if index < 0 || index >= size @arr[index] end ### 訪問元素 ### def set(index, num) raise IndexError, "索引越界" if index < 0 || index >= size @arr[index] = num end ### 在尾部新增元素 ### def add(num) # 元素數量超出容量時,觸發擴容機制 extend_capacity if size == capacity @arr[size] = num # 更新元素數量 @size += 1 end ### 在中間插入元素 ### def insert(index, num) raise IndexError, "索引越界" if index < 0 || index >= size # 元素數量超出容量時,觸發擴容機制 extend_capacity if size == capacity # 將索引 index 以及之後的元素都向後移動一位 for j in (size - 1).downto(index) @arr[j + 1] = @arr[j] end @arr[index] = num # 更新元素數量 @size += 1 end ### 刪除元素 ### def remove(index) raise IndexError, "索引越界" if index < 0 || index >= size num = @arr[index] # 將將索引 index 之後的元素都向前移動一位 for j in index...size @arr[j] = @arr[j + 1] end # 更新元素數量 @size -= 1 # 返回被刪除的元素 num end ### 串列擴容 ### def extend_capacity # 新建一個長度為原陣列 extend_ratio 倍的新陣列,並將原陣列複製到新陣列 arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1)) # 更新串列容量 @capacity = arr.length end ### 將串列轉換為陣列 ### def to_array sz = size # 僅轉換有效長度範圍內的串列元素 arr = Array.new(sz) for i in 0...sz arr[i] = get(i) end arr end end ### Driver Code ### if __FILE__ == $0 # 初始化串列 nums = MyList.new # 在尾部新增元素 nums.add(1) nums.add(3) nums.add(2) nums.add(5) nums.add(4) puts "串列 nums = #{nums.to_array} ,容量 = #{nums.capacity} ,長度 = #{nums.size}" # 在中間插入元素 nums.insert(3, 6) puts "在索引 3 處插入數字 6 ,得到 nums = #{nums.to_array}" # 刪除元素 nums.remove(3) puts "刪除索引 3 的元素,得到 nums = #{nums.to_array}" # 訪問元素 num = nums.get(1) puts "訪問索引 1 處的元素,得到 num = #{num}" # 更新元素 nums.set(1, 0) puts "將索引 1 處的元素更新為 0 ,得到 nums = #{nums.to_array}" # 測試擴容機制 for i in 0...10 # 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 nums.add(i) end puts "擴容後的串列 nums = #{nums.to_array} ,容量 = #{nums.capacity} ,長度 = #{nums.size}" end ================================================ FILE: zh-hant/codes/ruby/chapter_backtracking/n_queens.rb ================================================ =begin File: n_queens.rb Created Time: 2024-05-21 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯演算法:n 皇后 ### def backtrack(row, n, state, res, cols, diags1, diags2) # 當放置完所有行時,記錄解 if row == n res << state.map { |row| row.dup } return end # 走訪所有列 for col in 0...n # 計算該格子對應的主對角線和次對角線 diag1 = row - col + n - 1 diag2 = row + col # 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 if !cols[col] && !diags1[diag1] && !diags2[diag2] # 嘗試:將皇后放置在該格子 state[row][col] = "Q" cols[col] = diags1[diag1] = diags2[diag2] = true # 放置下一行 backtrack(row + 1, n, state, res, cols, diags1, diags2) # 回退:將該格子恢復為空位 state[row][col] = "#" cols[col] = diags1[diag1] = diags2[diag2] = false end end end ### 求解 n 皇后 ### def n_queens(n) # 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 state = Array.new(n) { Array.new(n, "#") } cols = Array.new(n, false) # 記錄列是否有皇后 diags1 = Array.new(2 * n - 1, false) # 記錄主對角線上是否有皇后 diags2 = Array.new(2 * n - 1, false) # 記錄次對角線上是否有皇后 res = [] backtrack(0, n, state, res, cols, diags1, diags2) res end ### Driver Code ### if __FILE__ == $0 n = 4 res = n_queens(n) puts "輸入棋盤長寬為 #{n}" puts "皇后放置方案共有 #{res.length} 種" for state in res puts "--------------------" for row in state p row end end end ================================================ FILE: zh-hant/codes/ruby/chapter_backtracking/permutations_i.rb ================================================ =begin File: permutations_i.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯演算法:全排列 I ### def backtrack(state, choices, selected, res) # 當狀態長度等於元素數量時,記錄解 if state.length == choices.length res << state.dup return end # 走訪所有選擇 choices.each_with_index do |choice, i| # 剪枝:不允許重複選擇元素 unless selected[i] # 嘗試:做出選擇,更新狀態 selected[i] = true state << choice # 進行下一輪選擇 backtrack(state, choices, selected, res) # 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false state.pop end end end ### 全排列 I ### def permutations_i(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ### Driver Code ### if __FILE__ == $0 nums = [1, 2, 3] res = permutations_i(nums) puts "輸入陣列 nums = #{nums}" puts "所有排列 res = #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_backtracking/permutations_ii.rb ================================================ =begin File: permutations_ii.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯演算法:全排列 II ### def backtrack(state, choices, selected, res) # 當狀態長度等於元素數量時,記錄解 if state.length == choices.length res << state.dup return end # 走訪所有選擇 duplicated = Set.new choices.each_with_index do |choice, i| # 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 if !selected[i] && !duplicated.include?(choice) # 嘗試:做出選擇,更新狀態 duplicated.add(choice) selected[i] = true state << choice # 進行下一輪選擇 backtrack(state, choices, selected, res) # 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false state.pop end end end ### 全排列 II ### def permutations_ii(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ### Driver Code ### if __FILE__ == $0 nums = [1, 2, 2] res = permutations_ii(nums) puts "輸入陣列 nums = #{nums}" puts "所有排列 res = #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb ================================================ =begin File: preorder_traversal_i_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 前序走訪:例題一 ### def pre_order(root) return unless root # 記錄解 $res << root if root.val == 7 pre_order(root.left) pre_order(root.right) end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\n初始化二元樹" print_tree(root) # 前序走訪 $res = [] pre_order(root) puts "\n輸出所有值為 7 的節點" p $res.map { |node| node.val } end ================================================ FILE: zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb ================================================ =begin File: preorder_traversal_ii_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 前序走訪:例題二 ### def pre_order(root) return unless root # 嘗試 $path << root # 記錄解 $res << $path.dup if root.val == 7 pre_order(root.left) pre_order(root.right) # 回退 $path.pop end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\n初始化二元樹" print_tree(root) # 前序走訪 $path, $res = [], [] pre_order(root) puts "\n輸出所有根節點到節點 7 的路徑" for path in $res p path.map { |node| node.val } end end ================================================ FILE: zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb ================================================ =begin File: preorder_traversal_iii_compact.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 前序走訪:例題三 ### def pre_order(root) # 剪枝 return if !root || root.val == 3 # 嘗試 $path.append(root) # 記錄解 $res << $path.dup if root.val == 7 pre_order(root.left) pre_order(root.right) # 回退 $path.pop end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\n初始化二元樹" print_tree(root) # 前序走訪 $path, $res = [], [] pre_order(root) puts "\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點" for path in $res p path.map { |node| node.val } end end ================================================ FILE: zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb ================================================ =begin File: preorder_traversal_iii_template.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 判斷當前狀態是否為解 ### def is_solution?(state) !state.empty? && state.last.val == 7 end ### 記錄解 ### def record_solution(state, res) res << state.dup end ### 判斷在當前狀態下,該選擇是否合法 ### def is_valid?(state, choice) choice && choice.val != 3 end ### 更新狀態 ### def make_choice(state, choice) state << choice end ### 恢復狀態 ### def undo_choice(state, choice) state.pop end ### 回溯演算法:例題三 ### def backtrack(state, choices, res) # 檢查是否為解 record_solution(state, res) if is_solution?(state) # 走訪所有選擇 for choice in choices # 剪枝:檢查選擇是否合法 if is_valid?(state, choice) # 嘗試:做出選擇,更新狀態 make_choice(state, choice) # 進行下一輪選擇 backtrack(state, [choice.left, choice.right], res) # 回退:撤銷選擇,恢復到之前的狀態 undo_choice(state, choice) end end end ### Driver Code ### if __FILE__ == $0 root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) puts "\n初始化二元樹" print_tree(root) # 回溯演算法 res = [] backtrack([], [root], res) puts "\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點" for path in res p path.map { |node| node.val } end end ================================================ FILE: zh-hant/codes/ruby/chapter_backtracking/subset_sum_i.rb ================================================ =begin File: subset_sum_i.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯演算法:子集和 I ### def backtrack(state, target, choices, start, res) # 子集和等於 target 時,記錄解 if target.zero? res << state.dup return end # 走訪所有選擇 # 剪枝二:從 start 開始走訪,避免生成重複子集 for i in start...choices.length # 剪枝一:若子集和超過 target ,則直接結束迴圈 # 這是因為陣列已排序,後邊元素更大,子集和一定超過 target break if target - choices[i] < 0 # 嘗試:做出選擇,更新 target, start state << choices[i] # 進行下一輪選擇 backtrack(state, target - choices[i], choices, i, res) # 回退:撤銷選擇,恢復到之前的狀態 state.pop end end ### 求解子集和 I ### def subset_sum_i(nums, target) state = [] # 狀態(子集) nums.sort! # 對 nums 進行排序 start = 0 # 走訪起始點 res = [] # 結果串列(子集串列) backtrack(state, target, nums, start, res) res end ### Driver Code ### if __FILE__ == $0 nums = [3, 4, 5] target = 9 res = subset_sum_i(nums, target) puts "輸入陣列 = #{nums}, target = #{target}" puts "所有和等於 #{target} 的子集 res = #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb ================================================ =begin File: subset_sum_i_naive.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯演算法:子集和 I ### def backtrack(state, target, total, choices, res) # 子集和等於 target 時,記錄解 if total == target res << state.dup return end # 走訪所有選擇 for i in 0...choices.length # 剪枝:若子集和超過 target ,則跳過該選擇 next if total + choices[i] > target # 嘗試:做出選擇,更新元素和 total state << choices[i] # 進行下一輪選擇 backtrack(state, target, total + choices[i], choices, res) # 回退:撤銷選擇,恢復到之前的狀態 state.pop end end ### 求解子集和 I(包含重複子集)### def subset_sum_i_naive(nums, target) state = [] # 狀態(子集) total = 0 # 子集和 res = [] # 結果串列(子集串列) backtrack(state, target, total, nums, res) res end ### Driver Code ### if __FILE__ == $0 nums = [3, 4, 5] target = 9 res = subset_sum_i_naive(nums, target) puts "輸入陣列 nums = #{nums}, target = #{target}" puts "所有和等於 #{target} 的子集 res = #{res}" puts "請注意,該方法輸出的結果包含重複集合" end ================================================ FILE: zh-hant/codes/ruby/chapter_backtracking/subset_sum_ii.rb ================================================ =begin File: subset_sum_ii.rb Created Time: 2024-05-22 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯演算法:子集和 II ### def backtrack(state, target, choices, start, res) # 子集和等於 target 時,記錄解 if target.zero? res << state.dup return end # 走訪所有選擇 # 剪枝二:從 start 開始走訪,避免生成重複子集 # 剪枝三:從 start 開始走訪,避免重複選擇同一元素 for i in start...choices.length # 剪枝一:若子集和超過 target ,則直接結束迴圈 # 這是因為陣列已排序,後邊元素更大,子集和一定超過 target break if target - choices[i] < 0 # 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 next if i > start && choices[i] == choices[i - 1] # 嘗試:做出選擇,更新 target, start state << choices[i] # 進行下一輪選擇 backtrack(state, target - choices[i], choices, i + 1, res) # 回退:撤銷選擇,恢復到之前的狀態 state.pop end end ### 求解子集和 II ### def subset_sum_ii(nums, target) state = [] # 狀態(子集) nums.sort! # 對 nums 進行排序 start = 0 # 走訪起始點 res = [] # 結果串列(子集串列) backtrack(state, target, nums, start, res) res end ### Driver Code ### if __FILE__ == $0 nums = [4, 4, 5] target = 9 res = subset_sum_ii(nums, target) puts "輸入陣列 nums = #{nums}, target = #{target}" puts "所有和等於 #{target} 的子集 res = #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_computational_complexity/iteration.rb ================================================ =begin File: iteration.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com), Cy (9738314@gmail.com) =end ### for 迴圈 ### def for_loop(n) res = 0 # 迴圈求和 1, 2, ..., n-1, n for i in 1..n res += i end res end ### while 迴圈 ### def while_loop(n) res = 0 i = 1 # 初始化條件變數 # 迴圈求和 1, 2, ..., n-1, n while i <= n res += i i += 1 # 更新條件變數 end res end ### while 迴圈(兩次更新)### def while_loop_ii(n) res = 0 i = 1 # 初始化條件變數 # 迴圈求和 1, 4, 10, ... while i <= n res += i # 更新條件變數 i += 1 i *= 2 end res end ### 雙層 for 迴圈 ### def nested_for_loop(n) res = "" # 迴圈 i = 1, 2, ..., n-1, n for i in 1..n # 迴圈 j = 1, 2, ..., n-1, n for j in 1..n res += "(#{i}, #{j}), " end end res end ### Driver Code ### if __FILE__ == $0 n = 5 res = for_loop(n) puts "\nfor 迴圈的求和結果 res = #{res}" res = while_loop(n) puts "\nwhile 迴圈的求和結果 res = #{res}" res = while_loop_ii(n) puts "\nwhile 迴圈(兩次更新)求和結果 res = #{res}" res = nested_for_loop(n) puts "\n雙層 for 迴圈的走訪結果 #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_computational_complexity/recursion.rb ================================================ =begin File: recursion.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 遞迴 ### def recur(n) # 終止條件 return 1 if n == 1 # 遞:遞迴呼叫 res = recur(n - 1) # 迴:返回結果 n + res end ### 使用迭代模擬遞迴 ### def for_loop_recur(n) # 使用一個顯式的堆疊來模擬系統呼叫堆疊 stack = [] res = 0 # 遞:遞迴呼叫 for i in n.downto(0) # 透過“入堆疊操作”模擬“遞” stack << i end # 迴:返回結果 while !stack.empty? res += stack.pop end # res = 1+2+3+...+n res end ### 尾遞迴 ### def tail_recur(n, res) # 終止條件 return res if n == 0 # 尾遞迴呼叫 tail_recur(n - 1, res + n) end ### 費波那契數列:遞迴 ### def fib(n) # 終止條件 f(1) = 0, f(2) = 1 return n - 1 if n == 1 || n == 2 # 遞迴呼叫 f(n) = f(n-1) + f(n-2) res = fib(n - 1) + fib(n - 2) # 返回結果 f(n) res end ### Driver Code ### if __FILE__ == $0 n = 5 res = recur(n) puts "\n遞迴函式的求和結果 res = #{res}" res = for_loop_recur(n) puts "\n使用迭代模擬遞迴求和結果 res = #{res}" res = tail_recur(n, 0) puts "\n尾遞迴函式的求和結果 res = #{res}" res = fib(n) puts "\n費波那契數列的第 #{n} 項為 #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_computational_complexity/space_complexity.rb ================================================ =begin File: space_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 函式 ### def function # 執行某些操作 0 end ### 常數階 ### def constant(n) # 常數、變數、物件佔用 O(1) 空間 a = 0 nums = [0] * 10000 node = ListNode.new # 迴圈中的變數佔用 O(1) 空間 (0...n).each { c = 0 } # 迴圈中的函式佔用 O(1) 空間 (0...n).each { function } end ### 線性階 ### def linear(n) # 長度為 n 的串列佔用 O(n) 空間 nums = Array.new(n, 0) # 長度為 n 的雜湊表佔用 O(n) 空間 hmap = {} for i in 0...n hmap[i] = i.to_s end end ### 線性階(遞迴實現)### def linear_recur(n) puts "遞迴 n = #{n}" return if n == 1 linear_recur(n - 1) end ### 平方階 ### def quadratic(n) # 二維串列佔用 O(n^2) 空間 Array.new(n) { Array.new(n, 0) } end ### 平方階(遞迴實現)### def quadratic_recur(n) return 0 unless n > 0 # 陣列 nums 長度為 n, n-1, ..., 2, 1 nums = Array.new(n, 0) quadratic_recur(n - 1) end ### 指數階(建立滿二元樹)### def build_tree(n) return if n == 0 TreeNode.new.tap do |root| root.left = build_tree(n - 1) root.right = build_tree(n - 1) end end ### Driver Code ### if __FILE__ == $0 n = 5 # 常數階 constant(n) # 線性階 linear(n) linear_recur(n) # 平方階 quadratic(n) quadratic_recur(n) # 指數階 root = build_tree(n) print_tree(root) end ================================================ FILE: zh-hant/codes/ruby/chapter_computational_complexity/time_complexity.rb ================================================ =begin File: time_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 常數階 ### def constant(n) count = 0 size = 100000 (0...size).each { count += 1 } count end ### 線性階 ### def linear(n) count = 0 (0...n).each { count += 1 } count end ### 線性階(走訪陣列)### def array_traversal(nums) count = 0 # 迴圈次數與陣列長度成正比 for num in nums count += 1 end count end ### 平方階 ### def quadratic(n) count = 0 # 迴圈次數與資料大小 n 成平方關係 for i in 0...n for j in 0...n count += 1 end end count end ### 平方階(泡沫排序)### def bubble_sort(nums) count = 0 # 計數器 # 外迴圈:未排序區間為 [0, i] for i in (nums.length - 1).downto(0) # 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for j in 0...i if nums[j] > nums[j + 1] # 交換 nums[j] 與 nums[j + 1] tmp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = tmp count += 3 # 元素交換包含 3 個單元操作 end end end count end ### 指數階(迴圈實現)### def exponential(n) count, base = 0, 1 # 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) (0...n).each do (0...base).each { count += 1 } base *= 2 end # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 count end ### 指數階(遞迴實現)### def exp_recur(n) return 1 if n == 1 exp_recur(n - 1) + exp_recur(n - 1) + 1 end ### 對數階(迴圈實現)### def logarithmic(n) count = 0 while n > 1 n /= 2 count += 1 end count end ### 對數階(遞迴實現)### def log_recur(n) return 0 unless n > 1 log_recur(n / 2) + 1 end ### 線性對數階 ### def linear_log_recur(n) return 1 unless n > 1 count = linear_log_recur(n / 2) + linear_log_recur(n / 2) (0...n).each { count += 1 } count end ### 階乘階(遞迴實現)### def factorial_recur(n) return 1 if n == 0 count = 0 # 從 1 個分裂出 n 個 (0...n).each { count += factorial_recur(n - 1) } count end ### Driver Code ### if __FILE__ == $0 # 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 n = 8 puts "輸入資料大小 n = #{n}" count = constant(n) puts "常數階的操作數量 = #{count}" count = linear(n) puts "線性階的操作數量 = #{count}" count = array_traversal(Array.new(n, 0)) puts "線性階(走訪陣列)的操作數量 = #{count}" count = quadratic(n) puts "平方階的操作數量 = #{count}" nums = Array.new(n) { |i| n - i } # [n, n-1, ..., 2, 1] count = bubble_sort(nums) puts "平方階(泡沫排序)的操作數量 = #{count}" count = exponential(n) puts "指數階(迴圈實現)的操作數量 = #{count}" count = exp_recur(n) puts "指數階(遞迴實現)的操作數量 = #{count}" count = logarithmic(n) puts "對數階(迴圈實現)的操作數量 = #{count}" count = log_recur(n) puts "對數階(遞迴實現)的操作數量 = #{count}" count = linear_log_recur(n) puts "線性對數階(遞迴實現)的操作數量 = #{count}" count = factorial_recur(n) puts "階乘階(遞迴實現)的操作數量 = #{count}" end ================================================ FILE: zh-hant/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb ================================================ =begin File: worst_best_time_complexity.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 生成一個陣列,元素為: 1, 2, ..., n ,順序被打亂 ### def random_numbers(n) # 生成陣列 nums =: 1, 2, 3, ..., n nums = Array.new(n) { |i| i + 1 } # 隨機打亂陣列元素 nums.shuffle! end ### 查詢陣列 nums 中數字 1 所在索引 ### def find_one(nums) for i in 0...nums.length # 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) # 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) return i if nums[i] == 1 end -1 end ### Driver Code ### if __FILE__ == $0 for i in 0...10 n = 100 nums = random_numbers(n) index = find_one(nums) puts "\n陣列 [ 1, 2, ..., n ] 被打亂後 = #{nums}" puts "數字 1 的索引為 #{index}" end end ================================================ FILE: zh-hant/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb ================================================ =begin File: binary_search_recur.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 二分搜尋:問題 f(i, j) ### def dfs(nums, target, i, j) # 若區間為空,代表無目標元素,則返回 -1 return -1 if i > j # 計算中點索引 m m = (i + j) / 2 if nums[m] < target # 遞迴子問題 f(m+1, j) return dfs(nums, target, m + 1, j) elsif nums[m] > target # 遞迴子問題 f(i, m-1) return dfs(nums, target, i, m - 1) else # 找到目標元素,返回其索引 return m end end ### 二分搜尋 ### def binary_search(nums, target) n = nums.length # 求解問題 f(0, n-1) dfs(nums, target, 0, n - 1) end ### Driver Code ### if __FILE__ == $0 target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # 二分搜尋(雙閉區間) index = binary_search(nums, target) puts "目標元素 6 的索引 = #{index}" end ================================================ FILE: zh-hant/codes/ruby/chapter_divide_and_conquer/build_tree.rb ================================================ =begin File: build_tree.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 構建二元樹:分治 ### def dfs(preorder, inorder_map, i, l, r) # 子樹區間為空時終止 return if r - l < 0 # 初始化根節點 root = TreeNode.new(preorder[i]) # 查詢 m ,從而劃分左右子樹 m = inorder_map[preorder[i]] # 子問題:構建左子樹 root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) # 子問題:構建右子樹 root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) # 返回根節點 root end ### 構建二元樹 ### def build_tree(preorder, inorder) # 初始化雜湊表,儲存 inorder 元素到索引的對映 inorder_map = {} inorder.each_with_index { |val, i| inorder_map[val] = i } dfs(preorder, inorder_map, 0, 0, inorder.length - 1) end ### Driver Code ### if __FILE__ == $0 preorder = [3, 9, 2, 1, 7] inorder = [9, 3, 1, 2, 7] puts "前序走訪 = #{preorder}" puts "中序走訪 = #{inorder}" root = build_tree(preorder, inorder) puts "構建的二元樹為:" print_tree(root) end ================================================ FILE: zh-hant/codes/ruby/chapter_divide_and_conquer/hanota.rb ================================================ =begin File: hanota.rb Created Time: 2024-05-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 移動一個圓盤 ### def move(src, tar) # 從 src 頂部拿出一個圓盤 pan = src.pop # 將圓盤放入 tar 頂部 tar << pan end ### 求解河內塔問題 f(i) ### def dfs(i, src, buf, tar) # 若 src 只剩下一個圓盤,則直接將其移到 tar if i == 1 move(src, tar) return end # 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf dfs(i - 1, src, tar, buf) # 子問題 f(1) :將 src 剩餘一個圓盤移到 tar move(src, tar) # 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar dfs(i - 1, buf, src, tar) end ### 求解河內塔問題 ### def solve_hanota(_A, _B, _C) n = _A.length # 將 A 頂部 n 個圓盤藉助 B 移到 C dfs(n, _A, _B, _C) end ### Driver Code ### if __FILE__ == $0 # 串列尾部是柱子頂部 A = [5, 4, 3, 2, 1] B = [] C = [] puts "初始狀態下:" puts "A = #{A}" puts "B = #{B}" puts "C = #{C}" solve_hanota(A, B, C) puts "圓盤移動完成後:" puts "A = #{A}" puts "B = #{B}" puts "C = #{C}" end ================================================ FILE: zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb ================================================ =begin File: climbing_stairs_backtrack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 回溯 ### def backtrack(choices, state, n, res) # 當爬到第 n 階時,方案數量加 1 res[0] += 1 if state == n # 走訪所有選擇 for choice in choices # 剪枝:不允許越過第 n 階 next if state + choice > n # 嘗試:做出選擇,更新狀態 backtrack(choices, state + choice, n, res) end # 回退 end ### 爬樓梯:回溯 ### def climbing_stairs_backtrack(n) choices = [1, 2] # 可選擇向上爬 1 階或 2 階 state = 0 # 從第 0 階開始爬 res = [0] # 使用 res[0] 記錄方案數量 backtrack(choices, state, n, res) res.first end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_backtrack(n) puts "爬 #{n} 階樓梯共有 #{res} 種方案" end ================================================ FILE: zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb ================================================ =begin File: climbing_stairs_constraint_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 帶約束爬樓梯:動態規劃 ### def climbing_stairs_constraint_dp(n) return 1 if n == 1 || n == 2 # 初始化 dp 表,用於儲存子問題的解 dp = Array.new(n + 1) { Array.new(3, 0) } # 初始狀態:預設最小子問題的解 dp[1][1], dp[1][2] = 1, 0 dp[2][1], dp[2][2] = 0, 1 # 狀態轉移:從較小子問題逐步求解較大子問題 for i in 3...(n + 1) dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] end dp[n][1] + dp[n][2] end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_constraint_dp(n) puts "爬 #{n} 階樓梯共有 #{res} 種方案" end ================================================ FILE: zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb ================================================ =begin File: climbing_stairs_dfs.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 搜尋 ### def dfs(i) # 已知 dp[1] 和 dp[2] ,返回之 return i if i == 1 || i == 2 # dp[i] = dp[i-1] + dp[i-2] dfs(i - 1) + dfs(i - 2) end ### 爬樓梯:搜尋 ### def climbing_stairs_dfs(n) dfs(n) end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dfs(n) puts "爬 #{n} 階樓梯共有 #{res} 種方案" end ================================================ FILE: zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb ================================================ =begin File: climbing_stairs_dfs_mem.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 記憶化搜尋 ### def dfs(i, mem) # 已知 dp[1] 和 dp[2] ,返回之 return i if i == 1 || i == 2 # 若存在記錄 dp[i] ,則直接返回之 return mem[i] if mem[i] != -1 # dp[i] = dp[i-1] + dp[i-2] count = dfs(i - 1, mem) + dfs(i - 2, mem) # 記錄 dp[i] mem[i] = count end ### 爬樓梯:記憶化搜尋 ### def climbing_stairs_dfs_mem(n) # mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 mem = Array.new(n + 1, -1) dfs(n, mem) end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dfs_mem(n) puts "爬 #{n} 階樓梯共有 #{res} 種方案" end ================================================ FILE: zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb ================================================ =begin File: climbing_stairs_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 爬樓梯:動態規劃 ### def climbing_stairs_dp(n) return n if n == 1 || n == 2 # 初始化 dp 表,用於儲存子問題的解 dp = Array.new(n + 1, 0) # 初始狀態:預設最小子問題的解 dp[1], dp[2] = 1, 2 # 狀態轉移:從較小子問題逐步求解較大子問題 (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] } dp[n] end ### 爬樓梯:空間最佳化後的動態規劃 ### def climbing_stairs_dp_comp(n) return n if n == 1 || n == 2 a, b = 1, 2 (3...(n + 1)).each { a, b = b, a + b } b end ### Driver Code ### if __FILE__ == $0 n = 9 res = climbing_stairs_dp(n) puts "爬 #{n} 階樓梯共有 #{res} 種方案" res = climbing_stairs_dp_comp(n) puts "爬 #{n} 階樓梯共有 #{res} 種方案" end ================================================ FILE: zh-hant/codes/ruby/chapter_dynamic_programming/coin_change.rb ================================================ =begin File: coin_change.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 零錢兌換:動態規劃 ### def coin_change_dp(coins, amt) n = coins.length _MAX = amt + 1 # 初始化 dp 表 dp = Array.new(n + 1) { Array.new(amt + 1, 0) } # 狀態轉移:首行首列 (1...(amt + 1)).each { |a| dp[0][a] = _MAX } # 狀態轉移:其餘行和列 for i in 1...(n + 1) for a in 1...(amt + 1) if coins[i - 1] > a # 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a] else # 不選和選硬幣 i 這兩種方案的較小值 dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min end end end dp[n][amt] != _MAX ? dp[n][amt] : -1 end ### 零錢兌換:空間最佳化後的動態規劃 ### def coin_change_dp_comp(coins, amt) n = coins.length _MAX = amt + 1 # 初始化 dp 表 dp = Array.new(amt + 1, _MAX) dp[0] = 0 # 狀態轉移 for i in 1...(n + 1) # 正序走訪 for a in 1...(amt + 1) if coins[i - 1] > a # 若超過目標金額,則不選硬幣 i dp[a] = dp[a] else # 不選和選硬幣 i 這兩種方案的較小值 dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min end end end dp[amt] != _MAX ? dp[amt] : -1 end ### Driver Code ### if __FILE__ == $0 coins = [1, 2, 5] amt = 4 # 動態規劃 res = coin_change_dp(coins, amt) puts "湊到目標金額所需的最少硬幣數量為 #{res}" # 空間最佳化後的動態規劃 res = coin_change_dp_comp(coins, amt) puts "湊到目標金額所需的最少硬幣數量為 #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb ================================================ =begin File: coin_change_ii.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 零錢兌換 II:動態規劃 ### def coin_change_ii_dp(coins, amt) n = coins.length # 初始化 dp 表 dp = Array.new(n + 1) { Array.new(amt + 1, 0) } # 初始化首列 (0...(n + 1)).each { |i| dp[i][0] = 1 } # 狀態轉移 for i in 1...(n + 1) for a in 1...(amt + 1) if coins[i - 1] > a # 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a] else # 不選和選硬幣 i 這兩種方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] end end end dp[n][amt] end ### 零錢兌換 II:空間最佳化後的動態規劃 ### def coin_change_ii_dp_comp(coins, amt) n = coins.length # 初始化 dp 表 dp = Array.new(amt + 1, 0) dp[0] = 1 # 狀態轉移 for i in 1...(n + 1) # 正序走訪 for a in 1...(amt + 1) if coins[i - 1] > a # 若超過目標金額,則不選硬幣 i dp[a] = dp[a] else # 不選和選硬幣 i 這兩種方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]] end end end dp[amt] end ### Driver Code ### if __FILE__ == $0 coins = [1, 2, 5] amt = 5 # 動態規劃 res = coin_change_ii_dp(coins, amt) puts "湊出目標金額的硬幣組合數量為 #{res}" # 空間最佳化後的動態規劃 res = coin_change_ii_dp_comp(coins, amt) puts "湊出目標金額的硬幣組合數量為 #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_dynamic_programming/edit_distance.rb ================================================ =begin File: edit_distance.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 編輯距離:暴力搜尋 ### def edit_distance_dfs(s, t, i, j) # 若 s 和 t 都為空,則返回 0 return 0 if i == 0 && j == 0 # 若 s 為空,則返回 t 長度 return j if i == 0 # 若 t 為空,則返回 s 長度 return i if j == 0 # 若兩字元相等,則直接跳過此兩字元 return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1] # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 insert = edit_distance_dfs(s, t, i, j - 1) delete = edit_distance_dfs(s, t, i - 1, j) replace = edit_distance_dfs(s, t, i - 1, j - 1) # 返回最少編輯步數 [insert, delete, replace].min + 1 end def edit_distance_dfs_mem(s, t, mem, i, j) # 若 s 和 t 都為空,則返回 0 return 0 if i == 0 && j == 0 # 若 s 為空,則返回 t 長度 return j if i == 0 # 若 t 為空,則返回 s 長度 return i if j == 0 # 若已有記錄,則直接返回之 return mem[i][j] if mem[i][j] != -1 # 若兩字元相等,則直接跳過此兩字元 return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1] # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) # 記錄並返回最少編輯步數 mem[i][j] = [insert, delete, replace].min + 1 end ### 編輯距離:動態規劃 ### def edit_distance_dp(s, t) n, m = s.length, t.length dp = Array.new(n + 1) { Array.new(m + 1, 0) } # 狀態轉移:首行首列 (1...(n + 1)).each { |i| dp[i][0] = i } (1...(m + 1)).each { |j| dp[0][j] = j } # 狀態轉移:其餘行和列 for i in 1...(n + 1) for j in 1...(m +1) if s[i - 1] == t[j - 1] # 若兩字元相等,則直接跳過此兩字元 dp[i][j] = dp[i - 1][j - 1] else # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1 end end end dp[n][m] end ### 編輯距離:空間最佳化後的動態規劃 ### def edit_distance_dp_comp(s, t) n, m = s.length, t.length dp = Array.new(m + 1, 0) # 狀態轉移:首行 (1...(m + 1)).each { |j| dp[j] = j } # 狀態轉移:其餘行 for i in 1...(n + 1) # 狀態轉移:首列 leftup = dp.first # 暫存 dp[i-1, j-1] dp[0] += 1 # 狀態轉移:其餘列 for j in 1...(m + 1) temp = dp[j] if s[i - 1] == t[j - 1] # 若兩字元相等,則直接跳過此兩字元 dp[j] = leftup else # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[j] = [dp[j - 1], dp[j], leftup].min + 1 end leftup = temp # 更新為下一輪的 dp[i-1, j-1] end end dp[m] end ### Driver Code ### if __FILE__ == $0 s = 'bag' t = 'pack' n, m = s.length, t.length # 暴力搜尋 res = edit_distance_dfs(s, t, n, m) puts "將 #{s} 更改為 #{t} 最少需要編輯 #{res} 步" # 記憶化搜尋 mem = Array.new(n + 1) { Array.new(m + 1, -1) } res = edit_distance_dfs_mem(s, t, mem, n, m) puts "將 #{s} 更改為 #{t} 最少需要編輯 #{res} 步" # 動態規劃 res = edit_distance_dp(s, t) puts "將 #{s} 更改為 #{t} 最少需要編輯 #{res} 步" # 空間最佳化後的動態規劃 res = edit_distance_dp_comp(s, t) puts "將 #{s} 更改為 #{t} 最少需要編輯 #{res} 步" end ================================================ FILE: zh-hant/codes/ruby/chapter_dynamic_programming/knapsack.rb ================================================ =begin File: knapsack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 0-1 背包:暴力搜尋 ### def knapsack_dfs(wgt, val, i, c) # 若已選完所有物品或背包無剩餘容量,則返回價值 0 return 0 if i == 0 || c == 0 # 若超過背包容量,則只能選擇不放入背包 return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c # 計算不放入和放入物品 i 的最大價值 no = knapsack_dfs(wgt, val, i - 1, c) yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] # 返回兩種方案中價值更大的那一個 [no, yes].max end ### 0-1 背包:記憶化搜尋 ### def knapsack_dfs_mem(wgt, val, mem, i, c) # 若已選完所有物品或背包無剩餘容量,則返回價值 0 return 0 if i == 0 || c == 0 # 若已有記錄,則直接返回 return mem[i][c] if mem[i][c] != -1 # 若超過背包容量,則只能選擇不放入背包 return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c # 計算不放入和放入物品 i 的最大價值 no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] # 記錄並返回兩種方案中價值更大的那一個 mem[i][c] = [no, yes].max end ### 0-1 背包:動態規劃 ### def knapsack_dp(wgt, val, cap) n = wgt.length # 初始化 dp 表 dp = Array.new(n + 1) { Array.new(cap + 1, 0) } # 狀態轉移 for i in 1...(n + 1) for c in 1...(cap + 1) if wgt[i - 1] > c # 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c] else # 不選和選物品 i 這兩種方案的較大值 dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max end end end dp[n][cap] end ### 0-1 背包:空間最佳化後的動態規劃 ### def knapsack_dp_comp(wgt, val, cap) n = wgt.length # 初始化 dp 表 dp = Array.new(cap + 1, 0) # 狀態轉移 for i in 1...(n + 1) # 倒序走訪 for c in cap.downto(1) if wgt[i - 1] > c # 若超過背包容量,則不選物品 i dp[c] = dp[c] else # 不選和選物品 i 這兩種方案的較大值 dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max end end end dp[cap] end ### Driver Code ### if __FILE__ == $0 wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = wgt.length # 暴力搜尋 res = knapsack_dfs(wgt, val, n, cap) puts "不超過背包容量的最大物品價值為 #{res}" # 記憶化搜尋 mem = Array.new(n + 1) { Array.new(cap + 1, -1) } res = knapsack_dfs_mem(wgt, val, mem, n, cap) puts "不超過背包容量的最大物品價值為 #{res}" # 動態規劃 res = knapsack_dp(wgt, val, cap) puts "不超過背包容量的最大物品價值為 #{res}" # 空間最佳化後的動態規劃 res = knapsack_dp_comp(wgt, val, cap) puts "不超過背包容量的最大物品價值為 #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb ================================================ =begin File: min_cost_climbing_stairs_dp.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 爬樓梯最小代價:動態規劃 ### def min_cost_climbing_stairs_dp(cost) n = cost.length - 1 return cost[n] if n == 1 || n == 2 # 初始化 dp 表,用於儲存子問題的解 dp = Array.new(n + 1, 0) # 初始狀態:預設最小子問題的解 dp[1], dp[2] = cost[1], cost[2] # 狀態轉移:從較小子問題逐步求解較大子問題 (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] } dp[n] end # 爬樓梯最小代價:空間最佳化後的動態規劃 def min_cost_climbing_stairs_dp_comp(cost) n = cost.length - 1 return cost[n] if n == 1 || n == 2 a, b = cost[1], cost[2] (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] } b end ### Driver Code ### if __FILE__ == $0 cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] puts "輸入樓梯的代價串列為 #{cost}" res = min_cost_climbing_stairs_dp(cost) puts "爬完樓梯的最低代價為 #{res}" res = min_cost_climbing_stairs_dp_comp(cost) puts "爬完樓梯的最低代價為 #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_dynamic_programming/min_path_sum.rb ================================================ =begin File: min_path_sum.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 最小路徑和:暴力搜尋 ### def min_path_sum_dfs(grid, i, j) # 若為左上角單元格,則終止搜尋 return grid[i][j] if i == 0 && j == 0 # 若行列索引越界,則返回 +∞ 代價 return Float::INFINITY if i < 0 || j < 0 # 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 up = min_path_sum_dfs(grid, i - 1, j) left = min_path_sum_dfs(grid, i, j - 1) # 返回從左上角到 (i, j) 的最小路徑代價 [left, up].min + grid[i][j] end ### 最小路徑和:記憶化搜尋 ### def min_path_sum_dfs_mem(grid, mem, i, j) # 若為左上角單元格,則終止搜尋 return grid[0][0] if i == 0 && j == 0 # 若行列索引越界,則返回 +∞ 代價 return Float::INFINITY if i < 0 || j < 0 # 若已有記錄,則直接返回 return mem[i][j] if mem[i][j] != -1 # 左邊和上邊單元格的最小路徑代價 up = min_path_sum_dfs_mem(grid, mem, i - 1, j) left = min_path_sum_dfs_mem(grid, mem, i, j - 1) # 記錄並返回左上角到 (i, j) 的最小路徑代價 mem[i][j] = [left, up].min + grid[i][j] end ### 最小路徑和:動態規劃 ### def min_path_sum_dp(grid) n, m = grid.length, grid.first.length # 初始化 dp 表 dp = Array.new(n) { Array.new(m, 0) } dp[0][0] = grid[0][0] # 狀態轉移:首行 (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] } # 狀態轉移:首列 (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] } # 狀態轉移:其餘行和列 for i in 1...n for j in 1...m dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j] end end dp[n -1][m -1] end ### 最小路徑和:空間最佳化後的動態規劃 ### def min_path_sum_dp_comp(grid) n, m = grid.length, grid.first.length # 初始化 dp 表 dp = Array.new(m, 0) # 狀態轉移:首行 dp[0] = grid[0][0] (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] } # 狀態轉移:其餘行 for i in 1...n # 狀態轉移:首列 dp[0] = dp[0] + grid[i][0] # 狀態轉移:其餘列 (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] } end dp[m - 1] end ### Driver Code ### if __FILE__ == $0 grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] n, m = grid.length, grid.first.length # 暴力搜尋 res = min_path_sum_dfs(grid, n - 1, m - 1) puts "從左上角到右下角的最小路徑和為 #{res}" # 記憶化搜尋 mem = Array.new(n) { Array.new(m, - 1) } res = min_path_sum_dfs_mem(grid, mem, n - 1, m -1) puts "從左上角到右下角的最小路徑和為 #{res}" # 動態規劃 res = min_path_sum_dp(grid) puts "從左上角到右下角的最小路徑和為 #{res}" # 空間最佳化後的動態規劃 res = min_path_sum_dp_comp(grid) puts "從左上角到右下角的最小路徑和為 #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb ================================================ =begin File: unbounded_knapsack.rb Created Time: 2024-05-29 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 完全背包:動態規劃 ### def unbounded_knapsack_dp(wgt, val, cap) n = wgt.length # 初始化 dp 表 dp = Array.new(n + 1) { Array.new(cap + 1, 0) } # 狀態轉移 for i in 1...(n + 1) for c in 1...(cap + 1) if wgt[i - 1] > c # 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c] else # 不選和選物品 i 這兩種方案的較大值 dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max end end end dp[n][cap] end ### 完全背包:空間最佳化後的動態規劃 ##3 def unbounded_knapsack_dp_comp(wgt, val, cap) n = wgt.length # 初始化 dp 表 dp = Array.new(cap + 1, 0) # 狀態轉移 for i in 1...(n + 1) # 正序走訪 for c in 1...(cap + 1) if wgt[i -1] > c # 若超過背包容量,則不選物品 i dp[c] = dp[c] else # 不選和選物品 i 這兩種方案的較大值 dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max end end end dp[cap] end ### Driver Code ### if __FILE__ == $0 wgt = [1, 2, 3] val = [5, 11, 15] cap = 4 # 動態規劃 res = unbounded_knapsack_dp(wgt, val, cap) puts "不超過背包容量的最大物品價值為 #{res}" # 空間最佳化後的動態規劃 res = unbounded_knapsack_dp_comp(wgt, val, cap) puts "不超過背包容量的最大物品價值為 #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_graph/graph_adjacency_list.rb ================================================ =begin File: graph_adjacency_list.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/vertex' ### 基於鄰接表實現的無向圖類別 ### class GraphAdjList attr_reader :adj_list ### 建構子 ### def initialize(edges) # 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 @adj_list = {} # 新增所有頂點和邊 for edge in edges add_vertex(edge[0]) add_vertex(edge[1]) add_edge(edge[0], edge[1]) end end ### 獲取頂點數量 ### def size @adj_list.length end ### 新增邊 ### def add_edge(vet1, vet2) raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) @adj_list[vet1] << vet2 @adj_list[vet2] << vet1 end ### 刪除邊 ### def remove_edge(vet1, vet2) raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) # 刪除邊 vet1 - vet2 @adj_list[vet1].delete(vet2) @adj_list[vet2].delete(vet1) end ### 新增頂點 ### def add_vertex(vet) return if @adj_list.include?(vet) # 在鄰接表中新增一個新鏈結串列 @adj_list[vet] = [] end ### 刪除頂點 ### def remove_vertex(vet) raise ArgumentError unless @adj_list.include?(vet) # 在鄰接表中刪除頂點 vet 對應的鏈結串列 @adj_list.delete(vet) # 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 for vertex in @adj_list @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet) end end ### 列印鄰接表 ### def __print__ puts '鄰接表 =' for vertex in @adj_list tmp = @adj_list[vertex.first].map { |v| v.val } puts "#{vertex.first.val}: #{tmp}," end end end ### Driver Code ### if __FILE__ == $0 # 初始化無向圖 v = vals_to_vets([1, 3, 2, 5, 4]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ] graph = GraphAdjList.new(edges) puts "\n初始化後,圖為" graph.__print__ # 新增邊 # 頂點 1,2 即 v[0],v[2] graph.add_edge(v[0], v[2]) puts "\n新增邊 1-2 後,圖為" graph.__print__ # 刪除邊 # 頂點 1,3 即 v[0],v[1] graph.remove_edge(v[0], v[1]) puts "\n刪除邊 1-3 後,圖為" graph.__print__ # 新增頂點 v5 = Vertex.new(6) graph.add_vertex(v5) puts "\n新增頂點 6 後,圖為" graph.__print__ # 刪除頂點 # 頂點 3 即 v[1] graph.remove_vertex(v[1]) puts "\n刪除頂點 3 後,圖為" graph.__print__ end ================================================ FILE: zh-hant/codes/ruby/chapter_graph/graph_adjacency_matrix.rb ================================================ =begin File: graph_adjacency_matrix.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/print_util' ### 基於鄰接矩陣實現的無向圖類別 ### class GraphAdjMat def initialize(vertices, edges) ### 建構子 ### # 頂點串列,元素代表“頂點值”,索引代表“頂點索引” @vertices = [] # 鄰接矩陣,行列索引對應“頂點索引” @adj_mat = [] # 新增頂點 vertices.each { |val| add_vertex(val) } # 新增邊 # 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 edges.each { |e| add_edge(e[0], e[1]) } end ### 獲取頂點數量 ### def size @vertices.length end ### 新增頂點 ### def add_vertex(val) n = size # 向頂點串列中新增新頂點的值 @vertices << val # 在鄰接矩陣中新增一行 new_row = Array.new(n, 0) @adj_mat << new_row # 在鄰接矩陣中新增一列 @adj_mat.each { |row| row << 0 } end ### 刪除頂點 ### def remove_vertex(index) raise IndexError if index >= size # 在頂點串列中移除索引 index 的頂點 @vertices.delete_at(index) # 在鄰接矩陣中刪除索引 index 的行 @adj_mat.delete_at(index) # 在鄰接矩陣中刪除索引 index 的列 @adj_mat.each { |row| row.delete_at(index) } end ### 新增邊 ### def add_edge(i, j) # 參數 i, j 對應 vertices 元素索引 # 索引越界與相等處理 if i < 0 || j < 0 || i >= size || j >= size || i == j raise IndexError end # 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) @adj_mat[i][j] = 1 @adj_mat[j][i] = 1 end ### 刪除邊 ### def remove_edge(i, j) # 參數 i, j 對應 vertices 元素索引 # 索引越界與相等處理 if i < 0 || j < 0 || i >= size || j >= size || i == j raise IndexError end @adj_mat[i][j] = 0 @adj_mat[j][i] = 0 end ### 列印鄰接矩陣 ### def __print__ puts "頂點串列 = #{@vertices}" puts '鄰接矩陣 =' print_matrix(@adj_mat) end end ### Driver Code ### if __FILE__ == $0 # 初始化無向圖 # 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 vertices = [1, 3, 2, 5, 4] edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] graph = GraphAdjMat.new(vertices, edges) puts "\n初始化後,圖為" graph.__print__ # 新增邊 # 頂點 1, 2 的索引分別為 0, 2 graph.add_edge(0, 2) puts "\n新增邊 1-2 後,圖為" graph.__print__ # 刪除邊 # 定點 1, 3 的索引分別為 0, 1 graph.remove_edge(0, 1) puts "\n刪除邊 1-3 後,圖為" graph.__print__ # 新增頂點 graph.add_vertex(6) puts "\n新增頂點 6 後,圖為" graph.__print__ # 刪除頂點 # 頂點 3 的索引為 1 graph.remove_vertex(1) puts "\n刪除頂點 3 後,圖為" graph.__print__ end ================================================ FILE: zh-hant/codes/ruby/chapter_graph/graph_bfs.rb ================================================ =begin File: graph_bfs.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require 'set' require_relative './graph_adjacency_list' require_relative '../utils/vertex' ### 廣度優先走訪 ### def graph_bfs(graph, start_vet) # 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 # 頂點走訪序列 res = [] # 雜湊集合,用於記錄已被訪問過的頂點 visited = Set.new([start_vet]) # 佇列用於實現 BFS que = [start_vet] # 以頂點 vet 為起點,迴圈直至訪問完所有頂點 while que.length > 0 vet = que.shift # 佇列首頂點出隊 res << vet # 記錄訪問頂點 # 走訪該頂點的所有鄰接頂點 for adj_vet in graph.adj_list[vet] next if visited.include?(adj_vet) # 跳過已被訪問的頂點 que << adj_vet # 只入列未訪問的頂點 visited.add(adj_vet) # 標記該頂點已被訪問 end end # 返回頂點走訪序列 res end ### Driver Code ### if __FILE__ == $0 # 初始化無向圖 v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ] graph = GraphAdjList.new(edges) puts "\n初始化後,圖為" graph.__print__ # 廣度優先走訪 res = graph_bfs(graph, v.first) puts "\n廣度優先便利(BFS)頂點序列為" p vets_to_vals(res) end ================================================ FILE: zh-hant/codes/ruby/chapter_graph/graph_dfs.rb ================================================ =begin File: graph_dfs.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require 'set' require_relative './graph_adjacency_list' require_relative '../utils/vertex' ### 深度優先走訪輔助函式 ### def dfs(graph, visited, res, vet) res << vet # 記錄訪問頂點 visited.add(vet) # 標記該頂點已被訪問 # 走訪該頂點的所有鄰接頂點 for adj_vet in graph.adj_list[vet] next if visited.include?(adj_vet) # 跳過已被訪問的頂點 # 遞迴訪問鄰接頂點 dfs(graph, visited, res, adj_vet) end end ### 深度優先走訪 ### def graph_dfs(graph, start_vet) # 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 # 頂點走訪序列 res = [] # 雜湊集合,用於記錄已被訪問過的頂點 visited = Set.new dfs(graph, visited, res, start_vet) res end ### Driver Code ### if __FILE__ == $0 # 初始化無向圖 v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ] graph = GraphAdjList.new(edges) puts "\n初始化後,圖為" graph.__print__ # 深度優先走訪 res = graph_dfs(graph, v[0]) puts "\n深度優先走訪(DFS)頂點序列為" p vets_to_vals(res) end ================================================ FILE: zh-hant/codes/ruby/chapter_greedy/coin_change_greedy.rb ================================================ =begin File: coin_change_greedy.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 零錢兌換:貪婪 ### def coin_change_greedy(coins, amt) # 假設 coins 串列有序 i = coins.length - 1 count = 0 # 迴圈進行貪婪選擇,直到無剩餘金額 while amt > 0 # 找到小於且最接近剩餘金額的硬幣 while i > 0 && coins[i] > amt i -= 1 end # 選擇 coins[i] amt -= coins[i] count += 1 end # 若未找到可行方案, 則返回 -1 amt == 0 ? count : -1 end ### Driver Code ### if __FILE__ == $0 # 貪婪:能夠保證找到全域性最優解 coins = [1, 5, 10, 20, 50, 100] amt = 186 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "湊到 #{amt} 所需的最少硬幣數量為 #{res}" # 貪婪:無法保證找到全域性最優解 coins = [1, 20, 50] amt = 60 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "湊到 #{amt} 所需的最少硬幣數量為 #{res}" puts "實際上需要的最少數量為 3 , 即 20 + 20 + 20" # 貪婪:無法保證找到全域性最優解 coins = [1, 49, 50] amt = 98 res = coin_change_greedy(coins, amt) puts "\ncoins = #{coins}, amt = #{amt}" puts "湊到 #{amt} 所需的最少硬幣數量為 #{res}" puts "實際上需要的最少數量為 2 , 即 49 + 49" end ================================================ FILE: zh-hant/codes/ruby/chapter_greedy/fractional_knapsack.rb ================================================ =begin File: fractional_knapsack.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 物品 ### class Item attr_accessor :w # 物品重量 attr_accessor :v # 物品價值 def initialize(w, v) @w = w @v = v end end ### 分數背包:貪婪 ### def fractional_knapsack(wgt, val, cap) # 建立物品串列,包含兩個屬性:重量,價值 items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) } # 按照單位價值 item.v / item.w 從高到低進行排序 items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) } # 迴圈貪婪選擇 res = 0 for item in items if item.w <= cap # 若剩餘容量充足,則將當前物品整個裝進背包 res += item.v cap -= item.w else # 若剩餘容量不足,則將當前物品的一部分裝進背包 res += (item.v.to_f / item.w) * cap # 已無剩餘容量,因此跳出迴圈 break end end res end ### Driver Code ### if __FILE__ == $0 wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = wgt.length # 貪婪演算法 res = fractional_knapsack(wgt, val, cap) puts "不超過背包容量的最大物品價值為 #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_greedy/max_capacity.rb ================================================ =begin File: max_capacity.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 最大容量:貪婪 ### def max_capacity(ht) # 初始化 i, j,使其分列陣列兩端 i, j = 0, ht.length - 1 # 初始最大容量為 0 res = 0 # 迴圈貪婪選擇,直至兩板相遇 while i < j # 更新最大容量 cap = [ht[i], ht[j]].min * (j - i) res = [res, cap].max # 向內移動短板 if ht[i] < ht[j] i += 1 else j -= 1 end end res end ### Driver Code ### if __FILE__ == $0 ht = [3, 8, 5, 2, 7, 7, 3, 4] # 貪婪演算法 res = max_capacity(ht) puts "最大容量為 #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_greedy/max_product_cutting.rb ================================================ =begin File: max_product_cutting.rb Created Time: 2024-05-07 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 最大切分乘積:貪婪 ### def max_product_cutting(n) # 當 n <= 3 時,必須切分出一個 1 return 1 * (n - 1) if n <= 3 # 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 a, b = n / 3, n % 3 # 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 return (3.pow(a - 1) * 2 * 2).to_i if b == 1 # 當餘數為 2 時,不做處理 return (3.pow(a) * 2).to_i if b == 2 # 當餘數為 0 時,不做處理 3.pow(a).to_i end ### Driver Code ### if __FILE__ == $0 n = 58 # 貪婪演算法 res = max_product_cutting(n) puts "最大切分乘積為 #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_hashing/array_hash_map.rb ================================================ =begin File: array_hash_map.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 鍵值對 ### class Pair attr_accessor :key, :val def initialize(key, val) @key = key @val = val end end ### 基於陣列實現的雜湊表 ### class ArrayHashMap ### 建構子 ### def initialize # 初始化陣列,包含 100 個桶 @buckets = Array.new(100) end ### 雜湊函式 ### def hash_func(key) index = key % 100 end ### 查詢操作 ### def get(key) index = hash_func(key) pair = @buckets[index] return if pair.nil? pair.val end ### 新增操作 ### def put(key, val) pair = Pair.new(key, val) index = hash_func(key) @buckets[index] = pair end ### 刪除操作 ### def remove(key) index = hash_func(key) # 置為 nil ,代表刪除 @buckets[index] = nil end ### 獲取所有鍵值對 ### def entry_set result = [] @buckets.each { |pair| result << pair unless pair.nil? } result end ### 獲取所有鍵 ### def key_set result = [] @buckets.each { |pair| result << pair.key unless pair.nil? } result end ### 獲取所有值 ### def value_set result = [] @buckets.each { |pair| result << pair.val unless pair.nil? } result end ### 列印雜湊表 ### def print @buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? } end end ### Driver Code ### if __FILE__ == $0 # 初始化雜湊表 hmap = ArrayHashMap.new # 新增操作 # 在雜湊表中新增鍵值對 (key, value) hmap.put(12836, "小哈") hmap.put(15937, "小囉") hmap.put(16750, "小算") hmap.put(13276, "小法") hmap.put(10583, "小鴨") puts "\n新增完成後,雜湊表為\nKey -> Value" hmap.print # 查詢操作 # 向雜湊表中輸入鍵 key , 得到值 value name = hmap.get(15937) puts "\n輸入學號 15937 ,查詢到姓名 #{name}" # 刪除操作 # 在雜湊表中刪除值對 (key, value) hmap.remove(10583) puts "\n刪除 10583 後,雜湊表為\nKey -> Value" hmap.print # 走訪雜湊表 puts "\n走訪鍵值對 Key->Value" for pair in hmap.entry_set puts "#{pair.key} -> #{pair.val}" end puts "\n單獨篇走訪鍵 Key" for key in hmap.key_set puts key end puts "\n單獨走訪值 Value" for val in hmap.value_set puts val end end ================================================ FILE: zh-hant/codes/ruby/chapter_hashing/built_in_hash.rb ================================================ =begin File: built_in_hash.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### Driver Code ### if __FILE__ == $0 num = 3 hash_num = num.hash puts "整數 #{num} 的雜湊值為 #{hash_num}" bol = true hash_bol = bol.hash puts "布林量 #{bol} 的雜湊值為 #{hash_bol}" dec = 3.14159 hash_dec = dec.hash puts "小數 #{dec} 的雜湊值為 #{hash_dec}" str = "Hello 演算法" hash_str = str.hash puts "字串 #{str} 的雜湊值為 #{hash_str}" tup = [12836, '小哈'] hash_tup = tup.hash puts "元組 #{tup} 的雜湊值為 #{hash_tup}" obj = ListNode.new(0) hash_obj = obj.hash puts "節點物件 #{obj} 的雜湊值為 #{hash_obj}" end ================================================ FILE: zh-hant/codes/ruby/chapter_hashing/hash_map.rb ================================================ =begin File: hash_map.rb Created Time: 2024-04-14 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/print_util' ### Driver Code ### if __FILE__ == $0 # 初始化雜湊表 hmap = {} # 新增操作 # 在雜湊表中新增鍵值對 (key, value) hmap[12836] = "小哈" hmap[15937] = "小囉" hmap[16750] = "小算" hmap[13276] = "小法" hmap[10583] = "小鴨" puts "\n新增完成後,雜湊表為\nKey -> Value" print_hash_map(hmap) # 查詢操作 # 向雜湊表中輸入鍵 key ,得到值 value name = hmap[15937] puts "\n輸入學號 15937 ,查詢到姓名 #{name}" # 刪除操作 # 在雜湊表中刪除鍵值對 (key, value) hmap.delete(10583) puts "\n刪除 10583 後,雜湊表為\nKey -> Value" print_hash_map(hmap) # 走訪雜湊表 puts "\n走訪鍵值對 Key->Value" hmap.entries.each { |key, value| puts "#{key} -> #{value}" } puts "\n單獨走訪鍵 Key" hmap.keys.each { |key| puts key } puts "\n單獨走訪值 Value" hmap.values.each { |val| puts val } end ================================================ FILE: zh-hant/codes/ruby/chapter_hashing/hash_map_chaining.rb ================================================ =begin File: hash_map_chaining.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative './array_hash_map' ### 鍵式位址雜湊表 ### class HashMapChaining ### 建構子 ### def initialize @size = 0 # 鍵值對數量 @capacity = 4 # 雜湊表容量 @load_thres = 2.0 / 3.0 # 觸發擴容的負載因子閾值 @extend_ratio = 2 # 擴容倍數 @buckets = Array.new(@capacity) { [] } # 桶陣列 end ### 雜湊函式 ### def hash_func(key) key % @capacity end ### 負載因子 ### def load_factor @size / @capacity end ### 查詢操作 ### def get(key) index = hash_func(key) bucket = @buckets[index] # 走訪桶,若找到 key ,則返回對應 val for pair in bucket return pair.val if pair.key == key end # 若未找到 key , 則返回 nil nil end ### 新增操作 ### def put(key, val) # 當負載因子超過閾值時,執行擴容 extend if load_factor > @load_thres index = hash_func(key) bucket = @buckets[index] # 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for pair in bucket if pair.key == key pair.val = val return end end # 若無該 key ,則將鍵值對新增至尾部 pair = Pair.new(key, val) bucket << pair @size += 1 end ### 刪除操作 ### def remove(key) index = hash_func(key) bucket = @buckets[index] # 走訪桶,從中刪除鍵值對 for pair in bucket if pair.key == key bucket.delete(pair) @size -= 1 break end end end ### 擴容雜湊表 ### def extend # 暫存原雜湊表 buckets = @buckets # 初始化擴容後的新雜湊表 @capacity *= @extend_ratio @buckets = Array.new(@capacity) { [] } @size = 0 # 將鍵值對從原雜湊表搬運至新雜湊表 for bucket in buckets for pair in bucket put(pair.key, pair.val) end end end ### 列印雜湊表 ### def print for bucket in @buckets res = [] for pair in bucket res << "#{pair.key} -> #{pair.val}" end pp res end end end ### Driver Code ### if __FILE__ == $0 ### 初始化雜湊表 hashmap = HashMapChaining.new # 新增操作 # 在雜湊表中新增鍵值對 (key, value) hashmap.put(12836, "小哈") hashmap.put(15937, "小囉") hashmap.put(16750, "小算") hashmap.put(13276, "小法") hashmap.put(10583, "小鴨") puts "\n新增完成後,雜湊表為\n[Key1 -> Value1, Key2 -> Value2, ...]" hashmap.print # 查詢操作 # 向雜湊表中輸入鍵 key ,得到值 value name = hashmap.get(13276) puts "\n輸入學號 13276 ,查詢到姓名 #{name}" # 刪除操作 # 在雜湊表中刪除鍵值對 (key, value) hashmap.remove(12836) puts "\n刪除 12836 後,雜湊表為\n[Key1 -> Value1, Key2 -> Value2, ...]" hashmap.print end ================================================ FILE: zh-hant/codes/ruby/chapter_hashing/hash_map_open_addressing.rb ================================================ =begin File: hash_map_open_addressing.rb Created Time: 2024-04-13 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative './array_hash_map' ### 開放定址雜湊表 ### class HashMapOpenAddressing TOMBSTONE = Pair.new(-1, '-1') # 刪除標記 ### 建構子 ### def initialize @size = 0 # 鍵值對數量 @capacity = 4 # 雜湊表容量 @load_thres = 2.0 / 3.0 # 觸發擴容的負載因子閾值 @extend_ratio = 2 # 擴容倍數 @buckets = Array.new(@capacity) # 桶陣列 end ### 雜湊函式 ### def hash_func(key) key % @capacity end ### 負載因子 ### def load_factor @size / @capacity end ### 搜尋 key 對應的桶索引 ### def find_bucket(key) index = hash_func(key) first_tombstone = -1 # 線性探查,當遇到空桶時跳出 while !@buckets[index].nil? # 若遇到 key ,返回對應的桶索引 if @buckets[index].key == key # 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if first_tombstone != -1 @buckets[first_tombstone] = @buckets[index] @buckets[index] = TOMBSTONE return first_tombstone # 返回移動後的桶索引 end return index # 返回桶索引 end # 記錄遇到的首個刪除標記 first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE # 計算桶索引,越過尾部則返回頭部 index = (index + 1) % @capacity end # 若 key 不存在,則返回新增點的索引 first_tombstone == -1 ? index : first_tombstone end ### 查詢操作 ### def get(key) # 搜尋 key 對應的桶索引 index = find_bucket(key) # 若找到鍵值對,則返回對應 val return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index]) # 若鍵值對不存在,則返回 nil nil end ### 新增操作 ### def put(key, val) # 當負載因子超過閾值時,執行擴容 extend if load_factor > @load_thres # 搜尋 key 對應的桶索引 index = find_bucket(key) # 若找到鍵值對,則覆蓋 val 開返回 unless [nil, TOMBSTONE].include?(@buckets[index]) @buckets[index].val = val return end # 若鍵值對不存在,則新增該鍵值對 @buckets[index] = Pair.new(key, val) @size += 1 end ### 刪除操作 ### def remove(key) # 搜尋 key 對應的桶索引 index = find_bucket(key) # 若找到鍵值對,則用刪除標記覆蓋它 unless [nil, TOMBSTONE].include?(@buckets[index]) @buckets[index] = TOMBSTONE @size -= 1 end end ### 擴容雜湊表 ### def extend # 暫存原雜湊表 buckets_tmp = @buckets # 初始化擴容後的新雜湊表 @capacity *= @extend_ratio @buckets = Array.new(@capacity) @size = 0 # 將鍵值對從原雜湊表搬運至新雜湊表 for pair in buckets_tmp put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair) end end ### 列印雜湊表 ### def print for pair in @buckets if pair.nil? puts "Nil" elsif pair == TOMBSTONE puts "TOMBSTONE" else puts "#{pair.key} -> #{pair.val}" end end end end ### Driver Code ### if __FILE__ == $0 # 初始化雜湊表 hashmap = HashMapOpenAddressing.new # 新增操作 # 在雜湊表中新增鍵值對 (key, val) hashmap.put(12836, "小哈") hashmap.put(15937, "小囉") hashmap.put(16750, "小算") hashmap.put(13276, "小法") hashmap.put(10583, "小鴨") puts "\n新增完成後,雜湊表為\nKey -> Value" hashmap.print # 查詢操作 # 向雜湊表中輸入鍵 key ,得到值 val name = hashmap.get(13276) puts "\n輸入學號 13276 ,查詢到姓名 #{name}" # 刪除操作 # 在雜湊表中刪除鍵值對 (key, val) hashmap.remove(16750) puts "\n刪除 16750 後,雜湊表為\nKey -> Value" hashmap.print end ================================================ FILE: zh-hant/codes/ruby/chapter_hashing/simple_hash.rb ================================================ =begin File: simple_hash.rb Created Time: 2024-04-14 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 加法雜湊 ### def add_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash += c.ord } hash % modulus end ### 乘法雜湊 ### def mul_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash = 31 * hash + c.ord } hash % modulus end ### 互斥或雜湊 ### def xor_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash ^= c.ord } hash % modulus end ### 旋轉雜湊 ### def rot_hash(key) hash = 0 modulus = 1_000_000_007 key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord } hash % modulus end ### Driver Code ### if __FILE__ == $0 key = "Hello 演算法" hash = add_hash(key) puts "加法雜湊值為 #{hash}" hash = mul_hash(key) puts "乘法雜湊值為 #{hash}" hash = xor_hash(key) puts "互斥或雜湊值為 #{hash}" hash = rot_hash(key) puts "旋轉雜湊值為 #{hash}" end ================================================ FILE: zh-hant/codes/ruby/chapter_heap/my_heap.rb ================================================ =begin File: my_heap.rb Created Time: 2024-04-19 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/print_util' ### 大頂堆積 ### class MaxHeap attr_reader :max_heap ### 建構子,根據輸入串列建堆積 ### def initialize(nums) # 將串列元素原封不動新增進堆積 @max_heap = nums # 堆積化除葉節點以外的其他所有節點 parent(size - 1).downto(0) do |i| sift_down(i) end end ### 獲取左子節點的索引 ### def left(i) 2 * i + 1 end ### 獲取右子節點的索引 ### def right(i) 2 * i + 2 end ### 獲取父節點的索引 ### def parent(i) (i - 1) / 2 # 向下整除 end ### 交換元素 ### def swap(i, j) @max_heap[i], @max_heap[j] = @max_heap[j], @max_heap[i] end ### 獲取堆積大小 ### def size @max_heap.length end ### 判斷堆積是否為空 ### def is_empty? size == 0 end ### 訪問堆積頂元素 ### def peek @max_heap[0] end ### 元素入堆積 ### def push(val) # 新增節點 @max_heap << val # 從底至頂堆積化 sift_up(size - 1) end ### 從節點 i 開始,從底至頂堆積化 ### def sift_up(i) loop do # 獲取節點 i 的父節點 p = parent(i) # 當“越過根節點”或“節點無須修復”時,結束堆積化 break if p < 0 || @max_heap[i] <= @max_heap[p] # 交換兩節點 swap(i, p) # 迴圈向上堆積化 i = p end end ### 元素出堆積 ### def pop # 判空處理 raise IndexError, "堆積為空" if is_empty? # 交換根節點與最右葉節點(交換首元素與尾元素) swap(0, size - 1) # 刪除節點 val = @max_heap.pop # 從頂至底堆積化 sift_down(0) # 返回堆積頂元素 val end ### 從節點 i 開始,從頂至底堆積化 ### def sift_down(i) loop do # 判斷節點 i, l, r 中值最大的節點,記為 ma l, r, ma = left(i), right(i), i ma = l if l < size && @max_heap[l] > @max_heap[ma] ma = r if r < size && @max_heap[r] > @max_heap[ma] # 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 break if ma == i # 交換兩節點 swap(i, ma) # 迴圈向下堆積化 i = ma end end ### 列印堆積(二元樹)### def __print__ print_heap(@max_heap) end end ### Driver Code ### if __FILE__ == $0 # 初始化大頂堆積 max_heap = MaxHeap.new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) puts "\n輸入串列並建堆積後" max_heap.__print__ # 獲取堆積頂元素 peek = max_heap.peek puts "\n堆積頂元素為 #{peek}" # 元素入堆積 val = 7 max_heap.push(val) puts "\n元素 #{val} 入堆積後" max_heap.__print__ # 堆積頂元素出堆積 peek = max_heap.pop puts "\n堆積頂元素 #{peek} 出堆積後" max_heap.__print__ # 獲取堆積大小 size = max_heap.size puts "\n堆積元素數量為 #{size}" # 判斷堆積是否為空 is_empty = max_heap.is_empty? puts "\n堆積是否為空 #{is_empty}" end ================================================ FILE: zh-hant/codes/ruby/chapter_heap/top_k.rb ================================================ =begin File: top_k.rb Created Time: 2024-04-19 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative "./my_heap" ### 元素入堆積 ### def push_min_heap(heap, val) # 元素取反 heap.push(-val) end ### 元素出堆積 ### def pop_min_heap(heap) # 元素取反 -heap.pop end ### 訪問堆積頂元素 ### def peek_min_heap(heap) # 元素取反 -heap.peek end ### 取出堆積中元素 ### def get_min_heap(heap) # 將堆積中所有元素取反 heap.max_heap.map { |x| -x } end ### 基於堆積查詢陣列中最大的 k 個元素 ### def top_k_heap(nums, k) # 初始化小頂堆積 # 請注意:我們將堆積中所有元素取反,從而用大頂堆積來模擬小頂堆積 max_heap = MaxHeap.new([]) # 將陣列的前 k 個元素入堆積 for i in 0...k push_min_heap(max_heap, nums[i]) end # 從第 k+1 個元素開始,保持堆積的長度為 k for i in k...nums.length # 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 if nums[i] > peek_min_heap(max_heap) pop_min_heap(max_heap) push_min_heap(max_heap, nums[i]) end end get_min_heap(max_heap) end ### Driver Code ### if __FILE__ == $0 nums = [1, 7, 6, 3, 2] k = 3 res = top_k_heap(nums, k) puts "最大的 #{k} 個元素為" print_heap(res) end ================================================ FILE: zh-hant/codes/ruby/chapter_searching/binary_search.rb ================================================ =begin File: binary_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### 二分搜尋(雙閉區間) ### def binary_search(nums, target) # 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 i, j = 0, nums.length - 1 # 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) while i <= j # 理論上 Ruby 的數字可以無限大(取決於記憶體大小),無須考慮大數越界問題 m = (i + j) / 2 # 計算中點索引 m if nums[m] < target i = m + 1 # 此情況說明 target 在區間 [m+1, j] 中 elsif nums[m] > target j = m - 1 # 此情況說明 target 在區間 [i, m-1] 中 else return m # 找到目標元素,返回其索引 end end -1 # 未找到目標元素,返回 -1 end ### 二分搜尋(左閉右開區間) ### def binary_search_lcro(nums, target) # 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 i, j = 0, nums.length # 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) while i < j # 計算中點索引 m m = (i + j) / 2 if nums[m] < target i = m + 1 # 此情況說明 target 在區間 [m+1, j) 中 elsif nums[m] > target j = m - 1 # 此情況說明 target 在區間 [i, m) 中 else return m # 找到目標元素,返回其索引 end end -1 # 未找到目標元素,返回 -1 end ### Driver Code ### if __FILE__ == $0 target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # 二分搜尋(雙閉區間) index = binary_search(nums, target) puts "目標元素 6 的索引 = #{index}" # 二分搜尋(左閉右開區間) index = binary_search_lcro(nums, target) puts "目標元素 6 的索引 = #{index}" end ================================================ FILE: zh-hant/codes/ruby/chapter_searching/binary_search_edge.rb ================================================ =begin File: binary_search_edge.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative './binary_search_insertion' ### 二分搜尋最左一個 target ### def binary_search_left_edge(nums, target) # 等價於查詢 target 的插入點 i = binary_search_insertion(nums, target) # 未找到 target ,返回 -1 return -1 if i == nums.length || nums[i] != target i # 找到 target ,返回索引 i end ### 二分搜尋最右一個 target ### def binary_search_right_edge(nums, target) # 轉化為查詢最左一個 target + 1 i = binary_search_insertion(nums, target + 1) # j 指向最右一個 target ,i 指向首個大於 target 的元素 j = i - 1 # 未找到 target ,返回 -1 return -1 if j == -1 || nums[j] != target j # 找到 target ,返回索引 j end ### Driver Code ### if __FILE__ == $0 # 包含重複元素的陣列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] puts "\n陣列 nums = #{nums}" # 二分搜尋左邊界和右邊界 for target in [6, 7] index = binary_search_left_edge(nums, target) puts "最左一個元素 #{target} 的索引為 #{index}" index = binary_search_right_edge(nums, target) puts "最右一個元素 #{target} 的索引為 #{index}" end end ================================================ FILE: zh-hant/codes/ruby/chapter_searching/binary_search_insertion.rb ================================================ =begin File: binary_search_insertion.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### 二分搜尋插入點(無重複元素) ### def binary_search_insertion_simple(nums, target) # 初始化雙閉區間 [0, n-1] i, j = 0, nums.length - 1 while i <= j # 計算中點索引 m m = (i + j) / 2 if nums[m] < target i = m + 1 # target 在區間 [m+1, j] 中 elsif nums[m] > target j = m - 1 # target 在區間 [i, m-1] 中 else return m # 找到 target ,返回插入點 m end end i # 未找到 target ,返回插入點 i end ### 二分搜尋插入點(存在重複元素) ### def binary_search_insertion(nums, target) # 初始化雙閉區間 [0, n-1] i, j = 0, nums.length - 1 while i <= j # 計算中點索引 m m = (i + j) / 2 if nums[m] < target i = m + 1 # target 在區間 [m+1, j] 中 elsif nums[m] > target j = m - 1 # target 在區間 [i, m-1] 中 else j = m - 1 # 首個小於 target 的元素在區間 [i, m-1] 中 end end i # 返回插入點 i end ### Driver Code ### if __FILE__ == $0 # 無重複元素的陣列 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] puts "\n陣列 nums = #{nums}" # 二分搜尋插入點 for target in [6, 9] index = binary_search_insertion_simple(nums, target) puts "元素 #{target} 的插入點的索引為 #{index}" end # 包含重複元素的陣列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] puts "\n陣列 nums = #{nums}" # 二分搜尋插入點 for target in [2, 6, 20] index = binary_search_insertion(nums, target) puts "元素 #{target} 的插入點的索引為 #{index}" end end ================================================ FILE: zh-hant/codes/ruby/chapter_searching/hashing_search.rb ================================================ =begin File: hashing_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/list_node' ### 雜湊查詢(陣列) ### def hashing_search_array(hmap, target) # 雜湊表的 key: 目標元素,value: 索引 # 若雜湊表中無此 key ,返回 -1 hmap[target] || -1 end ### 雜湊查詢(鏈結串列) ### def hashing_search_linkedlist(hmap, target) # 雜湊表的 key: 目標元素,value: 節點物件 # 若雜湊表中無此 key ,返回 None hmap[target] || nil end ### Driver Code ### if __FILE__ == $0 target = 3 # 雜湊查詢(陣列) nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] # 初始化雜湊表 map0 = {} for i in 0...nums.length map0[nums[i]] = i # key: 元素,value: 索引 end index = hashing_search_array(map0, target) puts "目標元素 3 的索引 = #{index}" # 雜湊查詢(鏈結串列) head = arr_to_linked_list(nums) # 初始化雜湊表 map1 = {} while head map1[head.val] = head head = head.next end node = hashing_search_linkedlist(map1, target) puts "目標節點值 3 的對應節點物件為 #{node}" end ================================================ FILE: zh-hant/codes/ruby/chapter_searching/linear_search.rb ================================================ =begin File: linear_search.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end require_relative '../utils/list_node' ### 線性查詢(陣列) ### def linear_search_array(nums, target) # 走訪陣列 for i in 0...nums.length return i if nums[i] == target # 找到目標元素,返回其索引 end -1 # 未找到目標元素,返回 -1 end ### 線性查詢(鏈結串列) ### def linear_search_linkedlist(head, target) # 走訪鏈結串列 while head return head if head.val == target # 找到目標節點,返回之 head = head.next end nil # 未找到目標節點,返回 None end ### Driver Code ### if __FILE__ == $0 target = 3 # 在陣列中執行線性查詢 nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] index = linear_search_array(nums, target) puts "目標元素 3 的索引 = #{index}" # 在鏈結串列中執行線性查詢 head = arr_to_linked_list(nums) node = linear_search_linkedlist(head, target) puts "目標節點值 3 的對應節點物件為 #{node}" end ================================================ FILE: zh-hant/codes/ruby/chapter_searching/two_sum.rb ================================================ =begin File: two_sum.rb Created Time: 2024-04-09 Author: Blue Bean (lonnnnnnner@gmail.com) =end ### 方法一:暴力列舉 ### def two_sum_brute_force(nums, target) # 兩層迴圈,時間複雜度為 O(n^2) for i in 0...(nums.length - 1) for j in (i + 1)...nums.length return [i, j] if nums[i] + nums[j] == target end end [] end ### 方法二:輔助雜湊表 ### def two_sum_hash_table(nums, target) # 輔助雜湊表,空間複雜度為 O(n) dic = {} # 單層迴圈,時間複雜度為 O(n) for i in 0...nums.length return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i]) dic[nums[i]] = i end [] end ### Driver Code ### if __FILE__ == $0 # ======= Test Case ======= nums = [2, 7, 11, 15] target = 13 # ====== Driver Code ====== # 方法一 res = two_sum_brute_force(nums, target) puts "方法一 res = #{res}" # 方法二 res = two_sum_hash_table(nums, target) puts "方法二 res = #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_sorting/bubble_sort.rb ================================================ =begin File: bubble_sort.rb Created Time: 2024-05-02 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 泡沫排序 ### def bubble_sort(nums) n = nums.length # 外迴圈:未排序區間為 [0, i] for i in (n - 1).downto(1) # 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for j in 0...i if nums[j] > nums[j + 1] # 交換 nums[j] 與 nums[j + 1] nums[j], nums[j + 1] = nums[j + 1], nums[j] end end end end ### 泡沫排序(標誌最佳化)### def bubble_sort_with_flag(nums) n = nums.length # 外迴圈:未排序區間為 [0, i] for i in (n - 1).downto(1) flag = false # 初始化標誌位 # 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for j in 0...i if nums[j] > nums[j + 1] # 交換 nums[j] 與 nums[j + 1] nums[j], nums[j + 1] = nums[j + 1], nums[j] flag = true # 記錄交換元素 end end break unless flag # 此輪“冒泡”未交換任何元素,直接跳出 end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] bubble_sort(nums) puts "泡沫排序完成後 nums = #{nums}" nums1 = [4, 1, 3, 1, 5, 2] bubble_sort_with_flag(nums1) puts "泡沫排序完成後 nums = #{nums1}" end ================================================ FILE: zh-hant/codes/ruby/chapter_sorting/bucket_sort.rb ================================================ =begin File: bucket_sort.rb Created Time: 2024-04-17 Author: Martin Xu (martin.xus@gmail.com) =end ### 桶排序 ### def bucket_sort(nums) # 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 k = nums.length / 2 buckets = Array.new(k) { [] } # 1. 將陣列元素分配到各個桶中 nums.each do |num| # 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] i = (num * k).to_i # 將 num 新增進桶 i buckets[i] << num end # 2. 對各個桶執行排序 buckets.each do |bucket| # 使用內建排序函式,也可以替換成其他排序演算法 bucket.sort! end # 3. 走訪桶合併結果 i = 0 buckets.each do |bucket| bucket.each do |num| nums[i] = num i += 1 end end end ### Driver Code ### if __FILE__ == $0 # 設輸入資料為浮點數,範圍為 [0, 1) nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] bucket_sort(nums) puts "桶排序完成後 nums = #{nums}" end ================================================ FILE: zh-hant/codes/ruby/chapter_sorting/counting_sort.rb ================================================ =begin File: counting_sort.rb Created Time: 2024-05-02 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 計數排序 ### def counting_sort_naive(nums) # 簡單實現,無法用於排序物件 # 1. 統計陣列最大元素 m m = 0 nums.each { |num| m = [m, num].max } # 2. 統計各數字的出現次數 # counter[num] 代表 num 的出現次數 counter = Array.new(m + 1, 0) nums.each { |num| counter[num] += 1 } # 3. 走訪 counter ,將各元素填入原陣列 nums i = 0 for num in 0...(m + 1) (0...counter[num]).each do nums[i] = num i += 1 end end end ### 計數排序 ### def counting_sort(nums) # 完整實現,可排序物件,並且是穩定排序 # 1. 統計陣列最大元素 m m = nums.max # 2. 統計各數字的出現次數 # counter[num] 代表 num 的出現次數 counter = Array.new(m + 1, 0) nums.each { |num| counter[num] += 1 } # 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” # 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 (0...m).each { |i| counter[i + 1] += counter[i] } # 4. 倒序走訪 nums, 將各元素填入結果陣列 res # 初始化陣列 res 用於記錄結果 n = nums.length res = Array.new(n, 0) (n - 1).downto(0).each do |i| num = nums[i] res[counter[num] - 1] = num # 將 num 放置到對應索引處 counter[num] -= 1 # 令前綴和自減 1 ,得到下次放置 num 的索引 end # 使用結果陣列 res 覆蓋原陣列 nums (0...n).each { |i| nums[i] = res[i] } end ### Driver Code ### if __FILE__ == $0 nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort_naive(nums) puts "計數排序(無法排序物件)完成後 nums = #{nums}" nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort(nums1) puts "計數排序完成後 nums1 = #{nums1}" end ================================================ FILE: zh-hant/codes/ruby/chapter_sorting/heap_sort.rb ================================================ =begin File: heap_sort.rb Created Time: 2024-04-10 Author: junminhong (junminhong1110@gmail.com) =end ### 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 ### def sift_down(nums, n, i) while true # 判斷節點 i, l, r 中值最大的節點,記為 ma l = 2 * i + 1 r = 2 * i + 2 ma = i ma = l if l < n && nums[l] > nums[ma] ma = r if r < n && nums[r] > nums[ma] # 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 break if ma == i # 交換兩節點 nums[i], nums[ma] = nums[ma], nums[i] # 迴圈向下堆積化 i = ma end end ### 堆積排序 ### def heap_sort(nums) # 建堆積操作:堆積化除葉節點以外的其他所有節點 (nums.length / 2 - 1).downto(0) do |i| sift_down(nums, nums.length, i) end # 從堆積中提取最大元素,迴圈 n-1 輪 (nums.length - 1).downto(1) do |i| # 交換根節點與最右葉節點(交換首元素與尾元素) nums[0], nums[i] = nums[i], nums[0] # 以根節點為起點,從頂至底進行堆積化 sift_down(nums, i, 0) end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] heap_sort(nums) puts "堆積排序完成後 nums = #{nums.inspect}" end ================================================ FILE: zh-hant/codes/ruby/chapter_sorting/insertion_sort.rb ================================================ =begin File: insertion_sort.rb Created Time: 2024-04-02 Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 插入排序 ### def insertion_sort(nums) n = nums.length # 外迴圈:已排序區間為 [0, i-1] for i in 1...n base = nums[i] j = i - 1 # 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 while j >= 0 && nums[j] > base nums[j + 1] = nums[j] # 將 nums[j] 向右移動一位 j -= 1 end nums[j + 1] = base # 將 base 賦值到正確位置 end end ### Driver Code ### nums = [4, 1, 3, 1, 5, 2] insertion_sort(nums) puts "插入排序完成後 nums = #{nums}" ================================================ FILE: zh-hant/codes/ruby/chapter_sorting/merge_sort.rb ================================================ =begin File: merge_sort.rb Created Time: 2024-04-10 Author: junminhong (junminhong1110@gmail.com) =end ### 合併左子陣列和右子陣列 ### def merge(nums, left, mid, right) # 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] # 建立一個臨時陣列 tmp,用於存放合併後的結果 tmp = Array.new(right - left + 1, 0) # 初始化左子陣列和右子陣列的起始索引 i, j, k = left, mid + 1, 0 # 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 while i <= mid && j <= right if nums[i] <= nums[j] tmp[k] = nums[i] i += 1 else tmp[k] = nums[j] j += 1 end k += 1 end # 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 while i <= mid tmp[k] = nums[i] i += 1 k += 1 end while j <= right tmp[k] = nums[j] j += 1 k += 1 end # 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 (0...tmp.length).each do |k| nums[left + k] = tmp[k] end end ### 合併排序 ### def merge_sort(nums, left, right) # 終止條件 # 當子陣列長度為 1 時終止遞迴 return if left >= right # 劃分階段 mid = left + (right - left) / 2 # 計算中點 merge_sort(nums, left, mid) # 遞迴左子陣列 merge_sort(nums, mid + 1, right) # 遞迴右子陣列 # 合併階段 merge(nums, left, mid, right) end ### Driver Code ### if __FILE__ == $0 nums = [7, 3, 2, 6, 0, 1, 5, 4] merge_sort(nums, 0, nums.length - 1) puts "合併排序完成後 nums = #{nums.inspect}" end ================================================ FILE: zh-hant/codes/ruby/chapter_sorting/quick_sort.rb ================================================ =begin File: quick_sort.rb Created Time: 2024-04-01 Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 快速排序類別 ### class QuickSort class << self ### 哨兵劃分 ### def partition(nums, left, right) # 以 nums[left] 為基準數 i, j = left, right while i < j while i < j && nums[j] >= nums[left] j -= 1 # 從右向左找首個小於基準數的元素 end while i < j && nums[i] <= nums[left] i += 1 # 從左向右找首個大於基準數的元素 end # 元素交換 nums[i], nums[j] = nums[j], nums[i] end # 將基準數交換至兩子陣列的分界線 nums[i], nums[left] = nums[left], nums[i] i # 返回基準數的索引 end ### 快速排序類別 ### def quick_sort(nums, left, right) # 子陣列長度不為 1 時遞迴 if left < right # 哨兵劃分 pivot = partition(nums, left, right) # 遞迴左子陣列、右子陣列 quick_sort(nums, left, pivot - 1) quick_sort(nums, pivot + 1, right) end nums end end end ### 快速排序類別(中位數最佳化)### class QuickSortMedian class << self ### 選取三個候選元素的中位數 ### def median_three(nums, left, mid, right) # 選取三個候選元素的中位數 _l, _m, _r = nums[left], nums[mid], nums[right] # m 在 l 和 r 之間 return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l) # l 在 m 和 r 之間 return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m) return right end ### 哨兵劃分(三數取中值)### def partition(nums, left, right) ### 以 nums[left] 為基準數 med = median_three(nums, left, (left + right) / 2, right) # 將中位數交換至陣列最左斷 nums[left], nums[med] = nums[med], nums[left] i, j = left, right while i < j while i < j && nums[j] >= nums[left] j -= 1 # 從右向左找首個小於基準數的元素 end while i < j && nums[i] <= nums[left] i += 1 # 從左向右找首個大於基準數的元素 end # 元素交換 nums[i], nums[j] = nums[j], nums[i] end # 將基準數交換至兩子陣列的分界線 nums[i], nums[left] = nums[left], nums[i] i # 返回基準數的索引 end ### 快速排序 ### def quick_sort(nums, left, right) # 子陣列長度不為 1 時遞迴 if left < right # 哨兵劃分 pivot = partition(nums, left, right) # 遞迴左子陣列、右子陣列 quick_sort(nums, left, pivot - 1) quick_sort(nums, pivot + 1, right) end nums end end end ### 快速排序類別(遞迴深度最佳化)### class QuickSortTailCall class << self ### 哨兵劃分 ### def partition(nums, left, right) # 以 nums[left]為基準數 i = left j = right while i < j while i < j && nums[j] >= nums[left] j -= 1 # 從右向左找首個小於基準數的元素 end while i < j && nums[i] <= nums[left] i += 1 # 從左向右找首個大於基準數的元素 end # 元素交換 nums[i], nums[j] = nums[j], nums[i] end # 將基準數交換至兩子陣列的分界線 nums[i], nums[left] = nums[left], nums[i] i # 返回基準數的索引 end ### 快速排序(遞迴深度最佳化)### def quick_sort(nums, left, right) # 子陣列長度不為 1 時遞迴 while left < right # 哨兵劃分 pivot = partition(nums, left, right) # 對兩個子陣列中較短的那個執行快速排序 if pivot - left < right - pivot quick_sort(nums, left, pivot - 1) left = pivot + 1 # 剩餘未排序區間為 [pivot + 1, right] else quick_sort(nums, pivot + 1, right) right = pivot - 1 # 剩餘未排序區間為 [left, pivot - 1] end end end end end ### Driver Code ### if __FILE__ == $0 # 快速排序 nums = [2, 4, 1, 0, 3, 5] QuickSort.quick_sort(nums, 0, nums.length - 1) puts "快速排序完成後 nums = #{nums}" # 快速排序(中位基準數最佳化) nums1 = [2, 4, 1, 0, 3, 5] QuickSortMedian.quick_sort(nums1, 0, nums1.length - 1) puts "快速排序(中位基準數最佳化)完成後 nums1 = #{nums1}" # 快速排序(遞迴深度最佳化) nums2 = [2, 4, 1, 0, 3, 5] QuickSortTailCall.quick_sort(nums2, 0, nums2.length - 1) puts "快速排序(遞迴深度最佳化)完成後 nums2 = #{nums2}" end ================================================ FILE: zh-hant/codes/ruby/chapter_sorting/radix_sort.rb ================================================ =begin File: radix_sort.rb Created Time: 2024-05-03 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) ### def digit(num, exp) # 轉入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 (num / exp) % 10 end ### 計數排序(根據 nums 第 k 位排序)### def counting_sort_digit(nums, exp) # 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 counter = Array.new(10, 0) n = nums.length # 統計 0~9 各數字的出現次數 for i in 0...n d = digit(nums[i], exp) # 獲取 nums[i] 第 k 位,記為 d counter[d] += 1 # 統計數字 d 的出現次數 end # 求前綴和,將“出現個數”轉換為“陣列索引” (1...10).each { |i| counter[i] += counter[i - 1] } # 倒序走訪,根據桶內統計結果,將各元素填入 res res = Array.new(n, 0) for i in (n - 1).downto(0) d = digit(nums[i], exp) j = counter[d] - 1 # 獲取 d 在陣列中的索引 j res[j] = nums[i] # 將當前元素填入索引 j counter[d] -= 1 # 將 d 的數量減 1 end # 使用結果覆蓋原陣列 nums (0...n).each { |i| nums[i] = res[i] } end ### 基數排序 ### def radix_sort(nums) # 獲取陣列的最大元素,用於判斷最大位數 m = nums.max # 按照從低位到高位的順序走訪 exp = 1 while exp <= m # 對陣列元素的第 k 位執行計數排序 # k = 1 -> exp = 1 # k = 2 -> exp = 10 # 即 exp = 10^(k-1) counting_sort_digit(nums, exp) exp *= 10 end end ### Driver Code ### if __FILE__ == $0 # 基數排序 nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ] radix_sort(nums) puts "基數排序完成後 nums = #{nums}" end ================================================ FILE: zh-hant/codes/ruby/chapter_sorting/selection_sort.rb ================================================ =begin File: selection_sort.rb Created Time: 2024-05-03 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 選擇排序 ### def selection_sort(nums) n = nums.length # 外迴圈:未排序區間為 [i, n-1] for i in 0...(n - 1) # 內迴圈:找到未排序區間內的最小元素 k = i for j in (i + 1)...n if nums[j] < nums[k] k = j # 記錄最小元素的索引 end end # 將該最小元素與未排序區間的首個元素交換 nums[i], nums[k] = nums[k], nums[i] end end ### Driver Code ### if __FILE__ == $0 nums = [4, 1, 3, 1, 5, 2] selection_sort(nums) puts "選擇排序完成後 nums = #{nums}" end ================================================ FILE: zh-hant/codes/ruby/chapter_stack_and_queue/array_deque.rb ================================================ =begin File: array_deque.rb Created Time: 2024-04-05 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 基於環形陣列實現的雙向佇列 ### class ArrayDeque ### 獲取雙向佇列的長度 ### attr_reader :size ### 建構子 ### def initialize(capacity) @nums = Array.new(capacity, 0) @front = 0 @size = 0 end ### 獲取雙向佇列的容量 ### def capacity @nums.length end ### 判斷雙向佇列是否為空 ### def is_empty? size.zero? end ### 佇列首入列 ### def push_first(num) if size == capacity puts '雙向佇列已滿' return end # 佇列首指標向左移動一位 # 透過取餘操作實現 front 越過陣列頭部後回到尾部 @front = index(@front - 1) # 將 num 新增至佇列首 @nums[@front] = num @size += 1 end ### 佇列尾入列 ### def push_last(num) if size == capacity puts '雙向佇列已滿' return end # 計算佇列尾指標,指向佇列尾索引 + 1 rear = index(@front + size) # 將 num 新增至佇列尾 @nums[rear] = num @size += 1 end ### 佇列首出列 ### def pop_first num = peek_first # 佇列首指標向後移動一位 @front = index(@front + 1) @size -= 1 num end ### 佇列尾出列 ### def pop_last num = peek_last @size -= 1 num end ### 訪問佇列首元素 ### def peek_first raise IndexError, '雙向佇列為空' if is_empty? @nums[@front] end ### 訪問佇列尾元素 ### def peek_last raise IndexError, '雙向佇列為空' if is_empty? # 計算尾元素索引 last = index(@front + size - 1) @nums[last] end ### 返回陣列用於列印 ### def to_array # 僅轉換有效長度範圍內的串列元素 res = [] for i in 0...size res << @nums[index(@front + i)] end res end private ### 計算環形陣列索引 ### def index(i) # 透過取餘操作實現陣列首尾相連 # 當 i 越過陣列尾部後,回到頭部 # 當 i 越過陣列頭部後,回到尾部 (i + capacity) % capacity end end ### Driver Code ### if __FILE__ == $0 # 初始化雙向佇列 deque = ArrayDeque.new(10) deque.push_last(3) deque.push_last(2) deque.push_last(5) puts "雙向佇列 deque = #{deque.to_array}" # 訪問元素 peek_first = deque.peek_first puts "佇列首元素 peek_first = #{peek_first}" peek_last = deque.peek_last puts "佇列尾元素 peek_last = #{peek_last}" # 元素入列 deque.push_last(4) puts "元素 4 佇列尾入列後 deque = #{deque.to_array}" deque.push_first(1) puts "元素 1 佇列尾入列後 deque = #{deque.to_array}" # 元素出列 pop_last = deque.pop_last puts "佇列尾出列元素 = #{pop_last},佇列尾出列後 deque = #{deque.to_array}" pop_first = deque.pop_first puts "佇列尾出列元素 = #{pop_first},佇列尾出列後 deque = #{deque.to_array}" # 獲取雙向佇列的長度 size = deque.size puts "雙向佇列長度 size = #{size}" # 判斷雙向佇列是否為空 is_empty = deque.is_empty? puts "雙向佇列是否為空 = #{is_empty}" end ================================================ FILE: zh-hant/codes/ruby/chapter_stack_and_queue/array_queue.rb ================================================ =begin File: array_queue.rb Created Time: 2024-04-05 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 基於環形陣列實現的佇列 ### class ArrayQueue ### 獲取佇列的長度 ### attr_reader :size ### 建構子 ### def initialize(size) @nums = Array.new(size, 0) # 用於儲存佇列元素的陣列 @front = 0 # 佇列首指標,指向佇列首元素 @size = 0 # 佇列長度 end ### 獲取佇列的容量 ### def capacity @nums.length end ### 判斷佇列是否為空 ### def is_empty? size.zero? end ### 入列 ### def push(num) raise IndexError, '佇列已滿' if size == capacity # 計算佇列尾指標,指向佇列尾索引 + 1 # 透過取餘操作實現 rear 越過陣列尾部後回到頭部 rear = (@front + size) % capacity # 將 num 新增至佇列尾 @nums[rear] = num @size += 1 end ### 出列 ### def pop num = peek # 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 @front = (@front + 1) % capacity @size -= 1 num end ### 訪問佇列首元素 ### def peek raise IndexError, '佇列為空' if is_empty? @nums[@front] end ### 返回串列用於列印 ### def to_array res = Array.new(size, 0) j = @front for i in 0...size res[i] = @nums[j % capacity] j += 1 end res end end ### Driver Code ### if __FILE__ == $0 # 初始化佇列 queue = ArrayQueue.new(10) # 元素入列 queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "佇列 queue = #{queue.to_array}" # 訪問佇列首元素 peek = queue.peek puts "佇列首元素 peek = #{peek}" # 元素出列 pop = queue.pop puts "出列元素 pop = #{pop}" puts "出列後 queue = #{queue.to_array}" # 獲取佇列的長度 size = queue.size puts "佇列長度 size = #{size}" # 判斷佇列是否為空 is_empty = queue.is_empty? puts "佇列是否為空 = #{is_empty}" # 測試環形陣列 for i in 0...10 queue.push(i) queue.pop puts "第 #{i} 輪入列 + 出列後 queue = #{queue.to_array}" end end ================================================ FILE: zh-hant/codes/ruby/chapter_stack_and_queue/array_stack.rb ================================================ =begin File: array_stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 基於陣列實現的堆疊 ### class ArrayStack ### 建構子 ### def initialize @stack = [] end ### 獲取堆疊的長度 ### def size @stack.length end ### 判斷堆疊是否為空 ### def is_empty? @stack.empty? end ### 入堆疊 ### def push(item) @stack << item end ### 出堆疊 ### def pop raise IndexError, '堆疊為空' if is_empty? @stack.pop end ### 訪問堆疊頂元素 ### def peek raise IndexError, '堆疊為空' if is_empty? @stack.last end ### 返回串列用於列印 ### def to_array @stack end end ### Driver Code ### if __FILE__ == $0 # 初始化堆疊 stack = ArrayStack.new # 元素入堆疊 stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) puts "堆疊 stack = #{stack.to_array}" # 訪問堆疊頂元素 peek = stack.peek puts "堆疊頂元素 peek = #{peek}" # 元素出堆疊 pop = stack.pop puts "出堆疊元素 pop = #{pop}" puts "出堆疊後 stack = #{stack.to_array}" # 獲取堆疊的長度 size = stack.size puts "堆疊的長度 size = #{size}" # 判斷是否為空 is_empty = stack.is_empty? puts "堆疊是否為空 = #{is_empty}" end ================================================ FILE: zh-hant/codes/ruby/chapter_stack_and_queue/deque.rb ================================================ =begin File: deque.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # 初始化雙向佇列 # Ruby 沒有內直的雙端佇列,只能把 Array 當作雙端佇列來使用 deque = [] # 元素如隊 deque << 2 deque << 5 deque << 4 # 請注意,由於是陣列,Array#unshift 方法的時間複雜度為 O(n) deque.unshift(3) deque.unshift(1) puts "雙向佇列 deque = #{deque}" # 訪問元素 peek_first = deque.first puts "佇列首元素 peek_first = #{peek_first}" peek_last = deque.last puts "佇列尾元素 peek_last = #{peek_last}" # 元素出列 # 請注意,由於是陣列, Array#shift 方法的時間複雜度為 O(n) pop_front = deque.shift puts "佇列首出列元素 pop_front = #{pop_front},佇列首出列後 deque = #{deque}" pop_back = deque.pop puts "佇列尾出列元素 pop_back = #{pop_back}, 佇列尾出列後 deque = #{deque}" # 獲取雙向佇列的長度 size = deque.length puts "雙向佇列長度 size = #{size}" # 判斷雙向佇列是否為空 is_empty = size.zero? puts "雙向佇列是否為空 = #{is_empty}" end ================================================ FILE: zh-hant/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb ================================================ =begin File: linkedlist_deque.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 雙向鏈結串列節點 class ListNode attr_accessor :val attr_accessor :next # 後繼節點引用 attr_accessor :prev # 前軀節點引用 ### 建構子 ### def initialize(val) @val = val end end ### 基於雙向鏈結串列實現的雙向佇列 ### class LinkedListDeque ### 獲取雙向佇列的長度 ### attr_reader :size ### 建構子 ### def initialize @front = nil # 頭節點 front @rear = nil # 尾節點 rear @size = 0 # 雙向佇列的長度 end ### 判斷雙向佇列是否為空 ### def is_empty? size.zero? end ### 入列操作 ### def push(num, is_front) node = ListNode.new(num) # 若鏈結串列為空, 則令 front 和 rear 都指向 node if is_empty? @front = @rear = node # 佇列首入列操作 elsif is_front # 將 node 新增至鏈結串列頭部 @front.prev = node node.next = @front @front = node # 更新頭節點 # 佇列尾入列操作 else # 將 node 新增至鏈結串列尾部 @rear.next = node node.prev = @rear @rear = node # 更新尾節點 end @size += 1 # 更新佇列長度 end ### 佇列首入列 ### def push_first(num) push(num, true) end ### 佇列尾入列 ### def push_last(num) push(num, false) end ### 出列操作 ### def pop(is_front) raise IndexError, '雙向佇列為空' if is_empty? # 佇列首出列操作 if is_front val = @front.val # 暫存頭節點值 # 刪除頭節點 fnext = @front.next unless fnext.nil? fnext.prev = nil @front.next = nil end @front = fnext # 更新頭節點 # 佇列尾出列操作 else val = @rear.val # 暫存尾節點值 # 刪除尾節點 rprev = @rear.prev unless rprev.nil? rprev.next = nil @rear.prev = nil end @rear = rprev # 更新尾節點 end @size -= 1 # 更新佇列長度 val end ### 佇列首出列 ### def pop_first pop(true) end ### 佇列首出列 ### def pop_last pop(false) end ### 訪問佇列首元素 ### def peek_first raise IndexError, '雙向佇列為空' if is_empty? @front.val end ### 訪問佇列尾元素 ### def peek_last raise IndexError, '雙向佇列為空' if is_empty? @rear.val end ### 返回陣列用於列印 ### def to_array node = @front res = Array.new(size, 0) for i in 0...size res[i] = node.val node = node.next end res end end ### Driver Code ### if __FILE__ == $0 # 初始化雙向佇列 deque = LinkedListDeque.new deque.push_last(3) deque.push_last(2) deque.push_last(5) puts "雙向佇列 deque = #{deque.to_array}" # 訪問元素 peek_first = deque.peek_first puts "佇列首元素 peek_first = #{peek_first}" peek_last = deque.peek_last puts "佇列首元素 peek_last = #{peek_last}" # 元素入列 deque.push_last(4) puts "元素 4 佇列尾入列後 deque = #{deque.to_array}" deque.push_first(1) puts "元素 1 佇列首入列後 deque = #{deque.to_array}" # 元素出列 pop_last = deque.pop_last puts "佇列尾出列元素 = #{pop_last}, 佇列尾出列後 deque = #{deque.to_array}" pop_first = deque.pop_first puts "佇列首出列元素 = #{pop_first},佇列首出列後 deque = #{deque.to_array}" # 獲取雙向佇列的長度 size = deque.size puts "雙向佇列長度 size = #{size}" # 判斷雙向佇列是否為空 is_empty = deque.is_empty? puts "雙向佇列是否為空 = #{is_empty}" end ================================================ FILE: zh-hant/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb ================================================ =begin File: linkedlist_queue.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### 基於鏈結串列頭現的佇列 ### class LinkedListQueue ### 獲取佇列的長度 ### attr_reader :size ### 建構子 ### def initialize @front = nil # 頭節點 front @rear = nil # 尾節點 rear @size = 0 end ### 判斷佇列是否為空 ### def is_empty? @front.nil? end ### 入列 ### def push(num) # 在尾節點後新增 num node = ListNode.new(num) # 如果佇列為空,則令頭,尾節點都指向該節點 if @front.nil? @front = node @rear = node # 如果佇列不為空,則令該節點新增到尾節點後 else @rear.next = node @rear = node end @size += 1 end ### 出列 ### def pop num = peek # 刪除頭節點 @front = @front.next @size -= 1 num end ### 訪問佇列首元素 ### def peek raise IndexError, '佇列為空' if is_empty? @front.val end ### 將鏈結串列為 Array 並返回 ### def to_array queue = [] temp = @front while temp queue << temp.val temp = temp.next end queue end end ### Driver Code ### if __FILE__ == $0 # 初始化佇列 queue = LinkedListQueue.new # 元素如隊 queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "佇列 queue = #{queue.to_array}" # 訪問佇列首元素 peek = queue.peek puts "佇列首元素 front = #{peek}" # 元素出列 pop_front = queue.pop puts "出列元素 pop = #{pop_front}" puts "出列後 queue = #{queue.to_array}" # 獲取佇列的長度 size = queue.size puts "佇列長度 size = #{size}" # 判斷佇列是否為空 is_empty = queue.is_empty? puts "佇列是否為空 = #{is_empty}" end ================================================ FILE: zh-hant/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb ================================================ =begin File: linkedlist_stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/list_node' ### 基於鏈結串列實現的堆疊 ### class LinkedListStack attr_reader :size ### 建構子 ### def initialize @size = 0 end ### 判斷堆疊是否為空 ### def is_empty? @peek.nil? end ### 入堆疊 ### def push(val) node = ListNode.new(val) node.next = @peek @peek = node @size += 1 end ### 出堆疊 ### def pop num = peek @peek = @peek.next @size -= 1 num end ### 訪問堆疊頂元素 ### def peek raise IndexError, '堆疊為空' if is_empty? @peek.val end ### 將鏈結串列轉化為 Array 並反回 ### def to_array arr = [] node = @peek while node arr << node.val node = node.next end arr.reverse end end ### Driver Code ### if __FILE__ == $0 # 初始化堆疊 stack = LinkedListStack.new # 元素入堆疊 stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) puts "堆疊 stack = #{stack.to_array}" # 訪問堆疊頂元素 peek = stack.peek puts "堆疊頂元素 peek = #{peek}" # 元素出堆疊 pop = stack.pop puts "出堆疊元素 pop = #{pop}" puts "出堆疊後 stack = #{stack.to_array}" # 獲取堆疊的長度 size = stack.size puts "堆疊的長度 size = #{size}" # 判斷是否為空 is_empty = stack.is_empty? puts "堆疊是否為空 = #{is_empty}" end ================================================ FILE: zh-hant/codes/ruby/chapter_stack_and_queue/queue.rb ================================================ =begin File: queue.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # 初始化佇列 # Ruby 內建的佇列(Thread::Queue) 沒有 peek 和走訪方法,可以把 Array 當作佇列來使用 queue = [] # 元素入列 queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) puts "佇列 queue = #{queue}" # 訪問佇列元素 peek = queue.first puts "佇列首元素 peek = #{peek}" # 元素出列 # 清注意,由於是陣列,Array#shift 方法時間複雜度為 O(n) pop = queue.shift puts "出列元素 pop = #{pop}" puts "出列後 queue = #{queue}" # 獲取佇列的長度 size = queue.length puts "佇列長度 size = #{size}" # 判斷佇列是否為空 is_empty = queue.empty? puts "佇列是否為空 = #{is_empty}" end ================================================ FILE: zh-hant/codes/ruby/chapter_stack_and_queue/stack.rb ================================================ =begin File: stack.rb Created Time: 2024-04-06 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### Driver Code ### if __FILE__ == $0 # 初始化堆疊 # Ruby 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 stack = [] # 元素入堆疊 stack << 1 stack << 3 stack << 2 stack << 5 stack << 4 puts "堆疊 stack = #{stack}" # 訪問堆疊頂元素 peek = stack.last puts "堆疊頂元素 peek = #{peek}" # 元素出堆疊 pop = stack.pop puts "出堆疊元素 pop = #{pop}" puts "出堆疊後 stack = #{stack}" # 獲取堆疊的長度 size = stack.length puts "堆疊的長度 size = #{size}" # 判斷是否為空 is_empty = stack.empty? puts "堆疊是否為空 = #{is_empty}" end ================================================ FILE: zh-hant/codes/ruby/chapter_tree/array_binary_tree.rb ================================================ =begin File: array_binary_tree.rb Created Time: 2024-04-17 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 陣列表示下的二元樹類別 ### class ArrayBinaryTree ### 建構子 ### def initialize(arr) @tree = arr.to_a end ### 串列容量 ### def size @tree.length end ### 獲取索引為 i 節點的值 ### def val(i) # 若索引越界,則返回 nil ,代表空位 return if i < 0 || i >= size @tree[i] end ### 獲取索引為 i 節點的左子節點的索引 ### def left(i) 2 * i + 1 end ### 獲取索引為 i 節點的右子節點的索引 ### def right(i) 2 * i + 2 end ### 獲取索引為 i 節點的父節點的索引 ### def parent(i) (i - 1) / 2 end ### 層序走訪 ### def level_order @res = [] # 直接走訪陣列 for i in 0...size @res << val(i) unless val(i).nil? end @res end ### 深度優先走訪 ### def dfs(i, order) return if val(i).nil? # 前序走訪 @res << val(i) if order == :pre dfs(left(i), order) # 中序走訪 @res << val(i) if order == :in dfs(right(i), order) # 後序走訪 @res << val(i) if order == :post end ### 前序走訪 ### def pre_order @res = [] dfs(0, :pre) @res end ### 中序走訪 ### def in_order @res = [] dfs(0, :in) @res end ### 後序走訪 ### def post_order @res = [] dfs(0, :post) @res end end ### Driver Code ### if __FILE__ == $0 # 初始化二元樹 # 這裡藉助了一個從陣列直接生成二元樹的函式 arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] root = arr_to_tree(arr) puts "\n初始化二元樹\n\n" puts '二元樹的陣列表示:' pp arr puts '二元樹的鏈結串列表示:' print_tree(root) # 陣列表示下的二元樹類別 abt = ArrayBinaryTree.new(arr) # 訪問節點 i = 1 l, r, _p = abt.left(i), abt.right(i), abt.parent(i) puts "\n當前節點的索引為 #{i} ,值為 #{abt.val(i).inspect}" puts "其左子節點的索引為 #{l} ,值為 #{abt.val(l).inspect}" puts "其右子節點的索引為 #{r} ,值為 #{abt.val(r).inspect}" puts "其父節點的索引為 #{_p} ,值為 #{abt.val(_p).inspect}" # 走訪樹 res = abt.level_order puts "\n層序走訪為: #{res}" res = abt.pre_order puts "前序走訪為: #{res}" res = abt.in_order puts "中序走訪為: #{res}" res = abt.post_order puts "後序走訪為: #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_tree/avl_tree.rb ================================================ =begin File: avl_tree.rb Created Time: 2024-04-17 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### AVL 樹 ### class AVLTree ### 建構子 ### def initialize @root = nil end ### 獲取二元樹根節點 ### def get_root @root end ### 獲取節點高度 ### def height(node) # 空節點高度為 -1 ,葉節點高度為 0 return node.height unless node.nil? -1 end ### 更新節點高度 ### def update_height(node) # 節點高度等於最高子樹高度 + 1 node.height = [height(node.left), height(node.right)].max + 1 end ### 獲取平衡因子 ### def balance_factor(node) # 空節點平衡因子為 0 return 0 if node.nil? # 節點平衡因子 = 左子樹高度 - 右子樹高度 height(node.left) - height(node.right) end ### 右旋操作 ### def right_rotate(node) child = node.left grand_child = child.right # 以 child 為原點,將 node 向右旋轉 child.right = node node.left = grand_child # 更新節點高度 update_height(node) update_height(child) # 返回旋轉後子樹的根節點 child end ### 左旋操作 ### def left_rotate(node) child = node.right grand_child = child.left # 以 child 為原點,將 node 向左旋轉 child.left = node node.right = grand_child # 更新節點高度 update_height(node) update_height(child) # 返回旋轉後子樹的根節點 child end ### 執行旋轉操作,使該子樹重新恢復平衡 ### def rotate(node) # 獲取節點 node 的平衡因子 balance_factor = balance_factor(node) # 左遍樹 if balance_factor > 1 if balance_factor(node.left) >= 0 # 右旋 return right_rotate(node) else # 先左旋後右旋 node.left = left_rotate(node.left) return right_rotate(node) end # 右遍樹 elsif balance_factor < -1 if balance_factor(node.right) <= 0 # 左旋 return left_rotate(node) else # 先右旋後左旋 node.right = right_rotate(node.right) return left_rotate(node) end end # 平衡樹,無須旋轉,直接返回 node end ### 插入節點 ### def insert(val) @root = insert_helper(@root, val) end ### 遞迴插入節點(輔助方法)### def insert_helper(node, val) return TreeNode.new(val) if node.nil? # 1. 查詢插入位置並插入節點 if val < node.val node.left = insert_helper(node.left, val) elsif val > node.val node.right = insert_helper(node.right, val) else # 重複節點不插入,直接返回 return node end # 更新節點高度 update_height(node) # 2. 執行旋轉操作,使該子樹重新恢復平衡 rotate(node) end ### 刪除節點 ### def remove(val) @root = remove_helper(@root, val) end ### 遞迴刪除節點(輔助方法)### def remove_helper(node, val) return if node.nil? # 1. 查詢節點並刪除 if val < node.val node.left = remove_helper(node.left, val) elsif val > node.val node.right = remove_helper(node.right, val) else if node.left.nil? || node.right.nil? child = node.left || node.right # 子節點數量 = 0 ,直接刪除 node 並返回 return if child.nil? # 子節點數量 = 1 ,直接刪除 node node = child else # 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 temp = node.right while !temp.left.nil? temp = temp.left end node.right = remove_helper(node.right, temp.val) node.val = temp.val end end # 更新節點高度 update_height(node) # 2. 執行旋轉操作,使該子樹重新恢復平衡 rotate(node) end ### 查詢節點 ### def search(val) cur = @root # 迴圈查詢,越過葉節點後跳出 while !cur.nil? # 目標節點在 cur 的右子樹中 if cur.val < val cur = cur.right # 目標節點在 cur 的左子樹中 elsif cur.val > val cur = cur.left # 找到目標節點,跳出迴圈 else break end end # 返回目標節點 cur end end ### Driver Code ### if __FILE__ == $0 def test_insert(tree, val) tree.insert(val) puts "\n插入節點 #{val} 後,AVL 樹為" print_tree(tree.get_root) end def test_remove(tree, val) tree.remove(val) puts "\n刪除節點 #{val} 後,AVL 樹為" print_tree(tree.get_root) end # 初始化空 AVL 樹 avl_tree = AVLTree.new # 插入節點 # 請關注插入節點後,AVL 樹是如何保持平衡的 for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6] test_insert(avl_tree, val) end # 插入重複節點 test_insert(avl_tree, 7) # 刪除節點 # 請關注刪除節點後,AVL 樹是如何保持平衡的 test_remove(avl_tree, 8) # 刪除度為 0 的節點 test_remove(avl_tree, 5) # 刪除度為 1 的節點 test_remove(avl_tree, 4) # 刪除度為 2 的節點 result_node = avl_tree.search(7) puts "\n查詢到的節點物件為 #{result_node},節點值 = #{result_node.val}" end ================================================ FILE: zh-hant/codes/ruby/chapter_tree/binary_search_tree.rb ================================================ =begin File: binary_search_tree.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 二元搜尋樹 ### class BinarySearchTree ### 建構子 ### def initialize # 初始化空樹 @root = nil end ### 獲取二元樹根節點 ### def get_root @root end ### 查詢節點 ### def search(num) cur = @root # 迴圈查詢,越過葉節點後跳出 while !cur.nil? # 目標節點在 cur 的右子樹中 if cur.val < num cur = cur.right # 目標節點在 cur 的左子樹中 elsif cur.val > num cur = cur.left # 找到目標節點,跳出迴圈 else break end end cur end ### 插入節點 ### def insert(num) # 若樹為空,則初始化根節點 if @root.nil? @root = TreeNode.new(num) return end # 迴圈查詢,越過葉節點後跳出 cur, pre = @root, nil while !cur.nil? # 找到重複節點,直接返回 return if cur.val == num pre = cur # 插入位置在 cur 的右子樹中 if cur.val < num cur = cur.right # 插入位置在 cur 的左子樹中 else cur = cur.left end end # 插入節點 node = TreeNode.new(num) if pre.val < num pre.right = node else pre.left = node end end ### 刪除節點 ### def remove(num) # 若樹為空,直接提前返回 return if @root.nil? # 迴圈查詢,越過葉節點後跳出 cur, pre = @root, nil while !cur.nil? # 找到待刪除節點,跳出迴圈 break if cur.val == num pre = cur # 待刪除節點在 cur 的右子樹中 if cur.val < num cur = cur.right # 待刪除節點在 cur 的左子樹中 else cur = cur.left end end # 若無待刪除節點,則直接返回 return if cur.nil? # 子節點數量 = 0 or 1 if cur.left.nil? || cur.right.nil? # 當子節點數量 = 0 / 1 時, child = null / 該子節點 child = cur.left || cur.right # 刪除節點 cur if cur != @root if pre.left == cur pre.left = child else pre.right = child end else # 若刪除節點為根節點,則重新指定根節點 @root = child end # 子節點數量 = 2 else # 獲取中序走訪中 cur 的下一個節點 tmp = cur.right while !tmp.left.nil? tmp = tmp.left end # 遞迴刪除節點 tmp remove(tmp.val) # 用 tmp 覆蓋 cur cur.val = tmp.val end end end ### Driver Code ### if __FILE__ == $0 # 初始化二元搜尋樹 bst = BinarySearchTree.new nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] # 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 nums.each { |num| bst.insert(num) } puts "\n初始化的二元樹為\n" print_tree(bst.get_root) # 查詢節點 node = bst.search(7) puts "\n查詢到的節點物件為: #{node},節點值 = #{node.val}" # 插入節點 bst.insert(16) puts "\n插入節點 16 後,二元樹為\n" print_tree(bst.get_root) # 刪除節點 bst.remove(1) puts "\n刪除節點 1 後,二元樹為\n" print_tree(bst.get_root) bst.remove(2) puts "\n刪除節點 2 後,二元樹為\n" print_tree(bst.get_root) bst.remove(4) puts "\n刪除節點 4 後,二元樹為\n" print_tree(bst.get_root) end ================================================ FILE: zh-hant/codes/ruby/chapter_tree/binary_tree.rb ================================================ =begin File: binary_tree.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### Driver Code ### if __FILE__ == $0 # 初始化二元樹 # 初始化節點 n1 = TreeNode.new(1) n2 = TreeNode.new(2) n3 = TreeNode.new(3) n4 = TreeNode.new(4) n5 = TreeNode.new(5) # 構建節點之間的引用(指標) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 puts "\n初始化二元樹\n\n" print_tree(n1) # 插入與刪除節點 _p = TreeNode.new(0) # 在 n1 -> n2 中間插入節點 _p n1.left = _p _p.left = n2 puts "\n插入節點 _p 後\n\n" print_tree(n1) # 刪除節點 n1.left = n2 puts "\n刪除節點 _p 後\n\n" print_tree(n1) end ================================================ FILE: zh-hant/codes/ruby/chapter_tree/binary_tree_bfs.rb ================================================ =begin File: binary_tree_bfs.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 層序走訪 ### def level_order(root) # 初始化佇列,加入根節點 queue = [root] # 初始化一個串列,用於儲存走訪序列 res = [] while !queue.empty? node = queue.shift # 隊列出隊 res << node.val # 儲存節點值 queue << node.left unless node.left.nil? # 左子節點入列 queue << node.right unless node.right.nil? # 右子節點入列 end res end ### Driver Code ### if __FILE__ == $0 # 初始化二元樹 # 這裡藉助了一個從陣列直接生成二元樹的函式 root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) puts "\n初始化二元樹\n\n" print_tree(root) # 層序走訪 res = level_order(root) puts "\n層序走訪的節點列印序列 = #{res}" end ================================================ FILE: zh-hant/codes/ruby/chapter_tree/binary_tree_dfs.rb ================================================ =begin File: binary_tree_dfs.rb Created Time: 2024-04-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative '../utils/tree_node' require_relative '../utils/print_util' ### 前序走訪 ### def pre_order(root) return if root.nil? # 訪問優先順序:根節點 -> 左子樹 -> 右子樹 $res << root.val pre_order(root.left) pre_order(root.right) end ### 中序走訪 ### def in_order(root) return if root.nil? # 訪問優先順序:左子樹 -> 根節點 -> 右子樹 in_order(root.left) $res << root.val in_order(root.right) end ### 後序走訪 ### def post_order(root) return if root.nil? # 訪問優先順序:左子樹 -> 右子樹 -> 根節點 post_order(root.left) post_order(root.right) $res << root.val end ### Driver Code ### if __FILE__ == $0 # 初始化二元樹 # 這裡藉助了一個從陣列直接生成二元樹的函式 root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) puts "\n初始化二元樹\n\n" print_tree(root) # 前序走訪 $res = [] pre_order(root) puts "\n前序走訪的節點列印序列 = #{$res}" # 中序走訪 $res.clear in_order(root) puts "\nn中序走訪的節點列印序列 = #{$res}" # 後序走訪 $res.clear post_order(root) puts "\nn後序走訪的節點列印序列 = #{$res}" end ================================================ FILE: zh-hant/codes/ruby/test_all.rb ================================================ require 'open3' start_time = Time.now ruby_code_dir = File.dirname(__FILE__) files = Dir.glob("#{ruby_code_dir}/chapter_*/*.rb") errors = [] files.each do |file| stdout, stderr, status = Open3.capture3("ruby #{file}") errors << stderr unless status.success? end puts "\x1b[34mTested #{files.count} files\x1b[m" unless errors.empty? puts "\x1b[33mFound exception in #{errors.length} files\x1b[m" raise errors.join("\n\n") else puts "\x1b[32mPASS\x1b[m" end puts "Testing finishes after #{((Time.now - start_time) * 1000).round} ms" ================================================ FILE: zh-hant/codes/ruby/utils/list_node.rb ================================================ =begin File: list_node.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 鏈結串列節點類別 ### class ListNode attr_accessor :val # 節點值 attr_accessor :next # 指向下一節點的引用 def initialize(val=0, next_node=nil) @val = val @next = next_node end end ### 將串列反序列化為鏈結串列 ### def arr_to_linked_list(arr) head = current = ListNode.new(arr[0]) for i in 1...arr.length current.next = ListNode.new(arr[i]) current = current.next end head end ### 將鏈結串列序列化為串列 ### def linked_list_to_arr(head) arr = [] while head arr << head.val head = head.next end end ================================================ FILE: zh-hant/codes/ruby/utils/print_util.rb ================================================ =begin File: print_util.rb Created Time: 2024-03-18 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end require_relative "./tree_node" ### 列印矩陣 ### def print_matrix(mat) s = [] mat.each { |arr| s << " #{arr.to_s}" } puts "[\n#{s.join(",\n")}\n]" end ### 列印鏈結串列 ### def print_linked_list(head) list = [] while head list << head.val head = head.next end puts "#{list.join(" -> ")}" end class Trunk attr_accessor :prev, :str def initialize(prev, str) @prev = prev @str = str end end def show_trunk(p) return if p.nil? show_trunk(p.prev) print p.str end ### 列印二元樹 ### # This tree printer is borrowed from TECHIE DELIGHT # https://www.techiedelight.com/c-program-print-binary-tree/ def print_tree(root, prev=nil, is_right=false) return if root.nil? prev_str = " " trunk = Trunk.new(prev, prev_str) print_tree(root.right, trunk, true) if prev.nil? trunk.str = "———" elsif is_right trunk.str = "/———" prev_str = " |" else trunk.str = "\\———" prev.str = prev_str end show_trunk(trunk) puts " #{root.val}" prev.str = prev_str if prev trunk.str = " |" print_tree(root.left, trunk, false) end ### 列印雜湊表 ### def print_hash_map(hmap) hmap.entries.each { |key, value| puts "#{key} -> #{value}" } end ### 列印堆積 ### def print_heap(heap) puts "堆積的陣列表示:#{heap}" puts "堆積的樹狀表示:" root = arr_to_tree(heap) print_tree(root) end ================================================ FILE: zh-hant/codes/ruby/utils/tree_node.rb ================================================ =begin File: tree_node.rb Created Time: 2024-03-30 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 二元樹節點類別 ### class TreeNode attr_accessor :val # 節點值 attr_accessor :height # 節點高度 attr_accessor :left # 左子節點引用 attr_accessor :right # 右子節點引用 def initialize(val=0) @val = val @height = 0 end end ### 將串列反序列化為二元數樹:遞迴 ### def arr_to_tree_dfs(arr, i) # 如果索引超出陣列長度,或者對應的元素為 nil ,則返回 nil return if i < 0 || i >= arr.length || arr[i].nil? # 構建當前節點 root = TreeNode.new(arr[i]) # 遞迴構建左右子樹 root.left = arr_to_tree_dfs(arr, 2 * i + 1) root.right = arr_to_tree_dfs(arr, 2 * i + 2) root end ### 將串列反序列化為二元樹 ### def arr_to_tree(arr) arr_to_tree_dfs(arr, 0) end ### 將二元樹序列化為串列:遞迴 ### def tree_to_arr_dfs(root, i, res) return if root.nil? res += Array.new(i - res.length + 1) if i >= res.length res[i] = root.val tree_to_arr_dfs(root.left, 2 * i + 1, res) tree_to_arr_dfs(root.right, 2 * i + 2, res) end ### 將二元樹序列化為串列 ### def tree_to_arr(root) res = [] tree_to_arr_dfs(root, 0, res) res end ================================================ FILE: zh-hant/codes/ruby/utils/vertex.rb ================================================ =begin File: vertex.rb Created Time: 2024-04-25 Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) =end ### 頂點類別 ### class Vertex attr_accessor :val def initialize(val) @val = val end end ### 輸入值串列 vals ,返回頂點串列 vets ### def vals_to_vets(vals) Array.new(vals.length) { |i| Vertex.new(vals[i]) } end ### 輸入頂點串列 vets, 返回值串列 vals ### def vets_to_vals(vets) Array.new(vets.length) { |i| vets[i].val } end ================================================ FILE: zh-hant/codes/rust/.gitignore ================================================ target/ Cargo.lock ================================================ FILE: zh-hant/codes/rust/Cargo.toml ================================================ [package] name = "hello-algo-rust" version = "0.1.0" edition = "2021" publish = false # Run Command: cargo run --bin time_complexity [[bin]] name = "time_complexity" path = "chapter_computational_complexity/time_complexity.rs" # Run Command: cargo run --bin worst_best_time_complexity [[bin]] name = "worst_best_time_complexity" path = "chapter_computational_complexity/worst_best_time_complexity.rs" # Run Command: cargo run --bin space_complexity [[bin]] name = "space_complexity" path = "chapter_computational_complexity/space_complexity.rs" # Run Command: cargo run --bin iteration [[bin]] name = "iteration" path = "chapter_computational_complexity/iteration.rs" # Run Command: cargo run --bin recursion [[bin]] name = "recursion" path = "chapter_computational_complexity/recursion.rs" # Run Command: cargo run --bin two_sum [[bin]] name = "two_sum" path = "chapter_searching/two_sum.rs" # Run Command: cargo run --bin array [[bin]] name = "array" path = "chapter_array_and_linkedlist/array.rs" # Run Command: cargo run --bin linked_list [[bin]] name = "linked_list" path = "chapter_array_and_linkedlist/linked_list.rs" # Run Command: cargo run --bin list [[bin]] name = "list" path = "chapter_array_and_linkedlist/list.rs" # Run Command: cargo run --bin my_list [[bin]] name = "my_list" path = "chapter_array_and_linkedlist/my_list.rs" # Run Command: cargo run --bin stack [[bin]] name = "stack" path = "chapter_stack_and_queue/stack.rs" # Run Command: cargo run --bin linkedlist_stack [[bin]] name = "linkedlist_stack" path = "chapter_stack_and_queue/linkedlist_stack.rs" # Run Command: cargo run --bin queue [[bin]] name = "queue" path = "chapter_stack_and_queue/queue.rs" # Run Command: cargo run --bin linkedlist_queue [[bin]] name = "linkedlist_queue" path = "chapter_stack_and_queue/linkedlist_queue.rs" # Run Command: cargo run --bin deque [[bin]] name = "deque" path = "chapter_stack_and_queue/deque.rs" # Run Command: cargo run --bin array_deque [[bin]] name = "array_deque" path = "chapter_stack_and_queue/array_deque.rs" # Run Command: cargo run --bin linkedlist_deque [[bin]] name = "linkedlist_deque" path = "chapter_stack_and_queue/linkedlist_deque.rs" # Run Command: cargo run --bin simple_hash [[bin]] name = "simple_hash" path = "chapter_hashing/simple_hash.rs" # Run Command: cargo run --bin hash_map [[bin]] name = "hash_map" path = "chapter_hashing/hash_map.rs" # Run Command: cargo run --bin array_hash_map [[bin]] name = "array_hash_map" path = "chapter_hashing/array_hash_map.rs" # Run Command: cargo run --bin build_in_hash [[bin]] name = "build_in_hash" path = "chapter_hashing/build_in_hash.rs" # Run Command: cargo run --bin hash_map_chaining [[bin]] name = "hash_map_chaining" path = "chapter_hashing/hash_map_chaining.rs" # Run Command: cargo run --bin hash_map_open_addressing [[bin]] name = "hash_map_open_addressing" path = "chapter_hashing/hash_map_open_addressing.rs" # Run Command: cargo run --bin binary_search [[bin]] name = "binary_search" path = "chapter_searching/binary_search.rs" # Run Command: cargo run --bin binary_search_edge [[bin]] name = "binary_search_edge" path = "chapter_searching/binary_search_edge.rs" # Run Command: cargo run --bin binary_search_insertion [[bin]] name = "binary_search_insertion" path = "chapter_searching/binary_search_insertion.rs" # Run Command: cargo run --bin bubble_sort [[bin]] name = "bubble_sort" path = "chapter_sorting/bubble_sort.rs" # Run Command: cargo run --bin insertion_sort [[bin]] name = "insertion_sort" path = "chapter_sorting/insertion_sort.rs" # Run Command: cargo run --bin quick_sort [[bin]] name = "quick_sort" path = "chapter_sorting/quick_sort.rs" # Run Command: cargo run --bin merge_sort [[bin]] name = "merge_sort" path = "chapter_sorting/merge_sort.rs" # Run Command: cargo run --bin selection_sort [[bin]] name = "selection_sort" path = "chapter_sorting/selection_sort.rs" # Run Command: cargo run --bin bucket_sort [[bin]] name = "bucket_sort" path = "chapter_sorting/bucket_sort.rs" # Run Command: cargo run --bin heap_sort [[bin]] name = "heap_sort" path = "chapter_sorting/heap_sort.rs" # Run Command: cargo run --bin counting_sort [[bin]] name = "counting_sort" path = "chapter_sorting/counting_sort.rs" # Run Command: cargo run --bin radix_sort [[bin]] name = "radix_sort" path = "chapter_sorting/radix_sort.rs" # Run Command: cargo run --bin array_stack [[bin]] name = "array_stack" path = "chapter_stack_and_queue/array_stack.rs" # Run Command: cargo run --bin array_queue [[bin]] name = "array_queue" path = "chapter_stack_and_queue/array_queue.rs" # Run Command: cargo run --bin array_binary_tree [[bin]] name = "array_binary_tree" path = "chapter_tree/array_binary_tree.rs" # Run Command: cargo run --bin avl_tree [[bin]] name = "avl_tree" path = "chapter_tree/avl_tree.rs" # Run Command: cargo run --bin binary_search_tree [[bin]] name = "binary_search_tree" path = "chapter_tree/binary_search_tree.rs" # Run Command: cargo run --bin binary_tree_bfs [[bin]] name = "binary_tree_bfs" path = "chapter_tree/binary_tree_bfs.rs" # Run Command: cargo run --bin binary_tree_dfs [[bin]] name = "binary_tree_dfs" path = "chapter_tree/binary_tree_dfs.rs" # Run Command: cargo run --bin binary_tree [[bin]] name = "binary_tree" path = "chapter_tree/binary_tree.rs" # Run Command: cargo run --bin heap [[bin]] name = "heap" path = "chapter_heap/heap.rs" # Run Command: cargo run --bin my_heap [[bin]] name = "my_heap" path = "chapter_heap/my_heap.rs" # Run Command: cargo run --bin top_k [[bin]] name = "top_k" path = "chapter_heap/top_k.rs" # Run Command: cargo run --bin graph_adjacency_list [[bin]] name = "graph_adjacency_list" path = "chapter_graph/graph_adjacency_list.rs" # Run Command: cargo run --bin graph_adjacency_matrix [[bin]] name = "graph_adjacency_matrix" path = "chapter_graph/graph_adjacency_matrix.rs" # Run Command: cargo run --bin graph_bfs [[bin]] name = "graph_bfs" path = "chapter_graph/graph_bfs.rs" # Run Command: cargo run --bin graph_dfs [[bin]] name = "graph_dfs" path = "chapter_graph/graph_dfs.rs" # Run Command: cargo run --bin linear_search [[bin]] name = "linear_search" path = "chapter_searching/linear_search.rs" # Run Command: cargo run --bin hashing_search [[bin]] name = "hashing_search" path = "chapter_searching/hashing_search.rs" # Run Command: cargo run --bin climbing_stairs_dfs [[bin]] name = "climbing_stairs_dfs" path = "chapter_dynamic_programming/climbing_stairs_dfs.rs" # Run Command: cargo run --bin climbing_stairs_dfs_mem [[bin]] name = "climbing_stairs_dfs_mem" path = "chapter_dynamic_programming/climbing_stairs_dfs_mem.rs" # Run Command: cargo run --bin climbing_stairs_dp [[bin]] name = "climbing_stairs_dp" path = "chapter_dynamic_programming/climbing_stairs_dp.rs" # Run Command: cargo run --bin min_cost_climbing_stairs_dp [[bin]] name = "min_cost_climbing_stairs_dp" path = "chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs" # Run Command: cargo run --bin climbing_stairs_constraint_dp [[bin]] name = "climbing_stairs_constraint_dp" path = "chapter_dynamic_programming/climbing_stairs_constraint_dp.rs" # Run Command: cargo run --bin climbing_stairs_backtrack [[bin]] name = "climbing_stairs_backtrack" path = "chapter_dynamic_programming/climbing_stairs_backtrack.rs" # Run Command: cargo run --bin subset_sum_i_naive [[bin]] name = "subset_sum_i_naive" path = "chapter_backtracking/subset_sum_i_naive.rs" # Run Command: cargo run --bin subset_sum_i [[bin]] name = "subset_sum_i" path = "chapter_backtracking/subset_sum_i.rs" # Run Command: cargo run --bin subset_sum_ii [[bin]] name = "subset_sum_ii" path = "chapter_backtracking/subset_sum_ii.rs" # Run Command: cargo run --bin coin_change [[bin]] name = "coin_change" path = "chapter_dynamic_programming/coin_change.rs" # Run Command: cargo run --bin coin_change_ii [[bin]] name = "coin_change_ii" path = "chapter_dynamic_programming/coin_change_ii.rs" # Run Command: cargo run --bin unbounded_knapsack [[bin]] name = "unbounded_knapsack" path = "chapter_dynamic_programming/unbounded_knapsack.rs" # Run Command: cargo run --bin knapsack [[bin]] name = "knapsack" path = "chapter_dynamic_programming/knapsack.rs" # Run Command: cargo run --bin min_path_sum [[bin]] name = "min_path_sum" path = "chapter_dynamic_programming/min_path_sum.rs" # Run Command: cargo run --bin edit_distance [[bin]] name = "edit_distance" path = "chapter_dynamic_programming/edit_distance.rs" # Run Command: cargo run --bin n_queens [[bin]] name = "n_queens" path = "chapter_backtracking/n_queens.rs" # Run Command: cargo run --bin permutations_i [[bin]] name = "permutations_i" path = "chapter_backtracking/permutations_i.rs" # Run Command: cargo run --bin permutations_ii [[bin]] name = "permutations_ii" path = "chapter_backtracking/permutations_ii.rs" # Run Command: cargo run --bin preorder_traversal_i_compact [[bin]] name = "preorder_traversal_i_compact" path = "chapter_backtracking/preorder_traversal_i_compact.rs" # Run Command: cargo run --bin preorder_traversal_ii_compact [[bin]] name = "preorder_traversal_ii_compact" path = "chapter_backtracking/preorder_traversal_ii_compact.rs" # Run Command: cargo run --bin preorder_traversal_iii_compact [[bin]] name = "preorder_traversal_iii_compact" path = "chapter_backtracking/preorder_traversal_iii_compact.rs" # Run Command: cargo run --bin preorder_traversal_iii_template [[bin]] name = "preorder_traversal_iii_template" path = "chapter_backtracking/preorder_traversal_iii_template.rs" # Run Command: cargo run --bin binary_search_recur [[bin]] name = "binary_search_recur" path = "chapter_divide_and_conquer/binary_search_recur.rs" # Run Command: cargo run --bin hanota [[bin]] name = "hanota" path = "chapter_divide_and_conquer/hanota.rs" # Run Command: cargo run --bin build_tree [[bin]] name = "build_tree" path = "chapter_divide_and_conquer/build_tree.rs" # Run Command: cargo run --bin coin_change_greedy [[bin]] name = "coin_change_greedy" path = "chapter_greedy/coin_change_greedy.rs" # Run Command: cargo run --bin fractional_knapsack [[bin]] name = "fractional_knapsack" path = "chapter_greedy/fractional_knapsack.rs" # Run Command: cargo run --bin max_capacity [[bin]] name = "max_capacity" path = "chapter_greedy/max_capacity.rs" # Run Command: cargo run --bin max_product_cutting [[bin]] name = "max_product_cutting" path = "chapter_greedy/max_product_cutting.rs" [dependencies] rand = "0.8.5" ================================================ FILE: zh-hant/codes/rust/chapter_array_and_linkedlist/array.rs ================================================ /* * File: array.rs * Created Time: 2023-01-15 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use rand::Rng; /* 隨機訪問元素 */ fn random_access(nums: &[i32]) -> i32 { // 在區間 [0, nums.len()) 中隨機抽取一個數字 let random_index = rand::thread_rng().gen_range(0..nums.len()); // 獲取並返回隨機元素 let random_num = nums[random_index]; random_num } /* 擴展陣列長度 */ fn extend(nums: &[i32], enlarge: usize) -> Vec { // 初始化一個擴展長度後的陣列 let mut res: Vec = vec![0; nums.len() + enlarge]; // 將原陣列中的所有元素複製到新 res[0..nums.len()].copy_from_slice(nums); // 返回擴展後的新陣列 res } /* 在陣列的索引 index 處插入元素 num */ fn insert(nums: &mut [i32], num: i32, index: usize) { // 把索引 index 以及之後的所有元素向後移動一位 for i in (index + 1..nums.len()).rev() { nums[i] = nums[i - 1]; } // 將 num 賦給 index 處的元素 nums[index] = num; } /* 刪除索引 index 處的元素 */ fn remove(nums: &mut [i32], index: usize) { // 把索引 index 之後的所有元素向前移動一位 for i in index..nums.len() - 1 { nums[i] = nums[i + 1]; } } /* 走訪陣列 */ fn traverse(nums: &[i32]) { let mut _count = 0; // 透過索引走訪陣列 for i in 0..nums.len() { _count += nums[i]; } // 直接走訪陣列元素 _count = 0; for &num in nums { _count += num; } } /* 在陣列中查詢指定元素 */ fn find(nums: &[i32], target: i32) -> Option { for i in 0..nums.len() { if nums[i] == target { return Some(i); } } None } /* Driver Code */ fn main() { /* 初始化陣列 */ let arr: [i32; 5] = [0; 5]; print!("陣列 arr = "); print_util::print_array(&arr); // 在 Rust 中,指定長度時([i32; 5])為陣列,不指定長度時(&[i32])為切片 // 由於 Rust 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度 // Vector 是 Rust 一般情況下用作動態陣列的型別 // 為了方便實現擴容 extend() 方法,以下將 vector 看作陣列(array) let nums: Vec = vec![1, 3, 2, 5, 4]; print!("\n陣列 nums = "); print_util::print_array(&nums); // 隨機訪問 let random_num = random_access(&nums); println!("\n在 nums 中獲取隨機元素 {}", random_num); // 長度擴展 let mut nums: Vec = extend(&nums, 3); print!("將陣列長度擴展至 8 ,得到 nums = "); print_util::print_array(&nums); // 插入元素 insert(&mut nums, 6, 3); print!("\n在索引 3 處插入數字 6 ,得到 nums = "); print_util::print_array(&nums); // 刪除元素 remove(&mut nums, 2); print!("\n刪除索引 2 處的元素,得到 nums = "); print_util::print_array(&nums); // 走訪陣列 traverse(&nums); // 查詢元素 let index = find(&nums, 3).unwrap(); println!("\n在 nums 中查詢元素 3 ,得到索引 = {}", index); } ================================================ FILE: zh-hant/codes/rust/chapter_array_and_linkedlist/linked_list.rs ================================================ /* * File: linked_list.rs * Created Time: 2023-03-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* 在鏈結串列的節點 n0 之後插入節點 P */ #[allow(non_snake_case)] pub fn insert(n0: &Rc>>, P: Rc>>) { let n1 = n0.borrow_mut().next.take(); P.borrow_mut().next = n1; n0.borrow_mut().next = Some(P); } /* 刪除鏈結串列的節點 n0 之後的首個節點 */ #[allow(non_snake_case)] pub fn remove(n0: &Rc>>) { // n0 -> P -> n1 let P = n0.borrow_mut().next.take(); if let Some(node) = P { let n1 = node.borrow_mut().next.take(); n0.borrow_mut().next = n1; } } /* 訪問鏈結串列中索引為 index 的節點 */ pub fn access(head: Rc>>, index: i32) -> Option>>> { fn dfs( head: Option<&Rc>>>, index: i32, ) -> Option>>> { if index <= 0 { return head.cloned(); } if let Some(node) = head { dfs(node.borrow().next.as_ref(), index - 1) } else { None } } dfs(Some(head).as_ref(), index) } /* 在鏈結串列中查詢值為 target 的首個節點 */ pub fn find(head: Rc>>, target: T) -> i32 { fn find(head: Option<&Rc>>>, target: T, idx: i32) -> i32 { if let Some(node) = head { if node.borrow().val == target { return idx; } return find(node.borrow().next.as_ref(), target, idx + 1); } else { -1 } } find(Some(head).as_ref(), target, 0) } /* Driver Code */ fn main() { /* 初始化鏈結串列 */ // 初始化各個節點 let n0 = ListNode::new(1); let n1 = ListNode::new(3); let n2 = ListNode::new(2); let n3 = ListNode::new(5); let n4 = ListNode::new(4); // 構建節點之間的引用 n0.borrow_mut().next = Some(n1.clone()); n1.borrow_mut().next = Some(n2.clone()); n2.borrow_mut().next = Some(n3.clone()); n3.borrow_mut().next = Some(n4.clone()); print!("初始化的鏈結串列為 "); print_util::print_linked_list(&n0); /* 插入節點 */ insert(&n0, ListNode::new(0)); print!("插入節點後的鏈結串列為 "); print_util::print_linked_list(&n0); /* 刪除節點 */ remove(&n0); print!("刪除節點後的鏈結串列為 "); print_util::print_linked_list(&n0); /* 訪問節點 */ let node = access(n0.clone(), 3); println!("鏈結串列中索引 3 處的節點的值 = {}", node.unwrap().borrow().val); /* 查詢節點 */ let index = find(n0.clone(), 2); println!("鏈結串列中值為 2 的節點的索引 = {}", index); } ================================================ FILE: zh-hant/codes/rust/chapter_array_and_linkedlist/list.rs ================================================ /* * File: list.rs * Created Time: 2023-01-18 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Driver Code */ fn main() { // 初始化串列 let mut nums: Vec = vec![1, 3, 2, 5, 4]; print!("串列 nums = "); print_util::print_array(&nums); // 訪問元素 let num = nums[1]; println!("\n訪問索引 1 處的元素,得到 num = {num}"); // 更新元素 nums[1] = 0; print!("將索引 1 處的元素更新為 0 ,得到 nums = "); print_util::print_array(&nums); // 清空串列 nums.clear(); print!("\n清空串列後 nums = "); print_util::print_array(&nums); // 在尾部新增元素 nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); print!("\n新增元素後 nums = "); print_util::print_array(&nums); // 在中間插入元素 nums.insert(3, 6); print!("\n在索引 3 處插入數字 6 ,得到 nums = "); print_util::print_array(&nums); // 刪除元素 nums.remove(3); print!("\n刪除索引 3 處的元素,得到 nums = "); print_util::print_array(&nums); // 透過索引走訪串列 let mut _count = 0; for i in 0..nums.len() { _count += nums[i]; } // 直接走訪串列元素 _count = 0; for x in &nums { _count += x; } // 拼接兩個串列 let mut nums1 = vec![6, 8, 7, 10, 9]; nums.append(&mut nums1); // append(移動) 之後 nums1 為空! // nums.extend(&nums1); // extend(借用) nums1 能繼續使用 print!("\n將串列 nums1 拼接到 nums 之後,得到 nums = "); print_util::print_array(&nums); // 排序串列 nums.sort(); print!("\n排序串列後 nums = "); print_util::print_array(&nums); } ================================================ FILE: zh-hant/codes/rust/chapter_array_and_linkedlist/my_list.rs ================================================ /* * File: my_list.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* 串列類別 */ #[allow(dead_code)] struct MyList { arr: Vec, // 陣列(儲存串列元素) capacity: usize, // 串列容量 size: usize, // 串列長度(當前元素數量) extend_ratio: usize, // 每次串列擴容的倍數 } #[allow(unused, unused_comparisons)] impl MyList { /* 建構子 */ pub fn new(capacity: usize) -> Self { let mut vec = vec![0; capacity]; Self { arr: vec, capacity, size: 0, extend_ratio: 2, } } /* 獲取串列長度(當前元素數量)*/ pub fn size(&self) -> usize { return self.size; } /* 獲取串列容量 */ pub fn capacity(&self) -> usize { return self.capacity; } /* 訪問元素 */ pub fn get(&self, index: usize) -> i32 { // 索引如果越界,則丟擲異常,下同 if index >= self.size { panic!("索引越界") }; return self.arr[index]; } /* 更新元素 */ pub fn set(&mut self, index: usize, num: i32) { if index >= self.size { panic!("索引越界") }; self.arr[index] = num; } /* 在尾部新增元素 */ pub fn add(&mut self, num: i32) { // 元素數量超出容量時,觸發擴容機制 if self.size == self.capacity() { self.extend_capacity(); } self.arr[self.size] = num; // 更新元素數量 self.size += 1; } /* 在中間插入元素 */ pub fn insert(&mut self, index: usize, num: i32) { if index >= self.size() { panic!("索引越界") }; // 元素數量超出容量時,觸發擴容機制 if self.size == self.capacity() { self.extend_capacity(); } // 將索引 index 以及之後的元素都向後移動一位 for j in (index..self.size).rev() { self.arr[j + 1] = self.arr[j]; } self.arr[index] = num; // 更新元素數量 self.size += 1; } /* 刪除元素 */ pub fn remove(&mut self, index: usize) -> i32 { if index >= self.size() { panic!("索引越界") }; let num = self.arr[index]; // 將索引 index 之後的元素都向前移動一位 for j in index..self.size - 1 { self.arr[j] = self.arr[j + 1]; } // 更新元素數量 self.size -= 1; // 返回被刪除的元素 return num; } /* 串列擴容 */ pub fn extend_capacity(&mut self) { // 新建一個長度為原陣列 extend_ratio 倍的新陣列,並將原陣列複製到新陣列 let new_capacity = self.capacity * self.extend_ratio; self.arr.resize(new_capacity, 0); // 更新串列容量 self.capacity = new_capacity; } /* 將串列轉換為陣列 */ pub fn to_array(&self) -> Vec { // 僅轉換有效長度範圍內的串列元素 let mut arr = Vec::new(); for i in 0..self.size { arr.push(self.get(i)); } arr } } /* Driver Code */ fn main() { /* 初始化串列 */ let mut nums = MyList::new(10); /* 在尾部新增元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); print!("串列 nums = "); print_util::print_array(&nums.to_array()); print!(" ,容量 = {} ,長度 = {}", nums.capacity(), nums.size()); /* 在中間插入元素 */ nums.insert(3, 6); print!("\n在索引 3 處插入數字 6 ,得到 nums = "); print_util::print_array(&nums.to_array()); /* 刪除元素 */ nums.remove(3); print!("\n刪除索引 3 處的元素,得到 nums = "); print_util::print_array(&nums.to_array()); /* 訪問元素 */ let num = nums.get(1); println!("\n訪問索引 1 處的元素,得到 num = {num}"); /* 更新元素 */ nums.set(1, 0); print!("將索引 1 處的元素更新為 0 ,得到 nums = "); print_util::print_array(&nums.to_array()); /* 測試擴容機制 */ for i in 0..10 { // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 nums.add(i); } print!("\n擴容後的串列 nums = "); print_util::print_array(&nums.to_array()); print!(" ,容量 = {} ,長度 = {}", nums.capacity(), nums.size()); } ================================================ FILE: zh-hant/codes/rust/chapter_backtracking/n_queens.rs ================================================ /* * File: n_queens.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* 回溯演算法:n 皇后 */ fn backtrack( row: usize, n: usize, state: &mut Vec>, res: &mut Vec>>, cols: &mut [bool], diags1: &mut [bool], diags2: &mut [bool], ) { // 當放置完所有行時,記錄解 if row == n { res.push(state.clone()); return; } // 走訪所有列 for col in 0..n { // 計算該格子對應的主對角線和次對角線 let diag1 = row + n - 1 - col; let diag2 = row + col; // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 if !cols[col] && !diags1[diag1] && !diags2[diag2] { // 嘗試:將皇后放置在該格子 state[row][col] = "Q".into(); (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true); // 放置下一行 backtrack(row + 1, n, state, res, cols, diags1, diags2); // 回退:將該格子恢復為空位 state[row][col] = "#".into(); (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false); } } } /* 求解 n 皇后 */ fn n_queens(n: usize) -> Vec>> { // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 let mut state: Vec> = vec![vec!["#".to_string(); n]; n]; let mut cols = vec![false; n]; // 記錄列是否有皇后 let mut diags1 = vec![false; 2 * n - 1]; // 記錄主對角線上是否有皇后 let mut diags2 = vec![false; 2 * n - 1]; // 記錄次對角線上是否有皇后 let mut res: Vec>> = Vec::new(); backtrack( 0, n, &mut state, &mut res, &mut cols, &mut diags1, &mut diags2, ); res } /* Driver Code */ pub fn main() { let n: usize = 4; let res = n_queens(n); println!("輸入棋盤長寬為 {n}"); println!("皇后放置方案共有 {} 種", res.len()); for state in res.iter() { println!("--------------------"); for row in state.iter() { println!("{:?}", row); } } } ================================================ FILE: zh-hant/codes/rust/chapter_backtracking/permutations_i.rs ================================================ /* * File: permutations_i.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* 回溯演算法:全排列 I */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // 當狀態長度等於元素數量時,記錄解 if state.len() == choices.len() { res.push(state); return; } // 走訪所有選擇 for i in 0..choices.len() { let choice = choices[i]; // 剪枝:不允許重複選擇元素 if !selected[i] { // 嘗試:做出選擇,更新狀態 selected[i] = true; state.push(choice); // 進行下一輪選擇 backtrack(state.clone(), choices, selected, res); // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false; state.pop(); } } } /* 全排列 I */ fn permutations_i(nums: &mut [i32]) -> Vec> { let mut res = Vec::new(); // 狀態(子集) backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [1, 2, 3]; let res = permutations_i(&mut nums); println!("輸入陣列 nums = {:?}", &nums); println!("所有排列 res = {:?}", &res); } ================================================ FILE: zh-hant/codes/rust/chapter_backtracking/permutations_ii.rs ================================================ /* * File: permutations_ii.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use std::collections::HashSet; /* 回溯演算法:全排列 II */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // 當狀態長度等於元素數量時,記錄解 if state.len() == choices.len() { res.push(state); return; } // 走訪所有選擇 let mut duplicated = HashSet::::new(); for i in 0..choices.len() { let choice = choices[i]; // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 if !selected[i] && !duplicated.contains(&choice) { // 嘗試:做出選擇,更新狀態 duplicated.insert(choice); // 記錄選擇過的元素值 selected[i] = true; state.push(choice); // 進行下一輪選擇 backtrack(state.clone(), choices, selected, res); // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false; state.pop(); } } } /* 全排列 II */ fn permutations_ii(nums: &mut [i32]) -> Vec> { let mut res = Vec::new(); backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [1, 2, 2]; let res = permutations_ii(&mut nums); println!("輸入陣列 nums = {:?}", &nums); println!("所有排列 res = {:?}", &res); } ================================================ FILE: zh-hant/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs ================================================ /* * File: preorder_traversal_i_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* 前序走訪:例題一 */ fn pre_order(res: &mut Vec>>, root: Option<&Rc>>) { if root.is_none() { return; } if let Some(node) = root { if node.borrow().val == 7 { // 記錄解 res.push(node.clone()); } pre_order(res, node.borrow().left.as_ref()); pre_order(res, node.borrow().right.as_ref()); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("初始化二元樹"); print_util::print_tree(root.as_ref().unwrap()); // 前序走訪 let mut res = Vec::new(); pre_order(&mut res, root.as_ref()); println!("\n輸出所有值為 7 的節點"); let mut vals = Vec::new(); for node in res { vals.push(node.borrow().val) } println!("{:?}", vals); } ================================================ FILE: zh-hant/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs ================================================ /* * File: preorder_traversal_ii_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* 前序走訪:例題二 */ fn pre_order( res: &mut Vec>>>, path: &mut Vec>>, root: Option<&Rc>>, ) { if root.is_none() { return; } if let Some(node) = root { // 嘗試 path.push(node.clone()); if node.borrow().val == 7 { // 記錄解 res.push(path.clone()); } pre_order(res, path, node.borrow().left.as_ref()); pre_order(res, path, node.borrow().right.as_ref()); // 回退 path.pop(); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("初始化二元樹"); print_util::print_tree(root.as_ref().unwrap()); // 前序走訪 let mut path = Vec::new(); let mut res = Vec::new(); pre_order(&mut res, &mut path, root.as_ref()); println!("\n輸出所有根節點到節點 7 的路徑"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs ================================================ /* * File: preorder_traversal_iii_compact.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* 前序走訪:例題三 */ fn pre_order( res: &mut Vec>>>, path: &mut Vec>>, root: Option<&Rc>>, ) { // 剪枝 if root.is_none() || root.as_ref().unwrap().borrow().val == 3 { return; } if let Some(node) = root { // 嘗試 path.push(node.clone()); if node.borrow().val == 7 { // 記錄解 res.push(path.clone()); } pre_order(res, path, node.borrow().left.as_ref()); pre_order(res, path, node.borrow().right.as_ref()); // 回退 path.pop(); } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("初始化二元樹"); print_util::print_tree(root.as_ref().unwrap()); // 前序走訪 let mut path = Vec::new(); let mut res = Vec::new(); pre_order(&mut res, &mut path, root.as_ref()); println!("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs ================================================ /* * File: preorder_traversal_iii_template.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use std::{cell::RefCell, rc::Rc}; /* 判斷當前狀態是否為解 */ fn is_solution(state: &mut Vec>>) -> bool { return !state.is_empty() && state.last().unwrap().borrow().val == 7; } /* 記錄解 */ fn record_solution( state: &mut Vec>>, res: &mut Vec>>>, ) { res.push(state.clone()); } /* 判斷在當前狀態下,該選擇是否合法 */ fn is_valid(_: &mut Vec>>, choice: Option<&Rc>>) -> bool { return choice.is_some() && choice.unwrap().borrow().val != 3; } /* 更新狀態 */ fn make_choice(state: &mut Vec>>, choice: Rc>) { state.push(choice); } /* 恢復狀態 */ fn undo_choice(state: &mut Vec>>, _: Rc>) { state.pop(); } /* 回溯演算法:例題三 */ fn backtrack( state: &mut Vec>>, choices: &Vec>>>, res: &mut Vec>>>, ) { // 檢查是否為解 if is_solution(state) { // 記錄解 record_solution(state, res); } // 走訪所有選擇 for &choice in choices.iter() { // 剪枝:檢查選擇是否合法 if is_valid(state, choice) { // 嘗試:做出選擇,更新狀態 make_choice(state, choice.unwrap().clone()); // 進行下一輪選擇 backtrack( state, &vec![ choice.unwrap().borrow().left.as_ref(), choice.unwrap().borrow().right.as_ref(), ], res, ); // 回退:撤銷選擇,恢復到之前的狀態 undo_choice(state, choice.unwrap().clone()); } } } /* Driver Code */ pub fn main() { let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); println!("初始化二元樹"); print_util::print_tree(root.as_ref().unwrap()); // 回溯演算法 let mut res = Vec::new(); backtrack(&mut Vec::new(), &mut vec![root.as_ref()], &mut res); println!("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點"); for path in res { let mut vals = Vec::new(); for node in path { vals.push(node.borrow().val) } println!("{:?}", vals); } } ================================================ FILE: zh-hant/codes/rust/chapter_backtracking/subset_sum_i.rs ================================================ /* * File: subset_sum_i.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 回溯演算法:子集和 I */ fn backtrack( state: &mut Vec, target: i32, choices: &[i32], start: usize, res: &mut Vec>, ) { // 子集和等於 target 時,記錄解 if target == 0 { res.push(state.clone()); return; } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 for i in start..choices.len() { // 剪枝一:若子集和超過 target ,則直接結束迴圈 // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target if target - choices[i] < 0 { break; } // 嘗試:做出選擇,更新 target, start state.push(choices[i]); // 進行下一輪選擇 backtrack(state, target - choices[i], choices, i, res); // 回退:撤銷選擇,恢復到之前的狀態 state.pop(); } } /* 求解子集和 I */ fn subset_sum_i(nums: &mut [i32], target: i32) -> Vec> { let mut state = Vec::new(); // 狀態(子集) nums.sort(); // 對 nums 進行排序 let start = 0; // 走訪起始點 let mut res = Vec::new(); // 結果串列(子集串列) backtrack(&mut state, target, nums, start, &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [3, 4, 5]; let target = 9; let res = subset_sum_i(&mut nums, target); println!("輸入陣列 nums = {:?}, target = {}", &nums, target); println!("所有和等於 {} 的子集 res = {:?}", target, &res); } ================================================ FILE: zh-hant/codes/rust/chapter_backtracking/subset_sum_i_naive.rs ================================================ /* * File: subset_sum_i_naive.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 回溯演算法:子集和 I */ fn backtrack( state: &mut Vec, target: i32, total: i32, choices: &[i32], res: &mut Vec>, ) { // 子集和等於 target 時,記錄解 if total == target { res.push(state.clone()); return; } // 走訪所有選擇 for i in 0..choices.len() { // 剪枝:若子集和超過 target ,則跳過該選擇 if total + choices[i] > target { continue; } // 嘗試:做出選擇,更新元素和 total state.push(choices[i]); // 進行下一輪選擇 backtrack(state, target, total + choices[i], choices, res); // 回退:撤銷選擇,恢復到之前的狀態 state.pop(); } } /* 求解子集和 I(包含重複子集) */ fn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec> { let mut state = Vec::new(); // 狀態(子集) let total = 0; // 子集和 let mut res = Vec::new(); // 結果串列(子集串列) backtrack(&mut state, target, total, nums, &mut res); res } /* Driver Code */ pub fn main() { let nums = [3, 4, 5]; let target = 9; let res = subset_sum_i_naive(&nums, target); println!("輸入陣列 nums = {:?}, target = {}", &nums, target); println!("所有和等於 {} 的子集 res = {:?}", target, &res); println!("請注意,該方法輸出的結果包含重複集合"); } ================================================ FILE: zh-hant/codes/rust/chapter_backtracking/subset_sum_ii.rs ================================================ /* * File: subset_sum_ii.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 回溯演算法:子集和 II */ fn backtrack( state: &mut Vec, target: i32, choices: &[i32], start: usize, res: &mut Vec>, ) { // 子集和等於 target 時,記錄解 if target == 0 { res.push(state.clone()); return; } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 for i in start..choices.len() { // 剪枝一:若子集和超過 target ,則直接結束迴圈 // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target if target - choices[i] < 0 { break; } // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 if i > start && choices[i] == choices[i - 1] { continue; } // 嘗試:做出選擇,更新 target, start state.push(choices[i]); // 進行下一輪選擇 backtrack(state, target - choices[i], choices, i + 1, res); // 回退:撤銷選擇,恢復到之前的狀態 state.pop(); } } /* 求解子集和 II */ fn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec> { let mut state = Vec::new(); // 狀態(子集) nums.sort(); // 對 nums 進行排序 let start = 0; // 走訪起始點 let mut res = Vec::new(); // 結果串列(子集串列) backtrack(&mut state, target, nums, start, &mut res); res } /* Driver Code */ pub fn main() { let mut nums = [4, 4, 5]; let target = 9; let res = subset_sum_ii(&mut nums, target); println!("輸入陣列 nums = {:?}, target = {}", &nums, target); println!("所有和等於 {} 的子集 res = {:?}", target, &res); } ================================================ FILE: zh-hant/codes/rust/chapter_computational_complexity/iteration.rs ================================================ /* * File: iteration.rs * Created Time: 2023-09-02 * Author: night-cruise (2586447362@qq.com) */ /* for 迴圈 */ fn for_loop(n: i32) -> i32 { let mut res = 0; // 迴圈求和 1, 2, ..., n-1, n for i in 1..=n { res += i; } res } /* while 迴圈 */ fn while_loop(n: i32) -> i32 { let mut res = 0; let mut i = 1; // 初始化條件變數 // 迴圈求和 1, 2, ..., n-1, n while i <= n { res += i; i += 1; // 更新條件變數 } res } /* while 迴圈(兩次更新) */ fn while_loop_ii(n: i32) -> i32 { let mut res = 0; let mut i = 1; // 初始化條件變數 // 迴圈求和 1, 4, 10, ... while i <= n { res += i; // 更新條件變數 i += 1; i *= 2; } res } /* 雙層 for 迴圈 */ fn nested_for_loop(n: i32) -> String { let mut res = vec![]; // 迴圈 i = 1, 2, ..., n-1, n for i in 1..=n { // 迴圈 j = 1, 2, ..., n-1, n for j in 1..=n { res.push(format!("({}, {}), ", i, j)); } } res.join("") } /* Driver Code */ fn main() { let n = 5; let mut res; res = for_loop(n); println!("\nfor 迴圈的求和結果 res = {res}"); res = while_loop(n); println!("\nwhile 迴圈的求和結果 res = {res}"); res = while_loop_ii(n); println!("\nwhile 迴圈(兩次更新)求和結果 res = {}", res); let res = nested_for_loop(n); println!("\n雙層 for 迴圈的走訪結果 {res}"); } ================================================ FILE: zh-hant/codes/rust/chapter_computational_complexity/recursion.rs ================================================ /* * File: recursion.rs * Created Time: 2023-09-02 * Author: night-cruise (2586447362@qq.com) */ /* 遞迴 */ fn recur(n: i32) -> i32 { // 終止條件 if n == 1 { return 1; } // 遞:遞迴呼叫 let res = recur(n - 1); // 迴:返回結果 n + res } /* 使用迭代模擬遞迴 */ fn for_loop_recur(n: i32) -> i32 { // 使用一個顯式的堆疊來模擬系統呼叫堆疊 let mut stack = Vec::new(); let mut res = 0; // 遞:遞迴呼叫 for i in (1..=n).rev() { // 透過“入堆疊操作”模擬“遞” stack.push(i); } // 迴:返回結果 while !stack.is_empty() { // 透過“出堆疊操作”模擬“迴” res += stack.pop().unwrap(); } // res = 1+2+3+...+n res } /* 尾遞迴 */ fn tail_recur(n: i32, res: i32) -> i32 { // 終止條件 if n == 0 { return res; } // 尾遞迴呼叫 tail_recur(n - 1, res + n) } /* 費波那契數列:遞迴 */ fn fib(n: i32) -> i32 { // 終止條件 f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1; } // 遞迴呼叫 f(n) = f(n-1) + f(n-2) let res = fib(n - 1) + fib(n - 2); // 返回結果 res } /* Driver Code */ fn main() { let n = 5; let mut res; res = recur(n); println!("\n遞迴函式的求和結果 res = {res}"); res = for_loop_recur(n); println!("\n使用迭代模擬遞迴求和結果 res = {res}"); res = tail_recur(n, 0); println!("\n尾遞迴函式的求和結果 res = {res}"); res = fib(n); println!("\n費波那契數列的第 {n} 項為 {res}"); } ================================================ FILE: zh-hant/codes/rust/chapter_computational_complexity/space_complexity.rs ================================================ /* * File: space_complexity.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode, TreeNode}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; /* 函式 */ fn function() -> i32 { // 執行某些操作 return 0; } /* 常數階 */ #[allow(unused)] fn constant(n: i32) { // 常數、變數、物件佔用 O(1) 空間 const A: i32 = 0; let b = 0; let nums = vec![0; 10000]; let node = ListNode::new(0); // 迴圈中的變數佔用 O(1) 空間 for i in 0..n { let c = 0; } // 迴圈中的函式佔用 O(1) 空間 for i in 0..n { function(); } } /* 線性階 */ #[allow(unused)] fn linear(n: i32) { // 長度為 n 的陣列佔用 O(n) 空間 let mut nums = vec![0; n as usize]; // 長度為 n 的串列佔用 O(n) 空間 let mut nodes = Vec::new(); for i in 0..n { nodes.push(ListNode::new(i)) } // 長度為 n 的雜湊表佔用 O(n) 空間 let mut map = HashMap::new(); for i in 0..n { map.insert(i, i.to_string()); } } /* 線性階(遞迴實現) */ fn linear_recur(n: i32) { println!("遞迴 n = {}", n); if n == 1 { return; }; linear_recur(n - 1); } /* 平方階 */ #[allow(unused)] fn quadratic(n: i32) { // 矩陣佔用 O(n^2) 空間 let num_matrix = vec![vec![0; n as usize]; n as usize]; // 二維串列佔用 O(n^2) 空間 let mut num_list = Vec::new(); for i in 0..n { let mut tmp = Vec::new(); for j in 0..n { tmp.push(0); } num_list.push(tmp); } } /* 平方階(遞迴實現) */ fn quadratic_recur(n: i32) -> i32 { if n <= 0 { return 0; }; // 陣列 nums 長度為 n, n-1, ..., 2, 1 let nums = vec![0; n as usize]; println!("遞迴 n = {} 中的 nums 長度 = {}", n, nums.len()); return quadratic_recur(n - 1); } /* 指數階(建立滿二元樹) */ fn build_tree(n: i32) -> Option>> { if n == 0 { return None; }; let root = TreeNode::new(0); root.borrow_mut().left = build_tree(n - 1); root.borrow_mut().right = build_tree(n - 1); return Some(root); } /* Driver Code */ fn main() { let n = 5; // 常數階 constant(n); // 線性階 linear(n); linear_recur(n); // 平方階 quadratic(n); quadratic_recur(n); // 指數階 let root = build_tree(n); print_util::print_tree(&root.unwrap()); } ================================================ FILE: zh-hant/codes/rust/chapter_computational_complexity/time_complexity.rs ================================================ /* * File: time_complexity.rs * Created Time: 2023-01-10 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ /* 常數階 */ fn constant(n: i32) -> i32 { _ = n; let mut count = 0; let size = 100_000; for _ in 0..size { count += 1; } count } /* 線性階 */ fn linear(n: i32) -> i32 { let mut count = 0; for _ in 0..n { count += 1; } count } /* 線性階(走訪陣列) */ fn array_traversal(nums: &[i32]) -> i32 { let mut count = 0; // 迴圈次數與陣列長度成正比 for _ in nums { count += 1; } count } /* 平方階 */ fn quadratic(n: i32) -> i32 { let mut count = 0; // 迴圈次數與資料大小 n 成平方關係 for _ in 0..n { for _ in 0..n { count += 1; } } count } /* 平方階(泡沫排序) */ fn bubble_sort(nums: &mut [i32]) -> i32 { let mut count = 0; // 計數器 // 外迴圈:未排序區間為 [0, i] for i in (1..nums.len()).rev() { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for j in 0..i { if nums[j] > nums[j + 1] { // 交換 nums[j] 與 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 元素交換包含 3 個單元操作 } } } count } /* 指數階(迴圈實現) */ fn exponential(n: i32) -> i32 { let mut count = 0; let mut base = 1; // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) for _ in 0..n { for _ in 0..base { count += 1 } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 count } /* 指數階(遞迴實現) */ fn exp_recur(n: i32) -> i32 { if n == 1 { return 1; } exp_recur(n - 1) + exp_recur(n - 1) + 1 } /* 對數階(迴圈實現) */ fn logarithmic(mut n: i32) -> i32 { let mut count = 0; while n > 1 { n = n / 2; count += 1; } count } /* 對數階(遞迴實現) */ fn log_recur(n: i32) -> i32 { if n <= 1 { return 0; } log_recur(n / 2) + 1 } /* 線性對數階 */ fn linear_log_recur(n: i32) -> i32 { if n <= 1 { return 1; } let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2); for _ in 0..n { count += 1; } return count; } /* 階乘階(遞迴實現) */ fn factorial_recur(n: i32) -> i32 { if n == 0 { return 1; } let mut count = 0; // 從 1 個分裂出 n 個 for _ in 0..n { count += factorial_recur(n - 1); } count } /* Driver Code */ fn main() { // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 let n: i32 = 8; println!("輸入資料大小 n = {}", n); let mut count = constant(n); println!("常數階的操作數量 = {}", count); count = linear(n); println!("線性階的操作數量 = {}", count); count = array_traversal(&vec![0; n as usize]); println!("線性階(走訪陣列)的操作數量 = {}", count); count = quadratic(n); println!("平方階的操作數量 = {}", count); let mut nums = (1..=n).rev().collect::>(); // [n,n-1,...,2,1] count = bubble_sort(&mut nums); println!("平方階(泡沫排序)的操作數量 = {}", count); count = exponential(n); println!("指數階(迴圈實現)的操作數量 = {}", count); count = exp_recur(n); println!("指數階(遞迴實現)的操作數量 = {}", count); count = logarithmic(n); println!("對數階(迴圈實現)的操作數量 = {}", count); count = log_recur(n); println!("對數階(遞迴實現)的操作數量 = {}", count); count = linear_log_recur(n); println!("線性對數階(遞迴實現)的操作數量 = {}", count); count = factorial_recur(n); println!("階乘階(遞迴實現)的操作數量 = {}", count); } ================================================ FILE: zh-hant/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs ================================================ /* * File: worst_best_time_complexity.rs * Created Time: 2023-01-13 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use rand::seq::SliceRandom; use rand::thread_rng; /* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ fn random_numbers(n: i32) -> Vec { // 生成陣列 nums = { 1, 2, 3, ..., n } let mut nums = (1..=n).collect::>(); // 隨機打亂陣列元素 nums.shuffle(&mut thread_rng()); nums } /* 查詢陣列 nums 中數字 1 所在索引 */ fn find_one(nums: &[i32]) -> Option { for i in 0..nums.len() { // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) if nums[i] == 1 { return Some(i); } } None } /* Driver Code */ fn main() { for _ in 0..10 { let n = 100; let nums = random_numbers(n); let index = find_one(&nums).unwrap(); print!("\n陣列 [ 1, 2, ..., n ] 被打亂後 = "); print_util::print_array(&nums); println!("\n數字 1 的索引為 {}", index); } } ================================================ FILE: zh-hant/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs ================================================ /* * File: binary_search_recur.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ /* 二分搜尋:問題 f(i, j) */ fn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 { // 若區間為空,代表無目標元素,則返回 -1 if i > j { return -1; } let m: i32 = i + (j - i) / 2; if nums[m as usize] < target { // 遞迴子問題 f(m+1, j) return dfs(nums, target, m + 1, j); } else if nums[m as usize] > target { // 遞迴子問題 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目標元素,返回其索引 return m; } } /* 二分搜尋 */ fn binary_search(nums: &[i32], target: i32) -> i32 { let n = nums.len() as i32; // 求解問題 f(0, n-1) dfs(nums, target, 0, n - 1) } /* Driver Code */ pub fn main() { let target = 6; let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分搜尋(雙閉區間) let index = binary_search(&nums, target); println!("目標元素 6 的索引 = {index}"); } ================================================ FILE: zh-hant/codes/rust/chapter_divide_and_conquer/build_tree.rs ================================================ /* * File: build_tree.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, TreeNode}; use std::collections::HashMap; use std::{cell::RefCell, rc::Rc}; /* 構建二元樹:分治 */ fn dfs( preorder: &[i32], inorder_map: &HashMap, i: i32, l: i32, r: i32, ) -> Option>> { // 子樹區間為空時終止 if r - l < 0 { return None; } // 初始化根節點 let root = TreeNode::new(preorder[i as usize]); // 查詢 m ,從而劃分左右子樹 let m = inorder_map.get(&preorder[i as usize]).unwrap(); // 子問題:構建左子樹 root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1); // 子問題:構建右子樹 root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r); // 返回根節點 Some(root) } /* 構建二元樹 */ fn build_tree(preorder: &[i32], inorder: &[i32]) -> Option>> { // 初始化雜湊表,儲存 inorder 元素到索引的對映 let mut inorder_map: HashMap = HashMap::new(); for i in 0..inorder.len() { inorder_map.insert(inorder[i], i as i32); } let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1); root } /* Driver Code */ fn main() { let preorder = [3, 9, 2, 1, 7]; let inorder = [9, 3, 1, 2, 7]; println!("中序走訪 = {:?}", preorder); println!("前序走訪 = {:?}", inorder); let root = build_tree(&preorder, &inorder); println!("構建的二元樹為:"); print_util::print_tree(root.as_ref().unwrap()); } ================================================ FILE: zh-hant/codes/rust/chapter_divide_and_conquer/hanota.rs ================================================ /* * File: hanota.rs * Created Time: 2023-07-15 * Author: codingonion (coderonion@gmail.com) */ #![allow(non_snake_case)] /* 移動一個圓盤 */ fn move_pan(src: &mut Vec, tar: &mut Vec) { // 從 src 頂部拿出一個圓盤 let pan = src.pop().unwrap(); // 將圓盤放入 tar 頂部 tar.push(pan); } /* 求解河內塔問題 f(i) */ fn dfs(i: i32, src: &mut Vec, buf: &mut Vec, tar: &mut Vec) { // 若 src 只剩下一個圓盤,則直接將其移到 tar if i == 1 { move_pan(src, tar); return; } // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf dfs(i - 1, src, tar, buf); // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar move_pan(src, tar); // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar dfs(i - 1, buf, src, tar); } /* 求解河內塔問題 */ fn solve_hanota(A: &mut Vec, B: &mut Vec, C: &mut Vec) { let n = A.len() as i32; // 將 A 頂部 n 個圓盤藉助 B 移到 C dfs(n, A, B, C); } /* Driver Code */ pub fn main() { let mut A = vec![5, 4, 3, 2, 1]; let mut B = Vec::new(); let mut C = Vec::new(); println!("初始狀態下:"); println!("A = {:?}", A); println!("B = {:?}", B); println!("C = {:?}", C); solve_hanota(&mut A, &mut B, &mut C); println!("圓盤移動完成後:"); println!("A = {:?}", A); println!("B = {:?}", B); println!("C = {:?}", C); } ================================================ FILE: zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs ================================================ /* * File: climbing_stairs_backtrack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 回溯 */ fn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) { // 當爬到第 n 階時,方案數量加 1 if state == n { res[0] = res[0] + 1; } // 走訪所有選擇 for &choice in choices { // 剪枝:不允許越過第 n 階 if state + choice > n { continue; } // 嘗試:做出選擇,更新狀態 backtrack(choices, state + choice, n, res); // 回退 } } /* 爬樓梯:回溯 */ fn climbing_stairs_backtrack(n: usize) -> i32 { let choices = vec![1, 2]; // 可選擇向上爬 1 階或 2 階 let state = 0; // 從第 0 階開始爬 let mut res = Vec::new(); res.push(0); // 使用 res[0] 記錄方案數量 backtrack(&choices, state, n as i32, &mut res); res[0] } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_backtrack(n); println!("爬 {n} 階樓梯共有 {res} 種方案"); } ================================================ FILE: zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs ================================================ /* * File: climbing_stairs_constraint_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 帶約束爬樓梯:動態規劃 */ fn climbing_stairs_constraint_dp(n: usize) -> i32 { if n == 1 || n == 2 { return 1; }; // 初始化 dp 表,用於儲存子問題的解 let mut dp = vec![vec![-1; 3]; n + 1]; // 初始狀態:預設最小子問題的解 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 狀態轉移:從較小子問題逐步求解較大子問題 for i in 3..=n { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } dp[n][1] + dp[n][2] } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_constraint_dp(n); println!("爬 {n} 階樓梯共有 {res} 種方案"); } ================================================ FILE: zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs ================================================ /* * File: climbing_stairs_dfs.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 搜尋 */ fn dfs(i: usize) -> i32 { // 已知 dp[1] 和 dp[2] ,返回之 if i == 1 || i == 2 { return i as i32; } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i - 1) + dfs(i - 2); count } /* 爬樓梯:搜尋 */ fn climbing_stairs_dfs(n: usize) -> i32 { dfs(n) } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dfs(n); println!("爬 {n} 階樓梯共有 {res} 種方案"); } ================================================ FILE: zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs ================================================ /* * File: climbing_stairs_dfs_mem.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 記憶化搜尋 */ fn dfs(i: usize, mem: &mut [i32]) -> i32 { // 已知 dp[1] 和 dp[2] ,返回之 if i == 1 || i == 2 { return i as i32; } // 若存在記錄 dp[i] ,則直接返回之 if mem[i] != -1 { return mem[i]; } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i - 1, mem) + dfs(i - 2, mem); // 記錄 dp[i] mem[i] = count; count } /* 爬樓梯:記憶化搜尋 */ fn climbing_stairs_dfs_mem(n: usize) -> i32 { // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 let mut mem = vec![-1; n + 1]; dfs(n, &mut mem) } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dfs_mem(n); println!("爬 {n} 階樓梯共有 {res} 種方案"); } ================================================ FILE: zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs ================================================ /* * File: climbing_stairs_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 爬樓梯:動態規劃 */ fn climbing_stairs_dp(n: usize) -> i32 { // 已知 dp[1] 和 dp[2] ,返回之 if n == 1 || n == 2 { return n as i32; } // 初始化 dp 表,用於儲存子問題的解 let mut dp = vec![-1; n + 1]; // 初始狀態:預設最小子問題的解 dp[1] = 1; dp[2] = 2; // 狀態轉移:從較小子問題逐步求解較大子問題 for i in 3..=n { dp[i] = dp[i - 1] + dp[i - 2]; } dp[n] } /* 爬樓梯:空間最佳化後的動態規劃 */ fn climbing_stairs_dp_comp(n: usize) -> i32 { if n == 1 || n == 2 { return n as i32; } let (mut a, mut b) = (1, 2); for _ in 3..=n { let tmp = b; b = a + b; a = tmp; } b } /* Driver Code */ pub fn main() { let n: usize = 9; let res = climbing_stairs_dp(n); println!("爬 {n} 階樓梯共有 {res} 種方案"); let res = climbing_stairs_dp_comp(n); println!("爬 {n} 階樓梯共有 {res} 種方案"); } ================================================ FILE: zh-hant/codes/rust/chapter_dynamic_programming/coin_change.rs ================================================ /* * File: coin_change.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 零錢兌換:動態規劃 */ fn coin_change_dp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); let max = amt + 1; // 初始化 dp 表 let mut dp = vec![vec![0; amt + 1]; n + 1]; // 狀態轉移:首行首列 for a in 1..=amt { dp[0][a] = max; } // 狀態轉移:其餘行和列 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1); } } } if dp[n][amt] != max { return dp[n][amt] as i32; } else { -1 } } /* 零錢兌換:空間最佳化後的動態規劃 */ fn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); let max = amt + 1; // 初始化 dp 表 let mut dp = vec![0; amt + 1]; dp.fill(max); dp[0] = 0; // 狀態轉移 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1); } } } if dp[amt] != max { return dp[amt] as i32; } else { -1 } } /* Driver Code */ pub fn main() { let coins = [1, 2, 5]; let amt: usize = 4; // 動態規劃 let res = coin_change_dp(&coins, amt); println!("湊到目標金額所需的最少硬幣數量為 {res}"); // 空間最佳化後的動態規劃 let res = coin_change_dp_comp(&coins, amt); println!("湊到目標金額所需的最少硬幣數量為 {res}"); } ================================================ FILE: zh-hant/codes/rust/chapter_dynamic_programming/coin_change_ii.rs ================================================ /* * File: coin_change_ii.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 零錢兌換 II:動態規劃 */ fn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); // 初始化 dp 表 let mut dp = vec![vec![0; amt + 1]; n + 1]; // 初始化首列 for i in 0..=n { dp[i][0] = 1; } // 狀態轉移 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a]; } else { // 不選和選硬幣 i 這兩種方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize]; } } } dp[n][amt] } /* 零錢兌換 II:空間最佳化後的動態規劃 */ fn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); // 初始化 dp 表 let mut dp = vec![0; amt + 1]; dp[0] = 1; // 狀態轉移 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案之和 dp[a] = dp[a] + dp[a - coins[i - 1] as usize]; } } } dp[amt] } /* Driver Code */ pub fn main() { let coins = [1, 2, 5]; let amt: usize = 5; // 動態規劃 let res = coin_change_ii_dp(&coins, amt); println!("湊出目標金額的硬幣組合數量為 {res}"); // 空間最佳化後的動態規劃 let res = coin_change_ii_dp_comp(&coins, amt); println!("湊出目標金額的硬幣組合數量為 {res}"); } ================================================ FILE: zh-hant/codes/rust/chapter_dynamic_programming/edit_distance.rs ================================================ /* * File: edit_distance.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 編輯距離:暴力搜尋 */ fn edit_distance_dfs(s: &str, t: &str, i: usize, j: usize) -> i32 { // 若 s 和 t 都為空,則返回 0 if i == 0 && j == 0 { return 0; } // 若 s 為空,則返回 t 長度 if i == 0 { return j as i32; } // 若 t 為空,則返回 s 長度 if j == 0 { return i as i32; } // 若兩字元相等,則直接跳過此兩字元 if s.chars().nth(i - 1) == t.chars().nth(j - 1) { return edit_distance_dfs(s, t, i - 1, j - 1); } // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 let insert = edit_distance_dfs(s, t, i, j - 1); let delete = edit_distance_dfs(s, t, i - 1, j); let replace = edit_distance_dfs(s, t, i - 1, j - 1); // 返回最少編輯步數 std::cmp::min(std::cmp::min(insert, delete), replace) + 1 } /* 編輯距離:記憶化搜尋 */ fn edit_distance_dfs_mem(s: &str, t: &str, mem: &mut Vec>, i: usize, j: usize) -> i32 { // 若 s 和 t 都為空,則返回 0 if i == 0 && j == 0 { return 0; } // 若 s 為空,則返回 t 長度 if i == 0 { return j as i32; } // 若 t 為空,則返回 s 長度 if j == 0 { return i as i32; } // 若已有記錄,則直接返回之 if mem[i][j] != -1 { return mem[i][j]; } // 若兩字元相等,則直接跳過此兩字元 if s.chars().nth(i - 1) == t.chars().nth(j - 1) { return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); } // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 let insert = edit_distance_dfs_mem(s, t, mem, i, j - 1); let delete = edit_distance_dfs_mem(s, t, mem, i - 1, j); let replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); // 記錄並返回最少編輯步數 mem[i][j] = std::cmp::min(std::cmp::min(insert, delete), replace) + 1; mem[i][j] } /* 編輯距離:動態規劃 */ fn edit_distance_dp(s: &str, t: &str) -> i32 { let (n, m) = (s.len(), t.len()); let mut dp = vec![vec![0; m + 1]; n + 1]; // 狀態轉移:首行首列 for i in 1..=n { dp[i][0] = i as i32; } for j in 1..m { dp[0][j] = j as i32; } // 狀態轉移:其餘行和列 for i in 1..=n { for j in 1..=m { if s.chars().nth(i - 1) == t.chars().nth(j - 1) { // 若兩字元相等,則直接跳過此兩字元 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[i][j] = std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } dp[n][m] } /* 編輯距離:空間最佳化後的動態規劃 */ fn edit_distance_dp_comp(s: &str, t: &str) -> i32 { let (n, m) = (s.len(), t.len()); let mut dp = vec![0; m + 1]; // 狀態轉移:首行 for j in 1..m { dp[j] = j as i32; } // 狀態轉移:其餘行 for i in 1..=n { // 狀態轉移:首列 let mut leftup = dp[0]; // 暫存 dp[i-1, j-1] dp[0] = i as i32; // 狀態轉移:其餘列 for j in 1..=m { let temp = dp[j]; if s.chars().nth(i - 1) == t.chars().nth(j - 1) { // 若兩字元相等,則直接跳過此兩字元 dp[j] = leftup; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 更新為下一輪的 dp[i-1, j-1] } } dp[m] } /* Driver Code */ pub fn main() { let s = "bag"; let t = "pack"; let (n, m) = (s.len(), t.len()); // 暴力搜尋 let res = edit_distance_dfs(s, t, n, m); println!("將 {s} 更改為 {t} 最少需要編輯 {res} 步"); // 記憶搜尋 let mut mem = vec![vec![0; m + 1]; n + 1]; for row in mem.iter_mut() { row.fill(-1); } let res = edit_distance_dfs_mem(s, t, &mut mem, n, m); println!("將 {s} 更改為 {t} 最少需要編輯 {res} 步"); // 動態規劃 let res = edit_distance_dp(s, t); println!("將 {s} 更改為 {t} 最少需要編輯 {res} 步"); // 空間最佳化後的動態規劃 let res = edit_distance_dp_comp(s, t); println!("將 {s} 更改為 {t} 最少需要編輯 {res} 步"); } ================================================ FILE: zh-hant/codes/rust/chapter_dynamic_programming/knapsack.rs ================================================ /* * File: knapsack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 0-1 背包:暴力搜尋 */ fn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if i == 0 || c == 0 { return 0; } // 若超過背包容量,則只能選擇不放入背包 if wgt[i - 1] > c as i32 { return knapsack_dfs(wgt, val, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 let no = knapsack_dfs(wgt, val, i - 1, c); let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; // 返回兩種方案中價值更大的那一個 std::cmp::max(no, yes) } /* 0-1 背包:記憶化搜尋 */ fn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec>, i: usize, c: usize) -> i32 { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if i == 0 || c == 0 { return 0; } // 若已有記錄,則直接返回 if mem[i][c] != -1 { return mem[i][c]; } // 若超過背包容量,則只能選擇不放入背包 if wgt[i - 1] > c as i32 { return knapsack_dfs_mem(wgt, val, mem, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c); let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; // 記錄並返回兩種方案中價值更大的那一個 mem[i][c] = std::cmp::max(no, yes); mem[i][c] } /* 0-1 背包:動態規劃 */ fn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // 初始化 dp 表 let mut dp = vec![vec![0; cap + 1]; n + 1]; // 狀態轉移 for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = std::cmp::max( dp[i - 1][c], dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1], ); } } } dp[n][cap] } /* 0-1 背包:空間最佳化後的動態規劃 */ fn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // 初始化 dp 表 let mut dp = vec![0; cap + 1]; // 狀態轉移 for i in 1..=n { // 倒序走訪 for c in (1..=cap).rev() { if wgt[i - 1] <= c as i32 { // 不選和選物品 i 這兩種方案的較大值 dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); } } } dp[cap] } /* Driver Code */ pub fn main() { let wgt = [10, 20, 30, 40, 50]; let val = [50, 120, 150, 210, 240]; let cap: usize = 50; let n = wgt.len(); // 暴力搜尋 let res = knapsack_dfs(&wgt, &val, n, cap); println!("不超過背包容量的最大物品價值為 {res}"); // 記憶搜尋 let mut mem = vec![vec![0; cap + 1]; n + 1]; for row in mem.iter_mut() { row.fill(-1); } let res = knapsack_dfs_mem(&wgt, &val, &mut mem, n, cap); println!("不超過背包容量的最大物品價值為 {res}"); // 動態規劃 let res = knapsack_dp(&wgt, &val, cap); println!("不超過背包容量的最大物品價值為 {res}"); // 空間最佳化後的動態規劃 let res = knapsack_dp_comp(&wgt, &val, cap); println!("不超過背包容量的最大物品價值為 {res}"); } ================================================ FILE: zh-hant/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs ================================================ /* * File: min_cost_climbing_stairs_dp.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use std::cmp; /* 爬樓梯最小代價:動態規劃 */ fn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 { let n = cost.len() - 1; if n == 1 || n == 2 { return cost[n]; } // 初始化 dp 表,用於儲存子問題的解 let mut dp = vec![-1; n + 1]; // 初始狀態:預設最小子問題的解 dp[1] = cost[1]; dp[2] = cost[2]; // 狀態轉移:從較小子問題逐步求解較大子問題 for i in 3..=n { dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i]; } dp[n] } /* 爬樓梯最小代價:空間最佳化後的動態規劃 */ fn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 { let n = cost.len() - 1; if n == 1 || n == 2 { return cost[n]; }; let (mut a, mut b) = (cost[1], cost[2]); for i in 3..=n { let tmp = b; b = cmp::min(a, tmp) + cost[i]; a = tmp; } b } /* Driver Code */ pub fn main() { let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; println!("輸入樓梯的代價串列為 {:?}", &cost); let res = min_cost_climbing_stairs_dp(&cost); println!("爬完樓梯的最低代價為 {res}"); let res = min_cost_climbing_stairs_dp_comp(&cost); println!("爬完樓梯的最低代價為 {res}"); } ================================================ FILE: zh-hant/codes/rust/chapter_dynamic_programming/min_path_sum.rs ================================================ /* * File: min_path_sum.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 最小路徑和:暴力搜尋 */ fn min_path_sum_dfs(grid: &Vec>, i: i32, j: i32) -> i32 { // 若為左上角單元格,則終止搜尋 if i == 0 && j == 0 { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if i < 0 || j < 0 { return i32::MAX; } // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 let up = min_path_sum_dfs(grid, i - 1, j); let left = min_path_sum_dfs(grid, i, j - 1); // 返回從左上角到 (i, j) 的最小路徑代價 std::cmp::min(left, up) + grid[i as usize][j as usize] } /* 最小路徑和:記憶化搜尋 */ fn min_path_sum_dfs_mem(grid: &Vec>, mem: &mut Vec>, i: i32, j: i32) -> i32 { // 若為左上角單元格,則終止搜尋 if i == 0 && j == 0 { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if i < 0 || j < 0 { return i32::MAX; } // 若已有記錄,則直接返回 if mem[i as usize][j as usize] != -1 { return mem[i as usize][j as usize]; } // 左邊和上邊單元格的最小路徑代價 let up = min_path_sum_dfs_mem(grid, mem, i - 1, j); let left = min_path_sum_dfs_mem(grid, mem, i, j - 1); // 記錄並返回左上角到 (i, j) 的最小路徑代價 mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize]; mem[i as usize][j as usize] } /* 最小路徑和:動態規劃 */ fn min_path_sum_dp(grid: &Vec>) -> i32 { let (n, m) = (grid.len(), grid[0].len()); // 初始化 dp 表 let mut dp = vec![vec![0; m]; n]; dp[0][0] = grid[0][0]; // 狀態轉移:首行 for j in 1..m { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 狀態轉移:首列 for i in 1..n { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 狀態轉移:其餘行和列 for i in 1..n { for j in 1..m { dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } dp[n - 1][m - 1] } /* 最小路徑和:空間最佳化後的動態規劃 */ fn min_path_sum_dp_comp(grid: &Vec>) -> i32 { let (n, m) = (grid.len(), grid[0].len()); // 初始化 dp 表 let mut dp = vec![0; m]; // 狀態轉移:首行 dp[0] = grid[0][0]; for j in 1..m { dp[j] = dp[j - 1] + grid[0][j]; } // 狀態轉移:其餘行 for i in 1..n { // 狀態轉移:首列 dp[0] = dp[0] + grid[i][0]; // 狀態轉移:其餘列 for j in 1..m { dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j]; } } dp[m - 1] } /* Driver Code */ pub fn main() { let grid = vec![ vec![1, 3, 1, 5], vec![2, 2, 4, 2], vec![5, 3, 2, 1], vec![4, 3, 5, 2], ]; let (n, m) = (grid.len(), grid[0].len()); // 暴力搜尋 let res = min_path_sum_dfs(&grid, n as i32 - 1, m as i32 - 1); println!("從左上角到右下角的最小路徑和為 {res}"); // 記憶化搜尋 let mut mem = vec![vec![0; m]; n]; for row in mem.iter_mut() { row.fill(-1); } let res = min_path_sum_dfs_mem(&grid, &mut mem, n as i32 - 1, m as i32 - 1); println!("從左上角到右下角的最小路徑和為 {res}"); // 動態規劃 let res = min_path_sum_dp(&grid); println!("從左上角到右下角的最小路徑和為 {res}"); // 空間最佳化後的動態規劃 let res = min_path_sum_dp_comp(&grid); println!("從左上角到右下角的最小路徑和為 {res}"); } ================================================ FILE: zh-hant/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs ================================================ /* * File: unbounded_knapsack.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ /* 完全背包:動態規劃 */ fn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // 初始化 dp 表 let mut dp = vec![vec![0; cap + 1]; n + 1]; // 狀態轉移 for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]); } } } return dp[n][cap]; } /* 完全背包:空間最佳化後的動態規劃 */ fn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // 初始化 dp 表 let mut dp = vec![0; cap + 1]; // 狀態轉移 for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // 若超過背包容量,則不選物品 i dp[c] = dp[c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); } } } dp[cap] } /* Driver Code */ pub fn main() { let wgt = [1, 2, 3]; let val = [5, 11, 15]; let cap: usize = 4; // 動態規劃 let res = unbounded_knapsack_dp(&wgt, &val, cap); println!("不超過背包容量的最大物品價值為 {res}"); // 空間最佳化後的動態規劃 let res = unbounded_knapsack_dp_comp(&wgt, &val, cap); println!("不超過背包容量的最大物品價值為 {res}"); } ================================================ FILE: zh-hant/codes/rust/chapter_graph/graph_adjacency_list.rs ================================================ /* * File: graph_adjacency_list.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ pub use hello_algo_rust::include::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::HashMap; /* 基於鄰接表實現的無向圖型別 */ pub struct GraphAdjList { // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 pub adj_list: HashMap>, // maybe HashSet for value part is better? } impl GraphAdjList { /* 建構子 */ pub fn new(edges: Vec<[Vertex; 2]>) -> Self { let mut graph = GraphAdjList { adj_list: HashMap::new(), }; // 新增所有頂點和邊 for edge in edges { graph.add_vertex(edge[0]); graph.add_vertex(edge[1]); graph.add_edge(edge[0], edge[1]); } graph } /* 獲取頂點數量 */ #[allow(unused)] pub fn size(&self) -> usize { self.adj_list.len() } /* 新增邊 */ pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) { if vet1 == vet2 { panic!("value error"); } // 新增邊 vet1 - vet2 self.adj_list.entry(vet1).or_default().push(vet2); self.adj_list.entry(vet2).or_default().push(vet1); } /* 刪除邊 */ #[allow(unused)] pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) { if vet1 == vet2 { panic!("value error"); } // 刪除邊 vet1 - vet2 self.adj_list .entry(vet1) .and_modify(|v| v.retain(|&e| e != vet2)); self.adj_list .entry(vet2) .and_modify(|v| v.retain(|&e| e != vet1)); } /* 新增頂點 */ pub fn add_vertex(&mut self, vet: Vertex) { if self.adj_list.contains_key(&vet) { return; } // 在鄰接表中新增一個新鏈結串列 self.adj_list.insert(vet, vec![]); } /* 刪除頂點 */ #[allow(unused)] pub fn remove_vertex(&mut self, vet: Vertex) { // 在鄰接表中刪除頂點 vet 對應的鏈結串列 self.adj_list.remove(&vet); // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 for list in self.adj_list.values_mut() { list.retain(|&v| v != vet); } } /* 列印鄰接表 */ pub fn print(&self) { println!("鄰接表 ="); for (vertex, list) in &self.adj_list { let list = list.iter().map(|vertex| vertex.val).collect::>(); println!("{}: {:?},", vertex.val, list); } } } /* Driver Code */ #[allow(unused)] fn main() { /* 初始化無向圖 */ let v = vals_to_vets(vec![1, 3, 2, 5, 4]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]], ]; let mut graph = GraphAdjList::new(edges); println!("\n初始化後,圖為"); graph.print(); /* 新增邊 */ // 頂點 1, 2 即 v[0], v[2] graph.add_edge(v[0], v[2]); println!("\n新增邊 1-2 後,圖為"); graph.print(); /* 刪除邊 */ // 頂點 1, 3 即 v[0], v[1] graph.remove_edge(v[0], v[1]); println!("\n刪除邊 1-3 後,圖為"); graph.print(); /* 新增頂點 */ let v5 = Vertex { val: 6 }; graph.add_vertex(v5); println!("\n新增頂點 6 後,圖為"); graph.print(); /* 刪除頂點 */ // 頂點 3 即 v[1] graph.remove_vertex(v[1]); println!("\n刪除頂點 3 後,圖為"); graph.print(); } ================================================ FILE: zh-hant/codes/rust/chapter_graph/graph_adjacency_matrix.rs ================================================ /* * File: graph_adjacency_matrix.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ /* 基於鄰接矩陣實現的無向圖型別 */ pub struct GraphAdjMat { // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” pub vertices: Vec, // 鄰接矩陣,行列索引對應“頂點索引” pub adj_mat: Vec>, } impl GraphAdjMat { /* 建構子 */ pub fn new(vertices: Vec, edges: Vec<[usize; 2]>) -> Self { let mut graph = GraphAdjMat { vertices: vec![], adj_mat: vec![], }; // 新增頂點 for val in vertices { graph.add_vertex(val); } // 新增邊 // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 for edge in edges { graph.add_edge(edge[0], edge[1]) } graph } /* 獲取頂點數量 */ pub fn size(&self) -> usize { self.vertices.len() } /* 新增頂點 */ pub fn add_vertex(&mut self, val: i32) { let n = self.size(); // 向頂點串列中新增新頂點的值 self.vertices.push(val); // 在鄰接矩陣中新增一行 self.adj_mat.push(vec![0; n]); // 在鄰接矩陣中新增一列 for row in self.adj_mat.iter_mut() { row.push(0); } } /* 刪除頂點 */ pub fn remove_vertex(&mut self, index: usize) { if index >= self.size() { panic!("index error") } // 在頂點串列中移除索引 index 的頂點 self.vertices.remove(index); // 在鄰接矩陣中刪除索引 index 的行 self.adj_mat.remove(index); // 在鄰接矩陣中刪除索引 index 的列 for row in self.adj_mat.iter_mut() { row.remove(index); } } /* 新增邊 */ pub fn add_edge(&mut self, i: usize, j: usize) { // 參數 i, j 對應 vertices 元素索引 // 索引越界與相等處理 if i >= self.size() || j >= self.size() || i == j { panic!("index error") } // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) self.adj_mat[i][j] = 1; self.adj_mat[j][i] = 1; } /* 刪除邊 */ // 參數 i, j 對應 vertices 元素索引 pub fn remove_edge(&mut self, i: usize, j: usize) { // 參數 i, j 對應 vertices 元素索引 // 索引越界與相等處理 if i >= self.size() || j >= self.size() || i == j { panic!("index error") } self.adj_mat[i][j] = 0; self.adj_mat[j][i] = 0; } /* 列印鄰接矩陣 */ pub fn print(&self) { println!("頂點串列 = {:?}", self.vertices); println!("鄰接矩陣 ="); println!("["); for row in &self.adj_mat { println!(" {:?},", row); } println!("]") } } /* Driver Code */ fn main() { /* 初始化無向圖 */ // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 let vertices = vec![1, 3, 2, 5, 4]; let edges = vec![[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]]; let mut graph = GraphAdjMat::new(vertices, edges); println!("\n初始化後,圖為"); graph.print(); /* 新增邊 */ // 頂點 1, 2 的索引分別為 0, 2 graph.add_edge(0, 2); println!("\n新增邊 1-2 後,圖為"); graph.print(); /* 刪除邊 */ // 頂點 1, 3 的索引分別為 0, 1 graph.remove_edge(0, 1); println!("\n刪除邊 1-3 後,圖為"); graph.print(); /* 新增頂點 */ graph.add_vertex(6); println!("\n新增頂點 6 後,圖為"); graph.print(); /* 刪除頂點 */ // 頂點 3 的索引為 1 graph.remove_vertex(1); println!("\n刪除頂點 3 後,圖為"); graph.print(); } ================================================ FILE: zh-hant/codes/rust/chapter_graph/graph_bfs.rs ================================================ /* * File: graph_bfs.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ mod graph_adjacency_list; use graph_adjacency_list::GraphAdjList; use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::{HashSet, VecDeque}; /* 廣度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { // 頂點走訪序列 let mut res = vec![]; // 雜湊集合,用於記錄已被訪問過的頂點 let mut visited = HashSet::new(); visited.insert(start_vet); // 佇列用於實現 BFS let mut que = VecDeque::new(); que.push_back(start_vet); // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 while let Some(vet) = que.pop_front() { res.push(vet); // 記錄訪問頂點 // 走訪該頂點的所有鄰接頂點 if let Some(adj_vets) = graph.adj_list.get(&vet) { for &adj_vet in adj_vets { if visited.contains(&adj_vet) { continue; // 跳過已被訪問的頂點 } que.push_back(adj_vet); // 只入列未訪問的頂點 visited.insert(adj_vet); // 標記該頂點已被訪問 } } } // 返回頂點走訪序列 res } /* Driver Code */ fn main() { /* 初始化無向圖 */ let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; let graph = GraphAdjList::new(edges); println!("\n初始化後,圖為"); graph.print(); /* 廣度優先走訪 */ let res = graph_bfs(graph, v[0]); println!("\n廣度優先走訪(BFS)頂點序列為"); println!("{:?}", vets_to_vals(res)); } ================================================ FILE: zh-hant/codes/rust/chapter_graph/graph_dfs.rs ================================================ /* * File: graph_dfs.rs * Created Time: 2023-07-12 * Author: night-cruise (2586447362@qq.com) */ mod graph_adjacency_list; use graph_adjacency_list::GraphAdjList; use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; use std::collections::HashSet; /* 深度優先走訪輔助函式 */ fn dfs(graph: &GraphAdjList, visited: &mut HashSet, res: &mut Vec, vet: Vertex) { res.push(vet); // 記錄訪問頂點 visited.insert(vet); // 標記該頂點已被訪問 // 走訪該頂點的所有鄰接頂點 if let Some(adj_vets) = graph.adj_list.get(&vet) { for &adj_vet in adj_vets { if visited.contains(&adj_vet) { continue; // 跳過已被訪問的頂點 } // 遞迴訪問鄰接頂點 dfs(graph, visited, res, adj_vet); } } } /* 深度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { // 頂點走訪序列 let mut res = vec![]; // 雜湊集合,用於記錄已被訪問過的頂點 let mut visited = HashSet::new(); dfs(&graph, &mut visited, &mut res, start_vet); res } /* Driver Code */ fn main() { /* 初始化無向圖 */ let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6]); let edges = vec![ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; let graph = GraphAdjList::new(edges); println!("\n初始化後,圖為"); graph.print(); /* 深度優先走訪 */ let res = graph_dfs(graph, v[0]); println!("\n深度優先走訪(DFS)頂點序列為"); println!("{:?}", vets_to_vals(res)); } ================================================ FILE: zh-hant/codes/rust/chapter_greedy/coin_change_greedy.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* 零錢兌換:貪婪 */ fn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 { // 假設 coins 串列有序 let mut i = coins.len() - 1; let mut count = 0; // 迴圈進行貪婪選擇,直到無剩餘金額 while amt > 0 { // 找到小於且最接近剩餘金額的硬幣 while i > 0 && coins[i] > amt { i -= 1; } // 選擇 coins[i] amt -= coins[i]; count += 1; } // 若未找到可行方案,則返回 -1 if amt == 0 { count } else { -1 } } /* Driver Code */ fn main() { // 貪婪:能夠保證找到全域性最優解 let coins = [1, 5, 10, 20, 50, 100]; let amt = 186; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("湊到 {} 所需的最少硬幣數量為 {}", amt, res); // 貪婪:無法保證找到全域性最優解 let coins = [1, 20, 50]; let amt = 60; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("湊到 {} 所需的最少硬幣數量為 {}", amt, res); println!("實際上需要的最少數量為 3 ,即 20 + 20 + 20"); // 貪婪:無法保證找到全域性最優解 let coins = [1, 49, 50]; let amt = 98; let res = coin_change_greedy(&coins, amt); println!("\ncoins = {:?}, amt = {}", coins, amt); println!("湊到 {} 所需的最少硬幣數量為 {}", amt, res); println!("實際上需要的最少數量為 2 ,即 49 + 49"); } ================================================ FILE: zh-hant/codes/rust/chapter_greedy/fractional_knapsack.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* 物品 */ struct Item { w: i32, // 物品重量 v: i32, // 物品價值 } impl Item { fn new(w: i32, v: i32) -> Self { Self { w, v } } } /* 分數背包:貪婪 */ fn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 { // 建立物品串列,包含兩個屬性:重量、價值 let mut items = wgt .iter() .zip(val.iter()) .map(|(&w, &v)| Item::new(w, v)) .collect::>(); // 按照單位價值 item.v / item.w 從高到低進行排序 items.sort_by(|a, b| { (b.v as f64 / b.w as f64) .partial_cmp(&(a.v as f64 / a.w as f64)) .unwrap() }); // 迴圈貪婪選擇 let mut res = 0.0; for item in &items { if item.w <= cap { // 若剩餘容量充足,則將當前物品整個裝進背包 res += item.v as f64; cap -= item.w; } else { // 若剩餘容量不足,則將當前物品的一部分裝進背包 res += item.v as f64 / item.w as f64 * cap as f64; // 已無剩餘容量,因此跳出迴圈 break; } } res } /* Driver Code */ fn main() { let wgt = [10, 20, 30, 40, 50]; let val = [50, 120, 150, 210, 240]; let cap = 50; // 貪婪演算法 let res = fractional_knapsack(&wgt, &val, cap); println!("不超過背包容量的最大物品價值為 {}", res); } ================================================ FILE: zh-hant/codes/rust/chapter_greedy/max_capacity.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* 最大容量:貪婪 */ fn max_capacity(ht: &[i32]) -> i32 { // 初始化 i, j,使其分列陣列兩端 let mut i = 0; let mut j = ht.len() - 1; // 初始最大容量為 0 let mut res = 0; // 迴圈貪婪選擇,直至兩板相遇 while i < j { // 更新最大容量 let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32; res = std::cmp::max(res, cap); // 向內移動短板 if ht[i] < ht[j] { i += 1; } else { j -= 1; } } res } /* Driver Code */ fn main() { let ht = [3, 8, 5, 2, 7, 7, 3, 4]; // 貪婪演算法 let res = max_capacity(&ht); println!("最大容量為 {}", res); } ================================================ FILE: zh-hant/codes/rust/chapter_greedy/max_product_cutting.rs ================================================ /* * File: coin_change_greedy.rs * Created Time: 2023-07-22 * Author: night-cruise (2586447362@qq.com) */ /* 最大切分乘積:貪婪 */ fn max_product_cutting(n: i32) -> i32 { // 當 n <= 3 時,必須切分出一個 1 if n <= 3 { return 1 * (n - 1); } // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 let a = n / 3; let b = n % 3; if b == 1 { // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 3_i32.pow(a as u32 - 1) * 2 * 2 } else if b == 2 { // 當餘數為 2 時,不做處理 3_i32.pow(a as u32) * 2 } else { // 當餘數為 0 時,不做處理 3_i32.pow(a as u32) } } /* Driver Code */ fn main() { let n = 58; // 貪婪演算法 let res = max_product_cutting(n); println!("最大切分乘積為 {}", res); } ================================================ FILE: zh-hant/codes/rust/chapter_hashing/array_hash_map.rs ================================================ /** * File: array_hash_map.rs * Created Time: 2023-2-18 * Author: xBLACICEx (xBLACKICEx@outlook.com) */ /* 鍵值對 */ #[derive(Debug, Clone, PartialEq)] pub struct Pair { pub key: i32, pub val: String, } /* 基於陣列實現的雜湊表 */ pub struct ArrayHashMap { buckets: Vec>, } impl ArrayHashMap { pub fn new() -> ArrayHashMap { // 初始化陣列,包含 100 個桶 Self { buckets: vec![None; 100], } } /* 雜湊函式 */ fn hash_func(&self, key: i32) -> usize { key as usize % 100 } /* 查詢操作 */ pub fn get(&self, key: i32) -> Option<&String> { let index = self.hash_func(key); self.buckets[index].as_ref().map(|pair| &pair.val) } /* 新增操作 */ pub fn put(&mut self, key: i32, val: &str) { let index = self.hash_func(key); self.buckets[index] = Some(Pair { key, val: val.to_string(), }); } /* 刪除操作 */ pub fn remove(&mut self, key: i32) { let index = self.hash_func(key); // 置為 None ,代表刪除 self.buckets[index] = None; } /* 獲取所有鍵值對 */ pub fn entry_set(&self) -> Vec<&Pair> { self.buckets .iter() .filter_map(|pair| pair.as_ref()) .collect() } /* 獲取所有鍵 */ pub fn key_set(&self) -> Vec<&i32> { self.buckets .iter() .filter_map(|pair| pair.as_ref().map(|pair| &pair.key)) .collect() } /* 獲取所有值 */ pub fn value_set(&self) -> Vec<&String> { self.buckets .iter() .filter_map(|pair| pair.as_ref().map(|pair| &pair.val)) .collect() } /* 列印雜湊表 */ pub fn print(&self) { for pair in self.entry_set() { println!("{} -> {}", pair.key, pair.val); } } } fn main() { /* 初始化雜湊表 */ let mut map = ArrayHashMap::new(); /*新增操作 */ // 在雜湊表中新增鍵值對(key, value) map.put(12836, "小哈"); map.put(15937, "小囉"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鴨"); println!("\n新增完成後,雜湊表為\nKey -> Value"); map.print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value let name = map.get(15937).unwrap(); println!("\n輸入學號 15937 ,查詢到姓名 {}", name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(10583); println!("\n刪除 10583 後,雜湊表為\nKey -> Value"); map.print(); /* 走訪雜湊表 */ println!("\n走訪鍵值對 Key->Value"); for pair in map.entry_set() { println!("{} -> {}", pair.key, pair.val); } println!("\n單獨走訪鍵 Key"); for key in map.key_set() { println!("{}", key); } println!("\n單獨走訪值 Value"); for val in map.value_set() { println!("{}", val); } } ================================================ FILE: zh-hant/codes/rust/chapter_hashing/build_in_hash.rs ================================================ /* * File: build_in_hash.rs * Created Time: 2023-7-6 * Author: WSL0809 (wslzzy@outlook.com) */ use hello_algo_rust::include::ListNode; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; /* Driver Code */ fn main() { let num = 3; let mut num_hasher = DefaultHasher::new(); num.hash(&mut num_hasher); let hash_num = num_hasher.finish(); println!("整數 {} 的雜湊值為 {}", num, hash_num); let bol = true; let mut bol_hasher = DefaultHasher::new(); bol.hash(&mut bol_hasher); let hash_bol = bol_hasher.finish(); println!("布林量 {} 的雜湊值為 {}", bol, hash_bol); let dec: f32 = 3.14159; let mut dec_hasher = DefaultHasher::new(); dec.to_bits().hash(&mut dec_hasher); let hash_dec = dec_hasher.finish(); println!("小數 {} 的雜湊值為 {}", dec, hash_dec); let str = "Hello 演算法"; let mut str_hasher = DefaultHasher::new(); str.hash(&mut str_hasher); let hash_str = str_hasher.finish(); println!("字串 {} 的雜湊值為 {}", str, hash_str); let arr = (&12836, &"小哈"); let mut tup_hasher = DefaultHasher::new(); arr.hash(&mut tup_hasher); let hash_tup = tup_hasher.finish(); println!("元組 {:?} 的雜湊值為 {}", arr, hash_tup); let node = ListNode::new(42); let mut hasher = DefaultHasher::new(); node.borrow().val.hash(&mut hasher); let hash = hasher.finish(); println!("節點物件 {:?} 的雜湊值為{}", node, hash); } ================================================ FILE: zh-hant/codes/rust/chapter_hashing/hash_map.rs ================================================ /* * File: hash_map.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::collections::HashMap; /* Driver Code */ pub fn main() { // 初始化雜湊表 let mut map = HashMap::new(); // 新增操作 // 在雜湊表中新增鍵值對 (key, value) map.insert(12836, "小哈"); map.insert(15937, "小囉"); map.insert(16750, "小算"); map.insert(13276, "小法"); map.insert(10583, "小鴨"); println!("\n新增完成後,雜湊表為\nKey -> Value"); print_util::print_hash_map(&map); // 查詢操作 // 向雜湊表中輸入鍵 key ,得到值 value let name = map.get(&15937).copied().unwrap(); println!("\n輸入學號 15937 ,查詢到姓名 {name}"); // 刪除操作 // 在雜湊表中刪除鍵值對 (key, value) _ = map.remove(&10583); println!("\n刪除 10583 後,雜湊表為\nKey -> Value"); print_util::print_hash_map(&map); // 走訪雜湊表 println!("\n走訪鍵值對 Key->Value"); print_util::print_hash_map(&map); println!("\n單獨走訪鍵 Key"); for key in map.keys() { println!("{key}"); } println!("\n單獨走訪值 value"); for value in map.values() { println!("{value}"); } } ================================================ FILE: zh-hant/codes/rust/chapter_hashing/hash_map_chaining.rs ================================================ /* * File: hash_map_chaining.rs * Created Time: 2023-07-07 * Author: WSL0809 (wslzzy@outlook.com) */ #[derive(Clone)] /* 鍵值對 */ struct Pair { key: i32, val: String, } /* 鏈式位址雜湊表 */ struct HashMapChaining { size: usize, capacity: usize, load_thres: f32, extend_ratio: usize, buckets: Vec>, } impl HashMapChaining { /* 建構子 */ fn new() -> Self { Self { size: 0, capacity: 4, load_thres: 2.0 / 3.0, extend_ratio: 2, buckets: vec![vec![]; 4], } } /* 雜湊函式 */ fn hash_func(&self, key: i32) -> usize { key as usize % self.capacity } /* 負載因子 */ fn load_factor(&self) -> f32 { self.size as f32 / self.capacity as f32 } /* 刪除操作 */ fn remove(&mut self, key: i32) -> Option { let index = self.hash_func(key); // 走訪桶,從中刪除鍵值對 for (i, p) in self.buckets[index].iter_mut().enumerate() { if p.key == key { let pair = self.buckets[index].remove(i); self.size -= 1; return Some(pair.val); } } // 若未找到 key ,則返回 None None } /* 擴容雜湊表 */ fn extend(&mut self) { // 暫存原雜湊表 let buckets_tmp = std::mem::take(&mut self.buckets); // 初始化擴容後的新雜湊表 self.capacity *= self.extend_ratio; self.buckets = vec![Vec::new(); self.capacity as usize]; self.size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for bucket in buckets_tmp { for pair in bucket { self.put(pair.key, pair.val); } } } /* 列印雜湊表 */ fn print(&self) { for bucket in &self.buckets { let mut res = Vec::new(); for pair in bucket { res.push(format!("{} -> {}", pair.key, pair.val)); } println!("{:?}", res); } } /* 新增操作 */ fn put(&mut self, key: i32, val: String) { // 當負載因子超過閾值時,執行擴容 if self.load_factor() > self.load_thres { self.extend(); } let index = self.hash_func(key); // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for pair in self.buckets[index].iter_mut() { if pair.key == key { pair.val = val; return; } } // 若無該 key ,則將鍵值對新增至尾部 let pair = Pair { key, val }; self.buckets[index].push(pair); self.size += 1; } /* 查詢操作 */ fn get(&self, key: i32) -> Option<&str> { let index = self.hash_func(key); // 走訪桶,若找到 key ,則返回對應 val for pair in self.buckets[index].iter() { if pair.key == key { return Some(&pair.val); } } // 若未找到 key ,則返回 None None } } /* Driver Code */ pub fn main() { /* 初始化雜湊表 */ let mut map = HashMapChaining::new(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(12836, "小哈".to_string()); map.put(15937, "小囉".to_string()); map.put(16750, "小算".to_string()); map.put(13276, "小法".to_string()); map.put(10583, "小鴨".to_string()); println!("\n新增完成後,雜湊表為\nKey -> Value"); map.print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value println!( "\n輸入學號 13276,查詢到姓名 {}", match map.get(13276) { Some(value) => value, None => "Not a valid Key", } ); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(12836); println!("\n刪除 12836 後,雜湊表為\nKey -> Value"); map.print(); } ================================================ FILE: zh-hant/codes/rust/chapter_hashing/hash_map_open_addressing.rs ================================================ /* * File: hash_map_open_addressing.rs * Created Time: 2023-07-16 * Author: WSL0809 (wslzzy@outlook.com), night-cruise (2586447362@qq.com) */ #![allow(non_snake_case)] #![allow(unused)] mod array_hash_map; use array_hash_map::Pair; /* 開放定址雜湊表 */ struct HashMapOpenAddressing { size: usize, // 鍵值對數量 capacity: usize, // 雜湊表容量 load_thres: f64, // 觸發擴容的負載因子閾值 extend_ratio: usize, // 擴容倍數 buckets: Vec>, // 桶陣列 TOMBSTONE: Option, // 刪除標記 } impl HashMapOpenAddressing { /* 建構子 */ fn new() -> Self { Self { size: 0, capacity: 4, load_thres: 2.0 / 3.0, extend_ratio: 2, buckets: vec![None; 4], TOMBSTONE: Some(Pair { key: -1, val: "-1".to_string(), }), } } /* 雜湊函式 */ fn hash_func(&self, key: i32) -> usize { (key % self.capacity as i32) as usize } /* 負載因子 */ fn load_factor(&self) -> f64 { self.size as f64 / self.capacity as f64 } /* 搜尋 key 對應的桶索引 */ fn find_bucket(&mut self, key: i32) -> usize { let mut index = self.hash_func(key); let mut first_tombstone = -1; // 線性探查,當遇到空桶時跳出 while self.buckets[index].is_some() { // 若遇到 key,返回對應的桶索引 if self.buckets[index].as_ref().unwrap().key == key { // 若之前遇到了刪除標記,則將建值對移動至該索引 if first_tombstone != -1 { self.buckets[first_tombstone as usize] = self.buckets[index].take(); self.buckets[index] = self.TOMBSTONE.clone(); return first_tombstone as usize; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE { first_tombstone = index as i32; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % self.capacity; } // 若 key 不存在,則返回新增點的索引 if first_tombstone == -1 { index } else { first_tombstone as usize } } /* 查詢操作 */ fn get(&mut self, key: i32) -> Option<&str> { // 搜尋 key 對應的桶索引 let index = self.find_bucket(key); // 若找到鍵值對,則返回對應 val if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { return self.buckets[index].as_ref().map(|pair| &pair.val as &str); } // 若鍵值對不存在,則返回 null None } /* 新增操作 */ fn put(&mut self, key: i32, val: String) { // 當負載因子超過閾值時,執行擴容 if self.load_factor() > self.load_thres { self.extend(); } // 搜尋 key 對應的桶索引 let index = self.find_bucket(key); // 若找到鍵值對,則覆蓋 val 並返回 if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { self.buckets[index].as_mut().unwrap().val = val; return; } // 若鍵值對不存在,則新增該鍵值對 self.buckets[index] = Some(Pair { key, val }); self.size += 1; } /* 刪除操作 */ fn remove(&mut self, key: i32) { // 搜尋 key 對應的桶索引 let index = self.find_bucket(key); // 若找到鍵值對,則用刪除標記覆蓋它 if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { self.buckets[index] = self.TOMBSTONE.clone(); self.size -= 1; } } /* 擴容雜湊表 */ fn extend(&mut self) { // 暫存原雜湊表 let buckets_tmp = self.buckets.clone(); // 初始化擴容後的新雜湊表 self.capacity *= self.extend_ratio; self.buckets = vec![None; self.capacity]; self.size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for pair in buckets_tmp { if pair.is_none() || pair == self.TOMBSTONE { continue; } let pair = pair.unwrap(); self.put(pair.key, pair.val); } } /* 列印雜湊表 */ fn print(&self) { for pair in &self.buckets { if pair.is_none() { println!("null"); } else if pair == &self.TOMBSTONE { println!("TOMBSTONE"); } else { let pair = pair.as_ref().unwrap(); println!("{} -> {}", pair.key, pair.val); } } } } /* Driver Code */ fn main() { /* 初始化雜湊表 */ let mut hashmap = HashMapOpenAddressing::new(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) hashmap.put(12836, "小哈".to_string()); hashmap.put(15937, "小囉".to_string()); hashmap.put(16750, "小算".to_string()); hashmap.put(13276, "小法".to_string()); hashmap.put(10583, "小鴨".to_string()); println!("\n新增完成後,雜湊表為\nKey -> Value"); hashmap.print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 val let name = hashmap.get(13276).unwrap(); println!("\n輸入學號 13276 ,查詢到姓名 {}", name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, val) hashmap.remove(16750); println!("\n刪除 16750 後,雜湊表為\nKey -> Value"); hashmap.print(); } ================================================ FILE: zh-hant/codes/rust/chapter_hashing/simple_hash.rs ================================================ /* * File: simple_hash.rs * Created Time: 2023-09-07 * Author: night-cruise (2586447362@qq.com) */ /* 加法雜湊 */ fn add_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = (hash + c as i64) % MODULUS; } hash as i32 } /* 乘法雜湊 */ fn mul_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = (31 * hash + c as i64) % MODULUS; } hash as i32 } /* 互斥或雜湊 */ fn xor_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash ^= c as i64; } (hash & MODULUS) as i32 } /* 旋轉雜湊 */ fn rot_hash(key: &str) -> i32 { let mut hash = 0_i64; const MODULUS: i64 = 1000000007; for c in key.chars() { hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS; } hash as i32 } /* Driver Code */ fn main() { let key = "Hello 演算法"; let hash = add_hash(key); println!("加法雜湊值為 {hash}"); let hash = mul_hash(key); println!("乘法雜湊值為 {hash}"); let hash = xor_hash(key); println!("互斥或雜湊值為 {hash}"); let hash = rot_hash(key); println!("旋轉雜湊值為 {hash}"); } ================================================ FILE: zh-hant/codes/rust/chapter_heap/heap.rs ================================================ /* * File: heap.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::{cmp::Reverse, collections::BinaryHeap}; fn test_push_max(heap: &mut BinaryHeap, val: i32) { heap.push(val); // 元素入堆積 println!("\n元素 {} 入堆積後", val); print_util::print_heap(heap.iter().map(|&val| val).collect()); } fn test_pop_max(heap: &mut BinaryHeap) { let val = heap.pop().unwrap(); println!("\n堆積頂元素 {} 出堆積後", val); print_util::print_heap(heap.iter().map(|&val| val).collect()); } /* Driver Code */ fn main() { /* 初始化堆積 */ // 初始化小頂堆積 #[allow(unused_assignments)] let mut min_heap = BinaryHeap::new(); // Rust 的 BinaryHeap 是大頂堆積,小頂堆積一般會“套上”Reverse // 初始化大頂堆積 let mut max_heap = BinaryHeap::new(); println!("\n以下測試樣例為大頂堆積"); /* 元素入堆積 */ test_push_max(&mut max_heap, 1); test_push_max(&mut max_heap, 3); test_push_max(&mut max_heap, 2); test_push_max(&mut max_heap, 5); test_push_max(&mut max_heap, 4); /* 獲取堆積頂元素 */ let peek = max_heap.peek().unwrap(); println!("\n堆積頂元素為 {}", peek); /* 堆積頂元素出堆積 */ test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); test_pop_max(&mut max_heap); /* 獲取堆積大小 */ let size = max_heap.len(); println!("\n堆積元素數量為 {}", size); /* 判斷堆積是否為空 */ let is_empty = max_heap.is_empty(); println!("\n堆積是否為空 {}", is_empty); /* 輸入串列並建堆積 */ // 時間複雜度為 O(n) ,而非 O(nlogn) min_heap = BinaryHeap::from( vec![1, 3, 2, 5, 4] .into_iter() .map(|val| Reverse(val)) .collect::>>(), ); println!("\n輸入串列並建立小頂堆積後"); print_util::print_heap(min_heap.iter().map(|&val| val.0).collect()); } ================================================ FILE: zh-hant/codes/rust/chapter_heap/my_heap.rs ================================================ /* * File: my_heap.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* 大頂堆積 */ struct MaxHeap { // 使用 vector 而非陣列,這樣無須考慮擴容問題 max_heap: Vec, } impl MaxHeap { /* 建構子,根據輸入串列建堆積 */ fn new(nums: Vec) -> Self { // 將串列元素原封不動新增進堆積 let mut heap = MaxHeap { max_heap: nums }; // 堆積化除葉節點以外的其他所有節點 for i in (0..=Self::parent(heap.size() - 1)).rev() { heap.sift_down(i); } heap } /* 獲取左子節點的索引 */ fn left(i: usize) -> usize { 2 * i + 1 } /* 獲取右子節點的索引 */ fn right(i: usize) -> usize { 2 * i + 2 } /* 獲取父節點的索引 */ fn parent(i: usize) -> usize { (i - 1) / 2 // 向下整除 } /* 交換元素 */ fn swap(&mut self, i: usize, j: usize) { self.max_heap.swap(i, j); } /* 獲取堆積大小 */ fn size(&self) -> usize { self.max_heap.len() } /* 判斷堆積是否為空 */ fn is_empty(&self) -> bool { self.max_heap.is_empty() } /* 訪問堆積頂元素 */ fn peek(&self) -> Option { self.max_heap.first().copied() } /* 元素入堆積 */ fn push(&mut self, val: i32) { // 新增節點 self.max_heap.push(val); // 從底至頂堆積化 self.sift_up(self.size() - 1); } /* 從節點 i 開始,從底至頂堆積化 */ fn sift_up(&mut self, mut i: usize) { loop { // 節點 i 已經是堆積頂節點了,結束堆積化 if i == 0 { break; } // 獲取節點 i 的父節點 let p = Self::parent(i); // 當“節點無須修復”時,結束堆積化 if self.max_heap[i] <= self.max_heap[p] { break; } // 交換兩節點 self.swap(i, p); // 迴圈向上堆積化 i = p; } } /* 元素出堆積 */ fn pop(&mut self) -> i32 { // 判空處理 if self.is_empty() { panic!("index out of bounds"); } // 交換根節點與最右葉節點(交換首元素與尾元素) self.swap(0, self.size() - 1); // 刪除節點 let val = self.max_heap.pop().unwrap(); // 從頂至底堆積化 self.sift_down(0); // 返回堆積頂元素 val } /* 從節點 i 開始,從頂至底堆積化 */ fn sift_down(&mut self, mut i: usize) { loop { // 判斷節點 i, l, r 中值最大的節點,記為 ma let (l, r, mut ma) = (Self::left(i), Self::right(i), i); if l < self.size() && self.max_heap[l] > self.max_heap[ma] { ma = l; } if r < self.size() && self.max_heap[r] > self.max_heap[ma] { ma = r; } // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if ma == i { break; } // 交換兩節點 self.swap(i, ma); // 迴圈向下堆積化 i = ma; } } /* 列印堆積(二元樹) */ fn print(&self) { print_util::print_heap(self.max_heap.clone()); } } /* Driver Code */ fn main() { /* 初始化大頂堆積 */ let mut max_heap = MaxHeap::new(vec![9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); println!("\n輸入串列並建堆積後"); max_heap.print(); /* 獲取堆積頂元素 */ let peek = max_heap.peek(); if let Some(peek) = peek { println!("\n堆積頂元素為 {}", peek); } /* 元素入堆積 */ let val = 7; max_heap.push(val); println!("\n元素 {} 入堆積後", val); max_heap.print(); /* 堆積頂元素出堆積 */ let peek = max_heap.pop(); println!("\n堆積頂元素 {} 出堆積後", peek); max_heap.print(); /* 獲取堆積大小 */ let size = max_heap.size(); println!("\n堆積元素數量為 {}", size); /* 判斷堆積是否為空 */ let is_empty = max_heap.is_empty(); println!("\n堆積是否為空 {}", is_empty); } ================================================ FILE: zh-hant/codes/rust/chapter_heap/top_k.rs ================================================ /* * File: top_k.rs * Created Time: 2023-07-16 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::cmp::Reverse; use std::collections::BinaryHeap; /* 基於堆積查詢陣列中最大的 k 個元素 */ fn top_k_heap(nums: Vec, k: usize) -> BinaryHeap> { // BinaryHeap 是大頂堆積,使用 Reverse 將元素取反,從而實現小頂堆積 let mut heap = BinaryHeap::>::new(); // 將陣列的前 k 個元素入堆積 for &num in nums.iter().take(k) { heap.push(Reverse(num)); } // 從第 k+1 個元素開始,保持堆積的長度為 k for &num in nums.iter().skip(k) { // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 if num > heap.peek().unwrap().0 { heap.pop(); heap.push(Reverse(num)); } } heap } /* Driver Code */ fn main() { let nums = vec![1, 7, 6, 3, 2]; let k = 3; let res = top_k_heap(nums, k); println!("最大的 {} 個元素為", k); print_util::print_heap(res.into_iter().map(|item| item.0).collect()); } ================================================ FILE: zh-hant/codes/rust/chapter_searching/binary_search.rs ================================================ /* * File: binary_search.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ /* 二分搜尋(雙閉區間) */ fn binary_search(nums: &[i32], target: i32) -> i32 { // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 let mut i = 0; let mut j = nums.len() as i32 - 1; // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) while i <= j { let m = i + (j - i) / 2; // 計算中點索引 m if nums[m as usize] < target { // 此情況說明 target 在區間 [m+1, j] 中 i = m + 1; } else if nums[m as usize] > target { // 此情況說明 target 在區間 [i, m-1] 中 j = m - 1; } else { // 找到目標元素,返回其索引 return m; } } // 未找到目標元素,返回 -1 return -1; } /* 二分搜尋(左閉右開區間) */ fn binary_search_lcro(nums: &[i32], target: i32) -> i32 { // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 let mut i = 0; let mut j = nums.len() as i32; // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) while i < j { let m = i + (j - i) / 2; // 計算中點索引 m if nums[m as usize] < target { // 此情況說明 target 在區間 [m+1, j) 中 i = m + 1; } else if nums[m as usize] > target { // 此情況說明 target 在區間 [i, m) 中 j = m; } else { // 找到目標元素,返回其索引 return m; } } // 未找到目標元素,返回 -1 return -1; } /* Driver Code */ pub fn main() { let target = 6; let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分搜尋(雙閉區間) let mut index = binary_search(&nums, target); println!("目標元素 6 的索引 = {index}"); // 二分搜尋(左閉右開區間) index = binary_search_lcro(&nums, target); println!("目標元素 6 的索引 = {index}"); } ================================================ FILE: zh-hant/codes/rust/chapter_searching/binary_search_edge.rs ================================================ /* * File: binary_search_edge.rs * Created Time: 2023-08-30 * Author: night-cruise (2586447362@qq.com) */ mod binary_search_insertion; use binary_search_insertion::binary_search_insertion; /* 二分搜尋最左一個 target */ fn binary_search_left_edge(nums: &[i32], target: i32) -> i32 { // 等價於查詢 target 的插入點 let i = binary_search_insertion(nums, target); // 未找到 target ,返回 -1 if i == nums.len() as i32 || nums[i as usize] != target { return -1; } // 找到 target ,返回索引 i i } /* 二分搜尋最右一個 target */ fn binary_search_right_edge(nums: &[i32], target: i32) -> i32 { // 轉化為查詢最左一個 target + 1 let i = binary_search_insertion(nums, target + 1); // j 指向最右一個 target ,i 指向首個大於 target 的元素 let j = i - 1; // 未找到 target ,返回 -1 if j == -1 || nums[j as usize] != target { return -1; } // 找到 target ,返回索引 j j } /* Driver Code */ fn main() { // 包含重複元素的陣列 let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; println!("\n陣列 nums = {:?}", nums); // 二分搜尋左邊界和右邊界 for target in [6, 7] { let index = binary_search_left_edge(&nums, target); println!("最左一個元素 {} 的索引為 {}", target, index); let index = binary_search_right_edge(&nums, target); println!("最右一個元素 {} 的索引為 {}", target, index); } } ================================================ FILE: zh-hant/codes/rust/chapter_searching/binary_search_insertion.rs ================================================ /* * File: binary_search_insertion.rs * Created Time: 2023-08-30 * Author: night-cruise (2586447362@qq.com) */ #![allow(unused)] /* 二分搜尋插入點(無重複元素) */ fn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 { let (mut i, mut j) = (0, nums.len() as i32 - 1); // 初始化雙閉區間 [0, n-1] while i <= j { let m = i + (j - i) / 2; // 計算中點索引 m if nums[m as usize] < target { i = m + 1; // target 在區間 [m+1, j] 中 } else if nums[m as usize] > target { j = m - 1; // target 在區間 [i, m-1] 中 } else { return m; } } // 未找到 target ,返回插入點 i i } /* 二分搜尋插入點(存在重複元素) */ pub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 { let (mut i, mut j) = (0, nums.len() as i32 - 1); // 初始化雙閉區間 [0, n-1] while i <= j { let m = i + (j - i) / 2; // 計算中點索引 m if nums[m as usize] < target { i = m + 1; // target 在區間 [m+1, j] 中 } else if nums[m as usize] > target { j = m - 1; // target 在區間 [i, m-1] 中 } else { j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 } } // 返回插入點 i i } /* Driver Code */ fn main() { // 無重複元素的陣列 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; println!("\n陣列 nums = {:?}", nums); // 二分搜尋插入點 for target in [6, 9] { let index = binary_search_insertion_simple(&nums, target); println!("元素 {} 的插入點的索引為 {}", target, index); } // 包含重複元素的陣列 let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; println!("\n陣列 nums = {:?}", nums); // 二分搜尋插入點 for target in [2, 6, 20] { let index = binary_search_insertion(&nums, target); println!("元素 {} 的插入點的索引為 {}", target, index); } } ================================================ FILE: zh-hant/codes/rust/chapter_searching/hashing_search.rs ================================================ /* * File: hashing_search.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::ListNode; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; /* 雜湊查詢(陣列) */ fn hashing_search_array<'a>(map: &'a HashMap, target: i32) -> Option<&'a usize> { // 雜湊表的 key: 目標元素,value: 索引 // 若雜湊表中無此 key ,返回 None map.get(&target) } /* 雜湊查詢(鏈結串列) */ fn hashing_search_linked_list( map: &HashMap>>>, target: i32, ) -> Option<&Rc>>> { // 雜湊表的 key: 目標節點值,value: 節點物件 // 若雜湊表中無此 key ,返回 None map.get(&target) } /* Driver Code */ pub fn main() { let target = 3; /* 雜湊查詢(陣列) */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // 初始化雜湊表 let mut map = HashMap::new(); for (i, num) in nums.iter().enumerate() { map.insert(*num, i); // key: 元素,value: 索引 } let index = hashing_search_array(&map, target); println!("目標元素 3 的索引 = {}", index.unwrap()); /* 雜湊查詢(鏈結串列) */ let head = ListNode::arr_to_linked_list(&nums); // 初始化雜湊表 // let mut map1 = HashMap::new(); let map1 = ListNode::linked_list_to_hashmap(head); let node = hashing_search_linked_list(&map1, target); println!("目標節點值 3 的對應節點物件為 {:?}", node); } ================================================ FILE: zh-hant/codes/rust/chapter_searching/linear_search.rs ================================================ /* * File: linear_search.rs * Created Time: 2023-07-09 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::ListNode; use std::cell::RefCell; use std::rc::Rc; /* 線性查詢(陣列) */ fn linear_search_array(nums: &[i32], target: i32) -> i32 { // 走訪陣列 for (i, num) in nums.iter().enumerate() { // 找到目標元素,返回其索引 if num == &target { return i as i32; } } // 未找到目標元素,返回 -1 return -1; } /* 線性查詢(鏈結串列) */ fn linear_search_linked_list( head: Rc>>, target: i32, ) -> Option>>> { // 找到目標節點,返回之 if head.borrow().val == target { return Some(head); }; // 找到目標節點,返回之 if let Some(node) = &head.borrow_mut().next { return linear_search_linked_list(node.clone(), target); } // 未找到目標節點,返回 None return None; } /* Driver Code */ pub fn main() { let target = 3; /* 在陣列中執行線性查詢 */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; let index = linear_search_array(&nums, target); println!("目標元素 3 的索引 = {}", index); /* 在鏈結串列中執行線性查詢 */ let head = ListNode::arr_to_linked_list(&nums); let node = linear_search_linked_list(head.unwrap(), target); println!("目標節點值 3 的對應節點物件為 {:?}", node); } ================================================ FILE: zh-hant/codes/rust/chapter_searching/two_sum.rs ================================================ /* * File: two_sum.rs * Created Time: 2023-01-14 * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::collections::HashMap; /* 方法一:暴力列舉 */ pub fn two_sum_brute_force(nums: &Vec, target: i32) -> Option> { let size = nums.len(); // 兩層迴圈,時間複雜度為 O(n^2) for i in 0..size - 1 { for j in i + 1..size { if nums[i] + nums[j] == target { return Some(vec![i as i32, j as i32]); } } } None } /* 方法二:輔助雜湊表 */ pub fn two_sum_hash_table(nums: &Vec, target: i32) -> Option> { // 輔助雜湊表,空間複雜度為 O(n) let mut dic = HashMap::new(); // 單層迴圈,時間複雜度為 O(n) for (i, num) in nums.iter().enumerate() { match dic.get(&(target - num)) { Some(v) => return Some(vec![*v as i32, i as i32]), None => dic.insert(num, i as i32), }; } None } fn main() { // ======= Test Case ======= let nums = vec![2, 7, 11, 15]; let target = 13; // ====== Driver Code ====== // 方法一 let res = two_sum_brute_force(&nums, target).unwrap(); print!("方法一 res = "); print_util::print_array(&res); // 方法二 let res = two_sum_hash_table(&nums, target).unwrap(); print!("\n方法二 res = "); print_util::print_array(&res); } ================================================ FILE: zh-hant/codes/rust/chapter_sorting/bubble_sort.rs ================================================ /* * File: bubble_sort.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* 泡沫排序 */ fn bubble_sort(nums: &mut [i32]) { // 外迴圈:未排序區間為 [0, i] for i in (1..nums.len()).rev() { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for j in 0..i { if nums[j] > nums[j + 1] { // 交換 nums[j] 與 nums[j + 1] nums.swap(j, j + 1); } } } } /* 泡沫排序(標誌最佳化) */ fn bubble_sort_with_flag(nums: &mut [i32]) { // 外迴圈:未排序區間為 [0, i] for i in (1..nums.len()).rev() { let mut flag = false; // 初始化標誌位 // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for j in 0..i { if nums[j] > nums[j + 1] { // 交換 nums[j] 與 nums[j + 1] nums.swap(j, j + 1); flag = true; // 記錄交換元素 } } if !flag { break; // 此輪“冒泡”未交換任何元素,直接跳出 }; } } /* Driver Code */ pub fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; bubble_sort(&mut nums); print!("泡沫排序完成後 nums = "); print_util::print_array(&nums); let mut nums1 = [4, 1, 3, 1, 5, 2]; bubble_sort_with_flag(&mut nums1); print!("\n泡沫排序完成後 nums1 = "); print_util::print_array(&nums1); } ================================================ FILE: zh-hant/codes/rust/chapter_sorting/bucket_sort.rs ================================================ /* * File: bucket_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* 桶排序 */ fn bucket_sort(nums: &mut [f64]) { // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 let k = nums.len() / 2; let mut buckets = vec![vec![]; k]; // 1. 將陣列元素分配到各個桶中 for &num in nums.iter() { // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] let i = (num * k as f64) as usize; // 將 num 新增進桶 i buckets[i].push(num); } // 2. 對各個桶執行排序 for bucket in &mut buckets { // 使用內建排序函式,也可以替換成其他排序演算法 bucket.sort_by(|a, b| a.partial_cmp(b).unwrap()); } // 3. 走訪桶合併結果 let mut i = 0; for bucket in buckets.iter() { for &num in bucket.iter() { nums[i] = num; i += 1; } } } /* Driver Code */ fn main() { // 設輸入資料為浮點數,範圍為 [0, 1) let mut nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucket_sort(&mut nums); print!("桶排序完成後 nums = "); print_util::print_array(&nums); } ================================================ FILE: zh-hant/codes/rust/chapter_sorting/counting_sort.rs ================================================ /* * File: counting_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* 計數排序 */ // 簡單實現,無法用於排序物件 fn counting_sort_naive(nums: &mut [i32]) { // 1. 統計陣列最大元素 m let m = *nums.iter().max().unwrap(); // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 let mut counter = vec![0; m as usize + 1]; for &num in nums.iter() { counter[num as usize] += 1; } // 3. 走訪 counter ,將各元素填入原陣列 nums let mut i = 0; for num in 0..m + 1 { for _ in 0..counter[num as usize] { nums[i] = num; i += 1; } } } /* 計數排序 */ // 完整實現,可排序物件,並且是穩定排序 fn counting_sort(nums: &mut [i32]) { // 1. 統計陣列最大元素 m let m = *nums.iter().max().unwrap() as usize; // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 let mut counter = vec![0; m + 1]; for &num in nums.iter() { counter[num as usize] += 1; } // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 for i in 0..m { counter[i + 1] += counter[i]; } // 4. 倒序走訪 nums ,將各元素填入結果陣列 res // 初始化陣列 res 用於記錄結果 let n = nums.len(); let mut res = vec![0; n]; for i in (0..n).rev() { let num = nums[i]; res[counter[num as usize] - 1] = num; // 將 num 放置到對應索引處 counter[num as usize] -= 1; // 令前綴和自減 1 ,得到下次放置 num 的索引 } // 使用結果陣列 res 覆蓋原陣列 nums nums.copy_from_slice(&res) } /* Driver Code */ fn main() { let mut nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; counting_sort_naive(&mut nums); print!("計數排序(無法排序物件)完成後 nums = "); print_util::print_array(&nums); let mut nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; counting_sort(&mut nums1); print!("\n計數排序完成後 nums1 = "); print_util::print_array(&nums1); } ================================================ FILE: zh-hant/codes/rust/chapter_sorting/heap_sort.rs ================================================ /* * File: heap_sort.rs * Created Time: 2023-07-04 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ fn sift_down(nums: &mut [i32], n: usize, mut i: usize) { loop { // 判斷節點 i, l, r 中值最大的節點,記為 ma let l = 2 * i + 1; let r = 2 * i + 2; let mut ma = i; if l < n && nums[l] > nums[ma] { ma = l; } if r < n && nums[r] > nums[ma] { ma = r; } // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if ma == i { break; } // 交換兩節點 nums.swap(i, ma); // 迴圈向下堆積化 i = ma; } } /* 堆積排序 */ fn heap_sort(nums: &mut [i32]) { // 建堆積操作:堆積化除葉節點以外的其他所有節點 for i in (0..nums.len() / 2).rev() { sift_down(nums, nums.len(), i); } // 從堆積中提取最大元素,迴圈 n-1 輪 for i in (1..nums.len()).rev() { // 交換根節點與最右葉節點(交換首元素與尾元素) nums.swap(0, i); // 以根節點為起點,從頂至底進行堆積化 sift_down(nums, i, 0); } } /* Driver Code */ fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; heap_sort(&mut nums); print!("堆積排序完成後 nums = "); print_util::print_array(&nums); } ================================================ FILE: zh-hant/codes/rust/chapter_sorting/insertion_sort.rs ================================================ /* * File: insertion_sort.rs * Created Time: 2023-02-13 * Author: xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; /* 插入排序 */ fn insertion_sort(nums: &mut [i32]) { // 外迴圈:已排序區間為 [0, i-1] for i in 1..nums.len() { let (base, mut j) = (nums[i], (i - 1) as i32); // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 while j >= 0 && nums[j as usize] > base { nums[(j + 1) as usize] = nums[j as usize]; // 將 nums[j] 向右移動一位 j -= 1; } nums[(j + 1) as usize] = base; // 將 base 賦值到正確位置 } } /* Driver Code */ fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; insertion_sort(&mut nums); print!("插入排序完成後 nums = "); print_util::print_array(&nums); } ================================================ FILE: zh-hant/codes/rust/chapter_sorting/merge_sort.rs ================================================ /** * File: merge_sort.rs * Created Time: 2023-02-14 * Author: xBLACKICEx (xBLACKICEx@outlook.com) */ /* 合併左子陣列和右子陣列 */ fn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) { // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] // 建立一個臨時陣列 tmp ,用於存放合併後的結果 let tmp_size = right - left + 1; let mut tmp = vec![0; tmp_size]; // 初始化左子陣列和右子陣列的起始索引 let (mut i, mut j, mut k) = (left, mid + 1, 0); // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 while i <= mid && j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i]; i += 1; } else { tmp[k] = nums[j]; j += 1; } k += 1; } // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 while i <= mid { tmp[k] = nums[i]; k += 1; i += 1; } while j <= right { tmp[k] = nums[j]; k += 1; j += 1; } // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 for k in 0..tmp_size { nums[left + k] = tmp[k]; } } /* 合併排序 */ fn merge_sort(nums: &mut [i32], left: usize, right: usize) { // 終止條件 if left >= right { return; // 當子陣列長度為 1 時終止遞迴 } // 劃分階段 let mid = left + (right - left) / 2; // 計算中點 merge_sort(nums, left, mid); // 遞迴左子陣列 merge_sort(nums, mid + 1, right); // 遞迴右子陣列 // 合併階段 merge(nums, left, mid, right); } /* Driver Code */ fn main() { /* 合併排序 */ let mut nums = [7, 3, 2, 6, 0, 1, 5, 4]; let right = nums.len() - 1; merge_sort(&mut nums, 0, right); println!("合併排序完成後 nums = {:?}", nums); } ================================================ FILE: zh-hant/codes/rust/chapter_sorting/quick_sort.rs ================================================ /** * File: quick_sort.rs * Created Time: 2023-02-16 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ /* 快速排序 */ struct QuickSort; impl QuickSort { /* 哨兵劃分 */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // 以 nums[left] 為基準數 let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // 從右向左找首個小於基準數的元素 } while i < j && nums[i] <= nums[left] { i += 1; // 從左向右找首個大於基準數的元素 } nums.swap(i, j); // 交換這兩個元素 } nums.swap(i, left); // 將基準數交換至兩子陣列的分界線 i // 返回基準數的索引 } /* 快速排序 */ pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { // 子陣列長度為 1 時終止遞迴 if left >= right { return; } // 哨兵劃分 let pivot = Self::partition(nums, left as usize, right as usize) as i32; // 遞迴左子陣列、右子陣列 Self::quick_sort(left, pivot - 1, nums); Self::quick_sort(pivot + 1, right, nums); } } /* 快速排序(中位基準數最佳化) */ struct QuickSortMedian; impl QuickSortMedian { /* 選取三個候選元素的中位數 */ fn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize { let (l, m, r) = (nums[left], nums[mid], nums[right]); if (l <= m && m <= r) || (r <= m && m <= l) { return mid; // m 在 l 和 r 之間 } if (m <= l && l <= r) || (r <= l && l <= m) { return left; // l 在 m 和 r 之間 } right } /* 哨兵劃分(三數取中值) */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // 選取三個候選元素的中位數 let med = Self::median_three(nums, left, (left + right) / 2, right); // 將中位數交換至陣列最左端 nums.swap(left, med); // 以 nums[left] 為基準數 let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // 從右向左找首個小於基準數的元素 } while i < j && nums[i] <= nums[left] { i += 1; // 從左向右找首個大於基準數的元素 } nums.swap(i, j); // 交換這兩個元素 } nums.swap(i, left); // 將基準數交換至兩子陣列的分界線 i // 返回基準數的索引 } /* 快速排序 */ pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { // 子陣列長度為 1 時終止遞迴 if left >= right { return; } // 哨兵劃分 let pivot = Self::partition(nums, left as usize, right as usize) as i32; // 遞迴左子陣列、右子陣列 Self::quick_sort(left, pivot - 1, nums); Self::quick_sort(pivot + 1, right, nums); } } /* 快速排序(遞迴深度最佳化) */ struct QuickSortTailCall; impl QuickSortTailCall { /* 哨兵劃分 */ fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { // 以 nums[left] 為基準數 let (mut i, mut j) = (left, right); while i < j { while i < j && nums[j] >= nums[left] { j -= 1; // 從右向左找首個小於基準數的元素 } while i < j && nums[i] <= nums[left] { i += 1; // 從左向右找首個大於基準數的元素 } nums.swap(i, j); // 交換這兩個元素 } nums.swap(i, left); // 將基準數交換至兩子陣列的分界線 i // 返回基準數的索引 } /* 快速排序(遞迴深度最佳化) */ pub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) { // 子陣列長度為 1 時終止 while left < right { // 哨兵劃分操作 let pivot = Self::partition(nums, left as usize, right as usize) as i32; // 對兩個子陣列中較短的那個執行快速排序 if pivot - left < right - pivot { Self::quick_sort(left, pivot - 1, nums); // 遞迴排序左子陣列 left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] } else { Self::quick_sort(pivot + 1, right, nums); // 遞迴排序右子陣列 right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] } } } } /* Driver Code */ fn main() { /* 快速排序 */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSort::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("快速排序完成後 nums = {:?}", nums); /* 快速排序(中位基準數最佳化) */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSortMedian::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("快速排序(中位基準數最佳化)完成後 nums = {:?}", nums); /* 快速排序(遞迴深度最佳化) */ let mut nums = [2, 4, 1, 0, 3, 5]; QuickSortTailCall::quick_sort(0, (nums.len() - 1) as i32, &mut nums); println!("快速排序(遞迴深度最佳化)完成後 nums = {:?}", nums); } ================================================ FILE: zh-hant/codes/rust/chapter_sorting/radix_sort.rs ================================================ /* * File: radix_sort.rs * Created Time: 2023-07-09 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; /* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ fn digit(num: i32, exp: i32) -> usize { // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 return ((num / exp) % 10) as usize; } /* 計數排序(根據 nums 第 k 位排序) */ fn counting_sort_digit(nums: &mut [i32], exp: i32) { // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 let mut counter = [0; 10]; let n = nums.len(); // 統計 0~9 各數字的出現次數 for i in 0..n { let d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d counter[d] += 1; // 統計數字 d 的出現次數 } // 求前綴和,將“出現個數”轉換為“陣列索引” for i in 1..10 { counter[i] += counter[i - 1]; } // 倒序走訪,根據桶內統計結果,將各元素填入 res let mut res = vec![0; n]; for i in (0..n).rev() { let d = digit(nums[i], exp); let j = counter[d] - 1; // 獲取 d 在陣列中的索引 j res[j] = nums[i]; // 將當前元素填入索引 j counter[d] -= 1; // 將 d 的數量減 1 } // 使用結果覆蓋原陣列 nums nums.copy_from_slice(&res); } /* 基數排序 */ fn radix_sort(nums: &mut [i32]) { // 獲取陣列的最大元素,用於判斷最大位數 let m = *nums.into_iter().max().unwrap(); // 按照從低位到高位的順序走訪 let mut exp = 1; while exp <= m { counting_sort_digit(nums, exp); exp *= 10; } } /* Driver Code */ fn main() { // 基數排序 let mut nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radix_sort(&mut nums); print!("基數排序完成後 nums = "); print_util::print_array(&nums); } ================================================ FILE: zh-hant/codes/rust/chapter_sorting/selection_sort.rs ================================================ /* * File: selection_sort.rs * Created Time: 2023-05-30 * Author: WSL0809 (wslzzy@outlook.com) */ use hello_algo_rust::include::print_util; /* 選擇排序 */ fn selection_sort(nums: &mut [i32]) { if nums.is_empty() { return; } let n = nums.len(); // 外迴圈:未排序區間為 [i, n-1] for i in 0..n - 1 { // 內迴圈:找到未排序區間內的最小元素 let mut k = i; for j in i + 1..n { if nums[j] < nums[k] { k = j; // 記錄最小元素的索引 } } // 將該最小元素與未排序區間的首個元素交換 nums.swap(i, k); } } /* Driver Code */ pub fn main() { let mut nums = [4, 1, 3, 1, 5, 2]; selection_sort(&mut nums); print!("\n選擇排序完成後 nums = "); print_util::print_array(&nums); } ================================================ FILE: zh-hant/codes/rust/chapter_stack_and_queue/array_deque.rs ================================================ /* * File: array_deque.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* 基於環形陣列實現的雙向佇列 */ struct ArrayDeque { nums: Vec, // 用於儲存雙向佇列元素的陣列 front: usize, // 佇列首指標,指向佇列首元素 que_size: usize, // 雙向佇列長度 } impl ArrayDeque { /* 建構子 */ pub fn new(capacity: usize) -> Self { Self { nums: vec![T::default(); capacity], front: 0, que_size: 0, } } /* 獲取雙向佇列的容量 */ pub fn capacity(&self) -> usize { self.nums.len() } /* 獲取雙向佇列的長度 */ pub fn size(&self) -> usize { self.que_size } /* 判斷雙向佇列是否為空 */ pub fn is_empty(&self) -> bool { self.que_size == 0 } /* 計算環形陣列索引 */ fn index(&self, i: i32) -> usize { // 透過取餘操作實現陣列首尾相連 // 當 i 越過陣列尾部後,回到頭部 // 當 i 越過陣列頭部後,回到尾部 ((i + self.capacity() as i32) % self.capacity() as i32) as usize } /* 佇列首入列 */ pub fn push_first(&mut self, num: T) { if self.que_size == self.capacity() { println!("雙向佇列已滿"); return; } // 佇列首指標向左移動一位 // 透過取餘操作實現 front 越過陣列頭部後回到尾部 self.front = self.index(self.front as i32 - 1); // 將 num 新增至佇列首 self.nums[self.front] = num; self.que_size += 1; } /* 佇列尾入列 */ pub fn push_last(&mut self, num: T) { if self.que_size == self.capacity() { println!("雙向佇列已滿"); return; } // 計算佇列尾指標,指向佇列尾索引 + 1 let rear = self.index(self.front as i32 + self.que_size as i32); // 將 num 新增至佇列尾 self.nums[rear] = num; self.que_size += 1; } /* 佇列首出列 */ fn pop_first(&mut self) -> T { let num = self.peek_first(); // 佇列首指標向後移動一位 self.front = self.index(self.front as i32 + 1); self.que_size -= 1; num } /* 佇列尾出列 */ fn pop_last(&mut self) -> T { let num = self.peek_last(); self.que_size -= 1; num } /* 訪問佇列首元素 */ fn peek_first(&self) -> T { if self.is_empty() { panic!("雙向佇列為空") }; self.nums[self.front] } /* 訪問佇列尾元素 */ fn peek_last(&self) -> T { if self.is_empty() { panic!("雙向佇列為空") }; // 計算尾元素索引 let last = self.index(self.front as i32 + self.que_size as i32 - 1); self.nums[last] } /* 返回陣列用於列印 */ fn to_array(&self) -> Vec { // 僅轉換有效長度範圍內的串列元素 let mut res = vec![T::default(); self.que_size]; let mut j = self.front; for i in 0..self.que_size { res[i] = self.nums[self.index(j as i32)]; j += 1; } res } } /* Driver Code */ fn main() { /* 初始化雙向佇列 */ let mut deque = ArrayDeque::new(10); deque.push_last(3); deque.push_last(2); deque.push_last(5); print!("雙向佇列 deque = "); print_util::print_array(&deque.to_array()); /* 訪問元素 */ let peek_first = deque.peek_first(); print!("\n佇列首元素 peek_first = {}", peek_first); let peek_last = deque.peek_last(); print!("\n佇列尾元素 peek_last = {}", peek_last); /* 元素入列 */ deque.push_last(4); print!("\n元素 4 佇列尾入列後 deque = "); print_util::print_array(&deque.to_array()); deque.push_first(1); print!("\n元素 1 佇列首入列後 deque = "); print_util::print_array(&deque.to_array()); /* 元素出列 */ let pop_last = deque.pop_last(); print!("\n佇列尾出列元素 = {},佇列尾出列後 deque = ", pop_last); print_util::print_array(&deque.to_array()); let pop_first = deque.pop_first(); print!("\n佇列首出列元素 = {},佇列首出列後 deque = ", pop_first); print_util::print_array(&deque.to_array()); /* 獲取雙向佇列的長度 */ let size = deque.size(); print!("\n雙向佇列長度 size = {}", size); /* 判斷雙向佇列是否為空 */ let is_empty = deque.is_empty(); print!("\n雙向佇列是否為空 = {}", is_empty); } ================================================ FILE: zh-hant/codes/rust/chapter_stack_and_queue/array_queue.rs ================================================ /* * File: array_queue.rs * Created Time: 2023-02-06 * Author: WSL0809 (wslzzy@outlook.com) */ /* 基於環形陣列實現的佇列 */ struct ArrayQueue { nums: Vec, // 用於儲存佇列元素的陣列 front: i32, // 佇列首指標,指向佇列首元素 que_size: i32, // 佇列長度 que_capacity: i32, // 佇列容量 } impl ArrayQueue { /* 建構子 */ fn new(capacity: i32) -> ArrayQueue { ArrayQueue { nums: vec![T::default(); capacity as usize], front: 0, que_size: 0, que_capacity: capacity, } } /* 獲取佇列的容量 */ fn capacity(&self) -> i32 { self.que_capacity } /* 獲取佇列的長度 */ fn size(&self) -> i32 { self.que_size } /* 判斷佇列是否為空 */ fn is_empty(&self) -> bool { self.que_size == 0 } /* 入列 */ fn push(&mut self, num: T) { if self.que_size == self.capacity() { println!("佇列已滿"); return; } // 計算佇列尾指標,指向佇列尾索引 + 1 // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 let rear = (self.front + self.que_size) % self.que_capacity; // 將 num 新增至佇列尾 self.nums[rear as usize] = num; self.que_size += 1; } /* 出列 */ fn pop(&mut self) -> T { let num = self.peek(); // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 self.front = (self.front + 1) % self.que_capacity; self.que_size -= 1; num } /* 訪問佇列首元素 */ fn peek(&self) -> T { if self.is_empty() { panic!("index out of bounds"); } self.nums[self.front as usize] } /* 返回陣列 */ fn to_vector(&self) -> Vec { let cap = self.que_capacity; let mut j = self.front; let mut arr = vec![T::default(); cap as usize]; for i in 0..self.que_size { arr[i as usize] = self.nums[(j % cap) as usize]; j += 1; } arr } } /* Driver Code */ fn main() { /* 初始化佇列 */ let capacity = 10; let mut queue = ArrayQueue::new(capacity); /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); println!("佇列 queue = {:?}", queue.to_vector()); /* 訪問佇列首元素 */ let peek = queue.peek(); println!("佇列首元素 peek = {}", peek); /* 元素出列 */ let pop = queue.pop(); println!( "出列元素 pop = {:?},出列後 queue = {:?}", pop, queue.to_vector() ); /* 獲取佇列的長度 */ let size = queue.size(); println!("佇列長度 size = {}", size); /* 判斷佇列是否為空 */ let is_empty = queue.is_empty(); println!("佇列是否為空 = {}", is_empty); /* 測試環形陣列 */ for i in 0..10 { queue.push(i); queue.pop(); println!("第 {:?} 輪入列 + 出列後 queue = {:?}", i, queue.to_vector()); } } ================================================ FILE: zh-hant/codes/rust/chapter_stack_and_queue/array_stack.rs ================================================ /* * File: array_stack.rs * Created Time: 2023-02-05 * Author: WSL0809 (wslzzy@outlook.com), codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* 基於陣列實現的堆疊 */ struct ArrayStack { stack: Vec, } impl ArrayStack { /* 初始化堆疊 */ fn new() -> ArrayStack { ArrayStack:: { stack: Vec::::new(), } } /* 獲取堆疊的長度 */ fn size(&self) -> usize { self.stack.len() } /* 判斷堆疊是否為空 */ fn is_empty(&self) -> bool { self.size() == 0 } /* 入堆疊 */ fn push(&mut self, num: T) { self.stack.push(num); } /* 出堆疊 */ fn pop(&mut self) -> Option { self.stack.pop() } /* 訪問堆疊頂元素 */ fn peek(&self) -> Option<&T> { if self.is_empty() { panic!("堆疊為空") }; self.stack.last() } /* 返回 &Vec */ fn to_array(&self) -> &Vec { &self.stack } } /* Driver Code */ fn main() { // 初始化堆疊 let mut stack = ArrayStack::::new(); // 元素入堆疊 stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("堆疊 stack = "); print_util::print_array(stack.to_array()); //訪問堆疊頂元素 let peek = stack.peek().unwrap(); print!("\n堆疊頂元素 peek = {}", peek); // 元素出堆疊 let pop = stack.pop().unwrap(); print!("\n出堆疊元素 pop = {pop},出堆疊後 stack = "); print_util::print_array(stack.to_array()); // 獲取堆疊的長度 let size = stack.size(); print!("\n堆疊的長度 size = {size}"); // 判斷是否為空 let is_empty = stack.is_empty(); print!("\n堆疊是否為空 = {is_empty}"); } ================================================ FILE: zh-hant/codes/rust/chapter_stack_and_queue/deque.rs ================================================ /* * File: deque.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; use std::collections::VecDeque; /* Driver Code */ pub fn main() { // 初始化雙向佇列 let mut deque: VecDeque = VecDeque::new(); deque.push_back(3); deque.push_back(2); deque.push_back(5); print!("雙向佇列 deque = "); print_util::print_queue(&deque); // 訪問元素 let peek_first = deque.front().unwrap(); print!("\n佇列首元素 peekFirst = {peek_first}"); let peek_last = deque.back().unwrap(); print!("\n佇列尾元素 peekLast = {peek_last}"); /* 元素入列 */ deque.push_back(4); print!("\n元素 4 佇列尾入列後 deque = "); print_util::print_queue(&deque); deque.push_front(1); print!("\n元素 1 佇列首入列後 deque = "); print_util::print_queue(&deque); // 元素出列 let pop_last = deque.pop_back().unwrap(); print!("\n佇列尾出列元素 = {pop_last},佇列尾出列後 deque = "); print_util::print_queue(&deque); let pop_first = deque.pop_front().unwrap(); print!("\n佇列首出列元素 = {pop_first},佇列首出列後 deque = "); print_util::print_queue(&deque); // 獲取雙向佇列的長度 let size = deque.len(); print!("\n雙向佇列長度 size = {size}"); // 判斷雙向佇列是否為空 let is_empty = deque.is_empty(); print!("\n雙向佇列是否為空 = {is_empty}"); } ================================================ FILE: zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs ================================================ /* * File: linkedlist_deque.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; use std::cell::RefCell; use std::rc::Rc; /* 雙向鏈結串列節點 */ pub struct ListNode { pub val: T, // 節點值 pub next: Option>>>, // 後繼節點指標 pub prev: Option>>>, // 前驅節點指標 } impl ListNode { pub fn new(val: T) -> Rc>> { Rc::new(RefCell::new(ListNode { val, next: None, prev: None, })) } } /* 基於雙向鏈結串列實現的雙向佇列 */ #[allow(dead_code)] pub struct LinkedListDeque { front: Option>>>, // 頭節點 front rear: Option>>>, // 尾節點 rear que_size: usize, // 雙向佇列的長度 } impl LinkedListDeque { pub fn new() -> Self { Self { front: None, rear: None, que_size: 0, } } /* 獲取雙向佇列的長度 */ pub fn size(&self) -> usize { return self.que_size; } /* 判斷雙向佇列是否為空 */ pub fn is_empty(&self) -> bool { return self.que_size == 0; } /* 入列操作 */ fn push(&mut self, num: T, is_front: bool) { let node = ListNode::new(num); // 佇列首入列操作 if is_front { match self.front.take() { // 若鏈結串列為空,則令 front 和 rear 都指向 node None => { self.rear = Some(node.clone()); self.front = Some(node); } // 將 node 新增至鏈結串列頭部 Some(old_front) => { old_front.borrow_mut().prev = Some(node.clone()); node.borrow_mut().next = Some(old_front); self.front = Some(node); // 更新頭節點 } } } // 佇列尾入列操作 else { match self.rear.take() { // 若鏈結串列為空,則令 front 和 rear 都指向 node None => { self.front = Some(node.clone()); self.rear = Some(node); } // 將 node 新增至鏈結串列尾部 Some(old_rear) => { old_rear.borrow_mut().next = Some(node.clone()); node.borrow_mut().prev = Some(old_rear); self.rear = Some(node); // 更新尾節點 } } } self.que_size += 1; // 更新佇列長度 } /* 佇列首入列 */ pub fn push_first(&mut self, num: T) { self.push(num, true); } /* 佇列尾入列 */ pub fn push_last(&mut self, num: T) { self.push(num, false); } /* 出列操作 */ fn pop(&mut self, is_front: bool) -> Option { // 若佇列為空,直接返回 None if self.is_empty() { return None; }; // 佇列首出列操作 if is_front { self.front.take().map(|old_front| { match old_front.borrow_mut().next.take() { Some(new_front) => { new_front.borrow_mut().prev.take(); self.front = Some(new_front); // 更新頭節點 } None => { self.rear.take(); } } self.que_size -= 1; // 更新佇列長度 old_front.borrow().val }) } // 佇列尾出列操作 else { self.rear.take().map(|old_rear| { match old_rear.borrow_mut().prev.take() { Some(new_rear) => { new_rear.borrow_mut().next.take(); self.rear = Some(new_rear); // 更新尾節點 } None => { self.front.take(); } } self.que_size -= 1; // 更新佇列長度 old_rear.borrow().val }) } } /* 佇列首出列 */ pub fn pop_first(&mut self) -> Option { return self.pop(true); } /* 佇列尾出列 */ pub fn pop_last(&mut self) -> Option { return self.pop(false); } /* 訪問佇列首元素 */ pub fn peek_first(&self) -> Option<&Rc>>> { self.front.as_ref() } /* 訪問佇列尾元素 */ pub fn peek_last(&self) -> Option<&Rc>>> { self.rear.as_ref() } /* 返回陣列用於列印 */ pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { let mut res: Vec = Vec::new(); fn recur(cur: Option<&Rc>>>, res: &mut Vec) { if let Some(cur) = cur { res.push(cur.borrow().val); recur(cur.borrow().next.as_ref(), res); } } recur(head, &mut res); res } } /* Driver Code */ fn main() { /* 初始化雙向佇列 */ let mut deque = LinkedListDeque::new(); deque.push_last(3); deque.push_last(2); deque.push_last(5); print!("雙向佇列 deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); /* 訪問元素 */ let peek_first = deque.peek_first().unwrap().borrow().val; print!("\n佇列首元素 peek_first = {}", peek_first); let peek_last = deque.peek_last().unwrap().borrow().val; print!("\n佇列尾元素 peek_last = {}", peek_last); /* 元素入列 */ deque.push_last(4); print!("\n元素 4 佇列尾入列後 deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); deque.push_first(1); print!("\n元素 1 佇列首入列後 deque = "); print_util::print_array(&deque.to_array(deque.peek_first())); /* 元素出列 */ let pop_last = deque.pop_last().unwrap(); print!("\n佇列尾出列元素 = {},佇列尾出列後 deque = ", pop_last); print_util::print_array(&deque.to_array(deque.peek_first())); let pop_first = deque.pop_first().unwrap(); print!("\n佇列首出列元素 = {},佇列首出列後 deque = ", pop_first); print_util::print_array(&deque.to_array(deque.peek_first())); /* 獲取雙向佇列的長度 */ let size = deque.size(); print!("\n雙向佇列長度 size = {}", size); /* 判斷雙向佇列是否為空 */ let is_empty = deque.is_empty(); print!("\n雙向佇列是否為空 = {}", is_empty); } ================================================ FILE: zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs ================================================ /* * File: linkedlist_queue.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* 基於鏈結串列實現的佇列 */ #[allow(dead_code)] pub struct LinkedListQueue { front: Option>>>, // 頭節點 front rear: Option>>>, // 尾節點 rear que_size: usize, // 佇列的長度 } impl LinkedListQueue { pub fn new() -> Self { Self { front: None, rear: None, que_size: 0, } } /* 獲取佇列的長度 */ pub fn size(&self) -> usize { return self.que_size; } /* 判斷佇列是否為空 */ pub fn is_empty(&self) -> bool { return self.que_size == 0; } /* 入列 */ pub fn push(&mut self, num: T) { // 在尾節點後新增 num let new_rear = ListNode::new(num); match self.rear.take() { // 如果佇列不為空,則將該節點新增到尾節點後 Some(old_rear) => { old_rear.borrow_mut().next = Some(new_rear.clone()); self.rear = Some(new_rear); } // 如果佇列為空,則令頭、尾節點都指向該節點 None => { self.front = Some(new_rear.clone()); self.rear = Some(new_rear); } } self.que_size += 1; } /* 出列 */ pub fn pop(&mut self) -> Option { self.front.take().map(|old_front| { match old_front.borrow_mut().next.take() { Some(new_front) => { self.front = Some(new_front); } None => { self.rear.take(); } } self.que_size -= 1; old_front.borrow().val }) } /* 訪問佇列首元素 */ pub fn peek(&self) -> Option<&Rc>>> { self.front.as_ref() } /* 將鏈結串列轉化為 Array 並返回 */ pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { let mut res: Vec = Vec::new(); fn recur(cur: Option<&Rc>>>, res: &mut Vec) { if let Some(cur) = cur { res.push(cur.borrow().val); recur(cur.borrow().next.as_ref(), res); } } recur(head, &mut res); res } } /* Driver Code */ fn main() { /* 初始化佇列 */ let mut queue = LinkedListQueue::new(); /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); print!("佇列 queue = "); print_util::print_array(&queue.to_array(queue.peek())); /* 訪問佇列首元素 */ let peek = queue.peek().unwrap().borrow().val; print!("\n佇列首元素 peek = {}", peek); /* 元素出列 */ let pop = queue.pop().unwrap(); print!("\n出列元素 pop = {},出列後 queue = ", pop); print_util::print_array(&queue.to_array(queue.peek())); /* 獲取佇列的長度 */ let size = queue.size(); print!("\n佇列長度 size = {}", size); /* 判斷佇列是否為空 */ let is_empty = queue.is_empty(); print!("\n佇列是否為空 = {}", is_empty); } ================================================ FILE: zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs ================================================ /* * File: linkedlist_stack.rs * Created Time: 2023-03-11 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::{print_util, ListNode}; use std::cell::RefCell; use std::rc::Rc; /* 基於鏈結串列實現的堆疊 */ #[allow(dead_code)] pub struct LinkedListStack { stack_peek: Option>>>, // 將頭節點作為堆疊頂 stk_size: usize, // 堆疊的長度 } impl LinkedListStack { pub fn new() -> Self { Self { stack_peek: None, stk_size: 0, } } /* 獲取堆疊的長度 */ pub fn size(&self) -> usize { return self.stk_size; } /* 判斷堆疊是否為空 */ pub fn is_empty(&self) -> bool { return self.size() == 0; } /* 入堆疊 */ pub fn push(&mut self, num: T) { let node = ListNode::new(num); node.borrow_mut().next = self.stack_peek.take(); self.stack_peek = Some(node); self.stk_size += 1; } /* 出堆疊 */ pub fn pop(&mut self) -> Option { self.stack_peek.take().map(|old_head| { self.stack_peek = old_head.borrow_mut().next.take(); self.stk_size -= 1; old_head.borrow().val }) } /* 訪問堆疊頂元素 */ pub fn peek(&self) -> Option<&Rc>>> { self.stack_peek.as_ref() } /* 將 List 轉化為 Array 並返回 */ pub fn to_array(&self) -> Vec { fn _to_array(head: Option<&Rc>>>) -> Vec { if let Some(node) = head { let mut nums = _to_array(node.borrow().next.as_ref()); nums.push(node.borrow().val); return nums; } return Vec::new(); } _to_array(self.peek()) } } /* Driver Code */ fn main() { /* 初始化堆疊 */ let mut stack = LinkedListStack::new(); /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("堆疊 stack = "); print_util::print_array(&stack.to_array()); /* 訪問堆疊頂元素 */ let peek = stack.peek().unwrap().borrow().val; print!("\n堆疊頂元素 peek = {}", peek); /* 元素出堆疊 */ let pop = stack.pop().unwrap(); print!("\n出堆疊元素 pop = {},出堆疊後 stack = ", pop); print_util::print_array(&stack.to_array()); /* 獲取堆疊的長度 */ let size = stack.size(); print!("\n堆疊的長度 size = {}", size); /* 判斷是否為空 */ let is_empty = stack.is_empty(); print!("\n堆疊是否為空 = {}", is_empty); } ================================================ FILE: zh-hant/codes/rust/chapter_stack_and_queue/queue.rs ================================================ /* * File: queue.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use hello_algo_rust::include::print_util; use std::collections::VecDeque; /* Driver Code */ pub fn main() { // 初始化佇列 let mut queue: VecDeque = VecDeque::new(); // 元素入列 queue.push_back(1); queue.push_back(3); queue.push_back(2); queue.push_back(5); queue.push_back(4); print!("佇列 queue = "); print_util::print_queue(&queue); // 訪問佇列首元素 let peek = queue.front().unwrap(); println!("\n佇列首元素 peek = {peek}"); // 元素出列 let pop = queue.pop_front().unwrap(); print!("出列元素 pop = {pop},出列後 queue = "); print_util::print_queue(&queue); // 獲取佇列的長度 let size = queue.len(); print!("\n佇列長度 size = {size}"); // 判斷佇列是否為空 let is_empty = queue.is_empty(); print!("\n佇列是否為空 = {is_empty}"); } ================================================ FILE: zh-hant/codes/rust/chapter_stack_and_queue/stack.rs ================================================ /* * File: stack.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com) */ use hello_algo_rust::include::print_util; /* Driver Code */ pub fn main() { // 初始化堆疊 // 在 rust 中,推薦將 Vec 當作堆疊來使用 let mut stack: Vec = Vec::new(); // 元素入堆疊 stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); print!("堆疊 stack = "); print_util::print_array(&stack); // 訪問堆疊頂元素 let peek = stack.last().unwrap(); print!("\n堆疊頂元素 peek = {peek}"); // 元素出堆疊 let pop = stack.pop().unwrap(); print!("\n出堆疊元素 pop = {pop},出堆疊後 stack = "); print_util::print_array(&stack); // 獲取堆疊的長度 let size = stack.len(); print!("\n堆疊的長度 size = {size}"); // 判斷堆疊是否為空 let is_empty = stack.is_empty(); print!("\n堆疊是否為空 = {is_empty}"); } ================================================ FILE: zh-hant/codes/rust/chapter_tree/array_binary_tree.rs ================================================ /* * File: array_binary_tree.rs * Created Time: 2023-07-25 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::{print_util, tree_node}; /* 陣列表示下的二元樹類別 */ struct ArrayBinaryTree { tree: Vec>, } impl ArrayBinaryTree { /* 建構子 */ fn new(arr: Vec>) -> Self { Self { tree: arr } } /* 串列容量 */ fn size(&self) -> i32 { self.tree.len() as i32 } /* 獲取索引為 i 節點的值 */ fn val(&self, i: i32) -> Option { // 若索引越界,則返回 None ,代表空位 if i < 0 || i >= self.size() { None } else { self.tree[i as usize] } } /* 獲取索引為 i 節點的左子節點的索引 */ fn left(&self, i: i32) -> i32 { 2 * i + 1 } /* 獲取索引為 i 節點的右子節點的索引 */ fn right(&self, i: i32) -> i32 { 2 * i + 2 } /* 獲取索引為 i 節點的父節點的索引 */ fn parent(&self, i: i32) -> i32 { (i - 1) / 2 } /* 層序走訪 */ fn level_order(&self) -> Vec { self.tree.iter().filter_map(|&x| x).collect() } /* 深度優先走訪 */ fn dfs(&self, i: i32, order: &'static str, res: &mut Vec) { if self.val(i).is_none() { return; } let val = self.val(i).unwrap(); // 前序走訪 if order == "pre" { res.push(val); } self.dfs(self.left(i), order, res); // 中序走訪 if order == "in" { res.push(val); } self.dfs(self.right(i), order, res); // 後序走訪 if order == "post" { res.push(val); } } /* 前序走訪 */ fn pre_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "pre", &mut res); res } /* 中序走訪 */ fn in_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "in", &mut res); res } /* 後序走訪 */ fn post_order(&self) -> Vec { let mut res = vec![]; self.dfs(0, "post", &mut res); res } } /* Driver Code */ fn main() { // 初始化二元樹 // 這裡藉助了一個從陣列直接生成二元樹的函式 let arr = vec![ Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15), ]; let root = tree_node::vec_to_tree(arr.clone()).unwrap(); println!("\n初始化二元樹\n"); println!("二元樹的陣列表示:"); println!( "[{}]", arr.iter() .map(|&val| if let Some(val) = val { format!("{val}") } else { "null".to_string() }) .collect::>() .join(", ") ); println!("二元樹的鏈結串列表示:"); print_util::print_tree(&root); // 陣列表示下的二元樹類別 let abt = ArrayBinaryTree::new(arr); // 訪問節點 let i = 1; let l = abt.left(i); let r = abt.right(i); let p = abt.parent(i); println!( "\n當前節點的索引為 {} ,值為 {}", i, if let Some(val) = abt.val(i) { format!("{val}") } else { "null".to_string() } ); println!( "其左子節點的索引為 {} ,值為 {}", l, if let Some(val) = abt.val(l) { format!("{val}") } else { "null".to_string() } ); println!( "其右子節點的索引為 {} ,值為 {}", r, if let Some(val) = abt.val(r) { format!("{val}") } else { "null".to_string() } ); println!( "其父節點的索引為 {} ,值為 {}", p, if let Some(val) = abt.val(p) { format!("{val}") } else { "null".to_string() } ); // 走訪樹 let mut res = abt.level_order(); println!("\n層序走訪為:{:?}", res); res = abt.pre_order(); println!("前序走訪為:{:?}", res); res = abt.in_order(); println!("中序走訪為:{:?}", res); res = abt.post_order(); println!("後序走訪為:{:?}", res); } ================================================ FILE: zh-hant/codes/rust/chapter_tree/avl_tree.rs ================================================ /* * File: avl_tree.rs * Created Time: 2023-07-14 * Author: night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::{print_util, TreeNode}; use std::cell::RefCell; use std::cmp::Ordering; use std::rc::Rc; type OptionTreeNodeRc = Option>>; /* AVL 樹 */ struct AVLTree { root: OptionTreeNodeRc, // 根節點 } impl AVLTree { /* 建構子 */ fn new() -> Self { Self { root: None } } /* 獲取節點高度 */ fn height(node: OptionTreeNodeRc) -> i32 { // 空節點高度為 -1 ,葉節點高度為 0 match node { Some(node) => node.borrow().height, None => -1, } } /* 更新節點高度 */ fn update_height(node: OptionTreeNodeRc) { if let Some(node) = node { let left = node.borrow().left.clone(); let right = node.borrow().right.clone(); // 節點高度等於最高子樹高度 + 1 node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1; } } /* 獲取平衡因子 */ fn balance_factor(node: OptionTreeNodeRc) -> i32 { match node { // 空節點平衡因子為 0 None => 0, // 節點平衡因子 = 左子樹高度 - 右子樹高度 Some(node) => { Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone()) } } } /* 右旋操作 */ fn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { match node { Some(node) => { let child = node.borrow().left.clone().unwrap(); let grand_child = child.borrow().right.clone(); // 以 child 為原點,將 node 向右旋轉 child.borrow_mut().right = Some(node.clone()); node.borrow_mut().left = grand_child; // 更新節點高度 Self::update_height(Some(node)); Self::update_height(Some(child.clone())); // 返回旋轉後子樹的根節點 Some(child) } None => None, } } /* 左旋操作 */ fn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { match node { Some(node) => { let child = node.borrow().right.clone().unwrap(); let grand_child = child.borrow().left.clone(); // 以 child 為原點,將 node 向左旋轉 child.borrow_mut().left = Some(node.clone()); node.borrow_mut().right = grand_child; // 更新節點高度 Self::update_height(Some(node)); Self::update_height(Some(child.clone())); // 返回旋轉後子樹的根節點 Some(child) } None => None, } } /* 執行旋轉操作,使該子樹重新恢復平衡 */ fn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { // 獲取節點 node 的平衡因子 let balance_factor = Self::balance_factor(node.clone()); // 左偏樹 if balance_factor > 1 { let node = node.unwrap(); if Self::balance_factor(node.borrow().left.clone()) >= 0 { // 右旋 Self::right_rotate(Some(node)) } else { // 先左旋後右旋 let left = node.borrow().left.clone(); node.borrow_mut().left = Self::left_rotate(left); Self::right_rotate(Some(node)) } } // 右偏樹 else if balance_factor < -1 { let node = node.unwrap(); if Self::balance_factor(node.borrow().right.clone()) <= 0 { // 左旋 Self::left_rotate(Some(node)) } else { // 先右旋後左旋 let right = node.borrow().right.clone(); node.borrow_mut().right = Self::right_rotate(right); Self::left_rotate(Some(node)) } } else { // 平衡樹,無須旋轉,直接返回 node } } /* 插入節點 */ fn insert(&mut self, val: i32) { self.root = Self::insert_helper(self.root.clone(), val); } /* 遞迴插入節點(輔助方法) */ fn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { match node { Some(mut node) => { /* 1. 查詢插入位置並插入節點 */ match { let node_val = node.borrow().val; node_val } .cmp(&val) { Ordering::Greater => { let left = node.borrow().left.clone(); node.borrow_mut().left = Self::insert_helper(left, val); } Ordering::Less => { let right = node.borrow().right.clone(); node.borrow_mut().right = Self::insert_helper(right, val); } Ordering::Equal => { return Some(node); // 重複節點不插入,直接返回 } } Self::update_height(Some(node.clone())); // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = Self::rotate(Some(node)).unwrap(); // 返回子樹的根節點 Some(node) } None => Some(TreeNode::new(val)), } } /* 刪除節點 */ fn remove(&self, val: i32) { Self::remove_helper(self.root.clone(), val); } /* 遞迴刪除節點(輔助方法) */ fn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { match node { Some(mut node) => { /* 1. 查詢節點並刪除 */ if val < node.borrow().val { let left = node.borrow().left.clone(); node.borrow_mut().left = Self::remove_helper(left, val); } else if val > node.borrow().val { let right = node.borrow().right.clone(); node.borrow_mut().right = Self::remove_helper(right, val); } else if node.borrow().left.is_none() || node.borrow().right.is_none() { let child = if node.borrow().left.is_some() { node.borrow().left.clone() } else { node.borrow().right.clone() }; match child { // 子節點數量 = 0 ,直接刪除 node 並返回 None => { return None; } // 子節點數量 = 1 ,直接刪除 node Some(child) => node = child, } } else { // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 let mut temp = node.borrow().right.clone().unwrap(); loop { let temp_left = temp.borrow().left.clone(); if temp_left.is_none() { break; } temp = temp_left.unwrap(); } let right = node.borrow().right.clone(); node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val); node.borrow_mut().val = temp.borrow().val; } Self::update_height(Some(node.clone())); // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = Self::rotate(Some(node)).unwrap(); // 返回子樹的根節點 Some(node) } None => None, } } /* 查詢節點 */ fn search(&self, val: i32) -> OptionTreeNodeRc { let mut cur = self.root.clone(); // 迴圈查詢,越過葉節點後跳出 while let Some(current) = cur.clone() { match current.borrow().val.cmp(&val) { // 目標節點在 cur 的右子樹中 Ordering::Less => { cur = current.borrow().right.clone(); } // 目標節點在 cur 的左子樹中 Ordering::Greater => { cur = current.borrow().left.clone(); } // 找到目標節點,跳出迴圈 Ordering::Equal => { break; } } } // 返回目標節點 cur } } /* Driver Code */ fn main() { fn test_insert(tree: &mut AVLTree, val: i32) { tree.insert(val); println!("\n插入節點 {} 後,AVL 樹為", val); print_util::print_tree(&tree.root.clone().unwrap()); } fn test_remove(tree: &mut AVLTree, val: i32) { tree.remove(val); println!("\n刪除節點 {} 後,AVL 樹為", val); print_util::print_tree(&tree.root.clone().unwrap()); } /* 初始化空 AVL 樹 */ let mut avl_tree = AVLTree::new(); /* 插入節點 */ // 請關注插入節點後,AVL 樹是如何保持平衡的 test_insert(&mut avl_tree, 1); test_insert(&mut avl_tree, 2); test_insert(&mut avl_tree, 3); test_insert(&mut avl_tree, 4); test_insert(&mut avl_tree, 5); test_insert(&mut avl_tree, 8); test_insert(&mut avl_tree, 7); test_insert(&mut avl_tree, 9); test_insert(&mut avl_tree, 10); test_insert(&mut avl_tree, 6); /* 插入重複節點 */ test_insert(&mut avl_tree, 7); /* 刪除節點 */ // 請關注刪除節點後,AVL 樹是如何保持平衡的 test_remove(&mut avl_tree, 8); // 刪除度為 0 的節點 test_remove(&mut avl_tree, 5); // 刪除度為 1 的節點 test_remove(&mut avl_tree, 4); // 刪除度為 2 的節點 /* 查詢節點 */ let node = avl_tree.search(7); if let Some(node) = node { println!( "\n查詢到的節點物件為 {:?},節點值 = {}", &*node.borrow(), node.borrow().val ); } } ================================================ FILE: zh-hant/codes/rust/chapter_tree/binary_search_tree.rs ================================================ /* * File: binary_search_tree.rs * Created Time: 2023-04-20 * Author: xBLACKICEx (xBLACKICE@outlook.com)、night-cruise (2586447362@qq.com) */ use hello_algo_rust::include::print_util; use std::cell::RefCell; use std::cmp::Ordering; use std::rc::Rc; use hello_algo_rust::include::TreeNode; type OptionTreeNodeRc = Option>>; /* 二元搜尋樹 */ pub struct BinarySearchTree { root: OptionTreeNodeRc, } impl BinarySearchTree { /* 建構子 */ pub fn new() -> Self { // 初始化空樹 Self { root: None } } /* 獲取二元樹根節點 */ pub fn get_root(&self) -> OptionTreeNodeRc { self.root.clone() } /* 查詢節點 */ pub fn search(&self, num: i32) -> OptionTreeNodeRc { let mut cur = self.root.clone(); // 迴圈查詢,越過葉節點後跳出 while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // 目標節點在 cur 的右子樹中 Ordering::Greater => cur = node.borrow().right.clone(), // 目標節點在 cur 的左子樹中 Ordering::Less => cur = node.borrow().left.clone(), // 找到目標節點,跳出迴圈 Ordering::Equal => break, } } // 返回目標節點 cur } /* 插入節點 */ pub fn insert(&mut self, num: i32) { // 若樹為空,則初始化根節點 if self.root.is_none() { self.root = Some(TreeNode::new(num)); return; } let mut cur = self.root.clone(); let mut pre = None; // 迴圈查詢,越過葉節點後跳出 while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // 找到重複節點,直接返回 Ordering::Equal => return, // 插入位置在 cur 的右子樹中 Ordering::Greater => { pre = cur.clone(); cur = node.borrow().right.clone(); } // 插入位置在 cur 的左子樹中 Ordering::Less => { pre = cur.clone(); cur = node.borrow().left.clone(); } } } // 插入節點 let pre = pre.unwrap(); let node = Some(TreeNode::new(num)); if num > pre.borrow().val { pre.borrow_mut().right = node; } else { pre.borrow_mut().left = node; } } /* 刪除節點 */ pub fn remove(&mut self, num: i32) { // 若樹為空,直接提前返回 if self.root.is_none() { return; } let mut cur = self.root.clone(); let mut pre = None; // 迴圈查詢,越過葉節點後跳出 while let Some(node) = cur.clone() { match num.cmp(&node.borrow().val) { // 找到待刪除節點,跳出迴圈 Ordering::Equal => break, // 待刪除節點在 cur 的右子樹中 Ordering::Greater => { pre = cur.clone(); cur = node.borrow().right.clone(); } // 待刪除節點在 cur 的左子樹中 Ordering::Less => { pre = cur.clone(); cur = node.borrow().left.clone(); } } } // 若無待刪除節點,則直接返回 if cur.is_none() { return; } let cur = cur.unwrap(); let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone()); match (left_child.clone(), right_child.clone()) { // 子節點數量 = 0 or 1 (None, None) | (Some(_), None) | (None, Some(_)) => { // 當子節點數量 = 0 / 1 時, child = nullptr / 該子節點 let child = left_child.or(right_child); let pre = pre.unwrap(); // 刪除節點 cur if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) { let left = pre.borrow().left.clone(); if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) { pre.borrow_mut().left = child; } else { pre.borrow_mut().right = child; } } else { // 若刪除節點為根節點,則重新指定根節點 self.root = child; } } // 子節點數量 = 2 (Some(_), Some(_)) => { // 獲取中序走訪中 cur 的下一個節點 let mut tmp = cur.borrow().right.clone(); while let Some(node) = tmp.clone() { if node.borrow().left.is_some() { tmp = node.borrow().left.clone(); } else { break; } } let tmp_val = tmp.unwrap().borrow().val; // 遞迴刪除節點 tmp self.remove(tmp_val); // 用 tmp 覆蓋 cur cur.borrow_mut().val = tmp_val; } } } } /* Driver Code */ fn main() { /* 初始化二元搜尋樹 */ let mut bst = BinarySearchTree::new(); // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for &num in &nums { bst.insert(num); } println!("\n初始化的二元樹為\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); /* 查詢結點 */ let node = bst.search(7); println!( "\n查詢到的節點物件為 {:?},節點值 = {}", node.clone().unwrap(), node.clone().unwrap().borrow().val ); /* 插入節點 */ bst.insert(16); println!("\n插入節點 16 後,二元樹為\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); /* 刪除節點 */ bst.remove(1); println!("\n刪除節點 1 後,二元樹為\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); bst.remove(2); println!("\n刪除節點 2 後,二元樹為\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); bst.remove(4); println!("\n刪除節點 4 後,二元樹為\n"); print_util::print_tree(bst.get_root().as_ref().unwrap()); } ================================================ FILE: zh-hant/codes/rust/chapter_tree/binary_tree.rs ================================================ /** * File: binary_tree.rs * Created Time: 2023-02-27 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use std::rc::Rc; use hello_algo_rust::include::{print_util, TreeNode}; /* Driver Code */ fn main() { /* 初始化二元樹 */ // 初始化節點 let n1 = TreeNode::new(1); let n2 = TreeNode::new(2); let n3 = TreeNode::new(3); let n4 = TreeNode::new(4); let n5 = TreeNode::new(5); // 構建節點之間的引用(指標) n1.borrow_mut().left = Some(Rc::clone(&n2)); n1.borrow_mut().right = Some(Rc::clone(&n3)); n2.borrow_mut().left = Some(Rc::clone(&n4)); n2.borrow_mut().right = Some(Rc::clone(&n5)); println!("\n初始化二元樹\n"); print_util::print_tree(&n1); // 插入節點與刪除節點 let p = TreeNode::new(0); // 在 n1 -> n2 中間插入節點 P p.borrow_mut().left = Some(Rc::clone(&n2)); n1.borrow_mut().left = Some(Rc::clone(&p)); println!("\n插入節點 P 後\n"); print_util::print_tree(&n1); // 刪除節點 P drop(p); n1.borrow_mut().left = Some(Rc::clone(&n2)); println!("\n刪除節點 P 後\n"); print_util::print_tree(&n1); } ================================================ FILE: zh-hant/codes/rust/chapter_tree/binary_tree_bfs.rs ================================================ /* * File: binary_tree_bfs.rs * Created Time: 2023-04-07 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use hello_algo_rust::op_vec; use std::collections::VecDeque; use std::{cell::RefCell, rc::Rc}; /* 層序走訪 */ fn level_order(root: &Rc>) -> Vec { // 初始化佇列,加入根節點 let mut que = VecDeque::new(); que.push_back(root.clone()); // 初始化一個串列,用於儲存走訪序列 let mut vec = Vec::new(); while let Some(node) = que.pop_front() { // 隊列出隊 vec.push(node.borrow().val); // 儲存節點值 if let Some(left) = node.borrow().left.as_ref() { que.push_back(left.clone()); // 左子節點入列 } if let Some(right) = node.borrow().right.as_ref() { que.push_back(right.clone()); // 右子節點入列 }; } vec } /* Driver Code */ fn main() { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]).unwrap(); println!("初始化二元樹\n"); print_util::print_tree(&root); /* 層序走訪 */ let vec = level_order(&root); print!("\n層序走訪的節點列印序列 = {:?}", vec); } ================================================ FILE: zh-hant/codes/rust/chapter_tree/binary_tree_dfs.rs ================================================ /* * File: binary_tree_dfs.rs * Created Time: 2023-04-06 * Author: xBLACKICEx (xBLACKICE@outlook.com) */ use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; use hello_algo_rust::op_vec; use std::cell::RefCell; use std::rc::Rc; /* 前序走訪 */ fn pre_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 let node = node.borrow(); res.push(node.val); dfs(node.left.as_ref(), res); dfs(node.right.as_ref(), res); } } dfs(root, &mut result); result } /* 中序走訪 */ fn in_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 let node = node.borrow(); dfs(node.left.as_ref(), res); res.push(node.val); dfs(node.right.as_ref(), res); } } dfs(root, &mut result); result } /* 後序走訪 */ fn post_order(root: Option<&Rc>>) -> Vec { let mut result = vec![]; fn dfs(root: Option<&Rc>>, res: &mut Vec) { if let Some(node) = root { // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 let node = node.borrow(); dfs(node.left.as_ref(), res); dfs(node.right.as_ref(), res); res.push(node.val); } } dfs(root, &mut result); result } /* Driver Code */ fn main() { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]); println!("初始化二元樹\n"); print_util::print_tree(root.as_ref().unwrap()); /* 前序走訪 */ let vec = pre_order(root.as_ref()); println!("\n前序走訪的節點列印序列 = {:?}", vec); /* 中序走訪 */ let vec = in_order(root.as_ref()); println!("\n中序走訪的節點列印序列 = {:?}", vec); /* 後序走訪 */ let vec = post_order(root.as_ref()); print!("\n後序走訪的節點列印序列 = {:?}", vec); } ================================================ FILE: zh-hant/codes/rust/include/include.rs ================================================ /* * File: include.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICE@outlook.com) */ pub mod print_util; pub mod list_node; pub mod tree_node; pub mod vertex; ================================================ FILE: zh-hant/codes/rust/include/list_node.rs ================================================ /* * File: list_node.rs * Created Time: 2023-03-05 * Author: codingonion (coderonion@gmail.com), rongyi (hiarongyi@gmail.com) */ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; #[derive(Debug)] pub struct ListNode { pub val: T, pub next: Option>>>, } impl ListNode { pub fn new(val: T) -> Rc>> { Rc::new(RefCell::new(ListNode { val, next: None })) } /* 將陣列反序列化為鏈結串列 */ pub fn arr_to_linked_list(array: &[T]) -> Option>>> where T: Copy + Clone, { let mut head = None; // insert in reverse order for item in array.iter().rev() { let node = Rc::new(RefCell::new(ListNode { val: *item, next: head.take(), })); head = Some(node); } head } /* 將鏈結串列轉化為雜湊表 */ pub fn linked_list_to_hashmap( linked_list: Option>>>, ) -> HashMap>>> where T: std::hash::Hash + Eq + Copy + Clone, { let mut hashmap = HashMap::new(); let mut node = linked_list; while let Some(cur) = node { let borrow = cur.borrow(); hashmap.insert(borrow.val.clone(), cur.clone()); node = borrow.next.clone(); } hashmap } } ================================================ FILE: zh-hant/codes/rust/include/print_util.rs ================================================ /* * File: print_util.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use std::cell::{Cell, RefCell}; use std::fmt::Display; use std::collections::{HashMap, VecDeque}; use std::rc::Rc; use crate::list_node::ListNode; use crate::tree_node::{TreeNode, vec_to_tree}; struct Trunk<'a, 'b> { prev: Option<&'a Trunk<'a, 'b>>, str: Cell<&'b str>, } /* 列印陣列 */ pub fn print_array(nums: &[T]) { print!("["); if nums.len() > 0 { for (i, num) in nums.iter().enumerate() { print!("{}{}", num, if i == nums.len() - 1 {"]"} else {", "} ); } } else { print!("]"); } } /* 列印雜湊表 */ pub fn print_hash_map(map: &HashMap) { for (key, value) in map { println!("{key} -> {value}"); } } /* 列印佇列(雙向佇列) */ pub fn print_queue(queue: &VecDeque) { print!("["); let iter = queue.iter(); for (i, data) in iter.enumerate() { print!("{}{}", data, if i == queue.len() - 1 {"]"} else {", "} ); } } /* 列印鏈結串列 */ pub fn print_linked_list(head: &Rc>>) { print!("{}{}", head.borrow().val, if head.borrow().next.is_none() {"\n"} else {" -> "}); if let Some(node) = &head.borrow().next { return print_linked_list(node); } } /* 列印二元樹 */ pub fn print_tree(root: &Rc>) { _print_tree(Some(root), None, false); } /* 列印二元樹 */ fn _print_tree(root: Option<&Rc>>, prev: Option<&Trunk>, is_right: bool) { if let Some(node) = root { let mut prev_str = " "; let trunk = Trunk { prev, str: Cell::new(prev_str) }; _print_tree(node.borrow().right.as_ref(), Some(&trunk), true); if prev.is_none() { trunk.str.set("———"); } else if is_right { trunk.str.set("/———"); prev_str = " |"; } else { trunk.str.set("\\———"); prev.as_ref().unwrap().str.set(prev_str); } show_trunks(Some(&trunk)); println!(" {}", node.borrow().val); if let Some(prev) = prev { prev.str.set(prev_str); } trunk.str.set(" |"); _print_tree(node.borrow().left.as_ref(), Some(&trunk), false); } } fn show_trunks(trunk: Option<&Trunk>) { if let Some(trunk) = trunk { show_trunks(trunk.prev); print!("{}", trunk.str.get()); } } /* 列印堆積 */ pub fn print_heap(heap: Vec) { println!("堆積的陣列表示:{:?}", heap); println!("堆積的樹狀表示:"); if let Some(root) = vec_to_tree(heap.into_iter().map(|val| Some(val)).collect()) { print_tree(&root); } } ================================================ FILE: zh-hant/codes/rust/include/tree_node.rs ================================================ /* * File: tree_node.rs * Created Time: 2023-02-27 * Author: xBLACKICEx (xBLACKICE@outlook.com), night-cruise (2586447362@qq.com) */ use std::cell::RefCell; use std::rc::Rc; /* 二元樹節點型別 */ #[derive(Debug)] pub struct TreeNode { pub val: i32, pub height: i32, pub parent: Option>>, pub left: Option>>, pub right: Option>>, } impl TreeNode { /* 建構子 */ pub fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, height: 0, parent: None, left: None, right: None, })) } } #[macro_export] macro_rules! op_vec { ( $( $x:expr ),* ) => { vec![ $( Option::from($x).map(|x| x) ),* ] }; } // 序列化編碼規則請參考: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二元樹的陣列表示: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // 二元樹的鏈結串列表示: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* 將串列反序列化為二元樹:遞迴 */ fn vec_to_tree_dfs(arr: &[Option], i: usize) -> Option>> { if i >= arr.len() || arr[i].is_none() { return None; } let root = TreeNode::new(arr[i].unwrap()); root.borrow_mut().left = vec_to_tree_dfs(arr, 2 * i + 1); root.borrow_mut().right = vec_to_tree_dfs(arr, 2 * i + 2); Some(root) } /* 將串列反序列化為二元樹 */ pub fn vec_to_tree(arr: Vec>) -> Option>> { vec_to_tree_dfs(&arr, 0) } /* 將二元樹序列化為串列:遞迴 */ fn tree_to_vec_dfs(root: Option<&Rc>>, i: usize, res: &mut Vec>) { if let Some(root) = root { // i + 1 is the minimum valid size to access index i while res.len() < i + 1 { res.push(None); } res[i] = Some(root.borrow().val); tree_to_vec_dfs(root.borrow().left.as_ref(), 2 * i + 1, res); tree_to_vec_dfs(root.borrow().right.as_ref(), 2 * i + 2, res); } } /* 將二元樹序列化為串列 */ pub fn tree_to_vec(root: Option>>) -> Vec> { let mut res = vec![]; tree_to_vec_dfs(root.as_ref(), 0, &mut res); res } ================================================ FILE: zh-hant/codes/rust/include/vertex.rs ================================================ /* * File: vertex.rs * Created Time: 2023-07-13 * Author: night-cruise (2586447362@qq.com) */ /* 頂點型別 */ #[derive(Copy, Clone, Hash, PartialEq, Eq)] pub struct Vertex { pub val: i32, } /* 輸入值串列 vals ,返回頂點串列 vets */ pub fn vals_to_vets(vals: Vec) -> Vec { vals.into_iter().map(|val| Vertex { val }).collect() } /* 輸入頂點串列 vets ,返回值串列 vals */ pub fn vets_to_vals(vets: Vec) -> Vec { vets.into_iter().map(|vet| vet.val).collect() } ================================================ FILE: zh-hant/codes/rust/src/include/list_node.rs ================================================ /* * File: list_node.rs * Created Time: 2023-03-05 * Author: codingonion (coderonion@gmail.com), rongyi (hiarongyi@gmail.com) */ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; #[derive(Debug)] pub struct ListNode { pub val: T, pub next: Option>>>, } impl ListNode { pub fn new(val: T) -> Rc>> { Rc::new(RefCell::new(ListNode { val, next: None })) } /* 將陣列反序列化為鏈結串列 */ pub fn arr_to_linked_list(array: &[T]) -> Option>>> where T: Copy + Clone, { let mut head = None; // insert in reverse order for item in array.iter().rev() { let node = Rc::new(RefCell::new(ListNode { val: *item, next: head.take(), })); head = Some(node); } head } /* 將鏈結串列轉化為雜湊表 */ pub fn linked_list_to_hashmap( linked_list: Option>>>, ) -> HashMap>>> where T: std::hash::Hash + Eq + Copy + Clone, { let mut hashmap = HashMap::new(); let mut node = linked_list; while let Some(cur) = node { let borrow = cur.borrow(); hashmap.insert(borrow.val.clone(), cur.clone()); node = borrow.next.clone(); } hashmap } } ================================================ FILE: zh-hant/codes/rust/src/include/mod.rs ================================================ /* * File: include.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICE@outlook.com) */ pub mod list_node; pub mod print_util; pub mod tree_node; pub mod vertex; // rexport to include pub use list_node::*; pub use print_util::*; pub use tree_node::*; pub use vertex::*; ================================================ FILE: zh-hant/codes/rust/src/include/print_util.rs ================================================ /* * File: print_util.rs * Created Time: 2023-02-05 * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) */ use std::cell::{Cell, RefCell}; use std::fmt::Display; use std::collections::{HashMap, VecDeque}; use std::rc::Rc; use super::list_node::ListNode; use super::tree_node::{TreeNode, vec_to_tree}; struct Trunk<'a, 'b> { prev: Option<&'a Trunk<'a, 'b>>, str: Cell<&'b str>, } /* 列印陣列 */ pub fn print_array(nums: &[T]) { print!("["); if nums.len() > 0 { for (i, num) in nums.iter().enumerate() { print!("{}{}", num, if i == nums.len() - 1 {"]"} else {", "} ); } } else { print!("]"); } } /* 列印雜湊表 */ pub fn print_hash_map(map: &HashMap) { for (key, value) in map { println!("{key} -> {value}"); } } /* 列印佇列(雙向佇列) */ pub fn print_queue(queue: &VecDeque) { print!("["); let iter = queue.iter(); for (i, data) in iter.enumerate() { print!("{}{}", data, if i == queue.len() - 1 {"]"} else {", "} ); } } /* 列印鏈結串列 */ pub fn print_linked_list(head: &Rc>>) { print!("{}{}", head.borrow().val, if head.borrow().next.is_none() {"\n"} else {" -> "}); if let Some(node) = &head.borrow().next { return print_linked_list(node); } } /* 列印二元樹 */ pub fn print_tree(root: &Rc>) { _print_tree(Some(root), None, false); } /* 列印二元樹 */ fn _print_tree(root: Option<&Rc>>, prev: Option<&Trunk>, is_right: bool) { if let Some(node) = root { let mut prev_str = " "; let trunk = Trunk { prev, str: Cell::new(prev_str) }; _print_tree(node.borrow().right.as_ref(), Some(&trunk), true); if prev.is_none() { trunk.str.set("———"); } else if is_right { trunk.str.set("/———"); prev_str = " |"; } else { trunk.str.set("\\———"); prev.as_ref().unwrap().str.set(prev_str); } show_trunks(Some(&trunk)); println!(" {}", node.borrow().val); if let Some(prev) = prev { prev.str.set(prev_str); } trunk.str.set(" |"); _print_tree(node.borrow().left.as_ref(), Some(&trunk), false); } } fn show_trunks(trunk: Option<&Trunk>) { if let Some(trunk) = trunk { show_trunks(trunk.prev); print!("{}", trunk.str.get()); } } /* 列印堆積 */ pub fn print_heap(heap: Vec) { println!("堆積的陣列表示:{:?}", heap); println!("堆積的樹狀表示:"); if let Some(root) = vec_to_tree(heap.into_iter().map(|val| Some(val)).collect()) { print_tree(&root); } } ================================================ FILE: zh-hant/codes/rust/src/include/tree_node.rs ================================================ /* * File: tree_node.rs * Created Time: 2023-02-27 * Author: xBLACKICEx (xBLACKICE@outlook.com), night-cruise (2586447362@qq.com) */ use std::cell::RefCell; use std::rc::Rc; /* 二元樹節點型別 */ #[derive(Debug)] pub struct TreeNode { pub val: i32, pub height: i32, pub parent: Option>>, pub left: Option>>, pub right: Option>>, } impl TreeNode { /* 建構子 */ pub fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, height: 0, parent: None, left: None, right: None, })) } } #[macro_export] macro_rules! op_vec { ( $( $x:expr ),* ) => { vec![ $(Option::from($x)),* ] }; } // 序列化編碼規則請參考: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二元樹的陣列表示: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] // 二元樹的鏈結串列表示: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* 將串列反序列化為二元樹:遞迴 */ fn vec_to_tree_dfs(arr: &[Option], i: usize) -> Option>> { if i >= arr.len() || arr[i].is_none() { return None; } let root = TreeNode::new(arr[i].unwrap()); root.borrow_mut().left = vec_to_tree_dfs(arr, 2 * i + 1); root.borrow_mut().right = vec_to_tree_dfs(arr, 2 * i + 2); Some(root) } /* 將串列反序列化為二元樹 */ pub fn vec_to_tree(arr: Vec>) -> Option>> { vec_to_tree_dfs(&arr, 0) } /* 將二元樹序列化為串列:遞迴 */ fn tree_to_vec_dfs(root: Option<&Rc>>, i: usize, res: &mut Vec>) { if let Some(root) = root { // i + 1 is the minimum valid size to access index i while res.len() < i + 1 { res.push(None); } res[i] = Some(root.borrow().val); tree_to_vec_dfs(root.borrow().left.as_ref(), 2 * i + 1, res); tree_to_vec_dfs(root.borrow().right.as_ref(), 2 * i + 2, res); } } /* 將二元樹序列化為串列 */ pub fn tree_to_vec(root: Option>>) -> Vec> { let mut res = vec![]; tree_to_vec_dfs(root.as_ref(), 0, &mut res); res } ================================================ FILE: zh-hant/codes/rust/src/include/vertex.rs ================================================ /* * File: vertex.rs * Created Time: 2023-07-13 * Author: night-cruise (2586447362@qq.com) */ /* 頂點型別 */ #[derive(Copy, Clone, Hash, PartialEq, Eq)] pub struct Vertex { pub val: i32, } impl From for Vertex { fn from(value: i32) -> Self { Self { val: value } } } /* 輸入值串列 vals ,返回頂點串列 vets */ pub fn vals_to_vets(vals: Vec) -> Vec { vals.into_iter().map(|val| val.into()).collect() } /* 輸入頂點串列 vets ,返回值串列 vals */ pub fn vets_to_vals(vets: Vec) -> Vec { vets.into_iter().map(|vet| vet.val).collect() } ================================================ FILE: zh-hant/codes/rust/src/lib.rs ================================================ pub mod include; ================================================ FILE: zh-hant/codes/swift/.gitignore ================================================ # Created by https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager # Edit at https://www.toptal.com/developers/gitignore?templates=objective-c,swift,swiftpackagemanager ### Objective-C ### # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## User settings xcuserdata/ ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint *.xccheckout ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ DerivedData/ *.moved-aside *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 ## Obj-C/Swift specific *.hmap ## App packaging *.ipa *.dSYM.zip *.dSYM # CocoaPods # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build/ # fastlane # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output # Code Injection # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ ### Objective-C Patch ### ### Swift ### # Xcode # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins # Package.resolved # *.xcodeproj # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project # .swiftpm .build/ # CocoaPods # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts # Accio dependency management Dependencies/ .accio/ # fastlane # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control # Code Injection # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode ### SwiftPackageManager ### Packages xcuserdata *.xcodeproj # End of https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager ================================================ FILE: zh-hant/codes/swift/Package.resolved ================================================ { "pins" : [ { "identity" : "swift-collections", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { "branch" : "release/1.1", "revision" : "4a1d92ba85027010d2c528c05576cde9a362254b" } } ], "version" : 2 } ================================================ FILE: zh-hant/codes/swift/Package.swift ================================================ // swift-tools-version: 5.7 import PackageDescription let package = Package( name: "HelloAlgo", products: [ // chapter_computational_complexity .executable(name: "iteration", targets: ["iteration"]), .executable(name: "recursion", targets: ["recursion"]), .executable(name: "time_complexity", targets: ["time_complexity"]), .executable(name: "worst_best_time_complexity", targets: ["worst_best_time_complexity"]), .executable(name: "space_complexity", targets: ["space_complexity"]), // chapter_array_and_linkedlist .executable(name: "array", targets: ["array"]), .executable(name: "linked_list", targets: ["linked_list"]), .executable(name: "list", targets: ["list"]), .executable(name: "my_list", targets: ["my_list"]), // chapter_stack_and_queue .executable(name: "stack", targets: ["stack"]), .executable(name: "linkedlist_stack", targets: ["linkedlist_stack"]), .executable(name: "array_stack", targets: ["array_stack"]), .executable(name: "queue", targets: ["queue"]), .executable(name: "linkedlist_queue", targets: ["linkedlist_queue"]), .executable(name: "array_queue", targets: ["array_queue"]), .executable(name: "deque", targets: ["deque"]), .executable(name: "linkedlist_deque", targets: ["linkedlist_deque"]), .executable(name: "array_deque", targets: ["array_deque"]), // chapter_hashing .executable(name: "hash_map", targets: ["hash_map"]), .executable(name: "array_hash_map", targets: ["array_hash_map"]), .executable(name: "hash_map_chaining", targets: ["hash_map_chaining"]), .executable(name: "hash_map_open_addressing", targets: ["hash_map_open_addressing"]), .executable(name: "simple_hash", targets: ["simple_hash"]), .executable(name: "built_in_hash", targets: ["built_in_hash"]), // chapter_tree .executable(name: "binary_tree", targets: ["binary_tree"]), .executable(name: "binary_tree_bfs", targets: ["binary_tree_bfs"]), .executable(name: "binary_tree_dfs", targets: ["binary_tree_dfs"]), .executable(name: "array_binary_tree", targets: ["array_binary_tree"]), .executable(name: "binary_search_tree", targets: ["binary_search_tree"]), .executable(name: "avl_tree", targets: ["avl_tree"]), // chapter_heap .executable(name: "heap", targets: ["heap"]), .executable(name: "my_heap", targets: ["my_heap"]), .executable(name: "top_k", targets: ["top_k"]), // chapter_graph .executable(name: "graph_adjacency_matrix", targets: ["graph_adjacency_matrix"]), .executable(name: "graph_adjacency_list", targets: ["graph_adjacency_list"]), .executable(name: "graph_bfs", targets: ["graph_bfs"]), .executable(name: "graph_dfs", targets: ["graph_dfs"]), // chapter_searching .executable(name: "binary_search", targets: ["binary_search"]), .executable(name: "binary_search_insertion", targets: ["binary_search_insertion"]), .executable(name: "binary_search_edge", targets: ["binary_search_edge"]), .executable(name: "two_sum", targets: ["two_sum"]), .executable(name: "linear_search", targets: ["linear_search"]), .executable(name: "hashing_search", targets: ["hashing_search"]), // chapter_sorting .executable(name: "selection_sort", targets: ["selection_sort"]), .executable(name: "bubble_sort", targets: ["bubble_sort"]), .executable(name: "insertion_sort", targets: ["insertion_sort"]), .executable(name: "quick_sort", targets: ["quick_sort"]), .executable(name: "merge_sort", targets: ["merge_sort"]), .executable(name: "heap_sort", targets: ["heap_sort"]), .executable(name: "bucket_sort", targets: ["bucket_sort"]), .executable(name: "counting_sort", targets: ["counting_sort"]), .executable(name: "radix_sort", targets: ["radix_sort"]), // chapter_divide_and_conquer .executable(name: "binary_search_recur", targets: ["binary_search_recur"]), .executable(name: "build_tree", targets: ["build_tree"]), .executable(name: "hanota", targets: ["hanota"]), // chapter_backtracking .executable(name: "preorder_traversal_i_compact", targets: ["preorder_traversal_i_compact"]), .executable(name: "preorder_traversal_ii_compact", targets: ["preorder_traversal_ii_compact"]), .executable(name: "preorder_traversal_iii_compact", targets: ["preorder_traversal_iii_compact"]), .executable(name: "preorder_traversal_iii_template", targets: ["preorder_traversal_iii_template"]), .executable(name: "permutations_i", targets: ["permutations_i"]), .executable(name: "permutations_ii", targets: ["permutations_ii"]), .executable(name: "subset_sum_i_naive", targets: ["subset_sum_i_naive"]), .executable(name: "subset_sum_i", targets: ["subset_sum_i"]), .executable(name: "subset_sum_ii", targets: ["subset_sum_ii"]), .executable(name: "n_queens", targets: ["n_queens"]), // chapter_dynamic_programming .executable(name: "climbing_stairs_backtrack", targets: ["climbing_stairs_backtrack"]), .executable(name: "climbing_stairs_dfs", targets: ["climbing_stairs_dfs"]), .executable(name: "climbing_stairs_dfs_mem", targets: ["climbing_stairs_dfs_mem"]), .executable(name: "climbing_stairs_dp", targets: ["climbing_stairs_dp"]), .executable(name: "min_cost_climbing_stairs_dp", targets: ["min_cost_climbing_stairs_dp"]), .executable(name: "climbing_stairs_constraint_dp", targets: ["climbing_stairs_constraint_dp"]), .executable(name: "min_path_sum", targets: ["min_path_sum"]), .executable(name: "knapsack", targets: ["knapsack"]), .executable(name: "unbounded_knapsack", targets: ["unbounded_knapsack"]), .executable(name: "coin_change", targets: ["coin_change"]), .executable(name: "coin_change_ii", targets: ["coin_change_ii"]), .executable(name: "edit_distance", targets: ["edit_distance"]), // chapter_greedy .executable(name: "coin_change_greedy", targets: ["coin_change_greedy"]), .executable(name: "fractional_knapsack", targets: ["fractional_knapsack"]), .executable(name: "max_capacity", targets: ["max_capacity"]), .executable(name: "max_product_cutting", targets: ["max_product_cutting"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-collections", branch: "release/1.1"), ], targets: [ // helper .target(name: "utils", path: "utils"), .target(name: "graph_adjacency_list_target", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list_target.swift"], swiftSettings: [.define("TARGET")]), .target(name: "binary_search_insertion_target", path: "chapter_searching", sources: ["binary_search_insertion_target.swift"], swiftSettings: [.define("TARGET")]), // chapter_computational_complexity .executableTarget(name: "iteration", path: "chapter_computational_complexity", sources: ["iteration.swift"]), .executableTarget(name: "recursion", path: "chapter_computational_complexity", sources: ["recursion.swift"]), .executableTarget(name: "time_complexity", path: "chapter_computational_complexity", sources: ["time_complexity.swift"]), .executableTarget(name: "worst_best_time_complexity", path: "chapter_computational_complexity", sources: ["worst_best_time_complexity.swift"]), .executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]), // chapter_array_and_linkedlist .executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]), .executableTarget(name: "linked_list", dependencies: ["utils"], path: "chapter_array_and_linkedlist", sources: ["linked_list.swift"]), .executableTarget(name: "list", path: "chapter_array_and_linkedlist", sources: ["list.swift"]), .executableTarget(name: "my_list", path: "chapter_array_and_linkedlist", sources: ["my_list.swift"]), // chapter_stack_and_queue .executableTarget(name: "stack", path: "chapter_stack_and_queue", sources: ["stack.swift"]), .executableTarget(name: "linkedlist_stack", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_stack.swift"]), .executableTarget(name: "array_stack", path: "chapter_stack_and_queue", sources: ["array_stack.swift"]), .executableTarget(name: "queue", path: "chapter_stack_and_queue", sources: ["queue.swift"]), .executableTarget(name: "linkedlist_queue", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_queue.swift"]), .executableTarget(name: "array_queue", path: "chapter_stack_and_queue", sources: ["array_queue.swift"]), .executableTarget(name: "deque", path: "chapter_stack_and_queue", sources: ["deque.swift"]), .executableTarget(name: "linkedlist_deque", path: "chapter_stack_and_queue", sources: ["linkedlist_deque.swift"]), .executableTarget(name: "array_deque", path: "chapter_stack_and_queue", sources: ["array_deque.swift"]), // chapter_hashing .executableTarget(name: "hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map.swift"]), .executableTarget(name: "array_hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["array_hash_map.swift"]), .executableTarget(name: "hash_map_chaining", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_chaining.swift"]), .executableTarget(name: "hash_map_open_addressing", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_open_addressing.swift"]), .executableTarget(name: "simple_hash", path: "chapter_hashing", sources: ["simple_hash.swift"]), .executableTarget(name: "built_in_hash", dependencies: ["utils"], path: "chapter_hashing", sources: ["built_in_hash.swift"]), // chapter_tree .executableTarget(name: "binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree.swift"]), .executableTarget(name: "binary_tree_bfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_bfs.swift"]), .executableTarget(name: "binary_tree_dfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_dfs.swift"]), .executableTarget(name: "array_binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["array_binary_tree.swift"]), .executableTarget(name: "binary_search_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_search_tree.swift"]), .executableTarget(name: "avl_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["avl_tree.swift"]), // chapter_heap .executableTarget(name: "heap", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["heap.swift"]), .executableTarget(name: "my_heap", dependencies: ["utils"], path: "chapter_heap", sources: ["my_heap.swift"]), .executableTarget(name: "top_k", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["top_k.swift"]), // chapter_graph .executableTarget(name: "graph_adjacency_matrix", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_matrix.swift"]), .executableTarget(name: "graph_adjacency_list", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list.swift"]), .executableTarget(name: "graph_bfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_bfs.swift"]), .executableTarget(name: "graph_dfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_dfs.swift"]), // chapter_searching .executableTarget(name: "binary_search", path: "chapter_searching", sources: ["binary_search.swift"]), .executableTarget(name: "binary_search_insertion", path: "chapter_searching", sources: ["binary_search_insertion.swift"]), .executableTarget(name: "binary_search_edge", dependencies: ["binary_search_insertion_target"], path: "chapter_searching", sources: ["binary_search_edge.swift"]), .executableTarget(name: "two_sum", path: "chapter_searching", sources: ["two_sum.swift"]), .executableTarget(name: "linear_search", dependencies: ["utils"], path: "chapter_searching", sources: ["linear_search.swift"]), .executableTarget(name: "hashing_search", dependencies: ["utils"], path: "chapter_searching", sources: ["hashing_search.swift"]), // chapter_sorting .executableTarget(name: "selection_sort", path: "chapter_sorting", sources: ["selection_sort.swift"]), .executableTarget(name: "bubble_sort", path: "chapter_sorting", sources: ["bubble_sort.swift"]), .executableTarget(name: "insertion_sort", path: "chapter_sorting", sources: ["insertion_sort.swift"]), .executableTarget(name: "quick_sort", path: "chapter_sorting", sources: ["quick_sort.swift"]), .executableTarget(name: "merge_sort", path: "chapter_sorting", sources: ["merge_sort.swift"]), .executableTarget(name: "heap_sort", path: "chapter_sorting", sources: ["heap_sort.swift"]), .executableTarget(name: "bucket_sort", path: "chapter_sorting", sources: ["bucket_sort.swift"]), .executableTarget(name: "counting_sort", path: "chapter_sorting", sources: ["counting_sort.swift"]), .executableTarget(name: "radix_sort", path: "chapter_sorting", sources: ["radix_sort.swift"]), // chapter_divide_and_conquer .executableTarget(name: "binary_search_recur", path: "chapter_divide_and_conquer", sources: ["binary_search_recur.swift"]), .executableTarget(name: "build_tree", dependencies: ["utils"], path: "chapter_divide_and_conquer", sources: ["build_tree.swift"]), .executableTarget(name: "hanota", path: "chapter_divide_and_conquer", sources: ["hanota.swift"]), // chapter_backtracking .executableTarget(name: "preorder_traversal_i_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_i_compact.swift"]), .executableTarget(name: "preorder_traversal_ii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_ii_compact.swift"]), .executableTarget(name: "preorder_traversal_iii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_compact.swift"]), .executableTarget(name: "preorder_traversal_iii_template", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_template.swift"]), .executableTarget(name: "permutations_i", path: "chapter_backtracking", sources: ["permutations_i.swift"]), .executableTarget(name: "permutations_ii", path: "chapter_backtracking", sources: ["permutations_ii.swift"]), .executableTarget(name: "subset_sum_i_naive", path: "chapter_backtracking", sources: ["subset_sum_i_naive.swift"]), .executableTarget(name: "subset_sum_i", path: "chapter_backtracking", sources: ["subset_sum_i.swift"]), .executableTarget(name: "subset_sum_ii", path: "chapter_backtracking", sources: ["subset_sum_ii.swift"]), .executableTarget(name: "n_queens", path: "chapter_backtracking", sources: ["n_queens.swift"]), // chapter_dynamic_programming .executableTarget(name: "climbing_stairs_backtrack", path: "chapter_dynamic_programming", sources: ["climbing_stairs_backtrack.swift"]), .executableTarget(name: "climbing_stairs_dfs", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs.swift"]), .executableTarget(name: "climbing_stairs_dfs_mem", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs_mem.swift"]), .executableTarget(name: "climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dp.swift"]), .executableTarget(name: "min_cost_climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["min_cost_climbing_stairs_dp.swift"]), .executableTarget(name: "climbing_stairs_constraint_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_constraint_dp.swift"]), .executableTarget(name: "min_path_sum", path: "chapter_dynamic_programming", sources: ["min_path_sum.swift"]), .executableTarget(name: "knapsack", path: "chapter_dynamic_programming", sources: ["knapsack.swift"]), .executableTarget(name: "unbounded_knapsack", path: "chapter_dynamic_programming", sources: ["unbounded_knapsack.swift"]), .executableTarget(name: "coin_change", path: "chapter_dynamic_programming", sources: ["coin_change.swift"]), .executableTarget(name: "coin_change_ii", path: "chapter_dynamic_programming", sources: ["coin_change_ii.swift"]), .executableTarget(name: "edit_distance", path: "chapter_dynamic_programming", sources: ["edit_distance.swift"]), // chapter_greedy .executableTarget(name: "coin_change_greedy", path: "chapter_greedy", sources: ["coin_change_greedy.swift"]), .executableTarget(name: "fractional_knapsack", path: "chapter_greedy", sources: ["fractional_knapsack.swift"]), .executableTarget(name: "max_capacity", path: "chapter_greedy", sources: ["max_capacity.swift"]), .executableTarget(name: "max_product_cutting", path: "chapter_greedy", sources: ["max_product_cutting.swift"]), ] ) ================================================ FILE: zh-hant/codes/swift/chapter_array_and_linkedlist/array.swift ================================================ /** * File: array.swift * Created Time: 2023-01-05 * Author: nuomi1 (nuomi1@qq.com) */ /* 隨機訪問元素 */ func randomAccess(nums: [Int]) -> Int { // 在區間 [0, nums.count) 中隨機抽取一個數字 let randomIndex = nums.indices.randomElement()! // 獲取並返回隨機元素 let randomNum = nums[randomIndex] return randomNum } /* 擴展陣列長度 */ func extend(nums: [Int], enlarge: Int) -> [Int] { // 初始化一個擴展長度後的陣列 var res = Array(repeating: 0, count: nums.count + enlarge) // 將原陣列中的所有元素複製到新陣列 for i in nums.indices { res[i] = nums[i] } // 返回擴展後的新陣列 return res } /* 在陣列的索引 index 處插入元素 num */ func insert(nums: inout [Int], num: Int, index: Int) { // 把索引 index 以及之後的所有元素向後移動一位 for i in nums.indices.dropFirst(index).reversed() { nums[i] = nums[i - 1] } // 將 num 賦給 index 處的元素 nums[index] = num } /* 刪除索引 index 處的元素 */ func remove(nums: inout [Int], index: Int) { // 把索引 index 之後的所有元素向前移動一位 for i in nums.indices.dropFirst(index).dropLast() { nums[i] = nums[i + 1] } } /* 走訪陣列 */ func traverse(nums: [Int]) { var count = 0 // 透過索引走訪陣列 for i in nums.indices { count += nums[i] } // 直接走訪陣列元素 for num in nums { count += num } // 同時走訪資料索引和元素 for (i, num) in nums.enumerated() { count += nums[i] count += num } } /* 在陣列中查詢指定元素 */ func find(nums: [Int], target: Int) -> Int { for i in nums.indices { if nums[i] == target { return i } } return -1 } @main enum _Array { /* Driver Code */ static func main() { /* 初始化陣列 */ let arr = Array(repeating: 0, count: 5) print("陣列 arr = \(arr)") var nums = [1, 3, 2, 5, 4] print("陣列 nums = \(nums)") /* 隨機訪問 */ let randomNum = randomAccess(nums: nums) print("在 nums 中獲取隨機元素 \(randomNum)") /* 長度擴展 */ nums = extend(nums: nums, enlarge: 3) print("將陣列長度擴展至 8 ,得到 nums = \(nums)") /* 插入元素 */ insert(nums: &nums, num: 6, index: 3) print("在索引 3 處插入數字 6 ,得到 nums = \(nums)") /* 刪除元素 */ remove(nums: &nums, index: 2) print("刪除索引 2 處的元素,得到 nums = \(nums)") /* 走訪陣列 */ traverse(nums: nums) /* 查詢元素 */ let index = find(nums: nums, target: 3) print("在 nums 中查詢元素 3 ,得到索引 = \(index)") } } ================================================ FILE: zh-hant/codes/swift/chapter_array_and_linkedlist/linked_list.swift ================================================ /** * File: linked_list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 在鏈結串列的節點 n0 之後插入節點 P */ func insert(n0: ListNode, P: ListNode) { let n1 = n0.next P.next = n1 n0.next = P } /* 刪除鏈結串列的節點 n0 之後的首個節點 */ func remove(n0: ListNode) { if n0.next == nil { return } // n0 -> P -> n1 let P = n0.next let n1 = P?.next n0.next = n1 } /* 訪問鏈結串列中索引為 index 的節點 */ func access(head: ListNode, index: Int) -> ListNode? { var head: ListNode? = head for _ in 0 ..< index { if head == nil { return nil } head = head?.next } return head } /* 在鏈結串列中查詢值為 target 的首個節點 */ func find(head: ListNode, target: Int) -> Int { var head: ListNode? = head var index = 0 while head != nil { if head?.val == target { return index } head = head?.next index += 1 } return -1 } @main enum LinkedList { /* Driver Code */ static func main() { /* 初始化鏈結串列 */ // 初始化各個節點 let n0 = ListNode(x: 1) let n1 = ListNode(x: 3) let n2 = ListNode(x: 2) let n3 = ListNode(x: 5) let n4 = ListNode(x: 4) // 構建節點之間的引用 n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 print("初始化的鏈結串列為") PrintUtil.printLinkedList(head: n0) /* 插入節點 */ insert(n0: n0, P: ListNode(x: 0)) print("插入節點後的鏈結串列為") PrintUtil.printLinkedList(head: n0) /* 刪除節點 */ remove(n0: n0) print("刪除節點後的鏈結串列為") PrintUtil.printLinkedList(head: n0) /* 訪問節點 */ let node = access(head: n0, index: 3) print("鏈結串列中索引 3 處的節點的值 = \(node!.val)") /* 查詢節點 */ let index = find(head: n0, target: 2) print("鏈結串列中值為 2 的節點的索引 = \(index)") } } ================================================ FILE: zh-hant/codes/swift/chapter_array_and_linkedlist/list.swift ================================================ /** * File: list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ @main enum List { /* Driver Code */ static func main() { /* 初始化串列 */ var nums = [1, 3, 2, 5, 4] print("串列 nums = \(nums)") /* 訪問元素 */ let num = nums[1] print("訪問索引 1 處的元素,得到 num = \(num)") /* 更新元素 */ nums[1] = 0 print("將索引 1 處的元素更新為 0 ,得到 nums = \(nums)") /* 清空串列 */ nums.removeAll() print("清空串列後 nums = \(nums)") /* 在尾部新增元素 */ nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) print("新增元素後 nums = \(nums)") /* 在中間插入元素 */ nums.insert(6, at: 3) print("在索引 3 處插入數字 6 ,得到 nums = \(nums)") /* 刪除元素 */ nums.remove(at: 3) print("刪除索引 3 處的元素,得到 nums = \(nums)") /* 透過索引走訪串列 */ var count = 0 for i in nums.indices { count += nums[i] } /* 直接走訪串列元素 */ count = 0 for x in nums { count += x } /* 拼接兩個串列 */ let nums1 = [6, 8, 7, 10, 9] nums.append(contentsOf: nums1) print("將串列 nums1 拼接到 nums 之後,得到 nums = \(nums)") /* 排序串列 */ nums.sort() print("排序串列後 nums = \(nums)") } } ================================================ FILE: zh-hant/codes/swift/chapter_array_and_linkedlist/my_list.swift ================================================ /** * File: my_list.swift * Created Time: 2023-01-08 * Author: nuomi1 (nuomi1@qq.com) */ /* 串列類別 */ class MyList { private var arr: [Int] // 陣列(儲存串列元素) private var _capacity: Int // 串列容量 private var _size: Int // 串列長度(當前元素數量) private let extendRatio: Int // 每次串列擴容的倍數 /* 建構子 */ init() { _capacity = 10 _size = 0 extendRatio = 2 arr = Array(repeating: 0, count: _capacity) } /* 獲取串列長度(當前元素數量)*/ func size() -> Int { _size } /* 獲取串列容量 */ func capacity() -> Int { _capacity } /* 訪問元素 */ func get(index: Int) -> Int { // 索引如果越界則丟擲錯誤,下同 if index < 0 || index >= size() { fatalError("索引越界") } return arr[index] } /* 更新元素 */ func set(index: Int, num: Int) { if index < 0 || index >= size() { fatalError("索引越界") } arr[index] = num } /* 在尾部新增元素 */ func add(num: Int) { // 元素數量超出容量時,觸發擴容機制 if size() == capacity() { extendCapacity() } arr[size()] = num // 更新元素數量 _size += 1 } /* 在中間插入元素 */ func insert(index: Int, num: Int) { if index < 0 || index >= size() { fatalError("索引越界") } // 元素數量超出容量時,觸發擴容機制 if size() == capacity() { extendCapacity() } // 將索引 index 以及之後的元素都向後移動一位 for j in (index ..< size()).reversed() { arr[j + 1] = arr[j] } arr[index] = num // 更新元素數量 _size += 1 } /* 刪除元素 */ @discardableResult func remove(index: Int) -> Int { if index < 0 || index >= size() { fatalError("索引越界") } let num = arr[index] // 將將索引 index 之後的元素都向前移動一位 for j in index ..< (size() - 1) { arr[j] = arr[j + 1] } // 更新元素數量 _size -= 1 // 返回被刪除的元素 return num } /* 串列擴容 */ func extendCapacity() { // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列 arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1)) // 更新串列容量 _capacity = arr.count } /* 將串列轉換為陣列 */ func toArray() -> [Int] { Array(arr.prefix(size())) } } @main enum _MyList { /* Driver Code */ static func main() { /* 初始化串列 */ let nums = MyList() /* 在尾部新增元素 */ nums.add(num: 1) nums.add(num: 3) nums.add(num: 2) nums.add(num: 5) nums.add(num: 4) print("串列 nums = \(nums.toArray()) ,容量 = \(nums.capacity()) ,長度 = \(nums.size())") /* 在中間插入元素 */ nums.insert(index: 3, num: 6) print("在索引 3 處插入數字 6 ,得到 nums = \(nums.toArray())") /* 刪除元素 */ nums.remove(index: 3) print("刪除索引 3 處的元素,得到 nums = \(nums.toArray())") /* 訪問元素 */ let num = nums.get(index: 1) print("訪問索引 1 處的元素,得到 num = \(num)") /* 更新元素 */ nums.set(index: 1, num: 0) print("將索引 1 處的元素更新為 0 ,得到 nums = \(nums.toArray())") /* 測試擴容機制 */ for i in 0 ..< 10 { // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 nums.add(num: i) } print("擴容後的串列 nums = \(nums.toArray()) ,容量 = \(nums.capacity()) ,長度 = \(nums.size())") } } ================================================ FILE: zh-hant/codes/swift/chapter_backtracking/n_queens.swift ================================================ /** * File: n_queens.swift * Created Time: 2023-05-14 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯演算法:n 皇后 */ func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) { // 當放置完所有行時,記錄解 if row == n { res.append(state) return } // 走訪所有列 for col in 0 ..< n { // 計算該格子對應的主對角線和次對角線 let diag1 = row - col + n - 1 let diag2 = row + col // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 if !cols[col] && !diags1[diag1] && !diags2[diag2] { // 嘗試:將皇后放置在該格子 state[row][col] = "Q" cols[col] = true diags1[diag1] = true diags2[diag2] = true // 放置下一行 backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) // 回退:將該格子恢復為空位 state[row][col] = "#" cols[col] = false diags1[diag1] = false diags2[diag2] = false } } } /* 求解 n 皇后 */ func nQueens(n: Int) -> [[[String]]] { // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 var state = Array(repeating: Array(repeating: "#", count: n), count: n) var cols = Array(repeating: false, count: n) // 記錄列是否有皇后 var diags1 = Array(repeating: false, count: 2 * n - 1) // 記錄主對角線上是否有皇后 var diags2 = Array(repeating: false, count: 2 * n - 1) // 記錄次對角線上是否有皇后 var res: [[[String]]] = [] backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) return res } @main enum NQueens { /* Driver Code */ static func main() { let n = 4 let res = nQueens(n: n) print("輸入棋盤長寬為 \(n)") print("皇后放置方案共有 \(res.count) 種") for state in res { print("--------------------") for row in state { print(row) } } } } ================================================ FILE: zh-hant/codes/swift/chapter_backtracking/permutations_i.swift ================================================ /** * File: permutations_i.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯演算法:全排列 I */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // 當狀態長度等於元素數量時,記錄解 if state.count == choices.count { res.append(state) return } // 走訪所有選擇 for (i, choice) in choices.enumerated() { // 剪枝:不允許重複選擇元素 if !selected[i] { // 嘗試:做出選擇,更新狀態 selected[i] = true state.append(choice) // 進行下一輪選擇 backtrack(state: &state, choices: choices, selected: &selected, res: &res) // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false state.removeLast() } } } /* 全排列 I */ func permutationsI(nums: [Int]) -> [[Int]] { var state: [Int] = [] var selected = Array(repeating: false, count: nums.count) var res: [[Int]] = [] backtrack(state: &state, choices: nums, selected: &selected, res: &res) return res } @main enum PermutationsI { /* Driver Code */ static func main() { let nums = [1, 2, 3] let res = permutationsI(nums: nums) print("輸入陣列 nums = \(nums)") print("所有排列 res = \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_backtracking/permutations_ii.swift ================================================ /** * File: permutations_ii.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯演算法:全排列 II */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // 當狀態長度等於元素數量時,記錄解 if state.count == choices.count { res.append(state) return } // 走訪所有選擇 var duplicated: Set = [] for (i, choice) in choices.enumerated() { // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 if !selected[i], !duplicated.contains(choice) { // 嘗試:做出選擇,更新狀態 duplicated.insert(choice) // 記錄選擇過的元素值 selected[i] = true state.append(choice) // 進行下一輪選擇 backtrack(state: &state, choices: choices, selected: &selected, res: &res) // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false state.removeLast() } } } /* 全排列 II */ func permutationsII(nums: [Int]) -> [[Int]] { var state: [Int] = [] var selected = Array(repeating: false, count: nums.count) var res: [[Int]] = [] backtrack(state: &state, choices: nums, selected: &selected, res: &res) return res } @main enum PermutationsII { /* Driver Code */ static func main() { let nums = [1, 2, 3] let res = permutationsII(nums: nums) print("輸入陣列 nums = \(nums)") print("所有排列 res = \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift ================================================ /** * File: preorder_traversal_i_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var res: [TreeNode] = [] /* 前序走訪:例題一 */ func preOrder(root: TreeNode?) { guard let root = root else { return } if root.val == 7 { // 記錄解 res.append(root) } preOrder(root: root.left) preOrder(root: root.right) } @main enum PreorderTraversalICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\n初始化二元樹") PrintUtil.printTree(root: root) // 前序走訪 res = [] preOrder(root: root) print("\n輸出所有值為 7 的節點") var vals: [Int] = [] for node in res { vals.append(node.val) } print(vals) } } ================================================ FILE: zh-hant/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift ================================================ /** * File: preorder_traversal_ii_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var path: [TreeNode] = [] var res: [[TreeNode]] = [] /* 前序走訪:例題二 */ func preOrder(root: TreeNode?) { guard let root = root else { return } // 嘗試 path.append(root) if root.val == 7 { // 記錄解 res.append(path) } preOrder(root: root.left) preOrder(root: root.right) // 回退 path.removeLast() } @main enum PreorderTraversalIICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\n初始化二元樹") PrintUtil.printTree(root: root) // 前序走訪 path = [] res = [] preOrder(root: root) print("\n輸出所有根節點到節點 7 的路徑") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift ================================================ /** * File: preorder_traversal_iii_compact.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils var path: [TreeNode] = [] var res: [[TreeNode]] = [] /* 前序走訪:例題三 */ func preOrder(root: TreeNode?) { // 剪枝 guard let root = root, root.val != 3 else { return } // 嘗試 path.append(root) if root.val == 7 { // 記錄解 res.append(path) } preOrder(root: root.left) preOrder(root: root.right) // 回退 path.removeLast() } @main enum PreorderTraversalIIICompact { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\n初始化二元樹") PrintUtil.printTree(root: root) // 前序走訪 path = [] res = [] preOrder(root: root) print("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift ================================================ /** * File: preorder_traversal_iii_template.swift * Created Time: 2023-04-30 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 判斷當前狀態是否為解 */ func isSolution(state: [TreeNode]) -> Bool { !state.isEmpty && state.last!.val == 7 } /* 記錄解 */ func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) { res.append(state) } /* 判斷在當前狀態下,該選擇是否合法 */ func isValid(state: [TreeNode], choice: TreeNode?) -> Bool { choice != nil && choice!.val != 3 } /* 更新狀態 */ func makeChoice(state: inout [TreeNode], choice: TreeNode) { state.append(choice) } /* 恢復狀態 */ func undoChoice(state: inout [TreeNode], choice: TreeNode) { state.removeLast() } /* 回溯演算法:例題三 */ func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) { // 檢查是否為解 if isSolution(state: state) { recordSolution(state: state, res: &res) } // 走訪所有選擇 for choice in choices { // 剪枝:檢查選擇是否合法 if isValid(state: state, choice: choice) { // 嘗試:做出選擇,更新狀態 makeChoice(state: &state, choice: choice) // 進行下一輪選擇 backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res) // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state: &state, choice: choice) } } } @main enum PreorderTraversalIIITemplate { /* Driver Code */ static func main() { let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) print("\n初始化二元樹") PrintUtil.printTree(root: root) // 回溯演算法 var state: [TreeNode] = [] var res: [[TreeNode]] = [] backtrack(state: &state, choices: [root].compactMap { $0 }, res: &res) print("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") for path in res { var vals: [Int] = [] for node in path { vals.append(node.val) } print(vals) } } } ================================================ FILE: zh-hant/codes/swift/chapter_backtracking/subset_sum_i.swift ================================================ /** * File: subset_sum_i.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯演算法:子集和 I */ func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { // 子集和等於 target 時,記錄解 if target == 0 { res.append(state) return } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 for i in choices.indices.dropFirst(start) { // 剪枝一:若子集和超過 target ,則直接結束迴圈 // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target if target - choices[i] < 0 { break } // 嘗試:做出選擇,更新 target, start state.append(choices[i]) // 進行下一輪選擇 backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res) // 回退:撤銷選擇,恢復到之前的狀態 state.removeLast() } } /* 求解子集和 I */ func subsetSumI(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // 狀態(子集) let nums = nums.sorted() // 對 nums 進行排序 let start = 0 // 走訪起始點 var res: [[Int]] = [] // 結果串列(子集串列) backtrack(state: &state, target: target, choices: nums, start: start, res: &res) return res } @main enum SubsetSumI { /* Driver Code */ static func main() { let nums = [3, 4, 5] let target = 9 let res = subsetSumI(nums: nums, target: target) print("輸入陣列 nums = \(nums), target = \(target)") print("所有和等於 \(target) 的子集 res = \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_backtracking/subset_sum_i_naive.swift ================================================ /** * File: subset_sum_i_naive.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯演算法:子集和 I */ func backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) { // 子集和等於 target 時,記錄解 if total == target { res.append(state) return } // 走訪所有選擇 for i in choices.indices { // 剪枝:若子集和超過 target ,則跳過該選擇 if total + choices[i] > target { continue } // 嘗試:做出選擇,更新元素和 total state.append(choices[i]) // 進行下一輪選擇 backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res) // 回退:撤銷選擇,恢復到之前的狀態 state.removeLast() } } /* 求解子集和 I(包含重複子集) */ func subsetSumINaive(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // 狀態(子集) let total = 0 // 子集和 var res: [[Int]] = [] // 結果串列(子集串列) backtrack(state: &state, target: target, total: total, choices: nums, res: &res) return res } @main enum SubsetSumINaive { /* Driver Code */ static func main() { let nums = [3, 4, 5] let target = 9 let res = subsetSumINaive(nums: nums, target: target) print("輸入陣列 nums = \(nums), target = \(target)") print("所有和等於 \(target) 的子集 res = \(res)") print("請注意,該方法輸出的結果包含重複集合") } } ================================================ FILE: zh-hant/codes/swift/chapter_backtracking/subset_sum_ii.swift ================================================ /** * File: subset_sum_ii.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯演算法:子集和 II */ func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { // 子集和等於 target 時,記錄解 if target == 0 { res.append(state) return } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 for i in choices.indices.dropFirst(start) { // 剪枝一:若子集和超過 target ,則直接結束迴圈 // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target if target - choices[i] < 0 { break } // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 if i > start, choices[i] == choices[i - 1] { continue } // 嘗試:做出選擇,更新 target, start state.append(choices[i]) // 進行下一輪選擇 backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res) // 回退:撤銷選擇,恢復到之前的狀態 state.removeLast() } } /* 求解子集和 II */ func subsetSumII(nums: [Int], target: Int) -> [[Int]] { var state: [Int] = [] // 狀態(子集) let nums = nums.sorted() // 對 nums 進行排序 let start = 0 // 走訪起始點 var res: [[Int]] = [] // 結果串列(子集串列) backtrack(state: &state, target: target, choices: nums, start: start, res: &res) return res } @main enum SubsetSumII { /* Driver Code */ static func main() { let nums = [4, 4, 5] let target = 9 let res = subsetSumII(nums: nums, target: target) print("輸入陣列 nums = \(nums), target = \(target)") print("所有和等於 \(target) 的子集 res = \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_computational_complexity/iteration.swift ================================================ /** * File: iteration.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* for 迴圈 */ func forLoop(n: Int) -> Int { var res = 0 // 迴圈求和 1, 2, ..., n-1, n for i in 1 ... n { res += i } return res } /* while 迴圈 */ func whileLoop(n: Int) -> Int { var res = 0 var i = 1 // 初始化條件變數 // 迴圈求和 1, 2, ..., n-1, n while i <= n { res += i i += 1 // 更新條件變數 } return res } /* while 迴圈(兩次更新) */ func whileLoopII(n: Int) -> Int { var res = 0 var i = 1 // 初始化條件變數 // 迴圈求和 1, 4, 10, ... while i <= n { res += i // 更新條件變數 i += 1 i *= 2 } return res } /* 雙層 for 迴圈 */ func nestedForLoop(n: Int) -> String { var res = "" // 迴圈 i = 1, 2, ..., n-1, n for i in 1 ... n { // 迴圈 j = 1, 2, ..., n-1, n for j in 1 ... n { res.append("(\(i), \(j)), ") } } return res } @main enum Iteration { /* Driver Code */ static func main() { let n = 5 var res = 0 res = forLoop(n: n) print("\nfor 迴圈的求和結果 res = \(res)") res = whileLoop(n: n) print("\nwhile 迴圈的求和結果 res = \(res)") res = whileLoopII(n: n) print("\nwhile 迴圈(兩次更新)求和結果 res = \(res)") let resStr = nestedForLoop(n: n) print("\n雙層 for 迴圈的走訪結果 \(resStr)") } } ================================================ FILE: zh-hant/codes/swift/chapter_computational_complexity/recursion.swift ================================================ /** * File: recursion.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 遞迴 */ func recur(n: Int) -> Int { // 終止條件 if n == 1 { return 1 } // 遞:遞迴呼叫 let res = recur(n: n - 1) // 迴:返回結果 return n + res } /* 使用迭代模擬遞迴 */ func forLoopRecur(n: Int) -> Int { // 使用一個顯式的堆疊來模擬系統呼叫堆疊 var stack: [Int] = [] var res = 0 // 遞:遞迴呼叫 for i in (1 ... n).reversed() { // 透過“入堆疊操作”模擬“遞” stack.append(i) } // 迴:返回結果 while !stack.isEmpty { // 透過“出堆疊操作”模擬“迴” res += stack.removeLast() } // res = 1+2+3+...+n return res } /* 尾遞迴 */ func tailRecur(n: Int, res: Int) -> Int { // 終止條件 if n == 0 { return res } // 尾遞迴呼叫 return tailRecur(n: n - 1, res: res + n) } /* 費波那契數列:遞迴 */ func fib(n: Int) -> Int { // 終止條件 f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1 } // 遞迴呼叫 f(n) = f(n-1) + f(n-2) let res = fib(n: n - 1) + fib(n: n - 2) // 返回結果 f(n) return res } @main enum Recursion { /* Driver Code */ static func main() { let n = 5 var res = 0 res = recursion.recur(n: n) print("\n遞迴函式的求和結果 res = \(res)") res = recursion.forLoopRecur(n: n) print("\n使用迭代模擬遞迴求和結果 res = \(res)") res = recursion.tailRecur(n: n, res: 0) print("\n尾遞迴函式的求和結果 res = \(res)") res = recursion.fib(n: n) print("\n費波那契數列的第 \(n) 項為 \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_computational_complexity/space_complexity.swift ================================================ /** * File: space_complexity.swift * Created Time: 2023-01-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 函式 */ @discardableResult func function() -> Int { // 執行某些操作 return 0 } /* 常數階 */ func constant(n: Int) { // 常數、變數、物件佔用 O(1) 空間 let a = 0 var b = 0 let nums = Array(repeating: 0, count: 10000) let node = ListNode(x: 0) // 迴圈中的變數佔用 O(1) 空間 for _ in 0 ..< n { let c = 0 } // 迴圈中的函式佔用 O(1) 空間 for _ in 0 ..< n { function() } } /* 線性階 */ func linear(n: Int) { // 長度為 n 的陣列佔用 O(n) 空間 let nums = Array(repeating: 0, count: n) // 長度為 n 的串列佔用 O(n) 空間 let nodes = (0 ..< n).map { ListNode(x: $0) } // 長度為 n 的雜湊表佔用 O(n) 空間 let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") }) } /* 線性階(遞迴實現) */ func linearRecur(n: Int) { print("遞迴 n = \(n)") if n == 1 { return } linearRecur(n: n - 1) } /* 平方階 */ func quadratic(n: Int) { // 二維串列佔用 O(n^2) 空間 let numList = Array(repeating: Array(repeating: 0, count: n), count: n) } /* 平方階(遞迴實現) */ @discardableResult func quadraticRecur(n: Int) -> Int { if n <= 0 { return 0 } // 陣列 nums 長度為 n, n-1, ..., 2, 1 let nums = Array(repeating: 0, count: n) print("遞迴 n = \(n) 中的 nums 長度 = \(nums.count)") return quadraticRecur(n: n - 1) } /* 指數階(建立滿二元樹) */ func buildTree(n: Int) -> TreeNode? { if n == 0 { return nil } let root = TreeNode(x: 0) root.left = buildTree(n: n - 1) root.right = buildTree(n: n - 1) return root } @main enum SpaceComplexity { /* Driver Code */ static func main() { let n = 5 // 常數階 constant(n: n) // 線性階 linear(n: n) linearRecur(n: n) // 平方階 quadratic(n: n) quadraticRecur(n: n) // 指數階 let root = buildTree(n: n) PrintUtil.printTree(root: root) } } ================================================ FILE: zh-hant/codes/swift/chapter_computational_complexity/time_complexity.swift ================================================ /** * File: time_complexity.swift * Created Time: 2022-12-26 * Author: nuomi1 (nuomi1@qq.com) */ /* 常數階 */ func constant(n: Int) -> Int { var count = 0 let size = 100_000 for _ in 0 ..< size { count += 1 } return count } /* 線性階 */ func linear(n: Int) -> Int { var count = 0 for _ in 0 ..< n { count += 1 } return count } /* 線性階(走訪陣列) */ func arrayTraversal(nums: [Int]) -> Int { var count = 0 // 迴圈次數與陣列長度成正比 for _ in nums { count += 1 } return count } /* 平方階 */ func quadratic(n: Int) -> Int { var count = 0 // 迴圈次數與資料大小 n 成平方關係 for _ in 0 ..< n { for _ in 0 ..< n { count += 1 } } return count } /* 平方階(泡沫排序) */ func bubbleSort(nums: inout [Int]) -> Int { var count = 0 // 計數器 // 外迴圈:未排序區間為 [0, i] for i in nums.indices.dropFirst().reversed() { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for j in 0 ..< i { if nums[j] > nums[j + 1] { // 交換 nums[j] 與 nums[j + 1] let tmp = nums[j] nums[j] = nums[j + 1] nums[j + 1] = tmp count += 3 // 元素交換包含 3 個單元操作 } } } return count } /* 指數階(迴圈實現) */ func exponential(n: Int) -> Int { var count = 0 var base = 1 // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) for _ in 0 ..< n { for _ in 0 ..< base { count += 1 } base *= 2 } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count } /* 指數階(遞迴實現) */ func expRecur(n: Int) -> Int { if n == 1 { return 1 } return expRecur(n: n - 1) + expRecur(n: n - 1) + 1 } /* 對數階(迴圈實現) */ func logarithmic(n: Int) -> Int { var count = 0 var n = n while n > 1 { n = n / 2 count += 1 } return count } /* 對數階(遞迴實現) */ func logRecur(n: Int) -> Int { if n <= 1 { return 0 } return logRecur(n: n / 2) + 1 } /* 線性對數階 */ func linearLogRecur(n: Int) -> Int { if n <= 1 { return 1 } var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2) for _ in stride(from: 0, to: n, by: 1) { count += 1 } return count } /* 階乘階(遞迴實現) */ func factorialRecur(n: Int) -> Int { if n == 0 { return 1 } var count = 0 // 從 1 個分裂出 n 個 for _ in 0 ..< n { count += factorialRecur(n: n - 1) } return count } @main enum TimeComplexity { /* Driver Code */ static func main() { // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 let n = 8 print("輸入資料大小 n = \(n)") var count = constant(n: n) print("常數階的操作數量 = \(count)") count = linear(n: n) print("線性階的操作數量 = \(count)") count = arrayTraversal(nums: Array(repeating: 0, count: n)) print("線性階(走訪陣列)的操作數量 = \(count)") count = quadratic(n: n) print("平方階的操作數量 = \(count)") var nums = Array(stride(from: n, to: 0, by: -1)) // [n,n-1,...,2,1] count = bubbleSort(nums: &nums) print("平方階(泡沫排序)的操作數量 = \(count)") count = exponential(n: n) print("指數階(迴圈實現)的操作數量 = \(count)") count = expRecur(n: n) print("指數階(遞迴實現)的操作數量 = \(count)") count = logarithmic(n: n) print("對數階(迴圈實現)的操作數量 = \(count)") count = logRecur(n: n) print("對數階(遞迴實現)的操作數量 = \(count)") count = linearLogRecur(n: n) print("線性對數階(遞迴實現)的操作數量 = \(count)") count = factorialRecur(n: n) print("階乘階(遞迴實現)的操作數量 = \(count)") } } ================================================ FILE: zh-hant/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift ================================================ /** * File: worst_best_time_complexity.swift * Created Time: 2022-12-26 * Author: nuomi1 (nuomi1@qq.com) */ /* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ func randomNumbers(n: Int) -> [Int] { // 生成陣列 nums = { 1, 2, 3, ..., n } var nums = Array(1 ... n) // 隨機打亂陣列元素 nums.shuffle() return nums } /* 查詢陣列 nums 中數字 1 所在索引 */ func findOne(nums: [Int]) -> Int { for i in nums.indices { // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) if nums[i] == 1 { return i } } return -1 } @main enum WorstBestTimeComplexity { /* Driver Code */ static func main() { for _ in 0 ..< 10 { let n = 100 let nums = randomNumbers(n: n) let index = findOne(nums: nums) print("陣列 [ 1, 2, ..., n ] 被打亂後 = \(nums)") print("數字 1 的索引為 \(index)") } } } ================================================ FILE: zh-hant/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift ================================================ /** * File: binary_search_recur.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 二分搜尋:問題 f(i, j) */ func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int { // 若區間為空,代表無目標元素,則返回 -1 if i > j { return -1 } // 計算中點索引 m let m = (i + j) / 2 if nums[m] < target { // 遞迴子問題 f(m+1, j) return dfs(nums: nums, target: target, i: m + 1, j: j) } else if nums[m] > target { // 遞迴子問題 f(i, m-1) return dfs(nums: nums, target: target, i: i, j: m - 1) } else { // 找到目標元素,返回其索引 return m } } /* 二分搜尋 */ func binarySearch(nums: [Int], target: Int) -> Int { // 求解問題 f(0, n-1) dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1) } @main enum BinarySearchRecur { /* Driver Code */ static func main() { let target = 6 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] // 二分搜尋(雙閉區間) let index = binarySearch(nums: nums, target: target) print("目標元素 6 的索引 = \(index)") } } ================================================ FILE: zh-hant/codes/swift/chapter_divide_and_conquer/build_tree.swift ================================================ /** * File: build_tree.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 構建二元樹:分治 */ func dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? { // 子樹區間為空時終止 if r - l < 0 { return nil } // 初始化根節點 let root = TreeNode(x: preorder[i]) // 查詢 m ,從而劃分左右子樹 let m = inorderMap[preorder[i]]! // 子問題:構建左子樹 root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1) // 子問題:構建右子樹 root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r) // 返回根節點 return root } /* 構建二元樹 */ func buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? { // 初始化雜湊表,儲存 inorder 元素到索引的對映 let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset } return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1) } @main enum BuildTree { /* Driver Code */ static func main() { let preorder = [3, 9, 2, 1, 7] let inorder = [9, 3, 1, 2, 7] print("前序走訪 = \(preorder)") print("中序走訪 = \(inorder)") let root = buildTree(preorder: preorder, inorder: inorder) print("構建的二元樹為:") PrintUtil.printTree(root: root) } } ================================================ FILE: zh-hant/codes/swift/chapter_divide_and_conquer/hanota.swift ================================================ /** * File: hanota.swift * Created Time: 2023-09-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 移動一個圓盤 */ func move(src: inout [Int], tar: inout [Int]) { // 從 src 頂部拿出一個圓盤 let pan = src.popLast()! // 將圓盤放入 tar 頂部 tar.append(pan) } /* 求解河內塔問題 f(i) */ func dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) { // 若 src 只剩下一個圓盤,則直接將其移到 tar if i == 1 { move(src: &src, tar: &tar) return } // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf dfs(i: i - 1, src: &src, buf: &tar, tar: &buf) // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar move(src: &src, tar: &tar) // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar dfs(i: i - 1, src: &buf, buf: &src, tar: &tar) } /* 求解河內塔問題 */ func solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) { let n = A.count // 串列尾部是柱子頂部 // 將 src 頂部 n 個圓盤藉助 B 移到 C dfs(i: n, src: &A, buf: &B, tar: &C) } @main enum Hanota { /* Driver Code */ static func main() { // 串列尾部是柱子頂部 var A = [5, 4, 3, 2, 1] var B: [Int] = [] var C: [Int] = [] print("初始狀態下:") print("A = \(A)") print("B = \(B)") print("C = \(C)") solveHanota(A: &A, B: &B, C: &C) print("圓盤移動完成後:") print("A = \(A)") print("B = \(B)") print("C = \(C)") } } ================================================ FILE: zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift ================================================ /** * File: climbing_stairs_backtrack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 回溯 */ func backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) { // 當爬到第 n 階時,方案數量加 1 if state == n { res[0] += 1 } // 走訪所有選擇 for choice in choices { // 剪枝:不允許越過第 n 階 if state + choice > n { continue } // 嘗試:做出選擇,更新狀態 backtrack(choices: choices, state: state + choice, n: n, res: &res) // 回退 } } /* 爬樓梯:回溯 */ func climbingStairsBacktrack(n: Int) -> Int { let choices = [1, 2] // 可選擇向上爬 1 階或 2 階 let state = 0 // 從第 0 階開始爬 var res: [Int] = [] res.append(0) // 使用 res[0] 記錄方案數量 backtrack(choices: choices, state: state, n: n, res: &res) return res[0] } @main enum ClimbingStairsBacktrack { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsBacktrack(n: n) print("爬 \(n) 階樓梯共有 \(res) 種方案") } } ================================================ FILE: zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift ================================================ /** * File: climbing_stairs_constraint_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 帶約束爬樓梯:動態規劃 */ func climbingStairsConstraintDP(n: Int) -> Int { if n == 1 || n == 2 { return 1 } // 初始化 dp 表,用於儲存子問題的解 var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1) // 初始狀態:預設最小子問題的解 dp[1][1] = 1 dp[1][2] = 0 dp[2][1] = 0 dp[2][2] = 1 // 狀態轉移:從較小子問題逐步求解較大子問題 for i in 3 ... n { dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] } return dp[n][1] + dp[n][2] } @main enum ClimbingStairsConstraintDP { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsConstraintDP(n: n) print("爬 \(n) 階樓梯共有 \(res) 種方案") } } ================================================ FILE: zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift ================================================ /** * File: climbing_stairs_dfs.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 搜尋 */ func dfs(i: Int) -> Int { // 已知 dp[1] 和 dp[2] ,返回之 if i == 1 || i == 2 { return i } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i: i - 1) + dfs(i: i - 2) return count } /* 爬樓梯:搜尋 */ func climbingStairsDFS(n: Int) -> Int { dfs(i: n) } @main enum ClimbingStairsDFS { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsDFS(n: n) print("爬 \(n) 階樓梯共有 \(res) 種方案") } } ================================================ FILE: zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift ================================================ /** * File: climbing_stairs_dfs_mem.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 記憶化搜尋 */ func dfs(i: Int, mem: inout [Int]) -> Int { // 已知 dp[1] 和 dp[2] ,返回之 if i == 1 || i == 2 { return i } // 若存在記錄 dp[i] ,則直接返回之 if mem[i] != -1 { return mem[i] } // dp[i] = dp[i-1] + dp[i-2] let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem) // 記錄 dp[i] mem[i] = count return count } /* 爬樓梯:記憶化搜尋 */ func climbingStairsDFSMem(n: Int) -> Int { // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 var mem = Array(repeating: -1, count: n + 1) return dfs(i: n, mem: &mem) } @main enum ClimbingStairsDFSMem { /* Driver Code */ static func main() { let n = 9 let res = climbingStairsDFSMem(n: n) print("爬 \(n) 階樓梯共有 \(res) 種方案") } } ================================================ FILE: zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift ================================================ /** * File: climbing_stairs_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 爬樓梯:動態規劃 */ func climbingStairsDP(n: Int) -> Int { if n == 1 || n == 2 { return n } // 初始化 dp 表,用於儲存子問題的解 var dp = Array(repeating: 0, count: n + 1) // 初始狀態:預設最小子問題的解 dp[1] = 1 dp[2] = 2 // 狀態轉移:從較小子問題逐步求解較大子問題 for i in 3 ... n { dp[i] = dp[i - 1] + dp[i - 2] } return dp[n] } /* 爬樓梯:空間最佳化後的動態規劃 */ func climbingStairsDPComp(n: Int) -> Int { if n == 1 || n == 2 { return n } var a = 1 var b = 2 for _ in 3 ... n { (a, b) = (b, a + b) } return b } @main enum ClimbingStairsDP { /* Driver Code */ static func main() { let n = 9 var res = climbingStairsDP(n: n) print("爬 \(n) 階樓梯共有 \(res) 種方案") res = climbingStairsDPComp(n: n) print("爬 \(n) 階樓梯共有 \(res) 種方案") } } ================================================ FILE: zh-hant/codes/swift/chapter_dynamic_programming/coin_change.swift ================================================ /** * File: coin_change.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 零錢兌換:動態規劃 */ func coinChangeDP(coins: [Int], amt: Int) -> Int { let n = coins.count let MAX = amt + 1 // 初始化 dp 表 var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) // 狀態轉移:首行首列 for a in 1 ... amt { dp[0][a] = MAX } // 狀態轉移:其餘行和列 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a] } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) } } } return dp[n][amt] != MAX ? dp[n][amt] : -1 } /* 零錢兌換:空間最佳化後的動態規劃 */ func coinChangeDPComp(coins: [Int], amt: Int) -> Int { let n = coins.count let MAX = amt + 1 // 初始化 dp 表 var dp = Array(repeating: MAX, count: amt + 1) dp[0] = 0 // 狀態轉移 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a] } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) } } } return dp[amt] != MAX ? dp[amt] : -1 } @main enum CoinChange { /* Driver Code */ static func main() { let coins = [1, 2, 5] let amt = 4 // 動態規劃 var res = coinChangeDP(coins: coins, amt: amt) print("湊到目標金額所需的最少硬幣數量為 \(res)") // 空間最佳化後的動態規劃 res = coinChangeDPComp(coins: coins, amt: amt) print("湊到目標金額所需的最少硬幣數量為 \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_dynamic_programming/coin_change_ii.swift ================================================ /** * File: coin_change_ii.swift * Created Time: 2023-07-16 * Author: nuomi1 (nuomi1@qq.com) */ /* 零錢兌換 II:動態規劃 */ func coinChangeIIDP(coins: [Int], amt: Int) -> Int { let n = coins.count // 初始化 dp 表 var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) // 初始化首列 for i in 0 ... n { dp[i][0] = 1 } // 狀態轉移 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a] } else { // 不選和選硬幣 i 這兩種方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] } } } return dp[n][amt] } /* 零錢兌換 II:空間最佳化後的動態規劃 */ func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { let n = coins.count // 初始化 dp 表 var dp = Array(repeating: 0, count: amt + 1) dp[0] = 1 // 狀態轉移 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a] } else { // 不選和選硬幣 i 這兩種方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]] } } } return dp[amt] } @main enum CoinChangeII { /* Driver Code */ static func main() { let coins = [1, 2, 5] let amt = 5 // 動態規劃 var res = coinChangeIIDP(coins: coins, amt: amt) print("湊出目標金額的硬幣組合數量為 \(res)") // 空間最佳化後的動態規劃 res = coinChangeIIDPComp(coins: coins, amt: amt) print("湊出目標金額的硬幣組合數量為 \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_dynamic_programming/edit_distance.swift ================================================ /** * File: edit_distance.swift * Created Time: 2023-07-16 * Author: nuomi1 (nuomi1@qq.com) */ /* 編輯距離:暴力搜尋 */ func editDistanceDFS(s: String, t: String, i: Int, j: Int) -> Int { // 若 s 和 t 都為空,則返回 0 if i == 0, j == 0 { return 0 } // 若 s 為空,則返回 t 長度 if i == 0 { return j } // 若 t 為空,則返回 s 長度 if j == 0 { return i } // 若兩字元相等,則直接跳過此兩字元 if s.utf8CString[i - 1] == t.utf8CString[j - 1] { return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) } // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) // 返回最少編輯步數 return min(min(insert, delete), replace) + 1 } /* 編輯距離:記憶化搜尋 */ func editDistanceDFSMem(s: String, t: String, mem: inout [[Int]], i: Int, j: Int) -> Int { // 若 s 和 t 都為空,則返回 0 if i == 0, j == 0 { return 0 } // 若 s 為空,則返回 t 長度 if i == 0 { return j } // 若 t 為空,則返回 s 長度 if j == 0 { return i } // 若已有記錄,則直接返回之 if mem[i][j] != -1 { return mem[i][j] } // 若兩字元相等,則直接跳過此兩字元 if s.utf8CString[i - 1] == t.utf8CString[j - 1] { return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) } // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) // 記錄並返回最少編輯步數 mem[i][j] = min(min(insert, delete), replace) + 1 return mem[i][j] } /* 編輯距離:動態規劃 */ func editDistanceDP(s: String, t: String) -> Int { let n = s.utf8CString.count let m = t.utf8CString.count var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1) // 狀態轉移:首行首列 for i in 1 ... n { dp[i][0] = i } for j in 1 ... m { dp[0][j] = j } // 狀態轉移:其餘行和列 for i in 1 ... n { for j in 1 ... m { if s.utf8CString[i - 1] == t.utf8CString[j - 1] { // 若兩字元相等,則直接跳過此兩字元 dp[i][j] = dp[i - 1][j - 1] } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 } } } return dp[n][m] } /* 編輯距離:空間最佳化後的動態規劃 */ func editDistanceDPComp(s: String, t: String) -> Int { let n = s.utf8CString.count let m = t.utf8CString.count var dp = Array(repeating: 0, count: m + 1) // 狀態轉移:首行 for j in 1 ... m { dp[j] = j } // 狀態轉移:其餘行 for i in 1 ... n { // 狀態轉移:首列 var leftup = dp[0] // 暫存 dp[i-1, j-1] dp[0] = i // 狀態轉移:其餘列 for j in 1 ... m { let temp = dp[j] if s.utf8CString[i - 1] == t.utf8CString[j - 1] { // 若兩字元相等,則直接跳過此兩字元 dp[j] = leftup } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 } leftup = temp // 更新為下一輪的 dp[i-1, j-1] } } return dp[m] } @main enum EditDistance { /* Driver Code */ static func main() { let s = "bag" let t = "pack" let n = s.utf8CString.count let m = t.utf8CString.count // 暴力搜尋 var res = editDistanceDFS(s: s, t: t, i: n, j: m) print("將 \(s) 更改為 \(t) 最少需要編輯 \(res) 步") // 記憶化搜尋 var mem = Array(repeating: Array(repeating: -1, count: m + 1), count: n + 1) res = editDistanceDFSMem(s: s, t: t, mem: &mem, i: n, j: m) print("將 \(s) 更改為 \(t) 最少需要編輯 \(res) 步") // 動態規劃 res = editDistanceDP(s: s, t: t) print("將 \(s) 更改為 \(t) 最少需要編輯 \(res) 步") // 空間最佳化後的動態規劃 res = editDistanceDPComp(s: s, t: t) print("將 \(s) 更改為 \(t) 最少需要編輯 \(res) 步") } } ================================================ FILE: zh-hant/codes/swift/chapter_dynamic_programming/knapsack.swift ================================================ /** * File: knapsack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 0-1 背包:暴力搜尋 */ func knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if i == 0 || c == 0 { return 0 } // 若超過背包容量,則只能選擇不放入背包 if wgt[i - 1] > c { return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) } // 計算不放入和放入物品 i 的最大價值 let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] // 返回兩種方案中價值更大的那一個 return max(no, yes) } /* 0-1 背包:記憶化搜尋 */ func knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if i == 0 || c == 0 { return 0 } // 若已有記錄,則直接返回 if mem[i][c] != -1 { return mem[i][c] } // 若超過背包容量,則只能選擇不放入背包 if wgt[i - 1] > c { return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) } // 計算不放入和放入物品 i 的最大價值 let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] // 記錄並返回兩種方案中價值更大的那一個 mem[i][c] = max(no, yes) return mem[i][c] } /* 0-1 背包:動態規劃 */ func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // 初始化 dp 表 var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) // 狀態轉移 for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c] } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) } } } return dp[n][cap] } /* 0-1 背包:空間最佳化後的動態規劃 */ func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // 初始化 dp 表 var dp = Array(repeating: 0, count: cap + 1) // 狀態轉移 for i in 1 ... n { // 倒序走訪 for c in (1 ... cap).reversed() { if wgt[i - 1] <= c { // 不選和選物品 i 這兩種方案的較大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) } } } return dp[cap] } @main enum Knapsack { /* Driver Code */ static func main() { let wgt = [10, 20, 30, 40, 50] let val = [50, 120, 150, 210, 240] let cap = 50 let n = wgt.count // 暴力搜尋 var res = knapsackDFS(wgt: wgt, val: val, i: n, c: cap) print("不超過背包容量的最大物品價值為 \(res)") // 記憶化搜尋 var mem = Array(repeating: Array(repeating: -1, count: cap + 1), count: n + 1) res = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: n, c: cap) print("不超過背包容量的最大物品價值為 \(res)") // 動態規劃 res = knapsackDP(wgt: wgt, val: val, cap: cap) print("不超過背包容量的最大物品價值為 \(res)") // 空間最佳化後的動態規劃 res = knapsackDPComp(wgt: wgt, val: val, cap: cap) print("不超過背包容量的最大物品價值為 \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift ================================================ /** * File: min_cost_climbing_stairs_dp.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 爬樓梯最小代價:動態規劃 */ func minCostClimbingStairsDP(cost: [Int]) -> Int { let n = cost.count - 1 if n == 1 || n == 2 { return cost[n] } // 初始化 dp 表,用於儲存子問題的解 var dp = Array(repeating: 0, count: n + 1) // 初始狀態:預設最小子問題的解 dp[1] = cost[1] dp[2] = cost[2] // 狀態轉移:從較小子問題逐步求解較大子問題 for i in 3 ... n { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] } return dp[n] } /* 爬樓梯最小代價:空間最佳化後的動態規劃 */ func minCostClimbingStairsDPComp(cost: [Int]) -> Int { let n = cost.count - 1 if n == 1 || n == 2 { return cost[n] } var (a, b) = (cost[1], cost[2]) for i in 3 ... n { (a, b) = (b, min(a, b) + cost[i]) } return b } @main enum MinCostClimbingStairsDP { /* Driver Code */ static func main() { let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] print("輸入樓梯的代價串列為 \(cost)") var res = minCostClimbingStairsDP(cost: cost) print("爬完樓梯的最低代價為 \(res)") res = minCostClimbingStairsDPComp(cost: cost) print("爬完樓梯的最低代價為 \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_dynamic_programming/min_path_sum.swift ================================================ /** * File: min_path_sum.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 最小路徑和:暴力搜尋 */ func minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int { // 若為左上角單元格,則終止搜尋 if i == 0, j == 0 { return grid[0][0] } // 若行列索引越界,則返回 +∞ 代價 if i < 0 || j < 0 { return .max } // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 let up = minPathSumDFS(grid: grid, i: i - 1, j: j) let left = minPathSumDFS(grid: grid, i: i, j: j - 1) // 返回從左上角到 (i, j) 的最小路徑代價 return min(left, up) + grid[i][j] } /* 最小路徑和:記憶化搜尋 */ func minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int { // 若為左上角單元格,則終止搜尋 if i == 0, j == 0 { return grid[0][0] } // 若行列索引越界,則返回 +∞ 代價 if i < 0 || j < 0 { return .max } // 若已有記錄,則直接返回 if mem[i][j] != -1 { return mem[i][j] } // 左邊和上邊單元格的最小路徑代價 let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j) let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1) // 記錄並返回左上角到 (i, j) 的最小路徑代價 mem[i][j] = min(left, up) + grid[i][j] return mem[i][j] } /* 最小路徑和:動態規劃 */ func minPathSumDP(grid: [[Int]]) -> Int { let n = grid.count let m = grid[0].count // 初始化 dp 表 var dp = Array(repeating: Array(repeating: 0, count: m), count: n) dp[0][0] = grid[0][0] // 狀態轉移:首行 for j in 1 ..< m { dp[0][j] = dp[0][j - 1] + grid[0][j] } // 狀態轉移:首列 for i in 1 ..< n { dp[i][0] = dp[i - 1][0] + grid[i][0] } // 狀態轉移:其餘行和列 for i in 1 ..< n { for j in 1 ..< m { dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] } } return dp[n - 1][m - 1] } /* 最小路徑和:空間最佳化後的動態規劃 */ func minPathSumDPComp(grid: [[Int]]) -> Int { let n = grid.count let m = grid[0].count // 初始化 dp 表 var dp = Array(repeating: 0, count: m) // 狀態轉移:首行 dp[0] = grid[0][0] for j in 1 ..< m { dp[j] = dp[j - 1] + grid[0][j] } // 狀態轉移:其餘行 for i in 1 ..< n { // 狀態轉移:首列 dp[0] = dp[0] + grid[i][0] // 狀態轉移:其餘列 for j in 1 ..< m { dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] } } return dp[m - 1] } @main enum MinPathSum { /* Driver Code */ static func main() { let grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ] let n = grid.count let m = grid[0].count // 暴力搜尋 var res = minPathSumDFS(grid: grid, i: n - 1, j: m - 1) print("從左上角到右下角的最小路徑和為 \(res)") // 記憶化搜尋 var mem = Array(repeating: Array(repeating: -1, count: m), count: n) res = minPathSumDFSMem(grid: grid, mem: &mem, i: n - 1, j: m - 1) print("從左上角到右下角的最小路徑和為 \(res)") // 動態規劃 res = minPathSumDP(grid: grid) print("從左上角到右下角的最小路徑和為 \(res)") // 空間最佳化後的動態規劃 res = minPathSumDPComp(grid: grid) print("從左上角到右下角的最小路徑和為 \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift ================================================ /** * File: unbounded_knapsack.swift * Created Time: 2023-07-15 * Author: nuomi1 (nuomi1@qq.com) */ /* 完全背包:動態規劃 */ func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // 初始化 dp 表 var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) // 狀態轉移 for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c] } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) } } } return dp[n][cap] } /* 完全背包:空間最佳化後的動態規劃 */ func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // 初始化 dp 表 var dp = Array(repeating: 0, count: cap + 1) // 狀態轉移 for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // 若超過背包容量,則不選物品 i dp[c] = dp[c] } else { // 不選和選物品 i 這兩種方案的較大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) } } } return dp[cap] } @main enum UnboundedKnapsack { /* Driver Code */ static func main() { let wgt = [1, 2, 3] let val = [5, 11, 15] let cap = 4 // 動態規劃 var res = unboundedKnapsackDP(wgt: wgt, val: val, cap: cap) print("不超過背包容量的最大物品價值為 \(res)") // 空間最佳化後的動態規劃 res = unboundedKnapsackDPComp(wgt: wgt, val: val, cap: cap) print("不超過背包容量的最大物品價值為 \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_graph/graph_adjacency_list.swift ================================================ /** * File: graph_adjacency_list.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 基於鄰接表實現的無向圖類別 */ public class GraphAdjList { // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 public private(set) var adjList: [Vertex: [Vertex]] /* 建構子 */ public init(edges: [[Vertex]]) { adjList = [:] // 新增所有頂點和邊 for edge in edges { addVertex(vet: edge[0]) addVertex(vet: edge[1]) addEdge(vet1: edge[0], vet2: edge[1]) } } /* 獲取頂點數量 */ public func size() -> Int { adjList.count } /* 新增邊 */ public func addEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("參數錯誤") } // 新增邊 vet1 - vet2 adjList[vet1]?.append(vet2) adjList[vet2]?.append(vet1) } /* 刪除邊 */ public func removeEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("參數錯誤") } // 刪除邊 vet1 - vet2 adjList[vet1]?.removeAll { $0 == vet2 } adjList[vet2]?.removeAll { $0 == vet1 } } /* 新增頂點 */ public func addVertex(vet: Vertex) { if adjList[vet] != nil { return } // 在鄰接表中新增一個新鏈結串列 adjList[vet] = [] } /* 刪除頂點 */ public func removeVertex(vet: Vertex) { if adjList[vet] == nil { fatalError("參數錯誤") } // 在鄰接表中刪除頂點 vet 對應的鏈結串列 adjList.removeValue(forKey: vet) // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 for key in adjList.keys { adjList[key]?.removeAll { $0 == vet } } } /* 列印鄰接表 */ public func print() { Swift.print("鄰接表 =") for (vertex, list) in adjList { let list = list.map { $0.val } Swift.print("\(vertex.val): \(list),") } } } #if !TARGET @main enum GraphAdjacencyList { /* Driver Code */ static func main() { /* 初始化無向圖 */ let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] let graph = GraphAdjList(edges: edges) print("\n初始化後,圖為") graph.print() /* 新增邊 */ // 頂點 1, 2 即 v[0], v[2] graph.addEdge(vet1: v[0], vet2: v[2]) print("\n新增邊 1-2 後,圖為") graph.print() /* 刪除邊 */ // 頂點 1, 3 即 v[0], v[1] graph.removeEdge(vet1: v[0], vet2: v[1]) print("\n刪除邊 1-3 後,圖為") graph.print() /* 新增頂點 */ let v5 = Vertex(val: 6) graph.addVertex(vet: v5) print("\n新增頂點 6 後,圖為") graph.print() /* 刪除頂點 */ // 頂點 3 即 v[1] graph.removeVertex(vet: v[1]) print("\n刪除頂點 3 後,圖為") graph.print() } } #endif ================================================ FILE: zh-hant/codes/swift/chapter_graph/graph_adjacency_list_target.swift ================================================ /** * File: graph_adjacency_list.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 基於鄰接表實現的無向圖類別 */ public class GraphAdjList { // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 public private(set) var adjList: [Vertex: [Vertex]] /* 建構子 */ public init(edges: [[Vertex]]) { adjList = [:] // 新增所有頂點和邊 for edge in edges { addVertex(vet: edge[0]) addVertex(vet: edge[1]) addEdge(vet1: edge[0], vet2: edge[1]) } } /* 獲取頂點數量 */ public func size() -> Int { adjList.count } /* 新增邊 */ public func addEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("參數錯誤") } // 新增邊 vet1 - vet2 adjList[vet1]?.append(vet2) adjList[vet2]?.append(vet1) } /* 刪除邊 */ public func removeEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("參數錯誤") } // 刪除邊 vet1 - vet2 adjList[vet1]?.removeAll { $0 == vet2 } adjList[vet2]?.removeAll { $0 == vet1 } } /* 新增頂點 */ public func addVertex(vet: Vertex) { if adjList[vet] != nil { return } // 在鄰接表中新增一個新鏈結串列 adjList[vet] = [] } /* 刪除頂點 */ public func removeVertex(vet: Vertex) { if adjList[vet] == nil { fatalError("參數錯誤") } // 在鄰接表中刪除頂點 vet 對應的鏈結串列 adjList.removeValue(forKey: vet) // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 for key in adjList.keys { adjList[key]?.removeAll { $0 == vet } } } /* 列印鄰接表 */ public func print() { Swift.print("鄰接表 =") for (vertex, list) in adjList { let list = list.map { $0.val } Swift.print("\(vertex.val): \(list),") } } } #if !TARGET @main enum GraphAdjacencyList { /* Driver Code */ static func main() { /* 初始化無向圖 */ let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] let graph = GraphAdjList(edges: edges) print("\n初始化後,圖為") graph.print() /* 新增邊 */ // 頂點 1, 2 即 v[0], v[2] graph.addEdge(vet1: v[0], vet2: v[2]) print("\n新增邊 1-2 後,圖為") graph.print() /* 刪除邊 */ // 頂點 1, 3 即 v[0], v[1] graph.removeEdge(vet1: v[0], vet2: v[1]) print("\n刪除邊 1-3 後,圖為") graph.print() /* 新增頂點 */ let v5 = Vertex(val: 6) graph.addVertex(vet: v5) print("\n新增頂點 6 後,圖為") graph.print() /* 刪除頂點 */ // 頂點 3 即 v[1] graph.removeVertex(vet: v[1]) print("\n刪除頂點 3 後,圖為") graph.print() } } #endif ================================================ FILE: zh-hant/codes/swift/chapter_graph/graph_adjacency_matrix.swift ================================================ /** * File: graph_adjacency_matrix.swift * Created Time: 2023-02-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 基於鄰接矩陣實現的無向圖類別 */ class GraphAdjMat { private var vertices: [Int] // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” private var adjMat: [[Int]] // 鄰接矩陣,行列索引對應“頂點索引” /* 建構子 */ init(vertices: [Int], edges: [[Int]]) { self.vertices = [] adjMat = [] // 新增頂點 for val in vertices { addVertex(val: val) } // 新增邊 // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 for e in edges { addEdge(i: e[0], j: e[1]) } } /* 獲取頂點數量 */ func size() -> Int { vertices.count } /* 新增頂點 */ func addVertex(val: Int) { let n = size() // 向頂點串列中新增新頂點的值 vertices.append(val) // 在鄰接矩陣中新增一行 let newRow = Array(repeating: 0, count: n) adjMat.append(newRow) // 在鄰接矩陣中新增一列 for i in adjMat.indices { adjMat[i].append(0) } } /* 刪除頂點 */ func removeVertex(index: Int) { if index >= size() { fatalError("越界") } // 在頂點串列中移除索引 index 的頂點 vertices.remove(at: index) // 在鄰接矩陣中刪除索引 index 的行 adjMat.remove(at: index) // 在鄰接矩陣中刪除索引 index 的列 for i in adjMat.indices { adjMat[i].remove(at: index) } } /* 新增邊 */ // 參數 i, j 對應 vertices 元素索引 func addEdge(i: Int, j: Int) { // 索引越界與相等處理 if i < 0 || j < 0 || i >= size() || j >= size() || i == j { fatalError("越界") } // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) adjMat[i][j] = 1 adjMat[j][i] = 1 } /* 刪除邊 */ // 參數 i, j 對應 vertices 元素索引 func removeEdge(i: Int, j: Int) { // 索引越界與相等處理 if i < 0 || j < 0 || i >= size() || j >= size() || i == j { fatalError("越界") } adjMat[i][j] = 0 adjMat[j][i] = 0 } /* 列印鄰接矩陣 */ func print() { Swift.print("頂點串列 = ", terminator: "") Swift.print(vertices) Swift.print("鄰接矩陣 =") PrintUtil.printMatrix(matrix: adjMat) } } @main enum GraphAdjacencyMatrix { /* Driver Code */ static func main() { /* 初始化無向圖 */ // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 let vertices = [1, 3, 2, 5, 4] let edges = [[0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4]] let graph = GraphAdjMat(vertices: vertices, edges: edges) print("\n初始化後,圖為") graph.print() /* 新增邊 */ // 頂點 1, 2 的索引分別為 0, 2 graph.addEdge(i: 0, j: 2) print("\n新增邊 1-2 後,圖為") graph.print() /* 刪除邊 */ // 頂點 1, 3 的索引分別為 0, 1 graph.removeEdge(i: 0, j: 1) print("\n刪除邊 1-3 後,圖為") graph.print() /* 新增頂點 */ graph.addVertex(val: 6) print("\n新增頂點 6 後,圖為") graph.print() /* 刪除頂點 */ // 頂點 3 的索引為 1 graph.removeVertex(index: 1) print("\n刪除頂點 3 後,圖為") graph.print() } } ================================================ FILE: zh-hant/codes/swift/chapter_graph/graph_bfs.swift ================================================ /** * File: graph_bfs.swift * Created Time: 2023-02-21 * Author: nuomi1 (nuomi1@qq.com) */ import graph_adjacency_list_target import utils /* 廣度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { // 頂點走訪序列 var res: [Vertex] = [] // 雜湊集合,用於記錄已被訪問過的頂點 var visited: Set = [startVet] // 佇列用於實現 BFS var que: [Vertex] = [startVet] // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 while !que.isEmpty { let vet = que.removeFirst() // 佇列首頂點出隊 res.append(vet) // 記錄訪問頂點 // 走訪該頂點的所有鄰接頂點 for adjVet in graph.adjList[vet] ?? [] { if visited.contains(adjVet) { continue // 跳過已被訪問的頂點 } que.append(adjVet) // 只入列未訪問的頂點 visited.insert(adjVet) // 標記該頂點已被訪問 } } // 返回頂點走訪序列 return res } @main enum GraphBFS { /* Driver Code */ static func main() { /* 初始化無向圖 */ let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) let edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ] let graph = GraphAdjList(edges: edges) print("\n初始化後,圖為") graph.print() /* 廣度優先走訪 */ let res = graphBFS(graph: graph, startVet: v[0]) print("\n廣度優先走訪(BFS)頂點序列為") print(Vertex.vetsToVals(vets: res)) } } ================================================ FILE: zh-hant/codes/swift/chapter_graph/graph_dfs.swift ================================================ /** * File: graph_dfs.swift * Created Time: 2023-02-21 * Author: nuomi1 (nuomi1@qq.com) */ import graph_adjacency_list_target import utils /* 深度優先走訪輔助函式 */ func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { res.append(vet) // 記錄訪問頂點 visited.insert(vet) // 標記該頂點已被訪問 // 走訪該頂點的所有鄰接頂點 for adjVet in graph.adjList[vet] ?? [] { if visited.contains(adjVet) { continue // 跳過已被訪問的頂點 } // 遞迴訪問鄰接頂點 dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) } } /* 深度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { // 頂點走訪序列 var res: [Vertex] = [] // 雜湊集合,用於記錄已被訪問過的頂點 var visited: Set = [] dfs(graph: graph, visited: &visited, res: &res, vet: startVet) return res } @main enum GraphDFS { /* Driver Code */ static func main() { /* 初始化無向圖 */ let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6]) let edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ] let graph = GraphAdjList(edges: edges) print("\n初始化後,圖為") graph.print() /* 深度優先走訪 */ let res = graphDFS(graph: graph, startVet: v[0]) print("\n深度優先走訪(DFS)頂點序列為") print(Vertex.vetsToVals(vets: res)) } } ================================================ FILE: zh-hant/codes/swift/chapter_greedy/coin_change_greedy.swift ================================================ /** * File: coin_change_greedy.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* 零錢兌換:貪婪 */ func coinChangeGreedy(coins: [Int], amt: Int) -> Int { // 假設 coins 串列有序 var i = coins.count - 1 var count = 0 var amt = amt // 迴圈進行貪婪選擇,直到無剩餘金額 while amt > 0 { // 找到小於且最接近剩餘金額的硬幣 while i > 0 && coins[i] > amt { i -= 1 } // 選擇 coins[i] amt -= coins[i] count += 1 } // 若未找到可行方案,則返回 -1 return amt == 0 ? count : -1 } @main enum CoinChangeGreedy { /* Driver Code */ static func main() { // 貪婪:能夠保證找到全域性最優解 var coins = [1, 5, 10, 20, 50, 100] var amt = 186 var res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("湊到 \(amt) 所需的最少硬幣數量為 \(res)") // 貪婪:無法保證找到全域性最優解 coins = [1, 20, 50] amt = 60 res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("湊到 \(amt) 所需的最少硬幣數量為 \(res)") print("實際上需要的最少數量為 3 ,即 20 + 20 + 20") // 貪婪:無法保證找到全域性最優解 coins = [1, 49, 50] amt = 98 res = coinChangeGreedy(coins: coins, amt: amt) print("\ncoins = \(coins), amount = \(amt)") print("湊到 \(amt) 所需的最少硬幣數量為 \(res)") print("實際上需要的最少數量為 2 ,即 49 + 49") } } ================================================ FILE: zh-hant/codes/swift/chapter_greedy/fractional_knapsack.swift ================================================ /** * File: fractional_knapsack.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* 物品 */ class Item { var w: Int // 物品重量 var v: Int // 物品價值 init(w: Int, v: Int) { self.w = w self.v = v } } /* 分數背包:貪婪 */ func fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double { // 建立物品串列,包含兩個屬性:重量、價值 var items = zip(wgt, val).map { Item(w: $0, v: $1) } // 按照單位價值 item.v / item.w 從高到低進行排序 items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) } // 迴圈貪婪選擇 var res = 0.0 var cap = cap for item in items { if item.w <= cap { // 若剩餘容量充足,則將當前物品整個裝進背包 res += Double(item.v) cap -= item.w } else { // 若剩餘容量不足,則將當前物品的一部分裝進背包 res += Double(item.v) / Double(item.w) * Double(cap) // 已無剩餘容量,因此跳出迴圈 break } } return res } @main enum FractionalKnapsack { /* Driver Code */ static func main() { // 物品重量 let wgt = [10, 20, 30, 40, 50] // 物品價值 let val = [50, 120, 150, 210, 240] // 背包容量 let cap = 50 // 貪婪演算法 let res = fractionalKnapsack(wgt: wgt, val: val, cap: cap) print("不超過背包容量的最大物品價值為 \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_greedy/max_capacity.swift ================================================ /** * File: max_capacity.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ /* 最大容量:貪婪 */ func maxCapacity(ht: [Int]) -> Int { // 初始化 i, j,使其分列陣列兩端 var i = ht.startIndex, j = ht.endIndex - 1 // 初始最大容量為 0 var res = 0 // 迴圈貪婪選擇,直至兩板相遇 while i < j { // 更新最大容量 let cap = min(ht[i], ht[j]) * (j - i) res = max(res, cap) // 向內移動短板 if ht[i] < ht[j] { i += 1 } else { j -= 1 } } return res } @main enum MaxCapacity { /* Driver Code */ static func main() { let ht = [3, 8, 5, 2, 7, 7, 3, 4] // 貪婪演算法 let res = maxCapacity(ht: ht) print("最大容量為 \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_greedy/max_product_cutting.swift ================================================ /** * File: max_product_cutting.swift * Created Time: 2023-09-03 * Author: nuomi1 (nuomi1@qq.com) */ import Foundation func pow(_ x: Int, _ y: Int) -> Int { Int(Double(truncating: pow(Decimal(x), y) as NSDecimalNumber)) } /* 最大切分乘積:貪婪 */ func maxProductCutting(n: Int) -> Int { // 當 n <= 3 時,必須切分出一個 1 if n <= 3 { return 1 * (n - 1) } // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 let a = n / 3 let b = n % 3 if b == 1 { // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 return pow(3, a - 1) * 2 * 2 } if b == 2 { // 當餘數為 2 時,不做處理 return pow(3, a) * 2 } // 當餘數為 0 時,不做處理 return pow(3, a) } @main enum MaxProductCutting { static func main() { let n = 58 // 貪婪演算法 let res = maxProductCutting(n: n) print("最大切分乘積為 \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_hashing/array_hash_map.swift ================================================ /** * File: array_hash_map.swift * Created Time: 2023-01-16 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 基於陣列實現的雜湊表 */ class ArrayHashMap { private var buckets: [Pair?] init() { // 初始化陣列,包含 100 個桶 buckets = Array(repeating: nil, count: 100) } /* 雜湊函式 */ private func hashFunc(key: Int) -> Int { let index = key % 100 return index } /* 查詢操作 */ func get(key: Int) -> String? { let index = hashFunc(key: key) let pair = buckets[index] return pair?.val } /* 新增操作 */ func put(key: Int, val: String) { let pair = Pair(key: key, val: val) let index = hashFunc(key: key) buckets[index] = pair } /* 刪除操作 */ func remove(key: Int) { let index = hashFunc(key: key) // 置為 nil ,代表刪除 buckets[index] = nil } /* 獲取所有鍵值對 */ func pairSet() -> [Pair] { buckets.compactMap { $0 } } /* 獲取所有鍵 */ func keySet() -> [Int] { buckets.compactMap { $0?.key } } /* 獲取所有值 */ func valueSet() -> [String] { buckets.compactMap { $0?.val } } /* 列印雜湊表 */ func print() { for pair in pairSet() { Swift.print("\(pair.key) -> \(pair.val)") } } } @main enum _ArrayHashMap { /* Driver Code */ static func main() { /* 初始化雜湊表 */ let map = ArrayHashMap() /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(key: 12836, val: "小哈") map.put(key: 15937, val: "小囉") map.put(key: 16750, val: "小算") map.put(key: 13276, val: "小法") map.put(key: 10583, val: "小鴨") print("\n新增完成後,雜湊表為\nKey -> Value") map.print() /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value let name = map.get(key: 15937)! print("\n輸入學號 15937 ,查詢到姓名 \(name)") /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(key: 10583) print("\n刪除 10583 後,雜湊表為\nKey -> Value") map.print() /* 走訪雜湊表 */ print("\n走訪鍵值對 Key->Value") for pair in map.pairSet() { print("\(pair.key) -> \(pair.val)") } print("\n單獨走訪鍵 Key") for key in map.keySet() { print(key) } print("\n單獨走訪值 Value") for val in map.valueSet() { print(val) } } } ================================================ FILE: zh-hant/codes/swift/chapter_hashing/built_in_hash.swift ================================================ /** * File: built_in_hash.swift * Created Time: 2023-07-01 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum BuiltInHash { /* Driver Code */ static func main() { let num = 3 let hashNum = num.hashValue print("整數 \(num) 的雜湊值為 \(hashNum)") let bol = true let hashBol = bol.hashValue print("布林量 \(bol) 的雜湊值為 \(hashBol)") let dec = 3.14159 let hashDec = dec.hashValue print("小數 \(dec) 的雜湊值為 \(hashDec)") let str = "Hello 演算法" let hashStr = str.hashValue print("字串 \(str) 的雜湊值為 \(hashStr)") let arr = [AnyHashable(12836), AnyHashable("小哈")] let hashTup = arr.hashValue print("陣列 \(arr) 的雜湊值為 \(hashTup)") let obj = ListNode(x: 0) let hashObj = obj.hashValue print("節點物件 \(obj) 的雜湊值為 \(hashObj)") } } ================================================ FILE: zh-hant/codes/swift/chapter_hashing/hash_map.swift ================================================ /** * File: hash_map.swift * Created Time: 2023-01-16 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum HashMap { /* Driver Code */ static func main() { /* 初始化雜湊表 */ var map: [Int: String] = [:] /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map[12836] = "小哈" map[15937] = "小囉" map[16750] = "小算" map[13276] = "小法" map[10583] = "小鴨" print("\n新增完成後,雜湊表為\nKey -> Value") PrintUtil.printHashMap(map: map) /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value let name = map[15937]! print("\n輸入學號 15937 ,查詢到姓名 \(name)") /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.removeValue(forKey: 10583) print("\n刪除 10583 後,雜湊表為\nKey -> Value") PrintUtil.printHashMap(map: map) /* 走訪雜湊表 */ print("\n走訪鍵值對 Key->Value") for (key, value) in map { print("\(key) -> \(value)") } print("\n單獨走訪鍵 Key") for key in map.keys { print(key) } print("\n單獨走訪值 Value") for value in map.values { print(value) } } } ================================================ FILE: zh-hant/codes/swift/chapter_hashing/hash_map_chaining.swift ================================================ /** * File: hash_map_chaining.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 鏈式位址雜湊表 */ class HashMapChaining { var size: Int // 鍵值對數量 var capacity: Int // 雜湊表容量 var loadThres: Double // 觸發擴容的負載因子閾值 var extendRatio: Int // 擴容倍數 var buckets: [[Pair]] // 桶陣列 /* 建構子 */ init() { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = Array(repeating: [], count: capacity) } /* 雜湊函式 */ func hashFunc(key: Int) -> Int { key % capacity } /* 負載因子 */ func loadFactor() -> Double { Double(size) / Double(capacity) } /* 查詢操作 */ func get(key: Int) -> String? { let index = hashFunc(key: key) let bucket = buckets[index] // 走訪桶,若找到 key ,則返回對應 val for pair in bucket { if pair.key == key { return pair.val } } // 若未找到 key ,則返回 nil return nil } /* 新增操作 */ func put(key: Int, val: String) { // 當負載因子超過閾值時,執行擴容 if loadFactor() > loadThres { extend() } let index = hashFunc(key: key) let bucket = buckets[index] // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for pair in bucket { if pair.key == key { pair.val = val return } } // 若無該 key ,則將鍵值對新增至尾部 let pair = Pair(key: key, val: val) buckets[index].append(pair) size += 1 } /* 刪除操作 */ func remove(key: Int) { let index = hashFunc(key: key) let bucket = buckets[index] // 走訪桶,從中刪除鍵值對 for (pairIndex, pair) in bucket.enumerated() { if pair.key == key { buckets[index].remove(at: pairIndex) size -= 1 break } } } /* 擴容雜湊表 */ func extend() { // 暫存原雜湊表 let bucketsTmp = buckets // 初始化擴容後的新雜湊表 capacity *= extendRatio buckets = Array(repeating: [], count: capacity) size = 0 // 將鍵值對從原雜湊表搬運至新雜湊表 for bucket in bucketsTmp { for pair in bucket { put(key: pair.key, val: pair.val) } } } /* 列印雜湊表 */ func print() { for bucket in buckets { let res = bucket.map { "\($0.key) -> \($0.val)" } Swift.print(res) } } } @main enum _HashMapChaining { /* Driver Code */ static func main() { /* 初始化雜湊表 */ let map = HashMapChaining() /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(key: 12836, val: "小哈") map.put(key: 15937, val: "小囉") map.put(key: 16750, val: "小算") map.put(key: 13276, val: "小法") map.put(key: 10583, val: "小鴨") print("\n新增完成後,雜湊表為\nKey -> Value") map.print() /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value let name = map.get(key: 13276) print("\n輸入學號 13276 ,查詢到姓名 \(name!)") /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(key: 12836) print("\n刪除 12836 後,雜湊表為\nKey -> Value") map.print() } } ================================================ FILE: zh-hant/codes/swift/chapter_hashing/hash_map_open_addressing.swift ================================================ /** * File: hash_map_open_addressing.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 開放定址雜湊表 */ class HashMapOpenAddressing { var size: Int // 鍵值對數量 var capacity: Int // 雜湊表容量 var loadThres: Double // 觸發擴容的負載因子閾值 var extendRatio: Int // 擴容倍數 var buckets: [Pair?] // 桶陣列 var TOMBSTONE: Pair // 刪除標記 /* 建構子 */ init() { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = Array(repeating: nil, count: capacity) TOMBSTONE = Pair(key: -1, val: "-1") } /* 雜湊函式 */ func hashFunc(key: Int) -> Int { key % capacity } /* 負載因子 */ func loadFactor() -> Double { Double(size) / Double(capacity) } /* 搜尋 key 對應的桶索引 */ func findBucket(key: Int) -> Int { var index = hashFunc(key: key) var firstTombstone = -1 // 線性探查,當遇到空桶時跳出 while buckets[index] != nil { // 若遇到 key ,返回對應的桶索引 if buckets[index]!.key == key { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if firstTombstone != -1 { buckets[firstTombstone] = buckets[index] buckets[index] = TOMBSTONE return firstTombstone // 返回移動後的桶索引 } return index // 返回桶索引 } // 記錄遇到的首個刪除標記 if firstTombstone == -1 && buckets[index] == TOMBSTONE { firstTombstone = index } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % capacity } // 若 key 不存在,則返回新增點的索引 return firstTombstone == -1 ? index : firstTombstone } /* 查詢操作 */ func get(key: Int) -> String? { // 搜尋 key 對應的桶索引 let index = findBucket(key: key) // 若找到鍵值對,則返回對應 val if buckets[index] != nil, buckets[index] != TOMBSTONE { return buckets[index]!.val } // 若鍵值對不存在,則返回 null return nil } /* 新增操作 */ func put(key: Int, val: String) { // 當負載因子超過閾值時,執行擴容 if loadFactor() > loadThres { extend() } // 搜尋 key 對應的桶索引 let index = findBucket(key: key) // 若找到鍵值對,則覆蓋 val 並返回 if buckets[index] != nil, buckets[index] != TOMBSTONE { buckets[index]!.val = val return } // 若鍵值對不存在,則新增該鍵值對 buckets[index] = Pair(key: key, val: val) size += 1 } /* 刪除操作 */ func remove(key: Int) { // 搜尋 key 對應的桶索引 let index = findBucket(key: key) // 若找到鍵值對,則用刪除標記覆蓋它 if buckets[index] != nil, buckets[index] != TOMBSTONE { buckets[index] = TOMBSTONE size -= 1 } } /* 擴容雜湊表 */ func extend() { // 暫存原雜湊表 let bucketsTmp = buckets // 初始化擴容後的新雜湊表 capacity *= extendRatio buckets = Array(repeating: nil, count: capacity) size = 0 // 將鍵值對從原雜湊表搬運至新雜湊表 for pair in bucketsTmp { if let pair, pair != TOMBSTONE { put(key: pair.key, val: pair.val) } } } /* 列印雜湊表 */ func print() { for pair in buckets { if pair == nil { Swift.print("null") } else if pair == TOMBSTONE { Swift.print("TOMBSTONE") } else { Swift.print("\(pair!.key) -> \(pair!.val)") } } } } @main enum _HashMapOpenAddressing { /* Driver Code */ static func main() { /* 初始化雜湊表 */ let map = HashMapOpenAddressing() /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(key: 12836, val: "小哈") map.put(key: 15937, val: "小囉") map.put(key: 16750, val: "小算") map.put(key: 13276, val: "小法") map.put(key: 10583, val: "小鴨") print("\n新增完成後,雜湊表為\nKey -> Value") map.print() /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value let name = map.get(key: 13276) print("\n輸入學號 13276 ,查詢到姓名 \(name!)") /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(key: 16750) print("\n刪除 16750 後,雜湊表為\nKey -> Value") map.print() } } ================================================ FILE: zh-hant/codes/swift/chapter_hashing/simple_hash.swift ================================================ /** * File: simple_hash.swift * Created Time: 2023-07-01 * Author: nuomi1 (nuomi1@qq.com) */ /* 加法雜湊 */ func addHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = (hash + Int(scalar.value)) % MODULUS } } return hash } /* 乘法雜湊 */ func mulHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = (31 * hash + Int(scalar.value)) % MODULUS } } return hash } /* 互斥或雜湊 */ func xorHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash ^= Int(scalar.value) } } return hash & MODULUS } /* 旋轉雜湊 */ func rotHash(key: String) -> Int { var hash = 0 let MODULUS = 1_000_000_007 for c in key { for scalar in c.unicodeScalars { hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS } } return hash } @main enum SimpleHash { /* Driver Code */ static func main() { let key = "Hello 演算法" var hash = addHash(key: key) print("加法雜湊值為 \(hash)") hash = mulHash(key: key) print("乘法雜湊值為 \(hash)") hash = xorHash(key: key) print("互斥或雜湊值為 \(hash)") hash = rotHash(key: key) print("旋轉雜湊值為 \(hash)") } } ================================================ FILE: zh-hant/codes/swift/chapter_heap/heap.swift ================================================ /** * File: heap.swift * Created Time: 2024-03-17 * Author: nuomi1 (nuomi1@qq.com) */ import HeapModule import utils func testPush(heap: inout Heap, val: Int) { heap.insert(val) print("\n元素 \(val) 入堆積後\n") PrintUtil.printHeap(queue: heap.unordered) } func testPop(heap: inout Heap) { let val = heap.removeMax() print("\n堆積頂元素 \(val) 出堆積後\n") PrintUtil.printHeap(queue: heap.unordered) } @main enum _Heap { /* Driver Code */ static func main() { /* 初始化堆積 */ // Swift 的 Heap 型別同時支持最大堆積和最小堆積 var heap = Heap() /* 元素入堆積 */ testPush(heap: &heap, val: 1) testPush(heap: &heap, val: 3) testPush(heap: &heap, val: 2) testPush(heap: &heap, val: 5) testPush(heap: &heap, val: 4) /* 獲取堆積頂元素 */ let peek = heap.max() print("\n堆積頂元素為 \(peek!)\n") /* 堆積頂元素出堆積 */ testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) testPop(heap: &heap) /* 獲取堆積大小 */ let size = heap.count print("\n堆積元素數量為 \(size)\n") /* 判斷堆積是否為空 */ let isEmpty = heap.isEmpty print("\n堆積是否為空 \(isEmpty)\n") /* 輸入串列並建堆積 */ // 時間複雜度為 O(n) ,而非 O(nlogn) let heap2 = Heap([1, 3, 2, 5, 4]) print("\n輸入串列並建立堆積後") PrintUtil.printHeap(queue: heap2.unordered) } } ================================================ FILE: zh-hant/codes/swift/chapter_heap/my_heap.swift ================================================ /** * File: my_heap.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 大頂堆積 */ class MaxHeap { private var maxHeap: [Int] /* 建構子,根據輸入串列建堆積 */ init(nums: [Int]) { // 將串列元素原封不動新增進堆積 maxHeap = nums // 堆積化除葉節點以外的其他所有節點 for i in (0 ... parent(i: size() - 1)).reversed() { siftDown(i: i) } } /* 獲取左子節點的索引 */ private func left(i: Int) -> Int { 2 * i + 1 } /* 獲取右子節點的索引 */ private func right(i: Int) -> Int { 2 * i + 2 } /* 獲取父節點的索引 */ private func parent(i: Int) -> Int { (i - 1) / 2 // 向下整除 } /* 交換元素 */ private func swap(i: Int, j: Int) { maxHeap.swapAt(i, j) } /* 獲取堆積大小 */ func size() -> Int { maxHeap.count } /* 判斷堆積是否為空 */ func isEmpty() -> Bool { size() == 0 } /* 訪問堆積頂元素 */ func peek() -> Int { maxHeap[0] } /* 元素入堆積 */ func push(val: Int) { // 新增節點 maxHeap.append(val) // 從底至頂堆積化 siftUp(i: size() - 1) } /* 從節點 i 開始,從底至頂堆積化 */ private func siftUp(i: Int) { var i = i while true { // 獲取節點 i 的父節點 let p = parent(i: i) // 當“越過根節點”或“節點無須修復”時,結束堆積化 if p < 0 || maxHeap[i] <= maxHeap[p] { break } // 交換兩節點 swap(i: i, j: p) // 迴圈向上堆積化 i = p } } /* 元素出堆積 */ func pop() -> Int { // 判空處理 if isEmpty() { fatalError("堆積為空") } // 交換根節點與最右葉節點(交換首元素與尾元素) swap(i: 0, j: size() - 1) // 刪除節點 let val = maxHeap.remove(at: size() - 1) // 從頂至底堆積化 siftDown(i: 0) // 返回堆積頂元素 return val } /* 從節點 i 開始,從頂至底堆積化 */ private func siftDown(i: Int) { var i = i while true { // 判斷節點 i, l, r 中值最大的節點,記為 ma let l = left(i: i) let r = right(i: i) var ma = i if l < size(), maxHeap[l] > maxHeap[ma] { ma = l } if r < size(), maxHeap[r] > maxHeap[ma] { ma = r } // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if ma == i { break } // 交換兩節點 swap(i: i, j: ma) // 迴圈向下堆積化 i = ma } } /* 列印堆積(二元樹) */ func print() { let queue = maxHeap PrintUtil.printHeap(queue: queue) } } @main enum MyHeap { /* Driver Code */ static func main() { /* 初始化大頂堆積 */ let maxHeap = MaxHeap(nums: [9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) print("\n輸入串列並建堆積後") maxHeap.print() /* 獲取堆積頂元素 */ var peek = maxHeap.peek() print("\n堆積頂元素為 \(peek)") /* 元素入堆積 */ let val = 7 maxHeap.push(val: val) print("\n元素 \(val) 入堆積後") maxHeap.print() /* 堆積頂元素出堆積 */ peek = maxHeap.pop() print("\n堆積頂元素 \(peek) 出堆積後") maxHeap.print() /* 獲取堆積大小 */ let size = maxHeap.size() print("\n堆積元素數量為 \(size)") /* 判斷堆積是否為空 */ let isEmpty = maxHeap.isEmpty() print("\n堆積是否為空 \(isEmpty)") } } ================================================ FILE: zh-hant/codes/swift/chapter_heap/top_k.swift ================================================ /** * File: top_k.swift * Created Time: 2023-07-02 * Author: nuomi1 (nuomi1@qq.com) */ import HeapModule import utils /* 基於堆積查詢陣列中最大的 k 個元素 */ func topKHeap(nums: [Int], k: Int) -> [Int] { // 初始化一個小頂堆積,並將前 k 個元素建堆積 var heap = Heap(nums.prefix(k)) // 從第 k+1 個元素開始,保持堆積的長度為 k for i in nums.indices.dropFirst(k) { // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 if nums[i] > heap.min()! { _ = heap.removeMin() heap.insert(nums[i]) } } return heap.unordered } @main enum TopK { /* Driver Code */ static func main() { let nums = [1, 7, 6, 3, 2] let k = 3 let res = topKHeap(nums: nums, k: k) print("最大的 \(k) 個元素為") PrintUtil.printHeap(queue: res) } } ================================================ FILE: zh-hant/codes/swift/chapter_searching/binary_search.swift ================================================ /** * File: binary_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ /* 二分搜尋(雙閉區間) */ func binarySearch(nums: [Int], target: Int) -> Int { // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 var i = nums.startIndex var j = nums.endIndex - 1 // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) while i <= j { let m = i + (j - i) / 2 // 計算中點索引 m if nums[m] < target { // 此情況說明 target 在區間 [m+1, j] 中 i = m + 1 } else if nums[m] > target { // 此情況說明 target 在區間 [i, m-1] 中 j = m - 1 } else { // 找到目標元素,返回其索引 return m } } // 未找到目標元素,返回 -1 return -1 } /* 二分搜尋(左閉右開區間) */ func binarySearchLCRO(nums: [Int], target: Int) -> Int { // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 var i = nums.startIndex var j = nums.endIndex // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) while i < j { let m = i + (j - i) / 2 // 計算中點索引 m if nums[m] < target { // 此情況說明 target 在區間 [m+1, j) 中 i = m + 1 } else if nums[m] > target { // 此情況說明 target 在區間 [i, m) 中 j = m } else { // 找到目標元素,返回其索引 return m } } // 未找到目標元素,返回 -1 return -1 } @main enum BinarySearch { /* Driver Code */ static func main() { let target = 6 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] /* 二分搜尋(雙閉區間) */ var index = binarySearch(nums: nums, target: target) print("目標元素 6 的索引 = \(index)") /* 二分搜尋(左閉右開區間) */ index = binarySearchLCRO(nums: nums, target: target) print("目標元素 6 的索引 = \(index)") } } ================================================ FILE: zh-hant/codes/swift/chapter_searching/binary_search_edge.swift ================================================ /** * File: binary_search_edge.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ import binary_search_insertion_target /* 二分搜尋最左一個 target */ func binarySearchLeftEdge(nums: [Int], target: Int) -> Int { // 等價於查詢 target 的插入點 let i = binarySearchInsertion(nums: nums, target: target) // 未找到 target ,返回 -1 if i == nums.endIndex || nums[i] != target { return -1 } // 找到 target ,返回索引 i return i } /* 二分搜尋最右一個 target */ func binarySearchRightEdge(nums: [Int], target: Int) -> Int { // 轉化為查詢最左一個 target + 1 let i = binarySearchInsertion(nums: nums, target: target + 1) // j 指向最右一個 target ,i 指向首個大於 target 的元素 let j = i - 1 // 未找到 target ,返回 -1 if j == -1 || nums[j] != target { return -1 } // 找到 target ,返回索引 j return j } @main enum BinarySearchEdge { /* Driver Code */ static func main() { // 包含重複元素的陣列 let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\n陣列 nums = \(nums)") // 二分搜尋左邊界和右邊界 for target in [6, 7] { var index = binarySearchLeftEdge(nums: nums, target: target) print("最左一個元素 \(target) 的索引為 \(index)") index = binarySearchRightEdge(nums: nums, target: target) print("最右一個元素 \(target) 的索引為 \(index)") } } } ================================================ FILE: zh-hant/codes/swift/chapter_searching/binary_search_insertion.swift ================================================ /** * File: binary_search_insertion.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ /* 二分搜尋插入點(無重複元素) */ func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { // 初始化雙閉區間 [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // 計算中點索引 m if nums[m] < target { i = m + 1 // target 在區間 [m+1, j] 中 } else if nums[m] > target { j = m - 1 // target 在區間 [i, m-1] 中 } else { return m // 找到 target ,返回插入點 m } } // 未找到 target ,返回插入點 i return i } /* 二分搜尋插入點(存在重複元素) */ public func binarySearchInsertion(nums: [Int], target: Int) -> Int { // 初始化雙閉區間 [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // 計算中點索引 m if nums[m] < target { i = m + 1 // target 在區間 [m+1, j] 中 } else if nums[m] > target { j = m - 1 // target 在區間 [i, m-1] 中 } else { j = m - 1 // 首個小於 target 的元素在區間 [i, m-1] 中 } } // 返回插入點 i return i } #if !TARGET @main enum BinarySearchInsertion { /* Driver Code */ static func main() { // 無重複元素的陣列 var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print("\n陣列 nums = \(nums)") // 二分搜尋插入點 for target in [6, 9] { let index = binarySearchInsertionSimple(nums: nums, target: target) print("元素 \(target) 的插入點的索引為 \(index)") } // 包含重複元素的陣列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\n陣列 nums = \(nums)") // 二分搜尋插入點 for target in [2, 6, 20] { let index = binarySearchInsertion(nums: nums, target: target) print("元素 \(target) 的插入點的索引為 \(index)") } } } #endif ================================================ FILE: zh-hant/codes/swift/chapter_searching/binary_search_insertion_target.swift ================================================ /** * File: binary_search_insertion.swift * Created Time: 2023-08-06 * Author: nuomi1 (nuomi1@qq.com) */ /* 二分搜尋插入點(無重複元素) */ func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { // 初始化雙閉區間 [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // 計算中點索引 m if nums[m] < target { i = m + 1 // target 在區間 [m+1, j] 中 } else if nums[m] > target { j = m - 1 // target 在區間 [i, m-1] 中 } else { return m // 找到 target ,返回插入點 m } } // 未找到 target ,返回插入點 i return i } /* 二分搜尋插入點(存在重複元素) */ public func binarySearchInsertion(nums: [Int], target: Int) -> Int { // 初始化雙閉區間 [0, n-1] var i = nums.startIndex var j = nums.endIndex - 1 while i <= j { let m = i + (j - i) / 2 // 計算中點索引 m if nums[m] < target { i = m + 1 // target 在區間 [m+1, j] 中 } else if nums[m] > target { j = m - 1 // target 在區間 [i, m-1] 中 } else { j = m - 1 // 首個小於 target 的元素在區間 [i, m-1] 中 } } // 返回插入點 i return i } #if !TARGET @main enum BinarySearchInsertion { /* Driver Code */ static func main() { // 無重複元素的陣列 var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print("\n陣列 nums = \(nums)") // 二分搜尋插入點 for target in [6, 9] { let index = binarySearchInsertionSimple(nums: nums, target: target) print("元素 \(target) 的插入點的索引為 \(index)") } // 包含重複元素的陣列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print("\n陣列 nums = \(nums)") // 二分搜尋插入點 for target in [2, 6, 20] { let index = binarySearchInsertion(nums: nums, target: target) print("元素 \(target) 的插入點的索引為 \(index)") } } } #endif ================================================ FILE: zh-hant/codes/swift/chapter_searching/hashing_search.swift ================================================ /** * File: hashing_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 雜湊查詢(陣列) */ func hashingSearchArray(map: [Int: Int], target: Int) -> Int { // 雜湊表的 key: 目標元素,value: 索引 // 若雜湊表中無此 key ,返回 -1 return map[target, default: -1] } /* 雜湊查詢(鏈結串列) */ func hashingSearchLinkedList(map: [Int: ListNode], target: Int) -> ListNode? { // 雜湊表的 key: 目標節點值,value: 節點物件 // 若雜湊表中無此 key ,返回 null return map[target] } @main enum HashingSearch { /* Driver Code */ static func main() { let target = 3 /* 雜湊查詢(陣列) */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] // 初始化雜湊表 var map: [Int: Int] = [:] for i in nums.indices { map[nums[i]] = i // key: 元素,value: 索引 } let index = hashingSearchArray(map: map, target: target) print("目標元素 3 的索引 = \(index)") /* 雜湊查詢(鏈結串列) */ var head = ListNode.arrToLinkedList(arr: nums) // 初始化雜湊表 var map1: [Int: ListNode] = [:] while head != nil { map1[head!.val] = head! // key: 節點值,value: 節點 head = head?.next } let node = hashingSearchLinkedList(map: map1, target: target) print("目標節點值 3 的對應節點物件為 \(node!)") } } ================================================ FILE: zh-hant/codes/swift/chapter_searching/linear_search.swift ================================================ /** * File: linear_search.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 線性查詢(陣列) */ func linearSearchArray(nums: [Int], target: Int) -> Int { // 走訪陣列 for i in nums.indices { // 找到目標元素,返回其索引 if nums[i] == target { return i } } // 未找到目標元素,返回 -1 return -1 } /* 線性查詢(鏈結串列) */ func linearSearchLinkedList(head: ListNode?, target: Int) -> ListNode? { var head = head // 走訪鏈結串列 while head != nil { // 找到目標節點,返回之 if head?.val == target { return head } head = head?.next } // 未找到目標節點,返回 null return nil } @main enum LinearSearch { /* Driver Code */ static func main() { let target = 3 /* 在陣列中執行線性查詢 */ let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] let index = linearSearchArray(nums: nums, target: target) print("目標元素 3 的索引 = \(index)") /* 在鏈結串列中執行線性查詢 */ let head = ListNode.arrToLinkedList(arr: nums) let node = linearSearchLinkedList(head: head, target: target) print("目標節點值 3 的對應節點物件為 \(node!)") } } ================================================ FILE: zh-hant/codes/swift/chapter_searching/two_sum.swift ================================================ /** * File: two_sum.swift * Created Time: 2023-01-03 * Author: nuomi1 (nuomi1@qq.com) */ /* 方法一:暴力列舉 */ func twoSumBruteForce(nums: [Int], target: Int) -> [Int] { // 兩層迴圈,時間複雜度為 O(n^2) for i in nums.indices.dropLast() { for j in nums.indices.dropFirst(i + 1) { if nums[i] + nums[j] == target { return [i, j] } } } return [0] } /* 方法二:輔助雜湊表 */ func twoSumHashTable(nums: [Int], target: Int) -> [Int] { // 輔助雜湊表,空間複雜度為 O(n) var dic: [Int: Int] = [:] // 單層迴圈,時間複雜度為 O(n) for i in nums.indices { if let j = dic[target - nums[i]] { return [j, i] } dic[nums[i]] = i } return [0] } @main enum LeetcodeTwoSum { /* Driver Code */ static func main() { // ======= Test Case ======= let nums = [2, 7, 11, 15] let target = 13 // ====== Driver Code ====== // 方法一 var res = twoSumBruteForce(nums: nums, target: target) print("方法一 res = \(res)") // 方法二 res = twoSumHashTable(nums: nums, target: target) print("方法二 res = \(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_sorting/bubble_sort.swift ================================================ /** * File: bubble_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* 泡沫排序 */ func bubbleSort(nums: inout [Int]) { // 外迴圈:未排序區間為 [0, i] for i in nums.indices.dropFirst().reversed() { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for j in 0 ..< i { if nums[j] > nums[j + 1] { // 交換 nums[j] 與 nums[j + 1] nums.swapAt(j, j + 1) } } } } /* 泡沫排序(標誌最佳化)*/ func bubbleSortWithFlag(nums: inout [Int]) { // 外迴圈:未排序區間為 [0, i] for i in nums.indices.dropFirst().reversed() { var flag = false // 初始化標誌位 for j in 0 ..< i { if nums[j] > nums[j + 1] { // 交換 nums[j] 與 nums[j + 1] nums.swapAt(j, j + 1) flag = true // 記錄交換元素 } } if !flag { // 此輪“冒泡”未交換任何元素,直接跳出 break } } } @main enum BubbleSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] bubbleSort(nums: &nums) print("泡沫排序完成後 nums = \(nums)") var nums1 = [4, 1, 3, 1, 5, 2] bubbleSortWithFlag(nums: &nums1) print("泡沫排序完成後 nums1 = \(nums1)") } } ================================================ FILE: zh-hant/codes/swift/chapter_sorting/bucket_sort.swift ================================================ /** * File: bucket_sort.swift * Created Time: 2023-03-27 * Author: nuomi1 (nuomi1@qq.com) */ /* 桶排序 */ func bucketSort(nums: inout [Double]) { // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 let k = nums.count / 2 var buckets = (0 ..< k).map { _ in [Double]() } // 1. 將陣列元素分配到各個桶中 for num in nums { // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] let i = Int(num * Double(k)) // 將 num 新增進桶 i buckets[i].append(num) } // 2. 對各個桶執行排序 for i in buckets.indices { // 使用內建排序函式,也可以替換成其他排序演算法 buckets[i].sort() } // 3. 走訪桶合併結果 var i = nums.startIndex for bucket in buckets { for num in bucket { nums[i] = num i += 1 } } } @main enum BucketSort { /* Driver Code */ static func main() { // 設輸入資料為浮點數,範圍為 [0, 1) var nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] bucketSort(nums: &nums) print("桶排序完成後 nums = \(nums)") } } ================================================ FILE: zh-hant/codes/swift/chapter_sorting/counting_sort.swift ================================================ /** * File: counting_sort.swift * Created Time: 2023-03-22 * Author: nuomi1 (nuomi1@qq.com) */ /* 計數排序 */ // 簡單實現,無法用於排序物件 func countingSortNaive(nums: inout [Int]) { // 1. 統計陣列最大元素 m let m = nums.max()! // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 var counter = Array(repeating: 0, count: m + 1) for num in nums { counter[num] += 1 } // 3. 走訪 counter ,將各元素填入原陣列 nums var i = 0 for num in 0 ..< m + 1 { for _ in 0 ..< counter[num] { nums[i] = num i += 1 } } } /* 計數排序 */ // 完整實現,可排序物件,並且是穩定排序 func countingSort(nums: inout [Int]) { // 1. 統計陣列最大元素 m let m = nums.max()! // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 var counter = Array(repeating: 0, count: m + 1) for num in nums { counter[num] += 1 } // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 for i in 0 ..< m { counter[i + 1] += counter[i] } // 4. 倒序走訪 nums ,將各元素填入結果陣列 res // 初始化陣列 res 用於記錄結果 var res = Array(repeating: 0, count: nums.count) for i in nums.indices.reversed() { let num = nums[i] res[counter[num] - 1] = num // 將 num 放置到對應索引處 counter[num] -= 1 // 令前綴和自減 1 ,得到下次放置 num 的索引 } // 使用結果陣列 res 覆蓋原陣列 nums for i in nums.indices { nums[i] = res[i] } } @main enum CountingSort { /* Driver Code */ static func main() { var nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] countingSortNaive(nums: &nums) print("計數排序(無法排序物件)完成後 nums = \(nums)") var nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] countingSort(nums: &nums1) print("計數排序完成後 nums1 = \(nums1)") } } ================================================ FILE: zh-hant/codes/swift/chapter_sorting/heap_sort.swift ================================================ /** * File: heap_sort.swift * Created Time: 2023-05-28 * Author: nuomi1 (nuomi1@qq.com) */ /* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ func siftDown(nums: inout [Int], n: Int, i: Int) { var i = i while true { // 判斷節點 i, l, r 中值最大的節點,記為 ma let l = 2 * i + 1 let r = 2 * i + 2 var ma = i if l < n, nums[l] > nums[ma] { ma = l } if r < n, nums[r] > nums[ma] { ma = r } // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if ma == i { break } // 交換兩節點 nums.swapAt(i, ma) // 迴圈向下堆積化 i = ma } } /* 堆積排序 */ func heapSort(nums: inout [Int]) { // 建堆積操作:堆積化除葉節點以外的其他所有節點 for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) { siftDown(nums: &nums, n: nums.count, i: i) } // 從堆積中提取最大元素,迴圈 n-1 輪 for i in nums.indices.dropFirst().reversed() { // 交換根節點與最右葉節點(交換首元素與尾元素) nums.swapAt(0, i) // 以根節點為起點,從頂至底進行堆積化 siftDown(nums: &nums, n: i, i: 0) } } @main enum HeapSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] heapSort(nums: &nums) print("堆積排序完成後 nums = \(nums)") } } ================================================ FILE: zh-hant/codes/swift/chapter_sorting/insertion_sort.swift ================================================ /** * File: insertion_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* 插入排序 */ func insertionSort(nums: inout [Int]) { // 外迴圈:已排序區間為 [0, i-1] for i in nums.indices.dropFirst() { let base = nums[i] var j = i - 1 // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 while j >= 0, nums[j] > base { nums[j + 1] = nums[j] // 將 nums[j] 向右移動一位 j -= 1 } nums[j + 1] = base // 將 base 賦值到正確位置 } } @main enum InsertionSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] insertionSort(nums: &nums) print("插入排序完成後 nums = \(nums)") } } ================================================ FILE: zh-hant/codes/swift/chapter_sorting/merge_sort.swift ================================================ /** * File: merge_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* 合併左子陣列和右子陣列 */ func merge(nums: inout [Int], left: Int, mid: Int, right: Int) { // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] // 建立一個臨時陣列 tmp ,用於存放合併後的結果 var tmp = Array(repeating: 0, count: right - left + 1) // 初始化左子陣列和右子陣列的起始索引 var i = left, j = mid + 1, k = 0 // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 while i <= mid, j <= right { if nums[i] <= nums[j] { tmp[k] = nums[i] i += 1 } else { tmp[k] = nums[j] j += 1 } k += 1 } // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 while i <= mid { tmp[k] = nums[i] i += 1 k += 1 } while j <= right { tmp[k] = nums[j] j += 1 k += 1 } // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 for k in tmp.indices { nums[left + k] = tmp[k] } } /* 合併排序 */ func mergeSort(nums: inout [Int], left: Int, right: Int) { // 終止條件 if left >= right { // 當子陣列長度為 1 時終止遞迴 return } // 劃分階段 let mid = left + (right - left) / 2 // 計算中點 mergeSort(nums: &nums, left: left, right: mid) // 遞迴左子陣列 mergeSort(nums: &nums, left: mid + 1, right: right) // 遞迴右子陣列 // 合併階段 merge(nums: &nums, left: left, mid: mid, right: right) } @main enum MergeSort { /* Driver Code */ static func main() { /* 合併排序 */ var nums = [7, 3, 2, 6, 0, 1, 5, 4] mergeSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) print("合併排序完成後 nums = \(nums)") } } ================================================ FILE: zh-hant/codes/swift/chapter_sorting/quick_sort.swift ================================================ /** * File: quick_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* 快速排序類別 */ /* 哨兵劃分 */ func partition(nums: inout [Int], left: Int, right: Int) -> Int { // 以 nums[left] 為基準數 var i = left var j = right while i < j { while i < j, nums[j] >= nums[left] { j -= 1 // 從右向左找首個小於基準數的元素 } while i < j, nums[i] <= nums[left] { i += 1 // 從左向右找首個大於基準數的元素 } nums.swapAt(i, j) // 交換這兩個元素 } nums.swapAt(i, left) // 將基準數交換至兩子陣列的分界線 return i // 返回基準數的索引 } /* 快速排序 */ func quickSort(nums: inout [Int], left: Int, right: Int) { // 子陣列長度為 1 時終止遞迴 if left >= right { return } // 哨兵劃分 let pivot = partition(nums: &nums, left: left, right: right) // 遞迴左子陣列、右子陣列 quickSort(nums: &nums, left: left, right: pivot - 1) quickSort(nums: &nums, left: pivot + 1, right: right) } /* 快速排序類別(中位基準數最佳化) */ /* 選取三個候選元素的中位數 */ func medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int { let l = nums[left] let m = nums[mid] let r = nums[right] if (l <= m && m <= r) || (r <= m && m <= l) { return mid // m 在 l 和 r 之間 } if (m <= l && l <= r) || (r <= l && l <= m) { return left // l 在 m 和 r 之間 } return right } /* 哨兵劃分(三數取中值) */ func partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int { // 選取三個候選元素的中位數 let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right) // 將中位數交換至陣列最左端 nums.swapAt(left, med) return partition(nums: &nums, left: left, right: right) } /* 快速排序(中位基準數最佳化) */ func quickSortMedian(nums: inout [Int], left: Int, right: Int) { // 子陣列長度為 1 時終止遞迴 if left >= right { return } // 哨兵劃分 let pivot = partitionMedian(nums: &nums, left: left, right: right) // 遞迴左子陣列、右子陣列 quickSortMedian(nums: &nums, left: left, right: pivot - 1) quickSortMedian(nums: &nums, left: pivot + 1, right: right) } /* 快速排序(遞迴深度最佳化) */ func quickSortTailCall(nums: inout [Int], left: Int, right: Int) { var left = left var right = right // 子陣列長度為 1 時終止 while left < right { // 哨兵劃分操作 let pivot = partition(nums: &nums, left: left, right: right) // 對兩個子陣列中較短的那個執行快速排序 if (pivot - left) < (right - pivot) { quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // 遞迴排序左子陣列 left = pivot + 1 // 剩餘未排序區間為 [pivot + 1, right] } else { quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // 遞迴排序右子陣列 right = pivot - 1 // 剩餘未排序區間為 [left, pivot - 1] } } } @main enum QuickSort { /* Driver Code */ static func main() { /* 快速排序 */ var nums = [2, 4, 1, 0, 3, 5] quickSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) print("快速排序完成後 nums = \(nums)") /* 快速排序(中位基準數最佳化) */ var nums1 = [2, 4, 1, 0, 3, 5] quickSortMedian(nums: &nums1, left: nums1.startIndex, right: nums1.endIndex - 1) print("快速排序(中位基準數最佳化)完成後 nums1 = \(nums1)") /* 快速排序(遞迴深度最佳化) */ var nums2 = [2, 4, 1, 0, 3, 5] quickSortTailCall(nums: &nums2, left: nums2.startIndex, right: nums2.endIndex - 1) print("快速排序(遞迴深度最佳化)完成後 nums2 = \(nums2)") } } ================================================ FILE: zh-hant/codes/swift/chapter_sorting/radix_sort.swift ================================================ /** * File: radix_sort.swift * Created Time: 2023-01-29 * Author: nuomi1 (nuomi1@qq.com) */ /* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ func digit(num: Int, exp: Int) -> Int { // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 (num / exp) % 10 } /* 計數排序(根據 nums 第 k 位排序) */ func countingSortDigit(nums: inout [Int], exp: Int) { // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 var counter = Array(repeating: 0, count: 10) // 統計 0~9 各數字的出現次數 for i in nums.indices { let d = digit(num: nums[i], exp: exp) // 獲取 nums[i] 第 k 位,記為 d counter[d] += 1 // 統計數字 d 的出現次數 } // 求前綴和,將“出現個數”轉換為“陣列索引” for i in 1 ..< 10 { counter[i] += counter[i - 1] } // 倒序走訪,根據桶內統計結果,將各元素填入 res var res = Array(repeating: 0, count: nums.count) for i in nums.indices.reversed() { let d = digit(num: nums[i], exp: exp) let j = counter[d] - 1 // 獲取 d 在陣列中的索引 j res[j] = nums[i] // 將當前元素填入索引 j counter[d] -= 1 // 將 d 的數量減 1 } // 使用結果覆蓋原陣列 nums for i in nums.indices { nums[i] = res[i] } } /* 基數排序 */ func radixSort(nums: inout [Int]) { // 獲取陣列的最大元素,用於判斷最大位數 var m = Int.min for num in nums { if num > m { m = num } } // 按照從低位到高位的順序走訪 for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) { // 對陣列元素的第 k 位執行計數排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums: &nums, exp: exp) } } @main enum RadixSort { /* Driver Code */ static func main() { // 基數排序 var nums = [ 10_546_151, 35_663_510, 42_865_989, 34_862_445, 81_883_077, 88_906_420, 72_429_244, 30_524_779, 82_060_337, 63_832_996, ] radixSort(nums: &nums) print("基數排序完成後 nums = \(nums)") } } ================================================ FILE: zh-hant/codes/swift/chapter_sorting/selection_sort.swift ================================================ /** * File: selection_sort.swift * Created Time: 2023-05-28 * Author: nuomi1 (nuomi1@qq.com) */ /* 選擇排序 */ func selectionSort(nums: inout [Int]) { // 外迴圈:未排序區間為 [i, n-1] for i in nums.indices.dropLast() { // 內迴圈:找到未排序區間內的最小元素 var k = i for j in nums.indices.dropFirst(i + 1) { if nums[j] < nums[k] { k = j // 記錄最小元素的索引 } } // 將該最小元素與未排序區間的首個元素交換 nums.swapAt(i, k) } } @main enum SelectionSort { /* Driver Code */ static func main() { var nums = [4, 1, 3, 1, 5, 2] selectionSort(nums: &nums) print("選擇排序完成後 nums = \(nums)") } } ================================================ FILE: zh-hant/codes/swift/chapter_stack_and_queue/array_deque.swift ================================================ /** * File: array_deque.swift * Created Time: 2023-02-22 * Author: nuomi1 (nuomi1@qq.com) */ /* 基於環形陣列實現的雙向佇列 */ class ArrayDeque { private var nums: [Int] // 用於儲存雙向佇列元素的陣列 private var front: Int // 佇列首指標,指向佇列首元素 private var _size: Int // 雙向佇列長度 /* 建構子 */ init(capacity: Int) { nums = Array(repeating: 0, count: capacity) front = 0 _size = 0 } /* 獲取雙向佇列的容量 */ func capacity() -> Int { nums.count } /* 獲取雙向佇列的長度 */ func size() -> Int { _size } /* 判斷雙向佇列是否為空 */ func isEmpty() -> Bool { size() == 0 } /* 計算環形陣列索引 */ private func index(i: Int) -> Int { // 透過取餘操作實現陣列首尾相連 // 當 i 越過陣列尾部後,回到頭部 // 當 i 越過陣列頭部後,回到尾部 (i + capacity()) % capacity() } /* 佇列首入列 */ func pushFirst(num: Int) { if size() == capacity() { print("雙向佇列已滿") return } // 佇列首指標向左移動一位 // 透過取餘操作實現 front 越過陣列頭部後回到尾部 front = index(i: front - 1) // 將 num 新增至佇列首 nums[front] = num _size += 1 } /* 佇列尾入列 */ func pushLast(num: Int) { if size() == capacity() { print("雙向佇列已滿") return } // 計算佇列尾指標,指向佇列尾索引 + 1 let rear = index(i: front + size()) // 將 num 新增至佇列尾 nums[rear] = num _size += 1 } /* 佇列首出列 */ func popFirst() -> Int { let num = peekFirst() // 佇列首指標向後移動一位 front = index(i: front + 1) _size -= 1 return num } /* 佇列尾出列 */ func popLast() -> Int { let num = peekLast() _size -= 1 return num } /* 訪問佇列首元素 */ func peekFirst() -> Int { if isEmpty() { fatalError("雙向佇列為空") } return nums[front] } /* 訪問佇列尾元素 */ func peekLast() -> Int { if isEmpty() { fatalError("雙向佇列為空") } // 計算尾元素索引 let last = index(i: front + size() - 1) return nums[last] } /* 返回陣列用於列印 */ func toArray() -> [Int] { // 僅轉換有效長度範圍內的串列元素 (front ..< front + size()).map { nums[index(i: $0)] } } } @main enum _ArrayDeque { /* Driver Code */ static func main() { /* 初始化雙向佇列 */ let deque = ArrayDeque(capacity: 10) deque.pushLast(num: 3) deque.pushLast(num: 2) deque.pushLast(num: 5) print("雙向佇列 deque = \(deque.toArray())") /* 訪問元素 */ let peekFirst = deque.peekFirst() print("佇列首元素 peekFirst = \(peekFirst)") let peekLast = deque.peekLast() print("佇列尾元素 peekLast = \(peekLast)") /* 元素入列 */ deque.pushLast(num: 4) print("元素 4 佇列尾入列後 deque = \(deque.toArray())") deque.pushFirst(num: 1) print("元素 1 佇列首入列後 deque = \(deque.toArray())") /* 元素出列 */ let popLast = deque.popLast() print("佇列尾出列元素 = \(popLast),佇列尾出列後 deque = \(deque.toArray())") let popFirst = deque.popFirst() print("佇列首出列元素 = \(popFirst),佇列首出列後 deque = \(deque.toArray())") /* 獲取雙向佇列的長度 */ let size = deque.size() print("雙向佇列長度 size = \(size)") /* 判斷雙向佇列是否為空 */ let isEmpty = deque.isEmpty() print("雙向佇列是否為空 = \(isEmpty)") } } ================================================ FILE: zh-hant/codes/swift/chapter_stack_and_queue/array_queue.swift ================================================ /** * File: array_queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ /* 基於環形陣列實現的佇列 */ class ArrayQueue { private var nums: [Int] // 用於儲存佇列元素的陣列 private var front: Int // 佇列首指標,指向佇列首元素 private var _size: Int // 佇列長度 init(capacity: Int) { // 初始化陣列 nums = Array(repeating: 0, count: capacity) front = 0 _size = 0 } /* 獲取佇列的容量 */ func capacity() -> Int { nums.count } /* 獲取佇列的長度 */ func size() -> Int { _size } /* 判斷佇列是否為空 */ func isEmpty() -> Bool { size() == 0 } /* 入列 */ func push(num: Int) { if size() == capacity() { print("佇列已滿") return } // 計算佇列尾指標,指向佇列尾索引 + 1 // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 let rear = (front + size()) % capacity() // 將 num 新增至佇列尾 nums[rear] = num _size += 1 } /* 出列 */ @discardableResult func pop() -> Int { let num = peek() // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 front = (front + 1) % capacity() _size -= 1 return num } /* 訪問佇列首元素 */ func peek() -> Int { if isEmpty() { fatalError("佇列為空") } return nums[front] } /* 返回陣列 */ func toArray() -> [Int] { // 僅轉換有效長度範圍內的串列元素 (front ..< front + size()).map { nums[$0 % capacity()] } } } @main enum _ArrayQueue { /* Driver Code */ static func main() { /* 初始化佇列 */ let capacity = 10 let queue = ArrayQueue(capacity: capacity) /* 元素入列 */ queue.push(num: 1) queue.push(num: 3) queue.push(num: 2) queue.push(num: 5) queue.push(num: 4) print("佇列 queue = \(queue.toArray())") /* 訪問佇列首元素 */ let peek = queue.peek() print("佇列首元素 peek = \(peek)") /* 元素出列 */ let pop = queue.pop() print("出列元素 pop = \(pop),出列後 queue = \(queue.toArray())") /* 獲取佇列的長度 */ let size = queue.size() print("佇列長度 size = \(size)") /* 判斷佇列是否為空 */ let isEmpty = queue.isEmpty() print("佇列是否為空 = \(isEmpty)") /* 測試環形陣列 */ for i in 0 ..< 10 { queue.push(num: i) queue.pop() print("第 \(i) 輪入列 + 出列後 queue = \(queue.toArray())") } } } ================================================ FILE: zh-hant/codes/swift/chapter_stack_and_queue/array_stack.swift ================================================ /** * File: array_stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ /* 基於陣列實現的堆疊 */ class ArrayStack { private var stack: [Int] init() { // 初始化串列(動態陣列) stack = [] } /* 獲取堆疊的長度 */ func size() -> Int { stack.count } /* 判斷堆疊是否為空 */ func isEmpty() -> Bool { stack.isEmpty } /* 入堆疊 */ func push(num: Int) { stack.append(num) } /* 出堆疊 */ @discardableResult func pop() -> Int { if isEmpty() { fatalError("堆疊為空") } return stack.removeLast() } /* 訪問堆疊頂元素 */ func peek() -> Int { if isEmpty() { fatalError("堆疊為空") } return stack.last! } /* 將 List 轉化為 Array 並返回 */ func toArray() -> [Int] { stack } } @main enum _ArrayStack { /* Driver Code */ static func main() { /* 初始化堆疊 */ let stack = ArrayStack() /* 元素入堆疊 */ stack.push(num: 1) stack.push(num: 3) stack.push(num: 2) stack.push(num: 5) stack.push(num: 4) print("堆疊 stack = \(stack.toArray())") /* 訪問堆疊頂元素 */ let peek = stack.peek() print("堆疊頂元素 peek = \(peek)") /* 元素出堆疊 */ let pop = stack.pop() print("出堆疊元素 pop = \(pop),出堆疊後 stack = \(stack.toArray())") /* 獲取堆疊的長度 */ let size = stack.size() print("堆疊的長度 size = \(size)") /* 判斷是否為空 */ let isEmpty = stack.isEmpty() print("堆疊是否為空 = \(isEmpty)") } } ================================================ FILE: zh-hant/codes/swift/chapter_stack_and_queue/deque.swift ================================================ /** * File: deque.swift * Created Time: 2023-01-14 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Deque { /* Driver Code */ static func main() { /* 初始化雙向佇列 */ // Swift 沒有內建的雙向佇列類別,可以把 Array 當作雙向佇列來使用 var deque: [Int] = [] /* 元素入列 */ deque.append(2) deque.append(5) deque.append(4) deque.insert(3, at: 0) deque.insert(1, at: 0) print("雙向佇列 deque = \(deque)") /* 訪問元素 */ let peekFirst = deque.first! print("佇列首元素 peekFirst = \(peekFirst)") let peekLast = deque.last! print("佇列尾元素 peekLast = \(peekLast)") /* 元素出列 */ // 使用 Array 模擬時 popFirst 的複雜度為 O(n) let popFirst = deque.removeFirst() print("佇列首出列元素 popFirst = \(popFirst),佇列首出列後 deque = \(deque)") let popLast = deque.removeLast() print("佇列尾出列元素 popLast = \(popLast),佇列尾出列後 deque = \(deque)") /* 獲取雙向佇列的長度 */ let size = deque.count print("雙向佇列長度 size = \(size)") /* 判斷雙向佇列是否為空 */ let isEmpty = deque.isEmpty print("雙向佇列是否為空 = \(isEmpty)") } } ================================================ FILE: zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift ================================================ /** * File: linkedlist_deque.swift * Created Time: 2023-02-22 * Author: nuomi1 (nuomi1@qq.com) */ /* 雙向鏈結串列節點 */ class ListNode { var val: Int // 節點值 var next: ListNode? // 後繼節點引用 weak var prev: ListNode? // 前驅節點引用 init(val: Int) { self.val = val } } /* 基於雙向鏈結串列實現的雙向佇列 */ class LinkedListDeque { private var front: ListNode? // 頭節點 front private var rear: ListNode? // 尾節點 rear private var _size: Int // 雙向佇列的長度 init() { _size = 0 } /* 獲取雙向佇列的長度 */ func size() -> Int { _size } /* 判斷雙向佇列是否為空 */ func isEmpty() -> Bool { size() == 0 } /* 入列操作 */ private func push(num: Int, isFront: Bool) { let node = ListNode(val: num) // 若鏈結串列為空,則令 front 和 rear 都指向 node if isEmpty() { front = node rear = node } // 佇列首入列操作 else if isFront { // 將 node 新增至鏈結串列頭部 front?.prev = node node.next = front front = node // 更新頭節點 } // 佇列尾入列操作 else { // 將 node 新增至鏈結串列尾部 rear?.next = node node.prev = rear rear = node // 更新尾節點 } _size += 1 // 更新佇列長度 } /* 佇列首入列 */ func pushFirst(num: Int) { push(num: num, isFront: true) } /* 佇列尾入列 */ func pushLast(num: Int) { push(num: num, isFront: false) } /* 出列操作 */ private func pop(isFront: Bool) -> Int { if isEmpty() { fatalError("雙向佇列為空") } let val: Int // 佇列首出列操作 if isFront { val = front!.val // 暫存頭節點值 // 刪除頭節點 let fNext = front?.next if fNext != nil { fNext?.prev = nil front?.next = nil } front = fNext // 更新頭節點 } // 佇列尾出列操作 else { val = rear!.val // 暫存尾節點值 // 刪除尾節點 let rPrev = rear?.prev if rPrev != nil { rPrev?.next = nil rear?.prev = nil } rear = rPrev // 更新尾節點 } _size -= 1 // 更新佇列長度 return val } /* 佇列首出列 */ func popFirst() -> Int { pop(isFront: true) } /* 佇列尾出列 */ func popLast() -> Int { pop(isFront: false) } /* 訪問佇列首元素 */ func peekFirst() -> Int { if isEmpty() { fatalError("雙向佇列為空") } return front!.val } /* 訪問佇列尾元素 */ func peekLast() -> Int { if isEmpty() { fatalError("雙向佇列為空") } return rear!.val } /* 返回陣列用於列印 */ func toArray() -> [Int] { var node = front var res = Array(repeating: 0, count: size()) for i in res.indices { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListDeque { /* Driver Code */ static func main() { /* 初始化雙向佇列 */ let deque = LinkedListDeque() deque.pushLast(num: 3) deque.pushLast(num: 2) deque.pushLast(num: 5) print("雙向佇列 deque = \(deque.toArray())") /* 訪問元素 */ let peekFirst = deque.peekFirst() print("佇列首元素 peekFirst = \(peekFirst)") let peekLast = deque.peekLast() print("佇列尾元素 peekLast = \(peekLast)") /* 元素入列 */ deque.pushLast(num: 4) print("元素 4 佇列尾入列後 deque = \(deque.toArray())") deque.pushFirst(num: 1) print("元素 1 佇列首入列後 deque = \(deque.toArray())") /* 元素出列 */ let popLast = deque.popLast() print("佇列尾出列元素 = \(popLast),佇列尾出列後 deque = \(deque.toArray())") let popFirst = deque.popFirst() print("佇列首出列元素 = \(popFirst),佇列首出列後 deque = \(deque.toArray())") /* 獲取雙向佇列的長度 */ let size = deque.size() print("雙向佇列長度 size = \(size)") /* 判斷雙向佇列是否為空 */ let isEmpty = deque.isEmpty() print("雙向佇列是否為空 = \(isEmpty)") } } ================================================ FILE: zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift ================================================ /** * File: linkedlist_queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 基於鏈結串列實現的佇列 */ class LinkedListQueue { private var front: ListNode? // 頭節點 private var rear: ListNode? // 尾節點 private var _size: Int init() { _size = 0 } /* 獲取佇列的長度 */ func size() -> Int { _size } /* 判斷佇列是否為空 */ func isEmpty() -> Bool { size() == 0 } /* 入列 */ func push(num: Int) { // 在尾節點後新增 num let node = ListNode(x: num) // 如果佇列為空,則令頭、尾節點都指向該節點 if front == nil { front = node rear = node } // 如果佇列不為空,則將該節點新增到尾節點後 else { rear?.next = node rear = node } _size += 1 } /* 出列 */ @discardableResult func pop() -> Int { let num = peek() // 刪除頭節點 front = front?.next _size -= 1 return num } /* 訪問佇列首元素 */ func peek() -> Int { if isEmpty() { fatalError("佇列為空") } return front!.val } /* 將鏈結串列轉化為 Array 並返回 */ func toArray() -> [Int] { var node = front var res = Array(repeating: 0, count: size()) for i in res.indices { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListQueue { /* Driver Code */ static func main() { /* 初始化佇列 */ let queue = LinkedListQueue() /* 元素入列 */ queue.push(num: 1) queue.push(num: 3) queue.push(num: 2) queue.push(num: 5) queue.push(num: 4) print("佇列 queue = \(queue.toArray())") /* 訪問佇列首元素 */ let peek = queue.peek() print("佇列首元素 peek = \(peek)") /* 元素出列 */ let pop = queue.pop() print("出列元素 pop = \(pop),出列後 queue = \(queue.toArray())") /* 獲取佇列的長度 */ let size = queue.size() print("佇列長度 size = \(size)") /* 判斷佇列是否為空 */ let isEmpty = queue.isEmpty() print("佇列是否為空 = \(isEmpty)") } } ================================================ FILE: zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift ================================================ /** * File: linkedlist_stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 基於鏈結串列實現的堆疊 */ class LinkedListStack { private var _peek: ListNode? // 將頭節點作為堆疊頂 private var _size: Int // 堆疊的長度 init() { _size = 0 } /* 獲取堆疊的長度 */ func size() -> Int { _size } /* 判斷堆疊是否為空 */ func isEmpty() -> Bool { size() == 0 } /* 入堆疊 */ func push(num: Int) { let node = ListNode(x: num) node.next = _peek _peek = node _size += 1 } /* 出堆疊 */ @discardableResult func pop() -> Int { let num = peek() _peek = _peek?.next _size -= 1 return num } /* 訪問堆疊頂元素 */ func peek() -> Int { if isEmpty() { fatalError("堆疊為空") } return _peek!.val } /* 將 List 轉化為 Array 並返回 */ func toArray() -> [Int] { var node = _peek var res = Array(repeating: 0, count: size()) for i in res.indices.reversed() { res[i] = node!.val node = node?.next } return res } } @main enum _LinkedListStack { /* Driver Code */ static func main() { /* 初始化堆疊 */ let stack = LinkedListStack() /* 元素入堆疊 */ stack.push(num: 1) stack.push(num: 3) stack.push(num: 2) stack.push(num: 5) stack.push(num: 4) print("堆疊 stack = \(stack.toArray())") /* 訪問堆疊頂元素 */ let peek = stack.peek() print("堆疊頂元素 peek = \(peek)") /* 元素出堆疊 */ let pop = stack.pop() print("出堆疊元素 pop = \(pop),出堆疊後 stack = \(stack.toArray())") /* 獲取堆疊的長度 */ let size = stack.size() print("堆疊的長度 size = \(size)") /* 判斷是否為空 */ let isEmpty = stack.isEmpty() print("堆疊是否為空 = \(isEmpty)") } } ================================================ FILE: zh-hant/codes/swift/chapter_stack_and_queue/queue.swift ================================================ /** * File: queue.swift * Created Time: 2023-01-11 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Queue { /* Driver Code */ static func main() { /* 初始化佇列 */ // Swift 沒有內建的佇列類別,可以把 Array 當作佇列來使用 var queue: [Int] = [] /* 元素入列 */ queue.append(1) queue.append(3) queue.append(2) queue.append(5) queue.append(4) print("佇列 queue = \(queue)") /* 訪問佇列首元素 */ let peek = queue.first! print("佇列首元素 peek = \(peek)") /* 元素出列 */ // 使用 Array 模擬時 pop 的複雜度為 O(n) let pool = queue.removeFirst() print("出列元素 pop = \(pool),出列後 queue = \(queue)") /* 獲取佇列的長度 */ let size = queue.count print("佇列長度 size = \(size)") /* 判斷佇列是否為空 */ let isEmpty = queue.isEmpty print("佇列是否為空 = \(isEmpty)") } } ================================================ FILE: zh-hant/codes/swift/chapter_stack_and_queue/stack.swift ================================================ /** * File: stack.swift * Created Time: 2023-01-09 * Author: nuomi1 (nuomi1@qq.com) */ @main enum Stack { /* Driver Code */ static func main() { /* 初始化堆疊 */ // Swift 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 var stack: [Int] = [] /* 元素入堆疊 */ stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) print("堆疊 stack = \(stack)") /* 訪問堆疊頂元素 */ let peek = stack.last! print("堆疊頂元素 peek = \(peek)") /* 元素出堆疊 */ let pop = stack.removeLast() print("出堆疊元素 pop = \(pop),出堆疊後 stack = \(stack)") /* 獲取堆疊的長度 */ let size = stack.count print("堆疊的長度 size = \(size)") /* 判斷是否為空 */ let isEmpty = stack.isEmpty print("堆疊是否為空 = \(isEmpty)") } } ================================================ FILE: zh-hant/codes/swift/chapter_tree/array_binary_tree.swift ================================================ /** * File: array_binary_tree.swift * Created Time: 2023-07-23 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 陣列表示下的二元樹類別 */ class ArrayBinaryTree { private var tree: [Int?] /* 建構子 */ init(arr: [Int?]) { tree = arr } /* 串列容量 */ func size() -> Int { tree.count } /* 獲取索引為 i 節點的值 */ func val(i: Int) -> Int? { // 若索引越界,則返回 null ,代表空位 if i < 0 || i >= size() { return nil } return tree[i] } /* 獲取索引為 i 節點的左子節點的索引 */ func left(i: Int) -> Int { 2 * i + 1 } /* 獲取索引為 i 節點的右子節點的索引 */ func right(i: Int) -> Int { 2 * i + 2 } /* 獲取索引為 i 節點的父節點的索引 */ func parent(i: Int) -> Int { (i - 1) / 2 } /* 層序走訪 */ func levelOrder() -> [Int] { var res: [Int] = [] // 直接走訪陣列 for i in 0 ..< size() { if let val = val(i: i) { res.append(val) } } return res } /* 深度優先走訪 */ private func dfs(i: Int, order: String, res: inout [Int]) { // 若為空位,則返回 guard let val = val(i: i) else { return } // 前序走訪 if order == "pre" { res.append(val) } dfs(i: left(i: i), order: order, res: &res) // 中序走訪 if order == "in" { res.append(val) } dfs(i: right(i: i), order: order, res: &res) // 後序走訪 if order == "post" { res.append(val) } } /* 前序走訪 */ func preOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "pre", res: &res) return res } /* 中序走訪 */ func inOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "in", res: &res) return res } /* 後序走訪 */ func postOrder() -> [Int] { var res: [Int] = [] dfs(i: 0, order: "post", res: &res) return res } } @main enum _ArrayBinaryTree { /* Driver Code */ static func main() { // 初始化二元樹 // 這裡藉助了一個從陣列直接生成二元樹的函式 let arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] let root = TreeNode.listToTree(arr: arr) print("\n初始化二元樹\n") print("二元樹的陣列表示:") print(arr) print("二元樹的鏈結串列表示:") PrintUtil.printTree(root: root) // 陣列表示下的二元樹類別 let abt = ArrayBinaryTree(arr: arr) // 訪問節點 let i = 1 let l = abt.left(i: i) let r = abt.right(i: i) let p = abt.parent(i: i) print("\n當前節點的索引為 \(i) ,值為 \(abt.val(i: i) as Any)") print("其左子節點的索引為 \(l) ,值為 \(abt.val(i: l) as Any)") print("其右子節點的索引為 \(r) ,值為 \(abt.val(i: r) as Any)") print("其父節點的索引為 \(p) ,值為 \(abt.val(i: p) as Any)") // 走訪樹 var res = abt.levelOrder() print("\n層序走訪為:\(res)") res = abt.preOrder() print("前序走訪為:\(res)") res = abt.inOrder() print("中序走訪為:\(res)") res = abt.postOrder() print("後序走訪為:\(res)") } } ================================================ FILE: zh-hant/codes/swift/chapter_tree/avl_tree.swift ================================================ /** * File: avl_tree.swift * Created Time: 2023-01-28 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* AVL 樹 */ class AVLTree { fileprivate var root: TreeNode? // 根節點 init() {} /* 獲取節點高度 */ func height(node: TreeNode?) -> Int { // 空節點高度為 -1 ,葉節點高度為 0 node?.height ?? -1 } /* 更新節點高度 */ private func updateHeight(node: TreeNode?) { // 節點高度等於最高子樹高度 + 1 node?.height = max(height(node: node?.left), height(node: node?.right)) + 1 } /* 獲取平衡因子 */ func balanceFactor(node: TreeNode?) -> Int { // 空節點平衡因子為 0 guard let node = node else { return 0 } // 節點平衡因子 = 左子樹高度 - 右子樹高度 return height(node: node.left) - height(node: node.right) } /* 右旋操作 */ private func rightRotate(node: TreeNode?) -> TreeNode? { let child = node?.left let grandChild = child?.right // 以 child 為原點,將 node 向右旋轉 child?.right = node node?.left = grandChild // 更新節點高度 updateHeight(node: node) updateHeight(node: child) // 返回旋轉後子樹的根節點 return child } /* 左旋操作 */ private func leftRotate(node: TreeNode?) -> TreeNode? { let child = node?.right let grandChild = child?.left // 以 child 為原點,將 node 向左旋轉 child?.left = node node?.right = grandChild // 更新節點高度 updateHeight(node: node) updateHeight(node: child) // 返回旋轉後子樹的根節點 return child } /* 執行旋轉操作,使該子樹重新恢復平衡 */ private func rotate(node: TreeNode?) -> TreeNode? { // 獲取節點 node 的平衡因子 let balanceFactor = balanceFactor(node: node) // 左偏樹 if balanceFactor > 1 { if self.balanceFactor(node: node?.left) >= 0 { // 右旋 return rightRotate(node: node) } else { // 先左旋後右旋 node?.left = leftRotate(node: node?.left) return rightRotate(node: node) } } // 右偏樹 if balanceFactor < -1 { if self.balanceFactor(node: node?.right) <= 0 { // 左旋 return leftRotate(node: node) } else { // 先右旋後左旋 node?.right = rightRotate(node: node?.right) return leftRotate(node: node) } } // 平衡樹,無須旋轉,直接返回 return node } /* 插入節點 */ func insert(val: Int) { root = insertHelper(node: root, val: val) } /* 遞迴插入節點(輔助方法) */ private func insertHelper(node: TreeNode?, val: Int) -> TreeNode? { var node = node if node == nil { return TreeNode(x: val) } /* 1. 查詢插入位置並插入節點 */ if val < node!.val { node?.left = insertHelper(node: node?.left, val: val) } else if val > node!.val { node?.right = insertHelper(node: node?.right, val: val) } else { return node // 重複節點不插入,直接返回 } updateHeight(node: node) // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = rotate(node: node) // 返回子樹的根節點 return node } /* 刪除節點 */ func remove(val: Int) { root = removeHelper(node: root, val: val) } /* 遞迴刪除節點(輔助方法) */ private func removeHelper(node: TreeNode?, val: Int) -> TreeNode? { var node = node if node == nil { return nil } /* 1. 查詢節點並刪除 */ if val < node!.val { node?.left = removeHelper(node: node?.left, val: val) } else if val > node!.val { node?.right = removeHelper(node: node?.right, val: val) } else { if node?.left == nil || node?.right == nil { let child = node?.left ?? node?.right // 子節點數量 = 0 ,直接刪除 node 並返回 if child == nil { return nil } // 子節點數量 = 1 ,直接刪除 node else { node = child } } else { // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 var temp = node?.right while temp?.left != nil { temp = temp?.left } node?.right = removeHelper(node: node?.right, val: temp!.val) node?.val = temp!.val } } updateHeight(node: node) // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = rotate(node: node) // 返回子樹的根節點 return node } /* 查詢節點 */ func search(val: Int) -> TreeNode? { var cur = root while cur != nil { // 目標節點在 cur 的右子樹中 if cur!.val < val { cur = cur?.right } // 目標節點在 cur 的左子樹中 else if cur!.val > val { cur = cur?.left } // 找到目標節點,跳出迴圈 else { break } } // 返回目標節點 return cur } } @main enum _AVLTree { static func testInsert(tree: AVLTree, val: Int) { tree.insert(val: val) print("\n插入節點 \(val) 後,AVL 樹為") PrintUtil.printTree(root: tree.root) } static func testRemove(tree: AVLTree, val: Int) { tree.remove(val: val) print("\n刪除節點 \(val) 後,AVL 樹為") PrintUtil.printTree(root: tree.root) } /* Driver Code */ static func main() { /* 初始化空 AVL 樹 */ let avlTree = AVLTree() /* 插入節點 */ // 請關注插入節點後,AVL 樹是如何保持平衡的 testInsert(tree: avlTree, val: 1) testInsert(tree: avlTree, val: 2) testInsert(tree: avlTree, val: 3) testInsert(tree: avlTree, val: 4) testInsert(tree: avlTree, val: 5) testInsert(tree: avlTree, val: 8) testInsert(tree: avlTree, val: 7) testInsert(tree: avlTree, val: 9) testInsert(tree: avlTree, val: 10) testInsert(tree: avlTree, val: 6) /* 插入重複節點 */ testInsert(tree: avlTree, val: 7) /* 刪除節點 */ // 請關注刪除節點後,AVL 樹是如何保持平衡的 testRemove(tree: avlTree, val: 8) // 刪除度為 0 的節點 testRemove(tree: avlTree, val: 5) // 刪除度為 1 的節點 testRemove(tree: avlTree, val: 4) // 刪除度為 2 的節點 /* 查詢節點 */ let node = avlTree.search(val: 7) print("\n查詢到的節點物件為 \(node!),節點值 = \(node!.val)") } } ================================================ FILE: zh-hant/codes/swift/chapter_tree/binary_search_tree.swift ================================================ /** * File: binary_search_tree.swift * Created Time: 2023-01-26 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 二元搜尋樹 */ class BinarySearchTree { private var root: TreeNode? /* 建構子 */ init() { // 初始化空樹 root = nil } /* 獲取二元樹根節點 */ func getRoot() -> TreeNode? { root } /* 查詢節點 */ func search(num: Int) -> TreeNode? { var cur = root // 迴圈查詢,越過葉節點後跳出 while cur != nil { // 目標節點在 cur 的右子樹中 if cur!.val < num { cur = cur?.right } // 目標節點在 cur 的左子樹中 else if cur!.val > num { cur = cur?.left } // 找到目標節點,跳出迴圈 else { break } } // 返回目標節點 return cur } /* 插入節點 */ func insert(num: Int) { // 若樹為空,則初始化根節點 if root == nil { root = TreeNode(x: num) return } var cur = root var pre: TreeNode? // 迴圈查詢,越過葉節點後跳出 while cur != nil { // 找到重複節點,直接返回 if cur!.val == num { return } pre = cur // 插入位置在 cur 的右子樹中 if cur!.val < num { cur = cur?.right } // 插入位置在 cur 的左子樹中 else { cur = cur?.left } } // 插入節點 let node = TreeNode(x: num) if pre!.val < num { pre?.right = node } else { pre?.left = node } } /* 刪除節點 */ func remove(num: Int) { // 若樹為空,直接提前返回 if root == nil { return } var cur = root var pre: TreeNode? // 迴圈查詢,越過葉節點後跳出 while cur != nil { // 找到待刪除節點,跳出迴圈 if cur!.val == num { break } pre = cur // 待刪除節點在 cur 的右子樹中 if cur!.val < num { cur = cur?.right } // 待刪除節點在 cur 的左子樹中 else { cur = cur?.left } } // 若無待刪除節點,則直接返回 if cur == nil { return } // 子節點數量 = 0 or 1 if cur?.left == nil || cur?.right == nil { // 當子節點數量 = 0 / 1 時, child = null / 該子節點 let child = cur?.left ?? cur?.right // 刪除節點 cur if cur !== root { if pre?.left === cur { pre?.left = child } else { pre?.right = child } } else { // 若刪除節點為根節點,則重新指定根節點 root = child } } // 子節點數量 = 2 else { // 獲取中序走訪中 cur 的下一個節點 var tmp = cur?.right while tmp?.left != nil { tmp = tmp?.left } // 遞迴刪除節點 tmp remove(num: tmp!.val) // 用 tmp 覆蓋 cur cur?.val = tmp!.val } } } @main enum _BinarySearchTree { /* Driver Code */ static func main() { /* 初始化二元搜尋樹 */ let bst = BinarySearchTree() // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] for num in nums { bst.insert(num: num) } print("\n初始化的二元樹為\n") PrintUtil.printTree(root: bst.getRoot()) /* 查詢節點 */ let node = bst.search(num: 7) print("\n查詢到的節點物件為 \(node!),節點值 = \(node!.val)") /* 插入節點 */ bst.insert(num: 16) print("\n插入節點 16 後,二元樹為\n") PrintUtil.printTree(root: bst.getRoot()) /* 刪除節點 */ bst.remove(num: 1) print("\n刪除節點 1 後,二元樹為\n") PrintUtil.printTree(root: bst.getRoot()) bst.remove(num: 2) print("\n刪除節點 2 後,二元樹為\n") PrintUtil.printTree(root: bst.getRoot()) bst.remove(num: 4) print("\n刪除節點 4 後,二元樹為\n") PrintUtil.printTree(root: bst.getRoot()) } } ================================================ FILE: zh-hant/codes/swift/chapter_tree/binary_tree.swift ================================================ /** * File: binary_tree.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils @main enum BinaryTree { /* Driver Code */ static func main() { /* 初始化二元樹 */ // 初始化節點 let n1 = TreeNode(x: 1) let n2 = TreeNode(x: 2) let n3 = TreeNode(x: 3) let n4 = TreeNode(x: 4) let n5 = TreeNode(x: 5) // 構建節點之間的引用(指標) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 print("\n初始化二元樹\n") PrintUtil.printTree(root: n1) /* 插入與刪除節點 */ let P = TreeNode(x: 0) // 在 n1 -> n2 中間插入節點 P n1.left = P P.left = n2 print("\n插入節點 P 後\n") PrintUtil.printTree(root: n1) // 刪除節點 P n1.left = n2 print("\n刪除節點 P 後\n") PrintUtil.printTree(root: n1) } } ================================================ FILE: zh-hant/codes/swift/chapter_tree/binary_tree_bfs.swift ================================================ /** * File: binary_tree_bfs.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils /* 層序走訪 */ func levelOrder(root: TreeNode) -> [Int] { // 初始化佇列,加入根節點 var queue: [TreeNode] = [root] // 初始化一個串列,用於儲存走訪序列 var list: [Int] = [] while !queue.isEmpty { let node = queue.removeFirst() // 隊列出隊 list.append(node.val) // 儲存節點值 if let left = node.left { queue.append(left) // 左子節點入列 } if let right = node.right { queue.append(right) // 右子節點入列 } } return list } @main enum BinaryTreeBFS { /* Driver Code */ static func main() { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 let node = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! print("\n初始化二元樹\n") PrintUtil.printTree(root: node) /* 層序走訪 */ let list = levelOrder(root: node) print("\n層序走訪的節點列印序列 = \(list)") } } ================================================ FILE: zh-hant/codes/swift/chapter_tree/binary_tree_dfs.swift ================================================ /** * File: binary_tree_dfs.swift * Created Time: 2023-01-18 * Author: nuomi1 (nuomi1@qq.com) */ import utils // 初始化串列,用於儲存走訪序列 var list: [Int] = [] /* 前序走訪 */ func preOrder(root: TreeNode?) { guard let root = root else { return } // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 list.append(root.val) preOrder(root: root.left) preOrder(root: root.right) } /* 中序走訪 */ func inOrder(root: TreeNode?) { guard let root = root else { return } // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 inOrder(root: root.left) list.append(root.val) inOrder(root: root.right) } /* 後序走訪 */ func postOrder(root: TreeNode?) { guard let root = root else { return } // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 postOrder(root: root.left) postOrder(root: root.right) list.append(root.val) } @main enum BinaryTreeDFS { /* Driver Code */ static func main() { /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 let root = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! print("\n初始化二元樹\n") PrintUtil.printTree(root: root) /* 前序走訪 */ list.removeAll() preOrder(root: root) print("\n前序走訪的節點列印序列 = \(list)") /* 中序走訪 */ list.removeAll() inOrder(root: root) print("\n中序走訪的節點列印序列 = \(list)") /* 後序走訪 */ list.removeAll() postOrder(root: root) print("\n後序走訪的節點列印序列 = \(list)") } } ================================================ FILE: zh-hant/codes/swift/utils/ListNode.swift ================================================ /** * File: ListNode.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ public class ListNode: Hashable { public var val: Int // 節點值 public var next: ListNode? // 後繼節點引用 public init(x: Int) { val = x } public static func == (lhs: ListNode, rhs: ListNode) -> Bool { lhs.val == rhs.val && lhs.next.map { ObjectIdentifier($0) } == rhs.next.map { ObjectIdentifier($0) } } public func hash(into hasher: inout Hasher) { hasher.combine(val) hasher.combine(next.map { ObjectIdentifier($0) }) } public static func arrToLinkedList(arr: [Int]) -> ListNode? { let dum = ListNode(x: 0) var head: ListNode? = dum for val in arr { head?.next = ListNode(x: val) head = head?.next } return dum.next } } ================================================ FILE: zh-hant/codes/swift/utils/Pair.swift ================================================ /** * File: Pair.swift * Created Time: 2023-06-28 * Author: nuomi1 (nuomi1@qq.com) */ /* 鍵值對 */ public class Pair: Equatable { public var key: Int public var val: String public init(key: Int, val: String) { self.key = key self.val = val } public static func == (lhs: Pair, rhs: Pair) -> Bool { lhs.key == rhs.key && lhs.val == rhs.val } } ================================================ FILE: zh-hant/codes/swift/utils/PrintUtil.swift ================================================ /** * File: PrintUtil.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ public enum PrintUtil { private class Trunk { var prev: Trunk? var str: String init(prev: Trunk?, str: String) { self.prev = prev self.str = str } } public static func printLinkedList(head: ListNode) { var head: ListNode? = head var list: [String] = [] while head != nil { list.append("\(head!.val)") head = head?.next } print(list.joined(separator: " -> ")) } public static func printTree(root: TreeNode?) { printTree(root: root, prev: nil, isRight: false) } private static func printTree(root: TreeNode?, prev: Trunk?, isRight: Bool) { if root == nil { return } var prevStr = " " let trunk = Trunk(prev: prev, str: prevStr) printTree(root: root?.right, prev: trunk, isRight: true) if prev == nil { trunk.str = "———" } else if isRight { trunk.str = "/———" prevStr = " |" } else { trunk.str = "\\———" prev?.str = prevStr } showTrunks(p: trunk) print(" \(root!.val)") if prev != nil { prev?.str = prevStr } trunk.str = " |" printTree(root: root?.left, prev: trunk, isRight: false) } private static func showTrunks(p: Trunk?) { if p == nil { return } showTrunks(p: p?.prev) print(p!.str, terminator: "") } public static func printHashMap(map: [K: V]) { for (key, value) in map { print("\(key) -> \(value)") } } public static func printHeap(queue: [Int]) { print("堆積的陣列表示:", terminator: "") print(queue) print("堆積的樹狀表示:") let root = TreeNode.listToTree(arr: queue) printTree(root: root) } public static func printMatrix(matrix: [[T]]) { print("[") for row in matrix { print(" \(row),") } print("]") } } ================================================ FILE: zh-hant/codes/swift/utils/TreeNode.swift ================================================ /** * File: TreeNode.swift * Created Time: 2023-01-02 * Author: nuomi1 (nuomi1@qq.com) */ /* 二元樹節點類別 */ public class TreeNode { public var val: Int // 節點值 public var height: Int // 節點高度 public var left: TreeNode? // 左子節點引用 public var right: TreeNode? // 右子節點引用 /* 建構子 */ public init(x: Int) { val = x height = 0 } // 序列化編碼規則請參考: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ // 二元樹的陣列表示: // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] // 二元樹的鏈結串列表示: // /——— 15 // /——— 7 // /——— 3 // | \——— 6 // | \——— 12 // ——— 1 // \——— 2 // | /——— 9 // \——— 4 // \——— 8 /* 將串列反序列化為二元樹:遞迴 */ private static func listToTreeDFS(arr: [Int?], i: Int) -> TreeNode? { if i < 0 || i >= arr.count || arr[i] == nil { return nil } let root = TreeNode(x: arr[i]!) root.left = listToTreeDFS(arr: arr, i: 2 * i + 1) root.right = listToTreeDFS(arr: arr, i: 2 * i + 2) return root } /* 將串列反序列化為二元樹 */ public static func listToTree(arr: [Int?]) -> TreeNode? { listToTreeDFS(arr: arr, i: 0) } /* 將二元樹序列化為串列:遞迴 */ private static func treeToListDFS(root: TreeNode?, i: Int, res: inout [Int?]) { if root == nil { return } while i >= res.count { res.append(nil) } res[i] = root?.val treeToListDFS(root: root?.left, i: 2 * i + 1, res: &res) treeToListDFS(root: root?.right, i: 2 * i + 2, res: &res) } /* 將二元樹序列化為串列 */ public static func treeToList(root: TreeNode?) -> [Int?] { var res: [Int?] = [] treeToListDFS(root: root, i: 0, res: &res) return res } } ================================================ FILE: zh-hant/codes/swift/utils/Vertex.swift ================================================ /** * File: Vertex.swift * Created Time: 2023-02-19 * Author: nuomi1 (nuomi1@qq.com) */ /* 頂點類別 */ public class Vertex: Hashable { public var val: Int public init(val: Int) { self.val = val } public static func == (lhs: Vertex, rhs: Vertex) -> Bool { lhs.val == rhs.val } public func hash(into hasher: inout Hasher) { hasher.combine(val) } /* 輸入值串列 vals ,返回頂點串列 vets */ public static func valsToVets(vals: [Int]) -> [Vertex] { vals.map { Vertex(val: $0) } } /* 輸入頂點串列 vets ,返回值串列 vals */ public static func vetsToVals(vets: [Vertex]) -> [Int] { vets.map { $0.val } } } ================================================ FILE: zh-hant/codes/typescript/.gitignore ================================================ node_modules out package.json package-lock.json ================================================ FILE: zh-hant/codes/typescript/.prettierrc ================================================ { "tabWidth": 4, "useTabs": false, "semi": true, "singleQuote": true } ================================================ FILE: zh-hant/codes/typescript/chapter_array_and_linkedlist/array.ts ================================================ /** * File: array.ts * Created Time: 2022-12-04 * Author: Justin (xiefahit@gmail.com) */ /* 隨機訪問元素 */ function randomAccess(nums: number[]): number { // 在區間 [0, nums.length) 中隨機抽取一個數字 const random_index = Math.floor(Math.random() * nums.length); // 獲取並返回隨機元素 const random_num = nums[random_index]; return random_num; } /* 擴展陣列長度 */ // 請注意,TypeScript 的 Array 是動態陣列,可以直接擴展 // 為了方便學習,本函式將 Array 看作長度不可變的陣列 function extend(nums: number[], enlarge: number): number[] { // 初始化一個擴展長度後的陣列 const res = new Array(nums.length + enlarge).fill(0); // 將原陣列中的所有元素複製到新陣列 for (let i = 0; i < nums.length; i++) { res[i] = nums[i]; } // 返回擴展後的新陣列 return res; } /* 在陣列的索引 index 處插入元素 num */ function insert(nums: number[], num: number, index: number): void { // 把索引 index 以及之後的所有元素向後移動一位 for (let i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } // 將 num 賦給 index 處的元素 nums[index] = num; } /* 刪除索引 index 處的元素 */ function remove(nums: number[], index: number): void { // 把索引 index 之後的所有元素向前移動一位 for (let i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } } /* 走訪陣列 */ function traverse(nums: number[]): void { let count = 0; // 透過索引走訪陣列 for (let i = 0; i < nums.length; i++) { count += nums[i]; } // 直接走訪陣列元素 for (const num of nums) { count += num; } } /* 在陣列中查詢指定元素 */ function find(nums: number[], target: number): number { for (let i = 0; i < nums.length; i++) { if (nums[i] === target) { return i; } } return -1; } /* Driver Code */ /* 初始化陣列 */ const arr: number[] = new Array(5).fill(0); console.log('陣列 arr =', arr); let nums: number[] = [1, 3, 2, 5, 4]; console.log('陣列 nums =', nums); /* 隨機訪問 */ let random_num = randomAccess(nums); console.log('在 nums 中獲取隨機元素', random_num); /* 長度擴展 */ nums = extend(nums, 3); console.log('將陣列長度擴展至 8 ,得到 nums =', nums); /* 插入元素 */ insert(nums, 6, 3); console.log('在索引 3 處插入數字 6 ,得到 nums =', nums); /* 刪除元素 */ remove(nums, 2); console.log('刪除索引 2 處的元素,得到 nums =', nums); /* 走訪陣列 */ traverse(nums); /* 查詢元素 */ let index = find(nums, 3); console.log('在 nums 中查詢元素 3 ,得到索引 =', index); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_array_and_linkedlist/linked_list.ts ================================================ /** * File: linked_list.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from '../modules/ListNode'; import { printLinkedList } from '../modules/PrintUtil'; /* 在鏈結串列的節點 n0 之後插入節點 P */ function insert(n0: ListNode, P: ListNode): void { const n1 = n0.next; P.next = n1; n0.next = P; } /* 刪除鏈結串列的節點 n0 之後的首個節點 */ function remove(n0: ListNode): void { if (!n0.next) { return; } // n0 -> P -> n1 const P = n0.next; const n1 = P.next; n0.next = n1; } /* 訪問鏈結串列中索引為 index 的節點 */ function access(head: ListNode | null, index: number): ListNode | null { for (let i = 0; i < index; i++) { if (!head) { return null; } head = head.next; } return head; } /* 在鏈結串列中查詢值為 target 的首個節點 */ function find(head: ListNode | null, target: number): number { let index = 0; while (head !== null) { if (head.val === target) { return index; } head = head.next; index += 1; } return -1; } /* Driver Code */ /* 初始化鏈結串列 */ // 初始化各個節點 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // 構建節點之間的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; console.log('初始化的鏈結串列為'); printLinkedList(n0); /* 插入節點 */ insert(n0, new ListNode(0)); console.log('插入節點後的鏈結串列為'); printLinkedList(n0); /* 刪除節點 */ remove(n0); console.log('刪除節點後的鏈結串列為'); printLinkedList(n0); /* 訪問節點 */ const node = access(n0, 3); console.log(`鏈結串列中索引 3 處的節點的值 = ${node?.val}`); /* 查詢節點 */ const index = find(n0, 2); console.log(`鏈結串列中值為 2 的節點的索引 = ${index}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_array_and_linkedlist/list.ts ================================================ /** * File: list.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ /* 初始化串列 */ const nums: number[] = [1, 3, 2, 5, 4]; console.log(`串列 nums = ${nums}`); /* 訪問元素 */ const num: number = nums[1]; console.log(`訪問索引 1 處的元素,得到 num = ${num}`); /* 更新元素 */ nums[1] = 0; console.log(`將索引 1 處的元素更新為 0 ,得到 nums = ${nums}`); /* 清空串列 */ nums.length = 0; console.log(`清空串列後 nums = ${nums}`); /* 在尾部新增元素 */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); console.log(`新增元素後 nums = ${nums}`); /* 在中間插入元素 */ nums.splice(3, 0, 6); console.log(`在索引 3 處插入數字 6 ,得到 nums = ${nums}`); /* 刪除元素 */ nums.splice(3, 1); console.log(`刪除索引 3 處的元素,得到 nums = ${nums}`); /* 透過索引走訪串列 */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* 直接走訪串列元素 */ count = 0; for (const x of nums) { count += x; } /* 拼接兩個串列 */ const nums1: number[] = [6, 8, 7, 10, 9]; nums.push(...nums1); console.log(`將串列 nums1 拼接到 nums 之後,得到 nums = ${nums}`); /* 排序串列 */ nums.sort((a, b) => a - b); console.log(`排序串列後 nums = ${nums}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_array_and_linkedlist/my_list.ts ================================================ /** * File: my_list.ts * Created Time: 2022-12-11 * Author: Justin (xiefahit@gmail.com) */ /* 串列類別 */ class MyList { private arr: Array; // 陣列(儲存串列元素) private _capacity: number = 10; // 串列容量 private _size: number = 0; // 串列長度(當前元素數量) private extendRatio: number = 2; // 每次串列擴容的倍數 /* 建構子 */ constructor() { this.arr = new Array(this._capacity); } /* 獲取串列長度(當前元素數量)*/ public size(): number { return this._size; } /* 獲取串列容量 */ public capacity(): number { return this._capacity; } /* 訪問元素 */ public get(index: number): number { // 索引如果越界,則丟擲異常,下同 if (index < 0 || index >= this._size) throw new Error('索引越界'); return this.arr[index]; } /* 更新元素 */ public set(index: number, num: number): void { if (index < 0 || index >= this._size) throw new Error('索引越界'); this.arr[index] = num; } /* 在尾部新增元素 */ public add(num: number): void { // 如果長度等於容量,則需要擴容 if (this._size === this._capacity) this.extendCapacity(); // 將新元素新增到串列尾部 this.arr[this._size] = num; this._size++; } /* 在中間插入元素 */ public insert(index: number, num: number): void { if (index < 0 || index >= this._size) throw new Error('索引越界'); // 元素數量超出容量時,觸發擴容機制 if (this._size === this._capacity) { this.extendCapacity(); } // 將索引 index 以及之後的元素都向後移動一位 for (let j = this._size - 1; j >= index; j--) { this.arr[j + 1] = this.arr[j]; } // 更新元素數量 this.arr[index] = num; this._size++; } /* 刪除元素 */ public remove(index: number): number { if (index < 0 || index >= this._size) throw new Error('索引越界'); let num = this.arr[index]; // 將將索引 index 之後的元素都向前移動一位 for (let j = index; j < this._size - 1; j++) { this.arr[j] = this.arr[j + 1]; } // 更新元素數量 this._size--; // 返回被刪除的元素 return num; } /* 串列擴容 */ public extendCapacity(): void { // 新建一個長度為 size 的陣列,並將原陣列複製到新陣列 this.arr = this.arr.concat( new Array(this.capacity() * (this.extendRatio - 1)) ); // 更新串列容量 this._capacity = this.arr.length; } /* 將串列轉換為陣列 */ public toArray(): number[] { let size = this.size(); // 僅轉換有效長度範圍內的串列元素 const arr = new Array(size); for (let i = 0; i < size; i++) { arr[i] = this.get(i); } return arr; } } /* Driver Code */ /* 初始化串列 */ const nums = new MyList(); /* 在尾部新增元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); console.log( `串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}` ); /* 在中間插入元素 */ nums.insert(3, 6); console.log(`在索引 3 處插入數字 6 ,得到 nums = ${nums.toArray()}`); /* 刪除元素 */ nums.remove(3); console.log(`刪除索引 3 處的元素,得到 nums = ${nums.toArray()}`); /* 訪問元素 */ const num = nums.get(1); console.log(`訪問索引 1 處的元素,得到 num = ${num}`); /* 更新元素 */ nums.set(1, 0); console.log(`將索引 1 處的元素更新為 0 ,得到 nums = ${nums.toArray()}`); /* 測試擴容機制 */ for (let i = 0; i < 10; i++) { // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 nums.add(i); } console.log( `擴容後的串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}` ); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_backtracking/n_queens.ts ================================================ /** * File: n_queens.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* 回溯演算法:n 皇后 */ function backtrack( row: number, n: number, state: string[][], res: string[][][], cols: boolean[], diags1: boolean[], diags2: boolean[] ): void { // 當放置完所有行時,記錄解 if (row === n) { res.push(state.map((row) => row.slice())); return; } // 走訪所有列 for (let col = 0; col < n; col++) { // 計算該格子對應的主對角線和次對角線 const diag1 = row - col + n - 1; const diag2 = row + col; // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // 嘗試:將皇后放置在該格子 state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // 放置下一行 backtrack(row + 1, n, state, res, cols, diags1, diags2); // 回退:將該格子恢復為空位 state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* 求解 n 皇后 */ function nQueens(n: number): string[][][] { // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 const state = Array.from({ length: n }, () => Array(n).fill('#')); const cols = Array(n).fill(false); // 記錄列是否有皇后 const diags1 = Array(2 * n - 1).fill(false); // 記錄主對角線上是否有皇后 const diags2 = Array(2 * n - 1).fill(false); // 記錄次對角線上是否有皇后 const res: string[][][] = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } // Driver Code const n = 4; const res = nQueens(n); console.log(`輸入棋盤長寬為 ${n}`); console.log(`皇后放置方案共有 ${res.length} 種`); res.forEach((state) => { console.log('--------------------'); state.forEach((row) => console.log(row)); }); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_backtracking/permutations_i.ts ================================================ /** * File: permutations_i.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* 回溯演算法:全排列 I */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // 當狀態長度等於元素數量時,記錄解 if (state.length === choices.length) { res.push([...state]); return; } // 走訪所有選擇 choices.forEach((choice, i) => { // 剪枝:不允許重複選擇元素 if (!selected[i]) { // 嘗試:做出選擇,更新狀態 selected[i] = true; state.push(choice); // 進行下一輪選擇 backtrack(state, choices, selected, res); // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false; state.pop(); } }); } /* 全排列 I */ function permutationsI(nums: number[]): number[][] { const res: number[][] = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums: number[] = [1, 2, 3]; const res: number[][] = permutationsI(nums); console.log(`輸入陣列 nums = ${JSON.stringify(nums)}`); console.log(`所有排列 res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_backtracking/permutations_ii.ts ================================================ /** * File: permutations_ii.ts * Created Time: 2023-05-13 * Author: Justin (xiefahit@gmail.com) */ /* 回溯演算法:全排列 II */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // 當狀態長度等於元素數量時,記錄解 if (state.length === choices.length) { res.push([...state]); return; } // 走訪所有選擇 const duplicated = new Set(); choices.forEach((choice, i) => { // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 if (!selected[i] && !duplicated.has(choice)) { // 嘗試:做出選擇,更新狀態 duplicated.add(choice); // 記錄選擇過的元素值 selected[i] = true; state.push(choice); // 進行下一輪選擇 backtrack(state, choices, selected, res); // 回退:撤銷選擇,恢復到之前的狀態 selected[i] = false; state.pop(); } }); } /* 全排列 II */ function permutationsII(nums: number[]): number[][] { const res: number[][] = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } // Driver Code const nums: number[] = [1, 2, 2]; const res: number[][] = permutationsII(nums); console.log(`輸入陣列 nums = ${JSON.stringify(nums)}`); console.log(`所有排列 res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts ================================================ /** * File: preorder_traversal_i_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 前序走訪:例題一 */ function preOrder(root: TreeNode | null, res: TreeNode[]): void { if (root === null) { return; } if (root.val === 7) { // 記錄解 res.push(root); } preOrder(root.left, res); preOrder(root.right, res); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二元樹'); printTree(root); // 前序走訪 const res: TreeNode[] = []; preOrder(root, res); console.log('\n輸出所有值為 7 的節點'); console.log(res.map((node) => node.val)); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts ================================================ /** * File: preorder_traversal_ii_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 前序走訪:例題二 */ function preOrder( root: TreeNode | null, path: TreeNode[], res: TreeNode[][] ): void { if (root === null) { return; } // 嘗試 path.push(root); if (root.val === 7) { // 記錄解 res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // 回退 path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二元樹'); printTree(root); // 前序走訪 const path: TreeNode[] = []; const res: TreeNode[][] = []; preOrder(root, path, res); console.log('\n輸出所有根節點到節點 7 的路徑'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts ================================================ /** * File: preorder_traversal_iii_compact.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 前序走訪:例題三 */ function preOrder( root: TreeNode | null, path: TreeNode[], res: TreeNode[][] ): void { // 剪枝 if (root === null || root.val === 3) { return; } // 嘗試 path.push(root); if (root.val === 7) { // 記錄解 res.push([...path]); } preOrder(root.left, path, res); preOrder(root.right, path, res); // 回退 path.pop(); } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二元樹'); printTree(root); // 前序走訪 const path: TreeNode[] = []; const res: TreeNode[][] = []; preOrder(root, path, res); console.log('\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts ================================================ /** * File: preorder_traversal_iii_template.ts * Created Time: 2023-05-09 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 判斷當前狀態是否為解 */ function isSolution(state: TreeNode[]): boolean { return state && state[state.length - 1]?.val === 7; } /* 記錄解 */ function recordSolution(state: TreeNode[], res: TreeNode[][]): void { res.push([...state]); } /* 判斷在當前狀態下,該選擇是否合法 */ function isValid(state: TreeNode[], choice: TreeNode): boolean { return choice !== null && choice.val !== 3; } /* 更新狀態 */ function makeChoice(state: TreeNode[], choice: TreeNode): void { state.push(choice); } /* 恢復狀態 */ function undoChoice(state: TreeNode[]): void { state.pop(); } /* 回溯演算法:例題三 */ function backtrack( state: TreeNode[], choices: TreeNode[], res: TreeNode[][] ): void { // 檢查是否為解 if (isSolution(state)) { // 記錄解 recordSolution(state, res); } // 走訪所有選擇 for (const choice of choices) { // 剪枝:檢查選擇是否合法 if (isValid(state, choice)) { // 嘗試:做出選擇,更新狀態 makeChoice(state, choice); // 進行下一輪選擇 backtrack(state, [choice.left, choice.right], res); // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state); } } } // Driver Code const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); console.log('\n初始化二元樹'); printTree(root); // 回溯演算法 const res: TreeNode[][] = []; backtrack([], [root], res); console.log('\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_backtracking/subset_sum_i.ts ================================================ /** * File: subset_sum_i.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯演算法:子集和 I */ function backtrack( state: number[], target: number, choices: number[], start: number, res: number[][] ): void { // 子集和等於 target 時,記錄解 if (target === 0) { res.push([...state]); return; } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 for (let i = start; i < choices.length; i++) { // 剪枝一:若子集和超過 target ,則直接結束迴圈 // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target if (target - choices[i] < 0) { break; } // 嘗試:做出選擇,更新 target, start state.push(choices[i]); // 進行下一輪選擇 backtrack(state, target - choices[i], choices, i, res); // 回退:撤銷選擇,恢復到之前的狀態 state.pop(); } } /* 求解子集和 I */ function subsetSumI(nums: number[], target: number): number[][] { const state = []; // 狀態(子集) nums.sort((a, b) => a - b); // 對 nums 進行排序 const start = 0; // 走訪起始點 const res = []; // 結果串列(子集串列) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumI(nums, target); console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts ================================================ /** * File: subset_sum_i_naive.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯演算法:子集和 I */ function backtrack( state: number[], target: number, total: number, choices: number[], res: number[][] ): void { // 子集和等於 target 時,記錄解 if (total === target) { res.push([...state]); return; } // 走訪所有選擇 for (let i = 0; i < choices.length; i++) { // 剪枝:若子集和超過 target ,則跳過該選擇 if (total + choices[i] > target) { continue; } // 嘗試:做出選擇,更新元素和 total state.push(choices[i]); // 進行下一輪選擇 backtrack(state, target, total + choices[i], choices, res); // 回退:撤銷選擇,恢復到之前的狀態 state.pop(); } } /* 求解子集和 I(包含重複子集) */ function subsetSumINaive(nums: number[], target: number): number[][] { const state = []; // 狀態(子集) const total = 0; // 子集和 const res = []; // 結果串列(子集串列) backtrack(state, target, total, nums, res); return res; } /* Driver Code */ const nums = [3, 4, 5]; const target = 9; const res = subsetSumINaive(nums, target); console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); console.log('請注意,該方法輸出的結果包含重複集合'); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_backtracking/subset_sum_ii.ts ================================================ /** * File: subset_sum_ii.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯演算法:子集和 II */ function backtrack( state: number[], target: number, choices: number[], start: number, res: number[][] ): void { // 子集和等於 target 時,記錄解 if (target === 0) { res.push([...state]); return; } // 走訪所有選擇 // 剪枝二:從 start 開始走訪,避免生成重複子集 // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 for (let 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.push(choices[i]); // 進行下一輪選擇 backtrack(state, target - choices[i], choices, i + 1, res); // 回退:撤銷選擇,恢復到之前的狀態 state.pop(); } } /* 求解子集和 II */ function subsetSumII(nums: number[], target: number): number[][] { const state = []; // 狀態(子集) nums.sort((a, b) => a - b); // 對 nums 進行排序 const start = 0; // 走訪起始點 const res = []; // 結果串列(子集串列) backtrack(state, target, nums, start, res); return res; } /* Driver Code */ const nums = [4, 4, 5]; const target = 9; const res = subsetSumII(nums, target); console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_computational_complexity/iteration.ts ================================================ /** * File: iteration.ts * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* for 迴圈 */ function forLoop(n: number): number { let res = 0; // 迴圈求和 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { res += i; } return res; } /* while 迴圈 */ function whileLoop(n: number): number { let res = 0; let i = 1; // 初始化條件變數 // 迴圈求和 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // 更新條件變數 } return res; } /* while 迴圈(兩次更新) */ function whileLoopII(n: number): number { let res = 0; let i = 1; // 初始化條件變數 // 迴圈求和 1, 4, 10, ... while (i <= n) { res += i; // 更新條件變數 i++; i *= 2; } return res; } /* 雙層 for 迴圈 */ function nestedForLoop(n: number): string { let res = ''; // 迴圈 i = 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { // 迴圈 j = 1, 2, ..., n-1, n for (let j = 1; j <= n; j++) { res += `(${i}, ${j}), `; } } return res; } /* Driver Code */ const n = 5; let res: number; res = forLoop(n); console.log(`for 迴圈的求和結果 res = ${res}`); res = whileLoop(n); console.log(`while 迴圈的求和結果 res = ${res}`); res = whileLoopII(n); console.log(`while 迴圈(兩次更新)求和結果 res = ${res}`); const resStr = nestedForLoop(n); console.log(`雙層 for 迴圈的走訪結果 ${resStr}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_computational_complexity/recursion.ts ================================================ /** * File: recursion.ts * Created Time: 2023-08-28 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 遞迴 */ function recur(n: number): number { // 終止條件 if (n === 1) return 1; // 遞:遞迴呼叫 const res = recur(n - 1); // 迴:返回結果 return n + res; } /* 使用迭代模擬遞迴 */ function forLoopRecur(n: number): number { // 使用一個顯式的堆疊來模擬系統呼叫堆疊 const stack: number[] = []; let res: number = 0; // 遞:遞迴呼叫 for (let i = n; i > 0; i--) { // 透過“入堆疊操作”模擬“遞” stack.push(i); } // 迴:返回結果 while (stack.length) { // 透過“出堆疊操作”模擬“迴” res += stack.pop(); } // res = 1+2+3+...+n return res; } /* 尾遞迴 */ function tailRecur(n: number, res: number): number { // 終止條件 if (n === 0) return res; // 尾遞迴呼叫 return tailRecur(n - 1, res + n); } /* 費波那契數列:遞迴 */ function fib(n: number): number { // 終止條件 f(1) = 0, f(2) = 1 if (n === 1 || n === 2) return n - 1; // 遞迴呼叫 f(n) = f(n-1) + f(n-2) const res = fib(n - 1) + fib(n - 2); // 返回結果 f(n) return res; } /* Driver Code */ const n = 5; let res: number; res = recur(n); console.log(`遞迴函式的求和結果 res = ${res}`); res = forLoopRecur(n); console.log(`使用迭代模擬遞迴的求和結果 res = ${res}`); res = tailRecur(n, 0); console.log(`尾遞迴函式的求和結果 res = ${res}`); res = fib(n); console.log(`費波那契數列的第 ${n} 項為 ${res}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_computational_complexity/space_complexity.ts ================================================ /** * File: space_complexity.ts * Created Time: 2023-02-05 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from '../modules/ListNode'; import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 函式 */ function constFunc(): number { // 執行某些操作 return 0; } /* 常數階 */ function constant(n: number): void { // 常數、變數、物件佔用 O(1) 空間 const a = 0; const b = 0; const nums = new Array(10000); const node = new ListNode(0); // 迴圈中的變數佔用 O(1) 空間 for (let i = 0; i < n; i++) { const c = 0; } // 迴圈中的函式佔用 O(1) 空間 for (let i = 0; i < n; i++) { constFunc(); } } /* 線性階 */ function linear(n: number): void { // 長度為 n 的陣列佔用 O(n) 空間 const nums = new Array(n); // 長度為 n 的串列佔用 O(n) 空間 const nodes: ListNode[] = []; for (let i = 0; i < n; i++) { nodes.push(new ListNode(i)); } // 長度為 n 的雜湊表佔用 O(n) 空間 const map = new Map(); for (let i = 0; i < n; i++) { map.set(i, i.toString()); } } /* 線性階(遞迴實現) */ function linearRecur(n: number): void { console.log(`遞迴 n = ${n}`); if (n === 1) return; linearRecur(n - 1); } /* 平方階 */ function quadratic(n: number): void { // 矩陣佔用 O(n^2) 空間 const numMatrix = Array(n) .fill(null) .map(() => Array(n).fill(null)); // 二維串列佔用 O(n^2) 空間 const numList = []; for (let i = 0; i < n; i++) { const tmp = []; for (let j = 0; j < n; j++) { tmp.push(0); } numList.push(tmp); } } /* 平方階(遞迴實現) */ function quadraticRecur(n: number): number { if (n <= 0) return 0; const nums = new Array(n); console.log(`遞迴 n = ${n} 中的 nums 長度 = ${nums.length}`); return quadraticRecur(n - 1); } /* 指數階(建立滿二元樹) */ function buildTree(n: number): TreeNode | null { if (n === 0) return null; const root = new TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } /* Driver Code */ const n = 5; // 常數階 constant(n); // 線性階 linear(n); linearRecur(n); // 平方階 quadratic(n); quadraticRecur(n); // 指數階 const root = buildTree(n); printTree(root); ================================================ FILE: zh-hant/codes/typescript/chapter_computational_complexity/time_complexity.ts ================================================ /** * File: time_complexity.ts * Created Time: 2023-01-02 * Author: RiverTwilight (contact@rene.wang) */ /* 常數階 */ function constant(n: number): number { let count = 0; const size = 100000; for (let i = 0; i < size; i++) count++; return count; } /* 線性階 */ function linear(n: number): number { let count = 0; for (let i = 0; i < n; i++) count++; return count; } /* 線性階(走訪陣列) */ function arrayTraversal(nums: number[]): number { let count = 0; // 迴圈次數與陣列長度成正比 for (let i = 0; i < nums.length; i++) { count++; } return count; } /* 平方階 */ function quadratic(n: number): number { let count = 0; // 迴圈次數與資料大小 n 成平方關係 for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { count++; } } return count; } /* 平方階(泡沫排序) */ function bubbleSort(nums: number[]): number { let count = 0; // 計數器 // 外迴圈:未排序區間為 [0, i] for (let i = nums.length - 1; i > 0; i--) { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 元素交換包含 3 個單元操作 } } } return count; } /* 指數階(迴圈實現) */ function exponential(n: number): number { let count = 0, base = 1; // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) for (let i = 0; i < n; i++) { for (let j = 0; j < base; j++) { count++; } base *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } /* 指數階(遞迴實現) */ function expRecur(n: number): number { if (n === 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 對數階(迴圈實現) */ function logarithmic(n: number): number { let count = 0; while (n > 1) { n = n / 2; count++; } return count; } /* 對數階(遞迴實現) */ function logRecur(n: number): number { if (n <= 1) return 0; return logRecur(n / 2) + 1; } /* 線性對數階 */ function linearLogRecur(n: number): number { if (n <= 1) return 1; let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (let i = 0; i < n; i++) { count++; } return count; } /* 階乘階(遞迴實現) */ function factorialRecur(n: number): number { if (n === 0) return 1; let count = 0; // 從 1 個分裂出 n 個 for (let i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } /* Driver Code */ // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 const n = 8; console.log('輸入資料大小 n = ' + n); let count = constant(n); console.log('常數階的操作數量 = ' + count); count = linear(n); console.log('線性階的操作數量 = ' + count); count = arrayTraversal(new Array(n)); console.log('線性階(走訪陣列)的操作數量 = ' + count); count = quadratic(n); console.log('平方階的操作數量 = ' + count); var nums = new Array(n); for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); console.log('平方階(泡沫排序)的操作數量 = ' + count); count = exponential(n); console.log('指數階(迴圈實現)的操作數量 = ' + count); count = expRecur(n); console.log('指數階(遞迴實現)的操作數量 = ' + count); count = logarithmic(n); console.log('對數階(迴圈實現)的操作數量 = ' + count); count = logRecur(n); console.log('對數階(遞迴實現)的操作數量 = ' + count); count = linearLogRecur(n); console.log('線性對數階(遞迴實現)的操作數量 = ' + count); count = factorialRecur(n); console.log('階乘階(遞迴實現)的操作數量 = ' + count); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts ================================================ /** * File: worst_best_time_complexity.ts * Created Time: 2023-01-05 * Author: RiverTwilight (contact@rene.wang) */ /* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ function randomNumbers(n: number): number[] { const nums = Array(n); // 生成陣列 nums = { 1, 2, 3, ..., n } for (let i = 0; i < n; i++) { nums[i] = i + 1; } // 隨機打亂陣列元素 for (let i = 0; i < n; i++) { const r = Math.floor(Math.random() * (i + 1)); const temp = nums[i]; nums[i] = nums[r]; nums[r] = temp; } return nums; } /* 查詢陣列 nums 中數字 1 所在索引 */ function findOne(nums: number[]): number { for (let i = 0; i < nums.length; i++) { // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) if (nums[i] === 1) { return i; } } return -1; } /* Driver Code */ for (let i = 0; i < 10; i++) { const n = 100; const nums = randomNumbers(n); const index = findOne(nums); console.log('\n陣列 [ 1, 2, ..., n ] 被打亂後 = [' + nums.join(', ') + ']'); console.log('數字 1 的索引為 ' + index); } export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts ================================================ /** * File: binary_search_recur.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 二分搜尋:問題 f(i, j) */ function dfs(nums: number[], target: number, i: number, j: number): number { // 若區間為空,代表無目標元素,則返回 -1 if (i > j) { return -1; } // 計算中點索引 m const m = i + ((j - i) >> 1); if (nums[m] < target) { // 遞迴子問題 f(m+1, j) return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { // 遞迴子問題 f(i, m-1) return dfs(nums, target, i, m - 1); } else { // 找到目標元素,返回其索引 return m; } } /* 二分搜尋 */ function binarySearch(nums: number[], target: number): number { const n = nums.length; // 求解問題 f(0, n-1) return dfs(nums, target, 0, n - 1); } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; // 二分搜尋(雙閉區間) const index = binarySearch(nums, target); console.log(`目標元素 6 的索引 = ${index}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_divide_and_conquer/build_tree.ts ================================================ /** * File: build_tree.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ import { printTree } from '../modules/PrintUtil'; import { TreeNode } from '../modules/TreeNode'; /* 構建二元樹:分治 */ function dfs( preorder: number[], inorderMap: Map, i: number, l: number, r: number ): TreeNode | null { // 子樹區間為空時終止 if (r - l < 0) return null; // 初始化根節點 const root: TreeNode = new TreeNode(preorder[i]); // 查詢 m ,從而劃分左右子樹 const m = inorderMap.get(preorder[i]); // 子問題:構建左子樹 root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); // 子問題:構建右子樹 root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); // 返回根節點 return root; } /* 構建二元樹 */ function buildTree(preorder: number[], inorder: number[]): TreeNode | null { // 初始化雜湊表,儲存 inorder 元素到索引的對映 let inorderMap = new Map(); for (let i = 0; i < inorder.length; i++) { inorderMap.set(inorder[i], i); } const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); return root; } /* Driver Code */ const preorder = [3, 9, 2, 1, 7]; const inorder = [9, 3, 1, 2, 7]; console.log('前序走訪 = ' + JSON.stringify(preorder)); console.log('中序走訪 = ' + JSON.stringify(inorder)); const root = buildTree(preorder, inorder); console.log('構建的二元樹為:'); printTree(root); ================================================ FILE: zh-hant/codes/typescript/chapter_divide_and_conquer/hanota.ts ================================================ /** * File: hanota.ts * Created Time: 2023-07-30 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 移動一個圓盤 */ function move(src: number[], tar: number[]): void { // 從 src 頂部拿出一個圓盤 const pan = src.pop(); // 將圓盤放入 tar 頂部 tar.push(pan); } /* 求解河內塔問題 f(i) */ function dfs(i: number, src: number[], buf: number[], tar: number[]): void { // 若 src 只剩下一個圓盤,則直接將其移到 tar if (i === 1) { move(src, tar); return; } // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf dfs(i - 1, src, tar, buf); // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar move(src, tar); // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar dfs(i - 1, buf, src, tar); } /* 求解河內塔問題 */ function solveHanota(A: number[], B: number[], C: number[]): void { const n = A.length; // 將 A 頂部 n 個圓盤藉助 B 移到 C dfs(n, A, B, C); } /* Driver Code */ // 串列尾部是柱子頂部 const A = [5, 4, 3, 2, 1]; const B = []; const C = []; console.log('初始狀態下:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); solveHanota(A, B, C); console.log('圓盤移動完成後:'); console.log(`A = ${JSON.stringify(A)}`); console.log(`B = ${JSON.stringify(B)}`); console.log(`C = ${JSON.stringify(C)}`); ================================================ FILE: zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts ================================================ /** * File: climbing_stairs_backtrack.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 回溯 */ function backtrack( choices: number[], state: number, n: number, res: Map<0, any> ): void { // 當爬到第 n 階時,方案數量加 1 if (state === n) res.set(0, res.get(0) + 1); // 走訪所有選擇 for (const choice of choices) { // 剪枝:不允許越過第 n 階 if (state + choice > n) continue; // 嘗試:做出選擇,更新狀態 backtrack(choices, state + choice, n, res); // 回退 } } /* 爬樓梯:回溯 */ function climbingStairsBacktrack(n: number): number { const choices = [1, 2]; // 可選擇向上爬 1 階或 2 階 const state = 0; // 從第 0 階開始爬 const res = new Map(); res.set(0, 0); // 使用 res[0] 記錄方案數量 backtrack(choices, state, n, res); return res.get(0); } /* Driver Code */ const n = 9; const res = climbingStairsBacktrack(n); console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts ================================================ /** * File: climbing_stairs_constraint_dp.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 帶約束爬樓梯:動態規劃 */ function climbingStairsConstraintDP(n: number): number { if (n === 1 || n === 2) { return 1; } // 初始化 dp 表,用於儲存子問題的解 const dp = Array.from({ length: n + 1 }, () => new Array(3)); // 初始狀態:預設最小子問題的解 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 狀態轉移:從較小子問題逐步求解較大子問題 for (let i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } /* Driver Code */ const n = 9; const res = climbingStairsConstraintDP(n); console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts ================================================ /** * File: climbing_stairs_dfs.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 搜尋 */ function dfs(i: number): number { // 已知 dp[1] 和 dp[2] ,返回之 if (i === 1 || i === 2) return i; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1) + dfs(i - 2); return count; } /* 爬樓梯:搜尋 */ function climbingStairsDFS(n: number): number { return dfs(n); } /* Driver Code */ const n = 9; const res = climbingStairsDFS(n); console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts ================================================ /** * File: climbing_stairs_dfs_mem.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 記憶化搜尋 */ function dfs(i: number, mem: number[]): number { // 已知 dp[1] 和 dp[2] ,返回之 if (i === 1 || i === 2) return i; // 若存在記錄 dp[i] ,則直接返回之 if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] const count = dfs(i - 1, mem) + dfs(i - 2, mem); // 記錄 dp[i] mem[i] = count; return count; } /* 爬樓梯:記憶化搜尋 */ function climbingStairsDFSMem(n: number): number { // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 const mem = new Array(n + 1).fill(-1); return dfs(n, mem); } /* Driver Code */ const n = 9; const res = climbingStairsDFSMem(n); console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts ================================================ /** * File: climbing_stairs_dp.ts * Created Time: 2023-07-26 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 爬樓梯:動態規劃 */ function climbingStairsDP(n: number): number { if (n === 1 || n === 2) return n; // 初始化 dp 表,用於儲存子問題的解 const dp = new Array(n + 1).fill(-1); // 初始狀態:預設最小子問題的解 dp[1] = 1; dp[2] = 2; // 狀態轉移:從較小子問題逐步求解較大子問題 for (let i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } /* 爬樓梯:空間最佳化後的動態規劃 */ function climbingStairsDPComp(n: number): number { if (n === 1 || n === 2) return n; let a = 1, b = 2; for (let i = 3; i <= n; i++) { const tmp = b; b = a + b; a = tmp; } return b; } /* Driver Code */ const n = 9; let res = climbingStairsDP(n); console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); res = climbingStairsDPComp(n); console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_dynamic_programming/coin_change.ts ================================================ /** * File: coin_change.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 零錢兌換:動態規劃 */ function coinChangeDP(coins: Array, amt: number): number { const n = coins.length; const MAX = amt + 1; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 狀態轉移:首行首列 for (let a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 狀態轉移:其餘行和列 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] !== MAX ? dp[n][amt] : -1; } /* 零錢兌換:空間最佳化後的動態規劃 */ function coinChangeDPComp(coins: Array, amt: number): number { const n = coins.length; const MAX = amt + 1; // 初始化 dp 表 const dp = Array.from({ length: amt + 1 }, () => MAX); dp[0] = 0; // 狀態轉移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] !== MAX ? dp[amt] : -1; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 4; // 動態規劃 let res = coinChangeDP(coins, amt); console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); // 空間最佳化後的動態規劃 res = coinChangeDPComp(coins, amt); console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts ================================================ /** * File: coin_change_ii.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 零錢兌換 II:動態規劃 */ function coinChangeIIDP(coins: Array, amt: number): number { const n = coins.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 初始化首列 for (let i = 0; i <= n; i++) { dp[i][0] = 1; } // 狀態轉移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a]; } else { // 不選和選硬幣 i 這兩種方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } /* 零錢兌換 II:空間最佳化後的動態規劃 */ function coinChangeIIDPComp(coins: Array, amt: number): number { const n = coins.length; // 初始化 dp 表 const dp = Array.from({ length: amt + 1 }, () => 0); dp[0] = 1; // 狀態轉移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } /* Driver Code */ const coins = [1, 2, 5]; const amt = 5; // 動態規劃 let res = coinChangeIIDP(coins, amt); console.log(`湊出目標金額的硬幣組合數量為 ${res}`); // 空間最佳化後的動態規劃 res = coinChangeIIDPComp(coins, amt); console.log(`湊出目標金額的硬幣組合數量為 ${res}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_dynamic_programming/edit_distance.ts ================================================ /** * File: edit_distance.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 編輯距離:暴力搜尋 */ function editDistanceDFS(s: string, t: string, i: number, j: number): number { // 若 s 和 t 都為空,則返回 0 if (i === 0 && j === 0) return 0; // 若 s 為空,則返回 t 長度 if (i === 0) return j; // 若 t 為空,則返回 s 長度 if (j === 0) return i; // 若兩字元相等,則直接跳過此兩字元 if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 const insert = editDistanceDFS(s, t, i, j - 1); const del = editDistanceDFS(s, t, i - 1, j); const replace = editDistanceDFS(s, t, i - 1, j - 1); // 返回最少編輯步數 return Math.min(insert, del, replace) + 1; } /* 編輯距離:記憶化搜尋 */ function editDistanceDFSMem( s: string, t: string, mem: Array>, i: number, j: number ): number { // 若 s 和 t 都為空,則返回 0 if (i === 0 && j === 0) return 0; // 若 s 為空,則返回 t 長度 if (i === 0) return j; // 若 t 為空,則返回 s 長度 if (j === 0) return i; // 若已有記錄,則直接返回之 if (mem[i][j] !== -1) return mem[i][j]; // 若兩字元相等,則直接跳過此兩字元 if (s.charAt(i - 1) === t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 const insert = editDistanceDFSMem(s, t, mem, i, j - 1); const del = editDistanceDFSMem(s, t, mem, i - 1, j); const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 記錄並返回最少編輯步數 mem[i][j] = Math.min(insert, del, replace) + 1; return mem[i][j]; } /* 編輯距離:動態規劃 */ function editDistanceDP(s: string, t: string): number { const n = s.length, m = t.length; const dp = Array.from({ length: n + 1 }, () => Array.from({ length: m + 1 }, () => 0) ); // 狀態轉移:首行首列 for (let i = 1; i <= n; i++) { dp[i][0] = i; } for (let j = 1; j <= m; j++) { dp[0][j] = j; } // 狀態轉移:其餘行和列 for (let i = 1; i <= n; i++) { for (let j = 1; j <= m; j++) { if (s.charAt(i - 1) === t.charAt(j - 1)) { // 若兩字元相等,則直接跳過此兩字元 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } /* 編輯距離:空間最佳化後的動態規劃 */ function editDistanceDPComp(s: string, t: string): number { const n = s.length, m = t.length; const dp = new Array(m + 1).fill(0); // 狀態轉移:首行 for (let j = 1; j <= m; j++) { dp[j] = j; } // 狀態轉移:其餘行 for (let i = 1; i <= n; i++) { // 狀態轉移:首列 let leftup = dp[0]; // 暫存 dp[i-1, j-1] dp[0] = i; // 狀態轉移:其餘列 for (let j = 1; j <= m; j++) { const temp = dp[j]; if (s.charAt(i - 1) === t.charAt(j - 1)) { // 若兩字元相等,則直接跳過此兩字元 dp[j] = leftup; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; } leftup = temp; // 更新為下一輪的 dp[i-1, j-1] } } return dp[m]; } /* Driver Code */ const s = 'bag'; const t = 'pack'; const n = s.length, m = t.length; // 暴力搜尋 let res = editDistanceDFS(s, t, n, m); console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); // 記憶化搜尋 const mem = Array.from({ length: n + 1 }, () => Array.from({ length: m + 1 }, () => -1) ); res = editDistanceDFSMem(s, t, mem, n, m); console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); // 動態規劃 res = editDistanceDP(s, t); console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); // 空間最佳化後的動態規劃 res = editDistanceDPComp(s, t); console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_dynamic_programming/knapsack.ts ================================================ /** * File: knapsack.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 0-1 背包:暴力搜尋 */ function knapsackDFS( wgt: Array, val: Array, i: number, c: number ): number { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i === 0 || c === 0) { return 0; } // 若超過背包容量,則只能選擇不放入背包 if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 const no = knapsackDFS(wgt, val, i - 1, c); const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; // 返回兩種方案中價值更大的那一個 return Math.max(no, yes); } /* 0-1 背包:記憶化搜尋 */ function knapsackDFSMem( wgt: Array, val: Array, mem: Array>, i: number, c: number ): number { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i === 0 || c === 0) { return 0; } // 若已有記錄,則直接返回 if (mem[i][c] !== -1) { return mem[i][c]; } // 若超過背包容量,則只能選擇不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 const no = knapsackDFSMem(wgt, val, mem, i - 1, c); const yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; // 記錄並返回兩種方案中價值更大的那一個 mem[i][c] = Math.max(no, yes); return mem[i][c]; } /* 0-1 背包:動態規劃 */ function knapsackDP( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // 狀態轉移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = Math.max( dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* 0-1 背包:空間最佳化後的動態規劃 */ function knapsackDPComp( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // 初始化 dp 表 const dp = Array(cap + 1).fill(0); // 狀態轉移 for (let i = 1; i <= n; i++) { // 倒序走訪 for (let c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { // 不選和選物品 i 這兩種方案的較大值 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [10, 20, 30, 40, 50]; const val = [50, 120, 150, 210, 240]; const cap = 50; const n = wgt.length; // 暴力搜尋 let res = knapsackDFS(wgt, val, n, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); // 記憶化搜尋 const mem = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => -1) ); res = knapsackDFSMem(wgt, val, mem, n, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); // 動態規劃 res = knapsackDP(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); // 空間最佳化後的動態規劃 res = knapsackDPComp(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts ================================================ /** * File: min_cost_climbing_stairs_dp.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 爬樓梯最小代價:動態規劃 */ function minCostClimbingStairsDP(cost: Array): number { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } // 初始化 dp 表,用於儲存子問題的解 const dp = new Array(n + 1); // 初始狀態:預設最小子問題的解 dp[1] = cost[1]; dp[2] = cost[2]; // 狀態轉移:從較小子問題逐步求解較大子問題 for (let i = 3; i <= n; i++) { dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } /* 爬樓梯最小代價:空間最佳化後的動態規劃 */ function minCostClimbingStairsDPComp(cost: Array): number { const n = cost.length - 1; if (n === 1 || n === 2) { return cost[n]; } let a = cost[1], b = cost[2]; for (let i = 3; i <= n; i++) { const tmp = b; b = Math.min(a, tmp) + cost[i]; a = tmp; } return b; } /* Driver Code */ const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; console.log(`輸入樓梯的代價串列為:${cost}`); let res = minCostClimbingStairsDP(cost); console.log(`爬完樓梯的最低代價為:${res}`); res = minCostClimbingStairsDPComp(cost); console.log(`爬完樓梯的最低代價為:${res}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_dynamic_programming/min_path_sum.ts ================================================ /** * File: min_path_sum.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 最小路徑和:暴力搜尋 */ function minPathSumDFS( grid: Array>, i: number, j: number ): number { // 若為左上角單元格,則終止搜尋 if (i === 0 && j == 0) { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 || j < 0) { return Infinity; } // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 const up = minPathSumDFS(grid, i - 1, j); const left = minPathSumDFS(grid, i, j - 1); // 返回從左上角到 (i, j) 的最小路徑代價 return Math.min(left, up) + grid[i][j]; } /* 最小路徑和:記憶化搜尋 */ function minPathSumDFSMem( grid: Array>, mem: Array>, i: number, j: number ): number { // 若為左上角單元格,則終止搜尋 if (i === 0 && j === 0) { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 || j < 0) { return Infinity; } // 若已有記錄,則直接返回 if (mem[i][j] != -1) { return mem[i][j]; } // 左邊和上邊單元格的最小路徑代價 const up = minPathSumDFSMem(grid, mem, i - 1, j); const left = minPathSumDFSMem(grid, mem, i, j - 1); // 記錄並返回左上角到 (i, j) 的最小路徑代價 mem[i][j] = Math.min(left, up) + grid[i][j]; return mem[i][j]; } /* 最小路徑和:動態規劃 */ function minPathSumDP(grid: Array>): number { const n = grid.length, m = grid[0].length; // 初始化 dp 表 const dp = Array.from({ length: n }, () => Array.from({ length: m }, () => 0) ); dp[0][0] = grid[0][0]; // 狀態轉移:首行 for (let j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 狀態轉移:首列 for (let i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 狀態轉移:其餘行和列 for (let i = 1; i < n; i++) { for (let j: number = 1; j < m; j++) { dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } /* 最小路徑和:空間最佳化後的動態規劃 */ function minPathSumDPComp(grid: Array>): number { const n = grid.length, m = grid[0].length; // 初始化 dp 表 const dp = new Array(m); // 狀態轉移:首行 dp[0] = grid[0][0]; for (let j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 狀態轉移:其餘行 for (let i = 1; i < n; i++) { // 狀態轉移:首列 dp[0] = dp[0] + grid[i][0]; // 狀態轉移:其餘列 for (let j = 1; j < m; j++) { dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } /* Driver Code */ const grid = [ [1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2], ]; const n = grid.length, m = grid[0].length; // 暴力搜尋 let res = minPathSumDFS(grid, n - 1, m - 1); console.log(`從左上角到右下角的最小路徑和為 ${res}`); // 記憶化搜尋 const mem = Array.from({ length: n }, () => Array.from({ length: m }, () => -1) ); res = minPathSumDFSMem(grid, mem, n - 1, m - 1); console.log(`從左上角到右下角的最小路徑和為 ${res}`); // 動態規劃 res = minPathSumDP(grid); console.log(`從左上角到右下角的最小路徑和為 ${res}`); // 空間最佳化後的動態規劃 res = minPathSumDPComp(grid); console.log(`從左上角到右下角的最小路徑和為 ${res}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts ================================================ /** * File: unbounded_knapsack.ts * Created Time: 2023-08-23 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 完全背包:動態規劃 */ function unboundedKnapsackDP( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // 狀態轉移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = Math.max( dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } /* 完全背包:空間最佳化後的動態規劃 */ function unboundedKnapsackDPComp( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: cap + 1 }, () => 0); // 狀態轉移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[c] = dp[c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } /* Driver Code */ const wgt = [1, 2, 3]; const val = [5, 11, 15]; const cap = 4; // 動態規劃 let res = unboundedKnapsackDP(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); // 空間最佳化後的動態規劃 res = unboundedKnapsackDPComp(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_graph/graph_adjacency_list.ts ================================================ /** * File: graph_adjacency_list.ts * Created Time: 2023-02-09 * Author: Justin (xiefahit@gmail.com) */ import { Vertex } from '../modules/Vertex'; /* 基於鄰接表實現的無向圖類別 */ class GraphAdjList { // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 adjList: Map; /* 建構子 */ constructor(edges: Vertex[][]) { this.adjList = new Map(); // 新增所有頂點和邊 for (const edge of edges) { this.addVertex(edge[0]); this.addVertex(edge[1]); this.addEdge(edge[0], edge[1]); } } /* 獲取頂點數量 */ size(): number { return this.adjList.size; } /* 新增邊 */ addEdge(vet1: Vertex, vet2: Vertex): void { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 ) { throw new Error('Illegal Argument Exception'); } // 新增邊 vet1 - vet2 this.adjList.get(vet1).push(vet2); this.adjList.get(vet2).push(vet1); } /* 刪除邊 */ removeEdge(vet1: Vertex, vet2: Vertex): void { if ( !this.adjList.has(vet1) || !this.adjList.has(vet2) || vet1 === vet2 || this.adjList.get(vet1).indexOf(vet2) === -1 ) { throw new Error('Illegal Argument Exception'); } // 刪除邊 vet1 - vet2 this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); } /* 新增頂點 */ addVertex(vet: Vertex): void { if (this.adjList.has(vet)) return; // 在鄰接表中新增一個新鏈結串列 this.adjList.set(vet, []); } /* 刪除頂點 */ removeVertex(vet: Vertex): void { if (!this.adjList.has(vet)) { throw new Error('Illegal Argument Exception'); } // 在鄰接表中刪除頂點 vet 對應的鏈結串列 this.adjList.delete(vet); // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 for (const set of this.adjList.values()) { const index: number = set.indexOf(vet); if (index > -1) { set.splice(index, 1); } } } /* 列印鄰接表 */ print(): void { console.log('鄰接表 ='); for (const [key, value] of this.adjList.entries()) { const tmp = []; for (const vertex of value) { tmp.push(vertex.val); } console.log(key.val + ': ' + tmp.join()); } } } /* Driver Code */ if (import.meta.url.endsWith(process.argv[1])) { /* 初始化無向圖 */ const v0 = new Vertex(1), v1 = new Vertex(3), v2 = new Vertex(2), v3 = new Vertex(5), v4 = new Vertex(4); const edges = [ [v0, v1], [v1, v2], [v2, v3], [v0, v3], [v2, v4], [v3, v4], ]; const graph = new GraphAdjList(edges); console.log('\n初始化後,圖為'); graph.print(); /* 新增邊 */ // 頂點 1, 2 即 v0, v2 graph.addEdge(v0, v2); console.log('\n新增邊 1-2 後,圖為'); graph.print(); /* 刪除邊 */ // 頂點 1, 3 即 v0, v1 graph.removeEdge(v0, v1); console.log('\n刪除邊 1-3 後,圖為'); graph.print(); /* 新增頂點 */ const v5 = new Vertex(6); graph.addVertex(v5); console.log('\n新增頂點 6 後,圖為'); graph.print(); /* 刪除頂點 */ // 頂點 3 即 v1 graph.removeVertex(v1); console.log('\n刪除頂點 3 後,圖為'); graph.print(); } export { GraphAdjList }; ================================================ FILE: zh-hant/codes/typescript/chapter_graph/graph_adjacency_matrix.ts ================================================ /** * File: graph_adjacency_matrix.ts * Created Time: 2023-02-09 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 基於鄰接矩陣實現的無向圖類別 */ class GraphAdjMat { vertices: number[]; // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” adjMat: number[][]; // 鄰接矩陣,行列索引對應“頂點索引” /* 建構子 */ constructor(vertices: number[], edges: number[][]) { this.vertices = []; this.adjMat = []; // 新增頂點 for (const val of vertices) { this.addVertex(val); } // 新增邊 // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 for (const e of edges) { this.addEdge(e[0], e[1]); } } /* 獲取頂點數量 */ size(): number { return this.vertices.length; } /* 新增頂點 */ addVertex(val: number): void { const n: number = this.size(); // 向頂點串列中新增新頂點的值 this.vertices.push(val); // 在鄰接矩陣中新增一行 const newRow: number[] = []; for (let j: number = 0; j < n; j++) { newRow.push(0); } this.adjMat.push(newRow); // 在鄰接矩陣中新增一列 for (const row of this.adjMat) { row.push(0); } } /* 刪除頂點 */ removeVertex(index: number): void { if (index >= this.size()) { throw new RangeError('Index Out Of Bounds Exception'); } // 在頂點串列中移除索引 index 的頂點 this.vertices.splice(index, 1); // 在鄰接矩陣中刪除索引 index 的行 this.adjMat.splice(index, 1); // 在鄰接矩陣中刪除索引 index 的列 for (const row of this.adjMat) { row.splice(index, 1); } } /* 新增邊 */ // 參數 i, j 對應 vertices 元素索引 addEdge(i: number, j: number): void { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) === (j, i) this.adjMat[i][j] = 1; this.adjMat[j][i] = 1; } /* 刪除邊 */ // 參數 i, j 對應 vertices 元素索引 removeEdge(i: number, j: number): void { // 索引越界與相等處理 if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { throw new RangeError('Index Out Of Bounds Exception'); } this.adjMat[i][j] = 0; this.adjMat[j][i] = 0; } /* 列印鄰接矩陣 */ print(): void { console.log('頂點串列 = ', this.vertices); console.log('鄰接矩陣 =', this.adjMat); } } /* Driver Code */ /* 初始化無向圖 */ // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 const vertices: number[] = [1, 3, 2, 5, 4]; const edges: number[][] = [ [0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4], ]; const graph: GraphAdjMat = new GraphAdjMat(vertices, edges); console.log('\n初始化後,圖為'); graph.print(); /* 新增邊 */ // 頂點 1, 2 的索引分別為 0, 2 graph.addEdge(0, 2); console.log('\n新增邊 1-2 後,圖為'); graph.print(); /* 刪除邊 */ // 頂點 1, 3 的索引分別為 0, 1 graph.removeEdge(0, 1); console.log('\n刪除邊 1-3 後,圖為'); graph.print(); /* 新增頂點 */ graph.addVertex(6); console.log('\n新增頂點 6 後,圖為'); graph.print(); /* 刪除頂點 */ // 頂點 3 的索引為 1 graph.removeVertex(1); console.log('\n刪除頂點 3 後,圖為'); graph.print(); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_graph/graph_bfs.ts ================================================ /** * File: graph_bfs.ts * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { GraphAdjList } from './graph_adjacency_list'; import { Vertex } from '../modules/Vertex'; /* 廣度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { // 頂點走訪序列 const res: Vertex[] = []; // 雜湊集合,用於記錄已被訪問過的頂點 const visited: Set = new Set(); visited.add(startVet); // 佇列用於實現 BFS const que = [startVet]; // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 while (que.length) { const vet = que.shift(); // 佇列首頂點出隊 res.push(vet); // 記錄訪問頂點 // 走訪該頂點的所有鄰接頂點 for (const adjVet of graph.adjList.get(vet) ?? []) { if (visited.has(adjVet)) { continue; // 跳過已被訪問的頂點 } que.push(adjVet); // 只入列未訪問 visited.add(adjVet); // 標記該頂點已被訪問 } } // 返回頂點走訪序列 return res; } /* Driver Code */ /* 初始化無向圖 */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], ]; const graph = new GraphAdjList(edges); console.log('\n初始化後,圖為'); graph.print(); /* 廣度優先走訪 */ const res = graphBFS(graph, v[0]); console.log('\n廣度優先走訪(BFS)頂點序列為'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: zh-hant/codes/typescript/chapter_graph/graph_dfs.ts ================================================ /** * File: graph_dfs.ts * Created Time: 2023-02-21 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { Vertex } from '../modules/Vertex'; import { GraphAdjList } from './graph_adjacency_list'; /* 深度優先走訪輔助函式 */ function dfs( graph: GraphAdjList, visited: Set, res: Vertex[], vet: Vertex ): void { res.push(vet); // 記錄訪問頂點 visited.add(vet); // 標記該頂點已被訪問 // 走訪該頂點的所有鄰接頂點 for (const adjVet of graph.adjList.get(vet)) { if (visited.has(adjVet)) { continue; // 跳過已被訪問的頂點 } // 遞迴訪問鄰接頂點 dfs(graph, visited, res, adjVet); } } /* 深度優先走訪 */ // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { // 頂點走訪序列 const res: Vertex[] = []; // 雜湊集合,用於記錄已被訪問過的頂點 const visited: Set = new Set(); dfs(graph, visited, res, startVet); return res; } /* Driver Code */ /* 初始化無向圖 */ const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); const edges = [ [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], ]; const graph = new GraphAdjList(edges); console.log('\n初始化後,圖為'); graph.print(); /* 深度優先走訪 */ const res = graphDFS(graph, v[0]); console.log('\n深度優先走訪(DFS)頂點序列為'); console.log(Vertex.vetsToVals(res)); ================================================ FILE: zh-hant/codes/typescript/chapter_greedy/coin_change_greedy.ts ================================================ /** * File: coin_change_greedy.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 零錢兌換:貪婪 */ function coinChangeGreedy(coins: number[], amt: number): number { // 假設 coins 陣列有序 let i = coins.length - 1; let count = 0; // 迴圈進行貪婪選擇,直到無剩餘金額 while (amt > 0) { // 找到小於且最接近剩餘金額的硬幣 while (i > 0 && coins[i] > amt) { i--; } // 選擇 coins[i] amt -= coins[i]; count++; } // 若未找到可行方案,則返回 -1 return amt === 0 ? count : -1; } /* Driver Code */ // 貪婪:能夠保證找到全域性最優解 let coins: number[] = [1, 5, 10, 20, 50, 100]; let amt: number = 186; let res: number = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); // 貪婪:無法保證找到全域性最優解 coins = [1, 20, 50]; amt = 60; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); console.log('實際上需要的最少數量為 3 ,即 20 + 20 + 20'); // 貪婪:無法保證找到全域性最優解 coins = [1, 49, 50]; amt = 98; res = coinChangeGreedy(coins, amt); console.log(`\ncoins = ${coins}, amt = ${amt}`); console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); console.log('實際上需要的最少數量為 2 ,即 49 + 49'); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_greedy/fractional_knapsack.ts ================================================ /** * File: fractional_knapsack.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 物品 */ class Item { w: number; // 物品重量 v: number; // 物品價值 constructor(w: number, v: number) { this.w = w; this.v = v; } } /* 分數背包:貪婪 */ function fractionalKnapsack(wgt: number[], val: number[], cap: number): number { // 建立物品串列,包含兩個屬性:重量、價值 const items: Item[] = wgt.map((w, i) => new Item(w, val[i])); // 按照單位價值 item.v / item.w 從高到低進行排序 items.sort((a, b) => b.v / b.w - a.v / a.w); // 迴圈貪婪選擇 let res = 0; for (const item of items) { if (item.w <= cap) { // 若剩餘容量充足,則將當前物品整個裝進背包 res += item.v; cap -= item.w; } else { // 若剩餘容量不足,則將當前物品的一部分裝進背包 res += (item.v / item.w) * cap; // 已無剩餘容量,因此跳出迴圈 break; } } return res; } /* Driver Code */ const wgt: number[] = [10, 20, 30, 40, 50]; const val: number[] = [50, 120, 150, 210, 240]; const cap: number = 50; // 貪婪演算法 const res: number = fractionalKnapsack(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_greedy/max_capacity.ts ================================================ /** * File: max_capacity.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 最大容量:貪婪 */ function maxCapacity(ht: number[]): number { // 初始化 i, j,使其分列陣列兩端 let i = 0, j = ht.length - 1; // 初始最大容量為 0 let res = 0; // 迴圈貪婪選擇,直至兩板相遇 while (i < j) { // 更新最大容量 const cap: number = Math.min(ht[i], ht[j]) * (j - i); res = Math.max(res, cap); // 向內移動短板 if (ht[i] < ht[j]) { i += 1; } else { j -= 1; } } return res; } /* Driver Code */ const ht: number[] = [3, 8, 5, 2, 7, 7, 3, 4]; // 貪婪演算法 const res: number = maxCapacity(ht); console.log(`最大容量為 ${res}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_greedy/max_product_cutting.ts ================================================ /** * File: max_product_cutting.ts * Created Time: 2023-09-02 * Author: Justin (xiefahit@gmail.com) */ /* 最大切分乘積:貪婪 */ function maxProductCutting(n: number): number { // 當 n <= 3 時,必須切分出一個 1 if (n <= 3) { return 1 * (n - 1); } // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 let a: number = Math.floor(n / 3); let b: number = n % 3; if (b === 1) { // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 return Math.pow(3, a - 1) * 2 * 2; } if (b === 2) { // 當餘數為 2 時,不做處理 return Math.pow(3, a) * 2; } // 當餘數為 0 時,不做處理 return Math.pow(3, a); } /* Driver Code */ let n: number = 58; // 貪婪演算法 let res: number = maxProductCutting(n); console.log(`最大切分乘積為 ${res}`); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_hashing/array_hash_map.ts ================================================ /** * File: array_hash_map.ts * Created Time: 2022-12-20 * Author: Daniel (better.sunjian@gmail.com) */ /* 鍵值對 Number -> String */ class Pair { public key: number; public val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* 基於陣列實現的雜湊表 */ class ArrayHashMap { private readonly buckets: (Pair | null)[]; constructor() { // 初始化陣列,包含 100 個桶 this.buckets = new Array(100).fill(null); } /* 雜湊函式 */ private hashFunc(key: number): number { return key % 100; } /* 查詢操作 */ public get(key: number): string | null { let index = this.hashFunc(key); let pair = this.buckets[index]; if (pair === null) return null; return pair.val; } /* 新增操作 */ public set(key: number, val: string) { let index = this.hashFunc(key); this.buckets[index] = new Pair(key, val); } /* 刪除操作 */ public delete(key: number) { let index = this.hashFunc(key); // 置為 null ,代表刪除 this.buckets[index] = null; } /* 獲取所有鍵值對 */ public entries(): (Pair | null)[] { let arr: (Pair | null)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i]); } } return arr; } /* 獲取所有鍵 */ public keys(): (number | undefined)[] { let arr: (number | undefined)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i].key); } } return arr; } /* 獲取所有值 */ public values(): (string | undefined)[] { let arr: (string | undefined)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i].val); } } return arr; } /* 列印雜湊表 */ public print() { let pairSet = this.entries(); for (const pair of pairSet) { console.info(`${pair.key} -> ${pair.val}`); } } } /* Driver Code */ /* 初始化雜湊表 */ const map = new ArrayHashMap(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.set(12836, '小哈'); map.set(15937, '小囉'); map.set(16750, '小算'); map.set(13276, '小法'); map.set(10583, '小鴨'); console.info('\n新增完成後,雜湊表為\nKey -> Value'); map.print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value let name = map.get(15937); console.info('\n輸入學號 15937 ,查詢到姓名 ' + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.delete(10583); console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); map.print(); /* 走訪雜湊表 */ console.info('\n走訪鍵值對 Key->Value'); for (const pair of map.entries()) { if (!pair) continue; console.info(pair.key + ' -> ' + pair.val); } console.info('\n單獨走訪鍵 Key'); for (const key of map.keys()) { console.info(key); } console.info('\n單獨走訪值 Value'); for (const val of map.values()) { console.info(val); } export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_hashing/hash_map.ts ================================================ /** * File: hash_map.ts * Created Time: 2022-12-20 * Author: Daniel (better.sunjian@gmail.com) */ /* Driver Code */ /* 初始化雜湊表 */ const map = new Map(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.set(12836, '小哈'); map.set(15937, '小囉'); map.set(16750, '小算'); map.set(13276, '小法'); map.set(10583, '小鴨'); console.info('\n新增完成後,雜湊表為\nKey -> Value'); console.info(map); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value let name = map.get(15937); console.info('\n輸入學號 15937 ,查詢到姓名 ' + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.delete(10583); console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); console.info(map); /* 走訪雜湊表 */ console.info('\n走訪鍵值對 Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\n單獨走訪鍵 Key'); for (const k of map.keys()) { console.info(k); } console.info('\n單獨走訪值 Value'); for (const v of map.values()) { console.info(v); } export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_hashing/hash_map_chaining.ts ================================================ /** * File: hash_map_chaining.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 鍵值對 Number -> String */ class Pair { key: number; val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* 鏈式位址雜湊表 */ class HashMapChaining { #size: number; // 鍵值對數量 #capacity: number; // 雜湊表容量 #loadThres: number; // 觸發擴容的負載因子閾值 #extendRatio: number; // 擴容倍數 #buckets: Pair[][]; // 桶陣列 /* 建構子 */ constructor() { this.#size = 0; this.#capacity = 4; this.#loadThres = 2.0 / 3.0; this.#extendRatio = 2; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); } /* 雜湊函式 */ #hashFunc(key: number): number { return key % this.#capacity; } /* 負載因子 */ #loadFactor(): number { return this.#size / this.#capacity; } /* 查詢操作 */ get(key: number): string | null { const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // 走訪桶,若找到 key ,則返回對應 val for (const pair of bucket) { if (pair.key === key) { return pair.val; } } // 若未找到 key ,則返回 null return null; } /* 新增操作 */ put(key: number, val: string): void { // 當負載因子超過閾值時,執行擴容 if (this.#loadFactor() > this.#loadThres) { this.#extend(); } const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for (const pair of bucket) { if (pair.key === key) { pair.val = val; return; } } // 若無該 key ,則將鍵值對新增至尾部 const pair = new Pair(key, val); bucket.push(pair); this.#size++; } /* 刪除操作 */ remove(key: number): void { const index = this.#hashFunc(key); let bucket = this.#buckets[index]; // 走訪桶,從中刪除鍵值對 for (let i = 0; i < bucket.length; i++) { if (bucket[i].key === key) { bucket.splice(i, 1); this.#size--; break; } } } /* 擴容雜湊表 */ #extend(): void { // 暫存原雜湊表 const bucketsTmp = this.#buckets; // 初始化擴容後的新雜湊表 this.#capacity *= this.#extendRatio; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); this.#size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (const bucket of bucketsTmp) { for (const pair of bucket) { this.put(pair.key, pair.val); } } } /* 列印雜湊表 */ print(): void { for (const bucket of this.#buckets) { let res = []; for (const pair of bucket) { res.push(pair.key + ' -> ' + pair.val); } console.log(res); } } } /* Driver Code */ /* 初始化雜湊表 */ const map = new HashMapChaining(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(12836, '小哈'); map.put(15937, '小囉'); map.put(16750, '小算'); map.put(13276, '小法'); map.put(10583, '小鴨'); console.log('\n新增完成後,雜湊表為\nKey -> Value'); map.print(); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value const name = map.get(13276); console.log('\n輸入學號 13276 ,查詢到姓名 ' + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(12836); console.log('\n刪除 12836 後,雜湊表為\nKey -> Value'); map.print(); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_hashing/hash_map_open_addressing.ts ================================================ /** * File: hash_map_open_addressing.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) */ /* 鍵值對 Number -> String */ class Pair { key: number; val: string; constructor(key: number, val: string) { this.key = key; this.val = val; } } /* 開放定址雜湊表 */ class HashMapOpenAddressing { private size: number; // 鍵值對數量 private capacity: number; // 雜湊表容量 private loadThres: number; // 觸發擴容的負載因子閾值 private extendRatio: number; // 擴容倍數 private buckets: Array; // 桶陣列 private TOMBSTONE: Pair; // 刪除標記 /* 建構子 */ constructor() { this.size = 0; // 鍵值對數量 this.capacity = 4; // 雜湊表容量 this.loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 this.extendRatio = 2; // 擴容倍數 this.buckets = Array(this.capacity).fill(null); // 桶陣列 this.TOMBSTONE = new Pair(-1, '-1'); // 刪除標記 } /* 雜湊函式 */ private hashFunc(key: number): number { return key % this.capacity; } /* 負載因子 */ private loadFactor(): number { return this.size / this.capacity; } /* 搜尋 key 對應的桶索引 */ private findBucket(key: number): number { let index = this.hashFunc(key); let firstTombstone = -1; // 線性探查,當遇到空桶時跳出 while (this.buckets[index] !== null) { // 若遇到 key ,返回對應的桶索引 if (this.buckets[index]!.key === key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone !== -1) { this.buckets[firstTombstone] = this.buckets[index]; this.buckets[index] = this.TOMBSTONE; return firstTombstone; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if ( firstTombstone === -1 && this.buckets[index] === this.TOMBSTONE ) { firstTombstone = index; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % this.capacity; } // 若 key 不存在,則返回新增點的索引 return firstTombstone === -1 ? index : firstTombstone; } /* 查詢操作 */ get(key: number): string | null { // 搜尋 key 對應的桶索引 const index = this.findBucket(key); // 若找到鍵值對,則返回對應 val if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { return this.buckets[index]!.val; } // 若鍵值對不存在,則返回 null return null; } /* 新增操作 */ put(key: number, val: string): void { // 當負載因子超過閾值時,執行擴容 if (this.loadFactor() > this.loadThres) { this.extend(); } // 搜尋 key 對應的桶索引 const index = this.findBucket(key); // 若找到鍵值對,則覆蓋 val 並返回 if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { this.buckets[index]!.val = val; return; } // 若鍵值對不存在,則新增該鍵值對 this.buckets[index] = new Pair(key, val); this.size++; } /* 刪除操作 */ remove(key: number): void { // 搜尋 key 對應的桶索引 const index = this.findBucket(key); // 若找到鍵值對,則用刪除標記覆蓋它 if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { this.buckets[index] = this.TOMBSTONE; this.size--; } } /* 擴容雜湊表 */ private extend(): void { // 暫存原雜湊表 const bucketsTmp = this.buckets; // 初始化擴容後的新雜湊表 this.capacity *= this.extendRatio; this.buckets = Array(this.capacity).fill(null); this.size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (const pair of bucketsTmp) { if (pair !== null && pair !== this.TOMBSTONE) { this.put(pair.key, pair.val); } } } /* 列印雜湊表 */ print(): void { for (const pair of this.buckets) { if (pair === null) { console.log('null'); } else if (pair === this.TOMBSTONE) { console.log('TOMBSTONE'); } else { console.log(pair.key + ' -> ' + pair.val); } } } } /* Driver Code */ // 初始化雜湊表 const hashmap = new HashMapOpenAddressing(); // 新增操作 // 在雜湊表中新增鍵值對 (key, val) hashmap.put(12836, '小哈'); hashmap.put(15937, '小囉'); hashmap.put(16750, '小算'); hashmap.put(13276, '小法'); hashmap.put(10583, '小鴨'); console.log('\n新增完成後,雜湊表為\nKey -> Value'); hashmap.print(); // 查詢操作 // 向雜湊表中輸入鍵 key ,得到值 val const name = hashmap.get(13276); console.log('\n輸入學號 13276 ,查詢到姓名 ' + name); // 刪除操作 // 在雜湊表中刪除鍵值對 (key, val) hashmap.remove(16750); console.log('\n刪除 16750 後,雜湊表為\nKey -> Value'); hashmap.print(); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_hashing/simple_hash.ts ================================================ /** * File: simple_hash.ts * Created Time: 2023-08-06 * Author: yuan0221 (yl1452491917@gmail.com) */ /* 加法雜湊 */ function addHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* 乘法雜湊 */ function mulHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = (31 * hash + c.charCodeAt(0)) % MODULUS; } return hash; } /* 互斥或雜湊 */ function xorHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash ^= c.charCodeAt(0); } return hash % MODULUS; } /* 旋轉雜湊 */ function rotHash(key: string): number { let hash = 0; const MODULUS = 1000000007; for (const c of key) { hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; } return hash; } /* Driver Code */ const key = 'Hello 演算法'; let hash = addHash(key); console.log('加法雜湊值為 ' + hash); hash = mulHash(key); console.log('乘法雜湊值為 ' + hash); hash = xorHash(key); console.log('互斥或雜湊值為 ' + hash); hash = rotHash(key); console.log('旋轉雜湊值為 ' + hash); ================================================ FILE: zh-hant/codes/typescript/chapter_heap/my_heap.ts ================================================ /** * File: my_heap.ts * Created Time: 2023-02-07 * Author: Justin (xiefahit@gmail.com) */ import { printHeap } from '../modules/PrintUtil'; /* 最大堆積類別 */ class MaxHeap { private maxHeap: number[]; /* 建構子,建立空堆積或根據輸入串列建堆積 */ constructor(nums?: number[]) { // 將串列元素原封不動新增進堆積 this.maxHeap = nums === undefined ? [] : [...nums]; // 堆積化除葉節點以外的其他所有節點 for (let i = this.parent(this.size() - 1); i >= 0; i--) { this.siftDown(i); } } /* 獲取左子節點的索引 */ private left(i: number): number { return 2 * i + 1; } /* 獲取右子節點的索引 */ private right(i: number): number { return 2 * i + 2; } /* 獲取父節點的索引 */ private parent(i: number): number { return Math.floor((i - 1) / 2); // 向下整除 } /* 交換元素 */ private swap(i: number, j: number): void { const tmp = this.maxHeap[i]; this.maxHeap[i] = this.maxHeap[j]; this.maxHeap[j] = tmp; } /* 獲取堆積大小 */ public size(): number { return this.maxHeap.length; } /* 判斷堆積是否為空 */ public isEmpty(): boolean { return this.size() === 0; } /* 訪問堆積頂元素 */ public peek(): number { return this.maxHeap[0]; } /* 元素入堆積 */ public push(val: number): void { // 新增節點 this.maxHeap.push(val); // 從底至頂堆積化 this.siftUp(this.size() - 1); } /* 從節點 i 開始,從底至頂堆積化 */ private siftUp(i: number): void { while (true) { // 獲取節點 i 的父節點 const p = this.parent(i); // 當“越過根節點”或“節點無須修復”時,結束堆積化 if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break; // 交換兩節點 this.swap(i, p); // 迴圈向上堆積化 i = p; } } /* 元素出堆積 */ public pop(): number { // 判空處理 if (this.isEmpty()) throw new RangeError('Heap is empty.'); // 交換根節點與最右葉節點(交換首元素與尾元素) this.swap(0, this.size() - 1); // 刪除節點 const val = this.maxHeap.pop(); // 從頂至底堆積化 this.siftDown(0); // 返回堆積頂元素 return val; } /* 從節點 i 開始,從頂至底堆積化 */ private siftDown(i: number): void { while (true) { // 判斷節點 i, l, r 中值最大的節點,記為 ma const l = this.left(i), r = this.right(i); let ma = i; if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l; if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r; // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if (ma === i) break; // 交換兩節點 this.swap(i, ma); // 迴圈向下堆積化 i = ma; } } /* 列印堆積(二元樹) */ public print(): void { printHeap(this.maxHeap); } /* 取出堆積中元素 */ public getMaxHeap(): number[] { return this.maxHeap; } } /* Driver Code */ if (import.meta.url.endsWith(process.argv[1])) { /* 初始化大頂堆積 */ const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); console.log('\n輸入串列並建堆積後'); maxHeap.print(); /* 獲取堆積頂元素 */ let peek = maxHeap.peek(); console.log(`\n堆積頂元素為 ${peek}`); /* 元素入堆積 */ const val = 7; maxHeap.push(val); console.log(`\n元素 ${val} 入堆積後`); maxHeap.print(); /* 堆積頂元素出堆積 */ peek = maxHeap.pop(); console.log(`\n堆積頂元素 ${peek} 出堆積後`); maxHeap.print(); /* 獲取堆積大小 */ const size = maxHeap.size(); console.log(`\n堆積元素數量為 ${size}`); /* 判斷堆積是否為空 */ const isEmpty = maxHeap.isEmpty(); console.log(`\n堆積是否為空 ${isEmpty}`); } export { MaxHeap }; ================================================ FILE: zh-hant/codes/typescript/chapter_heap/top_k.ts ================================================ /** * File: top_k.ts * Created Time: 2023-08-13 * Author: Justin (xiefahit@gmail.com) */ import { MaxHeap } from './my_heap'; /* 元素入堆積 */ function pushMinHeap(maxHeap: MaxHeap, val: number): void { // 元素取反 maxHeap.push(-val); } /* 元素出堆積 */ function popMinHeap(maxHeap: MaxHeap): number { // 元素取反 return -maxHeap.pop(); } /* 訪問堆積頂元素 */ function peekMinHeap(maxHeap: MaxHeap): number { // 元素取反 return -maxHeap.peek(); } /* 取出堆積中元素 */ function getMinHeap(maxHeap: MaxHeap): number[] { // 元素取反 return maxHeap.getMaxHeap().map((num: number) => -num); } /* 基於堆積查詢陣列中最大的 k 個元素 */ function topKHeap(nums: number[], k: number): number[] { // 初始化小頂堆積 // 請注意:我們將堆積中所有元素取反,從而用大頂堆積來模擬小頂堆積 const maxHeap = new MaxHeap([]); // 將陣列的前 k 個元素入堆積 for (let i = 0; i < k; i++) { pushMinHeap(maxHeap, nums[i]); } // 從第 k+1 個元素開始,保持堆積的長度為 k for (let i = k; i < nums.length; i++) { // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 if (nums[i] > peekMinHeap(maxHeap)) { popMinHeap(maxHeap); pushMinHeap(maxHeap, nums[i]); } } // 返回堆積中元素 return getMinHeap(maxHeap); } /* Driver Code */ const nums = [1, 7, 6, 3, 2]; const k = 3; const res = topKHeap(nums, k); console.log(`最大的 ${k} 個元素為`, res); ================================================ FILE: zh-hant/codes/typescript/chapter_searching/binary_search.ts ================================================ /** * File: binary_search.ts * Created Time: 2022-12-27 * Author: Daniel (better.sunjian@gmail.com) */ /* 二分搜尋(雙閉區間) */ function binarySearch(nums: number[], target: number): number { // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 let i = 0, j = nums.length - 1; // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) while (i <= j) { // 計算中點索引 m const m = Math.floor(i + (j - i) / 2); if (nums[m] < target) { // 此情況說明 target 在區間 [m+1, j] 中 i = m + 1; } else if (nums[m] > target) { // 此情況說明 target 在區間 [i, m-1] 中 j = m - 1; } else { // 找到目標元素,返回其索引 return m; } } return -1; // 未找到目標元素,返回 -1 } /* 二分搜尋(左閉右開區間) */ function binarySearchLCRO(nums: number[], target: number): number { // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 let i = 0, j = nums.length; // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) while (i < j) { // 計算中點索引 m const m = Math.floor(i + (j - i) / 2); if (nums[m] < target) { // 此情況說明 target 在區間 [m+1, j) 中 i = m + 1; } else if (nums[m] > target) { // 此情況說明 target 在區間 [i, m) 中 j = m; } else { // 找到目標元素,返回其索引 return m; } } return -1; // 未找到目標元素,返回 -1 } /* Driver Code */ const target = 6; const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分搜尋(雙閉區間) */ let index = binarySearch(nums, target); console.info('目標元素 6 的索引 = %d', index); /* 二分搜尋(左閉右開區間) */ index = binarySearchLCRO(nums, target); console.info('目標元素 6 的索引 = %d', index); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_searching/binary_search_edge.ts ================================================ /** * File: binary_search_edge.ts * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ import { binarySearchInsertion } from './binary_search_insertion'; /* 二分搜尋最左一個 target */ function binarySearchLeftEdge(nums: Array, target: number): number { // 等價於查詢 target 的插入點 const i = binarySearchInsertion(nums, target); // 未找到 target ,返回 -1 if (i === nums.length || nums[i] !== target) { return -1; } // 找到 target ,返回索引 i return i; } /* 二分搜尋最右一個 target */ function binarySearchRightEdge(nums: Array, target: number): number { // 轉化為查詢最左一個 target + 1 const i = binarySearchInsertion(nums, target + 1); // j 指向最右一個 target ,i 指向首個大於 target 的元素 const j = i - 1; // 未找到 target ,返回 -1 if (j === -1 || nums[j] !== target) { return -1; } // 找到 target ,返回索引 j return j; } /* Driver Code */ // 包含重複元素的陣列 let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\n陣列 nums = ' + nums); // 二分搜尋左邊界和右邊界 for (const target of [6, 7]) { let index = binarySearchLeftEdge(nums, target); console.log('最左一個元素 ' + target + ' 的索引為 ' + index); index = binarySearchRightEdge(nums, target); console.log('最右一個元素 ' + target + ' 的索引為 ' + index); } export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_searching/binary_search_insertion.ts ================================================ /** * File: binary_search_insertion.ts * Created Time: 2023-08-22 * Author: Gaofer Chou (gaofer-chou@qq.com) */ /* 二分搜尋插入點(無重複元素) */ function binarySearchInsertionSimple( nums: Array, target: number ): number { let i = 0, j = nums.length - 1; // 初始化雙閉區間 [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // 計算中點索引 m, 使用 Math.floor() 向下取整 if (nums[m] < target) { i = m + 1; // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在區間 [i, m-1] 中 } else { return m; // 找到 target ,返回插入點 m } } // 未找到 target ,返回插入點 i return i; } /* 二分搜尋插入點(存在重複元素) */ function binarySearchInsertion(nums: Array, target: number): number { let i = 0, j = nums.length - 1; // 初始化雙閉區間 [0, n-1] while (i <= j) { const m = Math.floor(i + (j - i) / 2); // 計算中點索引 m, 使用 Math.floor() 向下取整 if (nums[m] < target) { i = m + 1; // target 在區間 [m+1, j] 中 } else if (nums[m] > target) { j = m - 1; // target 在區間 [i, m-1] 中 } else { j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 } } // 返回插入點 i return i; } /* Driver Code */ // 無重複元素的陣列 let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; console.log('\n陣列 nums = ' + nums); // 二分搜尋插入點 for (const target of [6, 9]) { const index = binarySearchInsertionSimple(nums, target); console.log('元素 ' + target + ' 的插入點的索引為 ' + index); } // 包含重複元素的陣列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; console.log('\n陣列 nums = ' + nums); // 二分搜尋插入點 for (const target of [2, 6, 20]) { const index = binarySearchInsertion(nums, target); console.log('元素 ' + target + ' 的插入點的索引為 ' + index); } export { binarySearchInsertion }; ================================================ FILE: zh-hant/codes/typescript/chapter_searching/hashing_search.ts ================================================ /** * File: hashing_search.ts * Created Time: 2022-12-29 * Author: Zhuo Qinyue (1403450829@qq.com) */ import { ListNode, arrToLinkedList } from '../modules/ListNode'; /* 雜湊查詢(陣列) */ function hashingSearchArray(map: Map, target: number): number { // 雜湊表的 key: 目標元素,value: 索引 // 若雜湊表中無此 key ,返回 -1 return map.has(target) ? (map.get(target) as number) : -1; } /* 雜湊查詢(鏈結串列) */ function hashingSearchLinkedList( map: Map, target: number ): ListNode | null { // 雜湊表的 key: 目標節點值,value: 節點物件 // 若雜湊表中無此 key ,返回 null return map.has(target) ? (map.get(target) as ListNode) : null; } /* Driver Code */ const target = 3; /* 雜湊查詢(陣列) */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; // 初始化雜湊表 const map = new Map(); for (let i = 0; i < nums.length; i++) { map.set(nums[i], i); // key: 元素,value: 索引 } const index = hashingSearchArray(map, target); console.log('目標元素 3 的索引 = ' + index); /* 雜湊查詢(鏈結串列) */ let head = arrToLinkedList(nums); // 初始化雜湊表 const map1 = new Map(); while (head != null) { map1.set(head.val, head); // key: 節點值,value: 節點 head = head.next; } const node = hashingSearchLinkedList(map1, target); console.log('目標節點值 3 的對應節點物件為', node); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_searching/linear_search.ts ================================================ /** * File: linear_search.ts * Created Time: 2023-01-07 * Author: Daniel (better.sunjian@gmail.com) */ import { ListNode, arrToLinkedList } from '../modules/ListNode'; /* 線性查詢(陣列)*/ function linearSearchArray(nums: number[], target: number): number { // 走訪陣列 for (let i = 0; i < nums.length; i++) { // 找到目標元素,返回其索引 if (nums[i] === target) { return i; } } // 未找到目標元素,返回 -1 return -1; } /* 線性查詢(鏈結串列)*/ function linearSearchLinkedList( head: ListNode | null, target: number ): ListNode | null { // 走訪鏈結串列 while (head) { // 找到目標節點,返回之 if (head.val === target) { return head; } head = head.next; } // 未找到目標節點,返回 null return null; } /* Driver Code */ const target = 3; /* 在陣列中執行線性查詢 */ const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; const index = linearSearchArray(nums, target); console.log('目標元素 3 的索引 =', index); /* 在鏈結串列中執行線性查詢 */ const head = arrToLinkedList(nums); const node = linearSearchLinkedList(head, target); console.log('目標節點值 3 的對應節點物件為', node); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_searching/two_sum.ts ================================================ /** * File: two_sum.ts * Created Time: 2022-12-15 * Author: gyt95 (gytkwan@gmail.com) */ /* 方法一:暴力列舉 */ function twoSumBruteForce(nums: number[], target: number): number[] { const n = nums.length; // 兩層迴圈,時間複雜度為 O(n^2) for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { if (nums[i] + nums[j] === target) { return [i, j]; } } } return []; } /* 方法二:輔助雜湊表 */ function twoSumHashTable(nums: number[], target: number): number[] { // 輔助雜湊表,空間複雜度為 O(n) let m: Map = new Map(); // 單層迴圈,時間複雜度為 O(n) for (let i = 0; i < nums.length; i++) { let index = m.get(target - nums[i]); if (index !== undefined) { return [index, i]; } else { m.set(nums[i], i); } } return []; } /* Driver Code */ // 方法一 const nums = [2, 7, 11, 15], target = 13; let res = twoSumBruteForce(nums, target); console.log('方法一 res = ', res); // 方法二 res = twoSumHashTable(nums, target); console.log('方法二 res = ', res); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_sorting/bubble_sort.ts ================================================ /** * File: bubble_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 泡沫排序 */ function bubbleSort(nums: number[]): void { // 外迴圈:未排序區間為 [0, i] for (let i = nums.length - 1; i > 0; i--) { // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } /* 泡沫排序(標誌最佳化)*/ function bubbleSortWithFlag(nums: number[]): void { // 外迴圈:未排序區間為 [0, i] for (let i = nums.length - 1; i > 0; i--) { let flag = false; // 初始化標誌位 // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; // 記錄交換元素 } } if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); console.log('泡沫排序完成後 nums =', nums); const nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); console.log('泡沫排序完成後 nums =', nums1); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_sorting/bucket_sort.ts ================================================ /** * File: bucket_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 桶排序 */ function bucketSort(nums: number[]): void { // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 const k = nums.length / 2; const buckets: number[][] = []; for (let i = 0; i < k; i++) { buckets.push([]); } // 1. 將陣列元素分配到各個桶中 for (const num of nums) { // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] const i = Math.floor(num * k); // 將 num 新增進桶 i buckets[i].push(num); } // 2. 對各個桶執行排序 for (const bucket of buckets) { // 使用內建排序函式,也可以替換成其他排序演算法 bucket.sort((a, b) => a - b); } // 3. 走訪桶合併結果 let i = 0; for (const bucket of buckets) { for (const num of bucket) { nums[i++] = num; } } } /* Driver Code */ const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; bucketSort(nums); console.log('桶排序完成後 nums =', nums); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_sorting/counting_sort.ts ================================================ /** * File: counting_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 計數排序 */ // 簡單實現,無法用於排序物件 function countingSortNaive(nums: number[]): void { // 1. 統計陣列最大元素 m let m: number = Math.max(...nums); // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 const counter: number[] = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. 走訪 counter ,將各元素填入原陣列 nums let i = 0; for (let num = 0; num < m + 1; num++) { for (let j = 0; j < counter[num]; j++, i++) { nums[i] = num; } } } /* 計數排序 */ // 完整實現,可排序物件,並且是穩定排序 function countingSort(nums: number[]): void { // 1. 統計陣列最大元素 m let m: number = Math.max(...nums); // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 const counter: number[] = new Array(m + 1).fill(0); for (const num of nums) { counter[num]++; } // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 for (let i = 0; i < m; i++) { counter[i + 1] += counter[i]; } // 4. 倒序走訪 nums ,將各元素填入結果陣列 res // 初始化陣列 res 用於記錄結果 const n = nums.length; const res: number[] = new Array(n); for (let i = n - 1; i >= 0; i--) { const num = nums[i]; res[counter[num] - 1] = num; // 將 num 放置到對應索引處 counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 } // 使用結果陣列 res 覆蓋原陣列 nums for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* Driver Code */ const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSortNaive(nums); console.log('計數排序(無法排序物件)完成後 nums =', nums); const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; countingSort(nums1); console.log('計數排序完成後 nums1 =', nums1); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_sorting/heap_sort.ts ================================================ /** * File: heap_sort.ts * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ function siftDown(nums: number[], n: number, i: number): void { while (true) { // 判斷節點 i, l, r 中值最大的節點,記為 ma let l = 2 * i + 1; let r = 2 * i + 2; let ma = i; if (l < n && nums[l] > nums[ma]) { ma = l; } if (r < n && nums[r] > nums[ma]) { ma = r; } // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if (ma === i) { break; } // 交換兩節點 [nums[i], nums[ma]] = [nums[ma], nums[i]]; // 迴圈向下堆積化 i = ma; } } /* 堆積排序 */ function heapSort(nums: number[]): void { // 建堆積操作:堆積化除葉節點以外的其他所有節點 for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } // 從堆積中提取最大元素,迴圈 n-1 輪 for (let i = nums.length - 1; i > 0; i--) { // 交換根節點與最右葉節點(交換首元素與尾元素) [nums[0], nums[i]] = [nums[i], nums[0]]; // 以根節點為起點,從頂至底進行堆積化 siftDown(nums, i, 0); } } /* Driver Code */ const nums: number[] = [4, 1, 3, 1, 5, 2]; heapSort(nums); console.log('堆積排序完成後 nums =', nums); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_sorting/insertion_sort.ts ================================================ /** * File: insertion_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 插入排序 */ function insertionSort(nums: number[]): void { // 外迴圈:已排序區間為 [0, i-1] for (let i = 1; i < nums.length; i++) { const base = nums[i]; let j = i - 1; // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 j--; } nums[j + 1] = base; // 將 base 賦值到正確位置 } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); console.log('插入排序完成後 nums =', nums); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_sorting/merge_sort.ts ================================================ /** * File: merge_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 合併左子陣列和右子陣列 */ function merge(nums: number[], left: number, mid: number, right: number): void { // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] // 建立一個臨時陣列 tmp ,用於存放合併後的結果 const tmp = new Array(right - left + 1); // 初始化左子陣列和右子陣列的起始索引 let i = left, j = mid + 1, k = 0; // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } } /* 合併排序 */ function mergeSort(nums: number[], left: number, right: number): void { // 終止條件 if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 // 劃分階段 let mid = Math.floor(left + (right - left) / 2); // 計算中點 mergeSort(nums, left, mid); // 遞迴左子陣列 mergeSort(nums, mid + 1, right); // 遞迴右子陣列 // 合併階段 merge(nums, left, mid, right); } /* Driver Code */ const nums = [7, 3, 2, 6, 0, 1, 5, 4]; mergeSort(nums, 0, nums.length - 1); console.log('合併排序完成後 nums =', nums); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_sorting/quick_sort.ts ================================================ /** * File: quick_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 快速排序類別 */ class QuickSort { /* 元素交換 */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵劃分 */ partition(nums: number[], left: number, right: number): number { // 以 nums[left] 為基準數 let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j -= 1; // 從右向左找首個小於基準數的元素 } while (i < j && nums[i] <= nums[left]) { i += 1; // 從左向右找首個大於基準數的元素 } // 元素交換 this.swap(nums, i, j); // 交換這兩個元素 } this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } /* 快速排序 */ quickSort(nums: number[], left: number, right: number): void { // 子陣列長度為 1 時終止遞迴 if (left >= right) { return; } // 哨兵劃分 const pivot = this.partition(nums, left, right); // 遞迴左子陣列、右子陣列 this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* 快速排序類別(中位基準數最佳化) */ class QuickSortMedian { /* 元素交換 */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 選取三個候選元素的中位數 */ medianThree( nums: number[], left: number, mid: number, right: number ): number { let l = nums[left], m = nums[mid], r = nums[right]; // m 在 l 和 r 之間 if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // l 在 m 和 r 之間 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; return right; } /* 哨兵劃分(三數取中值) */ partition(nums: number[], left: number, right: number): number { // 選取三個候選元素的中位數 let med = this.medianThree( nums, left, Math.floor((left + right) / 2), right ); // 將中位數交換至陣列最左端 this.swap(nums, left, med); // 以 nums[left] 為基準數 let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // 從右向左找首個小於基準數的元素 } while (i < j && nums[i] <= nums[left]) { i++; // 從左向右找首個大於基準數的元素 } this.swap(nums, i, j); // 交換這兩個元素 } this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } /* 快速排序 */ quickSort(nums: number[], left: number, right: number): void { // 子陣列長度為 1 時終止遞迴 if (left >= right) { return; } // 哨兵劃分 const pivot = this.partition(nums, left, right); // 遞迴左子陣列、右子陣列 this.quickSort(nums, left, pivot - 1); this.quickSort(nums, pivot + 1, right); } } /* 快速排序類別(遞迴深度最佳化) */ class QuickSortTailCall { /* 元素交換 */ swap(nums: number[], i: number, j: number): void { let tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } /* 哨兵劃分 */ partition(nums: number[], left: number, right: number): number { // 以 nums[left] 為基準數 let i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // 從右向左找首個小於基準數的元素 } while (i < j && nums[i] <= nums[left]) { i++; // 從左向右找首個大於基準數的元素 } this.swap(nums, i, j); // 交換這兩個元素 } this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } /* 快速排序(遞迴深度最佳化) */ quickSort(nums: number[], left: number, right: number): void { // 子陣列長度為 1 時終止 while (left < right) { // 哨兵劃分操作 let pivot = this.partition(nums, left, right); // 對兩個子陣列中較短的那個執行快速排序 if (pivot - left < right - pivot) { this.quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] } else { this.quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] } } } } /* Driver Code */ /* 快速排序 */ const nums = [2, 4, 1, 0, 3, 5]; const quickSort = new QuickSort(); quickSort.quickSort(nums, 0, nums.length - 1); console.log('快速排序完成後 nums =', nums); /* 快速排序(中位基準數最佳化) */ const nums1 = [2, 4, 1, 0, 3, 5]; const quickSortMedian = new QuickSortMedian(); quickSortMedian.quickSort(nums1, 0, nums1.length - 1); console.log('快速排序(中位基準數最佳化)完成後 nums =', nums1); /* 快速排序(遞迴深度最佳化) */ const nums2 = [2, 4, 1, 0, 3, 5]; const quickSortTailCall = new QuickSortTailCall(); quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); console.log('快速排序(遞迴深度最佳化)完成後 nums =', nums2); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_sorting/radix_sort.ts ================================================ /** * File: radix_sort.ts * Created Time: 2023-04-08 * Author: Justin (xiefahit@gmail.com) */ /* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ function digit(num: number, exp: number): number { // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 return Math.floor(num / exp) % 10; } /* 計數排序(根據 nums 第 k 位排序) */ function countingSortDigit(nums: number[], exp: number): void { // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 const counter = new Array(10).fill(0); const n = nums.length; // 統計 0~9 各數字的出現次數 for (let i = 0; i < n; i++) { const d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d counter[d]++; // 統計數字 d 的出現次數 } // 求前綴和,將“出現個數”轉換為“陣列索引” for (let i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } // 倒序走訪,根據桶內統計結果,將各元素填入 res const res = new Array(n).fill(0); for (let i = n - 1; i >= 0; i--) { const d = digit(nums[i], exp); const j = counter[d] - 1; // 獲取 d 在陣列中的索引 j res[j] = nums[i]; // 將當前元素填入索引 j counter[d]--; // 將 d 的數量減 1 } // 使用結果覆蓋原陣列 nums for (let i = 0; i < n; i++) { nums[i] = res[i]; } } /* 基數排序 */ function radixSort(nums: number[]): void { // 獲取陣列的最大元素,用於判斷最大位數 let m: number = Math.max(... nums); // 按照從低位到高位的順序走訪 for (let exp = 1; exp <= m; exp *= 10) { // 對陣列元素的第 k 位執行計數排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums, exp); } } /* Driver Code */ const nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996, ]; radixSort(nums); console.log('基數排序完成後 nums =', nums); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_sorting/selection_sort.ts ================================================ /** * File: selection_sort.ts * Created Time: 2023-06-04 * Author: Justin (xiefahit@gmail.com) */ /* 選擇排序 */ function selectionSort(nums: number[]): void { let n = nums.length; // 外迴圈:未排序區間為 [i, n-1] for (let i = 0; i < n - 1; i++) { // 內迴圈:找到未排序區間內的最小元素 let k = i; for (let j = i + 1; j < n; j++) { if (nums[j] < nums[k]) { k = j; // 記錄最小元素的索引 } } // 將該最小元素與未排序區間的首個元素交換 [nums[i], nums[k]] = [nums[k], nums[i]]; } } /* Driver Code */ const nums: number[] = [4, 1, 3, 1, 5, 2]; selectionSort(nums); console.log('選擇排序完成後 nums =', nums); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_stack_and_queue/array_deque.ts ================================================ /** * File: array_deque.ts * Created Time: 2023-02-28 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 基於環形陣列實現的雙向佇列 */ class ArrayDeque { private nums: number[]; // 用於儲存雙向佇列元素的陣列 private front: number; // 佇列首指標,指向佇列首元素 private queSize: number; // 雙向佇列長度 /* 建構子 */ constructor(capacity: number) { this.nums = new Array(capacity); this.front = 0; this.queSize = 0; } /* 獲取雙向佇列的容量 */ capacity(): number { return this.nums.length; } /* 獲取雙向佇列的長度 */ size(): number { return this.queSize; } /* 判斷雙向佇列是否為空 */ isEmpty(): boolean { return this.queSize === 0; } /* 計算環形陣列索引 */ index(i: number): number { // 透過取餘操作實現陣列首尾相連 // 當 i 越過陣列尾部後,回到頭部 // 當 i 越過陣列頭部後,回到尾部 return (i + this.capacity()) % this.capacity(); } /* 佇列首入列 */ pushFirst(num: number): void { if (this.queSize === this.capacity()) { console.log('雙向佇列已滿'); return; } // 佇列首指標向左移動一位 // 透過取餘操作實現 front 越過陣列頭部後回到尾部 this.front = this.index(this.front - 1); // 將 num 新增至佇列首 this.nums[this.front] = num; this.queSize++; } /* 佇列尾入列 */ pushLast(num: number): void { if (this.queSize === this.capacity()) { console.log('雙向佇列已滿'); return; } // 計算佇列尾指標,指向佇列尾索引 + 1 const rear: number = this.index(this.front + this.queSize); // 將 num 新增至佇列尾 this.nums[rear] = num; this.queSize++; } /* 佇列首出列 */ popFirst(): number { const num: number = this.peekFirst(); // 佇列首指標向後移動一位 this.front = this.index(this.front + 1); this.queSize--; return num; } /* 佇列尾出列 */ popLast(): number { const num: number = this.peekLast(); this.queSize--; return num; } /* 訪問佇列首元素 */ peekFirst(): number { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); return this.nums[this.front]; } /* 訪問佇列尾元素 */ peekLast(): number { if (this.isEmpty()) throw new Error('The Deque Is Empty.'); // 計算尾元素索引 const last = this.index(this.front + this.queSize - 1); return this.nums[last]; } /* 返回陣列用於列印 */ toArray(): number[] { // 僅轉換有效長度範圍內的串列元素 const res: number[] = []; for (let i = 0, j = this.front; i < this.queSize; i++, j++) { res[i] = this.nums[this.index(j)]; } return res; } } /* Driver Code */ /* 初始化雙向佇列 */ const capacity = 5; const deque: ArrayDeque = new ArrayDeque(capacity); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); console.log('雙向佇列 deque = [' + deque.toArray() + ']'); /* 訪問元素 */ const peekFirst = deque.peekFirst(); console.log('佇列首元素 peekFirst = ' + peekFirst); const peekLast = deque.peekLast(); console.log('佇列尾元素 peekLast = ' + peekLast); /* 元素入列 */ deque.pushLast(4); console.log('元素 4 佇列尾入列後 deque = [' + deque.toArray() + ']'); deque.pushFirst(1); console.log('元素 1 佇列首入列後 deque = [' + deque.toArray() + ']'); /* 元素出列 */ const popLast = deque.popLast(); console.log( '佇列尾出列元素 = ' + popLast + ',佇列尾出列後 deque = [' + deque.toArray() + ']' ); const popFirst = deque.popFirst(); console.log( '佇列首出列元素 = ' + popFirst + ',佇列首出列後 deque = [' + deque.toArray() + ']' ); /* 獲取雙向佇列的長度 */ const size = deque.size(); console.log('雙向佇列長度 size = ' + size); /* 判斷雙向佇列是否為空 */ const isEmpty = deque.isEmpty(); console.log('雙向佇列是否為空 = ' + isEmpty); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_stack_and_queue/array_queue.ts ================================================ /** * File: array_queue.ts * Created Time: 2022-12-11 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* 基於環形陣列實現的佇列 */ class ArrayQueue { private nums: number[]; // 用於儲存佇列元素的陣列 private front: number; // 佇列首指標,指向佇列首元素 private queSize: number; // 佇列長度 constructor(capacity: number) { this.nums = new Array(capacity); this.front = this.queSize = 0; } /* 獲取佇列的容量 */ get capacity(): number { return this.nums.length; } /* 獲取佇列的長度 */ get size(): number { return this.queSize; } /* 判斷佇列是否為空 */ isEmpty(): boolean { return this.queSize === 0; } /* 入列 */ push(num: number): void { if (this.size === this.capacity) { console.log('佇列已滿'); return; } // 計算佇列尾指標,指向佇列尾索引 + 1 // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 const rear = (this.front + this.queSize) % this.capacity; // 將 num 新增至佇列尾 this.nums[rear] = num; this.queSize++; } /* 出列 */ pop(): number { const num = this.peek(); // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 this.front = (this.front + 1) % this.capacity; this.queSize--; return num; } /* 訪問佇列首元素 */ peek(): number { if (this.isEmpty()) throw new Error('佇列為空'); return this.nums[this.front]; } /* 返回 Array */ toArray(): number[] { // 僅轉換有效長度範圍內的串列元素 const arr = new Array(this.size); for (let i = 0, j = this.front; i < this.size; i++, j++) { arr[i] = this.nums[j % this.capacity]; } return arr; } } /* Driver Code */ /* 初始化佇列 */ const capacity = 10; const queue = new ArrayQueue(capacity); /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('佇列 queue =', queue.toArray()); /* 訪問佇列首元素 */ const peek = queue.peek(); console.log('佇列首元素 peek = ' + peek); /* 元素出列 */ const pop = queue.pop(); console.log('出列元素 pop = ' + pop + ',出列後 queue =', queue.toArray()); /* 獲取佇列的長度 */ const size = queue.size; console.log('佇列長度 size = ' + size); /* 判斷佇列是否為空 */ const isEmpty = queue.isEmpty(); console.log('佇列是否為空 = ' + isEmpty); /* 測試環形陣列 */ for (let i = 0; i < 10; i++) { queue.push(i); queue.pop(); console.log('第 ' + i + ' 輪入列 + 出列後 queue =', queue.toArray()); } export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_stack_and_queue/array_stack.ts ================================================ /** * File: array_stack.ts * Created Time: 2022-12-08 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* 基於陣列實現的堆疊 */ class ArrayStack { private stack: number[]; constructor() { this.stack = []; } /* 獲取堆疊的長度 */ get size(): number { return this.stack.length; } /* 判斷堆疊是否為空 */ isEmpty(): boolean { return this.stack.length === 0; } /* 入堆疊 */ push(num: number): void { this.stack.push(num); } /* 出堆疊 */ pop(): number | undefined { if (this.isEmpty()) throw new Error('堆疊為空'); return this.stack.pop(); } /* 訪問堆疊頂元素 */ top(): number | undefined { if (this.isEmpty()) throw new Error('堆疊為空'); return this.stack[this.stack.length - 1]; } /* 返回 Array */ toArray() { return this.stack; } } /* Driver Code */ /* 初始化堆疊 */ const stack = new ArrayStack(); /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('堆疊 stack = '); console.log(stack.toArray()); /* 訪問堆疊頂元素 */ const top = stack.top(); console.log('堆疊頂元素 top = ' + top); /* 元素出堆疊 */ const pop = stack.pop(); console.log('出堆疊元素 pop = ' + pop + ',出堆疊後 stack = '); console.log(stack.toArray()); /* 獲取堆疊的長度 */ const size = stack.size; console.log('堆疊的長度 size = ' + size); /* 判斷是否為空 */ const isEmpty = stack.isEmpty(); console.log('堆疊是否為空 = ' + isEmpty); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_stack_and_queue/deque.ts ================================================ /** * File: deque.ts * Created Time: 2023-01-17 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* Driver Code */ /* 初始化雙向佇列 */ // TypeScript 沒有內建的雙端佇列,只能把 Array 當作雙端佇列來使用 const deque: number[] = []; /* 元素入列 */ deque.push(2); deque.push(5); deque.push(4); // 請注意,由於是陣列,unshift() 方法的時間複雜度為 O(n) deque.unshift(3); deque.unshift(1); console.log('雙向佇列 deque = ', deque); /* 訪問元素 */ const peekFirst: number = deque[0]; console.log('佇列首元素 peekFirst = ' + peekFirst); const peekLast: number = deque[deque.length - 1]; console.log('佇列尾元素 peekLast = ' + peekLast); /* 元素出列 */ // 請注意,由於是陣列,shift() 方法的時間複雜度為 O(n) const popFront: number = deque.shift() as number; console.log( '佇列首出列元素 popFront = ' + popFront + ',佇列首出列後 deque = ' + deque ); const popBack: number = deque.pop() as number; console.log( '佇列尾出列元素 popBack = ' + popBack + ',佇列尾出列後 deque = ' + deque ); /* 獲取雙向佇列的長度 */ const size: number = deque.length; console.log('雙向佇列長度 size = ' + size); /* 判斷雙向佇列是否為空 */ const isEmpty: boolean = size === 0; console.log('雙向佇列是否為空 = ' + isEmpty); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts ================================================ /** * File: linkedlist_deque.ts * Created Time: 2023-02-04 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 雙向鏈結串列節點 */ class ListNode { prev: ListNode; // 前驅節點引用 (指標) next: ListNode; // 後繼節點引用 (指標) val: number; // 節點值 constructor(val: number) { this.val = val; this.next = null; this.prev = null; } } /* 基於雙向鏈結串列實現的雙向佇列 */ class LinkedListDeque { private front: ListNode; // 頭節點 front private rear: ListNode; // 尾節點 rear private queSize: number; // 雙向佇列的長度 constructor() { this.front = null; this.rear = null; this.queSize = 0; } /* 佇列尾入列操作 */ pushLast(val: number): void { const node: ListNode = new ListNode(val); // 若鏈結串列為空,則令 front 和 rear 都指向 node if (this.queSize === 0) { this.front = node; this.rear = node; } else { // 將 node 新增至鏈結串列尾部 this.rear.next = node; node.prev = this.rear; this.rear = node; // 更新尾節點 } this.queSize++; } /* 佇列首入列操作 */ pushFirst(val: number): void { const node: ListNode = new ListNode(val); // 若鏈結串列為空,則令 front 和 rear 都指向 node if (this.queSize === 0) { this.front = node; this.rear = node; } else { // 將 node 新增至鏈結串列頭部 this.front.prev = node; node.next = this.front; this.front = node; // 更新頭節點 } this.queSize++; } /* 佇列尾出列操作 */ popLast(): number { if (this.queSize === 0) { return null; } const value: number = this.rear.val; // 儲存尾節點值 // 刪除尾節點 let temp: ListNode = this.rear.prev; if (temp !== null) { temp.next = null; this.rear.prev = null; } this.rear = temp; // 更新尾節點 this.queSize--; return value; } /* 佇列首出列操作 */ popFirst(): number { if (this.queSize === 0) { return null; } const value: number = this.front.val; // 儲存尾節點值 // 刪除頭節點 let temp: ListNode = this.front.next; if (temp !== null) { temp.prev = null; this.front.next = null; } this.front = temp; // 更新頭節點 this.queSize--; return value; } /* 訪問佇列尾元素 */ peekLast(): number { return this.queSize === 0 ? null : this.rear.val; } /* 訪問佇列首元素 */ peekFirst(): number { return this.queSize === 0 ? null : this.front.val; } /* 獲取雙向佇列的長度 */ size(): number { return this.queSize; } /* 判斷雙向佇列是否為空 */ isEmpty(): boolean { return this.queSize === 0; } /* 列印雙向佇列 */ print(): void { const arr: number[] = []; let temp: ListNode = this.front; while (temp !== null) { arr.push(temp.val); temp = temp.next; } console.log('[' + arr.join(', ') + ']'); } } /* Driver Code */ /* 初始化雙向佇列 */ const linkedListDeque: LinkedListDeque = new LinkedListDeque(); linkedListDeque.pushLast(3); linkedListDeque.pushLast(2); linkedListDeque.pushLast(5); console.log('雙向佇列 linkedListDeque = '); linkedListDeque.print(); /* 訪問元素 */ const peekFirst: number = linkedListDeque.peekFirst(); console.log('佇列首元素 peekFirst = ' + peekFirst); const peekLast: number = linkedListDeque.peekLast(); console.log('佇列尾元素 peekLast = ' + peekLast); /* 元素入列 */ linkedListDeque.pushLast(4); console.log('元素 4 佇列尾入列後 linkedListDeque = '); linkedListDeque.print(); linkedListDeque.pushFirst(1); console.log('元素 1 佇列首入列後 linkedListDeque = '); linkedListDeque.print(); /* 元素出列 */ const popLast: number = linkedListDeque.popLast(); console.log('佇列尾出列元素 = ' + popLast + ',佇列尾出列後 linkedListDeque = '); linkedListDeque.print(); const popFirst: number = linkedListDeque.popFirst(); console.log('佇列首出列元素 = ' + popFirst + ',佇列首出列後 linkedListDeque = '); linkedListDeque.print(); /* 獲取雙向佇列的長度 */ const size: number = linkedListDeque.size(); console.log('雙向佇列長度 size = ' + size); /* 判斷雙向佇列是否為空 */ const isEmpty: boolean = linkedListDeque.isEmpty(); console.log('雙向佇列是否為空 = ' + isEmpty); ================================================ FILE: zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts ================================================ /** * File: linkedlist_queue.ts * Created Time: 2022-12-19 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ import { ListNode } from '../modules/ListNode'; /* 基於鏈結串列實現的佇列 */ class LinkedListQueue { private front: ListNode | null; // 頭節點 front private rear: ListNode | null; // 尾節點 rear private queSize: number = 0; constructor() { this.front = null; this.rear = null; } /* 獲取佇列的長度 */ get size(): number { return this.queSize; } /* 判斷佇列是否為空 */ isEmpty(): boolean { return this.size === 0; } /* 入列 */ push(num: number): void { // 在尾節點後新增 num const node = new ListNode(num); // 如果佇列為空,則令頭、尾節點都指向該節點 if (!this.front) { this.front = node; this.rear = node; // 如果佇列不為空,則將該節點新增到尾節點後 } else { this.rear!.next = node; this.rear = node; } this.queSize++; } /* 出列 */ pop(): number { const num = this.peek(); if (!this.front) throw new Error('佇列為空'); // 刪除頭節點 this.front = this.front.next; this.queSize--; return num; } /* 訪問佇列首元素 */ peek(): number { if (this.size === 0) throw new Error('佇列為空'); return this.front!.val; } /* 將鏈結串列轉化為 Array 並返回 */ toArray(): number[] { let node = this.front; const res = new Array(this.size); for (let i = 0; i < res.length; i++) { res[i] = node!.val; node = node!.next; } return res; } } /* Driver Code */ /* 初始化佇列 */ const queue = new LinkedListQueue(); /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('佇列 queue = ' + queue.toArray()); /* 訪問佇列首元素 */ const peek = queue.peek(); console.log('佇列首元素 peek = ' + peek); /* 元素出列 */ const pop = queue.pop(); console.log('出列元素 pop = ' + pop + ',出列後 queue = ' + queue.toArray()); /* 獲取佇列的長度 */ const size = queue.size; console.log('佇列長度 size = ' + size); /* 判斷佇列是否為空 */ const isEmpty = queue.isEmpty(); console.log('佇列是否為空 = ' + isEmpty); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts ================================================ /** * File: linkedlist_stack.ts * Created Time: 2022-12-21 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ import { ListNode } from '../modules/ListNode'; /* 基於鏈結串列實現的堆疊 */ class LinkedListStack { private stackPeek: ListNode | null; // 將頭節點作為堆疊頂 private stkSize: number = 0; // 堆疊的長度 constructor() { this.stackPeek = null; } /* 獲取堆疊的長度 */ get size(): number { return this.stkSize; } /* 判斷堆疊是否為空 */ isEmpty(): boolean { return this.size === 0; } /* 入堆疊 */ push(num: number): void { const node = new ListNode(num); node.next = this.stackPeek; this.stackPeek = node; this.stkSize++; } /* 出堆疊 */ pop(): number { const num = this.peek(); if (!this.stackPeek) throw new Error('堆疊為空'); this.stackPeek = this.stackPeek.next; this.stkSize--; return num; } /* 訪問堆疊頂元素 */ peek(): number { if (!this.stackPeek) throw new Error('堆疊為空'); return this.stackPeek.val; } /* 將鏈結串列轉化為 Array 並返回 */ toArray(): number[] { let node = this.stackPeek; const res = new Array(this.size); for (let i = res.length - 1; i >= 0; i--) { res[i] = node!.val; node = node!.next; } return res; } } /* Driver Code */ /* 初始化堆疊 */ const stack = new LinkedListStack(); /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('堆疊 stack = ' + stack.toArray()); /* 訪問堆疊頂元素 */ const peek = stack.peek(); console.log('堆疊頂元素 peek = ' + peek); /* 元素出堆疊 */ const pop = stack.pop(); console.log('出堆疊元素 pop = ' + pop + ',出堆疊後 stack = ' + stack.toArray()); /* 獲取堆疊的長度 */ const size = stack.size; console.log('堆疊的長度 size = ' + size); /* 判斷是否為空 */ const isEmpty = stack.isEmpty(); console.log('堆疊是否為空 = ' + isEmpty); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_stack_and_queue/queue.ts ================================================ /** * File: queue.ts * Created Time: 2022-12-05 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* 初始化佇列 */ // TypeScript 沒有內建的佇列,可以把 Array 當作佇列來使用 const queue: number[] = []; /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); console.log('佇列 queue =', queue); /* 訪問佇列首元素 */ const peek = queue[0]; console.log('佇列首元素 peek =', peek); /* 元素出列 */ // 底層是陣列,因此 shift() 方法的時間複雜度為 O(n) const pop = queue.shift(); console.log('出列元素 pop =', pop, ',出列後 queue = ', queue); /* 獲取佇列的長度 */ const size = queue.length; console.log('佇列長度 size =', size); /* 判斷佇列是否為空 */ const isEmpty = queue.length === 0; console.log('佇列是否為空 = ', isEmpty); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_stack_and_queue/stack.ts ================================================ /** * File: stack.ts * Created Time: 2022-12-04 * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ /* Driver Code */ /* 初始化堆疊 */ // TypeScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 const stack: number[] = []; /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); console.log('堆疊 stack =', stack); /* 訪問堆疊頂元素 */ const peek = stack[stack.length - 1]; console.log('堆疊頂元素 peek =', peek); /* 元素出堆疊 */ const pop = stack.pop(); console.log('出堆疊元素 pop =', pop); console.log('出堆疊後 stack =', stack); /* 獲取堆疊的長度 */ const size = stack.length; console.log('堆疊的長度 size =', size); /* 判斷是否為空 */ const isEmpty = stack.length === 0; console.log('堆疊是否為空 =', isEmpty); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_tree/array_binary_tree.ts ================================================ /** * File: array_binary_tree.js * Created Time: 2023-08-09 * Author: yuan0221 (yl1452491917@gmail.com) */ import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; type Order = 'pre' | 'in' | 'post'; /* 陣列表示下的二元樹類別 */ class ArrayBinaryTree { #tree: (number | null)[]; /* 建構子 */ constructor(arr: (number | null)[]) { this.#tree = arr; } /* 串列容量 */ size(): number { return this.#tree.length; } /* 獲取索引為 i 節點的值 */ val(i: number): number | null { // 若索引越界,則返回 null ,代表空位 if (i < 0 || i >= this.size()) return null; return this.#tree[i]; } /* 獲取索引為 i 節點的左子節點的索引 */ left(i: number): number { return 2 * i + 1; } /* 獲取索引為 i 節點的右子節點的索引 */ right(i: number): number { return 2 * i + 2; } /* 獲取索引為 i 節點的父節點的索引 */ parent(i: number): number { return Math.floor((i - 1) / 2); // 向下整除 } /* 層序走訪 */ levelOrder(): number[] { let res = []; // 直接走訪陣列 for (let i = 0; i < this.size(); i++) { if (this.val(i) !== null) res.push(this.val(i)); } return res; } /* 深度優先走訪 */ #dfs(i: number, order: Order, res: (number | null)[]): void { // 若為空位,則返回 if (this.val(i) === null) return; // 前序走訪 if (order === 'pre') res.push(this.val(i)); this.#dfs(this.left(i), order, res); // 中序走訪 if (order === 'in') res.push(this.val(i)); this.#dfs(this.right(i), order, res); // 後序走訪 if (order === 'post') res.push(this.val(i)); } /* 前序走訪 */ preOrder(): (number | null)[] { const res = []; this.#dfs(0, 'pre', res); return res; } /* 中序走訪 */ inOrder(): (number | null)[] { const res = []; this.#dfs(0, 'in', res); return res; } /* 後序走訪 */ postOrder(): (number | null)[] { const res = []; this.#dfs(0, 'post', res); return res; } } /* Driver Code */ // 初始化二元樹 // 這裡藉助了一個從陣列直接生成二元樹的函式 const arr = Array.of( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ); const root = arrToTree(arr); console.log('\n初始化二元樹\n'); console.log('二元樹的陣列表示:'); console.log(arr); console.log('二元樹的鏈結串列表示:'); printTree(root); // 陣列表示下的二元樹類別 const abt = new ArrayBinaryTree(arr); // 訪問節點 const i = 1; const l = abt.left(i); const r = abt.right(i); const p = abt.parent(i); console.log('\n當前節點的索引為 ' + i + ' ,值為 ' + abt.val(i)); console.log( '其左子節點的索引為 ' + l + ' ,值為 ' + (l === null ? 'null' : abt.val(l)) ); console.log( '其右子節點的索引為 ' + r + ' ,值為 ' + (r === null ? 'null' : abt.val(r)) ); console.log( '其父節點的索引為 ' + p + ' ,值為 ' + (p === null ? 'null' : abt.val(p)) ); // 走訪樹 let res = abt.levelOrder(); console.log('\n層序走訪為:' + res); res = abt.preOrder(); console.log('前序走訪為:' + res); res = abt.inOrder(); console.log('中序走訪為:' + res); res = abt.postOrder(); console.log('後序走訪為:' + res); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_tree/avl_tree.ts ================================================ /** * File: avl_tree.ts * Created Time: 2023-02-06 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* AVL 樹*/ class AVLTree { root: TreeNode; /* 建構子 */ constructor() { this.root = null; //根節點 } /* 獲取節點高度 */ height(node: TreeNode): number { // 空節點高度為 -1 ,葉節點高度為 0 return node === null ? -1 : node.height; } /* 更新節點高度 */ private updateHeight(node: TreeNode): void { // 節點高度等於最高子樹高度 + 1 node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; } /* 獲取平衡因子 */ balanceFactor(node: TreeNode): number { // 空節點平衡因子為 0 if (node === null) return 0; // 節點平衡因子 = 左子樹高度 - 右子樹高度 return this.height(node.left) - this.height(node.right); } /* 右旋操作 */ private rightRotate(node: TreeNode): TreeNode { const child = node.left; const grandChild = child.right; // 以 child 為原點,將 node 向右旋轉 child.right = node; node.left = grandChild; // 更新節點高度 this.updateHeight(node); this.updateHeight(child); // 返回旋轉後子樹的根節點 return child; } /* 左旋操作 */ private leftRotate(node: TreeNode): TreeNode { const child = node.right; const grandChild = child.left; // 以 child 為原點,將 node 向左旋轉 child.left = node; node.right = grandChild; // 更新節點高度 this.updateHeight(node); this.updateHeight(child); // 返回旋轉後子樹的根節點 return child; } /* 執行旋轉操作,使該子樹重新恢復平衡 */ private rotate(node: TreeNode): TreeNode { // 獲取節點 node 的平衡因子 const balanceFactor = this.balanceFactor(node); // 左偏樹 if (balanceFactor > 1) { if (this.balanceFactor(node.left) >= 0) { // 右旋 return this.rightRotate(node); } else { // 先左旋後右旋 node.left = this.leftRotate(node.left); return this.rightRotate(node); } } // 右偏樹 if (balanceFactor < -1) { if (this.balanceFactor(node.right) <= 0) { // 左旋 return this.leftRotate(node); } else { // 先右旋後左旋 node.right = this.rightRotate(node.right); return this.leftRotate(node); } } // 平衡樹,無須旋轉,直接返回 return node; } /* 插入節點 */ insert(val: number): void { this.root = this.insertHelper(this.root, val); } /* 遞迴插入節點(輔助方法) */ private insertHelper(node: TreeNode, val: number): TreeNode { if (node === null) return new TreeNode(val); /* 1. 查詢插入位置並插入節點 */ if (val < node.val) { node.left = this.insertHelper(node.left, val); } else if (val > node.val) { node.right = this.insertHelper(node.right, val); } else { return node; // 重複節點不插入,直接返回 } this.updateHeight(node); // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = this.rotate(node); // 返回子樹的根節點 return node; } /* 刪除節點 */ remove(val: number): void { this.root = this.removeHelper(this.root, val); } /* 遞迴刪除節點(輔助方法) */ private removeHelper(node: TreeNode, val: number): TreeNode { if (node === null) return null; /* 1. 查詢節點並刪除 */ if (val < node.val) { node.left = this.removeHelper(node.left, val); } else if (val > node.val) { node.right = this.removeHelper(node.right, val); } else { if (node.left === null || node.right === null) { const child = node.left !== null ? node.left : node.right; // 子節點數量 = 0 ,直接刪除 node 並返回 if (child === null) { return null; } else { // 子節點數量 = 1 ,直接刪除 node node = child; } } else { // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 let temp = node.right; while (temp.left !== null) { temp = temp.left; } node.right = this.removeHelper(node.right, temp.val); node.val = temp.val; } } this.updateHeight(node); // 更新節點高度 /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ node = this.rotate(node); // 返回子樹的根節點 return node; } /* 查詢節點 */ search(val: number): TreeNode { let cur = this.root; // 迴圈查詢,越過葉節點後跳出 while (cur !== null) { if (cur.val < val) { // 目標節點在 cur 的右子樹中 cur = cur.right; } else if (cur.val > val) { // 目標節點在 cur 的左子樹中 cur = cur.left; } else { // 找到目標節點,跳出迴圈 break; } } // 返回目標節點 return cur; } } function testInsert(tree: AVLTree, val: number): void { tree.insert(val); console.log('\n插入節點 ' + val + ' 後,AVL 樹為'); printTree(tree.root); } function testRemove(tree: AVLTree, val: number): void { tree.remove(val); console.log('\n刪除節點 ' + val + ' 後,AVL 樹為'); printTree(tree.root); } /* Driver Code */ /* 初始化空 AVL 樹 */ const avlTree = new AVLTree(); /* 插入節點 */ // 請關注插入節點後,AVL 樹是如何保持平衡的 testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); testInsert(avlTree, 4); testInsert(avlTree, 5); testInsert(avlTree, 8); testInsert(avlTree, 7); testInsert(avlTree, 9); testInsert(avlTree, 10); testInsert(avlTree, 6); /* 插入重複節點 */ testInsert(avlTree, 7); /* 刪除節點 */ // 請關注刪除節點後,AVL 樹是如何保持平衡的 testRemove(avlTree, 8); // 刪除度為 0 的節點 testRemove(avlTree, 5); // 刪除度為 1 的節點 testRemove(avlTree, 4); // 刪除度為 2 的節點 /* 查詢節點 */ const node = avlTree.search(7); console.log('\n查詢到的節點物件為', node, ',節點值 = ' + node.val); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_tree/binary_search_tree.ts ================================================ /** * File: binary_search_tree.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 二元搜尋樹 */ class BinarySearchTree { private root: TreeNode | null; /* 建構子 */ constructor() { // 初始化空樹 this.root = null; } /* 獲取二元樹根節點 */ getRoot(): TreeNode | null { return this.root; } /* 查詢節點 */ search(num: number): TreeNode | null { let cur = this.root; // 迴圈查詢,越過葉節點後跳出 while (cur !== null) { // 目標節點在 cur 的右子樹中 if (cur.val < num) cur = cur.right; // 目標節點在 cur 的左子樹中 else if (cur.val > num) cur = cur.left; // 找到目標節點,跳出迴圈 else break; } // 返回目標節點 return cur; } /* 插入節點 */ insert(num: number): void { // 若樹為空,則初始化根節點 if (this.root === null) { this.root = new TreeNode(num); return; } let cur: TreeNode | null = this.root, pre: TreeNode | null = null; // 迴圈查詢,越過葉節點後跳出 while (cur !== null) { // 找到重複節點,直接返回 if (cur.val === num) return; pre = cur; // 插入位置在 cur 的右子樹中 if (cur.val < num) cur = cur.right; // 插入位置在 cur 的左子樹中 else cur = cur.left; } // 插入節點 const node = new TreeNode(num); if (pre!.val < num) pre!.right = node; else pre!.left = node; } /* 刪除節點 */ remove(num: number): void { // 若樹為空,直接提前返回 if (this.root === null) return; let cur: TreeNode | null = this.root, pre: TreeNode | null = null; // 迴圈查詢,越過葉節點後跳出 while (cur !== null) { // 找到待刪除節點,跳出迴圈 if (cur.val === num) break; pre = cur; // 待刪除節點在 cur 的右子樹中 if (cur.val < num) cur = cur.right; // 待刪除節點在 cur 的左子樹中 else cur = cur.left; } // 若無待刪除節點,則直接返回 if (cur === null) return; // 子節點數量 = 0 or 1 if (cur.left === null || cur.right === null) { // 當子節點數量 = 0 / 1 時, child = null / 該子節點 const child: TreeNode | null = cur.left !== null ? cur.left : cur.right; // 刪除節點 cur if (cur !== this.root) { if (pre!.left === cur) pre!.left = child; else pre!.right = child; } else { // 若刪除節點為根節點,則重新指定根節點 this.root = child; } } // 子節點數量 = 2 else { // 獲取中序走訪中 cur 的下一個節點 let tmp: TreeNode | null = cur.right; while (tmp!.left !== null) { tmp = tmp!.left; } // 遞迴刪除節點 tmp this.remove(tmp!.val); // 用 tmp 覆蓋 cur cur.val = tmp!.val; } } } /* Driver Code */ /* 初始化二元搜尋樹 */ const bst = new BinarySearchTree(); // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; for (const num of nums) { bst.insert(num); } console.log('\n初始化的二元樹為\n'); printTree(bst.getRoot()); /* 查詢節點 */ const node = bst.search(7); console.log( '\n查詢到的節點物件為 ' + node + ',節點值 = ' + (node ? node.val : 'null') ); /* 插入節點 */ bst.insert(16); console.log('\n插入節點 16 後,二元樹為\n'); printTree(bst.getRoot()); /* 刪除節點 */ bst.remove(1); console.log('\n刪除節點 1 後,二元樹為\n'); printTree(bst.getRoot()); bst.remove(2); console.log('\n刪除節點 2 後,二元樹為\n'); printTree(bst.getRoot()); bst.remove(4); console.log('\n刪除節點 4 後,二元樹為\n'); printTree(bst.getRoot()); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_tree/binary_tree.ts ================================================ /** * File: binary_tree.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ import { TreeNode } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 初始化二元樹 */ // 初始化節點 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // 構建節點之間的引用(指標) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; console.log('\n初始化二元樹\n'); printTree(n1); /* 插入與刪除節點 */ const P = new TreeNode(0); // 在 n1 -> n2 中間插入節點 P n1.left = P; P.left = n2; console.log('\n插入節點 P 後\n'); printTree(n1); // 刪除節點 P n1.left = n2; console.log('\n刪除節點 P 後\n'); printTree(n1); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_tree/binary_tree_bfs.ts ================================================ /** * File: binary_tree_bfs.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; /* 層序走訪 */ function levelOrder(root: TreeNode | null): number[] { // 初始化佇列,加入根節點 const queue = [root]; // 初始化一個串列,用於儲存走訪序列 const list: number[] = []; while (queue.length) { let node = queue.shift() as TreeNode; // 隊列出隊 list.push(node.val); // 儲存節點值 if (node.left) { queue.push(node.left); // 左子節點入列 } if (node.right) { queue.push(node.right); // 右子節點入列 } } return list; } /* Driver Code */ /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n初始化二元樹\n'); printTree(root); /* 層序走訪 */ const list = levelOrder(root); console.log('\n層序走訪的節點列印序列 = ' + list); export {}; ================================================ FILE: zh-hant/codes/typescript/chapter_tree/binary_tree_dfs.ts ================================================ /** * File: binary_tree_dfs.ts * Created Time: 2022-12-14 * Author: Justin (xiefahit@gmail.com) */ import { type TreeNode } from '../modules/TreeNode'; import { arrToTree } from '../modules/TreeNode'; import { printTree } from '../modules/PrintUtil'; // 初始化串列,用於儲存走訪序列 const list: number[] = []; /* 前序走訪 */ function preOrder(root: TreeNode | null): void { if (root === null) { return; } // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 list.push(root.val); preOrder(root.left); preOrder(root.right); } /* 中序走訪 */ function inOrder(root: TreeNode | null): void { if (root === null) { return; } // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 inOrder(root.left); list.push(root.val); inOrder(root.right); } /* 後序走訪 */ function postOrder(root: TreeNode | null): void { if (root === null) { return; } // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 postOrder(root.left); postOrder(root.right); list.push(root.val); } /* Driver Code */ /* 初始化二元樹 */ // 這裡藉助了一個從陣列直接生成二元樹的函式 const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n初始化二元樹\n'); printTree(root); /* 前序走訪 */ list.length = 0; preOrder(root); console.log('\n前序走訪的節點列印序列 = ' + list); /* 中序走訪 */ list.length = 0; inOrder(root); console.log('\n中序走訪的節點列印序列 = ' + list); /* 後序走訪 */ list.length = 0; postOrder(root); console.log('\n後序走訪的節點列印序列 = ' + list); export {}; ================================================ FILE: zh-hant/codes/typescript/modules/ListNode.ts ================================================ /** * File: ListNode.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) */ /* 鏈結串列節點 */ class ListNode { val: number; next: ListNode | null; constructor(val?: number, next?: ListNode | null) { this.val = val === undefined ? 0 : val; this.next = next === undefined ? null : next; } } /* 將陣列反序列化為鏈結串列 */ function arrToLinkedList(arr: number[]): ListNode | null { const dum: ListNode = new ListNode(0); let head = dum; for (const val of arr) { head.next = new ListNode(val); head = head.next; } return dum.next; } export { ListNode, arrToLinkedList }; ================================================ FILE: zh-hant/codes/typescript/modules/PrintUtil.ts ================================================ /** * File: PrintUtil.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ import { ListNode } from './ListNode'; import { TreeNode, arrToTree } from './TreeNode'; /* 列印鏈結串列 */ function printLinkedList(head: ListNode | null): void { const list: string[] = []; while (head !== null) { list.push(head.val.toString()); head = head.next; } console.log(list.join(' -> ')); } class Trunk { prev: Trunk | null; str: string; constructor(prev: Trunk | null, str: string) { this.prev = prev; this.str = str; } } /** * 列印二元樹 * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ function printTree(root: TreeNode | null) { printTreeHelper(root, null, false); } /* 列印二元樹 */ function printTreeHelper( root: TreeNode | null, prev: Trunk | null, isRight: boolean ) { if (root === null) { return; } let prev_str = ' '; const trunk = new Trunk(prev, prev_str); printTreeHelper(root.right, trunk, true); if (prev === null) { trunk.str = '———'; } else if (isRight) { trunk.str = '/———'; prev_str = ' |'; } else { trunk.str = '\\———'; prev.str = prev_str; } showTrunks(trunk); console.log(' ' + root.val); if (prev) { prev.str = prev_str; } trunk.str = ' |'; printTreeHelper(root.left, trunk, false); } function showTrunks(p: Trunk | null) { if (p === null) { return; } showTrunks(p.prev); process.stdout.write(p.str); } /* 列印堆積 */ function printHeap(arr: number[]): void { console.log('堆積的陣列表示:'); console.log(arr); console.log('堆積的樹狀表示:'); const root = arrToTree(arr); printTree(root); } export { printLinkedList, printTree, printHeap }; ================================================ FILE: zh-hant/codes/typescript/modules/TreeNode.ts ================================================ /** * File: TreeNode.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ /* 二元樹節點 */ class TreeNode { val: number; // 節點值 height: number; // 節點高度 left: TreeNode | null; // 左子節點指標 right: TreeNode | null; // 右子節點指標 constructor( val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null ) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } /* 將陣列反序列化為二元樹 */ function arrToTree(arr: (number | null)[], i: number = 0): TreeNode | null { if (i < 0 || i >= arr.length || arr[i] === null) { return null; } let root = new TreeNode(arr[i]); root.left = arrToTree(arr, 2 * i + 1); root.right = arrToTree(arr, 2 * i + 2); return root; } export { TreeNode, arrToTree }; ================================================ FILE: zh-hant/codes/typescript/modules/Vertex.ts ================================================ /** * File: Vertex.ts * Created Time: 2023-02-15 * Author: Zhuo Qinyue (1403450829@qq.com) */ /* 頂點類別 */ class Vertex { val: number; constructor(val: number) { this.val = val; } /* 輸入值串列 vals ,返回頂點串列 vets */ public static valsToVets(vals: number[]): Vertex[] { const vets: Vertex[] = []; for (let i = 0; i < vals.length; i++) { vets[i] = new Vertex(vals[i]); } return vets; } /* 輸入頂點串列 vets ,返回值串列 vals */ public static vetsToVals(vets: Vertex[]): number[] { const vals: number[] = []; for (const vet of vets) { vals.push(vet.val); } return vals; } } export { Vertex }; ================================================ FILE: zh-hant/codes/typescript/tsconfig.json ================================================ { "compilerOptions": { "baseUrl": ".", "module": "esnext", "moduleResolution": "node", "types": ["@types/node"], "noEmit": true, "target": "esnext", }, "include": ["chapter_*/*.ts"], "exclude": ["node_modules"] } ================================================ FILE: zh-hant/codes/zig/.gitignore ================================================ zig-out/ zig-cache/ ================================================ FILE: zh-hant/codes/zig/build.zig ================================================ // File: build.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) //! Zig Version: 0.14.1 //! Build Command: zig build //! Run Command: zig build run | zig build run_* //! Test Command: zig build test | zig build test -Dtest-filter=* const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const chapters = [_][]const u8{ "chapter_computational_complexity", "chapter_array_and_linkedlist", "chapter_stack_and_queue", "chapter_hashing", "chapter_tree", "chapter_heap", "chapter_searching", "chapter_sorting", "chapter_dynamic_programming", }; const test_step = b.step("test", "Run unit tests"); const test_filters = b.option([]const []const u8, "test-filter", "Skip tests that do not match any filter") orelse &[0][]const u8{}; buildChapterExeModules(b, target, optimize, &chapters, test_step, test_filters); buildMainExeModule(b, target, optimize); } fn buildChapterExeModules( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, chapter_dirs: []const []const u8, test_step: *std.Build.Step, test_filters: []const []const u8, ) void { for (chapter_dirs) |chapter_dir_name| { const chapter_dir_path = std.fs.path.join(b.allocator, &[_][]const u8{chapter_dir_name}) catch continue; var chapter_dir = std.fs.cwd().openDir(chapter_dir_path, .{ .iterate = true }) catch continue; defer chapter_dir.close(); var it = chapter_dir.iterate(); while (it.next() catch continue) |chapter_dir_entry| { if (chapter_dir_entry.kind != .file or !std.mem.endsWith(u8, chapter_dir_entry.name, ".zig")) continue; const exe_mod = buildExeModuleFromChapterDirEntry(b, target, optimize, chapter_dir_name, chapter_dir_entry) catch continue; addTestStepToExeModule(b, test_step, exe_mod, test_filters); } } } fn buildExeModuleFromChapterDirEntry( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, chapter_dir_name: []const u8, chapter_dir_entry: std.fs.Dir.Entry, ) !*std.Build.Module { const zig_file_path = try std.fs.path.join(b.allocator, &[_][]const u8{ chapter_dir_name, chapter_dir_entry.name }); const zig_file_name = chapter_dir_entry.name[0 .. chapter_dir_entry.name.len - 4]; // abstract zig file name from xxx.zig // 這裡臨時只新增陣列和鏈結串列章節部分,後續修改完後全部放開 const new_algo_names = [_][]const u8{ "array", "linked_list", "list", "my_list", "iteration", "recursion", "space_complexity", "time_complexity", "worst_best_time_complexity", }; var can_run = false; for (new_algo_names) |name| { if (std.mem.eql(u8, zig_file_name, name)) { can_run = true; } } if (!can_run) { return error.CanNotRunUseOldZigCodes; } // std.debug.print("now run zig file name = {s}\n", .{zig_file_name}); const exe_mod = b.createModule(.{ .root_source_file = b.path(zig_file_path), .target = target, .optimize = optimize, }); const exe = b.addExecutable(.{ .name = zig_file_name, .root_module = exe_mod, }); const utils_mod = createUtilsModule(b, target, optimize); exe_mod.addImport("utils", utils_mod); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const step_name = try std.fmt.allocPrint(b.allocator, "run_{s}", .{zig_file_name}); const step_desc = try std.fmt.allocPrint(b.allocator, "Run {s}/{s}.zig", .{ chapter_dir_name, zig_file_name }); const run_step = b.step(step_name, step_desc); run_step.dependOn(&run_cmd.step); return exe_mod; } fn buildMainExeModule( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, ) void { const exe_mod = b.createModule(.{ .root_source_file = b.path("main.zig"), .target = target, .optimize = optimize, }); const utils_mod = createUtilsModule(b, target, optimize); exe_mod.addImport("utils", utils_mod); const exe = b.addExecutable(.{ .name = "main", .root_module = exe_mod, }); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run all hello algo zig"); run_step.dependOn(&run_cmd.step); } fn createUtilsModule(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Module { const utils_mod = b.createModule(.{ .root_source_file = b.path("utils/utils.zig"), .target = target, .optimize = optimize, }); return utils_mod; } fn addTestStepToExeModule(b: *std.Build, test_step: *std.Build.Step, exe_mod: *std.Build.Module, test_filters: []const []const u8) void { const exe_unit_tests = b.addTest(.{ .root_module = exe_mod, .filters = test_filters, }); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); test_step.dependOn(&run_exe_unit_tests.step); } ================================================ FILE: zh-hant/codes/zig/chapter_array_and_linkedlist/array.zig ================================================ // File: array.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // 隨機訪問元素 pub fn randomAccess(nums: []const i32) i32 { // 在區間 [0, nums.len) 中隨機抽取一個整數 const random_index = std.crypto.random.intRangeLessThan(usize, 0, nums.len); // 獲取並返回隨機元素 const randomNum = nums[random_index]; return randomNum; } // 擴展陣列長度 pub fn extend(allocator: std.mem.Allocator, nums: []const i32, enlarge: usize) ![]i32 { // 初始化一個擴展長度後的陣列 const res = try allocator.alloc(i32, nums.len + enlarge); @memset(res, 0); // 將原陣列中的所有元素複製到新陣列 std.mem.copyForwards(i32, res, nums); // 返回擴展後的新陣列 return res; } // 在陣列的索引 index 處插入元素 num pub fn insert(nums: []i32, num: i32, index: usize) void { // 把索引 index 以及之後的所有元素向後移動一位 var i = nums.len - 1; while (i > index) : (i -= 1) { nums[i] = nums[i - 1]; } // 將 num 賦給 index 處的元素 nums[index] = num; } // 刪除索引 index 處的元素 pub fn remove(nums: []i32, index: usize) void { // 把索引 index 之後的所有元素向前移動一位 var i = index; while (i < nums.len - 1) : (i += 1) { nums[i] = nums[i + 1]; } } // 走訪陣列 pub fn traverse(nums: []const i32) void { var count: i32 = 0; // 透過索引走訪陣列 var i: usize = 0; while (i < nums.len) : (i += 1) { count += nums[i]; } // 直接走訪陣列元素 count = 0; for (nums) |num| { count += num; } // 同時走訪資料索引和元素 for (nums, 0..) |num, index| { count += nums[index]; count += num; } } // 在陣列中查詢指定元素 pub fn find(nums: []i32, target: i32) i32 { for (nums, 0..) |num, i| { if (num == target) return @intCast(i); } return -1; } // Driver Code pub fn run() !void { // 初始化陣列 const arr = [_]i32{0} ** 5; std.debug.print("陣列 arr = {}\n", .{utils.fmt.slice(&arr)}); // 陣列切片 var array = [_]i32{ 1, 3, 2, 5, 4 }; var known_at_runtime_zero: usize = 0; _ = &known_at_runtime_zero; var nums = array[known_at_runtime_zero..array.len]; // 透過 known_at_runtime_zero 執行時變數將指標變切片 std.debug.print("陣列 nums = {}\n", .{utils.fmt.slice(nums)}); // 隨機訪問 const randomNum = randomAccess(nums); std.debug.print("在 nums 中獲取隨機元素 {}\n", .{randomNum}); // 初始化記憶體分配器 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); // 長度擴展 nums = try extend(allocator, nums, 3); std.debug.print("將陣列長度擴展至 8 ,得到 nums = {}\n", .{utils.fmt.slice(nums)}); // 插入元素 insert(nums, 6, 3); std.debug.print("在索引 3 處插入數字 6 ,得到 nums = {}\n", .{utils.fmt.slice(nums)}); // 刪除元素 remove(nums, 2); std.debug.print("刪除索引 2 處的元素,得到 nums = {}\n", .{utils.fmt.slice(nums)}); // 走訪陣列 traverse(nums); // 查詢元素 const index = find(nums, 3); std.debug.print("在 nums 中查詢元素 3 ,得到索引 = {}\n", .{index}); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "array" { try run(); } ================================================ FILE: zh-hant/codes/zig/chapter_array_and_linkedlist/linked_list.zig ================================================ // File: linked_list.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); const ListNode = utils.ListNode; // 在鏈結串列的節點 n0 之後插入節點 P pub fn insert(comptime T: type, n0: *ListNode(T), P: *ListNode(T)) void { const n1 = n0.next; P.next = n1; n0.next = P; } // 刪除鏈結串列的節點 n0 之後的首個節點 pub fn remove(comptime T: type, n0: *ListNode(T)) void { // n0 -> P -> n1 => n0 -> n1 const P = n0.next; const n1 = P.?.next; n0.next = n1; } // 訪問鏈結串列中索引為 index 的節點 pub fn access(comptime T: type, node: *ListNode(T), index: i32) ?*ListNode(T) { var head: ?*ListNode(T) = node; var i: i32 = 0; while (i < index) : (i += 1) { if (head) |cur| { head = cur.next; } else { return null; } } return head; } // 在鏈結串列中查詢值為 target 的首個節點 pub fn find(comptime T: type, node: *ListNode(T), target: T) i32 { var head: ?*ListNode(T) = node; var index: i32 = 0; while (head) |cur| { if (cur.val == target) return index; head = cur.next; index += 1; } return -1; } // Driver Code pub fn run() void { // 初始化各個節點 var n0 = ListNode(i32){ .val = 1 }; var n1 = ListNode(i32){ .val = 3 }; var n2 = ListNode(i32){ .val = 2 }; var n3 = ListNode(i32){ .val = 5 }; var n4 = ListNode(i32){ .val = 4 }; // 構建節點之間的引用 n0.next = &n1; n1.next = &n2; n2.next = &n3; n3.next = &n4; std.debug.print( "初始化的鏈結串列為 {}\n", .{utils.fmt.linkedList(i32, &n0)}, ); // 插入節點 var tmp = ListNode(i32){ .val = 0 }; insert(i32, &n0, &tmp); std.debug.print( "插入節點後的鏈結串列為 {}\n", .{utils.fmt.linkedList(i32, &n0)}, ); // 刪除節點 remove(i32, &n0); std.debug.print( "刪除節點後的鏈結串列為{}\n", .{utils.fmt.linkedList(i32, &n0)}, ); // 訪問節點 const node = access(i32, &n0, 3); std.debug.print( "鏈結串列中索引 3 處的節點的值 = {}\n", .{node.?.val}, ); // 查詢節點 const index = find(i32, &n0, 2); std.debug.print( "鏈結串列中值為 2 的節點的索引 = {}\n", .{index}, ); std.debug.print("\n", .{}); } pub fn main() void { run(); } test "linked_list" { run(); } ================================================ FILE: zh-hant/codes/zig/chapter_array_and_linkedlist/list.zig ================================================ // File: list.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // Driver Code pub fn run() !void { // 初始化串列 var nums = std.ArrayList(i32).init(std.heap.page_allocator); defer nums.deinit(); // 延遲釋放記憶體 try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); std.debug.print("串列 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 訪問元素 const num = nums.items[1]; std.debug.print("訪問索引 1 處的元素,得到 num = {}\n", .{num}); // 更新元素 nums.items[1] = 0; std.debug.print("將索引 1 處的元素更新為 0 ,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 清空串列 nums.clearRetainingCapacity(); std.debug.print("清空串列後 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 在尾部新增元素 try nums.append(1); try nums.append(3); try nums.append(2); try nums.append(5); try nums.append(4); std.debug.print("新增元素後 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 在中間插入元素 try nums.insert(3, 6); std.debug.print("在索引 3 處插入數字 6 ,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 刪除元素 _ = nums.orderedRemove(3); std.debug.print("刪除索引 3 處的元素,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 透過索引走訪串列 var count: i32 = 0; var i: usize = 0; while (i < nums.items.len) : (i += 1) { count += nums.items[i]; } // 直接走訪串列元素 count = 0; for (nums.items) |x| { count += x; } // 拼接兩個串列 var nums1 = std.ArrayList(i32).init(std.heap.page_allocator); defer nums1.deinit(); try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); try nums.insertSlice(nums.items.len, nums1.items); std.debug.print("將串列 nums1 拼接到 nums 之後,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}); // 排序串列 std.mem.sort(i32, nums.items, {}, comptime std.sort.asc(i32)); std.debug.print("排序串列後 nums = {}\n", .{utils.fmt.slice(nums.items)}); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "list" { try run(); } ================================================ FILE: zh-hant/codes/zig/chapter_array_and_linkedlist/my_list.zig ================================================ // File: my_list.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // 串列類別 const MyList = struct { const Self = @This(); items: []i32, // 陣列(儲存串列元素) capacity: usize, // 串列容量 allocator: std.mem.Allocator, // 記憶體分配器 extend_ratio: usize = 2, // 每次串列擴容的倍數 // 建構子(分配記憶體+初始化串列) pub fn init(allocator: std.mem.Allocator) Self { return Self{ .items = &[_]i32{}, .capacity = 0, .allocator = allocator, }; } // 析構函式(釋放記憶體) pub fn deinit(self: Self) void { self.allocator.free(self.allocatedSlice()); } // 在尾部新增元素 pub fn add(self: *Self, item: i32) !void { // 元素數量超出容量時,觸發擴容機制 const newlen = self.items.len + 1; try self.ensureTotalCapacity(newlen); // 更新元素 self.items.len += 1; const new_item_ptr = &self.items[self.items.len - 1]; new_item_ptr.* = item; } // 獲取串列長度(當前元素數量) pub fn getSize(self: *Self) usize { return self.items.len; } // 獲取串列容量 pub fn getCapacity(self: *Self) usize { return self.capacity; } // 訪問元素 pub fn get(self: *Self, index: usize) i32 { // 索引如果越界,則丟擲異常,下同 if (index < 0 or index >= self.items.len) { @panic("索引越界"); } return self.items[index]; } // 更新元素 pub fn set(self: *Self, index: usize, num: i32) void { // 索引如果越界,則丟擲異常,下同 if (index < 0 or index >= self.items.len) { @panic("索引越界"); } self.items[index] = num; } // 在中間插入元素 pub fn insert(self: *Self, index: usize, item: i32) !void { if (index < 0 or index >= self.items.len) { @panic("索引越界"); } // 元素數量超出容量時,觸發擴容機制 const newlen = self.items.len + 1; try self.ensureTotalCapacity(newlen); // 將索引 index 以及之後的元素都向後移動一位 self.items.len += 1; var i = self.items.len - 1; while (i >= index) : (i -= 1) { self.items[i] = self.items[i - 1]; } self.items[index] = item; } // 刪除元素 pub fn remove(self: *Self, index: usize) i32 { if (index < 0 or index >= self.getSize()) { @panic("索引越界"); } // 將索引 index 之後的元素都向前移動一位 const item = self.items[index]; var i = index; while (i < self.items.len - 1) : (i += 1) { self.items[i] = self.items[i + 1]; } self.items.len -= 1; // 返回被刪除的元素 return item; } // 將串列轉換為陣列 pub fn toArraySlice(self: *Self) ![]i32 { return self.toOwnedSlice(false); } // 返回新的切片並設定是否要重置或清空串列容器 pub fn toOwnedSlice(self: *Self, clear: bool) ![]i32 { const allocator = self.allocator; const old_memory = self.allocatedSlice(); if (allocator.remap(old_memory, self.items.len)) |new_items| { if (clear) { self.* = init(allocator); } return new_items; } const new_memory = try allocator.alloc(i32, self.items.len); @memcpy(new_memory, self.items); if (clear) { self.clearAndFree(); } return new_memory; } // 串列擴容 fn ensureTotalCapacity(self: *Self, new_capacity: usize) !void { if (self.capacity >= new_capacity) return; const capcacity = if (self.capacity == 0) 10 else self.capacity; const better_capacity = capcacity * self.extend_ratio; const old_memory = self.allocatedSlice(); if (self.allocator.remap(old_memory, better_capacity)) |new_memory| { self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } else { const new_memory = try self.allocator.alloc(i32, better_capacity); @memcpy(new_memory[0..self.items.len], self.items); self.allocator.free(old_memory); self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } } fn clearAndFree(self: *Self, allocator: std.mem.Allocator) void { allocator.free(self.allocatedSlice()); self.items.len = 0; self.capacity = 0; } fn allocatedSlice(self: Self) []i32 { return self.items.ptr[0..self.capacity]; } }; // Driver Code pub fn run() !void { var gpa = std.heap.DebugAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // 初始化串列 var nums = MyList.init(allocator); // 延遲釋放記憶體 defer nums.deinit(); // 在尾部新增元素 try nums.add(1); try nums.add(3); try nums.add(2); try nums.add(5); try nums.add(4); std.debug.print("串列 nums = {} ,容量 = {} ,長度 = {}\n", .{ utils.fmt.slice(nums.items), nums.getCapacity(), nums.getSize(), }); // 在中間插入元素 try nums.insert(3, 6); std.debug.print( "在索引 3 處插入數字 6 ,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}, ); // 刪除元素 _ = nums.remove(3); std.debug.print( "刪除索引 3 處的元素,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}, ); // 訪問元素 const num = nums.get(1); std.debug.print("訪問索引 1 處的元素,得到 num = {}\n", .{num}); // 更新元素 nums.set(1, 0); std.debug.print( "將索引 1 處的元素更新為 0 ,得到 nums = {}\n", .{utils.fmt.slice(nums.items)}, ); // 測試擴容機制 var i: i32 = 0; while (i < 10) : (i += 1) { // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 try nums.add(i); } std.debug.print( "擴容後的串列 nums = {} ,容量 = {} ,長度 = {}\n", .{ utils.fmt.slice(nums.items), nums.getCapacity(), nums.getSize(), }, ); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "my_list" { try run(); } ================================================ FILE: zh-hant/codes/zig/chapter_computational_complexity/iteration.zig ================================================ // File: iteration.zig // Created Time: 2023-09-27 // Author: QiLOL (pikaqqpika@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const Allocator = std.mem.Allocator; // for 迴圈 fn forLoop(n: usize) i32 { var res: i32 = 0; // 迴圈求和 1, 2, ..., n-1, n for (1..n + 1) |i| { res += @intCast(i); } return res; } // while 迴圈 fn whileLoop(n: i32) i32 { var res: i32 = 0; var i: i32 = 1; // 初始化條件變數 // 迴圈求和 1, 2, ..., n-1, n while (i <= n) : (i += 1) { res += @intCast(i); } return res; } // while 迴圈(兩次更新) fn whileLoopII(n: i32) i32 { var res: i32 = 0; var i: i32 = 1; // 初始化條件變數 // 迴圈求和 1, 4, 10, ... while (i <= n) : ({ // 更新條件變數 i += 1; i *= 2; }) { res += @intCast(i); } return res; } // 雙層 for 迴圈 fn nestedForLoop(allocator: Allocator, n: usize) ![]const u8 { var res = std.ArrayList(u8).init(allocator); defer res.deinit(); var buffer: [20]u8 = undefined; // 迴圈 i = 1, 2, ..., n-1, n for (1..n + 1) |i| { // 迴圈 j = 1, 2, ..., n-1, n for (1..n + 1) |j| { const str = try std.fmt.bufPrint(&buffer, "({d}, {d}), ", .{ i, j }); try res.appendSlice(str); } } return res.toOwnedSlice(); } // Driver Code pub fn run() !void { var gpa = std.heap.DebugAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const n: i32 = 5; var res: i32 = 0; res = forLoop(n); std.debug.print("for 迴圈的求和結果 res = {}\n", .{res}); res = whileLoop(n); std.debug.print("while 迴圈的求和結果 res = {}\n", .{res}); res = whileLoopII(n); std.debug.print("while 迴圈(兩次更新)求和結果 res = {}\n", .{res}); const resStr = try nestedForLoop(allocator, n); std.debug.print("雙層 for 迴圈的走訪結果 {s}\n", .{resStr}); allocator.free(resStr); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "interation" { try run(); } ================================================ FILE: zh-hant/codes/zig/chapter_computational_complexity/recursion.zig ================================================ // File: recursion.zig // Created Time: 2023-09-27 // Author: QiLOL (pikaqqpika@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // 遞迴函式 fn recur(n: i32) i32 { // 終止條件 if (n == 1) { return 1; } // 遞:遞迴呼叫 const res = recur(n - 1); // 迴:返回結果 return n + res; } // 使用迭代模擬遞迴 fn forLoopRecur(comptime n: i32) i32 { // 使用一個顯式的堆疊來模擬系統呼叫堆疊 var stack: [n]i32 = undefined; var res: i32 = 0; // 遞:遞迴呼叫 var i: usize = n; while (i > 0) { stack[i - 1] = @intCast(i); i -= 1; } // 迴:返回結果 var index: usize = n; while (index > 0) { index -= 1; res += stack[index]; } // res = 1+2+3+...+n return res; } // 尾遞迴函式 fn tailRecur(n: i32, res: i32) i32 { // 終止條件 if (n == 0) { return res; } // 尾遞迴呼叫 return tailRecur(n - 1, res + n); } // 費波那契數列 fn fib(n: i32) i32 { // 終止條件 f(1) = 0, f(2) = 1 if (n == 1 or n == 2) { return n - 1; } // 遞迴呼叫 f(n) = f(n-1) + f(n-2) const res: i32 = fib(n - 1) + fib(n - 2); // 返回結果 f(n) return res; } // Driver Code pub fn run() void { const n: i32 = 5; var res: i32 = 0; res = recur(n); std.debug.print("遞迴函式的求和結果 res = {}\n", .{recur(n)}); res = forLoopRecur(n); std.debug.print("使用迭代模擬遞迴的求和結果 res = {}\n", .{forLoopRecur(n)}); res = tailRecur(n, 0); std.debug.print("尾遞迴函式的求和結果 res = {}\n", .{tailRecur(n, 0)}); res = fib(n); std.debug.print("費波那契數列的第 {} 項為 {}\n", .{ n, fib(n) }); std.debug.print("\n", .{}); } pub fn main() void { run(); } test "recursion" { run(); } ================================================ FILE: zh-hant/codes/zig/chapter_computational_complexity/space_complexity.zig ================================================ // File: space_complexity.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); const ListNode = utils.ListNode; const TreeNode = utils.TreeNode; // 函式 fn function() i32 { // 執行某些操作 return 0; } // 常數階 fn constant(n: i32) void { // 常數、變數、物件佔用 O(1) 空間 const a: i32 = 0; const b: i32 = 0; const nums = [_]i32{0} ** 10000; const node = ListNode(i32){ .val = 0 }; var i: i32 = 0; // 迴圈中的變數佔用 O(1) 空間 while (i < n) : (i += 1) { const c: i32 = 0; _ = c; } // 迴圈中的函式佔用 O(1) 空間 i = 0; while (i < n) : (i += 1) { _ = function(); } _ = a; _ = b; _ = nums; _ = node; } // 線性階 fn linear(comptime n: i32) !void { // 長度為 n 的陣列佔用 O(n) 空間 const nums = [_]i32{0} ** n; // 長度為 n 的串列佔用 O(n) 空間 var nodes = std.ArrayList(i32).init(std.heap.page_allocator); defer nodes.deinit(); var i: i32 = 0; while (i < n) : (i += 1) { try nodes.append(i); } // 長度為 n 的雜湊表佔用 O(n) 空間 var map = std.AutoArrayHashMap(i32, []const u8).init(std.heap.page_allocator); defer map.deinit(); var j: i32 = 0; while (j < n) : (j += 1) { const string = try std.fmt.allocPrint(std.heap.page_allocator, "{d}", .{j}); defer std.heap.page_allocator.free(string); try map.put(i, string); } _ = nums; } // 線性階(遞迴實現) fn linearRecur(comptime n: i32) void { std.debug.print("遞迴 n = {}\n", .{n}); if (n == 1) return; linearRecur(n - 1); } // 平方階 fn quadratic(n: i32) !void { // 二維串列佔用 O(n^2) 空間 var nodes = std.ArrayList(std.ArrayList(i32)).init(std.heap.page_allocator); defer nodes.deinit(); var i: i32 = 0; while (i < n) : (i += 1) { var tmp = std.ArrayList(i32).init(std.heap.page_allocator); defer tmp.deinit(); var j: i32 = 0; while (j < n) : (j += 1) { try tmp.append(0); } try nodes.append(tmp); } } // 平方階(遞迴實現) fn quadraticRecur(comptime n: i32) i32 { if (n <= 0) return 0; const nums = [_]i32{0} ** n; std.debug.print("遞迴 n = {} 中的 nums 長度 = {}\n", .{ n, nums.len }); return quadraticRecur(n - 1); } // 指數階(建立滿二元樹) fn buildTree(allocator: std.mem.Allocator, n: i32) !?*TreeNode(i32) { if (n == 0) return null; const root = try allocator.create(TreeNode(i32)); root.init(0); root.left = try buildTree(allocator, n - 1); root.right = try buildTree(allocator, n - 1); return root; } // 釋放樹的記憶體 fn freeTree(allocator: std.mem.Allocator, root: ?*const TreeNode(i32)) void { if (root == null) return; freeTree(allocator, root.?.left); freeTree(allocator, root.?.right); allocator.destroy(root.?); } // Driver Code pub fn run() !void { var gpa = std.heap.DebugAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const n: i32 = 5; // 常數階 constant(n); // 線性階 try linear(n); linearRecur(n); // 平方階 try quadratic(n); _ = quadraticRecur(n); // 指數階 const root = try buildTree(allocator, n); defer freeTree(allocator, root); std.debug.print("{}\n", .{utils.fmt.tree(i32, root)}); std.debug.print("\n", .{}); } pub fn main() !void { try run(); } test "space_complexity" { try run(); } ================================================ FILE: zh-hant/codes/zig/chapter_computational_complexity/time_complexity.zig ================================================ // File: time_complexity.zig // Created Time: 2022-12-28 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // 常數階 fn constant(n: i32) i32 { _ = n; var count: i32 = 0; const size: i32 = 100_000; var i: i32 = 0; while (i < size) : (i += 1) { count += 1; } return count; } // 線性階 fn linear(n: i32) i32 { var count: i32 = 0; var i: i32 = 0; while (i < n) : (i += 1) { count += 1; } return count; } // 線性階(走訪陣列) fn arrayTraversal(nums: []i32) i32 { var count: i32 = 0; // 迴圈次數與陣列長度成正比 for (nums) |_| { count += 1; } return count; } // 平方階 fn quadratic(n: i32) i32 { var count: i32 = 0; var i: i32 = 0; // 迴圈次數與資料大小 n 成平方關係 while (i < n) : (i += 1) { var j: i32 = 0; while (j < n) : (j += 1) { count += 1; } } return count; } // 平方階(泡沫排序) fn bubbleSort(nums: []i32) i32 { var count: i32 = 0; // 計數器 // 外迴圈:未排序區間為 [0, i] var i: i32 = @as(i32, @intCast(nums.len)) - 1; while (i > 0) : (i -= 1) { var j: usize = 0; // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] const tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; count += 3; // 元素交換包含 3 個單元操作 } } } return count; } // 指數階(迴圈實現) fn exponential(n: i32) i32 { var count: i32 = 0; var bas: i32 = 1; var i: i32 = 0; // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) while (i < n) : (i += 1) { var j: i32 = 0; while (j < bas) : (j += 1) { count += 1; } bas *= 2; } // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count; } // 指數階(遞迴實現) fn expRecur(n: i32) i32 { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } // 對數階(迴圈實現) fn logarithmic(n: i32) i32 { var count: i32 = 0; var n_var: i32 = n; while (n_var > 1) : (n_var = @divTrunc(n_var, 2)) { count += 1; } return count; } // 對數階(遞迴實現) fn logRecur(n: i32) i32 { if (n <= 1) return 0; return logRecur(@divTrunc(n, 2)) + 1; } // 線性對數階 fn linearLogRecur(n: i32) i32 { if (n <= 1) return 1; var count: i32 = linearLogRecur(@divTrunc(n, 2)) + linearLogRecur(@divTrunc(n, 2)); var i: i32 = 0; while (i < n) : (i += 1) { count += 1; } return count; } // 階乘階(遞迴實現) fn factorialRecur(n: i32) i32 { if (n == 0) return 1; var count: i32 = 0; var i: i32 = 0; // 從 1 個分裂出 n 個 while (i < n) : (i += 1) { count += factorialRecur(n - 1); } return count; } // Driver Code pub fn run() void { // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 const n: i32 = 8; std.debug.print("輸入資料大小 n = {}\n", .{n}); var count = constant(n); std.debug.print("常數階的操作數量 = {}\n", .{count}); count = linear(n); std.debug.print("線性階的操作數量 = {}\n", .{count}); var nums = [_]i32{0} ** n; count = arrayTraversal(&nums); std.debug.print("線性階(走訪陣列)的操作數量 = {}\n", .{count}); count = quadratic(n); std.debug.print("平方階的操作數量 = {}\n", .{count}); for (&nums, 0..) |*num, i| { num.* = n - @as(i32, @intCast(i)); // [n,n-1,...,2,1] } count = bubbleSort(&nums); std.debug.print("平方階(泡沫排序)的操作數量 = {}\n", .{count}); count = exponential(n); std.debug.print("指數階(迴圈實現)的操作數量 = {}\n", .{count}); count = expRecur(n); std.debug.print("指數階(遞迴實現)的操作數量 = {}\n", .{count}); count = logarithmic(n); std.debug.print("對數階(迴圈實現)的操作數量 = {}\n", .{count}); count = logRecur(n); std.debug.print("對數階(遞迴實現)的操作數量 = {}\n", .{count}); count = linearLogRecur(n); std.debug.print("線性對數階(遞迴實現)的操作數量 = {}\n", .{count}); count = factorialRecur(n); std.debug.print("階乘階(遞迴實現)的操作數量 = {}\n", .{count}); std.debug.print("\n", .{}); } pub fn main() !void { run(); } test "time_complexity" { run(); } ================================================ FILE: zh-hant/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig ================================================ // File: worst_best_time_complexity.zig // Created Time: 2022-12-28 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const utils = @import("utils"); // 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 pub fn randomNumbers(comptime n: usize) [n]i32 { var nums: [n]i32 = undefined; // 生成陣列 nums = { 1, 2, 3, ..., n } for (&nums, 0..) |*num, i| { num.* = @as(i32, @intCast(i)) + 1; } // 隨機打亂陣列元素 const rand = std.crypto.random; rand.shuffle(i32, &nums); return nums; } // 查詢陣列 nums 中數字 1 所在索引 pub fn findOne(nums: []i32) i32 { for (nums, 0..) |num, i| { // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) if (num == 1) return @intCast(i); } return -1; } // Driver Code pub fn run() void { var i: i32 = 0; while (i < 10) : (i += 1) { const n: usize = 100; var nums = randomNumbers(n); const index = findOne(&nums); std.debug.print("陣列 [ 1, 2, ..., n ] 被打亂後 = ", .{}); std.debug.print("{}\n", .{utils.fmt.slice(nums)}); std.debug.print("數字 1 的索引為 {}\n", .{index}); } std.debug.print("\n", .{}); } pub fn main() !void { run(); } test "worst_best_time_complexity" { run(); } ================================================ FILE: zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig ================================================ // File: climbing_stairs_backtrack.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 回溯 fn backtrack(choices: []i32, state: i32, n: i32, res: std.ArrayList(i32)) void { // 當爬到第 n 階時,方案數量加 1 if (state == n) { res.items[0] = res.items[0] + 1; } // 走訪所有選擇 for (choices) |choice| { // 剪枝:不允許越過第 n 階 if (state + choice > n) { continue; } // 嘗試:做出選擇,更新狀態 backtrack(choices, state + choice, n, res); // 回退 } } // 爬樓梯:回溯 fn climbingStairsBacktrack(n: usize) !i32 { var choices = [_]i32{ 1, 2 }; // 可選擇向上爬 1 階或 2 階 var state: i32 = 0; // 從第 0 階開始爬 var res = std.ArrayList(i32).init(std.heap.page_allocator); defer res.deinit(); try res.append(0); // 使用 res[0] 記錄方案數量 backtrack(&choices, state, @intCast(n), res); return res.items[0]; } // Driver Code pub fn main() !void { var n: usize = 9; var res = try climbingStairsBacktrack(n); std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig ================================================ // File: climbing_stairs_constraint_dp.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 帶約束爬樓梯:動態規劃 fn climbingStairsConstraintDP(comptime n: usize) i32 { if (n == 1 or n == 2) { return 1; } // 初始化 dp 表,用於儲存子問題的解 var dp = [_][3]i32{ [_]i32{ -1, -1, -1 } } ** (n + 1); // 初始狀態:預設最小子問題的解 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; // 狀態轉移:從較小子問題逐步求解較大子問題 for (3..n + 1) |i| { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; } return dp[n][1] + dp[n][2]; } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsConstraintDP(n); std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig ================================================ // File: climbing_stairs_dfs.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 搜尋 fn dfs(i: usize) i32 { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 or i == 2) { return @intCast(i); } // dp[i] = dp[i-1] + dp[i-2] var count = dfs(i - 1) + dfs(i - 2); return count; } // 爬樓梯:搜尋 fn climbingStairsDFS(comptime n: usize) i32 { return dfs(n); } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsDFS(n); std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig ================================================ // File: climbing_stairs_dfs_mem.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 記憶化搜尋 fn dfs(i: usize, mem: []i32) i32 { // 已知 dp[1] 和 dp[2] ,返回之 if (i == 1 or i == 2) { return @intCast(i); } // 若存在記錄 dp[i] ,則直接返回之 if (mem[i] != -1) { return mem[i]; } // dp[i] = dp[i-1] + dp[i-2] var count = dfs(i - 1, mem) + dfs(i - 2, mem); // 記錄 dp[i] mem[i] = count; return count; } // 爬樓梯:記憶化搜尋 fn climbingStairsDFSMem(comptime n: usize) i32 { // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 var mem = [_]i32{ -1 } ** (n + 1); return dfs(n, &mem); } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsDFSMem(n); std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig ================================================ // File: climbing_stairs_dp.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 爬樓梯:動態規劃 fn climbingStairsDP(comptime n: usize) i32 { // 已知 dp[1] 和 dp[2] ,返回之 if (n == 1 or n == 2) { return @intCast(n); } // 初始化 dp 表,用於儲存子問題的解 var dp = [_]i32{-1} ** (n + 1); // 初始狀態:預設最小子問題的解 dp[1] = 1; dp[2] = 2; // 狀態轉移:從較小子問題逐步求解較大子問題 for (3..n + 1) |i| { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } // 爬樓梯:空間最佳化後的動態規劃 fn climbingStairsDPComp(comptime n: usize) i32 { if (n == 1 or n == 2) { return @intCast(n); } var a: i32 = 1; var b: i32 = 2; for (3..n + 1) |_| { var tmp = b; b = a + b; a = tmp; } return b; } // Driver Code pub fn main() !void { comptime var n: usize = 9; var res = climbingStairsDP(n); std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); res = climbingStairsDPComp(n); std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_dynamic_programming/coin_change.zig ================================================ // File: coin_change.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 零錢兌換:動態規劃 fn coinChangeDP(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; comptime var max = amt + 1; // 初始化 dp 表 var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); // 狀態轉移:首行首列 for (1..amt + 1) |a| { dp[0][a] = max; } // 狀態轉移:其餘行和列 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[i][a] = @min(dp[i - 1][a], dp[i][a - @as(usize, @intCast(coins[i - 1]))] + 1); } } } if (dp[n][amt] != max) { return @intCast(dp[n][amt]); } else { return -1; } } // 零錢兌換:空間最佳化後的動態規劃 fn coinChangeDPComp(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; comptime var max = amt + 1; // 初始化 dp 表 var dp = [_]i32{0} ** (amt + 1); @memset(&dp, max); dp[0] = 0; // 狀態轉移 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[a] = @min(dp[a], dp[a - @as(usize, @intCast(coins[i - 1]))] + 1); } } } if (dp[amt] != max) { return @intCast(dp[amt]); } else { return -1; } } // Driver Code pub fn main() !void { comptime var coins = [_]i32{ 1, 2, 5 }; comptime var amt: usize = 4; // 動態規劃 var res = coinChangeDP(&coins, amt); std.debug.print("湊到目標金額所需的最少硬幣數量為 {}\n", .{res}); // 空間最佳化後的動態規劃 res = coinChangeDPComp(&coins, amt); std.debug.print("湊到目標金額所需的最少硬幣數量為 {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_dynamic_programming/coin_change_ii.zig ================================================ // File: coin_change_ii.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 零錢兌換 II:動態規劃 fn coinChangeIIDP(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; // 初始化 dp 表 var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); // 初始化首列 for (0..n + 1) |i| { dp[i][0] = 1; } // 狀態轉移 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 若超過目標金額,則不選硬幣 i dp[i][a] = dp[i - 1][a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[i][a] = dp[i - 1][a] + dp[i][a - @as(usize, @intCast(coins[i - 1]))]; } } } return dp[n][amt]; } // 零錢兌換 II:空間最佳化後的動態規劃 fn coinChangeIIDPComp(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; // 初始化 dp 表 var dp = [_]i32{0} ** (amt + 1); dp[0] = 1; // 狀態轉移 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 若超過目標金額,則不選硬幣 i dp[a] = dp[a]; } else { // 不選和選硬幣 i 這兩種方案的較小值 dp[a] = dp[a] + dp[a - @as(usize, @intCast(coins[i - 1]))]; } } } return dp[amt]; } // Driver Code pub fn main() !void { comptime var coins = [_]i32{ 1, 2, 5 }; comptime var amt: usize = 5; // 動態規劃 var res = coinChangeIIDP(&coins, amt); std.debug.print("湊出目標金額的硬幣組合數量為 {}\n", .{res}); // 空間最佳化後的動態規劃 res = coinChangeIIDPComp(&coins, amt); std.debug.print("湊出目標金額的硬幣組合數量為 {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_dynamic_programming/edit_distance.zig ================================================ // File: edit_distance.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 編輯距離:暴力搜尋 fn editDistanceDFS(comptime s: []const u8, comptime t: []const u8, i: usize, j: usize) i32 { // 若 s 和 t 都為空,則返回 0 if (i == 0 and j == 0) { return 0; } // 若 s 為空,則返回 t 長度 if (i == 0) { return @intCast(j); } // 若 t 為空,則返回 s 長度 if (j == 0) { return @intCast(i); } // 若兩字元相等,則直接跳過此兩字元 if (s[i - 1] == t[j - 1]) { return editDistanceDFS(s, t, i - 1, j - 1); } // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 var insert = editDistanceDFS(s, t, i, j - 1); var delete = editDistanceDFS(s, t, i - 1, j); var replace = editDistanceDFS(s, t, i - 1, j - 1); // 返回最少編輯步數 return @min(@min(insert, delete), replace) + 1; } // 編輯距離:記憶化搜尋 fn editDistanceDFSMem(comptime s: []const u8, comptime t: []const u8, mem: anytype, i: usize, j: usize) i32 { // 若 s 和 t 都為空,則返回 0 if (i == 0 and j == 0) { return 0; } // 若 s 為空,則返回 t 長度 if (i == 0) { return @intCast(j); } // 若 t 為空,則返回 s 長度 if (j == 0) { return @intCast(i); } // 若已有記錄,則直接返回之 if (mem[i][j] != -1) { return mem[i][j]; } // 若兩字元相等,則直接跳過此兩字元 if (s[i - 1] == t[j - 1]) { return editDistanceDFSMem(s, t, mem, i - 1, j - 1); } // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 var insert = editDistanceDFSMem(s, t, mem, i, j - 1); var delete = editDistanceDFSMem(s, t, mem, i - 1, j); var replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); // 記錄並返回最少編輯步數 mem[i][j] = @min(@min(insert, delete), replace) + 1; return mem[i][j]; } // 編輯距離:動態規劃 fn editDistanceDP(comptime s: []const u8, comptime t: []const u8) i32 { comptime var n = s.len; comptime var m = t.len; var dp = [_][m + 1]i32{[_]i32{0} ** (m + 1)} ** (n + 1); // 狀態轉移:首行首列 for (1..n + 1) |i| { dp[i][0] = @intCast(i); } for (1..m + 1) |j| { dp[0][j] = @intCast(j); } // 狀態轉移:其餘行和列 for (1..n + 1) |i| { for (1..m + 1) |j| { if (s[i - 1] == t[j - 1]) { // 若兩字元相等,則直接跳過此兩字元 dp[i][j] = dp[i - 1][j - 1]; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[i][j] = @min(@min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } } return dp[n][m]; } // 編輯距離:空間最佳化後的動態規劃 fn editDistanceDPComp(comptime s: []const u8, comptime t: []const u8) i32 { comptime var n = s.len; comptime var m = t.len; var dp = [_]i32{0} ** (m + 1); // 狀態轉移:首行 for (1..m + 1) |j| { dp[j] = @intCast(j); } // 狀態轉移:其餘行 for (1..n + 1) |i| { // 狀態轉移:首列 var leftup = dp[0]; // 暫存 dp[i-1, j-1] dp[0] = @intCast(i); // 狀態轉移:其餘列 for (1..m + 1) |j| { var temp = dp[j]; if (s[i - 1] == t[j - 1]) { // 若兩字元相等,則直接跳過此兩字元 dp[j] = leftup; } else { // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 dp[j] = @min(@min(dp[j - 1], dp[j]), leftup) + 1; } leftup = temp; // 更新為下一輪的 dp[i-1, j-1] } } return dp[m]; } // Driver Code pub fn main() !void { const s = "bag"; const t = "pack"; comptime var n = s.len; comptime var m = t.len; // 暴力搜尋 var res = editDistanceDFS(s, t, n, m); std.debug.print("將 {s} 更改為 {s} 最少需要編輯 {} 步\n", .{ s, t, res }); // 記憶搜尋 var mem = [_][m + 1]i32{[_]i32{-1} ** (m + 1)} ** (n + 1); res = editDistanceDFSMem(s, t, @constCast(&mem), n, m); std.debug.print("將 {s} 更改為 {s} 最少需要編輯 {} 步\n", .{ s, t, res }); // 動態規劃 res = editDistanceDP(s, t); std.debug.print("將 {s} 更改為 {s} 最少需要編輯 {} 步\n", .{ s, t, res }); // 空間最佳化後的動態規劃 res = editDistanceDPComp(s, t); std.debug.print("將 {s} 更改為 {s} 最少需要編輯 {} 步\n", .{ s, t, res }); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_dynamic_programming/knapsack.zig ================================================ // File: knapsack.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 0-1 背包:暴力搜尋 fn knapsackDFS(wgt: []i32, val: []i32, i: usize, c: usize) i32 { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i == 0 or c == 0) { return 0; } // 若超過背包容量,則只能選擇不放入背包 if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 var no = knapsackDFS(wgt, val, i - 1, c); var yes = knapsackDFS(wgt, val, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; // 返回兩種方案中價值更大的那一個 return @max(no, yes); } // 0-1 背包:記憶化搜尋 fn knapsackDFSMem(wgt: []i32, val: []i32, mem: anytype, i: usize, c: usize) i32 { // 若已選完所有物品或背包無剩餘容量,則返回價值 0 if (i == 0 or c == 0) { return 0; } // 若已有記錄,則直接返回 if (mem[i][c] != -1) { return mem[i][c]; } // 若超過背包容量,則只能選擇不放入背包 if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } // 計算不放入和放入物品 i 的最大價值 var no = knapsackDFSMem(wgt, val, mem, i - 1, c); var yes = knapsackDFSMem(wgt, val, mem, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; // 記錄並返回兩種方案中價值更大的那一個 mem[i][c] = @max(no, yes); return mem[i][c]; } // 0-1 背包:動態規劃 fn knapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // 初始化 dp 表 var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); // 狀態轉移 for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = @max(dp[i - 1][c], dp[i - 1][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[n][cap]; } // 0-1 背包:空間最佳化後的動態規劃 fn knapsackDPComp(wgt: []i32, val: []i32, comptime cap: usize) i32 { var n = wgt.len; // 初始化 dp 表 var dp = [_]i32{0} ** (cap + 1); // 狀態轉移 for (1..n + 1) |i| { // 倒序走訪 var c = cap; while (c > 0) : (c -= 1) { if (wgt[i - 1] < c) { // 不選和選物品 i 這兩種方案的較大值 dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[cap]; } // Driver Code pub fn main() !void { comptime var wgt = [_]i32{ 10, 20, 30, 40, 50 }; comptime var val = [_]i32{ 50, 120, 150, 210, 240 }; comptime var cap = 50; comptime var n = wgt.len; // 暴力搜尋 var res = knapsackDFS(&wgt, &val, n, cap); std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); // 記憶搜尋 var mem = [_][cap + 1]i32{[_]i32{-1} ** (cap + 1)} ** (n + 1); res = knapsackDFSMem(&wgt, &val, @constCast(&mem), n, cap); std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); // 動態規劃 res = knapsackDP(&wgt, &val, cap); std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); // 空間最佳化後的動態規劃 res = knapsackDPComp(&wgt, &val, cap); std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig ================================================ // File: min_cost_climbing_stairs_dp.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 爬樓梯最小代價:動態規劃 fn minCostClimbingStairsDP(comptime cost: []i32) i32 { comptime var n = cost.len - 1; if (n == 1 or n == 2) { return cost[n]; } // 初始化 dp 表,用於儲存子問題的解 var dp = [_]i32{-1} ** (n + 1); // 初始狀態:預設最小子問題的解 dp[1] = cost[1]; dp[2] = cost[2]; // 狀態轉移:從較小子問題逐步求解較大子問題 for (3..n + 1) |i| { dp[i] = @min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } // 爬樓梯最小代價:空間最佳化後的動態規劃 fn minCostClimbingStairsDPComp(cost: []i32) i32 { var n = cost.len - 1; if (n == 1 or n == 2) { return cost[n]; } var a = cost[1]; var b = cost[2]; // 狀態轉移:從較小子問題逐步求解較大子問題 for (3..n + 1) |i| { var tmp = b; b = @min(a, tmp) + cost[i]; a = tmp; } return b; } // Driver Code pub fn main() !void { comptime var cost = [_]i32{ 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; std.debug.print("輸入樓梯的代價串列為 {any}\n", .{cost}); var res = minCostClimbingStairsDP(&cost); std.debug.print("輸入樓梯的代價串列為 {}\n", .{res}); res = minCostClimbingStairsDPComp(&cost); std.debug.print("輸入樓梯的代價串列為 {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_dynamic_programming/min_path_sum.zig ================================================ // File: min_path_sum.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 最小路徑和:暴力搜尋 fn minPathSumDFS(grid: anytype, i: i32, j: i32) i32 { // 若為左上角單元格,則終止搜尋 if (i == 0 and j == 0) { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 or j < 0) { return std.math.maxInt(i32); } // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 var up = minPathSumDFS(grid, i - 1, j); var left = minPathSumDFS(grid, i, j - 1); // 返回從左上角到 (i, j) 的最小路徑代價 return @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; } // 最小路徑和:記憶化搜尋 fn minPathSumDFSMem(grid: anytype, mem: anytype, i: i32, j: i32) i32 { // 若為左上角單元格,則終止搜尋 if (i == 0 and j == 0) { return grid[0][0]; } // 若行列索引越界,則返回 +∞ 代價 if (i < 0 or j < 0) { return std.math.maxInt(i32); } // 若已有記錄,則直接返回 if (mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] != -1) { return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; } // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 var up = minPathSumDFSMem(grid, mem, i - 1, j); var left = minPathSumDFSMem(grid, mem, i, j - 1); // 返回從左上角到 (i, j) 的最小路徑代價 // 記錄並返回左上角到 (i, j) 的最小路徑代價 mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] = @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; } // 最小路徑和:動態規劃 fn minPathSumDP(comptime grid: anytype) i32 { comptime var n = grid.len; comptime var m = grid[0].len; // 初始化 dp 表 var dp = [_][m]i32{[_]i32{0} ** m} ** n; dp[0][0] = grid[0][0]; // 狀態轉移:首行 for (1..m) |j| { dp[0][j] = dp[0][j - 1] + grid[0][j]; } // 狀態轉移:首列 for (1..n) |i| { dp[i][0] = dp[i - 1][0] + grid[i][0]; } // 狀態轉移:其餘行和列 for (1..n) |i| { for (1..m) |j| { dp[i][j] = @min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; } } return dp[n - 1][m - 1]; } // 最小路徑和:空間最佳化後的動態規劃 fn minPathSumDPComp(comptime grid: anytype) i32 { comptime var n = grid.len; comptime var m = grid[0].len; // 初始化 dp 表 var dp = [_]i32{0} ** m; // 狀態轉移:首行 dp[0] = grid[0][0]; for (1..m) |j| { dp[j] = dp[j - 1] + grid[0][j]; } // 狀態轉移:其餘行 for (1..n) |i| { // 狀態轉移:首列 dp[0] = dp[0] + grid[i][0]; for (1..m) |j| { dp[j] = @min(dp[j - 1], dp[j]) + grid[i][j]; } } return dp[m - 1]; } // Driver Code pub fn main() !void { comptime var grid = [_][4]i32{ [_]i32{ 1, 3, 1, 5 }, [_]i32{ 2, 2, 4, 2 }, [_]i32{ 5, 3, 2, 1 }, [_]i32{ 4, 3, 5, 2 }, }; comptime var n = grid.len; comptime var m = grid[0].len; // 暴力搜尋 var res = minPathSumDFS(&grid, n - 1, m - 1); std.debug.print("從左上角到右下角的最小路徑和為 {}\n", .{res}); // 記憶化搜尋 var mem = [_][m]i32{[_]i32{-1} ** m} ** n; res = minPathSumDFSMem(&grid, &mem, n - 1, m - 1); std.debug.print("從左上角到右下角的最小路徑和為 {}\n", .{res}); // 動態規劃 res = minPathSumDP(&grid); std.debug.print("從左上角到右下角的最小路徑和為 {}\n", .{res}); // 空間最佳化後的動態規劃 res = minPathSumDPComp(&grid); std.debug.print("從左上角到右下角的最小路徑和為 {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig ================================================ // File: unbounded_knapsack.zig // Created Time: 2023-07-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 完全背包:動態規劃 fn unboundedKnapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // 初始化 dp 表 var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); // 狀態轉移 for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[i][c] = dp[i - 1][c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[i][c] = @max(dp[i - 1][c], dp[i][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[n][cap]; } // 完全背包:空間最佳化後的動態規劃 fn unboundedKnapsackDPComp(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // 初始化 dp 表 var dp = [_]i32{0} ** (cap + 1); // 狀態轉移 for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // 若超過背包容量,則不選物品 i dp[c] = dp[c]; } else { // 不選和選物品 i 這兩種方案的較大值 dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[cap]; } // Driver Code pub fn main() !void { comptime var wgt = [_]i32{ 1, 2, 3 }; comptime var val = [_]i32{ 5, 11, 15 }; comptime var cap = 4; // 動態規劃 var res = unboundedKnapsackDP(&wgt, &val, cap); std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); // 空間最佳化後的動態規劃 res = unboundedKnapsackDPComp(&wgt, &val, cap); std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_hashing/array_hash_map.zig ================================================ // File: array_hash_map.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 鍵值對 const Pair = struct { key: usize = undefined, val: []const u8 = undefined, pub fn init(key: usize, val: []const u8) Pair { return Pair { .key = key, .val = val, }; } }; // 基於陣列實現的雜湊表 pub fn ArrayHashMap(comptime T: type) type { return struct { bucket: ?std.ArrayList(?T) = null, mem_allocator: std.mem.Allocator = undefined, const Self = @This(); // 建構子 pub fn init(self: *Self, allocator: std.mem.Allocator) !void { self.mem_allocator = allocator; // 初始化一個長度為 100 的桶(陣列) self.bucket = std.ArrayList(?T).init(self.mem_allocator); var i: i32 = 0; while (i < 100) : (i += 1) { try self.bucket.?.append(null); } } // 析構函式 pub fn deinit(self: *Self) void { if (self.bucket != null) self.bucket.?.deinit(); } // 雜湊函式 fn hashFunc(key: usize) usize { var index = key % 100; return index; } // 查詢操作 pub fn get(self: *Self, key: usize) []const u8 { var index = hashFunc(key); var pair = self.bucket.?.items[index]; return pair.?.val; } // 新增操作 pub fn put(self: *Self, key: usize, val: []const u8) !void { var pair = Pair.init(key, val); var index = hashFunc(key); self.bucket.?.items[index] = pair; } // 刪除操作 pub fn remove(self: *Self, key: usize) !void { var index = hashFunc(key); // 置為 null ,代表刪除 self.bucket.?.items[index] = null; } // 獲取所有鍵值對 pub fn pairSet(self: *Self) !std.ArrayList(T) { var entry_set = std.ArrayList(T).init(self.mem_allocator); for (self.bucket.?.items) |item| { if (item == null) continue; try entry_set.append(item.?); } return entry_set; } // 獲取所有鍵 pub fn keySet(self: *Self) !std.ArrayList(usize) { var key_set = std.ArrayList(usize).init(self.mem_allocator); for (self.bucket.?.items) |item| { if (item == null) continue; try key_set.append(item.?.key); } return key_set; } // 獲取所有值 pub fn valueSet(self: *Self) !std.ArrayList([]const u8) { var value_set = std.ArrayList([]const u8).init(self.mem_allocator); for (self.bucket.?.items) |item| { if (item == null) continue; try value_set.append(item.?.val); } return value_set; } // 列印雜湊表 pub fn print(self: *Self) !void { var entry_set = try self.pairSet(); defer entry_set.deinit(); for (entry_set.items) |item| { std.debug.print("{} -> {s}\n", .{item.key, item.val}); } } }; } // Driver Code pub fn main() !void { // 初始化雜湊表 var map = ArrayHashMap(Pair){}; try map.init(std.heap.page_allocator); defer map.deinit(); // 新增操作 // 在雜湊表中新增鍵值對 (key, value) try map.put(12836, "小哈"); try map.put(15937, "小囉"); try map.put(16750, "小算"); try map.put(13276, "小法"); try map.put(10583, "小鴨"); std.debug.print("\n新增完成後,雜湊表為\nKey -> Value\n", .{}); try map.print(); // 查詢操作 // 向雜湊表中輸入鍵 key ,得到值 value var name = map.get(15937); std.debug.print("\n輸入學號 15937 ,查詢到姓名 {s}\n", .{name}); // 刪除操作 // 在雜湊表中刪除鍵值對 (key, value) try map.remove(10583); std.debug.print("\n刪除 10583 後,雜湊表為\nKey -> Value\n", .{}); try map.print(); // 走訪雜湊表 std.debug.print("\n走訪鍵值對 Key->Value\n", .{}); var entry_set = try map.pairSet(); for (entry_set.items) |kv| { std.debug.print("{} -> {s}\n", .{kv.key, kv.val}); } defer entry_set.deinit(); std.debug.print("\n單獨走訪鍵 Key\n", .{}); var key_set = try map.keySet(); for (key_set.items) |key| { std.debug.print("{}\n", .{key}); } defer key_set.deinit(); std.debug.print("\n單獨走訪值 value\n", .{}); var value_set = try map.valueSet(); for (value_set.items) |val| { std.debug.print("{s}\n", .{val}); } defer value_set.deinit(); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_hashing/hash_map.zig ================================================ // File: hash_map.zig // Created Time: 2023-01-13 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // 初始化雜湊表 var map = std.AutoHashMap(i32, []const u8).init(std.heap.page_allocator); // 延遲釋放記憶體 defer map.deinit(); // 新增操作 // 在雜湊表中新增鍵值對 (key, value) try map.put(12836, "小哈"); try map.put(15937, "小囉"); try map.put(16750, "小算"); try map.put(13276, "小法"); try map.put(10583, "小鴨"); std.debug.print("\n新增完成後,雜湊表為\nKey -> Value\n", .{}); inc.PrintUtil.printHashMap(i32, []const u8, map); // 查詢操作 // 向雜湊表中輸入鍵 key ,得到值 value var name = map.get(15937).?; std.debug.print("\n輸入學號 15937 ,查詢到姓名 {s}\n", .{name}); // 刪除操作 // 在雜湊表中刪除鍵值對 (key, value) _ = map.remove(10583); std.debug.print("\n刪除 10583 後,雜湊表為\nKey -> Value\n", .{}); inc.PrintUtil.printHashMap(i32, []const u8, map); // 走訪雜湊表 std.debug.print("\n走訪鍵值對 Key->Value\n", .{}); inc.PrintUtil.printHashMap(i32, []const u8, map); std.debug.print("\n單獨走訪鍵 Key\n", .{}); var it = map.iterator(); while (it.next()) |kv| { std.debug.print("{}\n", .{kv.key_ptr.*}); } std.debug.print("\n單獨走訪值 value\n", .{}); it = map.iterator(); while (it.next()) |kv| { std.debug.print("{s}\n", .{kv.value_ptr.*}); } _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_heap/heap.zig ================================================ // File: heap.zig // Created Time: 2023-01-14 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); fn lessThan(context: void, a: i32, b: i32) std.math.Order { _ = context; return std.math.order(a, b); } fn greaterThan(context: void, a: i32, b: i32) std.math.Order { return lessThan(context, a, b).invert(); } fn testPush(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype, val: T) !void { try heap.add(val); //元素入堆積 std.debug.print("\n元素 {} 入堆積後\n", .{val}); try inc.PrintUtil.printHeap(T, mem_allocator, heap); } fn testPop(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype) !void { var val = heap.remove(); //堆積頂元素出堆積 std.debug.print("\n堆積頂元素 {} 出堆積後\n", .{val}); try inc.PrintUtil.printHeap(T, mem_allocator, heap); } // Driver Code pub fn main() !void { // 初始化記憶體分配器 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // 初始化堆積 // 初始化小頂堆積 const PQlt = std.PriorityQueue(i32, void, lessThan); var min_heap = PQlt.init(std.heap.page_allocator, {}); defer min_heap.deinit(); // 初始化大頂堆積 const PQgt = std.PriorityQueue(i32, void, greaterThan); var max_heap = PQgt.init(std.heap.page_allocator, {}); defer max_heap.deinit(); std.debug.print("\n以下測試樣例為大頂堆積", .{}); // 元素入堆積 try testPush(i32, mem_allocator, &max_heap, 1); try testPush(i32, mem_allocator, &max_heap, 3); try testPush(i32, mem_allocator, &max_heap, 2); try testPush(i32, mem_allocator, &max_heap, 5); try testPush(i32, mem_allocator, &max_heap, 4); // 獲取堆積頂元素 var peek = max_heap.peek().?; std.debug.print("\n堆積頂元素為 {}\n", .{peek}); // 堆積頂元素出堆積 try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); try testPop(i32, mem_allocator, &max_heap); // 獲取堆積的大小 var size = max_heap.len; std.debug.print("\n堆積元素數量為 {}\n", .{size}); // 判斷堆積是否為空 var is_empty = if (max_heap.len == 0) true else false; std.debug.print("\n堆積是否為空 {}\n", .{is_empty}); // 輸入串列並建堆積 try min_heap.addSlice(&[_]i32{ 1, 3, 2, 5, 4 }); std.debug.print("\n輸入串列並建立小頂堆積後\n", .{}); try inc.PrintUtil.printHeap(i32, mem_allocator, min_heap); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_heap/my_heap.zig ================================================ // File: my_heap.zig // Created Time: 2023-01-14 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 堆積類別簡易實現 pub fn MaxHeap(comptime T: type) type { return struct { const Self = @This(); max_heap: ?std.ArrayList(T) = null, // 使用串列而非陣列,這樣無須考慮擴容問題 // 建構子,根據輸入串列建堆積 pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []const T) !void { if (self.max_heap != null) return; self.max_heap = std.ArrayList(T).init(allocator); // 將串列元素原封不動新增進堆積 try self.max_heap.?.appendSlice(nums); // 堆積化除葉節點以外的其他所有節點 var i: usize = parent(self.size() - 1) + 1; while (i > 0) : (i -= 1) { try self.siftDown(i - 1); } } // 析構方法,釋放記憶體 pub fn deinit(self: *Self) void { if (self.max_heap != null) self.max_heap.?.deinit(); } // 獲取左子節點的索引 fn left(i: usize) usize { return 2 * i + 1; } // 獲取右子節點的索引 fn right(i: usize) usize { return 2 * i + 2; } // 獲取父節點的索引 fn parent(i: usize) usize { // return (i - 1) / 2; // 向下整除 return @divFloor(i - 1, 2); } // 交換元素 fn swap(self: *Self, i: usize, j: usize) !void { var tmp = self.max_heap.?.items[i]; try self.max_heap.?.replaceRange(i, 1, &[_]T{self.max_heap.?.items[j]}); try self.max_heap.?.replaceRange(j, 1, &[_]T{tmp}); } // 獲取堆積大小 pub fn size(self: *Self) usize { return self.max_heap.?.items.len; } // 判斷堆積是否為空 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // 訪問堆積頂元素 pub fn peek(self: *Self) T { return self.max_heap.?.items[0]; } // 元素入堆積 pub fn push(self: *Self, val: T) !void { // 新增節點 try self.max_heap.?.append(val); // 從底至頂堆積化 try self.siftUp(self.size() - 1); } // 從節點 i 開始,從底至頂堆積化 fn siftUp(self: *Self, i_: usize) !void { var i = i_; while (true) { // 獲取節點 i 的父節點 var p = parent(i); // 當“越過根節點”或“節點無須修復”時,結束堆積化 if (p < 0 or self.max_heap.?.items[i] <= self.max_heap.?.items[p]) break; // 交換兩節點 try self.swap(i, p); // 迴圈向上堆積化 i = p; } } // 元素出堆積 pub fn pop(self: *Self) !T { // 判斷處理 if (self.isEmpty()) unreachable; // 交換根節點與最右葉節點(交換首元素與尾元素) try self.swap(0, self.size() - 1); // 刪除節點 var val = self.max_heap.?.pop(); // 從頂至底堆積化 try self.siftDown(0); // 返回堆積頂元素 return val; } // 從節點 i 開始,從頂至底堆積化 fn siftDown(self: *Self, i_: usize) !void { var i = i_; while (true) { // 判斷節點 i, l, r 中值最大的節點,記為 ma var l = left(i); var r = right(i); var ma = i; if (l < self.size() and self.max_heap.?.items[l] > self.max_heap.?.items[ma]) ma = l; if (r < self.size() and self.max_heap.?.items[r] > self.max_heap.?.items[ma]) ma = r; // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 if (ma == i) break; // 交換兩節點 try self.swap(i, ma); // 迴圈向下堆積化 i = ma; } } fn lessThan(context: void, a: T, b: T) std.math.Order { _ = context; return std.math.order(a, b); } fn greaterThan(context: void, a: T, b: T) std.math.Order { return lessThan(context, a, b).invert(); } // 列印堆積(二元樹) pub fn print(self: *Self, mem_allocator: std.mem.Allocator) !void { const PQgt = std.PriorityQueue(T, void, greaterThan); var queue = PQgt.init(std.heap.page_allocator, {}); defer queue.deinit(); try queue.addSlice(self.max_heap.?.items); try inc.PrintUtil.printHeap(T, mem_allocator, queue); } }; } // Driver Code pub fn main() !void { // 初始化記憶體分配器 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // 初始化大頂堆積 var max_heap = MaxHeap(i32){}; try max_heap.init(std.heap.page_allocator, &[_]i32{ 9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2 }); defer max_heap.deinit(); std.debug.print("\n輸入串列並建堆積後\n", .{}); try max_heap.print(mem_allocator); // 獲取堆積頂元素 var peek = max_heap.peek(); std.debug.print("\n堆積頂元素為 {}\n", .{peek}); // 元素入堆積 const val = 7; try max_heap.push(val); std.debug.print("\n元素 {} 入堆積後\n", .{val}); try max_heap.print(mem_allocator); // 堆積頂元素出堆積 peek = try max_heap.pop(); std.debug.print("\n堆積頂元素 {} 出堆積後\n", .{peek}); try max_heap.print(mem_allocator); // 獲取堆積的大小 var size = max_heap.size(); std.debug.print("\n堆積元素數量為 {}", .{size}); // 判斷堆積是否為空 var is_empty = max_heap.isEmpty(); std.debug.print("\n堆積是否為空 {}\n", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_searching/binary_search.zig ================================================ // File: binary_search.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 二分搜尋(雙閉區間) fn binarySearch(comptime T: type, nums: std.ArrayList(T), target: T) T { // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 var i: usize = 0; var j: usize = nums.items.len - 1; // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) while (i <= j) { var m = i + (j - i) / 2; // 計算中點索引 m if (nums.items[m] < target) { // 此情況說明 target 在區間 [m+1, j] 中 i = m + 1; } else if (nums.items[m] > target) { // 此情況說明 target 在區間 [i, m-1] 中 j = m - 1; } else { // 找到目標元素,返回其索引 return @intCast(m); } } // 未找到目標元素,返回 -1 return -1; } // 二分搜尋(左閉右開區間) fn binarySearchLCRO(comptime T: type, nums: std.ArrayList(T), target: T) T { // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 var i: usize = 0; var j: usize = nums.items.len; // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) while (i <= j) { var m = i + (j - i) / 2; // 計算中點索引 m if (nums.items[m] < target) { // 此情況說明 target 在區間 [m+1, j) 中 i = m + 1; } else if (nums.items[m] > target) { // 此情況說明 target 在區間 [i, m) 中 j = m; } else { // 找到目標元素,返回其索引 return @intCast(m); } } // 未找到目標元素,返回 -1 return -1; } // Driver Code pub fn main() !void { var target: i32 = 6; var nums = std.ArrayList(i32).init(std.heap.page_allocator); defer nums.deinit(); try nums.appendSlice(&[_]i32{ 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }); // 二分搜尋(雙閉區間) var index = binarySearch(i32, nums, target); std.debug.print("目標元素 6 的索引 = {}\n", .{index}); // 二分搜尋(左閉右開區間) index = binarySearchLCRO(i32, nums, target); std.debug.print("目標元素 6 的索引 = {}\n", .{index}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_searching/hashing_search.zig ================================================ // File: hashing_search.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 雜湊查詢(陣列) fn hashingSearchArray(comptime T: type, map: std.AutoHashMap(T, T), target: T) T { // 雜湊表的 key: 目標元素,value: 索引 // 若雜湊表中無此 key ,返回 -1 if (map.getKey(target) == null) return -1; return map.get(target).?; } // 雜湊查詢(鏈結串列) fn hashingSearchLinkedList(comptime T: type, map: std.AutoHashMap(T, *inc.ListNode(T)), target: T) ?*inc.ListNode(T) { // 雜湊表的 key: 目標節點值,value: 節點物件 // 若雜湊表中無此 key ,返回 null if (map.getKey(target) == null) return null; return map.get(target); } // Driver Code pub fn main() !void { var target: i32 = 3; // 雜湊查詢(陣列) var nums = [_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; // 初始化雜湊表 var map = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); defer map.deinit(); for (nums, 0..) |num, i| { try map.put(num, @as(i32, @intCast(i))); // key: 元素,value: 索引 } var index = hashingSearchArray(i32, map, target); std.debug.print("目標元素 3 的索引 = {}\n", .{index}); // 雜湊查詢(鏈結串列) var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var head = try inc.ListUtil.arrToLinkedList(i32, mem_allocator, &nums); // 初始化雜湊表 var map1 = std.AutoHashMap(i32, *inc.ListNode(i32)).init(std.heap.page_allocator); defer map1.deinit(); while (head != null) { try map1.put(head.?.val, head.?); head = head.?.next; } var node = hashingSearchLinkedList(i32, map1, target); std.debug.print("目標節點值 3 的對應節點物件為 ", .{}); try inc.PrintUtil.printLinkedList(i32, node); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_searching/linear_search.zig ================================================ // File: linear_search.zig // Created Time: 2023-01-13 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 線性查詢(陣列) fn linearSearchArray(comptime T: type, nums: std.ArrayList(T), target: T) T { // 走訪陣列 for (nums.items, 0..) |num, i| { // 找到目標元素, 返回其索引 if (num == target) { return @intCast(i); } } // 未找到目標元素,返回 -1 return -1; } // 線性查詢(鏈結串列) pub fn linearSearchLinkedList(comptime T: type, node: ?*inc.ListNode(T), target: T) ?*inc.ListNode(T) { var head = node; // 走訪鏈結串列 while (head != null) { // 找到目標節點,返回之 if (head.?.val == target) return head; head = head.?.next; } return null; } // Driver Code pub fn main() !void { var target: i32 = 3; // 在陣列中執行線性查詢 var nums = std.ArrayList(i32).init(std.heap.page_allocator); defer nums.deinit(); try nums.appendSlice(&[_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }); var index = linearSearchArray(i32, nums, target); std.debug.print("目標元素 3 的索引 = {}\n", .{index}); // 在鏈結串列中執行線性查詢 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var head = try inc.ListUtil.listToLinkedList(i32, mem_allocator, nums); var node = linearSearchLinkedList(i32, head, target); std.debug.print("目標節點值 3 的對應節點物件為 ", .{}); try inc.PrintUtil.printLinkedList(i32, node); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_searching/two_sum.zig ================================================ // File: two_sum.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 方法一:暴力列舉 pub fn twoSumBruteForce(nums: []i32, target: i32) ?[2]i32 { var size: usize = nums.len; var i: usize = 0; // 兩層迴圈,時間複雜度為 O(n^2) while (i < size - 1) : (i += 1) { var j = i + 1; while (j < size) : (j += 1) { if (nums[i] + nums[j] == target) { return [_]i32{@intCast(i), @intCast(j)}; } } } return null; } // 方法二:輔助雜湊表 pub fn twoSumHashTable(nums: []i32, target: i32) !?[2]i32 { var size: usize = nums.len; // 輔助雜湊表,空間複雜度為 O(n) var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); defer dic.deinit(); var i: usize = 0; // 單層迴圈,時間複雜度為 O(n) while (i < size) : (i += 1) { if (dic.contains(target - nums[i])) { return [_]i32{dic.get(target - nums[i]).?, @intCast(i)}; } try dic.put(nums[i], @intCast(i)); } return null; } pub fn main() !void { // ======= Test Case ======= var nums = [_]i32{ 2, 7, 11, 15 }; var target: i32 = 9; // ====== Driver Code ====== // 方法一 var res = twoSumBruteForce(&nums, target).?; std.debug.print("方法一 res = ", .{}); inc.PrintUtil.printArray(i32, &res); // 方法二 res = (try twoSumHashTable(&nums, target)).?; std.debug.print("\n方法二 res = ", .{}); inc.PrintUtil.printArray(i32, &res); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_sorting/bubble_sort.zig ================================================ // File: bubble_sort.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 泡沫排序 fn bubbleSort(nums: []i32) void { // 外迴圈:未排序區間為 [0, i] var i: usize = nums.len - 1; while (i > 0) : (i -= 1) { var j: usize = 0; // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] var tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; } } } } // 泡沫排序(標誌最佳化) fn bubbleSortWithFlag(nums: []i32) void { // 外迴圈:未排序區間為 [0, i] var i: usize = nums.len - 1; while (i > 0) : (i -= 1) { var flag = false; // 初始化標誌位 var j: usize = 0; // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // 交換 nums[j] 與 nums[j + 1] var tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; flag = true; } } if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 } } // Driver Code pub fn main() !void { var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; bubbleSort(&nums); std.debug.print("泡沫排序完成後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); var nums1 = [_]i32{ 4, 1, 3, 1, 5, 2 }; bubbleSortWithFlag(&nums1); std.debug.print("\n泡沫排序完成後 nums1 = ", .{}); inc.PrintUtil.printArray(i32, &nums1); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_sorting/insertion_sort.zig ================================================ // File: insertion_sort.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 插入排序 fn insertionSort(nums: []i32) void { // 外迴圈:已排序區間為 [0, i-1] var i: usize = 1; while (i < nums.len) : (i += 1) { var base = nums[i]; var j: usize = i; // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 while (j >= 1 and nums[j - 1] > base) : (j -= 1) { nums[j] = nums[j - 1]; // 將 nums[j] 向右移動一位 } nums[j] = base; // 將 base 賦值到正確位置 } } // Driver Code pub fn main() !void { var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; insertionSort(&nums); std.debug.print("插入排序完成後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_sorting/merge_sort.zig ================================================ // File: merge_sort.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 合併左子陣列和右子陣列 // 左子陣列區間 [left, mid] // 右子陣列區間 [mid + 1, right] fn merge(nums: []i32, left: usize, mid: usize, right: usize) !void { // 初始化輔助陣列 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var tmp = try mem_allocator.alloc(i32, right + 1 - left); std.mem.copy(i32, tmp, nums[left..right+1]); // 左子陣列的起始索引和結束索引 var leftStart = left - left; var leftEnd = mid - left; // 右子陣列的起始索引和結束索引 var rightStart = mid + 1 - left; var rightEnd = right - left; // i, j 分別指向左子陣列、右子陣列的首元素 var i = leftStart; var j = rightStart; // 透過覆蓋原陣列 nums 來合併左子陣列和右子陣列 var k = left; while (k <= right) : (k += 1) { // 若“左子陣列已全部合併完”,則選取右子陣列元素,並且 j++ if (i > leftEnd) { nums[k] = tmp[j]; j += 1; // 否則,若“右子陣列已全部合併完”或“左子陣列元素 <= 右子陣列元素”,則選取左子陣列元素,並且 i++ } else if (j > rightEnd or tmp[i] <= tmp[j]) { nums[k] = tmp[i]; i += 1; // 否則,若“左右子陣列都未全部合併完”且“左子陣列元素 > 右子陣列元素”,則選取右子陣列元素,並且 j++ } else { nums[k] = tmp[j]; j += 1; } } } // 合併排序 fn mergeSort(nums: []i32, left: usize, right: usize) !void { // 終止條件 if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 // 劃分階段 var mid = left + (right - left) / 2; // 計算中點 try mergeSort(nums, left, mid); // 遞迴左子陣列 try mergeSort(nums, mid + 1, right); // 遞迴右子陣列 // 合併階段 try merge(nums, left, mid, right); } // Driver Code pub fn main() !void { // 合併排序 var nums = [_]i32{ 7, 3, 2, 6, 0, 1, 5, 4 }; try mergeSort(&nums, 0, nums.len - 1); std.debug.print("合併排序完成後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_sorting/quick_sort.zig ================================================ // File: quick_sort.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 快速排序類別 const QuickSort = struct { // 元素交換 pub fn swap(nums: []i32, i: usize, j: usize) void { var tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } // 哨兵劃分 pub fn partition(nums: []i32, left: usize, right: usize) usize { // 以 nums[left] 為基準數 var i = left; var j = right; while (i < j) { while (i < j and nums[j] >= nums[left]) j -= 1; // 從右向左找首個小於基準數的元素 while (i < j and nums[i] <= nums[left]) i += 1; // 從左向右找首個大於基準數的元素 swap(nums, i, j); // 交換這兩個元素 } swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } // 快速排序 pub fn quickSort(nums: []i32, left: usize, right: usize) void { // 子陣列長度為 1 時終止遞迴 if (left >= right) return; // 哨兵劃分 var pivot = partition(nums, left, right); // 遞迴左子陣列、右子陣列 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } }; // 快速排序類別(中位基準數最佳化) const QuickSortMedian = struct { // 元素交換 pub fn swap(nums: []i32, i: usize, j: usize) void { var tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } // 選取三個候選元素的中位數 pub fn medianThree(nums: []i32, left: usize, mid: usize, right: usize) usize { var l = nums[left]; var m = nums[mid]; var r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; // m 在 l 和 r 之間 if ((m <= l && l <= r) || (r <= l && l <= m)) return left; // l 在 m 和 r 之間 return right; } // 哨兵劃分(三數取中值) pub fn partition(nums: []i32, left: usize, right: usize) usize { // 選取三個候選元素的中位數 var med = medianThree(nums, left, (left + right) / 2, right); // 將中位數交換至陣列最左端 swap(nums, left, med); // 以 nums[left] 為基準數 var i = left; var j = right; while (i < j) { while (i < j and nums[j] >= nums[left]) j -= 1; // 從右向左找首個小於基準數的元素 while (i < j and nums[i] <= nums[left]) i += 1; // 從左向右找首個大於基準數的元素 swap(nums, i, j); // 交換這兩個元素 } swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } // 快速排序 pub fn quickSort(nums: []i32, left: usize, right: usize) void { // 子陣列長度為 1 時終止遞迴 if (left >= right) return; // 哨兵劃分 var pivot = partition(nums, left, right); if (pivot == 0) return; // 遞迴左子陣列、右子陣列 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } }; // 快速排序類別(遞迴深度最佳化) const QuickSortTailCall = struct { // 元素交換 pub fn swap(nums: []i32, i: usize, j: usize) void { var tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } // 哨兵劃分 pub fn partition(nums: []i32, left: usize, right: usize) usize { // 以 nums[left] 為基準數 var i = left; var j = right; while (i < j) { while (i < j and nums[j] >= nums[left]) j -= 1; // 從右向左找首個小於基準數的元素 while (i < j and nums[i] <= nums[left]) i += 1; // 從左向右找首個大於基準數的元素 swap(nums, i, j); // 交換這兩個元素 } swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 return i; // 返回基準數的索引 } // 快速排序(遞迴深度最佳化) pub fn quickSort(nums: []i32, left_: usize, right_: usize) void { var left = left_; var right = right_; // 子陣列長度為 1 時終止遞迴 while (left < right) { // 哨兵劃分操作 var pivot = partition(nums, left, right); // 對兩個子陣列中較短的那個執行快速排序 if (pivot - left < right - pivot) { quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] } else { quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] } } } }; // Driver Code pub fn main() !void { // 快速排序 var nums = [_]i32{ 2, 4, 1, 0, 3, 5 }; QuickSort.quickSort(&nums, 0, nums.len - 1); std.debug.print("快速排序完成後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); // 快速排序(中位基準數最佳化) var nums1 = [_]i32{ 2, 4, 1, 0, 3, 5 }; QuickSortMedian.quickSort(&nums1, 0, nums1.len - 1); std.debug.print("\n快速排序(中位基準數最佳化)完成後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums1); // 快速排序(遞迴深度最佳化) var nums2 = [_]i32{ 2, 4, 1, 0, 3, 5 }; QuickSortTailCall.quickSort(&nums2, 0, nums2.len - 1); std.debug.print("\n快速排序(遞迴深度最佳化)完成後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums2); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_sorting/radix_sort.zig ================================================ // File: radix_sort.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) fn digit(num: i32, exp: i32) i32 { // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 return @mod(@divFloor(num, exp), 10); } // 計數排序(根據 nums 第 k 位排序) fn countingSortDigit(nums: []i32, exp: i32) !void { // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); // defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); var counter = try mem_allocator.alloc(usize, 10); @memset(counter, 0); var n = nums.len; // 統計 0~9 各數字的出現次數 for (nums) |num| { var d: u32 = @bitCast(digit(num, exp)); // 獲取 nums[i] 第 k 位,記為 d counter[d] += 1; // 統計數字 d 的出現次數 } // 求前綴和,將“出現個數”轉換為“陣列索引” var i: usize = 1; while (i < 10) : (i += 1) { counter[i] += counter[i - 1]; } // 倒序走訪,根據桶內統計結果,將各元素填入 res var res = try mem_allocator.alloc(i32, n); i = n - 1; while (i >= 0) : (i -= 1) { var d: u32 = @bitCast(digit(nums[i], exp)); var j = counter[d] - 1; // 獲取 d 在陣列中的索引 j res[j] = nums[i]; // 將當前元素填入索引 j counter[d] -= 1; // 將 d 的數量減 1 if (i == 0) break; } // 使用結果覆蓋原陣列 nums i = 0; while (i < n) : (i += 1) { nums[i] = res[i]; } } // 基數排序 fn radixSort(nums: []i32) !void { // 獲取陣列的最大元素,用於判斷最大位數 var m: i32 = std.math.minInt(i32); for (nums) |num| { if (num > m) m = num; } // 按照從低位到高位的順序走訪 var exp: i32 = 1; while (exp <= m) : (exp *= 10) { // 對陣列元素的第 k 位執行計數排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) try countingSortDigit(nums, exp); } } // Driver Code pub fn main() !void { // 基數排序 var nums = [_]i32{ 23, 12, 3, 4, 788, 192 }; try radixSort(&nums); std.debug.print("基數排序完成後 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_stack_and_queue/array_queue.zig ================================================ // File: array_queue.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 基於環形陣列實現的佇列 pub fn ArrayQueue(comptime T: type) type { return struct { const Self = @This(); nums: []T = undefined, // 用於儲存佇列元素的陣列 cap: usize = 0, // 佇列容量 front: usize = 0, // 佇列首指標,指向佇列首元素 queSize: usize = 0, // 尾指標,指向佇列尾 + 1 mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 // 建構子(分配記憶體+初始化陣列) pub fn init(self: *Self, allocator: std.mem.Allocator, cap: usize) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.cap = cap; self.nums = try self.mem_allocator.alloc(T, self.cap); @memset(self.nums, @as(T, 0)); } // 析構函式(釋放記憶體) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 獲取佇列的容量 pub fn capacity(self: *Self) usize { return self.cap; } // 獲取佇列的長度 pub fn size(self: *Self) usize { return self.queSize; } // 判斷佇列是否為空 pub fn isEmpty(self: *Self) bool { return self.queSize == 0; } // 入列 pub fn push(self: *Self, num: T) !void { if (self.size() == self.capacity()) { std.debug.print("佇列已滿\n", .{}); return; } // 計算佇列尾指標,指向佇列尾索引 + 1 // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 var rear = (self.front + self.queSize) % self.capacity(); // 在尾節點後新增 num self.nums[rear] = num; self.queSize += 1; } // 出列 pub fn pop(self: *Self) T { var num = self.peek(); // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 self.front = (self.front + 1) % self.capacity(); self.queSize -= 1; return num; } // 訪問佇列首元素 pub fn peek(self: *Self) T { if (self.isEmpty()) @panic("佇列為空"); return self.nums[self.front]; } // 返回陣列 pub fn toArray(self: *Self) ![]T { // 僅轉換有效長度範圍內的串列元素 var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; var j: usize = self.front; while (i < self.size()) : ({ i += 1; j += 1; }) { res[i] = self.nums[j % self.capacity()]; } return res; } }; } // Driver Code pub fn main() !void { // 初始化佇列 var capacity: usize = 10; var queue = ArrayQueue(i32){}; try queue.init(std.heap.page_allocator, capacity); defer queue.deinit(); // 元素入列 try queue.push(1); try queue.push(3); try queue.push(2); try queue.push(5); try queue.push(4); std.debug.print("佇列 queue = ", .{}); inc.PrintUtil.printArray(i32, try queue.toArray()); // 訪問佇列首元素 var peek = queue.peek(); std.debug.print("\n佇列首元素 peek = {}", .{peek}); // 元素出列 var pop = queue.pop(); std.debug.print("\n出列元素 pop = {},出列後 queue = ", .{pop}); inc.PrintUtil.printArray(i32, try queue.toArray()); // 獲取佇列的長度 var size = queue.size(); std.debug.print("\n佇列長度 size = {}", .{size}); // 判斷佇列是否為空 var is_empty = queue.isEmpty(); std.debug.print("\n佇列是否為空 = {}", .{is_empty}); // 測試環形陣列 var i: i32 = 0; while (i < 10) : (i += 1) { try queue.push(i); _ = queue.pop(); std.debug.print("\n第 {} 輪入列 + 出列後 queue = ", .{i}); inc.PrintUtil.printArray(i32, try queue.toArray()); } _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_stack_and_queue/array_stack.zig ================================================ // File: array_stack.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 基於陣列實現的堆疊 pub fn ArrayStack(comptime T: type) type { return struct { const Self = @This(); stack: ?std.ArrayList(T) = null, // 建構子(分配記憶體+初始化堆疊) pub fn init(self: *Self, allocator: std.mem.Allocator) void { if (self.stack == null) { self.stack = std.ArrayList(T).init(allocator); } } // 析構方法(釋放記憶體) pub fn deinit(self: *Self) void { if (self.stack == null) return; self.stack.?.deinit(); } // 獲取堆疊的長度 pub fn size(self: *Self) usize { return self.stack.?.items.len; } // 判斷堆疊是否為空 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // 訪問堆疊頂元素 pub fn peek(self: *Self) T { if (self.isEmpty()) @panic("堆疊為空"); return self.stack.?.items[self.size() - 1]; } // 入堆疊 pub fn push(self: *Self, num: T) !void { try self.stack.?.append(num); } // 出堆疊 pub fn pop(self: *Self) T { var num = self.stack.?.pop(); return num; } // 返回 ArrayList pub fn toList(self: *Self) std.ArrayList(T) { return self.stack.?; } }; } // Driver Code pub fn main() !void { // 初始化堆疊 var stack = ArrayStack(i32){}; stack.init(std.heap.page_allocator); // 延遲釋放記憶體 defer stack.deinit(); // 元素入堆疊 try stack.push(1); try stack.push(3); try stack.push(2); try stack.push(5); try stack.push(4); std.debug.print("堆疊 stack = ", .{}); inc.PrintUtil.printList(i32, stack.toList()); // 訪問堆疊頂元素 var peek = stack.peek(); std.debug.print("\n堆疊頂元素 peek = {}", .{peek}); // 元素出堆疊 var top = stack.pop(); std.debug.print("\n出堆疊元素 pop = {},出堆疊後 stack = ", .{top}); inc.PrintUtil.printList(i32, stack.toList()); // 獲取堆疊的長度 var size = stack.size(); std.debug.print("\n堆疊的長度 size = {}", .{size}); // 判斷堆疊是否為空 var is_empty = stack.isEmpty(); std.debug.print("\n堆疊是否為空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_stack_and_queue/deque.zig ================================================ // File: deque.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // 初始化雙向佇列 const L = std.TailQueue(i32); var deque = L{}; // 元素入列 var node1 = L.Node{ .data = 2 }; var node2 = L.Node{ .data = 5 }; var node3 = L.Node{ .data = 4 }; var node4 = L.Node{ .data = 3 }; var node5 = L.Node{ .data = 1 }; deque.append(&node1); // 新增至佇列尾 deque.append(&node2); deque.append(&node3); deque.prepend(&node4); // 新增至佇列首 deque.prepend(&node5); std.debug.print("雙向佇列 deque = ", .{}); inc.PrintUtil.printQueue(i32, deque); // 訪問元素 var peek_first = deque.first.?.data; // 佇列首元素 std.debug.print("\n佇列首元素 peek_first = {}", .{peek_first}); var peek_last = deque.last.?.data; // 佇列尾元素 std.debug.print("\n佇列尾元素 peek_last = {}", .{peek_last}); // 元素出列 var pop_first = deque.popFirst().?.data; // 佇列首元素出列 std.debug.print("\n佇列首出列元素 pop_first = {},佇列首出列後 deque = ", .{pop_first}); inc.PrintUtil.printQueue(i32, deque); var pop_last = deque.pop().?.data; // 佇列尾元素出列 std.debug.print("\n佇列尾出列元素 pop_last = {},佇列尾出列後 deque = ", .{pop_last}); inc.PrintUtil.printQueue(i32, deque); // 獲取雙向佇列的長度 var size = deque.len; std.debug.print("\n雙向佇列長度 size = {}", .{size}); // 判斷雙向佇列是否為空 var is_empty = if (deque.len == 0) true else false; std.debug.print("\n雙向佇列是否為空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig ================================================ // File: linkedlist_deque.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 雙向鏈結串列節點 pub fn ListNode(comptime T: type) type { return struct { const Self = @This(); val: T = undefined, // 節點值 next: ?*Self = null, // 後繼節點指標 prev: ?*Self = null, // 前驅節點指標 // Initialize a list node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.next = null; self.prev = null; } }; } // 基於雙向鏈結串列實現的雙向佇列 pub fn LinkedListDeque(comptime T: type) type { return struct { const Self = @This(); front: ?*ListNode(T) = null, // 頭節點 front rear: ?*ListNode(T) = null, // 尾節點 rear que_size: usize = 0, // 雙向佇列的長度 mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 // 建構子(分配記憶體+初始化佇列) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.front = null; self.rear = null; self.que_size = 0; } // 析構函式(釋放記憶體) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 獲取雙向佇列的長度 pub fn size(self: *Self) usize { return self.que_size; } // 判斷雙向佇列是否為空 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // 入列操作 pub fn push(self: *Self, num: T, is_front: bool) !void { var node = try self.mem_allocator.create(ListNode(T)); node.init(num); // 若鏈結串列為空,則令 front 和 rear 都指向 node if (self.isEmpty()) { self.front = node; self.rear = node; // 佇列首入列操作 } else if (is_front) { // 將 node 新增至鏈結串列頭部 self.front.?.prev = node; node.next = self.front; self.front = node; // 更新頭節點 // 佇列尾入列操作 } else { // 將 node 新增至鏈結串列尾部 self.rear.?.next = node; node.prev = self.rear; self.rear = node; // 更新尾節點 } self.que_size += 1; // 更新佇列長度 } // 佇列首入列 pub fn pushFirst(self: *Self, num: T) !void { try self.push(num, true); } // 佇列尾入列 pub fn pushLast(self: *Self, num: T) !void { try self.push(num, false); } // 出列操作 pub fn pop(self: *Self, is_front: bool) T { if (self.isEmpty()) @panic("雙向佇列為空"); var val: T = undefined; // 佇列首出列操作 if (is_front) { val = self.front.?.val; // 暫存頭節點值 // 刪除頭節點 var fNext = self.front.?.next; if (fNext != null) { fNext.?.prev = null; self.front.?.next = null; } self.front = fNext; // 更新頭節點 // 佇列尾出列操作 } else { val = self.rear.?.val; // 暫存尾節點值 // 刪除尾節點 var rPrev = self.rear.?.prev; if (rPrev != null) { rPrev.?.next = null; self.rear.?.prev = null; } self.rear = rPrev; // 更新尾節點 } self.que_size -= 1; // 更新佇列長度 return val; } // 佇列首出列 pub fn popFirst(self: *Self) T { return self.pop(true); } // 佇列尾出列 pub fn popLast(self: *Self) T { return self.pop(false); } // 訪問佇列首元素 pub fn peekFirst(self: *Self) T { if (self.isEmpty()) @panic("雙向佇列為空"); return self.front.?.val; } // 訪問佇列尾元素 pub fn peekLast(self: *Self) T { if (self.isEmpty()) @panic("雙向佇列為空"); return self.rear.?.val; } // 返回陣列用於列印 pub fn toArray(self: *Self) ![]T { var node = self.front; var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; while (i < res.len) : (i += 1) { res[i] = node.?.val; node = node.?.next; } return res; } }; } // Driver Code pub fn main() !void { // 初始化雙向佇列 var deque = LinkedListDeque(i32){}; try deque.init(std.heap.page_allocator); defer deque.deinit(); try deque.pushLast(3); try deque.pushLast(2); try deque.pushLast(5); std.debug.print("雙向佇列 deque = ", .{}); inc.PrintUtil.printArray(i32, try deque.toArray()); // 訪問元素 var peek_first = deque.peekFirst(); std.debug.print("\n佇列首元素 peek_first = {}", .{peek_first}); var peek_last = deque.peekLast(); std.debug.print("\n佇列尾元素 peek_last = {}", .{peek_last}); // 元素入列 try deque.pushLast(4); std.debug.print("\n元素 4 佇列尾入列後 deque = ", .{}); inc.PrintUtil.printArray(i32, try deque.toArray()); try deque.pushFirst(1); std.debug.print("\n元素 1 佇列首入列後 deque = ", .{}); inc.PrintUtil.printArray(i32, try deque.toArray()); // 元素出列 var pop_last = deque.popLast(); std.debug.print("\n佇列尾出列元素 = {},佇列尾出列後 deque = ", .{pop_last}); inc.PrintUtil.printArray(i32, try deque.toArray()); var pop_first = deque.popFirst(); std.debug.print("\n佇列首出列元素 = {},佇列首出列後 deque = ", .{pop_first}); inc.PrintUtil.printArray(i32, try deque.toArray()); // 獲取雙向佇列的長度 var size = deque.size(); std.debug.print("\n雙向佇列長度 size = {}", .{size}); // 判斷雙向佇列是否為空 var is_empty = deque.isEmpty(); std.debug.print("\n雙向佇列是否為空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig ================================================ // File: linkedlist_queue.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 基於鏈結串列實現的佇列 pub fn LinkedListQueue(comptime T: type) type { return struct { const Self = @This(); front: ?*inc.ListNode(T) = null, // 頭節點 front rear: ?*inc.ListNode(T) = null, // 尾節點 rear que_size: usize = 0, // 佇列的長度 mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 // 建構子(分配記憶體+初始化佇列) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.front = null; self.rear = null; self.que_size = 0; } // 析構函式(釋放記憶體) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 獲取佇列的長度 pub fn size(self: *Self) usize { return self.que_size; } // 判斷佇列是否為空 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // 訪問佇列首元素 pub fn peek(self: *Self) T { if (self.size() == 0) @panic("佇列為空"); return self.front.?.val; } // 入列 pub fn push(self: *Self, num: T) !void { // 在尾節點後新增 num var node = try self.mem_allocator.create(inc.ListNode(T)); node.init(num); // 如果佇列為空,則令頭、尾節點都指向該節點 if (self.front == null) { self.front = node; self.rear = node; // 如果佇列不為空,則將該節點新增到尾節點後 } else { self.rear.?.next = node; self.rear = node; } self.que_size += 1; } // 出列 pub fn pop(self: *Self) T { var num = self.peek(); // 刪除頭節點 self.front = self.front.?.next; self.que_size -= 1; return num; } // 將鏈結串列轉換為陣列 pub fn toArray(self: *Self) ![]T { var node = self.front; var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; while (i < res.len) : (i += 1) { res[i] = node.?.val; node = node.?.next; } return res; } }; } // Driver Code pub fn main() !void { // 初始化佇列 var queue = LinkedListQueue(i32){}; try queue.init(std.heap.page_allocator); defer queue.deinit(); // 元素入列 try queue.push(1); try queue.push(3); try queue.push(2); try queue.push(5); try queue.push(4); std.debug.print("佇列 queue = ", .{}); inc.PrintUtil.printArray(i32, try queue.toArray()); // 訪問佇列首元素 var peek = queue.peek(); std.debug.print("\n佇列首元素 peek = {}", .{peek}); // 元素出列 var pop = queue.pop(); std.debug.print("\n出列元素 pop = {},出列後 queue = ", .{pop}); inc.PrintUtil.printArray(i32, try queue.toArray()); // 獲取佇列的長度 var size = queue.size(); std.debug.print("\n佇列長度 size = {}", .{size}); // 判斷佇列是否為空 var is_empty = queue.isEmpty(); std.debug.print("\n佇列是否為空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig ================================================ // File: linkedlist_stack.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 基於鏈結串列實現的堆疊 pub fn LinkedListStack(comptime T: type) type { return struct { const Self = @This(); stack_top: ?*inc.ListNode(T) = null, // 將頭節點作為堆疊頂 stk_size: usize = 0, // 堆疊的長度 mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 // 建構子(分配記憶體+初始化堆疊) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } self.stack_top = null; self.stk_size = 0; } // 析構函式(釋放記憶體) pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 獲取堆疊的長度 pub fn size(self: *Self) usize { return self.stk_size; } // 判斷堆疊是否為空 pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // 訪問堆疊頂元素 pub fn peek(self: *Self) T { if (self.size() == 0) @panic("堆疊為空"); return self.stack_top.?.val; } // 入堆疊 pub fn push(self: *Self, num: T) !void { var node = try self.mem_allocator.create(inc.ListNode(T)); node.init(num); node.next = self.stack_top; self.stack_top = node; self.stk_size += 1; } // 出堆疊 pub fn pop(self: *Self) T { var num = self.peek(); self.stack_top = self.stack_top.?.next; self.stk_size -= 1; return num; } // 將堆疊轉換為陣列 pub fn toArray(self: *Self) ![]T { var node = self.stack_top; var res = try self.mem_allocator.alloc(T, self.size()); @memset(res, @as(T, 0)); var i: usize = 0; while (i < res.len) : (i += 1) { res[res.len - i - 1] = node.?.val; node = node.?.next; } return res; } }; } // Driver Code pub fn main() !void { // 初始化堆疊 var stack = LinkedListStack(i32){}; try stack.init(std.heap.page_allocator); // 延遲釋放記憶體 defer stack.deinit(); // 元素入堆疊 try stack.push(1); try stack.push(3); try stack.push(2); try stack.push(5); try stack.push(4); std.debug.print("堆疊 stack = ", .{}); inc.PrintUtil.printArray(i32, try stack.toArray()); // 訪問堆疊頂元素 var peek = stack.peek(); std.debug.print("\n堆疊頂元素 top = {}", .{peek}); // 元素出堆疊 var pop = stack.pop(); std.debug.print("\n出堆疊元素 pop = {},出堆疊後 stack = ", .{pop}); inc.PrintUtil.printArray(i32, try stack.toArray()); // 獲取堆疊的長度 var size = stack.size(); std.debug.print("\n堆疊的長度 size = {}", .{size}); // 判斷堆疊是否為空 var is_empty = stack.isEmpty(); std.debug.print("\n堆疊是否為空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_stack_and_queue/queue.zig ================================================ // File: queue.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // 初始化佇列 const L = std.TailQueue(i32); var queue = L{}; // 元素入列 var node1 = L.Node{ .data = 1 }; var node2 = L.Node{ .data = 3 }; var node3 = L.Node{ .data = 2 }; var node4 = L.Node{ .data = 5 }; var node5 = L.Node{ .data = 4 }; queue.append(&node1); queue.append(&node2); queue.append(&node3); queue.append(&node4); queue.append(&node5); std.debug.print("佇列 queue = ", .{}); inc.PrintUtil.printQueue(i32, queue); // 訪問佇列首元素 var peek = queue.first.?.data; std.debug.print("\n佇列首元素 peek = {}", .{peek}); // 元素出列 var pop = queue.popFirst().?.data; std.debug.print("\n出列元素 pop = {},出列後 queue = ", .{pop}); inc.PrintUtil.printQueue(i32, queue); // 獲取佇列的長度 var size = queue.len; std.debug.print("\n佇列長度 size = {}", .{size}); // 判斷佇列是否為空 var is_empty = if (queue.len == 0) true else false; std.debug.print("\n佇列是否為空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_stack_and_queue/stack.zig ================================================ // File: stack.zig // Created Time: 2023-01-08 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // 初始化堆疊 // 在 zig 中,推薦將 ArrayList 當作堆疊來使用 var stack = std.ArrayList(i32).init(std.heap.page_allocator); // 延遲釋放記憶體 defer stack.deinit(); // 元素入堆疊 try stack.append(1); try stack.append(3); try stack.append(2); try stack.append(5); try stack.append(4); std.debug.print("堆疊 stack = ", .{}); inc.PrintUtil.printList(i32, stack); // 訪問堆疊頂元素 var peek = stack.items[stack.items.len - 1]; std.debug.print("\n堆疊頂元素 peek = {}", .{peek}); // 元素出堆疊 var pop = stack.pop(); std.debug.print("\n出堆疊元素 pop = {},出堆疊後 stack = ", .{pop}); inc.PrintUtil.printList(i32, stack); // 獲取堆疊的長度 var size = stack.items.len; std.debug.print("\n堆疊的長度 size = {}", .{size}); // 判斷堆疊是否為空 var is_empty = if (stack.items.len == 0) true else false; std.debug.print("\n堆疊是否為空 = {}", .{is_empty}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_tree/avl_tree.zig ================================================ // File: avl_tree.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // AVL 樹 pub fn AVLTree(comptime T: type) type { return struct { const Self = @This(); root: ?*inc.TreeNode(T) = null, // 根節點 mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 // 建構子 pub fn init(self: *Self, allocator: std.mem.Allocator) void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } } // 析構方法 pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 獲取節點高度 fn height(self: *Self, node: ?*inc.TreeNode(T)) i32 { _ = self; // 空節點高度為 -1 ,葉節點高度為 0 return if (node == null) -1 else node.?.height; } // 更新節點高度 fn updateHeight(self: *Self, node: ?*inc.TreeNode(T)) void { // 節點高度等於最高子樹高度 + 1 node.?.height = @max(self.height(node.?.left), self.height(node.?.right)) + 1; } // 獲取平衡因子 fn balanceFactor(self: *Self, node: ?*inc.TreeNode(T)) i32 { // 空節點平衡因子為 0 if (node == null) return 0; // 節點平衡因子 = 左子樹高度 - 右子樹高度 return self.height(node.?.left) - self.height(node.?.right); } // 右旋操作 fn rightRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { var child = node.?.left; var grandChild = child.?.right; // 以 child 為原點,將 node 向右旋轉 child.?.right = node; node.?.left = grandChild; // 更新節點高度 self.updateHeight(node); self.updateHeight(child); // 返回旋轉後子樹的根節點 return child; } // 左旋操作 fn leftRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { var child = node.?.right; var grandChild = child.?.left; // 以 child 為原點,將 node 向左旋轉 child.?.left = node; node.?.right = grandChild; // 更新節點高度 self.updateHeight(node); self.updateHeight(child); // 返回旋轉後子樹的根節點 return child; } // 執行旋轉操作,使該子樹重新恢復平衡 fn rotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { // 獲取節點 node 的平衡因子 var balance_factor = self.balanceFactor(node); // 左偏樹 if (balance_factor > 1) { if (self.balanceFactor(node.?.left) >= 0) { // 右旋 return self.rightRotate(node); } else { // 先左旋後右旋 node.?.left = self.leftRotate(node.?.left); return self.rightRotate(node); } } // 右偏樹 if (balance_factor < -1) { if (self.balanceFactor(node.?.right) <= 0) { // 左旋 return self.leftRotate(node); } else { // 先右旋後左旋 node.?.right = self.rightRotate(node.?.right); return self.leftRotate(node); } } // 平衡樹,無須旋轉,直接返回 return node; } // 插入節點 fn insert(self: *Self, val: T) !void { self.root = (try self.insertHelper(self.root, val)).?; } // 遞迴插入節點(輔助方法) fn insertHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) !?*inc.TreeNode(T) { var node = node_; if (node == null) { var tmp_node = try self.mem_allocator.create(inc.TreeNode(T)); tmp_node.init(val); return tmp_node; } // 1. 查詢插入位置並插入節點 if (val < node.?.val) { node.?.left = try self.insertHelper(node.?.left, val); } else if (val > node.?.val) { node.?.right = try self.insertHelper(node.?.right, val); } else { return node; // 重複節點不插入,直接返回 } self.updateHeight(node); // 更新節點高度 // 2. 執行旋轉操作,使該子樹重新恢復平衡 node = self.rotate(node); // 返回子樹的根節點 return node; } // 刪除節點 fn remove(self: *Self, val: T) void { self.root = self.removeHelper(self.root, val).?; } // 遞迴刪除節點(輔助方法) fn removeHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) ?*inc.TreeNode(T) { var node = node_; if (node == null) return null; // 1. 查詢節點並刪除 if (val < node.?.val) { node.?.left = self.removeHelper(node.?.left, val); } else if (val > node.?.val) { node.?.right = self.removeHelper(node.?.right, val); } else { if (node.?.left == null or node.?.right == null) { var child = if (node.?.left != null) node.?.left else node.?.right; // 子節點數量 = 0 ,直接刪除 node 並返回 if (child == null) { return null; // 子節點數量 = 1 ,直接刪除 node } else { node = child; } } else { // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 var temp = node.?.right; while (temp.?.left != null) { temp = temp.?.left; } node.?.right = self.removeHelper(node.?.right, temp.?.val); node.?.val = temp.?.val; } } self.updateHeight(node); // 更新節點高度 // 2. 執行旋轉操作,使該子樹重新恢復平衡 node = self.rotate(node); // 返回子樹的根節點 return node; } // 查詢節點 fn search(self: *Self, val: T) ?*inc.TreeNode(T) { var cur = self.root; // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 目標節點在 cur 的右子樹中 if (cur.?.val < val) { cur = cur.?.right; // 目標節點在 cur 的左子樹中 } else if (cur.?.val > val) { cur = cur.?.left; // 找到目標節點,跳出迴圈 } else { break; } } // 返回目標節點 return cur; } }; } pub fn testInsert(comptime T: type, tree_: *AVLTree(T), val: T) !void { var tree = tree_; try tree.insert(val); std.debug.print("\n插入節點 {} 後,AVL 樹為\n", .{val}); try inc.PrintUtil.printTree(tree.root, null, false); } pub fn testRemove(comptime T: type, tree_: *AVLTree(T), val: T) void { var tree = tree_; tree.remove(val); std.debug.print("\n刪除節點 {} 後,AVL 樹為\n", .{val}); try inc.PrintUtil.printTree(tree.root, null, false); } // Driver Code pub fn main() !void { // 初始化空 AVL 樹 var avl_tree = AVLTree(i32){}; avl_tree.init(std.heap.page_allocator); defer avl_tree.deinit(); // 插入節點 // 請關注插入節點後,AVL 樹是如何保持平衡的 try testInsert(i32, &avl_tree, 1); try testInsert(i32, &avl_tree, 2); try testInsert(i32, &avl_tree, 3); try testInsert(i32, &avl_tree, 4); try testInsert(i32, &avl_tree, 5); try testInsert(i32, &avl_tree, 8); try testInsert(i32, &avl_tree, 7); try testInsert(i32, &avl_tree, 9); try testInsert(i32, &avl_tree, 10); try testInsert(i32, &avl_tree, 6); // 插入重複節點 try testInsert(i32, &avl_tree, 7); // 刪除節點 // 請關注刪除節點後,AVL 樹是如何保持平衡的 testRemove(i32, &avl_tree, 8); // 刪除度為 0 的節點 testRemove(i32, &avl_tree, 5); // 刪除度為 1 的節點 testRemove(i32, &avl_tree, 4); // 刪除度為 2 的節點 // 查詢節點 var node = avl_tree.search(7).?; std.debug.print("\n查詢到的節點物件為 {any},節點值 = {}\n", .{node, node.val}); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_tree/binary_search_tree.zig ================================================ // File: binary_search_tree.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 二元搜尋樹 pub fn BinarySearchTree(comptime T: type) type { return struct { const Self = @This(); root: ?*inc.TreeNode(T) = null, mem_arena: ?std.heap.ArenaAllocator = null, mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 // 建構子 pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []T) !void { if (self.mem_arena == null) { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } std.mem.sort(T, nums, {}, comptime std.sort.asc(T)); // 排序陣列 self.root = try self.buildTree(nums, 0, nums.len - 1); // 構建二元搜尋樹 } // 析構方法 pub fn deinit(self: *Self) void { if (self.mem_arena == null) return; self.mem_arena.?.deinit(); } // 構建二元搜尋樹 fn buildTree(self: *Self, nums: []T, i: usize, j: usize) !?*inc.TreeNode(T) { if (i > j) return null; // 將陣列中間節點作為根節點 var mid = i + (j - i) / 2; var node = try self.mem_allocator.create(inc.TreeNode(T)); node.init(nums[mid]); // 遞迴建立左子樹和右子樹 if (mid >= 1) node.left = try self.buildTree(nums, i, mid - 1); node.right = try self.buildTree(nums, mid + 1, j); return node; } // 獲取二元樹根節點 fn getRoot(self: *Self) ?*inc.TreeNode(T) { return self.root; } // 查詢節點 fn search(self: *Self, num: T) ?*inc.TreeNode(T) { var cur = self.root; // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 目標節點在 cur 的右子樹中 if (cur.?.val < num) { cur = cur.?.right; // 目標節點在 cur 的左子樹中 } else if (cur.?.val > num) { cur = cur.?.left; // 找到目標節點,跳出迴圈 } else { break; } } // 返回目標節點 return cur; } // 插入節點 fn insert(self: *Self, num: T) !void { // 若樹為空,則初始化根節點 if (self.root == null) { self.root = try self.mem_allocator.create(inc.TreeNode(T)); return; } var cur = self.root; var pre: ?*inc.TreeNode(T) = null; // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 找到重複節點,直接返回 if (cur.?.val == num) return; pre = cur; // 插入位置在 cur 的右子樹中 if (cur.?.val < num) { cur = cur.?.right; // 插入位置在 cur 的左子樹中 } else { cur = cur.?.left; } } // 插入節點 var node = try self.mem_allocator.create(inc.TreeNode(T)); node.init(num); if (pre.?.val < num) { pre.?.right = node; } else { pre.?.left = node; } } // 刪除節點 fn remove(self: *Self, num: T) void { // 若樹為空,直接提前返回 if (self.root == null) return; var cur = self.root; var pre: ?*inc.TreeNode(T) = null; // 迴圈查詢,越過葉節點後跳出 while (cur != null) { // 找到待刪除節點,跳出迴圈 if (cur.?.val == num) break; pre = cur; // 待刪除節點在 cur 的右子樹中 if (cur.?.val < num) { cur = cur.?.right; // 待刪除節點在 cur 的左子樹中 } else { cur = cur.?.left; } } // 若無待刪除節點,則直接返回 if (cur == null) return; // 子節點數量 = 0 or 1 if (cur.?.left == null or cur.?.right == null) { // 當子節點數量 = 0 / 1 時, child = null / 該子節點 var child = if (cur.?.left != null) cur.?.left else cur.?.right; // 刪除節點 cur if (pre.?.left == cur) { pre.?.left = child; } else { pre.?.right = child; } // 子節點數量 = 2 } else { // 獲取中序走訪中 cur 的下一個節點 var tmp = cur.?.right; while (tmp.?.left != null) { tmp = tmp.?.left; } var tmp_val = tmp.?.val; // 遞迴刪除節點 tmp self.remove(tmp.?.val); // 用 tmp 覆蓋 cur cur.?.val = tmp_val; } } }; } // Driver Code pub fn main() !void { // 初始化二元樹 var nums = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; var bst = BinarySearchTree(i32){}; try bst.init(std.heap.page_allocator, &nums); defer bst.deinit(); std.debug.print("初始化的二元樹為\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); // 查詢節點 var node = bst.search(7); std.debug.print("\n查詢到的節點物件為 {any},節點值 = {}\n", .{node, node.?.val}); // 插入節點 try bst.insert(16); std.debug.print("\n插入節點 16 後,二元樹為\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); // 刪除節點 bst.remove(1); std.debug.print("\n刪除節點 1 後,二元樹為\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); bst.remove(2); std.debug.print("\n刪除節點 2 後,二元樹為\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); bst.remove(4); std.debug.print("\n刪除節點 4 後,二元樹為\n", .{}); try inc.PrintUtil.printTree(bst.getRoot(), null, false); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_tree/binary_tree.zig ================================================ // File: binary_tree.zig // Created Time: 2023-01-14 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // Driver Code pub fn main() !void { // 初始化二元樹 // 初始化節點 var n1 = inc.TreeNode(i32){ .val = 1 }; var n2 = inc.TreeNode(i32){ .val = 2 }; var n3 = inc.TreeNode(i32){ .val = 3 }; var n4 = inc.TreeNode(i32){ .val = 4 }; var n5 = inc.TreeNode(i32){ .val = 5 }; // 構建節點之間的引用(指標) n1.left = &n2; n1.right = &n3; n2.left = &n4; n2.right = &n5; std.debug.print("初始化二元樹\n", .{}); try inc.PrintUtil.printTree(&n1, null, false); // 插入與刪除節點 var p = inc.TreeNode(i32){ .val = 0 }; // 在 n1 -> n2 中間插入節點 P n1.left = &p; p.left = &n2; std.debug.print("插入節點 P 後\n", .{}); try inc.PrintUtil.printTree(&n1, null, false); // 刪除節點 n1.left = &n2; std.debug.print("刪除節點 P 後\n", .{}); try inc.PrintUtil.printTree(&n1, null, false); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_tree/binary_tree_bfs.zig ================================================ // File: binary_tree_bfs.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 層序走訪 fn levelOrder(comptime T: type, mem_allocator: std.mem.Allocator, root: *inc.TreeNode(T)) !std.ArrayList(T) { // 初始化佇列,加入根節點 const L = std.TailQueue(*inc.TreeNode(T)); var queue = L{}; var root_node = try mem_allocator.create(L.Node); root_node.data = root; queue.append(root_node); // 初始化一個串列,用於儲存走訪序列 var list = std.ArrayList(T).init(std.heap.page_allocator); while (queue.len > 0) { var queue_node = queue.popFirst().?; // 隊列出隊 var node = queue_node.data; try list.append(node.val); // 儲存節點值 if (node.left != null) { var tmp_node = try mem_allocator.create(L.Node); tmp_node.data = node.left.?; queue.append(tmp_node); // 左子節點入列 } if (node.right != null) { var tmp_node = try mem_allocator.create(L.Node); tmp_node.data = node.right.?; queue.append(tmp_node); // 右子節點入列 } } return list; } // Driver Code pub fn main() !void { // 初始化記憶體分配器 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // 初始化二元樹 // 這裡藉助了一個從陣列直接生成二元樹的函式 var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); std.debug.print("初始化二元樹\n", .{}); try inc.PrintUtil.printTree(root, null, false); // 層序走訪 var list = try levelOrder(i32, mem_allocator, root.?); defer list.deinit(); std.debug.print("\n層序走訪的節點列印序列 = ", .{}); inc.PrintUtil.printList(i32, list); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/chapter_tree/binary_tree_dfs.zig ================================================ // File: binary_tree_dfs.zig // Created Time: 2023-01-15 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); var list = std.ArrayList(i32).init(std.heap.page_allocator); // 前序走訪 fn preOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { if (root == null) return; // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 try list.append(root.?.val); try preOrder(T, root.?.left); try preOrder(T, root.?.right); } // 中序走訪 fn inOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { if (root == null) return; // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 try inOrder(T, root.?.left); try list.append(root.?.val); try inOrder(T, root.?.right); } // 後序走訪 fn postOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { if (root == null) return; // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 try postOrder(T, root.?.left); try postOrder(T, root.?.right); try list.append(root.?.val); } // Driver Code pub fn main() !void { // 初始化記憶體分配器 var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer mem_arena.deinit(); const mem_allocator = mem_arena.allocator(); // 初始化二元樹 // 這裡藉助了一個從陣列直接生成二元樹的函式 var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); std.debug.print("初始化二元樹\n", .{}); try inc.PrintUtil.printTree(root, null, false); // 前序走訪 list.clearRetainingCapacity(); try preOrder(i32, root); std.debug.print("\n前序走訪的節點列印序列 = ", .{}); inc.PrintUtil.printList(i32, list); // 中序走訪 list.clearRetainingCapacity(); try inOrder(i32, root); std.debug.print("\n中序走訪的節點列印序列 = ", .{}); inc.PrintUtil.printList(i32, list); // 後序走訪 list.clearRetainingCapacity(); try postOrder(i32, root); std.debug.print("\n後續走訪的節點列印序列 = ", .{}); inc.PrintUtil.printList(i32, list); _ = try std.io.getStdIn().reader().readByte(); } ================================================ FILE: zh-hant/codes/zig/include/ListNode.zig ================================================ // File: ListNode.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 鏈結串列節點 pub fn ListNode(comptime T: type) type { return struct { const Self = @This(); val: T = 0, next: ?*Self = null, // Initialize a list node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.next = null; } }; } // 將串列反序列化為鏈結串列 pub fn listToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, list: std.ArrayList(T)) !?*ListNode(T) { var dum = try mem_allocator.create(ListNode(T)); dum.init(0); var head = dum; for (list.items) |val| { var tmp = try mem_allocator.create(ListNode(T)); tmp.init(val); head.next = tmp; head = head.next.?; } return dum.next; } // 將陣列反序列化為鏈結串列 pub fn arrToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*ListNode(T) { var dum = try mem_allocator.create(ListNode(T)); dum.init(0); var head = dum; for (arr) |val| { var tmp = try mem_allocator.create(ListNode(T)); tmp.init(val); head.next = tmp; head = head.next.?; } return dum.next; } ================================================ FILE: zh-hant/codes/zig/include/PrintUtil.zig ================================================ // File: PrintUtil.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); pub const ListUtil = @import("ListNode.zig"); pub const ListNode = ListUtil.ListNode; pub const TreeUtil = @import("TreeNode.zig"); pub const TreeNode = TreeUtil.TreeNode; // 列印佇列 pub fn printQueue(comptime T: type, queue: std.TailQueue(T)) void { var node = queue.first; std.debug.print("[", .{}); var i: i32 = 0; while (node != null) : (i += 1) { var data = node.?.data; std.debug.print("{}{s}", .{ data, if (i == queue.len - 1) "]" else ", " }); node = node.?.next; } } // 列印雜湊表 pub fn printHashMap(comptime TKey: type, comptime TValue: type, map: std.AutoHashMap(TKey, TValue)) void { var it = map.iterator(); while (it.next()) |kv| { var key = kv.key_ptr.*; var value = kv.value_ptr.*; std.debug.print("{} -> {s}\n", .{ key, value }); } } // 列印堆積 pub fn printHeap(comptime T: type, mem_allocator: std.mem.Allocator, queue: anytype) !void { var arr = queue.items; var len = queue.len; std.debug.print("堆積的陣列表示:", .{}); printArray(T, arr[0..len]); std.debug.print("\n堆積的樹狀表示:\n", .{}); var root = try TreeUtil.arrToTree(T, mem_allocator, arr[0..len]); try printTree(root, null, false); } ================================================ FILE: zh-hant/codes/zig/include/TreeNode.zig ================================================ // File: TreeNode.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) const std = @import("std"); // 二元樹節點 pub fn TreeNode(comptime T: type) type { return struct { const Self = @This(); val: T = undefined, // 節點值 height: i32 = undefined, // 節點高度 left: ?*Self = null, // 左子節點指標 right: ?*Self = null, // 右子節點指標 // Initialize a tree node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.height = 0; self.left = null; self.right = null; } }; } // 將陣列反序列化為二元樹 pub fn arrToTree(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*TreeNode(T) { if (arr.len == 0) return null; var root = try mem_allocator.create(TreeNode(T)); root.init(arr[0]); const L = std.TailQueue(*TreeNode(T)); var que = L{}; var root_node = try mem_allocator.create(L.Node); root_node.data = root; que.append(root_node); var index: usize = 0; while (que.len > 0) { var que_node = que.popFirst().?; var node = que_node.data; index += 1; if (index >= arr.len) break; if (index < arr.len) { var tmp = try mem_allocator.create(TreeNode(T)); tmp.init(arr[index]); node.left = tmp; var tmp_node = try mem_allocator.create(L.Node); tmp_node.data = node.left.?; que.append(tmp_node); } index += 1; if (index >= arr.len) break; if (index < arr.len) { var tmp = try mem_allocator.create(TreeNode(T)); tmp.init(arr[index]); node.right = tmp; var tmp_node = try mem_allocator.create(L.Node); tmp_node.data = node.right.?; que.append(tmp_node); } } return root; } ================================================ FILE: zh-hant/codes/zig/include/include.zig ================================================ // File: include.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com) pub const PrintUtil = @import("PrintUtil.zig"); pub const TreeUtil = @import("TreeNode.zig"); pub const TreeNode = TreeUtil.TreeNode; ================================================ FILE: zh-hant/codes/zig/main.zig ================================================ const std = @import("std"); const iteration = @import("chapter_computational_complexity/iteration.zig"); const recursion = @import("chapter_computational_complexity/recursion.zig"); const time_complexity = @import("chapter_computational_complexity/time_complexity.zig"); const space_complexity = @import("chapter_computational_complexity/space_complexity.zig"); const worst_best_time_complexity = @import("chapter_computational_complexity/worst_best_time_complexity.zig"); const array = @import("chapter_array_and_linkedlist/array.zig"); const linked_list = @import("chapter_array_and_linkedlist/linked_list.zig"); const list = @import("chapter_array_and_linkedlist/list.zig"); const my_list = @import("chapter_array_and_linkedlist/my_list.zig"); pub fn main() !void { try iteration.run(); recursion.run(); time_complexity.run(); try space_complexity.run(); worst_best_time_complexity.run(); try array.run(); linked_list.run(); try list.run(); try my_list.run(); } ================================================ FILE: zh-hant/codes/zig/utils/ListNode.zig ================================================ // File: ListNode.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // 鏈結串列節點 pub fn ListNode(comptime T: type) type { return struct { const Self = @This(); val: T = 0, next: ?*Self = null, // Initialize a list node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.next = null; } }; } // 將串列反序列化為鏈結串列 pub fn listToLinkedList(comptime T: type, allocator: std.mem.Allocator, list: std.ArrayList(T)) !?*ListNode(T) { var dum = try allocator.create(ListNode(T)); dum.init(0); var head = dum; for (list.items) |val| { var tmp = try allocator.create(ListNode(T)); tmp.init(val); head.next = tmp; head = head.next.?; } return dum.next; } // 將陣列反序列化為鏈結串列 pub fn arrToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*ListNode(T) { var dum = try mem_allocator.create(ListNode(T)); dum.init(0); var head = dum; for (arr) |val| { var tmp = try mem_allocator.create(ListNode(T)); tmp.init(val); head.next = tmp; head = head.next.?; } return dum.next; } ================================================ FILE: zh-hant/codes/zig/utils/TreeNode.zig ================================================ // File: TreeNode.zig // Created Time: 2023-01-07 // Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); // 二元樹節點 pub fn TreeNode(comptime T: type) type { return struct { const Self = @This(); val: T = undefined, // 節點值 height: i32 = undefined, // 節點高度 left: ?*Self = null, // 左子節點指標 right: ?*Self = null, // 右子節點指標 // Initialize a tree node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; self.height = 0; self.left = null; self.right = null; } }; } // 將陣列反序列化為二元樹 pub fn arrToTree(comptime T: type, allocator: std.mem.Allocator, arr: []T) !?*TreeNode(T) { if (arr.len == 0) return null; var root = try allocator.create(TreeNode(T)); root.init(arr[0]); const L = std.TailQueue(*TreeNode(T)); var que = L{}; var root_node = try allocator.create(L.Node); root_node.data = root; que.append(root_node); var index: usize = 0; while (que.len > 0) { const que_node = que.popFirst().?; var node = que_node.data; index += 1; if (index >= arr.len) break; if (index < arr.len) { var tmp = try allocator.create(TreeNode(T)); tmp.init(arr[index]); node.left = tmp; var tmp_node = try allocator.create(L.Node); tmp_node.data = node.left.?; que.append(tmp_node); } index += 1; if (index >= arr.len) break; if (index < arr.len) { var tmp = try allocator.create(TreeNode(T)); tmp.init(arr[index]); node.right = tmp; var tmp_node = try allocator.create(L.Node); tmp_node.data = node.right.?; que.append(tmp_node); } } return root; } ================================================ FILE: zh-hant/codes/zig/utils/format.zig ================================================ // File: format.zig // Created Time: 2025-07-19 // Author: CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); const ListNode = @import("ListNode.zig").ListNode; const TreeNode = @import("TreeNode.zig").TreeNode; pub fn slice(items: anytype) SliceFormatter(@TypeOf(items)) { return .{ .items = items }; } pub fn SliceFormatter(comptime SliceType: type) type { return struct { const Self = @This(); items: SliceType, pub fn format( self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) !void { try writer.writeAll("["); if (self.items.len > 0) { for (self.items, 0..) |item, i| { try std.fmt.format(writer, "{}", .{item}); if (i != self.items.len - 1) { try writer.writeAll(", "); } } } try writer.writeAll("]"); } }; } pub fn linkedList(comptime T: type, head: *const ListNode(T)) LinkedListFormatter(T) { return .{ .head = head }; } pub fn LinkedListFormatter(comptime T: type) type { return struct { const Self = @This(); head: *const ListNode(T), pub fn format( self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) !void { try printLinkedList(self.head, writer); } pub fn printLinkedList(head: *const ListNode(T), writer: anytype) !void { try std.fmt.format(writer, "{}", .{head.val}); if (head.next) |next_node| { try writer.writeAll("->"); try printLinkedList(next_node, writer); } } }; } pub fn tree(comptime T: type, root: ?*const TreeNode(T)) TreeFormatter(T) { return .{ .root = root }; } pub fn TreeFormatter(comptime T: type) type { return struct { const Self = @This(); root: ?*const TreeNode(T), pub fn format( self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) !void { try printTree(self.root, null, false, writer); } // 列印二元樹 fn printTree(root: ?*const TreeNode(T), prev: ?*Trunk, isRight: bool, writer: anytype) !void { if (root == null) { return; } var prev_str = " "; var trunk = Trunk{ .prev = prev, .str = prev_str }; try printTree(root.?.right, &trunk, true, writer); if (prev == null) { trunk.str = "———"; } else if (isRight) { trunk.str = "/———"; prev_str = " |"; } else { trunk.str = "\\———"; prev.?.str = prev_str; } try showTrunks(&trunk, writer); try std.fmt.format(writer, "{d}\n", .{root.?.val}); if (prev) |_| { prev.?.str = prev_str; } trunk.str = " |"; try printTree(root.?.left, &trunk, false, writer); } // 列印二元樹 // This tree printer is borrowed from TECHIE DELIGHT // https://www.techiedelight.com/c-program-print-binary-tree/ const Trunk = struct { prev: ?*Trunk = null, str: []const u8 = undefined, pub fn init(self: *Trunk, prev: ?*Trunk, str: []const u8) void { self.prev = prev; self.str = str; } }; pub fn showTrunks(p: ?*Trunk, writer: anytype) !void { if (p == null) return; try showTrunks(p.?.prev, writer); try std.fmt.format(writer, "{s}", .{p.?.str}); } }; } ================================================ FILE: zh-hant/codes/zig/utils/utils.zig ================================================ // File: format.zig // Created Time: 2025-07-15 // Author: CreatorMetaSky (creator_meta_sky@163.com) const std = @import("std"); pub const fmt = @import("format.zig"); pub const ListNode = @import("ListNode.zig").ListNode; pub const TreeNode = @import("TreeNode.zig").TreeNode; ================================================ FILE: zh-hant/docs/chapter_appendix/contribution.md ================================================ # 一起參與創作 由於筆者能力有限,書中難免存在一些遺漏和錯誤,請您諒解。如果您發現了筆誤、連結失效、內容缺失、文字歧義、解釋不清晰或行文結構不合理等問題,請協助我們進行修正,以給讀者提供更優質的學習資源。 所有[撰稿人](https://github.com/krahets/hello-algo/graphs/contributors)的 GitHub ID 將在本書倉庫、網頁版和 PDF 版的主頁上進行展示,以感謝他們對開源社群的無私奉獻。 !!! success "開源的魅力" 紙質圖書的兩次印刷的間隔時間往往較久,內容更新非常不方便。 而在本開源書中,內容更迭的時間被縮短至數日甚至幾個小時。 ### 內容微調 如下圖所示,每個頁面的右上角都有“編輯圖示”。您可以按照以下步驟修改文字或程式碼。 1. 點選“編輯圖示”,如果遇到“需要 Fork 此倉庫”的提示,請同意該操作。 2. 修改 Markdown 源檔案內容,檢查內容的正確性,並儘量保持排版格式的統一。 3. 在頁面底部填寫修改說明,然後點選“Propose file change”按鈕。頁面跳轉後,點選“Create pull request”按鈕即可發起拉取請求。 ![頁面編輯按鍵](contribution.assets/edit_markdown.png) 圖片無法直接修改,需要透過新建 [Issue](https://github.com/krahets/hello-algo/issues) 或評論留言來描述問題,我們會盡快重新繪製並替換圖片。 ### 內容創作 如果您有興趣參與此開源專案,包括將程式碼翻譯成其他程式語言、擴展文章內容等,那麼需要實施以下 Pull Request 工作流程。 1. 登入 GitHub ,將本書的[程式碼倉庫](https://github.com/krahets/hello-algo) Fork 到個人帳號下。 2. 進入您的 Fork 倉庫網頁,使用 `git clone` 命令將倉庫克隆至本地。 3. 在本地進行內容創作,並進行完整測試,驗證程式碼的正確性。 4. 將本地所做更改 Commit ,然後 Push 至遠端倉庫。 5. 重新整理倉庫網頁,點選“Create pull request”按鈕即可發起拉取請求。 ### Docker 部署 在 `hello-algo` 根目錄下,執行以下 Docker 指令碼,即可在 `http://localhost:8000` 訪問本專案: ```shell docker-compose up -d ``` 使用以下命令即可刪除部署: ```shell docker-compose down ``` ================================================ FILE: zh-hant/docs/chapter_appendix/index.md ================================================ # 附錄 ![附錄](../assets/covers/chapter_appendix.jpg) ================================================ FILE: zh-hant/docs/chapter_appendix/installation.md ================================================ # 程式設計環境安裝 ## 安裝 IDE 推薦使用開源、輕量的 VS Code 作為本地整合開發環境(IDE)。訪問 [VS Code 官網](https://code.visualstudio.com/),根據作業系統選擇相應版本的 VS Code 進行下載和安裝。 ![從官網下載 VS Code](installation.assets/vscode_installation.png) VS Code 擁有強大的擴展包生態系統,支持大多數程式語言的執行和除錯。以 Python 為例,安裝“Python Extension Pack”擴展包之後,即可進行 Python 程式碼除錯。安裝步驟如下圖所示。 ![安裝 VS Code 擴展包](installation.assets/vscode_extension_installation.png) ## 安裝語言環境 ### Python 環境 1. 下載並安裝 [Miniconda3](https://docs.conda.io/en/latest/miniconda.html) ,需要 Python 3.10 或更新版本。 2. 在 VS Code 的擴充功能市場中搜索 `python` ,安裝 Python Extension Pack 。 3. (可選)在命令列輸入 `pip install black` ,安裝程式碼格式化工具。 ### C/C++ 環境 1. Windows 系統需要安裝 [MinGW](https://sourceforge.net/projects/mingw-w64/files/)([配置教程](https://blog.csdn.net/qq_33698226/article/details/129031241));MacOS 自帶 Clang ,無須安裝。 2. 在 VS Code 的擴充功能市場中搜索 `c++` ,安裝 C/C++ Extension Pack 。 3. (可選)開啟 Settings 頁面,搜尋 `Clang_format_fallback Style` 程式碼格式化選項,設定為 `{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }` 。 ### Java 環境 1. 下載並安裝 [OpenJDK](https://jdk.java.net/18/)(版本需滿足 > JDK 9)。 2. 在 VS Code 的擴充功能市場中搜索 `java` ,安裝 Extension Pack for Java 。 ### C# 環境 1. 下載並安裝 [.Net 8.0](https://dotnet.microsoft.com/en-us/download) 。 2. 在 VS Code 的擴充功能市場中搜索 `C# Dev Kit` ,安裝 C# Dev Kit ([配置教程](https://code.visualstudio.com/docs/csharp/get-started))。 3. 也可使用 Visual Studio([安裝教程](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022))。 ### Go 環境 1. 下載並安裝 [go](https://go.dev/dl/) 。 2. 在 VS Code 的擴充功能市場中搜索 `go` ,安裝 Go 。 3. 按快捷鍵 `Ctrl + Shift + P` 撥出命令欄,輸入 go ,選擇 `Go: Install/Update Tools` ,全部勾選並安裝即可。 ### Swift 環境 1. 下載並安裝 [Swift](https://www.swift.org/download/) 。 2. 在 VS Code 的擴充功能市場中搜索 `swift` ,安裝 [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang) 。 ### JavaScript 環境 1. 下載並安裝 [Node.js](https://nodejs.org/en/) 。 2. (可選)在 VS Code 的擴充功能市場中搜索 `Prettier` ,安裝程式碼格式化工具。 ### TypeScript 環境 1. 同 JavaScript 環境安裝步驟。 2. 安裝 [TypeScript Execute (tsx)](https://github.com/privatenumber/tsx?tab=readme-ov-file#global-installation) 。 3. 在 VS Code 的擴充功能市場中搜索 `typescript` ,安裝 [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) 。 ### Dart 環境 1. 下載並安裝 [Dart](https://dart.dev/get-dart) 。 2. 在 VS Code 的擴充功能市場中搜索 `dart` ,安裝 [Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code) 。 ### Rust 環境 1. 下載並安裝 [Rust](https://www.rust-lang.org/tools/install) 。 2. 在 VS Code 的擴充功能市場中搜索 `rust` ,安裝 [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) 。 ================================================ FILE: zh-hant/docs/chapter_appendix/terminology.md ================================================ # 術語表 下表列出了書中出現的重要術語,值得注意以下幾點。 - 建議記住名詞的英文叫法,以便閱讀英文文獻。 - 部分名詞在簡體中文和繁體中文下的叫法不同。

  資料結構與演算法的重要名詞

| English | 簡體中文 | 繁體中文 | | ------------------------------ | -------------- | -------------- | | algorithm | 算法 | 演算法 | | data structure | 数据结构 | 資料結構 | | code | 代码 | 程式碼 | | file | 文件 | 檔案 | | function | 函数 | 函式 | | method | 方法 | 方法 | | variable | 变量 | 變數 | | asymptotic complexity analysis | 渐近复杂度分析 | 漸近複雜度分析 | | time complexity | 时间复杂度 | 時間複雜度 | | space complexity | 空间复杂度 | 空間複雜度 | | loop | 循环 | 迴圈 | | iteration | 迭代 | 迭代 | | recursion | 递归 | 遞迴 | | tail recursion | 尾递归 | 尾遞迴 | | recursion tree | 递归树 | 遞迴樹 | | big-$O$ notation | 大 $O$ 记号 | 大 $O$ 記號 | | asymptotic upper bound | 渐近上界 | 漸近上界 | | sign-magnitude | 原码 | 原碼 | | 1’s complement | 反码 | 一補數 | | 2’s complement | 补码 | 二補數 | | array | 数组 | 陣列 | | index | 索引 | 索引 | | linked list | 链表 | 鏈結串列 | | linked list node, list node | 链表节点 | 鏈結串列節點 | | head node | 头节点 | 頭節點 | | tail node | 尾节点 | 尾節點 | | list | 列表 | 串列 | | dynamic array | 动态数组 | 動態陣列 | | hard disk | 硬盘 | 硬碟 | | random-access memory (RAM) | 内存 | 記憶體 | | cache memory | 缓存 | 快取 | | cache miss | 缓存未命中 | 快取未命中 | | cache hit rate | 缓存命中率 | 快取命中率 | | stack | 栈 | 堆疊 | | top of the stack | 栈顶 | 堆疊頂 | | bottom of the stack | 栈底 | 堆疊底 | | queue | 队列 | 佇列 | | double-ended queue | 双向队列 | 雙向佇列 | | front of the queue | 队首 | 佇列首 | | rear of the queue | 队尾 | 佇列尾 | | hash table | 哈希表 | 雜湊表 | | hash set | 哈希集合 | 雜湊集合 | | bucket | 桶 | 桶 | | hash function | 哈希函数 | 雜湊函式 | | hash collision | 哈希冲突 | 雜湊衝突 | | load factor | 负载因子 | 負載因子 | | separate chaining | 链式地址 | 鏈結位址 | | open addressing | 开放寻址 | 開放定址 | | linear probing | 线性探测 | 線性探查 | | lazy deletion | 懒删除 | 懶刪除 | | binary tree | 二叉树 | 二元樹 | | tree node | 树节点 | 樹節點 | | left-child node | 左子节点 | 左子節點 | | right-child node | 右子节点 | 右子節點 | | parent node | 父节点 | 父節點 | | left subtree | 左子树 | 左子樹 | | right subtree | 右子树 | 右子樹 | | root node | 根节点 | 根節點 | | leaf node | 叶节点 | 葉節點 | | edge | 边 | 邊 | | level | 层 | 層 | | degree | 度 | 度 | | height | 高度 | 高度 | | depth | 深度 | 深度 | | perfect binary tree | 完美二叉树 | 完美二元樹 | | complete binary tree | 完全二叉树 | 完全二元樹 | | full binary tree | 完满二叉树 | 完滿二元樹 | | balanced binary tree | 平衡二叉树 | 平衡二元樹 | | binary search tree | 二叉搜索树 | 二元搜尋樹 | | AVL tree | AVL 树 | AVL 樹 | | red-black tree | 红黑树 | 紅黑樹 | | level-order traversal | 层序遍历 | 層序走訪 | | breadth-first traversal | 广度优先遍历 | 廣度優先走訪 | | depth-first traversal | 深度优先遍历 | 深度優先走訪 | | binary search tree | 二叉搜索树 | 二元搜尋樹 | | balanced binary search tree | 平衡二叉搜索树 | 平衡二元搜尋樹 | | balance factor | 平衡因子 | 平衡因子 | | heap | 堆 | 堆積 | | max heap | 大顶堆 | 大頂堆積 | | min heap | 小顶堆 | 小頂堆積 | | priority queue | 优先队列 | 優先佇列 | | heapify | 堆化 | 堆積化 | | top-$k$ problem | Top-$k$ 问题 | Top-$k$ 問題 | | graph | 图 | 圖 | | vertex | 顶点 | 頂點 | | undirected graph | 无向图 | 無向圖 | | directed graph | 有向图 | 有向圖 | | connected graph | 连通图 | 連通圖 | | disconnected graph | 非连通图 | 非連通圖 | | weighted graph | 有权图 | 有權圖 | | adjacency | 邻接 | 鄰接 | | path | 路径 | 路徑 | | in-degree | 入度 | 入度 | | out-degree | 出度 | 出度 | | adjacency matrix | 邻接矩阵 | 鄰接矩陣 | | adjacency list | 邻接表 | 鄰接表 | | breadth-first search | 广度优先搜索 | 廣度優先搜尋 | | depth-first search | 深度优先搜索 | 深度優先搜尋 | | binary search | 二分查找 | 二分搜尋 | | searching algorithm | 搜索算法 | 搜尋演算法 | | sorting algorithm | 排序算法 | 排序演算法 | | selection sort | 选择排序 | 選擇排序 | | bubble sort | 冒泡排序 | 泡沫排序 | | insertion sort | 插入排序 | 插入排序 | | quick sort | 快速排序 | 快速排序 | | merge sort | 归并排序 | 合併排序 | | heap sort | 堆排序 | 堆積排序 | | bucket sort | 桶排序 | 桶排序 | | counting sort | 计数排序 | 計數排序 | | radix sort | 基数排序 | 基數排序 | | divide and conquer | 分治 | 分治 | | hanota problem | 汉诺塔问题 | 河內塔問題 | | backtracking algorithm | 回溯算法 | 回溯演算法 | | constraint | 约束 | 約束 | | solution | 解 | 解 | | state | 状态 | 狀態 | | pruning | 剪枝 | 剪枝 | | permutations problem | 全排列问题 | 全排列問題 | | subset-sum problem | 子集和问题 | 子集合問題 | | $n$-queens problem | $n$ 皇后问题 | $n$ 皇后問題 | | dynamic programming | 动态规划 | 動態規劃 | | initial state | 初始状态 | 初始狀態 | | state-transition equation | 状态转移方程 | 狀態轉移方程 | | knapsack problem | 背包问题 | 背包問題 | | edit distance problem | 编辑距离问题 | 編輯距離問題 | | greedy algorithm | 贪心算法 | 貪婪演算法 | ================================================ FILE: zh-hant/docs/chapter_array_and_linkedlist/array.md ================================================ # 陣列 陣列(array)是一種線性資料結構,其將相同型別的元素儲存在連續的記憶體空間中。我們將元素在陣列中的位置稱為該元素的索引(index)。下圖展示了陣列的主要概念和儲存方式。 ![陣列定義與儲存方式](array.assets/array_definition.png) ## 陣列常用操作 ### 初始化陣列 我們可以根據需求選用陣列的兩種初始化方式:無初始值、給定初始值。在未指定初始值的情況下,大多數程式語言會將陣列元素初始化為 $0$ : === "Python" ```python title="array.py" # 初始化陣列 arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] nums: list[int] = [1, 3, 2, 5, 4] ``` === "C++" ```cpp title="array.cpp" /* 初始化陣列 */ // 儲存在堆疊上 int arr[5]; int nums[5] = { 1, 3, 2, 5, 4 }; // 儲存在堆積上(需要手動釋放空間) int* arr1 = new int[5]; int* nums1 = new int[5] { 1, 3, 2, 5, 4 }; ``` === "Java" ```java title="array.java" /* 初始化陣列 */ int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } int[] nums = { 1, 3, 2, 5, 4 }; ``` === "C#" ```csharp title="array.cs" /* 初始化陣列 */ int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ] int[] nums = [1, 3, 2, 5, 4]; ``` === "Go" ```go title="array.go" /* 初始化陣列 */ var arr [5]int // 在 Go 中,指定長度時([5]int)為陣列,不指定長度時([]int)為切片 // 由於 Go 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度 // 為了方便實現擴容 extend() 方法,以下將切片(Slice)看作陣列(Array) nums := []int{1, 3, 2, 5, 4} ``` === "Swift" ```swift title="array.swift" /* 初始化陣列 */ let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] let nums = [1, 3, 2, 5, 4] ``` === "JS" ```javascript title="array.js" /* 初始化陣列 */ var arr = new Array(5).fill(0); var nums = [1, 3, 2, 5, 4]; ``` === "TS" ```typescript title="array.ts" /* 初始化陣列 */ let arr: number[] = new Array(5).fill(0); let nums: number[] = [1, 3, 2, 5, 4]; ``` === "Dart" ```dart title="array.dart" /* 初始化陣列 */ List arr = List.filled(5, 0); // [0, 0, 0, 0, 0] List nums = [1, 3, 2, 5, 4]; ``` === "Rust" ```rust title="array.rs" /* 初始化陣列 */ let arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0] let slice: &[i32] = &[0; 5]; // 在 Rust 中,指定長度時([i32; 5])為陣列,不指定長度時(&[i32])為切片 // 由於 Rust 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度 // Vector 是 Rust 一般情況下用作動態陣列的型別 // 為了方便實現擴容 extend() 方法,以下將 vector 看作陣列(array) let nums: Vec = vec![1, 3, 2, 5, 4]; ``` === "C" ```c title="array.c" /* 初始化陣列 */ int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 } int nums[5] = { 1, 3, 2, 5, 4 }; ``` === "Kotlin" ```kotlin title="array.kt" /* 初始化陣列 */ var arr = IntArray(5) // { 0, 0, 0, 0, 0 } var nums = intArrayOf(1, 3, 2, 5, 4) ``` === "Ruby" ```ruby title="array.rb" # 初始化陣列 arr = Array.new(5, 0) nums = [1, 3, 2, 5, 4] ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0Aarr%20%3D%20%5B0%5D%20%2A%205%20%20%23%20%5B%200%2C%200%2C%200%2C%200%2C%200%20%5D%0Anums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 訪問元素 陣列元素被儲存在連續的記憶體空間中,這意味著計算陣列元素的記憶體位址非常容易。給定陣列記憶體位址(首元素記憶體位址)和某個元素的索引,我們可以使用下圖所示的公式計算得到該元素的記憶體位址,從而直接訪問該元素。 ![陣列元素的記憶體位址計算](array.assets/array_memory_location_calculation.png) 觀察上圖,我們發現陣列首個元素的索引為 $0$ ,這似乎有些反直覺,因為從 $1$ 開始計數會更自然。但從位址計算公式的角度看,**索引本質上是記憶體位址的偏移量**。首個元素的位址偏移量是 $0$ ,因此它的索引為 $0$ 是合理的。 在陣列中訪問元素非常高效,我們可以在 $O(1)$ 時間內隨機訪問陣列中的任意一個元素。 ```src [file]{array}-[class]{}-[func]{random_access} ``` ### 插入元素 陣列元素在記憶體中是“緊挨著的”,它們之間沒有空間再存放任何資料。如下圖所示,如果想在陣列中間插入一個元素,則需要將該元素之後的所有元素都向後移動一位,之後再把元素賦值給該索引。 ![陣列插入元素示例](array.assets/array_insert_element.png) 值得注意的是,由於陣列的長度是固定的,因此插入一個元素必定會導致陣列尾部元素“丟失”。我們將這個問題的解決方案留在“串列”章節中討論。 ```src [file]{array}-[class]{}-[func]{insert} ``` ### 刪除元素 同理,如下圖所示,若想刪除索引 $i$ 處的元素,則需要把索引 $i$ 之後的元素都向前移動一位。 ![陣列刪除元素示例](array.assets/array_remove_element.png) 請注意,刪除元素完成後,原先末尾的元素變得“無意義”了,所以我們無須特意去修改它。 ```src [file]{array}-[class]{}-[func]{remove} ``` 總的來看,陣列的插入與刪除操作有以下缺點。 - **時間複雜度高**:陣列的插入和刪除的平均時間複雜度均為 $O(n)$ ,其中 $n$ 為陣列長度。 - **丟失元素**:由於陣列的長度不可變,因此在插入元素後,超出陣列長度範圍的元素會丟失。 - **記憶體浪費**:我們可以初始化一個比較長的陣列,只用前面一部分,這樣在插入資料時,丟失的末尾元素都是“無意義”的,但這樣做會造成部分記憶體空間浪費。 ### 走訪陣列 在大多數程式語言中,我們既可以透過索引走訪陣列,也可以直接走訪獲取陣列中的每個元素: ```src [file]{array}-[class]{}-[func]{traverse} ``` ### 查詢元素 在陣列中查詢指定元素需要走訪陣列,每輪判斷元素值是否匹配,若匹配則輸出對應索引。 因為陣列是線性資料結構,所以上述查詢操作被稱為“線性查詢”。 ```src [file]{array}-[class]{}-[func]{find} ``` ### 擴容陣列 在複雜的系統環境中,程式難以保證陣列之後的記憶體空間是可用的,從而無法安全地擴展陣列容量。因此在大多數程式語言中,**陣列的長度是不可變的**。 如果我們希望擴容陣列,則需重新建立一個更大的陣列,然後把原陣列元素依次複製到新陣列。這是一個 $O(n)$ 的操作,在陣列很大的情況下非常耗時。程式碼如下所示: ```src [file]{array}-[class]{}-[func]{extend} ``` ## 陣列的優點與侷限性 陣列儲存在連續的記憶體空間內,且元素型別相同。這種做法包含豐富的先驗資訊,系統可以利用這些資訊來最佳化資料結構的操作效率。 - **空間效率高**:陣列為資料分配了連續的記憶體塊,無須額外的結構開銷。 - **支持隨機訪問**:陣列允許在 $O(1)$ 時間內訪問任何元素。 - **快取區域性**:當訪問陣列元素時,計算機不僅會載入它,還會快取其周圍的其他資料,從而藉助高速快取來提升後續操作的執行速度。 連續空間儲存是一把雙刃劍,其存在以下侷限性。 - **插入與刪除效率低**:當陣列中元素較多時,插入與刪除操作需要移動大量的元素。 - **長度不可變**:陣列在初始化後長度就固定了,擴容陣列需要將所有資料複製到新陣列,開銷很大。 - **空間浪費**:如果陣列分配的大小超過實際所需,那麼多餘的空間就被浪費了。 ## 陣列典型應用 陣列是一種基礎且常見的資料結構,既頻繁應用在各類演算法之中,也可用於實現各種複雜資料結構。 - **隨機訪問**:如果我們想隨機抽取一些樣本,那麼可以用陣列儲存,並生成一個隨機序列,根據索引實現隨機抽樣。 - **排序和搜尋**:陣列是排序和搜尋演算法最常用的資料結構。快速排序、合併排序、二分搜尋等都主要在陣列上進行。 - **查詢表**:當需要快速查詢一個元素或其對應關係時,可以使用陣列作為查詢表。假如我們想實現字元到 ASCII 碼的對映,則可以將字元的 ASCII 碼值作為索引,對應的元素存放在陣列中的對應位置。 - **機器學習**:神經網路中大量使用了向量、矩陣、張量之間的線性代數運算,這些資料都是以陣列的形式構建的。陣列是神經網路程式設計中最常使用的資料結構。 - **資料結構實現**:陣列可以用於實現堆疊、佇列、雜湊表、堆積、圖等資料結構。例如,圖的鄰接矩陣表示實際上是一個二維陣列。 ================================================ FILE: zh-hant/docs/chapter_array_and_linkedlist/index.md ================================================ # 陣列與鏈結串列 ![陣列與鏈結串列](../assets/covers/chapter_array_and_linkedlist.jpg) !!! abstract 資料結構的世界如同一堵厚實的磚牆。 陣列的磚塊整齊排列,逐個緊貼。鏈結串列的磚塊分散各處,連線的藤蔓自由地穿梭於磚縫之間。 ================================================ FILE: zh-hant/docs/chapter_array_and_linkedlist/linked_list.md ================================================ # 鏈結串列 記憶體空間是所有程式的公共資源,在一個複雜的系統執行環境下,空閒的記憶體空間可能散落在記憶體各處。我們知道,儲存陣列的記憶體空間必須是連續的,而當陣列非常大時,記憶體可能無法提供如此大的連續空間。此時鏈結串列的靈活性優勢就體現出來了。 鏈結串列(linked list)是一種線性資料結構,其中的每個元素都是一個節點物件,各個節點透過“引用”相連線。引用記錄了下一個節點的記憶體位址,透過它可以從當前節點訪問到下一個節點。 鏈結串列的設計使得各個節點可以分散儲存在記憶體各處,它們的記憶體位址無須連續。 ![鏈結串列定義與儲存方式](linked_list.assets/linkedlist_definition.png) 觀察上圖,鏈結串列的組成單位是節點(node)物件。每個節點都包含兩項資料:節點的“值”和指向下一節點的“引用”。 - 鏈結串列的首個節點被稱為“頭節點”,最後一個節點被稱為“尾節點”。 - 尾節點指向的是“空”,它在 Java、C++ 和 Python 中分別被記為 `null`、`nullptr` 和 `None` 。 - 在 C、C++、Go 和 Rust 等支持指標的語言中,上述“引用”應被替換為“指標”。 如以下程式碼所示,鏈結串列節點 `ListNode` 除了包含值,還需額外儲存一個引用(指標)。因此在相同資料量下,**鏈結串列比陣列佔用更多的記憶體空間**。 === "Python" ```python title="" class ListNode: """鏈結串列節點類別""" def __init__(self, val: int): self.val: int = val # 節點值 self.next: ListNode | None = None # 指向下一節點的引用 ``` === "C++" ```cpp title="" /* 鏈結串列節點結構體 */ struct ListNode { int val; // 節點值 ListNode *next; // 指向下一節點的指標 ListNode(int x) : val(x), next(nullptr) {} // 建構子 }; ``` === "Java" ```java title="" /* 鏈結串列節點類別 */ class ListNode { int val; // 節點值 ListNode next; // 指向下一節點的引用 ListNode(int x) { val = x; } // 建構子 } ``` === "C#" ```csharp title="" /* 鏈結串列節點類別 */ class ListNode(int x) { //建構子 int val = x; // 節點值 ListNode? next; // 指向下一節點的引用 } ``` === "Go" ```go title="" /* 鏈結串列節點結構體 */ type ListNode struct { Val int // 節點值 Next *ListNode // 指向下一節點的指標 } // NewListNode 建構子,建立一個新的鏈結串列 func NewListNode(val int) *ListNode { return &ListNode{ Val: val, Next: nil, } } ``` === "Swift" ```swift title="" /* 鏈結串列節點類別 */ class ListNode { var val: Int // 節點值 var next: ListNode? // 指向下一節點的引用 init(x: Int) { // 建構子 val = x } } ``` === "JS" ```javascript title="" /* 鏈結串列節點類別 */ class ListNode { constructor(val, next) { this.val = (val === undefined ? 0 : val); // 節點值 this.next = (next === undefined ? null : next); // 指向下一節點的引用 } } ``` === "TS" ```typescript title="" /* 鏈結串列節點類別 */ class ListNode { val: number; next: ListNode | null; constructor(val?: number, next?: ListNode | null) { this.val = val === undefined ? 0 : val; // 節點值 this.next = next === undefined ? null : next; // 指向下一節點的引用 } } ``` === "Dart" ```dart title="" /* 鏈結串列節點類別 */ class ListNode { int val; // 節點值 ListNode? next; // 指向下一節點的引用 ListNode(this.val, [this.next]); // 建構子 } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* 鏈結串列節點類別 */ #[derive(Debug)] struct ListNode { val: i32, // 節點值 next: Option>>, // 指向下一節點的指標 } ``` === "C" ```c title="" /* 鏈結串列節點結構體 */ typedef struct ListNode { int val; // 節點值 struct ListNode *next; // 指向下一節點的指標 } ListNode; /* 建構子 */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *) malloc(sizeof(ListNode)); node->val = val; node->next = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* 鏈結串列節點類別 */ // 建構子 class ListNode(x: Int) { val _val: Int = x // 節點值 val next: ListNode? = null // 指向下一個節點的引用 } ``` === "Ruby" ```ruby title="" # 鏈結串列節點類別 class ListNode attr_accessor :val # 節點值 attr_accessor :next # 指向下一節點的引用 def initialize(val=0, next_node=nil) @val = val @next = next_node end end ``` ## 鏈結串列常用操作 ### 初始化鏈結串列 建立鏈結串列分為兩步,第一步是初始化各個節點物件,第二步是構建節點之間的引用關係。初始化完成後,我們就可以從鏈結串列的頭節點出發,透過引用指向 `next` 依次訪問所有節點。 === "Python" ```python title="linked_list.py" # 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 # 初始化各個節點 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 ``` === "C++" ```cpp title="linked_list.cpp" /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各個節點 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; ``` === "Java" ```java title="linked_list.java" /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各個節點 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; ``` === "C#" ```csharp title="linked_list.cs" /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各個節點 ListNode n0 = new(1); ListNode n1 = new(3); ListNode n2 = new(2); ListNode n3 = new(5); ListNode n4 = new(4); // 構建節點之間的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Go" ```go title="linked_list.go" /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各個節點 n0 := NewListNode(1) n1 := NewListNode(3) n2 := NewListNode(2) n3 := NewListNode(5) n4 := NewListNode(4) // 構建節點之間的引用 n0.Next = n1 n1.Next = n2 n2.Next = n3 n3.Next = n4 ``` === "Swift" ```swift title="linked_list.swift" /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各個節點 let n0 = ListNode(x: 1) let n1 = ListNode(x: 3) let n2 = ListNode(x: 2) let n3 = ListNode(x: 5) let n4 = ListNode(x: 4) // 構建節點之間的引用 n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 ``` === "JS" ```javascript title="linked_list.js" /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各個節點 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // 構建節點之間的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "TS" ```typescript title="linked_list.ts" /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各個節點 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); // 構建節點之間的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Dart" ```dart title="linked_list.dart" /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */\ // 初始化各個節點 ListNode n0 = ListNode(1); ListNode n1 = ListNode(3); ListNode n2 = ListNode(2); ListNode n3 = ListNode(5); ListNode n4 = ListNode(4); // 構建節點之間的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Rust" ```rust title="linked_list.rs" /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各個節點 let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None })); let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None })); let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None })); let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None })); let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None })); // 構建節點之間的引用 n0.borrow_mut().next = Some(n1.clone()); n1.borrow_mut().next = Some(n2.clone()); n2.borrow_mut().next = Some(n3.clone()); n3.borrow_mut().next = Some(n4.clone()); ``` === "C" ```c title="linked_list.c" /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各個節點 ListNode* n0 = newListNode(1); ListNode* n1 = newListNode(3); ListNode* n2 = newListNode(2); ListNode* n3 = newListNode(5); ListNode* n4 = newListNode(4); // 構建節點之間的引用 n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; ``` === "Kotlin" ```kotlin title="linked_list.kt" /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各個節點 val n0 = ListNode(1) val n1 = ListNode(3) val n2 = ListNode(2) val n3 = ListNode(5) val n4 = ListNode(4) // 構建節點之間的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` === "Ruby" ```ruby title="linked_list.rb" # 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 # 初始化各個節點 n0 = ListNode.new(1) n1 = ListNode.new(3) n2 = ListNode.new(2) n3 = ListNode.new(5) n4 = ListNode.new(4) # 構建節點之間的引用 n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%201%20-%3E%203%20-%3E%202%20-%3E%205%20-%3E%204%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E5%80%8B%E7%AF%80%E9%BB%9E%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false 陣列整體是一個變數,比如陣列 `nums` 包含元素 `nums[0]` 和 `nums[1]` 等,而鏈結串列是由多個獨立的節點物件組成的。**我們通常將頭節點當作鏈結串列的代稱**,比如以上程式碼中的鏈結串列可記作鏈結串列 `n0` 。 ### 插入節點 在鏈結串列中插入節點非常容易。如下圖所示,假設我們想在相鄰的兩個節點 `n0` 和 `n1` 之間插入一個新節點 `P` ,**則只需改變兩個節點引用(指標)即可**,時間複雜度為 $O(1)$ 。 相比之下,在陣列中插入元素的時間複雜度為 $O(n)$ ,在大資料量下的效率較低。 ![鏈結串列插入節點示例](linked_list.assets/linkedlist_insert_node.png) ```src [file]{linked_list}-[class]{}-[func]{insert} ``` ### 刪除節點 如下圖所示,在鏈結串列中刪除節點也非常方便,**只需改變一個節點的引用(指標)即可**。 請注意,儘管在刪除操作完成後節點 `P` 仍然指向 `n1` ,但實際上走訪此鏈結串列已經無法訪問到 `P` ,這意味著 `P` 已經不再屬於該鏈結串列了。 ![鏈結串列刪除節點](linked_list.assets/linkedlist_remove_node.png) ```src [file]{linked_list}-[class]{}-[func]{remove} ``` ### 訪問節點 **在鏈結串列中訪問節點的效率較低**。如上一節所述,我們可以在 $O(1)$ 時間下訪問陣列中的任意元素。鏈結串列則不然,程式需要從頭節點出發,逐個向後走訪,直至找到目標節點。也就是說,訪問鏈結串列的第 $i$ 個節點需要迴圈 $i - 1$ 輪,時間複雜度為 $O(n)$ 。 ```src [file]{linked_list}-[class]{}-[func]{access} ``` ### 查詢節點 走訪鏈結串列,查詢其中值為 `target` 的節點,輸出該節點在鏈結串列中的索引。此過程也屬於線性查詢。程式碼如下所示: ```src [file]{linked_list}-[class]{}-[func]{find} ``` ## 陣列 vs. 鏈結串列 下表總結了陣列和鏈結串列的各項特點並對比了操作效率。由於它們採用兩種相反的儲存策略,因此各種性質和操作效率也呈現對立的特點。

  陣列與鏈結串列的效率對比

| | 陣列 | 鏈結串列 | | -------- | ------------------------------ | -------------- | | 儲存方式 | 連續記憶體空間 | 分散記憶體空間 | | 容量擴展 | 長度不可變 | 可靈活擴展 | | 記憶體效率 | 元素佔用記憶體少、但可能浪費空間 | 元素佔用記憶體多 | | 訪問元素 | $O(1)$ | $O(n)$ | | 新增元素 | $O(n)$ | $O(1)$ | | 刪除元素 | $O(n)$ | $O(1)$ | ## 常見鏈結串列型別 如下圖所示,常見的鏈結串列型別包括三種。 - **單向鏈結串列**:即前面介紹的普通鏈結串列。單向鏈結串列的節點包含值和指向下一節點的引用兩項資料。我們將首個節點稱為頭節點,將最後一個節點稱為尾節點,尾節點指向空 `None` 。 - **環形鏈結串列**:如果我們令單向鏈結串列的尾節點指向頭節點(首尾相接),則得到一個環形鏈結串列。在環形鏈結串列中,任意節點都可以視作頭節點。 - **雙向鏈結串列**:與單向鏈結串列相比,雙向鏈結串列記錄了兩個方向的引用。雙向鏈結串列的節點定義同時包含指向後繼節點(下一個節點)和前驅節點(上一個節點)的引用(指標)。相較於單向鏈結串列,雙向鏈結串列更具靈活性,可以朝兩個方向走訪鏈結串列,但相應地也需要佔用更多的記憶體空間。 === "Python" ```python title="" class ListNode: """雙向鏈結串列節點類別""" def __init__(self, val: int): self.val: int = val # 節點值 self.next: ListNode | None = None # 指向後繼節點的引用 self.prev: ListNode | None = None # 指向前驅節點的引用 ``` === "C++" ```cpp title="" /* 雙向鏈結串列節點結構體 */ struct ListNode { int val; // 節點值 ListNode *next; // 指向後繼節點的指標 ListNode *prev; // 指向前驅節點的指標 ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // 建構子 }; ``` === "Java" ```java title="" /* 雙向鏈結串列節點類別 */ class ListNode { int val; // 節點值 ListNode next; // 指向後繼節點的引用 ListNode prev; // 指向前驅節點的引用 ListNode(int x) { val = x; } // 建構子 } ``` === "C#" ```csharp title="" /* 雙向鏈結串列節點類別 */ class ListNode(int x) { // 建構子 int val = x; // 節點值 ListNode next; // 指向後繼節點的引用 ListNode prev; // 指向前驅節點的引用 } ``` === "Go" ```go title="" /* 雙向鏈結串列節點結構體 */ type DoublyListNode struct { Val int // 節點值 Next *DoublyListNode // 指向後繼節點的指標 Prev *DoublyListNode // 指向前驅節點的指標 } // NewDoublyListNode 初始化 func NewDoublyListNode(val int) *DoublyListNode { return &DoublyListNode{ Val: val, Next: nil, Prev: nil, } } ``` === "Swift" ```swift title="" /* 雙向鏈結串列節點類別 */ class ListNode { var val: Int // 節點值 var next: ListNode? // 指向後繼節點的引用 var prev: ListNode? // 指向前驅節點的引用 init(x: Int) { // 建構子 val = x } } ``` === "JS" ```javascript title="" /* 雙向鏈結串列節點類別 */ class ListNode { constructor(val, next, prev) { this.val = val === undefined ? 0 : val; // 節點值 this.next = next === undefined ? null : next; // 指向後繼節點的引用 this.prev = prev === undefined ? null : prev; // 指向前驅節點的引用 } } ``` === "TS" ```typescript title="" /* 雙向鏈結串列節點類別 */ class ListNode { val: number; next: ListNode | null; prev: ListNode | null; constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) { this.val = val === undefined ? 0 : val; // 節點值 this.next = next === undefined ? null : next; // 指向後繼節點的引用 this.prev = prev === undefined ? null : prev; // 指向前驅節點的引用 } } ``` === "Dart" ```dart title="" /* 雙向鏈結串列節點類別 */ class ListNode { int val; // 節點值 ListNode? next; // 指向後繼節點的引用 ListNode? prev; // 指向前驅節點的引用 ListNode(this.val, [this.next, this.prev]); // 建構子 } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* 雙向鏈結串列節點型別 */ #[derive(Debug)] struct ListNode { val: i32, // 節點值 next: Option>>, // 指向後繼節點的指標 prev: Option>>, // 指向前驅節點的指標 } /* 建構子 */ impl ListNode { fn new(val: i32) -> Self { ListNode { val, next: None, prev: None, } } } ``` === "C" ```c title="" /* 雙向鏈結串列節點結構體 */ typedef struct ListNode { int val; // 節點值 struct ListNode *next; // 指向後繼節點的指標 struct ListNode *prev; // 指向前驅節點的指標 } ListNode; /* 建構子 */ ListNode *newListNode(int val) { ListNode *node; node = (ListNode *) malloc(sizeof(ListNode)); node->val = val; node->next = NULL; node->prev = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* 雙向鏈結串列節點類別 */ // 建構子 class ListNode(x: Int) { val _val: Int = x // 節點值 val next: ListNode? = null // 指向後繼節點的引用 val prev: ListNode? = null // 指向前驅節點的引用 } ``` === "Ruby" ```ruby title="" # 雙向鏈結串列節點類別 class ListNode attr_accessor :val # 節點值 attr_accessor :next # 指向後繼節點的引用 attr_accessor :prev # 指向前驅節點的引用 def initialize(val=0, next_node=nil, prev_node=nil) @val = val @next = next_node @prev = prev_node end end ``` ![常見鏈結串列種類](linked_list.assets/linkedlist_common_types.png) ## 鏈結串列典型應用 單向鏈結串列通常用於實現堆疊、佇列、雜湊表和圖等資料結構。 - **堆疊與佇列**:當插入和刪除操作都在鏈結串列的一端進行時,它表現的特性為先進後出,對應堆疊;當插入操作在鏈結串列的一端進行,刪除操作在鏈結串列的另一端進行,它表現的特性為先進先出,對應佇列。 - **雜湊表**:鏈式位址是解決雜湊衝突的主流方案之一,在該方案中,所有衝突的元素都會被放到一個鏈結串列中。 - **圖**:鄰接表是表示圖的一種常用方式,其中圖的每個頂點都與一個鏈結串列相關聯,鏈結串列中的每個元素都代表與該頂點相連的其他頂點。 雙向鏈結串列常用於需要快速查詢前一個和後一個元素的場景。 - **高階資料結構**:比如在紅黑樹、B 樹中,我們需要訪問節點的父節點,這可以透過在節點中儲存一個指向父節點的引用來實現,類似於雙向鏈結串列。 - **瀏覽器歷史**:在網頁瀏覽器中,當用戶點選前進或後退按鈕時,瀏覽器需要知道使用者訪問過的前一個和後一個網頁。雙向鏈結串列的特性使得這種操作變得簡單。 - **LRU 演算法**:在快取淘汰(LRU)演算法中,我們需要快速找到最近最少使用的資料,以及支持快速新增和刪除節點。這時候使用雙向鏈結串列就非常合適。 環形鏈結串列常用於需要週期性操作的場景,比如作業系統的資源排程。 - **時間片輪轉排程演算法**:在作業系統中,時間片輪轉排程演算法是一種常見的 CPU 排程演算法,它需要對一組程序進行迴圈。每個程序被賦予一個時間片,當時間片用完時,CPU 將切換到下一個程序。這種迴圈操作可以透過環形鏈結串列來實現。 - **資料緩衝區**:在某些資料緩衝區的實現中,也可能會使用環形鏈結串列。比如在音訊、影片播放器中,資料流可能會被分成多個緩衝塊並放入一個環形鏈結串列,以便實現無縫播放。 ================================================ FILE: zh-hant/docs/chapter_array_and_linkedlist/list.md ================================================ # 串列 串列(list)是一個抽象的資料結構概念,它表示元素的有序集合,支持元素訪問、修改、新增、刪除和走訪等操作,無須使用者考慮容量限制的問題。串列可以基於鏈結串列或陣列實現。 - 鏈結串列天然可以看作一個串列,其支持元素增刪查改操作,並且可以靈活動態擴容。 - 陣列也支持元素增刪查改,但由於其長度不可變,因此只能看作一個具有長度限制的串列。 當使用陣列實現串列時,**長度不可變的性質會導致串列的實用性降低**。這是因為我們通常無法事先確定需要儲存多少資料,從而難以選擇合適的串列長度。若長度過小,則很可能無法滿足使用需求;若長度過大,則會造成記憶體空間浪費。 為解決此問題,我們可以使用動態陣列(dynamic array)來實現串列。它繼承了陣列的各項優點,並且可以在程式執行過程中進行動態擴容。 實際上,**許多程式語言中的標準庫提供的串列是基於動態陣列實現的**,例如 Python 中的 `list` 、Java 中的 `ArrayList` 、C++ 中的 `vector` 和 C# 中的 `List` 等。在接下來的討論中,我們將把“串列”和“動態陣列”視為等同的概念。 ## 串列常用操作 ### 初始化串列 我們通常使用“無初始值”和“有初始值”這兩種初始化方法: === "Python" ```python title="list.py" # 初始化串列 # 無初始值 nums1: list[int] = [] # 有初始值 nums: list[int] = [1, 3, 2, 5, 4] ``` === "C++" ```cpp title="list.cpp" /* 初始化串列 */ // 需注意,C++ 中 vector 即是本文描述的 nums // 無初始值 vector nums1; // 有初始值 vector nums = { 1, 3, 2, 5, 4 }; ``` === "Java" ```java title="list.java" /* 初始化串列 */ // 無初始值 List nums1 = new ArrayList<>(); // 有初始值(注意陣列的元素型別需為 int[] 的包裝類別 Integer[]) Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; List nums = new ArrayList<>(Arrays.asList(numbers)); ``` === "C#" ```csharp title="list.cs" /* 初始化串列 */ // 無初始值 List nums1 = []; // 有初始值 int[] numbers = [1, 3, 2, 5, 4]; List nums = [.. numbers]; ``` === "Go" ```go title="list_test.go" /* 初始化串列 */ // 無初始值 nums1 := []int{} // 有初始值 nums := []int{1, 3, 2, 5, 4} ``` === "Swift" ```swift title="list.swift" /* 初始化串列 */ // 無初始值 let nums1: [Int] = [] // 有初始值 var nums = [1, 3, 2, 5, 4] ``` === "JS" ```javascript title="list.js" /* 初始化串列 */ // 無初始值 const nums1 = []; // 有初始值 const nums = [1, 3, 2, 5, 4]; ``` === "TS" ```typescript title="list.ts" /* 初始化串列 */ // 無初始值 const nums1: number[] = []; // 有初始值 const nums: number[] = [1, 3, 2, 5, 4]; ``` === "Dart" ```dart title="list.dart" /* 初始化串列 */ // 無初始值 List nums1 = []; // 有初始值 List nums = [1, 3, 2, 5, 4]; ``` === "Rust" ```rust title="list.rs" /* 初始化串列 */ // 無初始值 let nums1: Vec = Vec::new(); // 有初始值 let nums: Vec = vec![1, 3, 2, 5, 4]; ``` === "C" ```c title="list.c" // C 未提供內建動態陣列 ``` === "Kotlin" ```kotlin title="list.kt" /* 初始化串列 */ // 無初始值 var nums1 = listOf() // 有初始值 var numbers = arrayOf(1, 3, 2, 5, 4) var nums = numbers.toMutableList() ``` === "Ruby" ```ruby title="list.rb" # 初始化串列 # 無初始值 nums1 = [] # 有初始值 nums = [1, 3, 2, 5, 4] ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%B2%E5%88%97%0A%20%20%20%20%23%20%E7%84%A1%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums1%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 訪問元素 串列本質上是陣列,因此可以在 $O(1)$ 時間內訪問和更新元素,效率很高。 === "Python" ```python title="list.py" # 訪問元素 num: int = nums[1] # 訪問索引 1 處的元素 # 更新元素 nums[1] = 0 # 將索引 1 處的元素更新為 0 ``` === "C++" ```cpp title="list.cpp" /* 訪問元素 */ int num = nums[1]; // 訪問索引 1 處的元素 /* 更新元素 */ nums[1] = 0; // 將索引 1 處的元素更新為 0 ``` === "Java" ```java title="list.java" /* 訪問元素 */ int num = nums.get(1); // 訪問索引 1 處的元素 /* 更新元素 */ nums.set(1, 0); // 將索引 1 處的元素更新為 0 ``` === "C#" ```csharp title="list.cs" /* 訪問元素 */ int num = nums[1]; // 訪問索引 1 處的元素 /* 更新元素 */ nums[1] = 0; // 將索引 1 處的元素更新為 0 ``` === "Go" ```go title="list_test.go" /* 訪問元素 */ num := nums[1] // 訪問索引 1 處的元素 /* 更新元素 */ nums[1] = 0 // 將索引 1 處的元素更新為 0 ``` === "Swift" ```swift title="list.swift" /* 訪問元素 */ let num = nums[1] // 訪問索引 1 處的元素 /* 更新元素 */ nums[1] = 0 // 將索引 1 處的元素更新為 0 ``` === "JS" ```javascript title="list.js" /* 訪問元素 */ const num = nums[1]; // 訪問索引 1 處的元素 /* 更新元素 */ nums[1] = 0; // 將索引 1 處的元素更新為 0 ``` === "TS" ```typescript title="list.ts" /* 訪問元素 */ const num: number = nums[1]; // 訪問索引 1 處的元素 /* 更新元素 */ nums[1] = 0; // 將索引 1 處的元素更新為 0 ``` === "Dart" ```dart title="list.dart" /* 訪問元素 */ int num = nums[1]; // 訪問索引 1 處的元素 /* 更新元素 */ nums[1] = 0; // 將索引 1 處的元素更新為 0 ``` === "Rust" ```rust title="list.rs" /* 訪問元素 */ let num: i32 = nums[1]; // 訪問索引 1 處的元素 /* 更新元素 */ nums[1] = 0; // 將索引 1 處的元素更新為 0 ``` === "C" ```c title="list.c" // C 未提供內建動態陣列 ``` === "Kotlin" ```kotlin title="list.kt" /* 訪問元素 */ val num = nums[1] // 訪問索引 1 處的元素 /* 更新元素 */ nums[1] = 0 // 將索引 1 處的元素更新為 0 ``` === "Ruby" ```ruby title="list.rb" # 訪問元素 num = nums[1] # 訪問索引 1 處的元素 # 更新元素 nums[1] = 0 # 將索引 1 處的元素更新為 0 ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%B2%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums%5B1%5D%20%20%23%20%E8%A8%AA%E5%95%8F%E7%B4%A2%E5%BC%95%201%20%E8%99%95%E7%9A%84%E5%85%83%E7%B4%A0%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5B1%5D%20%3D%200%20%20%20%20%23%20%E5%B0%87%E7%B4%A2%E5%BC%95%201%20%E8%99%95%E7%9A%84%E5%85%83%E7%B4%A0%E6%9B%B4%E6%96%B0%E7%82%BA%200&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 插入與刪除元素 相較於陣列,串列可以自由地新增與刪除元素。在串列尾部新增元素的時間複雜度為 $O(1)$ ,但插入和刪除元素的效率仍與陣列相同,時間複雜度為 $O(n)$ 。 === "Python" ```python title="list.py" # 清空串列 nums.clear() # 在尾部新增元素 nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) # 在中間插入元素 nums.insert(3, 6) # 在索引 3 處插入數字 6 # 刪除元素 nums.pop(3) # 刪除索引 3 處的元素 ``` === "C++" ```cpp title="list.cpp" /* 清空串列 */ nums.clear(); /* 在尾部新增元素 */ nums.push_back(1); nums.push_back(3); nums.push_back(2); nums.push_back(5); nums.push_back(4); /* 在中間插入元素 */ nums.insert(nums.begin() + 3, 6); // 在索引 3 處插入數字 6 /* 刪除元素 */ nums.erase(nums.begin() + 3); // 刪除索引 3 處的元素 ``` === "Java" ```java title="list.java" /* 清空串列 */ nums.clear(); /* 在尾部新增元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); /* 在中間插入元素 */ nums.add(3, 6); // 在索引 3 處插入數字 6 /* 刪除元素 */ nums.remove(3); // 刪除索引 3 處的元素 ``` === "C#" ```csharp title="list.cs" /* 清空串列 */ nums.Clear(); /* 在尾部新增元素 */ nums.Add(1); nums.Add(3); nums.Add(2); nums.Add(5); nums.Add(4); /* 在中間插入元素 */ nums.Insert(3, 6); // 在索引 3 處插入數字 6 /* 刪除元素 */ nums.RemoveAt(3); // 刪除索引 3 處的元素 ``` === "Go" ```go title="list_test.go" /* 清空串列 */ nums = nil /* 在尾部新增元素 */ nums = append(nums, 1) nums = append(nums, 3) nums = append(nums, 2) nums = append(nums, 5) nums = append(nums, 4) /* 在中間插入元素 */ nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // 在索引 3 處插入數字 6 /* 刪除元素 */ nums = append(nums[:3], nums[4:]...) // 刪除索引 3 處的元素 ``` === "Swift" ```swift title="list.swift" /* 清空串列 */ nums.removeAll() /* 在尾部新增元素 */ nums.append(1) nums.append(3) nums.append(2) nums.append(5) nums.append(4) /* 在中間插入元素 */ nums.insert(6, at: 3) // 在索引 3 處插入數字 6 /* 刪除元素 */ nums.remove(at: 3) // 刪除索引 3 處的元素 ``` === "JS" ```javascript title="list.js" /* 清空串列 */ nums.length = 0; /* 在尾部新增元素 */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); /* 在中間插入元素 */ nums.splice(3, 0, 6); // 在索引 3 處插入數字 6 /* 刪除元素 */ nums.splice(3, 1); // 刪除索引 3 處的元素 ``` === "TS" ```typescript title="list.ts" /* 清空串列 */ nums.length = 0; /* 在尾部新增元素 */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); /* 在中間插入元素 */ nums.splice(3, 0, 6); // 在索引 3 處插入數字 6 /* 刪除元素 */ nums.splice(3, 1); // 刪除索引 3 處的元素 ``` === "Dart" ```dart title="list.dart" /* 清空串列 */ nums.clear(); /* 在尾部新增元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); /* 在中間插入元素 */ nums.insert(3, 6); // 在索引 3 處插入數字 6 /* 刪除元素 */ nums.removeAt(3); // 刪除索引 3 處的元素 ``` === "Rust" ```rust title="list.rs" /* 清空串列 */ nums.clear(); /* 在尾部新增元素 */ nums.push(1); nums.push(3); nums.push(2); nums.push(5); nums.push(4); /* 在中間插入元素 */ nums.insert(3, 6); // 在索引 3 處插入數字 6 /* 刪除元素 */ nums.remove(3); // 刪除索引 3 處的元素 ``` === "C" ```c title="list.c" // C 未提供內建動態陣列 ``` === "Kotlin" ```kotlin title="list.kt" /* 清空串列 */ nums.clear(); /* 在尾部新增元素 */ nums.add(1); nums.add(3); nums.add(2); nums.add(5); nums.add(4); /* 在中間插入元素 */ nums.add(3, 6); // 在索引 3 處插入數字 6 /* 刪除元素 */ nums.remove(3); // 刪除索引 3 處的元素 ``` === "Ruby" ```ruby title="list.rb" # 清空串列 nums.clear # 在尾部新增元素 nums << 1 nums << 3 nums << 2 nums << 5 nums << 4 # 在中間插入元素 nums.insert(3, 6) # 在索引 3 處插入數字 6 # 刪除元素 nums.delete_at(3) # 刪除索引 3 處的元素 ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B8%85%E7%A9%BA%E4%B8%B2%E5%88%97%0A%20%20%20%20nums.clear%28%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%96%B0%E5%A2%9E%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.append%281%29%0A%20%20%20%20nums.append%283%29%0A%20%20%20%20nums.append%282%29%0A%20%20%20%20nums.append%285%29%0A%20%20%20%20nums.append%284%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%96%93%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%283%2C%206%29%20%20%23%20%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E8%99%95%E6%8F%92%E5%85%A5%E6%95%B8%E5%AD%97%206%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.pop%283%29%20%20%20%20%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E7%B4%A2%E5%BC%95%203%20%E8%99%95%E7%9A%84%E5%85%83%E7%B4%A0&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 走訪串列 與陣列一樣,串列可以根據索引走訪,也可以直接走訪各元素。 === "Python" ```python title="list.py" # 透過索引走訪串列 count = 0 for i in range(len(nums)): count += nums[i] # 直接走訪串列元素 for num in nums: count += num ``` === "C++" ```cpp title="list.cpp" /* 透過索引走訪串列 */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums[i]; } /* 直接走訪串列元素 */ count = 0; for (int num : nums) { count += num; } ``` === "Java" ```java title="list.java" /* 透過索引走訪串列 */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums.get(i); } /* 直接走訪串列元素 */ for (int num : nums) { count += num; } ``` === "C#" ```csharp title="list.cs" /* 透過索引走訪串列 */ int count = 0; for (int i = 0; i < nums.Count; i++) { count += nums[i]; } /* 直接走訪串列元素 */ count = 0; foreach (int num in nums) { count += num; } ``` === "Go" ```go title="list_test.go" /* 透過索引走訪串列 */ count := 0 for i := 0; i < len(nums); i++ { count += nums[i] } /* 直接走訪串列元素 */ count = 0 for _, num := range nums { count += num } ``` === "Swift" ```swift title="list.swift" /* 透過索引走訪串列 */ var count = 0 for i in nums.indices { count += nums[i] } /* 直接走訪串列元素 */ count = 0 for num in nums { count += num } ``` === "JS" ```javascript title="list.js" /* 透過索引走訪串列 */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* 直接走訪串列元素 */ count = 0; for (const num of nums) { count += num; } ``` === "TS" ```typescript title="list.ts" /* 透過索引走訪串列 */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } /* 直接走訪串列元素 */ count = 0; for (const num of nums) { count += num; } ``` === "Dart" ```dart title="list.dart" /* 透過索引走訪串列 */ int count = 0; for (var i = 0; i < nums.length; i++) { count += nums[i]; } /* 直接走訪串列元素 */ count = 0; for (var num in nums) { count += num; } ``` === "Rust" ```rust title="list.rs" // 透過索引走訪串列 let mut _count = 0; for i in 0..nums.len() { _count += nums[i]; } // 直接走訪串列元素 _count = 0; for num in &nums { _count += num; } ``` === "C" ```c title="list.c" // C 未提供內建動態陣列 ``` === "Kotlin" ```kotlin title="list.kt" /* 透過索引走訪串列 */ var count = 0 for (i in nums.indices) { count += nums[i] } /* 直接走訪串列元素 */ for (num in nums) { count += num } ``` === "Ruby" ```ruby title="list.rb" # 透過索引走訪串列 count = 0 for i in 0...nums.length count += nums[i] end # 直接走訪串列元素 count = 0 for num in nums count += num end ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%B2%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E7%B4%A2%E5%BC%95%E8%B5%B0%E8%A8%AA%E4%B8%B2%E5%88%97%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E8%B5%B0%E8%A8%AA%E4%B8%B2%E5%88%97%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 拼接串列 給定一個新串列 `nums1` ,我們可以將其拼接到原串列的尾部。 === "Python" ```python title="list.py" # 拼接兩個串列 nums1: list[int] = [6, 8, 7, 10, 9] nums += nums1 # 將串列 nums1 拼接到 nums 之後 ``` === "C++" ```cpp title="list.cpp" /* 拼接兩個串列 */ vector nums1 = { 6, 8, 7, 10, 9 }; // 將串列 nums1 拼接到 nums 之後 nums.insert(nums.end(), nums1.begin(), nums1.end()); ``` === "Java" ```java title="list.java" /* 拼接兩個串列 */ List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); nums.addAll(nums1); // 將串列 nums1 拼接到 nums 之後 ``` === "C#" ```csharp title="list.cs" /* 拼接兩個串列 */ List nums1 = [6, 8, 7, 10, 9]; nums.AddRange(nums1); // 將串列 nums1 拼接到 nums 之後 ``` === "Go" ```go title="list_test.go" /* 拼接兩個串列 */ nums1 := []int{6, 8, 7, 10, 9} nums = append(nums, nums1...) // 將串列 nums1 拼接到 nums 之後 ``` === "Swift" ```swift title="list.swift" /* 拼接兩個串列 */ let nums1 = [6, 8, 7, 10, 9] nums.append(contentsOf: nums1) // 將串列 nums1 拼接到 nums 之後 ``` === "JS" ```javascript title="list.js" /* 拼接兩個串列 */ const nums1 = [6, 8, 7, 10, 9]; nums.push(...nums1); // 將串列 nums1 拼接到 nums 之後 ``` === "TS" ```typescript title="list.ts" /* 拼接兩個串列 */ const nums1: number[] = [6, 8, 7, 10, 9]; nums.push(...nums1); // 將串列 nums1 拼接到 nums 之後 ``` === "Dart" ```dart title="list.dart" /* 拼接兩個串列 */ List nums1 = [6, 8, 7, 10, 9]; nums.addAll(nums1); // 將串列 nums1 拼接到 nums 之後 ``` === "Rust" ```rust title="list.rs" /* 拼接兩個串列 */ let nums1: Vec = vec![6, 8, 7, 10, 9]; nums.extend(nums1); ``` === "C" ```c title="list.c" // C 未提供內建動態陣列 ``` === "Kotlin" ```kotlin title="list.kt" /* 拼接兩個串列 */ val nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList() nums.addAll(nums1) // 將串列 nums1 拼接到 nums 之後 ``` === "Ruby" ```ruby title="list.rb" # 拼接兩個串列 nums1 = [6, 8, 7, 10, 9] nums += nums1 ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%B2%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8B%BC%E6%8E%A5%E5%85%A9%E5%80%8B%E4%B8%B2%E5%88%97%0A%20%20%20%20nums1%20%3D%20%5B6%2C%208%2C%207%2C%2010%2C%209%5D%0A%20%20%20%20nums%20%2B%3D%20nums1%20%20%23%20%E5%B0%87%E4%B8%B2%E5%88%97%20nums1%20%E6%8B%BC%E6%8E%A5%E5%88%B0%20nums%20%E4%B9%8B%E5%BE%8C&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 排序串列 完成串列排序後,我們便可以使用在陣列類別演算法題中經常考查的“二分搜尋”和“雙指標”演算法。 === "Python" ```python title="list.py" # 排序串列 nums.sort() # 排序後,串列元素從小到大排列 ``` === "C++" ```cpp title="list.cpp" /* 排序串列 */ sort(nums.begin(), nums.end()); // 排序後,串列元素從小到大排列 ``` === "Java" ```java title="list.java" /* 排序串列 */ Collections.sort(nums); // 排序後,串列元素從小到大排列 ``` === "C#" ```csharp title="list.cs" /* 排序串列 */ nums.Sort(); // 排序後,串列元素從小到大排列 ``` === "Go" ```go title="list_test.go" /* 排序串列 */ sort.Ints(nums) // 排序後,串列元素從小到大排列 ``` === "Swift" ```swift title="list.swift" /* 排序串列 */ nums.sort() // 排序後,串列元素從小到大排列 ``` === "JS" ```javascript title="list.js" /* 排序串列 */ nums.sort((a, b) => a - b); // 排序後,串列元素從小到大排列 ``` === "TS" ```typescript title="list.ts" /* 排序串列 */ nums.sort((a, b) => a - b); // 排序後,串列元素從小到大排列 ``` === "Dart" ```dart title="list.dart" /* 排序串列 */ nums.sort(); // 排序後,串列元素從小到大排列 ``` === "Rust" ```rust title="list.rs" /* 排序串列 */ nums.sort(); // 排序後,串列元素從小到大排列 ``` === "C" ```c title="list.c" // C 未提供內建動態陣列 ``` === "Kotlin" ```kotlin title="list.kt" /* 排序串列 */ nums.sort() // 排序後,串列元素從小到大排列 ``` === "Ruby" ```ruby title="list.rb" # 排序串列 nums = nums.sort { |a, b| a <=> b } # 排序後,串列元素從小到大排列 ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%B2%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8E%92%E5%BA%8F%E4%B8%B2%E5%88%97%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E6%8E%92%E5%BA%8F%E5%BE%8C%EF%BC%8C%E4%B8%B2%E5%88%97%E5%85%83%E7%B4%A0%E5%BE%9E%E5%B0%8F%E5%88%B0%E5%A4%A7%E6%8E%92%E5%88%97&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## 串列實現 許多程式語言內建了串列,例如 Java、C++、Python 等。它們的實現比較複雜,各個參數的設定也非常考究,例如初始容量、擴容倍數等。感興趣的讀者可以查閱原始碼進行學習。 為了加深對串列工作原理的理解,我們嘗試實現一個簡易版串列,包括以下三個重點設計。 - **初始容量**:選取一個合理的陣列初始容量。在本示例中,我們選擇 10 作為初始容量。 - **數量記錄**:宣告一個變數 `size` ,用於記錄串列當前元素數量,並隨著元素插入和刪除即時更新。根據此變數,我們可以定位串列尾部,以及判斷是否需要擴容。 - **擴容機制**:若插入元素時串列容量已滿,則需要進行擴容。先根據擴容倍數建立一個更大的陣列,再將當前陣列的所有元素依次移動至新陣列。在本示例中,我們規定每次將陣列擴容至之前的 2 倍。 ```src [file]{my_list}-[class]{my_list}-[func]{} ``` ================================================ FILE: zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.md ================================================ # 記憶體與快取 * 在本章的前兩節中,我們探討了陣列和鏈結串列這兩種基礎且重要的資料結構,它們分別代表了“連續儲存”和“分散儲存”兩種物理結構。 實際上,**物理結構在很大程度上決定了程式對記憶體和快取的使用效率**,進而影響演算法程式的整體效能。 ## 計算機儲存裝置 計算機中包括三種類型的儲存裝置:硬碟(hard disk)記憶體(random-access memory, RAM)快取(cache memory)。下表展示了它們在計算機系統中的不同角色和效能特點。

  計算機的儲存裝置

| | 硬碟 | 記憶體 | 快取 | | -------------- | ---------------------------------------- | -------------------------------------- | ------------------------------------------------- | | 用途 | 長期儲存資料,包括作業系統、程式、檔案等 | 臨時儲存當前執行的程式和正在處理的資料 | 儲存經常訪問的資料和指令,減少 CPU 訪問記憶體的次數 | | 易失性 | 斷電後資料不會丟失 | 斷電後資料會丟失 | 斷電後資料會丟失 | | 容量 | 較大,TB 級別 | 較小,GB 級別 | 非常小,MB 級別 | | 速度 | 較慢,幾百到幾千 MB/s | 較快,幾十 GB/s | 非常快,幾十到幾百 GB/s | | 價格(人民幣) | 較便宜,幾毛到幾元 / GB | 較貴,幾十到幾百元 / GB | 非常貴,隨 CPU 打包計價 | 我們可以將計算機儲存系統想象為下圖所示的金字塔結構。越靠近金字塔頂端的儲存裝置的速度越快、容量越小、成本越高。這種多層級的設計並非偶然,而是計算機科學家和工程師們經過深思熟慮的結果。 - **硬碟難以被記憶體取代**。首先,記憶體中的資料在斷電後會丟失,因此它不適合長期儲存資料;其次,記憶體的成本是硬碟的幾十倍,這使得它難以在消費者市場普及。 - **快取的大容量和高速度難以兼得**。隨著 L1、L2、L3 快取的容量逐步增大,其物理尺寸會變大,與 CPU 核心之間的物理距離會變遠,從而導致資料傳輸時間增加,元素訪問延遲變高。在當前技術下,多層級的快取結構是容量、速度和成本之間的最佳平衡點。 ![計算機儲存系統](ram_and_cache.assets/storage_pyramid.png) !!! tip 計算機的儲存層次結構體現了速度、容量和成本三者之間的精妙平衡。實際上,這種權衡普遍存在於所有工業領域,它要求我們在不同的優勢和限制之間找到最佳平衡點。 總的來說,**硬碟用於長期儲存大量資料,記憶體用於臨時儲存程式執行中正在處理的資料,而快取則用於儲存經常訪問的資料和指令**,以提高程式執行效率。三者共同協作,確保計算機系統高效執行。 如下圖所示,在程式執行時,資料會從硬碟中被讀取到記憶體中,供 CPU 計算使用。快取可以看作 CPU 的一部分,**它透過智慧地從記憶體載入資料**,給 CPU 提供高速的資料讀取,從而顯著提升程式的執行效率,減少對較慢的記憶體的依賴。 ![硬碟、記憶體和快取之間的資料流通](ram_and_cache.assets/computer_storage_devices.png) ## 資料結構的記憶體效率 在記憶體空間利用方面,陣列和鏈結串列各自具有優勢和侷限性。 一方面,**記憶體是有限的,且同一塊記憶體不能被多個程式共享**,因此我們希望資料結構能夠儘可能高效地利用空間。陣列的元素緊密排列,不需要額外的空間來儲存鏈結串列節點間的引用(指標),因此空間效率更高。然而,陣列需要一次性分配足夠的連續記憶體空間,這可能導致記憶體浪費,陣列擴容也需要額外的時間和空間成本。相比之下,鏈結串列以“節點”為單位進行動態記憶體分配和回收,提供了更大的靈活性。 另一方面,在程式執行時,**隨著反覆申請與釋放記憶體,空閒記憶體的碎片化程度會越來越高**,從而導致記憶體的利用效率降低。陣列由於其連續的儲存方式,相對不容易導致記憶體碎片化。相反,鏈結串列的元素是分散儲存的,在頻繁的插入與刪除操作中,更容易導致記憶體碎片化。 ## 資料結構的快取效率 快取雖然在空間容量上遠小於記憶體,但它比記憶體快得多,在程式執行速度上起著至關重要的作用。由於快取的容量有限,只能儲存一小部分頻繁訪問的資料,因此當 CPU 嘗試訪問的資料不在快取中時,就會發生快取未命中(cache miss),此時 CPU 不得不從速度較慢的記憶體中載入所需資料。 顯然,**“快取未命中”越少,CPU 讀寫資料的效率就越高**,程式效能也就越好。我們將 CPU 從快取中成功獲取資料的比例稱為快取命中率(cache hit rate),這個指標通常用來衡量快取效率。 為了儘可能達到更高的效率,快取會採取以下資料載入機制。 - **快取行**:快取不是單個位元組地儲存與載入資料,而是以快取行為單位。相比於單個位元組的傳輸,快取行的傳輸形式更加高效。 - **預取機制**:處理器會嘗試預測資料訪問模式(例如順序訪問、固定步長跳躍訪問等),並根據特定模式將資料載入至快取之中,從而提升命中率。 - **空間區域性**:如果一個數據被訪問,那麼它附近的資料可能近期也會被訪問。因此,快取在載入某一資料時,也會載入其附近的資料,以提高命中率。 - **時間區域性**:如果一個數據被訪問,那麼它在不久的將來很可能再次被訪問。快取利用這一原理,透過保留最近訪問過的資料來提高命中率。 實際上,**陣列和鏈結串列對快取的利用效率是不同的**,主要體現在以下幾個方面。 - **佔用空間**:鏈結串列元素比陣列元素佔用空間更多,導致快取中容納的有效資料量更少。 - **快取行**:鏈結串列資料分散在記憶體各處,而快取是“按行載入”的,因此載入到無效資料的比例更高。 - **預取機制**:陣列比鏈結串列的資料訪問模式更具“可預測性”,即系統更容易猜出即將被載入的資料。 - **空間區域性**:陣列被儲存在集中的記憶體空間中,因此被載入資料附近的資料更有可能即將被訪問。 總體而言,**陣列具有更高的快取命中率,因此它在操作效率上通常優於鏈結串列**。這使得在解決演算法問題時,基於陣列實現的資料結構往往更受歡迎。 需要注意的是,**高快取效率並不意味著陣列在所有情況下都優於鏈結串列**。實際應用中選擇哪種資料結構,應根據具體需求來決定。例如,陣列和鏈結串列都可以實現“堆疊”資料結構(下一章會詳細介紹),但它們適用於不同場景。 - 在做演算法題時,我們會傾向於選擇基於陣列實現的堆疊,因為它提供了更高的操作效率和隨機訪問的能力,代價僅是需要預先為陣列分配一定的記憶體空間。 - 如果資料量非常大、動態性很高、堆疊的預期大小難以估計,那麼基於鏈結串列實現的堆疊更加合適。鏈結串列能夠將大量資料分散儲存於記憶體的不同部分,並且避免了陣列擴容產生的額外開銷。 ================================================ FILE: zh-hant/docs/chapter_array_and_linkedlist/summary.md ================================================ # 小結 ### 重點回顧 - 陣列和鏈結串列是兩種基本的資料結構,分別代表資料在計算機記憶體中的兩種儲存方式:連續空間儲存和分散空間儲存。兩者的特點呈現出互補的特性。 - 陣列支持隨機訪問、佔用記憶體較少;但插入和刪除元素效率低,且初始化後長度不可變。 - 鏈結串列透過更改引用(指標)實現高效的節點插入與刪除,且可以靈活調整長度;但節點訪問效率低、佔用記憶體較多。常見的鏈結串列型別包括單向鏈結串列、環形鏈結串列、雙向鏈結串列。 - 串列是一種支持增刪查改的元素有序集合,通常基於動態陣列實現。它保留了陣列的優勢,同時可以靈活調整長度。 - 串列的出現大幅提高了陣列的實用性,但可能導致部分記憶體空間浪費。 - 程式執行時,資料主要儲存在記憶體中。陣列可提供更高的記憶體空間效率,而鏈結串列則在記憶體使用上更加靈活。 - 快取透過快取行、預取機制以及空間區域性和時間區域性等資料載入機制,為 CPU 提供快速資料訪問,顯著提升程式的執行效率。 - 由於陣列具有更高的快取命中率,因此它通常比鏈結串列更高效。在選擇資料結構時,應根據具體需求和場景做出恰當選擇。 ### Q & A **Q**:陣列儲存在堆疊上和儲存在堆積上,對時間效率和空間效率是否有影響? 儲存在堆疊上和堆積上的陣列都被儲存在連續記憶體空間內,資料操作效率基本一致。然而,堆疊和堆積具有各自的特點,從而導致以下不同點。 1. 分配和釋放效率:堆疊是一塊較小的記憶體,分配由編譯器自動完成;而堆積記憶體相對更大,可以在程式碼中動態分配,更容易碎片化。因此,堆積上的分配和釋放操作通常比堆疊上的慢。 2. 大小限制:堆疊記憶體相對較小,堆積的大小一般受限於可用記憶體。因此堆積更加適合儲存大型陣列。 3. 靈活性:堆疊上的陣列的大小需要在編譯時確定,而堆積上的陣列的大小可以在執行時動態確定。 **Q**:為什麼陣列要求相同型別的元素,而在鏈結串列中卻沒有強調相同型別呢? 鏈結串列由節點組成,節點之間透過引用(指標)連線,各個節點可以儲存不同型別的資料,例如 `int`、`double`、`string`、`object` 等。 相對地,陣列元素則必須是相同型別的,這樣才能透過計算偏移量來獲取對應元素位置。例如,陣列同時包含 `int` 和 `long` 兩種型別,單個元素分別佔用 4 位元組和 8 位元組 ,此時就不能用以下公式計算偏移量了,因為陣列中包含了兩種“元素長度”。 ```shell # 元素記憶體位址 = 陣列記憶體位址(首元素記憶體位址) + 元素長度 * 元素索引 ``` **Q**:刪除節點 `P` 後,是否需要把 `P.next` 設為 `None` 呢? 不修改 `P.next` 也可以。從該鏈結串列的角度看,從頭節點走訪到尾節點已經不會遇到 `P` 了。這意味著節點 `P` 已經從鏈結串列中刪除了,此時節點 `P` 指向哪裡都不會對該鏈結串列產生影響。 從資料結構與演算法(做題)的角度看,不斷開沒有關係,只要保證程式的邏輯是正確的就行。從標準庫的角度看,斷開更加安全、邏輯更加清晰。如果不斷開,假設被刪除節點未被正常回收,那麼它會影響後繼節點的記憶體回收。 **Q**:在鏈結串列中插入和刪除操作的時間複雜度是 $O(1)$ 。但是增刪之前都需要 $O(n)$ 的時間查詢元素,那為什麼時間複雜度不是 $O(n)$ 呢? 如果是先查詢元素、再刪除元素,時間複雜度確實是 $O(n)$ 。然而,鏈結串列的 $O(1)$ 增刪的優勢可以在其他應用上得到體現。例如,雙向佇列適合使用鏈結串列實現,我們維護一個指標變數始終指向頭節點、尾節點,每次插入與刪除操作都是 $O(1)$ 。 **Q**:圖“鏈結串列定義與儲存方式”中,淺藍色的儲存節點指標是佔用一塊記憶體位址嗎?還是和節點值各佔一半呢? 該示意圖只是定性表示,定量表示需要根據具體情況進行分析。 - 不同型別的節點值佔用的空間是不同的,比如 `int`、`long`、`double` 和例項物件等。 - 指標變數佔用的記憶體空間大小根據所使用的作業系統及編譯環境而定,大多為 8 位元組或 4 位元組。 **Q**:在串列末尾新增元素是否時時刻刻都為 $O(1)$ ? 如果新增元素時超出串列長度,則需要先擴容串列再新增。系統會申請一塊新的記憶體,並將原串列的所有元素搬運過去,這時候時間複雜度就會是 $O(n)$ 。 **Q**:“串列的出現極大地提高了陣列的實用性,但可能導致部分記憶體空間浪費”,這裡的空間浪費是指額外增加的變數如容量、長度、擴容倍數所佔的記憶體嗎? 這裡的空間浪費主要有兩方面含義:一方面,串列都會設定一個初始長度,我們不一定需要用這麼多;另一方面,為了防止頻繁擴容,擴容一般會乘以一個係數,比如 $\times 1.5$ 。這樣一來,也會出現很多空位,我們通常不能完全填滿它們。 **Q**:在 Python 中初始化 `n = [1, 2, 3]` 後,這 3 個元素的位址是相連的,但是初始化 `m = [2, 1, 3]` 會發現它們每個元素的 id 並不是連續的,而是分別跟 `n` 中的相同。這些元素的位址不連續,那麼 `m` 還是陣列嗎? 假如把串列元素換成鏈結串列節點 `n = [n1, n2, n3, n4, n5]` ,通常情況下這 5 個節點物件也分散儲存在記憶體各處。然而,給定一個串列索引,我們仍然可以在 $O(1)$ 時間內獲取節點記憶體位址,從而訪問到對應的節點。這是因為陣列中儲存的是節點的引用,而非節點本身。 與許多語言不同,Python 中的數字也被包裝為物件,串列中儲存的不是數字本身,而是對數字的引用。因此,我們會發現兩個陣列中的相同數字擁有同一個 id ,並且這些數字的記憶體位址無須連續。 **Q**:C++ STL 裡面的 `std::list` 已經實現了雙向鏈結串列,但好像一些演算法書上不怎麼直接使用它,是不是因為有什麼侷限性呢? 一方面,我們往往更青睞使用陣列實現演算法,而只在必要時才使用鏈結串列,主要有兩個原因。 - 空間開銷:由於每個元素需要兩個額外的指標(一個用於前一個元素,一個用於後一個元素),所以 `std::list` 通常比 `std::vector` 更佔用空間。 - 快取不友好:由於資料不是連續存放的,因此 `std::list` 對快取的利用率較低。一般情況下,`std::vector` 的效能會更好。 另一方面,必要使用鏈結串列的情況主要是二元樹和圖。堆疊和佇列往往會使用程式語言提供的 `stack` 和 `queue` ,而非鏈結串列。 **Q**:操作 `res = [[0]] * n` 生成了一個二維串列,其中每一個 `[0]` 都是獨立的嗎? 不是獨立的。此二維串列中,所有的 `[0]` 實際上是同一個物件的引用。如果我們修改其中一個元素,會發現所有的對應元素都會隨之改變。 如果希望二維串列中的每個 `[0]` 都是獨立的,可以使用 `res = [[0] for _ in range(n)]` 來實現。這種方式的原理是初始化了 $n$ 個獨立的 `[0]` 串列物件。 **Q**:操作 `res = [0] * n` 生成了一個串列,其中每一個整數 0 都是獨立的嗎? 在該串列中,所有整數 0 都是同一個物件的引用。這是因為 Python 對小整數(通常是 -5 到 256)採用了快取池機制,以便最大化物件複用,從而提升效能。 雖然它們指向同一個物件,但我們仍然可以獨立修改串列中的每個元素,這是因為 Python 的整數是“不可變物件”。當我們修改某個元素時,實際上是切換為另一個物件的引用,而不是改變原有物件本身。 然而,當串列元素是“可變物件”時(例如串列、字典或類別例項等),修改某個元素會直接改變該物件本身,所有引用該物件的元素都會產生相同變化。 ================================================ FILE: zh-hant/docs/chapter_backtracking/backtracking_algorithm.md ================================================ # 回溯演算法 回溯演算法(backtracking algorithm)是一種透過窮舉來解決問題的方法,它的核心思想是從一個初始狀態出發,暴力搜尋所有可能的解決方案,當遇到正確的解則將其記錄,直到找到解或者嘗試了所有可能的選擇都無法找到解為止。 回溯演算法通常採用“深度優先搜尋”來走訪解空間。在“二元樹”章節中,我們提到前序、中序和後序走訪都屬於深度優先搜尋。接下來,我們利用前序走訪構造一個回溯問題,逐步瞭解回溯演算法的工作原理。 !!! question "例題一" 給定一棵二元樹,搜尋並記錄所有值為 $7$ 的節點,請返回節點串列。 對於此題,我們前序走訪這棵樹,並判斷當前節點的值是否為 $7$ ,若是,則將該節點的值加入結果串列 `res` 之中。相關過程實現如下圖和以下程式碼所示: ```src [file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order} ``` ![在前序走訪中搜索節點](backtracking_algorithm.assets/preorder_find_nodes.png) ## 嘗試與回退 **之所以稱之為回溯演算法,是因為該演算法在搜尋解空間時會採用“嘗試”與“回退”的策略**。當演算法在搜尋過程中遇到某個狀態無法繼續前進或無法得到滿足條件的解時,它會撤銷上一步的選擇,退回到之前的狀態,並嘗試其他可能的選擇。 對於例題一,訪問每個節點都代表一次“嘗試”,而越過葉節點或返回父節點的 `return` 則表示“回退”。 值得說明的是,**回退並不僅僅包括函式返回**。為解釋這一點,我們對例題一稍作拓展。 !!! question "例題二" 在二元樹中搜索所有值為 $7$ 的節點,**請返回根節點到這些節點的路徑**。 在例題一程式碼的基礎上,我們需要藉助一個串列 `path` 記錄訪問過的節點路徑。當訪問到值為 $7$ 的節點時,則複製 `path` 並新增進結果串列 `res` 。走訪完成後,`res` 中儲存的就是所有的解。程式碼如下所示: ```src [file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order} ``` 在每次“嘗試”中,我們透過將當前節點新增進 `path` 來記錄路徑;而在“回退”前,我們需要將該節點從 `path` 中彈出,**以恢復本次嘗試之前的狀態**。 觀察下圖所示的過程,**我們可以將嘗試和回退理解為“前進”與“撤銷”**,兩個操作互為逆向。 === "<1>" ![嘗試與回退](backtracking_algorithm.assets/preorder_find_paths_step1.png) === "<2>" ![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png) === "<3>" ![preorder_find_paths_step3](backtracking_algorithm.assets/preorder_find_paths_step3.png) === "<4>" ![preorder_find_paths_step4](backtracking_algorithm.assets/preorder_find_paths_step4.png) === "<5>" ![preorder_find_paths_step5](backtracking_algorithm.assets/preorder_find_paths_step5.png) === "<6>" ![preorder_find_paths_step6](backtracking_algorithm.assets/preorder_find_paths_step6.png) === "<7>" ![preorder_find_paths_step7](backtracking_algorithm.assets/preorder_find_paths_step7.png) === "<8>" ![preorder_find_paths_step8](backtracking_algorithm.assets/preorder_find_paths_step8.png) === "<9>" ![preorder_find_paths_step9](backtracking_algorithm.assets/preorder_find_paths_step9.png) === "<10>" ![preorder_find_paths_step10](backtracking_algorithm.assets/preorder_find_paths_step10.png) === "<11>" ![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png) ## 剪枝 複雜的回溯問題通常包含一個或多個約束條件,**約束條件通常可用於“剪枝”**。 !!! question "例題三" 在二元樹中搜索所有值為 $7$ 的節點,請返回根節點到這些節點的路徑,**並要求路徑中不包含值為 $3$ 的節點**。 為了滿足以上約束條件,**我們需要新增剪枝操作**:在搜尋過程中,若遇到值為 $3$ 的節點,則提前返回,不再繼續搜尋。程式碼如下所示: ```src [file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order} ``` “剪枝”是一個非常形象的名詞。如下圖所示,在搜尋過程中,**我們“剪掉”了不滿足約束條件的搜尋分支**,避免許多無意義的嘗試,從而提高了搜尋效率。 ![根據約束條件剪枝](backtracking_algorithm.assets/preorder_find_constrained_paths.png) ## 框架程式碼 接下來,我們嘗試將回溯的“嘗試、回退、剪枝”的主體框架提煉出來,提升程式碼的通用性。 在以下框架程式碼中,`state` 表示問題的當前狀態,`choices` 表示當前狀態下可以做出的選擇: === "Python" ```python title="" def backtrack(state: State, choices: list[choice], res: list[state]): """回溯演算法框架""" # 判斷是否為解 if is_solution(state): # 記錄解 record_solution(state, res) # 不再繼續搜尋 return # 走訪所有選擇 for choice in choices: # 剪枝:判斷選擇是否合法 if is_valid(state, choice): # 嘗試:做出選擇,更新狀態 make_choice(state, choice) backtrack(state, choices, res) # 回退:撤銷選擇,恢復到之前的狀態 undo_choice(state, choice) ``` === "C++" ```cpp title="" /* 回溯演算法框架 */ void backtrack(State *state, vector &choices, vector &res) { // 判斷是否為解 if (isSolution(state)) { // 記錄解 recordSolution(state, res); // 不再繼續搜尋 return; } // 走訪所有選擇 for (Choice choice : choices) { // 剪枝:判斷選擇是否合法 if (isValid(state, choice)) { // 嘗試:做出選擇,更新狀態 makeChoice(state, choice); backtrack(state, choices, res); // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state, choice); } } } ``` === "Java" ```java title="" /* 回溯演算法框架 */ void backtrack(State state, List choices, List res) { // 判斷是否為解 if (isSolution(state)) { // 記錄解 recordSolution(state, res); // 不再繼續搜尋 return; } // 走訪所有選擇 for (Choice choice : choices) { // 剪枝:判斷選擇是否合法 if (isValid(state, choice)) { // 嘗試:做出選擇,更新狀態 makeChoice(state, choice); backtrack(state, choices, res); // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state, choice); } } } ``` === "C#" ```csharp title="" /* 回溯演算法框架 */ void Backtrack(State state, List choices, List res) { // 判斷是否為解 if (IsSolution(state)) { // 記錄解 RecordSolution(state, res); // 不再繼續搜尋 return; } // 走訪所有選擇 foreach (Choice choice in choices) { // 剪枝:判斷選擇是否合法 if (IsValid(state, choice)) { // 嘗試:做出選擇,更新狀態 MakeChoice(state, choice); Backtrack(state, choices, res); // 回退:撤銷選擇,恢復到之前的狀態 UndoChoice(state, choice); } } } ``` === "Go" ```go title="" /* 回溯演算法框架 */ func backtrack(state *State, choices []Choice, res *[]State) { // 判斷是否為解 if isSolution(state) { // 記錄解 recordSolution(state, res) // 不再繼續搜尋 return } // 走訪所有選擇 for _, choice := range choices { // 剪枝:判斷選擇是否合法 if isValid(state, choice) { // 嘗試:做出選擇,更新狀態 makeChoice(state, choice) backtrack(state, choices, res) // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state, choice) } } } ``` === "Swift" ```swift title="" /* 回溯演算法框架 */ func backtrack(state: inout State, choices: [Choice], res: inout [State]) { // 判斷是否為解 if isSolution(state: state) { // 記錄解 recordSolution(state: state, res: &res) // 不再繼續搜尋 return } // 走訪所有選擇 for choice in choices { // 剪枝:判斷選擇是否合法 if isValid(state: state, choice: choice) { // 嘗試:做出選擇,更新狀態 makeChoice(state: &state, choice: choice) backtrack(state: &state, choices: choices, res: &res) // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state: &state, choice: choice) } } } ``` === "JS" ```javascript title="" /* 回溯演算法框架 */ function backtrack(state, choices, res) { // 判斷是否為解 if (isSolution(state)) { // 記錄解 recordSolution(state, res); // 不再繼續搜尋 return; } // 走訪所有選擇 for (let choice of choices) { // 剪枝:判斷選擇是否合法 if (isValid(state, choice)) { // 嘗試:做出選擇,更新狀態 makeChoice(state, choice); backtrack(state, choices, res); // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state, choice); } } } ``` === "TS" ```typescript title="" /* 回溯演算法框架 */ function backtrack(state: State, choices: Choice[], res: State[]): void { // 判斷是否為解 if (isSolution(state)) { // 記錄解 recordSolution(state, res); // 不再繼續搜尋 return; } // 走訪所有選擇 for (let choice of choices) { // 剪枝:判斷選擇是否合法 if (isValid(state, choice)) { // 嘗試:做出選擇,更新狀態 makeChoice(state, choice); backtrack(state, choices, res); // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state, choice); } } } ``` === "Dart" ```dart title="" /* 回溯演算法框架 */ void backtrack(State state, List, List res) { // 判斷是否為解 if (isSolution(state)) { // 記錄解 recordSolution(state, res); // 不再繼續搜尋 return; } // 走訪所有選擇 for (Choice choice in choices) { // 剪枝:判斷選擇是否合法 if (isValid(state, choice)) { // 嘗試:做出選擇,更新狀態 makeChoice(state, choice); backtrack(state, choices, res); // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state, choice); } } } ``` === "Rust" ```rust title="" /* 回溯演算法框架 */ fn backtrack(state: &mut State, choices: &Vec, res: &mut Vec) { // 判斷是否為解 if is_solution(state) { // 記錄解 record_solution(state, res); // 不再繼續搜尋 return; } // 走訪所有選擇 for choice in choices { // 剪枝:判斷選擇是否合法 if is_valid(state, choice) { // 嘗試:做出選擇,更新狀態 make_choice(state, choice); backtrack(state, choices, res); // 回退:撤銷選擇,恢復到之前的狀態 undo_choice(state, choice); } } } ``` === "C" ```c title="" /* 回溯演算法框架 */ void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) { // 判斷是否為解 if (isSolution(state)) { // 記錄解 recordSolution(state, res, numRes); // 不再繼續搜尋 return; } // 走訪所有選擇 for (int i = 0; i < numChoices; i++) { // 剪枝:判斷選擇是否合法 if (isValid(state, &choices[i])) { // 嘗試:做出選擇,更新狀態 makeChoice(state, &choices[i]); backtrack(state, choices, numChoices, res, numRes); // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state, &choices[i]); } } } ``` === "Kotlin" ```kotlin title="" /* 回溯演算法框架 */ fun backtrack(state: State?, choices: List, res: List?) { // 判斷是否為解 if (isSolution(state)) { // 記錄解 recordSolution(state, res) // 不再繼續搜尋 return } // 走訪所有選擇 for (choice in choices) { // 剪枝:判斷選擇是否合法 if (isValid(state, choice)) { // 嘗試:做出選擇,更新狀態 makeChoice(state, choice) backtrack(state, choices, res) // 回退:撤銷選擇,恢復到之前的狀態 undoChoice(state, choice) } } } ``` === "Ruby" ```ruby title="" ### 回溯演算法框架 ### def backtrack(state, choices, res) # 判斷是否為解 if is_solution?(state) # 記錄解 record_solution(state, res) return end # 走訪所有選擇 for choice in choices # 剪枝:判斷選擇是否合法 if is_valid?(state, choice) # 嘗試:做出選擇,更新狀態 make_choice(state, choice) backtrack(state, choices, res) # 回退:撤銷選擇,恢復到之前的狀態 undo_choice(state, choice) end end end ``` 接下來,我們基於框架程式碼來解決例題三。狀態 `state` 為節點走訪路徑,選擇 `choices` 為當前節點的左子節點和右子節點,結果 `res` 是路徑串列: ```src [file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack} ``` 根據題意,我們在找到值為 $7$ 的節點後應該繼續搜尋,**因此需要將記錄解之後的 `return` 語句刪除**。下圖對比了保留或刪除 `return` 語句的搜尋過程。 ![保留與刪除 return 的搜尋過程對比](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) 相比基於前序走訪的程式碼實現,基於回溯演算法框架的程式碼實現雖然顯得囉唆,但通用性更好。實際上,**許多回溯問題可以在該框架下解決**。我們只需根據具體問題來定義 `state` 和 `choices` ,並實現框架中的各個方法即可。 ## 常用術語 為了更清晰地分析演算法問題,我們總結一下回溯演算法中常用術語的含義,並對照例題三給出對應示例,如下表所示。

  常見的回溯演算法術語

| 名詞 | 定義 | 例題三 | | ---------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- | | 解(solution) | 解是滿足問題特定條件的答案,可能有一個或多個 | 根節點到節點 $7$ 的滿足約束條件的所有路徑 | | 約束條件(constraint) | 約束條件是問題中限制解的可行性的條件,通常用於剪枝 | 路徑中不包含節點 $3$ | | 狀態(state) | 狀態表示問題在某一時刻的情況,包括已經做出的選擇 | 當前已訪問的節點路徑,即 `path` 節點串列 | | 嘗試(attempt) | 嘗試是根據可用選擇來探索解空間的過程,包括做出選擇,更新狀態,檢查是否為解 | 遞迴訪問左(右)子節點,將節點新增進 `path` ,判斷節點的值是否為 $7$ | | 回退(backtracking) | 回退指遇到不滿足約束條件的狀態時,撤銷前面做出的選擇,回到上一個狀態 | 當越過葉節點、結束節點訪問、遇到值為 $3$ 的節點時終止搜尋,函式返回 | | 剪枝(pruning) | 剪枝是根據問題特性和約束條件避免無意義的搜尋路徑的方法,可提高搜尋效率 | 當遇到值為 $3$ 的節點時,則不再繼續搜尋 | !!! tip 問題、解、狀態等概念是通用的,在分治、回溯、動態規劃、貪婪等演算法中都有涉及。 ## 優點與侷限性 回溯演算法本質上是一種深度優先搜尋演算法,它嘗試所有可能的解決方案直到找到滿足條件的解。這種方法的優點在於能夠找到所有可能的解決方案,而且在合理的剪枝操作下,具有很高的效率。 然而,在處理大規模或者複雜問題時,**回溯演算法的執行效率可能難以接受**。 - **時間**:回溯演算法通常需要走訪狀態空間的所有可能,時間複雜度可以達到指數階或階乘階。 - **空間**:在遞迴呼叫中需要儲存當前的狀態(例如路徑、用於剪枝的輔助變數等),當深度很大時,空間需求可能會變得很大。 即便如此,**回溯演算法仍然是某些搜尋問題和約束滿足問題的最佳解決方案**。對於這些問題,由於無法預測哪些選擇可生成有效的解,因此我們必須對所有可能的選擇進行走訪。在這種情況下,**關鍵是如何最佳化效率**,常見的效率最佳化方法有兩種。 - **剪枝**:避免搜尋那些肯定不會產生解的路徑,從而節省時間和空間。 - **啟發式搜尋**:在搜尋過程中引入一些策略或者估計值,從而優先搜尋最有可能產生有效解的路徑。 ## 回溯典型例題 回溯演算法可用於解決許多搜尋問題、約束滿足問題和組合最佳化問題。 **搜尋問題**:這類問題的目標是找到滿足特定條件的解決方案。 - 全排列問題:給定一個集合,求出其所有可能的排列組合。 - 子集和問題:給定一個集合和一個目標和,找到集合中所有和為目標和的子集。 - 河內塔問題:給定三根柱子和一系列大小不同的圓盤,要求將所有圓盤從一根柱子移動到另一根柱子,每次只能移動一個圓盤,且不能將大圓盤放在小圓盤上。 **約束滿足問題**:這類問題的目標是找到滿足所有約束條件的解。 - $n$ 皇后:在 $n \times n$ 的棋盤上放置 $n$ 個皇后,使得它們互不攻擊。 - 數獨:在 $9 \times 9$ 的網格中填入數字 $1$ ~ $9$ ,使得每行、每列和每個 $3 \times 3$ 子網格中的數字不重複。 - 圖著色問題:給定一個無向圖,用最少的顏色給圖的每個頂點著色,使得相鄰頂點顏色不同。 **組合最佳化問題**:這類問題的目標是在一個組合空間中找到滿足某些條件的最優解。 - 0-1 背包問題:給定一組物品和一個背包,每個物品有一定的價值和重量,要求在背包容量限制內,選擇物品使得總價值最大。 - 旅行商問題:在一個圖中,從一個點出發,訪問所有其他點恰好一次後返回起點,求最短路徑。 - 最大團問題:給定一個無向圖,找到最大的完全子圖,即子圖中的任意兩個頂點之間都有邊相連。 請注意,對於許多組合最佳化問題,回溯不是最優解決方案。 - 0-1 背包問題通常使用動態規劃解決,以達到更高的時間效率。 - 旅行商是一個著名的 NP-Hard 問題,常用解法有遺傳演算法和蟻群演算法等。 - 最大團問題是圖論中的一個經典問題,可用貪婪演算法等啟發式演算法來解決。 ================================================ FILE: zh-hant/docs/chapter_backtracking/index.md ================================================ # 回溯 ![回溯](../assets/covers/chapter_backtracking.jpg) !!! abstract 我們如同迷宮中的探索者,在前進的道路上可能會遇到困難。 回溯的力量讓我們能夠重新開始,不斷嘗試,最終找到通往光明的出口。 ================================================ FILE: zh-hant/docs/chapter_backtracking/n_queens_problem.md ================================================ # n 皇后問題 !!! question 根據國際象棋的規則,皇后可以攻擊與同處一行、一列或一條斜線上的棋子。給定 $n$ 個皇后和一個 $n \times n$ 大小的棋盤,尋找使得所有皇后之間無法相互攻擊的擺放方案。 如下圖所示,當 $n = 4$ 時,共可以找到兩個解。從回溯演算法的角度看,$n \times n$ 大小的棋盤共有 $n^2$ 個格子,給出了所有的選擇 `choices` 。在逐個放置皇后的過程中,棋盤狀態在不斷地變化,每個時刻的棋盤就是狀態 `state` 。 ![4 皇后問題的解](n_queens_problem.assets/solution_4_queens.png) 下圖展示了本題的三個約束條件:**多個皇后不能在同一行、同一列、同一條對角線上**。值得注意的是,對角線分為主對角線 `\` 和次對角線 `/` 兩種。 ![n 皇后問題的約束條件](n_queens_problem.assets/n_queens_constraints.png) ### 逐行放置策略 皇后的數量和棋盤的行數都為 $n$ ,因此我們容易得到一個推論:**棋盤每行都允許且只允許放置一個皇后**。 也就是說,我們可以採取逐行放置策略:從第一行開始,在每行放置一個皇后,直至最後一行結束。 下圖所示為 4 皇后問題的逐行放置過程。受畫幅限制,下圖僅展開了第一行的其中一個搜尋分支,並且將不滿足列約束和對角線約束的方案都進行了剪枝。 ![逐行放置策略](n_queens_problem.assets/n_queens_placing.png) 從本質上看,**逐行放置策略起到了剪枝的作用**,它避免了同一行出現多個皇后的所有搜尋分支。 ### 列與對角線剪枝 為了滿足列約束,我們可以利用一個長度為 $n$ 的布林型陣列 `cols` 記錄每一列是否有皇后。在每次決定放置前,我們透過 `cols` 將已有皇后的列進行剪枝,並在回溯中動態更新 `cols` 的狀態。 !!! tip 請注意,矩陣的起點位於左上角,其中行索引從上到下增加,列索引從左到右增加。 那麼,如何處理對角線約束呢?設棋盤中某個格子的行列索引為 $(row, col)$ ,選定矩陣中的某條主對角線,我們發現該對角線上所有格子的行索引減列索引都相等,**即主對角線上所有格子的 $row - col$ 為恆定值**。 也就是說,如果兩個格子滿足 $row_1 - col_1 = row_2 - col_2$ ,則它們一定處在同一條主對角線上。利用該規律,我們可以藉助下圖所示的陣列 `diags1` 記錄每條主對角線上是否有皇后。 同理,**次對角線上的所有格子的 $row + col$ 是恆定值**。我們同樣也可以藉助陣列 `diags2` 來處理次對角線約束。 ![處理列約束和對角線約束](n_queens_problem.assets/n_queens_cols_diagonals.png) ### 程式碼實現 請注意,$n$ 維方陣中 $row - col$ 的範圍是 $[-n + 1, n - 1]$ ,$row + col$ 的範圍是 $[0, 2n - 2]$ ,所以主對角線和次對角線的數量都為 $2n - 1$ ,即陣列 `diags1` 和 `diags2` 的長度都為 $2n - 1$ 。 ```src [file]{n_queens}-[class]{}-[func]{n_queens} ``` 逐行放置 $n$ 次,考慮列約束,則從第一行到最後一行分別有 $n$、$n-1$、$\dots$、$2$、$1$ 個選擇,使用 $O(n!)$ 時間。當記錄解時,需要複製矩陣 `state` 並新增進 `res` ,複製操作使用 $O(n^2)$ 時間。因此,**總體時間複雜度為 $O(n! \cdot n^2)$** 。實際上,根據對角線約束的剪枝也能夠大幅縮小搜尋空間,因而搜尋效率往往優於以上時間複雜度。 陣列 `state` 使用 $O(n^2)$ 空間,陣列 `cols`、`diags1` 和 `diags2` 皆使用 $O(n)$ 空間。最大遞迴深度為 $n$ ,使用 $O(n)$ 堆疊幀空間。因此,**空間複雜度為 $O(n^2)$** 。 ================================================ FILE: zh-hant/docs/chapter_backtracking/permutations_problem.md ================================================ # 全排列問題 全排列問題是回溯演算法的一個典型應用。它的定義是在給定一個集合(如一個陣列或字串)的情況下,找出其中元素的所有可能的排列。 下表列舉了幾個示例資料,包括輸入陣列和對應的所有排列。

  全排列示例

| 輸入陣列 | 所有排列 | | :---------- | :----------------------------------------------------------------- | | $[1]$ | $[1]$ | | $[1, 2]$ | $[1, 2], [2, 1]$ | | $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ | ## 無相等元素的情況 !!! question 輸入一個整數陣列,其中不包含重複元素,返回所有可能的排列。 從回溯演算法的角度看,**我們可以把生成排列的過程想象成一系列選擇的結果**。假設輸入陣列為 $[1, 2, 3]$ ,如果我們先選擇 $1$ ,再選擇 $3$ ,最後選擇 $2$ ,則獲得排列 $[1, 3, 2]$ 。回退表示撤銷一個選擇,之後繼續嘗試其他選擇。 從回溯程式碼的角度看,候選集合 `choices` 是輸入陣列中的所有元素,狀態 `state` 是直至目前已被選擇的元素。請注意,每個元素只允許被選擇一次,**因此 `state` 中的所有元素都應該是唯一的**。 如下圖所示,我們可以將搜尋過程展開成一棵遞迴樹,樹中的每個節點代表當前狀態 `state` 。從根節點開始,經過三輪選擇後到達葉節點,每個葉節點都對應一個排列。 ![全排列的遞迴樹](permutations_problem.assets/permutations_i.png) ### 重複選擇剪枝 為了實現每個元素只被選擇一次,我們考慮引入一個布林型陣列 `selected` ,其中 `selected[i]` 表示 `choices[i]` 是否已被選擇,並基於它實現以下剪枝操作。 - 在做出選擇 `choice[i]` 後,我們就將 `selected[i]` 賦值為 $\text{True}$ ,代表它已被選擇。 - 走訪選擇串列 `choices` 時,跳過所有已被選擇的節點,即剪枝。 如下圖所示,假設我們第一輪選擇 1 ,第二輪選擇 3 ,第三輪選擇 2 ,則需要在第二輪剪掉元素 1 的分支,在第三輪剪掉元素 1 和元素 3 的分支。 ![全排列剪枝示例](permutations_problem.assets/permutations_i_pruning.png) 觀察上圖發現,該剪枝操作將搜尋空間大小從 $O(n^n)$ 減小至 $O(n!)$ 。 ### 程式碼實現 想清楚以上資訊之後,我們就可以在框架程式碼中做“完形填空”了。為了縮短整體程式碼,我們不單獨實現框架程式碼中的各個函式,而是將它們展開在 `backtrack()` 函式中: ```src [file]{permutations_i}-[class]{}-[func]{permutations_i} ``` ## 考慮相等元素的情況 !!! question 輸入一個整數陣列,**陣列中可能包含重複元素**,返回所有不重複的排列。 假設輸入陣列為 $[1, 1, 2]$ 。為了方便區分兩個重複元素 $1$ ,我們將第二個 $1$ 記為 $\hat{1}$ 。 如下圖所示,上述方法生成的排列有一半是重複的。 ![重複排列](permutations_problem.assets/permutations_ii.png) 那麼如何去除重複的排列呢?最直接地,考慮藉助一個雜湊集合,直接對排列結果進行去重。然而這樣做不夠優雅,**因為生成重複排列的搜尋分支沒有必要,應當提前識別並剪枝**,這樣可以進一步提升演算法效率。 ### 相等元素剪枝 觀察下圖,在第一輪中,選擇 $1$ 或選擇 $\hat{1}$ 是等價的,在這兩個選擇之下生成的所有排列都是重複的。因此應該把 $\hat{1}$ 剪枝。 同理,在第一輪選擇 $2$ 之後,第二輪選擇中的 $1$ 和 $\hat{1}$ 也會產生重複分支,因此也應將第二輪的 $\hat{1}$ 剪枝。 從本質上看,**我們的目標是在某一輪選擇中,保證多個相等的元素僅被選擇一次**。 ![重複排列剪枝](permutations_problem.assets/permutations_ii_pruning.png) ### 程式碼實現 在上一題的程式碼的基礎上,我們考慮在每一輪選擇中開啟一個雜湊集合 `duplicated` ,用於記錄該輪中已經嘗試過的元素,並將重複元素剪枝: ```src [file]{permutations_ii}-[class]{}-[func]{permutations_ii} ``` 假設元素兩兩之間互不相同,則 $n$ 個元素共有 $n!$ 種排列(階乘);在記錄結果時,需要複製長度為 $n$ 的串列,使用 $O(n)$ 時間。**因此時間複雜度為 $O(n!n)$** 。 最大遞迴深度為 $n$ ,使用 $O(n)$ 堆疊幀空間。`selected` 使用 $O(n)$ 空間。同一時刻最多共有 $n$ 個 `duplicated` ,使用 $O(n^2)$ 空間。**因此空間複雜度為 $O(n^2)$** 。 ### 兩種剪枝對比 請注意,雖然 `selected` 和 `duplicated` 都用於剪枝,但兩者的目標不同。 - **重複選擇剪枝**:整個搜尋過程中只有一個 `selected` 。它記錄的是當前狀態中包含哪些元素,其作用是避免某個元素在 `state` 中重複出現。 - **相等元素剪枝**:每輪選擇(每個呼叫的 `backtrack` 函式)都包含一個 `duplicated` 。它記錄的是在本輪走訪(`for` 迴圈)中哪些元素已被選擇過,其作用是保證相等元素只被選擇一次。 下圖展示了兩個剪枝條件的生效範圍。注意,樹中的每個節點代表一個選擇,從根節點到葉節點的路徑上的各個節點構成一個排列。 ![兩種剪枝條件的作用範圍](permutations_problem.assets/permutations_ii_pruning_summary.png) ================================================ FILE: zh-hant/docs/chapter_backtracking/subset_sum_problem.md ================================================ # 子集和問題 ## 無重複元素的情況 !!! question 給定一個正整數陣列 `nums` 和一個目標正整數 `target` ,請找出所有可能的組合,使得組合中的元素和等於 `target` 。給定陣列無重複元素,每個元素可以被選取多次。請以串列形式返回這些組合,串列中不應包含重複組合。 例如,輸入集合 $\{3, 4, 5\}$ 和目標整數 $9$ ,解為 $\{3, 3, 3\}, \{4, 5\}$ 。需要注意以下兩點。 - 輸入集合中的元素可以被無限次重複選取。 - 子集不區分元素順序,比如 $\{4, 5\}$ 和 $\{5, 4\}$ 是同一個子集。 ### 參考全排列解法 類似於全排列問題,我們可以把子集的生成過程想象成一系列選擇的結果,並在選擇過程中即時更新“元素和”,當元素和等於 `target` 時,就將子集記錄至結果串列。 而與全排列問題不同的是,**本題集合中的元素可以被無限次選取**,因此無須藉助 `selected` 布林串列來記錄元素是否已被選擇。我們可以對全排列程式碼進行小幅修改,初步得到解題程式碼: ```src [file]{subset_sum_i_naive}-[class]{}-[func]{subset_sum_i_naive} ``` 向以上程式碼輸入陣列 $[3, 4, 5]$ 和目標元素 $9$ ,輸出結果為 $[3, 3, 3], [4, 5], [5, 4]$ 。**雖然成功找出了所有和為 $9$ 的子集,但其中存在重複的子集 $[4, 5]$ 和 $[5, 4]$** 。 這是因為搜尋過程是區分選擇順序的,然而子集不區分選擇順序。如下圖所示,先選 $4$ 後選 $5$ 與先選 $5$ 後選 $4$ 是不同的分支,但對應同一個子集。 ![子集搜尋與越界剪枝](subset_sum_problem.assets/subset_sum_i_naive.png) 為了去除重複子集,**一種直接的思路是對結果串列進行去重**。但這個方法效率很低,有兩方面原因。 - 當陣列元素較多,尤其是當 `target` 較大時,搜尋過程會產生大量的重複子集。 - 比較子集(陣列)的異同非常耗時,需要先排序陣列,再比較陣列中每個元素的異同。 ### 重複子集剪枝 **我們考慮在搜尋過程中透過剪枝進行去重**。觀察下圖,重複子集是在以不同順序選擇陣列元素時產生的,例如以下情況。 1. 當第一輪和第二輪分別選擇 $3$ 和 $4$ 時,會生成包含這兩個元素的所有子集,記為 $[3, 4, \dots]$ 。 2. 之後,當第一輪選擇 $4$ 時,**則第二輪應該跳過 $3$** ,因為該選擇產生的子集 $[4, 3, \dots]$ 和第 `1.` 步中生成的子集完全重複。 在搜尋過程中,每一層的選擇都是從左到右被逐個嘗試的,因此越靠右的分支被剪掉的越多。 1. 前兩輪選擇 $3$ 和 $5$ ,生成子集 $[3, 5, \dots]$ 。 2. 前兩輪選擇 $4$ 和 $5$ ,生成子集 $[4, 5, \dots]$ 。 3. 若第一輪選擇 $5$ ,**則第二輪應該跳過 $3$ 和 $4$** ,因為子集 $[5, 3, \dots]$ 和 $[5, 4, \dots]$ 與第 `1.` 步和第 `2.` 步中描述的子集完全重複。 ![不同選擇順序導致的重複子集](subset_sum_problem.assets/subset_sum_i_pruning.png) 總結來看,給定輸入陣列 $[x_1, x_2, \dots, x_n]$ ,設搜尋過程中的選擇序列為 $[x_{i_1}, x_{i_2}, \dots, x_{i_m}]$ ,則該選擇序列需要滿足 $i_1 \leq i_2 \leq \dots \leq i_m$ ,**不滿足該條件的選擇序列都會造成重複,應當剪枝**。 ### 程式碼實現 為實現該剪枝,我們初始化變數 `start` ,用於指示走訪起始點。**當做出選擇 $x_{i}$ 後,設定下一輪從索引 $i$ 開始走訪**。這樣做就可以讓選擇序列滿足 $i_1 \leq i_2 \leq \dots \leq i_m$ ,從而保證子集唯一。 除此之外,我們還對程式碼進行了以下兩項最佳化。 - 在開啟搜尋前,先將陣列 `nums` 排序。在走訪所有選擇時,**當子集和超過 `target` 時直接結束迴圈**,因為後邊的元素更大,其子集和一定超過 `target` 。 - 省去元素和變數 `total` ,**透過在 `target` 上執行減法來統計元素和**,當 `target` 等於 $0$ 時記錄解。 ```src [file]{subset_sum_i}-[class]{}-[func]{subset_sum_i} ``` 下圖所示為將陣列 $[3, 4, 5]$ 和目標元素 $9$ 輸入以上程式碼後的整體回溯過程。 ![子集和 I 回溯過程](subset_sum_problem.assets/subset_sum_i.png) ## 考慮重複元素的情況 !!! question 給定一個正整數陣列 `nums` 和一個目標正整數 `target` ,請找出所有可能的組合,使得組合中的元素和等於 `target` 。**給定陣列可能包含重複元素,每個元素只可被選擇一次**。請以串列形式返回這些組合,串列中不應包含重複組合。 相比於上題,**本題的輸入陣列可能包含重複元素**,這引入了新的問題。例如,給定陣列 $[4, \hat{4}, 5]$ 和目標元素 $9$ ,則現有程式碼的輸出結果為 $[4, 5], [\hat{4}, 5]$ ,出現了重複子集。 **造成這種重複的原因是相等元素在某輪中被多次選擇**。在下圖中,第一輪共有三個選擇,其中兩個都為 $4$ ,會產生兩個重複的搜尋分支,從而輸出重複子集;同理,第二輪的兩個 $4$ 也會產生重複子集。 ![相等元素導致的重複子集](subset_sum_problem.assets/subset_sum_ii_repeat.png) ### 相等元素剪枝 為解決此問題,**我們需要限制相等元素在每一輪中只能被選擇一次**。實現方式比較巧妙:由於陣列是已排序的,因此相等元素都是相鄰的。這意味著在某輪選擇中,若當前元素與其左邊元素相等,則說明它已經被選擇過,因此直接跳過當前元素。 與此同時,**本題規定每個陣列元素只能被選擇一次**。幸運的是,我們也可以利用變數 `start` 來滿足該約束:當做出選擇 $x_{i}$ 後,設定下一輪從索引 $i + 1$ 開始向後走訪。這樣既能去除重複子集,也能避免重複選擇元素。 ### 程式碼實現 ```src [file]{subset_sum_ii}-[class]{}-[func]{subset_sum_ii} ``` 下圖展示了陣列 $[4, 4, 5]$ 和目標元素 $9$ 的回溯過程,共包含四種剪枝操作。請你將圖示與程式碼註釋相結合,理解整個搜尋過程,以及每種剪枝操作是如何工作的。 ![子集和 II 回溯過程](subset_sum_problem.assets/subset_sum_ii.png) ================================================ FILE: zh-hant/docs/chapter_backtracking/summary.md ================================================ # 小結 ### 重點回顧 - 回溯演算法本質是窮舉法,透過對解空間進行深度優先走訪來尋找符合條件的解。在搜尋過程中,遇到滿足條件的解則記錄,直至找到所有解或走訪完成後結束。 - 回溯演算法的搜尋過程包括嘗試與回退兩個部分。它透過深度優先搜尋來嘗試各種選擇,當遇到不滿足約束條件的情況時,則撤銷上一步的選擇,退回到之前的狀態,並繼續嘗試其他選擇。嘗試與回退是兩個方向相反的操作。 - 回溯問題通常包含多個約束條件,它們可用於實現剪枝操作。剪枝可以提前結束不必要的搜尋分支,大幅提升搜尋效率。 - 回溯演算法主要可用於解決搜尋問題和約束滿足問題。組合最佳化問題雖然可以用回溯演算法解決,但往往存在效率更高或效果更好的解法。 - 全排列問題旨在搜尋給定集合元素的所有可能的排列。我們藉助一個陣列來記錄每個元素是否被選擇,剪掉重複選擇同一元素的搜尋分支,確保每個元素只被選擇一次。 - 在全排列問題中,如果集合中存在重複元素,則最終結果會出現重複排列。我們需要約束相等元素在每輪中只能被選擇一次,這通常藉助一個雜湊集合來實現。 - 子集和問題的目標是在給定集合中找到和為目標值的所有子集。集合不區分元素順序,而搜尋過程會輸出所有順序的結果,產生重複子集。我們在回溯前將資料進行排序,並設定一個變數來指示每一輪的走訪起始點,從而將生成重複子集的搜尋分支進行剪枝。 - 對於子集和問題,陣列中的相等元素會產生重複集合。我們利用陣列已排序的前置條件,透過判斷相鄰元素是否相等實現剪枝,從而確保相等元素在每輪中只能被選中一次。 - $n$ 皇后問題旨在尋找將 $n$ 個皇后放置到 $n \times n$ 尺寸棋盤上的方案,要求所有皇后兩兩之間無法攻擊對方。該問題的約束條件有行約束、列約束、主對角線和次對角線約束。為滿足行約束,我們採用按行放置的策略,保證每一行放置一個皇后。 - 列約束和對角線約束的處理方式類似。對於列約束,我們利用一個陣列來記錄每一列是否有皇后,從而指示選中的格子是否合法。對於對角線約束,我們藉助兩個陣列來分別記錄該主、次對角線上是否存在皇后;難點在於找出處在同一主(副)對角線上的格子所滿足的行列索引規律。 ### Q & A **Q**:怎麼理解回溯和遞迴的關係? 總的來看,回溯是一種“演算法策略”,而遞迴更像是一個“工具”。 - 回溯演算法通常基於遞迴實現。然而,回溯是遞迴的應用場景之一,是遞迴在搜尋問題中的應用。 - 遞迴的結構體現了“子問題分解”的解題範式,常用於解決分治、回溯、動態規劃(記憶化遞迴)等問題。 ================================================ FILE: zh-hant/docs/chapter_computational_complexity/index.md ================================================ # 複雜度分析 ![複雜度分析](../assets/covers/chapter_complexity_analysis.jpg) !!! abstract 複雜度分析猶如浩瀚的演算法宇宙中的時空嚮導。 它帶領我們在時間與空間這兩個維度上深入探索,尋找更優雅的解決方案。 ================================================ FILE: zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.md ================================================ # 迭代與遞迴 在演算法中,重複執行某個任務是很常見的,它與複雜度分析息息相關。因此,在介紹時間複雜度和空間複雜度之前,我們先來了解如何在程式中實現重複執行任務,即兩種基本的程式控制結構:迭代、遞迴。 ## 迭代 迭代(iteration)是一種重複執行某個任務的控制結構。在迭代中,程式會在滿足一定的條件下重複執行某段程式碼,直到這個條件不再滿足。 ### for 迴圈 `for` 迴圈是最常見的迭代形式之一,**適合在預先知道迭代次數時使用**。 以下函式基於 `for` 迴圈實現了求和 $1 + 2 + \dots + n$ ,求和結果使用變數 `res` 記錄。需要注意的是,Python 中 `range(a, b)` 對應的區間是“左閉右開”的,對應的走訪範圍為 $a, a + 1, \dots, b-1$ : ```src [file]{iteration}-[class]{}-[func]{for_loop} ``` 下圖是該求和函式的流程框圖。 ![求和函式的流程框圖](iteration_and_recursion.assets/iteration.png) 此求和函式的操作數量與輸入資料大小 $n$ 成正比,或者說成“線性關係”。實際上,**時間複雜度描述的就是這個“線性關係”**。相關內容將會在下一節中詳細介紹。 ### while 迴圈 與 `for` 迴圈類似,`while` 迴圈也是一種實現迭代的方法。在 `while` 迴圈中,程式每輪都會先檢查條件,如果條件為真,則繼續執行,否則就結束迴圈。 下面我們用 `while` 迴圈來實現求和 $1 + 2 + \dots + n$ : ```src [file]{iteration}-[class]{}-[func]{while_loop} ``` **`while` 迴圈比 `for` 迴圈的自由度更高**。在 `while` 迴圈中,我們可以自由地設計條件變數的初始化和更新步驟。 例如在以下程式碼中,條件變數 $i$ 每輪進行兩次更新,這種情況就不太方便用 `for` 迴圈實現: ```src [file]{iteration}-[class]{}-[func]{while_loop_ii} ``` 總的來說,**`for` 迴圈的程式碼更加緊湊,`while` 迴圈更加靈活**,兩者都可以實現迭代結構。選擇使用哪一個應該根據特定問題的需求來決定。 ### 巢狀迴圈 我們可以在一個迴圈結構內巢狀另一個迴圈結構,下面以 `for` 迴圈為例: ```src [file]{iteration}-[class]{}-[func]{nested_for_loop} ``` 下圖是該巢狀迴圈的流程框圖。 ![巢狀迴圈的流程框圖](iteration_and_recursion.assets/nested_iteration.png) 在這種情況下,函式的操作數量與 $n^2$ 成正比,或者說演算法執行時間和輸入資料大小 $n$ 成“平方關係”。 我們可以繼續新增巢狀迴圈,每一次巢狀都是一次“升維”,將會使時間複雜度提高至“立方關係”“四次方關係”,以此類推。 ## 遞迴 遞迴(recursion)是一種演算法策略,透過函式呼叫自身來解決問題。它主要包含兩個階段。 1. **遞**:程式不斷深入地呼叫自身,通常傳入更小或更簡化的參數,直到達到“終止條件”。 2. **迴**:觸發“終止條件”後,程式從最深層的遞迴函式開始逐層返回,匯聚每一層的結果。 而從實現的角度看,遞迴程式碼主要包含三個要素。 1. **終止條件**:用於決定什麼時候由“遞”轉“迴”。 2. **遞迴呼叫**:對應“遞”,函式呼叫自身,通常輸入更小或更簡化的參數。 3. **返回結果**:對應“迴”,將當前遞迴層級的結果返回至上一層。 觀察以下程式碼,我們只需呼叫函式 `recur(n)` ,就可以完成 $1 + 2 + \dots + n$ 的計算: ```src [file]{recursion}-[class]{}-[func]{recur} ``` 下圖展示了該函式的遞迴過程。 ![求和函式的遞迴過程](iteration_and_recursion.assets/recursion_sum.png) 雖然從計算角度看,迭代與遞迴可以得到相同的結果,**但它們代表了兩種完全不同的思考和解決問題的範式**。 - **迭代**:“自下而上”地解決問題。從最基礎的步驟開始,然後不斷重複或累加這些步驟,直到任務完成。 - **遞迴**:“自上而下”地解決問題。將原問題分解為更小的子問題,這些子問題和原問題具有相同的形式。接下來將子問題繼續分解為更小的子問題,直到基本情況時停止(基本情況的解是已知的)。 以上述求和函式為例,設問題 $f(n) = 1 + 2 + \dots + n$ 。 - **迭代**:在迴圈中模擬求和過程,從 $1$ 走訪到 $n$ ,每輪執行求和操作,即可求得 $f(n)$ 。 - **遞迴**:將問題分解為子問題 $f(n) = n + f(n-1)$ ,不斷(遞迴地)分解下去,直至基本情況 $f(1) = 1$ 時終止。 ### 呼叫堆疊 遞迴函式每次呼叫自身時,系統都會為新開啟的函式分配記憶體,以儲存區域性變數、呼叫位址和其他資訊等。這將導致兩方面的結果。 - 函式的上下文資料都儲存在稱為“堆疊幀空間”的記憶體區域中,直至函式返回後才會被釋放。因此,**遞迴通常比迭代更加耗費記憶體空間**。 - 遞迴呼叫函式會產生額外的開銷。**因此遞迴通常比迴圈的時間效率更低**。 如下圖所示,在觸發終止條件前,同時存在 $n$ 個未返回的遞迴函式,**遞迴深度為 $n$** 。 ![遞迴呼叫深度](iteration_and_recursion.assets/recursion_sum_depth.png) 在實際中,程式語言允許的遞迴深度通常是有限的,過深的遞迴可能導致堆疊溢位錯誤。 ### 尾遞迴 有趣的是,**如果函式在返回前的最後一步才進行遞迴呼叫**,則該函式可以被編譯器或直譯器最佳化,使其在空間效率上與迭代相當。這種情況被稱為尾遞迴(tail recursion)。 - **普通遞迴**:當函式返回到上一層級的函式後,需要繼續執行程式碼,因此系統需要儲存上一層呼叫的上下文。 - **尾遞迴**:遞迴呼叫是函式返回前的最後一個操作,這意味著函式返回到上一層級後,無須繼續執行其他操作,因此系統無須儲存上一層函式的上下文。 以計算 $1 + 2 + \dots + n$ 為例,我們可以將結果變數 `res` 設為函式參數,從而實現尾遞迴: ```src [file]{recursion}-[class]{}-[func]{tail_recur} ``` 尾遞迴的執行過程如下圖所示。對比普通遞迴和尾遞迴,兩者的求和操作的執行點是不同的。 - **普通遞迴**:求和操作是在“迴”的過程中執行的,每層返回後都要再執行一次求和操作。 - **尾遞迴**:求和操作是在“遞”的過程中執行的,“迴”的過程只需層層返回。 ![尾遞迴過程](iteration_and_recursion.assets/tail_recursion_sum.png) !!! tip 請注意,許多編譯器或直譯器並不支持尾遞迴最佳化。例如,Python 預設不支持尾遞迴最佳化,因此即使函式是尾遞迴形式,仍然可能會遇到堆疊溢位問題。 ### 遞迴樹 當處理與“分治”相關的演算法問題時,遞迴往往比迭代的思路更加直觀、程式碼更加易讀。以“費波那契數列”為例。 !!! question 給定一個費波那契數列 $0, 1, 1, 2, 3, 5, 8, 13, \dots$ ,求該數列的第 $n$ 個數字。 設費波那契數列的第 $n$ 個數字為 $f(n)$ ,易得兩個結論。 - 數列的前兩個數字為 $f(1) = 0$ 和 $f(2) = 1$ 。 - 數列中的每個數字是前兩個數字的和,即 $f(n) = f(n - 1) + f(n - 2)$ 。 按照遞推關係進行遞迴呼叫,將前兩個數字作為終止條件,便可寫出遞迴程式碼。呼叫 `fib(n)` 即可得到費波那契數列的第 $n$ 個數字: ```src [file]{recursion}-[class]{}-[func]{fib} ``` 觀察以上程式碼,我們在函式內遞迴呼叫了兩個函式,**這意味著從一個呼叫產生了兩個呼叫分支**。如下圖所示,這樣不斷遞迴呼叫下去,最終將產生一棵層數為 $n$ 的遞迴樹(recursion tree)。 ![費波那契數列的遞迴樹](iteration_and_recursion.assets/recursion_tree.png) 從本質上看,遞迴體現了“將問題分解為更小子問題”的思維範式,這種分治策略至關重要。 - 從演算法角度看,搜尋、排序、回溯、分治、動態規劃等許多重要演算法策略直接或間接地應用了這種思維方式。 - 從資料結構角度看,遞迴天然適合處理鏈結串列、樹和圖的相關問題,因為它們非常適合用分治思想進行分析。 ## 兩者對比 總結以上內容,如下表所示,迭代和遞迴在實現、效能和適用性上有所不同。

  迭代與遞迴特點對比

| | 迭代 | 遞迴 | | -------- | -------------------------------------- | ------------------------------------------------------------ | | 實現方式 | 迴圈結構 | 函式呼叫自身 | | 時間效率 | 效率通常較高,無函式呼叫開銷 | 每次函式呼叫都會產生開銷 | | 記憶體使用 | 通常使用固定大小的記憶體空間 | 累積函式呼叫可能使用大量的堆疊幀空間 | | 適用問題 | 適用於簡單迴圈任務,程式碼直觀、可讀性好 | 適用於子問題分解,如樹、圖、分治、回溯等,程式碼結構簡潔、清晰 | !!! tip 如果感覺以下內容理解困難,可以在讀完“堆疊”章節後再來複習。 那麼,迭代和遞迴具有什麼內在關聯呢?以上述遞迴函式為例,求和操作在遞迴的“迴”階段進行。這意味著最初被呼叫的函式實際上是最後完成其求和操作的,**這種工作機制與堆疊的“先入後出”原則異曲同工**。 事實上,“呼叫堆疊”和“堆疊幀空間”這類遞迴術語已經暗示了遞迴與堆疊之間的密切關係。 1. **遞**:當函式被呼叫時,系統會在“呼叫堆疊”上為該函式分配新的堆疊幀,用於儲存函式的區域性變數、參數、返回位址等資料。 2. **迴**:當函式完成執行並返回時,對應的堆疊幀會被從“呼叫堆疊”上移除,恢復之前函式的執行環境。 因此,**我們可以使用一個顯式的堆疊來模擬呼叫堆疊的行為**,從而將遞迴轉化為迭代形式: ```src [file]{recursion}-[class]{}-[func]{for_loop_recur} ``` 觀察以上程式碼,當遞迴轉化為迭代後,程式碼變得更加複雜了。儘管迭代和遞迴在很多情況下可以互相轉化,但不一定值得這樣做,有以下兩點原因。 - 轉化後的程式碼可能更加難以理解,可讀性更差。 - 對於某些複雜問題,模擬系統呼叫堆疊的行為可能非常困難。 總之,**選擇迭代還是遞迴取決於特定問題的性質**。在程式設計實踐中,權衡兩者的優劣並根據情境選擇合適的方法至關重要。 ================================================ FILE: zh-hant/docs/chapter_computational_complexity/performance_evaluation.md ================================================ # 演算法效率評估 在演算法設計中,我們先後追求以下兩個層面的目標。 1. **找到問題解法**:演算法需要在規定的輸入範圍內可靠地求得問題的正確解。 2. **尋求最優解法**:同一個問題可能存在多種解法,我們希望找到儘可能高效的演算法。 也就是說,在能夠解決問題的前提下,演算法效率已成為衡量演算法優劣的主要評價指標,它包括以下兩個維度。 - **時間效率**:演算法執行時間的長短。 - **空間效率**:演算法佔用記憶體空間的大小。 簡而言之,**我們的目標是設計“既快又省”的資料結構與演算法**。而有效地評估演算法效率至關重要,因為只有這樣,我們才能將各種演算法進行對比,進而指導演算法設計與最佳化過程。 效率評估方法主要分為兩種:實際測試、理論估算。 ## 實際測試 假設我們現在有演算法 `A` 和演算法 `B` ,它們都能解決同一問題,現在需要對比這兩個演算法的效率。最直接的方法是找一臺計算機,執行這兩個演算法,並監控記錄它們的執行時間和記憶體佔用情況。這種評估方式能夠反映真實情況,但也存在較大的侷限性。 一方面,**難以排除測試環境的干擾因素**。硬體配置會影響演算法的效能表現。比如一個演算法的並行度較高,那麼它就更適合在多核 CPU 上執行,一個演算法的記憶體操作密集,那麼它在高效能記憶體上的表現就會更好。也就是說,演算法在不同的機器上的測試結果可能是不一致的。這意味著我們需要在各種機器上進行測試,統計平均效率,而這是不現實的。 另一方面,**展開完整測試非常耗費資源**。隨著輸入資料量的變化,演算法會表現出不同的效率。例如,在輸入資料量較小時,演算法 `A` 的執行時間比演算法 `B` 短;而在輸入資料量較大時,測試結果可能恰恰相反。因此,為了得到有說服力的結論,我們需要測試各種規模的輸入資料,而這需要耗費大量的計算資源。 ## 理論估算 由於實際測試具有較大的侷限性,我們可以考慮僅透過一些計算來評估演算法的效率。這種估算方法被稱為漸近複雜度分析(asymptotic complexity analysis),簡稱複雜度分析。 複雜度分析能夠體現演算法執行所需的時間和空間資源與輸入資料規模之間的關係。**它描述了隨著輸入資料規模的增加,演算法執行所需時間和空間的增長趨勢**。這個定義有些拗口,我們可以將其分為三個重點來理解。 - “時間和空間資源”分別對應時間複雜度(time complexity)空間複雜度(space complexity)。 - “隨著輸入資料規模的增加”意味著複雜度反映了演算法執行效率與輸入資料規模之間的關係。 - “時間和空間的增長趨勢”表示複雜度分析關注的不是執行時間或佔用空間的具體值,而是時間或空間增長的“快慢”。 **複雜度分析克服了實際測試方法的弊端**,體現在以下幾個方面。 - 它無需實際執行程式碼,更加綠色節能。 - 它獨立於測試環境,分析結果適用於所有執行平臺。 - 它可以體現不同資料量下的演算法效率,尤其是在大資料量下的演算法效能。 !!! tip 如果你仍對複雜度的概念感到困惑,無須擔心,我們會在後續章節中詳細介紹。 複雜度分析為我們提供了一把評估演算法效率的“標尺”,使我們可以衡量執行某個演算法所需的時間和空間資源,對比不同演算法之間的效率。 複雜度是個數學概念,對於初學者可能比較抽象,學習難度相對較高。從這個角度看,複雜度分析可能不太適合作為最先介紹的內容。然而,當我們討論某個資料結構或演算法的特點時,難以避免要分析其執行速度和空間使用情況。 綜上所述,建議你在深入學習資料結構與演算法之前,**先對複雜度分析建立初步的瞭解,以便能夠完成簡單演算法的複雜度分析**。 ================================================ FILE: zh-hant/docs/chapter_computational_complexity/space_complexity.md ================================================ # 空間複雜度 空間複雜度(space complexity)用於衡量演算法佔用記憶體空間隨著資料量變大時的增長趨勢。這個概念與時間複雜度非常類似,只需將“執行時間”替換為“佔用記憶體空間”。 ## 演算法相關空間 演算法在執行過程中使用的記憶體空間主要包括以下幾種。 - **輸入空間**:用於儲存演算法的輸入資料。 - **暫存空間**:用於儲存演算法在執行過程中的變數、物件、函式上下文等資料。 - **輸出空間**:用於儲存演算法的輸出資料。 一般情況下,空間複雜度的統計範圍是“暫存空間”加上“輸出空間”。 暫存空間可以進一步劃分為三個部分。 - **暫存資料**:用於儲存演算法執行過程中的各種常數、變數、物件等。 - **堆疊幀空間**:用於儲存呼叫函式的上下文資料。系統在每次呼叫函式時都會在堆疊頂部建立一個堆疊幀,函式返回後,堆疊幀空間會被釋放。 - **指令空間**:用於儲存編譯後的程式指令,在實際統計中通常忽略不計。 在分析一段程式的空間複雜度時,**我們通常統計暫存資料、堆疊幀空間和輸出資料三部分**,如下圖所示。 ![演算法使用的相關空間](space_complexity.assets/space_types.png) 相關程式碼如下: === "Python" ```python title="" class Node: """類別""" def __init__(self, x: int): self.val: int = x # 節點值 self.next: Node | None = None # 指向下一節點的引用 def function() -> int: """函式""" # 執行某些操作... return 0 def algorithm(n) -> int: # 輸入資料 A = 0 # 暫存資料(常數,一般用大寫字母表示) b = 0 # 暫存資料(變數) node = Node(0) # 暫存資料(物件) c = function() # 堆疊幀空間(呼叫函式) return A + b + c # 輸出資料 ``` === "C++" ```cpp title="" /* 結構體 */ struct Node { int val; Node *next; Node(int x) : val(x), next(nullptr) {} }; /* 函式 */ int func() { // 執行某些操作... return 0; } int algorithm(int n) { // 輸入資料 const int a = 0; // 暫存資料(常數) int b = 0; // 暫存資料(變數) Node* node = new Node(0); // 暫存資料(物件) int c = func(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "Java" ```java title="" /* 類別 */ class Node { int val; Node next; Node(int x) { val = x; } } /* 函式 */ int function() { // 執行某些操作... return 0; } int algorithm(int n) { // 輸入資料 final int a = 0; // 暫存資料(常數) int b = 0; // 暫存資料(變數) Node node = new Node(0); // 暫存資料(物件) int c = function(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "C#" ```csharp title="" /* 類別 */ class Node(int x) { int val = x; Node next; } /* 函式 */ int Function() { // 執行某些操作... return 0; } int Algorithm(int n) { // 輸入資料 const int a = 0; // 暫存資料(常數) int b = 0; // 暫存資料(變數) Node node = new(0); // 暫存資料(物件) int c = Function(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "Go" ```go title="" /* 結構體 */ type node struct { val int next *node } /* 建立 node 結構體 */ func newNode(val int) *node { return &node{val: val} } /* 函式 */ func function() int { // 執行某些操作... return 0 } func algorithm(n int) int { // 輸入資料 const a = 0 // 暫存資料(常數) b := 0 // 暫存資料(變數) newNode(0) // 暫存資料(物件) c := function() // 堆疊幀空間(呼叫函式) return a + b + c // 輸出資料 } ``` === "Swift" ```swift title="" /* 類別 */ class Node { var val: Int var next: Node? init(x: Int) { val = x } } /* 函式 */ func function() -> Int { // 執行某些操作... return 0 } func algorithm(n: Int) -> Int { // 輸入資料 let a = 0 // 暫存資料(常數) var b = 0 // 暫存資料(變數) let node = Node(x: 0) // 暫存資料(物件) let c = function() // 堆疊幀空間(呼叫函式) return a + b + c // 輸出資料 } ``` === "JS" ```javascript title="" /* 類別 */ class Node { val; next; constructor(val) { this.val = val === undefined ? 0 : val; // 節點值 this.next = null; // 指向下一節點的引用 } } /* 函式 */ function constFunc() { // 執行某些操作 return 0; } function algorithm(n) { // 輸入資料 const a = 0; // 暫存資料(常數) let b = 0; // 暫存資料(變數) const node = new Node(0); // 暫存資料(物件) const c = constFunc(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "TS" ```typescript title="" /* 類別 */ class Node { val: number; next: Node | null; constructor(val?: number) { this.val = val === undefined ? 0 : val; // 節點值 this.next = null; // 指向下一節點的引用 } } /* 函式 */ function constFunc(): number { // 執行某些操作 return 0; } function algorithm(n: number): number { // 輸入資料 const a = 0; // 暫存資料(常數) let b = 0; // 暫存資料(變數) const node = new Node(0); // 暫存資料(物件) const c = constFunc(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "Dart" ```dart title="" /* 類別 */ class Node { int val; Node next; Node(this.val, [this.next]); } /* 函式 */ int function() { // 執行某些操作... return 0; } int algorithm(int n) { // 輸入資料 const int a = 0; // 暫存資料(常數) int b = 0; // 暫存資料(變數) Node node = Node(0); // 暫存資料(物件) int c = function(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* 結構體 */ struct Node { val: i32, next: Option>>, } /* 建立 Node 結構體 */ impl Node { fn new(val: i32) -> Self { Self { val: val, next: None } } } /* 函式 */ fn function() -> i32 { // 執行某些操作... return 0; } fn algorithm(n: i32) -> i32 { // 輸入資料 const a: i32 = 0; // 暫存資料(常數) let mut b = 0; // 暫存資料(變數) let node = Node::new(0); // 暫存資料(物件) let c = function(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "C" ```c title="" /* 函式 */ int func() { // 執行某些操作... return 0; } int algorithm(int n) { // 輸入資料 const int a = 0; // 暫存資料(常數) int b = 0; // 暫存資料(變數) int c = func(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "Kotlin" ```kotlin title="" /* 類別 */ class Node(var _val: Int) { var next: Node? = null } /* 函式 */ fun function(): Int { // 執行某些操作... return 0 } fun algorithm(n: Int): Int { // 輸入資料 val a = 0 // 暫存資料(常數) var b = 0 // 暫存資料(變數) val node = Node(0) // 暫存資料(物件) val c = function() // 堆疊幀空間(呼叫函式) return a + b + c // 輸出資料 } ``` === "Ruby" ```ruby title="" ### 類別 ### class Node attr_accessor :val # 節點值 attr_accessor :next # 指向下一節點的引用 def initialize(x) @val = x end end ### 函式 ### def function # 執行某些操作... 0 end ### 演算法 ### def algorithm(n) # 輸入資料 a = 0 # 暫存資料(常數) b = 0 # 暫存資料(變數) node = Node.new(0) # 暫存資料(物件) c = function # 堆疊幀空間(呼叫函式) a + b + c # 輸出資料 end ``` ## 推算方法 空間複雜度的推算方法與時間複雜度大致相同,只需將統計物件從“操作數量”轉為“使用空間大小”。 而與時間複雜度不同的是,**我們通常只關注最差空間複雜度**。這是因為記憶體空間是一項硬性要求,我們必須確保在所有輸入資料下都有足夠的記憶體空間預留。 觀察以下程式碼,最差空間複雜度中的“最差”有兩層含義。 1. **以最差輸入資料為準**:當 $n < 10$ 時,空間複雜度為 $O(1)$ ;但當 $n > 10$ 時,初始化的陣列 `nums` 佔用 $O(n)$ 空間,因此最差空間複雜度為 $O(n)$ 。 2. **以演算法執行中的峰值記憶體為準**:例如,程式在執行最後一行之前,佔用 $O(1)$ 空間;當初始化陣列 `nums` 時,程式佔用 $O(n)$ 空間,因此最差空間複雜度為 $O(n)$ 。 === "Python" ```python title="" def algorithm(n: int): a = 0 # O(1) b = [0] * 10000 # O(1) if n > 10: nums = [0] * n # O(n) ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 0; // O(1) vector b(10000); // O(1) if (n > 10) vector nums(n); // O(n) } ``` === "Java" ```java title="" void algorithm(int n) { int a = 0; // O(1) int[] b = new int[10000]; // O(1) if (n > 10) int[] nums = new int[n]; // O(n) } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 0; // O(1) int[] b = new int[10000]; // O(1) if (n > 10) { int[] nums = new int[n]; // O(n) } } ``` === "Go" ```go title="" func algorithm(n int) { a := 0 // O(1) b := make([]int, 10000) // O(1) var nums []int if n > 10 { nums := make([]int, n) // O(n) } fmt.Println(a, b, nums) } ``` === "Swift" ```swift title="" func algorithm(n: Int) { let a = 0 // O(1) let b = Array(repeating: 0, count: 10000) // O(1) if n > 10 { let nums = Array(repeating: 0, count: n) // O(n) } } ``` === "JS" ```javascript title="" function algorithm(n) { const a = 0; // O(1) const b = new Array(10000); // O(1) if (n > 10) { const nums = new Array(n); // O(n) } } ``` === "TS" ```typescript title="" function algorithm(n: number): void { const a = 0; // O(1) const b = new Array(10000); // O(1) if (n > 10) { const nums = new Array(n); // O(n) } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 0; // O(1) List b = List.filled(10000, 0); // O(1) if (n > 10) { List nums = List.filled(n, 0); // O(n) } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let a = 0; // O(1) let b = [0; 10000]; // O(1) if n > 10 { let nums = vec![0; n as usize]; // O(n) } } ``` === "C" ```c title="" void algorithm(int n) { int a = 0; // O(1) int b[10000]; // O(1) if (n > 10) int nums[n] = {0}; // O(n) } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { val a = 0 // O(1) val b = IntArray(10000) // O(1) if (n > 10) { val nums = IntArray(n) // O(n) } } ``` === "Ruby" ```ruby title="" def algorithm(n) a = 0 # O(1) b = Array.new(10000) # O(1) nums = Array.new(n) if n > 10 # O(n) end ``` **在遞迴函式中,需要注意統計堆疊幀空間**。觀察以下程式碼: === "Python" ```python title="" def function() -> int: # 執行某些操作 return 0 def loop(n: int): """迴圈的空間複雜度為 O(1)""" for _ in range(n): function() def recur(n: int): """遞迴的空間複雜度為 O(n)""" if n == 1: return return recur(n - 1) ``` === "C++" ```cpp title="" int func() { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } /* 遞迴的空間複雜度為 O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Java" ```java title="" int function() { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } /* 遞迴的空間複雜度為 O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "C#" ```csharp title="" int Function() { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ void Loop(int n) { for (int i = 0; i < n; i++) { Function(); } } /* 遞迴的空間複雜度為 O(n) */ int Recur(int n) { if (n == 1) return 1; return Recur(n - 1); } ``` === "Go" ```go title="" func function() int { // 執行某些操作 return 0 } /* 迴圈的空間複雜度為 O(1) */ func loop(n int) { for i := 0; i < n; i++ { function() } } /* 遞迴的空間複雜度為 O(n) */ func recur(n int) { if n == 1 { return } recur(n - 1) } ``` === "Swift" ```swift title="" @discardableResult func function() -> Int { // 執行某些操作 return 0 } /* 迴圈的空間複雜度為 O(1) */ func loop(n: Int) { for _ in 0 ..< n { function() } } /* 遞迴的空間複雜度為 O(n) */ func recur(n: Int) { if n == 1 { return } recur(n: n - 1) } ``` === "JS" ```javascript title="" function constFunc() { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ function loop(n) { for (let i = 0; i < n; i++) { constFunc(); } } /* 遞迴的空間複雜度為 O(n) */ function recur(n) { if (n === 1) return; return recur(n - 1); } ``` === "TS" ```typescript title="" function constFunc(): number { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ function loop(n: number): void { for (let i = 0; i < n; i++) { constFunc(); } } /* 遞迴的空間複雜度為 O(n) */ function recur(n: number): void { if (n === 1) return; return recur(n - 1); } ``` === "Dart" ```dart title="" int function() { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } /* 遞迴的空間複雜度為 O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Rust" ```rust title="" fn function() -> i32 { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ fn loop(n: i32) { for i in 0..n { function(); } } /* 遞迴的空間複雜度為 O(n) */ fn recur(n: i32) { if n == 1 { return; } recur(n - 1); } ``` === "C" ```c title="" int func() { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } /* 遞迴的空間複雜度為 O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); } ``` === "Kotlin" ```kotlin title="" fun function(): Int { // 執行某些操作 return 0 } /* 迴圈的空間複雜度為 O(1) */ fun loop(n: Int) { for (i in 0..函式(function)可以被獨立執行,所有參數都以顯式傳遞。方法(method)與一個物件關聯,被隱式傳遞給呼叫它的物件,能夠對類別的例項中包含的資料進行操作。 下面以幾種常見的程式語言為例來說明。 - C 語言是程序式程式設計語言,沒有物件導向的概念,所以只有函式。但我們可以透過建立結構體(struct)來模擬物件導向程式設計,與結構體相關聯的函式就相當於其他程式語言中的方法。 - Java 和 C# 是物件導向的程式語言,程式碼塊(方法)通常作為某個類別的一部分。靜態方法的行為類似於函式,因為它被繫結在類別上,不能訪問特定的例項變數。 - C++ 和 Python 既支持程序式程式設計(函式),也支持物件導向程式設計(方法)。 **Q**:圖解“常見的空間複雜度型別”反映的是否是佔用空間的絕對大小? 不是,該圖展示的是空間複雜度,其反映的是增長趨勢,而不是佔用空間的絕對大小。 假設取 $n = 8$ ,你可能會發現每條曲線的值與函式對應不上。這是因為每條曲線都包含一個常數項,用於將取值範圍壓縮到一個視覺舒適的範圍內。 在實際中,因為我們通常不知道每個方法的“常數項”複雜度是多少,所以一般無法僅憑複雜度來選擇 $n = 8$ 之下的最優解法。但對於 $n = 8^5$ 就很好選了,這時增長趨勢已經佔主導了。 **Q** 是否存在根據實際使用場景,選擇犧牲時間(或空間)來設計演算法的情況? 在實際應用中,大部分情況會選擇犧牲空間換時間。例如資料庫索引,我們通常選擇建立 B+ 樹或雜湊索引,佔用大量記憶體空間,以換取 $O(\log n)$ 甚至 $O(1)$ 的高效查詢。 在空間資源寶貴的場景,也會選擇犧牲時間換空間。例如在嵌入式開發中,裝置記憶體很寶貴,工程師可能會放棄使用雜湊表,選擇使用陣列順序查詢,以節省記憶體佔用,代價是查詢變慢。 ================================================ FILE: zh-hant/docs/chapter_computational_complexity/time_complexity.md ================================================ # 時間複雜度 執行時間可以直觀且準確地反映演算法的效率。如果我們想準確預估一段程式碼的執行時間,應該如何操作呢? 1. **確定執行平臺**,包括硬體配置、程式語言、系統環境等,這些因素都會影響程式碼的執行效率。 2. **評估各種計算操作所需的執行時間**,例如加法操作 `+` 需要 1 ns ,乘法操作 `*` 需要 10 ns ,列印操作 `print()` 需要 5 ns 等。 3. **統計程式碼中所有的計算操作**,並將所有操作的執行時間求和,從而得到執行時間。 例如在以下程式碼中,輸入資料大小為 $n$ : === "Python" ```python title="" # 在某執行平臺下 def algorithm(n: int): a = 2 # 1 ns a = a + 1 # 1 ns a = a * 2 # 10 ns # 迴圈 n 次 for _ in range(n): # 1 ns print(0) # 5 ns ``` === "C++" ```cpp title="" // 在某執行平臺下 void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 迴圈 n 次 for (int i = 0; i < n; i++) { // 1 ns cout << 0 << endl; // 5 ns } } ``` === "Java" ```java title="" // 在某執行平臺下 void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 迴圈 n 次 for (int i = 0; i < n; i++) { // 1 ns System.out.println(0); // 5 ns } } ``` === "C#" ```csharp title="" // 在某執行平臺下 void Algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 迴圈 n 次 for (int i = 0; i < n; i++) { // 1 ns Console.WriteLine(0); // 5 ns } } ``` === "Go" ```go title="" // 在某執行平臺下 func algorithm(n int) { a := 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns // 迴圈 n 次 for i := 0; i < n; i++ { // 1 ns fmt.Println(a) // 5 ns } } ``` === "Swift" ```swift title="" // 在某執行平臺下 func algorithm(n: Int) { var a = 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns // 迴圈 n 次 for _ in 0 ..< n { // 1 ns print(0) // 5 ns } } ``` === "JS" ```javascript title="" // 在某執行平臺下 function algorithm(n) { var a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 迴圈 n 次 for(let i = 0; i < n; i++) { // 1 ns console.log(0); // 5 ns } } ``` === "TS" ```typescript title="" // 在某執行平臺下 function algorithm(n: number): void { var a: number = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 迴圈 n 次 for(let i = 0; i < n; i++) { // 1 ns console.log(0); // 5 ns } } ``` === "Dart" ```dart title="" // 在某執行平臺下 void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 迴圈 n 次 for (int i = 0; i < n; i++) { // 1 ns print(0); // 5 ns } } ``` === "Rust" ```rust title="" // 在某執行平臺下 fn algorithm(n: i32) { let mut a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 迴圈 n 次 for _ in 0..n { // 1 ns println!("{}", 0); // 5 ns } } ``` === "C" ```c title="" // 在某執行平臺下 void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 迴圈 n 次 for (int i = 0; i < n; i++) { // 1 ns printf("%d", 0); // 5 ns } } ``` === "Kotlin" ```kotlin title="" // 在某執行平臺下 fun algorithm(n: Int) { var a = 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns // 迴圈 n 次 for (i in 0.. 1$ 時比演算法 `A` 更慢,在 $n > 1000000$ 時比演算法 `C` 更慢。事實上,只要輸入資料大小 $n$ 足夠大,複雜度為“常數階”的演算法一定優於“線性階”的演算法,這正是時間增長趨勢的含義。 - **時間複雜度的推算方法更簡便**。顯然,執行平臺和計算操作型別都與演算法執行時間的增長趨勢無關。因此在時間複雜度分析中,我們可以簡單地將所有計算操作的執行時間視為相同的“單位時間”,從而將“計算操作執行時間統計”簡化為“計算操作數量統計”,這樣一來估算難度就大大降低了。 - **時間複雜度也存在一定的侷限性**。例如,儘管演算法 `A` 和 `C` 的時間複雜度相同,但實際執行時間差別很大。同樣,儘管演算法 `B` 的時間複雜度比 `C` 高,但在輸入資料大小 $n$ 較小時,演算法 `B` 明顯優於演算法 `C` 。對於此類情況,我們時常難以僅憑時間複雜度判斷演算法效率的高低。當然,儘管存在上述問題,複雜度分析仍然是評判演算法效率最有效且常用的方法。 ## 函式漸近上界 給定一個輸入大小為 $n$ 的函式: === "Python" ```python title="" def algorithm(n: int): a = 1 # +1 a = a + 1 # +1 a = a * 2 # +1 # 迴圈 n 次 for i in range(n): # +1 print(0) # +1 ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 迴圈 n 次 for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) cout << 0 << endl; // +1 } } ``` === "Java" ```java title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 迴圈 n 次 for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) System.out.println(0); // +1 } } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 迴圈 n 次 for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) Console.WriteLine(0); // +1 } } ``` === "Go" ```go title="" func algorithm(n int) { a := 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // 迴圈 n 次 for i := 0; i < n; i++ { // +1 fmt.Println(a) // +1 } } ``` === "Swift" ```swift title="" func algorithm(n: Int) { var a = 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // 迴圈 n 次 for _ in 0 ..< n { // +1 print(0) // +1 } } ``` === "JS" ```javascript title="" function algorithm(n) { var a = 1; // +1 a += 1; // +1 a *= 2; // +1 // 迴圈 n 次 for(let i = 0; i < n; i++){ // +1(每輪都執行 i ++) console.log(0); // +1 } } ``` === "TS" ```typescript title="" function algorithm(n: number): void{ var a: number = 1; // +1 a += 1; // +1 a *= 2; // +1 // 迴圈 n 次 for(let i = 0; i < n; i++){ // +1(每輪都執行 i ++) console.log(0); // +1 } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 迴圈 n 次 for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) print(0); // +1 } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let mut a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 迴圈 n 次 for _ in 0..n { // +1(每輪都執行 i ++) println!("{}", 0); // +1 } } ``` === "C" ```c title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 迴圈 n 次 for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) printf("%d", 0); // +1 } } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { var a = 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // 迴圈 n 次 for (i in 0..大 $O$ 記號(big-$O$ notation),表示函式 $T(n)$ 的漸近上界(asymptotic upper bound)。 時間複雜度分析本質上是計算“操作數量 $T(n)$”的漸近上界,它具有明確的數學定義。 !!! note "函式漸近上界" 若存在正實數 $c$ 和實數 $n_0$ ,使得對於所有的 $n > n_0$ ,均有 $T(n) \leq c \cdot f(n)$ ,則可認為 $f(n)$ 給出了 $T(n)$ 的一個漸近上界,記為 $T(n) = O(f(n))$ 。 如下圖所示,計算漸近上界就是尋找一個函式 $f(n)$ ,使得當 $n$ 趨向於無窮大時,$T(n)$ 和 $f(n)$ 處於相同的增長級別,僅相差一個常數係數 $c$。 ![函式的漸近上界](time_complexity.assets/asymptotic_upper_bound.png) ## 推算方法 漸近上界的數學味兒有點重,如果你感覺沒有完全理解,也無須擔心。我們可以先掌握推算方法,在不斷的實踐中,就可以逐漸領悟其數學意義。 根據定義,確定 $f(n)$ 之後,我們便可得到時間複雜度 $O(f(n))$ 。那麼如何確定漸近上界 $f(n)$ 呢?總體分為兩步:首先統計操作數量,然後判斷漸近上界。 ### 第一步:統計操作數量 針對程式碼,逐行從上到下計算即可。然而,由於上述 $c \cdot f(n)$ 中的常數係數 $c$ 可以取任意大小,**因此操作數量 $T(n)$ 中的各種係數、常數項都可以忽略**。根據此原則,可以總結出以下計數簡化技巧。 1. **忽略 $T(n)$ 中的常數**。因為它們都與 $n$ 無關,所以對時間複雜度不產生影響。 2. **省略所有係數**。例如,迴圈 $2n$ 次、$5n + 1$ 次等,都可以簡化記為 $n$ 次,因為 $n$ 前面的係數對時間複雜度沒有影響。 3. **迴圈巢狀時使用乘法**。總操作數量等於外層迴圈和內層迴圈操作數量之積,每一層迴圈依然可以分別套用第 `1.` 點和第 `2.` 點的技巧。 給定一個函式,我們可以用上述技巧來統計操作數量: === "Python" ```python title="" def algorithm(n: int): a = 1 # +0(技巧 1) a = a + n # +0(技巧 1) # +n(技巧 2) for i in range(5 * n + 1): print(0) # +n*n(技巧 3) for i in range(2 * n): for j in range(n + 1): print(0) ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { cout << 0 << endl; } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { cout << 0 << endl; } } } ``` === "Java" ```java title="" void algorithm(int n) { int a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { System.out.println(0); } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { System.out.println(0); } } } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { Console.WriteLine(0); } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { Console.WriteLine(0); } } } ``` === "Go" ```go title="" func algorithm(n int) { a := 1 // +0(技巧 1) a = a + n // +0(技巧 1) // +n(技巧 2) for i := 0; i < 5 * n + 1; i++ { fmt.Println(0) } // +n*n(技巧 3) for i := 0; i < 2 * n; i++ { for j := 0; j < n + 1; j++ { fmt.Println(0) } } } ``` === "Swift" ```swift title="" func algorithm(n: Int) { var a = 1 // +0(技巧 1) a = a + n // +0(技巧 1) // +n(技巧 2) for _ in 0 ..< (5 * n + 1) { print(0) } // +n*n(技巧 3) for _ in 0 ..< (2 * n) { for _ in 0 ..< (n + 1) { print(0) } } } ``` === "JS" ```javascript title="" function algorithm(n) { let a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (let i = 0; i < 5 * n + 1; i++) { console.log(0); } // +n*n(技巧 3) for (let i = 0; i < 2 * n; i++) { for (let j = 0; j < n + 1; j++) { console.log(0); } } } ``` === "TS" ```typescript title="" function algorithm(n: number): void { let a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (let i = 0; i < 5 * n + 1; i++) { console.log(0); } // +n*n(技巧 3) for (let i = 0; i < 2 * n; i++) { for (let j = 0; j < n + 1; j++) { console.log(0); } } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { print(0); } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { print(0); } } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let mut a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for i in 0..(5 * n + 1) { println!("{}", 0); } // +n*n(技巧 3) for i in 0..(2 * n) { for j in 0..(n + 1) { println!("{}", 0); } } } ``` === "C" ```c title="" void algorithm(int n) { int a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { printf("%d", 0); } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { printf("%d", 0); } } } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { var a = 1 // +0(技巧 1) a = a + n // +0(技巧 1) // +n(技巧 2) for (i in 0..<5 * n + 1) { println(0) } // +n*n(技巧 3) for (i in 0..<2 * n) { for (j in 0..   不同操作數量對應的時間複雜度

| 操作數量 $T(n)$ | 時間複雜度 $O(f(n))$ | | ---------------------- | -------------------- | | $100000$ | $O(1)$ | | $3n + 2$ | $O(n)$ | | $2n^2 + 3n + 2$ | $O(n^2)$ | | $n^3 + 10000n^2$ | $O(n^3)$ | | $2^n + 10000n^{10000}$ | $O(2^n)$ | ## 常見型別 設輸入資料大小為 $n$ ,常見的時間複雜度型別如下圖所示(按照從低到高的順序排列)。 $$ \begin{aligned} O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline \text{常數階} < \text{對數階} < \text{線性階} < \text{線性對數階} < \text{平方階} < \text{指數階} < \text{階乘階} \end{aligned} $$ ![常見的時間複雜度型別](time_complexity.assets/time_complexity_common_types.png) ### 常數階 $O(1)$ 常數階的操作數量與輸入資料大小 $n$ 無關,即不隨著 $n$ 的變化而變化。 在以下函式中,儘管操作數量 `size` 可能很大,但由於其與輸入資料大小 $n$ 無關,因此時間複雜度仍為 $O(1)$ : ```src [file]{time_complexity}-[class]{}-[func]{constant} ``` ### 線性階 $O(n)$ 線性階的操作數量相對於輸入資料大小 $n$ 以線性級別增長。線性階通常出現在單層迴圈中: ```src [file]{time_complexity}-[class]{}-[func]{linear} ``` 走訪陣列和走訪鏈結串列等操作的時間複雜度均為 $O(n)$ ,其中 $n$ 為陣列或鏈結串列的長度: ```src [file]{time_complexity}-[class]{}-[func]{array_traversal} ``` 值得注意的是,**輸入資料大小 $n$ 需根據輸入資料的型別來具體確定**。比如在第一個示例中,變數 $n$ 為輸入資料大小;在第二個示例中,陣列長度 $n$ 為資料大小。 ### 平方階 $O(n^2)$ 平方階的操作數量相對於輸入資料大小 $n$ 以平方級別增長。平方階通常出現在巢狀迴圈中,外層迴圈和內層迴圈的時間複雜度都為 $O(n)$ ,因此總體的時間複雜度為 $O(n^2)$ : ```src [file]{time_complexity}-[class]{}-[func]{quadratic} ``` 下圖對比了常數階、線性階和平方階三種時間複雜度。 ![常數階、線性階和平方階的時間複雜度](time_complexity.assets/time_complexity_constant_linear_quadratic.png) 以泡沫排序為例,外層迴圈執行 $n - 1$ 次,內層迴圈執行 $n-1$、$n-2$、$\dots$、$2$、$1$ 次,平均為 $n / 2$ 次,因此時間複雜度為 $O((n - 1) n / 2) = O(n^2)$ : ```src [file]{time_complexity}-[class]{}-[func]{bubble_sort} ``` ### 指數階 $O(2^n)$ 生物學的“細胞分裂”是指數階增長的典型例子:初始狀態為 $1$ 個細胞,分裂一輪後變為 $2$ 個,分裂兩輪後變為 $4$ 個,以此類推,分裂 $n$ 輪後有 $2^n$ 個細胞。 下圖和以下程式碼模擬了細胞分裂的過程,時間複雜度為 $O(2^n)$ 。請注意,輸入 $n$ 表示分裂輪數,返回值 `count` 表示總分裂次數。 ```src [file]{time_complexity}-[class]{}-[func]{exponential} ``` ![指數階的時間複雜度](time_complexity.assets/time_complexity_exponential.png) 在實際演算法中,指數階常出現於遞迴函式中。例如在以下程式碼中,其遞迴地一分為二,經過 $n$ 次分裂後停止: ```src [file]{time_complexity}-[class]{}-[func]{exp_recur} ``` 指數階增長非常迅速,在窮舉法(暴力搜尋、回溯等)中比較常見。對於資料規模較大的問題,指數階是不可接受的,通常需要使用動態規劃或貪婪演算法等來解決。 ### 對數階 $O(\log n)$ 與指數階相反,對數階反映了“每輪縮減到一半”的情況。設輸入資料大小為 $n$ ,由於每輪縮減到一半,因此迴圈次數是 $\log_2 n$ ,即 $2^n$ 的反函式。 下圖和以下程式碼模擬了“每輪縮減到一半”的過程,時間複雜度為 $O(\log_2 n)$ ,簡記為 $O(\log n)$ : ```src [file]{time_complexity}-[class]{}-[func]{logarithmic} ``` ![對數階的時間複雜度](time_complexity.assets/time_complexity_logarithmic.png) 與指數階類似,對數階也常出現於遞迴函式中。以下程式碼形成了一棵高度為 $\log_2 n$ 的遞迴樹: ```src [file]{time_complexity}-[class]{}-[func]{log_recur} ``` 對數階常出現於基於分治策略的演算法中,體現了“一分為多”和“化繁為簡”的演算法思想。它增長緩慢,是僅次於常數階的理想的時間複雜度。 !!! tip "$O(\log n)$ 的底數是多少?" 準確來說,“一分為 $m$”對應的時間複雜度是 $O(\log_m n)$ 。而透過對數換底公式,我們可以得到具有不同底數、相等的時間複雜度: $$ O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n) $$ 也就是說,底數 $m$ 可以在不影響複雜度的前提下轉換。因此我們通常會省略底數 $m$ ,將對數階直接記為 $O(\log n)$ 。 ### 線性對數階 $O(n \log n)$ 線性對數階常出現於巢狀迴圈中,兩層迴圈的時間複雜度分別為 $O(\log n)$ 和 $O(n)$ 。相關程式碼如下: ```src [file]{time_complexity}-[class]{}-[func]{linear_log_recur} ``` 下圖展示了線性對數階的生成方式。二元樹的每一層的操作總數都為 $n$ ,樹共有 $\log_2 n + 1$ 層,因此時間複雜度為 $O(n \log n)$ 。 ![線性對數階的時間複雜度](time_complexity.assets/time_complexity_logarithmic_linear.png) 主流排序演算法的時間複雜度通常為 $O(n \log n)$ ,例如快速排序、合併排序、堆積排序等。 ### 階乘階 $O(n!)$ 階乘階對應數學上的“全排列”問題。給定 $n$ 個互不重複的元素,求其所有可能的排列方案,方案數量為: $$ n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1 $$ 階乘通常使用遞迴實現。如下圖和以下程式碼所示,第一層分裂出 $n$ 個,第二層分裂出 $n - 1$ 個,以此類推,直至第 $n$ 層時停止分裂: ```src [file]{time_complexity}-[class]{}-[func]{factorial_recur} ``` ![階乘階的時間複雜度](time_complexity.assets/time_complexity_factorial.png) 請注意,因為當 $n \geq 4$ 時恆有 $n! > 2^n$ ,所以階乘階比指數階增長得更快,在 $n$ 較大時也是不可接受的。 ## 最差、最佳、平均時間複雜度 **演算法的時間效率往往不是固定的,而是與輸入資料的分佈有關**。假設輸入一個長度為 $n$ 的陣列 `nums` ,其中 `nums` 由從 $1$ 至 $n$ 的數字組成,每個數字只出現一次;但元素順序是隨機打亂的,任務目標是返回元素 $1$ 的索引。我們可以得出以下結論。 - 當 `nums = [?, ?, ..., 1]` ,即當末尾元素是 $1$ 時,需要完整走訪陣列,**達到最差時間複雜度 $O(n)$** 。 - 當 `nums = [1, ?, ?, ...]` ,即當首個元素為 $1$ 時,無論陣列多長都不需要繼續走訪,**達到最佳時間複雜度 $\Omega(1)$** 。 “最差時間複雜度”對應函式漸近上界,使用大 $O$ 記號表示。相應地,“最佳時間複雜度”對應函式漸近下界,用 $\Omega$ 記號表示: ```src [file]{worst_best_time_complexity}-[class]{}-[func]{find_one} ``` 值得說明的是,我們在實際中很少使用最佳時間複雜度,因為通常只有在很小機率下才能達到,可能會帶來一定的誤導性。**而最差時間複雜度更為實用,因為它給出了一個效率安全值**,讓我們可以放心地使用演算法。 從上述示例可以看出,最差時間複雜度和最佳時間複雜度只出現於“特殊的資料分佈”,這些情況的出現機率可能很小,並不能真實地反映演算法執行效率。相比之下,**平均時間複雜度可以體現演算法在隨機輸入資料下的執行效率**,用 $\Theta$ 記號來表示。 對於部分演算法,我們可以簡單地推算出隨機資料分佈下的平均情況。比如上述示例,由於輸入陣列是被打亂的,因此元素 $1$ 出現在任意索引的機率都是相等的,那麼演算法的平均迴圈次數就是陣列長度的一半 $n / 2$ ,平均時間複雜度為 $\Theta(n / 2) = \Theta(n)$ 。 但對於較為複雜的演算法,計算平均時間複雜度往往比較困難,因為很難分析出在資料分佈下的整體數學期望。在這種情況下,我們通常使用最差時間複雜度作為演算法效率的評判標準。 !!! question "為什麼很少看到 $\Theta$ 符號?" 可能由於 $O$ 符號過於朗朗上口,因此我們常常使用它來表示平均時間複雜度。但從嚴格意義上講,這種做法並不規範。在本書和其他資料中,若遇到類似“平均時間複雜度 $O(n)$”的表述,請將其直接理解為 $\Theta(n)$ 。 ================================================ FILE: zh-hant/docs/chapter_data_structure/basic_data_types.md ================================================ # 基本資料型別 當談及計算機中的資料時,我們會想到文字、圖片、影片、語音、3D 模型等各種形式。儘管這些資料的組織形式各異,但它們都由各種基本資料型別構成。 **基本資料型別是 CPU 可以直接進行運算的型別**,在演算法中直接被使用,主要包括以下幾種。 - 整數型別 `byte`、`short`、`int`、`long` 。 - 浮點數型別 `float`、`double` ,用於表示小數。 - 字元型別 `char` ,用於表示各種語言的字母、標點符號甚至表情符號等。 - 布林型別 `bool` ,用於表示“是”與“否”判斷。 **基本資料型別以二進位制的形式儲存在計算機中**。一個二進位制位即為 $1$ 位元。在絕大多數現代作業系統中,$1$ 位元組(byte)由 $8$ 位元(bit)組成。 基本資料型別的取值範圍取決於其佔用的空間大小。下面以 Java 為例。 - 整數型別 `byte` 佔用 $1$ 位元組 = $8$ 位元 ,可以表示 $2^{8}$ 個數字。 - 整數型別 `int` 佔用 $4$ 位元組 = $32$ 位元 ,可以表示 $2^{32}$ 個數字。 下表列舉了 Java 中各種基本資料型別的佔用空間、取值範圍和預設值。此表格無須死記硬背,大致理解即可,需要時可以透過查表來回憶。

  基本資料型別的佔用空間和取值範圍

| 型別 | 符號 | 佔用空間 | 最小值 | 最大值 | 預設值 | | ------ | -------- | -------- | ------------------------ | ----------------------- | -------------- | | 整數 | `byte` | 1 位元組 | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | $0$ | | | `short` | 2 位元組 | $-2^{15}$ | $2^{15} - 1$ | $0$ | | | `int` | 4 位元組 | $-2^{31}$ | $2^{31} - 1$ | $0$ | | | `long` | 8 位元組 | $-2^{63}$ | $2^{63} - 1$ | $0$ | | 浮點數 | `float` | 4 位元組 | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ | | | `double` | 8 位元組 | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | $0.0$ | | 字元 | `char` | 2 位元組 | $0$ | $2^{16} - 1$ | $0$ | | 布林 | `bool` | 1 位元組 | $\text{false}$ | $\text{true}$ | $\text{false}$ | 請注意,上表針對的是 Java 的基本資料型別的情況。每種程式語言都有各自的資料型別定義,它們的佔用空間、取值範圍和預設值可能會有所不同。 - 在 Python 中,整數型別 `int` 可以是任意大小,只受限於可用記憶體;浮點數 `float` 是雙精度 64 位;沒有 `char` 型別,單個字元實際上是長度為 1 的字串 `str` 。 - C 和 C++ 未明確規定基本資料型別的大小,而因實現和平臺各異。上表遵循 LP64 [資料模型](https://en.cppreference.com/w/cpp/language/types#Properties),其用於包括 Linux 和 macOS 在內的 Unix 64 位作業系統。 - 字元 `char` 的大小在 C 和 C++ 中為 1 位元組,在大多數程式語言中取決於特定的字元編碼方法,詳見“字元編碼”章節。 - 即使表示布林量僅需 1 位($0$ 或 $1$),它在記憶體中通常也儲存為 1 位元組。這是因為現代計算機 CPU 通常將 1 位元組作為最小定址記憶體單元。 那麼,基本資料型別與資料結構之間有什麼關聯呢?我們知道,資料結構是在計算機中組織與儲存資料的方式。這句話的主語是“結構”而非“資料”。 如果想表示“一排數字”,我們自然會想到使用陣列。這是因為陣列的線性結構可以表示數字的相鄰關係和順序關係,但至於儲存的內容是整數 `int`、小數 `float` 還是字元 `char` ,則與“資料結構”無關。 換句話說,**基本資料型別提供了資料的“內容型別”,而資料結構提供了資料的“組織方式”**。例如以下程式碼,我們用相同的資料結構(陣列)來儲存與表示不同的基本資料型別,包括 `int`、`float`、`char`、`bool` 等。 === "Python" ```python title="" # 使用多種基本資料型別來初始化陣列 numbers: list[int] = [0] * 5 decimals: list[float] = [0.0] * 5 # Python 的字元實際上是長度為 1 的字串 characters: list[str] = ['0'] * 5 bools: list[bool] = [False] * 5 # Python 的串列可以自由儲存各種基本資料型別和物件引用 data = [0, 0.0, 'a', False, ListNode(0)] ``` === "C++" ```cpp title="" // 使用多種基本資料型別來初始化陣列 int numbers[5]; float decimals[5]; char characters[5]; bool bools[5]; ``` === "Java" ```java title="" // 使用多種基本資料型別來初始化陣列 int[] numbers = new int[5]; float[] decimals = new float[5]; char[] characters = new char[5]; boolean[] bools = new boolean[5]; ``` === "C#" ```csharp title="" // 使用多種基本資料型別來初始化陣列 int[] numbers = new int[5]; float[] decimals = new float[5]; char[] characters = new char[5]; bool[] bools = new bool[5]; ``` === "Go" ```go title="" // 使用多種基本資料型別來初始化陣列 var numbers = [5]int{} var decimals = [5]float64{} var characters = [5]byte{} var bools = [5]bool{} ``` === "Swift" ```swift title="" // 使用多種基本資料型別來初始化陣列 let numbers = Array(repeating: 0, count: 5) let decimals = Array(repeating: 0.0, count: 5) let characters: [Character] = Array(repeating: "a", count: 5) let bools = Array(repeating: false, count: 5) ``` === "JS" ```javascript title="" // JavaScript 的陣列可以自由儲存各種基本資料型別和物件 const array = [0, 0.0, 'a', false]; ``` === "TS" ```typescript title="" // 使用多種基本資料型別來初始化陣列 const numbers: number[] = []; const characters: string[] = []; const bools: boolean[] = []; ``` === "Dart" ```dart title="" // 使用多種基本資料型別來初始化陣列 List numbers = List.filled(5, 0); List decimals = List.filled(5, 0.0); List characters = List.filled(5, 'a'); List bools = List.filled(5, false); ``` === "Rust" ```rust title="" // 使用多種基本資料型別來初始化陣列 let numbers: Vec = vec![0; 5]; let decimals: Vec = vec![0.0; 5]; let characters: Vec = vec!['0'; 5]; let bools: Vec = vec![false; 5]; ``` === "C" ```c title="" // 使用多種基本資料型別來初始化陣列 int numbers[10]; float decimals[10]; char characters[10]; bool bools[10]; ``` === "Kotlin" ```kotlin title="" // 使用多種基本資料型別來初始化陣列 val numbers = IntArray(5) val decinals = FloatArray(5) val characters = CharArray(5) val bools = BooleanArray(5) ``` === "Ruby" ```ruby title="" # Ruby 的串列可以自由儲存各種基本資料型別和物件引用 data = [0, 0.0, 'a', false, ListNode(0)] ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%A8%AE%E5%9F%BA%E6%9C%AC%E8%B3%87%E6%96%99%E5%9E%8B%E5%88%A5%E4%BE%86%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20%2A%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20%2A%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%AD%97%E5%85%83%E5%AF%A6%E9%9A%9B%E4%B8%8A%E6%98%AF%E9%95%B7%E5%BA%A6%E7%82%BA%201%20%E7%9A%84%E5%AD%97%E4%B8%B2%0A%20%20%20%20characters%20%3D%20%5B%270%27%5D%20%2A%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20%2A%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E4%B8%B2%E5%88%97%E5%8F%AF%E4%BB%A5%E8%87%AA%E7%94%B1%E5%84%B2%E5%AD%98%E5%90%84%E7%A8%AE%E5%9F%BA%E6%9C%AC%E8%B3%87%E6%96%99%E5%9E%8B%E5%88%A5%E5%92%8C%E7%89%A9%E4%BB%B6%E5%BC%95%E7%94%A8%0A%20%20%20%20data%20%3D%20%5B0%2C%200.0%2C%20%27a%27%2C%20False%2C%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ================================================ FILE: zh-hant/docs/chapter_data_structure/character_encoding.md ================================================ # 字元編碼 * 在計算機中,所有資料都是以二進位制數的形式儲存的,字元 `char` 也不例外。為了表示字元,我們需要建立一套“字元集”,規定每個字元和二進位制數之間的一一對應關係。有了字元集之後,計算機就可以透過查表完成二進位制數到字元的轉換。 ## ASCII 字元集 ASCII 碼是最早出現的字元集,其全稱為 American Standard Code for Information Interchange(美國標準資訊交換程式碼)。它使用 7 位二進位制數(一個位元組的低 7 位)表示一個字元,最多能夠表示 128 個不同的字元。如下圖所示,ASCII 碼包括英文字母的大小寫、數字 0 ~ 9、一些標點符號,以及一些控制字元(如換行符和製表符)。 ![ASCII 碼](character_encoding.assets/ascii_table.png) 然而,**ASCII 碼僅能夠表示英文**。隨著計算機的全球化,誕生了一種能夠表示更多語言的 EASCII 字元集。它在 ASCII 的 7 位基礎上擴展到 8 位,能夠表示 256 個不同的字元。 在世界範圍內,陸續出現了一批適用於不同地區的 EASCII 字元集。這些字元集的前 128 個字元統一為 ASCII 碼,後 128 個字元定義不同,以適應不同語言的需求。 ## GBK 字元集 後來人們發現,**EASCII 碼仍然無法滿足許多語言的字元數量要求**。比如漢字有近十萬個,光日常使用的就有幾千個。中國國家標準總局於 1980 年釋出了 GB2312 字元集,其收錄了 6763 個漢字,基本滿足了漢字的計算機處理需要。 然而,GB2312 無法處理部分罕見字和繁體字。GBK 字元集是在 GB2312 的基礎上擴展得到的,它共收錄了 21886 個漢字。在 GBK 的編碼方案中,ASCII 字元使用一個位元組表示,漢字使用兩個位元組表示。 ## Unicode 字元集 隨著計算機技術的蓬勃發展,字元集與編碼標準百花齊放,而這帶來了許多問題。一方面,這些字元集一般只定義了特定語言的字元,無法在多語言環境下正常工作。另一方面,同一種語言存在多種字元集標準,如果兩臺計算機使用的是不同的編碼標準,則在資訊傳遞時就會出現亂碼。 那個時代的研究人員就在想:**如果推出一個足夠完整的字元集,將世界範圍內的所有語言和符號都收錄其中,不就可以解決跨語言環境和亂碼問題了嗎**?在這種想法的驅動下,一個大而全的字元集 Unicode 應運而生。 Unicode 的中文名稱為“統一碼”,理論上能容納 100 多萬個字元。它致力於將全球範圍內的字元納入統一的字元集之中,提供一種通用的字元集來處理和顯示各種語言文字,減少因為編碼標準不同而產生的亂碼問題。 自 1991 年釋出以來,Unicode 不斷擴充新的語言與字元。截至 2022 年 9 月,Unicode 已經包含 149186 個字元,包括各種語言的字元、符號甚至表情符號等。在龐大的 Unicode 字元集中,常用的字元佔用 2 位元組,有些生僻的字元佔用 3 位元組甚至 4 位元組。 Unicode 是一種通用字元集,本質上是給每個字元分配一個編號(稱為“碼點”),**但它並沒有規定在計算機中如何儲存這些字元碼點**。我們不禁會問:當多種長度的 Unicode 碼點同時出現在一個文字中時,系統如何解析字元?例如給定一個長度為 2 位元組的編碼,系統如何確認它是一個 2 位元組的字元還是兩個 1 位元組的字元? 對於以上問題,**一種直接的解決方案是將所有字元儲存為等長的編碼**。如下圖所示,“Hello”中的每個字元佔用 1 位元組,“演算法”中的每個字元佔用 2 位元組。我們可以透過高位填 0 將“Hello 演算法”中的所有字元都編碼為 2 位元組長度。這樣系統就可以每隔 2 位元組解析一個字元,恢復這個短語的內容了。 ![Unicode 編碼示例](character_encoding.assets/unicode_hello_algo.png) 然而 ASCII 碼已經向我們證明,編碼英文只需 1 位元組。若採用上述方案,英文文字佔用空間的大小將會是 ASCII 編碼下的兩倍,非常浪費記憶體空間。因此,我們需要一種更加高效的 Unicode 編碼方法。 ## UTF-8 編碼 目前,UTF-8 已成為國際上使用最廣泛的 Unicode 編碼方法。**它是一種可變長度的編碼**,使用 1 到 4 位元組來表示一個字元,根據字元的複雜性而變。ASCII 字元只需 1 位元組,拉丁字母和希臘字母需要 2 位元組,常用的中文字元需要 3 位元組,其他的一些生僻字元需要 4 位元組。 UTF-8 的編碼規則並不複雜,分為以下兩種情況。 - 對於長度為 1 位元組的字元,將最高位設定為 $0$ ,其餘 7 位設定為 Unicode 碼點。值得注意的是,ASCII 字元在 Unicode 字元集中佔據了前 128 個碼點。也就是說,**UTF-8 編碼可以向下相容 ASCII 碼**。這意味著我們可以使用 UTF-8 來解析年代久遠的 ASCII 碼文字。 - 對於長度為 $n$ 位元組的字元(其中 $n > 1$),將首個位元組的高 $n$ 位都設定為 $1$ ,第 $n + 1$ 位設定為 $0$ ;從第二個位元組開始,將每個位元組的高 2 位都設定為 $10$ ;其餘所有位用於填充字元的 Unicode 碼點。 下圖展示了“Hello演算法”對應的 UTF-8 編碼。觀察發現,由於最高 $n$ 位都設定為 $1$ ,因此系統可以透過讀取最高位 $1$ 的個數來解析出字元的長度為 $n$ 。 但為什麼要將其餘所有位元組的高 2 位都設定為 $10$ 呢?實際上,這個 $10$ 能夠起到校驗符的作用。假設系統從一個錯誤的位元組開始解析文字,位元組頭部的 $10$ 能夠幫助系統快速判斷出異常。 之所以將 $10$ 當作校驗符,是因為在 UTF-8 編碼規則下,不可能有字元的最高兩位是 $10$ 。這個結論可以用反證法來證明:假設一個字元的最高兩位是 $10$ ,說明該字元的長度為 $1$ ,對應 ASCII 碼。而 ASCII 碼的最高位應該是 $0$ ,與假設矛盾。 ![UTF-8 編碼示例](character_encoding.assets/utf-8_hello_algo.png) 除了 UTF-8 之外,常見的編碼方式還包括以下兩種。 - **UTF-16 編碼**:使用 2 或 4 位元組來表示一個字元。所有的 ASCII 字元和常用的非英文字元,都用 2 位元組表示;少數字符需要用到 4 位元組表示。對於 2 位元組的字元,UTF-16 編碼與 Unicode 碼點相等。 - **UTF-32 編碼**:每個字元都使用 4 位元組。這意味著 UTF-32 比 UTF-8 和 UTF-16 更佔用空間,特別是對於 ASCII 字元佔比較高的文字。 從儲存空間佔用的角度看,使用 UTF-8 表示英文字元非常高效,因為它僅需 1 位元組;使用 UTF-16 編碼某些非英文字元(例如中文)會更加高效,因為它僅需 2 位元組,而 UTF-8 可能需要 3 位元組。 從相容性的角度看,UTF-8 的通用性最佳,許多工具和庫優先支持 UTF-8 。 ## 程式語言的字元編碼 對於以往的大多數程式語言,程式執行中的字串都採用 UTF-16 或 UTF-32 這類等長編碼。在等長編碼下,我們可以將字串看作陣列來處理,這種做法具有以下優點。 - **隨機訪問**:UTF-16 編碼的字串可以很容易地進行隨機訪問。UTF-8 是一種變長編碼,要想找到第 $i$ 個字元,我們需要從字串的開始處走訪到第 $i$ 個字元,這需要 $O(n)$ 的時間。 - **字元計數**:與隨機訪問類似,計算 UTF-16 編碼的字串的長度也是 $O(1)$ 的操作。但是,計算 UTF-8 編碼的字串的長度需要走訪整個字串。 - **字串操作**:在 UTF-16 編碼的字串上,很多字串操作(如分割、連線、插入、刪除等)更容易進行。在 UTF-8 編碼的字串上,進行這些操作通常需要額外的計算,以確保不會產生無效的 UTF-8 編碼。 實際上,程式語言的字元編碼方案設計是一個很有趣的話題,涉及許多因素。 - Java 的 `String` 型別使用 UTF-16 編碼,每個字元佔用 2 位元組。這是因為 Java 語言設計之初,人們認為 16 位足以表示所有可能的字元。然而,這是一個不正確的判斷。後來 Unicode 規範擴展到了超過 16 位,所以 Java 中的字元現在可能由一對 16 位的值(稱為“代理對”)表示。 - JavaScript 和 TypeScript 的字串使用 UTF-16 編碼的原因與 Java 類似。當 1995 年 Netscape 公司首次推出 JavaScript 語言時,Unicode 還處於發展早期,那時候使用 16 位的編碼就足以表示所有的 Unicode 字元了。 - C# 使用 UTF-16 編碼,主要是因為 .NET 平臺是由 Microsoft 設計的,而 Microsoft 的很多技術(包括 Windows 作業系統)都廣泛使用 UTF-16 編碼。 由於以上程式語言對字元數量的低估,它們不得不採取“代理對”的方式來表示超過 16 位長度的 Unicode 字元。這是一個不得已為之的無奈之舉。一方面,包含代理對的字串中,一個字元可能佔用 2 位元組或 4 位元組,從而喪失了等長編碼的優勢。另一方面,處理代理對需要額外增加程式碼,這提高了程式設計的複雜性和除錯難度。 出於以上原因,部分程式語言提出了一些不同的編碼方案。 - Python 中的 `str` 使用 Unicode 編碼,並採用一種靈活的字串表示,儲存的字元長度取決於字串中最大的 Unicode 碼點。若字串中全部是 ASCII 字元,則每個字元佔用 1 位元組;如果有字元超出了 ASCII 範圍,但全部在基本多語言平面(BMP)內,則每個字元佔用 2 位元組;如果有超出 BMP 的字元,則每個字元佔用 4 位元組。 - Go 語言的 `string` 型別在內部使用 UTF-8 編碼。Go 語言還提供了 `rune` 型別,它用於表示單個 Unicode 碼點。 - Rust 語言的 `str` 和 `String` 型別在內部使用 UTF-8 編碼。Rust 也提供了 `char` 型別,用於表示單個 Unicode 碼點。 需要注意的是,以上討論的都是字串在程式語言中的儲存方式,**這和字串如何在檔案中儲存或在網路中傳輸是不同的問題**。在檔案儲存或網路傳輸中,我們通常會將字串編碼為 UTF-8 格式,以達到最優的相容性和空間效率。 ================================================ FILE: zh-hant/docs/chapter_data_structure/classification_of_data_structure.md ================================================ # 資料結構分類 常見的資料結構包括陣列、鏈結串列、堆疊、佇列、雜湊表、樹、堆積、圖,它們可以從“邏輯結構”和“物理結構”兩個維度進行分類。 ## 邏輯結構:線性與非線性 **邏輯結構揭示了資料元素之間的邏輯關係**。在陣列和鏈結串列中,資料按照一定順序排列,體現了資料之間的線性關係;而在樹中,資料從頂部向下按層次排列,表現出“祖先”與“後代”之間的派生關係;圖則由節點和邊構成,反映了複雜的網路關係。 如下圖所示,邏輯結構可分為“線性”和“非線性”兩大類。線性結構比較直觀,指資料在邏輯關係上呈線性排列;非線性結構則相反,呈非線性排列。 - **線性資料結構**:陣列、鏈結串列、堆疊、佇列、雜湊表,元素之間是一對一的順序關係。 - **非線性資料結構**:樹、堆積、圖、雜湊表。 非線性資料結構可以進一步劃分為樹形結構和網狀結構。 - **樹形結構**:樹、堆積、雜湊表,元素之間是一對多的關係。 - **網狀結構**:圖,元素之間是多對多的關係。 ![線性資料結構與非線性資料結構](classification_of_data_structure.assets/classification_logic_structure.png) ## 物理結構:連續與分散 **當演算法程式執行時,正在處理的資料主要儲存在記憶體中**。下圖展示了一個計算機記憶體條,其中每個黑色方塊都包含一塊記憶體空間。我們可以將記憶體想象成一個巨大的 Excel 表格,其中每個單元格都可以儲存一定大小的資料。 **系統透過記憶體位址來訪問目標位置的資料**。如下圖所示,計算機根據特定規則為表格中的每個單元格分配編號,確保每個記憶體空間都有唯一的記憶體位址。有了這些位址,程式便可以訪問記憶體中的資料。 ![記憶體條、記憶體空間、記憶體位址](classification_of_data_structure.assets/computer_memory_location.png) !!! tip 值得說明的是,將記憶體比作 Excel 表格是一個簡化的類比,實際記憶體的工作機制比較複雜,涉及位址空間、記憶體管理、快取機制、虛擬記憶體和物理記憶體等概念。 記憶體是所有程式的共享資源,當某塊記憶體被某個程式佔用時,則通常無法被其他程式同時使用了。**因此在資料結構與演算法的設計中,記憶體資源是一個重要的考慮因素**。比如,演算法所佔用的記憶體峰值不應超過系統剩餘空閒記憶體;如果缺少連續大塊的記憶體空間,那麼所選用的資料結構必須能夠儲存在分散的記憶體空間內。 如下圖所示,**物理結構反映了資料在計算機記憶體中的儲存方式**,可分為連續空間儲存(陣列)和分散空間儲存(鏈結串列)。物理結構從底層決定了資料的訪問、更新、增刪等操作方法,兩種物理結構在時間效率和空間效率方面呈現出互補的特點。 ![連續空間儲存與分散空間儲存](classification_of_data_structure.assets/classification_phisical_structure.png) 值得說明的是,**所有資料結構都是基於陣列、鏈結串列或二者的組合實現的**。例如,堆疊和佇列既可以使用陣列實現,也可以使用鏈結串列實現;而雜湊表的實現可能同時包含陣列和鏈結串列。 - **基於陣列可實現**:堆疊、佇列、雜湊表、樹、堆積、圖、矩陣、張量(維度 $\geq 3$ 的陣列)等。 - **基於鏈結串列可實現**:堆疊、佇列、雜湊表、樹、堆積、圖等。 鏈結串列在初始化後,仍可以在程式執行過程中對其長度進行調整,因此也稱“動態資料結構”。陣列在初始化後長度不可變,因此也稱“靜態資料結構”。值得注意的是,陣列可透過重新分配記憶體實現長度變化,從而具備一定的“動態性”。 !!! tip 如果你感覺物理結構理解起來有困難,建議先閱讀下一章,然後再回顧本節內容。 ================================================ FILE: zh-hant/docs/chapter_data_structure/index.md ================================================ # 資料結構 ![資料結構](../assets/covers/chapter_data_structure.jpg) !!! abstract 資料結構如同一副穩固而多樣的框架。 它為資料的有序組織提供了藍圖,演算法得以在此基礎上生動起來。 ================================================ FILE: zh-hant/docs/chapter_data_structure/number_encoding.md ================================================ # 數字編碼 * !!! tip 在本書中,標題帶有 * 符號的是選讀章節。如果你時間有限或感到理解困難,可以先跳過,等學完必讀章節後再單獨攻克。 ## 原碼、一補數和二補數 在上一節的表格中我們發現,所有整數型別能夠表示的負數都比正數多一個,例如 `byte` 的取值範圍是 $[-128, 127]$ 。這個現象比較反直覺,它的內在原因涉及原碼、一補數、二補數的相關知識。 首先需要指出,**數字是以“二補數”的形式儲存在計算機中的**。在分析這樣做的原因之前,首先給出三者的定義。 - **原碼**:我們將數字的二進位制表示的最高位視為符號位,其中 $0$ 表示正數,$1$ 表示負數,其餘位表示數字的值。 - **一補數**:正數的一補數與其原碼相同,負數的一補數是對其原碼除符號位外的所有位取反。 - **二補數**:正數的二補數與其原碼相同,負數的二補數是在其一補數的基礎上加 $1$ 。 下圖展示了原碼、一補數和二補數之間的轉換方法。 ![原碼、一補數與二補數之間的相互轉換](number_encoding.assets/1s_2s_complement.png) 原碼(sign-magnitude)雖然最直觀,但存在一些侷限性。一方面,**負數的原碼不能直接用於運算**。例如在原碼下計算 $1 + (-2)$ ,得到的結果是 $-3$ ,這顯然是不對的。 $$ \begin{aligned} & 1 + (-2) \newline & \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline & = 1000 \; 0011 \newline & \rightarrow -3 \end{aligned} $$ 為了解決此問題,計算機引入了一補數(1's complement)。如果我們先將原碼轉換為一補數,並在一補數下計算 $1 + (-2)$ ,最後將結果從一補數轉換回原碼,則可得到正確結果 $-1$ 。 $$ \begin{aligned} & 1 + (-2) \newline & \rightarrow 0000 \; 0001 \; \text{(原碼)} + 1000 \; 0010 \; \text{(原碼)} \newline & = 0000 \; 0001 \; \text{(一補數)} + 1111 \; 1101 \; \text{(一補數)} \newline & = 1111 \; 1110 \; \text{(一補數)} \newline & = 1000 \; 0001 \; \text{(原碼)} \newline & \rightarrow -1 \end{aligned} $$ 另一方面,**數字零的原碼有 $+0$ 和 $-0$ 兩種表示方式**。這意味著數字零對應兩個不同的二進位制編碼,這可能會帶來歧義。比如在條件判斷中,如果沒有區分正零和負零,則可能會導致判斷結果出錯。而如果我們想處理正零和負零歧義,則需要引入額外的判斷操作,這可能會降低計算機的運算效率。 $$ \begin{aligned} +0 & \rightarrow 0000 \; 0000 \newline -0 & \rightarrow 1000 \; 0000 \end{aligned} $$ 與原碼一樣,一補數也存在正負零歧義問題,因此計算機進一步引入了二補數(2's complement)。我們先來觀察一下負零的原碼、一補數、二補數的轉換過程: $$ \begin{aligned} -0 \rightarrow \; & 1000 \; 0000 \; \text{(原碼)} \newline = \; & 1111 \; 1111 \; \text{(一補數)} \newline = 1 \; & 0000 \; 0000 \; \text{(二補數)} \newline \end{aligned} $$ 在負零的一補數基礎上加 $1$ 會產生進位,但 `byte` 型別的長度只有 8 位,因此溢位到第 9 位的 $1$ 會被捨棄。也就是說,**負零的二補數為 $0000 \; 0000$ ,與正零的二補數相同**。這意味著在二補數表示中只存在一個零,正負零歧義從而得到解決。 還剩最後一個疑惑:`byte` 型別的取值範圍是 $[-128, 127]$ ,多出來的一個負數 $-128$ 是如何得到的呢?我們注意到,區間 $[-127, +127]$ 內的所有整數都有對應的原碼、一補數和二補數,並且原碼和二補數之間可以互相轉換。 然而,**二補數 $1000 \; 0000$ 是一個例外,它並沒有對應的原碼**。根據轉換方法,我們得到該二補數的原碼為 $0000 \; 0000$ 。這顯然是矛盾的,因為該原碼表示數字 $0$ ,它的二補數應該是自身。計算機規定這個特殊的二補數 $1000 \; 0000$ 代表 $-128$ 。實際上,$(-1) + (-127)$ 在二補數下的計算結果就是 $-128$ 。 $$ \begin{aligned} & (-127) + (-1) \newline & \rightarrow 1111 \; 1111 \; \text{(原碼)} + 1000 \; 0001 \; \text{(原碼)} \newline & = 1000 \; 0000 \; \text{(一補數)} + 1111 \; 1110 \; \text{(一補數)} \newline & = 1000 \; 0001 \; \text{(二補數)} + 1111 \; 1111 \; \text{(二補數)} \newline & = 1000 \; 0000 \; \text{(二補數)} \newline & \rightarrow -128 \end{aligned} $$ 你可能已經發現了,上述所有計算都是加法運算。這暗示著一個重要事實:**計算機內部的硬體電路主要是基於加法運算設計的**。這是因為加法運算相對於其他運算(比如乘法、除法和減法)來說,硬體實現起來更簡單,更容易進行並行化處理,運算速度更快。 請注意,這並不意味著計算機只能做加法。**透過將加法與一些基本邏輯運算結合,計算機能夠實現各種其他的數學運算**。例如,計算減法 $a - b$ 可以轉換為計算加法 $a + (-b)$ ;計算乘法和除法可以轉換為計算多次加法或減法。 現在我們可以總結出計算機使用二補數的原因:基於二補數表示,計算機可以用同樣的電路和操作來處理正數和負數的加法,不需要設計特殊的硬體電路來處理減法,並且無須特別處理正負零的歧義問題。這大大簡化了硬體設計,提高了運算效率。 二補數的設計非常精妙,因篇幅關係我們就先介紹到這裡,建議有興趣的讀者進一步深入瞭解。 ## 浮點數編碼 細心的你可能會發現:`int` 和 `float` 長度相同,都是 4 位元組 ,但為什麼 `float` 的取值範圍遠大於 `int` ?這非常反直覺,因為按理說 `float` 需要表示小數,取值範圍應該變小才對。 實際上,**這是因為浮點數 `float` 採用了不同的表示方式**。記一個 32 位元長度的二進位制數為: $$ b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0 $$ 根據 IEEE 754 標準,32-bit 長度的 `float` 由以下三個部分構成。 - 符號位 $\mathrm{S}$ :佔 1 位 ,對應 $b_{31}$ 。 - 指數位 $\mathrm{E}$ :佔 8 位 ,對應 $b_{30} b_{29} \ldots b_{23}$ 。 - 分數位 $\mathrm{N}$ :佔 23 位 ,對應 $b_{22} b_{21} \ldots b_0$ 。 二進位制數 `float` 對應值的計算方法為: $$ \text {val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2-127} \times\left(1 . b_{22} b_{21} \ldots b_0\right)_2 $$ 轉化到十進位制下的計算公式為: $$ \text {val}=(-1)^{\mathrm{S}} \times 2^{\mathrm{E} -127} \times (1 + \mathrm{N}) $$ 其中各項的取值範圍為: $$ \begin{aligned} \mathrm{S} \in & \{ 0, 1\}, \quad \mathrm{E} \in \{ 1, 2, \dots, 254 \} \newline (1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} 2^{-i}) \subset [1, 2 - 2^{-23}] \end{aligned} $$ ![IEEE 754 標準下的 float 的計算示例](number_encoding.assets/ieee_754_float.png) 觀察上圖,給定一個示例資料 $\mathrm{S} = 0$ , $\mathrm{E} = 124$ ,$\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ ,則有: $$ \text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875 $$ 現在我們可以回答最初的問題:**`float` 的表示方式包含指數位,導致其取值範圍遠大於 `int`** 。根據以上計算,`float` 可表示的最大正數為 $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ ,切換符號位便可得到最小負數。 **儘管浮點數 `float` 擴展了取值範圍,但其副作用是犧牲了精度**。整數型別 `int` 將全部 32 位元用於表示數字,數字是均勻分佈的;而由於指數位的存在,浮點數 `float` 的數值越大,相鄰兩個數字之間的差值就會趨向越大。 如下表所示,指數位 $\mathrm{E} = 0$ 和 $\mathrm{E} = 255$ 具有特殊含義,**用於表示零、無窮大、$\mathrm{NaN}$ 等**。

  指數位含義

| 指數位 E | 分數位 $\mathrm{N} = 0$ | 分數位 $\mathrm{N} \ne 0$ | 計算公式 | | ------------------ | ----------------------- | ------------------------- | ---------------------------------------------------------------------- | | $0$ | $\pm 0$ | 次正規數 | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ | | $1, 2, \dots, 254$ | 正規數 | 正規數 | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ | | $255$ | $\pm \infty$ | $\mathrm{NaN}$ | | 值得說明的是,次正規數顯著提升了浮點數的精度。最小正正規數為 $2^{-126}$ ,最小正次正規數為 $2^{-126} \times 2^{-23}$ 。 雙精度 `double` 也採用類似於 `float` 的表示方法,在此不做贅述。 ================================================ FILE: zh-hant/docs/chapter_data_structure/summary.md ================================================ # 小結 ### 重點回顧 - 資料結構可以從邏輯結構和物理結構兩個角度進行分類。邏輯結構描述了資料元素之間的邏輯關係,而物理結構描述了資料在計算機記憶體中的儲存方式。 - 常見的邏輯結構包括線性、樹狀和網狀等。通常我們根據邏輯結構將資料結構分為線性(陣列、鏈結串列、堆疊、佇列)和非線性(樹、圖、堆積)兩種。雜湊表的實現可能同時包含線性資料結構和非線性資料結構。 - 當程式執行時,資料被儲存在計算機記憶體中。每個記憶體空間都擁有對應的記憶體位址,程式透過這些記憶體位址訪問資料。 - 物理結構主要分為連續空間儲存(陣列)和分散空間儲存(鏈結串列)。所有資料結構都是由陣列、鏈結串列或兩者的組合實現的。 - 計算機中的基本資料型別包括整數 `byte`、`short`、`int`、`long` ,浮點數 `float`、`double` ,字元 `char` 和布林 `bool` 。它們的取值範圍取決於佔用空間大小和表示方式。 - 原碼、一補數和二補數是在計算機中編碼數字的三種方法,它們之間可以相互轉換。整數的原碼的最高位是符號位,其餘位是數字的值。 - 整數在計算機中是以二補數的形式儲存的。在二補數表示下,計算機可以對正數和負數的加法一視同仁,不需要為減法操作單獨設計特殊的硬體電路,並且不存在正負零歧義的問題。 - 浮點數的編碼由 1 位符號位、8 位指數位和 23 位分數位構成。由於存在指數位,因此浮點數的取值範圍遠大於整數,代價是犧牲了精度。 - ASCII 碼是最早出現的英文字元集,長度為 1 位元組,共收錄 127 個字元。GBK 字元集是常用的中文字元集,共收錄兩萬多個漢字。Unicode 致力於提供一個完整的字元集標準,收錄世界上各種語言的字元,從而解決由於字元編碼方法不一致而導致的亂碼問題。 - UTF-8 是最受歡迎的 Unicode 編碼方法,通用性非常好。它是一種變長的編碼方法,具有很好的擴展性,有效提升了儲存空間的使用效率。UTF-16 和 UTF-32 是等長的編碼方法。在編碼中文時,UTF-16 佔用的空間比 UTF-8 更小。Java 和 C# 等程式語言預設使用 UTF-16 編碼。 ### Q & A **Q**:為什麼雜湊表同時包含線性資料結構和非線性資料結構? 雜湊表底層是陣列,而為了解決雜湊衝突,我們可能會使用“鏈式位址”(後續“雜湊衝突”章節會講):陣列中每個桶指向一個鏈結串列,當鏈結串列長度超過一定閾值時,又可能被轉化為樹(通常為紅黑樹)。 從儲存的角度來看,雜湊表的底層是陣列,其中每一個桶槽位可能包含一個值,也可能包含一個鏈結串列或一棵樹。因此,雜湊表可能同時包含線性資料結構(陣列、鏈結串列)和非線性資料結構(樹)。 **Q**:`char` 型別的長度是 1 位元組嗎? `char` 型別的長度由程式語言採用的編碼方法決定。例如,Java、JavaScript、TypeScript、C# 都採用 UTF-16 編碼(儲存 Unicode 碼點),因此 `char` 型別的長度為 2 位元組。 **Q**:基於陣列實現的資料結構也稱“靜態資料結構” 是否有歧義?堆疊也可以進行出堆疊和入堆疊等操作,這些操作都是“動態”的。 堆疊確實可以實現動態的資料操作,但資料結構仍然是“靜態”(長度不可變)的。儘管基於陣列的資料結構可以動態地新增或刪除元素,但它們的容量是固定的。如果資料量超出了預分配的大小,就需要建立一個新的更大的陣列,並將舊陣列的內容複製到新陣列中。 **Q**:在構建堆疊(佇列)的時候,未指定它的大小,為什麼它們是“靜態資料結構”呢? 在高階程式語言中,我們無須人工指定堆疊(佇列)的初始容量,這個工作由類別內部自動完成。例如,Java 的 `ArrayList` 的初始容量通常為 10。另外,擴容操作也是自動實現的。詳見後續的“串列”章節。 **Q**:原碼轉二補數的方法是“先取反後加 1”,那麼二補數轉原碼應該是逆運算“先減 1 後取反”,而二補數轉原碼也一樣可以透過“先取反後加 1”得到,這是為什麼呢? 這是因為原碼和二補數的相互轉換實際上是計算“補數”的過程。我們先給出補數的定義:假設 $a + b = c$ ,那麼我們稱 $a$ 是 $b$ 到 $c$ 的補數,反之也稱 $b$ 是 $a$ 到 $c$ 的補數。 給定一個 $n = 4$ 位長度的二進位制數 $0010$ ,如果將這個數字看作原碼(不考慮符號位),那麼它的二補數需透過“先取反後加 1”得到: $$ 0010 \rightarrow 1101 \rightarrow 1110 $$ 我們會發現,原碼和二補數的和是 $0010 + 1110 = 10000$ ,也就是說,二補數 $1110$ 是原碼 $0010$ 到 $10000$ 的“補數”。**這意味著上述“先取反後加 1”實際上是計算到 $10000$ 的補數的過程**。 那麼,二補數 $1110$ 到 $10000$ 的“補數”是多少呢?我們依然可以用“先取反後加 1”得到它: $$ 1110 \rightarrow 0001 \rightarrow 0010 $$ 換句話說,原碼和二補數互為對方到 $10000$ 的“補數”,因此“原碼轉二補數”和“二補數轉原碼”可以用相同的操作(先取反後加 1 )實現。 當然,我們也可以用逆運算來求二補數 $1110$ 的原碼,即“先減 1 後取反”: $$ 1110 \rightarrow 1101 \rightarrow 0010 $$ 總結來看,“先取反後加 1”和“先減 1 後取反”這兩種運算都是在計算到 $10000$ 的補數,它們是等價的。 本質上看,“取反”操作實際上是求到 $1111$ 的補數(因為恆有 `原碼 + 一補數 = 1111`);而在一補數基礎上再加 1 得到的二補數,就是到 $10000$ 的補數。 上述以 $n = 4$ 為例,其可被推廣至任意位數的二進位制數。 ================================================ FILE: zh-hant/docs/chapter_divide_and_conquer/binary_search_recur.md ================================================ # 分治搜尋策略 我們已經學過,搜尋演算法分為兩大類。 - **暴力搜尋**:它透過走訪資料結構實現,時間複雜度為 $O(n)$ 。 - **自適應搜尋**:它利用特有的資料組織形式或先驗資訊,時間複雜度可達到 $O(\log n)$ 甚至 $O(1)$ 。 實際上,**時間複雜度為 $O(\log n)$ 的搜尋演算法通常是基於分治策略實現的**,例如二分搜尋和樹。 - 二分搜尋的每一步都將問題(在陣列中搜索目標元素)分解為一個小問題(在陣列的一半中搜索目標元素),這個過程一直持續到陣列為空或找到目標元素為止。 - 樹是分治思想的代表,在二元搜尋樹、AVL 樹、堆積等資料結構中,各種操作的時間複雜度皆為 $O(\log n)$ 。 二分搜尋的分治策略如下所示。 - **問題可以分解**:二分搜尋遞迴地將原問題(在陣列中進行查詢)分解為子問題(在陣列的一半中進行查詢),這是透過比較中間元素和目標元素來實現的。 - **子問題是獨立的**:在二分搜尋中,每輪只處理一個子問題,它不受其他子問題的影響。 - **子問題的解無須合併**:二分搜尋旨在查詢一個特定元素,因此不需要將子問題的解進行合併。當子問題得到解決時,原問題也會同時得到解決。 分治能夠提升搜尋效率,本質上是因為暴力搜尋每輪只能排除一個選項,**而分治搜尋每輪可以排除一半選項**。 ### 基於分治實現二分搜尋 在之前的章節中,二分搜尋是基於遞推(迭代)實現的。現在我們基於分治(遞迴)來實現它。 !!! question 給定一個長度為 $n$ 的有序陣列 `nums` ,其中所有元素都是唯一的,請查詢元素 `target` 。 從分治角度,我們將搜尋區間 $[i, j]$ 對應的子問題記為 $f(i, j)$ 。 以原問題 $f(0, n-1)$ 為起始點,透過以下步驟進行二分搜尋。 1. 計算搜尋區間 $[i, j]$ 的中點 $m$ ,根據它排除一半搜尋區間。 2. 遞迴求解規模減小一半的子問題,可能為 $f(i, m-1)$ 或 $f(m+1, j)$ 。 3. 迴圈第 `1.` 步和第 `2.` 步,直至找到 `target` 或區間為空時返回。 下圖展示了在陣列中二分搜尋元素 $6$ 的分治過程。 ![二分搜尋的分治過程](binary_search_recur.assets/binary_search_recur.png) 在實現程式碼中,我們宣告一個遞迴函式 `dfs()` 來求解問題 $f(i, j)$ : ```src [file]{binary_search_recur}-[class]{}-[func]{binary_search} ``` ================================================ FILE: zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.md ================================================ # 構建二元樹問題 !!! question 給定一棵二元樹的前序走訪 `preorder` 和中序走訪 `inorder` ,請從中構建二元樹,返回二元樹的根節點。假設二元樹中沒有值重複的節點(如下圖所示)。 ![構建二元樹的示例資料](build_binary_tree_problem.assets/build_tree_example.png) ### 判斷是否為分治問題 原問題定義為從 `preorder` 和 `inorder` 構建二元樹,是一個典型的分治問題。 - **問題可以分解**:從分治的角度切入,我們可以將原問題劃分為兩個子問題:構建左子樹、構建右子樹,加上一步操作:初始化根節點。而對於每棵子樹(子問題),我們仍然可以複用以上劃分方法,將其劃分為更小的子樹(子問題),直至達到最小子問題(空子樹)時終止。 - **子問題是獨立的**:左子樹和右子樹是相互獨立的,它們之間沒有交集。在構建左子樹時,我們只需關注中序走訪和前序走訪中與左子樹對應的部分。右子樹同理。 - **子問題的解可以合併**:一旦得到了左子樹和右子樹(子問題的解),我們就可以將它們連結到根節點上,得到原問題的解。 ### 如何劃分子樹 根據以上分析,這道題可以使用分治來求解,**但如何透過前序走訪 `preorder` 和中序走訪 `inorder` 來劃分左子樹和右子樹呢**? 根據定義,`preorder` 和 `inorder` 都可以劃分為三個部分。 - 前序走訪:`[ 根節點 | 左子樹 | 右子樹 ]` ,例如上圖的樹對應 `[ 3 | 9 | 2 1 7 ]` 。 - 中序走訪:`[ 左子樹 | 根節點 | 右子樹 ]` ,例如上圖的樹對應 `[ 9 | 3 | 1 2 7 ]` 。 以上圖資料為例,我們可以透過下圖所示的步驟得到劃分結果。 1. 前序走訪的首元素 3 是根節點的值。 2. 查詢根節點 3 在 `inorder` 中的索引,利用該索引可將 `inorder` 劃分為 `[ 9 | 3 | 1 2 7 ]` 。 3. 根據 `inorder` 的劃分結果,易得左子樹和右子樹的節點數量分別為 1 和 3 ,從而可將 `preorder` 劃分為 `[ 3 | 9 | 2 1 7 ]` 。 ![在前序走訪和中序走訪中劃分子樹](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png) ### 基於變數描述子樹區間 根據以上劃分方法,**我們已經得到根節點、左子樹、右子樹在 `preorder` 和 `inorder` 中的索引區間**。而為了描述這些索引區間,我們需要藉助幾個指標變數。 - 將當前樹的根節點在 `preorder` 中的索引記為 $i$ 。 - 將當前樹的根節點在 `inorder` 中的索引記為 $m$ 。 - 將當前樹在 `inorder` 中的索引區間記為 $[l, r]$ 。 如下表所示,透過以上變數即可表示根節點在 `preorder` 中的索引,以及子樹在 `inorder` 中的索引區間。

  根節點和子樹在前序走訪和中序走訪中的索引

| | 根節點在 `preorder` 中的索引 | 子樹在 `inorder` 中的索引區間 | | ------ | ---------------------------- | ----------------------------- | | 當前樹 | $i$ | $[l, r]$ | | 左子樹 | $i + 1$ | $[l, m-1]$ | | 右子樹 | $i + 1 + (m - l)$ | $[m+1, r]$ | 請注意,右子樹根節點索引中的 $(m-l)$ 的含義是“左子樹的節點數量”,建議結合下圖理解。 ![根節點和左右子樹的索引區間表示](build_binary_tree_problem.assets/build_tree_division_pointers.png) ### 程式碼實現 為了提升查詢 $m$ 的效率,我們藉助一個雜湊表 `hmap` 來儲存陣列 `inorder` 中元素到索引的對映: ```src [file]{build_tree}-[class]{}-[func]{build_tree} ``` 下圖展示了構建二元樹的遞迴過程,各個節點是在向下“遞”的過程中建立的,而各條邊(引用)是在向上“迴”的過程中建立的。 === "<1>" ![構建二元樹的遞迴過程](build_binary_tree_problem.assets/built_tree_step1.png) === "<2>" ![built_tree_step2](build_binary_tree_problem.assets/built_tree_step2.png) === "<3>" ![built_tree_step3](build_binary_tree_problem.assets/built_tree_step3.png) === "<4>" ![built_tree_step4](build_binary_tree_problem.assets/built_tree_step4.png) === "<5>" ![built_tree_step5](build_binary_tree_problem.assets/built_tree_step5.png) === "<6>" ![built_tree_step6](build_binary_tree_problem.assets/built_tree_step6.png) === "<7>" ![built_tree_step7](build_binary_tree_problem.assets/built_tree_step7.png) === "<8>" ![built_tree_step8](build_binary_tree_problem.assets/built_tree_step8.png) === "<9>" ![built_tree_step9](build_binary_tree_problem.assets/built_tree_step9.png) 每個遞迴函式內的前序走訪 `preorder` 和中序走訪 `inorder` 的劃分結果如下圖所示。 ![每個遞迴函式中的劃分結果](build_binary_tree_problem.assets/built_tree_overall.png) 設樹的節點數量為 $n$ ,初始化每一個節點(執行一個遞迴函式 `dfs()` )使用 $O(1)$ 時間。**因此總體時間複雜度為 $O(n)$** 。 雜湊表儲存 `inorder` 元素到索引的對映,空間複雜度為 $O(n)$ 。在最差情況下,即二元樹退化為鏈結串列時,遞迴深度達到 $n$ ,使用 $O(n)$ 的堆疊幀空間。**因此總體空間複雜度為 $O(n)$** 。 ================================================ FILE: zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.md ================================================ # 分治演算法 分治(divide and conquer),全稱分而治之,是一種非常重要且常見的演算法策略。分治通常基於遞迴實現,包括“分”和“治”兩個步驟。 1. **分(劃分階段)**:遞迴地將原問題分解為兩個或多個子問題,直至到達最小子問題時終止。 2. **治(合併階段)**:從已知解的最小子問題開始,從底至頂地將子問題的解進行合併,從而構建出原問題的解。 如下圖所示,“合併排序”是分治策略的典型應用之一。 1. **分**:遞迴地將原陣列(原問題)劃分為兩個子陣列(子問題),直到子陣列只剩一個元素(最小子問題)。 2. **治**:從底至頂地將有序的子陣列(子問題的解)進行合併,從而得到有序的原陣列(原問題的解)。 ![合併排序的分治策略](divide_and_conquer.assets/divide_and_conquer_merge_sort.png) ## 如何判斷分治問題 一個問題是否適合使用分治解決,通常可以參考以下幾個判斷依據。 1. **問題可以分解**:原問題可以分解成規模更小、類似的子問題,以及能夠以相同方式遞迴地進行劃分。 2. **子問題是獨立的**:子問題之間沒有重疊,互不依賴,可以獨立解決。 3. **子問題的解可以合併**:原問題的解透過合併子問題的解得來。 顯然,合併排序滿足以上三個判斷依據。 1. **問題可以分解**:遞迴地將陣列(原問題)劃分為兩個子陣列(子問題)。 2. **子問題是獨立的**:每個子陣列都可以獨立地進行排序(子問題可以獨立進行求解)。 3. **子問題的解可以合併**:兩個有序子陣列(子問題的解)可以合併為一個有序陣列(原問題的解)。 ## 透過分治提升效率 **分治不僅可以有效地解決演算法問題,往往還可以提升演算法效率**。在排序演算法中,快速排序、合併排序、堆積排序相較於選擇、冒泡、插入排序更快,就是因為它們應用了分治策略。 那麼,我們不禁發問:**為什麼分治可以提升演算法效率,其底層邏輯是什麼**?換句話說,將大問題分解為多個子問題、解決子問題、將子問題的解合併為原問題的解,這幾步的效率為什麼比直接解決原問題的效率更高?這個問題可以從操作數量和平行計算兩方面來討論。 ### 操作數量最佳化 以“泡沫排序”為例,其處理一個長度為 $n$ 的陣列需要 $O(n^2)$ 時間。假設我們按照下圖所示的方式,將陣列從中點處分為兩個子陣列,則劃分需要 $O(n)$ 時間,排序每個子陣列需要 $O((n / 2)^2)$ 時間,合併兩個子陣列需要 $O(n)$ 時間,總體時間複雜度為: $$ O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n) $$ ![劃分陣列前後的泡沫排序](divide_and_conquer.assets/divide_and_conquer_bubble_sort.png) 接下來,我們計算以下不等式,其左邊和右邊分別為劃分前和劃分後的操作總數: $$ \begin{aligned} n^2 & > \frac{n^2}{2} + 2n \newline n^2 - \frac{n^2}{2} - 2n & > 0 \newline n(n - 4) & > 0 \end{aligned} $$ **這意味著當 $n > 4$ 時,劃分後的操作數量更少,排序效率應該更高**。請注意,劃分後的時間複雜度仍然是平方階 $O(n^2)$ ,只是複雜度中的常數項變小了。 進一步想,**如果我們把子陣列不斷地再從中點處劃分為兩個子陣列**,直至子陣列只剩一個元素時停止劃分呢?這種思路實際上就是“合併排序”,時間複雜度為 $O(n \log n)$ 。 再思考,**如果我們多設定幾個劃分點**,將原陣列平均劃分為 $k$ 個子陣列呢?這種情況與“桶排序”非常類似,它非常適合排序海量資料,理論上時間複雜度可以達到 $O(n + k)$ 。 ### 平行計算最佳化 我們知道,分治生成的子問題是相互獨立的,**因此通常可以並行解決**。也就是說,分治不僅可以降低演算法的時間複雜度,**還有利於作業系統的並行最佳化**。 並行最佳化在多核或多處理器的環境中尤其有效,因為系統可以同時處理多個子問題,更加充分地利用計算資源,從而顯著減少總體的執行時間。 比如在下圖所示的“桶排序”中,我們將海量的資料平均分配到各個桶中,則可將所有桶的排序任務分散到各個計算單元,完成後再合併結果。 ![桶排序的平行計算](divide_and_conquer.assets/divide_and_conquer_parallel_computing.png) ## 分治常見應用 一方面,分治可以用來解決許多經典演算法問題。 - **尋找最近點對**:該演算法首先將點集分成兩部分,然後分別找出兩部分中的最近點對,最後找出跨越兩部分的最近點對。 - **大整數乘法**:例如 Karatsuba 演算法,它將大整數乘法分解為幾個較小的整數的乘法和加法。 - **矩陣乘法**:例如 Strassen 演算法,它將大矩陣乘法分解為多個小矩陣的乘法和加法。 - **河內塔問題**:河內塔問題可以透過遞迴解決,這是典型的分治策略應用。 - **求解逆序對**:在一個序列中,如果前面的數字大於後面的數字,那麼這兩個數字構成一個逆序對。求解逆序對問題可以利用分治的思想,藉助合併排序進行求解。 另一方面,分治在演算法和資料結構的設計中應用得非常廣泛。 - **二分搜尋**:二分搜尋是將有序陣列從中點索引處分為兩部分,然後根據目標值與中間元素值比較結果,決定排除哪一半區間,並在剩餘區間執行相同的二分操作。 - **合併排序**:本節開頭已介紹,不再贅述。 - **快速排序**:快速排序是選取一個基準值,然後把陣列分為兩個子陣列,一個子陣列的元素比基準值小,另一子陣列的元素比基準值大,再對這兩部分進行相同的劃分操作,直至子陣列只剩下一個元素。 - **桶排序**:桶排序的基本思想是將資料分散到多個桶,然後對每個桶內的元素進行排序,最後將各個桶的元素依次取出,從而得到一個有序陣列。 - **樹**:例如二元搜尋樹、AVL 樹、紅黑樹、B 樹、B+ 樹等,它們的查詢、插入和刪除等操作都可以視為分治策略的應用。 - **堆積**:堆積是一種特殊的完全二元樹,其各種操作,如插入、刪除和堆積化,實際上都隱含了分治的思想。 - **雜湊表**:雖然雜湊表並不直接應用分治,但某些雜湊衝突解決方案間接應用了分治策略,例如,鏈式位址中的長鏈結串列會被轉化為紅黑樹,以提升查詢效率。 可以看出,**分治是一種“潤物細無聲”的演算法思想**,隱含在各種演算法與資料結構之中。 ================================================ FILE: zh-hant/docs/chapter_divide_and_conquer/hanota_problem.md ================================================ # 河內塔問題 在合併排序和構建二元樹中,我們都是將原問題分解為兩個規模為原問題一半的子問題。然而對於河內塔問題,我們採用不同的分解策略。 !!! question 給定三根柱子,記為 `A`、`B` 和 `C` 。起始狀態下,柱子 `A` 上套著 $n$ 個圓盤,它們從上到下按照從小到大的順序排列。我們的任務是要把這 $n$ 個圓盤移到柱子 `C` 上,並保持它們的原有順序不變(如下圖所示)。在移動圓盤的過程中,需要遵守以下規則。 1. 圓盤只能從一根柱子頂部拿出,從另一根柱子頂部放入。 2. 每次只能移動一個圓盤。 3. 小圓盤必須時刻位於大圓盤之上。 ![河內塔問題示例](hanota_problem.assets/hanota_example.png) **我們將規模為 $i$ 的河內塔問題記作 $f(i)$** 。例如 $f(3)$ 代表將 $3$ 個圓盤從 `A` 移動至 `C` 的河內塔問題。 ### 考慮基本情況 如下圖所示,對於問題 $f(1)$ ,即當只有一個圓盤時,我們將它直接從 `A` 移動至 `C` 即可。 === "<1>" ![規模為 1 的問題的解](hanota_problem.assets/hanota_f1_step1.png) === "<2>" ![hanota_f1_step2](hanota_problem.assets/hanota_f1_step2.png) 如下圖所示,對於問題 $f(2)$ ,即當有兩個圓盤時,**由於要時刻滿足小圓盤在大圓盤之上,因此需要藉助 `B` 來完成移動**。 1. 先將上面的小圓盤從 `A` 移至 `B` 。 2. 再將大圓盤從 `A` 移至 `C` 。 3. 最後將小圓盤從 `B` 移至 `C` 。 === "<1>" ![規模為 2 的問題的解](hanota_problem.assets/hanota_f2_step1.png) === "<2>" ![hanota_f2_step2](hanota_problem.assets/hanota_f2_step2.png) === "<3>" ![hanota_f2_step3](hanota_problem.assets/hanota_f2_step3.png) === "<4>" ![hanota_f2_step4](hanota_problem.assets/hanota_f2_step4.png) 解決問題 $f(2)$ 的過程可總結為:**將兩個圓盤藉助 `B` 從 `A` 移至 `C`** 。其中,`C` 稱為目標柱、`B` 稱為緩衝柱。 ### 子問題分解 對於問題 $f(3)$ ,即當有三個圓盤時,情況變得稍微複雜了一些。 因為已知 $f(1)$ 和 $f(2)$ 的解,所以我們可從分治角度思考,**將 `A` 頂部的兩個圓盤看作一個整體**,執行下圖所示的步驟。這樣三個圓盤就被順利地從 `A` 移至 `C` 了。 1. 令 `B` 為目標柱、`C` 為緩衝柱,將兩個圓盤從 `A` 移至 `B` 。 2. 將 `A` 中剩餘的一個圓盤從 `A` 直接移動至 `C` 。 3. 令 `C` 為目標柱、`A` 為緩衝柱,將兩個圓盤從 `B` 移至 `C` 。 === "<1>" ![規模為 3 的問題的解](hanota_problem.assets/hanota_f3_step1.png) === "<2>" ![hanota_f3_step2](hanota_problem.assets/hanota_f3_step2.png) === "<3>" ![hanota_f3_step3](hanota_problem.assets/hanota_f3_step3.png) === "<4>" ![hanota_f3_step4](hanota_problem.assets/hanota_f3_step4.png) 從本質上看,**我們將問題 $f(3)$ 劃分為兩個子問題 $f(2)$ 和一個子問題 $f(1)$** 。按順序解決這三個子問題之後,原問題隨之得到解決。這說明子問題是獨立的,而且解可以合併。 至此,我們可總結出下圖所示的解決河內塔問題的分治策略:將原問題 $f(n)$ 劃分為兩個子問題 $f(n-1)$ 和一個子問題 $f(1)$ ,並按照以下順序解決這三個子問題。 1. 將 $n-1$ 個圓盤藉助 `C` 從 `A` 移至 `B` 。 2. 將剩餘 $1$ 個圓盤從 `A` 直接移至 `C` 。 3. 將 $n-1$ 個圓盤藉助 `A` 從 `B` 移至 `C` 。 對於這兩個子問題 $f(n-1)$ ,**可以透過相同的方式進行遞迴劃分**,直至達到最小子問題 $f(1)$ 。而 $f(1)$ 的解是已知的,只需一次移動操作即可。 ![解決河內塔問題的分治策略](hanota_problem.assets/hanota_divide_and_conquer.png) ### 程式碼實現 在程式碼中,我們宣告一個遞迴函式 `dfs(i, src, buf, tar)` ,它的作用是將柱 `src` 頂部的 $i$ 個圓盤藉助緩衝柱 `buf` 移動至目標柱 `tar` : ```src [file]{hanota}-[class]{}-[func]{solve_hanota} ``` 如下圖所示,河內塔問題形成一棵高度為 $n$ 的遞迴樹,每個節點代表一個子問題,對應一個開啟的 `dfs()` 函式,**因此時間複雜度為 $O(2^n)$ ,空間複雜度為 $O(n)$** 。 ![河內塔問題的遞迴樹](hanota_problem.assets/hanota_recursive_tree.png) !!! quote 河內塔問題源自一個古老的傳說。在古印度的一個寺廟裡,僧侶們有三根高大的鑽石柱子,以及 $64$ 個大小不一的金圓盤。僧侶們不斷地移動圓盤,他們相信在最後一個圓盤被正確放置的那一刻,這個世界就會結束。 然而,即使僧侶們每秒鐘移動一次,總共需要大約 $2^{64} \approx 1.84×10^{19}$ 秒,合約 $5850$ 億年,遠遠超過了現在對宇宙年齡的估計。所以,倘若這個傳說是真的,我們應該不需要擔心世界末日的到來。 ================================================ FILE: zh-hant/docs/chapter_divide_and_conquer/index.md ================================================ # 分治 ![分治](../assets/covers/chapter_divide_and_conquer.jpg) !!! abstract 難題被逐層拆解,每一次的拆解都使它變得更為簡單。 分而治之揭示了一個重要的事實:從簡單做起,一切都不再複雜。 ================================================ FILE: zh-hant/docs/chapter_divide_and_conquer/summary.md ================================================ # 小結 ### 重點回顧 - 分治是一種常見的演算法設計策略,包括分(劃分)和治(合併)兩個階段,通常基於遞迴實現。 - 判斷是否是分治演算法問題的依據包括:問題能否分解、子問題是否獨立、子問題能否合併。 - 合併排序是分治策略的典型應用,其遞迴地將陣列劃分為等長的兩個子陣列,直到只剩一個元素時開始逐層合併,從而完成排序。 - 引入分治策略往往可以提升演算法效率。一方面,分治策略減少了操作數量;另一方面,分治後有利於系統的並行最佳化。 - 分治既可以解決許多演算法問題,也廣泛應用於資料結構與演算法設計中,處處可見其身影。 - 相較於暴力搜尋,自適應搜尋效率更高。時間複雜度為 $O(\log n)$ 的搜尋演算法通常是基於分治策略實現的。 - 二分搜尋是分治策略的另一個典型應用,它不包含將子問題的解進行合併的步驟。我們可以透過遞迴分治實現二分搜尋。 - 在構建二元樹的問題中,構建樹(原問題)可以劃分為構建左子樹和右子樹(子問題),這可以透過劃分前序走訪和中序走訪的索引區間來實現。 - 在河內塔問題中,一個規模為 $n$ 的問題可以劃分為兩個規模為 $n-1$ 的子問題和一個規模為 $1$ 的子問題。按順序解決這三個子問題後,原問題隨之得到解決。 ================================================ FILE: zh-hant/docs/chapter_dynamic_programming/dp_problem_features.md ================================================ # 動態規劃問題特性 在上一節中,我們學習了動態規劃是如何透過子問題分解來求解原問題的。實際上,子問題分解是一種通用的演算法思路,在分治、動態規劃、回溯中的側重點不同。 - 分治演算法遞迴地將原問題劃分為多個相互獨立的子問題,直至最小子問題,並在回溯中合併子問題的解,最終得到原問題的解。 - 動態規劃也對問題進行遞迴分解,但與分治演算法的主要區別是,動態規劃中的子問題是相互依賴的,在分解過程中會出現許多重疊子問題。 - 回溯演算法在嘗試和回退中窮舉所有可能的解,並透過剪枝避免不必要的搜尋分支。原問題的解由一系列決策步驟構成,我們可以將每個決策步驟之前的子序列看作一個子問題。 實際上,動態規劃常用來求解最最佳化問題,它們不僅包含重疊子問題,還具有另外兩大特性:最優子結構、無後效性。 ## 最優子結構 我們對爬樓梯問題稍作改動,使之更加適合展示最優子結構概念。 !!! question "爬樓梯最小代價" 給定一個樓梯,你每步可以上 $1$ 階或者 $2$ 階,每一階樓梯上都貼有一個非負整數,表示你在該臺階所需要付出的代價。給定一個非負整數陣列 $cost$ ,其中 $cost[i]$ 表示在第 $i$ 個臺階需要付出的代價,$cost[0]$ 為地面(起始點)。請計算最少需要付出多少代價才能到達頂部? 如下圖所示,若第 $1$、$2$、$3$ 階的代價分別為 $1$、$10$、$1$ ,則從地面爬到第 $3$ 階的最小代價為 $2$ 。 ![爬到第 3 階的最小代價](dp_problem_features.assets/min_cost_cs_example.png) 設 $dp[i]$ 為爬到第 $i$ 階累計付出的代價,由於第 $i$ 階只可能從 $i - 1$ 階或 $i - 2$ 階走來,因此 $dp[i]$ 只可能等於 $dp[i - 1] + cost[i]$ 或 $dp[i - 2] + cost[i]$ 。為了儘可能減少代價,我們應該選擇兩者中較小的那一個: $$ dp[i] = \min(dp[i-1], dp[i-2]) + cost[i] $$ 這便可以引出最優子結構的含義:**原問題的最優解是從子問題的最優解構建得來的**。 本題顯然具有最優子結構:我們從兩個子問題最優解 $dp[i-1]$ 和 $dp[i-2]$ 中挑選出較優的那一個,並用它構建出原問題 $dp[i]$ 的最優解。 那麼,上一節的爬樓梯題目有沒有最優子結構呢?它的目標是求解方案數量,看似是一個計數問題,但如果換一種問法:“求解最大方案數量”。我們意外地發現,**雖然題目修改前後是等價的,但最優子結構浮現出來了**:第 $n$ 階最大方案數量等於第 $n-1$ 階和第 $n-2$ 階最大方案數量之和。所以說,最優子結構的解釋方式比較靈活,在不同問題中會有不同的含義。 根據狀態轉移方程,以及初始狀態 $dp[1] = cost[1]$ 和 $dp[2] = cost[2]$ ,我們就可以得到動態規劃程式碼: ```src [file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp} ``` 下圖展示了以上程式碼的動態規劃過程。 ![爬樓梯最小代價的動態規劃過程](dp_problem_features.assets/min_cost_cs_dp.png) 本題也可以進行空間最佳化,將一維壓縮至零維,使得空間複雜度從 $O(n)$ 降至 $O(1)$ : ```src [file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp_comp} ``` ## 無後效性 無後效性是動態規劃能夠有效解決問題的重要特性之一,其定義為:**給定一個確定的狀態,它的未來發展只與當前狀態有關,而與過去經歷的所有狀態無關**。 以爬樓梯問題為例,給定狀態 $i$ ,它會發展出狀態 $i+1$ 和狀態 $i+2$ ,分別對應跳 $1$ 步和跳 $2$ 步。在做出這兩種選擇時,我們無須考慮狀態 $i$ 之前的狀態,它們對狀態 $i$ 的未來沒有影響。 然而,如果我們給爬樓梯問題新增一個約束,情況就不一樣了。 !!! question "帶約束爬樓梯" 給定一個共有 $n$ 階的樓梯,你每步可以上 $1$ 階或者 $2$ 階,**但不能連續兩輪跳 $1$ 階**,請問有多少種方案可以爬到樓頂? 如下圖所示,爬上第 $3$ 階僅剩 $2$ 種可行方案,其中連續三次跳 $1$ 階的方案不滿足約束條件,因此被捨棄。 ![帶約束爬到第 3 階的方案數量](dp_problem_features.assets/climbing_stairs_constraint_example.png) 在該問題中,如果上一輪是跳 $1$ 階上來的,那麼下一輪就必須跳 $2$ 階。這意味著,**下一步選擇不能由當前狀態(當前所在樓梯階數)獨立決定,還和前一個狀態(上一輪所在樓梯階數)有關**。 不難發現,此問題已不滿足無後效性,狀態轉移方程 $dp[i] = dp[i-1] + dp[i-2]$ 也失效了,因為 $dp[i-1]$ 代表本輪跳 $1$ 階,但其中包含了許多“上一輪是跳 $1$ 階上來的”方案,而為了滿足約束,我們就不能將 $dp[i-1]$ 直接計入 $dp[i]$ 中。 為此,我們需要擴展狀態定義:**狀態 $[i, j]$ 表示處在第 $i$ 階並且上一輪跳了 $j$ 階**,其中 $j \in \{1, 2\}$ 。此狀態定義有效地區分了上一輪跳了 $1$ 階還是 $2$ 階,我們可以據此判斷當前狀態是從何而來的。 - 當上一輪跳了 $1$ 階時,上上一輪只能選擇跳 $2$ 階,即 $dp[i, 1]$ 只能從 $dp[i-1, 2]$ 轉移過來。 - 當上一輪跳了 $2$ 階時,上上一輪可選擇跳 $1$ 階或跳 $2$ 階,即 $dp[i, 2]$ 可以從 $dp[i-2, 1]$ 或 $dp[i-2, 2]$ 轉移過來。 如下圖所示,在該定義下,$dp[i, j]$ 表示狀態 $[i, j]$ 對應的方案數。此時狀態轉移方程為: $$ \begin{cases} dp[i, 1] = dp[i-1, 2] \\ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] \end{cases} $$ ![考慮約束下的遞推關係](dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png) 最終,返回 $dp[n, 1] + dp[n, 2]$ 即可,兩者之和代表爬到第 $n$ 階的方案總數: ```src [file]{climbing_stairs_constraint_dp}-[class]{}-[func]{climbing_stairs_constraint_dp} ``` 在上面的案例中,由於僅需多考慮前面一個狀態,因此我們仍然可以透過擴展狀態定義,使得問題重新滿足無後效性。然而,某些問題具有非常嚴重的“有後效性”。 !!! question "爬樓梯與障礙生成" 給定一個共有 $n$ 階的樓梯,你每步可以上 $1$ 階或者 $2$ 階。**規定當爬到第 $i$ 階時,系統自動會在第 $2i$ 階上放上障礙物,之後所有輪都不允許跳到第 $2i$ 階上**。例如,前兩輪分別跳到了第 $2$、$3$ 階上,則之後就不能跳到第 $4$、$6$ 階上。請問有多少種方案可以爬到樓頂? 在這個問題中,下次跳躍依賴過去所有的狀態,因為每一次跳躍都會在更高的階梯上設定障礙,並影響未來的跳躍。對於這類問題,動態規劃往往難以解決。 實際上,許多複雜的組合最佳化問題(例如旅行商問題)不滿足無後效性。對於這類問題,我們通常會選擇使用其他方法,例如啟發式搜尋、遺傳演算法、強化學習等,從而在有限時間內得到可用的區域性最優解。 ================================================ FILE: zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.md ================================================ # 動態規劃解題思路 上兩節介紹了動態規劃問題的主要特徵,接下來我們一起探究兩個更加實用的問題。 1. 如何判斷一個問題是不是動態規劃問題? 2. 求解動態規劃問題該從何處入手,完整步驟是什麼? ## 問題判斷 總的來說,如果一個問題包含重疊子問題、最優子結構,並滿足無後效性,那麼它通常適合用動態規劃求解。然而,我們很難從問題描述中直接提取出這些特性。因此我們通常會放寬條件,**先觀察問題是否適合使用回溯(窮舉)解決**。 **適合用回溯解決的問題通常滿足“決策樹模型”**,這種問題可以使用樹形結構來描述,其中每一個節點代表一個決策,每一條路徑代表一個決策序列。 換句話說,如果問題包含明確的決策概念,並且解是透過一系列決策產生的,那麼它就滿足決策樹模型,通常可以使用回溯來解決。 在此基礎上,動態規劃問題還有一些判斷的“加分項”。 - 問題包含最大(小)或最多(少)等最最佳化描述。 - 問題的狀態能夠使用一個串列、多維矩陣或樹來表示,並且一個狀態與其周圍的狀態存在遞推關係。 相應地,也存在一些“減分項”。 - 問題的目標是找出所有可能的解決方案,而不是找出最優解。 - 問題描述中有明顯的排列組合的特徵,需要返回具體的多個方案。 如果一個問題滿足決策樹模型,並具有較為明顯的“加分項”,我們就可以假設它是一個動態規劃問題,並在求解過程中驗證它。 ## 問題求解步驟 動態規劃的解題流程會因問題的性質和難度而有所不同,但通常遵循以下步驟:描述決策,定義狀態,建立 $dp$ 表,推導狀態轉移方程,確定邊界條件等。 為了更形象地展示解題步驟,我們使用一個經典問題“最小路徑和”來舉例。 !!! question 給定一個 $n \times m$ 的二維網格 `grid` ,網格中的每個單元格包含一個非負整數,表示該單元格的代價。機器人以左上角單元格為起始點,每次只能向下或者向右移動一步,直至到達右下角單元格。請返回從左上角到右下角的最小路徑和。 下圖展示了一個例子,給定網格的最小路徑和為 $13$ 。 ![最小路徑和示例資料](dp_solution_pipeline.assets/min_path_sum_example.png) **第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表** 本題的每一輪的決策就是從當前格子向下或向右走一步。設當前格子的行列索引為 $[i, j]$ ,則向下或向右走一步後,索引變為 $[i+1, j]$ 或 $[i, j+1]$ 。因此,狀態應包含行索引和列索引兩個變數,記為 $[i, j]$ 。 狀態 $[i, j]$ 對應的子問題為:從起始點 $[0, 0]$ 走到 $[i, j]$ 的最小路徑和,解記為 $dp[i, j]$ 。 至此,我們就得到了下圖所示的二維 $dp$ 矩陣,其尺寸與輸入網格 $grid$ 相同。 ![狀態定義與 dp 表](dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png) !!! note 動態規劃和回溯過程可以描述為一個決策序列,而狀態由所有決策變數構成。它應當包含描述解題進度的所有變數,其包含了足夠的資訊,能夠用來推導出下一個狀態。 每個狀態都對應一個子問題,我們會定義一個 $dp$ 表來儲存所有子問題的解,狀態的每個獨立變數都是 $dp$ 表的一個維度。從本質上看,$dp$ 表是狀態和子問題的解之間的對映。 **第二步:找出最優子結構,進而推導出狀態轉移方程** 對於狀態 $[i, j]$ ,它只能從上邊格子 $[i-1, j]$ 和左邊格子 $[i, j-1]$ 轉移而來。因此最優子結構為:到達 $[i, j]$ 的最小路徑和由 $[i, j-1]$ 的最小路徑和與 $[i-1, j]$ 的最小路徑和中較小的那一個決定。 根據以上分析,可推出下圖所示的狀態轉移方程: $$ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j] $$ ![最優子結構與狀態轉移方程](dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png) !!! note 根據定義好的 $dp$ 表,思考原問題和子問題的關係,找出透過子問題的最優解來構造原問題的最優解的方法,即最優子結構。 一旦我們找到了最優子結構,就可以使用它來構建出狀態轉移方程。 **第三步:確定邊界條件和狀態轉移順序** 在本題中,處在首行的狀態只能從其左邊的狀態得來,處在首列的狀態只能從其上邊的狀態得來,因此首行 $i = 0$ 和首列 $j = 0$ 是邊界條件。 如下圖所示,由於每個格子是由其左方格子和上方格子轉移而來,因此我們使用迴圈來走訪矩陣,外迴圈走訪各行,內迴圈走訪各列。 ![邊界條件與狀態轉移順序](dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png) !!! note 邊界條件在動態規劃中用於初始化 $dp$ 表,在搜尋中用於剪枝。 狀態轉移順序的核心是要保證在計算當前問題的解時,所有它依賴的更小子問題的解都已經被正確地計算出來。 根據以上分析,我們已經可以直接寫出動態規劃程式碼。然而子問題分解是一種從頂至底的思想,因此按照“暴力搜尋 $\rightarrow$ 記憶化搜尋 $\rightarrow$ 動態規劃”的順序實現更加符合思維習慣。 ### 方法一:暴力搜尋 從狀態 $[i, j]$ 開始搜尋,不斷分解為更小的狀態 $[i-1, j]$ 和 $[i, j-1]$ ,遞迴函式包括以下要素。 - **遞迴參數**:狀態 $[i, j]$ 。 - **返回值**:從 $[0, 0]$ 到 $[i, j]$ 的最小路徑和 $dp[i, j]$ 。 - **終止條件**:當 $i = 0$ 且 $j = 0$ 時,返回代價 $grid[0, 0]$ 。 - **剪枝**:當 $i < 0$ 時或 $j < 0$ 時索引越界,此時返回代價 $+\infty$ ,代表不可行。 實現程式碼如下: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs} ``` 下圖給出了以 $dp[2, 1]$ 為根節點的遞迴樹,其中包含一些重疊子問題,其數量會隨著網格 `grid` 的尺寸變大而急劇增多。 從本質上看,造成重疊子問題的原因為:**存在多條路徑可以從左上角到達某一單元格**。 ![暴力搜尋遞迴樹](dp_solution_pipeline.assets/min_path_sum_dfs.png) 每個狀態都有向下和向右兩種選擇,從左上角走到右下角總共需要 $m + n - 2$ 步,所以最差時間複雜度為 $O(2^{m + n})$ ,其中 $n$ 和 $m$ 分別為網格的行數和列數。請注意,這種計算方式未考慮臨近網格邊界的情況,當到達網格邊界時只剩下一種選擇,因此實際的路徑數量會少一些。 ### 方法二:記憶化搜尋 我們引入一個和網格 `grid` 相同尺寸的記憶串列 `mem` ,用於記錄各個子問題的解,並將重疊子問題進行剪枝: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs_mem} ``` 如下圖所示,在引入記憶化後,所有子問題的解只需計算一次,因此時間複雜度取決於狀態總數,即網格尺寸 $O(nm)$ 。 ![記憶化搜尋遞迴樹](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png) ### 方法三:動態規劃 基於迭代實現動態規劃解法,程式碼如下所示: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp} ``` 下圖展示了最小路徑和的狀態轉移過程,其走訪了整個網格,**因此時間複雜度為 $O(nm)$** 。 陣列 `dp` 大小為 $n \times m$ ,**因此空間複雜度為 $O(nm)$** 。 === "<1>" ![最小路徑和的動態規劃過程](dp_solution_pipeline.assets/min_path_sum_dp_step1.png) === "<2>" ![min_path_sum_dp_step2](dp_solution_pipeline.assets/min_path_sum_dp_step2.png) === "<3>" ![min_path_sum_dp_step3](dp_solution_pipeline.assets/min_path_sum_dp_step3.png) === "<4>" ![min_path_sum_dp_step4](dp_solution_pipeline.assets/min_path_sum_dp_step4.png) === "<5>" ![min_path_sum_dp_step5](dp_solution_pipeline.assets/min_path_sum_dp_step5.png) === "<6>" ![min_path_sum_dp_step6](dp_solution_pipeline.assets/min_path_sum_dp_step6.png) === "<7>" ![min_path_sum_dp_step7](dp_solution_pipeline.assets/min_path_sum_dp_step7.png) === "<8>" ![min_path_sum_dp_step8](dp_solution_pipeline.assets/min_path_sum_dp_step8.png) === "<9>" ![min_path_sum_dp_step9](dp_solution_pipeline.assets/min_path_sum_dp_step9.png) === "<10>" ![min_path_sum_dp_step10](dp_solution_pipeline.assets/min_path_sum_dp_step10.png) === "<11>" ![min_path_sum_dp_step11](dp_solution_pipeline.assets/min_path_sum_dp_step11.png) === "<12>" ![min_path_sum_dp_step12](dp_solution_pipeline.assets/min_path_sum_dp_step12.png) ### 空間最佳化 由於每個格子只與其左邊和上邊的格子有關,因此我們可以只用一個單行陣列來實現 $dp$ 表。 請注意,因為陣列 `dp` 只能表示一行的狀態,所以我們無法提前初始化首列狀態,而是在走訪每行時更新它: ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp_comp} ``` ================================================ FILE: zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.md ================================================ # 編輯距離問題 編輯距離,也稱 Levenshtein 距離,指兩個字串之間互相轉換的最少修改次數,通常用於在資訊檢索和自然語言處理中度量兩個序列的相似度。 !!! question 輸入兩個字串 $s$ 和 $t$ ,返回將 $s$ 轉換為 $t$ 所需的最少編輯步數。 你可以在一個字串中進行三種編輯操作:插入一個字元、刪除一個字元、將字元替換為任意一個字元。 如下圖所示,將 `kitten` 轉換為 `sitting` 需要編輯 3 步,包括 2 次替換操作與 1 次新增操作;將 `hello` 轉換為 `algo` 需要 3 步,包括 2 次替換操作和 1 次刪除操作。 ![編輯距離的示例資料](edit_distance_problem.assets/edit_distance_example.png) **編輯距離問題可以很自然地用決策樹模型來解釋**。字串對應樹節點,一輪決策(一次編輯操作)對應樹的一條邊。 如下圖所示,在不限制操作的情況下,每個節點都可以派生出許多條邊,每條邊對應一種操作,這意味著從 `hello` 轉換到 `algo` 有許多種可能的路徑。 從決策樹的角度看,本題的目標是求解節點 `hello` 和節點 `algo` 之間的最短路徑。 ![基於決策樹模型表示編輯距離問題](edit_distance_problem.assets/edit_distance_decision_tree.png) ### 動態規劃思路 **第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表** 每一輪的決策是對字串 $s$ 進行一次編輯操作。 我們希望在編輯操作的過程中,問題的規模逐漸縮小,這樣才能構建子問題。設字串 $s$ 和 $t$ 的長度分別為 $n$ 和 $m$ ,我們先考慮兩字串尾部的字元 $s[n-1]$ 和 $t[m-1]$ 。 - 若 $s[n-1]$ 和 $t[m-1]$ 相同,我們可以跳過它們,直接考慮 $s[n-2]$ 和 $t[m-2]$ 。 - 若 $s[n-1]$ 和 $t[m-1]$ 不同,我們需要對 $s$ 進行一次編輯(插入、刪除、替換),使得兩字串尾部的字元相同,從而可以跳過它們,考慮規模更小的問題。 也就是說,我們在字串 $s$ 中進行的每一輪決策(編輯操作),都會使得 $s$ 和 $t$ 中剩餘的待匹配字元發生變化。因此,狀態為當前在 $s$ 和 $t$ 中考慮的第 $i$ 和第 $j$ 個字元,記為 $[i, j]$ 。 狀態 $[i, j]$ 對應的子問題:**將 $s$ 的前 $i$ 個字元更改為 $t$ 的前 $j$ 個字元所需的最少編輯步數**。 至此,得到一個尺寸為 $(i+1) \times (j+1)$ 的二維 $dp$ 表。 **第二步:找出最優子結構,進而推導出狀態轉移方程** 考慮子問題 $dp[i, j]$ ,其對應的兩個字串的尾部字元為 $s[i-1]$ 和 $t[j-1]$ ,可根據不同編輯操作分為下圖所示的三種情況。 1. 在 $s[i-1]$ 之後新增 $t[j-1]$ ,則剩餘子問題 $dp[i, j-1]$ 。 2. 刪除 $s[i-1]$ ,則剩餘子問題 $dp[i-1, j]$ 。 3. 將 $s[i-1]$ 替換為 $t[j-1]$ ,則剩餘子問題 $dp[i-1, j-1]$ 。 ![編輯距離的狀態轉移](edit_distance_problem.assets/edit_distance_state_transfer.png) 根據以上分析,可得最優子結構:$dp[i, j]$ 的最少編輯步數等於 $dp[i, j-1]$、$dp[i-1, j]$、$dp[i-1, j-1]$ 三者中的最少編輯步數,再加上本次的編輯步數 $1$ 。對應的狀態轉移方程為: $$ dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 $$ 請注意,**當 $s[i-1]$ 和 $t[j-1]$ 相同時,無須編輯當前字元**,這種情況下的狀態轉移方程為: $$ dp[i, j] = dp[i-1, j-1] $$ **第三步:確定邊界條件和狀態轉移順序** 當兩字串都為空時,編輯步數為 $0$ ,即 $dp[0, 0] = 0$ 。當 $s$ 為空但 $t$ 不為空時,最少編輯步數等於 $t$ 的長度,即首行 $dp[0, j] = j$ 。當 $s$ 不為空但 $t$ 為空時,最少編輯步數等於 $s$ 的長度,即首列 $dp[i, 0] = i$ 。 觀察狀態轉移方程,解 $dp[i, j]$ 依賴左方、上方、左上方的解,因此透過兩層迴圈正序走訪整個 $dp$ 表即可。 ### 程式碼實現 ```src [file]{edit_distance}-[class]{}-[func]{edit_distance_dp} ``` 如下圖所示,編輯距離問題的狀態轉移過程與背包問題非常類似,都可以看作填寫一個二維網格的過程。 === "<1>" ![編輯距離的動態規劃過程](edit_distance_problem.assets/edit_distance_dp_step1.png) === "<2>" ![edit_distance_dp_step2](edit_distance_problem.assets/edit_distance_dp_step2.png) === "<3>" ![edit_distance_dp_step3](edit_distance_problem.assets/edit_distance_dp_step3.png) === "<4>" ![edit_distance_dp_step4](edit_distance_problem.assets/edit_distance_dp_step4.png) === "<5>" ![edit_distance_dp_step5](edit_distance_problem.assets/edit_distance_dp_step5.png) === "<6>" ![edit_distance_dp_step6](edit_distance_problem.assets/edit_distance_dp_step6.png) === "<7>" ![edit_distance_dp_step7](edit_distance_problem.assets/edit_distance_dp_step7.png) === "<8>" ![edit_distance_dp_step8](edit_distance_problem.assets/edit_distance_dp_step8.png) === "<9>" ![edit_distance_dp_step9](edit_distance_problem.assets/edit_distance_dp_step9.png) === "<10>" ![edit_distance_dp_step10](edit_distance_problem.assets/edit_distance_dp_step10.png) === "<11>" ![edit_distance_dp_step11](edit_distance_problem.assets/edit_distance_dp_step11.png) === "<12>" ![edit_distance_dp_step12](edit_distance_problem.assets/edit_distance_dp_step12.png) === "<13>" ![edit_distance_dp_step13](edit_distance_problem.assets/edit_distance_dp_step13.png) === "<14>" ![edit_distance_dp_step14](edit_distance_problem.assets/edit_distance_dp_step14.png) === "<15>" ![edit_distance_dp_step15](edit_distance_problem.assets/edit_distance_dp_step15.png) ### 空間最佳化 由於 $dp[i,j]$ 是由上方 $dp[i-1, j]$、左方 $dp[i, j-1]$、左上方 $dp[i-1, j-1]$ 轉移而來的,而正序走訪會丟失左上方 $dp[i-1, j-1]$ ,倒序走訪無法提前構建 $dp[i, j-1]$ ,因此兩種走訪順序都不可取。 為此,我們可以使用一個變數 `leftup` 來暫存左上方的解 $dp[i-1, j-1]$ ,從而只需考慮左方和上方的解。此時的情況與完全背包問題相同,可使用正序走訪。程式碼如下所示: ```src [file]{edit_distance}-[class]{}-[func]{edit_distance_dp_comp} ``` ================================================ FILE: zh-hant/docs/chapter_dynamic_programming/index.md ================================================ # 動態規劃 ![動態規劃](../assets/covers/chapter_dynamic_programming.jpg) !!! abstract 小溪匯入河流,江河匯入大海。 動態規劃將小問題的解彙集成大問題的答案,一步步引領我們走向解決問題的彼岸。 ================================================ FILE: zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md ================================================ # 初探動態規劃 動態規劃(dynamic programming)是一個重要的演算法範式,它將一個問題分解為一系列更小的子問題,並透過儲存子問題的解來避免重複計算,從而大幅提升時間效率。 在本節中,我們從一個經典例題入手,先給出它的暴力回溯解法,觀察其中包含的重疊子問題,再逐步導出更高效的動態規劃解法。 !!! question "爬樓梯" 給定一個共有 $n$ 階的樓梯,你每步可以上 $1$ 階或者 $2$ 階,請問有多少種方案可以爬到樓頂? 如下圖所示,對於一個 $3$ 階樓梯,共有 $3$ 種方案可以爬到樓頂。 ![爬到第 3 階的方案數量](intro_to_dynamic_programming.assets/climbing_stairs_example.png) 本題的目標是求解方案數量,**我們可以考慮透過回溯來窮舉所有可能性**。具體來說,將爬樓梯想象為一個多輪選擇的過程:從地面出發,每輪選擇上 $1$ 階或 $2$ 階,每當到達樓梯頂部時就將方案數量加 $1$ ,當越過樓梯頂部時就將其剪枝。程式碼如下所示: ```src [file]{climbing_stairs_backtrack}-[class]{}-[func]{climbing_stairs_backtrack} ``` ## 方法一:暴力搜尋 回溯演算法通常並不顯式地對問題進行拆解,而是將求解問題看作一系列決策步驟,透過試探和剪枝,搜尋所有可能的解。 我們可以嘗試從問題分解的角度分析這道題。設爬到第 $i$ 階共有 $dp[i]$ 種方案,那麼 $dp[i]$ 就是原問題,其子問題包括: $$ dp[i-1], dp[i-2], \dots, dp[2], dp[1] $$ 由於每輪只能上 $1$ 階或 $2$ 階,因此當我們站在第 $i$ 階樓梯上時,上一輪只可能站在第 $i - 1$ 階或第 $i - 2$ 階上。換句話說,我們只能從第 $i -1$ 階或第 $i - 2$ 階邁向第 $i$ 階。 由此便可得出一個重要推論:**爬到第 $i - 1$ 階的方案數加上爬到第 $i - 2$ 階的方案數就等於爬到第 $i$ 階的方案數**。公式如下: $$ dp[i] = dp[i-1] + dp[i-2] $$ 這意味著在爬樓梯問題中,各個子問題之間存在遞推關係,**原問題的解可以由子問題的解構建得來**。下圖展示了該遞推關係。 ![方案數量遞推關係](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png) 我們可以根據遞推公式得到暴力搜尋解法。以 $dp[n]$ 為起始點,**遞迴地將一個較大問題拆解為兩個較小問題的和**,直至到達最小子問題 $dp[1]$ 和 $dp[2]$ 時返回。其中,最小子問題的解是已知的,即 $dp[1] = 1$、$dp[2] = 2$ ,表示爬到第 $1$、$2$ 階分別有 $1$、$2$ 種方案。 觀察以下程式碼,它和標準回溯程式碼都屬於深度優先搜尋,但更加簡潔: ```src [file]{climbing_stairs_dfs}-[class]{}-[func]{climbing_stairs_dfs} ``` 下圖展示了暴力搜尋形成的遞迴樹。對於問題 $dp[n]$ ,其遞迴樹的深度為 $n$ ,時間複雜度為 $O(2^n)$ 。指數階屬於爆炸式增長,如果我們輸入一個比較大的 $n$ ,則會陷入漫長的等待之中。 ![爬樓梯對應遞迴樹](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png) 觀察上圖,**指數階的時間複雜度是“重疊子問題”導致的**。例如 $dp[9]$ 被分解為 $dp[8]$ 和 $dp[7]$ ,$dp[8]$ 被分解為 $dp[7]$ 和 $dp[6]$ ,兩者都包含子問題 $dp[7]$ 。 以此類推,子問題中包含更小的重疊子問題,子子孫孫無窮盡也。絕大部分計算資源都浪費在這些重疊的子問題上。 ## 方法二:記憶化搜尋 為了提升演算法效率,**我們希望所有的重疊子問題都只被計算一次**。為此,我們宣告一個陣列 `mem` 來記錄每個子問題的解,並在搜尋過程中將重疊子問題剪枝。 1. 當首次計算 $dp[i]$ 時,我們將其記錄至 `mem[i]` ,以便之後使用。 2. 當再次需要計算 $dp[i]$ 時,我們便可直接從 `mem[i]` 中獲取結果,從而避免重複計算該子問題。 程式碼如下所示: ```src [file]{climbing_stairs_dfs_mem}-[class]{}-[func]{climbing_stairs_dfs_mem} ``` 觀察下圖,**經過記憶化處理後,所有重疊子問題都只需計算一次,時間複雜度最佳化至 $O(n)$** ,這是一個巨大的飛躍。 ![記憶化搜尋對應遞迴樹](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png) ## 方法三:動態規劃 **記憶化搜尋是一種“從頂至底”的方法**:我們從原問題(根節點)開始,遞迴地將較大子問題分解為較小子問題,直至解已知的最小子問題(葉節點)。之後,透過回溯逐層收集子問題的解,構建出原問題的解。 與之相反,**動態規劃是一種“從底至頂”的方法**:從最小子問題的解開始,迭代地構建更大子問題的解,直至得到原問題的解。 由於動態規劃不包含回溯過程,因此只需使用迴圈迭代實現,無須使用遞迴。在以下程式碼中,我們初始化一個陣列 `dp` 來儲存子問題的解,它起到了與記憶化搜尋中陣列 `mem` 相同的記錄作用: ```src [file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp} ``` 下圖模擬了以上程式碼的執行過程。 ![爬樓梯的動態規劃過程](intro_to_dynamic_programming.assets/climbing_stairs_dp.png) 與回溯演算法一樣,動態規劃也使用“狀態”概念來表示問題求解的特定階段,每個狀態都對應一個子問題以及相應的區域性最優解。例如,爬樓梯問題的狀態定義為當前所在樓梯階數 $i$ 。 根據以上內容,我們可以總結出動態規劃的常用術語。 - 將陣列 `dp` 稱為 dp 表,$dp[i]$ 表示狀態 $i$ 對應子問題的解。 - 將最小子問題對應的狀態(第 $1$ 階和第 $2$ 階樓梯)稱為初始狀態。 - 將遞推公式 $dp[i] = dp[i-1] + dp[i-2]$ 稱為狀態轉移方程。 ## 空間最佳化 細心的讀者可能發現了,**由於 $dp[i]$ 只與 $dp[i-1]$ 和 $dp[i-2]$ 有關,因此我們無須使用一個陣列 `dp` 來儲存所有子問題的解**,而只需兩個變數滾動前進即可。程式碼如下所示: ```src [file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp_comp} ``` 觀察以上程式碼,由於省去了陣列 `dp` 佔用的空間,因此空間複雜度從 $O(n)$ 降至 $O(1)$ 。 在動態規劃問題中,當前狀態往往僅與前面有限個狀態有關,這時我們可以只保留必要的狀態,透過“降維”來節省記憶體空間。**這種空間最佳化技巧被稱為“滾動變數”或“滾動陣列”**。 ================================================ FILE: zh-hant/docs/chapter_dynamic_programming/knapsack_problem.md ================================================ # 0-1 背包問題 背包問題是一個非常好的動態規劃入門題目,是動態規劃中最常見的問題形式。其具有很多變種,例如 0-1 背包問題、完全背包問題、多重背包問題等。 在本節中,我們先來求解最常見的 0-1 背包問題。 !!! question 給定 $n$ 個物品,第 $i$ 個物品的重量為 $wgt[i-1]$、價值為 $val[i-1]$ ,和一個容量為 $cap$ 的背包。每個物品只能選擇一次,問在限定背包容量下能放入物品的最大價值。 觀察下圖,由於物品編號 $i$ 從 $1$ 開始計數,陣列索引從 $0$ 開始計數,因此物品 $i$ 對應重量 $wgt[i-1]$ 和價值 $val[i-1]$ 。 ![0-1 背包的示例資料](knapsack_problem.assets/knapsack_example.png) 我們可以將 0-1 背包問題看作一個由 $n$ 輪決策組成的過程,對於每個物體都有不放入和放入兩種決策,因此該問題滿足決策樹模型。 該問題的目標是求解“在限定背包容量下能放入物品的最大價值”,因此較大機率是一個動態規劃問題。 **第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表** 對於每個物品來說,不放入背包,背包容量不變;放入背包,背包容量減小。由此可得狀態定義:當前物品編號 $i$ 和背包容量 $c$ ,記為 $[i, c]$ 。 狀態 $[i, c]$ 對應的子問題為:**前 $i$ 個物品在容量為 $c$ 的背包中的最大價值**,記為 $dp[i, c]$ 。 待求解的是 $dp[n, cap]$ ,因此需要一個尺寸為 $(n+1) \times (cap+1)$ 的二維 $dp$ 表。 **第二步:找出最優子結構,進而推導出狀態轉移方程** 當我們做出物品 $i$ 的決策後,剩餘的是前 $i-1$ 個物品決策的子問題,可分為以下兩種情況。 - **不放入物品 $i$** :背包容量不變,狀態變化為 $[i-1, c]$ 。 - **放入物品 $i$** :背包容量減少 $wgt[i-1]$ ,價值增加 $val[i-1]$ ,狀態變化為 $[i-1, c-wgt[i-1]]$ 。 上述分析向我們揭示了本題的最優子結構:**最大價值 $dp[i, c]$ 等於不放入物品 $i$ 和放入物品 $i$ 兩種方案中價值更大的那一個**。由此可推導出狀態轉移方程: $$ dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) $$ 需要注意的是,若當前物品重量 $wgt[i - 1]$ 超出剩餘背包容量 $c$ ,則只能選擇不放入背包。 **第三步:確定邊界條件和狀態轉移順序** 當無物品或背包容量為 $0$ 時最大價值為 $0$ ,即首列 $dp[i, 0]$ 和首行 $dp[0, c]$ 都等於 $0$ 。 當前狀態 $[i, c]$ 從上方的狀態 $[i-1, c]$ 和左上方的狀態 $[i-1, c-wgt[i-1]]$ 轉移而來,因此透過兩層迴圈正序走訪整個 $dp$ 表即可。 根據以上分析,我們接下來按順序實現暴力搜尋、記憶化搜尋、動態規劃解法。 ### 方法一:暴力搜尋 搜尋程式碼包含以下要素。 - **遞迴參數**:狀態 $[i, c]$ 。 - **返回值**:子問題的解 $dp[i, c]$ 。 - **終止條件**:當物品編號越界 $i = 0$ 或背包剩餘容量為 $0$ 時,終止遞迴並返回價值 $0$ 。 - **剪枝**:若當前物品重量超出背包剩餘容量,則只能選擇不放入背包。 ```src [file]{knapsack}-[class]{}-[func]{knapsack_dfs} ``` 如下圖所示,由於每個物品都會產生不選和選兩條搜尋分支,因此時間複雜度為 $O(2^n)$ 。 觀察遞迴樹,容易發現其中存在重疊子問題,例如 $dp[1, 10]$ 等。而當物品較多、背包容量較大,尤其是相同重量的物品較多時,重疊子問題的數量將會大幅增多。 ![0-1 背包問題的暴力搜尋遞迴樹](knapsack_problem.assets/knapsack_dfs.png) ### 方法二:記憶化搜尋 為了保證重疊子問題只被計算一次,我們藉助記憶串列 `mem` 來記錄子問題的解,其中 `mem[i][c]` 對應 $dp[i, c]$ 。 引入記憶化之後,**時間複雜度取決於子問題數量**,也就是 $O(n \times cap)$ 。實現程式碼如下: ```src [file]{knapsack}-[class]{}-[func]{knapsack_dfs_mem} ``` 下圖展示了在記憶化搜尋中被剪掉的搜尋分支。 ![0-1 背包問題的記憶化搜尋遞迴樹](knapsack_problem.assets/knapsack_dfs_mem.png) ### 方法三:動態規劃 動態規劃實質上就是在狀態轉移中填充 $dp$ 表的過程,程式碼如下所示: ```src [file]{knapsack}-[class]{}-[func]{knapsack_dp} ``` 如下圖所示,時間複雜度和空間複雜度都由陣列 `dp` 大小決定,即 $O(n \times cap)$ 。 === "<1>" ![0-1 背包問題的動態規劃過程](knapsack_problem.assets/knapsack_dp_step1.png) === "<2>" ![knapsack_dp_step2](knapsack_problem.assets/knapsack_dp_step2.png) === "<3>" ![knapsack_dp_step3](knapsack_problem.assets/knapsack_dp_step3.png) === "<4>" ![knapsack_dp_step4](knapsack_problem.assets/knapsack_dp_step4.png) === "<5>" ![knapsack_dp_step5](knapsack_problem.assets/knapsack_dp_step5.png) === "<6>" ![knapsack_dp_step6](knapsack_problem.assets/knapsack_dp_step6.png) === "<7>" ![knapsack_dp_step7](knapsack_problem.assets/knapsack_dp_step7.png) === "<8>" ![knapsack_dp_step8](knapsack_problem.assets/knapsack_dp_step8.png) === "<9>" ![knapsack_dp_step9](knapsack_problem.assets/knapsack_dp_step9.png) === "<10>" ![knapsack_dp_step10](knapsack_problem.assets/knapsack_dp_step10.png) === "<11>" ![knapsack_dp_step11](knapsack_problem.assets/knapsack_dp_step11.png) === "<12>" ![knapsack_dp_step12](knapsack_problem.assets/knapsack_dp_step12.png) === "<13>" ![knapsack_dp_step13](knapsack_problem.assets/knapsack_dp_step13.png) === "<14>" ![knapsack_dp_step14](knapsack_problem.assets/knapsack_dp_step14.png) ### 空間最佳化 由於每個狀態都只與其上一行的狀態有關,因此我們可以使用兩個陣列滾動前進,將空間複雜度從 $O(n^2)$ 降至 $O(n)$ 。 進一步思考,我們能否僅用一個陣列實現空間最佳化呢?觀察可知,每個狀態都是由正上方或左上方的格子轉移過來的。假設只有一個陣列,當開始走訪第 $i$ 行時,該陣列儲存的仍然是第 $i-1$ 行的狀態。 - 如果採取正序走訪,那麼走訪到 $dp[i, j]$ 時,左上方 $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ 值可能已經被覆蓋,此時就無法得到正確的狀態轉移結果。 - 如果採取倒序走訪,則不會發生覆蓋問題,狀態轉移可以正確進行。 下圖展示了在單個陣列下從第 $i = 1$ 行轉換至第 $i = 2$ 行的過程。請思考正序走訪和倒序走訪的區別。 === "<1>" ![0-1 背包的空間最佳化後的動態規劃過程](knapsack_problem.assets/knapsack_dp_comp_step1.png) === "<2>" ![knapsack_dp_comp_step2](knapsack_problem.assets/knapsack_dp_comp_step2.png) === "<3>" ![knapsack_dp_comp_step3](knapsack_problem.assets/knapsack_dp_comp_step3.png) === "<4>" ![knapsack_dp_comp_step4](knapsack_problem.assets/knapsack_dp_comp_step4.png) === "<5>" ![knapsack_dp_comp_step5](knapsack_problem.assets/knapsack_dp_comp_step5.png) === "<6>" ![knapsack_dp_comp_step6](knapsack_problem.assets/knapsack_dp_comp_step6.png) 在程式碼實現中,我們僅需將陣列 `dp` 的第一維 $i$ 直接刪除,並且把內迴圈更改為倒序走訪即可: ```src [file]{knapsack}-[class]{}-[func]{knapsack_dp_comp} ``` ================================================ FILE: zh-hant/docs/chapter_dynamic_programming/summary.md ================================================ # 小結 ### 重點回顧 - 動態規劃對問題進行分解,並透過儲存子問題的解來規避重複計算,提高計算效率。 - 不考慮時間的前提下,所有動態規劃問題都可以用回溯(暴力搜尋)進行求解,但遞迴樹中存在大量的重疊子問題,效率極低。透過引入記憶化串列,可以儲存所有計算過的子問題的解,從而保證重疊子問題只被計算一次。 - 記憶化搜尋是一種從頂至底的遞迴式解法,而與之對應的動態規劃是一種從底至頂的遞推式解法,其如同“填寫表格”一樣。由於當前狀態僅依賴某些區域性狀態,因此我們可以消除 $dp$ 表的一個維度,從而降低空間複雜度。 - 子問題分解是一種通用的演算法思路,在分治、動態規劃、回溯中具有不同的性質。 - 動態規劃問題有三大特性:重疊子問題、最優子結構、無後效性。 - 如果原問題的最優解可以從子問題的最優解構建得來,則它就具有最優子結構。 - 無後效性指對於一個狀態,其未來發展只與該狀態有關,而與過去經歷的所有狀態無關。許多組合最佳化問題不具有無後效性,無法使用動態規劃快速求解。 **背包問題** - 背包問題是最典型的動態規劃問題之一,具有 0-1 背包、完全背包、多重背包等變種。 - 0-1 背包的狀態定義為前 $i$ 個物品在容量為 $c$ 的背包中的最大價值。根據不放入背包和放入背包兩種決策,可得到最優子結構,並構建出狀態轉移方程。在空間最佳化中,由於每個狀態依賴正上方和左上方的狀態,因此需要倒序走訪串列,避免左上方狀態被覆蓋。 - 完全背包問題的每種物品的選取數量無限制,因此選擇放入物品的狀態轉移與 0-1 背包問題不同。由於狀態依賴正上方和正左方的狀態,因此在空間最佳化中應當正序走訪。 - 零錢兌換問題是完全背包問題的一個變種。它從求“最大”價值變為求“最小”硬幣數量,因此狀態轉移方程中的 $\max()$ 應改為 $\min()$ 。從追求“不超過”背包容量到追求“恰好”湊出目標金額,因此使用 $amt + 1$ 來表示“無法湊出目標金額”的無效解。 - 零錢兌換問題 II 從求“最少硬幣數量”改為求“硬幣組合數量”,狀態轉移方程相應地從 $\min()$ 改為求和運算子。 **編輯距離問題** - 編輯距離(Levenshtein 距離)用於衡量兩個字串之間的相似度,其定義為從一個字串到另一個字串的最少編輯步數,編輯操作包括新增、刪除、替換。 - 編輯距離問題的狀態定義為將 $s$ 的前 $i$ 個字元更改為 $t$ 的前 $j$ 個字元所需的最少編輯步數。當 $s[i] \ne t[j]$ 時,具有三種決策:新增、刪除、替換,它們都有相應的剩餘子問題。據此便可以找出最優子結構與構建狀態轉移方程。而當 $s[i] = t[j]$ 時,無須編輯當前字元。 - 在編輯距離中,狀態依賴其正上方、正左方、左上方的狀態,因此空間最佳化後正序或倒序走訪都無法正確地進行狀態轉移。為此,我們利用一個變數暫存左上方狀態,從而轉化到與完全背包問題等價的情況,可以在空間最佳化後進行正序走訪。 ================================================ FILE: zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md ================================================ # 完全背包問題 在本節中,我們先求解另一個常見的背包問題:完全背包,再瞭解它的一種特例:零錢兌換。 ## 完全背包問題 !!! question 給定 $n$ 個物品,第 $i$ 個物品的重量為 $wgt[i-1]$、價值為 $val[i-1]$ ,和一個容量為 $cap$ 的背包。**每個物品可以重複選取**,問在限定背包容量下能放入物品的最大價值。示例如下圖所示。 ![完全背包問題的示例資料](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png) ### 動態規劃思路 完全背包問題和 0-1 背包問題非常相似,**區別僅在於不限制物品的選擇次數**。 - 在 0-1 背包問題中,每種物品只有一個,因此將物品 $i$ 放入背包後,只能從前 $i-1$ 個物品中選擇。 - 在完全背包問題中,每種物品的數量是無限的,因此將物品 $i$ 放入背包後,**仍可以從前 $i$ 個物品中選擇**。 在完全背包問題的規定下,狀態 $[i, c]$ 的變化分為兩種情況。 - **不放入物品 $i$** :與 0-1 背包問題相同,轉移至 $[i-1, c]$ 。 - **放入物品 $i$** :與 0-1 背包問題不同,轉移至 $[i, c-wgt[i-1]]$ 。 從而狀態轉移方程變為: $$ dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) $$ ### 程式碼實現 對比兩道題目的程式碼,狀態轉移中有一處從 $i-1$ 變為 $i$ ,其餘完全一致: ```src [file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp} ``` ### 空間最佳化 由於當前狀態是從左邊和上邊的狀態轉移而來的,**因此空間最佳化後應該對 $dp$ 表中的每一行進行正序走訪**。 這個走訪順序與 0-1 背包正好相反。請藉助下圖來理解兩者的區別。 === "<1>" ![完全背包問題在空間最佳化後的動態規劃過程](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png) === "<2>" ![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png) === "<3>" ![unbounded_knapsack_dp_comp_step3](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png) === "<4>" ![unbounded_knapsack_dp_comp_step4](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png) === "<5>" ![unbounded_knapsack_dp_comp_step5](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png) === "<6>" ![unbounded_knapsack_dp_comp_step6](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png) 程式碼實現比較簡單,僅需將陣列 `dp` 的第一維刪除: ```src [file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp_comp} ``` ## 零錢兌換問題 背包問題是一大類動態規劃問題的代表,其擁有很多變種,例如零錢兌換問題。 !!! question 給定 $n$ 種硬幣,第 $i$ 種硬幣的面值為 $coins[i - 1]$ ,目標金額為 $amt$ ,**每種硬幣可以重複選取**,問能夠湊出目標金額的最少硬幣數量。如果無法湊出目標金額,則返回 $-1$ 。示例如下圖所示。 ![零錢兌換問題的示例資料](unbounded_knapsack_problem.assets/coin_change_example.png) ### 動態規劃思路 **零錢兌換可以看作完全背包問題的一種特殊情況**,兩者具有以下關聯與不同點。 - 兩道題可以相互轉換,“物品”對應“硬幣”、“物品重量”對應“硬幣面值”、“背包容量”對應“目標金額”。 - 最佳化目標相反,完全背包問題是要最大化物品價值,零錢兌換問題是要最小化硬幣數量。 - 完全背包問題是求“不超過”背包容量下的解,零錢兌換是求“恰好”湊到目標金額的解。 **第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表** 狀態 $[i, a]$ 對應的子問題為:**前 $i$ 種硬幣能夠湊出金額 $a$ 的最少硬幣數量**,記為 $dp[i, a]$ 。 二維 $dp$ 表的尺寸為 $(n+1) \times (amt+1)$ 。 **第二步:找出最優子結構,進而推導出狀態轉移方程** 本題與完全背包問題的狀態轉移方程存在以下兩點差異。 - 本題要求最小值,因此需將運算子 $\max()$ 更改為 $\min()$ 。 - 最佳化主體是硬幣數量而非商品價值,因此在選中硬幣時執行 $+1$ 即可。 $$ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) $$ **第三步:確定邊界條件和狀態轉移順序** 當目標金額為 $0$ 時,湊出它的最少硬幣數量為 $0$ ,即首列所有 $dp[i, 0]$ 都等於 $0$ 。 當無硬幣時,**無法湊出任意 $> 0$ 的目標金額**,即是無效解。為使狀態轉移方程中的 $\min()$ 函式能夠識別並過濾無效解,我們考慮使用 $+ \infty$ 來表示它們,即令首行所有 $dp[0, a]$ 都等於 $+ \infty$ 。 ### 程式碼實現 大多數程式語言並未提供 $+ \infty$ 變數,只能使用整型 `int` 的最大值來代替。而這又會導致大數越界:狀態轉移方程中的 $+ 1$ 操作可能發生溢位。 為此,我們採用數字 $amt + 1$ 來表示無效解,因為湊出 $amt$ 的硬幣數量最多為 $amt$ 。最後返回前,判斷 $dp[n, amt]$ 是否等於 $amt + 1$ ,若是則返回 $-1$ ,代表無法湊出目標金額。程式碼如下所示: ```src [file]{coin_change}-[class]{}-[func]{coin_change_dp} ``` 下圖展示了零錢兌換的動態規劃過程,和完全背包問題非常相似。 === "<1>" ![零錢兌換問題的動態規劃過程](unbounded_knapsack_problem.assets/coin_change_dp_step1.png) === "<2>" ![coin_change_dp_step2](unbounded_knapsack_problem.assets/coin_change_dp_step2.png) === "<3>" ![coin_change_dp_step3](unbounded_knapsack_problem.assets/coin_change_dp_step3.png) === "<4>" ![coin_change_dp_step4](unbounded_knapsack_problem.assets/coin_change_dp_step4.png) === "<5>" ![coin_change_dp_step5](unbounded_knapsack_problem.assets/coin_change_dp_step5.png) === "<6>" ![coin_change_dp_step6](unbounded_knapsack_problem.assets/coin_change_dp_step6.png) === "<7>" ![coin_change_dp_step7](unbounded_knapsack_problem.assets/coin_change_dp_step7.png) === "<8>" ![coin_change_dp_step8](unbounded_knapsack_problem.assets/coin_change_dp_step8.png) === "<9>" ![coin_change_dp_step9](unbounded_knapsack_problem.assets/coin_change_dp_step9.png) === "<10>" ![coin_change_dp_step10](unbounded_knapsack_problem.assets/coin_change_dp_step10.png) === "<11>" ![coin_change_dp_step11](unbounded_knapsack_problem.assets/coin_change_dp_step11.png) === "<12>" ![coin_change_dp_step12](unbounded_knapsack_problem.assets/coin_change_dp_step12.png) === "<13>" ![coin_change_dp_step13](unbounded_knapsack_problem.assets/coin_change_dp_step13.png) === "<14>" ![coin_change_dp_step14](unbounded_knapsack_problem.assets/coin_change_dp_step14.png) === "<15>" ![coin_change_dp_step15](unbounded_knapsack_problem.assets/coin_change_dp_step15.png) ### 空間最佳化 零錢兌換的空間最佳化的處理方式和完全背包問題一致: ```src [file]{coin_change}-[class]{}-[func]{coin_change_dp_comp} ``` ## 零錢兌換問題 II !!! question 給定 $n$ 種硬幣,第 $i$ 種硬幣的面值為 $coins[i - 1]$ ,目標金額為 $amt$ ,每種硬幣可以重複選取,**問湊出目標金額的硬幣組合數量**。示例如下圖所示。 ![零錢兌換問題 II 的示例資料](unbounded_knapsack_problem.assets/coin_change_ii_example.png) ### 動態規劃思路 相比於上一題,本題目標是求組合數量,因此子問題變為:**前 $i$ 種硬幣能夠湊出金額 $a$ 的組合數量**。而 $dp$ 表仍然是尺寸為 $(n+1) \times (amt + 1)$ 的二維矩陣。 當前狀態的組合數量等於不選當前硬幣與選當前硬幣這兩種決策的組合數量之和。狀態轉移方程為: $$ dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] $$ 當目標金額為 $0$ 時,無須選擇任何硬幣即可湊出目標金額,因此應將首列所有 $dp[i, 0]$ 都初始化為 $1$ 。當無硬幣時,無法湊出任何 $>0$ 的目標金額,因此首行所有 $dp[0, a]$ 都等於 $0$ 。 ### 程式碼實現 ```src [file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp} ``` ### 空間最佳化 空間最佳化處理方式相同,刪除硬幣維度即可: ```src [file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp_comp} ``` ================================================ FILE: zh-hant/docs/chapter_graph/graph.md ================================================ # 圖 圖(graph)是一種非線性資料結構,由頂點(vertex)邊(edge)組成。我們可以將圖 $G$ 抽象地表示為一組頂點 $V$ 和一組邊 $E$ 的集合。以下示例展示了一個包含 5 個頂點和 7 條邊的圖。 $$ \begin{aligned} V & = \{ 1, 2, 3, 4, 5 \} \newline E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline G & = \{ V, E \} \newline \end{aligned} $$ 如果將頂點看作節點,將邊看作連線各個節點的引用(指標),我們就可以將圖看作一種從鏈結串列拓展而來的資料結構。如下圖所示,**相較於線性關係(鏈結串列)和分治關係(樹),網路關係(圖)的自由度更高**,因而更為複雜。 ![鏈結串列、樹、圖之間的關係](graph.assets/linkedlist_tree_graph.png) ## 圖的常見型別與術語 根據邊是否具有方向,可分為無向圖(undirected graph)有向圖(directed graph),如下圖所示。 - 在無向圖中,邊表示兩頂點之間的“雙向”連線關係,例如微信或 QQ 中的“好友關係”。 - 在有向圖中,邊具有方向性,即 $A \rightarrow B$ 和 $A \leftarrow B$ 兩個方向的邊是相互獨立的,例如微博或抖音上的“關注”與“被關注”關係。 ![有向圖與無向圖](graph.assets/directed_graph.png) 根據所有頂點是否連通,可分為連通圖(connected graph)非連通圖(disconnected graph),如下圖所示。 - 對於連通圖,從某個頂點出發,可以到達其餘任意頂點。 - 對於非連通圖,從某個頂點出發,至少有一個頂點無法到達。 ![連通圖與非連通圖](graph.assets/connected_graph.png) 我們還可以為邊新增“權重”變數,從而得到如下圖所示的有權圖(weighted graph)。例如在《王者榮耀》等手遊中,系統會根據共同遊戲時間來計算玩家之間的“親密度”,這種親密度網路就可以用有權圖來表示。 ![有權圖與無權圖](graph.assets/weighted_graph.png) 圖資料結構包含以下常用術語。 - 鄰接(adjacency):當兩頂點之間存在邊相連時,稱這兩頂點“鄰接”。在上圖中,頂點 1 的鄰接頂點為頂點 2、3、5。 - 路徑(path):從頂點 A 到頂點 B 經過的邊構成的序列被稱為從 A 到 B 的“路徑”。在上圖中,邊序列 1-5-2-4 是頂點 1 到頂點 4 的一條路徑。 - 度(degree):一個頂點擁有的邊數。對於有向圖,入度(in-degree)表示有多少條邊指向該頂點,出度(out-degree)表示有多少條邊從該頂點指出。 ## 圖的表示 圖的常用表示方式包括“鄰接矩陣”和“鄰接表”。以下使用無向圖進行舉例。 ### 鄰接矩陣 設圖的頂點數量為 $n$ ,鄰接矩陣(adjacency matrix)使用一個 $n \times n$ 大小的矩陣來表示圖,每一行(列)代表一個頂點,矩陣元素代表邊,用 $1$ 或 $0$ 表示兩個頂點之間是否存在邊。 如下圖所示,設鄰接矩陣為 $M$、頂點串列為 $V$ ,那麼矩陣元素 $M[i, j] = 1$ 表示頂點 $V[i]$ 到頂點 $V[j]$ 之間存在邊,反之 $M[i, j] = 0$ 表示兩頂點之間無邊。 ![圖的鄰接矩陣表示](graph.assets/adjacency_matrix.png) 鄰接矩陣具有以下特性。 - 在簡單圖中,頂點不能與自身相連,此時鄰接矩陣主對角線元素沒有意義。 - 對於無向圖,兩個方向的邊等價,此時鄰接矩陣關於主對角線對稱。 - 將鄰接矩陣的元素從 $1$ 和 $0$ 替換為權重,則可表示有權圖。 使用鄰接矩陣表示圖時,我們可以直接訪問矩陣元素以獲取邊,因此增刪查改操作的效率很高,時間複雜度均為 $O(1)$ 。然而,矩陣的空間複雜度為 $O(n^2)$ ,記憶體佔用較多。 ### 鄰接表 鄰接表(adjacency list)使用 $n$ 個鏈結串列來表示圖,鏈結串列節點表示頂點。第 $i$ 個鏈結串列對應頂點 $i$ ,其中儲存了該頂點的所有鄰接頂點(與該頂點相連的頂點)。下圖展示了一個使用鄰接表儲存的圖的示例。 ![圖的鄰接表表示](graph.assets/adjacency_list.png) 鄰接表僅儲存實際存在的邊,而邊的總數通常遠小於 $n^2$ ,因此它更加節省空間。然而,在鄰接表中需要透過走訪鏈結串列來查詢邊,因此其時間效率不如鄰接矩陣。 觀察上圖,**鄰接表結構與雜湊表中的“鏈式位址”非常相似,因此我們也可以採用類似的方法來最佳化效率**。比如當鏈結串列較長時,可以將鏈結串列轉化為 AVL 樹或紅黑樹,從而將時間效率從 $O(n)$ 最佳化至 $O(\log n)$ ;還可以把鏈結串列轉換為雜湊表,從而將時間複雜度降至 $O(1)$ 。 ## 圖的常見應用 如下表所示,許多現實系統可以用圖來建模,相應的問題也可以約化為圖計算問題。

  現實生活中常見的圖

| | 頂點 | 邊 | 圖計算問題 | | -------- | ---- | -------------------- | ------------ | | 社交網路 | 使用者 | 好友關係 | 潛在好友推薦 | | 地鐵線路 | 站點 | 站點間的連通性 | 最短路線推薦 | | 太陽系 | 星體 | 星體間的萬有引力作用 | 行星軌道計算 | ================================================ FILE: zh-hant/docs/chapter_graph/graph_operations.md ================================================ # 圖的基礎操作 圖的基礎操作可分為對“邊”的操作和對“頂點”的操作。在“鄰接矩陣”和“鄰接表”兩種表示方法下,實現方式有所不同。 ## 基於鄰接矩陣的實現 給定一個頂點數量為 $n$ 的無向圖,則各種操作的實現方式如下圖所示。 - **新增或刪除邊**:直接在鄰接矩陣中修改指定的邊即可,使用 $O(1)$ 時間。而由於是無向圖,因此需要同時更新兩個方向的邊。 - **新增頂點**:在鄰接矩陣的尾部新增一行一列,並全部填 $0$ 即可,使用 $O(n)$ 時間。 - **刪除頂點**:在鄰接矩陣中刪除一行一列。當刪除首行首列時達到最差情況,需要將 $(n-1)^2$ 個元素“向左上移動”,從而使用 $O(n^2)$ 時間。 - **初始化**:傳入 $n$ 個頂點,初始化長度為 $n$ 的頂點串列 `vertices` ,使用 $O(n)$ 時間;初始化 $n \times n$ 大小的鄰接矩陣 `adjMat` ,使用 $O(n^2)$ 時間。 === "<1>" ![鄰接矩陣的初始化、增刪邊、增刪頂點](graph_operations.assets/adjacency_matrix_step1_initialization.png) === "<2>" ![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png) === "<3>" ![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png) === "<4>" ![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png) === "<5>" ![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png) 以下是基於鄰接矩陣表示圖的實現程式碼: ```src [file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{} ``` ## 基於鄰接表的實現 設無向圖的頂點總數為 $n$、邊總數為 $m$ ,則可根據下圖所示的方法實現各種操作。 - **新增邊**:在頂點對應鏈結串列的末尾新增邊即可,使用 $O(1)$ 時間。因為是無向圖,所以需要同時新增兩個方向的邊。 - **刪除邊**:在頂點對應鏈結串列中查詢並刪除指定邊,使用 $O(m)$ 時間。在無向圖中,需要同時刪除兩個方向的邊。 - **新增頂點**:在鄰接表中新增一個鏈結串列,並將新增頂點作為鏈結串列頭節點,使用 $O(1)$ 時間。 - **刪除頂點**:需走訪整個鄰接表,刪除包含指定頂點的所有邊,使用 $O(n + m)$ 時間。 - **初始化**:在鄰接表中建立 $n$ 個頂點和 $2m$ 條邊,使用 $O(n + m)$ 時間。 === "<1>" ![鄰接表的初始化、增刪邊、增刪頂點](graph_operations.assets/adjacency_list_step1_initialization.png) === "<2>" ![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png) === "<3>" ![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png) === "<4>" ![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png) === "<5>" ![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png) 以下是鄰接表的程式碼實現。對比上圖,實際程式碼有以下不同。 - 為了方便新增與刪除頂點,以及簡化程式碼,我們使用串列(動態陣列)來代替鏈結串列。 - 使用雜湊表來儲存鄰接表,`key` 為頂點例項,`value` 為該頂點的鄰接頂點串列(鏈結串列)。 另外,我們在鄰接表中使用 `Vertex` 類別來表示頂點,這樣做的原因是:如果與鄰接矩陣一樣,用串列索引來區分不同頂點,那麼假設要刪除索引為 $i$ 的頂點,則需走訪整個鄰接表,將所有大於 $i$ 的索引全部減 $1$ ,效率很低。而如果每個頂點都是唯一的 `Vertex` 例項,刪除某一頂點之後就無須改動其他頂點了。 ```src [file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{} ``` ## 效率對比 設圖中共有 $n$ 個頂點和 $m$ 條邊,下表對比了鄰接矩陣和鄰接表的時間效率和空間效率。請注意,鄰接表(鏈結串列)對應本文實現,而鄰接表(雜湊表)專指將所有鏈結串列替換為雜湊表後的實現。

  鄰接矩陣與鄰接表對比

| | 鄰接矩陣 | 鄰接表(鏈結串列) | 鄰接表(雜湊表) | | ------------ | -------- | -------------- | ---------------- | | 判斷是否鄰接 | $O(1)$ | $O(n)$ | $O(1)$ | | 新增邊 | $O(1)$ | $O(1)$ | $O(1)$ | | 刪除邊 | $O(1)$ | $O(n)$ | $O(1)$ | | 新增頂點 | $O(n)$ | $O(1)$ | $O(1)$ | | 刪除頂點 | $O(n^2)$ | $O(n + m)$ | $O(n)$ | | 記憶體空間佔用 | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | 觀察上表,似乎鄰接表(雜湊表)的時間效率與空間效率最優。但實際上,在鄰接矩陣中操作邊的效率更高,只需一次陣列訪問或賦值操作即可。綜合來看,鄰接矩陣體現了“以空間換時間”的原則,而鄰接表體現了“以時間換空間”的原則。 ================================================ FILE: zh-hant/docs/chapter_graph/graph_traversal.md ================================================ # 圖的走訪 樹代表的是“一對多”的關係,而圖則具有更高的自由度,可以表示任意的“多對多”關係。因此,我們可以把樹看作圖的一種特例。顯然,**樹的走訪操作也是圖的走訪操作的一種特例**。 圖和樹都需要應用搜索演算法來實現走訪操作。圖的走訪方式也可分為兩種:廣度優先走訪深度優先走訪。 ## 廣度優先走訪 **廣度優先走訪是一種由近及遠的走訪方式,從某個節點出發,始終優先訪問距離最近的頂點,並一層層向外擴張**。如下圖所示,從左上角頂點出發,首先走訪該頂點的所有鄰接頂點,然後走訪下一個頂點的所有鄰接頂點,以此類推,直至所有頂點訪問完畢。 ![圖的廣度優先走訪](graph_traversal.assets/graph_bfs.png) ### 演算法實現 BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入先出”的性質,這與 BFS 的“由近及遠”的思想異曲同工。 1. 將走訪起始頂點 `startVet` 加入佇列,並開啟迴圈。 2. 在迴圈的每輪迭代中,彈出佇列首頂點並記錄訪問,然後將該頂點的所有鄰接頂點加入到佇列尾部。 3. 迴圈步驟 `2.` ,直到所有頂點被訪問完畢後結束。 為了防止重複走訪頂點,我們需要藉助一個雜湊集合 `visited` 來記錄哪些節點已被訪問。 !!! tip 雜湊集合可以看作一個只儲存 `key` 而不儲存 `value` 的雜湊表,它可以在 $O(1)$ 時間複雜度下進行 `key` 的增刪查改操作。根據 `key` 的唯一性,雜湊集合通常用於資料去重等場景。 ```src [file]{graph_bfs}-[class]{}-[func]{graph_bfs} ``` 程式碼相對抽象,建議對照下圖來加深理解。 === "<1>" ![圖的廣度優先走訪步驟](graph_traversal.assets/graph_bfs_step1.png) === "<2>" ![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png) === "<3>" ![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png) === "<4>" ![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png) === "<5>" ![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png) === "<6>" ![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png) === "<7>" ![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png) === "<8>" ![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png) === "<9>" ![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png) === "<10>" ![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png) === "<11>" ![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png) !!! question "廣度優先走訪的序列是否唯一?" 不唯一。廣度優先走訪只要求按“由近及遠”的順序走訪,**而多個相同距離的頂點的走訪順序允許被任意打亂**。以上圖為例,頂點 $1$、$3$ 的訪問順序可以交換,頂點 $2$、$4$、$6$ 的訪問順序也可以任意交換。 ### 複雜度分析 **時間複雜度**:所有頂點都會入列並出隊一次,使用 $O(|V|)$ 時間;在走訪鄰接頂點的過程中,由於是無向圖,因此所有邊都會被訪問 $2$ 次,使用 $O(2|E|)$ 時間;總體使用 $O(|V| + |E|)$ 時間。 **空間複雜度**:串列 `res` ,雜湊集合 `visited` ,佇列 `que` 中的頂點數量最多為 $|V|$ ,使用 $O(|V|)$ 空間。 ## 深度優先走訪 **深度優先走訪是一種優先走到底、無路可走再回頭的走訪方式**。如下圖所示,從左上角頂點出發,訪問當前頂點的某個鄰接頂點,直到走到盡頭時返回,再繼續走到盡頭並返回,以此類推,直至所有頂點走訪完成。 ![圖的深度優先走訪](graph_traversal.assets/graph_dfs.png) ### 演算法實現 這種“走到盡頭再返回”的演算法範式通常基於遞迴來實現。與廣度優先走訪類似,在深度優先走訪中,我們也需要藉助一個雜湊集合 `visited` 來記錄已被訪問的頂點,以避免重複訪問頂點。 ```src [file]{graph_dfs}-[class]{}-[func]{graph_dfs} ``` 深度優先走訪的演算法流程如下圖所示。 - **直虛線代表向下遞推**,表示開啟了一個新的遞迴方法來訪問新頂點。 - **曲虛線代表向上回溯**,表示此遞迴方法已經返回,回溯到了開啟此方法的位置。 為了加深理解,建議將下圖與程式碼結合起來,在腦中模擬(或者用筆畫下來)整個 DFS 過程,包括每個遞迴方法何時開啟、何時返回。 === "<1>" ![圖的深度優先走訪步驟](graph_traversal.assets/graph_dfs_step1.png) === "<2>" ![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png) === "<3>" ![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png) === "<4>" ![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png) === "<5>" ![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png) === "<6>" ![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png) === "<7>" ![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png) === "<8>" ![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png) === "<9>" ![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png) === "<10>" ![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png) === "<11>" ![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png) !!! question "深度優先走訪的序列是否唯一?" 與廣度優先走訪類似,深度優先走訪序列的順序也不是唯一的。給定某頂點,先往哪個方向探索都可以,即鄰接頂點的順序可以任意打亂,都是深度優先走訪。 以樹的走訪為例,“根 $\rightarrow$ 左 $\rightarrow$ 右”“左 $\rightarrow$ 根 $\rightarrow$ 右”“左 $\rightarrow$ 右 $\rightarrow$ 根”分別對應前序、中序、後序走訪,它們展示了三種走訪優先順序,然而這三者都屬於深度優先走訪。 ### 複雜度分析 **時間複雜度**:所有頂點都會被訪問 $1$ 次,使用 $O(|V|)$ 時間;所有邊都會被訪問 $2$ 次,使用 $O(2|E|)$ 時間;總體使用 $O(|V| + |E|)$ 時間。 **空間複雜度**:串列 `res` ,雜湊集合 `visited` 頂點數量最多為 $|V|$ ,遞迴深度最大為 $|V|$ ,因此使用 $O(|V|)$ 空間。 ================================================ FILE: zh-hant/docs/chapter_graph/index.md ================================================ # 圖 ![圖](../assets/covers/chapter_graph.jpg) !!! abstract 在生命旅途中,我們就像是一個個節點,被無數看不見的邊相連。 每一次的相識與相離,都在這張巨大的網路圖中留下獨特的印記。 ================================================ FILE: zh-hant/docs/chapter_graph/summary.md ================================================ # 小結 ### 重點回顧 - 圖由頂點和邊組成,可以表示為一組頂點和一組邊構成的集合。 - 相較於線性關係(鏈結串列)和分治關係(樹),網路關係(圖)具有更高的自由度,因而更為複雜。 - 有向圖的邊具有方向性,連通圖中的任意頂點均可達,有權圖的每條邊都包含權重變數。 - 鄰接矩陣利用矩陣來表示圖,每一行(列)代表一個頂點,矩陣元素代表邊,用 $1$ 或 $0$ 表示兩個頂點之間有邊或無邊。鄰接矩陣在增刪查改操作上效率很高,但空間佔用較多。 - 鄰接表使用多個鏈結串列來表示圖,第 $i$ 個鏈結串列對應頂點 $i$ ,其中儲存了該頂點的所有鄰接頂點。鄰接表相對於鄰接矩陣更加節省空間,但由於需要走訪鏈結串列來查詢邊,因此時間效率較低。 - 當鄰接表中的鏈結串列過長時,可以將其轉換為紅黑樹或雜湊表,從而提升查詢效率。 - 從演算法思想的角度分析,鄰接矩陣體現了“以空間換時間”,鄰接表體現了“以時間換空間”。 - 圖可用於建模各類現實系統,如社交網路、地鐵線路等。 - 樹是圖的一種特例,樹的走訪也是圖的走訪的一種特例。 - 圖的廣度優先走訪是一種由近及遠、層層擴張的搜尋方式,通常藉助佇列實現。 - 圖的深度優先走訪是一種優先走到底、無路可走時再回溯的搜尋方式,常基於遞迴來實現。 ### Q & A **Q**:路徑的定義是頂點序列還是邊序列? 維基百科上不同語言版本的定義不一致:英文版是“路徑是一個邊序列”,而中文版是“路徑是一個頂點序列”。以下是英文版原文:In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices. 在本文中,路徑被視為一個邊序列,而不是一個頂點序列。這是因為兩個頂點之間可能存在多條邊連線,此時每條邊都對應一條路徑。 **Q**:非連通圖中是否會有無法走訪到的點? 在非連通圖中,從某個頂點出發,至少有一個頂點無法到達。走訪非連通圖需要設定多個起點,以走訪到圖的所有連通分量。 **Q**:在鄰接表中,“與該頂點相連的所有頂點”的頂點順序是否有要求? 可以是任意順序。但在實際應用中,可能需要按照指定規則來排序,比如按照頂點新增的次序,或者按照頂點值大小的順序等,這樣有助於快速查詢“帶有某種極值”的頂點。 ================================================ FILE: zh-hant/docs/chapter_greedy/fractional_knapsack_problem.md ================================================ # 分數背包問題 !!! question 給定 $n$ 個物品,第 $i$ 個物品的重量為 $wgt[i-1]$、價值為 $val[i-1]$ ,和一個容量為 $cap$ 的背包。每個物品只能選擇一次,**但可以選擇物品的一部分,價值根據選擇的重量比例計算**,問在限定背包容量下背包中物品的最大價值。示例如下圖所示。 ![分數背包問題的示例資料](fractional_knapsack_problem.assets/fractional_knapsack_example.png) 分數背包問題和 0-1 背包問題整體上非常相似,狀態包含當前物品 $i$ 和容量 $c$ ,目標是求限定背包容量下的最大價值。 不同點在於,本題允許只選擇物品的一部分。如下圖所示,**我們可以對物品任意地進行切分,並按照重量比例來計算相應價值**。 1. 對於物品 $i$ ,它在單位重量下的價值為 $val[i-1] / wgt[i-1]$ ,簡稱單位價值。 2. 假設放入一部分物品 $i$ ,重量為 $w$ ,則背包增加的價值為 $w \times val[i-1] / wgt[i-1]$ 。 ![物品在單位重量下的價值](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png) ### 貪婪策略確定 最大化背包內物品總價值,**本質上是最大化單位重量下的物品價值**。由此便可推理出下圖所示的貪婪策略。 1. 將物品按照單位價值從高到低進行排序。 2. 走訪所有物品,**每輪貪婪地選擇單位價值最高的物品**。 3. 若剩餘背包容量不足,則使用當前物品的一部分填滿背包。 ![分數背包問題的貪婪策略](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png) ### 程式碼實現 我們建立了一個物品類別 `Item` ,以便將物品按照單位價值進行排序。迴圈進行貪婪選擇,當背包已滿時跳出並返回解: ```src [file]{fractional_knapsack}-[class]{}-[func]{fractional_knapsack} ``` 內建排序演算法的時間複雜度通常為 $O(\log n)$ ,空間複雜度通常為 $O(\log n)$ 或 $O(n)$ ,取決於程式語言的具體實現。 除排序之外,在最差情況下,需要走訪整個物品串列,**因此時間複雜度為 $O(n)$** ,其中 $n$ 為物品數量。 由於初始化了一個 `Item` 物件串列,**因此空間複雜度為 $O(n)$** 。 ### 正確性證明 採用反證法。假設物品 $x$ 是單位價值最高的物品,使用某演算法求得最大價值為 `res` ,但該解中不包含物品 $x$ 。 現在從背包中拿出單位重量的任意物品,並替換為單位重量的物品 $x$ 。由於物品 $x$ 的單位價值最高,因此替換後的總價值一定大於 `res` 。**這與 `res` 是最優解矛盾,說明最優解中必須包含物品 $x$** 。 對於該解中的其他物品,我們也可以構建出上述矛盾。總而言之,**單位價值更大的物品總是更優選擇**,這說明貪婪策略是有效的。 如下圖所示,如果將物品重量和物品單位價值分別看作一張二維圖表的橫軸和縱軸,則分數背包問題可轉化為“求在有限橫軸區間下圍成的最大面積”。這個類比可以幫助我們從幾何角度理解貪婪策略的有效性。 ![分數背包問題的幾何表示](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png) ================================================ FILE: zh-hant/docs/chapter_greedy/greedy_algorithm.md ================================================ # 貪婪演算法 貪婪演算法(greedy algorithm)是一種常見的解決最佳化問題的演算法,其基本思想是在問題的每個決策階段,都選擇當前看起來最優的選擇,即貪婪地做出區域性最優的決策,以期獲得全域性最優解。貪婪演算法簡潔且高效,在許多實際問題中有著廣泛的應用。 貪婪演算法和動態規劃都常用於解決最佳化問題。它們之間存在一些相似之處,比如都依賴最優子結構性質,但工作原理不同。 - 動態規劃會根據之前階段的所有決策來考慮當前決策,並使用過去子問題的解來構建當前子問題的解。 - 貪婪演算法不會考慮過去的決策,而是一路向前地進行貪婪選擇,不斷縮小問題範圍,直至問題被解決。 我們先透過例題“零錢兌換”瞭解貪婪演算法的工作原理。這道題已經在“完全背包問題”章節中介紹過,相信你對它並不陌生。 !!! question 給定 $n$ 種硬幣,第 $i$ 種硬幣的面值為 $coins[i - 1]$ ,目標金額為 $amt$ ,每種硬幣可以重複選取,問能夠湊出目標金額的最少硬幣數量。如果無法湊出目標金額,則返回 $-1$ 。 本題採取的貪婪策略如下圖所示。給定目標金額,**我們貪婪地選擇不大於且最接近它的硬幣**,不斷迴圈該步驟,直至湊出目標金額為止。 ![零錢兌換的貪婪策略](greedy_algorithm.assets/coin_change_greedy_strategy.png) 實現程式碼如下所示: ```src [file]{coin_change_greedy}-[class]{}-[func]{coin_change_greedy} ``` 你可能會不由地發出感嘆:So clean !貪婪演算法僅用約十行程式碼就解決了零錢兌換問題。 ## 貪婪演算法的優點與侷限性 **貪婪演算法不僅操作直接、實現簡單,而且通常效率也很高**。在以上程式碼中,記硬幣最小面值為 $\min(coins)$ ,則貪婪選擇最多迴圈 $amt / \min(coins)$ 次,時間複雜度為 $O(amt / \min(coins))$ 。這比動態規劃解法的時間複雜度 $O(n \times amt)$ 小了一個數量級。 然而,**對於某些硬幣面值組合,貪婪演算法並不能找到最優解**。下圖給出了兩個示例。 - **正例 $coins = [1, 5, 10, 20, 50, 100]$**:在該硬幣組合下,給定任意 $amt$ ,貪婪演算法都可以找到最優解。 - **反例 $coins = [1, 20, 50]$**:假設 $amt = 60$ ,貪婪演算法只能找到 $50 + 1 \times 10$ 的兌換組合,共計 $11$ 枚硬幣,但動態規劃可以找到最優解 $20 + 20 + 20$ ,僅需 $3$ 枚硬幣。 - **反例 $coins = [1, 49, 50]$**:假設 $amt = 98$ ,貪婪演算法只能找到 $50 + 1 \times 48$ 的兌換組合,共計 $49$ 枚硬幣,但動態規劃可以找到最優解 $49 + 49$ ,僅需 $2$ 枚硬幣。 ![貪婪演算法無法找出最優解的示例](greedy_algorithm.assets/coin_change_greedy_vs_dp.png) 也就是說,對於零錢兌換問題,貪婪演算法無法保證找到全域性最優解,並且有可能找到非常差的解。它更適合用動態規劃解決。 一般情況下,貪婪演算法的適用情況分以下兩種。 1. **可以保證找到最優解**:貪婪演算法在這種情況下往往是最優選擇,因為它往往比回溯、動態規劃更高效。 2. **可以找到近似最優解**:貪婪演算法在這種情況下也是可用的。對於很多複雜問題來說,尋找全域性最優解非常困難,能以較高效率找到次優解也是非常不錯的。 ## 貪婪演算法特性 那麼問題來了,什麼樣的問題適合用貪婪演算法求解呢?或者說,貪婪演算法在什麼情況下可以保證找到最優解? 相較於動態規劃,貪婪演算法的使用條件更加苛刻,其主要關注問題的兩個性質。 - **貪婪選擇性質**:只有當局部最優選擇始終可以導致全域性最優解時,貪婪演算法才能保證得到最優解。 - **最優子結構**:原問題的最優解包含子問題的最優解。 最優子結構已經在“動態規劃”章節中介紹過,這裡不再贅述。值得注意的是,一些問題的最優子結構並不明顯,但仍然可使用貪婪演算法解決。 我們主要探究貪婪選擇性質的判斷方法。雖然它的描述看上去比較簡單,**但實際上對於許多問題,證明貪婪選擇性質並非易事**。 例如零錢兌換問題,我們雖然能夠容易地舉出反例,對貪婪選擇性質進行證偽,但證實的難度較大。如果問:**滿足什麼條件的硬幣組合可以使用貪婪演算法求解**?我們往往只能憑藉直覺或舉例子來給出一個模稜兩可的答案,而難以給出嚴謹的數學證明。 !!! quote 有一篇論文給出了一個 $O(n^3)$ 時間複雜度的演算法,用於判斷一個硬幣組合能否使用貪婪演算法找出任意金額的最優解。 Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234. ## 貪婪演算法解題步驟 貪婪問題的解決流程大體可分為以下三步。 1. **問題分析**:梳理與理解問題特性,包括狀態定義、最佳化目標和約束條件等。這一步在回溯和動態規劃中都有涉及。 2. **確定貪婪策略**:確定如何在每一步中做出貪婪選擇。這個策略能夠在每一步減小問題的規模,並最終解決整個問題。 3. **正確性證明**:通常需要證明問題具有貪婪選擇性質和最優子結構。這個步驟可能需要用到數學證明,例如歸納法或反證法等。 確定貪婪策略是求解問題的核心步驟,但實施起來可能並不容易,主要有以下原因。 - **不同問題的貪婪策略的差異較大**。對於許多問題來說,貪婪策略比較淺顯,我們透過一些大概的思考與嘗試就能得出。而對於一些複雜問題,貪婪策略可能非常隱蔽,這種情況就非常考驗個人的解題經驗與演算法能力了。 - **某些貪婪策略具有較強的迷惑性**。當我們滿懷信心設計好貪婪策略,寫出解題程式碼並提交執行,很可能發現部分測試樣例無法透過。這是因為設計的貪婪策略只是“部分正確”的,上文介紹的零錢兌換就是一個典型案例。 為了保證正確性,我們應該對貪婪策略進行嚴謹的數學證明,**通常需要用到反證法或數學歸納法**。 然而,正確性證明也很可能不是一件易事。如若沒有頭緒,我們通常會選擇面向測試用例進行程式碼除錯,一步步修改與驗證貪婪策略。 ## 貪婪演算法典型例題 貪婪演算法常常應用在滿足貪婪選擇性質和最優子結構的最佳化問題中,以下列舉了一些典型的貪婪演算法問題。 - **硬幣找零問題**:在某些硬幣組合下,貪婪演算法總是可以得到最優解。 - **區間排程問題**:假設你有一些任務,每個任務在一段時間內進行,你的目標是完成儘可能多的任務。如果每次都選擇結束時間最早的任務,那麼貪婪演算法就可以得到最優解。 - **分數背包問題**:給定一組物品和一個載重量,你的目標是選擇一組物品,使得總重量不超過載重量,且總價值最大。如果每次都選擇價效比最高(價值 / 重量)的物品,那麼貪婪演算法在一些情況下可以得到最優解。 - **股票買賣問題**:給定一組股票的歷史價格,你可以進行多次買賣,但如果你已經持有股票,那麼在賣出之前不能再買,目標是獲取最大利潤。 - **霍夫曼編碼**:霍夫曼編碼是一種用於無損資料壓縮的貪婪演算法。透過構建霍夫曼樹,每次選擇出現頻率最低的兩個節點合併,最後得到的霍夫曼樹的帶權路徑長度(編碼長度)最小。 - **Dijkstra 演算法**:它是一種解決給定源頂點到其餘各頂點的最短路徑問題的貪婪演算法。 ================================================ FILE: zh-hant/docs/chapter_greedy/index.md ================================================ # 貪婪 ![貪婪](../assets/covers/chapter_greedy.jpg) !!! abstract 向日葵朝著太陽轉動,時刻追求自身成長的最大可能。 貪婪策略在一輪輪的簡單選擇中,逐步導向最佳答案。 ================================================ FILE: zh-hant/docs/chapter_greedy/max_capacity_problem.md ================================================ # 最大容量問題 !!! question 輸入一個陣列 $ht$ ,其中的每個元素代表一個垂直隔板的高度。陣列中的任意兩個隔板,以及它們之間的空間可以組成一個容器。 容器的容量等於高度和寬度的乘積(面積),其中高度由較短的隔板決定,寬度是兩個隔板的陣列索引之差。 請在陣列中選擇兩個隔板,使得組成的容器的容量最大,返回最大容量。示例如下圖所示。 ![最大容量問題的示例資料](max_capacity_problem.assets/max_capacity_example.png) 容器由任意兩個隔板圍成,**因此本題的狀態為兩個隔板的索引,記為 $[i, j]$** 。 根據題意,容量等於高度乘以寬度,其中高度由短板決定,寬度是兩隔板的陣列索引之差。設容量為 $cap[i, j]$ ,則可得計算公式: $$ cap[i, j] = \min(ht[i], ht[j]) \times (j - i) $$ 設陣列長度為 $n$ ,兩個隔板的組合數量(狀態總數)為 $C_n^2 = \frac{n(n - 1)}{2}$ 個。最直接地,**我們可以窮舉所有狀態**,從而求得最大容量,時間複雜度為 $O(n^2)$ 。 ### 貪婪策略確定 這道題還有更高效率的解法。如下圖所示,現選取一個狀態 $[i, j]$ ,其滿足索引 $i < j$ 且高度 $ht[i] < ht[j]$ ,即 $i$ 為短板、$j$ 為長板。 ![初始狀態](max_capacity_problem.assets/max_capacity_initial_state.png) 如下圖所示,**若此時將長板 $j$ 向短板 $i$ 靠近,則容量一定變小**。 這是因為在移動長板 $j$ 後,寬度 $j-i$ 肯定變小;而高度由短板決定,因此高度只可能不變( $i$ 仍為短板)或變小(移動後的 $j$ 成為短板)。 ![向內移動長板後的狀態](max_capacity_problem.assets/max_capacity_moving_long_board.png) 反向思考,**我們只有向內收縮短板 $i$ ,才有可能使容量變大**。因為雖然寬度一定變小,**但高度可能會變大**(移動後的短板 $i$ 可能會變長)。例如在下圖中,移動短板後面積變大。 ![向內移動短板後的狀態](max_capacity_problem.assets/max_capacity_moving_short_board.png) 由此便可推出本題的貪婪策略:初始化兩指標,使其分列容器兩端,每輪向內收縮短板對應的指標,直至兩指標相遇。 下圖展示了貪婪策略的執行過程。 1. 初始狀態下,指標 $i$ 和 $j$ 分列陣列兩端。 2. 計算當前狀態的容量 $cap[i, j]$ ,並更新最大容量。 3. 比較板 $i$ 和板 $j$ 的高度,並將短板向內移動一格。 4. 迴圈執行第 `2.` 步和第 `3.` 步,直至 $i$ 和 $j$ 相遇時結束。 === "<1>" ![最大容量問題的貪婪過程](max_capacity_problem.assets/max_capacity_greedy_step1.png) === "<2>" ![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png) === "<3>" ![max_capacity_greedy_step3](max_capacity_problem.assets/max_capacity_greedy_step3.png) === "<4>" ![max_capacity_greedy_step4](max_capacity_problem.assets/max_capacity_greedy_step4.png) === "<5>" ![max_capacity_greedy_step5](max_capacity_problem.assets/max_capacity_greedy_step5.png) === "<6>" ![max_capacity_greedy_step6](max_capacity_problem.assets/max_capacity_greedy_step6.png) === "<7>" ![max_capacity_greedy_step7](max_capacity_problem.assets/max_capacity_greedy_step7.png) === "<8>" ![max_capacity_greedy_step8](max_capacity_problem.assets/max_capacity_greedy_step8.png) === "<9>" ![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png) ### 程式碼實現 程式碼迴圈最多 $n$ 輪,**因此時間複雜度為 $O(n)$** 。 變數 $i$、$j$、$res$ 使用常數大小的額外空間,**因此空間複雜度為 $O(1)$** 。 ```src [file]{max_capacity}-[class]{}-[func]{max_capacity} ``` ### 正確性證明 之所以貪婪比窮舉更快,是因為每輪的貪婪選擇都會“跳過”一些狀態。 比如在狀態 $cap[i, j]$ 下,$i$ 為短板、$j$ 為長板。若貪婪地將短板 $i$ 向內移動一格,會導致下圖所示的狀態被“跳過”。**這意味著之後無法驗證這些狀態的容量大小**。 $$ cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1] $$ ![移動短板導致被跳過的狀態](max_capacity_problem.assets/max_capacity_skipped_states.png) 觀察發現,**這些被跳過的狀態實際上就是將長板 $j$ 向內移動的所有狀態**。前面我們已經證明內移長板一定會導致容量變小。也就是說,被跳過的狀態都不可能是最優解,**跳過它們不會導致錯過最優解**。 以上分析說明,移動短板的操作是“安全”的,貪婪策略是有效的。 ================================================ FILE: zh-hant/docs/chapter_greedy/max_product_cutting_problem.md ================================================ # 最大切分乘積問題 !!! question 給定一個正整數 $n$ ,將其切分為至少兩個正整數的和,求切分後所有整數的乘積最大是多少,如下圖所示。 ![最大切分乘積的問題定義](max_product_cutting_problem.assets/max_product_cutting_definition.png) 假設我們將 $n$ 切分為 $m$ 個整數因子,其中第 $i$ 個因子記為 $n_i$ ,即 $$ n = \sum_{i=1}^{m}n_i $$ 本題的目標是求得所有整數因子的最大乘積,即 $$ \max(\prod_{i=1}^{m}n_i) $$ 我們需要思考的是:切分數量 $m$ 應該多大,每個 $n_i$ 應該是多少? ### 貪婪策略確定 根據經驗,兩個整數的乘積往往比它們的加和更大。假設從 $n$ 中分出一個因子 $2$ ,則它們的乘積為 $2(n-2)$ 。我們將該乘積與 $n$ 作比較: $$ \begin{aligned} 2(n-2) & \geq n \newline 2n - n - 4 & \geq 0 \newline n & \geq 4 \end{aligned} $$ 如下圖所示,當 $n \geq 4$ 時,切分出一個 $2$ 後乘積會變大,**這說明大於等於 $4$ 的整數都應該被切分**。 **貪婪策略一**:如果切分方案中包含 $\geq 4$ 的因子,那麼它就應該被繼續切分。最終的切分方案只應出現 $1$、$2$、$3$ 這三種因子。 ![切分導致乘積變大](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png) 接下來思考哪個因子是最優的。在 $1$、$2$、$3$ 這三個因子中,顯然 $1$ 是最差的,因為 $1 \times (n-1) < n$ 恆成立,即切分出 $1$ 反而會導致乘積減小。 如下圖所示,當 $n = 6$ 時,有 $3 \times 3 > 2 \times 2 \times 2$ 。**這意味著切分出 $3$ 比切分出 $2$ 更優**。 **貪婪策略二**:在切分方案中,最多隻應存在兩個 $2$ 。因為三個 $2$ 總是可以替換為兩個 $3$ ,從而獲得更大的乘積。 ![最優切分因子](max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png) 綜上所述,可推理出以下貪婪策略。 1. 輸入整數 $n$ ,從其不斷地切分出因子 $3$ ,直至餘數為 $0$、$1$、$2$ 。 2. 當餘數為 $0$ 時,代表 $n$ 是 $3$ 的倍數,因此不做任何處理。 3. 當餘數為 $2$ 時,不繼續劃分,保留。 4. 當餘數為 $1$ 時,由於 $2 \times 2 > 1 \times 3$ ,因此應將最後一個 $3$ 替換為 $2$ 。 ### 程式碼實現 如下圖所示,我們無須透過迴圈來切分整數,而可以利用向下整除運算得到 $3$ 的個數 $a$ ,用取模運算得到餘數 $b$ ,此時有: $$ n = 3 a + b $$ 請注意,對於 $n \leq 3$ 的邊界情況,必須拆分出一個 $1$ ,乘積為 $1 \times (n - 1)$ 。 ```src [file]{max_product_cutting}-[class]{}-[func]{max_product_cutting} ``` ![最大切分乘積的計算方法](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png) **時間複雜度取決於程式語言的冪運算的實現方法**。以 Python 為例,常用的冪計算函式有三種。 - 運算子 `**` 和函式 `pow()` 的時間複雜度均為 $O(\log⁡ a)$ 。 - 函式 `math.pow()` 內部呼叫 C 語言庫的 `pow()` 函式,其執行浮點取冪,時間複雜度為 $O(1)$ 。 變數 $a$ 和 $b$ 使用常數大小的額外空間,**因此空間複雜度為 $O(1)$** 。 ### 正確性證明 使用反證法,只分析 $n \geq 4$ 的情況。 1. **所有因子 $\leq 3$** :假設最優切分方案中存在 $\geq 4$ 的因子 $x$ ,那麼一定可以將其繼續劃分為 $2(x-2)$ ,從而獲得更大(或相等)的乘積。這與假設矛盾。 2. **切分方案不包含 $1$** :假設最優切分方案中存在一個因子 $1$ ,那麼它一定可以合併入另外一個因子中,以獲得更大的乘積。這與假設矛盾。 3. **切分方案最多包含兩個 $2$** :假設最優切分方案中包含三個 $2$ ,那麼一定可以替換為兩個 $3$ ,乘積更大。這與假設矛盾。 ================================================ FILE: zh-hant/docs/chapter_greedy/summary.md ================================================ # 小結 ### 重點回顧 - 貪婪演算法通常用於解決最最佳化問題,其原理是在每個決策階段都做出區域性最優的決策,以期獲得全域性最優解。 - 貪婪演算法會迭代地做出一個又一個的貪婪選擇,每輪都將問題轉化成一個規模更小的子問題,直到問題被解決。 - 貪婪演算法不僅實現簡單,還具有很高的解題效率。相比於動態規劃,貪婪演算法的時間複雜度通常更低。 - 在零錢兌換問題中,對於某些硬幣組合,貪婪演算法可以保證找到最優解;對於另外一些硬幣組合則不然,貪婪演算法可能找到很差的解。 - 適合用貪婪演算法求解的問題具有兩大性質:貪婪選擇性質和最優子結構。貪婪選擇性質代表貪婪策略的有效性。 - 對於某些複雜問題,貪婪選擇性質的證明並不簡單。相對來說,證偽更加容易,例如零錢兌換問題。 - 求解貪婪問題主要分為三步:問題分析、確定貪婪策略、正確性證明。其中,確定貪婪策略是核心步驟,正確性證明往往是難點。 - 分數背包問題在 0-1 背包的基礎上,允許選擇物品的一部分,因此可使用貪婪演算法求解。貪婪策略的正確性可以使用反證法來證明。 - 最大容量問題可使用窮舉法求解,時間複雜度為 $O(n^2)$ 。透過設計貪婪策略,每輪向內移動短板,可將時間複雜度最佳化至 $O(n)$ 。 - 在最大切分乘積問題中,我們先後推理出兩個貪婪策略:$\geq 4$ 的整數都應該繼續切分,最優切分因子為 $3$ 。程式碼中包含冪運算,時間複雜度取決於冪運算實現方法,通常為 $O(1)$ 或 $O(\log n)$ 。 ================================================ FILE: zh-hant/docs/chapter_hashing/hash_algorithm.md ================================================ # 雜湊演算法 前兩節介紹了雜湊表的工作原理和雜湊衝突的處理方法。然而無論是開放定址還是鏈式位址,**它們只能保證雜湊表可以在發生衝突時正常工作,而無法減少雜湊衝突的發生**。 如果雜湊衝突過於頻繁,雜湊表的效能則會急劇劣化。如下圖所示,對於鏈式位址雜湊表,理想情況下鍵值對均勻分佈在各個桶中,達到最佳查詢效率;最差情況下所有鍵值對都儲存到同一個桶中,時間複雜度退化至 $O(n)$ 。 ![雜湊衝突的最佳情況與最差情況](hash_algorithm.assets/hash_collision_best_worst_condition.png) **鍵值對的分佈情況由雜湊函式決定**。回憶雜湊函式的計算步驟,先計算雜湊值,再對陣列長度取模: ```shell index = hash(key) % capacity ``` 觀察以上公式,當雜湊表容量 `capacity` 固定時,**雜湊演算法 `hash()` 決定了輸出值**,進而決定了鍵值對在雜湊表中的分佈情況。 這意味著,為了降低雜湊衝突的發生機率,我們應當將注意力集中在雜湊演算法 `hash()` 的設計上。 ## 雜湊演算法的目標 為了實現“既快又穩”的雜湊表資料結構,雜湊演算法應具備以下特點。 - **確定性**:對於相同的輸入,雜湊演算法應始終產生相同的輸出。這樣才能確保雜湊表是可靠的。 - **效率高**:計算雜湊值的過程應該足夠快。計算開銷越小,雜湊表的實用性越高。 - **均勻分佈**:雜湊演算法應使得鍵值對均勻分佈在雜湊表中。分佈越均勻,雜湊衝突的機率就越低。 實際上,雜湊演算法除了可以用於實現雜湊表,還廣泛應用於其他領域中。 - **密碼儲存**:為了保護使用者密碼的安全,系統通常不會直接儲存使用者的明文密碼,而是儲存密碼的雜湊值。當用戶輸入密碼時,系統會對輸入的密碼計算雜湊值,然後與儲存的雜湊值進行比較。如果兩者匹配,那麼密碼就被視為正確。 - **資料完整性檢查**:資料傳送方可以計算資料的雜湊值並將其一同傳送;接收方可以重新計算接收到的資料的雜湊值,並與接收到的雜湊值進行比較。如果兩者匹配,那麼資料就被視為完整。 對於密碼學的相關應用,為了防止從雜湊值推導出原始密碼等逆向工程,雜湊演算法需要具備更高等級的安全特性。 - **單向性**:無法透過雜湊值反推出關於輸入資料的任何資訊。 - **抗碰撞性**:應當極難找到兩個不同的輸入,使得它們的雜湊值相同。 - **雪崩效應**:輸入的微小變化應當導致輸出的顯著且不可預測的變化。 請注意,**“均勻分佈”與“抗碰撞性”是兩個獨立的概念**,滿足均勻分佈不一定滿足抗碰撞性。例如,在隨機輸入 `key` 下,雜湊函式 `key % 100` 可以產生均勻分佈的輸出。然而該雜湊演算法過於簡單,所有後兩位相等的 `key` 的輸出都相同,因此我們可以很容易地從雜湊值反推出可用的 `key` ,從而破解密碼。 ## 雜湊演算法的設計 雜湊演算法的設計是一個需要考慮許多因素的複雜問題。然而對於某些要求不高的場景,我們也能設計一些簡單的雜湊演算法。 - **加法雜湊**:對輸入的每個字元的 ASCII 碼進行相加,將得到的總和作為雜湊值。 - **乘法雜湊**:利用乘法的不相關性,每輪乘以一個常數,將各個字元的 ASCII 碼累積到雜湊值中。 - **互斥或雜湊**:將輸入資料的每個元素透過互斥或操作累積到一個雜湊值中。 - **旋轉雜湊**:將每個字元的 ASCII 碼累積到一個雜湊值中,每次累積之前都會對雜湊值進行旋轉操作。 ```src [file]{simple_hash}-[class]{}-[func]{rot_hash} ``` 觀察發現,每種雜湊演算法的最後一步都是對大質數 $1000000007$ 取模,以確保雜湊值在合適的範圍內。值得思考的是,為什麼要強調對質數取模,或者說對合數取模的弊端是什麼?這是一個有趣的問題。 先給出結論:**使用大質數作為模數,可以最大化地保證雜湊值的均勻分佈**。因為質數不與其他數字存在公約數,可以減少因取模操作而產生的週期性模式,從而避免雜湊衝突。 舉個例子,假設我們選擇合數 $9$ 作為模數,它可以被 $3$ 整除,那麼所有可以被 $3$ 整除的 `key` 都會被對映到 $0$、$3$、$6$ 這三個雜湊值。 $$ \begin{aligned} \text{modulus} & = 9 \newline \text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline \text{hash} & = \{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\dots \} \end{aligned} $$ 如果輸入 `key` 恰好滿足這種等差數列的資料分佈,那麼雜湊值就會出現聚堆積,從而加重雜湊衝突。現在,假設將 `modulus` 替換為質數 $13$ ,由於 `key` 和 `modulus` 之間不存在公約數,因此輸出的雜湊值的均勻性會明顯提升。 $$ \begin{aligned} \text{modulus} & = 13 \newline \text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline \text{hash} & = \{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \dots \} \end{aligned} $$ 值得說明的是,如果能夠保證 `key` 是隨機均勻分佈的,那麼選擇質數或者合數作為模數都可以,它們都能輸出均勻分佈的雜湊值。而當 `key` 的分佈存在某種週期性時,對合數取模更容易出現聚集現象。 總而言之,我們通常選取質數作為模數,並且這個質數最好足夠大,以儘可能消除週期性模式,提升雜湊演算法的穩健性。 ## 常見雜湊演算法 不難發現,以上介紹的簡單雜湊演算法都比較“脆弱”,遠遠沒有達到雜湊演算法的設計目標。例如,由於加法和互斥或滿足交換律,因此加法雜湊和互斥或雜湊無法區分內容相同但順序不同的字串,這可能會加劇雜湊衝突,並引起一些安全問題。 在實際中,我們通常會用一些標準雜湊演算法,例如 MD5、SHA-1、SHA-2 和 SHA-3 等。它們可以將任意長度的輸入資料對映到恆定長度的雜湊值。 近一個世紀以來,雜湊演算法處在不斷升級與最佳化的過程中。一部分研究人員努力提升雜湊演算法的效能,另一部分研究人員和駭客則致力於尋找雜湊演算法的安全性問題。下表展示了在實際應用中常見的雜湊演算法。 - MD5 和 SHA-1 已多次被成功攻擊,因此它們被各類安全應用棄用。 - SHA-2 系列中的 SHA-256 是最安全的雜湊演算法之一,仍未出現成功的攻擊案例,因此常用在各類安全應用與協議中。 - SHA-3 相較 SHA-2 的實現開銷更低、計算效率更高,但目前使用覆蓋度不如 SHA-2 系列。

  常見的雜湊演算法

| | MD5 | SHA-1 | SHA-2 | SHA-3 | | -------- | ------------------------------ | ---------------- | ---------------------------- | ------------------- | | 推出時間 | 1992 | 1995 | 2002 | 2008 | | 輸出長度 | 128 bit | 160 bit | 256/512 bit | 224/256/384/512 bit | | 雜湊衝突 | 較多 | 較多 | 很少 | 很少 | | 安全等級 | 低,已被成功攻擊 | 低,已被成功攻擊 | 高 | 高 | | 應用 | 已被棄用,仍用於資料完整性檢查 | 已被棄用 | 加密貨幣交易驗證、數字簽名等 | 可用於替代 SHA-2 | ## 資料結構的雜湊值 我們知道,雜湊表的 `key` 可以是整數、小數或字串等資料型別。程式語言通常會為這些資料型別提供內建的雜湊演算法,用於計算雜湊表中的桶索引。以 Python 為例,我們可以呼叫 `hash()` 函式來計算各種資料型別的雜湊值。 - 整數和布林量的雜湊值就是其本身。 - 浮點數和字串的雜湊值計算較為複雜,有興趣的讀者請自行學習。 - 元組的雜湊值是對其中每一個元素進行雜湊,然後將這些雜湊值組合起來,得到單一的雜湊值。 - 物件的雜湊值基於其記憶體位址生成。透過重寫物件的雜湊方法,可實現基於內容生成雜湊值。 !!! tip 請注意,不同程式語言的內建雜湊值計算函式的定義和方法不同。 === "Python" ```python title="built_in_hash.py" num = 3 hash_num = hash(num) # 整數 3 的雜湊值為 3 bol = True hash_bol = hash(bol) # 布林量 True 的雜湊值為 1 dec = 3.14159 hash_dec = hash(dec) # 小數 3.14159 的雜湊值為 326484311674566659 str = "Hello 演算法" hash_str = hash(str) # 字串“Hello 演算法”的雜湊值為 4617003410720528961 tup = (12836, "小哈") hash_tup = hash(tup) # 元組 (12836, '小哈') 的雜湊值為 1029005403108185979 obj = ListNode(0) hash_obj = hash(obj) # 節點物件 的雜湊值為 274267521 ``` === "C++" ```cpp title="built_in_hash.cpp" int num = 3; size_t hashNum = hash()(num); // 整數 3 的雜湊值為 3 bool bol = true; size_t hashBol = hash()(bol); // 布林量 1 的雜湊值為 1 double dec = 3.14159; size_t hashDec = hash()(dec); // 小數 3.14159 的雜湊值為 4614256650576692846 string str = "Hello 演算法"; size_t hashStr = hash()(str); // 字串“Hello 演算法”的雜湊值為 15466937326284535026 // 在 C++ 中,內建 std:hash() 僅提供基本資料型別的雜湊值計算 // 陣列、物件的雜湊值計算需要自行實現 ``` === "Java" ```java title="built_in_hash.java" int num = 3; int hashNum = Integer.hashCode(num); // 整數 3 的雜湊值為 3 boolean bol = true; int hashBol = Boolean.hashCode(bol); // 布林量 true 的雜湊值為 1231 double dec = 3.14159; int hashDec = Double.hashCode(dec); // 小數 3.14159 的雜湊值為 -1340954729 String str = "Hello 演算法"; int hashStr = str.hashCode(); // 字串“Hello 演算法”的雜湊值為 -727081396 Object[] arr = { 12836, "小哈" }; int hashTup = Arrays.hashCode(arr); // 陣列 [12836, 小哈] 的雜湊值為 1151158 ListNode obj = new ListNode(0); int hashObj = obj.hashCode(); // 節點物件 utils.ListNode@7dc5e7b4 的雜湊值為 2110121908 ``` === "C#" ```csharp title="built_in_hash.cs" int num = 3; int hashNum = num.GetHashCode(); // 整數 3 的雜湊值為 3; bool bol = true; int hashBol = bol.GetHashCode(); // 布林量 true 的雜湊值為 1; double dec = 3.14159; int hashDec = dec.GetHashCode(); // 小數 3.14159 的雜湊值為 -1340954729; string str = "Hello 演算法"; int hashStr = str.GetHashCode(); // 字串“Hello 演算法”的雜湊值為 -586107568; object[] arr = [12836, "小哈"]; int hashTup = arr.GetHashCode(); // 陣列 [12836, 小哈] 的雜湊值為 42931033; ListNode obj = new(0); int hashObj = obj.GetHashCode(); // 節點物件 0 的雜湊值為 39053774; ``` === "Go" ```go title="built_in_hash.go" // Go 未提供內建 hash code 函式 ``` === "Swift" ```swift title="built_in_hash.swift" let num = 3 let hashNum = num.hashValue // 整數 3 的雜湊值為 9047044699613009734 let bol = true let hashBol = bol.hashValue // 布林量 true 的雜湊值為 -4431640247352757451 let dec = 3.14159 let hashDec = dec.hashValue // 小數 3.14159 的雜湊值為 -2465384235396674631 let str = "Hello 演算法" let hashStr = str.hashValue // 字串“Hello 演算法”的雜湊值為 -7850626797806988787 let arr = [AnyHashable(12836), AnyHashable("小哈")] let hashTup = arr.hashValue // 陣列 [AnyHashable(12836), AnyHashable("小哈")] 的雜湊值為 -2308633508154532996 let obj = ListNode(x: 0) let hashObj = obj.hashValue // 節點物件 utils.ListNode 的雜湊值為 -2434780518035996159 ``` === "JS" ```javascript title="built_in_hash.js" // JavaScript 未提供內建 hash code 函式 ``` === "TS" ```typescript title="built_in_hash.ts" // TypeScript 未提供內建 hash code 函式 ``` === "Dart" ```dart title="built_in_hash.dart" int num = 3; int hashNum = num.hashCode; // 整數 3 的雜湊值為 34803 bool bol = true; int hashBol = bol.hashCode; // 布林值 true 的雜湊值為 1231 double dec = 3.14159; int hashDec = dec.hashCode; // 小數 3.14159 的雜湊值為 2570631074981783 String str = "Hello 演算法"; int hashStr = str.hashCode; // 字串“Hello 演算法”的雜湊值為 468167534 List arr = [12836, "小哈"]; int hashArr = arr.hashCode; // 陣列 [12836, 小哈] 的雜湊值為 976512528 ListNode obj = new ListNode(0); int hashObj = obj.hashCode; // 節點物件 Instance of 'ListNode' 的雜湊值為 1033450432 ``` === "Rust" ```rust title="built_in_hash.rs" use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; let num = 3; let mut num_hasher = DefaultHasher::new(); num.hash(&mut num_hasher); let hash_num = num_hasher.finish(); // 整數 3 的雜湊值為 568126464209439262 let bol = true; let mut bol_hasher = DefaultHasher::new(); bol.hash(&mut bol_hasher); let hash_bol = bol_hasher.finish(); // 布林量 true 的雜湊值為 4952851536318644461 let dec: f32 = 3.14159; let mut dec_hasher = DefaultHasher::new(); dec.to_bits().hash(&mut dec_hasher); let hash_dec = dec_hasher.finish(); // 小數 3.14159 的雜湊值為 2566941990314602357 let str = "Hello 演算法"; let mut str_hasher = DefaultHasher::new(); str.hash(&mut str_hasher); let hash_str = str_hasher.finish(); // 字串“Hello 演算法”的雜湊值為 16092673739211250988 let arr = (&12836, &"小哈"); let mut tup_hasher = DefaultHasher::new(); arr.hash(&mut tup_hasher); let hash_tup = tup_hasher.finish(); // 元組 (12836, "小哈") 的雜湊值為 1885128010422702749 let node = ListNode::new(42); let mut hasher = DefaultHasher::new(); node.borrow().val.hash(&mut hasher); let hash = hasher.finish(); // 節點物件 RefCell { value: ListNode { val: 42, next: None } } 的雜湊值為15387811073369036852 ``` === "C" ```c title="built_in_hash.c" // C 未提供內建 hash code 函式 ``` === "Kotlin" ```kotlin title="built_in_hash.kt" val num = 3 val hashNum = num.hashCode() // 整數 3 的雜湊值為 3 val bol = true val hashBol = bol.hashCode() // 布林量 true 的雜湊值為 1231 val dec = 3.14159 val hashDec = dec.hashCode() // 小數 3.14159 的雜湊值為 -1340954729 val str = "Hello 演算法" val hashStr = str.hashCode() // 字串“Hello 演算法”的雜湊值為 -727081396 val arr = arrayOf(12836, "小哈") val hashTup = arr.hashCode() // 陣列 [12836, 小哈] 的雜湊值為 189568618 val obj = ListNode(0) val hashObj = obj.hashCode() // 節點物件 utils.ListNode@1d81eb93 的雜湊值為 495053715 ``` === "Ruby" ```ruby title="built_in_hash.rb" num = 3 hash_num = num.hash # 整數 3 的雜湊值為 -4385856518450339636 bol = true hash_bol = bol.hash # 布林量 true 的雜湊值為 -1617938112149317027 dec = 3.14159 hash_dec = dec.hash # 小數 3.14159 的雜湊值為 -1479186995943067893 str = "Hello 演算法" hash_str = str.hash # 字串“Hello 演算法”的雜湊值為 -4075943250025831763 tup = [12836, '小哈'] hash_tup = tup.hash # 元組 (12836, '小哈') 的雜湊值為 1999544809202288822 obj = ListNode.new(0) hash_obj = obj.hash # 節點物件 # 的雜湊值為 4302940560806366381 ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B8%203%20%E7%9A%84%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E6%9E%97%E9%87%8F%20True%20%E7%9A%84%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B8%203.14159%20%E7%9A%84%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E6%BC%94%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E4%B8%B2%E2%80%9CHello%20%E6%BC%94%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836%2C%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%B5%84%20%2812836%2C%20%27%E5%B0%8F%E5%93%88%27%29%20%E7%9A%84%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E7%AF%80%E9%BB%9E%E7%89%A9%E4%BB%B6%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false 在許多程式語言中,**只有不可變物件才可作為雜湊表的 `key`** 。假如我們將串列(動態陣列)作為 `key` ,當串列的內容發生變化時,它的雜湊值也隨之改變,我們就無法在雜湊表中查詢到原先的 `value` 了。 雖然自定義物件(比如鏈結串列節點)的成員變數是可變的,但它是可雜湊的。**這是因為物件的雜湊值通常是基於記憶體位址生成的**,即使物件的內容發生了變化,但它的記憶體位址不變,雜湊值仍然是不變的。 細心的你可能發現在不同控制檯中執行程式時,輸出的雜湊值是不同的。**這是因為 Python 直譯器在每次啟動時,都會為字串雜湊函式加入一個隨機的鹽(salt)值**。這種做法可以有效防止 HashDoS 攻擊,提升雜湊演算法的安全性。 ================================================ FILE: zh-hant/docs/chapter_hashing/hash_collision.md ================================================ # 雜湊衝突 上一節提到,**通常情況下雜湊函式的輸入空間遠大於輸出空間**,因此理論上雜湊衝突是不可避免的。比如,輸入空間為全體整數,輸出空間為陣列容量大小,則必然有多個整數對映至同一桶索引。 雜湊衝突會導致查詢結果錯誤,嚴重影響雜湊表的可用性。為了解決該問題,每當遇到雜湊衝突時,我們就進行雜湊表擴容,直至衝突消失為止。此方法簡單粗暴且有效,但效率太低,因為雜湊表擴容需要進行大量的資料搬運與雜湊值計算。為了提升效率,我們可以採用以下策略。 1. 改良雜湊表資料結構,**使得雜湊表可以在出現雜湊衝突時正常工作**。 2. 僅在必要時,即當雜湊衝突比較嚴重時,才執行擴容操作。 雜湊表的結構改良方法主要包括“鏈式位址”和“開放定址”。 ## 鏈式位址 在原始雜湊表中,每個桶僅能儲存一個鍵值對。鏈式位址(separate chaining)將單個元素轉換為鏈結串列,將鍵值對作為鏈結串列節點,將所有發生衝突的鍵值對都儲存在同一鏈結串列中。下圖展示了一個鏈式位址雜湊表的例子。 ![鏈式位址雜湊表](hash_collision.assets/hash_table_chaining.png) 基於鏈式位址實現的雜湊表的操作方法發生了以下變化。 - **查詢元素**:輸入 `key` ,經過雜湊函式得到桶索引,即可訪問鏈結串列頭節點,然後走訪鏈結串列並對比 `key` 以查詢目標鍵值對。 - **新增元素**:首先透過雜湊函式訪問鏈結串列頭節點,然後將節點(鍵值對)新增到鏈結串列中。 - **刪除元素**:根據雜湊函式的結果訪問鏈結串列頭部,接著走訪鏈結串列以查詢目標節點並將其刪除。 鏈式位址存在以下侷限性。 - **佔用空間增大**:鏈結串列包含節點指標,它相比陣列更加耗費記憶體空間。 - **查詢效率降低**:因為需要線性走訪鏈結串列來查詢對應元素。 以下程式碼給出了鏈式位址雜湊表的簡單實現,需要注意兩點。 - 使用串列(動態陣列)代替鏈結串列,從而簡化程式碼。在這種設定下,雜湊表(陣列)包含多個桶,每個桶都是一個串列。 - 以下實現包含雜湊表擴容方法。當負載因子超過 $\frac{2}{3}$ 時,我們將雜湊表擴容至原先的 $2$ 倍。 ```src [file]{hash_map_chaining}-[class]{hash_map_chaining}-[func]{} ``` 值得注意的是,當鏈結串列很長時,查詢效率 $O(n)$ 很差。**此時可以將鏈結串列轉換為“AVL 樹”或“紅黑樹”**,從而將查詢操作的時間複雜度最佳化至 $O(\log n)$ 。 ## 開放定址 開放定址(open addressing)不引入額外的資料結構,而是透過“多次探測”來處理雜湊衝突,探測方式主要包括線性探查、平方探測和多次雜湊等。 下面以線性探查為例,介紹開放定址雜湊表的工作機制。 ### 線性探查 線性探查採用固定步長的線性搜尋來進行探測,其操作方法與普通雜湊表有所不同。 - **插入元素**:透過雜湊函式計算桶索引,若發現桶內已有元素,則從衝突位置向後線性走訪(步長通常為 $1$ ),直至找到空桶,將元素插入其中。 - **查詢元素**:若發現雜湊衝突,則使用相同步長向後進行線性走訪,直到找到對應元素,返回 `value` 即可;如果遇到空桶,說明目標元素不在雜湊表中,返回 `None` 。 下圖展示了開放定址(線性探查)雜湊表的鍵值對分佈。根據此雜湊函式,最後兩位相同的 `key` 都會被對映到相同的桶。而透過線性探查,它們被依次儲存在該桶以及之下的桶中。 ![開放定址(線性探查)雜湊表的鍵值對分佈](hash_collision.assets/hash_table_linear_probing.png) 然而,**線性探查容易產生“聚集現象”**。具體來說,陣列中連續被佔用的位置越長,這些連續位置發生雜湊衝突的可能性越大,從而進一步促使該位置的聚堆積生長,形成惡性迴圈,最終導致增刪查改操作效率劣化。 值得注意的是,**我們不能在開放定址雜湊表中直接刪除元素**。這是因為刪除元素會在陣列內產生一個空桶 `None` ,而當查詢元素時,線性探查到該空桶就會返回,因此在該空桶之下的元素都無法再被訪問到,程式可能誤判這些元素不存在,如下圖所示。 ![在開放定址中刪除元素導致的查詢問題](hash_collision.assets/hash_table_open_addressing_deletion.png) 為了解決該問題,我們可以採用懶刪除(lazy deletion)機制:它不直接從雜湊表中移除元素,**而是利用一個常數 `TOMBSTONE` 來標記這個桶**。在該機制下,`None` 和 `TOMBSTONE` 都代表空桶,都可以放置鍵值對。但不同的是,線性探查到 `TOMBSTONE` 時應該繼續走訪,因為其之下可能還存在鍵值對。 然而,**懶刪除可能會加速雜湊表的效能退化**。這是因為每次刪除操作都會產生一個刪除標記,隨著 `TOMBSTONE` 的增加,搜尋時間也會增加,因為線性探查可能需要跳過多個 `TOMBSTONE` 才能找到目標元素。 為此,考慮在線性探查中記錄遇到的首個 `TOMBSTONE` 的索引,並將搜尋到的目標元素與該 `TOMBSTONE` 交換位置。這樣做的好處是當每次查詢或新增元素時,元素會被移動至距離理想位置(探測起始點)更近的桶,從而最佳化查詢效率。 以下程式碼實現了一個包含懶刪除的開放定址(線性探查)雜湊表。為了更加充分地使用雜湊表的空間,我們將雜湊表看作一個“環形陣列”,當越過陣列尾部時,回到頭部繼續走訪。 ```src [file]{hash_map_open_addressing}-[class]{hash_map_open_addressing}-[func]{} ``` ### 平方探測 平方探測與線性探查類似,都是開放定址的常見策略之一。當發生衝突時,平方探測不是簡單地跳過一個固定的步數,而是跳過“探測次數的平方”的步數,即 $1, 4, 9, \dots$ 步。 平方探測主要具有以下優勢。 - 平方探測透過跳過探測次數平方的距離,試圖緩解線性探查的聚集效應。 - 平方探測會跳過更大的距離來尋找空位置,有助於資料分佈得更加均勻。 然而,平方探測並不是完美的。 - 仍然存在聚集現象,即某些位置比其他位置更容易被佔用。 - 由於平方的增長,平方探測可能不會探測整個雜湊表,這意味著即使雜湊表中有空桶,平方探測也可能無法訪問到它。 ### 多次雜湊 顧名思義,多次雜湊方法使用多個雜湊函式 $f_1(x)$、$f_2(x)$、$f_3(x)$、$\dots$ 進行探測。 - **插入元素**:若雜湊函式 $f_1(x)$ 出現衝突,則嘗試 $f_2(x)$ ,以此類推,直到找到空位後插入元素。 - **查詢元素**:在相同的雜湊函式順序下進行查詢,直到找到目標元素時返回;若遇到空位或已嘗試所有雜湊函式,說明雜湊表中不存在該元素,則返回 `None` 。 與線性探查相比,多次雜湊方法不易產生聚集,但多個雜湊函式會帶來額外的計算量。 !!! tip 請注意,開放定址(線性探查、平方探測和多次雜湊)雜湊表都存在“不能直接刪除元素”的問題。 ## 程式語言的選擇 各種程式語言採取了不同的雜湊表實現策略,下面舉幾個例子。 - Python 採用開放定址。字典 `dict` 使用偽隨機數進行探測。 - Java 採用鏈式位址。自 JDK 1.8 以來,當 `HashMap` 內陣列長度達到 64 且鏈結串列長度達到 8 時,鏈結串列會轉換為紅黑樹以提升查詢效能。 - Go 採用鏈式位址。Go 規定每個桶最多儲存 8 個鍵值對,超出容量則連線一個溢位桶;當溢位桶過多時,會執行一次特殊的等量擴容操作,以確保效能。 ================================================ FILE: zh-hant/docs/chapter_hashing/hash_map.md ================================================ # 雜湊表 雜湊表(hash table),又稱散列表,它透過建立鍵 `key` 與值 `value` 之間的對映,實現高效的元素查詢。具體而言,我們向雜湊表中輸入一個鍵 `key` ,則可以在 $O(1)$ 時間內獲取對應的值 `value` 。 如下圖所示,給定 $n$ 個學生,每個學生都有“姓名”和“學號”兩項資料。假如我們希望實現“輸入一個學號,返回對應的姓名”的查詢功能,則可以採用下圖所示的雜湊表來實現。 ![雜湊表的抽象表示](hash_map.assets/hash_table_lookup.png) 除雜湊表外,陣列和鏈結串列也可以實現查詢功能,它們的效率對比如下表所示。 - **新增元素**:僅需將元素新增至陣列(鏈結串列)的尾部即可,使用 $O(1)$ 時間。 - **查詢元素**:由於陣列(鏈結串列)是亂序的,因此需要走訪其中的所有元素,使用 $O(n)$ 時間。 - **刪除元素**:需要先查詢到元素,再從陣列(鏈結串列)中刪除,使用 $O(n)$ 時間。

  元素查詢效率對比

| | 陣列 | 鏈結串列 | 雜湊表 | | -------- | ------ | ------ | ------ | | 查詢元素 | $O(n)$ | $O(n)$ | $O(1)$ | | 新增元素 | $O(1)$ | $O(1)$ | $O(1)$ | | 刪除元素 | $O(n)$ | $O(n)$ | $O(1)$ | 觀察發現,**在雜湊表中進行增刪查改的時間複雜度都是 $O(1)$** ,非常高效。 ## 雜湊表常用操作 雜湊表的常見操作包括:初始化、查詢操作、新增鍵值對和刪除鍵值對等,示例程式碼如下: === "Python" ```python title="hash_map.py" # 初始化雜湊表 hmap: dict = {} # 新增操作 # 在雜湊表中新增鍵值對 (key, value) hmap[12836] = "小哈" hmap[15937] = "小囉" hmap[16750] = "小算" hmap[13276] = "小法" hmap[10583] = "小鴨" # 查詢操作 # 向雜湊表中輸入鍵 key ,得到值 value name: str = hmap[15937] # 刪除操作 # 在雜湊表中刪除鍵值對 (key, value) hmap.pop(10583) ``` === "C++" ```cpp title="hash_map.cpp" /* 初始化雜湊表 */ unordered_map map; /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map[12836] = "小哈"; map[15937] = "小囉"; map[16750] = "小算"; map[13276] = "小法"; map[10583] = "小鴨"; /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value string name = map[15937]; /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.erase(10583); ``` === "Java" ```java title="hash_map.java" /* 初始化雜湊表 */ Map map = new HashMap<>(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.put(12836, "小哈"); map.put(15937, "小囉"); map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鴨"); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value String name = map.get(15937); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(10583); ``` === "C#" ```csharp title="hash_map.cs" /* 初始化雜湊表 */ Dictionary map = new() { /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) { 12836, "小哈" }, { 15937, "小囉" }, { 16750, "小算" }, { 13276, "小法" }, { 10583, "小鴨" } }; /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value string name = map[15937]; /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.Remove(10583); ``` === "Go" ```go title="hash_map_test.go" /* 初始化雜湊表 */ hmap := make(map[int]string) /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) hmap[12836] = "小哈" hmap[15937] = "小囉" hmap[16750] = "小算" hmap[13276] = "小法" hmap[10583] = "小鴨" /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value name := hmap[15937] /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) delete(hmap, 10583) ``` === "Swift" ```swift title="hash_map.swift" /* 初始化雜湊表 */ var map: [Int: String] = [:] /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map[12836] = "小哈" map[15937] = "小囉" map[16750] = "小算" map[13276] = "小法" map[10583] = "小鴨" /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value let name = map[15937]! /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.removeValue(forKey: 10583) ``` === "JS" ```javascript title="hash_map.js" /* 初始化雜湊表 */ const map = new Map(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.set(12836, '小哈'); map.set(15937, '小囉'); map.set(16750, '小算'); map.set(13276, '小法'); map.set(10583, '小鴨'); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value let name = map.get(15937); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.delete(10583); ``` === "TS" ```typescript title="hash_map.ts" /* 初始化雜湊表 */ const map = new Map(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.set(12836, '小哈'); map.set(15937, '小囉'); map.set(16750, '小算'); map.set(13276, '小法'); map.set(10583, '小鴨'); console.info('\n新增完成後,雜湊表為\nKey -> Value'); console.info(map); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value let name = map.get(15937); console.info('\n輸入學號 15937 ,查詢到姓名 ' + name); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.delete(10583); console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); console.info(map); ``` === "Dart" ```dart title="hash_map.dart" /* 初始化雜湊表 */ Map map = {}; /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map[12836] = "小哈"; map[15937] = "小囉"; map[16750] = "小算"; map[13276] = "小法"; map[10583] = "小鴨"; /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value String name = map[15937]; /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(10583); ``` === "Rust" ```rust title="hash_map.rs" use std::collections::HashMap; /* 初始化雜湊表 */ let mut map: HashMap = HashMap::new(); /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map.insert(12836, "小哈".to_string()); map.insert(15937, "小囉".to_string()); map.insert(16750, "小算".to_string()); map.insert(13279, "小法".to_string()); map.insert(10583, "小鴨".to_string()); /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value let _name: Option<&String> = map.get(&15937); /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) let _removed_value: Option = map.remove(&10583); ``` === "C" ```c title="hash_map.c" // C 未提供內建雜湊表 ``` === "Kotlin" ```kotlin title="hash_map.kt" /* 初始化雜湊表 */ val map = HashMap() /* 新增操作 */ // 在雜湊表中新增鍵值對 (key, value) map[12836] = "小哈" map[15937] = "小囉" map[16750] = "小算" map[13276] = "小法" map[10583] = "小鴨" /* 查詢操作 */ // 向雜湊表中輸入鍵 key ,得到值 value val name = map[15937] /* 刪除操作 */ // 在雜湊表中刪除鍵值對 (key, value) map.remove(10583) ``` === "Ruby" ```ruby title="hash_map.rb" # 初始化雜湊表 hmap = {} # 新增操作 # 在雜湊表中新增鍵值對 (key, value) hmap[12836] = "小哈" hmap[15937] = "小囉" hmap[16750] = "小算" hmap[13276] = "小法" hmap[10583] = "小鴨" # 查詢操作 # 向雜湊表中輸入鍵 key ,得到值 value name = hmap[15937] # 刪除操作 # 在雜湊表中刪除鍵值對 (key, value) hmap.delete(10583) ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%9C%E6%B9%8A%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E9%9B%9C%E6%B9%8A%E8%A1%A8%E4%B8%AD%E6%96%B0%E5%A2%9E%E9%8D%B5%E5%80%BC%E5%B0%8D%20%28key%2C%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%9B%89%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B4%A8%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E9%9B%9C%E6%B9%8A%E8%A1%A8%E4%B8%AD%E8%BC%B8%E5%85%A5%E9%8D%B5%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E9%9B%9C%E6%B9%8A%E8%A1%A8%E4%B8%AD%E5%88%AA%E9%99%A4%E9%8D%B5%E5%80%BC%E5%B0%8D%20%28key%2C%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false 雜湊表有三種常用的走訪方式:走訪鍵值對、走訪鍵和走訪值。示例程式碼如下: === "Python" ```python title="hash_map.py" # 走訪雜湊表 # 走訪鍵值對 key->value for key, value in hmap.items(): print(key, "->", value) # 單獨走訪鍵 key for key in hmap.keys(): print(key) # 單獨走訪值 value for value in hmap.values(): print(value) ``` === "C++" ```cpp title="hash_map.cpp" /* 走訪雜湊表 */ // 走訪鍵值對 key->value for (auto kv: map) { cout << kv.first << " -> " << kv.second << endl; } // 使用迭代器走訪 key->value for (auto iter = map.begin(); iter != map.end(); iter++) { cout << iter->first << "->" << iter->second << endl; } ``` === "Java" ```java title="hash_map.java" /* 走訪雜湊表 */ // 走訪鍵值對 key->value for (Map.Entry kv: map.entrySet()) { System.out.println(kv.getKey() + " -> " + kv.getValue()); } // 單獨走訪鍵 key for (int key: map.keySet()) { System.out.println(key); } // 單獨走訪值 value for (String val: map.values()) { System.out.println(val); } ``` === "C#" ```csharp title="hash_map.cs" /* 走訪雜湊表 */ // 走訪鍵值對 Key->Value foreach (var kv in map) { Console.WriteLine(kv.Key + " -> " + kv.Value); } // 單獨走訪鍵 key foreach (int key in map.Keys) { Console.WriteLine(key); } // 單獨走訪值 value foreach (string val in map.Values) { Console.WriteLine(val); } ``` === "Go" ```go title="hash_map_test.go" /* 走訪雜湊表 */ // 走訪鍵值對 key->value for key, value := range hmap { fmt.Println(key, "->", value) } // 單獨走訪鍵 key for key := range hmap { fmt.Println(key) } // 單獨走訪值 value for _, value := range hmap { fmt.Println(value) } ``` === "Swift" ```swift title="hash_map.swift" /* 走訪雜湊表 */ // 走訪鍵值對 Key->Value for (key, value) in map { print("\(key) -> \(value)") } // 單獨走訪鍵 Key for key in map.keys { print(key) } // 單獨走訪值 Value for value in map.values { print(value) } ``` === "JS" ```javascript title="hash_map.js" /* 走訪雜湊表 */ console.info('\n走訪鍵值對 Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\n單獨走訪鍵 Key'); for (const k of map.keys()) { console.info(k); } console.info('\n單獨走訪值 Value'); for (const v of map.values()) { console.info(v); } ``` === "TS" ```typescript title="hash_map.ts" /* 走訪雜湊表 */ console.info('\n走訪鍵值對 Key->Value'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } console.info('\n單獨走訪鍵 Key'); for (const k of map.keys()) { console.info(k); } console.info('\n單獨走訪值 Value'); for (const v of map.values()) { console.info(v); } ``` === "Dart" ```dart title="hash_map.dart" /* 走訪雜湊表 */ // 走訪鍵值對 Key->Value map.forEach((key, value) { print('$key -> $value'); }); // 單獨走訪鍵 Key map.keys.forEach((key) { print(key); }); // 單獨走訪值 Value map.values.forEach((value) { print(value); }); ``` === "Rust" ```rust title="hash_map.rs" /* 走訪雜湊表 */ // 走訪鍵值對 Key->Value for (key, value) in &map { println!("{key} -> {value}"); } // 單獨走訪鍵 Key for key in map.keys() { println!("{key}"); } // 單獨走訪值 Value for value in map.values() { println!("{value}"); } ``` === "C" ```c title="hash_map.c" // C 未提供內建雜湊表 ``` === "Kotlin" ```kotlin title="hash_map.kt" /* 走訪雜湊表 */ // 走訪鍵值對 key->value for ((key, value) in map) { println("$key -> $value") } // 單獨走訪鍵 key for (key in map.keys) { println(key) } // 單獨走訪值 value for (_val in map.values) { println(_val) } ``` === "Ruby" ```ruby title="hash_map.rb" # 走訪雜湊表 # 走訪鍵值對 key->value hmap.entries.each { |key, value| puts "#{key} -> #{value}" } # 單獨走訪鍵 key hmap.keys.each { |key| puts key } # 單獨走訪值 value hmap.values.each { |val| puts val } ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%9C%E6%B9%8A%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E9%9B%9C%E6%B9%8A%E8%A1%A8%E4%B8%AD%E6%96%B0%E5%A2%9E%E9%8D%B5%E5%80%BC%E5%B0%8D%20%28key%2C%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%9B%89%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B4%A8%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E9%9B%9C%E6%B9%8A%E8%A1%A8%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E9%8D%B5%E5%80%BC%E5%B0%8D%20key-%3Evalue%0A%20%20%20%20for%20key%2C%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%2C%20%22-%3E%22%2C%20value%29%0A%20%20%20%20%23%20%E5%96%AE%E7%8D%A8%E8%B5%B0%E8%A8%AA%E9%8D%B5%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%E5%96%AE%E7%8D%A8%E8%B5%B0%E8%A8%AA%E5%80%BC%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## 雜湊表簡單實現 我們先考慮最簡單的情況,**僅用一個陣列來實現雜湊表**。在雜湊表中,我們將陣列中的每個空位稱為桶(bucket),每個桶可儲存一個鍵值對。因此,查詢操作就是找到 `key` 對應的桶,並在桶中獲取 `value` 。 那麼,如何基於 `key` 定位對應的桶呢?這是透過雜湊函式(hash function)實現的。雜湊函式的作用是將一個較大的輸入空間對映到一個較小的輸出空間。在雜湊表中,輸入空間是所有 `key` ,輸出空間是所有桶(陣列索引)。換句話說,輸入一個 `key` ,**我們可以透過雜湊函式得到該 `key` 對應的鍵值對在陣列中的儲存位置**。 輸入一個 `key` ,雜湊函式的計算過程分為以下兩步。 1. 透過某種雜湊演算法 `hash()` 計算得到雜湊值。 2. 將雜湊值對桶數量(陣列長度)`capacity` 取模,從而獲取該 `key` 對應的桶(陣列索引)`index` 。 ```shell index = hash(key) % capacity ``` 隨後,我們就可以利用 `index` 在雜湊表中訪問對應的桶,從而獲取 `value` 。 設陣列長度 `capacity = 100`、雜湊演算法 `hash(key) = key` ,易得雜湊函式為 `key % 100` 。下圖以 `key` 學號和 `value` 姓名為例,展示了雜湊函式的工作原理。 ![雜湊函式工作原理](hash_map.assets/hash_function.png) 以下程式碼實現了一個簡單雜湊表。其中,我們將 `key` 和 `value` 封裝成一個類別 `Pair` ,以表示鍵值對。 ```src [file]{array_hash_map}-[class]{array_hash_map}-[func]{} ``` ## 雜湊衝突與擴容 從本質上看,雜湊函式的作用是將所有 `key` 構成的輸入空間對映到陣列所有索引構成的輸出空間,而輸入空間往往遠大於輸出空間。因此,**理論上一定存在“多個輸入對應相同輸出”的情況**。 對於上述示例中的雜湊函式,當輸入的 `key` 後兩位相同時,雜湊函式的輸出結果也相同。例如,查詢學號為 12836 和 20336 的兩個學生時,我們得到: ```shell 12836 % 100 = 36 20336 % 100 = 36 ``` 如下圖所示,兩個學號指向了同一個姓名,這顯然是不對的。我們將這種多個輸入對應同一輸出的情況稱為雜湊衝突(hash collision)。 ![雜湊衝突示例](hash_map.assets/hash_collision.png) 容易想到,雜湊表容量 $n$ 越大,多個 `key` 被分配到同一個桶中的機率就越低,衝突就越少。因此,**我們可以透過擴容雜湊表來減少雜湊衝突**。 如下圖所示,擴容前鍵值對 `(136, A)` 和 `(236, D)` 發生衝突,擴容後衝突消失。 ![雜湊表擴容](hash_map.assets/hash_table_reshash.png) 類似於陣列擴容,雜湊表擴容需將所有鍵值對從原雜湊表遷移至新雜湊表,非常耗時;並且由於雜湊表容量 `capacity` 改變,我們需要透過雜湊函式來重新計算所有鍵值對的儲存位置,這進一步增加了擴容過程的計算開銷。為此,程式語言通常會預留足夠大的雜湊表容量,防止頻繁擴容。 負載因子(load factor)是雜湊表的一個重要概念,其定義為雜湊表的元素數量除以桶數量,用於衡量雜湊衝突的嚴重程度,**也常作為雜湊表擴容的觸發條件**。例如在 Java 中,當負載因子超過 $0.75$ 時,系統會將雜湊表擴容至原先的 $2$ 倍。 ================================================ FILE: zh-hant/docs/chapter_hashing/index.md ================================================ # 雜湊表 ![雜湊表](../assets/covers/chapter_hashing.jpg) !!! abstract 在計算機世界中,雜湊表如同一位聰慧的圖書管理員。 他知道如何計算索書號,從而可以快速找到目標圖書。 ================================================ FILE: zh-hant/docs/chapter_hashing/summary.md ================================================ # 小結 ### 重點回顧 - 輸入 `key` ,雜湊表能夠在 $O(1)$ 時間內查詢到 `value` ,效率非常高。 - 常見的雜湊表操作包括查詢、新增鍵值對、刪除鍵值對和走訪雜湊表等。 - 雜湊函式將 `key` 對映為陣列索引,從而訪問對應桶並獲取 `value` 。 - 兩個不同的 `key` 可能在經過雜湊函式後得到相同的陣列索引,導致查詢結果出錯,這種現象被稱為雜湊衝突。 - 雜湊表容量越大,雜湊衝突的機率就越低。因此可以透過擴容雜湊表來緩解雜湊衝突。與陣列擴容類似,雜湊表擴容操作的開銷很大。 - 負載因子定義為雜湊表中元素數量除以桶數量,反映了雜湊衝突的嚴重程度,常用作觸發雜湊表擴容的條件。 - 鏈式位址透過將單個元素轉化為鏈結串列,將所有衝突元素儲存在同一個鏈結串列中。然而,鏈結串列過長會降低查詢效率,可以透過進一步將鏈結串列轉換為紅黑樹來提高效率。 - 開放定址透過多次探測來處理雜湊衝突。線性探查使用固定步長,缺點是不能刪除元素,且容易產生聚集。多次雜湊使用多個雜湊函式進行探測,相較線性探查更不易產生聚集,但多個雜湊函式增加了計算量。 - 不同程式語言採取了不同的雜湊表實現。例如,Java 的 `HashMap` 使用鏈式位址,而 Python 的 `Dict` 採用開放定址。 - 在雜湊表中,我們希望雜湊演算法具有確定性、高效率和均勻分佈的特點。在密碼學中,雜湊演算法還應該具備抗碰撞性和雪崩效應。 - 雜湊演算法通常採用大質數作為模數,以最大化地保證雜湊值均勻分佈,減少雜湊衝突。 - 常見的雜湊演算法包括 MD5、SHA-1、SHA-2 和 SHA-3 等。MD5 常用於校驗檔案完整性,SHA-2 常用於安全應用與協議。 - 程式語言通常會為資料型別提供內建雜湊演算法,用於計算雜湊表中的桶索引。通常情況下,只有不可變物件是可雜湊的。 ### Q & A **Q**:雜湊表的時間複雜度在什麼情況下是 $O(n)$ ? 當雜湊衝突比較嚴重時,雜湊表的時間複雜度會退化至 $O(n)$ 。當雜湊函式設計得比較好、容量設定比較合理、衝突比較平均時,時間複雜度是 $O(1)$ 。我們使用程式語言內建的雜湊表時,通常認為時間複雜度是 $O(1)$ 。 **Q**:為什麼不使用雜湊函式 $f(x) = x$ 呢?這樣就不會有衝突了。 在 $f(x) = x$ 雜湊函式下,每個元素對應唯一的桶索引,這與陣列等價。然而,輸入空間通常遠大於輸出空間(陣列長度),因此雜湊函式的最後一步往往是對陣列長度取模。換句話說,雜湊表的目標是將一個較大的狀態空間對映到一個較小的空間,並提供 $O(1)$ 的查詢效率。 **Q**:雜湊表底層實現是陣列、鏈結串列、二元樹,但為什麼效率可以比它們更高呢? 首先,雜湊表的時間效率變高,但空間效率變低了。雜湊表有相當一部分記憶體未使用。 其次,只是在特定使用場景下時間效率變高了。如果一個功能能夠在相同的時間複雜度下使用陣列或鏈結串列實現,那麼通常比雜湊表更快。這是因為雜湊函式計算需要開銷,時間複雜度的常數項更大。 最後,雜湊表的時間複雜度可能發生劣化。例如在鏈式位址中,我們採取在鏈結串列或紅黑樹中執行查詢操作,仍然有退化至 $O(n)$ 時間的風險。 **Q**:多次雜湊有不能直接刪除元素的缺陷嗎?標記為已刪除的空間還能再次使用嗎? 多次雜湊是開放定址的一種,開放定址法都有不能直接刪除元素的缺陷,需要透過標記刪除。標記為已刪除的空間可以再次使用。當將新元素插入雜湊表,並且透過雜湊函式找到標記為已刪除的位置時,該位置可以被新元素使用。這樣做既能保持雜湊表的探測序列不變,又能保證雜湊表的空間使用率。 **Q**:為什麼在線性探查中,查詢元素的時候會出現雜湊衝突呢? 查詢的時候透過雜湊函式找到對應的桶和鍵值對,發現 `key` 不匹配,這就代表有雜湊衝突。因此,線性探查法會根據預先設定的步長依次向下查詢,直至找到正確的鍵值對或無法找到跳出為止。 **Q**:為什麼雜湊表擴容能夠緩解雜湊衝突? 雜湊函式的最後一步往往是對陣列長度 $n$ 取模(取餘),讓輸出值落在陣列索引範圍內;在擴容後,陣列長度 $n$ 發生變化,而 `key` 對應的索引也可能發生變化。原先落在同一個桶的多個 `key` ,在擴容後可能會被分配到多個桶中,從而實現雜湊衝突的緩解。 **Q**:如果為了高效的存取,那麼直接使用陣列不就好了嗎? 當資料的 `key` 是連續的小範圍整數時,直接用陣列即可,簡單高效。但當 `key` 是其他型別(例如字串)時,就需要藉助雜湊函式將 `key` 對映為陣列索引,再透過桶陣列儲存元素,這樣的結構就是雜湊表。 ================================================ FILE: zh-hant/docs/chapter_heap/build_heap.md ================================================ # 建堆積操作 在某些情況下,我們希望使用一個串列的所有元素來構建一個堆積,這個過程被稱為“建堆積操作”。 ## 藉助入堆積操作實現 我們首先建立一個空堆積,然後走訪串列,依次對每個元素執行“入堆積操作”,即先將元素新增至堆積的尾部,再對該元素執行“從底至頂”堆積化。 每當一個元素入堆積,堆積的長度就加一。由於節點是從頂到底依次被新增進二元樹的,因此堆積是“自上而下”構建的。 設元素數量為 $n$ ,每個元素的入堆積操作使用 $O(\log{n})$ 時間,因此該建堆積方法的時間複雜度為 $O(n \log n)$ 。 ## 透過走訪堆積化實現 實際上,我們可以實現一種更為高效的建堆積方法,共分為兩步。 1. 將串列所有元素原封不動地新增到堆積中,此時堆積的性質尚未得到滿足。 2. 倒序走訪堆積(層序走訪的倒序),依次對每個非葉節點執行“從頂至底堆積化”。 **每當堆積化一個節點後,以該節點為根節點的子樹就形成一個合法的子堆積**。而由於是倒序走訪,因此堆積是“自下而上”構建的。 之所以選擇倒序走訪,是因為這樣能夠保證當前節點之下的子樹已經是合法的子堆積,這樣堆積化當前節點才是有效的。 值得說明的是,**由於葉節點沒有子節點,因此它們天然就是合法的子堆積,無須堆積化**。如以下程式碼所示,最後一個非葉節點是最後一個節點的父節點,我們從它開始倒序走訪並執行堆積化: ```src [file]{my_heap}-[class]{max_heap}-[func]{__init__} ``` ## 複雜度分析 下面,我們來嘗試推算第二種建堆積方法的時間複雜度。 - 假設完全二元樹的節點數量為 $n$ ,則葉節點數量為 $(n + 1) / 2$ ,其中 $/$ 為向下整除。因此需要堆積化的節點數量為 $(n - 1) / 2$ 。 - 在從頂至底堆積化的過程中,每個節點最多堆積化到葉節點,因此最大迭代次數為二元樹高度 $\log n$ 。 將上述兩者相乘,可得到建堆積過程的時間複雜度為 $O(n \log n)$ 。**但這個估算結果並不準確,因為我們沒有考慮到二元樹底層節點數量遠多於頂層節點的性質**。 接下來我們來進行更為準確的計算。為了降低計算難度,假設給定一個節點數量為 $n$ 、高度為 $h$ 的“完美二元樹”,該假設不會影響計算結果的正確性。 ![完美二元樹的各層節點數量](build_heap.assets/heapify_operations_count.png) 如上圖所示,節點“從頂至底堆積化”的最大迭代次數等於該節點到葉節點的距離,而該距離正是“節點高度”。因此,我們可以對各層的“節點數量 $\times$ 節點高度”求和,**得到所有節點的堆積化迭代次數的總和**。 $$ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1 $$ 化簡上式需要藉助中學的數列知識,先將 $T(h)$ 乘以 $2$ ,得到: $$ \begin{aligned} T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline 2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^{h}\times1 \newline \end{aligned} $$ 使用錯位相減法,用下式 $2 T(h)$ 減去上式 $T(h)$ ,可得: $$ 2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h $$ 觀察上式,發現 $T(h)$ 是一個等比數列,可直接使用求和公式,得到時間複雜度為: $$ \begin{aligned} T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline & = 2^{h+1} - h - 2 \newline & = O(2^h) \end{aligned} $$ 進一步,高度為 $h$ 的完美二元樹的節點數量為 $n = 2^{h+1} - 1$ ,易得複雜度為 $O(2^h) = O(n)$ 。以上推算表明,**輸入串列並建堆積的時間複雜度為 $O(n)$ ,非常高效**。 ================================================ FILE: zh-hant/docs/chapter_heap/heap.md ================================================ # 堆積 堆積(heap)是一種滿足特定條件的完全二元樹,主要可分為兩種型別,如下圖所示。 - 小頂堆積(min heap):任意節點的值 $\leq$ 其子節點的值。 - 大頂堆積(max heap):任意節點的值 $\geq$ 其子節點的值。 ![小頂堆積與大頂堆積](heap.assets/min_heap_and_max_heap.png) 堆積作為完全二元樹的一個特例,具有以下特性。 - 最底層節點靠左填充,其他層的節點都被填滿。 - 我們將二元樹的根節點稱為“堆積頂”,將底層最靠右的節點稱為“堆積底”。 - 對於大頂堆積(小頂堆積),堆積頂元素(根節點)的值是最大(最小)的。 ## 堆積的常用操作 需要指出的是,許多程式語言提供的是優先佇列(priority queue),這是一種抽象的資料結構,定義為具有優先順序排序的佇列。 實際上,**堆積通常用於實現優先佇列,大頂堆積相當於元素按從大到小的順序出列的優先佇列**。從使用角度來看,我們可以將“優先佇列”和“堆積”看作等價的資料結構。因此,本書對兩者不做特別區分,統一稱作“堆積”。 堆積的常用操作見下表,方法名需要根據程式語言來確定。

  堆積的操作效率

| 方法名 | 描述 | 時間複雜度 | | ----------- | ------------------------------------------------ | ----------- | | `push()` | 元素入堆積 | $O(\log n)$ | | `pop()` | 堆積頂元素出堆積 | $O(\log n)$ | | `peek()` | 訪問堆積頂元素(對於大 / 小頂堆積分別為最大 / 小值) | $O(1)$ | | `size()` | 獲取堆積的元素數量 | $O(1)$ | | `isEmpty()` | 判斷堆積是否為空 | $O(1)$ | 在實際應用中,我們可以直接使用程式語言提供的堆積類別(或優先佇列類別)。 類似於排序演算法中的“從小到大排列”和“從大到小排列”,我們可以透過設定一個 `flag` 或修改 `Comparator` 實現“小頂堆積”與“大頂堆積”之間的轉換。程式碼如下所示: === "Python" ```python title="heap.py" # 初始化小頂堆積 min_heap, flag = [], 1 # 初始化大頂堆積 max_heap, flag = [], -1 # Python 的 heapq 模組預設實現小頂堆積 # 考慮將“元素取負”後再入堆積,這樣就可以將大小關係顛倒,從而實現大頂堆積 # 在本示例中,flag = 1 時對應小頂堆積,flag = -1 時對應大頂堆積 # 元素入堆積 heapq.heappush(max_heap, flag * 1) heapq.heappush(max_heap, flag * 3) heapq.heappush(max_heap, flag * 2) heapq.heappush(max_heap, flag * 5) heapq.heappush(max_heap, flag * 4) # 獲取堆積頂元素 peek: int = flag * max_heap[0] # 5 # 堆積頂元素出堆積 # 出堆積元素會形成一個從大到小的序列 val = flag * heapq.heappop(max_heap) # 5 val = flag * heapq.heappop(max_heap) # 4 val = flag * heapq.heappop(max_heap) # 3 val = flag * heapq.heappop(max_heap) # 2 val = flag * heapq.heappop(max_heap) # 1 # 獲取堆積大小 size: int = len(max_heap) # 判斷堆積是否為空 is_empty: bool = not max_heap # 輸入串列並建堆積 min_heap: list[int] = [1, 3, 2, 5, 4] heapq.heapify(min_heap) ``` === "C++" ```cpp title="heap.cpp" /* 初始化堆積 */ // 初始化小頂堆積 priority_queue, greater> minHeap; // 初始化大頂堆積 priority_queue, less> maxHeap; /* 元素入堆積 */ maxHeap.push(1); maxHeap.push(3); maxHeap.push(2); maxHeap.push(5); maxHeap.push(4); /* 獲取堆積頂元素 */ int peek = maxHeap.top(); // 5 /* 堆積頂元素出堆積 */ // 出堆積元素會形成一個從大到小的序列 maxHeap.pop(); // 5 maxHeap.pop(); // 4 maxHeap.pop(); // 3 maxHeap.pop(); // 2 maxHeap.pop(); // 1 /* 獲取堆積大小 */ int size = maxHeap.size(); /* 判斷堆積是否為空 */ bool isEmpty = maxHeap.empty(); /* 輸入串列並建堆積 */ vector input{1, 3, 2, 5, 4}; priority_queue, greater> minHeap(input.begin(), input.end()); ``` === "Java" ```java title="heap.java" /* 初始化堆積 */ // 初始化小頂堆積 Queue minHeap = new PriorityQueue<>(); // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); /* 元素入堆積 */ maxHeap.offer(1); maxHeap.offer(3); maxHeap.offer(2); maxHeap.offer(5); maxHeap.offer(4); /* 獲取堆積頂元素 */ int peek = maxHeap.peek(); // 5 /* 堆積頂元素出堆積 */ // 出堆積元素會形成一個從大到小的序列 peek = maxHeap.poll(); // 5 peek = maxHeap.poll(); // 4 peek = maxHeap.poll(); // 3 peek = maxHeap.poll(); // 2 peek = maxHeap.poll(); // 1 /* 獲取堆積大小 */ int size = maxHeap.size(); /* 判斷堆積是否為空 */ boolean isEmpty = maxHeap.isEmpty(); /* 輸入串列並建堆積 */ minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); ``` === "C#" ```csharp title="heap.cs" /* 初始化堆積 */ // 初始化小頂堆積 PriorityQueue minHeap = new(); // 初始化大頂堆積(使用 lambda 表示式修改 Comparer 即可) PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); /* 元素入堆積 */ maxHeap.Enqueue(1, 1); maxHeap.Enqueue(3, 3); maxHeap.Enqueue(2, 2); maxHeap.Enqueue(5, 5); maxHeap.Enqueue(4, 4); /* 獲取堆積頂元素 */ int peek = maxHeap.Peek();//5 /* 堆積頂元素出堆積 */ // 出堆積元素會形成一個從大到小的序列 peek = maxHeap.Dequeue(); // 5 peek = maxHeap.Dequeue(); // 4 peek = maxHeap.Dequeue(); // 3 peek = maxHeap.Dequeue(); // 2 peek = maxHeap.Dequeue(); // 1 /* 獲取堆積大小 */ int size = maxHeap.Count; /* 判斷堆積是否為空 */ bool isEmpty = maxHeap.Count == 0; /* 輸入串列並建堆積 */ minHeap = new PriorityQueue([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]); ``` === "Go" ```go title="heap.go" // Go 語言中可以透過實現 heap.Interface 來構建整數大頂堆積 // 實現 heap.Interface 需要同時實現 sort.Interface type intHeap []any // Push heap.Interface 的方法,實現推入元素到堆積 func (h *intHeap) Push(x any) { // Push 和 Pop 使用 pointer receiver 作為參數 // 因為它們不僅會對切片的內容進行調整,還會修改切片的長度。 *h = append(*h, x.(int)) } // Pop heap.Interface 的方法,實現彈出堆積頂元素 func (h *intHeap) Pop() any { // 待出堆積元素存放在最後 last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Len sort.Interface 的方法 func (h *intHeap) Len() int { return len(*h) } // Less sort.Interface 的方法 func (h *intHeap) Less(i, j int) bool { // 如果實現小頂堆積,則需要調整為小於號 return (*h)[i].(int) > (*h)[j].(int) } // Swap sort.Interface 的方法 func (h *intHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Top 獲取堆積頂元素 func (h *intHeap) Top() any { return (*h)[0] } /* Driver Code */ func TestHeap(t *testing.T) { /* 初始化堆積 */ // 初始化大頂堆積 maxHeap := &intHeap{} heap.Init(maxHeap) /* 元素入堆積 */ // 呼叫 heap.Interface 的方法,來新增元素 heap.Push(maxHeap, 1) heap.Push(maxHeap, 3) heap.Push(maxHeap, 2) heap.Push(maxHeap, 4) heap.Push(maxHeap, 5) /* 獲取堆積頂元素 */ top := maxHeap.Top() fmt.Printf("堆積頂元素為 %d\n", top) /* 堆積頂元素出堆積 */ // 呼叫 heap.Interface 的方法,來移除元素 heap.Pop(maxHeap) // 5 heap.Pop(maxHeap) // 4 heap.Pop(maxHeap) // 3 heap.Pop(maxHeap) // 2 heap.Pop(maxHeap) // 1 /* 獲取堆積大小 */ size := len(*maxHeap) fmt.Printf("堆積元素數量為 %d\n", size) /* 判斷堆積是否為空 */ isEmpty := len(*maxHeap) == 0 fmt.Printf("堆積是否為空 %t\n", isEmpty) } ``` === "Swift" ```swift title="heap.swift" /* 初始化堆積 */ // Swift 的 Heap 型別同時支持最大堆積和最小堆積,且需要引入 swift-collections var heap = Heap() /* 元素入堆積 */ heap.insert(1) heap.insert(3) heap.insert(2) heap.insert(5) heap.insert(4) /* 獲取堆積頂元素 */ var peek = heap.max()! /* 堆積頂元素出堆積 */ peek = heap.removeMax() // 5 peek = heap.removeMax() // 4 peek = heap.removeMax() // 3 peek = heap.removeMax() // 2 peek = heap.removeMax() // 1 /* 獲取堆積大小 */ let size = heap.count /* 判斷堆積是否為空 */ let isEmpty = heap.isEmpty /* 輸入串列並建堆積 */ let heap2 = Heap([1, 3, 2, 5, 4]) ``` === "JS" ```javascript title="heap.js" // JavaScript 未提供內建 Heap 類別 ``` === "TS" ```typescript title="heap.ts" // TypeScript 未提供內建 Heap 類別 ``` === "Dart" ```dart title="heap.dart" // Dart 未提供內建 Heap 類別 ``` === "Rust" ```rust title="heap.rs" use std::collections::BinaryHeap; use std::cmp::Reverse; /* 初始化堆積 */ // 初始化小頂堆積 let mut min_heap = BinaryHeap::>::new(); // 初始化大頂堆積 let mut max_heap = BinaryHeap::new(); /* 元素入堆積 */ max_heap.push(1); max_heap.push(3); max_heap.push(2); max_heap.push(5); max_heap.push(4); /* 獲取堆積頂元素 */ let peek = max_heap.peek().unwrap(); // 5 /* 堆積頂元素出堆積 */ // 出堆積元素會形成一個從大到小的序列 let peek = max_heap.pop().unwrap(); // 5 let peek = max_heap.pop().unwrap(); // 4 let peek = max_heap.pop().unwrap(); // 3 let peek = max_heap.pop().unwrap(); // 2 let peek = max_heap.pop().unwrap(); // 1 /* 獲取堆積大小 */ let size = max_heap.len(); /* 判斷堆積是否為空 */ let is_empty = max_heap.is_empty(); /* 輸入串列並建堆積 */ let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]); ``` === "C" ```c title="heap.c" // C 未提供內建 Heap 類別 ``` === "Kotlin" ```kotlin title="heap.kt" /* 初始化堆積 */ // 初始化小頂堆積 var minHeap = PriorityQueue() // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } /* 元素入堆積 */ maxHeap.offer(1) maxHeap.offer(3) maxHeap.offer(2) maxHeap.offer(5) maxHeap.offer(4) /* 獲取堆積頂元素 */ var peek = maxHeap.peek() // 5 /* 堆積頂元素出堆積 */ // 出堆積元素會形成一個從大到小的序列 peek = maxHeap.poll() // 5 peek = maxHeap.poll() // 4 peek = maxHeap.poll() // 3 peek = maxHeap.poll() // 2 peek = maxHeap.poll() // 1 /* 獲取堆積大小 */ val size = maxHeap.size /* 判斷堆積是否為空 */ val isEmpty = maxHeap.isEmpty() /* 輸入串列並建堆積 */ minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) ``` === "Ruby" ```ruby title="heap.rb" # Ruby 未提供內建 Heap 類別 ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20min_heap%2C%20flag%20%3D%20%5B%5D%2C%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20max_heap%2C%20flag%20%3D%20%5B%5D%2C%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20Python%20%E7%9A%84%20heapq%20%E6%A8%A1%E7%B5%84%E9%A0%90%E8%A8%AD%E5%AF%A6%E7%8F%BE%E5%B0%8F%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20%23%20%E8%80%83%E6%85%AE%E5%B0%87%E2%80%9C%E5%85%83%E7%B4%A0%E5%8F%96%E8%B2%A0%E2%80%9D%E5%BE%8C%E5%86%8D%E5%85%A5%E5%A0%86%E7%A9%8D%EF%BC%8C%E9%80%99%E6%A8%A3%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%B0%87%E5%A4%A7%E5%B0%8F%E9%97%9C%E4%BF%82%E9%A1%9B%E5%80%92%EF%BC%8C%E5%BE%9E%E8%80%8C%E5%AF%A6%E7%8F%BE%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20%23%20%E5%9C%A8%E6%9C%AC%E7%A4%BA%E4%BE%8B%E4%B8%AD%EF%BC%8Cflag%20%3D%201%20%E6%99%82%E5%B0%8D%E6%87%89%E5%B0%8F%E9%A0%82%E5%A0%86%E7%A9%8D%EF%BC%8Cflag%20%3D%20-1%20%E6%99%82%E5%B0%8D%E6%87%89%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%A9%8D%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%201%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%203%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%202%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%205%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20flag%20%2A%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%A9%8D%0A%20%20%20%20%23%20%E5%87%BA%E5%A0%86%E7%A9%8D%E5%85%83%E7%B4%A0%E6%9C%83%E5%BD%A2%E6%88%90%E4%B8%80%E5%80%8B%E5%BE%9E%E5%A4%A7%E5%88%B0%E5%B0%8F%E7%9A%84%E5%BA%8F%E5%88%97%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E5%A4%A7%E5%B0%8F%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E5%A0%86%E7%A9%8D%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%BC%B8%E5%85%A5%E4%B8%B2%E5%88%97%E4%B8%A6%E5%BB%BA%E5%A0%86%E7%A9%8D%0A%20%20%20%20min_heap%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## 堆積的實現 下文實現的是大頂堆積。若要將其轉換為小頂堆積,只需將所有大小邏輯判斷進行逆轉(例如,將 $\geq$ 替換為 $\leq$ )。感興趣的讀者可以自行實現。 ### 堆積的儲存與表示 “二元樹”章節講過,完全二元樹非常適合用陣列來表示。由於堆積正是一種完全二元樹,**因此我們將採用陣列來儲存堆積**。 當使用陣列表示二元樹時,元素代表節點值,索引代表節點在二元樹中的位置。**節點指標透過索引對映公式來實現**。 如下圖所示,給定索引 $i$ ,其左子節點的索引為 $2i + 1$ ,右子節點的索引為 $2i + 2$ ,父節點的索引為 $(i - 1) / 2$(向下整除)。當索引越界時,表示空節點或節點不存在。 ![堆積的表示與儲存](heap.assets/representation_of_heap.png) 我們可以將索引對映公式封裝成函式,方便後續使用: ```src [file]{my_heap}-[class]{max_heap}-[func]{parent} ``` ### 訪問堆積頂元素 堆積頂元素即為二元樹的根節點,也就是串列的首個元素: ```src [file]{my_heap}-[class]{max_heap}-[func]{peek} ``` ### 元素入堆積 給定元素 `val` ,我們首先將其新增到堆積底。新增之後,由於 `val` 可能大於堆積中其他元素,堆積的成立條件可能已被破壞,**因此需要修復從插入節點到根節點的路徑上的各個節點**,這個操作被稱為堆積化(heapify)。 考慮從入堆積節點開始,**從底至頂執行堆積化**。如下圖所示,我們比較插入節點與其父節點的值,如果插入節點更大,則將它們交換。然後繼續執行此操作,從底至頂修復堆積中的各個節點,直至越過根節點或遇到無須交換的節點時結束。 === "<1>" ![元素入堆積步驟](heap.assets/heap_push_step1.png) === "<2>" ![heap_push_step2](heap.assets/heap_push_step2.png) === "<3>" ![heap_push_step3](heap.assets/heap_push_step3.png) === "<4>" ![heap_push_step4](heap.assets/heap_push_step4.png) === "<5>" ![heap_push_step5](heap.assets/heap_push_step5.png) === "<6>" ![heap_push_step6](heap.assets/heap_push_step6.png) === "<7>" ![heap_push_step7](heap.assets/heap_push_step7.png) === "<8>" ![heap_push_step8](heap.assets/heap_push_step8.png) === "<9>" ![heap_push_step9](heap.assets/heap_push_step9.png) 設節點總數為 $n$ ,則樹的高度為 $O(\log n)$ 。由此可知,堆積化操作的迴圈輪數最多為 $O(\log n)$ ,**元素入堆積操作的時間複雜度為 $O(\log n)$** 。程式碼如下所示: ```src [file]{my_heap}-[class]{max_heap}-[func]{sift_up} ``` ### 堆積頂元素出堆積 堆積頂元素是二元樹的根節點,即串列首元素。如果我們直接從串列中刪除首元素,那麼二元樹中所有節點的索引都會發生變化,這將使得後續使用堆積化進行修復變得困難。為了儘量減少元素索引的變動,我們採用以下操作步驟。 1. 交換堆積頂元素與堆積底元素(交換根節點與最右葉節點)。 2. 交換完成後,將堆積底從串列中刪除(注意,由於已經交換,因此實際上刪除的是原來的堆積頂元素)。 3. 從根節點開始,**從頂至底執行堆積化**。 如下圖所示,**“從頂至底堆積化”的操作方向與“從底至頂堆積化”相反**,我們將根節點的值與其兩個子節點的值進行比較,將最大的子節點與根節點交換。然後迴圈執行此操作,直到越過葉節點或遇到無須交換的節點時結束。 === "<1>" ![堆積頂元素出堆積步驟](heap.assets/heap_pop_step1.png) === "<2>" ![heap_pop_step2](heap.assets/heap_pop_step2.png) === "<3>" ![heap_pop_step3](heap.assets/heap_pop_step3.png) === "<4>" ![heap_pop_step4](heap.assets/heap_pop_step4.png) === "<5>" ![heap_pop_step5](heap.assets/heap_pop_step5.png) === "<6>" ![heap_pop_step6](heap.assets/heap_pop_step6.png) === "<7>" ![heap_pop_step7](heap.assets/heap_pop_step7.png) === "<8>" ![heap_pop_step8](heap.assets/heap_pop_step8.png) === "<9>" ![heap_pop_step9](heap.assets/heap_pop_step9.png) === "<10>" ![heap_pop_step10](heap.assets/heap_pop_step10.png) 與元素入堆積操作相似,堆積頂元素出堆積操作的時間複雜度也為 $O(\log n)$ 。程式碼如下所示: ```src [file]{my_heap}-[class]{max_heap}-[func]{sift_down} ``` ## 堆積的常見應用 - **優先佇列**:堆積通常作為實現優先佇列的首選資料結構,其入列和出列操作的時間複雜度均為 $O(\log n)$ ,而建堆積操作為 $O(n)$ ,這些操作都非常高效。 - **堆積排序**:給定一組資料,我們可以用它們建立一個堆積,然後不斷地執行元素出堆積操作,從而得到有序資料。然而,我們通常會使用一種更優雅的方式實現堆積排序,詳見“堆積排序”章節。 - **獲取最大的 $k$ 個元素**:這是一個經典的演算法問題,同時也是一種典型應用,例如選擇熱度前 10 的新聞作為微博熱搜,選取銷量前 10 的商品等。 ================================================ FILE: zh-hant/docs/chapter_heap/index.md ================================================ # 堆積 ![堆積](../assets/covers/chapter_heap.jpg) !!! abstract 堆積就像是山嶽峰巒,層疊起伏、形態各異。 座座山峰高低錯落,而最高的山峰總是最先映入眼簾。 ================================================ FILE: zh-hant/docs/chapter_heap/summary.md ================================================ # 小結 ### 重點回顧 - 堆積是一棵完全二元樹,根據成立條件可分為大頂堆積和小頂堆積。大(小)頂堆積的堆積頂元素是最大(小)的。 - 優先佇列的定義是具有出列優先順序的佇列,通常使用堆積來實現。 - 堆積的常用操作及其對應的時間複雜度包括:元素入堆積 $O(\log n)$、堆積頂元素出堆積 $O(\log n)$ 和訪問堆積頂元素 $O(1)$ 等。 - 完全二元樹非常適合用陣列表示,因此我們通常使用陣列來儲存堆積。 - 堆積化操作用於維護堆積的性質,在入堆積和出堆積操作中都會用到。 - 輸入 $n$ 個元素並建堆積的時間複雜度可以最佳化至 $O(n)$ ,非常高效。 - Top-k 是一個經典演算法問題,可以使用堆積資料結構高效解決,時間複雜度為 $O(n \log k)$ 。 ### Q & A **Q**:資料結構的“堆積”與記憶體管理的“堆積”是同一個概念嗎? 兩者不是同一個概念,只是碰巧都叫“堆積”。計算機系統記憶體中的堆積是動態記憶體分配的一部分,程式在執行時可以使用它來儲存資料。程式可以請求一定量的堆積記憶體,用於儲存如物件和陣列等複雜結構。當這些資料不再需要時,程式需要釋放這些記憶體,以防止記憶體流失。相較於堆疊記憶體,堆積記憶體的管理和使用需要更謹慎,使用不當可能會導致記憶體流失和野指標等問題。 ================================================ FILE: zh-hant/docs/chapter_heap/top_k.md ================================================ # Top-k 問題 !!! question 給定一個長度為 $n$ 的無序陣列 `nums` ,請返回陣列中最大的 $k$ 個元素。 對於該問題,我們先介紹兩種思路比較直接的解法,再介紹效率更高的堆積解法。 ## 方法一:走訪選擇 我們可以進行下圖所示的 $k$ 輪走訪,分別在每輪中提取第 $1$、$2$、$\dots$、$k$ 大的元素,時間複雜度為 $O(nk)$ 。 此方法只適用於 $k \ll n$ 的情況,因為當 $k$ 與 $n$ 比較接近時,其時間複雜度趨向於 $O(n^2)$ ,非常耗時。 ![走訪尋找最大的 k 個元素](top_k.assets/top_k_traversal.png) !!! tip 當 $k = n$ 時,我們可以得到完整的有序序列,此時等價於“選擇排序”演算法。 ## 方法二:排序 如下圖所示,我們可以先對陣列 `nums` 進行排序,再返回最右邊的 $k$ 個元素,時間複雜度為 $O(n \log n)$ 。 顯然,該方法“超額”完成任務了,因為我們只需找出最大的 $k$ 個元素即可,而不需要排序其他元素。 ![排序尋找最大的 k 個元素](top_k.assets/top_k_sorting.png) ## 方法三:堆積 我們可以基於堆積更加高效地解決 Top-k 問題,流程如下圖所示。 1. 初始化一個小頂堆積,其堆積頂元素最小。 2. 先將陣列的前 $k$ 個元素依次入堆積。 3. 從第 $k + 1$ 個元素開始,若當前元素大於堆積頂元素,則將堆積頂元素出堆積,並將當前元素入堆積。 4. 走訪完成後,堆積中儲存的就是最大的 $k$ 個元素。 === "<1>" ![基於堆積尋找最大的 k 個元素](top_k.assets/top_k_heap_step1.png) === "<2>" ![top_k_heap_step2](top_k.assets/top_k_heap_step2.png) === "<3>" ![top_k_heap_step3](top_k.assets/top_k_heap_step3.png) === "<4>" ![top_k_heap_step4](top_k.assets/top_k_heap_step4.png) === "<5>" ![top_k_heap_step5](top_k.assets/top_k_heap_step5.png) === "<6>" ![top_k_heap_step6](top_k.assets/top_k_heap_step6.png) === "<7>" ![top_k_heap_step7](top_k.assets/top_k_heap_step7.png) === "<8>" ![top_k_heap_step8](top_k.assets/top_k_heap_step8.png) === "<9>" ![top_k_heap_step9](top_k.assets/top_k_heap_step9.png) 示例程式碼如下: ```src [file]{top_k}-[class]{}-[func]{top_k_heap} ``` 總共執行了 $n$ 輪入堆積和出堆積,堆積的最大長度為 $k$ ,因此時間複雜度為 $O(n \log k)$ 。該方法的效率很高,當 $k$ 較小時,時間複雜度趨向 $O(n)$ ;當 $k$ 較大時,時間複雜度不會超過 $O(n \log n)$ 。 另外,該方法適用於動態資料流的使用場景。在不斷加入資料時,我們可以持續維護堆積內的元素,從而實現最大的 $k$ 個元素的動態更新。 ================================================ FILE: zh-hant/docs/chapter_hello_algo/index.md ================================================ --- comments: true icon: material/rocket-launch-outline --- # 序 幾年前,我在力扣上分享了“劍指 Offer”系列題解,受到了許多讀者的鼓勵和支持。在與讀者交流期間,我最常被問的一個問題是“如何入門演算法”。逐漸地,我對這個問題產生了濃厚的興趣。 兩眼一抹黑地刷題似乎是最受歡迎的方法,簡單、直接且有效。然而刷題就如同玩“掃雷”遊戲,自學能力強的人能夠順利將地雷逐個排掉,而基礎不足的人很可能被炸得滿頭是包,並在挫折中步步退縮。通讀教材也是一種常見做法,但對於面向求職的人來說,畢業論文、投遞簡歷、準備筆試和面試已經消耗了大部分精力,啃厚重的書往往變成了一項艱鉅的挑戰。 如果你也面臨類似的困擾,那麼很幸運這本書“找”到了你。本書是我對這個問題給出的答案,即使不是最優解,也至少是一次積極的嘗試。本書雖然不足以讓你直接拿到 Offer,但會引導你探索資料結構與演算法的“知識地圖”,帶你瞭解不同“地雷”的形狀、大小和分佈位置,讓你掌握各種“排雷方法”。有了這些本領,相信你可以更加自如地刷題和閱讀文獻,逐步構建起完整的知識體系。 我深深贊同費曼教授所言:“Knowledge isn't free. You have to pay attention.”從這個意義上看,這本書並非完全“免費”。為了不辜負你為本書所付出的寶貴“注意力”,我會竭盡所能,投入最大的“注意力”來完成本書的創作。 本人自知學疏才淺,書中內容雖然已經過一段時間的打磨,但一定仍有許多錯誤,懇請各位老師和同學批評指正。 ![Hello 演算法](../assets/covers/chapter_hello_algo.jpg){ class="cover-image" }

Hello,演算法!

計算機的出現給世界帶來了巨大變革,它憑藉高速的計算能力和出色的可程式設計性,成為了執行演算法與處理資料的理想媒介。無論是電子遊戲的逼真畫面、自動駕駛的智慧決策,還是 AlphaGo 的精彩棋局、ChatGPT 的自然互動,這些應用都是演算法在計算機上的精妙演繹。 事實上,在計算機問世之前,演算法和資料結構就已經存在於世界的各個角落。早期的演算法相對簡單,例如古代的計數方法和工具製作步驟等。隨著文明的進步,演算法逐漸變得更加精細和複雜。從巧奪天工的匠人技藝、到解放生產力的工業產品、再到宇宙執行的科學規律,幾乎每一件平凡或令人驚歎的事物背後,都隱藏著精妙的演算法思想。 同樣,資料結構無處不在:大到社會網路,小到地鐵線路,許多系統都可以建模為“圖”;大到一個國家,小到一個家庭,社會的主要組織形式呈現出“樹”的特徵;冬天的衣服就像“堆疊”,最先穿上的最後才能脫下;羽毛球筒則如同“佇列”,一端放入、另一端取出;字典就像一個“雜湊表”,能夠快速查詢目標詞條。 本書旨在透過清晰易懂的動畫圖解和可執行的程式碼示例,使讀者理解演算法和資料結構的核心概念,並能夠透過程式設計來實現它們。在此基礎上,本書致力於揭示演算法在複雜世界中的生動體現,展現演算法之美。希望本書能夠幫助到你! ================================================ FILE: zh-hant/docs/chapter_introduction/algorithms_are_everywhere.md ================================================ # 演算法無處不在 當我們聽到“演算法”這個詞時,很自然地會想到數學。然而實際上,許多演算法並不涉及複雜數學,而是更多地依賴基本邏輯,這些邏輯在我們的日常生活中處處可見。 在正式探討演算法之前,有一個有趣的事實值得分享:**你已經在不知不覺中學會了許多演算法,並習慣將它們應用到日常生活中了**。下面我將舉幾個具體的例子來證實這一點。 **例一:查字典**。在字典裡,每個漢字都對應一個拼音,而字典是按照拼音字母順序排列的。假設我們需要查詢一個拼音首字母為 $r$ 的字,通常會按照下圖所示的方式實現。 1. 翻開字典約一半的頁數,檢視該頁的首字母是什麼,假設首字母為 $m$ 。 2. 由於在拼音字母表中 $r$ 位於 $m$ 之後,所以排除字典前半部分,查詢範圍縮小到後半部分。 3. 不斷重複步驟 `1.` 和步驟 `2.` ,直至找到拼音首字母為 $r$ 的頁碼為止。 === "<1>" ![查字典步驟](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png) === "<2>" ![binary_search_dictionary_step2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png) === "<3>" ![binary_search_dictionary_step3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png) === "<4>" ![binary_search_dictionary_step4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png) === "<5>" ![binary_search_dictionary_step5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png) 查字典這個小學生必備技能,實際上就是著名的“二分搜尋”演算法。從資料結構的角度,我們可以把字典視為一個已排序的“陣列”;從演算法的角度,我們可以將上述查字典的一系列操作看作“二分搜尋”。 **例二:整理撲克**。我們在打牌時,每局都需要整理手中的撲克牌,使其從小到大排列,實現流程如下圖所示。 1. 將撲克牌劃分為“有序”和“無序”兩部分,並假設初始狀態下最左 1 張撲克牌已經有序。 2. 在無序部分抽出一張撲克牌,插入至有序部分的正確位置;完成後最左 2 張撲克已經有序。 3. 不斷迴圈步驟 `2.` ,每一輪將一張撲克牌從無序部分插入至有序部分,直至所有撲克牌都有序。 ![撲克排序步驟](algorithms_are_everywhere.assets/playing_cards_sorting.png) 上述整理撲克牌的方法本質上是“插入排序”演算法,它在處理小型資料集時非常高效。許多程式語言的排序庫函式中都有插入排序的身影。 **例三:貨幣找零**。假設我們在超市購買了 $69$ 元的商品,給了收銀員 $100$ 元,則收銀員需要找我們 $31$ 元。他會很自然地完成如下圖所示的思考。 1. 可選項是比 $31$ 元面值更小的貨幣,包括 $1$ 元、$5$ 元、$10$ 元、$20$ 元。 2. 從可選項中拿出最大的 $20$ 元,剩餘 $31 - 20 = 11$ 元。 3. 從剩餘可選項中拿出最大的 $10$ 元,剩餘 $11 - 10 = 1$ 元。 4. 從剩餘可選項中拿出最大的 $1$ 元,剩餘 $1 - 1 = 0$ 元。 5. 完成找零,方案為 $20 + 10 + 1 = 31$ 元。 ![貨幣找零過程](algorithms_are_everywhere.assets/greedy_change.png) 在以上步驟中,我們每一步都採取當前看來最好的選擇(儘可能用大面額的貨幣),最終得到了可行的找零方案。從資料結構與演算法的角度看,這種方法本質上是“貪婪”演算法。 小到烹飪一道菜,大到星際航行,幾乎所有問題的解決都離不開演算法。計算機的出現使得我們能夠透過程式設計將資料結構儲存在記憶體中,同時編寫程式碼呼叫 CPU 和 GPU 執行演算法。這樣一來,我們就能把生活中的問題轉移到計算機上,以更高效的方式解決各種複雜問題。 !!! tip 如果你對資料結構、演算法、陣列和二分搜尋等概念仍感到一知半解,請繼續往下閱讀,本書將引導你邁入資料結構與演算法的知識殿堂。 ================================================ FILE: zh-hant/docs/chapter_introduction/index.md ================================================ # 初識演算法 ![初識演算法](../assets/covers/chapter_introduction.jpg) !!! abstract 一位少女翩翩起舞,與資料交織在一起,裙襬上飄揚著演算法的旋律。 她邀請你共舞,請緊跟她的步伐,踏入充滿邏輯與美感的演算法世界。 ================================================ FILE: zh-hant/docs/chapter_introduction/summary.md ================================================ # 小結 ### 重點回顧 - 演算法在日常生活中無處不在,並不是遙不可及的高深知識。實際上,我們已經在不知不覺中學會了許多演算法,用以解決生活中的大小問題。 - 查字典的原理與二分搜尋演算法相一致。二分搜尋演算法體現了分而治之的重要演算法思想。 - 整理撲克的過程與插入排序演算法非常類似。插入排序演算法適合排序小型資料集。 - 貨幣找零的步驟本質上是貪婪演算法,每一步都採取當前看來最好的選擇。 - 演算法是在有限時間內解決特定問題的一組指令或操作步驟,而資料結構是計算機中組織和儲存資料的方式。 - 資料結構與演算法緊密相連。資料結構是演算法的基石,而演算法為資料結構注入生命力。 - 我們可以將資料結構與演算法類比為拼裝積木,積木代表資料,積木的形狀和連線方式等代表資料結構,拼裝積木的步驟則對應演算法。 ### Q & A **Q**:作為一名程式設計師,我在日常工作中從未用演算法解決過問題,常用演算法都被程式語言封裝好了,直接用就可以了;這是否意味著我們工作中的問題還沒有到達需要演算法的程度? 如果把具體的工作技能比作是武功的“招式”的話,那麼基礎科目應該更像是“內功”。 我認為學演算法(以及其他基礎科目)的意義不是在於在工作中從零實現它,而是基於學到的知識,在解決問題時能夠作出專業的反應和判斷,從而提升工作的整體質量。舉一個簡單例子,每種程式語言都內建了排序函式: - 如果我們沒有學過資料結構與演算法,那麼給定任何資料,我們可能都塞給這個排序函式去做了。執行順暢、效能不錯,看上去並沒有什麼問題。 - 但如果學過演算法,我們就會知道內建排序函式的時間複雜度是 $O(n \log n)$ ;而如果給定的資料是固定位數的整數(例如學號),那麼我們就可以用效率更高的“基數排序”來做,將時間複雜度降為 $O(nk)$ ,其中 $k$ 為位數。當資料體量很大時,節省出來的執行時間就能創造較大價值(成本降低、體驗變好等)。 在工程領域中,大量問題是難以達到最優解的,許多問題只是被“差不多”地解決了。問題的難易程度一方面取決於問題本身的性質,另一方面也取決於觀測問題的人的知識儲備。人的知識越完備、經驗越多,分析問題就會越深入,問題就能被解決得更優雅。 ================================================ FILE: zh-hant/docs/chapter_introduction/what_is_dsa.md ================================================ # 演算法是什麼 ## 演算法定義 演算法(algorithm)是在有限時間內解決特定問題的一組指令或操作步驟,它具有以下特性。 - 問題是明確的,包含清晰的輸入和輸出定義。 - 具有可行性,能夠在有限步驟、時間和記憶體空間下完成。 - 各步驟都有確定的含義,在相同的輸入和執行條件下,輸出始終相同。 ## 資料結構定義 資料結構(data structure)是組織和儲存資料的方式,涵蓋資料內容、資料之間關係和資料操作方法,它具有以下設計目標。 - 空間佔用儘量少,以節省計算機記憶體。 - 資料操作儘可能快速,涵蓋資料訪問、新增、刪除、更新等。 - 提供簡潔的資料表示和邏輯資訊,以便演算法高效執行。 **資料結構設計是一個充滿權衡的過程**。如果想在某方面取得提升,往往需要在另一方面作出妥協。下面舉兩個例子。 - 鏈結串列相較於陣列,在資料新增和刪除操作上更加便捷,但犧牲了資料訪問速度。 - 圖相較於鏈結串列,提供了更豐富的邏輯資訊,但需要佔用更大的記憶體空間。 ## 資料結構與演算法的關係 如下圖所示,資料結構與演算法高度相關、緊密結合,具體表現在以下三個方面。 - 資料結構是演算法的基石。資料結構為演算法提供了結構化儲存的資料,以及操作資料的方法。 - 演算法為資料結構注入生命力。資料結構本身僅儲存資料資訊,結合演算法才能解決特定問題。 - 演算法通常可以基於不同的資料結構實現,但執行效率可能相差很大,選擇合適的資料結構是關鍵。 ![資料結構與演算法的關係](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png) 資料結構與演算法猶如下圖所示的拼裝積木。一套積木,除了包含許多零件之外,還附有詳細的組裝說明書。我們按照說明書一步步操作,就能組裝出精美的積木模型。 ![拼裝積木](what_is_dsa.assets/assembling_blocks.png) 兩者的詳細對應關係如下表所示。

  將資料結構與演算法類比為拼裝積木

| 資料結構與演算法 | 拼裝積木 | | -------------- | ---------------------------------------- | | 輸入資料 | 未拼裝的積木 | | 資料結構 | 積木組織形式,包括形狀、大小、連線方式等 | | 演算法 | 把積木拼成目標形態的一系列操作步驟 | | 輸出資料 | 積木模型 | 值得說明的是,資料結構與演算法是獨立於程式語言的。正因如此,本書得以提供基於多種程式語言的實現。 !!! tip "約定俗成的簡稱" 在實際討論時,我們通常會將“資料結構與演算法”簡稱為“演算法”。比如眾所周知的 LeetCode 演算法題目,實際上同時考查資料結構和演算法兩方面的知識。 ================================================ FILE: zh-hant/docs/chapter_preface/about_the_book.md ================================================ # 關於本書 本專案旨在建立一本開源、免費、對新手友好的資料結構與演算法入門教程。 - 全書採用動畫圖解,內容清晰易懂、學習曲線平滑,引導初學者探索資料結構與演算法的知識地圖。 - 源程式碼可一鍵執行,幫助讀者在練習中提升程式設計技能,瞭解演算法工作原理和資料結構底層實現。 - 提倡讀者互助學習,歡迎大家在評論區提出問題與分享見解,在交流討論中共同進步。 ## 目標讀者 若你是演算法初學者,從未接觸過演算法,或者已經有一些刷題經驗,對資料結構與演算法有模糊的認識,在會與不會之間反覆橫跳,那麼本書正是為你量身定製的! 如果你已經積累一定的刷題量,熟悉大部分題型,那麼本書可助你回顧與梳理演算法知識體系,倉庫源程式碼可以當作“刷題工具庫”或“演算法字典”來使用。 若你是演算法“大神”,我們期待收到你的寶貴建議,或者[一起參與創作](https://www.hello-algo.com/chapter_appendix/contribution/)。 !!! success "前置條件" 你需要至少具備任一語言的程式設計基礎,能夠閱讀和編寫簡單程式碼。 ## 內容結構 本書的主要內容如下圖所示。 - **複雜度分析**:資料結構和演算法的評價維度與方法。時間複雜度和空間複雜度的推算方法、常見型別、示例等。 - **資料結構**:基本資料型別和資料結構的分類方法。陣列、鏈結串列、堆疊、佇列、雜湊表、樹、堆積、圖等資料結構的定義、優缺點、常用操作、常見型別、典型應用、實現方法等。 - **演算法**:搜尋、排序、分治、回溯、動態規劃、貪婪等演算法的定義、優缺點、效率、應用場景、解題步驟和示例問題等。 ![本書主要內容](about_the_book.assets/hello_algo_mindmap.png) ## 致謝 本書在開源社群眾多貢獻者的共同努力下不斷完善。感謝每一位投入時間與精力的撰稿人,他們是(按照 GitHub 自動生成的順序):krahets、coderonion、Gonglja、nuomi1、Reanon、justin-tse、hpstory、danielsss、curtishd、night-cruise、S-N-O-R-L-A-X、rongyi、msk397、gvenusleo、khoaxuantu、rivertwilight、K3v123、gyt95、zhuoqinyue、yuelinxin、Zuoxun、mingXta、Phoenix0415、FangYuan33、GN-Yu、longsizhuo、IsChristina、xBLACKICEx、guowei-gong、Cathay-Chen、pengchzn、QiLOL、magentaqin、hello-ikun、JoseHung、qualifier1024、thomasq0、sunshinesDL、L-Super、Guanngxu、Transmigration-zhou、WSL0809、Slone123c、lhxsm、yuan0221、what-is-me、Shyam-Chen、theNefelibatas、longranger2、codeberg-user、xiongsp、JeffersonHuang、prinpal、seven1240、Wonderdch、malone6、xiaomiusa87、gaofer、bluebean-cloud、a16su、SamJin98、hongyun-robot、nanlei、XiaChuerwu、yd-j、iron-irax、mgisr、steventimes、junminhong、heshuyue、danny900714、MolDuM、Nigh、Dr-XYZ、XC-Zero、reeswell、PXG-XPG、NI-SW、Horbin-Magician、Enlightenus、YangXuanyi、beatrix-chan、DullSword、xjr7670、jiaxianhua、qq909244296、iStig、boloboloda、hts0000、gledfish、wenjianmin、keshida、kilikilikid、lclc6、lwbaptx、linyejoe2、liuxjerry、llql1211、fbigm、echo1937、szu17dmy、dshlstarr、Yucao-cy、coderlef、czruby、bongbongbakudan、beintentional、ZongYangL、ZhongYuuu、ZhongGuanbin、hezhizhen、linzeyan、ZJKung、luluxia、xb534、ztkuaikuai、yw-1021、ElaBosak233、baagod、zhouLion、yishangzhang、yi427、yanedie、yabo083、weibk、wangwang105、th1nk3r-ing、tao363、4yDX3906、syd168、sslmj2020、smilelsb、siqyka、selear、sdshaoda、Xi-Row、popozhu、nuquist19、noobcodemaker、XiaoK29、chadyi、lyl625760、lucaswangdev、0130w、shanghai-Jerry、EJackYang、Javesun99、eltociear、lipusheng、KNChiu、BlindTerran、ShiMaRing、lovelock、FreddieLi、FloranceYeh、fanchenggang、gltianwen、goerll、nedchu、curly210102、CuB3y0nd、KraHsu、CarrotDLaw、youshaoXG、bubble9um、Asashishi、Asa0oo0o0o、fanenr、eagleanurag、akshiterate、52coder、foursevenlove、KorsChen、GaochaoZhu、hopkings2008、yang-le、realwujing、Evilrabbit520、Umer-Jahangir、Turing-1024-Lee、Suremotoo、paoxiaomooo、Chieko-Seren、Allen-Scai、ymmmas、Risuntsy、Richard-Zhang1019、RafaelCaso、qingpeng9802、primexiao、Urbaner3、zhongfq、nidhoggfgg、MwumLi、CreatorMetaSky、martinx、ZnYang2018、hugtyftg、logan-qiu、psychelzh、Keynman、KeiichiKasai 和 KawaiiAsh。 本書的程式碼審閱工作由 coderonion、curtishd、Gonglja、gvenusleo、hpstory、justin-tse、khoaxuantu、krahets、night-cruise、nuomi1、Reanon 和 rongyi 完成(按照首字母順序排列)。感謝他們付出的時間與精力,正是他們確保了各語言程式碼的規範與統一。 本書的繁體中文版由 Shyam-Chen 和 Dr-XYZ 審閱,英文版由 yuelinxin、K3v123、QiLOL、Phoenix0415、SamJin98、yanedie、RafaelCaso、pengchzn、thomasq0 和 magentaqin 審閱,日文版由 eltociear 審閱。正是因為他們的持續貢獻,這本書才能夠服務於更廣泛的讀者群體,感謝他們。 本書的 ePub 電子書生成工具由 zhongfq 開發。感謝他的貢獻,為讀者提供了更加自由的閱讀方式。 在本書的創作過程中,我得到了許多人的幫助。 - 感謝我在公司的導師李汐博士,在一次暢談中你鼓勵我“快行動起來”,堅定了我寫這本書的決心; - 感謝我的女朋友泡泡作為本書的首位讀者,從演算法小白的角度提出許多寶貴建議,使得本書更適合新手閱讀; - 感謝騰寶、琦寶、飛寶為本書起了一個富有創意的名字,喚起大家寫下第一行程式碼“Hello World!”的美好回憶; - 感謝校銓在智慧財產權方面提供的專業幫助,這對本開源書的完善起到了重要作用; - 感謝蘇潼為本書設計了精美的封面和 logo ,並在我的強迫症的驅使下多次耐心修改; - 感謝 @squidfunk 提供的排版建議,以及他開發的開源文件主題 [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master) 。 在寫作過程中,我閱讀了許多關於資料結構與演算法的教材和文章。這些作品為本書提供了優秀的範本,確保了本書內容的準確性與品質。在此感謝所有老師和前輩的傑出貢獻! 本書倡導手腦並用的學習方式,在這一點上我深受[《動手學深度學習》](https://github.com/d2l-ai/d2l-zh)的啟發。在此向各位讀者強烈推薦這本優秀的著作。 **衷心感謝我的父母,正是你們一直以來的支持與鼓勵,讓我有機會做這件富有趣味的事**。 ================================================ FILE: zh-hant/docs/chapter_preface/index.md ================================================ # 前言 ![前言](../assets/covers/chapter_preface.jpg) !!! abstract 演算法猶如美妙的交響樂,每一行程式碼都像韻律般流淌。 願這本書在你的腦海中輕輕響起,留下獨特而深刻的旋律。 ================================================ FILE: zh-hant/docs/chapter_preface/suggestions.md ================================================ # 如何使用本書 !!! tip 為了獲得最佳的閱讀體驗,建議你通讀本節內容。 ## 行文風格約定 - 標題後標註 `*` 的是選讀章節,內容相對困難。如果你的時間有限,可以先跳過。 - 專業術語會使用黑體(紙質版和 PDF 版)或新增下劃線(網頁版),例如陣列(array)。建議記住它們,以便閱讀文獻。 - 重點內容和總結性語句會 **加粗**,這類文字值得特別關注。 - 有特指含義的詞句會使用“引號”標註,以避免歧義。 - 當涉及程式語言之間不一致的名詞時,本書均以 Python 為準,例如使用 `None` 來表示“空”。 - 本書部分放棄了程式語言的註釋規範,以換取更加緊湊的內容排版。註釋主要分為三種類型:標題註釋、內容註釋、多行註釋。 === "Python" ```python title="" """標題註釋,用於標註函式、類別、測試樣例等""" # 內容註釋,用於詳解程式碼 """ 多行 註釋 """ ``` === "C++" ```cpp title="" /* 標題註釋,用於標註函式、類別、測試樣例等 */ // 內容註釋,用於詳解程式碼 /** * 多行 * 註釋 */ ``` === "Java" ```java title="" /* 標題註釋,用於標註函式、類別、測試樣例等 */ // 內容註釋,用於詳解程式碼 /** * 多行 * 註釋 */ ``` === "C#" ```csharp title="" /* 標題註釋,用於標註函式、類別、測試樣例等 */ // 內容註釋,用於詳解程式碼 /** * 多行 * 註釋 */ ``` === "Go" ```go title="" /* 標題註釋,用於標註函式、類別、測試樣例等 */ // 內容註釋,用於詳解程式碼 /** * 多行 * 註釋 */ ``` === "Swift" ```swift title="" /* 標題註釋,用於標註函式、類別、測試樣例等 */ // 內容註釋,用於詳解程式碼 /** * 多行 * 註釋 */ ``` === "JS" ```javascript title="" /* 標題註釋,用於標註函式、類別、測試樣例等 */ // 內容註釋,用於詳解程式碼 /** * 多行 * 註釋 */ ``` === "TS" ```typescript title="" /* 標題註釋,用於標註函式、類別、測試樣例等 */ // 內容註釋,用於詳解程式碼 /** * 多行 * 註釋 */ ``` === "Dart" ```dart title="" /* 標題註釋,用於標註函式、類別、測試樣例等 */ // 內容註釋,用於詳解程式碼 /** * 多行 * 註釋 */ ``` === "Rust" ```rust title="" /* 標題註釋,用於標註函式、類別、測試樣例等 */ // 內容註釋,用於詳解程式碼 // 多行 // 註釋 ``` === "C" ```c title="" /* 標題註釋,用於標註函式、類別、測試樣例等 */ // 內容註釋,用於詳解程式碼 /** * 多行 * 註釋 */ ``` === "Kotlin" ```kotlin title="" /* 標題註釋,用於標註函式、類別、測試樣例等 */ // 內容註釋,用於詳解程式碼 /** * 多行 * 註釋 */ ``` === "Ruby" ```ruby title="" ### 標題註釋,用於標註函式、類別、測試樣例等 ### # 內容註釋,用於詳解程式碼 # 多行 # 註釋 ``` ## 在動畫圖解中高效學習 相較於文字,影片和圖片具有更高的資訊密度和結構化程度,更易於理解。在本書中,**重點和難點知識將主要透過動畫以圖解形式展示**,而文字則作為解釋與補充。 如果你在閱讀本書時,發現某段內容提供瞭如下圖所示的動畫圖解,**請以圖為主、以文字為輔**,綜合兩者來理解內容。 ![動畫圖解示例](../index.assets/animation.gif) ## 在程式碼實踐中加深理解 本書的配套程式碼託管在 [GitHub 倉庫](https://github.com/krahets/hello-algo)。如下圖所示,**源程式碼附有測試樣例,可一鍵執行**。 如果時間允許,**建議你參照程式碼自行敲一遍**。如果學習時間有限,請至少通讀並執行所有程式碼。 與閱讀程式碼相比,編寫程式碼的過程往往能帶來更多收穫。**動手學,才是真的學**。 ![執行程式碼示例](../index.assets/running_code.gif) 執行程式碼的前置工作主要分為三步。 **第一步:安裝本地程式設計環境**。請參照附錄所示的[教程](https://www.hello-algo.com/chapter_appendix/installation/)進行安裝,如果已安裝,則可跳過此步驟。 **第二步:克隆或下載程式碼倉庫**。前往 [GitHub 倉庫](https://github.com/krahets/hello-algo)。如果已經安裝 [Git](https://git-scm.com/downloads) ,可以透過以下命令克隆本倉庫: ```shell git clone https://github.com/krahets/hello-algo.git ``` 當然,你也可以在下圖所示的位置,點選“Download ZIP”按鈕直接下載程式碼壓縮包,然後在本地解壓即可。 ![克隆倉庫與下載程式碼](suggestions.assets/download_code.png) **第三步:執行源程式碼**。如下圖所示,對於頂部標有檔案名稱的程式碼塊,我們可以在倉庫的 `codes` 檔案夾內找到對應的源程式碼檔案。源程式碼檔案可一鍵執行,將幫助你節省不必要的除錯時間,讓你能夠專注於學習內容。 ![程式碼塊與對應的源程式碼檔案](suggestions.assets/code_md_to_repo.png) 除了本地執行程式碼,**網頁版還支持 Python 程式碼的視覺化執行**(基於 [pythontutor](https://pythontutor.com/) 實現)。如下圖所示,你可以點選程式碼塊下方的“視覺化執行”來展開檢視,觀察演算法程式碼的執行過程;也可以點選“全屏觀看”,以獲得更好的閱覽體驗。 ![Python 程式碼的視覺化執行](suggestions.assets/pythontutor_example.png) ## 在提問討論中共同成長 在閱讀本書時,請不要輕易跳過那些沒學明白的知識點。**歡迎在評論區提出你的問題**,我和小夥伴們將竭誠為你解答,一般情況下可在兩天內回覆。 如下圖所示,網頁版每個章節的底部都配有評論區。希望你能多關注評論區的內容。一方面,你可以瞭解大家遇到的問題,從而查漏補缺,激發更深入的思考。另一方面,期待你能慷慨地回答其他小夥伴的問題,分享你的見解,幫助他人進步。 ![評論區示例](../index.assets/comment.gif) ## 演算法學習路線 從總體上看,我們可以將學習資料結構與演算法的過程劃分為三個階段。 1. **階段一:演算法入門**。我們需要熟悉各種資料結構的特點和用法,學習不同演算法的原理、流程、用途和效率等方面的內容。 2. **階段二:刷演算法題**。建議從熱門題目開刷,先積累至少 100 道題目,熟悉主流的演算法問題。初次刷題時,“知識遺忘”可能是一個挑戰,但請放心,這是很正常的。我們可以按照“艾賓浩斯遺忘曲線”來複習題目,通常在進行 3~5 輪的重複後,就能將其牢記在心。推薦的題單和刷題計劃請見此 [GitHub 倉庫](https://github.com/krahets/LeetCode-Book)。 3. **階段三:搭建知識體系**。在學習方面,我們可以閱讀演算法專欄文章、解題框架和演算法教材,以不斷豐富知識體系。在刷題方面,可以嘗試採用進階刷題策略,如按專題分類、一題多解、一解多題等,相關的刷題心得可以在各個社群找到。 如下圖所示,本書內容主要涵蓋“階段一”,旨在幫助你更高效地展開階段二和階段三的學習。 ![演算法學習路線](suggestions.assets/learning_route.png) ================================================ FILE: zh-hant/docs/chapter_preface/summary.md ================================================ # 小結 ### 重點回顧 - 本書的主要受眾是演算法初學者。如果你已有一定基礎,本書能幫助你系統回顧演算法知識,書中源程式碼也可作為“刷題工具庫”使用。 - 書中內容主要包括複雜度分析、資料結構和演算法三部分,涵蓋了該領域的大部分主題。 - 對於演算法新手,在初學階段閱讀一本入門書至關重要,可以少走許多彎路。 - 書中的動畫圖解通常用於介紹重點和難點知識。閱讀本書時,應給予這些內容更多關注。 - 實踐乃學習程式設計之最佳途徑。強烈建議執行源程式碼並親自敲程式碼。 - 本書網頁版的每個章節都設有評論區,歡迎隨時分享你的疑惑與見解。 ================================================ FILE: zh-hant/docs/chapter_reference/index.md ================================================ --- icon: material/bookshelf --- # 參考文獻 [1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition). [2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition). [3] Robert Sedgewick, et al. Algorithms (4th Edition). [4] 嚴蔚敏. 資料結構(C 語言版). [5] 鄧俊輝. 資料結構(C++ 語言版,第三版). [6] 馬克 艾倫 維斯著,陳越譯. 資料結構與演算法分析:Java語言描述(第三版). [7] 程傑. 大話資料結構. [8] 王爭. 資料結構與演算法之美. [9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition). [10] Aston Zhang, et al. Dive into Deep Learning. ================================================ FILE: zh-hant/docs/chapter_searching/binary_search.md ================================================ # 二分搜尋 二分搜尋(binary search)是一種基於分治策略的高效搜尋演算法。它利用資料的有序性,每輪縮小一半搜尋範圍,直至找到目標元素或搜尋區間為空為止。 !!! question 給定一個長度為 $n$ 的陣列 `nums` ,元素按從小到大的順序排列且不重複。請查詢並返回元素 `target` 在該陣列中的索引。若陣列不包含該元素,則返回 $-1$ 。示例如下圖所示。 ![二分搜尋示例資料](binary_search.assets/binary_search_example.png) 如下圖所示,我們先初始化指標 $i = 0$ 和 $j = n - 1$ ,分別指向陣列首元素和尾元素,代表搜尋區間 $[0, n - 1]$ 。請注意,中括號表示閉區間,其包含邊界值本身。 接下來,迴圈執行以下兩步。 1. 計算中點索引 $m = \lfloor {(i + j) / 2} \rfloor$ ,其中 $\lfloor \: \rfloor$ 表示向下取整操作。 2. 判斷 `nums[m]` 和 `target` 的大小關係,分為以下三種情況。 1. 當 `nums[m] < target` 時,說明 `target` 在區間 $[m + 1, j]$ 中,因此執行 $i = m + 1$ 。 2. 當 `nums[m] > target` 時,說明 `target` 在區間 $[i, m - 1]$ 中,因此執行 $j = m - 1$ 。 3. 當 `nums[m] = target` 時,說明找到 `target` ,因此返回索引 $m$ 。 若陣列不包含目標元素,搜尋區間最終會縮小為空。此時返回 $-1$ 。 === "<1>" ![二分搜尋流程](binary_search.assets/binary_search_step1.png) === "<2>" ![binary_search_step2](binary_search.assets/binary_search_step2.png) === "<3>" ![binary_search_step3](binary_search.assets/binary_search_step3.png) === "<4>" ![binary_search_step4](binary_search.assets/binary_search_step4.png) === "<5>" ![binary_search_step5](binary_search.assets/binary_search_step5.png) === "<6>" ![binary_search_step6](binary_search.assets/binary_search_step6.png) === "<7>" ![binary_search_step7](binary_search.assets/binary_search_step7.png) 值得注意的是,由於 $i$ 和 $j$ 都是 `int` 型別,**因此 $i + j$ 可能會超出 `int` 型別的取值範圍**。為了避免大數越界,我們通常採用公式 $m = \lfloor {i + (j - i) / 2} \rfloor$ 來計算中點。 程式碼如下所示: ```src [file]{binary_search}-[class]{}-[func]{binary_search} ``` **時間複雜度為 $O(\log n)$** :在二分迴圈中,區間每輪縮小一半,因此迴圈次數為 $\log_2 n$ 。 **空間複雜度為 $O(1)$** :指標 $i$ 和 $j$ 使用常數大小空間。 ## 區間表示方法 除了上述雙閉區間外,常見的區間表示還有“左閉右開”區間,定義為 $[0, n)$ ,即左邊界包含自身,右邊界不包含自身。在該表示下,區間 $[i, j)$ 在 $i = j$ 時為空。 我們可以基於該表示實現具有相同功能的二分搜尋演算法: ```src [file]{binary_search}-[class]{}-[func]{binary_search_lcro} ``` 如下圖所示,在兩種區間表示下,二分搜尋演算法的初始化、迴圈條件和縮小區間操作皆有所不同。 由於“雙閉區間”表示中的左右邊界都被定義為閉區間,因此透過指標 $i$ 和指標 $j$ 縮小區間的操作也是對稱的。這樣更不容易出錯,**因此一般建議採用“雙閉區間”的寫法**。 ![兩種區間定義](binary_search.assets/binary_search_ranges.png) ## 優點與侷限性 二分搜尋在時間和空間方面都有較好的效能。 - 二分搜尋的時間效率高。在大資料量下,對數階的時間複雜度具有顯著優勢。例如,當資料大小 $n = 2^{20}$ 時,線性查詢需要 $2^{20} = 1048576$ 輪迴圈,而二分搜尋僅需 $\log_2 2^{20} = 20$ 輪迴圈。 - 二分搜尋無須額外空間。相較於需要藉助額外空間的搜尋演算法(例如雜湊查詢),二分搜尋更加節省空間。 然而,二分搜尋並非適用於所有情況,主要有以下原因。 - 二分搜尋僅適用於有序資料。若輸入資料無序,為了使用二分搜尋而專門進行排序,得不償失。因為排序演算法的時間複雜度通常為 $O(n \log n)$ ,比線性查詢和二分搜尋都更高。對於頻繁插入元素的場景,為保持陣列有序性,需要將元素插入到特定位置,時間複雜度為 $O(n)$ ,也是非常昂貴的。 - 二分搜尋僅適用於陣列。二分搜尋需要跳躍式(非連續地)訪問元素,而在鏈結串列中執行跳躍式訪問的效率較低,因此不適合應用在鏈結串列或基於鏈結串列實現的資料結構。 - 小資料量下,線性查詢效能更佳。在線性查詢中,每輪只需 1 次判斷操作;而在二分搜尋中,需要 1 次加法、1 次除法、1 ~ 3 次判斷操作、1 次加法(減法),共 4 ~ 6 個單元操作;因此,當資料量 $n$ 較小時,線性查詢反而比二分搜尋更快。 ================================================ FILE: zh-hant/docs/chapter_searching/binary_search_edge.md ================================================ # 二分搜尋邊界 ## 查詢左邊界 !!! question 給定一個長度為 $n$ 的有序陣列 `nums` ,其中可能包含重複元素。請返回陣列中最左一個元素 `target` 的索引。若陣列中不包含該元素,則返回 $-1$ 。 回憶二分搜尋插入點的方法,搜尋完成後 $i$ 指向最左一個 `target` ,**因此查詢插入點本質上是在查詢最左一個 `target` 的索引**。 考慮透過查詢插入點的函式實現查詢左邊界。請注意,陣列中可能不包含 `target` ,這種情況可能導致以下兩種結果。 - 插入點的索引 $i$ 越界。 - 元素 `nums[i]` 與 `target` 不相等。 當遇到以上兩種情況時,直接返回 $-1$ 即可。程式碼如下所示: ```src [file]{binary_search_edge}-[class]{}-[func]{binary_search_left_edge} ``` ## 查詢右邊界 那麼如何查詢最右一個 `target` 呢?最直接的方式是修改程式碼,替換在 `nums[m] == target` 情況下的指標收縮操作。程式碼在此省略,有興趣的讀者可以自行實現。 下面我們介紹兩種更加取巧的方法。 ### 複用查詢左邊界 實際上,我們可以利用查詢最左元素的函式來查詢最右元素,具體方法為:**將查詢最右一個 `target` 轉化為查詢最左一個 `target + 1`**。 如下圖所示,查詢完成後,指標 $i$ 指向最左一個 `target + 1`(如果存在),而 $j$ 指向最右一個 `target` ,**因此返回 $j$ 即可**。 ![將查詢右邊界轉化為查詢左邊界](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png) 請注意,返回的插入點是 $i$ ,因此需要將其減 $1$ ,從而獲得 $j$ : ```src [file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge} ``` ### 轉化為查詢元素 我們知道,當陣列不包含 `target` 時,最終 $i$ 和 $j$ 會分別指向首個大於、小於 `target` 的元素。 因此,如下圖所示,我們可以構造一個陣列中不存在的元素,用於查詢左右邊界。 - 查詢最左一個 `target` :可以轉化為查詢 `target - 0.5` ,並返回指標 $i$ 。 - 查詢最右一個 `target` :可以轉化為查詢 `target + 0.5` ,並返回指標 $j$ 。 ![將查詢邊界轉化為查詢元素](binary_search_edge.assets/binary_search_edge_by_element.png) 程式碼在此省略,以下兩點值得注意。 - 給定陣列不包含小數,這意味著我們無須關心如何處理相等的情況。 - 因為該方法引入了小數,所以需要將函式中的變數 `target` 改為浮點數型別(Python 無須改動)。 ================================================ FILE: zh-hant/docs/chapter_searching/binary_search_insertion.md ================================================ # 二分搜尋插入點 二分搜尋不僅可用於搜尋目標元素,還可用於解決許多變種問題,比如搜尋目標元素的插入位置。 ## 無重複元素的情況 !!! question 給定一個長度為 $n$ 的有序陣列 `nums` 和一個元素 `target` ,陣列不存在重複元素。現將 `target` 插入陣列 `nums` 中,並保持其有序性。若陣列中已存在元素 `target` ,則插入到其左方。請返回插入後 `target` 在陣列中的索引。示例如下圖所示。 ![二分搜尋插入點示例資料](binary_search_insertion.assets/binary_search_insertion_example.png) 如果想複用上一節的二分搜尋程式碼,則需要回答以下兩個問題。 **問題一**:當陣列中包含 `target` 時,插入點的索引是否是該元素的索引? 題目要求將 `target` 插入到相等元素的左邊,這意味著新插入的 `target` 替換了原來 `target` 的位置。也就是說,**當陣列包含 `target` 時,插入點的索引就是該 `target` 的索引**。 **問題二**:當陣列中不存在 `target` 時,插入點是哪個元素的索引? 進一步思考二分搜尋過程:當 `nums[m] < target` 時 $i$ 移動,這意味著指標 $i$ 在向大於等於 `target` 的元素靠近。同理,指標 $j$ 始終在向小於等於 `target` 的元素靠近。 因此二分結束時一定有:$i$ 指向首個大於 `target` 的元素,$j$ 指向首個小於 `target` 的元素。**易得當陣列不包含 `target` 時,插入索引為 $i$** 。程式碼如下所示: ```src [file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple} ``` ## 存在重複元素的情況 !!! question 在上一題的基礎上,規定陣列可能包含重複元素,其餘不變。 假設陣列中存在多個 `target` ,則普通二分搜尋只能返回其中一個 `target` 的索引,**而無法確定該元素的左邊和右邊還有多少 `target`**。 題目要求將目標元素插入到最左邊,**所以我們需要查詢陣列中最左一個 `target` 的索引**。初步考慮透過下圖所示的步驟實現。 1. 執行二分搜尋,得到任意一個 `target` 的索引,記為 $k$ 。 2. 從索引 $k$ 開始,向左進行線性走訪,當找到最左邊的 `target` 時返回。 ![線性查詢重複元素的插入點](binary_search_insertion.assets/binary_search_insertion_naive.png) 此方法雖然可用,但其包含線性查詢,因此時間複雜度為 $O(n)$ 。當陣列中存在很多重複的 `target` 時,該方法效率很低。 現考慮拓展二分搜尋程式碼。如下圖所示,整體流程保持不變,每輪先計算中點索引 $m$ ,再判斷 `target` 和 `nums[m]` 的大小關係,分為以下幾種情況。 - 當 `nums[m] < target` 或 `nums[m] > target` 時,說明還沒有找到 `target` ,因此採用普通二分搜尋的縮小區間操作,**從而使指標 $i$ 和 $j$ 向 `target` 靠近**。 - 當 `nums[m] == target` 時,說明小於 `target` 的元素在區間 $[i, m - 1]$ 中,因此採用 $j = m - 1$ 來縮小區間,**從而使指標 $j$ 向小於 `target` 的元素靠近**。 迴圈完成後,$i$ 指向最左邊的 `target` ,$j$ 指向首個小於 `target` 的元素,**因此索引 $i$ 就是插入點**。 === "<1>" ![二分搜尋重複元素的插入點的步驟](binary_search_insertion.assets/binary_search_insertion_step1.png) === "<2>" ![binary_search_insertion_step2](binary_search_insertion.assets/binary_search_insertion_step2.png) === "<3>" ![binary_search_insertion_step3](binary_search_insertion.assets/binary_search_insertion_step3.png) === "<4>" ![binary_search_insertion_step4](binary_search_insertion.assets/binary_search_insertion_step4.png) === "<5>" ![binary_search_insertion_step5](binary_search_insertion.assets/binary_search_insertion_step5.png) === "<6>" ![binary_search_insertion_step6](binary_search_insertion.assets/binary_search_insertion_step6.png) === "<7>" ![binary_search_insertion_step7](binary_search_insertion.assets/binary_search_insertion_step7.png) === "<8>" ![binary_search_insertion_step8](binary_search_insertion.assets/binary_search_insertion_step8.png) 觀察以下程式碼,判斷分支 `nums[m] > target` 和 `nums[m] == target` 的操作相同,因此兩者可以合併。 即便如此,我們仍然可以將判斷條件保持展開,因為其邏輯更加清晰、可讀性更好。 ```src [file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion} ``` !!! tip 本節的程式碼都是“雙閉區間”寫法。有興趣的讀者可以自行實現“左閉右開”寫法。 總的來看,二分搜尋無非就是給指標 $i$ 和 $j$ 分別設定搜尋目標,目標可能是一個具體的元素(例如 `target` ),也可能是一個元素範圍(例如小於 `target` 的元素)。 在不斷的迴圈二分中,指標 $i$ 和 $j$ 都逐漸逼近預先設定的目標。最終,它們或是成功找到答案,或是越過邊界後停止。 ================================================ FILE: zh-hant/docs/chapter_searching/index.md ================================================ # 搜尋 ![搜尋](../assets/covers/chapter_searching.jpg) !!! abstract 搜尋是一場未知的冒險,我們或許需要走遍神秘空間的每個角落,又或許可以快速鎖定目標。 在這場尋覓之旅中,每一次探索都可能得到一個未曾料想的答案。 ================================================ FILE: zh-hant/docs/chapter_searching/replace_linear_by_hashing.md ================================================ # 雜湊最佳化策略 在演算法題中,**我們常透過將線性查詢替換為雜湊查詢來降低演算法的時間複雜度**。我們藉助一個演算法題來加深理解。 !!! question 給定一個整數陣列 `nums` 和一個目標元素 `target` ,請在陣列中搜索“和”為 `target` 的兩個元素,並返回它們的陣列索引。返回任意一個解即可。 ## 線性查詢:以時間換空間 考慮直接走訪所有可能的組合。如下圖所示,我們開啟一個兩層迴圈,在每輪中判斷兩個整數的和是否為 `target` ,若是,則返回它們的索引。 ![線性查詢求解兩數之和](replace_linear_by_hashing.assets/two_sum_brute_force.png) 程式碼如下所示: ```src [file]{two_sum}-[class]{}-[func]{two_sum_brute_force} ``` 此方法的時間複雜度為 $O(n^2)$ ,空間複雜度為 $O(1)$ ,在大資料量下非常耗時。 ## 雜湊查詢:以空間換時間 考慮藉助一個雜湊表,鍵值對分別為陣列元素和元素索引。迴圈走訪陣列,每輪執行下圖所示的步驟。 1. 判斷數字 `target - nums[i]` 是否在雜湊表中,若是,則直接返回這兩個元素的索引。 2. 將鍵值對 `nums[i]` 和索引 `i` 新增進雜湊表。 === "<1>" ![輔助雜湊表求解兩數之和](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) === "<2>" ![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png) === "<3>" ![two_sum_hashtable_step3](replace_linear_by_hashing.assets/two_sum_hashtable_step3.png) 實現程式碼如下所示,僅需單層迴圈即可: ```src [file]{two_sum}-[class]{}-[func]{two_sum_hash_table} ``` 此方法透過雜湊查詢將時間複雜度從 $O(n^2)$ 降至 $O(n)$ ,大幅提升執行效率。 由於需要維護一個額外的雜湊表,因此空間複雜度為 $O(n)$ 。**儘管如此,該方法的整體時空效率更為均衡,因此它是本題的最優解法**。 ================================================ FILE: zh-hant/docs/chapter_searching/searching_algorithm_revisited.md ================================================ # 重識搜尋演算法 搜尋演算法(searching algorithm)用於在資料結構(例如陣列、鏈結串列、樹或圖)中搜索一個或一組滿足特定條件的元素。 搜尋演算法可根據實現思路分為以下兩類。 - **透過走訪資料結構來定位目標元素**,例如陣列、鏈結串列、樹和圖的走訪等。 - **利用資料組織結構或資料包含的先驗資訊,實現高效元素查詢**,例如二分搜尋、雜湊查詢和二元搜尋樹查詢等。 不難發現,這些知識點都已在前面的章節中介紹過,因此搜尋演算法對於我們來說並不陌生。在本節中,我們將從更加系統的視角切入,重新審視搜尋演算法。 ## 暴力搜尋 暴力搜尋透過走訪資料結構的每個元素來定位目標元素。 - “線性搜尋”適用於陣列和鏈結串列等線性資料結構。它從資料結構的一端開始,逐個訪問元素,直到找到目標元素或到達另一端仍沒有找到目標元素為止。 - “廣度優先搜尋”和“深度優先搜尋”是圖和樹的兩種走訪策略。廣度優先搜尋從初始節點開始逐層搜尋,由近及遠地訪問各個節點。深度優先搜尋從初始節點開始,沿著一條路徑走到頭,再回溯並嘗試其他路徑,直到走訪完整個資料結構。 暴力搜尋的優點是簡單且通用性好,**無須對資料做預處理和藉助額外的資料結構**。 然而,**此類演算法的時間複雜度為 $O(n)$** ,其中 $n$ 為元素數量,因此在資料量較大的情況下效能較差。 ## 自適應搜尋 自適應搜尋利用資料的特有屬性(例如有序性)來最佳化搜尋過程,從而更高效地定位目標元素。 - “二分搜尋”利用資料的有序性實現高效查詢,僅適用於陣列。 - “雜湊查詢”利用雜湊表將搜尋資料和目標資料建立為鍵值對對映,從而實現查詢操作。 - “樹查詢”在特定的樹結構(例如二元搜尋樹)中,基於比較節點值來快速排除節點,從而定位目標元素。 此類演算法的優點是效率高,**時間複雜度可達到 $O(\log n)$ 甚至 $O(1)$** 。 然而,**使用這些演算法往往需要對資料進行預處理**。例如,二分搜尋需要預先對陣列進行排序,雜湊查詢和樹查詢都需要藉助額外的資料結構,維護這些資料結構也需要額外的時間和空間開銷。 !!! tip 自適應搜尋演算法常被稱為查詢演算法,**主要用於在特定資料結構中快速檢索目標元素**。 ## 搜尋方法選取 給定大小為 $n$ 的一組資料,我們可以使用線性搜尋、二分搜尋、樹查詢、雜湊查詢等多種方法從中搜索目標元素。各個方法的工作原理如下圖所示。 ![多種搜尋策略](searching_algorithm_revisited.assets/searching_algorithms.png) 上述幾種方法的操作效率與特性如下表所示。

  查詢演算法效率對比

| | 線性搜尋 | 二分搜尋 | 樹查詢 | 雜湊查詢 | | ------------ | -------- | ------------------ | ------------------ | --------------- | | 查詢元素 | $O(n)$ | $O(\log n)$ | $O(\log n)$ | $O(1)$ | | 插入元素 | $O(1)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | | 刪除元素 | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | | 額外空間 | $O(1)$ | $O(1)$ | $O(n)$ | $O(n)$ | | 資料預處理 | / | 排序 $O(n \log n)$ | 建樹 $O(n \log n)$ | 建雜湊表 $O(n)$ | | 資料是否有序 | 無序 | 有序 | 有序 | 無序 | 搜尋演算法的選擇還取決規模、搜尋效能要求、資料查詢與更新頻率等。 **線性搜尋** - 通用性較好,無須任何資料預處理操作。假如我們僅需查詢一次資料,那麼其他三種方法的資料預處理的時間比線性搜尋的時間還要更長。 - 適用於體量較小的資料,此情況下時間複雜度對效率影響較小。 - 適用於資料更新頻率較高的場景,因為該方法不需要對資料進行任何額外維護。 **二分搜尋** - 適用於大資料量的情況,效率表現穩定,最差時間複雜度為 $O(\log n)$ 。 - 資料量不能過大,因為儲存陣列需要連續的記憶體空間。 - 不適用於高頻增刪資料的場景,因為維護有序陣列的開銷較大。 **雜湊查詢** - 適合對查詢效能要求很高的場景,平均時間複雜度為 $O(1)$ 。 - 不適合需要有序資料或範圍查詢的場景,因為雜湊表無法維護資料的有序性。 - 對雜湊函式和雜湊衝突處理策略的依賴性較高,具有較大的效能劣化風險。 - 不適合資料量過大的情況,因為雜湊表需要額外空間來最大程度地減少衝突,從而提供良好的查詢效能。 **樹查詢** - 適用於海量資料,因為樹節點在記憶體中是分散儲存的。 - 適合需要維護有序資料或範圍查詢的場景。 - 在持續增刪節點的過程中,二元搜尋樹可能產生傾斜,時間複雜度劣化至 $O(n)$ 。 - 若使用 AVL 樹或紅黑樹,則各項操作可在 $O(\log n)$ 效率下穩定執行,但維護樹平衡的操作會增加額外的開銷。 ================================================ FILE: zh-hant/docs/chapter_searching/summary.md ================================================ # 小結 ### 重點回顧 - 二分搜尋依賴資料的有序性,透過迴圈逐步縮減一半搜尋區間來進行查詢。它要求輸入資料有序,且僅適用於陣列或基於陣列實現的資料結構。 - 暴力搜尋透過走訪資料結構來定位資料。線性搜尋適用於陣列和鏈結串列,廣度優先搜尋和深度優先搜尋適用於圖和樹。此類演算法通用性好,無須對資料進行預處理,但時間複雜度 $O(n)$ 較高。 - 雜湊查詢、樹查詢和二分搜尋屬於高效搜尋方法,可在特定資料結構中快速定位目標元素。此類演算法效率高,時間複雜度可達 $O(\log n)$ 甚至 $O(1)$ ,但通常需要藉助額外資料結構。 - 實際中,我們需要對資料規模、搜尋效能要求、資料查詢和更新頻率等因素進行具體分析,從而選擇合適的搜尋方法。 - 線性搜尋適用於小型或頻繁更新的資料;二分搜尋適用於大型、排序的資料;雜湊查詢適用於對查詢效率要求較高且無須範圍查詢的資料;樹查詢適用於需要維護順序和支持範圍查詢的大型動態資料。 - 用雜湊查詢替換線性查詢是一種常用的最佳化執行時間的策略,可將時間複雜度從 $O(n)$ 降至 $O(1)$ 。 ================================================ FILE: zh-hant/docs/chapter_sorting/bubble_sort.md ================================================ # 泡沫排序 泡沫排序(bubble sort)透過連續地比較與交換相鄰元素實現排序。這個過程就像氣泡從底部升到頂部一樣,因此得名泡沫排序。 如下圖所示,冒泡過程可以利用元素交換操作來模擬:從陣列最左端開始向右走訪,依次比較相鄰元素大小,如果“左元素 > 右元素”就交換二者。走訪完成後,最大的元素會被移動到陣列的最右端。 === "<1>" ![利用元素交換操作模擬冒泡](bubble_sort.assets/bubble_operation_step1.png) === "<2>" ![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png) === "<3>" ![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png) === "<4>" ![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png) === "<5>" ![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png) === "<6>" ![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png) === "<7>" ![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png) ## 演算法流程 設陣列的長度為 $n$ ,泡沫排序的步驟如下圖所示。 1. 首先,對 $n$ 個元素執行“冒泡”,**將陣列的最大元素交換至正確位置**。 2. 接下來,對剩餘 $n - 1$ 個元素執行“冒泡”,**將第二大元素交換至正確位置**。 3. 以此類推,經過 $n - 1$ 輪“冒泡”後,**前 $n - 1$ 大的元素都被交換至正確位置**。 4. 僅剩的一個元素必定是最小元素,無須排序,因此陣列排序完成。 ![泡沫排序流程](bubble_sort.assets/bubble_sort_overview.png) 示例程式碼如下: ```src [file]{bubble_sort}-[class]{}-[func]{bubble_sort} ``` ## 效率最佳化 我們發現,如果某輪“冒泡”中沒有執行任何交換操作,說明陣列已經完成排序,可直接返回結果。因此,可以增加一個標誌位 `flag` 來監測這種情況,一旦出現就立即返回。 經過最佳化,泡沫排序的最差時間複雜度和平均時間複雜度仍為 $O(n^2)$ ;但當輸入陣列完全有序時,可達到最佳時間複雜度 $O(n)$ 。 ```src [file]{bubble_sort}-[class]{}-[func]{bubble_sort_with_flag} ``` ## 演算法特性 - **時間複雜度為 $O(n^2)$、自適應排序**:各輪“冒泡”走訪的陣列長度依次為 $n - 1$、$n - 2$、$\dots$、$2$、$1$ ,總和為 $(n - 1) n / 2$ 。在引入 `flag` 最佳化後,最佳時間複雜度可達到 $O(n)$ 。 - **空間複雜度為 $O(1)$、原地排序**:指標 $i$ 和 $j$ 使用常數大小的額外空間。 - **穩定排序**:由於在“冒泡”中遇到相等元素不交換。 ================================================ FILE: zh-hant/docs/chapter_sorting/bucket_sort.md ================================================ # 桶排序 前述幾種排序演算法都屬於“基於比較的排序演算法”,它們透過比較元素間的大小來實現排序。此類排序演算法的時間複雜度無法超越 $O(n \log n)$ 。接下來,我們將探討幾種“非比較排序演算法”,它們的時間複雜度可以達到線性階。 桶排序(bucket sort)是分治策略的一個典型應用。它透過設定一些具有大小順序的桶,每個桶對應一個數據範圍,將資料平均分配到各個桶中;然後,在每個桶內部分別執行排序;最終按照桶的順序將所有資料合併。 ## 演算法流程 考慮一個長度為 $n$ 的陣列,其元素是範圍 $[0, 1)$ 內的浮點數。桶排序的流程如下圖所示。 1. 初始化 $k$ 個桶,將 $n$ 個元素分配到 $k$ 個桶中。 2. 對每個桶分別執行排序(這裡採用程式語言的內建排序函式)。 3. 按照桶從小到大的順序合併結果。 ![桶排序演算法流程](bucket_sort.assets/bucket_sort_overview.png) 程式碼如下所示: ```src [file]{bucket_sort}-[class]{}-[func]{bucket_sort} ``` ## 演算法特性 桶排序適用於處理體量很大的資料。例如,輸入資料包含 100 萬個元素,由於空間限制,系統記憶體無法一次性載入所有資料。此時,可以將資料分成 1000 個桶,然後分別對每個桶進行排序,最後將結果合併。 - **時間複雜度為 $O(n + k)$** :假設元素在各個桶內平均分佈,那麼每個桶內的元素數量為 $\frac{n}{k}$ 。假設排序單個桶使用 $O(\frac{n}{k} \log\frac{n}{k})$ 時間,則排序所有桶使用 $O(n \log\frac{n}{k})$ 時間。**當桶數量 $k$ 比較大時,時間複雜度則趨向於 $O(n)$** 。合併結果時需要走訪所有桶和元素,花費 $O(n + k)$ 時間。在最差情況下,所有資料被分配到一個桶中,且排序該桶使用 $O(n^2)$ 時間。 - **空間複雜度為 $O(n + k)$、非原地排序**:需要藉助 $k$ 個桶和總共 $n$ 個元素的額外空間。 - 桶排序是否穩定取決於排序桶內元素的演算法是否穩定。 ## 如何實現平均分配 桶排序的時間複雜度理論上可以達到 $O(n)$ ,**關鍵在於將元素均勻分配到各個桶中**,因為實際資料往往不是均勻分佈的。例如,我們想要將淘寶上的所有商品按價格範圍平均分配到 10 個桶中,但商品價格分佈不均,低於 100 元的非常多,高於 1000 元的非常少。若將價格區間平均劃分為 10 個,各個桶中的商品數量差距會非常大。 為實現平均分配,我們可以先設定一條大致的分界線,將資料粗略地分到 3 個桶中。**分配完畢後,再將商品較多的桶繼續劃分為 3 個桶,直至所有桶中的元素數量大致相等**。 如下圖所示,這種方法本質上是建立一棵遞迴樹,目標是讓葉節點的值儘可能平均。當然,不一定要每輪將資料劃分為 3 個桶,具體劃分方式可根據資料特點靈活選擇。 ![遞迴劃分桶](bucket_sort.assets/scatter_in_buckets_recursively.png) 如果我們提前知道商品價格的機率分佈,**則可以根據資料機率分佈設定每個桶的價格分界線**。值得注意的是,資料分佈並不一定需要特意統計,也可以根據資料特點採用某種機率模型進行近似。 如下圖所示,我們假設商品價格服從正態分佈,這樣就可以合理地設定價格區間,從而將商品平均分配到各個桶中。 ![根據機率分佈劃分桶](bucket_sort.assets/scatter_in_buckets_distribution.png) ================================================ FILE: zh-hant/docs/chapter_sorting/counting_sort.md ================================================ # 計數排序 計數排序(counting sort)透過統計元素數量來實現排序,通常應用於整數陣列。 ## 簡單實現 先來看一個簡單的例子。給定一個長度為 $n$ 的陣列 `nums` ,其中的元素都是“非負整數”,計數排序的整體流程如下圖所示。 1. 走訪陣列,找出其中的最大數字,記為 $m$ ,然後建立一個長度為 $m + 1$ 的輔助陣列 `counter` 。 2. **藉助 `counter` 統計 `nums` 中各數字的出現次數**,其中 `counter[num]` 對應數字 `num` 的出現次數。統計方法很簡單,只需走訪 `nums`(設當前數字為 `num`),每輪將 `counter[num]` 增加 $1$ 即可。 3. **由於 `counter` 的各個索引天然有序,因此相當於所有數字已經排序好了**。接下來,我們走訪 `counter` ,根據各數字出現次數從小到大的順序填入 `nums` 即可。 ![計數排序流程](counting_sort.assets/counting_sort_overview.png) 程式碼如下所示: ```src [file]{counting_sort}-[class]{}-[func]{counting_sort_naive} ``` !!! note "計數排序與桶排序的關聯" 從桶排序的角度看,我們可以將計數排序中的計數陣列 `counter` 的每個索引視為一個桶,將統計數量的過程看作將各個元素分配到對應的桶中。本質上,計數排序是桶排序在整型資料下的一個特例。 ## 完整實現 細心的讀者可能發現了,**如果輸入資料是物件,上述步驟 `3.` 就失效了**。假設輸入資料是商品物件,我們想按照商品價格(類別的成員變數)對商品進行排序,而上述演算法只能給出價格的排序結果。 那麼如何才能得到原資料的排序結果呢?我們首先計算 `counter` 的“前綴和”。顧名思義,索引 `i` 處的前綴和 `prefix[i]` 等於陣列前 `i` 個元素之和: $$ \text{prefix}[i] = \sum_{j=0}^i \text{counter[j]} $$ **前綴和具有明確的意義,`prefix[num] - 1` 代表元素 `num` 在結果陣列 `res` 中最後一次出現的索引**。這個資訊非常關鍵,因為它告訴我們各個元素應該出現在結果陣列的哪個位置。接下來,我們倒序走訪原陣列 `nums` 的每個元素 `num` ,在每輪迭代中執行以下兩步。 1. 將 `num` 填入陣列 `res` 的索引 `prefix[num] - 1` 處。 2. 令前綴和 `prefix[num]` 減小 $1$ ,從而得到下次放置 `num` 的索引。 走訪完成後,陣列 `res` 中就是排序好的結果,最後使用 `res` 覆蓋原陣列 `nums` 即可。下圖展示了完整的計數排序流程。 === "<1>" ![計數排序步驟](counting_sort.assets/counting_sort_step1.png) === "<2>" ![counting_sort_step2](counting_sort.assets/counting_sort_step2.png) === "<3>" ![counting_sort_step3](counting_sort.assets/counting_sort_step3.png) === "<4>" ![counting_sort_step4](counting_sort.assets/counting_sort_step4.png) === "<5>" ![counting_sort_step5](counting_sort.assets/counting_sort_step5.png) === "<6>" ![counting_sort_step6](counting_sort.assets/counting_sort_step6.png) === "<7>" ![counting_sort_step7](counting_sort.assets/counting_sort_step7.png) === "<8>" ![counting_sort_step8](counting_sort.assets/counting_sort_step8.png) 計數排序的實現程式碼如下所示: ```src [file]{counting_sort}-[class]{}-[func]{counting_sort} ``` ## 演算法特性 - **時間複雜度為 $O(n + m)$、非自適應排序** :涉及走訪 `nums` 和走訪 `counter` ,都使用線性時間。一般情況下 $n \gg m$ ,時間複雜度趨於 $O(n)$ 。 - **空間複雜度為 $O(n + m)$、非原地排序**:藉助了長度分別為 $n$ 和 $m$ 的陣列 `res` 和 `counter` 。 - **穩定排序**:由於向 `res` 中填充元素的順序是“從右向左”的,因此倒序走訪 `nums` 可以避免改變相等元素之間的相對位置,從而實現穩定排序。實際上,正序走訪 `nums` 也可以得到正確的排序結果,但結果是非穩定的。 ## 侷限性 看到這裡,你也許會覺得計數排序非常巧妙,僅透過統計數量就可以實現高效的排序。然而,使用計數排序的前置條件相對較為嚴格。 **計數排序只適用於非負整數**。若想將其用於其他型別的資料,需要確保這些資料可以轉換為非負整數,並且在轉換過程中不能改變各個元素之間的相對大小關係。例如,對於包含負數的整數陣列,可以先給所有數字加上一個常數,將全部數字轉化為正數,排序完成後再轉換回去。 **計數排序適用於資料量大但資料範圍較小的情況**。比如,在上述示例中 $m$ 不能太大,否則會佔用過多空間。而當 $n \ll m$ 時,計數排序使用 $O(m)$ 時間,可能比 $O(n \log n)$ 的排序演算法還要慢。 ================================================ FILE: zh-hant/docs/chapter_sorting/heap_sort.md ================================================ # 堆積排序 !!! tip 閱讀本節前,請確保已學完“堆積”章節。 堆積排序(heap sort)是一種基於堆積資料結構實現的高效排序演算法。我們可以利用已經學過的“建堆積操作”和“元素出堆積操作”實現堆積排序。 1. 輸入陣列並建立小頂堆積,此時最小元素位於堆積頂。 2. 不斷執行出堆積操作,依次記錄出堆積元素,即可得到從小到大排序的序列。 以上方法雖然可行,但需要藉助一個額外陣列來儲存彈出的元素,比較浪費空間。在實際中,我們通常使用一種更加優雅的實現方式。 ## 演算法流程 設陣列的長度為 $n$ ,堆積排序的流程如下圖所示。 1. 輸入陣列並建立大頂堆積。完成後,最大元素位於堆積頂。 2. 將堆積頂元素(第一個元素)與堆積底元素(最後一個元素)交換。完成交換後,堆積的長度減 $1$ ,已排序元素數量加 $1$ 。 3. 從堆積頂元素開始,從頂到底執行堆積化操作(sift down)。完成堆積化後,堆積的性質得到修復。 4. 迴圈執行第 `2.` 步和第 `3.` 步。迴圈 $n - 1$ 輪後,即可完成陣列排序。 !!! tip 實際上,元素出堆積操作中也包含第 `2.` 步和第 `3.` 步,只是多了一個彈出元素的步驟。 === "<1>" ![堆積排序步驟](heap_sort.assets/heap_sort_step1.png) === "<2>" ![heap_sort_step2](heap_sort.assets/heap_sort_step2.png) === "<3>" ![heap_sort_step3](heap_sort.assets/heap_sort_step3.png) === "<4>" ![heap_sort_step4](heap_sort.assets/heap_sort_step4.png) === "<5>" ![heap_sort_step5](heap_sort.assets/heap_sort_step5.png) === "<6>" ![heap_sort_step6](heap_sort.assets/heap_sort_step6.png) === "<7>" ![heap_sort_step7](heap_sort.assets/heap_sort_step7.png) === "<8>" ![heap_sort_step8](heap_sort.assets/heap_sort_step8.png) === "<9>" ![heap_sort_step9](heap_sort.assets/heap_sort_step9.png) === "<10>" ![heap_sort_step10](heap_sort.assets/heap_sort_step10.png) === "<11>" ![heap_sort_step11](heap_sort.assets/heap_sort_step11.png) === "<12>" ![heap_sort_step12](heap_sort.assets/heap_sort_step12.png) 在程式碼實現中,我們使用了與“堆積”章節相同的從頂至底堆積化 `sift_down()` 函式。值得注意的是,由於堆積的長度會隨著提取最大元素而減小,因此我們需要給 `sift_down()` 函式新增一個長度參數 $n$ ,用於指定堆積的當前有效長度。程式碼如下所示: ```src [file]{heap_sort}-[class]{}-[func]{heap_sort} ``` ## 演算法特性 - **時間複雜度為 $O(n \log n)$、非自適應排序**:建堆積操作使用 $O(n)$ 時間。從堆積中提取最大元素的時間複雜度為 $O(\log n)$ ,共迴圈 $n - 1$ 輪。 - **空間複雜度為 $O(1)$、原地排序**:幾個指標變數使用 $O(1)$ 空間。元素交換和堆積化操作都是在原陣列上進行的。 - **非穩定排序**:在交換堆積頂元素和堆積底元素時,相等元素的相對位置可能發生變化。 ================================================ FILE: zh-hant/docs/chapter_sorting/index.md ================================================ # 排序 ![排序](../assets/covers/chapter_sorting.jpg) !!! abstract 排序猶如一把將混亂變為秩序的魔法鑰匙,使我們能以更高效的方式理解與處理資料。 無論是簡單的升序,還是複雜的分類排列,排序都向我們展示了資料的和諧美感。 ================================================ FILE: zh-hant/docs/chapter_sorting/insertion_sort.md ================================================ # 插入排序 插入排序(insertion sort)是一種簡單的排序演算法,它的工作原理與手動整理一副牌的過程非常相似。 具體來說,我們在未排序區間選擇一個基準元素,將該元素與其左側已排序區間的元素逐一比較大小,並將該元素插入到正確的位置。 下圖展示了陣列插入元素的操作流程。設基準元素為 `base` ,我們需要將從目標索引到 `base` 之間的所有元素向右移動一位,然後將 `base` 賦值給目標索引。 ![單次插入操作](insertion_sort.assets/insertion_operation.png) ## 演算法流程 插入排序的整體流程如下圖所示。 1. 初始狀態下,陣列的第 1 個元素已完成排序。 2. 選取陣列的第 2 個元素作為 `base` ,將其插入到正確位置後,**陣列的前 2 個元素已排序**。 3. 選取第 3 個元素作為 `base` ,將其插入到正確位置後,**陣列的前 3 個元素已排序**。 4. 以此類推,在最後一輪中,選取最後一個元素作為 `base` ,將其插入到正確位置後,**所有元素均已排序**。 ![插入排序流程](insertion_sort.assets/insertion_sort_overview.png) 示例程式碼如下: ```src [file]{insertion_sort}-[class]{}-[func]{insertion_sort} ``` ## 演算法特性 - **時間複雜度為 $O(n^2)$、自適應排序**:在最差情況下,每次插入操作分別需要迴圈 $n - 1$、$n-2$、$\dots$、$2$、$1$ 次,求和得到 $(n - 1) n / 2$ ,因此時間複雜度為 $O(n^2)$ 。在遇到有序資料時,插入操作會提前終止。當輸入陣列完全有序時,插入排序達到最佳時間複雜度 $O(n)$ 。 - **空間複雜度為 $O(1)$、原地排序**:指標 $i$ 和 $j$ 使用常數大小的額外空間。 - **穩定排序**:在插入操作過程中,我們會將元素插入到相等元素的右側,不會改變它們的順序。 ## 插入排序的優勢 插入排序的時間複雜度為 $O(n^2)$ ,而我們即將學習的快速排序的時間複雜度為 $O(n \log n)$ 。儘管插入排序的時間複雜度更高,**但在資料量較小的情況下,插入排序通常更快**。 這個結論與線性查詢和二分搜尋的適用情況的結論類似。快速排序這類 $O(n \log n)$ 的演算法屬於基於分治策略的排序演算法,往往包含更多單元計算操作。而在資料量較小時,$n^2$ 和 $n \log n$ 的數值比較接近,複雜度不佔主導地位,每輪中的單元操作數量起到決定性作用。 實際上,許多程式語言(例如 Java)的內建排序函式採用了插入排序,大致思路為:對於長陣列,採用基於分治策略的排序演算法,例如快速排序;對於短陣列,直接使用插入排序。 雖然泡沫排序、選擇排序和插入排序的時間複雜度都為 $O(n^2)$ ,但在實際情況中,**插入排序的使用頻率顯著高於泡沫排序和選擇排序**,主要有以下原因。 - 泡沫排序基於元素交換實現,需要藉助一個臨時變數,共涉及 3 個單元操作;插入排序基於元素賦值實現,僅需 1 個單元操作。因此,**泡沫排序的計算開銷通常比插入排序更高**。 - 選擇排序在任何情況下的時間複雜度都為 $O(n^2)$ 。**如果給定一組部分有序的資料,插入排序通常比選擇排序效率更高**。 - 選擇排序不穩定,無法應用於多級排序。 ================================================ FILE: zh-hant/docs/chapter_sorting/merge_sort.md ================================================ # 合併排序 合併排序(merge sort)是一種基於分治策略的排序演算法,包含下圖所示的“劃分”和“合併”階段。 1. **劃分階段**:透過遞迴不斷地將陣列從中點處分開,將長陣列的排序問題轉換為短陣列的排序問題。 2. **合併階段**:當子陣列長度為 1 時終止劃分,開始合併,持續地將左右兩個較短的有序陣列合併為一個較長的有序陣列,直至結束。 ![合併排序的劃分與合併階段](merge_sort.assets/merge_sort_overview.png) ## 演算法流程 如下圖所示,“劃分階段”從頂至底遞迴地將陣列從中點切分為兩個子陣列。 1. 計算陣列中點 `mid` ,遞迴劃分左子陣列(區間 `[left, mid]` )和右子陣列(區間 `[mid + 1, right]` )。 2. 遞迴執行步驟 `1.` ,直至子陣列區間長度為 1 時終止。 “合併階段”從底至頂地將左子陣列和右子陣列合併為一個有序陣列。需要注意的是,從長度為 1 的子陣列開始合併,合併階段中的每個子陣列都是有序的。 === "<1>" ![合併排序步驟](merge_sort.assets/merge_sort_step1.png) === "<2>" ![merge_sort_step2](merge_sort.assets/merge_sort_step2.png) === "<3>" ![merge_sort_step3](merge_sort.assets/merge_sort_step3.png) === "<4>" ![merge_sort_step4](merge_sort.assets/merge_sort_step4.png) === "<5>" ![merge_sort_step5](merge_sort.assets/merge_sort_step5.png) === "<6>" ![merge_sort_step6](merge_sort.assets/merge_sort_step6.png) === "<7>" ![merge_sort_step7](merge_sort.assets/merge_sort_step7.png) === "<8>" ![merge_sort_step8](merge_sort.assets/merge_sort_step8.png) === "<9>" ![merge_sort_step9](merge_sort.assets/merge_sort_step9.png) === "<10>" ![merge_sort_step10](merge_sort.assets/merge_sort_step10.png) 觀察發現,合併排序與二元樹後序走訪的遞迴順序是一致的。 - **後序走訪**:先遞迴左子樹,再遞迴右子樹,最後處理根節點。 - **合併排序**:先遞迴左子陣列,再遞迴右子陣列,最後處理合併。 合併排序的實現如以下程式碼所示。請注意,`nums` 的待合併區間為 `[left, right]` ,而 `tmp` 的對應區間為 `[0, right - left]` 。 ```src [file]{merge_sort}-[class]{}-[func]{merge_sort} ``` ## 演算法特性 - **時間複雜度為 $O(n \log n)$、非自適應排序**:劃分產生高度為 $\log n$ 的遞迴樹,每層合併的總操作數量為 $n$ ,因此總體時間複雜度為 $O(n \log n)$ 。 - **空間複雜度為 $O(n)$、非原地排序**:遞迴深度為 $\log n$ ,使用 $O(\log n)$ 大小的堆疊幀空間。合併操作需要藉助輔助陣列實現,使用 $O(n)$ 大小的額外空間。 - **穩定排序**:在合併過程中,相等元素的次序保持不變。 ## 鏈結串列排序 對於鏈結串列,合併排序相較於其他排序演算法具有顯著優勢,**可以將鏈結串列排序任務的空間複雜度最佳化至 $O(1)$** 。 - **劃分階段**:可以使用“迭代”替代“遞迴”來實現鏈結串列劃分工作,從而省去遞迴使用的堆疊幀空間。 - **合併階段**:在鏈結串列中,節點增刪操作僅需改變引用(指標)即可實現,因此合併階段(將兩個短有序鏈結串列合併為一個長有序鏈結串列)無須建立額外鏈結串列。 具體實現細節比較複雜,有興趣的讀者可以查閱相關資料進行學習。 ================================================ FILE: zh-hant/docs/chapter_sorting/quick_sort.md ================================================ # 快速排序 快速排序(quick sort)是一種基於分治策略的排序演算法,執行高效,應用廣泛。 快速排序的核心操作是“哨兵劃分”,其目標是:選擇陣列中的某個元素作為“基準數”,將所有小於基準數的元素移到其左側,而大於基準數的元素移到其右側。具體來說,哨兵劃分的流程如下圖所示。 1. 選取陣列最左端元素作為基準數,初始化兩個指標 `i` 和 `j` 分別指向陣列的兩端。 2. 設定一個迴圈,在每輪中使用 `i`(`j`)分別尋找第一個比基準數大(小)的元素,然後交換這兩個元素。 3. 迴圈執行步驟 `2.` ,直到 `i` 和 `j` 相遇時停止,最後將基準數交換至兩個子陣列的分界線。 === "<1>" ![哨兵劃分步驟](quick_sort.assets/pivot_division_step1.png) === "<2>" ![pivot_division_step2](quick_sort.assets/pivot_division_step2.png) === "<3>" ![pivot_division_step3](quick_sort.assets/pivot_division_step3.png) === "<4>" ![pivot_division_step4](quick_sort.assets/pivot_division_step4.png) === "<5>" ![pivot_division_step5](quick_sort.assets/pivot_division_step5.png) === "<6>" ![pivot_division_step6](quick_sort.assets/pivot_division_step6.png) === "<7>" ![pivot_division_step7](quick_sort.assets/pivot_division_step7.png) === "<8>" ![pivot_division_step8](quick_sort.assets/pivot_division_step8.png) === "<9>" ![pivot_division_step9](quick_sort.assets/pivot_division_step9.png) 哨兵劃分完成後,原陣列被劃分成三部分:左子陣列、基準數、右子陣列,且滿足“左子陣列任意元素 $\leq$ 基準數 $\leq$ 右子陣列任意元素”。因此,我們接下來只需對這兩個子陣列進行排序。 !!! note "快速排序的分治策略" 哨兵劃分的實質是將一個較長陣列的排序問題簡化為兩個較短陣列的排序問題。 ```src [file]{quick_sort}-[class]{quick_sort}-[func]{partition} ``` ## 演算法流程 快速排序的整體流程如下圖所示。 1. 首先,對原陣列執行一次“哨兵劃分”,得到未排序的左子陣列和右子陣列。 2. 然後,對左子陣列和右子陣列分別遞迴執行“哨兵劃分”。 3. 持續遞迴,直至子陣列長度為 1 時終止,從而完成整個陣列的排序。 ![快速排序流程](quick_sort.assets/quick_sort_overview.png) ```src [file]{quick_sort}-[class]{quick_sort}-[func]{quick_sort} ``` ## 演算法特性 - **時間複雜度為 $O(n \log n)$、非自適應排序**:在平均情況下,哨兵劃分的遞迴層數為 $\log n$ ,每層中的總迴圈數為 $n$ ,總體使用 $O(n \log n)$ 時間。在最差情況下,每輪哨兵劃分操作都將長度為 $n$ 的陣列劃分為長度為 $0$ 和 $n - 1$ 的兩個子陣列,此時遞迴層數達到 $n$ ,每層中的迴圈數為 $n$ ,總體使用 $O(n^2)$ 時間。 - **空間複雜度為 $O(n)$、原地排序**:在輸入陣列完全倒序的情況下,達到最差遞迴深度 $n$ ,使用 $O(n)$ 堆疊幀空間。排序操作是在原陣列上進行的,未藉助額外陣列。 - **非穩定排序**:在哨兵劃分的最後一步,基準數可能會被交換至相等元素的右側。 ## 快速排序為什麼快 從名稱上就能看出,快速排序在效率方面應該具有一定的優勢。儘管快速排序的平均時間複雜度與“合併排序”和“堆積排序”相同,但通常快速排序的效率更高,主要有以下原因。 - **出現最差情況的機率很低**:雖然快速排序的最差時間複雜度為 $O(n^2)$ ,沒有合併排序穩定,但在絕大多數情況下,快速排序能在 $O(n \log n)$ 的時間複雜度下執行。 - **快取使用效率高**:在執行哨兵劃分操作時,系統可將整個子陣列載入到快取,因此訪問元素的效率較高。而像“堆積排序”這類演算法需要跳躍式訪問元素,從而缺乏這一特性。 - **複雜度的常數係數小**:在上述三種演算法中,快速排序的比較、賦值、交換等操作的總數量最少。這與“插入排序”比“泡沫排序”更快的原因類似。 ## 基準數最佳化 **快速排序在某些輸入下的時間效率可能降低**。舉一個極端例子,假設輸入陣列是完全倒序的,由於我們選擇最左端元素作為基準數,那麼在哨兵劃分完成後,基準數被交換至陣列最右端,導致左子陣列長度為 $n - 1$、右子陣列長度為 $0$ 。如此遞迴下去,每輪哨兵劃分後都有一個子陣列的長度為 $0$ ,分治策略失效,快速排序退化為“泡沫排序”的近似形式。 為了儘量避免這種情況發生,**我們可以最佳化哨兵劃分中的基準數的選取策略**。例如,我們可以隨機選取一個元素作為基準數。然而,如果運氣不佳,每次都選到不理想的基準數,效率仍然不盡如人意。 需要注意的是,程式語言通常生成的是“偽隨機數”。如果我們針對偽隨機數序列構建一個特定的測試樣例,那麼快速排序的效率仍然可能劣化。 為了進一步改進,我們可以在陣列中選取三個候選元素(通常為陣列的首、尾、中點元素),**並將這三個候選元素的中位數作為基準數**。這樣一來,基準數“既不太小也不太大”的機率將大幅提升。當然,我們還可以選取更多候選元素,以進一步提高演算法的穩健性。採用這種方法後,時間複雜度劣化至 $O(n^2)$ 的機率大大降低。 示例程式碼如下: ```src [file]{quick_sort}-[class]{quick_sort_median}-[func]{partition} ``` ## 遞迴深度最佳化 **在某些輸入下,快速排序可能佔用空間較多**。以完全有序的輸入陣列為例,設遞迴中的子陣列長度為 $m$ ,每輪哨兵劃分操作都將產生長度為 $0$ 的左子陣列和長度為 $m - 1$ 的右子陣列,這意味著每一層遞迴呼叫減少的問題規模非常小(只減少一個元素),遞迴樹的高度會達到 $n - 1$ ,此時需要佔用 $O(n)$ 大小的堆疊幀空間。 為了防止堆疊幀空間的累積,我們可以在每輪哨兵排序完成後,比較兩個子陣列的長度,**僅對較短的子陣列進行遞迴**。由於較短子陣列的長度不會超過 $n / 2$ ,因此這種方法能確保遞迴深度不超過 $\log n$ ,從而將最差空間複雜度最佳化至 $O(\log n)$ 。程式碼如下所示: ```src [file]{quick_sort}-[class]{quick_sort_tail_call}-[func]{quick_sort} ``` ================================================ FILE: zh-hant/docs/chapter_sorting/radix_sort.md ================================================ # 基數排序 上一節介紹了計數排序,它適用於資料量 $n$ 較大但資料範圍 $m$ 較小的情況。假設我們需要對 $n = 10^6$ 個學號進行排序,而學號是一個 $8$ 位數字,這意味著資料範圍 $m = 10^8$ 非常大,使用計數排序需要分配大量記憶體空間,而基數排序可以避免這種情況。 基數排序(radix sort)的核心思想與計數排序一致,也透過統計個數來實現排序。在此基礎上,基數排序利用數字各位之間的遞進關係,依次對每一位進行排序,從而得到最終的排序結果。 ## 演算法流程 以學號資料為例,假設數字的最低位是第 $1$ 位,最高位是第 $8$ 位,基數排序的流程如下圖所示。 1. 初始化位數 $k = 1$ 。 2. 對學號的第 $k$ 位執行“計數排序”。完成後,資料會根據第 $k$ 位從小到大排序。 3. 將 $k$ 增加 $1$ ,然後返回步驟 `2.` 繼續迭代,直到所有位都排序完成後結束。 ![基數排序演算法流程](radix_sort.assets/radix_sort_overview.png) 下面剖析程式碼實現。對於一個 $d$ 進位制的數字 $x$ ,要獲取其第 $k$ 位 $x_k$ ,可以使用以下計算公式: $$ x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d $$ 其中 $\lfloor a \rfloor$ 表示對浮點數 $a$ 向下取整,而 $\bmod \: d$ 表示對 $d$ 取模(取餘)。對於學號資料,$d = 10$ 且 $k \in [1, 8]$ 。 此外,我們需要小幅改動計數排序程式碼,使之可以根據數字的第 $k$ 位進行排序: ```src [file]{radix_sort}-[class]{}-[func]{radix_sort} ``` !!! question "為什麼從最低位開始排序?" 在連續的排序輪次中,後一輪排序會覆蓋前一輪排序的結果。舉例來說,如果第一輪排序結果 $a < b$ ,而第二輪排序結果 $a > b$ ,那麼第二輪的結果將取代第一輪的結果。由於數字的高位優先順序高於低位,因此應該先排序低位再排序高位。 ## 演算法特性 相較於計數排序,基數排序適用於數值範圍較大的情況,**但前提是資料必須可以表示為固定位數的格式,且位數不能過大**。例如,浮點數不適合使用基數排序,因為其位數 $k$ 過大,可能導致時間複雜度 $O(nk) \gg O(n^2)$ 。 - **時間複雜度為 $O(nk)$、非自適應排序**:設資料量為 $n$、資料為 $d$ 進位制、最大位數為 $k$ ,則對某一位執行計數排序使用 $O(n + d)$ 時間,排序所有 $k$ 位使用 $O((n + d)k)$ 時間。通常情況下,$d$ 和 $k$ 都相對較小,時間複雜度趨向 $O(n)$ 。 - **空間複雜度為 $O(n + d)$、非原地排序**:與計數排序相同,基數排序需要藉助長度為 $n$ 和 $d$ 的陣列 `res` 和 `counter` 。 - **穩定排序**:當計數排序穩定時,基數排序也穩定;當計數排序不穩定時,基數排序無法保證得到正確的排序結果。 ================================================ FILE: zh-hant/docs/chapter_sorting/selection_sort.md ================================================ # 選擇排序 選擇排序(selection sort)的工作原理非常簡單:開啟一個迴圈,每輪從未排序區間選擇最小的元素,將其放到已排序區間的末尾。 設陣列的長度為 $n$ ,選擇排序的演算法流程如下圖所示。 1. 初始狀態下,所有元素未排序,即未排序(索引)區間為 $[0, n-1]$ 。 2. 選取區間 $[0, n-1]$ 中的最小元素,將其與索引 $0$ 處的元素交換。完成後,陣列前 1 個元素已排序。 3. 選取區間 $[1, n-1]$ 中的最小元素,將其與索引 $1$ 處的元素交換。完成後,陣列前 2 個元素已排序。 4. 以此類推。經過 $n - 1$ 輪選擇與交換後,陣列前 $n - 1$ 個元素已排序。 5. 僅剩的一個元素必定是最大元素,無須排序,因此陣列排序完成。 === "<1>" ![選擇排序步驟](selection_sort.assets/selection_sort_step1.png) === "<2>" ![selection_sort_step2](selection_sort.assets/selection_sort_step2.png) === "<3>" ![selection_sort_step3](selection_sort.assets/selection_sort_step3.png) === "<4>" ![selection_sort_step4](selection_sort.assets/selection_sort_step4.png) === "<5>" ![selection_sort_step5](selection_sort.assets/selection_sort_step5.png) === "<6>" ![selection_sort_step6](selection_sort.assets/selection_sort_step6.png) === "<7>" ![selection_sort_step7](selection_sort.assets/selection_sort_step7.png) === "<8>" ![selection_sort_step8](selection_sort.assets/selection_sort_step8.png) === "<9>" ![selection_sort_step9](selection_sort.assets/selection_sort_step9.png) === "<10>" ![selection_sort_step10](selection_sort.assets/selection_sort_step10.png) === "<11>" ![selection_sort_step11](selection_sort.assets/selection_sort_step11.png) 在程式碼中,我們用 $k$ 來記錄未排序區間內的最小元素: ```src [file]{selection_sort}-[class]{}-[func]{selection_sort} ``` ## 演算法特性 - **時間複雜度為 $O(n^2)$、非自適應排序**:外迴圈共 $n - 1$ 輪,第一輪的未排序區間長度為 $n$ ,最後一輪的未排序區間長度為 $2$ ,即各輪外迴圈分別包含 $n$、$n - 1$、$\dots$、$3$、$2$ 輪內迴圈,求和為 $\frac{(n - 1)(n + 2)}{2}$ 。 - **空間複雜度為 $O(1)$、原地排序**:指標 $i$ 和 $j$ 使用常數大小的額外空間。 - **非穩定排序**:如下圖所示,元素 `nums[i]` 有可能被交換至與其相等的元素的右邊,導致兩者的相對順序發生改變。 ![選擇排序非穩定示例](selection_sort.assets/selection_sort_instability.png) ================================================ FILE: zh-hant/docs/chapter_sorting/sorting_algorithm.md ================================================ # 排序演算法 排序演算法(sorting algorithm)用於對一組資料按照特定順序進行排列。排序演算法有著廣泛的應用,因為有序資料通常能夠被更高效地查詢、分析和處理。 如下圖所示,排序演算法中的資料型別可以是整數、浮點數、字元或字串等。排序的判斷規則可根據需求設定,如數字大小、字元 ASCII 碼順序或自定義規則。 ![資料型別和判斷規則示例](sorting_algorithm.assets/sorting_examples.png) ## 評價維度 **執行效率**:我們期望排序演算法的時間複雜度儘量低,且總體操作數量較少(時間複雜度中的常數項變小)。對於大資料量的情況,執行效率顯得尤為重要。 **就地性**:顧名思義,原地排序透過在原陣列上直接操作實現排序,無須藉助額外的輔助陣列,從而節省記憶體。通常情況下,原地排序的資料搬運操作較少,執行速度也更快。 **穩定性**:穩定排序在完成排序後,相等元素在陣列中的相對順序不發生改變。 穩定排序是多級排序場景的必要條件。假設我們有一個儲存學生資訊的表格,第 1 列和第 2 列分別是姓名和年齡。在這種情況下,非穩定排序可能導致輸入資料的有序性喪失: ```shell # 輸入資料是按照姓名排序好的 # (name, age) ('A', 19) ('B', 18) ('C', 21) ('D', 19) ('E', 23) # 假設使用非穩定排序演算法按年齡排序串列, # 結果中 ('D', 19) 和 ('A', 19) 的相對位置改變, # 輸入資料按姓名排序的性質丟失 ('B', 18) ('D', 19) ('A', 19) ('C', 21) ('E', 23) ``` **自適應性**:自適應排序能夠利用輸入資料已有的順序資訊來減少計算量,達到更優的時間效率。自適應排序演算法的最佳時間複雜度通常優於平均時間複雜度。 **是否基於比較**:基於比較的排序依賴比較運算子($<$、$=$、$>$)來判斷元素的相對順序,從而排序整個陣列,理論最優時間複雜度為 $O(n \log n)$ 。而非比較排序不使用比較運算子,時間複雜度可達 $O(n)$ ,但其通用性相對較差。 ## 理想排序演算法 **執行快、原地、穩定、自適應、通用性好**。顯然,迄今為止尚未發現兼具以上所有特性的排序演算法。因此,在選擇排序演算法時,需要根據具體的資料特點和問題需求來決定。 接下來,我們將共同學習各種排序演算法,並基於上述評價維度對各個排序演算法的優缺點進行分析。 ================================================ FILE: zh-hant/docs/chapter_sorting/summary.md ================================================ # 小結 ### 重點回顧 - 泡沫排序透過交換相鄰元素來實現排序。透過新增一個標誌位來實現提前返回,我們可以將泡沫排序的最佳時間複雜度最佳化到 $O(n)$ 。 - 插入排序每輪將未排序區間內的元素插入到已排序區間的正確位置,從而完成排序。雖然插入排序的時間複雜度為 $O(n^2)$ ,但由於單元操作相對較少,因此在小資料量的排序任務中非常受歡迎。 - 快速排序基於哨兵劃分操作實現排序。在哨兵劃分中,有可能每次都選取到最差的基準數,導致時間複雜度劣化至 $O(n^2)$ 。引入中位數基準數或隨機基準數可以降低這種劣化的機率。透過優先遞迴較短子區間,可有效減小遞迴深度,將空間複雜度最佳化到 $O(\log n)$ 。 - 合併排序包括劃分和合並兩個階段,典型地體現了分治策略。在合併排序中,排序陣列需要建立輔助陣列,空間複雜度為 $O(n)$ ;然而排序鏈結串列的空間複雜度可以最佳化至 $O(1)$ 。 - 桶排序包含三個步驟:資料分桶、桶內排序和合並結果。它同樣體現了分治策略,適用於資料體量很大的情況。桶排序的關鍵在於對資料進行平均分配。 - 計數排序是桶排序的一個特例,它透過統計資料出現的次數來實現排序。計數排序適用於資料量大但資料範圍有限的情況,並且要求資料能夠轉換為正整數。 - 基數排序透過逐位排序來實現資料排序,要求資料能夠表示為固定位數的數字。 - 總的來說,我們希望找到一種排序演算法,具有高效率、穩定、原地以及自適應性等優點。然而,正如其他資料結構和演算法一樣,沒有一種排序演算法能夠同時滿足所有這些條件。在實際應用中,我們需要根據資料的特性來選擇合適的排序演算法。 - 下圖對比了主流排序演算法的效率、穩定性、就地性和自適應性等。 ![排序演算法對比](summary.assets/sorting_algorithms_comparison.png) ### Q & A **Q**:排序演算法穩定性在什麼情況下是必需的? 在現實中,我們有可能基於物件的某個屬性進行排序。例如,學生有姓名和身高兩個屬性,我們希望實現一個多級排序:先按照姓名進行排序,得到 `(A, 180) (B, 185) (C, 170) (D, 170)` ;再對身高進行排序。由於排序演算法不穩定,因此可能得到 `(D, 170) (C, 170) (A, 180) (B, 185)` 。 可以發現,學生 D 和 C 的位置發生了交換,姓名的有序性被破壞了,而這是我們不希望看到的。 **Q**:哨兵劃分中“從右往左查詢”與“從左往右查詢”的順序可以交換嗎? 不行,當我們以最左端元素為基準數時,必須先“從右往左查詢”再“從左往右查詢”。這個結論有些反直覺,我們來剖析一下原因。 哨兵劃分 `partition()` 的最後一步是交換 `nums[left]` 和 `nums[i]` 。完成交換後,基準數左邊的元素都 `<=` 基準數,**這就要求最後一步交換前 `nums[left] >= nums[i]` 必須成立**。假設我們先“從左往右查詢”,那麼如果找不到比基準數更大的元素,**則會在 `i == j` 時跳出迴圈,此時可能 `nums[j] == nums[i] > nums[left]`**。也就是說,此時最後一步交換操作會把一個比基準數更大的元素交換至陣列最左端,導致哨兵劃分失敗。 舉個例子,給定陣列 `[0, 0, 0, 0, 1]` ,如果先“從左向右查詢”,哨兵劃分後陣列為 `[1, 0, 0, 0, 0]` ,這個結果是不正確的。 再深入思考一下,如果我們選擇 `nums[right]` 為基準數,那麼正好反過來,必須先“從左往右查詢”。 **Q**:關於快速排序的遞迴深度最佳化,為什麼選短的陣列能保證遞迴深度不超過 $\log n$ ? 遞迴深度就是當前未返回的遞迴方法的數量。每輪哨兵劃分我們將原陣列劃分為兩個子陣列。在遞迴深度最佳化後,向下遞迴的子陣列長度最大為原陣列長度的一半。假設最差情況,一直為一半長度,那麼最終的遞迴深度就是 $\log n$ 。 回顧原始的快速排序,我們有可能會連續地遞迴長度較大的陣列,最差情況下為 $n$、$n - 1$、$\dots$、$2$、$1$ ,遞迴深度為 $n$ 。遞迴深度最佳化可以避免這種情況出現。 **Q**:當陣列中所有元素都相等時,快速排序的時間複雜度是 $O(n^2)$ 嗎?該如何處理這種退化情況? 是的。對於這種情況,可以考慮透過哨兵劃分將陣列劃分為三個部分:小於、等於、大於基準數。僅向下遞迴小於和大於的兩部分。在該方法下,輸入元素全部相等的陣列,僅一輪哨兵劃分即可完成排序。 **Q**:桶排序的最差時間複雜度為什麼是 $O(n^2)$ ? 最差情況下,所有元素被分至同一個桶中。如果我們採用一個 $O(n^2)$ 演算法來排序這些元素,則時間複雜度為 $O(n^2)$ 。 ================================================ FILE: zh-hant/docs/chapter_stack_and_queue/deque.md ================================================ # 雙向佇列 在佇列中,我們僅能刪除頭部元素或在尾部新增元素。如下圖所示,雙向佇列(double-ended queue)提供了更高的靈活性,允許在頭部和尾部執行元素的新增或刪除操作。 ![雙向佇列的操作](deque.assets/deque_operations.png) ## 雙向佇列常用操作 雙向佇列的常用操作如下表所示,具體的方法名稱需要根據所使用的程式語言來確定。

  雙向佇列操作效率

| 方法名 | 描述 | 時間複雜度 | | -------------- | ---------------- | ---------- | | `push_first()` | 將元素新增至佇列首 | $O(1)$ | | `push_last()` | 將元素新增至佇列尾 | $O(1)$ | | `pop_first()` | 刪除佇列首元素 | $O(1)$ | | `pop_last()` | 刪除佇列尾元素 | $O(1)$ | | `peek_first()` | 訪問佇列首元素 | $O(1)$ | | `peek_last()` | 訪問佇列尾元素 | $O(1)$ | 同樣地,我們可以直接使用程式語言中已實現的雙向佇列類別: === "Python" ```python title="deque.py" from collections import deque # 初始化雙向佇列 deq: deque[int] = deque() # 元素入列 deq.append(2) # 新增至佇列尾 deq.append(5) deq.append(4) deq.appendleft(3) # 新增至佇列首 deq.appendleft(1) # 訪問元素 front: int = deq[0] # 佇列首元素 rear: int = deq[-1] # 佇列尾元素 # 元素出列 pop_front: int = deq.popleft() # 佇列首元素出列 pop_rear: int = deq.pop() # 佇列尾元素出列 # 獲取雙向佇列的長度 size: int = len(deq) # 判斷雙向佇列是否為空 is_empty: bool = len(deq) == 0 ``` === "C++" ```cpp title="deque.cpp" /* 初始化雙向佇列 */ deque deque; /* 元素入列 */ deque.push_back(2); // 新增至佇列尾 deque.push_back(5); deque.push_back(4); deque.push_front(3); // 新增至佇列首 deque.push_front(1); /* 訪問元素 */ int front = deque.front(); // 佇列首元素 int back = deque.back(); // 佇列尾元素 /* 元素出列 */ deque.pop_front(); // 佇列首元素出列 deque.pop_back(); // 佇列尾元素出列 /* 獲取雙向佇列的長度 */ int size = deque.size(); /* 判斷雙向佇列是否為空 */ bool empty = deque.empty(); ``` === "Java" ```java title="deque.java" /* 初始化雙向佇列 */ Deque deque = new LinkedList<>(); /* 元素入列 */ deque.offerLast(2); // 新增至佇列尾 deque.offerLast(5); deque.offerLast(4); deque.offerFirst(3); // 新增至佇列首 deque.offerFirst(1); /* 訪問元素 */ int peekFirst = deque.peekFirst(); // 佇列首元素 int peekLast = deque.peekLast(); // 佇列尾元素 /* 元素出列 */ int popFirst = deque.pollFirst(); // 佇列首元素出列 int popLast = deque.pollLast(); // 佇列尾元素出列 /* 獲取雙向佇列的長度 */ int size = deque.size(); /* 判斷雙向佇列是否為空 */ boolean isEmpty = deque.isEmpty(); ``` === "C#" ```csharp title="deque.cs" /* 初始化雙向佇列 */ // 在 C# 中,將鏈結串列 LinkedList 看作雙向佇列來使用 LinkedList deque = new(); /* 元素入列 */ deque.AddLast(2); // 新增至佇列尾 deque.AddLast(5); deque.AddLast(4); deque.AddFirst(3); // 新增至佇列首 deque.AddFirst(1); /* 訪問元素 */ int peekFirst = deque.First.Value; // 佇列首元素 int peekLast = deque.Last.Value; // 佇列尾元素 /* 元素出列 */ deque.RemoveFirst(); // 佇列首元素出列 deque.RemoveLast(); // 佇列尾元素出列 /* 獲取雙向佇列的長度 */ int size = deque.Count; /* 判斷雙向佇列是否為空 */ bool isEmpty = deque.Count == 0; ``` === "Go" ```go title="deque_test.go" /* 初始化雙向佇列 */ // 在 Go 中,將 list 作為雙向佇列使用 deque := list.New() /* 元素入列 */ deque.PushBack(2) // 新增至佇列尾 deque.PushBack(5) deque.PushBack(4) deque.PushFront(3) // 新增至佇列首 deque.PushFront(1) /* 訪問元素 */ front := deque.Front() // 佇列首元素 rear := deque.Back() // 佇列尾元素 /* 元素出列 */ deque.Remove(front) // 佇列首元素出列 deque.Remove(rear) // 佇列尾元素出列 /* 獲取雙向佇列的長度 */ size := deque.Len() /* 判斷雙向佇列是否為空 */ isEmpty := deque.Len() == 0 ``` === "Swift" ```swift title="deque.swift" /* 初始化雙向佇列 */ // Swift 沒有內建的雙向佇列類別,可以把 Array 當作雙向佇列來使用 var deque: [Int] = [] /* 元素入列 */ deque.append(2) // 新增至佇列尾 deque.append(5) deque.append(4) deque.insert(3, at: 0) // 新增至佇列首 deque.insert(1, at: 0) /* 訪問元素 */ let peekFirst = deque.first! // 佇列首元素 let peekLast = deque.last! // 佇列尾元素 /* 元素出列 */ // 使用 Array 模擬時 popFirst 的複雜度為 O(n) let popFirst = deque.removeFirst() // 佇列首元素出列 let popLast = deque.removeLast() // 佇列尾元素出列 /* 獲取雙向佇列的長度 */ let size = deque.count /* 判斷雙向佇列是否為空 */ let isEmpty = deque.isEmpty ``` === "JS" ```javascript title="deque.js" /* 初始化雙向佇列 */ // JavaScript 沒有內建的雙端佇列,只能把 Array 當作雙端佇列來使用 const deque = []; /* 元素入列 */ deque.push(2); deque.push(5); deque.push(4); // 請注意,由於是陣列,unshift() 方法的時間複雜度為 O(n) deque.unshift(3); deque.unshift(1); /* 訪問元素 */ const peekFirst = deque[0]; const peekLast = deque[deque.length - 1]; /* 元素出列 */ // 請注意,由於是陣列,shift() 方法的時間複雜度為 O(n) const popFront = deque.shift(); const popBack = deque.pop(); /* 獲取雙向佇列的長度 */ const size = deque.length; /* 判斷雙向佇列是否為空 */ const isEmpty = size === 0; ``` === "TS" ```typescript title="deque.ts" /* 初始化雙向佇列 */ // TypeScript 沒有內建的雙端佇列,只能把 Array 當作雙端佇列來使用 const deque: number[] = []; /* 元素入列 */ deque.push(2); deque.push(5); deque.push(4); // 請注意,由於是陣列,unshift() 方法的時間複雜度為 O(n) deque.unshift(3); deque.unshift(1); /* 訪問元素 */ const peekFirst: number = deque[0]; const peekLast: number = deque[deque.length - 1]; /* 元素出列 */ // 請注意,由於是陣列,shift() 方法的時間複雜度為 O(n) const popFront: number = deque.shift() as number; const popBack: number = deque.pop() as number; /* 獲取雙向佇列的長度 */ const size: number = deque.length; /* 判斷雙向佇列是否為空 */ const isEmpty: boolean = size === 0; ``` === "Dart" ```dart title="deque.dart" /* 初始化雙向佇列 */ // 在 Dart 中,Queue 被定義為雙向佇列 Queue deque = Queue(); /* 元素入列 */ deque.addLast(2); // 新增至佇列尾 deque.addLast(5); deque.addLast(4); deque.addFirst(3); // 新增至佇列首 deque.addFirst(1); /* 訪問元素 */ int peekFirst = deque.first; // 佇列首元素 int peekLast = deque.last; // 佇列尾元素 /* 元素出列 */ int popFirst = deque.removeFirst(); // 佇列首元素出列 int popLast = deque.removeLast(); // 佇列尾元素出列 /* 獲取雙向佇列的長度 */ int size = deque.length; /* 判斷雙向佇列是否為空 */ bool isEmpty = deque.isEmpty; ``` === "Rust" ```rust title="deque.rs" /* 初始化雙向佇列 */ let mut deque: VecDeque = VecDeque::new(); /* 元素入列 */ deque.push_back(2); // 新增至佇列尾 deque.push_back(5); deque.push_back(4); deque.push_front(3); // 新增至佇列首 deque.push_front(1); /* 訪問元素 */ if let Some(front) = deque.front() { // 佇列首元素 } if let Some(rear) = deque.back() { // 佇列尾元素 } /* 元素出列 */ if let Some(pop_front) = deque.pop_front() { // 佇列首元素出列 } if let Some(pop_rear) = deque.pop_back() { // 佇列尾元素出列 } /* 獲取雙向佇列的長度 */ let size = deque.len(); /* 判斷雙向佇列是否為空 */ let is_empty = deque.is_empty(); ``` === "C" ```c title="deque.c" // C 未提供內建雙向佇列 ``` === "Kotlin" ```kotlin title="deque.kt" /* 初始化雙向佇列 */ val deque = LinkedList() /* 元素入列 */ deque.offerLast(2) // 新增至佇列尾 deque.offerLast(5) deque.offerLast(4) deque.offerFirst(3) // 新增至佇列首 deque.offerFirst(1) /* 訪問元素 */ val peekFirst = deque.peekFirst() // 佇列首元素 val peekLast = deque.peekLast() // 佇列尾元素 /* 元素出列 */ val popFirst = deque.pollFirst() // 佇列首元素出列 val popLast = deque.pollLast() // 佇列尾元素出列 /* 獲取雙向佇列的長度 */ val size = deque.size /* 判斷雙向佇列是否為空 */ val isEmpty = deque.isEmpty() ``` === "Ruby" ```ruby title="deque.rb" # 初始化雙向佇列 # Ruby 沒有內直的雙端佇列,只能把 Array 當作雙端佇列來使用 deque = [] # 元素如隊 deque << 2 deque << 5 deque << 4 # 請注意,由於是陣列,Array#unshift 方法的時間複雜度為 O(n) deque.unshift(3) deque.unshift(1) # 訪問元素 peek_first = deque.first peek_last = deque.last # 元素出列 # 請注意,由於是陣列, Array#shift 方法的時間複雜度為 O(n) pop_front = deque.shift pop_back = deque.pop # 獲取雙向佇列的長度 size = deque.length # 判斷雙向佇列是否為空 is_empty = size.zero? ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%88%97%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%96%B0%E5%A2%9E%E8%87%B3%E4%BD%87%E5%88%97%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%96%B0%E5%A2%9E%E8%87%B3%E4%BD%87%E5%88%97%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%20deque%20%3D%22%2C%20deq%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22%2C%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E4%BD%87%E5%88%97%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22%2C%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%88%97%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E5%88%97%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%A6%96%E5%87%BA%E5%88%97%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22%2C%20pop_front%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%A6%96%E5%87%BA%E5%88%97%E5%BE%8C%20deque%20%3D%22%2C%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E4%BD%87%E5%88%97%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E5%88%97%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E5%B0%BE%E5%87%BA%E5%88%97%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22%2C%20pop_rear%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E5%B0%BE%E5%87%BA%E5%88%97%E5%BE%8C%20deque%20%3D%22%2C%20deq%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## 雙向佇列實現 * 雙向佇列的實現與佇列類似,可以選擇鏈結串列或陣列作為底層資料結構。 ### 基於雙向鏈結串列的實現 回顧上一節內容,我們使用普通單向鏈結串列來實現佇列,因為它可以方便地刪除頭節點(對應出列操作)和在尾節點後新增新節點(對應入列操作)。 對於雙向佇列而言,頭部和尾部都可以執行入列和出列操作。換句話說,雙向佇列需要實現另一個對稱方向的操作。為此,我們採用“雙向鏈結串列”作為雙向佇列的底層資料結構。 如下圖所示,我們將雙向鏈結串列的頭節點和尾節點視為雙向佇列的佇列首和佇列尾,同時實現在兩端新增和刪除節點的功能。 === "<1>" ![基於鏈結串列實現雙向佇列的入列出列操作](deque.assets/linkedlist_deque_step1.png) === "<2>" ![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png) === "<3>" ![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png) === "<4>" ![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png) === "<5>" ![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png) 實現程式碼如下所示: ```src [file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{} ``` ### 基於陣列的實現 如下圖所示,與基於陣列實現佇列類似,我們也可以使用環形陣列來實現雙向佇列。 === "<1>" ![基於陣列實現雙向佇列的入列出列操作](deque.assets/array_deque_step1.png) === "<2>" ![array_deque_push_last](deque.assets/array_deque_step2_push_last.png) === "<3>" ![array_deque_push_first](deque.assets/array_deque_step3_push_first.png) === "<4>" ![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png) === "<5>" ![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png) 在佇列的實現基礎上,僅需增加“佇列首入列”和“佇列尾出列”的方法: ```src [file]{array_deque}-[class]{array_deque}-[func]{} ``` ## 雙向佇列應用 雙向佇列兼具堆疊與佇列的邏輯,**因此它可以實現這兩者的所有應用場景,同時提供更高的自由度**。 我們知道,軟體的“撤銷”功能通常使用堆疊來實現:系統將每次更改操作 `push` 到堆疊中,然後透過 `pop` 實現撤銷。然而,考慮到系統資源的限制,軟體通常會限制撤銷的步數(例如僅允許儲存 $50$ 步)。當堆疊的長度超過 $50$ 時,軟體需要在堆疊底(佇列首)執行刪除操作。**但堆疊無法實現該功能,此時就需要使用雙向佇列來替代堆疊**。請注意,“撤銷”的核心邏輯仍然遵循堆疊的先入後出原則,只是雙向佇列能夠更加靈活地實現一些額外邏輯。 ================================================ FILE: zh-hant/docs/chapter_stack_and_queue/index.md ================================================ # 堆疊與佇列 ![堆疊與佇列](../assets/covers/chapter_stack_and_queue.jpg) !!! abstract 堆疊如同疊貓貓,而佇列就像貓貓排隊。 兩者分別代表先入後出和先入先出的邏輯關係。 ================================================ FILE: zh-hant/docs/chapter_stack_and_queue/queue.md ================================================ # 佇列 佇列(queue)是一種遵循先入先出規則的線性資料結構。顧名思義,佇列模擬了排隊現象,即新來的人不斷加入佇列尾部,而位於佇列頭部的人逐個離開。 如下圖所示,我們將佇列頭部稱為“佇列首”,尾部稱為“佇列尾”,將把元素加入列尾的操作稱為“入列”,刪除佇列首元素的操作稱為“出列”。 ![佇列的先入先出規則](queue.assets/queue_operations.png) ## 佇列常用操作 佇列的常見操作如下表所示。需要注意的是,不同程式語言的方法名稱可能會有所不同。我們在此採用與堆疊相同的方法命名。

  佇列操作效率

| 方法名 | 描述 | 時間複雜度 | | -------- | ---------------------------- | ---------- | | `push()` | 元素入列,即將元素新增至佇列尾 | $O(1)$ | | `pop()` | 佇列首元素出列 | $O(1)$ | | `peek()` | 訪問佇列首元素 | $O(1)$ | 我們可以直接使用程式語言中現成的佇列類別: === "Python" ```python title="queue.py" from collections import deque # 初始化佇列 # 在 Python 中,我們一般將雙向佇列類別 deque 當作佇列使用 # 雖然 queue.Queue() 是純正的佇列類別,但不太好用,因此不推薦 que: deque[int] = deque() # 元素入列 que.append(1) que.append(3) que.append(2) que.append(5) que.append(4) # 訪問佇列首元素 front: int = que[0] # 元素出列 pop: int = que.popleft() # 獲取佇列的長度 size: int = len(que) # 判斷佇列是否為空 is_empty: bool = len(que) == 0 ``` === "C++" ```cpp title="queue.cpp" /* 初始化佇列 */ queue queue; /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); /* 訪問佇列首元素 */ int front = queue.front(); /* 元素出列 */ queue.pop(); /* 獲取佇列的長度 */ int size = queue.size(); /* 判斷佇列是否為空 */ bool empty = queue.empty(); ``` === "Java" ```java title="queue.java" /* 初始化佇列 */ Queue queue = new LinkedList<>(); /* 元素入列 */ queue.offer(1); queue.offer(3); queue.offer(2); queue.offer(5); queue.offer(4); /* 訪問佇列首元素 */ int peek = queue.peek(); /* 元素出列 */ int pop = queue.poll(); /* 獲取佇列的長度 */ int size = queue.size(); /* 判斷佇列是否為空 */ boolean isEmpty = queue.isEmpty(); ``` === "C#" ```csharp title="queue.cs" /* 初始化佇列 */ Queue queue = new(); /* 元素入列 */ queue.Enqueue(1); queue.Enqueue(3); queue.Enqueue(2); queue.Enqueue(5); queue.Enqueue(4); /* 訪問佇列首元素 */ int peek = queue.Peek(); /* 元素出列 */ int pop = queue.Dequeue(); /* 獲取佇列的長度 */ int size = queue.Count; /* 判斷佇列是否為空 */ bool isEmpty = queue.Count == 0; ``` === "Go" ```go title="queue_test.go" /* 初始化佇列 */ // 在 Go 中,將 list 作為佇列來使用 queue := list.New() /* 元素入列 */ queue.PushBack(1) queue.PushBack(3) queue.PushBack(2) queue.PushBack(5) queue.PushBack(4) /* 訪問佇列首元素 */ peek := queue.Front() /* 元素出列 */ pop := queue.Front() queue.Remove(pop) /* 獲取佇列的長度 */ size := queue.Len() /* 判斷佇列是否為空 */ isEmpty := queue.Len() == 0 ``` === "Swift" ```swift title="queue.swift" /* 初始化佇列 */ // Swift 沒有內建的佇列類別,可以把 Array 當作佇列來使用 var queue: [Int] = [] /* 元素入列 */ queue.append(1) queue.append(3) queue.append(2) queue.append(5) queue.append(4) /* 訪問佇列首元素 */ let peek = queue.first! /* 元素出列 */ // 由於是陣列,因此 removeFirst 的複雜度為 O(n) let pool = queue.removeFirst() /* 獲取佇列的長度 */ let size = queue.count /* 判斷佇列是否為空 */ let isEmpty = queue.isEmpty ``` === "JS" ```javascript title="queue.js" /* 初始化佇列 */ // JavaScript 沒有內建的佇列,可以把 Array 當作佇列來使用 const queue = []; /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); /* 訪問佇列首元素 */ const peek = queue[0]; /* 元素出列 */ // 底層是陣列,因此 shift() 方法的時間複雜度為 O(n) const pop = queue.shift(); /* 獲取佇列的長度 */ const size = queue.length; /* 判斷佇列是否為空 */ const empty = queue.length === 0; ``` === "TS" ```typescript title="queue.ts" /* 初始化佇列 */ // TypeScript 沒有內建的佇列,可以把 Array 當作佇列來使用 const queue: number[] = []; /* 元素入列 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); /* 訪問佇列首元素 */ const peek = queue[0]; /* 元素出列 */ // 底層是陣列,因此 shift() 方法的時間複雜度為 O(n) const pop = queue.shift(); /* 獲取佇列的長度 */ const size = queue.length; /* 判斷佇列是否為空 */ const empty = queue.length === 0; ``` === "Dart" ```dart title="queue.dart" /* 初始化佇列 */ // 在 Dart 中,佇列類別 Qeque 是雙向佇列,也可作為佇列使用 Queue queue = Queue(); /* 元素入列 */ queue.add(1); queue.add(3); queue.add(2); queue.add(5); queue.add(4); /* 訪問佇列首元素 */ int peek = queue.first; /* 元素出列 */ int pop = queue.removeFirst(); /* 獲取佇列的長度 */ int size = queue.length; /* 判斷佇列是否為空 */ bool isEmpty = queue.isEmpty; ``` === "Rust" ```rust title="queue.rs" /* 初始化雙向佇列 */ // 在 Rust 中使用雙向佇列作為普通佇列來使用 let mut deque: VecDeque = VecDeque::new(); /* 元素入列 */ deque.push_back(1); deque.push_back(3); deque.push_back(2); deque.push_back(5); deque.push_back(4); /* 訪問佇列首元素 */ if let Some(front) = deque.front() { } /* 元素出列 */ if let Some(pop) = deque.pop_front() { } /* 獲取佇列的長度 */ let size = deque.len(); /* 判斷佇列是否為空 */ let is_empty = deque.is_empty(); ``` === "C" ```c title="queue.c" // C 未提供內建佇列 ``` === "Kotlin" ```kotlin title="queue.kt" /* 初始化佇列 */ val queue = LinkedList() /* 元素入列 */ queue.offer(1) queue.offer(3) queue.offer(2) queue.offer(5) queue.offer(4) /* 訪問佇列首元素 */ val peek = queue.peek() /* 元素出列 */ val pop = queue.poll() /* 獲取佇列的長度 */ val size = queue.size /* 判斷佇列是否為空 */ val isEmpty = queue.isEmpty() ``` === "Ruby" ```ruby title="queue.rb" # 初始化佇列 # Ruby 內建的佇列(Thread::Queue) 沒有 peek 和走訪方法,可以把 Array 當作佇列來使用 queue = [] # 元素入列 queue.push(1) queue.push(3) queue.push(2) queue.push(5) queue.push(4) # 訪問佇列元素 peek = queue.first # 元素出列 # 清注意,由於是陣列,Array#shift 方法時間複雜度為 O(n) pop = queue.shift # 獲取佇列的長度 size = queue.length # 判斷佇列是否為空 is_empty = queue.empty? ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BD%87%E5%88%97%0A%20%20%20%20%23%20%E5%9C%A8%20Python%20%E4%B8%AD%EF%BC%8C%E6%88%91%E5%80%91%E4%B8%80%E8%88%AC%E5%B0%87%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%E9%A1%9E%E5%88%A5%20deque%20%E7%9C%8B%E4%BD%9C%E4%BD%87%E5%88%97%E4%BD%BF%E7%94%A8%0A%20%20%20%20%23%20%E9%9B%96%E7%84%B6%20queue.Queue%28%29%20%E6%98%AF%E7%B4%94%E6%AD%A3%E7%9A%84%E4%BD%87%E5%88%97%E9%A1%9E%E5%88%A5%EF%BC%8C%E4%BD%86%E4%B8%8D%E5%A4%AA%E5%A5%BD%E7%94%A8%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%88%97%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%20que%20%3D%22%2C%20que%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22%2C%20front%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%88%97%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%88%97%E5%85%83%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%88%97%E5%BE%8C%20que%20%3D%22%2C%20que%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E4%BD%87%E5%88%97%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## 佇列實現 為了實現佇列,我們需要一種資料結構,可以在一端新增元素,並在另一端刪除元素,鏈結串列和陣列都符合要求。 ### 基於鏈結串列的實現 如下圖所示,我們可以將鏈結串列的“頭節點”和“尾節點”分別視為“佇列首”和“佇列尾”,規定佇列尾僅可新增節點,佇列首僅可刪除節點。 === "<1>" ![基於鏈結串列實現佇列的入列出列操作](queue.assets/linkedlist_queue_step1.png) === "<2>" ![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png) === "<3>" ![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png) 以下是用鏈結串列實現佇列的程式碼: ```src [file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{} ``` ### 基於陣列的實現 在陣列中刪除首元素的時間複雜度為 $O(n)$ ,這會導致出列操作效率較低。然而,我們可以採用以下巧妙方法來避免這個問題。 我們可以使用一個變數 `front` 指向佇列首元素的索引,並維護一個變數 `size` 用於記錄佇列長度。定義 `rear = front + size` ,這個公式計算出的 `rear` 指向佇列尾元素之後的下一個位置。 基於此設計,**陣列中包含元素的有效區間為 `[front, rear - 1]`**,各種操作的實現方法如下圖所示。 - 入列操作:將輸入元素賦值給 `rear` 索引處,並將 `size` 增加 1 。 - 出列操作:只需將 `front` 增加 1 ,並將 `size` 減少 1 。 可以看到,入列和出列操作都只需進行一次操作,時間複雜度均為 $O(1)$ 。 === "<1>" ![基於陣列實現佇列的入列出列操作](queue.assets/array_queue_step1.png) === "<2>" ![array_queue_push](queue.assets/array_queue_step2_push.png) === "<3>" ![array_queue_pop](queue.assets/array_queue_step3_pop.png) 你可能會發現一個問題:在不斷進行入列和出列的過程中,`front` 和 `rear` 都在向右移動,**當它們到達陣列尾部時就無法繼續移動了**。為了解決此問題,我們可以將陣列視為首尾相接的“環形陣列”。 對於環形陣列,我們需要讓 `front` 或 `rear` 在越過陣列尾部時,直接回到陣列頭部繼續走訪。這種週期性規律可以透過“取餘操作”來實現,程式碼如下所示: ```src [file]{array_queue}-[class]{array_queue}-[func]{} ``` 以上實現的佇列仍然具有侷限性:其長度不可變。然而,這個問題不難解決,我們可以將陣列替換為動態陣列,從而引入擴容機制。有興趣的讀者可以嘗試自行實現。 兩種實現的對比結論與堆疊一致,在此不再贅述。 ## 佇列典型應用 - **淘寶訂單**。購物者下單後,訂單將加入佇列中,系統隨後會根據順序處理佇列中的訂單。在雙十一期間,短時間內會產生海量訂單,高併發成為工程師們需要重點攻克的問題。 - **各類待辦事項**。任何需要實現“先來後到”功能的場景,例如印表機的任務佇列、餐廳的出餐佇列等,佇列在這些場景中可以有效地維護處理順序。 ================================================ FILE: zh-hant/docs/chapter_stack_and_queue/stack.md ================================================ # 堆疊 堆疊(stack)是一種遵循先入後出邏輯的線性資料結構。 我們可以將堆疊類比為桌面上的一疊盤子,規定每次只能移動一個盤子,那麼想取出底部的盤子,則需要先將上面的盤子依次移走。我們將盤子替換為各種型別的元素(如整數、字元、物件等),就得到了堆疊這種資料結構。 如下圖所示,我們把堆積疊元素的頂部稱為“堆疊頂”,底部稱為“堆疊底”。將把元素新增到堆疊頂的操作叫作“入堆疊”,刪除堆疊頂元素的操作叫作“出堆疊”。 ![堆疊的先入後出規則](stack.assets/stack_operations.png) ## 堆疊的常用操作 堆疊的常用操作如下表所示,具體的方法名需要根據所使用的程式語言來確定。在此,我們以常見的 `push()`、`pop()`、`peek()` 命名為例。

  堆疊的操作效率

| 方法 | 描述 | 時間複雜度 | | -------- | ---------------------- | ---------- | | `push()` | 元素入堆疊(新增至堆疊頂) | $O(1)$ | | `pop()` | 堆疊頂元素出堆疊 | $O(1)$ | | `peek()` | 訪問堆疊頂元素 | $O(1)$ | 通常情況下,我們可以直接使用程式語言內建的堆疊類別。然而,某些語言可能沒有專門提供堆疊類別,這時我們可以將該語言的“陣列”或“鏈結串列”當作堆疊來使用,並在程式邏輯上忽略與堆疊無關的操作。 === "Python" ```python title="stack.py" # 初始化堆疊 # Python 沒有內建的堆疊類別,可以把 list 當作堆疊來使用 stack: list[int] = [] # 元素入堆疊 stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) # 訪問堆疊頂元素 peek: int = stack[-1] # 元素出堆疊 pop: int = stack.pop() # 獲取堆疊的長度 size: int = len(stack) # 判斷是否為空 is_empty: bool = len(stack) == 0 ``` === "C++" ```cpp title="stack.cpp" /* 初始化堆疊 */ stack stack; /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* 訪問堆疊頂元素 */ int top = stack.top(); /* 元素出堆疊 */ stack.pop(); // 無返回值 /* 獲取堆疊的長度 */ int size = stack.size(); /* 判斷是否為空 */ bool empty = stack.empty(); ``` === "Java" ```java title="stack.java" /* 初始化堆疊 */ Stack stack = new Stack<>(); /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* 訪問堆疊頂元素 */ int peek = stack.peek(); /* 元素出堆疊 */ int pop = stack.pop(); /* 獲取堆疊的長度 */ int size = stack.size(); /* 判斷是否為空 */ boolean isEmpty = stack.isEmpty(); ``` === "C#" ```csharp title="stack.cs" /* 初始化堆疊 */ Stack stack = new(); /* 元素入堆疊 */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); /* 訪問堆疊頂元素 */ int peek = stack.Peek(); /* 元素出堆疊 */ int pop = stack.Pop(); /* 獲取堆疊的長度 */ int size = stack.Count; /* 判斷是否為空 */ bool isEmpty = stack.Count == 0; ``` === "Go" ```go title="stack_test.go" /* 初始化堆疊 */ // 在 Go 中,推薦將 Slice 當作堆疊來使用 var stack []int /* 元素入堆疊 */ stack = append(stack, 1) stack = append(stack, 3) stack = append(stack, 2) stack = append(stack, 5) stack = append(stack, 4) /* 訪問堆疊頂元素 */ peek := stack[len(stack)-1] /* 元素出堆疊 */ pop := stack[len(stack)-1] stack = stack[:len(stack)-1] /* 獲取堆疊的長度 */ size := len(stack) /* 判斷是否為空 */ isEmpty := len(stack) == 0 ``` === "Swift" ```swift title="stack.swift" /* 初始化堆疊 */ // Swift 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 var stack: [Int] = [] /* 元素入堆疊 */ stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) /* 訪問堆疊頂元素 */ let peek = stack.last! /* 元素出堆疊 */ let pop = stack.removeLast() /* 獲取堆疊的長度 */ let size = stack.count /* 判斷是否為空 */ let isEmpty = stack.isEmpty ``` === "JS" ```javascript title="stack.js" /* 初始化堆疊 */ // JavaScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 const stack = []; /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* 訪問堆疊頂元素 */ const peek = stack[stack.length-1]; /* 元素出堆疊 */ const pop = stack.pop(); /* 獲取堆疊的長度 */ const size = stack.length; /* 判斷是否為空 */ const is_empty = stack.length === 0; ``` === "TS" ```typescript title="stack.ts" /* 初始化堆疊 */ // TypeScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 const stack: number[] = []; /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* 訪問堆疊頂元素 */ const peek = stack[stack.length - 1]; /* 元素出堆疊 */ const pop = stack.pop(); /* 獲取堆疊的長度 */ const size = stack.length; /* 判斷是否為空 */ const is_empty = stack.length === 0; ``` === "Dart" ```dart title="stack.dart" /* 初始化堆疊 */ // Dart 沒有內建的堆疊類別,可以把 List 當作堆疊來使用 List stack = []; /* 元素入堆疊 */ stack.add(1); stack.add(3); stack.add(2); stack.add(5); stack.add(4); /* 訪問堆疊頂元素 */ int peek = stack.last; /* 元素出堆疊 */ int pop = stack.removeLast(); /* 獲取堆疊的長度 */ int size = stack.length; /* 判斷是否為空 */ bool isEmpty = stack.isEmpty; ``` === "Rust" ```rust title="stack.rs" /* 初始化堆疊 */ // 把 Vec 當作堆疊來使用 let mut stack: Vec = Vec::new(); /* 元素入堆疊 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); /* 訪問堆疊頂元素 */ let top = stack.last().unwrap(); /* 元素出堆疊 */ let pop = stack.pop().unwrap(); /* 獲取堆疊的長度 */ let size = stack.len(); /* 判斷是否為空 */ let is_empty = stack.is_empty(); ``` === "C" ```c title="stack.c" // C 未提供內建堆疊 ``` === "Kotlin" ```kotlin title="stack.kt" /* 初始化堆疊 */ val stack = Stack() /* 元素入堆疊 */ stack.push(1) stack.push(3) stack.push(2) stack.push(5) stack.push(4) /* 訪問堆疊頂元素 */ val peek = stack.peek() /* 元素出堆疊 */ val pop = stack.pop() /* 獲取堆疊的長度 */ val size = stack.size /* 判斷是否為空 */ val isEmpty = stack.isEmpty() ``` === "Ruby" ```ruby title="stack.rb" # 初始化堆疊 # Ruby 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 stack = [] # 元素入堆疊 stack << 1 stack << 3 stack << 2 stack << 5 stack << 4 # 訪問堆疊頂元素 peek = stack.last # 元素出堆疊 pop = stack.pop # 獲取堆疊的長度 size = stack.length # 判斷是否為空 is_empty = stack.empty? ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A0%86%E7%96%8A%0A%20%20%20%20%23%20Python%20%E6%B2%92%E6%9C%89%E5%85%A7%E5%BB%BA%E7%9A%84%E5%A0%86%E7%96%8A%E9%A1%9E%E5%88%A5%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E7%95%B6%E4%BD%9C%E5%A0%86%E7%96%8A%E4%BE%86%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%96%8A%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%20stack%20%3D%22%2C%20stack%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%96%8A%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%85%83%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%BE%8C%20stack%20%3D%22%2C%20stack%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## 堆疊的實現 為了深入瞭解堆疊的執行機制,我們來嘗試自己實現一個堆疊類別。 堆疊遵循先入後出的原則,因此我們只能在堆疊頂新增或刪除元素。然而,陣列和鏈結串列都可以在任意位置新增和刪除元素,**因此堆疊可以視為一種受限制的陣列或鏈結串列**。換句話說,我們可以“遮蔽”陣列或鏈結串列的部分無關操作,使其對外表現的邏輯符合堆疊的特性。 ### 基於鏈結串列的實現 使用鏈結串列實現堆疊時,我們可以將鏈結串列的頭節點視為堆疊頂,尾節點視為堆疊底。 如下圖所示,對於入堆疊操作,我們只需將元素插入鏈結串列頭部,這種節點插入方法被稱為“頭插法”。而對於出堆疊操作,只需將頭節點從鏈結串列中刪除即可。 === "<1>" ![基於鏈結串列實現堆疊的入堆疊出堆疊操作](stack.assets/linkedlist_stack_step1.png) === "<2>" ![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png) === "<3>" ![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png) 以下是基於鏈結串列實現堆疊的示例程式碼: ```src [file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{} ``` ### 基於陣列的實現 使用陣列實現堆疊時,我們可以將陣列的尾部作為堆疊頂。如下圖所示,入堆疊與出堆疊操作分別對應在陣列尾部新增元素與刪除元素,時間複雜度都為 $O(1)$ 。 === "<1>" ![基於陣列實現堆疊的入堆疊出堆疊操作](stack.assets/array_stack_step1.png) === "<2>" ![array_stack_push](stack.assets/array_stack_step2_push.png) === "<3>" ![array_stack_pop](stack.assets/array_stack_step3_pop.png) 由於入堆疊的元素可能會源源不斷地增加,因此我們可以使用動態陣列,這樣就無須自行處理陣列擴容問題。以下為示例程式碼: ```src [file]{array_stack}-[class]{array_stack}-[func]{} ``` ## 兩種實現對比 **支持操作** 兩種實現都支持堆疊定義中的各項操作。陣列實現額外支持隨機訪問,但這已超出了堆疊的定義範疇,因此一般不會用到。 **時間效率** 在基於陣列的實現中,入堆疊和出堆疊操作都在預先分配好的連續記憶體中進行,具有很好的快取本地性,因此效率較高。然而,如果入堆疊時超出陣列容量,會觸發擴容機制,導致該次入堆疊操作的時間複雜度變為 $O(n)$ 。 在基於鏈結串列的實現中,鏈結串列的擴容非常靈活,不存在上述陣列擴容時效率降低的問題。但是,入堆疊操作需要初始化節點物件並修改指標,因此效率相對較低。不過,如果入堆疊元素本身就是節點物件,那麼可以省去初始化步驟,從而提高效率。 綜上所述,當入堆疊與出堆疊操作的元素是基本資料型別時,例如 `int` 或 `double` ,我們可以得出以下結論。 - 基於陣列實現的堆疊在觸發擴容時效率會降低,但由於擴容是低頻操作,因此平均效率更高。 - 基於鏈結串列實現的堆疊可以提供更加穩定的效率表現。 **空間效率** 在初始化串列時,系統會為串列分配“初始容量”,該容量可能超出實際需求;並且,擴容機制通常是按照特定倍率(例如 2 倍)進行擴容的,擴容後的容量也可能超出實際需求。因此,**基於陣列實現的堆疊可能造成一定的空間浪費**。 然而,由於鏈結串列節點需要額外儲存指標,**因此鏈結串列節點佔用的空間相對較大**。 綜上,我們不能簡單地確定哪種實現更加節省記憶體,需要針對具體情況進行分析。 ## 堆疊的典型應用 - **瀏覽器中的後退與前進、軟體中的撤銷與反撤銷**。每當我們開啟新的網頁,瀏覽器就會對上一個網頁執行入堆疊,這樣我們就可以通過後退操作回到上一個網頁。後退操作實際上是在執行出堆疊。如果要同時支持後退和前進,那麼需要兩個堆疊來配合實現。 - **程式記憶體管理**。每次呼叫函式時,系統都會在堆疊頂新增一個堆疊幀,用於記錄函式的上下文資訊。在遞迴函式中,向下遞推階段會不斷執行入堆疊操作,而向上回溯階段則會不斷執行出堆疊操作。 ================================================ FILE: zh-hant/docs/chapter_stack_and_queue/summary.md ================================================ # 小結 ### 重點回顧 - 堆疊是一種遵循先入後出原則的資料結構,可透過陣列或鏈結串列來實現。 - 在時間效率方面,堆疊的陣列實現具有較高的平均效率,但在擴容過程中,單次入堆疊操作的時間複雜度會劣化至 $O(n)$ 。相比之下,堆疊的鏈結串列實現具有更為穩定的效率表現。 - 在空間效率方面,堆疊的陣列實現可能導致一定程度的空間浪費。但需要注意的是,鏈結串列節點所佔用的記憶體空間比陣列元素更大。 - 佇列是一種遵循先入先出原則的資料結構,同樣可以透過陣列或鏈結串列來實現。在時間效率和空間效率的對比上,佇列的結論與前述堆疊的結論相似。 - 雙向佇列是一種具有更高自由度的佇列,它允許在兩端進行元素的新增和刪除操作。 ### Q & A **Q**:瀏覽器的前進後退是否是雙向鏈結串列實現? 瀏覽器的前進後退功能本質上是“堆疊”的體現。當用戶訪問一個新頁面時,該頁面會被新增到堆疊頂;當用戶點選後退按鈕時,該頁面會從堆疊頂彈出。使用雙向佇列可以方便地實現一些額外操作,這個在“雙向佇列”章節有提到。 **Q**:在出堆疊後,是否需要釋放出堆疊節點的記憶體? 如果後續仍需要使用彈出節點,則不需要釋放記憶體。若之後不需要用到,`Java` 和 `Python` 等語言擁有自動垃圾回收機制,因此不需要手動釋放記憶體;在 `C` 和 `C++` 中需要手動釋放記憶體。 **Q**:雙向佇列像是兩個堆疊拼接在了一起,它的用途是什麼? 雙向佇列就像是堆疊和佇列的組合或兩個堆疊拼在了一起。它表現的是堆疊 + 佇列的邏輯,因此可以實現堆疊與佇列的所有應用,並且更加靈活。 **Q**:撤銷(undo)和反撤銷(redo)具體是如何實現的? 使用兩個堆疊,堆疊 `A` 用於撤銷,堆疊 `B` 用於反撤銷。 1. 每當使用者執行一個操作,將這個操作壓入堆疊 `A` ,並清空堆疊 `B` 。 2. 當用戶執行“撤銷”時,從堆疊 `A` 中彈出最近的操作,並將其壓入堆疊 `B` 。 3. 當用戶執行“反撤銷”時,從堆疊 `B` 中彈出最近的操作,並將其壓入堆疊 `A` 。 ================================================ FILE: zh-hant/docs/chapter_tree/array_representation_of_tree.md ================================================ # 二元樹陣列表示 在鏈結串列表示下,二元樹的儲存單元為節點 `TreeNode` ,節點之間透過指標相連線。上一節介紹了鏈結串列表示下的二元樹的各項基本操作。 那麼,我們能否用陣列來表示二元樹呢?答案是肯定的。 ## 表示完美二元樹 先分析一個簡單案例。給定一棵完美二元樹,我們將所有節點按照層序走訪的順序儲存在一個陣列中,則每個節點都對應唯一的陣列索引。 根據層序走訪的特性,我們可以推導出父節點索引與子節點索引之間的“對映公式”:**若某節點的索引為 $i$ ,則該節點的左子節點索引為 $2i + 1$ ,右子節點索引為 $2i + 2$** 。下圖展示了各個節點索引之間的對映關係。 ![完美二元樹的陣列表示](array_representation_of_tree.assets/array_representation_binary_tree.png) **對映公式的角色相當於鏈結串列中的節點引用(指標)**。給定陣列中的任意一個節點,我們都可以透過對映公式來訪問它的左(右)子節點。 ## 表示任意二元樹 完美二元樹是一個特例,在二元樹的中間層通常存在許多 `None` 。由於層序走訪序列並不包含這些 `None` ,因此我們無法僅憑該序列來推測 `None` 的數量和分佈位置。**這意味著存在多種二元樹結構都符合該層序走訪序列**。 如下圖所示,給定一棵非完美二元樹,上述陣列表示方法已經失效。 ![層序走訪序列對應多種二元樹可能性](array_representation_of_tree.assets/array_representation_without_empty.png) 為了解決此問題,**我們可以考慮在層序走訪序列中顯式地寫出所有 `None`** 。如下圖所示,這樣處理後,層序走訪序列就可以唯一表示二元樹了。示例程式碼如下: === "Python" ```python title="" # 二元樹的陣列表示 # 使用 None 來表示空位 tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] ``` === "C++" ```cpp title="" /* 二元樹的陣列表示 */ // 使用 int 最大值 INT_MAX 標記空位 vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` === "Java" ```java title="" /* 二元樹的陣列表示 */ // 使用 int 的包裝類別 Integer ,就可以使用 null 來標記空位 Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; ``` === "C#" ```csharp title="" /* 二元樹的陣列表示 */ // 使用 int? 可空型別 ,就可以使用 null 來標記空位 int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Go" ```go title="" /* 二元樹的陣列表示 */ // 使用 any 型別的切片, 就可以使用 nil 來標記空位 tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} ``` === "Swift" ```swift title="" /* 二元樹的陣列表示 */ // 使用 Int? 可空型別 ,就可以使用 nil 來標記空位 let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` === "JS" ```javascript title="" /* 二元樹的陣列表示 */ // 使用 null 來表示空位 let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "TS" ```typescript title="" /* 二元樹的陣列表示 */ // 使用 null 來表示空位 let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Dart" ```dart title="" /* 二元樹的陣列表示 */ // 使用 int? 可空型別 ,就可以使用 null 來標記空位 List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Rust" ```rust title="" /* 二元樹的陣列表示 */ // 使用 None 來標記空位 let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)]; ``` === "C" ```c title="" /* 二元樹的陣列表示 */ // 使用 int 最大值標記空位,因此要求節點值不能為 INT_MAX int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` === "Kotlin" ```kotlin title="" /* 二元樹的陣列表示 */ // 使用 null 來表示空位 val tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) ``` === "Ruby" ```ruby title="" ### 二元樹的陣列表示 ### # 使用 nil 來表示空位 tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` ![任意型別二元樹的陣列表示](array_representation_of_tree.assets/array_representation_with_empty.png) 值得說明的是,**完全二元樹非常適合使用陣列來表示**。回顧完全二元樹的定義,`None` 只出現在最底層且靠右的位置,**因此所有 `None` 一定出現在層序走訪序列的末尾**。 這意味著使用陣列表示完全二元樹時,可以省略儲存所有 `None` ,非常方便。下圖給出了一個例子。 ![完全二元樹的陣列表示](array_representation_of_tree.assets/array_representation_complete_binary_tree.png) 以下程式碼實現了一棵基於陣列表示的二元樹,包括以下幾種操作。 - 給定某節點,獲取它的值、左(右)子節點、父節點。 - 獲取前序走訪、中序走訪、後序走訪、層序走訪序列。 ```src [file]{array_binary_tree}-[class]{array_binary_tree}-[func]{} ``` ## 優點與侷限性 二元樹的陣列表示主要有以下優點。 - 陣列儲存在連續的記憶體空間中,對快取友好,訪問與走訪速度較快。 - 不需要儲存指標,比較節省空間。 - 允許隨機訪問節點。 然而,陣列表示也存在一些侷限性。 - 陣列儲存需要連續記憶體空間,因此不適合儲存資料量過大的樹。 - 增刪節點需要透過陣列插入與刪除操作實現,效率較低。 - 當二元樹中存在大量 `None` 時,陣列中包含的節點資料比重較低,空間利用率較低。 ================================================ FILE: zh-hant/docs/chapter_tree/avl_tree.md ================================================ # AVL 樹 * 在“二元搜尋樹”章節中我們提到,在多次插入和刪除操作後,二元搜尋樹可能退化為鏈結串列。在這種情況下,所有操作的時間複雜度將從 $O(\log n)$ 劣化為 $O(n)$ 。 如下圖所示,經過兩次刪除節點操作,這棵二元搜尋樹便會退化為鏈結串列。 ![AVL 樹在刪除節點後發生退化](avl_tree.assets/avltree_degradation_from_removing_node.png) 再例如,在下圖所示的完美二元樹中插入兩個節點後,樹將嚴重向左傾斜,查詢操作的時間複雜度也隨之劣化。 ![AVL 樹在插入節點後發生退化](avl_tree.assets/avltree_degradation_from_inserting_node.png) 1962 年 G. M. Adelson-Velsky 和 E. M. Landis 在論文“An algorithm for the organization of information”中提出了 AVL 樹。論文中詳細描述了一系列操作,確保在持續新增和刪除節點後,AVL 樹不會退化,從而使得各種操作的時間複雜度保持在 $O(\log n)$ 級別。換句話說,在需要頻繁進行增刪查改操作的場景中,AVL 樹能始終保持高效的資料操作效能,具有很好的應用價值。 ## AVL 樹常見術語 AVL 樹既是二元搜尋樹,也是平衡二元樹,同時滿足這兩類二元樹的所有性質,因此是一種平衡二元搜尋樹(balanced binary search tree)。 ### 節點高度 由於 AVL 樹的相關操作需要獲取節點高度,因此我們需要為節點類別新增 `height` 變數: === "Python" ```python title="" class TreeNode: """AVL 樹節點類別""" def __init__(self, val: int): self.val: int = val # 節點值 self.height: int = 0 # 節點高度 self.left: TreeNode | None = None # 左子節點引用 self.right: TreeNode | None = None # 右子節點引用 ``` === "C++" ```cpp title="" /* AVL 樹節點類別 */ struct TreeNode { int val{}; // 節點值 int height = 0; // 節點高度 TreeNode *left{}; // 左子節點 TreeNode *right{}; // 右子節點 TreeNode() = default; explicit TreeNode(int x) : val(x){} }; ``` === "Java" ```java title="" /* AVL 樹節點類別 */ class TreeNode { public int val; // 節點值 public int height; // 節點高度 public TreeNode left; // 左子節點 public TreeNode right; // 右子節點 public TreeNode(int x) { val = x; } } ``` === "C#" ```csharp title="" /* AVL 樹節點類別 */ class TreeNode(int? x) { public int? val = x; // 節點值 public int height; // 節點高度 public TreeNode? left; // 左子節點引用 public TreeNode? right; // 右子節點引用 } ``` === "Go" ```go title="" /* AVL 樹節點結構體 */ type TreeNode struct { Val int // 節點值 Height int // 節點高度 Left *TreeNode // 左子節點引用 Right *TreeNode // 右子節點引用 } ``` === "Swift" ```swift title="" /* AVL 樹節點類別 */ class TreeNode { var val: Int // 節點值 var height: Int // 節點高度 var left: TreeNode? // 左子節點 var right: TreeNode? // 右子節點 init(x: Int) { val = x height = 0 } } ``` === "JS" ```javascript title="" /* AVL 樹節點類別 */ class TreeNode { val; // 節點值 height; //節點高度 left; // 左子節點指標 right; // 右子節點指標 constructor(val, left, right, height) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } ``` === "TS" ```typescript title="" /* AVL 樹節點類別 */ class TreeNode { val: number; // 節點值 height: number; // 節點高度 left: TreeNode | null; // 左子節點指標 right: TreeNode | null; // 右子節點指標 constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } ``` === "Dart" ```dart title="" /* AVL 樹節點類別 */ class TreeNode { int val; // 節點值 int height; // 節點高度 TreeNode? left; // 左子節點 TreeNode? right; // 右子節點 TreeNode(this.val, [this.height = 0, this.left, this.right]); } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* AVL 樹節點結構體 */ struct TreeNode { val: i32, // 節點值 height: i32, // 節點高度 left: Option>>, // 左子節點 right: Option>>, // 右子節點 } impl TreeNode { /* 建構子 */ fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, height: 0, left: None, right: None })) } } ``` === "C" ```c title="" /* AVL 樹節點結構體 */ typedef struct TreeNode { int val; int height; struct TreeNode *left; struct TreeNode *right; } TreeNode; /* 建構子 */ TreeNode *newTreeNode(int val) { TreeNode *node; node = (TreeNode *)malloc(sizeof(TreeNode)); node->val = val; node->height = 0; node->left = NULL; node->right = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* AVL 樹節點類別 */ class TreeNode(val _val: Int) { // 節點值 val height: Int = 0 // 節點高度 val left: TreeNode? = null // 左子節點 val right: TreeNode? = null // 右子節點 } ``` === "Ruby" ```ruby title="" ### AVL 樹節點類別 ### class TreeNode attr_accessor :val # 節點值 attr_accessor :height # 節點高度 attr_accessor :left # 左子節點引用 attr_accessor :right # 右子節點引用 def initialize(val) @val = val @height = 0 end end ``` “節點高度”是指從該節點到它的最遠葉節點的距離,即所經過的“邊”的數量。需要特別注意的是,葉節點的高度為 $0$ ,而空節點的高度為 $-1$ 。我們將建立兩個工具函式,分別用於獲取和更新節點的高度: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{update_height} ``` ### 節點平衡因子 節點的平衡因子(balance factor)定義為節點左子樹的高度減去右子樹的高度,同時規定空節點的平衡因子為 $0$ 。我們同樣將獲取節點平衡因子的功能封裝成函式,方便後續使用: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{balance_factor} ``` !!! tip 設平衡因子為 $f$ ,則一棵 AVL 樹的任意節點的平衡因子皆滿足 $-1 \le f \le 1$ 。 ## AVL 樹旋轉 AVL 樹的特點在於“旋轉”操作,它能夠在不影響二元樹的中序走訪序列的前提下,使失衡節點重新恢復平衡。換句話說,**旋轉操作既能保持“二元搜尋樹”的性質,也能使樹重新變為“平衡二元樹”**。 我們將平衡因子絕對值 $> 1$ 的節點稱為“失衡節點”。根據節點失衡情況的不同,旋轉操作分為四種:右旋、左旋、先右旋後左旋、先左旋後右旋。下面詳細介紹這些旋轉操作。 ### 右旋 如下圖所示,節點下方為平衡因子。從底至頂看,二元樹中首個失衡節點是“節點 3”。我們關注以該失衡節點為根節點的子樹,將該節點記為 `node` ,其左子節點記為 `child` ,執行“右旋”操作。完成右旋後,子樹恢復平衡,並且仍然保持二元搜尋樹的性質。 === "<1>" ![右旋操作步驟](avl_tree.assets/avltree_right_rotate_step1.png) === "<2>" ![avltree_right_rotate_step2](avl_tree.assets/avltree_right_rotate_step2.png) === "<3>" ![avltree_right_rotate_step3](avl_tree.assets/avltree_right_rotate_step3.png) === "<4>" ![avltree_right_rotate_step4](avl_tree.assets/avltree_right_rotate_step4.png) 如下圖所示,當節點 `child` 有右子節點(記為 `grand_child` )時,需要在右旋中新增一步:將 `grand_child` 作為 `node` 的左子節點。 ![有 grand_child 的右旋操作](avl_tree.assets/avltree_right_rotate_with_grandchild.png) “向右旋轉”是一種形象化的說法,實際上需要透過修改節點指標來實現,程式碼如下所示: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{right_rotate} ``` ### 左旋 相應地,如果考慮上述失衡二元樹的“映象”,則需要執行下圖所示的“左旋”操作。 ![左旋操作](avl_tree.assets/avltree_left_rotate.png) 同理,如下圖所示,當節點 `child` 有左子節點(記為 `grand_child` )時,需要在左旋中新增一步:將 `grand_child` 作為 `node` 的右子節點。 ![有 grand_child 的左旋操作](avl_tree.assets/avltree_left_rotate_with_grandchild.png) 可以觀察到,**右旋和左旋操作在邏輯上是映象對稱的,它們分別解決的兩種失衡情況也是對稱的**。基於對稱性,我們只需將右旋的實現程式碼中的所有的 `left` 替換為 `right` ,將所有的 `right` 替換為 `left` ,即可得到左旋的實現程式碼: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{left_rotate} ``` ### 先左旋後右旋 對於下圖中的失衡節點 3 ,僅使用左旋或右旋都無法使子樹恢復平衡。此時需要先對 `child` 執行“左旋”,再對 `node` 執行“右旋”。 ![先左旋後右旋](avl_tree.assets/avltree_left_right_rotate.png) ### 先右旋後左旋 如下圖所示,對於上述失衡二元樹的映象情況,需要先對 `child` 執行“右旋”,再對 `node` 執行“左旋”。 ![先右旋後左旋](avl_tree.assets/avltree_right_left_rotate.png) ### 旋轉的選擇 下圖展示的四種失衡情況與上述案例逐個對應,分別需要採用右旋、先左旋後右旋、先右旋後左旋、左旋的操作。 ![AVL 樹的四種旋轉情況](avl_tree.assets/avltree_rotation_cases.png) 如下表所示,我們透過判斷失衡節點的平衡因子以及較高一側子節點的平衡因子的正負號,來確定失衡節點屬於上圖中的哪種情況。

  四種旋轉情況的選擇條件

| 失衡節點的平衡因子 | 子節點的平衡因子 | 應採用的旋轉方法 | | ------------------ | ---------------- | ---------------- | | $> 1$ (左偏樹) | $\geq 0$ | 右旋 | | $> 1$ (左偏樹) | $<0$ | 先左旋後右旋 | | $< -1$ (右偏樹) | $\leq 0$ | 左旋 | | $< -1$ (右偏樹) | $>0$ | 先右旋後左旋 | 為了便於使用,我們將旋轉操作封裝成一個函式。**有了這個函式,我們就能對各種失衡情況進行旋轉,使失衡節點重新恢復平衡**。程式碼如下所示: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{rotate} ``` ## AVL 樹常用操作 ### 插入節點 AVL 樹的節點插入操作與二元搜尋樹在主體上類似。唯一的區別在於,在 AVL 樹中插入節點後,從該節點到根節點的路徑上可能會出現一系列失衡節點。因此,**我們需要從這個節點開始,自底向上執行旋轉操作,使所有失衡節點恢復平衡**。程式碼如下所示: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{insert_helper} ``` ### 刪除節點 類似地,在二元搜尋樹的刪除節點方法的基礎上,需要從底至頂執行旋轉操作,使所有失衡節點恢復平衡。程式碼如下所示: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{remove_helper} ``` ### 查詢節點 AVL 樹的節點查詢操作與二元搜尋樹一致,在此不再贅述。 ## AVL 樹典型應用 - 組織和儲存大型資料,適用於高頻查詢、低頻增刪的場景。 - 用於構建資料庫中的索引系統。 - 紅黑樹也是一種常見的平衡二元搜尋樹。相較於 AVL 樹,紅黑樹的平衡條件更寬鬆,插入與刪除節點所需的旋轉操作更少,節點增刪操作的平均效率更高。 ================================================ FILE: zh-hant/docs/chapter_tree/binary_search_tree.md ================================================ # 二元搜尋樹 如下圖所示,二元搜尋樹(binary search tree)滿足以下條件。 1. 對於根節點,左子樹中所有節點的值 $<$ 根節點的值 $<$ 右子樹中所有節點的值。 2. 任意節點的左、右子樹也是二元搜尋樹,即同樣滿足條件 `1.` 。 ![二元搜尋樹](binary_search_tree.assets/binary_search_tree.png) ## 二元搜尋樹的操作 我們將二元搜尋樹封裝為一個類別 `BinarySearchTree` ,並宣告一個成員變數 `root` ,指向樹的根節點。 ### 查詢節點 給定目標節點值 `num` ,可以根據二元搜尋樹的性質來查詢。如下圖所示,我們宣告一個節點 `cur` ,從二元樹的根節點 `root` 出發,迴圈比較節點值 `cur.val` 和 `num` 之間的大小關係。 - 若 `cur.val < num` ,說明目標節點在 `cur` 的右子樹中,因此執行 `cur = cur.right` 。 - 若 `cur.val > num` ,說明目標節點在 `cur` 的左子樹中,因此執行 `cur = cur.left` 。 - 若 `cur.val = num` ,說明找到目標節點,跳出迴圈並返回該節點。 === "<1>" ![二元搜尋樹查詢節點示例](binary_search_tree.assets/bst_search_step1.png) === "<2>" ![bst_search_step2](binary_search_tree.assets/bst_search_step2.png) === "<3>" ![bst_search_step3](binary_search_tree.assets/bst_search_step3.png) === "<4>" ![bst_search_step4](binary_search_tree.assets/bst_search_step4.png) 二元搜尋樹的查詢操作與二分搜尋演算法的工作原理一致,都是每輪排除一半情況。迴圈次數最多為二元樹的高度,當二元樹平衡時,使用 $O(\log n)$ 時間。示例程式碼如下: ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{search} ``` ### 插入節點 給定一個待插入元素 `num` ,為了保持二元搜尋樹“左子樹 < 根節點 < 右子樹”的性質,插入操作流程如下圖所示。 1. **查詢插入位置**:與查詢操作相似,從根節點出發,根據當前節點值和 `num` 的大小關係迴圈向下搜尋,直到越過葉節點(走訪至 `None` )時跳出迴圈。 2. **在該位置插入節點**:初始化節點 `num` ,將該節點置於 `None` 的位置。 ![在二元搜尋樹中插入節點](binary_search_tree.assets/bst_insert.png) 在程式碼實現中,需要注意以下兩點。 - 二元搜尋樹不允許存在重複節點,否則將違反其定義。因此,若待插入節點在樹中已存在,則不執行插入,直接返回。 - 為了實現插入節點,我們需要藉助節點 `pre` 儲存上一輪迴圈的節點。這樣在走訪至 `None` 時,我們可以獲取到其父節點,從而完成節點插入操作。 ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{insert} ``` 與查詢節點相同,插入節點使用 $O(\log n)$ 時間。 ### 刪除節點 先在二元樹中查詢到目標節點,再將其刪除。與插入節點類似,我們需要保證在刪除操作完成後,二元搜尋樹的“左子樹 < 根節點 < 右子樹”的性質仍然滿足。因此,我們根據目標節點的子節點數量,分 0、1 和 2 三種情況,執行對應的刪除節點操作。 如下圖所示,當待刪除節點的度為 $0$ 時,表示該節點是葉節點,可以直接刪除。 ![在二元搜尋樹中刪除節點(度為 0 )](binary_search_tree.assets/bst_remove_case1.png) 如下圖所示,當待刪除節點的度為 $1$ 時,將待刪除節點替換為其子節點即可。 ![在二元搜尋樹中刪除節點(度為 1 )](binary_search_tree.assets/bst_remove_case2.png) 當待刪除節點的度為 $2$ 時,我們無法直接刪除它,而需要使用一個節點替換該節點。由於要保持二元搜尋樹“左子樹 $<$ 根節點 $<$ 右子樹”的性質,**因此這個節點可以是右子樹的最小節點或左子樹的最大節點**。 假設我們選擇右子樹的最小節點(中序走訪的下一個節點),則刪除操作流程如下圖所示。 1. 找到待刪除節點在“中序走訪序列”中的下一個節點,記為 `tmp` 。 2. 用 `tmp` 的值覆蓋待刪除節點的值,並在樹中遞迴刪除節點 `tmp` 。 === "<1>" ![在二元搜尋樹中刪除節點(度為 2 )](binary_search_tree.assets/bst_remove_case3_step1.png) === "<2>" ![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png) === "<3>" ![bst_remove_case3_step3](binary_search_tree.assets/bst_remove_case3_step3.png) === "<4>" ![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png) 刪除節點操作同樣使用 $O(\log n)$ 時間,其中查詢待刪除節點需要 $O(\log n)$ 時間,獲取中序走訪後繼節點需要 $O(\log n)$ 時間。示例程式碼如下: ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{remove} ``` ### 中序走訪有序 如下圖所示,二元樹的中序走訪遵循“左 $\rightarrow$ 根 $\rightarrow$ 右”的走訪順序,而二元搜尋樹滿足“左子節點 $<$ 根節點 $<$ 右子節點”的大小關係。 這意味著在二元搜尋樹中進行中序走訪時,總是會優先走訪下一個最小節點,從而得出一個重要性質:**二元搜尋樹的中序走訪序列是升序的**。 利用中序走訪升序的性質,我們在二元搜尋樹中獲取有序資料僅需 $O(n)$ 時間,無須進行額外的排序操作,非常高效。 ![二元搜尋樹的中序走訪序列](binary_search_tree.assets/bst_inorder_traversal.png) ## 二元搜尋樹的效率 給定一組資料,我們考慮使用陣列或二元搜尋樹儲存。觀察下表,二元搜尋樹的各項操作的時間複雜度都是對數階,具有穩定且高效的效能。只有在高頻新增、低頻查詢刪除資料的場景下,陣列比二元搜尋樹的效率更高。

  陣列與搜尋樹的效率對比

| | 無序陣列 | 二元搜尋樹 | | -------- | -------- | ----------- | | 查詢元素 | $O(n)$ | $O(\log n)$ | | 插入元素 | $O(1)$ | $O(\log n)$ | | 刪除元素 | $O(n)$ | $O(\log n)$ | 在理想情況下,二元搜尋樹是“平衡”的,這樣就可以在 $\log n$ 輪迴圈內查詢任意節點。 然而,如果我們在二元搜尋樹中不斷地插入和刪除節點,可能導致二元樹退化為下圖所示的鏈結串列,這時各種操作的時間複雜度也會退化為 $O(n)$ 。 ![二元搜尋樹退化](binary_search_tree.assets/bst_degradation.png) ## 二元搜尋樹常見應用 - 用作系統中的多級索引,實現高效的查詢、插入、刪除操作。 - 作為某些搜尋演算法的底層資料結構。 - 用於儲存資料流,以保持其有序狀態。 ================================================ FILE: zh-hant/docs/chapter_tree/binary_tree.md ================================================ # 二元樹 二元樹(binary tree)是一種非線性資料結構,代表“祖先”與“後代”之間的派生關係,體現了“一分為二”的分治邏輯。與鏈結串列類似,二元樹的基本單元是節點,每個節點包含值、左子節點引用和右子節點引用。 === "Python" ```python title="" class TreeNode: """二元樹節點類別""" def __init__(self, val: int): self.val: int = val # 節點值 self.left: TreeNode | None = None # 左子節點引用 self.right: TreeNode | None = None # 右子節點引用 ``` === "C++" ```cpp title="" /* 二元樹節點結構體 */ struct TreeNode { int val; // 節點值 TreeNode *left; // 左子節點指標 TreeNode *right; // 右子節點指標 TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; ``` === "Java" ```java title="" /* 二元樹節點類別 */ class TreeNode { int val; // 節點值 TreeNode left; // 左子節點引用 TreeNode right; // 右子節點引用 TreeNode(int x) { val = x; } } ``` === "C#" ```csharp title="" /* 二元樹節點類別 */ class TreeNode(int? x) { public int? val = x; // 節點值 public TreeNode? left; // 左子節點引用 public TreeNode? right; // 右子節點引用 } ``` === "Go" ```go title="" /* 二元樹節點結構體 */ type TreeNode struct { Val int Left *TreeNode Right *TreeNode } /* 建構子 */ func NewTreeNode(v int) *TreeNode { return &TreeNode{ Left: nil, // 左子節點指標 Right: nil, // 右子節點指標 Val: v, // 節點值 } } ``` === "Swift" ```swift title="" /* 二元樹節點類別 */ class TreeNode { var val: Int // 節點值 var left: TreeNode? // 左子節點引用 var right: TreeNode? // 右子節點引用 init(x: Int) { val = x } } ``` === "JS" ```javascript title="" /* 二元樹節點類別 */ class TreeNode { val; // 節點值 left; // 左子節點指標 right; // 右子節點指標 constructor(val, left, right) { this.val = val === undefined ? 0 : val; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; } } ``` === "TS" ```typescript title="" /* 二元樹節點類別 */ class TreeNode { val: number; left: TreeNode | null; right: TreeNode | null; constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { this.val = val === undefined ? 0 : val; // 節點值 this.left = left === undefined ? null : left; // 左子節點引用 this.right = right === undefined ? null : right; // 右子節點引用 } } ``` === "Dart" ```dart title="" /* 二元樹節點類別 */ class TreeNode { int val; // 節點值 TreeNode? left; // 左子節點引用 TreeNode? right; // 右子節點引用 TreeNode(this.val, [this.left, this.right]); } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* 二元樹節點結構體 */ struct TreeNode { val: i32, // 節點值 left: Option>>, // 左子節點引用 right: Option>>, // 右子節點引用 } impl TreeNode { /* 建構子 */ fn new(val: i32) -> Rc> { Rc::new(RefCell::new(Self { val, left: None, right: None })) } } ``` === "C" ```c title="" /* 二元樹節點結構體 */ typedef struct TreeNode { int val; // 節點值 int height; // 節點高度 struct TreeNode *left; // 左子節點指標 struct TreeNode *right; // 右子節點指標 } TreeNode; /* 建構子 */ TreeNode *newTreeNode(int val) { TreeNode *node; node = (TreeNode *)malloc(sizeof(TreeNode)); node->val = val; node->height = 0; node->left = NULL; node->right = NULL; return node; } ``` === "Kotlin" ```kotlin title="" /* 二元樹節點類別 */ class TreeNode(val _val: Int) { // 節點值 val left: TreeNode? = null // 左子節點引用 val right: TreeNode? = null // 右子節點引用 } ``` === "Ruby" ```ruby title="" ### 二元樹節點類別 ### class TreeNode attr_accessor :val # 節點值 attr_accessor :left # 左子節點引用 attr_accessor :right # 右子節點引用 def initialize(val) @val = val end end ``` 每個節點都有兩個引用(指標),分別指向左子節點(left-child node)右子節點(right-child node),該節點被稱為這兩個子節點的父節點(parent node)。當給定一個二元樹的節點時,我們將該節點的左子節點及其以下節點形成的樹稱為該節點的左子樹(left subtree),同理可得右子樹(right subtree)。 **在二元樹中,除葉節點外,其他所有節點都包含子節點和非空子樹**。如下圖所示,如果將“節點 2”視為父節點,則其左子節點和右子節點分別是“節點 4”和“節點 5”,左子樹是“節點 4 及其以下節點形成的樹”,右子樹是“節點 5 及其以下節點形成的樹”。 ![父節點、子節點、子樹](binary_tree.assets/binary_tree_definition.png) ## 二元樹常見術語 二元樹的常用術語如下圖所示。 - 根節點(root node):位於二元樹頂層的節點,沒有父節點。 - 葉節點(leaf node):沒有子節點的節點,其兩個指標均指向 `None` 。 - 邊(edge):連線兩個節點的線段,即節點引用(指標)。 - 節點所在的層(level):從頂至底遞增,根節點所在層為 1 。 - 節點的度(degree):節點的子節點的數量。在二元樹中,度的取值範圍是 0、1、2 。 - 二元樹的高度(height):從根節點到最遠葉節點所經過的邊的數量。 - 節點的深度(depth):從根節點到該節點所經過的邊的數量。 - 節點的高度(height):從距離該節點最遠的葉節點到該節點所經過的邊的數量。 ![二元樹的常用術語](binary_tree.assets/binary_tree_terminology.png) !!! tip 請注意,我們通常將“高度”和“深度”定義為“經過的邊的數量”,但有些題目或教材可能會將其定義為“經過的節點的數量”。在這種情況下,高度和深度都需要加 1 。 ## 二元樹基本操作 ### 初始化二元樹 與鏈結串列類似,首先初始化節點,然後構建引用(指標)。 === "Python" ```python title="binary_tree.py" # 初始化二元樹 # 初始化節點 n1 = TreeNode(val=1) n2 = TreeNode(val=2) n3 = TreeNode(val=3) n4 = TreeNode(val=4) n5 = TreeNode(val=5) # 構建節點之間的引用(指標) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` === "C++" ```cpp title="binary_tree.cpp" /* 初始化二元樹 */ // 初始化節點 TreeNode* n1 = new TreeNode(1); TreeNode* n2 = new TreeNode(2); TreeNode* n3 = new TreeNode(3); TreeNode* n4 = new TreeNode(4); TreeNode* n5 = new TreeNode(5); // 構建節點之間的引用(指標) n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; ``` === "Java" ```java title="binary_tree.java" // 初始化節點 TreeNode n1 = new TreeNode(1); TreeNode n2 = new TreeNode(2); TreeNode n3 = new TreeNode(3); TreeNode n4 = new TreeNode(4); TreeNode n5 = new TreeNode(5); // 構建節點之間的引用(指標) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "C#" ```csharp title="binary_tree.cs" /* 初始化二元樹 */ // 初始化節點 TreeNode n1 = new(1); TreeNode n2 = new(2); TreeNode n3 = new(3); TreeNode n4 = new(4); TreeNode n5 = new(5); // 構建節點之間的引用(指標) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "Go" ```go title="binary_tree.go" /* 初始化二元樹 */ // 初始化節點 n1 := NewTreeNode(1) n2 := NewTreeNode(2) n3 := NewTreeNode(3) n4 := NewTreeNode(4) n5 := NewTreeNode(5) // 構建節點之間的引用(指標) n1.Left = n2 n1.Right = n3 n2.Left = n4 n2.Right = n5 ``` === "Swift" ```swift title="binary_tree.swift" // 初始化節點 let n1 = TreeNode(x: 1) let n2 = TreeNode(x: 2) let n3 = TreeNode(x: 3) let n4 = TreeNode(x: 4) let n5 = TreeNode(x: 5) // 構建節點之間的引用(指標) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` === "JS" ```javascript title="binary_tree.js" /* 初始化二元樹 */ // 初始化節點 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // 構建節點之間的引用(指標) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "TS" ```typescript title="binary_tree.ts" /* 初始化二元樹 */ // 初始化節點 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); // 構建節點之間的引用(指標) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "Dart" ```dart title="binary_tree.dart" /* 初始化二元樹 */ // 初始化節點 TreeNode n1 = new TreeNode(1); TreeNode n2 = new TreeNode(2); TreeNode n3 = new TreeNode(3); TreeNode n4 = new TreeNode(4); TreeNode n5 = new TreeNode(5); // 構建節點之間的引用(指標) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` === "Rust" ```rust title="binary_tree.rs" // 初始化節點 let n1 = TreeNode::new(1); let n2 = TreeNode::new(2); let n3 = TreeNode::new(3); let n4 = TreeNode::new(4); let n5 = TreeNode::new(5); // 構建節點之間的引用(指標) n1.borrow_mut().left = Some(n2.clone()); n1.borrow_mut().right = Some(n3); n2.borrow_mut().left = Some(n4); n2.borrow_mut().right = Some(n5); ``` === "C" ```c title="binary_tree.c" /* 初始化二元樹 */ // 初始化節點 TreeNode *n1 = newTreeNode(1); TreeNode *n2 = newTreeNode(2); TreeNode *n3 = newTreeNode(3); TreeNode *n4 = newTreeNode(4); TreeNode *n5 = newTreeNode(5); // 構建節點之間的引用(指標) n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; ``` === "Kotlin" ```kotlin title="binary_tree.kt" // 初始化節點 val n1 = TreeNode(1) val n2 = TreeNode(2) val n3 = TreeNode(3) val n4 = TreeNode(4) val n5 = TreeNode(5) // 構建節點之間的引用(指標) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` === "Ruby" ```ruby title="binary_tree.rb" # 初始化二元樹 # 初始化節點 n1 = TreeNode.new(1) n2 = TreeNode.new(2) n3 = TreeNode.new(3) n4 = TreeNode.new(4) n5 = TreeNode.new(5) # 構建節點之間的引用(指標) n1.left = n2 n1.right = n3 n2.left = n4 n2.right = n5 ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%A8%B9%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%AF%80%E9%BB%9E%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E6%A8%99%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ### 插入與刪除節點 與鏈結串列類似,在二元樹中插入與刪除節點可以透過修改指標來實現。下圖給出了一個示例。 ![在二元樹中插入與刪除節點](binary_tree.assets/binary_tree_add_remove.png) === "Python" ```python title="binary_tree.py" # 插入與刪除節點 p = TreeNode(0) # 在 n1 -> n2 中間插入節點 P n1.left = p p.left = n2 # 刪除節點 P n1.left = n2 ``` === "C++" ```cpp title="binary_tree.cpp" /* 插入與刪除節點 */ TreeNode* P = new TreeNode(0); // 在 n1 -> n2 中間插入節點 P n1->left = P; P->left = n2; // 刪除節點 P n1->left = n2; // 釋放記憶體 delete P; ``` === "Java" ```java title="binary_tree.java" TreeNode P = new TreeNode(0); // 在 n1 -> n2 中間插入節點 P n1.left = P; P.left = n2; // 刪除節點 P n1.left = n2; ``` === "C#" ```csharp title="binary_tree.cs" /* 插入與刪除節點 */ TreeNode P = new(0); // 在 n1 -> n2 中間插入節點 P n1.left = P; P.left = n2; // 刪除節點 P n1.left = n2; ``` === "Go" ```go title="binary_tree.go" /* 插入與刪除節點 */ // 在 n1 -> n2 中間插入節點 P p := NewTreeNode(0) n1.Left = p p.Left = n2 // 刪除節點 P n1.Left = n2 ``` === "Swift" ```swift title="binary_tree.swift" let P = TreeNode(x: 0) // 在 n1 -> n2 中間插入節點 P n1.left = P P.left = n2 // 刪除節點 P n1.left = n2 ``` === "JS" ```javascript title="binary_tree.js" /* 插入與刪除節點 */ let P = new TreeNode(0); // 在 n1 -> n2 中間插入節點 P n1.left = P; P.left = n2; // 刪除節點 P n1.left = n2; ``` === "TS" ```typescript title="binary_tree.ts" /* 插入與刪除節點 */ const P = new TreeNode(0); // 在 n1 -> n2 中間插入節點 P n1.left = P; P.left = n2; // 刪除節點 P n1.left = n2; ``` === "Dart" ```dart title="binary_tree.dart" /* 插入與刪除節點 */ TreeNode P = new TreeNode(0); // 在 n1 -> n2 中間插入節點 P n1.left = P; P.left = n2; // 刪除節點 P n1.left = n2; ``` === "Rust" ```rust title="binary_tree.rs" let p = TreeNode::new(0); // 在 n1 -> n2 中間插入節點 P n1.borrow_mut().left = Some(p.clone()); p.borrow_mut().left = Some(n2.clone()); // 刪除節點 p n1.borrow_mut().left = Some(n2); ``` === "C" ```c title="binary_tree.c" /* 插入與刪除節點 */ TreeNode *P = newTreeNode(0); // 在 n1 -> n2 中間插入節點 P n1->left = P; P->left = n2; // 刪除節點 P n1->left = n2; // 釋放記憶體 free(P); ``` === "Kotlin" ```kotlin title="binary_tree.kt" val P = TreeNode(0) // 在 n1 -> n2 中間插入節點 P n1.left = P P.left = n2 // 刪除節點 P n1.left = n2 ``` === "Ruby" ```ruby title="binary_tree.rb" # 插入與刪除節點 _p = TreeNode.new(0) # 在 n1 -> n2 中間插入節點 _p n1.left = _p _p.left = n2 # 刪除節點 n1.left = n2 ``` ??? pythontutor "視覺化執行" https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%A8%B9%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%AF%80%E9%BB%9E%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E6%A8%99%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%88%87%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%E5%9C%A8%20n1%20-%3E%20n2%20%E4%B8%AD%E9%96%93%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%20P%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%20P%0A%20%20%20%20n1.left%20%3D%20n2&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false !!! tip 需要注意的是,插入節點可能會改變二元樹的原有邏輯結構,而刪除節點通常意味著刪除該節點及其所有子樹。因此,在二元樹中,插入與刪除通常是由一套操作配合完成的,以實現有實際意義的操作。 ## 常見二元樹型別 ### 完美二元樹 如下圖所示,完美二元樹(perfect binary tree)所有層的節點都被完全填滿。在完美二元樹中,葉節點的度為 $0$ ,其餘所有節點的度都為 $2$ ;若樹的高度為 $h$ ,則節點總數為 $2^{h+1} - 1$ ,呈現標準的指數級關係,反映了自然界中常見的細胞分裂現象。 !!! tip 請注意,在中文社群中,完美二元樹常被稱為滿二元樹。 ![完美二元樹](binary_tree.assets/perfect_binary_tree.png) ### 完全二元樹 如下圖所示,完全二元樹(complete binary tree)僅允許最底層的節點不完全填滿,且最底層的節點必須從左至右依次連續填充。請注意,完美二元樹也是一棵完全二元樹。 ![完全二元樹](binary_tree.assets/complete_binary_tree.png) ### 完滿二元樹 如下圖所示,完滿二元樹(full binary tree)除了葉節點之外,其餘所有節點都有兩個子節點。 ![完滿二元樹](binary_tree.assets/full_binary_tree.png) ### 平衡二元樹 如下圖所示,平衡二元樹(balanced binary tree)中任意節點的左子樹和右子樹的高度之差的絕對值不超過 1 。 ![平衡二元樹](binary_tree.assets/balanced_binary_tree.png) ## 二元樹的退化 下圖展示了二元樹的理想結構與退化結構。當二元樹的每層節點都被填滿時,達到“完美二元樹”;而當所有節點都偏向一側時,二元樹退化為“鏈結串列”。 - 完美二元樹是理想情況,可以充分發揮二元樹“分治”的優勢。 - 鏈結串列則是另一個極端,各項操作都變為線性操作,時間複雜度退化至 $O(n)$ 。 ![二元樹的最佳結構與最差結構](binary_tree.assets/binary_tree_best_worst_cases.png) 如下表所示,在最佳結構和最差結構下,二元樹的葉節點數量、節點總數、高度等達到極大值或極小值。

  二元樹的最佳結構與最差結構

| | 完美二元樹 | 鏈結串列 | | --------------------------- | ------------------ | ------- | | 第 $i$ 層的節點數量 | $2^{i-1}$ | $1$ | | 高度為 $h$ 的樹的葉節點數量 | $2^h$ | $1$ | | 高度為 $h$ 的樹的節點總數 | $2^{h+1} - 1$ | $h + 1$ | | 節點總數為 $n$ 的樹的高度 | $\log_2 (n+1) - 1$ | $n - 1$ | ================================================ FILE: zh-hant/docs/chapter_tree/binary_tree_traversal.md ================================================ # 二元樹走訪 從物理結構的角度來看,樹是一種基於鏈結串列的資料結構,因此其走訪方式是透過指標逐個訪問節點。然而,樹是一種非線性資料結構,這使得走訪樹比走訪鏈結串列更加複雜,需要藉助搜尋演算法來實現。 二元樹常見的走訪方式包括層序走訪、前序走訪、中序走訪和後序走訪等。 ## 層序走訪 如下圖所示,層序走訪(level-order traversal)從頂部到底部逐層走訪二元樹,並在每一層按照從左到右的順序訪問節點。 層序走訪本質上屬於廣度優先走訪(breadth-first traversal),也稱廣度優先搜尋(breadth-first search, BFS),它體現了一種“一圈一圈向外擴展”的逐層走訪方式。 ![二元樹的層序走訪](binary_tree_traversal.assets/binary_tree_bfs.png) ### 程式碼實現 廣度優先走訪通常藉助“佇列”來實現。佇列遵循“先進先出”的規則,而廣度優先走訪則遵循“逐層推進”的規則,兩者背後的思想是一致的。實現程式碼如下: ```src [file]{binary_tree_bfs}-[class]{}-[func]{level_order} ``` ### 複雜度分析 - **時間複雜度為 $O(n)$** :所有節點被訪問一次,使用 $O(n)$ 時間,其中 $n$ 為節點數量。 - **空間複雜度為 $O(n)$** :在最差情況下,即滿二元樹時,走訪到最底層之前,佇列中最多同時存在 $(n + 1) / 2$ 個節點,佔用 $O(n)$ 空間。 ## 前序、中序、後序走訪 相應地,前序、中序和後序走訪都屬於深度優先走訪(depth-first traversal),也稱深度優先搜尋(depth-first search, DFS),它體現了一種“先走到盡頭,再回溯繼續”的走訪方式。 下圖展示了對二元樹進行深度優先走訪的工作原理。**深度優先走訪就像是繞著整棵二元樹的外圍“走”一圈**,在每個節點都會遇到三個位置,分別對應前序走訪、中序走訪和後序走訪。 ![二元搜尋樹的前序、中序、後序走訪](binary_tree_traversal.assets/binary_tree_dfs.png) ### 程式碼實現 深度優先搜尋通常基於遞迴實現: ```src [file]{binary_tree_dfs}-[class]{}-[func]{post_order} ``` !!! tip 深度優先搜尋也可以基於迭代實現,有興趣的讀者可以自行研究。 下圖展示了前序走訪二元樹的遞迴過程,其可分為“遞”和“迴”兩個逆向的部分。 1. “遞”表示開啟新方法,程式在此過程中訪問下一個節點。 2. “迴”表示函式返回,代表當前節點已經訪問完畢。 === "<1>" ![前序走訪的遞迴過程](binary_tree_traversal.assets/preorder_step1.png) === "<2>" ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png) === "<3>" ![preorder_step3](binary_tree_traversal.assets/preorder_step3.png) === "<4>" ![preorder_step4](binary_tree_traversal.assets/preorder_step4.png) === "<5>" ![preorder_step5](binary_tree_traversal.assets/preorder_step5.png) === "<6>" ![preorder_step6](binary_tree_traversal.assets/preorder_step6.png) === "<7>" ![preorder_step7](binary_tree_traversal.assets/preorder_step7.png) === "<8>" ![preorder_step8](binary_tree_traversal.assets/preorder_step8.png) === "<9>" ![preorder_step9](binary_tree_traversal.assets/preorder_step9.png) === "<10>" ![preorder_step10](binary_tree_traversal.assets/preorder_step10.png) === "<11>" ![preorder_step11](binary_tree_traversal.assets/preorder_step11.png) ### 複雜度分析 - **時間複雜度為 $O(n)$** :所有節點被訪問一次,使用 $O(n)$ 時間。 - **空間複雜度為 $O(n)$** :在最差情況下,即樹退化為鏈結串列時,遞迴深度達到 $n$ ,系統佔用 $O(n)$ 堆疊幀空間。 ================================================ FILE: zh-hant/docs/chapter_tree/index.md ================================================ # 樹 ![樹](../assets/covers/chapter_tree.jpg) !!! abstract 參天大樹充滿生命力,根深葉茂,分枝扶疏。 它為我們展現了資料分治的生動形態。 ================================================ FILE: zh-hant/docs/chapter_tree/summary.md ================================================ # 小結 ### 重點回顧 - 二元樹是一種非線性資料結構,體現“一分為二”的分治邏輯。每個二元樹節點包含一個值以及兩個指標,分別指向其左子節點和右子節點。 - 對於二元樹中的某個節點,其左(右)子節點及其以下形成的樹被稱為該節點的左(右)子樹。 - 二元樹的相關術語包括根節點、葉節點、層、度、邊、高度和深度等。 - 二元樹的初始化、節點插入和節點刪除操作與鏈結串列操作方法類似。 - 常見的二元樹型別有完美二元樹、完全二元樹、完滿二元樹和平衡二元樹。完美二元樹是最理想的狀態,而鏈結串列是退化後的最差狀態。 - 二元樹可以用陣列表示,方法是將節點值和空位按層序走訪順序排列,並根據父節點與子節點之間的索引對映關係來實現指標。 - 二元樹的層序走訪是一種廣度優先搜尋方法,它體現了“一圈一圈向外擴展”的逐層走訪方式,通常透過佇列來實現。 - 前序、中序、後序走訪皆屬於深度優先搜尋,它們體現了“先走到盡頭,再回溯繼續”的走訪方式,通常使用遞迴來實現。 - 二元搜尋樹是一種高效的元素查詢資料結構,其查詢、插入和刪除操作的時間複雜度均為 $O(\log n)$ 。當二元搜尋樹退化為鏈結串列時,各項時間複雜度會劣化至 $O(n)$ 。 - AVL 樹,也稱平衡二元搜尋樹,它透過旋轉操作確保在不斷插入和刪除節點後樹仍然保持平衡。 - AVL 樹的旋轉操作包括右旋、左旋、先右旋再左旋、先左旋再右旋。在插入或刪除節點後,AVL 樹會從底向頂執行旋轉操作,使樹重新恢復平衡。 ### Q & A **Q**:對於只有一個節點的二元樹,樹的高度和根節點的深度都是 $0$ 嗎? 是的,因為高度和深度通常定義為“經過的邊的數量”。 **Q**:二元樹中的插入與刪除一般由一套操作配合完成,這裡的“一套操作”指什麼呢?可以理解為資源的子節點的資源釋放嗎? 拿二元搜尋樹來舉例,刪除節點操作要分三種情況處理,其中每種情況都需要進行多個步驟的節點操作。 **Q**:為什麼 DFS 走訪二元樹有前、中、後三種順序,分別有什麼用呢? 與順序和逆序走訪陣列類似,前序、中序、後序走訪是三種二元樹走訪方法,我們可以使用它們得到一個特定順序的走訪結果。例如在二元搜尋樹中,由於節點大小滿足 `左子節點值 < 根節點值 < 右子節點值` ,因此我們只要按照“左 $\rightarrow$ 根 $\rightarrow$ 右”的優先順序走訪樹,就可以獲得有序的節點序列。 **Q**:右旋操作是處理失衡節點 `node`、`child`、`grand_child` 之間的關係,那 `node` 的父節點和 `node` 原來的連線不需要維護嗎?右旋操作後豈不是斷掉了? 我們需要從遞迴的視角來看這個問題。右旋操作 `right_rotate(root)` 傳入的是子樹的根節點,最終 `return child` 返回旋轉之後的子樹的根節點。子樹的根節點和其父節點的連線是在該函式返回後完成的,不屬於右旋操作的維護範圍。 **Q**:在 C++ 中,函式被劃分到 `private` 和 `public` 中,這方面有什麼考量嗎?為什麼要將 `height()` 函式和 `updateHeight()` 函式分別放在 `public` 和 `private` 中呢? 主要看方法的使用範圍,如果方法只在類別內部使用,那麼就設計為 `private` 。例如,使用者單獨呼叫 `updateHeight()` 是沒有意義的,它只是插入、刪除操作中的一步。而 `height()` 是訪問節點高度,類似於 `vector.size()` ,因此設定成 `public` 以便使用。 **Q**:如何從一組輸入資料構建一棵二元搜尋樹?根節點的選擇是不是很重要? 是的,構建樹的方法已在二元搜尋樹程式碼中的 `build_tree()` 方法中給出。至於根節點的選擇,我們通常會將輸入資料排序,然後將中點元素作為根節點,再遞迴地構建左右子樹。這樣做可以最大程度保證樹的平衡性。 **Q**:在 Java 中,字串對比是否一定要用 `equals()` 方法? 在 Java 中,對於基本資料型別,`==` 用於對比兩個變數的值是否相等。對於引用型別,兩種符號的工作原理是不同的。 - `==` :用來比較兩個變數是否指向同一個物件,即它們在記憶體中的位置是否相同。 - `equals()`:用來對比兩個物件的值是否相等。 因此,如果要對比值,我們應該使用 `equals()` 。然而,透過 `String a = "hi"; String b = "hi";` 初始化的字串都儲存在字串常數池中,它們指向同一個物件,因此也可以用 `a == b` 來比較兩個字串的內容。 **Q**:廣度優先走訪到最底層之前,佇列中的節點數量是 $2^h$ 嗎? 是的,例如高度 $h = 2$ 的滿二元樹,其節點總數 $n = 7$ ,則底層節點數量 $4 = 2^h = (n + 1) / 2$ 。 ================================================ FILE: zh-hant/docs/index.html ================================================

動畫圖解、一鍵執行的資料結構與演算法教程

開始閱讀 GitHub

500 幅動畫圖解、14 種程式語言程式碼、3000 條社群問答,助你快速入門資料結構與演算法

推薦語

“一本通俗易懂的資料結構與演算法入門書,引導讀者手腦並用地學習,強烈推薦演算法初學者閱讀。”

—— 鄧俊輝,清華大學計算機系教授

“如果我當年學資料結構與演算法的時候有《Hello 演算法》,學起來應該會簡單 10 倍!”

—— 李沐,亞馬遜資深首席科學家

動畫圖解

內容清晰易懂,學習曲線平滑

"A picture is worth a thousand words."
“一圖勝千言”

一鍵執行

十餘種程式語言,程式碼視覺化執行

"Talk is cheap. Show me the code."
“少吹牛,看程式碼”

互助學習

歡迎討論與提問,讀者間攜手共進

"Learning by teaching."
“教學相長”

譯者

本書繁體中文版由以下譯者審閱,感謝他們的貢獻!

貢獻者

本書在開源社群 200 多位貢獻者的共同努力下不斷完善,感謝他們付出的時間與精力!

Contributors
================================================ FILE: zh-hant/docs/index.md ================================================ # Hello 演算法 動畫圖解、一鍵執行的資料結構與演算法教程。 [開始閱讀](chapter_hello_algo/) ================================================ FILE: zh-hant/mkdocs.yml ================================================ # Config inheritance INHERIT: ../mkdocs.yml # Project information site_name: Hello 演算法 site_url: https://www.hello-algo.com/zh-hant/ site_description: "動畫圖解、一鍵執行的資料結構與演算法教程" docs_dir: ../build/zh-hant/docs site_dir: ../site/zh-hant # Repository edit_uri: tree/main/zh-hant/docs version: 1.3.0 # Configuration theme: custom_dir: ../build/overrides language: zh-Hant font: text: Noto Sans TC palette: - scheme: default primary: white accent: teal toggle: icon: material/theme-light-dark name: 深色模式 - scheme: slate primary: black accent: teal toggle: icon: material/theme-light-dark name: 淺色模式 extra: status: new: 最近新增 # Page tree nav: - 序: - chapter_hello_algo/index.md - 第 0 章   前言: # [icon: material/book-open-outline] - chapter_preface/index.md - 0.1   關於本書: chapter_preface/about_the_book.md - 0.2   如何使用本書: chapter_preface/suggestions.md - 0.3   小結: chapter_preface/summary.md - 第 1 章   初識演算法: # [icon: material/calculator-variant-outline] - chapter_introduction/index.md - 1.1   演算法無處不在: chapter_introduction/algorithms_are_everywhere.md - 1.2   演算法是什麼: chapter_introduction/what_is_dsa.md - 1.3   小結: chapter_introduction/summary.md - 第 2 章   複雜度分析: # [icon: material/timer-sand] - chapter_computational_complexity/index.md - 2.1   演算法效率評估: chapter_computational_complexity/performance_evaluation.md - 2.2   迭代與遞迴: chapter_computational_complexity/iteration_and_recursion.md - 2.3   時間複雜度: chapter_computational_complexity/time_complexity.md - 2.4   空間複雜度: chapter_computational_complexity/space_complexity.md - 2.5   小結: chapter_computational_complexity/summary.md - 第 3 章   資料結構: # [icon: material/shape-outline] - chapter_data_structure/index.md - 3.1   資料結構分類: chapter_data_structure/classification_of_data_structure.md - 3.2   基本資料型別: chapter_data_structure/basic_data_types.md - 3.3   數字編碼 *: chapter_data_structure/number_encoding.md - 3.4   字元編碼 *: chapter_data_structure/character_encoding.md - 3.5   小結: chapter_data_structure/summary.md - 第 4 章   陣列與鏈結串列: # [icon: material/view-list-outline] - chapter_array_and_linkedlist/index.md - 4.1   陣列: chapter_array_and_linkedlist/array.md - 4.2   鏈結串列: chapter_array_and_linkedlist/linked_list.md - 4.3   串列: chapter_array_and_linkedlist/list.md # [status: new] - 4.4   記憶體與快取 *: chapter_array_and_linkedlist/ram_and_cache.md - 4.5   小結: chapter_array_and_linkedlist/summary.md - 第 5 章   堆疊與佇列: # [icon: material/stack-overflow] - chapter_stack_and_queue/index.md - 5.1   堆疊: chapter_stack_and_queue/stack.md - 5.2   佇列: chapter_stack_and_queue/queue.md - 5.3   雙向佇列: chapter_stack_and_queue/deque.md - 5.4   小結: chapter_stack_and_queue/summary.md - 第 6 章   雜湊表: # [icon: material/table-search] - chapter_hashing/index.md - 6.1   雜湊表: chapter_hashing/hash_map.md - 6.2   雜湊衝突: chapter_hashing/hash_collision.md - 6.3   雜湊演算法: chapter_hashing/hash_algorithm.md - 6.4   小結: chapter_hashing/summary.md - 第 7 章   樹: # [icon: material/graph-outline] - chapter_tree/index.md - 7.1   二元樹: chapter_tree/binary_tree.md - 7.2   二元樹走訪: chapter_tree/binary_tree_traversal.md - 7.3   二元樹陣列表示: chapter_tree/array_representation_of_tree.md - 7.4   二元搜尋樹: chapter_tree/binary_search_tree.md - 7.5   AVL *: chapter_tree/avl_tree.md - 7.6   小結: chapter_tree/summary.md - 第 8 章   堆積: # [icon: material/family-tree] - chapter_heap/index.md - 8.1   堆積: chapter_heap/heap.md - 8.2   建堆積操作: chapter_heap/build_heap.md - 8.3   Top-k 問題: chapter_heap/top_k.md - 8.4   小結: chapter_heap/summary.md - 第 9 章   圖: # [icon: material/graphql] - chapter_graph/index.md - 9.1   圖: chapter_graph/graph.md - 9.2   圖基礎操作: chapter_graph/graph_operations.md - 9.3   圖的走訪: chapter_graph/graph_traversal.md - 9.4   小結: chapter_graph/summary.md - 第 10 章   搜尋: # [icon: material/text-search] - chapter_searching/index.md - 10.1   二分搜尋: chapter_searching/binary_search.md - 10.2   二分搜尋插入點: chapter_searching/binary_search_insertion.md - 10.3   二分搜尋邊界: chapter_searching/binary_search_edge.md - 10.4   雜湊最佳化策略: chapter_searching/replace_linear_by_hashing.md - 10.5   重識搜尋演算法: chapter_searching/searching_algorithm_revisited.md - 10.6   小結: chapter_searching/summary.md - 第 11 章   排序: # [icon: material/sort-ascending] - chapter_sorting/index.md - 11.1   排序演算法: chapter_sorting/sorting_algorithm.md - 11.2   選擇排序: chapter_sorting/selection_sort.md - 11.3   泡沫排序: chapter_sorting/bubble_sort.md - 11.4   插入排序: chapter_sorting/insertion_sort.md - 11.5   快速排序: chapter_sorting/quick_sort.md - 11.6   合併排序: chapter_sorting/merge_sort.md - 11.7   堆積排序: chapter_sorting/heap_sort.md - 11.8   桶排序: chapter_sorting/bucket_sort.md - 11.9   計數排序: chapter_sorting/counting_sort.md - 11.10   基數排序: chapter_sorting/radix_sort.md - 11.11   小結: chapter_sorting/summary.md - 第 12 章   分治: # [icon: material/set-split] - chapter_divide_and_conquer/index.md - 12.1   分治演算法: chapter_divide_and_conquer/divide_and_conquer.md - 12.2   分治搜尋策略: chapter_divide_and_conquer/binary_search_recur.md - 12.3   構建樹問題: chapter_divide_and_conquer/build_binary_tree_problem.md - 12.4   河內塔問題: chapter_divide_and_conquer/hanota_problem.md - 12.5   小結: chapter_divide_and_conquer/summary.md - 第 13 章   回溯: # [icon: material/map-marker-path] - chapter_backtracking/index.md - 13.1   回溯演算法: chapter_backtracking/backtracking_algorithm.md - 13.2   全排列問題: chapter_backtracking/permutations_problem.md - 13.3   子集和問題: chapter_backtracking/subset_sum_problem.md - 13.4   N 皇后問題: chapter_backtracking/n_queens_problem.md - 13.5   小結: chapter_backtracking/summary.md - 第 14 章   動態規劃: # [icon: material/table-pivot] - chapter_dynamic_programming/index.md - 14.1   初探動態規劃: chapter_dynamic_programming/intro_to_dynamic_programming.md - 14.2   DP 問題特性: chapter_dynamic_programming/dp_problem_features.md - 14.3   DP 解題思路: chapter_dynamic_programming/dp_solution_pipeline.md - 14.4   0-1 背包問題: chapter_dynamic_programming/knapsack_problem.md - 14.5   完全背包問題: chapter_dynamic_programming/unbounded_knapsack_problem.md - 14.6   編輯距離問題: chapter_dynamic_programming/edit_distance_problem.md - 14.7   小結: chapter_dynamic_programming/summary.md - 第 15 章   貪婪: # [icon: material/head-heart-outline] - chapter_greedy/index.md - 15.1   貪婪演算法: chapter_greedy/greedy_algorithm.md - 15.2   分數背包問題: chapter_greedy/fractional_knapsack_problem.md - 15.3   最大容量問題: chapter_greedy/max_capacity_problem.md - 15.4   最大切分乘積問題: chapter_greedy/max_product_cutting_problem.md - 15.5   小結: chapter_greedy/summary.md - 第 16 章   附錄: # [icon: material/help-circle-outline] - chapter_appendix/index.md - 16.1   程式設計環境安裝: chapter_appendix/installation.md - 16.2   一起參與創作: chapter_appendix/contribution.md - 16.3   術語表: chapter_appendix/terminology.md - 參考文獻: - chapter_reference/index.md